pax_global_header00006660000000000000000000000064151046570240014516gustar00rootroot0000000000000052 comment=89196378a4a729ac0077c048214d5fc07d6c90c7 ROCm-AMDMIGraphX-46524e8/000077500000000000000000000000001510465702400145405ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/.azuredevops/000077500000000000000000000000001510465702400171655ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/.azuredevops/rocm-ci.yml000066400000000000000000000012631510465702400212430ustar00rootroot00000000000000resources: repositories: - repository: pipelines_repo type: github endpoint: ROCm name: ROCm/ROCm variables: - group: common - template: /.azuredevops/variables-global.yml@pipelines_repo trigger: batch: true branches: include: - develop - master paths: exclude: - .githooks - .github - docs - '.*.y*ml' - '*.md' - Jenkinsfile - LICENSE pr: autoCancel: true branches: include: - develop - master paths: exclude: - .github - docs - '.*.y*ml' - '*.md' - Jenkinsfile - LICENSE drafts: false jobs: - template: ${{ variables.CI_COMPONENT_PATH }}/AMDMIGraphX.yml@pipelines_repo ROCm-AMDMIGraphX-46524e8/.clang-format000066400000000000000000000051041510465702400171130ustar00rootroot00000000000000--- Language: Cpp AccessModifierOffset: 0 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: true AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: true AfterControlStatement: true AfterEnum: true AfterFunction: true AfterNamespace: false AfterObjCDeclaration: true AfterStruct: true AfterUnion: true BeforeCatch: true BeforeElse: true IndentBraces: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left ReflowComments: true SortIncludes: false SpaceAfterCStyleCast: false # SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: Never SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 8 UseTab: Never ... ROCm-AMDMIGraphX-46524e8/.clang-tidy000066400000000000000000000157351510465702400166070ustar00rootroot00000000000000CheckOptions: - key: bugprone-reserved-identifier.AllowedIdentifiers value: '__HIP_PLATFORM_AMD__;__HIP_ROCclr__' - key: bugprone-unused-return-value.CheckedFunctions value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty;::std::find;::std::find_if;::std::find_if_not;::std::all_of;::std::any_of;::std::none_of;::std::count;::std::count_if;::std::mismatch;::std::find_end;::std::find_first_of;::std::adjacent_find;::std::search;::std::search_n;::std::nth_element;::std::lower_bound;::std::upper_bound;::std::binary_search;::std::equal_range;::std::max;::std::max_element;::std::min;::std::min_element;::std::minmax;::std::minmax_element;::std::equal;::std::lexicographical_compare;::std::accumulate;::std::inner_product' - key: bugprone-unused-return-value.AllowCastToVoid value: true - key: cppcoreguidelines-macro-usage.AllowedRegexp value: 'DEBUG|ASSERT|ASSUME|UNREACHABLE|FALLTHROUGH|DEPRECATED|STRINGIZE|_HAS_|_THROW|_REQUIRES|_DECLARE_|_VISIT_|_REGISTER_|_GENERATE_|_DETAIL_|_TIDY_|_MANAGE_PTR|_MATCHER|DEVICE_SHARED|_WORKAROUND_|_PP_' - key: modernize-loop-convert.MinConfidence value: risky - key: modernize-loop-convert.NamingStyle value: lower_case - key: misc-const-correctness.AnalyzeValues value: 'false' - key: performance-unnecessary-copy-initialization.AllowedTypes value: 'shape;operation;iterator;literal;tensor_view;match' - key: performance-unnecessary-value-param.AllowedTypes value: 'shape;operation;iterator;literal;tensor_view;match' - key: readability-function-size.BranchThreshold value: '15' - key: readability-function-size.LineThreshold value: '350' - key: readability-function-size.NestingThreshold value: '5' - key: readability-function-size.ParameterThreshold value: '10' - key: readability-function-size.StatementThreshold value: '150' - key: readability-identifier-naming.NamespaceCase value: lower_case - key: readability-identifier-naming.InlineNamespaceCase value: UPPER_CASE - key: readability-identifier-naming.EnumConstantCase value: lower_case - key: readability-identifier-naming.ConstexprVariableCase value: lower_case - key: readability-identifier-naming.ConstantMemberCase value: lower_case - key: readability-identifier-naming.PrivateMemberCase value: lower_case - key: readability-identifier-naming.ProtectedMemberCase value: lower_case - key: readability-identifier-naming.PublicMemberCase value: lower_case - key: readability-identifier-naming.MemberCase value: lower_case - key: readability-identifier-naming.ClassConstantCase value: lower_case - key: readability-identifier-naming.ClassMemberCase value: lower_case - key: readability-identifier-naming.GlobalConstantCase value: lower_case - key: readability-identifier-naming.GlobalVariableCase value: lower_case - key: readability-identifier-naming.LocalConstantCase value: lower_case - key: readability-identifier-naming.LocalVariableCase value: lower_case - key: readability-identifier-naming.StaticConstantCase value: lower_case - key: readability-identifier-naming.StaticVariableCase value: lower_case - key: readability-identifier-naming.ConstantCase value: lower_case - key: readability-identifier-naming.VariableCase value: lower_case - key: readability-identifier-naming.ConstantParameterCase value: lower_case - key: readability-identifier-naming.ParameterPackCase value: lower_case - key: readability-identifier-naming.ParameterCase value: lower_case - key: readability-identifier-naming.AbstractClassCase value: lower_case - key: readability-identifier-naming.StructCase value: lower_case - key: readability-identifier-naming.ClassCase value: lower_case - key: readability-identifier-naming.UnionCase value: lower_case - key: readability-identifier-naming.EnumCase value: lower_case - key: readability-identifier-naming.GlobalFunctionCase value: lower_case - key: readability-identifier-naming.ConstexprFunctionCase value: lower_case - key: readability-identifier-naming.FunctionCase value: lower_case - key: readability-identifier-naming.ConstexprMethodCase value: lower_case - key: readability-identifier-naming.VirtualMethodCase value: lower_case - key: readability-identifier-naming.ClassMethodCase value: lower_case - key: readability-identifier-naming.PrivateMethodCase value: lower_case - key: readability-identifier-naming.ProtectedMethodCase value: lower_case - key: readability-identifier-naming.PublicMethodCase value: lower_case - key: readability-identifier-naming.MethodCase value: lower_case - key: readability-identifier-naming.TypedefCase value: lower_case - key: readability-identifier-naming.TypeTemplateParameterCase value: CamelCase - key: readability-identifier-naming.ValueTemplateParameterCase value: CamelCase - key: readability-identifier-naming.TemplateTemplateParameterCase value: CamelCase - key: readability-identifier-naming.TemplateParameterCase value: CamelCase - key: readability-identifier-naming.TypeAliasCase value: lower_case - key: readability-identifier-naming.MacroDefinitionCase value: UPPER_CASE - key: readability-identifier-naming.MacroDefinitionPrefix value: MIGRAPHX_ - key: readability-identifier-naming.ConstexprMethodIgnoredRegexp value: 'quiet_NaN|signaling_NaN' - key: readability-operators-representation.BinaryOperators value: 'and;or;not' - key: readability-operators-representation.OverloadedOperators value: 'and;or;not' ROCm-AMDMIGraphX-46524e8/.dockerignore000066400000000000000000000002161510465702400172130ustar00rootroot00000000000000# Ignore everything ** # Allow files and directories !*.txt !*.ini !/tools/*.txt !/tools/*.sh !/test/onnx/.onnxrt-commit !/docs/sphinx/*.txt ROCm-AMDMIGraphX-46524e8/.githooks/000077500000000000000000000000001510465702400164455ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/.githooks/install000077500000000000000000000002361510465702400200420ustar00rootroot00000000000000#!/usr/bin/env bash cd $(git rev-parse --git-dir) echo "Installing hooks..." mkdir -p hooks ln -s ../../.githooks/pre-commit hooks/pre-commit echo "Done!" ROCm-AMDMIGraphX-46524e8/.githooks/pre-commit000077500000000000000000000004131510465702400204450ustar00rootroot00000000000000#!/bin/sh # # This pre-commit hook checks if any versions of clang-format # are installed, and if so, uses the installed version to format # the staged changes. # Do everything from top - level cd $(git rev-parse --show-toplevel) python3 tools/format.py -q -i HEAD ROCm-AMDMIGraphX-46524e8/.github/000077500000000000000000000000001510465702400161005ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/.github/CODEOWNERS000066400000000000000000000001761510465702400174770ustar00rootroot00000000000000* @causten # Documentation files docs/* @ROCm/rocm-documentation *.md @ROCm/rocm-documentation *.rst @ROCm/rocm-documentation ROCm-AMDMIGraphX-46524e8/.github/ISSUE_TEMPLATE/000077500000000000000000000000001510465702400202635ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/.github/ISSUE_TEMPLATE/bug-dev-template.yaml000066400000000000000000000014771510465702400243220ustar00rootroot00000000000000name: Feature Development Template description: Use this template to report MIGraphX related bugs/features body: - type: textarea id: DOR attributes: label: "DOR (Definition of Ready)" placeholder: The minimum set of criteria that must be fulfilled before a story is ready to be included and worked on in an upcoming sprint. validations: required: true - type: textarea id: Description attributes: label: Description placeholder: As a [role], I want to [do something] so that [reason/benefit]. validations: required: true - type: textarea id: DOD attributes: label: "DOD (Definition of Done)" placeholder: The conditions that must be satisfied before deliverables can be considered fit for release / done. validations: required: true ROCm-AMDMIGraphX-46524e8/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000331510465702400222470ustar00rootroot00000000000000blank_issues_enabled: true ROCm-AMDMIGraphX-46524e8/.github/ISSUE_TEMPLATE/issue_report.yml000066400000000000000000000070721510465702400235370ustar00rootroot00000000000000name: Issue Report description: File a report for ROCm related issues on Linux and Windows. For issues pertaining to documentation or non-bug related, please open a blank issue located below. title: "[Issue]: " body: - type: markdown attributes: value: | Thank you for taking the time to fill out this report! You can acquire your OS, CPU, GPU (for filling out this report) with the following commands: Linux: echo "OS:" && cat /etc/os-release | grep -E "^(NAME=|VERSION=)"; echo "CPU: " && cat /proc/cpuinfo | grep "model name" | sort --unique; echo "GPU:" && /opt/rocm/bin/rocminfo | grep -E "^\s*(Name|Marketing Name)"; Windows: (Get-WmiObject Win32_OperatingSystem).Version (Get-WmiObject win32_Processor).Name (Get-WmiObject win32_VideoController).Name - type: textarea attributes: label: Problem Description description: Describe the issue you encountered. validations: required: true - type: input attributes: label: Operating System description: What is the name and version number of the OS? placeholder: "e.g. Ubuntu 22.04.3 LTS (Jammy Jellyfish)" validations: required: true - type: input attributes: label: CPU description: What CPU did you encounter the issue on? placeholder: "e.g. AMD Ryzen 9 5900HX with Radeon Graphics" validations: required: true - type: dropdown attributes: label: GPU description: What GPU(s) did you encounter the issue on (you can select multiple GPUs from the list) multiple: true options: - AMD Instinct MI300 - AMD Instinct MI300A - AMD Instinct MI300X - AMD Instinct MI250X - AMD Instinct MI250 - AMD Instinct MI210 - AMD Instinct MI100 - AMD Instinct MI50 - AMD Instinct MI25 - AMD Radeon Pro V620 - AMD Radeon Pro VII - AMD Radeon RX 7900 XTX - AMD Radeon VII - AMD Radeon Pro W7900 - AMD Radeon Pro W7800 - AMD Radeon Pro W6800 - AMD Radeon Pro W6600 - AMD Radeon Pro W5500 - AMD Radeon RX 7900 XT - AMD Radeon RX 7600 - AMD Radeon RX 6950 XT - AMD Radeon RX 6900 XT - AMD Radeon RX 6800 XT - AMD Radeon RX 6800 - AMD Radeon RX 6750 - AMD Radeon RX 6700 XT - AMD Radeon RX 6700 - AMD Radeon RX 6650 XT - AMD Radeon RX 6600 XT - AMD Radeon RX 6600 - Other validations: required: true - type: input attributes: label: Other description: If you selected Other, please specify - type: dropdown attributes: label: ROCm Version description: What version(s) of ROCm did you encounter the issue on? multiple: true options: - ROCm 6.0.0 - ROCm 5.7.1 - ROCm 5.7.0 - ROCm 5.6.0 - ROCm 5.5.1 - ROCm 5.5.0 validations: required: true - type: textarea attributes: label: Steps to Reproduce description: (Optional) Detailed steps to reproduce the issue. validations: required: false - type: textarea attributes: label: (Optional for Linux users) Output of /opt/rocm/bin/rocminfo --support description: The output of rocminfo --support could help to better address the problem. validations: required: false - type: textarea attributes: label: Additional Information description: (Optional) Any additional information that is relevant, e.g. relevant environment variables, dockerfiles, log files, dmesg output (on Linux), etc. validations: required: false ROCm-AMDMIGraphX-46524e8/.github/dependabot.yml000066400000000000000000000017141510465702400207330ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/docs/sphinx" # Location of package manifests schedule: interval: "daily" labels: - "documentation" - package-ecosystem: "pip" directory: "/tools/accuracy" schedule: interval: "daily" - package-ecosystem: "pip" directory: "/examples/vision/python_unet" schedule: interval: "daily" - package-ecosystem: "pip" directory: "/examples/vision/python_super_resolution" schedule: interval: "daily" - package-ecosystem: "pip" directory: "/" schedule: interval: "daily" ROCm-AMDMIGraphX-46524e8/.github/pull_request_template.md000066400000000000000000000012541510465702400230430ustar00rootroot00000000000000## Motivation ## Technical Details ## Changelog Category - - [ ] Added: New functionality. - - [ ] Changed: Changes to existing functionality. - - [ ] Removed: Functionality or support that has been removed. (Compared to a previous release) - - [ ] Optimized: Component performance that has been optimized or improved. - - [ ] Resolved Issues: Known issues from a previous version that have been resolved. - - [ ] Not Applicable: This PR is not to be included in the changelog. ROCm-AMDMIGraphX-46524e8/.github/workflows/000077500000000000000000000000001510465702400201355ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/.github/workflows/README.md000066400000000000000000000235551510465702400214260ustar00rootroot00000000000000# Workflows ## `add-to-project.yaml`

This workflow adds pull requests and issues to a specific GitHub project board when they are opened.

- ## Trigger The workflow is triggered by the following events: - A pull request being opened. - An issue being opened. - ## Jobs The workflow has a single job named `add-to-project`. The following step is executed in this job: - The `add-to-project` job uses the `actions/add-to-project@v0.4.0` action to add pull requests and issues to a specific project board. The `with` parameters are `project-url` and `github-token`, which specify the URL of the project board and the GitHub token used to authenticate the action. For more details, please refer to the [add-to-project.yaml](https://github.com/ROCm/AMDMIGraphX/blob/develop/.github/workflows/add-to-project.yaml) file in the repository. --- ## `benchmark.yaml`

This workflow runs the `MiGraphX performance benchmarks` and generates reports by comparing the results with the reference data.

- ## Trigger TODO: Update [benchmarks.yml (archived)](https://github.com/ROCmSoftwarePlatform/actions/blob/main/.github/workflows/benchmarks.yml) link after workflow is updated - The workflow is triggered manually through the "Run workflow" button in the Actions tab of the repository and it will run reusable workflow [benchmarks.yml (archived)](https://github.com/ROCmSoftwarePlatform/actions/blob/main/.github/workflows/benchmarks.yml) - ## Input Parameters The workflow uses the following input parameters: - `rocm_version`: the version of ROCm to use for running the benchmarks. - `script_repo`: repository that contains the benchmark scripts. - `result_path`: the path where benchmark results will be stored. - `result_repo`: the repository where the benchmark results will be pushed for comparison. For more details, please refer to the [benchmark.yaml](https://github.com/ROCm/AMDMIGraphX/blob/develop/.github/workflows/benchmark.yaml) file in the repository. --- ## `ci.yaml`

Overall, this workflow automates the process of building and testing the AMDMIGraphX project across multiple platforms and versions.

- ## Trigger The workflow is triggered by the following events: - A pull request being opened, synchronized or closed. - On push to the `develop`, `master`, and `release/**` branches. - ## Jobs The following jobs are executed in the workflow: - `cancel`: This job is responsible for canceling any previous runs of the workflow that may still be running. It runs on an `ubuntu-latest` runner and uses the `styfle/cancel-workflow-action` action to cancel any previous runs of the workflow. - `tidy`: It runs on an `ubuntu-20.04` runner and runs `clang-tidy` for the codebase in a Docker container with the MIGraphX build environment. - `cppcheck`: It runs on an `ubuntu-20.04` runner and performs static analysis on code in a Docker container, and caches the results for faster subsequent runs. - `format`: It runs on an `ubuntu-20.04` runner and includes steps for freeing up disk space, caching Docker layers, and checking code formatting. - `pyflakes`: It runs on an `ubuntu-20.04` runner and runs the Pyflakes static analysis tool to detect and report Python code issues. - `licensing`: It runs on an `ubuntu-20.04` runner and includes steps to free up space, checkout the code, set up Python and run a license check using a Python script. We have 2 jobs with multiple matrix configurations, both of them are running on `ubuntu-20.04` runner but right now only `linux` works on all 3 configurations (debug, release, codecov) ,`linux-fpga` works just on (debug). - `linux`: this job runs continuous integration tests for AMDMIGraphX on a Linux operating system. It tests a variety of build configurations to ensure code quality and compatibility. - `linux-fpga`: this job builds and tests AMDMIGraphX on a Linux operating system with support for FPGA acceleration. It includes additional steps to verify FPGA functionality and performance. For more details, please refer to the [ci.yaml](https://github.com/ROCm/AMDMIGraphX/blob/develop/.github/workflows/ci.yaml) file in the repository. --- ## `clean-closed-pr-caches.yaml`

This workflow has purpose to clean up any cached data related to the pull request.

- ## Trigger The workflow is triggered by the following events: - A pull request being closed. - ## Jobs The workflow has a single job named `cleanup`. The following steps are executed in this job: - `Check out code`: step checks out the codebase from the repository. - `Cleanup`: step performs the actual cache cleanup using a series of commands. For more details, please refer to the [clean-closed-pr-caches.yaml](https://github.com/ROCm/AMDMIGraphX/blob/develop/.github/workflows/clean-closed-pr-caches.yaml) file in the repository. --- ## `history.yaml`

This workflow generates a report of the MiGraphX benchmark results between two dates and sends it to a specified email address. The report is also uploaded to a specified repository.

- ## Trigger - The workflow is triggered manually through the "Run workflow" button in the Actions tab of the repository and it will run reusable workflow [history.yml](https://github.com/ROCm/migraphx-benchmark/blob/main/.github/workflows/history.yml) - ## Input Parameters The workflow requires the following inputs: - `start_date`: Start date for results analysis. - `end_date`: End date for results analysis. - `history_repo`: Repository for history results between dates. - `benchmark_utils_repo`: Repository where benchmark utils are stored. - `organization`: Organization based on which location of files will be different. For more details, please refer to the [history.yaml](https://github.com/ROCm/AMDMIGraphX/blob/develop/.github/workflows/history.yaml) file in the repository. --- ## `performance.yaml`

This workflow runs performance tests on the MIGraphX repository and generates a report of the results.

- ## Trigger The workflow will run reusable workflow [perf-test.yml](https://github.com/ROCm/migraphx-benchmark/blob/main/.github/workflows/perf-test.yml) by the following events: - Pull requests opened, synchronized or closed on the `develop` branch. - Schedule: Runs every day of the week from Monday to Saturday at 6:00 AM. - Manual trigger through the "Run workflow" button in the Actions tab of the repository. - ## Input Parameters The workflow requires the following inputs: - `rocm_release`: ROCm version to use for the performance tests. - `performance_reports_repo`: Repository where the performance reports are stored. - `benchmark_utils_repo`: Repository where the benchmark utilities are stored. - `organization`: Organization based on which location of files will be different. - `result_number`: Last N results. - `model_timeout`: If a model in the performance test script passes this threshold, it will be skipped. - `flags`: Command line arguments to be passed to the performance test script. Default is `-r`. For more details, please refer to the [performance.yaml](https://github.com/ROCm/AMDMIGraphX/blob/develop/.github/workflows/performance.yaml) file in the repository. --- ## `rocm-image-release.yaml`

This workflow builds a Docker image for a specified ROCm release version and pushes it to the specified repository. If image already exists nothing will happen, and there is also option to overwrite existing image.

- ## Trigger - The workflow is triggered manually through the "Run workflow" button in the Actions tab of the repository and it will run reusable workflow [rocm-release.yml](https://github.com/ROCm/migraphx-benchmark/blob/main/.github/workflows/rocm-release.yml) - ## Input Parameters The workflow requires the following inputs: - `rocm_release`: ROCm release version to build Docker image for. - `benchmark_utils_repo`: Repository where benchmark utils are stored. - `base_image`: Base image for ROCm Docker build. - `docker_image`: Docker image name for ROCm Docker build. - `build_navi`: Build number for the Navi architecture. - `organization`: The organization name used to determine the location of files. - `overwrite`: Specify whether to overwrite the Docker image if it already exists. For more details, please refer to the [rocm-image-release.yaml](https://github.com/ROCm/AMDMIGraphX/blob/develop/.github/workflows/rocm-image-release.yaml) file in the repository. --- ## `sync-onnxrt-main.yaml`

This workflow updates a file with the latest commit hash then creates a pull request using the updated commit hash and adds labels, assignees, reviewers, and a title and body to describe the changes.

- ## Trigger The workflow is triggered by the following events: - Schedule: Runs every week on Friday at 05:07 PM. - ## Jobs The workflow has a single job named `Update and create pull request`. The following steps are executed in this job: - `get_date`: step sets an environment variable to the current date in the format 'YYYY-MM-DD'. - `extract_sha1`: step fetches the latest SHA1 commit hash of the HEAD branch of the `microsoft/onnxruntime` repository and sets it as an environment variable. - `echo_sha1`: step prints the SHA1 commit hash set in step `extract_sha1`. - `actions/checkout@v4.1.1`: step checks out the codebase from the repository. - `update_file`: step updates a file in the repository with the SHA1 commit hash fetched in step `extract_sha1`. - `Make changes to pull request`: step uses the `peter-evans/create-pull-request` action to create a pull request. For more details, please refer to the [sync-onnxrt-main.yaml](https://github.com/ROCm/AMDMIGraphX/blob/develop/.github/workflows/sync-onnxrt-main.yaml) file in the repository. --- ROCm-AMDMIGraphX-46524e8/.github/workflows/add-to-project.yaml000066400000000000000000000006161510465702400236400ustar00rootroot00000000000000name: Add items to GH project on: pull_request: types: - opened issues: types: - opened jobs: add-to-project: name: Add PRs and issues to MIGX project runs-on: ubuntu-latest steps: - uses: actions/add-to-project@v0.4.0 with: project-url: https://github.com/orgs/ROCm/projects/3 github-token: ${{ secrets.TEST_PR_WORKFLOW }} ROCm-AMDMIGraphX-46524e8/.github/workflows/benchmark.yaml000066400000000000000000000006121510465702400227520ustar00rootroot00000000000000name: MiGraphX Benchmark on: workflow_dispatch: jobs: benchmark: uses: ROCmSoftwarePlatform/actions/.github/workflows/benchmarks.yml@main with: rocm_version: 7.1 script_repo: migraphx-benchmark/benchmark-utils result_path: /usr/share/migraphx/test-results result_repo: ROCm/comparison-results secrets: gh_token: ${{ secrets.MIGRAPHX_BOT_TOKEN }} ROCm-AMDMIGraphX-46524e8/.github/workflows/ci.yaml000066400000000000000000000525241510465702400214240ustar00rootroot00000000000000name: migraphx on: pull_request: push: branches: - develop - master - 'release/**' env: DOCKER_USER: ${{secrets.DOCKERHUB_USERID}} DOCKER_TOKEN: ${{secrets.DOCKERHUB_TOKEN}} DOCKER_IMAGE_UBUNTU: "rocm/migraphx-ci-ubuntu" DOCKER_IMAGE_SLES: "rocm/migraphx-ci-sles" jobs: cancel: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@0.12.1 with: access_token: ${{ github.token }} check_image: name: Check if image exists in registry runs-on: ubuntu-latest outputs: imageexists: ${{ steps.check_image.outputs.imageexists }} imagetag: ${{ steps.image_hash.outputs.imagetag }} imageexists_sles: ${{ steps.check_image.outputs.imageexists_sles }} imagetag_sles: ${{ steps.image_hash.outputs.imagetag_sles }} steps: - name: Checkout Code uses: actions/checkout@v4.2.2 - name: Create Image Tag id: image_hash run: | echo "imagetag=hip-clang-${{hashFiles('**/hip-clang.docker', '**/*requirements.txt', '**/requirements-py.txt', '**/install_prereqs.sh', '**/rbuild.ini')}}" >> $GITHUB_OUTPUT echo "imagetag_sles=hip-clang-${{hashFiles('**/tools/docker/sles.docker', '**/*requirements.txt','**/requirements-py.txt', '**/install_prereqs.sh', '**/rbuild.ini')}}" >> $GITHUB_OUTPUT - name: Check if image is built already id: check_image env: DOCKER_TAG_UBUNTU: ${{ steps.image_hash.outputs.imagetag }} DOCKER_TAG_SLES: ${{ steps.image_hash.outputs.imagetag_sles }} run: | if [[ "$(docker manifest inspect $DOCKER_IMAGE_UBUNTU:$DOCKER_TAG_UBUNTU 2> /dev/null)" != "" ]]; then echo "imageexists=true" >> $GITHUB_OUTPUT echo "Image already exists, skip building available" else echo "imageexists=false" >> $GITHUB_OUTPUT echo "Tag does not exist, build and publishing required" fi if [[ "$(docker manifest inspect $DOCKER_IMAGE_SLES:$DOCKER_TAG_SLES 2> /dev/null)" != "" ]]; then echo "imageexists_sles=true" >> $GITHUB_OUTPUT echo "SLES Image already exists, skip building available" else echo "imageexists_sles=false" >> $GITHUB_OUTPUT echo "SLES Tag does not exist, build and publishing required" fi build_image: name: Build image runs-on: ROCM-Ubuntu needs: check_image if: ${{ needs.check_image.outputs.imageexists != 'true' }} steps: - uses: actions/checkout@v4.2.2 - name: Build and publish env: DOCKER_TAG_UBUNTU: ${{ needs.check_image.outputs.imagetag }} run: | # The TOKEN and USERID are github secrets, Action failures at this step # can come from a PR from a fork changing a file which forces a rebuild # Resolve by making an internal PR of the Forked PR echo $DOCKER_TOKEN | docker login -u $DOCKER_USER --password-stdin docker pull $DOCKER_IMAGE_UBUNTU:latest || true docker build . --file hip-clang.docker --cache-from $DOCKER_IMAGE_UBUNTU:latest --tag $DOCKER_IMAGE_UBUNTU:$DOCKER_TAG_UBUNTU --tag $DOCKER_IMAGE_UBUNTU:latest; docker push $DOCKER_IMAGE_UBUNTU:$DOCKER_TAG_UBUNTU; docker push $DOCKER_IMAGE_UBUNTU:latest; build_SLES_image: name: Build SLES image runs-on: ROCM-Ubuntu needs: check_image if: ${{ needs.check_image.outputs.imageexists_sles != 'true' }} steps: - uses: actions/checkout@v4.2.2 - name: Build and publish SLES env: DOCKER_TAG_SLES: ${{ needs.check_image.outputs.imagetag_sles }} run: | # The TOKEN and USERID are github secrets, Action failures at this step # can come from a PR from a fork changing a file wichi forces a rebuild # Resolve by making an internal PR of the Forked PR echo $DOCKER_TOKEN | docker login -u $DOCKER_USER --password-stdin docker pull $DOCKER_IMAGE_SLES:latest || true docker build . --file ./tools/docker/sles.docker --cache-from $DOCKER_IMAGE_SLES:latest --tag $DOCKER_IMAGE_SLES:$DOCKER_TAG_SLES --tag $DOCKER_IMAGE_SLES:latest; docker push $DOCKER_IMAGE_SLES:$DOCKER_TAG_SLES; docker push $DOCKER_IMAGE_SLES:latest; tidy: runs-on: ROCM-Ubuntu needs: [ build_image, check_image ] env: DOCKER_TAG_UBUNTU: ${{ needs.check_image.outputs.imagetag }} if: ${{ !cancelled() && (needs.build_image.result == 'success' || needs.build_image.result == 'skipped') }} steps: - uses: actions/checkout@v4.2.2 - name: Restore cache files for tidy uses: actions/cache/restore@v4.2.0 id: tidy_restore with: path: tidy-cache key: tidy-cache-${{ github.ref }} restore-keys: tidy-cache- - name: Clang Tidy shell: bash -c "docker run -i -v=$GITHUB_WORKSPACE:/data -w /data $DOCKER_IMAGE_UBUNTU:$DOCKER_TAG_UBUNTU bash < {0}" run: | mkdir build cd build CXX=/opt/rocm/llvm/bin/clang++ CC=/opt/rocm/llvm/bin/clang cmake \ -DMIGRAPHX_ENABLE_GPU=On \ -DMIGRAPHX_ENABLE_CPU=On \ -DMIGRAPHX_ENABLE_FPGA=On \ -DBUILD_DEV=On \ -DROCM_ENABLE_GH_ANNOTATIONS=On \ -DCLANG_TIDY_DEPEND_ON_TARGET=Off \ -DCLANG_TIDY_CACHE=/data/tidy-cache \ -DGPU_TARGETS=gfx908 \ .. make -j$(nproc) -k onnx-proto tf-proto tidy # GH actions can not update existing cache, as a workaround clear cache and then save it - name: Clear tidy cache before saving continue-on-error: true if: ${{ steps.tidy_restore.outputs.cache-hit }} shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh extension install actions/gh-actions-cache --pin v1.0.1 gh actions-cache delete ${{ steps.tidy_restore.outputs.cache-matched-key }} --confirm - name: Save cache files for tidy uses: actions/cache/save@v4.2.0 if: always() with: path: tidy-cache key: tidy-cache-${{ github.ref }} cppcheck: runs-on: ROCM-Ubuntu needs: [ build_image, check_image ] env: DOCKER_TAG_UBUNTU: ${{ needs.check_image.outputs.imagetag }} if: ${{ !cancelled() && (needs.build_image.result == 'success' || needs.build_image.result == 'skipped') }} steps: - uses: actions/checkout@v4.2.2 - name: Restore cache files for cppcheck id: cppcheck_restore uses: actions/cache/restore@v4.2.0 with: path: cppcheck-cache key: cppcheck-cache-1-${{ hashFiles('tools/cppcheck/rules.xml', 'tools/cppcheck/migraphx.py', 'CMakeLists.txt') }}-${{ github.ref }} restore-keys: cppcheck-cache-1-${{ hashFiles('tools/cppcheck/rules.xml', 'tools/cppcheck/migraphx.py', 'CMakeLists.txt') }}- - name: Cppcheck shell: bash -c "docker run -i -v=$GITHUB_WORKSPACE:/data -w /data $DOCKER_IMAGE_UBUNTU:$DOCKER_TAG_UBUNTU bash < {0}" run: | mkdir build cd build CXX=/opt/rocm/llvm/bin/clang++ CC=/opt/rocm/llvm/bin/clang cmake \ -DCPPCHECK_BUILD_DIR=/data/cppcheck-cache \ -DBUILD_DEV=On \ -DROCM_ENABLE_GH_ANNOTATIONS=On \ -DGPU_TARGETS=gfx908 \ .. make -j$(nproc) cppcheck # GH actions can not update existing cache, as a workaround clear cache and then save it - name: Clear cppcheck cache before saving continue-on-error: true if: ${{ steps.cppcheck_restore.outputs.cache-hit }} shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh extension install actions/gh-actions-cache --pin v1.0.1 gh actions-cache delete ${{ steps.cppcheck_restore.outputs.cache-matched-key }} --confirm - name: Save cache files for cppcheck uses: actions/cache/save@v4.2.0 if: always() with: path: cppcheck-cache key: cppcheck-cache-${{ hashFiles('cppcheck.rules', 'CMakeLists.txt') }}-${{ github.ref }} format: runs-on: ubuntu-latest needs: [ build_image, check_image ] env: DOCKER_TAG_UBUNTU: ${{ needs.check_image.outputs.imagetag }} if: ${{ !cancelled() && (needs.build_image.result == 'success' || needs.build_image.result == 'skipped') }} steps: - uses: actions/checkout@v4.2.2 with: fetch-depth: 0 - name: Free space uses: jlumbroso/free-disk-space@main continue-on-error: true with: tool-cache: true android: true dotnet: true haskell: true large-packages: true swap-storage: true docker-images: true - name: Check formatting shell: bash -c "docker run -i -v=$GITHUB_WORKSPACE:/data -w /data $DOCKER_IMAGE_UBUNTU:$DOCKER_TAG_UBUNTU bash < {0}" run: | set -e git config --global --add safe.directory /data python3 tools/format.py origin/${{ github.event_name == 'pull_request' && github.base_ref || 'develop' }} sles: runs-on: ROCM-Ubuntu needs: [ build_SLES_image, check_image ] env: DOCKER_TAG_SLES: ${{ needs.check_image.outputs.imagetag_sles }} if: ${{ !cancelled() && (needs.build_SLES_image.result == 'success' || needs.build_SLES_image.result == 'skipped') }} steps: - uses: actions/checkout@v4.2.2 with: fetch-depth: 0 - name: Restore cache files for ccache uses: actions/cache/restore@v4.2.0 id: ccache_restore with: path: ${{ github.workspace }}/ccache key: ccache-sles-${{ github.ref }} restore-keys: ccache-sles- - name: Build migraphx shell: bash -c "docker run -i -v=$GITHUB_WORKSPACE:/data -w /data $DOCKER_IMAGE_SLES:$DOCKER_TAG_SLES bash < {0}" run: | set -e export CCACHE_COMPRESSLEVEL=10 export CCACHE_DIR=/data/ccache export CCACHE_NOHASHDIR=true export CCACHE_BASEDIR=/data export CCACHE_MAXSIZE=1 mkdir build cd build CXX=/opt/rocm/llvm/bin/clang++ CC=/opt/rocm/llvm/bin/clang cmake \ -DMIGRAPHX_DISABLE_LARGE_BUFFER_TESTS=On \ -DBUILD_DEV=On \ -DCMAKE_CXX_COMPILER_LAUNCHER=/usr/local/bin/ccache \ -DCMAKE_C_COMPILER_LAUNCHER=/usr/local/bin/ccache \ -DCMAKE_CXX_FLAGS="-Werror" \ -DGPU_TARGETS=gfx908 \ .. make -j$(nproc) tests driver - name: Clear ccache cache before saving continue-on-error: true if: ${{ steps.ccache_restore.outputs.cache-hit }} shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh extension install actions/gh-actions-cache --pin v1.0.1 gh actions-cache delete ${{ steps.ccache_restore.outputs.cache-matched-key }} --confirm - name: Save cache files for ccache uses: actions/cache/save@v4.2.0 if: always() with: path: ${{ github.workspace }}/ccache key: ccache-sles-${{ github.ref }} pyflakes: runs-on: ubuntu-24.04 steps: - name: Free space uses: jlumbroso/free-disk-space@main continue-on-error: true with: tool-cache: true android: true dotnet: true haskell: true large-packages: true swap-storage: true docker-images: true - uses: actions/checkout@v4.2.2 - name: Set up Python uses: actions/setup-python@v4 with: python-version: 3.12 - name: Install pyflakes run: pip install pyflakes==2.4.0 mypy==0.931 - name: Run pyflakes run: | pyflakes --version pyflakes examples/ tools/ src/ test/ docs/ mypy --version mypy tools/api.py licensing: runs-on: ubuntu-24.04 steps: - name: Free space uses: jlumbroso/free-disk-space@main continue-on-error: true with: tool-cache: true android: true dotnet: true haskell: true large-packages: true swap-storage: true docker-images: true - uses: actions/checkout@v4.2.2 with: fetch-depth: 0 # Fetch the entire repository history and all branches - name: Set up Python uses: actions/setup-python@v4 with: python-version: 3.12 - name: Run License Check run: python3 tools/check_stamped.py origin/${{ github.event_name == 'pull_request' && github.base_ref || 'develop' }} linux: runs-on: ${{ matrix.os }} env: CCACHE_COMPRESSLEVEL: 10 CCACHE_DIR: ${{github.workspace}}/ccache CCACHE_NOHASHDIR: true CCACHE_BASEDIR: ${{github.workspace}} CCACHE_MAXSIZE: 1 strategy: matrix: os: - ubuntu-24.04 configuration: - debug - release - codecov steps: - name: Free space uses: jlumbroso/free-disk-space@main continue-on-error: true with: tool-cache: true android: true dotnet: true haskell: true large-packages: true swap-storage: true docker-images: true - name: Set up Python uses: actions/setup-python@v4 with: python-version: 3.12 - name: Setup cmake uses: jwlawson/actions-setup-cmake@v1.9 with: cmake-version: 3.20.0 - name : Install rbuild and lld run: | sudo apt-get install -y lld python -m pip install --upgrade pip pip install https://github.com/RadeonOpenCompute/rbuild/archive/master.tar.gz - uses: actions/checkout@v4.2.2 - name: Cache dependencies # Ignore the failure of a step and avoid terminating the job. continue-on-error: true uses: actions/cache@v4.2.0 id: deps_cache with: # This path is specific to Ubuntu path: ${{ github.workspace }}/cget # Look to see if there is a cache hit for the corresponding requirements file key: ${{ matrix.os }}-cget-4-${{ hashFiles('requirements.txt', 'dev-requirements.txt', 'rbuild.ini') }} restore-keys: ${{ matrix.os }}-cget-4- - name: Install dependencies run: rbuild prepare -d cget -s gh - name: Restore cache files for ccache uses: actions/cache/restore@v4.2.0 id: ccache_restore with: path: ${{ github.workspace }}/ccache key: ${{ matrix.os }}-${{ matrix.configuration }}-ccache-${{ github.ref }} restore-keys: ${{ matrix.os }}-${{ matrix.configuration }}-ccache- - name: Build and test env: CMAKE_PREFIX_PATH: ${{ github.workspace }}/cget CCACHE_LOGFILE: /tmp/ccache.log CXXFLAGS: -Werror -pthread -fdebug-prefix-map=$PWD=. -fdebug-types-section -DMIGRAPHX_USE_TYPE_ERASED_MATCHERS=1 --param ggc-min-expand=5 --param ggc-min-heapsize=8192 run: | echo "leak:dnnl::impl::malloc" > suppressions.txt export LSAN_OPTIONS="suppressions=$(pwd)/suppressions.txt" rbuild build -d cget -s gh -T check \ -DCMAKE_BUILD_TYPE=${{matrix.configuration}} \ -DMIGRAPHX_ENABLE_PYTHON=${{matrix.configuration == 'release' && 'On' || 'Off'}} \ -DMIGRAPHX_DISABLE_LARGE_BUFFER_TESTS=On \ -DBUILD_DEV=On \ -DCMAKE_CXX_FLAGS_DEBUG="-g1 -Os -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" \ -DCMAKE_CXX_FLAGS_CODECOV="-g1 -Og -fprofile-arcs -ftest-coverage -fno-omit-frame-pointer" \ -DCMAKE_EXE_LINKER_FLAGS='-fuse-ld=lld' \ -DCMAKE_SHARED_LINKER_FLAGS='-fuse-ld=lld' ${{ github.workspace }}/cget/bin/ccache -s # GH actions can not update existing cache, as a workaround clear cache and then save it - name: Clear ccache cache before saving continue-on-error: true if: ${{ steps.ccache_restore.outputs.cache-hit }} shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh extension install actions/gh-actions-cache --pin v1.0.1 gh actions-cache delete ${{ steps.ccache_restore.outputs.cache-matched-key }} --confirm - name: Save cache files for ccache uses: actions/cache/save@v4.2.0 if: always() with: path: ${{ github.workspace }}/ccache key: ${{ matrix.os }}-${{ matrix.configuration }}-ccache-${{ github.ref }} - name: Upload code coverage if: "matrix.configuration == 'codecov'" env: CODECOV_TOKEN: "f5d5a10b-3177-4c76-b25f-9b1c2f165e8b" run: | sudo apt-get install -y lcov cd build lcov --directory . --capture --output-file $(pwd)/coverage.info --ignore-errors mismatch lcov --remove $(pwd)/coverage.info '/usr/*' --output-file $(pwd)/coverage.info lcov --list $(pwd)/coverage.info curl -Os https://uploader.codecov.io/latest/linux/codecov chmod +x codecov ./codecov -t ${CODECOV_TOKEN} echo "Uploaded" linux-fpga: continue-on-error: true runs-on: ${{ matrix.os }} env: CCACHE_COMPRESSLEVEL: 10 CCACHE_DIR: ${{github.workspace}}/ccache CCACHE_NOHASHDIR: true CCACHE_BASEDIR: ${{github.workspace}} CCACHE_MAXSIZE: 1 strategy: matrix: os: - ubuntu-24.04 configuration: - debug #- release Uncomment when ready to test release builds #- codecov Uncomment when ready for codecov steps: - name: Free space uses: jlumbroso/free-disk-space@main continue-on-error: true with: tool-cache: true android: true dotnet: true haskell: true large-packages: true swap-storage: true docker-images: true - uses: actions/checkout@v4.2.2 - name: Set up Python uses: actions/setup-python@v4 with: python-version: 3.12 - name: Cache dependencies # Ignore the failure of a step and avoid terminating the job. continue-on-error: true uses: actions/cache@v4.2.0 with: # This path is specific to Ubuntu path: ${{ github.workspace }}/cget # Look to see if there is a cache hit for the corresponding requirements file key: ${{ matrix.os }}-cget-4-${{ hashFiles('requirements.txt', 'dev-requirements.txt') }} restore-keys: ${{ matrix.os }}-cget-4- - name: Install dependencies run: | python -m pip install --upgrade pip pip install https://github.com/RadeonOpenCompute/rbuild/archive/master.tar.gz rbuild prepare -d cget -s gh sudo apt-get install -y lld - name: Restore cache files for ccache id: ccache_restore_fpga uses: actions/cache/restore@v4.2.0 with: path: ${{ github.workspace }}/ccache key: ${{ matrix.os }}-${{ matrix.configuration }}-ccache-${{ github.ref }} restore-keys: ${{ matrix.os }}-${{ matrix.configuration }}-ccache- - name: Build and test env: CMAKE_PREFIX_PATH: ${{ github.workspace }}/cget CCACHE_LOGFILE: /tmp/ccache.log CXXFLAGS: -Werror -pthread -DMIGRAPHX_USE_TYPE_ERASED_MATCHERS=1 --param ggc-min-expand=5 --param ggc-min-heapsize=8192 run: | echo "leak:dnnl::impl::malloc" > suppressions.txt export LSAN_OPTIONS="suppressions=$(pwd)/suppressions.txt" rbuild build -d cget -s gh -T check \ -DCMAKE_BUILD_TYPE=${{matrix.configuration}} \ -DMIGRAPHX_ENABLE_PYTHON=${{matrix.configuration == 'release' && 'On' || 'Off'}} \ -DMIGRAPHX_DISABLE_LARGE_BUFFER_TESTS=On \ -DBUILD_DEV=On \ -DCMAKE_CXX_FLAGS_DEBUG="-g1 -Os -fdebug-prefix-map=$PWD=. -fdebug-types-section -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" \ -DCMAKE_CXX_FLAGS_CODECOV="-g1 -Og -fdebug-prefix-map=$PWD=. -fdebug-types-section -fprofile-arcs -ftest-coverage -fno-omit-frame-pointer" \ -DCMAKE_EXE_LINKER_FLAGS='-fuse-ld=lld' \ -DCMAKE_SHARED_LINKER_FLAGS='-fuse-ld=lld' \ -DMIGRAPHX_ENABLE_FPGA=On ${{ github.workspace }}/cget/bin/ccache -s # this is a workaround, with GH actions can not update existing cache - name: Clear ccache cache before saving continue-on-error: true if: ${{ steps.ccache_restore_fpga.outputs.cache-hit }} shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh extension install actions/gh-actions-cache gh actions-cache delete ${{ steps.ccache_restore_fpga.outputs.cache-matched-key }} --confirm - name: Save cache files for ccache uses: actions/cache/save@v4.2.0 if: always() with: path: ${{ github.workspace }}/ccache key: ${{ matrix.os }}-${{ matrix.configuration }}-ccache-${{ github.ref }} #- name: Upload code coverage # if: "matrix.configuration == 'codecov'" # env: # CODECOV_TOKEN: "8545af1c-f90b-4345-92a5-0d075503ca56" # run: | # sudo apt-get install -y lcov # cd build # lcov --directory . --capture --output-file $(pwd)/coverage.info # lcov --remove $(pwd)/coverage.info '/usr/*' --output-file $(pwd)/coverage.info # lcov --list $(pwd)/coverage.info # curl -Os https://uploader.codecov.io/latest/linux/codecov # chmod +x codecov # ./codecov -t ${CODECOV_TOKEN} # echo "Uploaded" misspell: name: misspell runs-on: ubuntu-24.04 steps: - name: Check out code. uses: actions/checkout@v4 - name: misspell uses: reviewdog/action-misspell@v1 with: locale: "US" reporter: github-pr-check level: warning ROCm-AMDMIGraphX-46524e8/.github/workflows/clean-closed-pr-caches.yaml000066400000000000000000000016631510465702400252230ustar00rootroot00000000000000name: Cleanup caches of closed PR on: pull_request: types: - closed jobs: cleanup: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v4.2.2 - name: Cleanup run: | gh extension install actions/gh-actions-cache --pin v1.0.1 REPO=${{ github.repository }} BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" echo "Fetching list of cache key" cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 | tail -n +3) ## Setting this to not fail the workflow while deleting cache keys. set +e echo "Deleting caches..." for cacheKey in $cacheKeysForPR do gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm done echo "Done" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ROCm-AMDMIGraphX-46524e8/.github/workflows/config.md000066400000000000000000000020351510465702400217240ustar00rootroot00000000000000#=====ROCM INFO===== ROCM_VERSION : '7.1' #default ROCm version to be used ROCM_BASE_IMAGE : 'rocm/dev-ubuntu-22.04' #base image from dockerhub to be used ROCM_BUILT_IMAGE : 'rocm-migraphx' #name of the docker image built upon ROCm base USE_NAVI : '0' #disable NAVI in image build OVERWRITE_EXISTING : 'true' #building new ROCm image overwrites old with same version #=====REPOS INFO===== ORGANIZATION_REPO : 'AMD' BENCHMARK_UTILS_REPO : 'ROCm/migraphx-benchmark-utils' PERFORMANCE_REPORTS_REPO : 'ROCm/migraphx-reports' PERFORMANCE_BACKUP_REPO : 'migraphx-benchmark/performance-backup' #=====PERFORMANCE SCRIPT PARAMETERS===== RESULTS_TO_COMPARE : '10' #number of previous performance results to be used in calculations CALCULATION_METHOD_FLAG : '-r' #calculation method used in reporting, -m for Max value; -s for Std dev; -r for Threshold file PERFORMANCE_TEST_TIMEOUT : '30m' #timeout for each model after which test is aborted #===== W A R N I N G ===== #VARIABLE NAMES NOT TO BE CHANGED, VALUES ONLY! #VALUES MUST BE ENGLOSED IN SINGLE QUOTES! ROCm-AMDMIGraphX-46524e8/.github/workflows/history.yaml000066400000000000000000000026161510465702400225270ustar00rootroot00000000000000name: History on: workflow_dispatch: inputs: start_date: description: Start date for results analysis required: true default: 'yyyy-mm-dd' end_date: description: End date for results analysis required: true default: 'yyyy-mm-dd' history_repo: description: Repository for history results between dates required: true default: 'ROCm/migraphx-reports' benchmark_utils_repo: description: Repository where benchmark utils are stored required: true default: "ROCm/migraphx-benchmark-utils" organization: description: Organization based on which location of files will be different required: true default: "AMD" jobs: release: uses: ROCm/migraphx-benchmark/.github/workflows/history.yml@main with: start_date: ${{ github.event.inputs.start_date || 'yyyy-mm-dd' }} end_date: ${{ github.event.inputs.end_date || 'yyyy-mm-dd' }} history_repo: ${{ github.event.inputs.history_repo || 'ROCm/migraphx-reports' }} benchmark_utils_repo: ${{ github.event.inputs.benchmark_utils_repo || 'ROCm/migraphx-benchmark-utils' }} organization: ${{ github.event.inputs.organization || 'AMD' }} secrets: gh_token: ${{ secrets.MIGRAPHX_BOT_TOKEN }} mail_user: ${{ secrets.MAIL_USERNAME }} mail_pass: ${{ secrets.MAIL_PASSWORD }} ROCm-AMDMIGraphX-46524e8/.github/workflows/performance.yaml000066400000000000000000000111301510465702400233160ustar00rootroot00000000000000name: MIGraphX Performance Tests on: pull_request_target: branches: [develop] types: [opened, synchronize, closed] schedule: - cron: "0 7 * * 1-6" workflow_dispatch: inputs: rocm_release: description: ROCm Version required: true default: '7.1' performance_reports_repo: description: Repository where performance reports are stored required: true default: 'ROCm/migraphx-reports' benchmark_utils_repo: description: Repository where benchmark utils are stored required: true default: "ROCm/migraphx-benchmark-utils" organization: description: Organization based on which location of files will be different required: true default: "AMD" result_number: description: Last N results required: true default: '10' model_timeout: description: If model in performance test script passes this threshold, it will be skipped required: true default: '30m' performance_backup_repo: description: Repository for backup required: true default: migraphx-benchmark/performance-backup flags: description: -m for Max value; -s for Std dev; -r for Threshold file required: true default: '-r' concurrency: group: "perftest-${{ github.head_ref || github.base_ref || 'schedule' }}" cancel-in-progress: true jobs: get_config: runs-on: ubuntu-latest outputs: rocm_version: ${{ steps.read_config.outputs.rocm_version }} utils_repo: ${{ steps.read_config.outputs.utils_repo }} reports_repo: ${{ steps.read_config.outputs.reports_repo }} backup_repo: ${{ steps.read_config.outputs.backup_repo }} repo_org: ${{ steps.read_config.outputs.repo_org }} perf_number: ${{ steps.read_config.outputs.perf_number }} perf_flag: ${{ steps.read_config.outputs.perf_flag }} perf_timeout: ${{ steps.read_config.outputs.perf_timeout }} steps: - name: checkout uses: actions/checkout@v4.2.2 - name: read_config id: read_config run: | ROCM_VERSION=$(grep 'ROCM_VERSION' .github/workflows/config.md | cut -d "'" -f2) BENCHMARK_UTILS_REPO=$(grep 'BENCHMARK_UTILS_REPO' .github/workflows/config.md | cut -d "'" -f2) PERFORMANCE_REPORTS_REPO=$(grep 'PERFORMANCE_REPORTS_REPO' .github/workflows/config.md | cut -d "'" -f2) PERFORMANCE_BACKUP_REPO=$(grep 'PERFORMANCE_BACKUP_REPO' .github/workflows/config.md | cut -d "'" -f2) ORGANIZATION_REPO=$(grep 'ORGANIZATION_REPO' .github/workflows/config.md | cut -d "'" -f2) RESULTS_TO_COMPARE=$(grep 'RESULTS_TO_COMPARE' .github/workflows/config.md | cut -d "'" -f2) CALCULATION_METHOD_FLAG=$(grep 'CALCULATION_METHOD_FLAG' .github/workflows/config.md | cut -d "'" -f2) PERFORMANCE_TEST_TIMEOUT=$(grep 'PERFORMANCE_TEST_TIMEOUT' .github/workflows/config.md | cut -d "'" -f2) echo "rocm_version=$ROCM_VERSION" >> $GITHUB_OUTPUT echo "utils_repo=$BENCHMARK_UTILS_REPO" >> $GITHUB_OUTPUT echo "reports_repo=$PERFORMANCE_REPORTS_REPO" >> $GITHUB_OUTPUT echo "backup_repo=$PERFORMANCE_BACKUP_REPO" >> $GITHUB_OUTPUT echo "repo_org=$ORGANIZATION_REPO" >> $GITHUB_OUTPUT echo "perf_number=$RESULTS_TO_COMPARE" >> $GITHUB_OUTPUT echo "perf_flag=$CALCULATION_METHOD_FLAG" >> $GITHUB_OUTPUT echo "perf_timeout=$PERFORMANCE_TEST_TIMEOUT" >> $GITHUB_OUTPUT call_reusable: needs: get_config uses: ROCm/migraphx-benchmark/.github/workflows/perf-test.yml@main with: rocm_release: ${{ github.event.inputs.rocm_release || needs.get_config.outputs.rocm_version }} benchmark_utils_repo: ${{ github.event.inputs.benchmark_utils_repo || needs.get_config.outputs.utils_repo }} performance_reports_repo: ${{ github.event.inputs.performance_reports_repo || needs.get_config.outputs.reports_repo }} performance_backup_repo: ${{ github.event.inputs.performance_backup_repo || needs.get_config.outputs.backup_repo }} organization: ${{ github.event.inputs.organization || needs.get_config.outputs.repo_org }} result_number: ${{ github.event.inputs.result_number || needs.get_config.outputs.perf_number }} flags: ${{ github.event.inputs.flags || needs.get_config.outputs.perf_flag }} model_timeout: ${{ github.event.inputs.model_timeout || needs.get_config.outputs.perf_timeout }} secrets: gh_token: ${{ secrets.MIGRAPHX_BOT_TOKEN }} mail_user: ${{ secrets.MAIL_USERNAME }} mail_pass: ${{ secrets.MAIL_PASSWORD }} ROCm-AMDMIGraphX-46524e8/.github/workflows/rocm-image-release.yaml000066400000000000000000000031451510465702400244620ustar00rootroot00000000000000name: ROCM Docker image build on: workflow_dispatch: inputs: rocm_release: description: ROCm release version required: true benchmark-utils_repo: description: Repository for benchmark utils required: true default: 'ROCm/migraphx-benchmark-utils' base_image: description: Base image for rocm Docker build required: true default: "rocm/dev-ubuntu-22.04" docker_image: description: Docker image name for rocm Docker build required: true default: "rocm-migraphx" branch_name: description: branch to use for building base ROCm image required: true default: "develop" build_navi: description: Build navi number required: true default: "0" overwrite: type: boolean description: Overwrite image if it already exists required: true jobs: release: uses: ROCm/migraphx-benchmark/.github/workflows/rocm-release.yml@main with: rocm_release: ${{ github.event.inputs.rocm_release || '7.1' }} benchmark-utils_repo: ${{ github.event.inputs.benchmark-utils_repo || 'ROCm/migraphx-benchmark-utils' }} base_image: ${{ github.event.inputs.base_image || 'rocm/dev-ubuntu-22.04' }} docker_image: ${{ github.event.inputs.docker_image || 'rocm-migraphx' }} branch_name: ${{ github.event.inputs.branch_name || 'develop' }} build_navi: ${{ github.event.inputs.build_navi || '0' }} overwrite: ${{ github.event.inputs.overwrite == 'true' }} secrets: gh_token: ${{ secrets.MIGRAPHX_BOT_TOKEN }} ROCm-AMDMIGraphX-46524e8/.github/workflows/sync-onnxrt-main.yaml000066400000000000000000000034201510465702400242440ustar00rootroot00000000000000name: Onnxruntime main weekly sync on: schedule: - cron: '07 17 * * 5' jobs: createPullRequest: name: Update and create pull request runs-on: ubuntu-latest steps: - name: get_date run: echo todays_date="$(date +'%Y-%m-%d')" >> $GITHUB_ENV - name: extract_sha1 run: echo onnxsha="$(git ls-remote https://github.com/microsoft/onnxruntime.git HEAD | awk '{print $1}')" >> $GITHUB_ENV - name: echo_sha1 run: echo ${{ env.onnxsha }} - uses: actions/checkout@v4.2.2 with: ref: develop - name: update_file run: echo ${{ env.onnxsha }} > test/onnx/.onnxrt-commit - name: Make changes to pull request uses: peter-evans/create-pull-request@v4 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: Update onnxruntime main ${{ env.onnxsha }} committer: Github author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> signoff: false branch: onnxruntime-sync-${{ env.todays_date }} delete-branch: true title: 'Onnxruntime Weekly Sync ${{ env.todays_date }}' body: | Update CI point for Onnxruntime builds - Updated with changes from ${{ env.todays_date }} - Auto-generated by [create-pull-request][1] - Update .onnxrt-commit to ${{ env.onnxsha }} [1]: https://github.com/peter-evans/create-pull-request labels: | onnxruntime dependencies automated skip bot checks assignees: | TedThemistokleous reviewers: | TedThemistokleous causten draft: false base: develop ROCm-AMDMIGraphX-46524e8/.github/workflows/sync_rocMLIR.yaml000066400000000000000000000077041510465702400233340ustar00rootroot00000000000000name: rocMLIR sync with extended accuracy on: schedule: - cron: '0 7 * * sun' pull_request: branches: [rocMLIR-sync-*] types: [synchronize, closed] workflow_dispatch: inputs: rocm_release: type: string description: ROCm release version required: true default: '6.4.2' base_image: type: string description: Base image for ROCm Docker build required: true default: 'rocm/dev-ubuntu-22.04' docker_image: type: string description: Docker image name for rocm Docker build required: true default: 'rocm-migraphx' build_navi: type: string description: Build navi number required: true default: '0' benchmark_utils_repo: type: string description: Repository where benchmark utils are stored required: true default: 'ROCm/migraphx-benchmark-utils' performance_reports_repo: description: Repository where performance reports are stored required: true default: 'ROCm/migraphx-reports' organization: type: string description: Organization based on which location of files will be different required: true default: 'AMD' jobs: get_config: runs-on: ubuntu-latest outputs: rocm_version: ${{ steps.read_config.outputs.rocm_version }} rocm_base_image: ${{ steps.read_config.outputs.rocm_base_image }} rocm_built_image: ${{ steps.read_config.outputs.rocm_built_image }} use_navi: ${{ steps.read_config.outputs.use_navi }} utils_repo: ${{ steps.read_config.outputs.utils_repo }} reports_repo: ${{ steps.read_config.outputs.reports_repo }} repo_org: ${{ steps.read_config.outputs.repo_org }} steps: - name: checkout uses: actions/checkout@v4.2.2 - name: read_config id: read_config run: | ROCM_VERSION=$(grep 'ROCM_VERSION' .github/workflows/config.md | cut -d "'" -f2) ROCM_BASE_IMAGE=$(grep 'ROCM_BASE_IMAGE' .github/workflows/config.md | cut -d "'" -f2) ROCM_BUILT_IMAGE=$(grep 'ROCM_BUILT_IMAGE' .github/workflows/config.md | cut -d "'" -f2) BENCHMARK_UTILS_REPO=$(grep 'BENCHMARK_UTILS_REPO' .github/workflows/config.md | cut -d "'" -f2) PERFORMANCE_REPORTS_REPO=$(grep 'PERFORMANCE_REPORTS_REPO' .github/workflows/config.md | cut -d "'" -f2) ORGANIZATION_REPO=$(grep 'ORGANIZATION_REPO' .github/workflows/config.md | cut -d "'" -f2) USE_NAVI=$(grep 'USE_NAVI' .github/workflows/config.ymd | cut -d "'" -f2) echo "rocm_version=$ROCM_VERSION" >> $GITHUB_OUTPUT echo "rocm_base_image=$ROCM_BASE_IMAGE" >> $GITHUB_OUTPUT echo "rocm_built_image=$ROCM_BUILT_IMAGE" >> $GITHUB_OUTPUT echo "use_navi=$USE_NAVI" >> $GITHUB_OUTPUT echo "utils_repo=$BENCHMARK_UTILS_REPO" >> $GITHUB_OUTPUT echo "reports_repo=$PERFORMANCE_REPORTS_REPO" >> $GITHUB_OUTPUT echo "repo_org=$ORGANIZATION_REPO" >> $GITHUB_OUTPUT call_reusable: needs: get_config uses: ROCm/migraphx-benchmark/.github/workflows/rocMLIR_sync.yml@main with: rocm_release: ${{ github.event.inputs.rocm_release || needs.get_config.outputs.rocm_version }} base_image: ${{ github.event.inputs.base_image || needs.get_config.outputs.rocm_base_image }} docker_image: ${{ github.event.inputs.docker_image || needs.get_config.outputs.rocm_built_image }} build_navi: ${{ github.event.inputs.build_navi || needs.get_config.outputs.use_navi }} benchmark_utils_repo: ${{ github.event.inputs.benchmark_utils_repo || needs.get_config.outputs.utils_repo }} performance_reports_repo: ${{ github.event.inputs.performance_reports_repo || needs.get_config.outputs.reports_repo }} organization: ${{ github.event.inputs.organization || needs.get_config.outputs.repo_org }} secrets: gh_token: ${{ secrets.MIGRAPHX_BOT_TOKEN }} ROCm-AMDMIGraphX-46524e8/.github/workflows/unreleased_rocm.yaml000066400000000000000000000105741510465702400241770ustar00rootroot00000000000000name: Specifed Release ROCm MIGraphX Performance Test on: workflow_dispatch: inputs: unreleased_rocm: description: Specifed Release ROCm version required: true branch_name: description: Branch to use for building base ROCm image required: true default: 'develop' rocm_release: description: Use tuned MIOpen database for ROCm release required: true default: '7.1' performance_reports_repo: description: Repository where performance reports are stored required: true default: 'ROCm/migraphx-reports' benchmark_utils_repo: description: Repository where benchmark utils are stored required: true default: "ROCm/migraphx-benchmark-utils" organization: description: Organization based on which location of files will be different required: true default: "AMD" result_number: description: Last N results required: true default: '10' model_timeout: description: If model in performance test script passes this threshold, it will be skipped required: true default: '30m' flags: description: -m for Max value; -s for Std dev; -r for Threshold file required: true default: '-r' concurrency: group: "perftest-${{ github.head_ref || github.base_ref || 'schedule' }}" cancel-in-progress: true jobs: get_config: runs-on: ubuntu-latest outputs: rocm_version: ${{ steps.read_config.outputs.rocm_version }} utils_repo: ${{ steps.read_config.outputs.utils_repo }} reports_repo: ${{ steps.read_config.outputs.reports_repo }} repo_org: ${{ steps.read_config.outputs.repo_org }} perf_number: ${{ steps.read_config.outputs.perf_number }} perf_flag: ${{ steps.read_config.outputs.perf_flag }} perf_timeout: ${{ steps.read_config.outputs.perf_timeout }} steps: - name: checkout uses: actions/checkout@v4.2.2 - name: read_config id: read_config run: | ROCM_VERSION=$(grep 'ROCM_VERSION' .github/workflows/config.md | cut -d "'" -f2) BENCHMARK_UTILS_REPO=$(grep 'BENCHMARK_UTILS_REPO' .github/workflows/config.md | cut -d "'" -f2) PERFORMANCE_REPORTS_REPO=$(grep 'PERFORMANCE_REPORTS_REPO' .github/workflows/config.md | cut -d "'" -f2) ORGANIZATION_REPO=$(grep 'ORGANIZATION_REPO' .github/workflows/config.md | cut -d "'" -f2) RESULTS_TO_COMPARE=$(grep 'RESULTS_TO_COMPARE' .github/workflows/config.md | cut -d "'" -f2) CALCULATION_METHOD_FLAG=$(grep 'CALCULATION_METHOD_FLAG' .github/workflows/config.md | cut -d "'" -f2) PERFORMANCE_TEST_TIMEOUT=$(grep 'PERFORMANCE_TEST_TIMEOUT' .github/workflows/config.md | cut -d "'" -f2) echo "rocm_version=$ROCM_VERSION" >> $GITHUB_OUTPUT echo "utils_repo=$BENCHMARK_UTILS_REPO" >> $GITHUB_OUTPUT echo "reports_repo=$PERFORMANCE_REPORTS_REPO" >> $GITHUB_OUTPUT echo "repo_org=$ORGANIZATION_REPO" >> $GITHUB_OUTPUT echo "perf_number=$RESULTS_TO_COMPARE" >> $GITHUB_OUTPUT echo "perf_flag=$CALCULATION_METHOD_FLAG" >> $GITHUB_OUTPUT echo "perf_timeout=$PERFORMANCE_TEST_TIMEOUT" >> $GITHUB_OUTPUT call_reusable: needs: get_config uses: ROCm/migraphx-benchmark/.github/workflows/unreleased_rocm.yml@main with: unreleased_rocm: ${{ github.event.inputs.unreleased_rocm || '' }} branch_name: ${{ github.event.inputs.branch_name || 'develop' }} rocm_release: ${{ github.event.inputs.rocm_release || needs.get_config.outputs.rocm_version }} benchmark_utils_repo: ${{ github.event.inputs.benchmark_utils_repo || needs.get_config.outputs.utils_repo }} performance_reports_repo: ${{ github.event.inputs.performance_reports_repo || needs.get_config.outputs.reports_repo }} organization: ${{ github.event.inputs.organization || needs.get_config.outputs.repo_org }} result_number: ${{ github.event.inputs.result_number || needs.get_config.outputs.perf_number }} flags: ${{ github.event.inputs.flags || needs.get_config.outputs.perf_flag }} model_timeout: ${{ github.event.inputs.model_timeout || needs.get_config.outputs.perf_timeout }} secrets: gh_token: ${{ secrets.MIGRAPHX_BOT_TOKEN }} mail_user: ${{ secrets.MAIL_USERNAME }} mail_pass: ${{ secrets.MAIL_PASSWORD }} ROCm-AMDMIGraphX-46524e8/.github/workflows/weekly_master_sync.yaml000066400000000000000000000010261510465702400247270ustar00rootroot00000000000000name: Master weekly sync on: schedule: - cron: '0 15 * * sun' workflow_dispatch: jobs: SyncAndMerge: name: Sync master and merge develop runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 with: ref: develop fetch-depth: '0' - name: Merge Fast Forward Only env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git checkout master git merge origin/develop --ff-only git push origin HEAD ROCm-AMDMIGraphX-46524e8/.gitignore000066400000000000000000000031721510465702400165330ustar00rootroot00000000000000#==============================================================================# # File extensions to be ignored anywhere in the tree. #==============================================================================# # Temp files created by most text editors *~ # Merge files created by git *.orig # Byte compiled python modules *.pyc *.pyd # Vim swap files .*.sw? .sw? # Visual Studio .vs /.vscode/* # Sublime Text settings *.sublime-workspace *.sublime-project # Eclipse Project settings *.*project .settings # OS X specific files .DS_store #==============================================================================# # Explicit files to ignore (only matches one). #==============================================================================# # Various tags /tags /TAGS /GPATH /GRTAGS /GSYMS /GTAGS /ID .gitusers /compile_commands.json /CMakeSettings.json # documentation artifacts _toc.yml #==============================================================================# # Directories to ignore (do not add trailing '/'s, they skip symlinks). #==============================================================================# # Nested build directory /build* # Downloaded models test/onnx/models # VS2017 and VSCode config files. .vscode .vs # documentation artifacts docs/_build docs/_images docs/_static docs/_templates docs/doxygen/DoxygenWarningLog.txt docs/doxygen/html/ docs/doxygen/xml/ docs/_doxygen docs/html /_readthedocs # JetBrains config directories (ignoring symlinks) .idea/ cmake-build*/ build*/ # Recommended location to install rbuild dependencies from README.md depend*/ # local Python virtual environment .venv/ ROCm-AMDMIGraphX-46524e8/.readthedocs.yaml000066400000000000000000000005721510465702400177730ustar00rootroot00000000000000# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 sphinx: configuration: docs/conf.py formats: [htmlzip, pdf, epub] python: install: - requirements: docs/sphinx/requirements.txt build: os: ubuntu-22.04 tools: python: "mambaforge-22.9" conda: environment: docs/environment.yml ROCm-AMDMIGraphX-46524e8/CHANGELOG.md000066400000000000000000000616661510465702400163700ustar00rootroot00000000000000# Changelog for MIGraphX Full documentation for MIGraphX is available at [https://rocmdocs.amd.com/projects/AMDMIGraphX/en/latest/](https://rocmdocs.amd.com/projects/AMDMIGraphX/en/latest/). ## MIGraphX 2.14 for ROCm 7.1.1 ### Added ### Changed ### Fixed * Fixed error when running `make check` on systems running with a gfx1201 GPU (#4397) ### Optimized ### Removed ## MIGraphX 2.14 for ROCm 7.1.0 ### Added * Added Python 3.13 support. * Added PyTorch wheels to the Dockerfile. * Added Python API for returning serialized bytes. * Added `fixed_pad` operator for padding dynamic shapes to the maximum static shape. * Added matcher to upcast base `Softmax` operations. * Added support for the `convolution_backwards` operator through rocMLIR. * Added `LSE` output to attention fusion. * Added flags to `EnableControlFlowGuard` due to BinSkim errors. * Added new environment variable documentation and reorganized structure. * Added `stash_type` attribute for `LayerNorm` and expanded test coverage. * Added operator builders (phase 2). * Added `MIGRAPHX_GPU_HIP_FLAGS` to allow extra HIP compile flags. ### Changed * Updated C API to include `current()` caller information in error reporting. * Updated documentation dependencies: * **rocm-docs-core** bumped from 1.21.1 → 1.25.0 across releases. * **Doxygen** updated to 1.14.0. * **urllib3** updated from 2.2.2 → 2.5.0. * Updated `src/CMakeLists.txt` to support `msgpack` 6.x (`msgpack-cxx`). * Updated model zoo test generator to fix test issues and add summary logging. * Updated `rocMLIR` and `ONNXRuntime` mainline references across commits. * Updated module sorting algorithm for improved reliability. * Restricted FP8 quantization to `dot` and `convolution` operators. * Moved ONNX Runtime launcher script into MIGraphX and updated build scripts. * Simplified ONNX `Resize` operator parser for correctness and maintainability. * Updated `any_ptr` assertion to avoid failure on default HIP stream. * Print kernel and module information on compile failure. ### Fixed * Fixed error in `MIGRAPHX_GPU_COMPILE_PARALLEL` documentation (#4337). * Fixed rocMLIR `rewrite_reduce` issue (#4218). * Fixed bug with `invert_permutation` on GPU (#4194). * Fixed compile error when `MIOPEN` is disabled (missing `std` includes) (#4281). * Fixed ONNX `Resize` parsing when input and output shapes are identical (#4133, #4161). * Fixed issue with MHA in attention refactor (#4152). * Fixed synchronization issue from upstream ONNX Runtime (#4189). * Fixed spelling error in “Contiguous†(#4287). * Fixed tidy complaint about duplicate header (#4245). * Fixed `reshape`, `transpose`, and `broadcast` rewrites between pointwise and reduce operators (#3978). * Fixed extraneous include file in HIPRTC-based compilation (#4130). * Fixed CI Perl dependency issue for SLES builds (#4254). * Fixed compiler warnings for ROCm 7.0 of ``error: unknown warning option '-Wnrvo'`` (#4192). ### Optimized * Reduced nested visits in reference operators to improve compile time. * Avoided dynamic memory allocation during kernel launches. * Removed redundant NOP instructions for GFX11/12 platforms. * Improved `Graphviz` output (node color and layout updates). * Optimized interdependency checking during compilation. * Skip hipBLASLt solutions requiring workspace size larger than 128 MB for efficient memory utilization. ### Removed * Removed Perl dependency from SLES builds. * Removed redundant includes and unused internal dependencies. ## MIGraphX 2.13 for ROCm 7.0.0 ### Added * Support for OCP `FP8` on AMD Instinct MI350X accelerators. * Support for PyTorch 2.7 via Torch-MIGraphX. * Support for the Microsoft ONNX Contrib Operators (Self) Attention, RotaryEmbedding, QuickGelu, BiasAdd, BiasSplitGelu, SkipLayerNorm. * Support for Sigmoid and AddN TensorFlow operators. * Added GroupQuery Attention support for LLMs. * Added support for edge mode in the ONNX Pad operator. * Added ONNX runtime Python driver. * Added FLUX e2e example. * Added C++ and Python APIs to save arguments to a graph as a msgpack file, and then read the file back. * Added rocMLIR fusion for kv-cache attention. * Introduced a check for file-write errors. ### Changed * `quantize_bf16` for quantizing the model to `BF16` has been made visible in the MIGraphX user API. * Print additional kernel/module information in the event of compile failure. * Use hipBLASLt instead of rocBLAS on newer GPUs. * 1x1 convolutions are now rewritten to GEMMs. * `BF16::max` is now represented by its encoding rather than its expected value. * Direct warnings now go to `cout` rather `cerr`. * `FP8` uses hipBLASLt rather than rocBLAS. * ONNX models are now topologically sorted when nodes are unordered. * Improved layout of Graphviz output. * Enhanced debugging for migraphx-driver: consumed environment variables are printed, timestamps and duration are added to the summary. * Add a trim size flag to the verify option for migraphx-driver. * Node names are printed to track parsing within the ONNX graph when using the `MIGRAPHX_TRACE_ONNX_PARSER` flag. * Update accuracy checker to output test data with the `--show-test-data` flag. * The `MIGRAPHX_TRACE_BENCHMARKING` option now allows the problem cache file to be updated after finding the best solution. ### Removed * `ROCM_USE_FLOAT8` macro. * The BF16 GEMM test was removed for Navi21, as it is unsupported by rocBLAS and hipBLASLt on that platform. ### Optimized * Use common average in `compile_ops` to reduce run-to-run variations when tuning. * Improved the performance of the TopK operator. * Conform to a single layout (NHWC or NCHW) during compilation rather than combining two. * Slice Channels Conv Optimization (slice output fusion) * Horizontal fusion optimization after pointwise operations. * Reduced the number of literals used in `GridSample` linear sampler. * Fuse multiple outputs for pointwise operations. * Fuse reshapes on pointwise inputs for MLIR output fusion. * MUL operation not folded into the GEMM when the GEMM is used more than once. * Broadcast not fused after convolution or GEMM MLIR kernels. * Avoid reduction fusion when operator data-types mismatch. ### Resolved issues * Compilation workaround ICE in clang 20 when using `views::transform`. * Fix bug with `reshape_lazy` in MLIR. * Quantizelinear fixed for Nearbyint operation. * Check for empty strings in ONNX node inputs for operations like Resize. * Parse Resize fix: only check `keep_aspect_ratio_policy` attribute for sizes input. * Nonmaxsuppression: fixed issue where identical boxes/scores not ordered correctly. * Fixed a bug where events were created on the wrong device in a multi-gpu scenario. * Fixed out of order keys in value for comparisons and hashes when caching best kernels. * Fixed Controlnet MUL types do not match error. * Fixed check for scales if ROI input is present in Resize operation. * Einsum: Fixed a crash on empty squeeze operations. ## MIGraphX 2.12 for ROCm 6.4.0 ### Added * Support for gfx1200 and gfx1201 * hipBLASLt support for contiguous transpose GEMM fusion and GEMM pointwise fusions for improved performance * Support for hardware specific FP8 datatypes (FP8 OCP and FP8 FNUZ) * Add support for the BF16 datatype * ONNX Operator Support for `com.microsoft.MultiHeadAttention`, `com.microsoft.NhwcConv`, and `com.microsoft.MatMulIntgerFloat` * migraphx-driver can now produce output for use with Netron * migraphx-driver now includes a `time` parameter (similar to `perf`) that is more accurate for very fast kernels * An end-to-end Stable Diffusion 3 example with option to disable T5 encoder on VRAM-limited GPUs has been added * Added support to track broadcast axes in `shape_transform_descriptor` * Added support for unsigned types with `rocMLIR` * Added a script to convert mxr files to ONNX models * Added the `MIGRAPHX_SET_GEMM_PROVIDER` environment variable to choose between rocBLAS and hipBLASLt. Set `MIGRAPHX_SET_GEMM_PROVIDER` to `rocblas` to use rocBLAS, or to `hipblaslt` to use hipBLASLt. ### Changed * With the exception of gfx90a, switched to using hipBLASLt instead of rocBLAS * Included the min/max/median of the `perf` run as part of the summary report * Enable non-packed inputs for `rocMLIR` * Always output a packed type for q/dq after determining non-packed tensors were inefficient * Even if using NHWC, MIGraphX will always convert group convolutions to NCHW for best performance * Renamed the `layout_nhwc` to `layout_convolution` and ensured that either the weights are the same layout as the inputs or set the input and weights to NHWC * Minimum version of Cmake is now 3.27 ### Removed * Removed `fp8e5m2fnuz` rocBLAS support * `__AMDGCN_WAVEFRONT_SIZE` has been deprecated. * Removed a warning that printed to stdout when using FP8 types * Remove zero point parameter for dequantizelinear when its zero ### Optimized * Prefill buffers when MLIR produces a multioutput buffer * Improved the resize operator performance which should improve overall performance of models that use it * Allow the `reduce` operator to be split across an axis to improve fusion performance. The `MIGRAPHX_SPLIT_REDUCE_SIZE` environment variable has been added to allow the minimum size of the reduction to be adjusted for a possible model specific performance improvement * Added `MIGRAPHX_DISABLE_PASSES` environment variable for debugging * Added `MIGRAPHX_MLIR_DUMP` environment variable to be set to a folder where individual final rocMLIR modules can be saved for investigation * Improved the C++ API to allow onnxruntime access to fp8 quantization ### Resolved Issues * Fixed multistream execution with larger models (#3757) * Peephole LSTM Error (#3768) * Fixed BertSquad example that could include a broken tokenizers package (#3556) * Fixed Attention fusion ito not error with a shape mismatch when a trailing pointwise contains a literal (#3758) * Fixed instruction::replace() logic to handle more complex cases (#3574) * MatMulNBits could fail with a shape error (#3698) * Fixed a bug were some models could fail to compile with an error `flatten: Shapes are not in standard layout` (#3579) ## MIGraphX 2.11 for ROCm 6.3.0 ### Added * Initial code to run on Windows * Support for gfx120x GPU * Support for FP8, and INT4 * Support for the Log2 internal operator * Support for the GCC 14 compiler * The BitwiseAnd, Scan, SoftmaxCrossEntropyLoss, GridSample, and NegativeLogLikelihoodLoss ONNX operators * The MatMulNBits, QuantizeLinear/DequantizeLinear, GroupQueryAttention, SkipSimplifiedLayerNormalization, and SimpliedLayerNormalization Microsoft Contrib operators * Dymamic batch parameter support to OneHot operator * Split-K as an optional performance improvement * Scripts to validate ONNX models from the ONNX Model Zoo * GPU Pooling Kernel * --mlir flag to the migraphx-driver program to offload entire module to rocMLIR * Fusing split-reduce with MLIR * Multiple outputs for the MLIR + Pointwise fusions * Pointwise fusions with MLIR across reshape operations * MIGRAPHX_MLIR_DUMP environment variable to dump MLIR modules to MXRs * The 3 option to MIGRAPHX_TRACE_BENCHMARKING to print the MLIR program for improved debug output * MIGRAPHX_ENABLE_HIPBLASLT_GEMM environment variable to call hipBlasLt libaries * MIGRAPHX_VERIFY_DUMP_DIFF to improve the debugging of accuracy issues * reduce_any and reduce_all options to the Reduce operation via Torch MIGraphX * Examples for RNNT, and ControlNet ### Changed * Switched to MLIR's 3D Convolution operator. * MLIR is now used for Attention operations by default on gfx942 and newer ASICs. * Names and locations for VRM specific libraries have changed. * Use random mode for benchmarking GEMMs and convolutions. * Python version is now printed with an actual version number. ### Removed * Disabled requirements for MIOpen and rocBlas when running on Windows. * Removed inaccuracte warning messages when using exhaustive-tune. * Remove the hard coded path in MIGRAPHX_CXX_COMPILER allowing the compiler to be installed in different locations. ### Optimized * Improved: * Infrastructure code to enable better Kernel fusions with all supported data types * Subsequent model compile time by creating a cache for already performant kernels * Use of Attention fusion with models * Performance of the Softmax JIT kernel and of the Pooling opterator * Tuning operations through a new 50ms delay before running the next kernel * Performance of several convolution based models through an optimized NHWC layout * Performance for the FP8 datatype * GPU utilization * Verification tools * Debug prints * Documentation, including gpu-driver utility documentation * Summary section of the migrahx-driver perf command * Reduced model compilation time * Reordered some compiler passes to allow for more fusions * Preloaded tiles into LDS to improve performance of pointwise transposes * Exposed the external_data_path property in onnx_options to set the path from onnxruntime ### Resolved Issues * Fixed a bug with gfx1030 that overwrote dpp_reduce. * Fixed a bug in 1arg dynamic reshape that created a failure. * Fixed a bug with dot_broadcast and inner_broadcast that caused compile failures. * Fixed a bug where some configs were failing when using exhaustive-tune. * Fixed the ROCM Install Guide URL. * Fixed an issue while building a whl package due to an apostrophe. * Fixed the BERT Squad example requirements file to support different versions of Python. * Fixed a bug that stopped the Vicuna model from compiling. * Fixed failures with the verify option of migraphx-driver that would cause the application to exit early. ## MIGraphX 2.10 for ROCm 6.2.0 ### Additions * Added support for ONNX Runtime MIGraphX EP on Windows * Added FP8 Python API * Added examples for SD 2.1 and SDXL * Improved Dynamic Batch to support BERT * Added a `--test` flag in migraphx-driver to validate the installation * Added support for ONNX Operator: Einsum * Added uint8 support in ONNX Operators * Added fusion for group convolutions * Added rocMLIR conv3d support * Added rocgdb to the Dockerfile ### Optimizations * Improved ONNX Model Zoo coverage * Reorganized memcpys with ONNX Runtime to improve performance * Replaced scaler multibroadcast + unsqueeze with just a multibroadcast * Improved MLIR kernel selection for multibroadcasted GEMMs * Improved details of the perf report * Enable mlir by default for GEMMs with small K * Allow specifying dot or convolution fusion for mlir with environmental flag * Improve performance on small reductions by doing multiple reduction per wavefront * Add additional algebraic simplifications for mul-add-dot sequence of operations involving constants * Use MLIR attention kernels in more cases * Enables MIOpen and CK fusions for MI300 gfx arches * Support for QDQ quantization patterns from Brevitas which have explicit cast/convert nodes before and after QDQ pairs * Added Fusion of "contiguous + pointwise" and "layout + pointwise" operations which may result in performance gains in certain cases * Added Fusion for "pointwise + layout" and "pointwise + contiguous" operations which may result in performance gains when using NHWC layout * Added Fusion for "Pointwise + concat" operation which may help in performance in certain cases * Fixes a bug in "concat + pointwise" fusion where output shape memory layout wasn't maintained * Simplifies "slice + concat" pattern in SDXL UNet * eliminates ZeroPoint/Shift in QuantizeLinear or DeQuantizeLinear ops if zero points values are zeros * Improved inference performance by fusing Reduce to Broadcast * Added additional information when printing the perf report * Improve scalar fusions when not all strides are 0 * Added support for multi outputs in pointwise ops * Improve reduction fusion with reshape operators * Use the quantized output when an operator is used again ### Fixes * Super Resolution model verification failed with FP16 * Suppressed confusing messages when compiling the model * Mod operator failed to compile with int8 and int32 inputs * Prevented spawning too many threads for constant propagation when parallel STL is not enabled * Fixed a bug when running migraphx-driver with the --run 1 option * Layernorm Accuracy fix: calculations in FP32 * Update Docker generator script to ROCm 6.1 to point at Jammy * Floating Point exception fix for dim (-1) in reshape operator * Fixed issue with int8 accuracy and models which were failing due to requiring a fourth bias input * Fixed missing inputs not previously handled for quantized bias for the weights, and data values of the input matrix * Fixed order of operations for int8 quantization which were causing inaccuracies and slowdowns * Removed list initializer of prefix_scan_sum which was causing issues during compilation and resulting in the incorrect constructor to be used at compile * Fixed the MIGRAPHX_GPU_COMPILE_PARALLEL flag to enable users to control number of threads used for parallel compilation ### Changes * Changed default location of libraries with release specific ABI changes * Reorganized documentation in GitHub ### Removals * Removed the `--model` flag with migraphx-driver ## MIGraphX 2.9 for ROCm 6.1.0 ### Additions * Added beta version of FP8, functional, not performant * Created a dockerfile with MIGraphX+ONNX Runtime EP+Torch * Added support for the `Hardmax`, `DynamicQuantizeLinear`, `Qlinearconcat`, `Unique`, `QLinearAveragePool`, `QLinearSigmoid`, `QLinearLeakyRelu`, `QLinearMul`, `IsInf` operators * Created web site examples for `Whisper`, `Llama-2`, and `Stable Diffusion 2.1` * Created examples of using the ONNX Runtime MIGraphX Execution Provider with the `InceptionV3` and `Resnet50` models * Updated operators to support ONNX Opset 19 * Enable fuse_pointwise and fuse_reduce in the driver * Add support for dot-(mul)-softmax-dot offloads to MLIR * Added Blas auto-tuning for GEMMs * Added dynamic shape support for the multinomial operator * Added fp16 to accuracy checker * Added initial code for running on Windows OS ### Optimizations * Improved the output of migraphx-driver command * Documentation now shows all environment variables * Updates needed for general stride support * Enabled Asymmetric Quantization * Added ScatterND unsupported reduction modes * Rewrote softmax for better performance * General improvement to how quantization is performed to support INT8 * Used problem_cache for gemm tuning * Improved performance by always using rocMLIR for quantized convolution * Improved group convolutions by using rocMLIR * Improved accuracy of fp16 models * ScatterElements unsupported reduction * Added concat fusions * Improved INT8 support to include UINT8 * Allow reshape ops between dq and quant_op * Improve dpp reductions on navi * Have the accuracy checker print the whole final buffer * Added support for handling dynamic Slice and ConstantOfShape ONNX operators * Add support for the dilations attribute to Pooling ops * Add layout attribute support for LSTM operator * Improved performance by removing contiguous for reshapes * Handle all slice input variations * Add scales attribute parse in upsample for older opset versions * Added support for uneven Split operations * Improved unit testing to run in python virtual environments ### Fixes * Fixed outstanding issues in autogenerated documentation * Update model zoo paths for examples * Fixed promote_literals_test by using additional if condition * Fixed export API symbols from dynamic library * Fixed bug in pad operator from dimension reduction * Fixed using the LD to embed files and enable by default when building shared libraries on linux * fixed get_version() * Fixed Round operator inaccuracy * Fixed wrong size check when axes not present for slice * Set the .SO version correctly ### Changes * Cleanup LSTM and RNN activation functions * Placed gemm_pointwise at a higher priority than layernorm_pointwise * Updated README to mention the need to include GPU_TARGETS when building MIGraphX ### Removals * Removed unused device kernels from Gather and Pad operators * Removed int8x4 format ## MIGraphX 2.8 for ROCm 6.0.0 ### Additions * Support for MI300 GPUs * Support for TorchMIGraphX via PyTorch * Boosted overall performance by integrating rocMLIR * INT8 support for ONNX Runtime * Support for ONNX version 1.14.1 * Added new operators: `Qlinearadd`, `QlinearGlobalAveragePool`, `Qlinearconv`, `Shrink`, `CastLike`, and `RandomUniform` * Added an error message for when `gpu_targets` is not set during MIGraphX compilation * Added parameter to set tolerances with `migraphx-driver` verify * Added support for MXR files > 4 GB * Added `MIGRAPHX_TRACE_MLIR` flag * BETA added capability for using ROCm Composable Kernels via the `MIGRAPHX_ENABLE_CK=1` environment variable ### Optimizations * Improved performance support for INT8 * Improved time precision while benchmarking candidate kernels from CK or MLIR * Removed contiguous from reshape parsing * Updated the `ConstantOfShape` operator to support Dynamic Batch * Simplified dynamic shapes-related operators to their static versions, where possible * Improved debugging tools for accuracy issues * Included a print warning about `miopen_fusion` while generating `mxr` * General reduction in system memory usage during model compilation * Created additional fusion opportunities during model compilation * Improved debugging for matchers * Improved general debug messages ### Fixes * Fixed scatter operator for nonstandard shapes with some models from ONNX Model Zoo * Provided a compile option to improve the accuracy of some models by disabling Fast-Math * Improved layernorm + pointwise fusion matching to ignore argument order * Fixed accuracy issue with `ROIAlign` operator * Fixed computation logic for the `Trilu` operator * Fixed support for the DETR model ### Changes * Changed MIGraphX version to 2.8 * Extracted the test packages into a separate deb file when building MIGraphX from source ### Removals * Removed building Python 2.7 bindings ## MIGraphX 2.7 for ROCm 5.7.0 ### Additions * hipRTC no longer requires dev packages for MIGraphX runtime and allows the ROCm install to be in a different directory than build time * Added support for multi-target execution * Added Dynamic Batch support with C++/Python APIs * Added `migraphx.create_argument` to Python API * Added dockerfile example for Ubuntu 22.04 * Added TensorFlow supported ops in driver similar to exist onnx operator list * Added a MIGRAPHX_TRACE_MATCHES_FOR env variable to filter the matcher trace * Improved debugging by printing max,min,mean and stddev values for TRACE_EVAL = 2 * You can now use the ` fast_math` flag instead of `ENV` for GELU * Print message from driver if offload copy is set for compiled program ### Optimizations * Optimized for ONNX Runtime 1.14.0 * Improved compile times by only building for the GPU on the system * Improved performance of pointwise/reduction kernels when using NHWC layouts * Loaded specific version of the `migraphx_py` library * Annotated functions with the block size so the compiler can do a better job of optimizing * Enabled reshape on nonstandard shapes * Used half HIP APIs to compute max and min * Added support for broadcasted scalars to unsqueeze operator * Improved multiplies with dot operator * Handled broadcasts across dot and concat * Added verify namespace for better symbol resolution ### Fixes * Resolved accuracy issues with FP16 resnet50 * Updated cpp generator to handle inf from float * Fixed assertion error during verify and made DCE work with tuples * Fixed convert operation for NaNs * Fixed shape typo in API test * Fixed compile warnings for shadowing variable names * Added missing specialization for the `nullptr` hash function ### Changees * Bumped version of half library to 5.6.0 * Bumped CI to support ROCm 5.6 * Made building tests optional * Replaced `np.bool` with `bool` per NumPy request ### Removals * Removed int8x4 rocBlas calls due to deprecation * Removed `std::reduce` usage because not all operating systems support it ## MIGraphX 2.5 for ROCm 5.5.0 ### Additions * Y-Model feature will store tuning information with the optimized model * Added Python 3.10 bindings * Accuracy checker tool based on ONNX runtime * ONNX operators parse_split, and Trilu * Build support for ROCm MLIR * Added the `migraphx-driver` flag to print optimizations in Python (--python) * Added JIT implementation of the Gather and Pad operators, which results in better handling for larger tensor sizes ### Optimizations * Improved performance of Transformer-based models * Improved performance of the `Pad`, `Concat`, `Gather`, and `Pointwise` operators * Improved ONNX/pb file loading speed * Added a general optimize pass that runs several passes, such as `simplify_reshapes`, algebra, and DCE in a loop ### Fixes * Improved parsing for TensorFlow Protobuf files * Resolved various accuracy issues with some ONNX models * Resolved a gcc-12 issue with MIVisionX * Improved support for larger sized models and batches * Use `--offload-arch` instead of `--cuda-gpu-arch` for the HIP compiler * Changes inside JIT to use float accumulator for large reduce ops of half type to avoid overflow * Changes inside JIT to temporarily use cosine to compute sine function ### Changes * Changed version and location of third-party build dependencies in order to pick up fixes ROCm-AMDMIGraphX-46524e8/CMakeLists.txt000066400000000000000000000324771510465702400173150ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### cmake_minimum_required(VERSION 3.15 FATAL_ERROR) if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message(FATAL_ERROR "The binary and source directroy cannot be the same") endif() # Setup valid strings for build type if (NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel" CACHE STRING "Configs") endif() get_property(MIGRAPHX_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) # This has to be initialized before the project() command appears # Set the default of CMAKE_BUILD_TYPE to be release, unless user specifies with -D. MSVC_IDE does not use CMAKE_BUILD_TYPE if(NOT MIGRAPHX_GENERATOR_IS_MULTI_CONFIG) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel.") set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${CMAKE_CONFIGURATION_TYPES}) endif() if(NOT WIN32) set(CMAKE_INSTALL_PREFIX "/opt/rocm" CACHE PATH "") set(CMAKE_BUILD_RPATH "${CMAKE_BINARY_DIR}/lib") endif() list(APPEND CMAKE_PREFIX_PATH /opt/rocm /opt/rocm/llvm $ENV{ROCM_PATH} $ENV{HIP_PATH}) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) project(migraphx LANGUAGES C CXX) include(CTest) find_package(ROCmCMakeBuildTools REQUIRED) find_package(Threads REQUIRED) option(MIGRAPHX_ENABLE_PYTHON "Enable python bindings" ON) if(WIN32) option(MIGRAPHX_USE_MIOPEN "Enable MIGraphX to use MIOpen" OFF) else() option(MIGRAPHX_USE_MIOPEN "Enable MIGraphX to use MIOpen" ON) endif() option(MIGRAPHX_USE_ROCBLAS "Enable MIGraphX to use rocBLAS" ON) option(MIGRAPHX_USE_HIPBLASLT "Enable MIGraphX to use hipBLASLt" ON) if(MIGRAPHX_USE_HIPBLASLT AND NOT MIGRAPHX_USE_ROCBLAS) message(FATAL_ERROR "hipBLASLt requires rocBLAS, but MIGRAPHX_USE_ROCBLAS is OFF") endif() # By default build shared libraries option(BUILD_SHARED_LIBS "Create shared libraries" ON) if(WIN32) # CK is not yet ported to Windows option(MIGRAPHX_USE_COMPOSABLEKERNEL "Enable MIGraphX to use composable kernel JIT library" OFF) else() option(MIGRAPHX_USE_COMPOSABLEKERNEL "Enable MIGraphX to use composable kernel JIT library" ON) endif() find_path(HALF_INCLUDE_DIR half.hpp PATH_SUFFIXES half) if (NOT HALF_INCLUDE_DIR) message(FATAL_ERROR "Could not find half.hpp - Please check that the install path of half.hpp has been added to CMAKE_PREFIX_PATH") else() message(STATUS "half.hpp is at ${HALF_INCLUDE_DIR}") endif() include(CheckTypeSize) set(CMAKE_REQUIRED_INCLUDES ${HALF_INCLUDE_DIR}) set(CMAKE_EXTRA_INCLUDE_FILES half.hpp) check_type_size("half_float::detail::expr" HALF_EXPR LANGUAGE CXX) set(CMAKE_REQUIRED_INCLUDES) set(CMAKE_EXTRA_INCLUDE_FILES) include(ROCMSetupVersion) option(BUILD_DEV "Build for development purpose only" OFF) rocm_setup_version(VERSION 2.14.0) math(EXPR MIGRAPHX_SO_MAJOR_VERSION "(${PROJECT_VERSION_MAJOR} * 1000 * 1000) + (${PROJECT_VERSION_MINOR} * 1000) + ${PROJECT_VERSION_PATCH}") set(MIGRAPHX_SO_VERSION ${MIGRAPHX_SO_MAJOR_VERSION}.0) option( BUILD_SHARED_LIBS "Build as a shared library" ON ) include(CheckCXXCompilerFlag) check_cxx_compiler_flag("--cuda-host-only -x hip" HAS_HIP) if(HAS_HIP) message(STATUS "Enable gpu backend") set(MIGRAPHX_ENABLE_GPU On CACHE BOOL "") else() set(MIGRAPHX_ENABLE_GPU Off CACHE BOOL "") endif() # Disable cpu backend by default set(MIGRAPHX_ENABLE_CPU Off CACHE BOOL "") # Disable fpga backend by default set(MIGRAPHX_ENABLE_FPGA Off CACHE BOOL "") set(MIGRAPHX_HAS_EXECUTORS_DEFAULT Off) find_package(ParallelSTL QUIET) if(ParallelSTL_FOUND) set(MIGRAPHX_HAS_EXECUTORS_DEFAULT On) endif() option(MIGRAPHX_HAS_EXECUTORS "C++ supports parallel executors" ${MIGRAPHX_HAS_EXECUTORS_DEFAULT}) if(MIGRAPHX_HAS_EXECUTORS AND ParallelSTL_USES_TBB) list(APPEND PACKAGE_DEPENDS libtbb2) endif() if(WIN32) add_compile_definitions("$<$:_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES>") endif() set(CMAKE_CXX_STANDARD_DEFAULT "") if(MSVC) add_compile_options($<$:/std:c++17>) else() add_compile_options($<$:-std=c++17>) endif() include(EnableCompilerWarnings) include(ROCMClangTidy) if(CMAKE_CXX_COMPILER MATCHES ".*clang\\+\\+.*") set(MIGRAPHX_TIDY_ERRORS ERRORS * -readability-inconsistent-declaration-parameter-name) # Enable tidy on hip elseif(MIGRAPHX_ENABLE_GPU) set(MIGRAPHX_TIDY_ERRORS ALL) endif() rocm_enable_clang_tidy( CHECKS bugprone-* cert-* clang-analyzer-* clang-diagnostic-* cppcoreguidelines-* google-* hicpp-multiway-paths-covered hicpp-signed-bitwise llvm-namespace-comment misc-* -misc-confusable-identifiers -misc-use-anonymous-namespace modernize-* performance-* readability-* -bugprone-crtp-constructor-accessibility -bugprone-easily-swappable-parameters -bugprone-implicit-widening-of-multiplication-result -bugprone-macro-parentheses -bugprone-nondeterministic-pointer-iteration-order -bugprone-return-const-ref-from-parameter -bugprone-signed-char-misuse -bugprone-unchecked-optional-access # Disable the aliased reserved identifiers -cert-dcl37-c -cert-dcl51-cpp -cert-err33-c -cert-str34-c # We seed random numbers with constants for reproducibility -cert-msc32-c -cert-msc51-cpp # Disable all alpha checks by default -clang-analyzer-alpha* # Enable some alpha checks clang-analyzer-alpha.core.CallAndMessageUnInitRefArg clang-analyzer-alpha.core.Conversion clang-analyzer-alpha.core.IdenticalExpr clang-analyzer-alpha.core.PointerArithm clang-analyzer-alpha.core.PointerSub clang-analyzer-alpha.core.TestAfterDivZero clang-analyzer-alpha.cplusplus.InvalidIterator clang-analyzer-alpha.cplusplus.IteratorRange clang-analyzer-alpha.cplusplus.MismatchedIterator clang-analyzer-alpha.cplusplus.MisusedMovedObject -bugprone-switch-missing-default-case -bugprone-empty-catch -clang-analyzer-optin.performance.Padding -clang-diagnostic-deprecated-declarations -clang-diagnostic-disabled-macro-expansion -clang-diagnostic-extern-c-compat -clang-diagnostic-unused-command-line-argument -cppcoreguidelines-avoid-const-or-ref-data-members -cppcoreguidelines-avoid-do-while -cppcoreguidelines-explicit-virtual-functions -cppcoreguidelines-init-variables -cppcoreguidelines-misleading-capture-default-by-value -cppcoreguidelines-missing-std-forward -cppcoreguidelines-pro-bounds-array-to-pointer-decay -cppcoreguidelines-pro-bounds-constant-array-index -cppcoreguidelines-pro-bounds-pointer-arithmetic -cppcoreguidelines-pro-type-member-init -cppcoreguidelines-pro-type-reinterpret-cast -cppcoreguidelines-pro-type-union-access -cppcoreguidelines-pro-type-vararg -cppcoreguidelines-special-member-functions -cppcoreguidelines-use-default-member-init -cppcoreguidelines-virtual-class-destructor -google-readability-* -google-runtime-int -google-runtime-references -misc-include-cleaner -misc-macro-parentheses -misc-no-recursion -modernize-concat-nested-namespaces -modernize-pass-by-value -modernize-type-traits -modernize-use-default-member-init -modernize-use-nodiscard -modernize-use-override -modernize-use-trailing-return-type -modernize-use-transparent-functors -performance-avoid-endl -performance-type-promotion-in-math-fn -performance-enum-size -readability-braces-around-statements -readability-avoid-nested-conditional-operator -readability-convert-member-functions-to-static -readability-else-after-return -readability-function-cognitive-complexity -readability-identifier-length -readability-math-missing-parentheses -readability-named-parameter -readability-redundant-member-init -readability-redundant-string-init -readability-suspicious-call-argument -readability-uppercase-literal-suffix -*-avoid-c-arrays -*-explicit-constructor -*-magic-numbers -*-narrowing-conversions -*-non-private-member-variables-in-classes -*-use-auto -*-use-emplace -*-use-equals-default ${MIGRAPHX_TIDY_ERRORS} HEADER_FILTER ".*hpp" EXTRA_ARGS -UNDEBUG -DMIGRAPHX_USE_CLANG_TIDY CLANG_ARGS -analyzer-max-loop 10 -analyzer-inline-max-stack-depth 10 -analyzer-config optin.cplusplus.UninitializedObject:Pedantic=true -analyzer-config widen-loops=true -analyzer-config unroll-loops=true -analyzer-config cfg-lifetime=true -analyzer-config cfg-scopes=true ) include(ROCMCppCheck) rocm_enable_cppcheck( CHECKS warning style performance portability SUPPRESS ConfigurationNotChecked unmatchedSuppression unusedFunction ctuPointerArith noExplicitConstructor passedByValue unusedStructMember functionStatic functionConst shadowFunction shadowVar shadowVariable unsafeClassDivZero # Disable because of too many FPs arithOperationsOnVoidPointer definePrefix:*test/include/test.hpp ctuOneDefinitionRuleViolation:*test/* useSmartPointer:*src/api/api.cpp useSmartPointer:*make_shared_array.hpp migraphx-RedundantLocalVariable:*src/api/api.cpp migraphx-UseSmartPointer:*src/api/api.cpp FORCE INCONCLUSIVE RULE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/tools/cppcheck/rules.xml ADDONS ${CMAKE_CURRENT_SOURCE_DIR}/tools/cppcheck/migraphx.py SOURCES examples/ src/ test/ INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/src/include ${CMAKE_CURRENT_SOURCE_DIR}/src/targets/cpu/include ${CMAKE_CURRENT_SOURCE_DIR}/src/targets/gpu/include ${CMAKE_CURRENT_SOURCE_DIR}/src/targets/gpu/device/include ${CMAKE_CURRENT_SOURCE_DIR}/src/targets/gpu/kernels/include ${CMAKE_CURRENT_SOURCE_DIR}/test/include DEFINE MIGRAPHX_MLIR=1 MIGRAPHX_HAS_EXECUTORS=0 CPPCHECK=1 MIGRAPHX_USE_MIOPEN=1 BUILD_DEV= __device__= __host__= __global__= UNDEFINE MIGRAPHX_USE_CLANG_TIDY DOXYGEN HAS_HALF_V1 TYPE_ERASED_DECLARATION NDEBUG ) include(ROCMCreatePackage) include(ROCMTest) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) add_subdirectory(src) add_subdirectory(docs) if(BUILD_TESTING) rocm_enable_test_package(migraphx) add_subdirectory(test) endif() add_subdirectory(tools) set(DEST_DIR ${CMAKE_BINARY_DIR}) file(GLOB backend_files ${CMAKE_SOURCE_DIR}/src/py/backend/*.py) file(MAKE_DIRECTORY ${DEST_DIR}/lib/onnx_migraphx) foreach(py_file ${backend_files}) configure_file(${py_file} ${DEST_DIR}/lib/onnx_migraphx/. COPYONLY) endforeach(py_file) if(BUILD_ADDRESS_SANITIZER) set(DEPENDS_HIP_RUNTIME "hip-runtime-amd-asan" ) else() set(DEPENDS_HIP_RUNTIME "hip-runtime-amd" ) endif() if(MIGRAPHX_USE_MIOPEN) list(APPEND PACKAGE_DEPENDS miopen-hip) endif() if(MIGRAPHX_USE_ROCBLAS) list(APPEND PACKAGE_DEPENDS rocblas) endif() if(MIGRAPHX_USE_HIPBLASLT) list(APPEND PACKAGE_DEPENDS hipblaslt) endif() rocm_package_add_deb_dependencies(SHARED_DEPENDS "hip-dev") rocm_package_add_rpm_dependencies(SHARED_DEPENDS "hip-devel") rocm_create_package( NAME MIGraphX DESCRIPTION "AMD graph optimizer" MAINTAINER "AMDMIGraphX Maintainer " LDCONFIG PTH DEPENDS ${DEPENDS_HIP_RUNTIME} half ${PACKAGE_DEPENDS} ) ROCm-AMDMIGraphX-46524e8/Dockerfile000066400000000000000000000116171510465702400165400ustar00rootroot00000000000000FROM ubuntu:22.04 ARG PREFIX=/usr/local # Support multiarch RUN dpkg --add-architecture i386 # Install rocm key RUN apt-get update && apt-get install -y software-properties-common gnupg2 --no-install-recommends curl && \ curl -sL http://repo.radeon.com/rocm/rocm.gpg.key | apt-key add - # Add rocm repository RUN sh -c 'echo deb [arch=amd64 trusted=yes] http://repo.radeon.com/rocm/apt/7.1/ jammy main > /etc/apt/sources.list.d/rocm.list' # From docs.amd.com for installing rocm. Needed to install properly RUN sh -c "echo 'Package: *\nPin: release o=repo.radeon.com\nPin-priority: 600' > /etc/apt/preferences.d/rocm-pin-600" # rocgdb doesn't work on 22.04, workaround by installing the older python packages that are in 20.04 RUN add-apt-repository -y ppa:deadsnakes/ppa # Install dependencies RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \ apt-utils \ bison \ build-essential \ clang-14 \ cmake \ curl \ flex \ g++ \ gdb \ git \ lcov \ locales \ pkg-config \ python3 \ python3-dev \ python3-pip \ python3-full \ libpython3.8 \ wget \ rocm-device-libs \ hip-dev \ libnuma-dev \ miopen-hip \ libomp-dev \ rocblas \ hipfft \ hipsolver \ rccl \ rocthrust \ rocrand \ hipsparse \ rocm-smi-lib \ rocm-dev \ roctracer-dev \ hipcub \ hipblas \ hipify-clang \ hiprand-dev \ half \ libssl-dev \ zlib1g-dev && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Install pytorch RUN pip3 install https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1/torch-2.6.0%2Brocm7.1.0.lw.git78f6ff78-cp310-cp310-linux_x86_64.whl\ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1/torchvision-0.21.0%2Brocm7.1.0.git4040d51f-cp310-cp310-linux_x86_64.whl\ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1/triton-3.2.0%2Brocm7.1.0.git20943800-cp310-cp310-linux_x86_64.whl # add this for roctracer dependencies RUN pip3 install CppHeaderParser # Workaround broken rocm packages RUN ln -s /opt/rocm-* /opt/rocm RUN echo "/opt/rocm/lib" > /etc/ld.so.conf.d/rocm.conf RUN echo "/opt/rocm/llvm/lib" > /etc/ld.so.conf.d/rocm-llvm.conf RUN ldconfig # Workaround broken miopen cmake files RUN sed -i 's,;/usr/lib/x86_64-linux-gnu/librt.so,,g' /opt/rocm/lib/cmake/miopen/miopen-targets.cmake # Workaround for distributions running cmake < 3.25 RUN sed -i -e 's/^block/if(COMMAND block)\nblock/g' -e 's/^endblock/endblock\(\)\nendif/g' /opt/rocm/lib/cmake/hipblaslt/hipblaslt-config.cmake RUN locale-gen en_US.UTF-8 RUN update-locale LANG=en_US.UTF-8 ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 # Install dependencies ADD dev-requirements.txt /dev-requirements.txt ADD requirements.txt /requirements.txt ADD rbuild.ini /rbuild.ini # Location where onnx unit tests models are cached ENV ONNX_HOME=/.onnx RUN mkdir -p $ONNX_HOME/models && chmod 777 $ONNX_HOME/models COPY ./tools/install_prereqs.sh / COPY ./tools/requirements-py.txt /requirements-py.txt RUN /install_prereqs.sh /usr/local / && rm /install_prereqs.sh && rm /requirements-py.txt RUN test -f /usr/local/hash || exit 1 # Install yapf RUN pip3 install yapf==0.28.0 # Install doc requirements ADD docs/sphinx/requirements.txt /doc-requirements.txt RUN pip3 install -r /doc-requirements.txt # Install latest ccache version RUN cget -p $PREFIX install facebook/zstd@v1.4.5 -X subdir -DCMAKE_DIR=build/cmake RUN cget -p $PREFIX install ccache@v4.1 -DENABLE_TESTING=OFF RUN cget -p /opt/cmake install kitware/cmake@v3.28.0 # Install a newer version of doxygen because the one that comes with ubuntu is broken RUN cget -p $PREFIX install doxygen@Release_1_14_0 COPY ./test/onnx/.onnxrt-commit / ARG ONNXRUNTIME_REPO=https://github.com/Microsoft/onnxruntime ARG ONNXRUNTIME_BRANCH=main ARG ONNXRUNTIME_COMMIT RUN git clone --single-branch --branch ${ONNXRUNTIME_BRANCH} --recursive ${ONNXRUNTIME_REPO} onnxruntime && \ cd onnxruntime && \ if [ -z "$ONNXRUNTIME_COMMIT" ] ; then git checkout $(cat /.onnxrt-commit) ; else git checkout ${ONNXRUNTIME_COMMIT} ; fi && \ /bin/sh /onnxruntime/dockerfiles/scripts/install_common_deps.sh ADD tools/build_and_test_onnxrt.sh /onnxruntime/build_and_test_onnxrt.sh ADD tools/pai_test_launcher.sh /onnxruntime/tools/ci_build/github/pai/pai_test_launcher.sh ADD tools/pai_provider_test_launcher.sh /onnxruntime/tools/ci_build/github/pai/pai_provider_test_launcher.sh ENV MIOPEN_FIND_DB_PATH=/tmp/miopen/find-db ENV MIOPEN_USER_DB_PATH=/tmp/miopen/user-db ENV LD_LIBRARY_PATH=$PREFIX/lib # Setup ubsan environment to printstacktrace ENV UBSAN_OPTIONS=print_stacktrace=1 # Disable odr detection since its broken with shared libraries # See: https://github.com/google/sanitizers/issues/1017 ENV ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 RUN ln -s /opt/rocm/llvm/bin/llvm-symbolizer /usr/bin/llvm-symbolizer ROCm-AMDMIGraphX-46524e8/Jenkinsfile000066400000000000000000000273161510465702400167350ustar00rootroot00000000000000import org.jenkinsci.plugins.pipeline.modeldefinition.Utils DOCKER_IMAGE = 'rocm/migraphx-ci-jenkins-ubuntu' def getgputargets() { targets="gfx906;gfx908;gfx90a;gfx1030;gfx1100;gfx1101" return targets } def getnavi3xtargets() { targets="gfx1100;gfx1101" return targets } // Test // def rocmtestnode(variant, name, body, args, pre) { def rocmtestnode(Map conf) { def variant = conf.get("variant") def name = conf.get("node") def body = conf.get("body") def docker_args = conf.get("docker_args", "") def docker_build_args = conf.get("docker_build_args", "") def pre = conf.get("pre", {}) def ccache = "/workspaces/.cache/ccache" def image = 'migraphxlib' env.CCACHE_COMPRESSLEVEL = 7 env.CCACHE_DIR = ccache def cmake_build = { bconf -> def compiler = bconf.get("compiler", "/opt/rocm/llvm/bin/clang++") def flags = bconf.get("flags", "") def gpu_debug = bconf.get("gpu_debug", "0") def cmd = """ ulimit -c unlimited echo "leak:dnnl::impl::malloc" > suppressions.txt echo "leak:libtbb.so" >> suppressions.txt cat suppressions.txt export LSAN_OPTIONS="suppressions=\$(pwd)/suppressions.txt" export ASAN_OPTIONS="detect_container_overflow=0" export MIGRAPHX_GPU_DEBUG=${gpu_debug} export CXX=${compiler} export CXXFLAGS='-Werror' rocminfo env rm -rf build mkdir build cd build cmake -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DBUILD_DEV=On -DCMAKE_EXECUTE_PROCESS_COMMAND_ECHO=STDOUT -DMIGRAPHX_DISABLE_VIRTUAL_ENV=ON ${flags} .. git diff git diff-index --quiet HEAD || (echo "Git repo is not clean after running cmake." && exit 1) make -j\$(nproc) generate VERBOSE=1 git diff git diff-index --quiet HEAD || (echo "Generated files are different. Please run make generate and commit the changes." && exit 1) make -j\$(nproc) all package check VERBOSE=1 md5sum ./*.deb """ echo cmd sh cmd // Only archive from master or develop if (env.BRANCH_NAME == "develop" || env.BRANCH_NAME == "master") { archiveArtifacts artifacts: "build/*.deb", allowEmptyArchive: true, fingerprint: true } } node(name) { withEnv(['HSA_ENABLE_SDMA=0']) { stage("checkout ${variant}") { sh 'printenv' checkout scm } def video_id = sh(returnStdout: true, script: 'getent group video | cut -d: -f3').trim() def render_id = sh(returnStdout: true, script: 'getent group render | cut -d: -f3').trim() def docker_opts = "--device=/dev/kfd --device=/dev/dri --cap-add SYS_PTRACE -v=${env.WORKSPACE}/../:/workspaces:rw,z" docker_opts = docker_opts + " --group-add=${video_id} --group-add=${render_id} " echo "Docker flags: ${docker_opts}" gitStatusWrapper(credentialsId: "${env.migraphx_ci_creds}", gitHubContext: "Jenkins - ${variant}", account: 'ROCmSoftwarePlatform', repo: 'AMDMIGraphX') { withCredentials([usernamePassword(credentialsId: 'docker_test_cred', passwordVariable: 'DOCKERHUB_PASS', usernameVariable: 'DOCKERHUB_USER')]) { sh "echo $DOCKERHUB_PASS | docker login --username $DOCKERHUB_USER --password-stdin" pre() sh "docker pull ${DOCKER_IMAGE}:${env.IMAGE_TAG}" withDockerContainer(image: "${DOCKER_IMAGE}:${env.IMAGE_TAG}", args: docker_opts + docker_args) { timeout(time: 4, unit: 'HOURS') { body(cmake_build) } } } } } } } def rocmtest(m) { def builders = [:] m.each { e -> def label = e.key; def action = e.value; builders[label] = { action(label) } } parallel builders } def rocmnodename(name) { def rocmtest_name = "(rocmtest || migraphx)" def node_name = "${rocmtest_name}" if(name == "fiji") { node_name = "${rocmtest_name} && fiji"; } else if(name == "vega") { node_name = "${rocmtest_name} && vega"; } else if(name == "navi21") { node_name = "${rocmtest_name} && navi21"; } else if(name == "mi100+") { node_name = "${rocmtest_name} && (gfx908 || gfx90a) && !vm"; } else if(name == "mi200+") { node_name = "${rocmtest_name} && (gfx90a || gfx942) && !vm"; } else if(name == "cdna") { node_name = "${rocmtest_name} && (gfx908 || gfx90a || vega20) && !vm"; } else if(name == "navi32") { node_name = "${rocmtest_name} && gfx1101 && !vm"; } else if(name == "nogpu") { node_name = "${rocmtest_name} && nogpu"; } else if(name == "onnxrt") { node_name = "${rocmtest_name} && onnxrt"; } return node_name } def rocmnode(name, body) { return { label -> rocmtestnode(variant: label, node: rocmnodename(name), body: body) } } properties([ parameters([ booleanParam(name: 'FORCE_DOCKER_IMAGE_BUILD', defaultValue: false) ]) ]) node("(rocmtest || migraphx)") { Boolean imageExists = false withCredentials([usernamePassword(credentialsId: 'docker_test_cred', passwordVariable: 'DOCKERHUB_PASS', usernameVariable: 'DOCKERHUB_USER')]) { sh "echo $DOCKERHUB_PASS | docker login --username $DOCKERHUB_USER --password-stdin" stage('Check image') { sh 'printenv' checkout scm def calculateImageTagScript = """ shopt -s globstar sha256sum **/Dockerfile **/*requirements.txt **/install_prereqs.sh **/rbuild.ini **/test/onnx/.onnxrt-commit | sha256sum | cut -d " " -f 1 """ env.IMAGE_TAG = sh(script: "bash -c '${calculateImageTagScript}'", returnStdout: true).trim() imageExists = sh(script: "docker manifest inspect ${DOCKER_IMAGE}:${IMAGE_TAG}", returnStatus: true) == 0 } stage('Build image') { if(!imageExists || params.FORCE_DOCKER_IMAGE_BUILD) { def builtImage try { sh "docker pull ${DOCKER_IMAGE}:latest" builtImage = docker.build("${DOCKER_IMAGE}:${IMAGE_TAG}", "--cache-from ${DOCKER_IMAGE}:latest .") } catch(Exception ex) { builtImage = docker.build("${DOCKER_IMAGE}:${IMAGE_TAG}", " --no-cache .") } builtImage.push("${IMAGE_TAG}") builtImage.push("latest") } else { echo "Image already exists, skip building available" // Skip stage so it remains in the visualization Utils.markStageSkippedForConditional(STAGE_NAME) } } } } rocmtest clang_debug: rocmnode('mi200+') { cmake_build -> stage('hipRTC Debug') { // Disable MLIR since it doesnt work with all ub sanitizers withEnv(['MIGRAPHX_DISABLE_MLIR=1']) { def sanitizers = "undefined" def debug_flags = "-g -O2 -fsanitize=${sanitizers} -fno-sanitize=vptr,function -fno-sanitize-recover=${sanitizers}" def gpu_targets = getgputargets() cmake_build(flags: "-DCMAKE_C_COMPILER=/opt/rocm/llvm/bin/clang -DCMAKE_BUILD_TYPE=debug -DMIGRAPHX_ENABLE_PYTHON=Off -DCMAKE_CXX_FLAGS_DEBUG='${debug_flags}' -DCMAKE_C_FLAGS_DEBUG='${debug_flags}' -DMIGRAPHX_USE_HIPRTC=On -DGPU_TARGETS='${gpu_targets}'", gpu_debug: true) } } }, clang_release: rocmnode('mi100+') { cmake_build -> stage('Hip Clang Release') { def gpu_targets = getgputargets() cmake_build(flags: "-DCMAKE_BUILD_TYPE=release -DGPU_TARGETS='${gpu_targets}'") stash includes: 'build/*.deb', name: 'migraphx-package' } // }, hidden_symbols: rocmnode('cdna') { cmake_build -> // stage('Hidden symbols') { // cmake_build(flags: "-DMIGRAPHX_ENABLE_PYTHON=Off -DMIGRAPHX_ENABLE_GPU=On -DMIGRAPHX_ENABLE_CPU=On -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_C_VISIBILITY_PRESET=hidden") // } }, all_targets_debug : rocmnode('mi100+') { cmake_build -> stage('All targets Release') { def gpu_targets = getgputargets() cmake_build(flags: "-DCMAKE_BUILD_TYPE=release -DMIGRAPHX_ENABLE_GPU=On -DMIGRAPHX_ENABLE_CPU=On -DMIGRAPHX_ENABLE_FPGA=On -DGPU_TARGETS='${gpu_targets}'") } }, mlir_debug: rocmnode('mi100+') { cmake_build -> stage('MLIR Debug') { withEnv(['MIGRAPHX_ENABLE_EXTRA_MLIR=1', 'MIGRAPHX_MLIR_USE_SPECIFIC_OPS=fused,attention,convolution,dot,convolution_backwards', 'MIGRAPHX_ENABLE_MLIR_INPUT_FUSION=1', 'MIGRAPHX_MLIR_ENABLE_SPLITK=1', 'MIGRAPHX_ENABLE_MLIR_REDUCE_FUSION=1', 'MIGRAPHX_ENABLE_MLIR_GEG_FUSION=1', 'MIGRAPHX_ENABLE_SPLIT_REDUCE=1','MIGRAPHX_DISABLE_LAYERNORM_FUSION=1']) { def sanitizers = "undefined" // Note: the -fno-sanitize= is copied from upstream LLVM_UBSAN_FLAGS. def debug_flags = "-g -O2 -fsanitize=${sanitizers} -fno-sanitize=vptr,function -fno-sanitize-recover=${sanitizers}" def gpu_targets = getgputargets() // Since the purpose of this run verify all things MLIR supports, // enabling all possible types of offloads cmake_build(flags: "-DCMAKE_C_COMPILER=/opt/rocm/llvm/bin/clang -DCMAKE_BUILD_TYPE=debug -DMIGRAPHX_ENABLE_PYTHON=Off -DMIGRAPHX_ENABLE_MLIR=On -DCMAKE_CXX_FLAGS_DEBUG='${debug_flags}' -DCMAKE_C_FLAGS_DEBUG='${debug_flags}' -DGPU_TARGETS='${gpu_targets}'") } } //}, ck_hiprtc: rocmnode('mi100+') { cmake_build -> // stage('CK hipRTC') { // withEnv(['MIGRAPHX_ENABLE_CK=1', 'MIGRAPHX_TUNE_CK=1', 'MIGRAPHX_DISABLE_MLIR=1']) { // def gpu_targets = getgputargets() // cmake_build(flags: "-DCMAKE_BUILD_TYPE=release -DMIGRAPHX_USE_HIPRTC=On -DGPU_TARGETS='${gpu_targets}'") // } // } }, clang_release_navi: rocmnode('navi32') { cmake_build -> stage('HIP Clang Release Navi32') { def gpu_targets = getnavi3xtargets() cmake_build(flags: "-DCMAKE_BUILD_TYPE=release -DGPU_TARGETS='${gpu_targets}' -DMIGRAPHX_DISABLE_ONNX_TESTS=On") } }, clang_asan: rocmnode('nogpu') { cmake_build -> stage('Clang ASAN') { def sanitizers = "undefined,address" def debug_flags = "-g -O2 -fno-omit-frame-pointer -fsanitize=${sanitizers} -fno-sanitize-recover=${sanitizers}" cmake_build(flags: "-DCMAKE_BUILD_TYPE=debug -DMIGRAPHX_ENABLE_C_API_TEST=Off -DMIGRAPHX_ENABLE_PYTHON=Off -DMIGRAPHX_ENABLE_GPU=Off -DMIGRAPHX_ENABLE_CPU=On -DCMAKE_CXX_FLAGS_DEBUG='${debug_flags}'", compiler:'/usr/bin/clang++-14') } }, debub_libstdcxx: rocmnode('nogpu') { cmake_build -> stage('Debug libstdc++') { def sanitizers = "undefined" def debug_flags = "-g -O2 -fno-omit-frame-pointer -fsanitize=${sanitizers} -fno-sanitize-recover=${sanitizers} -D_GLIBCXX_DEBUG" cmake_build(flags: "-DCMAKE_BUILD_TYPE=debug -DMIGRAPHX_ENABLE_C_API_TEST=Off -DMIGRAPHX_ENABLE_PYTHON=Off -DMIGRAPHX_ENABLE_GPU=Off -DMIGRAPHX_ENABLE_CPU=Off -DCMAKE_CXX_FLAGS_DEBUG='${debug_flags}'", compiler:'/usr/bin/clang++-14') } } def onnxnode(name, body) { return { label -> rocmtestnode(variant: label, node: rocmnodename(name), docker_args: '-u root', body: body, pre: { sh 'rm -rf ./build/*.deb' unstash 'migraphx-package' }) } } rocmtest onnx: onnxnode('onnxrt') { cmake_build -> stage("Onnx runtime") { sh ''' apt install half #ls -lR md5sum ./build/*.deb dpkg -i ./build/*.deb env cd /onnxruntime && ./build_and_test_onnxrt.sh ''' } } ROCm-AMDMIGraphX-46524e8/LICENSE000066400000000000000000000021351510465702400155460ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. 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. ROCm-AMDMIGraphX-46524e8/README.md000066400000000000000000000170411510465702400160220ustar00rootroot00000000000000# AMD MIGraphX AMD MIGraphX is AMD's graph inference engine, which accelerates machine learning model inference. >[!NOTE] >The published documentation is available at [MIGraphX](https://rocm.docs.amd.com/projects/AMDMIGraphX/en/latest/) in an organized, easy-to-read format, with search and a table of contents. The documentation source files reside in the `docs` folder of this repository. As with all ROCm projects, the documentation is open source. For more information on contributing to the documentation, see [Contribute to ROCm documentation](https://rocm.docs.amd.com/en/latest/contribute/contributing.html). > [!NOTE] > You must [install ROCm](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/index.html) before > installing MIGraphX. ## Installing from binaries Install binaries using: ```bash sudo apt update && sudo apt install -y migraphx ``` Header files and libraries are installed under ``/opt/rocm-``, where ```` is the ROCm version. ## Building from source You have three options for building from source: * [ROCm build tool](#use-the-rocm-build-tool-rbuild): Uses [rbuild](https://github.com/RadeonOpenCompute/rbuild) to install prerequisites, then you can build the libraries with a single command. * [CMake](#use-cmake-to-build-migraphx): Uses a script to install prerequisites, then you can use CMake to build the source. * [Docker](#use-docker): Builds a Docker image with all prerequisites installed, then you can build the MIGraphX sources inside a Docker container. ### Build prerequisites The following is a list of prerequisites for building MIGraphX. * [ROCm CMake modules](https://github.com/RadeonOpenCompute/rocm-cmake) **required** * [MIOpen](https://github.com/ROCmSoftwarePlatform/MIOpen) for running on the GPU * [rocBLAS](https://github.com/ROCmSoftwarePlatform/rocBLAS) for running on the GPU * [HIP](https://github.com/ROCm-Developer-Tools/HIP) for running on the GPU * [Protobuf](https://github.com/google/protobuf) for reading [onnx](https://github.com/onnx/onnx) files * [Half](http://half.sourceforge.net/), an IEEE 754-based half-precision floating point library * [pybind11](https://pybind11.readthedocs.io/en/stable/) for python bindings * [JSON](https://github.com/nlohmann/json) for model serialization to json string format * [MessagePack](https://msgpack.org/index.html) for model serialization to binary format * [SQLite3](https://www.sqlite.org/index.html) to create database of kernels' tuning information or run queries on existing database ### Use the ROCm build tool [rbuild](https://github.com/RadeonOpenCompute/rbuild). 1. Install `rocm-cmake`, `pip3`, `rocblas`, and `miopen-hip`: ```bash sudo apt install -y rocm-cmake python3-pip rocblas miopen-hip ``` 2. Install [rbuild](https://github.com/RadeonOpenCompute/rbuild) (sudo may be required): ```bash pip3 install https://github.com/RadeonOpenCompute/rbuild/archive/master.tar.gz ``` 3. Build MIGraphX source code: ```bash rbuild build -d depend -B build -DGPU_TARGETS=$(/opt/rocm/bin/rocminfo | grep -o -m1 'gfx.*') ``` Once completed, all prerequisites are in the `depend` folder and MIGraphX is in the `build` directory. > [!NOTE] > If you get an `rbuild: command not found` error, it's because `rbuild` is installed in `$HOME/.local/bin`, > which is not in `PATH`. You can either export PATH as `export PATH=$HOME/.local/bin:$PATH` to add > the folder to `PATH`, or add the option `--prefix /usr/local` in the pip3 command when installing `rbuild`. ### Use CMake to build MIGraphX 1. Install the prerequisites: ```bash rbuild prepare -d depend ``` This puts all the prerequisites are in `depend` the folder. They can be used in the `cmake` configuration as `-DCMAKE_PREFIX_PATH=depend`. If you have sudo access, as an alternative to the `rbuild` command, you can install the prerequisites in the same way as a Dockerfile, by calling `./tools/install_prereqs.sh`. By default, all prerequisites are installed at the default location (`/usr/local`) and are accessible by all users. For the default location, `sudo` is required to run the script. You can also specify a different location using `./tools/install_prereqs.sh $custom_location`. 2. Go to the project folder and create a `build` directory: ```bash mkdir build cd build ``` 3. Configure CMake. If the prerequisites are installed at the default location `/usr/local`, use: ```bash CXX=/opt/rocm/llvm/bin/clang++ cmake .. -DGPU_TARGETS=$(/opt/rocm/bin/rocminfo | grep -o -m1 'gfx.*') ``` Otherwise, you need to set `-DCMAKE_PREFIX_PATH=$your_loc` to configure CMake. 4. Build MIGraphX source code: ```cpp make -j$(nproc) ``` You can verify this using: ```cpp make -j$(nproc) check ``` 5. Install MIGraphX libraries: ```cpp make install ``` ### Use Docker The easiest way to set up the development environment is to use Docker. 1. With the Dockerfile, build a Docker image: ```bash docker build -t migraphx . ``` 2. Enter the development environment using `docker run`: ```bash docker run --device='/dev/kfd' --device='/dev/dri' -v=`pwd`:/code/AMDMIGraphX -w /code/AMDMIGraphX --group-add video -it migraphx ``` 3. In the Docker container, all required prerequisites are already installed, so you can go to the folder `/code/AMDMIGraphX` and follow the steps (starting from 2) in the [Use CMake to build MIGraphX](#use-cmake-to-build-migraphx). ## Using the MIGraphX Python module To use MIGraphX's Python module, you can set `PYTHONPATH` or use the `.deb` package: * Setting `PYTHONPATH`: ```bash export PYTHONPATH=/opt/rocm/lib:$PYTHONPATH ``` * Creating the `deb` package: ```bash make package ``` This provides the path for .deb package. To install: ```bash dpkg -i ``` ## Calling MIGraphX APIs To use MIGraphX's C/C++ API in your CMake project, you must set `CMAKE_PREFIX_PATH` to the MIGraphX installation location and run: ```cmake find_package(migraphx) target_link_libraries(myApp migraphx::c) ``` Where `myApp` is the CMake target in your project. ## Building for development Using `rbuild`, you can install the dependencies for development with: ```bash rbuild develop -DGPU_TARGETS=$(/opt/rocm/bin/rocminfo | grep -o -m1 'gfx.*') ``` This installs development dependencies in the `deps` directory and configures `cmake` to use those dependencies in the `build` directory. You can change these directories by passing the `--deps-dir` and `--build-dir` flags to the `rbuild` command: ```bash rbuild develop --build-dir build_rocm_55 --deps-dir /home/user/deps_dir ``` ## Building the documentation HTML and PDF documentation can be built using: `cmake --build . --config Release --target doc` **OR** `make doc` This will build a local searchable web site inside the docs/html folder. Documentation is built using [Doxygen](http://www.stack.nl/~dimitri/doxygen/download.html) and [rocm-docs-core](https://github.com/RadeonOpenCompute/rocm-docs-core) Run the steps below to build documentation locally. ```bash cd docs pip3 install -r sphinx/requirements.txt python3 -m sphinx -T -E -b html -d _build/doctrees -D language=en . _build/html ``` Depending on your setup `sudo` may be required for the pip install. ## Formatting the code All the code is formatted using clang-format. To format a file, use: ```clang clang-format-10 -style=file -i ``` Also, githooks can be installed to format the code per-commit: ```bash ./.githooks/install ``` ROCm-AMDMIGraphX-46524e8/cmake/000077500000000000000000000000001510465702400156205ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/cmake/CheckCXXLinkerFlag.cmake000066400000000000000000000063501510465702400221650ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### set(check_cxx_linker_flag_patterns FAIL_REGEX "[Uu]nrecogni[sz]ed .*option" # GNU, NAG FAIL_REGEX "switch .* is no longer supported" # GNU FAIL_REGEX "unknown .*option" # Clang FAIL_REGEX "optimization flag .* not supported" # Clang FAIL_REGEX "unknown argument ignored" # Clang (cl) FAIL_REGEX "ignoring unknown option" # MSVC, Intel FAIL_REGEX "warning D9002" # MSVC, any lang FAIL_REGEX "option.*not supported" # Intel FAIL_REGEX "invalid argument .*option" # Intel FAIL_REGEX "ignoring option .*argument required" # Intel FAIL_REGEX "ignoring option .*argument is of wrong type" # Intel FAIL_REGEX "[Uu]nknown option" # HP FAIL_REGEX "[Ww]arning: [Oo]ption" # SunPro FAIL_REGEX "command option .* is not recognized" # XL FAIL_REGEX "command option .* contains an incorrect subargument" # XL FAIL_REGEX "Option .* is not recognized. Option will be ignored." # XL FAIL_REGEX "not supported in this configuration. ignored" # AIX FAIL_REGEX "File with unknown suffix passed to linker" # PGI FAIL_REGEX "[Uu]nknown switch" # PGI FAIL_REGEX "WARNING: unknown flag:" # Open64 FAIL_REGEX "Incorrect command line option:" # Borland FAIL_REGEX "Warning: illegal option" # SunStudio 12 FAIL_REGEX "[Ww]arning: Invalid suboption" # Fujitsu FAIL_REGEX "An invalid option .* appears on the command line" # Cray ) function(check_cxx_linker_flag _flag _var) set (_source "int main() { return 0; }") include (CheckCXXSourceCompiles) check_cxx_source_compiles("${_source}" _result ${check_cxx_linker_flag_patterns}) set(${_var} "${_result}" PARENT_SCOPE) endfunction() ROCm-AMDMIGraphX-46524e8/cmake/Embed.cmake000066400000000000000000000246551510465702400176520ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### include_guard(GLOBAL) if(WIN32) set(EMBED_USE RC CACHE STRING "Use RC or CArrays to embed data files") set_property(CACHE EMBED_USE PROPERTY STRINGS "RC;CArrays") else() if(BUILD_SHARED_LIBS) set(EMBED_USE LD CACHE STRING "Use LD or CArrays to embed data files") else() set(EMBED_USE CArrays CACHE STRING "Use LD or CArrays to embed data files") endif() set_property(CACHE EMBED_USE PROPERTY STRINGS "LD;CArrays") endif() if(EMBED_USE STREQUAL "LD") find_program(EMBED_LD ld REQUIRED) find_program(EMBED_OBJCOPY objcopy REQUIRED) endif() function(embed_wrap_string) set(options) set(oneValueArgs VARIABLE AT_COLUMN) set(multiValueArgs) cmake_parse_arguments(PARSE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) string(LENGTH ${${PARSE_VARIABLE}} string_length) math(EXPR offset "0") while(string_length GREATER 0) if(string_length GREATER ${PARSE_AT_COLUMN}) math(EXPR length "${PARSE_AT_COLUMN}") else() math(EXPR length "${string_length}") endif() string(SUBSTRING ${${PARSE_VARIABLE}} ${offset} ${length} line) set(lines "${lines}\n${line}") math(EXPR string_length "${string_length} - ${length}") math(EXPR offset "${offset} + ${length}") endwhile() set(${PARSE_VARIABLE} "${lines}" PARENT_SCOPE) endfunction() set(RESOURCE_ID 100 CACHE INTERNAL "" FORCE) function(reset_resource_id MULTIPLE) if(RESOURCE_ID GREATER 0) math(EXPR _remainder "${RESOURCE_ID} % ${MULTIPLE}") if(_remainder GREATER 0) math(EXPR RESOURCE_ID "${RESOURCE_ID} + ${MULTIPLE} - ${_remainder}") endif() endif() set(RESOURCE_ID ${RESOURCE_ID} CACHE INTERNAL "" FORCE) endfunction() function(generate_embed_source EMBED_NAME EMBED_DIR BASE_DIRECTORY) set(options) set(oneValueArgs) set(multiValueArgs SYMBOLS FILES) cmake_parse_arguments(PARSE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) list(LENGTH PARSE_SYMBOLS SYMBOLS_LEN) list(LENGTH PARSE_FILES FILES_LEN) if(NOT ${SYMBOLS_LEN} EQUAL ${FILES_LEN}) message(FATAL_ERROR "Symbols and objects dont match: ${SYMBOLS_LEN} != ${FILES_LEN}") endif() math(EXPR LEN "${SYMBOLS_LEN} - 1") foreach(idx RANGE ${LEN}) list(GET PARSE_SYMBOLS ${idx} SYMBOL) list(GET PARSE_FILES ${idx} FILE) file(RELATIVE_PATH BASE_NAME "${BASE_DIRECTORY}" ${FILE}) if(EMBED_USE STREQUAL "RC") string(TOUPPER "${SYMBOL}" SYMBOL) string(APPEND FILE_IDS "#define IDR_${SYMBOL} ${RESOURCE_ID}\n") file(TO_NATIVE_PATH "${FILE}" NATIVE_FILE) string(REPLACE "\\" "\\\\" NATIVE_FILE "${NATIVE_FILE}") string(APPEND RC_FILE_MAPPING "IDR_${SYMBOL} TEXTFILE \"${NATIVE_FILE}\"\n") string(APPEND INIT_KERNELS "\n {\"${BASE_NAME}\", resource::read(IDR_${SYMBOL})},") math(EXPR RESOURCE_ID "${RESOURCE_ID} + 1" OUTPUT_FORMAT DECIMAL) else() set(START_SYMBOL "_binary_${SYMBOL}_start") set(LENGTH_SYMBOL "_binary_${SYMBOL}_length") if(EMBED_USE STREQUAL "LD") string(APPEND EXTERNS " extern const char ${START_SYMBOL}[]; extern const size_t _binary_${SYMBOL}_size; const auto ${LENGTH_SYMBOL} = reinterpret_cast(&_binary_${SYMBOL}_size); ") else() string(APPEND EXTERNS " extern const char ${START_SYMBOL}[]; extern const size_t ${LENGTH_SYMBOL}; ") endif() string(APPEND INIT_KERNELS " { \"${BASE_NAME}\", { ${START_SYMBOL}, ${LENGTH_SYMBOL}} },") endif() endforeach() if(EMBED_USE STREQUAL "RC") file(WRITE "${EMBED_DIR}/include/resource.h" " #define TEXTFILE 256 ${FILE_IDS} ") file(WRITE "${EMBED_DIR}/resource.rc" " #include \"resource.h\" ${RC_FILE_MAPPING} ") set(EXTERNS " #include #include \"resource.h\" namespace resource { static std::string_view read(int id) { HMODULE handle; if(GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(${EMBED_NAME}), &handle) != 0) { HRSRC rc = FindResource(handle, MAKEINTRESOURCE(id), MAKEINTRESOURCE(TEXTFILE)); if(rc != nullptr) { HGLOBAL data = LoadResource(handle, rc); if(data != nullptr) { return {static_cast(LockResource(data)), SizeofResource(handle, rc)}; } } } return std::string_view{}; } } ") set(EMBED_FILES ${EMBED_DIR}/include/resource.h ${EMBED_DIR}/resource.rc) endif() file(WRITE "${EMBED_DIR}/include/${EMBED_NAME}.hpp" " #include #include #include std::unordered_map ${EMBED_NAME}(); ") file(WRITE "${EMBED_DIR}/${EMBED_NAME}.cpp" " #include <${EMBED_NAME}.hpp> ${EXTERNS} std::unordered_map ${EMBED_NAME}() { static std::unordered_map result = {${INIT_KERNELS} }; return result; } ") reset_resource_id(1000) list(APPEND EMBED_FILES ${EMBED_DIR}/${EMBED_NAME}.cpp ${EMBED_DIR}/include/${EMBED_NAME}.hpp) set(EMBED_FILES ${EMBED_FILES} PARENT_SCOPE) endfunction() function(embed_file FILE BASE_DIRECTORY) message(STATUS " ${FILE}") file(RELATIVE_PATH REL_FILE "${BASE_DIRECTORY}" ${FILE}) string(MAKE_C_IDENTIFIER "${REL_FILE}" OUTPUT_SYMBOL) get_filename_component(OUTPUT_FILE_DIR "${REL_FILE}" DIRECTORY) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILE_DIR}") if(EMBED_USE STREQUAL "LD") set(OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/${REL_FILE}.o") add_custom_command( OUTPUT "${OUTPUT_FILE}" COMMAND ${EMBED_LD} -r -o "${OUTPUT_FILE}" -z noexecstack --format=binary "${REL_FILE}" COMMAND ${EMBED_OBJCOPY} --rename-section .data=.rodata,alloc,load,readonly,data,contents "${OUTPUT_FILE}" WORKING_DIRECTORY "${BASE_DIRECTORY}" DEPENDS "${FILE}" VERBATIM) set(OUTPUT_FILE ${OUTPUT_FILE} PARENT_SCOPE) elseif(EMBED_USE STREQUAL "CArrays") set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${FILE}) set(OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/${REL_FILE}.cpp") # reads source file contents as hex string file(READ ${FILE} HEX_STRING HEX) # wraps the hex string into multiple lines embed_wrap_string(VARIABLE HEX_STRING AT_COLUMN 80) # adds '0x' prefix and comma suffix before and after every byte respectively string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " ARRAY_VALUES ${HEX_STRING}) # removes trailing comma string(REGEX REPLACE ", $" "" ARRAY_VALUES ${ARRAY_VALUES}) file(WRITE "${OUTPUT_FILE}" " #include extern const char _binary_${OUTPUT_SYMBOL}_start[] = { ${ARRAY_VALUES} }; extern const size_t _binary_${OUTPUT_SYMBOL}_length = sizeof(_binary_${OUTPUT_SYMBOL}_start); ") set(OUTPUT_FILE ${OUTPUT_FILE} PARENT_SCOPE) endif() set(OUTPUT_SYMBOL ${OUTPUT_SYMBOL} PARENT_SCOPE) endfunction() function(add_embed_library EMBED_NAME) set(options) set(oneValueArgs RELATIVE) set(multiValueArgs) cmake_parse_arguments(PARSE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(EMBED_DIR ${CMAKE_CURRENT_BINARY_DIR}/embed/${EMBED_NAME}) file(MAKE_DIRECTORY ${EMBED_DIR}) message(STATUS "Embedding kernel files:") foreach(FILE ${PARSE_UNPARSED_ARGUMENTS}) embed_file(${FILE} ${PARSE_RELATIVE}) list(APPEND OUTPUT_FILES ${OUTPUT_FILE}) list(APPEND SYMBOLS ${OUTPUT_SYMBOL}) endforeach() message(STATUS "Generating embedding library '${EMBED_NAME}'") generate_embed_source(${EMBED_NAME} ${EMBED_DIR} "${PARSE_RELATIVE}" SYMBOLS ${SYMBOLS} FILES ${PARSE_UNPARSED_ARGUMENTS}) set(INTERNAL_EMBED_LIB embed_lib_${EMBED_NAME}) if(EMBED_USE STREQUAL "LD") add_library(${INTERNAL_EMBED_LIB} STATIC ${EMBED_FILES} ${OUTPUT_FILES}) else() add_library(${INTERNAL_EMBED_LIB} OBJECT ${EMBED_FILES}) endif() if(EMBED_USE STREQUAL "CArrays") target_sources(${INTERNAL_EMBED_LIB} PRIVATE ${OUTPUT_FILES}) endif() target_include_directories(${INTERNAL_EMBED_LIB} PRIVATE "${EMBED_DIR}/include") target_compile_options(${INTERNAL_EMBED_LIB} PRIVATE -Wno-reserved-identifier -Wno-extern-initializer -Wno-missing-variable-declarations) set_target_properties(${INTERNAL_EMBED_LIB} PROPERTIES POSITION_INDEPENDENT_CODE On) add_library(${EMBED_NAME} INTERFACE) if(EMBED_USE STREQUAL "RC") target_link_libraries(${EMBED_NAME} INTERFACE $) elseif(EMBED_USE STREQUAL "LD") target_link_libraries(${EMBED_NAME} INTERFACE ${INTERNAL_EMBED_LIB}) else() target_sources(${EMBED_NAME} INTERFACE $) endif() target_include_directories(${EMBED_NAME} INTERFACE "${EMBED_DIR}/include") endfunction() ROCm-AMDMIGraphX-46524e8/cmake/EnableCompilerWarnings.cmake000066400000000000000000000124061510465702400232170ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### # - Enable warning all for gcc/clang or use /W4 for visual studio include(CheckCXXCompilerFlag) ## Strict warning level if (MSVC) # Use the highest warning level for visual studio. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /w") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /w") # set(CMAKE_CXX_WARNING_LEVEL 4) # if (CMAKE_CXX_FLAGS MATCHES "/W[0-4]") # string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # else () # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") # endif () # set(CMAKE_C_WARNING_LEVEL 4) # if (CMAKE_C_FLAGS MATCHES "/W[0-4]") # string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") # else () # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") # endif () else() foreach(COMPILER C CXX) set(CMAKE_COMPILER_WARNINGS) # use -Wall for gcc and clang list(APPEND CMAKE_COMPILER_WARNINGS -Wall -Wextra -Wcomment -Wendif-labels -Wformat -Winit-self -Wreturn-type -Wsequence-point # Shadow is broken on gcc when using lambdas # -Wshadow -Wswitch -Wtrigraphs -Wundef -Wuninitialized -Wunreachable-code -Wunused -Wno-sign-compare ) # Flags for gcc 7 if(CMAKE_${COMPILER}_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.0") list(APPEND CMAKE_COMPILER_WARNINGS -Wduplicated-branches -Wduplicated-cond -Wno-noexcept-type -Wodr -Wshift-negative-value -Wshift-overflow=2 -Wno-comment ) endif() endif() if (CMAKE_${COMPILER}_COMPILER_ID MATCHES "Clang") list(APPEND CMAKE_COMPILER_WARNINGS -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-conversion -Wno-double-promotion -Wno-exit-time-destructors -Wno-extra-semi -Wno-extra-semi-stmt -Wno-float-conversion -Wno-gnu-anonymous-struct -Wno-gnu-zero-variadic-macro-arguments -Wno-missing-prototypes -Wno-nested-anon-types -Wno-option-ignored -Wno-padded -Wno-shorten-64-to-32 -Wno-sign-conversion -Wno-unused-command-line-argument -Wno-weak-vtables -Wno-c99-extensions -Wno-unsafe-buffer-usage # This is broken for now for moved values -Wno-shadow-uncaptured-local # -Wno-c++2a-designator # -Weverything gives contradictory warnings, so disable the one that requires default in switch -Wno-switch-default ) if(WIN32 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "19") list(APPEND CMAKE_COMPILER_WARNINGS -Wno-missing-include-dirs -Wno-switch-default -Wno-deprecated-pragma ) endif() else() list(APPEND CMAKE_COMPILER_WARNINGS -Wno-missing-field-initializers -Wno-maybe-uninitialized # -Wno-deprecated-declarations ) endif() foreach(COMPILER_WARNING ${CMAKE_COMPILER_WARNINGS}) string(MAKE_C_IDENTIFIER "HAS_${COMPILER}_FLAG${COMPILER_WARNING}" HAS_COMPILER_WARNING) check_cxx_compiler_flag(${COMPILER_WARNING} ${HAS_COMPILER_WARNING}) if(${HAS_COMPILER_WARNING}) add_compile_options($<$:${COMPILER_WARNING}>) endif() endforeach() endforeach() endif () ROCm-AMDMIGraphX-46524e8/cmake/ExportHeader.cmake000066400000000000000000000042071510465702400212170ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### if(COMMAND migraphx_generate_export_header) return() endif() include(GenerateExportHeader) function(migraphx_generate_export_header TARGET) set(options) set(oneValueArgs DIRECTORY) set(multiValueArgs) cmake_parse_arguments(PARSE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(PARSE_DIRECTORY) set(__directory ${PARSE_DIRECTORY}) else() string(REPLACE "_" "/" __directory ${TARGET}) string(TOLOWER ${__directory} __directory) endif() set(__file_name ${CMAKE_CURRENT_BINARY_DIR}/include/${__directory}/export.h) generate_export_header(${TARGET} EXPORT_FILE_NAME ${__file_name}) target_include_directories(${TARGET} PUBLIC $) rocm_install(FILES ${__file_name} DESTINATION include/${__directory}) endfunction() ROCm-AMDMIGraphX-46524e8/cmake/FindParallelSTL.cmake000066400000000000000000000052511510465702400215450ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### include(CheckCXXSourceCompiles) function(find_parallel_stl_check RESULT) set(CMAKE_REQUIRED_LIBRARIES ${ARGN}) set(CMAKE_REQUIRED_FLAGS) if(NOT MSVC) set(CMAKE_REQUIRED_FLAGS "-std=c++17") endif() string(MD5 _flags_hash "${CMAKE_REQUIRED_FLAGS} ${CMAKE_REQUIRED_LIBRARIES}") set(_source " #include #ifdef _PSTL_PAR_BACKEND_SERIAL #error \"Using serial backend\" #endif int main() { int* i = nullptr; std::sort(std::execution::par, i, i); } ") check_cxx_source_compiles("${_source}" _has_execution_${_flags_hash}) set(${RESULT} ${_has_execution_${_flags_hash}} PARENT_SCOPE) endfunction() set(ParallelSTL_FOUND Off) set(ParallelSTL_LIBRARIES) set(ParallelSTL_USES_TBB Off) find_parallel_stl_check(ParallelSTL_HAS_EXECUTION_PAR) if(ParallelSTL_HAS_EXECUTION_PAR) set(ParallelSTL_FOUND On) else() find_package(TBB QUIET) if(TARGET TBB::tbb) find_parallel_stl_check(ParallelSTL_TBB_HAS_EXECUTION_PAR TBB::tbb) if(ParallelSTL_TBB_HAS_EXECUTION_PAR) set(ParallelSTL_USES_TBB On) set(ParallelSTL_LIBRARIES TBB::tbb) message(STATUS "Using TBB for parallel execution") endif() endif() endif() foreach(VAR ParallelSTL_FOUND ParallelSTL_LIBRARIES ParallelSTL_USES_TBB) string(TOUPPER ${VAR} ParallelSTL_VAR) set(${ParallelSTL_VAR} ${${VAR}}) endforeach() ROCm-AMDMIGraphX-46524e8/cmake/PythonModules.cmake000066400000000000000000000172621510465702400214440ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### if(COMMAND find_python) return() endif() if(CMAKE_HOSTS_SYSTEM_NAME STREQUAL "Windows") cmake_minimum_required(VERSION 3.20 FATAL_ERROR) endif() macro(py_exec) execute_process(${ARGN} RESULT_VARIABLE RESULT) if(NOT RESULT EQUAL 0) message(FATAL_ERROR "Process failed: ${ARGN}") endif() endmacro() set(PYBIND11_NOPYTHON On) find_package(pybind11 REQUIRED) if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") function(find_python version python_executable) cmake_path(GET python_executable PARENT_PATH _python_path) set(PYTHON_${version}_EXECUTABLE ${python_executable} CACHE INTERNAL "" FORCE) string(REPLACE "." "" _python_version_stripped ${version}) add_library(python${version}::headers INTERFACE IMPORTED GLOBAL) set_target_properties(python${version}::headers PROPERTIES INTERFACE_LINK_DIRECTORIES "${_python_path}\\libs" INTERFACE_INCLUDE_DIRECTORIES "${_python_path}\\include") add_library(python${version}::runtime INTERFACE IMPORTED GLOBAL) set_target_properties(python${version}::runtime PROPERTIES INTERFACE_LINK_LIBRARIES "python${_python_version_stripped}.lib;python${version}::headers") endfunction() else() macro(find_python version) find_program(PYTHON_CONFIG_${version} python${version}-config) if(EXISTS ${PYTHON_CONFIG_${version}}) py_exec(COMMAND ${PYTHON_CONFIG_${version}} --includes OUTPUT_VARIABLE _python_include_args) execute_process(COMMAND ${PYTHON_CONFIG_${version}} --ldflags --embed OUTPUT_VARIABLE _python_ldflags_args RESULT_VARIABLE _python_ldflags_result) if(NOT _python_ldflags_result EQUAL 0) py_exec(COMMAND ${PYTHON_CONFIG_${version}} --ldflags OUTPUT_VARIABLE _python_ldflags_args) endif() separate_arguments(_python_includes UNIX_COMMAND "${_python_include_args}") separate_arguments(_python_ldflags UNIX_COMMAND "${_python_ldflags_args}") string(REPLACE "-I" "" _python_includes "${_python_includes}") add_library(python${version}::headers INTERFACE IMPORTED GLOBAL) set_target_properties(python${version}::headers PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_python_includes}" ) add_library(python${version}::runtime INTERFACE IMPORTED GLOBAL) set_target_properties(python${version}::runtime PROPERTIES INTERFACE_LINK_OPTIONS "${_python_ldflags}" INTERFACE_LINK_LIBRARIES python${version}::headers ) py_exec(COMMAND ${PYTHON_CONFIG_${version}} --prefix OUTPUT_VARIABLE _python_prefix) string(STRIP "${_python_prefix}" _python_prefix) set(PYTHON_${version}_EXECUTABLE "${_python_prefix}/bin/python${version}" CACHE PATH "") endif() endmacro() function(py_extension name version) set(_python_module_extension ".so") if(version VERSION_GREATER_EQUAL 3.0) py_exec(COMMAND ${PYTHON_CONFIG_${version}} --extension-suffix OUTPUT_VARIABLE _python_module_extension) string(STRIP "${_python_module_extension}" _python_module_extension) endif() set_target_properties(${name} PROPERTIES PREFIX "" SUFFIX "${_python_module_extension}") endfunction() endif() function(py_add_module NAME) set(options) set(oneValueArgs PYTHON_VERSION PYTHON_MODULE) set(multiValueArgs) cmake_parse_arguments(PARSE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(PYTHON_VERSION ${PARSE_PYTHON_VERSION}) if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") add_library(${NAME} SHARED ${PARSE_UNPARSED_ARGUMENTS}) else() add_library(${NAME} MODULE ${PARSE_UNPARSED_ARGUMENTS}) endif() pybind11_strip(${NAME}) if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") py_extension(${NAME} ${PYTHON_VERSION}) endif() target_link_libraries(${NAME} PRIVATE pybind11::module pybind11::lto python${PYTHON_VERSION}::headers) if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") execute_process(COMMAND "${PYTHON_${PYTHON_VERSION}_EXECUTABLE}" -c "import sysconfig; print(sysconfig.get_config_var(\"EXT_SUFFIX\"))" OUTPUT_VARIABLE _python_module_extension) cmake_path(GET _python_module_extension STEM LAST_ONLY _module_name) set_target_properties(${NAME} PROPERTIES OUTPUT_NAME ${PARSE_PYTHON_MODULE}${_module_name} SUFFIX ".pyd") else() set_target_properties(${NAME} PROPERTIES OUTPUT_NAME ${PARSE_PYTHON_MODULE} C_VISIBILITY_PRESET hidden CXX_VISIBILITY_PRESET hidden ) endif() endfunction() set(PYTHON_DISABLE_VERSIONS "" CACHE STRING "") set(_PYTHON_VERSIONS) if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") find_program(PY_EXECUTABLE py.exe PATHS ENV windir REQUIRED) py_exec(COMMAND "${PY_EXECUTABLE}" -0p OUTPUT_VARIABLE _found_pythons) string(REPLACE "\n" ";" _found_pythons ${_found_pythons}) foreach(_found_python ${_found_pythons}) string(STRIP "${_found_python}" _found_python) # Ignore virtual environments if(NOT _found_python MATCHES "^\\*[ \t]*") string(REGEX REPLACE "^-V:([0-9]*\\.[0-9]*t?)[ \\t]*\\*?[ \\t]*" "\\1;" _tuple ${_found_python}) list(GET _tuple 0 _version) # Ignore if the Python version is disabled if(NOT _version IN_LIST PYTHON_DISABLE_VERSIONS) list(GET _tuple 1 _python_executable) find_python(${_version} "${_python_executable}") message(STATUS "Python ${_version} found.") list(APPEND _PYTHON_VERSIONS ${_version}) endif() endif() endforeach() else() set(PYTHON_SEARCH_VERSIONS 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13) foreach(PYTHON_DISABLE_VERSION ${PYTHON_DISABLE_VERSIONS}) list(REMOVE_ITEM PYTHON_SEARCH_VERSIONS ${PYTHON_DISABLE_VERSION}) endforeach() foreach(PYTHON_VERSION ${PYTHON_SEARCH_VERSIONS}) find_python(${PYTHON_VERSION}) if(TARGET python${PYTHON_VERSION}::headers) message(STATUS "Python ${PYTHON_VERSION} found.") list(APPEND _PYTHON_VERSIONS ${PYTHON_VERSION}) else() message(STATUS "Python ${PYTHON_VERSION} not found.") endif() endforeach() endif() # Make the variable global set(PYTHON_VERSIONS "${_PYTHON_VERSIONS}" CACHE INTERNAL "" FORCE) ROCm-AMDMIGraphX-46524e8/cmake/RegisterOp.cmake000066400000000000000000000045151510465702400207120ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### function(register_op TARGET_NAME) set(options) set(oneValueArgs HEADER) set(multiValueArgs OPERATORS INCLUDES) cmake_parse_arguments(PARSE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) string(MAKE_C_IDENTIFIER "${PARSE_HEADER}" BASE_NAME) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ops) set(FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/ops/${BASE_NAME}.cpp) file(WRITE "${FILE_NAME}" "") foreach(INCLUDE ${PARSE_INCLUDES}) file(APPEND "${FILE_NAME}" " #include <${INCLUDE}> ") endforeach() file(APPEND "${FILE_NAME}" " #include #include <${PARSE_HEADER}> ") file(APPEND "${FILE_NAME}" " namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { ") foreach(OPERATOR ${PARSE_OPERATORS}) file(APPEND "${FILE_NAME}" " MIGRAPHX_REGISTER_OP(${OPERATOR}) ") endforeach() file(APPEND "${FILE_NAME}" " } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ") target_sources(${TARGET_NAME} PRIVATE ${FILE_NAME}) endfunction() ROCm-AMDMIGraphX-46524e8/cmake/TargetFlags.cmake000066400000000000000000000107541510465702400210340ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### function(eval_and_strip_genex OUTPUT_VAR INPUT) string(REPLACE "$" "1" INPUT "${INPUT}") string(REPLACE "$" "1" INPUT "${INPUT}") string(REPLACE "SHELL:" "" INPUT "${INPUT}") string(REPLACE "$" "0" INPUT "${INPUT}") string(REGEX REPLACE "\\$" "0" INPUT "${INPUT}") string(REGEX REPLACE "\\$]*-NOTFOUND>" "0" INPUT "${INPUT}") string(REGEX REPLACE "\\$]*>" "1" INPUT "${INPUT}") string(REPLACE "$" "1" INPUT "${INPUT}") string(REPLACE "$" "0" INPUT "${INPUT}") string(REGEX REPLACE "\\$<0:[^<>]*>" "" INPUT "${INPUT}") string(REGEX REPLACE "\\$<1:([^<>]*)>" "\\1" INPUT "${INPUT}") string(GENEX_STRIP "${INPUT}" INPUT) set(${OUTPUT_VAR} "${INPUT}" PARENT_SCOPE) endfunction() function(get_target_property2 VAR TARGET PROPERTY) get_target_property(_pflags ${TARGET} ${PROPERTY}) if(_pflags) eval_and_strip_genex(_pflags "${_pflags}") set(${VAR} ${_pflags} PARENT_SCOPE) else() set(${VAR} "" PARENT_SCOPE) endif() endfunction() function(flags_requires_arg OUTPUT_VAR FLAG) set(_args -x -isystem) if(FLAG IN_LIST _args) set(${OUTPUT_VAR} 1 PARENT_SCOPE) else() set(${OUTPUT_VAR} 0 PARENT_SCOPE) endif() endfunction() macro(append_flags FLAGS TARGET PROPERTY PREFIX) get_target_property2(_pflags ${TARGET} ${PROPERTY}) set(_requires_arg 0) foreach(FLAG ${_pflags}) string(STRIP "${FLAG}" FLAG) if(FLAG) if(TARGET ${FLAG} AND NOT _requires_arg) target_flags(_pflags2 ${FLAG}) string(APPEND ${FLAGS} " ${_pflags2}") else() string(APPEND ${FLAGS} " ${PREFIX}${FLAG}") endif() flags_requires_arg(_requires_arg "${FLAG}") endif() endforeach() endmacro() macro(append_link_flags FLAGS TARGET PROPERTY) get_target_property2(_pflags ${TARGET} ${PROPERTY}) set(_requires_arg 0) foreach(FLAG ${_pflags}) string(STRIP "${FLAG}" FLAG) if(FLAG) if(TARGET ${FLAG} AND NOT _requires_arg) target_flags(_pflags2 ${FLAG}) string(APPEND ${FLAGS} " ${_pflags2}") elseif(FLAG MATCHES "^-.*") string(APPEND ${FLAGS} " ${FLAG}") elseif(EXISTS ${FLAG}) string(APPEND ${FLAGS} " ${FLAG}") else() string(APPEND ${FLAGS} " -l${FLAG}") endif() flags_requires_arg(_requires_arg "${FLAG}") endif() endforeach() endmacro() function(target_flags FLAGS TARGET) set(_flags) append_flags(_flags ${TARGET} "INTERFACE_COMPILE_OPTIONS" "") append_flags(_flags ${TARGET} "INTERFACE_COMPILE_DEFINITIONS" "-D") append_flags(_flags ${TARGET} "INTERFACE_INCLUDE_DIRECTORIES" "-isystem ") append_flags(_flags ${TARGET} "INTERFACE_LINK_DIRECTORIES" "-L ") append_flags(_flags ${TARGET} "INTERFACE_LINK_OPTIONS" "") append_link_flags(_flags ${TARGET} "INTERFACE_LINK_LIBRARIES" "") # message("_flags: ${_flags}") set(${FLAGS} ${_flags} PARENT_SCOPE) endfunction() ROCm-AMDMIGraphX-46524e8/codecov.yml000066400000000000000000000001171510465702400167040ustar00rootroot00000000000000ignore: - "test/" - "src/driver" - "build/" - "src/netron_output.cpp" ROCm-AMDMIGraphX-46524e8/dev-requirements.txt000066400000000000000000000032611510465702400206020ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### ROCm/rocm-recipes facebook/zstd@v1.5.7 -X subdir -DCMAKE_DIR=build/cmake ccache@v4.1 -DENABLE_TESTING=OFF pcre,pfultz2/pcre@8.45 -H sha256:d6f7182602a775a7d500a0cedca6449af0400c6493951513046d17615ed0bf11 danmar/cppcheck@bb2711c22a0be09efe7f1a8da3030876471026c8 -DHAVE_RULES=1 # 2.11 RadeonOpenCompute/rocm-cmake@dfaa4ddba4dbb2e1c6e9964ce610e2a12fd93f39 --build -f requirements.txt ROCm-AMDMIGraphX-46524e8/docs/000077500000000000000000000000001510465702400154705ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/CMakeLists.txt000066400000000000000000000027011510465702400202300ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### project(migraphx-doc) find_package(ROCM REQUIRED) include(ROCMSphinxDoc) rocm_add_sphinx_doc( . BUILDER html OUTPUT_DIR html ) ROCm-AMDMIGraphX-46524e8/docs/conf.py000066400000000000000000000051131510465702400167670ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html import re html_theme = "rocm_docs_theme" html_theme_options = {"flavor": "rocm-docs-home"} templates_path = ["."] # Use the current folder for templates setting_all_article_info = True all_article_info_os = ["linux"] with open('../CMakeLists.txt', encoding='utf-8') as f: match = re.search(r'.*\brocm_setup_version\(VERSION\s+([0-9.]+)[^0-9.]+', f.read()) if not match: raise ValueError("VERSION not found!") version_number = match[1] # for PDF output on Read the Docs project = "MIGraphX" author = "Advanced Micro Devices, Inc." copyright = "Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved." version = version_number release = version_number extensions = ["rocm_docs", "rocm_docs.doxygen", "sphinx_collapse"] external_toc_path = "./sphinx/_toc.yml" doxygen_root = "doxygen" doxysphinx_enabled = False doxygen_project = { "name": "doxygen", "path": "doxygen/xml", } html_title = f"{project} {version_number} documentation" external_projects_current_project = "amdmigraphx" ROCm-AMDMIGraphX-46524e8/docs/cpp_user_guide.rst000066400000000000000000000001451510465702400212170ustar00rootroot00000000000000C++ User Guide ============== .. toctree:: :maxdepth: 2 :caption: Contents: reference/cpp ROCm-AMDMIGraphX-46524e8/docs/data/000077500000000000000000000000001510465702400164015ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/data/roctx1.jpg000066400000000000000000014302651510465702400203360ustar00rootroot00000000000000‰PNG  IHDR†šý¿î ÙiCCPICC ProfileH‰•—TSi€ÿ÷Ò-)¡7餄ÐB¤ƒ(„$PbL͆ÈàŽPGtPDÁѱ ¬(6ì2((ë` (û€%ÌÌžÝ={ÏùßýrßýoùÏûϹ€Ä‹Óa%2D™’0?/zLl×HÊ0®TÌ ˆÌè¿Êè}Ä‘;–“±þýý_ÊŠG8‰'åf ܆¬a®X’ ê(b7ÈÎOò]„i¤@„&9ešÇ'9iŠÑJS>a,„ À“9I dÄNÏ⦠qȡۈxBÂù»sÂH^0/#cÙ$!lŠø‹ Ðf$ý)fÊ_â'Éãs8)ržîkJðÞB©8“ûÍÿ–ŒtÙLcd‘ÿ0D«#ç÷ mY œEI CfXÈ›òŸbÌ?r†¹RVÜ ó8Þò½é ƒf8YèË–ÇÉdGÌ0_ê>Ã’eaò\És†9’Ù¼²´H¹]ÀgËãç "¢g8Kµp†¥iá³>,¹]" “×ÏùyÍæõ•÷ž!ýS¿B¶|o¦ Â_Þ;g¶~¾ˆ9S#¯Ç÷ö™õ‰”û‹3½ä¹Äé¡r~ºŸÜ.Í —ïÍD>ÎÙ½¡ò3Lå„Î0ÁÀ8G„XÀy&`ŸÉÏÉœl†µLœ+¦2éLäÆñél×jÝÎÆÎ€Éû;ýI¼0u/!5ü¬-‘€mb„gmœW@iɬÍt¤<ÎÝàÊ$YÓ6ôäˆ@ЀÐÀX;¤JWà |@ ,\ @²ÁJ°°l•`Ø‚#àh§À9p \·À=ðô€~ð ƒQ0A¢@THÒ…Œ Èb@î…A±P"”‰ ´Z•@¥P%´ªƒ~†NBç +Pôê…¡wГa¬ ÃÖ0fÂp¼N—Ãyp!¼ ®€kàÃp|¾߃{à×ð  H(5”ÊÅ@±P!¨8T2J‚Z*F•£jP ¨VTêª5„úŒÆ¢©h:ÚíŠöGG¢¹èåèÕèèJôAtúúº=Œþ†¡`´0 ƒIÁdcŠ0å˜ZÌ ÌEÌ=L?f‹ÅªaM°NXl,6»»» ÛˆmÃvaû°#8NgsÃ…à8¸L\n'î0î,î6®÷ OÂëâíð¾ø8¼_€/ÇŸÁ߯¿Ä”FBGÈ%l&ì'´nú cDe¢ ÑAL%®%Vˆ‰OˆïI$’>É™´ˆ$$å“*HGI—I½¤Ïd²9™EŽ'ËÈ›ÈÈmä‡ä÷ ŘâI‰£dR6Qê(ç)Ï(Ÿ¨ V lžÂ…*…&…Û o ŠFŠLÅ¥ŠyŠåŠÇo*)”Œ•XJ¥ÕJUJ'•º•F”©Ê¶Ê!ÊÊ•)_QPÁ©«ø¨ðT Uö©œW飢¨T•K]GÝO½Hí§ai&46-•VB;Bë¤ «ª¨ÎWRÍQ­R=­Ú£†R3Vc«¥«mV;¦v_íËí9Ì9ü9æ4̹=ç£ú\uOu¾z±z£ú=õ/t 4­ÍO5Ñšæš‹4³5wk^ÔšK›ë:—;·xî±¹´`-s­0­Zû´®khëhûi‹µwjŸ×ÒQÓñÔIÕ)Ó9£3¨KÕu×ê–éžÕ}EW¥3ééô úú°ž–ž¿žLo¯^§Þ˜¾‰~¤~~£þS¢à ٠̠Ý`ØP×0Øp¥a½á##‚ÃH`´Ã¨Ã裱‰q´ñzãfãu¶IžI½ÉSŠ©‡érÓÓ»fX3†YšÙ.³[æ°¹ƒ¹À¼Êü¦láh!´ØeÑ53Ïyžh^ͼnK²%Ó2˲޲×JÍ*ȪÀªÙ굡uœõVëëo66é6ûm󻯯ضھ³3·ãÚUÙݵ§ØûÚ¯±o±;ßb>þîù¨ÁëÚ¾::9J ªº4F(c#ã²3ÆÙËyó)çÏ.Ž.™.Ç\þpµtMs=ä:°ÀdÁþ}nún·½n=ît÷D÷Ý{<ô<85Ï= ¬‹^×Z¨]˜_Ø÷ßwõE E’¢îõ®ë÷|þ^ø}çû ;7|+æ_-±))/ßÈÝxõÛ*~˜Ø”¼©s³ãæÝ[°[D[îoõØz°T¹4¯´o[ð¶¦2zYqÙ‡í Û¯”Ï/ß³ƒ¸C¶£§"¨¢e§áÎ-;Ç+•÷ª¼ª«µª7TÜÅÛu{·çî†=Ú{Jö|ùQøãƒ½~{›jŒkÊ÷a÷eí{±?jÇOŒŸêj5kKj¿è9vðBS]Ý!­C›ëázYýàáøÃ·Žxii°lØÛ¨ÖXr•}õsâÏ÷k?Î8Þð‹Ñ/Õ'¨'Š› ¦Ü¦áfAsOKlK×É€“í­®­'~µúõÀ)½SU§UOo>C 769 1158 Screenshot "ƒŽÊ@IDATxì}À]Eµîü!ô$¨Hð^)$žE)R¥x…k¹ *%JW è+\i®^Ë£IBSZ)JïR! E t$rÞ*ó­YkŸ}þ„_ì³áß³Ê7³ö|³fö>;ûìÓס-µllìóv2tÈ×êËŠb] ˆRvR‡j¡ž6 €å.jüÊ?ò¨æ_uýñ«©[1!JY×ßzþ©ç_œ7üŒ‰WQÃ2+êõG½þ@ÕëzýQ¯?üjêVLˆRÖëzýQ¯?pÞð3Æ®-ZL!s‘á¯yý1yúú­¯y¿ˆz:×Wðjw^ø¤tI*ÜCÙ` N×øžþTù/¹!ÄPÊ kT/~µ;oÍ?Í¥:ÿˆ‡ºþØÌÀ”©ë¯ÎwΩç$–ލóâkyĬ±,Jç…¯®?ÂŽ1Jëü“Œá«>luþ.˜“zýù¨ëÖÙÿΧ;@6s”'ž?0ñE}ùÖ¸·£Yèò5 Uj‰­ÅáM5~å¿æŸÎF?/êü‹ tqÓ04ÔºþÂI 1ÞT×ߺþÖõ·®¿¼`úuAP·ëò5 UjŠ­ÅáMuý©ëO]êúà †_ÜÒSÖol€j©Óâð¦ºþÔõç_qýdw€lª”›B<AŠÈ~â±Ì3(o¸‘£Þ”%@Æ?UÈO#‰-:¤µbªñ+ÿÈÊ$Êš`Âq£¤ÔùÇÔÔõ§®¿:EÂÚQÏ?BŠp‰)v‘êù·ž‘ õü &tE¡}½þ0* 7õúƒI©×_‚͑’LŽæˆØ¢ƒ+…kÙºþ‚ ºþ‚ IÞÕõר(ܼ1ë/=±*[¸¤Ñr(Ž“IÄ8à Ä%£-v´¤"íð¥\ØséïÊ–Ñ®ñ…!¹0Ͷȶú”J/«Eölæqªü;RŠXó/~TfêüdJé¼’iDÆ:ÿ4C +‘õº½G»ºþ8RŠXןºþø@šuýd‘)+ Ûâj£>åÌËj‘=›ëõO]ëùÇMŠ"ÖóO=ÿÔó?«ðÜø×>ÿÊ!½·SN¾øŠ*¸)KHoIν 3(ÝuºÜZvåP¸ƪ»‰.Kh3(]P‰ÁVk¾Æ'2*ÿœ– !Aºs¨i )”&²ëÖ§*ÿDTå¿æ_^ŠxÚØÝ=‡š–0å‚ÒDÖù×E%]F§MÍ¿îÉÓÃr*(ݺÜFzÍ?£‚i«ù×<=,!§‚Ò]¡Ëm¤×ü3*˜¶šÝÉÓÃr*(ݺÜFzÍ?£‚i«ù×<=,!§‚Ò]¡Ëm¤—ü$ q±(*{s3±°hjÖÇÜ %mNqò/7âêÎVQÔRãYL…ÐÁì*âòŸPvjþIÊäT)ùSHªóäðԪ뮶uý•¬2”‘zþ!Fd1ᵃÙÉó&ya·®¿BYæ¨ð—i"{]AN]ëù§žõlSÏ¿²*ÊH=ÿ#r2ás³“×ÍXä ¼õü+”eŽ ™&²×ó/ÈY¸ó/=14ŸjÈtÄC#–t¸I¶°¼¾2ÔÇñYƒê•}–=̰)Ôg…·_y…§Ž¿äHH”L=̰)Ôg…·šÊCÍ?IÉ‘(™*z˜ `R¨Ï o5ÿ”‡š’ ’#!Q2=Tô0À¤PŸÞjþ)5ÿ$$GB¢dz¨èa.€H¡>+¼ÕüSjþI2HŽ„DÉôPÑÃ\ B}Vx«ù§<Ôü“d ‰’é¡¢‡¹ …ú¬ðVóOy¨ù'É 9â…~®^3$“%“ ûéLªˆ¼ŠˆÞZ‰åîŽ[Rr½_X(Du‘YùÏYGE–º8êe(´ÖüîxºÕùçÒ¥®?LF]ÿ›ó¢¤H]óªKE– 9 ¯3¬®¿Â]]‘9sêúËDÔõ׊œ¥¨ëo^u©ÈR!gãëúË$ÕóäN=ÿ`Bä™SÏ?LDóüÓG¿V¯/ÐÏ4YÁoŽΔ8³g¡,8Mé’ÙÞZ=צ‚ƒ·CÈ)ŽV¯„h÷Ôø•—“­IRóO&Wuý¡ùÑ>Eêú[Ï?¼Ž¶fG=ÿöd†|zÁÖJ_=ÿÔó%F=ÿÖó/¥AûQÏ¿õüË'‘Öì¨çߞ̣ž9qtkMŸ_ Ò/åF¡Übð’žTH<ÏMHÁ·Ÿ€;N]08"x ü‚@ðÖø•Í2M/KGŸz5ÿˆ:ÿêúS×ßzþ‘“tË2YÏ¿zʨ×õú ×TœõúSçïëõ7“R¤^òÇ;£ƒÅ°ÕëÏzýY¯?ÿ¡¯?ËC|RäÙŽ2Ìô¨(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$(CĨ(¤7°ÛÓ´Ý$(cÈ )¤7°ÛÓ´Ý$¹¤'†òA¾lÆ6FðoT:Q¡=î¦Mi]Þ‹6Cº HŸic‚éNTª*ÿ D ƒær*3Yó“ Uç_]xr¸¹‚©ëo^5x=)kŠëúËÕó²Cš›Sê® *ª®¿uýåŒp¹R×_"õü“— ^)ÊšâÅzþaŠêùÙ¡ Í­)ê® *ªžêù‡3ÂåJãü3!qʇhFÚ‹2“xK2z,Ú¢×e1¿ø -’YŸf/ Š:±_y¡E°PaT•¥‘yîTþkþi²Ôù‡LYRןºþJ"ÐNÙÁ³Å¯£E®ë¯®$F0Þ¸,\edªçŸÂ$a‰”zýÃYBl1`'Û$˜©’Suþ )…a¼qY¸ÊÈ:ÿêçœ õú3Ef )uýåÔ 6„°“m’5ÌTYSêú+¤J„ðÆeá*#ÿ¡×_zá¿^¾¡[’(¢øÎ¢«í%ò¾jª‹ÒИ! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ܦ˜Á-ˆ‹Ò ˜! ÁmРܢ‰¸( €¢ÜY¡Ÿ«/÷ºXÖ;ƒª7ó^îú0Ðm¸©$ï«#;J…P[À­r3RªSö¨/nQQ¬ŠÏµ¸× HÄE©0Âg€Ô¤]³Ôg|í¿°@»ÂŠò•Y«ü3AaSÈ;” "Þjþ ’A´Ë™d‚?6Ôù',Ю°¢|eÖêüc‚¦0ïP*ˆx«óO¨ ¢]Î$ãü±¡Î?av…å+³Vç60…y‡RAÄ[B…dír&‡à uþ ´+¬(_™µ:ÿ˜ °)Ì;” "Þêü*$ƒh—3É8l¨óOX ]aEùʬÕùÇ… LaÞ¡Tñö8ÿ䉡ÒK|× ¼^ãaÄæ¥Á,õëth‡3ÑÁy­Æ¯ü×ü«óOÖ¿0¸EÅ~íp&šP×bÀ³Q×ߺþÖõ·®¿²&ø…Á­¨*öëth‡3ÑÁy­®?uý©ëO]dMð ƒ[Qêúà ôKŽcËáL4Ap^«ëï?ïúÛ¸1SÈ'Ëž,ª·L©ÕD‹ÞRÙ›¼\Z‚¤Þþ1À¢_-RQ èM^6€ êícà<5 Z¤¢Л¼lÔÛ?ÆÀ5>Q¡ïdÒŸÂ[ yÞäåÂ$$õö¶æ_“-Ñ[Èó&/&!©· °•ÿ&[¢·çM^.LBRoÿ`+ÿM¶Do!Ï›¼\˜„¤Þþ1ÀVþ›l‰ÞBž7y¹0 I½ýc€­ü7Ù½…_„®ÌY‘URœÖaKD¢#ì:Ó¿ôü£CJ‰†²ÙSŸ!ŒÐx[XÓΪK^R£¹ÝŽ~‰SRS\5¾Ð`ìVþ5-05ÿêü‹3¤®?y±¨ë¯­š”!õüc§^1Š"Ü@¯ç_ä J% x*ÖëåÆªëo¦¢®¿.'êú[–Žºþ–åS®ÝhÍç£zþÁœAYÏ?œ"`yb CõÂÀuýuœ¼Áë¯û*'h Ä—Ù:$E’9îw±ŠyšfèZú½U!(µ•¨Eòh‘cs7ÍÐçÓ—"/½äâ4wÞ|z‡6÷Ž=)-½ôÒiÅW ¿•Òâ‹/.¶µ“îž4)=4í!²ž)Òj©J½Ï¥Æm¶Þ:-¾Ä¡'Ï?ÿ|ºô²ËÒýS§¤O?“Ö\cdÚ|‹÷¥Q£Öpl§ôðÃÓÓ]wM”Ø›o¾E2téÀÿÌ—f¦«®ºRâoúÞMÓ2Ë,#Çñò+/§+® ;EÝ`ýõÓÛþíßB|n—ðÙçžK7\-éÔsׯËÌÌ|ÚJ;í´ƒà‘Ï¿ð|ºìÒËÒÔ©SÓ3O=F®1*mAýYƒJlˆ×ÔÕî÷@pkyþ‘‰-r¬b–~oUHJm5>X/Lx¶DŽ”™»i†®¥ß[€R[‰Z$9V1wÓ ]K¿·*$¥¶µH-r¬b–~oUHJm%j‘ûì-7EpÜûî·_úùÏ~&Ý3¬ÜŠM=öècéßßþïf<é¤Óç>ûùôÒË3©i&§l›l²I:ùä“Ój«­&.–÷ÜsÜ{老ƨ57®yÅï/O[m½øï¸ýδκ£E¾ýöÛÒ˜1ë‹|Áù¤>°“Ⱥ‹üß|óÍé]ïz—ó{QûÏɪʾ™ÆñùVÑI'œ>û¹Ï%¾I¥wÅ­›ô‡ü«½c5˜¨Œñ#ˆ­ñARãç1§ñÂýù<èÂÆ_³@‡sCǘ`öHGl¢‡fO¤~¶šHŸšuþåÉ¢‹ŽÌšºþðâXpÜÔõ—È>(o/œ:ì«ëo=ÿôsòå,¡4ùWøüÓ‹…Úÿ:þ5ÿùlQ¯?qý9˜ N ){œA²[0O>ñDZl±Eӛ߲¬ÜÊHéèÍ'å'<-FOâ¼å-o‘su®¨Aißÿ‰'¸ÎTçÍá¨$Ý”‘!ÄÁâç ùéŠG1—><­µÖZiÞ¼yéÉ'ŸLSï›*‡µÝš0áÂ4á‚ ìøXàP¼9’ö_wØ[šÝtb:ÕwÉÅÓž½¸Šl#GŽJ+¯¼Rºñ†ÓÌ™/¥ë®».½ç=ïI=öX¦,äC‘£GS­ãç”o‹.¶XZ–ò€7inãÿøO¦%¨Î›ßLù¶ñ9ÎbTGrÁs)Mxyñmy±‹R×_ œ˜´Ú–ÿþjüÂÆÂŒå?/ï5ÿzþs†á¤X²M¥:ÿ #uþq¢äs¬¬åÄDÊÞç_a±®ÿÂ_!¤Ì_H¤2ßXòV‘kþÑõ/eK:y RÖõ§^ÿÕë? ´ÏsÂVÑzþ%*„;É´Ým ëçvˆPµÀ( §u×Y'}zì§•o6 ¨ÔµIÉȤ´îºë¦±Ÿþ4[òÍmK ¶+m°iõ¨ÎgƪW\±Žh Ÿ“¦Èûì³/}ëŠtõÕW§)S¦¤?=úhÚj[}"ç¢ '¤ëo¸ÞúOUÉq 6T°ŒŸJ“éëaSɓéoŠÜlBÿpôÑÊռ忛Òä)“Ó¥—^š^xéÅt>ÝtZa…Ó©¿>5ßÒ#äÏq¼ÉÑ’¬QÉ@‚Lu²eˆ–¬Óýñ†ñˆO"9Ñ‚öôÚ£å˜'O™jý8þøôFz¢h2õk ëúª³lÔÌÑG#Á9þÍ7Ý$ý¿ô²KÓ‹Ï¿˜.8ÿü´âÛWH§œzj¹)ÄñÙ¤}D/Ø‚MGÌdyÝuÖMŸËyëˆÆ;©Pêrÿ9G%ß^Güu×MùöimŽ)ÇgQ ߯#~Rã á´«ü—\ô¶¬†Dã]Kþ²æ_Φ¼þ3]F\vYQÖ1N-\‰-Æ*Üjªüƒ ¡©¿óWùgb.±Ùf9Wó¯Î?MŒºþÔõ—2!®¢ñ®ž@B=ÿîöz½þ#Fx«×¸®XØë/¹1¤•ôJÄ/?¸!Âk~’ž.™ùòËÊ·r.Ä3€ëZ}© ×yù%®Ã—¼åÖBAžÜ#fÐm·Ý.m¸ÑF à=ÊN;} MŸþHÚnÛmYÕmaãÚúD‚Õ';óÿäŒ'%ß°C‰H»\—Õ3žÒ¯»¹Özö?7ù䌧)ßfºŸÜrP¯'¾Ô‘ å8AJ¿Ì?ä1ÛàU,Žå¿›§šœXßòlEÉ!ÉÚÕüË)5JKœH‘,–Ü1Oƒ§:ÿ˜²:ÿ4?£§4Î)2[þp–Ùõg>í)…ìaI7K]óÔü#~Œ â’éªù§ŒÔü“Éh(sH|´«ó/¤Ô(-b*óJ§XáÎ< \Lß?Îú#7†d0ù$d£ª9ÀÏøò;f°Í§¯1ä%~njۣˆUªä¶æÏç¯$¥ôẩ—l$CGâiÛlíd7 u¨|iæÌ|Òd+ÓÚrŒb§4]ŽUu®¥›„¶øÙ¨Ùš«v?„#œ;wµ™ï4ÂÈ%äÜUì%þFn(žë¯¿>Ý3éíºÅ§8|³©ŸÛ Í#>;xã*¤ô;fGn©14”©wñµ:^;du¬ÿÞ`êùo '©î¹g’,Mè·Î¬±®¶"ÿ9P³ÿlù¦í½D_½³0"fŸœ£¼ñ×õü&_¾sý÷ù'¹G½q¨2Gw=Òæò¡û¶#v‡nàºÿ¾áæø³¯Ñ.›j|ðRùç|­‘'5ÿˆ7ÿ•#ͺ$78ó럒Jû&†›e§6Â’n \åŸh©üÇäh;ÿe„uý¯ç_Ÿ,7Ö6Õõ¼èzΜ4yªë/qR×_I KŽºþ zéR¯škÆ¿òõ_ù*ߨÀ–×W~÷ΛèׯöÜcÏtþ¹çÊ»xØõì³Ï<ƒ^F|bÚy—ÓZkЋ’óÝ '¨Î2Ë £÷ëì™Î=Oëð™ë¹gŸ•§?N:餴ówN£Ö¤_±Ê ÇYæÍoÒ:çž—ž¤wÄð =Kufï¤OJÿ¹óÎôËWkè %g¾l(G^3HS€š{â Ôÿ³Î:‹ZTÛzcÖ•š¢é½-ÑùI¦ô ?•ÂÇ=ã©é)~GOFgêÒ‡>üaižß¿óÎÿóNúúÝØ4‰^*-ä&—?HãJ|4B`±’ŽRTÏ ñ3Cðh ²—v4ïŽWqˆ™ô~„úCÛ‹ÜŸwþŸ4–¾N8‰nxñVª.$ÿ­ñû„ÏaËPP¾YîPûÏ>ýlzŠóàdÊɃQùæ>Qôf®CùvåΜ;Ôg|¾Q59wrÇ8¿†½iXÚkϱéÜœ×Ü Î7ÉkÊ7ÎkÎ7 DÇ[úÈ=ƦDߟ×´ÚÆ‰a(j|f£pSù׌ð9¢²p¢Ôµþ´¬ÿÆ-áQ5¶\óÏ8bjþÕü‹3ZÄ‘ºþÔõW®]œNv‹ˆXIw^L)*X|uýuÔ9ž2 Š@õüÏLìH+éÎëxU`ñÕüsÔ9žjþIŽ Qˆ¤¿ûùGON”½ ÿm»êÔÎÆ¿›Çºüiîó-³í²ËvèëeTo~çþûîëlüîÍçq4»²½¯³Ë.»tf¾Âu:û¨Î»›q¨}­+wp¤סŸg—:²s«¢30ª”ó;sæ¾fñwÝu×Îõ7ÞйöÚk:gŸ}V‡Þ›dǼ +t^æþÌ×öÝw_ó¡¡_ôèÏœ¹s]ÀNg.éŸØí'®ûþí·ï\véåÔ4=Û"ÐÝü²“&MRcŽÏÊå—_®~jãŽ;îÈþNç¶Ûn#»rtÁç«{ÈýgBL$ ËãÆ&íÒͥΤ{&…cBÿ¹9éÏ'>Á÷­2^Ëí¹?—]Þ™'ÇŠ€\ƒ6¨´;¾ußÔû:ïÚ¸™;eü%)>çÁ+2>š;>Ge\ò12ã´Ë.;SîÌ”Cºoêý”×'àq¨.ÝŒ¤ ÷QƒKÉ¥Ö¡8„÷¡¥ŽEZxaΜ¹Ö.nhº)o :´s÷ÝwÛ˜rëûì·Ös7€Çqò“æÆ÷J.œ0¡3f̘®›\[m¹eçÙgžÕ*„;ñ¤íØøÆ¨@n åøvcˆœzcHù¢_RkBÿ:7nÅÕþÛ )ªÕŒÏ qZO¸@ûÓìÿ–Ôz*¬ÿxM/‚ Ì~΃s¾!r¿þåÝ0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ×J49͉>0m¡t­D“ÓœèàÑJ׊Þr»‘=ø~€¦Î|SeäÈÕ;³fÍÊwÞÄùâ:å&Ïê#Gj b°Fìé¹nÜÐÏû8 0«ÒkÍD,ÖksæØñ 6´³êª«v†.6ŽG/Šî¼ðüóhÔ‚ñCz<}úúX‡¾ÞFå"?ùÄ ÒŸ0¬+‚HW^yeg‡w´ø|ce­µÖê¼úê«R÷ÄõÆÇ¡¯h•ö²ô{z*O݉'†ÈwÛ­·ÛS1ð!ë¶ f*–ÓC:¦åÆGæh˜®¼òŠÎŽÒŸ>‹/ý™­ý)Šä›ðrWgéˆjäΨÕ9f·Aɦ­ù:Ì#r±Pr#QGÇ7uFI¾µ<%„ Í#»sšh‚Å*–¿Ùœê™-&ñˆÝ9M4¡Æo¡°°“¡hñ¶˜*ÿÄ€ðâÈ1Ñ„š9· #uþ{.ÂÔ+“*š{U»sšhBÍ¿š]YU²#¦™j-ÞSiÔ9M4¡æ_¦¸0R×?ÏEw¶x[L5ÿˆáÅ‘c¢ uþÕùW¦J Ù䊒;fl1‰OìÎi¢ ÿùG¯®¡o»ñ튼©H“ÝF?V•該t=aA¿²µaš:õ¾ô½ï}Oî(Ð ‹‚Î"·¹ý$¢—g¿’¥”_b ×þ¶"bÄDÅ«ÞÆÃ¬%µ+ǨëldRŸÚ6Û|‹4a„tó-7§õƬ/FéÏÍ7)ÀdÁñ›ùÇú>ûì#-ì¶ënò«nSîŸJyðÝÜv9Œ?ç×áómýœ;ߣ:èM³ÿZgo«Ã9:å>ÎkÊ7·I¿ýˆop³¨¯¿ÿn¥ÙŸh¨ü‡ùWóOg\̃-:N„­®?`¢¹þç ZàõO]5Űæ0oëüc6a^„­Î?0QçfϘòù‡3I`Œ-ÙF¦R»Èuþ1CŽ/!Ôù&êü+3œðLj~þ ùô¯:ÿä®—ìò.)ÊÝ.¾%{Ê©§ðúÓ=zzçÊÌ?Õ3Œž¸aÎn»õ¶Ò„“N9åž‘Tg´¼è¡§wøkZR‡Þ‹Ã·Ñ|‰Ãu$ÎÚòŽŸ‡¦?Ô:lˆ´SâÄZ.äÅ9ù‰!îËá_;ÜâŸóÛßJ\Ž}ðAS;>ÆüξûéCC‡kø² À-ß=q¢ÅûÎw¾-˜é}GŸÿÎ9ç·®žË ?þ ùäΖ¾_'#˜îד'†\Í6ÑzFËãdzúòÄ Eˆ,›ÉkÞ÷çÛßþŽÙÛ«M‚Ȩ́œr²ôgô:œ;3;œÆpîôÑWçnmk¶sÊÉÈÎÑ—å‰#ú•9iç¶ÛKŽZLN¦|£ûpu8G)¯ùé¡¡‡òšs´q\AÏÞ×[øø&wÅñž,›É„×ÚGòcK>Ž÷x;7á}¯ÿ¬6 &û#£÷dÙL&¼þàþè©™ØRÖ¤ðoçÞ÷úÁj“`²4“5)¼ÇÛkü&k¯wŒYL®ü>Ï<3Þ^óÏxz½‰—ñÆ, &‹Ïóì=Þ^ùo²öz‡Á˜%ÁäÊ?1àóÌ3ãí5ÿŒ§×›xoÌ’`²ø<ÏÞãí•ÿ&k¯wŒYL®ü>Ï<3Þ^óÏxz½‰—ñÆ, &‹Ïóì=Þþ×ã?vE÷ò=4)ʽEz/O:`ÿ}`Ngÿö7i©¥–N+­¼r?þLª“ÒW¾ú•|'›?ƒë6çµ9é€LC—š~ó›³Ó’K.•V±R:ãŒ3ð•¯&árD±IœHÆM¿ýÍ9iÉ¥¨ÎJ+§3N§8ÔôW¿‚8Z«û._‰ŸHo—â<‹ï„ðKèÿƒô«U›l²‰øŽ?þ¸4ùÞ)®¡r\µÿˆo‘N=åTº)?”.m£ö¼Î<‹½ì²Ë‰oĈU¤ägŸõ„$M‚ßãE¾üpáÆâÓ½"><îmè°”±ÿ # ,s;€K}”ñWTJß:âˆtʯmX‹O•æÒ#eˆÿÖ·.[\ˆøv̹ŸsçÍIðYz:‹rçlÊ·%—Ö<àÜ¡cûÊW¿*U|ü×(Güìþ‰n5Öó_&¨žÿ˜—¤ÔóOá¤L&¦ØEªçŸzþA‚Ôó˜©Á»zþ1* 7õü+©!„9G ?œ7j[tŸl¢'¦e³§‚¬%œÊ¸ ¾±AzŽèjE늓ÖýN*ÒÎn2y'g<¨7·)Mj»Ò 9c4õi/»R‘v£ø¯Î~•~ÅmJš;ož|MnØÐaîàÚûÿ‹/¦i<˜”V¹š¼kGºñwÐÿ×^£þL™šæÐX«¯¶j:l™ÜŸ¿Oþ5qÿvã_ãszTþÿVëOÍ¿šuþÕõ§®?¸rÌ—+¹øg¿þ¬ë]ÿëú_×ÿºþ×õ?žùTk;ÿÉ!9q¸›?"rvÛÚÖsªÐ”­Gý.7j|&¡ ؃?v5·ÀiPšH9EˆÑš¯ü×ü«óæD¶ð a „,ýîÂ’”îj]n Zù7*˜¶Êwòô°„œ Jw….·‘^óϨ`Újþu'OKÈ© tWèré5ÿŒ ¦­æ_wòô°„œ Jw….·‘^óϨ`Újþu'OKÈ© tWèré5ÿŒ ¦ío˜ƒdäô(dhX•½‚h|ز©Wó 7”´9ùßáêÎVQÔRã£L…ÐQùo&^ÎFÍ+Ú×üc*êü“)ƒä°ù“Ó„ìuý9¼´ÔõW–Wd…(j©çÊ›?œ39ob‘'¼uýÊ2G…¿ºþÄK]‘uý­çŸzþÕ³mýü'«‚¡ŒÔëbDN¦|æ`vòº 9­Á©’1¥~†‘½ž@ÎÂè‰!ú­qb’«å‡Œtâƒ2€2Ôg…7$¨Wö((Ùõ0À¤PŸÞj|å¡ò/É 9%ÓCEs,@ õYá­æŸòPóO’Ar$$J¦‡ŠæX€ê³Â[Í?塿Ÿ$ƒäHH”L=̰)Ôg…·šÊCÍ?IÉ‘(™*z˜ `R¨Ï o5ÿ”‡š’ ’#!Q2=Tô0À¤PŸÞjþ)5ÿ$$GB¢dz¨èa.€H¡>+¼ÕüSjþI2HޏD¡7k†ôº)$9”_VÄ"¯2« ,J,wwØ’RZ—6j|¢!ðR¨•ŸÎeµò_ó¯¤ÅBIuþaZÕõGÖn^cÂ:£J]›¼”éU×_Éœzþ©çßzþ-ËÂBIõü‹ÓM=ÿÖóo>ÇÖë·vÔë/&£^æ¹á2âßâú³~­^@G’oIΆY ¯\ ´{"+`†¶‚òé’ Þ©ñ+ÿœC­ÙQó¯'3ä¨óG·Öô©ëÌ«ºþÖóO=ÿ¶ŸaêõO½þ“sGë ¤^ÐÙµrÔë|ñÑ‹¤zýQ¯?höÔë¯zýEiкŽþ¯?é—Èò:t>Üà%=¨Îx|nB ¾ýGÜqê‚ÁA˜ÁåÄ‚·Æ¯ük–izY:úÔ«ùG ÔùWןºþÖóœ¤[–ÉzþÕSF½þ¨×_¸¦âŒ¨×Ÿ:/x_¯¿™„– õú“?Þ,†­^ÖëÏzýù}ýYžâ“"Ïv”a¦GE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&A@"FE!½Ýž¦¥è&A@CM!½Ýž¦¥è&AÈ%=1”7òe?¶1‚ÿx£Ò‰ íq7€hJëò¾X´ÒEpX@jüLäHw¢RUù%J4—S™Éš˜\Ȩ:ÿêúÓÃͤH]óªÁëIYS¼X×_¦¨žš0ÐÜœR‡pQQuý­ë/g„Ë•ºþê©çŸ¼TðJQÖ/ÖóSTÏ?ÈMhnMQ‡pQQõüSÏ?œ.WçŸA ‰S>D3ÒÐöX”™Ä[’Ñcѽ¾(‹åÇâ¤E2ëÓì¤ 8†—˜^ÆÓïæ$êqéëq;Ôt¯DÉ`ªòωQó¯Î?žuý©ë/çVÇœ\Èù¤œSêùGH)§Ya¼qY¸ÊÈzþ­×9êõfJ½þ¢” 2êõOzýQ¯?8°:äœà¢^d”‹zýÅ<ІË,‘7\Â’²êϸþ ^kukÎb˜À‡Óï$5¥UÀ QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…à6Å„nÑ€D\”À QnSLˆà HÄEiPÌ…àÎ ý\}¹×IJޙ Ð|?“m È¥j²ÇM%y_YP*„ð 5i×lõ_ã ´+¬(_™µÊ?60…¼C© â­æŸP!D»œIÆ!øcCÂí +ÊWf­Î?&(l` 󥂈·:ÿ„ É ÚåL2ÁêühWXQ¾2kuþ1AaS˜w(D¼Õù'THÑ.g’qþØP矰@»ÂŠò•Y«ó ˜Â¼C© â­Î?¡B2ˆv9“ŒCðdž:ÿ„ÚV”¯ÌZLPØÀæJouþ ’A´Ë™d‚?6`þÉC†*ŽE¹™RÕkœ£<bóŽÒ`–úu:´Ã™h‚à¼VãWþkþÕù'k‚_ÜŠ¢b¿N‡v8M¨ë1àÙ¨ëo]ëú[×_YüÂàVTûu:´Ã™h‚à¼VןºþÔõ§®?²&ø…Á­(uýaú%DZåp&š 8¯Õõ÷ŸwýmÜŠ)ä“ÀeOÕÛ?¦Ôj¢Eo©ìM^.-ARoÿ`ѯ‚©¨ô&/Àõö1pžš-RQ èM^6€ êícàŸ¨Ð×ÕéMNá­…Fæú‘G‘/*¬·Þºéío'®ééï.ù]š;wž°À_{éæ n[j©¥Ò–[n)–O=•n¾éFj»/­±æšiÕÕVKòžk½L?þxºõÖ[¥½­¶Ú2-µäR™a×`›Œg—ÒD‹®Æ€õ&/(êíSj5Ñ¢·Tö&/—– ©· 3e é}4¦´­½ö;iþŒÙz—ÌŸ‡Ó ª¼î˜õtLs³hïÎ;ïL?úˆŒÑ;)/FP^Ä­“n¾ù–ô䌉Ǜí¶M‹-ºX€Ìœ93]yå•2æ+¬´bZgôÚä¯×ÿL’ð ²kÞäeñÙçžMwÜ~Gºþþô§ÇÒˆUF¤wÞ%­¸Â M¨è”Z5jjƒG}a¢ ¾É½ì ƒ¥VšÚàQŸC˜h‚¯@r/;ÃàC©U£¦6xÔç&šà+ÜËÎ0øPjÕ¨© õ9„‰&ø $÷²3 >”Z5jjƒG}a¢ ¾É½ì ƒ¥VšÚàQŸC˜h‚¯@r/;ÃàC©U£¦6xÔç&šà+ÜËÎ0øPjÕ¨© õ9„‰&ø $÷²3 >”Z5jjƒG}a¢ ¾É½ì ƒ¥VšÚàQŸC˜h‚¯@r/;ÃàC©U£¦6xÔç&šà+ÜËÎ0øPjÕ¨© õĉÿïÄΰ¡Ãø¶C×ìTÊG|Ñ7Ùä½ûï¿Ï8ñÄù§W3iÒ$²£-/¿ür«ÇwJ½Ã=TÚâö÷ÝwŸ\õ:Ñk¯CŸ4æYgi±ŠP°Å >”j°\þs_*\˜è@IDAT+—ûï¸È£G¯mãsæÊõ Ï¿hü7ÇßrÆ|äÈQÖÞyçocúÞ÷n"¼š“„óÎg¿Çô‡r®nþÑÑ72ÿ=ôË6çöÝw_„ÈåüÎ:ëŒÎcÑ×9óLÓfüµÖZËÆk¯±Ÿ¦º‘Å£Ž:Òæÿ 'œàâ(àÛßþ޶A¹pÉ%—Z|\{±u׋uü•©Â×/ñ«žóoï½÷é¼òÊËóÎ:ËæVY³Sgøðå;üã‹Öy½¦û12nX¯}ü˯ë5¯¿wÜq»Ôõs›×ÿ+¤A™Û>ô`çæ›nʹ…óF,ŸUÿøù¡Ìeëeg|(µVÔJK«ü£›­Üw¾÷•7UIÀ÷Øy3„‰îÇÎ(ŠøÐ\‡‡]6” „\+ÿš–!5ÿ2uþ¹œ …3¦®?ÄK!£®¿LF棞0gP*1ÐÀ“Fç}õ¢®¿™Šºþºœ Ä)KÙ‹ò†®?_|qÚs¯=Ó‹/½(W‘«¯>2m³ÍÖô$Ðѯ£ ~Ï{6IsçÌwrÙÏÄwZ¯?q°Têÿ–ã§6.ÍŸËmi_§M›–îšx'Á´B7XXûËõ_beÚÿyòOI<í´SiÜæ)įp}×DÒ3ɹ߃éK«\þF&ÿá×ËuåÁÐ4’ì#ß12­²Êˆl§úøÞ ͵×^Ÿþð‡+Š$J Ú4ŽßóXjØœaYüçáߺ Œ‹?¯ÿtÚi§¥ysçX»<ðPºóNSÚðyœE1ÐŽÈç'Ž&Ý; –tÖ™g¤9¯QØ2øÀÏ~Žž6â'T:é°ÃM/ä5[{ú©§Óá‡Mjì´ÃiÛm¶¹}þÇøF³€=ÎŽÐÀÿjãO·+Ò®»í–>ý™±Ä¸òÓ\þóŸ§ýèÇ6Þ¬ô#‘1âI¶ÁÑœ%ÏxêIÒ7H¼f»Ô¬´l†ÿâäèü@z,Ü(·%s»˜(ÍX¡?žäAiñ%–¤5bõ4rýqIÇW í‡ð">ZGx-!¿ :I[Ⳍd€‹/±³þ’tc¨AnVK¿|‡¹ÓeÓ.t  ÐPš%·ÈcBr_®TÚ¯ñ•ÁÌHå_è(ùQó¯p¡™‚}`‚˺þ„• 1S×_"ƒþçyTÏ?ºš”5‰’-Y-þºþ.x)[] ­õ÷èc޶ ·Üt³|ˆ¼ôÒKÓ‹/¾”&\p|­ë”SOMƒl8?Nkþ`Ó{þgx˜}é%úêÂu7Ü`m]xÑ…&ª†zþ2_ 9 wþIÄõÌtÃ× ÜÆ…^j©, 2DÆ}êä)RüLj›èëb“éÆEíw¿ûØ9¾Üøáñ—³@'}ã›GˆÏÚ•ÙDE™óuý+\~Xê½þ•<¦×Ýp£ÐÌÖ‹/¾€ö,Ñh ãšëzî¹çŠžþ å}ÝóŠ+¯Ùï–¢ø?úÑÈÄótf:þ˜cÅÍ-•u¢/õZ3r¸Þó¿ÄçF²&R‘ÙS¶Þý' ºEâÂåÿßüßþö·iüøqBß0™<é^[Ÿ}ö¹ô­#Ž ¯\í:ø`é?¯_üâ—„Ëat³…žJ·Ür#Õ™œÆŸ~†‘LO KÒ0x:XiÌ?øÉÃ_EÔMºç¹ýÍÀÿ| ñÑÔ7?ÑSˆ´FLMSdý ’Ö‹ã?Ñrsšœ×>V^OÚ¶æñ—³'¯‰ü=:œÞúø_V„M¥[GQ—U!„Íâ¢Ê<@r7Nm¨!jGeÀµRÞ“‘¶¿ò_ó¯Î¿ºþÔõ·žôìYϿăP« ¹ÈÐK ëõ‡Pó¯uýõ‡ßÿ®ûÒvïß6m°Ñ†|õhן;î¸SzøáåZ–¯+½o ñ»HrÒ„ëO1“Å>‚䔃å¼óÎK›mÊRûÒéü†kÓE0_·j þŒëß¹sçÓ»P®H·Ý~+½Sg"õazZý«Ó“PÛ¤}øÃi‘Á|™Nñæ§ôº16÷µ¹Ô÷õé)ˆms|*\üsÏ97Ý{ï=iÙe—Mûì½o¹þ¦¾`Âúp~±|zÛÛÞ–F•Yd°pÖ¡71ñúûÕ¯~•ºÇ}¤vu§qHæ.¿Ñ×ÿEãtsÍïzâøgžqºº˜^‰œ‡Z"Wa;C›yËÃGûôó|(¯½æêtõÕW§Í6Û,£f‡4Àmü™ýŸ7o^ºâŠ?¤Ûo»#Ýu÷ÄD_O“§¶ÞzëôaÓÁ2¦)ÍŸ??}ô1ô¾¤¹iý Ƥí¶ÝN¡ÿÜs~KOÑLNoYö­iß½÷ÖNRGé«&tC”Æ”n‚ñÝ·ýéÔ~îO©¶ìö"þ>y.;éüóϧùCcJáN?ýL j£Å} îPþâú7výøÇÒ°aC(_—Î=ç7iÛm·‘Z>ÿvÙegywØ•ôÞ™oÒ‰½÷Ù‡Ú§›AG%q>{ài-zï oš~/æ®ø‚mÿùó:é÷W\‘î¸ívÓ;iL§ËÓ$[oEcJOÅ ¦yÄAèÛBtcêiΜ9iƒõ7LÛÒû¤ÍÆþœsΡyzozË[hL÷ÝÛúO-¤ çO ›ÓM‰Éiù·ý[ZsÔHZÍý§ tWä«_ù*µÈ9N…›ÿÚCƾ7æó÷¬W^Nt49tȰtý ×§eß²¬Åç|>üð¯ƒdž~þóÿ¥vôd'ÛÿÒ“Cë#vÞ}ì¿>šn»õé?øA~ÊçÊô¾-·`—v…sB;&¶ÜI+îýñú+å>Æùí5×ÒܾJæ¶ö«·¢ìè^›Öu¼ä,ß}*üÓã‹N_[ë¬Iï@áKñ3ºÞ1ãëÙ@tÅ?õÔSè]8#ùóŠ´Ç}Ë=¤¤—'wè_Þ©mc‹-¶cXeÕU;óçÎsýVñÕÙ³„>¶/éËÙ?¿óÚks:;î´£ÔÍ!DÖxÚØ¥’;dmÄÞ¸õGß1”:cÖ#ý_A¸îtÿÓŸ¤ÿôÕ0ášóŒü>æBŽ&Ò¸ñã„?ÆÜ;éž–ó;òâxè¡O}òÒwºñ–ùÑ÷ ÿ=4t›?b|"q~‡žXëŒâ1•±äñÌúù´³Õ–ïËcª5eLÉ·)ÝPR££{öìÙÎ9nçË_æ1Õøs^}MÇ”ÛÍm£([•ªØ™©UpñÕï ý÷ÿËôÞ(Ž?f½1rÌôrvš?4¦yþ £¾àBgžyFO/­¦:òÑ»ss;?ùÉO¤ îÿk¯½–±1þwÞaý?ø Ïw=ì0‰ÏcýÔSOQwì":=DwJÂ󔾺(ÇN…ãÌý–[•yÊUß—çéª2¦sKÃ9¾ŽéPiOÇT!ÜGºÁã(ˆ×,}·|;ôn!SaŽR­)5 t“ÓúþÝï~GC70ÚDRè&§Ôá±›5k6ÜTjEº)fm~íð¯‰_×kí÷¤I÷jÙk[¯)¿î¼]×ëóÎ?OÆŸ× Ìmúê ÂtÎg?ÏA«‡|Èâ»Æ;ãÆ³õWâvõÍbþùv8¨!»…óoÉ\HÓÖ¾?‚7&~¹M&k³—·¼²ˆ&F2¸M4Þ‰OkñžÙ7¤Œ/WÒU‹%fŸõî­´!>‚¨…4bkF|¥n_ù·L©ù—§YXKêúÃ)a3$çe #AÀY]™‘È™h¼’ wõüSÏ?–)õü#KÉyý÷zÚ‚7þušwÒ/Ýklš4é±ñ,l›6è^ÿÕËC•ßd«W«dµæ¨´üðåÓc>*¿¾ÃOÚ0úCúü¥o&Ð6ðùñŗЯ M¥_ß’>½ÏÞé~úÓtÂO~œèF 5ÞIWлp~xüñ‰¥8@ʧM£_Ö¹6‡.ñÏŸp¡pÖ±c÷,×ýþ‘ßO’·ýöߟ޳s]:޾>_iãøÿ÷ë_O_;üp­Cá´UREÐøêÌGÃ&ñ)’÷mü3Zä—f|[*¯IO.½uùåÓ£Äõ·Ý–.¢§%¸ÿþЇÓ3ô«C²¹jÖŒ¥¨9€}m„+ÀÈñY¦ÿùkIøYv¦Ë.¿,]½~MßC¢ÇÆÕHr“YpÁ¥vÞ H‘¼çþÿŽŸÈâ1¥÷^íMO÷üìg?M?>á'‰n”"¥ßó˜þðG"óîS>¼œ–®¹–Æ”7Ÿ¿"É_•bÛØ½ö‚;yÔ‘:¦T÷€ýö—ºÇÇc:TŽãNãùuúÓ#¤ã“nľHƒœ¯®XÇš‘ Z‹÷:þüìÿBÔ(ywËc>FOÃј^¤_ üOÓ§Ÿ~FúC¾høÿúgË +® ¿4¸ÍÖún îÿÕ×\“±¹*Zgôè´ß>ûŠï8úZáQG)­}ë[ßLo}ë[ÉÞ_þ¡ÉÒ±PÛj!~jé¾ûxL‡¦½?ó™ôSÓŸð˜®KcJþ+~ÿ‡tüH¢žÿöç1¥ªÓhž^GcжÀÿš‹œ‹|l:¦Šø>ûENîwÀ~T—æé±ÇʘrƒŸßtø×ižæþãH¹Ä'‘üˆúç÷Ÿ¥‘ãqÈ~ðƒR.(þ¤IwËal¾Ùæô^ŸÅEFÿYá_Äúóਟör´Hæ,[¥Yã‹À;Né4õšJþêÚþ2·Sºô²ËÒ 7\'P~M6ÂøøjÔ=ÇTŸ‹€‹“´È;ñÉN…Ž>G£†Ôž[”ª½â熴9FR#¨­‚µ*íˆÆ»¿T|¾×d7žLÈw ø6/Ä.†& ?íå2î@ÐðÎULñšöVÆ\ÆÂ©ñ…:þ–cÈœ2YÌ¥‚²½U‡1—±¨ùz@mÍ?0QÒ®ÉM{«c.cQóô§Å`’ å²ioÕaÌe,*ÿ ÔÖù&êüÏLÐK¥;Ÿø„>ñ‘/‹ùÒ¸³ýûwèÐÏ×Ó“ qR•'†ú:t)NV‚þ^~å†/¯éWnò¿@ã)–ì´SçC‘ö¿ýïtè‘ÈôWJŽ‹_U*CÄD‚—c|ðÁiþç:/¿üJ˜ÿ³fÍ¢§[ô)yŠ&7ôêìWéé}Ò`=ö°æûí·—¾l±ùÅGÿ’ïG?úQÒJüïпþ+}—gÆ_’ÇðCV®¿Ó×h"7Ëÿk?}$£1ž¤žî¼sù—‰äcTçùçŸëðÓB¬ï°ÃörèçŸù¯’!—¨EkÎ c{gŸ6íÁÎÿœÇòÏšõŠ)¥ŸÏ&»þÊ^SmTÇ4uøÉ"ÔáfGÖ_÷’1uñ™?ӗ󘊠\Æ"´-‡=Àþã‰;Ó<¾óíïÒüù9®‹.ºÐ¸>“ž¸ÃQ±0fŒ>e´ÿþûÓñ¨‡¹âþ¨ „Æò‰ÇŸ”ñG¿W]eÕÎlš+¶ISˆ”ËXôÛÿ¦MëüÏÓÆ‘y*󱯳â +Z8§nLñmLß·EèSo§Ãëö-ÑZј§µ@õZus‹–6€ïtè½AæÌ™ÖO TL¯ÑÓl<'ù˜å—éÄP.©À/Ôm¸áF¿¬×©3é¬×åÐxwä'<éë§‹çÿóÏ?Os{[Ál¿ýr,ôUFñs=Û%>XÓ5Dׇ²†d\Æ0¶‹g1¶ØKUEˆc.cÑÝöçŸD¨p|0ÆÀ°ú¾ÉíZ¹û”ï’ye£;mÌpØâ+q F)ÐNî”éí2Âdg¾£¦UyìJu‘jüÊ#%ºr„ü5ÿˆD…­Æ\ªó¤ä²®?’/š:uýíZ[êù§žÊŠª–g¯ç"C‘ÂJƒ§7êü³ÈàEÒ)§œš&L˜@ï¬Xßâ]ü»‹ÒÖôëd[o½Uzî¹çâµ$Ž­eý/¿Ó½þÑöô_û˜´E?•žè+ ò/ö[Ñ;Exÿ ÿ9ã?bÄ*iÿýöKK-¹„Ä™Gïš¡¯ÓÐûi®Lkäw£<úØ£éU:Þ[|±tðÁ‡ˆ|òI''ú d×?ün"~ª‰·ýöÓ'(D¡ÿ’o£×Y‡öypèü· ý‚›nD?åœå\ü5Ö?!/¥Ù³^I£wÊð‘ÑÍ6y×?ù²åV[ÙáÊQ5òŠmú| ïÝ6pöôáù铯}M±ê¢‹.N¼åz?Œ>¹ª °ÿ«¬2‚žÊ¢1]jIŠ–ÒÜysÓÔûiLÿ@cJOIðÆOF½úÚl‘_ŒÆô Ï‹|òI'ɘŠBñ§?ü0½?HÇ”>`.ø}T¼^gt¶+™›l²IaÓuLߨù‡Ô‘Ày'Q…8Ï#ÂêìÙ³ÒÇòü9ã¬3hLÏ—'_ø<¶Q­J¿BGOLÝNO‹ñ¶Ã;’C?~ä£Û©¿>5Í#.›GJï"Zn8?¤¿`xqš+ØþÜþ¯²Ê*4§hL—^JšäwBÝßý4O¯'›¸Ï<ú©Ÿ§ôNÚN:ù¤ôÜ ÏÛõçÃ?bót_zÒ ýgaâ]wI™§.ÿxLÙÏØ|H0ØuóOiˆºeýc„@ðùÿñ?=nñ—ZjéÒ0Imñ_~y&¡ödw5k|C„Uþ-Ç„•š~¦7H”’9uþ¹©ãxÊ눪ëO]e‘EBPªÔó_™/nÕõ—h©çŸ¿«óOÕwÜ1ýñÖ[Ò•W]™v¢—NóÆþ@_Óyï{ß›è]b“òÓ=¬ÿÙ W`éüá éç”W±jº{¢~`ÛkìXzY3_6ó¹–€„+Uvþ¥÷ ¤ã~x|Úh£èCÌbò’âvÜ^>Ôðáp˜™/¾("ïöÜsÚkü³Î>ËâÿúÔ_³›>|IÿA_÷ð½«HÔKè%ÅóçÍ×c§šüb`Ù¨ô>•3%¬Hß´›ê {þ¹ýç&çÑ dù§«WYuµt8¦FÇ׃Y¤Ì¿(⫃ǟ?ß¶m~yçþoºÙ¦ôBäÍúÝï~7 6Td¹¥áÚ–Iï Ñ\_Súªé¢ôÒ`zçPÚ~±‰?¨ÊF•^z‘?<ë¶çؽDàøgyÌé×ô {<þü¢îÇô}[n!Á/ýÝ¥ôþ]þN£Æç1•.P Œ©#”œéÏ_Þüy2¦ôôŽÝðà14ˆÆ”Žì JJŸÜ Ô[J)=4ýÁtÝ$ãeÏ>ý,Ãå«BöU;©$½ß±ôu«§f<-Vîݹôók®½&ë.ïÈ4Àþ?E7/Ž;6)ÍÓÕégÌyLo½åV‰Ãýyá7O÷ÚSãS¼³ižÊ’åTº±Íò°¡ÃäkYZ™{˜èÌ[IyÉ%4Oé%Öb¥úò<寺ê*‚‘ޤ5ÙTò/¬ €èÿˆ•W’¶8þŒOävYkÿ¦7¿ÙO>ñ¤cKüÇ{LÚXmõwHi•òKAa\UÂq\î«|—¾Q¦^æqÆ/”/½ìœÍ2·—’ÛÎíPõ®ü¤m¼QóßfAZ×οf›Éñ‚ ad±ð8]µ²·;€8DLë ÉÕee§ÃÁ^YÄKK*ÕøJUå?çCÍ?!ùÐ5_ŠC™ªó¯0BdÕõ'Ï#.˜Œºþ"%êù§å"’È~0‰ð) ºfSÙ×ó¿q¡yU×ß*˜lRòî_øýL¸ ÝL¸×[o]¹t¥—L§oºI’yÉ%õ© ¨Wf½¬ùM2æÿ+¯¼Â.úB_Zbñ%JþÓáò»føý{칇ÚÉ&O@H}éTž0(^ÿø_zé¥ò–/ü¹i°6=ép0ýÜóè4{î¾»DâãËÑXL#FŒHÿñ½ö³ŸþLlóèÆÀOéýDÌÿþ쟖Xb‰R‰*ËD伆ÞÓ²ñÆï–öwýÄné8z' oc÷›–¤Ÿÿ–8Ä¿ÊüמÉÁèèwßcw9>ÿב¬#(ý¨~ÞcýÑ&¨ ~y.C²]:Å}A¿ÈÍãø7¿.@þ%´G¦?R*7Àþ_z™Ž)}5NžF½öhÓƒÒ~üCêßi~~bXyDúÀN ©“~ö¿:¦ô&jz?Ñÿ²[Þ Åùé·ýöÝ_Ƙ߽³ÑÆÓ{‹ŽOŸÜÆ4ÿôö^{ì•$ÿ…Þ½ñóÇ#!¨}¡üÑxÉüÙ}…‘]žÀcUЧ¸´&ÆO{Õ<àÀ´×ž{ÒMнÒɧœ¬Vrý–~Ͷ<þ>ò¨¼K‰{÷Ío~SÞOĘ÷?0ñxV™DÃü·¶X §øqünüež.7<òÅCdž®Ãóô §:¦\7Îÿ+ñ<å1Még47ÁóTÆ”íKï’y*q§}ó“~×\}Mz÷»6’wQíFóôXž§Tg/ºhkò_«Ê‡.MJgàdeàý_yĉÏC7•ß7ÄÛâ~çhÉ~²J+Çø¯Í#ï`â¦Þ±*Ý¢C\‚ŸœääYòÔÉ®S¯Ð“…¼ñÕË‹ëÚ^ºYøÿÆ7¾!mœOsûáéKnØæøË¯ ŠˆçÆ_hìÐ &Ayûc±VÉ ¶¿‡ø­ßG#£}ï¬ ˆ|ƒm`%ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁ&¢-”®•hrš|`"ÚBéZ‰&§9ÑÁE¼{âD½ú¦ëëoûÛb»ñÆÅÆŸ Î9çÜX…ÚÒ_=¢OèôIïîÀ;†øÁx›â½E~÷•?ûØs~½×«¯sô1G‹ë5×;çœs2ºÍO~¬¿Rç×k}Ç®i6·©æ&›¼Wâo·Ýv9ßúhn?˜Û¤ÍR)¿lH19.¯!p)ØiN, PB[(]3Ñä4':øÀD´…Òµ¢O Q–ÆsÀoœÆYŽl‡“0&š`¦bÉ0´…ÒBÖø‘Ò@^tTþ…SóÏò¤&9–Ô†\BYç_f ®?1%HCòDG]êúãÝ’'–0Å„âI&2r ež}ì&ÒP9:²N4l¤ExQšm¡´z£‰4TŽŽ_x9ĉ&˜©X2 \¢\þøÖòõº†U´*ó¿Æc[î­ËŠÈO×ðÆzÑ­ÈÐ9ɯק–§§–ZJßÂhH£F®A_Õ¡w Üÿ@꣯ˆ/‡×5‡’I”bƒ„ño¼éFAr<þŠÊ;Þ¯Sh|ºÉ ~®ˆÊùÀ¶ÝfÛD/å%G_:óŒÓÓé§Ÿ.zétE¿ðU*¨ô…/~‘þuþô…/|AÞ‹ò‹ÿ÷+ùÙ4úÕ¤SN=™Þq„¾sÈØù'ôF|FÉ&v8Éb¢ f*– 3’É“|ìô‰-M£_)ò¿&ÄPùW}«£á¹}^o9”Ü`>$‡(XjëëôKlüÒß]"vm~àý¿ñSnçØãŽM«Ñ˜âH¸Ä˜ò'UK2 š¶ÝvÛ´¢ŒiJgq†Œ)”ŒéHSÚ2–Å/ʘ>˜¾ø…CdLù+SzÏ´iÒ{¸hLó;Ž||®Ç›ØÐJu‰7šHC#Ñ‘ípjìyØHSúì™îà~Sé?âxÂùˆÆõÆ—ÆvZ:-ÿûõ¸tø×èW¸¨ÒŒ§f¤o¸5I¾I¿nG–Ãi,ùëYûØÇé—ÊÖ&K'Bùnœ[­|Ø8H”æãÅÅç§±Ê<•é¹gŸ‘šÜo[—2aÛИ¾Æ”}§Ë<=C°›½æéÝcú%Ói4¦:O•ÇôÁ¦¥SO>ÅÞq$ðN¨—šL4!I†æŠâE¿Qf#£©/Ñ 1ú{· Î>ë,{G’?{ölúEÅÒœ9ô”9>C¿Þ†í8úeÅrD)ñÓBÇ{LvwÒÇiÌx[eÄ)9þYG7Òråñ§—c¾Ürº^“ÝÏÏÿ×ù—Ûh»ä’Krun„[ÎÍ©¨¦Ü>¼p ˜ëd®®fkN4lÁÁ‹Ò¼‚RÐꦿ~|^„ñòwâ´ÜD%ïpC ¥Çy›¯Sdð25>ÝD·Í‰]6ÒÀ]‚GxÀÊå¹Pçû Q~¼2ÊÂ`Sò/Wç_È…:ÿÚgˆòã}Q›’Gx¸:ÿþQæßÏñ ¾¶–¿÷Ó¯o}öÙ¦=Hÿ <½C„;k­µ–ùï±_´™ßá1–zô âÈ#ì<ùä“G}¤sÐAþ+‡& Á‚'¶ÊO !kPÊS,ù8Î8óL$RµØíåN‡¾Öeñ?ÿ¹ÏuøiFð+ô3çüÙ™þø)—¾½ß$ÔfÜQG%õ‡_Þ~íªû &)¿ìDýç¾ñÓCeûÛæÿ¡‡~YúÐäÇ7oî<é?ú×ÌÿwÝÕ¡wQ9±óýï)mðóSlç¿é”à\ž*ÐOå‰!öΟ×Ù`ƒ …kÍ~ªà!„—£†’Q¢BÓÏӘΗŒé>Ÿ±ãäXúXäÿÈÿþoÁ >\ž€áñG¿},–ñk]ü+`œ;í[³Vã{4°¡d;d”ÀrYæÏVÉEÓ×»Œ‡3Ͼ{ãŶÞúë30l\ë±?=fuxÞ²ÞÖÙ`à ÉÞ×a¾ðÄW¾üòËóÜIúj&›hk;Ú÷Ÿ¾ªÇwJ$þçóô34O9¾äŽ©FÓýü2O—_ÎÍÓ32(ÆûŠ+J[~žâ¨QrEÈ(sc-…Gxп¡Xú xë'÷÷[ßúVçŽ;ï¢<~ºsù6”±HÿËzn¸ÕùêÿýJgÚ´i~²ó£ýˆÙ<ð³8éÖk^ÿŽ:ò¨Î¼^?öhçón½>Ô­×çɯŽéøø'†¸1ŸÇ°9·™I>ÿ—_6¤6úÜ/æ£'(Ù ¥u Kð/¸ðüûÚQ¢µîÒ#¼ d{|¾Ã™·\I ߀·3ÔûPwáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRx·3Ðû>.V›“Å™5)¼ÇÛè}huáK«M‚ÉR=kRxO±ßsÏ$¹Ð—&ùƒ@Éÿžš?hôѱ#ÃÝ5ñ.ò•oO¯’àÏ=÷¬Ö¡p¸Y¡_o*ñà¶\cÓ¯•…X R¬g$Ìš=»³üòÃÃñóWeð!s8ùä)ÅšñäŒÜ´µÐyüñÇ‹þðÏØÏš¥7"šÇ±ûî{X»ŒçŸó^}äÈΨQ#;Aß}÷Ou~ùË_væ¼Æ_Ó)1L6“ Í ¥[mX>Œ~®žgË-ß§õ`¨ÎÜys„ÆœyÆY„™ßy‘¾.þÛÆü¤¾a;ï¼òsõòá1Çgÿ…&7\7|Ý\Ø!™€¦CÉ?‘>|ørÚ^ÎÇœËÑ Œ×“30¦Ü„¶K¿þTŽ…êÓ¯mQžÌ‚Ûp\c÷Ýw,Ú㯕9ŠþV—Ÿ¶§÷5é˜Î-_½²£'Ádn š½'Ëf2Ajµíâü‰®ío E?WÿÈÃ[Ÿ¿uÄ·ácd™Š1ë­/¸å‰CÞð•1îÿ)'Ÿ"6¿Ûq§µ]âñî»õ§Ï­eL–JY“Â{æwfÓÍ=z¢ÚÒ5„ãñ¼Ežññ@ž¯ºyü Ó²þðWÞf½2«5þîŸÒ1•öè¸W]m•çñ¨ÕGÒ˜¾Oæé¯hžÎucúÿÙûx­Š£ý½tøb,(E$VìQAMìQìÝä3FÅùILbAcÁ1½a¯(EQЈAAéEšH‘Î=ÿgfv¶œó¾ ØýÝ{vvfvg÷ÙÙ=åݳ'0µZÒµ „£WÓþPüÐC²ÿ®jüÑë¹èmèÿÔ.õW¢ÛáÚY<lmù¡àIãÇþKÁðû꫹j"“×De>ž8q’ã¯WJÿñúÐ7×þGyØõá¨Qâ+R¯W”–Ämi ¾£¹”ÐN( ù¤ÊÖØ¼Ïbâ’B;X[®è8“Ô'äe ¤JrŒ'R#EN^¥ FÈKö=b‚K€Ž’'üŽ]¤ yÀyÉÿOYslLÔ• ì»r˜P… û }f÷¹‘ªW ÜÜßÝ«×KÛ7ûù\h¢MÐ>,|³…6Ñ*ƒ¨øÀ>Ý iYaûÃV’c³Ü¨)G”UÊ>É…H•äxÕþß½Ö®LÅZúë©§Ÿ&sÙ‚ У­íwíC;è—ÿ渙¦ÚQ5^xá×þ‰“r+‚f«V­Gä¥hڲnj§ø“Lø^:vÌØlßýd­Ss<„ë¥}jýö#*Nîr²«kاÞl"1«¥´üðFyh¿ÊŽ´}êm…%ynH4Ày«j÷nÝÿÃ:Ðø)ö?=ìÓñ×§OŸìŸÁJÀ¡C‡r5B[ÊøÛ5ר›|“-[¶‚W Q†q¿eº`3Œ‡AŠAǃBé.«yÏЧûPŸ¢ï¤Lø—íÓ)˜?îSÍ̱´_Ç)å½²Ûj¦„‡Ø²JPíøy|Zíyd'—WÌ©Q°• ì;e&T!憩‚Ê£ý†:u:ÊaKm¢öÓ~]ï¼óŽWä •Ù4<À&üµ/¨ ôpç·¿ým¶ÈJ…‘á|MåãŸÚô12_‡þ÷ü /:üõ¡/—IuÆŠÀ–vl“}Y1¤-ñ–ñ*.×ìÐø-hPY^ÝÍ¿Ä~ U’ã¢ÿkŽ ¸©E8!/l¿ð©’¯ý 2 À€:ù`Ñ@lôv¹PЩHÊ«±jRã@^(/ÙOø'ÿ‹FU˜(Œ—PèÆ•h"Õ¤ÆN/ÿžiþIóOš¢Y%LÆK(tóŠ›hÒüC(;Fš þô æßåø$ý˜OǘåøòÐŽ;îhêÖ­k½PÖØ³ç-˜Ï{U«Vû¿ìˆ}*6cáÚØ®—­ ‰Ô®ÆÞ~|YìåóQ/¬ˆ2Ͱ'ÍV[m‡€lÍ?ãÇ7xUŸ™ži|ø!Ó¦õÞfΗ³ñ9ì•+\ n–ÌÕ×\mÆ/XíuhßA£ÍÕx÷ÿ¶OiOéS Ý9 €ÄšÝ?ÎìÓ®™1c¦ÁÊÓ¦M3gÎôi%út±™8aúôo¼ßáÏ·yjNã šUpBA“;‰¾þx(‰qú1Ïõ¶ª'åŽj@c« Isö'ŒŸ€/bœb%ú¤}Û½÷6³±ÑÊ••föïÁjþüú8ì FÁÞºÛÂÃH hlešÔ8g?šÏ¾ÁøŸ‹=аB‡ ßyçM5ÊbþŠeËͨO?15«×4Í›ïÄûµiÿ”ÃÞüùæsì7V½*Í×;¹}à úÖ²DÚ@ËUËË å}ƒöGxm`ö+è1Tø<ˆÚçáçÓMàB~„C.Ërʹ¤W)!Yäüɾ<  qIøÇ°É1rÉä@€1)LÈJã/Í?iþMó/M˜á¼Àhp(ÈrŒ\’s2¯„ d¥ù'Í??†ù§[÷îæ½wífÒ4]Чºñiº¿ ›¹ñÏ>Ÿ?çœs¾³óo×›îÝ»™Î;™gŸ}ŽÇ¸6v°Ý|ÓM†6¨¦ÞlÌã¯ûº›!ƒ‡˜*x@G0Ñ’P ?·“þ5¦ úô\ô© WMç’Ìf^ AÈ*‡?öÊ2@ý:uêŒ>}VúRQŒBn¾}Ú•ú´}Z‰c¾ý¸$ëK!3lø¹$k~ö»cœâËWûÒ³T#ÿˆ€>öYgg÷ŸÔ§T¿N;›çЧòýOã”6¨æšªóq­õ g+þCÁßÖŽ£ï£ÿ“}Àªð¯Ïâ>~ñ%äi…'d·…yøDO¢´Lþ‚!#&[ÎÃ%û–‡$ᯅÉÿÒø GÏiþṕ°ç JÉ´ €ÒüËàx|]P¥óøL {’g¥óO:ÿ¨7¤óϪÎ?#†`Þø&ßVÒ|ZøC§*š‹éšx¿}÷e1#«ð2GNhžµnÆ_:X…Bû÷hú÷ëg:è S¿À“eË–šç^xÁ\…¯½QÀës2/pjÝØ§É– ?’ù÷ƒáÃÍ›o¾)•ç$’ZBâ°ß~ûY†ôáú>ÿÖ®]›Ocýû÷7ýЧ¿øÅ/ð ’>2™¥K—›ç_xÞ\uÕU\§Ãïð£ÄŸÚ²®çß|`õ)ƒ$ÝjI¥§ûí»Îí³2þ_»¶¬^Ø€íSŒÓ*U9 617/¼ð¢ÁLœ>ì°X~|}7þ'íHöu`mhøó«dáSIñ6êvÛTö3ëlì Nbý2ðÎÐS­.G¤B2:C–É~< D ÆÝK|ŒÝLõ"Ï9ãŒ8$ÿ+@CŒ4þÒø /ÀÄIÒüÃ8ð$ãgâųȳ‰ÎiþMçŸ`PxrC8ÿ <Ø|1}F¹Î2OpšÙÇ$@ý?l$kvÙ•>§-a}·ꔩxEeoƒ½PØ~ÝÚuLã&MÍŠåxµã“Q25¡*­÷jƒO;¿b6ß|s¤l;xJ“y§1/±Wû"³- ¤Âá#gÄáGæÿƒ±Zhú´iÆTAÝ+åÇjo߃'?Ô¬*8RcÓg³ë®;“”Tâ°Ú}±ÌÞíÚši“§p=°ñ¸iÚ¤1^«\iFÎõ髯¢Oò£ÃŸA$ìx»$Á›xŠ0‹¸õ1‡ó…‡ÿÁC£O§K)XEE…ÑðÔUan0PŸ6CŸî²Ë:µVKiÿÔ§nœBX§v]Ó´1ú´ãôãOÀᆘ½Úìe^{õ5óŒSAå›·_m†±ÚžØp(SrâÚU:ÙÿáÝÈC¹Îg? ^SÓ\E¹S”(f*ˆ“ýhð%ü­Ï$ÿ+ž2œhLE‰b†‚8¿4þ‚“šÒüäù·8y–áDsj”(f(ˆÓü›æßõ<ÿNÃŽ;ï¼Ó`“Uì;#{”ÐðÞ®áöfÏV­Ì¹çŸc?ìYITpТç9Q–(‘×ÔÛÛàö"ùÿZùÿ4<ˆ¼óŽ»ÌãèÓÏÇ}î€n¸útOêÓsѧ‡›ªX†B÷$ü× ÿ²˜5WíÿSñÐê®;îqŠ}¤8` noûôœsÏ5G~8Ó°ÔÈd”µ„.ˆ‰±žçŸ°ɾ á.¯~€øWÐÆÕ®‚\_®¥íÇ  -;”’’¤Ëm)¤”ÁñüR\-ſϩªZ¢¤IjXÆ„°Ë%3çMöþÉÿt¤Ûñ‚Az… •QŠh iü¥ù'rö :ˆ·XŸ ]ÇiˆRšѦ@¥ñ¢".£ØPŠh iþIóOäìto±>ºŽÓ¥4ÿø‘–a$| ÈÔÂf±UªU§Ñ„D¢)¤ñ÷C´êeÑ׋LÍZ5MµjÕ¤ÛôYòïÿ:kPr®Žÿã;T§èÓšµLµªèS? µgÓü$ܪ©°ëB ’ÿ¯ñøÇŠ!|³^¡-lvý*é(?%(¸³‘Hù)Š˰½Âj¨(?%($û‚C8µD@Y1¢2l¯°*ÊŸð´’ÿY¯ïàcä(ީʰ½Âj¨(?%($ü‡4þÙØG"G±ð *Ãö «¡¢ü” üOpHþÇÎÀ>9Š…Q¶WX å§…ä‚Cò?vö‘ÈQ,<ˆÊ°½Âj¨(?%($ÿ’ÿ±3°DŽbáAT†íVCEù)A!ùŸàü}$r ¢2l¯°*ÊO ?pÿÃneRC»’L*;âé‘pi»r*e“¤/èé¬M8P”¯HÌ($ûuD–ŠðYUÂÚðgìÈ÷’ÿ.#`¤ñ—÷ Qš쬃ÈRœÕP¤/–æÆ.Í?êÖsÒüC@¤ù×MÖ/|”æ_;ë"²”g5é§ù—@JçötþÑaGN:ÿéüC X—ÈEãù§Ä«dzÄ@•FËŸpr(R’g Ë/™ÝæF$»÷—*ÂdÀ”!.-Iø'ÿ ÆSI'IãÇUšÒü‹ñQzˆ¤óO:ÿÒˆJyÅ¢$ÏñiG)¡qiÓÌ•òŠEIžãÓŽRBãd¿,Qy Š’<ǧ¥„Æe­«‹–W,JòŸv”'ûeˆÊU”ä9>í(%4.k=õ¿@T¨¢$ÏñiG)¡q¿,Qy Š’<ǧ¥„Æe­'ÿˆÊU”ä9>í(%4Nø—E@ *TQ’çø´£”и¬õäÿQy Š’<ǧ¥„Æ ÿ²Då*JòŸv”—µžü_ *TQ’çø´£”Ð8á_¨GAÆ Òð\Ñ·¹‚þ×lª©v59òY.[’fW1§“}‚RT/‹Ú„çNÌ5×\k®½Vþ›·h!>°Û?lè0 ÛÐX°Á#½jÿßâ'›»»í¾›fw=¥~¯±( ß’ÿ3ìÁ8XO.àGŒ4þ¼W ^µ hüSK)ø–¦þOþOA¾î½bMýæÌ桇2íöig4¨o.íz)*ó ³#‰yt K&L0G}´ùŸÍbš4mjê×ßÚÔo°µ9çÜsÌ3f@ÃæÂø[¾|…¹öºë¸üM7ÝÄ´hÖÜÔ¬UÓ´Ý{/3`ÀwFes>xûí·Q^Ó ~ÄõQ¯fkJñwÝ•æ?îvÂzíûÿ»<ÿÒƒƒsÏ9ª™¦M™†Û54ÛlÝÀœvÚifîWs]ÿ[O`w(å$§Æ?£€CéþŸñÅ~lçK/½Ôij¿kL%-Y²Øüþâßó8Û¾Ñü°·éŽMÍÍ7ßLbÞ’1‹—.1×]w­ÙsÏÖfÓM73Í[4Çþš¦}‡öæÚk®1‹-Òl.ÖüjWcQ@o[îw6Äþ_Ü®ïA¸íijo¶©iÖ¼¹©Q³ºéÜ®¹¸-^\vþ‰ùUæë}L}îSz[@QU¼,jËõ¯r¼W¦ä•W^Ió!’Õ©]'[¾b½yÆaܸϙO2€˜=ñøš³D¬¹Jˆ"V çHG°f˜jÒ¤)×á”SOË\Å¢ò4æR^©8Ðs¤#8C˜ª´ ŽBA¡èU í@Ï‘ŽX¥ý_x‘ÞÖc<‚-—Q”+'Ðs¤#X)L}Óö¿øâ â+¨ßªCPº#±ÖöWãA•[ŽtD²B4¾iÿǹ¸ dPº#±Þñ?îØcØOwÛm· ÊëÖþK/bœÒ»»%ÇB`Ë‘ŽÈ¾üòËìï7Þ˜ýë_ÿÊæÏŸ—mhø ¤o?9@˜Jí—Ę„À;8®RXZÏeqë…©„¿@ǘ„Àˆn‘âHG°z˜Jøè†À€]¥0ÐôéˆÕâ?sÖŒ¬aÆr­c¯›i®?á„ãEòEœêԩ㮵åšZ®åèú›dÓ§M猓&OÎöÝwß 7þiÔ]ÿÝwß}9#•ÙÓO?Ír¹ž—ó^/ª½¿ßp½Ïçší–…©äcãQ´Ô*…v çHG°^˜š={NÖ´)ÝIÿãûŠögóæÍ³3f"_˜+0W =G:¢`¸@IDATcêÿ™À1Û:þN8áA7†‰±š7o^¶ÿþøqŠ1Ž¿³Ï9×p>ãèQgÔgÚëý·ò<è ì믿–¾°Y9òÅzuCïÿGΚ7nÑœ¨ó£Œƒ·E‚›4sV®OíœM}ê‡L l˜Ò®ÛñçCp:YN‰‰Ç,X¸À ŒUAV'3Í„¾œ+)‚ÔrÉç%0å“–«zHZûLX)EjŸlê“Ñ*dß m%µ\op]Ù§rØô÷lÎÜ9âÖÚä(þ~Ûÿåœ/¥6~µr+ªõx’ú'Ÿ|ŠÿOœ’‡Yúÿ‡‚ÿúô¿O?ýÔŒ-|Óöf£G¶¸)®¾˜òÉœ’ÿüÀú¦ö]‡1ñã°ŸÑi˜Eë©ýsæ`œ’gökˆÿæ›on.ëÚÕœ}öÙ7 <ÿQ\k[u$K„þÔÉzÂ_Áa;Θr“}IÂßúb<°|jã9ÿèè 8µ_Ñ(öEEU³lé2ž›i~–=7¯¹Ñ%¾…äܹsÍI]N2 ,0µk×1Ï<û¬™7žùꫯ̳Ï?g¶kØÐtïþ³õ6[sq}_}Õ ~çèÖ6½{÷6_|1Ãd•+ÍÐaC ~(e‹.ºÈ¬X±ÂÚ§¨«G¾rç›þýû™þý˜ýð #ZeDÿÇÿë4ÿ*j?‚ùïž{z™qãÆ¡Æ™y­ïk ]_è½Ù˜1c̳Ï>Ãý/žWô?m®ÄéüçÊõ•ªU̲å2¶ëblëŠyNô£ƒ´W¯^fÐ ìa ÎÝ —.YÂãü¢‹~Çc±÷¿î5ýúõsú ·ß+ÿj™:uë˜{î¹ÇL™:6—›!CÞ5Gc5!ôæÀ¦ßo OqþqíW×:7ÿĪª.l‰vÜ0ŸúáØo´}C~EæOÂm*p[θ nÿÀí×ej3µ­*Z,[†>Þ4_;t¨‘?¢ösŸøŽ¡æå‚ö«kájû/àI ,eqÚ%@<÷ìsfÿý÷gTŸxüIÑþ¡ª 53³²²Ò è?Ð|ðÁ03ò£ÌÄ “LóæÍÌ!‡bN8þxSµjUô­!û·öìia©ÜÙgel½5&ºiæ¾ûzã¤÷Õ"Óºõ^<@Zb?#É«ÎéË‘ZdfÚôéæþûþd%ï}tÆg€&=iéd¼Çýä“}̈˜i˜P¹èÕ’Ž;â½DyŸSJ•Ûníy‹YôõbsÖÙga .ê6ý Óû¾{ÍðáñLm)ê¶§Ô­%êfm€pAÊòö¹«rí'ér8êÓ}ú˜·°Ñ,-/®V­ªÙm÷–Ø÷ds$^ÙÛd“Mlñæ­·Þä‡&_£^ýúÑdA¡¯Æ\#øð.uL…éÒådÓ¸q#ÐÒþÊ•+p1ð&úg¸9ò#3aâÓ¢y s(úçøãŽ3U«U³ý¯×e¦ç-·šE‹™³Î¸P™6mª¹ÿþûÍСÃyÉdëÖ­Á1¦eËݹþ£G}‚þ\húõïÏu Û´¬¯ J¦RåibÆuk„ºQû']N:É<öØc *L·nW`‰eæKË`Ks‘ñQÚ0l¹’‡Ê“T˜_xÁ¼òÊ«æ“O?ÁÜmL Zº‰¶S 2ÓýÿK8¿6·Ýz»©Ì*qs~9Þçždú<ý”yiµjmjÚìµ—9ýŒÓñþðVÈ&öߨyýõ×®0_| –†¢ï¬Œì?ƒñ4zô(¼Z´¥9ÿ‚ó¹Žaû‰>é¤ÁœöĹKÈ¥ˆòíçï¼ó¥^öHãâuœ~m³ï~ûšC>”m…økû)÷·üxÒZïÿÄyÿý÷yŸÚTÿ÷_ÄË}­YŽžyæYôéÇf‹-·4œóÞzë-~°·>Dã”Ìн õ+ðù.]0NóÒ×[o½¸­DÝŒcÏT)äíС0ÛIbäCÆãïõ¾Àã›N|ûî³úç`‹Å‘þ¹óõJsÅåWØþyÇ,=¤þ9ù”Óàß{ˆµE`Q#lÏä­k›B¾ä”}:§kc‰ëÏþs¦Ÿxâq·úêÁ”¥]‚7Ýΰf1ÄÙÁ’ỢËè4({gðÛ.m½™Óá2Y¼‹Í¼SO=eá–Ý–ˆ'†Î>žf£>þ¯k&-ÿºãŽ;¥l¶n¶Ž­Z¶ÊfÏžÍåÙâ8Ò:àM†‡%ëÖ;\«•áÜHØ4Ùƒg|ñÅtWoŒä‚úõëg[ §~zA‡ðÕºjLøè?€³‘ýxK›Ñ¬RÐ¥<|p¶`á|5ãb-û§ ˆ¬úi¾nùþwu´öñk•³¡8ûi¡nxOÔëòÈÛ÷?+EB$lZñÇ“âìWGþª`CÛbCåM›6Íé^rÉ%ŽvúÀ ¿îe}_£{wÜqëQûgÏ"Ÿ;¨W—“»°œ–ºàä•~ñ)Ø 1pªœYÚ?fÌØBž%‹Ú¯ö†–a-äÑöqÄÙ_|¡êx…i~†‡¬E}´‘°xê©>¢k+7¸iÿÓØÙu×Ý¡g}4ð?<˜rù²O²Oˤ±Y³³­Ä’%KÜ’é+®¸Â²+³y ¨ngê¦öŸêÓ'Âß´%¾J¦£Fm¬}JmÅó„*þxËöii«†3N?Mêdç¿RíW?Òq:iòÄB;T‡â»îº+WC±?þ‚BÿhûiþÅÃp­Çx¸ëìàZ¶Û®»ÙºÆs Fù(QÊÿœR$D¦CÿÝHÑé„+2OÄ9×lüû²âR´žÉ~˜£b„„G>aÓÉÿûX"*v9~>Ò±ÆÏÿôµ_Ó«d%úãÝÜ{ñÅK ]£Q¢å9UNVfc?ÿ åɹõ¾Þ÷ù*³?ýñO8§Vd­[· øDæì¸¤#rú>küððjj+»!¿ÿ½ðBöº~[¹ÒoóAí>ïÜóX¦÷FÚÍRû¿Oÿӱͯ±£Åþ?è­Anl4òC7Ìÿ|ÀÝÑ+g.¸F9‚EX$àÊëÙ³§µèrˆík‹ʲũ}Ÿ3¶£z.ƒW,PqΦýñÀM¯{Þzë*¯ÿùž×ÖÒ§që #Öøa¶_êšÚÊ~ÓþÇŠ!ÀÆü´MRJ-ð«ñ”)Sy‡ô°ÂfÄôÅcŽ?æXóÒ+/[=ÿììåW^1cÆŽáåY'aÕÇXEƒ™Ëü K_G`… -eíyëmæÿþßl^*M,EOFes® ³VÊ|È¡¨V6Ðd<è@RáPÅe¡J‹}<´2ᑱÖþÁCÌO¾3ë“ú?ŠO>ÿ/ë7oÖŒ7£ÕKO=õ¤y +è—L¦æ¥—_²PÈsÁ"㥙7ß$‡í¾ënæ`¬°!ÕíJÝÈÿ ‰˜Í”>…f©œnݺ3nÄþË_þbØÿ@,5žcþûñ(sO/,-œ2+†jq.:tÆ ¢-±©3Ù§•ô•8ªíÅ—\LYlØp;±‰ë†W^{¿d}æú§%ú',M¾W@õÇ Ÿž=oEÿürê/¶8«ýCvÅœAÿÞ;ØàðÀ`;´iùxÊNá ,—ü/Õ OÂ/EÝdF¤RÍŒÙn»íp\´Ú´³¼bN¼:X•«Ì(ƒ | äšé[Ì%9>•\Äÿº×›—^|‰Kúío‹•'a5ÈPó§?ýÉ.ë®m.¹˜6¬´µ´"ºå–[»3N;Ý4nÚļúêkXÔ×L£±Yäà!C‚þ§V¢¨¬ÔKÚIG[0×!ß~’ÓW 8iLo´ú¥*Vi(ÕþÅXц:uꚊª2ÌóöiEÞÄí¥<´ð—¿ü%/ikЛæÉ'žâ_60§¸"»^Ö•W‰£ÝÞmÍq'U'‹ùñXÖ|ÜqÇò²Yý…„«]Š@ùãÁ£Ù«þž|âqžSÀÂçá/5¿ú寀S…ùßÿý_CK?ÇÏãjÿä|¤G–JÓÒ{*ë,¬.”Pa.¿”êv'éׂã±2‘VÄà­™@u;öX,5½}t¾ôkR)!£TÑÿUÉÏ?ÄQÿ³Xi±ü ¿üÒæë•ЀqúáÈXÿ’‹/¦Œ6ˆýí0N)З/¹DýÊ®À×Ìæ™{{ßëü¦TÿwÅF§´Š‚bð50ø7V^ÒO8ýƒ%èçžþŠÈêæàLAúgwîšs`¨^f~ù+éÑ;‚LÛo‘ Ú_jüù¬ªh96éå9üÕ8âdŸ@P@þ ûŽ&Oò?øð\ddùñ¥@YŽMzyì?:Ì‚ø›Í?´Ö –ÀŸ^ýÑpüñÇ1¹tÙR¬àÉçùvlfêÖ­-þ«ŠAœ·ÿßGBJ=‡ ¸f×@õÿŠ6"Æy<«\έ~öù8S ¯Çì¼óÎXíÞÚló\ÉÔÿ ÛÁÿîÔÙÜyÇæÝwßÃëå瘿âžaûv0¸6>ö(·£ËI]Òüÿ_/óŸ0Žãóï|KB…Ù÷‰ÞûÏLuŒ¹VØXºÍÞít¨âÍ‹içôê©Ì¤úß{ÿyßüö7¿%6¯þîܹ3Óùño‡®äÕ47Öó߀÷o~óƃ^ÿ£ÕBüÛ,pöø*PžCàúÔÆyþ£›u÷-zæ…»ÁnØ|š0:²cǬ륲JâoW_uîtùZöòË/³œtžx›OÛ§R¸¡ã_´¿þz¡ã‘%XñA+xHŸ6òÒ O±l¸õ{ôQV‘bƒ£µ#¯™ìÔSNa½iS§ÙÍ»*`§vöá‡#"û³fÍröé)»®¾PûW_s5× “Höá<åµÌÁ}\[1€3L¼^ªØzi>6^ày©Rš½­â6möŠêME,[º${óÍ7â¨L$PÀå—_îê§å–³?nÜ8Û?ñ&fxMŒñ¡ö7Ün;WŒ˜Ã»@LIÐy”ûGì³rT/â,Ë.ǪÍë mè+W¬ÌŽêÜÙå»ý¶[9›ˆì ˈø¤N²ÀP@ÒÃT'Þ<Îe¯Ì®†SûI¶H7yC¾éÁÊŠV-÷ÌÆgK«ÌV®X‘á WW,õdÙwÜ.<ÌF² -¨HÍÓÌ®. ÛÏUÇj™Î;qä·ßv»4'jAûi…MçΟ¶¯n»õ¶Øh:ì°ÃœÞ]wý#£_0]@±ï’½ûî a%ûŸ”‹ 5}B†÷x¡.öicd·Úã_faI;³-(|ÇØC»ïn>-6õx;¯6ÿw+ µTNú´‚WäIݼ¢JSŸZgC¬¦3Vwýã.¯h xs b žâµ=­v*‹‚_ ‡6×?°Oà¢e>ù­åju“6ZFħÒÁ(ðˆ‡oÚ~ÊÅÅEe’ ˈøV»À‹m³–Ë(Hað1I-#â'û‚Mï<Çw_`YÂ`xsÐ’,Ç*‘ôÙå) >²„?ÀðFèº_ ý눯 |)̳† žwüq2×ÚëÜÌdÿþ÷ý8/S= m1b{ exçoÞ¼ªDš$µSp¬çÇRqo¬0Ê_X¡e¡m™"'–ñIJ2-¥|ì³ÊR| dbÃ2"þÆaÿª«þõ+]Ï6l¸=ó.¹«Ð ˜û!á/˜¬‰ÿãkaŒ1o,ïtàÞ€Üiœ5Å’(Ü~»½ï•W^ɰ·˜ë·¾}ß`Ðþƒ<µÃ=jýúõX®¿vÛm÷lè°a¹>E—°Ïn„ãÞVjÛ¶Ãú`·Ýwφne‚§÷ßtoŽBÁ1S&þü†$=M¥àŸyJšî¾(àÕ óëOdúÉ'ž0Ï=ÿ,¯*8ô`¬–!•ð÷Ô›4æ'u›n²ËV¬XŽ<Ÿ™þzÔé3‹K—.å2½}NâaU…éóÔSÎ&ék`÷04ZøV†U*ðIÏ/°Rè 3vÌS++Þyû³Û®»KÝXÛ˜wñN®®4 UµjÉêµæg²²?lèP5Éõ%{úôyÒüú×Àƒ™¨€À•“º±.ÝÚZžæ•;ïÅÿüg¨¡_þ?ÀÊ,ÚDЍŽwS8à)ζ?²ÏvÄœ+½Œý&Mlÿlº)J6¼ZhìgcÍ›ò»³Ô~Úøl)6R¤`‹fZVXõ1'$]]û±o *ˆ+çj(lWwo‰Þ•îóô3XÙ4ÆÌƆzþ¶!°F©ö;á7ß~•£€‡Aõ+°Wξì$7~El3¬ñùœk7nâú¿ Vœ]†ýsTw$öéá@øSÀ/u.|Ãö“~0xÚa0\¸ÚöWÁXèótsfÏ1¿£ît©‚µOãàµ×hÿ#cŽÅJšßüæ £ø…é½±êfo¬ Ò^¡•_2sýõ7àG¬D")úŸ6FƃU«‹=‹žfIÛþžx·ú¨ÎGƒ‹¡>ƃ¦i0ë/¨Õ«×Àj™K UÕ/ÿæ8Eɘɓ&™W^Å*E!{øˆ}Ú³ˆ™»þ†ë±7êfíoŽO_}5í»Å*æElêï¾¢À›9öš]šÀÍ€ÔĤšƒ¯ígž-â°<âúìy‰äQ.›c|\ÌPnWa º™ùû GÿT·Æ2×?”tžþy"l©ÀŸöx;ê(ê äííÞ¤…×¥üu0þÔÅß´ý¬Ë;ƹê¾ýR¹o6þ© ɾ ‘?ÿ;|”@Ìþ•ú_FMò?ñ?€NãÏΨ2ÿÓØÑë¢Y(ãÇ#‡ƒ~q éóäSøL}}œs±šaþ‚…æôÓÏÀÿiœ¦C¹ñwãM7šodyë{`N…fpþÁ–œ?Þà³×W|ÙÒà«Ã¼ò™Ê=ëì3ùœAt>°MË,gŸÅ,Lýõ¿ÅÄaô¿òüð ‘V©ís›Ìãß«GhAq ƒU¾Oð›´BâŒÓÏ´|W£ÈÿÔ·±Ï|(r#é@*}c‰öÓµ7…-± aBoaháÕ2¬ªËr:Ì™3‹éðü;+܇`%ØÌ™"£<õ´SMÓ&MPŽÅ˜»µ´}.0ÿœ¦çáƒcùês‹_‰ËôØþøqŸcÝ3ËâFm8í”SLÂÂ*ÚO;yŠNØb×m,ú¡·]¿àNPœÅ¹ Èà䬕x8±6~nºcS~]‰:_Æ©Z¯µPû/‘¸7Vçàu¤›M›6mL Üèá×óË#~iÞú¾ô Ê¿`>ƒ-8»ícŽ>ʃ›UW&Jt± £›Åƒð=D U<ôú›<9¢ÒåvM_#¬ªÀ× Ll$Ý Al(]ßìѪ)³öd,d³ÄPöŽÁîðÇs tXÑÊDŽö–¤ûÄ’6Æm!>åüóUWñW(¨¶7c³ëV{¶Ä«b[²ÍxÐ|IËmÈÛGWq È•®LÛ~RPû³fÍ6·ÀF›6{ófÛ-š57G`Ó:Ú\—ÑB!óȆ…Ò~0äÏ›Çcb»bœŽ¥Ú¯•£ö©.qðe0Ÿlá!G3¼î·åæ[€F8EÎæó–²OyEÃÛÇ>W\䫯öÅòjzp%Ïá†YÛßdÇ&b‘E0&Ì#Ó\%–eæ§Ø¸›%ir¢ Ë5ð¢ö»BH[ìË‘2 ‡ž4Û©oPMZ\VkŸøDRû«"ÏNÀm‹-7·êÅö¤×™l!üŃ2ö¹¶Q× ‡qz-±q£FAÃXÁ´ÅfÕB3ö³Ï —›AA›ÔÚ$n?x»ccu 䪋—.qùϤWÄÀ¤Ù¤ÏO’ë=ôàƒÓÅÐQøâ€dÈÌð¡Ã˜¿-¾âÂu£TàÿmÛµµºT7éÎà~þÑþ×¶ˆ ±¯máâ)ÁJ¢IG~m@Ù}-‹ú_è°$*Í—A)j®æ ù1e§VMüšÄI¬Æ4;pÿh ÒÿíÚÊ q?sÈ)€ªZ Š5‡Úßc÷=Ø– 4é¼}ÊûMÇŸ–ãc±œ·Ïrf²E§žìŠ\ÿ'ü“ÿ¥ñ'ןÞB×¶ŸáÚ:?B °oòÉ.øÍ3Ï.¹ô2~Ð@ûÐÐ~-·Ýv«9ãŒ3lé(‰• ±OBl*Í:t²Bub–ÔRë2pà›Ø!~,l Ÿnzÿô‚²¤© :ñÍÀª" xÕÅÌœ1ÓÌš…xæL|Mb¶™…“2*·fÍšÈc-h1H6iBuS†’HCF\'á¬.eïQiÚ `Ë¢ óóŸþÌ|ˆ/SÜõ, f5zxFŸ0=ýôÓMã™§ð«R`Å’èÆßÛ q"‹r öûâÓ–X¢ˆý~.ÅÅþÃû] ú¶ž·qÿpµp þç€b•GqÓw>Ù¤(ÉIâsI‰U¡•C*1 ÈYáÏŹ<“>¯“ö)[Ø~2D+dÈÞÛo¿eÚ`eÌ­øâØÉør[O¬$£¬ôåµMñõR<ëK=œ×þ^qEúšÊSÁ3R®ÿáÿtñèÊ”¼^Û?}ú4®Ù§/h±åÕØ§=TWúŠš \aè'uÿDz+ÌBüÚIe’ص”´O8$¨VM1‚.áaKhܸ±éø«#¡’™ôº›TñÃk¥ùÇ?îf zçš¾Î"*p!ýH|={ùp`Zš1ÿSGëfÌÂ…8)SðÕ@Bìì’øë”àôØ(‹H±(iM‘nì6wξ–AR áüCªT†Íâ 6(öfàK‰¸XQjC<Ê+ï±SÊb@:R1” ]ŒwŸCìÓgZ5°j®\É ö)¯ËŸÓ3Z> ]$åËðö¬žˆ„+7Ù'Xïýç½UöÿÚØj¹Ñö¿Þÿ2ê9ÿßaº¾–þ ­%¨ÿ«W«a? S×Þeé²û¹BÄ*ú§v2ç/ yçÁXq4ßuA)ûù²Öµÿ9ãÚÊ\û(ö7ê ›Z¯Õ©1ÿ¨ 6âùÏߥÄŸ]’Vö´^—ÁVK¸áÄR6M:@H‹Þ‹¤N "èU%ê \:á ®Â®¨±ÏÏ4ûÌEZ¤¬éXüÅ‚ºøò=Hi‰e•×\{¡‰‘ŠÀf~†¾RFòï³ï>LÓáþîç5kÖàÕA5±ß­ªÿš5jb 4yûä?>HÂ׭ĉÔ*³Ž*"› ¤ (O­+W,zÅí|Yéá6wc²8zÔ(¦%%m£ÉEûaðàw¬ÜGjÿ=ì³ÄŒž·ÜŠþ±«lÿÏùr®“Áù`TkJö}+)åe¡„ö°Ý¶ò¥%JÆ2ijD©ö»Ü/“iÀ&~(‡ŒFÿOpLó=XÑ×\Yªª¹ÕVÔ§’wðg*›8a¢yóÍA,'0°ôÐCÛ±HŒ2ýÏùÔ>R®Ø×ë ˆýöµ7Ðz{´…tî³_l#þnØ[Ë·*&­R“vö™éRµOÚÂq Ž`U„R)ürPgAZ³:&DÑË’ýº*ÆH‚RŸR@ˆ èDæ"H\EÑË’ÿÐ8Yü((mþ´—É MíßaûFøqª)ãD¯>¸`A=úa!½+¾†{ïímŽìˆ‡B(s?싈 m±_P]”Z ´O%íÙ‰ÍmEàO«pé+³T> ƒýAQŸ ®2ÒZõ¸tQô²äÿtTŒ‘¥oëÿË–-ç2jàþÅí‚^Rûõqï öV¯kûΑ@h³ˆRûQãUG×Aû¿ûÔV4mÒf…íoØëc{ t?Á:ªˆl÷üëð*ÌG!û|áüƒ¹ðžšËÑ·8‹˜á}yA"dXi_cJi±¡}â‡!oÿÛúŸ”-µù¾í¯\¾¸ÝoV,_YÒÿ¦Lžâ ¨V½ÏoåÚ¿&Ït¢ü¾Û¿^ìGuÓÛ£Mª"Ãç‹®'W,_AžAn›ae •Ù Aƒ[æ_ô»‹ð5­¥\è„ ã³óÎ?×éS¾™³gùÂ@IY&Ãͺðu»ðHË'hÇwÊsê©§ÙzKíÿûñ]YðÕ#ª§´¬2Ûoÿý!ãG…Ù_¯þ[†Mx]xµ,»ê/Ézè!áö]Ý.³u#,!F(g6ì¾óV-Ôž:e*¾ÄT'Æ¿~ /¦YûTGl,m¶X…E¥Göû¾þ:Ëá$ٱǛ-\(_„£/k :,ÃÒEÎðõmÿï~Q† À¹(ÜÔfç{žØà>5ÙìÜ×›Ø÷ýÃYù m [ÄæXöz_©ùöŽÊ.¯¡-\„º jë&úZµ‘Ú¢u%ÛXÉÅ幃*KVw,ÈYú*ÕçÊ+¯pPE"áSîëMÈC_Ö{ô‘Gð•½%\§;øKUâó¸HS+Ùûÿyß¶Ád­ZµÌ°ir†[Ù‹/¼„]óë»qB_/á³ÿÙ˜1Qû|Î_=[ïÊì3à¦ãô Cª§´Ì·ŸÊøó_®rý½;¾x€ Û³eøÒ}-lÔ¨Q6mC[ëò—«HêeØÛ‡óÐS^~é匾†²dÉÒìλîb>Ùkß¡= “Æ8ÜÀïuÏÝRO[oŠÞxã ›¯‚íMbü±É5Ë /þ¢!ðç/ R…elPî¾dFu¤/&Ò¸Ç Àì®;¥n„ ~]Ã×¶(s”Ó=®ëÁ¶7ú:Ý"Œ!¼ ž 8ÛIJ؃˵³UËVò…‰/çf/¼øb†wÅ¥¿a§yóæÞד•™ŒS™ŽÑq É¢¯ñ8‰/'ºøñh~Ò¾ýðÛO¥ÓWÒ¨_H‡°¢þ¡/æQÿßu×.oûC;pÿPžiök{¸HÉzÝÝ+,åW¢ú9LÈ?¢À펃ؚ䘱ÿ©XJýˆ—k¿ÓS"P2`Ž&9¦C²ï à!¨8@Èr—ŽRÊõq 2`–&9¦CÂßAÀCPq€å.¥”ëã@,dÀ -MrL‡„¿ƒ"€‡ â!Ë]:J)×ǘȉ'f“&MÊ&NÆ?bº>åóá¡íÁ‡lòDæ/[J_ó[>úˆ=ŸTdtþÁ?,›9[ô)ã®Å×A0HG“Óáǃ?VP à„¨øj;ã/pBqA Ýô¯7któÎ7½Ì— JË©pŸ‘£4Ýèp°ejYôà!0• îsõ§ž*åGºqÒ‡ ×õèá$X‚—uìØÑ X²Ù¼Y‹ Ow][èD¼8zÁ/òU×KƒC®Ôµ$´-ˆ±ÂÙWèáfów9©KIct3¿»ý»Ï¿½+ó–›oæ|Ú?:a‘nýz ÜM£ï;ô{0$Õ›R÷`¨dMŠL_7ñ²¹==˜ALÿ7ßtK!ÓL†*×Ý¥øŒyè …LkÀ8ãôÓ}ù¸øÂÆê|3O7ôá3è§AŽ/w¸ÏžóíªwHÛv¦Ã‡wµ I½U«VlGý_ÛDé£:šeäù@ˆÓ õ­­Úxà±aV\®“\Têÿ|¾J|÷¯ý+l _J´GëúÀC¸¬£>=ÔRŠ©M4v&L˜àôõÁÉzõºÇñ™@#åÁØçÿàA<.ãO¤»º¡ŽôðtñâÅq96…•tÁÜC¾åýMêVuïóªÓ ?l£a¯±q>ÂŒû´^ܧGÛ>¥C%Ó¤«å7ÜãÔ–w³§Z5)BR3ðpWóøÏÕÇFèžÃqûå&£¶ï+ý#ø÷êÕ˦ö¹lÝÞŒþQÓü„–¥qPTÌ R¨¯©ei”³‚T@êkGjY¥Ä¬ úÚ‘Z–ÆA)1+Hd ¾v¤–¥qPJÌ R¨¯©ei”³‚T@êkGjY¥Ä¬ úÚ‘Z–ÆA)1+Hd ¾v¤–¥qPJÌ R¨¯©ei”³‚T@êL†sºÌÓÅù—øàG%(‹®¡>ê7ÿÓù_só<é?ÿÂsÎÔ·ß!²2ç=?Püá‡#9ßµ×]箿UÎ? ¸sP>é¼[ö5~DÔ73H¤ê®u¬ei³‚T@êkGjY¥Ä¬ úÚ‘Z–ÆA)1+HYrùŠåøAüÔÈOv†ßðýBЯúãƒRs¤«q ŽYA* õµ#µ,ƒRbV È@}íH-Kã ”˜åïÿtüèõšKsÿnl£¬¯¾š‡O§·å>"ýȪú”>õ”Sù†^SáKÙN®zëXGL׿ØV!¨é· µ‘EŬ úÚ‘Z–ÆA)1+H¤ª+n«ši¾Ã LͱÇX®}Ú_›ïáGöùJØ÷Â5¤´,ƒì1+Hd ¾v¤–¥qPŠ< BŠf÷îÝÙQ±gPÉy0ÄÛêf}úôqYñiúl¿}÷‰œœn¸ïÆÇT<ÙVgÇke‘e:)RçÒ*ŽB(Qyy0T‘söY^]õ°,€ig“‚аd/ûü¿L,‘Ž:¾:†UÿñåYª.$ÝXÑò(p¡Z2$Žt„cyN F¿øÿ¿achW­ƒzõ³nø»_Ý¢ù¸P)¯¢e”6k{(màz.Ô>­ÆÙg¿}6¤ÓýC7†ÓlÿPþ¨Y'¶n!}®’qÖ¤­T·#ƒþЉ•úœ.\â€'«¸Æ2KWÇÿ~c¬¢©ohŸÔ]Õ@ˆ_Õ[Н|L>DaÚÔiÜÿ¤K'Ûý÷ßÏæÿ§¾Šk„uKûpHíᕬlØÃyU ñš"H^zˆ P½n¼ñ&®KtȵŸ~%¤<ÚŽo¼Ñµ;lHÓj¿ø–àAOÎGŒ!æ‚ SðkæGµŸÚqÖYgaõ—®x‘ 3fÌp8ßÛ»·«ºçW ™ì½÷Þ³r•J²K—.¶=«ôp'l¿bÎuÃøÒ[Pn–½‚•FùñG>ŠMß­Ê¢`R¶ô?õ)=¤U:Äo¡«ÀKmiÌc¡“Ÿ›HŸú‹ìôà± šÎ$2+¶T÷.Z1DU}“Ð?¿týãOrøÒ¯*rÙ@hÿýÞ®|yqÿçÅRö©|ßjOùR=OtóÇPÓÊJ°XÂü@èHGð§|^jË¢Ò,ÎÂü@èHG8[ž“ì‡XDÐ{Pcv¹ Ì„ŽtDÂß"éIþb;¥JHK°8óE(ç™oõüò”~ðÁ# tž¿öÚkù¾æ£˜½‰>ÞN–áÕpwn—òäü£ekLùõÜMµ£Œhµ¶ûÑ@o6¡G«äàSJ4¶Ë×+:Ò®­žSaoÞåX5kc·O÷1×÷¸!k€ûíwwlÚ${~F¾¥¸kL¸…t€²%KHK°X™ùБŽp¶<çÇg_Æ¥<èÕ1*XÇãïôGTi-=l¥™µ_(¦ë¼«þúy(”û½âícЧ~¡å!›ØtZî1Â6ÍÌV[mI)m¼í·_ö¥*¡Í)m–ýÉ'c &WþÄsÕjÕÖYû¿‰}¤TûñúöÀ‡÷NW`×úmÍÖ[7Fþmbm‰ÆbKŽ˜Œð•¶OÍŠe+ðžk=ìk²=ÞIÅ»•9üÉÆÇÿ»ä73õêQÿä5˜år…¶”ÖX4KCÌ|ýõ"lx=†wèoÐ`kÞs¥jÕê%û¯Áðê£í[ãé¸$ñÓWÊzÜ"Ñž0~¼Ù›5Μ9Ë<ôЃØ}olþ8ǬÄûö‹—âË_ƒO·þd§Vl>Ýl»Í¶l+‰ÌYøÜ+¢™ 'šŸÿìçfóÍiÇRAl⨙ñK›²7ÇÞhÒ–RÚEûÔ®ñðÙ³fa\Ôǧ.›x79ÌM4õ?˜±ÿ~‘PÓ¹8ŸË·ŸÆéŒÓ¥K—£¯ð§L×åüC¾óñ¨ÿb¿´jÀ 90 9ÀÛ—ŠÛ_ÔXsü=åÛŸì ÿRç?ï?~N(åI!/ÌãéP#¤U#áŸðÿþ®?Ãñ }ó ®æá!;bÀzüëuïÿôÞqø ]Ñ&¹Uªàš×Nú¥GHšÿ ž¥5ÖÙ¤‡!­šß~þ¡}¢¾ÀWf§c/¡*ø¸EãÆñeRûuÖ’µVÛ{ûùë…°ôÒ¨…‡}¾ý±©Q£öêmŽ¥¨•oÿ ||hö­Ü$Úa‡†¼'®ÏU“!ªž}.¤U£¼}¯Q´¥%i¬ºÅ8ÔiÕ\÷öé£M´‡k½-qï»ýv¦V-»!4ÖŸ}-Yâ°Í!­Zë¾ýZò·±C8q°•æ(l@È'ÅP&9×äèrƒ Ç êè®\VpZÞžc9bMÌ:]—D²¿qâ}«áL§ÎùóžâÖ38ÊÌM7Ýl.»ü2á‘«¡OµoËŸ•ÌÌ=½z™sÏ;ÏùÔšÉÿ¬Ï¥ñ—æŸ4ÿ§óŸ›<ãùן™B>)»Ôå\Âå‘Îÿe‡+äPòx;–#Öv§ërƒHø'üÓõ¿ ;28r£$?ï$ŽPàÖ(v¹A¤ù'Í?iþÑácGGYôY0Ñ`¤diÀ`’ò¨+éã$â_Ž”¶ ã‰3å ß)$³)Ž’}™GIx’Nø{§\íM_ë\—-_jžzêi|ì/쿇áËXXJüS± BEšâÏGÆT Ñ3Óø÷ÞìQž¤“ÿ9DZÃÝ#¶ºñŸüXÁ¼g–6ÅQÞ›FX±/©O1xÌ-úßÑá×vèUºC=„?SNŸï¦W‚jÖ¬eŽ?á8³²6øää£=Êe±ËÒ˜Ðj} ûüS…”*õÄ1ù¿+$ÿñX—þŸüOç q iüÅx¤ñ§çY™ŸÓõŸ÷5½þHóošeyJçE:ÿ¤óoºþˆÇÃwý=Šìµ=ã˽¯›ºIl߇¤ª*ß)[¢ Ë1rIÎż‚Eoº%û‚zˆKÂ?F €MŽ‘K|ÉÜqçæñG3ãÆƒ_Ó¯ïyÔjÏÖæ¼sÏ5‡q¸©Bû:Áÿç/˜oþô§«X딓O1{¶Þ3ª@¾|~€ –‚¹™WB²’ÿ§ñŸæ¿4ÿÑdÎ ÑäSJ–SÎ%9;óJBVšÒü“æŸ4ÿЄÎ <‡‚,ÇÈ%9'óJBVšÒü“æŸ4ÿЄÎ ÁÔãç’™SÎ%}ž‚µÑÎ?h8á`C ±ÂtH—Qg¶è¹cü"§d$“nó-[V×sB›!m5J°´Î"Â1Ù/>ÍKøGþG¾²hÑצfØHY6\ ,¤“ÿ1% IãO@qÇ4ÿ¤ù'x@,ãÞ‘Îv £ü„¦CÚæ)ÁJó€âŽiþIóOšÂISFGšcL8•ŸPÃtH§ù·$\SÐÂ1Í¿iþýοX±ÅWEÈp'—¦`[ÃîÜ<”ØÇF"cAô I8|ä"q(9Ó<퟊˅åJövö•<†Ñ#“ðg,’ÿ¹±’Æ_0:xôÈ!Í?À!Í¿éü“¿B±Ã#Óõ»6Ë]eðé•|v!Q/òܘ#^¹Hœ@2]ÿ¥ë_]’®ÿy°`T¤ûž%x’áCšì´϶‚ˆBÚ*SDìtý—Î?kpþåCì8Á÷}Šz ùÕjût8K”(f,ˆ‰‘ì3z ‚-áO(|£ùT”(f/ˆè²é]ò‹Yò¿¢ó”áD>%Š âäiþOç? ”4ÿº©€¦4ÿ'Ï2œhNÅ ±=ùŸƒ‚`KþWtž2œÈ§¢D1CAì@Oþç  Ø’ÿ§ 'ò©(QÌP;Гÿ9(¶Øÿª°“0‚»‘œ$)kä#‚ƒHñ‹RÑ µç”ÂÍÕȆˆ-—ÂIö(AÁp$üóŽg½Qü ÇäE9Š…Q¶WX å§…ä‚Cò?vö‘ÈQ,<ˆÊ°½Âj¨(?%($ÿ’ÿ±3°Ž‚ÏÕ‹‡Ø•ì¬8ÂÓ#a ²T¬°Š”·<vNI“}FÁU@3áo½‘¥ •cxX“ÿ1v4ÜÒø Ü%Í?Fšÿóã»Hší¬‹ÈRœÕP¤/#,Í¿Œ]šÕ!¬ç¤ù—€Hó¯›(¬_ø(Í¿vÖEd)Îj(ÒOó/”Î?ì;éü£ÂŽœtþ! òçŸ |­žöM/Þ¹ŸØ%¥\¿A=sé%]%§fpåä±³ ª§q ¤bZÄ©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1õÚÂ@IDAT˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤©º1˦”©±*»X*Ö8'¶É@ª³lJ™«²‹E bsb› ¤éW )Scdóé§fÌØÏø™Ý.»îb5jÄ…‰JfF~ô‘™í(%4.Xõ Q)¯X”ä9>í(%4öæ ”¨ÄŠtý?eʾÆÞfÛmÓõG„ZŒ•¿Ø(UÑ8Ê'D¥¼bQ’çø´£”ÐØšœ4yŠÙl“ZfË­¶r••œ¢“mr¼¼®O;J ]Þ"!*å‹’<ǧ¥„ÆE³Ž#*å‹’<ǧ•Z¹ãgÆOÕêfÛí¶u¶J’Gs5Š’<ǧ¥„ÆÅbGTÊ+%yŽO;J Wq µ”à—ͨ™é}_oԾHü÷¸®‡T EµÂœrÊ©u4;u4ƒ¿c+®;îÛ$GZ¸8-—@€ 'Q"°¯Ú³>gqöém3fs^¯ E±i>xŽh!ÍÄ·´ï HÅ8”éäëßþ°aCÍ[lÝm<¦ß¡ý°¯|k¥Æ ·uÛV`§n`ø¯ªý:b Õÿ´eçûÿ‡Û~Û#Ta­ôšÌ?”a‹-6ÇÝvÛÕå˜gcíÿN:™k¯½Ö\sí5æ:ÄÍ›5gx*ïo¿à,Èþ¸ü/+©ýÒêµ'O3•¬ÈócÌë*”^æ9ëøS,Rû ø±~ÆßÌ3ÌC=dÚµkg4h`.½Ô®ˆ„å<þ+–/7×õ¸Î´Û§Ùt“MM‹ÍMÍš5M›6{™úÛnC.ÉùÿdçAØAÞ ê›K.½”-xo·ÙÑÞådçºëL[Ôi³Í63Í›‹½Û´1øVç?±â+XʾÖD´Ö/þ“ý)xppιç˜jÕª™Æóó[ÃçN;í43÷˹vHŸ Y7þwe·n¦nÝÿ1uÿ§®¹êª?[¬ýœò6VHÓøoÐ`k7À éfë­‘®Oüúæ®;ï”|ÚE8ÿ-Y²Äüþ÷±¼ÑÛ›­êÕ3;6ÝÑÜ|óÍÑø×'hÊÛ·B¤…ë4²nÚ¯HögÌøÂ<ͽ4'–oÿTà”Ô䢯‘TÒ8ÎÇ=Ò‘¿êˆ×îßr<fÜøq¦k×®fÔ¨QæŸÿü'ßWËýoP)öuüSùßÅüÇæ¿'û³fÎ6­Zïi¦N–9Qñ¦9¶\ûçÎk<à@3~Ÿ '‹—,FŸÍ2âû{ï½ÏýP¿~ýhþ•vRí7IBý!áÇŒj9 ÔFDH‚¤ á°CÞ""_zéE› RRÀ??Ÿ©±<"† JPlNP¾²ö5CwÈÙ½ÁS»¯oûZ+­¿ÚÕø»´?gÎ’{ìû°O½¢v5ÖöÏž_.P7ê-kÐÿŸ|ò‰ùdôhŸWËЂÿð§êh»5Öö[ÿÿÔbÀMŽ‚h©ösžO>íõ4þ´GKÙ'Ãëªý\–´$w,ß~Îcç…²øËôfûMj¦mÒù®Š9îO6ßÂt½¬«9ë̳L:uüàÛOm@´ÿ¸I®ÙŽZÊdpX{ü«Ð„@å{²/5GS’ýï¥ÿþv%ÿÛ`ü¯¢¢ŠY²l)^å¯cêà_C–ñe¶&yÎ!ÿõ•WÍ;XiOú½{ßkf|1ÃT®¨4ƾoš6mÌ\tÑEfåŠÑõO•*UÌ2Ø©[vp¾Q¡^wh¬çŸW_é˅ꪬ4Ê*Wš¡x(Ô´iSž†/ºèwfìд,ÁœtãUÅWÏjWcµ_öüKe$ÿ_kÿ¿§×=ü=aøÚk¯áät3÷Ï¿ð"÷ãØ±cͳÏ> zýÜ©ãmlýÿù矛S±}‰Œ …p×}êÿóæÍ·£¥Âôï×ß 0€W@<«ôïoNøõ Qÿ÷úG/ó6?2¦W¯»ÍÒ¥KùÞ›æ²C{Xöë×Ï•KÄÆ†¿m<"žÚ_¥j³tÉ2³YÚ<ŸúyŒ*íÿ´}=p£þzíµWÍôéÓÍœYsÌK/¾À&ÆŒùTƳÄìààíSû]ãóüWdýšHÂi:)Бh0idæùgŸ3ûï»Dæñ'g]FCÔ‰Íbzç•–¨~0|8öøÈLœ8Ñ4ÃòÕö‡jŽÅ¯êÕ±ôQƒÍfzÞÚÓ|'¨gŸu&–Ûmc¦N›nî¿¿·:t8:}‘Ù«õ^¼IlËV­8+W‹jiÏa¡ýiÓ¦™ûÿ}?÷Ö¶Ûn‡_CÎVA—òMž4É<õ䓿ƒ# én¿ýö¦SçNæÈ#;šXV¢BÅ÷ìy žÆ/6gž‰ºmCuCù÷߇“ø0^ú×z¯Öæl`»GËV®$Yµ£r¨®‚±Hé¨í'zéòeæé>O™AøugÂø ¦Zõªx=eüïb:¡nµjÕrå½… dô¨Ñf~%è׿÷Ù¸îºk©()˜½´ÂtéÒï®7vö+Ñ?4Q þù裑Ü?Í[´0‡rˆ9þ¸c±2¬º”#×Ç[{ÞŠþùÚœuöÙ¼röh¹¯70 –.1{íÙÚuôцö›zë-ÔíÔ úý0)R r®½æZ´Ÿy}ì1t»òJÁK+l¿Í—UV2ïб:Ò,ÇEç^mö6íÚ¶3‡ÞZÔWŒžÃÿMôo¿7^7Ãá?upºï~ûšC>Äüôg?‹ª·¸ÝÜ*aã²Ë/7+–-7¯ö}'ż‡éŸrÊ)f¯½öâÚ¬Ì*ÍÍØT~ùò¦5Æ{‡í­åØþÓÀîø×–[liÎ?ÿ|Ϋí×Xš‡|–Á%à ý¯¸‘íé0nüxóøã›!öbìŸyæéfs<øÑö/ÆX»õ¶ÛÌJ´‡‹¥B) 0ìäomßÁµEÞþ sh÷ðñÚò#3‰æD¼RuhûCÍqÇÇ«1óý¿ óÁSOõá‹…‰&šªU«šÝ÷hivÅ+kðŠo­Z›iÚnóöóí·Ù8Ïž=Í"ì=q&nm³ủÓñ*ñýèߡؤþк•9æècx£Å“òOÂ<ú$æÑŽ0ÓñëLÃíw0ôÚXG¼bLû‘ߪ~hŸi[­?%Ãöÿ¿š¿ñF_ò—\ü{³é¦›9ÿ#Ýgž}†ç?z…–ƶ[cÒ)g?´Ò¡}ÉÏ%¸þ_§ó5ì+Òqÿ+W|Åz̺œÿþŒ@ò?õ´oç[mµ…™9«£mhñÓ8Ç¡s؃qÐóÏ9çœƒ×Æj™¶m÷Æy ™Óiµg+sÅ•ÝÌç_`,\`þ?{ßhEqý=ª ú·&‚4+*ö.Ø v½%bI4нì{O>Å‚»&bW°`¬ ô6Péo¿ßïœ93³÷Þ÷(±€îÀÛ9sæ”9gÊîÎyÏЛàÓ2¹É ý¯¸âŠn&w,´ÇsÛÐaC]ªjA•Ž>æÏ®áÒøôã•ì£> yn¼ñ&¾ó럕 ±ÙŠ“Îy*Øô¿Â²  öÎl¼ãÎ;rzb")\D–A“&Nå6Ù©ýXÒ–Ý ßZ8üð#Ô¯æcm½ç}îó0KmlYï»ïÉÚµGý”Л®Î;gÓ§YýIJ[™X?¯Ã–6û)ï_?GQR6úùªm‚º½þX¶¨ T"ßÊD]3fÎL«ój’D‘5ØòL¼pg»í¾[bƒ•Íü¦1õ3`™v Å ]±þ›5o– 2$¨½ñÆÁ£í_ÚTÈQààC™ì+’r:t¨÷W,Ó3g)•ì'O¬òUeØl2ð”Ì6ÝtSáI}m2vÝeçlâĉíÛéÓ2L²V´ŸõùÐÃZ˜`åÁDf¶n‡R¦´þ©ë¿ÿý/¨Õþm·ÝVxðk&~Øœ›“G |ƒ-»ì2Bƒ[åJ\—cDåLLl†²Qg%û›7[ãÇGAä¨Ñ£…‡ãŸùÈê˜6Ý|óÍ6Õ:&¢Ï©Ž|û§ö¹ïàÛPdú½}»¶‰ž¨“ú7n"rźÊ&zqµf ËÀ²ÉØ›ôíÔ'ìÛ©¤n`ûÖ6&qâÞ3¦NÁ8*!åR̾ûí+ý¿ÚC @öÓo²Š Ç¢¿m»öÖt¢¨”rå2J ] ´iªRÿ+è“)We Å&t €¤©BâµÔ1e.®53¡Nè€Âÿð@ê¢ýiÓŸ¤Žñ-JŸ«²ØßcæE!Ÿû\Æ6ŽöLEîHØ'8þqüÅꃤ\y’Ï?W=ä¿óŽ^13°@òÒ”é\š¥x¨ÖÌ„:¡ ` ýð@ê¿þõoRï›lºY†Ís?f&ƒ$ï0¾å¸rd%‰Dz ´iê·XÿǼöK<ë >"ÚHWegœ~F‰/³ì‚ .¼6Ú°,¯+…D6ûâàAƒ=Iô8ß«å9 ùÓ¦}ú¿PD² ¢kÍLèº@èÒÔâ\ÿ6ÿpÀB¹ÓRGsÿv¢ö¾ÿ`!KŽ®ë1Ú?ìÐÈ£ˆã¯H¯¬ÂóÖš™ÈOè¡KS?¥ÿã²´84pyóÀ͈`x Y¿êóƒ±øVú¬„U6üán?¬,éûÔS F"nG$+0ð‚*+ :è@×qý ÜœLu–˾þ—ú½ˆÐëÜyçœ+z¨Ž­ž¿_}6ÚBX§C¬báìh†ÙÒ×\§m¶<Ó~"ªÁ)…vn~éæì¬éç¦Øk®¹–ȦuôyGKÿUd´Ç¯õ§œÚÍÕÅ ‘‡zX–câEÙ†§žêû”çñ‚É«<®Á&`,+^jðøvR–ß+VæWr©úècYVHÿ/ÍC2ü2ßE6u¦¸bƒ‰äp¶9¾Ua§õU€R…O?ó”ü’Å¥ÅbuÉúø.ž»²÷º½—üjô–9^wýuîœsÏÞ ‹E‡úà*n„†ÀúÙ«ŒXfG½¯®à/ùdxéÅ~nÐàAÂ˲±ü«ƒ ßüVÁ†_ÌO8¡ßz2ŽÂ¹*¥.– ²Xÿ‘>É0‚$6:JS \}ôÔ“}…æ„¿žàÆê¤wÞ~Çÿ pìøtY þw¬ò úð"®½æ×K8âHײe+Y†ø<~™áF€Çüå˜Ü'—dðlÊì%JIô>+’(%&‚ý”³Ì²Ë¸úu¸š‰¡²ý<‚= Ë œõð+’Ê Ò%={°¾ø«$Ö[nåvÁl9y^}åU÷àƒºw± ûº…Ó°‘æ]wÞ)üP1ì%?8LŠºá_ Çj³ýÐVq—ÿ•Ê9¬¬3El½ÕÖc«^Ös?ð ƒ6M$¿ŸÞm·Ýþ+ú'…Žoç¹rnë­·6õÂÿ$–Jó”;†£Ž>Zpz d%@yý‹'ÿS—ÿŽ}¸ŒŸËüŸÇIcc0îuíz,¾ÕUd.ƒSõºIV_Ò7Ó§Os·cl#K4k²Ê=…Uiüõ˜ËÿÄ~ëaU]†>w;öà/µ/aYñµ×^ïÎ9}nnøÙÐa’¸ðÂn+Œü.ú£?rÿú÷­2&óg£W]•®5د&&¹ëškJú64p¯îg º@wŸû݉'þMxÛµkëºqEÛ”qô¹g±ÒÈ£r¨ ÊdÊ4h'`têL•äC«WlúµýK¦rÅ Eæ®&7,PLzj£Cò4;/8¦*÷¿œÚ0¹Qa¡ß×t¨8=x¨ðÑþ¤Eäˆ ÈYRúÊ©#T¸rX˜_ÿŒUÜF·úêkÌKôFj?¤±¿PhÅPóøÃÂ"¼íÛ¯áÛ„ýo±ï{íµ§»é¦Ý[Øâƒ«ÂºwïáZ¬ºª9r¤»ÿ~¬´Gàû+SÚ[¬]…)tz1:aYìíÿ_žÿÅô…´Ÿ>å~’ìƒ?òˆkÕª%¾”˜…%ï¿@Hà³󸪨76DþÅçx¯çÖ\k-·Á†ºU›5×¾çéÇŒ§pE8‹Çw’ºàÙ«7ÁÊBêçÞ9«¯Žm |Z̬¬^µòŤç2:$5ýŸùfW4˜Ð^]öt7Þp£{ó­7ÝŸñLôŸæ-VÅ×3è?x¾e8ï…i ï5¤ã/01Ã’¸²~yþÍñÊÓÒæ5?±~›®Jg¢ ÇC(b¶{—=²nÝNøâ‹/ÎöÜc/¼ÏWeO=Õׯȯ>ü‹ìæ[nÎðY˜‰’/­^:…»çò¨Þp…ÄèˆÀVûù¾¤„Ä œÈÒYq®N7úë ^¢³AaÖUUaÓ¾ Ëw±’bf(E^rÉ%Aÿ Aƒ|žê§äOê®*ësä'e ’Ô„Oóò4LYÙ7Æ,t ªÖ¬™ÙËý_Vt` @vúi§kùàÃùé1b8V6Ü”«J¢?0#¿Òsu˜)S-üè >ðö³þï¿ÿ>OSÅr1çôÓY6­ÛX¶•É .–Û®¿þº¨$ æ Tž™š‡Ó£¤LXšã`û¶ö‡Ï愞ãÇ öc¢31|„äႲVgøT/”õå—û­+ӼϦNýRp¦ŸñÁ"<¬÷J%æ,öž{î!å¤ï¯¿þz•ˆàekY„Ç×Sà ÆSí´ÓΡÌ7a¥‹Í@éo¼‘½ùæ¾lÕÒþ¬þÿ„™ø9Xueá«/¿Ê¸úÃêïI–øÍ—…6<úØ£ž¥Zü†ãÝW"2Ìž5KÚ!é±!fÐï³]vÞUÆŸm·ÝÆP%±ÙX‚–¤åU'«™ª2|ž*¿}ŸÖ)6´¬X…¾dجÎû‘+†néˆ,ÇÕDßÿG*Ž‰Ë ³fÍ# ûÕcÃ6F*‘ pö¬Ù™µ³“$Ú²L˳X ¤o'㯎½Ì+§ÀÞe±“<›m¶in%Ç%—\Ɖ8Ž2‡Aõî»ï>Rï¶b(_¥Ôww«²©S§(2Š@ý$ã?ûO>T’f–g±âó)£e\n¬Šš¸j«<•ž§É§”Âè4/¡`RÀ5áIfy+k>¥8ËѼ„"€Hׄ'™åY¬¬ù”â,GóŠ e\žd–g±²æSгÍK(€”pMx’YžÅÊšO)Îr4/¡`RÀ5áIfy+k>¥8ËѼ„"€Hׄ'™åY¬¬ù”â,GóŠ e\žd–g±²æSгÍK(€”°âíyΞ1L^>.OaO¡ÌVÍbÒ=ÃãB¢+€5é *a<+:ŸJùj÷Ýåþ¨÷o¬Ãsß­øœvÊ)ÝRA?‰þ €ªÙš˜—§É§R©µÛŸRF¸fi‹ªÿã?’çúõÌ3Îôªª³u×ÕgÞüŠ!ÕS»=³žüïÙY¯^w„"“ãŠ+®ú6X)Ïpà 7(?žž~ú© §Ì…窞5ÙYžR-Ïbâ"VSéuñ𮄡èH ,ÖÈ1ð[ùjMãѸ{÷´ÿþ€¬9Þ}YOü2„z•Òøåõ%8Ѩ&D¡( Èšõ‹3JüÏ}¯Øf1°çJ³Åæ[h zöœ©dÿ±Çuu-[µêgYO̠̾ ùPTú ÕÔäìuf’HgR2ÇM&}ô1ôÏàƒ/áƒ5Ó›p #YÕo¹¡Ÿšßȃ/×ôOÇéuÏb¯¼¤»Ž?^Wà%ú¹"h“M6 õÏUi*'s=¯è‰•vÜCFÃò+,ç.ºäbKb?²ÿÌ2ª™ö»ÎíµçÞžÆÁo;`+&‘ä7Õ§t;Yèî¼ó.÷í7ßV£ñ)—ãŠ7Öö±Çïe1Z¸úWyfl†}Žu­Zµ´¢HÙN;í4ÈeÈÂj㌳\£FËŠ¬£ÿ|”»ÏÑ ô?~üxì—Ã:ã*r xþÄûʲÿ>Q’¹) -ž?B«åü/_CÄ>3ÿè[~uÃ/$4dÒÆŒ‹¯3¡ÿ ôo³ýcbÈœë=瓚Ò7MåF¬<Á€3péUü¬…tæ@­6\N¾lŒMlëÕo€ ¡vn×]vÁfÍoƒ–Ò«Ü4Ùµ½D?röÆÆ{c£S i‡ó(ñS$†1ø„‡ösnôÙ-[µÂ&¸«K/Ò †-6I±^P›âxǦMWöqlšÜ1ðP¦Ó¯åÜ{Ÿ½Ý>ûhÙÂ{½æí‡¢L¿Xí™|ÑAc„™ûÇ?º;¬Ô¾«¯ºFŽ$]a…åÝÞÐw÷ݽå3’ ”œQˆÈQI¾¼&9•ôOÆÑªV?Ü$¶>«Ûu×]7L–Ùú¹ŽÙ¯h—õCTÖ¯µÌ?&’âà-Ž›93Ä¢[A=Æ'™âÄHÛ¶«¹åå“4aË]T%û;á@â™gž•‘­DÿùïTòZ·nØì÷³â"'Õßjš•¹/†«XÚB»-¦Âz!À ˜ÀçS2AѦm[ÿYžRÚ5ÕOF+Zý†Ï{ôS>JFþ§úqbÌ+a}†„bú‰7˜›ŒSÛh‹-rö3c lVmr>DzYнˆ¹qziý¯»îz$“0s&&"½~™\ð‰~ÈHÜ=8†•Ÿî½ÇžOFϪåµ(*Ùo6~â΄0Ÿu*²¾>"ý¯ÊôêýUAÿd3|ݵWcLÜ37ð}n792ØdN›þ­€Ô¯ã>³„,NÀ`ß·6*gß»ûî»Ý·_éæ†Ê»¨ö'… ëÛÑ?±þUsàSbrr“KCWvñ´©|&'¸ŒÅM¶²ÿí7 Óï5ú¤¦|B"£3ÉB!™÷¿Ú_I?u•Ûo%ˆõOL¡ßjHT³f…káÿà Eû³&·xµ¿*ý˜zÛß§úº³ð 1;?aæçBóoÿj¹}Ͳ ö?‰­DZ?ûÞ ›­J(úŸ¸!ú|ñÿoÀv|&à|؇J¶xÀjq)?·XwÝuqøÉóh?¨ÔlÅø«Ý ­ÿã±µÁÇ8$†Ç–÷Á'FõêÕÓ®ˆØiÊÇß³Ï>[>ëøÁ lËqŽ;Ûôìy©šÒ¸ISéã_|‰žˆZž‘"—?S4ö•ô*Í›áÇÛýð>6-Ü7Æ!7A?ÉÊõ 3.EýWnÿ×á‡êŽ7@ÿ™ŽþÓË=†mø<Ë0}Úwè?ë¹ç°-BÎÍ’«—_³ÿå^¢ïD|.¨âÇi^4!©Â·wÊê"Ïç#FìDݺuÃKÏÛ®¨“±/Ç 8Íç™…#WÔeúU^ÈÛ´F£ggc°XéåêY¤œp„•%&‘'þ\pÁùàÿMÂÓ)2›2yv1ŸœüMX4‚a)¬ÐÑ Üæ ®† B$‹:¼T™¾Rý&ItxéŒÌþ50ÃÌ—®[o½Åm‹½_¨“ƒ>÷ô9òˆÃåe›ûÖ¨ßl›Ÿþçž{Î5ÅNùZ?ï¸uÖéà°ìTöbcaµÙ¤E[·â„ ‚d??ûýÚÉ„)Ô qfiûKéÍh)EÎÐЯ‚¼Nµç„ã,6Û•U1×]wƒÃfÐ2YFû>ê(¬îà~NJÏÇ; Ôm ¯Ä~+VÈ—†¬Øo·ÉSq¼þöOĉrVXa%€¬í`AÅþÇãI²ÂòüECXpÂ×?6z4e}÷Ýw>tÚ’ôÁ5o?OÙÒ×ߪE+9u‹y·Þr+®(¾f¼Eà*Ç}¡JýDýV|Õ@Næ±4R͆óYˆ¸Sˆ}ÈDgó$G(ŒL„ “ÔrhÃI2ísM°êT™§Œès§àÄ6Œ‰Ø«*–RÅÿüʼnãÁÍ7ßê:m»µd`SxÜÀÇ8z„[µe œX†I³ùõ?éýzA3¬ý1ÅÉ†ÚÆßIqòÄÐ^Cu,äÇS|j&2xiˆqT5š…¦ß¼eÅÏשÿ­ÿ —gµnhþ· 0}RBÐZÚ4³\†#lö—Ö?ó"¥*Mõ«`~$ÿúÕÇâõP¹VS…ÿé—¢ý™KÓXÌûjŒ{¡0”_ÆŸ'û>‰¥v—ç¯ÎØ'ó:œDd\¾æk¨ÿñÝÇzŒðgiÓß·o_×'W1tîÜÏ<׉žbüñ¤7óñç½ï¹¿Ÿr2«Ð=ûÌÓòn¶çž{ÉñÚÜ“”ûþ1ì¸ÓŽ~âÁڵɖKqÿ£ÊŸ?ˆåŠç»±j¹|ÖùNË®ªÂ^§XÆÇ?Ù£ yø Lú9\·þÆþ×bÕXñ—úû½þñî$¿?#'%tBUØk[}Hûã¤ðlœT6rÄ(צM+‡8ü0àiEìb>þUÔÛ¼jøiÚŸïË5èç o<)šá™gžCÿ9 '‘í)ýŸ âGÛ¶RçØnCNH®}üº(/­ؖηRjøiì§l(_Dýò®æÛ- 0|KÒ<¦¡uë–z¬%ýA–ä¿Âño; jS|†2lØ0lVý>~ñ¾Úý ŸÂðÈyÓôz„áã‹5µEÇ“T¢:ÑÏy¹á)5¦7®èy…{îÙç=3 [«¨ç‹/¾cæq‘Ƴfà3…y<3öìZ£W ^œ×/… ´`©“Û+o.b”qK¶]H,!²WÉQ¤Çâ˜Ò~ýú» 'ÉÒ¶ƒpÔ<%p9`ÿýÝø ãTµˆUýu°13Õ}#µ%TÐïTÝÎ8žŸ ±~cc諯¾ ƒÕIrÔvXn‡ALÈ_p,ª¨NôKŽ·UÐDxý*iäÿð=Ë(H•¸¯L“l@<ueA(~ÿóHm{ ~«¤N>åï®Ï}÷‹šC=\6%KuæJ\Áþ#G9ËÇcjð±”Ä|ò›ÂAAå¤Õj?YF`f¶W†µ_x>î}^óæÍE/ŒOßJû¿ ¯PËV­ò!cµ×õo/Îò¬UÁ~Š:-Õ>wdxýú“O>qÞxÝà *ŽŸ+Eýª|aú_â— ú‡IõÚ¯®«‡Ä>ÐF3€Á‚7Q¹þ¯¿â˜ˆ>2މŸãhaì½#c"?ܘG‹~ºR4˜ZŒ+¹ãñ™ÛKýúa<˜(ãÁÁ(¾ÿãÁþ“Çã3Z ÂZ®ßW”ÒÄ‚¢ÒëÈHKD¸EK´È!ÌÕq3gaÜäÆO~vÌãe,1Óv:?ÇKü\“~P•Ö?ž¼€ÕÀÕV RïDŠ ÿ$ZDû…ÝËañ’böúÕ,`¬Î$³Ðj»ÂýG}(ÄÉÑ}©§ ÿ›R¯\´¿¤Á#±Áe‹Yÿc%VëÄÔg ã'…öؽ‹l²ÉfrTòRKëV ó­»ÿaíöóà†.˜|bàóßã?Ž-ø‡ÏT“ <¾.Úß/Þþ¸Òµ…“pÝxߊ•9þÀ|ï=÷†J{ë7œ<àb÷)ê?8ųfé§ýù;Rø/ÞâÅþrÛ”@[Ëýo¹å– ª°ïŒÀ«6_Ub¾ á¦ðüš£ž·{›ŒkÈ0¹„•ä¢Êc"»öþ¯<¡„".²‹E^GŒR¬ÀK@ÿc™8±WÀLš¢…üúaǹEÿÖXcM|™p\¥–¿…­,,)öKykifãJõŸ¼µÅø+©ÑÄÐè`lg,ˆöíÛãŸÊà%UfAÉÍ,ÿÇèõ7ßÂUQ×ⳉÕV[ )¾.iøG C•H²Ð µ”šú=>Óã$Å ˆxRP¿—úÉ' _z¾íÄ>FÈÚÿ}1‰Â—&Õ¿ùæ› 7óîºën×°aÃø‡}M Í¿† ¸:8©,¯_Ä@’†T¿•”ô¹×[øÏ¨ RMŠÏ–(“<££¢&M›À†ýÝ}ä¹>qäüøãµLCæšq ðÉ7^]`¾lz Øò¹b«Ü5øEJë‡TŠý/±â-ÈfýKH•S%I%æEò”Ž×Jöóæ%|¹÷ÜŠ ×(CP}äÑG¹ÕÚ¬&eä‰ iXýÔ§Òi¥pO ÓÝpÌÌsï–~xéîuÛmî‰ÿþ/ºÃñ™Î]néßÿ.ð(Fý/Å(Õßë¶ÛO=k¯µ¦ÀMšp%ŽZûú›Z'¦äÈQÇ·û\R•ÛOÌÑG_ ÚÀm]÷ÝI(¡T?‘¤§ÿ¹Ú©5xø^÷îÝ-·­µÖ–SΘÇ_;8¡Ë ¥¿œú¥GâS±-·  $<•kLƒrõÂrfãç*A¶?¦‡ ³_¥ðª¹ljÌ3~¢wØiל'7 <€Ìx PcÓiì…Õ¾L?y+µ?šÜœ~L¿RÝÖë6DšO¥A°¼ˆRhõŒá³I{ä[oë˜HÚ«ÑçÚ .ÓðûòJí>"»Ê­ŒOµ8Ü{ï½î¬*”€¼?úÈÀ…³?)D%ý¢–4^¿‘o¾™Ž£LßuÇ]XÔ@Vqì¬SÒ–²qt©8Qƒ+ÂÊý¿ÜrËKýñÛùé\"­JüUXÜJè?æÿ7߈7gäºQ#GjÿaÙô'á]¨ú§ ÔÚ`3ê6Àh;0ò\ý'e/ô[ÁSÖ)*Ô¿ù1Æ…ÿé‹ÐæŠö}±„ö?ûl’·«×ÜýÇ7þ^Ø{„“BÌÛ|‹-Ü38ɱ~è4dÄ ÝER¼㟌ߒ¬¹ÿÝ=]°§Y¶ØbKìÕ÷´|’$‚j :!;ÀÔ¢?á-Æ¿šý/^'NO. çü!¼ ê5úÙ¸˜ÿ›àYÁjlÒ¤‰¡.+µ?-ÄÂéf[ä5ý"óW0þo²ñ&øºd2~Øâ&c{”©ˆ-MÜš«ãYæ{LWǯxèañû\œÂýÈ#ùm*òÏ?8äEV}“«ì[·j%5·¶hŒ/mH}×=w‡:S ÊÝvÛÿٻ첳œn+‰_¹ÿ¥‰ÖšµµÎ¯ý‘%räýϜٳç÷îU:Q|Þtå•åù…½y’ŸCPÍ ¦_¨–äöŸî“6ÌÖ²±ûúôV†å¦1+ÙL{.NO`>ÿ|à!áâé=âQàN:éï60<–Ãe]»èÉÃÓmL¤ÉÂÑÕ‚OI0š@ ¬øy‡vXü‡úäq»ä©G,§òUg8ò9”ᢠ/Êp4y9yÒä¬Gf Ëô‡²uóeSŠx ô¨ŠiCú8‰°G†ã²3üŠ“=ûì³8égʬX)”í³ï¾¡ÜXåõzèùç^À˜/-1Ã÷¨ÙwÓõô#ž¬…½aàë© ¬ÎðéTƒBÙìÙ³Äþ‘#Gf]éò´~ôd)ª`I‚¬~˜aÁÛbÉàT pì7x­lûŠÏI÷ýw(ÛÀ÷2 ¨Ê–È ¦Ïb¬Hðõ¨ä¹kÂ+øŠiCVãä†f"ÿ̳ÎÌpäd™~•aôY6'ÞY9xrÛ}÷Ý—±<üÓÓ“äã—l‹-¶Ðjë;ï¼xx’™œ,ðõ×Ù“Oþ7ç–’‡‡F9¤R™‡ øM÷Ìä$=-´¿ú¢ªßÔ׿óè7³GãîÿèNHèÐaÝlÀkpÒØ9“Ùa‡*§ƒñ´?ràÛgi£, }ðÔSOIߢü›oº%´¿vØ1ÃR0òjÙ«²ýëÖ|aAòâ‹/„|êÏ´ÿž={J>n˜á¤2l€Ÿ#“„ª‹øŠiCj<~Üø`?nÐÙ}÷Æ:½é¦›‚ïY§ó|_LÛµ)ÃcÐjýߌÓÝJË£}Në䤓N˜¨§!Ž>w¬õ9Íçɉ :4 ãÁPtŽû–ŽfšR•÷É7"'‘µ•0öŠ.£OÄyTn½è¢ ßd›æŒþÀÑŸGï 8õ›É«Î.¿ürñ/ÛÿE8 'XbϹ¬ÿþ:^ƒó·ßuÀSãž~úi9™ãÉ'Ÿ ý‡m‹§·•Õ‹©²HÚ>ÎGd}’Q&YãR|Å´!}œ%^´µ9$3yõeøÀ`²2¯Ø°!ÛDúÍÑ5ÁY!KR|Å´!}œbõ˜èÂÿæ‰%Âÿ£GÉF…¿ÑÙèQ£ñ|ÚFÆ/>ƒŽóøÌÅÀ{d {ø1®*ë°N‡ Ÿ¦dC† ‘x0NÆešX¡«¾@›Á ªÃÇú\•íˆûnª/?ªí¨G÷ ÃXÊYEÏà!Ð58$: §øò4Ίö]auaK[\Н˜6¤óÑBõÿ_|1Ô'ïÙãÆŽ·’d<ÝõàƒxJ®>SŒ1ÂËþñôe˜èZÓFäã|´Pö›š›hCTL2¯Ø°qñB ýëI¼.;ãt;©LyÏ?ï|ñ?ß_~ùe<ÛLGOŸ}ôQ¡îþú×¢ZÄN&ãsÍÝwÝ%ý‘ïÐgø‰çûTYFøœŠiCú8-‘þ7?èøÇØÖ­87 ïÿ:k>ÍK5Þ;´ÿðù³ûϸqÁ~é?á”i—±ÿ˜ç"`škówB#LŠóQи ýEÚ)`ª -iCæ6µ+€4$…0”Wßy»dbÈ2ÏÃq—úÒ‡‰!yY«ÆKöL<´7åÄtÈÃÊ—ûbòMž<)Jƒ~âÈ—¾œX¡-Ž YÖÆßx=ìPŽT·àÍÊvyÏËÕfdc¯l.]BiøbѤqã Ÿ/½ØWdšD‘…FtZ…I£)¯ÙXZ¡‰„eåísÿýA¿•›“ëðpè5ý‡ Á–Šå ]`'­|!&?ÈÕÕWKñøß4©ʵI Âi_ð¬ÈŒ)‡õÃŽ”£©ÉþyÕóÔð¦¶ŒÏ Á¥Pö <¤X>õ6¤N±KÕ8rF(dz@r’ì#Ž8"ÊG¹0k/í =Ú¶Ûn›1ÿ¶ÛnËfc¢„a&Ì~»áÚD\('ä ÄD—…jØ„Ícý%ö“gï½÷’2è‹mR8/€G}‹l_ÿø\RŽr7ùGÎj<ä‘G럼lËô[©ýä­F½\xa|`$}ÚoE7p½ïéÚÁ'˜0bÿVZ}ð0:ÆØá?ã$£™òåÿ×­ÿ2´«³^ˆ8”L +þ¢ÿ gYØ3£dr,µ¿DAHV²§ ©Qn³¡’ýßì`Æ1ã¿ù?1”äsLäÄ–Ñ0Nû\ sršáþ>}"½÷VÞeër<@ýRÛßÁÕþ´¿ZÙN+™ø6Ù›Y:Žîˈòðhå&M8ŽjûÓqT'ÁJýÏ›µéLcúÿ7Þ5ì?œb¿ÄZ?jÿ>{óÈ{N¬¶õÅ*-¥•Ö[Z– JÆã4 ‹ ŸÆ1/Bi>aÉ)ËN…þR—IÚŽ‰HSÆWUl¦ãAÌ~+/'縚ªt²Ô³–h/I&þ·œØ·Ï ú-ÏÊÒÀröì¼óÏÃ8Ê_gÔ~+N7ËpÂa)‹¦½~¶ÿ0)æíçÊ­‡z8ðIÿY¿c¨Êßb‹Í1û>V¬õ•>I?ZXTû_âR!¥é@\žQŽ ÄQÚÿb†‡J…”¦CyF9&GD…þZ(>Ò™^‹?è0àsò¨ú%úrô^ÿ`b¨Ô‰¥i_ŽrÂJ˜@ŸÈÿTPcQ£v!ú)êÿçÖÏûoÏ+zâÝ ü‡:®"cýçV€™ ÿKÿ3w”Å¥¨4ðÃ/ž›ÙÎ9çÁ¦¤|¦äÊîðŒc}1¶…À*k®"*ü’‡‹äú,ß?¹ºïµ1¤°bË1‘:@ ú5´ÿ´·sLËgôuSý˜\ïÞ½ýsçÌ•gh©Ÿ@«ã*ÀZé?5:µ<£¼¯?"J R!¥é@^žQŽ ÄÑüêÛÉ'Bð+]+Þ…?}`?®)°éúl²x‚ÎGØá›ûĬÔXO2bm‰ëåM'°«°ó hüõKs 0)/óú¹·ÊgŸ}æš4nêV]uUW·~½ŸU°U ìçFÓ#°—Óì¹óÜ*«üGB7•ãÛÅ»$îVP¯ßÿð=lêæ`ã´¦+7uÍViæêÕ×ÓŸRû§áHË?üPöðYi¥Æâ§ŸÚÿßaóéa(Ûl|#Ýekβի§¡ø©þysæ¸qLäÊ £ýe¡ûåKÌ‹ÖÿÈá#ݦ›m‚o‚'»{z÷Ææ¿›¸¯¾üÊap3°‰.÷/¹ä’KdÓg]°9Oäâil·ý¿Û&#±ò87jô(·Æêk¸åVXY¦Ð—À’ˆÇëÆŒãÖXc-÷<½ r,ÛSWôÿÜyÕî£>„V.÷A"@A½òûæ>ü{Ò€¾«­ý§íoŽ‹ÿè“]lxÇýÄê³^jéÿXÝÇ ¯[·žÃª ð±‰V>‰yQÿ/Ný?)­øiÚÄì3…É:œ¾Æ}prbC¸˜}b-O$²¤Ä¼87 § ~ŒºY­íjnÅ•¸‡N@’¶îaú9 G¹ðKƃUd¿!žŽaDEPèe*Ò—ƒ ‚ùñoAõ{‰ùÈäûÕ×_»Ï>ý§6q<-­ÚD(ŽÐUÖ?oî\7bÄ9qc%ø£E«–ÒþDQb?–úº1£Ç¸5×^ß×ûÓïý & °¤Ä¼ütö«*S(ª ýæ‰y)üÿSõ?uµ9¼h¾±éøX´?ø¡è¿Öñ? â`Š ?^Êž~¼‡®¸ü ÅýdžÃÅ ÿOž2Iö4mÒde×¢y W·OÁ o2\‘>ÿñýç£O>•=oùÓ’ºÂÿeþ,Ú_ÑþìÉ3×·4QÖ^r4ÖÁ,.úŸxÀÜaq1þÀ-zË.kOÅøSŒ?Åø“UÓDYI3ø͵¤Å®xþ)óg1þãO1þäF•4QÖ_ÒÌ0®„¦ès‡ÅQŒ¿eíé«p =gdr!Ôë’Ù~€Hñ9Òá/'¦Q’vÁUÈHQ…þÂÿEû³—Ç’>–t´ÏÄÎ Êò‘%¸ )ªèEÿ+ú_Ñÿ8’¤ãBY*Ë+A”$…Ip2RT1þãO1þãŒt\ÐQ'^ËòJ%Ia\…ŒUŒ?ÅøSŒ?ÅøÃ#âÈ£PY^ ¢$)L‚«‘¢~©ñ§NùlNúòºE›GâZàCÌS$—}‹+=MÌ'Z‘‚Ëgˆ´ˆ*ôÛ Tø¿h±_H7‘î塤oý>)ÆñBh#¹¶SŒ¿ÒmÄ'yÇD¼@Åý§¸ÿX)î?æ é¼øg;‚1¯¸ÿˆkÄ!ð…÷Qôý¦HÁå3ÈšóeÑÿÌAEÿ3OH#áÅ·-‚1O‘ÅówPÑÿ舤} QŒ?Ñ'±ãÐ1/Ðo÷ùO>%Kg¥Ì9¡)ÑWþ8xßÇB#“,q¦yW1âÓô"Œ¸Ô°4±ÐŸo€ê:ïSq©úUÜÈêð¾Ø<&u½ÀˆKáÿ2×Q´¿¢ý¥àÚHŠþ'~A&Ž4ÄåGÍSŸ¥°bäJ4ïÅø“8%‚ÅøSŒ?Åø“Ž*ìÅø+#„ ©:®Ê0=㟼5OhsÏ⊠2Šñ·¸ÿ÷ߤSD°¸ÿ÷ßâþ›¿ÿêC%7™b¿1ÚØ‡j„r7­\¢œ¥,»Ð_‹ôÖ^æ r–br,¹D)eÙÔŸ" ýpTUáza>í§¼E•´©ùð—eQ´¿¢ýý¯l(à S<Ð rcj.QÎ^–]Œ¿Åý§¸ÿ¢£Ïa(à°QŒ¿åƒg ˜Ü˜šK”3”e§í/¸‚n+Ú_yã©“kS¹D9CYvpzlu„H*@kA²J’¹^L> ÚG²xMÁÍ•,P‡jóXI(¦Ð?‰3é-úÌû-™+}náq™÷QôŸwðEû3ç°yýOG›bü‘V!ÎPã/<"ƒ ÇzÇ÷›|äË-Æ_q™÷QôŸwðÅøkÎ)ÆßâþSÜõnSÜeTg¨GŠû/<"7Þ;è?næ#c±Üâþ+.ó>Šþón¾¸ÿšsŠûï‚ܱb¨“áÈ~´κT‡-Ã,\œã·ú 5W®9¨£´»êª+Ý”)SÝzë­ç<ðÀÈPåø™`øô« ù_ ý%î.ü_â%³ÿ•¶ü'Ÿ|Ò½öÚk®Ñï¹óÎ?/d×ÖþŸ|ò¿àà~ߨ‘;ÿ\ð„~ØåÑ :̪M¿>Z,¸ÿûöíë^}õU¯1JnÚ¤‰;¥[·Š%‰TÈf‚!¤¹rÍ*¯5 #Á| ? …~õƒ÷®ø(ç(Ÿ¨t$˜”ãg‚¡ð¿ú¡ð¿4i#¹†â݃¨t$˜”ãg‚¡hꇢýIc6’k(Þ=ˆj@G‚ù@9~&Šö§~(ÚŸ4i#¹†â݃¨t$˜”ãg‚¡hꇢýIc6’k(Þ=ˆj@G‚ù@9~&óöŽ«¯dýgŸ}æ>6Lö`Y§C×bÕ²©«Ù4xð`7zôh±sýõ;ºUVù£Àé%:…s–UÑ)áHXšƒÜvØaîî»ï.çˉýñõWTX‚Œfý´úçÍ«vÓ¦}ëêׯïá%ÛÂÏ¥ßô•ÆÔ_Ͳ}û«×°[“C,hyv õ_NXމbZÿ—kVÌoIÿ±Çëþýï»ÆM›¸É'‰æg?yþy7q“&+O™/£ú?óì³Ü={¢ˆ…h†Öuƒ}`ÉÏÏþ „€ÅÈþtü å«(ì·ûU1þüØ÷ÿZš]È*Ú_Ñþôy±èEÿûqß? S PŒ?ÅøSŒ?ì ?ßø;cæ 7vÌX×¢E W¿A?_P»þ?€güXײEKy‡Îuéßàó·~JF/Øl‹yθãŽ^n÷.]\—=öp=/¿\hŒŒ'dº0¯¿>À8õ}‰ø3zBf¦æ{(%ôy^S¶i˜ŒÀ§Ôù«±"6zBö܈<”ª,6Ë]ô¿÷Þ»n…Vpü#&ç¬èˆ£Í¿œý,ÛŠ+®èVùƒMz¯#òPZÐ%Òÿ¹H£üßâà-ÛÏPÿ¾2«ªÎ~Y¥Å]Lûßž /»ôRwÉ¥»K·k×N˨{¢¢Y?ƒÿ}I¬íú ÿíÏ:EÑÿb[ð5"¥e±͆Pn«Þ4f¦ÿ3zaÏÈC¿¸ýüQäž{ïq›m¶™[¹iS×í”S¼ˆJž?`UnÐ4-ýkâqˆoºñ&1Îl€U¹”+<+7Ñ«\UÎÊ®éÊà¹<Óû¯ÙêÍÊ›ÆÌôF¿¸×Ò8Õ’…ðÿ¤Ih;÷ ílº™Ôk·S°rù7dZõ/fõÏú¹÷ž{ÝæèÛìݺ±o# œ,ª„ØP%9iâDÔio𮼲;•«ÑAcdϳç¢ÅÌ~-ÛO;þΙ3Ïå—‰¿~·ôïä¹¼aÃ¥Ü&oìúõë"”ëŸ9c¦»ô²ËÜlà~÷{åi€‰¤wÜÑ]rñÅnÆŒ|ѽ×Cþ¯ç?¢P°AYËÃÍ(îÔí\ï{ïu×ßp½«W¯¾´æ#†;®ªÒì@ŠzûÙYä& Ý ST¤Ü+™Y)üœúÍ_i9~ýS¦L‘LŸ>ÎQ¯™ï,Eü1ý¿öOFÙX†éÓ¿Ó"üÌúiû3ú}[¨ât3`³ÛâŠíOÚ*)RBÿ0œ¢¡ÿ/@ûËË 0‘O¢$7k6ß|sÜà7W~п÷Þ{nèС,} ‹YÿKÍ’Bþö‹áøKù¿ÐF[ø¿h¿Ê:¶‚ŠñŽˆO¶enZŒÆ¿©S'»ŽëoàÆŽë+O£±ã'älP¬Z2yò7eòä}šàØü^jÌLrñŒ|¡ÑHûA޵#äüð¢°Åôþ[Œÿ¨0V¢Éh|©-m;ã' -)Y¨^VsÖ0€Œ½¹Ú¼”¬¨ñCê’à ïÿœ¿‚søñwò”ÉnÃŽ¸1¡o³27n,û6BÿOž:Åmб#êtê«\X„1ãÆáêK³€ú=µq‰¹üÊêŸíŸ[É  ‹SØtÕo™{÷w\çΰÈåwä‘Gª`ÿ'Ÿ}êöÚsO7tžÝ½-~þùçÝóÏ¿à^|éE÷ß'ûºßÿîwÞw¿ÿ× ËK+µ"qgÚ2÷&^ãM·õV[É §oß'µaÃÉ\¤•“ÉnÀ|aÊX'~&ÏP §¨ð0 ¬í„u˜oZ"™É´œŸL¿<£f…~ ý_~ùe(Æ/¡?(Pªÿ«/§úlV.‚8 ­4!VXIýöégÀenõ5V'#Yü#ÿKÁüÅlú±Ú}PU»5ÖXC4„ö_‹ýŸaàã1œk¬¾¼VâÔ…ð¿Ù ¦-˜ÿ«Y@ã­U5:1ƒÑbÅš\0ý?¶ÿ}Q•ë¯ò¶Fš¢£ÒÙïi—TûS» .ì/êŸm¿hÿÚ#jÿŠþ¯íÄÆ}‹ý`bmÈ<ÉxAîKJû«rõÜìÙ³Ý2–‘çÙéÓðÃöxÐX®vQo|Ïò-³ß‹ýbGódälݺU®ÿ}ó yTæK/½€G'½ïêX <þ·jÓJI§' ø5õkò¿Ý«–ÄûoÝzuÝœYsÜ2Ë í Ȉêù«~²ñ§nݺnûö2Ü>£*Ô“%õ¬1v7ÿË®‰îUýRǃFòz3 ?¦óþ¡=¯èâ3¸Aî©þýç™gž‘I!öƒë®»ÎíºÛn®I“ÆnàÀî€ýífN:é$w衇baK=0g®y³fn©¥–Â~¨Ëº«¯¾Òí¶+x°’òÝ·ßuWüó ÷Øc¹þXiôâ /º=öèBµ¿ÿcÅ„—FN÷°µÆiŸÿ<ñ„ÛzË­¤Á>øÀƒr!…úK ¡gSç¾2/õ{ɽÿþû²²hÔ¨Q²¼k»í·CeíïêÖÑ òÝBd\{͵ø¥ã{wôÑv+ciëøñã1Ów'~•×Íœ1Ëm¸Ñ†nï½övëw\_”òÞ™×O#´«‘÷Nð²lü¼Êf IO£Gv=ü ûàƒAnü„ñh(ÍÝ]ö@#ØÝÕ«ß@äà‚ 2¯½æ:ùæè£Žv+ÿae7¿èÜŽYÈX10sÖL·!fî÷Ú{üú³žÈW^»šsµ¼b³$ ªäsæÌr?ò(6¸}Å9RVh­»n×auñYßîn饖ÿ“áåW^qŸ~ò©øìÅ^0eò©‹$ÔX>ø`ײeKñõÏ›;ÏõëÏúùÀ 2؉úiÛÎm¿Ãvn¿ýö‡Þº^žÚϧ±k¯Cý|ÿƒ;úÏð–7Ž£{ÝáÞ{>˜1Ãm¸áFnï½÷rÜoê”í“O>q?üðƒ{ñÅCÙ.¹ôÌç¡[{ûiÇÁ‡ lø¶SBÒþ:è ×§Ï‚>ë̳Üe—_&Ôñ ôqÛŒM|ªq­ìÿÌU»'1#üô3O;N¾Ð¦öíÛéª8mYÂ{Î9gÃî²bŽíú´3NscÐ~~ôQÇ%ÚKÿn)·ñF¹Ã8Ò5^iEhS}ï¼ýŽ{þ…ç¥'Ÿ|²ûfžµ”ÈðØãÂGŸºV\ÁwìqZÚÄ~":è`ø äu|€å’ó³ÿàR,—´êçI{úõïçÞ¨ýtô¨‘®m»ön{ôÓýöÛ/úBŸ¥Üóªçºîïãú£ŽG¡vÜ £Û´´!zÛ´V¹¹sçŠl³Ô±þzà9`?÷ûß/­ ÆTb¿f¢Ô‰þÚì¿öÚkÐæf` Ñ6ªcÚ(ûéÌ™nƒ 7tûî½76¬Ç]VW£Ç`Úõ×¼y3Œ{à3Ù=ðÍ1ƪôg,¯”žeˆ25íÜ;+_ ´üí@~ŠPÚÇÌ}òñ'ò)èqÇ 6 4y®A¿¹Mé•Öl2ý¾€’µ”Ÿè— ô?ú¿’ý…~s®ÕRáÿ¢ý¡×%ãŽ!Š+ú?ûËocü[i¥?5±°:ž?>:LÚ†'ãÿ·˜ä¡‡V¾|ÛvÚÖ³êÈyL"ã*§CÎ5Æçc:uödÆ“Òî·áÿp¯´!z ºÿ­°ÂŠnâ䉾ò2<»®.«—Í”X«£µ‹´É³Š‘,Aö[KU;-•Ø`ÆYïøyì_õ“öíöíÛkýð¶ÿ¯Æ¸P§a8º¶ç_ mú¶?¨7·ŸÆÿùË_0ÉÓÐm²É¦®mÛÕ Vë{¬Ö:ãŒ3ÜqÇ'“rœ(ÚŸ–Ñ?< ç¼÷ñ=T¶_ñ…Ýt³Mdu'†X¯¼ò²Ì Ôôü_Ú²~5þÇ¡dr! gžy}“uìØQâfÍšeÕÕÕÙ„ ®Ê0𭹿𒇉¢À{OïÞY»öíO~éú/|¸1e˜ UúD?iQgÙ+¯¾–aY˜Ð Žxÿ‡%aA~üa‡pÆôc1ûø£sÞxÃ*›kõX>)›ÂxÉͦLý2' Ó ›ZÊvgŽ¾Ò€õi!-€¦&NœèË­e¡M/ã&Mšd½{ß$qÄá¹|Œâ뼿Õÿ/õïøî¾»w†—øºÜ~ú¿Sçí2üúè °„VœÊ¼Ò†§¼;}ýqä!?_³ ±×ß¿ÿK9]Làóð­ÖÿŒ3JèÔo‰7sùµùÖì9Ùn»ï.eLëŸöG½ªŸB1 ì9å”SšØ6›5ož}øáP†n¸Aå£î¦|9%àéL–üàC‘üöíÛÆ¼:tXÐi>Îû ÜþaÆ–ô7—ÍœñClo”ïõ÷¾ý´m»\ûOíÓ4aÂølÛm·­hÿ2Ë.+ºqB—XaþŸžm:‘§Ä·¨üR*ú›4išX^šþ|Ža-ÎBûõµW3œ&:KÛß½îˆþëM7±ž|[+©Ž}S§Nô¹TgûèèСC.‡ ³ÿ¦!_ä:ÈÂØ¡Ä9ä ÑýŠÊd"ÐBbÃZœË ú©-GDH RyIª2…a-Nšý…þÂÿ¹‚DH Rù”¤*SÖâ„¡hEÿ[Èç¯|ëɧjhaž¨†ÜEЯÏÍUÙ+@ªá‚ .ûÅFm¸ÀúÏÿy\¶ÑFKç ò©œÎ4Q™Â°§ÅøÿsßÿÚµo/Ï?òmççÖŸ¯ý¢þKýÏç;>gî¿ÿ |ÿS—í_2¨¯­ßYœ¯Rý!äC€ $¥@e ÃZœçZôþùçá}(Ì ¸¡Ä„”0bä(+YW×^{­àòf§a-6¼Æ‹ƒý¾$Ñ^"PÜPbBJÉq­‡¼­%sŒt pòÇ÷8„5V_]¾c;n¬¬úàž¾“¹ýöÝÏõ}êi¥§6{ÄÄ ýl¨,q䪎X0« V— ÄfP/¹°úäœsÏÞRýO<ö¨»êê«E7^¸ÜöÛm'%ymÀkn›m¶}îw;ñobb[¬é†M½ê`Éß#?ìž}öY¬èŽ8ìPØÐW4F°í±ÇÇÒ3-Ûº([çí¶‡þÌ xõ5·õÖZ6óCØ£ MÓQÅAþØìçŠúy=.ìá¶Újk÷õ×_»?úÈÝú¯9ú)¬²ÀN+¬°’è KÞ"~:ùäSòú ¿ÙW16™)ý|èg¡~Ö[}¬™çzõê%ËïúãÛÊëP?çòøp¤¼¼ Ž9›J0¹Ncê¼}gy­~õÕnkÔíï²{·Âò+÷K/¼ä $ô'Ÿr²à¼(ÿðÇf˼$Ô¿˜P_5Â2Áº\˜ ”Â"iK0¿[\›ÿ¯Àꣾ8^á¸ãOp‡`EWøà¡Ë}÷Ýt×Ë?éG ¢Éúšk®ßyÄ®e«ÖîégŸv/<÷¼ì„ÿgÌ^¿ñú›â[®M rŸAŽÈcAlé¯$¼ý´Ê6?ójÝ2ËÂh«1ˆ”œý\-#RU´”³núªÏ½þ§ÙOÑOèÛƒÑO×G;àJ2n:ÿf×_’vpÚÁ¹¿k×c]ÿþýEÛÿÁ‡ê¾Ç*¿^·Ý.ßE3£Úë6ÿw=æ8÷2–e²´ëtXÇrÈ!nV’Ýv;yÆŠ,iX„û½—$_-õ¤!*·ß«v=ªm”¤\mÚ(Çm· öôy ûë_O‰ÜHš7rµÜC?„ñà9é‡c“ý¾O=´F€úñç gíÎb³ßeX¶‚)væŽ,kýû’ÿösœ)׿`ãØUè÷!nñU«>Á.Êø䱾РB:¨ @¢©D½ç2~«w‹cû+êAî¿âÝàöþ‡BûÌ5AÅíOû¯õ;‹©ÿÉím1¤þç3!ó¹Ú÷ljûùðÏ]½:uÝšk®Uõ±ú}Uÿüá¹ðü÷ÍW_KΛ7W6¹þÅòÜ»ÖZkÉ*ÚU›7We¡Ù@ð©þÒRõÿ#Öp{Üÿh;ä*}þâOÃÅø[-;BhϬý Ep{Üÿ`©íùK~z¤¶…Ôϲ†ò&fÖÊoãŽÅ‹4þ³ð£Ùоö“oQí×}Á §¬ujÑÿö[o»N8^Èø àžØ‡ˆaQõ ó’Øÿ‘tîH¯gpÅæ]vï²GvJ·S`žË.¾è¢lÏ=÷üÓO?ÅÖ#>W ><»ù曳ïøRãlÔXñÁäáê£Ò€ä¡*²ûîï#$i™ð3X`kݦµÐãt4ÁM•B˜•e9¹RhÐàAÀGzlÀôoºé¦XI‘_‚Ïœ‚þAƒ=”À2S®Ú˲Ýòk¢~p‰˜<'gü)?þ¤ù¤Ÿ5kföÊË/ƒ¹2÷égœÊ——éÂå _??|Ïú‰aÆÌ™& ¤ Íš5*A^_ƒýl÷÷YTg§Ÿ~ºçC÷„0•ÄçÓ5vZ;c[¸þúë”®ûsBr‰¨M \s…ëß æ¨/ºøâ`ã÷ßÿ y¤‡¶eõÏU$ÃG |óæÍÃꟃCþˬ'„oâÊ4m3aÕ „Q&£$ŸmÖ‚æéuÞ<ø`¯=A£ýâ†ë¯³qG>fY°©šêE¿L8q1 ¡Ÿ&í€y˜BÿYVô6kŽU‚¢¢:{îÙgU.ʳÛî»I›4íßNû6Ûr‹Í%ßV 1ïÙgŸñ<.÷¿ès3ƒþiß~›m±ù’ÏÕp ¦Kþ’ÃÍÇþ\?e½ÿ¾ Jäà¢ò²l*ÇßÞ9°ý§º.¾äRçèE¯rJô‡Cë–¯ò Ù7&í`Jùê#|æ)>`ÿOõ[Ás¸ýFSslÖ–×%žœ.OÃú+¹­\ásŽx—èˉ±æå)r¸¢ýEg-}).S.@óò9\áÿr§ÕЉ¾—ˆ)gl+« ð\’¬PzÏÿzèaz_ÂýÍž/øì¯iý3‰ÑS~¥Ö« ÉáŠú7·,`}).S.Bóò9\-þoß®­Ô±­RéQ–@¸DÌ«¿\1QÛo]¿õíýØ¿²«‚·¢Ïlaà©¥þ+ ²~kþŸ;o.¾nØFŸ­1®ò’Í1zD=Ö_8ñù'4†1²C‡u³wß(Jï¹~#þ÷;¿âÖ ÷½eð¾ÃWÒY3g¸ƒþtÜ…xè!÷Ä»e±Ê S§í„Zè1[m¡e«Vîøãw<6ŽysçÎÁ žaXiЫw8kW%«_fÍže,Û†{LpÏAþ PR&¦’©oìÇ+'LtÛtê$ßpr>#ÁJȧE¬oçÞzëMY BÜ5Ø‹¤!6ŸJÃQXbá]¬Ñ`úM ÊöËv ü2O‘®*öbàž$§v;UVg̓ïHß AC·ÕÖ[çí÷:%⯩„’KëVZ?Kû=a¸ÿ‹ÔVˆpuÃØ±cܬY¬³_ŸB$¸G~Äýé€%)Ò}Y4?½Fýb’RŽ”$ÀQ÷!zôÑGܰÏ>s_bóêOG?åñŽ«ã{hÚ>nÌX7[ÚAvéIíþºk¯ÅÞ; %MéË6ZÖ­¹öÚ!í ±ê¨¿² ­!¾6ûñé™[kí¸šÏ|-òöäp´ õo¬<ôN+8ˆ\R†RûßÀx`6rØR ø²,Gy„NÉ!ñ޻Jú¥¼~,¢Ü…Z}‰æ~-ÊÑ!š”‘W3@ÀQR:þy‰}‰PáÖL‘ƒ‹Ê+¥c:Ñä%8Rúƒãäçñš]BVÐÛ…ÿé¢ý©ŠþÇÖP)$cÍozüoj±Í5y Vß®ÝÁ]rÉ%îÿaµî™§ŸéaE1ÃQØ_W¬½.Ãj¢5ä*ßK.¾«uosgœu¦¬(&!÷äûç=ý½ÒkÑï‡ç! ô–k1Kª™Òîq‘زsqQÿê\ƒ?ŸÐ“µÜ-/°Šo)±ð?]!¾ÅEbñMé%ñupb‚¹ù˜œ }ÿ÷º«¸‚¼bHtyýúN ¼YøŸôSç/iÿϬÿÊ+®ÄœÃ+âéž={º:þá»´þ‡îÞ|óM7%Ùë_ ´nÕ¼I0UKÿCv…@mK^ÿ+ù6G_~Åq¸Ðþaå–¨nä°BÇ ÁõÌç$nP ª’¶ÎãÓï¹çwÿý÷Ë‘qꚤS1ý›i®!v·`4{ï³·Ûgß}D·.Ò4Š„¹VGc±qì6¶‘ϰHÙªU+·ºŸàPNµël„­!ÆR{:žª-ýÍêÎ €4?Ûb2§xn~½Ï>û ‡úóþòÂ+DF§I PL Ïÿèî^{ý 7º¯¾æjùãNëÛo¿ƒÛk¯=±n·üòË{ù%úéÊ«ÊõóøÒûzßãîçu+I°”×ûãÛiÓ\“Ʊ~¤âA·6ïe1ÈSC>Lt_`6F+'ÒÇO±VÃç~iˆjJì)¹‡MnTÈ ûc¢ƒ;ÚŸuöY4èÀ*÷Äþ#ºiX+´wMøˆ:€WUyýí×ÀD Å#p ‰tHh¯Æ È&%y8±D¼mÛ¶A\ EnMí“C)uªÜ ]ÓHNÁñ˜ÖOßÁÑŽ¨„¤>š†“R7n(›”‡A®M›6½\,¯ç‘ eååI2Éæ½G¨fû™»7Ú(Ç &W%GhÐ@~‹²dHŽ2®FÿH`GŽÆÒxýB€KÉø'lþRe™*L°ez¡Ü!Ið¤Ná¨Ñ£!Ôn¿ÒØÕäé*;&=¡Ñ!Yè/ü/Í hÖ‹Goý¯¦ûOê/…m\‰Ž@1éYŒN­ÙÑã$Š©_¯ÿÅHZ“ýgŸ}¶;èàƒ\‹U[À!áéÊø÷“°mÃú8š~²»ø’‹]·SùY4ñ«ðŒs¶;8áÙ¸œ„Üx˜ËäI“ÝE]âNÆ’õÉS‹~ÖCqÿYüÚ_¥ç/­+ëWÚ¢´öPÅ1©d¨ô€*ê¿Æþ§ÎZøúÈp¼ø98Û»¿‚ÿ¡™øc¤PŒ¿ùöê#Qß¾}彎‰c»vu{îµ èðÔÿ‡bënYÃ9‹~Ø&_¸ÓN?ÍuÇv.ïcKÕV[Mdòâ«Adýšý&†ÌeÑðèÉCc>òð#ÝÝ/@û®ÂŠ™ƒ½sàn¾MÙK.°Ï=÷¬Ûiç‘Ai™ë€•0ÛuÞ6-±gÇ{î®»î’>’›}¥jÌ\ìÛb)ò³s$M€(ªlFýû¿ ‘ Ur Úÿ¸À]~Ù劬Ãñ“ë”É“=Ö,·6lh+T¿ž“cA€x[ªDY†eiÍ>ÆbØ äà¯:ƒ°ÓÃX•Õç`WYÑÀ=}þK<Ò´öáÞNÁ~H’üÏtíúyBÒŽ;î:¥Å²9ì•Ô3¤­µ~î¼3‘øß‹m%õ£¼ b?ÙL—•­’ý”È? 'ú-Wʱàú)Oe)S'œë÷R?œþöªÛt“Íäd4î1tß}÷ -uûìçdõ¥Þ~iïB•Ô¿)š°XÑ›à-@¤~ŠúihÿÉ„€r(»ç¥o/¢y?‚ýÔÿÜó/¸vÚQÊL¡lÛI;h%{ ݉vÀ ¦enÒœž‚B­² ÷#P[Òú×ò*^qá© xêŸñ¨7 ÕÚ¿·Ÿ<*ƒÁ [ÿª_ú©ŠIdYÙÔŸe @6ôONÆ£JØ14ˆÉÄÿ˜ƒC‡:ÉÓA —“˼¿ª=a®ýÓA ð‡‡4)×…µŸLyýĨ\^5/§?`ŽúªþÚ?|m5VÔ¿4=¹¨/ŠöW´¿Ÿæþ·¸ö?yaŸÏø«§¸rÔˆaœ¾{"Že>ÿüóÜtì“8vì8ײe !`_jÑ¢%®Êcã?Oä9ñoÆó‡=ÿZ¶$]1þ/iã–—×|»ˆuiy¾?áyÃ9˜Šƒ‹ñ÷ÇÅÏp²õ¿ñ¿Ò$ujBPÏù'ÆRi–¶ºdíjM/ŒþÓþŸCß¾Oâo1_å:wÞÖ]ƒýrµ]W¶Ÿï½|§eî1Çã.êÑÃmŽ/GøNp$¾x'O‹¿¼u¡Êúµú¯5ÚàÔqðLhN9àOH>Nñoî2\ü,è×_ƒ—MN 9·é¦›È'Jœì¸úª«ðYˉ8Ò|H‚*—TêW8½²r5/”( ©~nõ&x,8CÏË{b‚ê9¥0Ú¢eK¤Yæ*÷6ß›9kްö8r^à³öÁŒá©ž×ôkRìMô{"D@-€ &$ÏÒ:©@:/ÄgXŠG‹ãõúaƒnœRæ|ðAù¥‡¼Ùï¿ÿþnü„ñL‚Ó¸°`¡n„¹±éB&|y¾úê[L ¡~x47éÆþ)îl&}"6åÞhÕâ8#Êrªd/Ÿrè W4ø˜puñI” ïûïg uâ ƒ¤À§Àðá#ÜÔU¤`jmúE·—kþ犱ÖmZIYÞyçmw Ú '…Å2Ân¼^àÊö«°T¿~v¦,«¯¾†™+>èéD$Ð6 @'Ú |P“ýÔ?+†1Ü—‘'N¸€ÌûŸôlãP€û¹åN;sRÈ÷ÓχI;¸úª«å“µ6ÞPYq•êƒÒ6«a•ä û|(ìІ@T¢ßôqe͆OÛX„œýÂ,X3)”3%í<óµ467g:¨!ÕߢŪŠDɾŽñ€cïÿ:6pL˜)ŸÒž~ÚéžQ¢?Œ5ØO&™;¢õhÿS&éåX«ýŠD¡}9#°(öCvCÉHøtj¿êÔ +Ñ«y"g¿ÊPQe¡?úQ}céÂÿÖŽ,.Ú[ˆyÃÚIÑÿl4ùí?byx6X8û—[n96'i>ÕÕóf£òî¬4þ,·Üÿºêyæw‹N¿ *Æó·ú£vÿ“æGéÿAHáÿ_ÄÿZÙZ›Ö}çëß¿Oi´ÊRBKÅr›æ¬íù?Êa¼~Béû³'ON=õT´~îv|sÿ|FÆÏÀî¾ûn·´ìE]f¿/˜9©þÛqâžfá{þµô{þÆòž¶8ö Údúq$¢{ååW¨À‡rû™qÔQGºÖ˜iƒzêÞ½»§Õ(ÕÏ ·¢yÔQ2‰Ã¾×~«dÿo¾mU®ýÔ>óòB¾üò+(1‹TŸ|ž÷åyñÅsös‚dúZð——ÓŸÒ2žôö /û‰ãX2{ƒi¨lç©Ò¨²ýy9¤¯d?©6ß|ó î.¬Žâ*Á†¾ÿsO/Ic¥P}àyrai ~}ç*Å!þB}Þ~‚+6n¢IØûÖ[ˆRÄ#Ù^±v&Ïç­Ê“3†Êö—ë¯É~•dõZ ýâ–èsëÿÑïþ7O0ŽãøÎšrŠögà?ýbãl_æ(ñɘ_´¿è ¶³~Õý/¯l¨êÆþ›8 9wÿ•W–3p ‚V­Z ÅËfüOL›6ý»l¿}÷ å6ôóD˜!Ÿ‡…ü}÷Ý/ûî»ï@S}ÿÝ÷Ù{ï½|­õ£¶œtÒI8Yj–ØÔ옮Lj L~IÌS›Ô,9Sð§v;MðzQý+aI^–=ÿ IÙöõe˲ïqË6y²é‰† ƹíÀG˜mI("t!±þç©xlkg¢Ïœ9Û?\òÄ;kŸË4Z§Òݾ™ø›‘Ýxà !R(V")w#ždöÌÓOgX©“=ùä“Oá2ymÛê©d¥ýAóÅ ö§¤l¥öcqðhÿ3ù3QÇ¥!mØØí`6ÄVgÒOÕ~j:yšõ|úɧ¾þã²–‡>Wžêl̘1e<¥ö[™ÕÜäZ‹ýZ†ªìÔS»I™ó¾2‰1ŽãAUváEÊx ¹ÐU^ºg½9xA¢:Ñùå—»/¾èbiÓØ“)ÃFû6Ý6ìÝhØpêЪ²¶8U$„D‡‚É5É+­ÿÀ/s´ù\KÅî“—Šk’Wè‡3¢Í¥>f^ ªB2²'Äe "äšäþ‡3¢K¼Ë¼T…ddOˆË@EÈ5É+ügD–x—y%¨ ÉÈž—Šk’·¤û4î£FÎF“=*ã3-Ÿ¿vØ~{¹¯ŽnhæÌÖ{øyçŸ{IU†Oÿ3ž~: §ÒÿãÇÏŽ>êhŸ©ÊN8áxŽÂÿó/¸@ðñ¯<Ó¤„çèÈsü ÇÇÚI|¬`rMò–tÿÓà%µý±í°}Œ…?Àø$Fêy‡vT¼äÎfÏÁsH-aIµ_MBcŒ”XɼT…ddOˆË@EÈ5É«­ýkݰŽôÏÞW·ßa‡€cÞ¬YZ?|þ5ZÖ)Ÿ«ñuŽÔéŽ;l/y Fƒ:Õw–Úôk^ƒKP?•ý?—þj¼ßó½ÝÞ?x¢Ž©Ï† œ „¿!C4 Omf8ãŒ3„~£6Êžyæ¹ì«¯¾Æ©eópúð ¬{÷Hß¶ÚjKß„¾þ.ûŠö/C%íD“ðÙ<®ÎI'†,“nš7CœDÀŸW̲Æxéµ—Jò7iÒX+ tMǼɘˆIƒñœz*&rT)B_!±‘è>ìp=®^eiÞ-7߬:¡ÿòË. j¦};-Û£Ëî!åãqáMšÆrñ¨îY8V; Öè°Âhÿ ›)û<¦C~<.I{¾>}ú$>³éær¬ºéfÌ£­+ég£¶#ØIG_6[E'?˜¾úê«E ojõ<ód’uC°Pý6a£EL&†ðÒ-”›ËF&Ÿ?Dz³J[)›Ÿ˜I˦zô:øƒA(›–ƒ4øTP&±4÷÷ÿG‘kØ“&kvÀ¶°í¶²#?<»í¶Û³9þ¦:>™byø'íÕ·³íýï™ÙY5Ž›çD€Ñð$Ku–ønµÀ zœ¶ç}P³ýƒq¬z(xxû¬YI[öÇI­trŠeã1óª¯ ý”}Vý? $V™ûÛßB™*ÙOû¿_ÿûÛ_•‡¾B¾ÙŸÚÆþ— ž7âRDÍö›ìÓr¸ ¯€1=mÚ´ ›ºKÛ4ÞvíÚçì§ß9CÔ?jÌhm×%¶QÖ¯¿!,œ<“vPBcöK;@Û_±ˆ•"¢þ@ŸÇt` €Ç%éÀW”‘¤ˆB¿x#uIp_áÿà–r h57œÐ‚¾3tŠ(ú_Ín\²úŸÝxÿµûÝwcì2|æ. á2<ËЃo™e—É=[­Ó¡Cö~t³ñÿ²K/-»ÿò¹Àä0Æ> ÊS©¹•5È¢ý-íÏê_ž+Kž-b[BÛ¹WÛN¬Æt,± /‰ËHRDQÿ Rÿiÿ¸¤Ž,ÿ¾ûî ÎWœ½÷X¬ÏÍFÏø^ò¤U$,Yã_(v)Pf[ŠÈ·¿oº17þÑ?iû~ƒÿá½’ŽÃ×L¹ñ¯ÒøËy€O>ùÄ—¬fýù¢ÿ:üOÉ,ÐOð¨%áÙ*ì[à êq)I¹½{ëâÓ+‚õñiÖëÈæMÆ1eò×¶};÷¯[ÿå~ð¾×QåêÕ¯ŸHÌÜïq|5CýzølÌ¡,˜4e‘/ÈkàåŒ-„èc±¹0&€„˜§8˜,éùÈ£¹óÎ;_7’5öžÂ©Á¸N3ê‡ÏdàXmã!À¥¹,'6@*uÈEˆ”’WÓÏl}¯ È¢ÏM „?aÿ¦É8–½;6¾ÂÄŒ7§ÀéÑçx‰w=ÿy…»óŽ;„'ˆñúyªÖ3Ï>ëv“M·Ôw<ÝŒaY”«F¦]¯á»É-¶ØÒC7Þm“¯ný÷¿Ý@œÜ&ãÔ­ï?ŸU br½ºõÊ~~–÷̳ϸ.üöâ¸1"…ð3@¬ZòXÍ#ÍZÖv;ïº3HÄR÷œØÖŸûY(µŸxÊ]Pÿcò1ØOÞø¬ì3ì·Ä¶ðrÿ~î®Þw»¿óg·ï>ûúBA£þ—Í·Úz+²™R·Ù&›ºÏÀ».>gÔƒýãl"¾AÇŽ‘768ð}‡ZHµ89´¢×Zg-·ë.»*/®ÿèþàáâEØ"ïšk¯ãvÙeA3û‚'¶eküLjÀë´€Ž· I8ͤm»vîßÿºÕ½}Áˆ¤ÿëËgT¢Ñ]Ýu8îöbi `ýË4jäÎ=û×÷©§D/?Ë” ÷5箿þzœ’r‰lžîY¤ÞÏ9÷÷ÔSO iä+¯7Í‚Ÿ$WSìÿ=û)B]ŽY^?}¬‚L1ãÿ³÷€VGÿû¨*%Q¤¨(MÁ*ˆbŒ 5šX£bKŒû—Ä(Xã—Dc‹½Eý«AÅ®‰½€ ¨Ÿ "*((½Ùé¼ûÿÍÌΞÙsï}<®RÔ=ðÎÌÎÌÎìÎÎî9gïž=R’£~ýØ£º?_x¡kKËã?îC7 _k£ü‡ýò0è•S{¨šÍ6ÙÔ=/ ´Äëbt¨L&¹iÓ¤ÿÑ×õ>ø€£W]3™*·ëO%NÁ†wTÔj¿T…`@u ’•™øj_x"IçÚÆ?ÛˆN™&'ûÉÿ)þÔSÿóNàØÈÆŽ4þè ùGüBÑ¢Xþúã#‰E…ç¥)À_µÑ—é8ÿüóy/ÀÃ?ÜÑ=!)ž‡¯†ª©¿âþqØÐ¡nÝõÖãüœ÷¿#ñuPº—Á6\–yóæ{ÓU¿¶»aøåÑrR€œ‘Ôÿ¹üi­èÿTjüiûk›Ô£½¦=œ2ÛC$T.µÿ7õ¿Bmñ1¨¾-°Š <2pâ™þÏ-¥YÀ*‘ál’—ÎÙˆ!ù9Ÿhd´xü µ/š(¯R€3‘ «Â\<>1OrÑyMØÇ›-5Æ(9 (©â½ˆGјˆ9Ñ8JýÇŽ¿½OèíÞñ¶|Ýœ«´°8E'æñ‰Ñ5Q_ˆoÕ~Ïv¡~\QªŸ­?Þ‰Äk ìˆÀ “³SžŽô¼ùóÝ{ïá}†6l¶!t©‡1ú}‹öãºPÝŠíñÕîƒ>ÄgÙ[àóŸ›a²Š&Áüñ ë_û$£¥ÊÙÇeörZºlöÚĵÀ~CuéHAX ©eô¾ûúën܇:,!Å>E-^áq4™“¯?ÝP¼÷þ{ØÇgs×|C´O^÷*¨?— ÞK– l-ZºM7ký[üDSÎþ2”ÿ=ì§ÔªU+®?W/'¼§—L ‘6ŠîÑcÞu~À½\÷î;¹Ï?û ƒm5&Ðaß—OÜ?0‘1Áv}~OwôÕ:î¸ãwâ‰'º©S§ºO?ýÔmµÕVný¯üÏB9ûS§LÃ'Ï?Å—ç¶qM1ÁÆÂ^&ˆ„5 = ¯A¢½çZ­EmäE-}öxoÌ×j£¸ý³ò¨Øð\ì466GlØq G í×Hy¯+òÍ6ÛlããK3ª)%!½lùRäyÕªFžmCž Ÿ©ûª6À¢¼àDc ¥UÈÃp™¿øò ÷!úí%ãL^k΢zù,[ºq3Ÿ ž‰¼-]Û6m\ýfâ›3ˆƒÉ“$0)Z®ÿ³Ú5PÿØg(ô7ÿc]µóœ'ÙOþ—N¼¢þWÔ/CßA9­DcPbÌ‚@ŠЍû¦.dª9åé%ÓJô0ÅºÓø·Âø£/çÐ}JË­ðÕ±Íp/…{D:ÔÕ’òi!b¥>òLäû¯6m7ÃXuK·mòÿ ý¯î-íïÀü'Å?*!Tâý–â/ÅßjºþÎćž>™ø‰k¶a3·é¦›ºuxÃjäj²_òÚºãŸ'†¸šB˜á,Œwù>[J& x–éqÎ_¤Ä’ý„Ö}ê!…–§xÆË0å)dNÛ~@þ¿üòË­";äƒy×zöQ®þWá+zçœ}¶gabh&†ZoÌs ·Þv»;…Wûˆw3/f˜ú]!sŠØ†³åCÂH*+ÀŒ—aéæ± a-²OmóúoðÍ*—£&æ’xÍíú5õØ_ëN<é$¹±­ÁK¬‡O’WΆ°Õ¿T MI-›ñŒ—ay!æ± !Õ?¿þ&ÈÆŽFˆBËS<ãe˜ò2§ˆm)þRü¥øÓî ö…aŒ—a†Í(sŠØ†ú_ê©ÿå»M¸£4=¥¬LMwé©ÿ¡{9ÑÒø³V?Ùò;(˜öÒ™,ËÎz†² eÉl–Î$™¦ d+ Jt™¬dU&RÉ>‚D|ÇþHþÏb)Ã*‹¿Æ³z™¿®µ×^{¹:´ 6avÿÅ×Éþö·‹9½ß~ûq<Ê'Û%4õ¬šEnŠÓuÙzb©£ TSÿ¯Ù½òÊË™›¡„²òRO2ÀSÜ®»íª]ÔäÿoÃÿiüåP QFþHã/»#w’hÓnú_Òø#ת\G‘k”v8©¦ë_á=ï+iüM×í;QÇJ×ê(™kÒõ']ÂÐõŽ ”Z\²C¤†3Åefñ‚EÓ ‚‚×ÇQËd:‘ ²ËI'ûp=üjûÑ«De®ìK{!¾ÌyT“ é”üOñ7 ûõØ¥‡›B{0Á-ØØÚµoן.Ä«Nïßâ¼ÓŽ;ºgŸÁ­¿þñYõéncz• C{eá {œWK\-ç @“ é”ü_›þÿúk¯±¿18¡?ЄøÎê;E¾@IDATÆ;õê±wYçÎ=‹Mn–C“ é”ü_ÿ§ñGÂ'¿è3¥gÄÄAÚ¿¸_ÑÐh$¡I†tJý/õ¿tÿ—î1à"£Ã 28¤û_ø%]}D¤çŸtýM÷ax,BÌ*¨!øñ”&Ó©ö÷_21”Ógu²6;„—™ ΪK¡Q“LÊ F¼2Tu)4Zb’IÔˆW†ª.…FKL2)ƒñÊPÕ¥Ðh‰I&eP#^ªº-1ɤ jÄW>mš»é¦›ÝÀòûö<[ Ý´S·nݰñôÉîçûïïêòÆË‡O»¿`óç*¼Òôëã~Í2+m4ŸAë¢Ððc’IÔˆW†ª.…FKL2)ƒñÊPÕ¥Ðh‰I&eP#^ªº-1ɤ jÄ+CU—B£%&™”Axe¨êRh´Ä$“2¨¯ U] –˜dR5â•¡ªK¡Ñ“LÊ F¼2Tu)4Zb’IÔˆW†ª.…FKL2)ƒñÊPÕ¥Ðh‰I&eP#^ªº-1ɤ jÄ+CU—B£%&™”Axe¨êRh´Ä$“2¨¯ U] –˜dR5â•¡ªK¡Ñ“LÊ F¼2Tu)4Zb’IÔˆW†ª.…FKL2)ƒñÊPÕ¥Ðh‰I&eP#^ªº-1ɤ jÄ+CU—B£%&™”Axe¨êRh´Ä$“2¨¯ U] –˜dR5â•¡ªK¡Ñ“LÊ F¼2Tu)4Zb’IÔˆW†ª.…FKL2)ƒñÊPÕ¥Ðh‰I&eP#^ªº-1ɤ jÄ+CU—B£%&™”Axe¨êRh´Ä+†£„d ‹3Ý00­”QJüZlb%=£‰9L7Ì€$hË(%-xC¬¤'— 1‡é†Ѐm¥¤oˆ€•ôä$æ0Ý0 -£”´à °’ž\‚Ħf@´e”’¼!VÒ“K˜Ãtà h@‚¶Œ[  ”.Xà4lèêñø¬äª·ï-ì›ßÍÄ/%\ ¦f@²Âö7Ž÷h–7ðJ’}x€ýbœЀ$ÿÃMô+pæ‘ç8ŠNVÒ3J˜Ãtà h@‚ÝŒ’ì[_D®Ïœ“Ëe`ºa4 ÉÿðdŠÿ¸Ïeч™¤JpKX–é†Ѐ¤øKñ—úb ë1.}Ξ­¤§— 1‡é†Ѐ»%Ù·¾°ž¼·)ù`¿ç4 Å&†ªñ\Œå½~ýb¦.k.¥)$Žâ 3éš¤Óø›ø%óX½oØ%éúc"#]84à #ÙÈBDŸbÆß,f2/ MÒß÷ñ7L ÅÃÂÄÖÜÇMæ"Š£¼ë Wyþó £YhªŠ%ÐE/É•ìÇþ(êÈêcòûR}ÊÎcŠ´– JJï,•’ÙHþÏ|AîJñû#ÅŸö3ß™RÿÓQ%?ì Syðߤë9#]$ÒõW/)éþÃG„:$Ýÿ¦ûÏ,(8Òýgìtÿ™î?Íu½ª¦ûOö„ñŽúæÝb‹¡ìÎM|M.W3´1o•ÏÒC«x¤ˆ—#ä’œ‹i%–”ì'ÿ§ø“ÞhûE걊|“#ä’iüØ'%cIiüMãoÓøK¦x5§"^ŽKrN¦•`XRÒø“ÆŸ4þЀaÇ3ôdc‰%æ„sÉ,O †%¥ñ'??Äñ§N˜ ]%›¢®¨NaÜv<©ùC'’”(? AÀËd|d𫑘3X[FJö“ÿ5 l Sü©'ŒoÄ)©ÿ‘kÒø“Æ_é"ÑØ‘®?ìöI옌ÎXºþ¦ë¯Hºþª'dDÁ9ÝWd¾I÷ä”tÿÅ^}$‹rŽÄÓbeŠîeÓø«Jã¯z‚ƒ„Niü ®È|ó팿ü*ÍŠÊAê 7iFé$ï*‡äÃàG >DŽP)(ÎA¥É©[€K&ŒÊ žƒ4³D/U’®ºæjwÁùç¹zy‘£iâsPir~Kö×tý“}jcÓ®Œ‡–OíO½ Å¿„Hc(,$F.\èþtáŸÜyççþï7$^8†4¦ Ç(ÊC† qÛm·Û¼C{÷ò˯@VÜÎçäÿ²þgGáTéøŸÆ?ò ÆªâÛáœâ/Å…H‰ñ"†ŽÔÿ´…Î"Ž¡‹'³èT~ü÷ÂäI–£têÞ Á¥êcrŽx‡$èHñ§¾ ÎǤøKýCCÆ:k¤p¿ánQ@–¾Î!¤Œ\ê¾ HãÆFz•w D÷Ç}èÆϱÖeÛ.n³6›Eýîh7éÓÉŠÛí°½Ûd“M$,­(‡$ŠØÁ¨\|õ9ã%4©ÃæÜ„'ºßwœ»ûž3‘;D‘XE»BûVk¤3JX)Á‹Øß‚ýeË—»¹sçºõë»Æ‹!vÆê±Õ)J8·¼eûj®«OekÜ8 n¡xßBý#“QbÍ×?_‚¢â¥ú—VÔÿ­_#ŸF +%¸²gLŸî6Þx î¶Ûou§œ|j6’hPÉWî¼ßÏösÏ¿ð<³÷Ýw_÷üs‚—˯öƒz"”Ëå/UŽHg”(–.b'ûÉÿ)þ¨VÔÿ©‡E}*J¤þ—÷@‘{Òø³ZÇŸøAdÊ”)®]›6®~ƒ¼©ÿ#L¿»ý_Û´-Ú´A}´)áC’zNýO<ܳ–Ž? I?mÛ®­«_¯¾6_Iµi”(/b¯¥õ/¿Å5J×ߨM£D±·ŠØ%Ú¿ q‘nÂC#¡øë×ï.wÐA¹ƒ<È]vÙelA•Râ¸_þî@ü½þÚkÙ~P>?g 2ÙÍåȆXóTNÅÚ—æöc ª™F:ªY_4¡$ê‚Ü*±/Uã3™[“öG îšm°t7– ÂZTÿáo w4ÛÀmBe#?}[í]OkÒÿkºý“ý•ï<ÖørNöá_©ñÇÇß×ÿ…5]ºtYëúUñû>þ¥ø_ùøQÁ× >ñØ,S g]e„ÍŽu• ™äÿäÿtýõÝ‚;Cè"Ü…V4þΜ5Û 0Àýä'=\ËV-]Ÿ¾gAAÍýoæÌ™®ÿn—]z¸V-[º¾gõ£%ì/Y¶„ïáwÙeרÑz®S§Ž®aÆ®ûN;¹Á/F¾ÔÿÉy•\ÿ¿ÍñoÖÌnÀ½hSŠƒ–-\Ÿ>8âa˜IK—.u—^z·£õЦ¥MwÚy'¬\¦6õò®(þRû¯žø?ÿüóÝ~ÔMÝÅýUš•M‹ýÅ‹ºË.¿ÌuÛ±«[mÚmJ}?Û÷gîÿø‡[°h‘¶*C ‹tý%ï}ן .8Ï5EÛ4E]„öÉw<ñwÖßWÿד‡#¦ZFËÌànJcQÑ}÷ p7Ý|£«[·{e„ nôèÑ2ž ‡5Ÿ2§)FKþ÷IØ“L c2ë¹”}HYrl"g8fªy~åmÕØ÷õFùùXƒöçÌžÍE˜7o^¨·"«Îÿµ«ÿ¬9(šj.•“üžú¦í_;ûkºþÉ>µ“ïkQûëô4E$/'¥²åÇ?âr¾‹SúÖ[os;ï´³ûÑìzõêJ|¤ö_;Û_JÅ­+mlš›PZÛ’Kµ?é1âqã#•Ú?µÿÚ8þ¥ø_ó÷³gÏrݺvã<4pÐHBãÍÔ©Ó|ªøú3÷z]»v•<>©S§â\|Lž:ÅsôÑnذa~¤’Ldçí·Þr={ötwõëçN8¾7 @<=Oã_æëò’¤=5Ïôn¬Íø? mÚm‡®hÃ)r‹ ÍôT6uÚt¹6åLЊ¯£>Ê Ãò$@lj':ÞB›î…6í‡6íÝ»wºþÀ9kÃøûÄO¸+¯¸í$­µ`á"nc*µÝcßw¿üå¡xSgRRâ‚þ~ñÅÜ‹/áïÅÝSO=Å“FÔÖrö "GGmâû8Ÿ¼.Ö ¸%[.‰HÚSóL_ŠïŠýÿ<Žö¹üJߪÜB´¶‹ù*~ŸêÏk¾¢ø™ÜW[ß·¤úÙĹóæ»×†½Ÿ§žzZ¤(û%9gjýœ5™‹¼[³}Ò¢e ãÞœ('E1ÁÓ3°jígvÊa«ËþœÏ>/Y„Õe¿¤qÉþçZ6mû•hÿ?øæXQe¤oe޵¡þRÝÊãÿÃ÷ßwïã*_Ûú¿QžïCýË÷ÿGBõ÷Îä°Ì‹”®Èþúë¯_xûºO<Ñ5mÚ4ÊŸ©­¼ýWd?2˜K$ûz¹Iþçp×18ĉŒNrSˆ’â=·Rã¯:0õ¿Ôÿjºþ×­S×-]²Ä5iÜÄ5iÒ¤äõ=ßÿêÔ©ã–,YÊò”¾ÿCzžîÜsÏ>Ç“B›6qwÞu—›‰U)ÕÕÕ˜zÛµoßžoÇÿø‡3ܲêeQ^Mäí+½60Åíâ¿nÚtélµÐÄ5FP‹²ïð£|)ÿ?ËmúÇÍ]hÓ3g¹êÚôí·];ìuHÇgœá–.[æ·ÒõGñ5týûøãܯsœïß\3Ÿ]7m³)Vñ­Ãíûí·¹É˜è]†±ÞÆ9ô—¿¤õX ö2Oq×âÄ1Är©ýkjÿ }ìŽ íCóm¬Tÿc—Öâô]õ½PðüE…¢ÐNTb?ñŸ'Ün»ïFI7ðÝð™“8eŽ$t9ö¼yyðËnÄÈáXY4{}â¶À²¸}öÙÇÑëpW¯.Þ™„œÌ&"; \wýunÁ× Üoû[^;mú4Ìzßí†ã5©ÅXB×mÇ1£úK·Ã;XkZ€ Bï´iÓÜÝwÿz«yš9ϪXp“&Mv=ø {gÔ(7²›nº©;øƒÝÁxm®öÂá^ÈpÂ,Úõ×]ç¾^àËÖªfò§º»}Ù¡l;RÙEÙºî¹Bg©‚Ö>¥óõ§ ÐMÂÃ<↼úªû>«‡wKicÛíðŠÊèÖ]w]®'y›d>ÀÃ?•륗^b:.ýÇ¥§²W¯r¿:öXצm[O/ }ªÝàAƒÑ>ï¸wG¿ë>…­ŽÐ>{ïãzqV†Õå"òÉW‚Û¶NDû´€Èoô«µû [Ö>¯rÙ>p_/œ² æºSH]z)•JOõÇ`ÝT¶¶¡lZÄ‚;æØc°±øL¸K0/ÅëŒÈŇ×àS9@Lø¢#UìÿBuÁý÷©'ݳÏ<ë>üð×j£VXŽÛ >Ǫ8*,«rúÓŸÜBÔûúnàªsÎ>Û}2i’{ìÑGÝСC¹MvÂ’ìãs¼Û°ù†R=X| 7^/¼øÛ>ëÌ3ݺX*‡”žòÅN³ š¹ÓN;ÕóTDìs̱¼¹:å<øÀ¾Ò©¾ˆ3:ü:¿qÿ¬rçcsvÉS\?òÓOþ×½õö[ ƒÏ»üd·ß~û#ü½%µùÊ«¯¸_xѽóÎ;|ƒºë®»º½÷ÞÛmµÕV¡þT®E Åo܈ž}Î9n–??ûܳnb&ý¶Üzk¼’úk×½{w®É]uõÕÜvì¾£ûöï ¿bû>ö(bÿ·a³fî”Só¾Ë<¢E/ÕþZPº©nа›0q‚{ðùBLËx»ïÔ¿¸à6À+š|Àþ"üŠpÆ©eØ7ËzT ïí1¤u!šµ_Íc"úÜ;#0&¢Ï}ò©Û}n_Ñç8îXN>þèàŒC±ÁõÄOh<¨ç¶Çx°m—ÎüНŽša-ã_ÛŸÛTV™ø•0þ—VáLöƒClûg.öް׿‰ä?ØPÐyÏ¥ø ãoê><¢Nãß¡ñg\¿fà•0=:uêäÆÓŠêÿzŸ”‹ÿfœgFˆÊ3Žó”®ÿI'èÖ]§¡ÛiçÝ[lôvëÖÍsî¹îôÓNwóæÏs#FŒp;ußI”¤ñgµŽ?Í6l† ;xV‡ý]9”síO t"Út´i´éæÜ¦ÒlÔ¦ç}®;íôÓÜ<ü˜ÿΈán'¬f¦Ê¤ëø(ô+8wu\-ZìŽ:ò(7î<^W{GùH‰Ÿü£b¡qhoXº7¦Õëo´Qˆ¿xýón¨.¼”M틼Ï)ŒÿCÙ2WÆ_Tÿ… HTNa¨™"ÂP¶BåR /Yº´pÀМ@IR¬ª)ßÔiÓXŽêÖ™g•ÌÓºuëÂ»ï¾ i±xãM7ѸDzsLLyváØceûÔWøÈtüøqEv01¡ê3(¹ùŒÄPnò9ÙÇÆ„F¨±3âá…=zÙÑúïÿóŸ¦Ï˜áóWæÍWè}|ïòRχ~8 3˜< ²¯"n°ÒqûS9ÿûßÿ†rí¹çž,Ó¡Cû6U÷¶ HÁÉøu•õž{¯¨Èi5*WëÉãÐþbSê ±Jí¿ Úô½0~T&Ošê#r6+ÜrË-¡œj—`ÿþý1&ù>W¢þÜçà[{à—]3ˆŠ?µÛ¢y Ökó.–­ýHFL4 ƒª|Lò)%*Tá…¡l…9¶O®AU6&ù”ªp€ÂP¶ÂÛ' × *“|J‰ U8@a([aŽí“†kP•I>¥D…* 0”­0ÇöIÃ5¨ÊÆ$ŸR¢BPÊV˜cû¤áTec’O)Q¡ ( e+̱}Òp ª²1ɧ”¨P…†²æØ>i¸UÙ˜äSJT¨Â CÙ slŸ4\ƒªlLò)%*Tá…¡l…9¶O®AU6&ù”ªp€ÂP¶ÂÛ' × *“|J‰ U8@a([aŽí“† Tïÿzyó iŸR¢‡z¯}ä‘GF:sbž—©!ÂGד~wõ#Rt¨!ú”F9(! e+ bÁ$ ª²1ɧ”¨P…†²æØ>i¸UÙ˜äSJT¨Â CÙ slŸ4\ƒÒ½Ýô mJâ^@åÅ‚|lÚ š+“Šò™„AU8&ù”ªp€ÂP¶ÂÛ' × *“|J‰ U8@a([aŽí“†kP•I>¥D…* 0”}Úé§û>VU˜8qb¡ûŽ;ñóÜ»ª2TU) ? ²ºÿ»îÚë„­LPÊV˜cû¤áTec’O)Q¡ ( e+̱}Òp ª²1ɧ”¨P…†²æØ>Y]8=´“öéÞ¾®*œwÞ¹¹&ñšT¡Â Xa([¡rc¥†kP•I>¥D…* 0”­0ÇöIÃ5(V !Ôð¤Á„Pø1ÁãHlµåVü>ó”)SÝpüÚ=räH0;+žyòÆíé©§ŸÅl÷8×KWiõÂøb­P¡%ô«Ä Á/ñ œ /¼æŠí?Ž™Ñk®¹†‹Ñ¹Ëvnß½{r±huÆî{ìîgyQP~N +%˜Ž/ í±gO|MMìöšÛfk¬bðÇÀûºßÿáwœ¢½ÎêÓ«—긇zØ=÷Âs˜aÁ_9{úiyU.ÖîÜ#X©p-ÊFG—íº¸}öÚÇUãYmʶǞ{0]ë¤yeîASág;I«,Uðü?/¿Aäo_ìvÇê¬Ï¾øÒ½ÿÞwëm·q4À¯2ËZpr¯6!ûƒ°bˆ÷|BÞ?žyVø‘•‡}1Ngž}ÊûðC^éqôÑÇâ}u´Ï²åî®»Ñ>ÃÑ>ƒIûüï…!4/Íq?úøcâTa»ÎÛ¹½Ð>TjŸ=öØU˜Ñ>ˆ7œ&û/½ô"¯ àÂ¤Š©?´êHÙ ÇJ<Ä$Ä"ˆä?Z^«û[iý³JjéªOÕã¥ü9VÑ»ºdôþçtw,Væ¼5ü-÷—ÿý Ï7Á×ÓÎ:ëL¿­¹´Sý¯½îZø®±;«ÐÚ·iïž~yÛçä“Ov¯½þ:WvH—8LûscŠâ¨þDZ¸`!ÎRÒCKÍë Vkª?}‚§ð|åii2ÅwtxûÓ°êm÷ÝöróçÏe6­üÅþ¿€¯c5Ú÷ÐCb5Ø„«Ö¢ÊÕ·»ûßw³|lzyäáGºù ¿æ_4&`ß1Zùrëm·»SO9™} 9)Å3={îå¶ß~‡ÉdøŒöE(¸¾è‡xlU¹ßýîwìÚ¶më/Á}=÷ozšX±ý·Þ|+ÀNgto~ð¡‡G¦ë?û$RŸjÛÕÿ÷Ý÷€û—¶žíÛ¶Aû,^,þ%GÊ%õƒÿÂ,R@tÚ¨šgÌÈCøòX¡ÏY}h4)\ò÷K ‡zù¦ðÔÓO“ë¨ >ø@˜Âbá_·ü«ðõ×_­„`3§ðy&¼õ&­AQ["FúI/Aú»ÿþû„Ar*ªœvíiÅPUï².ZÕѱ㜗VŒ5Êç€Mû‚ý;÷@yü /õKþì5’u ïÚ˜rQ}Þ(’ÊÄPRùs(>#!ÅbôËÕ¯¢ M<åW°Ü­ðÊ+¯d$Ã¥Ìçœsv(#¥Ë¤qâÄ …[n¾í£«¶D“ …¦M³^fì|€úS9#+kFìœsÎ yUJT‡”É"´åË—9äàï†ëo“Å àiJ¡QiPåvéL+W\!û•M„.¹äïÁfæŸê´©ÙÊ—®Ýº&N˜,.¯^ŽÕ?Ç„|ÜNPwV ©ÏtÅÚ§Ìš¯ð•(auA|pHÐqà ðAP¡È/Œ’yJh'ñýöû™èF{ÒJ,¡ zHß믿Vxã7„Õ‹ëù£ðK­ºÒãóÏ?ó«¤ïΚ5‹Y¼*Çô¼&Y ÊŠW¯xü ½´‘ìb`ÆŠ ‰C¼Ê4_=®æÏñsŽ¿=°²(;¨ìt(”Tþ¸@01êC«"'L˜òSÙ´}¨l´JŽ  ³4kö¬ÐF·ÜrsÞ$§I÷-çë˜Èzª1-,`BŠóÓŠ³Lku˺wßÑ’Yßøèe¡S¸Þ~Ää„J4¸A5’$­)…*ÃÀe$¤ŒÒÜ *¬$IkJ¡JÅ0p )#¤4‚7¨ +IÒšR¨R1 \FBÊ) Á ªÂJ’´¦ªT —‘2BJ#hpƒª°’$­)…*ÃÀe$¤ŒÒÜ *¬$IkJ¡JÅ0p )#¤4‚7¨ +IÒšR¨R1 \FBÊ) Á ªÂJ’´¦ªT —‘2BJ#hpƒª°’$­)…*ÃÀe$¤ŒÒÜ *¬$IkJ¡JÅ0p )#¤4‚7¨ +IÒšR¨R1 \FB*uò÷¯GÙ 4åT…•D÷t’Õ%JU©.eË–x¥,®çtþGF5£BS:±  …±]M.#!¥l@¥4¸AUXI’Ö”B•Šaà2RFHi nPV’¤5¥P¥b¸Œ„”š>ÇÈý)h*ª9 R¡MY›fêCFB*Z‰§|@ƒª°’$­)…*ÃÀe$¤ŒÒÜ *¬$IkJ¡JÅ0p )#¤4‚7¨ +IÒšRX(Œ;6ÜžwÞy,FÜí¶“gsÏ=GU(ùû÷¿+ùw.´hÑ"è ÕöÃßd3KDҔ !ËHH¥4¸AUXI’Ö”B•Šaà2RFHi nPV’¤5¥P¥b¸@ÆŽ}O|‹ñOÛ‡lÊ[ ®µ„5£B¨5¨IÅÔØºÉÃb¥d•FÐàUJ’´¦ªT —‘2BBÃò\RäD<òóAPVð;’ø•›Ž±Šà ìÞݸISìGÓ(`÷fùÚµkÇï¸êþË—-uã>ïÄ«hŠ~í^Œý3òöé‰Ê@ûjÕFr å2õžFRl: nÆô™n¯½öâ_Øñú˜:l¨ëܹ³—¥™Yçðëæã½[² ö ZgüÚN 9¹Þ'ôövª°‰ÛpÐKõ‘‡qGá+b_óËÂÛÄÄ!ö c)µžÖþöÛoÏtÚ›¦oß³±’`¤[†Íã(W}ì²ûî»gEbj¦Ÿ˜AÖR­röÛ¶Aû`•Ìzë5¢¢aåÂR¬°úí3Øu¢=b …Vr,^ŒöáÃøŸÒÈóð#b5ØQÂæJ¨e…™}ö“'3(SQ&‚u°)ã£>ÎïÎöÙgî÷güÁ²ÿ&þý.ýªR…½›¶ƒ.õ£s»þô§HK&NüTl"â”SO9Õµm׎¨Ì¯=g÷=‡õQÎ1¤‡úŸhÿS kÖúWIÿR=œÉÛ¦2ˆeÌþðø€¨¤–› 0(í]D+ö0%yPN•TûsçÏwÏ=ÿ+:âðÖSÂÄUU®G]ÜÎx‡hTÿ°”]qå?]ýzØ‡Š‚[ý Ü%øœ¦jøÏÿÕŸסÏÑ>\TÒGeÝwŸ}½Å*G{Š‘­ `µ}z· û„Ýí¾üò«àÿI“?uÏ<ý TTaoÝ[(«VþÌÿd0_nO®¯ä8õÔÓ\»víØ&U‰ÊvöÙhSŒ=J0ª ?B»i?‡¾°d´ýÉ>ÅËéxïŸ>+K…YŠý†Æô1ïóµÕ– M}n ÆÄ%AûöÛweœÆƒ>g÷åýœ–-— BëÃGÙJª ë›âõ™Ó Ìpö—e3Wó%ûÖWä&:lû«§Ø£Hˆ?‘b†r)Wæs‹'ÿ“op¨{U¿T†ÂäÿY|(ÆÑÄÚÚÿèWS‰e…H"¶kìÿþúƒ«é+ŽÚËVÊR®+®¸²èúoûTöy#-t(\±}–å†ÑÖ±ùm]VPÿd?øýê«®v¯Ðþ(pßå—_îÛ´Ìý %ÿÃwß~ü}½àkwøá‡S@»½ð&Éßÿq ãÕØ#0ºÿ/ãÿ &âyõM‡X9~ó›ß`5{Û÷Ÿ)þáò, áZ½/‡´…™‹Ä|ì{D¯#9µÏ%—üƒqɯ:äùŒh?´ñ¯ù‘²Â¡dWJ‚6MíŽÍhÛwh^W:ñÄßâlÒKy);éñy Ò'5éA6å­_¿¡ë´EGwÀ/~ yßdSÔs¿úJòåì†Oöv ÍaJ¨z!œ„*zk܇ã¸ÔmÚ¶s[uÂJÿàG­JùGa¢Å÷D÷Kl4ݲeKײUK×¢e+ÆiSkR@e›2er°(¡Í¯;ì0O’R©ÿlýI oßê õ¢ùÿú׿ºÖ·fûô*}’”6¾=öúßsûâË/‚ AbûTvj-GMö?Cû\{íµØp'lhÛÀá ÷ jŸ7ßbû¤î\´?Ø’/´ø€Ú'¶¯vª}z]Œ æ³3bq­?›¢ x¤N*Þ,‘|ä•ͰvöµýCVdÛ»ç^ì(Úìm9/îÿlI!Úã•"=ØŸ”BPTþNôpl”ó#¼Å\?ñCrôŠT8€rÊ;ªÚòLýIž¾@BF6Ó ƒB2û”R¿Óg‹Í‘›Tòáã_8#Ûšèð9T㼜}ÍC–†c#C:°²Åµi³°Ø~wáììL4jû³ˆëùÓ)›äÄ«ˆÛoç‹QàÕþ 'ô†~)ÌÃ=äû÷:½VרzÈ¡Œkühýû%êÏö}]E^¬§‚£M=é Y¬c<>I-äf¨ ]åìÏ™E}îîs ë×sÑ®¿8àîÍ7ß–Ì8Ïýr®7Qpýë_Ø×d…^_ÅJ5hüù÷¿ïq_â5S:*®?e†rÍOIªƒá¤ÔTy }ý}ëh½®Ðÿ¤&ÙOþ÷ñÃQÂ. LNñÇnÈRÿ#‡èø¥ãŽÂïäøƒ‡F¹ÿX‰ø÷÷¹Zo…5Õÿ¼FO¯*ÓqÒɧ¸Cùõv&ÓÌ…€Ó©ÿy·D`U÷¿,jãÿ'Ÿ|š?PB¹NA›Ò}:åîˆ ÅÚD–Ï8e¶‰Rûw kŠ¿ ï{jÿwÿó;þ*0Vü8z]©>?+{ߢÎÜÇÉQeêÿë_ÿ ›M?ên¿ývþ8å<ûœ³qßÝÖ}ôÑG™"`Ú~êw…?dÿ“ƒjªÿ~÷?n,¶d ö¹÷¾xö?rs“hì+üáÅ?O‰…NJAÆ r….G0ï³BמŽï :U¼b†Ø<€ˆ8‚Òû­<ÏïÓ·&‚Þrñ³°/Çuøª6®eŸ…qŸZ’ӴLJm i9‡§.HhY_ü2¯Ðú1ïŽvþË_X—Ϙ>k¦§973±³ñG3² ga²„²DŸ ÔÃ[¦—æ\ûvT¶øðêµøŠ2yÖƒC£Bóc#o|%m¤»å_·¸=÷ÜU¼ßÍ£=î°™³k»Yì…ôÉ)(çÇIë¯í ÜbûÏ=ÿ¼kŽÑ·´–ÎqûÜpã¼oŽ”Ï·?”HÑà NŸ8µ‡–_í*Ìì«QæÕšŸÚ¦Ái’%œmû“<š_í*.¬y¶‹Azg—ÈCðµ§]zôp×_½û¾ŽEûŸ¾Œ§+Þ(MsÎZÿ t:X=NÁÿ¬”Sò ä„rµX² +£|ŒÉË‹®o«þÓ°—Z /{éQ“ÿgL›Îbà+jöP=?þQ&Sz>V$ÉAž“²ãž—+ãkÂlþúb lVVpmÛ¶st0»á–[oãŒôÅ2y_«…0°7\Gú¨Ú×vW(jaÍ °]œÔ¾æSóÕ¾d‚þÓ8X-?ó ¡¹³\ÜÌ,*¼RöŸ§>ײ…냽”dLìâú`LÄëøòYoQ†ŒÖ>#1Üü¯[yâ›ìÐÞJa<è}Âñn³¶~X«ÆRöÉ—')¥˜¤³õƒµ¯"ïs•ª¿T» …Œ|ÞkÀÉkRõÊæt²On e­"þò^Kþç8±'õ”ÆB‘ßRü±+8‚pò‘\¨þ#Bêìœ2¯ˆ¿¼×VCÿK+aŸ2@¼h–•Z4T…õâô$&…è ·$Ös¯ž¼Ÿ$P>Rû“¼CÅ%¾¿°÷ÀòÐó¨»Õï Eò^€sâ”× ùI>ó?=šƒÿ"ïs•±ÿÔÓO¹ƒ:€Mîݳ'ÿ(Ï Ê]‘ý¬Tµ±O¶´Þ ¨ö‡?Üý›ýNÏ—mÜ ¾ÁÃþö‚%ŸþóJY¥G &ôÐöoß¾&ku´giÿ{ïå=s[6oÁ÷á~ÑgÒ–R¿+vjÿ|üÓ×—ïFûߨ}è pÜ6ø¾ ‹F¡}¨ñ®Ä[”þù/¨_I/X}ý_[U-¯^û4ú˜ƒ§‚F¡8žäaXx«\¯£äÕ!ZIÑ+MðœæÈ{œ6GýÙ~û1ûøð+Jد‡7“>¯Âì„O@³~ª7œnÝpOí Ï” „ðë<áÆ~“FM°1öpw&> N.¼âŠ+=Œ±.ߨmñ %«ÜÇ>v /âϬӧÖéo16”>%?cÆt|>û‡½NXb. ñ¹Ã¾-œÖki¯ƒ2¨8·Ëä²#¶ÿ%VíOS!vÆæÁãñILìÅÄíó‡ßÿŸ+¥ö¡£à›Gë¯ÓÂe‰ZÖŸVþð@›JûT¦(`™-õçD´Ó„?†„äú¶ü0VŒµë \ô Nqs?`:ð•;wã 72N~ Ë´â'kbÁ¬ÿ'~<1øŸ6ü£—>N+è¸81MÛ‹ørןìÓjÚˆ™ŽÚÖò|ü&åµö7ÝlÓ`Ÿ63–£´})o•kÛ®‹Æj#ú䦭?9gÂ'™OÖ¶Ýv[QI³=þà ”‰>¥fú¿Ú"}ôêM†Ð'WéÓô´©7½‚J>9ñ„Šìgš,F©DÞ®±¿"ÿc/.?·Wp[c~:jò±>ÊQå¾øâs·‰°M“ôÚ&õ¹«± ˆ^ÔÏÛSýéµD”"óJ1zeîeL~ãëpîlØMãIá q®W¯^{«iÉ`î ÉÒõÏ ²I[û”7ÐHÂÜuäÛŸ³’ýäÿÔ-¸á$ý©¨£€`úšïÿ$Rÿ ŽûþŽ? ¾ž&&ʶ?" ñbc#8‰£-ëOáà x!Ýÿ=þÄãá°(ÖRü­„ÿ©a2ÇõöÌÿÔµëÿ”öžˆ(%úÿŸ|ÊxÀà9¼þßÃ=ö¸mÓJík-j¡ŠšÉÀž}Ù¼X}bZ¨¤ÑûEÙ.„\f|âIh›ã €“O=•ý:lØ0LÍýÆ÷¿¬,:•·¯%/\ÔPÞH!TcarÝ¥êy!Ÿ^=ö—,Â)(‡ú_ “Í?…ÊŒòÐV8ß§ús;P‹ŽÌÿÙS ™‡_rh¦I]'|Å‹ÞÛ“N¸*X€”×ôØÇGzUióÍ; ©z îóÏ?­ÈâàPqo‹Å¹¬m &©Ñ|‚^+üÊ`·öä¸ôÒKýÒN}!iºß·„týd—]˜NµºûxÅ­aÃì¯V 5ðéºXZVdöòöI†iÃÛ° «¿RY·P’›>mFäƒxÕ­Þƒ¼ÿª5þþرÈ#õWû›lº‰6–{íµ×‚NA2û´Ï "ãuWSûlnd«ÜgÔ>¾.Rc:íºë®ŒSq¤×»€F.>NwÝÕùtÚn;Ú»‰Žê¬Ìz'*—D‹Ã @ÃH­$Ê´ï~?sø\<¤«ÜÀÄß@Rˆw¶÷Ä«‹ì3³äIõíRnXööóõ¿1!9ªÜÖ¾Mƒ(8ùúg… ñxWœ(Ó1‘õúâȘ(>ñ$v*­êòÞåìôê둘¢ñ›…‹Nœ? ñ +X gHéú—Wåú¤,±â,U¢þ™Á¦zCíDw–ôò*—ì'ÿ§øKý†…lĉSßãñ‡ªŒ¿Ú·?$I¾h<%Ñ!ãêwÞU¸˜‚æŸüdW÷ìSÏð—NY„Oiü .\)ÿ³‹37aê× ½V×?QCÁYJâŸîQ>è@njSÚ‘¾^•Ù—ÒfIg–ú÷?_ÑJëOÛ§ÐÁò7ÓÍNo£Ð›)DÛš¾’ å§žt §zXÞYºt™ëw÷Ýn)MDÀÑyûS&Ëö&Ô´Ò¾èþ“Ê]òHí/¾”óN;wÇJ¡ÙnÎLm#D£vÚz™?8_#¦4­Ê×ãÿ˜èá£ÔþÔ´S7RØ«çÞ1¥ðœ©*<øÀƒ¬gÈ¡œ‡®EgœñGþÂ1>ùäÓÂ)§œÂ<Éã ³gÏálØø»íöS_fWø;¾°†W3˜GÚ±¬¬pñÅî0 È«}*ýõ=e E ˆ‘'´Ýòb™ÉS¦àKLM±}ÂóÏ=Ç_+¥Õ…¹óæ?ü0¶Oõ?î#a;/¼ð‚ðáC’ï¿8F_†>|xðõ«C^ õ·íƒ‰ ´Ï©¬ÃIÈ3›ë"%õ_fƒ}|Z¼È¾'”¤¿ð"•MÚ–Ê&_ûª\Ê{£PÀJ&_iGò=Vs‰þ ؼñKޤ䡝®Q™(Æ/^RC‰?û«&›î¿ï~”g!ò.Â×ÇneÝuןâ§ål› >ëÚµ[Ÿ,/|þÅçüj‡¯ 4gûT¯Ž;•´Ÿù@|Ç>€½L¸¸þXlªÏQžphžê&Û|¹« ñÅüQXŠ/aO1þ¢VNñ×Á¦ú¯…}6ç3ÿ­*|½®iáé§Ÿâ/‹aszþª•lRŸÓ^Ë~ó}ñ¶ÛnC)2ûT¤_|1øìç+®¸œùä¯&ñõ.èÒ±&Ó•Ï¥6òtJ+_šC½´ÌôµÄûî»cl1âìæ›³6ý‰iSÖ¨*¼.\P¼úºÛ¿Œ –.àuÅPÇ3ÎøCaÉ’Å,“‰Ú¾U¾Ï ؈¾oÂ_¦xö¹ç£/(Λ;¿pÄáG°MK©Íã#0&sJy E$NÙlÚ’F" ±€—£“˜òJÖ8%4åÏH4 6ðrtSžBɧ„¦á‰€Äf^ŽNbÊS(Yã”Д#<#Ð€Ø ÀËÑILy %kœšr„g$›x9:‰)O¡dSBSŽðŒD@b3/G'1å)”¬qJhÊž‘h@làåè$¦<…’5N M9Â3 ˆÍ¼Ä”§P²Æ)¡)GxF" ±€—£“˜òJÖ8%4åÏH4 6ðrtSžBɧ„¦á‰€Äf^ŽNbÊ8iҤ¤I“ Ÿ2œTÀkúôC6­SxŸ‚7Ù_3Ä̤O'&ƒ6iÒ§…É“)O¾Ð>IÑ'¾d‰|5”Fð‹/þ[¸FÑW‘°ê·ðî»cGãïÝ^Ÿ`¸×þøHã¿´”¶›iÂЖ±Ç²6ÎÓ)­zŠŒ¶?µùdüQн ÝKQûs›¢Ý—,]"*ýâ‹/ Ï_ô%%jKiÓQ…ÑïJ{¾š›jW¡pâT&MÆ„g$›x9:‰)O¡dSBSŽðŒD@b3/G'1å)”¬qJhÊž‘h@làåè$Vm¾J&_*j_Á’çm¼µQxÏ€øÁŸ%FŽ]À~“Òq¿·ÛOw§,8ÊÙ)G·yb™8ÅÊýé‡×þÛ¡/Qß;÷ÜsÙÇâã¡€Ä:Œóä&©y +Nq´±ðŒD@b3/G'1å)”¬q s1™ „|@tb¨gÏžžƒ¥Ë–…‹ }®ž”ÓC¢ýÄ9¸%?㡱ªªÐ¼yKä‘!šˆ±öå! <1”/jl›Rò¹zW XÒcsÐçØUß—_Ì|õÕ¼ö.?°Êgm™élÑâ…‘AÕÕÇLZY{yûQa(á…ù‹à‘fa x(/ÛBÙèóÕ4à«m‚Ç{lÈmíÓçµ;ógõ!SòSºÑ¸æêk8ßÂ…Ô>ÔÔô ï¢öjÑ܃®mÿZʇÍ9?}V4¡RÕ¼néÏû ˆrfñÿ(\Dl™(Ï3™dãrüíoGò!¯oJ÷ïß?>ÁiûJ÷~ ÉÍO0ɨíלBýo»ývø$¨bäEž4?64äSdút¼šÀ¡O¼Ó«*,é…­ýÛÔÔĈl/RûÔOÔ21n1&¹_™öç‰AoÃúsÖL …䯸—ÆšÀS[ ãÁÊן 弘%µv"UêK$û‘?ò Ÿ¶ñ—üOˆe’9z‰Œ%RüEþÈ'|:Å_>"G­öø³cy„›ëÑéG ="9ÝÍhÙýŸæ¹ù¦åša®?,Ÿ³A´Q#G{39¿¨qc‰Ôÿ"ä>]®ÿeíçï)K´ ÉÜëãছnŠî²üYû+mô¨‘!®ËÙÍÊÀÊ#±DjÿÈù„O“ÿ»tÙŽÛN'Ô¯ø0IÖ¦eÚŸîßÿ}ΙÀu4Jç>Ú_½­0r÷~æ†ÿ¥}T®Æ9¿_þÇ `xlåƒb‡ORŠ>ÙLG½úõæOu|ÊT»®SÖ† àSñÃÂk'Ä›‰%ZôÚ­·ÞêF)ÊX…ÀýWÍXq!,$zP ,ÃY0œÔ~ƒ YŸ­ÉiØŸD–ÍVa§þ ¸^¤§iÓFîÑÇqþ‘t{Ö5nܸðY@ÚÉРAød6é¤Cìã—Sõñ5!=xÖêcí‡ø4Aÿ¹Ö>SV'Éuöo¢Ï‹cÅoÜMÂôÉxÌøs^Læ`3¬+ñ \¿Ìb¦„Ûê…gŸC±¬ÔôùkÊÜKK—,YÄö×Y§¾öÚP‡UR HàÁ–¿J†î‘ïHnè®WÚ=ó?&¸,Òn@}b”«¿C=ÿü³î@Þð2ÑWßd_Ò)ïäze^É6Ûlë~þs¿)hýëEüs)œœkk¿”ÿûÐR¦üð Ù¸qãysú„ë¿ï¾ÇtÒÉüu<óøóŸÿìvÛm·P_ª;½Û=q´ÃÛ‹ZÈцfXÝâ¶Ç×å87&bi¼ßuןà _ïðÆrY}âúSjÛm;Ãû“zUðÁ_#”ªÿ¶ÛnãöÿùσZÊSb:H'ß2âŸÿü÷ ö­éÚu‡‡ì»ïÏÜHÄmÊ­ÇV[må° åú¹‹\@w"6ì¦}y6kÓÆ—1„%¯b‹â cD™ø'5o¢Šó­¾x̯Ž&ŒßaÓéutÓiRŽòñWªý£¬ÐAû a^Q}íµaX¾ÿÔ‚,Ðçæà«d÷¹w|Ÿ§ž_Ž<’ƃÏ0\Ä›VSý©?¿‹õéÀd’ûçWº~ýúqšøj’ë¦ pk®¿ r.u3—Rgýß @ë/„d?ò¤&àœä8ÿ)ÂÊ÷ÿÜ|Üxopôd8K„SêÁ@¾«ã­ƒÁ¹Ñ}0,_®ï=g2žËu'ª¤³hÁêž…ëóüý‡ï‰X";üf¡™–4þg¾ÈüDØ·ÛÿbÝrö-Ì@pÚÛ‘zVð\¤“s¾´Un9‘Àdà I©OñÉŒŸÚ?óy>;*mùVñóï™gžÅ{OÒgîe ß ªm:=ûmn©{˜Fû®Žk_üñó-|›þÍZ^°JÛŸrgM·ö՟ʇý–è‚òÑ‹JîË,¨9W‘Ó¬ ä!åó°AÖ{cƺͷØÜm¸á†D*y¬*ûR6”Ë×£”qÚ[åƒ>ä­ÍÚlêêód$MMÍ…lx5Õ¿6ö©L¥êOËÒC6–Žº7ií6ª0Á¦W`¯ha’ãL-q-[´r­±Ñ0= çúÂm^9sï}“Oõ1iÑÉÕ¤CÌ™³ ŽÊ6vì{®.â²#>íÎ}'ò5Køß˜ý[ã—|%íÏÆ$6MÒ§ä7ð_k35¯•ÿ³úןúÅôØ3JÇDõMýïkŒ¥&|âcûE4 ”Q¼˜úRaò¿÷@Š¿8$Òà‰ßÛþ‡‰!,oÒJ‡~×žÙÆ7=šEVù€*’™}yL¹É~òŠ??‡~•õŽÐ×@Ò>h$Ï¢F> Š¤þ§žHãOAê“4þ¦ñ7¿4¢Ê!=$ëDeHYïÉð4þ’‡Œ¿ªHºþ¨'Òõ'ëAê“týIןtý¡1TŽtý!?d£¥ÒõW\’ž«þþÃl>MfÑ ¾g0½d?fóißD Êý¶A•¹âY^ªŒ?eˆq¥úÜŒx‹žëS ’ýÌg™—„&éäÿ,¨Ä/™ÇBÀ1)ÅŸñLê6ðºQÖ³ˆèS Òø“ÅLæ%¡I:?iüQH\d“Æ_ïvIºþ˜ÈH× xÃh6²ѧ¤ëO3™—„&étýñc €ø%óX½oØ%iü5‘‘Æ_ xÃH6²ѧ¤ñ7¬¢î}¾F^ó]{ßÕ(’B(Éà ‰†C“ X+oÿÚk®q³fÍvÛo¿½;úè£ÅFiCÂS» ¿¡ýÈ_Æz‰Š&ûäõ»Â@¨¬ý+õÿSO>í† }ÕHì·hÑÒõíÓÇ· ¹Æÿ“O>é†ê5näþü¿–²ÕÁ ½€&®!ÿgfCA@Zùþ_iû'ûˆ¾&ÿ{G¤øKýï;wÿ“Æ?3Ÿ®9踮0]ÿÙê…Ù@ÿÓøŸÆ¹)”Áâ;ðüŸ®«îúWU¨Fäôë¸ùá¸ݸǹ:x@Þ¶sg·Y›6‘è»ïŽvŸ~:‰iûvp­7ÙÄ_T‰§ä’LeZ †%¨xþÝÒIÁæ›wp<ÁwÜqîß÷Ü•muØ÷UËêR!_veZ †%ÕTknÙòånÞܹ®~ýú®qãÆ«ÅÿÖ¾-3Ó aùòj÷Õܯ\-›ÏÈ"FNõYRmëoóäíSÚòÏ?ÿ|wÅW¨9†4ÈtîÒÙ5*’]öÕ0—ÉÌ3,)oÿ´SOuÿïöÛ]ó-ÜÌ™3UUTf&Z% ä’™H †%åí熊dß{Àú,snpOò?\‘öY‘ãâXMñWþú—EWì³ð@.®rÉÌE%–”â/Å_¹ûÏïrÿ›uÚdp%ÆÂ™ÌÅŠíñƒØ¡CäWjokÂÄ îì¾}ÝcǺÛÿßMÕFñGIÿ³¶å€šî?àž5yýÿjþ\wð¸W‡ ¥æð-ï\Q›rÃá¤÷} 9Ú1?pwxõæêÿ)S¦bK”£Ü°×t‘ý L›,8÷Ö[Ã]Ïž=Ý]ýú¹z÷æ–û¾Žÿ³çÌv]»b|›yP|Á‘¢Žç&¾R&µ ñ$Ãw!þ´äR#9§öOíŸâß÷ñâ¾F¯¿<¼¬àú—ÆŸ53þÖ©SÏ-]²Ô5nÚØ5i‚-¨±¨(ºžCJâŠÎôW·ªŽ[‚û¬6ìuפQw×ý0¡1Ã-¯®Æ¤ÐÛ®Cûö\¾?žq†[¾lãmß¿þWÇÕuK-áñ°1Æ8:¨–úÇŸÖQèó/¾pGѤâ¿I£¦îñÇw_b2üË/¿pOüç ׺ukwÁð¤¶ÿmè3C^•IÕ[o¿ÕÑ꡹XXqÆïÏàp¸óÎ;Ü‹ƒ^Š,‰·¥‚ËYtz®°ýŽõ¿zT)³V”P ââ Gæ'žxÂýt·Ý¸x` Ó…ã•Aªª±çÍ —»wF¼ãÞ5ÚMœü‰ë´E'·Ï>û¸#zîêÕ­ÏŠi0Uû×]w[°àk÷ÛßžèZµjå¦áW“~˜!>|„[´p‘Û±ûŽîÐÃs]±Ù4\<*™¿$ûÓ¦Msý˺ñF»ãO8ž,q6š…„÷ Æ âˆ‘£ÜtÈnŠedÓkqèê×k ú!'G•»îZ”máw‰'b©oK” úQ¶·ñ ×âÅ ÝŽ;vw¿<ô—n‡®;ˆ3|NPC¯*Yû"$Z’]ºt±{äáGù"NËâh…ÖvÛmïºtî³›ô^¸?÷ꫯº±ï¿ïÂg/½4(X¼ô²KÕ¬ÈÂþ1¿:ƵkÛŽéTp^4È 1Òñ^Q“&¹Ž[löÙÛqD/¶ëy`:®»þ:÷õÂ…î¤NÀÒ;jŸiî®»ú9zUŒ–)ïØmG÷ËÃàì7Ee{ÿƒ÷Ý‚¯¸A/¢sAMhÐ+\¬ •ÆÎ箃ð¯~u¬kÛ¶m(›Æß1Çí|€éçŸw¾» ¯3BE¨?1$í}«LØŠÐkò¿(”Lü Ê&)¤i¯›ÀPÔíù^pï¼ónÌš¸Ÿîú·÷Þû¸­¶ÜJ §Ù9/ú²åî¥W¹‘ðõèÑØ—kÒ'®cÇNnß½÷v‡Á×õÑÆZ+µ¿ ƒÿÀ÷³?ùäSüBØÕõ:²—[·Q#¯UÊ&Åúfõ—êÖ¾þ¶zÉ>µ\ò?m Ê>N@¯©ÿ¥ø#W¥þ'^@°¬`üMãyJŽ4þ~·Çß ðŠ=½F¦G§-;b_Ïñ¨µ,ýш—†f°ö¢‡F:ˆ¼e'ä7^²Ñv$—àÞìŸWÿ“Égžu–;õ”“Y¨aƒúîÚë®ÅýõÛ¼J᪫®âû¿ÔÿÔ¥ý¯\rµ4Ëê¿þ/Æ$ÐUÿ¼ŠKpæYg¢MOáÂ4Ä^4×]6Å=ù°×†9nSÜ›¦ë¯o#Ž|ß™ÐzLå•VÕûoj[i]‚àù,š“¨t|›í^wZ¯í¼s·žÅÔþŽÝº¹sÎ9ÇvúiX 3Ï[#\÷vúÖí“Â5YµßlÃf~L$ï:¼N·%Æ7z£CýͨIÜÕ»h¢›ŽwFŽà×)9ÓÁìöÛ?×°~C&Q/Y‚<ÿDºàΘxÊɧ2¯ÆDzÎåþƒíq®‚ ÍY¬Žö§¬ þçñ¿@»Oãà³ œ¦Óyç‡~SUØ¡kWžCÝtÓÖ…êêêÂôéÓ©Å øÅ¢°õ6[3þЃ†|ýïé_À¾-L÷]ŠqÊCéž{í]ÀÌž—Ïì+¿l† &ó¶²¶•qÜ/0ì´oߎúu›O!S§L ö›¢ŒcÞ{/ãÃÜM7ÞÊC6í_7ÔuΜ9™<0*!•›ìkÙBnI©—-›U¹•E!͘>£ÐÉû !RT¼gYèß¿P}üñÇsýÕo¡L¦NêÿÁƒK>ؾçžÁ?ù<Ü>={¢}æ;Z\ª?ÉG>0õ'žú€ËF>3e!¾¤Ã]룲± 5˸Ñ)ªÿ¢E C™V1jÅÑ9Âá‡ÎåÀä[Iµ*ŽWá ½{÷ŽâEz²Ôëá‡.Ê?íµEhÓâú÷ì¹÷¶á QÿÚsÏ=‹êOþ§>G~Ä>E¶Ê´ü̧DÁ“"z¦­ 9Xå§D!Ùg—D~ÉœZ†œ ¬‹òS¢ˆüÏ.‰ü’9µ 9Xå§D!ùŸ]ù%sjr&°,ÊO‰"Bò?»$òKæÔ2äL`X”ŸE„äv Nzÿwä‘G¯Fî Ô ¡{mº¯:²W–'ã ¯â~šîè?ÒùŸî)•?÷«¯lVÆWd¿(CŽå§D!µ?»$òKæÄRdºç6Ã=!~lÌ„=¦mJqñ¥mSR)”Ÿ#z¦² 9Xå§D!µ?»$òKæT"üÑGüüIm®ÏX™DÍX¤–E„µÛÿôüDq|ÔQ¥Ç·å˗˳dÎ<óÌ"gU„ЇúËAê?ü¬Šù‡/¿š[¤seF­8ºˆ°öùKP}ü¨.(§õD«:¶ÂXS§Lq“'Oq#°JbÔÈ‘”ÃáÚ=ýtöJ™æyæ¹gx†–ºUô>ôòåKÝXaóVÿ ü’»þúëÝ…^Èz(Û¤ÑîÑÇsô)z:¶Ã×ÐèÝJšÅŠwiwßc¦Ë‰›Î¤ÃCµÛsï½xϲ?ïkn³õÖ‘ÙaZéôû?üŽóЦôÉ_½ºuÝC=ìž{þ9̎௜i½H|@å¢SV¶*×¥Kg·wϽÉø„ø0·Çž¶l”Aê#nµ³Ó“³p©þç_p¾ûÐÏŒþío»Ý°:ëK,‘3f /ž‚6h¸Î:!3m¢¼Áú°ý—°bhôèQÌ;³ÏYÆŸ4wTÇm²ÉÆ\¸Ñ=ûìÓ¡}Ž9æ´ÏnVµÜÕï.ž„•D×cÖTÚGêOþ‡øx ísÍ5×2N+™öÆÊ*ÄP¼¿¾‡o.Û°×½4XÊÛgžy–(¡3é¾f—¿…XÅlï{Z™S§ª®fÉt¬ó&Øy‚꺎 Ô"žHRûSÆ>ØÜñn¬B££G]\¯^GðЍ~w÷s&LÄJ¸#-O<…~½ñÇSÏ<ƒXïšàKqGceT×íw@_€¯±±;Íü4ØÝ€¾ð§?¡/xû”ÿå—_f ÿÇ{,V«-twÞq'ï+ ªËÖÛUP›ú“lÞÿš_xRr>–µ³,’}õ׊û9Çjü%ÿ§øKýOû“¡ÊãiüQ¥ñ§Üõ_®q8—¹°­Îñ—n5òGmìSÝJ•ŸÔá‡RQ ‘mqÏJÇ›o¾éêÖ©ëºíØÍõØygáã<«À·lÚ4¤ ©ý(ƒI¤þ§Íòíö?Ú†8¸3îéà6ųL7¬0ÙÙ·)ùú´éîGM›àö¡ôý/åM÷äòDñ±6Äÿ(¼i cÖ[nY\È2”ïKÿ£zÐǦ²údž†g~ÿpîÕ«3hE½Q·^]·y‡-\S¼ªk㟞¥õàþÅo¾•‰ÔØœ>{¹ý¨)ÞásÿM^Ùi0¬ :ï¼sÉ7<äúöéÃø%—\R8ôCÇä C’ÁÆN!÷„'n¹ù–‚ù … ÊŠÌ1à?ž&³öÑ1Yú'Ãûï¿/Êo”¯}ûö,§+†¦MVÂ4iܤ0zd<“>{öì`¿G…… F“§ÿ¸ôÁ>&¿`!+–‰êJø}5”³QÖ,»-zÆßŠtì$«¬vêÞŒXÁ’Å‹ ¯¼òJÐcóK ¹ìT¾ÙŸ0ñãÂ-·ÜRøzþב}|‘ýCõÃdMVNo•tký Þÿýž“Zt_H*›úO$=Àc9…Þ£-rÈÁ¡N7ÞpC$S. Sÿ¢\šð¹ +†¶3+†rþ¾×ú…_ó–,]ªÊ Ÿþy¡K—.?kæ¬`âô…[þUXðõ×Až˜Ü7æ<­[oxÏ?÷\ðÕü¢€¯NÞWøå›f3Ÿ«ô"Dëî«YÄçZƒêë_Ì'ž*)ÉU å™5f÷KöËû1ùŸ‚°t|ÕÈñÌ5¸/õ?¬4þ”ïaiü©q”)?2}³ñ'[ýÓ«Ööuu~X1¤cŸ/ä•W^É÷ t¿LÇ7ÝîUž~æ™Â_|!iÜ¿øÂ ,“®ÿêDqGþ¼ªÚ?ØYAÿ»âÊ+¸ÍB›ÞxchÓgžF›~NmÊÓ=…^x1¨Ä— l=V`¿l>2@Lýƒ¹³Ï ౟òר |>Ò¤YË+{à—Q%"š?—ÍG‚šµ¬g”!Žp±­Da/¼A@ÏÔ×É7eó­û¤2¬NûaL,³ŠrèС!ö‡¿=¼Ðëˆ^>-ñߤIÓÂ=ÿþ7ܦ¥®.ИHþl§c"õ?ÿð,‰x¦Óç½tL4NPM†”¡ÄÔ¿Œj0ŸÀc†çÑ5ÿuÄpä =°BBvjwØ?f‘;ò裙óЃ¹Ç±×­ÆáÕ2*o`»ömÝéÿsº[§ÑºL]ŠýRÆï~Ùñl'.S´Ãøâ%‹}[øÌÞ>‡y;´)˜hIBg̘áöÜkO^ C³ƒC‡ uÛn'3éZ­7Þø?ÞœŠ¦v®Å~F묻Nd¿÷oz“*¶ù6V6‘ͫ֗<üð#î”-09—9Q<ÒL²„˜2ÀÏ,8·}Ù;éÍ·Þr}Î>ÇÄÊ,òõñîðî»ï&FD} ÅÂæëA öÛµiïN?ýt·^£õØ>ígÃíóÒ`× ³ÑT×)S§aƒÃ%EU¤úÿáGqG%1á­f Dý)oµÌ1¹ú[cuðË­L¢Ts>û «¼þ€ü¢…»Ö'Yµ=•°oÙ¡aòöuÃGæ¥EYë¼ðmBFv«ÜW\)ûyû믿¾Ã„)óéôß'ŸàR3·m×Žß ^w½õ˜Ï¾÷±ŒA[n%{M™2ûT-æê½ˆÕZ¢¶ŠWÕ5h(ïÅRæ¦øÕgÛm¶eþ*©¿õg®þ\øUíÿd_ÜLçär‚Fœ÷‹€ÿp‰í/ÞC *ÿ¬¾peŠ?!àRÿ[eãù›ÿ°ÁôJÇŠ”MHo3鋵¤³ö&¢cЋ/CF„ß}wî+ü !Íâ¯ÛB(õrBnˆ%/â÷†î DsÎùßçÊ´aБ+¸ÿÏ™óëkÖ¬CZå¯Ç»cЦ?¢6•uô•§èøì§ë/»7rkH|ËíÿOìŸCÏ t\‰gŠššÿ©kК;>lý'Nœ(tœiàáGâÏÔïÜcg–à î7ÇïŽ?¾·È!þé™’Ž 7Ä78¸ÿxõ#±Ò¨é~¬C¤“/~ÿöÞЪâøßG³6x¨ €Ø@QP•D‰ØbO°Æ’_Ô(˜¿ÆÄ.¶o°&ö^bI¢¦XÀ.E£¢‚Š ŠJ±6z}÷ÿ™™ÝÙsïƒÇ Èåììì”ÝÙÙ9çîÛ³KT•õSIr-åþ²—Ÿþ: št’µ½n® ÃU¼‘t÷vp˜v#ýçJÇw¬kظ!X¸‹˜\o$“‚ÑÕØØnGl’Õ¤qcl¶ÛÉýô§{c‡õWE –hÑšeúQzÀ¸ŸccºRé’“¸†ÊáÙIú'`#iú„I7¨jÛnSüèÞ<ðjð}›RÉUrûí»¯k…M¤««7ä´Uu+×mûn¾#IWªß¹ýØßðóQýÅBæ )ÐbÕï‹Á¬´$î¼ ÎÃgUm˜ìª+ò†Ãë­·>lq »ûî»ÝW_Ãe¡ýª @ejq%ýX=宺êJ×½{O×XûgŸŸº×¸HúçÛieM¤Á¹¿ö*¨³~ßîZÚÏÊ¢i00ð†Øëã“4¹DáÒh¿VYmÅyz3ú §M|}Ä&iݦµÛ¤][!ÔBäzõêÅåt3öC†µx*ޤ½ê* lëÎÝO÷öcAö&e´Q;]Õ-[bµÍ‚~F"GÇÕË…TxŒ&Ú&-®ÔÿEZK *Þ¨ ÉJn™ØŸôfýÙþÙÿüȳ#NQyü‘%rüa+äøŸ>œtà&Z¼2=ÿôÕˆ?_A‹´ Ò8É-©ÿW·” ¡/qr-]GõK ¢ŸÔsàó±Í\ñÕ-Z2¨·¥¡e²ÿÊòþÃ}ŠÎùjêWÜUGy$§túÒÔ§ø1LÎCSííÓÚ~ä÷/Xh{ÿx§Ÿ}ÖYÜu´ÅÄ~ûï·Ê=ÿtŽAÓbü£?¶Ûë‰'csþ/øuú4öüf§ëž{îÆô£1.J®e ‰‰S§|ÍeGöÅøÁx¡ßßa‹ìµæ}¡ÊµjYÍ4‹ú}aHVæçú£øà'eh €F‰¤Ô)G}´;÷ÜsÙ>´bϦà€"Ó?5ø)·×^{Cá3Þ‡¦}ûM±‡Ïw÷]w†ï%U­%ÊÓ”—ŒTê£ÁƒòÇ’ƒã-Ìø{ιî²Ë.CŽ.ÑòÅ“%‡ìäɇb¼ ìåÓE„T SºhƒH“ÇÀTiÕ…ƒîE*䙜\È—!Ùr‹-Ý›#ßàýŽh/¤çŸ;­Oã}ho#Z¥uûm·ó^6$S5ŠÊɵ•cˆ Ë…Ýž{šþÙýƒ½’Ú·oSäFà4·»˜QúŸ@pÉh`|‡Ô?•ô“fÓ&¦¦[¬‘Ôy ­’ÿv‘Y±ÿ+H®—~Õíë$õ¥ ]¢Ÿ¾Ó¦k½u×÷­IÛO«yôš‰“ôüh÷×þ˜0 þ ð õéc?àr*líÿØL0ùûÆWVÿÏïŸÒCÔS=ö˜ÛgŸ>Ôm®÷î½±ÐâjîÀÁÿÈ‹´¦\Á3>%¸«sÕÏÿèÔñpAi±ýí7mŠŸÇIèÛm·}Èo¸á†îÔS‹¯OeÜ›#ßt[bÏa]€ñÑGòGüý1yD1±!ÆOЉcÇp‹¨ÆµÙP+¾ÊØ¿´žºX/íDJi ]pdqRç6ÅäH÷i™ñá'Â÷ Vµü„&…¥ œè¥‘èŒØLú”SNv;vßÑO !<5€|VÍ7–MzȵęD¦êgE1+f4iòúëo¸ÓO;¹ñ ®4h³Q¶mÛV¸ùàÃqn>ß™3{Ž›;g6·NŸÌÍž;‡á3ð)—ÔD$J Á—´Ÿ@’®4šK3W|Å).šÚÉ£°ôš6.êÿè£Q!±—­·ærì=Ä“B$¥G¯ž<F‰c¯t'Ÿz !‘C¬bëM7Û ¨*Ð~ûEýûfiµŸrµ6šÖ¢ßÓfýêU”F[yóÿ_œÿeû«Õï46Müß8ûŸÚ-û_q¬äø#Ð÷•)þÒÑCâËšR[éÊ»7>ÿý{¦¼}(!Zíñ›lŒUèýÖ¨·øqÓÇ2Ó½ý÷å—¼N‡A åk ô/Å÷ßPQ®°¶e1í_õ·ÙxßO%èL¯üç>EÉKÿ¥>•«?x†rlQô¹ørüá›§Ž6·}±Hÿ[í/¶T»Qjí&¦ÔñGFVʺÚÿ?ÿ¡I¡}YöÃuüóQ·ÆØšEÕ°F•ºôõ‹"UöÝ?±(7Ú€Òþ¶›ø±€ØùÍ7òU ©ýwÙy§`·¯¿B9ØÚÇZ,$Áo:ºèk𢋶Q¦ÖµYtüýúr4™ˆa{°Ñ"£‹áÉ=è_gœNFÆþðÃÙ¦áAä è9ôÒË/ `šáÜŒ~è"ª ]_}%ËYù¥žÄ£ ¨_f«D73 ™‚!UýM±ZGž»m·ÝÖ ¸ä|F¶%Wv(§SÊTÿÎ½à ¾M÷Üu§[ ûö¬¶újØ¿gu·Új€±ŸËjñpÆ:)Ô©Ÿ”!´þþ9  ‰îѤú#Ú¬ö°íçU)¡Ù%þÄÚpß}÷¹n¸!0ñr8&RHkœìEM#ö¡Ã†K©w|«ÿe~ Q%>ùm³Ž› µá¥©Ü7¡²0E$ó¥Q?!´Ýšjû[o$Ý êáÇq;‚(V?7€¥—Ü1ǃϩ:¸Žð¡ Î?Ÿ±é­númû™lVóµ×š°£ä“.& Á¹]vÞ90ýýïgœ"¨þ8B’O\]ºnÃåÿýï+îjœâÆc0^ÿWþ;W±—èëˆôÉþ4 øÌ3O{~ÑDìÏŒ¥ÜÒimöʃ&‹!8ë'+¨ýÔï5Uÿ_œÿ)?ɲþÏy þj‘Ùþl µŸÚ]Ólï5æùË:øµŸ7¦÷0‰G>éÁ©HQ~µ»¦ÙþÞÊÙþ©ÿÀ,Þ2 ¨ÿ°K· €ñ´Äùùúþ8.ï_KÐ~ÿž§ãNSÝwèωªÙZwß}›ÍÚÿ¦›o…-Kno|ÞÞl-]ù¼úµñò\NéuOæW–Å‹RçëA “”ÜûíÏG4Òó‘fUÿþ·‡XÇôÓ±áÔ‘na¬ð艙Ãìò.»à Ýl<Ÿý¨&Úé‚‹.ÄD̽ QlÐ,«~d(uFS)G}½(®9n±BAÅ“°wgì‹Ôs§^nðàAXÍ4O qŸ>c¦{êé§D?òíÚµeÿ[nT$ß|ÓnæÌ™L3kÖL|²ôº›ê7 ÄimŒ'Êî§þ™ÇüÔ?'b¥Ò9çžã¥øþH…7+Î’Bó´ûB*Å%·ù–r¼"±ßxÃn†¯­lM¾¿Ô IÛªÄ+ºô3+òà .¸KýàKËÀþ:v”ª¢ã®ýóuŽlF6þ…Ùw¨ÞGš{Ûü î‰'Ÿp5ð+Ú4úFLÚÑ 5²Ø{ìázaÓ3ºšatºÈŸq‚6[GŸBØÇŸ|ìNÄX8÷œs¸ÜÞ~yd_"Á…O79Ö½úÚ«®ˆ Ø úh|Gþïÿ›Ë¸Ó˜nñöWZ–‹›È÷Ì"-dÔþQªÒ{®e`îr_ƒ¬Ÿ A¶ŽVËgû³‹dÿc3Ø›zŠ>w4ø'`ÂÍ{R¡ü„Èã­€[´ŠØË[-û(¹ÔRêwš ì–ýMÁ„›÷$Æ?÷³¤}2é?á.ÿ+»ÇO˜È¸ñ(›?ß¿Âÿˆ–pDOðÂù ØÆß~ó%çé0 ÂÏ_0:J®ñjÝgœÁrèîÆ^8¡ÇÍÇûÈYgžå†Âué׿ÿì«ÔCêÕÚ_¾×VñßÔîÿ»3¸¯üÓ@wϽøq‹ó¼é}þìߟå†PŸ¢ ýúšÂ-ÀÍ·„ûœn±¥([IÚ*oê¯qGS¡Ak}WôöSýè÷çq¿ú·ª+þÀ|Ã7¸ñ'aUË(÷ö[oñ6)o½õ6~`Ù_ÚÚnM¥xåi?Õ—üo„ñø71Ä¿y ä÷û7_}í&Œ§2‰ 8¾9×_ËàDqts ¿ýÿî®Ào1ýªƒbáqÇǦX sÛuÛŽýŸsúb"]W"&ÞƒÉUœææ"ΞuÖ™?XÀ‰ýú÷óã…½ô)sÊíûfŽXd.sxÀ3Ï<“ú©Ô»wï@c(Jóç/ +‘ç•zPŽ«§ãµ«q”¶à¥¬ºº¥äÑs˜åfzêÅÉSp¬·¹Hñõïßßœãf54ø°gÓÿ²ï‘iÊè8v­Ûe—]´LŸ6­Ô§ŽA'/¢ÐGÇáQU?&·JøÄÌóˆ~mžA–ž~Çi5‹,d’x èW]ØH®´M×.¾R×Ã8"ȶú.XXêŠãÖ•—Râ§¾!ŸŠ1ß\îŸVIûµ¿¨ý öaO¬»Êæþ¡_ÄI$ õS`áÂrœ;ì-6®*µÙuóö׺Y]#ßÉõ}Ò´Y +¦6u×oåjl:~üøD—¶“Ò—^HG¿ûn‰Ž‰'<‚/§B+6nÖ¼Yé£?ô2`kÐó?ðXÿ¯nljµõÉ'Ÿx´ýA†—Õ ¼uµÿâÚ*l­ú3LfsY¿XŽmb  ê¡EjCÀ0Íeû‹éØ&Ö0Æ¢†¢ [Ž0B&·¹lc]k˜2Ã.²ÐPº Û°ÖÈþ'®Ã6±†1e(ʰå#$€`r›[ö·ÏõJÏzÅýõ¾¿ýÄSéýCß§T¦ò×à‹RÏ^=Ãû½«Xú¾}û‚la4Ihh‚~%ZíWY’] @Ö Xk`ò°„ÏŒð®ß?µï)¥>•>²\©ÅÓœ¡ `²ý ö_Úþÿ—?ãÈtý­àSÿEü¨Q#¿·ïÿ¶­µµŸâß}&&.À\ÄìG¿á±—P$ó_ÿþWâîß|«ãG~Ÿ5oÖ4ð}YL\ÆýŸŒî0ì°\Ç_á{)ùµ £ÐdïD–jܘ6b– há¹2%>¡Œ ès¬aÆñ8!,¹)“§¸Ž8•ì&,ózã7€†Tü߸A#!ñ÷¦~EF£ÆÀ{`ÀP• úWÃ÷ÒDo/Ú§G—ãýþ¬ßsϓҦ˜5|øá¸?üá®C»M™eÌØ1¼5µ•NÜzö¹gù3Ê«àfÍpü#t4n¤uýDê ú™5ÜÔ®"‘РŬ;ôÐCÝ”)SÝùXƒ æœ8q"NÃ7áÐPSÓ®¸â w}²ÄWª¿>}{òÉA®ß¬ŒHˆŸ4Ñ2DÚO‰`:þ|ذ¡n§v+€0AǧÆÝxÓMX]„þñmiÈm Ãˉõ6Hõ+n´ÿÑ“Øï©Ï>ûø’þJ6‘õÓ 3>ªKÔNÎmÝek÷,ƒU¹óÎ?O·‹µ©»~jȉgÈg7Þxc÷øã»–ºû¼WB¾2i’ìçDÕÛbóÍNF'Š¥Ç%wìqÇb?¤°©t[ßF C†FXè£MÏ;uêèn¼ñ&÷æ ¸ ¯‘·5Uéšk¯q `_•*Vaãñæîì?œíž@=©zô=,ù?•suù†LÅkÑí,JŒ—í¥’¨féÙ?‡RioÖŸí/¾ý/Ž8'1—Ç_Ž9þ/ž~PÛgŸ>WV½)ý+¾R¹$Õ•òÄÑ|íµÝ3O?ôG·›>mzxÿ¢ÕØwÝuÈü–£Ä%ª[¤ÉtË•ãÏwÖYgm÷ô3ϸ£±šú‹ú”.z§Ö>•ßHú^ës@Ѐ«|¥›ÇNazËÁÿ„¯òLQ¦zŒÏÆò¬?Ú"í‡lkìÉHÒLˆ;0þ§q”ãD“SÔQ<ÆgcyŽ¿Ñ6ÞÀ—’‚OÒLŽ?9þäø›Ÿ?ˆ.óóWñÑ¡Òc|6–çço´E~þZ ä÷Äô”á‹ýE3Àä÷ÿ“]êy½¡|LFËI㬢6¾4•2¾+!#D3)÷œŒ@±æ%µw!ÌúÉÙþÙÿt`ÁòøóQD£‡Ø†ïj&.B&ÇH%Q‹åøK¾‘Ÿ?ê’Ú»wOùù“Ÿ?ììùù“Ž$° [HÍÄ$Èäç$’¨åØZ0æ%µwÏÆöÌñ'ÇXùý7Çß4jäø+cƒï:LØDÈ,“çßcHB´¹“ríF[…0ü…Ô¢«/£|(€Ç™|à+e$‘õgûgÿËã1#‰QCrüáØ ¦ðö1±“A“'ºJW‰Eäø›ãoŽ¿9þ"päø[ˆžùù“Ÿ?~õhxd ÿþaS{FOÈ–‘XD~ÿÈïùýcY¾˜ÊhàIÜb|Á(2}àݘ(ò†‡ áš3dÉBˆTQ¨¶ $f åáݸL(éžõû‡™4ÛŸ¬€‹þ%Wö?±‡7‡IâbdÁgÈñÇ8‹üÌñ7?8lÄØ‘Ÿ¿ùù"e~ÿð!3¿ä÷/q…üþIvÂJâ3„‘ QŸÉï_d‘Ôfœ£)Ú.?óó7xÊÿðü•OÉÌH BÉu‰µRf9Kò˜!äü¹m( BE6JDެ?Ûß{¸{NѯÄa¢ïÿÉþ{käñÇþãx„WIÇ ´süõ"®‘ã/Y€Mãj~þQbì–)ØIÞQsüû$W\ÊØ,JWŽ?9þˆ#„ŸJ9þâJŽ¿)ÂoçyÄ”“üü!»Ôýù‹gŸý¡$=®†-LãœMSFn±,Bá/+6ˆ¬?Û?û_qØ„qgFJ­4‹¥Ì_&Ä òøËã/¿ZÇ–)µÒäñW»•¸¤¬Ø rüÉñ'ÇŸZc‹)µÒäøS»•¸¤¬Ø rüÉñ'ÇŸZc‹)µÒäøS»•¸¤¬Ø V°ø'†lw›ú2º˜´åå˜@•0…g}Å2‚ŠBŠù@]^PŽ ÄQÖŸíŸý/ü}!Ž ‚Šƒ¨˜Ôåå˜@åñ—Ç_yüÅ ` b)æiyA9&GD9þäø“ãOŽ?1(¨DŠù@Z^PŽ ÄQŽ?9þäø“ãO *‘b>–”cq@´¸øcö1»öÍbÙ¼äâ=œœäùÀ­¬LäÙE(ehÓ0¹hP(ìQY? Â6Q#fû«³I€ìÁ2–òøÓ¡Ã†ÑÃ)ÝrüQ“äø›Ÿ?ê 6ˆ0NQ~þˆiÔÁPÈÏŸ`ñ¥üüI\E§tËÏ5I~þäçú‚ "ŒÓA”Ÿ?bµG0”òó'XD|)?WÑÆ)Ýêþü‘CÄ“H” Šˆ2 ½R_@eijä¤(“3 !¯¨²45RR”ÉÐ×TYš))Êä hÈëª,M”er4äõU–¦FJŠ29òú*KS#%E™œ yý@•¥©‘’¢L΀†¼~ ÊÒÔHIQ&g@C^?Peij¤¤(“3 !¯¨²45RR”ÉÐ×TYš))Êä hÈëª,M”er4äõU–¦FJŠ29òú*KS#%E™œ yý@•¥©‘’¢L΀†¼~ ÊÒÔHIQ&g@C^?Peij¤¤(“3 !¯¨²45RR”ÉÐ×TYš))Êä hÈëª,M”er4äõU–¦FJŠ29òú*KS#%E™œ yý@•¥©‘’¢L΀†¼~ ÊÒÔHIQ&g@C^?Peij¤¤(“3 !¯¨²45RR”ÉÐ×TYš))Êä hÈëª,M”er4äõU–¦FJŠ29òú*KS#%E™œ yý@•¥©‘’¢L΀†¼~ ÊÒÔHIQ&g@C^?Peij¤¤(“3 !¯¨²45RR”ÉÐ×TYš))Êä hÈëª,MY1g€|Qaæfˆ™®"-ãµå @@EŒ'SYš²ºeý©IS㥯…ÙþÁN±’àÔ–šfÿóÈã/u äÔyÒ‚<þØ.jœ‚ŸD @ÆJ‚S_Ò4ÇŸØ9þ¦C9‘¤à&—/á$P¡Èâ‰2–uèÐÁ7Ψ÷õ@IDATÙ·¯»ëî»EÌbîÀÿª1ª*/+ý Ö¸iÓ¾u7qMñC¶¶kYé¯MáÖ,tÓ¾™î7iäÖjÚTúü{fÿEµß–}ö_œþš… Ý·ê;k5Å  µ«‡*VlaâÞkÏ=ÝSO=Å<{4h0`«Çê°xb±e,¢N·O8ÉÝ|ËM®euµ›üÅ,fqãÿÄ“Nt7ß|3&VªÝŸ‡ƒUýôk%7€ÅéÏaMÔT¹”žuÖYîò˯Dĸü›c×mºº‘oŽd”³´õ³‚ÅܲþèFÙþÞÖ3ÙA‚— ÄÀÅxZåâÀ Û?Ûÿyÿ¬ìa‹Æ.mÿ›9s¦›€wð 7jíÖ^»ù¢•£TõO?Á­¾æ®e‹žÇ—p¢TTdñ&ï¹–4 ’äñ—ÇßÊ>þ²ÿ/™Väñ?aÂD·Æêk¸-×G£Ô3WÍøg6Ÿö†àD6F»ãö;\Ÿ}öÅ¿>îòË.ó?7„Žöë‹ ™>}ú¸}ð綾‡E![ÒÅ)ýí:^ž›íî5úBŸãÄnÌ©DŽäu_2b®Ñ_@ªF.'ý¢N•¢ .Gý¯¿þª[o½õÜFmøèJËÛÿÚk#Üz믋ºmä‡ ƒnŒ=Kçsœ,Yÿ‹¹ÕèåúMIÖO¦&5ömúg½õ¹Ä@ËÖþë®·Žö¶ëÒ¥+×h™÷?Ov©–´ýR þGŽ£ÿ<$Þ_–!üXöñOõï»ï¾î’K¸K\â.¹ô×i³N\C+¥•à0ý/ͨÐ~.ðxÀ¢ÇK(knb¹·_Ú*šõs—,?ÿËö÷СíσQÍAÖñÑ#Ç"FÖhA.þùûăÜ6Ûlƒ?6u[l¹¥[wݵÝøC7x0þÈ¢/øßœ9sÜi¿ý­kµAµkÛvWݲ¥ëÐa3wå•W†ž©«~ûLuª¢,è­DÁÿØþU]ÿE p­ZµâÕH7@Ÿj¾V·Ú`Çi«j÷È£’éùÊã̰|ýïsü!òÞ{ïu½zõr ¯úõ?}±þÿùçʳ÷k¬ø–aå{P2ܧ–!VÉ÷Ÿ/ÈÖ÷ÀÖ;Án¼RþôŽÈXEÿŸ3g®û-ÅÄV¸M6AL¬né6ëÐÑ H1‘.á hU²#j0]ôúˆE’ÁK‰$G(XäÞ¿Þç®ýóu®Q#a÷чFzZŽ.]qàÓPB–ey¸1R•õ‡ïKH°²¨ÈÖåŸòeRºœôûFHSü+ðèŸ}:ªåíêÓ`5wøS"êgÿº¶*>÷£:Ì ºÑµœõ×í_ÑõOAÿОù|CáЈÐÎGß¼)]ŒÿÝxýMnÇî=Ü:ë¬í>øÖX)þ,MýT;®!ÝêèžÛ»ôÚ_WýKÚþð0¤4š©¶#^{ÍÁŠOnµA»¬Ží_Rý¤‚¯Åôÿ²j¿ª_Ñâ_¶¿ï™ìDòøóÁT‚BSùûgŽ?!r°½4wÝu×¹SO=5˜‡V´Nž<™?#Þk¯½ÜŸñžþ›ßœ ±/ñMŸ6Ííó³}Ü‹øœšŸ~HŽ÷¡ëß¿¿{gô;îÖ[n…LѲ,Ÿÿ9þÃÆlæúûÿŒ™ÓÜ”)“± GxêR …3gÌ2U—ãÏÿl[dX?bÄÆŒCO™:Ùm·ÝönâĉáŒL?i§žV;$Æ?zßnûíÜD¬^áôÝ8ñÓIA3ë;2µé×:J9UÝ÷°ÿ§L™ [Ãn°µ\b¸I“>v+´Ÿ¾°Ù‹^tûâ#’1·qÆýÝ{ïŽv7ߊ˜H¸;r^…)Þ”jߘøK,+“ýi ¾ì¤£ð€ ÿ¡rJ¦Ï˜î†Yˆqü±ÇL€ãqà!&ÉzòÔ“g¬¤ŸåšÈguXz¦ šXõôOýòKã¸+Vû¿üò+v‡ÐKÐÿï¾û.è»Ü»Ÿ{Ø á’ï·ÿ½ÿÞ{îÝ÷ÞMÆØâüÿ½wßsïO/¶š¬`ÿ/Ùw´P‡®ä±™ŠX"ý‹ÿë®·.¿Œ{ܯ\óf²üݶGêºtõ× ,—n…öS+é·ñh¢%RúbÙŠô%Ÿê#6¥šâªcû‰tYôÖO–%BGx§ªäL³ Æ_¶?›?Û?ûßJ7þè™þ[L ѵ÷Þ{»Ï?ûÜÑ_É?ûüs·/VîÓuò)§¸±cÆòÐ 7Ýä^*“B7ž;wž›†?ÔzŠÈºã¶;Ü3ÏùýÓÑýøÇ{¸ƒ:Ø5lÔeiûIî5W_ãf£Ž9ö,]ÝÀ}ŠÙØ;î¸Cú6èŽþÙï€ýÜvݶs/ þï½7ÚÍš1Û=û^8¸jUîRÔ Û»R…Ž8Bë–úßá‡æxàAæ=ëÌ3Ý¥—^&õ×»s.šQbvÀ‹°?ùmJüø“Oº÷ß—yvî¼¹k ›Ë"ëì³Ï†_Ît×^{Ã^í<›ü ¾ë§~:l¨£>Ù±ûŽîhøØú-×C}¤ÿ_{õ÷Ôà§Ùÿ·§æÖ\cM©9ôRÿ?úÈ#Ž&¾Ö[}wÒ‰'¢LôÙ¶mxð.¢=d.½ôR‘Aw!ç¼å¡¾~àðà:ëLð\v©øcpƒþ±I1éfßyöY˜ Âð?ùy¦mÿ¿@ÿ´mËxqõÕW³=Ž=æ8·Á†ðŒúìôõ¯»Ùsgñ8Ýÿ€àÛÂ_æ¸k¯»ÆÑXd’lû=öà—’« Ò¶,\¸/>Ï»7Þ„Ž ï:wêì~„rÐA¹F ±zÑ+Ï‚óÙg^xñE÷ ÆÏvݶw‡z°[kM²½sÞþ̃Ûì±ôÀ÷»_xcî·=þÊsÐÁ¡¿d¬‘ QÝ<7eäªÅþ¤§’ÿ]Íãg¦;ú˜c܆nè>#»aüЧ|sç"†l¿½;ñ­[·nA¿ñÆ»¿=ô{û}öégnãÛ¸>ûöáÏ|7‘X¥íW’~}©¯B߯rˆ6íõµWÝ`<ü †?3Xs͵ mÉ=Šeè4)¹Þºëº“Núµ¯W¨^™ý­~êëE?®SZ1¶V¶qâ·¨¼i¿i,Š¥Cc•²~ÿmϰ-—Ðÿ³ý½²ÿa˜{oŠƒ-¿:ÄŸgž–Éò¤{î¾ïô¾áøÝåþûpí6mç¦Lžâ.¼ø"”ßÅñwÞü¹ø4âO ªrýN?Ípüñlÿ&xv\sÍÕî5¼_>ÌýéOÿ‡÷¿›˜ãߊÿZ·níZc_)ÓYìô ûË_nà?ä7kÖÔ=ôàCnµÕšø2%AàÎãOL· ãmíñ> Ó>¢ßc±oox/£î(è_ý²&wUÉ­ú¦~MH©CýJ;dè‹¥aDZNt(óhÙíwÜøÚwØ”u`¯#“zOš4)èÇlmé·ß¡F¾¿üùÏ,Û÷6×õ ®Ûm×­4õË)VLÙßü F݆2µEøØýJwÞq{䳩6úmÁjÿÏ?ÿõî(íå_UU"ßÃØ8·t7l«×‘G•èuaz­›Øÿ¹gŸU¶’ôOçвƒ¶ŸàÞ?Ú½„bBÏfã›Ð£œûgú§Ð~’ºÌwÔQZ·òþ·ºHƳ¦nâ{5%C¶9ÓúöÏš=Û{¦©ZŒ”š{ûc®ô³ŸýÌûVÚ~ªÿCŸ“~ª&ÁBûOïwz°´Eúã6­Ko¿õVPlý ŸÓ¼˜ôa=;vTR®,çÇŒ›ô?õ Ù@/é¹ ®¦„`XÖ&Læ‘“)§˜( m(ö±ýÏ=ÿ,óú'¶q% Çù±€º[À~êãÇOv#=Ä+tâ×ßp}ðVâÛϽC:—µ_eôF ™Æ>ÛõùgŸ–vÛm·P?«AãzT·ª5þþxvÝuפNÚ~Š$£ºUK¦Öö«Ñõ‹u´Ô¤ÌH1DÚ¥«¥aƇ¼µÿC¢Ì¿\‡XåýŸù½ ‚»m¿]é˩֯"i?èç?‡ŽªR×.]Ëú_k÷ç¿P,”>™:u*Ð\Y-.ýâpñQê‹´D[kuZ8ˆð"k)#‰o´0›ðJ¸>Z Ñ&tFsD':RЬßÚ­`e.’òBI¶?»‘Ø&:Zê[$û_Ãx«%¶)x›Wl\(Y%ýï˜cåç>›¨Øþp9V{yqˆUh̘˜å .t?øáÜ×_íÞ~çw –ºM˜8»¦¯Îåt£¥Ã믻>‹ê™§Ý[8%Ž®ÓÛwô]ô¹6þǤ—Ñíñ'ž@ÿ¼Ïxš¹Ã°º¤Û¶ÛbÑw;–¿þúîÙgžs×\uûÃÿPVwâôáGÜÀ+¯æ².]·q?FÿÔ@ßСCqô8úí§ Ë×ņӤŸ–¶Žéëvšn¦ÞRåÚ´¦º‘Ñ¢ÿÍž9‡qjJªkc,ÍŒVôUó¡ObQàÄÌ9‰öÒË.ï>ÆDÿï׿vGüòp÷ê+¯¹sÎ={!MÃRÜf˜I>¤à‚\ZRᯫ¯¼Š—‡uô‘®}»Má7ObÕÅ`ôÏ$÷+ü5î¥á/1%™žÛE©á×6I1Õ™ ÂÆö#ãfÏž•4¥)lÐ6Ћù™W¸‰GȈJKj?üp^…F+ˆnC y1äY¬v»öøèÙðQ\Tã_p"V)>¨ŠWØÑ÷7{ælðÜÆßl£€ }ëXÿ 8­ì¬¢ ­Ê;â¿ÏLwê7q’ý^9¶Ÿåà&ö»È”–$FÄL"tTö0VŠQ !L—.Û †ôvc•ìöÃ]wG…–VŠýæÚ+BbU?رúÿÿ›{rÐSl‡_ö= ±ê1¯2Õ_£s²tÄ@¡ÿCµ¼.Ÿ@d)žj$µ"N¹$/÷:·¬F< ªÔÿ¾ ±]Ö/v×{¶?Y"ûY!?ñö†òü$ÚJ=‡Râ"¦ôùËh(>&w’ —Å./û¯³öÚŸé= “8nµ×áʨþ¶XÙKÏÚ/pêWS]‹õZ`E¼>Çð¼Ùº ·ô•WþË+n·Ç*Õž=zúW†’›ôéçx/\;6pk¿7='ß…ýWtýýÏ8ƒûïŸüÄýôg?‹ýÈÎ,Yñ••ÓÿWtû³O–Õo¥Á§Ûx J"¦òøß¿„j¥o¿o)µ£.í"¡TzµÅŠÉ½(ƒb;L¹©xÂD9MöFÆN™á•W^Åûu#·ö*êѽ'ãˆöS¬ÒoÞq°½©Ÿ•SÍHO§ÅTFšÊûßÖ=J n/Xâ& ç$£>ÜåÅTVÐ_>Ç1gžùÿu '•úÞá‹ð‰ý÷ÛaLžpJ4¼bȳŽ7®tÃõ×—fΚ)scÀ×à?|V¢¿dPûÛlÜe„׎l#µ,Ýÿ_™†)<¡¥ßtÓM™¶oß_2 ­ê ™BâÇDBiä(?èõcc¯ ¿g¯ž¨Ï¬D¿þµ…øe‘´‰F­~`‘¿¡n÷›jùZù„ ì ø´¥J˜b¹îh?¾ öÜD'´sçÌ-álR_"’ÒšÒï~÷»`7ÏYMØ8¼D+5fÍœ‘È›MýÓ\VV´Áê‘.ˆmàÛO0Ù@õ³2O ô»3P7´‹ì&—ÚZ)—”-\¸°´ß~û†6]{͵L œ)µÇ Qª©¦„#¼¹>fï/¾ø¢ s&ì£+†Ðþíñ׺qãÆ…ú`¢īÐ>² õ]ºƒpS*¬ìøÅ‡3=õ{¼b#Hnbƒk¯Mú‹x„Úó »Éø$›_ "*¶_ô –}í¢zªD-ù¨‚íF´úOÆiU1VÿäÉ_ø âDz‰¢qãÆ•®¿þ/%éPxý³æÌá±MºÛ`£ kJƒ õ¡•`sA§×´o¾-í´ËN\olÔ„ ôdäÙgŸNm eß~ûmiçwâræ1íW¹”ª~Æy[®xj?ù?ÛŒbþ=€øVÎ"l,‰X%c±gÏž%^õô•J«¼¼‘#ß•aD+†\i¬²ö×ZSz¯ž¤úT•âŠ!mA üÚû(þrEWA…Ï{l±0Š©U9‹Åìói¢’³~¶D-ÆQ+¡¸¶þ÷œJ‰Ôböù4 ô‚®¥P©Pœõ{±MN­"æÒ2Êìói"¤¡´–B¥BqªÉÓ°B®e”#ØçÓDHCi-…J…â¬ßÛˆm¢pj1—–QŽ`Ÿ÷ÉíwÞž#—]vYiî¼yŸ …çÅ}|¢LBJW\~?o:à}™®ëþ|mxþâ„%üñ‘ã?ñ<õô`¦‘[¹~_@#y-*ðiK•0ÅF†±Ð~å´EiYHWýÿ}ù¿¡ÿñLh>™0µ´Z1Å ƒ–QŽ`ŸO! ¥µ*ŠSMž¾€r-£Á>Ÿ&BJk)T*¯ú±ûçCö5Kk%HßÎÐ*#|A‚qyÈÁ²ÊÈ–‰ä=¶X(™(Õ¤„)VȵL¥§²m©Rh-´«TmHÁ”jR))VèµL¥û|šXÑ€Ë ÙÖxǦ•VTœjª)]ñWpmb¢¬œ§÷ò'8&~ÆÒSO=UI×A4—ë$ ‚~)Ok¥8IéNrSÙ>H$ŸÒ„BPœjR))VÈ¥ []QûáyáF³GzÆ_šgcÿ˜Ã?”ÿèü7ì-ôè?ÿÉɧ•"0$_œx¸}ûöî$¬ÂÐ=Uæã/ýŒý{Í<ç¶À7“4E@»®ã¡FýÀür‡8üãÿp‡v8Ê<…Õ#*q±Wü6ßÃg$¼R‰V Áê™ du «r/¿ü²œŒýWaÕÇj«¯¡Ò¹´GŒê§#ÖE¸è'5üGwÌüëv×BªU±r\N7òÀ0ßKí#_„•Ó}Ûm¶eý¯¾ú*oÎûÆobÿZ‹ã\|'LÇ‘*§ðK tE'iQû¶›º_c¿5x?‘’[°`VXå=‡¶è¼óNœø©›‡ I6×Ú@ÊÑ~ÊKÿæëãkU¬KòÍË¡[-íŠ@Ò«¸ÁÊ$ªm¬}Ê©§°´¢ É/^Ñþ´‚‰ª²Í64[ë¶óλpžÚýñGÖöÓ»–Ü Ø¨}ûöLG¨ ºßÑ_jüõölâ |ÍzÈ´¿Ä©¯„rˆÜ‡yÔ½$¿ürª;›JÆRᓼÇ"!»=üðÃ<¾œ »§Øÿ•ìOí²#¬í¥$L§È+',eúK%,a’cýßö?Ù÷׿þŽö¼!:Z-ôÁcÝóXmØysÄ\tÁ¼ysUÁwŸeÝh¤&«­òM×nî¶ÞrkÑG¼ÿ=ƒUGz]uâAâ‘F4oÞÜmµÕÖÈ–·_y(jßðÅ´_Ú &èÿÇßÿáE|«­ý/¿ü bNþ+Q·Õו‚ªâè£&õ\‰U3Îß(–} n•üO°T+\Ôÿ ªB#-7Bâ£-e¼”ò• ”Üë⬺’~µè¬?1­7¹·JÑ8ž4Û_<ÍAÎäÇ¿`Ù»ÄǬ£114ûŸ5KÑÅ$ï±ÅB¶¡˜{qñ‡uXEÙþì{Eÿ;ôàƒÃêoÚs°E‹õ>—À˰—žœðéÍîj°w]Sðî@N{RúìÓÏsJæ5j¤£g>ÿ§Nù’XüEšýߺeÑÅ%ï±ÅBoÅåiHºèwœN*X^úýÃH”š{ÖŸ4ì"ôþËEEö®ÚŸ?ùýKlSËty![7*©äSqŠÑ´ð{´éÆûôš; _Þ4o¾Ž¸3Þ¿§LEü,Wá{’Z ™¢²~)ªCÿC6·¥‚®¥ðÐõ¢I»—ïåqBŸ•Ô`â€6~¦ÏfÈht‡…賩5%)3»G?¶ºï¸£kÒ¸‰Ãª,mü©{›*Ó´o¦6ú} m\{à"'Wø´ud#iñ1IÿŸqÉ&]ðµw›û±Y%÷ÆÈ7=Éí·ß~®U«Vø·oÜ·Au+þäMåOœ8!j O¿Šu DµV?˦æ&¦Ší?ï‚óÂg;WâS:~o}lúúsØß»¯¿úºÐþ¨´h-©M?÷&Çvܱú§1VÇýƒI)¹Jî|òC—ÚŸÚO×…þlå»è÷ Ö¶×ÒþÔ.ЋIŽŽ›mÆ6`éæ«PYY¬è§éCÖý½éó\ƒž„ ¥kdBøýó_Œ'zÌ&3,íÇ]„òN4áé¯?§ ¯/{RýLàå‘9øw7…«S§Í:à³/y©[D'Maß"ð¬ÇúElÚþ@Œ UP‹iÿ“°¨¿ªN~@ôEý:É J¤´Rû'#†\Ò'kÐfõ;"†üô§Ž&NUªÄçFŽ’É8:ºw3øŒ^A?uTÑCB¬±¬x:vTEY9¥¶WnBVk&è'Ë‘~PpÀþ&¾‰ÒbûßĆÛÌ ýûﻯÄ*ĨjŽYˆUØà/MÀYmËà†Õ¡ÿki?Éæøa$oI.ÑOµë§Òbû §×Ò²?×Ú˜´³²ýU7¥Y¿ïu$²æY$LôbáÜÿl»ìêÞoòø#CÔ5þ­CÞ~ûmwòÉò‡²éÓ¦ó!*ŽûÈõ=²¯Ã*î`ßöíÛóx­nYͶžú&}`î#êËy¬¸ÅvñQöŒÀ­eË– †4Ç?õx(ØfqÑ/ø÷õ×߸þëŸ\Ãph‹^ËK¿ê+¦Y¿é8ûvµÔ¢ãŸŽ½üþ{Áxl?5]RZVQiüiœ£³é:ªï‘œÒ¶t8eϾ‹÷ïê-¸¬>úYs¯\ï?BÅÅzbºÃ Ô t§¿ZÓ+Gs´;÷ÜsÙЇ–þuÂvÙàAƒÝ^?Ù‹YFWÞ‡¦·k×¾=ï1tç]wzr‘Ϫé&äK^E¿@7_H<$õ¬ßWÄg¼…Õ çžs®ÃR[ªº¿ªÜdÚ!ž.à&Ož,°"H¢§¥d5ìN.•DŽÚïs\·X¨ÂÅF¼ÕïÅ£€C"³™’Ýr‹-Ý›˜¼ú;NK£½žÇÞ't¤è#8ˆN¢=fhŸ&:‘ÉK%|7J XIÿà§¹½öüIà¥þùÑzc"ýƒ“¥îºóN/ß ŠB˜‡lP®?ˆ“æù¬°Êàd—b¼çFbÛo$ …l0ÜT?žhÉô“)a5ʯ±Oͳ|\až½Ü/pòÖ+8Eì¯÷ý•uƒ“£Öð«4´ÿµ~T‡rý„ñ×BÀøŸ?:ô(?Z÷ÀíYØ:±P8–qûÙ’TMèõՈƥ,B¿´¿äð)§o]! !2´«Ø~á ¤NðƒÁGñ¼^Û`¯±ÞX¡Ø¾ý¦Ø[g„»>Ê—çûâ‹Ï9K/»±AQ?ï”P…#„‡÷ß*è/Æ?Õ¥d"±å· 'ê'}Z¯M1Îø]mþ¯u#Q4AFzU¬…IÎêfuå! WÑþ(#ýLî%ZrßÿV'UBI¯pP¡€2M ªE¿Ê t^È"ü(²þlÿ¢ËxωþDö?µJÑ_|ô@R[ü]Ôó,ú]ÆŸµ±Ïе×]ëNÅ*j:yg͵ÖâSq›b?Áo¼‘+G{$¶À Gta[ŽÕ}(°ÚÿýøíXЧÏÒj~¾` 68튯}€…Q4ˆ(ÖùNûŸªPô¿_|Á×̹^=z 0Zuª¹Ö=p¯¤þ_©ý¡ñZè+Tû©Óøò@ì¯,Üw¸i^a/4&ÊD4hë¶µY !òþûŒçc"þX;'•5¤˜ˆ/|¢ÊILØŸ¢TÚ÷ÝþáS2´?†áQücŠ3r÷‘lhúqÖ+MØj¦œ@Ú,ù'{Ó¤s=zöÀCg,Ov Ä ú¬ûç€, ùXÙ Ké˜Á8´xtR O€„;ÈÐÓçc´±õi8œÃåW\îaC`º”¬mÛvœ'ýÂ!°§ˆ›3w6RúGkSJŸÎñ»ÿ´Q¿Ê`÷ª _û;Í£ú»LyBíP Âõ±Ü÷Ä“NrÏa]ú±û Ž©¦#ËIm(ˆoMù˜øbûi•ˆ^³ç̬UÿW蟽öÜåU{—¸±ŒqØ£ÄÑ ¥S~#ý#õ­âÏ’Šu§ö‡w†b¡V Ð~êg•IÇ£ÃTh¿ŠÐþ§~úðÃ=Ú+DRlà#  ¿¬š°ÿ¾X1Æô*&„ÈohRˆ®_öíë®ûó_HPè.!LåöôÑGžÂ¹-¶ì̤£\OH¡÷á2ý/±]†œ˜táNÁKlé×þqXµôÁ8±›ç ¢‹ö¾Bî¢4ú #ƒ*ü âµXû“aÄ2Éê!/}ô'{3IòQŽ!#á£CNæÕ‹ªWcT,¥§‹h]z âÕÌ$VD‹N-è@+‹€"|_«D,‡”«lAÐÝW KòÙXî¡ZÛ¯úýXE6zXOR6i×^MÄ}O1‰âöæä§æøô ó#× èÇJ8[¨¨…6¤|ÊÆG ý/þïå á–IJnºý‚)#3úkk¿4>)…°¬Ÿ-šíŸý¡l`±wäñ·ŠÄŸŽ›uä †wÛm7G“BtÑgäº7ßbs}̹6ož¼Jñ·1Và6ĤEØ—_~Iþ´‘N â ÇŸ.þè§0ô;hë.8l> Ù²0¡ Iž°–0÷?=±Ž8þÿäÿüÖ„ŽYüû_ñý'¿yÃ/ý)P‘Å­[; Œ7Ù„A"Ð/7l,_A¡ä¥ÿ¾Þ¿[kLTI«@ü£s«Ä@d½¢Ù¢ôSX®Îø\ÛñNŽôû.” íãÃ?ÁtõÕW9úñe‡×W¼t ‚™ˆV ô{]š¨nÉKNâdxý ŸÃþEݺusôíæXyCb>ø ÷N)Sý;íÔÓ‹-ñª˜Õð—ö&MVw”Òþ"«­îSä6”Á›ê§åQgFB±0H—-VýÚ&Y7,9ÛþϾø,ñâj|:r¾)¿ï¾ûÜ 7ÜÀ d±wÞ 8Õ§B {™i+éÿ/ú‡yaÿ«pò[‡™–ÄQ{¤Dª‚éHÿ’¶¿M똗>zH™\¶ýlÏXäŽÅªÍð‰}NvþùçƒAZ´¤ú+ÙÿŒþýyÂ¥ßég°ïÜvëmî_ÿþ—‡¿ªÑg{k®¹W0ÚPë[¹ýtŠ•^[`?ºª«[’ ùzé%ÿòE9´ÿã?v/¾ø"Lâ5!Ê@t¸Ž9æXÞ?€>:ÿü ·¸ö žøü¬cGâ9b B ö¾ýÇ¡Ìåkç1…œ C- ö©R—ëÇcŽÇ?%^ˆiôQ½Ú|&¿üZ–’½tU}:GMž>õôs «~|+Èq ×·Ÿy`LâyæégûÓDÑg8€AއgÿÚÛïû&®ü¥´ÜE¤È“ìÜKÿêWåî¾ën‰Q«­Î{5¡8…=Ç8^q¬Â'½æR™ëâ3TG§NóŸ…2™éÿë· œ/ý>êõòöÑ!Üj±Üëß~¯Êè׺Û¯•ж'LÖOVÈög+¨;P&¹Ô§ÔwÊãOtr¥Íþ’Á¢iئjC1pd‡uüщœØ(•­'ýú$-Û±ûx©¦šó³¤øþuË-·p÷îýÓ½ù4VÎø[îÿrk¬ˆý/Ÿ×;G§ÌU5hĕ־Ëñ¯Ôb~ùƯ·ïÑëäýSú2Ç_²KÆ™ ÿÈâ•ü¿û>&BýÞ+>ÿo½åfÆýdïŸò~ʤW_8ê¤?PWÖÏâp[¾þçµÖåý[7®ŽiܱZO%ëÝ{÷°9¶n’Môó,€Ý¡–è¡YÄ!/R_ð¿SO=µ4wî\Æã‡pé„N`<&•8¥“wìE|Ô—ýû÷šêëbAå¡ÅI?¾­Nhñ 6Ë'y{ì±G ›++Ki—ü€öüåò‹.º°„…¡lò“K\p~éÞ{ïõò¢~mSÿ~T7¹´4Í¥X¥Õ4”2r%l¤Ë'¦õêÙƒOXÂQå`‘òé3¦•~~ œ0DõûÁX¹K¥§žÚŒo$K3gȉp3gÎ,Àét"]/¢´ýXŠœöÏñ'°ýµ­±  ÿ+¾ÿb«_ëSVÇ7ÚÕ]y¤ºÑi_`¦”ëVáÄ®±cÇååS£ŒRBæ4ú-¤¥mZ·aùgžy&Nö:¥ 4ÂáT2Ø¿Yóf¥ûÿú×Vqà$«¹Ééct’UMð½úÊ«¡ Ø/ »Ý?Yúêë¯J=öŸ¾ûe:W:•¬¦Tѳc]µv¶ÞyÌ ]‡É=õt쟃~~`iÆ 9-ôSeDJãÆi?§j]C ¨ )QM™<9´ýú §’ 2$”s ™‡ÓÂÀÈ1>ªúÉ/¦L¿~÷ÝчN+{å•W¸°ÿX©oß¾¡¬%ŸJ†J@^àA{˜ç¿¯¿°$<¿ <˜àKÚ¯ÍÒK^sš*ULÕÏ ñÃd•hWSúÁv u¸è‹$Vùb²áù\Pº÷ŠUtE>É—J—]zYà¿øâ‹1Þf•¾6­ôÂóÏ—̧˜XS %Û“êF>úäó)5ÿùÏ¿KÔn­s'*YPc4оrýZ›*U]ÚŸ´I5ÍúmWkYÃ{8”2r†Rq”Ø€J¬(ÉkNS¥JÓPÊ@È"ÅQj`*±¢$¯9M•*MC)!gˆG© ¨ÄŠ’¼æ4Uª4 ¥ „œ!R¥6 +JòšÓT©Ò4”2r†Hq”Ø€J¬(ÉkNS¥JÓPÊ@È"ÅQj`*±¢$¯9M•*MC)!gˆG© ¨ÄŠ’¼æ4Uª4 ¥ „œ!R¥6àɧœ\úéÞ{—F¿û.žq¥Òœ¹sJár|¤"‘œ¤Ÿ””gƒ‡dy™óqaÔDìšÓ4ˆL€PÊ@ÈÅQj`*±¢$¯9M•*MC)!gˆG© ¨ÄŠ¢<Ùë¶µØ/Ø ¿ÿmÈ)Ž"úŠ+.—˜ˆ÷Lñ‰Îó4&úXb¢*ÓTDji¦ŠOÓPÊ@È"ÅQj`*±¢$¯9M•*MC)!gˆ‡?¦®A ³Øp½{÷®H±`Áüð°¡£2颇 6s zµ¢~2«`*ü'ŸrJâÿJoíOãœÔ©Æ“s²Ÿ(¿”8åaЧôI¶–R?A¨íW»õÃärà` äñ«úP¬’zJÉGmû©ñ‰Y§™O>ù„ymûÕ¦Ã_z™ÉjÖ”¶ß¾[´µ‰?D‹æYFGè-¿´Þš¦µõ?5>p0r©“«L¡XM À¬_í²øçOj¹òœJJK«i¡´àÿ¡äƒ $E 2…b5M¹rÿ«]rÿ«%ØC yB.u “«L¡XM ÀeáÆ õÏyFQüϼ4Gþå—^âŠXý_ûM [„8ßï¸úþEï?}ûiê›¶ƒrµ´ÐÖRšÇ°Ob!dBžóôåIe ÁÎÇÄûúÿ·¿=50ÛþOø‘ yB.ðÊŠÕ4åZÕõ븬ôþ¥ï¿Dsÿý÷ÃUzÿT96ýë_ïÚ]Ó †UÉþlø?Ù:µwšg»Á\d±o¾ý¶Ô³GÏðþϱԼÿÚ˜X‹…½Ák)]‰ãmòó10¬\bø*aÿ â¢S«âl¸…En(¤ï•é'c|’5dè0·óN;ºÉ“§:üÅÙÝ|ÓMî7F0žìߨ!–<ý誌kY  LfrªþÆøœ‚.þÉêÓ_Ÿx’ëÓ§ãÏ:ëìPÿfÍšº‡yØóÇ?¸MÛ·çò÷ߋͨecêö;G_?ßjp¡¿qÝP×Fd™¦ À…'¬¤ÞvZ°G \U(˜ŒòˆÿP5JGè]pÞiLH'£Ñ÷DKK¯ø¿+ÜwÜá…P"RˆŸö‰yò‰Aîg}ö!²ù&MšD–7wóæÌc˜>A6|˜Ûeç‚Á¾ÀÞ":ut7QÿŒxÒ¤!¶­¤ +eXFÄGýT íÖTÛ_UÕÐ Æé_ûîCý!²?¥Sß@HvíëÆÂ¥Øm½õVnï½Â(ÒrþyçÁ·šp>ÞꦟBƒPzNd~׿_¨0ÕwöãƒǼ?ƽðüsî®»îr¿úÕqáô(æ÷BÎ9ç‡Õ^&øŸökzÿ½1n[|Ψí§>yðÁÝvݶçf {Éí¼óμòñ¿úW¨†B™^¾ýÄ 6À^;¾ì¼ó‹6P‰>/±¾öçñfvçÁn¯d°(«LÐ?ƒ=)ã2¨ÿé(xBý3oîÜH,”5nÔ˜Å-©ÿÓgšìcd(TDù½0{x†à£¸¨®X!ã0i'>úÆÀW#ÔAù¯¾êwÑ€‹ÝZØxH¾¨®gÿþîñÇg9Mš Î¦ý×\ ž‹ M²Gñ5|öÙ¿w?þˠ能D¬¿ Võk¿kªþ¯þלê7Šò BùY¨¯7a9VaψsþøÇ°ù¨lœ_râtÀgŸ}Ÿ—­.¬žWõo‚ï¨üqWÝŸ‹¡LkK6ùlõ/pˆñ÷?ðN@ìÆy¡#Ý {¶pÇ<ó-ö‹Rªu¸¶öz!³Yk‹|ZhÑþA@Ö_'ÿ ö ¶‚³ýÉ ê¿:î4Íþ罦Âó7øµÙ’â\àl ×bƒU~µ»¦ßGûï„÷êaÆá}£¾éÓgè0tûãÝõ½1ï»{È– ¶ýë4_Û=ûÌ3ŽdËÍœŽga(zœ‡OÔïºëNÌqgx gû[W[‘ýOO¦±ƒ? …÷OÛÿIý‘ y:~¸½¡ÛÀè@o’ã¿7™XG㎦ÑTlä`-‡÷_ÀóÄ­¨õ÷—Ã`û8‡¯`X ÉÒþS½š®jýO¶#¯eÏåY‡®hÿ… çs–L¹vóæîüÖï{äQÈ•xK b¦÷|9c¢HÀ½xI‡|íÓðÔ¤ÑdOqÌè… ŽÈéR‡kúŒéî­¹Ùf] ùÆrp£ßÁµIHty n ô »pÌnVÿW8~ ¦t|]Ûví°·lÈgk¸,õû&†DuMŸ1“÷»™¿`žÛh£ÖnƒVÕ˜üÁ„YÚ?kæL÷>~@Ò.ët7Vñ„V±ý3°·Ê[o¿Ã{ÑPÿÐ¥ú—Uûg¡]ï}ßÍŸ7mjÅ;¾Ç‰¦Tÿ‚ Ý;£ßÆQÝpû¹Nuh0¦iQh8îã\/LäÐdÃÝ÷ÜëzôØÑ}ùå—¼mìû Ê ¸“EAeÉaâ{Ô|êÚ È>XIäŽ;îX7éÓI ýÄm±ÅŽös©|‰Ei’nü„ n+ìÕlíæuöÿ àxÚÑnƒ ªaƒ êÔÿ ñÐxûwÜnÀý¯}É5ÁÍú±Î3à;c1æ¡Èîm6n1AÒŽºø_*ÓóYýBy(†¼ Ý {&µl Ó:ô?µÿÑ£¡¯ä¶ÚrKø¾N`qwVl?=h±4}]ã¶Þ*òP[’:ÕAÿÒl¿Õÿ ޤ}ï½÷ðò× ±j™XO•UÈ•ð±Ð}„ÍÑiôjذmûv<Ù_$&?>ºå®9~Hèµ¢´iÿEùÒVo€÷ön?Þ²ýáµ½=鈵©x/aÂ-û?$k¤'cÝcÜ äXïÞDzèP7ïþPÀԞ@†p›wŠwñ|¤Ó,±T&„c{§¿'%—÷ ‹Ûe—]|Vý4<†b¶@‡,äI±x›Ê¹ì«¢ÿåþבý?û¿ÆU‰—êå©ÒÅ€›ã/1ÑÞdj'±g~þÐc8F\2RÌåø“ãO:^ü ª(]p9þäø³"Çß°b(ºlÑ¯éƒ †"€(0Õ†'2-ÓTXÓœ—õgû/]ÿà c®¾ÑŸ8ñSöGl6îÚµoçpR‡=ú~ø“?vïÞ÷ÞYgÝõÜçø”Œ>é£Â›n¸ÙpâñÌk_€ðWíÞœý_m£©˜,Í©)]±ÆÿðáÃyõ˜¼ÕÈä8­Mæ?ßP}éÕߌcÒ¼SÇήK—­ i.m©¦R”æ ù Ö~®Y¨ll…׆'2-ÓTXÓœà´$Ç¿¥ÿÄGS‹§¹lÿh+þp½Bg V7%(à)«<š Iš³l¹ý9þäø“ÿ˜ÀØ€abLÍYåÑThÒœåËñ'ÇŸ–wüÁÄGb¡ˆe(¥Ð쉒Bdü‡ºü-ŠJã{Kx‰”E(åÌú%€dû³ç(÷¿O±ŸÊ_®¿Þ=pÿý²—7ޱwÛï°½;þ„_¹Ÿìµ7ï9EEÓ§Msçb#gÛ÷—¿tÛo¿}ÁC³ÿeÿ£˜¿ãJfž _"iJ)²ÿeÿËþ—ÇŸIp@&¿ñ|h~ÿä¿Y˜'Iâ(æqTÀSŠüüÉÏŸüüÉÏŸüüa $Á™•ìùV ™'‚o—†¹BÀ×§¥IãcA­yIí=ò…E­²Ô «àBA­y«Yq‘5ÅD­Š´JYBq­yIí=°P*ÁE­²Ô §,¡¸ˆÖ¼¤öX(•à¢ÖYj†S–P\Dk^R{,”JpQk„,5Ã)K(.¢5/©½{]x[œ5k&NÃZ=œ:µF((P@kÞ§E´æ­fÅEÖµF(ÒÖ¢¨´JÎúé·µ‚µ¨ZIpÑê²Ô §,¡¸ˆÖ¼¤öX(•à¢ÖYj†S–P\Dk^R{,”JpQk„,5Ã)K(.¢5/©½J%¸¨5B–šá”%Ñš—ÔÞ ¥\Ô!KÍpÊŠ‹hÍKjï€R .j¥f8e ÅE´æ%µ÷À@©µFÈR3œ²„â"Zó’Ú{` T‚‹Z#d©NYBq­yIí=°P*ÁE­²Ô §,¡¸ˆÖ¼¤öX(•à¢ÖYj†S–P\Dk^R{,”JpQk„,5Ã)K(.¢5/©½J%¸¨5B–šá”%Ñš—ÔÞ ¥\Ô!KÍpÊŠ‹hÍKjï€R .j¥f8e ÅE´æ%µ÷À@©µFÈR3œ²„â"Zó’Ú{` T‚‹Z#d©NYBq­yIí=°P*ÁE­²Ô §,¡¸ˆÖ¼¤öX(•à¢ÖYj†S–P\Dk^R{,”JpQk„,5Ã)K(.¢5/©½J%¸¨5B–šá”%Ñš—ÔÞ ¥\Ô!KÍpÊŠ‹hÍKjï€R .j¥f8e ÅE´æ%µ÷À@©µFÈR3œ²„â"Zó’Ú{` T‚‹Z#d©NYBq­yIí=°P*ÁE­²Ô §,¡¸ˆÖ¼¤öX(•à¢ÖYj†S–P\Dk^R{,”Jp¼õ6ýE~21À%U4Ã%O¥ŒïJÆ$Ȉ‘ jådŠ5/©½‹è¬Ÿì@?YÕ°Èeû‹sïÛð]ÍÄ®„ÌÿèdûµÖjê6©oª5Û_-áÓegÿ<þÉÄyüçø§-ÇÿüüKßšôåŠ=DÝd)=ÿrüÍñ7?òó7?5°æço~þ®êÏßÂqõþW ¼‡¨mIƒF~Ë¢ £®5Š“P†A“| ŒÄ"²~¶±5I0Ÿ· åCy<Îä_(#±ˆlÿl,¶.Ü'û_Ž~IyðäøÃ¦0ö㦔‘XDŽ¿9þæø›Ÿ?ˆúJÂG~þæço~þò°Ìä÷6…±Gˆ ŒÄ"òûÇ÷ýýƒW ‰KPÇ˸a#K0ŠLŸ@œ£EÞ”I?¹²d!F*‹(T[Ð ’3òpŽn\&”tÏúýCLšíOVÀ%”ýOìA÷ôŠcˆñyüåø£®‘㯚ƒÒüüñF`߈±#?óó7DŠüþáCF~ÿ‘ß¿òûgxzú±¡I|†0&¿æ÷ÏÄ5ÂSEÝC^ÂòûìÇÎÒzÿ’OÉÔÔoÍ ˆ¤k5H™Aä,q1kÈù9 ªv(Ôöøf¡$ëöƒ™²ý³ÿñp‘¡Á`q\‰Ãı#ã ¤yüÑ›ÄöÈñG,âÇU’DR£åøã $¦³0*úUd–è;Á2;åñGvÊñGü# <ò¨2>D–¢+ÇŸÄÂO…²÷šy¤°]hlåøËSæ'ùùCvÉÏŸüü?àQ’<†aä5~àÙëg(ÁËÅ‹x «i±œò±,BE:.)+6ˆ¬?Û?û_qØ„±eFJ­4v$‰òøÃð*3¢Aäø“ãOŽ?Ű‘ã,’ßõd±e&ž<)?òó'?}0 cÃŒ—üþ‘ß?òûG èÑTñ6e²åsIY±Aäñ—Œ¿81d-iìÅèb>Ж”cq@TÂSÂ,ºŽe…󺼠ˆ#¢¬?Û?û_ùD0’â *æÃH*/(Çâ€(OùŠ@IDAT¿<þòøËã/ƒH1HË Ê18 Êñ'ÇŸrü‰AÁ@Å RÌÒò‚rL ŽˆrüÉñ'ÇŸbP0P1ˆó´¼ ˆ#¢ÅÅŸtbˆä/d,¥–Y´ ÁÔB@ƒ "ÍrJ7Ba8x¼s¢Âu×?ðOÝ”©SܶÛlë=ü0H.HÔ,§t[ºú­:Q¥ YÕ2oÖ;ûز$öŸ5c–»èâ Ùרo?׳g/é0Yÿòâwò)'»3fº[o»Õí¾Û®±B$QÝSº­8þ7sÖLwáÅñrýý÷ß_l€*.Iû©}C†Â'Ÿâ¦Ï˜în‡ vÛmwn§o¬ôÏ Ø~ò®V¬-#–´ýõñ¿¥ÿl¨-?ö7dÈP¶9‡n éP-[V»~ýO÷]²øø;tõéÉnæôéîÖÛï@Ÿîj­ac@ ‚¨4Ë)ݵxý~Hƒ<þŠÆ‚D˜øË%Pz ÍrJ·Êú{ì17v£Å³¶ÿ[µléNïßß «¨|–[ýA2äYýË«ýY¿·@¶ö?ŸåòøËã-ãOŽ?9þäø«„åôþ«êèõYß?ebˆØBF¨1ï¿ïÆŽ‹²’ë²uW×¶m[z³´£FrãÇg†nݺ¹Ö­[ s]ï*KS×¢L΀:tpãÆs}ûöuwß}·á®#¨²45l)Êä hÈëª,M”er4änáÂ…nú´i®QãÆ®iÓ¦¶¨vXeij(S”ÉÐ/Œuk‚º­iU–¦±Äº°†À€†|‰ÀÏ>û̵Þh#–zÓM7¹Ž?!ø´ JÕ˜œ÷ÜsO÷ÔSO1Ë{ìá¬ìuKU–¦†+E™œ ùƒŸÃÂtÝtãÍî„Od¤jL΀Ä`m°'l0hIl ²455HQ&g@C^?Peij¤¤(“3 !¯¨²45RR”ÉÐ3øû³Îr—]~¹AÓOœ’ëÚµ«9r¤Á{PeiêѶOů¡Äÿ\*ÐÒØž†¸ÓxIâŽÖDeiªx¤)Êä hÈëzYgù{wùÿ]&J¤h7£Ô€†´~ ÊÒÔHIQ&g@C^?Peij¤¤(“3 !¯¨²45RR”ÉÐ×TYš))Êä hÈëª,M”er4äõU–¦FJŠ29òú*KS#%E™œ yý@•¥©‘’¢L΀†¼~ ÊÒÔHIQ&g@C^?Peij¤¤(“3 !¯¨²45RR”ÉÐ×TYš))Êä hÈëª,M”er4ä Nœ0Á­¾Æ®E‹Å¢Êy•¥©¡JQ&g@C^?Peij¤¤(“3 !¯¨²45RR”ÉÐ×TYš)„š3{¶›8q¢kÛnפñjRZÖ°-¨²45Ü)Êä hÈëª,M”er4äõU–O'`ü¬álj`“3`ý”.•¥i…¢ïJ¿œJþD¢5£¿ë:wÇw¸>}ú¸>ûìë.¿/ÎtZšéÓg_·/h†.åÔH¾À/ù„Š«,M…‘)SrÊœ€–äi¡Uq iJjVYšR_Ò~Íq£•¹HËx-GP³ôõ1­»Þzn£ åǾ©ÈrÑl÷Úk¯qÝ6ÜhÔLm©©)MQÈ©¸´Àãµp ìoåX˜ëP·þ_wÝuAMþçøÇ·gå$V8B¦–ÒÕ«©ç$Ž…œ2§¯…ÀTÄ8Wcåð– œ…ØâEùÿºë¬êÚAYdõ3¬J4 UXöíÿ>êï³ï¾nÀ€nÀ%—¸K/à:uêÈU»kJÈEµÝu× }J“#‹êÿ¯½îÖ§¸ã'CgG @uÕOµK]9eN <^ µa¡…VC%8/«Ï~}Ü%.q—°Ý.a»¥j–­þBc¹ÆY?w¿eû'Nc©øö?ëk/ßø³$ö¿xÀÅnƒV­\«VÕø‡´zä=ÌùV®)áyôQnØE]Ä´„g¤Õ¦™­Ü?™gÅm™­8¨›ÈÀ„¡1~ØèXÒ4¸ÁÊÓþ9³çºÓ~{*÷ãÆ›l‚ÕÁ-ݦøƒùUW4­ðûØ~é\Ó²`¥êÿóç»K/½ÄíÜ«—[sÍ5ñ.ÒÉ­Ödu×£ûŽî¹çžçW±Ð±p3c[Cc# C%8õ{MƒàÃÿ¿øâ wï½÷º^½vrT·rýúaõv±®…öþùçîðìÔ«'‰~ýú-²ýsæÌq§žö[Ž©›`üTcütè°)ÆÏ•Á‹zÿáÆ² À"õ³’b›À‘¢SqiÁ2ëÿF$¹„õCºbŸôËO]év¹÷þûÜ5¾Î5n\b¥­¢Kšák*®• l—úké¢ô³p–º€Aê&XµóWÉü—Á ,;ý¤A–^©. ¨ÀòÑ?eÊn>}Ò#×òÕï•VlÿÔ©S¹xF¨›![„ÿ©Lîw4§ØÿÔÂúÚŸ…±òñ/ÊŠµ®‚þo¼ÑußqGGDtV–Oÿ‹rÜUmjÑOö——¨ý¤ê†›np;öØÑ­³ÎÚîàƒ®»~UµÅ%á>2éÁ*\ßþfúéßi§ýÓkĈ7ܘ1c‚'Ô5þÞxãMnÇ{¸u×Y}zˆØ½ûOƧ¼äaÓñÙ_Á° ÔâZÉÀþ;áÅcgo7ò«×0É>fÌØ`·ìuÌQ×)œÇ…<õu&€ ¬øþŸã/zÐw—øµöyº÷u ÔçŽÊ€_žý?cÆ ÷ÅäÉTsQ}©vé5 Ÿ¸~æÌ™n2ñT&c&-šZãÄ\ÆíÁŠÐ~ª']Z—åmÿI?}!ð³}öáÏ mÿ„ßfýúŸáF~ÏÝ|ËMðmýû¿ZÍöp~ÿZQâ­:ôðÃÝð¡Ø—øš¼µ½2âU×»÷î¼Xãè£þ^ûÿÔ)SÝvÛmç&ÁdòZújiâ$ÊË¥ž¬ãÊ”ÉàÙžWX) ÅåIàÑ1[|ÿ¥•îûìó3Þ~AiJxŒ÷‘ë-Þ=ÚÝrË­ãA :I¾Âª_ueX9ŸÿˆúPdóËþ?ÒlÚ¤ˆ3¦Mw/ٛĹ'ÌØ4d¡Š—ÅûN&:baúÚõ‹82¬Ð‹$å’Rºÿÿì] ÅñßÀÄ* €HQAÔØ°D±kì½&jT°ýc‰KDì%Š(VDˆ‰]Š ¨QA;R¤ R¤óîÿûÍììí}ß÷ˆ¨€·ðnwgfgvfg÷îöÛÛÅ÷pi¦(õãʹ‰@L±êõ7îßÌøÆ‹7ˆÕ&ÎÿxòMZq\澙ɺ¡¡Í‘ éÊÛÿ³Ï?uŸ}ö©Ð«&Ùöµ+–MHLéï‰yÓÔ‡BÏW"M+IÅþÇ ¡.@N?í4W»vmϱ0ª@>Dx‰Ë•ÿù矹Ï>ùÔkb¥TNÌ=–üÙgŸ¹Oi·Šô÷ý•ºW&ž¯DšV~ëOüF°AçλÓO?½Ä5Œì^ârõ·ê)'+¥µ‹¹+¤ðSü²äËØMs¬˜þ\1ÄãihÓZµkjq˜½”ýgbÒ7âZhüX¸¤µõDÂZreû…Õ˜)bЍ¦+ _»Håþs/)>SÃï'_ùåòÅÆh/µµyZ'·i¯K¡±…rÿSïuV ÿ« W¯þw>ö4|}à@÷ú뱂ÀþHºÿþ®V-}i×®;òhþXUæÎ¿à|Ym0pÀ@7ÀÊøô€\íZµd,f™£`™`%IçýOM²º?Üû²WßÿnÑ¢Eøfž»à‚ Pa‡ýtýû´^òþ<{5ìÿ/¿ü²L ÕªUÓuïÞÝMöµ[–,ÅUï¹­6F–¡mÏwË–.õ÷B»'FM\”\óÆÿ*x!Y¼x±«Y»þ°õæt‚ºh šVW)«*e8þÑ~ úük„dýÿ>öL‘â¾ûïÓþ3{ŽØ˜ÔvÈõðšÚb•Siù¤/*–oœVÇç_]þ#y$òd͵öBñïÿ>çvßã7’~òÉ^óB’²*!’Ë’r7ƒÒsÃGŒtã¿úJ–Åí»ï¾îÈ£ŽpÕª®ã mlÊ\·nÝÜüùóÝi§.Ë\§Lž,3¤üwñ‚…nÇöíÜá‡êÚìÐFVyXÉrá$µP’“§Nv=º÷§Ú|‹zîT̲2Xãò{Â^Ï<ã>üp¸›`öóÍ7å×Èh[¾Œ´üK~ü9îøcñ|£ åŸð•ëÝ«7î bìÆ^~¶ÌþHU¹•¹Û0ö.D_:åŒÓÝæøôeºcl6ì}Ç¥ÀmwjçŽ8ü·Ã;²€jeî?RV ÄÊ#°¡æ+aQ¢ÄøCÆíäˆü¬þ¬ƒÚD*íÿÁx¹|˜5·ÿ÷yþPwÏýOû{ö·Õ¥ÿÕ«W{yÖ/Ùÿï¾ûL ÌqµjÖ³m/WCö%Ið™o=ì©ïÿ™jÇ2s°Ú³&‡zõB™uu/“”bõÒ•<ÿ­ãïÂ…‹Ý?ÿùOº¥ûóE¹³Î:©2WÏrÝnëæ†âYb^zÿùÏ[Ü>xç²Vôwš%……”•”äýÿçðÿ3Î8Ïëº;wp[oÓ$´Y[¬„¹ôÒKÝÙ<‡¼Ì“¶m‡OËø|Æ uMk¼¦ßÿ7Þx7Ÿ‘™Ï6kÖÌÂÊ÷D˼CÝXÿ7ÙÈñÓ33H3¼¿pdZ'²Œç™È$Ò­è?|»è¢ ÑΖ¢ì?·w»CÞiß2Äýó–[ñ°Ÿ±EùÒòãŠà5óùƒ/Ê“rKz@yrùe—ñ™"Á².ª˜Ô¯_ïwå 6ï•|íÚµ’m·ÝVÒ¸©„ò={>š4kÚLàÒ2( sÊùuÜ»c2oî\Ч2)ßh ~3ÁL^ 78cì{$rHo¥ ö: ²™Àò± 7¼äã?V¼w×wY1o¦©ëŒo¦£jiÝXØè0“ 2DòpÌë–ê•åÔ à v:5Á7¥bsò£Íh/“]§N„¶e ý)§œpF“Æx"fyÿ‡_™Bµz>ÚSåDx£cÜqŸŽ –©Šœøb4ƒ JAûDu3œ¶êvò)A6qqû­ÅøåKô‰e¡Sé—ò@Bý+†³ØSúìäI“CÝn½õÖàßV³ùóÿýoMÖC£‹õ¿çž{­Ž¡ÌÅ]T¤ù°O9²HúÃLû“ÞlP‘þøtÈËLÛßÊ“Áx¹uÃé~I›Û„¼éÏ6ûïóϽ0 ’Ò°M Ú?µÙ½<È!¯‹/¼¨H³Áˆ‘#‚Iô¿,Rý¿Æ<&W’vF=Sý)ÃüïÀߘL2Õ-O°œý(뫱þ½Ÿí“iÓ‹¡xþÕ¯_/ùhäG(§vÀäŠÈߺQãdõ2óø6pNê¡ ùüñTy+©¿/,ÑG.<±OP ÎÈ/å×±þ÷Ü{¯–®˜È¾…ퟵƒÚŸ};è}×ÝË{gÌPòHã«cïà`ïX¾ÝXxeï?VÏ#Žúè kyúÛüC\&ˆƒ|¾ÃÚØÇþSȇY…÷µÐ‚ôíá~ŒöϰG¦°¾Y¼Øbµ¬ÅÁ ÅüªÉk Þšl& ÆRŸºg¦Eóæò}ßĉ“W˜ Ç/½ Gq„{á…%M+[x Kâ>ÿâs|ZRËƒÕ mÚìˆ_Ηº‡±4kذ°Re€ë†Õ'ÿ÷—¿èë[ü>}þ­›?Þrû–n?Ìx“ÿ ÁCÜž{î)bâú@<«;eò·7~•çÞ”?dÈ[“W^''«/Î;ÿO,â¶ÁÆ^/ºØUÅê˜g{?ë^~åeùÕþ„N§r^/ùå\*!e®o__7”çÆ¬;î#¼av~/_7©‰Tˆ5ÒÉz0ß’åõIØ—9ž*Äz3\{íµXµ»›ùÍL¬¼ùÄ݇o…'N˜„åõOýù û¯½‘äûá—ûFŒ=/ºGSc¾’’_–XŸÍybœ—ûÒË/‰œšø•è8¶ÏŽmdÈCÒ>Ãܬð¹ýŽ;Ü_Ð>p)ÌÌšŒ×·O_×õ¶®2cK샕Ô߯jû”áÛ̓ÜFÿZä÷¿#ôT¤‹.DÝ|›àÂÓìâö$|V˜ÿQ*—V©š.p‹éÉNx1!%`õ.Ȧ`ùd†EöA;¶Þ¡5~5{ß©N.wéìüÝï\•*U䤷 á+2â…ñ×¹|Hh JƒÊíÖõ¶Ûd‰ãÉ'Ÿâ5jä¸l”'šñ»â3Ï<Ó½ýöÛ¡¸V½ÌÍÇ 5êÁêÒ5kÖvU«VºŠôçŠ-Àvc™Z(ã»91X¬9×z’W³ì<Ýëù¾—21¡å‚ Èlƒ jcvíçõ'ÿÙXzùÐCjƒ”-Rª„—@«%®k7Ú –¬jÔ¨‘ã8ÑÏÛàì3Ïvo½ý–ЛþÌÄþçÅ M)ý¹Âþgûlíþsà¿pÞ3øEtèûC¥.Â:wƆv=zôýwî°3öÔ9+ð¸ØxÌØ1îH,­KNÏ<ë,Q‰Æ£ž¬ËmÒ¦ªOÃFÜ+Ö¦“&9þúóö;Ú¦—^z™´õhð#Í(:XÿÇMÊMž8Y`§Ÿ~šð6ßù>úÚßx°ÎÍä×Ü ¦´)µÒßV7Ÿ wG›²ý1A)´¼˜ý;ÔÉm´6¾_¿þa¯9ñ ²"~ü±“*Yöé§ŸtçýIÇÞ¦M›9ŽSÕªTu˜|s¯¾òªŒ½'žx‚{áÅ—À[µýQ–uéÓ÷9ÙT“nt¾ï¾wÀ˜Ë÷c/>s†Èý‡<Dÿhü3ý‰+åZcÅf îmg>¶ö§½b~"'ÖßÒ"—ü³ôÌÇu“P&DIÈdsùÞ&°º7\nsNóUÍÿñGˆÜÿ~’þ׫qÙbàÞòûßdè­_õMݹË%‚çýè –ñAÇŠ¼ýiŽÕÕÿ'ùgTÖ±eË–2þ¿÷î»®*î§mwjëvÆJlÞ6x/›Œw¢ZØêÀßFäö ºñ®“bÕ}üŽw;m¸Ä5oÖ<ó¼ mšUé´%!“õýyþ ôeáTŸ¸½ñn¬HÐi¿õYÃ{½É†ï]ØÞeÿ©ZÅíØv'ÇÕý|þ#c¾Wp«XÆòä+± ôõ³¬WgûÓ€QSI»ì²Ki‹¤ÓÁ’‹ý¯ã×_w}rè!‡üÅ^”˜4½ž¶Cåɘ1c“»±‚â»ùóÒÉ3pÄg ¾g–2õÔñE›í¡¿6?ùä:[7"ãx–±CS&MI¶iºÀ0é‘ ñaFþôé3‚ü»t«/LÈõ×_'e)Ç;œÊŒë†vM¤n)ÚX„2ŠÂµM¿à!‘ß¾]{Ï/-¼hÑÂä7ÞˆäQ»¤Ë%ÁnT&ÌèÑÉ=÷¢}¾ûNÊÚeÁ‚ù WXQ®d1.Š×]ÄQÚãÉ'ž´¢Q$kyd/½ôö”ÁnP èËã*ŠC9Xä°ìwÜ®eãkjžZ,?BJrÒ$¬ä‘ú¨}úöõ$å åî¿ßþ*:ruœ†baÓ¾žè WËÚV—à†œìØvGé ú/+_–àºPþ7Ù¶YË–-… Ð×|]ï¸ã­Š]³ä e|á³'£NcÈW ±-ûþ›6P†ø+á*óù)SiƒŠmpï=\]âyˆ4ÈÁê,êO°9\2vÌ( mOUðo¼ñº”J/J§ù8í) @ì@¨3ÇXÙª#Þ~ûäï¼ä³?™ŽGÿáèdÉ’ÅAôÌ™3“V­Z†ºM›öµà0ñüÇ6lÓ1¡ ívÜqÇž¢Ô±ŽŒÓ%С¤Òñj+†ZCv&,§ÿMŸ6=èPè×Y>IrÉ%èÛÞ×—Êãûjcs|Œ-µÅv;ï¼s²`aºú宿áúÀgÄpûT ›ÿSÇŸ'ŸôãŽç©Z}̘•»ÿH}Á “R¶J+2ŠÂµÍrì_Zf F%@&PQ¸–¢Éå§&Τ çã´/TdW®¥hrûg¬žf çãtn±€7Éÿþ÷?¯86r%7ƒ¢p-a6Þ­ ÇÔÁX¯¡8ÎÇé ȬtáZ¢˜Ýÿ=—(*$Žóq:—óÍ7I›7nÜXŒqçwʽ‘íùÒK/'3gÍ ÷Ó~ý^‹lŒd>þdír…>çã´/PdNQ¸–¢ùžöÇ¢Šd¯½ö”öäJ˜Ð~¥x§=Ÿ ½Jä«¶%„•™@E­ùM›á+øufõ+U‰|,úÐ2G¥«ŒT–+On¾ùfÁ‡þs×’§œ—ÑfEýç5é?%„•Y¥…k)šïÙþZïŒJ€L ¢p-E³òå«=†=8•eöaóâº+ŽùñÈÞ%ÿþÏsòËÇýö¼ H(eË\£F ÝÏ=×­¿7ŽÂ>K–Ê7‚±Rˆßþ‘”«_¸YCV>ò˜z¶Ï³²ÚHXz¾qͤj/b‘á1u{í½äŒÂì8V áWäV-[G*%îü‚?'c±Ìm]»É÷œÂÇëÊ)§2+òßú>R Óð¢’ø«zï>½¥n|ËñL ¤tJ+Rqa$TéÔ½fMÿ°wûï¿+«8>ø`¸[Û‘Aõê5°‚ha%D„Æòã4p’啉ù7v瞃öÁqˆ KpDâ—£FaƒÂ7\óæÍDî¿hÑ-+ ”#õ§ÜgŸ};èã™ ŸVºpE"á¢QßÛ üt¡åLÿ¨²²J‡+ÇFa¿›o¾™áÎ?ÿUÃD‘¥‰ìÕ3–¯S:Y …±r­›ÃD§çZ¹enßý± L”9LlPþ8»áÕðÔd¡r`Jé,‡5Ó½Î>ó,éıý«`97®6a®”ù ÷áן¾ÏöÁê®Ï7?ïüóE‚ q)¥?ËôéË2_`µÙ7²ñ¤V\yJY±¿¯9"®|9ôCR¸Òl¿ýù]­jÀ=¸˜&‡ˆ‹È/“9\ $°¡•o¸rVÈ<ûì³\ÃF@¢X®Äâj%²9òc¤b) ¼”–o5⦋¯¼ú2Y9| $㬬Ֆ ~Ø'¨íäs_]wËM7ËÞb&Ÿ‹_wÝ †vÿùÏóRì®äíîÜYçž¶)kq]ø+¬ªçð9™¦ÑPÜ#‹¼_Äj=îceaöœÙºb u<ï˽äÖ­¡{¥‘ŠòOÅJ:/Ô½7ô]‚@J…Ü3X]tÌ1w€P ?ôþÃö5ýÑ Â^ùò*YÄ É'Nk‹+R0*¹û§RPXŠñR¹ÿS¦ÉÈçòsû{·ðÏ8ô%KaÿÏýÏúêšÑÿlo·½öÚ§+î&ÍF&¨NAûßpÃ?„n¯½öÂÑλH:¬Ò|>þÐxø[½Æßx䨶ɯúÅ÷Ç´~ËÕùr…´b™›>mFQû Ê_òþõwí,Þ2°àjàÿÜ'êõ×ß”:ýã¦Ãø?Ð$Öv–6¯×ÑLîu$)ÿ¤ÿ¼ñ&Ëýû!ûnøÈ]í ¹‰¿>AÏÀÉ»L ¤,¬Íö—osT}ªMåíe€êsI/-— H1»æFbIMúég`éªJ/”¤VN,ÄãÓ{>ÑÓ=õÄSî½÷ß˶è àqq›nº©/ef/s‡á%õðC*¦U0¯¬eNœ0Q>%à¤C£­Ër;Éð"¾R†¦ñ ÄPÒÁ‡t’i/–§áæÙÒÒšŸ8Þ/5óe±7W>ü°#òl•¹àr2HÆbË(Ÿ,Z¹_uÕUnÈ[oã3ž ®ë­]åŸÞpÃîÃ=ÌÔé 9e”£^)ÒĆ„‰”Ø2JÇãýìq÷ä“hŸ÷ð"F´0Héæà¥•íc’pø¡hŸÃ}ûøZ¨ì ôÇûU¬ž&IKšõ?NÒ4iºM(jôм^+•/$ii¥•–wëaÃàBù­[îàm‘¸…‹J½(3å 5àûù<{Š¥ÕŽøšFÞª!£—A•ï0 × ˆLÜè±£…©ð0 Ê°´q›&MU`Á•%­VZD5©ÏÞ¦ ì¦>¶Œª†íÇÍÍ×Ö_„äÚ_l¡ ènÊ@æéE,gH´— J¨0\µ® nÖL7w¦ÆŒ tˆ D¨\„M`bì=t8†¬Øa‡À7™Ñb>¶L*6ާýëãSÆ-·ÚRXÇòwÙËI½@|ã-ìÈ…õ mí…(]™ãFyÖ¦_ú6%ÉGéêcA.a½ëž{VIÉ>ÏöU¹ ágl±ü ÐËH5ÐJi^¯&_r¾ÒÁÿØÿJøŸñ+ô8®7ZUˆ”ÒèUºš5nÿ`/ßW[?®Ÿ’Ò ËD¬–d™0þе1žÇD$yP ÊèPŒ½G„qÇPJ "U–]¹ûú¬¿ÊW¾*¦¾¯ZiùBd$¶Œ7mªø§ ¯Êö÷ô†Êå[鯘ÿ0ÎíŸûßšÙÿfÍšíþóÜĵƶŠà´ÿc5‰{î¹ „[=äþ¿æõÿM7©#M=ã›YŸxÒIØæ¢¯ÃJÜ#tsð‰¿Ü»ñB¼i]<ÃûÿÊ<©Ë¥÷_©”¹œÄ–ÉïÅݳbÿ{‡¨\~ùÒG¹Áax÷² ÕëÚnji+¤? ¢Ï®Hˆ‰ÔN,K[m²éÆþfÆL±í‰'ŸˆÔûºzõ¸#CÿùvŽP’vÓMµ¯¥Dм^IGˆä4c%å+µò¥X(}ÿ ™ C•¡W-Ãk ]Õò«Iu2 ƒˆŠ³ªà“÷7L^ðAþ˜cþà{¸¾<’… (H¼úÚ«éé?`ÁµöÞ·£Ã櫲£zGzK}4qª&™4Þº±×ÚäÇdIyá2ðõš÷×#‡£žs7ÞˆÙU”å*¤i¶k9øðÅõ%[ Ô™³Šˆ¬±nuM( ¥l´õÖ± ¡¾9•Ëó/ ¢|ï6Bo‚*ôe²ÒpœâöÌ3½±ÇS˜%~¿²Ï•Ÿƒ>'‰ÂÞ'Gao' ©|ƒ ^Žü×^{ís@Ð_ö ÂäS£† ݰ†b•G<3rW ^}ð.s§6HåC¨'-’à £½h/ Žõ¸"ÿKy[IÒ2hÞÛÖ ÏÀ‹ìOÂyz-ĺqÏ©4)#øH@’ÁÅ`¢Ù(+û(ßB¬¿rÄÕÓ î'Ð_VMI…P/©¦ÖÕäW­–Îæ Æ+é#Sõ¦þª[PÑ«!˜cý­,±äeòµ¬ò•’ÉÖ‰›:eJÇ ‚0BQ¶¨ýAÀSõˆÜH~uÓÚÅò¹G™”g?dàwqÈTþK¥o#pHp8‘€¸Ú:Õ6ó—U_wÝu'V#ý]~Õ{ð¡…ä¼óΗ}¬„c,C°I^i$P^\‘ÿ{Ðÿhçå?ä§ú3eÁ+Q‘üÂö§Hk ¤’S§M0ÙpRšþâ¹#ã ('ãHÖc…÷ü0AHÌ^óYýÈý'«?¹{‰ÙˆÕ—PJ~¦‚¤Q)ÿ#Bj. ©V±ÿ©l2.—›F‘ÛŸfÉúîæ©“¬­ýŸ Ë(BMwíàWþÄ$ãÏ Aoè 8¶Tдwš|üQì^$ƒ. ¸ú¿õ`ÏPT•?¦1Á ƒÅ‹cÎ*²%¿°Póz ø>÷s °úéoº±†këø÷ Ïc¯ÖN¢êÞ{ï-+­U_ÓÞ ÖòñJÛ{Šêº0MÅòx®ŒŸy 9ƒô¸÷¡‡&'•U©Z†}ºª¹QQÿ‘½à•/ÌíRB¾º«ïóÖdPUu0MÚ @(Ò°°P ù‡? ÂÄñS$Ë,”žÜÒø6O'…PšÇîq€ú¿æßÖõ6y)j£…ÑJ*ß^däS¢V‡ÂÈ5•oÆ@ìåcï"7lè0wá… ꦛnr¯¾úª¤)ŽN±åV[)9£¿í`5?“ã‘ÇÜ´w!Žn^„?æ»t¹eSù’"Ÿð¶§5 ˱Àã×ãËÿv&Úgl~ Y;cƒ­Q£¾pØOÉát.w>^NÛïÔÞEÝømŒÕ‚¤³É‹`ÊÝä D3)Ò§¸ê…ò¨ã;]™þÁ–žá˜±ãÜ—£Çp¡ˆ‘ë/õ็¾>…ö‚ ·ð† XoÍ„Hj'L|…”Öƒ}D˜GÃo|ŠÌ|z̸qžÒù•&ÌZ KÃWÇŒõY¬QhTt̘1nôh¿ p¯TÙúŸèE&’޵Qÿ']$‚Ùð %ðBdLÞA†ÙÜÆaCf ð+nª—•@†•õ£Fßö=y}üÑÇŒB(l³CLT3àTlŽ¿ÌW0Ub|ÏlÙr{¡eÿ=ÓÚìË ;v¬ÐñÒBô[Àiÿ“N>ÙÕÆfà`O67nÜW8æ~ˆÐŸ~úéEòM‚ÈKEþL(¸4’c)1\TÔþÂ…PÕF®± +Q©ÿ;Ù¬Ïê1AvܸPÿ†[6I¬ÏèQcd¬­hüí‚T5šzåø£9x€ùƒî?EúSÚŠù_T™´r>eí/YÊ @Beö'A.?·îq·´ž£ýÇz¾‡"=ÑÚÜÿ è/Zòùw»í·3³dâBýû¿6Pî5<|dûí¶Z±qlhæãÏê:þØ‹-o#dçÖ©¾ŽL ±éÞ~çic¶àæõ£Ã]ØÆùý‡&BX}ï¿Ï?ÿ‚ã!|þä{Wø­·ÞzòD u׫yz-BzÂÂþ¿&¶?5\‘ç?³g,WJÿX$4ÀóùŸéuª¡ÿ`Rˆáô±*„nQ¯>]E‚4ƒ«æ+@zªRòµúúÞ½j´L¡ÖáEÈ&CšnÓÌa/#yáäŠ3>•ä| j|¸wIã&M|V_¨¾™5S¨…XDò=FÀB “OYÊÀ•40‘ÂÓµø½u ³;’“(X ·Ën»úò‰{ä‘G\ ìÛS{]Ô¨QíËôº5\u¤™¯ÊÉ ìeªÈ7ŠŠcÖ×äKÝ™Q€/¤¾Äa“ß £ºu뺣 Ã=æpltXðÉ'Ÿdè˜çõP;݉ÙBùoÿíãµàd]~r„ r¨Ê78MCôLÅ|¤è¼²d}v,ð“§*Ð? wîÔSOuM0 ¹M“­ÝÕW_-ÕÖš·ºêOÙ}q´2¦ÊTõýŸfËÊGY0ÿOËkJÑ”áíɤŠtýë!!bvûmñPçáô—Óh¬ Ûýèê«®6q1IQúÔÓNu[ce[”¹ vS¶Åò­ ñ+§? Æú—¨?÷ä1ÅäyD„™d‡ͺµ+z°­¸ÿ§|¶Ýn{¹éŸ7ËÓ‘tIÏÀ~U&0ÞáY¬Ò38Ë1tÇê<“ݪ5ö+3¯.°6¥"¤á"'´y;l·Ývù|¿'z×]w¹Þ”‰Ð¾];·C+ð/a?â­YùÄT~õ«_‰ü‘xpäg¡R÷û›Ð¸ýUÇñ=Qµ ÿÇãŽÕŒ¼(Rö±@‚ùGí!clE㯼g<³÷JҺŘlúݼý7„Ý(‰'ÌÅçÎ+kÖÌôgJê¾öÏj´òíŸËÏíŸûŸpkgÿ{ï]lÓ€À“t*7uXKõ{X2´mÛeôE(ÿ]òñÏßuùTdÊ“?Vÿk×®½«S§ŽTGkUâ_ÿú—À~‹ç8i˜ßRÿÏ>giïXüŸÏ°m}ç7»îê^Æ Ù|v$`uñ¿"‡«°ªüŸ|Väù¯°|ÿ(ÿh‡gné?(Ðý§°ýÙ(ów€s7P¶ö}éKÉOy¬~þǺqg Z?ËÁÄXgòHDFÊ… ©%¿ˆ+û -]¼X2ã¿ïÎ9ûlwå•W¦LŒ?)È8äKaN2 ò©LŠe”mÛî($œU}æ©^’憴§`SÓeËðF’»í²«Û}÷Ý… ƒ¿îïÇÑÖó„–Œ§c“)Âüñ"ù&×âbý•\Õt4_¨/SVV$ÞøL¿Ͱñó.Øð5¬tZÈcÇ=vÞwß¹þýú k^5lʰ6õ¶pß=÷¹ïæ}'ÅbõаaÃdóbÊç$šU§û¸E¾}¾ÿ•;û´Ïß®44b_;³¿@>Vo“™‰‰ô¤mÑ¢…òì¾ûP·ùjóùX=4u›1#}Y7>\iÖ£G çÚk®A]…º}ùʳ„ý}5ÏŠú%U¾é„áŠåG&åϨéqî)Ø{16\_´x¡»ûî»Ým8¾a×ÝvÃß®’ô¿/¿å† Èéšk¯‘åÂÌGÒ˜ áKì…Óãá>_æÄnXb\Òÿ<• iUj“ÌU¦(‡Y¥Dw”ClhÆ—tî‚=­èsX™;Ü}l€ÉcÊß­Ð~ü¡øJå ÃgXÈÑóÌNÿzšÛkϽ°çmW¾t©Ã&üî³O?s'ã;|~ޤŸ9wòÉ'É —òÏ<ëLsþ’+ÇÊ!¶Ñ½Øˆ«© NhÃêGnZ­ú¨N Vz}ئԇm }Xˆ“N»í²[П%Yî\lÊÏ¥¸äÒKX]÷§óÎSÆ’ó—ï©\T*:Ä{SÝuç]nÆ‚yø$î7^ǘ¸LêIý$¨R…IaU™ý·EßÖ8w0V±ûö°¡`Ü™.húøî¿ÁØ‹píß1ö^wê2Où&c/à2ö’(ÖY©.VÍPoÒFáÝ<Ÿ¦Ûèd9³wÜy§›±Xì†Ï6pòœPU$? I€?«/SiÚ—FäS1¡°Èè_YÿSêì•Lý_*3—ŸÚÂ[‘OåöO“û-?«qÿãÊ`†:u6•X.¾ïW4þŒñ{ßÕÁþ‘¹ÿ{³­aþÏÕA¶²–«ý{öì‰çœrùært1hÐ (Væº\|qP0UÑ·:¢¼ýƒy|ÂG?CÿçÛ6ß?Ï<ã ©NE•'MÂA&#ÝÈFÊ&L/C[Wبþ/mó5çþ?aÂ7aüx7aÿ&Èj~ê1ëÛYØOx‚üއ&YÐ2,§<ðŠþÿí¬o…÷!fÙ¥R†‡9U×Ã@õOìçÛó1ôÌ<-Áûðå—[ÿqî"öŸÕxü7ý3ñªiÿè<³(‰ý>’Ë/¿LDtìØ‘YOà#?T¡W/=®Ÿa%uënàhÐdÓ:u|¾,Á,Ò˜ŸÃ²iÓ¦¥<‘‚rò×¹sg “­RV~áqõV†õä1ËäGùÿ¸ñƀ†×I§ƒp º.á;aÒGÛi½T>&O’… eä[ݺtîÌ`æÌCBëiu·¸í³Š}ê©§‚þ*«,iР~Ò²¹¶nîp¼÷ñ%åóèoì¤:ëŒNRGÎS–ïÚµ«ÈcûÔÙ´nFÿX÷¸­°HFEòÁð’t¾Ø·O‘b¦¡" Ït|Ýd^õ)Kê7h z¡®V·XØpYþ¨4‹ódÑ¢…*À[lbCœ•_H6yÒ” ÿýÜJ™ü~ýúI)?=Ê•dž“¾þúëP¿ôX}ÁþÖ¦!ŽüwZ‡‚Šât‰ÀŸåjÕª™à;òP «ŽÖ+> ÌØ ¿% ©/:È™<6Oúÿý÷ß_$¿_¿þOjƒ¬þ”Ï>¬:9és1#RO™<9à¥ßS¦ïÿ&Ÿ±ØÀ³õD" ò9Zì àÁ(Á5Ô›|ÍÿµŽê=},ˆøäÓ3}_é´ŽlúÝØqãLôñv=RÿLËA&ÊQ«žÅ&ôœ³Ï þW«vÍdÞ¼yŠ*$4 ò9ZlŒC¬Lö–ÔŸöÇRsO1ñɯ¥Mññ#ô¸ãh<A;fƒ ê…6¿ ㎑ϙ37éÔ©S¦ý›6Û&cÚ›c”–5¾w¾XÁÆÐb#FüCî?VѯƗ:R“mñ;oÃn&×âH~\AC[È2€(%6 ò9ZlÄ!V„¡-.@ûl„’F›ùœ-6â+ÂР}6ÂFI£Í‚|΀qˆah‹ Ð>a£¤ÑfA>g@‹8ÄŠ0´ÅhŸ°QÒh³ Ÿ3 ÅFbEÚâ´ÏFØ(i´YÏÐb#±" mqÚg#l”4Ú,Èç h±‡X†¶¸í³6Jmäs´ØˆC¬C[\€öÙ%6 ò9-Y¼$ŒO\pA¬VÖbàeJÆ6>ÿj™%Yäs´ØˆC¬C[\€öÙ%6 ò9ZlÄ!V„¡-.@ûl„’F›ùœ-6â+ÂР}6ÂFI£Í‚|ÎGxéM:tè|€ï-¼OÙ}ë„O„+àuטYœD™(Y’Ô8ÅFbEÚâ´ÏFØ(i´YÏÐb#±" mqÚg#l”4Ú,Èç h±‡X†¶¸í³)?Þ…¶ä3šµ£={HÛòÙmÌç|kŽÀ7$VN~)~iíÈÜç hqk EÚbÃÍGXŸT}+ן4ø1°Ô2ú¼Û­ðùÿñ'žj|û-úÏ.;›ãGŦýO:éÄpAF(h ‹#ŠX'C[È2€(%6 ò9ZlÄ!V„¡-.@ûl„’X1Ó1Ð$L2öGjX}u}{»¿þå/²z€¯Ž<Ú/¸Âž§ñèÇ5”§É—•6`R…u *lHcîc³©¡õý""’¾­y£ýÃÑpüÌåš«¯ñKÜ9mŸ—½ÝtóÍø¼¥{Iù<úû•W^ÁDèL££Ð$9Ï$&dE%²}†¼5+ü (CÝ›nƒöyà~÷¡´)Ñ>Xfl:BpV»*6Ò•‹‘$ðÁt2tUìd¥u;(Ppö–¡æµµn¾ÎRaÀ·Ç÷ò€½Pò¹›žWÇç~ ?ÔþU«£‰¾¦Ýø°ÈöƒqWò‹H ¦òˆþd:§…m±…Ùà!wÚé§¹)XË—[lÛÜýú×E²™\uòùy÷ãâÄ‘glÜKéOûô“ÑϪa"{0+hiÓú[Ƚå´éhS~úÕ¸q®y‹mÝF¿.Õ¦©Š”ÿüÿã>øò“²–-[¦¡†«ÆþdL½ÆbSlžÄ¸ >hܨ!&}1(7ÈÊýïû´¿Ž;Ÿc9ûñ=kãŽ)ÛÖ·311ú…ÛŸAlµ%Ç^?ál6—B+ßÿø üCî?j·qrŠn»qƒBë²A©§å,6³qÀJ"ä""ƒ1fXyýµ|öjÜKù¿RE._í‘Û_ÆäŸøùGïlÜþ¹ý1åþºB©ûÏÒ¥Kð¬ó©ü Ý´i3ù‘LǯìÕF÷|üçЬÉ`Œòñç—0þðYï ëbAÈ6Ò´õ©í_Æe]¥;ˆFcë.¥¡`ÉZfÜ8ÏÍ|‚ÜÆùŠ¨Ò’JCsùêÀÖÉíÿ³úO«W'B8w/Vc}æYÚ@¿ ÿŸ:uª«‡‰!ö÷ûï»ßul°ý¯¸âÿ°‰ýÛ°•¿)#UÜÉ+wÇ‚;ãŒÓ=-¢(pÎ{U?ÜŸh‹-êAJV@Þ }ÎFº¸v¬F)ùÜÜûòLÃ$Í¿;нˆÓ'J¿W\qlð?Ï»”ÕŸõ8þøã`ƒ3)2JÉ‚åØ?ÃÄg*ÐXk#ÆÙË_õþW`adsû—ê´Sî¹ÿ­êñ?ï±ì®`qŒËû_>þäãO>þèó HXýš=$èGµò¦·Å«“þ‚HÄi«¿éÿáÃÜ믿4%­˜/$Åìo~³G°Ÿémñ£¿Õ<­µ¥L®Å&Êä¯Ýc÷t_á³´G±ÉW²ËõØ€™ +/Zùfÿ@l0P@¤­H–ÝÍ¢hr-6ùùø›ßòû¯t%½ C±OI¨ ÿi T)½•³Âˆ­ÿZ¿³8ïÞ~ùó_þü›v%é|!‹„õéRþþ—÷¿`ˆh¤‘dtQ+šýlܱ8¼—åãO>þ„ÝéEÂút¬ÐíBBÀ>ê}ÆÅÊ[¿³xMî\»Ÿ iþ²¯ß$¦¦PyC™Ö‘ŒÒ>-V”óဋÊHXyBrù¹ý×ÿóŽ¾ †s‚è—æÿ¦¿|¼úsïž3ν¼þ¿+ŽzÇŸxÄÃû2|GXŒ. ”¦H‘‹‹Ä)A”ŠúZàÁ@¹>6r§>äѦ ôAXžü7ÞÈx≮:¾_nÚdwÌ1ǹ-·j e³—TÖ®»`sx©h¨ˆÆ¹XÿÔ^YŽÊDK ;\$.$óÜóñŸÖÁ_0tÚ&4Q>þæ÷ë?qÿãWéŽEê¼ÿÑ8b7\Ì~ìOÙõµ`Ïâ¼ÿåýÏü'ïèK0†t•Ð_²=J{[>þäãO>þʸ‹…=…=I­dŠŒ¹_ÈûwÑæÓñø§K‘&®œ&-¥t)µ¤Òl ŒAq:„„b+§ ľž)µ¤Òl ŒAq:„„b+§ Ĺ|˜"ö±[ ãÅ 8ZÒRŠ­œÆhýÍ3òV)W¢p ŠÓ)'K)¶r£ÍåZKò%ŒƒâtjIK)¶r£Íí_h-É—0^ ŠÓ©%-¥ØÊiŒ6·¡µ$_Âx1(N§–´”b+§1ÚÜþ…Ö’| ãÅ 8ZÒRŠ­œÆhsûZKò%ŒƒâtjIK)¶r£Íí_h-É—0^ ŠÓ©%-¥ØÊiŒ6·¡µ$_Âx1(N§–´”b+§1ÚÜþ…Ö’| ãÅ 8ZÒRŠ­œÆhsûZKò%ŒƒâtjIK)¶r£Íí_h-É—0žx½âÙ4MÛ‡ F"IËÙ´R!¢õh¥Ó2É$ATØJD Ï;—¯¶1 Áx’T[Æöò¦6‚4‹”Òi"rûÈ‘ñ̺(÷?ñ ¼ÿ©o˜‡À(’Ô¾û‹˜ËÛ,MkJé´ !yÿƒ#ã™u#PÞÿÄuòþ§¾a£HRûRì/b.o³4­)¥Ó2„äýFŒŒgÖ@yÿ×ÉûŸú†yŒ"IíK±¿ˆ¹¼ÍÒ´¦”NË’÷?12žY7åýO\'ïêæ!0Š$µ/Åþ"æò6KÓšR:-CHÞÿ`ÄÈxfÝô‹î˜R“ƒ˜…<œÖS\ ÍN YV· ÅÅ/ÁÔóM¸‰†‹sù4‡YÃì –Û?÷¿LI%ï¡×À(ùø†nö˜4#¶±|>þšÏXœß8¼˜5ÌORÊïÿj›`¡`¬|ül’¿i×ÉÇßtøààBËøûQ~ÿ±>cq~ÿ¡‡˜5ÌO‚ã¸@ˆóñ7² 绘Z3Íäý–ñöÈÇó‹Õ0–3;abÈ@Þr>ësâTiÙ(Èwõ!oØ·±^:0òäÃÍTRžFè!¹üûÄ^Pá’Û?˜‰Üÿ2=É2°LÞÿ` üÏÇŸ|üÍï?ùý—wüùƒV`°Eþü›#õüù+µ…X(\òçÏ` $òçÏÌHbX&þ„1ðŸý(þÐÑ$SÌQ<ÄgS|>þ¦¶ˆÇøR±jÇù”Œ›¸Ñsã&(«`eZ…¤‘QáiËšZ@[^ãøªŠæòsûçþ—÷¿|ü±•ƒ~vÔ´ÁU(ŒLHÉÇ_#ÑÈ,—ßèùý×üAãøš?Ðùó­ßóû¯ÝXóûoþü‘½kÈ”c%‡ s!A&þ¢U‚]Ìr€y,¯q|õÅÄžùø»z¿rþ³6PæÊƲD   ˜¡ŠA¡°Ç1ð!áaQ>”+H‘Ä€\~nÿÜÿòþ‡1#3Fq ÉÇÔÞ>ÑØ)É(OºR¡ˆ$äão>þæão>þbàÈÇß‚Ñ3¿ÿä÷ÿë}¸e†Dþþ#¦ˆìQÐ{B¶ˆ$äÏùóGþüñc>„ͧuIJ«ÞðÙ%H"û 9^§”¼†› JÍ%¡dć‚„Ç!å!PÐ)9IHÁP °\Z6—ïoJ´Tnï/œÖûííKÞ2™HÒ’È–ÉûŸ7£ØF-Åk>þäãOè)ùø«C í!èaàð¨¥}H@ ³2šV5´cB”–Íû_Þÿ‚§äýÏ÷®¼ÿÙX’?t‰ÐC¼0JÇPæão~ÿ1ï×ÈúŒäx\ê;ùýwí¸ÿÊqõlVièðÁ%reêæ5FkL*Ë[ag#u2§L–#ÿÖ[ouÓ§Ow;ì°ƒ;æÇdÇ;å«#, æÍˆ |c-qIy…à(+Ø VŸPj9ú:&-™7 ³Q  XÛeõ_°`¾ûû߯såËÊÝ¡‡êvÞ¹Ãéÿ+ªÿóÏ?ïâjÖZßýõ¯W¦Îò mÿàè¹þ2fz3³¿ÊÇŸµrü ûîÿécPp‹¼ÿ{ äã_>þ­…Ï¿¡£çã>þçãúê„óñ_&†Ä ÑMÐ >ÿüs÷ŨQ27°ýv-]Æ[Jâ‘#Fº¯Æ%†Ý±MW¯Þ€sâ¥8ˆÍ Ÿ6 Æ* ý —¶"Ǹ&[7q£ÇŒv'œx‚ëùhO JQi Á¡#ÀJÈ·º¤\Ò”á,L:ü@ùåË–¹9sf»uªWw57¨ibCücË×Vô ’[V¾ÌÍž=ÇU¯¶&'âºEô?PÿÊä³*±þS¦Lq[lA_uîþûpguæJùŸ0À%Õ"MÎâX¾Áâ’+ãÿÆ'•š¦ ÇøìsÎv@Ï:›Öq_Oû:BEô?¡ýÓ ,_þRøõܹs\5øN­š±ï”âñKÑ’L:¬¦ú[ -.P+Õ­ UH'勘D€\ÿï}ÿ1§VLS†³X0EèÛ?·ÿ÷|þÉø–d"2¤sÿƒ{™'äý/ïkxÿ›0a¼[o½õÜ&›lZÐû³Ï¿)2÷8½šc5ìÿ ,p&N”÷îuÖ©n5M›Ï§ÒVLS…D‚)BG€ÕPê`5´¸P¯˜&¥.¦’òELRÀ‚ùóÝÄI“\£† ]µuÖ ŒÂ ).MEhI ¦V3û§Ÿ’Ń"ë‹ððûƒ;urêän¾éFF×N<Þu:¸“;¤ÓÁî­·Þ&]²‘IRºŸïƒlm~%:%)JÈ0/ µ¢ÌZH\¹ ª8Sã§•¯Ž×íç“?tèP·ñF›¸Í7× ³‘Å?†ýWTÿ¡ï³n¹-dâ5ZEí¿¢òiƒBý ÐEôCDæAæ¥ÿû!òW…ÿ¯°|ÓÇTDþ'•Oócª=WTþ0øõF¿ÞÈÕ÷yd“UÌÔ¢S5¥ÑÔÊÊ—r`šÊˆ9çòiÔ6¹ýsÿ‹ûGšÎû_ÔQà$+:þåãì–¿Ñ›ö©øþùå—» kÿÊÕª]Û]}õÕ1‘¤­ÿ}ýõ×¹»vp›ÕÝÜ]|ñÅJ»6?ÿ@CÓ_”]‹úßÂE ÝE¾ÐÕ­[×mµeC·)~üÛzë­]×®]µ]ýumÕŸê‰n¢çšÿü±dÉR÷þávÙu·þúë»fM›ºÕk¸íÛ»¯ô­iamÒ_RµRÝOu°¿¤úmºÈž;on¨ÒO)_…¦ú{CÀ}&y^¥K}/ÿ[ý å³ÈO®?õƒ¾Ôøg‘¿’ýúô¬¹›ûµ@p‘ÆÒŒêeÞí LY‰yùyüµhÿ•´¿tïãÞªi”Û_;“ø“¹ÿéÝÅ»HÞÿÔ?òñvÈÇ_ö !Våýÿ¹çþãnºé&étª,\ ép³‡é§Ï˜ævl»£›8ap¤ѤI•–W€ÖÚ矵ÐÿæÌžëêt4h°6®lÙ1cǸÎ;»O>þÄ=øà¿ºê¿¶=ÿMœ0ÑýáØcÜ[C† Í¬—j_}÷½÷\ǽ;ÊbSN9Eðk›þ?düáû/¿†k©ÉÄFQÒÍ_ð]?ûô3wÈa‡ºQŸáéøôÆ{Tâ^ë÷šüõõ5÷ü‹/a‚n=µwáu-~þÕ¥6´¬HC2ˆ‰ä )çâÅì­wÞVðÏ¿ø‚Ðé…·;+­Ð0/þ±Ñ“ù±*ð$½G‰üè Ý3Ddu%ĈÔd(½e%æåÇ’oU1*>Ôï'?ó›oD\lžŸR¾j^Zÿ3µnÈ‚)ØVY‹4‚ÿ}öÙgîÓÏ>ñEd´„qHF€/`^Rù2^(pPš|²É5Z‚8$#@Œ0/©|A/‹AbVµ|á›TQþ^ÈŠØŸõü9õÿæL ™Q ªÕ$&±e%æå糿V&—š‚­a~o1‚nUûnÿÜÿé¹ÿ¥ãbÞÿ` †5xü=z´;[)0зE•HCT­RÕ-^´ÔÕ§üü“~ ƒì­¿èçuOï*J’™µ«ÿßÿÀý˜½l‰p?Úw‘ü€vÁùˆêÞÝõïßß›eíÓ_XTõIkp…¼€Wý_~åe™ªU«–cÛMÅê¾rlÃñÞÐ÷\ãÆ¥Ù¶K—.•–´j¾fëÏö2 thÊäTgÛ˜34’³¿ýV²Dè?À 8PþøøuÄGuŒA¹-4pëU_ca-l³ñ/7qòD·dÉ÷öÛo»Ã;ŒÜÝÀ7Þpý0I”Ùd,_«Uˆ ,+1/kÎø£ËL*#Á~×YÑñ?ÿþÛã7» ðé§žòH*µ“àW¾l©0ðu÷Á¸á#F¸ñ_}å¶ÁR­ýöÝ×yä‘®*V…•^~·nÝ¿õ;õ´SÝf›mî&Ož,3¤üLj–uµm×v¨kƒýŒÒPZþ”É“\÷H½7Çg(§b–ÕWMŠ~…ïq{÷zÆ}øá‡nälGétðÁòé\µuP7)iRÊ\·ÛºaÆq;Mê¶ê6 uëᆾ?Ì-Z4ߵ݉u;̵Ùu}Ri–²˜\%mv·ð%‹»ÞÏöÆfÁƒÝØqãd…VëÖ­]«–­ðë@'|C¼®Uʽùæ›îÓO>Ã’·y®:y2\à ø¸ŽÚ© V~üñn«† ƒVK1à¼>``hŸ Qûqä²§‹rãón·ßîæÏÿÎ~Úi®îf´Ú§{7lØ{ø•j‘Ûi§Ü¡‡êvÜqG7èÍ7Ü'˜•ö쇛“Ö-q7 n 2Ùê+|Üqǹ†¨[ÔÿŽ=öX÷”÷³+.»ÜÝpã?¼mSJa,O¸Þˆä)ÉP€SˆÊ' :ödâ/,½zõ’Ï!kn°Û©ÝNî”SNuáó7 ª?ùv»½ÀG'Mq=z<äÞ:Ì-\¸ÐíÔ68ü0·c›¬""wÚêÕ×^s¢?p0Úõ7»¹};îãZ´hQ¬J$Ø»?3öŸì?ãК5sûî»;Ê÷©›(¥šñ†ñä“Oº7ñ°0~ÜX×fǶŽrëaI*)tF\5’ÞdåI¹{á¿Ï;þ*ñÑÈ‘nÑâE®}»ö®Ã.»¸ßþö·è§©þ´÷³â€+u>Ò?}›uCß>âH|£ËaEëDiL©_? ¿äÆÁ¯«‚f‡VðëVð냒oãIËð&|‡3úßÁwäÁ XÕ°Ä–ܘ¶µOÇ{¼kØhK@EbC"€Rˆ'óE¬¨2P&…ý?0±2F,L#Î!¡h Éå‹-Ì–›Ma±Üþ±Q6ç‰Áêªp}C’!@)$÷?±…ÙÒâÜÿ¼òþ·&Œ?|Þ8ú¨?¸yøÑ¶cÇŽx‰,w¯¿þºô÷h üFoŒÍ©áV×´y³ðkyæþ¿Æøÿ"LýóŸÿD}ËÜÅýûdž%uç^4ݺÝî† }ß ò–»4ñ\Æ!.ÿÃS¢ÚÂÆ}‹}ëÓR?Gÿ?ãŒ3Üzë®ëÚïÜÞm³MÓP›vÜÉ]vÉ¥îìsÏuüêâƒaÃ\;|Z&A5jÙ ‰Ðî)Äû‚émqøóèį„ý¿ÅÄõã>ª{ï½7R©¶–bla}ìWʉ8¾Û~³ÄuèÐïõÝÝ¿ûö|Ç>ó]¡­„¹Iƒ ‰J!«¿ýa¼–Ã<>DÉäòË.ã/˜ô”¤^ýú -O°y/uLð‚›´Øv[¤Ë’^½ž6IÏž=LIYo*¡'æqK°)H4ùB[V–`<2dç<<ùú²Ø÷Hʱž˜E~'žt’ç¥Ñ¤I“‚|ü:’|üñǯÚÝ}×]ZmISå·iÛ6™1cF†K™|«[F/}2O¬n™Â°oâ´ASûO:5Á˨[¡þ›Ö©#¶eIr:ùä“=­¼„rV7¶¥1ƒêK%£iÓmŽ4¦?f ÚGe¡p § £}Œ·3‘¼Ùà$Ô-–/´¾ ÓrjóX7 f¡/¾ø¢¨ý11gdÁªFE‰˜BӘРußk¯=%ëÏúÕ‡¯âýÆ80¶ºcòE|Tó©™7°*Ø<ÁÒO-WBÿÞ½Ÿ)êì?øÞµH“ÍþƒO«DS«Û”)““½öÚ+Ô´föSæëÔÝTÊÄ—aÆ&8•Md ´¿É¡M~wàï’)ðKʲºÆi™lßÖRôkêc}([Ö%ø.>y :+uê×…þ_XŽyLPI¹Š/f!ã^H™ö¿Š)´L)N1¬sÅ¥bÊ\¾ÿ´J){Ìâ˜.†ÅVMÓ1Eœ6ŠÜþ¹ýÍrÿ+ÝCÔ>1ÎÒ§,LÅqÚèòþ·²ýïÜsÏõ÷ê²dìØ±I»ví$饗šqÇ6OÓöüwôÑG=D…%i¥,&ÐÒ–Ió1Eœ6мýW¦ýùüiÏC#FŽ4c†øÑž†ç¿Ù³g{xnÿ` X3üïËÑ_†öæû…µ¤ÅA¢DL§pÍПµkoi‹ÿö·¿‰}8fƒQr0ªbýñõðâûÇíÝnrKqŠaÆ1ÇqÚ¨ŠåÆb+e1á–¶Øh‹ã˜"Neiùøi¯°l€?ÿ*)ïyx‹•7³«š¹É&¸I'a•À07üÃáRæˆ#Žp/¾ø¢¾ï‘؇—^zÉúâ l„WË{̱X¹ÐÆ-[Zîºw«K†¹8Üýß_ÿB‰‘|0À»wß¾}°yÚm¬VlïöÙg_,Sp˜âöÜs‘’Jã´ë®aÊ”©˜1Ü òG‰ü·0k¾í¶Ûzl™{+PÎ;ï<ÉsS|‹[µJ÷LïgÜ+¯¼ê>Àê$.Í}ßjàû¬Ú…)Öí6Ô6k‰U<û쳫)ßùî¹çž¾Lejê­ <ªL“ÅúsÁQ8 ŽáÚk¯u»ï±»›5k¦ûß ?pïýnvM_wÝ‚'×NXAÄU-L÷ë×ß9©2w~Aã¡ÍŠãê ŒÙ—Ñ>_À>5±råøãÐ>Xå´+¼ºwXÚgÛ«ƒþò—¿ÖQøˆ¤DfS»Þ¦›ÛqÅǾ°›`ðÁhŸ½PÆÉÊ«7ÆŠÀû@ݰ²„D^t‘p1ý«€uƒúõPB+ª®—`Ô±ìjoPËU«š®D¡j k®Ô5¦Ð™™˜ê ,¬ ;œtòI®Q£Æ°ÍËò}éDœpæÙgÉ Ó_û‰Ê|îß}Ý­·ª Z·j‰_ìöÄàÁôÑ=ƒˆÎ»¸ô@¾ÌuØygw$VðpUÜðõ¬T⊞ûï»ßÉ“Ñ@Cý_zéE´ï?øæ¸M›]ù’eX^Šöù`¨ôŸ;ï@ÿù?ö gu¶üZHì3ÇV¸Íw=øvÛŸ(¼iPµ2²HLš2Ùa2ÉÍ£{?í¾ûîAð‹ ÄŸ{=Ó˽Õ`B zêϺ±o×Fß>†}«÷¸‚衇º£nÃܬB»«ªþò—¿jÅPŸË¯¸\ü€kà×{@ά™³ÜGŸ|ìþ…åÐü¶º†ù5ätÂwó²Z òú÷ï'{™Ñ.^ˆ2Ëü:8àèsõëÑw* f!Òdõ/ìÄ›ÿ1­Aû?í•'=Ö¨|¶DSäòaFµ,¹ýÅã²~% V‚µrÿËŽ¿jëU—èxSäý/xyÿ[sûßÓO=éî½÷^ññ>}žu 6ÄÊõEÈÓßÙÊ*ò…s5pæ¾&¼Lòñgu&G{CµÜ~{iìwß}×UÅ3r[ì%ÕaçèßìäÎMÆs^mlJNß-K\€ºåíOó©eV×ç¿ê»ëÙ¢yséå¬uÜ»™/1ÅÚÛþ³fÍÕ—áK‹ÇëéFƒ¯’ªºí¶ÝN¾iP¿ðË×ÿ½ÿ½ëÎýã9«&Þq>´“Úî‘é'Þбu=¨ Š)–/µñ?›7*Œ9·tV AËIç‹.Æ¢,¹îúë’C9„fJ0y"xÒ<óLºbhÌè1É=÷Þ›|7ï;pÁŒ”gŽ—}YeÄöi€…|`Fü‰y|“’€‰ñ!³ì¶bè„O"$áJY™8!Á–À­ÜôéÓƒü]:tHXŸ8\ýõAþ‡ÃY6 ¬õ÷¾•<ñÄS©üM À–”8Õ?)„)[%‚ÏxŠ(/\”¼þ曡@ᯠ—^Ú%´E(\ü1cG'÷ÞsOòÝwlŸ4И$ûsuX&€W¬?ÛßÚGÕÕ«”±¤ÄåI—K.Ië–!ÈHÈdpL¹÷3±“;o¿#¨EÂBýS£éYyù–õÐdò”IÒÎô·±n̘1†ßeÉñÇêËÕQÀ„|¬ý©?Ë?ùä^\$Å'ßxýÀçè£Nð «—SžÌš93Á¤ðÊgÚ´i—$cÇŒMî.lðœ¿p!üZWÿÔ«× Ð¿üÊ« -‘õûßÿ>Y¸`¡âPföìo“]wÛMpu°â, ÀgbAþ=ÇÕ€˜Â÷¶É;ï¼£ 3}´óæÍ7R/\8_ûlR¿ž÷–«i«t6_R~ѢŠ&ç/KÅ%ÞwØÿ Fš¨ªa-)ñŠ÷?“-üãŒ[ÉFMÔ`¨]„ñ\‹!%ÄeA(—ÊõOÍ£v‰¬cI‰sû›9 -–æ‹SEeˆa¹ÿ¥6S»DÖ±¤Ä¹ÿ™9 -–æ‹SEeˆa¿dÿûä“OÂ}›Ïèj—rÿ<á’K/ñ+†QÚÿpÚ‘Ü“ù\R*Ķ<1ì—lÚãçÖÿ¦›n‘öã;Ûå®»î<Ÿ³^Â{Ù¬og…<¶0&L/qK¦Ð8UD@ û¹õÿ%ÉÇÉÞ{î%ï_M›5…ï¡OG¡É"€o°Òý?nëR鈛¢ˆa«›üOLßÙØä]•±ÿëÞ½{¤f¬‰~OȾÙüCK¼›a ›´Ìj®¦uL=‰W®ýíœwX6LWáÀHxou²w wKçOô½žîåž{î9YU°ï¾C„&õ¡Qã†îÜsÎqëoÀݼ˰Zh©ûrÔ(ÙÓ¦y‹æÒT°"cŽZ´`ò¥%ñ®Ü»wo¬H€L `ŸJ4:ƒGxâ¦NýZV?ÈJ l®7{ô´jÝ’„¡ÜÿÞ~G¾Ñ¤ª]o»-¬¾¡Ê?ÙïCÄ™ö¡Øo%bèϲÏ>û¬;æØ£ZëÕNÞõ–8•ϲ‚­  ò[ï°ƒ¼ûþ»®3Žå~4˜ ‘"ëÔ¨îöÄJ‹L”Û_åAþÅòmÕØƒïUm·uîMóŨ/d³®f˜¦þ“Ð>ÜÌ."úÖûÙgBû¬ˆþ¤Q:$*П|-TEãöíÓõåf`cíó.8?-O¢ý#î)®ý]¹Î¢ÍÙXÔ¨Q#Ø]Û¢¬¬Šë| V¦ °¾ÜK2RÿÐd‰ã/u\5£zéUi•¨ÿ€~Z×[n¾Ù¯xBúÿê׿v×]÷wf„æ¹ÿ<'1/ 5tdÿÁÞ@ lŸ/¿dÿàš7CÿA˜4i‚[Œ½€bE]“ûtÕX·ºf íÚºí·ÛNêLùÜ—à•W^]â¸òïœsÏAJu`ÿç÷¶;c•“ ˆmÔ°‘ÃvômÝëÊúö€¯»æð®PZ¼h1 °ReX‘¶ƒ”}}ªs—ÎøFúC(»zõuÜ{ì”W@8°¨Ïû*)'d”¡ÈU“VVKyfjÄñt(T¡|ãŠ"V*#…GX«›×?`L\.ß[Õ ’Û?÷¿Ôè¡ÿ{Oa' ýH “÷?±Dd|üQßX Ç_ü˜'{trÄäÊøë¯»n¥ü?ÛÛòñwMg|3MÞ6ÆÞQìùýû ãሑÃ݆µ6 ùofàkôüùCí ²zû?÷‰øÆë¨dânºñF}N·1šèÈÿËÿ[´ØNÚ”_°\ýuî¡=è.»ìRÙÏ•î‹{ËM7 Máó?²8üîðã¼xßO:ñD׸Q#¥çæ,ŒŒu&ƒGXÃI\üþ](ŸìW·ñ'û3¼Ÿ³ §Ë.×Cû`OÎ;5j¼5­Áa%¹ð “eË–Jž°§{õJgךöõ´ä¶ÛºúïžÅBVì€2_s•„ CƒzØaWá*†€@bë­u!|ã÷ÒvmÕºU²+lâ@1ÿûµAF:uîكͪd¦°.òœ1´:\sÍ5RÜäÃ+€+K°ÁtÌ6¤#5V(È*o^#÷Bª_¿^¨eÖ¬Y392yä‘ä›of)ïW®Êa»°þ"¾Ìd®P¹µ+Úg'ý.î›Êõ|¦MŸ&³ÓÊí£4‡ZlƒBþ±ÀKð –ÙÕx…:ÌÖÕìècQºˆM ÎÆ{ á3®ˆ‹&ça%•Ôö¿è¢‹ž<§ ý æ/8è ß }ýú bs~8b1ØåŠË¯pòbûàs½zk»zÿ3ùŒ§£}<ð@áSþBT!|f&ø:uÒ=†d?öØc¡QÑLûœ}»+|§}{õ±•·‹¤QWÒXÀ§²?™Ñ1®U«vÂ~þHG’™X=“CX—.]‚þ1<¦gºW(ÈJq•@Ä Ãÿ¬î¹üR —mËÜþ©7¦)ó 4. ²RP`%1(·j4•ÚÝRE¸@A6·?, 6)a˜”û_j ¦¸¯$Ÿ?ø¬:uês?±ekYŒC—aÅPZ,õµ@ÍÕ麗åÑG¯Š‹æöO­‘¦"Cúd®PMÛ¤"Udÿ[n¹EV‰sÅCŸ¾}Å/¸7&÷£Á§úa9¶™(t‡,_D—¸Î)ó” ”ÀJ bPEú§œ5—H  ›’”@Ä Ÿ[þÿûßðü{&žã+ qSåRê"÷ ô^ î7Ìw}}ç¨}µ‘ê“l6<ðÀÉqÇÞ‹øu÷ºµÛÅ`á Y)&°ˆôA‰nr-. ²Ë•Ïã·¢@›Ä3\ÌëÉN=ådwÕß®"+%þ€I%b5È”$÷ê«ýpšÑþ†r­[·’=X¸‡Ëƺ‡{ôi6‘¯Eš4ÞÚ—åª #à &K+Úäó$Å'ØÏf„»òª¿av§±[é÷õ×Ó…µš6ýkª‘²öyò©Qƒ{ùDò—¸­CÝ<½¯’PÅ¢P$H 2ÇâµI b/¤8%m¸ëýLo÷ÔÓOÉž1óæÍs}°Ÿ ~™ýäÞ4<5Lëdò¥~ÌH‹--ÿU¬ùí©œ µN»jÔ°!öúÀáe\X6Ò_øj`B®X¾B¼|Ì  *2—„ìg,Uã…$àv•œy"@V~ŒñèTe€+õÏʧ X z…´¡¤•õ‡ºk6`Uw­C‚ã#¹P¤üSù<µaã‘oúo(ß} ‰›ûÝ\$´l¿W_sûGý“³NûÏ8ía)dþÿ5޵dÀ®©ü"û;¿@úcy)ÃËÆ›lâÓZ6Ö¥°ÿ½†º±o“’3ôÜg«Q£FèÛ o£n@²ý-A¿ŽÓÿž_÷‚_D;oŽìUÅÝÿyJ[÷‡»ãD3úuÜÊÚþ <Óz‘7+á)N±ÿ‘&ų™y9Y„0LA?Žÿåòsûçþ—÷?gÒÁ& h)( ï?0R~aãÿÐ÷‡ºGyTôç¯Û<¹7v缿Œóï@<ã½øöÊ„C¥>…rÿ£ñ²¤ ÜÿVWÿÃÈÒŒcÆŒ‘f< §/^¼ÄaÁ»ì3Ä•ölf†úõ·(hæüþ»&Ü_xáyÙG–×'mÝÞ ûîÆá6þÙøLP ÿV[m¸Âf|ÿÙbó-Üœçþzå•nîÜïäK¡‹žÿ1¹Š÷9¾Ó&îÌ3Ït×^ów·ën»Ê ¢SO=ÕáGtÁ-O>åªüÊß?ׄçyS”eQŠ·Vëð"©7 æËÜQG Lâab }û]‚ÒŠ´”›5ë[÷ÛtRûø¸Q£¾Ä'QÃ]×[ou矞۩};-ƒ–³²YùŽ f5€Bª¢òy•¬ÇÆòùr9lØûØ öB©ÔÍ7Ýäø+B¤`â¶ÚjKï0ÎþrŒ|"·Ÿ³q£ãE "¿ Ü?ßå’.’•O±åQ ä…5ÊkíT«peÂËgy a²K³±þܰùìsΒϺøy?ÝÃ,&ËàØs±yñ‘ØLn’ŒåW©Ê¦TëpÓáŠäãÛcwÀœâfÈh|¢ô!^Ú»b#åóÏ¿ÀµÃQí²i”•Uõ,ÉW9²ýU®°2¥B"?HÖ2äà ©Z""sPŽC¬¿ª,9ËüFùRxj–õg^±Åò¥Ý%A%7{¯£BùcÆöê–¹òé¥Ê4 X™þÚù j$t¸ˆµ7j,©á˜¨ä'W!xýÇŽëå`#óí¸`™l6¾?ûêÕ¡Ã.îKlÎInÆ~Þùç»¶Ò>žwîF}¶ÞZ'RGúd„ÐÓ±¡\™›‰ Þ8aE›ñ3³/à;dznôóá;m›*¢#ñŸ<6Æ&éçœs¶sSÊà×ÏÀ¯<ýúè#r“'s²ŠSùUù¤#¡Lޝ7 A+ÛþßÇÿbýãšåòsûçþ'½€ØCØá£«ïÿá-EJ#„¸Ž¿qÿÏû­¤vÍÇŸÔ3Ä*âF´Š…µÛÿâÓþôù:Cÿ‚ž¤†ðÀeK± Óâ8‘xoÖÁKèóþg¶ ÆR;Òx‚âÅžÿ< ÑÏ9þËóªÇðÛT[§ªL ±†ÿç1Ä‘b‹Íq@Gôü›?ÿ¨eÄ<¸¬Žþÿ<&…:è`iÃvvÿþ÷ܺ뭧îè+þsú«°¦Èß°ö¯PY©±[Æ—!ÿþåÍXÔþ[7iìÎÂ#ì;oáÀ«9sgKš,„/¸¦è/•þžý_ÞºtŠFUÆoÚâÞiGjÞ¬™qô—£ååšPÒóé˜æ·zvÓ¹µ[Wפ ^ZIècæ73…–ÔKå“ ˆ±Œ'Ò²Â@“deiʬ]«¶¼p¶ÆéM7ÜpƒÛ¶¹~oxøQGèÊBþ®»î*|)¥Ç#˪ Õk¸uk¬ëªcŸø«ŽtuÖuU9) µ†!+òu3ý¥O…ÈË7@1Z5á*ŽØþuëÖÁdÜQîñÇžp÷ù(ÈãÓ?VR5/ œÎD¾ o ~;µ‘üw°Ï’Ò%î6Ì@7Ùº Ð*Ÿ¤ßÌüVË ."D%Ï`û?(T¯$—º“@3LI¨× =9ЧÄ 3òcý9[ÛþÓ¤É6îš«¯‘ªÄ5YQùZ0-Iù¡¶èݱþ”ÿпº«¨é¶ÛÓ—bIÔŸåµ”áT'/Ã+¸ËoàoÔàÞØ—Ê‚G»‡~h-Ój‡V‚~ûwU6ˆð)¦Ûz›&€§ò±iu*_Àe®é6M¥ìœ¹ó\¿þýCyyÓ›òuº:H`¸l»Ý¶råßró-nÖ·3‰BmÁ”ub%f|ÃïÓUþ»ìÛ̱n]»¹mà;†#ù œ4&!*ϼÈã:uëÊçã?îî½ç¾P›jÉßBN¬S)ï¼õ–¯‰‰¬¢Õ­Èÿ„0’Ϻzõ„QaûÇú«ÕŸWMåòiÔ*¹ýÕ§¼w;Œ•zN1š8õÈÂþ—û_ÖÓR+æþ§¶ÐkîìAÞ;Š;ØZÑÿ:´oïp€Š›ÕBÓ§Íô´é3Ü üá³t×ÜŸ¾{Ög?ÝõÂI»h–¢ñ‡§{2?ÿä÷?XF‡c?Ș¥|VìfiÅýÔý¯]»öŸŠÏ÷|´§T7öÿ=ð/iF®ãéÐÅíï•ð` ç÷¶ýÏÛþ=ØÝu:¨Z&ÁÊ•ÝpBöËXU_Sú«z›¶¦õúSûk°ºÈçø3Ø“xY¹ï´j 1>År}Ÿë+i.iÔ°‘ô•ÅK–àË‹nÉ’¥2úÿÄñ<É™ÓðÎU«¶Ž”§¾ª³dWýY›UÞþÅßž¥lÞ$ò:b!ûÀ-Å& wK·:õâC@ò'ƒ]ðç ¡)ð±ãÆ&gu–ÕŸãO2ƒ{ØàÍÕ‚”¼sçÎD¬§ÈF‚ç÷µ,ƒãå•ÞÓ|ôÑGAÎ~ûí—,[²\‰#¹E6å_‡=‡æÌëeé¾.×\su’î¹Â2ZÎtº{žÄA±zµªÆxImòçé#èD| ÇM:ì²s‚Mý‰i¤+O°¢"9âð#¼Ne 6ò"”SyòN°:b3aœ7Ohxò– 'Ü/ˆAÚºSÿ?_p~‚_¡>´}ˆó~¦{Øøº"8¾ ëÚǬ“Ò³‚ >yèT ë6ï;µùüïæ%ï£n<-. Êkt´zX¼§ryI\ó•Ëg¡Øþ“'O õ© ›?ñÄbò¿ûž»‚ÜÝvÛ߬.SY^n°rÛ0­L>6Í'uÕ¬]§E¼(}fá¢8µï»ý$ôQÏíófÚ´‹üqcÇùöQù´ íÆrŸ~ÊSJ´Ýø9Ž-…ÂåÉø Ð7NTÚ5݃H¥]sõU¡\«Ö­“!C†$K–.ÆÞaËž|Â~…’œöGAê;*ÿ‚ ´o“Ó¸¸oßÑ68qbR«f­dgø5>aŒü:IæÍ“ºÃ®lsó(QB_{íÕPGúÎ\’žø5N ˜6rT‹´lt:nÿ@_5™iIòÍòö¹@¢ù,M@Zè\~l9Kg­¢æ2sLû|6RÒ€­iT@g%yú¨’Ž9¦}>)iÀV€4* sùÞFbKg­¢æ2sLû|6RÒ€­iT@g%yú¨’Ž9¦}>)iÀV€4* sùÞFbKg­¢æ2sLû|6RÒ€­iT@g%yú¨’Ž9¦}>)iÀV€4*  ù­Zò”Sì1t)öŠä5þ«düøñþ﫤1öçäóؾxö8ž/^¢ÏƒZœzeuó¹À]óYš€´Ð?…þVUâµTþÍ7ßž}ôÑ/Èò¾e§IóŸï´Knï£â–ÎZEýÅpÌ1íóÙHI¶¤Q•äé ¡xfçû§=Ë·jÕ21bx2räÈÛ£ ="ŽôHÄ|FgPN«F¾V×êfܳ¼c¬QX-ÌTÊ'º¢ÐŠè¯%b Lû|6 Ì ¾òÊ¿ŠÍ:îÝQN8ž3gŽà'á”òÓN;M퉾ð§?þÑ—+—}Ø8þíÔ®¼oãkÙŸhøˆ“«®Jß‹vßý7Z¦ù…uôBÒeLý ùXQ ËÕ+oɃ¶½91T"àódŸñ›Oóå:ÞÄ™Æç&Ïæìu±Ù3æÝ$Ï vcù¤!½½t›È‚jÇÕ7ù|ù- 'hðYY(Cù„ãG—¢º¢J™ú3SH’'Ÿz*ÔWmU–4ÀK>_ØMÊÇç7%%•c °-cÃQžõfž›3à“9l¸­›q]Ü^œë&2L˜áó+­;ütµ1AS¸*S!.b+ÊLš4Åë¨úX=›½è£Ã† žqy£]Q|Š ó­”¿êÄ<'^dÓ4J‚ E¡ÿø èÏòæq[M›öuÐùüóÎ+©SªnX)†ö q°ºæÚk2åRúTþ£={ŠNRÅòYÿ:uÕ_X޹›}´o'ÉÓÞ¯ Nºúõ0ø±ôëØÐ^³e¸YòèH–3ÿ£_ÿ?{߯Eqµ?—f…¢  *1 X“hŒ]±k,±Ä¨ñKÔOQ£&ùŒ&¶ä[,‰I£&öØk¤HÑD)‚ˆ€Š `¥ ïÿ”yÎÌìî{±@¸Èìö´gæÌœ93»ïÞ-(‡¼¶@T0Iµ,”^•èCEuÔ°.)ÏBI‘ýKH’¸„ ÖQÀb¸¤< %EŽ¿„$‰Kju,†KʳPRäøKH’¸„ ÖQÀb¸¤< %EŽ¿„$‰Kju,†KʳPR,™øãüï§g§†ØŽ—z|çЃòÉŠ[ÒÜ¢ñSÈIyJŠ%ÓÿzM)¹+)–Oÿ}ø!ý!y;[WþÆ‘©ü.¢ó§RwKŠå³ÿõÆ»¨/u·¤hý¿öšk“ß2WýïŸøü—ÇvôèÑÅnÖ•KÝ-)šFÿÓD ÝIšÔÆ]zé¥QÜô7ÿ†ç8ézçä÷ÿ1Y|P…W^ye2W0o€çß«S¯¼új½f™ÿÅ1IûY()š^üéQ2 mr¨PVdÞ5kFï¢cJ«-TG¡ähbÞ+š5W ¿´yØÓÃÜŽt olæÛZ»m²‰ûãŸnp#Ÿåµt‹–” þi0ßþ¸Úðè½;5pÛIѪU+/ròÉ'Ë‹»O_|¢½ÞÖšn«üǽ÷¸_üâþ…SÎ7ŽÚ8M pÀ®?}œ+ÃÆþéκˆF/v+´ ˜z”ýkÕ¿IœU8ü°ï¹wé³’Ð#St!€JÕÜ›ôÉøF–ªY÷›Ë~K/éíWéªÆêñ'·—•1ˆîÖxÑ ]p¾ÕÊ<>Cm|Ç}çÇ‘nàñ¡ëV³¾"þ­W£[RikñbÀýoÎm{ì1·ï~½¥ Ü7no<æóéÙùbþ}áÚs¯½lìÏÿå®e‹V¾¼ýT»ªøëZ«Åi•;£2ÆoKïÏyõÕW\=Dþ³sÎÞp{¡ì6ýêWÝÈ#Ý^ü^'MWB³çŽÿÁñnâÄIôî«ÎÖÿV<è¹Vº[IPì›Çg“nÝèqBŸQŸ†¤ W]ý{úLãÅOŸpŸû³ŸÑ­¨ôJÚZrNsºùüã›ÈÏÿ¿ó]Ôu=é1LÞ´eÊÑ_ÝhòwôQG‰­UË•ÜPiÏm­„¾>æèªûã r£dn« 9rè÷8¯ß£G/pméÑH®ò[oÚsñt¡‰eû­»‰^ªŽv‰3¿ãw=Á¹Ó›rÇ7Ns§AúG_Œá%ž‹hKŸÅü‹+ŠÇß7!67Êgÿ9þ9ÿxŠäù'k'ƒ&„_7TÈëO1.><¬ÆÂOÌëoˆK‘kÑRÏÁ[´RÊöpü!RMã—F1NGy/Q¡â/"iä ª¯ñ]òëßêk¬áú?ÙßÑ—ê¨]ôÒYü:G¥ß8ôUeG_2–¿¦--ÿâl1»¥ÙÿŸóòäŸ_ÛòKósõX¹c1/­³[žúŸC‡NˆO°Ÿ{î¹îyz+=Q Wr=üÞRÞá…¿¢Hq«¬ºªNF²ó»ˆù·—¡›! <ÓãèC[ÃGŒpüúœÅù·ÂÌòÿ¾x¥ G¡Wœyb¨´JÀ«-T½YôΓ_~AÞ³Î:øòûñá"ÂÎ+ëYþ¥GÕ•“‰ äÿ}zó¸±c]Û¶m]ç.ýÅ*n"¥leè¸(í¸{Ø*‹3@ý×ëÿÌ3Ü„‰äÈŽôþ ô^¾øƒÍ×1¡³gÍvô™=ùJAûíäkU-Zøw& RKÏœ1“Æç%·ÉÆ»t|´”ú?göl7vÜXm[»ö®ãúè‚O8‘±Žÿ 8úÔ9}}£ƒkOý·Æúüü§Œ?åƒãÍÈÖ¢%/þÓ£—Žî rÍZ4#?›R¿[6šÿ| çþo\1>õúÏ>^¦24­Ý×é…ÖÍé…„åÍ—&çß¼ùó¤l«–-åbOËVülmeé…l3 ûReÛ‹ÿ,*÷ÚÄ×Ü'ó¸Žë­çÚÑØò…ÃÒV‘tÅ_òú“ùóé"S{GwÔU_¤äîa«l~uÿQDh…ÿØîkˆUÏþC,rüC,ŒËù·¸ãßÒX-ü8zÓ0Äë_°—çÿ2=ÿÉñ§ä”µ³rE')kB>þX(ªO_¾|믜÷ѹ%ÿažÏaíkj•éóåëpârþ‡p¬€ãϬž@_ëëоƒÛ óú®Yóâo rþóq&½>É­³ÖÚôažõe­Èë/] ã76ùìññ Y¥s,äV¨©ÀkY5À jõ&ŠHˆX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bÃCP‚Z-eF!õeKQdãÀ€–ÝšF!õeKQdãÀ€š·2£úÀ²¥¨ ²q`@ËnM£úÀ²¥¨ ²q`@Í[™QH}`ÙRÔÙ80 e·¦QH}`ÙRÔÙ80 æ­Ì(¤>°l)j‚lв[Ó(¤>°l)j‚lPóVfRX¶5A6 hÙ­iRX¶5A6 ¨y+3 ©,[Šš ´ìÖ4 ©,[Šš Ô¼•…Ô–-EMZvk…Ô–-EMjÞÊŒBêË–¢&ÈÆ-»5BêË–¢&ÈÆ5oeF!õeKQdãÀ€–ÝšF!õeKQdãÀ€š·2£úÀ²¥¨ ²q`@ËnM£úÀ²¥¨ ²q`@Í[™QH}`ÙRÔÙ80 e·¦QH}`ÙRÔÙ80 æ­Ì(¤>°l)j‚lв[Ó(¤>°l)j‚lPóVfRX¶5A6 hÙ­iRX¶5A6 ¨y+3 ©,[Šš ´ìÖ4 ©,[Šš Ô¼•…Ô–-EMZvk…Ô–-EMjÞÊŒBêË–¢&ÈÆ-»5BêË–¢&ÈÆ5oeF!õeKQdãÀ€–ÝšF!õeKQdãÀ€š·2£úÀ²¥¨ ²q`@ËnM£úÀ²¥¨ ²q`x (è«¡ÊñGH4`¢œ²4Dr!¢9ÿòüãäˆr)’×?kx>…9³yýáåõÙ¡ )šSjXUT^óúËåJ^uŠäã_*x¥kJÌæã‡(š0¢5E +°ŠÊÇŸ|üገr%tŠøãO3$&Ž=V&—w,Zò,¾` "!,M<Ý`¥°ósú²ù¯Ej~¶_Ê8¯c’ýûh,4^‘JXÄ)âšãŸó/ä8ÉòüÓy•×?޲ÃÇ„I^} 4yýå8Ð/Ä"o˜Âš?ùøò\>þÐT¡`äã¯_L$1^Ç$| 4ùøÃq -^òñ'5'$H Ð|þñEÎ?èCZÜÂÉk´Æøà×'@Êû©,¨•À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lb6Á˜\! ¿ À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lb6Á˜\! ¿ À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lb6Á˜\! ¿ À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lb6Á˜\! ¿ À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lb6Á˜\! ¿ À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lb6Á˜\! ¿ À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lb6Á˜\! ¿ À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lb6Á˜|õP@IDAT\! ¿ À)“˜M0&WH@Â/¨A0EÊ$fŒIÁð jPL‘2‰ÙcRp…$ü‚S¤Lbö‚|È. ^¯L'P]’u ðT%Ù£<}YZe(D"¼—¥$íŠ5Äð쟃Æ QÑxù¨åøKVÅ;D*çŸFqP‰ò&Ï? …Ì Úù™d)„üaE^$ ´ QÑxù¨åõ‡”lˆæ¨‚(nyþI($ƒhç3Ébˆø±"Ï?‰íBT4^>jyþq€’ ‘¼UÅ-Ï? …dí|&Y ?Väù'Q ]ˆŠÆËG-Ï?P²!R˜w  ¢¸åù'¡ ¢Ï$‹!âÇŠ<ÿ$ ´ QÑxù¨åùÇJ6D óTA·Ï1ÿäŽ¡à…ƒÕ"l¤#k,ñq#DB…žkÔ¡#œ±Æ.–²ÿÿœyþÉš/ ÑŠ¢l£ÆáŒ5&¯?8yýÍëo^óú+kB¼0D+ª²#t„3ÖÁÅR^òú“ן¼þÈš/ ÑŠ’׎@£Á‰¢áŒ5Fp±”×ß/ïú[¸0”¦PœQöxV­cB©"ZäŠÂ±*æCMàÔÚ8Xô+ … ¢cUÌÀµ6Ž1°Ÿš-\ «bÞÆ¨µqŒ³ …¾C/|JÜ*‚«b>DœZÇ›ó¯-‘+‚«b>DœZÇ›ã_Œ–ÈÁ‹U1" N­c€Íñ/FKäŠàŪ˜‘§ÖÆ1Àæø£%rEðbU̇H‚Skã`sü‹Ñ¹"x±*æC$Á©µq °9þÅh‰\¼Xó!’àÔÚ8Øÿb´D®^¬ŠùIpjmlŽ1Z"W/VÅ|ˆ$8µ6Ž6Ç¿-‘+‚«b>DœZÇ›ã_Œ–ÈÁƒJ%ãð±‚7¿PÄãÅÑ@IXFFoõ–ØEX¯Òzµ «äGzä %"•oKö¯±A„(xÂj,ãxùPDâ§eØãOAŒ‚‡èFªœ’Ayþin C((Âê\ŠóEÂåcxå§eX“ç1 ¢©òü“ÔÉóOsBAVçRœ/.³À+§8-Ú<ÿ(ˆQðÝH•矤NžšÈ а:—â|‘pù˜^9ÅiÖäùGAŒ‚‡èFª<ÿ$uòüÓÜ@†PP„չ狄ËÇ,ðÊ)N˰&Ï? bD)©)¦ì_Â`ÑÍñ×´@ÂäüËó/!yýñ‹E^mÕ¤ ÉÇ;tðŠ‰ ä|üE΀j  !N!€ùüGccÊë¯E^£œÈëoX:òú–O9w£5Öòñs48E ä‰% YÔfçõ7ŠÉ^éÂ*÷3׋^’“ÊÀË,·ÿä½—1€~HQ­Ô@ýãz|¡P'€^ãÅ`OøÍµ0Ùÿ$“ Pˆd!9ç_žyýÑ•5¬¯˜(^ãÅ`ÏëoˆE¼ÞÒ\J yýM2 B^óñ‡OþèO—¼þꢖL¯ñb°çõ7Ä"¯¿qòñ'‰FúKÓŠ ùüŸ‚AÿxåõWW“°¦ Q¼Æ‹Á¾b®¿ú¹zŠ‚¾Ä§P™„åPÉ hzBŒxrº‰‰ ‰f+ÛHoØÈ±á%VŸÍÿå—_î¦OŸæ¾±å7ÜáGñ_÷¯]XvýÏþ99þËjþåüËù—ç_^òúC뀽WWŰ6.½óŸà#ûÏñÿïŸçüãäõ?¯ÿyýÍëï—ký-½|Z{]ïÆŽçÆ'ª-¶øºëܹ‹7óU´÷âè1îõÉoˆ®G®ãz‰W›\ ¢5S/ #ëháÊ’¯³@¢"j‰êc“®¹×&MpG}´»ù¯7û þ{þ劽t4nû²ñ¿pÑB7㣮UËnÕÖm ab'l$ÇMù$Vø>Æ*+[îÿÂ… ÜŒ3]ËV-]ëU[SNT´„)AbÅgóï“Në—jâºR·&• ±"û—K¸qH¢ÀÙå]³³Dçu•óïÓŒ?æOË–”£­)G£&G‰ã{™ÇÿË6þ–¾`òøþ $çÎúbœ˜7ÿÅó¿ìß¹¹óæºÉ“'».]º¸–-ZÒ(,þø7gÎ,÷úoºõÖ[Ï­±Æ6r SÛX‘çSœÿ‹-ro¼>IÎÿÛµmÃDc'l$'ƒ %H¬ÈãßÇ_Goñó¿Î >±ñp‹%V|9Ç¿FógÒë¯Óù×vuVèóz” ¼æƒ0tÁ£ïM}Ý~½÷s½{÷v—]öQcÇ×CŽ>æh×{ßÞòÿéaOK.É„1Öɧ¯*]M¡eÿ(£¦´ŒH¼cßr–|RÎز”ýGm_&ý'ÿ#†pk­µ–ë@çšZÿGŽÐ¶­·îzrYaÆñÑÆPDêħÿ¨ì²Š?zý/Ÿóoäðán­µ×rëÒÉrÎ?Š@žyýÑ™àb;ª`zè2.‹tX»óú·|®8ÚÚ17Ïÿ&;ÿ?þøcwÙ¥—º­·ÞÊ­ºÊª®Û&ÝÜJ-[¹ÝvßÝ]tñ%îã¹suæÎ{ì1×ý[ºÕè‡Ïæ›Í}eͯ¸o{'÷Ï'ž |˜Ã˜à– Âäù¯Aõ¡eÂ!‘Ø„Ø-‹õoÒ¤‰îÀpk|å+nCúcyûví\ûöí݉'þнýÎ;ÒPŒåRùýåβ蜻˳ÿ__|‘kGãÖþ·_·½k×ù~<‰ŠL6²ßß}M&ÿ–÷øOš4‰æÏþnõ5×t]»v¥x·•øÿðÄÜ´w¦ùî­XëŸ\ÒECÏâî7,â+ƒj½õos ,° M˜0Á3†—GÝhÅá²V^Š™¤)©.Õøe*!d!9” Þ_ða¥‡švj:à²V^Œ&-ÿÜu¨ \–þ§O{—šÒàfÍœ)ÍÒà4þO›þ®j&µM[äG.!!†ÚÅ¿à¤ÂPVë'Ë 6þÜ_î{î¿ÏžÏ8þÓ¦¿'9có‡c¹˜õGþ¿÷îtéN^Ê¿¨FínPÕ—¡ÿr×e±o¹ÿyü1ÉA‹9Búœÿ‰K´Zâ”×_NÅÓOqü"ªˆ*T’ýŠ”l°¾[yåU\›6mÜŸÿôg÷Ö[o¹ùŸ|BçæO»Ü_â1hР׿ÿ“!>8íO™/ñ…‚wèN’·ßžêö¥;þy;唟¸ñÿ/¼íòúߤ×ÿ?øÀAïVå?¸¶iÓÚÝ{ß?ÜG3>r~ø¡{àþû]§NÝyçç:tèÏÿxÝhâëÏ©§ê˜üÀò îÉþý]›Õõ!½zmã=ôP›¦+ÒúWúÈÃÊ‘ãBt´('ªŽ¿|ø¾;üð#äÚF›Ö«¹ûè.¬éü¹Ÿ.´vêÔÉëç»P/ë?¯µÖ7j)Ö½ ×ß¡ÃQ«¸§‘È=gñþîwßúÖ·¤ÈwÜIj54DE.\( =jä(7†þJñ:=»×­[7·ë®»ºC>Ä5oٜꌜP®ºê*7gÎ\wü~à:¬ÛÁM™2Åõë×O“š7ïc·U¯­Ýûèzôì!m¨¹EÒ°fOÄù—c“•ËÞô×›dÔøYêãŽ;.ô‰ùú›¯»{îºÇz~”`×§®>6·=«MañMBøß_}•›=wŽ;þ¸ã}Û¦º¾}ot#GtóæÎs[Ñm½x Û’ÞµõJÚÉ;ÑÁ@õÕ褨ØÆÍŸÿ‰»çî»ÝaCܤ‰“\ jË–ô-·ìîö齯[…N °=õÔSŽúüסþ´pp}<&—\r‰BØmGy¤ëÒ¥‹ð¬[P[àžð”´_Æç º%y×]w£ñ9ص wñ&M&< »Šb0gÅà‡4>Ö•“Ž1?ÆÆ·7÷Úº—ÛÿÀ\OŠÁàÁƒå‚Ú¦Ž¶Í÷ŸÇÛ›´ÜfòǽÛo¿]Šžwî¹îº}:Ý(mCÿW'þ”·>ô {ôÑGÝØqcå¶ÍM7Ý̵hÞÜIJqõ´ý켟ÑíÙsÜÕ×üÞ-Z°ÈuöYî zFÿž{îvÞ&c²Í6Û¸ãŽ=Ö­EϦ¢=Ï ŽnÓþ§Ôqú§Ëmß"øþßïýîåW^–GO>ùd1ÁŽJŽô1à:Ï9ï\wé%q ªû2\ǹçPŠ›´ óþ‘ülúC?äž{ö9÷ /ºùóæ¹^ÛörÛo·ƒÛcÝÉ3!QÖ·rðà!îŸÿ|œæÏónõÖ«»wÜÁ}÷»»ºM¿ºš.È9sæ¸ßÿžâF>Î>ûl÷É‚OÜã>îúêïÆ¿:ÞmFøïÿû®W¯^¾f"Ú-‘ù¹ß|È §XŽyñjÛ|Án¿=·m7jW³È_ƒ[@ëÎ :˜ŽEëÎ ´îLŠÖC‘ùÄõóü:x¨ÄŸÛØ@õGµ]B·ãÇãÏø£Ž:ÒuîÒE0a§ ¡ùŒùµ< pÔ©›äbüµ Ù¿ä¦ HŽ”:f$¨ªwü‘BQ%9ÿ("<ÿ$3 ;M¤gY”: 6ç…¢ }Þù·½ÿî±Ç¡ëø ôŽ VévÛmçnê÷Wwï½üΠš{ŠÎ¿zï·ŸŒÁ“t'¶[o½Õ­µæZ„¨Ñ#)Üm·Ý&OL£¸üú׿v7ß|³ä;Úšç¿„³IÎÿÿ÷»ÿçfÎâ»ðÜót¾Óu£lþñØï±×žôÎÑV2ôyþQ–ÀüÃú_—úŸ<å-ó¿z›6µ—^|‰ðlãÖ×j×^s]Ò®—WÑõ ¾¾ûî»R?ý(UJ{´!mú¦eûõëkxaP\(ïÔ¿4B¤þöÔ·k›ø¸i›$%Íw»víj·Ür‹:ö˜c½-ÄHâíãêpµ˜ÿ[nöã“àB»ì²‹ŒOÜv*1 2ƒ§i|“xŒ0>Ç«m«Ã{ÿt•œ«—!BÈÆWÿ¹sç*{€µ¨íaFœ•¦ñŸ?~mï}÷±>`üCŸ8'4þ\]h´þŸqÆÞ–ާŽk/¾ð‚¹¿öšk ÷î»ÓIŸŽÿG)uv붉–¡&F]ª;–ìa\¸ms?Žb•]TOq£`Ô/*3‡Ë0"õÏš‘#GÔ¶Ûn[jgê‡}qÿ÷Úkïý…QÛGû3gÔè"«õKãÊÞ}÷݆e—7ªkð§jtÛfèSÔ·|Hš¨ýÐ*Fà¶mo}_¾Œ´mϽ¬m\ŽNjk›òü‰êóo—ï~‡Ú?Ó‚|Ìq˜?aþk4~ÖYކÞY=¬ÒvÇ­7¥7²­.kA’¢õÇ01•Œƒ(”wÙ¿…" ‡J62ŠÝäD‚6ÐȬl¤`D¡¼Ëñ·PDááPÉFF±›œHЙ•Œ‚(”w9þŠ(<*ÙÈ(v“ Ú@#³²‘‚Q…ò.ÇßB…‡C%Ånr"Ahdfvâĉz\¦ãý‘Õ*;þÇË1´çV=´~_Å/¾ø"9þóùgöqÍ›µpwÿã.÷øct=·Í_9{˜úÕ`— ¹´‘“{ÿAm»’ÚF|÷-ºÓ]ßÓСCÝÎ;í,¼íÐ>¡*D¬þg…ïÿ¹tGý°—âþêWîÛtwÖt»è‹/¿èþôÇ?9ŽÿÊ+­lÕ÷¦«šüÂ\ÞøŽ!~~œ«:ãô3Dï:uìD"ÿmÁ¹G{TüЉ€;âÈ#\oôt 颾7ö¥ñé @w]í~ñsÙ|}[e|8´u§ç3ƒaƒvÚIôüÂr~6oÖ6Ã3Îø_Òh;äRÕÙq=n›¨¥}ÌΡ(²W a¸­Í[Ð<¾)bâöøMÙHÁzˆB#±¿¹ì2÷ÈCKéÿÏOÜ‘ß?Bî˜9ÿüóå*rkºUûŒÓO÷µkK¤>b¯¼òJ¹­“.̹ 7Üþ‚÷Ý=óO¹ƒê„OÔG.Å¿^¡V k¿Ñ®ºáM¶š¿ãÚ¥­ÔnΡ»°Ðîwkº•´yÅÀ$+Û qómkºÅXâf»©oM¡ñÚ™úKÏ+Óµo}ó[ô~‚½å«\ƒ‡ qwÝy§1b8Ý—‡†:wægº›nºIn·ívrK+ßqÃwöM¤÷ŽBwåÜpà NB£È¡²ßþÖÎÄÕ]x¤œû†ã»9§ÙÚç¬>nï}ö&N{òÝñ÷í¹môÎ,RqÛö"{úªÝ¡CÜwQÛh-¡ÊkýÓ8ð—ù6{¾Û¬Gž´î, u§¯ÌëýÉ?ÿù/¤Ì~ûôvkÓ_Oy1ë?ðI7†¾²È]øKÒŒÿº+Éa#»CÜAM) Q¨ [šÿŒW”w“¬?Þ’ý‡ !XêH‘ã_ˆ“ÆFö>Lr9—yäñÞ¤ªœœE´!@)kÁ"µÆ Ñã2´A!¼>ÇßÇ᥸øÐäüãÐ|†ù÷ܳϺÿÏÿHÜø\iÿýé±2Ì5¾²†°#G<ï>úð÷•¯¬ÉÙiÇŸÎlH‡ÀšÜ}òþ{ï¹µ×^;Çÿ3Æ_Cì•ðZüu"„‡}‘ù?…~ç`;ôÐCÄßMÍï}å§ 6¦»‡Ú¬¾ºBÄß’õ/û~0Õ®>ƒÒ#„/÷Eú/NVÿgž}¦twϽ÷rûìµO9¾¤‰¢[¶‹1Bø¸­Èñ{ Í>îÐvè!‡ 7ž<ÝÄó‡ï¾[óGWCÁĬºè£è*Ž÷>ÎJ#ÄrþaU±éu£sÎ9‡CW£ùµ3ÏèC|Cí¢‹.ª°ÿþ¢§‹B¢£~Öî”;†¨**:aÂÄÚ®¿®6{öl©W¡èÇ~~´IÙŽëw[¼c_\(Ýæ̨$hjôqñÌÑß-ß@CIùÕÛ´®=Zô(J/ñ2ÿÛn¿]­xÊÅ_lþÇø²p§í’©$˜Ûnû»š¤rx •±Æ˜*h"廬ØO¯^[GZeç<¯öÔSƒKz­|QÓ‘~sìt ÞÀN˜0¡vÝ®·ñA¥¾ÃŠëXŸîK6*Ìã±a*ã#•¢f*a¬15zôªÐ¶–8@WVéŠ.ÜX¹k®þ=Œ)ýþÑP®`‹-ùÎW;ì°Ã’ú.¦üF‘¿ ˜òß1¤ýߊî*ãÊF•ÒŇ=gmôÔSdZT»öÚkMÇw¡Åþ™G¾K,ÝÉWµ΋ßÿþšÆ’@CÍ hÌ#zŒËü”Ûc=¬}¸þzºA ¥ôN±Ú¿þõ/õI*Î?Äæ°Ã­}òÉ'æÿý÷߯ÑEB«oú´w¤\|Ç—¥‹Š¢gÜ¿ÝvÛÍêä;u[TÛ3jÛõÔ¶ÚB´MÏ<ó´¶MÔj›0aBíúë8¯ç(àãu‡ïzä-XE´ùÃq+[«UR2òdæ‚p ©UJv1Ò›*Tb}d4ÖkIÐÄ­+¸•¦†¸pl}d4Ö˜ìßÇ+D$Ç?ŽEœNÊWX+T‚}d4Ö˜œ9ÿBªTÄ«"rÇ”*±‰>2kÌRË?¾{|»m·¯ÑWtì¸K¬¬ §;mã¦o¿~f¿ì²Ëj|·4ZÇçí8¦3¥/È&e›rÿ—uü—¥ÿ¡C‡ø1m 1^£wÎØó8òþúW9§ÃXƒr»c^ú‘ì*¬*)"úÈh¬1æ+h²ÿ8IèCP…û÷¿ÿmósذ§ë@£ÚŒ5&ÇßG-D¤V£›Ll¾ðz‰ùƒßMü;ø¯~þ†$©4ªÙXc–ËøÓ¯ úú`àÞŸK†è/øµ]éÇÛ³Ï=+Ä?º£¢6oÞ<ܺ0tG0“ÉH~4èÑG¦‹½l@è½4„ þyA£?“ˆýž{î‰jP6j¦´S/ ¹ÚÑÇC·ÑE!ÿ_|ÂE¡x zè¡Ì7ÿ°D­è?½ÈÏüßx㉺êge¹mh ¨KLŒˆyCÿù"úß§OŸݽT[ðÉ‚J_¨ ô슋/ê–b>ø§÷É#H|¡oëm¶±~Γñ ¥d|xŒèÿÝ~|ªjDÍJ£‹VÃòú϶¸>¾84~ÜøÚ»ï½'Å›¯(ÖyUĈÀ£/|5ÆŸ 4ÈúÿÒ‹/J]\j åÊÐ1ÖNÔÈdÁ΄x»îÚëL§'-æpaˆ.fbKº 2–b@/ð6ŸŒå™òžoï¿÷~„dK@Ϙ1ÃÚvÐA³Q¶€€&”ú¿óÏ·2'M*áx ̯¿üå/bŸBu".W_}e¨Ôs¿ýíoÄÎ òZ Ù?ÚÆ:z™µ:nxÐRŤà¹3nÜØ絬;~#¯µŒæßOqa•ÖŸâ lZ,äó@ÔÏÿ€P.. Ø21döÏÿêåøsâØ€E6•iŒˆy sþåüC.¤9-²”õàA-Óó@.¿ùwá…úc«ž3óqöw¿û]ÿH¶Eò@þƒÎaùü˜î2ñXÕó9ü ú÷sÏ…¢žCÔ@Y ´TÈ1"æX~ãÏ=¨êz¦4FÄF?J×c«žn͵¿â>ø wó_ov¾ÿ!YPJ`¡wÿØcb9øŸNŸ<¼‚‰ÚvÛmÝJ4>t׊àÁþÎ:ël×¹ó.>þ®ºêªîÅ_r§üäiaø®»î¦ó” î˜cŽq·Ó ¨µWÎuíÜ…0žÇ¿)Ž??"³êñÇŸ/Íñ—£éqô;å@y<ð–[n•l(vÉæ×¹,óÿËîÿzìó¾î£ ×܇ÞäÖŸå9þ >YÈÍ—küûë±Ç“ùó¯aOGóǹ›o½™¾ý²,‰šëMgý_šñ§ŸØüâŽý—ßJtöpìñÇÉJÀêÃ;‚ö!T Í4`¤tOÐ"Å]úœÑG.mAïâ9ƒ¾ÊÄ_'¢×Êod.©¥¹o*ñ¾k×£CS@©•ýõ)9pÐ úÔ¦¾›‡;ÜÑ å½*î¿:á}ÔJféÿ†]»–ü§„)úY•ZñÄ(Ï>xó’è=!’T’Ê–¾@uüüãÿ‘²ƒéËTÛÒ=®¾ú÷ôå©£èËxW’Ç÷ƒã÷_€ÓvùfI3ÙSqü ƒô:×.“LÿýE"­Ä0Ï_Ñ–«E”´STÁL‚+ú‡5zUuÿùSµR'ù_‹Þ)`~¤Õþ§p²¯Iêªü¯.ÏäjËgÍžmþµáq/‰§zØU³fÍ£¾j3¦NÕyʆµ×Z‡›)[Rƒ ‘¾æžx‚Övm]Ÿ>gºç†wÝ¿®ë-ÏÛx,¼ÓIk´M¼Õs+uSÊUýÎ Kêø×ó¯ÍÈþsü‘M9ÿ‰<ÿ8yýá(äõ—Ï“»Ò'êp?¤÷þío·ºñãÇËñßx}ÙWCzüYcÕÝ5×\#<}ðáåËüþ>z\Â}@Ÿ7çßÍ·N[>ÞæãOS?þtîÌ´Ôîrw»ï¾+D·n‡õÜiÿ{šçž^ÿÐÆ¿zŽ,Á’Ï?4Maü?5XÇÃtÍÇ¿§_týïÚuC®L~ñõƒÝwßCdÎÿu×]ÏzšŸ?tÌŸ)þva(ýùBëE^Ú~ïÐC%Qù€´Í¶[û§ž4žü—7~Yò{R)Šôµ#9XÑûzä˜SO9Õõ¢¿ä³ƒ¬©’b$kØeO?ÜØ²¡P2F]Іn“'ÓÆEþÍe¿u|„[X— :kg¨ê×^{îP˜ëÿìèkOrÇÂÜyÄÏ›ëÎ>K_øwÒn“0\-­{ÁÃ&4ø·²>V¡2æ´>~ñßNþ‘D/€~çí·å%»ü9w¶Î¢;=äPº¢9¥ä¿!z)ñœ¹ô£œ· ÿ|zÝ÷”út|Æ9zìÎ]~ÅåîÔSy|zÉ_¸x³8ÀÄÏÙÿæt çÿüRi¹2G|UÿEM;Œÿ„‰e¬¸0—· 1&…ê#+lý—òÿýèsžº8Ôäâ%çÍßÿþw‰Ë÷ù¾»–.bÆ› Y#ýŸ0a’Á7£—µóÆwû`{—î óC\迎;ÆxôŸeþËß…õheÿ'N ¸Mà»vªó¯Óúëûò5G_í£¦…úØû 9ïÚ¥‹ôa ½àpá¢ø/VRÂM¤±Â¶Å×¾îÛ¨­æ½59ÉÿàÜúè]s\楗_Ò*IК¼«Lõ|ð!}¾^wÎëÿŒO/b|žÖ+Ýi”×Ûôê¥å¹¼ì;êsƒÞ¶%š9sçTö¿1ÿIëÐ6¡Õñ×^ ÇÖÀ³­*þÙ¿{ —(:9þ›œ‡<ÿlf`‰IÖ_M•¼þ 8˜:©Ì‹#C°Æ0+E"+lÿåù·ñÆëG¨=Æs3f~$­³–Q—øNâ}öÚWþðغõjÒòüãBÃ]öܽå¯ÿÒ Þ-£øÿ7ýwî²¾ýþù~s! ¥ ÔÿoîðMkÎ￯<†4Ϥiž   ñâÂl¿öõ¯5ùõÇn9˜ø'xäùšEñø÷ÍvÐxSüeþµu”;Š>2+¬° ]>Ï?šá÷HÔ|UÑEý1ŒŽÓ#-›nJ×jòC½Yü«—ƒ)F/«õjt÷ÅUŽV¼‰bÇ_=À&—ž|ÁÄ?ëüE ö‡-pì" OœÊ£`—\r‰ÿ YM¾˜Ä_)ãËn¿#=êâ+¹‰ÍjE_øZ‰î Zi¥•äk_LYæ»…šÓ›É¥ü‹»à3ö/8AcG8ñð‰èÕÅþÇmåþó]WôŽúkÐßÜþøª\ ¾òòËIÿÙëz;ZDž¦ÛIëùÿ×34>~»âJŸt|Ð#ÿ8þðÏuº|/rAfµ½é‹h,òúÌ=Ô$—Ö§Eù®¸è"ä&”C\xVÉÕÒöYü3>jŽø?“n¹ž0a"Ý5u¦H¹Ã_d£wäÈE˜¿Rn¬ºšž4UõŸ{Uôÿ—oô½u´ˆÓErHϬ²kÚÜ0 ‡ÐI“&¹§Q³ `D[Uæ;ìø m²±ûåVâY)ýóV¾Û©+=Ç'\ph‹ýÿ]¸á/¯ñöÛÿ÷[÷]XÁ¦XõÏ„bÛÁŸl°åž»õÎ5ظ ™L6ð×êâ³ÝÎG´jj31⌑!¢›oþu92þ7—ý†¡äƒ Ïè÷Þ{WxÞ=C뎯–a½Êm$wöAãÜ»tr$®xçÕ±ÿõ;âBTÓí¤Ÿeýaÿ¾ÌÒFDü'b…ÿÐ{UPœ±Ïc[öŸãŸóÏÏC&´åùGAÀr”ןX'84rE妶þÒä« ôþ<évXÿåÒôškÙ²%ÌuóÐÀAòEUîÿÉ'ŸìãBä3œs!ø— 8–É„+ˆ>Ôññwy‰¿ö/Í—eÑÿ Öß@îcßüØbü_æß>Î|®— GŽ¿ cSÏ¿g‡?'gë­¶rÍš§wÖÇóeYäßòî}š?üä o<âþð\yåÕWíúÇ׿ö5ÁÅs,Ƴ1¶É6™pËáúG¾h‹_ÔT«sÎO¹{µ]¾» aRâ—»ú˜Ôî¼ãN©gðýrëO;íÔÚ<úšoôC¸vÒI?’k R†®;ÑN±a‡ºÎ:ó,¯Š}Ƽš»nØUüÓçåQÑE5zžÚÚµÛ®»ÑËx˜>¯6òÿë_ÿºFÏ[›m}I‰_è÷·[ÿæuÁg¹mâÊÊFËØ>TAÊJúl7}1­µ¼<ŽÅó_LSÜÌY3kôž!i7%dí?ãÇk]Q5ô¹tŠ­\¢$ìÁµ™³f†^<8kv_ŒŒXâ¿f@ý?í´Ójóéâ¼MzÇçDóÁý¥ÇíĦ»Ef;ëÌ3¡Šì`µQ¶'æÉ'ÿÉk0•o — l1ç¯~ñ ‡ùkqÅ é¶hñ‡\aª/+‡¢QÿƒÖ<+  CoûìD_Åã:ùË{óè+ºÁAÆK”9ÆüÆú¿ÿýoµç~,mÂ×ÇØ¶ÃŽ;Èˬ¹äsþ…ít€®õܪ'½|ý±ýu§öЃÖÚµogýã—¦Wù?~œb$v²öØ||hž/_«Yê›ÄŽè¼yü’÷hóo;¥‹F†éN_iFoêç/ñ ¯éÄB^\H]åÅÛì÷è%à«Ó‹ù´õ<üHmÁ‚þrò¿4¶P|,’¯¹qÿyüo¸áOÚþO>i}dÿØk]¥¶]£ÏòJý\fÈà!æŸóúcÉëEµI'Õ~ô£“,ÿ¸íôÈ(Üý'稿ðzæç¨ÌŸr™/’æ8aŠË1ï U¨0)ÔDû*LÿÄ} Žùì_"P\M´¯Âäø§igR1X±ó9ÿrþQ*RJ5Ѿ ³Ï¿s~Ê„á/×öª=þØãò þš(lå‚_†ãù7éþª6Îù/ûÍeô[†¾²LÛä7'ÛG²øwÎG2*Y¡BÀÕDû*Ìr¾#„Ö‰¸õÌ«|Î9çÊAh—ï~‡a¢‘ŸÐ>Hqpõsõ‹è‡ëÇö£Wm µvmÃ`þA ý´wô“Öðϸ®3Ïê#þÌ©oOh™šéåÀRW¸0Z§?T¹m 5^4±Θ9£¶_ïÞ¢—¤ ¬tg}ò“Û¨þÛ¬ÞZœ±Øúà¢Õ[ð ¶.ÚKÃc\dãz(þwÈAZýËEjW§Në×¶¤Ï⢠·÷ú,:¶ØÿBú1¿e÷-$~Š×OΣÝW\~¹ã‚¸¯ŒmÛ¶­‰,F¤c=½wÉwI=I¼d|ôÂPì_ÛTÝG>`uß"´ÇŸû†ø_îÛê 1£e<$W¨-üåþ±{ø´þ5qã’µÚ±Çgþ¹tg}ÕnSú¿ }`§Ú±Ç[ûË_n¬- /¶qôsõšëˆ/SÍeÕ3?’¾$gkÏž[©²¡¿Z¦¡vàJœ9ÿÂÆíÔ¶Ž=Fëçñ Üä/êEWõŸÛôù¢mã¸ÉÅÙdŽûò¤ûÕ¯.”v¡Mh£ö±AüßrëÍR€ý½Deì¢VEÿy!å‹ÀئL™jý篹·'ûë…!ö?lØ02kÿé]ýÔ¶_'ý—¶q%Jo¥¯ ðö1]€¶Ïöz{œçí(ÇÑ·â…!ž|²Ü½û–†aœ£⯸ü ß2q'»ªøkÄI—iÅú§ í02<ŵ-б:_¼büÈþã¸1ɪ\°D£b‘“R)¨u¤•rüãØ¢,&µ,9þ’>ŸIUéå—Ô—Ârþű)d™˜Ô^°,“ü£†$Ç?=në¹ø6«·©½B€°Ñcev æãÿa ç…,·nݺö¯gžñðB/›Xÿu$´©Ê—5è·Q†| üûë ù«U:þü‰zzO©åçÃ<˜çÿr8þŸÐE ž£<†§ýï©’ÊÒ Kjœ¥¬¸ù¯=ÿüýç?jÓKÚeþpœù·Òæ›Uç­‹ÿï€.þraHû:/2‰çwžÞÝ¥c`ФÉÛ Ÿ/Ç÷ŸÚŽ;ìh |ø®þa(Ÿ„÷ ™}VS .’»¸>þ«ˆ‹à¬‚£ÇeÄÿ ?<MVÕÇw=ì»_ÒqñùtWÄÏñsÿ¹Îèb a8ð ÚsÏÒ';Ñ_ï”àºø“ªáV$Êè’Y .ªñ'Í/¸ðWÉ…4´¿}»öµßþö·t÷‡ÞáS¬•«˜2uj­wᢗç…K/½ÔŠŒ£»qøÎ=‘ÐþwÛtŸÉo…Ï²Ûøø’ü£Ÿë;—î$+nIŸA‘ô˜œo›ĸLÆK/á¶ItOì‹è/\{Ùø]þ;½°Uô 9q™@ÊæÑcüEÊ7ÄX(djËOÞ¦F8Îÿ¿ÿ«}ë[ßöùÍ‹wC^`MŸ•'Xé;¡ï|â»…´n­s‡w”»P¥Ï¨s¾w“ÏÕkÙóŽþógç%4ØŠ¶Þæ‹h™=÷¶>U•uñÝ~=·êáÛèãA}ç;žþyÀŒN~ó ùë#ÏkÌî=Â&9÷Ÿ^ønë@ß¾}¥ŽØÿ“Oö·¶þûßÿ¶ñW¦&œ9~¸‰ýpÞì¶ÛîµçG‡¶qœ×;Rl¹Mo¬;J×÷ès¾Œ·M„E5ÎÑ}{ï§eÉÆŒç\\¶e&îSÜÿ2R3½Ê?c}S0ªŠWê²ÿ(dI0Êá*™-è9þ aʱ«§Ibšå%³ø’]ÎW)@å5I‘D("+ª—ÐË.ǟõ˜ø•#Z(²˜ò%³„^vŸ*þüÇŸƒé®VýžàXEž×Æ¥;ʵ:k*ÿÑ%þ”9Ž£Òù.ŸÛ €5¥PÞ*ªÃ0ÜŠ$B¹@É,µ´±*– ×Ñ0ÜŠ$B¹@É,µ´±*– ×Ñ0ÜŠ$B¹@É,µ´±*– 4üa>n+ØÆ9KCmó¯n^4xP­"WToN½¾¨¬&Q&E!‰P2³Â·ÈXaÊeëi’:¡\¢d_²ÓV”å:Šš¤H"‘ÚSõæm"¨ì»ÓßµóÏ‹.¾¸\IAÃå´ϘPV™áu$••ËWi’"‰PF—̬XFãÏóç’‹/¡µÔÿáØÿvàùóÔSO•_G“ô)ÊJæeØi]…ÿÖÑ­hã|„†yÞø§XBDË;UWé(y±-¿Çfí¶kS-q½Ìó+Ÿ‚V+õu‰ÀGÿ‹/Ó»h6v묃¯?ùJ–Rÿµmc]œs(æëuZßµl¡ïsŠûÏ<ÝÚç^zé%סúÒÿ( Jûªþ› âñŸ8i½}{7ýiîfúœç¶Ûnãèb£»FÜÇôâñI_w_|‘¼ƒˆ« )âèä‰Þã´¥bƒ»ñÏvÇÿð‡Ž.rºI“^w›o¾™[s͵Ðx ”üOž2ÙÑ-Š„ÝÜ­¾:¿ÛçÓå?]Õ¦¼LãØÁµïÐÞ÷7©šjòcæ —y™âÖ®C6Çý Mç]tt/ÓËž[¶lå6¥÷‰µl‰q©žÒ6z–½å%ÝmEãHï4ˆ“Íü”þî;!ó¼58ºëI>»Ú¢U+÷ÕnÝ\ ÿþEû2žðÜá¼ÙˆÞMÕ–óšôŸ¦ÿê«æfÏž#kÂ|š?íiþÐCÔGŠEƒBi_åߌ`>£ÿD®·àXý©–÷Ù?GÇG¥„‰ôŸeüsü1‰9 ¼åü«—bšr>ñ<јE{Òçü‹Ït¨ôø£ƒ%æyËùW/ÿ¦N}Û½þúD·öÚë¸õé8µò*+kÈâ}”ô8¾7~¬| t3:Ž6§c=f»Éñ_×~êØWǺ?úPÞ¯HO„ ˆÆ?ã<ÿâU) K<xËëO½õGW Ý£1‹öËAþqÆÒ{…>¤3mD×+tþ°–·oüéãüîjÍqH–:j±}š]RžÞÌZeŸÆû:êX —”g·ì_ãà£+1JåÍDê¨`1\RžÞ–aü/¥ÿìgçºýØßÝwï½ÒicÔPz¼¾LwÐ78ºÕÞ½=u ] ëH‰ôee't’tãÓì¢j“c–U«ì`¨¹Ž:Ã%åYàmÆ?ûÏñÏù'³€v:;eŸLTØògå’jYà-ÏCŽ¿$CÎ?šÉDñéA¤Ž:Ã%åYà-Ï?Cž’ ’#I¢øð©£€ÅpIyxËù§qÈù'É 9’$Š‘:êX —”g·œ‡&šôAm¡zECþ5ÌŒ«*¿g¼zà«Ó^°¤€‚0ÈÊd³OgFêH¬ ¡Úì_bDZ_Áãߦ~ql@ÿ®?ý§çÏ-ÿø®™»î¾ÛÑKÈ%×öÜ}w²ia4žq©5çÒ-Ï¿<ÿüÚ³‚¯?+úú›û#t2äó¿6Ä¡ñ|>ÿ“#G>ÿ¦0øHTdIµŠñ:Ãòù‡ÄŽƒ‘¿Q²äõ—ƒ‘?ÅyRdE<þ4³…6Y,((ò+Ø[é® ÙˆfåBüÇFÿx.x_šˆçBŨ%û÷A$’ã¯Y±„òïÀ Çƒ:9~äh×]wuk|eM·å7¶¤ÏÌÍ­¼òÊî{‡*¶^½¶v¿í6‰È]MUË[äkLÙèÿ‡r9ÿC,|ôˆx.Ïÿͤ¼þùID$¯š”#H›7jI÷lôÿç’÷¥‰x.TŒšrþù Éù§Y‘óÏæÍÌ—˜²Ñÿs.Ï¿ ="žËëOŽfR^ý$"’×_͉¼þæõW3!¬›^Nˆ?ö0(,+ŸíøCÃôQ2©˜+ 5U¨<8ФU,¨fPƒ%ŠHˆX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`ÍSÞžê®»öZwû·¹ ¯M4SGº`´õV[¹O<Éíµ×®¡¿›É¹3f¸_þò|⛹£Ž:ÊmµõVHs+˜Åûó,j]Ä¢®Tå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ6Uy JP€ªfЂً‘5bMU^‚`£j€´`öbdX`S•— ب`-˜½Y#ØTå%(A6ª˜A f/FÖˆ /Ÿ†Ôj)3 ©,[Šš ´ìÖ4 ©,[Šš Ô¼•…Ô–-EMZvk…Ô–-EMjÞÊŒBêË–¢&ÈÆ-»5BêË–¢&ÈÌñÝqsè…Ã+­²’kÑL_2lÎ*-ê(BÊ–¢&ÈÆ-VÉ ©,[Šš 4òWdRX¶5A6 hÑi$+¤>°l)j‚lÐÈ_‘UH}`ÙRÔÙ80 E§‘¬úÀ²¥¨ ²q`@#EV!õeKQdãÀ€F²BêË–¢&ÈÆüY…Ô–-EMZtÉ ©,[Šš 4òWdRX¶5A6 hÑi$+¤>°l)j‚lÐÈ_‘UH}`ÙRÔÙ80 E§‘¬úÀ²¥¨ ²q`@#EV!õeKQdãÀ€F²BêË–¢&ÈÆüY…Ô–-EMZtÉ ©,[Šš 4òWdRX¶5A6 hÑi$+¤>°l)j‚lÐÈ_‘UH}`ÙRÔÙ80 E§‘¬úÀ²¥¨ ²q`@#EV!õeKQdãÀ€F²BêË–¢&ÈÆüY…Ô–-EMZtÉ ©,[Šš 4òWdRX¶5A6 hÑi$+¤>°l)j‚lÐÈ_‘UH}`ÙRÔÙ80 E§‘¬úÀ²¥¨ ²q`‹¼þphÃ2+,ò†)  ”UùøÏ£-Ÿÿ SòñGÒ!%h'‰ìโõƒiàóú˱¡ !qc h^óñ'ä8Éšúù?½cH‡ÏÒÙrÜžn@òy¿' Ô `Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸B~A €)R&1›`L ®€„_Pƒ`Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸Bje¾Œ@IDAT~A €)R&1›`L ®€„_Pƒ`Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸B~A €)R&1›`L ®€„_Pƒ`Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸B~A €)R&1›`L ®€„_Pƒ`Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸B~A €)R&1›`L ®€„_Pƒ`Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸B~A €)R&1›`L ®€„_Pƒ`Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸B~A €)R&1›`L ®€„_Pƒ`Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸B~A €)R&1›`L ®€„_Pƒ`Š”IÌ&“‚+$ áÔ ˜"e³ Ƥà Hø5(¦H™Äl‚1)¸B~A €)R&1{>W®u2¯W²¨ÿ{&ëà©J²ÇE%{y<b%¼—¥$íŠ5Äð쟃Æ QÑxù¨åøKVÅ;D*çŸFqP‰ò&Ï? …Ì Úù™d)„üaE^$ ´ QÑxù¨åõ‡”lˆæ¨‚(nyþI($ƒhç3Ébˆø±"Ï?‰íBT4^>jyþq€’ ‘¼UÅ-Ï? …dí|&Y ?Väù'Q ]ˆŠÆËG-Ï?P²!R˜w  ¢¸åù'¡ ¢Ï$‹!âÇŠ<ÿ$ ´ QÑxù¨åùÇJ6D óTA·Ï1ÿäŽ¡à…ƒÕ"l¤#k,ñq#DB…žkÔ¡#œ±Æ.–²ÿÿœyþÉš/ ÑŠ¢l£ÆáŒ5&¯?8yýÍëo^óú+kB¼0D+ª²#t„3ÖÁÅR^òú“ן¼þÈš/ ÑŠ’׎@£Á‰¢áŒ5Fp±”×ß/ïú[¸0”¦PœQöxV­cB©"ZäŠÂ±*æCMàÔÚ8Xô+ … ¢cUÌÀµ6Ž1°Ÿš-\ «bÞÆ¨µqŒ³ …¾.P/|JÜ*‚«b>DœZÇ›ó¯-‘+‚«b>DœZÇ›ã_Œ–ÈÁ‹U1" N­c€Íñ/FKäŠàŪ˜‘§ÖÆ1Àæø£%rEðbU̇H‚Skã`sü‹Ñ¹"x±*æC$Á©µq °9þÅh‰\¼Xó!’àÔÚ8Øÿb´D®^¬ŠùIpjmlŽ1Z"W/VÅ|ˆ$8µ6Ž6Ç¿-‘+‚«b>DœZÇ›ã_Œ–ÈÁƒÊ. AB Ž_Ü×ü¨ 땪§ç:aU?©ߊ¯çÿòËçÞ}÷]·å–=Üá‡/*T¿¶%é?Ô¹6ûçË0Õbª¨TŠK6ÝüËã_oÔ‚þá‡vC† ¡å|`=Ífú3_Û¶m]Ÿ>gÚ@‡¦ò̲ÿ‡zÈ :„¾X·ºû¿_üŒÚãsÚkŒ5ú¡‡¦2ƒ]ë6mÜ/~þ Óe@Õ’JÍñª·þåü«µzzŽ+l 9þqT4ñ>ç_žMóü/Ìå8_ÏæP&Ï;žQÈÒhÄñÌó?Ïÿ<ÿã?*‡ÉRoÖÔÓÇkSŠI¥<ÿBòú³,Öº0´ˆr2úoZscÇŽsãÇë×·ØÂuîÜ9Œq£ÇŒqo¾ñ†èzôø†[¯c§P“ÕÃføáIú‡ÛϤì`t„*èCãºn´‘›8a‚;úè£ÝÍ7ÿ•¼ðæ·¤øÒñŸ¸ ïËÒÿÂ… ÜG3f¸V-Wr­éÓëò$K!þ‰‹Fú¿ˆÛöÑL×r¥VnµUW“Ç1L…†FbZ{À.E,Ûøs×åø7Uÿçž{®ûío~#éhÏÒvïþu7zôâÒQ bA†Ý¸±äãÿ£ìþô§?¹víÚ¹wÞyÇü‚©ò²”¹ÁµkKe¦¡ !—áü“gÿKüøS5þ+êñ§©®?:WóüËëÏ’?ÿl óÿã¹sÝäÉ“éÜ|ײÕJÉ)_"äõÿK½þÏ;ǽùÖ[®Ë]\«V- §Uyý[׿… ÉÜnÙ²¥[o½õpÚi´)¬?v¾C'îù÷Ï—çú½|CËiF›Yê×ï&×»wo·ï~½Ýe—ýFÌñîØï-öÞ½÷sO?ý ŠÊo;¨' ÿ“DnP?ÞYëûW_œpÕ[33paPDjɲ´üû®øÆ-[ÿ#FŒt묵6- ëj¼šPÿŸ£¶­½öZnÝu×¥k:h6t_`ü›RüWôü«×ÿýöÛÏ]tÉ%îÒK.v_|±Û´ÛÆ2#kNÞ{/óV'ÏŸ·!?êÏÿ¥?þÚ>§®Úªý§ýàr+ÂúSoüsÿóøçü§„þñʺ>†õ‹kºnû²\ÿuõ ma9lÕ뿎³Ö|Içÿ'Ÿ|â.½ôR·ãvÛ»UW]Õuë¶©[i¥•ÝvÛlãX:ÿûõE»Ú»víÛ»u;´s퉶kßAh{£ª¿ïþû¤ƒ¡Ï9þ!aì™[VãV,œ¯y°ýö;P¬æ6Û¤åA+׋ó`Ð@À–xþ¡âeÝÿ/«¾ÐûÃNp-Z´t]ºtq;u¤¹ÚÁsÌ1îƒ?@ø—yþ-Ïñç5‘×ÁâúמÖI]akçî½O×D ¼g–çþ‡5­úøÛ‚û¨wñðÍr|S`ýÒÛçàÜßÿv‹»öšk\³Í%!'LšàF¿ÀñçÍ»aBÅáJô\Wøš¥DÀÔ÷¯©•ËÞû±lµER§4ƒà(±´ý«#jŒ´‡¼úv- ÿÓ§O÷3gÎlrýçÇý84³¨m£Ï?þM)þv7 u,ôscéåÿòÐÿè¤e‡vÐùIû‘#F¸±ãø.DÎÝê­?Ú?DS'—ì•õ&AdÂy=Jˆâ ÄßÚÈõúÊÅ…÷Sé·€Aôÿ ø_ÖýÏþy0‘M:è²Wv©ç_ŽŽο<ÿxàøó&ýh<â{G¸aÏ U•Ùœ{ö¹çÜ.»ì"Ð=î¸cµ fÏžMw¯Ndý³fÍš%>ÿÏ뇢iæßä)“Ýáß;Ü 6̆”K¼ n¸Ïƒ¾î¸c°Ô~ihÈ«)NBCÄD‘Ï>Sü?xÿ·óÎ;¹×^› ámÛ®­›3÷c7î>¿å–[Ü¿þýo7tðPº°ÛÖ:ÇÿóäßìY3Ý´dMÔõç_¯ß$ÄsJ뚨É-{e¿Ôñ×ÏÕûµ/Å›ü¥Ã+fÌœE£§A—Êø]‚æ]üŸBÉq“Í.¦ÑH„ÀÂ…£Ô¿¯%i•úTrÅŽÍ|9!ÿ=ÿÚ r:@Y6þß{ÿ=¥´@Xj ·ù¥ÿOÛÿ÷袕lÜiJÔR…ðIcë÷¯¾úª{õ•WL'øP iB„[6ñ—Jã–¼ÿ±ƒW| >mÿ¥Ì˯6‰ñ/æŸôÁF4¾¢EA±öÓö_J~Áü×?4wqþõb’¿Àþý'>½°8ÿ_Æü_VãŸã¯ÈñG&øåt;þäño:ãÿøc¹aO¥×´qýnìëÞ¦Œ‹è£ÃG w]»v•Ó½SO;Õ-\°€4rôïÔŸüDî$⻉è `rÿþý]zÿµw›^½Ü¡‡:ë¹<þ!$‹&0ÿ}ô1÷4]j³z×·__yl}Ñ¢EnÄðç܆]7”<8íÔÿu è5Åó¯D&k8}ŽGºÐg/6•þYÏÿo¸áº(4Q¢ýØãO¸©Sßvï¿÷ž{ð¡E7~Ü8wßýÿX¡~ÿÄY¹¤òïÔSOµ5PÖÅhM|òÉ®MëÖã׋ÖÄâ5qIù³«iÎ?¹cHÉSMÖhçù¸¢?pðS÷îwßþÖ·~çw„bÌQq™¬T—_¸h¡4`€1êy÷"½‹h⯻MéVÇï;ôCè6¹ÈµÔTsW^}µ›3{Ž;áÇ»öëvpoMâþÚ÷&7œ»çÍs[÷ÚÚtÀîô>#´Qœòå=ÚbÿS¦PÙ›ú¹E„ìDÏgsÜqRF• î7ÞtwÝ}§{~Ôh7eÊ[nƒõ;¹}÷ßßõÞ·7½§‡žÑ%\Ÿø!æ*jÛlzŽWÚF·›M¥úoê×Ú6‚Ú6×õÚº—ÛÿÀ]=´ 7ˆâ§5„¸Â?›ÓM±óæÏw÷Þ}¦ñùÁ~à:ÐcaSi|úöíçFŒîæ}<ÏõÜzkw Å 'Å`ðàÁtAçe7kÎ\7N>и‹/½„ZÄí9>#<ÂuéÒYtæ“̇“þÎÛî ÷5ÇïªáÛ§±I¼“ZŠ÷Úoa´~ñV®÷¡v>úˆ7öU×nÝõèQ§nsê;ÕÍÿ›Ñî¼óÎsséYþ«®ù='¶;ó¬3Ýo¾éî½ç4CÝ*«®ì¶Ùf[w̱ǺuÖYÇ÷þr3üY÷ø?Ÿ”øŸvÆnµUVµøs ï½ï^÷ ]¼Y{­5ÝNþ‘´=î?óGq¤»íöÛîÎ=bp‰Æ ±þyÄîöÛo—vœãã†z¹Oœ×^{›1ó#wâ '¹û©>ø€ûê×·p}¨µ ÝŸoü‹{ŠòëÛ;~Ë{Î9ò~¨giöâŸt5½æNïs†[•úÇÿ÷RèÖÚk­åN>ùäºñÇ8I§l§-D;¹ñÆ #=6t½þs^¢àQ£F¹(¯'½þé&n—]w“u‡óZW(«JN¢o§¢—dOš8Émݳ§;„«¬¶Š`éElNýó;½î¸ív÷ÔSƒÉÇD·UÏ­ÜÁTfeº¥›7.ñyæ¿yúœý×Ñeç´}Žõ'û÷#ãÿ¹æ_οhuÉó!ùuÇ^žÒMÀq1ã…ázÂ&ïĦHÞÙóïzÄdå•VrÛn¿ÛdãMü¦&ÇŸ³úS÷?tÜå;³GŒIç$½$;urëÑ —ÄIw×]w½ã»ÌWoÓÚÝq×®Õ-5ª$ #WíE² Wœø#˺ÿ'R¬J¿ø¼sy<_Ïq{lµµ;‡ò€Ï¿fÑ]œÛÒ£eË<ÿ’ÃI4ä0Æ1ûoæ?ßÄ#¹ûž{¸ÝvßMæY³†fnŸ}öuû°¿{à¾ûÝ“ô›â¤“è÷Bž~ØÂØ%£ÛÈñ·cÇŽŽÿ‡-Ôñ‡ë®s3gÏr­é¢ë]wÞåV¢w·7äˆæF(ûiýëk*ùrž~å-¢ÿ²£"-0Œ«õìÙSh§Nj‹.ªM™:UdúKCmóÍ7þÎ;îò•ÔjtË[­Û&ÝH/##ö˜ße—ïÔè`dŽá–}ñúŠQn‘ôåT[¿¾ýÌý…D0ôòi«‹o½õ–ùç6¾ôÒKÞ®ž®½ö«›®–{^ÛÊ}¥GŸÔÇ"´L~ nÈà!µ¡•mk¨Ñ;™¬m!¨^%U¡>OSR{{êÛµnÝ8nhSJé%¸µ[n¾Å|{ì±VþKúÇ|à€ATNÞrËÍæ§ØöM·#ûñA{µ$Ú5¸r|Ô¿~ý¤}ÇÃm«ÔÔý8PÊÄ17n\Ô7]œAïCSKu‰Bì-ªÍŸ?¿¶ÏÞûˆmOÜÖ4î\þ­)S¬=gœq†ñ¡/ µõ;­_{á…Ì?]|1Ü»Óß#}ðÏ #8RüoBc.Ì*ÕÆkåágîŠAÕæËjÜÐ¥!np°¨Æó‚ëüæ7wô>{ÜqÇÕè*yâ—.ŒI˯½öZÓÛA[¨ê£ŽX0Ý»w7 Zeá±2f ¦ J)éo¹åVŸ×iÿ1¾¶î 6*3õí©µvÞ™Ú„2aìé¯ÒVžwæ–^ÿv–2Ëåé'¡kC^>]jŸuµùJ㺓BÑúSƒðŠJJOSšgu™‚ (¥E}¥ ¥§)‰œ ÀG¦ ÊþããR)CéiJ¢ çøKòü³ Cæ`é2xE¥ ¥§)Éù‡ðXLƒÂ8c ñ~í?¯ÉñŠEñy² ¤ +}aÌ vþCOäø#<ír:ÿÿç?×y‹zš’<þÏ2ÿSN9EæðvÛmW£»þо“N:Ilò{×,ž)µ½;@ž¦$?Ń¿á·ÖC>hADäV”ãŸÜ¶#W¿øÚýJO6/oöÕÍÜ›oNv“ßšìž~”õührð!îáGÖ"ö»ªîÀxŒ¾f6NnQ=‚î^èѳ‡[@w!ð­¯#Gtèv֫霟ÿ\?mþ©&æï¥;®¸â ©w‹îÝÝ®»~×ñk„øZ~þþ6'w4i%tËT÷ïìbþŸ~úiG¯äÿt»íŽÛÝ)§œ*Ut£; úô9Ë5oÞÌÝ}×]îñ'ž ;hFÊWÎyä‰g‰E…˜ûèîŠË¯¸RÊoÙ}K·Ëww¡ºknØÐan§ôn*1Z!‘ôN½\j”˜ù^Püù޾U· õ+¹;ëý> »‚^rüà î-z®x•UôŽ!ÆìGw­Ewg°¾ch Ý™ÅÛgœn“EAmY¯Óz|F:ó]@ ñi펠;Rø.'¾õ¸/ÝÅý@wzñR¿øùÏ©æ´ÿÜdŸ+i|¸å[ÐÝLæùÓÞ;ùñÙ^X¾½pš·þú»1ò*çN§;RBh´öN|å¶sé¹ZÛ¨@ëÕÚÐ]LͣƘUÛ(•F­ N(]âÙe—YîþøÇ?v|—ÍsÇ»_ž>ÝI3S>7~Æé§KÝZí©Nîÿ•W^%¹}ܱǹ.vq>ö¨\Åsò›îÄOpÏ<ó/ßNnmTŽï¾Cü½Q*“n3¦ÐVÍýØÇ€ûBÅù¶áƒÂÆ^tªÖ¨Ì\±j‘š´“ãVì?ªJyËï%ø„îT2t(Ýew“ôù„OtwÐ]Gü×ÄûÞè.¦HKÿ©`ñ+øçûÓlãDü3Ö¶BþW­?R· ¦P/›àÿÑGöy½:çátg!åõÂ…r«õHz:¯;Èk©’üŸtâIî©AƒD¤‹UŽ.nÑxsÜ_n¼Ñ½EïsPœð?¢¿Ö ¢2Èÿ#:ÒÑ;w#Ýe5yò[#K¡è¿¶ß—pÿ}$´±Ù4hНÿ9þ–œ’yþI.H6Txû,ëo^¢hù)&yF»ÿÆú3zÌópç¾JçìñVåÿ¬³Ï”ÓŽ½öÚËí³÷^¶,X¹<þËeþóœ~ü»`³Í4ªÆ_Nf ™ç?‹ænñ¤mäÿôÔ ýÖýë_ÿ’P_pá…®ó¸I“&É“žûqb§¯&™ÿ¤,ù§8‹/m#·ív*[nXY“¸P@ô\xÉ w ýöÞðªâhÞU ð·QTTŠ€±!`C_KŒID5±—”/úFÑD“XcMbo1¯‚]4±%ö¨Ø±±¡€J•b¡Hç~Ï3³³»çÜ{ UwáwvvfvfwvvÏ9{÷ì6TðYZ‘Ö9³gWžzú©">‘qòÉ'£Œj»SUr䈑•«®¹ª‚Ïö 4mŸVb¿öë·4_T¶¿ÕŸítÛm·EUתRaÙ,Ÿ°©Výƒ0Ðñ)¢÷3ÕyÅå—rªµUc"w€À´y÷ÍÅ^ûï¿@8çú€¶õ ³øé[Vÿž={ÀÇG[µqÝÁcõ·ÿSOi;]yU6ÈÆúÇ6~Å$JP±|l@ÿ»üв <£åCù&y×]¾òðŽÄ³vìØÑýìg?Ã7°ØƒaÞÜy²‚‡{Út“ì7vôX7kÎl¡/*é.ì³sà(É—§È‡nmVæñ&Èj•÷¸++žÅê‡-6ÛB³ˆÈ vtAV@°‚—^|©k¶JñÛA®¸rg™E¦U£$ÀwÝ…²ˆ²Q¿iý DzIðú)ÀP˜D°ñoÙ}K€¬\yÉõïßß½ŽÕ;\iEýM›5à ¢o«Æ%ý”K$¢<#jVýlŸŸÿôçØeU¡ÏÃj¡÷Þ}O6&ì¶q7ÁÁ ±9éŠb)Â+¸û®¿¹ƒû 0&/aH@+ìèѺ¾ým|†õ®ÜØ:vèè¾É (Ë.q6šÖO­Øø\²×®M[×¶-þÚ´sm±¡4?y³ÀŽX¥röÞko·ËÆ`ò˜ Rº×Ï d«‹Ï8ó ‡ Ì)ŸÒõèÕ ŸŠ­áöÞwowÓM7¹?ý„E!QG)9”/(ú'£}.Å'qÛ`cº¦M›¹.ݺ¸ìñß>ªâ3|F¤ÁKõ ¸Á´¶'#A¡Ç+Rˆ¢#Iìñåú“èIR†Æ]—.‰ ‚¦´‚ƼúUrÔ¿+>Ÿ¢’‡~Äaq’Ї¼üýïAUÇÎ=é'ú7ÆÆÝL’+²4ü×çÔ¨Ÿ ˜8’`ó³`1‘’ùavHm°¦çõysÁIµ.]º„Où”´¨_8U‰:™"æC¥ Èô0‚ ïõSº=O!Ÿ$ŒCÇ‹EµUÿƒÓO0È`ƒŽ;—x¿Æ„Vz¿æD¯˜›n3 {]ǃ6­ÛºÎ¡­A¶*± Ü€ å3@f@êó´nÛÆmß´ ù|î…Ö9<ÛÒ¨¿•Gb¯Gõ1ÛŸô¬_¬} &2“IJû“Ç´f 5ŸåfKJÌK¶0­afµD¡ƒ–íO¬‚ÙŠ°‘˜ Rº yÉþLAk˜--Qè ­Lþ÷À¸SqÈË~̱Çâµ «ŒÄ¼hû‚çÈ¿ãÇ]Vò@þ¸·’×?VÕ*L ‚%Kõ7üW¥ý­ª>ð ÒÂöÄ~4®ßÞ©ÄöÿªÖ?6¸´~L®Dí9¶ðÀ>·²€[{܃yûöë+š†ÍĹ}É£ØòD‚ù7 &2XRb^òøLAkظ‡˜câ}8\‹á@,‚Ò×øþÛD,a$ ^øâ‘üuŸÿŽ8ìwúé§ EVÌøÌñõW²ºGÿùO÷½Ý¿î7²OЮ»b?–޲‡Í ÜÇDýT$ˆ@…Ä'b) 0³¥Ò<žŸHîbmL4÷Úa9Ï¿àBᲬ>šr}4i¢ÊôŠ6>Æ<ù!Ötº&Ç A2YN2áÌP UøŒßÉ^HC^]fˆï¼ãNüô¤ ÷Þ}¯»-°Šaö{ù!N !èW´Æ’˜~,»c×{aAkŸŽµ}¢}¬h*)f–F«¸Î85„ ßç0%ýöK‰„Í”XlDäU‹Âë'_™w±ôk¶P4ˆùÉφ½žÀ©bÏà„mÝ!?ú‘{ñßÿv·ßv›(=êÈ#±Ÿ“®¨’ÜÌìë_(BÐ̉ãc…™D/†±¼Bñ‰tâA3,ýú/°‚P!`$­7Z\­ðRµe¨i’¹0 øL&MóH}Š~+ƒÅ‰Â/Óþâ׻ï(’{íZw„ˆ ËòŽúeÀg­ÇKÑþ²7ÔSóP¶ì‹Å Iý%éåM­á³*+™gõ¶XIBý2õ—ì¦,Ñf(‹É—õÃGÌî‹Õ:Ùþ©Q›ó¤èàHF4Ç !k•ývÉþ'µ5*Ž¿Â`Γý¯`©îÐõñ+|wÆ]—bÿC³¡ÖLg1OŒõ2®wïÞÆ–ÈÍö_ÙÆÿ0)´'öDa \†å-X»[L¼ÀÖ—,¶  ®lõN*€•æþóÊ+¯8îkÊðè#àT²ï ܯ_?÷Öoº~X0|ø;x·ÞÝñôê¦<=[ªë*%\гv·X´ñòõkÿgpº0MÑ'>M‚”¯HøÊÚKðÒÎJû  ÖžW‚Cb¿ý÷®Ž˜ØÇ$jàK?95'gÞv÷“B½·ë-Ÿ 2DVÀÜ/N<²lx0Ò|ý‚#>Ó¯aÕ=…Ü«øôê—¾c]páÜ?q´6ËeÒ:tà§1p‚ƒÃž"ø›‰£Èëßç³g |> ²@]úNœjUªâMp4 êO_Z¶ÿk­…£&†OV»ðyË;âRÏ´iSÝ~8{ܸqÞÚV.]YbåýüóÏ“‚õ‚ͬwçË3ª‚ï¥}†`3qnöÍÏŽzakÚÈ?¹J­¥ÀZ^Šú™GB¨˜ü¼Éo˜ ”–-1$Ú¿(Ä9ʽÇ8Èk:Ébð—µÿ^}ú:œj'Ú¸Š~ƒ=“`çÙ|üò+®ZÐ…žbZkÕÔ¨Q_V«¡À\¹c+èj·+¬Z/³æŒ5"Ô™ØEÕ$òèÊ¥bû«D˜3½dÿ¤ÿ±ú€H’º'Nš¬°U.’ŽÃ}Aý"Àòª4©+PVgb ¶öÿä“Õ¯A£_Ç'dCdܹÈ¿–qÇ‹å¸C°³_Æ•†æ„*WyÕñþ“Æ<Œ‡ãÓK|“ëÇåÚiüŠÐpuêOf %û Îòb¯_1Õõ7|P€¬ß¬Y»ÿeû‡¡N”ú¿ù”à²ÿ…1‡vQ;!ç2!€äþg–Èýϼ&qxÒ—í÷ß?>ûÞäžwï½÷ºUýöQCµÿ=þ8>¡FQøÌ¼é¦8˜%Ëûµµ˜ŽB*a pöÚ(±W ¨¶1Ç—oÊ¡ýïO&…øìsü`•Uõsz¶š•$÷¿èÁfÚgEñì_)ïüjä;»íÆæ•ÀRýôæ[nikÏû­[3‚†[<ÿ—1’¾ÑªÆÄÍ(4„¯ãø‡7W¼úˆ µÓèÊ…ét\Í t ø¹ ¶r#FŒ“´ž 8ê¡ØM]–ø‹/½Øm´Q‚>4¸)ÚÎk,è×^ªÍä(mjËe2ìw4'ñt­óðíô&›|Sˆûî·/N)‹«„¶ï½ƒà)çÆntͰ*¨yóUÜ*Øk¨Yóf€›»Uš2n†“Êüd†XÕ.ÌYÔ¯e±u.)G24CŒI2ûóDµÔþmð™ÛQ‡Ûn½Åýùªk _e½õæ›Á†&}½°GLƒãIl11 îìvo˜‹ñÉ ÛÇô“ã“ÉSIÐ&ö7ýÌo2,öYjD n½ö\‘NTø9+[ú3sZÿ#Ž<ÂuÚ¨>‹êìÎ:ãL¯Ó¬¦ªG¿rŠtosçNÂîó#GŽt'Ôß=‰I¸ëp¢ÔßñÙHLÞtÓaŸZtÈg_ªWÓkñõ×]ïÕ4ÈÃk·Y[q`’“ÊKKý‹F½ïžÂ/w´=÷ Þ ÆåÜGŽ6‚ :oäÎ:ó¬È§’C9|R¢#Ž8B&0:ÃngJ£FýþŽý¤ ¸=*“Ü%¶þ/T›µÛ‹ûâ¿^0÷ßßñ—HµL,´¹Üÿ[}u)óСÃÜgS§ .õ¿´ý™#Ø"ÅL¾œ ÎaÜù·²ƒë’K8îØg^Êùñ”Òg˜·>·£îX¢ûØãI~ÓÏIŸqÆûñÌ^??ë£DæáÍÄÊÁÌÌcãMÄGˆ¥… *pýñ×E‚×üÄ‹I¥{T)J9²þlïÙÿÂH²$ÆŸR§K’¹ÿEcäñgI?±r¼N¥eøì‹øÐƒÉDO´µAÕþ÷’lçà\OlUиq¹†ûŠ0”BâóŸJRšIµØ´UÇ)Gnÿ`YfÉÿfü€ÓSûú•B8,CN„æ„_ Ùþ©-–´ýSëF=)”r,Üÿç`ß›aë•bÿk‡-O(q¢ÿú%•.ĪKʱpýÌjÏ¿QLÔ¯’Ìk•#•ó¤Pʱâêÿ÷Kúþ°UžrJy¬Á×£þa4’æ­¸¸¤Á^q¤}’ ò®¨ ®Þ=D€‡‰m$ÜœZ Eކ¿õv7L“òÁ`ÓÞŸ¸ÓO;MÞI™Ss“™ASzmä͈'¤”ÃÊD|ß~{»^[õ$(ǹßyÇ_Eÿ´©ÓÜa‡êpR“иÉÞŽ;î(úÿû³Ý¹çœã¦OŸ!4ê窎³¿åV]$%¥$¨£x° 9”7æ™^¹Éê¥Y‰d7vì8× oÛoß+‹G•ƒkúôéî±Çðâ*úÛ°C‡*ý›lÒMä³4¾æÏnW AÍçŸÏp¯`%Õäɺ£n|᧨A·³}æJ¾>øPÛG>$AÊïa¦ âèÔþ¦Ìäòù,æ‘™œ¨S®kÿ̲M93QF®òÒ²y=5¸wß{ÏÝ0ð¯Ì¹³Î> ›•cà”B|1ýªYcnwï½÷HšF½áGy” {Y=…¥œ­?Ô ø‚¢^KÇr+¾U«o•©ÛÑ6sæÂ¯‘áƒÞwÇbÜ9íôßy:E€±‡rüXm}ÄG:y`m̘qîÐÃÃ~SÿPáªXôrè¡3'à^ eècÇŽÆXƒ<ÿø»êñö§Ÿ]ðZÕkF’OId>‘àª/!ª`ÂgÈúËx+3z‹ÓÈ>%Q¶´Y´’â4]îæbJË,qö?3Gô2``ÆhÙHQdö¿h³h%Å}½ý68ûì³ÝQG#]ŸK_…ç½±ãF»aÆé~töŸÿ`ïDþšUÝÿFbõ1­Ø¶þÐ#‚ÌżÙÿVtÿ[€¦¥}ôÑò\нûæîšk®Á³ üÀ·¿ÄƺùdöÁ÷ž<þÀj‹¢e–çøû­o}KÔù¾;éä“åË+%¿9å·§J’¾Ù³§ÿò°újôXÈÏb’êñO Sj~aÁÐ{‡ÒÈÑšÑK§é¯úó^Œ4T‡ã.Oáqõ°Ñ.»ìbl!&ÿüùóh+ù³ãêyäs›6­“¹G¡µÁÑÍ0§üÁð!Ïĉ䙬þ'õÇÂ$ìÔ²@ý;wY‡rH‰sAåš«¯ z.¸à¥CäÔiÓ*}öìhÔÉã=[K™µ.8Õ GÙÏ,È eë²!TÙ«Àme·Ø-iqr.ßíƒneR¿”# ·èÞ=âQV‹ÎPÖ—ê Þ†Jûöë…ô%_ÌL8^{v…Ço[}³},-°×?iâD_pŒ'¦Ué/0†ziEY¶î(}À÷9” Ç/2¿‹/º¸JöZ*ðãW9VÝô”ëoxÍÀ{ª%ã%>èg0!$~@_Øiç*‡zhåú뮫ðøO|¾Wàg¼ûWá0ÑnU´»Zc#9á1ÿ·ú3ÿÞ{÷š/l&ê bAŸ`{‘?µA½úãó)_&íiž Å#ž2O=õTQÈãÙ™æß›o½)ú1‰ô“‰GàZ}ÈW®?6%—ü]»v±Jø8&?ýaÐcú,~þ…"ÿbôÿÈ ¿–q'ú1LJ´Nxþ4ñ#óë•_üâÕåñíjþÊ>‘ËS®¿ÕƒqÛ6m‘ÅÎ綤нª=¿`ýÓòE½A‘’-iqÖÌ–íœÂûJ),e®´0ºÑ,ö™-iqö¿`ÕìÁ)¼ƒ•ÒÁRžœøN‰„¤åµØsXÒâÀÇé”Ò%%UüºåµxÉé¿òÊ+Ãýja÷Þÿ† y½P*V‘Ï3vŸ:þøã½ª>ùþlS ¨²WÉÚÝbO´¤Å‰ÏUÉ[„ý¯¸òŠªç/kWãûŸ %˜^‹ÿ ýUå]Æõÿ*êŸ;wn…ï²öŽÀçÏM6ݤô^ç*§vZ.4bÁòuº:ʨ’·ÿ«â/”ÂäZ쉖´8©C•¼e¨Ÿv·¾süñÇ{[…Bjf‰ªòAbËk±'ZÒâ¤þ,]¹>aiŽîºóx qIACc,(‚Éšq“+à Ä95¸mùO¯ø9?Ú«0 BÂĉ“ÜF8èÏ×^‹Í§_ ¹7\6 åáeU`®â°Ì(³àx‰PQ?ÛNi¤ruRß¾ºÔö”SNU:ÊÓ²E 9Îûw¿;Íq¯$†á8â~òD]Qƒ—u|"ò„k¾Ê*B3ý˜,BºÁ5ibûuë_ÖŠ_vËâcŽVð8PVΜuÖ™®uký%gôØ18n¹d&ó ÿè(ËöoÀ RüFU–‹ü V1ŒÞ–Ø´z&Wß¶zîùç—Z˜8q¢œw-ÛçµWC™7m‰Cc“ͯ™hÂåÇRª@²jx„ÕK ‚“µ¹‡P69Ý‹ã±á <‚q6V•¥òÈ²Ùæ›»ïýàûRJ9ãŒ3\S”ÝBÊO\,%SEý®År|J–†÷1KO?àß“ƒŸt7Þt“;꘣ÝÞØø-ÀUo\y–†m¹_òòsFÓÏÏ´î¸ãÌî÷’òIq¡í¹RêècŽ "Òú:Ϧ›o†æ¾§–†êÓO?SŽ–g¦”Ÿi«ÿf›mæ¾O»ÃþG»…þ‘Ô_Ê®føt’¡icíã„­ÿq&…‚-²>½°Ü2 ¬W¥{Ì1Цb­D`³äúí×w>ø kƒÓÀãáÇyU^ÕšúŸà}¦rýùYèóÏy¿–¬YˆI7wíµq¯¼öš BšÀ¯Øÿ.»ô2wî¹çâsÔV^²s-à“¿ùÍoÜ=(õ`ŸQ~eáÉÌcã±-0výyX7L6Œ–+šC«&ú•PlÏ/SÿÔ6R¢ƵÌúÅ.éø›úS¶?ÌÃ>Œ°¸ý/ûŸ˜Ë_`»B‡+%sÿ;åþg~²øã?WW{÷INà€ Ø#͹bÙB›¶ø4ųåñ/XÏw]M¯¨ãß”dËkÏPßþ~ÇžÇóÿ´£¬hãß- à.À¡Iø1RVR¼‰M§‡á0%vúŽ:»›o¾[Jœ‰dhm©’ïÆÞ@+T¸”ôYW´ú›kѵˢÿ}öÙgÁžm±bhYëg½Óö\ôcÚäÍ_L’:‹héöMX¥.¨?¹OŸ>Õ½1ìM½‘[k-¿ß ³R¥Z^”Fei:…}¦¨T¿”‚o·VÓ•èçfÌo¿ó:bk·á`â'¾ ›¬Úev è·"•uðóš‘#G¸¹sçÉ=\öÛ¨‘í{äsÕÑ?cÆL÷v¯Ÿ‹ —¶ø.uý Úë˾)óõ§Ž7ÞxGuoäZ¯Íö) LÓ)¼pý4¸rãZ²ÿç3fˆ½1KëÚb¥õ±ÑZc?ÙVÖ?oÞ\”ïM×nv~Ùs©!kÉdÔÓÏ’ÂÞBÛá“=¬q·Ür‹ÛfÛmÝ”)“±ôz>6Ÿí>xÿwιçÈDäg?~œ[wÝõ¤_wÝÿ¹#: Ÿ‚•O$¿‰=­ÖXc ²"Íj_NÒ=Æm ^™PXLÿŸ7o¾{ãÍ7\;ت-äÒP§þóæ-pÿyã?nvÈÃï’Kf[ýo,&-G8Æá × ¶ÅP»þ‘öœ·`¾l,ÎÏ7[÷;tØÐ5?H ›V0…½´(ÚÚ´Rÿ.8‚~íµZ/VýçÍŸçÞD_`ר ›Ï5n¾–*HaÕ?}««Ä?6ÝtS?i¬|áZòÉéûŸ¯E•u¤éöYj ¬ÌJÂ5ë_¬öW‹– š¦S8Û_,PÃ$ÙÿÔ(ášû_îé-'ÿð‰²QÄ0¸¤øt€Iaò"Ô@2÷?ZVÈãOÑ¥Äo`—Üÿ¸­ÂìqÉ=+aF‡ 7tk®¹–¸}¸dÿ¯n[?(äþÿßõ™⋯ž&½Æû ÅÂÁÌB³¡;b‹˜à¸‘ V³3‚’õgûßg¿,=ÿç…îÔSãöÚ«/öº·¦ÿ]„SÚNê²Ü\±ÌÎ7Á­»ÞºâÑ×þåZ¬Ž9¶0 ÁcN®ÙÿaŒÅïÿ§þæTÝÄ5<=­ÂýËÏ k(ÊrðÁýJ¥lÆÆâìÁâ8¸„1&PÈ÷Ÿ|ÿÏÏ?6n„QV; “òUJ2þúî#¤Â˜«OŽ‘ˆÄ%÷¿h“ÊãOòø“Çd°èí!Cª\âó¯;"6µÁéCÜyüMŒÁ<þV¿òM….c27ã˘7}LüL­ìr)V³øQi~ŠJd0YM&±|øÈú³ýé%t‹Äw‚^4­×/âßÀç„ô·ÁOç#dbÈZ™Ü‚t;î ŸôEÚ¤ÚÀ#WM&Ø<þäñ7ßl|“.#ÝFûNìAÒ|Rlî´I~þo¨`Å6ê5:Òªµ£VËão¾ÿäûo¾ÿæûo¾ÿú;o&rCÑg ½zš‘üSH~þ ]–ÜóG÷A*\oÐjþäïÑ)•<š®CT!Â$ÇÞûF´\Œe 4>‰S „PÂ:*]‡¨™³~˜'Û?z?ƒÚ®wo7ŸvÑsøi÷›š‡ÏÛøi ßzë­dߦ5ÖX+†ÆëŠ!̺ÿ'«s¬î”ýOgÓ>jÝ.Ä_Ðÿžþùt/äOû?ì߈«ˆ<± öÚ{1-Iý"Kä›–<þäñ·¶‹åþ¿äûîöD–Çæóø›Çß<þ&AÚ-pÍ÷Ÿ|ÿѵœqŒ Îaœ$¿ÿÅ÷?ë5ŒS¬šK{T„ EÛ¦TR¾ªý_jð°eïZåZ³æuÐJ\Œk!? ö쓚¶À¨l¼ÖAG†E@…üY¿ZëknÿqØ3誫¯rƒn¿{ Ä=zöêåŽÁ§bßÿÞîø¾·±øßô©SÝgž!ÛüøÇ?v½À³¸!û_©»Óp_sÿËõ·Þ£½C®…Žbô<þ×1K4Ð" B~&rÿS;äçqñ‘‚£xó ªƒŽ ‹€ ù™`Èþ§vÈþ'Î >RpoDuБaP!? ÙÿÔÙÿÄÄG Žâ̓¨:2,*äg‚!ûŸÚ!ûŸ8ƒøHÁQ¼yÕAG†E@…üL0,ÂÿâæÓ…Üš7\ñ ½|ŸˆˆË‹‚ÌÀPˆbkÍÎ%ù"c‚ô`ÖŸíÏïc—–ÿÁ¿fÌøÜ5_µ¹œ¸Vå€Ùÿ²ÿ-Mÿ£Ãåñ¯þ%÷¿Üÿrÿ[z÷¿<þäñ7ßòý§Þ‹]¾ÿæûo¾ÿ~­î¿5>%ó¯Å2®=Z,ì>"/9^Líì>7¢ºMY¿7}¶¿¹Rgÿ«×3a%ÇBM÷ÉýO¬—ÇŸ<þ¢Ôî"p!Ô¤.t/÷?|×4_òøÇÈãoᵇˆ<þæûï#5½#ßëZÞ^-Ô4_¾ÿæû/c!÷ßFú™÷¢‚CÑ£ T¤="ñ·”ß‘1wÏ¢^a ¹"‡@/hHåq†²HEÚ3¨øŒ‚=”õgûgÿËý/?2 ¦Ãª"¼aåñ7±Nê ëÀtž‘X0å÷·‰òý'ßà ö˜Så&FÈý/?6¦pàH%?ÿ yüM]"8ùþSê/ì?òý7ßóý÷¿zþˆ+†8q´±Ø:YXYê3VSʘ˜×Ðk(e©ÏXM)cb:@XlÊjÄÊRŸ±šRÆÄt€ °¸†^C)K}ÆjJÓ2ÀbSV#V–úŒÕ”2&¦d€Å5ôJYê3VSʘ˜›²±²Ôg¬¦”11 ,®¡×PÊRŸ±šRÆÄt€ °Ø”Õˆ•¥>c5¥Œ‰é`q ½†R–úŒÕ”2&¦d€Å¦¬F¬,õ«)eLLÈ‹kè5”²Ôg¬¦”11 ,6e5be©ÏXM)cb:@X\C¯¡”¥>c5¥Œ‰é`±)«+K}ÆjJÓ2Àâz ¥,õ«)eLLÈ‹MYXYê3VSʘ˜×Ðk(e©ÏXM)cb:@XlÊjÄÊRŸ±šRÆÄt€ °¸†^C)K}ÆjJÓ2ÀbSV#V–úŒÕ”2&¦d€Å5ôJYê3VSʘ˜›²±²Ôg¬¦”11 ,®¡×PÊRŸ±šRÆÄt€ °Ø”Õˆ•¥>c5¥Œ‰é`q ½†R–úŒÕ”2&¦d€Å¦¬F¬,õ«)eLLÈ‹kè5”²Ôg¬¦”11 ,6e5be©ÏXM)cb:@X\C¯¡”¥>c5¥Œ‰é`±)«+K}ÆjJÓ2Àâz ¥,õ«)eLLÈ‹MYXYê3VSʘ˜×Ðk(e©ÏXM)cb:@XlÊjÄÊRŸ±šRÆÄt€ °¸†^C)K}ÆjJÓ2ÀÇX1䃜m•À8PYëÌæ#‰ò‚EŒŠAZ€„×X²~o6(1zª©²ýÍ$j0K%>å-™ýÏ:—yTîyüaçHúйHý¨Áñ$Ž))˜Ç_š(ßÌ;Ôa,•ô)%ˆ­ T®<þæñ—‘øJµ‹äû*8RÄ1%óý‡&Ê÷óuK%cŠÄV*W¾ÿäû="ñ•Òý§‘È'¾D“3p‡eI%ÔèŒ)¯ÉªpŸ ñxi‘´®¦FJXŒÌQg Ûêë@NËÇ8ÍG9õ«!`d³T¶?#û_îìyüÉã/ýÀFGïŒä~ï)ùþ#F‰·Y±Ùq´•çÌ÷ßüüá]!?XOÉÏ_p #?°käçüüA?°ÑÁû£üüám ¶ÈÏ_´‚=f h~ÃØëþ‹ç9•,+>*² X©n0N–C6/÷qÈ` Q ä@‘¹FÊ8³þlÿìÔBW±E @‰™k¤ŒÓôZX! Š@(2×H§éµ8°C@9$Pd®‘2NÓkq`5†€(rH È\#eœ¦×âÀj Q ä@‘¹FÊ8M¯ÅÕ¢È!€"s”qš^‹«1D(C"Eæ)ã4½Vcˆ"P ‡DŠÌ5RÆiz-¬ÆE @‰™k¤ŒÓôZX! Š@(2×H§éµ8°C@9$Pd®‘2NÓkq`5†€(rH È\#eœ¦×âÀj Q ä@‘¹FÊ8M¯ÅÕ¢È!€"s”qš^‹«1D(C"Eæ)ã4½Vcˆ"P ‡DŠÌ5RÆiz-¬ÆE @‰™k¤ŒÓôZX! Š@(2×H§éµ8°C@9$Pd®‘2NÓkq`5†€(rH È\#eœ¦×âÀj Q ä@‘¹FÊ8M¯ÅÕ¢È!€"s”qš^‹«1D(C"Eæ)ã4½Vcˆ"P ‡DŠÌ5RÆiz-¬ÆE @‰|ÇÖÓ@IDAT™k¤ŒÓôZX! Š@(2×H§éµ8°C@9$Pd®‘2NÓkq`5†€(rH È\#eœ¦×âÀj Q dŸˆÇÕ{^ÅXý\‘ÇUi-*©NEYáÂÙ¿8¯UÌ¡Ü1©\¢ÿ¢‹/r“&Nr[n¹¥;ðÀ‹‚B*Ê—%¥?¨X(õ[kgûÃ_3ÿ›9ós÷ûߟã,Xàöê×ÏõÞv»Üÿ–Ðø·Ða'óø“Ç½Ûæñ÷ë7þÚÓ‡ƒÜþ¹ý¿nÏÙÿóý?ßÿóý?ßÿ¾Øý¿41>E¼3ü÷Þ»Ã6¸Í7ÛÜmØaC?I£oC† u£G(ôž˜œY¯ýzJ¨º&r«h)"á `„1MuêÔÙ5ÒýøCÝÍ7Þhý?èá4W r@%| €p¥)›£\Jò X(ј'| €ð¥©Tÿ‚yóÝÔ韹¦Mš¹-Z$2 ¦¹J¤B2á `„3M¥úåÓp ™˜?ž›:uškÚ¬©kñrÙRöDz Œijqõ/ïúõ?Á­»î:Òv×þåZwì1Ǧ ^‚“Ö `rûé5²ÿcÈǘ#6I Sòª¢Õªˆ "À_šÊöÏöÏþ—ûŸŒ éÀŒ( .”˜p'| @ÍñgÖ¬™nô˜±®Ã¸fÍ›%²R°(cæÌYnÌØÑnà 7tÍš¦y¾ ¦þìÿ+†ÿÏú|¦3nŒÛp´i³´MÙlÅ6L=£'| €°¦©|ÿË÷¿¯JÿŸ?¾;f´k‚ñpÝu×MºLêñ š½ ¤¯JýuˆumT’ ½ðÀ=`à@·gŸ¾®þ.¸ðBaïþw衇€ÖÇõí³§{þ_ÏER&—Š5“k|Hú~<•QªŸÂ %E‚" W“.)ý”#ª—³þW^{Å­¹ÆZnÝuàÔUaùÖÿÕW^uk®¹&ʦ,ÞWÍþ‰cVYŸ¢ž§×¯WýDÓp$õ!·? Íá­b~‚$Æ%–˜Ât½ç\$xYidr£ÂlØ'šÃË설·­݃1£™³ý³ÿ© åþ§ý%t’*ÀúUìpyü‘¢9¼ÅÌNH.dü™7w®;ïüóÜöÛ÷v«­¶šëÖµ‹k¾js·õ6Û¸ÁƒWYŸÂæ ÏùÌÓ›yVu]»tu«4kî¶až'-ÏâéWyü[Þãß¼yðƒóÎ×6m±š´ióæ¾M ~`íN ˜üBþ—ÛŸX:þÿ̳Ϻ¶íÚÊ_›¶ˆÛ¶‹À·îê«®ZjúƒK,düù*µÿ˜1cÜÑGíš4i‚…/Ýzë­'¶?ô°CÝÇŸ|‚ªÆ'>Ö;¦–Nû¯(öCV XñÔÎÝzëÍnÞ¼yænÔÈ÷ÝСCååD8()0¤IÀ*—WÕ$CU¢¬¾~Û¸Ð8L„ÊÒÁ9$z^I/Eý^~1ZöúùIôéÓq]öúEy¸õOœÌ²5¸iÓbÙ–Xû)PÔOŠzˆúŒ¦Á“¸åHPž¼ÿKÕؤÄ×ZØhR¬ ¶ÉíŸý/÷¿8>؈‘ÇŸhµEÕ7ÌC`i<œFs%PÂë±Ê§yˆÊã/l”Ï,– ¡þ$_ê_xá1 ï˜÷?p¿Àº¼ø–(æCL¨PŸ®Tm/®„úúü :p„:Ý"è‡Â¨rÙéWƒðº|õO™29…‹Æ²YzIÛ?*­_ÿ'³l(fñ¾¨þ·ß~ǽóÖ[È_»ýG¿èööø¢ú©W5/?ýÃß~Û½ýÖÛ±ª¾(Åa±hò¿%vc6T~9ÕŸe”Ï ¿dû¯ö_ÞíŸõÓ‡—_ÿËöÏöÏþ—û{oŠ…Ýí~«–Üý÷á‡vÏ=ÿ¬kѲ…»~À7aÂGØ¿o¾{ùå—\ÇNDÝñÇÿ¯›7×ÿˆ Ì#?äž{îYײU+¼ tÆO”=ÿ˜§S§Žà¨¸ÿ=þxýá×?'„†àù'¿lÖbÿ{HÚôylÑÒ p½ûè£ n~eüàe´)üíxüñÇùó—œÿ±$V”åáÿ_uýŸ~ò™hî'žxBVlñ`7ø‰Ánÿö3dû'}V C4&`«Úþíµv#GŽú#>âÆç¦LþØýã~Ìm@ÄðáÃݽ÷ÞçÝ=ÑåÁ¯²ý›„^.–DC¥é{>Á:÷ÝwŸÛqÇIpwÜq‡PÕþd°hmÞ¬¸¬õµ×^sC°²hô¸ºvußýήnßýös7ñÒ½è¿ôÒËÜçŸîŽ<òHYÎ5~Üx7pàõîe|Š4{Öl·ÕV½ÜÞûìízlÙCÊÐ(4~µ~6ò þWuðÍà‡®5ô›á|ðáh÷·¿Þé^ýu7nÜ8·þúí]ß¾{ɧqM›65k@®Á]&e›áŽ<êH,çk'yn0нüê+n–/Û>{ïí¶ìÑÃë‘"Ê…ùòS¯ŸEךÇú3gîw×]w¹§ŸyÚ}ðþ‡XâÖÈmý›ºoÞÝí¹çnÕUW ‚Ÿ}êi÷æÛoÁf3Ýã?&òXÚsÏ;—uýøàƒÝ: ªyÞüîI 8l®üúàƒ÷]×®ÝÜw¾ó÷â} W%hý _zé¥îsl&|ä°A»vnÂØqn~zíÃoÝ·Þj+×ÏÛà”ŸlÏÇ\ˆY¼ Î?Wì)6K9w0ÊÖûW‰E|±Yʃ:Ø t» Oùõ©²è|U|:ÖXjèåXÏþœU¼ƒÀC=äÞyçmÔi×­[7YV¨µæ‚ºîTüÒ66¾üòËÜÜ|Oê’ûðÃÝ=wÝížÁת«®†%Ù[»Ã;Ì­µöÚR+¶7¹ùç?ÿ)éùK· –}›ýYÆ{ï¹GlÄÏì~úÓŸƒLIý tÐnúûß)§ü68X õêàA¹; ¦SO9Õ‹%äõüoΜ¹âol¯÷ßÿ@êÞ½{w·ù›»>{öEÝš«ý MêóØ?iP÷KÔg5ÔÛÊKà>ÔçM´ù¨ÏÏ~úSÑÏ:H{ f¿zÿý;þЇÛ°ßÔjnë­a7ôÍ5×\ÕõgnõW¸Ô«ÿâ¶¿šZ¯¡”>ieÎú³ý³ÿű5퀹ÿé¥6‰6ÊãFrVaœz÷ß<þÒsÌPþ޳ˆû?{Xe•UܶÛnëºlÔ%dïÕk+wʯ~å~‚ûî´iÓÜ«¯½*Ÿ‰QÑG#y¶ÆÁ]»l$:©­W¯^îW¿ú5ž=|žWcž|ÿ3Ó®˜÷?úÁªÍWqÛl?è?ð¡W¯žîdøŸ¿ÄЦüİÞóŸÕ2?ÿ,^ÿ3;3^÷¿O?ãçKεnÝ+Âv´bú˸4êoÃá²^|+ívß}w÷ÝÝvc\£fÜ{ì¹€¾îï÷ýÃ=†÷cŽ=ÖŠ%üvYÙë¿°û/Þ+• xEŒ!<å׿fûTzôìÉu>•öíÛWæ?~ÜÞë+XvUÙd“o ÏwÞIn¾ù–J×®]Ïüþm8¤wÞe— ­¨Êç^ð?óÌ3•çž}VtŽú cߣ 3ã‚;äÇ?VY^ÎØ±ã‚þ–-ZVÞxóÍ ‹À•W^Êbò½›Wz¢®“'O:ÈO±¦ÿi)ÛóRÿ˜WË6pÀ@²'9}™dhö?a<Êݵ‚A\Êg:©‡p›6m*7Ý|syØa‡Õ¨óŠËFf#0IôRõËóì²+ÚgjÒ>’sAàæég+Ï=ÿ\H§r²} eÓÙšüÌûă%OêÃßlnõŸ5s–š³`SÚØ# xŠ$MDW]æÌ™SÁ”)Ö¿lŠ?nlà=á—'Ölÿö뵯 6,èºòÊ+4ê/>( `BLè]ºtDZ–ûÝáÃNó…ÏaƒbXG_ID˜åFžXÚnæ,ä©&L˜øAÌcõo »ù曼º•«®¼Ê—õ™‚>"!êÇDžÐ1ÁhãЭÿï´ÓN¡>©Ï¬»½ñÆ›ÐãëÁÜU "äšÐ„1©¿Wô§rŠ´˜ŠÙÁU "äšÐ²~#0U ÒJ¨ɘ=a®!ׄ–ícD–¬KZ U#³'ÌU "äšÐ²ýaŒhÀ’uI+¡j$cö„¹ T„\Z¶?Œ X².i%TdÌž0WŠkB[‘ìÿÞ»ï…û³>‡¡ …²VWþ½÷bžD†$Ÿ‚É5¡­Hõ¯®+ Z(k¬^ }UÚßêúÞ{#ÂsVõ;IZs…¿jõ5\ùÚÿôÓO—÷?üȪ¡.œ\ >Í:zDÏì¤1u˜=a®!ׄ¦:<¢€_ñõÿâ¿~²í¶ÛUæcb#}ÿ8ö˜cäýóC~ìM¨•“k¡žHD–lLZ U#³'ÌU "äšÐ––ý±b¯‘x딸4/Æ·_†7ÞXvìæ·Ê¯ã—ˆ×_ ¯›Îí»Ï¾îÁ‡„0Ù>ô {wø»“2î ƒr=zl‰“©*ØÌúzLjŸÄ¹Ë.»Ìýö·¿ùQ¿ˆu÷Üs¯»GÑSèX¹°Ë®»ª¸gž}Îí„oùÞ˲ɫ,õ6Òr² °Rh'Ì´¾çõ?ÿÂón“on,zxtû wÜ/~!iLޏO<«—»¿Ýõ7÷èøW1Ã~N9{ðBJ§1p•ÇÅ_,p÷Í·p»ì¶«ÀÏbÓ°oSÊF,]KЯ܍ Pp2âI\ÝÁåkä=û¬3ÝŽßú¶ûä“ÝÞxÃýËÞÆàô‰Uñk‘Õ¿Ïž}Üšk­)2Ǫœ¡8%Ž¢þ÷„_bCn¨2µðò-|¬È#hêá÷”auI¬rš^ñ­å+¨ÿFû\ê~û»ßI_<Íë=÷Þ\"º6ï¾…ÛíÃðì3Ï¢}¾%¶ê‹MÉ×\eCf®f:t˜À'üï 2O`¾Å|íÛëivñ7WçfÍœ)vTƒ9Y:ݨ±_ÅTh(°J¼xö¿à‚ ܾþóŸËª%®ˆÁÀ,{!µlñ ÷ËNDé´¤4¥…K.½åiéŽ8ô0·açŽXªý°¬ 3v NÝ:Ú=÷¾ý'3Û_â$·´‰\”†«µZ1 x˜ƒ:›ÀÖþêFÐáÕ0ž…]i™ÙÆ­PZ¢ÿ”SNñþæÜÙgŸ%«?Áw·˜¤q×þùœ\2¿4®ÄÓ²YEý,Jà3ÿfçž|êI?üPסc'Çeñ\Q5–vûÉ1â?^A!²ê)ðhÿh4@^AÙþVˆ¬ß,‘íŸýб5ú¿ö~ß“¬ÿë@¡N$yäœ*÷?5EÒ‘6¸G¸ÿ“ûP»ÿ ¦Ï¤~Ïì‹óü9tè54d~são*\º¦­’í_ßþꜾmhÃå4þ A›²8Ên,ï>Éx›Ç_é?~$a+­PÏ|þæ2ž’uË-·º#Þsð~ºÙ¦›¸^[mí6XýºýEñ¿/ûþ»¬ï{íµ—Ã"÷â‹ÿrG}¤;ë̳Ü8Ý‘_NÜ~ûíè? x?>XÍ*žâ½¦Îøû•²œÀJf¤<òW§ü.ÚPé³çžL ÐN•sÏ9§²×^}9¼TzèA‰ sÅ%ðoäÈ‘•«¯¹ºòùŒéA<ñ3gάàåVò´o¿~ )€)éJÀ· ºÝHU¼Dt¶C˜Õc;n\¥‹_ ÃÕLC‡¼.x^¨Ò¤IAÿvÛõÆJŠ™NŽsÎ;WʆAµ‚ÁUò2³Ž,i„á8š—‚='AI*¥2¼Å% *]ºuÙÛl½µ`/€9³gWž|ú)Ï(^á‚ʯN>Yˈ²Åù bŸYQr®,aû°Ží×oëâ3§õ§ ÑBS^=«—«©_|R([‘ž¨÷9މªJß~{‰ÿQ×—_ž0—)Ô4¯ ¥2¼Å[lÑ]Úsÿýðr•ò{ø·´1ÚzºÙ$|nÚ¿g•‘£Fj>ÐæÏŸWù‘_ýò>õÔ“B»Ê¯¢¼I\…†öþWØ4Ȫ£¤ôy°A¿½úÛ]~Å>oÂ% ¦y‡X x’‡z/÷v³SP×n\5ÖPÁç\HE¤Íž“ø›ÏtåUWÿ+ „¦ yp5š†Ò'Åž( Wã9ÒÓ`7ÔïàƒÊúô³ÏìãÕZÑB¾"ÑO0äî˜2ÈâD˜•’ÒË• Ô4¯ ¥2¼Å&)ÆJIé€q'5ÍkB)¤ o±IбRRz€`Ü B@MóšP )Ã[l’b¬””àw‚PÓ¼&”BÊð›¤+%¥8Æ Ô4¯ ¥2¼Å&)ÆJIé€q'5ÍkB)¤ o±IбRRz€`Ü B@MóšP )Ã[l’b¬””àw‚PÓ¼&”BÊð›¤+%¥8Æ Ô4¯ ¥2¼Å&)ÆJIé€q'5ÍkB)¤ o±IбRRz€`Ü B@MóšP )Ã[l’b¬””àw‚PÓ¼&”BÊð›¤+%¥8Æ Ô4¯ ¥2¼Å&)ÆJIé€q'TæÏ›Wù¶_‘Ëû¯þúù ²˜’æá¹e§v–û/N6“²§ô ¾~RÈVd)ƒ,6I1VJJpŒ;A¨i^J!ex‹MRŒ•’ÒãNjš×„RHÞb“c¥¤ôÀ¸„€šæu.üÀVfK›úå–Ãb“ã(Ãp7U_ᘷÈSYl’b¬””àw‚PÓ¼&”BÊð›¤+%¥8Æ Ô4¯ ¥2¼Å&)ÆJù¾~±÷}ßÂ,žåï$ (ÈU…š—W…LjLd±qÄX))=À0î! ¦yM(…”á-6I1VJJpŒ;A¨i^J!ex‹MÒ™gžÞ?hïýöß¿²>¾Œ"üËOP¶r¦T‹Ð”×"kLd±é±RRz€`Ü B@MóšP )Ã[l’b¬£sa‰xÌ…ÁDpÊ0WófÏvx 0ÞqÇØkèï²Ú„' „ÀWx$ø×±cG÷³ŸüÌ­ºÚ7„ÌÓÌÞþözBÉv̘ѲoØ2¦ŸÓwýí.wÐþ0™’<³–Ró)Äñ¹]vÚ+•¸¦•¬>Ø|‹î’]8Àü¯½à¦ãûk†K.¹Çubõ ‚é?âÐÃCúå—^Æ+³×`FA’˜¿a i¡áAcU¡!Yd¦¿i\5¸¾¼ÿÆÊ•þýû»×±z‡¶c¦&Íš¹oïø-_Ø(W‹Åþ ^ôÅtª¿cÇîg?û©[m샀›‰{÷ÝnðãÚ>Ì:fô7g–_±1¾øƒÎ}Ä'D…2”$â´þZk¥èU¤y‹@Úò7jhäî¼3ü7eÊ÷ÿw¥äJZ2ļ–?ÕO¾´þ"¿”A_÷-·PVjFúvØAÓ€ßÁɘ٫ÿå;þŽ: ‰´F£½Nˆ°aocEÖmiž÷È­”‚°¢Q0M@éJnŒÔ]wß%«z&òÒM˜.cÞÆ¹»ï¾ÇáS<7yÊÇî8o·rý©aË-¶ý\)Åò¿öêk˜¢¼YSøÛÿè~b,”¨Eвjùäšè·]©¢ÁÔ"š§Áýä'ǺN;"›bÁ>'Ü_ÄÐ2C_Ó_ºLzÚÿ‚^D©²%ú J‰¯Vý­2*9„ü”•ôå²$W3ļ!‰/ëgs•üßU£hCr2dû{©iÔ(%¿Êþ'žúnîê&åq*?´ËWwüùã/rO?ù”4þ…^ˆ±“=!ô†ªç2þérO=5Xò\pá„'æÈãïÊxÿ¹èOtOz?¸àÂ?âùN[4÷ºùŠßÿ7Ýdº·À×(çþþ÷×ýŸûõ¯-_ pöß½ð”>+i^\@#¨­-UMS5û¿(2Væô2€’°2úÿ­ß½úˆmÅ3Pÿ¿Þy§/£øµ¿QCˆiObeFÛ}Qý”&"W0ûËÄT†/A¡V,.‚Tø™ÑVØX¸SçNxéJ‚;ꨣðY‹|‰&¬êyZE"&ãˆòK.¹D6ÀkÚ¬©ëÚ­‹ûÁö l5ƒsS§}&`Ðá½±ñ>ûì#ú£DŸ+)£½ˆ8úC|Æõ-™D WÇŽÝ7“ÏLjã}òu¼xZà2²¶íÚ¸¶mÛb3é¶û©¸={Ð9Xÿ‚~ÈàÓû²lI)W”“¬:/`Hëh­:aÿ3Î<Ãa¶R$ósµžØùî³ï>î¦oÂgeؘÌDTé×I¼€.ñ•õcõ”ã'QÛ`cºfØ{B¹ìñ9òÔdLÅ$ZR#ÑM±Ü`ZÚÇ3.²þR–T’/\©þåºq åFØX›3‡Pªñ‹ÔOæ“ s4àX×]¸‡zØáQÐ <ÿÀ¤§…N;ÇúS èª^¯ÆGR·»‚WJ‚îGxFM3S…uMôK^àB· €IE ÿkÀD7 6(ªfÑ"xÕןŽuA{ê†Î*¯\bÏ8óôð‰!?Ýì‰ ×¢¿Á¿o¼ þöé§±þ̪¾XÓ_áL—…”EàÚõïÖÕ>ó¬8¬Â²Ü—û±©\Ïmúa`©æ«U·T‘uìŸHTÙY´±‡²ýÍßo)ùIö?Ø¨ÆøGÒá¶+Ù¬êþ˜ Äý¶P#F£”l™ý¦ùŠúßýø$þ”S-ÏÇ{Œëׯ_U_*·??£?åÔSäùãXl®ÚÏùÿÅî#ÐJöüñNŠ>ÛQ0ü„mÚmêC¹ý½ˆçï<þ–Þÿh´Ò¸JÔ’¼ÿœzê©ø”é}l×2Äý[­‰÷ln{ÁS†Û´m#êÏ=ç\Úµ/Yý*Ñ_W2ÿ÷–Зک–ÿ_vù¥®gž²Xd Nö»ÛÄÈ8 ܸ½;}zäÑG &YÚí_P¶í/CR"„äùVpxßkúá8=HI¬˜90Èf4„HÆl‰–ûŸ(Aœý<á„pªÓå"CdJ¾¨žƒgî:aÿ ‚…P¥VcÓÿô“O˪ +Wƒœ†½bÒÀüMü( &Nšè&Mœì&Nœ¨˜È"lsÊ«¬Ò\xËú;&e3GŒe«1xÂcŒ¨N"L¨ÿ&˜-~ Â5W_ƒ}’v’œ\átV€q8ö³ÁÉ]ûëß¼DFÚ&ÅêŸ00Õÿè#Ê ÓÿÄ“´}¶@ûœ¨ísØáGh/T¢ÄþÔØYŽ8%[Q¿µ¿ H®ÊHת`HøËú‹©¼"Ç׉Åþ)VLÑ^ØèÜm·Ývî²+.w?úÑÜÅ8y§Ïñ8ÓoÛô¨F+`ÔO>³?–ç‰ Í ɲ~Ö{à+¯uúŸé—8r{(êWÄó¿M6ÙTN廿šèoS§M—òpúÛ†båüÍÊ%ª‘W–¨¨?ÖGñZ¦Àæ éØþžâ‚Ý’ìK³þ±@(Ór°ÖŸxu¶è.¾[I:û¿Ž‰§D;•Æö§dèHø|O3!`ŠãgËþí•1ûÌ{˜ëD#RCEÚ²÷¿|ÀõÅ^“ »bÿ%—^&pù"eô}àþÜž}öDô2ž6›ï±ÓAD°+pû³YFî‰ÊSdvÙegø>Ç ©‡UõÉãû®„`k·Ó v·ÿ†lŠcú×[o]YýÏ¢M›6Uö›]Æ)ZnÙ_Vÿ˯¼ìpˆ³»GyÄ~Ä2¡~Ͻ÷º7þó²tÚ÷pbÙ\œÜë(hQG‰´•§þ‹êÿÉ›)êŠzJU}Mu)>½ÿ~û ŒÓÀܶ8ž[~ñyÔD÷)Vµìþ½ÝÅpÛm³{ïÝwÝLvpEÂqÇýÂm…ã©EóµGc›¥Ë/Øž#Dœôú¹1/›Çh³¼\NË  R¸ &V6ðY܈÷F¸ÏgÏÄ1ë³äoöÌÙØ¸w–þC’æ‘äòs s¨Ñ•¾ËÇ_ ”Ä«=êäõPDÖp"¹™4'-?1Ø}4~¼»óÎ¿ÊÆÈ̇“ÂÜ~ìç°ßˆOõse ¥S?”—`Ê$æ¥â>–öùž”i›ÞÛâ2´Ï´ÏEãS¥ãÐ>[…¼¡y¬R!°GÈ4l`ùÆ*X¯ŸŸ…IqޝgàC‰'KZ.@΄ ýGŽåFpN­°˜úU(%GûïÕw/סsG‘ÊO©¸)öm·Ý&<‡rˆ»âò+ÀîKˆòÄ‹•9ý#Fé$ë†`ÝØ'a"2ÕOÊÿ‹—.‘è±L^?WÒŒQÉ~-iô¿‘#‘>.!>Ößì¿ÖZkÉqµô· ã'¸;î¼Cüæ¤ä~èóã°¡;ƒÔÆ—m2ëSÒωZ!‡òK¶xá )Ÿ2ý£`7Újãn+=ä÷Ü–öyCTÒOXM™Ä¼Të²€EXMHÒþ>Kdýf µ®f:!ˆ1IÌK¶¿™Äü?ˆB7#fÿSó˜= Æ¢½"Aíšý/ZDýIL&Æá%÷?õ½—,6âÁ™Wðþwÿ?0Á³ÇžRæmñãÖ=÷ÞíVÃY!X%æ¥âþqÿý˜ÂD’|þ»/BüLpº’Ô?÷m¨û1Ñ·ÇžôƒŠë ?¸÷>ߦҘÒìþ’û¿™de¼ÿ®þÿVçÀ¥]_ñdÿ·Ê[ðÿˆ1¿·˜-ýŸsüŠƒí&GÕ+¹6ÁFß·Ü|‹—Õà^ü÷‹ÀÖ_áç}c½%VÌ(³¨0¦íÖ­›«àt1yI%-´‚5Höñù—µf­/q7ÚÈó‘¹Á}òñÄÊ߀GL¿O2T˜ÅMÐô·lÙÂ=9x°Ãæ¶î¼óÎÃ)d›ˆ¤î·ŸÉåkpÛ÷þ¯¡‚O³nt«4mîš7Ç_³æ®Yóf®9V ­Â4þøS˜¶ÕÒ¨+3%…ú˜ Rº µþdv«‹q\}IT›víðbþCwë­·8®ê"o¾ù&¥›RÛ¯·ž‘ÝóÏ=/¸ ,(…“³}¼ý/½è·ÛÇʾO>þXó’«lÊ1^r‰\&ˆˆtë¡ó é…çQ6ÏV®?ñ&jø=mg|¾¸Q—Ü™gžI‰_J¿ ¥d™Ú%'Ÿt’…=„¸—Óà'žt× ¸ÎýýïÇDÔHw>£Zu5ì¿ôê?àúëE ˽)Vâ0´nÓ:˜ë_8©,Õÿ–‹>ƒ•nl†´Tê?3Ù7ê,ítNª ™ÑŒ@EH|ÇÏð΢ÝÍK¬?nÂ8ô ߦmÛµuû£ÏÜzë­îjø›¢+îÍ·°gÂÚ¨ñ>ýºRýRŸ§ŸQ²——Ò¥¨g( ä±ý¯‡Ý(ˆc̦›m²ˆˆ²ýY“MX„00AD¤'’V¬ÿ¢ü¯ªý)3ë§4$æV0AÃ’ó’íLAk˜/Y ¢ÐATöX‚Á Tƒ±€V»™õ˜Á’ó’ý/˜‚Ö0³Z ¢ÐAËþG¬‚ÙŠ°‘˜ Rº yYrþw=>}èÓWW í°Ãöø$þ!Ù[“jC°âxý×_?Ðñ´XV‚{)>üó´ŒuŸeÉí3­Àí/mŒÆâóS¬þb`›>H?Àþ(¬1%æeÉù_A¾H&Æ 5&³~o›…Û›ÁË ýØÚ¢Üÿˆ»ûÞ{¤¯²ÏvàW‰¹Lisš—…ë§<“PÖï[4Ž©|‘\B¤ÉPÿÜ9s¤.Íðî—–V*oõoƒíet¨¯¸ñù÷$ò(’P4V-·P#}¬¿ßêb1Êjw¥N!Ý››^1Kû±”·»flŒ ºls'§’ùôÓOK(¨üñ•Ù8M‹ÒðÝdå˜ce¢rJ˜ò♯ÿS´Â¶]vBÁÊ%Ѩ?•LHàûÏÞ9”‡Ù@Ù©Ÿ4ŠØqǵ Ðÿûßÿ¾‚o %/ø”¬röYgUn¹åæ€3€åb]1‘`(¥\IူSxÄT›s̘1ž¤†ÿÊ#<\ÁqíAËøÃ}öý¬Vaš _PÁ±ßZ/Ð÷ÙwßÊŒéZ¯3fT^~ùe±5õãÓ©Pk cûà[ó`7Ö—§¸…€Ì¬?õŸ”Ú@*•Ô,€¨<š”m_”múôéRl–í•W^©LLõˆÂRGê²?–gÖ¬Ù¡8X ýV‹™w=œºFÙôqúh1¤œJ7§’ùú·B;ÝvÛ­(Ï,´ÓìŠ>FyÛo¿¶,bžl"ÊϹpÓ®`Ÿ¨Êý÷ß_iݶM ñ$½bPýXÍxÌÿ¨³¼.ý;\ó(¿Ú§Í1¤µ=Vý ŸÑÁß)ùÛÔÊÑNf{–ƒ™_zéeÁQvÏž=p*!êóñ§•îÿG¥M›X=•LµÉinà§,žx‡UYb·™³gU®Â)g¦c‡h7+¡ÅV«Rá´V*á`B½#¦h‹TœÂ)§§Ö@ Eð 1€Èúk˜0ZÇ Q j T¶?, vIŒÀdÿó¾-’ûj‹B׋ªˆ®—Að 1€Èþ·ý÷H>£Ú}s‹Í»W†áÝaÆU†Š“xñ7t˜àpäµhž¯ï_ÿßÏnY¼!i @¶¿·V´H¥ò؇1ôD¼ÏŽ;6Øtüøq…S¦GUßaEh"9€X)í¯ep°e¬C¨­t ^š1ÇXÙ™C ›À:1¤&wÞy‡ððÅÕ^™—70Ks2(ÀÀs"&Õ/7;ÓTž|1½[YtbÈU9ôPÑ^®¾új);ebó.OZ ­OŸ>Á)HïÒ¥k›O+ú9A“¾€S/ËN^+›•ÅâTwN9RظÔþw ô‹ ‹Çæuç±ê´£×0Žg0Ió!€7ËËxýöë‡46³–\iûoëÖm„Pë¶mCžIÒ>Q—ñÓ¦×b)TÍ ŽRµ²ù:P–ð=]IJi0yø¼Mʤ>„6i…6I&pŒÏbË_§>ì°Cƒ~–§3&9¡Ñ¥[79îó°Ã«\Ýu•ysçI]ñ9Ußl¡±ú9áW_}5ê8x“ú‡MÖ…Fÿ³KXÁCm@ÙÚþœX±I,ã³ØòÓn¦y±š.ä‰-Y© t{à#?'{ÚÃß6ïNS_'îG8NÞtð“\F±ÖßꫯTÆùKõù]å5o7Óeõal8‹ëáÒ<®•+R)½Þøg\&Ábâ ¶Øx«ã”#…3ëÏö7_ˆ~1—zÁ§üE8åHaãÊþ—ýÏ|!úZÄD\ê=[œòá”#…+ûßñ¿+¯¼2ÞOý}Ú^Óû)aN±õBž…Ü퇤×1‘PÖj“n°Åå<1r¤°qäöÿ"íoV»¢àñù3õ{ÿâ3¡†l³_ŒWÿ;ÿüóñžÛ’}²UËVèïúü϶å;P·–´8Ö§ ¥)l|+NýY¢Ú%Ô²¦4ƒ-¶ÚTÇ)G„çÎ[Á–!a,¥­±ÇoÕûÏé§ÊsÇr¦¸jÝĤ)lÜ+¦ýñ)†XváE^CÜûÖÀ'›áT1™þÏ'°^x¿iܨ‰$øÖsÏ=ç°@òÉž»víæ®ýóµîÕ×^ƒF ›0OÔ¯ËZ+®I|ÆçDØ=W­¿9NÕ*‡Ÿþô§nϾºÄ’§/hh£éî¹ûn÷»ßþÎuÄ^I¬û{ïÇÆÔÜ'¢íÕÏ=ñÄãò9™æQý­¾Áå™ ¡lV‹•·Ö5åÐ;³p¡V–aÿöÇIn“ÝYgœéÚð“¬êpC†á$5´AÛµÛ¸?àøÉͤZÜ{ q­>}t:Í?Zx1©âfÏÂÒ9”¿Úçy¶Ïö;¨à~1<ÅêÚk¯u¯¿òªàyiÜ”ííß²e+¡5E»™^‹…Pó‚-‰Y¶‡Åæx\Ƭí8fÌX[¡lsçÌì­!QÅm¶Ùfî{?ø¾úêÆigà“¿f‘Or«,Ö‰ÒFûsãmÚž#ð ÙðáÃݻ#ã>Ÿt7âSãŽ9Úí½o?­«|B¨²N?í4÷­oí(ùEʾݶÛIþ=z<3ÞqÇrÚ—hÑ*Jßxv>ú˜cD¿nl]¬?…lº9m°;²¢ @>óô3µ/‚¦%‰±)Ýl³ÍÝ÷¿ÿ}I²ïžj&v#*Öÿ€ýt“àogŸ}–œÈGíXµæ†a¿)Lຠÿø7àýDŽÍà èï¸cë…O6i7 ÿƒ¥ì¬Ï1¨–Hë¢t0 oƒì5†IæPvÒ{÷ÞνóöÛnË[ ;ëi¾ ˆÄÿT¥Ê6õ+o­kÊëÏú˜$­‹OI¤pÖO ÄþŸí¯öàÕ¼Êbâj‡”#û_èYŽ÷Ú/÷Z!XI`ñ  Ô“Ìjä3œÂµ¯Ùÿ¢]VþþÇÓ~Ù¢éý7©)¡º ä”Ð7yÊdÅ{·Š*‡éàqóù R ?PšOi&Ãb0Ö )GRRˆ1IùþKÓykH¤°´öý÷ciSåH¬ D´·mwVI8¡Âkô¾äS)¼0ý¦Åbå­uM9²þ`Y Çûß)§œâ†¼öºÛ§Póù›¿ÖN>MªÏÿgy¶{öÙgÜjØæÂ¬jq-Ë+.åÈö7ûóDõë pøÃ…áý‡[´Øû÷Q¾é–›Ýg®¶FFµ¤µZµuk·ÁÊiì/m;ì«Åê§U¢ÓQ 'ù„ò&9<ÈÍ’ßxó?Ø#¥‹k½öÚ‰‚„7Á¦`_T¿ôK¯þ˜ívÃß}ÇÍ='£µÅæ_ë‡É¶X>Õ?Þ<)_[ìµÔõ_Rí?êý‘®÷¶½e2ð–[nqÛl³›2eŠãw¼³gÏrïúÀýþÜsÄþ,»÷ãY{%±d×ýßuîÈ#Â&àcݼͦ7qk®áÌË̽$Vÿ7v¬=z´œ<ÇɰÀ ¨V0X•'6X§m;×{™O.¬ý±ÊɽñÖ›b3¶ÿÂüßtÓFz[o½örj]cLè…€¥ö3f&’>”ú´lÕ å²#‡W÷¿‰“>ÂþN£0ÙÕ­¹æšAE ˜ˆ€"Õ¿°ú/J,`^dý%ÍöÏþ‡ÁgqÆŸÜÿ0œÐP2ˆTyü©]óø[²@•…òø›Çß<þ~­î?øbCžÉÛâ=iÃõ7p X0‘ï¿:P.Éç|Ñâ&L˜ ûsC‡Ýk®l-¿fão˜ªz|I-_ºi1YÅ_à±ÛšÅžhI‹Ãd yY?.m(WÙþÕÓÑJæ`ýï‚ó/p§þæT·×^}q*Ç}5íù§?ýÉýêä“åa«eÐXwÝu…—+«Ž9«}ŠCGTüº¶þ@Ž R}²ÿgÿÏý?éSE°ª¿ÈÖï,öDKZœû £÷˜*{æñ'?yü)Œ*i¢ª¿¤Ä0®„F©–´8ðåçß*{æñ'?yü)Œ*i¢ª¿¤Ä0®„&?´€™Ãâ€ÈãoÙŸxƹ„ô%WP¾S¦ Šî¯û>±:[&…¾»Ûw…W/ÚF¼¿³ÿ›%[¿.û´çñèÜÿÍNEÒ¡S´4üßÏ`èÍúóýGûd:žeÿã–û=#?ñΡâx-vâ%WÕ‡ˆÖ>å<:¿f§¢=óø?Éãt–<þÄQ'BÅþBC¥4R‹N)™Çõ­`§¢=óøƒmCì%A,åÝËû¸[ƒ èZ¯Ü©7Fšº(?ûwôéD+RpE‚Ž(èÌúÅ&ÙþKÎÿúõëçÚ¯ßÞMÇçSßÙí;nõÕWwÝ·ìî6Ýt·Ê*«¸öÛßñÓª­·ÚÊ 4(Ø_Û zgöoDÑ*¹ÿÓ*yü+°ËHˆþAãäñŸF› S²U¾ÿåû¿9È’»ÿåþ—ÇŸ<þäñ7ßì&,·]\’qAPùþ›ï¿_Ïû¯|JÆYÙÔ´›xƒ°¯ÈŽqãªh*®Â Y °G1’þ†K˜äIh$gýÙþÁ7ÄYàK×ÿÆaÏ «®ºÒ ÂfÊ#GŒm|ônß~]×k«^ØHùX÷½Ý¿m¸?{ÅM:Ý~Æé⸇ò#lÀ¼•÷öìÿ¡­rÿ~+žâ/yüƒ!òøŸïöävŽ|ÿÏÏ?ùù3?ÛØ°lžÿÒ(÷¿ÜÿrÿËýOÇ„<þкÇm‘Lþ(T^/ÌYHTç¯"gýÙþËÑÿ¸‡Ðç³fàÔ³UOÊËþïû¬Ý+ª»p¦Ð§ ‰*V':ˆÏý?÷ÿåØÿu>Sœ0‚tÛà Õ>\Æ\¾(súßIRñÙÿ³ÿgÿ—º»Mî´Âb…ÂSHTg¯"£ûŸy«ªe”1…,…D™3Uæ!"÷!ßÍØmòøG+,V(ô©B¢:{9÷¿ªñ§a¦Ë‹þ'VòÖLLèÑ)•Lš®C´6™G'Æ} LJyË£(Q³’%¬£BÑuˆšYòfýÙþÙÿ¬§ûþ‚N•z…v£1E˜!÷¿<þÜ@¼‚õï3©ëeÊãoÚÓÌP¹ÿ¥VQ—1Û0E˜!?yü)¸x/ê-ÞgR× ʔǟ´§™¡òø“ZE]ÆlÃa†<þäñ§à⼨·xŸI]'p(SÒžf†ÊãOju³ S„–ýøƒCX.ÅRœ´LZ"¹ÖA'  ù™`@]5(U®F£[ç‹é/ Ä2Áõ«Ò¡­`(OFTògû«µ²ÿy¯QïkÁQ¢SÕAG†E@…üL0dû«rÿg)8Š7¢:èȰ¨Ÿ †ìj‡ìâ â#GñæATò3ÁýOíýOœA|¤à(Þ<ˆê #Ã" B~&²ÿ©²ÿ‰3ˆÅ›QtdXTÈÏCö?µCö?qñ‘‚£xó ªƒŽ ‹€ ù™`XÁý¨h9 ¥T¸`öHaDV¯@\Årvê‚Q Td$²²~ouD*Øga‰hÖl±]ö¿Üÿòø“ jŒ<þÂ$¿ˆ&Ê÷×Aä¡hœE@äW³æûØŽÆ(ø™&rÿ+Û%:Vî¾×!òP4Î" òçþG#åñG|'?Ö!|ÏÉã/ ‘ï?4‚w‰Rôu¼ÿÔø”Ì[…“Ab¨ÚÖŠ7œ’™”Èãkf÷¹ñ;¶Ú, ¡&UTÔ¦dýÙþÞ÷Õt’ìb˜ÜÿòøƒþQ»‹äñ7ßê ùþ[ïÖB“ñöb¡vç„<þæñ7¿ùþccEç÷/ÿ`RsÍ÷½ƒ¤á|ÿ‰¶¨é>4P¾ÿ.ìù£‘~DæíXp(Z´@EÚ3 {§ü^„Dœ~¤Ý…)äŠF½ !•'§T¨Y¶¿úPö¿Üÿè éx¡ž¡×<þäñ7ßòý7?ÈxX5Læç/ŸÈÏŸ…'ìÔQòó7|¤`¤½ÉÐ’ÚK=Êû•2X7«b3B~ÿ)Z85Tö¿ìEïÈýoŽ?qÅ;%G;‹Ö ÊRŸ±šRÆÄt€ °¸žrà•¥>c5¥Œ‰é`qÖ_×j¢ú†ª¦”11 ,®«=·¿š¨¾¡ª)eLLÈ‹³ýëZ@MTßPÕ”2&¦d€ÅuµgÿWÕ7T5¥Œ‰é`q¶] ¨‰êªšRÆÄt€ °¸®öìÿj¢ú†ª¦”11 ,Îö¯k5Q}CUSʘ˜×Õžý_MTßPÕ”2&¦d€ÅÙþu- &ªo¨jJÓ2ÀâºÚ³ÿ«‰êªšRÆÄt€ °8Û¿®ÔDõ UM)cb:@X\Wû²ñ|>gYI¬dŒülg¹KGÆ©|–²X±åk  R ›á3Ô×ÿ§‹.r“'Mr[öØÒxÀ‘WòH檋I×Ù¥Jø ·hýÆ©™-eq"2U€ªÁACýús[ÊbÍ]¾ª!•°Ž1CÖÏ®øÀýºgž}F{%wæ‚yÀ:m[·q'ö?Pj7Ú¬vxú™§ÝñÇç¦Mî®pÛi§KŒ©’²ýŶ+àøc-¥ h)‹KÍê“*@H%̆cÌÛ?·?|!ûè Ò%Ð3¬§H7 ©"Viñ¨„Td(È!:÷¿Üÿà'¹ÿ…®ûŸÅÑÃR'CJª!Uƒƒ4†<þäñ¾ÇŸÐòø£#Cqô°”ÅÊS¾ª!•°)®‡’áÄ´Z νóÎp÷î»Ã%Óf›oî:l¸a"À¹¡C‡¸? \Åõز§[¯ýz:çœdI 7~‹‡…óÙËü\ŸÃjëO…Õâèܹ“9r”;ä»›nº9exië—ËnóÖ¤ËRÿüù ÜÔ©Ó\³fMÜ7¾Ñb…ªÿ‚ùóܧӦºfMš¹-P¶%ÔþËÒþ¿>å÷‡ /LZWoØ[l±…2äuà™.†ZíÿÝïîîþùÏGÁØàvûînîчXË»K²$G·,ë_»„†µ¸X¾Zõ°/‰þŸëov·8Û?µ@ö¿¥{ÿÍýÏúÅ©÷Á:Kùù'Ûßìnq¶jzþ7óó™nôر®C‡ ñLÖ4?|Ež¿FãV[u·ÖÚk‹Ôkÿ•ñù»NG=ýÄÙb>«a0r"›<±‹àÚÒ }ÉkJqyü5»Y\²ÎJvÿ[°`ûàÃðþÜÒµi­}ÈjT§†Þ+êPW²ú[]7bïà¤M¬£„Š8p€ëÓ§ë‹¿ /¸À!>ôCAïëúôíëžáy<ùœ>ÖI!àÐ  Ò#Cz!úƒ"?AªT|,“OP³Œõ[©Ì~ËSÿ+¯¼âÖ\sM×nu‚–¶ý·þ/¿üš[kµÜz묫­‡Æ íù_´¿IYöß >~Þùç¹óÎÓ¿®]»ø:ð;t­Íâ´ÿk¬.6`®-0Ùʬ–_IÿK\=Ú+Eš^‹¿ÆþO³,ŽýÅ|°¹¶RÙþÙÿ¢7°3ús#$±z‘õßìj³ÃŠrÿYZÏ6Šäö÷ý ߤ,+ÿŸøÑDwË-·ºí¶ßÞµmÛÖpBÿºúçÍë.8ï|×»wo·Ú7Vsãù¥y³fn›m·qO ~R;.¯aØ €ÐÂý2rÊãͱ<ûÿ¬Y³Ýñ¿ü¥´ÿlàÖnÝÚuêÔÙ]|ñÅþGypé¼åö_zþÿÑGзo‘þÚ}»ÿ‰ü*¡–žþd ¥+KÿŸ»Ý|³·[»vîDÚmþ?êý\¿~ýÜꫯî:uìäÚµiþÔÆ}ôÑî£>Z©êÏÂ.‰ûO J]gRySð/¼ V€¼åÖ[Ü•W\á7n Rƒ9j¤2l(³«ÏÏÏ„8mâNΫ¥òZO¿qXi„“µ6y’Ã2–µ~ÕË¿<õOš˜ ºz'3ñoÕÊc }Í‘” rÿýö—¼XD2£I¬º8˜]˯§ýW]uuÈ“”.¼áÇW¶Új«RýöÓ»ï™ò“¸â úuìó”î1>ÕLf±f-¦”g•% Dšt}|ª™ÌbÍZL)Ï$*K4ˆ4èúøT3™Åšµ˜RžIT–h2iÐõñ©f2‹5k1¥<“¨,Ñd Ò  ëãSÍdkÖbJy&QY¢È@¤@×ǧšÉ,֬ŔòL¢²D#H3€®O5“Y¬Y‹)å™De‰F ‘f]Ÿj&³X³SÊ3‰Ê@"ͺ>>ÕLf±f-¦”g•% Dšt}|ª™ÌbÍZL)Ï$*K4ˆ4èúøT3™Åšµ˜RžIT–h2iÐõñ©f2‹5k1¥<“¨,ѤöÓ¯_?Ÿ1(¤•dªóî»ï†g†øüšfË÷_E*Á4H]Ÿj&³X³SÊ3‰Ê O{Zž ùü?¦@/¬Üú×[C¿~öùg‰Üʱج$*2÷¿"•àÈ@‹}\bKÒòX¬:ÅTÌg¿Óúõë™A9Q&T}| Mf±f-¦”g•% Dšt}|ª™ÌbÍZL)Ï$*K4ˆ4è…á÷7ÑÒ< Ìãã„N µ*$eiŽÄV QÊVŸj&³X³SiqKgüa ç—äïÒšÔ5\Ö_¯«›0~ÞUÝ!/¹ØÀûî·¿{pð`h(Æ–õÁr#ñE3nÏâû{=ð9ùùóºnÂîìþy ;H.½ôR÷ÛßþNóû,Á¹û r]|‘ÐüêÓN;í$ôÓO?í¶ß~{VW‚¬RH^ôËgLúÀí¸c/Y-¤ýgŸ}Öm°þúÖLwÇw¹_üâxÉߥkg7àÄqnRwvY<üð´o¸;äÐCÝàÁxUN %؀ú]ŒƒÝ6FÝvuæ¶Ûu“à—v|=Å8—{Èæ’’ðMȺ« îŠÑUNçÎüÓî‡?ØÎ}òÉ'îõ×_w×^{­¼SÞ|…Bû÷ħÕV[],>öØ£²«„‰Ñ&v‰áÄu‰¶môàgÚÌþÁ*4·„wÏ»wïá,\àn¸þzéßÇÜ]zÙ%î·ÿËþ±öûú¢ÜACÿ[ÿ`þB!ýƒ<½ÜÛ­¶êj¬v =êF`Ç ~ÂÿœèÛ¯+¹¬hÛ¶m¬ª¡Þ³fÏ”¼z©ˆ/ÉùVd„ªXÝÈL¿|…¤ý’(áÚi¿Á_ÙÞfFwæ™À;á>ùäc÷úko¸kÿXÃ×W Öåö3ƒO(Y#RøÏê£ö[´ø®ôñ'î®QûìÓϱËêzÑ·5œhCÙ'MtÛï°|ÒÔín=ö/º=…÷Éï¾ë.÷¶ËR¼Ãýò—¿Uç°¸¿€]yá×÷¸!?,»º=ì0÷ÀýÔëRPÀ͈¤M¢ &¼ªÉ˜¬n?qÒ€²Jø ˆ"T;©ªæËö#Ã8  ‹þF 3þ`eîc>–ýOüfQ÷ƒ*?OyüqÎÉóμ yþåd§SžÀK8ÿñË–s=<çÇ|V@îÿ†Øÿãð{ŒÏŸo¼‘ôá¿þýoìzoŠû=Ü–›o)<êLÂS+wűt ÜëûýAÝÜÿÀ@:¼áøÒ)ÒEZ?rmljÇÅô’+[}ü[ÔÿrÌŒõ!âÒóßv‘ß_ñ ¶Μ9¸&x³¢ÓºÝÊ+}\Ã8 äýã[Ž\›*®YqêÔSO#²¼^U0à$¡Ï:û¬ÊÞ{ï-ôƒ –˜ßuWÜ14jôèÊUW]U™1c†o%ÏÄŽ•±Ëˆe¶m·v4íµÈOÿÝqÇ@HlŬ¤Žd§Eßv Mš8Qvܰ î1b„/YóNž2%Øßr«­+aŠ×:û쳃ý—G¼¬™¼}õ"‹¢sÇwÄÅ?¯i‘µ·œV~z5 ÆXåÄn!Ö}óÍ7OZ­íŸ3gnåÉ'ŸL3ìŸrÊ)t}É¿8û88¼r%v«Xÿh¡ âÆ:´m×ÖÛŠøKßÈîµ3ðv`P#”ퟜÔÍ*]Ö1¾7+º{ï½Ôc³rÙe—›(ÆÕ…ˆ¬Ì¶´Æzíìw k ¦U©ë'ž|"ð£Ä³í·ß~‚7.#3ä(²,?ã)“?Ô6aÌpŒÄ`Z•Ê®»îšè\YÁ+eQ ÔsÏ=WyþùçdS¦Lgÿs—ÇY΂_›o¼òŠúu±Dj9±×#•–)t1K—Ù–Ö8½†, LKyÑj¤Rm¡‹Y‚¸Ì¶´Æé5daZÊ‹V#•j ]ÌÄe¶¥5N¯! ÓR^´©T[èb– .³-­qz Y@˜–ò¢ÕH¥ÚB³q™miÓkÈ´”­F*Õº˜%ˆËlKkœ^C¦¥¼h5R©¶ÐÅ,A\f[Zãô²€0-åE«‘Jµ….f â2Ûҧׄi)/ZTª-t1K—Ù–Ö8½†, LKyÑj¤Rm¡‹Y‚¸Ì¶´Æé5daZÊ‹V#•j ]ÌÄe¶¥5N¯! ÓR^´©T[èb– .³-­qz Y@˜–ò¢ÕH¥ÚB³q™miÓkÈ´”­F*Õº˜%ˆËlKkœ^C¦¥¼h5R©¶ÐÅ,A\f[Zãô²€0-åE«‘Jµ…öYlÇÐ~ÇP±¤Xrj™ôüùó+;ì°ƒ×LD«‘ B#¢ºq$.³-­qzM³™–ò¢ÕH¥Ú5 y…bIõ·¿X^1W´©¢>RÅ,A\f[Zãô²T­F*Õ¾à‚ ä™gD ûòË/Ïxƒ|°‚?(‡gÈ!<":©e«S,³È‰V#u=UÌÄe¶¥5N¯! ÓR^´©T[èb– .³-­qz Y@˜–ò¢ÕH¥ÚB³q™miÓkÈ´”gVeÇ~ÿôëw@ªlJÕêØ¡ƒ;î¸ãÜw¾ƒópÀãyCï¼óŽ{gu]»w¸‰+t f_¸pEû^ìt8ðÀ˜òl-\®ÞgB 8‹…ï\o¿ý~§ÐÊŽ»‹ºuÃçÀ¡kjÿ~þy÷9Ñf¸;’tGH´×ŸDFû/þçE¡-·aÁòî½ç>ùʃ †mÉ`öÄ8x–Ö8½ª¾µœoÃð ÿq'8ɽŒ÷çÏ_ ¼fÍš¸í¶ÛN!övÊö lÉN ûÚwp?Ç{åßùÎwDkÞ<öÏ(|¶ôq·Þzë þpºû\髽/]¢:w/Îæ9à ‘Ÿ6RYloh?õЯ¿ýÜó`¡q]c76¸ƒjêÔÜñÇÿBD¢ajR‹³ï M-w÷Xó«XôÄ;¦#Ü<¾ÈЬYS·ý·/ä’¶€ì£0­Æ³ÏU} š›´õ?ÛÏÓñ‡ yXTößo_Œ£Ÿ#ók8+`+·å–ø‹UqÏ?÷¬ä£ÆÅ_Œs¨¸Ó‰us¥¹¾B¢~mµ-V)¢T_QµÂ¼ýÿÿl_ÑÍøÃE³ÿÁ òøKç;lÓî2‘¥<ÿâ2šø/îyþt>,ûó~…ÅÜ ö-F þ·´Æñú—¿üÅ=ñÄ‚Âùçÿ9*‚“Ç_µ†6ÿLÅ]8®¾:vå£ãÇ.™Á{»óW6©8!}{¾ÐÕùþ+À5Ôû/ºNưãìIëhéP$là{¾y®(J^é~?¤^ üeþó /´?ò ´åürŸ®Ü}÷½®%>S?¬Ëï_~lëðþ‡¹þýû{5-O®¾h‰o!þ²0Ä…Û4¥)º• ¸°Ós³Í\|ÁàÕWø‰ú aþ©kÒ¨±Î0T³·©S¦à•«KÜ8ÌŽ(óõ–ݼ‡|Ó›sŸþ™˜Šö%‰Cœ÷uû&p5}2„Æ1*´!§©€1¯ýl׸øùpÊÛwh×ßÖC5‘"C´qÀ^c »ž\ëÖ­åßšø]küë‰Wª¸Ýr¼ßªÉ4õÍúôéƒWèöÑ´0iCŒ[¦[¶Ï²’Œ’äŲŸqúqz;±~àãl'¼*¶°Ø×ÝrË-îc¼V&–|ûµ,o?©Â’ØŸÂþÁâØ[l…ô^«ÛsKÿÈkO¨×gŸã~¬Ÿ\ã¥OŸ½]Ÿ>ûú¶xûA1y¼7üê¤ 5”B­ýeÿ«Ã ý;wv« R—Zí_”ýÐ{PÓwúé§ ÖÌFzôìîÖÀkyŠõ­òZe iûƒ}”£4®±ЮÑþ’ý´<)ß3ØþW0¾ ÿ}ˆ1Bª/t©ý/s;¸WÚ‡¦µ‚_¯Ißnµ¦øuw¼Êi‡Ï7¦õaù‰}U,^g?)@ʽkýŸÚóø§²}E£ìÿ†QÆßðn^òÿìa¨ÅÏ?yü‡J+Ï?:¾òü“δ¥9Ç'E£¡Í?xE]îÿR¹%ÿüL3.`8úè£ñ̽7ŠXFÛ0Z³dí—†ûK̾ì´5ZJo}ôñÇòw~Ä2¬/)í‹?(Nûüsß:çZ¶\#Ðú(–ç¿HCþUŸ´“TC›â¬ cü…¹L3ô¤zrÁnÉà¬þÃCr“?œìð6>q? ¹âoü:wë­uo¾ñFÔ%µàŸ|KLÁ ¬ ÀuX««kä?âp™Œˆ Ϧ _‚c&üÓ¨Î=2dVßZ¹“N ›à,žpîÍe—^æú‡Ý 0`ÅéPÒ‰ÎuêØ2-Ï‹y¡0|Š,}bèîœiÄÀ:pñê÷ø”˜bÐ+éiï“'Oq“§LvOžì¦À)>DÚ´y–åìuÂâ˜êàêí[M©¯ˆ‰J•}rµÅ¶¨vÅ­¿Á²xuÍÕW¹vÜAòsåòo8רÿÃ]{|UŒ;©¤„ľ5•u¥k íòˆkÕªµpÒI蟰³ª›p îÒË/—þmè±ÿ%H†þï:vèXeŸzT«ÕþÐÍPXTû­ Æ4à­{Œ$HŠQRM^kÙgaV–ÙßXãuAwõÕ×8l§Wù|ú47Xs¥xíÝÝ÷*Öš¹†}ÖGÖ¼¾˜}ÕFy…"•;ï¾þ«­áÄDTPÖ¬dAö!vËYW}¿žþ>-þ< Ðm¶BsŲ‚„ÖBU-D1¤¼}ËEµ%Å_m¦Wµl¢ì@ ‘í§heüFö??HâØÉã/<Â?l©žÿÓ±¤tÄPÒyþÉó¯9IC¿ÿðùud5—dü?ðÀ`·çž½eþìÕk'w)¾bew[HfÿoÐþ¿6þ€Ì0zÔh\+nŸ½÷‘/Æ×…×íÔÉMž:%<åÉ›EšáÁÁ3B¤=ŸûßÒ@ü?ÞͤãBo…n”‹}·$ã_œBQžˆe£´_ê"Uûbíg–˜£úþß¿aU¡NvMþh—]‚©µÖZÓýêþrE“g*§¥QñÛŽ#6R]B=!‚‰Æãt[YÿÃ/{êзŸÄx¯UvšØ*–Á‡y?ž»îº›°p޼Böòˆîâ /Âë@¿t[lnŸçæÍL­¥ö¥(ÉÍ~Q ö”'Ò|ªÜ¢ÅÊrpò Xà`·þùXyԗIJœ[gï‡ô¨QïºÙ3gãu¶YnÖìÙø7GÒ³…žíN>ùä‚}Ɉ2´Jj_xBjI‰T8¤Ä>+¯ßšB Òkàs…Ç{¬ŠOÉ0q’»ó®;åsî´5 ;xöïÛ×M?)ØãÕ+›DfΜUÀ‰ºúŒ\Aÿ|‚þÙœŠÛj‹­Ü»ïŽÄÉwáE»ã¡ý#ÕÂ…Ÿ“— U“2…Vz õöYw’’d&U$å긳ÌKfÍä¡Ò¾àB¤FB~æØ£Gv£F²…r¡â‹yCþľ¨Iºˆÿê«®îŽ=ö¼B7ÔMÂâÊ]﬩J¬ûíß×Mœ8QløªìG_Ô>k„ܲ¨dhhß_{mm+Äo¼úëµo²uÖi<ìý:7jÔ(7›¾Œ×?ŧ½?ÏžÞìYîøµY4Pµm± R.Í.fü™}« ±ç? Išÿ©\-fû TD°ü Êø{?Qh°”„T’A’ý/E#ÌÿŠOâL¢©£åñ—çŸegþáý¿þ-nþ{àþû±(´'\\Ÿÿþþ÷A®ùŠ8ò9ÑÜŸœ|ÿש@¡QZ\"AIHU $%½¯úùgm{N„M|•Lú®YÓ¦8|šÏÚÎ=ÿìóèN­M›6móü/ýaˆHB0#VšŠ}HCCðÛãkì+¼Ì;\¬{H†Z.ûßz[û’WãTt-Õa`òM’iª!l»í¶‚5ñ§|yÃ_† Þ ņ¨)壮]»ba¤"?>ë KnCÐ%t8ïä_ÿ’<ÌÂWtÖ]w]IË.²õQ8¸èl©fýM¨P¿ eê' fE¹¡O<ޝkmâÎ9÷·ÁëK™û￟“ÓÇ‘‘ö·ÙíÃÍ7ß욯Р¯Q5w+4oîšã\™æØMÑœ4þɰÊöQ†Abå0§‘z÷bª¡öýr=^Xjÿ¤’hµ^sMYºí¶ÛÜUW_-Ù˜óÍ7Þ,N«mÛµA=Ô>¿Äf&LÑðÇ¡ÅÆr_r‘ëÔi]UõýÿñGØšj¡Vû!KZ)š’^DûquÈ%u³Ê•Úol3Ä‘G¢~ܺ×uÄkvJ¸’·8û¢Ã|Suê‰ýÖØáÖ·__G¬¯&Ö>¼žl#–·¿Ê*« }õë3nÛ]Dû™Ïð/`—Ø—G”±Ñ†Êؘç‚?ÿÙOZLùàíãÓóÆq[o³5hö~ÅÝ|ËMðßfÞ—{n_oÞlõk­,ꬵѪƒöe‡‚ËýOAYG,?i]IÏÚÏ"B!Ù~D£žù7ATt3þ CzŒÔ‰#;û_ÄTañΑç‚¡häù'¸…€"ÀÄ¿4G^ ±ôçÖ€wÿ—&Ö¬Æüwýu7¸Þ½±S?||h0¾tºRù?>š‚£Z³æxæ»Ù±¤y¹îºÿ‡kÅí¾ûîò<™çÂaƒb›ÿY]{´&€eaéÏ?¨‰ÔË*ii«¡‡þÄ_§Çú”ýŸ «ñf‡ýÿá‹ßåñÿæ›o…Êo¸Ñ†.Ï«|ñ—…!iuºâñT@p¿©áz#‚ÀhHñFC¨XÄø„öܹs¥Œ±ï¿0Ǹ?üþ÷Z¦ä‹æmÅEÖôûæo‹²¿/Î$êÙ³'Ê­ÃÒ+:|!MìOëA<<Šg$1ÿ6Ûn#Ÿ%gÎüÓ™îì³ÎqÓ§ëaÔ´ÏswÎ<óOî¶¿ÞF eût¤4ëVãAÂ+‹Ž)"›"d ÆO”sámë­·ÁçҸÙsfKNjðu²ÇÕÝO´ÈWÊ4Dû¬ÏÅ0-ïšk®vøâ˜¨ÌÄáÇëtã«cšôÀ;î ýóþ¸±î˜cŽÑWð¨¡Ei3>)¸ÆVFûV¨r4e×õýãL_sÍ5nÆôÒþYرźÉ"GÉÿÞÅŽ®›nºI‹@¡Â'åçÌ£ép]2ûT—úûFð`í.ëu‘…Â!xí‘»ÄÌÿ¦£n>¬½n‡öíeäûd°Ü¹síC®À+xÜ 5}Ú ÷Ä“OŠ¿EOH~\B9BWûÿw[´p'á0l*NÆë`;î°ƒ{ö™gÝB¼‹óéÝ›o¾éÃgç;v줻™Pþ¶Ûl+~Íþ?óŒ?¹³Î:+ú5äôë?y¦»ívï×€­ìÖ~©nÒ‰R_¤C½}{4úïð§eûÄ.AµäÿsÆHdÿK=Å\CÁ5zQœ™%!EÇóøËóUâ9yþ·ÁXÂm`óÏ8<«ÃYcÇ"ÆY˜ ýJ>ýä37vÜ8ÈôŸ}LƒÌåýÿgGýLZÕmãnîªk®Â`&ºW_}Uÿ½þšÄø0Yƒo¿õU4öÜò3ÿ5kÖ GA !|ñ…ã,”[åu2žõÔ°a"0:‚‘%]L'7É„¦Èá"HM„X£lùÁ_AøêÛÏq=n,Æ/Æ5Çñž…3Ÿ}ú‰ò(ÃøŸ7üŒ¿týXñüT›ÿæ7ðu>/SLeNž7rçœ}®ä¿ûî»Ýù¾ ünc9?;ê§"ãïå›öXþü_?Næ¯øbYúѲÓ~ýk@XWéÕ«WAÍóçÍçÈ;åsõ +sfÏ®´jÝÊóe¶Òª¥OcÞÀÙC¢Ï|8ÓÇŠ’XËr•“N:IùÉç3 Š>³nÄ?W_þ?Îòà•óÎ=9¨±°‚Ý•Þ{î¥õÓy Ÿ‰ï\Áê»ò ÏO¶c±êP·“}ÝX‡(®²Oq‹žÚd"¦êï öÍÖÚmÛVºuÛ¤À?ø'S]Cbþ‚ÐÝXÚ /–<íÚ¶Ó4ÚtÑE">IÏþiéÛêõZµj =ùó©ïÍ?åÃÍ’ÄR/ä‰ýÅZ•¤Bùä‚ùZ7«} ï=s Pê©uS}+aĈ¾îZ/ö ¾b &åGÒr{5KJÌ‹~úoàSoŸíZ»ÝÚ<0°þÉAù‚ª£qcÇ&ºZG–C{öùç4C û8ûùTÿÊ«ÓÏÕKõ2~6öÌ3Ï,´ß|Â0£[o¹5TLüº÷žšÇ·«Kç.ðë8WZ¹…ô¿õ‹U/F—øÐI‰DYÉ„A=KJÌ‹âo|‡"Á(ð²ýMM"KÉ„Á –”˜—Œ€"‡PI€Pä!]H7ƉXÉ„A-KJÌKÆ?@‘ÀC¨$@(ò.¤ŒãD¬d –%%æ%ã Hà!T yHRÆq"V2aPË’ó’ñP$ð* Š<¤ )ãÊsïÿá™À?OØó‡=#Ü~Ûí’çŠË¯PÝð¼ãŸWBšÏ-ÊñÁNúü™ •TOÉ„A5KJÌKîÿE¡’¡ÈCº2nˆ?ýäÓ ¾¢ž'WjÑ¢ðüwè!‡¢@+”eeüQ«ž¿À[ ÏŸqLëo û­ø~ÌÞq;ÆvRY%[eI‰yùöö¿á#óba^KæIðoKp›·`~¥OŸ}ü|ZWY©ÅJœóæU–õü3úG™2|Ô ƒº–”˜—eݲ@(€þ—@V¼¯ZqMðÕªL— Söúnkk†WWžyú}GÏß¡xÈsW|•ìÚkþŸ`ŽŒú®_ÓF¾\_¦ífiÒ¤‰ôf\ÅNŒ&¤Ùççŵ*¡Bî8œÓÓ»÷^RÎi¿9ÍW¶Î­ŒÀûÝë~ÿ»ß¹Ží;²ø$ú»8xúC¡ùÕ1~ú‘íHíKÝP|«3µ£¹*ûR˜É%æÅïéñ|¹Smêw@?Ù9sÆgà`èV’}Ü„ xX¿8EW5o¼þF‘ÉÅÊG¢1¾àÅF{r«°Çh<ò“dÝçÎáÎ-ì¨bÿ<û´û‡’åLž<ÕuÆWɮŖº—_z‰, ñβ-°ÅÊØm’ý#!±¯d ‚Oru–uœæC!Æ—²X·9³ç)úV†mävÃ6XQ“_ã_I¬m’Á”‘P2aPÁ’ó¢øpಃêŒÓ ë:|Ùnœ{åÕW˜Kð?ÿ‚ Ü ¶c)5ê±m‡­ˆƒö}¥LZhöLÄŽ$ 5ì7æDPžÔÆv~û2É>þzó;ì¬Ãb«ëÑ£{±ÐÝ¥½„ƒ³9ôµƒ+ýzÐ}ƒÜoé×á* !¥®Àd)6}U Iaó’ñP sk‹!9dÙÿP 0 +Ò&dÂHåÂæ%û_€‚h–C(rȲÿ ` V¤MÈ„‘Ê…ÍË·Çÿ¸ƒ‚B–P£ ˆ ?m.ê!“>D,ñ‹ÉœÑo‰—"2þb(S ¹´ýï{«|Ï=öØcx+â0©'ß~`õð×ñwÄÍø’±Œ!é|^¾=þ/ –v å»"amI‰yiøí×ÖèU] ×´Þÿ° òCSMÑ—bI‰yiøí·*K3¿ÀüÃ3¯äó‘c¤”‹ùoáÝIDý&8—÷ž{î•C-[·t|ÃȾ@ÆAñíÞ½õ<6êK°™°Ê2a¤raó²ìàóµŠ/G±= o©‘¤ÄŠª‰07 ¯@½þúërNLË–-¥“¬ˆ &…”/5¤5X‹³o¶¬3ŸÑÜjöÖÛo»Ö¨ß÷×YÇ/z˜4©W ÖWa?±y}l^ž7ožkÛ¶­[ï×a¥*H½’ʜךÞù–›=wž|¶¼-¾`ÐÔ/¶¥øOŸ6Mûç@µ\#ù¤e@'±–˜H¸ÈDÈ@„ÒÈ™‰WÜFŽéæÌ›+mâÂBS(Xóò“‚ôŸ5qÖ’¼Om•z D5Ú Æ"¨D+ðXÔ4à0XÏÖí€5v¼¹Fr`vb(Íœ°çÏ[àÞ{o¾p7ŸméÚ¯ÓÞ5ÅBeô°H¥E¤tRœ'‹Rì”ro¼þÊm†× »¸¦aÁÎÌ$úžä!ðoâK}­¼_×êÿZpj’ò¬r5X"~" d Ö‘1±â‹qªé%5XÙ>\pˆŒ?`¢¯GDŠ´øQá’jzA –H„Ÿˆ`7r²ý‹ôÔ"»¾ ÂO„ DÆHfÿ/޹èE7ÓT i –è ?2Ùÿ²ÿ}íão>^'|ça6Ç™©]p$Õ]’ß?êïåkÌ$5X"~" d ²ÿ¨<ÿ6ðùË \øìÓO]ÇNñ[N7g,¯þ…!nqÂJ–ÿ•‡s€d‘»–~ÌI*ÕHiÓÂi#Ù~Æ?ûŸ ˆÚ#¤þK-}Y§)mZyüåù'ÏÿùþW=[Ä"Ï?õ=Ø×šQ ·jDkiçù7Ï¿yþÍóoõlaóˆÍ“o´Å¦[§)mšyþÉóOžòü£óÍÉŽ!Ï’ÈÄTNùIZËùÂ×P2îlõë%;A«Ä§¹TÆô !w¶ŸñÏþ—Ç_˜>üÌ Q˜% MùTNe!ó!7ˆ<ÿåù?ßÿlè¤ã,Œ’8Þ+–ñ Å!7ˆ<þòøËãφ…Q’ÇŸÝï$0à¾PrƒÈóOžòücÃÇ ‰Â(ÉóÏRš’w“¼‹JßÍ+]K§/ ±õ“‘V¤=›‘Z„·è¥>%Q¶1‹()OÓÿèTŠKD,8œ°²ÿ%Èäñ'nD0ŒâÈ"Ó§$ÊóOô™ˆ’ò4çŸ<ÿêÑcòüë‘Hòý'ñŒ|ÿ×"˜FãÌJ¦OI”ï?Ñg"JÊÓt¾ÿø9‘âËó¯ÇF Éóoâyþ×"˜FâÌB¦OI”çß°0T>€)y=n í°ºi"5™Ï5HÌC¹T.ÁœÜ"MQ¶_ģʑ cb%X¦žpôtKlÔ”ÝA'ÚÈøG,Wö¿"ÙÿlœùÁ”ÇŸÍ*yþ$lN•ÉC±É÷?‚‘ï?ê ùþk·”üüá=ÂÉÏ¿ùù3ú#?ñÈÏŸùù3yÂâ±»j~þ$t ›/õü‰#†â“›bMÈÍ Ï¦®ó/à¥üÐ+ž¨’•¥¤ä^ AÊÊö3þÙÿt4¦ã"¿"UØ”¥dž€€`R˜”•çß<ÿæù7Ï¿œ0ÓyA&ÐäR%+1JIÉ)¼‚”•çŸ<ÿäù'Ï?œ0Òy!™zâ\’2KÊ¥dÌSC²òü“çŸåqþiV€ÂP‰‹BŠŠÐéÀ#Íäƒ-$Sÿ(¯åÈàw# ¯(Ò"+ÛÏø›7ÀÌÙ,ÎþgH$Ø((yüš<ÿäùW‡HaîÈ÷E0)ùBåûo¾ÿšƒäû¯!¡3 ®ùù#@±ÉÏ%? aŒDÿ 8ê#Â+ ˜©ð,›ç_(Ï¿†„8 /yþ PDl¾šù;%€Ôš7E;~K’õƒUBDÒ;EN¨­’;úÛø>NWecogû€‘&¯ˆ¶ÊÊ”VŽ\Éf?eüP"™ý¯øP‘ÉãOp!¥ãJ†˜yü©‡DTŠˆ¨4¹ p¸äù'%’yþÉóOúH=#Ï¿‚ƒL2q¦!¯8Û¨L1KiåÈ•ìüü“çß|ÿIE$óý'ßòý'½«pl,ß÷_YÒµxóµWÔ ›8…ÔOɽ×à,$ªóT‰Å´\bU˜Íúªºˆ*N¡ÌB¢JUlŠÏöFÆŸ\¡à Õ>Tæ\®(kV-}*#ã 2þÙÿüTÄa&èê1Tæ†\!QÖÌ㯠žàtyü(è6ÙÿªO=œ‚OÕªÄôì –ý¯Úyêá|ª¨ÎP% gÿ P¶ìÕÎS§àS…Du†*q=û_€‚°eÿ«vžz8Ÿ*$ª3T‰èÑÿ‰’t€ö‚ˆHJ’R_L1 Ö”­ÛÜ JZœêA)=\Ž6Tì¹’PN¶°…ÀAtáR”ñ÷(:ÙÿÄe¼«Dÿ‰ åñgàphåùGgÛ<ÿŠWŠH¾ÿ™L8w?nŠ‘ŸXLšç_Ìcñó0Ÿç_'Ï¿ùþ“ï¿z·É÷_™ E$߈ÜLxï :~Þ,FþÆbÒ|ÿÈRp¢zØQa1T!? Ùÿ‡ìâ â#Gñ𠪇Cò3ÁýOqÈþ'Î >Rp¢zØQa1T!? Ùÿ‡ìâ â#Gñ𠪇Cò3ÁýOqÈþ'Î >Rp¢zØQa1T!? Ùÿ‡ìâ â#Gñ𠪇Cò3ÁýOqÈþ'Î >’8 >W¯âßdñ`£ðéDd4¿*jÔŸŠ¶’Õñà”Ì—í ¨*03þÞëyª £úÖ삇[‰»äù‡`äù¿<.¢‹äù×Ϻˆ<ÁY E}ayþìòüká='Ï¿"Ï¿a¢ð~£<ÿúY‘§"8‹¡¨Ÿç_‚”ï?â;ùþcœ|ÿ!åûO¾V¯è{˜BÄ“ã3.ð='œ²iñ@ϯ™ÝçFDãµU AM©˜¸è ݔ)Sܦ›nê<ðÀX‘oÈ~íš}síÏöc—¨ÜÿŽšNB€ @ôeÆ_Í¢iùKâ?øÜSO=Uÿ´Õ²U+7`ÀZ‹³?lØÓî—ÇÿÂMŸ>ÃÝpýunûv¼!wm¿Tr˜‡¿´£¦RÃö¿Œ?ÈþW˜Ä'’‹÷à„“_rþ“’2þ™;kN Ktÿ?ª™=Ï¿ ùù' ç¿<ÿåù¡æþµÏÿXŒ×WÉt2*ÖcäÈ·ÝÛ#ß‘ªm´ñÆ®ý:ßW_yeÄ+n츱’µ{÷î®mÛ¶BÛ/{x²Ø M\ЕD•bIÕàðz:vr£ÇŒv‡z¨»å–[Bñ–ËŠ³8(I"!M·Èò)cZlÊ!V‰-.‰}2‘&¤éY>eL‹¡¼`Á÷ù矻¦M›º-¾ Ž= T¢¦ÅI"!U±œ×+˜žÅ¦b˜x>ê6MêÖLëfÑO iEY>eL‹M9Ä*0±Å%±O&Ò„4Ý"˧Œi±)‡X&¶¸$öÉDš¦[dù”1-6å«ÀÄ—Ä>™HÒt‹,Ÿ2¦Å¦b˜Øâ’Ø'©'O;í4wþùà#Vø+y>të¶±9'8t’Õt4VÁ.?ÚÅ=öÈ£¢¾Ë.»¸!C†¤bŸ%)$!­¼"˧Œi±)‡X&¶¸$öÉDš¦[dù”1-6å«ÀÄ—Ä>™HÒt‹,Ÿ2¦Å¦b˜Øâ’Ø'iBšn‘åSÆ´Ø”C¬[\ûd"MHÓ-²|ʘ›rˆU`b‹KbŸL¤ iºE–OÓbS± LlqI쓉4!M·Èò)cZlÊ!V‰-.‰}2‘&¤éY>eL‹M9Ä*0±Å%±O&Ò„4Ý"˧Œi±)‡X&¶¸$öÉDš¦[dù”1-6å«ÀÄ—Ä>™HÒt‹,Ÿ2¦Å¦b˜Øâ’Ø'iBšn‘åSÆ´Ø”C¬[\ûd"MHÓ-²|ʘ›rˆU`b‹KbŸL¤ iºE–OÓbS± LlqI쓉4!M·Èò)cZlÊ!V‰-.‰}2‘&¤éY>eL‹M9Ä*0±Å%±O&Ò„4Ý"˧Œi±)‡X&¶¸$öÉDš¦[dù”1-6å«ÀÄ—Ä>™HÒt‹,Ÿ2¦Å¦b˜Øâ’Ø'iBšn‘åSÆ´Ø”C¬[\ûd"MHÓ-²|ʘ›rˆU`b‹KbŸL¤ iºE–OÓbS± LlqI쓉4!M·Èò)cZlÊ!V‰-.‰}2‘&¤éY>eL‹M9Ä*0±Å%±O&Ò„4Ý"˧Œi±)‡X&¶¸$öÉDš¦[dù”1-6å«ÀÄ—Ä>™H‡Oû)c2öáÆnt{õÞ ÿz»óÏ;\ÕÕ-FwhÿCEÞògŸ}ֲᇘêÙZ—.Q1ЉFL·ðc/UMè²}׈³­L“|möÍ@4-ÔÒ°ÿâ‹/ºÕV[͵iÓæ›Ã Û?üÅ|ÝÖR¤¾ªþ_BûËCÿ§.¸4üï«´¿×^{¹sÎ>ÿÎqgŸs®ëÚ¥«/^;ÜæŸC_ô¬ý6ÓtëÖ-V1û_À°ú¯æß<þŽ)a˜üðC÷×[ÿê¶ÞvkÁÝÞJ š†¡"«©†0þåFY¬œVׯ¢ÿ›„²Iƒ ¾“IõzÛ_os—_~¹kÒ¸ þ’ïܘÑcw t’×Àfµ¬0ˆ%Ä4µD.D¢k*‰ý˜Êì ꀤ}&¸›€Å¨9RªðÙ7Ó¬ƒÔdéÙŸ2e²ÔbÚ´i ‡¤ì"€IBQBZˆ/‰?J,—Ì‚ËøO™2Ul³nKÃ~u-¿Ùögûe/Y4þÛl³Ûf›­½óÖ¹á/þ;ßÖ4(ŒÿÅøß5W]ã¶Ø| ·Ê*«¸¾}ûJþìôÆEã¯þJ¤Øw kÍ¿^J-?¡H\ÿZ†(åñ2þÙÿt $÷J3†/òüCuÉÄ‹ÎlŒóø‹¨(<1½<Îÿ|•zòd}>$¼¦;qmЧlúŒiŒòü¯8ÐwÞýoûôCö)êÇ9úK+]¸²ÿ-P-ß8D2W²›–âü;ýóé8eò¢ºÒ×Ñ9ö¿ö¥²|ƒHÚd"àšÈýýÊÔÉ®Gžnü„ñaüÐ &L˜ 8*bäµåìù·‰zWêba„lÉñaÚôiî¹çžs?üá…sÿƒ½„ Ôe4–,Ü}êäW—ôqE¤g¤Ä´\|y–ßb²±„¤p¶®„²°Zù¿)û¬ÐÒ·ÿÑÔ0Nîøß þ‹oÿÇaaÈ«I÷±Ë@¤7(ß»Ò×ìqK¿õÖ[âGë­¿~=ý¿xû쟯Ïÿ¾~ûo½õ¶ÃY`n`  Bæw'¸½ý–[¸`¡Û`ƒ À\¶Û_j´6Wÿõã¿Êª«¸“N:)´Ÿ©ÍẤþGu±„Œ’W.+2)0tLTh(ãïÛÐÿõÏÿÄ»ÚÿÙCÿoâþ›ñÏþ—ÇŸ¿;øûgŸ†3ÿüêø_º]wßMóò>…g‰ Ÿñ#xáü…®Ï¾{ã‚nóÍ7s}ûõÓʇû“‹zþÎãiŒÿ_ÿ+·ën»JßDû|¢FŸ.\èöo˜†>ÝÜõ뻸C²·òó Kòûƒp}Sþü¯Žw»í±›VNzL­óº}Úgô)þ¸¾ùf›»ýûö“¾Öy'Ï¿Š‘'±Øç¿F»¹s溕WjÝFÀösñ þVLË!½¼=bûç "f˜A„ Ã)D¤þþ¿‡…¡wøD®²²F>®,\àú„{é¥áîÕ¯º÷ƾçºvîâvÚe'·ÿþ}]“&M¤ìÔþ%_âfΜé~úÓ#e+äĉ“Ü7Þˆ/ºY³g»Ípãê³OÇóŒ´Öì4vdÑþ¤IÝM7Ý$ü6kµu‡Ñ_h¹@}üرïv/¿ü²›0q¢ûþÚk‡×âš6k 5¶XËežK/AÝfÍtGq$¶h®‰UÅ‰îæ›nt/¼8ÜÍš3ËmÞs3·Ï>ûbrÓ€ƒ.б(Ô4%ņ$ˆ:ê}÷Ýëžö”{ï½÷§M7ÙÄmŒWaöÄ+{+®°«#aØSÃÜo¾åfÌšáô1©2k}6^Á)؃ýƒrkß¾½f¤MœûóØÐ¡èŸ—܈WpVÔûï».]º¸wÚ»+öw›4†n¨¥P—\ fƒ#ƒIèŸn¼É½0ü7gÖl׳çæòÑ}Ónêÿ&ê6s&ëö¸Ô¥sÎÙR.iÖ•á ƒvÚ·:Â^qt°8PýìTœ;sî¹çªN¸²–£žúÛ¿8üùÃñþûïwƒ|Ð|ûméW¾¶Ô´)×Kcÿÿïÿþ¯›¿¼ìòËd±åä“Ovcá?ƒî»Ï {ú·ÂwVp[bgÊa‡îZ¶\=ôÿ /¼àyäT°Îpâÿ¸ï|ç;ZsÖ Ô Aƒ€Ñ›nU¼òtܱǪ,˜Uâ ƒrw¦Nû 0À«U1Ônÿ"Æ'Ïëaž‚? Ûl´çÒËÐÜpNB{Æ¡=÷Ý{Ÿ{úé§QÏÝæ[lîú÷?Ü­±ÆRŽZR.O>ù”{ìÑGÜð—^v-Vná~°Í¶®W¯±µ^hÿaÈ3yès+¯´’ÛfÛmÝN;íä¢K.µŸ¬`;ä2(ƒý?{Ö,wÉ%hOe¾à]h/ðßåG;ËC’ecNÎ5—Þt}Ê)nÞ¼yîá‡vC‡>îÞÂÂÚúëoà9äÉW6Mßy@|ç!÷ÖÈ·ÜZØæÛ¥Ëz®IS,¦'㾓¦SûF›ÿJºFûÉ/Ûݤý”Qÿ—21Û¯ïþQ”"Ÿñ'ŠŠùOö? Âñ„1¼$÷ñ!êÇà_yü{· @L/Ùÿˆ‡ùùÅvÿmÓ¦kÓ¶]:ŸºòÊ+±(4Ý­„ûñ]wÝåš7mî%›ßHŸ–žÿµmÛgº¶‘jXOY¿_qÅ•²(d}Ú¬YüÍ PíÐæ?…ÂÀM´¢¾(Ùeéµ_kðí²Ï3zÛ¶Á9½Ò¬"þ§Üœa}Ú¼y3ë„ÐÖÿÛøÏÏŸµ¯¾ÚêîÃÉOïÚµ«9r$¶Ãû"þêirO|»üOeÍÆœBàWÊx 5ïO=sF¥{ž‚ÔÚíÚa½gAeÒ¤IÂ_iå•*Ø‘ ²»î¾Kòðrë­·V°È :aÑ1zÇ^;V°òôÀÓ”èaQ¡òÌ3Ï€FšKàú·üÜr•o¸ÁÔ+;u}>x$°,Ø_i¥•×_]äÒ*\.¿â ÉÇò¬NÃ~÷=*S§N)”Ç|”SØS¾n¬—ÔÍ—ú¦o,äÓ„XÒìGNQýƒ?¨t&nR/Ym+´¿u«V•[o¹%d:üðþ±þ°oí±8­ßСCµWaü–[o|‚žáàñïÅþ™>=ØÑú. å6¬ò4û§Ô~–‡…<É׿?ê–ÊÍF›}ÖÍ‚ÙùÎ;h[±ÿñã®YzVB9ŽúBáÂxîܹ•=üãˆëåÛ_ÆŸú&Ní?ñĵmiû@¯Ý®måÕW_ ¸âJõ3–7uêÔ€¿ÔZ?9ø`±Ï±bAez9ˆ Ä T¦Z3ÆÄêiøÏ"nR*.Œ'Nœô¤=5Úß¶ÝÚ•W_{UóùëçŸ}V9üðÃk¶Ÿõ»çž{ÄV¼,¬àPtÍCÉ?ëÿ4¶\¯Vƾûí/ù6éÖ­ÞþÇ!ø¡=V®µŸ6¯ºêªBûYö$``õÁWÐ*Ý6Ù$¤uÎQÿûÇ?¯üoîÜy•ï ß)õ¿Ùµ˜òÏ(SlIPYQ£ÀûŠü?1Y ¶¼¤ÀËö x->ûR(\"§:·ÊŠ^Æ¿´Er"–Bá9ÕUVÔ(ð2þÕ -’± —ȩΨ²¢F—ñ¯m‘œˆ¥P¸ç5<«Øý “RTfZp—ñ_$ÚÕˆ¥P¸DN}ÚE MyÞbðçó§=Üÿ@òÜSR.¾¤jã¦SÒP}Ÿk1ö« Ö„Â%rêÓ.jhÊó–Cû¯¼úšŒSöëýÿüg5h‹äD,…Â%rª3ª¬¨Qà}‹ñïÒµ«àܯ_ß0ËKûÙhk«ß¶ƒÛƒþÖûñ âïþ$]½®n¸qnÜøñn8vØŒÀ?jíÛg?÷àƒ|¥ ©…R€\ä ¬¼qu“»>ºwß_ÍZˆÝ%7¸—°ÃfèãO`·Â¥î·ÿûÛ˜‰¥`ý…=3à]tÑE"Û_Cë…],<ýì3n»w>/o“µ–Œˆ&NšävÜqÇ`ÿÙgŸÃ.¼‚ƒÀ:s'Åñ¿<^X„qNàcK~œº!?ì^>_9;Ì æ«rÌ@ ”÷ýíoîbÔÜh£nnçwg† sÛí°=3” a«|)HJ±%-&sê©îàÆpæg¸n÷C÷É'»×_Ã]{͵ò.dóW9/?þqo·êª<`·»ru¯¼¦_l:áăŽZ[(_Œ»¸<ôàÃnä;蟕Ñ?Ø]ÂXüªÙ7\ï^þ’{üñ¡îRìÞúíïØ?Ö~ýÌ‚¹Óåâ‹Ñ?hõeÿ°ïž~öi·ýöŠAï={ËÓ´ÿØc¸W^yUª2à„ñk•À±ç¥_³3ÿ›…IbrzÅJ-Vr°ËL¶@kÎð—Y–$K'5%S4w$É9ïüóÜýƒ’~þ󟻃±ké…ÿüÛýþ§ËvÍ•Z´À.Ÿ +Þ…òcû/¾øb·2°;ôÐþ®cÇîAì2yô‘!nÞM=ꨣä•K)˜fÅšû”†bˆX³Ê³°CíÒQá\ Œ§FØÍd‹²tk?¤µ¢ÁJ+­ ÿÖWBÅœo?+#ö¡Ãöp¬Þÿ0×¾CG÷ÐCÉN§ ãǹ£~ö3´çyZ"`wwâÑÿ·Øb+‡IÔÍÀΛ›pPýè÷Fc7àþîÚk¯uG}´è3ßI' ÐÝ{ȳõ–[A§Ÿ›9{†ì=*Ís´iÿ¤Q,‚˜0Áú‚Â?j0¤íoñô×ø½ªŠœ_é»þú¨)iɇKÈo¤Ûm·ð{õêå6ÝtSw×wÉx#®'£þ½¼‡V öÏ;ï\ÌðÈè;Üñö¿ÿãþðÇ?È_êØO'œp‚ØTk´¿øñÏk„+X@IDAT·ŸÕÊÖ×~ëo4‰˜/ÛÏøgÿ“„‹Ž¤dˆ2¿<ÿÐ;ðO§L™;&LÕsÿYçß“N9Y<÷Ýwdzä‹m]Ú//\–ýñÏÝìì:éÓ=ا ùþ¿,ßÿO9™Ç!8¼f¶‡ÛcÏ=…æ%}þ]çŸÐ@,ÝñWg ËÉü`DŠamÌVŒ”ùëSOåŒRÁW‚*8©›¹*guVeŸ½÷þàÁƒ%¦NºchôèÑòú3f„Ò¹…º¼B"yÚb÷Qyé’åÈ?ý%X¹ýŽ;Bþjba¥cGÝ1t˜ß1„WÂ*]:ëŽüЭŒ1"d£}ììo¹ÕV•Ù3u'…)}ÖÙÁ>óêê™JÓzñ±áö;nAªcåÔŠU/j “’ÅêŽ×xªÊž3gnåÉ'ŸLŠ.–xʯO }‘(2Õfÿ\‰3§Çþ¡"w–àG­ôs;î ¹•`ÿÿØT¹ýSÖ)e‘$^Ó©ª›ä«‘ÙXX¨ªìåý “zå²Ë./­š¦_V%ËÚx5OÚѯ_¿‚îŸàßÖÎé3#>ÜacüžØU6zô(ÉÇr¹‹îàŸèîâcýtE²3m v ¥Ï|?9ÈïêZÞ1$EWæ/\PÙ[0i¿‚WÙT ×r‹TDÜ4ö^•ª©Í±býØ£'Û3:è-€],”…>Ãkc"c»¬ÿ¹ªŽW¯BÿüÉÇb*ãà°KŸç‰`‡yæ#kÿÉGW°°pÅaŠ¡<Óa¼Ÿß1Ômì ¡vûƒë`õ¥¿—ñŸ4Áïò~=è¾AÁ>qÜy—]|{êd—¤•ÍK,—¾£µP‰øø”¥s¥åÚJ:Íì OY)íÅI¤ÒEëDõ²¶¤kdNY)K2J¥‹Ö1ÝÜþ2Z’®^ÊJ鈤Q*]´ŽéfüËhIºx)+¥#’F©tÑ:¦›ñ/£%é६”ŽH¥ÒEë˜nÆ¿ŒÖóÿúW¸/sw¾…Ï”6yŒË%FI-ª¬-éRVJW—©ÒEëÄ\emI×Èœ²R:–d”J­cºßŒÿ±Oíù+íSÖ¢\[Iר|ÊJ騣TºhÓÍöËhIºx)‹ô¿¤Oõù¿Ø§ª™êG´«©²¶¤kdNY)½ø«5RŽ–K*&ƒjÊJé •.Z'(/‘ÿÛïï~èoÂE—ýÕÛgmS›)[bÔ×k?œ.Í…?C0z&Î9ðÀä'€Üßþþœ²2Îé%;$W²c¨C‡öî¸ãŽ gªÌ_0ß½ûλ8»ã ×»ðG7»æÌl©}µÈapï½÷b7ËÛ~ «¡°p¡.~S"â/Ë&}àvÜaì„y»Z¸§ŸyZv³@IµŸþ_îs~µ ‰K°ã¥ùŠñÝ[–~ø‡«.<†y¢}!Åì=÷°n Cu¨—†¤®ž­z¼ªL~‹&™ÉÝÔŸÄÝ<4÷eœÅ2þ<ÉѬYÙÕ ÅIZV("àmxÓ©žÊ:tèà~~ܱnÅïê™7óç/ÜØ?믷žì>þ™;gò&øÓ.Š Þ<†>¡å¦–Hk«)Ó´bc%9ŒÕ¨Q#9Ãg$¾F5õ£ÜñÇÿŠóÅS“5©Šö©¡zš‡éWq®so‚ó›,ÇpîLÃà½7zŒHT†+ qôÑǺØYcöëPדè_ߨðê«üZƒZåXü«£–Æþç_%@A¹–CÙëÉySocÙG8À[vºQ$ÊÌ[Ý~âÆ3ª¸#ì£>n؇ –4¦Ù¯Ê=íiß¡CÐk»üË“…W^Ån/h?öαBæûóÁùW} έºÊªîì³Î‚ž–ùþC²?öØã²’HþüÙŸ]ÅTÅ­²Úª Í(Ðòü3”ÇÌÊ5ÿÃ||í£([ƒ/Ñâð¿gˆíƒ56´Çí²‹/;'Nôì~cy›lÒ-èSHß±ZyoLÐ'¡åòªu«5þ£)Ëcí·6A ¤–eõ• á’èz^¶O 2þÙÿòøÓ‘’ÉÃfŒ„åÅyþQl ! '¤Î%)^ÄUC¢ë9ª§yÈZÚóÿ¹8ë‘÷ñð̼Í6Ûø»RÁ%rÿKß-;þî9çˆgí€7اihhþ·´ýY±¯g²:ŒÓ¤O¿óOƒÄßÿ³ß:f–ñÇû—¿ÿ4ŠоØPf pàî|9 ;tü'ê+îÈŸé5Æ«=¢† ÐŒ9êÜ”ÉSä•-¶ØÂ5kÒ³vv{`»êþó"ò(äŸþŒj.ëÖbŸ>}ܾûöñÒŸt…[–*ËöÇë¶ßa;y}Œ™ø£}=”ðÿË#F€—„}쪮[·nåZ·níÖÄ¿îÝ7•,¬Ñø ã|v³¯É}pøõ¾ûíëõ|E|Šù4€`!áÓÖöذ¤ÓOÿƒk‡Cɹ W=zôÀëX«»ýöÝ×ÝrË­x­ì“P< ûa/LófM{’¬aÊëŸÍå°å®x­n=vGÿüG«Š ñUÖS[©W ûàtôOÁ>R¾‰’'IqððoQí—º†B*òã¼3,_ ¯H.©}±íË5û½zíˆ"+xmêA9€Ù·Îý‹ž¢ ývUæ5(ôX§ö»®× £¹ $…ÈEÕu Ûú,)I³  %5jìºt^WüÀÊ÷‚†Õš’ˆ?ótîÔyˆÊòåZû…½ûi{ÆŒõ:÷"g9íÚµuß_çû ‹ö·Úz+)–—wG¾+4óÐL;¬·Î:ëÛ­%VîÝwß õŒñ÷•7s¡+ŒAS±ý¬«fÑ|ÒÌZø[¹ˆyà¶oŠØ¢!éÙ³¹Hª¡W¯Á©È+„<¼[C‡óëbÓÛÓw, P_nÄßêî똨*i)ù…KÜþ¤Ðl_ÀÈø›ßYœý/RaœF"?õó—<ÿ,ëóï'êþŽçޘ徱K£ÛÛ Íý_˜!äý—GNüýïgEѧø£µ=>‰|ÿW$üoÏЉ˜0»ÜŒXzþÏß{2NQG݈ MU/?[E!& ŸÎÏ?ÖÓ+0šÒgyåxÀ–Óù–¤öÉ.¡ñ£‡Îº?`ñ‚Nv vóØ11šÉsâvÛ•ŸRd¨“/jí²S/·l^þ¢»ñ¦›…/b3hž &Ó¦V…?ÁbÙšG¯fÿ‰'žC‡ µ_ÁnÖó¼óÎEÉÂ>øà”¤? 'OÆ'ÔAך5‹_gÐ’Y›ŠëÔ)þà“œZIÈb¥î¯5ãb¤òµez¥uSÄçÈñiñ—G¼ìî¾û7ðÎ;Ü“O<%çÝ úÛ 9w‰;¡xNÓþûíÏŒºà`Ù½}¶nqö‡àKY»þh7”@M='ˆ»¿¸ 6g,ñ ±p–ÛÆêê‚Imû,¯l¡Õ ?Îõ|gý©¯U·xD|Ò§ªíÓ€_ÿcû…œ¥4lØÓn«­¶r?ùÉÁîߨ©uûí·KéGq„[QÎsÒö‡~²êB«–}j/仪AO=ŽíV\´e¼ C¬Y™¾¡öcÙmÑö¥‚¬ Û# ‡Cç¥~«á+j åö¯²ò÷DŸ²é3¦3’<̽úê< y|&¡‘^Yò€@˜>yj¶ŸR/ éCÙ¾i¨ »ô;óKµÌk!ø6šÊcá[‚l¥;Ù ùøã;ž+¦¾s|ç_îó#á;¶Ð„düÔ°¯%Z¹Z¯Ô¾JÔÿLK+ä«å³ø”eõ8GÍló¸Jþý*ã/XxG‹®•ýOÆONZ²ù'ÿ<ÿ4¬ùç©§Ÿ„sB¬à3¶³$ÏËòü÷¾PÌ©›³õV[ookÂkXþÇÊÅû‹¿áÇ'£|ù½ÿ°O%“­Â8U¯ùþC øU|'ð¿àý×·¼û~ÊýW[2QdxØ®_MÞúéëd;tr[l¹…½ÂT W9wÛ•‹pæ­¶”Ô_ÁN /ºØý ¯µl†ÏzkÀãŸÖô7YÒ“ i_ƒÅjC®fÎD°ÏÃs_ÄÁÖzèk;ÿüóÝ!KÕL­=w,øRGz»fË¿Y³æÈŽ€Y³gIzΜÙN)Ø7,¤®VˆŒ„ý¨ô|³'ÆÁ³´ÆéUMXû9®¾úêîØcqOà€î?˜„OˆÞ)xÓ>¾äæúîß×Mœ€è R”Úç«/Þ<>­>Kåä”ìòɧXâ¢n Xy¯Þ½üòö}±ûåñ¿ÔOsû 7’Ø'¬t±¾Iì‹A_ËaíçáÞ^„O„kÝ¢´DXí,“»oFá€b)Ë®VØÚ7[ëuï½z‡.î’:á„aQè°ÃuW\vÍ#X.]M–†Ôhÿ˜1cB×_oÍ)YQYøùä)SCI±TQ[dûiŸŒ†¿2HÓ— ý£GnÈCój¦dß !þÔ(âÿÞè÷„ìë­ÇÃÛ+®C‡RÇ9D¾`”£(õ¤>m´bäiÝhC>{1°nуҙWi¯ÅŒyü*B@iÏ??ö¨Ô¥>¼³Ñ†ú%©¡U³ùŸTÔ×+úøßÒ§WÉÕðæT±!ô¿¢cè)¸rUÒ‹Ä×0ÿÈÂ~7Iˆkn¾ÃB—jºK×.¨CÅW> +–Ìïÿýëùçà߈𵣋.qëâ5ñ\>ƹ'ÞRÚÂh_TÀŽä¤)¡a˜9Í>¿®5ô‰¡®{îŽïN¯¯QoÿýúºIX\1;öÞ-e7ß|³kÞ¼¹ü[a…fˆ›¹š¯ iîâb†êúÂÛW&¤…$Ó ÛÁÌ!·‘Ve1;ÏIJC+¼ÞÖ·o?wûm·¹«®¾*ˆÞxó5-QŠQûmñzÍ=ƒ¯·I¨aÿy|aJª~YlÝu×Mª_ç>þøcô±jh/ú&h‰z­[Xe_’­ºýmÛ¬åó9÷ì3Ï‚®Ýþ²ÿqđءÕÉu^·“ûãÿM|øó .žð,§¡C‡ºëo¸Îý¯‘wóÍ·„ó±h”Í*Ô¸Fû¯ÇÝLwƒ ua¨å­„Ç«_ö²‚*î½÷ÞsøDºæñRn?…ܹ´.!:­ÛÙ Du1í?\òtBßvv<ã(%©½IRÝÅÃvúŠ€wýõ×!—¦7ôíÙFÎÐîjÓb¡ãýÿÆ›nô9*nSžÝ?Úö8wÇO\wãËš'Ú¿ñÆY Ê`}•SñºÊ*«H¶W^¯~áõÆÅ´?”îýXíû¶‰Ð4,VûiûiÝÚ¯4¯Ä_);eêà;à;˜wØ÷e ^é/Ò~Éœ´³Ð~)ÒºvRK*ã5å í'ÝD¾¢ù?ãŸzZö?C E%?NHþCæ²<þ¾èüï¯2ôìÙSžsãô•zš¨È%åfÿ$ Ðÿþó¤¯6ëÙG|ÈÏ:yþÒ›w¾ÿo^†žþýŸ¤Ú§üïÑ<þ¾¾ñ×Èžtíwƒwšåqþó3ÐÉ#L!@£‚ «üyñçO17Eþ£ïâPjÒÞîæÊÓuî½÷ßwÇs >þ{o ìŽäsXÈß÷i_ø¾0ë4Ï“ÎâJøJšëÙ£§è7_ñ;îî»îzúôi®ÿþr† ËâÂÐ~øC‘yæŸÜYgŸ…×Wfø+nòÔ)îŒ3Ït·ÿõ6i`Ñ>ÔXe¯Ú·šR_3%+Aw,«m´R¼¢‰Æ».8ø™Û@Á«x³çΖzRcÆ´éîQþ•ÇÛoß¾½ÖƒÅˆ ¼†&»:4yí5Wãâhúo&>%þ^ÃW±X”kE4­U»cà@ôÏ<á}ÿ=w4v*ýþ÷¿GÙÐ@Ùì ©qšôbÑ1ûždòóÆçç+rPÎÕ×^ãfȧ蛉ϱþ’›*uS[Þ¢{wô»î¦›o ¥qÆ™…ÃÊ¥¼H†˜·–}VÔÊ5ü ú›Ô¿ οÚ~q$SöìÝ;Â,í×Z{±ý\TxÇnî¼9nΞ¹òÊ+Ü%ØqŰ5OXCÇŽí%f5/¿ârù ü'Ÿ}êx`°ÛŸm>W©ÛÀí¿ƒ?7Ëk—Âqgžy†?ÜçªÑþwÞ…<7IiŸqò` –Û/%P†´=· ¾³qàøUW^é.¾ôˆ*8Pùî²µÎõ?¼?_I*sôÑG¹‡~Ð-ÀÁåÜawÍÕWË.=ÚÙe—ÝØ-Èü‡aüYž£Ž:Ê Æ™N ,Dž¹îjøéùç_ åýhç¾ê™âß‹—Pß+.¿\|šcû©§žû)viÿÇ/hx_´þ×ò€€àç8+IÙ6ÿ‰®×#=hÐ a5mÚÄm³•úNï=ÕwRûl¿†jÿó‚$ªîË­ h=-ƒ¤x¥˜7Û¯žØÇ†e¡ÿ L‰#†’¶–'ãOD²ÿ‰_øKÞ%dı“矆?ÿŒ=F:¯Î׌!ö¡ðòü·LÍÿcdW½sk´jgêüüáÝ{Ù¼ÿ3 }YçZ·l‰väûoœ«<„äKÜÆ‡ó‰Çãß87çÏÅYÊ Ÿâ¼.ÊÆâßxÈçËoäå 슟H+}íÔ_ÿš°WzõêUSgþüù"§Î]wÞÍ¢*x=«ÒªUkÏ—™ éVH§´tg…Ÿ§f0³,‡ÿ°‹Cø•…&‰:A8ëFôñúF¢ Y¯ºòª`g yù T®ôî½§ä£-¬MU:wé’ÔÙU°SÚ‘Úuàë¦fâ5VUy5ÓÆôq 80ÔÉláÀ^ýœ·Ç…üƒñ‰óZaÁü…•MºmâËP¬Ûµ[;”yÑ…#ÛBß?-ŸíoYoÿ|L±ªV¯Ð?A*E§©BðÓßÝ6¶ºi·k×NÊ£ý /ºHóêèŸÅf×m¼±ÛÐ(Âé§¼òXiû7Úx#ÁM³T4p+·ßÊ Yîû!_ùBð¦d÷ÎH`±i÷î¦*1_Ó|_cߤígûŽ8òp7fÌhùúX(¹Ö_y†¿àvßÍ÷gR"_|ç ­óýïGã&÷ãoíµ×v ìøN­Ÿ^WÆîÄ #iû-+5R=Ò²cH@ †ò9g±ý M›`.òêÊÁ•È€¨IrõI'ìÙ*WÈF¾ó6úÿíà;Gõ³äËŠÕþ§¨q)¥Fÿ§óèóâ«Ò êkA‡ùJí×µz_d¶_m2ÿ,3þ #²ÿ ÄI•/Õœe-Þ²ŲoeÿÓ¹-¢ZdÙÿ–Üÿ>ýì³€fë5õ9S]7ûß²:þ>Å.tŽþǯ,K(Í%yþ!DŠðü[§­µOyµ&DNžÿ.r'PdJ8ÕôhÚó¿¿‹Ô‚WÏS5¼Kå’ým¼ÿà-±µ]ZšÀ–§éz$S†×=^íu9Çf5Ö¹X~ñ5(ªl¯œêÕ‚jNPŽ”Ì>Ë~ûí·]ËV-ñãt]”*RN‡’ªÕœ  (™ýÈL(ȧ͘†sLƸyóæº¶mÚº5×ÄQ,°Cµ5rfÎ˜áø£~.ò¶ÆÖRìΉ‹m,ÀÛŸ1m†{íõ×jöO¡¿«ÍøjT ª9^ÕG|Åmä[#ݼ†µVëµÏF ¦ê ™×”^GýÖl½¦köW‡jkÕœê\cFq[n³¥›úáTwë_ou[l±…ûèãÜB¼âÄÏ’s¡âìsÏ–sd˜ËÅø¢Ý$·VÛ6‚Ýu×]ç~úÓŸbám¼‹-ˆëã5¾UWÕOÃ×gŸ ܲÈó‡Vn±òbûßðÇ®<Ck­µ&FÊT[#gó¼þº[yØÿUJ?˜èÚµiW¨8këøþûlÏznÕð©{€6¨“ÂæÏŸçÞxóMפQ× ¿MšêBb Õk>¶k¾ñÚ®QÓFn½Îë!OõBk•=oŸyÙwS°Ðܲek×¾ý÷±P[\(«§ªÁ¾PZÜø+´7)t |ƒ‡¶c7’»õÖ[°€¶¥ûh*|Ÿ­çk…£ßãÎ9¾ƒÅ"–Aß© _Â~±¬¤b^PÍ)æT¶ÿ_÷Íj´«9Å ý/û~”ÿ°Œ“ò *§ƒjµ š”#¥ÿvþ…ª¶VÍ)æT¶ŸñÏþŸÇá!3™'Ê“H9T«Õœ  (åù/ßuÿ-. ‰Ã$¿¹fd5n©Ä •LÔ²¤Ä¼…êx¾‰E B8l¶~Xgü¿ÿ;ÿÜóÜoþ÷7n¯}övçYCàæ^x¡;g Ñ_±ÇR>»Þ¦ †Ð9ÜùvôÑGGÿF~õå’G[Rb^ŽÿóPö6~¡‹;Ŭ=Ö~­,êœÇ¿@a¼–ê~ó›ßȹfvNeÚÕzýËEð¿«HÖÝ…ÍKÃéú±ÖVª¥0rÿçûO¾ÿø1‘ï¿yþÏ÷¿äQ"“ˆ’ ƒª–”˜—|ÿË¿òï¿üûA~þ Ó£LŒ:96˜çoÝ‚¢s¶t–= +ÜMAföÐv®%–”˜¿Fåù2@¨gŃïEÊÊö‰‚T$X`+n†ó XRb^2þD¡ÅJ-š¡>îø™Ï… <¾òµ¶{ðõ¬?âÐfrwÝõG¸F¹‘`F|)@‹H.–”˜—†ƒ¿TÉÜŠ« ˆ<¥ÍÌãOqI:½Åw[ﱡðÇá;Ø)Ä@Üæâ@múΙx”a×]w•XA¥FÃéV,Ï¿Ú=¡{ÑEì%ÙÿŒãÙ’J& ªXRb^²ÿ(ˆF˜ „"™ýxÐ BáV,´‰‘P2aPÁ’ó’ý/@A4²ÿÑK¢OÁÇXÙÿžãÙ’J& ªXRb^òø P<þè%ѽŽàc¬<þžãÙ’J& ªXRb^–|üéÂÀÂ$p]; H™žoBä d +r¼š•eq0™í!AÊÀ+ 2þ‚‹³dþ×§O×fívޝ:a}oÕïÉçÕ7ÜpC·â +¸¾}ûâKpÓÜæ›oîîÀ×Çd„Ñ„_@)tÁaŸn.Ù¬ ‹)ðõûÿÂÔf¸C}sö—vûÿ[û}öíãÖÆ«™Ó>WßYå{«Âwº9úNsï;Óà;[l¾™»]|']±z„õÅ?¼£ÈBÊ2žoBÈ@Väx5+Ëâlß#ñ/ºRæðÇY<»yóôëS²ƒÆ–{R\ˆTŠ"÷E®!7ˆì˾ÿñ ¡Y3gâkqð~Ùl1!÷¿ïóìÿyücеY6Ì«2@Â(Áhòt`b1#­¶8ä‘çߌö?'~dHFIyþ`”—„9ΊCnyþÍóožmøø‘!Q%¦|ê¦2Ë»äqÈ bQã/ùÌ•ï"‰âgÚË®kižËhAÉ*†¶ I$þA؉NÁ’²}Á3ã/¾}&z‰ò4ý•ú–«¿‹³cäëhÿoÿèø~XvæŸ:øÎŠßý.|'™JÍy%ÎóŸÁÁnö£Wˆ8²£D–þg͵}I+”8÷¿Á{t~î"bÁ£!Qöÿè3ÑK”§é¯ôþ/]ñ0dÿËþWš—Ìã˜äù‡(äù—(hÐqgŒ°à!¬eóù/üš)VîŸö<ÛñàoŠFr.‰ò©Éü΋ 1ì¸T%Áœt"MQ¶_Ä#ãŸýÏF c+ÊkA–ÇŸ p³!•çï+Hžóý'ú#ß‹xäû¯ÝgüÔ‘ï?Ã%Üe8xT–ᅵIüó HRùþë}ÇÉ÷ß|ÿ¾@çÈ÷ß"ùþk÷?uØ=†¾"¬¯úþ³+@I™´Á.1_•à_ÅË|a$—4O-å*9”„WC²²ýŒö¿<þ8§¤ó‚Ì1É¥JVb”’’Sx5)+Ï?yþÉóOž8a¤óB2õĹ$e–”Kɘ§† eåù'Ï?yþÉó'Œt^H§šš²’r))Ù…WC²òü“çŸ<ÿ,óO£°¦Š¸(ĩȜBèZ³‘ç)tLpZÁU 5Y2£LQ‰CÞÈÊö3þæ ðñ¬äâ}‹œ(Sfö?¢,GÝš¢nø,¼„eÿhÏçhO3´ç»%¬KM—äÒÆ?Û/ wöÊ2ìµ|lQ¼Üÿ¥îÎý_¤öýß|ªà?Æüq!? yü)þéF0*åňêaG…ÅP…üL0dü‡åÿY³f»ñƹöÚ»¦Mšˆ3ˆÅèvTX UÈÏCö?Åá+ò¿Ù³f¹qãÇ»öíѧx.MCÆ¿ängð?éÓ äww³¦§ÿ]Èý_ên¸ ô¿õv¡ÿŒùâB~&Ó~|®^5üNVÍä¯7Üx£ÛkÏ=Ý^½÷tçw¾Ü!¬\ªrèÿgï=­(®ÿñóHPPiQ±ÆÑhL¤XQŒ‚Æ–ÿ7~£ Q£I,±å÷Ô[¢`bK± (@é**("òèj¤×wÿŸsΜ™Ù½÷xJxÀ,¼3§Îœ93»;wvö êv\7êÖ­ýfFÖ2¡PlÎйÊÛ7˜½RIl_d³ñ)“Õ†°ïÍ”þ[öÇŒGÛ7ÞžvÞyçLYþ[ö3F£ ÛÇeÛ~{Ú©9Êö¶d¨ ¸!ê?vìXÔ§1í”óu©"lû¥ì”Ã%û[FÿOí_Ú)þSüo ×ÿÒÑ?ÔoÞ÷?©þ¥=`ãßòåË膛n¤ýØŸ¶ÙvkjÛ¶-~تKG} Ýpýõ´|é2(Š•¿ÿ.b-B˜}ŽD†7‡û¿¢J®±!ë¿|ùrºñ´éþûÓÖÛl«mZ·.ýà˜£éz´é2LmHûk©¶'%ûÖ­Ö-þ¹Mo¸ÑÚtjצ ÕÃýG6ýÝõ´mº>Gòÿöÿ¼¹s鑇¦.‡t¡fM›ÑÅýúe\¼%Žu|àèÝSpˆLƸ`öÑG¡?üáTG~a úxÚ4š„Cá`MîˆÀ°ßRüʘ³Š„ßcÓ¥ìÁ”/ÌD&ÌjH#¢‰²•`â›±ôÓmûóçÏÓ‹-Ê8§&ÔþüùâûÅ Q6ï x~ú:í¿±ü¿õዯ]2ÉFhÿd?ò@ò¿wFMèÿZ˜Í{üõg ÅŸwGŠ?sEŠÿp½æ²é^ÿ¹E] ¬q³i ëÿï¿ÿß³}8eŠ–“•ñ¥—^¢—^N/ NÏ>÷,m³õ6ÊÃ÷ÿÒ`8Y'fÐÕtSª¿UÚÊî[•Øï?¹>“ßmÚ“¦X›ºvádø‹/éßK/ÓsÏý“Fܦiü©éíÿþïSÏ=i*ÚÔuOŸ—~Šv}y8=÷ì³Tmê5>ƒ&Ì­î™Rû{Wˆƒ*hþ¼y´&U+gVªÓ˜¾›…UZòš”àdNd,|°‹ D|Ú ü_K‘»ZEbg„ú‰bÂaô›£•|œe^~¯ÙæE±Z‘1‚8:¢F 6F¦tu H¤Qbþ¸0[ ýÏ?ÿÔ@úQFß>þaÅ›Id's´YÊ>Ç3]Wýí¯£ïö=¹á2äqá¯À Ö±‹UPUÕ1b7+‹&Ò§ŸÎ¥ŽGytò‰Xy¤ïÁÆöo½ýVZºx)ýÓ³iÇw¤Ù³fÑ}ƒÓø±ch–ØpÀÔd?ìg$C¶¼']D »3ëœ={ Œ€ V®XIO>õ$½ñÆôéôéT ~ÚoŸ}hïŽ{Óqx­oë­·v¿rèõ‘oГ? ÅK—Ð+/¿,úøt=–—J\sFÚ°@½{ŸN­ZµbŒØ_³¦JÚgܸ ôÎ;hŸéŸRÛöm騮Ü>'ù•aQ©ià­·ÑRçƒvlF³æÌ¦Á÷ ¦·Ç¡®}Øû¢}Þxãu™ÐYºd)½ŒYn= X¶|CTDN¡~úiÔŠ÷¯bcÁ5Ôû´Óè±Ç‡H[_vùåt#–XòÁ×5ñ©¯¤ ýÉ|je/çÿ•+WÒOÓÊ.Œkyþðã€Ó˜Úß»ÖûpCÝÿ6ÀŰçÑOñ/Þ¤ Út0¶uùöÓO í5<[uï6M÷ÿqãxØî)-vË=ÿ4nܘæÌ™§½Cb»íhʇnU¥Óf:4[súÿ¿þã£d¹ƒ¿SÆGUá²Ë~ÉWB§NûIÚ¢E‹BÕšBaöìÙÜ7 ˜Ù,ì¹ç^Ü…¡C‡¨äzè¡Þa–Ïÿyd×¢…‹Ä†’„ùX/&E £F,’cú ûy‘ֻ힊Âgž\(wee¥·õÂ{ï½çe˜ë¼#«[#Gpû£® ,pú‚N«Ã¯sÙFI9'³";hÐ '‡¤Ìaµ¸>'ÜsfÏ)´kÛ&[¶ÈM›6-<ôàC^ó™}ú€7Ø·2–J1I'rlñanŸ6QûDõgÿwíŠöY´œV>¤øoz¹}FÃùús~РûÅ&/<¿ÉYŠñ,Cñê‘ѓڜ2åà ËâéP$0+§‰ZÎRÃgS£²¯Ûš¯£òpýÙVÓ¦MÇ:áªBŸ3£ú€ßøJùß|m–ï¸#oqý;uêTÀŠ*c-àW_ï‹/¾ÈÃæ;Nñ `áÝwÞùZõWaó†7ÀpœFp·¡4o9K+›zª>1ŽÓŽ@c6”æ-g©qeSOÀç"&ÃqÁh̆Ҽå,5®lê©ø\Äd8N#8ÙPš·œ¥Æ•M=UŸ‹˜ ÇiG 1Jó–³Ô¸²©§ às“á8à4fCiÞr–W6õT|.b2§Æl(Í[ÎRãʦž*€ÏEL†ã4‚#И ¥yËYj\ÙÔSð¹ˆÉpœFp³¡4o9K+›zª>1ŽÓŽ@c6”æ-g©qeSOÀç"&ÃqÁh̆Ҽå,5®lê©ø\Äd8N#8ÙPš·œ¥Æ•M=UŸ‹˜ ÇiG 1Jó–³Ô¸²©§ às“á8à4fCiÞr–W6õT|.b2§Æl(Í[ÎRãʦž*€ÏEL†ã4‚#И ¥yËYj\ÙÔS¨*LŸ>]îßù¾?~‚Ù88à4†Ò¼å,5®lê©ø\Äd8N#8ÙPš·œ¥Æ•M=UŸ‹˜ ÇiG 1Jó–³Ô¸²©§ às“á8à4fCiÞr®MÝ}¬¶©IªÚd=p0ÍèH#и ¥yËYj\ÙÔSð¹ˆÉpœFp³¡4o9K+›zª>1ŽÓŽ@c6”æ-g©qeSOÀç"&Ãq`üpíŸn½ûi J&âÕ|é³iTvŸ‹˜ ÇiG 1Jó–³Ô¸²©§ às“á8à4fCiÞr–W6õT|N˜x΂ŸëN9åä™ft¤h ¥yËYj\ÙÔSð¹ˆÉpœFp³¡4o9K+›zª>1)®Ž<G3ÈxÐÅ5‚N±$Gûö°ã}%ÍÄ®÷ã'Œ%^õÀW““N<ï°> ÷ÂóÏÓ”©SäëX½±z¢Ó~ûÒªª*tßý4+ˆ^~ùºõöÛéª+¥²‘}n™§ž~†¸–+¨#VÊyä‘RÌQX5qø÷—2ñÉ?ÏËœ…ÚÇ?u=âoôèÑ´çž´š‚(ÿó??é¶íÚR¿‹.¦:[ÕÂê¡§hذhìøñtÆg¢^ñ«rV7¢§Ÿyš  ò;~e; p«NF¡lßôÎ;PU ‹/¾Xg.ðÁ9¶Ï~Ú¹…~1ŽKÃm6v6jD§õ:•:í¿/­^]À¯G÷Ѹñãè•WFÐm·ÝNW^y%›Á¡3! ع0` dµ}º ÏÈQ£èûâ’_)¶Ûn;±ÿ V MDÙP ”­~¹Àåâ Œ-xSj0’°qÞrœfñòÊÚXú'(ÁH©ÊÅ kóÿeW\|$š®ƒ¯¿ûÝÃáë/è½ÉïÑ=wß‹XÿŒê×ßÚ[:¿ºðæÞ|ðŠ¡I“P” “7’ª]õu ÷u>¶?dȺðçˆ7“qÔ¯_ª] ñöä“ôâ‹ÃÐÆÑ™gü„ž}ýˆqŠÖiàÀÛd‰#¯jÕº5 ï~¼'ÀÌÏfÒ9瞇eãXD,ÃüìzÆ_V>èa¯õŒuKYCKöׯÿ³÷‚ÿ‚“ÿSü¥þçzFÔ-Òøc㮥ñø‘ÆÿtýYûõçí·ß¦Ÿÿ3¹b7Àkf=zôH×ÿ 4|§ƒ¬`"tMÞþÚôg?“Âò€µM×íùƒGWË(UܦRÿPþÍgüûÚônSüê`îÜO¹¦6î[ªX!â”Æ¿µü´¦. þã8‡”x×ü(žбzRÊfퟗb8dxÅÐeì±B÷nÝ ýúõc~÷»ß0ØþùçžCª+V†Z¨rP<­p×w–,Y**M'>¿Wà<¬³%VÙaóVŒç?i¤=ö˜±”Lww+†~"+† …ÊÊÙ…6nÛ™8q’Ê9 æÍ÷ö±ðåïω' iVå,ÿ¼šˆÛÊŽ5PŒ‰V/óúkå웜/ Gd“¬Ïx Ë\"gœVoK=«1xDÈ}ÆYæ9ã4»–zVcðˆ,!ûŒ²Ì%rÆiv-õ¬ÆàY Cöd™KäŒÓìZêYÁ#²@†ì3È2—ȧٵԳƒGd Ùgã,s‰œqš]K=«1xDÈ}ÆYæ9ã4»–zVcðˆ,!ûŒ²Ì%rÆiv-õ¬ÆàY Cöd™KäŒÓìZêYÁ#²@†ì3È2—ȧٵԳƒGd Ùgã,s‰œqš]K=«1xDÈ]Æú)¿%bÏÃØâ"ôÓH…É›]K=‹1xDÈ}ÆYæ9ã4»–zVcðˆ,!ûŒ²Ì%rÆiv-õ¬ÆàYÀÈöüÝ˯Êò•Ë™¼ÙµÔóƒGd Ùg#°a»í¥|3g΢Øïˆ-/¢Á¼‚ž€z¹ö1sf×Rfi'(â8y5Ê`Å“œÅ_­Zµé©§ž–•_ó±±ö…ò*/Qû+ÂWÏ׻ؾrÆö÷Ùw_±Ë¿š]Ò¿?öš_¯‘Ùž­Ø×ß;ÜÔûú Âì£ÒZÖòößrñÆ¿× D¼Õåx‹êVß¾ÞÆ˜1cT=먂Î?ÿj¶Ò|j¡b\V±¾wÞåUKåí ‘¥]‰œTŸRcy†Íÿ^ˆqL0)«¿àôäÔ¯—ÿ#q+ž ’}v{ܼjžw­–ü/qŸÌSÖï-Uø-Å¿¸B"'IÞ…æ?F¤þ'^À)xEýå¼–ú;(s˜§¬ßYªLð[êâ ‰ œ\$yšÿ±¡ûß´?¡·Þz‹æâ‹W~cÖs´ÿÉÇÓˆï5ÌŸ+1Åíz&·)žÃÄÛ®¡ÅïÉÿ›DüM›ÆýôŸ4_›Ö,D}ð&KëV­¤ã“kÞtÿíQíëäç5Ѥ㕞¿©çÏRíǸ =þse\M|,~bû21ä9 ¢L8CZ¦†Vabç Ä@³›¼®ÄÄsÎþ)¯öÈþª …¡ùóæãu°tÐAi«­¶’§üãј1oƒ ~üÿjÑÂŒ¿Åo`|Âñ'8ZT&` Q«c/=9fàÕšÃ;ܽ†EòÚMûö:¥ ¬z± ˆÍ⨠ø|g3l Ü¬Yøë„É;fbÓ_=̾:”7¿>á-›Ý#Šv5aâQÊ•(¶qÒ2D,¼ú7¿&ìã$9~] ûQc¼Žu"ì=øÀCòZY¦þΦ$ð…U^oiûó }nåö9(´Ï±ÇÒL”èQ … ¿hõ·i'nŸžR¦õ­¿T|-õm±Ëjá›ymöØÃ½.§^+[ç‹|íãö Îþo¯þ-5Ç×:ø`_cu|ýmõõƒðõ_:U¡þŒ0û¢+ö û°±·µ6/éÝñ&Ív”ØÛík¿¦êÏXV¨Zs=·ÅÄñ}ôÑ4f.=I6wb9-œhÀI5åØ$ÕÕ×'±¦¸ÿ9I<^o²Ÿü¯Á‘â}#õ¿4þä‡HŸÆZ?žF8ð¥ñ7\½Ö÷þ#?„m~‚ͦŸ¡{ÿt/vzo‰¼K.¹„vÝuWúø£©®oÂS)þ\¯¬ùýïôŸœAO?ù4݃6å­;¸é¤MwÙ•ô1]¹17¥þÿ“ÓÑOŸ~Šþ„-Dz÷>Mb±?úé.­Z¡Mu Œ4þ‰[t¨òã•â™[}Ýâ¿Â&8R¼¾šßÿ¥¨¾¼¡æ ­{ý¹ÒÌÍ=%71þÍ7Þ¥x ìëV8°ð)¼‚Hµ ®HÓ‹/¾HM›5¥~ýûÑÛoÿ‹öé¸]„}on¿í6§üø_‘›LR[Xq´ûnÒ‹5ïp‰]ÁB%éµWGД¦ˆN¦Lš4‰~ó›ß0è­×Ü9s\¾@óæÎ£ùø›‡_NxV–Ó¹˜Ì’zëÕ¯ÇEÄÙˆ××kö™G8"6Ç%æW«¡+jÈ o‡=÷ÂNè®»ïò{õ,\´Ä3Ô¢ˆ¢@IDAT§oÚµÕ®ôäO8½ZoŸýŸÓ µ/íÓ¤)õÇ~?¼b†¿ÂÅû݆}Ÿúp»º¨º¨bTäìGlÁ®Bx•J¨|Fyãô(ˆ(œUrënŸ+¤º¼vêÐaOšˆ‰›»îÊûúiï뿈¯ƒE.#çø/”…#ËÆ¬ˆ'þåÄYâÌâ x‹?æc†zõxï(WN§K'ú²Šk¡ ŒQûÑÜv–µFGqýÅÁŽ×ø…>%g‡Ü×ó²'ÿG1Ê`Š?©ÿ¥ñGzGqµ‡§4þòý÷ñPÖMæ$—Ú¸â{—ÆVÈæøT©’³ŠCnÓô?ÿ¸ÛóøtÎ9çÒ#="+Åù^}Ñ¢EøÚéY®Ïm¾õw ½YµÿîXÔ?ÖžËmúˆkÓ¦Ú¦}ûž)©±^ un±~’Úcßÿò3pž=e/QmÓä™z1ú©¾i°iŽ?5¹ÿleòøÓîF|.{ÈmÞþ÷Ó96F„Š'(­@Ø­[­[ïN±ÈnNé&y¾üòßtÌ1Ǫsç.rᙀ•:±*ã ÿ—8ð?¡ƒ%!ìk9âF°W–´,úðk͘™ªÃư7Žlä{ÑE ËÍ¿¿›û¾$0Ÿ˜{×][Icó ÅÇDËðŠÜr|f}ù²€‘ºü l|Ì3îYûPÀ…T³Pd3‡y3 •Ï C x"a“؟л¯áËVø”ÞÂ×Þäóç¬ _r£“O9™° rªËTÔò3sø¤ü’`ØAÊWA_`ƒåþíƒã`×>±™ø-Ò>ö7²7¿äÕ%®´– g H'%²öE¡?YmQ!í¬%°²•«¿r99$Ó¦}La©¬W¼hÝí³”êUÍh{|òý‚ Ô׳Å×CåŽ?¾Q:ådøzVe¨¿3_QQ úTãòeK6N´þo|ðxƒ}·$Ö–-_†Wô8Ö–eâ àƒµúø5‘ÿA›6ý”‡­W`SøvÂÏrÙ#ë¦iiù¬´uõ?ó«D¤S@Õ¥z³ÖÍFŒMöÙÉÿ)þЉ¢Îc½+B9rêê󺑀ڗb…ñ&âuHåSF¥øƒ"ç™Ç"TŠ?‰Í»ÿíÙç{¾Ôt>Ô²haXů±°y×_*ŽÓæÿ{ì±;wþy¨U5šâÍ mK>kMÓø·i{ì±ú)ÚM8mºhábiKmW€™Ã¢9 Sû³/þCü»•&þùKÜ·eŽ˜™±Â‘)À WP»ví;•ÑÇÓ°” >æT[¹ÀO³ÌÅï/ËÌÀˆ/ð±­í¹×wTFÎÅöÐâú¶÷toÎñyÔEižÃ‹~ÿ{%ɾ4“÷nò¿ [sDŠ¿Ôÿ8$|ñ`"Ÿ`µ# ø—·ëŸw¢#xM^΀Ôÿ6õþ·zÕJýö7WÌ‚f×R¯LÄC.xÓ¬©ÙïÙóxÚ¬Dzë­ëÓ¡C…W}ôŧ¾W¯AƒƒØ¥KlàÌ“C|þ:ÂÖhñb[]S |EŠ®½îZYŽYl_Ëé&E¿Í,JÆÕŸa‘µjIüg|… ú•±²r&&ÞÚSL&ðJ'^Á¤G-YL/ Y³`oÕª•À±}~5J5äU´%nÕв¥Ëhì¸ñôùçóÅþ·m Y) qû¬Dûðñé§Ó±Ùñùôkìs$‡¯DÎÿjDXbû\A#yQÕ„ ­]Ù0«vï=w—y–.]ŠU^ãiÁ‚ȉ”ø‰Å¦â=ÚÁ¸ðÉÅ×\s-VÚèf劫1¦rö¥ƒÈììÿÊÊJjÛ®ƒÄûšW™ýÅðõðáÃ]¶¾ÞÕJffñZÙÞÝwßMøú  Z.õG¼;ãC¡Ã°a8Û½quâméÒE"Ë8‰7Ä!/5û±/¹äR´Ñ£´|å Ùüü#ÝŠ}¡ø8“œßírˆÀz*ïÿ|ý™?¸.ã5)w–žk5&çêú?«?ÙW—ºqîí“ü|À±?RÿÏŒ$–‹Òøgà?÷£r÷þŠàüú\‚/âþ¶éŒ?¿ºê×töÙgÉýÈðáÃd¯Jü¾+[.ðnƒñ£‡vm³-0ÆGƒ@›zý7ÇþÕUWÑOñÃ)ßò=,¿©Á{´ò6¿½újüÀ`nF¹ÿ¬¿ îùSÿ¯ñã·éYè§Ü¦Ã° ·)¾Gß™$?ŒK?E;J?ŇƒÒý„¸;­ÿýÏgŸ}FŸabMgÐêÕkD×—_~I3Ã÷¿¤¿™ 6KØßM>WO…3~òás,ßyçÞÎM7ݤtœ.\XèÖ­›§±Í6mÚfÊܨQC|Z|¹—áâ¹qµÐ¯¶lRÐà@/£$]Ýr„L–Å|ˆ/S….Ñ*´lÑ¢€/« Þ쟆ϕgu²ªÂš5k ÷îètÈQàO΋Oq7ˆ –Ŧ¶Oh¦7iÂy•‘Ï#:ûó­}D’o%´­û÷»$*¿Úw,žA¦ ü™uþ|¦ÉsŠM¶}þ–[´lqüáõ6ïsæoÔ°Aa劕Zuè Ƕxzìñǽm+S‹- {›¯]ýOg_ãPsá¼f5êƒOGšl¾> TC\øâ­{÷ /1î  Þ–¯wUaÖ,|®ÞÙ·6gý1Ìm5nü8o#êÀg ûÆàòi߈¹T„œ#š¶±Cdðlˆ"\q9’}õIÿ±ßÔ…Ñ9ãSd‚sÎeZU"Ä#æ"PrŽhÚÆ‘Á³! ŠpÅHöÕ'©ý£`)!爖âÎ(×¹˜–C•Èñˆ¹T„œ#Zò?œ˜ó.Ór¨Ù 1ŠsD[ÿ㋨™ûø¾Åà† °=”.²YÖêD´u±”–†6týK[ ØMÑ>>³–6Õ{yüH_xòû¡¢e M±þ¡*ÆP€ˆi9T‰l˜‹@EÈ9¢}“ñ?`àiÓµÝÿ7hØmúAT &T Â3È´ªD6ˆGÌE "äѾÉúÿ·ïlìÓTû Ãyÿ?òè£êK;o&õ·P(Õþò¾!G˜ssy|2œþª˜ r‹+?^ÚÆ)¿Ž5rÔHú®¼¶¥RóæÎÅWÉÚÑ=÷ÞCã±BDt€T‡ep˜}þÌ<a9«d=?çD6g¿.6Š–CˆZ¶Ÿaï˜nÇuôå—_®tœy/¢§Ÿz’xvK‚Ÿ:u 6 ž+ºù‹h/¿øl»-·¬]GÛ]ªf J"Rg«ZQ5@ýõÇÇÅhذaÔ­»¶‡–—º¢lй+bøˆãoïï|‡~xìϧßüö·ÄŸ‘—²|MÿŸê}}5a‚Fl̬üŒÞ5_Ãÿ¿¿ùfº˯ù›)ÌÝâ…àkŽ/&âo&V|ñ ,ZŽ=„ä@û7lÔžzò ÄÛ¯uSu¦NA¼a³s>8Þø5Hî7VóÿU¿þü:À|1;w¦)S> }÷ÅÍ ãÿÁÿAÚ™6Ö‹Ç5UšÐ¨¾øšþÏ”'Ùš/ù? 5Æ^8Åßz¿™þ&]?õ\iüIã`ìúÇÛúøsÑE¿ ‰'щ'žHMñÑ‘¸>\¿¾}ûbUùêÐ^WB3.>b~Óø»ÑÇß_üâ"š„½AOÄv¼y¸5ª¶¶aà63ŽÚ»¯ØúöLã?\Q3¯c܉'â«×'¡Ÿ6A)CÏã󇯋6m»Ñã/„ʸ‰?HßÀ˜fÞ¶ç/Ã`Ñ…ï>³ŒX}(»æ¸Î¢K¼ƒ€˜£Àâ¢! æù̇±.Ʀgï¼7™Úb˜íwh"øHR™ý9è0yÏëcŽjžÏ|˜¼š3 K•“d¹Þ‡~€Ž¸>Ø{ Õöòž×&!Ô<ŸùXû*Áç ƒå-^„½œ¦Ñš•«içwÂgÎw$ÝÄ9ð2¤bA–QK—,ÁäÁ¼Æ·©%>Ë^«v"ÿ³÷ÞŒ} v§vh Éo¶ý­N\&-!Ñb”'Fø¶f;6ÅþC-Ýa¨ƒùoÕšÕô>Ê× ¾f¨8L›)²ÌcòÁj€"I¯ŽwúçM®Wàüæ;7§°ßûÚózÀDb ^Eœ2õC©VÆVB‘L kà3è ,SœòᇘükНÌí‚IPL4F¼¼'V‹w–%BúóŸéœ³J•3gÒ§3fPû=;ÐößÞܦÍ@Íó™õ­¿J&ï­x@9“ýÈ!jžÏ|˜ÿb?™„¥ÊŸƒ“÷¼0þ! æù̇É'ûÁOYªžŠÏJ‰éö€ñG5Ïg>’ÿÕ)þBœd©y(¤J‰éö€qG5Ïg>Rü©Rü…81ˆSþ:ïôéŸRã¶§–-v¡úòêr{ÐÃ(ö«2YæHñWì's¡¥ÆÒàCóŸçõ€qG øPÍ\údútjŒ×ìÒ²%Ú´>˜ŸA–š¦*%¦{ØÆ!Ô<Ÿù°ò'ûÁOYªžŠÏJ‰é³çΡŸ Ÿn~ºKK|)›Û”ˆKÀ ËÔäöÁOYªôø|hþó¼0þ! æù̇ÉoÊöebÈWÓZA,A-µšžäÇcI_2oH—f“ت5ÙOþßÂâoöœÙ˜ÔÄïÁþE¼/Ös ]ÄŸ=ƒÃ”ÌÒ¥Ù$2b:À°…ù?ÕáTÿÔþéúÇ£*"À–x‡(™7¤K³I±îÔÿÒø›®?Ò¡¬ç„Žhomý-â¦%Ûñ [¤;õ¿ÔÿRÿKý(;FØã¢dÞ.Í&‘Ó†â ó\hs¼ù i˜‘*áK¦›–¬ã ëÖWÉX·\ˆ4 eŽØ¸#à#î¿U%Ë DÆ¥ ®>É~¶aÙOÉÿ[^üE](µâpˆ‡NÝ)¿ÿ¢›O³LF£ê ¨ˆ!¹)¾Öaº,”eQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒHKå"0b¯hº,´dQQ.#öê¦ËÒH‹® 3@Ž”›yãY#æ#Ï+x#‚îAxTÀ86Óe©àS²Ÿu ræ¼,Áá˜üïýE^RœùÒÒΩÿeC9 ž,!õ?ñ‹9'?>Nà¡ÈKгX²4?iü¤ñ7Û%³Î“%¤ñ7¿ñE'ĉ˜€²â.&°Å’¥Ò÷”šE!gÂY‚ÃÑ«SgTK=ÕtYj0–E!gÂYB²/~1çÀô€GŒc3_Zšüï<â/ÈYðd ›mÿÃÄ–Y¥}¿ÊÖ^È‘o8zLDWóEü4 Yüò˜Q“ýäÿn<öý*ôß×€²>çqÌ/¬¿ HýÏ<‘ÆŸAæ“4þ¦ñ7¿<¢ê¡=$ôÆ ¨Ð{œÆ_öPä/®?æ‰tý =È|’®?éú“®?<†ê‘®?ì‡0:p.]Õ%aôÜð÷ú*{_Üaëþ³fy|”g°‡¨g9|SBÀQ’ýäÿè Ö3â~á:ާq~ý§-õ?8"?!Ê$ 8®Òø›Æß4þbPMã¯^YÒõGb!¾púØX¿k/sÇjÒõ'ô2ï™týIןtýÁH‘®?:º¦ëÏÆ¸þD›O»@”¤ÜoÜTÊW<ËË è%ÅŒ¸á‘:iœEGu9I’ýà³à%Åi>ù?•ú%xÌœ RüEžIýOÂA7 =‹‘.'IBÌ/)NóiüIãy@ã"DLgÄ%éúEFºþHhÀ#FÃÈÊH—“$]BÌ/)NóéúãÆ$ê—à±4þ:߈KÒøEF%4à #ada¤ËI’Æ_?1”í>pS<ò:¿e)³ºâeçÒˆj47óë)¡ºþ†`(Àb.ÙWçÚNô~T%ù_<y'ÅŸÆFêðÿÐ6Ĥñ'ç4þ¦ëu týÏúƒ?ް‹ìà¸,¢-¿âïsi%bÒý¯Dºþ¤ëOˆîéú“õGºþØuÖ våX”¿Êpð(SºþŠw¼g,¤Öéú[…/©þdyC˜\¡¹¯œá\DË!rY\ BŒJö“ÿSü¥þÇF<.„‘G¡"Z‘ËŠàJbTÒø“ÆŸ4þð€ :ê„s-‡ÈeEPp%1*?iüIãOxÀˆÇ…0ò(TDË!rY\ BŒJãOÒø³Ž?èø<¸#çc¸ » •ÏŸyvJýê„X-¨n²) ÊÛˆó1¬R·üß-4Á|Úwß}éÔSOuª”ÏŸ7 ý¢âJ ¼eTp²¿ÁÚ¿¦ûÿ¹çþAo¼6’*x‹w DÇÄMšPÿþý]¼Æ‰ÆNÀÄùv%PÖ‡^ã ºðçÿC‹-¡ûÝG‡þýHm–­XN×]{UUUQž=©KçΠçÆùþÏö•çÿÿxö4òQÔ°Á¶tå•W~#ãO‘»Ä-¾æ©ÿ³ƒjHû‡àç0G¹¾ëOjø2mwNñ½ µ±Rü¥þ—Ɵ̬5!Îǰã(²{%áœÆŸ4þä¯QiüMãoÃ0ë¡0 ÊæÓñ¬°¬ÌYA~8…>š2… µ ´÷^i—V»Š ëgß™D3?ý·‚´ß~ûQóæ;‹œ0Å'¶Ç7Œ%tT›•Œí‹ )«œä–“qf·=v§éO£Ÿœq=øàƒjÃSEZOÈ~(••H1‘åÿšý5kªhÑÂÓV[Õ¥m4Èa#×Íê5´páW´UÝºÔ _6”´ºí_“üÚbèŠË/§›nþ=×0´ÈûìÝ‘&LœèqåêÏmºð+ø­^j°mC¯Å,¬­þG}4 é%ÈTÐŽ:Š^|qX¦ÿÍž=‡vÞy'éLwßu7þù®›YI»îº ÕÅ=YM¿þmnþz¦€ÉÿÕ{þ W…4þiL©GòñUî¿kúýÏŠåËݘØRÆD—!. ‘ƾÍF[ìóŽZÑ›ØýŸì1¤»pHéQ|T›gÙ‘4è~:®{7êÖ­;Ý|óÍâs WõLLÈ×­u}ÔèÑ*ë|Àòþ`¡è¢Ì¤,™Ûy1¨VE õ¤R\z½=Y&Ø`ö-LÔ¤–scØ3v m·}cÚigLÎÕ°ú?–¶oÜ+$+fcû¿”ýãzt£n¸žn¸ñ¤7PÛ¶m¥òU(>óÛQ®ÿE›nßx{ø­¹°®Oý·ûövâF¶Ôqߎ%â¿Jý_—³ïFžÞ”üoþ•âÛøÇk[yj¯ÿ˜1ã¨1úOsôŸMÞÿpû¦æuÖ§ÿå:x‰þ·éÄ_ª¿öºÔþ »Á+Ö§S¢ƒ§ø·ëÏFÿV­^M7Þx#uéÒ…¶ÞfÜ‹´¡úõêÑAL¯ŽxÕ•ŠÛm¸™Üi”â,×Ŧ«éºÜ‰¼ˆ©¬¨ñJ­/(M¯i ÿ·ãÞÜyôÐÃSç.‡P³fͨßÅXž­`ê¹ÿ­ïó/ÿxù0Ú´Ë!]¨ÙŽ;Rÿ~ý5Œk`üq‘6füW×þêU«0&ÞD‡ ßl½­Ž‰õêmMx0xåUVë:yÍîÿ\ÌoÚÿuX¡ö«<0¾1ÒÛÿp;Õ®SGtÚ´i4iâ$#kЊ2 Üø(D>_ÀFPúÊdcä-×€þµØg…NÄ$E7g@(=ä"ËjTõß±íZW7+ÜF°¿`þ|©ë¢E‹j\ýÌEÙà“Å\6ßXûCWMñ¿Äv®ýír(ñ’ãoìØ±4+ð*¤/°'ÖÿóçÏc&¬ƒßœn3!œ4_wßs7xðA´Ý·¿M§œt²Y1öf…žÄ«­´M¾ÿsã­§}+Ÿ¤Ýèýëì µ©Å÷mŽîÛëÚþ5ÎÿbüG%ËŪäšÐÿRûcü‰ÆX×>þkts;nãojÿÖþ3?›I½z÷¢7Gò·Nœpæí·ÿE]ìJƒ¦¾}ú Œ@nŽ×ÿÍ0þæã¾`¿ý:QeåLiN;UÎBÞnv\;oôû¯ÍÐÿεÒgJÝÿ[{p—ZWÿ/À=§NûÓÌ™Ù6µ<÷ß\Ó~£ö·”þ?³²’NëÕ‹FŽ­Í$c}z{ÌÛ…1}ûöU;Ço)þ¯¥Iî„k ;„ »H,^¼ˆÞzëMO}öÙg™Ù³K°Æ«T9óvS$W&FÈÁX1ªçRöɘ¤ÊC@%‰ÎwÊ eߪ¿ñíñùç¾¾y`c×þ(šJš—OëÑþàuÆÉï`ŽÖæŽ*¨žßøþ—"¹bDÅ0ö¿°TÙÇ×ÿŸþê­[Þ„æ³Á³Œë¡Å&·E<ÿHm]7 5WhSÿkÕªM+Wr›6Ò6u÷ÿüc2ù*jÞaóD­~jø%ÿüñÂó/ȤPÆ èþû1&Ι#c☷yLÜM<ý‹ÿý­Æ8YSûˆ‡o¾ýëX¨IÜ•,þl ;†ÿžyú¯tèw“à2d(—Ë=ís’^³f 1‚ÆGïLz—>þ µiߎ~päQtÒI'Q¬<ÒCåØþ­·ÝJK—,¥³~z6í„ås•³fуÑ\À–¯XAìõ<áxê„ýŒ¼¬´šËFÉ,ÈƯ \úæ;· ¾}Ï”2ÚsöŒ3è/Cÿ‚½^&Ðlð¶hÑ’º÷èŽ×æºaŸž­"Mð|pëm·eÊ6kÖl™Q;f,ʶœ8à@:þøž²×R,ÌÅÓÆ¿Æ¡þÌȃÂO>‰rß éÓ§ÓVðSÇŽ©ã>ûÐq?>Žêo³µ÷ô믿Nï¿ÿ>-Yº”^yù¯ôz¼ºäƩբ޽O¥][µöó Ö>ãÇGûL‚­O©m»¶t·ÏÉqûhýYðÖ·Ò’eKé§gŸM;¢}ØÇƒîDcÇÁËÑ>îOÇ÷<^|ʶe¡eCýøµ*8çœÞ»7ÞwoeŽòäÓN;†<þ8ê@t9öë¹ñ†Ý çYþ#PÊÿwÜqöÊ‚Îþm·¢Ÿ.]FgŸ}–Ä÷ÓÁƒcEú)ÞÕ=àÀ|p1Ôd^CL‡9æøfæÐC¥#<’:tè .É•côUCÆ£I#1äSjƒå=“NÂ*¨:ujÕe5–Ì?6äqlúýºôŸý:íG§œÜ‹¶Ùzk)ˆ<fì¸Òáðwlþö¿Þ¦w`‹û!ûà,½çýš¨&ó"ÿóþO#F¼BãÇGÙ&ѧŸÎåùGqÙ¸ÿÔÖñ-®ÿ ,]}ê‰'é ×·y Üýzôï£oo¾m‡õŸeˆƒádzi±=â@KÌg.QïÓ{S«V­˜£èˆí—n§-0éÈ·¿J±•Dµë6þ•R’ìËQÿKþ/%¥q)þÂø›ú©ëßæ?þ{Î9´u=¼6Ö¥3µÝ£ï(ûï¿?ýò——ÑÏp¿Â«¹ù^ý ƒòtÒø[3Ç߯wÈì—Ø®];Y‘^°ßÑ€iüÛ´Æ?Þ2`îܰf{´é‡¼Ï/ßõ¡1Ãý¿õε§©ýK·ÿ9ë׫Ow>˜Ú´Á˜è¥câ/ån!ÆÄñx˃Ÿ¶¸û/|­¾ôQUU¸ì²_²» :uâx,´hÑ¢taÎìÙ‚ÇÃ]aÏ=÷xèС^ÏC>ThÛ¦-ð2=!©†µ¸¿Ðµë¼Q(@Wl±/ºðàT5r¤Ànlû 4Hì°fö„çŒ3Î𶨜Yéísß{or†~ÇÿÑíË û\×  `l!”ÎÊÏe=jTN^Ëme±¬xÆ¾× ž`~;§€}h|]­îœ²ý¦M›zè!ÑÅr}úô)YŽ ül_†‡zPìñ9ÿwíÚµ°háBðÇ¥ÃìÊÀ˜´*Œ•m£‰ ÖçL.[°oþ>gÇd0èËfÀ”?ôö˜å1bä\ÉôCœ -gß³ú0düÅWˆ†?Þq‡/Ï爬g …Þ½{ ÛÏÛÎ1xâ‰ÂÓqŸ½µT çX¤M3¾r>q›ß0ÉéËf>ÕTëuçw©=®*`RÏË|ÿûßWص‹éoÑ¢9úÎ{^Ê£K?-×îä+·pá¢ÂY}ûz{ÖÏ­¬O<ñ„©öéCrŒ¶ñ2fÓbáŽQC¢cö¬Ù_ñ[ˆ?ü¢&º¸ù‚9ÿcâ©Ð¹sg¾ö Ol‹í{ì±…¹sæxK£¶A;[ùói×#¸ÿèøfB,ϱ!¼¹øç~"}ûaíÛ,ÓçÌ33úã2Å0ë³80[åâÏÓ-êJÄŸçÉï€|ÜfèL´¿ Á2N‰ƒŒÒd¾(ëµPœ‹–w$åY˜R–ºJ²/ÎY«ûœ_‘”õpŠÿå£c-”õëS?ú×½N<ÂîHñG”ík¡81-+îHʳ0¥,µ$EŸ¹¨pÊɧ¨hYqG@Rž…)e©k¡l¼úòVô²Åw$åY˜R–ºŠ[«¸Ó‹¤¬×ÿìž±×)hÓè(+Ç øgzó‹KËúéL´?ÇŸMœ4eÉœÛÈþ¯£Ï(xÔàƒEÈ3d¶Iûöí±c÷Lyïq~íç_üù8ñ„é¹çŸ8>½0ì9š2uŠ,]=«>öÁ Ÿ*üú?V—ŒÃê’W°±Óm·ßNWþêWîY,²EO?ý4 0P&龃/8…U\´Q#ß ïóg·ùRÆ"ŒÌ³gÏÆû߇ý©bÿM¬€è°gÏõV üüþW´ÅLa¿þSíZu§4lØ0Y€‰&zî9­—¹ÄL=ƒ•!· ¸EôuDÙŽ<êHGމO‚®vä:Î=÷\züñ!´xñBúó}÷ÉjW h…^¶ÏŒEЉ£lû;OÚ«dȆRBúŽÃŠ5^uij#^NÝ>^¿¸øba¨ÇaØ™²ænSjþÒÛÅBgã\µ}õÕ"ºeg‹™Câϕɫ¯¾*+x0™G­°´|Ø /ÐK/ Ç»ë•tîyç¡ßtâZaÓÆq0°DŒõþýÃý¯¼‹÷0àÚvÆ ýÉ'BK—/Åj³ûiÚ'Ód!Ç÷yçžÇ,r<ÿÂ0šêúpoŒ!üÕÃ5kVC+“0†ŒàÅÊ¥«®ºJp>_ãºpÜì¾É+i–ceÓŸàî;^¹Ô_³³gWJ¿åý{øøîa‡Ñ~ô#|Ö¾!âùuâ•‘¼7ÔntñcƆçi*~ÅažÓ°jKËÆãúV7xõŒo·Ñ¯®¼ÒùŸè2¬<›2eªØ¸öêëè°Ã£/¿ø‚Þ›ü.Ýs÷½2¶Ö¯[_è|:®{j¼Ccª‚ÙWâ¾}q?ßþ\®«ï?&]6þCTÿ|ü™ÿE1ع֮IBÎÙÍrP)“µÔ3$ûêT86ù?ŠŽLñ§½.v‰ï@r¿OýÇ?ª ?â‹K½ƒ¢ñ÷wøë¨ÌAÔ¾m{Iå$–1§ IŠ?x©È±Î}+þ\_¨ÀW¤¹_Xñ,õµºþDÞ‰Àštýávãg8ߢ(gêÁâšøô5ûß÷¬ÊÏQí0÷ [‚ÿýŒ•–âéóòË.cðÕ±B¿~ýØ+…ë~÷»B÷=Ƥ†i…B¼bèãiŸî¼ëÎÂ’¥K …UÊ+>xëáÕG¢,î.YèÌóøc Oþ¤Úª »í¾›Ø·C¼ ÂfX6jX˜8i¢U læëíó .¯.€ëo¸ÞÛž8ÑdU—'þ{ì±GóÅòy¯³,&px^å¼j%¬X±¢ðêk¯ Ze¼¤à.½ôR_¾,…ÉYÌ´iî¼í³dI €eÙr´O×>͵}D¹;Åõgø±Ç×Ö>AòÒK/ñeSl(‡ p)&ª =%Îd©pûí·{…ÊbíbJƒ÷J¥<¼ÚmùŠ•²*FꆫäÍ7Ý,zðê_>Á‡…Ô8ðú™¯Ã|^Q<'ôxÅPÖ“ãW ut+†<%"á_J›jýY‹Ê1Á¥L™‡X·þtç,â+†*d•Ü´§)„Ù÷\«?¯‹ð÷ÿGsý4.ÙkˆYã?¿z¬Zµ ª”ã‹/>/ì½÷>žÎe6·N›6­p—ÄèÒØ´ôYì• 2-Z†Ť®×ó£ÿíºÜË}õÕ¿ xmMè™Cà8úèz9³°÷‚—c`ô›oþùÖ[ÜÇ(û4ŒoJ–þS¢l\)—´o qß~íUíÛVÿØ ÆŽ?ŒWé #æ ô›ç y`i,žƒ•¥œïíP–£‘Sò˜÷–Fúò ²”g,¦ä1!ï!,ÍòÊRž±˜’Ç„¼‡€}Öü[í°Â_îD¶TV/™£=FçyCÞCX‹ç`e)ÏXLÉcBÞC¸Û—¹Ã™ça¯3Ðg¦eøuÿÔ^§ ã_†ü…þö·¿ÊÆX]»êJýÕ†yù(Ðn­[á½åŸa_݇e5ömá_ÿG೘<dz¼Ëú ìÍcf€ Ë?ùäÔëÔ^¢QžÏƒzǃ§Rà ¡9sf^#‘7¼fÔ£ˆWôè¡Zßzë_òe¶?+’êׯçí1Ð÷̾>?;“;íQªÚžÄ@§žzšÚVTÄ#oƒz¬jŸa­òD¼@íÛq_±ÿöÛoS¬²Õ;¼g 3×ݪ.V}OäT›Îõ«þ ™¡`ͨÃf[·ÞMÛÇíW´+1¦~„öyeö{i'B3ñ¥n= ¥×,Ô=ôr1áyˆê¤œ³ö%_¢þÂîâ7Ö{ò©'ÑžÒ‚ÏÐ…?¿PµANµ•«ÿ¶o+áz`O©zu·"L0xÝݺýXà]wÝÅU™ Š?)¯²}û¬A싾¨¬æ¢ýϱªqdÖÉ> ‰ XTY‚O+ÐùœO­Ý&”<Ï[«V-ºä’þŽ¥@&Mü,q)`^iwÚ©<6˜Â¨NÀòJ6;~ÿûßËW Eì¼Aöõ×_g¢¿ýõo€UOë֭肟]€½œxß‚lŒ)cVvè€1XþÚÊŠ+aUÍ+ÁïT¯n=Áó©Q£oÑ^{í%yÕ®õ_´h!½øâ b“vÒ'*Ü¢4­Oº`…ÓAøº[T=Œo­ég?ûÿhk߸ÿ`…$÷ŸöØG-Û ÙÀ¾ûî+0÷íK.¹Dö&Z½z•Ô£nݺô½ÃÑ·ùˆÚߌfV|¡â{œµ.*f¼œ*Ýð¢ÔgâúgxM¨„}ÑmÆ’ý䉦Ö%´sY.õ?Jü Hê%pùÊ„ÒøãÜÆ 1ƒêª¯ßÿþïÿþ½öÚ¹ Üüû›ÑJМüÿ_óÿ†¹þÚýAè}1F#*õ¿Meüá^.m ßTÿß0ñé>µÄœ­éñ÷·ü?yë€ËzÓÍ7éÊ,.õæÿ:Ra¹XãÅ©>%ñF«ûcCYÞ­{’,=%:û§ç`ƒU¦;§ñ¼¦šÎ_0Ÿ°×^ÉyœÆàaHÈâÜÀÇ›;5á×'"ûÌ׳ç t^S ºð"iâ ꥱ@Ÿa#éÃñŠ¿>Æd~pk‡ L´éC8ÊÈá:a¿)à»÷쩎w5xéeAvlCÊ pš9³RxqŠRÂÓÇ£l'8,WH"žPÀ¼}¦•]@.Ä¢BõüöêßÒè·F㡲R^¥0àV¼âÓ€xCÛ(ow¼fÄÓvhýY£+f¼°¾ÅÈEõíÏ[°€yä!zì‘ÇhÌX´VÜPubojÒ„_oQÿ›bÞ`úxñ³kö…eõ°úK9]1… ±RõW)“Ç–ÙØ¡_6Á,©Ó°–2Þä!“k¦ð_AÖeâYˆêì¨Pј¯‡ËÕŸ­ðQÒÿk±ï¥`]ê¯ïÛ©”ò‹QEhõ¶”I\æ(¨¦ìžå?ö‘x—9ùj׎—P2\ iâÊëQÀV /ô Nä¾PÂ>c¡›_Ãb-;còm×]w„Cì³ö L¼t:ëûè£@TûœòçX~øazì±ÇðY]ž¨-ÛÂ…_!FwÀk”ï@¤‚š5iB{챇0fÚß©µ>Çþä ­ù`ÒñÇkŸŽí QNÿ¢Âé™?Êö Êöx(›ëÌÂú¿úê+ %ÎÑo¸oãÕEWn¹eþV5ÒQ˜\?þ„x-´úö·Áë ø” `¸æé°a/ÒxŒðñiŸ½÷¡®Gu• ›±Øƒc0ö‘ fõÒ6Yû{ÈêGtZÀVtØ0ˆW¬\Qµ«LÄ;‚\ΛnºIeÐ \®ysx·wÖK´`Þ\I6sb-õêÕÍà$ú);#i©ì¦ßêo©­Lá^ÈöýØg˜<^m¢ ã&`Æ_èq|YéÕW_“NÏ`Ï%Þw‰$y þª›ª…åå@Õ¸vÿÉþ‹/¡}Ž9Æ×oìÄû8µnÝšÆG<0ØÓT±+/›àƒ`_H q=óöeÒi‘²¶ú ™+ DÚó›Žkòf×Òµûú…ïïr/µˤ+gûü§‡ØØ",¸\éÀÎ7,g_ŒØ„V~ÑÉ&…ÈÿiÖó ÆNŠ5y³ËiTtÀ¼ÓTt ƒŠ€WýZGg”0GJÀ—‘Öí¾ÛîÒžÛç8`ÿóž_¬°ñvÛ ¯œ˜ær¾ÕÈ£€=k¹A|ñÅatÌ1?4vù2ïçÕºbÔ!LæÞZ¡_r@‘wnѼ´ÿAS› 0ŒÍªE?—»qcW>ÀÊèáQYQ¶Q¶c„mï½ú&xZµÚ {t¡? õˆão¯{ÒÄñh(úö!Cè5ì…„ ÞéégзñÇ«}ßcÁ¾·í-G¨Qyÿ ç:Ž?Ê‹sÎ>ã‹L ³bKÅŸµ¿°%ûë4þ«¯ÌÙ¡ýŸü/ÞÉRü±CRÿKã÷„R×_é0¹ñ÷Ùçž¥îØŸ’®]»b¿ÈÛ–“v< è4þ¨†?üƒ# ÇûòßðõØ€}€B÷ÍîA{~¯„ÅšüºÆ‹y}L^T{³´ç—œkò›«}®å–\ní ]ÿç0)Ä{öòqd×#°é@í´¡í{ý>ì= Eðt+P„Ýñ/Ë#b£òŒ,Ë>pq‹TI³¨×ɧHqxbààÎx½BQW|¾üòK:ÖM uÆç1ù ~eÀ€ô?^Hâ³î|°O6ñ¡g ë„,¼*<÷~wø²Å“&ãÇ¡‹ð)qÖróÍ7O€ˆœ3вծ&J}ü1­À§µùóÚþoÅ lz¼Ëè’KéyÍ¢<ØGöÁÊoƒ¯¥J‡´c=8™¾¼<ç·Ç†³üj¿v7Yü ñS{Ÿ.¬‹±‚çä“O–ÏÄ›,§¬¾Voé´Ñr|R>Yû_b3k~¨å2téÌí3•&Mœ(ís!Ú瀃tåƒVY5cåEøå îŒü§úת­,¾tÉÒµÖß·©soNü>¥n¥ˆÛß™7÷ZøøTéÙú³qŽ/.‹/·W ’e¸k ƒÿÂË 8xµH|ˆˆ°j-×þUÂèkâU8´ä½}®ÀŠ)µ_ÏÃ/Qþõ‰?³ç .ÅljSþ3»–²³ÿ > / ¶mÏ›·‹4' ¼òFzœ¢¸þ­[·‚dA6F_ ÎYÓDŸ|ò‰¤|úÎÞ{‹Î/¿ü1z,0جcȇS?Â&ÜC°ÒæÂŸÿŸÔ±‡í³-6»û»#-ÐGàµ~À:Åš+—ØìïÒ²¥²à̼óaõµ²òj©yÃh™°‚ÎƒÝø6i‚öŸÿýߟÓA(›3‡öcr±úöç_@¯Œ¾=‡þ2t(a/'±Ë›_ŸÜ몜=KeLˆ©8¸—.qq =y{ˆýÛ—à$i yFý§ú3ý/2ÐÊŸì'ÿ§ø“^éÚ_]¯]ŸñŸUEãhÀÉi*ŸRÿó®ØìÇ¿üýòÄ1À÷Ï<ó ¶|à×µõHýýÀÞ ½Bû‹ë55¸ÿÕªÐ_5Ãþ ®.®*Rœ\M˜(G¨)h’aŽ€U~'Uƒëo÷=–jíPîM¹þ(;?Uè j”ü¯Í-R­Ý-]×öö:&òÃÄÁù‡ZÛ&‚ul‰ñ¯ïÍx'sò!Ñ(áÈ#ú UAmÛµÃlt>Ƥ ÿFSåWW€ßÁo½õ–xâu‰=öh}êZÖÍ}¢ŸM0":ÂkPL4BT& äGÒW‚_kD#^Aûî×I¾ Õ+oø8éÄ“°b!¬:ôÐCPj>*è ºõêaePô‡ýfêK¾>Õv“"\+&)cß•Q_^1XZ‰¢'Ó1 8…JÃ";6kŠ/8D>ò0Ý}×]Þ%“'O~#X{ó°RÂ)æ¯m…ƒ‘Áþ?ÿùòÀáÿÌŠÚk7Ê_/%¬ínõÏ>n3OlßN…£s¾š´³æaóÍ7G—­?—“K+…C‘Ï:ë,Ú«RÚà¡ÿšß^#”oÆÿ{2‰b¶Š yûò³ ò(ãšìÐÔW‘ëÀ‡Õÿ“éÓéõW_/Ò'L"­þçW…X×;xíi!ö·)>"ûi¿éœNÞý¦°¯sýÙ¤˜e‹j_ꆓ¥ùú3ŸÐp¾ïO9¶ÿ½¸?)Eí+Ÿõ?Ñ®&À—==ô0à}ÃXjR4>ééûvÜ[ho½õOàXa{ ö7)¶M«U‡³ÉI›ÝÛˆÿy‚ex´¯Ëa;8]¹Ä¶#û{~g/Y©Ãàýþ±Iˆ³ÓÍÀçxíÒì¿ù/-ó ñmeuüŸcâȋʘȜ±/ºqâ´Y³ft&yyäº }›Ž¿÷¥oƒÃ+)H0Q£ÞÒ8¼ã‘Äó3%>Øšbã’Æ,íÛW~=¯süy,—ì³;ă8©'½ƒ" ù_}ƒ³çÈ'ðTŠ¿=výWyEá$ ó§þÇ®Ïá<(ŠNQ¬yF8pnÊñwÿý÷Qw|-–=pèw¥ç^xÞ_ÿÔ Q]7Ãúoîío±é›N•kú?»bSl}&F¿t7ëÖÆ\Ÿ4þ³\tgƒ^ r^{üó—›»a‹>¾{HzáyY—69Í:mÎþÏM Eÿð'û:R‹/ªp˜ô4Æ‚ËiâO8Û¯VÑÊ•+‘­ éŸ~JçãsÒ¿þõ¯•"n‚ÛØ-aCyÛÌèM‚ÛìŸÐ³íÀþ¢gküú/¤ ̉}úœAUkt¾õ.]è0|ŽšË{í5×Òõ×ýN__n¬™·Ÿ‰¿–yô‘ÒöaDŸrûœw¶«‹/ª˜5Qÿ›ýÈ1l6sìÙA7þåÂZÙXnVñkA ¤l,b~"¬úH^9´2\}í5´b%>aï5¯»ý WµYý}ü9¥L53dyÙœÙñýñŽ;èÜTákWô,Aì|ðÁ4s>‡žQáp¢?@IDATà êëÕÆí}ÃŒ·ßþ©?Ç(¾Ü…O±¯ñ|" [í÷ìàÊSAwq›bÕaÚ”?Ùþ96åf“R,W6‘å“ÏÃOúâV@uÒ‰°:îÙLJ7r^ŽUsø: tK)9ôP½2jYµÈ(kßIpÿk$1Gtî9çÈÀËue;wßy—¬ìã²þà?À&ÏE¯þ³ƒ÷ðÁ—Ì$Ë1zÁùçã5Ñ_‡‹"×Ô3`ǪÇм'¿c=sægÔ·ÏYô÷¿ÿÝÓY—»Á6 °É{Q0oÞ<ìSvñ¤*oÆŽ/´ÐäÞ§3ÏìƒW'w׉%È4ÂøfõãÛ Œoœÿô³Oé7œû¶fi×V­f¥Â€_1•¸{îºÛ­*Ð2¬ ‹W?eâÊÙRÆüÙú•jdªÙÏr°‘ý˜'˜ù:ý/ÙOþ· ÌF—оÿg=d¹Ôÿâû„Í5E©k>º¶¬ñ•|_{öå£#~€¹×ÞÊ3éì³÷λøãôwiýÚ›ú_ï|o3cÆgØ“ô3ú¬ò3Ü'­–öå·6>cœüÍÀ‡;Ö¸ºl¡ñÁAk^óë?ƒÛ íÊ ¯Â‡›øfìËk›J[£ÍW­Â}§ë£rO&'iþ§M§þ¾…6ðøÃ \®ÃÀ9x&ákGGl§r'î­?ÃG±d<Ę8IÆÅwå9œܼ™_ñð$‡}­Ìòœ^&Ÿ«§Â]D.âp ¾ªÃhhËŠÂÐ!CEtùò…¦M›ðÓ¨àáÐBÓfM šÊPaÞ¼ù"Ã'VÉz˜ÿ’þý=ž)j®Øþn»µ]gœqfÄÏ`U?=mú°×§c³Ú~5;Fo‹O6iʈÙÞœŒÚçrñ_ÿKP6_xý ”Ã3Õh–ªÄc?îËëúD¡E‹\Ä=ží÷>½wPáué'ÆÜþoÑ¢¥”™ÛbÀ€bh9>IßLÚ#jŸ¦Í”þçÏy›}<,Kyµ¤¶„>ègí“­ƒÖ„ÏYüÌÌuì>KÎíߢEs_V.[V¢P˜8q¢+“–“?1ÏŸõ–Ã3{@ñþ\Ï U…FôsçW\ñ+‘À&Û¾þX%8L`yû¬­ uè´_'×X1't‹lÈ-yŽ¥|ýE!N3f̈d‚ÿ9G¿ù¦±ù(EíÏŸhg~¶?`À-ž7 Téçê]¹/„C}3kV¥èѸֺX°ögÚ¸qc]ÛŸš}|9/¨Í@—ѓߟŒ¸²“IwñƒÙà }«®¼¼â)q¨eS?ɘÂ}±Ó´I3©?Óçó'îÝñóŸÿ<Ô |¦?3aüÑCËX…ïQ^{íu™òÄõ7ÿ<ôЃ"† ÛŒ}nÿ¸nÞÆ7lÀÊ‚2[ü‡>¡þïÝûtW¶8©*¬^Såâ@ëÏõjéú6ë²¾¤²þx†Œf©R³¹X¢üøtÅü —×hYžl.Ö—ì«o"yбÒÿSüåâ!ÎZÌXª´l.˯´ˆÃƒˆ—Ã3›Ñ,UÑlNqFQZÄáAÄ€Ëá™Íh–ªh6§8£(-âð bÀåðÌf4KU4›SœQ”qxбà,þŽ?ÞQtý±ëZ>åû®ì‘Æ_õfäSz 벜ÿ³D“±T©Ù\,QÞÿÜvvÿ©íXúžçÑGš%K«o?gÁHVwÀ3d4K•šÍÅåëtÅü —×hYžl.Ö÷ßµ/mÊ÷¹™?»?÷=úØZêY¾65½þ±çÞ0þ¿ãŽ?Dc¢ùWÓüýÿ„‰“²¾öîõ@®ØåðÌf4KU4›‹Õm˜ú‡r”¶u>ƒxÔ3Nš€?[ÍGÝ­êàì9<(OiÂ_Àg¨e;[Ù´yÔ¨Ñth^e ÊæÏGmÛ´¡{î½+D&bNuÕ©Ã{gû n‹<¹OZ[Aø¥5>ÊÛ¯‹WÀXI0ïXýÒ­Ûq"yÅå—›jР=ùäÓtÕUWÑn»·:}>V 0qëåW^‘zºØ²®´AÙjî(\ÿpdíG… Jæ gu÷JáTlæ½`Á<ºæêk“TBž‰ÙËIx툅ùëFüºË ûyÑØ>Þý…a/øeq¬–Qà£Qƒ²ŸÃuñ¸‘#Ñ>x­Nç °)76ânÛ¶-ÝsÏݲºHŠŠªÕ©£í®µ¬P€XGâKµnõ罤†½€²uïŽàIí/_}C?䯮­Àê(¯IŒó~3{ѱÇËEÆQ ßþöj”½®fóºÚç;½lÛÚ n=›Úòu=µS§6ãpÈá@ÎâÕÂ!C‡Ð~x]‘õYkrÈ¡ˆé±tîy˜yö èì) üo‰=mž}öy|E«™¢ öÁ¬Ê™žÕ»Å1ãù3謬d–c…O8¬DŒá5BÜwµ²Õ•Çê.rÎ>ïßӵ랟Y;w9˜>øà©¯ºÛ94./[«]ÇùJ”eí;ÓBéо=3†ŽýÑþgŸ}öYØghµŠ¾XƯxŽ=2Z©T@ŒÎ×½û?aœØgåÒÅJn½ý6ºþúë}œrý¹ß^‰/ ðærüËV[Õn.@a•ÜUXe8„:uÒOÊ‹«\uy%ÓD|Íð'?ÁŠ$õêjÙáþƒƒ§³µÿ´¡»ïEÙÆó—™ÀýG÷ê…½ƒà kü«-·½Å¿}á“^èÛ7Ó A÷[“©QTAµÑ·5°äU ‡/1º¾Ý+˜¸ÿ0AHN&ʨ.G íoí©Ë9Ó¶öñWÌÉ)Ù7Il…Lò?•óGŠ?ëi–ªc,g~òÃ8¥4ÏaCX#r!䀙ì ç-1þ>ǵGÀ;ÄŒ÷¿Ç±`A ùí! —âO{YÔ׸±ûSî?|p3Âi>mÃ*¬€Þ’ã_*ïÂÙǾ;•à[×ñy‡ï¿Úþh?)‚/7¨/” ²ª}cÇߦlÁ‚Ï1š_³iÞÿ2(F âÀM¹þþ&ÂÅ–¯sžùÑÆ‚Ï“ÁÎÕæ|€”/:³dñCók2¼©ëî{ì=Zv"à²$BÆã…ÞòÁj€ŒæÓb%BÊ£-¯i|ÖͲ?üðCùäõ®»¶r“!q­7¬}_—\ýyšO¦MÇ’Á´sóØohG|I=çl«XP"Ðb¼z6uÊyMŒ÷2iÞ¢m…Iž¸æ /víÃû í°Cc`‚þPëåÌè D<9_,Ësºlébúðéxg•ì¡„UQÎçÆ¥jØêšÕUˆŸw¨ÙŽ;×M¦x+²"ž’G[^ÓøìEŠ”…Zȸ+++e™.iª!¾ª•?Ìžá-o–yæihÛóç!îšP«V­0a¡“pA&ÛÿxÓnžÀ\‰%¤Íš6#ñ[4Iirœš=ÃYÞìs;ÎxXŠ'_¦M›&0Ûm¿¸¸q‹ëïeŠ•)¶<§UkVÑ{“ßG,סvmÛ Þ6¹d\ªÝ¬.B?x÷½ÉØg¨‹Qo½¸¢Ž´jõjÙ«‡‡¶ïìõª… ­I¨yÖ *«Òˆ÷ðâÉÏöí: |µA±›ƒÈ6@Þ þÝÉïÉ>jÚ”ž×myM ´¯}üñ'Xâ½ûo5—ý†´o‡ÎdõÏûŸ_'œò¡öíÍ[Zÿ e3{†±¼Ù^0NKqåìÇ9OÊj š“ýàù¼‚—’ÿÙ)þlÔ žðÌ€â JmyMã³)âÔ¸¬(æ8+âÉy´å5Ï^€q).X PÌ-pVÄ“óhËkŸ½ãR\° ˜[ଈ'çÑ–×4>{Æ¥¸`5@1·ÀYOΣ-¯i|ö"ŒKqÁj€bn³"žœG[^ÓøìE—â‚ÕÅÜgE<9¶¼¦ñÙ‹0.Å«йΊxrmyMã³`\Š Vs œñä<ÚòšÆg/À¸¬(æ8+âÉy´å5Ï^€q).X PÌ-pVÄ“óhËkŸ½ãR\° ˜[ଈ'çÑ–×4>{Æ¥¸`5@1·ÀYOΣ-¯i|ö"ŒKqÁj€bn³"žœG[^ÓøìE—â‚ÕÅÜgE<9¶¼¦ñÙ‹0.Å«йΊxrmyMã³`\Š Vs œñä<ÚòšÆg/À¸'CòžI2EˆøbUG4Q(yQÀw¾Ñ¯Êà"²“}¸HÜg7ƒpLäc£sDKþ‡3ÄâÀ\¬1-Å_êÅcNŒIãOÒøËÃhºþø)àè«`tŽhéú gˆ?ÒõWý¹²¤ûtÿ•»'ãCátÿ‘î?ÒýGºÿ¨I÷_ÑŠ¡Ü€•¹ùaZŒpUˆQ^<ªž§{À©‰ò^.±Äˆd_nác—x÷%ÿûÇï¤øWDþðq“ŠXbDê©ÿá: >iüIã›^ññá4þŠ+"ø~“ŠXbDÓø›ÆßtýÁ˜ùØì‘®¿éú›®¿Ò-ü%ÓéþC\ù#;xøœû–ç™ÙÎÜ@™$Ç' ²¾S² ‡O_]aÿ€Pb4c ÜøcHd9¼šdþ¾Kþwƒ"GIŠ?é+Ü™¬/¥þ'áü'¡ 6?>fHão-éúo°Òõל ®H×ßtýõ#eºÿpCfºÿH÷_ éþ“ýàG×?8I÷ŸÁ €à"ë3 d}&9>¥ûs‚¸â›ºÿ‰!mm‰Øý²i-·–’r q ¨ zŠ(õ9÷ŒÎÅöJ|Ë'ûìxŽÌkLIáœ_U ÈzùŸÞ£$ÿ«œ_3Ið¡::ù?ÅŸ  Ôÿعq%?ì”0vèøRì§4þ²ŸÒõGã#sáÑ.Å{Š4þ¦ñWÁß*§ñ7]xd°°ØHןtýµgG¹~’î?¸·¤ûu½ÿÀ½‡›}°]Ã#Ç+³4GÎð¬Kä‹”DˆdŽöÝÚ»ÙMw ƒ#‚3!I¿+š($ûGs¨/%ÿ³®#†±&1_Rÿ3“¤ñ'¿æ ~œu¢tÿQÓ˜=¢¡î?Ñ"êKéþ“së`ó%ÝÌ$éþ“î?æ ~œu¢tÿQÓ˜=¢¡î?Ñ"êKéþ“së`ó%ÝÌ$_uÿÑCL³¨NÐd(GàÀè••&Ëb''r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤äQ.å@G^h²,vRò(—r #¯ 4Y;)y”K9БWš,‹”<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤äQ.å@G^h²,vRò(—r #¯ 4Y;)y”K9БWš,‹”<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤äQ.å@G^h²,vRò(—r #¯ 4Y;)y”K9БWš,‹”<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤äQ.å@G^h²,vRò(—r #¯ 4Y;)y”K9БWš,‹”<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤èŠ¡l(dÍ<ò¬3s(¦¼e"?‚ˆ¨ ÈL–Å¢€/IÞ$H™ñòo™ÉþÑN!g%Å™--Nþ,ú_Þ%2çÉg¤þ'v1ã¤ñ'úIDÈYIqæK§ñ'?b4þæ»RÖyòiüM㯿éd~&C™ qØ|Ébé{š›G!eÌùŒ€·L¬2LœåZsM–ÅÆeyRÆœÏHúÅ.f0‚ˆ¨ ÈÌ–'û $ÿË»Ræ<ùŒ5¶ÿabË«¬Ò±_åk/ÙÎ6ì=Æ¢«ù} ÈœÌ/µÜ¤?Ù?ù_c¿ÊzGìk@YŸ‹8¦RGARÿ3K¤ñ'ó ³IÓø›Æ_Q5hÉzcTÖ{28¿l!g¯î?f‰tÿÉzÙ$ÝÒý'Ýx Õî?l‡ltàTºÿªI²ÑóÛþp‡O³Z4ˆDæVQDŒ ¹+{‰’ðCUæ!G¢H•ô'ûÃÌ/ÙÛ¼opzåBäü/õ¿4þXÿ =C¢ØKéñLëóŒwÅãÈ õ¿ÔÿRÿ³¾ãûYì%Y‹¨ãJÅ‘@ê©ÿ¥þgÝ'ô ‰b/IýÏî÷Ñ$0íT¹¤ñ'?iü±îz†D±—¬²ñÇ>šH¢š~ÛàJ(]é,/W&%ÆŒ¸án‚ÆR­˜þk®¹†Î?ÿ||ï½á´ø-ë·j©:Sꊒô‹1œe¾ÑöOöW $ÿc;8/3PâooüIþ—ü/óÿˆ¨ènÎÊJœÆ?3ÛKmL²ò?ë@‡Þ(ÑŠ½»ú—ó2%NýÏÌÁöJý­‹¤ñ'?ì 1¬^ãO\1ÄßTf~&NœH'M‚h‡v ¦M›fÙ cÂúhÊGòJ¶ãŽ;RãÆC>,ËâšÐY~9ýTåÊDXÄô­¶Ý–Þ2}ôÑtÇwXb“kqȲ¤Å‘Ž9JëÿUúsörÚW…þåË—Óܹs©~ýúÔ°áz(Í·oÿ­?—m—­^}Z¯QÃÌ•¶iº 5h°V²?,°&øßÔ©SiuÖ¡M6Ù8Œ«ñF#mo75¢þZI¾Z½,9–´8Ò­íŸêo°¶ø«Ûᢅ4}ÚtjÖ¼™¼§Êù:_1ÿ°¢ï¿Vªèn±XXíú_),Jî+ÉÅåI‘ƒÒþûíGì¿?]uõUF*õdzžÙyûãoÄË/ ^/xØW!—Kê»HŒ®¼~f.¸¥I"#Hô庀×ÈäE@®éúGŽEo´mµÕV¨íêUÿQ£FÓ†(Û–·ŒÍÃÀêÐþŸÎù”xåYÏ#zÒÖ[oM:´§SO;Æ/eMþ—õº Ê÷WmK1W¸¬^þËgÃAˆWÿÓ)oO«´Éÿ2¯Ë ¼½bûFLþ—¿á­ù÷?ï«Ûý/úg´‡¦ñWìð]Þ–~ù%]yÕÔ¥KZw½u¨UëÖ´ÖZkS§Žè¹çž+ûü/~›ž¿ÕgqõcÌêòþ±xñbúÃþ@›m¾95ÝfÚtÓM©%~0ï{M_”Øœ4þKÛ™9¾ãÏ'³fÑ]wßE;w¦-жgŸ}öjéÖ9|ßø¾Ü-^BW^q%í¼óÎÔp݆Ôcbƒú hߟýŒ.¿â Z´xÑvþ¡ž;ÂÐ熼pSའwßuÝpý T¯^=ñ…É“'Ó¸qúò̈ªè `)&xdAfØHj}R˜Y.&ž—ÏÞ …[gœµãÛh–Ãr Õ"M.YÎw§_zjP¼ªõÏ™=v¨¢yóæ‰=¾ û¯hýçÌ™ÅECÙhƒ}Cí¿¢úYwÖ>(BÐ?jäH:è ƒhÚ´iB°nªãÇO  ãß [o¹…~ø!êÑã -3”ûß×Õ/eÊ º¼–Úý?ésIûdfüaöÿTÿÐMC§XÆ¿šÆŸÔÿ³þ«­§}×ûrm÷ÿ4þeöó6ã{Hò±îõ°Œþ·"ÞÿÅf™á¿fúßôÓèˆ#¤Ã_fÏP;p½¾>òuêÞ½; ÄÀ½Ž=Èôü‘¹Äêýü5÷‹¹øa~?z饗¤Mµå 4»(zŸÓ›ÞzûMúç?n£ª:¾M¹ÝÑðßÀûW¥»ÛKÄÆd¥ãïìÙshçv¦©Ó¦š@¿¦OŸÆÃX>¨*Áey_Oÿ¥ÿ¿ýî;ÔãÀ²+*M‡0ô™géÙgž¡gŸ}†{ìqZwÝuýa½ÿÉCÙ¯Òán!ÎÍ&ª‘_ê_~啘óØc‰ÃF‡ óTÅãÊ£y0²'{Q³~0 ›Ê1&q¥W=†T:N)W„‘/!|SúE‰“+Š¢f(ûîôúé§Ð—•%–‚U\ÿ9³Q6)Z(ßJØÿÝ·ß¡·ßyKl)Õd__g…cÍ‘¸†ú¿öÚëÔ±cGš†÷7¿ù MÅ–ÈO>™E_~¹”nùÇÍÂwÌ1½há|žÌbÍúHj€4°_C¿—Šý?éçVp¦Ôvb„˜¦–öwLîWè,!i¯rù*ɛڟ ‘³ÒÑò G“:º•èÿÉþfã4þ¤þWÔÛ¤«¹~%%õ?î1Ñ iü¡'“B#¨¶ñ÷8Ï9ŸPõò|ýujѲ›‹þpÆïiÙ2þ¥Õ÷2OÜ‹/iüñ–a›­êûÿ-ÿ¸•†‡I¡[oý-Y²ˆæã‡áßÿá÷\<Ð 6,4©4¤àyRȇôüi¶‰ƒE0Ïwïÿu0‰·äËÅÔhýFè¯ë‡qŒ‹“úß7Ùÿ¶n² ­“lç[o½•f`Ù—_.£W^y™z| Ü@žþE™$Òôò¿,ÿ±™fu>¤¬Ÿà®` w™ÿë_ôÓÝw—Nsß}C$¶y4m´03Ÿ+óÜ ÏјÑchü„ñ4åÃ)²TkŸ}ö¡C=TVÙðdúûõ»Ž,\D'wm±Å4cæ 8p Â6©%XöµË.;S¬ðàóŒ8h7fnm4ƈLèŸ1¼w D]ªh«Æ[á×^ÂÃ.ëGM¡î€ÆŽG3¦O§­›6Á–¹15«¢D˜¬¢ë¤l èøãײMŸIo@£FŽFÙÓN(¯>Ù‰ËÆ 8dt‰rÙZfX²t=ôÀò+À‡S>;uøIê°CÚo¿ÿ¡µ±‡ØÂK/½Ho¿õ-Z¸žúlT{ÅåW‚mW­Ãw/ü?ªçQÔ¬Y3aåâqû¼€%ģnj¥ñãÆÑ”©RëVmhï}ö¥Ã9DW†q±ø/Täº~ýh!tpüq´ù[ÒLnŸƒh4Úg!–Ýí²Ë.t0·ÏOv¤_DÙÞ}›.Xˆ›ÓP"á ,ÑSó˜çT¡l=±·³)ò¹цÁ~=êI÷òÁâ(Ã}. +®¼"HÑHŠʦ,Yû …äêÀx¦=ÿ‚>Âyî¹}èª+¯Ä¯*ÂAuqÒÉ'žBï¼õ.]ÛïZòÀýÔ«W/Øx]wýuT]]MçžsMùh*=ôðƒ¸1 uÑ&»têHÇâ·M7Ù¤¤—І¡/ŒA_Àµ§p_hEûÂÖ‡zÕ«[7”*«?¶5ö½š¿Íœ1“à¡NûÂbÚy—ŽtÐÁ=ÄÖ\r®ÓòåÕô<ÚtÌh´éøqôáÔ)Ô¦UkÚgß}èÐC´Ïí7Þ(ç>tÒÉôÈÃÓ£ý›¶o÷c:ëìÞT½lÝvÛmô"ük·Ývƒ­Î—%–fN–ÁáëØ?ãg9,‰kÕŸó-J¼&ɉÙVŠ•m¯qÒŸìŸü/õ¿4þøûoqwXî?'žt"žצ]wÝ•Zµj%wZ¾ïíŒçµóðüsê©§ÐÜùóhôèQÔ±S§øü•Úõmÿ%K–â„¿¡-«èì³Î¢“N>‰›”êą̃~}¯£Q¯¢¯Œ ¿ýíÿÑ>ûìm}V²''ŽÓóÏêòü³ÑÆ›Ð'3±Ã‚š¥M›64ñ½‰ÒF2iŽe+®éù—M²âïëáÞ'ŸzRÖ«lµ%Ž8‘—Î*ÚµsØ=üÐbÕð´ÿ°pé%„‹¶‡^×8ûWcº82LŸ>çq} ;í´“ÄMš4),ñÇ3gJºQ£F…¶mÛ RÒ˜[C¦Q!;¯)Ða•Üò8ÅpHç#%¹5d²“þ`#±‰Áy«¨¹,S ‡t>RÒ˜[C¦Q!;¯)Ða•Üò8ÅpHç#%¹5d²“þ`#±‰Áy«¨¹,S ‡t>RÒ˜[C¦Q!;¯)Ða•Üò8ÅpHç#%¹5d²WWýïMšïÿü<ë+eçzåëR’ËMçib¦È^]ë_\«‘sóu+¦×tžÆªcdÓõçw${ÎÀYqƒR~”çL<¿Íýï߸~«ÛªªÿE?~@–v>ü°Ã¥ÊÁÓ¬úÁ;¿{ÿ³¬‰íÿá‡ƾկߵZÕL¼&ÖŸ+\OßÕ͑[fˆWY¤Ûl·N잊/L£±£Gc•ÍX‚•%?ñ¸À2o&¢'žx‚ÞÅÍ0)#«A:`M5V¨ 0«FÓ°aÏË œ?ýéOaìRý,ˆ_OÆ †¾}¯EÑ:Èh>%{øˆ´çO*úôb¯ðÌ™3i¯½ö’½ƒ¬ÿнÝöí” L÷ ¾—Nÿ.µlÕ¦µÌ¶×­[îÇ*§ŸzŠÆŒ#‡j?þ8×K+$fa…H>òÐC(ìFÔ¾}{Ú{ož',ÕN{칇ÀÅ]´£²äµI’g8U:_Ïïs>½÷î{Â~É%—ÐîXõùÿKo¼ñýóÖ[°íi:­³–~E‚‰öÇþâp¨3‡aC‡Æ“Ï:ó,+º¨á˜´:V÷ÄãOÑÄwµ}zö<+°v¢ehŸXz:f ·Ï0ºîºëˆÛ‡ƒ”‹Œ?ø VÊpûà°æ½»ï-z^z 6ØcááCÉùÀi®æ3(Û¬”a9g£l|ÇÒZ&-È×ìD‡)vÑ¢ÅÂ[P¥´>–A׫WWpÌeåç*k..¶ÿ{J{¯‹_Ò8xýÿ…ÍgÍš#º>ÀJ7ÎãòZýû^{­ø6¯jÞ¼9=ù䓲'•·¥tÒIXŽø sHx’ûfüÙ{beÔNXMµ +{ ì_èFc%‘ÚúÜ\ý¹Nì?ò/$x”¥=lݶæ0þ¶'lmõg=üË‚è9²'í¸Ó޲*l úÜ(Ñóõã6ý#·)¸ ‹Õa’SúNÉË’ox»à¹÷aµÖÜùóé¶þý‰WzqP}ºtÀg²bû‹¡Íû¾þ̬½YŠUh:`‹3“~±@²?{Rpv&†%™üÏßRÿc×ðƒˆÂiüÉÌâ­z’ö§,ÁP.¤ñ‡‡›`¹Õlüá•Åð\·]›íúNþ϶Yû¿œ‹É…Cí°C{†èµ×^Ê÷ºr˜.¯ã‚s NÃÊþ¶?â¶EXÍü/ÝÄ»ÔǼ£Ic¡õÂVýÝ6ß;™D{¨^s]Wøõ’Æ_¶S°ÑWøÿkØ^{>>ÄüÎy ΘõͤDë®±ö×é0\ÃŒXLèÓ§ûT_&+àTt¶OáÒË.+xà€« ˜<㇠¹?²Nþ`rᦛn*,X°À‰­.`»Q/®ÂërúYŽÿ»gð`%©Î¦ë|1³CG ¶p²¸Ì”³žqãÆåôÏš3;êÇiï^_~ùåQ?óÆý¾\ |oÌ® ÈÊ›ÿÍ „„RvÈÞ+6rú—,YRxáù\}r…sÏÕÕ]\6ÊéŸ<9kŸH BœÂ^hÔ°LûûçmP„ö‰BJÖˆ»@!+¨`«›øûë½þúëÅG‹Èjg}f^Å2ßzë-#+à ¿ÂÕW_]è¸K~µ ¶ÆAou}ËêÏ+è&Oþ òrY:ê×1ÿù_ˆyï‹­oF_˜Ÿk;ö=ë [s_È *¼¦Ëê/¶MYÔ3yò…›nÖ>‘Šžõ¥l·Îúœén×®]a)|ëM¬ª3WÁÎ?ÿ|éÿŒ—P‹~%(½fåÍì_JLFXšüij#+eôôI­¶«-3Ù_]+ù_êå™ZpY·JãOf‹2«-3?ßÊø³lٲž{î)÷x^m\£‡&û+öÏõ‚•ôÿ¿þõÿÉóZ‹æ-DÌ 7ÜŸßžxò‰ÂçŸÓÏ<ó,hjlÝX·\y|"µ´QmÍäMfpF_™ýeç ž¿?+† ,“gj3úÊô‹ô5¼ýïÂêºÎ»v–9ö„E|é[»†×_Û8ïG8…&ú²¿ ŠøºxÉꉯp¸È9k¨!fÓlµŒdÈû»BÍ›6§ÓN= «1Ö•yºeË–Ó¤÷&Ñó8ge;¬>b$Ïxó¹A9ý¡ =ˆ<=Ç'è#sR›4qñ9RÜç˜oúÔi„I @÷ˆ}é ¨ÉÖXQ„°ß~ )PÓ¦j0h¨E¿‘HÌâß•×Û?êM,IF¨¢â<1’æI¿5Qf7µVþšìŸü/õ?ñlXÉÆÿ8ê¤ñ'G2C¥ñ—-ð¸ÿüíÿý?zþùçáUtõ_¯ÆÕ9Á þìüù809ðI€Y78‚S¶vƒÅ(I7ÃäXÔ/Þ)èÜÅêdâL$ 7N-¡ÎB‡¯„Ý/aÝwß}ôê5Þ|ùjŸ»„í?ÔgÆНº‰Ì õ?ýÌSôóŸÿ"§JÏJÂÙ5Í[¢}ð•µA·’’q™¬¨è¸}8³8W©¬N–«õç<³3Çšk´’Ũ,‹ŠôD•Ö_V`AÉ01ÄaÑâ¥ôꫯ Ü·__Úhƒ ^¶ìËP\Õ©WFáŸ$j®?Ž’ÀdO?ý4lýsEàªçRuG_húÂíÀB–ÕÛâ€kÑ¢eä5k”ó¿=|öοjÞŒûÜ(4Hõ¨ë±>V„¨H\¸ÀŠ”ÂÚÃÁ AT©ýE@ þõ«%•«]’~¶D²ò¿Ôÿ0P­ÔýÇ*<È…±±@†²8 9Ž+ã2Ù?Ùÿûê=þøÂíþâá{í¹7ñPKŸ?´$ÿDjÕáþ»5V¸óÀõÁä¤d<±·ôË¥ø1µÕÁWn'açóc^|•9ÒóÌÚ±@6î[ ¦Tf½Àƒè»ÿYkUÍß½~Õ¸êêÿ]èçùlÉ#óÙª—\|1uëÚ ‹[fQ/ÌyŒþRÎV‡þÿmû_hxdÐ`;È+p£Ãqøáb ˜صӮ2èp?R¯ˆ>ÿü3úÅÏ1)„$öíÑ{ï½GãÆ—ÃŠy;Îω÷Sžl2µ€'¦åN¥2E˜•¦NÐ@4ÂÖ6>d÷Ì3ÿ e¼úª«å¥\AjÓ¦M%ÉÏ“ïOzŸãëò‡-S‹ñÉùÅ8ðx‰àcư7hR$ Œ¢þ€Ó(£Õ|Ÿ™a” ixZ$„l¼ÉFø|è©Øj4Œf~<“† ¹ø³íxUËá‡F3gÌ@J\Cð|©“µ¤|¾>fµÐgÜ>¿ÐI¡ÎwEûLDûŒÃ ¥¾tÆégP§N»d¬‘ …µê!WŸÇóú3W§ ©Ž[Y†¯€±„ë/>VX ÉXæ÷þäI^|°ÙŠë÷^Õ“kë¯ßH¶2òäÏÚk5 Í6ßL4þû_ÿ†O-§‘#GÊ*".jùêúðÁd‘Á—¶¼]áóÏ>“B8Ó ¶žDcÙÖ8Xmݱ#Û²aŽ*k¿Øÿ´Ê%ú•|Yý?ÿ¯×ÃmŠ>7z®A›¢ÏuìØ‰‹£AæÌþbú¸­£l&õý_ó›ÑdúÏJÛ_*s0ýŽ–õs°òˆ¼L¦ÏÜLQ’Œ)'S3„Ö@•˜ô«¹œ­’ýÕE’ÿ…®Â=%õ?5ìàL‘ƶJÍ%¢àÆTÍ[¨<«îþóèÿý>Z¢“Büü÷¯=L묳ŽÜ’¥Œiü MÅ-e-ŒØ«Cÿçc¸L\¬ñÆKYë׫OuëÖ‘ç£Wql†•¹±}x&ÖhÕùŸš1é×¶qc…:UÉó/²b35œ´éêàkòø¿-V|ÊIb÷—G Çê»/b_bäÁþü±¯8\0dÕÖ8¬&à‡Xƒ·Ëàˆ"™TáÙ}4€–ÁÿùÏÔpHò—«ØÈL jñË2Oèˆ|žaØrUŒv–§29â3”ä2_5lˆ¯;=O;áëgW^y%mß®ÐñWÓø+e¦¿k׮ʶAwÜAka›ÔZ ÖÂ_ÀˆñÇ[§8æ/•eʬ¥’påÀå0‹ÕŠJ¾ÕPr‘Ðú %š«e5þ*Ú|ó-è0LÝ}÷=tÓÍ7³" 84Ybµ?ƒüe/>#F'ÚøKl‚-£ÿ?¯¾í-~)Ú¶¥¶0àòéçŸãš•AñÎþŒ•ñúKy@ôË/Aä+/¿"üåêoº,>eË–(c+úËÅZc\WT?+ E–šµk»½pOÇ[옣#éãŽ;ŽêÔ«') à¾NÑÿ€5a^|ÑÍìÿãíY>Ñ+¯¢®\u^´í¶-f~–÷™ØÚ´ÛÜ85ŽúLäj­^µ¯ ¡ýûa ¢ö9èöÿü³OM ô£O‹~Êõ?ÛEJjý_ËË%Ñ ЬʱÌâL?+4JÉýA˜dXnÀ‰x¦4YД’«”ÆÇq)Õ?éOö7O/A"ú¹dX®z•:Sf>•üO­M"Ö1»qœÙ*PÆñ'õ¿ÔÿÌSRÿCï€1VÅøÓ_=`ÿäù¯k·Ýè |MÑY§Íõã¬O§ñ/ÈL‚äª|þÙ?öm¶o'+зß.¥ÑjÿyÛ?%ùË_þR~4OýÏYgõ?.´ÎJë·ñÂX]¬U96Ç´û ¢IÕá ésè¶Ý’¥´7Þt“fÎuìÒ­+uî„•Iº]¹ï½ç^ BZþ)M¡Ó‚­™Ní*"Æ‹ÑPýÎ"-PCÓÞ»ß#ebš>úHÛgW™ýe·2ؤ±bÀj¿ XP ÛŸÖ=T=S-bWÖþ¬3Ê`ü"2Cõgê"}†¤Xã7¿³ØüoEú_›ôSDCH:Ù?zˆ’ÿ±1Rÿ ~ºL´‹¤ñ7Œš+ðü•8ìF@²¾Öø[]E—^r tâ‰Ò[y;û­7ßDÓð1Š oLÀŠ“7h„ òWmûàåþK•ݯ3 4þ±9VÅø× A}:÷œs¥ ®Áðwâ¼P|WÞ·úô9Ÿ^zQ·¿àkÒ(àW¿p=$D·‹€ ¿–ÿ%ýùþcF{0ÿaCOýh*úåTš:õ#üM•1íñ#òÔiŒ×¿/¿ä •büvß±8¿ÁÊeüïÂÿý‡mbÝvëFO=ý v:ñ‚•Ç/º`ßæ v—úIÀù°ÛÆÈý¼^Ïës>û\a¯î{ƒ&|øÎ>ÝÌòå˜qaëáoÈýCD¶d6ßl3ÅËìå>·Ù¦!<³gÍÊéÆL†ðõî}vïËÄŸ·€-mBôÑú¹zÃs|ÓM7IÙ¹lW]u•d1'–|¿ý÷>4«Äü™øÍ¸ÌA?¶¥ð%6áá ó±¦Ç×Â"~Å€¬¼á’a2 ˜D‹úÕ¦U…ÆM¶.´ïкÕ.¬ÿ¨ž=3&-¯^^èÐ~‡XN–/MIša¬Þ½Ø.WØt³MstRwÐpý7µ¶ƒÎOfÍ ZÞj©?ËêÝ»·ÓüU`5üdy?ÿ§õR›c&Êã²Y0]ãÆ=ê->TUÀWðä³ê¾ý§ö8³¶@¸Ü7ä>)K§ŽKXM¿Ï`ÜôÓcyc¹‚K½sûŒ=:².^²~¥¶¶º{ÿý|³B_0ýÚæU…Þçôþ¢9åêÏ}Ûábù¸êÏjëØ¾¬ç“Y"?WÏt\p”‡_Gÿ{ë­7wË-·FûG¯uý/V´V ”4á’aJ5/O‘Ã%ý¥F«“ÙR \2L)£æå)r¸dÿR£ÕŠÉl).¦”Qóò9\²©ÑjÅd¶— SʨyyŠ.Ù¿Ôhµb2[ „K†)eÔ¼enÏ!óýß§ùù[Ý!-Ùß›tuõ¿/¾øoGhâùs}<#ûv=ú˜£ñ¸¸Êf9A§DEúF* '–ÓªgÙ:âˆ#èSvu1VÆàEؾN6•&`“aL(à“¢¥· À:ý8rŽž|ú)Ù?nEá_„X?Ÿ¿´Ÿ)gýk­½½<â™)åžÁÁÚçXÍ3v ·‡Õ¯ÇmšÙŸÏçáPÛ­8xý «!ð×­ªè©§P¶ö—2p™¦MŸ&O&| 6—MC¦ëÇí¶§_þòâ\†‹.¼H>«žkWÿÚô3?³ÿÁB­Û´¦×F¾N=ö˜ª–k¦?“§¸*˪ȫÞ~Š™äˆ€äÎ:Ó»8¯iGlg´ÀÛG°­±ŠH ÀšµnÝFúÂhé š¥6ÍôóöHVP j3éµ\ý ÏxiuÅì7.§´i+ô¹[ÿAc°*KDÔGŸI¸pû7‡¬×e=¡A³þ‡òH¥ù"\ùþ ITKÑ•ù”P$࢒ŠÈ$™Õ?³­Ã±$7þ¬¨ÿ%ýÉþì^ÉÿÔ©ÿ±7” n¬ cj/½¦ñ'óž4þª­î÷?ý\9ÊŠ¦‹¾ Àžÿ¬'Èý_•3 ›ü?Ú &Yú£F?¢¡C‡Ò1Çö’öš‹ø9ýâ‹.¦ÛÝŽ¹>~ÅK÷¶‹´#.ÚžŒ)Î×W‘ÿg%â q_[˜‹ö|+ô…akÑcÊ£GyØ‘Ps‹i´ÏåÛ”Š©%]ÌéTE™lÍk±D—U,¦–tåáR‘š[;MÆUL-é2ÌåáL’Aš[;Ñ&û[KÒeŒçQÎ,iæÖNc´ÉþÅÖ’tãy”‡3K¤¹µÓm²±µ$]ÆxåáÌ’iní4F›ì_l-I—1žGy8³¤Aš[;Ñ&û[KÒeŒçQÎ,iæÖNc´k¾ý—ñ³ò›oÉóü#|ÜFLPl-I—1žGy8³¤Aš[;Ñ®ùöÏjZ*¶–¤ËÏ£<\*Usk§É¸Š©%]†Ù£<œI2Hsk§1ÚÕ¿ý?þøcúðƒ1O±1m³ÍÖø(ÑÚñ]0«…‡Ö¬ú[ͬ=ãÄ!Œ ‹ùàDÞ£ê("Œ\ šðœiy+k>¥8ËIú“ý¿ ÿ»á†¿ÓïqžO-í±ç´×ž{bEM}|Ñk, ¹ÿ~Úwß}ñe»§Äÿ?ž9ƒ¶Úª±ÌBÝzó?äÔúÌ—½¿2\³7gyyš|ÊËKý/õÿÔÿ¿þï§”SÿóÖHãOf4þ¦ñ7¿iüuwˆF .ª Ï™–g±²æSгœÔÿRÿKýÏõFÀwÀ5á™Ìò,VÖ|Jq–óCìØ¤&±UÖgç@IDAT2ÑnàNÉ!RDP ¦æc9Y´9K#ÁëVXwƒ˜¼¤Ÿ cÖÈìf´¼H‘ìL‘ùûàÊûßgœŽÙá&tùåWÐ Ï?/æÍÝvëJçw’Eö—fá k´Q §û Ÿéþ“î?áaÍÝcÝWõ]^ºÿÂbØ-gg8/ݾ÷ŸxÆPÉí¢\#džYÄu†8¼ÆüçqéÅQBâI¿ØØ›$ÚÑÍðÆü$û‹)œ=¢ÝŠ€Hþ—üPÞ%¢û¤þ—Æÿðxý#iüS8{Ä~S”xDÓø›ÆßtÿÁ˜Þã³Ñ#ÝÓý7Ý¥[Ä[fÒó‡˜ÂÙ#8òP ‰G¬ùÏîsõ\qõÂ`†ÌÀ’â‹°e¼qPbF¹s  dLïC&C° S Rcdˆb$/ãMúàȖJöþ¢G·q"ù[!ß—c½-ö¹ÔÿÒø£Ž‘Æßtÿ'äÇŒtÿ &IϹ{GzþJÏ_q¤HÏŸášž?í¹2=³KÄüƒ#µÙ‰I",@žGR|‘<¥äkÓø=åkŒ¿21¤nq¢P8–áÊ@-N* oäÖ˜ sì¶‚Ü\”É\’þdñu u‹"¿R‡Ê|Gý ¤EtÚG’ÿ©}r/ôÆÌ†jèÔÿÒø“ÆŸ4þÀ:4è°P4®¦ñ—Í’:¾Ud§tÿa;¥û¯úGºÿ²7„ôü!ÆÈÆIâ’ž?Òó‡s u‹¢ûJºÿ²Y²¾“î¿ê&_÷ùcO˜ý±ÆA®Eì‡ll‹ ïã,/ƒ|>Ã’S’íI? Ý:šÏ,dqÌp@–—A.[@É)ÉvˆdÿdÿäÅÝ&Ž{®§ÔHSÛ(™úºW‰"?iüIãOc‹ë)5Ò¤ñ§f+INI¶C¤ñ'?iü©qlq=¥Fš4þÔl%É)Évˆ4þ¤ñg5²‰!ßÝ¿ º8iK3J1‘8@TÀ[‚[ô•å1T,¤8©K3J1‘8@”ô'û'ÿ+ˆ”NR܉ŠÓ±'•f”b"q€(õ¿ÔÿRÿKý/T<ˆ§#iiF)&gˆÒø“ÆŸ4þ¤ñ'T<ˆ§#iiF)&gˆÒø“ÆŸ4þ¤ñ'T<ˆ§#iiF)&gˆ¾jüqg ˜ÝÚ‹g1}ZSÙU7ŒJZ)¹³»ØU('øÐ& Ü) Ž@΄$ýf¬h¢ìÍ¡¾”üϺŽÆ:˜Ä|IýÏL’ÆŸ4þš/øADpÖ‰ÒýGMcöˆ† @ºÿD‹¨/¥ûOÎU¬ƒIÌ—tÿ1“¤ûOºÿ˜/øADpÖ‰ÒýGMcöˆ† @ºÿD‹¨/¥ûOÎU¬ƒIÌ—tÿ1“|ÕýGW 1u΢:A“¡£WV ˜,‹œ<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤äQ.å@G^h²,vRò(—r #¯ 4Y;)y”K9БWš,‹”<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤äQ.å@G^h²,vRò(—r #¯ 4Y;)y”K9БWš,‹”<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤äQ.å@G^h²,vRò(—r #¯ 4Y;)y”K9БWš,‹”<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“’G¹”ye É²ØIÉ£\ÊŽ¼2ÐdYì¤äQ.å@G^h²,vRò(—r #¯ 4Y;)y”K9БWš,‹”<Ê¥èÈ+M–ÅNJåRtä•&Ëb'%r):òÊ@“e±“R~+™¬Ûɦ…„¾ s†w™Œ@”–aŠ'ž\‰2¡y¤g9×\s Íž5‡vܱ=qdOÅFº|kú³¢fº¢² ˆ£Š¸|%-å)® *é‡Ä.Î8Œ@´µa,X@—\v©œyÒ£Gêܹ³¾fc³£|饗èôßA Ì¥Ûú =÷ÜÓ9vÆ`Å,ÂÃNdËä–A ±à]f#ue¢ù Ðe—\*"„ ºälà)k.ç¼ôâp:ãŒßÒ¼ù ¨ÿþ´ç{†IæL†A3Ÿ‡9erË „Gð.3‚ˆº2Œêü±ÇˆÛÐãyÔÛt³Méì³{—-Òcà.< ¿ZUUUƒgsðœí*–I5Èbìá¼¢rkb¼ËŒ`¢® “ô{[$û[ ŒuÊ „Kð.3‚HþCñ¸’Y$[¿l®gö ‚w™Œ@Ô›aÊjpR=e@—AIŽà]f#ôÃP©ýó>—y‡s»–É-ƒJþ ˆ]œq"Ôÿ`¦ÔÿRÿËzDÞq؉€§ È2(ɼˌ`¾—ýCÕ8~š_nÔYuˆ&¾û.½÷Þ$ªFVûö?¦m¶i1Ý„ñãiÊGS…wÇŸt Æ›¨ÜÕKô°ayW ú3Šò{Û–-iòäÉtô1ÇзßnäE±×éa#«\ñ`có±×éa£úfô/_¾œæÎKõë×§† špÄ^§‡ä›ÑoÒJã-_^-ekР­·^£"’o_¿z«ÕúÏœ9“¶Új+)Ç­·ÞB'tJYÿ·‚šÕ,6I?ÿÙÏè™gžA²ŠöÝwozúi†‹C1Wèh‘컯?«ž9slÐXJqË-·Ò)§œ,°/­ p1œÅŒ7x_ØàÙgž̾ûî <ÍÙ.%£K<[/iüÏŒñý¹ÿI›Þ6íÚ™¶Ø«ÑÏ:;<ë›×”¶nVO¥öϬQ¾ýgÏâ1ñNé?[ÈÊß~˜ÏŸõ¼Ñ? dT?¤»ï¾›®ûûT¿n]!{ÿýhTòüýMßÒø³rãOݺuhéR´i#=žxÍüÐJÞÿ“ýWÎþ߆ÿ×A?]òåj„ã@ª0þò{7?”ÅGfë´6Æp3k£[Nl÷Ôþü4ë¼Z“јu°UbéRØý‡Ç¿ùóæ§çßàEõÄð,g>EÁ)Ù9!.üëÑ814ä¾!™#*™KWÑòeËéùž£Ñ£Ç`eÑúpʇԦu+ÚgŸ}±²àPª‡s,d*(4ëïׯ-Ä¡¸Çm¹Å4ç  À¡¾cF¦EKÓÎ;w¤ƒêæwÌ•—[›k!ªhÆôtûƒdfÏR9¶×±ÙsêòÑGÑûï§qcÆŠžm¶Þ[Kö§ý÷ߟêáœî”"—'ÈsÙ,XD'œp–ömA3g̤AØj7[¸/FÙvÙe;HÊ&“© ‘ƒK©Eº¸þLÇý>(ܲÍêÖ©K;þä'´¶éüÏ~ûÓºë¬ÍdRÿ—^z‘ÞzëmØl!=;t(pj‡+®¼RnþØ%%õgêžGõ¤æÍš1«èçs†=7ŒÆrûLOMùˆZIûìM‡zÕGûpÀTÑ×^{-Z´€Ž?îxÚbKØ@Úg l[t‚ zô€ vÚ‘^|áEzçÝ·a³…4tè0i–ÇÛo,ˆ‰p9ê×GQ3+2ÕtZæ{ß+õêsÁùà½R`æ÷þÊiáa@,¡B ‘O"ÛBƒú Ä~O?õ 6ŒÞ}÷LÀ´££óêØ±c¬ÿ¢Å‹¨ßµ×#½<*ãö,ð\ˆ÷Áù:@¯Aõ3Ïu×]OÕÕê}Noš Ÿ{衇høðá´Î:ëÈJ›^ÇK›lºI®>V–uä‘GÒûاÏt¶0q¨­þ=:ŠÄn(FŸ>çc»Ô•5ÖŸe5hЀ/\LO>õ$ 6í6‘Úaê7Gÿ†vÙy—Øþ -¦ë¯ëGÕ𮳯?—ùg°ÛL2ƒýÙ7¯¿þZ^½œÎ=çéwjƒ´6|y׎¤on²É&\ ¾þÞÿhjª?Ÿ“óÜóÏјQchÂãéç`»WÚ{Ÿ½é°0îpÙ9÷¿€Ö>Õcçÿ_¥_äÞPÃYòÅ_”s©Æ`Üiب!í†H\6žø´ ´™~.Уý½öÚHz}õKŒ»ÀÎ]»v¡ŸýìçÒ>¾lË1‘úÜÐçhì¸18+ çÀ…¾½/ôоÍc/®ÿKè§o¿ó6- cHÈ C¸ð]l¶ÿëyÔ¯©y³¦Bböçqãþî§1cÇbLœA[o½5pà´ÿÿìOõÔZ¾Xý³DÅöd¤­¢±Bõ‹ÿ~A§œt2=üïGèßÿþ?úñöídY5ûSÿþ·Ñ (ÿn»íF}pöÓZðc jþÕmÈûiìØq4}ÆtÚfë&Ø} Œñ|›ég=óp>Û‰'D<=> =ÛÓٽϦe_.£·õ§0Övë¶;]pAé/VÖÇmú4Î7f Ú´uÛ mºÚ´]q›jéú¡q¿8îøãiK,ýçûÜ ¸—`õÙbŒ;ïÂ÷¹ƒ0æï@×üíªÆ½tW,)ßk¯î溈Õ?Xâ“O>‰1x4m°þúô»ÓOFë/TUVdf]ÿÏJ”ôç ^™ÿ'û'ÿû¦ú߆oLcË„…6m¶ÃJ×wÕ1ôãIñý'> JðôŒñ÷Ÿ4þ|7ãÏÀ[ŽÌþmÛ´¡w±zYÌ€ Ï0–“a ¶ûß.|{¦ögÓ©‘¾©þ·¢Ï?s?ýøn |TÁ¤÷ÞØ÷1ß^œéó¸5ÓýF‰_dŽ`Ü6Þ„>­ÍØmÐxõ?·#ïwÝþ±-W‘ÿ™~~ðs¢ÎBŸ>籉 ;í¼“ÄMš4‘³ªqx¯¤5lTh×®À˜( ŒÕ…;SЪukÁ3?êÈcÒU<×TØk¯½ øÅ"S ¦å¿á/ /Œ1"ò+¯æ 8(òµhÞBhpÖQÄq§OŸõsßzëM—_(Üxà ¡<*SôJùP×v*|:çÓ@ŸÙ£Æ²…2s¾–Myâ5‘•3åÂÇÏ,` €d:¸¤þ›m¶Y{‰#ë±Ç›Ñ@ _(Ûÿ¹çž‹|wÜqG¡u›¬}|ýÞ»ûÞüúz_N¼!ø’ÚgxN·å 8@ôslV¶âö/Ö—•-Ó‡šÉm³hÑ¢X2r‡Wd¼–¡aÿ0ŸÂ—í ;¢Í-mõàøÑGÿå~ôÑ”Xÿ0ìfåíM7Ýh£æÂŒÐÃ6CùÏ:ë,Àêÿ¾þܧ&¼ñFŽ×”âÁMu„ú3ßâE‹-;ß<y@+z˜gq±ÝªQ¶1ÿo°õq)¯ó¿G}4– “©âcåêÏ|7ßt3h³ú3Œ ÚØÿÏ:ëléÿR.W'µÁ„È¥Ë4dê®;îB¿oëeõ7ÿ뿞Ï~]Cÿ;䃅·Cû¡^‡³ìC9DxÚïÐ>CÆ’ƒ'°Í7·Ð«×qeëÏå|àÁ"W$éÑ£G:wéëÄ}ÜÛÿ—¿üeáã?Ž,w܉¾íÆ^–mõgÿÛ»{÷&A"}/ë§®=JÆ‘—õSe¿ñ†c¹DóîSXêë2Pƒý‹‰5j(ò1ÑõpÙŽëÕ«€ ȈcÝ\pAP¡¿áƬl¾þl;ü°€²Í‰EÂ/FÒÿ»uë*2­þ½z«zœmþxÁ#ßÜ/¸M{År—‡ÿxàþâ* ¯äC¦Ž£á>çt°œ‚¶º€Ý¥l-Z4/`Ò3êUe÷–ù»ßþNx8O­€«B/+hÿCd·nÕŠÎ>çl¾[‡Â*'Ÿz+œFÓoŽù5=þØB£UâñVmóàÃѵ×ö};thOûvï’*Y²Ç»:¥–k4©êF”ëÿùç_ 3–¬ê’K.¡Ýwû)}þÙgôæÛoЭ·üSöB6X{­ÈºV8m¼ñ†¢(V ñWâ8œÅ‡Ï±:À\6®{ãð.Î ¿0O|÷=,Wl$+‰~‚IpVê?`€ÔèsCéºë¯£?ýñO"Gß÷´Ì¼äîáG¦¾×À@Iû콋¥Ã_ÒÏ•>+¯6Áaجè³Ïʪ$v&ÊÆÓ^V.–a_³³,^mÃi~_bë7j´>Õ©[W`- dÐ÷©ˆ\X¬^`| v—_˜8^ÉÃYxa¦°Ã!÷Ñ4ìçåл÷¹´ß¯öãn@ ñ5µ3ÏÒCÉD x¾ÀyN±š@Ä‹F†¢f@ £„°ÿµ×^+KyåZófÍñ+ÿSX9ò4tM£O8^}õÕÀ«Ò˜oÑ¢%"Áäó “*,¶P®þ˜8Ò"€‰µ7d»Uá\°àÿÂ+Ðr2oãTwØà'øºàû‡èÙPp>Ûþ?ûý ùuh=,oýÃYgª!ŸõÏÿâ º «ú4„åSH¨t\¡‹Usà~ÃKÙy•T³æ-èÉ'ž g†>#ãÊÉ'žD/¿ÂØb‘~N†e³>‡å×ÿ‰'C¿‡_CGÏ#{ÒNXY¸ «›ôï/㯆êg~ÍB°þg¥.àóóVT?¨¥ÂRXa•$#º7䆜î²kg:ô°Ãiá‚ù4ðöA4ùýÉtØ!‡Ò-·þƒN>ùDU Â3>¦=ºÍ]ÀKÄ«h·Ýw£_ýêWð£†ôÒ‹/I;5RüK™ ôÔèÛïM_;ªgOúÉŽ;aõ&ÆÞlƒ14«¯»žÇ^ôm„ýðñ€ °%²ÚéY¬ì?~ìZ LdŠyKMÏO‚±·j¬‡Çs•î½÷^:ý÷§Kõð#¼ìMõêVaõЃôÔÓOÉ*ÏcŽ>†{üq©¿\ÄïÀl1!o¶!Ö¢÷üH ¾¹ôË/e%%ÛŒmzâÉ'ÑÁƒi.–÷G_Ä™ËvVΰ„VX-v6êÂËîy%&—m,ìp4Êöøãzïý7PèÞ}/úrÙ—ôÒKÃÑ^w@FNB›Ü;xÎH›KÿÄ*%>¨œÃÙçö¦Û ¸sçÎtøa‡Ò‚…‹h ÚYÚ«³¤M±ÉןËÅ[~ãh?½—tØ¡=uß{o¡>b8í±ç€«¨Ï¹çÑ3Ï>ƒ¯o~ˆûÓS„‰@©2Åÿ‡cµ’œ‚ô ''<œÇ:ä*ÊFÍî3¡fûƒl+Úÿ±ÈLúÙ °B²¿ºEò¿Õ«ÿñÍ3„Ôÿñ4*cbì¬Úw¹ó~¯Æ¿ðÜ‚bkë€ëúßêÕÿBß“–Á3VMþ'¾B«o: jÛ¢E¥yÅA…"8k ”LœÚ_`Ï=‹¥`žh}·ZùçÿØ"¨p¼~_í£„Y-7ÓÅ þúôéÃ.UÀ«Bï³{ |Ùe—zx àªÂãO<.1ÿÂyß!qrlòûnúûM… ˆx»ðŠùu27ilè¨?´žèaxðàÁM¨EK^1TUÀ ˆèžŽUúkyUW ?.Ç…ÃU£þ];ïŠÕa%…T½ºpùå—‹<Ö=nìø/×QË'ý±0øž{sùÁdŠË%rd’(Éú­ìwéX2Ó¸tÉÒ /¼P*,`Î9÷ÜX>1†È,OþþäÉ…¿c…Ë¡}‚~nL$ˆœ&±:¬(øú³Ýß›µO®N¹D¡pîy®lY”ÛŸÕ…¢–/«.(~†a câ ×ßPTš|2'3—ÈÓqŠý„ë‚>‹¸ª€4`Es …}÷Á¯ô’G^WS˜5{–ð³o°=UB…Äô™ÓUÊÏ+Ñ&Ož,¢˜n~éÿõQG ?ëzáÅçcž¯€ŸuÜpýõBSîÂ2ùOxèõ^_†‡é°µ&ÖŸõ?üÈC"Vd,[ž³ÁŒÚl0k¶”e° ¢„ +“ôžmÀYò‡úuÔ¯cYÙ¿…ÍDŠ]˜…ýšWn-˜¿ÐšS˜1Éû=¯®(”ê꟪‚¬þÜWë>äC¥þØê©eg–"/¼ø‚Ö6â_ó0Ái?ûìsÑÇ>Î6üö4þŸÿüçÑ6²«è—žW^}¥ðê«ÿ‘BšÊÉ“aƒ¿ß Ô6öª bíBýÏ•1DǹXÀH”sà÷˜¨•~Ù¹sYÅfú™êòË®ˆå7.?s¾§å´ê’Bd ’v¯h·}»o½ù¦Èå~{õUVL¦Gÿc 2Æ7Ô1lW¬´*^e¨c¼Úz|(ß+Øþ¼úu1ô¼ù†êáö¸úê«EÏÐÃiþãðÂó/ ü S®M?Í·)>˜ßÃãhiý™§mÞ¾C{©ã¯°:¬8è*´*Yõ¨˜)ü‡tI¶0*w5Yƒ„R4“G–\â+h9[•;‚š,e®Ãä‘%—(e(ÉF厠&K™kÀ0ydÉ%JJ²…Q¹#¨ÉRæ0LYr‰R†’laTîj²”¹ “G–\¢”¡$[•;‚š,e®Ãä‘%—(e(ÉF厠&K™kÀ0ydÉ%JJ²…Q¹#¨ÉRæ0LYr‰RÎæ•µ<þÎ¿Ž £rGP“¥Ì5`˜<²ä¥ %Ù¨ÜÔd)s &,¹D)CI¶0*w5YÊ\†É#K.QÊP’-ŒÊAM–2×€arÛ¥qøa֦剙6'^Љ`Ž ¼eòÈ’Kx*…K²…Q¹#¨ÉRæ0LYr‰R†’laTîj²”¹ “G–\¢”¡$[•;‚šŒ+†²U,¥òÃä¥(QJŸ£5æÀ-2JJecr,¹D1e™âE¥¡_Á_*±HæWð—dýöþ}øaXE)¸ršÊãr2s‰Rú’lÑ%U[BP*£“cÉ%Š)‹lÅÙ¢Z.äß‚ñl/ú~ÂiÁI.ñ*„#Ž8‚‰è>œËó¯ýK~µÆ–#`xE?#ó#2S5oÑŒNûíi8g]I/Ç9 'M”•BÛm·ÈÆvZŠsƒŠõ›|ç«°Î 4‰\ÍbíÕØ+ˆ•B{ì)+nøuluÂ'ÄÛG:¦úVeÈm(,Ÿ´VßxýØ+1rÔëA·*f~ ˜m|¿<ÙSía¹¾þj %—+ˆ"?ü(æ5¬$ª°Zã'¢ÿõ‘¯c%ËÙ4««–á×k|&ÆOúS+…àøâõG¤&¦„ÈëoѼýö´Óhu×þeXÍõÞ¤÷é9¬äjÛ¶µ0òJ–%hÓ‡ÙViŸ#ŽÌé·ö/©XH¢u®¹þ¬XÞAX«zèAÙ­}tºœK#e䋯úËØ?4]ÛïZês«X3^³ïÏö˜/ئ°*Šf¤àزyÿõçG/ ;ùÔ“©yóæ’ä:òaçðj%ÎD˜0áM‰­þ,ƒGàÌl}¡Ó±Ò-Chù¢éL?^÷ÐÃÊŠ¹9°›¬Ž‘oñ¿8k^¾ÐãÀƒ¢þ:0¾?ÓU`Œä³cLq‰~^Q2k³?×çÔSNˆ~È­Býøs¹ª`ƒ ƒ6ÿ‰Ü 1)K `:&oѬ9¿^w½u„_üû³ŸúµiÛFhxuÅÒ¥‹U<øD¬è’ øVR¨?s×TÿaÏ •ò0Í_ÿúWœßU7êß`à è²Ë/cnü=úè#òá Oa…‡C>„N=õTàÅ:‚ãKg¬<ê´k'@Yû7oüöTØ@ÇÞeX-4 «¨†=? +'1ö"LGßæC÷˜¯lý™HŠÃ¹B‘‹0%«g8«oß¾G¹Õ¿×qDz #GŽ^óFZû—Ó_ÚþDâ\ ÿ7i"6`ûíÿ?Ñ6M·±Zˆýyåݼùø2#µXuºöÚkçôëLÔo$ÆY©ÔµŠ0 ‹sŠêQœEda¿ý‚žm¶QTè7C‡=lR «ÿz•œÉfõßp£ èriSeyô‘Ç2 †õ‰N¢À}®ç‘Q\V‡I)á+ìÞÆaü¾Àj=^…ƶ•R¡©´1Œ 1ð•Û?ÍGQ¸¢3ÿ“ W?I'ýÉþì„!¬\ÿÏ;^&E…i:ùŸt93Ίô¿0ŽÉ¸Àôa@* W…–4t²ÿJÛ_,FåoÜþp‘š É4þ[ç`Ó¨¿F«|ãöÝIàëÚŸùñœÉ!«E-ØÕËÿP¤ï©ÿ­ôó¿4Ú7þÕ“t´%{]¸aH}¹u‘Æê*» µlÙ‚&Œ'Y'`ûK=líŸë[’ÏŸ>¾ë®»°0½ö:O²hPák¾˜;6Ý”·Fi)˜‚ËÐoŒ—! ¾XÊïr€˜:u¶0í![(8§yóæÔ&¼qšßϱâBHÍ@ðˆ‘.Žuócty¾jS ‘ìÑ£tðÁWPšÁ%y˜~I³0&Z.•Jçë_.ü ½òòËøTá4¼põ“›†8Ptol18¨Ç88õÚ`ƒ EŒ]T%Ë©9”ÓÏŸEäö<øzýu¼¼…ÉÁ‰øÒ>kk ‘Á®Á”¶Ú@éWˆL–ŬŸ…HÕå„Ðæë_ìupðv«V­•¯¤|ŠP•z­ÉÅõ/0ûÊc‡ys±L‡8… ÑoÝ"h ¥#BT¬Ÿ³B6dNVÄXüoí|ôý÷ßW™"&Û`[ly Ü1–l¡P:I"žb Åõå(‡D|6ÃVæëÐx ÊË›b+Óp°â ¾8“ó… Ó'J Ûm§‘p,Ù~ã8"·”-Šò‚\Mg™2îÜ ¿¾w0üúu­ƒ•!Hüâ¿ói³Í0a€\ßÿ¤¥|Æ rkÕŸ©ÎÝD ~}ô(©?Ol4ݦ©^øT—.]€SÅï¿?@m¡LÂöçþ& ¤ƒ:&‘ é€ ÑìYèÛwsßL#a_%&ámL8cï¦eëŸÑ«@Nûv惦-ô8ã(T.Zbà§c,+ëB—·¿÷?–¹¬XÊ'JÙNZ 8¡ÕËX_6Lô0U˜—qÌ,}±€qv:òEºÈ5E¶¬›óªÐÿ˜‚u³ ìmÔ(´)O"5mÚL`Q¦u–6UsL|²´¼",èçv=ä•:É%Љ…Æ6훜#[\oþûßq˜ûõ’û0²±à;ü°Ãú}T©ýM /–/ëÐtÀg†B$ýl§`6&Ã’d¬XW¯ÞÐb;åñè %X6Ù_í¬Rlœ`¥äeüN•y^€½£%ÿƒÔ¡¼YŠ]LÓ[œù]û_Ð' k0âäÿeü_Úê{0þò„ªäÁÅËŽÃF†¶‘TvY?´)óa[®¾þZh|<x­‡Ù?“A °Âì<|]WÿÝpà t饗Ò~ô#œíÕ_ŒÄ«)ùü¯r5©¯ßS~µþZýß‹*'ýÉþÉÿ¸c¤þ·²ã¿ 'iüÁ(*¿JÀ‹Ð™´?™u\2øwO iü5{}?û_|öáVGcÖÚþÖè.Ní¿ríÏöâ?~ˆãn%c+6ò̉ó4óð#çµÄÄ@ÇNÎ.jÎÏ?û9¿[§– Í| otè7½;ȵ g†?£g¥¸(VlãM6–m#ÏáÓéøJáì&: Ÿg^Þ^¿ óÖžÈtʯé^ˆOÄפÿóÏÑ>üâˆÀ[Q&b«ÍX¬ë{m_lGÐö±ròôÌþ–D\¬?diÄ™áEȯíœ;r#ÍdL—¥CûOž<™pvL¤1²Ègô>æÌðgôeíÈ„5#tíÊgõ‘•êw˜¨ß¨U{–RÚ& ªùi»¶Û /Îÿ'ðVÓ`%I­ú#§š¿_Õ—QkÐ?ùƒC€¬~üc·æmS\¾7ßzKàÚêÏ2öò„/˜¸oOš8‰pŽV^ƒI…ßS'|]ôȪ˜ ßp¼…W çáÊÚÌ`̇d³¦MqÕ<öËÅ/ù3ë^ˆ?Œ­KpxúyçœËVlüSZ\ƒýE‹Ó¯’X»êçòhÐt\‘$û?å\.‰¹Lœ^¢e=çœsDŠùŸIÊd†ZB¿Ç1]³æÍ8’•]8×KàxAQ´o+×¥MŒ(ÌjΈ RŠü_“Å9÷~Ü>øðCl—!Ä'žxb` ›"üebþ`;Ž˜LBF¨é"ýFcd‘O©ó× ›…}¥í¯zƒD&/éÏ›<—JöOþ|ÀúKÙþ§‰ý(#TWJý?³Ž»ÿ˜™¢Ýr/$VUÿ …’og„2Xy˶?hK6°Z}Rû¯íoí#툋µ§á­¹r1g†?£Oí¿Ï?ÁXýÀýïþÎÅÈ/º¤>s¹6m°­›„óhø+6žÕ¨^ýÏ«ñ=¢o¿k¨å¶ÛæÈæà+[:í ~yàvJ¨Br¥ Þ®‘6žjÅ!¥r>ÎŽ;íˆ/Ó\AÛoßV2ø«i‡_Þѵk7eÀ´ëíwÜ!g ­_´×’¿µh­úkQ¬⿺õ°˜ ÝЊd±¸^¿ uW~fóQ3Yò3 |–ËÌLl³ùæ[àëE‡ÑÝwßM7Ý|³ä±þ7ß|«Dc|1ÈÂ+¯â…¡ýòõ+™v®’óuZmÛÒØ¤ Ü>ZWm«·±~î3RµâÌH¤VMûšsÀV¹h˜¢úGc³ÔÿøãÇöÅ–´-|è/]dP¸’úKŠÉ?©°ÿ"äVdâ9GHò¼sÛ¶–“ ±ý«ÕN(wNN0LÿøÅ?rûí·ÕYR¾*êuÜqbîG]ôUa-§ÖÇŸÐ ý®%µlÕ’.†Ýrº™(´¿Ð{AžKXƒ‰ ø ý2¹¢FJjõešÁÊ‘0à6.eû¶°…P_$£cr_lfѯº)s¿ëúb+ÆSô§<î(Ä„ŸÔïûßëo Æaé*ŸËY¡4Nÿ(oýš‹Õ}bûk²k·n³Š†àL™X°  VJ€Üd#Ñö?ÞÖÇ×Õ8ð¹DŸaB·œ~>{ʽØt|vV ´?‡PjÂA×’Ží_TÿÆ[êÂô/¿Â²¼u˜U%uéÒ•Ý~»¬®ä–ñgñùmõÛ—^bk˜"ý± L„JßX¿Œ?œÍ4(ŽÕ“ºvëývàƒÂxÞ@b.íkÕ_[ÊZWÎzb AJX‘¤©|ÿ7=¬ŸÉ»¡M-ð9A¹€öç/“1!—‡Gç²3û[mB¶)ád‘ÿ7Ä=î,þ" þÝpÃr6“uìØÛ>yë« EþçÅ Õ Ú¿¦öçú›ýÖ LúÅ@fædÿÌ5’ÿÁæèD¹Ö;JQÿW ‚o¢ÿ‰|èç¾jÁÅãÏ7®Ÿ®Êú¯‰úeP†Yùç~Àæf¾Y¥-#ÙÕõ¿•ô?}4 -©Í+Ï!Ҟŗtÿ­ÌÿÃóŸ˜óÛW²ýµ‰¿»öÇFÖÉ`Ð-X…µ\VÇdòûµñ ž¤‘É6ÈâÏ/]ºš2e ‚Ãg/üóŸUI˜‘+ÑäXJ’š*ÖÏçþì¼óÎBÃç¥Ü{ÔA:+lŽÁ/­Õ8\™C×®]h÷ÝwT K.¾˜.»ìR¬Âá,‚¢ÙŸÎþ™ˆQ¤šÄ`ŽykLÔŸ/\$³:YvîæÏTîfl´ÓqÞÛ¥sâí ü+·âÂ[žÅ'ß9ð+vÓæÍò¦oÛV'ÃI¤[ñÙä’½+tÆàŒ{äíhÒȰ?ŸA²d©ÚfʇhŸÓ¬}X Úÿ­ŒáÀúW¶þíâj˜Ýró-(›n—á²5VËÆ¾ÆÊ8F˜4i ’m/êaÜ^KpÞ‡•Õ_jŒâ…I‚"ýR)‹·€Â¦ŸO.ö)_°¿Ø†êà ésä<§%K–Àæ‹éï8+„?aÏ•á—Ì®ü²íêÏrÞ‡ nt{Ft1|“ùMÌ`âØnÞ®² ï"ØyÏö¿Pÿ Lª›×Ï4Ì…sŽ"s¾þLÏNRR¡4Ÿfj«?gðaÛø“ÔŸë!6è{ðtäm×Ý`Ï€Õ($1U®þ<îX¸ûŒ;_ò¸C2îœzÊÉôgw˜(~ŽžÕ ÐÛªMv.¾'«Ûæc‚èÅ^ ]¢´^ëmÃIËÛ|اçÎ[@øUWëJ’^ÇV{èd|ºü‰'ylº‘zÞ¹Ó®7l¸áK€«h6¶½îµçž˜T}‡Ñ/£åØSõöÛoÑ1½Ž¡–Ø&‡¯Æ Ù€›–ÏXú6àðÑ”eìýóŸÿWÒbdóWÿ¶aB‰nÁD4¯â/Â'ØGM³q˜9‡.˜|Ù}÷ݤq.½äRºô²Ë0ÖjŸæüY³çÐ%—^B÷Üs7'!£¸Q³´·¿(˲‚/ø>Æ’Œ1 WÜÿºvíJÝvüKQ|Eãç<)_øü¥K.½8ŽñQ?g²£›xD…¶9gs°ñOÚw¬ÿ$nS ½íÛô¯hS°î³/ÚTW~½²lVÄÒ‚|N2ȱ ^û©§ýV8x«ôyçb%2ÏøÝŽZA“i¼ÛâL†JÖ”÷ÿå@$ýj'³]²±Sgióñ{6X–%®e6T?ÓTò?¶ •7ŽšHr4òÙÿ¦Nýÿì} ^E±ÿÞ4P šI€Ié‰Þ«øþT)ŠÏ÷,HÇ‚ÀS¤£ UUº¨ ½w©’„$4‰ $¤|ÿßofgwÎù¾{IîKÏžäžÙ™=ç|ç쾿‘á‘HG “'Mú±ï} eÄ~#LúTñÆÌxh^sEÿÔÆôéßô)i‹ë±{ªw:ìŸã5jä¨0RÆö ¹6 ÿ÷ƾ1Fþ8î“ãu€ÈæiÉŸûK²‹üõ/þÉñ„?ŽâØMž"j~? Ž|CÇz$ÆU®íªÎ™Tl1ÕŠËüSw*Í3Ž9:Å¿ÉSõ^˜_˜þ¹~ñäIŠ·€;_Ä?¼õP;tÛ2îavćSƒM6ùFÜÒ ¤V pÒäÉ(G”å]sÍÕŸ4zõê%õX—>¿$ÊâýrãwÞ®È&-Œ¹Š¢ †ÔV ëýˆü½÷Ù;Ò³D ¹½³ÉÇZCV¥ñÁ¸qvØ>•‘fà ©”¬ûa¼˜/ܬ%^Ö$EXÎÒDVR©)×ÀC4‘Aù&«oß> ¬“’òÄï±ûž‰_®ÍmÊ'7Ñrknã…݃¤>kôî]—ïÝkÉT'$á¿ñ:äPÕ—ïu•n¡îûÁñïÛǵítm›’+W|~˜äQ.·”ÆMWâi•ïÛÃ:ø/ñ>ï¼ó|±pÅC¸TŽÏ5—„9¡o¿£ÛÕÓþÏ>û¬È'Œå˜î˜šýîɧžtm°úSM:€]Nœði¢5JEh®eê-VÇ·í¼ó½”âŽ;nw:x +Û€A¬1#þϾp«øL¨e»z”Y_[õŸúíPQ´ CêÀH˜PÌVÜñ1Z"?cªG¾>Râo«Á?ô°¹*Y‡í7:Ÿ¦: 6lXŽ1uzäs^{õ5Pš€©Øª|Jã¸ãŽkæ ú'Ú—]~™ÔÃÃdÈÈþ˶ø~Lûã¸eYqÐO×X}µŠ¼¦›÷!âèvÛ3Žjü§,n÷éåk ÌãÙz§ÝL9Gc¸©ÐÉBÒ–£Ž:RšŠ7²´mhÿóÐ')Ï=÷¼¤e2µAºí¥my\´m÷ØÆñFÏ…±]=ÚÔQG…Ö·q万Ú*tvà?ô·w’ou,•1}cš¶ÙüÿCÎ ÊýÏã\Dÿ×÷¾/ò8þx‹¨ñÑGÇš¾^bÖ•òK9Gg8¦v Jó–³Ô¨ªi* å‘á˜:ØFl(Í[ÎR£ª¦©T€”sD†cê`±¡4o9Kªš¦RRÎŽ©ƒhĆҼå,5ªjšJH9Gd8¦v Jó–³Ô¨ªi* å‘á˜:ØFl(Í[ÎR£ª¦©T€”sD†cê`±¡4o9Kªš¦RRÎŽ©ƒhĆҼå,5*M-&1µø“pˆ‡ãa~¬H>Æ ©³¡4o9Kªš¦RRÎŽ©ƒhĆҼå,5ªjšJH9Gd8¦v Jó–³Ô¨ªi* å‘á˜:ØF¬c–ç_ÃzzÅWX•ÄQcšŠ«25èŒÔRG©µ­ÀRÏ3éT€”ËAVŽÔFl(Í[ÎR£ª¦©T€”sD†cê`±¡4o9Kªš¦RR.åëºì“õñäõæ•ÓjmËYšXV€T*@Ê9Ã1u°ØPš·œ¥FUMS©)çˆ ÇÔÁ4bCiÞr–U5M¥’^]ük¥‰‰VÑR°u ËU±U鮎µ¢5S;ÐxJó–³Ô¨ªi* å‘ât…Y±A¨I¨&¦]ºðs*n•ÞCîD"Zž’>¡â®‘v aíƒçWíù‹÷|††›ðð4WÖ«ÛB·îÝ1M‘“œäWuÞñtí<¸ëË!)7‹9L~òz¢•o¿l·ÝvBwÔ‘G¦.Œ7 þtÝõág?ÿEXŸ)ñxiøp,Fý®À;í²s¸ó®»ñ©Á‚Èg¡|•ŸG·îªÂ*ŸÛŸi‰ám+€rƒ„ írRÐÿùŸÿ)oÎsܱØ1©·Ô5jŒ¬aÁ ¨É'%]<$ò¬ÊçîU·Þzv.Û^[Öº³Z cý¥ xK…Ç‚ø”âûÄ'üÅ_ÛÌñ8pŒÏ“²«â»É8°–ê7:Ì`7:ÕÁ´öŸk ýý¶¿ܬ)/ôyÔèÑ"Ÿo0UßhQÙ«®ºZØzëmDOGã3ª=z°)é˜VùT²×?wFèw飕’5˜Øh7öUûÏÒT°+ú%(Õs’ñ0©,ùùÑ¿ˆo«W¤çÎEñýZk®)ôzÊ|V]uuè`ëTöË_B ÐÖõhÕÿÕV[-lÃ:‘ÔéѽÒfº°_qü{8›N…2¡´ç².©OøYûÑÿhÿ‚Á‰oí|å+_Mý§|®…óÒðÃZk­•ze½ómiO¾Ò6Ä_ù™â`¼}eò¸ø0„óÏ;7<ùô“‰ØoÝÿXØoÙ~á¦[nNþgòi÷£ÆÀ^yäîlH›oº uz¡Lß_! 6ÀÆGIZÂñFw²¢MÛø›þØ¿ð*ÖZnùåœðÂç|ÔÙ5XSf­µ×޼@ß|:°ùæ›…§Ÿ~*ìµçžRŸJ=u`í¤ Î=ÿ<,lÿ”ÐQ~W‹™2vrÂZ`]à§·ÁOwˆ²ÚR aüãÛ0Æ—ù?_ÿ§ðóŸÿ ‹{÷z®WÆ7rØÿGï¼S>ÙbkUB¬ŽL+ý«PS0S}W­‰&½. 4êsŽPû'3­iõ9^×ÿ1þghÖÃ#§á/1Æs¡lnK¶Ýc<ê*Ÿ6/Èuñ%MþïDñMÈ'°¨ùÖ[mEçñßÿ€ýì¶\\ɚȶ,ôÆQΈgR`¥dC L3löÿÃý@êpü÷ßoßð…/|t¬Ÿi‘‘Ãüå&A¨™6ýg,ß¡ó@IDATž&?‰„Vñ'·2¢ÿùEÿÅþÔ'g—ÿe¿…×Ê` %2_›Çª¯O™jßS0¯8¦Åÿuü’Jæ€ø§-b0·¶åùÇ·“_L$’²Š -NR`¥,Ìcîá2þÔ S€¦7¦V`)TÛ‰ù/ e%ž’É'ˆ›<…ËTÌxùY&ûa}™Gý¿Eük¥ÿ)ÐõüfÿXp[Í×L@lM2ÞØ³M¶‚Œ’~@][ú!>Ã:ô¹°ÒŠÂK.Þªªà¬~5SÁ¶[—Fir-M•Œ !2Àׯ^xñÅÐ [7ó"^zÌ þ')È'M¥8f>üpn,^Å+ƒ“B_¬Ô«wo¹qK<`õ­ßø9¼„!~Æ×õúõé‡A> ©~ø!Æg¨¬µäKh¡1«¶ªZ±–³*&ßÒDf@|ŒÏS†ãAÜ$´} ²žS+ûãk”lßÒK-%´‰_ 0ö&×ÒDf Q*Å)“€*q‹œQš\K¹³Ô2Ë,ƒðÚþpÁÂFãaÁë¯ «àAÁ"‹.*ܬ~5£X~:4 :è p,[VßäN«ÏsuŒ Ueà*Å)“€vje´Qš|KIAôY¦d4Â…\öŒÆçM¯ËÃ’E¡«/S&YP;QšÜqã> ÃЮMµíÚ>£¾ÉÇÛwX{DxŸDqK÷þË/¯±;¨?ã4bÄkøÜêí°ä½ð°ÄÕ©Éç˜âY‡®ø€¼•ýç‰YO„Ïкƒ~РA’²ÄØ[ÿ™òó©ç@+:X:H”—?[}ÁÅÌÇ?}IbÈR°»>ðS}Hâk*Ì*|Õù…†ãÙaY,òßÝ?l¬h]?5/Ñ& ¹B c”¾ÿqÎÊãÕú‡¿ 1~YÆxyØ›™X}Á¤L2a;)9þC‡>Þ]€ñ)?ÀMÔAýiíÿ 7Ü€[; §b]«Uñ ØZÚ^ÿÁ ߊE‘¯îUô_½þK¶b’U Rœ2 ¨·ÈeÑÑ¿¿ÿH¦b’U Rœ2 ¨·Èe±¿bÅþp= ‡ð×_åúA#]à5‹R’2 h®PÃ¥éÝÒDf Q*Å1#†<™â+¤q\#®Iª¯Ý μ‰OßÚÓ“Rç:äXÁù5¯k¥s˺§¢ÿYcobW¹e–^FƒoË}÷ÀåóîùIÿo¾9ÇúˆÎ…¾÷Ý?Óþ~zÔ‘XLúQÔa À#%y\?ïð×N0g@öÜcÏд+“77+ûu~¦þõ—¨¡Eo¦GÓEÇe´6>Ó ?‚ 5û§–¦ãÊñÝkÏ=0¦ßMè!ŸÌ„“Ÿ8 "+â+¸NöŸ¬þ¯òǾ76p-9®á´õ6[…›oºÙµ¼=P[?#ä‹ÓÍÆþù𘢘AŽMíY}Æû¿Bþ¯ñ§ø_ñ¿àE%þäðú™P‰¿scüÕo‚ÒàòuNº €šH8 ø²MËvbN*HüZ™I=œ¬~…L2NV‘åtBMý'û™ãíOÞÊ6ûÃ9ßÙ¿~D÷–ùtúÿ>7½çž{X%Eɸyqüã“È §/’4û‹q{ô/ZOüfLüágl÷Þ{>â¨JƒÑ@ʉ‡µô+ƒñ™ë –Ï>‘¿ˆ6M©ëë"ŸV/¿üáu|jxéå—¾åÉãø_ý:]£KSS{ë2­Îý§!hËeôÊüWæõc˜ù?ÿ‹+ÿ³`>7ÆßJü-ó=·Ì¿ çóËýwíÁû­v ×šj2Ç%óáF¯Jo4Í©Ñå«×Ö³F§‚Šüt;“TšG»è?ß?fÅ$]eÀìjÛŸ´‰«ú’yÛ5ÿØ?¿…Žýç•|<:êÿÑ¿íÙ? ,j$â4ãÕ—æ?ý›ffwÿ7Æ6òzØØXZÅZ{™j›ÛËú…k{Üfwÿ;+ñÅ {í½wX G·0`¥a÷=vËö[6ª©Ì¿óúø3ÆÍ­ó_4RI:kÿ¥ÿeü‹ý—û¯rÿé®—œ?®JüŸ=ñk q+v¶eOö—?¿Wà*…u ’T ‘‰ KØë¿™Q…v›“€LZƒªE~å6¡¢dŠþÅ®ŠýA ÎÝéw%»I@Íër¶JQü¯øŸ»M¯2%þ”øƒ¸Sâo‰¿eþÉsh™+…SG ïUá*E¹þ(×åú#]ÚWœ™rýU®¿¦ãú €˜)Ñ’pÄlÌÁ€Ûú°e"´4;¥Ð[ဠþß+ežF11›Ë‹ü¬‹ê8ý{}û«x’eŠÿ!î@øO?*ñG£IŽ)f(³¹¼Ä߬ o`K•‚*–d™Jü)ñ·Ì?ˆ —eþÕI#O(#&fsy™³.Êüë5P®?*Úà,#‡Ø‹e€)×ÿPþS/ÓeeX]úƒ/ëelãF9,Õ29™!£ T2…Çš‚@±å5õg%,ò©‡¢ÿbæX°†â1ŠXôPÝÈÙÔ$EÈ”ø©&¦±ieþ1{ÐÔŸ£Ùˆ?•ù§Ì?bbeþ©zhF4djdÊü‰&¦9ÑÔcyMý9V}–øSâ9V¹þ-ñ·5JüUß³¹‰¨™™2ÿÈþÏ +g ·±‘`Ã/¤•*Ç2æSy"ÎåS½ÐDâE~ѱ¿âˆ•ÅRâÄUEÔ‹º<éZM$Qâo‰¿%þ–ø‹ÀQâo-z–ù§Ì?ñíÑ4e& Üÿˆ*œ>jÞ“²M$Q®?ÊõG¹þ˜™×y/iý-$žu§+Ê!@õ @rh‘¢k|•a®«üMDÊù¢·¢µˆŠáEkÌ6TìO5Pü¯Ä±u 5Š¡‡<¯”ù‡f‘cgÒLÍNô©Ì?ªŸ2ÿH0©¨!۔ѪÊõŸªBU£pͯJü¡Z²í¨UÓS‰?ÔS‰¿j•ÀS®ÿi·$KL‰¿ªŠù0þbìãè3©n!J¡ Ñ™,UMUϹ,CUŠX¿©Ø!Šü¢ÿbu·I~ç<¥]šŽ¼Tê71qˆâÅÿŠÿµë[ÎSÚ¥)þ×¾–¤¤©Ø!Jü)ñ§ÄŸvc‹ó”viJüi_KRÒTì%þ”øSâO»±ÅyJ»4%þ´¯%)i*vˆ9,þäOɼS°½ñ'¬Èë“VÃZª„¹,¿2h– ¢¿­pí„üSN9%yÔ‘áê«®Šbf­|uß‹"_-ÂF=§3cüg¥þ?ùøc±µ#82<òÈñc³×þ}ÿO=õÔpÔ‘? W&_Ⱥ'4·ëßBGµWsŽþµ]ó¯ÿßÿýaÍ5Ö +¬¸b¸çž{ªÃ„ܼf<>yä}䑨ßùwüUsvÿïàþ°Æk„WX1Ü}ÏÝf•±é.¸IL°È»Ò8ÖLJü¡²næìñO Ű͈ëÏ2þÅþ‹ýÿ/ñ^À£Äˆª 1C™ æŸüÆ{Á£ñÖþá/¼†¿üЬ¶Úja¹å–ó} Ï>ólùÆHàÚšk­úöYFàDD-‘™¤<Ì‹FY±haE¾¬¶í%Bx7âÕaï½÷ —\z 8×8ZVRžf¬|/NE™@m_*Ÿò'O™Æ}ðAX Gð……ÒXsf|e«ýŸ2mûðƒÐ½G÷ð…/ôÔÏú`5jAxûû¬ñ÷ :’¯t¤èüø¿ùæ›a™e`ßh÷yçž<ðÀJf¶ü¨ÑjâÈòWGŒûì½7|áR¥³rIyê|ÿ•aílü…/Uã$µ¬¤<ÍùS¦N |0.ôèÖ=,Ô¾0‹åKGµ³Mñoò”©áC´M|~j±Vê sªý§>y@ôªˆÍ·Ø"ÜqûíҟͶØ<Üvëm³müS­}3aüßB‹ýUíïQ£Ãç>·`Ó¤,ÌD¬º¬iÔ²’òTÆ¿£ùï‘1&&ÿÎ:xþà XU­gQ´*[ÇGÐ<Í=ú×7†´ÍÒ »Q!jÈŇí·ß.l¿Ãöá„Nމ2 {ï³7Ê·š‡zX« ,+)Oqa¤ˆOk³µ#?Š€…³87ÈX`—4O3K¾Iž}òŸ|≰Äâ‹§›Q5g6÷ÿO>_lñ°ÌÒ}u„b»fÜøÏ"ý³Ý-íoÉç v ¿Þ¼ä³yüó Ïÿ{â´·ÅÂ2}úˆÌjù*çhç^þ“Oü#,¶øb¡*ÌuöÏŽµc‹-º¨t“Å«¯¶:)[ö?™"‹—¥(4• *MÊF€JLÄI”Õ‚‡e%åiÆÙŸo¥¢L HŸ©òE‚WäS#N!>+è¶°è"‹‹Ú8^k¬¾ú\mÒ×Ý2þ~Àã0›~âøÏHÿŸõ?a§á ÿ8ô^ªwXnÙeC¯%– +â¥ÓN=]=™Êæôø;7êfØß„ Â1¦Kõî–Ç÷K.Çô´Óò`û‡¹P 3nþŸ™öÇ1ý‘øéRaÙeû…%{阞z:ÆÔ_x˜³ÆÞå—Öålÿ¨¡æñÿùÑA—]11úÏ©â?¦`§t¦`9ê¿›˜õ?Ú“^è:$”qÅå gžùûЭ[W¡·ž}öYQIå2*Nª&@l™¨Œ‰°‰°T8ó$ÏõRNZå&Z6 ãatNš¡,%¥ÀÆËRã1=ò…‘ãœÀ¤–dÌŒ—ÿî»ïŠœ?ü°®ˆY"?E¡ÔÉ„ܶqNèaz·Ô•FŽ»:­à­ä L@BeL'õÏŸ3À­Òaê8'03N>W„[)Kû$ÂRN‰­%–í\§ÿûƒN¾ûî¿DM~¨öfý¶tvöŸmã1î#ø©¥¥RÂÓtÄ?é”ëY0KìïÜsÏ ë¯¿~Xä? »}ë[Òë µÄRíá\èÿûcox”ù'½Ju¤AN@BeL$3»·”õå˜9öιçÀF× ‹,²Høæ·v3óÌ©‰Ÿ ý/u")9sŒþs‹fÏøÏÏò?7.l»Ývá|ò+} áuü!‡†=?4üáÀ5™Ì‘×_lZ=VÈ º‘M`æYû‡1ÝnÛíÃÜ'}ÔÕ1=ôC°aÃdLy¡‡;©O´cº´Ô€[¯ÀfNM} Þ -Ÿ ¤ªŽJqÆËRЪŸn ?}@hŒ¿8ôàCÃóC‡… à§éæiËy¼ëÌLý/Š7†ÁdzÀw /ÜM0­Wû¯mË¥ìÝ´ößúLVÿIi-™±ý7®³~þyv0Ìì@šamqjO½Öþ³Î‹/ 3õ§Ò¹WÿÚ•éÿÅ]Dmô€Âô\8êbnµ¿éïÿŒµÿ"Ÿ˜û›ú?ï¼óðA x℉?"þèG?”rá…áÎ;ïŒqÕÅö‘@YÌeÖ๥ÿÚkõ¼0ÿðæóÂýòP¨Mà‰mL$ã3äÂ!Ó;R¬Ÿö7/ê_ü`çßiíÿ9çŸúPˆ~úéÄO± ÇGá‡?þX4Â…ðÓ;’Ÿ3ƒå«‡Ìþ/þ”¦ÿ|ðÑGˆ‰æ?†»î¼KcÝ|ÿðÆnŸÄh€%Q˜F׈ïû3¹á/7„¯~e°Ð]{ÍÕ´u9$ਅ*LÁš7\àñ©'Ÿ ÏþóÙðúk#ÃÀ¦›m¾õÍo†®]»‰QóæÍäÿö·¿ 㱨çþì–Zjé0æÍ1ᢋ†„'žx2L?!¬³Þzaçw k­µ6jå#m«—Qa̘1áâK.–l¬}´ï·÷E©†FÖùúÈpÍu×b¤g˜ѣCß~}Ã;ì¶ßn{¬ÿÁ©L/¬ÛÂoOÿm?þ´m_iÛ[hÛ!¡mO`’EÛÖ]mÛm[ uýa|ˆËòimT™ï?s“þtýu^{ý5yCk5Ö «¯ºzØŸí}îs r½ï¾ûäFqüøñ2©Syü¯%(ýgºÇî{†å—_NŒœh®ûsÆç™§1>X+в6Ût³ðÍÝv…Üî â¡ýgÅßq|>ù(°ÿxõn)ÑñEÐÁ“øTìè`]ÑÁ.ÐÁšÚ6Ü”r<ï¼ëNðTÜþúøã¥YÖÿ.Ò¶=Ârh›hrìèÝ÷Ø=\S>êð#Ãñ'üFÚ#t€x°/ž¢YÿlÇßn¼!<öøcáŸÿ|Nñzë¯6ÚèËaË­¶þ£0•RÈ«!ë8Mød|øûßo wÝuWþâ‹á‹+¯öÚk¯°l´z¨ü‘¯¿®½îšðÔÓO‡7a›ýúö Ûï¸CØq‡ kÚ›t¼âSSÃßþvcxüñ„çžùg˜8iø¯¾ÛæõgðñýeKÚÄ.r À†|j¸ß¾ûЏVöçýŸµI(ýGóÌOEÚvÃCÛÏýmûôSé»è kyè/¬ŸÛsß}÷‡Û°Í3O=Z¨gøòàÃæˆ_üâEi)ü'‡3Î83°ÿ‡vh˜4ir¸õèúî»Ã‹/>VYy•°×Þ{ÁÞÖKÜ﻾@{ƒòbˆc×ñÇÃfØ„©8!O-ï¹ÇxízùŠÿùz¸öÚëÂÓO= }ýú-vÀøðSÚîÝzˆ¾Øÿ§žz2üý–¿ Ïm¶ÙV³¥,;ØGþ‚GÚoÿåû‡ûîGÛ†½€¶},“:›ÃvÐhÿÌhËÚà§h[ÿå¤>N8Tÿ{ü¿ÝÕW3æ6°È8|á7ôíš-Nâ?Wba~È8òˆ£Âo~sY^"ø½ïI©Qq>½óéÓOc>3*ûçSèG9Ÿ~ ½ç€ýCïÞœKFc®¿8ð3Ð ?ßÞyŒéšk¢%‚öNBüûÓŸ®•_S9_ñ‚‹I¯ŽÏ¶ƒ®Å`Ç'Ÿ|¢cJe#ÿ3»ùf›‡uñöy›þIÈøÃmþö;nïÙsá0x£Ã&ˆU+¯<ˆø§Ç'˜{Ï8ãŒ0r;ü0U·Þ¢vû^ù‹˜«Ôrÿ½þ[É×FSFÖ….Ê·vÄl‹ÄSù2¼Ô’³ÿŠ^ãøÏKúŸøéÄpÊ)§JŸúÉO°ÚwÇz”¿ûÝáI\_?ðàƒ 9E®ÿ¢D[*ö'Þ»¨ØIÔŽ÷®ˆª%žbÆùß$ÓSDÖÁ?9H×¼D®Æô·¿ý­Ü—<øÇôÔ°b3N¾õÊÒ( Eâ)ŠüŽâÏDÜûzÒɢߘŸ"Pq=Ê߆1}üÉðàC„S駘‹xxí ¢éä)ŠþMÿ|0ÎkúôObLü®h³GwúÏéÉN>õ$<³ØTâåœäÿ6ª–6 {BxŠénWßêÀ|ãˆ#Ž k¯½6¯!}úõkàf­…x&={öl¬²Ê*BsíµW'6—]zYcà A‚gýúß&›~£ñч&zHG9xbÚxà+õˆÇ(6pC 丈o¬°Â B³×ÞûÇë1zÔè$¿gÏ…Ï jE’þþ¬3E޶K.“¬uÐ×ýë=ÇM«š|ìlÒxðA¶­ZFӸ袋”Øš")5V?š1¤xë­·¸IBÛª¼M/KöîݸìòË„ûÿío;µ›ò+z†®|þî»ï–z<]vù¥"§UÿÙÏM6Ù´1®>>Gý#a|îÇø©Ž”×ìßø1½ûž»*ò~ïÆ‡z¶ñ'-cß»ŸØÿ÷ƾ×èÕ«dµ5úöíÛøàƒ¢^¦6ÞyçÑ1ëmÀ7ËB™G²ö.,to¼±¤Öú/r&ÄùÓŸJý‘#G_/æÿì6©iRö¿O?ý´±í¶ÛJ ‘þ€¶‰pÔ¿ëMj×}<ÐÀÅnlSUþʼn}C|›Îù?"Dž¡ ²ÊøÛØôëÓ·ñìsÏ%£Gg=ñ`uÖJ<¤N´¿o¼!õo‹©í`üûEÛ1†f;ÔËà7>DÛqò"Ç4ù™öŸcªºr3éðá/VÛþŸLï+øõ¯]èVÄœŠ‡©ÍF8ñ“ …†­@þá‡^‰¿gþþ¬†ßÿµ×^KýÇI:Uímá\¢ó©Ú¿Ùy¤ùuÞ~ó­Æ^SÔúoãÓ»÷’Ë.Ó¹‘ǦMý6Zò8çìh£•v5x½±d¿¦ºb¯Óëþ¤±Êæ<ÐZößb•ï¿É”y!ɲJˆ& ‰3ù¬¨xWj ¤Íci5š„:„±H( <®ÈOš™ãõ|ÌìË? á~$/á7V>nÜÒ±*…Vñ¸2þ³wüï»ï4fÏ<ûÏê˜b xŸ`cú>®Qª‡Éj‰åš(€ð¸2þ¦)Ó‹ÓŽ’N{üå\hóß3â§Y•éå—§ùïý÷Ç¥BgB6PÒi—Ÿ: ^Æ–¸¹}üñImô¶†ÆÄÔS.Å5¤ù¯ñÙùy©ÿÕÞ6çøª‚üÅä–Å¥9ó8pâ/ú£F £ßxo=)¿ âº-|󛻆›nºYëÓ¤ãqËßoÆ[ÃÊÈ/Ÿ|‹f*Þ â«O<ý$^Ϻ;ü¿ÿìg?E ­§¿˜“kþËõXï4)Y ¿nºÉ&Âù_ûê×T ®‹q1&‡^+j†»G}c“¯‡áÃ_ù=ôpøÒ*«(!˜_uÍUáÿ£¯Ïò &þ2ÞµkWùµ“;é<þí½Ï^øUŸÊ™Ôf»øÆËŸñKòi\˜ Çk¬ðE`iÛ×rÛ¤_ríî´J&l&u%Í5ù7°ýñ‘D¤ìØcŽE_¿ƾÿïðÜsC_+ä,¸À"÷e;`QðŰÈ.¾ ø'ø!~ûÄ×\T/}¹owËM9~|&O™ôíŒÏÝwâí ß†ŸÿüçÂONÒn2ÀøüYu@õ¬†…f7Ý´¦´o^ñÕ}ÊgÛþÛvÐÁ?qÍÒõAÛêö7¿ÄŠH9ì.Õ3të"ßsIô¾”…ÖC…;Úþù6Ú×¾öU¼Ný‘Tü•Áo€P'üæôš«¯Á/iOH»H æñçÁOfxlºé&°5Ã5×\#cC˶Ýv´m…ü«ðëþðéËÀÃO…½µu ×á—ï[o½UÞ¶ÂÜpÓÍ7FûÀ7V¾Ž¶}„׉úÊ௠mÛà-›Ø6È{âÉÇ!MÛÃZ 禛7ß¾ñMÂK°©…¡¿‡|(àAn²4é_ÞÂsöÏvPÙÿ윯¡³xp˶ýƒ/!HÿY‹ß»_rñÅRƒ6 »íºÞ /oŽñ*âÈ7ùç¾w ¾…ÚXYå}z`77A Xod]ãí¬°u†l·ÝaÑEuqY¾1ôì³Ï þòb¼È§aé³t?Á‘1ÇçñæÊÁ‡¬ñàšk±ƒÑmáI¼!ÄÞn¾ùfiÿb‹.&mÀtÀC7Yø7°¨Ý:è Ñ y]vÉ¥¡[÷®‚ßoú-A?…ü;à[|C`P?¥VYDL[èÓWÌöúçÛ,µo®ð€oØh=¢ÊyŽÿ¼9厷.lŽJüÎ7爛›„Éx+ƒÛzÛxñ—”«ðÖÇ{È„ßüú×b‡ýäÇxÛ ‘W«®wÀלå¿Tó¿s‚Ø9;óýÿþï°Þ¨zoÃ}ôÑŸý¢Ù!ßò¤˜¿\y!ÆÞÕV_UÞ6£|ÆÞ¯~õë$“cºçŸØV>ýôÓCÏ…{â ¡}~x·à- Ûo»=¼±>ð;ß ?ü° Òþ)ŸöÍ#ÛèÕˆcwÞ@ÚvÛíС6ÄCØÎµ×À·¿Þ@,ÿ1^ç[2<~|Гí\rÙ%¡;æ¤ÊYŒ‰j,iÈ'{Ý@§#¯g©õNý‚·»xXéBè[×6{ƒ‡×’ÿþŸÿ÷ÜsOxeĈpÿƒ÷…¯†ß±ƒñ¸ñ¦¿…0Æ<öß”i]¾ÑùÃüàŦÿt ×]w=âÛßßžJþ#m'KØŸ©Üæ2à›?2—€æ¼±óu›OQvÄQG†—ðf&ãŽ;.0^ðþû27ò5p™Œo ¡m a÷Kï_;»^plÔ„§¥×?¼¸ø’‹DÎFn(k}Œ·^/†€n¾¹ë7å³ û…ÑôG;ø*æk›lò ε'®B»F Ž6²æyE°âªc“/Ä8ÕíŸÃ¤”‘ÂÆ˜´‚r¥VÓTÂ~3SäG¥™Bòø‹*©&¨³ÿ¹Yÿ£k̸»0ÍžoHÓO×Á‚l°u;ŒÆÛÌ_´ð<Õë{r÷yÀþG™æŸÕW[UÆï±Ç8¦]1¦k‡ ×ß0)ßPÿ…ñ鬙û<ЛØIß4Êt^ÅÎeñ×l9Û¿úªqLá§]pݶî:ë`Lñ¶à:üÍQá?þceRÏ3ý·ûéÓLŽ¿Ôµ Üîµ6Âc=x=µu½Áú¢Wº ¿>Zø‹ˆ‰±†$fcÈÌ+öçõ¸æ§E†:âH}c76ƒ>&ÙÖøÕ¯~ÕØiÇiž[n¹YRÂW_sMb„-äåWc|:‘päÉ_7ùÖéùk»È1a('Þÿ]yÕ•x2™ 2¤lW\Qßâ[,ã/¿¸8å^£çB=ñ$ð%Ä™åï¾›ÑçÛãù ºcúë_ÿ:Éæ­kòÉS<m„!4®¼òÊÄ›€c£ø¢–Á¹i;x¯‹_êí0ùx¸qÏ=÷º)=ô°Ã¤]ÔŸŽ/ó>;bÆçìs}Œñq:> ‰8>RÏéß÷Ÿr®ÂøØáØ(Ê!;ìð¤W£g*$ŽÎʈ§ wR;£¾Ï8ó ßT#Mi›¢–ml¹å–©Mgó×cG@7€G}$ÙŸ¾U Ñ@ôŒš$›mÝlóÍ“þß|ëMá÷¯½í½­±ìúõǯœ½áaA*ÚÂÚ›;묳ÀKgM|ø!´í‘G=þø¥Ÿz’7†ƒ7œ ²7…n<íÙW~ÆÔx”Ù‹‰§Þ(‡ãÎ9g'ýXiôfú¼÷Þ{A«zûÖ·vkàs$é½±cØÁJx!(É7,ÄÅ©ŽÞ?6þr½êš2¨k|¢uPŽÏŠ„ŸÉgŸt¤r)ÄÉ—îw1>ˆìËFxsÌÆÇh}¼Æö÷Äóþ/~ñ‹ô–Ä 7ÜÐÀ'BI/C.Ò$Ðê?¥<þ Î ¬QH=ŠúÇCõè ªÇ3Ï8ÃQ+èëƒOE;Eÿ¡þ×Nëÿ—VùRßXËeÖ¾O:QÆŸ®IL%ŸöŽ·ñ¶”ÙÅÙ°‹J'P‰í[}õÕ¤ßßúÖ·„´'Î)Êý1É÷…ý¾.þ^yEŽ;Ö_gÄ«Ó7ÿзM>ßpÁÃcÛ˜LUÅ}¥ôéM¼íâÛGÛ1ßð×0Òv´ÿC† ©Ðzÿ›Œ7yvÄÜkz>1ÑÏ_p!qtÇ’<|ödUTN¤ÃZinæÛbv˜ü­·Ù<Ú|³ÈŽ߂ķ >¾/>iŽrÛšâõßÒ«Ü|êûbòJ,k“·ØL¾¥´Û{⸮¢D ÉówÞNzàü'rœ0U:f´ÑI“'¥ø6±jµÕ×úÔ?ßðâÁ8k} ? žÌ§Ò0/Xü{óÍ1ZÏuùVèš”ä³ÌãÖÒ¦²¢–•j‚kQàQ¦Vðx“kiSY QËJ5Áµ(ð¨"?k#C¦õœ6•9ÄI',6ºÂ ý¥Â™gž™lö–›oiüûßÿNñçöÛo¯ŒsÑVd†²Þ j*«!jY©&¸ÕžþO:é$¹þÀÂë Œ©Í ·üc:cçŸÛo¿£2¦•6{a±À£Ú“o<,õuWCÔ²™¤EGÍOò9¦œKx-ÏãÌß«Ÿr\9¦cǾ/åÌÓOýáu&ø¢–Í$- s5|Î}®e•­ŒcuýŸ®ÏUï:R‹TþgÍ?ò ãAþxû$³ŠÇ[c©ÿgá³)X'Û(KTò‰ñ"åŒãþø26ÜHm§7máÙG³Lë;{„‡Cx°ÖMT&(ÒŒ,P0¾¿g¶ãÆßW>úh}`Åqä…‘ x}äë©ÿXß(UÁzT ÏÂzdùæ?¼AÑøf$˜K¢¾©Ÿëd®g™ÖT•¼Ÿ±¹Q>ÕÌÔ eùÂϱ|çwÅþ(S^Öªúþ¿öÚkùäÅÀÔ mø‚ ÀÛ?Â|©ŽµBæöüïáX¡Ê®E8·¢™Åú—ëxØßzë­/ ßi'<(Žþwâ‰'bžÈþXýÁÓ7ÞÃsWÿÓð0‹õobS:äË55â?÷æ±óN;Klblã˜NÆI„ùçÀâ _ùȹvR;Içfƒ ©VDzu¼Ï{8Ò·@™ÃiέhæPù6¦ëG?Ýyçøƒ|õÄOEmþ¹òÊ+Z÷-vxnìs‡üàyøÿ>þüž¾Áç<°žaòÎÙüAÍü‡/¨4·­5Êèævýãà hG^Ö!„K%Ìàè"Ü”`1Èõždür.¯Ì€=ºàS ÖCêR1ÿÂöé\`r}¼¦Ê…ŒùÍ6ÛlûÇãJ0†t©Z•ÂN»ìvÙeåE–Ò¦8“I =ñ³J{Ÿ¸}íë_ÃçcúªyÿúcÑG.h‹RÈ7ð ÜSé-êwÜ)ôîÕ‹(÷¸± KõîE“ׯ\´v>ÊN> wÜy§Ø6°•® Ô³v2#E5ù‘…Zjyëÿ/ylè‹OI(ÿ´ÓO k¯½nXŸì²ëÎáÒK. cÿýoéJªçå{xäsÛìÓñYÆúxe®;?$ã³,&, Žƒß¸qø”©Ö¾r·Ýe—]§¹ÿòYQâÃÆµî¿j„ªS¾B9`¥øcQéQ.AGgõÿ,KŽ•±`øNØ?ÚiíVaÈ6‚Ÿÿ\“ü5𙓵› ⲇÏ<õ ΰ[ðà"Ó½ac´·¥°p7\]k͵´Ë ý†ÚÞr“ñ'ƒwÞ5Ë'Ÿ$aQ£40òiÀF‰/ðó1Óg „/$rõ/L­ÿà†ïž¥ÿ”»óλTå³AÒBi”Èf}~šIÑ}—é–[nY¡òòqƒ,8>Rxùå£ÁOÉ„M[øü‚Ô5‘û¿í•4±Pm>”. 7Z½EÿŸÆ"ØF»žïÝ{iüõÂñOãö&„QoŒ®èŸ ê^vÅåRoH¼èÙó øÌä|kRÔ&6+ʧî„g´'›ÿ;b¢q€’uq¢/¬4`@Xd1ú‚•ñTì?`Êè‚OàsÙEáglÂH+ºñ·fPÿ¬Å"óÿ6Ô§|%—Vks”Kuü¥8Ê3¥Æ™Ñ¿É&ß:·áJ\›E.!üõ¯ÕGJ Ưڱ‹Q9‘µ…°¸?cõzæ¶ÍúE¨|¼y§mÁùå¯ä2ã?=Öx u‰ aM|­P[ø'xýsÑ×Ë/û£Ð½ý.l¯5/ŒÅØÕvDYR–ûOœÚ??E¢,¶ØâUýK ¥#h² ð³Ö+®„:ÑvÜø“Öìo¿ýð‰óP’6ù—]v©Àüœ— æÛñÔ3OG9ˆo;íˆ9~ƒ¸&1±nml!íF³F|ê…FEÃㆠ»àO¶ºuÿ97öéÛ¥pªÌë„E—X,ì {¸ôÒKdź©çÈKç#KåK±Ba†ÏZÑ7žxñGŸ~Ë V-”¬&§¶°áF‰|V}ùå—¥ º”ihüÌ[¸§3ça>ø¡Eè|ü#Âäk]mg$¬È÷%RO;¢¤r&EU¾d¥"Oñhgü‹|êÎkyQOyþ15ª 9½F;ay…ÄÒÑÍbý/±ø’ÒÛ¹öÛßþ6òm?f„]±?É´ø·dZ`?öBšÍÓÜÛÒÙ§ÿ™!É%{‰‘»ïóí}0F Ü?è˜rÛs³Aÿy«ÿ´Î¹Åÿ¦uüeLѯ}—Ý û쳯Œ!ï wÅò X«¬ðKÚy­ÿ5i§f«è¨€<ÍØø“uýžÌ¿\:€2¨ë]¾¹3v‚ãgó¸ÆYc⌕¶é˜çYcH»oÊç3Ik3Í,sÙ~ûî+ëA°üÿýçî¢4ÞPy¤3ø¶[oÇŽN[¤q^kñlеxú÷ï5ž c½©'HÏ•VX~EeHÆüÓS<3¯‡Êm ÷Þs/d§Fc ¼©N8ñ„LˆÚo¿ý¶ð%%^+~z Œu‚®äènkùEn!çTž‚Õ2žUºÒ¥¦HÛÐ2Fk½ÊÜWYeeìZõl¸kÈ\ˆî¹÷ž0¦Ï_°¾Æ_±ÎÎBØ mˆ¬É’%+_Ê×68ÀDJj™nÅÚ)[m©;n±ÿøœ'lºù&¡ÿò+àFþ)¬/rÙãÇZ]bWìߟ‰ųÊn§ÿx€çk§µ(k—·œ'pòÞxi^ÏÊw3]ñ ÏxЉº›AÒrçK®ûÄ­hÙá6ÞHl‡e_Dv”´d™Ú¡=×s]¾ÐhQêƒÕSÚÌÏz·üòËcçËd—ÆsÏ>7|÷;ß•Ï=÷<‘ý?Xês¶Ž˜q•x-0ü' )Nþ®žÊ×ø»Â +‚,Ò#~¾Â£ k£­Œ4ŸÆÎ×bý·«±ãé½âw×ÿù¯˜ÿ¾çÆÝ°^EËáä§Æ ŒÅþzÁäK¬B[±ŠD¬å“ß"\§Çç£>–"Ú‹Ž+úE¬ËY®ã ‡³+œÉ'a{ý'aä.ur^±ÊS9µP©]ä« MŸóºþû.ÛW:<kdñØq§°‹èDY³± ÖÔxiøK)V-Ó§ 4j†‰€šXáÁ“ÇûSˆNš,i‹TÍŬEì´û_ŽŽWF¼&)Çtv†íÚ¥‹Ì=/½”Ç´O?¬[X›XÉÆs^·oÃì÷ŒÐÖŸjQyþßâ/êqTF¼òª4KÈn¿¼·àõÄËS‘ a}ðPW%Ï8ùÓc3£ÿ³R~Ÿ¾Ëˆþ¨kŽØú1± zæZ¶/½ô2º¨W Ôu.?oê¿‹´ôÍÌ ‚’•RÕ@|w<Õ]oš`^)¦òô"LüåN ¡Öñ‚_’7'N=íÔðÃþP·óŽü¹å¯°Yr`b¹– E$LIj1üÇ…K¹í¦,^Š<ÖÇÀÖØ·%:Ò诀@ÿ+øå—¿à}‚?¾á1Û×I:q¼¤‡b›l/Ÿõyð»D¬6965·?Roõ‰ÑðO(b…¥ò]bñEÃ}ÿ¿pá{wxë­7±€åÕ²…5)¹ìn.&g‡Éç„`ö•6ÂÉçøl…mØY¾á†|b ovêé2>믿ŽÕŠsIìTL™u`ò‰i¾¡QV]xgÒŒ—E¥Õ^ZõŸºBÑÀ‚Ÿ¯„W^Á¯ôµfX#§E>ëzý/ÛŽ.ü/}.ЍË7Áé©nãom2^nÙåù/x9L;ƒ­á-— isÙþ=ôp¡]–“x<ð) ía-1’ˆ6}*š hó¬úB'žpb¸õö[SŽì¯½þSP¿~¸‰ý×¶Yë²ÿ‹ª2ê·ÿþBôì?Ÿ•èYîåxU/V‰] î%ùˆ§öú<ÿÇŸãEd$íÒ5 GmÚ›ÐàL¾¾Á¤•^yeDÅÿ5&06à øÚ‚ãQDÀÚ3XTwø¤þŠÃ–òM˜sÏ=—ôHlg”ß…~Ë?ÁöõÂOš«mNý—,K£D$¼°9^Ü ßTÚ¾þYgÄ˯$ù¬gý×VEþñ[[\ø[ùGùH¬ýF­åÖºªý™ý »Àú|kŽsûÿüCìS poèk_HÛL?Ê]*O“ü1ÿHÿ©{§ÿWG¼šäZyeí…‰2Ø3}$àã_%LLÕþ›>¦L™ø«=ãyd‚…šoÛéhüYŸ"_ŘJLLµÛÖa¥W^}œˆ£8ªã/;…ÿþïï‘D]þùaXlû!¼-ÇE1a?<¸“. EßXÒöÐOÌoR¬CÌ›ˆ7¦9ìXKå«~£^D×r’¶I™Ó¿T„àÅñpù{ÿõ_Ø!ÎØ `w,`Nù|8û­Ýv“…"sý(uI“ðÖñ‰Ø(¿ÿþ(hƪ)S§€œub=$#^}Õ*"V}IØ1þo?þÒH"CéZÆßØJ<1ýìñ·6J<À\¤Í©öŸ|[ÉgWCxDmœóÓ.ŸufTÿ‹|Žõß0àsnPBý÷À¯öcÖR”Q¡>ûö]Z Å@8ÚqÄ« IåPtÇòÉÂì•Êø«¾’VD×rÝÈ@}†þûñaŸ(ñ ?þ‘¼¾ÌàâÓÌè˜Ê…¾Këæ.š+úŸSíO¯¯Õ£8q€»wï&…8Ö??}ú,#éÔÅÿhÿÍ¿õø³ì²ËFwÐùž^Á¯ºñÞAüç¡XއpøÚA}-%©l^Õ?žÍX×ÐW©b;¨¤HpРAX1ujx#|ƒB(#¹ÁX×*ãS¥ÓÃJ+­$?@Dêðï÷ÆB¨!‹æ\‘šip… ¹fùDEqR*L€àC¡{p±¸ÖÚk…ã?^w^wá[GI_þò—µ>—\|Iè±À‚Øå«GX;}õÀ¯ÿ ôX J^¬P«|i ùÈhŸÚÃÒ”QØM¾ ¨c"ä¨ök«keA^Ëßm·o…ËÿøÇpî9çhL€ü¡Ã†ÅúFÍׂó…‡ÊFMB/ÿч1>D §áS¿+áí,ÒÄÓØ÷øJ>¶Cÿ¤  Ä$¹9‘ÂZáš.X;-ƒ×ót¼ܰ3V{ý×F(GÖå§ +®4@lèØcv¹T²QdÄÖ •¤ÒÊøÒ*«,º+âN>ùDy˜iýO}AÝýë=œÍþ`­äßNÿ+¢…Y›½¡Ú¥_ {£}á¯;l ©·?y`€z«~iU±gÊ=ù¤“ÿßÿ ¢“ƒ-îÁÕZkÞ¥¾p÷]ðìxü¯O;òq'0îØ'ÌÚ±¿Š ÄÝú¶áf¨'vFc÷°Îô6¶¥þÿ…Ïí¼ñÆ ¢Òµ×]×$ÿ⋆ÄòFÀúe€­‡±OmñM¬ˆfÕ+õdL¬N¤á.wr€ðaø‚·ßÿð†ã-FïÿŒ >ðãÖµ.¸pXëN9å}; î‚ù¥œoÅ`ázíäåó“:ó¿±ÃUÔ®öI:fX›°Rì¿ÿ~ðƒñIЊá˜cŽa¡Jëøª(åüJ _qÀJ»ÊA­ÆŸq ÿe!¤þÉò#„lMDÌDZ’zÍ'ëÿ!‡*5ùÉ¡6zÁ à 7üUx]rÉ%áó|[¨"Aå“£@È©(ïìücoåþ'©áì¶fòWµ.E`l˜µ™Dñ¡¤>‘·þKp'Ÿtæ«{dÌ}ìÑ€¥AÔ7ª¸c]>DªÚFäÍ·ªVÀ˜r^¥(:Æ$×Wh_ÚÎ +!–Â~yŒ6HŠ8nÒR=Üró-ås¶œ;3^…?ßÀ§Úœÿ½ˆ/y#í Ê/Åø-° }FÿR¬C¾;ðݺÈËÉ b*)ÁºüÄe +wX4 S¾™·]¹ñÎRÂ077FTEÿBå“¿—¿1â]\ÇX%\#*^Ì7icü_Ÿ òÐø£”©R¢'k—*8ë¿Wå+Mª ÂY>«X)}{EØÁøö1æÛVHBw˜ýY[þ~ûäp®‹Ð|ÄÖ cL¾dgPÿ#ëØž"_ôÑŽþ×]½Ð‹Ÿž€ˆ»eÖãÿþð±ë­·Úó»¾§ú%ÃÖö§åfEÿ¢vô?3ìk Étl­‡+ŒŽiÛl½uøv¢¤§ËQü¯Éþ©ªezâ/Éy¨V£nc¢%ù<­ã¿Þºë…ÞüD?VîÙÿ°§øéVSÞóò˜‘ògwÿg¥|êzI,!ú®ÿþ ×|[s7hêº!žgõ_¼ôh±¶ÓGA›nlòo$OÆ[ Wù»æšk…[}ó>Bp?úѰÐ&×^}µñÝï˜èYï],™06^]Êw@IDATrHB `‹…ÆËçŠü¬c;1Y¥ç°hªñÚlóͰøõawœ¿ò•¯¤²ÿ=îøÕV«¡;àªqÙå—«X& bH½¦¶U)›r¹½*¿‰ "ð«¬,èÍm±yÚ!‰Åø^‹ýîšÚ·°šØpçëó.ßܵñÑG w㢗ï¢ol‹ŒO3ŽwtáñÚ«¯5<°:>ܵIލã·'? gÊLmƒ]°Ö6î@Ä…š±Hu`Q‡}¤<ÚŸÉÅ/Ò•ñŸñ޾ª\'¾k¬¾zãÁ”]h¸èn.Ħ¸À:ÞÎBÛ¦Ê.DÖŽêµÚŠ;î¸3ñ{à¡SÓ‹½ÉíFã¸_9{ì=öØc¤½eCi4·íY„œ;Má¡`lÛÂÒ&„O2D~Ýþ‰Å|Õ¤l3,"OŸmy8ùMåÎÿ~™ôÖ†]¦¢Þ°ÓØ,Š;ìym¶ÄNmûÉíÙsa‘O<¶}—6àÍìŒw¶à9ÆÜiÌF(íJüù\ ØÉg3︃¶®}â¸ÕÛï¸]ør¼vÁÞøì$Sa—DÚÛ»f×À2˜ýïÿVLJ;m‡ñ¹ü²ËT„Sþ£r!~•ÏEßy¼±ä"Ô”Ù»%~€Îydµj︛„ÙÑ®»8_@¥m>& ø¶:–â ÇXÚ:1ÿQzmë'«‹ý³&mœý?êÈ£„7ôx(p¹?R”N\Ø×êù…}3ýÔFŸ~}…†óŠÅÄ &”Æ÷ƒqú´ÒjÚ™ù‡>n2¸KÛ¯¸B6F žõû¼[ÈB­”8Æ-Þ:äxüàƒ¡†Ž¿ÚŽÆ4³wÞ~»7i‘7ÛI=‹öO>R;ÍþØÆŽŽáGuüm^nYÇéÿ,^J}Оmqòk¸ë(h™ˆÍói[£î?œO9ö˜Æå—ÿ´æÝÚZÓwšKêŒ]§F½ñFšÿ^Ÿ1ëܨ}¬Ï™íÔ>Mú“( vì½÷ÞK»Ò/«¦ ^r·5‹Ul7w³”Ÿnkœw>bUí¸ÝÅ*?/ÔÈšë LÚÈþ¨oÇ1E{ئöâõÿ%,>nãoº¯Ø:¹”c±Ñê|òöæª ˜ÙVÇ¿N×dXž Ö_ôYp‘¯:‰>¯Ëq#$‹«2Û5>Ç—ófË£è_ÕcÊöÔRSMÈL?ãíßo‡C˜›¦È¼zxÜMšcz[Ú½jÆËoêl ÄÌì qM¨¹M>>¶X{ ü”;Òâí[ñSÃ×w$kê´CÌmýwMÏàLŠ?¦kÎq—^‚˜hþƒëSÓµìH6“äçÊUAKf6ÈÇíhŒíJ¶É&›4±^õÁîJÆ‹ »Èµ‹×^½z§ ¹yŠ/ˆ½|›¸äbQ”áK«M`I{†Èôì³xÓ©O'œpBªÌ‡,Ûï°]xÀ@ìfæÛÅ‹aÝz7Ë· $»Í%‰uXhë XI„°sÀUiâ6YXKn¾ÙFÃí­“yX=cʶõ·5¾CŽýlk`¡i!eéÕ{ÉJÿ9>RãB=˜,¬!aì%5¾‡âÁP]~……ö øðÅêsœe'ô‹¸ÓbÛ<¬'‘éAÃ1ñ7“Ó#ßóU“$ì‹7ýÚ¦x‘íÒúϲËâCóȇuùú°Bû#+bÿÅÞ¶ß!é”<"0Ï'Õ‹tß6åéÛd°µ}ªø‚é>6’»DYõ…X€$B-TÄ’æRºåÿw\elT‡ÚNƒ/Ã…ŠU6ìùw_²2ós˳ÿ¯É.@Ú nne­nºï”›­¨ë› ñšú‚Ý<µ5úqç'èš§vª B_° _c{Œ”Åñ4p bXöÞ$ÒoxŒ÷akÝý@Ð}2~¼òÂù®»îJ2öØ~Ú¬>™èW_C·m·öôíýò“/¸ø‡7Òø±ŽÄ§ø@7 ÷ä>óô3hKîÕbÃàס;ê(}0D{5[6l¨pÅçq gbê]ãCëc¯ô½F„ϧ”&êy…þýÜ=ñ÷ëØýÛû~»q!v{šŒzç­ È—m²ØË6ÔX[³Ä¦wþIöÖÂÿ­O”Ï3“œÑcòŽ‚M6ŠÆÝ!êUÿFåC7ñSÈ¡íŒÿx|ê‡ØN”¿'c¼J;0]0¥]r‡³¦ƒÊ‰ÏJÝIËêLü4>L ÉêÞ,Lciu*[Ñ£‚ÕÓø¶}…MÄkí?ýÛüGeMÃ-®ÿœÍ&­ÿXˆ:ÍÄñO|ŽbßS#U¨üð#´Ð³<jÑÿçñ0´«"o“O¿ŽXeýO¶ºdN¾Æ*Õ¿ÙAÒ^ ù±© 1& “Óo%>y;°ªLãQ©ƒþkL›˜úat)…ügžÉs0û¯r>Ç:UI@ ù©Ì×]»|:è?Y´[Ï ­ "¯~е‘´Ëg“¦XÚAý4Ž¿ù ç þ Tyt0õ¿jóÆø¿ïÇ”þ‰5ò˜†Æ>{íãHµ÷Éêç#û¯j`ÎúéFæ§1ærLmþ‘~á›±ÕnYŽ…ög¸J:çö?5sÄŸ÷ÿý~cÑu¼NÁ×U]ï%÷‰©M˜×õ7£]Hü[SQ[n¹eTI6("&Ošœ‘n/¯d/¿<¼±ñÆW”Ì aìºÒ%[tc °ÇرcQ! µÀÆ_1ìÈ¥ÄTåÛvõØ!ÍÈcª7þÛm¿}r(O0éÓÉŸÿâé"ÝSì|ÓxìñǹÉרÐ8òˆ#S3¬9‰8ZËêZZ+ŽÙ\ŠOpð ë±ñ¡.&ãÛ‡×ÞX7 7¸0³*–FN¼ ßž}võPØößüæ7IüKÃ_nl¼èä–Åc“(VÆÚ5Ðg[ãˆÃãøÔä'±–3Õ¶Åpðàø³_=^(·Í*€ž“°‹¶t'Ÿr °‘Àè,Í‚#¤Vli"sˆk®½º±öÚk'9f ›m¾…\é[xˆÉ2öÈ…C”•"ç =öØ#RnÅŸâF÷ç?ÿ9ìMßê‘öØÿwÙ©ñ¸Ù›Uˆ ½öÚkkÕÚFù|»æi<83uÜ ©/(#cÇíhiö À׫0± Q>“**æ\7°ëP“Þøkú3Ï<%¬.SìØØzëm"}ÿýدÏâ„Þ„©®Õþ/2DËG½éÖ#¾Ñ|L{óþ¯ãÚ&77¿9>?(f'Ùøàm êŠ~TS?üÁ ÖÉi}š™ÿ]Á-Eq˜ŒøÍ1oáátõaå2þïüÔêò¡/^¹ÿSN¦/à¨3V¬X» À§o©Ô×£ÿQö/ùKáÇ·øLW|Ë€"ø`Èô"LL®¥@r æœsÎjIöì³ÿL¼Íÿë Ƀ¶jEð†”øÃ8\Ñ&×R#ŽéôÎ?þæžó‚¾“ã/o¨†ã­­¤pȵxÀvaC€Z ¦¦x@ý?môÉvÚØ­/ÖÉ8îØüÀõŠ+¯’ò\ʘ8¹±Mò!ÄÄ“O®Éµ¬Ö♿8šíЦ%Žz¦Þ~=öLvœçãH`t1Mþ˜šýS7Øq¬2ŸZ§z.¼°ŒéGÆ1eñ"=ê]Øß>ÂN¢ÉÎÈŸü1ƒ¿žç´–ÕµŸǺmØ®þœ(ÅJ³ü‘1Vù~PÎ~ûï×xúXµS’k–¯D-H£`>øÞ:ƶ«âÛŽ§qfÊxàãï)§Æ‡ã©3 ² *ÅÞv8G{3:K#}N´ÀŠ-Må„Ë8Ðh«¨˜3¤¥FœR-°bKkÅ1ëJh´UTÌÒR#N©X±¥µâ˜u¥4Ú**æ i©§T ¬ØÒñ ZÎçù—ñŸo-ó‡²úø“ÕUÖ1gHK“\´ÀŠ-µÒ*SWê@£­¢bΖqJµÀŠ-­Ǭ+u ÑVQ1gHK8¥Z`Å–ÖŠcÖ•:Ðh«¨˜3$RÎ×:¦ùAñÂqLyÝÇÃÈgáJh´UTÌÒR#N©X±¥µâ˜u¥4Ú**æ i©§T ¬ØÒZq̺Rms†´ÔˆSªVli­8f]iuL÷Mó/c<ïá8ÿÉØYKc´ÀŠ-µÒ2þIÇÔä?¼Þ<×bTvUw1gHK3»i[šÈ*—q ÑVQ1gHK8¥Z`Å–ÖŠcÖ•:°0.^azø#`)qíJÒ>¡•|ˆ]††>7TÖÆXb l™™£pâ ei¢m”¤}Âæ’:&çÇŽ}ÛÜ¿–Dû–뿜®… i–k­yÞÒæ’:&çd@L¹0é«Xôqâ¤É¡ë…5dQ«`RsúÑÇã±è÷‹XeýÓ€ iÙ‚¯[·î™ r Œçž†u V K`—©=þY`n+‚ôͶM’u"úôíºsý#±•ñxnè°°4¶gÿíPGh1m.©crÞ ‰X||Ø ÃB÷®Ýà/’…ÈjlSVëXÍ„N@sIÆü{ì¿Ã‹´7|K¼¶DîÚ­›¨½Uÿ!b]©¡Ò&®óÑ­{÷x?ÝÚP³4ãPÇä|‚ °Ôª¶H•DÏlÛsh³½ÁΚYd mÆ›(X$±[ÀÃÈØ1K[È5”’´OèKêöƺÉzAª»DŸ1>K†åãø˜\K•4Õ4tJ›Kêͳm/¾øR˜4i¢ú)””µXZ +õ6tèPlŽíÀá ú<·!ZaÒ&OAÄߥà?ÜF<F‘iS¥\”Ék’´OØ\Ò¯¾úšlFÀà.¿ü²°þúnŸ<küNÄbìø”5üú7¿Æɺ9¦¥šÔœVùÓ:ÿpºe–ÁbŽPçç_øÎaô¨ÑaäÈ×à•W ‹-ª;çY ¦U¾Ñ;-GTÖP‚ °4W®@´ƒç`Œ‰Õ1Ídu“'O‚í sv)d€¥™]¤$­ ¹¹Á /¾ˆõ/0ŸÂºÁÏgdÿÙ˜q27¾&A}—îz-Õ+Í,onY“ó 2ÀRðáü3tØóèC×0`à€4'(‰#¤Pw4—Ô19Ÿ ,uüê I¦pL±žÒÒ½––þ{šfбâãA¢5ÀÒÈŸá‡awho:+IÐ5 ¹¤ŽÉù`©ãW•¤}Âæ’:&çd€¥u¡.¯$í6—Ô19Ÿ ,uòê ’´OØ\RÅ0à-^YlÀÀA f§©’ÖEK^IÚ'l.©cr>AXÚR²"•¤}Âæ’:&çd€¥s™|¹®Âf\'1iêC½S9Ÿ ,Mu›%iŸ°¹¤ŽÉù`i³Ø„Q’ö ›KꘜO–&iÍ€’´OØ\RÇä|‚ °bmþYëôqþ±‘•ÄÖšØ\RÇä|‚ °´ÆÓg•¤}Âæ’:&çd€¥^` V’ö ›Kꘜ7/¹Èýw#8h`ÒuM´dµŽÕl¦h.©cr>AXÚÌ6a”¤}Âæ’:&çd@L±–rý ÜS°-qÖàã#? Ä(µÅ–³T±õs* å™á˜ò(òEÓEÿÉÄ$`f)b&)WÅjY>§RR.Tø=oÚÖ”ÁÎkÿ̯%Çõûðž{í¾³ÿw“*ŠþiÅþªÞc9KUGvÆ'ŒŸ«…°Ðò_ÿò—¨¼*í©§œÃnÄâךXuæûß›Øýq™¥u—óÏÃ6írg.¶‚ÇÌ—¯rìl:)òU#óžþöÓ£°(>w˜‰cíÂ-øúø»çž{…ïàA¥|L`tLq˜¥TsU¬–ås* å2AâÊ2óžþE}¥ÿÙþt ãÙl¢Œ¿*¤Ø¿Dšrÿ‘Ba¹þUϰHQÍU±1¨¤$• r©<Ïj,ãQüovø_7½ð$Óf-.˜S°$ÿjݪ>ºƒgNú”S/vÈY¸ƒ\ù!'U[Õ·vDIÈ Æ¡‹|Ó›¥ª+žyýûkÏÿž|úépϽ÷ÑHà£LèX°#I"¬™0xðàt_¢N(æÐìÎRâYWbÅþÌþ¾Ðs!1 îFvçw†¯o²Iè*†§“&`w²›Â±Ç+v¶Å[6Ûá™9ÿx;æûÛzXÊÜÌ•Ÿˆ`“ki‘?¯éÿ‰'žBì½WMü3âïàÁ_)öWüo¦Æ¿Z]¿”ø+ÓQœ“fæü[ì¯ØŸN†z­£vWüovúŸ¼1äÍ2Ýëe ·¯ÈêóÝ#½ÉÔ4‘ABTJqÊ$ JÜ"g”E~ѱ?yÖ#~˜\Å$!t÷1ܾG*&€Cï×6`1æ°êj«ia‹³Qÿ+þמÿa±æ°Ñ†7F ÂbØaùþýƒ¼ï¶ç6¢·ÝvkXD>ßò- /¢þ¯öG?èÃOÉpœwîyáÀïȸ ºÃ“Qû/ößžýãÙF:zè¡ÀÏi7<Ì~ áã/?·]­ƒØëëû+ö7-ö'FçNÉþ¼1e«t”­A«_ì¯Ø_±¿i»þöždþ#¸”I€'m eñ¿â3Êÿš>%S#3SS;¬àÌúZšh+dæ%Néés òЬX^Áùjý-t×¥Úc™@8ýC…­•/‡²Î’ÞìB©Ø_±?Fßi>²-Íïþ7z̘pöïÏ W^}%ÖO{U|:áºO묳>á:0`£¬éÒجcÕ`ÔãLð?ì°ø¶Øõž{îÖ]gíY*ß›R¥¯± ‚› ý/ò³*º.ú TtRì¯ÌeþËã3!õ’ „S¹þö³{U•X‹*¸Jü)ñ§ê4æÔ{H"NÓj†2»Žì’hGÁ¯PâÛH}A¢6 ÃB#Bê蘡ó¹"¿è_žÂ2ŠýQ â"-NÞkZ'”£K`„ÊçŠÿÿë¬ÿñãâñãÇËb™Ý°»κ˜€bЀ×Fñ¿âõ¿èpµÄYWPü¯ø_‰?°»Ì*ñ·Äßã5Iušh^©•T³ŽI ¤>Wüoæù_íÁPíÂCaA°:€Ìéùj¦É˜:µä[Tö(gNiiÇ4FÛÜZ©×¢²Gy8s2HK;¦1Ú"¿®-É·PžGy8kÒ -í˜Æh‹þëÚ’| åy”‡³& ÒÒŽiŒ¶è¿®-É·PžGy8kÒ -í˜Æh‹þëÚ’| åy”‡³& ÒÒŽiŒ¶è¿®-É·PžGy8kÒ -í˜Æh‹þëÚ’| åy”‡³& ÒÒŽiŒ¶è¿®-É·PžGy8kÒ -í˜Æh‹þëÚ’| åy”‡³& ÒÒŽiŒ¶è¿®-É·PžGy8kÒ -í˜Æh‹þëÚ’| åy”‡³& ÒÒŽiŒ¶è¿®-É·PžGy8kÒ -í˜Æh§_ÿ|g_ àá)Ìßvy…,Åâª,j:m,S:­C”,øé*[ ‡Š¼‹ü¢ZŒYˆjKÞ^H¥‡£¥Ó:DûƒŽœòLcUüOl§Äµ ³(E@õ%o/¢®¨³ +¤tZ‡˜âP¢SžiסŠÿ‰éÿSÛ0 RT_òö"êŠ:˰BJ§uˆ)þ%:å™vªøŸ˜Nñ?µ ³(E@õ%o/¢®¨³ +¤tZ‡˜âP¢SžiסŠÿ‰éÿSÛ0 RT_òö"êŠ:˰BJ§uˆ)þ%:å™v …CŠ2DÌBu™TËEbX},d¬jOÕ™e&²ÓÝ"•!MÜM¬U@I‘ïõ••UôŸ¬J)ö—\‡“3¢Ëÿ3›±Te9ÓSV`‰?ª›¤¡¬Küq:)ñ'»N‰?9|pê¦fb<.ñ×|ÆÒi!¦ ³“d0(ѲD‘ˆKüu:⢋©6s¦ø5õQâÙŒ¥ªË™ž’Šÿ•øÃµàÁy9þâÁ¹DŒ1sЃ¸¢–t’u]r®ÊÉØ dðŸ<Ûb¥Ìß‹|QeTGÖOÑÖE26UU¥À. bH7³¥8p±¿â°…Ôi²ë˜£DL‰?[²~JüͺÕ¤S™ÿ“*”ù§I,Í”ùÊÀúQ™4šä˜b†11›ËKüͺðñ¶T)(ñ§bI–)ñ§Ä__ü§»”ø«A#‡s”ˆ‰Yù”Œ‹8Qs>·qéj9,u «!£ TóÂ)VåPà¿qÐÔŸ•®È§Šþ‹ý™cÁŠÿÅ bÑCu#gS“!SâO ¤š˜Æ$øB=–×ÔŸc5Ñg‰?%þ˜c•øSâo5jØÅXˆ™‰ Sâo‰¿Ô@´ ³œ2ÿÐ7Êükö ©?—ëqñ›rýU®¿b…QÌþë†Íêê¤éÌ6šG Ò#8„xBíQ®¢”1ŸÊq.ŸêÕ€&(ò‹þ‹ýÿC̨Ä(ÆŒå.vèò¤ku4‘xD‰?%þ”øSâG‰?µèYâo¹þo¯¤)3åú_TáôQóž”m"ñˆrýQ®?ÊõG¹þ˜y×iñiØvV ErP½’OR¦”<§I‘eäP2–§ŠÄû#ó,胜R1UHl¤,×-òã¤LMýG{áÓx=ŠýQU_Šš‘ÄôD’ P­Sü/ªQt£šâ¹ÄŸ’§”ø«¡ÁD=þ‘G,JIö!AÎê(´jÅÆ„(×-þWü/YJñ¿è]Åÿ,–”øC“HíƒIŽ¡‚,ñ·Ì?fbU›‘OR–m§Ì¿óÆü«Ÿ’¹Hà‡{Ù«i¡8*•€`*‘ )ŸQÐlxDn•%È瀋|Ñ•UUô/ŠpV" *ˆ`*©Ñé5b±?ÕO´¦J’u¨FWü¯ÄŸh jj5¿R‡Ë¶£öÒ]ñ?ª¯ÄµJà‰s[¶!5´Jü)ñG|A]CÝ¢WKü¥ZrìÐøTMOeþ¡žÊü£öQæZCœxËü+ÊÈ1D²8ÍÏó/úŸ¾0iKaÕt#FC,]©¹TÉrY†ƒHIS±CùEÿÅþên“üÎyJ»4yiñ?z¨®Ä¢ÄŸJüi7¶8Oi—¦ÄŸöµ$%MÅQâO‰?%þ´[œ§´KSâOûZ’’¦b‡(ñ§ÄŸÚ-ÎSÚ¥™—âO~0ä»[×B=Ÿh› š1‰8 jà.ͽt•ËÕ™Ô󉺹 “ˆ3¢"¿è¿Ø_å)IÅ?*PÚuªæ‚fLf› â%þ”ø_æ¿JIÑ¡Ìÿõ ZÏ'U54cq@T®ÊõO¹þ)×?9(8¨DêùDÚ\ÐŒIÄQ‰?%þ”ø3çÆ·Æü–K÷µöò)²Ïk.Ÿõƒ]É+%ݱº2e†‹¦éA£08ÕBpƤÈ7e%U€¢ÿ¤µ¥bæ:¢s0Iy*þg*)ñ§Ä_³DgNTæUé#)*eþIQ[*óOÅTÌÁ$å©Ì?¦’2ÿ”ùÇlÁÁ™•ùGUcúHŠŠ@™’FÔ–ÊüS1s0Iy*ó©ä³æ}cˆÔꚌrLVÙYÀxYêøTQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸TQ.ç@GÞ9ÐxYê¸èCù P,ª=yäS#VæQ§¼¢< H¨Œ‰dÆËRÀS‘_U r¦¼jAÄ[aÑÒSä´¤8Ó¥¥Åþ¢ŠÿUM93žjAñ?Ñ‹)§ÄŸd'HÓ’âÌ–,-ñ§ÄÑ@‰¿U—@Μ§ZPâo‰¿~ÒÉv’ &£Ì„èb›-Y*¾§¥UrV¹ZñVhŒ•‡±³RKS©ñ²Ô*@X…œU®ù¢S˜À$TÆD2Ó¥¥EÿQÅþª&œOµ`žõ?<ÂëUÖéäWÕÞK±Ó ­ÇªèÛ|Ž>d#ó/Zi‘_ô_ì/ÆãäWÙ;’¯e>—p¤RGŸ@Šÿ™&JüÉd:)ñ·ÄßQõPÉÞA¬à€ÊÞ“á©!§¯PæÓD™²™NÊüSæŸ2ÿ0†êQæê!GæÊü«*ÉÑsæ_¸Å§)"I„9*ŠH‰¤tzO‰3^Teˆ%’$ª"¿è6`vIkó¶Áüô©6€bÅÿJü1ÿ‰ž!IòzÊükóL 6ÇÐV•¢,G‰$-óOÒŒ™T™¢™BÊüSæŸl 4Ž2ÿLÅ 9Ô_¨"C5Xܦ9WÊ|n*«!jY©(¸Uäýû+þÇ€áãBŽ< 5•Õµ¬T\‹*ñ§ÄŸJüaÀðqA£N>7•Õµ¬T\‹*ñ§ÄŸJüaÀðq!G…šÊjˆZV* ®EG•øSâO‰?ó_üé’ž¥P‘ 1™QÜ*EœªŽ†œX³51Š’\1Õͨ"¿è߬¶ –åNѶˆÉeŠ,öG­@QGY?D+RpÕVªè²ØŸ)¨ØŸiBŒ„§h[s™"‹ÿEÿ£"œ} SâOÖIv*&ã*×?eþ1)óiB\ƒ§2ÿ$UdÝ”ùWLC]DÉú¡Ý(RpÕÑgF•ø[â¯YÃüñÆ”•@m¢BèCñ•Ìècé"OŠÄó²òj—€ÊJ*âßû§ÒÉ“MŠÉ’X«*MË”›‡3 (+òR2Xô_T3ÅþEâRêW<ó(þ§zÈZ©jDKÝY‡S‰?N),ñ§Äª–Qâ¯èA‚LŽ4ÄU£–©Î<¬9Í‹ œR2XâO‰?%þø¨Bß(ñW"„„T«F³f⯖ íÿgïM.©Š³á3lÎ`\fP™AAÀˆ¢¢QÔh ‰HÔÄ,À5n £~_4QpADve×  ¢²#›¬² †eX†í½-ç©SÕËûÂó‹VÃÛ§–çœê~ºNußž¾}ÃgaµØYóüó:ÿÊ!Íç–üøŠ.jƒKñQ1Lš ô»ôÜZVmS¸jUˆž%Œ”Tb°Õ†ÏøDFòÏa©¤ŸC]KH¹ t‘½[ŸjHþ‰¨ä?ó¯–"ž6V ûs¨k S.(]dο=–t9ÿŒ N›Ì¿þ䱄œ J¿CÏm¤gþL[æ_?yF,!§‚ÒïÐsé™FÓ–ù×OžKÈ© ô;ôÜFzæŸQÁ´eþõ“gÄr*(ý=·‘Þòo)ÉУ .Eeo&6MÍú˜U¸¡¤Ã)Ž@þåVCÝÕ*ŠZ2>‘ÅTÌ®2Üi’ÿÊ€²“ù')SS¥åO#)çÈá©•õG«mÖ_É !CÉó1"Å„k³SçMlja7ë¯PV9jüUšÈžõädýÍóOžõl“ç_© B†2’ç_bDN&|î`vjÝŒM=±À›ç_¡¬rÔø«4‘=Ï¿ çÑ鉡)ê!ÓXÒAà!uÚÂòØÚÐÛgªWÖØbŒ˜`)ôg…—Œ¯_„®ÊY“URœöaKÎ?"Ñ‘v)矤NÎ?Í d‘"¢Î%Ÿ/BWå¬É*)Nû°%ç‘èȻΔóOR'çŸæ2„HQç’Ï¡«rÖd•§}Ø’óHtä]gúƒžtcH)1BÀPµ3{ê3„o akÚYwÉK´Ž;Ñ/qJjŠ+ã Ænò¯i„ÉüËùgHÖŸZ,²þZÕ¤ Éó:¸b4E¸žç_ä Z% xjæõrc eý­Tdýu9‘õ·•ެ¿­|ʵÕØz>Êóæ Ú<ÿpŠ€ ä‰% yÔggýuœ<Îõ—n að:s«Z5¹¨l²Ìr[Í ÀzH1¬Œ@ ýÏðYµSë `µTµùý¿…!ã{>’ÿIPˆ") ¤gþåüËú£•µÕWL”j©jógým\øzKs)8²þ†L‚’õ7Ï?|ñGÿótÉú«E£•L”j©jógým\dýõ äù'°?ÉbZ$¯ÿ‰ úŸçQÖ_­&­¦ Qª¥ªÍÿ‡Yõçê‰}‰O!ÇL'å¢ /.¿ºá¹ ^oƒ”ùkÌ—iW3®ÜyÇ匟œÁéWÖ[üó× l¤Aê8äåòË//W]u•È[o½µý¼=ŽÍ5W— ο \xá…eÅ'¯X6~ù&e£oHòl ÅGÍÆ$Et2;Ï>Çw”Ÿþä'‘ÀnÅw±uÿ§$¾ÈS¥l½ÍÖ‚Òa'eáÂÛÊYgÿ¼œC+Óúâ‹~Q®»þzÉ¿½øËª«¬*ÞK~qI¹öºë$Å“^uÿkü×üÉŸ”'-¿<ùâ2Ýþ3’w³2¯²ê ýý¯íéø_|ÿýå†o¤y1¿,»ì²ûߊo;BÆ—éCǶŸÿLÕãuü;´›šü'ÿZ>3ÿ¦»þ°*¼„õ×&\GÈù—ó/çŸç²þdýákgZ\Íë;ó(-Ž!Jtâ-Ø+‰îú_xXåù'Ï?¿{çŸÉÈ2ÕµOMÖYw]N}ùÛy—]&“æê«¯æi þ¿Ùm7çw@ß°ÍÖ‚™3gNrÏ¢E“wÞ™|rëÁµe2gÎìÉñÇ_ñSÚ Ûª>6˜jrÖYgñYOâb±/Ò’¯Ù5~s29òÈ#¤¯ïÃøy+Ï›œsî9=û¬3§Áý[Ýëì„u×YÇö_¸n;C¨¶ÿçžžîm¿ò3¼ÿ6´pbƹ'TˆðV©ßwÛæu߸ëÝwÝÕãkˆÿµÖZËBï±ÇµÏ¬É®ïyÙU˜š¬¿Á6æáGaÇw×÷îÚ;¶]Žy{oºé¦ÚçÑïÛß§ñßü"ÿ÷ßÿäS{ï=Ùpà Ã6¾æ5¯™ìµ×''÷Ýw:ZûãŸ1™7wîdÞ¼•éoÞd.ýÍ›7·¶*á‹_´ý·Ž"´ø{ì¾Í›92w>úÑ)LvÃïKìÍÇ`Μ•&sfÏžXŸ:nC>úýwA8ßbô Þñ… O‰#ÎÌ}á=ÿÝAw¢m˜muOèA¼!¿°á)1Ûüo§ ïh#ˆàº¨Ã’ÿq“Ë”¾õgôòïÿñŲìÒ”¢´!×^{m¹øâ‹dlÝRêÆX¡cÆ·òX|.qÏf^À?ëS´Zf™e$ÆcÙKbTZc°I|²‘£yùå—–7¾iÛr%ÍF`aù¤“N.'|R9ùäSÊ÷¾{BYaÅȪÇévÊ•…·+¿³ˆÎ"Yh`ìçý÷ÝÇðÑøÇwlÙwß}¤'Ãð ç1l ÅTW“rìqÇ—}öÙG‡%Ü}‹µ£´E4^ɮ˪n¥]î¯2H÷Kà´rˆ6`ÝRIºT bpﶈ&’­ÆQ–ÜèŸÉ‘?ð:Ýñ÷lj»¡ ¸Ñx% Eò:Ýä_øð¼&ÿ!C$ctÕr¹¥òŠùçÈ’Y-+á¦qØÍùG”qÞäùs)ëO˜!nJµ9”õG§ r&ë/ó‘ç7YòüÃdpJÈ$iµ#T—'ðùWn énÑ^’àóŸ¾g¦ruÌ1âßd“MËO~ú“²èîEåG?:µ¼öµ¯U†*ô­;lO7†.¦ ËÅ¿¸´¬¿þzLaýì6‘¯s‰‚½aë7ˆÈñ?ò/“›>lØ~‡Ê׿þõòdúšoÐÍ7ß\vzûÛË»ßõ®2þéc+>8|„:Û®Lœ}áz”+è«J‚Ug9ôCËÛß±ßW(gžõó²ÎóŸ¯néA+r,ºçžòÿôbᯌJû¼!}­—Ãé†ÍÛÞö6‘÷Ø}OúÊÕË éÆØtc§†ß¡‡ZvÚéí$OÊÙgUÖY‡âÔmnMÛ‡£+×›n² qýSÙ†SN=µ¼Ž¹f²ÂA’džE7´Ü‡âNs·ÄiÛ¨Z‹¯#RÚÿõ7X¿\Qol›>ä0âm'â€gÍû³Ž…¾³Wœ].§gX=ä0:†;ŠzÖYgRŸu­âËM.B°Îùu&}%o³W¾R¨:þ»ßûÂ"Å~³ÊJtÃé—Ì=/:¨Ê¦£wuºæŠËéæÍÔTYg]Ú—ºøü·áD Šªñ9ž?,¬¯¶úeyúêßûì¿}®lµõŸz ¨œsÎÏé¦ÍgÊÑG]N;íGå'Ÿ\Þøš uŒ;è&šl?Eøá)?¤qÛ…­Ø)æ‚ Fã_M7Wßùöw”7No+ñ &–5Cd'4™Y»æêk©Ï;dLÖÙ8kJ$áŸ%AÀ~GŨƒxs·¤ù'¤ÖmÌøÉ7¯4)3ÿd¾ÊáIJ‹LØfÈùGœ cõOì\óerþËúC„ÐÿYÿóü'S£SW²þr±Õ âkGÖ_¦E2† âÒ+«¬¿ÌI­¦¡éçÐÿôóGòŸùÇs—Ç:ÿäÆÐ‡›@:Ž®ib×Ü-‡|^èÇŽe¥§<¥ü×}¯sô1rcH0:=Õó†òáÿ‹œ/ø3¸1$ÿRA«ÿ:ñD‹°Å–¯ù‚óÏ/_ùÊþ"ÿék_WøFÊRKñs;:…VYeº õ#=‰5®||ÙHvËö4⫽:‰-ÞmY&ô¼K§O¬ýöߟnÌ,Èþ$ã¦ÞúÖ·–sÏ=·|æ3Ÿ¡8g”SèCü[l)Ø0–Œ‹@ìnñYV7ùk!=äâš–éæËâúÄÿ«}ÔÑzc¨»‚äßÀ3;¤vpcû?߆tÇ¿ÙL–kŽèN´À½ømÿëSh¶ùý×ÍòØãŽ+¯ C<Ôá‡.Nî¢7<Úvè­%ßÿvܱvØa2àÿÙsÏò©OZŸaÿ±²û?{ö“éØ(G†Ÿª»[6Þøåå€Cœ?>ýôòÆm¶¡ýSÒî¤'Ìx¡¯‘•Í_ýjÙ‰€ÖÇ_üÀeº9{åì[lQ¦èfש§JO J’€;׃S7èÅ‹ËÛ¿Eò|KzßÓ#4N¥‘¼=½¸µ«ß¿­ˆŸÿüçòdïçÿöïäa 3!Ž¡'/+O{êSË®ï}¯¶<øÐCåÛßù=÷ãrí5×ÑsË”?Þà…òTâ6ôn²VXAðÓÅÇ€¿ü·±IÐCçØêð”ñ‰£‘üªf8ÿ€k¤®é$äñÏüËùÇ3"ëO;·ø QO}Y*=ñüŸõ—§Ízýe™ó8\ÿ`¬<ÿ½9ÿrþI±Éú#u¡~þú]¬¿z÷…·ÒE¾¾¨ o4¿øô5^¶Ør˲ÕV¯'iVùÖÁ•‡~È]«Ï¢A”5×\ —(ÇœôáòázJ‚—íߺ=}°£'‚h9öØc+€žúèGé]Ån³À5›ÛÞ¢Ú|úA¶#⋺ñm~ÆÆ~A1·ÿ'Ÿt’Äç§Q޴훀¨í¤ì¼ËÎ4Žö=õG§É~w@Òßl ­ñÃ^Uþ¯ºêêr}‡ÜrË?)[oµ•à=ä`áÚÆ"âóþ¸MPÙBYMâ³tÑ00Q<ŽÂkćkþ+ŒÏÁz3ÄéÌGeÃÑY¤òY|¾å×·”ŸÑSjô¾}Ò¨Æb¨ëRõ8.Éþ_yå•zS¨nʧÿuŸ²øþÅØ!Þ SÝfWyMUU8©bmžE_\…o ±ŽA¨ÇmõÕW·ýg¿Ä±k¬±†vQšÙÕYúñ?øÁ¿/çwž0ôµ¯­Ü{ï½mû]|Ïÿßÿ=õ¹à|û«ô¤Þ½÷PŸŠ•æ1Ä×-j›yöÙg—ýó‡Ê‡?ô¡r?}T!þ·é+§"ßçÿßÿmÈëÂ[©–¬/O¦}é?¿T¾O7GéZå“{íUÞJOò“ƒtôÁnñFwãcPÁèâÃüÝ ò?ÝŒŸñ‰‰ä_Ó!óÏæ?æGÎ?Lb$ëOK –ˆÇŽù@Tóeýw©ãxªü¨¬?Y¤È !(Uêõ·$K"AäüËúª M”–EYÝÔIÒ G Š@øüc ß±ùçÞ1D›ÈLoÛXï¢ßiõ}@IDATsì1RFV[uµò|úºµ…S€ßûsúi§Ó Œ-ÅÏ6ölOO,ìC°|ú ÿØSÿè©4è¤\uõUô‹c×P Ye»7mWû}wžMïùyù&/7®ì.>m•ì´^" ¾Áø(q_iyÅ¢~çõ”ܱ³ÞÍØÿK.¹Tü•¦åŸÔùE+ŠÿüµŸ/_Z´èîrõ5ô‹Z:œ®}|¶“)UQ'Qô1zãL¸^›Þ—SÆÝ‹×:­yÿy›«ŸêŽ'ÙEùý³ ÆãéÝãåzüeDV¶aZþÁ'xî?¼ÿ x>}ÕîÆ›n,7Ñ/xO¿NwÝ´ànoÞîÍå{ô¤/Ò»ÿº ÄíT¹¾ÂÈf ÇŽ*Ï[y.w#3GÆ”B/‡;Þå3{Îì²ô2K‹í·•?§›%ïÝí½´úE»Ùåo|cGÛGÇ7xû§~¸|ë[—«iÞ,½ôÒåë¾ ðû­äF÷¨û/"­¥'ÍöûÒ—dŸ¿ó£ÊügÏ/ÒDº0\ŽâþFïrúõaëwèéœ5è×ïxú\eåÎLØÐÒ‰?tüCWÚ_Þ Ï¿bEN’Ä=÷ØSÞÍÄèO|âãe³Í6+wü÷å’K/)ûí·Ÿä¿ÿ ‹Ž‹½«V“–W"îØ>‚ ¾‚ÉÀ“!€Ä£«G±ÿÒ7ãóVJ“I ÎBI —N>ÿ•¬Ì¿œYdÂøy9믛´–àìV B‘–WyþCYóüƒéQg O޼þãÓ ®wëçÿ¬¿–!Qø=>ÿ´ïkñ.»k9Ô 9ä0ýjÓ6oÔ÷ ¬½öÚeÕÕW’Ž:ê(i5—tý†môÝA<àøÃzòšUNúɲLÊŸ¾îuø¬@O#]JrR^@?qo9I8~·Í™gžY~vÖÏj{f¹ù¦›ëÔl¢30 ª´¼Ò…°ã6l—¯Ú]xÿ¢æü“ñllÆyàºTð‚óÅÀOV…¥ú-HSµ×ÏJR¤Ù£¯ÑñÂ_Éãº6ÝZuuþYöR¾s4q-FÔ¶±ÕXG¯NjjœtæøèâZãó>ùEÕA£l‡DF§ºãCûÏc.¢›`oÇ;d¯ø+‹ÇOO”ѲÃ[w—}³¬ÛÇ/ºQ÷Ðû V^yåòLzÏ3©]ù™Ï}åg®\¦á¯Ùõ÷½õÖ£÷û¼I?!èÄä§ägÚŽjüš8¾ÿÓ ž—¿|SÚ®yåe¿¬œOOõl@ï½:õÔÓÊü5æóh•'º1D7Sy9—¾bùÎw¾£|ìã+ùÈGÊvoÙ®<ûÙÏ–¯¡ À…»œ¾Žõzo7ýYÙnÛm;ÛÇà¸ÿ—_zY}÷Õ¬²ûž»—m·ÛÖïq‚ʯÄt¢+xAvâ“mÂoÁ¢¥Âôø³ÒŸün-µÝûÈG>\6ß|sÙ¾èÃt£ìjúzÜiå-ôõ7,:d8k<#ˆ Ê?Œ »ùxí¿ügüz SÄSò¯œdþ9ÿ:„TÌö`²˜è Þ/f^eý1*˜ p‰–œâ'_Î?&(°AZÀËp›è Þ/f^eþ̸DKNñ“/ó lpÅ2Ü&:ƒ÷‹™W™F³.Ñ’SüäËüc‚d \± ·‰ÎàýbæÕ'ÿôÆPgxïaâ'|Î?÷|Ñ·ú3úZSulÿýPvÐ7¿U¡¯“ÙBĽìeÓ‹€g“iBï"ú/ãòúõ%^^G7…žBïÁX×ßx½$éܹúTâ⟠Ó//›l¼IÙ¤¶‡Ò‹Ã"Ê-M°øÍR/v’÷×;Y§ |£AòÍ^ñÉ*[Uyð?zÊJ¢Üþ›ßÔYuÒWÕšM$ŒU[ášnð²õëùëzºìð–d{¾E\?üÈÃ0k‹›M¤É˜ÂÛ’féì?¶ÃFnÇ_M@g]nö@ÁFp¯fcIµhÃñ·–p<öbúÊÑŽõ¥ÞGqD9î˜ãèWãæ”-·øò"mêXÔè¿} †@ê1^Ó*–‚ñ»¬Ž:ú;ò s¿¡ã÷þ÷¿Ÿ˜~ÿ5B]Ë ndM}¿šæÑ™gþÌ~ÍiûówýyY°`vȆ]—^€Í¶‚[ÈÜïžûîµ+¯ÚüUeïOîÅæº<~û˜Zã_R¼æ"Ó¤løÇ,.~GÑ?üÃ?Ò´óËÃ?"\-·Ürå•üRr^¤Ÿël¢ ¶ßÍÒ‰Ïd‡åñÛqÑš qÑÚ6düH i /:òø / Ç%›Ö¨s(õ‚K´™•œ1%HCòDGοœ¾è´<±„i&¤O2‘‘Khëìco4‘†ÎÑQípb`‹ #‹ּ ­ ÕM¤¡std|áäw&š`¦f©0p‰6ù¯ dþÇ” É9ÿ„óøÍ?º1D7-zã6ö;–ßÄÿ¢_ʵ×][¾ñoÈßoèk¼,ºçîòã3~*²¬·4ýTùNïx'õšUN8áx 1‘¯ì|ï{ßÈ›·ÛÎ~þÉòbÿÕ¯~U? BËj«®ZV¦§?æÒŸÜ°ìhy¢vÅK'!´I†ŒƒWú¸•R¡Þ§ÒÍ+½Ç7«ÜrÛ­.zŒãM¿–žóœç¸âãÎ#L!~ãŸßµ„§4®½îºr q}à7,Ì5ï']íŒ3ÎX8dü¤SÛC?0‚5ïðþ·ø<°ß  g1á`Ÿ„A,2˜¡Å‡=ðò¨!ÿˆ«Gèéž—¼ä%å9k®)¿pÇèÿj—²å/¼•ãOÜ;©n½õÖ¿¾õyºèVjYç¯bíÿ,êÿÜç=¯<íiO«ûضñxƒ±ÿfãí¨Ã›A·ôôÔÑÇUøæ;ÑKÅy°üǤ÷=»\éž4ãÿçÿü3͵ëÊ…\PþùŸÿ™ÞcµKùô>ÿZ.»ü2y!5c>I7¦'Ú8þnï{¹ôÒKÅÇ7N—‘¯Âµø¼½¼øýßn»Õ>så×ù–^F¿Uªû¨È0)êÓ± ?šý×ßÝýÁØqTh³Ê¿Ð»ÆV_m5ÚæYåsŸû|Ùð%–§>í©e;ªßüæ7Ë×§ªM|ÄòûG ù'Û¼¬Ê¾’ ûl6ö ÔáM„€LüÛñ“ÿÌ?žQºèk³ƒ­9ÿˆ¢$ëæˆå熤ŠË!dýyþk3œäù7Ï¿yþíÖÕ6;¬Ö’©Íž&gýe†_&Bxbžè“6}ô’}ÐîŸÝZ rØÁ¡#Óûéè_üÅ_”éï ohÙtôQß1¶oÒ¯ç,\xýlýÅåg?;Ó²éõõ…Ê5"ýLüÚ’tüÒå‡ä§Ô³Ï¾û–[ný5}À¿¥¬üŒydç E/úUZ{„;5Ò0<ﻎZÇ•Femý®'ûõ/ñþ Š«¡¦_Nºöš«E“CÕÞš€£PWÙäÆ?ÿ>¥¾ï}ï+ïÞù/èïÝôAøõ‰ŠYå¨Ê5o3/|\Ü^©1¬ô´ñ!h[I¦šØ‡ªkÎÌ[FäuÓýW…uùŸ„¶ÿâ“*M1(À»þâÝÔOÊÛvx#Âk¼+œe¼ ‡_NÍ¿äÅzCq.É+‹.=S#ñ9¶Lûô÷Ÿíº%­UìÐZ‘kÒM®7½qÛò—õWåàƒ¦÷ç\I7<çÉSi;Ó~"&âó‹§ub¶ø«Ò¯ó½ÿýï“Ø÷Ò¯ŽÝHï`:÷çç”oÒ D^Òû•ä×ψ;y‘tÑEËFî»ïgȶTù3zíœsÎ)øMésÛm·•UWy–â©ßRô'/?'ï>ŸÙ·Ì¢1äeó|¤‡ôØö_:ÒŠr@{Ó@$°ÌÛÄnº<‘ѱÿë®»n9ÿ Ê~ù?è—Ù^%y~½Óìhzyý»ßõ®²€Þ…Ä/®–¡¥çØÊ#úñy¯h—i‰ñÛh©â*ÀÞúxÉ#2¾²Gü&“Ηʆ4*keþazæüsy¢É‘õ¯ò0ÞdýmÜäùÇ*+ *çù?Ï¿DUžèç¡Á×!ô•C¾õ­Â/Ñå'>ä ò/ÿò¡™ ê§?¥'‚°ÐM€åŸô¤òŽw¼],ß=á„Â?SÏœó „õ>»4ewÙyÁñê£ô’Ûn_hOÀ!÷!¸¢"º zHëåðIks®_·ahÿy¨¿¢ï¼0êÿ}þó"ËŠÆ~˜Þ­ôÙÏ~VTÞ¬wÜQýñyëd å;¶Ucs§ãŽç¯ìi~JK¸fžë߇™k ²ðÖ…ôôÕÏÚ>ÖZdø6¸éÇÛéÇ+ê‚^j£;éÕ¦vçˆÏ^Aðj i×öŸã³&êÏùÇ/9ŸPË/æG„äÉ tio§q©/ì¼qñÙ¬ƒs‹mÕNü?íÅ7ö>ö±1Rc‰TWԽĂŒUC²AÇ}èáék—~ªLlc//6¯…_BãwH}û;G–É#´ñX¢ “©©rôQG‹}öœ'ÓÙùtÃìerÃ…ŸÌã/ú·~å……Ÿ¼á…oBÝN¶#é)›—¾dã²P°„# cRË6þ[çt3´ö¹íöÛÊ·PHù ÛÎñê²µ°—ç¡AÏCyýýD¬?úh†¤µm¾^®Ñ§®ƒé§¬Yyñ†ÒS>«ºË8¾ ™U^ûÚ×jÐú;õ×ÉjFH¿mù—‘h9…þÅŸaŒKÏÖ[mm×0òùW£ø—”xù%á^ô¢—c;®Üüë›Ë-·ÜB/ßýŠ|pÕŽº.ݤ_§¬ÕkÎî> 4Ư¦¥ nV «öÝr‹-ÊKé)'® Ÿÿüÿ-úЇÊu×^[.ùÅ%tãëô«R§Ê¯B_õÑ…À²Aˆ¯w Åħš±ÿÜç°C“®nø"‡~ñ?õÒÂ7FxQ®ÕÆ¿gû¯nÁð{f.¾øbúêÞ/äÝ<¿à–õ‹¡ïdi›#x߯# Ù‰Ï,” ÍæL„¤÷Ñ‹‚}ükñþ(^ñËËe›.¢íú=)Å ÇÇæÉ.ÆW¯ZºËŠícûñp×Ýw)¸âýç_’ã÷fa¼üãe1ÿÄû`|«RMÝýg²ø—Äø¦Ó¦›¾¢|ÿ? Ÿ¡ÿo9ž]|Ý€ú¸Ü4âîüSìO®/7ÿÅßž^6þš[§ÿøtz2æn‰ðë_ÿš¾†ö×4—NžþüNöYe™e—-ÏxÆ3ÊÓé[ý›[žñô§“<—Þ7ÄïšUVú£?*OŸûŒò”•V*Ë.·LíÃ8êCö¹„[û/½´¾oè)O¥>ôÔÎSžòÙ¿àK´ò>ŽW“¨,X°@â³ÿ ÿñ…râ‰'–;ﺳ|÷»ß+/¥—Ôó×áxñóïÆ›n*ÏþóËË7yyá'òX¼X0<¿þä“NWkÌŸ/òX|qÒJü8dµ]âü§ñíþg|e ù—¤Q22ÿ„‡œ”µpùz¢sE“ç&¬B…;ïú|ý0úÖ¼ãòÌKòŸü#©|>eþÑäx®ÿdÊLËëÍ©JFÖ!"ëoÖßßÙúKn™2ùúë¯ç¹,Ÿøä'ÈÞ|"Ó†n(˜ysçU³âx}×ÝwŠj,ÏÉZk­­úLwyèá‡&ï~÷»¯·E–mh÷U&ß=á»­kòµø"ajüC9Äb\zÙ¥Ö·€>ÈOæÍ›kÛVÓÙún¼ñÆz’§vëÇ?ôPŠ#û0krÙ¥§³ÿ¿b®ë>Ò/±µðó)ášNà“•çU®)ÔyçžgÛÁ>3ß2ÿ»ï¾{·ß9š(’'¯É7ÞŸ°(îλî²mÁñ§%ÿ[Úëþ®½ÖZÖ{=vß–[lYm-&Kú õ×1Ž8âpêÉ®»î*ý¦Ûæ÷è£ÑqÝþ_tá…a{è=E“xp0¾m(¿y°¹üûüç?§ÛÅû*ûÛßÿ•(Þe—_nñ>ýéOË>úã7g¥9ºÄïã 7Ø`rï½÷Úþ[ht£°Þ`ý ø}æéø¿pƒJ¼Ýwÿ'7ôÀΘ°Qˆ?õÈÔäÅ/¦1²ÿtóXb­µ6çö:ü°Ãm1ÿW[mÕ ýJ›¶Ó×,ÝöADdÖIVNmÝñï8¢: 4`B@uÑz“ñ;gñ·rõ3Ù÷å@ô1”,¯ýŸ‰ŸÖH‘…>¥•7o§OñWQ´/÷БVšó”²Í6oЯùPç·ò×ÈÄ©U¨Å_†žRøÚ× _mÚ¯¾ðY€Ÿ%‡>@—?ÛªýŒ;ÇטÜòÂ[Øâ‹Ìªl8¯êBŸ”yáí"À²ò”„”¾ë¯·ýBÔEeëm¶¡¡è?í^f¯4§üÍnï•§8æÎ›K}†ãÓ+}ÕEþ¥—¥§1jü¤|Ÿž¢Ð§gU®±­HDw[úE&Ž«pM.r/³L{Ò©‚mŸt3é9w·cLr'þÐñ×m‹ñer,µ´ŽÌÜ-+¿€¥h¯¾¥égàUB|Æ(wb¯$rƒøK-¥Oª,³ÜÒu@vj?Þ)¯cðS-:ÄD~r^;xdÿ™«:®Ûÿ¬¿^ùÓ?ýSãÿ£ýy¢FÇŠ_7Äweïdýw÷Árá…•í¶}s™GOät÷ÿÝï~w9ç¼s Ý«CÌ*{î¹G¹à¢óË›ßB}èÅÙ<Ú¢Eüþ!÷£ÿXùñOÎ(+®¸âŒñ¹?Qć~úU6[Üþ³ ük ÎQÊ) ·Ì2Ë ðo£<ªøüòêÃ=œ~~þEIúðžlºÉ+ÊyçžK_Õü+ùϘÞº}¹ý7·—üeåúU´o¼Y^ŠÍù?wåye_z1ý×<@Æ”dÅÇ_y²5 l~ó_Bñ„Ç’ñ“ˇ]K蔆ä¯Eò ÝŸF_óZ}õÕË Ë/¯Îiøç_»úškʳV~fyö³W§›rîOÚÊÓã5ÿdèüw!ƒv9( v}Eìzû:ôÞ£•è+m6ü4ûϽÑ/°]{õµåÁ‡”¯´>“ÝxôKR¹çž!~„¾%Œ”°ìÎøt&Ì‹%+Ó/ò ôûõÜZVŸèúmÕ?‰ä_™°ôÎüËùŸõ+OÖ߬¿yþé qæoC— ôûôÜlÈúÃ$dý©©0‹² É!É‘ÚÝ0BT³÷2Hõg…AüKTúüQ뎽Uá>˼düJ4¥…ÖÊVåÌSg%ÿ>Ó@TæŸgESܰÆ2/9ÿ0ñ<;ÌŒêÕÚu2€²çüó™¢rþyV,Yx¾ÉÂ<ñ’ó/ç_HÉ ^éLªó ÓʼU {Ö?Ó@TÖÏŠf ¸ae^²þdý i YÁ+Í–š3>u ¡ ¬?~¦¨¬?žMpÃ˼dýùß®?ôÄýæ5/‡Ã="²1;ÄôbèÏ /¸öõ¥%ÆësÌ …þ_ÙJþkÖhvÈ:$JKªsÌ …þ¬ð’ü+9ÿ%$GB¢Tz¨17À RèÏ /™ÊCæŸ$ƒäHH”J5#æ˜A ýYá%óOyÈü“d ‰Ré¡fÄÜ3H¡?+¼dþ)™’ ’#!Q*=ÔŒ˜`)ôg…—Ì?å!óO’Ar$$J¥‡šsÌ …þ¬ð’ù§… fG^Œ2C޼þàÄÑe0}f¾þ ß r,:Q9 x)PP#ñ<Â-ßþ'^^ÔƒÁA˜ÁåÍùÁ›ñ“Í2M/KG5ºuæ_ο¬?Yå$5P&òü£Å2Ï¿yýk Έ¼þjyýI\äõ·Ÿ6Aòú“?ÞmÒT)¯¿óú;¯¿ŸÐ×ßí‰!®€<ÛÑöf{3(dØ÷t-M7 Ú®')dØ÷t-M7 Ú^ÔfPÈ8°ïéZšn´-\ORÈ8°ïéZšn´½¨Í q`ßÓµ4Ý$h[¸ž¤q`ßÓµ4Ý$h{Q›A!ãÀ¾§kiºIжp=I!ãÀ¾§kiºIÐö¢6ƒBÆ}O×Òt“  máz’BÆ}O×Òt“  íEm…Œûž®¥é&A@ÛÂõ$…Œûž®¥é&A@Û‹Ú ö=]KÓM‚€¶…ëI ö=]KÓM‚€¶µ2ì{º–¦›m ד2ì{º–¦›m/j3(dØ÷t-M7 Ú®')dØ÷t-M7 Ú^ÔfPÈ8°ïéZšn´-\ORÈ8°ïéZšn´½¨Í q`ßÓµ4Ý$h[¸ž¤q`ßÓµ4Ý$h{Q›A!ãÀ¾§kiºIжp=I!ãÀ¾§kiºIÐö¢6ƒBÆ}O×Òt“  máz’BÆ}O×Òt“  íEm…Œûž®¥é&A@ÛÂõ$…Œûž®¥é&A@Û‹Ú ö=]KÓM‚€¶…ëI ö=]KÓM‚€¶µ2ì{º–¦›m ד2ì{º–¦›m/j3(dØ÷t-M7 Ú®')dØ÷t-M7 Ú^ÔfPÈ8°ïéZšn´-\ORÈ8°ïéZšn„ÚÒCu _öe#øj¨Ð‘M †Ò¾¼n†tŒ_ic‚éNTª’P¢„As9U™ÌüÃäBFåüËúÓÃͤHÖßZ5¸ž´šâŬ¿LQžš0ÐÜœR‡pQQY³þrF¸\Éú«S$Ï?µTp¥h5Å‹yþaŠòüƒìЄæjŠ:„+ˆŠÊóOž8#\®tÎ?K $NûÍHCÛcQfoKFÅX~O‘,õÅW$ˈdÖ§ùIp n1½Œ§ÿÍ-HôãÖ÷ãqh茯DÉ`*ùçÄÈüËùÇS#ëOÖ_ÎTÇšÜÈù¤Sòü#¤´Ó¬0Þ¸m\Udžóú£¦B^`¦äõ¥‘‘×<5òú#¯?8PjNp“וå"¯¿˜Zp™%"ò†[8ÐRVý®?è…÷Ú݆³&ðæL»ÉÛÁ_ÍFk0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸«B?Wßî5±¬wæ´ÞÏdj«š¬qSIÞ×G´ !|HOZuG@Æg|aV嫲–ü3AaSÈ;´ "Þ2ÿ„ É ZÕL2Árþ ´j¬(_•µœLPXÀæZo9ÿ„ É ZÕL2Árþ ´j¬(_•µœLPXÀæZo9ÿ„ É ZÕL2Árþ ´j¬(_•µœLPXÀæZo9ÿ„ É ZÕL2Árþ ´j¬(_•µœLPXÀæZo9ÿ„ É ZÕL2Á0ÿä‰!CHÇ¢ˆbóŽ6`•¦u:´Ã™h‚༖ñ“ÿÌ¿œR|apEÅiíp&šõ‡ðldýÍú›õ7ë¯Ô_\EUqZ§C;œ‰&ÎkY²þdýÉú#5ÁWQ²þ0Ó’ãØr8Mœ×²þþþÖßΡ˜B> \öTQ½ÓcZ¯.ZôÎÞäå6$õNûÕÐ"5Õ€Þäe˜ Þé1®S³¡Ejª½ÉË0A½Óc œñ‰ }]ÞäÞÈó&/7&!©wz °™]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D &ù*ÓÇ^Ú³Añâ@ $"#Ý[­¹³-[m:®öa“|HwÁÐÙê¶d|å y"*—ž¯J5M%IqÚ‡É?‘èȻΔù'”óOsB¤ˆ¨sÉç‹ÐU9k²JŠÓ>lÉùG$:òÀ®3åü“ÔÉù§¹ !RDÔ¹äóE誜5Y%Åi¶äü#y`×™rþIêäüÓÜ@†)"ê\òù"tUΚ¬’â´[rþ‰Ž<°ëL9ÿ$urþin Cˆu.ù|º*gMVIqÚ‡-9ÿˆDGØu¦?èùG7†”# U;³§>C¡ñ¶&°¦u—¼¤Aë¸ý›¤¦¸2¾Ð`ì&ÿšH˜Ì¿œq†dý©Å"ë¯UMÊ<ÿØ©ƒ+FS„èyþEΠU¢ §F`^ÿ(7ÆPÖßJEÖ_—Y[éÈúÛʧ\»Q­ç£<ÿ`ΠÍó§Ø@žXÂG}†0pÖ_ÇÉã\鯝3·ªU“‹Ê&Ë,·Õ¬àÀ¬‡ÃʤÐÿ ŸU;µ®VKU›ß_ð[h2¾ç#ù™…(’Bzæ_ο¬?ZY[}ÅD©–ª6Ö߯…¯·4—‚#ëoÈ$(YóüÃô?O—¬¿Z4ZéÀD©–ª6Ö߯EÖ_Ï@žñ“,¦AòúŸÈ ÿyeýÕjÒj ¥ZªÚü˜õW¾JÆ/‘âÌñÌy²G `MH$EÐÌ“‘x<^Ø'9)šrî×b®Ý3~òÄ¢œÉüÓÉOwRÜuEîœDBÖŸ¬¿µnÔFÏ0<}87òü>ü™6aIxËóožë¢¤Èó/fZåFÖ I\¤äõ/—©µÜ€±¬¿œåů™-¤OÖ߬¿(,Yóüƒ*ŠVsCÖHq‘ò[9ÿL0ª)[spl“½§0ý ©7¹ŽâcÝü&T›Ó­_GèA¼!ã'ÿ™9ÿ¨f„Å5¤Ö«ßÕθ¡¥ñ†¬?Y²þdý¡Â‘õ§S=³þæõo}zÒN™&äõ¿PáøèÌS{oÈë¼þÈë¼þøí]ØË§µbc­¹É"B¼WâS$¯í¤Èåȉ 0ö[G¶û¥!V©…4¤£u°aÄ×úfüzRf¦’ÿš/ü¯1ºdþ1q.Uf¤O 1Y„Ø'ç_¥Q¸Q¦xõ'ëÍ”¬¿ZZ¨˜è ¡ùa…£º¬isHL„CŒU¸µŒ ¨õÍù—óÏ2%ç_]9ÿPK²þpJØ ©ùÁM«¡bÌú›çd‡¤FÌÑx%¾–;yþýý8ÿêWÉ\%ð‡Ÿ~Ë^SƒŒUª‚C‰¨Í#L«÷(8mx©£…†<¤·$g|a‹I©T%ÿB„Ë• ÍÓÁé5bæŸòS³)4CMºœYj‚hjhZtæ•N¸–;š_íàrþ1}Y4?Bá©ç¶–CšhY²þdý‘¹ SC§E§®fýeZZíÐúB¦Oyþažòü£ù‘çΆzâÍó¯Ñjˆ¨´úC>ÿÒ¾×»/Ü̲² n$iØÊS©ïUXó5ɨ‚xzngÈøÉæ_wÚØ¼s3e3Ý,ÍùÇ…ž¨ E̲þdýÉú3Z[ÜLÅdýgI<=·3dýÉú“õg´¶¸™2ŠÉú3Î’xzngÈú“õ'ëÏhmq3eóûTÚ!¿»]ººaû޾ÅÀM Є>¥¹‡®š¥î ]ÝÐ}Gßbà&(ã'ÿ™á.I˜áÊè¤ê;ú–6¬I9ÿ²þdýÏó_(2Vòüß-¢]ݨê;ú7@yý“×?yý“×?­(8©[DººAû޾ÅÀM PÖŸ¬?Y~wë{ÇÍ[ž°<}±½|Ù몵µ~aWtEr²»¥v×AYá—¦éÂIÙzAl$ãƒ,£(ɿѡ¹”ù‡©#Ä`‚IË«œ $ëOÖ_ä‚/"bÃ$ÊóR>Œ¨*äùÇÑ\ÊóOHL0iy•çP’çŸ<ÿ |&Qž”ðaDU!Ï?ƈæRžBª`‚IË«<ÿ€’™Î?úÄ££zƒ¦™À‰–•K*`,´nœhrš|ÉDŒ…ÖMNs¢ƒ/™ˆ±ÐºQ¢ÉiNtð%1Z7J49͉¾d"ÆBëF‰&§9ÑÁ—LÄXhÝ(Ñä4':ø’‰ ­%šœæD_2c¡u£D“ÓœèàK&b,´n”hrš|ÉDŒ…ÖMNs¢ƒ/™ˆ±ÐºQ¢ÉiNtð%1Z7J49͉¾d"ÆBëF‰&§9ÑÁ—LÄXhÝ(Ñä4':ø’‰ ­%šœæD_2c¡u£D“ÓœèàK&b,´n”hrš|ÉDŒ…ÖMNs¢ƒ/™ˆ±ÐºQ¢ÉiNtð%1Z7J49͉¾d"ÆBëF‰&§9ÑÁ—LÄXhÝ(Ñä4':ø’‰ ­%šœæD_2c¡u£D“ÓœèàK&b,´n”hrš|ÉDŒ…ÖMNs¢ƒ/™ˆ±ÐºQ¢ÉiNtð%1Z7J49͉¾d"ÆBëF‰&§9ÑÁ—LÄXhÝ(Ñä4':ø’‰ ­%šœæD_2c¡u£D“ÓœèàK&b,´n”hrš|ÉDŒ…ÖMNs¢ƒ/™ˆ±ÐºQ¢ÉiNtð%1Z7Š>1ÔîUWçÎ#ß5âμt±b‡“ü&š`¦f©0Œ…Vð*ãGJHyÑQíp&ÿÆSLr,© \¢Íü« äü‹)A’':rþ / 'ëåILr,© ¹„6ëOÖa ëoœ¤aòDGÖ߬¿þ¤ÓòĦ™B<ÅDF.¡•¹§Þh" ££ÚáÄÀ:†ƒ­y1Zt `ÑD:GGÆ^@h¢ fj– —h“ÿÊ@æ_L Ò<Ññ{;ÿèÆ=^…¶y÷^ÜŽÎtѧùÞD-Éüãðfüä?ó¯Öc›WmvØ\#æœÙ/P‡7BÎ?0‘õ§e8Éú›õ7ë/WT]t†´ÙÁV±‘©Íž&gýe†_&BÈó˜ÈóO›Aà$Ï?yþÉó×P]òüÃ<´êÀZž•’V=û×îåÓ–ˆ4U棢kœ ÞǺ²‘Ià‹ª–Õ#¡2~òO9€¼äló¹Áúc[¬7 ™9ÿ²þ`þÔ™!Írz;c½}}k½IÈù—ó/ç掟g6KÚ|3“ èø˜ZëMBοœ9ÿ0}êÌÆfIÎ?œï@Ücj­7 Y²þdýÁô©3C›%ÿ¿Õ÷òézˆ¤û· Þ ÅõïòòÎÔ¢´tGvjkojÄê­š4¿qÖXR›êÉK*å¥1f '¦Ì?ÇLÎ?Ib„¦Q›Yl¬š4YZÎ4–Ô¦zÖŸ¬?`@ó¢eLÖßÊŒP’ç—yþ‘Ô F¨Œ¶ÊÊÆªI“矖3%µ©žçŸZc¨Q^cY+7BIÖ_—Y%5ˆ*#­²°±jÒdýµCqúM¾òVÞ‘ôsÃXÔî¼ðIËï¿® ºð­bY`àâÖdveüÈG/‘Á1s%\‚S!O,D¢x̓!“åÇå\æ’©õÌ?̳š:9ÿ*àŪ o$µYÿL©¬¿5w@HžÿóüÓr“#¯ÿ"yþÅy¦–œc8WÄdU–“GAÒæùǘAJåù§&ÉóOžZ.präùgŠîYåÐùÂÁ4a÷,Õ¼]‘mÝóu U:ŠmÀáM?ùÏüËùÇÃ×…VyTêù:†Ž*Ä6àð¦¬?Y²þdýá‚áë‚V¶îù:†Ž*Å6àð¦¬?Y²þdýá‚áëB«<*õ|CG•NbpxSÖŸ¬?YþðêÏRvÈJE»)Ä¥I!òP5ª6¥Ž.+´ɪv¨Q ­£õm¦ŒŸü#($³Üªæ[šO™Ì qQ9jü°Yb‹î¸ÌüA™`B’„W5·Xl>5æü«åüc"\~’õ§qÒ&Óì"åõOž yþ25x•磢q“ç_I !„¸¨9Òøá¼Q£Ø¢Cøl¦¬¿Y‘ Xõ—ž˜’%ÜÒjS á9TɬsÌ.òÄ%3¯‘×¹Ô¡¤#­ê“GjlkWÚf2¢H‰{ÅhêÓÑ¼ÜÆ×â@¾ŒïHibòOÊLæ¿ð SJç¯yÉù§<4V"#êuk!ŽVY)MÌú“õÇ_€jfdý¤È´JöXmÔ§œyY-²f3_eýq¤41ëOÖŸ¬?¾ªðÜÈú+BJªÖU)£™úÉW}‚ Ÿ…ÕbcdýÍóÏèü+7†4Ÿ[òã+º¨ .ÅGÅ0i‚ÒïÒsKhYµMán¨Uý!z–0fPzP‰ÁV>ãÉ?g„¥BH~u-!å‚ÒEön}ª!ù'¢’ÿÌ¿ZŠxÚXîÏ¡®%L¹ t‘9ÿzôXÒåü3*8m2ÿú“gÄr*(ý=·‘žùgT0m™ý䱄œ J¿CÏm¤gþL[æ_?yF,!§‚ÒïÐsé™FÓ–ù×OžKÈ© ô;ôÜFzË¿¥$@‚¸X•½u˜ØX45ëcVᆒ§8ù—[q uW«(jÉøDS!t0»Êp§Iþ+ÊN柤LM•–?¤œ ‡§VÖ­¶Y%+„ e$Ï?Ĉ®ÌN7±©…Þ¬¿BYå¨ñWi"{Ö_“õ7Ï?yþÕ³Mž¥*ÊHž‰9™ð¹ƒÙ©u36õÄož…²ÊQã¯ÒDö<ÿ‚œGwþ¥'†¦¨‡LG<4bI‡Ôi ËckClŸ ¨^Y`‹1bn€¤ÐŸ^2¾ò …'¿äHH”J5#æ˜A ýYá%óOyÈü“d ‰Ré¡fÄÜ3H¡?+¼dþ)™’ ’#!Q*=ÔŒ˜`)ôg…—Ì?å!óO’Ar$$J¥‡šsÌ …þ¬ð’ù§3_,¥_"ªƒByÄà%½¨‘x_‡†o?wœz08"x ¼Á;x3~ò¯Y¦éeéèS/óÈù—õ'ëožä$=P&óü«§Œ¼þÈë/\SqFäõ§Î ^çõ7“RÂ$¯?ùãÑÁbXòú3¯?óúó }ýÙžâ“"Ïv´a¦GE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A@"FE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A@"FE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A@"FE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A@"FE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A@"FE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A@"FE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A@"FE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A@"FE!ãÀ¾§kiºIÐÆASÈ8°ïéZšn´!bT2ì{º–¦›m 4…Œûž®¥é&A¨-=1Tòe3¶1‚ÿx¡Ö‰ ¹›N@ ¥}yÝ,: é"8, ¿ÒÆ9Ò¨T%ÿ D ƒærª2™ù‡É…ŒÊù—õ‡'‡›+H‘¬¿µjp=i5Å‹Y™¢<ÿ ;4a ¹9¥á ¢¢²þfýåŒp¹’õW§Hžj©àJÑjŠóüÃåùÙ¡ ÍÕuW•çŸ<ÿpF¸\霖AHœö!š‘†¶Ç¢Ì$Þ–Œ‹±èõEU¬/þˆdÖ§Ù[‚¢O'ã+/TFU+Ìsügþi²äüC&È,Éú“õWV’Èž-¾Ž69ë¯V£G˜oÜ6®*²äù§ñIX"%¯8Kˆ !ìT›$3Õr*çŸÒ(vÀ·«ŠÌù—Ÿ?j*äõfŠÌR²þrjB Ø©6ÉfªÕ”¬¿BJ£DØoÜ6®*ò ]é…ÿzù†Ý’DÅï,vu¸RÞWM}Ñ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚Û"x@qÑ3D!¸M1!‚4 ­A0C‚»*rcÈÃÔ r¯háÂ[Ë9gÿ¼œ}öÙe…W,/}ÙKËK7ڨ̞=Çw/×_}¹ø¢‹¤Ï«6U™3{6ùõ¶zÏ݋ʩ§Ÿ&–Wn¶Yyø‘©òÓŸþDÇ Þñm*yfÖRz7“-åßM›U¦èOý[o³àøÃ“Ëý÷/®}) XñÉO.«®²JYmõÕËŠ´uÁüŠ·íBÚ6ÚœÍ_EÛ6'nû¢{î)§žzªÄß앯*ȶы_xÛ(ïYHáø[m½u5 a@ÛÆp|µÓZEOÊwÜYÎ?ÿ‚rÁ…ç—o¼¹,˜ÿì²Ý¶Û•UW]•:÷{ßy×åäü \ñË+Ëon¿½¬µöÚeóWo^žO-¢Ýqçå'?¦ý¡î믷^™?~ \%ŽÅå—•«®¾F¨Ûzë­ê½Âº½¬ë a†E÷,*§Ÿˆ/#Ð ûÏ{Á¶uÖY§<ç9ÏùÄO,<üu?þ+>yvÙb‹WSO]î½÷Þò£S$üoøâ—g=ëYpQ[·Ré%—”뮽V¼È¿?yÍ–åIOZžŽWç1쿎;Ÿ÷}hQtëØ`Ëø”cì3j<™È¿qôoÿ©‡)w-º«,·ìrT;¹6Æ%ëê ¶ßâñdjªÜuoÛ2¡®Ïÿþûï/7Þtc™ÿì5ʲË-wó†¬2­Àÿ7ÜPž´ü eîÜgXŸ«³ÿ‹©Öß@}Ö˜¿ ,·Ü²ÖgzAGdL7þP¿éâKû-òoÛè8ëÙ–0>ó¶ü ÄõÓŸaüÿ®ì?oÛ ´mÏxmm”ÕÚºÁFûÿâ(ßn( æÏ/Ë,3Shoã‘TäߣÙÿn$Þ8GŸñtÙ29áOSx.Üt㔣ó˲Ëò¶ýÏâÛvcwüyÛV mcÞÆ—ÿ>Þ¶n,Ïž¿FyRožêŠn}¦‹?ó¢ /,×Ó1¥YÖ{Á Ê|š¯6 ÍüÿòŠ+ÊUW^){µþúë—5ÖX£aÈzÑÅ¿(×ÿêzcRþø_TV[uòSG·ÿÒaÆUÛ‘j|ipQtëà`ËøtÆØ¢´q)­ËüKþ3ÿrþqå«ó(ëOÖŸÿú+O ÙŠ¼¾.&NMŽ<òHÎÊðGÛ6™;wÞäœsΙ´â8àÃ]zé¥ÝÁ&?<ùdòË]•É^09묳 ϧý^œ6‘nJU<ßn¡¾:{lŒÏîó“|ˆàºq|ã>'‘ÖäÒ˰mmBÛv’õ½à|ݶ¡mâX¼ÿm[[ü6š—ŒLo÷Õ¯~ÕÆîÆÿ«¿þëÉ}÷Ý'}§ê°_'Îç0a›tû6Ýô“«®ºRðW_sµíÿn³Ûhüm¶ÙFâÓ3ÂÄm÷â‹Í;êÈgÉǶrSø—m­Ûû…/|AzÜ}×¶ßà¶»ÿl_k­µøçžwžö£ñŽ?þøÝ7ýÛõ½»öøŠuÓM7úˆ?¶ÿ­c?~óyã<Æï@IDATÉáL4A€^ËøÊpâ‰ñ”öXë9Á b¢ ½‘–„ÿ³¥ÞÍšÌY‰çVwq±L4AÀ^[’øÝˆ^?û¬3Û¼·@&„ø=ôàdï½?5ÙxãÛ§ù·ÑFMN9å?,ÉmŒÅ÷ß?ùÀþv2oåy6ÿÖ\sÍÉg?ûÙ€C—‡|`ò©OQœ—qœVs_úÒ&?ü¡Æ‘Ñ[‹}˯o™tзh_6™7oÞäƒÿAó nM.^ÿ¿¾…â|ó ÉÆ/ßXã|°Ç÷ÒÈ·ÜJ}¾E}ˆ?Ù6éãp&š0_&XºI¡\Ï›kõñ9Ìõ¿}N7¢·vƒTq=vŸðy€Ï/ýØÇpXt[*F×µ7lè¥ÞûÓ¶ýídßq\Ÿ³æ‚š„±ñTà¼á,Xó9tLyþô¾þùÔ§öÖyJóçßhþœò£S:\,M¬×°ÿbsŽu×]×¶m—]þ’ú9g¸Çž{Øüßu×]«µá6X=δGqDõÇ‘ÆâØ„6®™‡3Ñéᵌ¯$ 'ž˜·Ó:ÚáL4!ù'<™š:‰'Æe”Cô¬}ƒÄDîµäß±ë‰é;­Ó¡ÎD’bÀ³ñÛÌ?ú˜¸„ÀäÚo¿ýèÝn|ð…ÿÚôÁ%ÜžqÆ6Èð!ìÐT½1¤øÒÍ— /¼p²ÖÚkɇýµž·Öäy4¶|h¨=|QºÖZkë_Åi°)¹`åøŒ¡§c&¯Øl³ á·mƒ 6˜Ð¿4 ¡ðõzaT&—ðM+¿³4èÉrÓŠ¶. .¸à‚ÉѶÑöøø|‹EŽËÛ+Ú6–Ç ÕŠÔTé6EGzÇv² &Þû5¯y-íãJfÿ×Oÿ+áµów¿÷=Ý_ÚfÁÓþó)oxX™¶ùÁ‡ôÙ ‰¶Ï%Ûä‘ÎШ÷ÒM'Ü`â›Pã‹öíÐzà¦óÅñÖZëy’;¼OÌÕÚ´­,ãÂoÑ¢EÊg=ÎÜG÷>HÓþ#Oþôu¯£ -þy|c¨îÿð!Öí{]Œâ8b\ätÞÆ[o½µíPZü`QºhÑÕzx“—HõNi½ºhÑ:{“—ÛHÔ;=Ø>ÿÒo ³7y¹I½Óc€ý݉ÿÝN°yÙ­?¼µ~¼ÜöÒã¿ÿ'`Ûh.ñ2ÿ†ëo˜lºé&¶2GëüCÍáó€_x¬»èÆ/=%jý0ÿ0wwÙy*I-*=u@q6%|;ÿðøÖd¾)Þ].\8YmµÕzqvØa{ƒJ”*Ú«6à6³ÃqVE·ÿ;찃é¨cMMnsÛfûA}ÑÇÇôroÀztÆ0ôä×d³ÊµÅqÜí¼Ë.ƒõßwô±ÇT•ÿÝwßÝm†"=Þ9{¢Gó¶ñ¹Ú¶ËqÇÇw—¿ÜeÂçBŒ}Ùe—Ñybm«ñÖ°È7>ÿÓ“£½¸0øøl¢öNæí•-Guü–»0o’£Ú™¯#ø†ÆöøöÕ›o>¹G¶­‹ßýùw×ÝzL‡öŸãó1eްГX2O=¾+ÍSôo#Áâ[õv1—_~¹怯C|àÁ^¶î¾Çvüù&ùCr}¢ûÌÿ€åù;âˆ#}à*ÇŠ©‹½»ñ„ô&/÷ÇUïô˜Ö«‹} ³7y¹I½Óc€Å~5´HM5 7yÙ&¨wzŒ{Ç_ú tö&/·‘ ©wz °¹ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À&ÿ]¶D Ï›¼Ü˜„¤Þé1À>ñø·CC;x÷ÝwÓ‰}6¤ùƒùìɹ眫{JàÃ;\NÞü/wüT –ø†žÔÉ~饗ÁlíÉôTþµï‚ .4»è!‡Ø…Á¥—^Ò;©àìÍÛÄcí¶Ûߨ8,üæ7·ÓEÒβÝ|ñµ¯Mü¸iÅ}x\¿pü“Oþ¡îõáCºàU·ðÙ¶YtQCOuÆhãù½iVŒÛ¦}ûÛß¶ø|±ÉÄœR<ÿ ýÇ?öñÉV[o5y`ñƒÚ‰<[n¹…í'}ÍÏì,ü òéû?ø~µOè_W?iü_tÑÅfÇŸtŸzÁÍ©-ưQ (ଳΖýá‹VÞ]Ðm´bn•k¾øïpm]U8ï¼ó5m·Æ1€®ÊêÛu×÷ȸ|áÊËxxü=ø2zêŒÿúËøh-RÄDÍ8¿åñ,Ö|5?^ÆWnC&šà ›–ÿoø ›¾ÓØH|¼Ôç&šà‡š6þtÇÿÀ”yÄó¿-ýø_Ùÿ«Rÿ¸þòÍö[o](HùéÑ5ééù@(önôï¾ûêøTöÛïË“è#ß æ'ˆôƒà¬ ×,_ùÊWÄ>›n jø­·Ü*lœóós&Ï©qVˆsûí·É?.ðÍd¹ANñ¸†èÍp†V£E [Àmÿë™p~rÃâHÝä8픉åÛ¬Ïlé‡ýÞ~àf’öߎe7ÊÔdßÏ0×zCc¿ý÷s\¿ßò邏N¶ŽÝý¿êª«”»ºO¼»ÿS½1d`llsÇàÔ©Égjðöñ?Ç{\gÛQ͗ʶõ¯—|þí»ÓšËûï·?ñ¶Xâ·ùüñ¶éØ_ùŠ>yÌ7^¾þõä9¦¦¡ë8ž§ „7ž—qkê¦JóØòï_ÿuÙçM_Ñnèúël?‰†üäö´ÓO· _ø÷WÞ$÷Êäp¹1ä¶ÐD¬¯ cvö‡v¦mÿg-ãƒw´31–ü+SŽ/MPm=fg|hµSÔl Á«Ï!L4Áw>ƒSÑ­º¢æà´½êsMðH³3 >´Ú5jjƒG}a¢ ¾Écv†Á‡V»FMmð¨Ï!L4Áw yÌÎ0øÐjר© õ9„‰&ø$ÙZí5µÁ£>‡0Ñßä1;ÃàC«]£¦6xÔç&šà;ŽÉ>ûì3¡÷«É ®Í oó5bjrÖÙgþSÒçÞ{üÓ´ôÿQGM_mùÔäK´-Xx¿¿üeÚ¶Ï}v²ÕV[YNïý)l—î+oëµ×ê¶q߃:hrÅ/‰a¨Õýÿò—ù S¾Säk ˜’§6ç­¼²ìû?ø÷±}2ˆêØkùi@^hƒiá8W†8b–mŽqÆómzR‘±;¼õ­ú°‡Q×ÇL¦š`®®S“çÑ“Ž|n‘PÁIJÕ»ùÇOQʶáf’õ3¡Öôˆhû¿xñâÉ<úš7ûA|E­‚yd"ÿþ׹'/ÿ?ö¾n‹âZ>ÀÄŠF@)bA±7°‚½c½×,±%×Xb4j®½D cÅN¢"ˆ *b¡#J±Q¾ý?Ï9sff÷}ß$Þ”{w~ß·sæÌisæìì¾»³3©P~Jµñ&]„Çw̶njÒçg …&yÖ¨_9ãPðåï¿ÿŸ1ÛLã`k>P€m»ö&˜8q"ºLÈá(ò›oâçÇgŸsN¬/è?ä`Œ×uzí¿à‚ #LÎogŸ™ÐLcTüšämû2‘Dúh4ñºÊTìæ®%çóZ”k^¡Oý ZíÓHÁYB6ƒo—]vͱ÷’óçÃ@lúyÎëC¦ô/a¬9¦_ ù{<ÒŸsŽ TÆ‚¤Îf絑½ðøãlħžzãüEÙ>ûì“í¶ûnÙïQ6`À˜ç[Uo?Ê=È)§œ"má'ö¿Ã5‚öðÞÈHऺúꫲË1~øœÚc•RB6þrŒŒ)G˜4ª€ ÊS„h}®_6ÿ!VaˆÀ«©Èó¥þœ?Š_.ý_ £œ£Bœ&@‘!”óœeüåüQ,ør!|<¥lÞ?Í_€§¯I¢~>Ø£HÃó¨hc±1‹ãSY”meÍÓ£q07*Å•úÍÑ©·6’BEmeÍÓcÊhTŠ‹Z#”R œg ÕE´•5O€Q).jPJ-pž%TÑVÖ<=F¥¸¨5B)µÀy–P]D[YóôX•â¢Ö¥ÔçYBumeÍÓc``TŠ‹Z#”R œg ÕE´•5O€Q).jPJ-pž%TÑVÖ<=F¥¸¨5B)µÀy–P]D[YóôX•â¢Ö¥ÔçYBumeÍÓc``TŠ‹Z#”R œg ÕE´•5O€Q)® .vx8„ˤ|ßÅk .]HüXê½1ï Ü­k7ìÌ¡»ÍðJ-?@¶Îzëbg¯¦nöì9nÜ8ÝÅJD†\ÓyY ›P2wÓÄz ¯¸Q?kôœÒ{ YŽ¢ê½ Å“‹;¬!x¬«#9ñ¦U% :×~bà i¿Ö’‹I•Ê*‡T?ðRœ’Hëˆ(I³ô¨xÊ;öc-à¸ï¾û¨~!õ̦Ÿr¼ ìÙó÷æ›o¢f»õ±ÓرÇëÎ9ç7®cÇõ@ Â*ú{x »úª«n„±ûÙ×nùåWþÿèã¤iïþûîçíiX¿ê Q «Ö~"Qg&ç½ *jÅ_è#ŠÀ®tL"Ç„‰i(d±Ž4f±ÚVËÿ¢ ŸÂHû3¿ó`af84iª”Çï¿ÃNxl®Ѭér®qãÆÞ¸ÈѰ~ÕÍc­ö«êJýiÿ«©åÿ*ýO&•ðÿeýçœsŽ»÷Þ{Å#[l¹¹;èÀƒÞ»{î¾Û}Š]ëzö<Ðýåö;ÜI'/4:~Á{µÎP©wø‡ÿ÷Æ.Š+­¸¢Ô¼ð ?ì¤oþý_¯»Ë>füSÊõ×]çšcœ=ê¨cÜšmÛ¸ºç_xÎ}1w¯ } Ä &±$ö. øOúûü –$Öÿx îVXaE‰ÿ¿¿ðñ¸ãŽR?b0õ oª´$…ö›~ó˜PÚoqÀ:îFÅ„Oޱ»X‡YJbã…}2i"â 9â@’*•£×ÿÆ›o¸SN9µ™ì0º÷ÞûeQ?fùÖŠEBÛ¤±õ‘êâýwùb¢þNè=ýÖ¨q·é&;,€®Ðð‰“&¶栆1b&³ºå7^‡m§H}3Ðí½®é(y§y×즉µÚŽ&MU\ÿ'|‰º˜ #œ?hc´c9¶õ$™4q’ûE°­zü5’¤’ÖY‡>þÇãÿÉ'Ÿy­Zµvë®»®ø‘ÏÁn¯øTÌí´ÓN¢‡DO¤û;¥qçÕ·G¼íF¾3RøzîßÓ Ð_äéA+Gïgu" RÎû?ð þ&¡¯ºm·½Ü#bn÷Ýw—]!y„@nøð·àʆã…ç_è>û¡”/½ô2·]×mÝ ìû.v3½ã/w¸/'bGÅe–F½ê?í´ÓÜK/½$÷OC^âºuëêëÔÿ}úöuspÆÄ{4)þlãúÁ"Œ²i[õþ/õ«ïÅKpSµß?ÚIæ) 2u±‚ÞÑô1¤È ÅÜ£E<ëJÿÓ5Lš¥GÅÿ\×ß2þÕÉÞÝ(”ñWž‹7þà¢T=ÍÃwÿ8•9äe\¨7ÿDÉ?ÅÂC¦N±˜1®òÜå‚é^¬9Agö^˜QŸFýßÀ³Ž4ùCÂ.‡0SúmÖQ5ý2cºs;lñz,F¼•ì–£zûï×m6“ØVåÓ$Ý1?ÕÙsÝå³~ºR1 ÁBHŒÎc5Q‡W„ÊÐþ¾}ž‰–š!ü/Ÿ’ù8¤¿Ãæ‹ûùóÓ7AX¶>Ç¾ÒƒïÆ›n¬´˜À€ ?a¨&,J"bÿW2/^üUÈIÕIeŠøß¡ŸŸQIߣÿ:ø ¬[ÂÚ51þí³ÒLª‹ë[pééUf ýê°CE&gTëÿssnˆ3ÓU-ŸˆÏf,¹Ó8,Èj‰ñg³·/¿ò²TÑ6‹á¯8c(t™‡ýJg3ñÓªjéÜðip]Âk”AÕûþüùÙÛoÚ_·Çš7\W¶rw'òs'Bó=?åÀê`»¬“’ª"bþüyÙvþ =ŠO">ÚFÏ?×:Èã¢,]íPA’"ª·Ÿ³èÿ°^P` @…þ0cñ—K ‹âSDuýFw•_'G}Á×~Íø@ÿÙ7_Ͼkùdò¹®öI]v>f5XÚÐoXpnÀ©þ÷Þ“rè!ñ3s=ä`™¡{(`~¾ÅϤî[¿k×É¡8³‚)Äl{ÖÇÅ}º’u&gÆp—9nL ±>Þƒ ÇŒMI4x—ñ|ÙgŸ}Ã5â¦oð´ùLbTlk/ênÂçP1Fûg3£V~Žka¥]Q´m«Ô6Èâæ\Ó'$ቌü ‰Ÿ5ÙõçÆýµ$’+?U¢nõ[=®97…öxçÏ7j}"¶‘+È€ààü±Ïeö`å÷ ¯YžÈ°3.EŒ×xê?E>íWý¶|˜Õ-<õÙùX|šíáçYgŸ}¶´ÿrì.'¾ÀýÇ€~ýQ¯÷Ÿ:c(UÖpüJìÒ£‡öbåÖ[nEÛÒëz†µª^ˆ f •|íüijNfdkE*k;½ô2ÆÜˆÊ~Ä}Ìö…¨’B}=>EÛ]Ú¸ývÛ£*T(]µcIŠøiíÏé1©¬jÊ«™˜ò”úk»±rü)ýŸÄNmÇå1aÑŠQÆ_m7–ñ"¥ð§b¨ÈÇ\Zª Iÿûã/¾6“ß:¸ªájÍ4ûÛ9r]Æó&×tÙeÁ%/IRÂáÍ~!L3¦%¼õžŽrä“~áÁÙóêÝ `ΉT á“GÈ$áµDR•#VV¢P‡‹½[«}{תukWר1Þ mì^6LhðcÇa” ŸšUS^±\•“‰ÖfðŒÒƒÕ ‰è=!RJ½ÅZ/÷5óí/4\h'ãÍ-}ôܲËI5ýJá%€¾q]c×ë¾û\¿>}~@›ûàzôèîvÞyg÷ f™,êØo>ù–˜€Ùš2×§¿¾¡Ûu—nÌp0¼qžZJ<‘Rò˜¶ßúŸdXÈõëÛÏõëßÏõí×ßõã?àÉ“§x‰Q† À”Yÿ!qà)™åô#^ÌÕŒ?©"•IjƒÔǤÀæ¡FõK’Lˆ¥Ø¨Q#÷ä“O¸±u_MûÊqúé,m0ªª~ªËkÛ\ÐOž`ºÔ)%¥þEíç^xñEó¤»3ècÖ‚zÒIüÿဟµß±FÝîKÊKBeˆþOÇ¿Zý¯Ô1žTZ”!eŽE>þO9ñ$×¶m;%Ññwîo~ØFË $ö½þ³B¤YüÂj)« ÀEÝÊQÿD3©0…ýQ$ð¾æÏvƒ^zY(þÖ…«K›6}ºÐ®¸òJ¨¯s/¾8(1gý¢y³pþ}5mšÔys+ôÿùš?»—ñvžéª+¡G %«ÿÖ‡$‹ç¿g ™j :!4À˜eR§”<ò?Œ6Eÿ³>0¢ IåâQœ”ôè  tÊ\WEûjú4á]ye¥6Hâœ33­š5Ç5› ð7×m¦ïp½ßÿÀžãáž»üŠ?ýf›½ù%­™þÕtÇFü„ùû‡~Ä="9p(U¸¹óçŠìiàasVZie)K¢«ÞÛ0{‰P0í+ÄD£sŸbfò°a¯;ìîF¤ÔyÄ®m›¶BV­ÿážàñ'wc? ›¿r§Ÿñÿ”7wÌ 1Š´òJðô¿0èdl)f¨a&Ý/~ñ _rð1hY%¶ÉÁ}Û^ó¶Ãê#Ž<Òa¡g%#¹ˆS™(ʹüäO¸>üÐM›AÛÎ Z˜U*`ÒO`[y%ž?Î¥}:jäH܇qö’šÃþ—T#þ®ùócvËˢ㪫®Æ8F{T[Ð T€ MLR l™ô ¯ñ$ݳq$Aÿ˜L²^ëåðà8i?°øû³ÑððPúÿ‘Þ½—Ù‘;vÇì"ðÓÿ”“”xÛÔR‹ýÏ™ÔÏ=÷œ0°ÿîäS1‹+L‚ßrË-1SmsU’smÜEôc¡{wÎÙg»#ÞqøDLh–\rI·]·nb¿é_r‰%>ù•â}˜Šµ¯„Ÿ _|þ¹Î~‚ð“d†[¾-"Tù#0 1[ÈóH‰!RJ‹íWÙaDJü¯5ñe®Ô_úß‚CB£Œ?sóòüóN؈cG9þØý£~a´T¼þ3–4EJù_<þÊ­»š¤–Øé¿Â +ÈE‡ÓÛ§à&̦€QaÚ:¦{3µk·–œ,Z£Ç,L©71l,A¢Ÿ¹[Ô/Î#ÿÔO&¥Qœ ôâ>÷©›è?W`=¶w÷ÿíwÅW…öQ¢Ü¾¨”Ô+$Gì†8ÕïïßE¦ÀÒ 5 Y+BBÉLjµ_õX£Xj½F›ÐHú›©šþ¢\*¬ÃÎî»ïáÞ>7|ƒÜ{ì)üÔö"~l³mW7ïÇyÀ©~N??üð_‰¾~ýú Ù¦´ÿèúcº3Ó~ûï/ù¢ê—V£Åö[ÿ³}Gã3˜k®¹ÿ×øðU׸Î7ôn·—!Êq¨CHÿ“¼),zÙƒ<ñä2Öè¬ÿµGX‰üñáÕä)Säʤ)ÛÓ+ŒVMìRM£bá¡ÒF¨[k­nEþˆ VÊRˆTN ¨£J±RsÁ Γ&2¤‡Ôÿj‘J+õÓùóoø›Ã¥ÿ±å¸[cÍ6Òê³­ñÕâoìÇú©gîü'™0û_˵ýOFtþBú_I`‰ól”»Î:ë;>_øTdáR ƒ•]^¡qô×åÆcòxGÑ‚Šr­ýýúöw¿½àBR¸ñ kß}öéZ¬ÜBê0 yæŽ<ò‘ÕºuK·ÿ¸™³ð™…W·r‹U„¶š~>\¾àBÕs‰'º}÷ózÀKmAcb§ŠeM¾ÿM_¨÷2D9eý„öSVô?.«"4Xü¯ÕH{x.c%˦|1ô“—ÿ«¬¢¾žþÕ×"íˆ#’¬ãâzÂ׳fÍDÊð×¢ÅÊbê©§á0åîÁ‡rMðùTl¿· ôBì­ì¼ÑFø ìuù6ì ù$ìõׇ)H†á1Ö¿…ëÒò|æ«hßbçP‘uäQˆ¤–­Z»žûàfÏš¥*h›xDuTÍô«_î°ˆ¯Ã`³æ÷›ßœëÖ\£÷ñ'ÑÂàíUcôN‡kùO:½´\V}j1uøЋO(1>0FgÍœìXÅ·CªG‚ž/ î¸ãNwØ¡‡¶Nàrlùä£O ±züáé[ ÷++-O:E³¢ÿñ@ˆú`¯>È«Ãùs¤ØBÛ8 §›9s6x•{•Uð t¡ývþ÷ï‡óç‚ Ä–ÇùƒÏÖÕ Ú†Zú½«*äªÊ:‡Eƒþñã?s÷Þs/>Ñíåf̘!òçÌšã^}u°·Òð¿õóÝ&›nêÚ¶kïFãÓ±Ççãaž’€®È!I€ÄJ£í¡ÆÓñ¡³òÖ¹ý0Vh½—–Ë¢ ê¹ø÷»V«·oæ°i>wÝØ­ˆ{âý÷ÛßÝ×ë~ù_ìÊ{ì1Ç ŠgaïG{Vm½îÿ›À\va_|RèXÈòú)(íoªçQ¹¤Q‘7ÔC(Õ(BeGÄ1•ú½×Õ5ꔂ_KÿK¤H1ÚBÄüdãÖ{¿æ2urà/ã¯<ÿ~âýc'ÄO©7~òø/k ‰3Ù é+AˆÅ4hYããOâú7ÑóÎaËX][´í×j‡c斑﮵ƒ¾ûî[à4ñÇ ×Màú––^f)kég¥;Œ%éy‚»ï±»ûÝo'ÔüžÖkâíž¾A3Fê_fiÎ|Ò$vÈh奠ýß}ÿH¤Î¥—欽!Q-ʧ."†7yZCzÅ ä 5³£µ_Y¼ÄD›5×RÖ`AJ·*×·©¢ß«4±’§ú·Ûn{ÇL_w'žt’ÌÔyïý÷ܰ7†¹nx@dú÷Ýw_wÛm·áAÈT÷îèÑn:ßÒ#Q?>ñxqô{>£eHºÏýÝ^|`å›®‰¿ªÄŸwjž'ÇoR(Çÿ„·zË•D~\W®Ûá°Î@3·*~ ùý=uÌ YK¤Ð>¡.Ð-¬ÿ«ê_ŒþÎ*õÇNdÁÅþŸ4³ôÐe²þO•økŽ’À;ë¨1åÏAåû_ï€Bè*EÔÏ )9ÿ‹ýŸ§ ¡{‰áøÃ¨ÇÄVÁs,Kj§•T¿],|RS¡_ÅÝ‹}ñcs¯½ôaôŽ;îிþzÕ»–xÄ4³*XÉñgîÜñ›¸‰¬“‚ǤžaÛ²¥ÒÇÎ0T=uŽzn¸þ᡽Ú^1 Œ:$ŸW´ßê¬rüÏÉõd¦?ú˜‚¨jñÇ«5ÃN’ïC_ÒlýoúÑ@ðÕɃ ªÇç‡"‡¾Æ§.˜]Ú³â;ìp§òq\}õVr­¸ï¾{Ç™8¿üå/C½¾-WóÁþÕW»]wÛÝõG_pÍ7ßÜk%¥êÏ3kIjà>„e’8@ûùqîܹxÐ^'3öRÛH[”Ø®};ǦN8Á]zé¥nëm¶Æ ¢iÄÇ!R·¨þ§¿¤yèˆV­[ ¬1еþöÛ~›+ñÉYGc >ÒòÊbTÊmÛµÁ޶¢Ÿk]zÙ¥;l¹©Ó¦º£Ž9Ê üª ïП\ߋʱ¸ÈgŸÎ›;“_ð’ëtÑoþ ¨¶yBLQ{†ótGwý ×IµVÿŠç_$6©š³zøÁ ÿtÌ Õ‘ÉÓQä?ñø“nûíw@A4j%˜Ù÷Gu¤ûýÅK gåhò”Uø¨š$Z<³VQпҊ:K-ÈnÀÿ;vtïàAÕ£˜Åô裸—½äfÏ™`Oº'Ÿz 3®›Ê:tôì©1 }mÚ¬áöÚ{O×ç™~îöÛÿâN8ñ‡µ±FÝ_`jæN=å4ÜWr]"ŸÐ/‹yþ+¯)Ñ¼ÖøC_¨ëà;èË¥R¿ömꔂ‹X%Q§NDÉèJÿÃ3âzÌ|¤ç«º®Œ¿òü+œ4åøó/ðjÓ§ô¢Àó‰ 2 {m˜ûÓa‹éÙäç¥e­µÖBu[³M[äz‹b‹9’Oº‡Ï¿ðróNHEýÂàù8fÂÈ8*Ú®ÙÖm½õָ߯m€…M‹…ÈAþ6mÛà¨IíH¤Bÿçh£½Ók½†ÞÀ E¢Ÿ÷OþRZý!¥è ¯"— B†zÈÎuÁâ–LÔÓ7^¡Hô‡ ĘÄ7Q?ßÂÝ×ë>%Ù+/¿ˆr×íº‚OõsAÜçþþ¼ÐnºÙæ®%Þ”YZ\ý¯âÅ]Ò½I¹ØþÜ ¨Í2„?jDKŠÖæÚ?)¢:Õ›2ãŠbüyÂ{Œ5•Ì^Ÿà?ÖEÿ§t„…Æ!oQÛ/,5ôÇö›¶R1t¢g¬{ÿ·kÛ^ªùVy˜[òB>ýt¼bPî´á Kœ N~ðiÝJH§ÖÞU=Zÿ7nä#YÄ܈ ñ—9ÌDÊ@ÛD *:®ÛQðY©¦ ¶ùÏI<Æô³Ès¤Züñ5ÿøœIø@_Ô/• îÛ¯¯Û{Ͻ¤¸Õ–[áGÓÓáå€ÒèqÖ­CqÔH¾ÅÏÜøü‚³™†a±^&ê¯6þp6ãž{î‰zç°Æ ~ =ír/„»Fü“‰ÉQ£ýJ„£Ñ ÊÆhPB«ÃOô|uR#¬¦?ðCZLý´’¬­ák‘Œm¦%—XB ~ ×s6ˆã_Ë–«;lËhýÏú ÌŠ”;ßf޲:R‰ÞÀ¡Á!ÄàÑ£Þ‚%Ä6yG%Ÿb õ-ñ0DøýQ"¡Lî;N>ñdAòfžÍÊ“øåŒÃCŽ×4²Zü«ßD£,nNVñÎ+b±o_&¶-¬ý¼':³ÚÈ4tÈ7k¶·­Šþqã?uŸ|¢ò¼ªí_s =Àú´ÉqþDÛìzÆ>-¦~8Oí¡Ñ~òé'qžÆeµ®¿ ó?õ|6~¼l€Aýülÿ°Ã•Ù\XÓ ðak]‰9à ÖDTÿ®¿r°Ð´ÅÌ¡Íq‡¦!Uÿ´.“>ýô“qž´2þاæÏ1ï16 (_ûZé€ëU¯„ÏöN9ùd7è…AnÊäɲHµÎRËdÃnâÁEÈ£”:wòɧB\æ°Î¥{ÿƒ÷q®½æ¾ÄâÚLÇ{ŒäT½(úI¼(þOõ'^S]þ(4FXê/ýÏ L"÷9ÿ-¬(¯Œ¿Üi åù—„[9þüÛ?ù;Yé°Ø'|(\Ç·ÀIÂB­î¿ÿ|­^ä?äÐCqÌ\Û6mÂxÓûN§E’Ñ‚‡ÌaAa䘶éîKs-4¥ú뀃£)¥Â|íy(…©"ƒþ6m×T>P=Öû15Ëc˜=$¶aº;f,Ë›&{Ê õ° ÔïÓ¢è§!fo¢ ¦ެɮ'Àö~¬7¾IÇC·‚~î®rË­·¸yð½$èçÓû1µ™ŸƒQ~´Ÿ_Í·O0]Ööˆú—YjwØ¿1}ûöqO=ñ”ÀØj9ú» _ÒÃÂÚŸÞÐyû¬ÍÅö‹XTJ½ùXx´`|©ú0ÀW²u¹€6&ÉyˆíçÛ ]CȈ¤:§¿èÿT÷±ÇãÚ·_ k[­å.¹äRåÃ1õ¿GúFôú_dƒD¬1!ÿ¨ÿ­i’ó´¿Ôï¶ÚvëÐ¥÷~\œŸúÿÞ»ïB½vF§Î…Ö>!áÙûÚPüÀö‰|ãñãîU쾂@„¡Br2·ºÌ†L_}ÍÏnð2ˆRª šqTÀ¨$¿û®¿jt|»M¾•pŽ )üA¢ÂPJÚöÊà—•TÕÎ?} CIü!;| WÓOš»þz·Û …Hµ5fFpFOófÍX¥É³“`³Í6Å.ø©Æ«¢þ¿Þ¡íÙu·]±Ž—áõß…~Ø»§QÎ6Ûn#c#ߨ‡ó2UUCçˆðcÓL*ê§]¬”zó÷BÚ_ÔÏæ“sJ®¦_-’·©ÿ ~ʧ~l!×06æþ^½|£¢þ;QÜewøýÅY?Ó§N—Y7œÝ2 9g:fM›6=Ä׉'œ(u½qíÔv™£¼ ¯_+q¤J&ÉUÿ¦›Á6ÿ©Ó½÷ß§õvÉwÞ!¥ÝvÝÍq·G¦yóç᳤{ãuO°8øëÏ_êozwÉ&ú€IHýÇs4f5·ãuµþßlÓÍÜ*þ3Æ^÷Ño^€oæøDŒÞm7Øæã×bÚ6W®É•ñ7ÁÛF{ø ÇLJõƒkÉZøŸ’.¾ä’x¾'úÕo˜Ù ý½Ø§LÉõ÷Ž;ï„ìÌaÓ éS­—#ÎÓ»ä<%/ÏSÞ[4oÚ<Ø¢T”'Uõšðúû`}C&êŸ!>ðÀú¿æý×ERÏØÊ‘Š×ßuÖÆ§±øtœ³Ó9 жàHJ¤”Ž9úX×’Ú›ßÀ¤<ž 26X}Ä‘Ž'W_svûZ*Sÿñ×ÚJÚ/3ÿs‡Ò±C%ÛÆ™Öv÷† VDX‘2·k2óŒ¥GÒõ¶ou»ÖYgmªTÔ/ÈD¿–=u®Qʯõ±ÂôG (L™äøÀƒJŸÄÓªëZ(ÖÄúœ;İ=öÙe—e#GŽÌ¦NŸš±nSìüƺ£ý®wÞyg ÅÍjöøce˜Ÿ?>ÃÍcÖ±ãú¡þ½1é®^jŽùzübò}üþûïK¥™¦”I)Ö>¬/ô÷íÓ§’Üdùœ»á ·üD~å•W*?l{òÉ'3lQa¶´/†E/…Ž}zÕUW ?äE>:Ó|”ä3f|Øð&PxC£FŽVÙ^7m ~æXD<Ãra›0qbÕ´ySìÐ÷`öÃ?d?|ÿC&;£ùóŸªÛÞzÓÛÙjÛ³²ÛW´ çyj›ùÒçfi°¦ŠÚk¾ýnŽØ6u:l->Èð@:ØÇ—x®à“TÉ ÛùW]—,]}õ5,²Øõ³U°Öá|¢ L4)èñmÝ»OñüçõGÆŽRww²„™ŸYúÏœèïîÝ{ÿ™ÔaAØl.vÞ”d~0È󨤔€©Âí¡‡¾ëÞ½{N?>• ’SýŸö¹ì2ÅøëÞ#σO˜OΚDBPê[Úß÷Á×ì‡ÑÞóÏ?Ol¤?¸k¥¤*2SÔ†7žsÁ[pL…ÞEAÐ6ꧯxZ0¿^vu²ªˆ;kAà¹çŸ+´¼þ |v`†uk`F½\/¾øb©#O·®]«ªçx­úÔ¤ý‘ãµ5Òrp«ßÛè·¹?è.Z>-F©ì<ìÔFy¼Ozn · ç„Î.¾ø’†mƒ^¹–ð‰¥—MY“ EKW]ƒýPÏñ›ô)ο×ô©?h7³ó”uÜeQÎMœ?r~â\b™×¿…ºªé§Ì‚a‚ÂâÍb6ýrñð%vX4ÿŸyæ™R}·yÇw¬*sÆakë#½‰"½a}Œk°÷…åÒ§‘2@—\rIÐϱjÈ!²%ÇÞÃqø‘2þOĘËô%îU›â¾€íˆ>ýþ‡ï‚¬Ù³fg`l´þbÿïAhS‹«úë v}4îþèÍÉ–‡Š¢K‚LÈ4Y–'Rò¨¤”€ ùâ&ËòDJ•”0!_<ÐdYžHÉ£’R&䋚,Ë)yTRJÀ„|ñ@“ey"%JJ ˜/h²,O¤äQI)òÅM–剔<*)%`B¾x É²<‘’G%¥LÈ4Y–'Rò¨¤”€ ùâ&ËòDJ•”0!_<ÐdYžHÉ£’R&䋚,Ë)yTRJÀ„|ñ@“ey"/Ū¥HÉÌ  V^ðô"¯7([n±U6Íoå,’ÀÊä)]¸-çñØ^µVzðÁxøM lú`¨.;íÔS.Þ¸Dƒ˜³=bGr#–¶ o¼ñcjF”gÂhÖ6±Mð¦Ä @@ELBfò}þÀßþ&ö™ó™–õÆölKÍ4{6Ûtó`Ѧí!̇sLEý3gÎ 7W”̦”šgö(É„4¡`}6,y0Ô†BUÁG{¬Ý–[›ÒöËVàÆ¡#°U±ÑY^ä'>n·œeØ$ÇcôÅœ¤$%Æó¦ßôðG]ófMånž.2d9éRXør‡*µUPÂ"ø¤2€º"¦Ôo¾à9¶¾NÆ6‹Ž1|ÐjNäà þJûßh‰ÃÚR—n oº˜óœØ ò`-•÷×]{-¨ê3þH‰:lÌÕó?Õ÷öðBÏX m]º¨m)ÉÁš)"S¶¨&C!Ѷδ-ñƒm7My×^{ppü1™Œÿjº ÇÛÖþ¯ñÕ~D²ý:~ëu„ôGqÊ!À€öT¶ßôS$°Fzå¶&eèçX^=™ÅIm”Êm¸ý¤Á,3-ú­ÆõÇüVa[ýAhð00žn&®¯Á×°!õ5m:üÈ#$^’&`^ã•vwÞù …4¡`̤óaû[éÃÚÂáÖnƒÃ}¨Žë®»Î××î¶O^jµȰVLô»÷ÅØ^¼Zš)1ºE°Çü¦öÕ%1êm»öú@«ñÆ«Âlm‹…n’‡[ä³öóþãG<®H`þæ›™Ò§F«¶ÅøãùS¿À´ÔëdÈNí p!þxM‹ÉdDLÎøýùg_Ûù"+ÒE„ø šºy/ÉtÞº]½<Lz¨ÇÃÂø`èQ>T‰â„ç»¶EÛO_ð!s¤‹ 0¦Ð¶Ðvú¤Ð~úôþûïÙ?üPh“ñ´nÙJ¬±lþÇgr^_ÔEý'M 4¤gÿÿý÷Ñ4hI8Ò†{¸Jm” >© `‚®ˆ)õ§¾¨ì€*µUP¥ÿáñK✠Œ?`Ñ#åù—ú¢<ÿЍâ*(á|RÀ,Öù׈×H›1Š‹˜ÜEàf› ¤ 7ÜPáÛsoLå÷µ¼Ââbç0S[º¾ˆ30ÅÙX7Äz£° 1לÈ%Ôí‡?øà·Â/–÷U•ú¹Ð#uЊ%š,¡ZQ ÎR„±VÅ’K:1ÜL¼ÐÄ{¶‡‹-o¹Å3Úö¶]~yìÈ–$Ñåõë:ZÙÓÖy(M@Óš×ob­–ö¥þ?ôW¿’Ï@öÆî¤á”;©‡‹lâm—;õ´ÓÄü„dÈkCÝ_Ë]3º˜hÉióú×wüL o§÷BJF©\<Ô%öàƒ¹I#„8m¿ p0ÿ7Ôþ&ÜY$IEýZÛ/¾õßúS~±ý¦•u©þFˆ‘bR~JˆÉv"¦qº¦@$Ÿ•ÇôW‹¿õ7Ø °îæ¹2,šy©ã¶’‚J~Zÿk»ŒWE (­Kpò¿Qš´…ùŸôÿ×ô¯·ÞºX|w¸|"íOúÿ¸cuã±–Ïk®)ÁHßpkç‡yÄuÁ.5š¿ˆÔmpnr»fYSñ#Î|ÿ3ÆñŸvèbÍ”aŸ›àÇû‹ÝÚùÇ~ãùÿ_ýÞmÛ­+JÚ;–.¾ø÷z 6µd„ÏËÑ_]t’ֱѣ}½s;ïÜݽóÎ;Ø©õpÑqðÁ‡8Žq—áqƒmcÜ’°çœü˜–‰AvgPéÿ)“§¸ñŸw+®¸’,´Ûé©FïóÜ3æ]ŒÕ«aÌÆx½í¿aÁâ¥p¿³ÎZksû,HPUÓdÚ6~œ[i¥•]k,Í{¥4îD?fǸwaÛj«ýRD,¤ýŒM^Æ`¬Zj©¥fÐeþJµUÂÕô+•¯‘,P¡*Å“2­SΟr ÜþÑøÃg¿ð®ÁèSÙÅu ¡þy¸O¢ïš,¹„[ìø‚/´K V ~öœ9îÓÇa©ùØUï—rͯÃCôÅIA2€´ý¥þŸîÒÿþÊVÆ_yþá’¯þÌ,œ%8ÁR<Ï·´®<ÿ~ª‚÷ü;ÿɃ¡|C ]h'Ù½ˆÒ&JŽ'bz»g’A‚*€@¤Ž,õ—þ/ãOO=W’3Æ@ÉËóï?qü™8q’ìžýöÛþ-Ç¿x#ósŽÿ^x¡ì8/A|·ßtÏßêõx0p8vZ:þ¸ãå-ÿ?cü1ÛL¿¼uƒ-ÜýÎf@ò4o‚Ã;ÜÏ #ä¼RòòüÿO<ÿ­ {’€øgœÿ¿Å¹ðÚ°×kƇþ+ÄÛq>Ü‹ ”¼Œ¿2þlîTÌÉORæ¹…PÀñψÓWê×ZñIéÿ2þ’I?çý—si^žÿÚó¯J$z«Ê@IDAT/ßxbËâóDŽãI …ØuŠOj­Îç¡ÆXÂoÞÃæ/Ÿ¥þÒÿeü…3GN¾¤Tž6Æ`ôR¿$Þ±ºÿàñ‡#"[Ä¡RrµÇNÅ'µÿ ÚÿÏÿGŒá^zé%õ3t%‡_f&š©+>Ë“d>FA=¯Ç\ÝÏ#Þ)ئÚU Tò2mi›mø¹!’Ùö3èW”iíôÓ¡Uá(µVWê¿„ȰSößøþçmž /ªÿÒ¿þЭ›ž Ú¾ÐÊ2þÊø—¸·q(DÆ@ü[|ÿ3¯?ÔYþþ)ÿ¥OËøÃ`Q>°áè_pÿ…5Šüm0ÂÆo°Xí;(ÅbTÔ…¢p ®JEŠâ—n¥~½´¦~)ýŸ÷@…o ˆB±Œ?x@|RÅ1)ª<ÿþ5ãÏäI“ôS2tƘ1t‰˜1„> 7Ùùð¯¬+Šeü'ñ?ë´ÑßôQîÎÆÊ}®¼Îßòœ¥³Î:d:!Mþ- Eá\•ŠÅ5å¦à³BÓ/³„</ËXÂI‚‚¨uñIÑXC/ WÇT¦aW¥"E•çÿ¿æü·óüŸíÿ¡C‡¸‰ˆ7Ñ/ñ•ÿ4þ¸ýúh¼¥1#ñU@Š‘¤JEŠúg·Ÿ†•úãu¦ôÿÿ­ó¿Œÿòü/Ç¿rüÃÉôcCv»Â:¤"y‚Ô*ÃÛ1©ÔU¦OCUQ`ZNaÏPeFiŽ¥þŠnË}|O èдœÂ¥ÿÅU\b>Ô*Ëø+ã¯0l.Êù7kÖ,YP•Î;üˆÃvBCÈ•ñWá’òü #¢¥ÊñcFaØX”ñG¯ðé“Âåõ¿¼þÃUBÂZ…cyÿSžåøãL7q^”¿ó>‘Rq@IË)l~D^Œ-?()5ŽåøSé£Exþ"†Ò·:°ÓñÞãâáàfé니Íc„(=Võd@M2+¨Ô/΂¿JÿKIÉANyâòѦuB[ýNŇèÊøS7Žåùß –ãÏ'¦rü7Èð¢cLÁ3¹[¡-ÇŸ7êèú¦S¯¸Ëñ×fŗןÂ(+C¯dtåISÞÿéн’÷HX —âPŽ¿æ‘\^Ž¿åø[Ž¿é‚¡'?o%ÄCyê-á…${@#,¹B%cEµ¨–C©Ÿ>¬pP¥‹˜K®P¤¬¸õPDÙÿô|ôÂBâ§2¢ 1µþŠj"Êø+ã¯<ÿÊñdž2åý½°H)7¦æ •ìÕåø[^Êë/N”òþ/ 6Êñ·rð¬É©¹B%CEupzÁt[•ÁS“‹©\¡’¡¢:8=Æ_#!’Ð^*‚Rd­“Ï‚6Eã‰#Y<‰ˆÐ§OŠ£Õæ±RPL©NgÒWt¨wj>SG†ÚÒÿâ2ï£è?ï&àËø3ç0¼ÊóOG›rü‘¨g¨GÊñ‘Á„c½ãÏ›|æ«-Ç_q™÷QôŸwðåøkÎ)ÇßòúS^õjS^eTg¨GÊë/<"^;è?næ3a±Úòú+.ó>Šþón¾¼þšsÊëï¢\1c«k"’è6ÿÒ œtH#Ïñ³À¤ã ­•cŽP¨äP åøY`*õ«JÿK0HŒäÅ»Y t$X”ãg©Œ?õC #¹@ñîAV åøY`*ãOýPÆŸƒÄH.P¼{Õ@G‚…@9~˜ÊøS?”ñ'Á 1’ ïd5Б`!PŽŸ¦2þÔeüI0HŒäÅ»Y t$X”ãg©Œ?õC #¹@ñîAV åøY`*ãOýPÆŸƒÄH(,Bj=’âbELÈ<¤åE8F]ÉÛ” …RpE…WñôNq¥ÿËø«ˆŽ†åùg#L9þÈ(Âá¶““¦¼þÐåõ·x^Ä)¯¿åý‡DCyÿUÞÅaa‘ òþËn7Êû¯òþ §Lyÿi'„?þ=ï?ë°[=×í®L|!UkåQ½¢ä ð"«ùáYµEÃ…³Ô_ú¿Œ?œ UO òü«éT”ã ¡r¨>åø+çUyý)¯¿8?ªŸ"©¨Z[Ž¿åø[=n8è–×¹ôÈ¡êéS^Ê룼þ–×_„Aõ!¢¼þþ+ï?éGd~Ï]ÐØ]¹Z”=õYJïEHÆ×<ï}WYêsRBYA?W[ê/ý¯Q¦áÂ1 ½2þàòü+ÇŸrü-¯?åõW.ém…"¼cÊûòþËî©i ”÷ŸpHyÿ†Dòþ“?ï‚;æRyÿYÞ"@ìg~þB¤XEyýͰ©£þÅן8cˆFñl·ôàA¥ˆ"&–d€å9‰ù‚’Ô&¬¬)bb9@XžW™+‘¤¿¾n𫯺ºú:Wa¹Q¾E‹Ïee, å`yNc¾ $µ +k27xð«îôÓOwsæÌqwÝõW·ýö;ˆÐ@k€åy•¹’’T'qͤ¤QfθPŸb‹´± ,OÙ °’Ô&¬¬)bb9@X^Й•¤6aeMË2ÀòTaV’Ú„•5EL,ÈË :Ó¢’Ô&¬¬)bb9@Xž*,ÀJR›°²¦ˆ‰å`yAgZT’Ú„•5EL,ÈËS…XIjVÖ1± ,/èL‹JR›°²¦ˆ‰å`yª°+ImÂÊš"&–d€åiQIjVÖ1± ,O`%©MXYSÄÄr€ °¼ 3-*ImÂÊš"&–d€å©Â¬$µ +kŠ˜X–t¦E%©MXYSÄÄr€ °ÿ©è +›H …unçitþö»9nÐ /‰øM6ÞÄýrõ_Š,ó”À•êW\åѨ”\K_óµòê!NÛ¯Ü0ŽOàñD›‡{Éí¹ÇžŽþnàsnþ‚yp—IÖœ~1ÿ³íôÓ~è>ùø#iÿ†6pk®ÙFðv=j¤ûìó/PÌ\—.]\Ë–­Õ»¨‡ý¬úÅ$PQØýË.»ôì ØièŸ×Ñ?Lë­·žkß¾½Àf変蟷ß®Îí¸úgíê%Žù'Ÿ|âFŽåÞyg„[nÙ¦n«­·r›l¼©[®©Ò¥Nø”_±Å#y¾Ã–A/¾2/ê–ƒí6ìäVZyEP¨~åõ0Ú=æ½1îÓqãÅÿÕÚo|;ïÜÝ}ÿý÷nÈíS•Ãc>þSý4Ï=÷µŠ¹“öñåçÒÿl¸kÃþ¢IH‘’çá³nþü›ö¿ãÀþÙaÇ”Ì׃^y׿d\G©ÊÛ/Œ¾zæ7߸Ï?ï>úp¬›>cº[gíµåÇæº#Ì@;štUJ á˜3U×Ï£$Ky¬ÖÅc¨ ”"AÊ:¦¾þ¯¿þ±óªª§þ*ç1þöÜc÷úoºiS§ˆÍ»ôØÅ-±ä^†fü18è¥A2þ¶nµ†ëÔyC¡Í-bû×Âùüɸqîˆ#Žt½zÝæKËóR­j%«.È!úŸïÿ S¬ª®ÿ .pºê*µÁ¦ uê´¡œ³Zª<† J ¡á˜3Eý ê¸Y3g¹%–ZÂ5]®ið¸Ò¥|äɧ=z¸çq¾2õèÑãÅsW"?¥Måõh,F©ÜZ¢ìçŸÿ» ºwïîž{z’x¥*¬cúiúc*wñ4.†þ“N>ÉÝqǮŪ-Ü”ÉS~’ÿÍŽ¢þ êݬÙèÓ&èSåoH°&ëÔ¹S°åÑGsgΜ™×ãû¯hÓÚë¬#ô<<ýÌÓg[ôOÔ€Jž~¦bBýÿÙgŸ’%¤Ù³çdÇ{l°#ÕÓ¬Y³,öIµ<ˆ –ÿß|ó-•]¥ÿÛµo—]uõ5Ùüùós¨ádƵ´=‰—PfŒ«ÿ'"v^ãõ@›ï$¶ ús }¡cÇŽAÎqÇW Ñv‡8ðúóúb<¬½öÚÊ6r¾=Bãšñß§Oß‚ì|Q5)îž{îÉš5kì >þm·Ù&ûäã…°–ÿ©<È ”òJ“Ru ÃZž0üOÕ?ìõ7¼o-VoÅÿ|[~ÕUW‡óÿ–[o©hÿþðÈQ¾g>§Î×U÷Ÿ$d ¢]»öbÓGluþÿTÿ'Í `±…C† Í®¸âø¿Brž[<ÿ;wêyqü¯å¿ õãA»øœc &³Êr¶ÚDÿäc¨.;ûìsb kuþTZu ÃZžetP¸þ`†‰YõòÀ!@(¥*spu ÃZžc©ˆÿP òÀ!@(’"@ŠOìõŒã/ÇÓ†R ;òªé?è ƒCœó›ßxGDéC†ÉþxÅ•>®¯È$®1öl˜ÄuÔb|–ÇBÕô ȇ¡””Нû1RÏ…b­2þúíœkιĨЊÿ9ý¦­†_]£69ÿs(„²¡dê*òꆵ<Ïösøß$ÖÐà«kÔ–íþÉy…P ”ÌÝyu ÃZžg+ûßüÒð¦7òLJ&)A4¬å…Ú2þ½CJÿç"…P ”B5áÓ7>µA¦‰· Rð÷¾ê=Y¿~ýÜo/ü­Ôîºû®îü£¼…’éÇ‘ÕáÆ?Epçƒß#üI»F&fM7 ^dÂTü•[¸õ×ï(³S¦L™êÆŽ+´§žrŠë‡éû}ûô:µZC†u/¾ø¢Ûa‡qoDÉÏ£ÌÅöÉï+ªÍ\‡k 5‡ö‘…&M{î¤.Y•"Ì¢(Ëë(ˆ í3PÊ@û–\jø©ƒTÑ<ÒrÑÔ©Ó×¼Y3·gs‰ÆúÔ¸Qcð­|ûõ×3Ý´iœ1à\³¦ÍÜ/eˆsmÚ´‹ú’ö?ð·¿¹›n¼Ñ5n"aàÆaÀ(ÌÐ)¨q5’~´Šo0[dêÔ©RlÚ¬¹[mõÕÄßmÖl+º‹þ2ä5ÌÐy1ÌZ"#§§‹£À¡ý/¬nò¤ÉnÛm·‘ÙŠqÜsçº7ßzÓÍž=Ûíµ×^bkÛ¶^ŠÍÞw ó?ýËþ'];Ì4ûÅòË»o¿-êÆ}2Î]pÞ¹n@ÿ¾î‘G•OCXAùz िD'%Yl3Žëê]#Äõ2K-­>­ÅÿLÌ™2mªð7ÇÛGéS”Ø¥"Ÿ5¡¹1ç½÷Þ“:¢íý¨»õÖ[Ý’K.IJ$åªkÌ8XíQýßH¨Ü8»ÕW[]llצm`N E¢T±ÏB¥¬ ö 0ÀsÌ1×ñ·F›5Üë¯ CßÌq¯b–Ô–[mí&Mœ q%„ ‰;”Ö”G]äjŸñ[¿[¾¨çŸñ‹è 6ÿtýË.½”Ä—y­üšçcf±OynOÛL§Ÿ~†»ñ¦Ü„/'¸ οÀþ«#\óæÍ@Tç¾újºûýE ݘ]´ fvH‚Åi¿Î«¤žI?íü£á´]ÒbêÌþú·ÆìEþ3ÑûÇ¿kÕG˜[©ãŠŒ1<0ýLñÏO÷˜8jRù‹ÒþÛoû‹ÛrÓÍÝ/VXÞxàÿcþ¿õ/·¹M7ÛÜ­==—uíÖUðvxæÌòõãÇ4õž|’Îúˆo]Csµ-oáp?cˆúû@îOKQ–@8DL¥$­«Ï|ðAñ;Û0æ½÷Aè¹ÂSàJÞ‡| ø Üz›mƒ½ød,/¾JIcÇúgL Ù8˜ExæÛÊC:.ÕoŒ)nÇvß°oßxó #‘¼/d´jÕ*{nàÀoÚâùÿÞûï׌›T— ÉሣÏç•ú#&OÉRN—¯ÎáþEúÀ¹mçË{2TÚNÌO<è.¾äb`ÔúóÏçý˜06#ŸrmõU9œo?gÖ1tÆP^F¾¤ÜÄ „CÄä)MBé=׿ÈÿÁêúèy€ø½SÅÌŠØZpˆ˜ŸÖþ^÷ÝçÏWù¼’¹*&jûGõ‹øí¯ªZBÕºþ“ÂŒ¡UC›4ìÿ{ïµ>Å}-’RGž Ü?ÙÿŒk^ 7촡؇i@š/ò1Ò „CÄT Ѻûì­oPPÇ—_a¥We%±È ’€J¦hOFŠ\Âí®¯…²¸Õñfüúë®ul° Fy×áǻ҉|Ѝs\Tñ•Á/»n]·‹2¥Þô«^}sXˈBûÅP³IMʸ@’OödRL•ƒÕ¤¹é_xû©¡šÿõþGõrÞ¹YPÔï»Hü-Èëíg¯:÷Ì3O»nݺ ù£?ÙÄR$6±ä_½ŠTV‡¹u2Ãë+ÄZ.~:ÿ¯ º©ý {½ ²¼3âm÷×;ïî]°ÂÃ=è×am§ÕÝ‹/½T³ý¡B$Ø!ß~ÒÔaóD5ô4rn°¾{ò·ß~{Yèž»ïrgýú ·a§Î†^ðm7΢ÿªŸº!I"Š@EJ| ãð OüzØ¡‡¸å1K¤ÿ³ÜO=åz첋p.T¿4mÕRó¼ `ñ"Âäׯ 4/$”»b ›M7ÙÌsѧÎí5’>Çl”ð¤^jµÅl9¡Ã;Ô=ôð#ÂpÖi¹òŠ+…J‰.Ú.>Jp@5ÿJŸˆ0¯Ÿ4"6!»þúëÝwß}çŽ;ö·êj¿t±þÕ=w߃5°†»øÞm²éfn¿}÷s]6î’Ó‡ªîïX¯ãíï¸æXd›®Û¸vÜIÖÔŠâóú1,»>}úaöÛnôèÑnîsÝf›mæ¶Új+·ë.=à;öx í—YgA(+A‚Œ!)¤ž~¿ýös\Pö%Äñ¥—\êN:ñdPÖ»«¸hÿfuÄš_ÕSâk/ÒØDPÈñ_û,=ÿ'Nœèî½ç^R¸Õ[¶tG}$ ?nñ‹Ï¾p½k‡½ã&N˜ˆuäZ»½÷ÞÛí½×ž® Ö é‰þ®¿ë‚}çŽ=îXôÏj˜Fù÷À‡èŸþÙß}¥Ø7bd=Ön93ÿæÍ›ï€¥ƒÜ‡c?t×]Ïý 3©6Û|SšWH™ûë«õîÝcl›4ѭѺ•Ûkï}`Û^°m‰Ð~2¦þ‡‹QW'E"µ1RCU2¡M|íÛÿÊ+/;¼ˆ‘ö¿0èE¯ ÃysfZB„ )‡~˜¬AÆõÍè3LåWG­üÝÎ;÷¨hÿÍ7ßìfc-•ãO8Ñáa¹ëóL·>ÆÃ³Ï> kÿÕc\þ+®¯¯¸m¶ÝÖq}%ÎZüî»ïáë°Æ –(¦Xû޳¥}(s¡Í0;%mÿ÷ßçn¸á&X´À{.úgî<ôÏ@™ñ;öÃܺëut‡~¸Û çœ ²ˆ£Š>˜5ülÿn,ÖÊk±êjŽkšÉŒ×Dÿï~˙̖h +cü‰}VËÕÿ\«íᇖöŽÿ©Ûë Øó@·Œ_§Påᱩÿ?ÿò3÷裹‘ŒkĨÆõ>˜áº‡[q-zqüò`÷þ‡ïɘóÂß_\yf]+•™,ãe›6mÅ~ÎÇøñÒ ƒŸ#Ü;˜ÝûùçŸ9ÎÚì¾ÓN®çA=u¶—(qpήI¹üƒÍ2³}%ýC?‡TÒHZåƒÛβÛ?wþ\÷Øc»Wq­ÇË̺nâ:wÞënm ëO.³Ì2fFИªèÀpÿÊ+¯¸÷>xß}±àÅá7v+Òq.ãï°Ãp.´iãFŒé èª:\Ÿöpq-§4K”ù*‘gâu¶MÛvЃþyï}÷bõŸ^–*Âg£©5œéì0–æÖ„ÑŸ ‰<IþE?áQáµøG¡æøSêÓE…Tú¿Œ¿òü+ÇŸrü•Ë’FI-¦×ßÂC£ôiÒ7³lm™ºì2¬1„Ň3|†À[ži U}údk±¤³rìI”©“²W6o¾Ÿ1™:cH©Ì–Ï>û\ô¡EÙ-7ß,•O?ý”àøí÷‘Xç‚u;wïnâ3™1äíü 3†L¿ÍúoÆMAàÊ?M³ê·ßôsV„&“˜07©¥l >C…YhÃû˜aQ™Š³¬¡)F}Ÿ9Ðeã¥9Ã_`b±îàÏŽëëº6ø5†Šº|€³™4jÍ>C a yÄ‘BÅ.ƒ¨§«Ìºø÷?cáæª±„˜† k?iÞz‹k Éï¸0cÈxI‡]YBû~ûÛßZÖdÒÙfø´¸Tb © úÁdÖ‡øMEäx õÑG>øàƒìæ›o‘§?çÍ žÚúÓØ±™IEj–G ÷3†pîäg ©IfKj bHÜ4kÖ4{÷ÝwÒG‘“²ÆŽý0Œæüx‰D€R)œ#’‚Ö6L¹ŠÔRöÌ;h.gøNülñ­y]n–ÚìY³²£>*ôÐx„{챨ØCTõöÛó­¶Ü2ðÙ8júwÛu·lÒäÉ×›'eéS¯ã}™ñ£µ)1âKÐqæ™gf^p¡”ÙgX´_È„¯ sŠJa“my»vmEæá²Æa1ÃóË a]Æê˜1:cMeÕËøm¾-¶ã.gø\*3ý¸”‰.<\Î8.>øÚdÝsï=‡M‡}gð¤ô?ÃqÜxx8¢¼I_ªüºlcŒ—ÓÛŒÙDèÌ —a‘Þ` Xû 'ec6$òEø¨£gÚ~‹kƒµ]rØ–9%æesçÎËðÃ^ìHÛ“òl:ÓÜôNÊ©_1×ÁíwØ^ÛâKý/³¤[UÖÊK¼ù;'Ææ&ˆkåÑ>ÕëØ\ô]R¶>¥y÷ß¿œ[µÚ¿ÃŽ;føÌК˜Yÿ¤ô)|Û-±Œ)u áÐC:u6ŸçÛ?yÊä¬×ÙªÒ~êÄ®f°¿W!Ü©²œT-«òcnÚ† /ñqæ7|B+ýE\«V-³YX/‘‰²§M™*÷'Œ·m0S˜3׉7=Avç^”€cáIÛ[$P,)*…A´¶aš@ìÏ¥H-P,• ZÛ0M .õ‹+¢·ŠÅਕ ZÛ0M .ý/®ˆÞ(ƒ£RT ‚hmÃ4¸ô¿¸"zK X ŽJQ) µ ÓâÒÿâŠè-b18ÊPaƯè¸àÊ?2Ir;dÜç\ô_¹~ýõíËi§žêöØc÷P› Âp˜êwÉQg©`W2àWi± PB„K¶Ï7H¹y̰¶JoÝFØ)‹97QeúvÎll{†ëuÿýîïØueèÐ×°öÖªN¤é̕˻܇¢@h*¾aRâý©&n9̤eåôŠ©ÈÕvò—Pøƒ¤OEZ­“ûkÀšÎ«¼™2>©©¡_-ñ: fYµ=¼iÅ8÷å—_b}wÜìüEí=8ëëô5Ê#¨É·‰ífM˜Ð“3´Â\'ëaœvúiÒ?Ü­‹[ºm¶æŒ/•kí¢ô÷?ø@¤4ÇúH[oÍu˜ªè¯Ñ~¥Wž¢'­ý”§p ™ʵí6ÛQs7”¤ý È×iS§7åèCy+¯Ou»ê(-ˆñ:EJˆ¿`ȼè€â[{&ÜÌ:,ê­ šxoœwÚyGÁÕŒ?62œWæ A¢Â÷žÅ訟É|á¼ÿ{öìé°x7f ~+³øŽÃ,޳ÏþëØ1…b­‘r Üï¿ÿAÛ‰ÝÅõ©c}$&ãX˜þHLÊŸéüó'õ“ðùµ×þ·ø‹­ºðž:¿:Ôm·]7QÍïu÷ÝÛKÊ[mµ¥ëÙó ÌèøÖááÖ¾úåžîö;nw'bö…%î·ÝvÛËù@·o‹™»ï¾§ì¨Ã­Ä}ôÌPÂ:WtŒOê‹ä<ñN’x+«·¿sçN1Ý_n»Ýq6”vj»ôÒ˰ëÞJ"]zÜú˜ŸêÓóŸ;AâÇ©¬ ‡Ñn(ÖÃâé^Ÿsc¦·2§Ú:HÜ4nÜHf ø,f^pxÈïúÆï÷äOºk¯¿Å:¼ùï„Ý wóoðà!n»nÛ)™µ :º .“µÍ6Ú¨‹ø˜ãI~sÎÙ²³£ú³ó~XÖh¢u°ö·œgl>öXoÌhÁ¬0ô >›SÛÄYäLüÏI9L¢ß¼©(A ¨<´Qý¿fQ­°âJ ¯ÇnR/ºQñæì³Î‚öüøÓ³³˜¸KÓY˜é£ç¿Îš5k¶lSo¦WÓO»†b°Ð‡sçÍ“Yž÷Þ{/$Ö¹N8³hq³¿íþú×»e–õpFQ:þ™°Lêg¶>CaÞu»íPÃþÙÉuÞ¨³ëýho\¾Ÿáçœsî/öFzùOºÂq+u :åäÓd¦Æo¾î.¾øb7kÖ¬©×Ôá!¨×´øþÇçbîeîÔˆ„‡V¢çÛï~pwßu'l› öÈxÁiÿ3®O?ítáášgŸ}¦Ìbz¬÷ãˆgÝpÌ„ÅËÄï2™}¶âŠ˜e{˜0#eԨт?ë,nO™ð÷96þ‹_ÂÁ¼ræ©GËó~ø·?ÿòï(ñàÈHw£âZBèæðÏò÷?üž4‘ÑXmÆÑãdS>¬!§ÈÜõ©^ÞÔía‡–½öÚkxs?XÞ¼s'&«ãì–o¿ýVtÚŒÖa1䌳Qï¶ÛîRÿL²kw%3ãN:‰»’é@íØ&Ó£¶Ykø¦¿r-*‰ZÊ•‹_®üD2G„>ø®D›t†uäSž³3†È£m{oŒ­1DPzâóÎã®dupÆî4g‹Ï.¿üòlŸ}÷¸¿’Ó7¶+Y^+g¾ÄC6#… R{ž~*îJ6󛯳î;wÇý]]¶ë®¾ª¬…‡ ¢{K̪ú3füêsî—ê+êÏWÆöÓÿo½9<´ñ©§žÌ7Ï ÅVÞB“®RŒk‹—4Çg*/gœê𡇠Sã?õ[ÎÏ·ñ&]Dÿ©§žªÿÔe¸œŠÄÿ•ý£ú)(¿\\÷õ3á‚ÐýxnàS±#m÷n»íš=÷÷ç£ü»êÇ''ÙÞ{ïë}à²oº‘åäÇbŸ§’Rž"QIU¿@y&©KÛAø!ôS…]dFzéå—CÛÄL|ˬ©>ûjÆW˜Ñ9ÔcöP‡íã%þÙÿ·Þzüd†hþÚkC³aÃ^KÔZ½Š¨ìÓ„THòíÇ"îÁŽ¿íÚ¶Ë~øñÇØ?Þ²¢”è·¼þ@î»ØC±Ãd‡u8c NÞÂ|gdÂRŸM›: kÎ5—ú-·Ü*‹3ÅTÏÿxy°wäHòFýÕû'¥ *m?> rÈ÷Ä“<¿! Œ?|F%6°Ž³$Yfuæp[n±¥·-êÇÆ A¦ÚF}"R8`¡ 3+"»RúrèöÀ™#L„ð Ç§WÁ²ç)òýŸ¯¬Ï¦bvÇ_‹¿ ÞÚŒ¡ŽëuÌ~DœŒyïÝ  Ÿ! 9>ý ¸Zú±aB ‰3†`©7vÂö^£xyòÉ'‚)\çM®èÖiÿhµÌ,žçœ%ŠäµËâcÎwzo õ9çDý ùŸ´Y{ì¹{öCrÙ&œUB]¶+U¤±³®[xø­æyý—#vìþgä¨Qçß¹çþ&èÌÛk€T7Nf{Ù=Qð\âl@ÚÖªUëœþPðÄS¦°Ôÿ·ÞÆC&E)cQñ:c3á*ÖÎÊsbó ÑÏÙe’±sü!{ù¥—à+‰õP²Â±x.1ÇQ(à¥fˆÿg°#*²IŒÑgxP¥>*õT!TÃú͸†âÏ‹ñ ‚!QaÊS”úsþ(|¹ô!ˆ,0 ü«©ÈóeüåüQ,ørÅ0Ê9j‘ÇJÉs–ñ—óG±àË‹xÿaÏHy]GòE-ù²qxÎÄ7ÁLã°ƒÕí·Þ&°â³C/É×ɳ1‹·:Z²·à$1ýJþàCÊzÛnÓoÞÄ[λ„Û¹ºgŸ}Ö-¾óœÜ™â¿0›‰’ èï°(.Öƒ°ÉP|K‰üÑ®Føæ‹GªLj¥âi+¥ÇLÀ\C í÷ä¤c«MÜ› Ž5LFè…ù"_€Y]½‘•$¹½ e¾Ã'a^«Sý°k1ßÿð£;äCX7é:<ȑݎt&Šà òrÍ$ƒOy³jµ¿ÎÍDÿ\tÑï 6sŸí'oâêÁT‡õ=¾øRÀUVYÅZræ.ùÃeˆ‰­%.¸×cÁw½½õ¶ÐV³'qÔ¦íÏ&I#ÌûmycXçfÌø*±âLIT¤}¼$tAƒñõó\{{¸ÎàÚ}÷Ý‚~î&Dýø¤ÀáMí÷²ÄFÆ2˜ðGleüɤÚ%Xü+gQôÂÛU®-¶I— á<ëvÙ¹;Ö/ÙÉÍøzF#¢½~¾e}ê©ÇðÖûC7ý«î Ìô‹„±ýÊãEÃêh‹á4/Æ¿¹\è­Ò†ÛŸ‚öñÇ—ó"êÌë—µ-¼¾êêð–™íà[æË/ÿƒ¯åú]Ï< ñÿÜsÅÇ=ØÏ‚Ù<úÆ…ÕªCÝ[ØLGí+dú½‰¹ µŸã´Î|Ô±¬S§ e]˜Êþ7‹á5ÐMža3,µÍf½6NŽ|Š¿€1ÅŠíõ­sxMŠ©åÛ¹®]» ÞÀc:ürZ£µ¿MÛ6‚゚©ÿ[®ÞÒ­Úbeý¡+:Tÿâú?Ø-Ú¢þ4þ?ùècˆÏ–ól­ÜzxýSð‰¤Æ4bfò$mÄœÀ:“,N²2sï"‘¨¼LÓõ³¤ŠúÇÿÌñsŽûð™Ò×3¾º-<ÌÁ`®ðznY[T¨5f“ÙÀ9ò¦Ÿ¸ÔÿJ£–óH?ð3·ðùÂK/ r{àáëøãªë¶ÝÜܹs¤éb^WרuX«ƒ[I>¥ÉúQõ'N$· P€1}ZËŠOÔn©Îí»ß¾nÿý÷D­öó¡%¶jÝÊ­ÑfMÀyý|hiù"«GãüB&ißý¼|_f¶(íçãmI$Ÿ² —¦äõ“†h~‡K€PÏrO?-‹ÂšmêR/š~‘CZaÀñÅçøŒ««|>ÆrÛ¶mÝzø‘ª üQ×,ÈËDþ[mÕUݪþ¿E‹U]—.›H=ú9‘Y¨Šx.paíýè? aêðÃk…[f<|.‘„V)/*0#DàXhڨĶÕVu«Á&³± >_Ö±íÅÃkÓSПC‹d` úMÙD²Eéÿ T”xî‚~³+ÊŽúmöĦ¨?ŽÄ5’× B¶³ŽŸ #TÃú­Bœè"—ÙAH#&ÎËäùß¹3öâ¹ð»ÉÚiÇíóÅϳ¸v©]ìXnž zplÛ¶IB ¾ ßj½ ¡7ýü4‰iÕU[¸í;lô¦CÚŸœïÈ'ØjÏÞX¤œ1½Ŧ þwÁ6ˆð£ÉKõ«"Òøv‘Êt„Öh?¿îÚëÝæ[lŽ.KÈõ‰/øÐɸgã³AMÀàÏt1Oýï‰rþ7\Ô/L¡ãè0E"˜:2ù¤O&:±krá3®Üþ8_ïÇ‹„¯¿þÚÛ¡Ö˜MÔgþ_”öý`fxeÓ6m?ŽðoŽ‹ð ³äÜí·ß¾ýÖ~Õ&G½cQÐ4¨ŸíL[m4öEëÿR¿öý©-ý_ÆŸ?!}Ï-Ɔw–§g¡DÑBΓ¦Âå¨`â8£ÿ| if#Á÷XYóôøïr7h7bòÃWm•c¼‰&:LËÇ.-­Ý5¾Jsð–õ—_Ž–û¦KÆË»r6Ã7î-psÔë ñ¡’ü¯‚&®/”$yЇ×dç:ü‰ìÓ¶]{¡:t¨ì|‚¨-cQa ýBØžYpüaÍÝeB²À%Âóñ¦Àlâ m\?ˆ«–À&Inñ¨Ÿ%9ÀŠŠö+­ü€#Ûx’2P‘]ˆ=3ÅkYo+-˜ô+‡çôp¼¡*ªM ß c]/8s‡r¨Àöð,êWU?nM …úÆt§÷?û‡éüHŘó`ì뮳®àFŽíæc K×\}ux°¨ý£ý¿¸þç#4™~‹î¶óå„  ª obIÏõ1˜š5[έ‚8nÁÆ ~ H,!ެýBXÍÿ¨Èù†e0ýaG&ê?k²{Ìÿgï:µ*Žõ^š%¨/QÀJG±ET,X°ˆÆË³×Ø¢y ¢b7 Ô[TPQPì-VĂ頠RDÀ. ýß7³³»çüÿ½Â¾“³pÏÎÎÌÎìÎÎî)ÿ–ãÝqÇŸàúßÕ_ð¤<ôÐÃŒ*ÙßZNÉžQóJ«¿Åäcž|É4KŠ8ñ?î›óäSí³áR!0Í«oÏF¬¾Cœ=8Ë $$™ ñ©ïÿž{î9<£4ÆŒœ³Ü;o¿ã¶Ü| ÙK Krq8Nd±¬ÁÕøŸÕÇ”[ý+ëWn+±¤*ø?÷5zö5» §"î*&á<úˆÃfÛ2Û ›ò³pRBÓ͸fýY ú!GáŒ$Bí‚C˜´ôõq’ŸINîcÈy5ëWîï«?Ä]Æ›×/ ©ß)sÈž’<,*37ƒì„)7Nå”s†?c³Ã÷Þ› 0óòa’9Û´-–Îä²­|i H ªä>j| 0%ûšë®iòê |€J‚UFû»c³Ødj¹“ÍŽµä,‡–‡Ù©_uÕ„*Öô³WÙ‡%à,—ļSÌkõ©¬”AV¥ú+O”¡Y¨L¥©ÓlWÔ陋V~Õ¤ò=T—“µjÑ¿Bn'å”Ö™í/A"åi%鉱ìVÿÐ'ÁföçQÞ8õFtÜsÏ-°(~-ÞJͧþ±dÄk÷6VýV ‰y¦rýR:+?t˜,mfÄ_Bî(ã¶[nQòo¶éfA?ë!Y4·&VRÔd’ˆaPýS¦NuÃßy[dï´ËNîH¼üqøáØlôwŽÆm…—cnV¼ŒÇWÔ¯7‡¨![ÿP éDÊe>®¥€ ,@”Fý’âÅëßfëmðáꮇ ªó?.•ûè£É“ÕtBv€_R¼M9y5ÿÆjÚ?Ô?HӢƱ„jô·lÑBrñÈòÒ²2ýM™*t–eóͱôaà ý‹&ãðѬ’~«­ÖDY, ¤ÿ)²ÌþÄ‘õçqÝ„×IJ®³°ÁjŸ?ý‰d7Ç[߉™gµÖ/Rô‚¢h€"nü;âÝîðQŸz¯îÓÇ=÷üsPÃC•Ûp£ ­ŠîÃ>ÂlEXò²PâE˜¹³G¬‡%0Ø·Î{ÎÙRDË­2€ J…¬aRN^eÙ0bÂÕÝ”›ÆT\"Å¢²ýY6ÎTaÙ¾[ôàE(ʇrq‰Î¹çœ#Å‘œ¼ˆ0X]…Š~•Êx$êouhÁ§A9Œ `øÑD 3}'é'Žl5ú¿Hž|`ÿOý¿Ä'”Ÿws­òÀ^–Sb^D¦ æuå쟖"ÊkAvzÿÙ³qZb&1Uróû³àk\vNýGãxû¿þù/ß_7<ÕM¸äZ´Ö¢>ÄÏÔ­%.¨;òJvIée£fIh#úÎú þ¾ƒÿˆ^$¾´ÐßÉ믃• Ì¿} è„¢øò˯\×½»Šþí·ßÎ}€±£0éïµ}±$÷ ×Ñ;ÏJ ¢¶¿¯0³ù*¡ŠJe (IrBlmÜ,„SÎ¥}œœÖBE|yržtü‰¢ƒiž6óePñÚ7:Ixš1dô‹ÐPš _1$âÿSýL=Ú]Ì™M€¯ÓÈ}ÌÑ&Þ² o”« Ò”•RNù¬þÄjƒðX¤2ž|â Eƒ6èÞûÜ€{¸{ïc|¯»wÀ½²—óc3Y9¯²~-ŸÙŸü¦ß(ıìåYú*t*]¨Èã ¨Eäe—^†®˜’ŸÐ$YÍ_ eµ×"J3ýœýÔªU+×/a—^r©ÈT-ª$æ@îD‡/‰7VÂ%`Ì(’!¤*×?¥Xø(í!%ÒK%ý;âD= ƒãpÉ€ dìßïN\UÊ[b B{|\äIB œýö%÷`bðú5ªrŸÍý¬fý`4^€kNÿ[o¾=¨ðÑ¡÷Å—º5±7Êa˜ ¸Åæº7Ë9øÀ_~«ƒdÆ¥RýµZ?–…3/¹Ç —Ìüéô§átÃv³fÍ6Q®ÓÑnwÝÕ³‚¸Uê¯"q}ÌÂi€YB«Ôoà8#§N]{YÖÚ!L Jõ >W}¶òUwÿaNB{3ì€<ìþs7>n6@VÁ¾(«¢< PNþHÁ2®‚ú"_®­µ~ަ¨’eƒ<™+_Nó+™(öÕÓ‰ä2ûsI­Ï‚ÓÃ^ùöù…1¤ +¾p™¨¬ÿ3?þ2÷ôGÓ¯Êq±¸€@°6öW™Q†¤)ÏÆzà£ÊdÜ£x" Ž"—½Ã^Üëðî»ïÁ²´Õ½~_2ÂiXû·õËǾÁGŒ_x1cÞ#y²`(«—kÏ'ôŸ{îº[ü—3ƒèÛâÓ€ˆ/­’|ØS)Ö´éú"‰ù_m˜ÔÁ‹Îèû`‘Ÿ9ûbÿ£Ö­Z+›T±Ê}þ9—jY}‹ KÖ’/÷âðþ/$æs0OÉ­õ_úÜÇåæß`°¼\ÍPÛ|* åçìëƒ9ÔÝ‹ûÖM7s/J¥à Àª1ŽÇ½Ÿ?z´iÝÚq–#CMãOàóRô>èËnúóå¼îêë°ìy¨È}ûí·°œõWRŒÓO?3¸„9+Ô}?Û’yb_^®ÕßòV§?kÿòúW§_ ¬E°ÐO3+{0¶] ˆ‰C*çÿ…ýÅ™2fˆ6TGƒiÿž¿´…~C û« ÿóT»†EPq\û±î?òä,j8$ú¤þ€à}‡S‡O9ùa{õa=ó@IDAT KIäb½kiV¹–ÜØ±cÝØqcqÌ.â1ã$=n¬Æ_óU†—‰´UøU¨/>RŽàMìAHWµ²yå¶Ûnë³$†%=ópŒ½?pôïX”eˈ–S`¤¿þA¨¦ ƒ³£˜‡õ²zŒñu /\ùŠ˜ê ßWBŸ´¬9û›¹)ÊꟇ—/[.å0ýÓ¦M^&Mš(4–söìÕ¸dåjJM€/™Êñõ_¶|™Ê¢}ð7mÚ4¡Óþ'NtQÿT͇«ºŽ×¡¦¯½örÑ>Bµ4ÁÞ=ù‘a޾í°õÖ²7Ê'ø•q6öïùûíw\v’+eÒFIí2‰úExRË9s>s}ø¡ûŽÈ¾{fmõË_†h¯½î:÷3̆2U,^+´ÿرêCê;!—Vÿ ¶BûÄG †­°,kƒ¦M³í|×®]pÕRðøn†¥ËÐ>hÓÏöQŽ’´õ¿iS?~Ëϧ%ÚŸaÂxø4Û—¾,L“¶ýò«/…çö;nǑ痺c9Vöz5O™2ÅM6ÕÝ3à÷߇&|¼¤Ç»3ÍúøÁG®?öK²pÙå—á×ôÅZÖàFElý/AùŠ¥˜Ú·¿·¿ SÿÏ® ŸK/¹>£çÒ´ÿ¢Å‹ÜÍXFѧÏÕBë?ßn»Ž7„õ8§‡À\ª´[çÝð2ó:ò-ÁqÓ%÷>>xó˜vΈàËg©~¶l¨íªWã“&j9úéï~÷;AsÉÉé§ëòî{uÝõ× ž¿Ü_a³/+øŸÉ”8Õo„D5ûÿØÜ™ý”[uµU܃Îyó¾Å’cNš’ôvÀÞS; |ùåW`“î+ݼùø€‚@‘\‚s96™çÇÏL¿édlp`‚o¼@BQ¶˜®©ÿq&ìÎ;釫+.¿\Ë6Ëu¼œ‹}ô.¿ eÃKnuúÛ´mr¨¾¿`igôÐÖ/cæÇϬ~_‰•´ÿ&íuƒaâÌj\0Ôuþ‚nŽD—ýþcö°û_´…Ý7ýÛ’7~%ˆæP¨3PŠMhP!’•ê î˜¶úG ÈꟑlíVë@¬C%øqö„Np8YÓµhÑ¢¢þPfÁôkÊkÉéç¦ÿ8|À~gøpùÑdÆÌ2æ=ñäeõçr¤vÞ‰Ç3n>?O|G5áÔ2ñë÷Áw¤ÄGýoÜ^‘ÿ–[Ù¦:û…Ë™GŒéæ~ö™Ð¹ŠÙ˜s–ënNîÅ'Ÿr2>_ ‰u­"&ièO8 LSÂZÍøÛºM›PÜ¿üå/Þ¯ç‰_óùƒöi·q[ ±½<r6 UqÞ¼yî…_ rámÞ¼9b_8¯Ÿ÷ßþ2“Q)—]¦÷ë’Ñ.>ë&›°/hâæ[nvóeÖP ³®ÐFŒÀ‡2µ³½ýÎÛ®×ç‹„sÎ=×áä4wÛm·Éö¬ö¯±¯N Å2UÔ¿1ôXq¥}üì¤ès#¡ç3´ÔLŠ¢u”ü¾œ&kEüÏôXžšêxúa„Âþæ ê…ÿ©üµèts ¯ÀøŸŒhýŠÈbüþÝÆüò_üÑfxÁe›Ë˜Ëø¦OŸÎŸ ànU%|(:³†c½õ9$È0Yƒöè#JùL9Ä÷î}qþºÒÑGM—œ†$y³ãè‘/‚ã…úÉc:O:5ÐÓ²±ÜÂçe+¬8¬y°L"èü–úMNÏózæŽÐ "² ™;\¯ŒŽiò¡lÙ£ç#ë×_ìÏrY9B½MÛ¶m2õìÙSäî¶Çn—†>Aý• zï€A>NŽó<1†á>vüØHÈC¹ú–µø[¶l%²ð2Éͬ<¦Üêô§?ý)Ð1ã „—ù@cý铨Ÿ+Ô ÝJ íÈoŸ“²ØÿÏ>Ç·O˜/h©4ó“OBÙ‡ó/©A?fÝò7ßZÙ¢_‡²yý8é-s„yÚPì»™öDëCo¼ñFÐ#Šý}ã&ƒ¯&>0•¶Ø| _?-'6Az¯ïÛ·RS‰(| v¹éo7•égÝhç^½.jÑÒ†ÀÙý/Ƨå+·?öÜ ,YN(Çæ€©° –Ê“òé`ÉrB9&0GL…ýk°Tž”OK–Ê19`*ì_ƒ¥@ŠsíqWæ­™wJÒkH­N}¿d¿ÌØT÷8ûYøs•W]©åB’G¡2Ôµ…ôdÁŸrªþ}ºï#3.ˆ•BätY›®ú3¿¢òÖ/A%Õ«‹úzýuëÕUù$y6}ŽA8ÍÁ•4¶Ã‚J aà%þ¦‡²ç޲ ƒ'À¨ª*W¿’V uª`¿D?¥K_FÒLô[ýÔkP~a¹õÄVÚžA„—J‚Ï8ÖŸGWÒOÞ}öéî:lÕO›1§ÔIÚúŽ;ïpxiÀæš›­ …—ý~µŸ{ÿý÷1ceÈ·ŠZ|ýÍXñL¨ý­lQ?N¢iÔÄuÛ»›;¯çy²<³AB½9•§Æú«þÐ^9ýu´CIê×­ìfÅ}ö™g¥ŒLïÓõób5…õ ƒ~-eãÆ¸ Ú¦Z;a5~d7P³þWU/vëfÉÐv”à…´ß¤½{ýÍ×qâU_·u]&E>«æ&›lìžÂ1öç§³¾A°_öÜÙ»[7–X2õ¾ôb9[ÊÆ‹É²BWSÿÈoŒV†kæOûXgýÙ票&¶ýüµzŽ-§¿„2KñK2“aòÔ)z´·1Eöî}‘»ÿàû˜aúÿw®Ëž]Ü(œŽuäQG{§í/mLyTÏÆâÄÿ—àhhî'D=í7m/{{he¤`r¹úš>ˆªÜçë>DšöW–IÉŠ¨¦þÊ"œ®–[1hJí¨2›ƒÈ^çã—z!–0Óª¡{èá‡ÝEöv-q‚ëÏ™œEEûxÀîÅ!/â({lð,BU*—«’1Y)zMꯜ%ð`LôíÇ>&ÁëgA´J`Îî?äጌ‡zØ]xÑ…rR#éHÙ´€ц¼0Kß|Ù˜)Ñ߇2<õô“²9°é§Œ5Qö™3f’YþxeHýO1¸‚(t_þ´ýÉÃñ‡û¢p¦ŒM3¦Ï< ¡‡û3YV‘iÊ×áý2©Ý$g¸ÿÖÇIWä‹›t'÷Ê 2'õWU%äØb<~©’VŠX°&ËÁ€dݹûpa„\ß/)¤¼X)r¼ì)“?‚ï|à&Mœä†}ÙÝ…%€'âòƒý©‚R+ªd˜— "³õ7¾n¼Áé©™¶Iq•,—ì…}»ž~êi©[ƒêóVÿ†Þ¯/¼è'{À¡Œâ×XîËâÒ¯‡Ð¯±,ÑŠdúy/zöÙgܯöÅÒ&_ÎÀ!㚘iÈ•ÂåðԬ“ŸÙÆÒsüoÛ¦-fÝ*³‹X;ê«kÏ ¦Lb^@—{:¸\žŒ?^5ѱýÐYšÁ¯ŸÆlÚF˜(D*@àþeŸÌüDàÃ;Lf6ñp ÎTd!f`|îiFvÒpõÕ}\¿þwª|à¬þ°Ùf›¹}öÆýN–›CŽŒ+5Œ?ôçgŸyÎí·ï~AæôÓE>rÊ~`$p¿µÉXnÈ0pÐ}nÕUW˜úwÝ­³»³¼„vï@™‰•¯¿´úܾèsVÖöç˜Á=È$®Þ<‚óHO$Ç÷û_^¿ÊUÑr­Ðÿ2¬Z¨B¿Ø¡°áEÿ³!!È¢Ðm©aü-ÆXàßxüÅ2>´>½ÁœÁ{I•¤гÖ>2Y'’²¨$•€ {í@“eq"%‹JR ˜°×4Y'R²¨$•€ {í@“eq"%‹JR ˜°×4Y'R õ6ôütΧòá†KmxŒrÞW“l+š‹“ÜYT’JÀ„½v É²8‘’E%©LØkš,‹)YT’JÀEØŒ—/@KñQ‚ûq?›ï ä?~¼[oÝuåD7iËD¦åÏ¢’To­c“eq"(‹JR HvÖ‡Ź\«]Û¶øˆë?F$²*‹a»ñã'8¾ˆ·k»1âìKrVM’JÀJrW g²,N2gQI*öZ_~ñ¥›ˆ%¯Öiì6j±aæÃOVM’JÀZ)M3™,‹Ú8^{ÒD”­Q#·Qs–-ùÈ”»W&Ùĸ¼’Kˆxra‹æUï¦×âDP•¤<È¥3ü(Â%EžedOÙ¦<é²YhScD.€ßb™&ÇÐÖØgu¸—[-ƒ‰µ8“E•à×ËÜ|ðž‹ûá׿-š»úõ*u\BɶÀì-ǽ€èŸòcL"_@S‚x)R?n¼[÷òI³$©4qf·%Þn+ÜL–Å&q¥)ésï£}–ü}.Ñ”d• G•¤0U+ØdYœÉ¢’T&ìµM–ʼn”,*I%`Â^;ÐdYœHÉ¢’T&ìµM–ʼn”,*I%`Â^;ÐdYœHÉ¢’T&ìµM–ʼn”,*I%`Â^;ÐdYœHÉ¢’T&ìµM–ʼn”,*I%`Â^;ÐdYœHÉ¢’T&ìµM–ʼn”,*I%`Â^;ÐdYœHÉ¢’T&ìµM–ʼn”,*I%`Â^;ÐdYœHÑC BÁ œPÂ+ø„ÀT¼ÿEj™r *P+  ýfªÄ8 @°fÄT´pÒ)§GW@ö/ì¯Î•8GPøÜ„ߢE²°ô£Ì%åô„ (¡>!0AoÄDý½zõrÜØÖ¾uh9ùËwvF'ÓŸN¬ŒL¡‰ä FýY!óÖ„š°&ü À¿Œþóaë·¸‰0l² ±¤æ'%wÔ‘GaÌI«pµFŠX߉„à_¦þ±DÑÿÒªD8åtËݰݯö?À=ŠãÏ3AXKîºëúÊ©_¤ñcν·_¿%¬&ÍbáIìä‘G¯[‹Iv½¤™´²&Ä H‹˜•«¿¨K3ú£Ä.‰q€Âþ°–Žëq‰Ö‰¦ŒPj”ð >!0…ý ûþˆ=" Ç~gPÊéqPB|B `‚Þˆ)ô§¶0«Ç¸µêßÙþø™†ð”ŠÕ\±ž¹'P± è©m –Ü”ßËIoA|É`ÐW ƒ…K¡¿°áEÿÓñ@Ç•0ˆ²0vÂ$6æÒ(ÿ³ãïHlZüòС2BS¦*(3jó+ÆŽ²É.™~8ýÿIãÿ¨Q#°ìi¨Ø/¡Ç§ößÉoÔM>ië©ý öÿ–ìÐϹÌðÅ_t»í¶›,‰¢Íb–Orä†Å ]»v•ø]lÜü2–˜ÅÅ@3ƒº¶ðØ¥S'n­Alí=?ƒC^¥¥|ÀoÏ7B¡Ž  Y‰*ž¢ƒ™ØB0bqÿS¯)ü/ן`±IÑÿŠñGÆÐ´ŸãoqÿÉáÆb@qÿµç“•yþHf ù›¶D–>˜â‰HiÚIWærP§¶Ü©žÀõT,ãJÅ!7€B?;•o‰‚•@LñäMi–wÅã@aÿÂþ…ÿYßIûYè%±¿T,ãJÅ!7ö¿7ßx]NBÓh „ÁsIãxûÅ´m×ÎmŽ}¢R¯])å`öÒøOëÿ<‰î“Y³tÌ…!XÙ*°YF TrmÛÒÖÜcÍ[ÌG!½²†÷ü©˜ŸŠý±¡¸Û¡ãönúÌ™bîÛÔ¢e ·d‰.㤛²^<ôÙgŸs?ÿùÉ©³>͵\ø¸™¯?íÏ}z6Û|3H@B——lx$£&V:É~*övauC Vºî™ÜEý‹ög— ^äýJ¢ÔÇR|ƃBΕ‚dEÿ+ì_øŸõž´Ÿ…^bŠ'oJ³¼+‡ÜŠþ÷¯Ûÿ’CÙÆ hè\CröWž$‡óa̾Y%Â’[‚aÓØD©#ú ûþ§½CûJÒc ”¸èÅøSŒ¿ñ}Æ:H¸»”e@÷Ÿø ó}ÿ…ŽÿróMîþAÝ丙0[´${+uØzwÊÉ'b¶P7Ùë+4o1þÁLÅø_ŒÿÅø_Œÿé-®ìî–.ã(îÅýÈúÑÿõýŸ÷ôâùëÿÎþáÃPÙãCÚòeÃÉÊo·‘͆‹=Å’‡'¸ ò ýðë†Ñ²ö/ü/ÿ¸g¾ÁÞ¤CˆÅžbI‹_ÑÿÊúS1þãO1þÆ!%•õ— Ý‹Øñ‡{q#àÕV]‚²›¶«&Ókñ«ŸÒþ?ë_è/ì_ø_ñüW<ÿU÷~dã¾ÅÅø/0sX\<ÿÃ,êCeãéøóU gևπ¹þÃ$ªürð'Ï—Fe´"—”¬‚«@HQ…þÂþ…ÿÙàeÃXÚóNûLì\‘¯Œ’à*RTÑÿŠþWô¿¢ÿq$IÇ…8²(TFË!rIÉ$¸ „UŒ?ÅøSŒ?ÅøÃ#tÔ‰×2Z‘KJFÁU ¤¨bü)ÆŸbü)Æé¸G…Êh9D.)™W¢þ¿ÆŸ:ÙB,RúòšEÝCXô¢ìGš"9íKLéy"èDO–P&Ë:e¡¿°ÆU¼{‰Ãài…ÿ‰iÄ °EÑÿÄE¢ YŒ?Ñ&YÃD¼@Åø_ÜÌAŠûYBº/~l%iÅýGL#)î?æ#Ñ?è7ê#‚ËhºŒ/ã¨Ìâ$¼ãO0E´M1þŠkˆAŠñ×úHôö›bü¥ˆM²†!Zð²”,ý*GŸƒ6”suuOkËÈXHþÊ´aN®Ò¸T35¿ÐŸ}SËö;þWô¿büAW(Æ_¹ˆ%t„”¡¡¸ÿ”YD­“\Åp¸÷ßÄ(,ž?ŠçôŒzFñü%vA6Ž´ÄÙ»DÄf1j¿äJFŽÓÅø“%‚ÅøSŒ?Åø“Ž!ìÅø+#„ ²q¤M,ó£~Ñ=†rÊå=,-”°æ E0HsfŠO¯eäB? 'FÐÆ.3Pj½Êp&K&QÎ_F&¢ÐO#ö7W ÛØXMø{BƧ2‰òŒeäÂÿŠþWŒ?è(Åø†ÅøS>xVƒÉŒ©™Dy†2r0záÁ4[áåÎS &ãS™Dy†2r0záÁ4[áåÎS &ãS™Dy†2r0záÁ4[áåÎS &ãS™Dy†2r0ú¿ŽÿÕ‘BЍHÑJ’T_lj«hæ™ù ¤â”LÜÜÉu(Ùc%¡˜B?ìDSˆ9h3o·ld¦ôÔÂþb2o£h?o&à ÿ3ãеŠþ§£M1þˆWˆ1Ô"Åø ‹È`±ƒÖñý&ùōŸ+&ó6Šöóf¾Í8Åø[ÜŠû¯ÞmŠû¯Œ b µHqÿ…EäfÂ{­ãÇÍläo,F-î¿b2o£h?o&à‹û¯§¸ÿ®Èý3†–Ãb2Ù¤‘Ðé  IuØ2ÌÊÅ™üÖ>A RåšaŒ:ªAG†ï2ù™`(ô«dà-Ú_|$ã(Þ<ˆªAG†ï2ù™`(üOíPøŸ8ƒøHÆQ¼yUƒŽ ßeò3ÁPøŸÚ¡ð?qñ‘Œ£xó ª¾Êäg‚¡ð?µCáâ â#GñæAT :2|”ÉÏCáj‡ÂÿÄÄG2Žâ̓¨tdø(“Ÿ †ÂÿÔ…ÿ‰3ˆdÅ›Q5èÈð=P&? …ÿ© ÿgI%W_£÷q³&®FÄM¥ƒOyÓÖE]üfYý‹…~iŸÊ¦,ì_ø_ÑÿŠñ§2÷Ÿ'Ÿ|Ò½öÚkîg æz_ÔãúOóþ7äÅ݃>èÆ/ýoëm·q[wèàŽ:ê¨Ê÷ª¶¸ÿÛóÒO³ý¥9‹çâù£xþ(ž?ŠçŸÌó‡Ý†‹ûoqÿÕï5+÷üQ…Óêõ1ó$‹åa€‰øhôèÑnúôÀ”\ûÍ6s-š77îL<Þ|÷ÒK/Ég ÛlåÖ[oý ]FXÊMšùó‘g(ò×aë­Ýzë®ëUGý©èð)ÖÃ$ZH²Ï›7Ͻcºäج‚“•æ 0‰’úêÛyßÀ¯$ö'2!²å€Üo“ö›¸V­Zc÷’{ö™gÜÒeËÀÇZ•¤þ ö3·a³f®)þVYe/¢B2çÌ™ãÞyûm÷ö;ï¸ÕV[ÍuÜn[·í6Ý ×È ´'MtNú@²²Î5ßHô™¬1cƸ?ž.ú·Üê—®éM”¿§þZ@ |´ÿ´ét´)ë¿Ù¦¾M+´¿øèKCEÿV[wpë—ùµ/F¢Á‚ùnˆÏ#~½Þz`"CÔ¨òÊËÀU¹wÞÙ­µÖZnîܹޖú·Ûd׺U«LžYŸÌrïŽxWôï¾ûînõÕW—jÓW_}åžþy7 íüÙgŸ»vmÛ¹]wëì6Ùxcc‰ñJÔ?fòPbš¹<ü°ö/“ÿOêÿòKŒ!ø¨ e× ðªbcÿ#Îðûb ym3gîaìÒ¥‹kÐm®Ù¥ˆéø·a³Ü›o‘é™züÛ_ìðOÖÿ”SNq·ýýï®qãFnöìÙÓjK¢aРìIE¾ úÙ¿¿A¿lP¿>î[ +磄Òÿ¤Ð~¯ó]Ÿ>WK’e4±„—^±Üä6F™Ȣ•©?ÅYð,™ ýÑ…ý£-Tø_ÑÿŠñ§‹ûOqÿ­î÷¹wV¼Ê#Ne n2ÿÏü4äCVBµo¿ üH>”N8ñD®B«FŽ|—¦‘¿'Ÿx¢œ-“O#FŒyž@ V“dH‹9ÄJ0²ÅÇ[¹P‡*­_ô¶lÙ²tuŸ«KK—. RX^E./mÚ~ÓPÎN€ rá­·Þ*áÇ•À£·K{´a¢?ˆH”µ‡“s"m-Á3ŸÅA€J0²ÅF}ëÍ·3ú­­²qUé¯ý‹ÔÉ’%I}¬V'o¾ùæ^\¼Š¨ÿÁÁƒC^ô4W•7iR>|8øcézž×3ðžzêi"+RK¥-¶Ø<øßýlCó}?Ôö‰ùûõï¯u†¯N˜0¡¬\/¼ðb3jÔ(¡?öØ£¡þ:u*3ìã?.2©êÔ©ÁîÔÚ¯_¿Òk¬éíœõÊúðÃËäQ©UU `)CZ¬Ääª#[2ˆ$‘€Æ›Eù”!-6æ+ÁÈçÈ>™P=ø&Æö•´­oÚøki‹)¬OŸ>ÁþûÛßÌZ!¾âŠ+ƒýŸ}î¹jõ{‚DIéö)CZœfHrÙâÀ–A$‰4Þ,ʧ i±1‡X F¶8GöÉ„š€Æ›Eù”!-6æ//|òÉbë&›Õ5‹H„$ 1§¨7ßzSú>¨+9%Z‰•`d‹K‘$ð7ã}¬mÛ¶¥{ (=ýôS¥>êS:ôCtýVÖ¤H@ù”!-6æ+ÁÈçÈ>™PÐx³(Ÿ2¤ÅÆb%ÙâÙ'joåS†´Ø˜C¬#[œ#ûdBM@ãÍ¢|Êsˆ•`d‹sdŸL¨ h¼Y”OÒbc±ŒlqŽì“ 57‹ò)CZlÌ!V‚‘-Α}2¡& ñfQ>eH‹9ÄJ0²Å9²O&Ô4Þ,ʧ i±1‡X F¶8GöÉ„š€Æ›Eù”!-6æ+ÁÈçÈ>™PÐx³(Ÿ2¤ÅÆb%ÙâÙ'joåS†´Ø˜C¬#[œ#ûdBM@ãÍ¢|Êsˆ•`d‹sdŸL¨ h¼Y”OÒbc±ŒlqŽì“ 57‹ò)CZlÌ!V‚‘-Α}2¡& ñfQ>eH‹9ÄJ0²Å9²O&Ô4Þ,ʧ i±1‡X F¶8GöÉ„š€Æ›Eù”!-6æ+ÁÈçÈ>™PÐx³(Ÿ2¤ÅÆb%ÙâÙ'joåS†´Ø˜C¬#[œ#ûdBM@l>í¿{ñ1– ã$ØW±‰'º ÞY?p¿[¼tqÂÁåx|YˆTl^hL—éb2ýŠÖÅ‹ä,QY„¢#ëw’9χ¡äZ¶há:`J¼üd ÌäÉ“ÝyçŸçöÜcO™á"l¸˜ ¦'NœäÆOÀ”z?ø€[²x‰%%^uÕU]ë6m]Ûvúצm[ùÕX™J˜™±†kÛ¶µkÛ¦ â¶®MëvF öW[ëÔ}"?ð€[¼˜¶ÖÒÔ¾þªÊUÁüE¡Ñ:M¤m­ÌŒQ®¶mÛ¸&›ˆFÎQ°Ð¿€ïÚ¹3f‘ì$u4Úi§æ~õ«_ ›µéßÿ~›;øC,«ÛË(›öŸƒ_Ñ·Ùf,·xCé,NÀêî0À-[º4hfûŒ39µÜÒ¦±X1# ÓoäØþžM¾O)üþÄà×4 ƒï7[”ÏĈ’Ìw›‚„ƒ`ªß¸•ŧýÆ+b)/›!§BÖÔþ7v»`Ðλì,ícâ´}öÓbP¿oóÿrýä!;[Ø*ëÏYqC†Qyvõl"°åzæO»ã?Þ}ûí7ÂIÿßkÏ=¥/P?eí¸ÃŽnéò¥ÿtý©Àljúkjã-¯¿VÊd¤©šìÿCè_ cûûd̪âÌ*¶«Ê.¹†2†€Ž1†4íSÎù»3]Ó¦M¥i9ÛÃìÍúöÙg®wï‹DF÷îÝ\—½öòò| Ù֥ͅ$—ÿúGíч§¥ù±íÿƒègQñ§fÍ5¦WÆÿ>›ûä•Ü·ó¾…pÈÈ6N(¶É4rmüÿé§žô¾Påî¹ûnwÔ‘GºnÝöqçõ<Ï Â½ÿÇÖo•±:hZS?‰öG­ìµ±Qÿ´Å³Ö(ÚŸöøqûÖâÙTaÿÂþ…ÿý/Üàlxðññüa"íªéâþO;ü_Œ¿u‚á /­|xåCÉ=úÈ£uÚ±“<ñÌûv–‹ œ^"¯ˆJ³'¤\àÀ\˜ 8+†2(·À ¨<ùÇ­ÈeXñ ãs"ê{]_÷î»Ã±üf©3vŒÃ¬QóòËC]¯^½´0Iý >ú¨·ÁN¤œß~ó-–¿ õJT?fµ¸Iøˆ6ñ}ÿø†n´‚8ÌLBŸø>–Õ¼¯´PDØÚëÙ±ÓŽ •Ü7ß~놚­¡Æ×"”N¬HýÕªñEß¹;ï¼åAY±¼GÊ„òHe;øƒEt‰’|8åäSQ–—Ü+¯¼êÞGg̘áöê¢/™O=õ”{ýõaR>úÇ9gŸ#¹ø1ìÝwßuoc9ÙûÐuÿ A&ÎqX)¨13ðåçõ7^÷|%÷Ô“Oy˜X«±,•øTÈaRµþ"@ØKî±G‘¤µÿ<è*mJ†(“`²Òú­ VfðúS™ÂUMÿK‹¢yLr!ÎrÊÉÚ>/¿"í9sæL·—ÿÀö†ö‰õ§F_1“‘è×ÚãUT>$’×ð’ý’K.5 bpkÅ%ðµ×^øÞz[ýÿ¹gŸÃ²˜¯ÝO<Ž¥nMÝ=îqõêÖƒ`ËÈ8Âï¿÷¾{ï½÷BqY£f®\û[µƒ «Ñ”A©æùñôo¥­ÑÿÞçø ñ{î†ëo6|ÛÛý<ì¯4Ìj«®æþügk86qŒÓP宽öÚ_`1^b+3Ä¿@ýµ , µ0âü±íÿƒèO‹œËªH}VÎþŸþ¹M ðãù•ÐÿXD.…Û¦ã6Vp‰öWKù«–˜ u£ÚÙ_„A@QZâÇõ?m)m-°°aÿÂÿŠþgC‚ŒËÉBí•b#6Sš§ÿÕ+÷ü!Ö„‹ñ—–ø÷îõÄAäeÀºS>V#Ü;ð>ñ‹Ã8Ü­¹Öšîÿx‹v]»tžÝ-戗Ðsù;§~Žðüê%Éóë×0r 'b4‚ð (õˆ˜?͇¹RX±E9ª_3Bð\§AÞ:ˆ¹WÐС/»Îw•ç;ïìçþçþÇñ-äû}÷yü7l°ÆZ°ÁÓîØ K—=E–ñúL¢‡úÅ…´€ÔP–é¾ûŠˆÃ?Ò­¹&õüÃ=Œ]ºÂ֔ヺ¦‘!_RøÇs責ÒP–/ƒU…™M2+ˆ|ëo°¾»¾o_·Ùf›ƒVåÆçvÄÇÃ[ÿ~+f,ð—mçþ~Û­n+ÎÎB ôC=̽ó^#³E^zéE·Ûn{QTéÅ=öؘ™´‹äÒ_©)öƒ¤ÚÖ_ò£ ”pß½÷áêÜá‡[ïŸùÚôQ´)ýÚ4™YˆZ ýdgVÍïbæ÷ú)Kmj ò±JP~/Œ(„P›ýC_ß¶ÿúë¯ }ݦ›£} kÜØñ®Ó ŸÀûˆÂrY~ÆüÓÂVªÿk¯¾Š„¯¸]0;Ix©?cþ÷ÒÌ,¡k—®®ã¶M$PUnßîûº}?Þ¹lº˜I°XÛfÿ˜xþùç»?þñ¢%Ô_è5Ç|¦Ÿ Õ_´F¾J¢Ÿð¾ÞÍŸ¿ÐpÂñn]ìm6{%õïw'>fŽp ~'3Û8ð@×a«­¼Uõ*ö`zþùÜÈ‘#0ûiM×i§0ãpw·ñÆ›C%ýX ƒaO¸·‡G_ë/\„=·¶qÛoßÉuEÿŽíëA¥yûSA¥úxàAn7Ìè{ ¸/»ì2|(ÔàKTrGA=ÍiÁÞ@?˜?8ñDôŸ&nÖ'Ÿ¸;û÷sÃßî-Z„ýä¶vû„þóË*Ù–¢M‡bOÁ‘èccÇvS§N—Yr{îµ§;ø×‡¸z,›ýëßÜר[éäßüF~4aÿÙ{Áõèñ{ü°³ÜÝ~ûßÅþ;í´3~Ôééê×o 9­ÿMÿxš»ÿÁÁnôÈÑâ£Í0«n¿ý÷Ç ×ý\ýð1Ú6STõíìÌÄÆO`?½wÜôíEìÛ;b6äön¯½÷ÆçÇGm.·dÉR÷}tØ+nÊ”¥Î[þrK·ùæ[bßÁî²µ/ǘqíu׊ÿs–­Ý›Ìþ¾å¾õ>ú÷K<ýÞêÿ}㟵?uY+ÇXq5ÞïHóúqשÒþYÓý§°]¢ÿX?,ü6)úŸ÷ŒÄ-òã=%úF=>Ûý6É>p¸V‚1¥ëý/ú9˜ÅêSÿqýÇVîLqÁÙ‡|@Ïá½­„‡Tì=óWI¯¹Æ¥Å‹—„üx€‘€*åGžš÷bQ~fK÷â¾DIA”©†«•Ôô[²X¹ÞÊõØ£æÉ¥W_yUèì.\Ð+Ð |@èHl@>hÉbá5½›€ûîä†ý\ŒˆØÊOÔ“>  ¿ÙZõÀÖØë§R°ü¦×âÀk !û ylŸ 9$P ™Ú õïÝ»wkÀÔ§)O8ô êïÚµ«à0[¨´pÁBc ñûð#ñ)”ã‹.|ÏžºÇN½–Å@ÖòÒ§³f©¬5×ûÝ?öòÁJjõ¶ØèÙ ¬¶iÎÖ,“úõ¢Èè¡#s~íñ5é§_ãu@ʯ~]&6[<–Å–gJ0‹—ÚPU¥‹z_$öOÈ¥iS?ýô#¶Cìùcöo{ ‘àõ¿ðÂóŽÐI)=æ÷b›}ôÑâ§{íÕŲ”ð!/䙊=†,tܦ£à×ÄÞ(øphè‡* Lš4)ôëß}÷]È›f±v·80™“ÌìÿÃ^õyߠݸG’l\:î¸ã<ÚÖ ¸¹H=~ÐX5öúñ\Ú~ûí…Çú¸éfÜ­[7øú§>o¶Ðø(ôäÇã´z9*´ÃïÿûR¯ž½‚ýçΓ-R–_!€2~ChªJ¯¼» Zÿ´Nb7ˆ¢ßY±¬ÍD•Gq„”ËçóáIi‹-·´É7ý?©{ÒYýgÁ†X^x­ÿ™~,Ë+ ¸çžP†LMC"/p\êÜy·Ð.iýÙGXNêJ |Ï=wKÙ¬,V‹wßm÷Ò¼o¿…*Õì±Ç»E?ÓûƒÕßðC† •"Ò÷ ~fƒ|ý©kÝw/áýòûŠqŸ=Òòe iïç8XÂçÐÈô¿òÊk¥×†½ìnu¢þ´ÿ°lí’ö1>‹µlßIë´S§ŒìãÑ·ÝcKÒÿ°„23þ©ß©_†zø:b)y‰¾˜ié@(Gu99Îo·Ý™òY}XöíÙ¡o—J³?Íú¨ñ2f91SKÚÐtvîÜYêÉ6Š{ù)•ú.\XjØP÷p;÷Üó,[dÈb2©LMC"ÞJ ã´þgqà5†€ÈrH Ë\!eœ¦×âÀj ‘2ä@–¹BÊ8M¯ÅÕ" dÈ!€,s…”qš^‹«1DÈC"Yæ )ã4½Vcˆ,!‡D²ÌRÆiz-¬ÆY C‰d™+¤ŒÓôZX! ²@†È2WH§éµ8°C@d 9$e®2NÓkq`5†€ÈrH Ë\!eœ¦×âÀj ‘2ä@–¹BÊ8M¯ÅÕ" dÈ!€,s…”qš^‹«1DÈC"Yæ )ã4½Vcˆ,!‡D²ÌRÆiz-¬ÆY C‰d™+¤ŒÓôZX! ²@†È2WH§éµ8°C@d Ù'°v#~+#̧}ÎàÃÃ~iS3œµq»vø ‹_÷AþËn^Á¯æü’A>Î&1y 1(àSÑTi‚& ÀW^þ ™iÊã@ŠrùËô =ò/ŠT¦Ï®ÅDb'ì›Ã@M~ø‘À–~3vXö 6Ø36vUX„ÇzpW±l‡6¨^?%z™JŠ’yìQ¡7mÚ,êgß¼úòËŽ§=E"5ʪ^¿j½¢?ká´,•Ú¹/¡>^SJ6<øÀ`)DÚf+̪`9ÆóxcÄœM²Êj«H† øv°!øe™Üì$ÁW ÝÆíp"ÛL,S›éFމüõÚ¹ƒúµãr(U&(¹XùW¸þÈÀ<ø8(ù¹/‹¶©ÎZáÒ½W^}ÍáEèRn´ŠÔß4šR¤ ¬N¿Z[ßgãVëÿUÉþ¦_4Uòe@ùøî€V _zî…Å@¶­~¹•Ä>‹À){ÐÏ>Κ¡ÒÚþ¬?sÑçç¹ßžq†,ÿzþùçܯ¿ŽYb;böÒSyÔúëC~#oËxÁÓæŽ?áxYfˆM¿E^Êô{«~‡;¬åP|CœÆT—¿ò'Áò[½-Vdò–Ž2òö7Qì{}¯ÓeoœY²»ôé’{õÕan×]9s 68ûì³]ÿ»ú‹y¶ßn;™!²à»®ÿwº&Oq‡b æ-·Þê°13ˆ~|äp;wÞÕᥟR0Öììºï³Ž3où¯º°Ø»#FÀÖ,¥”TøxQŒÖŸ°(–X/ùúÿ³N=õT”á,cKÐ.¹ü2·ÎÚrÒ³Òòö§-/}Àì)Í‚¥¯?âúbV“›c–åž°¹ñ‘@íæ HƒõM%ú!—ú£F$ðŸçfî²Ë."÷Ývs¿ÄóÀàd+ÛãœgË, ›muöWÂEÉ{9fMqÖ!Ozã‘ë·Ýr«›1s†[û8YðÅ“äÊÔÿd.ÛĬ|´rGqfö|çî¼ãŒ_3|Åh/a›üãég¤løpäŽ8üp© ^ô±¤÷N7bäHœ`8ÄÝpã î u?¨ý0«åç¿à‰™ËÝK/q£q2#þ³eD¬‰fM×ElÎ2ôÁ$9m³!9.óTÉ~wPÏ÷"fñýùÆÝ^hÅ“~ùùç_ ]r÷Þ;PöÚkܤf%ú9ã¸6=áÂ2@çc>ä®ë{½È£ìPlŸ×^cÿÙUêÏþ÷4Ê6íÃñÿ¿1ûv+̲ôëçF`É1Ëvㆠ.vÿyrØþK0ãêÕ×^qýØ!ï7˜I4èþAn–OÞ‰úýQfS9ÁqŠûpÕ£‡«W§®üÐ`Ç¥¬8Àá·{úé§…‡—•iËÄR»²oC?Ã.èÛÝзâ¤ÇW1ãëܹ”z Ëÿ°UOÌ|ü€>Špùå—ËÉ_~E¿1ãøèjBGwúoO—U'ã^ùN,d°qîIì 5žîávÒ‰' 1r[EªÒøÇüjSËïÕ˜:/²Ðoa;ö·>f'CGHþG{ˆ§D›Ðo —Ü¡èj‰büÉÚASÅø[Œ¿:B¬Ôý§ÚoGá3ÒòYƒ“•NÿíoïkÍš5UÜé§+.ðggÿ<ñObJˆY…!¥3+dôËÍ2ÊæMSöeLp)!H5@‰ÃßÎû{JéQœ°T–ãä+ýUG9{²æí°•Úà4Ô×ŧþÛ«xß}÷ G÷÷ÒÙ&(ĉ­O3=ËKœ9“·?³¬lýMžJ; ìS‘î˜E¶Xë œž.V¹qlÀ¶Ûn+ºÎïyžäÛo¿ýJ=Îê!ð•W^QÚÿýÆ E‹ ¹k]³C‡­½_‡6MlØßø3³Ú0KaEì?bÄÈPæ'üÌ“W'v `„=M™þÅK–ª´'g^°}^{õµ–Ò }N úÙ> Ð> ýú÷“ög»Å(Qú Ï¿ ùØ_©d~Æó|ýõW¥=÷ÜKxöî¶øÿãO<tM2Uôð²3šŽ:ê(Ñgý¾×mŸn%_;š^‹‘Ƀ˗- íO½7þåÏA®eÖ$k`@ÄPf-›+ 80èO‘6ôå—Õ.(ÿ!8‰}Ãôñåçaü`Yç̶Ù9ËuòPÇM7ÝŒ<Ù2±ÍÞzóPâHV>,y å´öª©þœyÄ2XßäLƒ…‹ÊgîE…„’20š¦T~Ô1pÐ}Yq!µ\g™úú6÷ó@1€~KÛp–‹(âŒ!kêzä‘GB —/_üuœõÉ,fÊØ¸’Ökñ¢E¥—‡¾x³@R»ÀŠ%ñsÏ=ÚcŸîû–~mûõ×_—xÒËܸqðGø.í?öCÓÏYpk®ÑPòp¼YbÞsÏ=7èd¾šÚ²è¹ ãñ‚Œþ…Ð#§¢-8dCԵ߾û‰®Nv̰Dލß|Ì¿½hÿ©6W©4yò”ÒM7£l9,ünʦ³ƒšâÞªayÀñôÈ…‹•ð£C8±'ñ±Lç÷ë¬ßËÃNÈ‘¥F }–¥dÀ©Ü(] ˜Ìæ±¥©Ð28/±Œ!ƒ ÷ðáï„G¸”,ÑoÌ8qK°d“GNž<9<ø=õ¤Ù`y鬳Î<Ø–àå·º0Ë@ä%-~Õ9ýjký`ó$ôù,ÿ‘d ,¥â². J5žêt>åæR2–'¼táÖÊ'1Ò|96ÙØ«Béž/äóub–Í– }ñÅAÞÙg÷"¤ú‰`z—v¾–­Z WͬKبZhú¡® ÅX²‡—+÷ýÜ/y¬Œ’¨á’×ÿ!üÚê‘úõïÙ¦¨'Û”K´Ò`~Í|Ø4YHß§„q¯öµ<Ì(ù*dNQ)œ–Ca¥ò*Kýòm˜´ ËË~Š=m‚˜;ûõ õŸðÞ„€7/¼ †ho{qzìñÇ‚ý?žþqéeÿ‘„ì{Ëe‚ü#òª+¯’e^¦3/ˆ›¼wÇFÓ˜µ'K"ºwß'è2ä%Ù¸û•e²ié¡yÛ´iå~ñ‹_h™¤°¡æ¡œ†˜¤²4 ™þ¯ ä¸qó¯±Y²Ó¯ÒôŠRîfXê¹ÑFfÊË|;ì°£fGf[Ž:zLÜÈwÿzF?0VbÕD%è'.­Ò…Ô áZ’¥]sæÌ Ùìq,Yã¥Òj¥ßä‹-)û?ívþbP1ÍÒ*¿¯R€%lšÃ¸4gBÆF¼º52W¹-±|‹ú)û˜i&\/¹äb9éŽø¾XÚ´ÕÖÜÚXŽu–¢Þu÷]î+,+«¬?S$_¼Êýoì˜q¢¯ úY«Ö­ƒn¬n± ñZrŸÍ+ÂwÄ2Äúõë»¶mÚ¹}°ôèlHÎÀñûX•ë§‘ B[]e©\É–\Ôšsq¼ýõXâ×q»Ž®6dæÆÓ\â„H„—r¨‡Asdë/P”fÆœê—BÁtýš~ðª_ÍCÔgsç¸ë±Ì‘›3ׯDzµ… ºcÃêw¤þäùök–Íô H4BØÆ÷µ@ôÕ1jô(ð)íWìïÖm²®k"eƒìØÝzÏŒé3Ȥþ3"2IDATC‰5‡(ñ¹MR'Uϸ±cTp¡þ^Q¹š‡¨Ká£` a bæ.Æ·µeü¹çî»e飿‰ú?îøÐþ ~9µ÷Ü3@LŽ3Üþp|ù~ýÿ¬ÿ«mÍ‚¬E ý©5çÛ¿°?|D"¦2IP+ÔÿRþhs“1…ýi ZAmSø_áEÿ‹ãƒéx¢p¼ÿnaÖ¾”òú¿Áý·žñ±Jê$µeí«ÜÀøˆÓ\Vš $•ëÿéìO¥(q¿\ýÅÿ¥pªÃûÿ³Ï¾àöÞ»«â t‹-6Çþt{º°áˆwGÈ/’‹}SB¢Ÿ…´Ò²VÉêç:]EZK£Ý8ñ«ùF-ä=þà _`TVYý+é÷%²¦‹úUF |È×iD¥B“þå£rŸÐÁÇÓ?÷Ài~-Z´TÜÕ?ÉËú«|/\Ê­­ 5Q<{ÖlÄŠ›‹'òþ§r€ñö\e•™¢j‚9ÈØ_3*0ëoãI¿X‡{A1Ô\C|ûç Æ^Y8HAN}äøè#ø¦!öKê=XJA™Í›7—{51p˜!åNüÍIëÖÜ-€°ÌÝ­*c5V|¡ ‘ØŸúC%<£ ˆù ¨¹þ!2FÖ²S&…~u Ø¡°?} Ú#`†AáÙUST!Ad[˜ O‘*Yb”ÛKîVlkn S§NÁ/¹ü…µJ6ˆLë$6õä¬Òhƒå8˜!¯Ÿ›gŠè¯SGߘóõ=8Þ—agl^y86&å±ÀGq¸;6oÙ²•Ðxà~9†˜‰•®?ó0“dD‰2õ%òæˆåÃæÃ‚S‚>Ô8·Ê*«¸¾×\+e〈+®¸Rë ón¼ÉÆB{óÍ·Ü4Ì&"‘xÑø¼¸¨~çZË/îB•<¼0ÅcíZàÃQÇŽÛ ” UÛúO:~ý®(¡­ÕŸioõkiS@ýz‰³Vúi5«&†ú§AíŒá“‘¾"þ¯™ôªü«àáÚkÙ>˜€ö¹òÊ˵Þh5‡ßz%Ó§£},áõO›ö±äe7ØPÙ&œ·?7]ïÔi'!ÝË_®srHý°µ?UsVØ]wÝM /cw“Ÿ¯ÿäÉS–Z«ÄµmfŽò}…Ubµú…Œ‹~q2“­„1/íãS¦L(n´Í°A3?³íÿÞ„±‚+×ï1¾8‘níï Iq««¯ .ü‚½ºÜþÔGÌ:fôhØûŽZê—l™K, !{€÷¶Diÿ:œÙAÿGjîçþE]¤yFÐ2É4Á¬æÊ%×:ÁH”‘Õ¿6^Ò¹7NÐrŸ|ú©Œq“c*õóüCÃK=?ØyýÀK°âh W³@ЪU+ɉÓ*ýlj¬þ V4B(þ™JÝöî"2·Û~l>ü=z fŒôugb“d,ñJ”ÄrY5ëÔ©ŠË1ªk,3 …p ž›ˆ2rÖÚuØXýw¿û­Û¶ã6Ðù,IˆÖ\ý%É2T®?³DýZV”NBÈÒ «:÷õW([WÚ Êm¿ÝöŽöÃRUµ>\nÛÑÛ ” úE¼êð"%ÊbÌþZÞšoäY«Ü7-\è°Ï~ÐXà.úÎ}‡£ä±/”ÃR:wÎ9ç€WJ-ådFŸÂ‡õ܇ø«XÿfÍš¤%yG3HÞ¤pÕg3úé§1ÂÇônâ„÷ö&“Cˆ;ñøã­°~aöe5öå¶úaÍaרþĘý ý+ÚþjGsoåÂþb–ès…ÿE[¨Çصèf ÆÅø“I,ËTwÿQëc1þˆ=ŠñWÌ ÏrúLÎGæ8UáiìÉ'WßzÐÀ{qRɽnÀÄü|ÑŽ…>gÎly@±ìºÌÆg•—…ÕæéÕxÛc%º8F<ê×`±–M®VL!!a/o9ä8K3ŽY Òì£GuXÃ/*¹<àØ£›gK&Z‡³LÒú¸ï^×»÷E"uΜ9˜9ó†ä1}©~ÓÆ/v ‘Gõ?ñÄS‚ç…zîpl<ˆaï‹{ë -ØÐ3@ÞT^É+QjÖ²X‰,{yû%(sÈÅl>+—Å`sRA\ýõxé}O²°X<)FC 'ÌÜðÔ½pú“~T"Ag%ú!€íß®][T­„å}©OÐj¢› ÈÈ?"V²þØBóáÚv öf›ª_Ï5¿¦«t-ô£ð’[=\ ­E¹Vü¤þÀ¯œÿg?6ªçÀŠN8-Œúû^ƒ›øÛG©-‹““SsˆOûÿ û ¦qãFnõÕV—úË­·Bý/–>À}ÏúQÙ68u'CáˆjÔ‰¸lý±PÈ×y iýÇ Ok|äÄK0FD“QDQå"øH5xp–N5NòÉ%[ÿT¿ò¨ðwì„$rãÿà‡ |¢¿_¿~JÇ•KHæ"ìñŒs}°L“Rýš½ÊÍýl®ðð’×_…S©$°66Ь~lrëÈ:‡Ù\½q*ÕòA„§D1ôèqNµúÓöÏë—ŠP5…È%D’²ëƲizF˜S²¾q2ò87K”qÔ¹ÐíBýÚk4“Õ?£?Éo{1ªÝ&MÜÁxɾ÷¬›n¾9а‰·Àfÿ•©›¶m¤.\rúâ Ï«L_ fÍâÌ2·b¾¶1–°ÄK?ˆ «°døsÉÂïñ¡ßr—6dV‚÷õ7^÷†f½Táp‘ÃËõX¦ÔÆ/uSIЃ“Ǽ á«®þÁþʯV È„"¬VbÁƒdiÆoà‡ %,s»^lêÇ^c®$¨~$DíèKDÿW4ø@ôеªoÇwP! ß¥Y ðCƪøkÐ`U·J äÇ þÀQ‡K”E¶I@ :NÀ¸ÓªUk×—]z‰ÈcC6ŬÌ5þLh}ú\,# Œø6‡}µ$Ögzÿ x¸Ì3.ù\e3€XÛ þž*‚À»×^]áø¡ƒð±sР¢§sçβT ¯‡PÔñ,MÊ£ú‰YÿB¨ >R œ¥S͆.ÉWè/ìïN±"÷u,ä)üݨèÞ}âØBß(ƳG1þ²“¤V`Ú†âþ“Þä]5>ˆ™ ©ÁîÅË!ÃVXÿ¾>Žig<µ²vÙ ¿ü¿‡yØI‹ÃØØÖÅ’¢±cǹ±ãƺ±cÆ:®Ë;v,¢ø€Šl"4æ?þ=ð€—y+ÌôXÙ‚OÍ2È™c ÍÓ(Ø?@ÊèÀ´T7ûŒ£¸£~A eüø n|yì8øèxïÛðoú6_ü%˜ÿùöÑÔÿéÏâßÈÇã´CýMM?“'þ'ÎÆðLÙ¶ªƒ}<®GEÄPk{›•ð"Ò¿Ük=¹„§û8œäãfΜé°Ù¹†#èYÿN8ÑDû–e)²öß 3ì—~-sBýo¿ãvÇåLÇsŒÛgßî›Óº©˜Uƒ Sñ±ènùhA‰ìÓwÙUÀôÂ>ʽ¶´ú‘iÑâ…š$ë*¬ÑK*öÉcÒMп"ö?îØcñ±/‚qòI¿qÿxæ· F°1º,ï¸v$±Ë^{¹í:n/íÒðg«Ë÷4 ? ­_‡}±©;–`.wï¿ÿž;æØc\kÌ Ò¢‰Ä¡‡Ú«býqÒ™ûŸß)ÜM7v§Ÿö[ÑÏ}X¸ õóCÆUW\žKÍç혡J¶€ï ƒDÿc¨²ý[´hýí¯qÏÀn_áxî§žzÊáÔ(!?]³«$¹²E,X‹ ÆôIDò÷ÉÌ'>¹wØG’?Ù!Ü{HsÏ›?Ïá´½ µyóæ !»¥6^R¬ÀÐÔQG+7ÇŸx‚Ì*eÍgΘ.mÉå>$ƒê·ƒDñƒ4No–?žæN9õwIï‹%m?HØfa²>7ß|‹[0o¾P,üNŽ ÿìóÏ.É1é–g >,†o2°Ïr2õè‡oA‚¿rýYJÉZEäzûƒAèžÍ´rû«.ìQ×pP³õ³l§žrª|”·úçÛŸ^¯úñ!ºÆ•$¡‹Å¸;ï´³¨å‡é+¯¼Ò±ýµÌ˜µ†û?—ÜòÇ—Jõç^ƒýúßeÅv—^¦Ë§EMRÿ†¨ÏÙçbÆs°oÒnèÛ;—±o/à AöícŽÆLÖڷQ8úh»ví°|ô¹ç0‹I÷Ç¢¥çÍ›çžþEÑËÚ6oÞܪ&¸zuë¸31ë‹õ¸å¦›±gÖÝ¢›ÇÙ+cÍö·ú‡Š¨Tÿ =Iäë/J£púéìþ¯F÷8FCpÑ|b]åK®)¶°? “ô¿ÂÿàÑ`œ¢ÿ…ÞUô?é™ñFŒ,$£LtŸt¤‰PŠ-ÆŸÿñ§º}³?þøc>— kU•ð`ÙŠ{qÛ1îx‘“HáIL¡àD™˜¸øWU:'»XÉ<|«µ|„½þéžç1OÔ¯ ¦yM(™ñØÓ$£_õá.“è¼åÖ[!D¥LÃÉK¬?é—_~9$XH´ìÐa+©k#oã²ÒàCDЋ‘ì!ÚZl…ú]–è Z<ÀãÕɇYQ†Ð”WÏêé1ec=• õ‚>̈Êð‘/]¼DËþ‹{÷N( }ôQZ~”í‰pj[©4vÌh9Éü ´#øxwß~‡íKØ —ª–—Î;¿§èÙ§T)F¢P¥Ë–=à¸úȤ%åÕÊœ# iôk¯ÿò+ÊÛÔòËQö¨o7í‘ïŽôŸYÑ׉?ï¼sU=2¾‹‘ª«ê=}ÓO€)´dÉâ ã"iŸ,ß1Gíëœmì%íê‚ú¦pCœ4÷Õ—_…‚<†·¬.x‘S%R¶å%žÖǼö7'v1#–îèOA2šÆêô¦¯îó'‘—V•ð˜Ñ,£—‹ò­‰ÓÕð"¥ºyõú Ló‡‚[“ßå³^=Î9;µl„³BK&Œ/asï` ËoåÄ,*[̈G2†Ô[ì­õ»"}÷=÷X±2úíT2òQIýï0 Ø ³$ÀõêÞ½{ —ÈaÊs$¹SdÂk¾ž<˜e)Öbƒþ|ý:ð2§’ÍâqõjŸÛ8.‡ r_|1žœ7lØ0©íÀAƒ‚+_3ÿ¾ù–[<ÛËtƒ4Tf,ñ÷×Ë¿‚LÓÛW}*=•Œ'€ñ¡<êÿ<ΞiæoÔ$Âø€[ÏŠ'ûm¹E¬óðXwË]ß¾’‡z¨×ʤ²M/ï„U:þÒVÿý~…SÉà;uÚ)"ƒÅŒK3˜þggûOšÑr|çËfú‡²Bí#å<{îѸFÆRÇp*Ù7z*ù&ŒçýtyéÖ[n–Å·àãéjfÒè_iýå´K9™MKhåÄÒ»Ìóûô¢Å‹½hãÕx\X–ëÛÉ8 ¦³&žƒÄGÕþbð4kº|tËЖ´=–«¾D³üi}&w5–~÷ÈβƔA«àôª””à‚PÓ¼&”LÊð›¤+%¥8Æ Ô4¯ %“2¼Å&)ÆJIé€q'5ÍkBɤ o±IбRRz€`Ü B@MóšP2)Ã[l’b¬””àw‚PÓ¼&”LÊð›¤+%¥8Æ Ô4¯ %“2¼Å&)ÆJIé€q'5ÍkBɤ o±IбRRz€`Ü B@MóšP2)Ã[l’b¬””àw‚PÓ¼&”LÊð›¤+%¥8Æ Ô4¯ %“2¼Å&)ÆJIé€q'5ÍkBɤ o±IбRRz€`Ü B@MóšP2)Ã[l’b¬””àw‚PÓ¼&”LÊð›¤+%¥8Æ Ô4¯ %“2¼Å&)ÆJIé€q'5ÍkBɤ o±IбRRz€`Ü B@MóšP2)Ã[l’b¬£ËŒ!>à'r•Ô³\‚4¿?ïÓ½;A €×=…Ĺ٘m@,ÿêÖ­/T>Y1hÞ˜GÄ&+ê0W"/øK¾kZˆ‰,qž` äøøz!û\¨rŽ™ÜÞݺ¹ózžï&8¿¨ž !*å¹<ôó4 9¹TÈÓnX×ϼ ò|U~ª:ñõëÕÃU„„ˆKpÄV@w7=àÒ¥7”•'ÿ0'æqõAŠª %IR®œ¬?‘¤«€À.Û’Wòƒ/è·Ì"Ì À+¯¼J$óϳ{ô "‘½6<åþûî·1de¯„!/ q­ô×õË ëᤆ YE-”kYécœþO&`Ð’+§Õÿ¹°Ü ~½·¶©ÊT%–_NYa6–“Q(yê¡•—:}™žµ·ÜèVÂXåꡜšÇh>±ø÷ÜÑ<)WYýƒÒ„ ÷ 1ýâõ3ËøµÜ,£í£%Ú|ÓÍݸqcÜv؇ÄüÏTxÀ²‡ÅZÿµV¨?OÓœäòAV¹nݺ»­;l‘^{lPÎ_Ðû^Ç“w¶²\ˆÁ€ÿ›nº‰ÃÇDwîy=Õ ë¿éf›Bv7Å‚Ÿ›]7ÀR¼~Ê"X©þäµöW:˜|Ä™¬ý:õb~«³ÿ&›´wïWÆ ‘#¢´ýO8áx,šâ6Úp#J0²«ƒ×C.;ÅMo(Çkÿ=1û’&}ÔQ!Oª¿ŠûËhW¯žöaUné’eîœg Šû£ýÏRý¬ÿÕWs6“†^½Îõ ‚X%+ÆÏ4ó$¥³rÙ )Ïò'|ÌÈúßÿ ™¥g§vtøxêNúÍ)eúë‰_‹ W× ,£•ÒŠGL=Ü?¨û¿;L–í\vée®Qã&$aãín,– 3ࣀء_¿;ÿéú߀%²W]u•,׳þÇ¥{\p¡{ú©§QÔ*øjÑKûs“ãׇ ËoY—¹sg;|¬p·Üz›…ͧXÅz¸O¤íOûß3Ï<ëð±ƒlÂ7³O6\Ó-^´XlÀåQÃpRèŽ~ï/æ;{.–µ‘}ôFޤ`ñ¿6"'=ßþu`{e†UÍfö·l¬³Ô~Y]û[ÿãr®aÃ^SP4œEצm;wëÍ·ºQ#Gª.ÐêË}“ES©H¥KAŽ¿Üìý{ÿV)´ Þ˜=8jähÌ2;R0‡v(|t®»ì’Ëäùƒ<Óg|M~ Ų´þwÀG FÔ°îzëÉ~x†;ýôß·VÙVþ|9ÍþV2Š2¡*G•„ü çý/æI¸ŒyE„†Tão¡Ÿ–6;cœ’³«4z’7X<ÇW´?Íg=R=®ðÿÄ¥RláEÿóý¤l¨ð÷Ì0ÊʰŽÝòáûþîÃÈ?€ÙÀÀ˜õce-&."-ByÊÈ ¢ÐÿomÿÅØWè}ì—ÀM‘Û¶iãäE7õª¢ýÿ_Ûÿ믾r“ñAƒ²xüöꫯ.]Øz¨Åù~Ít¤E(Ï'\¸äŠ/gK°Ô‚§Ñ­¹&><1ÔÐþË–-Ŭñn½uyä´¾ìk&½F­Jé„M¿>H5á¯AMã—ƒq‰ íÆïú|y­òúyªfþ€ÙKK>ÿ‡Œ¿\®È ÏÛ·ßd…Ú¿&ûÓÔ±#dMÀ¥s“±tqñâ%®éú¸&ë6Á‡q|ôðײý)†¾ÌSõð{ >dn_ЪC¯ùög¹Æc jëÖmÜ:ë¬S£ÿWªÿüùó¥a‹kŒ>YQø˜¤{å¤zEüŒ{å4Zz(,„¦þQJ„‚ äëO47ç’\îµ´6bþ±Ÿ?¾úâK÷þ¤‰® ö»Úp£²öªÐþìÛãÆcÜYo]|Pl’5]RÁXk…áÝ{°9àà’±úˆ+Õßÿ|0”JÔοëoÕÆì%u—ê#ìXÏÅqçþS†<æüVôöö–SY6“…w³tT@//¿½¾jiÚdþŒ|úýS´Éì]äõúÿÊü ׯë—ïý7ê~<ðãæç3ÊÇÌʾwýÏ!9J%þ‰Ú~Ù˜äZœ³EÞüü_åQ „ç¯ç¯÷ŸÕÞÿ³ýw~bè6¡´Ñid!P«ÇVZ²ê<@„ŸAiÉB¥Sð~¥% •NÁDø”–,T:ágPZ²Pé<@„ŸAiÉB¥Sð~¥% •NÁDø”–,T:ágPZ²Pé<@„ŸAiÉB¥Sð~¥% •NÁDø ­ñµç_>Ç×»ÿÎÐ^7ž&ß]‹g¬ãÛ¾ÆÿK•¯)ð[¬N" ­NÁDø\Zþñ×Ë—¿?õúÇ“ö_~Žü9ø7]‹,4;ágPZ²Pé<@„ŸAiÉB¥Sð~¥% •NÁDø”–,T:ágPZ²Pé<@„ŸAiÉB¥Sð~¥% •NÁDø”–,T:ágPZ²Pé<@„ŸAiÉB¥Sð~¥% •NÁDø”–,T:ágPZ²Pé<@„ŸAiÉB¥Sð~¥% •NÁDø”–,T:ágPZ²Pé<@„ŸAiÉB¥Sð~¥% •NÁDø”–,T:ágPZ²Pyü§dëýÄ]_)ÚK) å‚Jm3Ïå¦0#שP¹’< ðù#QóÝïý;ÝÎÎÊo3VPÎd ó‚ä,àú‹4¹þúÌÛÕÑo9VPœ< pý¹þÜQ»#:^MÃÈE? r%y,,PçÝŒÏÏ\ ñÏ“ýì€ä±X°€óYõþÛ{nWǽúzäó’Ì•‚ZÁ®?ןû/j`wDÇ«Ã`¹èT®$ł꼛ñù™ $¾’_=2>±¥Û„cÙ·ä‚BXáñ.ê~¹Pãóó6?<*¼ÈPòù• åX›¼óyˆ”¸þ®u9ÉRA½pÿ)ž?»ƒ”“ÑUž¿×¾ÚÙ+ž¿‘„HÉ®ž=F… ^ xþ*ž¿»ƒ”Ï_ï?ÞÇ ·Ù!»;ëý7’)ÙÓccï¿£BP/¼ÿ*ïÙñ‰¡Qv!‘fá‘óI”ÈÕ÷Þ•r€QÔú¡K×ç¤T–œ×_Ô€úrtkcøï»ÕÑÜž?ž¿êŸÕiªKb‘üˆåšŽý÷¶Žàþsÿ¹ÿÔ;ì³ê’ÝoEÐï²ut÷ŸûÏý§öY‘¦ºÄý§ý¾RR@‰{—­£xþxþ|Ôù£ïíâ^?bšg¯-˜q÷W¹Gɯ›`Úþ_z®£Sfq´¼4>¿R¨|$Mnep,þF¬œdFÕëú‹2ZçþË ¬l¤ñüÙ=³«drÓ÷ü_mSsygl dÏßL‰÷T†÷Ÿl›ÈHŒ‘=Y¹¼4ž¿»fv–&7}Ïß,$USر‚Iyþ 3ž?ª˜h£ÝYƒ\^ÏŸ]3;K“›¾çO’ª)ìÎXÁ¤þŸóçlž¸4öóöIEND®B`‚ROCm-AMDMIGraphX-46524e8/docs/data/roctx2.jpg000066400000000000000000003655771510465702400203530ustar00rootroot00000000000000‰PNG  IHDRÞ»“¼F_ IDATxì½iØVÅ‘>þ<ßoâ‡1B.^F3’KÀhF\¢Æä÷1Ñ8Q1&A fD0¢É¸‹‰&wEáEûã ÊŽ‚ ²£" ûòôÿº«ú®®>ç<ðBfÌ$9Ïu½oWwWWuUWïËi„6¿V1¼‚k9‚¥€˜ •¥S²·È'úK5ÿLµþ“áÔö]¸CPܺþQE‹Iþ2TJS·?ÞÂBÝþ$›Q[qCPܺþQE%*¥©ë_]ÿœ™ÔíOR†ÖWcŠÛõögéÒåáwÜ~6ôâðÍc¾úxxøçÏ”¿}÷úüËÁáŸÿEý}ö;Øâú|þà°ï¿ 0ÿ?ï70ü3pá~~@èiÖ?ÿË€°¯…%:ˆGø¾Ÿ?¸”¦æ_ëÿïÉþ¬¾¥êé[6"9·„ïâÒ$€-@Œ¤—®ëJJôjþ™F‹ž’¾2*˜n­ÑÕA·¶?³š’=ÕõÏtS”ô•!ÑÀèÆHzéÖögZ+é³¶?ÓMPÒW†D£#é¥[ÛŸi­¤ÏÚþL7U@I_ ŒnŒ¤—nm¦µ’>kû3ÝT%}eH40º1’^ºµý™ÖJú¬íÏtS”ô•!ÑÀèÆHz[!4œíYRÆ# å À‡r¦… ^A“°ŠTóOÚHiÙ€R\! à­õÏ=â Åø Úþ’6dfg@)®PðÖöWÛŸv=†áƒêú—´‘ «v”â o]ÿêúW×?Ô‚ŠŠáƒêö'i#AÖìPŠ+¼uûCÓ«PŒªí/i#Afv”â ï_ÝþlÇ[%HÙ1bD1bxwd‚GŽFJ܈0b8ÂÓŸ§¡” çè&7™gO›ÂR~vÙBZ"㬦íI§æOMÜ¢²¼ßÃ1YEËI£êò¯êسsºY êý®õ/¨PImªû_¥£ºýËj]ò•åý®ë_]ÿ8rNÖ£Ú‰ý¯0›ºý¯R ´W ÷~GW‘†FÕã’JEÍ•Š«õ_R–ד‡kû T¨¤®ªû_¥£V+ÈÄÛ¯ª¨âZaÄÈj]’ÐÈd¦™BC1bdEÅuÚv°—望LË1qRÈe­"G––Õü©’[Uþ†$ ¯õ}ˆ™b¢iÖöWÒˆ P°®uûS·¿¥jÁ€ºýÕþÅ5TMÝÿ‹jT?ÒŒ&ÍÄž§¨;‡@Pz÷\X¿~½¤a<Á¨òW¤ü?ÑwìØ¦LQÛ7/¯“Hyyä+#R™9Km:¨’á¨#²ÓctU[põï™gžóçÏ›7o&is?0½ÁvToŒDþ©7ðß°~#£Â¬Y3%¿RGcýÓ²Òògû·™õ'Ñ¥ÈÒÙ)tX€úCôšÕ‡{î¹/µo7\æÒF=¢£Õ&Øa(èÛ•AÛ“bûƒúÇßÊUZ7³`~®7à,[¾\Ú´#7–ëéìÌeþgÍšµÓògûgmU$ÊôÓ§¿i¶õÞ{åþ¨*¯^ž”¶êJi«®ùï«Ãûï§öíÿÒ¥KµŽNî ŸX»”¨­úà±EÔÿ5kÖ¤ˆ.BÌ¿´üð”0mÚ47vKT±Ýa9ËŸõŸ6J£+ò'=¸.[>X`ÎÆ`¾ð³¡á†nþÔ#féái… m•Œç'ô%Uüwgþ³ÔÉ]]h'?ô³i3Æäš»*þ”A1èSwûöíáaŒÅ¦t†ÅÈŸ©gÏœµËþGò¶IÇXàýð#:óíïÓO?`,¶¥<Ã8žå1l–ÿVølÄOé 6è¸8³fÎ*õ?ÞþÈ?ÙNë7¬·~nÙÒe3s‹ü/^zöìÛÙ¯Øàdˆ‰D›à„° (K¼jÎDY,îlçŸ~h6’Á3ÏÐIÿþ‚óM~Øu5gÎ¥ã”óä“OZüô7ßý£.0M©|B}û~Áâï¹OËgíZÚA²ÒðnØAä?iR…œþ'M¦𡏀ÔøÃÐ(ÈòïÞ­[˜<¥Üy›üF ò_»†ò4¨Q£k.×D®f# 6LÂAöÌ3‡X¸—›0ÛL€ðÛ¶m›é’qÄ¥{à 7†;°#¤¿IÎ6¾6 ÙÅš ÛŠzñzCY¶ã^Œ“¼ÉÊuÁë4:ýÈ|ËïnM6jm“òèÿµþaÁ;副ÐvåŸè•¹Z»SQþ¢·îÛØeÌê\$ßiöÖ ß[H3-3o’òÛ g a[Å6T]ògÙ.‰v`$£üûí»ŸÙÅi§ý‡E·~Sl«œ^|[õ»ªöÀ½ëÎ;ïhvãçå_Sêªåïû…=í½Ô/€GûöÇêÜÖTçXÞþßpý GôÚýöÛ/–O³N;í4Aóò¿ü*úí¼ì‹<Šñ ²vÍšŠtQ&£× Þ®™ÇO«ÿ!¿¢ëåOp+„-›·„#Ž8BäÜ¿o_Yt1¬hÿgžáëOY~ênÉ2mG/¿üò¨·f¸îºkKuwô¨Ñú÷Ø3vÝØºö´áþêW¿ [·n3|ÖÆ£<\=ÓtÉþho>ño­ž–mã Œ)6lˆc>ß^~òèÜòVÊ—Òcûü½öúëfoŒ£üŸíÕ+üò—¿ Û··¯^¶Tæùž—Žasþäá]ÏÂÞ}÷Ýoyô:Ƹ굩i\E|¤L°Ïa„ ‘ìƒ=Ÿ¯}öùL˜8qb‰´;%=§ò' êÚø[8€6ýŸÌÌV=41æë/ ð‰l.¿µUFø´U ÑXïÎüçÞ{ï³tg–ú¹²üÐÃÒ%ZO%a#X!ÿÚµë¬ü/5ÊP ØX¬±Øù6ÿÉû…ööÇòÙîÆb,;u“¾±Ø±«£ñçÇñ_ëÿµšô?Ù)¸ÉØ ¡¬·4þò¼™7ž6uZÔCS!˜º)W‰?â°ÙÁùÂàã¯,Òhg¿ ÿŠ£æJeÄðááüó‡YáöèÞ]Ää±àw,JÑ£é{Ó7™2wà_˜\˜òšð§?ýI„eưSÆ•½£wï0hÐa¦„¡QÙ¶m»ì.bp‚Ž–\ÁLÞ;zw„>½ûÄø>Ìvt#Wפ²c‡…ô‘ÀàÃÆÇxq{ëDú“uŸdü{öÜ;OÓ§·¤;âðí¾ËÄ.6X¢ë­[c·Â;ï¼cé!ß}÷Þké|E‚ÌÌGQþU+W:»&f­V8ñÄ¥ )?x 4ÈÊy7vœ&k…0áÆ fHsàJþXÎÀv¬°¤ÿÔSO2J\VXÄ¿çN`ˆíØ ¶Ó6(tïÖÃøï-¶£ƒ…[o½Ålq6'ÞŽÓSnâ•Qü.po”ÏÖmiàò,ü»÷ž{Eë×­öÑGìßl´Ù!Ÿ°c ¸ø›ì&B WÜÅ™<¹ÓlÁO —/_z}¶—åiûm€èoêô½wß5»‚±þ‘¿¹ÒØç±\H­KF]b¨°Nù:ò¨° ³˜òûŒöOù;:´žb÷?ߨCg‡rH8xà@µ7<úè£-~ÁBôöäSÆHÙ’¶Ó á Y¸ÒN¤£Ö©ë½QGµž"Ͼþ¼1}CVh- 7ÿbv¨Ø¿Ôà :ÈUþ=÷î)²K’ ý“<ܼd4;5¢_´½;BϽöŽ<›6+qbo‡k‚VÉBO<w[µ³i_ç¼½Uñ·üQö’µUQþbùSÿ8)Eþ¤‰Ýê¶½Ù.‘¢ÛÚÑ '~mM©ÖVÅvvÜØ±¢G[ˆk6Â\¶NÿwÝu—Ùï›oN.Ñò´‘ßÊ'ÚϽö²ò‘¾$ÚŸ´­VØÓþBé$-Úu‡¶?,ÿWÿ°ŒŸ ´£^ÐVòõ¯‡´­sEù±ëÂò‹>xëfôaé7}útµAÇ/ÙyÐ|vï¡6êëRCoì!òG^¾>âðØŽFýZýO’0BmÊ?áEí¹ö÷'?ù‰Œ ÏŠ+Jö´¬?°ÿâøÇê}GGX¹õ'„M6…}öÙGêê?ÚoýµÂ|`:<úècRÖB¶Bʰ{7i{˜ßÀ‹~ç‰yC:ÔküùñËOêvl½PO¿'cŠÔþ@©§ÝºYþÆ'c>Ú/hA~ëOaohß ãŸÈF&Þ»Óÿ`cˆö…cÿýúõ³úpÐúú!‡•%SÕ[jÿs,Ôíé’_-GíÿÐþ“>Ê}ÿ™ù›pÇ×:æ°q•ƒüé_3Mî–í/aÛÖ­¦è'10Y…ž©Käm§ÿy;¨*)Ã>½UwÎþ= ]û[Š“]X×ŸÂæ`7óÙD¼!ýÆÅß¼·æšl¡{÷徤ÕÚ­ùO¶f dºˆñ íŸòÃ]¹bM´˜ÅÊúï‘pyÇߨÂÄ{J'ÇbÍpÔQGÉœ‰i‡`¼Ûùvý/òмÁˆýŒ‹ÁnÉn6{ˆ›GZ¯Ÿz*°C>&w2ãøtº—oØæEûÝűX´à¸ ¢z°M.¶¿/ýùÏQ†f¸ÿþû©"uwaäŸ'о.ðodC8G ;Ø\vù Üçž.f*Èñb¯xN¼qÔ¥Gm418š6uª×Ýwßc‚ö0Àå2„o|ãV¯¼úªÉƒ,A¡ÿ´Ï>á±Çþhüpù½ÓíJÌ;;‹vhyú «z4›§m…»î¼Ëò9gvÜ-Uªî¿¦bÚ;,oÍ ;¬ŒpüýŽªêú‹ÅQnêºÙh™Ø!¬¸ã'ýrþ»˜®ˆ+þb²Žûî×VðG…ÅŠ •ó’K.‘ʧƒá–GBù£Á:uª±¼û®{B£©•²ÿ®ˆ…0É*,*R3|ð@²´ØµèB*l̯·WÅv’ü8j£¶“Vôo½åwÆN´/¿îx7C³ÙÜá'ÞäÿÜóÏ›ü¾|·ûî»Ç“3î4Ûi†9Ð_†¥å™&ÊÀÀNK®#ÐüqÛýèGf#'œp¼‡fqb“Wœ (²4!”¼Æ»ÿ¤ µkטþe•5FúÕC T>þø#¡À´:¹Õ.‹(zbmìUþ‹.¾X°ô_+à8:XêàÅ_”¨¤·†”‹äW'j; ßK§%Îr†Ð’úÃGç‰Ó*kCÚ&¡°"9¸j£ÊÿÕW^sIZBd¿ëDÒðt3Áˆ˜¹šêŽ;ï°A’ìž#:iòC¦Kè4·7œBá ·ØÅôt3Þ‰£érç béÇØ<_ŒgìØqã$Ÿú°üVé éx ¶Â "Õ5`^2rT8ú˜câ`«%ƒGÚS¦§˜¥;ïJmþtL¼™)ºÌ¬¹Áhº…è }V•ûÅÎÓv½ÿa¿€ WN£¨gå zÀþ‹öã°AZçÐþ½ø'­sž0&GH‹&´›€ÿø˜¶¹;ãç]wXyVõ§yZõÝyG:P,/j ãO«ÿ¡®’«ùdÞéZ|Ð’…uÚ_q"ãQÏ<ó i±¨!?iÄhþ?8q¢é×N%¶B8ÿ‚ 4¼ÙÐ1IJ'/ÍðãŸå‹8|øÑ‡á´žfô~û›ßWÚ›ŽCGÖpIƒ]›tt„¹sØ'¢žn£F]¾yôÑnRŽÎw8Ûá‚™pÉùïnÿ£;ÞÚÿ<øÐCB§«f̘|ûóïÿþÃ$SåücvF<¹]«.R,’·6ux*n6®ºûn)Ô?ÔC¦¥kÈœÇÐ?ëqj“zNp±Hâ°¤C<ÉÑ5¼,ÀyHÜ<(úZ!êæ :æc lL–§víϸ±—Ëø:¢ý=†¶*g¶[ó?ñÇhð+-¸XRš<(úZ‹UO¼1ãü §3pÕÊÓÐzÚ Ý{°v±$l0±mºøâ‹48âáÊ+ÊŸñ‹Mvc Œÿ‹cŠÉ“:mü½pQº2Æ=Ú†d¬bv"cæ³ÅÓ‡…‰w”Ø¡QœÑŸüôlÉ?xòª£G&,aKwÌ«ú+Ç´t.Žš[ èb"­»|i·ì¼óΓ´@éßÿkb´œˆpâ=~üx-ˆf#Üu÷]‘‰‡ðóÿü¹vó#…×l[µ¶\*€ûqü)”ü÷ïÙ®©™4º)­AèRЛ&Ñ0gnõÄ»HâÎ;‹D $GÍ〺8ï\Õ5ø@áV3ï‘£æšrH<ÆÛÞXA\(EòD€.QC7lŒ+çÚ ~ð!î:U Æ4ˆåxkÔîºëNG `+üüç´ƒ¦îꎚC^TÚgŸ{ÞÒr÷ qþÍê†;ÇÅœé#5F&ä;Þ±³g¢V(gŸýcÿ'în#¨Gy#(á‘™Ó ƒè&äÑS³Ž<üÈÊøü`qN´oßý%½ô%G~S`LúäçPwgþã'Þ²ÐûŸÑcG€®&¬ül‚ ½†ýó¨9î*cW˜ºIó“DÔ×S·t#gY€‰sö Š¢ÿgÏš­üpãMèâ˜s7ŸÉÇnÇ›› î¨y'ÆLÏÿ©oÜwǯ BEHÂX½:]cz𡫸‡pÂw¿k:¾ð‚ Kñ‰:£Š!ñ;ÞŒVW‘FŒHGÍûõ;@¡¡ÄC›8ÖŠJ‰`¿ý>/ >~Gq¸àByþ(y`÷+i]ô_ ŽGb¡G÷nò0Œj€vgRTPgդƈ;À’ `¾ Ä1>ñ—UI1øFÈ);\G `¾(àï3Mûô¨0_4]ÿ“¼j ]#ÞKa%GjÙE’³‰f!âµXÌçPWa¡Ët×TqÛµU¤Oêžÿ­¿Ko>Ì£§¥”·bcámÕ›oèQsÐ3ZBœ>ºä˜»‹6dý®£Q&±“ýwµÿ¡®‹ƒRôò#‡¬s°k`åü-\d:€þñ#­ù æ[Üܹs´Ðxo•+SILCþ¥]£šó÷ÁHÃú7ÇÙIYÝO³ÿQérþIâ"ådž_sÍ5N«À$¶–¿•iœÔ0vWüчÑ~6th¸ðBÝíFƒ#çù¯eï³Î£’ý!KÈ/ô(irËqcr—y~æég,Oc.KcŠLæ‚üB‰BºÓ”þgNVosž¦7¿( t1KÒ œxCgÉŽw.ÿ /¼`ö÷‹_p KZt`X¬æÔíd×h‡Ÿøcáý-N²lt~õe}ÑEq‡Ò倕´ùJÛ¶¥£æ²ÓIÔèsôÑÂêO#’ø‰w"O2»â¯ò;ÉO•|9ö§8&ކ)rO½=¾Í?¬­ŠýŸ¶U‰'¯Úveþsï½zÊü½½yÞ€Iý/•íx7èѣÂöíÛäÄ)Ëþá)Y‹‹r"ANÍXn.Ãণæ +ý££,Ò/Ä 6Ú}¤á&ˆŽãó±’ãAAæÓ__ƒÞ`ÿÚg)ÍX¹ü 9}ùëQs—A'A‹@}§œzŠäç;ßþN¤JêÄNüß~k¾Ô?ÖSlŒùGò\ “eæ’b£`ûüóµÆÝ^½‹ÕS§¾~ûÛßÊNÜ©§žb÷l¸ã 2uô1GË+ÇÊ5_Ò£2ÍpâI'Å|¶Âå—ÿR C”Ø”G¢f»£ÜÌ1v~@ IDATp&…™qK™MÝç=½´KžóG2£'€ùròÎG ,5plÜÒrÉÿ. °¢qÈ@¡‚¿éú¤î„Ã0qŒHuݧœzªéšoHá+ùÊ•«ä.*î£â¾,îŠñNó/Y«à_Î} ¹ÉÝ)ÂÎGú‘*ݳÏgp·Œw?ªõã.À9é¤ïIB©°±üýz8Â~ò¤«)ßË/×cŽ8¾Û;í‡? ³çÌnk·Þ‚6:ìfåøÉ'ŸR[l4ãÄ;5G]лeMWáT”ÏÞz—wðñcù£œSxë—ÊT¤Ñ{îŠã`ùSQúa•4QFù*«âè©Õ‘oâRËÿU<¾ë›Å[cß ï¹G°ҭGãzH]‘zƒ¼¡Å?p5}¡±—Êg=åGêq¿Œ6¯JüûOÓ…»ÝÐÒ!æŠ.ÃÕåQLÔ‰ÙþtŽ«ÿ8a¡ ¡M}À(Ö¿tl¬º“TäK7çï冶Uxè¯{©üaíîL^qÅbXøÅOï«þq:ŗ΄ 7éàµÙ3gr®ºýb­~÷;¼ù ô¦<<%¼ûî{áÝwß5÷׿þu´G.ÄQNÊM—áêå·Ø¨žH…8›@VCWûÞ—Ã fÕÊaÅ*í |ýB¬Xçš`•O™ø6Úõ~ÅÚwïó™ÏI¹ëˇù'Oúe²m®Ý.Ò°oG5áÓìòÜ)ɳÒkoØ1eýÂb»$,öõxÕZÛ¨r¯eëÞþÒÆjç°÷«~uUÌz®?Lf´Mÿ±‘#Æ’%‹­m¾Ì&Ì\ÀÔ·$ŠãÚŸ'vãMì¸éìÙ¬§†QÈßG¤ºàŽË;Ê¿»ýÏk¯O5uâ].a¶ƒ.XgÊù#u–×þj„ƦE¨x%1‘²ôzJã*ÜË'Uºš“rôßûžŽ«ºÊß±‹ qj+çœsNÀ«óØÂøÿ§?ý©µ—>ø ËGC†ÄÉS}«öÉòW[]%Ù¶ `>Ÿ… .bØ|!îÆâQÙ|#¬<þÁ+®Ð¹ÿ´¶UóæÎ³ú§mUbkcò.Ììúg÷¨»uë&õ³(?Æ?&ÏÊïWÕŒ /¼PêúzžT©²?^÷êÖ½G6þbÿËwv¨.È¢=àXŒq(H;áÜh„_ü³Dá"Ž¿0†Ežàg¿¡$ëø¶ÒŽ¥}“qb‹¹1Þ")ZùoÙøq¹ëBû‹‚è”;çjë;äމ!–ÞfàØ®..Çï9ºUú—(¾jN¢ „«;ÞzÇû[Ç+Ÿbòð™¯ãŽ;N˜ëƒAºâˆ‰÷–-é>”h¿Œ^€î+…] üçñ¿ÿ}Ý唂Š <ùÄnaT @zßû »10ì2eÏ<d ”õµ ~Ä`9ÓeÂ;ãq:ÈU5¸'e>ö˜cÃyçž+ú¥®‘Î?>—&ÞiÇۃšGÀ;¶íüÎLRóÎ=ç5¶f#}–ŠÂYµ‰ïA„¶lI«¥b‰”K¥v€ü})ÚWÊ ïš5Ûý‘ou”¤Ã'7(&OümÛAÛá Be‡íàÓ(¡ >Ž–ÒÆPYöZ!T=®ÆÇïŽe]ÝÔ…oK£¢¼)ÿ{ð¸šÿEw¹»‰´ yþ2жOa÷y…,ø¥‰wܹ !ìûy}Mø+_ÑÕgÚß¼yo…—^z)¼ôç—Å}ùÏ/…¥Ký§”4“žÊL*ˆk×¥—4qHu¨õw»7mÒˆü•zCâqnêeèaøQÿå×ÊWÃBÇË/½$1ÞÿÀýòZ2m‹|6l”,«í¨þ?þXõ<Ñ[KõFžÅUVÒ£K<úw0oQ x« Øñ¦¦(7]ÊÈwòIÚ¾.y ûöÄŒªQi 0 ùÒe[…ãhUí²Î!Obo‡ÑÞôìyåÕ†bFÚñ7¼ˆ0ÄIòSÇt·o‹vàäç'–üÑV @‘¯³Îú±aBî¡ç µ6býúøù3fÐ2”·¸WÍ}Ù0O”ý[ôHžz§k8D°X>àcåc¸d©|¶!Ä$_¸œ¤©#fT‰•»ÒŸÊ|!·ÿ#:2<þøi0ŠdŽÊCž89E´\Mâæáâqµ®Ï0&§þÏpý\•üóuÁXfyÍBsáã&ˆê³>û9>ª«õ쳟íeŸ"”„Žû–òRüÓ¼i"ô Ä=ñ$ô :ëúx ^ b¿°>~¦×3HSÇz ú›G¦1,ib,ÆìµËqÑÿmgÚ´7l’?y²ž>d9˜ÒÈÀr'G™ß˹ To=pþ‹|᳇Ä$_ºÆ…BÃÁ*N+Œ92 ÃŽw³txxåU]¹è»?îGàr>Ž‘m± ã¸ëÇ«??2ÄûàÞê…v+Èe{ŠÆßÿï¶éÑ+U3úC塳nŸo¹ï“Oˆ%IMþ)¤@ÄêhŽ¡>ý§<è¡ùÂQ•]ÿZÙQs1Ω+Ø5å›Wå³+MýDUÇSñêâf«ùÄ;~b$‡^½¡ÂOc¾~µµ"ó^VFŸðÝïY¹¶vò9âã{­bÈMØAº ]ä—ª‡I~þ!'|Ã÷»a°Ø_zº™½†ˆ´XSÆw ³HcG‡­'òç@z²o̬«ýØua L½Óõƒ?™xÇ/5>Ù?åÇNÚ,;E âÛš i©ì’¡~à83ËGä(èÿ·‰kh^VÒÊÂZ±?õz#.Ü¿Fÿãùç°æaUòó4®‘NÔéP~ÙI“¶<Ž‹|ùÆþŸ}=ÓÒz°XÂS‹(_ «~à…2Ó2o†^Ÿë°;ˆ4©½j†ÿöó,9O—í/í W“ xdõÔµi”ÕÏÂnÖ¾µ‹í~ÿóú믙üMz(–†æ‚íÏ!‡"öÍS8>¿9Óµ)›Ë“Þ. üx(‹uóÜÿLoy€m\õ9Œ«ºÎiÉ e%6 6¦í¢•k—ãT‰¿»‹ôhw ÏÛ§ƒˆ3ýä0WþÈ-®·Édº0þeJ‰@ωI¯Å)i«°øÕ£GüZrÜù>[ÈŸÕÓ6ò£ ËõTSg²F‚YX”_>êéËõ}ÔúÇ'ÿýôÓ¯µ.Y¼DÝ%‹íÎ2ø½1=}Ç{wå'?ºÙ@[^evem ’¬«ýOZPjŠ ‹]¯Â±OضÖ?æ.êûZ ¤ÐÿàJ Ãp·Ÿ°ñ¹üW]u• Ìp÷ºÄß)§°|š÷îÛý¸{ ݦûõÖ{@H€?Ò°þÁ®%O>cÙ8äÓïʲ2çø­m|uÂŒñ–Ä€à¦(GYE¹JŸ¸pe'±i…Ñ£GKÿ¤å©÷ç_H—*®ò’…ÜØ.±üY×ð¡?üár§ÔÛ¿ ècû+¶æÆ<Ÿòƒ«[ì¿”ϧId’µJ~ 8ŒmxeÌsHðîö?þŽ÷$<¸ä~äÿ™x•NÆkˆg„ÃMàN# M®$F½Ûc‹Â,¼ÿâõïÑ8Nç¸j™3þO[Œ‹-\pAغe‹Ü)_ºl™¼ó¯îUq¹ELíNÃÆŸ,c†Ý¶ÿ9=h€$ó>/¿ÌŽÑ{çÔ‘Œù¶¤Ïòâjí_ÚªßåmâžA_‚^þ?ÿ|ÁïÊüÇ5Ït W¨â¸Âµ¿Ê¤øßIg Ì–¸ëÖ¥WÍ11ÅøèýÅ‹ìD êð«ø~C¢¡‹ZÿQRWã1nŽ<£íî®?h⤣ô ±®c,öñšµÞt²St‹½/Ê”1E³¾1Ås/ØøÛŸ>”+!Ñþuî¢ã/kOVº#ú j¯šë‡«µše®)!°”¿œEˆ©ÌÁó>o ¿ŠO¥ñ…‰w⇣ã¼×úoü«`ã{qÌàK/¿$«64rìxã×7sÁ‘$ÿSÖ­ìû€8zXüù,~íµ×B¿~_ÔAV³!hMCŠê+brÊ9v”Ó3‹è>Èä&>GvúŒlãúìx cÀc-˜ØáG]C¿/¿üJؾ}‡uTZÉ51w‘0éÙÙO±C’×’ú Â7ÜpÃ.õo"ÀWniJ‹Cزu« ê0 ÇÏ&͆< lkÅB‡Oûã®e¢XÌmçãƒI~wó³†<¤âS‚Þd»Üû¤ˆ× º0?¾cMÛùå—Ãö¼—Ü剷Ú`8‘ûH™—ï*ÇFma¼ó‡×ÿ…W³!ƒÒàDèøã7Û(>fDÙ¸ÛÁ‰·°®àïƒ< :²Êêv>pg8xl†‹?ýI|%—ŒåÎS|9¼{zxGhÄöNWYuBÊ{E›7o‘Ýè;o\m$ ~ñœxsúºÝÙõ†ÌK'wî‘eÒ+ÂN}Ð#¶¸þЕŸÒMÔjEGu‘wùL]$˜°«8)–q8©üzÿ-Q$©s\°X¸Häò©'ͶµÎiýó$5•(–ùûàaŒ¼ 'ØÓ¬Ò)Úí_êD£~ú“Ÿ™ë¯¿ÞòŽ…:þ”n¢.Pôrâ zþž ±ïºK?ÓþÓßäÄ[c‰C>íÜ"6üvõÄ-zz&Ý®ö?üA×øUñ÷Êö‹]×ωmÚ²9.¶7å[·ØIN?¥ˆkBRцX&ÞE›ÐŽ¿ØhLk“èȤJ~DñHn±¼RÞÒ5¦¿fÿãóÓN~±½¦n*ßËìaÄñ¨9Ú?bqJ«¥÷Ïã#¯U ö<«hÿË~aËfô Ÿ>xà4ïÜ8^Æbz%ô‰'uLûq¼S˜íT>IIèjN§N¦òîâô!åÏRGa7lLõùÕ×Ò§^c4“Ü"ÅBtÁ[͉w‘‰>®¦w•‰wKµi6dÇ…†—ôtå¹ø¸ÚàÁ'˜"tµ#Ï^´ãjõøñWZd‘¿E„V˜13}Ê+·É`R*k´²GÏ•1 ]É} Ì4Nÿë`8ùÇÕ<>àœÓ ‚´{M+ySv¼AAÂ1ìÏ|iºFz þÒêZK³ÀŠWÞÐåüóÜ1Ž®Ææ>Ÿ¢þüÒËÊ»ÑÇ>Þ‰×.U+ >a°í„p"ä9éˆZ¡a 4)~Ú•3¥i…¯Åo÷òEOÈ«;âíùS<°¤ö¦¬ú#ó¯¼LyšAq"…®‹¯òBß6¬—¼ñ…Ëoü+N„ðÖ¼·D'ØùÆÊŽIùà;ëöÂ%s¡®ÚîV';ðZÑF‹‚ê@íO>ß×h„#ŽÄkñj|\m̘ˬ|0IÈ¥Qú|°ïŠñWÄLåºË}Eœdÿò¸štRÍðŸçýgƇ÷^·¢l:(£’]åå}8N¤ºÄƒÅÚÔ ˜heyî9¸ÂRÙ¢ l#îÜ‚2¿ÕyÄGM92Ój'Ù”×Æ½Ô Nò3 /iòÞÚ”ø Æ©›ò•‡ÃÇ8º!Ì´ö6êS•ù‰øÆÃ¾/9]$_uUoZS‹zƒ½ûY¯¡yTò/Ú½cyÕ¼wæÊ§ÁwêY§pß×'NüÞIá¤O’¶} lü^b[Õl„ãÇÃGÅ9Ñ'`dSuŸ-Šo5ÇŽ·%5 À¤]8Ї‰?¥ÕÌ&ü9±¼üÙ—ìªÿÉ뜧˜øûP½?ªå<Ü}Ç{âĬüÏzŽ&‰$Þ{ï]ëÿ±£!åsâIáÄ¿'åÔ«×ç¤üð 6oËG=P”sz¡Üç,—1èëYÿª¯{ýßé¼$Uòc‡õ Øê_’_Ú*,v«øo–˜e¬'ÿ/ kû‹ûqa&L$ŸBçâ×Âø'GN©ÒdyKáY¶\b¼!Àvõ´}Š$¿ÑŠÈ²û£üDON-¯ Œ£«T½/¿ãý`Ì[¸êJìœj]¹þzýª†åÍɘ‡ÑG:t5>d§6sù9®ßÏ‘:Þ‘6³Ù~|ÍøT6eþ ‡.]üÖOz:üÊD¶Ù‡r¨%åã]í7‚H‡®&Í}FNlIã†øaæÌ6N=z”Äát ÇŒ¶ê{áDi«N;¹°Gìæò8sÚ ÃÆãÎç?iLOøÉb>ë©ÏóLWãr_ޝq£ºµúÞú¿ód,–Òà„Ǽy½ˆ‹ßñ PJ‘¬Â‡N×þÜãj­œø èüÏ‘±XJ)_˜‰õÒÛ¨ŒÅp¤Œaa[x X'掶£ ‹¤—Û¿„š*Zá70ñÖº¨ÝÃHĹŒ£äUŽ3˜Ÿ” šÿ»ï¼,˜ŸÐ2(Ñ΂ţqR&cš#ñ91xËQ‹2¹GcÆclHúÄi·w'ô§Ûѹý뇚¢–.]jJ9rd¸í¶ßËçÊ,ML=Ã^’kì¾òçųãÜ|HÈG"gÑÏã¤a °t0F\õƒÜ~wt²ÎcGˆšÍ0{6À$«ˆ6±;Tw¼‹ùÂ}<êš;ªH™:¼nmù ­˜Ÿ=•ß?ª‚û²ø9æMÃ5×]kŸ÷yâq (´á>÷œ¡† þø|[4®4ªVÊÞO§=þØcF‹4QAÈŸ¶³ÃBRæð !'²7Üx£L’W,_©ôš i€YŠ“54Âü±.ðDÂɰ-ŒÈÄûÞ<˜òN ´ÑHø¾§¤‡â+Òqןü«tÀωá¨õÒÑÑ'¬Z¥/‡2-˜ËQíf3\qEZìÒøœª^zÕ|TB A>‘¥yhÊÉ#D¸CÕû“UÖhÜñF^qÄß¾¡Z¨üœt+ȘýÇ{ÌʯØ#>?j^øÆ¸‰m@Rc„ì^QÃßñn_ÿ¡ÿ‘#F†Ûn¿=”ŸHh…ôRf#Üp½~ ³`@¥<ˆ>²Ð2ÿl ¿QÔÒ»ÒçÂ{üfS´­b§”k¨Ì?eOí_Êêîi—˜òôÕôPÞ’ÈÓµäs´øDѶÂ'鑟Ó1{‹,‰·»ý5ýOqÇ+b“u§È>ùä“ȦZÿY-¿ÅE Y]˜[~\ hºP£2µñ5bZa˶­2 FÞðÇq•(¿BÿEþâxÛ¶§sßkšÒ¢hC¾CHÊÉS~ò¡kò{þf0dXp1æûým·¹G—“þ³þTæ ­põ__Ño†%‹1ïàOö}€žÀH«ueþƒÍ–¨:7&çbÚÿ¼üùf°¸äŸ=;}[ûè£Öš3w†˜§?˜ë¨ó*àO>x;(ŽÅ|ýK›  §6UþÇÜÆ¿b£M\_Ó/ó€{Þ¾YFXP%~,6Ùo‚dÉw®ÿ‰'Ú8cëÞoÏd¼ñ#Ö1n8{ý r–¼Ìßv¼‹†>\¾ã­Ÿó“ Ÿƒü îxУ户ãgFø¯_üWxçÝwåQ–ã¶Nàlw$õæ›o6AðÊïý÷ß/êà“Q˜Œë«ÍºªÏDÙÏ ‡Î˜¬|Bœ$Stÿß(º¯ÆdÐÀöGÚçºÏyJxøÙˆNh”˜o´x?10‰>£üÈã"÷²û°aúøÂñJ' èx1)¿i³¿+㎷Ëz”&)ÚÒŽwC®BXAG gœ~ºé^þäÛ‘ñxÓÌ™ÓÍþYGÔ.¯j#}ÚH>'Æo¥öêk¯#?‚,äšõŸlG‹íãàúE›ÔØw³òÀ±Êé3¦ËñÊ3¦‡é3g„5¸ÇlìA w4¯uMë<ë´n¾Ùøó›GF}¯d£(;tšøUéŸüÍÍ‹LÞ@>ÈìØ±VÏÐî ú÷òçö–>g„Ïjäu.­N·ãÏðB¶ÌLh¸¢ý+–?ýÐ5äÇ7b¡¼š]õ[¶di´…¦Ô[âà•x¤Ó¿f¸mÕôéòþÀ“O>nÇ×ÑVáç]Ü!ðú¿S&ÞÚãFü IDATÆÈŽ7áÅ`úÑ/xû×òÑ|>ˆ~!¶ÛZ>L•rò]é2Ùu-÷hž?øáÕYüü+Æ`%®­ð “Œ» 8qÃßW¿¢'\ó?æíÓáM ÷ÿÛM¢sûÇW+XÇÐwRo¨8±Ä8=ÉÂ>¿¦ð¿Õÿx¹ Sþ¢ßKN `y'÷Ê+Ó¢¨×?q¥Le²Ü]Û›éÚÞP~öÿÇ2Å ¼¬Ã{÷ìÖoÀ+ÿJí ûd&v¬°AB.x\M'–øb€/ ÊcnJ"ö†òкÍ|¥ñò&ãµKr$õ“HÔUŒ)0i·ÅõTû||JµêgW5²…l/‰¦b»ƒSEûgC;Ϻîx7ÃÍ7Oo/˜yäQùœ’î$j½ý÷·UeËiR£©&uýŸíJf¯½Â±X3à®7r]2æ › ”lG«¾|à—¬_FÞuì‹”éDmCòñÛ8´ÿ¦·Âï_ÊX, õ‡¿5þ¿æ'µ\Ùä/i=Aˆ›¿`¾Ù õkþGyF¿ç,o®B{x,킸㭆ç’EJéxmC'Þ1J²d²¨ƒ6v$dW.ælÖ¬9ÖȇGÑ)˜„ÅA®°ˆ|49VòF+YK+ TB•[%¿Y‹5 ÊTwôq¤)¡ó忱'2g¯ÅjeAxGG‡&“‡t‘ÃVלŒwð¨yGÍï31¸Ò,|Ü`3ó7Ó‘jK耮ÊÏ»Ò »  ç…F™?Tfî´z´òÇ ¢ˆCùsZÿá)éSbàÏÉ^¶>ðK¬ÌÔmnoØéÄÉÿP)™uSðïÞ­[Xóq$\€Ç` uÁ•ä®ÕrGÍqÇ[ËÇîá´)òïÝ;ÚAÐÅÚÿûñŽ·²Ã«íxÑ2ªýi£¥ö‡•ÉSOáJ\’Eðÿš'õAýûú_eÿ<Þþ£â.ÊZxŠùÛo¿}µä°8”½VÉ2*ºÍ0ñÁ‰’%ž@ý÷GÍ ]œüýÄ£Tèô;¥þçH<è±[!L‘—à?ž ¢ÓX¨ßþ`â)†iSÓ',X†¹«ú†—[ANÆp°”ã¥<ÿ¸ËÇ c},ò÷eãõ¿v \i_þàÏvô’ÞR"¼"ÎüB6a¯°¦×þåí¯½öjíT.?ya2Û§þ1qÖ’ÿýŽ›÷$ŠpÿÃn·¼ëIÏKa¶U·Ür« 29ñö:ÆÄ›yyãÍéñ¤ d’+ËŸ¾ P΋·¿>}:2¾ü»Òÿ€w²k\O*؃Ó?tì§Å®FŽ;ÞNÇprúޔ·Ãû‹ß7ýŽºä7W@+ô; Ÿð÷§‡¼«,¶Ò¦ÿ….|ð!aau Ù” I‘/vè»Y³°c¬Lqáüä’†ºÿ.o”?³·¸HCÚÞþxµ‹yÉÆmÚßk®Åqî2ÿd;ÅÓ¤®õowûN¼Yç!‡‡¡ÿ›&Üw[µÿŽ™^’׿ªè²ù¸ãÎ;DïèÒãj™ä¢…Óg\ß0ýºúŒ0,X®Z…GÌv?ËùÃãj^f{û»ð tW Š«vPho*Úo”ßóÅ-ªr%/Á³1_ør|[¢ »êDù_Žþ´äXê ‹;Åùók#{ï­}ÉîÌdâËŸ»þäéíßÂ⢡¨-¦ëªü‰ïí€^Uû»dIz×a¿ÏÞDÎë©ï‡R™Áþœ¨ãîx£ü‡_¬§š}Ùà‘?Ê4¥S?çÅ+£ç#ÉÌÆŸÄ‡+cŠ(??EW5þòö÷àDmß@sÚ´©=¥d³)®2ÞýðÃcúf¸ý·§¨˜'(¨ÿßœŽyCêprwÞåçÒX@)LcÜŽ·ðÿˆáÃÃ…þB2u¸¬fx j"²ã Ù|\ ÙæŠå˾I핌{g 9+lÚ¿¯Jf¨èÛ·É ©ú0§î¾ûí§lÃ&ê²t÷=zôÖÛo+¦ÅµèüF³”P4€sA®ùoE>YÒVX÷É'Y£e:ˆ%È€/üÅ…’ÆVŽ ü9­¸_¡¾m+«°ÚäAÞ¸û#?¡[ žå?zJ(€ïøÉvF±üûè/±°üÉk9ìàØcÔbñR5¾ŠgüýŸÐüâw:jœVkG8À=š!“§˜G±_]•}JŒòãeË쮤фX%¶c©îsßùÎÿ K G[ÑÁ oZ>eûããjà{ÿˆXŸ¬û$³®–3o,o¸§"[î¹s)ŽÞ?bwn£±Ãqó„›Âþ}û–ìïØc¿æ½5ï/²«cžÇ\vY¤™GgøÅ dd}ÖYñ3%-qÝç ìt‡ìxëCOF,94àÑIŠ|†Pma|”Žüa;¢·È‘;Þ;$ÿT.yûƒx¼IÀßôéošŒV÷ ²Ö°Ÿ‹IZ2±Á Иˆ$>Úñ‰>ë'™DÛ¦×ËØ™±¡ |H›®ÈÆÁIÌcŸŽÞ–FíMÛpÇ 0 ñö–,ªù+ÑÉ €´Ul7˜'º®þ¡­úÍœ‚Òrxíõ×=1)ÿè1—ZûWäÿÒŸÑVg6Ëú7 ÿ×b[¥)°K‚8è¬ÝÄ›zÔïxï™üŸ¬×úÓ®lŽë"IwšGt¥ÿn0ibžÍ-Ø(Ây/íù_|ñp²#s«sHÓÑÑ;Ø)Žf#à1ªôËËÿÒK/µ| .&¸ûîxϽÙóÙo#NMF°|»c²ÐŽ SüÓv4çÿiô?Q”èäü5Ð Sñ ,0û»ñÆxÕÄ%! <äµ3ù‡2㸧Ê/Aì·ï¾r¼\è8þþHê±ßJŸ¢ÄqqÐÊ;ý)¡f¥¢ý)ÖmÚ’Ï/Ê©s2£Ì…“1Å·3ûc½ï߀«§>êøn¼½ËŸ÷Ñ_Yÿ»ÛÿÈ·¥]b?€…½½Â‘G%×/ß}÷•Àg)r‡l\¢èa©¤ºÝ¬[ú4­°lùòð-Œ¯ýc§ðÇg 8®²$씿όžfMò³Á×~pÊ)á•—_ñIÚ!Ó˜‹¼Fe»S"À—e ò-ùìîÚ÷ÃäÔA¤ù‚~zRSâÔ‚ämÕkž„ CHmUSô´;óŸ&ê˜ QOÛÙ?击Sxq%‹>ÀÍ1špÙØË’Îþ.¾èb•c±øÃü«]ÞRx3L‘zêß.j„‹ìíhã­¾¿ÈôÏöíié˜"å½µ£pê@ÊC&Þé¨yê³hwtýŽÅ„b+ÈÉÄbù{“N„Ê £>N“o‘7>—]iIYŽ˜€1öQGé{AñŠ+‹_ êZýO%› òÃDšpb|€ á£â[š(%^YÄÊóÜ·æd:'=¸ƒÿXAÀ‘‚©oL“GʉSZc4urñ| 'yHF&F9.¦´.&KÈpºä‘ÜDƒa†k@)& ”Òæ¨ÉGˆ.)%Wc|¼Á»°RôÆ´iòÂ3¾1ŽÐrÔäÃý##›;O퀔’«¸)…£åSL)q)í&ØÎŒr÷c{…•$è&¾AŽá¿1mª…Óãxk¸0• Pýøïb2Ãé’Rr5ÆÇl±]€€êÇÆ,xgAøó‹’ãa˜x2œ.)%Wc|¼ÁÛ¨~üw1™átI)¹ãã 6€Ø.@@õã¿‹É| §KJÉÕo°Ävªÿ]Læc8ÜÍb£3ô7¦œŽÑŸbað?ßTr÷ï—ÿG~ ßçÄÍ[òcaI¿òCFšÝ$7¡¿où?­þ'×oò¢K­Ó½tô(Àâ8ô’÷K°á@l  úñßÅd>†Ó%¥äjŒ7Øb»Õÿ.&ó1œ.)%·>øèC©§oL&Ÿþ4\ˆíT?þ»˜ÌÇpº¤”\ññ@l  úñßÅd>†Ó%¥äjŒ7Øb§€­[0®z3¼5o^Ø^_'Þ`ˆíT?þ»˜ÌÇpº¤”\ññ@l  úñßÅd>†Ó%¥äjŒ7Øb»Õÿ.&ó1œ.(=óì3¶¸ÏÝw qo°ÊÇïqål%>­,qŠ€ÿ9jò¢«GÍ+ä‘;Û<ƒá 2%ÓDwÄð‘yHOü ŒnîsްtAâ ZÈ^Í_ )W<µbêaIîFù—Ó’Ht… 9ÕüE+¹’yRuµþ©‰d^4!‹‰@1¼ÒÏÀèæN­ª‡º­íš¨íš(ÚH»ð"žøÝÜù‡®[¶lµO±bçß®•#'Q¿Ô\2D*¾ˆ°3?©äŠgh‰v]ÿMÉmuD C¨õ/¨Ô£›;ÿÐõ¿ZgÔ—SM ¢å©[ ¯ô30º¹ã˜DÒÇõ‹ðxÇÚG¶»*õ¿(ÚñvL v>–V(ª¼°Y>†Oì2šÄ”¢]@?)ƒ.ý›âäãKL)ÚÔü‹*?5D· )Å%¨ˆ'1¥hP뿨²Zÿ¬·Î­RR²¢ñ$¦íjû+ª¬¶?gwÎRJzJq *"IL)ÚÔöWTÙ?œýá¾ãç>‹Ï5äºÐ¦M›l.ì,¥¤§— "’Ä”¢]@mE•ýÃÙ_•h!tw†Sž¹%lI_"âjûKÊr5D×E˜âd‘˜R´ øÒ?ÚY\™@;;dÈÕЧ,šxû’jwgÛ><ŽÀ©àŠõª7RZU «QmQËåuiÜIü2B‘HÑo)ÊåCN@-¿»‰’ÔbPQ‰E[Ä5ý–¨.ÿÚþjû³!½«‹õ­è'^¶¨–Fû“ºý݉¦ŠQE¿é²Q1äÔõÿÿlý_²diÀ#qW^•^8¯¨f±,Ë¥]IÅnP]þÿgË_ʨXˆE¿/Hƒh‹êñêò¯Ë¿}£RD·5ªrD9Ä^„ÿÊö·eó–ðï§ý{ø·ÿïß+ýŠBý– Q1ä´BÈ'Þ¢/5O©ä¸ —ω¹ Ñ+.þ¥Kù>Ú(:þÖfÂãVVdCéÿjþ¦ W<Ô!Ê‹*“°Zÿ¦šJÀ)KA€ôŠ‹µý™*œzL·µý™ÉˆNêúg¦Q °~™-¹$ ×Œ®®¦ §Óm]ÿÌdD'¥ú‡OŒý5ù'{`²pÑ º`Ò+.þÕõÏTáÔcJ­ëŸ™Œè„õÀTh_¦KTzMéµý™*œzL«ÿ`ö‡ÏTf¿OQ~xÓ@].t"͇à@ÆÂ厷Û%LZt]‚<ÈùèÐ÷ $-ºŽJä|tè{’]G%r>:ô=I‹®£’9Ÿúž¤E×QɃœÏ}Ï@Ò¢ë¨äAÎç@‡¾g iÑuTò çs Cß3´è:*yó9СïHZt•<ÈùèÐ÷ $-ºŽJä|tè{’]G%r>:ô=I‹®£’9Ÿúž¤E×QɃœÏ}Ï@Ò¢ë¨äAÎç@‡¾g iÑuTò çs Cß3´è:*yó9СïHZt•<ÈùèÐ÷ $-ºŽJä|tè{’]G%r>:ô=I‹®£’9Ÿúž¤E×QɃœÏ}Ï@Ò¢ë¨äAÎç@‡¾g iÑuTò çs Cß3´è:*yó9СïHZt•<ÈùèÐ÷ $-ºŽJä|tè{’]G%r>:ô=I‹®£’9Ÿúž¤E×QɃœÏ}Ï@Ò¢ë¨äAÎç@‡¾Û`¾ãmÉõ•róhÇPÂ[iâmxXÒÒžœòô˜1A#á.Ò@jþ*LÚ‰‘™S[TëŸõÂ)Ç@jû«í/U• ]Ä ç$Û±ÀŠ ‰“pi µýE%&Ôý×…Ù˜±AµýÕí¿6.Î8 4 nêö'5ºˆAÎI¶cA'á.Ò@jû‹JL©û?¯ ³1*b+‚þû“íú¤ïw°F Óõø–ÿ”³DlÑòwÝC}>5aº?‡=†‡‰Uó¯*j.µF·]˜O“àªT)ÔkþI^[ e]„¦Kܲë1"Œ12Œ1\¾Ù=q#ôßðn?ñf«kE_kû3ÝT%}eH40º1’^ùœ=.¡²O[¸é´C5Ч‘ÀB@Á›P*"|PÍ?i#A¦vJq…€‚·Ö?í¹B1>¨¶¿¤™ÙPŠ+¼µýÕö§Ãž ÃðAuýKÚHU;Jq…€‚·®uý«ëjAEÅðAuû“´‘ kv (Å Þºý¡éU(ÆÕö—´‘ 3;Jq…€‚÷¯n¶ã­³çýŽòV±%Ó¨Âùª©íjJ‘ ÷{¸æ/¨PI­UŠý¯ÒQ…ý=ÿÂó¡oßýC¯^½Â³Ï<[a`„*‚jý›æµk©ÒQ…þUáEdï÷pEñÄ Zÿª'û_¡¶ì5CÓ€"²÷{¸Ö¿h B%Ô¡Fýí÷O>ùdrÖ0pà€0pàÀpî9ç†Ûo¿] àoPþ§ Ï™g†yÎ9çœpå©í?Vl:¾€=ã+‚þûþùçBß¾}Ãçz}.<óÌ3…ö¨‚Y!hJç”0ìüaaôèѱ$ÿöëK‚zMþ‚z,B•bÿ :´ºÿ5må@QYÞïᘪ"ˆå¤Qµý•šT¨®¶¿ o¿ª¢†£f³}ûŽðpç”Ð9er˜7ï-Ñ¡·µ¡}äáÐÙÙ:;§XK0kö, ›Ã;'w†ÎÉ“oJg`ø¦M›Ä‚wìØ}ä¡ÑÙ9YøuNé O?ýLXðöü°y‹â7ù¯Zµ*LZ“ J5aÙ²eÆgÃú VØÈùK¾§t†É“'»0å¿i³òÃõ>‘¼Mîì K—-‹¼˜æH,Êâ2@2îñ²Øý°j•É¿àù A’µÂ²¥ËøãoÃÆ ¯Ýéo¾¡rLé ï½÷^N#û¼·æ™¬ #Ž/ÿéÓß4ý/Y²4 Ù ³fÍ6þ(èŽúgùOéœ6Ç2MÌc¡µ­l(š$ƒ·¿$d’´ˆÉRP~>ÖåÁ'Q­pø A¡Ñh†F£~XB’JWȤ˜h‹§Á. î‚Â.p’J·ó?ÆõêÕá¾{ï —^zi:ôgᆯsçÍu"ýÏñ߸ic˜ÿöÛaëÖm&ò®ÊÿÍéoXý{×ÙuÊIi}æŠâÚÄÅò7\ѪÃPýøïbþxõGá©§ž W^ye8ïÜsÂ5W_/^œÈ’„>e!:³I¦i 11/§=ìxHÂ6q¡üe8¾+øoܸ)¼ýöÛaËÖ-ÿ'ê?³ +÷:M{-*\I‰#T!?b6nÜÞžÿvؼy³K"ÈÉ/ä•G!ÆåŠèŠGŸ¹’°MÜNÊÿüó/ÍF#4š kÑ>ŸRôt=lÜwjÿë7¬³gÏk׬‰ @ÃÑPý…˜ÝæþùçKÞE¦ØÖ«l~ÿ¡Àe7ùoŠeºuëÖ¤!)„R˜ƒ6lØ mâ–-Hó—ñwd¸ þíÚ_ŒÿV´:|²~Ð*äl·õŸ2”Cä?Hú`µ¯A‡R$Q›ü+j¦ÄÿŒÓ$ýwϽ{ˆ‘çaÎGþ‰¡òÿnðÏìÖÑ׌;š>N²æã;¿€ê/Ä”äÏÒy’Piø`µü^7-K”Æbþ×ô¿}Ƕ°pѰté²X¤Ÿ.ÿÌŽþ òÿ#ðw=h*\m(BX»v­4dèœFÍzjîØqc¥Cn6šáçÆYsqæC,;jqÑi»¿¥K— ­m[·ÅνÐÁ·Ù7ÞpchíØa¼'Mšl‚ý0Ëß a²Å7Ĉ™ðÌ3Ï´¾Ð·ošÐ7²ýÇǯ¨ –´-±Ì<šäå—“<½;:Âí¸=Êsy|üà’üQmùùˆÍ›6K™`훎;ü0YÌ,¶ÈÞ–X:諪géKáK~9<ýÔÓ,Ïf§p&ræ)'+E# Pþð¾òÊ+’·î=º—‰B2š™§€X¬«Æºlö }p9iÛ°üÑé§K~{îµ7Å©Ä/eò#tÅŠr"â«}u§íAF3ó”³PŠÞ ÿrêêŒfæ)ã—¢kþmË¿¬½.8XèÞ½G@½À—žðËtšy”ÆÝÝ»w—¿á#FDk‹Ô£þ±`ÿÃÓþÃÚ]Œ%Ðüƒ“ÃÇ«?ŽÈÕNÆ2ó”ñKÑ‘?0  œ¶]HF3ó”S”¢…—üû‡áßPq½rRÈÚµkÌF]2J;eÊ‹;ê¨#ömÛØv›Ü6¡££#ôéÝz÷éúttˆ¿£Oï€Î|ÕÊ¢h¤m4µ³ê¹÷^áC‘£m½Œðè£6ƒ4iR‡]'‹”‰*'æ°ð½…öx›ä \³útôè„‘ÇŽ>}RÞ::ÂÊ•+¬:M›6ÍxMž‚ýôsæ’ñOJ&¿ŒO-¦ÐI“'+Ø‹<"âÊB&ÌÍfX´p¡‘'ÿyóæY¡«Ý»Ën’ ʉÅäª?äïѽ[ØŠr‹¿wÞyÇMÀáÞ{ïaU gž,šÍTŽ¡wo请ê®7ô¶’ä’ëøk`Yþ Ù<À‹¸¹“aq,’@ø¯þøã0~üøð›ßÞ, NšôÓãϬš,ð¿Ë§Ut¢¨v…z€Ý~t ïÙs¯¬nÇb°ìª?†#CsæÌ‘ú{Ó‰–Ö=ïG}ß°‘“TO¤æÍ›kyAžºõè¶nÙÒeþTcªi!CWŽc‘Eß ‚ IDATZ!¼¿øý0 ÕYlÍb»vë­·&~’¶V­ZöÙgŸ¤ëfSìƒtüºÊ?×4¥H¡«>ø@ùÄö—ö?˜“Ì6"î ¬°˜Û÷ö/¼bÿwë­·Pkÿ£úO’&­A{ÔDbª!_|±é“V"ñwG~ò¸úšÿÎäÇà’ý?ôríµ×FTPs‡¤º\þ ¿ÈÚŸ—_~ÉèG²FÏøIÈ®ùÏž3Gú?ÚKæF[:ä뇄õ×Å‹ßýô·üøòÛh4Bª ¹àÅüª?Ç1Fö°þO™’û Å@Ê]Öni”"ÅÉ«ñ㯿ùÍob ¼\6¦dÔ¯ÿ±Ø ]ž²ßȯíÎ?Y`¹ EÒ¥Û«—ê§{w]è²Ô•ÕÂÅî$Á„§P.èyí¹÷ÞaûŽ,Á®0|êys°¦‹ìG}ó¨°|ÅrÁ^¾bE8ö˜c­ÿyûítÝʧ/î’éu7µöÞ«gرÝSTXþû`G¹M°`|²þ¹— »Aû†_hؾ±?ÇxE~­nž€“2Ú—ÞrËoÅv´Záõ×^Ÿ‹v€5Ö…ñwÙl féá)”ËÄ~ÿûßÛø¯-ñ.D”Ø•ªù“t†ÎÀ ·ÝÄ;KO) ÌÿÃ? {÷ìº÷èºuï.v‰ñÏî´;Ìb‰]) ÌŸiáfè>¢‹p–žR@Í_T’é%)—Áï¼³Àæ=¨¿ø6 ïö¿w,û!>Úƒa??ß%Pê8Äc|³cG+lÛº5LyX7G>aÂM.]×Aæ_RÀS ¨Ë_T’é%é·MpBØ”¥‡G^5g¢,VqÔœǨQ£$v±»ÃÎ;ZÅß™Ctw“´D6­îñÑÁÐ0±Ò^üÍš9Óâo¸ñF‰–£æÑø5m3<÷œN"À‰7â.\$Ò"/ÅEã•2jA¦MjüqÇzw~‰l{ùAoÒCù>òý,åiµ ò¤oæ¥o<"ŽÝ h²ùû£æÀ9ï¼ó”D+ènŒÓé=æ™p¡½ÆÉˆâï¾È}ÌBÛ7Œ•Î;÷ÜÌÄJvED½¤~Ìö°ü‘E[}çw„3ÏÒ6}äE]î½ç^yg@²ù?÷ìsÒ6\uÕUÒFqüG{AY«þ.Ëß‘ !üêW¿’¸eq1‹£/¾óÿŽ:òÈpÑð‹l?éƒ/Ó¾¤Øÿ‚Ç«®m+Ö?,lÜ~ûÂ~ô£0è°Ar}îõ×§†¡C‡Šîörw¼£X¢‰…ï/’>êûßÿ¾ö§§œxà™àd…#9tÿZ-«ÇÇ“6.v§ çŸàŠ$;‹ü Ë_¹í|ü¹+ù…ÆnÖ?¤IbýmóÇ{TýЫr‹rÈ¡bkÃdÇ»¢zÔ¨°}ûö¬1Æqóª%ce¿úÍ!*rÛ¶íbX8faoiloÑ¢E¶píµ× IîcrNaùñŽw\Y÷;ÄØ¹Ù¹ÝYA:þè,Øé#r¾A©Ùæ_E´¥vüe?æë”gPzä ;ÞìqÔÒ¯a’J½|>Þ×£æšZôWéâù{úSöˆ„Ý êOòÙlH£"mé8ý—è;þ(GÐô;ýðŸzÊ©áË_úrÔ‹î^]xá…Bêý÷™¾ToºÃc>7\}lç¹[·n]8õÔSsYÜBÅý÷ݯYò/[ºÜøŸ{Î9‰g<¹ÞÐÿÌ™³J"^ë¨Àóúûb¿~áƒÓ ªðüsχ¾ûãî¤/AåO§5T.Ü­vWfÍ*ä¡Bÿ䃼 õò8àXðÌ3»dÿð2Ÿ¿þõ¯s´Zaþü·M®y°ëkÕ®õEn×H¼bù  ‘.]äƒn}‘YåïÞMr™íÄrAø¥/oÐøE´¼¶Ë²À´òËß÷'Ñv$?®ü™?ì¢ûß²¥KÿóÏ?ïÃêB ù#-ŠäGZÖ볽Ží;J• xI}iÆ•ú=¬‡ùôꕽ»Á|àΫ¼Ðh¶ÙøÿÙû²¨«Š£ísî;á"z¡.u}fŬ˜˜qœg5jŒC>œÎC™5Q£F@" ÆÄÅ9Šˆƒ2É ƒÌœþ×SÕOuõÞû¼À3|ÉÙkÓÕ]ÕUݵ{÷ÞÝ]]­úçóÿñÇŸh]ë4±ÅN%’«†£þm~3k“s@,—_ƨÉ_MÚ‹4MY“=ËPÌ(ñ²|8eÿ#fÆMêOv[’¿aý†pø¡‡Ç6¤Ï¿¶µ¸ÕÃ=³ä ë5ÐøöÅ<>mÒ /ˆ—.]*ÏhðÜc¢žÕ[¼h±¬ì׵뾶z{æ™gJ=ñþáý§|„x¦ ϦÝ'Oá-Õ¼wÚqç­ª¿zÐaþíG!¿OÄëzb!`käÏþŒï™z¸#ëßÊ÷ŸUÄ uo; …†[#…\¸pAØu×ÝŒ§ÔÇõ?°&1/õ!„ÓN;-§í„eB~ÂÔKF]½üÒËá•ɯØâ ­G€§ ýܹsã½qï©X.ð¿÷|ç•k‰ïö?”ÇPûœšLÞHV—ýžøîZWÄѱÂ]…çÛ$AËoÇ:Ë& ù+cS½ÚÙÿ‚eK~¥b5‘ºoª¤ˆØýŸ×+ù8ÂwËöÒo§Êï(ÿ¼sϳþyö²<ÑÔÜÉÇ" úø0_V®ý}öÙòÌùÉȦUcãˆe¨ÖҶ׿ȧ%¿¨§î›*©¯æë@°Y¹2:W«Õ€Ãe—ÁË©RÎ;ï¼,«zõ:GVG/Z.Z$æT‹-VxábGÞÓ,vžWV¼Q$ÌÀÿê+¯Š\35¯ÕÌ hÔ=­£÷¦æºâ]"ìG^´xa,Û¢>(«¿ ¦æ”#+Le}¥|Š š¡á³øú`¦[^VõZ˜M빂ýcϺ\‘ÏÍ7ß,åÃG®?Lûbq|Šw)<»ÖêáäSNѦz-¼ýöÛaÔ¨QÂã´ÓOSõ0zôc–÷œè”®SÇN¦'8Æû«…¿J¤Üw§NúÓ¶ƒ ´·œT–-±)%EZfi„Ìô3‘8ò†&ù¸ggŸu–™útîÜEx/[º<ôéÛW'õéÛ[B8íâ=¾ïxQ¢ÿ{ÖÿZûG‰=á8ºG1ï°a0Ò\_ÌÿÂ>R€Çì­·Üzx æ‰­ˆ‹Â~ä»ÿXáÃ*ÌT<è@“Õ\ÞˆYÔºèÓ·˜‹3`âÁ4ÊÁê;?öAsægЇaÖ¡¯¿Ö'NRÈÇqG­Ï­·Ê*,åüøÇ?òÕÉx«™ü‰“M²Šæ…… ûýÀ“íû½Ùÿà˜£"c ˜AúÃBçù^ã< ¼öZ[¥~lôhËÇA(òHÛÙw?»xFÎ>;µ| "#VÆYÏ÷*<wZV!VgÀy÷þÑí·Ý¦mgçß°¡É”ì‹ù ²vŒ|Ý{tË€í¿•öôµÑ¥K—xl¢e†Æ€"`í„ OÈ”}䵺„}z÷ÍúZ àäÞ<«ØÚ “[n½5`»›ê³.Ž÷DRòᘠÎÖP|ÑJ œÏ1i„0ÖÚA=`/ºÃnsý‘÷ŒØ'¢ì^+Çt=>v\¸vÐ íëµ€~ƒå=fL¼ß}Ãw¿Ë \håýïÓ[ují@s›î.¾8M2Ãb‰mj¯½~>ýôS¹å°œD»âû0ÂRÿ÷Þ;„EŠÜ4‘Gèêè0ÁŒwØÕW_ûw˜ìÒe»,¯¾Ou€þrèaaÄȑ᠃²÷ŸŸ:¦ZÚœKÞ†>ƒ“L4C#Ë\Ĥ͓bŒ‰ Il¡"ˆfX@ǨÃ:´yRŒ1‘!‰-TÑ èuX’6OŠ1&2$±…Š ša£ë@Ò"é?lírÜããäþãØ;´ÁÊwh„‡c<ÿx¦qÑ*µÊÔü¯ý«É@?:{Î\ɃS\¸¸ôÔSOj±XN†šêþA4C#È\Ĥ͓bŒ‰ Il¡"ˆfX@ǨÃ:´yRŒ1‘!‰-TÑ èuX’6OŠ1&2$±…Š š¡G§obbà-^Íu¶Ü.ÐàvÚiǰn½?ÅØ¤aV±0³ÈN!VÏqAœíñ®×ÂÉ',û£&¿<9Œ3F¼ü¡ñ">Àar‰eŽ˜âUÖ%ýmÚœî)ó!ܼyS8ê(5¥A9ï¼óC[É04Š2 $úß©C')ïî»ï.Îäp4ÜÛz-ÜxÓ’¹L6hRnI!¬Ððþû7¥c€Aøè߸1­¸Â1;bÐÀTWP†A¦|t¦p„ž:èRýÁÄ×—‹¿´ŽöÇ{ï֮͟1˜á±S§©y'L™†ðñqã„þ §gÏŸ~Á|ì©ÔZ£ŸrŠZ‹HþØþð áE`&~‘›—³çžß Ÿ~öi&Ç;£ï%Py>ø€ÌèÂqËûï|W&~b‘X4Ɇ•}´ÏóÏ;ßä @ÞóÎëeihŒÙèüjˆ©ø‹/æç³ë ­¤í¬[ÞŸÛN­&&ôàѯÿe6y‚¸´-á(¤ª‹mŠ8Èe}áA9k;K—§¶S¯´C\h¿§ýï8wOaî-÷TÖÕÃüºOGsñ%}ú›–÷åèÖíÍÔn@±ðF™€uë!§ƒ]÷-²M…‚X©±}ã=õÔÓÖ¾´^“½ìþŽ`Å“´7Üp}XÏcaê>ú1Ã[¼ª.•œäiʘ†¼ïÀÖ-¾&+ä]R¯É³ýöÛo%"Lª=vðþ¤Õ Ú_±ÿ%Íx0¡ÇðyÁj[º”æô“&½¤ÉÌÄ0„pé%—èpÍ·Fí9ÂȘ÷UÂz-<òè#S¤Mqƒ!,þr±Ýÿªw0ö¾RÆa‡š}ÂÚ‚–K:Iª¢±õ‰Nª0Y®GØšÔ0hî­_yŸ&T,»Ô#ÞýJÒ„0k]dQ¤Mqƒ0dÖŠPIš–1Å”7ˆÃ ¹LR’æ„eL1%Å "ÀÂ*B%iNXÆSRÜ  £\l!äøÖ¢¸@²Çß‘g¼_¶_[3½?qü£y”)¿“s5>KÊôš×XûF{Ä6§om¿ƒÈÇÄ/åV((‘­ö—úTÓIQW)n†–· (IsÂ2¦˜"+ÞEÆ$ÊMÍÙáaµ ñ¶Ûow™S¾ÊAš3mÃLú¦¤iž›Òo6rÊc3"1‡bÆ›Wóz˜÷ù¼à?n±?èé§ž¶FìMÍ1)ùä+¡‹C¾¼!H…½óNÚã=a|2¯E‰:`Œ¡S +€ÅW¼!Þoóú¼ž ˆYäþä“Oõ!‡Ù©˜þ+O<¨¨>Ä1±Á +Þ¨?ÌòßxC ‘´H»aý:ÓM25o$¯æ¸ÞC5Vñ¢þ’Þ(-…øpÉù’¥Ñ¤«PÿDI t°I‹$¬"C>¬1pùí„ã2dˆÕ‰l”µÅdO8ôýëK8âáª+¯2^ÿ"@| ¤=úðʊ˯ èJxâÖÖ®œ'_œ…ζ9eÊ«‘}²IÂ1wÔÿÈQ£TN4‘D¾ßþÖ?“š{Éó-~JÕ²Z:ñɉvÔ—þzôè'ª|[Y¯×ÂðaÃL;*¥‘Y†Ü·„Ä H€ý¥¢c÷¼Áj`ÙrïœQuG&,óÄ''˜â°Ú‚úÃÜ-Þ¦÷¬í çÏN<Éô‡Hé"Ì0a®º*µÏ8 (dJ+“„±5r„dœ??™šÿöŽÛ­þTúÍvOëáíw0@Q^W]•¼Qû#H°]‡ºùã£FòrYÉGCâSû£ü«­Nõ°ü«´r9gÎl“óè#&%Dˆ•O#àx*ö²š@’:Éßu—Ý„%Ä*kÆ–DK‚a°˜#Ö4ž¨¡¦¥I¾šçfŒ¡cé@à  ±nTǘ¸?ãÆéêÈ–ô–ÆKø3¦!ïuq;¬@øþ{ï½é®d5ÿG¨™º Tsö™Ä 7„½÷þ‘ÜwLªqr “­Åkõ×_‡0)Û;ÞOÇŸp¼Y ùþï'½þ~ýc"2÷‘7•ÇJ©”4ÙU ÆÁ— ñ*ßN7©×L—‰e¯‰ýë†Pú7ç|Öò`1aqÃõ7ØG¾¶âÛ_´_–}"úŠM›x ùk X†—\òkËk•uY(º¢„Ø;]uY,&¤˜täý¿÷^oj®õ7ÿ4õZøø£¬H,¯'漚c¡„å‚Ã;“åc:ûXýe÷ܨ2!>zô%ú’3CK°a£ü„!D „v`‘Rã$t!©”µÅ*(€#¾ýíÏ1ÿ/È_½z•LÊã¾cBL¾£cè{&­x+"ËÓ-æ‰JàÀ»_¿K£jMBïœ4ÄöÊGSéCÃeq`~¯ŠJwqË#€Å*(€#þ¿óþÿ#ë_kþ`³2k0×ÂþXÌb% B)ñéýã>j4˜"ãØ°…fÒ½8,\¸Èn)€M›6?Ðãl¼wÚÙLÓ å_}åV¼Ó{¸öÛé¸>ê¨ðÒË/YÙqî5Kx®G]‹«yùb)Ûb5›^œ›šƒ¯½pKçx“+ìˆfŠ˜Q¸v¬ˆFªO玫™ <›£þø€yñ¥TüÀNP@ƒÉüîþûåwÚé§Yú¤âù°rŽwIïÞã'"ûAõC¬.ÎI0ëŽ{ ~ôjŽZÑÔéj^}ÑÔ|¡˜ŸçµÇ„=k½åúóãä”§7l…û·W­Z©/ïzÍ̦‡ b/WÉ_¡8g=Ö1ðV37/ëÐÃ<ÍžÇR5d¥æ“øp¸,:“—zÌõ)£|¡™Ð_œ‚8£¡þaî‰g?Ìâ–rÖêaÀ=CÒiõzæù’úÿË3qJ¶bèV –œí²a6zØá¨'­âjðúõBfï>L‡ç3œ¼­Y#õ‡ú^Ü·tÿq\ݸqsí“N>Å<ævêÔ!|ôÑÇªÒø¯íZ˽Ý÷Ý¿ìÄDÖÿ…žÔZíOìòñ÷ø°?ö˜£Ãïø]:}!êkÁëòËuÒ{øõ#¬.Ç ¹wv„8^´”ý“z™6c<¦Æöøa‡ _k;…ö·d Vz´®p ˆKu­i˜´)Jx晿Är`{Íd“‹½d¬?'{ÐO\;øZ¡ÇDÕÚ5k?a\änì (R|öY`{o«pÄùèÇá@I¯êçß<¶Ökáì³Ï1YUåsåI«1•až“íkúßý4üiܸ0bøðpò)é¸A|Lñ¦]ò'NŒG;Ökᬊg’Œ‡LeXÀ6òÜaûoÙûºÅ"ÆÑÇ~ÿÀïò¥˜ô¬Î£’Xßj ¦jÈ÷߱ǣ¥sú'%‹mq4‡¨ì‹+Þ  ÅÞƒz‘‹†èCÐöàž?lc¨ïÏ.²|;¼[»(Ìö‡‰áfMÍÙ(].¿˜wkžU=ù9¤x5S&z@-ùÔKÞÿ4{þsíÁßn]C»¡UiÌl\¥QN§Ÿ®>:wé,ïp¥Wù:ð®Ú_Õ£¼^~ùeiH‡w]槞ωg>ã£W.Äb¬\±ƒ¿ò õj¾nÝZ›ÇêVñÈ2¬k¹êÞ*qýõ¯ÏŸ‡l¯ÎñVlÈ0Ó£ÉqDÊK?æçjù܌γx!?Ìö¨ÿóÏ¿@H8Pžä⇹þ IDAT`êIÝÓ”è‚ó/ˆí:Yeð~¤û¬8!‡ë [ñV9Åö÷\|.ÀÇÒ$sñ³«Â[o¾`Jβ½ü’š#zÓy_Þ²Õ«u"ùX‘A Œ!œâ¥z]YK…Ùk¯äܬYý/ºðÂŒ=ê §s˜8ÁÙà,?÷…ã(+ø-à…þ 4ý£ã4¶È›1C­%0ð¦|äÃ!ùR/¬õÎr¾ÿ½ïKžï~§Üv@ÓmòÄýÇe“)8FDü›†è´\õ09öË”{øá:È@›ÅÕØÔUHÐÓ\Îscù•˜˜,•‰Yx¤¬"ÖÃ÷¾¯r6mj„oá#¿(§‚Õ“'Z»÷è‘éFk@&“oï\”¬?CËHKÈ #ùŠwN_Œ1?å24:X‚°Pâ}‡™¨0Z ¹ÊQRR.Bî÷8™„óÂSNJƒÁ_žy¦1b~Iˆ‘|ÅÛH+d³¬qÛdb²Ü®L€¥Šwwè«ð«V­Ä{‡H»Æ¤þÖ^dïëŸåm15Gû·~Æ0?-”@§+ÞŽ(N,ò~aË /æ/ÊÇd"ú7ô?Ur™‚oÝ{„µ2aE®¤j’²(Ÿ9–|¹T,wºG狨ƒöj'~/HLf¡.Ss¥%º*d¾ÿ8‰l´$°„ Z¶{ŲéŠwN·ç÷Õ«4û5b™ÿ¬³Ô·ÐvÛq`Ä ûÖÙן÷!,ÒkŸ\UŠô;¶uƒ’5$e3ý'žy>Ƙ_â1€dMCR¶ä«Š¨Sd 9¡cäÍ7ß°¾ÙÚ³ÔKmH->1)ôÆoYß®šµ?äÁõæ[)ß3æ‰$ZXmÒ±¾ÿŠ'Ú ?ËÏz3´Z’Àr C[Ä€œ¸"FJÊeh¤$°„ÈÐ1 '®ˆ‘’r) ,!2´E ȉ+b¤¤\†FJKp®–_J£ÿ²âg€±‚%WäzÔÑGÙ€¯x¤˜yÀî„Ám’*Ÿí¢ÓïzPçjšgìããlŽ=¼ü'Æ?aòaFÉ /8<(Þqªàj×ÝÀ›yªdýg:«/¼‹oÛ•x TQð“w<3+øHïºoWЩc-p±>0[å¾ßþû…SN>Yö 3¤—Z¼ü¡cÈço|èáš9s¦ðÀ~à7nŠækõðÇÇÔé ÒÏ=Wg“ÕÛ¬dÝÊ?©µÐÂÉÎÇ}ìZD™…R§< ÈÒ ­šƒ'š cðÄŽrÆ8ꮑ¯Z6ѿζë cñ¥ùÇw¬ñÝÑ…kæ‡3 ã‘pªR'ÿý÷Þ³ûÇìMÑQ>b°~Ö®[0µ~ݺ°níÚ°ñõëdï¼ÈÁïhþO>©xÀ ´x›Íµ›(Q×Ó¥lúRs2`Ø^òzæWð# ÖúOåHÒŠõG6kÃ6¼båJ‘ÿÙgŸÆIŒz@»ÆêøÉ'Ÿ¤íû¤“tB®Ž3†µ]ç5H±…‹l—ð{Ï!ëó/>"L¾!Nk‰•«VYYfij¹‡Ãñ>Ú.iÊ«¯j\oŸ„5…á¸ãŽ3¾Øb“4¢™ø bÅHßj¤ïz- õÖ±ý?ûܳÖÿ©CÉT€§ŸŠ~ê:ÇZÔñÌ*œ¥¬vÿ]Ržæž¿§ŸNþ ð¬½29MÈ¢Nz¥ÚòþãHF”íÖSxfy)uʃtŸ&ÇBÕêáø«½“O /Üó—hDYp®†û~‰4i|\ýS®¶ jù0e?åu@ùžc–¶•ò1ÈþŸ÷žáÏO=U&ý¼ ”u„aS—÷Ó„¦B>Þ-ØÏÏú02–CšyÚ‚þéÄ ʼ¶$Ÿtա榩y×}ººå9²þz8,zô¢|öohUýé}xÅ¿±gï+x}/Ôß›Bïýãô^gàEù·Bÿ^^ÖÜ䃓þûDÞ'´ Xg‘†/í×ß¾¿¾¦?‹6ä“߯yD©ãh¼ õ’ø—íñ’¬ÎXÿŽ?^t‰Éö¦p8çl8°L+ÞH¼ùæ´Í ß8qa-Þ£ëÖéû4ˆÃweùr!m—]w‘ö2lÛ•ë¿xÿ‹¼šÉç=i¶ï¿È'Å[ò© ÑDítÔ¿¾3¹Rßÿlßò=€q‘ˆ÷<°göþ“¾ÖáÙ÷úðÀžz ¶!}ûvº}-Ý3” Žh‡|Nö³N[¯m©¿çKHZϿϓà–|êÂë¿0ðNJB ô{¨z\°"ÄFtèa‡³Å –«Ê¶â›3 P 9ðFÃJlj©©L×®û˜| ³„ã1ðŽ+”8ï˜ï€Cñõ0›û*C:)¯®iÅ;•‡Üí À›šŸ˜Žïa¤BŸ"äøQ1žVðkA&b1Žõ´úÎVÓú;î¸3ê >Ÿ§ƒ Ï^Ùh§#‰põ^ÍaŠX¾²¿rtÅ[ Ò+Z1øs¼·¥þâq:¯¾ZM¤«ä[š©ÌAùäëÀ».ë¯ût¹j‰:pBh(LÍ¡Ãz¡¹›0]ñfÇ*fn&HÛnM&ýb,ZF¤A ±£Å^2\º‚¢úOQAɼŽS×èèш1ˆbö[ÃŽÙ¬XN¾9=«ûo’óì³Ñcf½^^ò…3x 3Ic.ì)B9 ì‘ÇÅ|¤Ã¼Ø×Ï_VŸ—ÕüyÆÇâTÍÔ‚'=‘ƒçšøqwçl×µðùçóX$ Q´k5¬´kщ8GÒÇ,CÌ%ûücûûë_Ÿ•TôË÷*wŠÃŒ÷1i‚lSˆÏâ…å?õÔS&_2Äó;aÏ:ßvÛ­zOëµ û«ICZ¡@×yÛQýû¶ÃûÿÞ±\ròÑY÷ÙâY»à‚ó…69;+À¢Ž‰•áH•SW_uu¸à TN7:U#ã”kÔÈ‘6qIEõl]¦S¥"=åEŒ&Ÿ²Ç;¢$ÈÉÈ0†m"m¢ƒ“3è·èÄ-QäÏ¡˜Ž'@Ÿ+„‘#GéG[]W`“O+>^è…^(å¿øâ_lÉÀþUøðÃ<½Â‰ûÑjï_%ŠTÿ뮿^d¡ÅG¢w¤©ç{û\ž[cKÛô¨‘ê¿¢¨;Ï©­ú眣sµz-ìË=Þ9Ä8¿ é„$‘~SPÆW&O–þÏ8¶Áàø¶ªëôÓϰ:}½æëŒdä(< :™Žƒ¯¼¯ k€äõ±­©ÿ|çL3ß'|/ÖjêLÓ·ÓFè‡ùN|Î6 -¹(Ÿ‘‹³w1W±)þåâE¦{ïáÉ"©Æ—ÿ&MbàæåÃ)ãq;x½ò &JUÇW]÷i[W’•T¡iì2K›H.ËZ–Ð&Ò‘;: :óõOåuì ô¹,±pt ô>öŸ,Ï3¶º|ùåb þ2,–´/÷¿½»´+lB¶ÏÀ×ñp’+¿/—Ý¿½»'šÎ­õ‰çQÇ'Öˆ{íÕcv•\¿ÇøW½a1ЈoMÔ£øM#,dÅ;ÊùcæÕ¼—|,âžê=,׺„ÞŠV¹y/ø"Ǭ³)Øep*©B—(1Þ~Å›²8ðÆGÓÈ@ä8aÞÔüÞ!úÒwè€3k9PÃJ)Žv€39Ô¦éüè€Ó:î­â@uF9~ø6Ë~×ÝwÛG  Ì™Xåey±Gmõêä=åpÍ€ðàCJU@Ÿö&V¼Ÿ{6mÁàÀ{øˆ&ÿC 8G^×á1ÿ0§"(õèëSP™úc%þî{ôŒmÐã#’õOԞƟyæÏaù²e‚Ãs×\¥/è{Qq¡>{ÿ>$jâ­Ÿ÷ˇâ`.>ûÚ®ƒ¬bCÇ{ÿhoÙ ¥Þk5×êU«Ã±qÕe‰;äÀôrJmGVŠgîñN+ÞàˆPäãÛ5à/]xƒgÿÃã•–-]jGÚiÛyJ,KÖ¯]§¦ëñyCÛ¡Ã6ê2†»Õ7êÁ&Sdû€n"¡Ï.çÊ~r–=(¡pù$Èìü†n=Àú€Ï¢ß¢Â|¨|ðùÿÎ{ô¿Ó§O/õ¿›jQù0až;w^ 犵xÀ«;psæ?W>z(«*Ôº¤ ”¢ÊÎÜ0òæ©è§exù7p€UäX%5¥ºÑØ:\Øz ïÇ÷¦ë{ȽàŒÒ/12¨mùœx#|D©%^!€I¶BÜt ª”¯½¦:ñ ÿú=Ÿ>@¸…§Ÿ¬\¡–,ìðq¤"<ßÏœñô°ÞÁd&Ÿ'x·§…–U·¨|–·„.$«qà-ù ™ijŽºà=‡c„detýúp÷Ý÷X¸—¸¸z¯¢{FöL£ï›:uªs…:¡½î+ý[,s£àˆÏœ›J;˜>]¾‹ø=2uú49•¢P•¼ª‘’úùçŸK=p:ÆŸŸy&¬]KŸ !Àºçÿ-qêÚ'"“WÉ_8a[«‹ÕOqXóõ×N:1ñô¼å7ù&jRTÊKSsè“ßtø`¦“ ½m¿ý·äÛý œÑžú‹t:‡xƒñþÑWÊ7èÚke[åËûtÀÙÊçë/}‹ô=ÚÿÈ©>pJ‹þ@ú‰9Òg¬·þ@ªÐôOë’jDùÅ ‰‚:+R0^äÈôê°HÝ’_hìQm[«ñj^«Éó_­ñ<|‘m~b¼þŸ{>}Ÿõ½øâ€c[q~2N9åíë5w,¡–TÿsYU±"µÄ+2û$—y*¶mš”«H-ñŠÌ>ÉÉ!ŶMCZ>K‰Z 5BŸäa#0@±mÓqì˵ ¼SR"æo |tŽ'"1V£è˜Gòð‚Y2:O˜3/¢ø¡ GK¸°r¢v=­x“Yh„ŸŸú {)Ã3j1>:W/||ùkâÄ ™|šfƒFL¦£|+Ÿs&Åe:¾ÞšãÀÛ±^¶oÃÕ³èáЗ)ÁÔ4CÅüI¼´ë¾îñfŽF˜@Ç+Q>ÞóæÎ þ8Ÿ3¿oîMæÖøCx¬ös`0É®x'÷t õ‡îôž’›JÀ $éº&磮/E—çÈK¦8Ga`Cx7W-W®ŒæÂ2xú@åæÂÌÌPeá0–Q÷x³ 4jkÈ ÐUÝ|¬Íž­ûó‘« Ò®]‘öUh˜Øñ¥Yµr¥#ùkõ€=§·‹ÇÔàŒÙŽÄü<“Srz¦u³äØÆ'GG\ðÚ-þd©íŽõçý‡ìo¼Á: ù0m£ý³¬ðÖÎëöÛo³gFñéù§<˜Ò´<”-vÕÓP#„ïï©û¦»tÑÕ xЦ| ëø8ÛAêrú‡Éº^p̧ÎÕ.»ìr‘Á¶ùœlPk‰|à â?<ô{þX¢||¬ó‚¿FØÿ¥|u´ú~ʼ3Ss0l„ÀÉðªN~5‚…h{ñ¾Ê@¢Sá(§ÏêoȲZÿŠl8˜39r2ä¤|–K.ÒÖÊ÷?å×òN›ªÇä!o¦Ó6úŸ‡ÿð0EµY¾‚S¨Ù¼¶?Msï±ZM&›bƒlCN®[ VàYuÿ‹ò1pÓ«mý“w6‚:×L÷§v‡qèGºwïN;í²òŽ.®b‰ÓÀ{Ÿˆs\¹rEØyçäþc¬w¦÷¼¬^«|8ã…þÇ×}¦¿ÿˆ¿–åÞ¾úSÃ#âùÏißÕ'ÖŸÏ\[÷ïxÖ N‘nwåwmÅõ9˜°ýàƒ´õ⛤Œ÷‡Ïi ½ü©Sßmªÿò]cmµn>úG}~\Y0`MçsÇûsò)¥ûN˜üÑË sXx¤×÷,yüÅ6|1·%z¤ÁÔ;CE ÆÉoÔ?9WËï¿ZÕT¿)žõýµrÞ§éüo´?yŸvîbý & ±…‹—òâäjªñûï‘G|¿ÃÜyݘª!q óÔœ±¼þ‚·¬²5KW~JœÓä1Ï®%_uã4Ôá»{|WÚŽx(÷êØÑ:\æÕÜ=qÿ`ûûA´Œ§HÏto ¬l«å4ï‹y¶Í©Õþþ1í¿–nDl!v?23Èèºë®‹))i˜gG3 ¡½Hd3:1íÈ&ˆÙvnÞLçj¾\þ(˜âò{£tÅ› ÒUâ=¿§Ž8 ^ÍÙÞzõ‚ÙeìHå…ÄÎ5'LL{¹ñ!èëÓ ¾D<FýPMKh©ìA€.È—õ1šFÃŽ{ >ÐGŒaôo½…sBSý)áµÑË0òáêßÿr©ÿA(qSLŒmÚ¸YùÖó#AŠ÷”e-†&ÄãÖ\ýáU÷ƒq¶¯Þû[nö/lGˆ2XÔ€X²r ýp«…«¯¾J²~ýurð5kÖ,ÉdæÂõº™§%n*gÉ’èÕÜ^úÕòçΛgÞU}ÝÏ<ó 12¾0}Ž{¢ÑþÑ®áu_Ûž¶?¬ìΚõ¡e ŠÝ¸iƒì–™ö8`¡¼cŽ9&¼ù曦*¬0wß}÷+?Wüç¸rQ¯‡×^{ÃäÁÒO29S±û·ÿ'OgäVÈÂsUÊEø£½QŸè½×ÉŸ6õÝpܱljY_|ÄÀ (ï$Í/ëµ í:É÷РAéPä5‘Ü»Sÿ–²7Ú’?mê4k?ÍÞ¿xþàD-»"Ó£ŽÖJ[NÁο@· ¼8«:+O#ˆ•pÿÈÃz¦3Úß+“_ÕãÇ õ?昣Ã1F‰2nYÉ)ÊíŸtàuÔ‘ðISûìS<—>£þ×\£Ç*ê .ÿî0yQ7ú,¤šxˆò5­ºþ_.Y¸&NÚ²íéû“È7Ýt£85¾ÆT¬¸qÆÊŃt5¢ÅK]8PlùùUö’&-“ ÀžX¾÷Q&À—_~YxR|NÔeû õ­yaã†â?hÇw.õ¿ú>ïÅXî6ï{W>hýAµþ)ŸåùGô¿”QTyÒÛÖé_艉MÊ)þ»êÏo¦Ë/¿MkMÅdÆ5ôÿÆé®ÿM7†¿½ûnX`b™R ò”t×”h#”g1t1™q ý¿eÙæúc¥hÚ»SålyìcöåéÀ[?r°·é_|>Oöq/]†³ýÅ\š–jÝ- S¦¼&Gö ƒ¶+ÏÒ4™dúË"«ç0w„?8Uôò™_ê_rVy#˜3bÅÕŸ:@zrgáÂóeó‡³>Š+°E*WÉ'¥…dl ÌÄa>Z1A=ð 3ÝÕLå¬yʶÈ_²d©è §À©UΩÜÿá¾;uZl;œÈsm‹üBõ·(ô¹´rJK>5”4QÔs……„9Iϸ†þŸI¥iIj‚<µÀyCãX+ZWà4ŒgÍ ¯My5ÀÀóÏ?FÝ7JŽìä@Š”†Ì·]>s’ã¦lÁ Û 6lH«È—¤&ˆü,$cKP ˜Ìx’\Ô6òÑ¡¡„GŒ!Ì`Õ½-+õ×¥[&ô¨¾:åU s»ËÖÊ×Òëªu‚<^`2. ŠÉŒÃqÛÔ©‹}₇<©ªå¯^ýµôq¯½þš˜»Š¥DžÅJQLf\CÿoYJÊLµN§æû}¨˜–²æ)Ij‚m„ò,†.&3®¡ÿ·,¥%© òÔ“qQLfÜKfZÊš§$© J´ʳº˜Ì¸†þß²üÛÔ§ø¦zãÍ7ÂÛo¿–.]RÑVþsëÏ;’îz‚ˆ³7Ö(&3îï<ÓRÖ<%IMP¢PžÅÐÅdÆ5Ôxs.Ajk’.›ÃI’˜pÒL[‘ESvǸ¶äCi¢§›–þÑÆ¢B2½Dm•Ò²¦'‘”Ý—@M‡kKÿ¶‚R¯>NŒ%ùGÉײQJópkås³¶É«ï¿¯þ¿éúCƒÛrÿÿ/Èïß¿8à€n¡[·îá€nÈ1lpÂÖí€B÷nH? Э[èÞí€0r„Nå_ÿ^÷õA]Pnù¡nÝG]ò¬†äžÆçy[Þ8o]ù ºXô¨ºôòGŒÙüáC›jC~ÿþýLÿ{Ñí€îâE¼(Äp­Ïõ×ß «ÝGudl°eñ·Ür‹­ˆ·%_ô#ÙµÿŠö߯i[ª·º‰%Xªí¶}`o%&#`Šœœ«Q…ÿºúCòeýûI{këþ£þÐшloÛVÿmiÿ¹ŽU7y¿[ND%ú2ôïÞþÜÿëÿå.Ùís7«:-8œ¾ãbB–ÞºÿÿÞÿ­ûßöû7¶b~ù}cÏ¿[ñ†wU=D†Ž]t‰®û6¼ñ-æâƳ”H|BK¾hëÄÔ×Ò¿©¥ üSÛŸ9ë±½×|~­`v×J@‰Ä'ükÚ¿yO¯áˆ«aQ—Å’·ÚŸÝ©2ðOméCØ R’/Ç.FÓ63I6sYµÖàÊèo®¸"ÞìMûK-­¹üƒ:POΔå§ù뉣ìš^NeJãáÀƒÒ-T°áÏm«¢ ÈÆ‘Rv ÏË09IìþTÔGdPvrPŸF¸ó®»¤l;t”£7oN+‚8NiÌèÇÌT×;,Í P*¢Oh®È7Ê2Pj™L±¼Atm÷Ðé¡XÐ\yÅoÚ-ßO,:Ì—†wLûçÖB±5¬¨ƒªú#ÍžSÓ¡íÒ¿VÚñà=öI¦­oþþ·äC^Ùÿüö×’ßÒ¿=â­ç_ŸFß$L9[×ÿ¹·rñ¼ 6€Ü]‚€Ç¿Ãd1¦3$§*Æã 6€Ô.A@ãßa²Ó’S ãñ@j—  Æñï0YŒé É)…Šñxƒ µKPãøw˜,Æt†ä”BÅx¼ÁÚ%¨qü;Lc:CrJ¡b<Þ`HíÔ8þ&‹1!9¥P1o°¤v jÿx< >††Æs¯=žÜ4L<˜n´”0Q`Ê›“¦!†ä”BÅx¼ÁøbÁ|ó×`+Þ‚SüGÒÈ6Å1Lr )Æã 6 §•˜à”ÿ9iŠbHN)TŒÇl©]‚€Ç¿Ãd1¦3$§*Æã 6€Ô.A@ãßa²Ó’¶ À£ýØ1£Ãè±cÂhÀ£ÇHâcÇŒ cF–ø´éÓc6ÇE@ãßa²ÓR~ ãñ@j—  Æñ³à1c´¼R'ÖmL¬S ÇŒ+ÞÔÉ‘%wœS]|¢dÐcÿÆ‚—èJåAwøåOŸ>Íx•XYãñ€_Åý‰ú×û™¬_.'l Ì£áÕ\fµZ€Wûï|wì¤X­Àw2¡®–«k¥TÀ•RÀ”×a¾±û£»ÐF·Tè:HW* !†‰†P# ÃàzCÿf´$ZBJ¤øÏISŒCËo€b<Þ`ì‹å•­ª?t€-Cz+WJ1Á­Çló¸5އÉbLgHN)TŒÇl©]‚€Ç¿Ãd1¦3$§*Æã 6€Ô.A@ãßa²Ó’S ãñ@j—  Æñï0YŒé É)…Šñxƒ µKPãøw˜,Æt†ä”BÅx¼ÁÚ%¨qü;Lc:CrJ¡b<Þ`HíÔ8þ&‹1!9¥P1o°¤v jÿ“ŘΜR¨7ØR»5އÉbLgHN)TŒÇl©]‚€Ç¿Ãd1¦3$§*Æã 6€Ô.A@ãßa²Óª©y?I¢ gÁœÌð˜^gb ó XêdÐ’Ÿn(UH½3,¦WÆ™ÃDŸїå#N@ëùs+ÞP‹($éÇ›Z¹Ô:-+è"?!–düåN! Ô-ù-ýçÝa“Y@k€®)èZíOž§ÖóÇÚF«ÿ‘'$>&…§¥Õÿ¶úßVÿk/t¥'ÄcSÿÊ.&×^ÂÛC×êL¦3§ÒÖó—· Vûs£t§‚.äŒJˆ¿Öógªpê1Ͷž?k2¢“àó§+Þl vR›Õ$Gà@GÞ>¼:.y’‹9Б·$/†ŽKžäbtäíÉ‹¡ã’'¹˜yû@òbè¸äI.æ@GÞ>¼:.y’‹9Б·$/†ŽKžäbtäíÉ‹¡ã’'¹˜yû@òbè¸äI.æ@GÞ>¼:.y’‹9Б·$/†ŽKžäbtäíÉ‹¡ã’'¹˜yû@òbè¸äI.æ@GÞ>¼:.y’‹9Б·$/†ŽKžäbtäíÉ‹¡ã’'¹˜yû@òbè¸äI.æ@GÞ>¼:.y’‹9Б·$/†ŽKžäbtäíÉ‹¡ã’'¹˜yû@òbè¸äI.æ@GÞ>¼:.y’‹9Б·$/†ŽKžäbtäíÉ‹¡ã’'¹˜yû@òbè¸äI.æ@GÞ>¼:.y’‹9Б·$/†ŽKžäbtäíÉ‹¡ã’'¹˜yû@òbè¸äI.æ@GÞ>¼:.y’‹9Б·$/†ŽKžäbtäÛ V›šçã~eÚL ¤;¤·”RØËí)#®"I0’îÐ’_¡Â¤¢î¯ÀV$µôOU9åh€i3¥TjØÝO“+’Zúoé_—kÐjPÒŽ{ì ¬ÀV$µž¿Öó×zþð¸‡Ã@ ›R²öÔ%ÀSÆÔŠ$ÁHºCh@K~… “v’ÖT­HjéŸMß)Ç@Zí¯ÕþÒ£uQCèWÔSs‰l[.†t uò&]K~Kÿl ©]¥””æ[a†ž>‡=…‡IÕj­öǶÚZJIi¾õfèésØSx˜T­ö×jl ©­¥””æ[a†ž>‡=…‡IÕj­öǶÚZJIi¾õfèésØSx˜T­ö×jl ©­¥””æ[a†ž>‡=…‡IÕj­öǶÚZJIi¾õfèésØS$Ø­xÇD ‰µ$rþ[³ÜÅýÊ Q¥!¾%°•s2ËÝ’OGEÍH`ZjéŸZ2•7¬­ŒYîVû£f[íO4[†ÖJZÏ[‰©Ä€­|âr2ËÝzþ¨ÙÖó×zþ ýŒ=%…t(Êãògkkb–»õü45#i)éÛ’ Øu—h,wKÿ-ýg­#¶ ¬•´Ú[‰©Ä€L{[an7ðγ’ÀR jy–Äå (ath`Œ KÌ"%Š–üLk-ý§æ¢mŵ‚n]û{饗Â{ìvÞi§ð “2]‹¤VûËtÒjßlûKÜb¶ôVûkµ?k ¹ß/m+®Å”pëú?ÇZ@²xöÙgC¯^½ÂþûîöÝÿЧoŸðàƒVXÉ1‡{½ò­\ßPû—úœwnØ¿ýÂ~ûíúôÑú˜œ¸ÚhÊ7$ߘâÿòñþ®¼ƒwÏ¿ðB®‘­Ðÿ„ B¿~ýµ¯ujþûÚ¿b+ä{Zÿ Û¿SŒ· øol^­ú{mhŸîuÒúþKúQ½8í”°ºÿ±w Ýh„Í›6…‰O>С}8sf’$÷\áé§ž¼ðüóá£?ëÖ­#k -^&Nœ |>þøcKW æÏ_`ò¿þúk&‡÷ÞßäûògY£üµk´lȸzõj‘ƒ:Οÿ…ð*éK%ÄÿLó SJfB¨ø£îòqþeB˜?¾éõ©’ÿî»ïFšñá³Ïf»/ T¬™~hõŸ=û3+ùM6ÕôÿÅçŸþý÷Þ3ù('ôEýK¹Qv¹§kÜT?aÄ(C÷)Mù&Ð?Ù–˜€}BµKþO{þ4Ôj5ùõ<°gIÿ{‘`•(¢Ú%¿TŸbý—/[þøØõƒ…Þ½{‡!C‡„f̨¨WJ*•7¡Ú¬ÿšµkì? 7lptèI útõG»FöõÙgÚfKô[)_ÈL”mÊÏXÇå/[¶,<÷ÜsáÖ[o }ûö wÞyg˜7o®Qey)Ž¡«3ù½«¿¥9 DïpibÓ)–Q†ÿBùè_gÍú0¬_¿^ÊVªÏxýQé5k¾³fÍü;Ö¿¿~¡V×>‘}#ÃRyÛÙþðn1cFXþÕòŒC6²Ï1küþ—nC}ÚÙþÖÄv½qÃÆ¬‹…Öšž?äùÐ= JkDš§ðßžúÛãn¬ ¨ìÿðý·lù²°jÕª‚ôŠþ:£ _†É(C+òûiÏžú®×ÂOúÓŒc1RUÿ³Ï>[òwî¼]A`!ÚD~&£÷_yPÖ|ÑÕÁ°pÿ[úO˜3gNXòå—)¡UµÿDB3ŒF¶ôo*+éóüüלî]!\±r…tdõZ- 8°Dzà ×kGY«…K.½ÔòŸÛë\Íg/íºÑñÅðó/¾oÄ7†z­‚&æ‡| 6m¶žxâ‰Pø®]»–Ê6þ‰ñ†Ÿ={ŽáÏ=WËæË¸(ÿ ” ¯ÎF#üííwâH]>þ­¢H%‹‰…„BTˆ$-"P^–iŸ®]3äãÂ@·u4[ÕF"¨¾½ûîÊ£^ ¿<ó—A#3”sιç¦oôH½Çß±r<6ú1©?PzOõá~ˆÎìþÆx­¾ø\õf |)ßJÒ*>‰õG>ŸîùTâ Ä…¨d—´ˆ8ñ„­Þ}/¾ØØÿ³ä›ÀB=ÿÑòï¿ïþбcÇRûÇýíºo×ðÑGYѪtHd çÖ®]_w]ØsÏÁZƒ®ðáé/—UÚŸ´kikõðË_æíšù|I+$¢‰¤á“ÚÒÿ¨Q#³úð¹À`åì³Î¶‰Eó<³2W |R[òɧ’¿gòo"Æ áºë® {ï½w¦·½öÚ+¼ð|ZáòEgý.\xà!ÉÛ¥sgY©üGÔáÂá9»t–‰(ÊñåbîÀh3üp¯†çŸ^Ø‘Ö_` «}A ÑDR@Òk¯½fƒîÝvÝ5<ôÐCáɧž7ÜpC8áøJ%(±)$¢áé§ÿ¾³ÇÖðþÅ ô3Ï<“=+ßTý§ >ñ½µ«Õç©pÃõ7„NÈëã˺5ò×®]+ýÛ÷÷ÜSeÄ÷âö ƒ¡[›Õ ÊÛ°a£äÙûGú,ðûc¯½~ž‹ít[#_èŠwÄW¢Ïÿëo¼.õéÔ±£p/°Ì$–p…„B4ñsÜÞ£‹ã;x[êoï.K®cå–´ „OZ°`AxèÃÞ?Þ;l×¹K©ß!3Ÿ'UŽØRqI)cN»-õOÒÊ7¼BŒ¶É „OjÉOÚ ÔïÒ~òÍÔ©SÇpõÕW§{oÀË“_·ëºté:wƯKØNÂíb¼s¸ûî{JúG?rÑE ­<õZØqÇÃm·ÜZ’‘ÝëòíÎQži¬”cè“Z÷?i#ANY,á …háïÂëaÅŠ¯ìE2pà€LÚĉ­“<äCÏz5‚ÜîºË®a·Ýv »â·ën/ñ]vÙEâ‹-’,7m²—<j÷nÝå%¼ë®»˜|4ÄÃ;ÌÊ€7;i„Xu¢|„OŒOÙ9s°ú«W/¼ëµ€0ʃPË·KLÛ-hÙT]ïüí“…•|»ŠÚ„&ÚMÅlÊãS}ðÖú¤Ìã#¸Ù³c}:Ìœ93@ê5é<6Ù} á“O>–zò#à±ÇþhU=÷Üs g:Û%êúÃ}Þu·°pÑÂ\ ®ŒÆ¬¢þŠ+û¸‡#§Š$VXQû0 B>föo¹åæ0räȰrÅÊ Â*’Ú+ÿ_Qÿ§žzÊÚ5Úž·ž={†N;Y:žÇ›*VoPà­¨ÿŒ÷gÈó¯+gõà§|~»uë¾^«”"Æ´kÍ«“;(Û†¸JZ"%*û/²”rW%VUÈÓ9¸Ñ'Ÿ|²éõ@û?ð§†;X:&&+ËXÑþ¤è%b'³„«P×?«þZØêºÅrjÉÓó7o޼е뾕÷_ÚA½î¿ÿ>rŽa#,^¼8l¿Ãö¦SôMhC'œpü6ÉÏWèñ—‹ÃöÛKä°ÿC¹Ò Ìß +^ÆV#Vs½A.Û¼yŸ‡}»vÕº&-ÙÆï¿ÿ~ÇÓeþ'ßÿ+¯¼ÒtþÚë¯U76_<+uóú“äÎ;î°÷=tŒ>†õGüž»ï‰¤^€‡+ÐdÞ¤ý]yê£mgÊ”)FE>îá$«õø~@¹}»Aœ¿îÝÙ¿i¾¹sçʤ&ñºö>Ú¼L'ùyùS:û¯ÈÖÜš '†EËF¾™¼œ<+T{ä/[¶4ÜrË-aÔÈQaÅŠVe\!¬tÎYú‚öÔù¾ŽÒïÄþÀ·MLÐÛUïËù÷Ê÷¼Êp,AK~T„Ló¥þר*úÅêã ?ñ§'Ò;¬V —ºGòxüñÇíÙÇó# „qá€ß?7Ýt“9÷Ûoßê~ i¿<3›„“̾xÆMí¿Šæï¨¿‰PÅ;=yÿýÿ§ÈSs?«¡ÚT®X±RY½Ðo`>˜ù ÚÐ!í¼ÓŽæ–zi>®xs¦TîEÓ›Ùß ±1×ÕW^‘ÝQ¬ ö<fÀºÚ:ù•WD” ¼ëxÙé ³å¾%Œ?ÞxÊ q”²!OÇŽì6«¿4Bxçoo›üñΤ^k«ÿ¾þƘHÚ¨?ä—‰„¸ê_¯…ýöG}X¥F?>¾kµ0{Ím@V"P/¬þóEñ—gžI<¢ü~ýû™^Ð)¼ôÒ‹ö@ßyç]ö¢ÇUŠÉ”^‚Àž×¶Öÿƒ>3>(›0oIÿ¼¯¢F §j²§ßß‹áôŸ¨ R„¥ò-`².Fó+]â!ÉØ'MÃã„8±”â ˜¿[~î=¬­¼ùƾ°²Ý`ûíwù3ÚPûå¯Zµ:ìñ=BÇÂðáÃÃó¿˜ì™2åµpÌÑǘ|´½òZbE mQÚu|ÖŸù‹¶ëT*ªKS³PX6ÁµCÿcÆŽ¶çd·]w h×z5¤n IÂl‚࿼ý>\tÖ±SÇpÿ}÷…E‹K¿óÖ[oʬ>ûäó-K¾\:wÙNúkô=`@üMÞÿ¥K–ÈÊDÇŽBÇNb»¬‡eu7Iò­(¥²¥y,ÓâãÓh„#F_¼ï»ïþ°háB1ñ}óÍ7Å·Þq¿qt/a™$y )5#f$… ÜÆöw‰X}¬‡.Ûu››„—°I\Û¥|;è;»0a ôÿÐÅGi÷wÖG³T’T2ÕT!‡Š¥ð)®ˆ–|ÂñÇ oX146oÎHþž÷ÏêU+Å7¬†F `)‡‰J î=æûnxâO2™#Ñp¯;t ÷Ý_€5îÏ[o½vÚiGi#xN6nÜdï_dþ&ôo…p@³úÿþ÷轪ëŽÄ¶|)¢Üp½ë[’ÎÙgÙ=vÕ‹ŠSYzŒTÕÉRí:uè¨ýAüþ<ÞYG¤R‘kRð&¸v¼riž¯‡IņÓ×’oß¿ª1èÉéJ@cÛ«¼ƒ8IV¯…~—^RPtgZܵZxþùÄš Ûf_xá…ð¤$„./´¿›oºÉúŠadžõÖ|;ýê‹ì]ô—¿þÕ—,®¬–Ü[÷_Ô²ï?Óž»ÿËÚ.§X^’ÑÓ…¸â-iŠ0°dÖ‘:tX±|ùrY%–p½fÌÐNäS!œ{N¤uê@ÑYèiÀ~(¯¸òªØQ¦,ï½ÿžÍ&:TOÀ4Ûf‘tÀ:éÅ5S#1ÝŽÈl·âÍÕx¬LÙe•Žu(ðíwÞ1ùØgZueY²H™º„n`…þOúÑáLî_d}BÜÀ{ŽÛ›Mî4Û»ûî»Ã!*¼Îéu.ÑÂùˆÜS™¡¯…‹/þµUZ7qð_ÃÀû1Ë×+¼ñQueuÊ"Jý³ŸýÌäöëßßÚŠ`AS  JRžv×]w‡ë_-^† Ž8âˆÐ¿ßeañ¢ÅaþÂùáÚk¯ سÁ÷“~½fM¸îúëÂàÁƒÃàÁ×…Áƒ‡Á× ’ø Á×…7^ƒÅÉ…ÅØK/N W\qe8ìðÃÂÏ~vR¸û®»Ãû3f¤:Åúc÷ú믃¯,²a¶}ã7†Ã8"Âñ2»ÿå—KLÿ`ïë}>Xøù©??éñ“púi§…±cÇÆýÑ©hk¾F}®ƒ–I¬¯W¯ }<œá²‚}ѯ. oØÀZe@?€:ð JÎäç$!¬ùZësÝàAaýú á£QŸ›Â‘GðAró-·„/±/)ÖŸ%œ¿`¾|2NôН`Q£ííâ_÷´¯?àðÏ9VÁ=äiC°Z)^ä‰{üð h¯zˆ´‡+®¼BÚ2MÚI{÷]wÉýG[>b˜Ðöë¯mgÁüÚvzö × 6¬×Áü+ì +!õ€ã%K–Z¹¥ìˆQ@±!<Ó¿¹âŠpèa‡†“~ö³€ö «€xçMÿ¸‡ÒvpO7n¸Ç˜I¿à‚ Â=.ºèWá79Y¦͛Ã7Ý$mø™?ÿ…ìJ%´“!±µÂ«d“£¥ü›7o7ßtc<èºÀ eUniƒ®Ø_ƒN¹°§9£ ! 2Ôúÿ×øì™2ó"`uíö¿òT!¿YùKeeBÌ bA,ÐO¦ï¼ÅXV§,’SBF¦ÂÐaCbÿX¯¿özÓûgy @&2F°o нÎëzôè!Vèqÿ ÖþýŸV½!ÏÈÝ÷Ün¹ù«?VA>VûËÁavô³K0äîoï¾›TåOzi’æ¹npøôÓO¥ÄwÝ'wkµ°t©>;”ýÞ0ç…ÞOýù©…¶­ªÿGÿ1ÜsÏ=á–[o±UiÖÇ×ÿ³O1‘Ýx¶¯»þy¬_·A¶Ù ¿>âÈ#V:ÑKÿV( ú l›C䊅ùÊY Òtx xððѬòä¡Ãì=ùúë¯G†[ªêÏ\øˆøá‡ÅI&;ðþùÍ¿‰}âZ»ÿ ŸôâKò<Þvëm2yÈï?ÿÑÿãùÿÌ}‡@þm·ß.÷{Á|L$„ðùüÏÅÔþ˜c‘IXQ¼óÎ;R¼ƒµ+¿ñ>–w0+P1qûà…³Î>+ôìy`è߯xó­·Â¯z÷ÝuÞ®K¬Sž‘ïÓS~þóнG÷ø>}<öí¨S…F¬ùÚêÚÒ¿1‰@F› m.¿È£Ïxf‘"¥Ê‰]Ñ3£l €Ü²d‘r–Z2jn5ZÎÜ$ä–%‹”3”Ð’Qs¨Q˼níº°gÜJò“ÝC·nÝåué¥ý„äÌrÛ­·K[Ù® } ›-À“Ëh_½ûôÍn¾Á°åV0„ï´êËË—‚°0ä-ðB« ÔhEîê$[–,R¦/¡%£æ6P£åÌMR@nY²H9C -5·-gn’rË’EÊ€®±áSŠ™šc÷€aÓ¦MáÐÃìµss¥Žyb`ƒÛNq&‡‰o*¢ni‡™z­®àW^…o^ÈÓè0åP«…»îQ34ššwèÐ1œzêÏßó§=™Qœ³q0onž—²ÁÌZÊÆTËæÕᆼ,(üDgjnÔyý=7·PÐÐ4«-§žzªÖ§'Œ(oÛ^¯‡945Ø>šeúÁêß(o‡Ne ¬© ¼ëµ°ç÷÷”ûø­í·—¿ JÈß=îWSs•î9º7¾C§d)€r+¶íúôá,é¤t §fîkש¶˜Ó©Ì§Îy{,2áþ£®~EñÓO?=ìõýL/h —_v™È™;wN^1½õZ¸÷Þ{cyrù+W® §q†µ¶ †cFqõá‹ùóM~ï>½S>LEk èúôé.ŸÖ÷¼-¬×ö.Y²Dò€ÿˆ§wv‰sfVêWe*Heüp¯H>L¦Àé Ç%XiqààÏ)î#¼S¦·/ÕGo!Û_â&˜÷·wÜQ’Ÿµë±]×ë+†°’Á¥¥Óÿ… ÚvUý±7æêÍÂÕJ$Ýi;Q7,ßeýµíY8ÃÄ=BS<×ÿ$¾€T>œc²ÿÏÍÍ!Ô !ì²Ûn¢ˆ›Ó¨ ÷¿ùŽÒäq›“˜´kQ…,J²,ß”ü?ùDŸ1¹§¹9¸çuÛZùp^ˆ‰A¶ö7Uí•A»å‰&Y¼^“¢Çi œ·Û®³Ðã¹_ñ̃õ‚©.Þ?ÈÛuŸ®aC\Å?óÌ3EÞ?¼ÿ̃pðàA’+ÿZ÷öÕ¼vÜy§Tÿ¼¹þåÃJ.íß´þ}úè .«{­¾µÃá=×_kéò2 ³Âg³g›üÛï¸ÉéžV<ò‘åÑg!çcÆ«-ù Âjún»ìjï©«?öžjŸ¨,O;ý&¿XwÍ›¶ Ao^>é_~ùåðÊ+¯äm)êŸ&ôsç͵~”|5Týë;Ücc0áÂÀþ‡ò‚0&W²«Â]÷Ü•ä¹úƒª%œ¢! ¿¯Vr¤±¶VÈ6âQ[òsz– ©€c<,‹&7A’êСä“ÿïɯQ±RôX~ÖjåŠÖ °ÇûòË/“›pÞy瑬ràúÅ‹……‹Éžiì›^¼pQX¸p±ËÓýálXW]y¥Ã)ˆ•4|ìc¯:t\xãC3ÃÔ™~ðD:^€¼zÉ&}¥LØ“ŒrIj9IËPW¼U~³oÒV…™Z)%„ð§¸gåE}°ø•W_rxy§39Ûã…Ý|óÍB»ý·¶—ì÷¦>Ÿ{öÙLœxv­Õeõ/(нýöÛaä¨QcСéu[ñFq±ÊùÔt¶(êNîkܯ_Uÿw§Nµò N;t²•!*Éô’85IL ð>Ãt[Xw„gŸu–šñ…ÜaéÒå2pÄàXÏüå/Oxk(ÿìhʾpu뭷Ȫ(>XQ/¤6Ì Ž9¦Ý]xþá–[n“•h¤‘—¯ÿ#¤ü¬÷=Â~ûîçîW=œuÖY¡c'ÝïÞ%zÊÅýexúßš Õ/äC{ØûG?ÁCLKÙv†V´8!‰|?ù‰ÞSÖüà›‚– £‹&𵚬¬³lTë˜1cbýê²w^ñŠ•2c ‹É£GWËa6L@QG8 #ËH!ÁÚëɹŸ–oÊl+ôùEv)!ɥà t~‰'æÅmKPœc«‚J !Œû!³¨ƒŒ¼‰Œb2Ú?Û[¯óÎ “'O·ß~{€é¬˜9wì°ïY·vi±`Õ¦}b1»GY:wéz÷éúöéúöEÙG¬(oÒ¤I&Ï/ú@L*ðêÛv:Éë96©c#ˆ7m;õlr‘ù·%ÄóÕ·O)?ßu0ïÓ»¯Õý?­0æáûkà»à Ã-·Þ*V&|ÿ¢ïßÒ…w8ú7Ôæ©÷Þ;„UÈž?X´Q¬¡ðƒƒ+ÿž+¼–÷©òÅ$ÄÐ!ÃdÛVÉë`¼O›(ÉØR„û¿-ý+±E¤”“²tæ.§ä­†2¶-ù…ªvä?STRï~XÚ%î?žO\b‰W¹Ç;„ /¼PÚžÿ‡|0\}Í5ò¼!ï¼9sKòõ{OÛ'¥¢ÿÀÖ\ÎúÈä§mm¤Ür˜U ‘RBLÊÒß&ɉ` P–‘RŸ|;N,W†j.ØÊlxìÑYí´ÓΕÇ|!'÷x³Sã‹ÖϦcõœL‡H‹ö«S¦È‡Âè1cÂÿÆðX®ŽGƒ‰©y,ÏW_}%&G á€„o¤é~ óá¤å!d™æË†2¾ó6öxëƒá÷x³üm…©åsNÅ>L©ƒ=÷ü¾™j}áäS¢Ã±zø¥ÛÚ<½EÇ ð ÏWUÿÃ×ózõÒ"7‚ ‘ï¼óÎg5,ä ìœiv}üúõ뤗âV”˜ƒm^Óׯ[ÞŸñ¾Õm×eýû[ʈDÕA]Ž4áMÔÿÅ_Ò<ñÃ-9¢ âC>pãs„vˆ‹m‡}Ó¸qãDGà‡¶c÷´V óã=…I©Ô©®àãõÓ{”ÎìJW",¡|ûÌùá#¼0QW¼qrÛêþm+X¸Öæ? ±¥E/•/ÛÚ~kbZmåN•$pà nKõWGlÚÿà=õôŸŸ6£î£Ã<•OʇÿZ® ¡bkqßB¿€«õ׌©ýqøáò^€uKåE‹žø¢ï¡y<äK}Rr¨ˆgºxaå}öv]tBuÀ°·ß~GžÄ‚Šóì lqíÀ? Gýõy©‡½~ðרh<Ƕ­¢p—\r‰½$GÅû×WÂî]´šÀ€"±mÞþÉV䑽ƒcýŸùó3†?ì°Ã³ïOø#¢ÕôÏ NÙWýèÇ{xŽö¬–ØÇâ(ÕÊ«Ñ0K*¿Ç»’¶¸-õ/dMÑ¿ãþ·äS[n¤ô!ŽUe›„7sê“ß{—ÊŠ·Ïdk óh¿ÍâsqŸõ‰šO÷wcLµ“$øí8~úé°|Ù2+öyoËÅò¢'Lp‡¶­ö§ ÛBÿW¡U§ó\ÿ¦æ1{£¼©9;'|ØÆ zÕE³dpÇÕ[kŒõš˜­£T¸ßÄ.6Lò7úZMLßs+L\! <æúö=õ$<6kƒ÷+ÄâÕ<ÊIüóBú²×Öˆ}I,Ó„èÕ¼­v*•AS¢ˆp7’Îâ¬>q%qìÏÅyé,/LÍÉúSš(ÖÔô_îG#„>qe÷ËijïÕoÈÊ:?:hÄê1B±®Þ«¹yƒ¯˜¨`¹l‚ug!¡ŠÍ›eeA÷õ•ë_jGò°;b8xº,š‘c²ˆå™ñž:r2d¨ÕIÙ”åËK?¶‹!05/È¿:š·×?‹å'FFŽ)÷~þ bYêa¸[ gÃH:uÙ^8¯šúŸ2åU²·Î{ˆ5j”¤s†²ýö·¿Uö¨b¬&êÿª«®¶<ÚED|þÍqb½F)\æ‘¶È=%ïÞxS²VÐ{ªš¹¡^Ë—}e¹æºí:>úˆ¦»þÇ Ú?“^éž]¹TÌœÙi[У¤S˜OÃTÿëo¸Áúéqn¨øü1?ÄЛÀý3o “ü%T„vÝEOÔh¶ÂÕ,ŸdßFù8¢KÚm½d‚L¶ ÿ-É×ç .¦Û¾r°áø{Óß+>¢F*Õ¸…ÇàåcðŒ'dbµä€>òh—CAø‰Ø~õ<ÇǼ|t²\|¾Þx£ùg/¿$¤BÿGÆ•RxÇÜ…ö_(ä¹|×Ýw—ĸ&öo±ßÂ÷NÊ(÷oÕòo¸QÛäÃR§Ô:‰yÉ ñ‡ö‹r€', PsÞW¨Æ¥Ñ—\òk©/ê\¼ªäƒŽ¿±cƶQü˜» ƒdê÷^ÙR˜Ká¢ê2ËsɲáGäÇQN¬?Þ§¬²$Iò±7Ÿß‰£îÓ÷©ð+<8ɼÙä%c b$”F“ü¦| ò‹lšæ!e7%Šˆ‚þ3-ùaõêU¶í“Ãþ{ƒ>•Ê^̓ø¼@[ùîw¾+ýïÈ‘£Nâ·*ú9™ØúÐÓ4ýqʨÏè0é‡6Lfñg÷ËGZ÷?=^/ÿëÛ¿™šK™ êÊxŽ7n8~;oæÍxö$öÔT9éÕKu@žÜÔ|q4énnjŽ1f~pöM»`„U`Õ¦fjŽÐõH3ã£>2`¯+ä£ÁzSs]ñ®‹ •˜K/VSsa_0™†J`jNÈqbÔC»¡A4Cb¥c,D|}fÇúì¿¿šMuÔQáE«ö˜¤ãÄn»í6+öcEáw¿»/œvÚiVÿçã6ˆ¼Tœ«ÕCŸüDVVvÚyGËßûW½eBDëZ£qŽw,'ÎüV}Öâ¨'ìS“ó¢‰þ¶×_ÄP˜éÆîxL‰¤k$sa¼±›íÏpÁ‘ë$ Ìï"ÍfÛIzèaº_r‡íwH…"2yØF.»LnÀƒ7;Lo‚N¥Â™?6ñQ„t˜E³¬8RÏd¸]èæšk®‘ø·ÊÑ‚±x~†+õ §áêpÒ “ë)+õZø¬Çã»´>:I%r2>AVÐP.èõ!šá'Ÿ|7. 6"œ|Ê)Zϸ}AÎ 'aáÖÛn·ûÈv=ޜܟýŒòÃÚ–¼ÿ( ö}Ì1á÷¿ÿ}X¶ ¦­©‘aoÝ¿cäT÷Éidj;øølÌz“Æ[(cþkEXê^âå""FЇ’'âúbþ«Gj;1c#ÈyÇÌÃí5È“VÖÎq!—®t꛵kÖIºþ¹Â8yRŒ5TåÃ[=/È|èvM\]Êy$ýË‘”qgðZƒ!³DSrd!äÊÀ»Ä˜A4Cbs9ë@15¯ø‘€t 1EÍX/â“é´ ŠWž7ƘȰ˜)4d« ô´ÿ~û‰ã=’¨Å†¾ÿÔÙ câ@xÇ}Ü'[!ޤcB}úñ;÷ÁBµ®qdRŒ¯–¯0á–öã&áàP-™ÃÔüK¯ªÔÈ!+g¡"ˆF¨+øuYÍ™’*oÑ3lXzvÈöë¯W›“Õ>±¿&á'Ÿ|"'#F ˬ™ðM#ý‰“H«“?žUž…H›‘²àLdH¾*ÿØ:„­:ú¬jß­^Ç{¬ö‰Ë£ƒ;ä-ð»ôÒK¬_RÖ‘€t £\•Q Ç«.D3,/F6·:SSó„ áCýËÀ¹=2„Às¼»D§V@²÷)¾_»ÈÄœ¯a &‰èÔ eÇÄIñ¢ZÚÈqbLdXÌ F4C#Ë\Ĥ͓bŒ‰ Il¡"ˆfX@ǨÃ:´yRŒ1‘!‰-TÑ èuX’6OŠ1&2$±…Š ša£ë@ O?]¿¡ÑNÔª,4Ú65GÃÄ;5p‘í_Ì·mœØÂ¦ùF-u°"ÞQW¼ñ„ï¯vØ>À“ú²åËí9|îÙçC2Ž5IA.·D–%¸ˆÉ+OŠ1&2$±…Š ša£ë@ÒæI1ÆD†$¶PD3, cÔaHÚ<)ƘÈÄ*‚h†V¼‰eB=ÞnµšGy½gxÉÖkáWýÊøAš90Cªc*D) ü®¼B÷xÃAÏÎpöRÓ½V~ÖyžxâO"Þþ8P}ö¹g­>ô‡‡lP3Ûyߤ©9> ³’±H «soìñV’ ˜§Œ)¦¤8!15!œHÀþlÕjnï'4ç÷ú¡Õ™/>ê’ƒº /¼ÀjÃöã0€½ZÀy­›h}P«…Ñ45Çö8ð†ÞT2Kn¬ (cŠ))n†Æ­ (‰þwê &¯6xZOð¸.:”ƒ9sŸ„’—~ÔfæÅ«9¿šF{ù,§Ôÿù¨¾¹‚‚ô4xBå ïh• \œñÑA{rfCþ ±B‚Ë;qƒÖI!„gŸý«ñ|e2|$$ ƒ4BÀñF{~O÷'¢ý½ü’šUærôtÊ@/î,[Ÿ¾ZÅ“9©5ŽÑí¶S/ÆpÀ$W$Å~À-Õÿ‹.$C átîÞ!CC·îÝìùg™Ðv±šõGõ“¶Óaå*?ðÖI›¡C‡Ùä „ mßK/—ÍMAS¬íž{~OòÀüW¯DÈÚN½&÷4i¿)¬%ÒÀ–ü垊â8™9#€×b<ûêÀ ¦é›l;ž_B+ †Ž_T’†x{Fÿû½=á( Ö›“œþ:ù¤y‹Lae11ê±.ƒDxm—«Hª©öOpszf˜”YSRÜ 1L+ë¹3%HQfHr •1Å«t0ÿkÖ®!s²ª 5w‘g"fô˜ÑÖþ÷Úëb í-h¿pr†Ë¸ˆ!ªxþöéºob!%aMÄÙÔú<é$ýA{¼9-¥boõ„‰OŠ/†U+W £!÷j_IýfW•|O[”y4ÏV¼ õoÞ_+wí¯µ~ØÞì¢|œú€vè;7q'…jž…z-H;XŸ…&¶µþ˜À€å¶˜°ïbØ¡C§€=ÞÖRÑÂ¥—`à­ï_”fòɳïÅ{r¯å˜ž„Ô+ÞàÓÖ;XûËi²8ðÆd5/œÁrU‡ÑÚ±V ò>-³Vìw0á§$M+j›”ËR¥¼`HÒŠPIš–1Å”7ˆÃ ¹LR’æ„eL1%Å "ÀÂ*B%iNXÆSRÜ 1|ó7·øýw,ÞhWÜâŠâ’U*zJ ¶CL’c{¼±2’ŠH,oCÍJ±Ç[IÏ$G¡2¦˜’â`XdêâJÒœ°Œ)¦¤¸A:yEPIš–1Å”7ˆÃ¢PW’æ„eL1¥sLí·ÜÔüâ¾±3<Ž>J÷£cÖ™§ýÀx/RiëÑνdc¼JÎñF®Fxüñ±–Î ¦ž{—`=Ì™‹oåÕµë>’Ç;Îð¦æ˜yy±[i\1™†PáwÞI{¼'ŒOf¯¤ÔÌŒ1t,hX,fÎâð±£oà¶o G>QG¬R¡þèöÛo_™e?ùä“bx²šïEó?šÉ`ÅõÇ üÅ[]÷—Àtv³»ÉÔ¼¸}³å¼Ré‘ÂCR¥+ž˜Á“KȪh™†ÐÁ$G$Éþ-YµÔ•Bo¥1#®xãH#êŽl”µÅW¼¡}éG|#wìqƃ{Ù‰EyfÎüÀðédm×'vÚYÙá+m£`i†/çØ7êà 3nÜZxNFü²ËãŠ7¬%¢îgÌ€—÷†‡Å² ÜS^"åÝÿ;žûg¡\†¬qÇ—Úï™o3Ó¸aCu¥Ç{5ǹžEö:)§ýß+¯úÉ”Fð~ð À±ëðÁÌ™Z0)f¹¬¹ âM"“BOÊöí ¯—œg~˜à dŸ81­òÂD预Žà®»ì&úÇÞkb5/c GV‹9MK^ÍOÐr”¡+]{äûã!÷þñÞ–:e>V­bËù‚‘TôËÂ6ð‹_œ¾^ýu©žÏG.mƪÆÐåÆû»[·¬Q{Å˲ `1#ÃQyÈÉ‚ªûïozž›1†ÆR¼m_ÈrÚ4Ù…þý(ñªëßšô*‘y4&ý[ìS°Ù°4&ò9AŽgaÕJz†©·ýþçµO1X!`Býd·_ò±…I¯$³_?5ÅýÐö⊔ƒã}÷G§%É9w­e)“ßìァׯõÇ–ÈiùžIОuv45wo®&"¾?Ö®[àïG8­[·6¾_¯Óí^¦ƒéÕÛ™x±tyÍòTÒ24¬#:¯³“ïAç¹cHª<4¬sDLCè`’˜IgŒ!©òаXÌ1 ¡ƒHb&iœ1†¤ÊCà `1G¤i“'ÇÅÅøì²mKèOHˆßß=å"°©â™Øßy×]òý­íQ¿…ñMFþÓ¦¾‰ÁGy=ðÀï ¿r…NP–%Q.Ã$ÓC†ÀbŽ„iì@3IãŒ1$UV‹9"¦!t°IÌ$3ÆTyhX,战†ÐÁ$1“4ÎCRå!±5åÍhN„Ä€3äûï'§‡vh†ã>êxVv¡“tïÈß´iƒ k5ñ´Jf°ÔÀ‹Ÿeà@ xï\mŽ;§û/yF;og†c‹XÃsãyÔ:Ƥ ˆ¥ä<äÞ.”#÷jÎ| ó|ihÛõÇ ¶úÔka6<Æë3ÖÁÊÀ÷wÞaºù|ÞçÌb!œ8€/³_VÇRýú©³¨î=~¢t…úcP)ƒùÂ9Þ45Ï'SXo†&Z_ÿÓΠwÕz¸úª«í~´Õþ<·&l•…+Þ«VÅÁS½&¦ÇàÁ ?n´`é‘¢üÅ‹£¹¯'Vþ`¼Õ™ô?ú裱h,U#èÞy]xmÊÁãc†ú:ç?ðOýó#f©+fŬ˜5`#ã˜äÓ\F7ÎÞQ/ ˆ³¹Ž ‰4N Æ9&qNdžQ@e>ý­§ªŸêê½÷yyysïÞkÓÕUÕU]Õ½{ïÞ=aã6æ/Ðà·Ž·Œàc–¹Ò°ÿR×Pßð0~|¹ýÓí·…õõø/QäîLLJ 舮k¯åY¿Í0¾Ökj]7hÒ/ÍXRÂ|,\˜6ŽíýF›õÐ ?ö¸äœ;–Ÿ{.?Ú¬Ž~j¶5þ£d.„¸žUõ£“Ký¤cC¼ëo¸Þ¦è_sõUæÿ»ïþ«°›=-lþ‡\•÷âd­;êkÅUÏbxÒîýçù1%f_Ê·ÿºN/ÅŽ¿gžqºè·MÕœÿµ-74¡VqlÚ´ÑF¸¡Ó‡QöX‡ËûŸé±Þ uööìÕ+| ÓÿI-©598ò¤›«ËÐØ¨Ò¯Wb’´œ^õ4·°¦“évN?ö€òn6娭?\™'¬ˆµÑ9ËTÌŸÑ úûÓO…[n»U6èü'άîDùgS³K5œT½#F\¢eÚl†©/½ä£|lo¦MË䖟˜ÖŸ7÷¯È51Æv3$^C_þª»a÷Ä~ûåž‹mQ>CIë ö—кËöZÛ7,±Mâ\ ›Jg²é6³‹úy’Ú?äËï•àÄØÆÂÈÖ†ÚjI›ÝÎÿ\Fý?^>%"µÃP½©õjˆÐaëYqÄÛûŸœ4Æâh,í³Ò,xƒãÜsÏ5ÿk~)¥%mÍ!‡*úql$.èÇ2úû_t¤_‰ÕÿœjÎ5Þ&ˆ+’m­ý" EK !± Ô8Õ¹]ù'Õé½´jbúÕí?uZ ,– p±jbºmž?ÕûKÀø[jðRÁa3_´˜e±lÙÒ¸}™aü¸q2û«XþX«½ï¾?‘tèH®[!lذN–AH{}Vq¶H°“zü¨ºZJ»~vöç’´ý+â¶Ö_öMÇõ_æÞfn³F¼u£*4\8ž¤x,_uÄDÞç'Ç3ŸÑI›1cF˜9}¦†3fH(¸3¬²nàTst¼ãTs*6“†³Ñ~úÖD+¾Þ—oªÆþàîüftTÿ¥kÀ‘N™ÆÚOä¿é._3¦Ï3%oq=hÁ¦šÇM˜|3fÌ ÓgªS…‹~0™O “ û5ÞØ‰öãæýa<3'v¼÷’ mšá{r&jÒKý©ã× ýúéÔ8nPò“8Õ\òërƒ©æô7F y‰ßâÚÍvöOŸ>SÊ”ú™öÝ·u£,ÊEƒ[^™{,bÙ*Ã4â=XüŽ‘#¾Ížý†¤u“ýU4qÄies5m›Âñè2Ôkt¸ÐÁY·n}¸á&N?n†ýúî'»¸#!;ª˜Ž„4wýe¬~a_·.ÜÏZ‡>ŒÂX#Ý ².“6àHœÖ¤¯œÈ'6)3æNËÛ;æ«)SÚ‹þòqí¤Á~vÒp<uà(#ƒ]|±À˜;î»ìò-£óˆ1?².öŒ…=ëÃÚõëâÙñÚQü¦ÇL`ãØ©ã=úhX¹r…Ø:cút9†yÀȶ\­´^ëùªf¤R½n„~ýúKI¡ƒŽi“{ì¹GøÛß ²|LóÑêdÔ™õë-aôâïXwÀ§#ÞAF¼™ŽYÀŽïH§øfzÑ0ùP±térÙ žíwü^±|¥”?øóº³.›ºŽ [8îMêNÚ\-ߘOsY ÔÿœŒx›ËÅõØ”yÄTW.Ç÷×t?Ó„Ùf{>41B¿{øÛß +V®öß·ohÿ¬}“÷ƒVÆ&ãÎç¼øþ1cÆt©xYßÒÅüíG›ˆöf¯=ö’=!²6qÍšl&ÛÄÔ´¤-CyÀ˜µÃeðôå—_ Ë—-“¬Q??àìHfœ ŒB’¹ÆútÄ;gÄlÖÔo|äÍsçÏ Ç{¬}¼ÄÙòþúq¯Û=‡Ù[k ÏÓ ‡•¥|’&ffîÜ9Ú&ÄöGÚfCŽE[1'¶6l4—ýoy †ÈŒlræŠ9kýêúÁ\E"2²E È™c m7ê)îrBïó†H]ÃRNlø,3™â‰ 'žð[«¿§ÛiCú¤Ã¸¬ÛxÿBû%hh¿¡´Ç+ŽPEv¼~‰ÁœãÄÇ0#[Ä€w9JNú¡q’Á9‘-b@Î\#'õ24V2"2²E È™+bä¤^†ÆJC„òTsåÑñŽ_€‡‹O8©ó̷ʲ˷w1±¶vüêÍ U ïÇ9Ùïf"çxç9=æØßˆT@ìR‰ë°!×D S ¹~0êgGR9eya…¶|Å3s·ÝeÝæj‰_?6ðA,é›0𜳙&[ò_Û—t¼£~?‚¬ƒóù…=sæÎ5û«>ŠP4×—âÅú‹k¼ÉÇSaÕÍðWY«7ö)§œìüUm?üãý¦2[qôTÈîÖ½›R/CõVòðÎÕ?ÐØyªœ.,k¼ Ó…Ûø_¿¶kDZøÐ§~-±í6ÛÄÑ*Ýã@}¥pîÝl6òæ;ªÆç¦.±>aF/èÂty|½×Q1Üq§¾ä³lðSæDÏÂ…R ¯8Rïù©æXã ؈Ì>è0O®þëKT3`÷lÚ¿z„·}ùƒîí¹æš´ù_Ñ´eÈó)¥^GC‡]$öQ¿F4†õ˸ÿP¯qÝý×»­~R.F}wÝõ;1ÏZÿ0ÅR%Ùx yâl‰ÕkÖïì7ô£ÍÈQX¦ õÂësgÚCBìrÓϨ£Þ¬½AÇÔxïò¼?‡§èHñãÔ IDATˤ¬ñ޹Žõ_ö³ˆí›ß\Mò‚çÛ'È—ÍÎÚÔ¦Ë|‘®pÿaƒ9o?ôø|Ú/Ö³zûU†úyºM· ±S|â/׿±w¥ …¥sÒÆ~¯_`)ó¨'–?ôýË_ ò·Mú±¶ÊÖº˜ì‰m£»ÿð1‚WGþ'O1”Í5ÝóŒÚaÊì>½{Ëæ›ØAŸK˜žºØþôìÕÓîáqåÙEü‚óä׺µÉO=õ”=Ÿ°Ôd£ý¡`?ê ã1ÅFdÅÓ˜§¢~‰oñOSsª9ö’PL9aÞ^£Î•ËyÍÛ·k²úº•kô?l´µ™­XŒ÷…+msšG\uÕ~´‰æÛ¨;ÎsGfêgùÀ#^ŽÝõ»»Zûþí¿†éÕ/W_suæDÊ㈷“·jsÿ7[ãí–)øCöAûF[ü áí·óý.ÄÌwÿ“×ë"nú´×˜= ½™•?-_¾BfYH›X(ä#m<µ"kD¢¢E‹–:­È[ÝÃ%—ŒHy³¸‡~D)·>×ÌY9\±<îpã 7F†<í¦Ö怳·¡Ÿ>´ÝsŸ~D\Åhx~µäF#¹a¯÷?ž§ØPÔÛOÙUeÛœÝ.9̳™«ÏžRuB 4@8}¬3å¯â}ªLa!âø 4 ÖïjÈ÷ñá¿Ñåâ!ç&,Ó“÷yÉÛ_ÌtY7’,8_Þ?xës0Ñ[§†¨ºþÁ‘Þ1EÇvLtÜNˆŸ}šû¯ÐñÎóÜEP©ó¤TEn‰W$ö('I„”Ú1yiWâ(EÑ£–‘þÉ“'Ëæ—\ àyÊyLåKÜ¥¨1z”‡!|þ¾6mš{&¹R\eé?Ê÷Ã{ï¼ÛfƇ¦ëH¹D“®*Èë]â <ÊÃe™Jí˜'¥*rK¼"±Gy8I"¤ÔŽyÈ[ö–¤«HìQN’)µcò~yú—,Y°çÏ{ÿü§¬ÿfŽ$ß™ "hßxëM[ÒÂt)ÔÄ"‹ƒŠÜ¯HìQv¢"¨ÔŽyRª"·Ä+{”‡“$BJ혇¼_^ù3ÅÜÚS·½ÜÅqhåǰdÒj’<æÅÕúÕ7ÎCàæ|\@K”i*Oóéjÿ«oœ‡ 4À;Ìü;äË¥ìZÎ#¡˜†á¿ÿåÅTFéxÄUnCrB;<8Hcøïc¿ÙgY7ÀH[²æ«fÿ AƒBïÞ}dú1BlÀ†ÍÑêoŸ°à÷‘ÙbŸ™mÀWÆþAƒËÛ8NŽö¨jvßî#4Øs‹å»%(/¥9ŽVƒ£•EÙÔ¹gGý£ãGª¼n8yî®° E`РæÿÞûôV¹û Œ “¶¨~ÌÁ%›5á°Ãô<å\¦ê½òÊ+l4ô<7>EµýÊÑ.U;¼×”óä±\ÿ@–éìG9k™¶—–,ÍyóÓ’/ß~ä õ`KåÏz>z4Ÿ5Þ„i7CÅç1ò"üjØŸÊÍWÖv¹n‡W{ª,nŸ¢¶_}ã&”wÚþüõðEÛÿ¿QÿìoS6å#‘­7å2Šb郿¾Šþ?à€Ÿ–ì±é¢²†Sÿtú¦žvõþóÇUf~‹²=î¼!ÿmj¶¶ým¯'• uýw<ŠóºëôT,ÃÁþ-lÔ kíÚõaÜ=ãõ(½f# ü³Ë Õ€Œì#9Ççwÿo†psÖ,s{ÿ°Ó!оá¿-\™ ë ÆD±sú%¥¥3@Ð÷AS–(±¼57Ÿ^´* ò~~åŸ)u‘Z¿sÆç\ÿ¼&µÿé „uýÏêC1ã[ûüó.™Š3ÿÛˆ·=™£u©%¨hxÞ¸'jî$Yñþ?¥I\ŠKZä¹.*Š E4ã^3qIfŽIZ”xÛ(jƒ¦äZ?ä½à=J/).y=Až[à<‰‘‹hÆ5ôÿ–ÄòELÒš Ò,¤`C(@´vTõ%;S{ÍäIIsLÒš Ä¡<‰‘‹hÆ5ôÿ–¤Sö‹=q]ªŒQ°S’ÄgØuý5¸E4³é5—ç˜äõ%ÞåIŒ\D3®¡ÿ·$%¯%­ òÜSpPD3î5‡Ù°£ýøñãbx|'ñ ‚'ô ²!SE!²\,d«ÒŸ’’K1Éê%ÞåI {4ïj‡Ú2>L?.À„Š+Ú“ KZäõCÏ„qÔ?A¶÷iãe×v¤íŠý8…€þ‡Žñ’Ü¢~œ² { pWs|dÀ†eØEþ[»ìâÖ,ëIi õÖÙ/Šò$‚Â_͸†þß’”R%¯·ÂsRGÕÇÙÿc¹ õQ:ã^3qäA®ý‡Elª¨WÊIâ5R DQ6ã[Òï…%­ aºû–ÊŸõÏoÌçóóiôû¼Là ¡@Íx­þñ^ðŽ£—èCÆSMðÜ“¥@(¢×Ðÿû„äR\Òš Ï-pžÄÈE4ãúKb~!&iMiR°!(¢÷š‰KIsLÒš Ä¡<‰‘‹hÆ5ôÿ–¤¶¿Ðj&¯'È{K`:¶@(¢÷ž'.%Í1Ik‚o„ò$F.¢×Pÿ¥ãͯ™ r[•pÉM’$À”+¾´Y4%w‚K`­N/8ßÔþG‹Éü½UÂeUO")¹c.ŠGëÈÿ«V}úý¾_è׿ÐÂʺ%—QÞWýþ[½juø}¿~¡ßïû‡—_ÁÆg’û¯¬ÿµlb6;>¯ò¯õ×íá}¢²~õoÁ‚ùáÜs‡¾±CÀnáG‡Nš4I6Ò”;øß¤ý‘¼Ú]ùÙµÿ¢½FûÖ¿Ÿl^é4ðÉ[??GÿÕŸfz'Ÿÿuû_·ÿ_Vû¯mWíÿ¯ªÿ݈731ÌkrŒ(äòº H[ÿ¢¬6¥%Ý#bíQdóSŒn@­_\áüa~+%¨ýßÞîõÁ\f@]ÿÚ;.¯€ÎeJðˆºþµwc]ÿ¬¦”ÿ“÷_«µ9`s,lÚSÑsÌï;ÆÌwUˆúþ«ï?ûÁ úý±[§ Ô÷_û'¯Gæ;¢=¢nÚ»±¾ÿ¬¦”¯Ìýç:ÞšKË«oW=Rî‡Pãøw”,F„óé²ÚÿpPæ s“ެ C€‰Q ñW×?s…s÷']&¸ÚÿæšJÀ9KA‡@F%Ä_]ÿÌÎ=æÛºý³*#>©ï?«•ï/«KŒZ¥«ï?s…sù¶¾ÿ¬ÊˆOêûϪF%ÀûËê’C £VéêûÏ\áÜc¾ýï?íx³€,©ÌåèØ»RC'%G¹˜{×@Êbè¤ä(s cïHY ”åbtì])‹¡“’£\ÌŽ½k e1tRr”‹9бw ¤,†NJŽr1:ö®”ÅÐIÉQ.æ@ÇÞ5²:)9ÊÅèØ»RC'%G¹˜{×@Êbè¤ä(s cïHY ”åbtì])‹¡“’£\ÌŽ½k e1tRr”‹9бw ¤,†NJŽr1:ö®”ÅÐIÉQ.æ@ÇÞ5²:)9ÊÅèØ»RC'%G¹˜{×@Êbè¤ä(s cïHY ”åbtì])‹¡“’£\ÌŽ½k e1tRr”‹9бw ¤,†NJŽr1:ö®”ÅÐIÉQ.æ@ÇÞ5²:)9ÊÅèØ»RC'%G¹˜{×@Êbè¤ä(s cïHY ”åbtì])‹¡“’£\ÌŽ½k e1tRr”‹9бw ¤,†NJŽr1:ö­óoK^!½%ì‚wD È?¼D‰jJPA­@ÕúùÙÆ9Ç@jÿWÔ¹ä%>ûì³a×]w ßÜa‡ðôÓO¹ºX‘ØSE“f µÿ+\˜¼ãI¸‚ZnÁ;¢Ôþ¯ýŸªJ…/"Ê©î²%4Á;¢ü[׿'žx"œzê©áǽö{ïýãпÿpç˜1æ5.ÙjÆ&À Çe¸$ÈCž3â+PB¼#h€é†öôú1ìÙ[ì¹ÃÛ“„ú G"MÔ$]¦,9.Ã%Aòœ_ŠàÑ@LW´7GµyÎ/N?žÁßÝu×°ÃßO?ýtTÜyý'N çœsNvÑE|1’Ä´†!ε¡¶K xG4ÐÓ•0µ~ï‹ÚÿETx§%©ïˆP׿èÞä‘/ÿþk ~F‡ÏÜæM›ÂC“ NœÞ|ëM+@ؾV«zø‘0qÒ¤€F׬Y³$>i"ð ú¤I Oš8Qpk×®I›7·Â#?,¸£,ð?ýÔSáÝwÞ k×­Ñ>oK–.Uþ‰“»ï½GÕ.X¸ PÿÇñ­€¼Q?t >8ÑpÔ¯yÓdk>ú8Lœ41<8qbX¸p¡ ™†¦¸x“Qý¿ŒöLšÞ}÷]%Dª…‹Š~äöPC0N›6Í|ý¯ý+Êð­ðæ›oš­ï¿ÿ¾øß—ÿ´3LÆ‚ùóMÿ¬×g™~ä¾Ðp’•?âÞoš\Ì” Ú×?2QCà 3$o9ô&§êß¿oßÐh4B£Ù }ûö%QB¦b$a†Y‚,â9(‡©O˜!yË¡çð09¿û¡]}ðVXŸoÀUçPóêi„Òšrè9fŸš0CJ+‡žÃÃäLúñÜÅóåâŸÁLÅ) 3N’´Ûn»-…;NŸÊ‘ÿ¶¿ÉÉÿí=¤ÜÞׄ&yEÈsx˜|µ~ÿþ }ôÑGaöìr›è½G˜!½Y=‡‡ÉYû¿èz†!½Æx É[=G‚݈wDJ ðªÕ«ôÛl„aC‡E™)ñˆ#¤¡k6ᜳÏ1§œrŠ>àø°–PT4ªàG8ÁI³iãFåx{ "]ü¼ñ¦€:M~ðÁ,M¯ž=M7|Ð^ÞŸƒ¦^'ŸrŠé—¼4S~Lo£ļAß«¯½Ó4¥£NY] Í{…õüâ°ö“O’]"Þëh…]¾½‹Ý'žøÛNë­b4É…ò7ÏTè9>a|Ë-·dö¨ïôÁKà'kaW~y1 KŒIà)FOËew&f©?û;£Ïó 32ü’Kž{îiõ íÈwß=<õÔ“Ê*´\š½‹-cî¸3ì¹ça»m· ô÷¢; ›ä6ö‹ž1wH·ÝnÛÐ@?—/€&¡Ó:=ãú Â%ðÁêØûa÷Ýžzê©(Þ눰¡ ðb; [ê6ö«y­0yòd{öî´óNáÎ1w†‡~$\zéˆð«#ì´¾"#õ?òð#á;»î*϶~ôo|S0aý?ùÅ)VçvÞIíyèá‡Ãe—]íñzLYÊ‹¡ 0ÓÖ­EûvIlßÒ{NßýûÊÇLß0õ†õz/ìµ×žÙûÈî?p÷‚hˆ)˜°‹ö3³^ŒÁ%=J™òÒ©Ý{twÕ>OE¹ -uEý;òÈ#­Œþð‡?D‘1…OØF™v¼›aÛm·iÑ›a"…ÛëI”ÅK…1wŽ {í±gØvÛíBÿþ±=h«¡c‚I®°?eø:Uÿ:Ö˜SMr­¿p'EÏH`^ªô?>HöèÑ=tïÞ=\pÁ¹ƒÛÄž3Hø»uï.Dš ÿ?òÈ#Á¿£ï³÷{…G{4¯¸mttm–Uè—ô°¿3zÚñÔúÕ3®ã» Zµj•5„ –UÔ‰2ÚOüYØ´‰#X!œ‚NZür¹ÓN;†wÚ)ìä;ïðð[²d‰(Ýè:Þx©êÝ»Oèµ÷Þ’†<œvðÁ–ÉûïGG4}…ê©'Œ†ŒbD–ô¹Žª|ˆ/;š§³¼!_;ï´sX¼X󡯼;ÞͦŒ¼WþJbU*Ý$±;JÌcóÀú!vb”á‰'ã‹hL!oø´Ù©.êë­7Ì^ØÝ½G°^FR↠QÎÀEô áظqS¤„ðÞ{ïÉK€Žr4Â=÷ ã­—t¼£~ømÇ“ßvÜiç°óÎ;Š¥LiÞVØO= )‚ñbCU´?9=µ•ZasûU^IzX¹re¸òÊ+í·ÞV¯ZÕuISh¨>kýfgJ9üô?üðÃö1u劑†Ý{X]ÂèîÏ®úöì×åþç½(aö±«z÷î-³8¼h?fh ¼œ7›/22D+î"V‰Î•¿× ØIS’ó?fúuÔ¯ÍGÈê?|‡{Jml†KG\*’J²ÊÒ‹êËN¿äÏ U°„øÜìïŠ~ÌèÙ³gôÿØ™:*·ß~»ùÖ,]º4l¿ýöVþy=âˆ_x™Âž :Wþ^Û?”#:r}þ7ê¿Ô—ø,£œ5Ÿ™~sX¹v%R„†œwžÕï'Oîòý„E[®½î\g¥mMîó®¿¡”/gkÛßóœ=“§¼X²·ˆðº„ÖF?F¦ðüó¢yÿ³\÷)´oóæÏ ,Pzªÿˆóù›»:ÒšæÕ嘠„eÿ3EÑfGÒI“&Y¹|Zÿ{ÙíôÛ3ø–[Ç«V•’Ð,ªÊßF¼·‰#ÞŸÂþ%K´Ýaòùs䯴=¨ÒofF?…~Šð2=®ÖožùÜêÒ 7ü¿ é}‹¥ Z.®tJØ ÷ÝŸÕPŸÎxv&Ì”ó÷ÿ6®Mþ†ë®z4i]þÉ…[ò?‹£˜"ÅËP)M›ö)»¢ß:Þ¥æ¹Õ «ÑñŽ#›Ã†qÄ;È4ÔnÝ»ÉÃf‡vN‹Ïú)§œ,/ôÝ»wóhæ0…1˾ã¤\±faʸLAŠ/\ E IDAT&Ï?ÿœqø‡¾–§«&>˜:Þ:¥Z=tÊ©:d—‘òë«Ùèx³Ætj\%%åNn¡øeèn¥tCë(ÿÞ½ze'b?ú@:Þ.-/½ôR¡ÛËm£}ôo™ $8h É<¬«Â{xó³Ì1ý˜g1¨ßºfÿ›³ß³1…¹Âþ’?ÿ™–ø=ј"¥2ÊÐø*ìù? ß}ûX]À膿Pß¾¾ýöáÑÇóhƒ;ëÿ5kÖÈÚyŒªusÀý¼qæðâ‹/†ŸÿüpÓúŸ]Ñÿ—^V¬×ðè£~æ÷_ÉžÊüøñ’o´?øXá§å£-:ôÂpðAË4êܦû?Xÿn=Z:øprÛ­·ËG×Vksxùå—ž!h‹äCà¦ê¤èÿå+–KÇ 4üØqä™kó̰àwC–-_–ôôP=xÙæ‹v.¿¢½È(—¡o¹y´Ž vën¿í¶°dÉbY®õòÔ—Ã7vøFîƒê¤•êk'ô[sgÙ2 $ïˆ#Žò¨ÿæÍ›3éŸFÿñCÊñÀ/^,²-^9ä-ßf#¼ýÖ[%±µöc”å)öl¢Ý +üÙIÿÛ7ÔÍ›o¾YfÊmܸ!¼øâä¬}{ð D®Ñ7ëL¼݆z°xIØÜÚ¦ò^h6B÷nÝf¶»¶Ö~‘Cs2CUõ©Õ wüùÏöþWÌÇ¡¿¨ÓÇ«ô[ÇÛF¼ÍPMÊ(Ãì_¶l™Ì¬A{åÛ~ˆ«ÒŸòG #…Q†èϾ,$ÁÕú͉æ“Æ0Re¸•þÇP_ðüGÇ»|QAK–"~PÓwû4;XùðþÀçÚ0Ëå¿dñâpÈ¡‡ K³xÕåOÓ#>$a¤1Êp+Ë?×`B<º"Å3‰'ƈKN”L5_aÑñþƒ>ÑjTT<|ñÅÅ4€Ù¹Åt ^žžáZAFÔ8ÊpÞyC2Y]z}Ö¬øh†‘#GJrŒûŽ7òóÌ3ÏPtxðþþûsLfê@öP\EÆ< ú_{åUÓÏŽ·)Š€O#¨¢M,‘€üòæcøÌ³ÏÈ‹˜œ˜¦š¿ÿ¯4u>ª×i{F¸þúëÃ?;PdzjšJNý2âíf üa§t…4"é˜jûqi™6¥á¡NR¾á ˆ_ýǘ}ƒ 6a)ðàQÔ_ÄSÏu×]†°æôÍ7ËËÛ ÁƒÂÒ%KÃâÅ‹ÂE]úöÝ?\8t¨Œ”"Öáa™ÄÅLJá—ô—–ßÅaÊK/Q¼Ùïõ?ó÷gÂ!CÂÁþã?þ# ¯Çû %„žEÏðá—„uëׇwÞy'\vÙe’G<À¯¸òʰlé2KB€öÏ™;'\qÅáØcŽ ûî»oøÍqLJ &„¶>ZS`Íÿ%#FHþ1÷ãO>÷N¸7œyæòÑê¿þë÷™MÔÃzvÀ%!õ£q÷þÿècõ|†Qçwß}'\v©ÚsÄ‘GÊŒ³‡B°?ÁÂE¶¬Ä+úàíNœõ‡³Œä’†ï|ç;òкþ†ëfÖ Ï§fK$4Ó _cïN=åÔð³Ÿ(¾2ä¼ð×{î‘r7%øÊŒºsñð€ÑÎÑ7Öº3h Ô¥E‹…‹.¾(ì·ß~aØ…CÃú ºþ¾Æ äíÛòeËE$õ›ü‚Q´Q¨;tpÀ=qݵ×YÊ´à…ž—Ž©ª(o”é„ ÷…3Î8#ôݯoø¯ÿú¯0ujª§›6o—_~¹Ø“OMS©Ôï½÷J=9rT§Û?Ÿ/tÂ.¿ìr‘ñ¦ÀÅ‹òGý»ä’áÖ^ƒ>f̲®›<’¦„‡íÿ”)“;¼ÿñ¡C:ĉn£?RhSŠúgˆ|A>@¢Ê«h#ñK´‚Ñ1cƘ £F޲{aò”ôÌß”áõ.Ñ¢p,¹:õÔÓBŸ}÷ G}T:thlç†[ûG˜átà 7H»Dû·Ùf›pIl#/¾øIË}^{íUk?_›6b4l…ðì³ÏD]—„÷þùOÁ£­d»³|ÅŠÌqXÛˆi {ì±Âÿiì7Ní¹òÊ«lÖ F×µOöÓ¹çF\"~A;òλïÈíC9Df>\qÅU²â…}_¸lÎh-ÌdûÖ lê´r ¼…éBÁáý†þÁÇÐOc?ó²~ý†ð—±cÞðbÖ&~œ/‡y晿‡›FŽ W_}u8èàƒ$/¸ßà3–?Ÿ™ï¿Ï}dÔŒk®¹Fê>¢À®… HûõóŸÿ"üôÀÞí^}õUÉÖ'Ÿ¬ÕgVÅóÏå—â3¸Ê~|€ÿN:éwò|Ã; >Zôë×Oò‹+E¿ÂÍsæ¼/Ϩcìyz\˜0~‚ÛoD=æ‹„ú±ÜKíØñ¦oú4‚+ ÑÄRAð(êG§^†%ZQˆÖúéÏ ÇxýårßÛíûR¿öí³¯ÌÒóoŸ†Î]·n­-?AûÛ»÷>’žuIÓ òþÈçߊ˳rÆÀfãYṯ¿aq—Â*ýž©DßJû!«Ju”hD!*ÉWAð(úÿ›~ñVz“݃k¼‡ ›6m’‘4Äx8`ó²òÕ ©sÛ­º´ _“7nÚ;¶`#Þ"XóƒF˜††‹S³ñ5 iÐ÷ßo«¸b4¸¼ðÒ^Y·D¤…¹ýÌ<ÖxSfs‘]ä(Òþ«x ö#ÙqÍ:ìùͱ¿}}ûîg9ãšvÜ ~ èÐ! ½ñÆìpýõ7¨Ý»¬%óÖ§€w·ïëz[LãDñ°¿tᄑ˷¾çž¿ZRÅÐiȆ¬²-ÞžÞþwÞyÛ>\°î !+_E>îᘲì dÏõŒiÝ<îøãeݤø%î0xð¹’pΜ9Vß@—Ÿ[³~Ó7%–ÉVX½zu8áøäÅßÒ1}£&Œ›`õ¹Å‹õc7`<¸i?gÀÿ3gÍ*éÃÓáò†2þþn»…åË—±zʆ¢§Ñ øXƒÝÙ-mü@}y¿Æú·ûî» ê6Ï3ÎfCKô@?lÀZ7¯ƒú¿¶ý×ÂÌ™³R-¨(6~ÍG=¦Œ?þÏKú¥ÞÀ¿ÍF¬××Kcô\6/*È^²hIØq§M&e˺fCÖèÉŽÌÑ~Ø žaÚ§ó1ʘ¾ÍFè?a|f?^æ™ÿgÿ÷¿#qúŸ4ÿ!°OoÁ€dtÄ¥<°‰×óóáŸçß1 ÁÇܧwoÉËߨ¡b4´%›…a-ò˜¾î;M…ö›cÒÿœj­ÜI'ë&–¡þaO†ê7Mcÿ^³PÐO´(O?Õ¼JY•ìTóKþ7=úß}ï]+ï?Ýþ§Èêx¸‚œ„ „µä¤ªXÿ«Ú”/ì&8êë—„®®‚ƧW,_aÓÄ·ÿÚö²D‡>ćPŒè"}¯^=ÃÆ8“áÄßþVpxþ”¯–tð ÷&e)ßÖÙn³ùÊí/òG{¬½n4e·só³ÿëÛ=Ìš9³m–˜gèGÇ2àmߢÅåOU£ÿo¿ý¶˜ÁÖÛ4س`gtÛØ—ú´«{+wÜq¦¿Ê~äϰ§ŸÊwg¾ÿñ„çžÞê¯ÂúV+Ì;×è¢Çùrn¼ñÆ‚Íj?ìaûcù‹Ï`.‘ÂÇåNóCñá¶]ýÿž§+ðµèãÇ’IèûÕ‘i‰K‰]JË4«¼$"•e埘Še^!¨E,U<µþÜÍ+:+ÅñÑ åº‰u|?(?GÍóò¡KÒ41ÍåÞ9ûì|ªù h›°Ûnß³œ( ²†_2\tk›èXRöJHËEO]þÎ_,:ËÇ=ÓT :{ÿÉS×UЄ*qÕªÕÚÈÆÍÕ ¬e³N;í´Ô¸YÞ5xwï°VS©0më—,]bk»™ /ÑÚ˜7å«(3/ôVW^u…4š¨ÄhÐqIÇ;®•›2E7NŒç#_ú)SFˆceCÞ€ïÞ½GÌÓb™î¶y“¦¾9¶BxõµWL?ÖŽóR.ÇëÓ‘ !XÚVvZSÑð`€S^ŠÛ4šáùç`oKv^ ¿÷çè—fêǨ(ð_ßþk¢;Ð#;ŸxâÉL?¦šôÑ¿Ûn£_Ò0Ýó¶[oýÇ\Øv»m¤!¸çžqÑø˜rªÈÄ õ’¥Z–‹—, K—Är•õúÞÉÓ§M—´’ÿ&:ïè8¥itíêŸH‘´4f'жG|¹ƒìŸìÛ'üxï½ãK†Úÿ»ßºwÓNȸ°–¬ÿ¡¿þò‚…/åÿyâ‰æÿoÒ;Eýÿù»ß©͆l¾tåUW…‹/fSeá×›Gй a‘tžÒK,Î3Î<#\uÕU2 Oì±Ç–Þu÷X{QÚyÇÂÈQ7…Ñ£o ûp€éÇȽÂNôS&FÈÿ0`@Ø~û¯›ïÑ™ðS”.Ãò„øÂƒ´¿ýíoÃëŸóÿvcšn=º‡3Ï8=ÚÓ7Þo°ç^{:{ªëÿ”)/…ݾ·›èïÖ­[x?Û…_-»âŠ+Eæö_×zmÓT›¨×O˜ýTvÂñÇ™ý˜¡ó÷§Ÿ÷Þ{_¸hØEaû¯}MhãÆ#»Ö‹hËOöÝWŽK¢ÿàÔ¨;±ÓŽ\5Ê|6³è«îÿßýçZÞöÚsÏpÕUW‡‹† ;|S§YÃÿ£FÝLgÉ)ö¢(ylʨe”é×uÄùÚy§mTlü¸{L‡ŸýÃöG¦ÈÇ6óÍ7ÓTÞ­½ÿÆOzž}æïæOÞ6¿Ñ¢Žë ýÃ,­»Íðâ‹/DD±®þÄ~!°#OBÊ|®—!î@•ýáúÕXÓ™4y KžJ\L¾ý÷Ý{¯µ?/N†â%"“&¯!a3f‰ þÓ§§zZxîÿ‘ÄgàñüÃGîóÎ?Ïêfáƒ6”è!êßvÛèsøpØ/þÞ~G§/b°láù“Ø…9:MemeLJ¡ZÉÿú¹[|§4P¦JÅÍÕšð;i*É;|3¬[¿Î|,¾±SNÖNH Ù1h4F¹ymܰÑÀ£Žþuxñ…Ãsÿx.àNcyYlÊ&;˜†ëLÍŽ/¤«>\°C2mtHPOdêvl ý®æ/+;{ Ͱ Ž©;D—n®¦(?ÂÄü#ôöçÏ¥pÆ?ð 7`h†?øP¦“š=!dkÖç¸)^Hþý8æÔÓN3|¨â‰¿8ÕüàC–ãðbpñE‡Ã×µ$8Ný?1u—xó[ô?¿t[™ ÄúµÃ;ÌÒÿϵ×z÷F_«Ãå¿Þç¿ãåú¿µË.2ûõ×_·FêÒ˱¹U˜zμ#^ß aé²%‘§n¼áƬü‘æÙgž5¿:òˆlS:¹‚z©cé^°HG¼QO±£÷?ãËôcDò×Gmy}æYíÀ,]¶Ü>`÷çâñl˜òGÿOŸ1]ì[´Pg+Pÿ}÷ÞoùÇÔ`ì®K¦PÓ~sÌ1:[$Ý£ÍpàO ?ö„½‹Á×QXèßm·ï¾D{Ž{ÐùoLÇ/^˜›¸qäàÅì•—_É#1ƒÜý´ÓSæ Öi§ž‰Gé”a‡èüÐì'¦Ž²3Jûuô·vùÖ.aý†uÁ×L¡Ç…eð ê.¼pÒÿ}¬ÇìO™Â”E‚ÌB ÿÑaܰq£Õ¼€ÊN¦±­Z‚¥˜¦¹ xCÿý÷¥5ð-”ißý­ýÃ(Tb9ƒŽâ7ÃñÇ/Yñh‘lö~»$¢3ã`ƒèé!úñb ìLràOu¤uŸ>½ó„1æy7mÚzïÓ[dáÃ\Aa àÿô#OýYf éõú…,ºT!ÁvÄÔR·¹Z…ÊdfÏ¥p‘ŒçMï>: uµàÉH9m;L&³¬N§ÆâÈ@ÖÅOx̦èR…9X×[cÏ-]çŸw¾ÉÄïGþ›Æ› YÃìÓcFõc_ù•¢ˆ¶ƒôW^A»Ð¹ "Ô‚ª2½raOÆ ñQ¿v¼U?> ²½–Í­Ø^ë{Ã3×½Q˜;ˆÀÈ1Ú·m·ÙÎìÇËö+¯æv´Ó…ûÄY+¨y‡ˆšªÃL¦‹ÈÌ&vÍßÝ’Œûø™gž5ûÁ„‘9)‹fšaB Ò±ÜX†cï[àÒ(õ1*Å’¦½ñÎ:3®ðØ£ŠÜÿXªãß?±›Ì\BÇ{›í¢!A–àcòÆç©×e`´qúôé™ýIs wÁW¼ŸÈãeҤÌÄh ®˜²}<“™EÊiJdÑ%)+-'nƒÉdf‘r‚YtÉßWN?–Òòùwg^xoþœsçmšýz\³Žy–Fß϶ ÙTÚí·Ý.²P¿F\z™ÎRUwÈG1ê½]›èõ×õ÷K+3_$¾V‡¨ÑÉtÄ‘¦á.ì’7û(m-Y—D>œóí/ÍOÌ3çË{”'f®0Õ<®ñnžyk­¨ö<üv F¾›Ùqb2M3úÊOƒG¶àE|Ó¼è«Néx7Ò›ŠãB¤ó³«È/: Ô«SÍ5ìTõ[9ÊH>Xmöojm’ó›W`]Ÿ•"såÑ71!y#ÚSÁÑ-Žfeaw]Vƒ~ˆý¨«h7¼þ#HÇÑàLm|0˜RIÇ ¹ÙRwxŠC´p¤ºé¦-Hù«£Ž2ÿãÅØëg“- ]€ŽIlot™ˆ¦b®¤îÄöï–[GK¢R™Æ$L‰õÜôÏ+¯¼¬ŠZ!œ~ê­üàÃãã#ïÛ»ï¾Kñ¯s¤ŠãhTA¿×£_鵎È2ŽXîºûnIOB€V1B7ÏC»vß}÷¹¶ Pš+ÞqgN5» G9>Øÿµ)úß³R§–Ê”v0äÙ°$ŠnC$×ôËÆ˜±üï»OGT4)äæ²cŒ’#5ç‘÷>Ô‰þÇ´j¹Ç›ðú¬™”nò¨ÙÑ–e’è ÖK§:1«Çvøa*×éLJsÌ`áý€TXG¿Ã7ã‡}×þ¼$>æŠcÌò«ñœÇˆZ!|èÁRÿõC¥Ð+ʨ훶Ú¾/ÉÆúdÔSÜGX „K©ú?ôÂØ¾ñ¹ØlÈšâ•+VšÿUS”YÀj=PýZÀyó@ŵ 1é(². M„ íšÜþqæ•ñ81¬Uj7âíù‹pæVDJSŽ£=:mº{žq›Å¥5ëØ…vâe# øa­ñO=Öb :k¼›²Yn{®ƒo¿ß÷  NACv±6^Öj5V–Ø•×ʵÂoôCÉÜ"º$Ã3uÁ] -¿fŸ‹Žw²[óŸ6þÃf1ĉï3=ÁšDð ¾èC?)ÇÁ$ö㥲êZÎŽ{£!£¤àÁ‹œÔa™F§ CXÔ ³˜/L!Ó=×”M5¶Ýn;!ÞnÛmäþË-Ó`÷ì¶z\&ÿG î,]ˆú•E#øÇúeØIýÈû.ßþvX÷ X´(²ãíÔȆ`Ì^òxQ:Ó÷ß{_¸yÔ耩¨äE;ò6^¤È‚lî#¾k6Ãu×ß ug޳^#-×fR:Ë|é§l´'‡ÿüçáÏþ³vð¡#ê^ð¡ã Ž‘cyÍžý†ˆ•©å±^5]”Íû:sý,úTêŽÓ¯i[Ò†Ræà¸ñ Õf3Œ”)è¹&ìì.išiy 8°þŒ²°Ë2.¨äH'¦Î¯ý$ÍT¢3Ä%ò—ëaú"Öô4²›3éÔßr¶F&‘ˆ˜ôP:®èw'dxÊbèÓË^#OLãCŸ^t—QµÃcj)ê‘¿ú³ëEnìĪ"‡˜4)¾ŽCÕåØ«È•8,5A=À3D×û+Û€~º×hz¾´J—§èCû{öêÕ)ûß{7­Q‡l”?w,wbÅþ?X%›²ž2<ö˜cÃ]wqZpÓÞ-²ô•ÖvŒDz~Hèõ£ž…z¦ÒñY&¼ÿuهʥ~l–ɼúöÚѾáãî;¶o‡ ì(¶«ãÑ]àÇîÜþ¢~ë,<ûõ×Ã×â‡äò»wë~ÛÄñêè((’ïøA¨3ú蛟þóŒ=‹H ¡(lˆJÿßhû¬$vl– \2–) !Tu¼±Ü€ùB:,Àˆ8`ýéðàÃIÕ…ìbIÞÚxW¥#®dn mÌðLìO˜­ƒ2±ˆ”µ~qIæ—ŽÇ;t³)ï_x÷ñ÷Ñ)®ñæû êfâ¢X¤Aý>çì4rN*6š=ãô3¬þãý¼Çþæ7aì]w ŒúWÕßòùª‚©ß2SBÔå/.Éü’<Ù¶eé‘]Í™(£*£‡lÑ0a½1ذy§awÝâÅ5Þxø&±U_—4%v©dãÈÍÕÖ­['pèÇÑ2*&_n4L5õ÷cÇû‰'ŸP9ÍF¸óÎ;M&v5‡µÈ ? oÙ•2š¡yõ•42é×x—+Il{û‘ìûµã ?˜=OœžvåïÏÆúúO »+{õ¶>­•–§šgü.ÒN¿cQ01Jœ|CåõØJbŠˆÄ_®ÿôÊ;åjG¸(7Q”âÊ¿‚+C¡ž³Ní¾ûÃÿxM8ê×écösÈ®”QA³lqÞtgôÏ;/.sÐÙd<)!‰-ÛãqàìŰfõÑ{ÓM7I¾³çsì§mÔÈÁËs¡íÔyÙ…»Í‡Å¨ÿãRÇkÄ·t¡}Óe5ÍÐóG?rmažò!w^¶ÔƒOòÆ;ãÿ\bŠÁ~¬•ÇÌ,ý``?G›HG¡ÔÚ¢ IDAT¥¤o¶¿‚ÞBý£Ìg¥“R’Ørù;U¢_§škŸ=ƒ£ÿwÞ ²Ì©jc¾ ,@ß»šÇëtyžjûÏ÷æ“!mÄŒƒÊ«ÕÒSà×xWò[c!iŠ~õŸïIhJ-ºÖþPH»åògšRø%Ø?uêT©k¬¬+)Œu*¾ŸxàÏÓ”ëYuý“e±46:Šm"°£9®›â;fý&2aÇaâÿ÷òfÕ—PþŸ·þŠ©æQe«åŽÃh„³ Ça`–•/©Ì,§%ãËjõ«ClÈ7nŒ£¬M·«y«d£‘X±±ù†¿8BŒ¦Îç£[8Çü裎–/íutØá:}³x&(§šcDBtê¦|äÀ­‰oÚêw5·ÝàcÇ»+öcD##Uö—Üäü_¢% ö²ÇéÂÒñŽuæ×ã¨åÈQf“ÊŠ¹wþ÷ÓÜÐÐéËN²òÿýò—Vßá£DQ‰oºóiGÞ4RL\¸€k¯›Ù¦k´ÖÌYQf3°ÃŒòèÿ÷Þ{7à~ëcˆ©¯]·N>’@;;i¨;"™ã/v¼)S7Œ¹wö}¨»äè@úzE¹Ø‹F¦Bë‹96m[Ãzð)Ë¿h?ó†YÚ&¦0Èß‚ È³Ø ¦Ù=¶@äÊ=£H–½QJäRóÔÿ/]æfÉÒ«\ Ú[èÀ,&,‘*^6â½MGñLÅ3V—?2‹¹;cSG7Ó¦¬Ýå†IÛ2EBÁ~'¡ôþ‘Ñ:¼3\ݯõÝã[ö?6£d}FÈg~&Ú-ÿüS<;çZÿÈËŽyßýökÛþú÷Ok û4´1.µ}uù·qÑ–Ëßû¿JH[ׂDþª‡V°©æB/H[½:n®†i8Æ¥Û½âFDú’}ÐAeâO=õd©´-ˆÕLYŠ|ªùóÎ3 ¹a*íì¸fFù5Þ¾ã)˜œ4¸aüTsñn†î=øQÀåÎÌP˜jΛFÖQ“!™-TÉ äUª·‡#Þ`ðÓ„Õžfà9Þ×þϵ–¯ùóµ3¡BUæm·ßn ¦Þã:GŽk†}òeõ·~+¸ïfÀ9¨ÌûÉñ6ÙaH£¤(d„Ǽu°.8_§H+?¹RõJr’ªŒ—IÜqb¾ãÍòçÙÚiª¹neù¡*¬Ý×vªºÊ¦ô7ÃÝw¥µ«Ì+g Œ&Ç]‰,\`þgÇÚ©”ºØ£s„|¥cXšá‚ ÎϳÊ3ÃRÇ;ò‘-uÒšz*@$\xá…áŽ1N›ø0Aaúôva —Ú£ëàÓqã1©£Í†tö gP£Êñ_äuzŽ?!îDÞhŒ(áºöZÔë¦èŸWQ¯e3’Øác½F:øA/U@5u$MóŽãpÉl 9*,.SXåG¼õ£Mª;èx«Ä=÷Ð¥6°ÏâµnÝ'²G:ábÝM²î™*^põˆ?È{!®C\°p‘ù?Õ˜°…Y í;Þ›7o´)÷^pAÐQŸFèÝ»fµ _‘fžEd¬ŒE$ötàÔþ .¸Àfkôé³È`Z†@bÿøSøÐ úàC·¾ß3Æ\䨖mr$ïœS PÉ !C¸ˆeª9Îñv»S.Ý`¡ & ‚(Ž!©·Œ¾UìGy÷ìõ£ð[‹O†yÚ#’!™-lÉlÈÇs,5ÂúÕœ¨†eé\Ä2Õ¼©Gí™XL¾Ž1žCØäð¸Q'ôOŸ6©JbAa!àx*¤ÁïÖ[õ(­DwŒÉ0IŽHF¨#øZç(¦¨¿jFe€·_ì>¬mÈ?b{v K»²ö-*@Úä¹§6}ÌYQ¨Ü qvšÜ +㞠ί¿+öãd\Fr£´‰Qÿã?nâ©þºëÒû…Ð)‰Æ,»³ÎÒÙ$3¤\ŠÑx¢æ³Îô™“¨!œ{î`ó1¿Øoƒ³4¶Ûn´/¸cÍÎ?ÿ‚¢Êä P¼2å47î”ãÄÈÇ0ò¦@ $34z†p’7GÅ‘ Él¡HfX Ǩ£:¼9*ƈdHf •@2Ã9FÕäÍQ1F$C2[¨’È1ê¨Ħ8²{ÿ¤ßÒ°|Ùò°lé2[Çe!˜þýá‡Jxùò¥‚Ç$,ADzàw‰Çôžô»“v'Gš¬Ò9ýÌ+ÚnÞW·Þª{Y¥¬à×tJ ™!eZzA8ªÉ›£bŒH†d¶P $3,cÔQHÞcD2$³…J ™a£Žê@òæ¨#’!™-TÉ =9x“ÊPÖ<¢ã­_k0%™—²´ÂI'Ÿd !:¼¼¤“†QåÝÃŒ3ÂŒÓc8ÅQñ ËÖxcÄ{ˆïx‡0奩ÖyñÓ×xàþ¸AKÓ¦fC?àà|b?vs|©‚||aÆUÔŸ^BkýïÑ­‡ä×w¼%ÿÍF˜ýÆlI8r¤N_^/Ójêä¡ýŸMs‹)°)œtÔâš½‡~H¾cd üô¾NÊÎÌèº5ÑH;vì_ôKûºµÒ1Ó4ú2(;CW+È(/høh4좋ÃGkÒîÙX‹~áСÇîðò›¸¡“–¬SŽ'žxÜòÇãéFqM9 `ü¸ñ²K9>êÜqDzӷ~åm†Ù¯«½œÑ;ö®±ö£,ý¹ãøXƉM\©ãh#d£¡Öæ€dÑY“NF¶{í­™mÛ¨ ÇñT]ó9Œ)íqŠ>><ÁÇØâo>ü9ñkÖ¬uf¡>âÒòlÊo8­jª9–)ðk7óòØãÑŸq·s|”„=x¸â…íÏñ'èÎâ+V,·ºRw6m¿Ý€ÿâ}ÝçYÒzS̖еÚÔò•2úyÄ¢§_vùeÒþaVÏSN3XR 1ˆC «•Eÿ/¿T7yS=ºìÖ¨XÚ–´ËØ“€¶b½›< fÎ*µ¿Ø‰Ÿ×œ¹ó¼¹seo‘9sç†oÄÙ}ûîæÎ›æÍ™€÷Ÿ² ELŠš;g®È“pîÜ€3ÊQîûõÝ?Ó=š†)™Ó–)1E«%ëH½ðü™9s¦>wÜóÇû IV¨3úåȹ8å½;}ÄKly%Ctb`ÿzö*ªÏìiòKV¦<Ÿû¥ÈqdMœ~²ƒÌœóBÎ8ãtisÞxã i+Ñ~àc&Ÿ×˜Õ€w‚vWgìÏÓ¦NY6â]°?k¯»uw+gÒ£+µo-­£ÏÖfÒ¾=ö¨¼ ýlj\ˆöM;ݘ¹Ã«ÕÚœ6šl4e£&¼wÌœë¼_àÝb†,a:†µþ¶‰{HÛË=Pê˜ÆúË_þÂÚßwÞÖÍÊR…á içôýí'6ŽÂõÑÇlè¸lù2fIBÚ: 0;ÒÕ²ÈÏÊæ(r²H˽B¨/Ô±ýö_ /M}IÌ7/{l<SÍ㱡ÌÜÞ{÷²t8ZÏ^K–,“²¸sÌŠŠYC;€v‡íê1tãô´7sçΑö'ò¯d)ELŠD€!“V„ÊÒž±L)bRÜ  +ô¥,íË”"&Å "ÀÊ*BeiÏX¦1)n†z‰Ë®»~GÚ+ÜÿÅ«,B1i]x:‚Œ¼Xß}àÏ oÌ~Cž•hÿz÷ÝÖÿòm¢¦aÊ¢vwO©È›â`hiË€²´g,SŠ˜7ˆÃ²ZÃ(K{Æ2¥ˆIqƒ04me@YÚ3–)EŒŒx“)Ÿj>lhìx“BÀh”Ž^4d÷?•”Ö³‘,†‘ä®7¥5ÞçŸ7$fŠTÙ±Çc 礉 ]ϽÆC­æÌÕ5ÜÔϵsÔë§šã£õ“Î>â|ÉN»Š†ðªÛ}ZÒd;¢côF®8&€yŽF”sŸ³|è׎7hJŸôPÚ€úa šÿfv‘›àŸœ²¾T×EáÅF¼a?Ö’Q¾ÏäfWþEË:²4–©—‰N‰æUýÔ£Gwé°UéO¸d¿àè*†1÷8N þ}Z.“b:ªÊ'ACnòÃü£ã€—7ŒÜõéÓ;÷›ãÂ-·Œ®èàjz¶?ÖQÍÅ‹ÊU«W˱¢(ìÿ‰¬MVF?zå!¼ÐþxûQ/‘žùìѽ›,ç`ù›mú‰2r,õr4[Λ>v¥ü0_>dû†Ô×\}M–ÏG6²}Cšn¸Þìg±þÙ½À¬2ÜBùÃFù˜á>vC6ZûîwÓÑ—ÀýëT>¾oÞ¼©Ô~ò˜Rä3yÒ•îÕ?”:Þž+Âb‡3&„o®†S>HOíÿ§Ÿ)þN~*·¿|סV,ã`ùÃ^Ô?}žÆÕbû«ƒªSÊ¥à;ùö'µ.¯T\’ë³j“ Jf˜(2ªs,Ä!t°ÉL”ÆcH®<4ªsLÄ!t°ÉL”ÆcH®<4ªsLÄ!t°Áœv(ç™Ü'‘ ßE°„×?žß¬[¨×Ò&ºº‡ç8bY.ªq¹Ë ŽŠ]hT,VÁééþ3”£Öúáç+çÍ"H®Øa4g[³æ#«Ã/¹$'J¬°+ N?í´ìa$¤=\Òºìä‰übj$G¾Î;?ñ†LtD©/¸°Öš¸9sÒfcBÄfXß×'xpî5-Äù¿šN§;Ö˜/Ég3Û½¼Øq´ôîæŽgúIP¶‰ã‘âê±f¬•ìibsµ¹Íiɨ¶êÕŽ÷èј²©//¿œ1ÀÄÐ]†é౞éö?à§Ê&Ž¡wp3F¼ÕþHœN}²ÝKJ³°K//ÚßÚ¼)ü4îH Ù8BÊ4ô3m14þŒÐ²F £§¸°ó,í}ë­·7 #Þ±þ ¢ÂÿØ€Fìj¢ã]>C”úqtž‡œÛÍŠ Ã_X7™ÈÏóÏ‹#ÙL× {îµWxû-Œ¾RzJŸ!CΓ£Üï Ù‘vêT!¥é°»¼æ½n¿]§hBýÿ$F¼£ýS¦è(RâÜõ«¯º:|ÿûß³2W¾¦L¥š4I?r‰¬ÂZòóÎ;?ø‘èßsϽG“½þÓ§…_ü¿_ÆÉ~L7ÆyÓï¼£å„4£oÁH¼ò¼ür<&Ë,‡^_<Üê#ýÿ:Tvƒ§½ô?6ÜÁ\ØnƒýXnú…ž/Be³»xÿc“\þ£jNÿ8îÍŸOÏ£†°!¦ô?´AŽ Ä&,RVqº5àO<¡TwpZýpÛm·Å’NºýæjS¦LNuÔQ¢ õïœAîË|EýwÉ*A­iE’b>ZõÀþAîÜRoÿ6Ú—¿”Oô¹ø¥ÙÓ¦éùôЦ¾rmŒµ¹”£´1w¤ ̥ןå¿Â~úšõ_õæ:€3æŠwµ2“nô¢~,í0=®ü«ž?Ó¦¡ã®6"C™ŠÙGôÛÃÜ&}þá¼nÞ?I[ˆõ:NÍ®äÀF|–6Â+/çgUC„~¦ósÏ=öØs/›nOû±Óö¢6'”-„bz Rû¾C×=izb¿¢ü‘R>,ƺˆ÷š½÷þ±ù¾Ãl¶ ªIõâ# F„qlõþÆ«wÞ~ËrŠt^ëAÜ1œöKùø{¡Ñ ¬m,Œ·¡¶Z›Mz¡l8V,äGMnØàO9ˆ"c€x¾Óª2Ð9èFŒà;¡êG{ ÛÙë¿Inãä•÷_qÖ-ÄL‘'Üc[€ŽÉ¹çž~äÑÿõxòˆ×¿až§CôC‘¤Kï¿øÅáAŸ§–ÃXîåûŸöó>3ís—Òòú3Ž6öç©óX–ÞHÄ24‚µ~úeËïß¹çÊ1J"…¦¢Þu¶ü± )ê͹ƒÏM5F·ÂóÏ¿ 3ý ÷¿´‰‹%~f@BæŠaF¬ëßVô¿rÏ•cm<ÛP;Ð/Ã8Y2‹PÎECNêahld0Ddd‹3WÄÈI½ • †ÈŒlræŠ9©—¡±’Á9‘-b@Î\#'õ24V2"2²E È™+bä¤^†ÆÚÒåXïWuÓ ¿E 01írR/Cã'ƒ!r #[Ä€œ9ÆÐ1ž1}z˜5ëõ°~£®å¥^†!Ærží=þ¼€ Î>pgWgš,b€ðbS/œÙ^5“œÔËÐ2NCäÈøÚ©¿8‹ÇV=LüÈz²gAxî…çeŠ%õ24-N?Žå€-ï¼ý¶Œ:²S›aMT@NêEˆMŠÐÃN£ØänËSw£d kó¨«Ò¿lÅr9÷•W^ ëׯ·Mˆ×d*&ÕY2E:#[ÄÓ §·?ã%C†L‘Œlcˆœµ~uý` ‘Ù"äÌ1rRï{ïýÓfÇà4ŒwÞzGî9%øä“OÊzonPˆŽÓ‹h‹P¡1G‘“ú ¡¦>ã£öŒ™3ÓRã5 ª%'õ24N2"Hæ %t1–°`°œª’'³ÓC/ÚÈ^`û;´d°9‘-b@Î\#'ífh¬‘AÛÄiÒ&bí7ÛD¦~‹ hL3ÇÌ,œ|‚Í/ýsˆœÔ˰¨ßâ€ém ÜeŠ6j\ÔËÐD¡,'ãyúî»bGF¶ˆ&¦@Nêehüd0Ddd‹3WÄÈI½ • †ÈŒlræŠ9©—¡±’Á9‘-b@Î\#'õ24V2"2²E È™+bä¤^†ÆJCä€'¯ýd­¼WΜ9#µ‰9{)ÆôÔËÐÉ`ˆÈÈ1 g®ˆ‘“z+ ‘Ù"äÌ1rR/Cc%ƒ!r #[Ä€œ¹"FNêeh¬d0DÀæjù¥<9g†+IÍÓ—cI–@þk_™9¾¤4`©õ'/ØÓ¥ÂwÕ¨äËÚÿZ™’GÊËêZ$g¸­¨ÿ¾£jk¯ÿë?^Ö8j2êæò®æeo“¼-п±ýbßV”¿úã«mÿ AƒdGjìJÝ»OŸ°ÂÞûD\ŸÐ»Oï°Ooà÷ ·ŒÞn« »ûÅÚ{úˆ °ÇÛ;z‡Þûİwoµ§cÍmÊ3È.–ÿ Áƒ¢|èP]}¢ßŠú±Ÿ‚ÉÙÊú=ô¿ÈÝ'– lû’~)Ÿ7;;ìðÃ,÷EýW^y¥ŽÌr …ä°`kîK¨ÿ –ú·%ûáú:f½­ý‰ž ¬½¾9í«PUþ)Õ– T—ÚÊò‡t¤4h ÖëÊŸõõ@söÙègͨß?’?·TòµÿY{'ºXÿëúëÝ—ÐþªæZ¿ÔâÏÈÿ…Ž·kT 4@õê=”åA8r6ǰC¢ãu| :=w$I³9.€¯ã3Ðáó1–à<ÁIT°C¢ãv|Pë/”äWÝÿ:‚¢ÓÕÒÎÔ®¸K +k øÒËŸ#øè|ß|ó({“æÙ,XÖ!Ññ:> øÒíG|n¾êõ/9Öç:aaÃJNŸ”)²6•[§p{Nœð’¾Šö{{ø‘ˆöa«Ú£ëB‡Ø~"ô‰³Î@„‰1¼#§úå ¦³Ñ C†ü7l!¤ôTÑ8`+Ÿ$“Ë£8_ÛÚsÝu×I¬Ä’¬Õå…ã”ÆÝ3^6>E~¹a©ÒËúóŸÇ¾¨ò/—iµý°Ӊ˗³«L4Ì"·é(‡(WžÖǾ(û‘ =•åÝÞ~Ô‘²|®£Y•ã3Ѐ’7¾Hû™]Ÿ›Z¿zE|âCgYØ!Ѹ²»Û’ |>Vû_]'>ñŽqu%lá„h€°ûXíç]c;$:nÇg Ÿ©ÿ ïìÖË^²\î"¨ʳUæ"¦È-ñŠÄåaÊI¡R;æiÏ-é*{”‡“$BJ혇¼ôkâ(EÑ£c¸%u½þ­Zµ:à7ü^¶Í‡¨—áç§¿P)b”zv^ÿªU«ä,Û~¿ï7SÊe$}íðà açõ›|Kj€‘¶$­ÖOŸ1ܒǺ^ÿ …£Ô˰ÖäÞðžëšÿq”ä¹çþ·nÎè6ìÁ1L‡~˜l‚¹iS:¶í³Öï-Èajb¨Ô<æStÍ~/!‡©‰aÒÏöº¿´×ÜØñ‹ÓŸç“±Z¿–”+/  ³bØ2i 5Ióâjÿ«oœ‡ 4À;Ìù¸€–(Ó0Tž<æÓÕþWß8h€wXí»Ç n‘(}ÆPyò˜O÷ùÔ¿Fjˆ¢2Ë>œs0ƒE9ˆ§…œ‘åR’Ï ø”À œ£ÖŸù£‰ñÚÿV}"9ÊÝ|1Y©y«ë_æ±b$ÆëúW¬H™£êúgî0 è0‹çõý—ùÃGZ›ÃGk>’S êûϪO¼£|£^À“yV¡Õõ/óX1ãuý+V¤ÌQuûoî0 è0‹çõý—ù£‰ñúþ³êÌQ_Øýg#ÞN£d(U㳜:É9¥`б)Þÿûtyª¤5Až[à<‰‘‹hÆ5ôÿ–¤ƒÇh­ŸþóÞªý_ª2枢¿÷58Kdw‰bR­KPâPYˆŠhÆ5ôÿ^"¹—´&ÈsW*Š ¹¤Ô²xÍEžÄU뇒×Ý›‚²…VD3®¡ÿO¢jÿÓKê“äõyoU::2ä’’g½ç‹<‰«Ö$¯'(º7e' ­ˆf\CÿŸDÕþ§—Ô'Éë òÞªttdÈ%%ÏzÏyW­H^OPto ÊNZ͸†þ?‰ªýO/©O’×ä½UéèÈKJžõž/ò$®Z?<¼ž èÞ”(´"šq ýõEû_:Þü ’™ÈÜŠA—aG|à-Ñ4o£)¹c.ŠG«õc6AtHæ—Xf%\¹ RrÇ\!ÿŽVû¿ö]ÿâ ‘Ýõý§mC¹½)bêöG=R?Ý T!ÿŽV?êçOýü‰7Dv_ÔÏŸúù“fŸ¹>^?¿øç¯ñöEaýl‡ôwu|Eð(ãt¯F7@î…øg)*—DéQëox—˜kÿ›[Ê@]ÿÚW«A˜ïˆöˆúþkïÆ™#â IDATúþ³šRêû¯}ÅᦡùŽh¨ï¿ön¬ï?«)e ¾ÿÚWÞhõýXÝ¡[<¢nÚW£ºý±šRêö'V×ñV/™¯ü½ç‘r:„€Ç¿£d1âòvN¡R<Ý`ÈíjÿŽ’ÅˆgHI)TЧl¹B@ãßQ²ñ ))…Jñtƒ ·C¨qü;J#ž!%¥P)žn°äv5ŽGÉbÄ3¤¤*ÅÓ 6€Ü! Æñï(YŒx†””B¥xºÁÛ!Ô8þ%‹Ï’R¨O7Ør;„€Ç¿£d1âRR •âé@n‡Pãøw”,F~ï{àøBóB<â‰9â‰Ñ„¨$J‚ŠkLþ.1ùE1Ñ0PÙAQóUPŒ‰MŒðA‰‚û®QPpCeGnÿÏSÕOuõÌ\¶Írçœ{«ºªºª»º§gzBjJP9žo¸!”vA5ŒÇÉB¤RS‚Êñ|à ¡´#ªaü;N"šTŽçn¥AP ãßq²é„Ô” r<ßpC(í‚jÿŽ“…H'¤¦•ãù†BiGTÃøwœ,D:!5%¨Ï7ÜJ;‚ Æ¿ãd!Ò ©)Aåx¾á†PÚÕ0þ' ‘NHM *Çó 7„ÒŽ ¨†ñï8YˆtBjJP9žo¸!”vA5ŒÇÉB¤RS‚Êñ|à ¡´#ªaü;N"šTŽçn¥AP ãßq²é„Ô” r<ßpC(í‚jÿŽ“…H'¤¦•ãù†BiGTÃøwœ,D:¡.5¯Ð'$®Að*“q‹ôÊ0‰栘괄ºe?(]H¿é•a#ÌAËÿtù4 3„Bé•a#ÌAËÿt]Ûjè‰ÖýOOëH3zQNÂ$F˜ƒÖýG÷˜OÁ0C(a‘^&1´üO÷е­öžhµôD±Ž4£å$Lb„9hÝtù4 3„Bé•a#ÌAËÿt]û_Üþ¥o—Iæ¾ ô<≗0ò…Sb;BË>]•Azˆ0cÆ@â%¬('œÛZþ/ºLÂôa•Pâ%¬('œÛZþ/º¬åÿVûÛzþϵ¥û$ñVN‰í­ö§è2 ÓC„UB‰—°¢œpJlGhù¿è²–ÿ[í«ýw…k)J÷Iâ%¬($œÛZíOÑe¦‡«„/aE9á8vêx{I'Y÷2MŨ¥( ÈžÿíHYŰ)-3ÊNH˾;50¹Å°¢‹á¦‚i ÆDªCÿ?þÄã¡K—.¡S§}왳òTÿä?Ëp1¿Å° —eŠ 'äß°üSâÜÛ‰M3Uf”)Tâ`+ÿ­öÇ^)]½ Z¬DÅ0å*t4µ8»öü}øá‡Ã…½. ßêÖ-tûV·Ð§wŸ0iÒQ[ÙZ™â §hn'dY ¨¤¶eF™b‚<üˆæçˆo¾Õí[¡OägR.TTR ›t™Q¦˜pBZ÷åýÿøãx:íÓ)ÌzÔ=ƒ›:5gÌh›úõ|]…µü_ésUîÖí¼Ô·#jÊw­ý‘he3Q[™Q¦xÃ)ÚÕþ0?-ûÛ)©"«¶"-3ÊNÈ¿ÁýŸw¼%A)}Û¶l 3fÜÚÚÚÂo¼‘klk„x ´µÍÓÛÚìõµ×_“8mÓÛÂŒ¶¶ØÖ6=´Í˜.rmÓgýÆ ¢©Ñh„xPä öð›õÈ#aÁ‚·ÂÆK¶W,_õ¶…·Þz;¾‚$·/Z¼Xø°¿îÓu1m0çµ×íOŸ‘l¡ÑF:iÆ ¶ÔýÓO?µ4-^¼8¥%™+Ù׌EQ‘Ã_¼Ýb<=¬X¾Âì¿õÖ[ÑBÁ®øeF[X·ù1wúÊ+ÿPÏh ï¾ûnjýyo¼aùwá{ª‡ÿ^™=ÛòºèƒÈ ¯Áo(—è³T®ð¡–?Êx#üÆ‹tö Í“¯1 õÏN+¥¾"¤~ÓåeÐŒ6÷ÿÑ=޵ZM~=zôˆé¡‚¢ávlEaí‹Æ/(ÿ¼*L¾gr6tXøUïÞáæ›oóæÍ³úß$÷É¿æêçÝúõaþ› ÂæÍ›­xL“ü¿òÊ+V'¥^3‚3÷EùÕªUá‘Gf†1×^+/ñ7ÞpCxÿÃxß0}ñ×¼þ}‘åOw¢@˜dMO21C[QG€ƒñ—ò¿~ýúðæ‚ùaÓ¦M¦îó´ï“g øó›ë6¬óç;|ÎöY<Íò? _¿P«Õ­]”ö±®m¤Äq u0(©üÉÆ³uîܹaõ'«EžQTRöçØŠ: l„þ¤?æ)æ4¹>ÿo@û¶`¾´o;“ØÅ½°`þ‚°iã&‹¢é)äGS™þ{gò_åÿ¤LËË© xÿ[µêã°víÚLÌNø³°ßã»=¬®É3ؽÿ˜Mìÿìg? õZ-tܳ£J‘/åúçÕý¯µ?tùà3¨ÿ¦  ´üѪV\õ°:ó?Tÿô‰ÃÄ< ÷ÌêÕ«µ!¬×Â!C”ëdGŽi e¿~ý,v¯^½ò\­& ¢=ðbxч‹$ΖÍ[CÍ=Ù2X¯Ig`[c›ÝÌÓ¦O3Û‡~¸Ù&Ò6}º=`ß}/u0™64Цâ‚ýiڠ良¤²õštŽi㟆ôe#„b~ÈR0ù‰i|Ïåǧá+`yúéOêY†÷ï¯/ȯ zQ½ñ1ëK¿ÜsÏ=‘Þâ7ï#G_Bç"ç7SÚ a& \Nr!‡:ñÝC©«Âi§Ÿfù¾ä’K왡Š)XÕZìži‰Eµ„NUNr!‡:ñÝC!Ü~ûí¡}»V·Xö€¸¯tÈuèÎÅ ÖÈ#ÂÁÍ|Œº‚­aÆ…õëÜ`MTêÍ|ù+_ÖxõZøéyÕõzgÓ’ÉÑ¡cæ$rèĉ-?ð—oSÎ?ÿü°~}9_΄µe…Ê&"ÎL^érF¦n—ÔEèä$r¨ß)tóæ-aĈá¡k×®™¯=äëa欙™ŽÜL#,]º4Üqç¤Ðõ]CÇŽe#‹°;! Aì`ô]½'àÐÝ1‰§#F„ovíêêN=úõCÃ̙ɹrèîØÏâP¡c>óì³–¾ýöß/Üyçá£FŽ §vš“Ü=ô¯>ºÈg>»uëzè!Q˜'É…º+–Ÿ“üh‡»sçÎáÈÏý„Q£F…ÓN=½¤*7ãBe$´o(Ó¯|°ù í:Ò¾­_OQƒ[P†{AÞ‹âóôÐCµäf\È¡¦lwê"tz@zî¹ç$?í:´Kœ ÙÄÜEŒº"D½âó§ï%—8eÌ›B' èçŸ/ñÑ>ìÔEµ„.IÚîÜ!å´ÇžeŇûçP!tÚr’ 9Ô‰ïJ]„NKNr!‡:ñÝC©‹ÐiÉI.äP'¾{(u:-9É…!ôïß?´ïÐ>´oß>\yå•.–¢O<ñ„<£:vÜ3ÂŽaÏŽƒ†÷Ú¸±c+Ÿÿ¨oÿÊçO–Xf‹Ð1s’ 9Ô‰ïJ]„NKNr!‡:ñÝC©‹ÐiÉI.äP'¾Ëh>ãmÑU»u¼kµ0dpìxG™3fÄF²Žëylزe‹rA:i| ÅCn¿Îƒ‡‚ïß9,[¶Lâ .åQA<òÛ²´ rlˆÁ?þø,…Ó¦±ã]—¸<òˆÙÒæ:æœ%C®ØñF'ú™®ý#®p?Kt½üòË1}u™y³DW¹Ò0Ô{¯N”tÏQ×ô˜úa&óÚ¦¥„…¾ã-J²"¾ìоÍ&Ñ.  ‘«×¤á°r !¼ýöÛVHGêx‡páLÑúHü·ßþaÿÎû™/ÑpèE«)¯‰’h̽dI]…¨“7Ôò?f.ÇŒn›81¬^ýIn¡¤.ç Ý1 5Ä¢&JsuªÜKFs$áÝ1 5¤©}¬VaAy£<îÑ#´oßÎê^b¤Ž$u»”Ìfáþ§ƒnàæÛGÖsGÊ”`Xiƒ8z_Ô¥În.Ì’fÞ´tÒ4ÿyF|(Å5ª'5áÌ3ó¤í|‡íöíÚ[zGŽ%Ñ•D› C*¸¤¤Ü1 5Äl%Êçoÿƒ>‡~xª®üY¾Ò+¥tùòåaï½÷¶ò§ìi§êD“¼e6!†9)£Eƒ!·ÃúVKL™‘…b¶ÞÿàÌ’{ "ÿ ÿ^|âš© ’ð„böeÇåÅ AVVÏ>ûló¢Ôi6Ô’ýn¸!èÛ£cGkopŸ{£eÛ¤ÎH»’ÿA…ü4S§Ê+¸$ÈÎ}}ž<ÿ¤LÝ`¾¶W:¸ö «Ô¨Bî…#ܽ`í[ªwé^ˆÙeä4 tÇ4Ô³›(Í‹SõªdzÇ«5 ¢N³¡†ì´}<ƒ¯3&Lœ8!¬Y½Ú²(HRWAW&;Q¨O>ÁŒJÏ*—í˕ϟҠ“(rÚ 5Äl%ÊŽí—Òä#{¦ÐÓPCZö£¿’Gvßÿ÷ÝwŸµU¨ý.íŸ×¦FSï½7“aòpô5WûRü\ž?ŸEþ³D# JfC iÕ¿Šú'-ª_ѓܤñ«Kg«† bÄ2ÔvíõsŸNd9’/vn;´ïÀ’©0Í€™>¨^q/Z¾Û£‡=¨Ÿzê)¡ßgo]:†Ñra†XÓ^ i†8ÎÜÖÐéä(n#[ÑìóO}/½ü²ÙÇrk\”#¤lz SRí³ã]«kçÖ籦·¡ã­r䇚GÒÕ˜¥¤Üßú›K©¦Ú:ÞQ×ã?fù×¢¸ ¯V “ãŒ7lôºPW1`”O5iúi_CUÿIâ7æÊ²Â\jÇþ§BÄ'N˜ëô!/áqÊüïÚïÞ½»Õ•ž^B¡žiï½êi„$NHoáÚµkdï<êÍ„ ·ÈŠˆ­[¶„gžy:œrÊ)fõß륞ѣFË€ÏáîE³b´KHù2ô§ä®—ÿ”)S,ÝTÀö ^¤sоg]«r”)È|^%­ùÿhåJ™‰hסƒ îÀžI§Ÿvºið±‰æ6}(ILœx«Ôäówâƒe¡ÑØ^xáŰo§Nƒ}? MÔ@ØŒæ­&¼*VâB{Õó äÏŽ{†­ÛTG•&OóZî%²……ϧž={†¥K—ˆýeK—†O<1ðù‡%ø¼¨tâ„”-C•ÙÔzMʸ±m›«Î¿¨´E»kÖ~*û’Q¦·Þz«´o(¿§Ÿy:|ÿ”ï[;‰¦z"îyA=¸-,]ºL¶õ¼øâ‹aŸ}÷5ÚI\´EØŒæÓœðªX‰Û¬ü!qÇþß¡ê¡J“§y­ ÷§Ä?篑ï|ÆÛKx|ûöW®\)³”x‡m×>­cÇ›š©­ ½„Ç)ùÙåŸsèmzœR-ûUí½H¯-xë-{6° »Ô­ôe´íúü¨‡™3 ³fÍÒߣ3 ï½w£¸úÿóxþ¤œ0¥š»Í?ý ±T‡§y­ ÷§Äÿfýs3ÞÑ)_½fµv8±Ô<Îxc?(f<ëqÄ3ZÅ‹o,Ç(^æz·žVäA¹ªŽØ€=ãq?þfQ9ÝÍh³ÓþØc™¹li¶ÛË|A¯^ú’×®½ÝP©œÒ_þ‡Ÿñžn6v©Ê?ôL›vŸù€¾xìqäGc4ñŽ‰à²½±cǼÐ@:ËÅ ‡Ð_ùõ¯¹¤«‘fcb§üžÉXj®ö9ã,Šš·>ã ÎÖÂeˆZó…D!žãéô¼n¼ñÆ0|øð°|Ùryñ9ñē€dqÉÒ%rÐJ£{„ÁW ‘ˆ}x#GŒ Æ—¥¯#†Ç=Ç Ï?ÿœËLÙþã= Ž?þøpÆg„±7ÞæÎçâ(ºnýº€íÃG ›6m ,£F'xb8íÔÓÂ5cÆ„•+WÄxÞŽâ ß_Æ\{M8ëì³B÷£º‡sÎ97L:%`™¢¿0[ŒüÀÄÂìʽ÷Þ.ºø"™…ýå¯~žî‰­ˆQþ¸=æ˜äWP)Ý‘c4BX·n}9rD€¿6oÞ$ùAǘùÁŠ+Vú¤ ¾xÉâ°èC=ÁÛÿø“OÄ>êã%—ôÕxÎ>]:PÚ q7Ž ={'õúÂ^åzM£ðó]wý1\xá…¡çq=åe~à áž?O–½”Þþ 7Þ(åó"n½õ‘ÐugYX²dI:lhèqôwÃà!ƒÃæºyݺOe@~ÃËöÊŠü2-UðQÔÃñÇNGÝ;6Ì“64ù…ƒ29 ugXؼesXÿéºpïÔ¿„‹.ú…¬JøÊ4–ÀζmpõÕW‡áÃGØrÝ*ûG=óøÂ”ÛOq=v¶†k®¾ZüÆeÁI6a¨¨÷ãÇ7"–нÉ•kÿo?ÞÚ¾çžÅ½g¥”ðHêÜy?i¿N?}÷–;›fg_èóÙÕXuÚé~f’¦Áòµ+È“&…ðAÁ>žkl÷Ÿ{îY§Ò§k×ìãþÅsðŸ_ºõpæ™g†«¶vŽíŸkÈ §qcÇ…1c®±üwÜs)G´‹Ã‡vò=œW‚ }øððÊ+³UûÇó u õàw4î9>V~ô‘“nì÷î¸GGñÃÙgŸy»Ÿ<¿ÆÆ\;&tèÅl¨¤iØpkÿ5?ñž“vt˜<+¼µ Œ¾zT8ñ$m¯Ç\smX±b¹K³¢8…Ûæ<ó“ÕŸh™Öká’¾¿kϤ;ܽà8¸/­Èsh÷óÏ´lÚ¼)ÜõÇ»¥M<¶gOi¯þf`øó=÷9ËÆÙìÑÇêâu×]Ž?áx{ÿóåö[F¸’v®¿þzi–,Ѷ~ÑâE²Ôþ”ïŸ"m7V`×úuëÈ‘#+Ÿ¿x§¶­œ¼/Þy礀=ÝXe4 ÿðâ /†Þ½{Ë mÇŽ{0I\¸p¡KŸ§?>çœpï½SãóÔÛa)!ºâûï‡U[õ`+m2Í;0Í…ûŸvÔœI™ýÔìxÞÎÛ¥¤ÅnÙO.çDÏ0/™ÿ7®ßþªn•ûN÷îá¨£Ž”ûÔo±¥¯»îZáÙYd˜6E¼•bùcÝ¿âùc6[å¿K埄̓®Twel×ñÎ#CÀ/5:xHزm›6ÆÒ1«ËÁkˆ¥Ê¨2ÈÞaŒ\cÙ%¸‰C9#Ä|Ø`‰[Ò©òï-\¨ëz=Œ7Vˆ÷ݧ3Àxù=ëìË ¼È#ã4ê,íñ®c)vyP@­é¿O!ò²P¯Ëább (Z"DÇì8ÿÐÇ¥óH×Ïþ±¤Ýòƒ¥óXjŽÙðºÎàûQªù Þ²¬F{ãX‘…ÿÑAr)Ó¥æõšíGÛ{ï/ÉÌ:ðì倯^ZjŽ2ƒ)ÞþŽò—m–…Àz-d‡°yÇGܧYH…†‚öQþÐyće6ÎýɹáÐCÍh—]v™¨zÿƒXŸb=öiC9ß|ÓÍ™ÏhõÚµzU>­ @1Ì„R«5¨§çGòåþŸ3gŽÆsÿ7Ž›ä¹1–ÿÁV¬H/­8Har2,öNÆ•f³VsÛ$Ô»ØOˆ— ø‡çù«Êÿ‹PGâ€[ïÞ}óôE:–Ïy5æG”4¯ÿX¹Áí ¿ýío½yñ?*˜þ7æÎ 7Ž'ö‘ÞM›âÖ×þ`›ƒ<°P®ùÇLˆž`¬¹ÓºSÏœP.çžû“pˆø&qù€Ë$M8mWÓT—x$Zµ9¨ù_³fM8÷ÜXw\ù3ÿVw¢¹ºóØã‡ƒX¦¶DYëV&Ðþ‘GêËNÞ†ÄÙ‡ZR)y®×BÿŠ‘z_̂Ѩ.„ vê¡S§NaÛ¶mfŸ²7l í;t?õëwiä“ ˜kÇ9,g.±Í%4 h÷×m §º}¹*ëbؼþùqnà ’Lµ’P´D(ù?Ùp²‘H þÝ~ûï3["²‹ö7oÙ"ƒ;ôk õéByÂ>WŠù8Ò^Åö38¨^}ô‘Ì CîK_ÚKÞ˜Öå+–Û¶ l1à,þyç'÷0öCW]èÃ>V7d×.æqqø<^È?òƒ hæ¿·o¯]û»÷—ö–öšÏÄkVþköMÛ¼}+—¿$@¶{½eþç½ ¼ÝÈ?†¬ËL‹þ`ß©¶‰š‚sÏ9Ç웼Ë?Û´[ôóOù'"<õôS–wÒ5OðÁûïG~á9ë߸›n¢KÆüc0í×ËPÐ0x#—$Lï pš|¡üñ<Å ·¿Š%$ïz]΂ÜΔ¿VŒÝkŠö¡ËÓZöSi©_œwˆ ülüÿó^Úû7œ9äëRŸÐñ.Ú¿bÐ"‹÷O”“SLq —1Þ³Ÿ×󧘂Rš[õ/+Çöþ³Žw©z4²Ôœ -WCÇ…FÑ›]½z]_èÛÉ^iìå^ŽßÒeaÙòea–VÉ¥Åë;ÞUV*zÌ ÒîSO=)ìû¦Ýg ]ŠÞCÐÇ–šcÿ¹[2åŽHÒ³tÙÒ°xÜwî[VYj i©yJŸ&Øÿ“Gy º¢D~˜Þçž×ƒmð€.=,Î/57%ášk®‘¸èôàzãyÖ¡Áç`ìÂÁt7f?02 ›/½øR˜x›…ÎFê@çRsÄ·U íÛ‹à/Ì Šÿ¼ßÄÓ¦pöìÙñ…¢š¼é¬-å*š'_³- a}ÕΓúK§±D_ý¨tŒŠC¾Ü3>W}´JgêÓ§w„}Ây?=/Æ«…›n¾)²F³ÐÅ2úF×oŒlâÓ%xa%ý–[n±W±Å‹ö;´k/³Ð׎¹VFê‡M1?0u÷Ÿî’xˆÓyÿýeÖË9æÓwlÏcÍxa¤.ÂîÝ¿úöí#ûcAƒ.¼<ˆÇ£ÛG•ÅûÉyç…×ç¾®z+üïó:´]tQ¸öÚb~¾Ó–ÊW 6ôàžƒþªØGù¼óî;–•m„1¨×õšä4î÷†í¿?ü÷\>„ԱřC††Y³fÊÌ?ÊËæáƒÉS&Û VoÐ_Gu?*ÕØ¹=u§ƒìpù"Ê—q^=¬H rDè‚}ä„ÁgC‡©¨;‡RžöP¿ûöí›Ê4žS%Û¸&Ožli{ì±Ç­Æ$„©“§ˆ}è{S–Ç3»vÿÑ^vý*#Ú™2éÐû/ûÍÆöïÞ©÷ZºŸyæUSQÿÀàÀÊ©ÜãM£¤ÂÈdе¿þþ;ûéÌ:]¬ºŠò¹ F.ƒ„ÎþÔ{§&<ûteûãmìÈ>ê?ëÍÏþóðÔ“O†ß^w½ÕgÜsXµÃç-ô :TÛþ}BÇ=öú‡g ýËÀiÿÑG5;xvèÕ?:óGFÇÙ!r5BèÓ7 @bõ/êÃ!n|çÀòËfå«ùê`Ü_L7ÎŽÁý$ùéÍühûÏü`S|ï B_tñÅòÅ ‚ÓŸÅöºœ†Fxî¹çƒoßdVåNèÊŸùÁJæÿYÞ eæÿ –»ïÖ&"ï˜<ÁÀ!î7´‰{ï½—ä _´ } ¢Œ0{|Z¾  {÷é,HÛúÏÞÿ8¬qúöé¾~È¡áøµìçöÏ_è;ï§?5ÿß4n\eÖd;B|;è .aôèÑrÀÞ}h?Íx«£ï¾ûn){øwão¾%èó4}ÉÄ?OÅp¡ýa»ÃŽýU™H+W+hcÐäv­ý…²–}sbEGE$ÜIÿÿñ.}Cý \v9PúU3Þ_|‘ÔÜÿԺꪫ°¡CÃ_¦ÞÞÿà}{ÿpH©<;ÎÏŸR}*Ôÿ¢“Kò™L™ î¤ÿ3զʔT°É#´HŠÙ’ÿ}ÿ¯Â@Y>¢^²ýšË»E¥¸,5/íöEçB_¦Ð€¡³±iSúÌã@ ;·œ…c<6Œ€[·Æ™’FÑp޲ãТ§Ÿy&<ùä“aÊÔ)#ä°‡À^{I–`Âfˆ©ÿ“O>‘}àÃ2k\Ó§M7þ{ï-´l²éÓœöoqû ñRû—¸Ç»âp5±æo¡j‚÷O”Ð4EÒË4a ®<è± øXí`éoõ?FÒý…[ÄýÅÏnä½÷Ú[ò—.\´Ï=ÞX’ݧ¯ÎZâäÕïü=ÑáÜÂR=¾Ôk™¦òŸÙì›v|·9¿™Á¸ ö, c IDAT¤“O¶üa/¹¥‰ Šþy훼“ÊŽ÷—8 àЭ×_Ý^®¾z´HŒ|t±a¹1ýïGÛi Öc,ç ôá%âÀ.]´žÖj³=È?–Ü©ÎzøêÁ‡wÞIK, þá™gšM,_Ç…¥ŒÜËÚõ›]ey4ÓŒ¤êŒúûÕWuy';ÞrŸÔjá/÷ýÅ 3’=ܧҰ²êùY?:KÒàë?–#þýï—Æ…ùGœE‹tUì Î½ë:Ë[·m ?b~Ð!{4mû@\¤ßÿÅK+:±ô5¶H¼ôÒKL’ÀX$áà¯åõt´Åz͈àa ôâ„h_ÿ ³qÓ¦ðØcR\ ý|€ÔÍZwâK^èpá~aûx‰¤Ÿ±ìœÓ̰·N0ó‹7ÖÄÁ ^Né f.eï/ØûË_þb*±GíÛ?”)â þËýP¯…sÏa'HS~ÏžÇJ:0c„ 4Sªb‰þüEÞÙ}v¶ÈC4,ñG~±—ç{ÚÖ-[e ùÇË-ìyÙ¢}ÈÀ|öºˆ ôJª³¹ÝüË‹v½N=5.5o¢ƒ6 æJ~-ñ©¯ÂÖ­[ÃQG%>ÃRzY­óO{EXÔû åC?‘¶žuñShH†S’î·®…Ã;Ì8¢ÂãÁe(,m𯊠ØÇ~G\Œ‹OÚÇáƒz‚šÀ +ò_zù%{þx*ÿS¿Q „°<~©2"âäØŽBíé|Qå‡q0ù,8A ^²¥}‹×È?îí—^zÙòOÛ€Eû¨8Xöå^@ ì^€]R½ÁK¼Hz\«Ë žb$i í´¥+„pi¿K%-ȯ§õ9¤õ?ýén-¥¯"ÿ8ÐLâ×já¦8ãíóÿׇþféÁ6/ÿþùÉêÕáˆx¾,ñW¬XÚcv­ºvý†[Z¯þÇzðÐþ`‚€yaÂi_WÚ`©y³8ƈ°áBP„„VÁð$ÚGO/X+ó Â…`Ë>ýYáOÂû¤Ôëz-ôë?ÀÜ.ÏlÐâÊ1[¥NÙû±¾7³ngm¢µƒôy>,SÎ>i­ú— (aôN‚%^P›ñV9§;Ke± „¥ÈìÅóúë®O– K˜±¢á¤±ž úÖmétØXƒÇ—PƳS~9,—fC§„âEŸñ°?è~wbóB·ÇûCE¯ËS1¼m+_Q`7Ó‚ï}Û•»+’•hÿU2îaJ]þsb~ð¡<Øióùž÷5yZÉþI)o=$¦Ê>:Ëô#–šÓsÅzÊ/|ãWPDAyá¤ß&N¼M„œ!Fý¿åVÌ„Ç+æ§åÓ¦n¡hÈrpêÁdéÒücù:ýÛÄÛ„ÍFÄûíoõžTi]tÍ5WÛý÷2:¹ÞÿF˜qÿýÖÉEzh¿{÷£dPAÓЈK1õ–™ýB‘üã¥X~°T^œïdbiçýŽ}á­âÌWÆ,}3ã~ÜsÊëÛ§¯è@¹oÙœê5Òxú©§[ *½üòK%ÉKÌ¿ÕË/2ëò.lâ›æL+„~xÆ-xè”D¶?M§ý7B¸òJ,=ÃKi=È ¼ÿåàÄ6³O”áb™Â¿ý?,ÃO¾ƒJô¸`=Ofc4ìo„}ÔÌU_¹ÜfλâŠ+ÌþÇlêÞ[˜–×Ê‹wMå\þñ9'ÉGƒ ÷šž(èªhÿÎûKd¯e•îè'eÖÇQ›³ORÑžvXRÇ5÷EŒõÙG”u ,éò<¾cû¬èXøëÑYq«D½–=OU&ÙàÌbúTg♾HÂ'Ò°Š÷Í÷°'Ÿ|RÉm8b¯½ö²übVùÿ;õÔ ûÚqcûSyÖ†/Ý‘£‰²ÿ˜FìÓFýGç,kÿœ>k¯kõàW.©H#~ÝDï!,].Ü’aÈUCìž ÿÇ\3FïCg§™}Šük=ˆ‰—¸Ê*HL”²r–ß¿úö½$üãå—Kg„¤¤å /½ô×voRo’-ûŸå†¼O™Š™Á]»ÿ0舸УoŸžFUÿcRÑÿü +-ãzÑgmÓÛt€²^ Ï<ÏNp÷?VIYÕká¶Û´íMyLö1 9¶hWb‰‰RÖ®åŸq“2oÀãQ¢‚D-ûðÑ?çÿO?]0(¶b0‡fygÆ©æ¥-[ ù´ êJ—.´¿¨WÀÇßÝ8°ŸÊ:¦7>ÏçOJC±Bù°ÇcŒ R«þ©Sì¿ÊG†v¼ý¨†:N¥W¯^£%¾4â`’…ï¿0‡ŠÕ®ò‡h<›ñ®Õ²eËe)7N-•eÉqyr*ìðàÖ ‰ý¦íB§}÷‰Ëµ‘=æØ£Ã'Ÿ¬–v–ùŽwÜ«ƒ—J\ßêv„¤ëäï°×•:e†86¶H隦¥òíV,…×eÓø­(ú²|Ç[G­°wœ—J9YB€q½g »Ñzª¹ÚXø¾Îhwëö-IëÉß;)<îfÍÞ[¨Õ@íõñ ”Ǹ±7Ê*Ì*œ#{µôeÖÌYfKÍ‘,EF¹ó…4,/C']^ëøœ—åb0åB}@¡L—kYbiþr.×—%úÞ.—;‘ÿ$-Ây0úµÀ*>›†¼_v:O°vÍ{©ÓC«‚$üª.øAg¼Õÿ7Ýì–¹I–ò);Äß+.ç‡aÅŒ¬k½ª…—éÈèYº¨:q€TñÂZ|ᾤ¯.†å‘ô?ößay<8Žû?ÆÁAIH;i°«,uÏ-=ôׇ¬ì¸uÁKðþDzÑã?ÎìãEõ+_> lÚ¬+[üRL¼˜úüCßúuŸªzM–D› )¸†|®³ñn½5œÉ¥¨õZh×®]x /Rr‰p¸îú묣8vœÖëßÝ~{À8ÌÿLÙ›©©@Ô¹¯¿¦/ýqP þÀÃî”ïþðûß»Aµ$Ÿþª×ä0>PÖ¬YkéŸ;O¼‡Åö–pŠ)í/‘Oç%û¬ª=ýã 5ÔnIÅ0Ó£u§ ÐÏ“à@:v”o¹UëŽzFãà`3¤ñø¥ØÇþ3¦~–«d©Ò€¥óÅóXþ&K–q™/oŸv¤ÎÑNhÈaJ°A ³#½ŸÔ’|®(–¾Á»3öeï[œÉU^¯Ç£:wÐþ&iŽ{ÉëáôS1Ã¥z•“K’§T•K“ˆÕ< *¡\àGœÊ›TH4+jÇ¥ŠÄdÛ ÏùÇ2bÐQ·ÖÇïKW•¿uT;|§òÿÖ[éS”,<_å’„§´­þä“pÑEÛ½ÆüŸ}öÃÝwߥõ·^Óƒ%šÆ5Ìj–*“ RhÿÄN{ø:BñbþÙŽ"ý6Pêìc…Ò >¶|¤T©ÆwÞ~K:Ì·N@ûv¦ù패W4ð¿|žUŸç_ð³$éìU›Ê.ÚϹ*óúësV¿IºcÚ1ØqÊ)ßmâGÙ¬º¯—^ÚOóPO3ÞÛ³¯õªp˜ZvIÂ}ÌŒkömÆ»·{AL¢i\¬ÄB>dF›,ç‘ Î×­`ä#úСi…VZgßT–g«®¾BÚñ5 ŸIe´ÏÃùø5Kš³_åÓ±‹íÄ3#šü+¦ÜD¥´ç’Æ,5ᥠrNVP 8QÊÉúxμ3žùïk‡Ù¢^àýkÉb]UÆÜr²ª¿K]fÈ :9Ã<‹ãòÅÔ?èÄ{ ;òUþç ‹õ󉯲o™’,i¾$Æ`-Qž’=^¤/™èÿ¢ýÔ¢úÛ(zØW“¼º½à=ù–NêA_¿üå¯Ä¾Pz] ´vøÉ.ºX¡—3Wx˜¡Q8è )Ñ6ÙþG¼,~¼*ͪ Î49lL_ Þ_ø¾¤þï?0õp礴W ³0¼¸Ô¼}NŒíŒVù/$PWÓ‡"÷xSa% P"Á»´éÜãY±…âìÏæƒþwþ1æ­Ú§B8ôÐ¯Û ¥=\ã–úôâ_^lƹÔ{ZqÙ^Àz-à{­[¶n3;Ùáj±ãêª+ËS(K—Ø TÔ¿rÌ2EGëaÀezø±’ºY«vžÆß’NŠ¥%1Iu–¯ÐÑvĽiöx“©ðk뉖hp«.,Û£ÿ/úÅÅÑ’4š®3(“ùÇìÓÚ§ooaþ‚/¤q €åïgô§FÖïDã¡@Û6íÒÿð#¼/´“Fû–‘ÕøáùÂÁ_ÕeÞЋýpñÅöo¹%vê¢ijÓZ=ôéÝÇÔÈâ‡Î6`ãð#tI+è`fãÅö‡ydþ}½¦N’sóMãÑß>ÒÚÞ¨»ØŸLY¼€Âÿ— À¡{Õ×õ”zœÊøð?.ÌÄÐþãO¨_b’S•¡‰l5ëN6mÜdiEùC‘?˜Ï:QÀÃ?ãÔÃÓO=myïÄNŸ~õk_•XmÂ=¸ÿ‹ö5á j¹”„Uúcç ÷bc»Ú©‡þý0€P}A¶mÆ ó#Nì_¿aK*ð'H9>fàÿÓüÌS·(Û‰%¶DÔØD;Ë)Æ~Æ»œOÉtf/¥8Ù¾³Õý;G¥S÷)PŽÚ”’Ei„€}»¬÷ØzýÐä=ô“ó~’t!rt8ÑO(/ÍNÊä¹u÷Ô1GãK ÕÓŠÎèŒ÷ ¯ýtßOõFguW.ê”8Y@µøüÀä@iÞ¾ABÛk$éÓ'~…AÕçu*À©è²­¦^“£h.BÀ*:–ÕQÝ¿#'~Çâ`”Â,OY „•­”•;<|‘u÷žŸ¨+Åò‡ýK/Õ¥æHÛŽ.˜¤^9Á„&W!yÑtC¾DÂçáMã¸ê,)ÁòØøÚÁÚ®%ŽªøYüŽwÇ=ödvä ú–é#d;.áz-`õ$»”@lcÒSñ··Ôܲ\¿˜V“CJ14ð±«qˆ[”,P–/±%¢Æ6TƒåÈM(·(Y ¡Ä–ˆÛP –#7¡@Ü¢dr„["jl¢xb=Aý©zÿÒz…ÉÁzÀ jˆ ±@ng+P¯}€F™‡FŸÇóÇRVa?eƤ¶‹@…e9 ”£•ØQcªÁrä&ˆ[”,PŽPbKDm¨Ë‘›P nQ²@9Ø56~¢pé%*Ê%}õ³Säžìöìε™T‰unù91YñÁ˜!®ûù0òÃSˆ]1ÐR‚ƒ XIù"Ϙ~©¹-%o;¡X¢ŠyÌxsç ¤ 32’6RͤKÓ©K̘Žé3ÜRs“Ž©b⼺¨&·DÁD6=îñ®×gðá§Ã×Óºñ"#i¨Çåª!èìVì áP±3x¦¼\á ûm÷‘}ùzÊçæ­ú *éx×k¡ûwºKêñ bèí´ï¾2òµu[:a^—škZ{] KôñY)7æ‡Ùò.ÿï¼ývÀé½ÅòÏŽà¹nÏE¼víôð+YjÞÀ¬%:Þ:ˆÃŽ7g-ñ Õ‹Z’ÿ—¯Xý›ö—©m•ýÁ÷`þ÷{Ù•ÛÈþâ'”l†¸V ã㬥·ÿúks,­·ÄYCYÓÿöÛo…7ÈiÔ8)§RoØ´)lÚ¨8öÂ>—H"ß¾ƒ3ùûÃòb‡üóðA¦ƒž ,ý“ÖéG<,`H:Þu]…B;Ì?â¿6ç5‹“ͼoçþÿÍÀæW ôÁ̦Z½>Bët¬Û?üÑì›Çx©ÅéÍL3« ó‚i ýè‡gZþ‘§E‹>‘´ZBO»—ºÛ ýLbC>­C? ÒÓÏ>;Œºÿ×ç¿hŸéº#oœm-6ôºÖ?94.žÂ>þ}á,•iŒÂ˜6('eªNR­ÓP¯äãÉ'ž´üëagÔ’ê?ÓšÒ ärÃŒ ®ÙÁ íÄrÓÃÛ¢ÖBù·ÍÀ´ó®]åžÝYûûíÏ¥æº×RÓ“§1骶oy²§‚¡º¸´Ô>ÔÄÄîÚo›Ñfõû‚׬]›kÉöÜp eE0±ßŸí¿Ôßèoàgÿø¬€O"^Qu¥ŽjÜã'#³¿eËæxÒ´¶»¼_nŽŸþ„‘Ü­æT(ÅçûÿÐCÙaýƒ¼jj’8¦² ' þÖ¥óÕöµ}ÓëµÀxÿg´¸u“eD³}|þV:ìRߪˆ¢™øW?4mùýGš¥[R—×­¢‹5œË¤øsùÏmüïÚ¯ñF×¼ºf5¾ã­#ÀC†`Nºä+¼dÕkò=ãÄÉOÀ=S‹@ ïøÂÆÏ‰‰¾F#~„.ÇÃn^\þ žu¼³ŽjoØú›ñüçÄÒ÷¨9¢®‰‘ÿ,])GüœöJ5›ñNÒe,S‹@‰Â}ӦɎô¦Žwý-("{µjú91X¹ñ†ô&FgâCíLxëXr}ð–ÞãÐ{¼ëKõÒÄÈ#t*Õué´@<ì‡ýf3ÞQY%@|~J é¹òŠ«œ\nß1 U fO¾<~*,ëxÇoÌßì¾,™Éj`Y<ØéãÁ.4 œ`NÿÿéO"Ë ÷ÎÃwÏ<­'3ãEŽþ狚·ƒºXWŸxòIq4:Æôÿ•W\!ú³äšÅ„TÚIìV‚ÔÂÓO>eõ§mþáŽ?ÈKŒØp†fÏ~UÓ^¯ÌBáJ‡Å¥OgFf¹™Žˆ §×c‰8¶“ÈÎιçžc>àËßC…ï>\´È‹‹ @ë5˜%®º`NöjÇ%½ÿ›žôºŒôbÐ2k¸µF:’qÆ;žbŽ´ðBG‰ù|àÁH6ˆoãb(ó|íuX6¯÷áŸïþS–¸ 2<ÕÛÊ´^ã « `È:Þu¦˜yyä ÷UW^~qNYÅ>µ#Õ¶ó?#¤Œžô5!K[Ï+A7ì\ô g'EÏÊï¶ øz‚>SÐ ZO¶ÞYûå½oÎP4K?%B$9:––¢ÌO;ýô\¼‰í‘Z14qÂmV0°Š=ò"“ &MÈI »øb]ÊýëK. Ø’qÛm·É9ï¾ýÎNå‡o"ÿxþîÈþÈ‘#µ­¨ÕäÜ?(?û•WòøP–)Ôþ1ó ›¨O·Ý¦‡²!k™xE^wDB|$¥ó™B àߟŸáWôP\OëÖô±}Û¼e³lïÒ¯t”Sò~B°VÓƒa¡,*ä—D_|r­¸ªÚhŸá]Øî”Åwö10Ûøýíï·tQ¿¾_è½ú0ø;¸¨‹“4of_ùÊÅÿòeÜrƒgpšñfüßüærK/ÿÌ®F#p•†ïx?mÏÓZÀ¹â *É~ûwÞ/;¬0qwŒ•Ì•Ñõ=émBN;À²ø”-ûâç—Í›·l\±reÀ¤ ½]¹P²÷;n Ûgp¡/ƒ$8Ô–Õ‚ö£ºËóïÚíTnÑþ¿òùÓ*– `µÿËž²k8Ë_b!ÐîpµŒ«Š1Å!>'†^ {ÒØØ>ôÐß,5ÜãÊ5ûÕWë¯Î 8yöàé‡ÓÈqmußñæçM¨ì…xì`Ö—öe©y|hà;ß¼0zÈ×óï7ãTs¤q9´1-s¦Ù¯†W]ú˜6èÅTÌç¨Ñ£c<ä)åyÄéÖÅ‹é¥ý"Ÿái÷iÇv|~°7ñÐC1ûÂO—γ€%XU—R«‡Þ¿ú•äŸ/ù©ãcÆ„bÔ—yýó=÷(³‘ÌÃrBÍw9ÿðŸ÷"Cí‚ñ;½q4ú1òœ]ÉQYÜR¨à¥Î“Uœµ„Úñãã' ¶³d}æ»ØñFà›µ²'X–çµ<ð tpȳ3Cë8Ž'3(RGñMûváwߥ#î7JÇŒöpà?¨ëû$ZMöæbߺú! `/ÚwþQi8mÜï%/ìñFþeY2Òá:i&L°ü׳g˜2yŠœâ‹Õ!¸ã=T$vT_ßøöËŸ±Dñ®»|~Æ™¾ÃŽ8Üf$ôdܺÔá¿þí!¹GpâÙ+¯¼Râ évD7ËfAá«×…ò×z­/„8—ׇ.’º€{â¡¿=d§×"ÿk×~~ðýïÛ •ÌãêŽ Ú`µÄZ·Z"Ú`o>Ë •úðÒÇòoð!xƒÇöƒMÿè£URþð?ê+fŠqŸ±î°•ºóʺžàê¿•i­ž|J?9h A>³ƒ´áL”ð?OŽ÷³$Ž„6» þ÷b£Gé!oØ2À{ÑÛ¡ÚFc[¸jÈ`ó%–Ý£ÁÊŠbûë_Zð½ß÷. ï |?ì?݇“úIÃ2g¼(U]´¿£öÛ•¼>~"ðè=2û›8€Te¬‚æíã;¸wYŸèƒ×æÌ)=¼lº+ÿ S‰/ýú÷ ›6aЋ­R&–)¡B³Žª?Œ¬ÂþsÏ>óS—¥Éˆ¼tù2ûrÁ>ö 8+ÆÛÇ'ñõ‘yóæI;<ºûOæìãçþGIÌvê_Ê@5ÆlÉïÂ’ï,WôÄw Ôe|FHVÅ4–Œ˜¤~—ê¬0¶|¡ÝÁ;<íÛ7´ÝÜÚ?_ðÙ.¼Ìy­øL}5à f¬ÂÿY ?üP–ÿwíúÍðÐC ë7n0‰µk×®âB³`>ÏØ0iËØ6ýà?Ÿ®ýTÚ3œM‚“ÚÑÞù‹þéûë4ãMÿûò÷q o„¸Ô\²gptö¼¹øLªò÷ÞûK2È_¾ÿÁç·ÇžiÆöqÎðê5ùŒòÎ ³ìCðçOª >Åß³"ÿ*IIígëÿJcÄWûKÍcêà—šEÇÛ]Èv6vâÅeÉéåTg¯(+K-ð‰œ{aË–ØÙ«×컢ú°Q·}öÙ6€=a¸8C ~†<Ù3;<àÛ)Ôqæ–ö-=…=ÑxØ ml¶pú4@ˆãñ¤£ì&D²ù£S2¨ù‚LÄt?vL%?RÙ•‹Cˆ¼MœŒ¼ð}œR¬!ñúi»¤ó‚rÀ"¸l©9f¼}FÇŒ7mùSÍí4øíäéù˽ñSVLCÒ)¡NÈ´ïÐ.àôô*ûL‡/£9„~ãË>O5÷3ÞólŸî-–'Uc»üûen7ãS&Îÿ4‹å´M÷yaH¾ÚµzŸ–ÿâøù-ø?‹ã õË_èhó0 êǧ²`aèÂ/ØIOfÖéû˜MtÒhŸKͱb%í£ÖN,ï ÊBŸ?}“v<Ÿéóðå—^¶ú½;$Íd ùGÇ/á¸ðRCýÅU6ÞGø<ôqv«L?_ÎöúR<}4ù[0x×a0qô`¾Ôñ†ý¹ñTsÙã}îíOºsRÉ^Ñþ¸Ó!}8èåG?'y*Ù>ø³(ð &ßVK¸øU épµ(€OÙɾÿX®5]ž‰™x½Êõß©Ž"¬D%Ž¢†€O™1€Èdžõ¬ü{ÜØ±I®PþöœˆôWñYŸxÿQ7ëÂ'ƒ@v1éL¤1ˆD†»ÿEO´_¥Ÿ4Úiª&šØÇ*o‡i/æ¶0SÌzÊT{¸#ûçü+IRùïÛ©“|¢+pB/ßœ8abܪá5+Îö'jžËÀ>ÚZvH —‡µ!ÿ3™iöq"/YÑãÊ_÷…§öõGV}T´¿Ô¸£ü[ÄH'eŸ‹±]ùCL4¬®_,”N§ý믿>ÕëØîËíÛ±}ƒ™dð OùÝÛâ'®b6 о<f#„?Ýýçh'µérЙ}_>¦¦ÑÛ¶mµÓ›™&Ä'~Ýu×™8æIf¼c2 ÄÔüÏ/‹@¿~Ç»œK ÚÐ>íaÇ=:¦{0šÉòÙº~® ߪ׸Úþbù9¯¢NoÓãlO ó^N~«Î®ƒJ2ªšª†£6ŠŒ‚ÿM¹è ’Œj¦ª%®KƒÅðȾ}|>u¤xªùÈ#í>à}ûß×ܼB9Ѭ—€^–õ4Ö7Æó^5LþŒè‘ÛÙ÷\Á?ãö7×ß²oKÍÕÙ¹{pœ> ~øˆ±4@áòžãõ‹_¤°YaTOzÀv]¶ÝÙÚ8hÕDa˜|r»ˆKZ±ã¶ØáÔJì—šc¤Inˆ¸„¾ü¬Ù¶øÙ0ØÇöü! ¤¦›P+V¬^vsYÆÈ°æùñÊž½^ IDAT3ÞŨ.;°ÎŸ±|ñÅMµ"Éþp÷ÝVðÐAEþ‰ßO·¾–kšñ®ì±gR/¿ÁŸ,CõóNÈ2…-ÆÅ¡K˜á`C4æšk]z)å"Ts“£4ÒŒ7fOq¡Aã’ð7çÏZZjÎω91•8”‹yð£í4¥1Ÿzëy¬ž®Jy@|o:(¸hñ"kDqŸØÈ;|÷ôÍ_ðfŒ“²£ h3|´ÿã”îç_xÁŒÓøÉ×oDjª™öÔI«‡gŸ{ÎâaVéºë¯µ{…:8à+áþûgX¦ühùK~Žèfv³ÍóÅç¹ýWfϘ1áAjþþ;÷'çÄ8šÿT¯ëᥗ^Ìì[Â! -Ôk¤þÇLÒž{ò…*=ÄÐé½úêklÀò2hS¯ÉÌ;ÂXêNàÓw¸RÝAû=ÁÓO?ü²ZÆ=âˆÃd/=ýOøêï×þXÝ¡ f ±ì2vPøÝOoß–š×já9”iv©"|'ží/ÜìrvL'˜]¥sR ‘¡?´Kí(Ëa[ï_þUíï+ÒéTûô)a³ößPNWÙ~âó©²ÔŸ`ÝüŸh5óË3Pmÿª«8Û­÷¿èuPoç•WÊß¶Ú ûXM},ÕͶ;Ù?ñ¤˜hSÂÉ'Oò%Ð銆#àÖèöŸ´£¢ÁC›}ÿ »§ž|:týæ7õ^sùG»¶xÉ’d®XT¬ ;‘(ñbzŸÖõ32ÄR ø=ƒ]¾õ­bûö f”ñ~ í[uyÂRs>‡˜±«®l[øªê?Ë ÷ÿ?ܽ`9*اÞUÿ­\ðõ ôËË_ÚÄkFk›ˆÈÔ›IypõƒÖ'Õ¶s„¼&aiOkøJ„ÎR¡If„X¹Â?ƒu©yâj±2 Ÿh¢-¤ øå—_ngN|éK{‰)ÆijnàÀAq (Õøÿ”SN /¸ç)"£bû«å¡ï;Åö'ow˜Cµìí““%¬`'œ“bˆDB'¯¨2È&4±Œà¥lNŠ! )lPdØ1踥lNŠ! )lPdØ1踥lNŠ!›ÐªI½£,᫯¾"íV›Û_Ü‹kV§¼é|S¼ºþÉÊ &І *ƒlÂ;ס”ÍI1D"!… *ƒlÂ;ס”ÍI1D"!… *ƒlÂ;ס”ÍI1D"!… *ƒlBÏN3ÞäšTQ‘æ‚eN‘’†!,›5ŠŠ4,sŠ”6Œ¡Y+#*Ò\°Ì)RRØ0"„e³FQ‘æ‚eN‘’†!4keDEš –9 yðá…zé’üsm&K„°lÖ(*Ò\°Ì)RRØ0"„f­Œ@ Í~uvxýµ×m//%©‚3(hHù}ftÜŸzêIÛ–@Y{é1µ…ðñªU2 „ƒéð¡"‚1J™S¤¤01Þ†­˜­ÖCÎ’}bþ°¸‰'yÑ‹d©³ßfAŒÇ‡ ˜‰Å÷ÉÑAÇ ¬É!L‘K˜Š4ËÆ_™ýмPá ²lén²š0ª#,YMI‚؆U øa%Gâ0NN‘º3{¶|Gy—HS„Q+ Š4,sŠ”6Œa…]’T¤¹`™S¤¤°aDi¬ªHsÁ2§HIaÈVØ%IEš –9EJ F„Æ* Š4|ûwõsœõZ¸óÎ;ÃüùoJûvç‘G‘ïÊr¦ ¹¨Ž°Â.I;²_VQ¤h÷ÿ+Xb=gNØ,Ëá«;´KøYÙ‡>K–šË'ü´ƒÅöúƒì.·o“Ɉ O?óL˜¿`~Ø`K»‹²)lÂLkP‘æ‚eŽR°BÏà^x^ÊD›h²Ds“áÓO? 8ëæ™gŸ ï¼û®<ÿ ",«(RRØ0"„¦­Œ@„ÏßÙ¯Ìη%ø2µ¨¹ÒU«>²çéæ-8ìÒEÊEMƒGT¤¹`™S¤¤°aD½Á®"ÍËœ"%… #BX°éƒ*Ò\°Ì)RRØ0"„Þ`W‘æ‚eN‘’†!,ØôAi.è9˜ÁûÎ;ï¼#Œª'IF„Ð,à*Ò\°Ì)RRØ0"„›>¨"ÍËœ"%… #Bè pi.Xæ))l‚MT‘æ‚eN‘¿ã핦G„Á­I Éq5>„¹V†Œ+ˆ…ÈvZÁ#¿eß»‚΢w4Ì!¥rh\A,ä„Ht¸C)L’†"¤T+ˆ…œi€w(…IÒ0C„”Ê¡q±" Ðá¥0Ifˆ°ðI¨¸÷Ú¸‚Xˆ*s›ÿ¤}§ÔP³¸ö¹Ô³8µÜt‰v†Íd†W 9ÒîP “¤a†)•Cã b!'D ÃJa’4Ì!¥rh\A,ä„Ht¸C)L’†"¤T+HC¾a~äQGÊÁk8|í¨#¿q@‡ûHYecIr©ûgìç©Cˆ)t¸C‡$oßAG>šý¾<"¿’Ÿò'£2›Û±Ù€ºÄŽøÏùŠô±Šƒ—¥Y ‘ç9ÄòA9|;Ú< åtdɾÚiv†Á>Ìò/ZsÍ[Ý‘ËPÐá¥FËÿX¢¯×Sÿx¸–¿õ–T8ÿ¡õß:Þ²—/¸É¿)s û¢ýß²ŸÊ‡˜”Ž«8°R—TÆe¨²LÖ-IuûVñi¤ü¢VÂ÷ ¸ÿŽ=æ]Þç–ûÊr? §¼É¶&Izuú}nŠÇs¬u\m™`üÔLn¯ üÝ)¢ÓùGÏ?ù,¦+³U±å‡Û´ÆÆOÖ´k×NN¿olÓÏÂöÆ›Â=÷Lípà^ßÛ.æÎç\ñj R óxŸÕýWí–£ÛòT¯Ëö¦bWíKûëñ\…&9Œ¦šp?Ãúô1ñs¢Vªóº7(Þ§»šú­ 6Éáç–ÿ–ýí•Jï|†õ¯‰…Vù‹šx§å«™‡váùÇZŸÅ'ÑžªM¸ÿÆþ—ÙY²-`ˆe³BIæ“Ðä)`„ÉØ0$®Q’v M”FÈ‘ŒmCráŠ%i—ÐD)`„ÉØ0$®Q’v M”FÈ‘ŒmCráŠ%i—ÐD)`„ÉØ0$®Q’v M”FÈ‘ŒmCráŠ%i—Uì +¾Èy5Œ/4 âE+qJÒ.¡ SÀ9’±-`ˆ û¥˜>?”¤]B‹H#äHƶ€!¹pEˆ’´Kh¢0BŽdl ’ W„(I»„&J#äHƶ€!¹pEˆ’´Kh¢0BBpØÕä)SÂü&ëoª„'+mÊ” á)áÕ9sRD‡Q=회#Û†äÂ!JÂî“O=&#Ì“äkª„§ ]ó…À ÐA;ôÐCã)ÜL‰w†%Ä1«QJ²Ü MšFÈ‘ŒmCd©·”çòÿ£Lw×>÷xÃ7·Ü¢ŸPÌSZbJi—Ф)`„ÉØ0D„QvTþ¬øÂBvåª2Û†”ä‹J2ß„&G#äHƶ€!¹pEˆ’´Kh¢0BŽdl ’ W„(I»„&J#äHƶ€!¹pEˆ’´Kh¢0BŽdl ’ W„(I»„&J#äHƶ€!¹pEˆ’´Kh¢0BŽdl ’ W„(I»„&J#äHƶ€!¹pEˆ’´Kh¢0BŽdl ’ W„(I»„&J#äHƶ€!¹pEˆ’´Kh¢0BŽdl ’ W„(I»„&J#øÏ‰E¢Êä’­¤Õi«D“.ÁühG…|f«*M-û^Û©åzçó®«×¬½{ÿ*à“W8ïó¶¯ùþìÊÍêÕòY:äç…Ò~ôr~vöEsëþ/:xá–ÿé ÿôûoWê?VÛ\þ›ËC§NûXgð½÷Ú;œtòÉ_ÊÀ,Š—Ö–TgÀÏhÿÅ÷>ýó«Þ½¥Íö–þ¯ä¿TÖ±r´òŸ^«þ©oœ‡ 5Ä;¬UÿZõ¯P|u†Pyy(—Wž“0Ô¡UÿZõ¯P|u†Pyy(—Wž“0Ô¡UÿZõ¯P|u†Pyy(—Wž“0Ô¡UÿZõ¯P|PëL-u„#Óê’!>V†ç|A.êA8mláòˆ¤(גʬ@O Ë%Zö31Üò¿UŸˆdŽr·C^ŒVj^[õ/óX1íúW¬H™£ZõÏÜaHÑaÎ%Z÷_æb †[÷ŸUŸˆdŽjÝæCгp.Ѻÿ21ܺÿ¬ú´î?ñ@VQZí¹Ãb…±p.ñŸÕþØŒ·+ñX˜­B†,Û¥ž‡q“†úJRJiÉj¼´àycÉ +ôÿ¥e¿å_Bªu Ë`Å*0Šd†}Í#-EÍ)ÉjÂ’lÄò(Æ.’Vèÿ-J)3Éj¼´àT\`É {ˤ¥¨9%YMX’XÅØE2à ý¿Eiå¿P™“׿½%8[`É {Ï“–¢æ”d5aI6bycÉ +ôÿ¥Uþ­ò÷•¡Õþ[}hÝl?² ‚@F‘̰Bÿï5RJiÉë óÒ‚çQŒ]$3¬Ðÿ[”Rf’Õ„yé–ý’ËÌ=ô7 {Ï“F™beJ^OX’XY‰0Šd†ú¯‘RJKVæ¥+ E\SÊ™·\”IRÿö¥ãÍQ¸ÌÅÎ3%‡9ž8,)(”Mšé.0²`Šî—P— ÇkÙ‡£C2¿ÀÅ-ÿ7{8ú ˜ÜçXB• ÿŽ×ª­ú׺ÿâ ‘Ý­ö§Õþ¶ž?­çÒVã­ç¯ú¥õþí %T òïx­÷¯Öû×âû—›ñ.4ŠYåÏbáIÝ5Æ7$ªqa‹W@J"žÐ²/Þð.1÷µüon)#­ú×¼âX Ä|G²'´î¿ænlÝVSÊHëþk^qx£)4ß‘ì ­û¯¹[÷ŸÕ”2ÒºÿšWÞh­û°ºC·xB«ýi^ZíÕ”2ÒjbÅqoõ’ùÊß{ž(÷¡#ªaü;N"·s‚Êñ|à ¡´#ªaü;N"šTŽçn¥AP ãßq²é„Ô” r<ßpC(í‚jÿŽ“…H'¤¦•ãù†BiGTÃøwœ,D:!5%¨Ï7ÜJ;‚ Æ¿ãd!Ò ©‰ð‰Ç tPاS§ðè¬YB6YC(í‚jÿŽ“…H'¤¦•ãù†BiGTÃøwœ,D:!5%¨Ï7ÜJ;‚ Æ¿ãd!Ò ©)Aåx¾á†PÚÕ0þ' ‘NHM *Çó 7„ÒŽ ¨†ñï8YˆtBjJP9žo¸!”vA5ŒÇÉB¤RS‚Êñ|à ¡´#ªaü;N"šTŽçn¥AP ãßq²é„Ô” r<ßpC(í‚jÿŽ“…H'¤¦•ãù†BiGTÃøwœ,D:!5%¨ÏþÈÄ {õ ݺu ݾÕ-ôíÓ'LºãŽB´×Ç÷©!0) –tä”,‘å´šâ:N‘ô¿?üp¸ð C·nß’üôéÓ7Lš4Éd)C†RJYRüç¢)DŒšTŽçn¥AP ãßq²é„Ô” r<ßpC(í‚jÿŽ“…H'¤¦á±'véöíÔ)Ìš5+é*ErA5<½­-ôëß? :$ªMrÄ“]bÊñ|à Ée%$<À.šBÄ©)Aåx¾á†PÚÕ0þ' ‘NHM *Çó 7„ÒŽ ¨†ñï8YˆtBjJP9žo¸!”vA5ŒÇÉB¤RS‚Êñ|à ¡´#ªaü;N"šTŽçn¥AP ãßq²é„Ô” r<ßpC(í‚jÿŽ“…H'¤¦•ãù†BiGTÃøwœ,D:!5%¨Ï7ÜJ;‚ Æ¿ãd!Ò u©y…>¶nÙf̘ÚÚÚ¼7ÞP)Æ„ÚF#<ðÀÂo›>ƒZÂk¯½Ú¦·…¶6‹øà·µMWÙ7¬ß q¶‰žcÄÓßÌG ,7nLŠö—/_nro½õV1Çañ¢Åfݺu‘߯½öZJÇô¶0cF[@ƒ-i•4«í 6Øn|UeÚD¯eÔ#Î/B® “¡’Ÿh_ò%\ƒBX´ùÛ‚äÇÛÙf¿2;údFx÷ws ±Õo¼ñ†åÿ½wߦ Nž=›:Ú‡|`:æˆßÔ>Ë'ƒÓg„m3‚ø-¦Ç"[˜–\Æ}õ$›]þe…",Ò+Ã$Fè@G‡Z­&¿=z¨ÒÏѾdò˜µÏÉþª?÷ÜsO6tXèÝ»w¸ùæ›Ã¼yó²úgI3„‰Œ°H¯ 7ÂúõëÃüùo†Í›7Û=k¢†P§^yå»ÿJõšÉ(Å%ƒº)a›QwÂÿ«V­ <òH¸vÌuá’¾—„n¸!|€û†¦L׿Æ>ÕWÛ3nL•gœÔ²ã$V"9åε¤å$Lb¬oΛ7oj®{'üOsII¤Ð ö…eLÔêHà ¡bÊîL˜‘®_·>,X0?lÜ´I#“MU_pþû÷ëgm"ÛFB&Ñ`)íÆQDøаºuŸ†×çÎ Ÿ|²ºâž¡¼c%Rn H¯÷ï_ÎO½®m¾(+ÅIà Éͳþiû¶ lÞ´9xŒaë¤M\6mŒõ€1¿àòÇûÚ·µk×2{® ˜È™5’+Ã$F˜ƒÐã»=¬¾É3xóÁùHüŽ÷,§Slmß~Ê$óDy§.‘˜S…Eze˜ÄsàŒ´ì‹v±ü³¡«I”0‰¹ãIýw*ÿmÛ¶…wÞ~; _€«i³ü1à#Dš( –sàŒ0å+‘œ±föœˆÄcäsàŒü÷ÛO3Þ®’Ó]«W¯Ö†°^ ƒs‘ÜFi e¿~ýÌû½z]ê±#êøpãC›pÑ¢EÙ–-[LO:êµpÓÍ7‡mmf|Ú´i¡^«K¼Ã?,ÒY°!´MŸn:ß[øžVÜF½zõ2:Óá!í¸h‘è„Æ—ÿñ²Æ©×ÂŒ¶6K‘d5aä §ÄN„i÷Ý'6`ÿðÃg4»á¦!?õšøõÝ÷´ÃL!j9à€/ǼÕÃÿûéOÉHûf~{õº ÉÄò?¨Kã£3FÝ\ÐËì×¢ßéêd™&ÅŠÑ~N§vT‡;!R ËÐÄK˜1#"œ;N;õTóÿ%—\bÑ)Ah ‡$^Â[Pá”ØŽðåÿöÛoíÛ··2gýGY¢.À –ÝÙÅ\i8å"a^rÃÆ aøˆáà¯lv ¿Gï†aÆIg|{åÀWÐxõZ©^çi«¶o2%¶#ì†ÿo›x[–ŸtÔÃ矯ùŠŽKΜ’a7ìGÕv–Ÿ\”ˆgî‹°ÎêA×®]3¿z衃¬U“¼dÉR™™üf×®/Ö}úô1qÊ|ù_í|Cìt ½£dÃÌ’x 3fD„Ó2à4¢Ê‡fΜYŒfeÛ\³Ïus)ÚÏ 8ùBý{æÙg­Œ:ïß9LštgxàÁÂèQ£Â©§–©IZ– °ýpì|0tér ÙÀ½ƒYõ¿=ôP•Q3f $^Šr>?ûwîî¼óÎðÀ÷‡Q£F…ÓŸBþŸ I÷pý† ezðÁ_ù©ËsGiß6¬×rtJ0ð8bÄðÒ½ðuÜ ®0 ¡·K<ñF¡pJlG¨ÈÿsÏ='ùiß¡½ÕCêó0iI˜çN‰í†”Ÿ?}ã3˜„E½¦;„pþù?“ôJÇ» (ñKJ¡"ÿP±déÒ0éÎIáßè:îÙQV|T;ß8}¡ÝµO„µL¼„å„Sb;B“üS‚°¨áÄKXQN8%¶#üÙÇ;4Þ™:´ï®¼òÊRþŸx≰gÇŽ¡#{v {ßCià;6óÛŸwß}7œ|òÉ¡{'C½;?†¥K—Ý*áäÅ„…Sb;Âÿ+óVðD¥ŒË®òásÎêxû”Æô¬^½ÆŒC‡äï3îW^½ŽëyœÌŽC¢Zç¶^ ;w.ÿöëöÛ¿°|™ŽæHÇ;vÎÑhùí#eâòe òñ'o©œ6-v¬¥3Z·7ºrzì¨"þ{®£êÓ¶çýSÚög:÷÷ë–-_f¶^~éeH¨×dFÙî–#ö®„ræL.9mú´˜WLxd¦{mÄ„è£÷ÞãlvÒñæoØ@òŒ†aÓæÂz¡?;Þõš4ð=¯·ß~;°SN>:Þr5‚,7 ºµLá»ý^ÊèGÀ¥Ë’ߨ·+òŸÉ¤l)¹6á2£L1á„TØ_õñª0f̘0qâma5f_üÕTi™Q¦xE¯°ŸI•Ã&\f”)&œhÿÁ´û e‹rÅÌûvíee‡Ç–Í©Ž$%ÀÊÖŠ”¹sçŠ^èÊ~ñ¾íÈ# ëÖ¯Kª¬Ð€Œ¼Õk’¶Mœ!Ü ûI©ÃþIÿc¥Ï™gþHóóßÝãèì9jô(gÔ¡ÿ¤ý¤É9*Ë”$mØ`«0cuÀ•¿Ðêµ€A ¹\&0â¿÷^{[ù3þi§¾SõÏò쑊ü/_±<ì½÷ÞYû[Ú)ó‘=îÉeJ’Ï|P‘Ø»ýöß—o«¦JËŒ2%Ù7¬"ÿÆÒaЃ¬¬ž}æÙŒekeJ’&vÃÿÝqõY‡6†Ï<ÆÞˆ—Rw5UZf”)ªgРoÿ}¼`k;ö-)M’^äKx'ÊröŽW«~M¬JXÓ¤ú„4±Ùõ1c® 'L «×¸gpS¥9#ÍxwôÖÊxû&Ø!µúÜâóç´Sý SnñËÓš°Ÿ„·§´l­LÉ4i eß}µ Ú?Õ9õ¾8AÆû´_L8¦ ¢÷Þ{¯½ðyU„£¯"E «‹Û·ïPõ`K/qñ\Â"µLqâD[å¿ÛåO*,{»LÉcH¨BÞ¢J$Á5Ÿ|b/"C\ÇËP;Ä™N:…U­Š Žšíu¡Î*㞆´IÊe`"ÑùãCkà A)FX´hqÀÈ1+îSO=%üéÓØQEÃXGtë–µPÒñŽ/캤Öá™ñ®KÇ”öÍ`!ÿ…zùå8ã]«…¶nÆÛ)PÔ ”Aø«Î?D§s !vP0úOûÂç ~Ý $Paä¨Qâ£#ðrâý5Î8û:ã­/<{üñÇ,ûòBä:H“ÿügãqÀ7¿]ξ¢Ž!6¼7æÊ²BÒÉöú2Z“Q(/O\ãe±Kö·çÑÓ¤üi£¹ÿDûG}§»ÝW/<ÿ‚ø‹YÂ6‚/íµWxè¡¿•²m [Q;„!|ºvmèr`—о}»pë„[V’lÙ¼5<óÌ3á{ß?Åì£þC>ÓÐh„Q£F‹ŒuÚêµðgÅœ°¢ŽÕ IDATíKZ…Œ¿æ÷å2 Mêß”)S´#¡æÍ{#¦»!W Ž?þø´tô3¶/iõùÛŽÿ™/õìg“ÿݱ?a­Ò.a$ÿw·Ý–-[¶…FxñÅB§NûHûçÅf7ˆöoåÊdð‰²2#¶m§Ë,¥¥ÄüŸ(æ”X©¶_þÁÎeàDìÄg‡vð3­)à*‹¢Ž)6„‰·J]nß®CøÝï~–._&Ûµàƒ};í+<øÇC›Ô?Kõ›)G¨°¿+õxÙÃ@8–=V^Μ¢ŽÐÄþÜyóìâØãz†¥KõErÙÒeáÄN´ö`þüùÙóÚi§JýÃÌÓ¶­•©•òÊr°þÇ2ì.]’zsë­·„Ehß¶n O?óL8å{§X;1mÚ}RJÐ?áÖ‰ZÖÚK=X¶l©Ø~ñ…匼ë nÅvœí].±•9"_ þvíþ¿ã÷¿·²ªLõÿ êŸØÛ ÿ3]HÊÏ~ö3­³{ÆŽ7Ó';Ÿÿ•+W†=÷ØC:BlP.§î:ÞÔÿo’©Dt Ó·ùßÿ'ƒ1ÿeö±│2S«]éËœk†wû﬛5sf˜5sV˜5kf˜õèÌ0sÖ, c2î·>^õ±NxÄ{|ô¿°«{1(76²¬:¦Ã:7±óm3Þ¡¡«âŒcQç΄xÆö0`€Fqù/ê +,TŒÂ7Þ†.{a&Üzk8ñÄôc¦lÉ’%aèС2p3dðà°)î»Ã>l¸Ä>lD>"âLJçŸÞªF•}”ËÀA¥suÆgHªîƒuëÖ‡‘#G†aLJ͛6… Þ £G’4¢ã0æš1aÅŠ©:' ÿ .”Yøý£Ð½{÷ðãsÏ S§LÕýÑN{þagìlÞ,g`Ôõ¢‹.–¼ÿò—¿ /<ÿ¼‹¡(³Ž9úØö1³«—BÌJ1R|†Õ8aôèÑ’¼¨_;&æ§  ç,Ž[7<ë“1°§g—ü:-ï‡ -£^£m@½Æ‹:äq7»0~×]wÉJ={N:ñÄ0pàÀ0ùž{Âúx¦ãÞxC¬;ËV„['äug±«;W ¹*p–}ݧëtf÷Bûö/h»r=öØ£’žŽ?>œqz¬;¯Ï+©ø4–)êöæ-›¤L§þåÞpÑ/.Ò2ýÕ/¥ž2":G×\}µ” Léà½÷N™[ÆON¦³\NJ¡m[·…«wÊνÉNÔ‹åÊÒ¡¢¨vüÍ·Xûå­¸’Å<„ÕH¨2M=» i„ÐéÙ/®¶R;NÀ¡N|§ÑI“îH> ®Fãoï…Z€ÈRÅ.äÐÅí‹ç ûN÷î²Jcð૤\ÐV°ý£1´÷ãÆ“6‡ùÇlô°Ã$Î0´‘Æ,‡Ä…iÔOüpCñB; Ú¿wßyGØh¯Q~xþ`°C®˜'œ¥"³ßµZ8ëì³:—i‡:Jù¹iܸp͘1ËË1@ß±ã’&ŸÿwßÑ­[lG‘fì»gûvÂI'H}»æÚ1aÅò%[‹/–w‘—fmßjá×nûäîœ4)Ìs¾Fqy?>¯%'[´×,Œ¶ëni.ïxF80Üóg´‰ë³› e†4\{Ýuò|“ô×kZޱüY欴ûÛë¯9½{‹¿Q‡ª®… ßcÆ\Î:ë,¹Îùñ9aêÔ©aó¦òŠ.gQZ#¾óæ3ÞUVvF#„.jNr!‡:ñÝC©‹ÐiÉI.äP'¾{(u:-9É…êÄw¥.B§%'5ä좯~M·Êá] «Xpo°ãí¢†ë®».ÖÃ==¹ŒÓH#„Ës¹µèà“¥‘öþQ`”uî …F]ÜœäBu⻇R¡Ó’“\È¡N|÷Pê"tZr’ 9Ô‰ï2šÏx[tÕnïZ- <$lݺMcvÚ°4B+‚KMÜG ¼˜â"—ÐӀ댷v ¯Ž·—ԈڬǽAFPñ1{ðã³Ï’J«rˆÉÐfK·kö¢­ìx·ó3·Åýçöñ‚Aû˜ Ì.uò†²ÃüCgð1 pöÙg‰=ÉO4Ö&3â:°Ð-‡ò·Ì·›vÞÜyaìØqþGGŒ)äRs6"IC Køö±WùMï.Äïš®ØÕü¿9?¥OäµZX¿QÖËtY€)6BªDŽå´ ~rî9{GipÀe—Iì÷ßß^ôÉ×òÕ›nW°¢•xÍšµáÜsÏ{ŒŸ0.fBÍÙ¡!âQ{R‰ ŒWðÿœ9s öaÜØ±™¼ÚÐòÿÚÁ‡•+ã `#„%‹™,^œ8“Ù«Õä€2‚‡é̲½þÚkd¹;6'-^¬uiéÓG_r|þaË‚çÌ®T†Ä¡8F}™Æÿûíosc!È‹/ùoÄz0ê¨ÎFEÑö@±cÆ2a|@Ìvé ÆjŸ3‡~¸•!â{î9{-}ÜËhÝÁi»ÔD5írf¨!"²fÍšpιç&±üiCêŽy ð2OÞã?:g/hùÓ>øz¨¥ÚâËNæolÛê´)ºaãF5®ÕÂ¥…%r&,HJ»Ñ鍸ÒÕN:{è˜6nÜ:´o'éÇ‹ ¹„Ðéq¼h0O¿—åæžSIx†,f0库7Ô³•(¹ýhÁF쨹¾ì$U¹cjÈNÙû­·Ä_ÈÛí·ÿÎ[P<©Ëyÿ{_}WQå{ï÷„oÀჲZWÓK]‚ØØ>úÁ´OÔµÁ–¡›Äf”„TdˆÚÌ5‰¡i‘ ’ H o½÷Û»~»ö®sî?ÿÏ¡ß9kݻǪ]µO:CUí¾ftã†õåÅÉÍbb›â‡nÐ

y©¯´¶?îú°v-f l/iÐáy•]³úkk¸¾pGѾøÅ/J=qÿi;ðB ›òaÜ+¸*zöHþG}Xþt×®i¸æXoDo÷iXÔ󗾿–r¹Â:HøàÏ<¾ûÝïÒ5bËÔBeRŠí`ÓÍG°¬Ñ'ò# Ë"0×/©ß“¨îZ£:JÊL?0 ëš2ñ›³OÝ{î½7-\0ßêN> —“,_¹"È™'áå—]=“†çö?šo¹ÿ²?G?o 1cøpëËáÛÿ‡ä~ê? 6ÏÎûäƒ_¿ùÁÏÕ_ lI ±ó^8¾t±šJyÍ,oa{Nh¨!ýïdaMéqÿü»ÿ>ûÌ3i·Ý>,í锓O m Ξ4i’\#x¾ÂÁÜ=8>š³ÝããQI!ÉõÏ'vlÍÜ 5d‹ö}vC•좺ðÐPC:ûÙcÅ#2Õ|àg4›“ ûûW_ÍSÀûéìsΖv^_9î8ÉÎgÆ3—[À4*L+”ßê‚SlÃL5×ï3'M*¢Œá‹5;JN5¿ÅM5¿ÿþ¬S¥#Äœ¾^Öxç‘[ù(0&—kUZµê…´:—qUŽ"è ±SÍóŠ/Þ¬7¡×¸×ð8µÔÿ|ñîõû2â?Ã' ,”sò£[]°87M9^pÁ…R<à(ëbûiîÜ;iH.h¾xyägltáÁ§«gh ¨£¾ðãc”lØò±cÅoXËsªpµœggÈ¡‰”.í&ûTixûc&ô!øÄ5…>âK$¦è³âüsÌ?ÙàvÛo+Yb-^ñ2 xÒIã%h—–±Ÿ.«nú´õOÇhðè!øÒÔ©SÓÙ眓ޓ§‰‚å•W²Ø²‡e»Í˜t 'H:£ [xÀÖM7Ý$2œ¬›¿üŠ+Ó´éÓÒþŸü¤¥9àÀ% ôùBÌü÷Ùw‰²½Ãï²4ï}ß_¹Qì$£ÕL[x(þeË Ö#×ÔÄy<þ„ÓÔ©ß–@i”!xV9˜œ‚ßÿ¢´Ë.úy̘± 7³¨‘Ò…\ ×ð»vØQdØY6°-P­"”O9{Jš÷óy #¼˜õ€k×ðÌ™3%þø †4”¶Ó×¶Þ±Ç#qð‹'(=Î/m`‡„R#˶BT£3ìCàŸ©S/”raš5òƒÿ‘7óãK€öa}I»Ï¾û¦ &äµÈÚ_⡚iP7–mÞÝór9(MiÖì"G¡ÇÖ_ÑN™eÄŠã#‚Ö©Ÿ~õ+?š_ÊB]´‰Ù³(õGš…‹šë‰™ PFžz= ðEYɯƼ†Ç©×^¾àú>mþõ©‰2·&ô§¦ÚŸcëû² ƒR@¦"ÆóiÐþyÿ;î¸ãÒüùóÓw¾ûmkóXúÙe'ži÷\''M˜ ý"^ÈÐ&·ßn{ 07~Âx…ãÇˈ=Ëòóyóì×Ïy¤ÙGì=’7ÛèË/¿,µ³‰5)¥ën¸ÁÚðÚ<›„¶‘qBÚõ»4°Ÿ—‘ô¾Æ¬`¿Oøë<ò,»Üõ—ÿO81]8ujÚoÿ²Ü ×/íz»ÀÝ¿(íºË®Rô3::ܦÛßì9s¬þ‹î»O²e*B0‰ÖöIã#"ý}ö9Sd›.̆–[Ò'ÊöÒ'Μ=;ßÇ'ZeÿãÏ?ü† ƒ˜½âíÓú(àH‹zêçñi·|8=g>¼ôÒÚÌ×û/>t|éK_JxþAZ¦ @ŒÖÓ>0ë¼óÒY“'[] ‹#Þ»Ÿ¢ÿÅuÙ>Ó¦MKŸt÷SŒÈ{¿Òä!– òæL_&¯Ûĩɜjxþ½65É#ô9yõ#ô§Vgß÷?mºáÆíÞk¶½C{8Y‚JÓ— O<ñD‘áúÇ §É“'˽þ‡sf' úÄcV®|Îîó.1vrÂ,Ňz8½òêï…çËFœ0æé)¯áqêtçKçŸ^#„çˆÒ›Mè5 ^>w3+‘©\ÈÃb¿—vz7¾k§ˆòN;½G¶ùj*£Ê|ÐTXh‘~ÓÆM´(ë"Ù™ù™#Ó}÷-”…Y³g¥/ýÓ¥ H³Ã;Êö#°ù#7¢ýê+¯ØV˜^ªrŽ|ÕœÖx#/ÚÃv"žÓ&?õh QÍU§/[q1¯·ÍõÕzV¬c™^yåe™B¿ø÷: xèˆwJ # H‹,|Ñð<ȰÆuÁzºñ&$Ôÿ_ÿz7îÉãößnåøÁL¼xk‰9â ;HÏmXHnÚTFÙX@D£7îPË÷â‹.’lÍ¢œ)^âùPô2}yByvþëe:βe™°8íôÓ¬íZêÊÿ«W¿`é∷¦¸{ÞÝ–¦‰û6‚y¬c†p /¿ý]~Qí÷Ò.ÚU:hSvåá4·?LAÆ©Œx(F^»t÷ôæº?ŸÕþÆ7¾nþÇÖo8üè(ìÏ™£7Èð5UõúÃj90eoÓ¦ôùϾÑþ<ð€tçwæA¹Äƒ”~û|ñÞu×]ì!Jí àØ‘æ#Œ¼×F›ñÁQ=ÕW½ôÁ~ =´Ó‹â»`jW¿—Ž;þ8²>$à|^uèKY_¾FW"Yf€2Ñÿ@ðÑíøovFÛY—–-[fåÂz§~ªõ? ñ Ïö©±£9î¾§´D„Æ6=°²¼üòKò€KhÀÉ<âUù7ç‡s²©AlÞœöw/œÚ‰)¥ Îâ_‚XF.AÁÈ8íSf™—ªóí,›Ø&¯uÑÎ>{—ì3f9çú£}r ço°¦UðÌB@GøÿÓ~­eÃÊp†å\]ÿfS¶œê>íGÖ‘¯å0ÜÈKíìoÞ´)í½÷Ç¥íaÆÆÀíÞaö|Âò§ˆK4ø¢@þ9_×þí ËÊ‘ è N¿—öÜ+ï2‚}ÌRcûýÑnMˆX.t¿'k˜‹”®¾æëo—ÄÖ³§Ì°b>€‹eZ²/r²‚øl·ˆ|°Þß0ú.‡ó?¾ÅGÁ§ŸÁôxµ7Ø´Ù*¢ýùØ(4Žþm÷Ý?*ÓÙYœ­­díû¸"°,Òʵ ¶ß^ý¥Oì÷Òn»}D ›÷03eýzWoGñ“O9ÙúŸâvËÕ6¨ç®<çÝtóM&#b©[êåaš‡ñ.åB¼ÈáÿƒúŸáù€uÖ›.' =,åÒu¹}‰ ôrdûˆ,ü¯ÜOÅœ•ÒÎñÎý l%´œ[êÏr±ÍiÖ¥þÞJ»TïìgOŒÒÿ=¾Ìîÿ§ž‚%’êA<· Í´M5ÇRiÇy ©~ÿ¹æê«%ž‹óËìÅ.NGqxI%mÛŒM×þǵù)SñŒn´Ô£¬?ë;šë4%éì«—Ü‹wtä§šKçÔï[p<àç;ß‘DêLsiúgÙzªtÀÒÝË."6xàæKØá—zò·36=ºl“¤[n)#À+V®L÷܃/ïÚ>pÿévÞü{=™òÅ„2^¶c9a/E¥6)aÄ›õgpµæW—‚¨ÀЀH…ÂD”vðlž¼èi}°Þé¿ðBŒ¯Á9¸ík$r}–©ÿÈr¾P#?¼Äù€E2âÝï¥ý÷ÛOÖˆâ<ÊCZÁľºôcªy¶¯~®üÖw/Þ¬ž«?°ñ…Óíb.Õo`ÌÂUGÁúsÔòŒ3tݸµÙ~?-û%ÚË@¶¢cÛÐü¹ËšpúÿòË.çiΚ8©Õÿ!·[o-Áþ•ö’§]?ÎúkÐ>õ£n/H²·šÿ€ÌŽ\ÿçmZy?͘¡SR1Í!˜Òh…Ïþ?ïüó­‹ýK.Ê>€ÍÛlôÙ_˜A€ r RzËòÍäŠ+®òE/ø „—\zi.GiÿS¦LÑr¸ë‘ä͆å˜ÒSa_Û?—´ :l×cÆl“6nÔ>DϦnGCOøê„„Ý6n,K-JöšBÛN?ž—  í°þ¿Ì} ö4g»@ªÿýÄ*@¹ú6RHû–¿2¬þ'M2ÿëì*h ‚D¾3fhÛAPIÖ…çÔ§:ï¼ Lþ ;§˜æ&éú} Ø‚B â0¿›¿ß| ¶²gÄÛVuýMœx–å‡}àÙèÌN¿Ÿn¾éfá7ò²šd;'–kÎ~\`n®TÙ>GžŽ8QÍõÐü¢Kû£~KîE”1Nϵתþì þvícK«Ú¬‚l+ísäê|^˜Œkí36†å‘E¼ÌÈ1‚ýõ6¦~ôo¥mÄrØwȸÆùÇ:êvÔg¨3>qĉÓÂyýA†XvŒ`4þçH©ÖÇ{Ä,ØTsÜÿ®p3—XÿÅ?lýßÅ—åH´oýú‡¬ï¼³neºÏÇ÷NÜ;ËûßÿAi7e)UÉAÐpýc0ýï´éÓe‰'ŸUÑÿžŸ?ìÃ0>ر b– ú,—ðÛnâºøÜ?þ£•³kæ »þÃS!ðÏóú³ïFñ™jÎm4 ÜTW¬Xn £Êj¬ö‡Æƒ)Òpš9¦r¯^õ‚lÓ…¦z¨‡ü‹7ç{vzOÚnÛm¥†}L ¨¶?nùÏ[ìe{88Í£·÷Üs¯5f›jŽõç9⺖-O}_…)Ó«ò´é\6ײeªy¾y•©æ<»¾TÄ)#Ì|’„îV†úðâ[¾âYy#ú;L›î÷d_?¬ó¤õáùb Èðò†5TX#xôQeÚ-×°ÁÜ©§é>Þx©ÂÈ xþ¤“NJ›6—@wœjŽÒsùôtŠù*Ùåù,+H¸åú³>–‡ó¿ñB}vf|yÂZZõS?=ö˜~¨A°½¡çænÅ2DÚ¥¦Ãžñ—5"ê"J5äœÎOû,®¦?í4¬ûÁHt¹©_…9šËð7^·4x¡Äéž¼æ02Œ)θîüvÎ>{J¶S^Òô?B°-ÖÁÂ¥V ° ÓF¥žùåi°‡6‹ùú T}࡚e›0ÁÒXÕ|ú©ß¤9·ÌIW]9-Žë‹5ÎáOæ@C9SLã‡}ø¢´ëkÒQy "ìÌ›§k3YŒXsú$ûäà>ß»öÚôòÚòi8³mõ·¶Óï¥Çò‹7ÀY'¤Á–´D#¦ñYo…ð)ê²ã»t)Hq‹Ê1Ëy2ð YÿøÑF ý䎟XÿÇå5`J«æÕOˆ²Ì³Z¾ž¿ù‡u™Írêù§®À-\ äÇž«®*aL ö±ÌèÍ7ól fLsâ#ë}̱ÇPKáû:«SÍGyª 1盌fýßûW:²¢3Ÿ!sEÌ| ³„$aÈG¯âƒc]¦Tx«û'’Òí»ï'Ä·¸'⣧îƒ,lþÁmáWçwðAI{Ùs¯üâ Øý‡l¬Óg=qþ±œ:l÷¯¼üª,¹ñúÀ1ûF–ÙäþçEžrÔåjôa’ ‹hÿRc"õ±¥4ýžLGŽy& pÈ2³¿ö:øŽG¸î>Ëíóí§ž|²t‡fÖÙ-…yûåc÷oøhëÏ>ug‚ÝÐ'^{íØGÏF~ƒA:/Þù\ŒÆ>Ëÿ©OšÕKý„A’ "×çŽx÷{¯¶É;¸6Ê+ûxçïítI¼$÷Ó\÷í±2î§y?eÚC^,÷ä)z?Õ²Z!…d¿ÃqmöKy™–0KHVõ/éáS lý‘üCá[ó¿Ä;ÈËS8«ŒþÿÞ/÷¿¶o´»gŸ]^Í\Jé·Ï=/íí Ï%Àgv¶Aðïøévþ1k×*帞qtç¿:·âþQF˜ù$ ÿ„×_ÏÙf©%SÍ]‡=¾®?¸7¿ÔâÁ “qX]Üz`¼˜óðòÀËQ*9Ê*kÎô¬“p48|¨˜Ï#Ä|ðË7dwÍ« ´ßK×_w½Éå"ÈFù‰/í’ŸÏ4ëx=¼˜k¼1Õ¼ ®Ö’FX>“Ê?9I°ï·{6H˜{—Ö7Ì믿Þ>fèöhš dÁOrcÍ/+Bç›æ‰'þ‹ùŽk¼åÅ;% š§iûiÑ¢E2]4~˜jÎÈÖúÁ¢%¸Ú;TúлŽök>õÑQ¡¬ œ&/O¹Þ?®÷¯¸"¾<1­ØÉÆÖð¦ßë%ÿµöeÚsžÀôbÚý‰'H%~Ç©æm#(ø¢ê_TÇOìN8áxëdùÐÃ&¾LÒ"¤ãàIðí%Í9qîÜ»,Oÿ’æëïëòÀ/~¡#àÙ÷Þ}ˆŸwSÍÍNNˆ¼þÀúô{iüIú!ùºâ•žxòIyÂùÛkϽŒ]i×Ùþ°úÿË¿œÒ€xqÍ‹éò+.—)›ìè7¼hc}2гo{ñîõ£ÔcÄ›öQ.¬=¤ÿï½Wý‚üõsŒ]òZO¬wô‡¨ ’|Ø`ÿ‡ó¾®7ÕëP>ÚT6æÎkýΩ3—>D×Ab9ŽÍ›é]²GuO¾¼³ ´OšÐçÅö™çƒ>$ÛÁ´\È0žK\N=E?>µå žß#}Ñ›.â¼·SÛÇ0üÏàaù×…õy†4-°8E£±-ùãcùþŸ *-f‚nÞvëm²îøõ×tÙg˜ _ÇñVë_ŸÆ>ëã Æ~u`ÿæí¿þÆÄhX·ì—²ôô©'ó‹]?Ùw—HÒäG¼þ÷ÝwŸô"çÃÛoØ ëâh_lÉwÙå—Ûr¶@¼„û>YÐŽL5ÏÏžïÍGö‹²C3É )\á9AÛTs_ÿå~t×5ó!ñÆvb9_ÄTaÿïë]ãð?fàpE²ö§3mZ‚« «ŸÏ¤Ê3'Q;•Þ0û5Ÿy6²©)É„×"ð,ïϧ]†¬bT䟽ý_üâöÑIÚµ<‹è`"Ûyy¾è%ÆÜ¡?B#r矂ÐþžF\›AJóæÍ³YŽ‹?’Âÿf©Ï‚}Ù±Ål8¤áߊQ‘öþgÕþ»¶?ñÖŠÆÓóê«y»Ÿ~/}õ«úb@‡Œ7Înâ|HeååvLìظúš‡µ¦|ÉàvbPE`$é ñ ?ÑþÊÁ©ÙsÄÒ=öÜCÒø@TËÝï¯È>Þ:òSr#ëÏÂ?ôpÙÇ vÔLÞG IDATê"P¦ý·éTõG2¿ØŠå À0ÐuK2꿾é÷%4Ò<ó4G·zéc{}L¾²ãá km±~˜›¶ß~;Ù[iN;µŒxƒþuX…YÇ:3>üà~oÌbБ ¤“£­n¹Ûh«ÿo~ó´Dmþ5·ÎÐÓ/öùâ]F¼Ë.XŸƒâ\‰­zrÇ©ånæµzMY_†©æz½Ãû_æ[&QÄÐŽ~Ë>D!F}¥}dý1ÕSÚ7ôD>Hä€bàÿæ7Oɨuë×¥uëÞ|ýºõiý:Ðë$NÊÉFØG]qhÑt~Ú]ø€“ë¿pF¼]Áµ¢ù¿ðie;çëçHް¸ñȃiQ—ô˜ÚÏ4:U³šG[Î>¶µ‘4ý^Bƒ–˜FìŽüì‘òÒ€v‘¤r´bŒ\”5¢–¹ (FܰeÎg?{¤Õeî¹çDÇÚÎgý{Ä´MÌ–ÀG›l­Ã¾ J‹î[d˜G^^Ê$ùS§Øÿ ¥Ã?ÌìKÛqõG’_ýú×YÞ{àñœ¢üµàÞV&“VØL&[\ѧ˜N÷û×^ËU«•=­8‚ âJTs/6ç(ÓþÛt*ÿ[Ò|Õ€®G¸xE]»È‹z[²%K<êЩ ëig“-,æìÿå1ŸøîóŸû\Âրͣd§fC³È,]fáÄÚdö¬×åW`æidÿc'ä£}bç¬ZX4¨¢ÒÿèǨ~Ú Sç‡Ø·þZúQÌñ25¤þOKe–%ý™_;S?€Hÿ¦A’¼ýÛnÍí ßK»ïþ·2ëÆ9ª ¡,[_fôªUéûßÿ¾ô©Hé!ÙÇ[o*?r#ÞZ#_/3÷ì´!7{hÁ¾F5W2Õ<'ÿéOX•á‚v¹F€þ÷‹/Ê~ãËD´­yøµ—{œcª9ꩈ<|ýqó‘‡â>¶›™O¢Ác;…ž¤²t‚x›§–ÖŸÛ” ïÉgå„ZÿÂ@.A•®$¢Åm‹N?/Oƒô¦šçNìñ¡{¿³£,v †ú눷úø²ËëÎm¾=U÷fD>\#[J¥Û´i{ëYTâßIpÍSöM.&è0ËŠ8ì%©ßK“&eþ‡¬­þàò% ö9:*™å¿¹wº—4L5Ï¢m~ï{×–©¨ÂW+K–.‘¶ƒ<åû²2ª9>$Xnõ’NàcýžG;\sõ5+› €‘Èaûž£>]üïÙ/ýôÜJ¼$O»æ?òÔ¬~/Ý{O â†mÕZAJØžFÚ@¿—æÞ©‘þ1Úÿsz7¶‹£Îcy¶Ä•h;ù¥E=“$èõþëö;“ZÎ7×½)Óã7nЩd±í`Ýs<0]MÛ"ZcVÑ æË9®*SÔB~ñÎý_yñV)–‹`äyžuÖdÙûå• jÑ´PþúW_Ó ]¯4þ$mܼ)혣æÃΉy¶ÆÞuP5I¨)§Ï˜nuÝs=%¸ÜÖØ—µ×}ñÖ}‰<î* öúߢ-ʉ#\Ÿ>â¬Ö*‰šÑ#£³?cútë—1õ9Æ8¨¬H–šo%qWˤz Yçÿ«_ý·ôóŸý<Á&b hÀ0ê+ló¿½¨î©æ#Û?÷Üsåº=¬Ëw¨~”ýÈ#Kr“*e‹ÖKÿ/ëÏóóÆŒ«óz[I6²ý’߃”0u×@ÛÔyÖŸý(®wûPêì#’7¯{<˜C„mÛ0UýýTÊ“ÒÑGAÓHÿ?vLG;È÷*|`”÷³TÈl@ñ&ÇÛÇŽ’¨¥ýËì>ðç>1+K]t±^«ý^ºóιšË¼¶ó ÿȈ·ÓfŸ*ô¿xcª9–{á#jéô3ò^ÇRÿ4¹¼9XÏ1¦“óX°`¡ùñ/ÚÚWrÕœ…Áß'ù 8&o¿¤¥ÔAÉÒåéD@Gc_ôbÉBý5Ë!6:û[ÝÿãewÍš%ØíšW \½fM´¬ýî÷Ò—=F–Û¾òÊ«rv83Hly¸ðöýľrMÉŒÛ|MâÃ¥&ô%°ZÝþ–,}Äî?Ë[2Û ëÛ­6ˆÒ,Z®ªtíÏ_7r±Ð5¡ÿ©$oëúÓo±«Æ ¸àjý^:çlŒzÅk‘ðb‡¯@?¹ã;íÿüåü’¶Í˜´téÒ´ôÑ¥ /]š–ä×moÜ_öz½tæÄIZY-Ž“̾¬õf þûZçbŒC]~ƒAÚíÃÑÎ6ßT|TsŽÆ;&=*åy4-]‚2.ÉeSëÐxhp5}B´cK'õz4=ºôQ©“Qaa˜AâF¼¹Æ»Ÿ–?»Ü®'\»}d·0š°üY}ñFÔkøÛ„<3ñÜoŸ3?`ý6N5ÇvSv@?Ÿ½›6Û*WË/Þ%Vœ×W°?&¥'ŸzÒò”—^/aOa;œ}C1¡:0´?L5G.¬çŠ/OXL»Èz!ûAJ«×0ªy?]vis¤æ¥µ/ÙV úFÔ7eDƒgÿsÔt{¥µ/­µ¶ƒvðãÛo—ˆò}¹Œm§ß“ Ô;æó³%r!dù@îÿ¸å òAJç}ë<)7F4ª/>¤•íMù ¡CƒkÞ„çw¾øÁ`8uÿß绢~ÌŸÂ9ô#Øó½îÜ’(¶_Á4æ•+VÊV,Ükz¿ýöO+V®H+V¬Ùú .ˆÊ>BùâAJ+V.—ü—ÃÎòé=²‹‡žoßö/U,䈢XûÛ5-]ª÷úþ£û£—´[‚ÉAÊÎtiÁú ëCò  ÙaD±ë®A ®r(¶òĵÝ;ùd¼°jµ.!é÷$†–¬ùãøNHx@úÕãË Óº7×ÉÇLí?ú ‘Ý7n(ÁW}Úax¨S 4…Áoˆs¥µÓºà£Ü7¢['£¡iÒòaYÌöç”Sð!»Ÿ>¼Ûnò $Kâ6§´ä‘%iÒ䀰ߓø3,;ú¿ÉS&[ÿ‹ L8ÿ˜e´di~Yò¨<„xfÐC2±òù•Òïà9}ï›9¢7Ęe¢}¢Ö÷ɧ4ÆFv…4ôeRç~/~Øá鼋–-~ð¡´æÅ²é蟯Nø76§–’ªHìPšê‹·–ç²K9ëŒJIÚ làþƒnøÅrðqösŸÿ¼ÙÇÞòþúÿØßÅû)êNû«W¯IS&OI×ßpÊE‘>}ûŸò–¡¸¿¯\±<­X¾RúýàRÕ ™Ð@)¾a 12YCÁ’EB’@4“4Ä`T×ÿHåoæøÿ_ý?ðÊý×?½7bÆ.ž öÝgß4ïžy6ƒ} –÷ðùÊq_)n¤tÓÍ7fY?}ëÜo¥MõypåÊ•öüg€m;Â9 DS»!£;ÿâ(s… Mß ãŸ¢™â^3ÿ±©æ=ñFEšdÊ&¾QÈ—[~Í<:C²[æüP’Hpµtâ¤3]Iam AWhçÖëSœjž· Ë…Cd³ƒâgJ02)X}yä+»vø,³nɤö}´fD+ä—]–‰ð”“Oqew¨Ìxñž#^B$üçr”ö¾Ÿ:¯ö­>¹œËŸ}Vn´Ë [ô¿³lѪ·Í{X3ª9¾¼Á·, ÓlÚ\"ÌëTsÕHõÙþ°ú£<>21ó\úÈ’àoìg­ƒMûÖˆ$±ÖßЪý= aW¦šøâ­_ÞùâÍQK”[ÖºØÇK“ø_Û-¢j´é´Ûm¿ýÐó‡5 äçGˆÅoÕ|×þоʡ/€|Pä9ÆÃ¨‚Éí0 QBN‘„.×&–üRº #ÞÙžŽŽ^¦?²Û‡sg_¢ð²ýÓ.בÃ<˜ŽÐþ™Æ×;ßÕWl8uÑ©ÉI‚72m×ù\EPÚµD°¤›o¾¹ÔþîõÒ»vÜÁ"ö³þ˜®Î6VfKœ.îòñÊTóòQJ¹9æ‚ÚaykxÉ¥—èi¤´ìñeeµ–þgì6Û䶣紪œj®³%¬p)%ƒñçrÌ6cÒ:n¥cŠ¥ý—ö²¶Ñ°—B£ÞCç’ø–ç ¥‹/Í£gùZàù0Ÿåþ|n“o›<ŸÓaý/^’¬Zf8#£è…Úû¬a»×K7Ü#«?¢jó(<})?NµœÿÚ¾úùƼ3e&”Ž:20ëðîÞÞ÷Þ÷Êzl]vÔÑG¥Ó§…/4SÍ‹ýÏž{Æíļ}\/ïÎ(05ÿ2cEsùùÏ~fíﳟ9Òî4èÄÇùúçÇÖ÷†}:ÖÍÛ‡%¥£Ž–ÀýR:è,N¢´3—ØþµùšF9výõ·¿û7¶O娮£~ìWo¥ºäÒ‹­?æùaýëö·dÉ#Z‘!U©þ7ù>1û3a>ðÁ÷ûÚ'Ò_ô bCl’}¸­ŒÒ§îh÷?võö©‡©æáÅõ}yñÎ>+SÍcÅÏ39h« â>é×^ý½ÑÇ~Þï{oÚ~»m­Aÿ‹û)[T[¾š6>ÿÝpÖ†—ª·-ø(ë¯éŠÿµ,±þ^ }¥£Ng¿òÀÿÿs' “噿øŸ³ØÎ9³ŽÏ͸þt©Oé6mÞ¬AÔØgŒ“vÞù¯­}"ÌXêÎ<·©í¿.ÝꬾþÚk¶-ö;l;&NÒ=<ËKM’ý¤¥á寓ĵñéK®nÍ5½°'h} ØÒa­nð8äLò /ªš# øC{Ï<[öñ>þ+Çéh•¿A:œÇ­n-÷#K–š}_Ú§a/ÞÁ­ Œ”0…ù”©óZŒ‚íºË‡,X^î¦M›fõðÁU±åÑŒYfˆÏ8ít©?"Åë¡…‘ÿA’mÞT¿ŸfÏž-*ÜqfŸù ÌEñ[3ø:F$…ÞLͶ¢}'0T5Œ Gó8}ÝGÖÆöe8.Ǩe>Çb.d¨Äš_4ÿ—o5åÕñ’uñõÿÒ¿(½|áð G¿àK(Ö++­í£³O<ñkI"6œ!l•…õÏïÙi'+ƒy Ê%‚ð@DyÚ¹{DVÇn=ð‹î§ËeÝÿÔoOµkõa>;ïü7éÇ·i<«‹ëÉ—]ÙEÀŒTÓç¾ Æè ˆ|ýaï裒4¬ÿt¶ë^?±]Ӿϓѳñ ÊÕ0ª*(òùf}À;ÿüóÓ†uë­þxIDÎ:ë,io¼þ†Õÿ‰'ž”lÕùÔ¦…ûiµÒò(a™þmæÚÎßp`£ÿÁ—p´?ß/Ø9í÷ÓŒ«¯©M—5Þý^ºÿþûr0ð@Íúホpfp¨òø%Ë!lShµcR5‡e ¥}圮ߥïPÞGÉ/ø2œ_ÈÁgû§®ÀÜþ®¿NG¬œYCCùA4e´Žþ¨…à•}lý²µÍÁšÿÈõG½dŠv6Äô[c3ËO©¾PÖö>ø ÉVl8CãÆ*i÷Ük¯è.W \•ýÿliç„ÜyöÙfÑÊiAZÑ÷…óØë¥Oú)ù`s©NW-j1& –®Å i¥ñY&ô×ĉ“’î*¢ç ü~´½ÃG’ÃÑ¿m["d³n_8úh "gŤ4ù¬¼­âö¯iµý¡ït§Å²-²öÅ¢u³Oôç¼óÎ?O‚;ŠC¿[µ*!0Òùë}ç¹ç~3C^0z½4b`EØv”À?úoú3xxuÌøÆ7¾a3ˆÐÿ£,_;ãŒtûí·K›ÂÇ =wÎ<óÌ´“|(Š×î§(êË5ìúçy%MàË/6Œl:ðK †°‹Â°DƒÑÙ—¿§¶±ù~qÆ×4>LÑNòá8>ï”ygOž’~ÿj™ÁÈ“˜éõÍo~3_«åþó×;ïœî¾{ž7±Ux(?ˆ£;ÿâ’à—ââ!좰,¤1H©"Ý·paš¿pAºë®»ö+öÛHJ)ÞAû®Vî²j¶?\ÿr<úhÚ°~„½¥·²þ£µïõPÐÒ_÷Ö_ãxýõüùÚ_¢ý£[¸¨ôoÅ­Íú×öí‰w¨þ¯¾¦}">Ú¢®a{)¨·¬ø` {ãÞpߢE²e!î£9J¶ïlýyÿEþtØÑf÷^܃°ž¥-ŠÍ¬Þ!ÿdÂ](ýÚÁþÇ6Å÷Ý·(=ýôo$&J]5£]ãÀÀáã?žÞwŸÄêî¿Ù9£èÍŸ)n}gûŸÚÎ0z˜ý–©æ,1’”duÆÃ%9ÙˆÉsê‘)Ûp+Ã%}9mÿGh¾œö÷Û¼ý¾žÇ gûÃ/ß·fŸ¦•Шåu¯À¾Ã]ƒ­*o;Ï*çàY‡¿ÂuXg_œ1HéÔÓN“ k¼† hüã²ýé}öÞ[fÙЉ®þG V”ŸuÁTkàÓ®Òa¬S€[¸ÿ¿1oØßEûÓ¦éK\ÉôíOê³÷ÞÉ—øÇiÓAÌ‚Âqî75ØÙ¡‡p?åbY±Aºð mĹ– ½…úÿ1Ï?|0šú㜋P8þÚ+G7„.{æb$ý›Ã^Ëág{¨RlÅóÏéî:vþ¥ÍçvýVê\õgtþC¹21ÔµCÈ_[bžõ­ðMg¿öˆ£éû¡NÊ‚Îÿl‰Îyí®¿|7]ÎЦE!Û`kò?}û³©æR¾–ÚDV)ð0}­§ê1-¡ù 0áPêFV¦È$¤²APLX‰3餥ndeŠLB*TÅ„•8“NêPêFV¦È$¤²APLX‰3餥ndeŠLB*TÅ„•8“NêPêFV¦È$¤²APLX‰3餥ndeŠLB*T¶háÔ¹°ö:¤s„C™UdeŠLB*TÅ„•8“NêPêz–ÖG§éY7*¨©˜–ÐTÃ¥ndeŠLB*TÅ„•8“NêPêFV¦È$¤²APLX‰3餥ndeŠLB*TÅ„•XÈO Ûrêdý0µü3ÏüšfÁ -c"* ˜Òx×tR‡R7²2E&!•S’œ~J,¯?­§˜"0__¦ž†[xK~‘•©AJ~»Êâ³<;O¿ö¿v¦[R?58 e•j; dVÛ©´8.¹ä"yÄú黿ÎMXêÃëXgþ`VšV¤Áù·£Å~deŠLBË€ˆ (&¤tkÏ?–E±¾#Õ¿øà­Ù÷3z®¼ò*)n,{¦È$´ŠQÅ„”nmý‘Ží`Kõ‡&NÔë”v ߎ}M›sb†„–1PLHé[©gÈž¤C ͱDT@1!¥ÌFi'u(u#+SdRÙ  (&¬Ä™tR‡R7²2E&!• ª€bÂJœI'u(u#+SdRÙ  (&¬Ä™tR‡R7²2E&!• ª€bÂJœI'u(u#+SdRÙ  (&¬Ä™tR‡R7²2E&!• ª€bÂJœI'u(u#+SdRÙ  (&ôâ2âM)¡i5U®Ø”ÔœBF„°iÖ8ª2\±)©9…6Œ¡Yk"ª2\±)©9…6ŒaÓ¬qTe¸bSRs mB³ÖDTe¸bSRs m¦Yã¨ÊpŦ¤æÚ0"„f­‰¨ÊpEJ8‚‚‡¡ðâíî|Ô5–1švÉQ•áŠMIÍ)´aDiÌAÛNLê£[\9±¡Í,jN¡ #Bh¹5U®Ø”ÔœBF„°iÖ8ª2\±)©9…6Œ¡Yk"ª2\±)©9…ÆzÛY³f¥Y3g¥Y³3ÌôìY³ÒÌY³ t°n‡¦.yÔ%lJjN¡ #BXgêhUiWÄ2Ž™(/ꂟ”}f¨ç,¥QË…¡³W£PÁ¶ÈöÌÙj/ûöjûK—.©²(F #B˜SÌ_°Ðü|ù“:Tö±T"üï°ãÜlìØ1dP¶Àq늱WöÚµy‰íV%ö¤ª WlJjN¡ #Bè .\°@Îë.þÅ9¬êÏ6ÚÌ¢æÚ0L5Ï#ÞÍþÚæOÔþٮ郶ó¯m}–DFo+qáY­ßrûg/Pò,XɼšShÈ2i T•áŠMIÍ)´aD[ì’¥*Û’šShÈÒX T•áŠMIÍ)´aD[ì’¥*Û’šShÈÒX T•áŠMIÍ)´aD[ì’¥*Û’šShÈÒX T•áŠMIÍ)´aD[ì’¥*Û’šShÈÒX T•áŠMIÍ)´aD[ì’¥*Û’š3pk¼™«uÅPf7'„,'Õ¤Z†1© F9òîP*“¥4)BjEhRAŒrJä:Ü¡T&KiR„ԊФ‚å”Èt¸C©L–Ò¤©¡I1Ê)‘èp‡R™,¥IR+B“ b”S"Ðá¥2YJ“"¤V„&Ä(§D ÃJe²”&E¨Ûña7üÂD­è2Ï`ómÚ/yÌ,¾ûXû}Ò¿ž”NRZüàƒVºaõ/V övìK.–AñTg c‹Ï=fRAŒr*ä:Ü¡T&KiR„ԊФ‚å”Èt¸C©L–Ò¤©¡I1Ê)‘èp‡R™,¥IR+B“ b”S"Ðá¥2YJ“Røüóϧ3Î8#¬åÆ å»vØ!zè¸tÛ­·¥M›ÚÖÆú|îÐÑØ§N ™VÍ(§F ÃJe²”&EH­M*ˆQ¢„þmüIÿšûk¯¤¼{þñ® Gé¥IR+B“ b”S"Ðá¥2YJ“"¤V„&Ä(§D ÃJe²”&EH­M*ˆQN‰<@‡;”Êd)MŠZšT£œy€w(•ÉRš!µ"4© F9%òîP*“¥4)BjEhRAŒrJä:Ü¡T&KiR„ԊФ‚å”Èt¸C©L–Ò¤©¡I1Ê)‘èp‡R™,¥IR+B“ b”S"Ðá¥2YJ“"¤V„&Ä(§D ÃJe²”&EH­)íiÞ$£’§Ú5È%ô)°g=ùÕÂvwëìÃgôSôŸ§Ú5È%ô):ÿwíí¢»þè ¹Bºþ§ô8â˜à؉dª]ƒ\˜´»þè—?ñõ7Øœ^{ãõHŠ%ëî¿h³æØ€Õ®A.¡K€\»çŸì?qû·Íì¬8åŒË©k?þl¶kKèStç¿kÿl]û§'ä éž¿tÄ»vŠv{”Š¢&ï3„¦FcD$ˆ0$*·PÔ¤]BS¥‚1"ÄF•[(jÒ.¡©RÁ b# ‰Ê-5i—ÐT©`Œˆ±†D劚´KhªT0FD‚ØC¢r EMÚ%4U*#"Al„!Q¹…¢&íš*Œ‘ 6¨ÜBQ“v M• ƈHaHTn¡¨I»„¦JcD$ˆ0$*·PÔ¤]BS¥‚1"ÄF•[(jÒ.¡©RÁ b# ‰Ê-5i—ÐT©`Œˆ±†D劚´KhªT0FD‚ØC¢r EMÚ%4U*#"Al„!Q¹…¢&íš*Œ‘ 6¨ÜBQ“v M• ƈHaHTn¡¨I»„¦JcD$ˆ0$*·PÔ¤]BS¥‚1"ÄF•[(jƒþ×Å ’IDATÒ.¡©RÁ b# ‰Ê-5i—ÐT©`Œˆ±†D劚´KhªT0FD‚ØC¢r EMÚ%4U*#"Al„!Q¹…¢&íš*Œ‘ 6¨ÜBQ“v M• ƈHaHTn¡¨I»„¦JcD$ˆ0$*·PÔ¤]BS¥‚1"ÄF•[(jÒ.¡©RÁ b# ‰Ê-5i—ÐT©`Œˆ±†D劚´KhªT0FD‚ØC¢r EMÚ%4U*#"Al„!Q¹…¢&íš*Œá·ËLÕ‰š×ÈÕåÖŠ–¼ó_;Zôƒ­¶2uö[¼6«ó?½Óµ?Ü)-‚ž)°»þà‹è¡à“®ÿ)eTXñ¥`]ÿ_µ®èÄÐÖ²(ðºö¶EªktQwýu÷?t>åŠ`Ë(0ô5™x]ÿSœ5*¬x[°Îÿ]ûKïÒHŠw¯z$å5È5¼ÆÔ(#¤•* #;ûê)ç/C 1omÉ›ÿé3Â-y¬kê)ç/C éÚ_ðÀ0¿@‰2®ýy¯7 Ñ]ÚR\{1ÔÊmÃøÞÓQ'R>»Îÿêç!C ñs×xÅ’iU'R>]çõó¡†x‡uþ·{Lå!é3BÕ‰”O×µ?õó¡†x‡uí¯kU{𤶙^yÌBkK†øT¼@ë|@—… œQ2й”sVñKâFg?ø£&2ÝùßšOF‚£ÜåPñëdîµkÁc5‘é®ýÕ )8ªkæCj‡5ºë/ø£&2Ý]Ö|2Õ]æCj‡5ºë/ø£&2Ý]Ö|ºëO<J×ÿ˜; ©ŒÑQã/«ÿ±owÆss`µª Yµo&aJ2H+ôÿÔ¤–òŠÕ‚ymÁc×lÒ ý¿%éìwþ÷Ám@Òµ?^?ÁA †j6i…þßçH-å¯Ìk “˜¸f“Vèÿ-I£2ÅjÁ¼vg¿á2sýMiïyò¨S7¦âõ‚ÝŒ53AÍ&­Ðÿû©¥¼bµ`^»ÕPVˆ9•šy˵NÑêìÃÅëËî- éD‘ÕlÒ ýɪó?½¤>)^/˜÷V«£³BÌ©xÖ{¾Ö)Z}x x½`Ù½4(²šMZ¡ÿ/Yuþ§—Ô'ÅëóÞjutVˆ9ÏzÏ×:E«³¯,»·€¦EV³I+ôÿ%«?¶ÿåÅ›_áBYZ{¾rv2)pÉÀ×DSÝJœÉ’Ü)7PeÈ¿“uö1› ;$øÎ-3 Ú=¯Ü’ÜeÐ@•!ÿNÖù¿ó×þò®‹îúëúŸ®ÿöqÎߺûO¾{g¹¾DQ÷ïdÝý·»ÿv÷ß|A„뢻ÿv÷ß?ßû¯ñö·B{ÛvLߪó+ºg™¦{}7¹!ÒòŸ¥hE\•{Fg_¼á]bNìüoni"]ûÞp¬ b¾#Û3ºëo¸»ëÏZJé®¿á ‡šBóÙžÑ]ÃÝØ]ÖRšHwý o8¼Ðºë°¶C·xF×ÿ oF]ÿc-¥‰týOn8îÅ[½d¾òמgÊuè‚*' ù„¼œ T‰—nµCP¥ñï$"Ÿ9¨/7Üj;† JãßIE>!s*P%^n¸!Ôv A•Æ¿“Š|BæT J¼ÜpC¨í‚*' ù„Ì©@•x¹á†PÛ1UÿN(ò ™S*ñrà ¡¶cª4þ$Pä2§Uâå†BmÇTiü;I È'dNªÄË 7„ÚŽ!¨Òøw’@‘OÈœ T‰—nµCP¥ñï$"Ÿ9¨/7Üj;† JãßIE>!s*P%^n¸!Ôv A•Æ¿“Š|BæT J¼ÜpC¨í‚*' ù„Ì©@•x¹á†PÛ1UÿN(ò ™S*ñrà ¡¶cª4þ$Pä2§Uâå†BmÇTiü;I È'dNªÄË 7„ÚŽ!¨Òøw’@‘OÈœ T‰—nµCP¥ñï$"Ÿ9¨/7Üj;† JãßIE>!s*P%^n¸!Ôv A•Æ¿“Š|BæT J¼ÜpC¨í‚*' ù„Ì©@•x¹á†PÛ1UÿN(ò ™S*ñrà ¡¶cª4þ$Pä2§Uâå†BmÇTiü;I È'dNªÄË 7„ÚŽ!¨Òøw’@‘OÈœ T‰—nµCP¥ñï$"Ÿ9¨/7Üj;† JãßIE>!s*P%^n¸!Ôv A•Æ¿“Š|BæT J¼ÜpC¨í‚*' ù„Ì©@•x¹á†PÛ1UÿN(ò ™S*ñrà ¡¶cª4þ$PäêTó–ü„Å9`> ¦dšßJ“™au©ËêÎ~9¡t!ýNXó[i23Œ ó?Ýc>- à ¡R†5¿•&3Ã:ÿÓ=tm×ÿÐÝõOOÔmd¿ÖšÌ #è®?ºÇ|Z†B¥ k~+Mf†tþ§{èÚ®ÿ£'ºþž¨ÛÈ0~­'4™FÐ]tù´0 3„JÖüVšÌ #èüO÷еÿû¿2âí*ÉzÒ„^F¼È F¡HbÇèìÓUÒC„A˜‰"+X­'’†Ø1:ÿ×.š"lS*²‚Õz"iˆ£óí²Îÿ]ÿÛÝòUázŠÆuRd«•DÒ;F×ÿÔ.š"lS*²‚Õz"iˆ£óí²Îÿ]ÿßõÿùªp=Eã:)²‚ÕJ"iˆ£ëj— M¶)YÁj=‘8qyñöšN!X÷:Y°qSdÖI£4S²æ}ÍZTÓ–iSÐä˜rA:û.j`q‹aµkz¨bùPc*mHçÿÎÿvKmi u{«iKÒ49¦\®ýuí¯kåz¨±ú"ªiÓo šS.Hwýu×_wý•ë¡Æê‹¨¦M¿)hrL¹ Ýõ×]ÝõW®‡«/¢š6ý¦ É1å‚ü_¥ÿ» •DzIIEND®B`‚ROCm-AMDMIGraphX-46524e8/docs/dev/000077500000000000000000000000001510465702400162465ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/dev/contributing-to-migraphx.rst000066400000000000000000000171471510465702400237560ustar00rootroot00000000000000.. meta:: :description: MIGraphX provides an optimized execution engine for deep learning neural networks :keywords: MIGraphX, ROCm, library, API .. _contributing-to-migraphx: ========================== Developing for MIGraphX ========================== This document is intended for anyone who wants to contribute to MIGraphX. This document covers some basic operations that can be used to develop for MIGraphX. The complete source code for the example shown here can be found at `ref_dev_examples.cpp `_ on the MIGraphX repository. More examples can be found on `the MIGraphX GitHub repository `_. Adding two literals ---------------------------- A program is a collection of modules, which are collections of instructions to be executed when calling :cpp:any:`eval `. Each instruction has an associated :cpp:any:`operation ` which represents the computation to be performed by the instruction. We start with a snippet of the simple ``add_two_literals()`` function:: // create the program and get a pointer to the main module migraphx::program p; auto* mm = p.get_main_module(); // add two literals to the program auto one = mm->add_literal(1); auto two = mm->add_literal(2); // make the add operation between the two literals and add it to the program mm->add_instruction(migraphx::make_op("add"), one, two); // compile the program on the reference device p.compile(migraphx::ref::target{}); // evaulate the program and retreive the result auto result = p.eval({}).back(); std::cout << "add_two_literals: 1 + 2 = " << result << "\n"; In the above function, a simple :cpp:any:`program ` object is created along with a pointer to the main module of it. The program is a collection of modules which starts execution from the main module, so instructions are added to the modules rather than the program object directly. The :ref:`add_literal ` function is used to add an instruction that stores the literal number ``1`` while returning an :cpp:any:`instruction_ref `. The returned :cpp:any:`instruction_ref ` can be used in another instruction as an input. The same :ref:`add_literal ` function is used to add the literal ``2`` to the program. After the literals are created, the instruction is created to add the numbers. This is done by using the :ref:`add_instruction ` function with the ``add`` :cpp:any:`operation ` created by ``make_op`` and the previously created literals passed as the arguments for the instruction. You can run this :cpp:any:`program ` by compiling it for the reference target (CPU) and then running it with :cpp:any:`eval `. This prints the result on the console. To compile the program for the GPU, move the file to ``test/gpu/`` directory and include the given target:: #include Adding Parameters ---------------------------- While the ``add_two_literals()`` function above demonstrates add operation on constant values ``1`` and ``2``, the following program demonstrates how to pass a parameter (``x``) to a module using ``add_parameter()`` function . migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {1}}; // add parameter "x" with the shape s auto x = mm->add_parameter("x", s); auto two = mm->add_literal(2); // add the "add" instruction between the "x" parameter and "two" to the module mm->add_instruction(migraphx::make_op("add"), x, two); p.compile(migraphx::ref::target{}); In the code snippet above, an add operation is performed on a parameter of type ``int32`` and literal ``2`` followed by compilation for the CPU. To run the program, pass the parameter as a ``parameter_map`` while calling :cpp:any:`eval `. To map the parameter ``x`` to an :cpp:any:`argument ` object with an ``int`` data type, a ``parameter_map`` is created as shown below:: // create a parameter_map object for passing a value to the "x" parameter std::vector data = {4}; migraphx::parameter_map params; params["x"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); std::cout << "add_parameters: 4 + 2 = " << result << "\n"; EXPECT(result.at() == 6); Handling Tensor Data ---------------------------- The above two examples demonstrate scalar operations. To describe multi-dimensional tensors, use the :cpp:any:`shape ` class to compute a simple convolution as shown below:: migraphx::program p; auto* mm = p.get_main_module(); // create shape objects for the input tensor and weights migraphx::shape input_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; migraphx::shape weights_shape{migraphx::shape::float_type, {3, 3, 3, 3}}; // create the parameters and add the "convolution" operation to the module auto input = mm->add_parameter("X", input_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}}), input, weights); Most programs take data from allocated buffers that are usually on the GPU. To pass the buffer data as an argument, create :cpp:any:`argument ` objects directly from the pointers to the buffers:: // Compile the program p.compile(migraphx::ref::target{}); // Allocated buffers by the user std::vector a = ...; std::vector c = ...; // Solution vector std::vector sol = ...; // Create the arguments in a parameter_map migraphx::parameter_map params; params["X"] = migraphx::argument(input_shape, a.data()); params["W"] = migraphx::argument(weights_shape, c.data()); // Evaluate and confirm the result auto result = p.eval(params).back(); std::vector results_vector(64); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, sol)); An :cpp:any:`argument ` can handle memory buffers from either the GPU or the CPU. When running the :cpp:any:`program `, buffers are allocated on the corresponding target by default. By default, the buffers are allocated on the CPU when compiling for CPU and on the GPU when compiling for GPU. To locate the buffers on the CPU even when compiling for GPU, set the option ``offload_copy=true``. Importing From ONNX ---------------------------- To make it convenient to use neural networks directly from other frameworks, MIGraphX ONNX parser allows you to build a :cpp:any:`program ` directly from an ONNX file. For usage, refer to the ``parse_onnx()`` function below:: program p = migraphx::parse_onnx("model.onnx"); p.compile(migraphx::gpu::target{}); Build this example ---------------------------- Build the `ref_dev_examples.cpp `_ example with this command: make -j$(nproc) test_ref_dev_examples This creates the ``test_ref_dev_examples`` under ``bin/`` in the build directory. To verify the build, use: make -j$(nproc) check ROCm-AMDMIGraphX-46524e8/docs/dev/data.rst000066400000000000000000000014351510465702400177140ustar00rootroot00000000000000.. meta:: :description: MIGraphX internal data types :keywords: MIGraphX, code base, contribution, developing, data types Data types ========== shape ----- .. doxygenstruct:: migraphx::internal::shape :members: :undoc-members: literal ------- .. doxygenstruct:: migraphx::internal::literal :members: :undoc-members: argument -------- .. doxygenstruct:: migraphx::internal::argument :members: :undoc-members: raw_data -------- .. doxygenstruct:: migraphx::internal::raw_data :members: :undoc-members: .. doxygenfunction:: migraphx::internal::visit_all(T&& x, Ts&&... xs) .. doxygenfunction:: migraphx::internal::visit_all(const std::vector& x) tensor_view ----------- .. doxygenstruct:: migraphx::internal::tensor_view :members: :undoc-members: ROCm-AMDMIGraphX-46524e8/docs/dev/dev_intro.rst000066400000000000000000000177551510465702400210100ustar00rootroot00000000000000.. meta:: :description: MIGraphX introduction to developing for the code base :keywords: MIGraphX, code base, contribution, developing, introduction, developers Developer Introduction ====================== MIGraphX provides an optimized execution engine for deep learning neural networks. We will cover some simple operations in the MIGraphX framework here. For a quick start guide to using MIGraphX, look in the examples directory: ``https://github.com/ROCmSoftwarePlatform/AMDMIGraphX/tree/develop/examples/migraphx``. Location of the Examples ------------------------- The ``ref_dev_examples.cpp`` can be found in the test directory (``/test``). The executable file ``test_ref_dev_examples`` based on this file will be created in the ``bin/`` of the build directory after running ``make -j$(nproc) test_ref_dev_examples``. The executable will also be created when running ``make -j$(nproc) check``, alongside with all the other tests. Directions for building MIGraphX from source can be found in the main README file: ``https://github.com/ROCmSoftwarePlatform/AMDMIGraphX#readme``. Adding Two Literals -------------------- A program is a collection of modules, which are collections of instructions to be executed when calling :cpp:any:`eval `. Each instruction has an associated :cpp:any:`operation ` which represents the computation to be performed by the instruction. We start with a snippet of the simple ``add_two_literals()`` function:: // create the program and get a pointer to the main module migraphx::program p; auto* mm = p.get_main_module(); // add two literals to the program auto one = mm->add_literal(1); auto two = mm->add_literal(2); // make the add operation between the two literals and add it to the program mm->add_instruction(migraphx::make_op("add"), one, two); // compile the program on the reference device p.compile(migraphx::ref::target{}); // evaulate the program and retreive the result auto result = p.eval({}).back(); std::cout << "add_two_literals: 1 + 2 = " << result << "\n"; We start by creating a simple :cpp:any:`migraphx::program ` object and then getting a pointer to the main module of it. The program is a collection of ``modules`` that start executing from the main module, so instructions are added to the modules rather than directly onto the program object. We then use the :cpp:any:`add_literal ` function to add an instruction that stores the literal number ``1`` while returning an :cpp:any:`instruction_ref `. The returned :cpp:any:`instruction_ref ` can be used in another instruction as an input. We use the same :cpp:any:`add_literal ` function to add a ``2`` to the program. After creating the literals, we then create the instruction to add the numbers together. This is done by using the :cpp:any:`add_instruction ` function with the ``"add"`` :cpp:any:`operation ` created by :cpp:any:`make_op ` along with the previous `add_literal` :cpp:any:`instruction_ref ` for the input arguments of the instruction. Finally, we can run this :cpp:any:`program ` by compiling it for the reference target (CPU) and then running it with :cpp:any:`eval ` The result is then retreived and printed to the console. We can compile the program for the GPU as well, but the file will have to be moved to the ``test/gpu/`` directory and the correct target must be included:: #include Using Parameters ----------------- The previous program will always produce the same value of adding ``1`` and ``2``. In the next program we want to pass an input to a program and compute a value based on the input. We can modify the program to take an input parameter ``x``, as seen in the ``add_parameter()`` function:: migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {1}}; // add a "x" parameter with the shape s auto x = mm->add_parameter("x", s); auto two = mm->add_literal(2); // add the "add" instruction between the "x" parameter and "two" to the module mm->add_instruction(migraphx::make_op("add"), x, two); p.compile(migraphx::ref::target{}); This adds a parameter of type ``int32``, and compiles it for the CPU. To run the program, we need to pass the parameter as a ``parameter_map`` when we call :cpp:any:`eval `. We create the ``parameter_map`` by setting the ``x`` key to an :cpp:any:`argument ` object with an ``int`` data type:: // create a parameter_map object for passing a value to the "x" parameter std::vector data = {4}; migraphx::parameter_map params; params["x"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); std::cout << "add_parameters: 4 + 2 = " << result << "\n"; EXPECT(result.at() == 6); Handling Tensor Data --------------------- In the previous examples we have only been dealing with scalars, but the :cpp:any:`shape ` class can describe multi-dimensional tensors. For example, we can compute a simple convolution:: migraphx::program p; auto* mm = p.get_main_module(); // create shape objects for the input tensor and weights migraphx::shape input_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; migraphx::shape weights_shape{migraphx::shape::float_type, {3, 3, 3, 3}}; // create the parameters and add the "convolution" operation to the module auto input = mm->add_parameter("X", input_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}}), input, weights); Here we create two parameters for both the ``input`` and ``weights``. In the previous examples, we created simple literals, however, most programs will take data from allocated buffers (usually on the GPU). In this case, we can create :cpp:any:`argument ` objects directly from the pointers to the buffers:: // Compile the program p.compile(migraphx::ref::target{}); // Allocated buffers by the user std::vector a = ...; std::vector c = ...; // Solution vector std::vector sol = ...; // Create the arguments in a parameter_map migraphx::parameter_map params; params["X"] = migraphx::argument(input_shape, a.data()); params["W"] = migraphx::argument(weights_shape, c.data()); // Evaluate and confirm the result auto result = p.eval(params).back(); std::vector results_vector(64); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, sol)); An :cpp:any:`argument ` can handle memory buffers from either the GPU or the CPU. By default when running the :cpp:any:`program `, buffers are allocated on the corresponding target. When compiling for the CPU, the buffers by default will be allocated on the CPU. When compiling for the GPU, the buffers by default will be allocated on the GPU. With the option ``offload_copy=true`` set while compiling for the GPU, the buffers will be located on the CPU. Importing From ONNX -------------------- A :cpp:any:`program ` can be built directly from an onnx file using the MIGraphX ONNX parser. This makes it easier to use neural networks directly from other frameworks. In this case, there is an ``parse_onnx`` function:: program p = migraphx::parse_onnx("model.onnx"); p.compile(migraphx::gpu::target{}); ROCm-AMDMIGraphX-46524e8/docs/dev/matchers.rst000066400000000000000000000053241510465702400206120ustar00rootroot00000000000000.. meta:: :description: MIGraphX internal matchers :keywords: MIGraphX, code base, contribution, developing, matchers Matchers ======== Introduction ------------ The matchers provide a way to compose several predicates together. A matcher such as ``m(m1, m2)`` first checks a match for ``m`` followed by a match for ``m1`` and ``m2`` subsequently. The most commonly used matcher is the ``name`` matcher. It matches the instruction with the operator equal to the name specified:: auto match_sum = name("sum"); The above matcher finds ``sum`` operators. To find ``sum`` operators with the output ``standard_shape``, use: auto match_sum = name("sum")(standard_shape()); Arguments --------- To match arguments in the instructions, match each argument using the ``arg`` matcher:: auto match_sum = name("sum")(arg(0)(name("@literal"), arg(1)(name("@literal")))); The above matcher matches a ``sum`` operator with two arguments that are literals. Note that the ``args`` matcher eliminates the need to write ``arg(0)`` and ``arg(1)`` everytime:: auto match_sum = name("sum")(args(name("@literal"), name("@literal"))); Binding ------- To reference other instructions encountered while traversing through the instructions, use ``.bind``:: auto match_sum = name("sum")(args( name("@literal").bind("one"), name("@literal").bind("two") )).bind("sum"); This associates the instruction to a name that can be read from the ``matcher_result`` when it matches. Finding matches --------------- To use the matchers to find instructions, write a callback object that contains the matcher and an ``apply`` function that takes the ``matcher_result`` when the match is found:: struct match_find_sum { auto matcher() const { return name("sum"); } void apply(program& p, matcher_result r) const { // Do something with the result } }; find_matches(prog, match_find_sum{}); Creating matchers ----------------- The macros ``MIGRAPH_BASIC_MATCHER`` and ``MIGRAPH_PRED_MATCHER`` help in the creation of the matchers. Here is how you can create a matcher for shapes that are broadcasted:: MIGRAPH_PRED_MATCHER(broadcasted_shape, instruction_ref ins) { return ins->get_shape().broadcasted(); } For parameters to the predicate, use ``make_basic_pred_matcher`` to create the matcher. Here is how you can create a matcher to check the number of dimensions of the shape:: inline auto number_of_dims(std::size_t n) { return make_basic_pred_matcher([=](instruction_ref ins) { return ins->get_shape().lens().size() == n; }); } ROCm-AMDMIGraphX-46524e8/docs/dev/onnx_operators.rst000066400000000000000000002411101510465702400220570ustar00rootroot00000000000000.. meta:: :description: MIGraphX supported ONNX operators :keywords: MIGraphX, code base, contribution, developing, ONNX operators Supported ONNX Operators ======================== MIGraphX supports operators up to Opset 19. Latest information of ONNX operators can be found in `the ONNX GitHub repository `_. MIGraphX supports the following ONNX data types: BOOL, UINT8, UINT16, UINT32, UINT64, INT8, INT16, INT32, INT64, FLOAT8, FLOAT16, FLOAT32, and DOUBLE .. Note:: FP8 support is only for E4M3FNUZ, see `Float stored in 8 bits `_ in the ONNX documentation. See below for the support matrix of ONNX operators in MIGraphX. .. Note:: The listed supported types are taken from the ONNX specification. An operator might support other additional datatypes. Operator Support Matrix ----------------------- +--------------------------+-----------+-----------------+------------------------------+ | Operator | Supported | Supported Types | Limitations | +==========================+===========+=================+==============================+ | Abs | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Acos | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Acosh | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Add | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | And | ✅ | BOOL | | +--------------------------+-----------+-----------------+------------------------------+ | ArgMax | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | ArgMin | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Asin | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Asinh | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Atan | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Atanh | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | AveragePool | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | BatchNormalization | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | BiasGelu | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Bernoulli | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | BitShift | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | BitwiseAnd | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | BitwiseNot | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | BitwiseOr | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | BitwiseXor | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | BlackmanWindow | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Cast | ✅ | BOOL, UINT8, | ``saturate`` | | | | UINT16, UINT32, | not supported | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | CastLike | ✅ | BOOL, UINT8, | ``saturate`` | | | | UINT16, UINT32, | not supported | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Ceil | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Celu | ✅ | FP32 | | +--------------------------+-----------+-----------------+------------------------------+ | CenterCropPad | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Clip | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Col2Im | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Compress | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Concat | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | ConcatFromSequence | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Constant | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | ConstantOfShape | ✅ | BOOL, UINT8, | dynamic shape | | | | UINT16, UINT32, | is not | | | | UINT64, INT8, | supported | | | | INT16, INT32, | | | | | INT64, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Conv | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | ConvInteger | ✅ | INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | ConvTranspose | ✅ | FP8, FP16, | ``auto padding`` | | | | FP32, FP64 | not supported, | | | | | ``output_padding`` | | | | | math differs | +--------------------------+-----------+-----------------+------------------------------+ | Cos | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Cosh | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | CumSum | ✅ | UINT32, UINT64, | ``axis`` | | | | INT32, INT64, | dynamic shape | | | | FP8, FP16, | is not | | | | FP32, FP64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | DFT | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | DeformConv | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | DepthToSpace | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | DequantizeLinear | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Det | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Div | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Dropout | ✅ | Any | Changed to | | | | | ``identity`` | +--------------------------+-----------+-----------------+------------------------------+ | DynamicQuantizeLinear | ✅ | FP8, FP16, | ``uint8`` | | | | FP32, FP64 | quantization | | | | | only, dynamic | | | | | shape is not | | | | | supported | +--------------------------+-----------+-----------------+------------------------------+ | Einsum | ✅ | Any | more than 1 diagonal per | | | | | input is not supported | | | | | e.g. ``iijj->ij`` | | | | | | | | | | batch diagonal where batches | | | | | are not the leading dims is | | | | | not supported | | | | | e.g. ``ii...->i...`` | +--------------------------+-----------+-----------------+------------------------------+ | Elu | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Equal | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Erf | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Exp | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Expand | ✅ | BOOL, UINT8, | dynamic shape | | | | UINT16, UINT32, | is not | | | | UINT64, INT8, | supported | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | EyeLike | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | FastGelu | ✅ | FP8, FP16, FP32 | | +--------------------------+-----------+-----------------+------------------------------+ | Flatten | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Floor | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Gather | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | GatherElements | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | GatherND | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Gelu | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Gemm | ✅ | UINT32, UINT64, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | GlobalAveragePool | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | GlobalLpPool | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | GlobalMaxPool | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Greater | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | GreaterOrEqual | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | GridSample | ✅ | UINT32, UINT64, | `5-D inputs` | | | | INT32, INT64, | not supported | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | GroupNormalization | ✅ | FP8, FP16, | ``stash_type`` | | | | FP32, FP64 | not supported | +--------------------------+-----------+-----------------+------------------------------+ | GRU | ✅ | FP8, FP16, | ``Affine``, | | | | FP32, FP64 | ``ThresholdRelu``, | | | | | ``ScaledTanh``, | | | | | ``HardSigmoid``, | | | | | ``Softsign``, | | | | | ``Softplus`` | | | | | optional | | | | | activation | | | | | functions are | | | | | not enabled | +--------------------------+-----------+-----------------+------------------------------+ | HammingWindow | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | HannWindow | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | HardSigmoid | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | HardSwish | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Hardmax | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Identity | ✅ | BOOL, UINT8, | ``identity``, | | | | UINT16, UINT32, | ``sequence`` | | | | UINT64, INT8, | datatypes are | | | | INT16, INT32, | not supported | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | If | ✅ | BOOL, UINT8, | ``identity``, | | | | UINT16, UINT32, | ``sequence`` | | | | UINT64, INT8, | datatypes are | | | | INT16, INT32, | not supported | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | InstanceNormalization | ✅ | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | IsInf | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | IsNaN | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | LayerNormalization | ✅ | FP8, FP16, | ``stash_type`` | | | | FP32, FP64 | not supported | +--------------------------+-----------+-----------------+------------------------------+ | LeakyRelu | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Less | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | LessOrEqual | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Log | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | LogSoftmax | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Loop | ✅ | UINT8, UINT16, | ``identity``, | | | | UINT32, UINT64, | ``sequence`` | | | | INT8, INT16, | datatypes are | | | | INT32, INT64, | not supported, | | | | FP8, FP16, | ``max_iteration`` | | | | FP32, FP64 | has upper-bound | +--------------------------+-----------+-----------------+------------------------------+ | LRN | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | LSTM | ✅ | FP32, FP16 | ``Affine``, | | | | | ``ThresholdRelu``, | | | | | ``ScaledTanh``, | | | | | ``HardSigmoid``, | | | | | ``Softsign``, | | | | | ``Softplus`` | | | | | optional | | | | | activation | | | | | functions are | | | | | not enabled | +--------------------------+-----------+-----------------+------------------------------+ | LpNormalization | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | LpPool | ✅ | FP32, FP16, | ``lpnorm`` not | | | | FP8, INT8 | supported | | | | | pooling mode on | | | | | GPU (MIOpen | | | | | limitation) | +--------------------------+-----------+-----------------+------------------------------+ | MatMul | ✅ | UINT32, UINT64, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | MatMulInteger | ✅ | UINT8, INT8 | dynamic shape | | | | | is not | | | | | supported | +--------------------------+-----------+-----------------+------------------------------+ | Max | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | MaxPool | ✅ | FP32, FP16, | ``storage_order`` | | | | FP8, INT8 | not supported, | | | | | ``dialtion`` is | | | | | partially | | | | | supported on | | | | | GPU (MIOpen | | | | | limitation), | | | | | ``indices`` 2nd | | | | | ouput not | | | | | supported | +--------------------------+-----------+-----------------+------------------------------+ | MaxRoiPool | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | MaxUnpool | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Mean | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | MeanVarian | ✅ | FP8, FP16, | | | ceNormalization | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | MelWeightMatrix | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Min | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Mish | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Mod | ✅ | UINT8, UINT16, | ``int8``, | | | | UINT32, UINT64, | ``int32`` | | | | INT16, INT64, | `issue `__ | | | | | on GPU | +--------------------------+-----------+-----------------+------------------------------+ | Mul | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT16, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Multinomial | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Neg | ✅ | INT8, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | NegativeLogLikelihoodLoss| ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | NonMaxSuppression | ✅ | FP8, FP16, | fixed output | | | | FP32, FP64 | size unless | | | | | ``use_dyn_output`` | | | | | set | +--------------------------+-----------+-----------------+------------------------------+ | NonZero | ✅ | FP8, FP16, | fixed output | | | | FP32, FP64 | size unless | | | | | ``use_dyn_output`` | | | | | set | +--------------------------+-----------+-----------------+------------------------------+ | Not | ✅ | BOOL | | +--------------------------+-----------+-----------------+------------------------------+ | OneHot | ✅ | UINT8, UINT16, | dynamic shape | | | | UINT32, UINT64, | is not | | | | INT16, INT64, | supported | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Optional | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | OptionalGetElement | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | OptionalHasElement | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Or | ✅ | BOOL | | +--------------------------+-----------+-----------------+------------------------------+ | Pad | ✅ | BOOL, UINT8, | ``edge``, | | | | UINT16, UINT32, | ``warp`` | | | | UINT64, INT8, | padding not | | | | INT16, INT32, | supported, | | | | INT64, FP8, | ``pads`` must | | | | FP16, FP32, | be constant | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Pow | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | PRelu | ✅ | UINT32, UINT64, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | QLinearAdd | ✅ | UINT8, INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | QLinearAveragePool | ✅ | UINT8, INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | QLinearConcat | ✅ | UINT8, INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | QLinearConv | ✅ | UINT8, INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | QLinearGlobalAveragePool | ✅ | UINT8, INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | QLinearLeakyRelu | ✅ | UINT8, INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | QLinearMatMul | ✅ | UINT8, INT8 | non-scalar | | | | | inputs are not | | | | | supported | +--------------------------+-----------+-----------------+------------------------------+ | QLinearMul | ✅ | UINT8, INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | QLinearSigmoid | ✅ | UINT8, INT8 | | +--------------------------+-----------+-----------------+------------------------------+ | QuantizeLinear | ✅ | FP8, FP16, | ``saturate`` | | | | FP32, INT32 | is not supported | | | | | | | | | | | | | | | | +--------------------------+-----------+-----------------+------------------------------+ | RandomNormal | ✅ | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | RandomNormalLike | ✅ | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | RandomUniform | ✅ | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | RandomUniformLike | ✅ | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Range | ✅ | FP16, FP32, | ``start``, | | | | FP64, INT16, | ``end``, | | | | INT32, INT64 | ``delta`` | | | | | dynamic shape | | | | | is not | | | | | supported | +--------------------------+-----------+-----------------+------------------------------+ | Reciprocal | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | ReduceL1 | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceL2 | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceLogSum | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceLogSumExp | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceMax | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceMean | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceMin | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceProd | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceSum | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | ReduceSumSquare | ✅ | FP16, FP32, | ``axes`` | | | | FP64, UINT32, | dynamic shape | | | | UINT64, INT32, | is not | | | | INT64 | supported | +--------------------------+-----------+-----------------+------------------------------+ | Relu | ✅ | FP16, FP32, | | | | | FP64, INT8, | | | | | INT16, INT32, | | | | | INT64 | | +--------------------------+-----------+-----------------+------------------------------+ | Reshape | ✅ | FP32, FP16, | ``allowzero`` | | | | INT32, INT64, | not supported, | | | | FP8, INT8, BOOL | dynamic shape | | | | | is not | | | | | supported | +--------------------------+-----------+-----------------+------------------------------+ | Resize | ✅ | UINT8, UINT16, | ``cubic``, | | | | UINT32, UINT64, | ``half_pixel_symmetric``, | | | | INT8, INT16, | ``tf_crop_and_resize`` | | | | INT32, INT64, | not supported, | | | | FP8, FP16, | ``linear`` mode | | | | FP32, FP64 | not supported for | | | | | non-constant inputs, | | | | | ``exclude_outside`` | | | | | 1 is not supported, | | | | | ``antialias``, | | | | | ``extrapolation_value``, | | | | | ``keep_aspect_ratio_policy`` | | | | | not supported | +--------------------------+-----------+-----------------+------------------------------+ | ReverseSequence | ✅ | BOOL, UINT8, | variable | | | | UINT16, UINT32, | ``sequence_lens`` | | | | UINT64, INT8, | is not supported | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | RNN | ✅ | FP32, FP16 | ``Affine``, | | | | | ``ThresholdRelu``, | | | | | ``ScaledTanh``, | | | | | ``HardSigmoid``, | | | | | ``Softsign``, | | | | | ``Softplus`` | | | | | optional | | | | | activation | | | | | functions are | | | | | not enabled | +--------------------------+-----------+-----------------+------------------------------+ | RoiAlign | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Round | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | STFT | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Scan | ✅ | UINT8, UINT16, | ``identity``, | | | | UINT32, UINT64, | ``sequence`` | | | | INT8, INT16, | datatypes are | | | | INT32, INT64, | not supported, | | | | FP8, FP16, | Number of iterations has | | | | FP32, FP64 | upper-bound | | | | | Version 8 not supported | +--------------------------+-----------+-----------------+------------------------------+ | Scatter (deprecated) | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | ScatterElements | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | ScatterND | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Selu | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | SequenceAt | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | SequenceConstruct | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | SequenceEmpty | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | SequenceErase | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | SequenceInsert | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | SequenceLength | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | SequenceMap | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Shape | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Shrink | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Sigmoid | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Sign | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Sin | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Sinh | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Size | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Slice | ✅ | BOOL, UINT8, | variable inputs | | | | UINT16, UINT32, | are not | | | | UINT64, INT8, | supported | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Softmax | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | SoftmaxCrossEntropyLoss | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Softplus | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Softsign | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | SpaceToDepth | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Split | ✅ | BOOL, UINT8, | dynamic shape | | | | UINT16, UINT32, | is not | | | | UINT64, INT8, | supported | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | SplitToSequence | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Sqrt | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Squeeze | ✅ | BOOL, UINT8, | variable ``axes`` is | | | | UINT16, UINT32, | not supported | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | StringNormalizer | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | Sub | ✅ | UINT8, UINT16, | | | | | UINT32, UINT64, | | | | | INT8, INT16, | | | | | INT32, INT64, | | | | | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Sum | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Tan | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Tanh | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | TfIdfVectorizer | ⌠| | | +--------------------------+-----------+-----------------+------------------------------+ | ThresholdedRelu | ✅ | FP8, FP16, | | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Tile | ✅ | BOOL, UINT8, | dynamic shape | | | | UINT16, UINT32, | is not | | | | UINT64, INT8, | supported | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | TopK | ✅ | UINT8, UINT16, | dynamic ``k`` | | | | UINT32, UINT64, | is not | | | | INT8, INT16, | supported, | | | | INT32, INT64, | ``sorted`` is | | | | FP8, FP16, | not supported | | | | FP32, FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Transpose | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Trilu | ✅ | BOOL, UINT8, | dynamic ``k`` | | | | UINT16, UINT32, | is not | | | | UINT64, INT8, | supported | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Unique | ✅ | Any | only | | | | | ``axis = 0`` is | | | | | supported | +--------------------------+-----------+-----------------+------------------------------+ | Unsqueeze | ✅ | BOOL, UINT8, | variable | | | | UINT16, UINT32, | ``axes`` is not | | | | UINT64, INT8, | supported | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Upsample (deprecated) | ✅ | BOOL, UINT8, | | | | | UINT16, UINT32, | | | | | UINT64, INT8, | | | | | INT16, INT32, | | | | | INT64, FP8, | | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Where | ✅ | BOOL, UINT8, | mixed static | | | | UINT16, UINT32, | and dynamic | | | | UINT64, INT8, | shape inputs | | | | INT16, INT32, | are not | | | | INT64, FP8, | supported | | | | FP16, FP32, | | | | | FP64 | | +--------------------------+-----------+-----------------+------------------------------+ | Xor | ✅ | BOOL | | +--------------------------+-----------+-----------------+------------------------------+ ROCm-AMDMIGraphX-46524e8/docs/dev/operators.rst000066400000000000000000000007141510465702400210200ustar00rootroot00000000000000.. meta:: :description: MIGraphX internal operators :keywords: MIGraphX, code base, contribution, developing, operators Operators ========= operation --------- .. doxygenstruct:: migraphx::internal::operation :members: :undoc-members: .. doxygenfunction:: migraphx::internal::is_context_free .. doxygenfunction:: migraphx::internal::has_finalize operators --------- .. doxygennamespace:: migraphx::internal::op :members: :undoc-members: ROCm-AMDMIGraphX-46524e8/docs/dev/pass.rst000066400000000000000000000031251510465702400177470ustar00rootroot00000000000000.. meta:: :description: MIGraphX internal passes :keywords: MIGraphX, code base, contribution, developing, passes Passes ====== pass ---- .. doxygenstruct:: migraphx::internal::pass :members: :undoc-members: dead_code_elimination --------------------- .. doxygenstruct:: migraphx::internal::dead_code_elimination :members: :undoc-members: eliminate_common_subexpression ------------------------------ .. doxygenstruct:: migraphx::internal::eliminate_common_subexpression :members: :undoc-members: eliminate_concat ---------------- .. doxygenstruct:: migraphx::internal::eliminate_concat :members: :undoc-members: eliminate_contiguous -------------------- .. doxygenstruct:: migraphx::internal::eliminate_contiguous :members: :undoc-members: eliminate_identity ------------------ .. doxygenstruct:: migraphx::internal::eliminate_identity :members: :undoc-members: eliminate_pad ------------- .. doxygenstruct:: migraphx::internal::eliminate_pad :members: :undoc-members: propagate_constant ------------------ .. doxygenstruct:: migraphx::internal::propagate_constant :members: :undoc-members: rewrite_rnn ----------- .. doxygenstruct:: migraphx::internal::rewrite_rnn :members: :undoc-members: schedule -------- .. doxygenstruct:: migraphx::internal::schedule :members: :undoc-members: simplify_algebra ---------------- .. doxygenstruct:: migraphx::internal::simplify_algebra :members: :undoc-members: simplify_reshapes ----------------- .. doxygenstruct:: migraphx::internal::simplify_reshapes :members: :undoc-members: ROCm-AMDMIGraphX-46524e8/docs/dev/program.rst000066400000000000000000000013751510465702400204550ustar00rootroot00000000000000.. meta:: :description: MIGraphX program :keywords: MIGraphX, code base, contribution, developing, program Program ======= instruction ----------- .. doxygenstruct:: migraphx::internal::instruction :members: :undoc-members: instruction_ref --------------- .. cpp:type:: migraphx::internal::instruction_ref References an instruction in the program. program ------- .. doxygenstruct:: migraphx::internal::program :members: :undoc-members: parse_onnx ---------- .. doxygenfunction:: migraphx::internal::parse_onnx parse_tf -------- .. doxygenfunction:: migraphx::internal::parse_tf onnx_options ------------ .. doxygenstruct:: migraphx::internal::onnx_options tf_options ---------- .. doxygenstruct:: migraphx::internal::tf_options ROCm-AMDMIGraphX-46524e8/docs/dev/quantization.rst000066400000000000000000000006351510465702400215320ustar00rootroot00000000000000.. meta:: :description: MIGraphX internal quantization :keywords: MIGraphX, code base, contribution, developing, quantization Quantization ============ quantize_fp16 ------------- .. doxygenfunction:: migraphx::internal::quantize_fp16 quantize_bf16 ------------- .. doxygenfunction:: migraphx::internal::quantize_bf16 quantize_int8 ------------- .. doxygenfunction:: migraphx::internal::quantize_int8 ROCm-AMDMIGraphX-46524e8/docs/dev/targets.rst000066400000000000000000000006711510465702400204550ustar00rootroot00000000000000.. meta:: :description: MIGraphX targets :keywords: MIGraphX, code base, contribution, developing, targets Targets ======= target ------ .. doxygenstruct:: migraphx::internal::target :members: :undoc-members: gpu::target ----------- .. doxygenstruct:: migraphx::internal::gpu::target :members: :undoc-members: cpu::target ----------- .. doxygenstruct:: migraphx::internal::cpu::target :members: :undoc-members: ROCm-AMDMIGraphX-46524e8/docs/dev/tools.rst000066400000000000000000000043261510465702400201450ustar00rootroot00000000000000.. meta:: :description: MIGraphX tools :keywords: MIGraphX, code base, contribution, developing, tooks, knobs .. _tools: Tools ===== roctx.py -------- You can use the :ref:`roctx` command with :doc:`rocprof ` binary to get marker timing information for each MIGraphX operator. To process timing information, use ``roctx.py`` helper script. :: Usage: roctx.py [-h] [--json-path json_path] [--out out] [--study-name study-name] [--repeat repeat] [--parse] [--run run] [--debug] The ``roctx.py`` helper script provides two main functionalities: ``run`` and ``parse``. .. option:: --run Runs ``migraphx-driver roctx`` command with the given ``migraphx-driver`` knobs followed by the parsing of the result which provides GPU kernel timing information. You can pass the MIGraphX knobs via a string to `--run` knob. See the :ref:`_roctx-examples` for usage. .. option:: --parse Parses JSON file in the given ``--json-path`` and provides GPU kernel timing information. .. option:: --out Output folder .. option:: --study-name Optional. Allows user to name a study for easy interpretation. Defaults to timestamp. .. option:: --repeat Number of iterations. Sets to **2** by default. .. option:: --debug Provides additional debug information related to data. Use for debugging purposes only. .. _roctx-examples: **Examples:** **Running inference with rocTX for a given ONNX file:** :: python roctx.py --run '--onnx --gpu fcn-resnet50-11.onnx' --out output_folder --repeat 5 Example output: .. image:: ../data/roctx1.jpg Hotspot kerel timing information: .. image:: ../data/roctx2.jpg The output provides ``SUM``, ``MIN``, ``MAX`` and ``COUNT`` information for each kernel executed for a given model. It also provides the average total time. The following three files are provided for reference: - OUTPUT CSV FILE: Provides a summary of the run which includes utilized MIGraphX knobs and related kernel timing information. - KERNEL TIMING DETAILS: Provides the hotspot kernel timing information. - ALL DATA FROM ALL RUNS: Provides all output data related to all iterations executed during a run. **Parsing an existing JSON file:** :: python roctx.py --parse --json-path ../trace.json ROCm-AMDMIGraphX-46524e8/docs/dev/triage-rocmlir.rst000066400000000000000000000205051510465702400217220ustar00rootroot00000000000000.. meta:: :description: Issue Triaging Guide for suspected issues :keywords: MIGraphX, rocMLIR, issues, pipeline, compilation, bug, code base, kernel, contribution, developing Issue Triaging Guide for suspected rocMLIR issue ================================================ This document serves as a guide to narrow down whether a bug is due to rocMLIR backend of MIGraphX. There are broadly 3 categories of bugs that can be due to rocMLIR. 1. ``[B1]`` rocMLIR compilation bug 2. ``[B2]`` rocMLIR runtime failure 3. ``[B3]`` accuracy issue from rocMLIR Step 1 - Use ``MIGRAPHX_DISABLE_MLIR=1`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First see if the bug persists after disabling MLIR. If so, it is highly likely it is not a MLIR bug but rather a MIGraphX bug. If you dont see a failure, please proceed. Step 2 - See if its a ``B1`` - rocMLIR compilation bug ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MLIR has two pipelines that are used to generate a kernel: highlevel pipeline and backend pipeline Step 2.1 ~~~~~~~~ If the highlevel pipeline fails, you should see error that starts with ``Invalid MLIR created:`` If error message like above is present then proceed with following steps. 1. Please disable threading first by setting environment variable MIGRAPHX_GPU_COMPILE_PARALLEL=1. 2. Set environment variable ``MIGRAPHX_TRACE_MLIR=1`` . 3. Create a temporary directory to store MXR files. Set environment variable ``MIGRAPHX_MLIR_DUMP_TO_MXR=/path/to/temp/dir/created/`` 4. Run the model and pipe the logs to a file. You should see MLIR module printed just before the failure. For example, it would look like as follows. :: module { func.func @mlir_convolution(%arg0: !migraphx.shaped<2x8x3x3xf32, 72x9x3x1>, %arg1: !migraphx.shaped<1x8x4x4xf32, 128x16x4x1>) -> !migraphx.shaped<1x2x2x2xf32, 8x4x2x1> attributes {arch = "gfx90a:sramecc+:xnack-", enable_splitk_for_tuning = true, kernel = "mixr", num_cu = 110 : i64} { %0 = migraphx.convolution %arg1, %arg0 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x8x4x4xf32, 128x16x4x1>, <2x8x3x3xf32, 72x9x3x1> -> <1x2x2x2xf32, 8x4x2x1> return %0 : !migraphx.shaped<1x2x2x2xf32, 8x4x2x1> } } 5. Provide MLIR module printed just before the failure from previous step to rocMLIR team to debug. Please provide only the **SINGLE** module that fails and not the entire log for rocMLIR team figure out which is failing. 6. Set ``MIGRAPHX_TRACE_MLIR=1`` . Run each individual MXR file from MXR dump directory with ``migraphx-driver``. Find out which MXR file is failing to compile. Failing MXR file must have exacty **the same MLIR** module as the one from the original model identified in previous step. Provide this MXR file to rocMLIR team. Step 2.2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the backend pipeline fails for *all solutions*, you should see error that starts with ``No solutions provided for mlir_*`` or ``No valid tuned compilation for mlir_*`` NOTE 1 : Currently MIGraphX does not run applicability pipeline of rocMLIR. Therefore, you will see them for inapplicable tuning params as well. NOTE 2 : Ignore ``MLIR backend compilation failed: "`` if you don't see ``No solutions provided for mlir_*`` If the above error message is present then proceed with following steps. 1. Please disable threading first using ``MIGRAPHX_GPU_COMPILE_PARALLEL=1``. 2. Set environment variable ``MIGRAPHX_TRACE_MLIR=2`` 3. Create a temporary directory to store MXR files. Set environment variable ``MIGRAPHX_MLIR_DUMP_TO_MXR=/path/to/temp/dir/created/`` 4. Run the model and pipe the logs to a file. You should see MLIR module printed just before the failure. For example, it would look like as follows. :: module { func.func @mlir_convolution(%arg0: memref<1x1x32x32x8xf32>, %arg1: memref<1x16x3x3x8xf32>, %arg2: memref<16xf32>, %arg3: memref<1x1x30x30x16xf32>) attributes {kernel, arch = ""} { %0 = memref.alloc() : memref<1x1x30x30x16xf32> rock.conv(%arg1, %arg0, %0) features = dot {arch = "amdgcn-amd-amdhsa:gfx906", dilations = [1 : index, 1 : index], filter_layout = ["g", "k", "0", "1", "c"], input_layout = ["gi", "ni", "0i", "1i", "ci"], output_layout = ["go", "no", "0o", "1o", "ko"], padding = [0 : index, 0 : index, 0 : index, 0 : index], strides = [1 : index, 1 : index]} : memref<1x16x3x3x8xf32>, memref<1x1x32x32x8xf32>, memref<1x1x30x30x16xf32> %4 = memref.expand_shape %arg2 [[0, 1, 2, 3, 4]] output_shape [1, 1, 1, 1, 16] : memref<16xf32> into memref<1x1x1x1x16xf32> linalg.generic {indexing_maps = [#map1, #map2, #map1], iterator_types = ["parallel", "parallel", "parallel", "parallel", "parallel"]} ins(%0, %4 : memref<1x1x30x30x16xf32>, memref<1x1x1x1x16xf32>) outs(%arg3 : memref<1x1x30x30x16xf32>) { ^bb0(%arg4: f32, %arg5: f32, %arg6: f32): %8 = arith.addf %arg4, %arg5 : f32 linalg.yield %8 : f32 } return } } 5. Provide MLIR module printed just before the failure from previous step to rocMLIR team to debug. Please provide only the SINGLE module that fails and not the entire log for rocMLIR team figure out which is failing. 6. Set ``MIGRAPHX_TRACE_MLIR=2`` and ``MIGRAPHX_GPU_COMPILE_PARALLEL=1`` . Run each individual MXR file from MXR dump directory with migraphx-driver. Find out which MXR file is failing to compile. Failing MXR file must have exacty the same MLIR module as the one from the original model identified in previous step. Provide this MXR file to rocMLIR team. Step 3 See if its a ``B2`` - runtime failure of MLIR-generated kernel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each individual kernels are run in the benchmarking process of MIGraphX. Therefore, set ``MIGRAPHX_TRACE_BENCHMARKING=3`` You should see messages like following when compiling model. :: Benchmarking mlir_* : xx configs Problem: Benchmarking solution: In this case there are two things to indentify. (1) MLIR module that is causing the runtime failure (2) PerfConfig which that used when compiling MLIR module. Follow these steps indentify those two things. 1. Set ``MIGRAPHX_TRACE_BENCHMARKING=3`` 2. Set ``MIGRAPHX_MLIR_DUMP_TO_MXR=/path/to/temp/dir/created`` 3. Run model. You should see MLIR module just before MIGraphX starts benchmarking. Please provide that to rocMLIR team. 4. Just before the failure you should also see ``Benchmarking solution : ``. Take note of that and pass that to rocMLIR team. 5. Set ``MIGRAPHX_TRACE_BENCHMARKING=3`` and Run each individual MXR file from temporary dump directory to see which one is failing. a. For the failing MXR, just before the start of the benchmarking process it should print MLIR module. It must be the same as the one identified earlier in step (3). b. Before the failure it would print ``Benchmarking Solution: ``. It must be the same as the one identified from step (4). c. Provide the failing MXR file to rocMLIR team to investigate. 6. Note down whether model was compiled using ``--exhaustive-tune`` or not. Mention that in ticket to rocMLIR. Step 4 See if its a ``B3`` - accuracy issue of MLIR-generated kernel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Create a temporary directory to store MXR files. Set environment variable ``MIGRAPHX_MLIR_DUMP_TO_MXR=/path/to/temp/dir/created/`` 2. Set ``MIGRAPHX_TRACE_MLIR=1`` and ``MIGRAPHX_GPU_COMPILE_PARALLEL=1``. Run each individual MXR file from MXR dump directory with ``migraphx-driver verify`` to find out which one is failing the accuracy. 3. Provide MLIR module from failing MXR to rocMLIR team. For example, it would look something like following :: module { func.func @mlir_dot_add(%arg0: !migraphx.shaped<1x5x4xf32, 20x4x1>, %arg1: !migraphx.shaped<1x4x3xf32, 12x3x1>, %arg2: !migraphx.shaped<1x5x3xf32, 15x3x1>) -> !migraphx.shaped<1x5x3xf32, 15x3x1> attributes {arch = "gfx90a:sramecc+:xnack-", enable_splitk_for_tuning = true, kernel = "mixr", num_cu = 110 : i64} { %0 = migraphx.dot %arg0, %arg1 : <1x5x4xf32, 20x4x1>, <1x4x3xf32, 12x3x1> -> <1x5x3xf32, 15x3x1> %1 = migraphx.add %0, %arg2 : <1x5x3xf32, 15x3x1>, <1x5x3xf32, 15x3x1> -> <1x5x3xf32, 15x3x1> return %1 : !migraphx.shaped<1x5x3xf32, 15x3x1> } } 4. Provide failing MXR file to rocMLIR team. ROCm-AMDMIGraphX-46524e8/docs/doxygen/000077500000000000000000000000001510465702400171455ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/doxygen/Doxyfile000066400000000000000000000021151510465702400206520ustar00rootroot00000000000000# Auto-generated doxygen configuration file BUILTIN_STL_SUPPORT = YES CALLER_GRAPH = YES CALL_GRAPH = YES ENUM_VALUES_PER_LINE = 1 EXCLUDE_PATTERNS = ../../src/targets/gpu/kernels ../../src/targets/gpu/device EXTRACT_ALL = YES FULL_PATH_NAMES = YES GENERATE_LATEX = NO GENERATE_TREEVIEW = YES GENERATE_XML = YES INCLUDE_PATH = ../../src/include ../../src/targets/cpu/include ../../src/targets/gpu/include INPUT = ../../src MACRO_EXPANSION = YES OUTPUT_DIRECTORY = . PREDEFINED = \ DOXYGEN \ MIGRAPHX_EXPORT= \ MIGRAPHX_API_EXPORT= \ MIGRAPHX_GPU_EXPORT= \ MIGRAPHX_CPU_EXPORT= \ MIGRAPHX_ONNX_EXPORT= \ MIGRAPHX_TF_EXPORT= \ MIGRAPHX_C_EXPORT PROJECT_NAME = MIGraphX RECURSIVE = YES REFERENCED_BY_RELATION = YES REFERENCES_LINK_SOURCE = YES REFERENCES_RELATION = YES SEARCH_INCLUDES = YES SORT_MEMBERS_CTORS_1ST = YES SOURCE_BROWSER = YES STRIP_FROM_INC_PATH = ../../src/include ../../src/targets/cpu/include ../../src/targets/gpu/include STRIP_FROM_PATH = USE_PDFLATEX = NO WARN_LOGFILE = DoxygenWarningLog.txt DOT_PATH = HAVE_DOT = NO ROCm-AMDMIGraphX-46524e8/docs/driver/000077500000000000000000000000001510465702400167635ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/driver/compile.rst000066400000000000000000000013251510465702400211460ustar00rootroot00000000000000.. option:: --fill0 [std::vector] Fill parameter with 0s .. option:: --fill1 [std::vector] Fill parameter with 1s .. option:: --gpu Compile on the gpu .. option:: --cpu Compile on the cpu .. option:: --ref Compile on the reference implementation .. option:: --enable-offload-copy Enable implicit offload copying .. option:: --disable-fast-math Disable fast math optimization .. option:: --exhaustive-tune Perform an exhaustive search to find the fastest version of generated kernels for selected backend .. option:: --fp16 Quantize for fp16 .. option:: --bf16 Quantize for bf16 .. option:: --int8 Quantize for int8 .. option:: --fp8 Quantize for Float8E4M3FNUZ type ROCm-AMDMIGraphX-46524e8/docs/driver/read.rst000066400000000000000000000032701510465702400204320ustar00rootroot00000000000000.. option:: File to load .. option:: --test Test MIGraphX with single layer GEMM model .. option:: --onnx Load as onnx .. option:: --tf Load as tensorflow .. option:: --migraphx Load as MIGraphX .. option:: --migraphx-json Load as MIGraphX JSON .. option:: --batch [unsigned int] (Default: 1) For a static model, set batch size. For a dynamic batch model, sets the batch size at runtime. .. option:: --nhwc Treat tensorflow format as nhwc .. option:: --skip-unknown-operators Skip unknown operators when parsing and continue to parse. .. option:: --nchw Treat tensorflow format as nchw .. option:: --trim, -t [unsigned int] Trim instructions from the end (Default: 0) .. option:: --input-dim [std::vector] Dim of a parameter (format: "@name d1 d2 dn") .. option:: --dyn-input-dim [std::vector] Set dynamic dimensions of a parameter using JSON formatting (format "@name" "dynamic_dimension_json") .. option:: --default-dyn-dim Set the default dynamic dimension (format {min:x, max:y, optimals:[o1,o2,...]}) .. option:: --optimize, -O Optimize when reading .. option:: --apply-pass, -p Passes to apply to model .. option:: --graphviz, -g Print out a graphviz representation. .. option:: --brief Make the output brief. .. option:: --cpp Print out the program as cpp program. .. option:: --json Print out program as json. .. option:: --netron Print out program as a Netron viewable json file. .. option:: --text Print out program in text format. .. option:: --binary Print out program in binary format. .. option:: --py Print out program using python API. .. option:: --output, -o [std::string] Output to file. ROCm-AMDMIGraphX-46524e8/docs/environment.yml000066400000000000000000000002371510465702400205610ustar00rootroot00000000000000name: RTD channels: - conda-forge - defaults dependencies: - python=3.10 - pip - doxygen=1.9.8 - pip: - -r ./sphinx/requirements.txt ROCm-AMDMIGraphX-46524e8/docs/gpu-driver/000077500000000000000000000000001510465702400175545ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/gpu-driver/gpu-driver.rst000066400000000000000000000032411510465702400223720ustar00rootroot00000000000000MIGraphX GPU-driver =============== The MIGraphX gpu-driver is used to test the performance of GPU kernels produced in MIGraphX. Example usage:: gpu-driver kernel_test.json The input json file describes the gpu kernel and the input data settings. Random data is passed into the gpu kernel and the kernel is run a set number of iterations (1000 in the example below) and timed for performance. Format for the input json file ------------------------------ * settings: * iterations: the number of iterations to run the kernel * lens: the dimensions for the input data shape * strides: strides for the input dimension, optional for standard shapes * type: data type * compile_op: * name: name of the gpu operation to compile * lambda: lambda function * inputs: input shapes into the kernel, need 1 more than lambda function input for output buffer *TODO: many other possible settings* Example hjson file that tests a pointwise GELU approximation (note this is hjson and needs to be converted to json before usage):: # sigmoid GELU approximation { settings: { iterations: 1000 lens: [10, 384, 3072] type: "float" }, compile_op: { name: "pointwise" lambda: ''' [](auto x) { using x_type = decltype(x); x_type one = 1.; x_type fit_const = 2.; return x / (one + exp(-fit_const * x)); } ''' inputs: [{}, {}] } } To convert the hjson file to a json file you can use ``hjson -j``. To install hjson: ``pip install hjson`` ROCm-AMDMIGraphX-46524e8/docs/index.rst000066400000000000000000000036741510465702400173430ustar00rootroot00000000000000.. meta:: :description: MIGraphX provides an optimized execution engine for deep learning neural networks :keywords: MIGraphX, ROCm, library, API .. _index: =========================== MIGraphX documentation =========================== MIGraphX is a graph inference engine and graph compiler. MIGraphX accelerates machine-learning models by leveraging several graph-level transformations and optimizations. These optimizations include: * Operator fusion * Arithmetic simplifications * Dead-code elimination * Common subexpression elimination (CSE) * Constant propagation After optimization, MIGraphX generates code for AMD GPUs by calling various ROCm libraries to create the fastest combinations of HIP kernels. The MIGraphX public repository is located at `https://github.com/ROCm/AMDMIGraphX/ `_ .. grid:: 2 :gutter: 3 .. grid-item-card:: Install * :doc:`Installing MIGraphX with the package installer <./install/installing_with_package>` * :doc:`Building and installing MIGraphX from source code <./install/building_migraphx>` .. grid-item-card:: Reference * :doc:`MIGraphX user reference <./reference/MIGraphX-user-reference>` * :ref:`cpp-api-reference` * :ref:`python-api-reference` * :doc:`Supported ONNX Operators <./dev/onnx_operators>` * :doc:`MIGraphX contributor reference <./reference/MIGraphX-dev-reference>` * :doc:`Environment variables <./reference/MIGraphX-dev-env-vars>` * :doc:`Develop for the MIGraphX code base <./dev/contributing-to-migraphx>` * :ref:`migraphx-driver` .. grid-item-card:: Examples * :doc:`MIGraphX examples <./tutorials/MIGraphX-examples>` To contribute to the documentation refer to `Contributing to ROCm `_. Licensing information can be found on the `Licensing `_ page. ROCm-AMDMIGraphX-46524e8/docs/install/000077500000000000000000000000001510465702400171365ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/install/building_migraphx.rst000066400000000000000000000021431510465702400233640ustar00rootroot00000000000000.. meta:: :description: Build and install MIGraphX :keywords: build, install, MIGraphX, AMD, ROCm, rbuild, development, contributing ******************************************************************** Building MIGraphX ******************************************************************** ROCm must be installed prior to building MIGraphX. See `ROCm installation for Linux `_ for information on ROCm installation on Linux. .. note:: This method for building MIGraphX requires using ``sudo``. 1. Install `rocm-cmake`, `pip3`, `rocblas`, and `miopen-hip`: .. code:: shell sudo apt install -y rocm-cmake python3-pip rocblas miopen-hip 2. Install `rbuild `_: .. code:: shell pip3 install --prefix /usr/local https://github.com/RadeonOpenCompute/rbuild/archive/master.tar.gz 3. Build MIGraphX source code: .. code:: shell sudo rbuild build -d depend -B build -DGPU_TARGETS=$(/opt/rocm/bin/rocminfo | grep -o -m1 'gfx.*') ROCm-AMDMIGraphX-46524e8/docs/install/installing_with_package.rst000066400000000000000000000017731510465702400245520ustar00rootroot00000000000000.. meta:: :description: Installing MIGraphX using the package installer :keywords: install, MIGraphX, AMD, ROCm, package installer ******************************************************************** Installing MIGraphX ******************************************************************** ROCm must be installed before installing MIGraphX. See `ROCm installation for Linux `_ for information on how to install ROCm on Linux. Installing MIGraphX using the package installer is sufficient for users who want to use the MIGraphX API. If you want to develop for MIGraphX and contribute to the source code, see `Building MIGraphX <./building_migraphx.html>`_ and `Developing for MIGraphX <../dev/contributing-to-migraphx.html>`_ The package installer will install all the prerequisites needed for MIGraphX. Use the following command to install MIGraphX: .. code:: shell sudo apt update && sudo apt install -y migraphx ROCm-AMDMIGraphX-46524e8/docs/license.md000066400000000000000000000000531510465702400174320ustar00rootroot00000000000000# License ```{include} ../LICENSE ``` ROCm-AMDMIGraphX-46524e8/docs/migraphx-driver.rst000066400000000000000000000615651510465702400213470ustar00rootroot00000000000000.. meta:: :description: MIGraphX provides an optimized execution engine for deep learning neural networks :keywords: MIGraphX, ROCm, library, API, tool .. _migraphx-driver: ===================== MIGraphX driver ===================== The MIGraphX driver is a command-line tool that allows you to utilize many of the MIGraphX core functions without having to write a program. It can read, compile, run, and test the performance of a model with randomized data. It is installed by default when you install MIGraphX. You can find it in ``/opt/rocm/bin/migraphx-driver`` or in ``AMDMIGraphX/build/bin/migraphx-driver`` after building the source code. .. _driver commands: Commands ----------- The table below summarizes the MIGraphX driver commands. .. list-table:: commands * - Command - Description * - op - Prints all operators of MIGraphX when followed by the option ``--list`` or ``-l`` * - params - Prints the input and output parameter shapes * - run - Compiles, allocates parameters, evaluates, and prints input graph * - read - Loads and prints input graph * - compile - Compiles and prints input graph * - verify - Runs reference and GPU implementations and checks outputs for consistency * - perf - Compiles and runs input graph followed by printing the performance report Options ---------- The table below summarizes the various options to be used with the :ref:`MIGraphX driver commands `. To learn which options can be used with which commands, see the :ref:`MIGraphX driver options `. .. list-table:: commands * - Option - Description * - --help | -h - Prints help section. * - --test - Test MIGraphX with single layer GEMM model. * - --onnx - Loads the file as an ONNX graph. * - --tf - Loads the file as a tensorflow graph. * - --migraphx - Loads the file as a migraphx graph. * - --migraphx-json - Loads the file as a migraphx JSON graph. * - --batch - Sets batch size for a static model. Sets the batch size at runtime for a dynamic batch model. * - --nhwc - Treats tensorflow format as nhwc. * - --nchw - Treats tensorflow format as nchw. * - --skip-unknown-operators - Skips unknown operators when parsing and continues to parse. * - --trim | -t - Trims instructions from the end. * - --optimize | -O - Optimizes read * - --graphviz | -g - Prints a graphviz representation * - --brief - Makes the output brief * - --cpp - Prints the program in .cpp format * - --json - Prints the program in .json format * - --text - Prints the program in .txt format * - --binary - Prints the program in binary format * - --netron - Prints the program in Netron viewable JSON format * - --output | -o - Writes output in a file * - --fill0 - Fills parameter with 0s * - --fill1 - Fills parameter with 1s * - --input-dim - Sets static dimensions of a parameter * - --dyn-input-dim - Sets dynamic dimensions of a parameter * - --default-dyn-dim - Sets default dynamic dimension * - --gpu - Compiles on the GPU * - --cpu - Compiles on the CPU * - --ref - Compiles on the reference implementation * - --enable-offload-copy - Enables implicit offload copying * - --disable-fast-math - Disables fast math optimization * - --exhaustive-tune - Enables exhaustive search to find the fastest kernel * - --fp16 - Quantizes for fp16 * - --bf16 - Quantizes for bf16 * - --int8 - Quantizes for int8 * - --fp8 - Quantize for ``Float8E4M3FNUZ`` type * - --rms-tol - Sets tolerance for the RMS error (Default: 0.001) * - --atol - Sets tolerance for elementwise absolute difference (Default: 0.001) * - --rtol - Sets tolerance for elementwise relative difference (Default: 0.001) * - --per-instruction | -i - Verifies each instruction * - --reduce | -r - Reduces program and verifies * - --iterations | -n - Sets the number of iterations to run for perf report * - --list | -l - Lists all the MIGraphX operators Usage ---------- This section demonstrates the usage of MIGraphX driver tool with some commonly used options. Note that these examples use a simple MNIST ConvNet as the input graph for demonstration purposes as models of higher complexity generate considerably larger outputs in most cases. Option: op ************ $ /opt/rocm/bin/migraphx-driver op --list .. collapse:: View Output .. code-block:: python @literal @param @return abs acos acosh add argmax argmin as_shape asin asinh atan atanh batch_norm_inference broadcast capture ceil check_context::migraphx::gpu::context clip concat contiguous convert convolution cos cosh deconvolution div dot elu equal erf exp flatten floor gather gpu::abs gpu::acos gpu::acosh gpu::add gpu::add_clip gpu::add_gelu gpu::add_gelu_new gpu::add_relu gpu::add_tanh gpu::argmax gpu::argmin gpu::asin gpu::asinh gpu::atan gpu::atanh gpu::batch_norm_inference gpu::ceil gpu::clip gpu::concat gpu::contiguous gpu::conv_bias gpu::conv_bias_relu gpu::convert gpu::convolution gpu::cos gpu::cosh gpu::deconv gpu::div gpu::elu gpu::equal gpu::erf gpu::exp gpu::floor gpu::gather gpu::gelu gpu::gelu_new gpu::gemm gpu::greater gpu::layernorm gpu::leaky_relu gpu::less gpu::log gpu::logsoftmax gpu::lrn gpu::max gpu::min gpu::mul gpu::mul_add gpu::mul_add_relu gpu::pad gpu::pooling gpu::pow gpu::prelu gpu::quant_convolution gpu::quant_gemm gpu::recip gpu::record_event gpu::reduce_max gpu::reduce_mean gpu::reduce_min gpu::reduce_prod gpu::reduce_sum gpu::relu gpu::rnn_var_sl_last_output gpu::rnn_var_sl_shift_output gpu::rnn_var_sl_shift_sequence gpu::round gpu::rsqrt gpu::set_stream gpu::sigmoid gpu::sign gpu::sin gpu::sinh gpu::softmax gpu::sqdiff gpu::sqrt gpu::sub gpu::tan gpu::tanh gpu::triadd gpu::triadd_clip gpu::triadd_relu gpu::triadd_sigmoid gpu::triadd_tanh gpu::wait_event greater gru hip::allocate hip::copy hip::copy_from_gpu hip::copy_to_gpu hip::hip_allocate_memory hip::hip_copy_literal identity im2col leaky_relu less load log logsoftmax lrn lstm max min mul multibroadcast neg outline pad pooling pow prelu quant_convolution quant_dot recip reduce_max reduce_mean reduce_min reduce_prod reduce_sum ref::batch_norm_inference ref::convolution ref::deconvolution ref::dot ref::elu ref::im2col ref::leaky_relu ref::logsoftmax ref::lrn ref::op ref::pad ref::pooling_average ref::pooling_max ref::quant_convolution ref::rnn_var_sl_last_output ref::softmax relu reshape rnn rnn_last_cell_output rnn_last_hs_output rnn_var_sl_last_output rnn_var_sl_shift_output rnn_var_sl_shift_sequence round rsqrt scalar sigmoid sign sin sinh slice softmax sqdiff sqrt squeeze sub tan tanh transpose undefined unknown: unsqueeze Option: params **************** $ /opt/rocm/bin/migraphx-driver params simple_graph.pb .. collapse:: View Output .. code-block:: python Reading: simple_graph.pb x: float_type, {1, 28, 28}, {784, 28, 1} Option: run (ONNX file input) ******************************* $ /opt/rocm/bin/migraphx-driver run --onnx simple_graph.onnx .. collapse:: View Output .. code-block:: python Compiling ... Reading: simple_graph.onnx @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1} @2 = hip::hip_copy_literal[id=@literal:1] -> float_type, {784, 128}, {128, 1} x:0 = @param:x:0 -> float_type, {1, 28, 28}, {784, 28, 1} @3 = reshape[dims={-1, 784}](x:0) -> float_type, {1, 784}, {784, 1} @4 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1} @5 = gpu::gemm[alpha=1,beta=0](@3,@2,@4) -> float_type, {1, 128}, {128, 1} @6 = hip::hip_copy_literal[id=@literal:0] -> float_type, {128}, {1} @7 = hip::hip_copy_literal[id=@literal:2] -> float_type, {10}, {1} @8 = hip::hip_copy_literal[id=@literal:3] -> float_type, {128, 10}, {10, 1} @9 = multibroadcast[output_lens={1, 128}](@6) -> float_type, {1, 128}, {0, 1} @10 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1} @11 = gpu::add_relu(@5,@9,@10) -> float_type, {1, 128}, {128, 1} @12 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1} @13 = gpu::gemm[alpha=1,beta=0](@11,@8,@12) -> float_type, {1, 10}, {10, 1} @14 = multibroadcast[output_lens={1, 10}](@7) -> float_type, {1, 10}, {0, 1} @15 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1} @16 = gpu::add(@13,@14,@15) -> float_type, {1, 10}, {10, 1} #output_0 = @param:#output_0 -> float_type, {1, 10}, {10, 1} @17 = gpu::softmax[axis=1](@16,#output_0) -> float_type, {1, 10}, {10, 1} @18 = @return(@17) Allocating params ... @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1} @2 = hip::hip_copy_literal[id=@literal:1] -> float_type, {784, 128}, {128, 1} x:0 = @param:x:0 -> float_type, {1, 28, 28}, {784, 28, 1} @3 = reshape[dims={-1, 784}](x:0) -> float_type, {1, 784}, {784, 1} @4 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1} @5 = gpu::gemm[alpha=1,beta=0](@3,@2,@4) -> float_type, {1, 128}, {128, 1} @6 = hip::hip_copy_literal[id=@literal:0] -> float_type, {128}, {1} @7 = hip::hip_copy_literal[id=@literal:2] -> float_type, {10}, {1} @8 = hip::hip_copy_literal[id=@literal:3] -> float_type, {128, 10}, {10, 1} @9 = multibroadcast[output_lens={1, 128}](@6) -> float_type, {1, 128}, {0, 1} @10 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1} @11 = gpu::add_relu(@5,@9,@10) -> float_type, {1, 128}, {128, 1} @12 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1} @13 = gpu::gemm[alpha=1,beta=0](@11,@8,@12) -> float_type, {1, 10}, {10, 1} @14 = multibroadcast[output_lens={1, 10}](@7) -> float_type, {1, 10}, {0, 1} @15 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1} @16 = gpu::add(@13,@14,@15) -> float_type, {1, 10}, {10, 1} #output_0 = @param:#output_0 -> float_type, {1, 10}, {10, 1} @17 = gpu::softmax[axis=1](@16,#output_0) -> float_type, {1, 10}, {10, 1} @18 = @return(@17) Option: read ************** $ /opt/rocm/bin/migraphx-driver read simple_graph.pb .. collapse:: View Output .. code-block:: python Reading: simple_graph.pb @0 = @literal{0.0136018, -0.0839988, 0.0375392, 0.0613085, -0.125795, 0.176185, 0.0761055, 0.0093384, -0.110057, -0.170587} -> float_type, {10}, {1} @1 = @literal{ ... } -> float_type, {128, 10}, {10, 1} @2 = @literal{ ... } -> float_type, {128}, {1} @3 = @literal{ ... } -> float_type, {784, 128}, {128, 1} @4 = @literal{-1, 784} -> int32_type, {2}, {1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @5 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @6 = identity(@3) -> float_type, {784, 128}, {128, 1} @7 = dot[alpha=1,beta=1](@5,@6) -> float_type, {1, 128}, {128, 1} @8 = identity(@2) -> float_type, {128}, {1} @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1} @10 = add(@7,@9) -> float_type, {1, 128}, {128, 1} @11 = relu(@10) -> float_type, {1, 128}, {128, 1} @12 = identity(@1) -> float_type, {128, 10}, {10, 1} @13 = dot[alpha=1,beta=1](@11,@12) -> float_type, {1, 10}, {10, 1} @14 = identity(@0) -> float_type, {10}, {1} @15 = broadcast[axis=1,dims={1, 10}](@14) -> float_type, {1, 10}, {0, 1} @16 = add(@13,@15) -> float_type, {1, 10}, {10, 1} @17 = softmax[axis=1](@16) -> float_type, {1, 10}, {10, 1} @18 = identity(@17) -> float_type, {1, 10}, {10, 1} Option: compile (on GPU, quantized for fp16) *********************************************** $ /opt/rocm/bin/migraphx-driver compile --gpu --fp16 simple_graph.pb .. collapse:: View Output .. code-block:: python Compiling ... Reading: simple_graph.pb @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {456}, {1},id=scratch] -> float_type, {456}, {1} @2 = hip::hip_copy_literal[id=@literal:0] -> half_type, {784, 128}, {128, 1} @3 = load[offset=256,end=1824](@1) -> half_type, {1, 28, 28}, {784, 28, 1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @4 = gpu::convert[target_type=1](x,@3) -> half_type, {1, 28, 28}, {784, 28, 1} @5 = reshape[dims={-1, 784}](@4) -> half_type, {1, 784}, {784, 1} @6 = load[offset=0,end=256](@1) -> half_type, {1, 128}, {128, 1} @7 = gpu::gemm[alpha=1,beta=0](@5,@2,@6) -> half_type, {1, 128}, {128, 1} @8 = hip::hip_copy_literal[id=@literal:2] -> half_type, {128, 10}, {10, 1} @9 = hip::hip_copy_literal[id=@literal:1] -> half_type, {128}, {1} @10 = hip::hip_copy_literal[id=@literal:3] -> half_type, {10}, {1} @11 = load[offset=256,end=512](@1) -> half_type, {1, 128}, {128, 1} @12 = broadcast[axis=1,dims={1, 128}](@9) -> half_type, {1, 128}, {0, 1} @13 = gpu::add_relu(@7,@12,@11) -> half_type, {1, 128}, {128, 1} @14 = load[offset=0,end=20](@1) -> half_type, {1, 10}, {10, 1} @15 = gpu::gemm[alpha=1,beta=0](@13,@8,@14) -> half_type, {1, 10}, {10, 1} @16 = broadcast[axis=1,dims={1, 10}](@10) -> half_type, {1, 10}, {0, 1} @17 = load[offset=20,end=40](@1) -> half_type, {1, 10}, {10, 1} @18 = gpu::add(@15,@16,@17) -> half_type, {1, 10}, {10, 1} @19 = load[offset=0,end=20](@1) -> half_type, {1, 10}, {10, 1} @20 = gpu::softmax[axis=1](@18,@19) -> half_type, {1, 10}, {10, 1} output = @param:output -> float_type, {1, 10}, {10, 1} @21 = gpu::convert[target_type=2](@20,output) -> float_type, {1, 10}, {10, 1} Option: verify **************** $ /opt/rocm/bin/migraphx-driver verify simple_graph.pb .. collapse:: View Output .. code-block:: python Reading: simple_graph.pb @0 = @literal{0.0136018, -0.0839988, 0.0375392, 0.0613085, -0.125795, 0.176185, 0.0761055, 0.0093384, -0.110057, -0.170587} -> float_type, {10}, {1} @1 = @literal{ ... } -> float_type, {128, 10}, {10, 1} @2 = @literal{ ... } -> float_type, {128}, {1} @3 = @literal{ ... } -> float_type, {784, 128}, {128, 1} @4 = @literal{-1, 784} -> int32_type, {2}, {1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @5 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @6 = identity(@3) -> float_type, {784, 128}, {128, 1} @7 = dot[alpha=1,beta=1](@5,@6) -> float_type, {1, 128}, {128, 1} @8 = identity(@2) -> float_type, {128}, {1} @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1} @10 = add(@7,@9) -> float_type, {1, 128}, {128, 1} @11 = relu(@10) -> float_type, {1, 128}, {128, 1} @12 = identity(@1) -> float_type, {128, 10}, {10, 1} @13 = dot[alpha=1,beta=1](@11,@12) -> float_type, {1, 10}, {10, 1} @14 = identity(@0) -> float_type, {10}, {1} @15 = broadcast[axis=1,dims={1, 10}](@14) -> float_type, {1, 10}, {0, 1} @16 = add(@13,@15) -> float_type, {1, 10}, {10, 1} @17 = softmax[axis=1](@16) -> float_type, {1, 10}, {10, 1} @18 = identity(@17) -> float_type, {1, 10}, {10, 1} @0 = @literal{0.0136018, -0.0839988, 0.0375392, 0.0613085, -0.125795, 0.176185, 0.0761055, 0.0093384, -0.110057, -0.170587} -> float_type, {10}, {1} @1 = @literal{ ... } -> float_type, {128, 10}, {10, 1} @2 = @literal{ ... } -> float_type, {128}, {1} @3 = @literal{ ... } -> float_type, {784, 128}, {128, 1} @4 = @literal{-1, 784} -> int32_type, {2}, {1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @5 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @6 = identity(@3) -> float_type, {784, 128}, {128, 1} @7 = dot[alpha=1,beta=1](@5,@6) -> float_type, {1, 128}, {128, 1} @8 = identity(@2) -> float_type, {128}, {1} @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1} @10 = add(@7,@9) -> float_type, {1, 128}, {128, 1} @11 = relu(@10) -> float_type, {1, 128}, {128, 1} @12 = identity(@1) -> float_type, {128, 10}, {10, 1} @13 = dot[alpha=1,beta=1](@11,@12) -> float_type, {1, 10}, {10, 1} @14 = identity(@0) -> float_type, {10}, {1} @15 = broadcast[axis=1,dims={1, 10}](@14) -> float_type, {1, 10}, {0, 1} @16 = add(@13,@15) -> float_type, {1, 10}, {10, 1} @17 = softmax[axis=1](@16) -> float_type, {1, 10}, {10, 1} @18 = identity(@17) -> float_type, {1, 10}, {10, 1} @0 = @literal{0.0136018, -0.0839988, 0.0375392, 0.0613085, -0.125795, 0.176185, 0.0761055, 0.0093384, -0.110057, -0.170587} -> float_type, {10}, {1} @1 = @literal{ ... } -> float_type, {128, 10}, {10, 1} @2 = @literal{ ... } -> float_type, {128}, {1} @3 = @literal{ ... } -> float_type, {784, 128}, {128, 1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @4 = ref::reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @5 = ref::identity(@3) -> float_type, {784, 128}, {128, 1} @6 = ref::dot[alpha=1,beta=1](@4,@5) -> float_type, {1, 128}, {128, 1} @7 = ref::identity(@2) -> float_type, {128}, {1} @8 = ref::broadcast[axis=1,dims={1, 128}](@7) -> float_type, {1, 128}, {0, 1} @9 = ref::contiguous(@8) -> float_type, {1, 128}, {128, 1} @10 = ref::add(@6,@9) -> float_type, {1, 128}, {128, 1} @11 = ref::relu(@10) -> float_type, {1, 128}, {128, 1} @12 = ref::identity(@1) -> float_type, {128, 10}, {10, 1} @13 = ref::dot[alpha=1,beta=1](@11,@12) -> float_type, {1, 10}, {10, 1} @14 = ref::identity(@0) -> float_type, {10}, {1} @15 = ref::broadcast[axis=1,dims={1, 10}](@14) -> float_type, {1, 10}, {0, 1} @16 = ref::contiguous(@15) -> float_type, {1, 10}, {10, 1} @17 = ref::add(@13,@16) -> float_type, {1, 10}, {10, 1} @18 = ref::softmax[axis=1](@17) -> float_type, {1, 10}, {10, 1} @19 = ref::identity(@18) -> float_type, {1, 10}, {10, 1} @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1} @2 = hip::hip_copy_literal[id=@literal:3] -> float_type, {784, 128}, {128, 1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @3 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1} @4 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @5 = gpu::gemm[alpha=1,beta=0](@4,@2,@3) -> float_type, {1, 128}, {128, 1} @6 = hip::hip_copy_literal[id=@literal:1] -> float_type, {128, 10}, {10, 1} @7 = hip::hip_copy_literal[id=@literal:2] -> float_type, {128}, {1} @8 = hip::hip_copy_literal[id=@literal:0] -> float_type, {10}, {1} @9 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1} @10 = broadcast[axis=1,dims={1, 128}](@7) -> float_type, {1, 128}, {0, 1} @11 = gpu::add_relu(@5,@10,@9) -> float_type, {1, 128}, {128, 1} @12 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1} @13 = gpu::gemm[alpha=1,beta=0](@11,@6,@12) -> float_type, {1, 10}, {10, 1} @14 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1} @15 = broadcast[axis=1,dims={1, 10}](@8) -> float_type, {1, 10}, {0, 1} @16 = gpu::add(@13,@15,@14) -> float_type, {1, 10}, {10, 1} output = @param:output -> float_type, {1, 10}, {10, 1} @17 = gpu::softmax[axis=1](@16,output) -> float_type, {1, 10}, {10, 1} Option: perf ************** $ /opt/rocm/bin/migraphx-driver perf simple_graph.pb .. collapse:: View Output .. code-block:: python Compiling ... Reading: simple_graph.pb @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1} @2 = hip::hip_copy_literal[id=@literal:3] -> float_type, {784, 128}, {128, 1} @3 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @4 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @5 = gpu::gemm[alpha=1,beta=0](@4,@2,@3) -> float_type, {1, 128}, {128, 1} @6 = hip::hip_copy_literal[id=@literal:1] -> float_type, {128, 10}, {10, 1} @7 = hip::hip_copy_literal[id=@literal:0] -> float_type, {10}, {1} @8 = hip::hip_copy_literal[id=@literal:2] -> float_type, {128}, {1} @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1} @10 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1} @11 = gpu::add_relu(@5,@9,@10) -> float_type, {1, 128}, {128, 1} @12 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1} @13 = gpu::gemm[alpha=1,beta=0](@11,@6,@12) -> float_type, {1, 10}, {10, 1} @14 = broadcast[axis=1,dims={1, 10}](@7) -> float_type, {1, 10}, {0, 1} @15 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1} @16 = gpu::add(@13,@14,@15) -> float_type, {1, 10}, {10, 1} output = @param:output -> float_type, {1, 10}, {10, 1} @17 = gpu::softmax[axis=1](@16,output) -> float_type, {1, 10}, {10, 1} Allocating params ... Running performance report ... @0 = check_context::migraphx::gpu::context -> float_type, {}, {}: 0.00057782ms, 1% @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1}: 0.000295ms, 1% @2 = hip::hip_copy_literal[id=@literal:3] -> float_type, {784, 128}, {128, 1}: 0.00027942ms, 1% @3 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1}: 0.000232ms, 1% x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1}: 0.0003206ms, 1% @4 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1}: 0.00033842ms, 1% @5 = gpu::gemm[alpha=1,beta=0](@4,@2,@3) -> float_type, {1, 128}, {128, 1}: 0.212592ms, 52% @6 = hip::hip_copy_literal[id=@literal:1] -> float_type, {128, 10}, {10, 1}: 0.00085822ms, 1% @7 = hip::hip_copy_literal[id=@literal:0] -> float_type, {10}, {1}: 0.000382ms, 1% @8 = hip::hip_copy_literal[id=@literal:2] -> float_type, {128}, {1}: 0.0003486ms, 1% @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1}: 0.000299ms, 1% @10 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1}: 0.000234ms, 1% @11 = gpu::add_relu(@5,@9,@10) -> float_type, {1, 128}, {128, 1}: 0.0416597ms, 11% @12 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1}: 0.0007548ms, 1% @13 = gpu::gemm[alpha=1,beta=0](@11,@6,@12) -> float_type, {1, 10}, {10, 1}: 0.0733071ms, 18% @14 = broadcast[axis=1,dims={1, 10}](@7) -> float_type, {1, 10}, {0, 1}: 0.00088142ms, 1% @15 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1}: 0.000408ms, 1% @16 = gpu::add(@13,@14,@15) -> float_type, {1, 10}, {10, 1}: 0.0410144ms, 10% output = @param:output -> float_type, {1, 10}, {10, 1}: 0.0010222ms, 1% @17 = gpu::softmax[axis=1](@16,output) -> float_type, {1, 10}, {10, 1}: 0.0385636ms, 10% Summary: gpu::gemm: 0.285899ms, 69% gpu::add_relu: 0.0416597ms, 11% gpu::add: 0.0410144ms, 10% gpu::softmax: 0.0385636ms, 10% hip::hip_copy_literal: 0.00186824ms, 1% load: 0.0016288ms, 1% @param: 0.0013428ms, 1% broadcast: 0.00118042ms, 1% check_context::migraphx::gpu::context: 0.00057782ms, 1% reshape: 0.00033842ms, 1% hip::hip_allocate_memory: 0.000295ms, 1% Rate: 2866.1/sec Total time: 0.348906ms Total instructions time: 0.414369ms Overhead time: 0.00348144ms, -0.0654627ms Overhead: 1%, -19% ROCm-AMDMIGraphX-46524e8/docs/py_user_guide.rst000066400000000000000000000001521510465702400210630ustar00rootroot00000000000000Python User Guide ================= .. toctree:: :maxdepth: 2 :caption: Contents: reference/py ROCm-AMDMIGraphX-46524e8/docs/reference/000077500000000000000000000000001510465702400174265ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/reference/MIGraphX-cpp.rst000066400000000000000000000043421510465702400223620ustar00rootroot00000000000000.. meta:: :description: MIGraphX C++ API reference :keywords: MIGraphX, ROCm, C++, API, reference, development, developer .. _cpp-api-reference: ============================ MIGraphX C++ API reference ============================ shape ----- .. doxygenenum:: migraphx_shape_datatype_t .. doxygenstruct:: migraphx::shape :members: :undoc-members: argument -------- .. doxygenstruct:: migraphx::argument :members: :undoc-members: target ------ .. doxygenstruct:: migraphx::target :members: :undoc-members: program ------- .. doxygenstruct:: migraphx::program_parameter_shapes :members: :undoc-members: .. doxygenstruct:: migraphx::program_parameters :members: :undoc-members: .. doxygenstruct:: migraphx_compile_options :members: :undoc-members: .. doxygenstruct:: migraphx::program :members: :undoc-members: quantize -------- .. doxygenstruct:: migraphx::quantize_op_names :members: :undoc-members: .. doxygenfunction:: migraphx::quantize_fp16(const program&) .. doxygenfunction:: migraphx::quantize_fp16(const program&, const quantize_op_names&) .. doxygenfunction:: migraphx::quantize_bf16(const program&) .. doxygenstruct:: migraphx::quantize_int8_options :members: :undoc-members: .. doxygenfunction::migraphx::quantize_int8 parse_onnx ---------- .. doxygenstruct:: migraphx::onnx_options :members: :undoc-members: .. doxygenfunction:: migraphx::parse_onnx(const char *) .. doxygenfunction:: migraphx::parse_onnx(const char *, const migraphx::onnx_options&) .. doxygenfunction:: migraphx::parse_onnx_buffer(const std::string&) .. doxygenfunction:: migraphx::parse_onnx_buffer(const std::string&, const migraphx::onnx_options&) .. doxygenfunction:: migraphx::parse_onnx_buffer(const void *, size_t) .. doxygenfunction:: migraphx::parse_onnx_buffer(const void *, size_t, const migraphx::onnx_options&) load ---- .. doxygenstruct:: migraphx::file_options :members: :undoc-members: .. doxygenfunction:: migraphx::load(const char *) .. doxygenfunction:: migraphx::load(const char *, const file_options&) save ---- .. doxygenfunction:: migraphx::save(const program&, const char *) .. doxygenfunction:: migraphx::save(const program&, const char *, const file_options&) ROCm-AMDMIGraphX-46524e8/docs/reference/MIGraphX-dev-env-vars.rst000066400000000000000000000554671510465702400241330ustar00rootroot00000000000000.. meta:: :description: MIGraphX environment variables for developers :keywords: MIGraphX, code base, contribution, developing, env vars, environment variables ======================================================== MIGraphX environment variables ======================================================== The MIGraphX environment variables can be used by contributors to the MIGraphX code base to customize tuning, verification, and tracing. Model performance tunable variables ************************************ Model performance tunable variables change the compilation behavior of a model. These are the most commonly used variables. .. list-table:: :widths: 40 60 :header-rows: 1 * - Environment variable - Values * - | ``MIGRAPHX_ENABLE_NHWC`` | Forces the model to use the NHWC layout. - | ``1``: Forces the use of the NHWC layout. | ``0``: Returns to default behavior. | Default: The use of the NHWC layout isn't forced. * - | ``MIGRAPHX_DISABLE_MLIR`` | When set, the rocMLIR library won't be used. - | ``1``: The rocMLIR library won't be used. | ``0``: Returns to default behavior. | Default: The rocMLIR library is used. * - | ``MIGRAPHX_ENABLE_CK`` | When set, the Composable Kernel library is used. - | Use with ``MIGRAPHX_DISABLE_MLIR = 1``. | ``1``: The Composable Kernel library is used. | ``0``: Returns to default behavior. | Default: Composable Kernel library isn't used. * - | ``MIGRAPHX_SET_GEMM_PROVIDER`` | Sets the GEMM provider to be either rocBLAS or hipBLASlt. - | ``hipblaslt``: hipBLASLt is used as the GEMM provider. | ``rocblas``: rocBLAS is used as the GEMM provider. | Default: ``rocblas`` on gfx90a; ``hipblaslt`` on all other architectures. * - | ``MIGRAPHX_DISABLE_LAYERNORM_FUSION`` | When set, layernorm fusion isn't used. - | ``1``: Layernorm fusion won't be used. | ``0``: Returns to default behavior. | Default: Layernorm fusion is used. * - | ``MIGRAPHX_DISABLE_MIOPEN_POOLING`` | When set, MIGraphX pooling is used instead of MIOpen pooling. - | ``1``: Use MIGraphX pooling. | ``0``: Returns to default behavior. | Default: MIOpen pooling is used. * - | ``MIGRAPHX_USE_FAST_SOFTMAX`` | Turns on fast softmax optimization to speed up softmax computations. - | ``1``: Turns on Softmax optimization. | ``0``: Returns to default behavior. | Default: Softmax optimization is turned off. * - | ``MIGRAPHX_DISABLE_FP32_SOFTMAX`` | Disables upcasting to fp32 when computing softmax for lower precision graphs. - | ``1``: Disables forcing full precision computation of softmax | ``0``: Returns to default behavior. | Default: Upcasting to FP32 is turned on. * - | ``MIGRAPHX_MLIR_USE_SPECIFIC_OPS`` | Specifies the MLIR operations to use regardless of GPU architecture. - | Takes a comma-separated list of operations. Operations can be any of the following: | ``attention``: Use attention fusion. This is used by default on MI300, but must be specified on other architectures. | ``convolution``: Use MLIR generated kernels for all convolutions. MIOpen is used by default otherwise. | ``convolution_backwards``: Use MLIR generated kernels for backward-convolution. MIOpen is used by default otherwise. | ``dot``: Use MLIR generated kernels for all GEMMs. hipBLASlt is used otherwise. | ``fused_convolution``: Use MLIR generated kernels for convolutions when doing so enables extra fusions. | ``fused_dot``: Use MLIR generated kernels for GEMMs when doing so enables extra fusions. | ``fused``: Equivalent to setting both ``fused_dot`` and ``fused_convolution``. | For example: ``MIGRAPHX_MLIR_USE_SPECIFIC_OP=fuse, convolution,dot``. | A tilde (``~``) can be used to negate an operation. | For example, setting ``MIGRAPHX_MLIR_USE_SPECIFIC_OP=~convolution`` specifies that MLIR generated kernels should never be used. * - | ``MIGRAPHX_MLIR_TUNE_EXHAUSTIVE`` | When set, exhaustive tuning for MLIR is used to find the optimal configuration. - | ``1``: Exhaustive tuning is used. | ``0``: Returns to default behavior. | Default: No MLIR tuning is used. * - | ``MIGRAPHX_ENABLE_HIP_GEMM_TUNING`` | When set, exhaustive tuning for hipBLASLt is used to find the optimal configuration. - | ``1``: Exhaustive tuning is used. | ``0``: Returns to default behavior. | Default: Exhaustive hipBLASLt tuning isn't used. * - | ``MIGRAPHX_ENABLE_MLIR_INPUT_FUSION`` | Turns on input fusions in MLIR. - | ``1``: Turns on input fusions. | ``0``: Returns to default behavior. | Default: Input fusions are turned off. * - | ``MIGRAPHX_ENABLE_MLIR_REDUCE_FUSION`` | Turns on reduction fusions in MLIR. - | ``1``: Turns on reduction fusions. | ``0``: Returns to default behavior. | Default: Reduction fusions are turned off. * - | ``MIGRAPHX_ENABLE_MLIR_GEG_FUSION`` | Turns on GEMM+GEMM fusions in MLIR. - | ``1``: Turns on G+G fusions. | ``0``: Returns to default behavior. | Default: GEMM+GEMM fusions are turned off. * - | ``MIGRAPHX_MLIR_ENABLE_SPLITK`` | Turns on Split-k performance configurations during MLIR tuning. - | ``1``: Turns on Split-k performance configurations. | ``0``: Returns to default behavior. | Default: Split-k performance configurations are turned off. * - | ``MIGRAPHX_DISABLE_FP16_INSTANCENORM_CONVERT`` | When set, FP16 is not converted to FP32 in the ``InstanceNormalization`` ONNX operator. - | ``1``: FP16 is not converted to F32. | ``0``: Returns to default behavior. | Default: FP16 is converted to F32. * - | ``MIGRAPHX_ENABLE_REWRITE_DOT`` | When set, the ``rewrite_dot`` pass is run. - | ``1``: Runs the ``rewrite_dot`` pass | ``0``: Returns to default behavior. | Default: The ``rewrite_dot`` pass isn't run. * - | ``MIGRAPHX_SPLIT_REDUCE_SIZE`` | Minimum size of a reduction to perform a split reduce. - | The minimum size must be an integer. | Set to ``-1`` to disable split reduce. * - | ``MIGRAPHX_COPY_LITERALS`` | When set, literals won't be stored on the GPU but will only be copied over when needed. - | ``1``: Literals are copied over to the GPU as needed. | ``0``: Returns to default behavior. | Default: Literals are stored on the GPU. * - | ``MIGRAPHX_VERIFY_ENABLE_ALLCLOSE`` | When set, the range tolerance is verified using ``allclose``. - | ``1``: The range tolerance is verified using ``allclose``. | ``0``: Returns to the default behavior. | Default: Range tolerance isn't verified. * - | ``MIGRAPHX_LOG_CK_GEMM`` | Turns on printing of Composable Kernel GEMM traces. - | ``1``: Composable Kernel GEMM traces will be printed. | ``0``: Returns to default behavior. | Default: Composable Kernel GEMM traces aren't printed. * - | ``MIGRAPHX_CK_DEBUG`` | When set, ``-DMIGRAPHX_CK_CHECK=1`` is added to the Composable Kernel operator compilation options. - | ``1``: ``-DMIGRAPHX_CK_CHECK=1`` is added to the compilation options. | Default: Compilation is run without ``-DMIGRAPHX_CK_CHECK=1``. * - | ``MIGRAPHX_TUNE_CK`` | Turns on tuning for composable kernels. - | ``1``: Composable kernel tuning is done. | ``0``: Returns to default behavior. | Default: No tuning is done for composable kernels. * - | ``MIGRAPHX_REWRITE_LRN`` | Turns on LRN-to-pooling lowering in the ``rewrite_pooling`` pass. - | ``1``: Turns on LRN-to-pooling lowering. | ``0``: Returns to default behavior. | Default: LRN-to-pooling lowering is turned off. Matching ********** Debug settings for matchers. Matchers are responsible for finding optimizations in the graph compilation stage. .. list-table:: :widths: 40 60 :header-rows: 1 * - Environment variable - Values * - | ``MIGRAPHX_TRACE_MATCHES`` | When set, prints the name of matchers that have found a valid pattern match. - | ``1``: Prints the name of the matchers that have found a valid match. | ``2``: When used with ``MIGRAPHX_TRACE_MATCHES_FOR``, prints the names of matchers that have been tried but which have not necessarily found a match. | ``0``: Returns to default behavior. | Default: Nothing is printed. * - | ``MIGRAPHX_TRACE_MATCHES_FOR`` | Turns on the printing of traces for the specified matcher if a string is found in the matcher's ``file-name``, ``function-name``, or ``matcher-name``. - Takes a string to match. * - | ``MIGRAPHX_VALIDATE_MATCHES`` | When set, ``module.validate()`` is used to validate the module after finding matches. - | ``1``: Runs ``module.validate()``. | ``0``: Returns to default behavior. | Default: ``module.validate()`` isn't run. * - | ``MIGRAPHX_TIME_MATCHERS`` | When set, prints the time spent on a matcher. This helps identify time-consuming patterns. - | ``1`: Prints the time spent on the matcher. | ``0``: Returns to default behavior. | Default: The time is not printed. Pass controls ************************ Debug settings for passes. .. list-table:: :widths: 30 70 :header-rows: 1 * - Environment variable - Values * - | ``MIGRAPHX_TRACE_ELIMINATE_CONTIGUOUS`` | Turns on the printing of debug statements for ``eliminate contiguous instruction`` passes. - | ``1``: Debug statements are printed for ``eliminate contiguous instructions`` passes. | ``0``: Returns to default behavior. | Default: Debug statements aren't printed for ``eliminate contiguous instructions`` passes. * - | ``MIGRAPHX_DISABLE_POINTWISE_FUSION`` | When set, the ``fuse_pointwise compile`` pass isn't run. - | ``1``: The ``fuse_pointwise compile`` pass isn't run. | ``0``: Returns to default behavior. | Default: The ``fuse_pointwise compile`` pass is run. * - | ``MIGRAPHX_DEBUG_MEMORY_COLORING`` | Turns on the printing of debug statements for the ``memory-coloring`` pass. - | ``1``: Debug statements for the ``memory-coloring`` pass are printed. | ``0``: Returns to default behavior. | Default: Debug statements for the ``memory-coloring`` pass aren't printed. * - | ``MIGRAPHX_TRACE_SCHEDULE`` | Turns on the printing of debug statements for the ``schedule`` pass. - | ``1``: Debug statements for the ``schedule`` pass are printed. | ``0``: Returns to default behavior. | Default: Debug statements for the ``memory-coloring`` pass aren't printed. * - | ``MIGRAPHX_TRACE_PROPAGATE_CONSTANT`` | Turns on tracing of instructions that have been replaced with a constant. - | ``1``: Instructions that have been replaced with a constant are traced. | ``0``: Returns to default behavior. | Default: Instructions that have been replaced with a constant aren't traced. * - | ``MIGRAPHX_DISABLE_DNNL_POST_OPS_WORKAROUND`` | When set, the DNNL post-ops workaround isn't used. - | ``1``: The DNNL post-ops workaround ins't used. | ``0``: Returns to default behavior. | Default: The DNNL post-ops workaround is used. * - | ``MIGRAPHX_DISABLE_MIOPEN_FUSION`` | When set, MIOpen fusions aren't used. - | ``1``: MIOpen fusions aren't used. | ``0``: Returns to default behavior. | Default: MIOpen fusions are used. * - | ``MIGRAPHX_DISABLE_SCHEDULE_PASS`` | When set, the ``schedule`` pass isn't run. - | ``1``: The ``schedule`` pass isn't run. | ``0``: Returns to default behavior. | Default: The ``schedule`` pass is run. * - | ``MIGRAPHX_DISABLE_REDUCE_FUSION`` | When set, the ``fuse_reduce`` pass isn't run. - | ``1``: The ``fuse_reduce`` pass isn't run. | ``0``: Returns to default behavior. | Default: The ``fuse_reduce`` pass is run. * - | ``MIGRAPHX_TRACE_PASSES`` | Turns on printing of the compile passes and the program after the passes. - | ``1``: Prints the compile passes. | ``0``: Returns to the default behavior. | Default: The compile pass traces aren't printed. * - | ``MIGRAPHX_TIME_PASSES`` | When set, the compile passes are timed. - | ``1``: Compile passes are timed. | ``0``: Returns to the default behavor. | Default: Compile passes aren't timed. * - | ``MIGRAPHX_DISABLE_PASSES`` | Specifies passes that are to be skipped. - | Takes a comma-separated list of passes. | For example: | ``MIGRAPHX_DISABLE_PASSES=rewrite_pooling,rewrite_gelu``. Compilation tracing ************************ .. list-table:: :widths: 30 70 :header-rows: 1 * - Environment variable - Values * - | ``MIGRAPHX_TRACE_FINALIZE`` | Turns on printing of graph instructions during the ``module.finalize()`` step. - | ``1``: Graph instructions will be printed. | ``0``: Returns to default behavior. | Default: Graph instructions won't be printed. * - | ``MIGRAPHX_TRACE_COMPILE`` | Turns on graph compilation tracing. - | ``1``: Turns on graph compilation tracing. | ``0``: Returns to default behavior. | Default: Graph compilation isn't traced. * - | ``MIGRAPHX_TRACE_ONNX_PARSER`` | Turns on node-by-node tracing for the ONNX parser. - | ``1``: Node-by-node tracing is turned on. | ``0``: Returns to the default behavior. | Default: There is no node-by-node tracing of the ONNX parser. * - | ``MIGRAPHX_TRACE_EVAL`` | Turns on model evaluation tracing and sets its tracing level. - | ``1``: Print the run instructions and the time taken to complete the evaluation. | ``2``: Print the run instructions, time taken, a snippet of the output, and some statistics. | ``3``: Print the run instructions, time taken, a snippet of the output, and statistics for all output buffers. * - | ``MIGRAPHX_TRACE_QUANTIZATION`` | Turns on the printing of the traces for passes run during quantization. - | ``1``: Traces for passes run during quantization will be printed. | ``0``: Returns to default behavior. | Default: The traces for passes run during quantization won't be printed out. * - | ``MIGRAPHX_8BITS_QUANTIZATION_PARAMS`` | Turns on the printing of the quantization parameters in the main module only. - | ``1``: Only the quantization parameters in the main module are printed. | ``0``: Returns to default behavior. | Default: MLIR ************************** .. list-table:: :widths: 30 70 :header-rows: 1 * - Environment variable - Values * - | ``MIGRAPHX_TRACE_MLIR`` | Sets the MLIR trace level. - | ``1``: MLIR trace failures are printed. | ``2``: MLIR trace failures are printed and all MLIR operations are printed as well. * - | ``MIGRAPHX_MLIR_TUNING_DB`` | The path of the tuning database. - Takes the path to the tuning database. * - | ``MIGRAPHX_MLIR_TUNING_CFG`` | Sets the path to the tuning configuration file to use with rocMLIR tuning scripts. - | Takes the path to the configuration file. | For example: | ``MIGRAPHX_MLIR_TUNING_CFG="path/to/config_file.cfg"`` * - | ``MIGRAPHX_MLIR_TUNE_LIMIT`` | Sets the maximum number of solutions available for MLIR tuning. - | Takes an integer greater than 1. * - | ``MIGRAPHX_MLIR_DUMP_TO_MXR`` | Sets the location to where the MXR files that the MLIR modules are written to are saved. - | Takes the path to the directory where the files should be saved. | For example: | ``MIGRAPHX_MLIR_DUMP_TO_MXR="/path/to/save_mxr_file/`` * - | ``MIGRAPHX_MLIR_DUMP`` | Sets the the location where the MLIR files that the MLIR modules are written to are saved. - | Takes the path to the directory where the files should be saved. | For example: | ``MIGRAPHX_MLIR_DUMP="/path/to/save_mlir_file/`` Testing ************************** .. list-table:: :widths: 30 70 :header-rows: 1 * - Environment variable - Values * - | ``MIGRAPHX_TRACE_TEST_COMPILE`` | Sets the target to be traced, and turns on printing of the compile trace for verify tests on the given target. | This flag cannot be used if ``MIGRAPHX_TRACE_COMPILE`` is used. - | ``cpu``: Turns on traces for the CPU target. | ``GPU``: Turns on traces for the GPU target. | Default: * - | ``MIGRAPHX_TRACE_TEST`` | When set, the reference and target programs are printed even if the verify tests pass. - | ``1``: The reference and target programs are printed when the verify tests pass. | ``0``: Returns to default behavior. | Default: Reference and target programs aren't printed if the verify tests pass. * - | ``MIGRAPHX_DUMP_TEST`` | When set, the model that is being verified using ``test-verify`` is output to an MXR file. - | ``1``: The model that is being verified is output to an MXR file. | ``0``: Returns to default behavior. | Default: The model isn't output to file. * - | ``MIGRAPHX_VERIFY_DUMP_DIFF`` | When set, writes out the output of the test results, as well as the reference, when they differ. - | ``1``: Test results are written out when they differ. | ``0``: Returns to default behavior. | Default: The results and the reference aren't written out when they differ. Advanced settings ************************** .. list-table:: :widths: 30 70 :header-rows: 1 * - Environment variable - Values * - | ``MIGRAPHX_TRACE_CMD_EXECUTE`` | When set, commands run by the MIGraphX process will be printed. - | ``1``: Printing of commands is turned on. | ``0``: Returns to default behavior. | Default: Commands aren't printed. * - | ``MIGRAPHX_TRACE_HIPRTC`` | When set, the HIPRTC options and C++ file used will be printed. - | ``1``: HIPRTC options and C++ file will be printed. | ``0``: Returns to default behavior. | Default: HIPRTC options and C++ file aren't printed. * - | ``MIGRAPHX_DEBUG_SAVE_TEMP_DIR`` | When set, temporary directories won't be deleted. - | ``1``: Temporary directories aren't deleted. | ``0``: Returns to default behavior. | Default: Temporary directories are deleted. * - | ``MIGRAPHX_GPU_DEBUG`` | When set, the ``-DMIGRAPHX_DEBUG`` option is used when compiling GPU kernels. ``-DMIGRAPHX_DEBUG`` enables assertions and source location capture. - | ``1``: The ``-DMIGRAPHX_DEBUG`` option is used when compiling GPU kernels. | Default: Compilation is run without ``-DMIGRAPHX_DEBUG``. * - | ``MIGRAPHX_GPU_DEBUG_SYM`` | When set, the ``-g`` option is used when compiling HIPRTC for debugging purposes. - | ``1``: The ``-g`` option is used when compiling HIPRTC. | Default: Compilation is run without the ``-g`` option. * - | ``MIGRAPHX_GPU_DUMP_SRC`` | The compiled HIPRTC source files is written out for further analysis. - | ``1``: HIPRTC source files are written out. | ``0``: Returns to default behavior. | Default: HIPRTC source files aren't written out. * - | ``MIGRAPHX_GPU_DUMP_ASM`` | When set, the hip-clang assembly output is written out for further analysis. - | ``1``: The hip-clang assembly output is written out. | ``0``: Returns to default behavior. | Default: The hip-clang assembly output isn't written out. * - | ``MIGRAPHX_GPU_HIP_FLAGS`` | When set, the hip-clang compiler appends these extra flags for compilation. - | Takes a valid string, a valid hip compile option, e.g. "-Wno-error". | Default: The compiler will not append any extra flags for compilation. * - | ``MIGRAPHX_GPU_OPTIMIZE`` | Sets the GPU compiler optimization mode. - | Takes a valid optimization mode such as ``O3``. | Default: No compiler optimization is used. * - | ``MIGRAPHX_GPU_COMPILE_PARALLEL`` | Sets the number of threads to use for parallel GPU code compilation. - | Takes a positive integer value. | Default: Number of threads is equal to number of processing units (`nproc`). * - | ``MIGRAPHX_TRACE_NARY`` | When set, the nary device functions used during execution are printed out. - | ``1``: The nary device functions are printed out. | ``0``: Returns to default behavior. | Default: nary device functions aren't printed out. * - | ``MIGRAPHX_ENABLE_HIPRTC_WORKAROUNDS`` | When set, the workarounds for known bugs in HIPRTC are used. - | ``1``: HIPRTC workarounds are used. | ``0``: Returns to default behavior. | Default: HIPRTC workarounds aren't used. * - | ``MIGRAPHX_ENABLE_NULL_STREAM`` | Whem set, a null stream can be used for MIOpen and HIP stream handling. - | ``1``: A null stream can be used for stream handling. | ``0``: Returns to default behavior. | Default: A null stream can't be used for stream handling. * - | ``MIGRAPHX_NSTREAMS`` | Sets the number of HIP streams to use in the GPU. - | Takes a positive integer. | Default: one stream will be used. * - | ``MIGRAPHX_TRACE_BENCHMARKING`` | Sets the verbosity of benchmarking traces. - | ``1``: Basic trace | ``2``: Detailed trace | ``3``: Compiled traces * - | ``MIGRAPHX_PROBLEM_CACHE`` | Sets the JSON file that the problem cache will be saved to and loaded from. - | Takes a fully qualified path to a valid JSON file. | For example: | ``MIGRAPHX_PROBLEM_CACHE="path/to/cache_file.json"`` * - | ``MIGRAPHX_BENCHMARKING_BUNDLE`` | Sets the number of configurations to run in a bundle during benchmarking. - Takes a positive integer. * - | ``MIGRAPHX_BENCHMARKING_NRUNS`` | Sets the number of timing runs for each configuration bundle being benchmarked. - Takes a positive integer. ROCm-AMDMIGraphX-46524e8/docs/reference/MIGraphX-dev-reference.rst000066400000000000000000000010431510465702400243050ustar00rootroot00000000000000.. meta:: :description: MIGraphX contributor reference :keywords: MIGraphX, contributor, reference, code base, development ======================================================== MIGraphX contributor reference ======================================================== Developers who want to contribute to the MIGraphX code base can consult :ref:`contributing-to-migraphx`. Developers who want to use the MIGraphX library in their own applications should see :doc:`the MIGraphX user reference <./MIGraphX-user-reference>` instead.ROCm-AMDMIGraphX-46524e8/docs/reference/MIGraphX-py.rst000066400000000000000000000250301510465702400222250ustar00rootroot00000000000000.. meta:: :description: MIGraphX Python API reference :keywords: MIGraphX, ROCm, Python, API, reference, development, developer .. py:module:: migraphx .. _python-api-reference: =============================== MIGraphX Python API reference =============================== shape ----- .. py:class:: shape(type, lens, strides=None, dyn_dims) Describes the shape of a tensor. This includes size, layout, and data type. Use dyn_dims for a dynamic shape. .. py:method:: type() An integer that represents the type. :rtype: int .. py:method:: lens() A list of the lengths of the shape. :rtype: list[int] .. py:method:: strides() A list of the strides of the shape. :rtype: list[int] .. py:method:: elements() The number of elements in the shape. :rtype: int .. py:method:: dyn_dims() The dynamic dimensions of the shape. :rtype: list[dynamic_dimension] .. py:method:: bytes() The number of bytes the shape uses. :rtype: int .. py:method:: type_size() The number of bytes one element uses :rtype: int .. py:method:: ndim() The number of dimensions for the shape. :rtype: int .. py:method:: packed() Returns true if the shape is packed. :rtype: bool .. py:method:: transposed() Returns true if the shape is transposed. :rtype: bool .. py:method:: broadcasted() Returns true if the shape is broadcasted. :rtype: bool .. py:method:: dynamic() Returns true if the shape is dynamic. :rtype: bool .. py:method:: standard() Returns true if the shape is a standard shape. That is, the shape is both packed and not transposed. :rtype: bool .. py:method:: scalar() Returns true if all strides are equal to 0 (scalar tensor). :rtype: bool dynamic_dimension ----------------- .. py:class:: dynamic_dimension(min, max, optimals) Constructs a `dynamic_dimension` from a minimum, a maximum, and optionally a set of optimals. .. py:method:: is_fixed() Returns true if the `dynamic_dimension` is fixed. :rtype : int argument -------- .. py:class:: argument(data) Constructs an argument from a python buffer. This can include numpy arrays. .. py:method:: data_ptr() Returns the address to the underlying argument data. :rtype: int .. py:method:: get_shape() Returns the shape of the argument. :rtype: shape .. py:method:: tolist() Converts the elements of the argument to a python list. :rtype: list .. py:function:: generate_argument(s, seed=0) Generates an argument with random data. :param shape s: Shape of argument to generate. :param int seed: The seed used for random number generation. :rtype: argument .. py:function:: fill_argument(s, value) Fills argument of shape `s` with the given value. :param shape s: Shape of argument to fill. :param int value: Value to fill in the argument. :rtype: argument .. py:function:: create_argument(s, values) Creates an argument of shape `s` with a set of values. :param shape s: Shape of argument to create. :param list values: Values to put in the argument. Must be the same number of elements as the shape. :rtype: argument .. py:function:: argument_from_pointer(shape, address) Creates argument from data stored in given address without copy. :param shape shape: Shape of the data stored in address. :param long address: Memory address of data from another source :rtype: argument .. py:method:: argument.save(arg, filename) :staticmethod: Saves argument to a file encoded in msgpack format. :param argument arg: Argument to save out to. :param str filename: Path of file to save out to. .. py:method:: argument.load(filename) :staticmethod: Load argument from a file encoded in msgpack format. :param str filename: Path of file to load. :rtype: argument target ------ .. py:class:: target() This represents the compilation target. .. py:function:: get_target(name) Constructs the target. :param str name: The name of the target to construct. This can either be 'gpu' or 'ref'. :rtype: target .. _migraphx-module: module ------ .. py:method:: print() Prints the contents of the module as list of instructions. .. py:method:: add_instruction(op, args, mod_args=[]) Adds instruction into the module. :param operation op: 'migraphx.op' to be added as instruction. :param list[instruction] args: list of inputs to the op. :param list[module] mod_args: optional list of module arguments to the operator. :rtype instruction .. py:method:: add_literal(data) Adds constant or literal data of provided shape into the module from python buffer which includes numpy array. :param py::buffer data: Python buffer or numpy array :rtype instruction .. py:method:: add_parameter(name, shape) Adds a parameter to the module with the provided name and shape. :param str name: name of the parameter. :param shape shape: shape of the parameter. :rtype instruction .. py:method:: add_return(args) Adds a return instruction into the module. :param list[instruction] args: instruction arguments which need to be returned from the module. :rtype instruction program ------- .. py:class:: program() Represents the computation graph to be compiled and run. .. py:method:: clone() Makes a copy of the program. :rtype: program .. py:method:: get_parameter_names() Gets all the input argument's or parameter's names to the program as a list. :rtype list[str] .. py:method:: get_parameter_shapes() Gets the shapes of all the input parameters in the program. :rtype: dict[str, shape] .. py:method:: get_output_shapes() Gets the shapes of the final outputs of the program. :rtype: list[shape] .. py:method:: compile(t, offload_copy=True, fast_math=True, exhaustive_tune=False) Compiles the program for the target and optimizes it. :param target t: Compilation target for the program. :param bool offload_copy: For targets with offloaded memory(such as the gpu), this will insert instructions during compilation to copy the input parameters to the offloaded memory and to copy the final result from the offloaded memory back to main memory. :param bool fast_math: Optimize math functions to use faster approximate versions. There may be slight accuracy degredation when enabled. :param exhaustive_tune: Flag to enable exhaustive search to find the fastest version of generated kernels for selected backend. .. py:method:: get_main_module() Gets main module of the program. :rtype module .. py:method:: create_module(name) Creates and adds a module with the provided name into the program. :param str name : name of the new module. :rtype module .. py:method:: run(params) Runs the program. :param params: Map of the input parameters to be used when running the program. :type params: dict[str, argument] :return: The result of the last instruction. :rtype: list[argument] .. py:method:: sort() Sorts the modules of the program for the instructions to appear in topologically sorted order. .. py:function:: quantize_fp16(prog, ins_names=["all"]) Quantizes the program to use fp16. :param program prog: Program to quantize. :param ins_names: List of instructions to quantize. :type ins_names: list[str] .. py:function:: quantize_bf16(prog, ins_names=["all"]) Quantizes the program to use bf16. :param program prog: Program to quantize. :param ins_names: List of instructions to quantize. :type ins_names: list[str] .. py:function:: quantize_int8(prog, t, calibration=[], ins_names=["dot", "convolution"]) Quantizes the program to use int8. :param program prog: Program to quantize. :param target t: Target to be used to run the calibration data. :param calibration: Calibration data used to decide the parameters to the int8 optimization. :type calibration: list[dict[str, argument]] :param ins_names: List of instructions to quantize. :type ins_names: list[str] .. py:function:: autocast_fp8(prog) Auto-convert FP8 parameters and return values to Float for an MIGraphX program. :param program prog: Program to auto-convert parameters/return values. op -- .. py::class:: op(name, kwargs) Constructs an operation with name and arguments. :param str name : name of the operation, must be supported by MIGraphX. :param dict[str, any] kwargs: arguments to the operation. :rtype operation parse_onnx ---------- .. py:function:: parse_onnx(filename, default_dim_value=1, map_input_dims={}, skip_unknown_operators=false, print_program_on_error=false, max_loop_iterations=10, limit_max_iterations=65535) Loads and parses an ONNX file. :param str filename: Path to file. :param str default_dim_value: default dimension to use (if not specified in onnx file). :param dynamic_dimension default_dyn_dim_value: default dynamic_dimension value to use. :param str map_input_dims: Explicitly specify the dims of an input. :param list[dynamic_dimension] map_dyn_input_dims: Explicitly specify the dynamic_dimensions of an input. :param str skip_unknown_operators: Continue parsing onnx file if an unknown operator is found. :param str print_program_on_error: Print program if an error occurs. :param int max_loop_iterations: Maximum iteration number for the loop operator if trip count is not set. :param int limit_max_iterations: Maximum iteration limit for the loop operator. :rtype: program parse_tf -------- .. py:function:: parse_tf(filename, is_nhwc=True, batch_size=1, map_input_dims=dict(), output_names=[]) Loads and parses a tensorflow protobuf file. :param str filename: Path to file. :param bool is_nhwc: Use nhwc as default format. :param str batch_size: default batch size to use (if not specified in protobuf). :param dict[str, list[int]] map_input_dims: Optional arg to explictly specify dimensions of the inputs. :param list[str] output_names: Optional argument specify names of the output nodes. :rtype: program load ---- .. py:function:: load(filename, format='msgpack') Loads a MIGraphX program. :param str filename: Path to file. :param str format: Format of file. Valid options are msgpack or json. :rtype: program save ---- .. py:function:: save(p, filename, format='msgpack') Saves a MIGraphX program. :param program p: Program to save. :param str filename: Path to file. :param str format: Format of file. Valid options are msgpack or json. ROCm-AMDMIGraphX-46524e8/docs/reference/MIGraphX-user-reference.rst000066400000000000000000000007461510465702400245160ustar00rootroot00000000000000.. meta:: :description: MIGraphX user reference :keywords: MIGraphX, library, reference ======================================================== MIGraphX user reference ======================================================== The MIGraphX library includes :doc:`C++ <./MIGraphX-cpp>` and :doc:`Python <./MIGraphX-py>` APIs for creating applications. To contribute to the MIGraphX code base, see :doc:`the MIGraphX contributor documentation <./MIGraphX-dev-reference>`.ROCm-AMDMIGraphX-46524e8/docs/reference/driver-options.rst000066400000000000000000000047161510465702400231540ustar00rootroot00000000000000.. meta:: :description: MIGraphX driver options :keywords: MIGraphX, ROCm, driver, options .. _driver-options: Driver options =============== This document lists the MIGraphX driver commands along with the eligible options. read ---- .. program:: migraphx-driver read Loads and prints input graph. .. include:: ../driver/read.rst compile ------- .. program:: migraphx-driver compile Compiles and prints input graph. .. include:: ../driver/read.rst .. include:: ../driver/compile.rst run --- .. program:: migraphx-driver run Loads and prints input graph. .. include:: ../driver/read.rst .. include:: ../driver/compile.rst perf ---- .. program:: migraphx-driver perf Compiles and runs input graph then prints performance report. .. include:: ../driver/read.rst .. include:: ../driver/compile.rst .. option:: --iterations, -n [unsigned int] Sets number of iterations to run for perf report (Default: 100) verify ------ .. program:: migraphx-driver verify Runs reference and CPU or GPU implementations and checks outputs for consistency. .. include:: ../driver/read.rst .. include:: ../driver/compile.rst .. option:: --rms-tol [double] Sets tolerance for RMS error (Default: 0.001) .. option:: --atol [double] Sets tolerance for elementwise absolute difference (Default: 0.001) .. option:: --rtol [double] Sets tolerance for elementwise relative difference (Default: 0.001) .. option:: -i, --per-instruction Verifies each instruction .. option:: -r, --reduce Reduces program and verifies .. option:: --ref-use-double Converts floating point values to double for the ref target .. _roctx: roctx ------ .. program:: migraphx-driver roctx ``roctx`` provides marker information for each operation which allows MIGraphX to be used with :doc:`rocprof ` for performance analysis. This allows you to get GPU-level kernel timing information. Here is how you can use ``roctx`` combined with :doc:`rocprof ` for tracing: .. code-block:: bash /opt/rocm/bin/rocprof --hip-trace --roctx-trace --flush-rate 1ms --timestamp on -d --obj-tracking on /opt/rocm/bin/migraphx-driver roctx Running :doc:`rocprof ` generates trace information for HIP, HCC and ROCTX in separate ``.txt`` files. To understand the interactions between API calls, utilize the :ref:`roctx.py ` helper script. .. include:: ../driver/read.rst .. include:: ../driver/compile.rst ROCm-AMDMIGraphX-46524e8/docs/sphinx/000077500000000000000000000000001510465702400170015ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/sphinx/_toc.yml.in000066400000000000000000000021471510465702400210610ustar00rootroot00000000000000# Anywhere {branch} is used, the branch name will be substituted. # These comments will also be removed. root: index subtrees: - caption: Installation entries: - file: install/installing_with_package - file: install/building_migraphx - caption: Reference entries: - file: reference/MIGraphX-user-reference subtrees: - entries: - file: reference/MIGraphX-cpp - file: reference/MIGraphX-py - file: dev/onnx_operators - file: reference/MIGraphX-dev-reference subtrees: - entries: - file: reference/MIGraphX-dev-env-vars - file: migraphx-driver - file: dev/contributing-to-migraphx title: Develop for the MIGraphX code base - file: dev/data - file: dev/operators - file: dev/program - file: dev/targets - file: dev/quantization - file: dev/pass - file: dev/matchers - file: dev/tools - caption: Examples entries: - file: tutorials/MIGraphX-examples - caption: About entries: - file: license ROCm-AMDMIGraphX-46524e8/docs/sphinx/requirements.in000066400000000000000000000000471510465702400220550ustar00rootroot00000000000000rocm-docs-core==1.25.0 sphinx-collapse ROCm-AMDMIGraphX-46524e8/docs/sphinx/requirements.txt000066400000000000000000000150141510465702400222660ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### # # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile requirements.in # accessible-pygments==0.0.5 # via pydata-sphinx-theme alabaster==0.7.16 # via sphinx asttokens==3.0.0 # via stack-data attrs==24.3.0 # via # jsonschema # jupyter-cache # referencing babel==2.15.0 # via # pydata-sphinx-theme # sphinx beautifulsoup4==4.12.3 # via pydata-sphinx-theme breathe==4.35.0 # via rocm-docs-core certifi==2024.7.4 # via requests cffi==1.16.0 # via # cryptography # pynacl charset-normalizer==3.3.2 # via requests click==8.1.7 # via # jupyter-cache # sphinx-external-toc comm==0.2.2 # via ipykernel cryptography==44.0.1 # via pyjwt debugpy==1.8.12 # via ipykernel decorator==5.1.1 # via ipython deprecated==1.2.14 # via pygithub docutils==0.21.2 # via # breathe # myst-parser # pydata-sphinx-theme # sphinx exceptiongroup==1.2.2 # via ipython executing==2.1.0 # via stack-data fastjsonschema==2.20.0 # via # nbformat # rocm-docs-core gitdb==4.0.11 # via gitpython gitpython==3.1.43 # via rocm-docs-core greenlet==3.1.1 # via sqlalchemy idna==3.7 # via requests imagesize==1.4.1 # via sphinx importlib-metadata==8.6.1 # via # jupyter-cache # myst-nb ipykernel==6.29.5 # via myst-nb ipython==8.31.0 # via # ipykernel # myst-nb jedi==0.19.2 # via ipython jinja2==3.1.6 # via # myst-parser # sphinx jsonschema==4.23.0 # via nbformat jsonschema-specifications==2024.10.1 # via jsonschema jupyter-cache==1.0.1 # via myst-nb jupyter-client==8.6.3 # via # ipykernel # nbclient jupyter-core==5.7.2 # via # ipykernel # jupyter-client # nbclient # nbformat markdown-it-py==3.0.0 # via # mdit-py-plugins # myst-parser markupsafe==2.1.5 # via jinja2 matplotlib-inline==0.1.7 # via # ipykernel # ipython mdit-py-plugins==0.4.1 # via myst-parser mdurl==0.1.2 # via markdown-it-py myst-nb==1.1.2 # via rocm-docs-core myst-parser==3.0.1 # via myst-nb nbclient==0.10.2 # via # jupyter-cache # myst-nb nbformat==5.10.4 # via # jupyter-cache # myst-nb # nbclient nest-asyncio==1.6.0 # via ipykernel packaging==24.1 # via # ipykernel # sphinx parso==0.8.4 # via jedi pexpect==4.9.0 # via ipython platformdirs==4.3.6 # via jupyter-core prompt-toolkit==3.0.50 # via ipython psutil==6.1.1 # via ipykernel ptyprocess==0.7.0 # via pexpect pure-eval==0.2.3 # via stack-data pycparser==2.22 # via cffi pydata-sphinx-theme==0.16.1 # via # rocm-docs-core # sphinx-book-theme pygithub==2.3.0 # via rocm-docs-core pygments==2.18.0 # via # accessible-pygments # ipython # pydata-sphinx-theme # sphinx pyjwt[crypto]==2.8.0 # via pygithub pynacl==1.5.0 # via pygithub python-dateutil==2.9.0.post0 # via jupyter-client pyyaml==6.0.1 # via # jupyter-cache # myst-nb # myst-parser # rocm-docs-core # sphinx-external-toc pyzmq==26.2.0 # via # ipykernel # jupyter-client referencing==0.36.1 # via # jsonschema # jsonschema-specifications requests==2.32.4 # via # pygithub # sphinx rocm-docs-core==1.25.0 # via -r requirements.in rpds-py==0.22.3 # via # jsonschema # referencing six==1.17.0 # via python-dateutil smmap==5.0.1 # via gitdb snowballstemmer==2.2.0 # via sphinx soupsieve==2.5 # via beautifulsoup4 sphinx==7.4.7 # via # breathe # myst-nb # myst-parser # pydata-sphinx-theme # rocm-docs-core # sphinx-book-theme # sphinx-collapse # sphinx-copybutton # sphinx-design # sphinx-external-toc # sphinx-notfound-page sphinx-book-theme==1.1.3 # via rocm-docs-core sphinx-collapse==0.1.3 # via -r requirements.in sphinx-copybutton==0.5.2 # via rocm-docs-core sphinx-design==0.6.0 # via rocm-docs-core sphinx-external-toc==1.0.1 # via rocm-docs-core sphinx-notfound-page==1.0.2 # via rocm-docs-core sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 # via sphinx sphinxcontrib-htmlhelp==2.0.6 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.8 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx sqlalchemy==2.0.37 # via jupyter-cache stack-data==0.6.3 # via ipython tabulate==0.9.0 # via jupyter-cache tomli==2.0.1 # via sphinx tornado==6.5.1 # via # ipykernel # jupyter-client traitlets==5.14.3 # via # comm # ipykernel # ipython # jupyter-client # jupyter-core # matplotlib-inline # nbclient # nbformat typing-extensions==4.12.2 # via # ipython # myst-nb # pydata-sphinx-theme # pygithub # referencing # sqlalchemy urllib3==2.5.0 # via # pygithub # requests wcwidth==0.2.13 # via prompt-toolkit wrapt==1.16.0 # via deprecated zipp==3.21.0 # via importlib-metadata ROCm-AMDMIGraphX-46524e8/docs/tutorials/000077500000000000000000000000001510465702400175165ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/docs/tutorials/MIGraphX-examples.rst000066400000000000000000000017501510465702400235060ustar00rootroot00000000000000.. meta:: :description: MIGraphX Examples :keywords: MIGraphX, AMD, ROCm, examples ******************************************************************** MIGraphX examples ******************************************************************** Example code for the following use cases is available in the `MIGraphX GitHub repository `_: * `Diffusion inference `_ * `AMD MIGraphX usage and utilities `_ * `Natural language processing inference `_ * `ONNX runtime `_ * `Transformer inference `_ * `Vision inference `_ROCm-AMDMIGraphX-46524e8/examples/000077500000000000000000000000001510465702400163565ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/README.md000066400000000000000000000004541510465702400176400ustar00rootroot00000000000000# AMD MIGraphX Examples ## Description This directory contains examples of common use cases for MIGraphX. ## Examples: - [MIGraphX usage and utilities](./migraphx) - [Vision inference examples](./vision) - [Natural language inference examples](./nlp) - [Diffusion inference examples](./diffusion) ROCm-AMDMIGraphX-46524e8/examples/diffusion/000077500000000000000000000000001510465702400203445ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/diffusion/README.md000066400000000000000000000002331510465702400216210ustar00rootroot00000000000000# Diffusion Inference Examples - [Python Stable Diffusion 2.1](./python_stable_diffusion_21) - [Python Stable Diffusion XL](./python_stable_diffusion_xl) ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/000077500000000000000000000000001510465702400263175ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/README.md000066400000000000000000000031071510465702400275770ustar00rootroot00000000000000# Stable Diffusion 1.5 + Control Net (Canny) This version was tested with [rocm 6.0](https://github.com/ROCm/AMDMIGraphX/tree/rocm-6.0.0) revision. ## Console application To run the console application, follow these steps below. Setup python environment ```bash # this will require the python venv to installed (e.g. apt install python3.8-venv) python3 -m venv sd_venv . sd_venv/bin/activate ``` Install dependencies ```bash pip install -r torch_requirements.txt -r requirements.txt ``` Use MIGraphX Python Module ```bash export PYTHONPATH=/opt/rocm/lib:$PYTHONPATH ``` Get models with diffusers ```bash python3 convert_stable_diffusion_controlnet_to_onnx.py \ --model_path runwayml/stable-diffusion-v1-5 \ --controlnet_path lllyasviel/sd-controlnet-canny \ --output_path models/sd15-onnx \ --fp16 ``` *Note: `models/sd15-onnx` will be used in the scripts.* Run the text-to-image script with the following example prompt and canny control image: ```bash python txt2img.py --prompt "blue bird" --control_image bird_canny.png ``` *Note: The first run will compile the models and cache them to make subsequent runs faster.* The result should look like this: ![example_output.jpg](./example_output.jpg) ## Gradio application Note: requires `Console application` to work Install gradio dependencies ```bash pip install -r gradio_requirements.txt ``` Usage ```bash python gradio_app.py --prompt "blue bird" --control_image bird_canny.png ``` This will load the models (which can take several minutes), and when the setup is ready, starts a server on `http://127.0.0.1:7860`. ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/bird_canny.png000066400000000000000000000557171510465702400311540ustar00rootroot00000000000000‰PNG  IHDR{C­[–IDATxœíÛ–´ ®…í5Öû¿²û¢öï¢r˜L*Zó»èa)„€š„ öß&„ض}ßÿþþ˜M2?@ˆ)s´xì7„¢˜†mê#Ò„âìûî­íþrÏy ”³†ª xùÖ}õQâqÈËŸ& uMìß3µŒ‘Å~È`œî•ô³M„sATžÅݳ"è&!F².9Ù\A<—ÿÞ­€ 3ïY¾>”S-Câ'"Ç~?{È “|´-ÿšýåtáó³M‡€øQ˜}ƒV¬utÜú—ÍùÍÏ0›tØn p¸ß8cúÃmñ8tÚįÀdÛÃ’›3ý¸ú†üN Ohš±þf†QUO>àéèœ ±4Àèo5÷€÷gm…a>v0Z.~(:[âµð!?S½CÂ\2O=Â>Þ–÷­€W;|ù澉›Ð©¯$1ªQjY ËŸ,bݪ¦Ö¬B7-Jûq¨¶µ¹©@µ–BÌdäÙD³àé×?¿ë<YÕ!|”T¾µ›Ù¢ºXùgñ|Ø{"ó ©Rò Nꎫ…õщï<Y-_}‚þ +v¶_Áò'f´Â—´0°>:+â=„™îl-ôC¸NRÿ ­á“àþ]-_¥Ã 7™ÔªÀGùVŒð­ð: !D?‡ÅñÙü,µí¬îûÉ$8™m÷eLŬwUá @Sp•END‰²xû÷Ç ¶Æ×w}ÉO²Q/7Ñe+aì¡{“uµ²ÏÄÅR¦ÿ¤D«4!„h Æ/?Ãè{P‚9d&4M ‘SÎôè,nG®X¼D{»Ó4ÀbÃ'î;'.@7ð- ì©|<ÏÈ¢õŽN™êFM„“Ñ%˜ðA®/³G4jm¥yÝÈ*^+P‹Ø7ºuKg ?,^ÂHh™E¸­ó\KØ£d=Ó)Pæ8ÔÂï@¸ £IÀRèˆÇÓ—aðé¦Êà³í>ãó6¦­°¹lO)gŠ-2 óž l}|Å\tÄó Ìg5=ècB ˜™ÅÐìòÙüL~w\ßQ…ñaâz4úâyL´§š¡0ø­6íÍôqˆYÜÞ’™A¶,\¶h6°¨&ð„@!XÆW™5Û)ëº[¡í± š‰-w’f«¬Xx©Œ×ÐW©J«ªç›æ…!Äÿ˜h”§άðY¾¹Òþú.ï/(–µhš3ʘ¶²vyäîEó/ñ0ö– I&adý¶${ëØ—oz’gïz?l1S#lb–‹;^å ­!Ä>ü “ }’³æp”ìãmevU#ìñÛa8ߤƒih¥©‰Vú:òzäuÅc0±ð–ij¤¨ê‚*Y Ôjÿ~Þ±[UÐå¬ðF,›)Ë^[¡mf³¢ê$#ìétü0~vþñ£Ý#4¼Õ¨w\‹Ià„êe{Z=–o«?ø¿9 f`B{J •¬Ž†9t’`Ä’'ú}üVoÅé¾9AÜês^lfËJ3äã÷Rr8óÈÅó 3›áÝ­ÑÄþƒ†âÄNv~}í‡=+Ò—€nªJö¥ŒoO4 „k%¦J8nåNܵPr)h À]m Õíçñäåĺ´æ:ÒŒ—ÍÎAÈ@µ[fGØÎÂbÕ)ÈVÌ! fPî~Ð ~¦[ëå'„˜Æ„`#˜Ø<1~$q8ly0ÏÀûq»+œw?7z+ÿ½[!RÞ|õM2ʘ½¬•½7ngdGCi`9ᨕi‹Û½àY rO¨Vø5¼¿‡âqàtÄ­W Ñ–±§[ôø&n×gWŒœ­0v™}Ìöµpþ§ªó~ô¶oºÕF¯¬øJ^Û1ñPîJ¼†&~âòCÓ@h4³´~‡2¢Êê™Y7ó£|«éq¡ÅÅpa XÞú“)²E\Þ/Kú’Æžfª:“ä ëçP¶š ”áÓ2FªÎLˆÍŸ1úF“>ÇðJ~¥Ÿb}H{áMOÕøb9á“?~–Ka”̚ȄdÁ¾oº´ïM…3Z­6IFÐf®àká9Gµfbñƒ¦_ˆûÙ Èò@HS£ Z&­åy5ªeJ9^fG+¥´ª2Œü²×ÕÖUC ͆Šùa÷V¦ûzxrwâú®Ýe®q0kp½MM—ðOã˜bûw˜Bçî.ÿ~H õlŠ©½ð‰øqó›l§—†³…¿õÿnÏÅ-ø;Øo«¶©üÙ”´iÅk’ù›Ž™ZÇ€fVÞ”ÏÔó®e‹†«lãñ^*lq£O(£Ø`ð~½ÿâJ€a…}¨[þÄÍ•?ñâ¡)9â0B'×$ñs ;xZŸö©>@IÊ)înÑÂK>F)l7ìòN,œt°GË ™Úe™ê"Gµ Ø/„xS¢9O'ŒñÁ´ b6@ÿÓëôœ; ™æMÉâz¯3èµ8½ Î…‰æ²xóì­á³§UQÊgê,ˆ“s4l¥|Œgî$àh®<`æyeÀ„¦ü©Øÿ24Ðâ\€Uʲ4YZƒÌêðŠ‘rv÷Ÿ J²(·^…½ÿ;Ào9ÌråÈ€sÁ¿‡ÑÚ¨¸ ÍÄm´ÞíãK¥(>ÆÏâY#Ü„ä›ój@x¨I)Çç‚üôb¢¨ 1†UBÿß›>‰9q"™Õ Cãðæ·¥ÝÉVw½éÌÒPŒÏ\ ˆß·èC^æþïcG¢<Ì#aC³|N–1 ÕGIñ`€•ÏÖ-}1_rîb ㊲™†¼’#ç/dDŸ²:#Ê«êwfÒ“ު°A31N 3QÞ5Oi 5a¢òêzFÖ—L™ruáh+l"[Fö3•c~Ð1€¾¤0ŸãµUÄ-Ȉ³À³'Ì'\ÀÑn˜) W÷ckè£à,-n$„yªR¿*ÀãIÕÛm-®¯ˆ»£ðÁfkã,9#ó ãa›¾vCQÌr±/_‚ëf‹aÔÏY_²zNÍÚoS+UÅÀøˆ¹h|E?MÁ{¶Zˆ‹«÷¡Úîà}©0ÊÆ'u }(¿ÕNb6†å‚³éE8ã!ç~CœŠ†XtÒ—º1ÁéxMH9Ø®u(Sö«# ÞÜÈ€a/@GÂÂ¥¶Ø|‹Ü”8ò2›‰3ÐX‹¦'î[… ^®¦frZ“Ú¾•ìPUs0ñÕ«a;ã½¶I祔:&r÷nž%.@-š1Ö¿Õj7­†>0Ñ:¹Úæã¶ºÓîU~6ÌkU+&SV©ž89!ÞÀak> ÕÃZÇ~pW;;ô¬Tj­UîÉâ†H5öoFš8NÖœ,Fê „X`û°¥n5pÞ”+Feo•p|Óü!Ð5ßn¶Í $û^n—«†ŠaâåË,‚üE›ïµTLÇ/X"ÎŽzóÔ$9ëÅàÒe¨Æßߟo7T ÃêZ…i+|€ôÈ+i~~Ìý±ú’É)÷ÿÕ>UT d´BÜI5|fC/ŠRÊ ->¨êlÊïß*e«šd‡úê†ckôñÅpCMZùV|‹ °â4`ÒZ•̬ cË:ÐJØÁ²-rÄpï¼üê6 s‚ü™zÊñŒI嫘írOYË 5Á  @ÛLTi ±X¯m¨³)vÊwœ/Ê›]¨ÕþS¾¹QÂ'ÅwS±(¼½6µ2kz‚° °õ›³°Œ¡ÁÖÖóæ,énCž$«bt fz†ÂAÝ>ã‹ÙŸÜ–€«Ó*’r2Ëë·³YÒú„0½>WÏô1Úf­`ÿÑÔh·h*Œ=“€«îRÞ\«ɲ䖨DìK²º¤•gðz¶VÌ´ Ëø¶ÂQ­¶Zäå«åËSà÷ !Ö%»QÍ͘/*´Ú™½`jyá@S=´PL:Ljf [”ÐÚ¯¬nUC,ÁlÈñ0B[y—ìå„n#ó™>¡ôÂ*aÃà}P8¬ÂŒîTV “Éçɼˆ|€Ï Éd´Š%í¸ß_µ¶a[åÏ-éÜ=Àîcë Éü¯è{Êt N+­>OœÞýœ÷Æf÷'…³ý[cúWXÍgî´­ÅT}JVE|¯ «ê›0l2»áKà­ü¹m_ªª—…xÕh½[fµ ¹?Ó f³I@u’QÀq®ß¨Öê˜4Tuæ¹àtkpšˆ”ð>̾3ñ¦Í†_ÒÇêfT2‘æî>Î$€Lü ºY³/*ïß_Ñ95‚ž.™™l !.‡ÌdÀ»;¦(ãšÓAr¢àåPø87«êhƒ£L5á¯7!Ä¥ó‡óSšö2Gä‡f—1î¦z¨gXØðÍ•e²ñ>&õDä„X‹jÐí­Rõ6îäG&¸.ßǦ<›lÑ t«ÇHxïèÅ#ЀˆÙ»žKÙkYl^lVñ0 Õ§}¶<ÿ¾»Ì¾ßö2÷ücúÇ!ò_*†Ò€ÿ¦ö«ýÜ4:8¡Bˆë s#¸z–Ê0ÛMA½Ï®Tƒè¬/fg5lÏ*ú¶ Lwp-¬Þù ÌhÜ«Ìï —+¾0*°—ágXÞħL²¹LmP×<'cl óÎ>ü«ü€#|Ô:t(ö)³{4B\G|áˆ>‹CáÎj£âZeɾ`Ów6VÌm xK9|­G]KBˆ+w7v¤WÀòù{>lèPnx£åðgØh¦ãF¬?–öh²AB\DÇ]WÊ­Ñú—ëO¶b 4PÀˆzzƒ ®Ú²Ð ðƒÿ&»_R= Bˆ³h5+ahŒ­¿1ßLD lnX=«ÈˆÍŠe:àòx(2 ˜·šþrBÜ@5eîFÆäU­yUïBšZaðF¼lÔ«ª¶ÒªØæºÿ2€''¡÷Ķ՞^ß’3Ž£øø°!óìŠyü&U6ú€òŸ{ñ¨LU½O1ÿ}вépHɦ¤y õ¡——=$ãöebEÈÐð†8«ž…Ï[ôeº‘  CTû˜éPN#Âù èÎïÂá…!.@nöw9bÞÖbæþôÁû}–ÒGy™aɰ•í;”¯×–m™Šàí„£éðh6·ÈBûpˆ²¡ ÏÎ;ð£ñ#âÈÀŸ‰ÊÃØÖÑf» Àý6V ìgBi¿‘•/µªjÒÂWwþNìðãÝâ:º­ÿæVêºM6h±Ãë”mu›ÐÌmTG S«A©û:Œ¸Q“ßA‹À?ÇNäŽ2¦ð^|Ÿ¤ï0ùslƒ…hÿ…œ’úå0û”u­*³V©‚§/}2ßÊ;¾o!ÄZ´Æþl“a"ÅL2²’8ú6ó‰°¼ÙSÊtô½ª§Ñ°Uþ ÈNÁoކ'Òmý«Ö³¯u°'Ô$T×LJJ!Æål‘Éöex2¿UÝùS„'ú>u„x)8Â6×” w’ „¶Õï¯z‹Òˆ‡¦÷ªba¦ƒ :°n²wá%q«FB¼lÁ›î½ÖºæÇÕËbYÓƒþ)4ý»ƒéhèlëÿt‹éÇçÑÝbQ2{Wåålß7*Y=,ÆDßU¿eÌcú™Œ’Õ2áÈÏå¹F3 žÛ!% uýv«4ÒºeeJwbüJÕ„%±«¨މw0F¾éÅ5öËÝãð'Ël!†MÒˆõo2v¸ÝpX¬©^_2´æ¤½Øl½Ã\fàÑb  ij¾$#°*Á» s«cgêl¬sS/L[Ù(yeš$œ‡qŠÝM_ sÇvlÈÑOÕúoµ%Vs4ÜSÕ!ÛÚ}Ð"ï3|ù¬ ¯ö:„ÞqPÎy€aÏ®=\@Q§jýAàÌR°‘‰2šdæ>t!Þô“z>…ªóc$0µ¦ ^MJ !,ÞXcëo¶Öò^¬Êˆ&¡/ñ%IM2 Ú q­x×8Ò )Œóšã/ÄÒ`“z¶õ/ïgohúâ;o#ZíÂ^à÷„6Èxšpû. 5¥Ýªþ@·³‘8} îø»%¼øЛ£‡4²ügùi~@ö|Ó¤„Í}ênûvWæ+uáNÿ­ºËù\ÚþýÙ.пÚJõ¨>79€‚uhjñ­å’ÿýÑû #Šù—À(€ÿÃstüO’ªzw|:Ñì‚Ò‰ÙäUa gÙYNÃü ­X˜¹ÆÞÍm(LõdYp§¯xW槉,ÃcºÐÚ‘‘ZZ븷ìuÕ ˜b¡á¨z—PŸ¾ÛÞ_ ÿ W‘íy– –º¨cB~³Qʧ,aÔ\z‹gMær×¼áLJ]ˆ/|<Ý` ;€ª2”á?Ñ+ÖÓÝ—PNµ<Øó”hô±3yêfoÃø>VÌ›— ™Çá}€ïmeކv÷ÞÁÂ@þoQNJÀ$#“,.àâ^À;ÉfÊàž© _¡ s&eÓÇãÛF±&°³Á23ËúVÄ­·zµ‹yYÂd삼óiJ_„åq.g{B§êYΧ<ÔW½Zبö1,¹A'ÚAv²úäLu;fL^У{Ñ àÙa»Ÿ}ùð!Qû‡¢öï\ ±Aw|<‡0ãUÏ:Îï܋ԓ))ct­#ˆ 9€Ç ¢^%+ŠÂæÛˆ Ÿ%oMÔcÚçÙji¢ý{i!Sc/2Zd~v·ì!>”cò§ïCˆŸ%Knð pgN²ÄHØJYuŸÕñy›ŽVxïè•im´ƒY­¼iv^Æ·hò4x6Ì&xXHÀ‡J ò6UªOwT• '=8ð÷7÷tP8Mäõmw_?2—!ð6LÖ¢<”åI²Ô?àïûà Àú“7j«1­&yðS"¡¶e (ÏxAÒaÊ}ˆ \Y7r/Ä›°¦ðŸ4܇¹Ìrâ¸uü%›æ‰K²Õ&NåõíXä¸[‘ŸFà©aBÔÌb‚¬K‡éÙÝã1Ui)“ŸÉ"óEGa3˜~9ÄkMdºY\Ö—,«Ø ð6€ Î –™+TM›9 Rÿ™™ö>‰tØÄg“ì–B§r\¡þ9Uø: ãëgHB¤4EˆÇbfö  ¶×LëF8~N£lkUÒ$™éS¸ud¦ðSYòD0rÆ…ñúõoUX^o¾§ÜçYÌvµXx4Ô38«G­zŽ^™)ׯkFãJ”z$äs2ÕE¶¾\ÿßðډʼní;ã5áÃsœŒÂ*­dé ò¹ ¬Ê\~3­áÓA­ã¬õä>äy•Ÿd§Bcm / }¿È%nf¶ªê³ç«Ùg[ÿ>ð˜¯&6ãϽÓÛý$ÂlÕÞŒëaÁìF¡ –ùF ­vmˆ7çcŒãÁSÍÊîÖ®§;ûë{mæ—­ È´¢À“&²´'Í…3ÚtËu<§Of`°Éz†SìÛÖ™ T3rãH»™ 89€ÇДü9;Cmü®þ¬Âä—²2ð…5¦z©F‡òs)ǺgjšÒÍŬètøù ñNÀ¢ëõ ˜íÂZ»Ã´ˆÁkŠ ëVUeDJÙGÓ_³1¨!éί¡uÀWÐùAhðl®¼ÜË3{†‡Ô'[eÝ¢ÜK¹ß¼Ÿ…›0{¼LS(FÙž‰á§ ÃË^g­ûgé°B(½‚/FàdFçÆz¸‡Ô'ìÎÇ+dÉ™s6ÀW/1V¾i™an^(ôU™k%ÀA Àc辦ϸšì¸yZZ½ÒðÞÂd}säQSŒ?º¥Y)øC”™ ÌôM»Bˆµ ÷¦$¯Ã$ø ä°z¹LJ¶ïkÊŒŽRw]#!ì]y4Ûiqk«5y´ò£ÀèÍ|4:åáüO–Õ UÚòy€~«ÊøÆV¤Êx˜ÿPSS h›ñð –àNŒÙþÿ&r/§/G_RµÅe~&¬{4Š*MIné/дhvn‘½ËlnY ¸ÒYF™m³Qj(Dr«3þ·¾¹ØÙÿ,Ëû¤|x(TÕ¤éI“j” ÓñÞ–G³&ü‚sÖ£ªªäy̦)ဟaô/p$šmq.Ýyy#É÷aP²©îaä˜AêÜÔÝà@e‡²Î†Ý/[™rRÌÆ8Us§Ê?[ù7¡À ©¦H|üË”,£x_1Km‘ÂÙ³.¸ƒLnÇ e‚ä’ùirV»[x'Mgê ë#çÍþô$¨øeΈýA,\U À½EóἯBö.î£þpQmWɶ3 «%½L ÛÆ'+Lþ&4xü¥oJV×]}üû—|­%K¸‡v—IÏzÁÄt¡ï)s#dù}¿ŽêáZ…Wfu”B™—¡Å!FS€œææ'#pÿ¦©õ‰Áf&‡Ýôj‡ÒÂmÐtuü\Áü­rAl~4tA'µ¢‰fïÄ›LI³³üyûYÚ?´~”)oKÿüŒ‘@>2ÔW ŒÀQÌt¹*“ fÁbÉC£æŽIbÛ¸ ø¬Ø?+@j˜UáÕËÔšã0ß´n¶³†øÀœd³‰oŸk1CÍ5 ¸—G†?E5gŘˆ|1ì3ÂÍ †— Ž2dw°n^HV¬| O€gGÜ|Õ³Ü-á‚nf­w*kë¡S%!þ2>ª«FýÇž%ÉêaØ‚%0GÃ~ËbX èÎ5L³0|bUÂKñŒ†¦Ë|ZX&2ªFµYto„ã¶L0FâÛw0îw{ñŒ|ØV¦ 6T]™À¦[âÇò„žo·FÇaÞ_¡ºÿ#Œ…™¾ZÀGÁŒœR¥j™m‡Ò@Éê„ TÌD÷d°ŸIn*?ΠÂd›ëÚe=-{wY[B<‰Ðj·ZØLÎ6ÃÊx#îiî+2¦¿ãP«>ƒrp¾ÅðP·Ì¬y ¡CèÎ@€A) µØ[23á¡òºÏÖ$Ó ™nþ½­í_V¡c.Õ·1^¬6™åqÿ‚XŸ =ÿ¸/Óp†Ì>ôèçRÈ,oýCáoõëöf.T¦,–ÝùÀ”“O Zõ‹FÕÖÅæÔÛ·3ö{²º}¬cý?,¥Œ÷ãL”ä3dæ¤)3Z…¹/ðìüÉ _ëØÞjCv­*™i}KºÐ=taw‹êÆЕ¬£É²hp?­iŸ°:®XΚ„oÉŒác¤²‡‹|wJi 9Ã'||£déïß÷‹¼´jêhHþÛ÷xnQ÷׺ù+ÞgÆ£w°ÚtGMãºY<{ ã¾¹€ •Š«‘¸—}g™^,(VÝoÊ€Ÿ¡ÖŽàÓÁ)ÜëTÇð×Ð à~ÂGìùŠYÜjŽÎ½LÀKFñ¦bÓtÄL>ÌN¿ïß«Õ)˜6Uû• ßÝ2ý?QŒý[ËO7Žû¿† QÇž}÷$±ÎºÛC%q_Ýé¡/øõêeÛ[48 gb¦pÖJUž° ÌÎqª#pšˆg0Ýú‡Â'Þê¥iÞ @ëÙž¬:©'¶’Þ 1Cmj1ZÜÆúƒê ·;€“„7éýb fYÿ-É&—bϾªž ëéþMX&”‰VM60Á^,0åÙvŸõ¥µâû~ª­­“-ž­˜Fk7°gÍÏn9åαa+þÜç<±àѾ–?T6jÞB¨>³Ÿ=uc0« YGÂF™W7È!zÊþ QgJ’ÕЕ;+n üÍy©ñV²ŸfBà'UÉÌ\$ë®{FœžÍ'΄ñ$€<Åg+¦F3€«1!R·™Î„øgó›8ªWCÔ2¤ %lI¸jë%˜F³£{þ:‚Ÿm”!ju|LÌž©´»‡vüO÷£_‹ãvêx©Bˆwâ㾎{ ‹ò@)ðb³’>î®ÆÈ¾k~V‘…ù@1PÒǰ@·ñ(ž–IiäÎ) J¾àB•Áhp& ªÆGU åι2ÃþYû£L×ã†ÂŸ¥œšÅf‘øEÜYÜ ª`MȉE¦üy¼fz!ÄSý}Ì›Âe&­ü¡)ÁiË·V)kùp›Òj±£€ÿÈôiÕ䉱ªfOG3€ë˜û‡¡ë=—Rµt^2Æ÷Êášަ½4ÿ¬ŽŸ9>̯ÆïxºÃ¬Ð”Á¾™‚d’}C[ï×™ªb' <›kæ@BÜI_ Z–«€™iȇä¾Q–†Ãüª²Ñ,$Ï$›N¦A]ÜsB³æüÆS„4Iiq®¶¡Ø“šx šÜ@GœîߒضUùfOë“eÊ>»å|£muZ³¹þš=~›©†«éÁ†°ï¦­áÙêË"!vxá-¢›'BÆÑ¾|vèØGd¦¡² £3Æ7äÕ›«Îª ›I€ikOÒ÷¾L¹?Sà‹]°ó—\_ÅùÙ50p q7Ifܱ í°þUMú´mj¥Æú‡¶~+Œ86šÞ=d^jDáYd}™(m°¤©rÁ…!€Q è"Z—|}êÃËÁG½LSfo_Û¿?cp¥w°V¡œC«rÃ/óúÄŽi:ìÿ‹0æð-âÁ¼þqϪJ'µËÐqù ñHšÂ,ðبK UbfŒ´ŽÐ„ÞÌüÃÇòXŸpO¸±l¼ég<[¤3¨~žn§²ìYÍÂÇDYhßzÆYa´ë…“7[¶P¹Eñ5^®F‘¸@6¯òU|MB ϽIùНFàtÈ{5³þÙ³+ÀŒàgBZ­C˜Õ1¡%ÿøP–)‡+t6&5´%#œùÎ?øÒ2°þa6)k:ÔóFQã~ª³ƒÈ,cý}F¢).Ë´ëðP¸–n¼ê$&³éx!ìc—³®ñh%äp±ä2üÈ, À¹ð¹læBã’D¦œ±×áí ¶ªa.Ș0o©5ðÌ)Ë8…î6¬Œ,¶¿×䎪j¼Œßéir'ÂÚa4…4]ÙfYŒ±þa„žËµ˜ÄTxôÏ=¥ctó^+Ã4ZZùQòÛ¡þWRž—>#¸‚éü)_u rçRµþU[SM­`LpŠU*‚¼J8óh º3ÝÊ@˜YöÒª}©kÒh¸|Ù &üçµ}®MK!p8ô#Ó>>ÝÑq›ÄQÕRƒX~s1¯÷+a¼ïs,ôŒõ/SC›:Üßœ/ö4Ô™ñ¬ Ó§ /ˆ »¯ùô]<•2AÅ…=Çþro&La²"Ö¼ãh«ငuË’ÌÈ´Ž^Uˆß0Úú¿ ­ñÔ]ò.ªg¤» ë÷}4˜Ž÷–³¯™´r˜ÐàïÓ„IËÍ•ûñÄ¢,& ÂÖÃi™½©*D=Km«Ê„Uæ‚ÏÚˆÌòç6<õ©V7ÍYó-!N¡;B÷uAL‘…»õºùbf£*?Ü®†Ï¥¨# .©ª Äfú”?ýÌÀüÍt8)ü©De#Ð-,ÓÚÜÄ^¿Í&ÓzfÉè,ÌÁá?:†±¶o%¬{$è}®ôú¦™…ß,/oª´Z„°zÓ*Ø¡ÕÜp¾fz¶7¶.D>ŽcBÚ0î&§ŒnFÉ,67ŰLfX××XI ð~¶X­ŽK>(ü7ÇgM—hÇÕÛ©Ö/¡ÀLøùìF,„1,~”p‹BÔ½7\m™ ੃ÿ‰¥y}Ÿ¼ùñ€„©|Òúo³ça^æ]Ì]Wàå¬Ð÷÷!0 œœ!Ëë_þ¬*àM!£(æMyfÜËFC·aš0ÎÀ+Pš°ÿ¨hÂÒ3–=M[äéÆn~¢%ÍXöHEèâ=0™‡¾ìD÷}BN®ÃJ˜– %0˜P“Òdgª2Â<\ ƒYùPI,ôÈ‹­ê<Îtù¥Ú}™Ëiyͦ1û›Â>þå%„&¯'›ÜfW>³',vè`& aìï7ÂTOUgÐÆ[gJâQ=M¦_¼æU…AÒo©I@Óx ñ˜À³I”‰=›ªWcpS „Õž¬<å÷W›Þ¢q(ÿša #tßhØ®ÖbNãWBW•œ‰*$k¨»‰¬Ýr£C²Wlp(¼ü‰ý¢¶‰äåèm °q­meæÀÈM'ßV¸X`‘Aá&•²¦³ê ER>è¯5býJgV#jÐT/Èq …¸‚q럙¼&9ØúcïR–äågÆÝÿõòCQYßq§ŒŒÏðcËŒy*ÃN5 uSLÉÀ–tD@<‰ªá`$øíЊ5Yç°•ªõ÷wuhû€óæ©jš›’ÙâÌ ð­gÍU5É,©O+ •Í]BÝfºªÀ éÔdZɈ‹˜hýC;’ÝÛ¾!ÆúcÝJëŸÙJsÏ—M‡Ê‡0TÆw?ë,¨v-”ßa,@ÉP²×¡I&¯ ÞƒÙÍ d|UŒ«* .b¢õÇÅ&6D#à˜ºÕ™&á˜0V¨gJöÙšja~¸æ’ÙÓóP µ0.øQ•W,i”™bÙžjD‰%·*ì[ÌLshˆ±¥öý"=MÖ÷K‘™Bf« t”iÒ¡Cû»†+?7:\fÓóéæ{Z»Yí×]½¿EfæüϬèE1—~XÀïv'3¸YÓ­7¼)ﻀ-¾?#å~l¸A£YÉL&sÆÃ­–È¿’j£á69|T@œ¶ËUë7Èmoà†B»é÷`;UÚͰ«eNW)TµébF[Cæìã­ 'å_Üqñ[dw8o;²ªé6¯F7`éÂ*ØŒnôm\Õ<Ô°:ªÙÎRÿЂ‡…7×_ÿÓ‹Êz„wf#<Å]iñ¥R+7øË¦©Ø•¿‹7XÕ2`£º³Ú–©~”ô–ŽïcÖDvg½0†8ÔŠ1 þÞƒMp¨LÕš—V&³8xÿöÝ__rŠ!»Ìâ Ì2}'›à‹]Öqñ»0V53ëþPf7ñ6PŒ´¡ŒÍ5bñý ºYÎ+œé–U1€VÂÞmÑyñ=ÍtûýyŸÅ•Fзßs† /9Q¢7Ûø\aøce™ýû¿ûšC¾Xµ€o7{ùv^a=~ò_AßÞñ]3øXøšîž¼ìß»Æobƒ‚¯8xåw÷á# 8}sÙéw§´åw–ï?›ýSZä_ùb>>Àô@ F@>ÆÌ àb¦À`L Z•;A£Uå77†Fp¸LëX2£yØVõL1{ÂÞe[ ¤£"S¾Z%;A­Í1µÆ‡î7Ñ  oþÀç€öïXÌÊvfÂ&2%G¢¡Cl­ A/ÄÔߨa¾GäˀɯŸËÓ„§ ÙׇÂùgïºi½:¬?#¿z~G˜2PB4ÅžYØ‹çl'ŽpqàÉÄe¯@H¨À¸Lˆg6Ì0zÈŸ~¨ÃF·ü2`<;UQewxõÎëEx§´‚Ï‚oEˆùd·}vñ…«º_Ý¿B }ªBº[¯ÍÌ=°†Æ f£š™x¯Ûà8‡Û!Ów$ÛèP îv# ëo·X!š™nýCQŒõÇ ô1Å…0eJ£l*â!eŒ¾—ãEv.ŒÏð‡€†Ý”ÍuXm¯ž)À‹šX2¬u†õ1býãÕíðèVة閈‘Ù×tV+³Ô¦ )?ÛÆvŸß¨šéŽ32hûüGŸ×;€ðÞ”)D3¡]&+úò™#a¬ÿúxã †.ë{ÕìߘYáÍͬ-_¨×m»ÍwØ…>Zku——OÅ_gÀBy“Á˜¹°‰ìB¿þºßÕbþЖhží ¥e­{O6N¥¯âÅçŒØøep¶õßàÈ75' n€7@Û·áà­f.„nC3ßœ¨Ð’fC‘É í]8 ¡ég :ãF W _Yà§Ì\xFÉj×ÌU=¨§©Ø7ÂBtÒv1Q¡—œ5‘‰½+äácÀ¬_@f¹§è`þ†ÒB­BQa™R¥P‡‘sáå„}/†Z_ÙYè¶CTUÈ]·ƒø °-K6YÜD¶½&™áö¦$ëKfgM­Ðdƒ!õífç(T¸jýÃ^W!í#¸NªuIBÐ$¼±lÜ/à°úâwÄ#P (%¼¼²ÌO¹QÍüø2øÐ¾Ò[ïGgýç(²/(ìQ΄îEmßi ŸðcU¶ž¥8²¶v÷íŠ ž&Ü;žSÏ8p´þKÝ’Kø4¸æíðtäbøû!4ôø¾ ­ï<¶yV†/ ²ÀåííÕ mhxÔw³ÜŸÓxùá²ÊáK²‘/†£aœ\¨sF¶Dq†¥óý2òÁsâý+(ÖÚ©ì‚‘gáç›ÙeÝ(0M˜CÕêדµ¾çj¬mØ;_ÅÈÏ0Û”ešòþ´šÂÌΰïx—º–µ‚›f*òÂy±M ƒëÿÆ{áMh)÷û°±Œ%·(°Â?÷|>qj¼sô1‹y·dâ’}~Âz³^‡üŒm9È^Ã݆~ÑŒ|6÷ ¡”Cš×P±lÿHI_‘)v{ˆ­`_\4@ä›md˜¸4Úå+|4‹CA‹|Ô–ô#i"bþ1d'¨lÔœÐîvL/Ùò}ç»ÖQ¾C2Ók|AšíìéTT@xÿƒ2ë[ÿ¾Z¡cð}–—´ÞÌmÉ`fÞÉæÃ=Â×Cø“1‚a/²AÈ$“ ñŠÀ cn®=|r…˜ c ýÑÌeö Ÿhý«æo\ZدL‚Ù®ÚܪõÚVËlÑ4;Áéó­ø†…A½Û÷(á&Lõ¦›)ߟ÷½ *pâU-ÄÿÀæööÂðW60”™e$»Ð}{ì°pùÓð…™ÞËåÇœ©:k~%½ò 9/´˜ ÷ƒÖ½ÙΑ ã.˜~ 1‡ìNöwNuOkuæPV¸jO'¶¶nŒfhÁ3ÍK+ìz!¦–)Ãw”Ì–ºeMãÓ]݃ñêebW\WBÌ$3ÙŒ½3{Z«3‡ø^ŒTg„ûî`M€aêÓ!”Üdý}ùL1\ ;_@±²"£¤¯^îÌ죿 q§V£Õ 1DvÃæ{Üú“!«Þö}¦ü¬Úâ²Àˆ'0-òJfê…×èŒÛÍt«åª¼¨ÔÑî d·Éõšˆ—ãïì«ZØ>ëï: à-xÿQuÙÆV˜Q_±©FíP_ “”gjefãIv<쾺29Ls ‚Oœ˜Â¯¿ö¹¤ÂÏ ˜Þ¼„EíÑëHaõ+¯éêK^U¼ÑÁ÷üÝ®îwÜþèob3ÂÁ÷'L/Â’ø,‡Ã•}8Á÷ ¿˜½WèõÉÎÂÊÚqAÏ•Ö$!34æ'¶>'ÝØÞòÌŒ¡÷Õ³¼q§€ì,Ð0Û¬ Ye5ª›C4£“¬«*­›ß?ëÉÌz¸?,iÔö;òMºy \Ÿ [ ‹áYùVÂýdsxϲ&Ä(†¾IÎFX7ÜVßå>ë>ñÖ‡±&¡ËvfÖ0»íM|ú²£Õñ1e€kÁšd%›\…Þê,ÍÏÙPYÿkøÝEàñõØOy°øV]1n=T…üeÖhø]ÌrO‡ÌLϲX¶D黳ÿûÖf¸Ø~ªÓwhë×]ñ)w÷ÕÒêšsß`n…é^}вjø\†˜Î/:€>Á—'E?“ã+ò ˜*þsÖXãfŒ±ó¶Ò4]ÕÙûƒp ³‡p2áfØ÷â{ÎM—$ {µÃêU7à÷Cm¨b1“p:Ù:Çå[çøÝ:ôa)aëF“ŽÈ)¥dƒ¯›Ù8¬yU‡&aaÓY\ÞTÌä4uÖ ÏTµƒLGÖçš›â—ù¹ÀŸû¿}Ç~Rýø¸Œ—9…²]ðèºÙ.ÇÊ’aGp¤9ÒÇÝ…±Y²«Ã^ø´ sšüUYáþªòဃñgßâQY=³;ÕÈ®,ƒ#â¹xá><œP›ŠMá*#Ķã#éöç( ÉËCa„Ñ’’ýS·©­Õ8û¦¿Î•ÖË»g_å¡ -”©B ,ÀóFps]¨v“ÇKÎ~úŠF7ã˜á OGµLÖ‹çÚÐçjþ,~.TÒ4»Ü“ i¶?”¦_Î&Ìzjû¥E“ù)%ø¯dòÃBeæf Â”ËÆ]>›”mg~¬ÀŸ—° ^ø³õ§Ót‘Äþa• f¸Y8™…ÿ~RâƒnÒnÑ0†‘ø`g;Bû‘}7ð¹r¬¼´r8@rµü–ŸšGpÁ­!~tÐjý·<Új µ.Í|ôîÜ£g4ù6kÔ—Ä…ꀔy¢±Œ—û¨Ízí6åË£¥`Vav†çîLz¾G‡ÿ›~Å©4…Óã‘»ÿr#‹=Ato¦þo¨‚Ñ«†À>LÆC7>-ÀÚâýfÄ|ø_n„uœ¦ËµÚ—{ÁŠ-«¶x ÷Zÿ펉y«ù† l‡æ;k%óR¡½Àƨ)Vm+À«ýe·¨*|/ÕjMµ_Ì/¦€š^Èœ;Ý/œž÷ßb5µò*fg˜P*‡Ôßêîƒøe™ì¥_#³úºovZùSSJ0zÝ$ÖÐqÍ\œEñç",³E‰D³Ì.ÄYÜ;Á\yzëCN{–{²P7Qû·¼x^R¶ØS3Õ3álë.{Í”0×Cu’ôˆž¾ŒŸ›ܵ¾teì?ŽC÷bŘ‘ÎEL]¼ÖšÉ4/0ïn¥Ô„äU…ñüTÿË?|TˆË>Eú³ˆOžž1Ûâ‹{o¤Õnã0:Û¾zS[ u¿?^*œmÝpצÉI)ê–•ÕÛ]~ðý¡§ôQˆNÖ¼ÄÃEö3«NZmÓb‡uóm1Õ«~Î3JfrÀpá1ÙÜ8±¢é2È ñB¼Ê³€·¯..ƒÃç¾Áñ—T&”z&ì<2+Æ·ÊlòjÀtªéÄ ñ–ºÐG¬ŸüÍ™Nã!šÈ*fr|DïEùŸÙ(yi|HÊ÷#CJx4²þ·ós‹À·°/¶Æåõ™ný·¨¿Ç"§ÿ¸PÇ ÕeŽöbõñP¾aɦUq#|w˧ë\*ÓÙÝ*÷Êq«…9>ª¨!™l1ŒÍ}r¦:ÃÈâ}¬a§7¥nŸ ÿÉ¡[íªûÐ=çâÁ¬s¹Ÿª #Üðf‘l%´&XÈá0jø¦}/°æåßLRSrMÖ¹~¥€.âö©îN<™>Þ#Ü¿ùÉçOLüò°Wi/¾ÎfÌ}ø¿o<˜ßúÆu˜"#%<ò:BÌÄ­Ó›à “A·©ÈÍBls4ÜŸ ̪7i‹ç|±¹s…+Qì¿ÿ¹[q§\M1ÝþoØÝ#vÁ(à„½ùö/ 3±ÅúÔ6ú´×"6W±¿WsvøßÓõEÇYŸù sA^Zk£áÜ¢*¡ª<þ“ÒŒäE‚îEÔ%šüà+ž³äó…?† ïã?ŒG˜™¹2–g=Êû¦J‡Ëf*³}¼žu4´,†èKD0_{. ­9ÈÉT÷TÕÎ^&(½ ø²±Ôš¾ì?Šwªï'Qì/Ä4MÀ™Â³<ŒÙÈ$àºYb'Ì5iÎh¦‰|s™’áF±ªÎ‹$^VÐAx4ø p|Z–ÙŠ¤A¡3Ò àÑÏŽÅUðíe&£ÂçšÀ¼¡õ¡ÏrÛ`ÇâûT= Y!n£#´Uº#Ê0æ­nøÂFŽÄM\U¾,iô1;è——Æa"¡bw±ˆBü"M鎓2Õ|Åæl·W ï›F€¬ú‰-waÅVõxÖq']QBˆ:ËÞ{Æ.øŸ¦p¶ˆmõgÝ~¢Õ¾û)B7ë›×ÅÕûq´ ®ã°á·&fÀû¶¦õwbÝ"4³Þ)Û“‡‹ÖAÖ_ˆÛXêöãã}_KÃ;§šý*¨j–¥Çú³¡ÁÄE˜×©þІnJî­ÅšbgY_‡–÷èWUa Ðìôîd/þ¡ÂšQÿ3BˆX$éx2g¦‹*}Àx’Ýÿõ8•û2dw|_ÈyÆ,¨’¿ÈÊ·bfÖ½#­?(Ù¤^(6”Ûͬ¸§ÜŸIc$ÜÅøÈ !†Ø îÖ%Æ[üc›)æ¥mS­¿n=™pÞïNø,ù.V¾ä„AO½“}ñaÂO§•Ëá»»¦ú^K+ë-òçþ³ –¾½ ª$ÄO°NÔ_ÖÍ6V»Ú©¹A±—Vm+Ï7T¶¸~ø¦kBˆ+Ø¿¹[mk±×@ónK*2C„ÛõÖ¹É9U9¤ë<Äe„gm‘ËOq¤aªÎx ›µnŒ©.ïcpБ̔û’~¸®Û}æ°åž…ÖÄdöüûe´Ã¦9ƒÕ 1ÒªÿœÀ4]•ŒÛ7ÖŸù?+$ÙÃWVPLq| h’>Ø÷­šx•Z#qPžÑŠL„á?u/aæg) ś٠îÖE|Ñ—¨)÷9“Ž&ª×L“÷b¬?#Íhõ°%.Vˆsi èÄ•ŒœŽéF¤#JÍvsÉñf/ö K:t«7ê#FxØ·€v—gÔõ·gÁÌäü³öÝšam]K0±¾­’ðèËb¶©§Bô ½À½TS+Y­²:ØÙ§¹“—©4ÅgsŽeQì/n uò.®¤/ö÷ugYÿP%FffÁ™à£CyŸKaj݈qÕë+,Þyëêr¼ž‰qúDO9•¡äjàƒV:”bìÿ,w%Þ€,ûÊ ZŸO˜kýMÄÊW4*£¤ä¦v×D±¿¸]j+3ÑúJ«6ÁO¶Èáë°û*-›ë¨~%PR¼ ]so%<³Ïu–¬Èì{U·3hššÜ‹îijèó[;ýxYyiêq´qNÏã´žÝJ(óh´ü0Ãî¾D}ÁUTŽÃâíec"ĶÑ+i8ŽS¸fç oÕ6œ+œÍƒ.Ú©*y÷þ²Ý¿Æ"_9ÂÛjud„ÎÐäh4^ÏB_%'›š“žÔ÷2Cr†|À”›. Ÿ{Ñueõ×Ñ”ö¹F¥5™,Ÿ$y\“³å” °~°7ž”eÇDLd¡¾ ÍŬÄn7íD+ÓªyE­y]Ýb‚oœŠ¥n(9xÓ8\³˜Žž×ô®×P7Ú¯qç  ¼Ôª_XT<âyÁàœ­yǴใ٠s'ŠWr› S:º=Æî?1d» ¡WSk^c—)¦äê/s`®¹„·g`Œþ²ö s¶ÎÏ5j·œÐ'”x*ü?×èóP4DrdÌK|Åó¸R =V'®†\ïÕEÉðܱ:[gF~9tÞÜÂaý/Ðá¡WŽ˜ËuS?¥tN"¼×ä“räeVæˆöïÏÈÞ5†ggt'Šàgå LZyâ MW˜ŸØÿöa¼`nt{Å:\´LÞ–Ÿ &­@ˆ*Šý…â'P–_,ˆæžBœKøöÖ¦ÌX]‚Bœ…·õJú‹¥8÷ŸÂ‹·R²cû—­[×—?y|„ïÁ¿þý ¤ö,=÷o68ñ;(ÍøHöq±mG"Þ›ï°nöOÊôÏË„oÀ„ÿÏmyµùOñ˜CàKYBÜÎüÁÄOñ l!¶õñd”FÿFF<9ÑÌg½÷G¬[8¿gþ-’‹  šéÈø/kûÌ"v˜À1Sœ>·÷ ÎR<9ÑÀa"sV&FÀ0ר×Êñ«ªu[Ù^¶ËBцyØ1<Š+ž¬`?Ç:v¹Çïî% !Ä3ðÀ”{Ì6¨¸,aæg–òÙCAB¬€Þ1þIùrOiÈúVD×Á+yžÚŸ¡{İ””:g§û¶ÇuùG&ýíÁAhÑç>lÖK—Ÿ}™x!ÞÑ“!&)Tî‘õ/ÁCw# wW–¹P#qrÚ"'+Ž£>? `𠜯¢ÐŽ›ù……u…1 ùyn\ &ÛZÿy-qZ£d b±äщB4à—DFø¤é]Êq 7…8óޱ‹  :1oŒÏ±hôÄíÈqšˆâ:ô¥â%h¸ƒðÃsBÜ‚fB܆Aâ^䄸™r9]Î@ñ ”b ?¤tâIèå&½&–E) Ñ^`ÐÓ>bY䄸9N±rB4cþ‚ÙÏ  V@_¢Ï×ü¿å!æß'!ÄKø©FÔ©þc5!®G) !(>!ü'êï0Ü ÿÅ‚($DóGL¹ABˆ÷ð”'ÜÁ»Wæ¿!Ûgü—D½ „x‹/›ë-~Xò¼Žx}NjH!Ng}sŒþ-胠b´,^ΑÁÿp·:}T܈Åk1‹·Ë¢—ÂÄ]ȈǓYÏeMÿñÙÝŠ!Ä7>Öòܺ´‡vD<ÍÄ“à?½°>å$@Y q râI<Úâ—Èâ !^ÂúO‚.ˆ> $nG q²øâ^äD?Õ¥Ëì¨ ß¥³”£5ÑIõ)ûã‹:þÐkRùBñàD¿?ª`–A߆7¢€¨sû‡yR/„¯¥ãjæSš fIôm8qšˆ Àÿp„ùe>ö×,¡ƒã?Žiô„“1ÿê„ù>ÿ‰|_RÁ,þÏB1³33r}„£¤¡ Л S:äôµË&4\â.äž !›ò±2OùJþ¯! „¨0ø¯ OM.ëU€ôi !DÌ Ý¿ = :‚^ £Т¼ãÃ÷Ên ±2r7“Ey2šBîSœÀuøÕWÝÞBˆ‘¸‚ðÁ›w[E¯B¬N&Ëœþ¬ÂöÒ$ìþ=1“r0Gå&@~ò^à‹VôÞ†˜‹‰0äúiåóVT+Ä"èSƒC0ŸNú"Ðz@œMiÇ4¨PÞ·©)¹80az÷ꑸŒcp·"kŽ‘F­JøA@sTã)ÄYd7˜n<eF`²@ºÅ”Ú¶(ÏãÑb¦X„ã«®º …èaÿænuÞ†fƒøI@8’U1ÈÏÍ´„+G¹j§KWˆ~4]@¸|Ÿ:/A$ˆé¼j ûAœ ùÊ÷I(öM0SÆÇ_Rä¼XËeWRŽöÓG>û¦“9z^³'”U^ûîˆ_¿Õƒ›ëðš…Jòú9ã2Óõ,f.›'¹&bèpþ T[C§éÝ3§‡©¸‘çMÄwGV¬ºG\L½Þ¢LÝÚNì#Öáé#,.&»<Öò ŠßßAn<" ‰¯gÅæ‡œr"ÎtM™ÁvÅ‹ /Λ¯¼Â&ŠY>ö¯ÿ6õ¸Ÿâü(…b• ƒÜó¨b–×óÜS<¨ðI/m…¢ŽÙÀãY,ÂE×"ýß!‹^Þqö«f·Ï.?×qŠ'2/5èjþ ÂäO™ùùDÇ/¸:þY#¹Äõ‚ÁOaÂ¥öÊøNôÁ<ýùð¡ûaÍô¹ì·ŒŒx3=®#ªø73^IæÊ/6{gpÒ]ó&ç*. göºÉè‹c€p€üŽ«¨|dÓ÷È<»yžÝ7{Þ1¶âl‮§d®ÿ;0 xÍU>‰Ÿ0 _³Ð"Îæ@Y>¼?BéÌþ)ò—Â{‚³ï#-$ˆAÚ^#Ô3j¯$4[eòzk<ã¾Ö»¯œªÝ?Ã1\“\ïFH¿ÞÌ<ÒãE}6ü |™ƒ§¯&û{ÆT[¯ !:Ùÿª– ËoÎÐW›ø5È!uÍžIz‰âUÿLôÁDŽåk\LÿF˜ü&áøÌÔÁ[|%‚„3 CÔ¾C¸½LVýiàÈNDµŒÅ "G”ä#(Õ_qëA¶VœMüÐi‚%„˜@N‚¸2›€ÂŠúIªãߺ_ƒÖD€9³ ìññ¬y]Ö'…·öäE ¾ú¦QB4‘å÷ùÐ<£’Í43ðt?#d~jH…Sì…É Uߢ: ¤«ÖÀŠÿÜ­€Xæ‘DlSüfö~l)§|ØTqk+xXƒ)„¨Buc—mÕ![Ssè×ýk#&„è'Ì×o¹%2.a°8 öÿå9ï2÷|¶†â¡èñ€6ð,ûI›xBÿ(|üOù=¥œð{A/ç&ð¹Ø“¯9U+ŠçWÆtóQZ®Öï£=”ýûk %Øpƒtö\éž|u®Úú7àÂ=ão±?§Ú]¸ÅoÐH™*aùÐUüÈg¥1`xñ «uþ–üÁ“õybd—!s|È™M BCïú2ï&X… œ§Û±ý;gäé,qž˜HpÙÐ/Œw^Ÿ ÂàDЇÐÐg™¢² {”\ö‚„ ð¯Ì•ã|{®I´rÏÙ"#¸‹cŠV“‘•o TßÌf±ªIúû7îóÈü•Óú±åGÀgÃø!mUÀìyÇÀþ,WŸ<2Müá²kËD1dë­ñNö4Þ#n¡ÖSƒ@VÀ7×ú–Æ\™režééÇ=®x(3K³™ûW{É×ký¡[È‚÷Œ,! Š=nšÜƒÊ“«îÛz7¦8‰ §64šdà¶ÕÜÆÅßÄg¨Ã,ÇöœÛi\a˜Œ?çjž Tñ<úŒ| ²“‚=±x+C'›¼…°cX-Ö0þl¢JYüFH[9Þ“^ðۇè8A5ú%UÏך` UœûÿŒ™fn¤ì>™1(•Êt<Ï’H‡0 r%ÕõÉ*}Æ0Kæscvþ%o¢eG«'‹\A}á |`ÉA±í„0H,Esâ/ÌÞVß:ÂGI™²b5æ½x‰›\}=O‡ŽÎûgg­šHÄù¥L²øEÝ+4[tI€q~ÄøˆVf®¬ÀÄ€…4žŽÖ§˜!ì†/xœƒy΄7[ÞH*¸él…“>‹‘œÕÁ,Gn| «yÀ[¡r÷Õ'V›´Â$(¶–˜«é†™åA¥ûpÁÉúÒ·þ1ËWÏ—Iƒ’# Jž¸t ý•“¹“u¨X±ê]—Õ ÷o“"Ü¿ü_Ë:„‡kMi±>&:€>fÒ5þZ긮>®·:{˜2È&…÷ZºÌ²ƒ¤N¬ Î`:„ˆó¨ä©ÿW.ɱnĹ4qV_¸}g„Cc]Vì¸LͽQ½âG"»Ì™µV/)òí{üOÍÚe9ß®„P0åM æ‘?ËøæÅN9S&ÿ^M“`ƨ¶"îeµ51Ñ1%–ïÀÇ> uƾðcâ ·&:Èb <'éNô»Sê–ýÌÔ0N:œdðzv‡)^±îÄW«¡´êÄ+sÛ^08Êí<Ê„;ŸŒ¶À}Õ:ŸÇø ÑÖ@»ÕÃ[±ã^ªöÈD‚¸°Û}jZ§}#þiÄ6eÙ²õ‹ÝjSÕ~59S ü)ëÿ,¬•¯ÂYÁÇe8|Îä‡å[£§ª÷2yÇÐ-’}é6ýL]Ú‚¦y†÷šŒ’ÝÎ#œ f'—L³€œFÙÄÈ$€áv‹^áà‚Ùà ù£âÁìûZÌcçgãø{àKz±eݪ¾Q²W+õñ?›šfæuó?Iɾn8&¡Ì¦.7 ²/ †"¦0.ÐwN±äªnä59¨‰Ù(z²ëA¼€8tÚáóe@y•ôeTÿˆ•XÐh‰Y2ýÑý;­œE Ì˜à†üѾi 3h{2“ u¨v<׸SÝø«w‹Î8yšöÞœÑÇpý°lÄ5^<¾û¥œó´ד¦qÌ~Ÿ" úh2¦MN¢ÕL75ÄCf[Ç-;P $OBÝÌN|ÆÃ,Af_Håy2P6ÚRtèÙwyàò}‘“‘Ìœ ßVv`ųøúPy¹øIßßßoª0àŠüì?czaºš°j,é+–·D5âö:ÿÁåèÌÎÓÝÕFPLþxWíÈ\þ¾¿8ôç–+Ê’¥J|hÒ4õ!Ëðð¥Ã^‡UªkâYÄËzÙÜЗìÀÌ'ðv¦ÀøÜ¼ï„zšC`i„Ï™d,“Iïlœ½ÃŠ1]À}¹ÌX„»<„õ{B!|@[¦JŸ#/Ë3~N6ý©_÷dR"<l\6ò±]#ï“( Wó!dJtÊ} I“SòÆ·z{ƒ$ð…¦ ÓArpõHH{G^6x~F¶ëÅV¥5ÕªN¿âŠ_ >OçÛ­v eòCû Z÷-fQj«;É„W5Œj½»šnH3—-öås˜FÆÏêÈvÁœÏz¯Ô¿é˜rúä~œtŽoÀ3èp?¦éâ3Qm¨ØtìoòVßæ«ð÷XfÁC5|Ô¿E³P“V•JÉLu&96âꀪÌü©,Yzúª§ä'7XÉ2(á ãò|îK¼•ÉŸ…áó!Ì•çm¶;`6ÐÚ…íÛø†b;fô`¶ŽïOÆàzûÅ ÚÖxÖª§ ëQæPÁÍ´â}€—ƒóTahŸ©ÑA“X2ªhª^"Ëüm‰ÚìŽò?¿Γ3Y™²d5 ž°t}Yª¯ ùld”j$kÊoð$n‰;Ù"sÉX´ñ`¿œâd=2Õ½ß2vÿ0ñâcüìdu\` 9?­”øq¨ C6ò2±œ,ôþ.ëÖ†Œ@ß∙¹R>#‡·&aP 耧YøBêK7eB|w˜œÏ殥l"â»ÃÜ&}óBï°/eäÄÿ¿Ð”™*87"¸° 3í=õªFdJC^‚ÜqCŒO µ5VÆGЙ†[rÊ>ªf} %ÿ‘5ÇøìD2¥’á¤á£’9TN2|‹ ÑªcȦÅxXF®=0>âÇi¾¤ð \ }•ê!R% @kPzwR€f²+Œ=)MÑ=­§€ çߌ™d£IõÚ+½cuv•µÈ”oš`ŒOj™ I3€_£’±õÁé6iA \âäD;œÅãÛûŒ.Ì5Ä}þï³23Y+³Òe¸Eàï·ÜÅ‚æÎv½™=J¨O‡ªä¼yd0Å‹7ñ_pÌ”ñëϲi²™¤ãG¸3Ë{`mCÍ™t inJWZ¬UØ}ÆŽ“:gôÍ3ÌÙr7â,öˆ½fìÈ§Ú 0©Ú¾=ßY޶Žö.IeÅÄ[²/WFjS`°[ËÚæq(Lm™LΣ—»Žæªí)]8/-Ç_´¾Ì–[p°4‘©·ã<9€§?ìÈÀÜ¿û2GIP×Û¯,Þ÷9ýí;=É%ö¨Ô9ämwXT²l¢i(²³Szýµ›jÝK‘,<åÞÍ|Î:8÷+³ÿ£ZÀ—)÷Û~4L]üÓK«„ÅÁãP–Étƃy*‹ v¡U ×¡z˜Š7PaõYç+ü)Âù½§ê n=s^€_mtJ} î0K³Õââ0/÷ìIæÝì­Œã[Ïö—'G;À`²¦Í5CæLüéóBMÂZÙxVÉ.Î}ÞD 7š(«Œ+ gÚjmàØ&]p­&Õh`Ö³æ²bF¬ßöwþN¯F†6<ä% ž8ìoü؆£m„ðçÝKîË>e]G鬄 ®=±,è=€óÈ–Rã¤,Œ/ܦUµ#{ð=DϘ¤ê=s(\MøÎÍ'ù`„«A4ßJ«ž­ò³ëÇ—a.'ã-üà«xª“PÞךe‚ñ à鑸VqõäÕ?2£Ÿ¢Œ™FÖŸ^tp8³,å­†ùY™³“„>ð7 øZU}ð°ãÑcô_ ú ç:šü÷Ì<“î“LOU<9Ã=WÞi¦Ñ&‹ÿ9D³¦..ÆhBZö&ÇÌ$!ù.¬LÓÍ¥ÙÀO±Šغ.зÁ"¦GPÕ– ðyù­æ’Ï›wœ‹¦LW¨ÉçúŒ+ ˜ªiXñ&cÎ }©˜Ádý2#ÛC3yƒ‘ÙdZZ“àØNe†ÞdÞ@6ì¤Ómf3áÚõyMg·Ìs\b„…fM‹uw+² ÚíË¥0ËÔØZñ‹:´>`Õ¡j.Áåñ—?&:¶Y” —Ãxê\–¡ìòCcÑÁƒOóÅ—éšwEDWs2[oˆÇ$d@CáÐe;œ² ã-p£áT8†>f¦¡¦ ÜV›.xµ‹éªú¿S8d2­#@1F»Ã Ïo‰V4èˆ0©2'† £`ñ¶ª†_r̤‘¢LþšW¬oèZcj¦G^°ˆV‰·|4újðš oî$^æ dúWàÍCßúTÉvš›Â •éÈ mɆ¦“Y ×WGÖ«d½.-]µ‰Ö¡ë6mã×UÇE’ý{'4âÞy²8·zŸg&¿¼ °=ÃÌIÀÖ˜U4 åY®Š:ï$Þ¾‚:Òµ“®sM®|ýï< ø fñø¹ÈõŠ-È e“Flµ•2rÌ ïǪòšÈó5k±·uÍ µÞwNIFMÑý•pqðÎîœê*(oÂn¡Û|o…¥n2” ‰Ž”q_zÛ££L“‘Ê&R`ʈ[¯ÂË)'©³šî“¦ÜÎyçy wøâöe–šltVÇ„„Gaß¼ÊÌ–2³Û·êJ¨JnuM:”ã6%ÐöM˜=MÞtúeÙœ¡†¸€w>êm_¹ÿ¯x‹Ø\µÆô7¼ùy1dlåm½/_v'¬;¾¶ÙãóðYÙ°>Ž3M4­—f«#¦•nõ&rFÊN,ËOŸBÒ w$:µ2{@ž¡# „…gÅŽÂŒè¥Ö‰Ð(Ÿ1 ›3ã®l>:1SäÕ8Ê´®¬Tõ¿+ gðÓ'²)Óz†YÙ«Ðô7Ù}ÞÜc5Â9VŸjªîEt^|[Y¢ '…γþ@7hA¢ …eâMÛ …¨„M³?Ú½|:¢Eu4 Ú R6'8c-a.·Œ³¸ÚÓIžvdÿ³Â™(lú;”_û­Ê¿2#Gb´â§\ va[Û3‰‰è÷¾%­12_·´2Œ2LV§©VØPÇÊm+ÞÚ^oªZÖgijöÍ Åí¼ó) ëé›/ûçvš&) ƒ…âVeZhimhœëÃên;~£*uh-O®¯(Môtä†è¸:–MáY*ÉŠ¿íGÄ2µ°æá–S üåÑhw{qI3†UŒ`ªˆ'¢ó7pÃlé|^~5)ÔôœÏøÓ„#TÕ E}uKñ1Mè/K ^ó‘ËP7{Új«yÖkPª]Ìe¹ èq Ó’ñè>«Å'^| [ô̓+[dþͧœ»ŸaòÝï³¹e¿ÞÚG‘±Ö™¾Æú?ÚȃÝÁZSE&bík¢{Y{J0Þqô$Öds¢å~µR@>¹"}"»”ÌT¯–Ïdš0‘×_íáñ°!r©–q`¦¿Ý½´Váû¾ÝñPÍïØG¼¶$^ÉZ`û¾Ãï·Ð¬ðZ’ŘÂYõRÃÌ]eO …-»]BGºñ=:uÝx®äliýšÑóɺqiÇö¶Æ:‡¸’åÀçB÷¥…5Ï·áŠdsüJo˜O?vfsì‡:–¶Ólb¡­?I™lþ7"ÐKyÚ :éóò`]‹x pòë#8×T}ÆÎTïËrTg$ÌÂÀÙã<>íàÍßIÝñ „gzÓÓ»Âñ;<à”/uu†sp—ù[w<ÒdI[%êÐD† t4ÇÛÁ³ýYÙ©¹—¨—´„óÇéJyíøSS‘Þ†êzƒIÐà´§ßš@éÓ>æ€3ËRÐS’7Äqáœð8üûÐ">­ƒÔŽE+¤jå9åY¹ü†M:æ8åí'º•ßv<¸8BÝsÕp úqž3Nó%¶.—1à®ÒÒ¡%9éÛ+ŸNµ¿4ŠXLˆñ aÊg¸ö8ªú’Gtlîüö‰Nb”ÁøÚÀõÅ.gÔ´e<;H úcÓœæ‹õû¸¹ŽPŸññwœo_öx<ØàR,‚E ¤aœŽr? µ©Œ¢ã¸¹#Œ};Ó¹äÌšfî8èiݰN8 ‘OÝï“JG¦;Òc·~ô§¶ri€¤Œ“ïNÓT`ÿ…( tüh~œÓÁç=©¿Î”)Ï\~4 p?þªróëM Šrð21øÒ¹ü3N'Ú›KÏc@Å$ùSºëL,wtðsŽôÀ=³ÏÖ”{Sva·Í;ë@Ѓ87oZiÇ¥ê8üèœ{ Ò zñJzàútíIÔcõ Bpçšpi7drG63Ó8\ç#ô¤nÇ4„ Aý)ˆB{wÜ‘ô¥ãi¬¨ËƒŽ(g<âŒ\wÏ$÷¤È éBä{úæ€Î984£=©§‘ÀïF1éŠ>”­€~”‡ƒ×¯j8ëŸñ ÎNH˜­ äiSŒ}) vsœÑÓŒc4ÜäÿAKߟևhÀã¥?‘£¿­!änôco Ü~”»¹>´UàòO´{àÒ ç¼‘Š{ãÚ€žã5RáÞîæhí&– ûå–0T.1 Ö¦ya”˜Ìò"qäR?…}½Oû•y^?<5­º–Úï÷;¿ Ös•´6§O[±³6f¶³By1òcÔ±ãô¥F¶º]È'¹#þ[‘þ9?€«‘ZGFÇ¡YÿñîåJm¥Í:…=CÞH®GÐa±ÐüŒöŽw³pyÛç4ŸªÓ¤1¹òEµ `xeŽPAôÁÖ§Xîå—ýê)o&=ÃÿAÀ§\Á©¤x’íJù£’dû`úÕE&g$È>϶%IÖ<ƒò‚¤óŽÊÇ#ó¨îï.Z‹Ê”:gvçÀýG4²Z—xåuhî!Œ´ í>ÄqÖ¥’{½Ëç[DìUŒtúñ­’F-Ý܆^;söûhšN°·ÚÕ[…@$þ" Én×SÂö£Çœüûwíè{Љå·xÀ¸³”mc÷ÕóÞžÅJâÚ;˜äÈ;’v‡n@È>àŠ¤‘-ˆuDí\æ'˜€žû€àS/$Ô6§î-¥_çÔÒç †Yã„n‘Ò1ܱUC­éÛ ¸åsü1!ü—57+•½ prÔeOSô¬ßíXº-½ãŒã?gaüñM}vÒ,™Òæ 8Ë@çùE×qû)ö5ÀÛùÓƒ§õª:•þ~Ëu¤ •Sóî:Š¿·åäÑqY­Çu œv£hÛGNÂúw´G4méÐb”ŒÀâ˜Å;Rå@8þ´`g§"€QÀàPG¥'|æŒÏNFh#z8#§9ÅqÇ=qŽÔƒ¸ ð@#¦ FÉ&?„‘ïÔÏÞ­#7¡Z˜ffIãã©Îߪ•$S.o™Še·OñEŒû䊒âþÑâf†n¥‘À9ëØ~>•]\Ir6[[Ë#É #¦zõôÇ<µeΑ9™¬Ó—ÆF0—aƒ}S¯ä*Œص˼Z]ìwoòàS?í™úÝ3ŠuΣäN<ø£´·þ2bYR?s…'éœÖ€ƒJ³´{£¨éRFÅ~HÚ"I<ò ®Ò@ÅkuÓ“?„ïuT‹RÓµD¤-îrÌFyVa‚г¢xn×Y,u;e†âÛq Í¾æ Ø†'%O§øÓ<„†8/lZ[+ˆßç¶•›kg¶UñéÒ°õ_øÖÒ)R{e»¶BÌ´œ†ÆsÙCVN¬­£;cBNÚ›ÚÃÝ:i6Éf–òÄö’•Ú@ÈàŒ`ôçZó-cM“Ož7Þf€±Ü  ’2v8ìÃŽ?¥tÐ|VA.ËÈïmdQ°¹¹_^©N+_S0ø“Kk¨nã¸K„òÚHSï°å .qìpu«£‹©JZ» ©†Œ–Ög› FæHFsö{¨Ýϳ3éíø×F—ßd¸pYKD×<㎤þuÅ_:‰_-ó¶BǦ=:ŒÖÈÕ-ž˜¤.Çf9ÉùsׯJõã(ÔoÌòêPqµ—sªŽõaºófbÓ]8r26ƨ„õ$þ‚«Ý»5µôû²—Ÿ]ÀÿJÈKýÓO{rv†Âÿp£òÉúÔ2ÝI{`À0V¸`̰}Xö ýH®z°PÔtùÛK±wRŽPóÈ­±¢`s÷xÉ?§ãWôË«X­åæ·up°°Tv«›ûmεu2XCæÄO8"@sÈ?xœƒéÐU«*Æòà˜ÖëÄW¹û±–ÈG«}ÜƼºÕ¶=j4W¼l¤>‚L¢ û‚2 ×NßÌS®õ­JbÒ-4‹c·¤¡;vyÏ·5u4k›;c.­¬[é–yæÞጟN©¬Eœ2ùš‹œBñO~r eÛ¿ ®m^Ú-Æ*ìÉ·Òõ-Ty×—šäk÷ÝÛû>×ôÆ÷øZziZT¶ÿf°Ò쯜 Ÿò"9ç2¾æ 'ð­ó¦©<ÝNæKé7gcŒD½z ã¿|Ö‚ \0;cµh©7ñóÄ­ Œ-;Ã6Ö—)u2Ä×IO&‰Ø`d÷ûÄý+xŒ~tŒ‚÷—Œu­’IhrJr›¼˜¸aÀã š ÂÒ¯_CíL94íß•4’ ~´îôs»Žž”ày¤ÎuÍJ¡*7È´¸lõãÓ ‚29¡zÂ.ÒN:Ð1 úó“Å pzRIÏLqKߦh½H÷¤Ç}îG4íïháÛ¦ð{ô£=;­'r3ô¦3žsŒzS[¦zÒŽ>Ý(ëßõ BOÒyôÐÓ˜G=y @H¦Hö£ƒAç·¿ZLŸ_z_^)½‡AÏ4 ä~¼Ò{Z4sÓŠS‘€2E!bâ^´ì’:ô¤íÅ_ ö¤ÏÌs€>” +ž˜£¹ã­)$~4™úš@(Z73MPxö£ŒÐºŒ(äd šnO4Ñ bÀPàñÓ½$8Èwæ›4hÖ쎢E#æB2ךG‘"@ÎÊ'ôäý*'‰.n.ä1Úýèíð7ÌqŒ‘ý?É™JÅÂ<̵fŒEY€èò @œôU{—©¨exæGG¹’ä.<Çc¶1ô¯°çëV obS!6¶‰Ñ6áˆô#¨úT–×Q4ßbµ@¸UF?ÚvèÔæ²±ÐŸbmíà…^MÂÜå‘T©©éøÔpÛÅ<Áî,"qæÈAüûŠf«¢\ê8îµd†Ùyx¢‹Ì-‘Ó$€õsu¤l6Äh2.Y‡NÔi¸\t—Ë8ÿC¤€î<µüÏôÏJwïÀ;÷±Æ22lŸ× ­ ;IÝË|±ñÿŽäUoÜM/”ëÜçdûåýùf™/̹,±F78ŠF:–÷àÖkkЉ1onå»´6íä?]kvŽTKhJ“œ„ƒbûèùU{»«›fÜ‚e ço깦 þÔ}ªÒ[¼³rœ{¦âJ†îK ™ rà óç¶;d·õ4õ–êxØÏmlw€[mÉ Gâ©]]YÌ\[C3½ÊLð9$!V–¦ržš w<«¥ÿ!]†ÇÏ^HÆÏ4ä‘`¶+$mpfb/®IéΈ{U;ˆUИncž2?ÔÊUÿ Ì5BæKÙ>uŠTDRáeϢʨ¿øñÍuF›hãu£{-‚ÒîÚQ˜¶ÁüÜyÆz’qž‡<~|ýÔ"ÏTxÂ0‚årÊäím§s(Ï#•#¿Òx{VÓ-o NZÑÝ€’ÞX¼ÔcÓ‚6àï>´ÿi7“GsÈ$óbPP¯Làdgž:š–ùt7§ ¾fr¿ˆõÏêwm¾ Æ8›÷lȤ•<ƒ’=ª§âŠuY8qüªO[«xoÒöÿè·ˆTR¼#z—?ìŠäë’KSÓŽÇf|%Þ•=–©¦ÛÞ4œ¬§ Tã™÷àçÞ¬|4Õ/6žÛä·¼FW\ô` Vã‘ô5Á9í]Ãè^×S—[—)od„ï8±ã÷Á?¥8EÉØ&Ò‰Ôø¶Ím.à• rãÎ@äb¹àþuÍFЬŠÓÈ1ÄÌ‘žvç©©õmZïQãH÷Úª‡ó[ï˜c<óɵÏOç—x²£Gôœ{ã==kºgJzœR¢ª»ÞƤš‡Ú®»È-‚‰‚Î{—=¿!Wä°VÕ8mÁÌv"M û¹cþzV$þ^Ú7‘xH—t…yêz †Q¤VÝA~²Jw±õ?˜®z³©SY3¦œ)ÓÒ(êŸÄd‚8…¬š‘Sˆ­–?*Ú.p0ϻִt=CÄzŬ’Y½¾dÒPˆ½+Ž·ŠX­Ô&[¦ÂàtŽŸŽ+׬,!±±†Ú ¨£ñ=Í/`£ñÕÄ=¢V°Ñ-aœ\Nd¼ºã\¶öNÃð­¥ 8è{T w¤íÐå»z±G^GJ# ëϘÀéJ@>Ù B•ç<ŒtÁ¥aÀýiúÓ±ÀÁ aÑE“×ò ä.hR¤à`Ð!ÙçÓdWtœÞœ9n €¼“Í!„jB®NO­IíŠ@6IŒ¾zSìã éF1ÞŽƒŽ@ Êg‘é@ {zÒÿrÒg"ƒòœÿ“@ A<÷¤æç½h$õëè&xëÇÓ¥&xõ¤ïžiXzŸÂ±ÀÀÿëP[$ôéKœãµÏOziã R8â1¥'¿4€sÈí@ &“8ÇzBFïóŠOáçÒãÏNÞ´gžE7±gælÏlq#îÚ?ÝWm8¤´9nFŽm¡IjÖ—ñŽKO³ãýåA·¿Û“^…¢E¤ÚYCmdºeÌ{°º2GÉ…y“ª4iw{l÷+ó2¥ €prøÀýOj½³n–ÐA³ã=1ƒÀ´áÇ4ÀÀìü½ÇjNÙéAéë@ Æ(P£‚ šQúRF)G^¤ã¶)z‚0iTcŒ“šPyÏO­#1ÀÎizž´¿…0œ>´¾¹¤{Ž”ÉÀ F@ã'ùÓǰâš9Zvzó@ƒ‘‚i­ŒûS›‘Éæ“8'¥4ò(8<}iy$ŒŸzn=ºÓ„w<ÓIÇ^=)zôÏ¥4ã=²(¥°qÓ4ÒÄz3œ’:Ñ‘ŒÐ!{×?Z éÚ œp1éIÉ9ìh$àãJPyÆÿiÈúz ''œzâ‚x=E ) ôâ€ç®>)¾ã½Žá@ÿhþt†!ë‘Ô Lõà 'Ú—<ô4¸>£ÚÜqùPO$úÔRËåDòžcÏjl—LynsÒ4Æâ}«:[·Ë"<&XÆ×™€òíÇp8ËÓ8ªFýo7\K+Cn~]Àd‘ýØÇñݱô«g‹ÈIî‚Ág1£G:qÕ›úŠV».ܾ£´ødx‹Ä€)ùžîégô!{}8­3mf\Ës/$3Œ±ï‘µC.¡iohCË,„¢ ó7mÇ=§¯jƒÍ–F#SºeÉ%,mÉ,àŒ Äsú⥢µ{—c¼e§š2|¨#ó%팱éùSˆ›þZÉu<ü¡ÁiŒ~5Êb>\i ²±ùUÆù'ŒÓŸ­[„ƗЛ›7Û9!¥Ê±©Û¸=Ž*,RßBÑIž-Ùãi²!‘ˆ‡¸Ê®EÛJ±Ä[¨*A_˜ûgŒS^æ9n!û=©òb”8‘"lc‘‘ÓŽ}y¢æ{iæ4e¹‘€Ç¦IçÛ=zÐÆÙ<ñ5ÒAlþò8Àeî1êkšÔ5­JÒxnm!Xôèòĸ̓ŒœóÔgiéÇÓ:uÍû01ˆ,|Ûþëzuåÿ3ô¥1ùö“ ÙÊ$–#ªð8§Ý׺ZL›nÝ£&2¡–?•IÁÈOAߦ}iÐÚÅ%¨·—kÇ_2M§hž¼ ô ¹ŽEÉKrÙj3˜fómp#ˆìÃþ€:*…ü“2F ËÈÛ•Q|ƒø‰ç¿Ô÷­s4Gìб…Øo#©ú{`TBK˜¼ØmS˶ •7Þ8è¾Þõ¯*µ‡Óç{™‚êò;¸m˜í†Ü å ÷Æqúâ¥Ò¯¦Òak ù1lBßßs’еyi+ɱ¤ýÎíò–ôãո˚†êu²ØÛÅ…lJÌÃÏsô—³héS„Ò[w4¤¹´‡FŠÒÚY„·¯çÈÍ ÀE¯¿¬XÔI“v#F$)ûÌíž~˜ó¦NÌÆådlu‰xèÉV¢éoý‚ú€ÀŠ) c üÏ´|}€­)ÅEŽVŒW™‡4žVšÙÈy¥ ?ÝQ“ú‘ùTZ›¸»û:žetf@þf´5}2{k­6æxÖR¼|¹r0ïŸÖ¨CÚ¥-ŒŒ?>Çü¬'y].§LU¦üÿ¯À…•d´/÷„SªGf#üv¬Â Z­Ø]³[Êpõ'‘ù„@cӉ¾tö֬f½·ÜV<îN~éê>¼V~ÏoCG=íܗàÅâË_³>#g9ãnGô¯\8$Œb¼{M¹‘|Ia,,¾\Ž0§ž¯_#çãÞ¼úŸ°Nú\~8Á•ÆjËê×BÝö$˜H0|{#·|V¦¿u-Â2ÑÑ$uß4¬ØXzžÙ¬»A²5„žLD~ÿR˜|ííÒfÔÕ•ËŠËip‘•7º£}ÈW€œc'².:ò,ya.Ò;·ú‚Ÿ¹Ü[aÓÿÕTmiRÉM•±ÚÒ\H}*ÿxžÀöý;Õ­’Íi‹%ò!#÷·2AêIè=ÿ!Fâ²,Mu©)…ïY²Î£suöE©ëÛ×lÂÛ†yeû:H2$8-îÄœ–<ñÒ³­n­må6º]°¹—vv]ÛØt××9c>ôÖ×[›”žàs!\8°±´ž'¦*lZ] G¹ºi Ó`6Ñu’êìaÙxÚ~nÇŸ¯µh­“HcŠá$žVÌ–G'î/¯]™=†}émcH`…ç Ö¶¬Ë¹Û»·^süDð8Ø®¥Í™ ‡ç•P–cìr>^ÀT¶ÑKRüBIšW! …‚(Pmìq’j­.úòXwÀñ£©4±Žõ‚Iyäã`‡eОÝî<ݸY%T% 8àwëŸéUnmÑÑä»[›û¢bXR8¥sp‰Ý·ŸNœ\ܺŽ4ù‡ê¼²¦c6pÃBcOAÉéD«Å êW ±2ãŽä'n•hM|±¤’Æ¡ð?xŒäÃ8Ít-58§w¥ÌÑ&ùf@‘‘ó`rx:þ5^;9$•å‘òÎÀsµsÀúúÖŒ¬n£ ,MБ·,3ÔžÔÙQ Ý·!FG5Ó †/B£ÌL2DŸxa3èNIþ­A »$@äç“ÔÿOþ°÷«ÂŠ@ °åŽO$õ5³yŠUI.y=±]tÒ±‹rmÆ%[ëieu€e!83v$z¯z’ÑM¬ŠªvÀàoLÔÆ&KtFöãÜ÷©D^R— ï~;zNRH\Ï“—¡‹6šŽv™‰ß#g«tý?­>èK¤V"LF$.NNrFÒ?*ÕxL0ç‚îÀ ߨõ°'Èçhn½ñJ3[ª²vocš{ˆÃå¥U“æcÉÉã?™©mmQ"UÁ\Fþš¹%«¥Ú003ÜäKåþð:®OøVÔi§«6«U캙öèÖÒ@FxÂç çÿ¯RMg‹Åo5~Ò¡›€TçÁ«Ûì ãªÈIãª÷«—6"r¤®¬ŸP}±üéû5Ë`u¹g«ÓS•ž8¡xžÙdò œgxÁàãÛkØm'[‹&Bv”dg‘ÏÒ€Ïä)üOJò¢ÇöéBÿJCÀÅŸë@ÅŒw£÷¤èiIÆc@¹'½<ð8œô@ ŽP)@ô¦ääñúÓ±ë@ÈTóßëK•#©>çŠAÛ½ž*ŒÅÈ+M`wcÓÖ”ñÓ#=h#ŒÐ!9CM#‘Î3N=}éÌ¥'LŒçð¤Æ'Ú”Œ¯„€yÆ÷ÇãHzç¶)O\ã<úPp3žÆ€NNFqíIêy tàœ“H;’yúМŒ)=ÇÖ†àc8þ´ÆO§z@&9ãúÓ1Ï<Ó˜·Ê“œãó ÌpsíM9ëÄPØÏN=qLcБúÐ1K0?"+^ײ-K$a¥$ 1áAÎ ÇÐÖÃ:"-…’}­y¾±¨Å«_,2ŒŸ.Ç™ýÜúI ºPæw{@ÑϼÔZFótv å®ÔÔg¥i‡û[,·NQ¿wŸ”מøîÝlž™¶J·½Ã9~1-Áè3ÀHÇ¿Lÿ“¼–‘Ùs*à(\GÙ¸ƒ××õ§Êi9$ôÑɸÜÜ•Ü(Œ6Ç\ƒÔãéQFϬdyšßHAó>iñÜûpqýq‘DµÞµw„{ÖÕ±#¼€‚Ùçv3üC '¦=3[7óCi˜“uµ©Ù KÂÏ0èIî®9ÿëUr™·ËêG ¤2/ÙÔý¶ü`â4Ï%øù‰ÏLþ}jö‘"\ymdŠ–‰!UÝÿ-Ÿ¡b{Ž£ { ”âµeŽAwu‹»•Y/îNCD‹ü \ô8­˜m-Ö(îتˆ I|˜Ç°êOôÏr1YÎÉv%ºÐ´™äâÖïQËÂà2ƒèce}G¥Oqh¢(Ù4q€“†ß¼OáFBï¹¹ÝneÎ^êP¥Î:àòzw¨¢½œE·%pg…ú “ùV{jÖHeÄwsŒ,PÀ„ýñ'ËõÃGŽœwëøÓ-ô•‹`¼¼•ãc”ÜŽcÓnÕ?¥w|\ÁbFÙ$Î^imáÕtæf†ò(!•·L×÷gé–ãðÇãEÚ%¤ü½šÎÌe>Úå# ­º¹Tã 8V?®}éZ渋X”œaVgPÞØ¥½ÕZb¶ÿÚÒÌz,VP_Çg8üj.¡o30¿Ñ”ì]ŽOàMk&ö9çk©v=A-âd6þ\Ë÷Ùå'Ûñªq0ºžrldµFpewQ rq¸>ø#ëVí×ì:|±†ã-D uTONI¦{Ÿ-¥¼ž(sQÅtcï’Gì+]·1ÓaxÂx-¼»VmÂW¸bœd/9<þ•T¹“ Œàó€I÷Ç@*{;m?Êw£€@–[—Þ íT¡ÿ{5“G<¤Û@Éû€Ü~¼V±¶È樅%P(ÞLr|¾§§ÒšÒ® ìTàOzc(î`Xv^Õ)ÂŒ…é·¯çï[Eò}[-.[r£"šÉÈùT÷#<œÓÖ&,ç$wêj`¿¼Øp:“ÎG_ð®•.S´1ùð³+oŒ‹Ûžçפ»Šß?PJ‚:Ô¨PÍ$‡¤á@¢Õü»—ùÚséÒ’m»ƒz[± µêÁã«;)“YXã+ÁÜy-Ÿ¨Áö5æ÷Pyö…1lc¦NqW!Þ@Ï×'¯õ¥Ž!æà ‘ßµgµ'.çEJ‰ÁE-Š-kö‹Ô“Ï÷Gùþ• ÌDê/ŽT}zÖŒ{¡•p$dÔ{†â9î{×E:Î ™ÉÝÒ ™ÀÉüiªº«Àu?þª¶è<`J«$+ Á^ŸÌW]:œÈÁµÍï®íãÆÉ¹wd>Þ„v?\Õ+Y Ž¥Äh>ÓÆGü´^„ÜãšÔ†=ªÈI;²À“Èö¬mB@e@w)ÏåYbi*wÜíÂÕ´¹nze­ÄwVñÍÜŒ¹¦ÇPMs^¾[‹,0ܽ½ûóùWHä׃%fz-YØvpsK×±Í }E*ôþ ÒßÃé@#½'qíïAÇ9Í!ŽíGfš~ïøÒ¯n‹IÅ@ô§F(E.GZjž)ÃJLŒçàq‘Ö0iHÙ —¡íÍ`šAŒ` ŠR;ïTHrsÀè)É¥ç¦@¤*H<ƒHCJœõ<úR‘Ï¥. sùÒÇçÍ1± ÆzÐG|`ÓÆéLÇ#ƒÇ­¾÷ÓO?ÌóJ@ãœö¦±Ï~(ãL<÷?ãNÀõé„çÓŽ9 œ8þ´Ò9êsŠR>nAǽ0w8Ç×½!ŠÜcØúÓ[‘ŠP=»SLgš:`s×Ó¯~ôâØç’Hõ¨g”[Á$¬7RØîh Ì\?“ä$‹¾V+’}s\UÃyÌD»YÉè_ú(ë튲n'2}¢áÄ·2±xÑF@õ?Aù«Ħv$qË<‡$¹=I=qÛŽOOSI3º0Pý(¼·¹|²B aŸ•ùáG|zdÑs;ÝLÎJ´¼*“•yIÁsêàz‘ÇCŠÿoxm’(”«}Ø“Ðã–>à~ øW-gŠÕ`;L‘B¡¢ŒužSÆïaÛ>™õ­cfrI5&Ù¥GN¶0I)ŽâEó'|e£SÜãø›€GQM"; y¢]ñ¨Ha ~ï'ž½9튉§òƒ\LÅævÞÅOû˜íšÌ’êÎk¨e¿ŸËØ¢çƒÆæÆp¾ÝOãZ]E\çŒeRZu…¶éa)(ši‰pÎØAýç=ð>èõ©'Ô µ»ht¸ÍÕÉ=ܲª`÷ÚAô¬&Yídݲ°+4÷±ü8P¸Ïš¶— ¸ýÛév“ÂÜŒºHԜҸ§.fz0‡*.Éeª\#JÍj¬å¢îsvÛ“PGg"H¦}St]ÄpúœÑš¢9.¥†]2b96óŸÕASƒê{ZÞ×S‚É}É9ÿ"¥vû2Qanì$´K¦“þzùäŸìªÑ6‘£å Õ¬"èž ûß'ØM8é s5ö§nŒxÝqìËÇáYöÂÏÍæ)'¿‘,i¸ãêr1õŸÌ››’O …cºœÆ<¨ã¬\ÈUÍä 0ƒu*cç.Ë·¯ÍÇ~0;UvGp3G“…óGœÏÏdÀ_¯¯Z±$pÁµï'6èà çȶ9㜠ö5i$sÊR}Mqwtª’]í¤­W½ÎAÀ÷éP®›kcs¦®nÜ!L³}X€æ1õ«i{övûAÄ›DλóôÎ9÷"ª¥Ü¥.b³`Á[™ jP’9-ž½{ qÒ­yÊá5¢Í?Û/-‘€ç€íÇÏ4Â^E`Uù‚ õ=úR%¹kîväwÇü)ÁY“-ŸÂ¥¿•tGcŠNìb„P `‘Ðàg󧪪F† ð¬:ûý3Cü ˆÔ±ã<óš‘ÜîfsŒöµI˜ÉÙ ÇÎdßÎÝ c¨çùÒɉ Vfè3É'½If ;I"Š7ï´;~5^ævOº»¤,8Ç““üÒº#·S';ØšH䶈£ àsƒÛŸÖªÎ¸€ÅÑœ…Ô“ÍKcp"Ô#{„IÒ<³Fçåc´àăU"“3£Ès忝’JêÝ:Ù—ÆùAùÎ(Y1à|Ø*ؑÿפi],c® ?@@ª§OB%&Þƒ¡$ĸêTgò§ìÝ›*'§ô©SïqÞ±¨µ5M_…#‚Ë’ó¨ ‡n@ÁïïZ ™#8<1¬ë±sa] 0J“…Ï8=ò9…táÚµŒ¥†!6â9!I+×ð¬{ÅYUð¥eQ‡ûÃÖ¦–á”)RU”ýå8?…E<Æé|ÇUYׇ*0ý¯oq]1ZšR‹Ž£|5uý©íÎRLÇEzH9PTpyÍyd!¢¸Rx(Ûõ¯EÒ&X`òP•æ¼<]/gQùžÌeÏú—ðHàPýqHÄ(ù±J8â¹F(ÏŠ?•‚=èê;R¼¥QŽ”›¸Æ*P®h€œ´àqÍ4u8éJz=;PƒéúÑ‚EýirP1xï@Ï¥ŒPùæ€"G­>¦} æŸÜS3Ó’@Ïzv2rsHØÎ) íšSÇ~M4ôöÍñÔ}) tö¦[ çŠBÇ©iO¿$SN3÷sïïA"7âza$Ž)OÎqõ¦¶2Oô <â[ õƒ‘HNO€=©¹=p0M+3škéH·lãÖ˜N[ úšŽÍFÇ’@ëÇ VÉë\çŠu¶ëd§2Ió68Súâ·§ž;xÚYX,j¹$ž•çšÎ¯åä³[D]]vcøè uÛßðæ¦FØxÞW`GO9Üy{~fn2;;nôù®ãòÀ[ïb8Ô|ÎÝ?¯nîj”—~D_é/ó·"?îûŸóÇò}¸û1óæùZA…wôï´u=q4u4]²xácö$ðQHBÀzÖ’íä2Ï4lî71?*¨ü{Ÿ‡½QYìmí–âC´¶V5|1ÉfÇ,sŽ9ª@^ê7Üm+gr½À ÌOp‡ŽƒéõÅ>kºn£×c§¶z„±ÍXP¿»3Ƕ$SÑÛ<¹î{ÇÁµÒ¾À-.„3”bò<…ãýíÍó~¿…dÆö“²^]'ä‹ÍêÛG'ñ©Õ.£—lVvP0ÿH&Cÿt¿g)9(«DÕŠ}Ò5š,mÕx¢³9ᘟ ¦ùY ·œÀç <óˆËޏÜFúûQk y湆Ök¿ùgw Ec¹ ‚Ç»U¨„Wæ¸ÕePB0ˆÿ€‚6Ÿ­HôÜ¥ I{½Ú41ûöö­!<à*™õÅlD%Ó­ÖÞd³€rZ¿Žä‚6©=ê,]ÊØ jŸ7W/tëÏePBþBœöð.?´.–. |±ÀNˆŸ1?Z]I“¸Â4÷mÐ[=ýÆÎeÊ1žäü UÀo'€‘< ±À»ÉöÜFШ\YZÛ®Õšáî±a>»ŸŽ9éN]Mæ0°ŠáÕÉýÜ·ÏÇsƒ´cÐ}*Œ¤î1£-)Š'!¤È2ù¬Ò0n#œ`d ô¨Î“® Vy'1!·2’;¹éœúÕÛ;øäÓmšÎ(šaæ@Ògåb­—ry;wsÇ•„ú­¼¦ÒSšLÍ,å·ÎBžÃ ö¢ìc4ÒÕŽkf&öFÇh|äóùÔ,1cÊ ØûŠå'·==¿µ¢ìsI«]Ž‚=dßÀê«8'>QŒˆTz–8Á´íI/c•’+Vû8Î$,ıÜFâÍÏ8Û5Ï|±¤böè° Y$ýåôªò¹‘–À€{Ÿ¯ÕFƒ“¹ÍS’±^[ø-ü¯¶^ANÒK9ò@QÓÜúU‰&#x ’¤ žsÏ5EìàŒïòÔ¹PªsÉŽ¿—z·ã'v[ÝÏ ÿëW£JäsJQir“Ú]]FÒÆ$w§¡ãÓ ¬ä—uÔ¼ˆc!xû÷â®Ï:$©²«"áPžpjž3ÎÜl‘èÐWU:i»™óò¦™4XXç,2s×$棉À.ǧ§­5Ù•\ô µQ¥ ˆÏÝÁb?ÏÖº½ŠhPÖþf»ŽÕw–Ë”%G«UD¶¢Xò0>üЍ0 ÝXþC¢ ˜[=MO±ŒV†Ð§dÍÈdÉ?Z±»Œt¬Ø¤àŒú¼Ž¿/?y•éY’›C³ëÛõª·Öþt>bƒ…#q=¦JåÛ(VÏjŠàÉn¤«²EÔrOÔz‚¡©§>ç? Ã0•F¼¸ï¸b¦”eÁîzÔEqÓ äWLN¨½ jù Ô ê|1pÌ&‡8 þJæs†+gA”ǩýÎÙ®3׌ÿA\Ù•;ÓRìtafîâÎ¸à‘œúÐGéIÇðýiÀœqüº×„v3Á9Í.1Hã=iA9 bÖ—¨¥ïœu¤ç('Ý9õ§œüÓGAÏ4¼ãÞ€“Ö”{Ò™Å(w4ã“@ÎOz=sH:œÐ21ïÀ”䃀)3èir9#ŸÂ™˜Gc=©;ôç¸èFAíIœç¦ í@}i¤qÇò§qœ~4ÞiˆAÇõ¦ žÿZsd·=)½ˆ B7<ä‘î)‡§×ÞÛ ëL=2=;ÐuáLw8ç¥;·&šyù kã×¥1†0sŸ\SÙAê2zt¨Ø ¤óùR1# ãÞ¢w…™” $ž;®MrÞ)Õ2ßÙÑsƒ0^­èƒëÞ“v.svFf¯¨Ë­\VÙe8SÀnÛ›ú §SÕjŽ~a€Ãúÿ*Œ1p³1_(^¿·°õ«› Œ$`ŒÇ¡a‘Ç ö¬¯sÓŒU‘^+(•ã‘G™3œ‡—zçר~µ[S[µm¢io¶ò9â,u$t۷פó_Èîöú`g”€$¸Â=‹{hìáx…ÏúÙHÁüúþüiÜNÄpZáž~¹oáQéÙE]·ýä›®‰aÔ¢ƒ´g¹ãŸÄŠb!Y-¥pAeûDðòô«ÐG °š?-£ûÆw|B§ëÕÏ¿¯¥%± n³b8ö2«·-dúš×··œ[«^™míwd¢*À¿‹˜Õk9š/™ZEžUêQšR‡Ž¬:Æ–ÚkyQ£–Õ#¶Œ¿˜»›×<~ƒŠlöBÃç½x-¢RDQ0 OF¼úqúÔWsÙ\w²Ü­¿úù÷p¦zO®4’Ð~C†½<šmÅôzwökÇ)·Ï@©"sµÀaÀ 9¹ãЧ.®·ŽdR‘ “yƒcÜ„ùCÔ/Þ` w¥´¹Œr,ü±lчcÈÁÏñÎ=*†·}÷qÞZÅç-»y(ÌI 8èIãëNWЪ•dÓº6çíþf,`dÎÑ:FO±ÁÝŸj§=ÌvѱÄÈ-#)f'ЧaUot»;&—í:”Nøû2ÚM¼‡ÎrÙà g¿\Tf4‚?=Ô»¶5ÀÓÛþ½k¦”.µw<úóQz+ “TQ&õŒg&MÙ‡AøRyΣ Æ1ïR$qÚÚ‹ùÓt¥š8 a¸u|c3êsÙpaV7ï›j¹%Üúg“úW­ƒW¿cÍ®­kîÇ¡†0fŸt…J²Â<Ážrz¨Àúý:Õ™®ž¸”(C6ˆ 8$€=RŠO³Ì.ýê?˜çžÀý8âœñIÍ"°aÙºóódýsšÝÓn¥ˆ½£©X(7fæ\`±ïëC>ÈžCÔ {~\òO5Ÿ¨9ku~ó°ü?Î+Ó„RDÅ:’W$šR,×=[¯ãН$Ÿ: ù¤a“ž§Ò›&@Uê~QýjœƒÎ¿’Væ8 ŒqËUú?…TåÊŽº4o–Þlüà]‹ËúT6{MR$)UÎ@^qüªÄ%iZå¸r£R&äôê?¥\ƒ-" ÏÍ€áYÈãi9=jí´fäˆ#É—?'lû{q\X˜Z79öz–uTÝÆW ²Cñî€üx‰ýŠDp +n ãõþ‚‰ÝÝa-™ ¨FÓÏ_fÏýõQõ3€ÜÁÁúŠÂŒ†¤ÔÝ™r'Í´ž‡ÖšGNÜsOóœJ ’989?çÚº¡§dý×^*Õ„‚) •‰\èb@þ¦ªÈËäÎW“òö=‡ëSi¿¼Ž@Ý}ÅræR±Ñ…MÊçëšNƒÎ(=sŸÒ—pÉùzûWγ¹8Ç'é7|Ü~”˜=yúS—ޏüé RI>œRäóéIŽzÐ8 QÀëNë׿­4¹ sÎM;ñš\õíIŽ(àÐ9¥Îhúš¿*D9t—<ôõ¤ÇŒsJ €¿jdHëÎŽãڔàϽèÞzñéMnFM8œg Æ›ìF>”A÷H9ZacÀ§sÛßši9ã§õ¦[9?•0ão|{ÓÙI8çZŒ’ÿ!Î9éM´㵟PB¦S¿ÞrHRãûÌ…}?Ö‹ÊV#v‚Òïʱ’Dy€%SôäþuLJ …ìÑßo&âvÀ¹ÀïúÔ×v¡n#i6ˆBæ8¶xÂõcýjÂÄZEW_2UäùÇpŒ{ à}N=©òؔȭíþÑ´ÄÏ3÷pp¿Äkz+2hwA¸'ݯ¿d©ª˜È)–àƒ=Èú þ¢¦çVŽg üÆÞJ}Y»Ô6ÂÖ³7ú¦Ô%FÀ „†2=3õëÍ<\Ü¿-x©ýÿ³'îÿúT$#*¤1©Oጠþÿw;WמjÂØÝI™ðˆcä=À2íÿuPl{“MG¹œ¤¶$ÝbÖ¢â+W™Æ6ˆ O©•ø€jÚòÔ6èiåPw-¹i‚óÔ‘¶©Â°ÝK›„–þ䨿q°qÙ~UjÖi ;lV"Êçˆr#þ‡'ñVfRK¡b¦»˜Ç3íQƒ‡mÌ=¶«ç=ðH«ËvС²³ jOÍ+Á°õflO·OZ¦nµ+©];}‘q¿ËQ @÷$¿'òëÜUˆç¸¿òìtHÂÖ|3H¡|ÉPuo½Ðð9ëÖ¥«-DµÑ3= K»Ãgc2æ[™2ÛRÌÍŒúõíS=·úZÇ)´Óîwqº[—Ýéß·»÷­Wˆæk;±ZÛ2¤˜,†rzüÜ/^ ç½gÞÞ\2ïµh­Ó;Mã'ï$1ã £¦IçŽõqW"rQх͹ެm•2]¤¯\1ÏR£àg=*¤¦(Ò--tGŽK»¦ØYØ·|(àyÇ~b]*[­²JÒØýì¨ÒÈ~€?ÏZ–Í`µ½hì~ΗxÇŸxáäþìk¹õ)¸1Äûpw®Â§Z„“Óñ¦ÌÑØŽ Š—N&V»W!‚HÏÍž=è†< •Qäù<ŒgóÅ[‚e+°òàð9éSdm$⮆ÌËk+ ©ûËíœV†‰<ÅHÉÚ«õ ä:ÌwE°0!³ƒíÓùÿ*Úðêí¹U#«9é€Mxùœôå;°‘÷nv=N1Á¥8ãž4PØþµá3 œt§žüSHÎ8¥ †ì>”€S÷‡&”ò8ãÞ›œŸosN?wÿ¯@ÀœR㜚hÉ÷4 œœñÅÁ/Ô`mâ—zЊ_jháŽÔ ‰z{õúphŒðH½NiqןZ3‚zKŽI¤ûËÓz!çžE4Œ`ƒÀ¥ãnô¤?{€:úSÆ=¿3L,}=©ç³úÓKu#zP|óÛ)¬pZybq‘øTg¸4BA#°íšCœqÓ=iI÷"™œ žO  b1 Ó‘YÚÞ¡&›¥É<1ù’ðˆ§ '¹ö :pã\'Šuƒ5ë[d¬ –•»Ÿ`:T³J0çŒ'Ìy ¹f?¼ùç¾)mbó>hÕ〜d­˜új[kf¿a$ˆD2±/çú×Cik퓹HGÊ'ý˜Çó5)Ó•ˆ­4Åe`QDQŒÈ‹‘ÿ¾GRúÕ­0—;ÂÇ÷6ǧo]£S€*ghíLÏnvµÒã9ÛþܧüÇ7Ýj7ï0WˆºœaWß©­9e+êgkMj¦f›}ÎÀ²¼kòCÏž§T-qko‹„òã걬}HOÔà{Ôwןr-6Ù’Ú,¤R¿-!îÀÖ©ÛAÎdÝö‹â}Ø>­ÔŸ¥&k µ5c–K†L…Tp¨íÂ÷Gnµ¡röñ"-Ã;¸ŽÑz“Û tü±ùsšá‹ê$”ç8Ú¨?Ù¾½jΛ¥Í2ù“y²Æßx¢¯ž¹=Híɬºšz–"ÕþÓŠÆÈi??(ü9ö«zb˜|š/öËçašàíŒ:¯Cõ©ÒãK³&& ã?»¶Ì#Ô¼ жÕä…Ô®™,±öy¥X²Ýþ”üŠJ'yÜ_AkÕȆT]Ü~y©&½òŒR*£g!$¸”È1ÆB nEàRÝO%Π³Éq²&÷)&QŽ1ÁnÇÿ®®èÚ[j\Ã"ZÃ+[™Ût’mÆFI8'ž>QÔÂPnWd2ÉÄH÷ÒÜJ‘c1$8TÏBAPvÉç­ZKyô½(¼W cTûWWåéÀUçs ž+'û[@™Ik;››K)÷ šb?¼)ŽGë¡5|½y/ž„“&ØNA xÛ‘·ÉäsÄÉ94žÃV§Ø®–Ö¶Qé–åÖ×ÊS$‚?–5^Y‰¾Àê)n&•@}:Ä ¦]ÆK¦;aA€8Éý gšzÎö®²@¨™`í"«Í$Œ;‚êØŒúñUïn¬‘nd’f9&‡+·L·$=ø«n‡/3ok”$îD¦¥s¨\‘ml0Š=ñÓ¿$Ž*͵½½…ÔØG î‚Ûy7ž>fÀ!TsÔÔ:kê·×1Ei:u¨pÍœ;‘Ž­Áö#=jCk§éZ…Åô®Ó}–ÐË=ÄϺYÙˆTROÝÒˆFþ9?…êQצ•¥Ù#¨¶ÏÉ K…cœœgïc<¹g 5Pf¸HöŸ´Ÿ ‡ ˆÉ ¡zž¤“Œš§y~n[‹-Ì¿$@•{“=ùíRéQ»[¢…ÞêáE¼/‚wHÄ猠äöÁ®Ø¥r8¹|OR…€ó"”¨Ú¯ˆÐs½»(ÿ=ªÔh^é#–@Ç~éG$üñW^Ám®îˆÇ•eƤtòw棊oIkÙ~Xbo”#‰ôPzúžkºb£tcSšRÔÒîmBH‰ÚXF™ÎëY×DBÃ.L‡žÇŒýkUR8|›eRб†;O$Ž_S’LV0Äš”Ò†'h@^ß®!]XfôLÊ)9I­¬Z*nnàdgŽxã󪉙*ÇæF„äæFÚ¼.y?…O3bEVèNyã§?áU®£W‹Ëcv“^‹mÞÄÓÒÞccMòÆT ¶„{þb«Êdä.å 7u#¹«öÒKkepÑ3DcÏñëT– ©dýÑ׎xÿëÒR“vfðkVH©ˆ½ÿúÕ4xò°G\Òì ™ô>¹©0Ñ Ï_ÓWÖÆnZîaG,¤~5-°bòçÑqÇûÔò ¸õúñ©y9çq#»úé_Qº‹’Ãùå{ñ×Úª^J«hÀ†Ý÷³ƒŒÇó`°;X¤ôäVeë¶ÆRx8^Ÿ‰þBœ…I^Jå!!ûOÌGÌHúqŸê*áb“Fá¾`:ú‘Òª[Æ$•Ü‘ÃñÏ8éS<›š5b3øšäR²mÓIÉ$KnLÀ1ëÇuÞ‹;ƒÂ¼ûŸþµrö° qÆ:ÿ*íôX‚Øï8ýá'5àcªsÈëŠå‰¨9ÔÄ1ù#Ž)˜Á#óœg“ëL æ€@Îxü)¤üÄð½8õɦ7=ÔÐWˆu&Ó4‰g‰—Í$"{zþUçÖö³ê—«$èÑÚ”yûÃ=ϦA5Ùø°A=½­½Ã—{*rï€F{ç¬@â\Ïqû«àyu•‡DvȨkS®‹´4$¶[uPÌ’<*ØDN ÏÙG·©íõ5©þ‘­7îóýX$â >3µ¼þÃŒž•Xƒ[˰¬6Ãg¢zÝÿ/ZzÛ¬ÁÅÎD0 ó‚  Ò$þîOR~céêìî)>âZÀ³Á;Å3Ãbã77òœKp3Ϻ®x©üðû©7ÚÄ"µÛf-lÀÛç7÷˜u#¹?‡©¦ÞLî5Õâ€Öúz|±@1÷ŸÐëÎ=: Û뗂墺‘®î¤ù^(ŽÜŒ}Üÿ{u#“Ž•¼U‘ÎÛl;’IîÎ÷7Žß.ñÄJ{ª(ûÀtÍX·†fHîYûAÉØ^ÿ¥E{v ³$2!OõiŸº¾ƒõ?-ÅÁ*Ì[Æç™î%‡¯5GL/cj;»”.ñ*ÄOʲãê õ«ÒØBQ>ÖÞp ®ÀGø(çó5›Œ—¶^\\È8b±Ê­Áok?b²$>w9_¯$–5Šò)¶úšFâÖ;`­$VÖã´1ª¯O\ŸÎ§¤—rƒ´p[)æK´Éq×!HÉãž0)Ö~Dr-ÅÕ„ï:ð%©è¤ájÌpé÷º¬w²L»‘,‹pv'×>Õ ëa¥er¯´«{©|©^{„_ÚR8¢AÐð Ä{瞦¢{­Nâá>Çw;¬G‰¥‘¢íÐ…Èˬ֯$­-õÄ1+ç–àœ¹?€¨¥Ô ‚Dû0J¨C´m8ëž>õQI‘);Á§ZÈ«öëKyn‡`ÌûúãÓÚµn Sg=Ñk.%iV2@\¨À@€dug=²#žá!#M³wäÿqsÎæå¿ީϦK|ßñ6Ôf’ŽVU,€ŽƒqÏ^OZ«jC––{LÍzË<·N8x­Ž.ñŸÃ5-½Œ–†Ü§œ˜U1Û® ”±ãŽÉ'ŠrYÇ,&<-ÕŠùù²9=ÇLúj{ý*kýÝ%ò‘".|¼”Ø q•Ür9Çç[.ǶwE´ÈæÞ—ÍÌ‘Anæ8Tã–fÆ[ØëY·6vïsý›îe”I<ÌÜ'®ÕÉÀϯҬExñÛ4y„^dv‚;7sƒÛ§­Eb¿e˜ÉYr?pƒØnõÏ} wB—,\Ž•í+-ÿ­Éî­"†÷É´O4Æ(” ncŒ ÁbzVÆ•}Ÿöý§–öU¿‘ ©Ëpã Ùô#éXRÍ/Ø/¯â•Z dhÖ^wK#cqÇN¼Ó¡íbÑKÓì,ž6ò,×í׸CûÇ<¢Мà{×ióÙt;°”}šr{–¤&ÖÊ+[ÙÙ?ôÝBN2ÎÇä^?¢²í.nbž_.9g#ˈv.~UÇ·Sßš5Ëéon’ë½–O´_•™ÝŒc²Œ~Yê*ÄIm,ì±ÄžXWÇÍÀbqø¯çšîÂê®rc/¦ì‚åžÞkß8¬‡pA ^Bú`“ŸR3Tå†(šÓÊù™íÃ2ãÀÏãÏëëV$¶ÚF“æLC¢{KsÈÈãÔÔW˜o# ¼¢ 6ç òàø×u*–i£$’¼QVè2]¤g'd|ç× J†X÷G'rXb´ÞØy—N㪆ëÎ2sÛ©ªûb@Î[o¿lW£Fių)=¬S(|½€Œdr{þ´*7˜n¿þº°cgbí&Á€Hô϶*ÃÚìTm§Nqø~¸ªUb‡²(ºlÍÔí'ô« …X.xi²ÆX8QƒÆ:œU¹"ݦ´ížfoši#ÖuNýËsDö°=K ï–1ÁÇñÍ&࿇Jº—±H¹!8AÀ?WlŸÊ¡âl“iåŒÌ8ô(üê×Û­¥ %õÌ€}Ë|qøY±¥qð\ÛŒ«-·$ü¨óJçñù@©Rñ™‡hsüBór?¯­,WÍd¤½ ·SüW7e·ÀG_¥,ån^11–F Šü#sü(;wË{TÅ܇[[^jwl-ñÌÛHQLõü¥O 6³3.œ»„*]®æVbzP”têq½0)ádÕŇƒJ€òƒrAéì™ôëO€=Û˪Gkbê Œ(ÃHªNâLrGùÅ»-4ñÎe02¤1 ò\Jx\ wÇ<ñÞ«Csn|¢¶LÍ0?f· wol n$ç“€1R5ݽâƩý›)À )ÿdqß·¨e¾¡u¾ãY ª¶Ï+1Ú›G ÷ü«[+òné"À»Ôß[Ô¦‘¦›b D œ)Ç-€A&²&º¼ÔbXŽèl²Á"fǘçûǹ$Ž‹ëšµöy¢Q¥)x q%ÁC–¸¸nˆ§¹3Û“×­ki5¬Öó8‰u)ÁÑ„ –Q¨'y_QÑW×®hMEÜS¼´FUõD ~]Ò ¦W^¿/` czÖ|ŧӦž"ÊÏ(ﺀ:rß^¥hßÝ&¹©>ŸjláˆE,êA;˜ð2Ì~~ƦŠÉÖáaŠ#¹†àBñŒsè}½wºëÙ¤Ï:8vês.Œ§pV (´È­ƒ@ŠÎs–eˆ8öè~•n)ZÊÐËzçÎ,·ÀŽc‚1Ž[¦I(ëK…·³‘v¤‚'œÍ,¹'`ðÌǾ1ôR]oTû5¦ø´ûW)æ–vÎY³Üžy÷õ®+³ÑŠå½Ù¼BÒÐÛu3å²rÝYÉÏsԟƬM"†Ä³§å¤l|€ýæ>™zõô­„±hmÞí yyHÕW±×ŸRïðªPZ‹r··Cí×`+ ±óócœàÄí­£Y%c™Ò”È|˜â¿…Ù‘FÄ©ngêHÏ''Ò³#†k›Û«Æy#Aþï :ö<ƵÐ-Í“Ý&KÆ)l$1ê»›¦Hë÷LšT-¦• #;1’æAÁP†ôÎNö&´X¥rVÙ™×ö¦×D¸iäUyai@|‚I\ž¾˜ØœUKK@‘Û™Ûõë=¸ò­Ë»Hîd¿¹Êhí­ÚÞÙX_nwzœg§½CjtÛxm›•ŒüŠ{ žyöãùf´§Œj-"g‡µŠ@Ы͵Áe@¸È'8ÉúU‹«n¿€¤oáÎ?B9Z2xäô¦sœtçÖ•*1ÓÞ˜ÏqŠpàÒg$gùQÏjCC²síKÐRƒÓ4¸ãA@:ú Qì3Hèiqô ÆzzÓ†9Îpiªxõç8 Žr>i)<Ž”Þð:Óˆ?€õ¦ž†€6žj>ù§õ­0ûPÅ4ýÑÍ8—§éMÎãŸZi$ŽDH G8ãþtÀ}84ÄPÖu§Ù<ɤ>\(:–>ÞÕɶq¬Iûï1Î#ëçËݘÿp~¾ÜšÕ׎ëÿ6RÈ"R¨ÌøâïÔzñÅg]:ÚŽ]—lXèéïéøPo $2I’'iÜâæàpÒ±ÿ–j}>«6òù®QJªåIÄ™01ìSøw9YÛb$rq…!#SÑ{’}ÿúÕ•{©-«˜àÚÓž uìK6„nô$'ìo½±5ûýÝÃî{Ÿ@AÿêªfV˜——ýdÄãw¨íTÚg@ùbîÿ}½O½_²²Ym ÅÓ¨‹$ncŒãõ=z -ÐÙ®UvCk¼0X­š^~ò1ŸsÛô­?&áT4‰iíæ»ù’j-ÇÉ ’%­¿@ïþ±Ç²Žƒòüiª‘HƒË³š|u’C´~¼WDc'wvhÅväŸ[†(óÒ÷ÐV”vö¹Yá¦5Ú¹õ?ÈV=³ÅÛåÙà ˆù!’kJî]I…¥¸$m\;GæžjfžÂV¹¶`‰.¶ €ÀE§~Oê*å“y±˜ÒèÈ«ÿ,lþâŒô%@ãñ¬-BâM&Ú+Ý@Cu<²yq[((–?/©šÔºg¶½‹Ë2Kq$`-–B¢ÿyßhû£ïï\úu4w4-ZÒÚð(X`,JŽï#g Ýy#¥T×DæüiºkÄnn$-¸¹`8 Zp»{3öÂÆååÂîUÌàQŽsüû“ZY[éÓÜ2Ék¨’]íÊ÷ÇÓŒã©Áü¦¤–u$e·ï!vÐu"ã,|¨¸îH$ã úŠ©le¿ŒI?îàf *EÜcä‰úHªin÷&êÞw)g¹—¡šBldÃÚ´¤¸‹N–+»½Ë.ÌCnaNʽŽ03þ<¥ žÄÒØù–Ëi4©;nU@DE9ÚOE»þÔzo•sy> ‹Zu° 8T8 ¸°§`>¦¨][Ýj-‹†Tƒ ›xØíŽ7œ|ÄÝÉì*ëGö‹H¬-7Å¿ïõõÇ»ÈU#)¥ ±^9xõ)­¼ëë·eÓìÆ6ƽ ŸÃ©÷÷§ÛÃ/ömä¦ážiŽÙïsÆ?‰bÉáAé×Ò’K·]ËÜ(¤±0!É]Ç©'ŒÀžÜÙ»½ 2¤q–·€ì†%ÂùÒ}{(îjœˆjëR(-£°³ŠÎ6KqÂCè;»ú €ëI}vÑ;Ù@Ùº¸Pe‘ý\cÌŽþµŸ-9wYï&¹¹oõq(êwÇ@SøÔð˜áÂ1óïö‘°Y#’ÿëÒ¿p°ñp,KilÎW|VFBªàmf-Ž£'$w⦑u5ÒLÐmቑq5ÁànÈ\/¶IôÅ;Í·U’c¸P!·µ€~ñ¸ (Àààä‘ôíO”ê6Öövöâ`Tã€Iç’2Ý8⇨$Ó¹$·ó\[AlÒ ni *6’qÐ/ÌîGãYð¯ö¤2ÜJµñÖqž‘¦N c$ã¹äñW­ô‹m:ÖæÀHï䲨ýá$—o^CS‡´·º†b¬±ØÚ»üÄ nà{@cøÔܦ®—{,É™²%µ¢K·_oN¿áNiO…àŒn»*´€žiGÿ«­EdSȱV/ö›Ù|âÁN1=1À ¢• ›«½A÷ˆ`Üã |ÄŒ9þï¿jh†G«²hòXÚÛÉT\ ª/Þcϯ€õ©âŽ8!XâQxUE\S—*±7šz€EÏSM¿©Î;¦=)\ö4™Áõ4“Á„.lƒøv£pɤ9éKü<ãé@”áÛœcµ¶qøPƒÇOÆŸŽ™éMì4îüPP óÓð§}ñMÇ~ôîÿ‡zëJsIÓ¿£Ûñ ‚qG “Òtî}©AÉÆ0)(ûýºRr{Òç ךCÍ0œã¦)§íN$àãÞÔÃ÷{~4ÖÈé×Þ˜ÙÇ~4æœôÏJkuã·jFN1åU¯.¢±µyåeUQŸ©ôõ3¸E,Y@9=pö¸·sy¥ÊZÄH„ž¬GV×ÓÐS.sc¯5»»ûD©óŒ˜à õoóÚ±nµrÂÝÕæQ‡˜ýÕ裿ÿ[ò¡6ª÷ÿåœ'ï*œ³sTÚç9 „B~ïP&ÎèR¶äòÜ2n\œœ»±É?_ðª{¶·L;zöô1cò{àõ'ÔÒ.HûŹfæ•Í’$u+Ô_sZ6ÖóH‰q!Ä@áAŸAžßJ« ,‹Fe‘Ž¿Ô×oc£O*ÆÏ÷Q´/ð ôÇAJúèDÚKSÞÂk†Yc˜žg›ŸûåiòÛÙÆ­ö»¦™áõüJ¿<3Iq-»G,òÆpð!òÐsß×úTYý˜œÇtÛ >™Æ[ô­S0¹RÁ€YÙÊÃmLdûšÕ³ºÔ£e1Á–fÝ·ôëT–M§w g?—­Æé‡¯LTZl>l²=ų$„<›¤ãbƒÈ'¦O8­7ò&‚V•Ì@eexßjÀ˜áCcƒìy®y;;‘.žÑÜßÍ©®N°S²œÅGÌÝ9ç<úÔV\ÇywªÞtN¤’só7_aU Ô¶Ár°Ld­Ðü‘±XôÏ¿j¶"hÒ]ÓŸµºy—×Gƒ žH_RzSbJëA›bÙmNËxWl×1ŽÏ «„õ©˜ž-È~×*ùvÖÐàýž?â9ÇÞ>½ªV ©i3Ã…O–ÆÄq¹Vo äžÔƇÉI¢k€.o¿ºÿžQöE÷=1éA6 û,sÉÔyJDQ,hvöþöÕÎ=I'½H÷¥´·~^f‚¢§`"ý8$œö¥¼ºE œMó/—o_š$?yØŽ„_Jveë3H¹[9|µÞ¸ÜýðH탑ÇPy¥p±jÓNGO´]Ü.- ß3˜™ñ€†÷åxç†hͪj?n¸aK•#l ‘©~gúV\²M¯uw)ƒH7 4‡¿…T¸Â§>§Ÿ1ý*¤W°vJ}©¼ßBðä'®€´$óO’®ûqå†q‘гä$ÌÁp\ '§AÇó5áO‘Ø­ôì8_ëW5i#‚T–w'|Ä ‚~EÇÞu¬ÛÔÞ+F@ƒ–'«çéÏ4ò>ffé‚:ãŸþµ9ÆÔ(>ƒŽõRQ˜D€þÈÇOÏ?!^Á%–@F<¼ #§AŸÒše—Í>R—g”Ïîþ?tÔ„…Iq'©ÿ Ž1JÄçl(îh •H\ â8PÏrÖ«],«c|¤ù·à`œ~|}3S¾ë™£Þ2¬ægçøWîþ¸?U¼ÿIŸÊÆ"‹Sýâ9UýrZ¥«°Þš‘[G#oº™6K/D?À‡õ>õg;†}ûÒ¹‰$sÖ†íÓÞ·JÇ;ww) ÁcéIïÎ(=z \óß>Ô=©p3ÇãH:ä~´ “õýhlîö¤_Ãó£ßŠ@8õô  jB:t¥šP2¥ôíMÎ3œâŒÐ‡£<úÓG\cŠ_j8›“¯zp=†8 c‚qëC~¢”g½'Qþ4;šÝG¤ã¿™Ï·µ0GÒ£`lzyéëÜäÓïŠ1ˆÊ£vàó×õ¥$ä翽sþ%×¢Ò,[ Ã)ؾžÿ§l8ÅÉÙ¾/×âµµ{D|³ Iµ¹öQõïíõ¯<ófÔgß9ÀQÀÇ¥(Iõk‡º›,ÿäšÒŽ݃p0°¬å#Ô¥IAXÊ’Ñ€óIÆ WXe•°‘³Çt1D]ˆæç£$“ùÕ«k%;exR žV1ŒþyÍJZ9¨m^BT ¸bz(õÍjiz%æ¥0‚Ò&’ØëŸóí]N› >¦¨J¼ ÓåÚÓ~žõÙ[XÁk†$òÔåIëê}MR¼Žjµã ¬ÄÑü'i¦¡óçn –ú‘Ð{ ݪ.ÕP`¼ ˜—š1Æ+T’Øàœå7yŸ‰l$ymî-®É™S;†29Ž•šDèᄱ3™fs+“ëŒc?w¼·6þRË$iœ¿”pÄCƒþ:梫®B ¬±LHú’û:w“z›ÂO–Å_²Åcí7¸wºÂ‡ëŽjx8ÁK(÷ê¶qà~.xíI#hašóNŒŒ±C¸þ•zÞóí?º¶[ÉÔŽ«‰Oâpj[5Jâijºù7²RÑci<¨ü¯õaÇÊŸî'sÏSKn’#ËGoŽLVɽÇÔöü…JöÐZ8˜Ä¨O{©r߇?ÔV\Å ‰®nØŸ.Fˆñº|,‚ã-øÔ«§ c%ì¢B¤í ;{/j,‘‡„³³tÆUHãéšH gn\¶:ô9>¤ôÿ hÎw$i÷¿î#ýç/ÎßsØ}?É]‰ùÙƒLGï'rN`=Ol W(€¤[¯,OD÷>§ÐR£X–¹ÇÊÏÖ<ÿ?¼{zuª¹‰š7Óì’þáyäµ€‘ñ§÷ºg銰æÜf9›ýЉî›þz¹U÷æ²e–êÊkV{s{%ÅÒDO—‘iñÎ@cýî1н6òH@ø–Ù±•ä'‰ä?ú?ž*o©|ºšî{ià‘”>©~ ›Ÿ8µˆvǧó5Î-‚YÛ#M±²ÁÉ¥nAb{–>œôךD¹{˜¡/ªj2£|˜ÇBž:ÏjHÌ6x˜rÄâ;˜õ>äžIü‡j.‘A-†ÄI|Í^ì%Û¸¨þ#ƒÛæ3OHK§YÊ#Ó£>\“âFïƒÆF8'€z Öåá{8¥õÐÍÄã&1÷±ŸnëÜ⦶$SdE§Z¨+Ðs·ß=Ï~”®; –â%µ·¿{wò"llñŒç;{ã‘èïO[im~Åe¸µü²ý¦òQÎÌvüGüšœÊâ=Bâ<°%,m¸Ï dŸ~?Uä™ä·fv o®‡Gd_|(¸ì:y#º¼[K6Ê&^IÏ™1Éÿ²¼“ÿ¦ 5óË$V(¹H@ûØÀó:“´ãÛi­âkHÉYóŠqå®2ìÝϽD÷ q>` -mƒ<½öW×ÔU&g%Ø[H§¾Õ2y—rãdl~à=ئ•¤–ÛV¹— ñ–l+œà¹ôÎxöô¢xä7 ¨þkÌDRÈ8^Ÿp~D“éô¥ ̲o’n#m¼Ÿy¾@ãN÷"Ø3#Ã,@®Ð³º·R£ÛüjVÞç»)èÁÛpx€§Gi$“ÅlTB)ãær9'èM8°Ôu'¶Œ±ºïn!@çÖ¥±¨¶<¡¾µ´Œ<%Ê‘ü$Ó‘L†)•ZCoŽÜHwPœzsÈT×r¾š8NVÚ @ノ?!MóþÜoôÒ²æ:l±Ü rsÀü)TÀ8ÇLc4Œ¡UŽå‘\û“×ô¢B–Lt¹ãú‘EÈp'Ý$઀p?AùRF¬JÂWæf'ÔóÐ~Ž„:Dpqó7áLiD(^FÚ€$luÏ{Ó¸Ôl†]ÎÑ(1dìAì? äÔH‚(¶+tïÜŸZdbIÏ"•‘†:ì_O¯¯ÿZ¤$8í[ÁYNWv¸Ç8#­!8ð¤Èð äão•faÒ—v=x¦ž9öê(=8úPº·9 uÅ7ÛúÑž1ÒàŽhè{…'_ñ¤ÁàPø#Œf§éL¼SûúŠU ñÚÀ™‘Í.sƒ‘@<æ—‘žM38ëÞ—¨ö c€ç<ÒçÖòíJ~½h€ð)x#·OJN@È¥ÈéŸÒ™cŒàRþ´ÊžÀzCÓœâ˜ÙÛŽÞ´¬O¯™­ë6º%ƒÏpë¸ "Ëj&Ý‘[_ÖíôK’FS3 Gy'×é^höWºâOª^ypZ1°ò}}‡,‹s¯ÞOS-³pòâ¨ì=…tvWºÍ´‹gˆàÛ°Í&Bœ àsü«)I·dz鯔nÎeDpª Ë…)=¿­Ko²äˆÒá@8ç·Zë­ü/%«üº]³¾1æù¡—9ãï ñì*tðÚ®–mNè•%½·Ê tÆî§òr6SÄSG%m ·W"Þ%yäçAÔvtv®ËKð²Ä]ClŒËó}¼kzÎÆÒÆ%ŠÒÞ8Sý…Æ~¾µ`Žzæ©A#š¦"RÑh†ìP¼p(Æ0=(ÀÛÓð£ŽÀ}jÎa9±ž”yÍ/ ž‡ë@À$8 ,FÊJœ=+˜Ô´‰.¤¶Ú}ÕÂÎ’!}Ãs×<×WƒžM vO¸ûOÓ52W4§7qAªÆÞ_Ù–ékgÏýôÇ«N BLï†yÿžÎß*Ê£ñº§Wgûl±Är2±ƒ’?Þ\Š®³Û° ö·t-&ü~KX¶uëØ¤¶·1ü³ÎÆ UüÉþ•"[À­¾¿_1A‘‡w·­¼Vë‡[võ Ú?ZsLÑG–ò¢AÞFéøR ƒìÛG™3ŒðZWåý¸ÇåHòr«h£=ß½•{}M8£?ïË€?ç¼ p?ÙŸåNUعzàÌßë韺=éÜ––ìiA„‹ož½üËîÍýæö¨Iû;Àg•¹É?1=É©ùÛ®×üsïJ¯ª/™1ûýÇâjÑ“dÑAˆKw& ü»TüÌ= ÷¥¸ò Ž5”) ó4qýÈÔtQë“ǹ>•M!+pó¾YWk鎿€ýiëåÂã9eÝ’Ì9võÇ·§jM6Í£ù’®bŠ{›­¢Y—÷¼gƒÑ~°:ÔIuöR.î`*Íòà aŸ ÇzIœÂY3µFäLà±õoóÿ×]ÿ1šf]ëÁlpžÃßÚ‚¬•契§µÛypqå'Àûª8QÔýy=…¸­›MƒmÛÉw4àÈöý3!h•BŽÿZ‚!Nñ+]·¨Ã;?Úcô$ûUÏ1mby¦“Κ\eñ̇¶=½½~‚¢ö7Jû¯ž.˜‰¯åà0X‡r ýO?Iä‡*-lÎØâ9žwîÝqžç¹?@1žn¦XÝjîÍw+@˜;GT¯õô¨­QãµxnT%Œ;K«¶ZVÏ%É÷íÜûpDì¤w7BX’]âF³†‘z’¿^¥»}*sh! ÔIÌܱ1qè«Ðw$zrŸ-ÖåS÷óqñóg®Ðeïî2y£ m°Är„ŸõŽOßolôõþn÷ÜŽ[cI@³$(ù+÷€œŸ\ŸRqÚžŠ±<“ÝI1$ê:CåSëÆ YF(“3¼†ì£îî=†yÿëÓ’Ö6RBÒB{–ÿ—‡ë“íœSl\¢ZO41+°"âä[þ™F$û·ÿÕIinÖïÙ¤dÝïhýç#’O¥Oˆã‹k¼ ´Òw õÍ>Ü—˜ùXC¸Œco?J‹–£aa ’*&FTIçŸÿU –MýÙðåŸçG&bøHøçÿ^™©Ž$=«“Ÿsþ“e¤>Lù‘ŒΒV8XÜ  ˜/ï]¸ãÌœúsUînV4,™+·Ê€ðy#òÀ&— 5ÜYçû0 #âK™•@^J¯B~˜þ5D n$Y&Hð"ÓïSü©V=Ò4Ò±yØ`¹ì= ©‰sôÅtBÕœµ*_D?8 ãñõ¦whàÿ:@rr:}kCå¹àsÖƒÁÎy¦;žhÜ (쟮=i ï“I‘Ð Ò1À ‚'­ä曞ž” ‘œÐÔ“Šqè8üi£'¯ozws@ŠwCÇéLíøÒƒÆìb€Î>”¾ÜÓG Ç4¼{P48tÀ šh'ÞׯZvrJQÓ­4Ó‡·jCdƒ®)°§|SÇ|çÚ¨¦’~‚œÀã‚j¦£¨Zé–­=Ì€ÑGÞcè“nÈ‹SÔm´»7¹º$kÓ¹cØS^a¨Ü^ë:šêw¶ÄÀ£0ÛÎÎyÁüýêmCU“]ÔêéT[Dp»¹Ž!íýæ÷­­ FŸXQq}榜ò£<4ÃßÑx¬õ›²;¡Ñ4·£hSës‹ÛÖa`§iþƒ×òõ®á!Ž$ *F Eð©cUŽ5H‘UFÞ”`n'µZŠG%JŽ£»##m ™§ž£¸£óúU#¨õñN$öñ Ÿ`3ÖÆãÛÚ”ž¿•Èö¤Ç'æ hCŸ|RçúþŒôÞŸ¡üh#ŸAíF;qHy9ÏAAäñš@V˜\%ÂLž‹‘µU|ÅìqŸ×ó 2Jåmõ;Æ+ÑùK½¹?•X9ö¨$·Û|ˆ\¯BæCÔ~³”:£¢m¤Šò\[)Ù&¥zÇ‘µãòÏç´b•bpLÑAogžàHÀ{ŸçO·–âY Gy4%G±#þÐ*@ÿ¾³HÂuusW¾“ DþC"²: (3y¯Úiûº½?!DЉûÉXàÿœ–ú/_óÒ¡óä2¶^Ú&ldÆZy ú‘Çäi&Q½‘Ôž ²\ÿAB"Jãšà±!QÆpAsõ=éIdâ5õ8ä/¿=M"(Eã8ö?8¨ ²uÏøÕ«³+X<ß”ˆqŽ­+r>£ûÇô¦ª¡L¿ú³ÀÏ%Ïõü8ü)îqæ ïü1gîÔòʼŸ™›¿sìaÿ롱z ÆÐYŽÐ9,Ï_åÚ–0¤$ò§Èî Î7œu>Ã×ü˜Ê…uó”JçîCœ(÷oAõÿëU›kV—77˜Àþî7zqØz§½C•á¬u²<¾d³1_’À¬ö^áSWÖßÏ q4Š:íÉá@iÅTV”m@x¹>þ§ùt¨î k…äØ"c ÑO·r;ð:u‹šØ„n¸+rÊÛ´-×$ýãîzAú,þ[¸ÎY-Û,þZÌzôõíÅ(š@ï'ü´|ˆ‰ä¨Æÿ<Ÿzj£mFD Û÷$õsïÖ‹ŠÄòdÊù™Æ$+Ò5þêÔ"¨BS ªˆtÏ©õþ•epÃ#qÏ`=©ÁZ"$Æùåá²_`?ϳ¸¹I"¶Ë2³dd5ßâ=‡áš™ÏšÃæ $²£Œþ5*ÇÝÄÄœ±Ï.ÝOù÷¨®IÛ±ŽnùHÃÙϽ…TûA]à 6 ÝŒtŽsVâ`Æêç8ËmÖª®õY¸’WPxéÞšÇm½¼À2!Ï(<ÿJC$ŠP²Ú£}é7Éø3MFFHUqÜnÀÇà S¹»%’tl€<˜€í¸‚¥S‘®f ¿™j©ûÖûÄŽNlç4Ô\¶’ŠÔÖº¼òbò`§ÞXŽÉ“ßðŠ£{³HÏ!<³u#œè9éNHÒ4ڹ뜞I÷$÷§Ïo·„NZ•\½zÐãM8À÷ °ÀïVb;¿`ŸNüÓO­/aßÚ˜ ɤ'ž) Ú<;f 9¥'§Þ3Øš8„8ýhÏqúšnp{Q“@É8£9ïIœóÈÀ¡N29& }iÀ÷ÕíÍ.N==èCÇC@úS3ŽqKÐç­ïÆiã×­0u9ëŠ8ÿ<ÓN‡¯à/Ö™ž<Ó—¥!ãƒÓ§sÉÏZAÀ†œ@õíVfDò*#3:¢’O@=My†µ¨j2O#˜´»RUäoõüÿÞ´€qœóPHÜgŒ)ù‡ZpäöãÞšyý3@Ù<ÒsÐSˆŠFg¥4ƒÏjCŸ©÷§}qIÒ‘Bdž8¤#·8¥9#§åMèúô€:t'ÚŒñž)3‘ÿ×£=_¥ð¹çŠ2@ÉÿJ*ƒ$uÉþfžÎdT±ÔÿŸZHíÀ8»/aHcRŒrq’{ú~ÕÈ$n2GaëÿÖõü…!ßu’IÚ?‹‡¯Ö€AÙ‘·‚GZÃÈ^þb8TϧøÐŠÙg‘†X|Ííè=¿"Æq¸íu?uõ÷4K$jòÇø±Ž¾ô‚ÂÉ Ž1#°}ÕõÿëÔ{¦Hä#vï:rHÀÇÝZ¦ºµ³)¸Yâà?––ÑÅprOä*³‹Û¢ÿj˜Çlùp·$`}æëùb´Œ\¶"rPÜйÔ-áI_G~?Ÿ•u}=äÎ ŠFvc ˆ22I÷ôñVÖÞÎô‰Cldþf¥ãkUK¹ÎëöC`·H‰cÌËËè)ù­#7 šùGOÀÖ¶¶ˆÁ¶õaœûsK»ŸoZi8 :f“Žýhî‹×¯j ÎFqއµ4ôàu çÍ8ãwµ(8'Ži€à‘ÏóÍüÙ'ó4~N84ÇÝÜw4„ãéŠ~Gaǽbd€?J ç§lP ê½¾”âÁàÓ•rFxÍ;#œÔpi½x£ƒŽ<qŽiA89ýz`<ð1KH nãúÒã¦áMÝŒir:cŸ­)ÊpµÎiWé@ÉO'‘N_Σ#ž™â•xî9  ÆqKŽÙ¤#qNéÛ9æ¬×µÁ掀gëG~¼Rê:sš@xíÍ)c¦}i¤žÔÄ7…oéMÝŽ@ëÞžGÍŸåM9ô BFEíž)2 ãùRd€x cˆãž´Ó‚¾ŸJo'$ŠCÓ°õ¤•š¦ÍgZ)ˆ—ë)RcžáOI$\cßôàÔÎU2òJ¢V?”gÓ'¯éK·œ?C Lá¼¥.½®MW²FXò¨Û4‰²æ"² $påÝÏ»~\Üæ­4ÊÛ#M¡€ÉÏEëÔõª2B¯œ3®p Š’Æ2;R=¬2Â#h•”r3Ÿ¯¯jŸdËUàj%Ä"-Ñ”°ÈaücÛÛÞ¡k»4qK4m4Ÿuê=Ú³ÞÑ\»<ófŒà`tPàQoe¬ÒM™À&K1ŽqÒ—²cxˆ-x#«ºÇå®â„òø:®utF’+hŒ’ 9U=¿¡'×Þ«%œ1ÎfC!v$¸,HrxÉÏ\cJš(b†2!EŒ¸€1“Öš¥Ü—ˆ]M¨L2Ò¬12xëæ+ð·Ërò  QY†6véÛ‚?/zß8-“ëן¨@²Z—u-°ïÀï§µiÉ DÔõØg‡l#Ó´Ø¢@¾d‘$®À|ňùú+dú‘žÕ™¥ÊÓYÛLÇ™·“?Þ^ŸšíüEižœž´SwBÄ+Tõq‚}M!=1ÏjRH/ÒšN:óV`*Žç·½ž‚›“’HëØSXÜ` u —Ç<}3I¿#š…åXþóªö¸ª“k0ºá>‹Î?*Afö4ð;{ {þ°äñ5šãdSË×î'ÿ^¡oEÚÎäû`E{9¾‡E¼qƒŠPvŽq\ïü$ëŸøðºÁû£üjXüAæøò¸ûF?ô*99.†Ø<váØÖJê²³öI0{îÓ®F¼4Dظ¢äÙ›ÆRއ¥bÂG2"SÿmE95ø–GƒüS- 3oZ3ô¬¨õ˜Ÿ ©î¤Ÿ×§ Uwcn>§?Ê‹…™¦0AÆ($c®=k,ê Ã"@ SU¤š)s¼HÛ¸ùA¢áfk=õ¼xÝ*çØæ mjÕr'ô¬¿.0Ã{¿l³‘V¢i£ʳµOr7ÒŽaò–YóXˆ­¥qì ©’öàŒ›Y©+U~×x¿ë%…yû»øÔæý[„½pp)s¢[K¶Áß ‹ï´Ô«pŒ85D\K“jŽØÉ $’–?•.aòbNäвQYâÔcFZzÚ'üö“_þµ>`äó6{sÍ;<âš>îzS‡QŒV†"H4œžÔ£©¤êz~´˜Å0ŽÔâ=)¤÷çò Bqϵ =GÍ4œò1øÓè FéÅ)êyíIß $ã8úŠ9'¿”ç>ôÒr}óHhç¥!$ ÐGÍÒ†ÔŠœÒÎN8ô¥Í4’>¾ô„óÁëMÏ@zûÒ±úÒZµ!ã4¤¹æ“ vÍàãJiéÏ4 sÅ!Î1ùó@Æ{úÖV«ò=‹õà/ýô ÿ2+S¦3Þ²µþ4©¥ wBV\úm`ßÒ¥—‰ ðªìÒš/ùå;§?\ÿZÚå[÷&²4|CªêvÀ oIqƒü…l6~½sDvUï°9?4 ƒœŒt¥ M,$ñŸÊ™˜áÆ}èôê)¥øùyú B@çéHÎìç§JN¼u£ãiK/§¶h@!†qÅ(9'ôÌås‘Ò‘ÊyÁôî8ö5s’>†’I¢‹d}I5VMNÔ6Ì„ôòÔ°üúP>Vö*è²”Ô¯l‹J¢HÜžw§\}A'ð­Ä“+»‚{€z†¹x.ó«,©ˆ»f?3Ü£ž3ÐŒþB®jWåZÝÙmå‚ .qœf±RQ“;jÁÎ õ6ä™#ÕG¿BmfÕ2<ÐÇÑZÂ’‘·HNîä‚OæhHÀùrOæ?ATêªÍׯ"·lz·ÿ^¨Íw6C̨¤ôCŸåIörÜí'óV#µbé^Lv„LæŒJ rÍß,? 4áÓò‡ý” ÿ×­4´ ž™Ï¥”:˜@ ö§ðOüi‡=(„äûvæšr)Çïg\Rqß­1 #ô¤Î4¹ã®=©89ãô ôã ¦“Jsñ¤qךCpÝi¬z`Òó‘“øÓ[9Æ?ZEx#âšO΂G±¦õÀŠCûg¥78ÀâƒøjBRâ î:qHG9ϸ¤,IùÐsךSÓ¤ÎÜ㟭4°Úx8Í/8ɪ÷°ý¢Îh3áLcÔTÌÜç9¦·N´ :TÇûVÊv'ý*ÅPûºàŸë]±ôé\šOªXÊì¨-u)mÉ=‘òsù0ü«aµhŽáRÈzp˜®*bͪŶš4Cgqõ<Ò¨ÇR|VWöÁ-º¨éó??Ê¢kÛÓò™Q?ÝLŸÖŽdgÈÍ‘êZšÎ‘©geU’k šáˆ4§ŒuÇò¦‹efÎÜú±Òç§ÝšrjÖ±“²O1»¿^•]õiú»WôØåš„B sœ{Pj^)s2­9﯒<´ø$ÔLÓ¸ $Ò·°ùGéS|¸á1ÓSF3÷õúѨî‘_la¾X—Žì2i$2‘‚ØþVö’òÍ(= ¢ÌDŒk¥šMÀf/ (ç·>0 lܺM•V1nâq‘¸Ó1øÓ‚«YïÒšvŸº¬Øïž)ÿ0î¨üÍ2[Ý÷qŽûiÙ sž)€c$‚~¢œ3’vàöªàCP gŒÒÙ=ÿJLsŠ<¨Ú>QžÙ 3€3\Sp:uõæ—n8gÞ‹Ã’ÌÍ)uDåÇçJëøSÆ(°s‡ÈÁü)øcÏ"Ÿ )b)X¥!7©£b¿­8±#¡Éô¦…fϯ֋ H (1J%ÀëéŠQƒÊäÒà(à{R±\ÀÏ zSùèIj€9É—>˜ãÒ‹0ð‡·ð Z˜1ê•=TŒçéNÂæ: ¸üM<Ž¿&ÞG"”:Ÿ¥;ËË÷xÇZ\œg‘H:}iG§õ¦0GAš0{ŸÆŒ®:Ð×4ÌN´ïQúÒg° CxäSXr8ýiÜž;:iôè:Ð!^)3ÉÈ?ãFpyÎ}Å5ŽFz}($E4ƒÆ?Z ÀãšacœŽG¥s“œý)œ´Âø''JŽIV1–pë“Hh”·|vìi¥»çò¬Ùµ»X¯ž®ÝKÒª¾ºÌ3œ„g†vÚ?­+—ÊÍÝKtõ¤ÝÎ{u®|꺋³ A°@^î~%¼—œ+þT®>T·gI$ËÜì´qTåÖ,c;Zê3îüÇô¬CenIfùÛ=IÜjxስ[ê£P÷QlëЈ žCêΣ:Åä¹Z$~îùý1Pnû¸?AK”R2£>â‹1sG¢ö‹ù9’ì'm¨€~¦¡’ÔÈù–{‰ìd?Èb­‡P½É>ÔÑ ÷G±¢Âö¡ƒ} qYê¶Ñ& CÊžÀ£íl~ á[vìn-bÁ_1öúdf¨_"¾£`¬§lÍ%¹Ïs"mPàX4ž!ôµ w4nPätïýjm©Ñ7zJF ’sŽÄR„cCm ‘´( EÏ]ÙöÅ;¼ÒAänè;Ò˜ð>ñ8ïÐRa<ô<ŽEeGsêM; ™‹°mÉÁ=³JÇn:{ñI·zà“B(Ú«…Úqw1 §aˆ#ò¤Ü ÇUÚùO‘¹yBñåüjÊõù±ôÆjž£ ÍhþX+2üѰÀùÁÈ?¥&®¬])rÍ1ö!ía–Õ@So)QÆIFù”þ¤~xeÇßcÇ `U4¹†imî ô>Yð®9úýå©c˜Èz‚½Ì(;¢ñQ´ïÜ“÷yÚr~¤“N!ÀùSß‚j2®¬HØ£¶9ýqNÀÀù˜ûQËqø?Äànµ&Ÿ|Š€ùòTdöÅH#ýØÛ§ŠK´E—'ù¥á”rJj¦Ñ™w~tâwcsÜ h’1ž8æŽpãÉëM™±È#Ú—æÜ c>•@;'ÞÆ‹¢¹Y :t¤>¹£8“JMÀç4Æ8òF?:knyéAÉÉÎ;Ši<~”€SÈ=雀ÇcéëJN3ƒŠ«=í´ D³¢ŸLó@rG8Ç­7žrsøVlšõ¨È$—·Ê¦ª¾³y >M Që#J.5 3g©Ïô܇œ\û]ßÌÄ=ÎÌŸ»Š…­ÚC™ä#¡vÝú)s Auf´ÚÅœ9w˜qÀŒþUFmvi8¶³Ç¡•ñÀUd·Û’;`.3JÖ~¬çéÀµ+ÜDÝj6&¼/÷bL~¦« wsI9뙟ҮùŒ€ŠÇÔiÞ\«óÛøŠv%ÔìU@JÇÑŸá\SÂÊTqÇ^˜©ºÍÈ=ëHѶ~fÏ×4XÍÎäX#ï0_ΤܭÒSM´¦2¨2pO8§.ܹñA7/ø-; ׺m¦S‘´óèhÞ1‘ï@…ÜÀã8>˜£q^üæ€ÿ)ÉÀÏ­&àýr3L±==hÜäcîy¥lœ’H€f‘@`7Pùë@Ñ­1ŠÁnþòÖT8î­Å>Íc¶ÖuK4e1ù‚hÉéµ¹þ«Sj+ö:â5¿vÝóŒ ÕXîmƒiW°ÝÀòIj–÷0¬ƒx*½p}ÀÏz‰hÑÛMsÑq5IÎà†îä S9Ù“ÀÀÂâ—s9À/·× fš1îçpXæ™ÇqKl9ý)<ÌóƒßŠcL¦Lî,Ǩ4ŒÎOSþ6ç`£pP ;~1È'úÕ|‚ö'ŸLgð¤2`}ŽhËäÊ=psšHã'æëÇ~UVY]–b§Ørk&õ5I1äXÉû á±õÅF7fÿ˜¬Ç‚>£ÿ­NÉaÀû W,—Ú}¾÷‘zHîõã#þªÜÒµ«yUWÆå°0äMÍžÚéÜèÁpÀüØŒSê\zó“P;ž–ĄɑøR · ‰ìyþtÌ,ÑCb¥•ݳùޱN%Œ!ÆÕn~_psÅM¥êR]#„Μò8ÿ=)‹åÏÚ£2‰@^™ õ'µVŒ/ÛêLˆ$P‚%ùÁÆ1¸Ž*’‘èJ›©A7¹ºr@b’gmþtáÈ¿4ƒœ¨þµE¯.n_$¦:ó“ë€:š®š¡˜ºÅ4IpNÈü¿Ö©ÍkQëc`ˆmò@ âcš>Ðgùc%±Üp*²ê̦é!hÿˆ!äcó«;7Q€;súÒ "<»Ü|¿Ö€7·$àºNsøÓH›ÜTCÆr«þy©mmÅÉÏC€9ô¦™“ ŽƒÖ—h“k°8O™zu§p$?3‚@íšwÈ:òHéQ©f#wCïÍ)`Æ3Øn¦PÀc>ÀRïp£‘ØÔe˜dílzSClÈÆ=Ù© ˜È nÇOR)T3.XÈ:X«“½˜Êp?¯ó¡ñ#Ðg‰â‘H–ï•´gï þµ*Ãoai ï#àçéV#´v Êø?ÝC–üêÚ[ÅܱŒÿy¹?rœbR1o ?º›GæqýjÄvÒ¶|Ùdôbyü:këh‡Í*–”n?éU›R‘‰ò­ßo÷œàP5赋?0fú¶?AŠ™LDq¡®:Ç[Ë«Œ„}ý±o2x©N¾œóÿËy2!‘KÔ¥¨Ûò<åoeÉþT‹— A1ôýÙΣ‹I™£ -ñUèÇšš="Ä’žd“0ûÁ¦$Ã4®ŠIKå/å²IŸáuÆjgHn¢hßXr ±²šÍ‘c ´‘Œr§ëU¬ÞY#pùó"bŽOSŽÿˆ¤Í,®,³Ã-,©ûÄ £.¹h™Ù¾SŸàCýk*(a B@XƒÁ+þ5/’_û¡GaÍivedM.¿)8†ÐãÕÛú ­&¥©LpcÏ÷Z‘bçŸÓŠq‹2M-Cš+¡Ÿ*O0&y݇£9ÇåÒ…²N@éíZ;6®IP=H¤ýßÝ$}9§fKªT ùWðÅ&ÖÇÝÝÏÃV™¶ ¡xÿi±@ ÇŒ{àqEˆçl«¶AРüI"¦áŒ»ƒ°úæ¥77ò §2ºÄÆ'˜ƒîH»W¯ãLÛ“ÐqÎX–þu+ànôÇzäXß$ª{ÈF.´ØÉŠñ¸ŸQÓð¨ÝvƒÀüp?ZV¸ŽPpÒÊOD ~|T,Ì£& ª?ŠGÆ??­4““‚ÿdô¦°-ÀrÄs…Ó÷Lê6²ÿLããó5>Ó¶IœûÀü…ØP»x#òi¹[ç˜sÖ˜vò(=óóþ”™-ÑGÞ Ê‹"Q$!x·ÑM8lÀl6:g=*¿Í÷ZN¿Ý¾KF3ÏñfèXó#TÀ8îÙ4†Rå;AàaÏ5¨S†fû¨¼þt8;HÎÿï9•.)6Oqms ]ÛË8Çœ džj+Œ0€ŒdŸé\¦·-É" hÏîÈfqÔßø ¡µ©D6K1nq¶a‘úÔ)w:Þ5îÈÙu4Þ`Ý4‹HP:’ÇÓÿõ«;C..o'Ó¢·ŽÖß…híi>aî3óÍeØø¡’@×1Æ]C„î’ÊW§=˜ŸéPZNˆÖðCp-¦Rä;TýqÖ”šèoB“г:XË [¦ª¥N1ЃÜf§ØŒá&zá¾oåP3Xˆ\¼ºeªÇ¥gv‘˜g’$3ÐÿõÄ•‘±$%<6Ü«wàô4ã+œ¸š/š+Bb1ÂŒwéK•–8èó¤quê1úR3“÷Çû5g ò ýÕ'½>5Þ¼ö¨²[’?†ž±óÔœw=¨S{¯µ1âÁù‰ãê)À|İzS†ÓЂÙ¤;”oìÔ&=ê¨$gšæ_AºÀ‘bò˜±Ç#?¡é]¨Ïœ`z±Å åvö³úRjû›Ò­(hŽR+›ëFÎU .<ÖRWêO¯fy®ZÞ6Yß-ÈQ ÆqÙúÿõë òÆ©`†Tn4{yîY¡cœl9\ÿºx¨qìΘW…ï$r÷ :»Lé3¨l ¸ÏÕi¶åÔ‚J)ÁʱÏ_aÒ·¯­î¡C,±¤ªY¡b™÷aø×5}§¼S›;çb$ùF1ÓEŸS©T‹W‰¿¥]Ê4ÌfFèÊ“»<œç Œz£%†¥}pga±ÎæÉüzZ¡i¨M~c+Ì›|âHûýk§ieY âòÙs#'×¾OéS³Ô¦î®· ÞÜ–{{¼†UùYØ“ŸO˚葎Öfù¢¯øÖTr8(exݹÚË'=ƒÅhîœÇ˜£PØèìNJÝ=.º¼ï°èÌ“kˆ?º¢Lîúã¥(˜G̲* `Çðªe¿žLËv¨½Ö5þF­G§AnÚîOwoóš=â?v··±!Ú¸9õçðÍ9e™e…€?óÑvв#ûQxÈEÅ(;œ²ùŽÞ£ ¦“%Î=[Ÿ7,ÁŸº#úS£5ZFÁîçõ©¾HÇóâƒc†‘Ü{œ~œUXÏšär˜Ô‡Ç nIú •iñ¶-‹ýçãô Éo -Á“@Ë=&žLíhè Šyü(f+8önfiHôáGà K%Õ­¾æTô@y?€¬Ä‚îöáà3¾ÔæL’ƒ=09èjÑÒì-уݘä #õ楻¢º}XÂÞ"òYùýõ"ª4óßqÉ/_ºFßÓÔÕñ&=A‚ÿ§AúTÿk¸eÄ6ËOOÀ]"¥¾“tÀoháÝ7æxý*ûéö6¨$ºÛüWŸÈéU]o%b$»—û,~cŸÖ¢û$1¾ÿ o=XòÇñ<Óî_­¸ùmÒi±ÇÈ›Gæp*6¼½•°žL {’doèëMHY‡ qÓ“ŠrÛõÜÄsÚ‹ æåý&îæcÜnاð\S­f´Ó²-R(Yº•8'êMKöXþîÍÞäšp·‰?…GÐb•†¤É`¾Crò4™WQò®zŽÿ•:=¢Iå³) F:`cù b²(ùW?îŠ\³’0áÍ$–æœí«¡/Ì É츦ylEuïL[ÛmŸ»Þù'î¡çó¥k³÷DŽ›Ÿ8ü5w9õ!%¾lýýi6.yecžƒ&˜÷r¡W‚=7ýãùÿ…1® ”çÍ3ù䇞¢â³'!vü»z ŸçŠ`u\†‘ô-šiwΣ?óÖ_è3L&v‰bÑQ2@úÿõ¨L›çž „ãø#Ûúšk0L3ÂÇå ~†£dRäI-ÄŒz/#ü!·Š1ÌQ&:$Üß•¸¿mó ÈÀÏ4-úç´­’pÕ¹ãðãó©H—$@D¦¿—2*&G +óþ~†îFB4`™]‡L§ øàb›¶=¿¹¶Ãz±Ë“CÜ#Uå›'îÆ§Ž{T3܈ coB»ÇËõÉÛEÐÕØöœ¯Ë#`ÿuOÇÿÕU݆âcæììÅ›ô¦á€Þ5ÎryÿCKùïÌ'²/øQv°ç,Ëó+¾Oñ>åQàÿÆ>ƒ'ô¥ªÂÚSÎrìó4ã2®T´h?ºãòP3A.]„1œÎI=‚úÔ¢"î€=\ãùQçr½~E*?šx vâ™7bº„”n}6çOz¨f8Ï8ɪ¥çs…# =}²z~¹™ÊŒ2ä(íԶ΀UŒTf䃅Úè<Š…V0¹ÆÔ#“Ò¢ûTØó8ån4¬RzèU¸´¸‘¥1˜Èbà†úqY¯¦]4I1SÇ##ßž¿¯zÛ{æ(R8_Æ\íÇÖ¢ûTÄàÏc8Â)v56GJÄTF4‘ZH|¹í|¹¹çÓŠ§s£’‚[)7)\…büÿƺy3$AdŠIqвlRŽÅ ,ê$µ$d§¥4Ž!œäœÐΫr\:¿“¹~µÒ¦¡®‰˜ï)‘÷yÈÙE_R›x#¯_jÃ{#{,ââOš<'^xªÑÇ.—r<^d'v8>~*\NØÕMÙîu·ë¬w‘Ì—0™0Li·åé§‘ƒøzw«6÷‘Ü xÉ OÝp7 ÏѵS Ž{rÓyc-i¾§©Oq×ϼ×z¾—BòM>#ç€À$åwg#;Tœtéÿë$f֌ζ3Ö:3W÷aérsœR£Â ë–5Îi—Ðß8­™³±`¹Úø¡²?JÝ[O–ÖV–E›Ycò¦LvÛÑ¿ Us£–XI%u©38nT+{ã­ q±6ïÇs·ÿ­Q[O‚§r²œ`AR;™™pYPdryi˜rÛFœ63éšr°Þ2à¿Jh÷œ¡Ÿ1¦,Š„2 ôEKó)4Z r[i êqΛ¾0ü8|ôXÁ?Ò¢äÀÿ’˜Å,Ck4Û·á~ƒÒ‘vº¸Í@I䪸ڭÎ3“õ5ÍͲÞÚG™@o)F>ö ãð#5Ò·a’C·$Hê}…d߬k|ê@!Oß}á¹›û½2<ÑÔÞ…”YžºQ],[1ʸË`C b¡Ðõ·‘´Û²ñ‚ا‰—ÖµÐDÉäîŸäȬZÕ¤tŸjG&0¥2·Zr2*V§fo@Þ|Ë«uÉvm÷=ÿ´ãpøTP@ãå9®Z¹o#‡÷‚6„LÇŒ†¯ÛZêÚGi0wp^X¸É€=D.昚^Ñ]nt°ÇzÛóJTg¨Çó¬ÈPšv¾·Unso'ÿÿ œiÖçgš~sûÆ8ü‡½Ï-Á-Ù<÷–Q±Ü)~˜,Xÿß"’;òÜAm<ž‡ËÚ?ñìT±$. ¶U#ûªN©;teÍ!4Š _JÃ) +Ÿãmçò ´ÁVž[™[ @»þ¬²,_ë¦Ûõn!H.!˜`gÏ>d‡‹Šý‰|‡|ì#oR9?•8Û‰?×Î@ƒm5Ì‘HYÙP ,mŽO©ü)®míË9 qÄî?™ëÒ¦ý Pv»¬‡œZÞY@)·~FÓß<õü=ù« an¹2!ôÝ ÏÙâ©ýºY!LÌçŸÈPa’S¾y  Ž…°?!ÅP8¹nÍ< …VÁyþTÏ´«’ªŒÎ8aŒùâ Šˆb" u#ÆM¬HǼåäqß§ãKQòE-Ël‚$Ýž71? ©„Û@Äh>‹P}—œ4íôBñýjUXÐÀÀîÍšbæÉÿLÇãÍ=K•äœúm¡d„ýŠPÒñòíí5Hz†ÜIl{T‹$ç,}ÍCµúË!Àô­I»åÄl«žíÏéJå¤? 8•*…TãŽù5 {öáÀb;…ÚáNÃîc×¢ô¡cŸràÒHyûˆ?^iÊ‘2(%Ü÷DÄçðzRF€ {{7'±q·òÝÏéO>am’I yDMÄŸ¥-LÈ|¸"?»ŽÖ,¼Ï¹¿*x“ ;?´qÿSM‘ã„wf à ŽÝáJÏ<‰ç{v§OëJé ¦H!U?½*¹èe|“ø F–Î-¢V‘ùÈUü*°‹î•žUXgô¥òÑyjÇ<çþtsX•݇Ëy#1K{hÕq÷Áãñ=ÿ Ѿb¯é`ƒøškùŒÀ+;’zT%K6<Ö9ì 5*w6–¥¸÷I|ÌÊÒþòL!PÈ`‡ˆ1ç …ªO²#g(@þó)R5?.ÑÙOëÒ‹¶gÈ–ò"ŽE2Þ9fQÎ3´SÁ1¯ÊAÆ@#¿½+**†`s€éþ>õV{RÒ‰Ç8q8ã¿Òw±­?dž¬²öƒ’ÒF@=pÎN:nÇ_jn"„äuúöª e3)‘Á<œôþûóWáÓÝc!cU<(=‡§$šÒ71­Ëˆd‰ùŒEû’Ù?ΕL~[¸…œ|„ú³íÖ”=¼x/q±êIÞG9Î1ÇëLšñ"ð_pà¶Ç~ßË"¯CŸ]‡´• eOÝfPA?PqŠŽEÜ?x`c,G¯áQ­ê³FÞc6z¨Ù–ü:ƒøÓ%û;HTÛ¢³þõ·b‹‚‹½€H£rvqµAc“Û{Ôl²HYY¤N¸‚j…ΫÒIÀdÓå©ub¿‘8تH9õƒxòþðOsëÈ©–%Ú bG8çëëM8GÄÒÈéËg·µ I¶DÌ,™œ8)ßÛ4Ö†8Y»Îïáä“úàt©‡3±ÌÉ‚7qÓå¨Y2ͼIœüç9?¥g(Üë¥QDkÄS!EçŒ¶í£Øtü6wSû¹%‘Û1Â0[ëÜ~tæD•HÝ<àÿ CbŸÇ€ZaDT ”1‘±f?\cúÔ8+*®ã”Ûòâ9ÈO¾ÿéúÒyYmÆ9˜ 奓`L" ‡ËÁû‘E–üO4±D‰!Þèýéðîyý? juŸB$q –Qiÿ g'ñÀÍLeYYs0u?A׿zvÆûÁæn>ü©€>ƒ#ù‘á w3‘êÌ"Aùg?¥RV1œù·+ÎÒùªåÈ€Ë4‡%¿R%P5dŒóó•ùT7–•/),ƒû€±ü ȦÛßK¨Û†‚Ëjôù”qõ=(Ðj2¶Ä²Cæ¹Ë¼ñúv¬Qb “÷*JÌIŽLm>ßçús¸mî-­k›¨âÀ'b'ñ5RïE·–šF»;óµ”–ºuÆ=(z«Ñ—#»e .n.ÜIP6iŠMjK[’€£ÔœU8騡)){§=®Øˆ!Hâ.ÀEŒ³d¸ÿ—Âé¶ rA+´àóÔƒü³ùU½j3"D¹óÙ2àHÇ'P*¿…Á‚i!*Cªn!Ž<çùÖLôâÛ†§A(ƒíUùSåÜ}IäÓ”2¬|Â*I? â¤KrÍ™¤gÜs·pÀϧµJÊ–ì’ »€*ÁôýhW9äâÝÈU&,6FH=ÜmãØUˆíåP| ç(Š™ÌŒŽT úŸÖžˆY>rGc†Ç­;ÍäF–Á´’ gåÝÓõëVNJÞüùšÅÊ›Ëw^Ÿ™ Ü,n0/Ù’*’d‘—YÈÁ;p§€ãU5ŒDïè¬ÅËœóVDmz|»ˆ¤K~½òdŽ•R &‘ä2)6*¹ãpõÇ×ÄþiWŒK´xå9Æô¦¸äŽ>¼Ó¹ÇQAVsÉÏ9ÇjUu8ÏçL¹âƒŸþ½8€djrÆ?3Hô§zŠvÃ9ëž)ʧ#¥"‚Í<Ó¯Z,4.1ŒýiàõãÒ˜:dsN?…Ç…œÓºõ"š»ºr8 P;˜{KÊeAïO)`x¥.3ò©œämß½3'Ê„üïaMihç¦sCFÿ) 09ô¥1äðsøP+ o1”s´z×­F©dä©cêMX0’s’=F)­=Xã>”XEVÚˆ±üÇäÒùaOR;þ½Lñ`ðÄ‘èi®½Øœg­+È™Š»ÍŒ€ÃùÔ,B©v+tÕ‚ÈÀRkfni'ª¶çÚ‡ä8Û¨›¶ü§™5S&I Ïsž?:›Éuˆ–%ùã'}}j”öÛîRu™âd˜íú Z_qì§ì/j¯cšN2pÊF2}jËÈ‘çvXú…ëøS%c冨Ãû»€‹ÌìDîCîÛ†èGôq°™ÉiÃqÎ3Çò¢F•ŽPª']¤‘þ5 2¡v’b¼uÈýsH"™hê,³´{›©ÝÇà:Ó%×-@T 778¸Ïà:Õº³fåÕœ÷UÏÿZ”M&òÍÀêYÈASK™›F’ê‹)«JåA¶—È`¡9úõh·{½¬¨°Æ[È.}ùâ ‰d“æimÀ$eö3äç¢óÏà;Ö‚X´¨ŽîVþ©Ù²ÛŒtHà ingv ~ïÌÚä T°ùyý«Jü²]Çó51ŠÖÐyw—6vÄûµ@Ò?ç“úSe×mbw¶‚+ëÇSµ£Aå 9èqŽ(÷P¯R[ˆÌ„‰äÁ9;$~ ýju·‘Çî¡$cïIÂþCúš¨×:«¶Èá´²ˆu ûÖ_ä*¼¦söÛÓrã’%”„ÙTb“•öBåó-›«EÛ ·ÛØœàõú/?­Fo K#ÛØÎU½ˆÁ9üÍ:ÖÒ[ˆÚÁ'–FFѱs޼ü«Jõ£yaGe±ü…-K²êŒ¨§¼–ÒWF—;°›ã8§>¿…3ûK‚[Q»ÞHÏï…ü3ÒµDZM¤ Kz÷z)'?÷ÏAR¶©a~ãNr{3€êO_Ö§B¹¹~ Úfm†‚#;¨ÀÅæÿ#rD½‰2–I›yp¡Tú¼T_ð\<›Di6@*¹côÏøV%í½ííÁóegMøˆ3c¯9ïùãÞ“•ºN £¼™«{%ü–7P¾£måyCæˆä¹Î0G•St‡û:KK«Ý÷H1½ˆn£ÓÍUŠÚKX#¥YB®=†IÇך™Q¦, 2ü¹È ?\Í#[$ìŒÖÜÊöÈOÉ/8_Pyàæ¯Giil’±/Ü@Æ=}éË¿=x$ã?ZØQ€`ìF@lg©»«Öã¸WVUˆîAËl ?•O »Ì ³ÿÀU³ú“ÅAo¤4ª#bI§éWZ# eƒ€Ã Û×>†µŒ.®Î*µ¤¥h‘‹T ¡$ŽN?Èõ©|…Àc*F£±RN•V¸¾X ¬ÊÎɕ„§_”M,ê ¼/÷T±üÍZHÊN£W“.©! 0øý8 ÉoQ¦3Û·Z†8w®$ɃŸŽáÒ†ÖM«qñÉ'î1ÍR!%êK–oxöé—EÜí"íãñþU$™u"T^FBÆ¡k©÷Ž'•sÙÚ>¹Å9ä•AÜÊ ðÜhÑܪK-ÜŸ*ÙÀ?€©69æ8°;à~­40æETÎâIüª@]‡Íç08û©·?W)›«Ù"S‘s9?ì©Úþu:` ð…N태w5 '—.TD™ÉmäÿASÌøWV•€Æ^õâ¬MîÈågf…¼#’âU=;û~´ÁqœúôªÍ. uk¨nc+n˜% ù}†G«A*3ëŠSºv$ Ì:cÜñJà1ašx™ïJ©´gõÏz£1g€;S•pqëÍ ì #wQO9ÅŸ^i€£®zSÀö¦ã úÓ†qÇã@èzRñÓ( Œv§´Þø§ñÅìE/n´ p>¸áŸzh$ñN^{Ð3#{cÑÉ\œúRŒõãž:PÜy¦b ÀçšF•@Áž‹Mc–À'>‡¥!?0rÜcÅ»c9ªgs†ãœR’NáŸzcL±œ 9 C™xä÷ãµ5Û 2FsÞ¡’éYö.só0ÎŒõ⢞äÁÄ…Q@Îylý?*f[ ž£±ëPK"FXgÐËžúY@) »³€Y6)üÍP–;“,$©C¯Ó9Å.nÅ{>úS^@-'¹¬û^×n £9çjðª+mÜæ=1O>äÒÈ¿gË”·†2\†#·jWeªQê+j%‘IÊœcéC]Þ•_1ျۚ.#›ù“\8Xþ@.jÄ6§åd‚ÚÝ3÷¤`Iü:þt®Ù\‘] ¾`—ïßM(î CüèK,¿îté¤$ýéÛ{Ì·,ªd–îOî¡òã×ùÑui=„B78[ ø’?–iY²—m€[MÂï O çPÏ{§Ú±¶Y}ǘÀýIÀüªõ¾•m —•s.ØCn8$n8Æ uü*H“N· öz|DŽ’¾~»>¿‘ôª³H•k÷ü ѨßÍ(m>ÂFÞ ${ûTW)ª4%u @ÛÿÓ q¸}æ¶åÔ¡jHž«l3ønëQC$<Ëo§n ÿ-flgß'&“¸Ô’ÕGúù™vz]£ÉZÜ6ÏœK»nOcÍj‹ … +˜mÐ’ìì3ÏLŽ‚¤gº?zàD?» c?™ÍUwµVÌ€ÈøûÓ’æ‹ ÉËrÀ:bñ$³Ý°ê©–=°?MâÀA´Ò¡„޳·Íùñª¢é]v© ;…_ñ¦¼ÏÜÒ@€÷Ç?™¡¡)t4÷R‘ÆovF@ÊéÕ) eOŸ,³¸<,ä~}ª%”Î>V–`:íl(üxΔÛ8nJ¢súôý*l¬È9ÀUëœü©<øÜŒ3Ê}#× ¦•·VTîn€ÈÙϯJÐÊF¢(þ7ð§f=:¥iÆÈÆÈcíÂ噎9æ§HÛÊ1)Bœ圚`E.vÈáÔ¶sÛÓµ; ÁÎ?:9WRù´I¶¥¼@â/©SÍF²£¹‹ú„SΣ]âÃí,BðjvÊÏlu?ýji·Ðˆ‰÷…H›íS8aV;œ|Ù >¼P† ¡ZE=x_ÿU0¸‹åTQ“ŸF!Í4‰roD5YÓåi—'¶Ñ“ô¡â.DnÇví­+4Œ¸ýîÜ )»K ²ov“#ò ­E1Gå˜ÎÌ7YËgô©Ý€ÀY8è¡¶(ü)DÄ2¦8Ç®1úÔˆÁv“=D}(°›GˈGûOŒþ½hgè¬Òg)þ}*_³ †Ke_C+ þU9I6 Ë·=•1úÓ±.ä[fa…„(ìd|ŸÈR¨òÆ×’(ûŠ=:dóR¢Û•Éf”î±#ñ"œ¢6`4@yŸÀcŸ­+‹jî«”;º´¬On¸¸¥ŠVÛ¶8R=®ÊŒªß?8ÝÛ¨Ç55´² Ø¡k[¡›sè1ÏnÕŠþaÊ"O•]$c¸zN1Q¼ŽÅxRõ'te™äÁì"þ}i±:û¥É=|¤þ¦@`¿÷›sÔTæ6 6pp¿Ê®Ç5Æsƒ÷H=s¹±ý)ÈN ³ÔüÌ<$j¤HèÄÿýzÄh¹‡s¯û#¯„…HewÚ?vw8äþA‰dir]‡Ó 7Fìå~â£Ê;{Ô™“Ì °PÇåÃc4kä¸gÜ ÜmŸc!N¾„`ô?ç¥X‡ñÉûÛNFj]ûJÆÅSŒh1*JΠ`œ°ÆIüsBLr±JU[z ©b6mÜc"”Éìªç±À?çŠnÓ1)³jÿ}‡ãÀ¦HäPç®8NHúÔhòP ÕŸ ursÏÿ^£ûCgÏ÷†Öb„`³`õǧý±d¸Bäàg¹ö¥gÙ¹¶±UáqøTC‰mdyT‘»æÀÈíOUgFG æ>w9\ó8 ,LåíN8œÐ Çn”à¥1Ü{QÀôò§Ö”1LíÏjzŒr{ûÑ‚çŠzçü(ÿÙROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/gradio_app.py000066400000000000000000000056771510465702400310150ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from txt2img import StableDiffusionMGX, get_args, image_to_tensor import gradio as gr def main(): args = get_args() # Note: This will load the models, which can take several minutes sd = StableDiffusionMGX(args.onnx_model_path, args.compiled_model_path, args.fp16, args.force_compile, args.exhaustive_tune) sd.warmup(5) def gr_wrapper(prompt, negative_prompt, image, steps, seed, scale, conditioning_scale): result = sd.run(str(prompt), str(negative_prompt), image_to_tensor(image), int(steps), int(seed), float(scale), float(conditioning_scale)) return StableDiffusionMGX.convert_to_rgb_image(result) demo = gr.Interface( gr_wrapper, [ gr.Textbox(value=args.prompt, label="Prompt"), gr.Textbox(value=args.negative_prompt, label="Negative prompt (Optional)"), gr.Image(label="Canny Control Image", value=args.control_image, type='filepath'), gr.Slider( 1, 100, step=1, value=args.steps, label="Number of steps"), gr.Textbox(value=args.seed, label="Random seed"), gr.Slider( 1, 20, step=0.1, value=args.scale, label="Guidance scale"), gr.Slider(0, 1, step=0.1, value=args.conditioning_scale, label="Conditioning scale"), ], "image", ) demo.launch(share=True) if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/gradio_requirements.txt000066400000000000000000000024721510465702400331350ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### gradioROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/requirements.txt000066400000000000000000000026221510465702400316050ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### --extra-index-url https://test.pypi.org/simple accelerate diffusers transformers hip-python ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/torch_requirements.txt000066400000000000000000000025771510465702400330150ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### --index-url https://download.pytorch.org/whl/rocm6.1/ torch torchvision ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/txt2img.py000066400000000000000000000444741510465702400303040ustar00rootroot00000000000000# The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # Model Download Instructions from diffusers/scripts/ # python3 convert_stable_diffusion_controlnet_to_onnx.py \ # --model_path runwayml/stable-diffusion-v1-5 \ # --controlnet_path lllyasviel/sd-controlnet-canny \ # --output_path sd15-onnx \ # --fp16 from argparse import ArgumentParser from diffusers import EulerDiscreteScheduler from transformers import CLIPTokenizer from PIL import Image import torchvision.transforms as transforms import migraphx as mgx import os import sys import torch import time from functools import wraps from hip import hip from collections import namedtuple HipEventPair = namedtuple('HipEventPair', ['start', 'end']) # measurement helper def measure(fn): @wraps(fn) def measure_ms(*args, **kwargs): start_time = time.perf_counter_ns() result = fn(*args, **kwargs) end_time = time.perf_counter_ns() print( f"Elapsed time for {fn.__name__}: {(end_time - start_time) * 1e-6:.4f} ms\n" ) return result return measure_ms def get_args(): parser = ArgumentParser() # Model compile parser.add_argument( "--onnx-model-path", type=str, default="models/sd15-onnx/", help="Path to onnx model files.", ) parser.add_argument( "--compiled-model-path", type=str, default=None, help= "Path to compiled mxr model files. If not set, it will be saved next to the onnx model.", ) parser.add_argument( "--fp16", choices=["all", "vae_decoder", "clip", "unet"], nargs="+", help="Quantize models with fp16 precision.", ) parser.add_argument( "--force-compile", action="store_true", default=False, help="Ignore existing .mxr files and override them", ) parser.add_argument( "--exhaustive-tune", action="store_true", default=False, help="Perform exhaustive tuning when compiling onnx models", ) # Runtime parser.add_argument( "-s", "--seed", type=int, default=42, help="Random seed", ) parser.add_argument( "-t", "--steps", type=int, default=20, help="Number of steps", ) parser.add_argument( "-p", "--prompt", type=str, required=True, help="Prompt", ) parser.add_argument( "-i", "--control_image", type=str, required=True, help="Control Image", ) parser.add_argument( "-n", "--negative-prompt", type=str, default="", help="Negative prompt", ) parser.add_argument( "--scale", type=float, default=7.0, help="Guidance scale", ) parser.add_argument( "--conditioning_scale", type=float, default=1.0, help="Conditioning scale", ) parser.add_argument( "-o", "--output", type=str, default=None, help="Output name", ) return parser.parse_args() mgx_to_torch_dtype_dict = { "bool_type": torch.bool, "uint8_type": torch.uint8, "int8_type": torch.int8, "int16_type": torch.int16, "int32_type": torch.int32, "int64_type": torch.int64, "float_type": torch.float32, "double_type": torch.float64, "half_type": torch.float16, } torch_to_mgx_dtype_dict = { value: key for (key, value) in mgx_to_torch_dtype_dict.items() } def tensor_to_arg(tensor): return mgx.argument_from_pointer( mgx.shape( **{ "type": torch_to_mgx_dtype_dict[tensor.dtype], "lens": list(tensor.size()), "strides": list(tensor.stride()) }), tensor.data_ptr()) def tensors_to_args(tensors): return {name: tensor_to_arg(tensor) for name, tensor in tensors.items()} def get_output_name(idx): return f"main:#output_{idx}" def copy_tensor_sync(tensor, data): tensor.copy_(data) torch.cuda.synchronize() def run_model_sync(model, args): model.run(args) mgx.gpu_sync() def allocate_torch_tensors(model): input_shapes = model.get_parameter_shapes() data_mapping = { name: torch.zeros(shape.lens()).to( mgx_to_torch_dtype_dict[shape.type_string()]).to(device="cuda") for name, shape in input_shapes.items() } return data_mapping def image_to_tensor(image_path): image = Image.open(image_path) image = image.resize((512, 512)) transform = transforms.Compose([transforms.ToTensor()]) tensor = transform(image) return tensor class StableDiffusionMGX(): def __init__(self, onnx_model_path, compiled_model_path, fp16, force_compile, exhaustive_tune): model_id = "runwayml/stable-diffusion-v1-5" print(f"Using {model_id}") print("Creating EulerDiscreteScheduler scheduler") self.scheduler = EulerDiscreteScheduler.from_pretrained( model_id, subfolder="scheduler") print("Creating CLIPTokenizer tokenizer...") self.tokenizer = CLIPTokenizer.from_pretrained(model_id, subfolder="tokenizer") if fp16 is None: fp16 = [] elif "all" in fp16: fp16 = ["vae_decoder", "clip", "unet"] print("Load models...") self.models = { "vae_decoder": StableDiffusionMGX.load_mgx_model( "vae_decoder", {"latent_sample": [1, 4, 64, 64]}, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="vae_decoder" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "clip": StableDiffusionMGX.load_mgx_model( "text_encoder", {"input_ids": [2, 77]}, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="clip" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "unet": StableDiffusionMGX.load_mgx_model( "unet", { "sample": [2, 4, 64, 64], "encoder_hidden_states": [2, 77, 768], "timestep": [1], "controlnet_conds": [1, 1, 3, 512, 512], "conditioning_scales": [1] }, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="unet" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False) } self.tensors = { "clip": allocate_torch_tensors(self.models["clip"]), "unet": allocate_torch_tensors(self.models["unet"]), "vae_decoder": allocate_torch_tensors(self.models["vae_decoder"]), } self.model_args = { "clip": tensors_to_args(self.tensors['clip']), "unet": tensors_to_args(self.tensors['unet']), "vae_decoder": tensors_to_args(self.tensors['vae_decoder']), } self.events = { "warmup": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "run": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "clip": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "denoise": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "decode": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), } self.stream = hip.hipStreamCreate()[1] def cleanup(self): for event in self.events.values(): hip.hipEventDestroy(event.start) hip.hipEventDestroy(event.end) hip.hipStreamDestroy(self.stream) def profile_start(self, name): if name in self.events: hip.hipEventRecord(self.events[name].start, None) def profile_end(self, name): if name in self.events: hip.hipEventRecord(self.events[name].end, None) @measure @torch.no_grad() def run(self, prompt, negative_prompt, control_image, steps, seed, scale, conditioning_scale): torch.cuda.synchronize() self.profile_start("run") # need to set this for each run self.scheduler.set_timesteps(steps, device="cuda") print("Tokenizing prompts...") prompt_tokens = self.tokenize(prompt, negative_prompt) print("Creating text embeddings...") self.profile_start("clip") text_embeddings = self.get_embeddings(prompt_tokens) self.profile_end("clip") print( f"Creating random input data ({1}x{4}x{64}x{64}) (latents) with seed={seed}..." ) latents = torch.randn( (1, 4, 64, 64), generator=torch.manual_seed(seed)).to(device="cuda") print("Apply initial noise sigma\n") latents = latents * self.scheduler.init_noise_sigma print("Running denoising loop...") self.profile_start("denoise") for step, t in enumerate(self.scheduler.timesteps): print(f"#{step}/{len(self.scheduler.timesteps)} step") latents = self.denoise_step(text_embeddings, latents, control_image, t, scale, conditioning_scale) self.profile_end("denoise") print("Scale denoised result...") latents = 1 / 0.18215 * latents self.profile_start("decode") print("Decode denoised result...") image = self.decode(latents) self.profile_end("decode") torch.cuda.synchronize() self.profile_end("run") return image def print_summary(self, denoise_steps): print('WARMUP\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['warmup'].start, self.events['warmup'].end)[1])) print('CLIP\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['clip'].start, self.events['clip'].end)[1])) print('UNetx{}\t{:>9.2f} ms'.format( str(denoise_steps), hip.hipEventElapsedTime(self.events['denoise'].start, self.events['denoise'].end)[1])) print('VAE-Dec\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['decode'].start, self.events['decode'].end)[1])) print('RUN\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['run'].start, self.events['run'].end)[1])) @staticmethod @measure def load_mgx_model(name, shapes, onnx_model_path, compiled_model_path=None, use_fp16=False, force_compile=False, exhaustive_tune=False, offload_copy=True): print(f"Loading {name} model...") if compiled_model_path is None: compiled_model_path = onnx_model_path onnx_file = f"{onnx_model_path}/{name}/model.onnx" mxr_file = f"{compiled_model_path}/{name}/model_{'fp16' if use_fp16 else 'fp32'}_{'gpu' if not offload_copy else 'oc'}.mxr" if not force_compile and os.path.isfile(mxr_file): print(f"Found mxr, loading it from {mxr_file}") model = mgx.load(mxr_file, format="msgpack") elif os.path.isfile(onnx_file): print(f"No mxr found at {mxr_file}") print(f"Parsing from {onnx_file}") model = mgx.parse_onnx(onnx_file, map_input_dims=shapes) if use_fp16: mgx.quantize_fp16(model) model.compile(mgx.get_target("gpu"), exhaustive_tune=exhaustive_tune, offload_copy=offload_copy) print(f"Saving {name} model to {mxr_file}") os.makedirs(os.path.dirname(mxr_file), exist_ok=True) mgx.save(model, mxr_file, format="msgpack") else: print( f"No {name} model found at {onnx_file} or {mxr_file}. Please download it and re-try." ) sys.exit(1) return model @measure def tokenize(self, prompt, negative_prompt): return self.tokenizer([prompt, negative_prompt], padding="max_length", max_length=self.tokenizer.model_max_length, truncation=True, return_tensors="pt") @measure def get_embeddings(self, prompt_tokens): copy_tensor_sync(self.tensors["clip"]["input_ids"], prompt_tokens.input_ids.to(torch.int32)) run_model_sync(self.models["clip"], self.model_args["clip"]) return self.tensors["clip"][get_output_name(0)] @staticmethod def convert_to_rgb_image(image): image = (image / 2 + 0.5).clamp(0, 1) image = image.detach().cpu().permute(0, 2, 3, 1).numpy() images = (image * 255).round().astype("uint8") return Image.fromarray(images[0]) @staticmethod def save_image(pil_image, filename="output.png"): pil_image.save(filename) @measure def denoise_step(self, text_embeddings, latents, control_image, t, scale, conditioning_scale): latents_model_input = torch.cat([latents] * 2) latents_model_input = self.scheduler.scale_model_input( latents_model_input, t).to(torch.float32).to(device="cuda") timestep = torch.atleast_1d(t.to(torch.int64)).to( device="cuda") # convert 0D -> 1D copy_tensor_sync(self.tensors["unet"]["sample"], latents_model_input) copy_tensor_sync(self.tensors["unet"]["encoder_hidden_states"], text_embeddings) copy_tensor_sync(self.tensors["unet"]["timestep"], timestep) copy_tensor_sync(self.tensors["unet"]["controlnet_conds"], control_image) copy_tensor_sync(self.tensors["unet"]["conditioning_scales"], torch.tensor(conditioning_scale)) run_model_sync(self.models["unet"], self.model_args['unet']) noise_pred_text, noise_pred_uncond = torch.tensor_split( self.tensors["unet"][get_output_name(0)], 2) # perform guidance noise_pred = noise_pred_uncond + scale * (noise_pred_text - noise_pred_uncond) # compute the previous noisy sample x_t -> x_t-1 return self.scheduler.step(noise_pred, t, latents).prev_sample @measure def decode(self, latents): copy_tensor_sync(self.tensors["vae_decoder"]["latent_sample"], latents) run_model_sync(self.models["vae_decoder"], self.model_args["vae_decoder"]) return self.tensors["vae_decoder"][get_output_name(0)] @measure def warmup(self, num_runs): self.profile_start("warmup") copy_tensor_sync(self.tensors["clip"]["input_ids"], torch.ones((2, 77)).to(torch.int32)) copy_tensor_sync(self.tensors["unet"]["sample"], torch.randn((2, 4, 64, 64)).to(torch.float32)) copy_tensor_sync(self.tensors["unet"]["encoder_hidden_states"], torch.randn((2, 77, 768)).to(torch.float32)) copy_tensor_sync(self.tensors["unet"]["timestep"], torch.atleast_1d(torch.randn(1).to(torch.int64))) copy_tensor_sync(self.tensors["vae_decoder"]["latent_sample"], torch.randn((1, 4, 64, 64)).to(torch.float32)) for _ in range(num_runs): run_model_sync(self.models["clip"], self.model_args["clip"]) run_model_sync(self.models["unet"], self.model_args["unet"]) run_model_sync(self.models["vae_decoder"], self.model_args["vae_decoder"]) self.profile_end("warmup") if __name__ == "__main__": args = get_args() sd = StableDiffusionMGX(args.onnx_model_path, args.compiled_model_path, args.fp16, args.force_compile, args.exhaustive_tune) print("Warmup") sd.warmup(5) print("Run") result = sd.run(args.prompt, args.negative_prompt, image_to_tensor(args.control_image), args.steps, args.seed, args.scale, args.conditioning_scale) print("Summary") sd.print_summary(args.steps) print("Cleanup") sd.cleanup() print("Convert result to rgb image...") image = StableDiffusionMGX.convert_to_rgb_image(result) filename = args.output if args.output else f"output_s{args.seed}_t{args.steps}.png" StableDiffusionMGX.save_image(image, filename) print(f"Image saved to {filename}") ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_controlnet_canny_sd_15/woman_canny.png000066400000000000000000000403641510465702400313450ustar00rootroot00000000000000‰PNG  IHDR{C­@»IDATxœíÝ–Ä(«†S{}÷ËÙ™qhQDüC}Ÿƒ^Õ©D‰Q@ÄÔóÆó¾oø à"Þ÷…öЃX ]ø¿ÕX ¤À›âKÀ« àR`\÷€ËÀO`¿ÕÒäB‚¿†-èz^ˆ4~NÑ kEô’÷}a*€ ú‹ ½]_sÛ`à³7XÐÿ­€Ké¨÷C¼(åtÜ S¡Ù ‚¿Ë?ÿ4ú*YšP…2â€IØ\þœšn××Ñ‚Aci´¨Kâ'À ‚çÞ~‰¬¬•.tŸR˜Õ7´ÿFà!0–ZÇ?y¾²®yͧîKÛa°ÙðµoøˆN—Óã/£KÕã Ù‘K^ÒãÀ Ž?wùsî|2Ì’‹á4Ævîœ \r×'ß«¨ŠГszG(02’O¦„ª‹ëgkà Áã 3‘B×kÕ¢×Ïòó‹•æÄÈ•&K+_œƒ'@O”ž2מüÂÜ9šòÍÈòçLlÀ¦à±Ð Ìâ0‚Z¼~ªmÔȵ6`Ç輚uð„™dÀ½xZ2ñ†RSZ’v¢ò‹µhnÖ'ÊÆEÍ›KÙŒ”Q®œèœ\Õr3½_ua$Ì.ÊtG™‰­hRt’'ÐàC1ð2(X…›” ÂÑåÊ»ðChÏgiÇqïÐ…\@? çÕÐw0ÏlUÖòzƒˋڅËî½sÚ©u! .gd$>”fC#€°j“¶ö’Z.WÙ}A;`$§ì„°2wÀ…„õå«ÌOmF©M¡6°€ÅŒX?Ôgût©B.¹˜T¬¥¥A¼ËWn;‚×A0ŠdÌý#çÝÓÍ/‡ '  oã´Jj)Y–-iIš¤º̉\‚æ’èdá_Àá8÷þÜ öÁýhú9i¢ÏB ü* OM£q ä¹µ=ÙeÀ“³½Ø¤ùLjUþ!:^››4ÂÛTyTÑ.]`dz×O‘}êÆ’;¶--id×>w‰Ò$OÓÜ]ÒÌøï;ËU•C¥í^¸P×¶6Ÿ=yIѮȺ¾QÀ,ÞÔS6\5Hž^‚¤/Ÿü O]ûèmý¶x³Ë;  ?s˜9jm’ "s–êbùZM-Éskö\½,"À Ëdz¬z<ˆW%ƒ¬%‹šW)ƒÁð“UóiAîøòGh"áK6÷‚½©ƒ$ò%‚Bl/!§¬y-Éòsù¿É¢Ã5¯aÖJ’Ä U±UÀ/T|òÂ胬ës’hþ쇥OœP'Ø&%I;*PvÏù%9•Âkj ÃÇð9t½É#`hÀ¤Šˆ{žôÜsÚ™ #K(”)˜eE\B€Sªþ§>߯‚óž;_>Séò+Us®dáò¤q*JØŸŽ?ųl9Š­ZTëÉϲiI*ñ¢œÂÉJØçª?°…œœUª™yL6@xÊÅÒ"yrR6£*R±ÿæ(g^“ŠÌ— Òo±…“–°+{iÿ]äähæO^+m@qê`2yí¾°¥öÿØKZ™*5ͧ¹‰‚àΛm€Áœœ²©ö6¸HÒ—ONžÉ¿bAÅ*øÁâÀÈã;œßÎKO{ÔZU¯Ásö XBñ ç6¤q®.mìrGÉ0N.ª£)*÷ý›ü7)L±Fá_ÀPEpÌÞå^"7ŸjgÃ-ä.‘й­`{޴ܽõ Ï´%Ï6 ¯’ë*øåý˳ҔiTmóé(¤0{à–¦ÅöÀ°1\Mì¢.5ìu#Ý¥­µ¶ˆSñÀ#'éúH*ÓöMUè¿¶€…DA¼ÿ[$ ØŒ¯ßü~¿Õ‚€øý~Ñ`þžÎ§”mOŠ–«‚ê‘p~U-BÄ©/°.h`¾¿Or%éëÕ–\ôÖ ‹]ôu¡ßÐ F‘gä¨}cÉÅ(“¡ÆÈ8 5ƒJ> „€€Ä¾‘?ã¿Å‚j®•çf’áÚx€h†Š"ñüô¶aÇaãsÊÒ‘¿­]›ÕÇˆŠ…‡¢”7ùþUWÚ Œì;r¼‰=Z½t·&“´ü_eoÉEäËÍ]Ñl9.! fßàÏLüØH¥$4°óûý迹˓± ~rmFP¢‘{š9Ý€jüè53å¯r{GK¢ å΂æE]/ÿ·%$U¬këž ÀlÐþ~jìƒVªøÚs”G”6@®ÿ«¼V#  Œ.‚OÝ«=•Qò¢ÉÉ}%¸ÿèWúÚ£•m…. À(0º ä¢ ÝóMÁ몪W(k|u‘í„ *ú+ÁüÚ†ÒenoÛ¤á6 å9ò¹ ¨*„_ØbÐE;‚, sFâÄd‘k´(ÍæK}é«Â¢ÍY¼Þ*Þ3g^’Æ÷|KV `Ð|kÉÚaNø‡ÃF‚«Ûi¦Êµç´Wš+G©7sfƒU¡”¹—h/€C8o<¸òï•r2Ô#œ6úÞ š:i"7\_ìˆx÷2GqÌ!ÒƒnªVÇi ät)Y¨.÷¯æªÜù‚‘Дl»ëåý_œ4$rQ‹µ– ‹¾–/zƒÜ…WÚú+n›Ð ÐýZä°€ðÕî÷(ËŸ43]î7gV•ÒrKÀ‹­•³ÅýÏYÐ笱@îÜ­’¤¢‹M‡÷^wš”9©Iù…OÆ•uí<àaJ6wZ­<ék8Ý‚4Pð*ÂSøFÚߊ+Aà Ê4Ü+¨ÒËÞ\×Ãf<††zTT×û¢~ÕeÈ#cgor+B`(#ªæ€F²œÚÚyi­5Fœy ©JÂ\™EÁ’cÄÉx¡² Q¦è’gïÈÖi¡a6Ïf&I#ÞhœçTR­Näh×ìÅ““Ú­c½ÇØ€Gì]ÏßnÀ4>òKàßò«lìQõ­1wãE 9R+˜g-Æe+€äi03q´œ@sThÖÊ“Zë‹®2/06®×j[šrª<3*– ýê‹R‰kª3³DÍqµžƒ.Õt\›ÕÜ5–‚H¸]ò%]|ö§¤& åht:¿VðîùAa<_¸\I)C®„b-Ý)Ч´Ó£'Ü«²»Jõc¤+¹Ù0ë߆[éA¢÷¨©…› AÁÉ,ËÃÿ]H±·ÐÓd£l7o0¨È ¶¢FSη ]díOK‹>TÕÅï——,Tª©Îfe•È%Ë’–;9ÙÙ  wNQ¶b±·5€ÛI&·dÞjDó¿Ò’{²º(²¾¯–|ò)¾­”$ãvQ) }(ãÙš’…s‚ÌÂme¯{3+š •’p29çëéêSs]™si[ªÐxå†*о0?Gö‘s\”ŸŸ¯¿£ªïòè…‡.ÌrÝ£X‹ò¸æ[nû•áxÇA¢W÷-õj¢Í çsu\lÆÜCQ]naJíœTý† €Üi ŸçŒ›:Ö”©¬WSõû¹:½ºªàÏBsGÅoçk½œäB›ÀLÓ‰“Iù”Wn¨çÔ\îÞyd¦¯ URR7 ÕÔKú@ò~m€Ÿ¯oíâÁ\u[B@`?¢¥Ñܯ•£CÅðKô;e¶Ÿ9£ÕÉç𯄠wgfìK ¶y|à.Nòb¨ƒ,8hɃÒl™ PÙ”'ë§,íÌï É[1ЗV5Pž €SΛÆ>L_Ëf §”‹„FÙ©žÒs¦˜ÓŠFWo&“—kþÕ—À±œÚ×#ßY¯OÝÔÞVQ!9Ã#¨?ÃücÜÉf„F ÿ®2¶óéUK†U{¥Ø!¹GW8? ž(оJÐ<ž´ú’µzŽ\‹!N­œÕµmÃÕ°$káüŽˆ¹¡×R‡ºÊ§æ'Ës}cFS^»\µù©%/”ë5«¹Š~H6þª@÷Û™Ãñcôäxí_TýÂ%OF G''k*’•EN'ö}jí åÂg¢¨*Ð\u•+Б³Ç2èÏñ=Fé5½pIÎTI¨—«Enªdx µ×òrªj¤GrU7~dð0”ŸàÖnÁC—@¸Má5jÉx´ §Iëôæ¬ðh‹€`Þ|¢zCS#•¹ý•«úZZISøÂ޽|L-lõ}%§^snµ< ¾³­I•¾p²Þ–‡Ø(¶\lÕ™#fÉùÐP§¾{KÚð ðNQ¯ÍfÉXDRƒ(®Ù$M‘F!š-Y-jŠÕX@_T²XÚ,rE 7bS±ÁT4>æI–#èß.>i‘ø·I»¥·fˡĦj¹²6&L,`ÀÌQ1šs.éLIoQ>„ ôƒl„œ<2³ªÕ·ƒ\í½I£)ÇIc‚»ÝíjýÄ¡Â8Aé m [Ç\×Ї›œTiN®º°¶js@étÊ4Œ^¿Ç» Åû”dB]Ô\Ü©W½_QCSqÖÁf&çðmÕá_ºÇ{´ Üð‡ÛÞv[|k Ÿ {IF&¯7ÏTZŒz_#Ô±@=ô•Ú·õm 0àFøÏäÇÂ&ÜDÿr%NýÙ¢„-Šï‘+ §Qåû²W!…ãf!“bGÂhªðÿÂ"p £ûœ!zá|9wËsÚ!i¢ªeÙ’—Ðã¹BŠ‚ Ó£âµÅ¯¸üUâå*J–¯)\yÎUCŒb‰r™pá¾$Ýê9íÀµ9ÿ KBÏÉÙƒZ‘ÌßòÚùç¤Ó—/œ)´dŽ-Æá¬s¾îóCîW'´û‡Ú‹Ñ¡¨„èÌ¡«úÅÈRû O?á±`=»æçš9l\i®mž`S¼„>;úò¢Ú Aã¿™pÿL– à€•,Ô,BH¾/ìÎñ“tœçÄëŠï¤Ó—@“ £wÀyx¦‘ {y{I+pEç.Šêí¯‘d»{Áýî™y„ þ#ï+}Øl†ÞÅÐ'K3„oU}a§5p‚zJ´€`?Žqvô$—õŽswÕ#Lׄ§vðÓ¢R}¥x¦ô.p>Å„¤þâ3ƒœ–¹måHï]Ù]šK?ŸS– ?_“ŒY’¦Eî~Ñþ¡X]®öÛº.¸”bG/Ú¡œÛF‘ ¼ôѶ¾2DÍ µaEÁú>_åÜ‚[2ùˆ\TÎxÜÖuÁ½Ô9½KxÛ(â-“ô»ÝEÃWô½/<âùVõF~D¯¾s7ˆI½^q$£DôJË< ½¹ª,tcÝÉyôüù|ý €_P‹ÀW Ië ª!œ•ð\6ÞT–Thb¾&]h5#¼-ÎP2Ï÷@oN#Þ€ó ƒ:Dœço yTÈ{Íû©Š”WÇò$ ·ŸÀ Lǘ5-ÔáY…á6ý?€[£ýGˆþõÖéׯrú1ʲ/ÂVyS´|^Ͳ׷7ö-Èõ†üþîò‹”霴œ\-A€9 ¤TóËs¬´?¸ˆ¢Û’óµy EJzñE1׬(¤|›#îq\ÉÁkNÖ’;>îÈçæÂó €?_Ûíß å1¸ÍX >õ›¡»HQpŸú­rr!=“êÁÈÓOÊ,{ ÝýSê•÷mC:û¡ÇC-Q{NVgæºÚªãCDB'°ãÇ}àY¯Ú}«H1é T:û¶kûÂ]uå%šÓr.vr~0íÆ#Ÿ£êAD—è›Bÿ¯YøÀ `8~zOѹÈ]eð×^öÛ2½ögF N™N3 eJÒwÊ6‰æ¼UÛÙLÔ1æ¬@Œ@ÿ,và"ZáPç±*ÊÎäÃR?PW i ¸ª2s,qÿ“LkóŽ·9! Ëò!Äþ­-Š_Þ~U! bd ᧵Dzlh:F¯Î³¶êŸ&?h E!¯*aª„<ÌâvæhÚ<©ÐM/xFc•`úã¼Ò0ù˜yJÆÉ°…‡ÝßYÙŸ26l’×Ο.` >{¡aV;aD ɉ )æüDû ¦!?¾W–…Ý—» 3sÿsµ/©z`Ʋ|&©•ª¨š» 3ƒÂJæƒúlsŽÞ×6/·µ²õEgqÝÅ`@õ+Èò'£Œ2k"?]F¬þdëSþàËGÁÏŠ@c” 1L‚ôsãg „#8œœúÐ< Œ—íoúNkÉZ‹&d‹×9ib$$™xê¤Xƒ¼Ò(_¨œ,1šz—[/=òílt#øÝE«Ùüœ\ƒðeðb]á¸ðnÌ®FîÙµ{‘f"»ÆBÄô†ÃƒWÑ·óEúðÓV£œçtOëËÿV –!tkew_;Ål@núï*6 6À­„« 9»ÅÆ‘=ÿ ŽÅì¹(ã?¶ÂçùŒ¹°@òä…ø‘ä™"Œ¨áÁŸbèR(¹ê`Õ ›‚H£Ìa=à¦z* Ù–«e¹sƒÓ½rUWE—+ÙK·¢?0  (õl´³iôìmPt¥Ö®hÎßÈP hÁ˜ñƒA'3 YE¸áYË﹡€³‘B×±4ÐHíÃÅã ŒùTô2$—8VCIvª®Ý‚Ñnxñr7ôU e#Ýq CÝØ/X¤T‚{½šâ¶í¾0 L¯±tÊ ¾ÕLº7ixÜúçîÓeÎm“®Z9€&nÐea¡,÷êÄZlÉì GxFßÞèA-v!ÄÍë½I`Z¹¤…¸³Y/$_g:b[èXµÛg:îÍB» ô 4Ó½øÏ“§ù˜w ¯%§—GÄgx Va¯f4зŽO É,é^ã!D-º”"v´IÖ:ì7ôO€&î"!¬\%¨Oꃮf ¶$ùí ¯½‚º'K  F£écáúî#~ÎñüÀ™DJðN£«¿k®ë×þ Àñ´¸Û‚gÔšhš¥NÏx»W´C`€„aCÀ^ûE`ab¨äÎe¦=™(M:¬%ñ‹` Í  ¼¡åYîiÑ/„3lIÌ@‚ÑnΑc Œ`fö‡pÓd`ÎÇÖ­'èèÛ›îióª7¾EsÓçÄhOõûAÓ€ù³ùÎæÓÈ—DÖâ˜Ít20à?Â"m´86Nk?À\±Qk÷}k›­óiÁyÀ€ÿH¾Ifœö¿ÄÉÀ-0 †Æ@GG ΈHlÁI/6èÛ3å…žÑb9`®@3} ¢ýÍÏÀ!›>ÐÑ™9Q›/¿:ÒÀ€PfC÷»À”o‚KÚȾ{!½þƒnw<ÒßnµØPO¿—ŸqÞ €8{ã;Ø‚¢¾îò†+euô-nͧ&™ÀÆÍÌüÔ±$æôûóÆpKògŽøæÇK~àÌ^ÂA`>TÛ¶kުί™ûb,€ýhìµÝmÀI™é ¢Â¥'hºM•OöÆÜ¿Å·JìÒ«1ZúNv!àø2o4uPÎ$6 á÷@íKs^2€}‡,žù™ ÿ=TSõ¢Ýç¯c•Üh³ÅPsè¸]«B@Ñ1»®vËØKàÇ1DA@ÞŽ[EK×ÊõêÃ~43PÍË^-L䯠ú’¾j—O„éÂÁ3`! å„<ŠèCnðœ:¨@;‘£ëH­ÕòrÐò$`î¢ËVÞ7OÁ¦o Ë *8 't$ê²´ä(o#‚8%.¹U’úM‡EK—`hÀ’Ћs ú.kåvŸÜ“a@5‘›/÷¿½ê)ÆÜv„ÞK²)z™‡äÖ°dá'Ù€íùºé1=Pª²­~ÿþ’Ï É¦Ý~{ŸOnòºm(! h{V û«ÆÉB"˜®¯\õŠ÷Û1W§X”r‡ÿô!€ë(ö]a¢-Ç9ïÜGBµÿÙнtÄw,ö{:? °+9O*gˆB<ÑÌ„&¨œÔþ´ƒÉ'$ÏÉu]e¥Å*”5j*`%É®¬×æèß`ÅÏS© ‹eÊ…$¿ŠN(Vê|°`øFx8•çY¿„è ¦ çýìÂ’Ž¤¬ô€ŽXÿýO&ý.¹U’s|xLctGÊ•_•?Š~öC÷k.΄û ÝFÞ±…€žTÄ&ÃÉ-ºh"WnÁ ÄÈîL4àýÞ8€œ_Og øþXuðóz¦sßÜ@ín !â$%Tá|» À½äº¦¡¿FËÅü]ZžÇ8ƒîª6W­H9 pÛÿaÀÂK~jý¦@XCæ¯Ó:/uŒÀæ‚ôª]¿µ^ÿq&zß$ÈuYêéç¶DŽX8‡š‡dŽ)¨o ¤ØÞ7M¼¡À\¾ã~’ºùÁÇÏ­¿©×¸‡#ÂåÀ9frš×쌫=W ŠfyîáÈ*r+ñ䛂4áT°sžšaŸÐsè —Ì…´—0·±)0\FÎw8ÊÀÖ‡‡Ï-rŸ“_™“äôžd½´Ÿ§Dn;9B@·SåD}z„~õüÕïIkea ¢ŽêJ:é¡ö\VèSóÒtràÁ—çîOtÛ¡À*ÀmP‡@žƒFGgU× aú볇c ¤‰œ¯ÈñH‡ŽÄ }š£·5¼aÑh„½]šK¢sl­”yÿ]È¢ý›Z‚䯞½ÆÌ*nÛhíVŸÒ,¸wó×BÀ€çÉ 6!jOÏ¯ÊØÆÉ›Ê—ØqPM`SuS Ï!Vž9“7•î ÀfäRr'ËOCíK;aéå$’P~îÃ2€ÜÿéWʇâóa! üG.ÊÌ»õÓMµ=nTŽ0늎£¹(}5¬a€<-ó™þ¡8¿NúD/ÛœÓVŽ'$ÛóàæòpkÊ>ìS³+ÿue®Ð¹“ÜÿÔ`årÂ…äüJ´•Ð¥ÛUöëU0 ×ø9'”Ï BthФwµó¾¾g‘ª[Ó¯Š/\?wø¼`\³vi4éËó”žÆP ‡/ÉÜæ#nMî«- Zs¡ÃÜ-×Ð…Öi5F>fôí÷áÓA|S •Üì²¢4‡¶Õ¾«0  ˆ„Nio®gŽÌàìK2¿sPìþf`ö@Ø<5§ê(ÔCåê¯b‰½ÜTåé7‚µÛ«Ì=!“Ko þצ Å µ:‚§f ÜóÉÀ€:’ãs/¯g!†e@j}wô1ÍÐì²b»9±d¨ïZ¢…G¾OòÅdƬr ¸þ[¨’yÜ=þÔï¤û±&úÔA•~Ô×a rŒÈÚ,uðtÁÏMEC 8Ñ›“9 ´1ãæ›r?þž¿ÚÜnRÜ ˆ»ûbÛúQ4£Y: ya6ÆIÿKîNú‘wGßlF»áÉmz4QÛøk÷æ 96IÙz-´'«JÚ'6àI@£èQç0n³nTr¤yS–»«°î W ·K?Ñ9Ð êõ?û·‡¾>qª?ÀãÎI—߬ õWåªHîä2'˯Õé}Šmf6ÆÖéÃUQ!tú³-ÁÕÏÞéêB{ G—k–ø9¹90µ©Çló]º: èÌïßùÑîgþÛ—A÷%¨’¤1 Â´Tú}P¦?æ.2j½½•x˜k•Îå•jbhNBUX-z$70\M{1ÔªiVeõn¸PE€&tN@•¶º’µçf3{Ý9Z˜€,]LÒÍ9&"”su;B“¬„©@ùÔ† ß^`”PëH–žŒ»³B¢:`@Æ Ñ-F…’™ª‡N›B½¹„«'ßÎ,–s” £ÊÆ¡+Û;‚è¢#øšpÇÂW±ḋ  ×ðsAùðmKxg}ã6Åuæ.æØÅ*`ÊtÉ-ÙbŸ]´‰.'Ttqg¶Š{såP&ˆŸ¶Ë½s4ß`”'ïho`@¶þí"ü±jýÓ@K6'ß4àÓæåùýÝW8a‰Â`9œ› Åò<>vTý‚w¯QÉÏñ¦@½ÉÓ ¨€fô0 vÑûJ?©›’Û/Ü>Açêu‹uÝ*`@t#ÒãR‰p„Aë\~ÙDñP~2c=·6°ÅÖ¼Æ(Šíò–JÛ°`š·Ý½%P¾–œäE•]•®Ã7”ÕÊi iÒ„ Ì#`[ô»|-Ÿ";_€vhwV+Ùî[ÿWÁµa¯ÎňƵ’`±’Ó”Iü¨Ú"E0  žLòùÁˆÑ˜Ìv„Ù~]î+ÒDI_{\r—Pì4 že£À€>P¥?ny€{Aø7 ¶,þªò©9É-+=ÖâÒ‚^*Û…»@[õ÷ûy6W0 '4îÙ× ä#ÜÞð‰HÚ™¿è*§{F¡jþ•PZ/©’•&5æ5ªL¢-.ì‚CˆÔq{CŒüØ…Hñ-÷¿|ÎE¥¿DNC¥}îK^›Ú^²«gŒEpË#$i<’WQ}yÙ¼W ;ôú)0àdô#9—/¿\Õú1Bá$Þâ€E`p,æ±çaõý—v1<¨ÂÈZ~¹ Hî|ôö,0Øÿ.Ær¢&R¦‡NnU®:nQÞ±‡Ð=S åÏ ˆTõ*Žå3p2U¬—Ç]­4ìê¥ý—ë—^}ç`-¢ÓhFìœí{µ9 (àmÒj x TML‘è¿Jƒº¯Ú‘"’;l‹¦´¯UØNû?mÍ„çaÞˆÆy|¦«~^㈬} ¢=%|ì¸M˜lŒŸn´/“µÿü©Æî¤]~ƒ›ß23(Î9lÅ”ñÖkk)¦‹ÌÃCrÑŽpŸzB¥3vF²„øÏjAú[œY;T¿ž9}/W Ýbc—Qƒ5p>Tù†ñ<ÙñŸYãU ©7Fv1˜€Ã‰^òSÔþ}‡.Â>C©jU½^nï»Ø ±E'–¡ ‚‘özwKRKÆ¡J »µ¿“Êb/ÏýEd9Isq]Ìwz½wá¤ÖÛˆö¬åœÏínÌZk0ç#;þ|/îÜ;e-ž>ÞÜg=}7sOÕiJn76k» 8îûÿÈ{fè AÐàoå !œ«þä›.êôä·KÖ{=< p2É(<ßÞü#<•ºÒ¿ê§l!$§ËV€ù —Ï5é¿›> ¶yz¤]#ìåSo$j ù¼dÅ*É5BTÍ®âç '¬3p,E½Ð8ß.Ég#Q#æ(Ê ðaÀ™$Ó~6¶mC}¯° ä¬¾¼< ÷¢3£àUµ¸ƒ¢Ôþá+}¸Ÿ¦ uôgÚ¦\fvÅ…–„ƒ ÀL.k;wf±ûÁë_ÈÐ6O*_ƒ ¡Ó>ÑLVý#@ >Ä7b“Þ¼þ]Ð?÷H'7då\ûdQ4{¸(†«-; €Ÿv›"‡zäky¶(¼þK nxô/>úp•¡“Ð=(Ï èèUc yÄVu°í2|lºiøÝiî—OørS@ œ‚´Ø’*B÷ö5ÀŒìûÛ¶õvo>4xµZ;¹è9Üw ­ØÚ-w>Ï#ŠvFgކÞ 8‡FM—Œùì– Z&X‚Õõ$¸:¹Ï†’Í- ¢O[(©JNW‹t QÁ%UäaJsßÛ”fÕ™üùO0T-[ XK;Ñbf`{#6É•ÞóÒ~»xLæ#ÙhܦJ;'-n®„ —s™ÙÔ AˉþïƒÑ²Ž¤ã¬Ÿÿ Þz—Ú•‘ä„ÃPo ˜€í±9¶Üñ§Aó§ÇO‹€U|š4·«ö™òÁùv[úr 0àFdE@MÂ<™Á6ý¢ë¨`À–´¸ÿÀ-¶EZ=ú’më@JË%%× ŽÞ@¶‘ ÷ß3QüÄO–Ò2ñ¼£¾åw3°µzg|ŸDî0^«^c0ºó¬ê™0àpVùVÀF‚wydÖ" Ôȱx.°°À«™LØra«€'í9æ~´•,¹6+TÚ¼ŠÄÚ{O0׿n_xËOÍ€ö?*…h‹…}dIs%}|óŽe'€›s·ï´| ýahÜ_Sø¸QF×’å ÀäR†Áí7-j”Î0èû¥~‚¦ˆ¬ÓC™UY¡<ÏÅïª\è¯Õ¢Y9j_Îá'kŽÈ•Ví&S^(\[oØĉï߸8ÓÚ3TÔ>›¤3 ªÄi™¹®‚ý+*àa]ÂŽîÿjAN@ã Gçç.©šЇh!*J¾Pžµ êH'ÌàdÛBÿ9wk¥ÒˆÊé"A_am Úå*dgÍ[g˜6 ðvãÝ©õ¾‹EÕN ¢©@Õ:Dñ`ô•fM¢ÛgÁñÁñ¯Ú0îÓ§Ûšx£99:¢y¬QáÁyÓWÛp9W…}’”`ªˆ6÷Ž ©è‹ öP‡AûÏ÷ÂF ìB~•ý[]BPîÑÝ(d¶áe¿žñì¦û:ê…¡W™þ™¦[ûVTõŒ„ª»ß; Àɼ„Õ²´Â“êž}´ÿF.¡O6mÀ…V€ç9(&`S8|r’=žO÷D ª#QPmùµ‚azrŒïÀWAfVäˆ"60žÙtÎQ Àœ×•Ã6ýiÚþû1Ø6…UÑ}š²Hûö@°ïß³¬¢Ë¢ëû÷}“=予U¾ÚUÜÆ 7Qáà.ó]à…´Œæ’¨ ó =×r%+ëe¨=g:äVz5f»¢×g» ÓînDÎåÙÔæ ï¿¿J¾BøÜK2úáAp s7ÊŒ\rÍ%Êbs¾&ÑH™Œ¤3€Ó8O›ð; 1V p ûBÝíÉUSŠZÓGsžÉ9ÈôøNôîòO û Ò°É^Ô20¬$§ãÌ”l4>7U ýCˆ¾ä= ’‚Z¨öŸù²6ZN±Ò.5‚& V– ¸?É ò¾i_ɧѱ‰4ÿäaB ¯åÉôÞèªÆå%˜×ä< ~|I$̧JýÑ@ >vW©Ñºn•xIhFP “À¬_‘7³a*·n&Ïзnð¡*`;d39ÎaóT´9˼¥6*L‘Wq5±©NøMà ÀµœŒr’þûûs¯Ñ¿üZås ãY)U¨]Sx‹`@ èóãQÖ Ï¿’ûÏ“r2„®R,'yBî«A=`ö2´q’Ÿk³,ô­KÍäê£ E½“æZÕN '?Lqs§Á&Iè'\¤Ÿî§c8!B•|ŸDw¥°€dô-“xü4Ø‘Go­²Öœ/Œm…8žhçG¤îyx'|~X ÛüëœïÏEzþÎ0º?¸Žý`—gMã(P?Z"U"„€jS÷”±#®¶¨£Z”# Ó2ª¾“kB¹)cU-Ѽp¨M{ú3ú,|i.ZmY¸ËW®Îyj¶|’½Ð/kÓvˆ>´4å LöÆÜçâåÅóåå ØžÜèÞïk•ȸ—ðr½…K*Ù¤U0È«"'?¹èÅ+Ú€¨¡úöC„€ÀDSàÆIq4\5úêå’K("Þ‘¡ä…èÓ±åÚZÿL£@üîr÷”þI}€š@÷y@ñ„\p`Є€Ç:º×²ÂܨÅñ—c,B½Âi¹~"ˆÊo'÷Üyç¿¶K€“‘»u{××Îè> i.çÍC]õÚ?w‰Ù<¢¦Ö_.”“TúCûËÐÃÐK¡„9cÃûÑÅÓõCã‰çÎÔkðbár9¼´œД©¯‚Û̬‚jðªp*½jÎxy¢èæÞ"†T`e™oÃÎýôbÄÞÊl}úï£^Ë5 öNÌʶ˜ÞCíiš§Ÿ¼JSš­µ{=#jEn~ôàFª&ËÑ¿²ÕüYÅF¢Ž€Þ~m÷(v¢Kx]µ!—\O‹þR’çD¥iBaìMûk.O~µËÚEÎØÌ¶AiFŠ[°Åêr@(Gˆòz{ïûb l®òøŒvð#¶¾ -(–ÒXQãª~¡"cD‡cèâÉKèAþ™Î»wa/i;Ò÷ˆ´æ¡çº“FÁÒhJÐ_^<^˵½ëj¼=u›öWÜ#oJC_®N³Pu¤XÝœ.XC—>M+ê"]«7ä%/Z0/öhz¯b÷Ç=ˆÚ6á͈V`}³÷n-ð47B´ýVe1`U±WDü³‘*¤)•‘º?u9îØHe¸Âg»Í”ªo]Å|ÐKR‘€wÜ&}ÑÀºO m¸mp6}þmô“5lþ*–fûöA7À†ÿJg‚xØKrZJvêêåhÁ°iÁë“°ã ÊüÉ )äÖ³Ëpêâ~Úê\£O:n‰@æÏ ؘùêx‰Ö¸ùå#Ýá?çvåý÷­¹ÓÆjô¶¯Kt„žqïLî[25¥¹G)›Ûc¶gÀIôõa•›6Gð#̬ô cfÜ4(ùf·Ë[€ÎwÖè0ÌÞ÷Ö£ÚÿœcxC}²ïÝ“ „˜AÑw«rw®à9ÿUØÆäz?𾺑ðµÀ¬Ç¿g׎0§~Ùà‹²ÉpF#{¶|ãdkyvš]]*Ú¼ b1ݸæÐ1«,§‹ ð¬='æRþ‹mù eù!Tõ¦Âºf`ÉÑ¢×鑟ùk¶[0NŸ&{örä飋¦TÓÅ÷).ü†ÈØ _uGz5Âu&gé„„Ý…š”WuBþ¦h@ŒaxÔ^BÇ!Fcw¦¥Ó„¯Úh£Ìµ‰=ż 3º%fÀ#tË|ÿîŒý e&,}-š“«”uî+Ma†ÊÁhòô]oÓ醓‹µWÙ‰3º4f€&4äuøiÊ4i„4y¢§NCaÀp6MuU˜U¤l?бx~¾°ýdRzŠÁ¥—!‹± ؆sª÷z¡ñ‚fÏe…U-󧤨9‘„@'yL@ èD ‹3å™á/M=JfEßòÒsöXCrøóÐDQr—h`éxòÚ?)Û…Jó0:yÇTú`äç›ûÖÔ4)¡Ë ` t1SÀv}W‰Î,þf‹²Ø›AèOô‹KÁC.ÐÍ(|00škñŒ¡#ÊhÕŒŽÀi$“.Àv´?¸â¯­ ô¨v0¤ùòa<ûæ°8…N T‡hö^õªHÞнd 30 Ïž#PêÍ^êuòÏ%0`8ðѼ¡ ìøW¯èZ8E³É¬bæ  øš[™èWí`Àø÷îe4ï E`À(vW1à£QÉ*_Ø`¬0øh[ÓEÉŽÓÔØ[Þ <µ­÷øF§™‚"0` –@ —iÁz€€cqòfs¸ÿ`üeS«%Ú €“Y¥|1ÁðÞÿF`@àŽ™P€^Àó û!Fý d0Á£û­Ç¡µÌ@Oè¯?®–\|0 'Tû#& >r¯íÞCÔB`Z¹3°8Ú—¼°I¯ÏœÃ õEåûa¨<®r~@w̃¥×(C×=y «e™ÁèÛmMZÒëEï=¹ª?M»Ù«ZõãÝ´JûÓr.ijº1sóׄZ¼qÏ]›ï´cèžÖÀ"0¨ÃC*áÁÜ3 t`‚¾Sf¸ÿ÷0zeka! p¹~ÜwÜ.™2cp.dNRÃÂ(Ј¢6! áLîdí;æøý~C{Q£ö‡âî Àpú$kÖ×EÂ.|0‚–wBtSèØ`'¨43(øÏ=xŽÿt«î`VÍgšÌñoÀÕó½Ù ¼@ãÎXÆÓ¸¹³ÁlÆ÷æÁû˜ÆQ¿ |<²"¦z³]e¯Ê&‚ö?$ªùàF ª%êÚá`øÏ }‚ÝÖ÷ ¨vÛ£ñÙ£|J¥áæEì:öm)*ö„½Éi¦´ê™ÕÉø€EšÆE3€¡‘ÇÑ}蛢Në©pÿÁÜv§kmÀEÀmçS2G~,Ðqøì]E2SkÉ.2>§Ÿ` À¦ Çw&w‘ú~«­u%ß'F)¼ŠÑÐæESÿeI+áÑôÂsKº•Í­`fλ#;Ðþ2Q–jk|>>çýʳlf޼);hŽ$ÜПOЧTçâ â¢5€kP)¢°,Ú€{ˆ Àñã #wî†?8:6.l´?kô·Á{<:À`ÅËzþó¸_¨ãÀ£?ó»49 ¢I”ûù<¨x…fçó#Ê2Ë®Ò<»Â³“CÊŸOÎhò™œN§']ʾ&ÿLîš™=gˆ Ls4ÆLž6“`L–˜=``z?Ëaryžáô¢™¤Ì}²Ó¬´óSÈ~^éiå |#"2@Æ1Æ6=fˆˆ Î>„äp @Ì€—ÝÊÒåÉä0™ç‚, ANr²×Ï‹_‹Š’0/-Îð˜O:ON³äD°LZpÒÖH@€ˆ€8}0ƒ7Î$k‚ò©(ÒôÑiV€@D@ˆHDÓì€0÷ß4“É›')&%™]¡¬Œ4W%„„“/ œÈ0Nòš "Ndp’ç,œ×a"Ó¶™ô2L_>{/Òôþ\ê³l&êg/-jš “ŠfmE0­ MËójÂä8;˜vCN›áä­³gh(#eà3lç@€¢K,êó9ú±u\”ES€ ©/”²Ø\ þqñÒðóõ›#¸˜bn‚°dš¤ff21Ë@5WýÓCÌlÄ´k‘21@¤ ¦pÚ­Ógr0ö´hS1@Ê£nZ Ê*2…%ΰJ³ä“g™N‹;»:m½¹lÂìñéWŒTh¨ìÕ0«ú¬Ì³^šµÛ¬™Jâaz±‹²YŠEë]h(b8Ã-ί”4òT½XÊ,XÀÜ"ú¡£ÜÅ2’¾_°%ó$ "•O^RöåÃ2š‹_™®§\JšW„¨$t@D™ëùA&Z!bÆZæR’e“)§Ù¿ F§ÊsRÊ oPhŽ”©eÀ @@šC çzg‚C9cSP˜™±y%'Řçbf«¦’ùwO 2©uf4ç¦#«NÌY‹gr˜ÃÃÔüå…tnN‹RY}!S™}ŸÕr޲Ìäp;¿‚ï™*Ÿl^Dd¹rä ŸGb)*!|©™— ³wd‚9âÔÊL_‚³¯Iòé­ÙÕœœZ9œ§Ëê±`òVsúÀüCŒ¢È³V­N†òâ¼ %!Ï ”µ@¾ r”®ÄÙr¥Ÿ5M®i3ï ç ’ï¿œ"™ô1Ë9oº ¸P›bréŠÊ{u%5S8Îú6{s×çmUxyY‹æ3- ÈÙó/]  ühÞyxc®UY–´ Ü+)üÍz`¡ƒrÌ+ƒf: 畜a<“‡y³ž™WmÚ,©€¼”Ý/Ý*¥™!¢"M¢šëN•P±!Š5+T^: v¹dƒs2°Ìn¡QÞ¹–XÌl v pÍ=´¬âùæÏc¿hwË™±Ÿs(ÊYIJŒº \ýš¹#óÆ,ª®\6óãbQ±ÔŇYÖ‹¼?×8¹îÊ‹gVŸ9p!‡wœé³¬ æÊmæ¬gyä 5×H¹~™=4ï-œ?˜ï°œÄſŅnBCÔªV%£–Åë®dpÚús¥½¨a\2€Eläg] 9ÑÁ…„%Ã]0As—¯T¾÷±pÙ§d¢gE ²òž TÁMÌ›%m³ §ËMH^òšu± Yäú 9à…Ïž`Y7Ì_\ϼ†˜«ürg§™/Òܽλ þ3ñ¬séæ·ŠÉò¶¢x¹RfŠ0k*"c”ªYÒ¥äÅVÍçH@„yÖ9÷,JMš“Ž¬š¹n˜7X®Õs/Ë+ˆÃ"$.ÂUùfNÆóF…&ôy)<ñ¢,qÞ{¼Ÿ–¼{I^eÇ3äy sf±øôòf˜wqQ¿-}:§'¦îż}'>À¼‡Ê¦{^Ûìnæ^”ììW3¸æ´FÞTa®¯óÌ `Ì ß0yX’„¢ÆÊê_‰…BÎÅ8 êŽåssÙ‚«¾e³çh ¼Û‘+Ê…=Q¸UÔ…¨,8/(<]`åÑZÖ(ÖÜ}‹o³A‚?ÃgÁœå±°¬‰Š®Lî6Î ê…m¹`­fí$æúw‰ÙÈAËm\ íøüÏBº¹RŸ£b.”»“Ôä˲ ›ójæ,x.Œó¹ý‡„–”Ž`›¶±T*ª4HðiÈPOdŸ–d”‹zLƒ¡³!$ÌÂåóâ,4 Æ—c›”Oùwâ<4š…"óï›T‚Š%Ì$qKŃæñJÌ…Yò v!œg1aÊç7}dÞ³Þ \Óå"c‹¯˜ráåŘÓE…*ûR…çsPœã³°¼dà¶i~¹ ò þ1@Ñä-zÙR”„`r=32Yq°^r5©\ô\eE˜„â—~æ7¢”( ÇU+öaШ°8å˜%d$ã 3¯~6(6{Y brZ´éÉ,dŸ‹"™¹&)ËjÉ+)÷Ây„*'ËЙ Ã,W˜6/åp8…þ4ö8‘¥bœ3“NÌ‚¯y‰„|Òù7L{ƒr–^Êe–Ä|`ø‚‘ÿ²ähÂÑec>  Ëé΢¥Î¹8'çåd¥hOF®1Y\zÎ,ç”w–wæsÌž*œf‚´h´JÿB‚¼41ÂÎhx¶ÿ÷R¥Ç¯µ¢7멇àqáqÁ&£Ò+ Cõ…VÀ!fÏ äšK©ru,i¥·[èÙ‚Ó](ã¼W—Ÿ]Pˆb£-¦Àå—‹%Ÿõrî.æ°ŒžN__ÀÛL °q$æ 9@Ln‰2Ó|N› ¼ÌÛ+(þBåÚB}0#-R”o/€r3áTË >æìñìZVh*ä=W23ý›j¥ÿøáÓ^ïƒöcÚ~å+oV¶\r¢jßøÀÅX§å2L4)"™¹ÙÎ,úÌUà4-Ñâ4‚KšOȩϹXBR Ì)O±ž7'ªPÂåŸ Zž[‘·åËVj„"ÇÉUi9ùr[ä_Yb<Ë4Hùr¹N 敃9ÞK±ÿiÜ®@à1Ÿ¬€þ"jóªk²ÁÂyš‚µ…ãÅÓ%Zfþ–¬åŠ*o¤³¬² )ÑXA÷<þÙ{—/{µ¯l®Ü~õZ›ÃP|òÁ½žô÷RB¤f>¥aò¸`Œ(¥„Z©¬Û‘X(.-tí \` ˆ™ƒãB,cM9(f3.(Iñù„aŸóF[–×”ÁÌÙä3©,䞇©l%~™>¸˜ù`Q¥—à^²KغÈÛˆÒ½9è P.$+Òû%"’/ oc!nP4!ó,KE¡~sˆÅó‹5!G¨è´;мvãûû¼ÝoT¹éÅ«ß}é+W?üà¿ý½?ék”"Ö”kVTZÛ¶MÆ0ÆLq–Í>³YóE9ØMÒgl©äbÞäx6æéÍ–²êÍßU|Þ…Y.92?±½E䞘½óo_òþœ?Q¯RÒÅÓ%º\åç5õÒ( óoÔø‚ÈøTþÑîsAÿì¥y;1&+…Asû¹ËcvYš2ÅLòsÿ—ØZé—ý?’82–ÄGwßß¹q[økû÷Ïûá8žžÊK¯¾}óÆ¥ÎÓûg½P >î–Ëß²,2ÆåÏÔÖ#dóhŠZ6—hé“Å®,ö+LùQ6UhòÜ¢fžæG¥\óÆ$‡ó¬ts#•“DšÞ \j,D––¸EKh-,$Yr¿ Âq6l„ˆ,?ý¹xœ%ƒÂ(S^KÏÁßúúÛ‹€.ˆLF‡ Q¼Hæ%Îñ¨¹€`¡0å"Î뾨øçNøÜ#Ÿ6' ¹Q¶üÅùÍ"޲cž'ƒN¥±Fiâ{>)% "²ñéqŽ×®¾xëÕÛÁñ£gÇ}âVV>ÆÐ÷m;Žã T|E)"[€âRÚKEÈ/Àéâ-¨Ì…/ ÀüBA°pfÑ™,ÍDÿg¦…JYÂ2 ¸°&eä—P0¿›y&¬ø'÷ÉGaò::Âd.ÐɾrW³[%©\‚þB0'Gë!?j—Mh(vòÊ?™ƒ7{$ƒñ»É°¬R2(cá+oÎu\#(¶BžnÌy‘Zø1W,Kuv„Å‹˜‡ù‚%˜‹2¯qŒ\Î…1Rk‰ÄQsR'Ô)#ÓØÞ!õµñÉøÙÏ~ÔÚ|á¯þùoú,˜¬õBÆX’$–eOB}ÙJÖ¥˜Èî CùÂÌéœe7ž§F3™¢Lþ ÓtqîôÎ,!Mkd̺…W=Ø­9gãÁ€8g³æ+¢°Ô'%\–¡[2j …/IÕ…B¶t<KŸ.Kø[ßüZAGgaÕ9{™êx‚l¨6Ÿ2Ï—2ª–c?{+äñ›#þ9IαXü’ÈU=|,ßË‘¦|s#Ñù‘“4Žl Š#«†W©"€ 5#’Uo´¼F[uºL$;·_ÃàìÎÃC."BdZ+ÇvŒÑišNGØç(u=ç¬Ü½¸xý¹ ` ³U¿³Ë'ز¤JUve""§ãÓhÌ–0/ûÉ–‹£Øœ‰°m×õ”ÑJéi1JbUŠ‹Ò‚‰*iô‹ÌÆJ–ÉÔudˆ|âOþ/D‚J 9ž‹0C9Ž‹èϳÄÊZ4g Y•sÈß+i‘âqQÇ´ä`y%jù¡…&žÌxÉÜU¦“ÒHÉ׆â]ßm¶-ÇU†¢^»¾¶¡p«Þ¹ûÀ¯×¾ó½ïìŸýøñˆ9‘À(N\× ‚@01ƒïd°çs=ÑTŠ3`2TCñ ô%»g…Jé,Í<)¢ÑÆqìlÌnb8€'¹VF#8¤Öøè–=Ü‘ºŸúJÕ…ã&J‚q𦓠åJ˜ƒh^ä07xW ôcn‘ä4!–ùò.Èeâ’”ô!,Øœ­È¼È[rX.*ç¹D-¼(9ÕÏ{ºyeúç…Ì;uóbe)2ÔÏÂD6UI€'#¨D ©f†,Æ€ šÓDG k^¹¾þ•oÈÕpÐõۭʥ뢲ݽû)ý—ÿÒ/íT­4LÖU†¡e[…ÁÿìÍ/RiÔ9)KÅrò¾È²(7cŠ›Bƒc¤”Å #À ê ½¨ëgÛñÉ­dïuxr”Ð_ï”éb¾¤ùÝôlÎuf´+3’9ÖT5˜¿'`Æ™dÿœÅ‰H•+¸d À)Pq‚dj«›~{=ŠÆ¶ëÕÖw@ëôìYýú‹u¡>¹ó(á.HÓÔq"H’‘a‰¼Sð4›Ÿ@‡‹Üƒç[šÑ)­3ý0õĉÑqìÑhŒˆ“.¤I=îÞj¨W/W¾þêÖW^Þ|ýFCt±x¤¹Ä×FbdÐÀlÌ-?ž°Ä¹(Õˆ®\—9> Hž³¦¼5Ÿ›ŒøÌʃ,iŸSôüpm–±X@hq àæ‚9TN\"úX¬[^˜Jyâ²ð§(ËÈT¡åŠ ›ÓH…[”í–CIĈ@D0@Új¤p§±q:èÅýóáé¡·ºUݽ JGIbÕvDáÓG/¿ýo<Üûíιã@†žïGCÆyn°hþn"Ì&J§Jcc¥1Ú¥øÇòf#4Ÿ2V S` Q­Vô`I^lÓ÷¿òÆí7^¯o^µªkdÙ®_éø¯?þûÿûã36H0ÔFÏ×DgD/û`®Tó‰Ý¸°>bI—eügaÂÓœF-JËÜ—Ë«ÓYÉ Sôþ@5eõ…œ|Ε_TòEOc)éϵœo!Å‚9œü+Ðý,ͲÉ,‹ÇÓÒt@“ Mh²ïXªŒä Æ@k£4âh$`ÒïžÇÁðì°}íÖ›h­ðð„‚ðû¿òË÷ÿÁƒ¡–‚GAT­Vceö‘íûÈÓZ/+ðs<ÜÜŒž¹Û¹\J2£u‰ÿcêµêx4Ö©âœ%I¼Æ¢_þæ¯|íM«¹™{4ªÛ5„~3½üÖ¿wåäÉÁ?ùõNÈÇs$ £Â~ 8ÏN°°ýˬ{©´NsîÓÜ„^رós,߯œÚ_&6{ÕN• 'cAš–Püâţ\(Ö̹sûË_ÿÆÏþàÓ>ãVŒüJ%Ʋ÷ÐÔ&1D¥RÈà¹0¥,{dNwh~mv{î=.c,¯¡•RÕJ…” Çc.˜z_¾TùÅ_xÛm¬Žl§æ  ‰2@‚È ‘ÁQ§ßÜXÛí±:.Ð@1Ê™ë–%”?w˜Sÿù`Ð\ëg6e³4ƒÓR{³È™‹ ²œz©‘§HyxfšžæP\j–„ü`~a!@Eô— ¹w-ˆzñˆ e Ü¶ Ù¼Ä%ÞÕälÆ# Zq&@`ªˆéT0 ÈRÜpÅD>y8>>¡þöîÊÍ—ëZNE‰cÓðÄœ¼óË¿ttøßÜéë$Ššà ÈL<"â3f.Æ©‚MÎþ. ú\ÌæÎèܘP6Ãh3›ÒÊs=ÉX¯ßå kQï»/µ¿üÕ¯ ·6‘¦9C†‚! (J¥Qô:°»–Ziuyëbèr§s“exÎp -¿”ÿÊËÁbW^ÌpscµóÙ<|òùåÖäô|&ÙŽ“K¶°œ}þ Œõâ¤Ñ¹°E¡t³ø§\ÅEˆOÒæEá¢ÖŸµ8‚QZ' s}fKEH‰†"¤œI†Œ#O*Z%á¸wxè¬my«ë;7oÚŽA…ññ“»µ—¾ü+¿øöè_ü郱Ñqâ¹Þh4à\LvBv#0 YEˆ~’pÄ0›5‘вMªsepfÇK¥…Á(Æ‘!dhŒB:Rö;gÜ$íäìW¾|ùÖ›_ MÅÄ\HG0!¥-¤Å'­TEIJqŽûÚ^_òd%³Åî´ ðçkù—u@Ñ”aŸs¢³Ý—  ¤õ‹Ý»è¶.™w Šø,jú"ó™O7(y¸Gx²Ó¹9œ­¸¦lP–U`Ö %8Ÿ¸8B´¨"faœÎ@`` €Üv˜í&£1E¶Ûr¶v„cs)ç$¥]kÛÍu»Ñ’õZ÷ôôñ½‡×^º…(ú#Sƒ8xüÉ¥/½ñ§gçü± ƾëªH¦´VDuÎ’ƒ+!FÃ#e&{ÀÌ0Oåq*HÅfò C–j ¹¨:2BXÆh„ x»V;?=`qÿ ÷ík—_}sLU†žíúÒ¶ )m’ˆ%F†(Á£±¤dò,Tçƒ1³¬i‡æ—òöE~T¤>Åeø¥­! I—ôØÂ­2=¸àF&4x±ˆˆ<þÿæD&¯¿<€¹0d) ^,IŽ£àru²ÌÀC[KÒf —õE9p4ÙëÀ@È8riy•¨Û‹ãĤêô¬c¸ÓØÞtj5»¹â5›$l^oi![»/¿atzvrº²º'85á¡Ó^ûÒw¿zºðÓG}ÏqRÉ[@†ãØÐºÍyÍæ†O$ >“AMpM“Y¤”éø©¦§ÌLÊ;™llΔ¦„H Sd(g"„àãaˆŒ¥Ú­V:Éþñu/|ç›o®½ðrd*Œ¹’Û ´fBƨ5#Ö¤]Áh2¤AHÇ6 çf gû@Àl.Ѽ‰´}‘¾ã¼Û³yËÕ‘ -ÁþCK¾K•é|*Ä’¨NŽèä PÞÅh)äo/0Ÿ’:/OúÃYM tc~’§YTÈ'ïÏn\¤Î\È™9!ÎøÄ6H×GËQQˆ†â09~ôðøÉc»µR»rÝ_ßô[«ufUZmdb08Ž]_[ =ÔFF1xü rûÕïüÊWÓ¿ÿ›}ÔͺçEãÄPŒ¸îß³ë6H[¾ÐÇcõ¬  qÁ@¢&h(K¦‘œé?$@D h1ªH~žF‘¥™7@$„à€Z+ S©V˜Òêøþ›­ø_{³~õÕªR8œIdBØ6‚!X\rËbã±õÓxl‚QŒ½8‰S£™Qi¥æŸà¦2›»[mÛ¤e¨^Q–N×/Çÿ P‚étiÃQbÛäñV¦ù C\î_X1–ÇùÂ|Ó\º‚FŸZÚ%#%o ù¢hÎÍ&B>l‚Ål§Vxâ"dŒJ!mfÙ¢!D)ÝVûÒko®¼ð¢]¯sËf\jêD$œÙÃÁ8âX©øHF‘ôz–/ØÞÿ…_ÿÎå?ªÜ5¤HiHIoVìvEH9‰„|A»"žjm!Y‰fƒ8i{Î(I ¢ÅùidF‰6‚D¨#­VlM4 @r •žÕÀPÅvÒ$ÒRØ é¨½÷¾´®¿úµo¸k׬ î nsé0Á'k€c’ .ˆ Š@[H‚iÛišŒ‚0Qª Ê)zD0E=}¤—RœlFPy«¡EZšÍ…bï_\š°äàÜ Î™ŠEòQÖÊE‡µ0R°ð®’é)³©\î3¹,ŠaÁáB6¢º—qá×ê«»uðÑ»8èWlЇ§›Ð”þƒÚ˯·ßx­ÿɽÄyvàÕWP0Æ̘H«3‡Ù¯oÕ r†` KB‚”àªHÀ²Âˆ"¬T,æžèaLx¦ma¾¾e]_a*Ži2| mŒö9oTÜpcã\pÉ„`Lpi1ÎLƽEcD0ãõɲmaÙǧQ;–$˜Ï\*}ŠT}‰ŠÉè T?,HÔÜ9Èg‘ƒbÙë-‘Ìfϳ@æEñ^žÍ]Ø9H‹ãdË'-á.…LK"W’±%@Ù¾à-‹NÌv*ƒrX4Û8Š“¶‰Bé:Áá VVVÝfÃö½ööNcmÓm6ýzÓö<˱¥mm&Ë‘†Ýóƒ;ïwî}<îÜ}ÿÖ—ßj\¾´½Ù9 ãÑÈ¥‘²šéð˜)D¡ë×’0 ;ƒƒ{gGO‚ö‹íµæeÇñý;w{¡ß\ƒCB.•ÖOï¼×ÞÜ%¨ZÿD†»ÚÖÈ„Cu.{a¢vœ ›™óÎé{øòË×_xýVcs7%ËrÚܪ §*Ý ’IQM)2&,aYÜuÅÆnÚ=NûçÂõµYaÃsæûFXŸ=ÓJ)†Zë¼ë›kÂòv†ÅÏ™2‡éñ93‚%Ó¾q±aÚ·%÷·ràçñ"懅 æh „ ˆ+¸ÌG°œAÁŽ ºÜŸ›}ÌÍJyÁ…-½ÀQ§–Òq’>9ßkÔk­•õë7ê›Û~½ž$1 iùU´â2ÑÆZ©Ñpú++W¾ýNúå/÷ï ;Ï,®ê .*¾4 Ó‘IÒ(ÑjÐ3þ*gÔí `<ŠM”ôÏÇqhy—^x™·w€(Øï¯ƒ F:Q•v»¾sãèÞû~úÃËo¼~~røéß[kÃʦ¯ã1s<äÒu¢íZOŽ:ƒÎÀø­£Ç{_ÿú«—_{ÙjìÄ18~‹IWX6çDÆøø‚sHBÕï¨(`©BDnYf`^0°ô=+dÞl"pÆT0zôчû÷>e×/_f¶'©!0ZºÞñÁ³Ï>ùãßøçªûÌÆÑ‹·¯¾ý/ߺ}#Š‚êÊz÷NþXŃpxnuúèþÁ§?CÜ»w_7®½ÿãÓÚ‡"jt:ñÙÙøÑÓè,bÚ¶‡csððÞùѳ8ˆÜµMVm©4厭ÍíQ·ß=:uê--8øíÊ¥€‹4N€s dˆˆ[¯¯s),L¸íH×ód*b:!1f„@!ˆÈ0É]_¶VK/¸¯}­þ­_²/ßd@œ3Ë÷ýZCE«Vº¡þé-ÚR˜÷A Ë‘~Q",(-\ªÈæ¨[Š´2AÏ]œ-È*x Kˆôü’(1§þKË‚K¼h)㟰%ì%KIa»€ÔÌó®.->4; Òy±Ûò¬,4ù–Œ=z0Ü{|ð̲dõÆë_Þ½yë³÷~¦ÃÙ¼¶Ò`’»ÕJð[¿õί|g|öhmsk½–ÊЄ燎ß O“ÁQÜ?1,á<½÷©U9m\zYPþî³ý3æ¬vFjœ8Gg§G= .ß\fôáÂl¯ÖZžOìZýøá^ÆQ“r¿Rݺ (‰Yv½hÑ9´n$:‰Hi`d8a™à˜ Ò­p{üàÇw½ŸRÛ\Ùxá5«ÍaÿýTÁÙáùfkcíÖ«õ›_a™$TãÁx˜öFÁæKCîú(-ІlßÄ#=8í[Vr"LDÂã2Á£8Ô±ª¡°#Î9—6‘3R0R'{ñãÏ$“Nų][r–;ÈÕÖüþ%qê9¶1f7›øK Ñ åÀœGí‹sß ýðyùÀ)g:´ Z0ÿE,ãaAÙÝÜÅŒ²—, .fZ`á·8…%Ç&€|©°Xó ZœŠKÀ_lV¨ãpÜ=Ü•ÖËßùÅÕK»\:+ÛÛ{ÇýîÍWº›ÆaØÝØÙ †ýG÷5*ÜÆtÐ=G•º’ƃÇÇkíÔvouޏe¬úêÙ“û$+þöÊÚå³îQ«½9 Öé‚Pm½°µùÂåÕ­ÆÉ£ã»?9Þ~ñÉÚ¥[)cöÚåq̹ãÅ1pÇYÙ~ÁY¿¤ulF§ƒ‡Ÿp Îº½úÖ¦V†@øI÷‘tW08ƒàý&“Ú0d–cY¾K¥d¢±J’T¢àÒá¶#+u{c×Þºª;Çêä¡íHž$QïÜ}éµgãøOò3!xyBd“«2‹;YåÎÓÏ&\an/Œ¬õËš¿`,.² ØÁe°XôæèÎ/©ÎÏÊû¾y—§l–àµTB–-ú]Šñ|ݨ$ÙálâÃ´Ý £‚¹i=Ï߬ Ø¡Ódˆ:ML ‹cµºqëE·VëŽÃuλϞî}÷åï¦FºÝÃýgkkí0è…Ý¡g•$Ñx,šµÄ0LLrôHnÝø’Ö[ ŸÄapvð(µõë¯ÿü§=k<6¼ÍyìU]é{­•Ÿ<ý´³ÿàèÆ›ÃV­„Ç£QX÷ìNwtC:Ín4>Ü{÷ðƒŸ­î¬%ñi†AŽ­¥Œ!Ò5g÷ÐqM:&«B–+%K•$Ë0aù+Ü«3n›T¥K¨€RæÖÄ%W„cÓ=IOŸ&`j/|ù'?þñþÁ‘ç¸dôL‘”§hfÛº,Eèâé²äËByãQȪ~ÿC[¦Ùªž>%•Œ†½A¨”V¢TJàÌ€PQ|üèáØ›/¼V[ßm]z‘{ŽpøõW^ÇŠ€×OM"9^Ím¬ÞüÊ—˜‹ÇO{: µ2Ý£§FÑáQj6Œ½ÆªIâñ£?ü—ÿ„ùÛßÞ|q] DMhÛ)¯šQÇ®më˜•:›ŸÚú̦‘Ð}ACI=Ø >ŽQÐÃ4d:²¥ôªuéW]I¼¦Ú¸œTëòÊíÀ­þîïý~y(´ÕÀ4 pÌ»—6ïÅ=±Ä-.êèÒÓyv±Ð}…¤ÏóòМE®f?’‡ 2Uâ6Ï5;È=ô< ælÑ¿„ Í.Nø/{ ùùý%3°P‚ ¶ B.%B´Ö׌Qd“ÄÑxŽÇ^Åw|ïÃ?]Y«x®Œƒ¡`0õ9QqÎgŠ 5ÆåŽÖiª±áUFGŸù­­¯éW½æÚê:?½ß9xìÙl@ 8chù«×®‹Š‚pÐ úáÙYxÜ3ßøúŽ­âX÷ÿþï|zþ ÿÓ_n}í—¢ñà臿;îìÕÛIjl)!°hùuá®YÉPBʤ Z1@K¦4¤ã$ Ò0&É<Ë©pÛOí#æT¬FËj´H =LÌÎËÍ×^ÿÉgwöó<×5ÓÁÙÙÒÓõh°¨·³x>ýøüâB±cF…´Ó…cZú=¥H3y~zZ­ÕÃQŸ3aÒÈhí¹’ ô;këõ8 z^³Q9=M,Ë"•6ƶ­0­­ïpk]r\ÝÞ5Z=;Ø»¿â÷[Wo¯_yª»ROI“´µ" ¹°+i¢˜åX®µ~©ÑXßÜ¿óÑ /­þáO:Œ³$Ö§gƒ°wrøèÈÞh¬¼õÖVAЪ±¹Æ™Db›ÛÒñ°÷w,bà!ñ¤@ÈmB¸¥iÌPºÕ&CHúñÓ *$B0[²zÍzëUhmüèßþýÑ(üÑŸüðêÕ+¯¾öÚ'}¤”B¼@,¸«yZ“haVh1ÇBÒå‹gò½¸àÌuï\t6}T,¤Z2U!Ù)èg“@–…zð9è/𡹳…À¾Àµf3>§?µMKÒ->[Žä£ÆhÄ”„Á°¯ OÏO«5ßw=4 •ñ<ÌriÙ¨´ZñÃ0\_o´Ûu)ÚÒq]¥Ré:Mß3ŒKË©4Ún­ÎýÊå7¨ýÓ}ðÓþ»ÿæöw ‚¶<Ÿ1ÆZ¸ŽíWÒ$ÕqÄ"Ò«×6i»V±œK§¿ýÔq¥)ˆ†!pؾ}ÍYÙ&é#S¤Î,èíC5¨\~ðX®^×q˜ÆH‡S@ A&EäŽSA0z8ê<Úç1rÂbÀJÅÚ–}ãePÁg€ZmoïÑ ×k¯¬"gÏÙÞ²Á œîš4.Ì¡² îø\§õ‹}íHŽOOñ‰=5[ŸÁoΛ‹Ë!^VœåVLVô’/.x¦ìË„ æο«`)¾Xcòš9bˆL{áp@šl!ã`„:q¥†.oW*ފѤ EiâW\•Xd¨V«@šÄ~µª‰Èn¬··¯º–åVão~·½vëƒýÎáÃOmÏF&RÀИA”ŽFAÿì)å*év»+í0¶víÖwNŒqÉV.mÕš²Ó´w׿ú†c‚ÈP<àÁ zü^ýKßpw_7¢ÂšW¨{ÏîXf,¸(„Q)2dŒÁ,R™tpÖw}ÏY©PÊxʹ¿&_ÿ ‘ÂJKxNzr‡ëÈE†y®;Â`Ì9'Êˉ岠ÃL(žß/‹±´+tÙüâŪ/_’ÒýÜ•ùH0ÂD·–—Ϧ²MöSP‹6¢à\d.²Z 4*»\šÔ¼Øˆ¥ú–FègB@d4)…Ja%ýÎðdӠѨ­µj–àWÖ<‡¡i¶ê~¥DIœšD%¹!ƒŒ†#ÏóOO;ÕF³ÒZµªm<6šAOvÞWbu{ÍmŒÃÞiß0æÖ¼¤;ŒwcóLÈÒ$UÚ­xL8–_.ÝfÅ C°²³B ízÕª· 2 ¸ž Ò«±‰ÕYÙ1aOﲤCˆ¤ ¥n1)”FÐZH¤ÒÔoÕ¤¨©Øèn £ÊÖ*ß¾Ž?9xÜùõÿk}»UwådûR œå~€#¿i¸Ìô|DOûxÑBÌl¹ë¿ÅÏ)Ù<×Ï}Dñ4ƒ&›=O³)8%5Ÿ'n ³–¼îç¼ìi-©Öò_Bùᚬ+ È“¦:Óðì`t¸·V‘k—o:’Õ«U[0‡S³Yµ-QkT…m¯Y®ÒfFJQÆÀÐrÝpV…eŒvOûÃÁêîU+õÌà‚ŒÑI’(Ÿ•r–†ã$Õ—o]î î¸5ç¬38=ë™4À4ÑI¼«7„tÆã¨ººáפ_³]×B”ns½wt c’1Kd.Œñx쬬áÊu^YCuããñè>r·Ê¶‘œ SJ‰1@N–´ì4J!¥¸3”àêó#}ðXÜ|Yé@uŸòݦÅÙT bêKðX²Íø³Kщó¹CÊ\ò–/x ð…‹½]Z‘†KÞ\†©:,aDbŽø²?§éKå'ó‚ó„§\‚á(®èT¶«™Å£x:Ÿ=Û‘nɧlæÝ¸ÕÅî›M©x®Í€2Ò‰–½ C×sže¼— Átc¬r\1Kº4ÄC9(—\åbh*£…àê<%•J3¡[…]ãh1ÐO8™ÆC´lAÞEÔt2 ɘ(4Á ?s\›y] µ6»ºòìøéIg Ï6¥.þ€ÁÜ^JÛKÎ-7 ed^0ó­ð«²Ÿì~^î 3y²ïydQä˜L^r‚P6KèÐ’]­ ŸEQˆ.Š|þYjJ‹/š½–™‚ ¢$Qá8ê;Ç< I@ª$Nã8âL8Ž%8ëôGnlƒØœlNÃN¿Æcmsô<É=‡,ic2±¡TÁéI¦¼KÎ'ǧ—w›­š£‰‚N'0J5¯¼úöOøƒý0!)ŽŸÛí6“Ìr} g©&C,ŠcBàR:¾ïW|¿Ö¶%¤¥¢}åfëÖ;¡q÷-oW±¤ûøÁù½;a·#-V_©¯^Ý©n_ç­-â>ãº6!7±‹ÉLj¢m„AE&‰,_¢kÉŠE7D!‚ÛÿäÞÓóa È•Ös´/þÆëEX3…–^šˆ.~z¡³ðóâ§‹yƒ’ÏI—}#Àt_ ÙÕ%³"à9žö²ÝßrYãÉ#ÖYêh”CsÛT›ÅHqþ÷ÕhûD­!‰“Q?õY'2&MµÑ”&‘à¬V¯Ø®TA¬“8…q˜ÄcŒ)‰‰R#@ H ˆA4+×ÞxëÁûŸ­®ù[[í0Œ¨×‹žõÇæöÎÿà?ýßíþÆ?þG÷ÿv¢t£7\¶_'dL:Jk¥x'£Ñ8U†2!,Ûõ+5éq¦ϵÜ[ߪ®¿’vNÒ¢ÎùÞëîoýÞé³£4NmF—o¬9·¿uýæ¯ýM¼þ Æ©!zÌY%0F…̤èX0ÐF¥c#ÑZ÷iDÃ}·¶ƒ#–ðt0~|÷Ó;höÛ™¹&§‹Ô}É}+£uj£ Ï/»¶ä™¢ÎÏÖÓL_ZÂJ>eØ3b_ÐàÅhÎä$?´ˆül:tîêRêR¾TfüÏQÿ…|Ÿá\ò¢ù³) šmêgˆ´†T™4!JÒ4¦$QDF•j°,†ñ8J¨Ñ ‰b6™81;ë••ºíJ¤ƒ0‰’Ô÷ìÖ¥íú¥[g=LÔøÚµU¿^Ž5 âÁp„L 9ì÷~ùßûž=û7ÿÍ?>’ñ0¬­6"'CÆh4‘V­Ùv*Uá8žï¨ñ‰³ýbåú·É’²UÜðG¿û£_ÿ—÷Î’Ã"øeã~­'+¿ÿ¿ðJ~ã/"Úˆ`¬ÕLJZ2n;ˆÌ’Ž>9<æLðæ5hÖ¹àÉÁ£÷ð£Ã³#ù8QPb?³_&"œ™„ •s1Ðó¹Z<¿îÒé¤x!ZÄ^ùÃìOÉ=žÏ0Îá,Û®°`a/·Üc m.ɇâb;PÆôüüy. -Ã\Ÿä‡¾¡!ÚhEišFá¨wC0† ÅJ©T ¥¡cKdz³0ˆFƒd4¢Qb¸à•ª%%¾üÊöÖîJµY7†ˆ ‡lüv õ?ø÷7ë›mËöt·/„šDc– ßø¯ÿ‹JkcxòL2Åép8ÞЊIi{†Ìµ-£SKr†È-‹<¿½±mW*Òµ‘ªÞæëÒ[5F1iw>ûà_ýú¿xohWd}¥Ú–<ÓOzï~ÿÍWwWŸ¦Ç¿þvÿÇ/›úmTœ1 dLŒ CÉ1­$–¨_Û6I 0ÛFP§ŸþôÃ÷?F‰Ñ)Âò˜N1 ³<ó\ɸ8ø9¿>õàŠ»ƒ^‘ '(-»VÐè¥T¢¸ùÏüïu‡r2\v¯@~¾­Ç .Röœ¶,.IM_ 3`ˆÈyJƤ‰‰£dg°ðX!ÁäþsÚ¦¤òçnïEŠþD„@ €# Nâ~gÔ=K£D+£51Ƙ(%çœaSš 1(D½Q]]©·ÛU“†ãÁ0 G:õÎÎ)ia áz^}½½µ#•þ½ßü·ííöõ›»ãÑ(MôÎΚïIÒ:öƒQôìd%©Zuío¼ùâZË;ò©.אַ¶ëÛ;Û®k{žÓ\ßôÖw…WaL3fsçŠbÈþëÿDZޫ_}©Ó bM?úñý0 ×o¾øþÒ_Þïö_¸õâ—¿ñíÿêÿ·ßüúwiïO*+žÑO(èPwÓĤfœʘú³hÐbxèÂ!ª¥W‹Û,¨E|îùDf6ùŸ!ËÎqvŒùTexvÉâ_{ç%å Ÿ—”hOàP~iv\€þEbŠÅ43×þùS@Dƨ$2q÷Îç'i§ÊMÀZ’sΈ To Ç‘!@Á¨Qå’ɱ٪§I28¤I *8yòxÔ= Î÷“X–¬m¾påÖí)ÿõ?ÿ7šáå«[•ª´mpm¡“°×Þ{|ÚF¾í½¼½eÇÁ‹—MO<¸¿Zw¯]Þh6ª¾-<×­µ›^³aùáÖÐtÙ»È"þçÿçÿleǽýúÍT§QŠ?y÷~E~Åݾ´k€¥ ®½xãÙÁþê­¯9½ã£ßûGÍš‘i“!û\,™qu AØLºÀÀ0b¶]ñÖ*ÜN¥154ŽÓX“íÎ?ÿ±¾²wó«93W•˸Tü° éË:õB˜²?¹ %Èm:>q‚Ë»e-ßË$uÂ%érèž?¸ÔaÎ*„‹‡K Â=±¸[÷,eÉ›£k£áèøhÔï­•&£±iÛimˆ Žt¤‚R†Pñ­FÓ±öÏ:ýÓóVÝÃÄh†#r8¨ðh|tØ{ül÷ñÕ=YûÒ/ýÚÿürí•›ÿðïÿ£wÿàS§BQƒø¬;>ïD®ôv/¯oWäË[[?øáqçñÝylÂèêåK›+M·â3·[Ç4:‡jÈgÂV½=æw8_?Û?¬¯ì ë¼^ó…Å[íZ³áõ»Ý+;µ‡ïý«{?.ëo|çWW.]9=?zãK¿ø£ûOþèï®ï´ëkžd»Â®H)¹Ý^Ûב]"étФu8`.cž_¹¼ýðö;ã£.3`ñØ@¬´ÉzéóC󋣸Hc—ñ|,LZôJ`ø³ ”÷²Êq.Dà_ûÞ;¹ðO½”®cª3þ²èù–£B¥Qža®¶3×wÉÌÓbý/P“”§>†ˆˆŒ!­P¥‚´Ði<8vÎÒH!2ä¹`ˆ˜&z4Ò£”&T«V­æyžÍ‚Qh4 ÎT¢J×u´6Žc[RD½Q´ÿɸɇ&8¿úÊ—~ù×þÖ_úòJ}­]i]Zßyó•×ÿÂ/ý¹¿üËï4udw<Æ?||Ð?êIß¾µ•ÆI8µšÕÖJ­R÷¿Ê…DЏf¹tþPwî2îœ?ýÆoÿôðð“¿ú×ÿÜÙÙ9€‡Úö7ÿÖ;+µt0Lû‡µÆÆGø“+¾ñ‹ß¾üÕW‡G{Ÿüà³GôŽôOuûOÏ’Þ)¤]¡#áVЩ!ib pÂEËA•¨aÐF©AάX©DkC¹»Ë‚CEáøüøgÑÿÅ?ØÓm§&€•,[$@™iÈј2Y€â|ÎÛÁîs6eáßPÊ·Ÿ*©X奧XP* "™‚…™)#`:‰‚îyÿxxzôF"à…`ŒÖ4›H:WV¼FÓ‘…ä¶%(“Z£ÖhUQµU©µ*Q–eK[é “ž|Ö»ûƒáþÍÍö—¾ùo|ï¿ñï¾õö[7n\Qçÿè·džwúÏz2|ç«WŽ»O÷;ŒÌÚjmûò.&e:¦q¢ã)czüÓßHï¼ë9~ûÖ?û§ÿòÖËo¾ýæþ³ýÕõÖK¯_µlHµ¢„¶·vOz½ŸýóõkßÿÆê—ÞÍËÛ/íl\­85íÖ¹íc«hˆé ±`ìU%÷`»hÙ S3! ókdW¥%]R%a¤‡± £§~À”ÿä¢?å @´ÐMåPÑs‚Û³„ÓŠ9;¿@òÜJr0ç?ÅéÇP .‡J Ó.TÄY˜)ö‹"ùXNXÜÿ„r·/šì°Ðpœs†LiͧÃé\:"C*‰’`lTj[‚3dHR""2ÎašRèQˆÌ«Hª8¶t}ÙhÕÛæœixi,Á¤Z‡ÊpÆÏMPó*a€pXª·7ÔyøäwîWÕ•ÝëÅÙ“ÿà÷œtìVe/L,C7·êãDýþÏOÖ|¹Ö¨èDÏ;Õv  í¥½'Õ«WðøÝŸ¨;§áoþo.ý½øŸý_þ“úJûÊõ«bÿ0%ÄÆu­íµaOÿãÿòÿõ«×®^{û+Ä…J#òvW¿ö««¯~Iõ££§§Ÿ||øÉÃ~wÌb·y¯ÑÚæ·% *&I€Ss×Ûî¥a:Lq˜˜XkÀ4š}šûcyÕ™S Ïô*`2Û^13¸$ß* ËG–€ý{ï,ø»ËÞ¼ˆàÊ“?™mÝP ÷‹›éNNrn1*D³ÔEB‰MHDÆdLê“5¦F)ι#EÔ;;}ú( B¥ U”¦&Më ¢8%CT­TêuÏ­Øž+m›ãhIÉK“(I2œ1Á™àL: "ÇI”Œ‰ëq×%Jzqgo¸çüñ0Øhú£q|ïÑ U÷¤ÅîìwzéŠË¯l¯\º~µRoJËcÜcÂcô‚gËŠ…J=ù£÷Çǧ¤o¿óÖ›¯¾ôÞO>7jnÅu-ÃóîÝ÷ïÿÓÿ÷¿üÊÖÎü¿ý_W^x íc4@%ܱíj­ÒnºU¯ßé{Ãcµ&V.·!I( H§HzÒš:ŒONúŽzÝ0M ´™Ž§—‡ç¾öÏü_(„G—Cõ |rÝ?õƒanòÁ–7 °èœá0/ùÿ XÏÞ?TXæ2,H¬%Êù`®:%!)7cý¹Â. ¡ 49çÓIÚ­™’‘?ø4ìž%‘J"ƒD¨Ä1$ 4VZG¡kA«êT+¶e1Û±,!…ŽïùU_HŒÂ±NÒ$ÑIj¥;çA¤X§ƒ~hãdYVsETVsÜ6x«vkÕ¯Vöî?;9ìo^nWZöy?>éF¤ÍZ£úâKoÜxí[õõÌ^Õmáms¯-IGOßg2нG'݇–7rkÖWþÜ[ïü•†UôÎî>9»8|:¬¤îßþã?øÿþîU4! Ÿ%G†OþD|¬;LßDÎ…¤ Z<­Ô}{óVÖH§&#L~!w@JÔ+>LQ3³ üÚhÊÏ›S¡Ï,n‚•Å} ²ìé’ïšÑ›2Zâä}€ÅHÿ4Ë<𛉠 f|eqÚÒ#‹¯‹i'p8Ývl²ªbÙ,‡ey"@~31šVˆ”Ò€À‘I!‰E`H«8ŸìŸ1“0DcH)H5ic&ñqœ,ë»Vw,¡Jã,ÛÂBiY¶ãVüþi7%£QŒ#Áe¢Y£â$ñ(< Á­¬UW#ѰܖðÛ\:–Í`Ðýø‡ï GÉK_¹ÞâLJ}4ùiˆ1>‹Ÿ½ÿÔliÏó«ë>[«Ê›¢¾-ïþ`øÑ'îÆjc§iÆ ¨tüé°ñ{ÎkÛÛ/sëÅͰ*ÄT‡£þ³?ÿìžî§§÷“îÅ#ÛáNÕwüŠô«Œ úÊÑ aÒ8<õºØÜf¶ ±C*BÎAZFÚÈwCl§üd„NƒÓ"c€s.€´ÖF›ù!À¹ÈÞ/.aý¢ÈOn2@…0ì’âŒÊ¸$Q|ÓÅŸœœÒP§¢R¾ð3[õB Y,¯dñ³`OA` [þ*£ÁG´,i4‹£€YŒ÷£0ŽÓŒADä 9+¾åùÒõ¤ãÚ–c3†@†!’ÖÑxHR:®Ãó<¯@‡£^ÈT¢ÂD0XßÝE„DˆQ¢´e’í}t¯r´µÑxõë/1·<9é<ìô£aJk¶õÖÆÊ þž‚(NcöÈÁõ-vþšøö7äî[ƒû?ª¾ðÂö;oü¡M)c:xÿßö¬­WMØ÷÷tªÒQ/8?>¥ñQÊ™‘‚s!ÐPØ1N0ÑÁÉ ¬zõE÷òÊY=ô£Ê³{ÞÚUò7€ÛFGÀ,d®ât8 {ƒ±m[žc’„1Ü4¥Y·‹È.Å>qæ?/…~¹‹KÂóÿiz~$%{L×à…y”–¹Pž³d€,Ç~¦«sÆë9ÒçKK}ße%Ÿ¤œï"PØO S ©R‰J)MP«jµÆ‚F²²“D DÇQš¦Z ¾/êMÛqË–Ò¶8g@À€«4öúá0ŠB£52†Rr×®çÔ­zƒÅq FiÄÁÆ¥mË!bJöø,ÜÛ?9½ói÷d|ãön˜ÐÝŸ\{ó¶µ¾»ñbåîéÏŽ®4ý”É?¼ûø}qõʦRÅ1«+\ðSLtõË¿ƒíÝ+òò+« {ýXnnS‡xØÿÓœœéñX)Mš€3ÜŠ´$—\z—"qœ H#ézÕ^òn|Ug¼ýŸ²óý»ÞƒGÎö=Ü®ÄmÊ&§ÇÃèà¬ÿ츆€J©X¥D¤µal¾6ë‹LT¹ëc—‰ Ód悪Ÿ‚Éãt¦Ðù׿÷½¼g[Èð „Hl‚¹Yš‹G «ŒË+§á¨Ü¼Ât¥ç£?W‹Ò9ôϸ)c´­LEã^2êŽÎÎTJn¥^o¯UZ+•V˯V]Ïö|×õi Æ "DRJEÃñðlÐ9Oº]5«`¤FƒdÐÇý±I.É«yµ•zccÍñœ(ŠHzAtÿÑÞq§ÿôÑaÅönÜÚ¹ï ·Ã£³î8Ö¨¶\×ö÷OÎÞ;>ûãîÎß¼})9שּׂ­þôγÿïÿóï¯×,Ù¾|ç÷~Ü|ñ+Îå—e{UHJBå\º%[›•• .¥BK§¨ ©¢ë)¥Ð’h¹Â®p§â­®sß³›-}—7×Ýas&Æ'ÁIßbqµîˆÆ&H‡F†À$ ¥Ç@†ƒè¬“£î JÒh­ ù¾o c¦M|üi rƒüýò À…(˜ûåÁàåNÀR ¯¬ÅR÷caZ?Ì~ ©NÙsx>™§ò­)*´ÈsŘ>çÂüG¸HÆ04IÒ=|zúô³q?‰c DËwjõzµVu+Jµb;6瑈ŒÒi!&©v ÄB*0g¶Ã\O0‡…qœœú}ÛõÜFÓv«ã~@ŒñJÓµ­f½ùêwÞ1†Ÿ|úÿ¹üÂWŽþäôT¿ùF­Ñ\µ¶ôk?¸ÿôÓ»r8i:êôÒ“3ë­·ÿÍ?ûƒ;÷ž6þîß{ññÁKíïÔ¿öMe´ëîKˆ÷Þ ÏO¼+·Œ]õ¯8¼±Õ}p§³÷,ìö¥‹vÅc¶'<ŸÙ2%àB–ãpšR”^톥™³¶XîáÙ ùôpuç„Y£bB\IH§ ':^1Z›(ImW´VVãiš„aŸó™®MÅ•-p_j ž³MâŸñS€1bª„ºüFYÓZ¢ÂN¾T–ˆÉ ̫䅥™åñ¯y»ä<‹Âþå.ã–˜ *0i zãg£ÎCfÙ’H)&ŠGæTGÝ$p’±çz®´m!¥63Ê”$*•¿ºÆlKr!\Çæ’q ¹dZk`F­UœšxãÀäÎW^}óÍKׯ5Ú[D°~ë•(Õ×_:~ú¸Ž‚Jcíæ›k·^ÿÚù£½wÿá?»d½£n¸füæ/ÿÊ_~ëËßûêßþ﮽~ÓnÕ 2†„Fž9—nèÏ>èþüÍ7ßÑ~›¥P»þšrêúÉ^|~®Gc)-ˆ”c;^½bY¶I£8êXÌØ—n[—¿ Í+ÌòPØ••UKŠÞIt¼wTÙøÄãókTŠ–a@PƒP\8ÕÆ®ÓFQãÑxŒÓ$åœMüÞÙàýsY=U}qÒô„F-»OK2*9_,`2¹•ó:‘€æ2P[ðÊf*M@g–¿£Èš¿ñ‹š…{®'p‘GSvÆ&®²VqÐï ÎŽÃ`$¤–D ›´;RZ–#„0Æ$AÓШ4¢ñ(ô’hLZ“èûÒó]·^÷[Šßrk5ǯHÛG&'!,ª4‰†Ãžç×n¿ñ¦´ÄÏþðß%q`ÙöÝ~¾º¹Ý9>¼÷ñ‡×_CúµK7_¯®¬®¬mÞüÒWÛ«»wÿîÿýÉýcÅþÑ»üæ—_ýkÿÖë7¥Í´1Ęetpúþo§w¥Ýží'Ã__ÿÎßH««ÌnÊ 5§ýÄUœ2´9!FçÏ8Kk›—½k¯ñÝW°uì¶a‚¹u±²¹ö‹É ÎÆ¼}Æ¥–k´ebΈâ@ ¦(;ÃÓ½“Þ`<ÖF3dŒ¡”‚h2(¶¤çèÂYè­9š—jy\˜”Ùƒ2[Êåú…!3£,˜ ^æcÌFÓóF#g*Ê{ãR9·¢¾^6ño±m€rvàù)—´5!i†a8êë4ñ*5QOEa¯‹“¹|„Æ`šR+)CÆ1›ŒNÂp܃Di$æU6ëÕz£Ñj×5¿êûUKŽ‚Pš¤I0RZ)•&I’$IÇQI~0:V¯W÷ïÞqkÕÝÓg­õ­ÚêæË_o®líH¯Rm¶€@¥$„ó•¿øý~üþþòÒGÝÛ¿xÕÞÞ„!0Çæu2fÜm}éïÄÛ÷“þÁÎëùì§ÿ|´÷±sí;‡²»ëAÁèx4êaÜlºÍ­­ú•Wª—n‹Õ]l¬Be…ì:CAƒ¨¸ëWœ­ýÞI×òVÓJ#ÒŽ S ™J±T…}ôý^_§©+ybËTq2†€&Ÿé®‰%åOtIÆ,£—›ïç÷vRäÑ÷Eum1ŽCbÎÊC4{fûö”JPê*òG3‘(µÂEÕ.Ù¹eƒà¹àñÄ¡ 2Z•¯RCÇ5áh¥¹àŒ2Ƙ¤J¥ý>v‘wymkëÒ†°$ã I¢auN»'(¥` µÖD€L¡àÒ‘Ò¶mÙ^Yi´Ú•Zûúë_w|_ºÕ¾ú½8I“dTªŒíU¤t´Qa4:??M¢àÒw¾ùéïüöùþÞËï|—m­Å2”£Ä ¶£ÎÉÓþÓßúK•Ko"ÿ {}ó =z<t ÙØ²W~6&94–±ªÍêv»~íVí¥oY[/¢Û4@4ñ'a?“¢ ¸’»û}¹lÓ³¶™¶‘1¬®2Q?ïvöŽ:Üöª•4Ò ‚8NâTSžÂ0§*Aù‚‰q CgK¶ Æ‹ É…sþ Ê¢@K‚@0w›ç˜Gd€™F_XƒÂ,œ‚ã1?™Æ/—ˆG¡æ g4MÞþ.—Dd\)PkGq0d&MÑJ"Fë$ÖFiW+Ûí­«õ•ËNmE)œž?~|tÿþáýÁÉQ4:MÃ.’––p<ÏõªŽW©57V¶¯¶6.¯ï\_ݼÒXY'@"ØÜÙu](œ>º?êœÝ}ïÝ“'{á°÷þÿaG”ã$ÂÁ`pzõ NŸ=¸Ï\Y½²„ã8‰5×q,Dz9sü!—Ï~|vçê»×Œ!»y‹Ù•Qï0‡)2Íp¡…ŠSÞ룳ƒôü±Hº‚"Éd>CAéãK‚¤:::4ˆÈجõÉi­Ÿé/øùbI±¨ïÊBqAÖË­AöÆæ¿ 4—y¹3KyÑÈ;š\¥¢Ô‹z¡ÿZ*f&X&ù,ƒÌ‹sÊ'E˜t¼8¿JÛ0±uõfµ±'jÐéîß} †iš-ŽÆÀrl¿Ñh­¯¯n¬qΣ8ŽÓx¤eUØLé4zîÇQ(„¨¯¬¶WW,ÛÓiÚœ‡£!’^_ßpýj¥Zñ*> „9õÖ†!c `„H$lÎïÜß»w÷•·ÞÔFÙÒŽ´‡¥%OŽŽî~põÅ›µj]8rmû:\{í¯i•]5ŒS{謈êØ]ªÛׯOwïßI»B~öø™]ÿh­±Ê„M\¢UÃæeÆí:³¹ÁSþCè;œA1`ÌixB†ññÝÇ#ƒ~µ6 ‚ÔhȨÓ,(zcùNº€ -†Œ.ýkÅ,rÀŸûs\—Ø÷$;þµï/[á…™ϨæÉÍ\ÇOl aah s¦fþšüîçùßàX"Y¬•Í_9µçê‹<ú³×ÂÄÖœ!C䤵%øêÚgâüðYç`oÔ=V:F$` Ýz}íʵ+·^Ú¸zµ¶ÒŽ¢àé“ÇG§§(D«Ýd:>ß{p¶¿‡a}eåò›ÛW¯[–ìwO{g‡:UµÆJµ±Œ"hµWüzs4ë”Fý~ïøÙ¨ÛÑq¢¢HE‘+¥+-&£n÷áÞýÙoþöIï4´·Ö­ªÏ˜ä–ä\Z®»±³[_ÝâRtüô£§Á)Æ\ÚÒm‘B$f§šôŒôŒðSr4s˜]¶¥¢A4èräép(ôØõ]´<Ñd•9-ô›è7Á®hâ`ŒãX¢Qcõ5fUɈóaºwÒE "NÄd0Àr»ÓÅ}R˜ÄüœÄEHÏÃC‹Yθ0D†¬LJ  ð 9 ”eÎßþÞ÷ò[}Î*9wAfeÂB7 Q®|r3èf²˜É@©ŽÙš›<ïÏ5Å¢ É ÈøLu"2Æ€3t+Nž>ê“0ÎÁ2hy••Ý«[×oV×Ö èÎÉÁ³Ï>}üéƒTE×o¿rùÚõ súìî§qn\ºzéÖ­ÕÝKBòÎñ“gïÄa°¶uiûêM¯Öèõz‡O÷„í6×6<¿:öÇý®mK­âz­å:NµV•\ø¾gqqøèéÝŸ¾ÿø§ ÷<ËixõîùY·sÞÞܰ]G©˜ 9ìvÂQ×öܵíË«WnmÞxÃv«iÿÒp#ìZœ&±1*M•I,Ëá¶GÂG&N•å¶œê:2‰BTÚ$±”ÆñláÖ¹tOÚE15ެµÍw_ÄÖx-†ÎÁ³Ã'‡=áÖ†ADQQÃåÌú`z®ÇšõÖ¢? A£‚@}†°l5X¦ŽçBWìSTò¯ÿ{~f‹ˆ'¸Í™‚ùŸlÂ?`I#`á|®E[brîC¹þX„\àgéˆKv‘!LG°8ëwÎúçÇF%È8—~£½ºuÙo´T’=}tôð^÷àxÜ´6Úo}ë›—_¸q°wÿîï¹ÕÊoýÒ 7U::?zrüìAçøÈ©¶^|ãíÍ+/ÆIzøôñþÞCÇun¿ú–†C‡óN÷üÊÍWGa°{éºÖʯÖ—É8:Ù?úðOþàÃO1Õë›»×®­ïî¶Ö×ÖÖ7ÏNΞÕêÕJ£F: GýóãÃóƒýîÙ1h-Û«·Û·íÆ&2€"LÒÑ ;îwÂ`¬’sæùµÄÈ“N¿ÛïÅFšX96ºžÍr§bÕ×Pp”¢ÄdlºOÙðÀIz\h±²küÍ«JTî<=»»÷T0FˆJ›ù ‹Y/PQÇzábx?øKNJƃæ€y˜ü·¸ ¬t9>“1ä_ûþ÷ ö-·î13S^’›#D‹Û@ÌèOIÿCAç¤pyQËeb6u(§?–¸Å€(1‚18ŽŒÖ’sθm¹¾_µ\F½ÓÞÁÃqçT€›íæk_ûæÚÕëï|ôÉ»?n®­½õí_Xi׎²çÝqÿÄSomÞúò76.ß8?=ÙøYïôsùÂK¯­o_þô“mÕJõÝÿè¥×Þ<:þéñÞ]2‰–íׯ½òÖæÕONŽ÷î~cL{c煗ߣÏ>|oscmmcã“>ØØÚI5ž?k5[î|Ú9={rÿÁÃ{F£ÑÊÆÚΫ­ ¯^w+¾”¶‰»â2×õµÖZ;‡ïÝ7F·×W,Û’R¬ml5W×+µ†_­…ýÓ$ùµfœªqŒÇ#eÈ÷kÁhp¼ÿ8èž&Ýãî“ÏÆ÷ÖZ5Ç÷Ÿ>:ó­µíKÀx¯ß ÃP8[!<rYÒð‹?7㨸´÷>%2›åR¶ˆÈ°âD8X8Ì9Ùrö¹g,ò¥,Ð,–„f#pyÚ“™^6–M¹@˜î¥„¥úbSÅ ?äÅ 'KGˆãÍDÄ9cÑ8 2:T„£AŒI+nIÎ+dÛ:¸•Daríö—¸Uùáoÿ+$úÖ/}ßvìGÿ8œWM§ÕŠ‚ñæÕ›;7^霜|ô³0Æm×S†®Ý~­ÞXytïãá ·¾µÓZݸwç£Z½qãö«úƒ?º|ùÒý?Ü{ð€!³„¬4š[W.U5¯Rwíª]©HÁ¥†@¥Ü±Û—¶ƒ$ò6W*ë럼ûÓ½œªûò[oZÒå–Í-Ç«xŒ;Êh6im´JÒÈ’2{½³æê–QæÎOÿ0~ò©9?Taü4þÝúæå›7Þ’û?oëòe«µýÁÞ ^ùÆwìÕ]’ÂHCbDiÀ 0"hø®?þä“;Š[Í•õ7¿òµÎéé½{wŒR¶ei*FB—Äg–Ü~7Ê2ø³yÓO6 ñņÂfì&Q  ”çHsO8ž\ÉùhòSœ‰ÌLöʆ G}èsÕCéöó~†ãq‘V:‰Òp¬“´fŒ2 éÒaŒ \åK£qôóü»+W_þêÛçÇOŸ}úXmµµ1†Ë—Þþn½½y÷ýŸö:‡žçøÊW¿¦êÁ§ïi¯ÒØÚ½üäÑ=£õå/ކƒ`¾w祷¾Úh­îÝùY4ê¹µ —Öx4¬m\zå+¿p¼÷賟ÿHô¼J†²R}ýkïc"ŒâË/¿µºvéç¿ÿ¯Ïöx®-¸ zv­õÚ×¾ûþÿ8ìœúÕjš¤ë—®¦Ñ¸~FH[;—“$û­•¶I!„å8“wzž+¥`‚»^EØ62ƈ(£úúŠ×j9ÕŠ´,·ReŒq!ªZc¥eû"ÖZíZk9÷«5m G†Áh<LH.‡Acu÷ðÞ§£'Ÿ¬_{Ñ߸®‡ÇŒ—Ìv„Íõpïýµ«×wn¿ÕýðE­Òxå-¿êJÛÛíMllCu•Y5"[)v÷áÁ'{õzKkêö‡„(¥L“äÉãGÍvëʵëÏžì1,„í?§ó‚0{މïEŸyÌ4>c0ó†‹{@\lrZ|Af°/ÄA!ç»”¦ å¥b.…}äX6¶Fsæ³ 5™`̃A¹9°¹ëE«ˆtüs®RÅÉ£”R)ãÜ©Tl×gÒâÒƸ%ë–Ji8è®®­ŸÄQhû.$c,N㛯C'ê½ßýu™Qz0è»Õ/ç—ø»ÿš¢çWŒÒ¶_«6ZûØ–Dë[[GûÏlÇv]W§©åXRJiI˲G.¸eÙBJDpWÚVŠߨû͆[©çLZ‚3´l«ÒhT[­µíÝÖÆv{mÓõ|)e’*ιNÓp4èXÂ’Â}z÷ƒ8ìm\¹5ì}T_ßô¶^@J×ö|ßq­jÕ‹Ï××W·_~;,×s_ø²Ý¨qÇAË'¯ N‹ÉŠ!a4¨ÔüäƒÏ>ÛÛ¯7Œ³( µ2ˆ ¤Ø?xêÚΛ·öö Á ¸^õås_²™3ßviW.‘®™Œ8àÅ€OÉdŽð·9Õ>€ïg , ”µžô—°¼í©ÌE¨Ì¾ úb½ê‹8ϧ„àz¶—!Bä¶¥ã!À0Î9w-'GBb0 ÎO9Ò¶0"ãäÆko÷Nîÿì]Ïáœ1‰J¥ßøæŸÿ«ü[ÿ‚Æ=˶tš†QÔÜØÙüˆ´ÒÖŠ²×9«Tª‚3"’RpÁ9çB0)cÌ’–ÖªZ­"Ò²‚aOJ¬ÔÒq¼Z #KJäÜvׯ6škõözµÑö¼ªeÛ YGZ+!ä°× ççÇÏš­6hsðà ×v¯‚ÇŸ8~µ¾}Õr}¯ê·66ëíV½Ù`Á¹ãºí›/yI¿+ט×ÀÊ&z+ÆðAg*MT’¤¾_=:ï?xú -)•ÖJk"r,yr|dÙöå«Wž>y:õ¾ÐHýrT_è÷-eÇs€Ü¾@Ë×Cæ·ŠË %àíïg³A‹«Û}á~N†¼€BPuZ&+¹½sK5‹ÿCŽþd¿§SŒÉ¯!pÆR¥púߌ ŽŒàˆ\L âÚöùéi³Õ8ÛßëžžZÒ`Z¥ÒQÞþÒדp|òäNsuÝr=Û÷mÏc–ûí_ýë?ÿÉŸ„§{œL0¿¹šjÓ9>dÈâ$ݾruØï¦qìzŽ1ÄrÆ9gRJ)c ¥DDZ¥´8ç†tŒ¨+«Ü¶9çŽë%Qd×åRz~ͯ5]¿jÙ. Óš0Žã0 „à*³xÔíŸí¯mîª$é>H‚áÊÎ5Û¶zOï Ë©­nI×s=§Ö^­4•VÛ–h{ŽÕÞŽo*[LV™ÓbLžœüä'vúcbÖÑY— KžõzI’‚1DD†ŒeYÇG‡kkkµZíäøØ²Ñ‚xŽW¼Hœ.`>‹’2ó€óã 0ó„!·môEPZ¶ ¹…íJf³‰‰ýr NÙÁœy|sÉXÈý0Lâ[4 ÄâôÂâË&×¥à“ÅŠ9kG€À#B –íôÏϪ­Õhp~üô CÄçÀ$J¿òæ7FýÞɳûn}M©™%‚ñèKßýæãÇ‚~§Ö^w;*„W©ÔÏž=•R$Iäx‹Ú[W…+¸–ÅYpTÂu¤ï ¿Î¥ Ò1Ì6*¡DYŽ?NÔïüÎ_¹tÕ¯U>Ýïö–”A­³ø¿6ÚõÜ>øàKo¾Ùh4GÃÌ]É /(«Ü͹*+÷ëÅáŽ\¤}úUT¦eÕš…ÑË`å‹åuËsúÜÍd§Dë³\žÉYr–޼L.›íu>Qÿ…q˜ c(-öðÁ]…I!¢´\ÿ¥7¿Þïvßÿ¸R­¤£€1”Çqtå…Wb¥Žöj­•$ d•ÀöV·/Ç!çÜr•¤Â¶ÏONž>|¸¾±.-ɹ4®ãº¶RŒ !2)-˲g\0Î9Œ³l[Æ9C†I4æ\X–ä‚Ki[–Ã×Ú ¡$ŽG£Q0j„Ã~äÊF£…dÀhÁYÔ; Ý«Íø¬wto}çFÅfB @åTטÅ-[X®ÅÔˆ Æý:óÛà6¸UΕîùÁý‡ Ëõç‡ïÞ¸vyks=ˆ“þÙùdä‹•Rܹóéë×ï|ò À¢=.xr 7Š“‡h¹   ÈE˜Á%W²ºËP'žŸAñÞŒÖäÁœ +qœ‚ÏŸÕhaÂêo+T™ŠŠ7gÃĹPSE•Å2‘!2ÄVQyŽÓyrÐ9‰Ãq…FS{}{ãÚ‹£ÁàÁ§?¯T«Zi)%Ò€©µ7Ö6·ßûÓ?’¶D "^ݪ éV’Ñ™åºI¢‚Ñhm÷jïü4Mâ~¯–t<¿¦kQW*>2̵-DCF0)˜R‚e ˶—dRƘRÚh΅͹œ¸+i²4MGÃa’„ItÏOt†Cÿµ7,Ë9éÚ\ÄI"EzüôÞúõWŽîüD¦ãhܯ¶7„×W7˜°¤í Y¥êúWP0fy`yPŹ L ¿žx|p”&ÊóîÜÛXi7šFšvûÃÉÏéhc&Û¢i Ç?^ÛØÜ{üÈs³D5ÑÒ½¸Óó‡¸T×]”:‡©‚æ~žq_DŸSΠü6>¹¼Eo~±¬\0*° ZðÒõB+Ð4&;'G­5æ¶XÉv%£ÉØ0`F@ÆŒ‡'{TÇQ˜$ÉúƳ¼8Nï|ø#ËBJÏóĉ~é—î}öY’A(„Å9SZI× ¢$U:Ib2”*ãW§‡‡À91FIëÑ`Ôjµˆ cϵ‰êJiÛ²µ"ò"ç“PÅÁeÀ Ðv1‰ƒ±‹|<Ga˜&q…ÏÝ[[mºç•JõÙÁ³F«Uu,4=cRíØV08Ý~ñõÁþÉÏ÷¸ZE^½!ì* IB«nUW@XÀ$#‡Ã(è˜$V·¯^}y8<;ïÃqÜ}4rŽüŠGF“Ö ‘ Á9ãœs†œ¡”’ÖÖ×èÙÓ'RÊ•Y˜±äƒóу/ëŒW¶ÚÏ}–D¡ç5”RNWR*}ñ•7FÃñӽǾç%‰NP!6Æöêó³0 2 –k9þ`0Lµ ã$6…a½^Ò:ï<×Y][ ãÔ²¤c;®k{‘#…´lKHi¹"ˆÅ-Mh1À$5ƒQ,œ±ëUàéÉIçôPpL£àÞGû* «·^îŸ=zl]Ù¶θà‰V¾ç:\ÕWV8¿ÉU`@ÛÕ¦%@ שT…íp×—~ƒ;MB DˆÄ¹z'á8ìŸJÇÛܹò ß”W¯^ÒJKœ r»Rñ9ç—Ž!s]çŒ ”*ÍÿèùŸ™¼öÉ|9›ÿü.¬ðÀ (Qa#Â¥»ðÑlEàEØ„¢ÀMhEÁ\ ¹,–—­¸àÍÈKa^Ü¼Ñ M:Á½1fæ$Xc˜1”$*t08}úH%±JÎX¯Ó«âØâѽƒz£Gq/삊ïû•Êøo+‰GÝι´,ÇqµVBZÖ8ì÷‡ŽcJk!ì NOÎÎ 5hI=׿óé]Ûu76ÖçI’X¶Œy®íûž%…çùIš6ZíJ3Šâ¸Rm!wFãQG+ë›§Ç7Z+íÖÊÉñÁèìdïÑ}É¡]óÞÿ=DZ˜IŸ<¼ÓhØÈ¹IËIºŽ-,[: W7¯ŒÎ( Æ)—aÐMNN !³,mФ©N“4Ic(•ÆiDã@ºþêön…Ô[—VQ@ÔZÅq2Œ±IpyFj2Ç9—âðäèïü¹V³qvÞ‘R–š‹Þ}î§¼2>ïÅÑìåØßRhMI=OiO|€]™wÜ™RŒY`%7„G%ÉYö‚ Mê‡{3ßjë‚e™î/üøc!ât¥6â|šJÖrDH†Ò$ ¢~'ì=2zÕ‹ÂøÅnï=¼Ï‹ãHkc Õëµ8I¶v¯¼ûã=}ò¸Z­¸® ŒFãñh¼½³= Â$…!c<‡µFsŒñë:<5qp.<ÞCmÆû½Þ`0dãq ¤@ÄV«Yñ=ÆÙxÔÍf{õüülµ½¾ºuéÁÃ{“ýk7nJ!¾÷'¯¼ö&¦ô^\¯¦Á`­a×_½ÙbzýÒΠsbűeÙl}+9}¢‰ºA„ç=züÄm® A÷Ür BÐ*ÕÚD CÊ­Í$l2^ š(õCayÏ>`B`ØdxŸN2N”Ò3ÒrÛªU«Ç'§–´h9-(¹3AúrR‚C)º˜\^ô`>1ÇëRâTÔáóÊä¦uÌ÷˜ ðÑ7³P4«ß4즟?·gy³-¶á¢U%0¤5ªDÃÓƒ {ôÏŒÖ €#ÆJ5Ûí4NºÝŽëºI" ëvûÌr”Á÷Þû¹ïÙJC:ÆbUÁã½Ça2LÓÔ©6Ç£0 #.¬á¸·Ònt»½q8ªWª£Ñ8Mb2„È:½žç8¤ãxhÛAqÎÏ·wÇçgçf4ÂxXÑ&ŽzÉÁÝÝ›¯j5ê?þ`wk›©¡äöJ³IØ´ב”–D: 5ÝZ+>=0¸6õ[;»Ää¸?0³Æ9c‘۶І„¤RÍ97†,!À¡¨ßiì´üÆJŽrÑh cˆ1&… d(¹ŒâįVŸ>ÛO£hmmõÓÏîæ|ÅR-ëâåŽ]Ω¥ )Í@(@ÿâ Ë‚ÆÙÆòìRÌ„ ¤‹f\ a.-ó § Ÿæ~A~xašÍLÌŒ0?X^Ž=¡eÊ$7v@(MÓñptrpòäªtØë&qDZ›$ÕÛ­öÞÓ§œ12f²8ƒ8оñÕ·=¼×ò-Û±Œ!chµæ#bÕ÷“¬xÜnÔH ž&ñîZm­ê\Y­¹ž'8«Wü3œÕÁù Hüª£µ2ÚTš®Å9cX±0Ž5Ƥdµ»›5ËÖ«†•jÍ^ip4v·7V£ÑPÇ’I’ë² QVZ•:0Ndâ`ÀOTÉð‘1!¹ä, #¿½ž )c“ý=Á‚4U¨QJRŒs0ºúÌk¬ûÇu ˆ´±l‹q†È82¶£ ŠlÇ@ÎØ°ßo5å #–¨¶‰ý'º Ýs¥¤p@4`Ê6“~>Ö ï›}ÄÂõ²Ÿ1È-D ›}œå/ÓüdŽùBʬ2Sò?û¹0ÔDÚ˜Ùè7#2Y+"gÀ&Ã}@@fBu€ˆŒbd ¥ ¥±‰ƒtÔ‡t°]÷,Áa¥f”ddHÚN­ÖØjû–m!CF+­Rå7š›»Ûºóô¥Í›iªˆqDFkË’D´YYãÉç,‰âfÝ^«;—W*–ë8–†ÁfÓŸ¸%¾ípÒhX1Æ<#@iI)„(1­W¼8N„Q”Dõj…T"M"-)*%„t\[§±í8Žë0 ‰’4ÃTXÕ– Æ‘q $©” &]_Gã†Èh ˆœsÒAÆÈ"p.h2âˆÆ‘tÜêÊZÜï2mt2¢`(¥R¥ ‘ãUZ­ö¤±ò8 ,[ÎàrñÏhÏ~סü2ç¿ØcÈSïsƒ\ xÌ…/—%9xÏÉxILŠaþ‰/=Oü 7•­ÀõóÖ«`ý'ëÙQ« ‡{¶TiB*E­‚AciÆ€NV`tÓ“ˆ`²“Ö4ÙaÆý–ž¸ DD”¦©U©†ãVÍøiJ$E¨ÕúÆÖÞý»’£VŠ10šT’ CPFÓLÊ€6FÃàÿGÙŸZ–UõáøZkï3ÝéU¯Æªz¦›nº¡›©AG4§¨ùFœPC"šÑ|5ŽÁhЇ¯"|MpJÔ2#ƒ@COvÓô\s½ª7Þñœ³÷^ë÷Ç>ã}¯ðû»Ð¯îpî¹ç콆Ϛp– ¢,›“+¥ÄÅ‘µ¬ ÖJD”Rq)…Q ƒ@‡JÊ@€IDATâ.+pAR¬ ;(EJG¤$I,ˆI·7XXît:F†¾ë.šé )æÙlFa4ÝÚBÇ옳·.2`ž?ÏËKã8É2Ãβ“1  °<|ôÂÙÓŠDØ•üX,bŒ%$¯Á–ͦÓ0ŠÂ¸›NÇZ)k]ysˆH$ì”&­T UGŠP)¥D‘Š£ˆDPrkòq¾3ÚqÞhÇâœcft,@ij,79¦t{©M;ê ×íñµAAŠK+7Ýù\ç\Å:t(RZkRy¶$BR>ÅŒ€P)M Â¤›e3òþa1Y:›N“N˜ŽSÃУ”rÖ…¡Fü²2½ †®|Üÿ_±½ì Eÿ>n‘²”Å#Ô ?|™ƒYö?lRò\LwÞ(˜W>-¨ß2x[ªý×¢ºTPZi½¸¸0³Ì$qHâXØQ‘óˆè„a‘®‚‚"jB""öñ笵…#ØY‡ÎZÄy6Eè,`6vW0àpã²´s,À~ö³#$fP â„•B46µl–—Ò(ND)¥i­•"$"?¿2P¾~IaAx HR¬”(J‘VJëH‡: thhQ†a†žŽ•ÒQ§F‘„al“ž ’÷i•ŸÅSíØúôah7?ðZ²îø€ˆR\± ;AÂ΢;g½ÕFa:Îó<‚Ð+Ƙ(ˆŠXÕ¼ÃbHÿRw+޹_ägîñÔd·S©ÛKËWLó¼’Ë÷ ;TýÁ°= iïŠ ø®èÍê ˜N¦À.IâA¯?™ŒµòÚ¦(„ó)wŠ”_fñžOúÆZcrAavŽ«x™sÖZÑt2¶l@Ø1ÁÑk®yæñG;ýÄS“ÒžtI+EZ‘RJë ð ^k­”"EʘìðÑÁáµ—ì^>—t»H@ZéâËš´&R¤”©¼.ÐZë´V:PZ+U*å  =ÐPÝû“yº§êecÔó>©Xˆ DA]{4°ý_{Ç EDD…€³AŠã‚ë|IƒW¢A¨I)™¥Ù‚"ïݺ•¶k%ÍG¿ê”Ÿ}âaµÔ…"x„Wd”zò\…öDÍtÃÅÒÃ>Çi¸‚ÙÇn]H­µ®ŒmÑ8Q½"ˆ Ã(€ñ„Ln»‹ƒò]ßœôB¥ ‘©]¢ORÞ˜£bÞc!‹šßåØ7ú.>…Î_5&fFtâÊø)ú|Š¢Ò¨ ÕÆEŠD »ƒek'D§fR ±ä7RX@¥j~ðD¯*¡ÅÔÚJZ‡}8‘—ؽ¾Ûýö ÷U¥áW‹$Dd ObÎY£´f[”‰H‡ò¨K)b`Ehm ÕÑ$Ý ðϤÖWö˜ûAè¶X£¥V –ëTˆ½ä¹/ÁJ#®yÖÒšªXh>rúÇ=âËL‰Æ—9À1 át<¾ýŽ»°§Y*%ÓJKXH¥‚¤êäZzpêá>\¦fx‡¥w)aôÍ©¡PøÊŸ¨UZWÒ~ ™mÒLgV+ ¨<”n"RXò-Qa{k³¨ï«NˆØÀX%ÔZ?#$ñÛPÕak…›v\Û]¾—ê1ƒ#€c €RA‰°µ6F "DpÖ)TJë<ÍpލKו»ÓÐ5mj)/çx &'læA¢ø2qÙ½ô\,”.~s¿(BÓb˜g/©ÅIۯȫPAþW°ýö^øTÝ‹ÖNÄû欳„è¬e–NÒ5 ,Å…@ÙºJª¯X4‰bŸ @‚àAddað(_Iù )%k“ZP®Ò*Œ:L¨ HCÎñ,á}ŽÕG髲Ék£Ï€÷É”„S1@C¬ûø\S¶·_“;^z# yãžv¡>u½ ^ÔƒùôG_g‡H†¡cgRÄìóÍÅ÷hÑlEºRódI÷Wp6¿\}kž²°õ­½²´~ÕÔ…Q+ a¢ç~yî,íA ª7¢j…íPÇØÃ†•h®÷~z¦ùÛ‚DÖ 8a M&wì\*U(ÀeÕ€ Rða!ûQP¸‘JÛèPQ¬ —™È^¤Š !ˆª®­h1\qAk23!y% FcR~*&¨=îDˆ¤Z ¿ÿè9³ÖZkerã]Rs¦\ÅÆnÔNêb­Yúƒ)•¦™§Ú¤“šL&Æ­ìݱ"dBÀŒ€ˆg: s–¥þ@­U–§ˆ¢Ua´h¶Pjâoç=WQ™Š{áñÜKÜçí+À—‚ 所 è¶Gs®Þ¦¦GÙ{þ9·2f÷e”}(}oŒ¢úÅb¥|ë#¥”ÖÚ:„‰sÎYS€id)}׫7õV ~êZú ²U#10" z۠г>¸I ¯W}ÊÈ»)I„IÅ a"# •i±HË*ÌS6.#ÌÒíö&“t{gtôèÁt6a–Úh«g)ìœ6í8vaaô¾÷ìÃýä©Sg'ã)"ú¯ýk^ûšW,­,NFÃ&².ÝåÞ›„Ec)P€‚@ˆ³V˜Å÷™dѤ¢((¼@ ’hKçyñþe €ýHŽ¢ÊP“ûG›~Ϧ%e.Põ)–‹Z›ÂMÇQƒæk龯u;º° Ìëµ}ÂHš¼9+" ìØÙ<'R> €M5T%rHQo°Ç·Œ¥«¯Üæâ7É|,eë±ÚUQ0oïPª€âzQJƒ Å/ŠˆµV©@DPÕP~qqÉ1*ˆpšÍ¨nÒèìM…뎙“NçÁ‡Ë¿ýéõõoü†¯~ë¿}3 )÷xOsîŠ ²”™Ã ޲Ÿxë¿ÿÀ?fk*¥AÀ:óÉO~þ×ã÷¾ãÛ¿ñÞôϦÓaúê­À–}$ÀÅ}é @cŒç{ncg£(bnRýœðŸßû=@ûøö—Z¨F {Mà2_¹’Èó¦kƒ!ÄC & ïq3ˆTb³¦LÜ_Æ7äa€}8bÏI9BTJ{‰×¥è[., Om‹ ³c€*eJiW2< °å ÄK¤šÊDP‘ˆ²‘O7ÐJ{¿“gHaו ”L¦S—%RI§ÿ?ÿäƒó¾k­~à¾çyÏ»Éäy‹ôKÏ~¹¢Ä"oÿÙ_úÂ9pðà}çïåW¾ìå/Áx4B¥ÛŽê+4ˆ,>I¿é~øÓŸ~`õÀæ¼òõ‰DDtéÒæOÿô/›Üýø¿ú¾ííËÚ7Mj®­ üxÑAA¡±¹ÿ]¥ˆÙYk­¤Ý¥áïl¢àªð©¡æÓ~è0¿Ý4lÜ&]Î¥/ûÞ®)·$ÜyºoºÓæOÝÐ=ÐòÌ⬂ÄT¯,%" %ÌJ‘s.ȳLQÙs®¹:ذ´<ºw’$( ­µãñD„k`‡Øä³ã—–Ï_¼„«+ ³é”HC)7Üét˜97v:JGãÙp8'»ÃÉh4ÙÚÞÝØØšN¦¤uÿôŸ~ý=/¸m<"Q·7øÕÿü{¿þÿOÐÇ›Û;ïý¿íƒ»Pû|š6‘RÃáøòÆæÊÊrNow8ò@oÿåÂùճή¬øÿñŸù̃®æyF‘16MgHÇ‘µ6 ÃC‡ÿêþÍ[žsãk^õâÑp¨a›µ B(  RFJ)ßz@yî%Ä8мÉQûwö¡lù‹Ýg÷÷~¸·ÑÖ>ب@öõ—¶ê¡Ôʲ¦™8ÕO”6e­¼êt±×Eú°ì·Í›÷ú·ˆ7¡õ3:™ÙÚv‘´Ú¬Æ-#3w:ýûxôs÷=xòÚk_ô¢Ûˆ€­-•zk¼ñ. ½þào>ð©w¼ãwEäíoûÑçßy³/x†¸ßÿÐ?ûãÀÁƒìx6ËF£Éx2ɵÖ9ÇD¤µò ||vñ§>ußïýþ¯Þ|ã5yžO§éGÿk£ ˆ“ÎÎîd8š,-t|C¹ dÅ€βs\Œ»TÔëõ˜« ÀýEH)]sK‹+ï{ßÇûwÞµ0X°Ö.,,Œ'ÓáîÎñãGP©³g.$"p„?÷óÿùî»oµ*h­òs`¨MCD ”BcŒ°ˆònMç:Iì SP¾TóŽ¡}ZÉýcAlÛŽMûsz#ÎÇ®šìQú¤÷ ÈhŸ©e94ÏWá{s„åÃWûØ:´äTEÊ cQ»@Y–!‚ƒ‚njZH+vÜI:÷}áÑ7}ÿ[§³LØ}Û·½áßÿÔ†;¥[4šCféõzûäƒ?ñ“¿Üí F»£?þŸ}ÏÝ·Î €ƒ0<{îÒ3Ïœ¿´9f" BFqú[*rرsE¡Öú©'Ÿ|ê©gžwÇÍYn”R½Þ€pË:³Y6™ÌV—ûŽ¥vwÖþMa"šLg“Iê=\Îòp8òIs‚®ÿŠ5dá(ŽÏž»ôïÿÃÏ(@·°°8›ešä—~ñí¯~ÕËXøÏÿ×ûåW~s8™ôûݤ?ýä©Oÿý¾ú«^6ÜÝ%­ZµÅoqI΂HZi"²Ö–î6Akm…MÏ~íï)ÿG@m^iÓLӳݒŽÒ¤Â}d@a´´rwjüS—èúsP+ ]ýnÓ˜*Ï2Oõ{® Y)¼ç²d¿çå+iÅå‹€ ç Kža1žkYP]Zé#‘ Ô<øèdš9rèÐáÃò'ïûèÇ>Ûë |RqíÓAè§Ÿ:…v:I¯ßßÞž¤Y®<&&$ ˜ƒ0Nâ8‰¢8¬synÒ,Of;;ãáhº;¯¯_~ê©§¾öë^ûº×¾z2ž*¥H˜5Jë(Ц“ÙÖÖŽÒA ©+¿Rí”'¢ét–çÆw"áõK—©Öíã,–(ê¼ã¿üöÓÏœŽ£(I’t–u’à÷~ÿßöO¿.Œ “ЛþÅ?}Ïý×ïýÞoÉòËG>òIð¹®PmÿSÜMH™ ÉŒ(>Ñ4)5@u= âoRÛ€:Œóôj£©rt±4ϱ_ÒÖsKÏóº‡8~kjÞ)ÐPÓŠ”V9|õ}¬§e—K&•²òS`­Ú«@ò$+ÂHÀÌYV”¢ƒƒ+>˜yeuEÊËÌ‚êÏþüý/»÷îÚj,î‰Ð9wâÄUA@ÌŒˆYn­3 "ëD€ ²Œ†»’$Q’ÄZ+DTDa­®.;vä¹·ßôÆoøj¥ÄZñ=AÃ0ðp:Iâñh¼µ½«”*aVCH È2÷Å‚XÁ@µ¾~i>àµw阹ÛíÝ÷ù‡þìÏþjqq‰YŒqîw~ýWîxî—7Ö­-ÈÖæÆwÜtãM'>ô¡_¸p¹Ûí|úSŸ»ty«ß ™¹ln‹ªrqu(­Œ1~EÀ—$î!~I³•™Ù8¯Hòí̸†â«j³ õQù¥Êù³W—Í=Z±Ýl®æßn€Â+žU¤ÚĆÂÂÊjØgIöÒsaTÀ’ûXªïJ`rSäþû/4 ÷Ö+Df‡Xx¬ƒ /G“P+ñ8µ  1ËÊÊb"‚i–寅a(b¼¯Æg“"ç„ï÷¼ñ–›¯;¸¶ººº†Oî ƒ Óív»"ÇÖŠRZD¢0J’´ÖA igg¨´*AÓ·à×DëH9ëXX)½±±íØAÆ9WDI""J…ð‡ïOf‹ "mnm¿ýío¹ëÎ[76.EaèWˆÛ;ÛKK+/}É=ø®÷:´vñâåÇ{ê¥/~ÞdÎ|ŽTîÏv &a‰¢0Œçœ8v³45Ö!¶@êMA ¢Yš}ò¿´´ä­ˆ8‰nºùf‹T%/V7cÖÖ,-/dYEѳ§ÎNg霡!õºBYƒ~äû&Ñì@Yc¡Vä3ÍkJ’2E·i‹ÖÖî Þ+}ÐÑÐà¨òyùCm´ÕæåèÊWи>)ð\žÌ‰|i^Î>W/_ö'ö9›§eå¹R‘´Ö¤(Ï2v÷­¾ƒRÛ!HG>‡‚™1D†³p‘@Z«0 ’$év{ËKKQ²oâà8Ï,‘®Ü”Aúä@tÂY^|ZÑ ZÚ¹=DÄÂW]u41Æk¬cv®Š›‹¿"‹ëv:ÝnÂìÙãØq;ôW/ wFj­·¶vΟ¿Øétò<n·{ÕUGlY½Uý‚_(fŽ£hÐïç¹!¥&“iž›9/v‰2+>CD$(­œ³þSô})…­üÝGUDl“¸‘ ½çÓ+XÐó¯[¶Á>‡‰H›K¿Uòdñ²j‹Òê÷VäË´Íæsáßi][‘FQ‡´›öjkç UóJ•²Î‘Bç\šå`sÖb³ kE9•@DÂ(ß ÄZît“0J‚(cfçLn§iž¦ùh4&›»;áŠf©yn‰ •Ó‚žØ9‡ˆÄÂè¡¡×¥¾§tmiEÖÚµµq:ÇJ—ÝYª°n+dS¬3ÇIÔô|·w_lÐòJÁ*õº ÃÝñtšv»ç ý¥Åunßž—"‚„a"‚"lãÝÒBÙGp¡¯g梦‘G€Q@(f¯LªnW’é{5Ã<ؘ7€ È!{>/ wîèúÃ6‘êÆ‡s=ë-¹BãôÆF´¾…횥‚*Å:Î=¿LZ;E¾[ˆˆ³–Å ÄÊëZ¾6¢Dâ(f­ÕÙsëßó½ÿjeyQk•¦ùl–Nfél:K³<Ïsk%Ž;aøO³ŒHy…B¤XD|é­°czƒÇÇaµî¤´³Î˜¼L]F/ÌÙI¯ßI’h63>lT×—Áè¹^ž˜V—Wœ+ ?sÌ‚ ¸é㫊þÀG£p6K±¾Ä ¢( ÍÌ€ÐÖ™e ‘”Rιn·¡Í§UN^ÝK¡å˜$„1938“Ð:××Z)"ÄÌ–Ú¼sÌq‰pšfEgùÊÉý}âµQÐdNlIàÆ§¡Î®’¼ ÏN=E@×õî²o³Ã–†¾Â•–™¡MŠ.I¸mÏ5S© fƒþ›aÒeöþcfÖZeYfó\uºÒ¨€‚5"F`‘8‰=éè@ƒ‚<“Ë—‡.n[kü‹Q¤“$@$뜳ηPpÖ‘*JI‘1Xã$ùÐG>ýàƒ9gNœ¸vkkg{{ÛXgóì~èŸÝ}×ÓÙT)ÕððpQM§yÝW ¤ŽÿîYTÖ^Y]ñ^yÈóÔZ•(ö,b2°Î8fc­´1&Ï3cl7ÑÜÞ¹Ê h­KÓÒYvàÀJyƪÁ*2/¹”RAdyîœ ‚Ðß!(Ä(С&œÆqä¬³Ö s U/élnoaA&h@Cä_‹×Ƚeª6ÍFi«C¡Zé™ ¯ú}Þ–z¨ˆ¶Ž:\™J|w%8û=¯•ÄžcK€@Ê:ñN!$vÖj¤Â2nËý2‡"ÒI¥‹ R¤ PA ¨‚6ˆ¾G¢—ze:¢"›‰Ln|,Ÿ“wßçvöcD¤´ Â`<¯¬.¿ø…/€•è¤ì¿§U .‡XÎf³"×°½šõâ "b¯ÛñËCŠò̘ÜBG—ÄR§• …ιååÅn71Æ !MÆ“ñx²¼Ôcš Ë“µÒjggrùò8LJ¯)USäžlÛšQI« ýfß„IDZ[cúÝD°;åÌ–†Ã û<óE7÷¾™BTSv“ÏŒY’[à‹Õ«É¨ŒºL‡M¤Su‘=ÏÅo`‹1ª\Š&{@SêWã=x¿ú¼2nÛÐmÿ``‘J¾FÊo3[›eYBT|™¯X,–1¥8Ž´R¶ ÌN˜=àßçÉYfvÌAÄqEÁÀp4ò»>ËÎ÷Æ¥Tê8Št I‘ÉÏ¢søÀno£XKU|#(Þ?ä-ï0 ³Ü03Rkg¤SDH’Øg\*¥Œµi–u\a=7ý?^Kc–eG¾þú“ŸûÜCI'I²µµõēϜýÄ¥õËN@Nœ¸ª´kh›ª…—H…:f®Ô®ÍóP©ƒK ÃÔ s¬ÉÏ:t`œM&ˆŠ&®÷ÔÍìŸÑôè´‚RsÖËÕV"\Šò†¹’È=–‚T…î0gTš¥Üj}AÕõ#TÔ9Oèó÷×x^A"_&@à;£°ažæÖäU%¶¥CU Qk›±0¸bуÎòòÒÕåCk«ÇAÂÑhtÛm7¨·þÔ2F¼ÏçüùõÚôda¥!æÖ2+åƒeS3›¦Ö9С^^îÿËùC½^7ͦŠTÔ¢"ò £µÎÒÔ¹ù¯kJË}ðÅŠ,¢ˆ¦ÓÙööÎUÇä¹ñ•‰m $ÀÂq¼äÅ/úä'î‹ãˆˆ´þê/?ðÚ¯z93‹ªã»^Cj­¿ôøÓ³4ëvûÝNçæ¯ÏóÜ}%K*(Ð|!鉂0k-9k‰Ð± óá•wiguÐYêÇOŸ]_[,÷»<ùìÊâ Ôáh6ÏfšTǦ)4MŸ¶A\7Oiq ÎÙ-©£ûÕ{‡VæLCü‹¢ÞÔÔvBAa+xÏÎ|ßÐò€}üs̲KµÝ@…ý…—¾øÎÍÍ0ê6""ðÙÏ~^«Ž=|òÄUY–56¶Dµ›ÊæX„¦i‚@²Î±MXk‚i–[]<°²ôП¾éÄÕQ Ï\¸<™Íúý^¨ƒ4ËgÓY%ÀÊèÛ¼<œ· ¤¬xªù«[&@ÚªèpñF®”6@µ%å6ªºZÕG­Ü·ý|SÅ?•ÅÐöûVUf³w%¤tÎ"ì´RN8 ƒ0›ÎH}BÊÈi ¯|׎¢0ŠÂ"Ÿ Qº×èo^¾œÎܬ(Ý@ÐJ#@§Ó'ÌŒH—7¶³ÜúB@ì$PJ)¥³,û†7~õ[ÿÍH§›h­PXÒ4N'J©þ—ç'RJkñ­´Æãñd6ôg-͹*q âÊÀ*8ÇçÎ_ôsZüÞ±÷ËK$˜ÍfϹåú{ï}Ñûßÿ‘ÁÂÂ,KEð'~âgû·ù–›Nîîl‹€ô ‹ÿðøC·¼¼”çéÍ7]·¼¼0ÜÙö±óÊT-j „ D!ØÜˆ0;§•±³;o¾vµ£yòLêÂN^õÈ—NÝ}˵@êÑgÏeÆ\^ž¤ÓËÛ»"Ç1 :ç \ͽߟ$Zï4X³¦®–ÃRJLW•y""h™æ5ø©øGj»Í_W›'ꇿ¸ý }iý[ ,š“ A â¬1Z«<˰$±–ÅQÝ ¢ C_¦]`Ya°ÖúôU†:ݤ×ïòÅKþ4;;ÃÙ,ëÄÊ9á¤Ó €ˆ<-ìlí úÑp{2¶È)ÛßÂ\+ÿJ6;³ u4OG£Ñâ ã*5KY‚î]ŠXt1ÝÝayº¢8Ù7“?£À:/vœËìGßô¹û˜Í2R:ÐÁùó—¾ó;ðçîm_ùÊ—*Åy–A4šÌ~æg~y8šž\[;~|ï½÷ÐÜh«JË0A$(®ðd‰‚°½ýyÏé…Ù5GºÝÎææÖU+7 ¨Ï>|f©ZY8u~3ËÍêêJ¨˼³;,ɯÖóÙr²çO;8Ñ0œË¼Ë¦G¾1RmvQD…âã{«Xæ=’ %ÝDó%æÙ •Ùv¥¤oËzlóxã›Þ; Î2øÒG.’§…Šœ†<+ˆu|ºÛáï Y$ŠÂÁBßO ð£|[ÂV bY†AÒIüUi­GÃÉöÎppü@–Yféõ:Š|b6„axùÒÆh<&"Ÿ é›+beÖ5©¿€Î( ,Â"Æk%ÏsëœOC#Uë½>ña:M“uhœ±Ök­µ&7Jén¯»²´åS$šÍ¦·Ü|âí?ý–7ÿÈOu:}­)Ž¢ííñüà[î¾çyw¿àöW~Ž»»“_þ•_ÿÂ>pà ÉíÂ`ð’ß3›ÍŠ9J•yݲG¥n0 uÎMÚHæÄj¥Lš'ƒø¦Ûo šéáH Õ¥K;³ñøØÁö4?¸80 6‡“ó—·fÓI µw%yŽÞ#áÛï´+ªöÚÈuÒrÛ œ_ñç–¶hr,ý,%(,ÝIU$«\s^$©>wû4¹Ìƒ®¼P¨icÐç «zW´V9Hžf@…O¡ž^a@)ºX¢0XYYôå€DÎ:*ÌZj«+¥’$FDaQZM§æòå“×öŸw»‰Ÿ$çƒÁ“é,ËM'Ò¶ˆÔ UÁ#fß®¹„¨ŠD14Æ$I|ðà¡å¥•,Ï}3©²I#ŒÇ#qneuɇ̜µVù—ïÿìgî³ÖæÆXkMÁFiÍÎ}Çw¼ñ-?þƒ»»ÛZ«ÝͯyÃWNƳÿ¶ŸKSê÷û1";÷ÉO|ö£þÄû­wå¹aþ`!ŒÃ3çÎ~ë·|ÝUÇíìli­æiO|Õ—M%ÙƒZ+…”e)€æ Ð$Os°YçØ2!'+‹ôš—]·¹iîûâùõ­Ë—7ÏmíŽÓL€1 T’D8›¥\ÌhÛã&Ù++÷`}QÇœè.ìà% @5 N’žs3ͳO½¯M"®_´ÏZù£wý™Ô>v)ûÃ!"}ò“Ÿ½é¦k¾ù_3ô´&$åA’OmáÚ5âsJ•ÒëÖ?ú‘ßyç­ÓÉQk¥vw¶ï¼ãæÿñÇ¿õ_~ãwÞóž?Ÿ¥&Šâ@+RZD&ÓÙd2¾õ–õW~æèуÓÉDUZ«å{)Ò9…+g¨*E³YJ„a Çãa¯¿Ðï/66âÙ=XÐñ@)é‰XΧH¸º¨.œßD›ÛÜ:ÔAAèëÿµY–9ÇÉ4BÕŸF¾¿”ÐGdÅP\’Ö¨…)‰QÏ«„rY›­ÿÛïWT³Î)!R«ü²Ðü…ÊÒç*\`Ùj"EÈÆ‘sÖff6U ³ÑŽnÑ¡.‚ ¸0 R…6Ë€ÅC *DS±S…Û¤ø—×Öø`0²cß«¿ÀÚ  ²³Ç|ÌâœÛ¸´é0|„ØÃ+Bý–ûöéx6OAªù»ïÿ?~òÉSïyÏoV»¦”O&£ÑØ7¥ƒNç k*ËÏ/wÅÃÝáù ëŽùªcG;øÅÇžõÙfHŤG_¨,Â, „d­¹úš£¯~Í«Šn.€h=™ŒüôøñW¿êeðÿãáxbk{7›åqÝ|㉯|Õ˾óÛ¿yi±;µÒí{îÜ[â_Þ|Óµ£ÑÐO§AÐ:‘íí­<ÿ¹w¿àöõõ /M'éÒÒâ‰k¯êº“ñh2ù©s€UjË¥qÆÀŒÎ»<ËÉ­:¶ÖØn»éö{ïa’,XêcPÒ…(F—‘FäP@®:ˆÉU[Ù‹o½æèÚêvÖwF[;»³Y朳b}mÓöÞ7±~Ž2[ÖB3s­ì­Ó¦ßÖ_ÝBUõ“¦–hËþŠ£êóÕéJÌP³YÇ—BŠkÅZN'&Ÿ¦»ã 3_8ýLšf‹Ý¾R¤¢&Ô„6KűR5ò­ ¥ ˜ú½N •w¡:k‹48ßÇ ðWO >äm‰8ÂP­:èG0s¿Ûïuº›Û»Iôº‰sN™³0ƒµ¹µÎZ‡„»;»ßüÆ7;v4ËÆˆ »……Å“'¯zôÑ/~Í×¾^XŒÍóÌÌÒt:¤i–¦;A„^¿×ï÷®;yÕ+^ñÒY:—¿ôž?þãÿvýuו”-ßÅþÒ,õ¦³RTA%î­fÓ©,/ \&"vœgÙöÖ¦"¯”ÊVóÍôË¢¬É';°§~a'.·éìêcWw^õꫯ½æÚëodgAw±uº"(nBnˆ`жÀ¥ CP$ ª¥ã«7‚šåv{l–=”ÄñæövnÝh4.=ÀmÊo'Ìa#\c¡ÊØ­³ˆ°>Jb²Î¿‘:zOŽO£t¸A¿5–üP ‹:>ðeùªjDfð³wØAžñx+ÛÝo\b“¯_8?è€e:ÍF(Åi…„ ²ÍÙ9¢"¥ XŠ>'X P„ÂÜï÷â82|'æN7›î— ›Eý­·Ü˜eY:ËQ8Moºézö=ÁEâ8¶Ž‡Ã‘snÄcñ#v )JÂA¿ßï÷ ýn§³¼8ø×ÿú‘~Q!¢Ùlüc?òÏ¿ûŸ}ÓwÞáGݰ;6ÖZëLn¬µD'I’$a &“‘0#aék®::íîÞ"ØÑˆûÖ„ ˆÊHgËÇ €Õð(c‹VDäS-ÀÔæªrUÄOEfvÖ™Üå™·²´põÑ5“MFg» JÓÍËÓ3Q Àe(™v™dÛéå3JQ|àÆ#Dœôz‡|äÔæîöÎúåí³ë ÈãŸæo6‰~ßGq‘…D-~]óWŸ©Y?oéê='mÕÏ7¸¢Aðö%í×ñ¸*ìP"’Zq €7Z0 qhÒl{}÷ô1äùÖæÎÂÂbžæ‡Önn&Sƒˆ¡&dˆ#Å9‚³ÂŽ´RD ýè_¬ (ïÓˆÂP‘ Žíl–ÍfimøUB@¥p6K_ð‚Û¾û»¿ùÓŸþ<;ûê¯|é]Ï»5M3¥•°tºÑ7½ñõŸüô}NQ¨‡­8qÕ5×?°ºÚô|‹”(ŠòÚ¨NJpLÊ“íá¹gÝh§“„«V:¼öÐêÁÕÅÅå€Ì+^þ¢®:ÜKpi°(e%va¯‡b‚F…èê¼8`#eòüÚ«ýÁïýÚ_þõß~åWÞ{ÏÝwXkÒ,¥ÂL¬Dh‘ýÄ,ÓÉ(Ž4 N'#, üZ¤³‰÷µÖ«)ÓP p-?„Ò›V-Ê«¤:=jR-xR Zè¦W ÁZ‚ý²"›® ͈P½‡Ø? ÷g!÷Y˜Ùc³™›Ìö¹týÔî…ó³ñØX£H÷=¥µ8g-ºr°—èKgÎ…¨W×úSƒO~éâ‘‹IÔc$;P´@‰Z8xpeùìé »ÇŽÿ‹÷}(ˆâåå¥ œîîîú jSwé7ÒU¥lÄ"õÚµâ½ÅD%¨rÁ[Ì ›¿Ô®1mTéU=SÛ©åþ/¶öõ×ÛP:Ä¡µhsȳt{+ïÚt¦Å…h][;°°2ÀN0Nz=¨oxý ÒiÖé†étbR›^Ôq’ ×UŠt~î—ÕK~Α·So{ÎÉ»îü—Y–N&#ð½¤A•3Ô϶ÒÌÀ…[³¤Ü¦Œ­¨»¦RlÀ‘FȞܬÚ@m-w][ò¡:[—ü+/ØYçõ&àžŸ)©¡áá)Ã)ÕG·»îñŽˆ8眵&7é4ÍîúôÂã¾ôðÆúec°»°uâíÍí<·È(mq6¿ÿ ÇšžÿÜãÖ /Ÿ·©UéNº{iwÑÙhõjë$ŸÎ$E½•#W­ýõî7V^qï‹?øñO:}6Ž#¬§‡í‘ŸYBYÜÊŸ{]:äëqZ˜FHX·Ïßäi–Ì)Š¥ãÜÎ_s½ÔPía'yºség³ƒkúÇŽêˆBÂn¶Í²3ÍvÙÌÂ(¶Ûš„;AÐkG¼{Aˤ×rÂ$䜕º‰0Mg³)©b|46®}Ž ­ÐȪ¼I`XÑ6¼îZªÆ‚ÔÒZ*ÿ•õeÙ^²`‚6|,Vu.mûQ`ž¦\ûç¢2XJ·rø$$öWÖæ™Ig³ñîdãâÎù§G瞺|ú̹Sçw†0Lú]Ò‘//f“l4šõzá¹3;;ifW.Ù™M‡; ‹ƒéD²íJÖôâ“_:ý°×?¼¶ü¡¿þ8áÍ7Ýøø“O“Q©…ZfpU0—¡Ðºí‚«ØRIseîÃ.$ˆêêœ_GT³W‘!Ú´¶æ½>m ´/ûýArާ» ‰Z\>zpu©×Mùš™é ÂÝ‹çÌdS‘Û4ÔZGI˜ôTë(Ž”Ù&ê‚鄨GHJ¯5 (4Üe€µ «-¢ý9¡É$5e·²(¯ÔÕîÍ2¬”j½Ûßou•Ü+Ékܵ—¦Ö7qPuºù€Ôz%éûJ9笵6ÏòÙ4 GÛ—/Ÿ}zãÜÓùÆÆÆæh23™Üå;3ÛíuŽ=°°0X]¸özÉ=/PW]s…äۉ;þôlj—{G§VÏ ‡ãxõZg[=rä9·Ýpëg>r߃gÏ_XZ^ÞšòîÄߢ¡æ× ¢n7TÖ»ÕôZ íÒóY‡j#íöèmk8tšïKÃÃÚ4u¬œËXó¶Î*ŽÑYȳñÖÆîú¥YHhgfÐ9»½ºÙu'-u£Å¥8€l¼±#Ù¬×$ÑèŒ1)tºZÎ7Ÿv£uL–ÕÂ!JVHw……ч…kû»„6H¯¦Ý=ÔßK­ÃOZc¤!v[µ§Õ7ê Ãê «Øy[¼Õ^ˆjíZ„Œób¥I*ÐÖ­"©&E•Eâg;fç¬3y–§Ót2šìnl^<}ñ̳£Ë—Фq–æQÒ?rèÈ‘«Ž­Xî÷;È‚,½~o°Ü wfÓéÚ±ã¤yîí7 ÓÉî®Å~@¡Ë2;*2&½n}έžzüô¹Sç/ŽÆcD*Šâµ¢p˜cå†dhfBÔöCiú—ÉþiÈ!u×Ë_^µo*ð}úï§¶ñËèòý¶Æ»Bœ±ãÝ §Ñ¦W_‹:{úÂÖÆðÙg6%a:ܺpaº³»»>ìö¢<Í$·ŠÐ™Œ@Ðå`¦eº ÓMÉ'¨ˆ¢¨©|Ü¡º)ªð 4î²TKXÞℹÿÓT«­ÓÌ­Iís•¹•`öÅžâ«ì‹CTýXS5A%@؉Ïél꜆šgÙù>>ź2½SÀÇñ—᜵Îc²4›M¦£Ý ëgžÞ½t1ŸN]ž‡Qx`íÐñ“'n¸é楕%¾xæ|E‹+«&Ÿn®¯¯Ÿ½@J¢ž}ô‰|2Ž¢pw8zòKgW.:y SAd¦Cp3Ý?:ÏΞÝÀ0>{áÂ,͈>€òOË.¯ÖÄ7·+¸ Œì”Ù‘ÕÓrñ«6M ß_aß]±9n)Êê…,­Ü=ú Aès„QoBc×A€QÄf³PcØïn^Þ °¡¢Ý4»´=[J4宣a[Ïf“Y܉ít²´!r¯×ãYž[‹Ý„‚L©1d#‹‹H!(_¨Eÿ*œBMGq)JêëlýçöY°Ê}âO]F™çNÙt³s¤(I¢0ŠÊŒhðÉØiš¦YV9“QŠx\a¾A¿ßEÂédf‹ùÚMW›|…£Ž‰ÔÂbœsÂàÇ´:gÓ4-­bÁÖ9kL–›YšNG“áÖîÆÅáÖfžåaœ¬¬t»Š(Kg»£í`¢S“¿á¦Ýúú³——7F<ø˜ü导{ûÒåSŸ=ytñÌúBv]–; ¨(/on z[nºéï>õY*Âuä·Äµaܪg¬–Uê­…j‘‹  ÒwÙ¨ núæ õÚ/ºÉš¸¡×õ ?t£èŒ…Y¬›A–²µÖZùÊb„—Ïc-÷v†ö@¢nþÑNoñþûž|ô‹ëãÉp¼:Nä€K5ÅÝHLîÒAu;M •R~¸9 ԎѺïQåK©á˾Hºº`ß,Ÿj»yºCô”ãó¹¼#ÉÏMRJùV.óUz,J©……¥Ñxü>õÐÃ>}n¸;dq+ËK7ßrÓóî¸õš«“M&cíó±æª"e­¼ïý7ÖÜsÏó»Ýkø“怩ÉdöÞ÷þÕ³§Î]º|)Mó~¿äðÚÍ7ßpËÍ×;vD†Ã¡µœ5¹ñC?&éd<ÝÙmo™4æ¤×YèuóÙäòæfÒéE®³püÄu.Ÿœ}ö‰ÕC‡Œ ò|´~içâ…KÙ,{ÎM'T­wœ\„yžwƃ¥“ùð"¦[q?qA؉;Ƀ?vðàÚ‘#‡/]¾ èÇ-7Z66¥MdÞ¥R•DI3 \J·L_o KÝCP×ÂqOÚ£*²¹ûMí)× ”~è‚Û„Ù9¯£¼ëDé@+m&°f2Ì:ÝÞõ7öÏÞ8}vgœª³g¶¿êkoºùöÃÍÍñÎ.)„n/p6tì4EÆ÷1ê‚òþx¨ºæ"J“¤r4¶ukðAeBqE1 fiJJµÒÐËçΘ0Š:ƒÁd2ÛÜZc¢0\]]N:Ñd21ÆV…2ñ³h¥'Óì¾÷ÿâýÍã?=›eïøD7ëÜÒÒàÞ—½ðûßôÝ·?÷¦ÝMÇ ÊÔÌ.‰»oý©Ÿ~÷{þ ^ûº¯ü½ßyGêFm±Xã-ç\¿×ÿà‡>õæ7ÿd°è3 3³ÓZ8°t×·¾á _uïK^ÐéèÝíakMfó4O§éd8n§Ó±°ZGJ·76.žŸgqwe° ËËÏ<þ¸ËFVllŒG£áúú6Û|yipô9+W_s,Œ"§hui08xèÙG޲$i¦éN²pµ¨(Y ÒÉhèø±ãD´µ½ç¦„èÍ€X“!Š-šN Nhíe5à¥rý¡l„Óü ìŠeÚQüoÕqÈ&@ë ¶¡,í|&-0’Wµ ‚ Önllj—Xîha°{yóôùÝ$Q«Ç3±§Ÿ9søÈ²Ëg‹Ë½Åµ#,¹U €Í£N7ì%„áÒa5X°ÃB  ‘EXØùä]¤Bþ7&uTKW¯iÁÖ ZG:Ÿ~ú¡ºþÆk§ÓI1nµXô®ò…¥¥³g/¼ó7ÿàŸ¼ïâú†16ÐÁÕW½÷Þ»¿æ _uäÈÙlV‘¯ÿÂ?ú£?ù}rqi9Ž¢……Ø_ ‘Ÿc@“éì/ÿ÷>ü¡Oü‹ñ]?ð¦ïd6ÆäJ+a@ÄÉlúÀCôú ½^÷ó÷=øì³g®¾z-K3ßcÂB D¹³“ñd°¸|`u%Ës?ÏOò, §ïÿ'>ôáOÝzËõozÓw¾è…ÏÛº|ɚܤ³|6™‡“ÑÐëy&ͳñxçÌSÏlmça2Šû[Fò8 ¯¿éº ;³‹g/\Zßž ‡/ºçæ^÷L–åa¨Â Z<.ÒJÆNɶ/¿å–¨w@â ‘f«Æqô=¾°°Çq’$ÆØÂnàîæ_h&$HIЕê ï9®ã`%°©ý3¨îzÙË~¾9#ê/4d}íbØÏ*.%hÃëÜ(sF"RHl²tû’nõ»‘É2BqgÎnœ»8^[î/­tG»ãñÎx¼3Q ÌlªÄÆ¡"…qov:¨µŽ{aIÅÑ]Ô±²€cFq§×£Ø1;ÇMwcé>«Mƒ¦ÙÊ"Z»ÃÙ¿ù'~ý7~ÿÿøÏÔwÜæ¼Öh›aaaù½ú7oþ‘·~ô£ŸÙÚg™5†ÓÔœ=wñ#ùÔ?ð‘—¼ô…‡ÖV³,«&oë@o\Þùõßøý(J”VÖøA‚ˆZ+Dñ}¹ý¾ï}~èáG^ùÊ—u:‘5@”R»»£w½ûÏŒq [[Û/yéÝ7Þx"emUSް„QøÈŸøÀ>ž$c­ŒÇ“éxÖ* ‚ /\Øøßùþ8‰ï¼ãÖÑöf–NgãÝñöÆdgÛå¹0‡Z+q£ÍõáönšÓ4Åñ(MóÁòÊd:{èþGv·w‰ø¥/½suy¡×ï-®,³ó#µ Â`<6~üz÷Ò¡G»Ço¤°§»‡Òt–ÎòáhòècŸ¿pQ+}ic£(KnÆ€KÛ¦"A¬>òŽ•§£²‚¡ è4";Øðg”8¤ám¶‡¨¼Fµ"ÂRÖï9Ðä„:p\}"eÖqÁµ"&ÏYkMÈ[—7¥Ílª§YªÜ¸Sg6gio©1ØnÊæx…]:iE$ˆ:q4è'ýE“»½Ìf¡±@1 º¤”îôz§Î\þ­ß}÷ÎöÎ[ÿí..tòÜ4]Á–]F¨Zç¬-:ÚúŒ´0ê<ñÄ#ùȧ–W’þÃOý|’Dßû=ß²³½£´ò,ÅŸÿ…ßøß{VÁÂâ’7F½µDqÐ;waógö?ÿþï¿C)UZ±^>±ôt–£N'c "13)ìõ:K‹ ÖˆµîúN~êSŸÿþ7ýø»ÞõNRè¬U*ÜÝN')ZÇÆñx2ñ–ìóaæ~·KDþ¦ÓéwÝÖ‰“O|âÓY– è ¥Ô/þÂ;»aøê¯xÞîæz6›f³;ëg‚°5Æ›ça. Ôxd¶wgNëÎi6WZ^Y]8qý‰ P¨4ͦçÜÊÚáæf6¥íu³qæÔñ•øÐ5‡‰Îm´¸ˆHé$}üÉ'»ƒ…—½ì%ùè'ãlnl”­Y¤Aûå³Vt«GX}RûŽKJ¬4@‰UêÊ_ÐÍ€V;t¿HÖãwÿpXÉ${|æìœ”Sp5âx:›* ˆ IôÒÝ¡‰b‚±Is·Xc³2c¿8¸$ahPiG³i0„qœ¶Ül'Ö$@â$ºÿ¡'ÿ¯ïÿ·Ö·w‡;Öº_ÿµÿ;Ë2DåWÂZ»´´òÑæïü}kí¿åïºëÖÉx¢¨h\¬•>{îB”tˆVÜÚÚöÊœ³‹‹ËïøµßyçoþÁÁC‡¬É1ZëÜfös¸ò<[^^úħîûíÿþîõcÿ׿æ†R„%  X$$Êòìu¯{åóîxÎp8Êm>OîÿÂÃ}é™ÅÅÅ^¿3Üݽù܃ïùã?}Ó÷}Ûæåm"šL¦ÆXD2yÆÚ9¢haPf—$ Xg}[Ò—ÅKþÕ~߇?ø‘ÿñÞÿõ—ùA­ƒ0„]'éþÂ/ýו…¿êPw{:ñŨ¾÷ŠRšm†¤µVÖ˜H»…>dÎ,DáÚáƒËK‹Ë«K§žyvyiqeuÅdy>›--/ó—{zs}ëøª¾áÖë–úèÁpT,ˆ¤;÷¹ÏÝ—æfaaáÙS§|C;©%£ ¹M—>ò%e@ªØ®—ð€²›V¡ëK6(xBWk†S”ždi@¥Ú¿Ð¤·±4¶¹£uláaç87l, è0 B=Ìr¶0ìô:ÛÛ£0„ÔÈӧƃD?‹põ‘þj_'²™™ö£ÌÈÚ‘CÝ J3 u¨%r–HH¦»²{ŽH7‰{ƒ¿ü«>sêâñcGWV—Þÿþ}êï?ÿ»o&@äw’ÎÇ?ñ¹õão__ßI§ãµ#k÷Ü}§È h~€H_züi?Õ+ŽÂ¤¿ð…÷ä&ç\·Û½ÿþGþÛ{×âÒr:›ù£áèࡃËKËëë‡ÃQœ$Ææýþàÿð~Ý×¾æÐÁeÿu!B¥•ˆ)ayãßðõ_ûªÝ¥HD²Ì½û=ö ÿé7¹×I’dssóàÚÚ{ÿ䯿õ[¾Žâd:³Ö¡òÅ=Û[»…^ž«—ÒÈìD¸Ðé„£Ñx¸»yÛm×Ý}÷Û^óê¯øÉŸü¹Éx†š§SûŸ~é·~ößý gsk¯~'f!Ò: u@‘h­uWéN·JA@J;»ÞIbgÝúúåÇ׆»é†ŸùÌO<öìB×ÝvïòUW™t¢Žêþ*ˆµ’‚å•åçÞvóG>þ÷ÆZDœNg…øo•×Öp-ÆKº«Œ…¶¢4…°n—P–rþ& PÏ÷†š”܈›´¼ö€K;ËZ¢*ˆEËöF@â8Œ b•t+ ”Z\ê. TÒQA¨:‰î̦©;{qöø3[£”s#Ï^ÜZߎfíÝó_zjã‹NŸyÌ ·8ŸH>TÄâÜl6]ZZdavLþÿñ¿Ii.{Ò¿û»ï¾pqóðѵk®;ùéO}á©§NÇqÌÌ @ˆiš?õÔ)­´ úƒ«®:œe™—ÎÁ/ýÊTT4±‚0P?ùÖûãw¿óþð×þè]ï¼õ97N§SÔZ]ZßüÛ|sÞ'g¾LÇ?Ο_¯Ó—+aY¼U¤7°°w@)…ÖÒ„0._:ÿêW¾ð?ÿÊÛµ"cœs¦ÛINŸ½ô§ñA­C¶®(#R •¢ ÐaÆQ뤄!ƶB¨F£I¸¹=Á³ç.>ñäÙ¿ùëlolÝtòÐϹ®·z8ZX úœÒ$Ì @,f¸3œ¥ùUW]}óÍ7ÍÒYÇUò~ëo°®)®¢»ÆŸ’R÷·¤M)]~;ÖEƒ°e/Ô¢Úœ°çë Ö€²°¢ˆ¡¡ÒJ‡ZLjŠt'1 ¦¹Õ:ôyeD\‰­%7\½þú×]wäöÛŽ-¯,œ»0~öôÎÓ§¶~äô“_:ûÀÇ>{æ¡ûÏ>òpžŽU¤y6–é°Å(Ò |“’ÜäýÁâg?÷Й³Ã(bf¥Ôp4¾párÐ7ÆXgwv'÷?ðHÅŽY´Ö»ÃÑÅ —Â0D„,Ë®¹æøÊÊ‚É ;,,¼ïý~àG®tì”"Çæ~ám?ôß~`µ«T~ÛsN¼õ­?ª5:Ë"Néà¾û´–‹ä¥”Ö÷º)¥ò<×ZiUüß9¶_ýš{­µÓ4ËuÎf™ÙÚÜQJIÙ‰™@Æ£1ÏW˜ieŸ-¶J©( YL±D/žÁ]7¿åßüÀt:F€<Ï“NüéÏ>|éò®Ö©‚¦Ã¸¿ØYXì ’^/êtÖŽ^Y[’°×ï¡Ï_ÞÞØ=ùÌ…gN]>}æüB7~Î-'zƒîÊñ«Œs"Jw±Æ/‘zú‰'>üá¿ûèßýý`°袥vò´ªa$&¥cžX¥&@‘šúKù\“~uZj.Z“° Fù29WR z_B„ ¨/íÿWÞç„€ƒ°3X`€Ü8Ò!)5ÏØII'ê: Kýåƒ+q·sõ‰ã·Ürü·Ÿ8qÍêµ×¦OýÃÙîPi:tÝѰ7pÙdœŽ D½áõ_ÕëÄY–;ÇJÓÖÖîÃ?–tÇ. Ãsç×/®oh­cg?þ‰¿÷#DDiuùòæö¶§˜ÍÒ›o¾>Ž#ßTb–æïþ£?ëvzY:ë÷zÛ[;oøêW½þu/¿pá¼µ €›—Ÿ{Û wÝùÜÙlª•Ž¢à‘G¾tqýrh_WA§Ó©1×;.„d];¸'±m–¦™ÖºÓI˜Y€ƒ ¤ÂWø¨ˆ”¦  ‘Öj:™­_ÜÌŒ¨@!BF³Ì‚Ö¨‰…wGãÁÊrw¹»vpùªk|õ×¾ôÅ/¿õªëÖV®->Ú[;¬z«.*ÒÉl<½çž»^vïÝÃÝ!“3ã}_xÈO¿ ÃäÁÝÚ*R"`­‹âø¾Ï=pöÜzElÖúÙSgÇ“i!¨=ïy·2[ét;÷ßÿð{Â÷ðȲL)zÃW¿:Ïf¤Šù Çú+¾âE 'I/^úâcODQâ~i­zý®süosq1ÌÏöúƒû|t2™ÆqL„ÓivÝÉ7Þp}–¦EMc¹´…ã¯]dR¥¹±Ò ýÄUð±ò5É>ùS„ólüßðš $ç˧Î\ôóAÀkßëN)Ñ¡P8ÌžúÒSŸùÄŸýÔ#Ÿþô£÷}îKãÝ X“„tð@çøá¥k+݅Ť· âI;F ½}ñÂl< » ¢#¢$¥V€´zöÙÓ¹±Ïž:E!;.$wƒ ÊþIR‹õ f7‰»’í÷[úB %š¥Îui’ö>R¿¼ª=õrÐà²6{HEôU]S#‰³HÇq’ZM†Óñp¶µk¶vmnœÖˆ¤¶v&ç/lÓ܈ÝÝÝ;=ˆâh0è®.Ÿ¸ý¶“wÝuèÆ“C׆ËWSo•:K’ê €¼øEÏg¶„16ÔC}i<™>öØS~a;?~âÒ¥~àï&—œw1;²¤¯9œ=Ô9z¨»º²@Jw{ƒ¸×SAd7.¬Ÿyü)Ë,ÂÙhKE¡î.¨sgÏŸ?ammM+ýì©ÓI’L§S"™S-ÄQẶž«é·¥šø§–Ç-%@5?µÌà’Ö÷ƒ>°ÏG•ãª67*™_rVM/Ø9qöt‘Æ VaDx¸3Y¿<»´1rÌÓ4¤ ëÛIoÑ8ÚÚÜu£éDwúÑâAì-ä*šävœsΔ«ô®v (ÎYs÷óo;°²dŒ¡0ŽÎž½péÒv†³4öÔ9­5ûQr~TD ÿæý™Î2¥µµü̳§=§㮾úøÁµc "0óéÓç­u,€HišÞrËõ,[gê„OD“›k®9¶²²0N‘H=óìY?爰ӉªpÊÒòÒòÒr·»°µ=þÔ§øwoûåïûþ=Nã8áÉ$½ãŽ[¾ù›^?íj¥¤è!W8èüX¤õW©ÀÒXjNâØ#lÇÌ,yž{èUp³³&Ô5×egD)µ¹=še¦ègê€|w­£p°¼|øøÑkO¬=Ò][묭uW–’~7è$1¢FÒ*J”Çé¬Ó[ºtáÜl¸½sþT¯‰ˆSÉ’1æÂùõgOyð¡GNœ“‡ùÚž0,-tÏœß8²¶:ͳqÊ ‘Þ¼xáÀ±k{k×r> ¢ v‚0X•dA„lžÚÜ\sÕÑÛn»ù#ÿû¤7PJïxòÔU¯|á¹s—OŸ:AQJ ÌA¨Ÿ~êÌÓO¹áú«'“ôÌ™sZi`¶7Þp2IÂt¬ˆ²,êÉgcDòES÷Üsg(f!%•gØ97ô\½|y+Ž#fÖ:Bd"ZXè;gE„>ú±Ož={ö“Ÿúì“O=»½54Öu:0|—wû–ÿá~¿3ÜÝö-„£0 ô¾;!?bCÚÛÒUι(Š¢8ÊrÃŽ­µY–‹ˆˆ(ò±cÍ®ŒˆD8™å³ÌÆ]eÙ[€ J?kö:Žâ^¯;›<µ† ´e·²¶6KgGŽ_e\6»ÑöV”N†K—gÛ—º%YZA§§ÎžßͺÝî#_üæÆv:Y:SBs HªZ)2€ 4„ª{®Ýÿ ·?̹B«´È¢IA#]¸QkPÆn”oÔÌP±î¯~Ûô_†æD@P[G@¬ã 7P;.Ë¢0LÂAº;™dãñl6Éâ8¼þ$8+YjWW]‹™Ä½di–Y Är`°£8Öñ  c׉ô‹^xç>üwˆÀ ÆØÏáá¯ùšW9ûÐúåÍ ¥XLB½½3¼ïóÝñ¼[ž9}îâú¥ |ýÚk¯AŸÄ–®_ÞP¤™ÙYÛévž{Û-&7¾hu§,¢´J:‰cö³m¦ÓY9æZDxyyYœsQýù_üÉs¥t’ÄaE1Zc•R¼qyóÇ~ì_¼üe/îîT³%u UÑ,´èq ‡G)ÿ:ç’$êt’‘/ô­„?@‰ƒ¤`.)¥˜õ]×6 ”åÿ! ‘ ‚¸“ôÙd,Žmž÷+ˆr._?{™„&;Û<ÙÕDÜ[èJ4XÃpq8œ¬_Ú¼péòƒ=Üí$Öšñd‚ˆqä£` ßÍ<TÑü²NºÈþ©x[ü–Bëä„VF6ûµ«Û‰¡²ZEÔ'hýöœ(OVÚ1D$Î!¢QaLQ7ˆ"d‡"a ¥ò4‹BŠ‚4ÏÅææ9·vpéøÑ¥í­3 Æ:^Á@w4m;ßéõ™.9õ–€Î3f¢<Ïî¹ûÎ~¯ë¬C:ïàacñé§ÏL§éÒr'ËsßGHiô±~êŸÿózéÒfšZ"Åâ‚@_sÍq•V—7·wwFZkD4Æözý«ËÖšªÑVEƒ„êb”78Ç>³ÚaºÝnYÍIw;ŸVmÞÑ9›NÞò–üÁ7}×h´[6§(DšRäœWè˜ýãFi^˃ÎÂqv:‰Ÿ¹íœ £йÇ~fƒ f¹-+$ t\5Q²ðÙàAÑÂLu;yžÆÌ6Ëg“Q¯×_?{:Žâñdš$Z’~7LºaoA:X-ž?}æ‹=9ŽN=ûìÁƒ8°³³ÇQ–¥e3’}x FçeMo‘ã+"EŠNݶì"uîg›h‹‡ž§õ¶©¹¢AÉefqC3Ô%5K̽ôÔàó(‘(D8ì,ÀÂÂ(O97ˆºÓí sQ 8›§rþâF:™vã`¼;{øó_¼öäÕƒåŽȆÃHÃM·_×é߈:ç˜Gö³4½þú«®¿þÚ~¢×ë…aüÔÓ§Î_¸ôô3§«%Ðpâ@:ÝÎgï{ðK_zæÒ¥­,Ë•Òâd°Ð?~ü°19j­777§Ó©R!"°°R¤´*ú&K]ºå‘zVþÊr8€©±ÝN"P8Ù™¡˜OE>’8g‘è%/¾ÁYk}̯»ïm­©¶¬ötàˆ0»@‡ý^ÙAÙåNŠÒ3ÏìÓå·¶v )ÐZû½¤ÛIòt\eC¢ßÿ¨¿öÍí|äÔésa{(Žôk¿úµïýŸÿ[Q@ ‡Óì¯ÿúƒi–ÇÓÅ…±öÄ¡µƒ–1Þ’g9³„!eYn­íúIû¡Ý!9ëÒÙŒH;^YY Bm¦3ÿ ಈDŽ“ØZ‚yž+¥'ãÙ¿ûw?÷žwÿ¦ÖšÙ)ß(|cÝBß`å …’þÛfpáRÔéÄE±%¢7ˆEØ;‰òÜ }é™ûz,‰ã0 '³Ñu×éõ:›Ù¸Þ¸B ˆ €P È™Ó,c¯ß];¸Üí$ñ ›@ˆA °Nâhqµsð*«z»—v>ÿàñì™óƺ4Ë:xéÒ¥Á ?*ç¸lS${¨C¦ð_”mTê>%”\÷‘¬Skáá“áJ(UOá“Fz6övÿ4h1CÕn Qªç;A{Ù @@€¹u ‚ÞÊQ3ÞšvA$KÓétÚëuãN’O§ Ë+!ÙCkKY:‹‚ÀÎfJìê……åÅÅÕÕåÇ—¯½%9xF ,àC0°€Ržg/}é ÿûïþIÑKÕ½û>ùÔ³ƒA/7V)B„7~Ã×|âãŸ>}æbœÄÝNç¯þϵVI‹°±æê«õzÉh˜ù&Ïý~?ŽÃ(ÓYfk(¥EÜ\…"dy¾½³C¤;çÜ7œ %\l¤ÒˆÔh´ûº×¾âÍoþ&K––Þþ3¿øÁ|bõÀYš.-/ÜÿÀCïú£÷þ蛿gkk‘ ×úl%tìq{{×X‡e9yþ”Ñ.@ ‚@ ç©äY"¾„WV—Ïž]ÿ¥_ýÝé4ëuq/ºç¹¨(#¶– pUV‡@è'¬é °"ÃÑt{w|îÜzpäèÁ»_p[j¥u‡ý…¥ÅÃWG®šfpß}÷?øÈã³4'ë/N&ãsçÎt:ÝÍb:Aíµ¬ ߪнL)ƒÒ›Y–8‚ˆCåÔÇò[%$*¿]u-¨h[W¤]„”£v §Å£•‹2ÇœõÌ¢fÈ ÀD´ÆCÔéY“E‹ºãÎ&Ž‹ÂðÙlªH”VA,,Æýî!1yÐ!X8||ñÈUñÒAÛ]5ÑRöÙfÖ?OÎ{ØgizóM×8yÕSOŸâ0ŒÂÏ|æXXXȲœˆ–——n¹éäÏ»õñ'žñ=/_Úʲ,Šc櫯>ªˆ|@Ô9^^Z ú"˜ÎfAœ=söüù‹×8’f¹ÂZi#Ñt–îì…™™‚0¸ù–ë˜]Ñš¬ÌVc̱c‡ïºëÖË—ÖuþìÏüÔÎö¿yèá/uº½4›Â'žxêüù‹‡Ö²,¯Z´—"´0Ž ! ƒ ; G#k-¡ »»£¿øËüþÿó§ç/\Žã(N’ÝÑso½ñÖ[®n]Ž“ŽÉrksq¶ƒ…ÇWi¥ThãØYÎ¥Yo4UaDqÜíº ‹ÝÅÕxéèx"éôžxúÙÏ}î¾…ÅÁ¡ÃGΟ? A¦iê½jXÉîšv°ÈÚlb‰ZÝJÙÄ…¥¥(ª®w>RN”¨°jk…Ëi½Óü¨¤g‘æÇÅ—ê#*ΩõØ”E$HbPÚ’Ž—Žô–$Ý„Aà¬Çìœc7Ž-coqiíØ±ÞÊÁáµ¥«¯ÂÁbö¦Ðá «ÃØ´r–0ˆ1v°Ð¿ó®ÛÒt¦”À uøJEcìµ×ïv£{î~žR„€Ì¢´£ÈZ (JÑÑ#‡DØ—T8k‹ƒ……ÍsE*Ðz¸»û…/<äSŒ õX  67w··‡€`­[]Y¹á†“y–ÐÁ‡ýòZç&ãÑl6Œ‡‡.ü—_ûO‡Ö–Ót D´¹=þµ_û­8êˆOdîvº>ˆák?Fãñææ†¯j/Šˆ4Qháß#b·“x±âó¨—œè?ý‹üÀ½íç~ñ·/mnGqÔíö Qþé7•R Ã0Œ“¸“ÄI'ŒF¤•ÏC­AiÔ ˆH©0ÐQFQÄaÞb§¿¤;ý™ÁáÄ]º´ýwûä>ðÁíÝS§O=ûô3gNŸêt’õõKþ‚©*Q))·ñÿBå4Š`r¾VtRï>P¸J€«"ã5—AjÐl“–îåæ9ê¯J-Ó«cÊŸ«Žn^´þñ% DÎ êÀ¨(\>ÒYZ‹:ݤ#‚RÄŽ‡»ÃÉl¶»3ÜÝä ‚î‚î,QM/‘x)î/õÙäëŒî·ø”Ý=/x²°RÊ9Ÿ]cl~ûsoÉòôÎ;o;zä1†­5J)aF­õ¡Ã‡œ³~ísý~÷ĉ«&ÓY"¢ƒðÿüõÓÔ Ö7o­ív{ýè'wwGJ)kíu×_{äðªÔÕ ŠÉeÎZD$ÏQ£cGWÞñŽÿ; 4³dY:ôÿê/?ô'ò×+++ìØ97tƒ.2DX‚ Lâ.)m,ƳÝñæöðÂÅM¥ƒ"Ø'Òë&vÈRóÞ?}ß÷~ß¿ù…_úï§Ï]îtºQ.-.Äqxþü¹oý–¯}î­'µQ‡q%qÜé$Ý^§×Oºý¸×{½¤×Kúý¸ÛUA€D:“N'IâN·Ûïvz£ÞØ=¿ýùò™³<üÈcO=sæì…,ÍfÓéx<:}êÔt2 ÃÀ+ÆšÖ=N¨L(‚ш„@UÑWE˜Ð"ðŠô¹žê4O„P3èf@«š¸X–K¥KìðeM#M”´ß—E@رƒt—µ{Ad‡—ÄÌ49p<OÃ@Ïfé™3çÂn"\>r“~–;?q£Ž"fvE^ —@J4{Î-7X]MR"U9Õ<Á=÷öçÌ&ÓC‡VŸ{û-øÀ'Â(4ÖhHä3Ž^³Ö6œáòÚ¯zåß¾ïc ýÑxÜévïûÂßøÄ}¯yÍK._º¬uÀâVVVŸxòÌïþî{Â0ëì _xW' g³q…P‹¼4°ÖA1E­wv¶^xÏíoûÿú-où™þ‚s.îôúg~ùšÇï¸ýæÑhÇqЫq'âÏüÇ_ít’áîh2žÌÊ[ãÑðïø™ßó¼í­-­õ‚g†N7ùÀ?þçþ׊t¯×›¥³n- –Ǔљ³ÞøM_û]ßþ†éðr'ÎEÄ>dÞª•¶&O'cRwÂ(Œ„9 Bgóåå•§O]º¼³k@¹¸yõ$ŸMÍ©Óg­ëÝo‘+•"fy~èÐëo8ñÉOaqa3ãÝ—"²²²tÍñ#yžwºÞsç>ðq¿h>ÈsèÐÚêÊ’µÖ“šRj<¿ìÞ{n¸ñÄ¥Ë[qœäƪ_ø¥ÿròº«¯¿îjk¢àþ¿ø–óöíaFˆ”Äñ ïy^žçu§&¿¥“!ÏrïªóK®u°¹¹ñ-ßü†ûïøßõ§Ë++Ìn25ÿî§~îÝïy§" ‚ ×”²ÖÜÿ˜cö‰Ë¾Væã¶æŒ¶ÐÍ•ó"¾·íå™Ò8+>¯Þb–Â2'mX%Ð ƒ¸v—(Ý&›±5*‰£…E ;ç/n,ä ›ëÏžê€UZç–,ƒpÙºi§0Ké;î¸õã÷9TŠlf»d–fÏþ­+«KÃáŽÉ³ÜuûÒâÂ,ËµÒÆ¥•1vyy¡Ó‰òlROdÇýAôO¾åëÞþÓ¿tìøñ‹ë—ã(:uêâ?û®}Ã×¼êØñÃ_|ôñ¿ú«8Á~¿—ç&7Ùõ'¯½éÆ“Ó锈ª Êދ‹-*¶ýD4íþäOþÈSO?û™Ï<Øïw{½îc_zêþû¾÷¥w+¢C‡×ü°aA„¤#"qé” Ä¤Ó}ò‰Óãñ¸Óé<üðŸøø'V×1³NÂ(ŠBEJ„wwG[['O^ó¶ü®—¿äöÝíËD„Aè˜H©¢qÔó±ËDúFÒ •R½^2ÇIbHEq¼¹}fk8Is3šL£¤[cÓÙ̇‘c[P\í㯥x5î¹ å—­áx‘–¨ÃE6´ˆ“ÿÇ#-@FDÔÌŒE"3–ãÊ;mŠñßµ/Yê•iy}ö„)õûìK9½kI¥~\…ñ ²ÓŽ‚\n&ÓK—/mÇAOF&KÅ—†ÔfPÃ÷¡&kò»_pG…¹1ÞE£uç»_ýºW dy~âÚ«n¿ýæ~ü3‹ ,ìòËË A ²™ B/œH«ÑhøÆoxÝßþíÇ>ùÉÏ--/¦iGñöÎøw~çÃ0”~¯ÇŽ'ÓéÒÒêÙóg¿ñ¯ïvâí‰Vºp~!:ëª F¤²_õ9Çq‚¿ô‹oû'ÿäû66·ã8,Nž8iò<èFK‹‹õêù.3"àœsÖ1³Rz29²Ö‰;³lzõ5Ç^zï‹>òÑO €ÎÒÔä6ÐúðáÿìŸ}Ó×¾á ½`¸»¥ˆf&!áEC­P XXé‘°ÓéXk•RN« ‰h{86Öäy>ÐöÖÖÒ`°»»è€D] =¹W!ÞÚ=_†± ,[‘YõB܇´Ê2àrLS ²"­!Šª: ìJB—êj´Ãúÿ®dŸgÕÍ+®bÉuvky½^›! #ÅqÙA0ÞÚÒÓÍN˜Ù,^ìë@ÏfSqN@±03”!°ÒÌ"JÓô9·ÜpòäÕOòÜ[oºñ†«Éx¸3µ}ÏQ¢Ú’òò·ÑLÄç‹Öè $…„¥´Òìg±Mf³0P~.÷txÌd0+¥óÜ8kA«*½»Ì‰iÙLÖºÅÅ…»_pû?<òxGˆeé·Û7öºÉÎÎŒ”RD“Éøå/}Ñ7\ûôÓçu UšÎZ…²§j¥o‰ÈäfÐ~ë¿þüŸýùß¼÷Oþò‘G¿4Z?ÆË‡^IÑÉ“W÷w}ë7½ñõÌYsšYk¯¾æ8çy6^÷ºïL’Îd2l4öAÑZG£{_òüwÿá;Ï_ÉK^g3­õt:yþòôÙSwÝu×õ×_[¸>•J’8 u µï€Âì¬ÍMži­&ãÉ7\ó;¿ýK³Ù̧ˆ³y6N‡»;èUHI¨ÙíFª<‘ ÀÂ:Ìu’¿/¥új'¤Ncâ PJ;çÒ,ͳL+]íoQ;Rv[*ð½TÔ]?A¨¤ú¨Ò%£”¨¨ÑÚ­°*šyXL@aBÔ>+Ú¡†=yªÛÑÍŠÑßÒ¥T |©•@98x^±ø„¦ÂUÕ:xˆˆ"€Z'˜h¥2¯¡9ÇÆäDRLñ¬Cm­ 4"‹»óÎçþá»þDÁx4:´¶ò’—Ü=Š10Hì\¿ß{Ó›¾ó_þØÛÐ(ðóžwËW}ÕWL&"(N¤”1‰¿ë;¾áë¿î«>û¹û?ý÷÷=þøÓÓiŠÇ}Å+ï}Ù½w/-ö†Ã]„"òím3"Jg³ÛŸ{óþÈ÷½ëÿß_ü…ÿðßñÆédè]áõ±žŒ'·ÞrâywÜ4œBá$Ö?ù?✵ΖU]Âì˜Ó4õiže´j¬ëìx´…,’e~òQ!ø‚ òhG‰T­ó ¨¨¡h¬]!ç´”üH EJ‘ ‚À2á`ÐSDJ)­rÖj­ ®]’iUÝÜ;‘ª“¹¿¬ÃZy5º¼A÷ÚÏôl¶7©ùÀÿ5xÝ@õèq©–«…êoíÜ©n¶’ÞWm”œ,­«iqlÃü._ ¨ P:ˆ¢h–刄„ÖZv–ÛAŠy_æ¹¹áúkƒ7._4ýà÷ÏòRßG÷‡)­'“ák^õ²ÿþß~ù“ÿ¹ßÿ½?ú‘7¿íškŽmnløÊ๛ðÒzggKiõН¸çÕ¯º7ËŒuÖçô"Êd2ÞÙÞÒJ{Ú)‚š…æFvùýÈ?ÿŽoûúW§ãaÙµ¬>¤rw¢YšMgi =À{QÇ£Q¹ì¥â„¢~¼_¥aYyR&Ò"Œ Š|:®Üå¾u¤ˆ ¨R×dUßò:=A‘ÒJkRä2Gª¨ $fév­µÒ °ä¥šìk|~¡Y¸RfµŠHùåòò† {¤Ê‹þǦ…”¸¨¡ ÊA"K PYÓØª‰i¦|J©ð«± €è“¤ÞÛ=Ìèɺ°ŠzäÒ*uHÉdE±}q!J)“¹âäJ±pf¬sŽ¥Z¬4"yˆh¬Y[[þÍwþâÿþßï{ík_õêWÝ;îøô²Š¹I©ét|ïËžÿÒ{ŸÿÂÜqû·Ž†Cíg¸ïwGˆ¨”‘ñhäY“Ê(›ú—Å@ÈÕ»!2KÇ‹‹½édT”Þ6”¹Ô|¾Æ²åÃ÷­ÆI´¿^´ô+eyó ¡ô0¡`-”¥Î;hâ‰VSAFT¤t H±¨ˆ”rÌH˜fiAøØõ¾Tiq@UÇU2V+\T¼#e„ PuŸç†º€¦"h‚’êÚ*ô#’°ÎýiZ×X,BmÀ–‰§5E´ŒñÂÚTå-‘\Mqžs«tÑêÜŒ &AÉ• `°Æš, ‰š”!ó?Y¤!+¢—½ôî¯xù‹­ÍG£Uñm\)*£Ñ_ñÊÍÒÔ7Îo^H{y h¸1“ç—Ö”ôÙν–ÖïÖËÛ@íC ñú¿ Ó– mÀ‚ö«OHHHˆiMš,;ÌqDÄÎQFï»ešAù·y é»(9¢<º¡D¤¬¨ªÜœÕüG(ç#56´eF·i% €€ÆFÆQeSúêÎÐÍuiíSmwWëNóè÷û§º¼†9±@•)ˆ¨´"ƒ¾0׫•crD¬Ra›¡†æ¯{P7ýO©ã5VJ!àx<&¢zÈL{fLæ()¥á–kaóJ£ùFRÌEu®øÀ†¨È½4½jΘ‹Â4ôIqCÜø” ìœYGy[·Ý˜ôQ!$BRJi¥”uV˜€Y©(ŠHi Bê7þÒõïË m¨ß‚ÒÁ]؆Õ*¨Ú£‹Ô›^­U™6ÚfÑXzc‡ë¿}ﳇ•ÜkÞ„ ÍÃÛ{¼çi-ïë««ì"Ò:p,ÖX¯’|U–\[̪ ™P%m Azþ«mW@U87¹²d”²`¢½i-ðÓ˜ž"¾;*­U  '’"k8ÇZë(ŽLn€¡ü BE#’¦I\i ªMnªihަ |Tu¼…ÒÛTªè†<”ö2 ˆ. ºú~í7GØ­çs«Õ|¿ÞööŠ_‰L®hË"An‹ˆ_ÉÌÌyžU]Ò ¯[ï{ ¨j•л-åÕzZ“¹ï45¶?h‘ܼ3jÿ#dߌ!WX“Ú£ÿ P¥d¬W¯æ†ÍïVCß×x¨¾­Ò<@ß)%Ërg]†ìœÏ‡ÕJEaÄ‘JÙßpµd¿ˆ€¯*ßk‹'Ë?ò¨MÅWkÜH¡Ày*ÓˆT;ºš7?GÕ l»3ì¥þ–}¹—pîÅ—ud!‚Òõ s ‰ˆLžWþž¯D1õbÌk­ÆU·ÍÖ=~Ÿso¶äv•²—OJrÛ EšÐ¾ºa~˜ü>ç»ÒšVSNªváÕ<‰ÚŒÞ{ßuA|㬵]ˆˆD:7&·& cfVZ3@EaæÆ"QÑ~¦:Wâê%‹jÓ}™ã[çûCñ_ŸKï_¡Øü­–>£ruR@P—¦•Eæï­aÕU*¡ýÌú~fà—¥þ=$4/[Î ­!_PαB“e„Ø@ÊU. 6‚ŹšfŒTï´ííJ.–¦ý•±Í„›*ë¹äûßêÃÑ<¥4¯¾¼ö=b¿ð¡ÙkhXÀeï{iÌ Üç>ÚvÞœ —šþýÉH;gsÓítÙ¹0Ц¹AÄ$ަiî‹+îœ=®©¸ÅÜLì/&xC‘óN"BX1ÄŽ*#R‡ô©Ê8m¢Dí;´â0{ž6é»­%ڵؤ±œXnö¼±=·ê-èÞ.  É;@ˆ:æ<Ïç(³Š¦”$SƒÞJè6çd^‰;¥4ø°²pç‰x/å4Îò–ιö%¡7ï|ïåI[ï7<üÕTˆúÕ•€sÖ6ÝWØm»¡bÿÆY«¦F",âX 3 ¢&çkä}ó8DëØ9“$1 §Z%xPϵç_ªóµâþ}j¨èŸÊ?EŠž°°û¤ú¸Š#k+£–ú¥y\ª D, å“+í~h‰@l½³W ÌÅæAmÖh2/ˆ¯øPä=?Î9MÄ€Æä ½k¯‘´ {(fîW¼LlØM»³°™[.ÊyzßÄÔ)íMâ©ýѸ/Û\A„Ë>÷áï¢Ê–jåCíׯW¢ä/)uKÅpe$­ü­/¿Þs,……@Т°¤ÓÙ… ›OÞ¸7ã@+ߤȳY'¤4iŸk#" ê˜WIå-3·pA-õÛ:€Ëw|ùoRÑá‘DØÇôJŽ)c|-ö.‡ÇûeÔEHhÛ«Xé}P~‘Ûæ‹ýX¢ýyƒú±õ²zSö9Ví½ÖJ˜9¶, Z©,Mk™es±5?³IÔíË( [hKü¦ù·çä] ï5ÈJÊsV™]{ñ_󜅤oü4WŽÞ:p^йcCõ½‚°´ >¯‡ÊÜNªkm‹ÊÃfé-0;ñÍãœÿkM–¹ÙLM'AšÝ|íÍö+:›Û»Ç"Dt`­âˆ”V:öÝ„ j‡†üÇ‚ †€ÚÓÃ"e¢ŸYÈÓ<3©¦f!FfÏ ÂŒT®Ð•/­RC¢N@S“J+²iD4dmÂoýWSO ;}BÙK—>LiRÚ—~ûDTyžÍç ÎSSS‚C›ìš„Ö”~ØÂ¥åÜ>qÓÀlúöM,mï"V^Û(ÐPé/°Ê/)¿rl‰Hãb¤ óš‰Êþ?"À:·ÅÎP!ifvÆc1Ö:çLž;k­16Ï­µyš9gMžg³”­ËóÌãüpÇÖçŠP@/ ^þ‚[7·¶GãI†¾*€ÇQ¬‚ ÐãZöWп¶*š‡3´Í_*‘Oú#¡¢£¯Øa"?H„ÉÅ)#…Á_ @ûéˆ ¢÷tZú#j`Ô´ZîÁÆlŸ§!ç$äþ6@ !Ú ¿ÝJkRÐ ";fDÉó\˜Ë¦}ep¨f,)´¾ai ÜϯQSUzŠ,®¦m­¶¾_¥þ½â¯ïÅVŠ–ZÒ·¼jR‚QD…eYxñ‚!PÃ"ì,;ç¬gœ1Îk­Í2“ÍLžçyfó<›¥6ÏMžçYnŒÉóÌë¬uÖŠˆ5VD¬³ ~f€RÚ#ìj& !ù‹'E€È,¹Ëi{w¨­´6Yî)8ϲ$ µÒJk䦌‡ µÙ ù¤aÚ–• Ì"â¨zÊLÌBÌ,HµIÀÌLLŒž!QH„ë’©b-Ê4á<î©Ñ2¶-lñHóo[¨ãÑPSªïóWx¾:¥4* h˜YJ“ËÇÂT‡Ø« ¼q¿UÒSiײ½¬ö«ÏSÄÙKªo°Vm´–Ùe­Ì™bâxQz¾×,”ô\Ê )Ü9"ìÄ®qÎËcgŒˆØ<ËÓ”™³ÙÌä¹ÍM–ei:3ifòÌäynŒËs“çÎ9/§YØZ+¾Ssâ)XDk…Ê7"­D¥TDeÖPAë5°ˆ×/NI‘sŒŠ‚ D„(EDœCc­§X­Ii]tp©ýªô ¢ï£ïdz8Ç©ñã­1y–åYfMžM§ÖãŸäyž¦ÖZgsÖYãœã²fWŠÑ,È ˆ U‘zd­ÓZWꟳη7DT¤‘”­5!‘RÊ3£Â¢±."¢R€È"¤3# )í­P­a®ªM*#Düzq BªÞD$e(­A•_%òÄÃαc$´Î%ÝÞpw7ËrÝ[t uàØA+ñy^øSýJÕ–+•DZØ»¾‹µ·q¹ô’ï…,ÌT ý‚ü™I¼_Êy;…üŒĪa’èb£kËŸ3|çÿVÜS³É~ñCìgÏùˆšRJéÀ+±Ïávlò\ƒ( ¬)xÃStEÊG#¸‡ì„8vÖ:kœu…©—kMž¥&Ëmž[“§ifLîrcòÔYkKš·&wÖ²cgzß'¢(òGJN#¿ ÊWQJE))¼~ ì3ë¢;>ú±QhExSÍ9Õ–ê›R±”]ÍE¤ž:W:ŠA1çÝ X¬˜jZ|•.4Öú$+B4Æ‚s D(Rt§S%‘ø!¸¢èÜæ.Ú(éÄ¥+µÖ¥¹Û@¥hÄ  ¹0°8,9AX¼GˆÉI ~|h€Š8XXÈe{ &‡™‘™Ñ3@hüSŠ}lzIÝ%—´i¾a ì‰7¸  ñ§­öõ ÕA$H\ö?aç²<·³1éÄY¶ÖZ“›‘ }¯!¢b°¿tEˆ¾è ó¤*Ìb­c?NœµŽ]¸™ ½lÇ ´ÒÆZf¶Öø:E¾?;)DbçH¡ÒZ„5)A"ñ]MI|2MÌ, ÐY«H#¢µ¹³99çD˜Q i†¤uF´Ö¡Šâ$ê$Q†aZk¥œ³iš §éî$ßœ˜Í©Ýº(ô ëÚÁËADIDATâ¿bâúUC!P#åÍ;ú+P \üàî „…ßS »ÒÚ-¨_yø| ²#$FW$f/tjt -Нå:^éýí·lÀ¦¿³¥'öpEá`™̹f„"¡(¿÷"¨h¸½óg¿ÿ{ãáЛ¦²sÂb«pEy’ÂRð ´#û>D±ÖJ)€Â„¡*+¼´ò$……ÐÀZ+úQÄRp VÚÝOaÑJù lD—;k­5Ö·õ´Öúõóö¥cŽã(´´"å;ë‚@ûé2Y–†QÔétÀû }Û@ 0҄豓µN‘BD?$Ü Ûp =Ç€„šHk¥‚ÃH:ëò4›NÆ»ãíÝÑ$gËB€(Ö QñJ`On§ÌY5°IÀõ¢_¸lY'XåG”ðÙÛÄ„…oÈ1 ²óö€#‡Þ**ãÐÜÙŠ¦[0¨¥jòŸÔG7Iþ˘Åm…0wLiÝ"‘"äÎëu­[{ú©gü(tß™CFŠHkÅαøÑ¬Ê™óWÈ,Œ%THŽ™ˆ„Y)¥1K¥äÊ¡t ‚Z©¢¨ÉùÁ×"ž¦‘ˆÙ *­|´ÎÚ";ÒZë±V*Hbôv´VÏ )_Ÿ™Sò+¢,ÏuD‘s6ËRvEk|l1Ʀ™Í-çy®Ä"›<Íò43™gmžƒ3b­B«#%ËêtøØÁþâ êtWW’nw:aÅZ+6<Û³›$aØ¥°×SÉBÚå9Xa´ÖY2Få•B@agMžç¾‡ºøeV„˜”RXN}l:‚öÒ~‹ø¹òÓxÀ UP<­\@ìD˜ ÷gÆ!+bÇìPˆ˜½ GÌ$ÈÀYšÙ ØÂ/%·h¾‰}Zô_Ëñ½6îÃûSÛ`h©RD¶Î9kÀYÓIbßX˜¤²Û\ï ܨK—H¡{‘Ñ«f FvÖ6"¢qdÐÛ ŠŠÐT…d*šÖŠh­´"R"b­C@–¢âQ•½C|¯}fvΤ٠˜­õ÷d½Þ°ì@Äs‹u6¥„„”= ÄZ3OÆ£ñlš^ÞØ>{q2Í Ñ°ÒDƒ3`sôÿ¯°7ë•$KÎÄl9ç¸{DÜ-×Ú»šÝ-’Cr8â2$8‚ @Àü8½ =èu H€€„Ñö"p@Ä¡DE—fWU×’•Û]"ÂÝÏ9f¦;÷f6y+ëÞ¸[f„»™Ï>3û ºàl¬°:Þ1¢õÔYηß} wo÷‰mÿŒ>ø0ÂcE4îÂÙ†6ë²íŒû+H%Vò e'ÄZפìšSsÄŠ„ HÔt?ïî|éƒÏaá‡èˆ…–èZ Põ¼y9 Lõ ¸¦°o’5R%i…e¥Š‚¢º8À)+}ê w±œþÀ;G¦è8ÿ‘®$¤’“©q Ä-6T­Ù¸í>7nA$Bñ¶t@`¦ bmYà’Œú$&5h4=aƒŸ ¸0Dff*æÜ&8ý ":M¥ª7‡AÉÅCZ•b ã`µ–RJ­•]|ŽM%†´ßïÞ¼yóáG Ã)uH@1%F+µp "¦J¥@U¢ÈÈ»n5d"½ð¼'¨`U†V°UOW‰@w}$Ò%ÆäÐ!Ëþõ«PgÙ]¯¯®èÑ“ÀO6ç†.×p}e!Õ©V3ä¦Îì劶•õ´\ ™8rà hroëï±7ãùéޜ׃3AEâ#ê’uKj¬ÇÜ õÄ- ÿ´e½b¤¨ˆJ¦Jʪ¢$$Ö Ù-ø…°æà>ûùþÇï9ÞÛ2ñ‹>¼û£Œ©Ëš…ÌTDaÙ’#¢k¹ù€¥}€ˆ˜XÍ7˜ÙcmóUES)¥ˆ¨'Ó¥di }-yÎRë<çy’9×ReÊz3ÉÝ<Ç›uì;ŽˆÖëUß%$ìû„д׻.b"Qä.êŒ8rŒªä‹Ý 9…*ÅD‘#2”y6çô Ä,A@f ¨ T‹ÖI¬ïp½¦Hðøñf½Š‘àñÕYŸb"b­¡ aÚ^´”‚Õ¼ý"Ù¼Šª=¦þ —¶~‚iSk©ZÅÈÕŒý¬Å؉1S PlI‹ààGK_úCéHtp…“áaÇA'ÖO´ !wÒöøÈ™8þGZö"£ ª 6>H‘þ|hú'ïü§<ÐÃÇïTÞcú¿(Ú¿ãèWoX­^¾½¹ZÑ¢hU5@p:<0ûq,,ªLRT­J-¥xX¨""­Î:Ïó<͹ÔRœ7*óœ¥V30­¥‚i`%0F@0FdFôñ4i”i‚¢ öû ±ç†{ÏQ‡Õ°Z¯‰)0‡ÈªÊ`1F™"22!×*~›‰" " ©)‡S­"¦D HcžIT­Šˆi¨  Åôü<=}¼úÐéÉ“+”J¨—Ï?H]ì‡n5ô)&:&™w¯¾ï»põä©Õl2Šä²¿F†Ô¯CèDtÚßÍûÀ˜ˆ€ ØÕôZuD´IH!!Æ`¦rl|»Wð>¥˜èþðà#©SQ©ðòþôLp’‡ZS„¢²©’Ê’£¢ŠPpï~¼õßOÿŸxÅ{R‚{ÝB÷Ï„Ó/mþ½§ÀÉϘY¿Þ<ùìGßß¼¸<ï ™‘ÈDÊ\ò<Žû»q{7û<Ïs.¹T­¢EUDÝ1±cŠ yK[D „º™CDÆu‰™ 2¢ö­b`L$EwÛi»ÍûlTªy>€µJJ1ÄK-ÍÕA‰9E@@ò5admÕ©‰*2¸V. "Æ„XÔ‡Rý(’* _xv®ÌðèéæÙ³‹³Í#÷]·¹¸·×]¤õÅeHa½ÙćÍyê×`ÖõéÉMÆÛ¾ ý0Ø´³¼…`j(óDóvʰÝÎ5ÏL!`ˆLȺ”Æ=þ{}¯ŠŠ5¶‚ ˜±¥Yð@ÝÁ¹,zHûœÔàÝL™ÚL-`È a 5g ÅtI–Ǧd*¨ŠBˆŒXQQÃ=|¿Øìß “Þë0¿øüi0þ}Öï¿)b¿óŸüóiÚ«h-%ÏÓ¼ßíïnëÍ›íøúvŸïnî¦Ý¶ä\EtY>jˆ†ì+ž™9êÈb`é qˆÎüp`&tl©5fB0‰L&5b"&dB3%¢~à®ïR·‡›±lk S+"†Ôs¤…†„à‰…ÌK­™ƒªøz€*jàý ®²‚êmO€ÈQÅ—Ÿ‘:;”s‘*ÞE}ÏOŸl=º\mVÃzÅýЧa=M»4t”únµÁ®ï6ƒqŒ›Ë”†yíìùã00á°³yH‘)$ã(bBŒ”ˆ¬õšxàÖ¶–TÕªh©RªU5db`0F0"<…?Ç3€N>]ä­Øóóדœ¡õDPÓñnvüÏ”©1BÇÖ%PAR¯­«À»ÄwMüï ü¿ø^øs¤ô|ëï5÷÷•„Íæ"€,*¹”y÷»ín{3nïæýNrnDM  æÄ÷KÍÑ3@Sbâ˜É7ù"P-%Æ4 Ã4íS蔈RA%Fv="O!R×ǾKCǯ·õzœTâÀÄä»ÔªÄàT=‡h~Ã}™‘ª ÓRŠ2"2SA0€Z«šÆ  Hu#Ì¥šH©¥J53Dízòøìì| ”Vg9ÅpvyAÅ9Åa‡ž»U?¬€¯6›auûâçß¾øòbè.®.#V,;3†y¸ÐþÊtš f•l’M$’%€œ^ÀæjY *ŸŒe=è…»åéðm8÷ ÿD‚Ú|ÀÁ <Ù=>"lž€þ ÅQI< >4ö|Íõõ#lºçÇ(R^bû}xH½§rüÐþL¬–šóPD˜Ù‡ÜˆqT³*‘ÈÄgpÉj•R…9”*fFHU*,“Ç0ôÝ0 ¡ëûÕº§ùó_ú±ƒ°œ¨dGì³T½ Ô ßR@ øÀ¸ßß¶ƒr¤îa®õ(;ñà½ØŠ fÂOçÇ—é¾Ó¬ùÞi`f*"5—yšÆÝþîf{ýúîÍ‹ÛWßïn^ÏãXk•ªU´ T11h-ƒËü8ˆ"T Œ†@£'ˆ°Û¯_ßîÆy½ê>þäy×ÅZkß%"3f2ÀZkJ€D"Ê‘ ˆÍû>}_…šÕ*DÞ®FHŒ °ÔR«Æ„Nþ°cfA ŠÔNÍ@Í·ˆ4çÂDtšg•ÊLÅ B  »Ž8¨Ù<åÍùÙùùùÏþöož=}Ê!ªbb¬¹ÜÝ^?~ö¼ªÀ¦ãõËÃÅãçyêT2ô+&¤1­Œ‡yÆ\'1DŽBÃ&q¢&U E±V-bÅP€A]fÍœ$Z&}–©ƒv/—Ø'HÔÂüî-œÏÑZ= ‡ˆ¯J¸¤¿¾  }ÆÛ?oF„ښ»¶Þ¦•q¢‹Ö\Kç[zïÚýáýIþ=¯°E¥-#kËëG°Ãž?„“!rhÖ_kÎy÷»íööúîúÍÝõëýÝÛÉûí( s0QM]<¿¸øúç?Ÿ§qµYÍû=¤®+’7›uÎÓíë1Æý.<ÿè“"óÍ·?c-| UËNJqEÜ)еЖ 2“RQU$XêNµ ¢ Fê‰$­]vÂo2^-œ¢ŸƒCøÏäNÜá0×{ü´©Q nƒÀzœ˜Á…ÿâÉŽŽe@{ùþ|t:tx|ð%{¯ Ͻ°ý-K¹]¤–Ròœ§yÜûí¸ßNû]žæZƒj+ê;'ÁA¸+ éA¶ý$ã2@#œB­R'@H‰ú¾»¸< vû}—Œû1b0‹ äˆÊœ3« ‘ 8Ä~µM!—Š è“C ¨¥Î¹øÝnSЦvˆƒ‚¸Ü›Ui·jžgôC Ö¢"¥T3õê -ƒ]€ˆ„ôöõk3[¯×øæå÷1P×eÜvÃêêɳ¼}óæëŸu]×Gúàó—€o¾üsªãæÉ‡œ¢‚U­“Qªu_J©óXvw2ß¡ìƒUF ÈDˆ $HU¬VÅ ¤Þ7nHËD™™ñ‚x™¸Óôs˜üw6Wñ~Ã,˜çT Å|>¸Õ½ˆ–Þ ƒ3¬Û8˜OȨÊѧÐ`÷äD —]yG½]„Åš~Ç‘+âAdãåX;`÷ðï ßÞ}€‡ùó{¾¡ªm˜ª¶ÑªöÀǺÅD¬ TœK¿‚:ÇnËl/ªAQ‹!vý€*„]—˜išfU%Äyž"BƾKM˜¨V­…Àò4¥”8 Þ³€Ô:|˜¹ˆ(@G­C†QD§iÎ¥¤Í@ÔÜ7Tª"TW2ñ¯”*µÖ£™•RÄ'-Í| мo‰LmÇ”âÙf˜Ê8~ûó/ŸðÜ|jG$ÆtùÉeÞm¿ûâÛ?{£·ß~ðÃ_Þ$Þ~ýç §áñÇܯ) ´¾K¹ÜŒw×:ídÞËx‹e¤Q-D!Aä VŠa @„dˆËî»cv{ªÑôÞ/臾[ £ ÝüéP; ? Áªêtª-àã`Úå”hCH­¤qPLƒƒ‚$.0ÄaP£¦3n_·Së·æ ¸¬šzP¸Ïý¼Ÿ#½Ÿ85¬*ºHÁxô†v,´n3ó˜ÚèGopÕã™Û~ÏÁ¤ˆæR ‘úØ)i­&bÛí.çc0Ó.%&6Õ~µf¢yÚ0©%£¥Tµä9„^)FD¤*­`E!0]®Ô\¦yQ‹`fâc+HEÍ™ª¨™˜±Z­ZkõšÀ4M*CP3i¯¤%4†ˆÄdªBÀ¤–›7¯^~óÍfè®_¿¨ÓD.·7/7W?ùÉoÿÁë¿;ß}ÿå«¿ú·ã·õÁGŸ «Tß|!}ŠÃû3 ½•VèÍy²<¢Š©ŠF2ï,4¬FÕX€Œ‘!X[ô ÀߟTîsC´°¥'Y‚5%P3sYŸ62vØïäY¯)úøûÒÿãn€ê”¨›=zÐÒ,ä4èá_= Ž<ÏIÍà·ó÷´ b¡6°}€Ì÷í¿Õ(o?.,Ó³Ée†ÆÔ¬ªYQ1Bˆ™ÙJɹxûDßu}ß÷}J!h×u`öÝ7ßö‘ÏV]žÆ:Oéê‚™¤d•B!-ǦSŸ†^óbV5$¤*:O“ 9²ЛC0ÀZÙEÚjê*5瘫ÇQbZè’Æìù]òÙOÏ• 䜵̕ ºaè¶×oÇ»›‹uu|…øøŸüÎóŸü†~úÃüæëý÷?ݽý:áE†þ£üæ§åúk‹1®/ ;êÀp¿½ûþ›¯tÜG¨ b&vÂL8¢€j¤Æ lÄŒ€ÔDΛ ­µ–ÓY\b;6ÅŽfÜ'¿×œ­íí{¯.zFêÀÇ¡¸“†¤¦Ð À†Ú2Ï TTB\` éG¬‚@v4§f¡ þÑêßiz8äÄ÷ ýè{×ô0©ËÐC‹„ÔÕÀ·d{¯[Kü‹j¢ ºœ‹71@m‡®‡S ¡ŠˆhÉ¥–yèBdìSÒÅj5¤À`¢Z}L¬ë€©T‰H®Õ˜9‚/]çÂËÚ%Df PKVUæÐN*ÑR…ƒŠˆ ¶-×Çj- Øö›´±Ä–W‘’ª¦ÀC y¬ó4íî¶Ÿýà3|þ8hžn¯w¯¿û›ÿóŸß|ù+¿óç¿Ì?ê=Ö›OÊÝK©% ‘¨IUÇmÎcİ€Ec"0$CRCª@ŠlÆ@ކÁjÌN×r4èÚL¼ùÒÿ¶…4²%hŽ;ClƒÚpÔp94Í9³èŸ£ª!zG"úiŽ„Ú䩳¡â!\Ñ0˜(.ê|‡¼ñÀ×§wOÿAü?Úô1ƒ~ïþCv¿-æ{&'MϽ›>à-4j†  fU¡¶æ;Q¯ò¿œÙ,¢‘‰c&»ÛnKÉ*JˆWçg«!­W]dF­ýj RçiîbH)&€€¦1pÎ( 1‹š–BDuáo Œ]b ˆ‰¸Jºn“šˆÔZªÄ*R¥b›ì1ô±¯Ô/ú-Ä-üK5%‚HíWg¦%¥X'üêg?{úüéÓ'Wëó«ç~”·Œo¾»þò§úúÛý‡¿óü×~{xüi|ü)õƒÌ£N[\{ýBì»aS¥H™Ð `5B3Z.;U㊬Àj¤H-|ž¬ëƒ,£Á"Ì¿P‡ª—ýŒKñiaOWWêaˆÞo×Ùô`ü†fŠ`j¸¼WECõ‡MšàÄÒÑw3=´€Î;9ë‰Ù¾Û)q æö‹ÌÛ|üEF ‹ÆÏâõ.ñ .í„„Ä èí1b D¦Ö˜4o­YÂÿa«ñ’ «i­2I ˆäqʹ0Ñf½¾º<ëcDÔ¢Ô\¦ ÍöÛ;“òøê"¥èÏ÷5)†ÀÄŒbPjE€BöÞ$‘œE$ÈAÔJo jpN-—Úà§¸Š¶;׸ã£ûTJL)–ZÖÃʦqztq6D¾yóòoþ¿wóèüòlýÙ/}~ùøÑ“ ~ò“í÷ß¼ùúgóãÏuõáÃùs¬“…‘UÉ 9&æ¨LZ@j(Ù,(+° ° ’“(ì⣰ð‡‹Å…™–Ñ¥£õ/?´üwìpt`CQñŠzkìR|jé¶ aP<î ŸŽm[yÑÔÏÄ ÷NñÌý¦ÓÐþž„õCÏÃ%¨Ûñ{÷¿~|w:±´†ødV)E¤š™Ù.Ú’ø¶†ñ¤óþs²e $—R"C­j]ßõ]ßwqÎy¿Û¥À#!HMv»­©¬úk­ÞÚà !ø#w­*ªVѪˆHE$ m•´+WR­¥Ô ËÀ€S@9—Š«BøePÐ¥¸H@^]&BB«ó<ÇbJ¥ !uÝùú⣟¡id\õÑöÛ»ÕzX_>ùô“¤À€†ÄeÜâúŠ6Ï”p0ÅqšnÞ¼ÒéJÖ’-O*aâØ¡‘Bˆ‚Q0 °yà$€Vf.å`Kwºö’Ýajæh õs¨{äLÉè”D5³E!]AÛ˜‡¶²9;E€€Îaa«L¸;"ÿ“ÿÞ.ÍûñÄ¨Þ âG sxyG‚Nh#°T= –LGM¤”3sÙõ }©;žÏ ]5ÁIdw€=›‹‰Ÿlo> åØž6žìä> õS§^J¹÷j÷º£ŽX`§cκ|Tw€*µLÓ4M¹V507k €ÿm¤hÐØ!ÄÒÐ è­qªZªTQˆ-Cf©³é¢ˆÌóH bHɤÄUuœÆÔ÷Ĥ„–b$¢*ªd†ŠKÖØôÿà0t/¢¹”R*D4Áê³öµJÎ¥Q¼ªØfZ$Rï£6Crg¯"fcìûabXõI™æùÛï¾R|tuÆÌ/¿û&@þøÓ§@BI¨î^«Ìñü¹“°LQªŽÛ›Ó¥+ÀˆH)9¹äªfÈ€dÞ >]„‡^8ô8ž¤~Tû.u}rNºc̪Șú˜€À˜0Æû¸NL]×ûWà€÷ûÝ4îW›s@ ´.†£ÓÓÚ䉨ûvÌ…àØ‘@J͹T@d3Y:8ÚÞ§Öú½ ÓåR#’¶TS&P•ÉUzôÕ«W} áêòêòœA"ãjèû!=º<ë;"µ ¯:0²€u’»o­l¨Le{'a;éî Ëc9M–¢ò€|Náy a`î!$/(ŠRà;Pu9fïz'ú ÈifZ̵'^ïúèƒÍ©î×ëÔ÷gPg­¹Ì£™¨˜Q­PR`ãk¼ JI·*ÅúKæH}«õgÖsð CK_ZƒAxéø½15ÍS–|÷`[x`9[ÿ$°úÍlÉ–¡.€v!ž@M]¬—J챘El à“ddfH Uü‰‡ZK£®ÞÁ:§9ì)Ô¹s–óë4¨·Ç>Ç¿ôò-Ê-§â-'¡]åA°wó·å³¥Ê Á ~XP@V‡ÄpX5¾~uü f B`!†yèãf³Z÷}ñ|ÕŸ¯ºB¤Pk)³÷–œçÙÊ<–É3›Üå9}‡¦Z&NLà¢LØ¥„Äu®^•³`h`âr]5FMf8MsÉEªˆhQ5ß~àhÍ V±¥­¥!€E-¼ˆTfî“9¡Ä ^:Ö’¹ë×ð†¡ï"ÛGÏŸ^¯V,6ïA§ñm%›”wë ÇägWd¢Ð…áŒÂ8pJ©ƒÞv}*…’…ıÝºŽ»Žb"Dìe¿Öh†>skF¦|€`ÉüFk8!:œÌÖvóîPŸtõíÞ ±d+èÂ’!)’žhâ/"[&~aÔ ¸ÐÚ)I‰§gÔ½Pÿ ¦½¸÷=‚ª*œÆùEÃZýÑêOc|s;1úƒÛ»S[Ð$  Ȇ è¬a{ZŠœ÷1XéR0°¡æ"µ"ˆ”:Ïe?å»»½Ô9ÄHfV c dSÄü²äZ§¹ÌÅ51‘ ELüå#v½`)µÎÙTÁ —Zj„Ž˜}^¹Tq)p]rÆE|Är©sÎP‡¥| E àòòââü<ÆôèòòãŸ={|A2ïÞ|›e‚º+óÞLSJëÍ,•Û4ô±K@(F£”k0r\?Þœuݺ¿µ;‰Â=…Ž8r!DdBB#8)ýxo(žûrÏÞpÙu¸¥"ä …~Ì ÛXâ1-“xRjó`Š&þCDd UjRðmK¸˜’yÑ0ÔÅì<°úTÍñã¡CõoìôýIh?Àœ쟦Á'‡Ž •9tr«Q\┵9¸&Uà'zL50#¢ÈábÝòüч>~þôêÉùº Xò,eN¨y?F”uŒÞk.¨"U¢uÎ×ý庿¹½óŽT+‚ dbÆ$%3E$0³9×ý”KQfŠÄbVKÍ¥š{³ê8ÍEj §9—’Cˆ)z5Àj•\ÄTw\*ª¥Ô*šs§RµæªDÀʦ‰qÕwg›õÙùù³§O]—qýíßéþ CýþÅ›Räù‡]^æ’™a5tçWç›~Hëu躚'5 â°A˜7«îƒÔwSÚi/Á§|ïÆ¡d´Ls4Rñ~•ÇóÚƒñ6µ‚Ei ½‚fÌ‹õƒR›6?ž+í§´€Ùñ/„¥†lÚJä†>Z°4ñà’m7<±ü$¡Y¨µžbúƒ×>ìã;y°¤ë÷LQ©;Dð¼È!Þ»\ñ÷S µØø}.êÞ!z̦½1âP겓¦ Õ,„0t]dèBx|><¾:KŒX ƒH-yÓÇ«Mžoê¸Íû­dµ j6UA³Ze?nè¢g3H¹øŽFPÖR(T æ;HMÐLœ·šsAÄ®S‡7s®€8ÖZ÷ã¤"«ÁÌ@ÀJ•q.s.gScB"ªbs‘œK«Ì¹ä¢„©C6)%¿xñý<Ï1E&œ¶wßów¶ÛéüÅ/þøÏß¿ùkøèr¿ßM«”>ýüñçC?”¹ —”†rwKBì ?ãÔk™PÌ`¾:_­.†·¼«,˜ZýŽ ¿Øöa„ï`åŽ^–I#B24E׸f¤e‡e³"ôUl"Š´3Xª DìM@ ­AßLÄ´T–w† ¢âM­ŸížœÀû#QyŒô÷Ù%ijX;æ®ï@ûö~\ÔtZpcG|?ïz< šSkŽhýc‹<ŸG¥Ö‡b "㸯ˆë‹õO/ž>>_Ý“«s2¹¹ÛëõÕ“§ÎT§ª5€Æ@L±“9—W7óϾxûè]t](õ„ç"&”MŠ–LQÀxʹTDQ­fVj-Ec §es©U$Ƥjã4í÷DZwÓ|·kÉ13fŠ!ˆè\ê4Ѷf)çŠD] ²b%°'Ož<¾z´ê‡Ûëë»:ÝÞl;¢”†çŸ<ÿq ÿÓÿõòæO^þè£~Ó…ž÷onrüúí°9ÇÝþñù“·×ÛÄØ_~ˆqP*ÚŸ_–›ïç·?_=ûñ³«Õ7×6)jë! {5Ð%@B4FÇñf¨¶ÈÎ.Ö¶üºÏ$‚+°¶3¾­;Ddb†ùƒéìø´Ë}³÷é; óïÀ›÷y¸¾Ë1Á‘'’Ã_‡»CÎvZ«JúEY­†|òü£çë½Xºžq.¦Øç*jJ hj"„ ¦¹œ`]+"¢JÁ@Ô¦9‹*qÈ¥Þmwûiº8;CbQÛÓÍÍÝÍíÖT†¡G€9g"ê’9¸šæì䈚U±ý8ïúp‘úU´Ußåqüjûå·ß~{~±>ëÓÝÛ×—ë.=Z•ªÿè—?úúZþõ_¾ùÕ_¾8»Haˆׯ^íŸ=߇þ V‡'ŸÝ}ñe.}AÃåþÅßT`ê/w_ý€WŸþÆùf%#JÓYrþ ßé]o´ ´š¼ë5YÎì;‹E°t½[Šñ°žj‘ñF ¢¦àoÇ’¹WÙá0aë“Ø¾»Þ¹ׇÕfE³X¥U—I)„Ð’`k"<ùKï±HÓwâý»°fÉcOŒþ4Ð?|;…ù§qÿž›´¿¥ñÉpïiœ/!4±ø¥‚´êûÇ«óuÜÞ¼Ýmw}Šã~¬sýò«ok•ŸüðÃØsŒÑ8ô›U@C4):¬ºùûüú®þί?}üxSël2w]× }­Z£6Ò¬i5ô8ÎÅo«ˆéÂ1ã4gX!ÆRån»Sµ” î÷û7on^¿½Ùï'"äÀ<Ï D ŠS¦žl…HP«x(»¾¾a"U+UŸ  Üõ@áõ‹Û~™âóg›óoî6Š( ¬Ìàf»–Ö»Ý|õÃ_ÿê/þ|õý‹ÕÇ¿œÎžâëŸË¸ž}~Qr¾þZ§›ÍæÑ]®&Í$&«ðø¨6¦ýPÄ;­zºnq£vÜú©)·¡Ù]LæAçØÓÕChGk²—Þ1ì·ÜÍ2„ؤˆ Ú CŸ-¯µÒ²ܓࣹ¿ÃÝëÑÚ`нcôbÇöhø'qþÝ‚ò½&ŠÃ¸Í½o¿ký¶$O°”týò!-]´ÇŠ:€Å¶BôöæîêÑãÕf£×·ÛÝ~ꆵ§¡'Ò*ÆCϨ¬Lá&¿Z_ ?üñG)ÔÁºyÚ›êÐ÷€\U}˜÷Þz cÕw›Í°ÏYtaÇÀ‘™ŠÔý8º:Q±Ö:OsêRˆišæ›ë›7oov»±Š0Q΢™¨–‚Þõ™RDWã"$¤ø|ݧhãî.˜bÀ››}ˆUÏÎ6çg›×ß}³éÂêüŒú³ëÝW›ŽnÞ¾Í]|úüH,Ü^OòÑÝL+^­>üåáù_}ýïŸüäM2¸Þ¿ù*=ûQ÷Ùo#PŠ)q„\³oiÕm4 $3OðçµWNA’5 âBó°p»¦ÞÞ§zØÞ§Ò„È;–Ž~3BäTU¥!cUE„*RK 1ús |<èÀWŠxG „¯¹Zx¨%/ÉÊi³ÑíÀíï±:vRº²c#Ãiꌧ¶ÿž¦Š£…ßÿÞ»ìñ»g†™¶h}ùþ 0ÀS§;ø”HÝ ½©V‘ÛÛÛ«««Ðǻۻqš¿ýîåùzøäãg6]OÓüìɳ@ ¥D¢·×»?û»×ÃfØœŸwÑ4!-s)%&Ž1GµQÔЈ¶»üÝ«7%—È,èÓÈðr.mM`žæZ+v)•œoÆñöönžg LD¦¦•™¸Õú(Q`ß¾cH]J}`¾™î´ªìÇYÕ=:)¾}sýæûW&ùÑžkêÂê2sÿòÍõÕOÏ>«¥Ô‰#ïÆ)lž½|ysõiÞßÝæ›[­3Qç§W_•ýÝðágvþ91'°LÐGÛîÎÖi ¦f࣠jƼÐñK£ƒw¶;çÚö^P=™ò!&5TßjLÔlÝÛÝ¡­ô`ðMÁʘCš)3±ËšÎ$3©—ÏÔÄ\|’CXv'›©e ØÍн1gÉÊßµûw8¤Âp‚qx2Hðp/ûÉ!p Áb³vÌèO•)þÁ a?Z=ý^™}yj–}©»ÝžCŒ!ܼ¹¾½¹™Æ¼ßÃ0s㦹žŸ­¥VFúîÛ×ß|·={¼™$œ?¹¬ãÝþõ7ggÓ4IŸ !H©¢†ÇRÞÜíÔlÕ§{/ÓsZµZ«ªÆ`·ßãd&)’…Fa[ÿŠL8`#¦H1rJ”bPMÕ°Î V«dZonnŸe¿¼11»•ÍóL~ 4Fɯ‰ËÁ1› …@ ÁLsvµ?P@µ˜3« Q3W–†PJ9­Ú2Üc÷Â=ÎÇNóåÓÂñ§ÜOi—YҖݼÛpw˜Æ¼w24byÉ%Á¹®åRCndúu>fÛǃĎ°ŠŒSÓ<Ï»ý¾æREbJ——ç¹dŽ)ö´­ß“iH1Ähž]}öÑÙÏÞ¾ýó¿ùùÇŸúÄi5+0ŠÈ©KÜÍ* wç¯G¼Ù×Íúl¡ªƯ>.¯Ûîw›ÛÄZXÃ%Ë­M¬–ö›äS8ˆlDÞ¨¹Öq?õ}O!f`‹›ŸþÝOöÓ¯ž=Ž)Q}ùêÍúò²[ ±‹ó¸ ¯ž^ž?¾°ØÏU(i¦éNóÞp•‹¨ ]¯ýx‰LÕuãsÉæü Öizøo3û)E ¥3ó`æÓÏÄËÊ@d5WÙjËîÍÜvÕ ]ÔEáRˆ“˜ËaÀ~?‰ˆ‹Óø1BT"U•*âÃÔ ƒÔÒ _DULäXνÏÙ/¶2ÏpêÇhýð€ûñ»å@GiéC2|œ™]ä'––ëcÒ|xBÜf=I9ñáö`™ç·\ê~.‘BÎu •Cœ÷»y.Ãj˜¦išº"À©ïÏÎëîvž3ǤÃÙÅóç—é/Þþìë×¹r.±/¦ †Þè¥õ™VÛÚê¦dAÞl6Méˆ~ÚäîòXê•þºœ¨µ%XúÞ=J!•RK®RÔWÕ/΂ŒÑ`0P„ªÖø¾˜ FKýëÛéOþâ‹í¶~úd-R†¸N]@æWß¿XýÒç ð껟ïöy?Áε>˸§§ý6m6»1› b©Ub6arCÌR I Áøër‹?ô·ûøè±)šÖ!¡¶eî Ö4s_à“ù~¿ED¤2‡ZË<ÏBdfO¯Ý”Jž[6bÆÌãœ3—ÌB­ÒŠª¢ªb¾ÄPõ´| ÑO,üÓ;-|ÐŽ;ŒËKæÇ©2¿ýKÙëÞôÙâÔT¥Næ2œ')HcE·¶t±jS¨1Q»ÛM}\)pUÕižº”ö9ÿýËGWçs.Ó\6žíöS>?ß°Až÷ëÕju_íó_¿ÀOŸèSÂq»þ«™u’Ú7×å.C¿Z7¿ÃcI´3ã"#O-“CUåÀ!D¿»fŠD*âT¢÷ÃÖ’k)5眧<ïçqò©eUS© ºÈJÊDh°+záf´ÿûoÿêï¾yóÙ“.uØúU×õiÎ2Í @Û»Û/¾-†Û/ßÞ~P•»Í÷?ûÛ_óá§ŸQºTLEpœö£·$aZDÕ5¿tÙlàæ"ê[2×RD•˜KÎ¥_H€$µ8,p+aˆêd/"–RbL1%ñ6X‡ƒÚVET‘œKJÉÛÚ€//µ’¸ÄH­ÕÇ¿ÐE{ADƒHm§Š-Ò§'èä¾  ÐÕ¿K¹ïà*xbÛÇÓÿÞ¸å’ÓÛÁÚ1àÙ €q“@: ãøÐÓ3JDT¤ÖRl©,ÛÛÌlšËÝX¥}Ý”÷½•ýË×7Woî=ÿpÿÝõ§Ï¯x¸¸3gìOë«ËÏ?}úÅŸ~óÇýå'¿ö›%\ýñ¿ÿ“G›Xæ¢ã\ïÆ2 ú.¥âçD"v¸*~]|e*ƒ‰Zu’9ÔZ±üA¼²í_qÈm#Î4î·wÛ»›q·•"K:†»¢SÎëœRDf(¥^ }ëo^ÞýÑŸþÕ:ÂÐ#¦ÄÅ QªF·»üÝ‹WgíÞÞŽû‹·£Néq\?ýë¿ý£/^Ü<úáÆÂê›oJ.Ìb¬¥šYÉ9p0pÍSo»'©\¦p‘ÍßÎÖb¹ïG$æƒâ #©ùNªµú™à‡Cˆ "®ªíÿn`Uçïµø‹¹þÄÀ@Dˆ ̘YTJ-HC(¥„JÎÍZ½q‰ˆÂ¡µæÐmºT›é>ßbÛi«BCCtÒöw¢+‡D‰lš–´ô'LâÑiî¸ &ÿÒB5ÁZX`Ck=")EM¡”CÆá˜Rƒ»ýŒH·w;îVO/Ïò¾*ëÍÍöOþò«Ï~ý÷µŒßýå7φGç}”´Ý‹„3<§9|w£ðÿþõ×°5°ô/ÿðÏ?yvõÁÓmŒáìì|½ÚPÖÄæ#Žj‹l Á"ìK¹=Ì»Œ“_¶îwD," †E¥–bô ­îÛRk.¹æ’çi÷û»ÛÝövÜï¦i*ó<ŽÓnÌ×û˜p®vV¶çw_¼ø²˜ªñÛÒ@d©Lz—Ç®ð…ÅÇÝs9ûÁÿöGÿýkéÍájõLWófû?ÿá¿ý“óoÎ~ðŸÝÍûýÈDà<:"„¤ˆo®µ¢¯Âq¬fMKª­jn¬¿šŸóèŸßíÆkíý×~¿ýò«ÍzõÏþÙ”BRجW)Fƒ&bnHÕH½Ù°¡?—1Äà,"Ä ÐÖq›‡H$ªþ‚U%„HÄÄ\k53Ÿ¬o¼›i-YEÊ<û}žçÝn»½»»¾¹Ýn·w»½í§»ÝøÿüÙ_^ßÜF€±Èß¾Ú½¸›†. ÂTt—ëO~åW~õ?ý•ÿîü?þÅ¿úÃá¯^Ÿýþ?ÿêåþ¿øoþ³qÜÿð×o”îúf×¥®ºG«µ239“&zð GzµÖê¯Aª ¶b™ª†à‹•œ,m½„¨¢H{¢[—¶mWØ*­‹ ´-ùª§Å1¨ÐôOÍÔ‰¨”‚„¹TG!ÄR2w)ÍóìDjË1÷Ÿü:´ƒ¶ÑLUáö„©X–ì.‘ÛšøEs‡¶ ÍÚÆv3 å8œû¹Ù×´ã‚8ðÂY`&n|8!‘ïbiIL@&^²5ƒ¸é\ÙÑ{ ]ÉOE4ç9çRʜ織š‹äœa'‘¦Ð¦Bð(ëèJUZÜu„†h±-Â0Dd"$R5BdbC4“aX3ƒ1!¥ÁGäÇqVµ®ë¼ì5îGBŒ1˜‡ä*’R:Ì …}ĵ{¬Vñm.„-ûuNÀ¬ë:‡@è¯ÚȦÿ x–Œt¸¼l¢ÄlÖ¤Á}J®M‰äy¢’‹©”Zñ’ùa±ë‡¾Î.Îc—V«õÙù¹'Z12µr„iÎ9rlb^ØâcÛXTkm– èÓψätn©Õ¹K/{1QÓöº ER©¢*!„9gWOÍ¥¤”<]ö¬º–šKîR§f1ÆR‹7& «Õk-9íÓàØÉÆ)§.%w'‘êçƒ7{à²×Æ‘ÐÌRJª&*1D3 1ú¡š1€ÅØ™&Äã4MØ‘3Çþ+‡:ˆšÅ=FÆ ,„¨"D/VçP¥ÃP™µf$Lq©€"0³¨Æ™©0€““ªZ¢ÂZšŠËÅ$Š!zu˜CðcŒ™›i"ò«d!ÄÐŽÍb3fcfGG!„‚ˆr.ùß÷ƒŸEĬ"!°j Õj’ºÁ[„c B* ¾°˜‚¢,õ“ †1FWßó"]®ÂDtàûÀ@Áb-Û¬UÜæˆD5pÀZKÛ|¬K¥ÈË6rì˜P±®>%ÉLÌÌÀ –â×zµ*¥Ö¦-´4KËÂÏó|qqỽþ¥íŸÃªKÇ^éº~šf$b*BFÿ?S"§ztPͺ%tEXtdate:create2024-12-18T19:17:27+00:00 Š%tEXtdate:modify2024-12-18T18:14:43+00:00~Í}3IEND®B`‚ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_flux/flux_pipeline.py000066400000000000000000000373701510465702400261520ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import os import warnings import time from tabulate import tabulate import numpy as np import torch from models import (get_tokenizer, get_clip_model, get_t5_model, get_flux_transformer_model, get_vae_model, get_scheduler) from PIL import Image # import migraphx as mgx def calculate_shift( image_seq_len, base_seq_len: int = 256, max_seq_len: int = 4096, base_shift: float = 0.5, max_shift: float = 1.16, ): m = (max_shift - base_shift) / (max_seq_len - base_seq_len) b = base_shift - m * base_seq_len mu = image_seq_len * m + b return mu class FluxPipeline: def __init__(self, hf_model_path="black-forest-labs/FLUX.1-dev", local_dir=None, compile_dir=None, pipeline_type="txt2img", img_height=1024, img_width=1024, guidance_scale=3.5, max_sequence_length=512, batch_size=1, denoising_steps=50, fp16=False, bf16=True, exhaustive_tune=False, manual_seed=None): self.hf_model_path = hf_model_path self.height = img_height self.width = img_width self.guidance_scale = guidance_scale self.max_sequence_length = max_sequence_length self.pipeline_type = pipeline_type self.bs = batch_size self.steps = denoising_steps self.fp16 = fp16 self.bf16 = bf16 self.exhaustive_tune = exhaustive_tune if not local_dir: self.local_dir = self.hf_model_path.split("/")[-1] if not compile_dir: self.compile_dir = self.hf_model_path.split("/")[-1] + "_compiled" self.models = {} # self.stages = ["clip", "t5", "transformer", "vae"] self.generator = torch.Generator(device="cuda") if manual_seed: self.generator.manual_seed(manual_seed) self.device = torch.cuda.current_device() self.times = [] def load_models(self): self.scheduler = get_scheduler(self.local_dir, self.hf_model_path) self.tokenizer = get_tokenizer(self.local_dir, self.hf_model_path) self.tokenizer2 = get_tokenizer(self.local_dir, self.hf_model_path, "t5", "tokenizer_2") self.clip = get_clip_model(self.local_dir, self.hf_model_path, self.compile_dir, fp16=self.fp16, bf16=self.bf16, bs=self.bs, exhaustive_tune=self.exhaustive_tune) self.t5 = get_t5_model(self.local_dir, self.hf_model_path, self.compile_dir, self.max_sequence_length, bs=self.bs, bf16=self.bf16, exhaustive_tune=self.exhaustive_tune) self.flux_transformer = get_flux_transformer_model( self.local_dir, self.hf_model_path, self.compile_dir, img_height=self.height, img_width=self.width, max_len=self.max_sequence_length, fp16=self.fp16, bf16=self.bf16, bs=self.bs, exhaustive_tune=self.exhaustive_tune) self.vae = get_vae_model(self.local_dir, self.hf_model_path, self.compile_dir, img_height=self.height, img_width=self.width, bs=self.bs, bf16=self.bf16, exhaustive_tune=self.exhaustive_tune) @staticmethod def _pack_latents(latents, batch_size, num_channels_latents, height, width): """ Reshapes latents from (B, C, H, W) to (B, H/2, W/2, C*4) as expected by the denoiser """ latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2) latents = latents.permute(0, 2, 4, 1, 3, 5) latents = latents.reshape(batch_size, (height // 2) * (width // 2), num_channels_latents * 4) return latents @staticmethod def _unpack_latents(latents, height, width, vae_scale_factor): """ Reshapes denoised latents to the format (B, C, H, W) """ batch_size, num_patches, channels = latents.shape height = height // vae_scale_factor width = width // vae_scale_factor latents = latents.view(batch_size, height, width, channels // 4, 2, 2) latents = latents.permute(0, 3, 1, 4, 2, 5) latents = latents.reshape(batch_size, channels // (2 * 2), height * 2, width * 2) return latents @staticmethod def _prepare_latent_image_ids(height, width, dtype, device): """ Prepares latent image indices """ latent_image_ids = torch.zeros(height // 2, width // 2, 3) latent_image_ids[..., 1] = (latent_image_ids[..., 1] + torch.arange(height // 2)[:, None]) latent_image_ids[..., 2] = (latent_image_ids[..., 2] + torch.arange(width // 2)[None, :]) latent_image_id_height, latent_image_id_width, latent_image_id_channels = ( latent_image_ids.shape) latent_image_ids = latent_image_ids.reshape( latent_image_id_height * latent_image_id_width, latent_image_id_channels) return latent_image_ids.to(device=device, dtype=dtype) def initialize_latents( self, batch_size, num_channels_latents, latent_height, latent_width, latents_dtype=torch.float32, ): latents_dtype = latents_dtype # text_embeddings.dtype latents_shape = (batch_size, num_channels_latents, latent_height, latent_width) latents = torch.randn( latents_shape, device=torch.cuda.current_device(), dtype=latents_dtype, generator=self.generator, ) latents = self._pack_latents(latents, batch_size, num_channels_latents, latent_height, latent_width) latent_image_ids = self._prepare_latent_image_ids( latent_height, latent_width, latents_dtype, self.device) return latents, latent_image_ids def encode_prompt(self, prompt, encoder="clip", max_sequence_length=None, pooled_output=False): tokenizer = self.tokenizer2 if encoder == "t5" else self.tokenizer encoder = self.t5 if encoder == "t5" else self.clip max_sequence_length = (tokenizer.model_max_length if max_sequence_length is None else max_sequence_length) def tokenize(prompt, max_sequence_length): text_input_ids = (tokenizer( prompt, padding="max_length", max_length=max_sequence_length, truncation=True, return_overflowing_tokens=False, return_length=False, return_tensors="pt", ).input_ids.type(torch.int32).to(self.device)) untruncated_ids = tokenizer(prompt, padding="longest", return_tensors="pt").input_ids.type( torch.int32).to(self.device) if untruncated_ids.shape[-1] >= text_input_ids.shape[ -1] and not torch.equal(text_input_ids, untruncated_ids): removed_text = tokenizer.batch_decode( untruncated_ids[:, max_sequence_length - 1:-1]) warnings.warn( "The following part of your input was truncated because `max_sequence_length` is set to " f"{max_sequence_length} tokens: {removed_text}") # NOTE: output tensor for the encoder must be cloned because it will be overwritten when called again for prompt2 outputs = encoder.run_async(input_ids=text_input_ids) output_name = ("main:#output_0" if not pooled_output else "main:#output_1") text_encoder_output = outputs[output_name].clone() return text_encoder_output # Tokenize prompt text_encoder_output = tokenize(prompt, max_sequence_length) # return (text_encoder_output.to(torch.float16) # if self.fp16 else text_encoder_output) return text_encoder_output def denoise_latent( self, latents, timesteps, text_embeddings, pooled_embeddings, text_ids, latent_image_ids, denoiser="transformer", guidance=None, ): # handle guidance if self.flux_transformer.config["guidance_embeds"] and guidance is None: guidance = torch.full([latents.shape[0]], self.guidance_scale, device=self.device, dtype=torch.float32) for step_index, timestep in enumerate(timesteps): # prepare inputs timestep_inp = timestep.expand(latents.shape[0]).to(latents.dtype) params = { "hidden_states": latents, "timestep": timestep_inp / 1000, "pooled_projections": pooled_embeddings, "encoder_hidden_states": text_embeddings, "txt_ids": text_ids, "img_ids": latent_image_ids, } if guidance is not None: params.update({"guidance": guidance}) noise_pred = self.flux_transformer.run_async( **params)["main:#output_0"] latents = self.scheduler.step(noise_pred, timestep, latents, return_dict=False)[0] return latents.to(dtype=torch.float32) def decode_latent(self, latents): images = self.vae.run_async(latent=latents)["main:#output_0"] return images def infer(self, prompt, prompt2, warmup=False): assert len(prompt) == len(prompt2) batch_size = len(prompt) self.vae_scale_factor = 2**(len(self.vae.config["block_out_channels"])) latent_height = 2 * (int(self.height) // self.vae_scale_factor) latent_width = 2 * (int(self.width) // self.vae_scale_factor) num_inference_steps = self.steps with torch.inference_mode(): torch.cuda.synchronize() self.e2e_tic = time.perf_counter() latents, latent_image_ids = self.initialize_latents( batch_size=batch_size, num_channels_latents=self.flux_transformer. config["in_channels"] // 4, # num_channels_latents=16, latent_height=latent_height, latent_width=latent_width, # latents_dtype=torch.float16 if self.fp16 else torch.float32, latents_dtype=torch.float32) pooled_embeddings = self.encode_prompt(prompt, pooled_output=True) text_embeddings = self.encode_prompt( prompt2, encoder="t5", max_sequence_length=self.max_sequence_length) text_ids = torch.zeros(text_embeddings.shape[1], 3).to(device=self.device, dtype=text_embeddings.dtype) # Prepare timesteps sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) image_seq_len = latents.shape[1] mu = calculate_shift( image_seq_len, self.scheduler.config.base_image_seq_len, self.scheduler.config.max_image_seq_len, self.scheduler.config.base_shift, self.scheduler.config.max_shift, ) self.scheduler.set_timesteps(sigmas=sigmas, mu=mu, device=self.device) timesteps = self.scheduler.timesteps.to(self.device) num_inference_steps = len(timesteps) latents = self.denoise_latent( latents, timesteps, text_embeddings, pooled_embeddings, text_ids, latent_image_ids, ) latents = self._unpack_latents(latents, self.height, self.width, self.vae_scale_factor) latents = (latents / self.vae.config["scaling_factor"] ) + self.vae.config["shift_factor"] images = self.decode_latent(latents) torch.cuda.synchronize() self.e2e_toc = time.perf_counter() if not warmup: self.record_times() return images def record_times(self): self.times.append(self.e2e_toc - self.e2e_tic) def save_image(self, images, prefix, output_dir="./"): images = ((images + 1) * 255 / 2).clamp(0, 255).detach().permute( 0, 2, 3, 1).round().type(torch.uint8).cpu().numpy() for i in range(images.shape[0]): path = os.path.join(output_dir, f"{prefix}_{i}.png") Image.fromarray(images[i]).save(path) def print_summary(self): headers = ["Model", "Latency(ms)"] rows = [] for mod in ("clip", "t5", "flux_transformer", "vae"): name = f"{mod} (x{self.steps})" if mod == "flux_transformer" else mod rows.append([name, np.average(getattr(self, mod).get_run_times())]) rows.append(["e2e", np.average(self.times) * 1000]) print(tabulate(rows, headers=headers)) def clear_run_data(self): self.times = [] for mod in (self.clip, self.t5, self.flux_transformer, self.vae): mod.clear_events() ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_flux/models.py000066400000000000000000000524521510465702400245700ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import os import torch from transformers import (CLIPTokenizer, T5TokenizerFast, CLIPTextModel, T5EncoderModel) from diffusers import FluxTransformer2DModel, AutoencoderKL, FlowMatchEulerDiscreteScheduler import migraphx as mgx class MGXModel: def __init__(self, model, input_shapes=None, fp16=False, bf16=True, exhaustive_tune=False, config=None): if isinstance(model, mgx.program): self.model = model elif isinstance(model, str) and os.path.isfile(model): if model.endswith(".mxr"): self.model = mgx.load(model, format="msgpack") elif model.endswith(".onnx"): if not input_shapes: raise ValueError( "input_shapes need to be specified for loading a .onnx file" ) self.model = mgx.parse_onnx(model, map_input_dims=input_shapes) if fp16: mgx.quantize_fp16(self.model) elif bf16: mgx.quantize_bf16(self.model) self.model.compile(mgx.get_target("gpu"), exhaustive_tune=exhaustive_tune, offload_copy=False) else: raise ValueError( f"File type not recognized (should eend with .mxr or .onnx): {model}" ) else: raise ValueError( "model should be a migraphx.program object or path to .mxr/.onnx file" ) self.config = config self.mgx_to_torch_dtype_dict = { "bool_type": torch.bool, "uint8_type": torch.uint8, "int8_type": torch.int8, "int16_type": torch.int16, "int32_type": torch.int32, "int64_type": torch.int64, "float_type": torch.float32, "double_type": torch.float64, "half_type": torch.float16, } self.torch_to_mgx_dtype_dict = { v: k for k, v in self.mgx_to_torch_dtype_dict.items() } self.input_names = [] self.output_names = [] for n in self.model.get_parameter_names(): if "main:#output_" in n: self.output_names.append(n) else: self.input_names.append(n) self.torch_buffers = {} self.mgx_args = {} self.start_events = [] self.end_events = [] self.prealloc_buffers(self.output_names) def run_async(self, stream=None, **inputs): if stream is None: stream = torch.cuda.current_stream() for name, tensor in inputs.items(): self.mgx_args[name] = self.tensor_to_arg(tensor) self.start_events.append(torch.cuda.Event(enable_timing=True)) self.end_events.append(torch.cuda.Event(enable_timing=True)) self.start_events[-1].record() self.model.run_async(self.mgx_args, stream.cuda_stream, "ihipStream_t") self.end_events[-1].record() return {p: self.torch_buffers[p] for p in self.output_names} def save_model(self, path): os.makedirs(os.path.dirname(path), exist_ok=True) mgx.save(self.model, path, format="msgpack") def tensor_to_arg(self, tensor): mgx_shape = mgx.shape(type=self.torch_to_mgx_dtype_dict[tensor.dtype], lens=list(tensor.size()), strides=list(tensor.stride())) return mgx.argument_from_pointer(mgx_shape, tensor.data_ptr()) def prealloc_buffers(self, param_names): for param_name in param_names: param_shape = self.model.get_parameter_shapes()[param_name] type_str, lens = param_shape.type_string(), param_shape.lens() strides = param_shape.strides() torch_dtype = self.mgx_to_torch_dtype_dict[type_str] tensor = torch.empty_strided(lens, strides, dtype=torch_dtype, device=torch.cuda.current_device()) self.torch_buffers[param_name] = tensor self.mgx_args[param_name] = self.tensor_to_arg(tensor) def get_run_times(self): return [ s.elapsed_time(e) for s, e in zip(self.start_events, self.end_events) ] def clear_events(self): self.start_events = [] self.end_events = [] def get_scheduler(local_dir, hf_model_path, scheduler_dir="scheduler"): scheduler_local_dir = os.path.join(local_dir, scheduler_dir) scheduler_cls = FlowMatchEulerDiscreteScheduler if not os.path.exists(scheduler_local_dir): model = scheduler_cls.from_pretrained(hf_model_path, subfolder=scheduler_dir) model.save_pretrained(scheduler_local_dir) else: print(f"Loading {scheduler_cls} scheduler from {scheduler_local_dir}") model = scheduler_cls.from_pretrained(scheduler_local_dir) return model def get_tokenizer(local_dir, hf_model_path, tokenizer_type="clip", tokenizer_dir="tokenizer"): tokenizer_local_dir = os.path.join(local_dir, tokenizer_dir) if tokenizer_type == "clip": tokenizer_class = CLIPTokenizer elif tokenizer_type == "t5": tokenizer_class = T5TokenizerFast else: raise ValueError(f"Unsupported tokenizer: {tokenizer_type}") if not os.path.exists(tokenizer_local_dir): model = tokenizer_class.from_pretrained(hf_model_path, subfolder=tokenizer_dir) model.save_pretrained(tokenizer_local_dir) else: print(f"Loading {tokenizer_type} tokenizer from {tokenizer_local_dir}") model = tokenizer_class.from_pretrained(tokenizer_local_dir) return model def get_local_path(local_dir, model_dir): model_local_dir = os.path.join(local_dir, model_dir) if not os.path.exists(model_local_dir): os.makedirs(model_local_dir) return model_local_dir def get_clip_model(local_dir, hf_model_path, compiled_dir, model_dir="text_encoder", torch_dtype=torch.float32, bs=1, exhaustive_tune=False, fp16=False, bf16=True): clip_local_dir = get_local_path(local_dir, model_dir) onnx_file = "model.onnx" onnx_path = os.path.join(clip_local_dir, onnx_file) def get_compiled_file_name(): name = f"model_b{bs}" if fp16: name += "_fp16" elif bf16: name += "_bf16" if exhaustive_tune: name += "_exh" return name + ".mxr" clip_compiled_dir = get_local_path(compiled_dir, model_dir) mxr_file = get_compiled_file_name() mxr_path = os.path.join(clip_compiled_dir, mxr_file) if os.path.isfile(mxr_path): print(f"found compiled model.. loading CLIP encoder from {mxr_path}") model = MGXModel(mxr_path) return model sample_inputs = (torch.zeros(bs, 77, dtype=torch.int32), ) input_names = ["input_ids"] if not os.path.isfile(onnx_path): print("ONNX file not found.. exporting CLIP encoder to ONNX") model = CLIPTextModel.from_pretrained(hf_model_path, subfolder=model_dir, torch_dtype=torch_dtype) output_names = ["text_embeddings"] dynamic_axes = {"input_ids": {0: 'B'}, "text_embeddings": {0: 'B'}} # CLIP export requires nightly pytorch due to bug in onnx parser with torch.inference_mode(): torch.onnx.export(model, sample_inputs, onnx_path, export_params=True, input_names=input_names, output_names=output_names, dynamic_axes=dynamic_axes) assert os.path.isfile(onnx_path) print(f"Generating MXR from ONNX file: {onnx_path}") input_shapes = { n: list(t.size()) for n, t in zip(input_names, sample_inputs) } model = MGXModel(onnx_path, input_shapes=input_shapes, exhaustive_tune=exhaustive_tune, fp16=fp16, bf16=bf16) model.save_model(os.path.join(clip_compiled_dir, get_compiled_file_name())) return model # migraphx-driver perf FLUX.1-schnell/text_encoder/model.onnx --input-dim @input_ids 1 77 --fill1 input_ids --fp16 def get_t5_model(local_dir, hf_model_path, compiled_dir, max_len=512, model_dir="text_encoder_2", torch_dtype=torch.float32, bs=1, exhaustive_tune=False, fp16=False, bf16=True): t5_local_dir = get_local_path(local_dir, model_dir) onnx_file = "model.onnx" onnx_path = os.path.join(t5_local_dir, onnx_file) def get_compiled_file_name(): name = f"model_b{bs}" name += f"_l{max_len}" if fp16: name += "_fp16" elif bf16: name += "_bf16" if exhaustive_tune: name += "_exh" return name + ".mxr" t5_compiled_dir = get_local_path(compiled_dir, model_dir) mxr_file = get_compiled_file_name() mxr_path = os.path.join(t5_compiled_dir, mxr_file) if os.path.isfile(mxr_path): print(f"found compiled model.. loading T5 encoder from {mxr_path}") model = MGXModel(mxr_path) return model sample_inputs = (torch.zeros(bs, max_len, dtype=torch.int32), ) input_names = ["input_ids"] if not os.path.isfile(onnx_path): model = T5EncoderModel.from_pretrained(hf_model_path, subfolder=model_dir, torch_dtype=torch_dtype) output_names = ["text_embeddings"] dynamic_axes = {"input_ids": {0: 'B'}, "text_embeddings": {0: 'B'}} with torch.inference_mode(): torch.onnx.export(model, sample_inputs, onnx_path, export_params=True, input_names=input_names, output_names=output_names, dynamic_axes=dynamic_axes) assert os.path.isfile(onnx_path) print(f"Generating MXR from ONNX file: {onnx_path}") input_shapes = { n: list(t.size()) for n, t in zip(input_names, sample_inputs) } model = MGXModel(onnx_path, input_shapes=input_shapes, exhaustive_tune=exhaustive_tune, fp16=fp16, bf16=bf16) model.save_model(os.path.join(t5_compiled_dir, get_compiled_file_name())) return model # migraphx-driver perf FLUX.1-schnell/text_encoder_2/model.onnx --input-dim @input_ids 1 512 --fill1 input_ids --fp16 ## Following decorators required to apply fp16 inference patch to the transformer blocks ## Note that we do not export fp16 weights directly to ONNX to allow migraphx to ## perform optimizations before quantizing down to fp16. This results in better ## accuracy compared to exporting fp16 directly to onnx def transformer_block_clip_wrapper(fn): def new_forward(*args, **kwargs): encoder_hidden_states, hidden_states = fn(*args, **kwargs) return encoder_hidden_states.clip(-65504, 65504), hidden_states return new_forward def single_transformer_block_clip_wrapper(fn): def new_forward(*args, **kwargs): hidden_states = fn(*args, **kwargs) return hidden_states.clip(-65504, 65504) return new_forward def add_output_clippings_for_fp16(model): for b in model.transformer_blocks: b.forward = transformer_block_clip_wrapper(b.forward) for b in model.single_transformer_blocks: b.forward = single_transformer_block_clip_wrapper(b.forward) def get_flux_transformer_model(local_dir, hf_model_path, compiled_dir, img_height=1024, img_width=1024, compression_factor=8, max_len=512, model_dir="transformer", torch_dtype=torch.float32, bs=1, exhaustive_tune=False, fp16=False, bf16=True): transformer_local_dir = get_local_path(local_dir, model_dir) onnx_file = "model_clip_workaround.onnx" if fp16 else "model.onnx" onnx_path = os.path.join(transformer_local_dir, onnx_file) latent_h, latent_w = img_height // compression_factor, img_width // compression_factor def get_compiled_file_name(): name = f"model_b{bs}" name += f"_h{latent_h}_w{latent_w}_l{max_len}" if fp16: name += "_fp16" elif bf16: name += "_bf16" if exhaustive_tune: name += "_exh" return name + ".mxr" transformer_compiled_dir = get_local_path(compiled_dir, model_dir) mxr_file = get_compiled_file_name() mxr_path = os.path.join(transformer_compiled_dir, mxr_file) config = FluxTransformer2DModel.load_config(hf_model_path, subfolder=model_dir) if os.path.isfile(mxr_path): print( f"found compiled model.. loading flux transformer from {mxr_path}") model = MGXModel(mxr_path, config=config) return model sample_inputs = ( torch.randn(bs, (latent_h // 2) * (latent_w // 2), config["in_channels"], dtype=torch_dtype), torch.randn(bs, max_len, config['joint_attention_dim'], dtype=torch_dtype), torch.randn(bs, config['pooled_projection_dim'], dtype=torch_dtype), torch.tensor([1.] * bs, dtype=torch_dtype), torch.randn((latent_h // 2) * (latent_w // 2), 3, dtype=torch_dtype), torch.randn(max_len, 3, dtype=torch_dtype), torch.tensor([1.] * bs, dtype=torch_dtype), ) input_names = [ 'hidden_states', 'encoder_hidden_states', 'pooled_projections', 'timestep', 'img_ids', 'txt_ids', 'guidance' ] if not os.path.isfile(onnx_path): model = FluxTransformer2DModel.from_pretrained(hf_model_path, subfolder=model_dir, torch_dtype=torch_dtype) if fp16: print("applying fp16 clip workarounds to transformer") add_output_clippings_for_fp16(model) output_names = ["latent"] dynamic_axes = { 'hidden_states': { 0: 'B', 1: 'latent_dim' }, 'encoder_hidden_states': { 0: 'B', 1: 'L' }, 'pooled_projections': { 0: 'B' }, 'timestep': { 0: 'B' }, 'img_ids': { 0: 'latent_dim' }, 'txt_ids': { 0: 'L' }, 'guidance': { 0: 'B' }, } with torch.inference_mode(): torch.onnx.export(model, sample_inputs, onnx_path, export_params=True, input_names=input_names, output_names=output_names, dynamic_axes=dynamic_axes) assert os.path.isfile(onnx_path) print(f"Generating MXR from ONNX file: {onnx_path}") input_shapes = { n: list(t.size()) for n, t in zip(input_names, sample_inputs) } model = MGXModel(onnx_path, input_shapes=input_shapes, exhaustive_tune=exhaustive_tune, fp16=fp16, bf16=bf16, config=config) model.save_model( os.path.join(transformer_compiled_dir, get_compiled_file_name())) return model # migraphx-driver perf FLUX.1-schnell/transformer/model.onnx --input-dim @hidden_states 1 4096 64 @encoder_hidden_states 1 512 4096 @pooled_projections 1 768 @timestep 1 @img_ids 4096 3 @txt_ids 512 3 --fp16 # migraphx-driver perf FLUX.1-dev/transformer/model.onnx --input-dim @hidden_states 1 4096 64 @encoder_hidden_states 1 512 4096 @pooled_projections 1 768 @timestep 1 @img_ids 4096 3 @txt_ids 512 3 @guidance 1 --fp16 def get_vae_model(local_dir, hf_model_path, compiled_dir, img_height=1024, img_width=1024, compression_factor=8, model_dir="vae", torch_dtype=torch.float32, bs=1, exhaustive_tune=False, fp16=False, bf16=True): vae_local_dir = get_local_path(local_dir, model_dir) onnx_file = "model.onnx" onnx_path = os.path.join(vae_local_dir, onnx_file) latent_h, latent_w = img_height // compression_factor, img_width // compression_factor def get_compiled_file_name(): name = f"model_b{bs}" name += f"_h{latent_h}_w{latent_w}" if fp16: name += "_fp16" elif bf16: name += "_bf16" if exhaustive_tune: name += "_exh" return name + ".mxr" vae_compiled_dir = get_local_path(compiled_dir, model_dir) mxr_file = get_compiled_file_name() mxr_path = os.path.join(vae_compiled_dir, mxr_file) config = AutoencoderKL.load_config(hf_model_path, subfolder=model_dir) if os.path.isfile(mxr_path): print(f"found compiled model.. loading VAE decoder from {mxr_path}") model = MGXModel(mxr_path, config=config) return model sample_inputs = (torch.randn(bs, config['latent_channels'], latent_h, latent_w, dtype=torch_dtype), ) input_names = ["latent"] if not os.path.isfile(onnx_path): model = AutoencoderKL.from_pretrained(hf_model_path, subfolder=model_dir, torch_dtype=torch_dtype) model.forward = model.decode output_names = ["images"] dynamic_axes = { 'latent': { 0: 'B', 2: 'H', 3: 'W' }, 'images': { 0: 'B', 2: '8H', 3: '8W' } } with torch.inference_mode(): torch.onnx.export(model, sample_inputs, onnx_path, export_params=True, input_names=input_names, output_names=output_names, dynamic_axes=dynamic_axes) assert os.path.isfile(onnx_path) print(f"Generating MXR from ONNX file: {onnx_path}") input_shapes = { n: list(t.size()) for n, t in zip(input_names, sample_inputs) } model = MGXModel(onnx_path, input_shapes=input_shapes, exhaustive_tune=exhaustive_tune, fp16=fp16, bf16=bf16, config=config) model.save_model(os.path.join(vae_compiled_dir, get_compiled_file_name())) return model # migraphx-driver perf FLUX.1-schnell/vae/model.onnx --input-dim @latent 1 16 128 128 --fp16 ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_flux/requirements.txt000066400000000000000000000001061510465702400262040ustar00rootroot00000000000000transformers diffusers==0.34.0 sentencepiece accelerate onnx tabulate ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_flux/torch_requirements.txt000066400000000000000000000000721510465702400274050ustar00rootroot00000000000000--index-url https://download.pytorch.org/whl/rocm6.2 torchROCm-AMDMIGraphX-46524e8/examples/diffusion/python_flux/txt2img.py000066400000000000000000000137341510465702400247030ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from argparse import ArgumentParser from flux_pipeline import FluxPipeline def get_args(): parser = ArgumentParser() parser.add_argument( "--hf-model", type=str, choices=[ "black-forest-labs/FLUX.1-dev", "black-forest-labs/FLUX.1-schnell", ], default="black-forest-labs/FLUX.1-dev", help= "Specify HF model card. Options: 'black-forest-labs/FLUX.1-dev', 'black-forest-labs/FLUX.1-schnell'", ) parser.add_argument( "--local-dir", type=str, default=None, help="Specify directory with local onnx files (or where to export)", ) parser.add_argument( "--compile-dir", type=str, default=None, help="Specify directory with compile mxr files (or where to export)", ) parser.add_argument( "-d", "--image-height", type=int, default=1024, help="Output Image height, default 1024", ) parser.add_argument( "-w", "--image-width", type=int, default=1024, help="Output Image width, default 1024", ) parser.add_argument( "-g", "--guidance-scale", type=float, default=3.5, help="Guidance scale, default 3.5", ) parser.add_argument( "-l", "--max-sequence-length", type=int, default=512, help="Max sequence length for T5, default 512", ) parser.add_argument( "-p", "--prompt", default=["A cat holding a sign that says hello world"], nargs="*", help="Text prompt(s) to be sent to the CLIP tokenizer and text encoder", ) parser.add_argument( "--prompt2", default=None, nargs="*", help= "Text prompt(s) to be sent to the T5 tokenizer and text encoder. If not defined, prompt will be used instead", ) parser.add_argument( "-s", "--denoising-steps", type=int, default=50, help="Number of denoising steps", ) parser.add_argument("--fp16", action='store_true', help="Apply fp16 quantization.") parser.add_argument("--bf16", action='store_true', help="Apply bf16 quantization.") parser.add_argument( "--output-dir", type=str, default="./", help="Specify directory where images should be saved", ) parser.add_argument( "-o", "--output-prefix", type=str, default="flux", help="Specify image name prefix for saving result images", ) parser.add_argument( "-b", "--benchmark-runs", type=int, default=None, help="Number of runs to do for benchmarking. Default: no benchmarking", ) parser.add_argument("--exhaustive-tune", action='store_true', help="Perform exhaustive tuning when compiling") parser.add_argument( "--batch-size", type=int, default=None, help= "Set custom batch size (expects len 1 prompt, useful for benchmarking)" ) return parser.parse_args() if __name__ == "__main__": args = get_args() prompt = args.prompt prompt2 = args.prompt2 if args.prompt2 else prompt if args.batch_size: assert len(prompt) == 1 and len(prompt2) == 1 prompt = prompt * args.batch_size prompt2 = prompt2 * args.batch_size bf16 = args.bf16 fp16 = args.fp16 and not bf16 pipe = FluxPipeline(hf_model_path=args.hf_model, local_dir=args.local_dir, compile_dir=args.compile_dir, img_height=args.image_height, img_width=args.image_width, guidance_scale=args.guidance_scale, max_sequence_length=args.max_sequence_length, batch_size=len(prompt), denoising_steps=args.denoising_steps, fp16=fp16, bf16=bf16, exhaustive_tune=args.exhaustive_tune) pipe.load_models() images = pipe.infer(prompt, prompt2, warmup=True) if args.output_dir: print(f"Saving images to {args.output_dir}") pipe.save_image(images, args.output_prefix, args.output_dir) if args.benchmark_runs: pipe.clear_run_data() print("Begin benchmarking...") for _ in range(args.benchmark_runs): pipe.infer(prompt, prompt2) print(f"Run time: {pipe.times[-1]}s") pipe.print_summary() ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/000077500000000000000000000000001510465702400255675ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/README.md000066400000000000000000000034531510465702400270530ustar00rootroot00000000000000# Stable Diffusion 2.1 This version was tested with [rocm 6.4](https://github.com/ROCm/AMDMIGraphX/tree/release/rocm-rel-6.4) revision. ## Jupyter notebook There is a dedicated step-by-step notebook. See [sd21.ipynb](./sd21.ipynb) ## Console application To run the console application, follow these steps below. Setup python environment ```bash # this will require the python venv to installed (e.g. apt install python3.8-venv) python3 -m venv sd_venv . sd_venv/bin/activate ``` Install dependencies ```bash pip install -r torch_requirements.txt -r requirements.txt ``` Use MIGraphX Python Module ```bash export PYTHONPATH=/opt/rocm/lib:$PYTHONPATH ``` Get models with optimum ```bash optimum-cli export onnx --model stabilityai/stable-diffusion-2-1 models/sd21-onnx --task stable-diffusion ``` *Note: `models/sd21-onnx` will be used in the scripts.* Run the text-to-image script with the following example prompt and seed (optionally, you can change the batch size / number of images generated for that prompt) ```bash MIGRAPHX_MLIR_USE_SPECIFIC_OPS="attention,dot,fused,convolution" python txt2img.py --prompt "a photograph of an astronaut riding a horse" --seed 13 --output astro_horse.jpg --batch 1 ``` *Note: The first run will compile the models and cache them to make subsequent runs faster. New batch sizes will result in the models re-compiling.* The result should look like this: ![example_output.jpg](./example_output.jpg) ## Gradio application Note: requires `Console application` to work Install gradio dependencies ```bash pip install -r gradio_requirements.txt ``` Usage ```bash python gradio_app.py -p "a photograph of an astronaut riding a horse" --seed 13 ``` This will load the models (which can take several minutes), and when the setup is ready, starts a server on `http://127.0.0.1:7860`. ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/example_output.jpg000066400000000000000000000715011510465702400313500ustar00rootroot00000000000000ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?å¿ )qøQßÒÄÇ&´ìv”™¦Ç^•7±¼wï@´äÔ…r?qÓ¥8{ R(#ÐRöô4ŠW®*2¦¥4žØ D$v¤©ˆÆ¦­L1L ƒN)@çùÔl¾•&y¦‘@‘Hj›Žô›A¦~”õ¾_½(€ ^½¨Çj\du¤J‚9¦ìÍI·ß¥&8æ˜ =ûÑ·ñ§`çÖ“€ ´mô¥ÇáKƒ@ Û¥.9âך1j@/áFïÚŽ{SŒ”Ï(ôíRNô¾ýE±Ò<Ø©0æŒPqùQý)p~´£Ï) ôüQ€  ¶ô4›H>µ6?*1žÜb€#à=)qíKŠn=iqíš_ëF84œñÅæœ:ûуšnn´¸?…)ÉÅ.ÿª€Ž˜¤Áô§ã߃I·ñö ㎔`柷ãI·ŽF(˜SÈãµý(˜¢ƒŽŸ…çÓÞ€øSO·=¸§ãŠ1Ø~tÂ3HqøSö¾”&1ÔQ·ŽqOÛúRíÇLŠ…úT˜'ü…{PxÈéF1Ïý¾‡ëFÜö dxü),vÑÎ>´»È <”Œ09à0iÌŸ'C×ð£ô¸£·­jϽ?¤Ç¶ b}iÀf“åê(=i¾õ+¯Ë‘Q÷ óF=éÞçô¥ÆGJhëÿÖ¥Š]¼ÿJP8ö Çâ=…j@2?Ï4à P2§'¡£nOó©Šdr) @lÍ.Ï^•0\v&‚´‚ÄqëF9Æ:T›8þ´músLñèhÇz~ÃÅ.ßóë@ˆñŠR½±ÖžWŠ]½1ùÐ[OJ6öïR`õèhÛùúÐxü¿•'oçRzuÏz6ƒ@†`Gÿ®ŒcœŠ]¤sÚ”9¦@íJG°§í?g  c6æŒsœ}iûGãF§ZB#è3þE(Á>ôüsÒ“iê)€Ý¼qKQÒ”{Ž8 ôÿõÐ@•>ÁŒS{âc¯c¡©1ÏOÖŒtã9¦"<1RmãÚŒb€ŽÞ”„du§•έ'NüPŽ9˜Ï~´üqIùRÆ÷úv¤#Ž”óIžÆÜ„‚§#¦²óŸÏÚ¦b:cŠ=i€ÑÇò§ãü©§Ôìq鎴€víKƒKŽ)xÎsõ¤1¤‘LåO¥K‘ŽqÅ4ò3@Áö4õ8¤Î3éOÓ­1 ´¥<.}=hQÓÒ¤ ÇÒ€¸Íj—hÝM#žø¤&3¯_Ò”ñÓ­8:~TŽý{PqÜb `ÓÈ#½`œçëL. ©@ïúRã¡Å8Ö\n;RãþTìp)߇?ÎÜn àQóÚ¤ÇZÆ21@\n&Þzþu.9éõ£ .Dcç=(Ø Î3ÏÒ¥Çm9äP>Xlð dr9©¶ñ×sHG?ʘ>œÒ…çÞ¥Ç^”„p;šä{p3@LF­KÐôëÖŒž>” ä{wuZO/*xª`9÷£gÓ'žh"œðhÛÇ×Ò¥ÛíJ84ȶô=)vöíRãŠ] vÆ(”ý¤ÇZPd÷ .GÇCF§56ÑéFÎP2 «Í.ÏÎ¥~¿_jäAzñÓÖ¾‡ð©vgÞ½28 w#Ç)¥8<Ôû;f“`Î~ž´X.~¾ôí½8©6ãÒ·ž˜ëÎ(N¢”(Ï~zÔ˜à~”»G\gñJ¶ÓÞ¥Àã"€¿ã@íÜ=)¸Æ8©ðÏ4Ç`hçŽôc¸è}ªp¾œÐÛó B{P•6;tõ£•bœt¥ØåRøþmÉãõ ö€)1ϤÛþQ³·j@FWð¤Ûøö©vLÒÓŒP{AÞ´Ö\üêm¿R=)¬‡hþT _o­7xöæ¦)“ùÓJòxâ€!ÆýiŒ8¦#¿ZišŸ'š1Ó¦*M¹â¼Óßn”mÏ?ΤÚ9íFÎhÓõÍ}³õ©1ŒqFÞ=½(2õ¦íÇáÞ¥ÇùÅëÍBÈ01£Ùßd§ùlÏ¥*•ãœÓJ½ªÑž)¾^9Å ±[ËÁã?áFÏÌÕŸ,Óð£ËÀÆ?*.+mã8Å9ãõ«>^}3IåŒÐ+ùÏëFÑŸ­Yòý8Í'—Àù Ä>=½©È¼Ö¦ÛÏf³¯†„éôüéê¹çŸzz¨µQ@íÖ” S±ô£SëLã§z1ø})øõéF'ýzf3ךM™À§yÈüiyÎ4ÓÜw¦ííS•Êñɦcž†€#äö£㌊“Ù£Ð4ÈÈÏN}hÅHW £OCH¤Gþµ&9'Z—oN´…h-¸”mÀãJ—n{t¤+Ûâ€*m<ÿZ÷ÇJ¶QO8Ã8†Ú@ëži@¥ÙÇN´lÉàs@ ÛíÓŠ1ÏjP3ŸÖ”(=¨ {qíFç>Á“úÓvsï@=zRÍHWy´mã¥G´xý(ò‡¯5 \œÞ´í¸<($úƒKå…O^Õ2jQ×­VÚF3š6ö«G¸¦4c¯O¥CŽ?¥(?i£}h¸ëëüèÁúÓñïÖŒ~ÌœŠ1Àâ¤ÇçIŽ=¨›A¤+ÛšŽ(ä“È4Ž)>}ê^ãI··Q@qŽœô„ Ô˜÷¤Çnæ Ç8Rvü)øãú â€"1·cša_lf§Ç9àÓN:RŠÇßò¦ØU–PG"¡e Døüip=zÒãŠv*€­(ìfŒ~CҀƌsïR}8¤Ç8â ýz0ÛÚ·ßó¤ïÇziŒŒñI·Õ2¶3žE+(Á"€!ÛŸþ½sÏZ~ÓžœQ´æ€¹;õ¤ÆG"¤Ú:Lr{ЃG>Ôüýhg΋Æc¦F;ѶŸ·Ž*8Á=(°\`8ç­<®áF3Ú•ASÅq»NqÚžãõ§ã ‘øÒíä(1“ÓëŠP ëOüšP¼P¡W±ÎzÓÂöô§…Èì)ˆ@1鎘£oÍšz€x=E)S@ ÛùRŒãùÓ¶zpGZ\ ã”ãƒ×Þ‘£#$ÑŽ1éR”#8ïMÇ"˜‘Ç®i»1À”¯cÞ›Žzþt9Æ3Naž”¸\ç¿­(Áþ4Îý~´îاìÏ£aàã4ÑÏåHG^:vÞOj\~tr9¤Ç_Z~8þtõÀ C1Ò½©øïŽsK´zÐ1˜¤Ç>õ&3HW߯é@ #ô£n~??jL¥ #Ç=)1È!*¸¦‘Ür*LvïëIŽüPxç<žôƒ¡¥I·éÍ5€ˆo­ã‘õ§ɦŸCÅ7Øv¨sœb¥9#ñ¦àÒ>=¨ÇÅ?©@éùÕ3õüé„ ~8ç×µ.(>¤wÅÇz~}3Iƒóšn2hÛÇô§Û­çÖ€ŽiA#úÓ±íK¶ Æi6Ô€`÷È£'ãò m=)¸ïʦ9˜£­GŒgúR‘Ó¾;SñFÞp8 ç½7nG5.29Rb€"#“ž8©JÓH>Ô ŒŒãÒ“Ÿ½HFOÓ­!õ¤xõ¤##×4üzqIŽN1@²qƒÅFFjÁûTDqÒ|šaÍJËŒöÍFÞâ€-âŒvϽ;¥Å1 Ç~”c­?œÐGCÚ€Ž;ÑŽHÍ;cœu c¡£iävÉ£œç4ÀE(Ôà;{Ó±ï@ Ç4`õÏ_Zv9Ôc=óHwÛÐ1RqéIŽÜPwÉúÒmÃt§wϯ­¿Î€¸Ò¼æ‘×µ;4ìuãñ Àpsø{ÔepqRAéȧ²îî)‰‘¯ó©Tu¦*ÿ:™G·ÒN”à8çò¥Ž´ð£=9ý(дð¹ãŠP9ö§*’{Ó¸ãŸÎœœSŠœóÖ“SBcÏ RcÔu©£úS¶PÉ£iÇN•>Þ™ü¨Ù@öàóøÒÏ59^}i6zÔÀ¯·'ÖŒséSã¥&Ìf€"Ç^ô¸ê:“iÅ&ÞØ8¤er1Љ‡>µk5§'±Í+cŸJéÖ¥ÙíœÑ°ŠbÒ¼õäÔ›ýT»®h<Çjv3ŠpZãœR2¸àSHç=ªÁ\öÅ0®zŸJFÇ¥.:{SöñŒö¥ @ãµçµI´ŽÜÒc·4€f8þtÒ¿Î¥Å&=(”uÅ7hÇÔ})ßÊŽôÌrzQŒ÷ëÍ;zRãŸZhtâžxü(`Tp2=hx£olf¥*·>´m E·œi )àtô©È8íM r¦{hÆ}êM£¯J6{u¤xçáÐg“NÇ? P´Ò¾”/ÒŸŒg4¸ cJmmÃ8úS‡~ufÒÊãP”ÛÛÆKm,\œ"V=…t6~K{fÔA»¸̱ÆHLÿ¿S@vº]ýêo¶³’DÏÞû£õ©äÑ5X—ÓåÀçä!¿•mÜøÊ×Ké‹i=Þ¨ªYÚ.Jp:ž€{ûW ñ'Çþ0ðÝ„2tí1njBfónw8è(KMWÖ.ÞÞÆ)'1¶É$ ô$÷ö«Þ×u½8A§ýÁÜHè»™€àcеÇ|ñ½¨ëÒéfuUº=ÌË´i÷n½•{Ùæ€±ãÿ4Ë«–Õô]WS•µ]2-âg1Ÿºàúëî¼.ð#lºw|p0>õÇüE¸ ø¥á¯Æ6Áw›K¼tÉ÷þ•éZ®¡§i¡¦¾Ô-í†CI \Š¡æú¦¥¨ø|‰/4ã=¸êðGÔU Å:ˆ%[{;ôK£ÿ.óŽ~™àÕS† ÚÂÜö"‹ƒúWˆxïRðuõؾðÌ÷‘ÎN^7‡bçÔÓLV>ƒ–Õâ%]J‘ØÔ%yÇjóÏ…ŸdÕ¥‹Ã>"œ<¬Ù^È~löG=ýz„öͲ°Ã)ÁÓ¸š3öúR©Ý<ÓïŒÕ‡ð¤ÁÏõ©vûõ¤+ëúR-½:ÒuúT¥NOµ&1ž>´=sŠkõö©¶æšË@ÊØöéF=AÁö©¶àŽ)vƒ@ˆ1š1ß1L}s@ãÖ“zTÅ2AéHTÐ1Ÿ‡ÿ^½ÁàÓ±Ò—;`óF8©JñÅ&>´1Àãš1ÏJ¾(Æz@FGëAüvþTžô ”˜§‘úRw¤d{SHãÚ¤ÇÒ8 4޹ÍH}?0ƒ×½!‘2ä Tl0y©˜†£aÐÿ*DF­1†{téR0Ša÷¤2î=¹£äÒŠ\gµQ#;JàzS½8æŽçµ!Å&3žiØã8£(¼Ñ×¥)hÇCŠUþª”.GáQ¯õa@#ü(,dÐô© üÄR€=? b#ÛMÆ}óR)1ƒ@àtéACJ ÿ:]¼úÐXíÈ4»p? ©08 h›IéASèxéR…ìq’jkhš[˜‘q–`(ÔžÓoì4Ø5+É¡O5g’Ö*.?/˜{¨ãŽ•è=ÛÒ¼.çÄš¥¿Ä©,u'm=Õ-í˜oiÎ2Ý?Ò½vóU›OðýõüðâKKrØ…Í"®r:tâÏÆþ(Ôæ6ʹXUW;Ø*ž¼c©õí^ãûýBóÄ_ê0]´ùX¼²J ô¯FðOˆ5‹¿‡~(Ö¥¾K¨üâÒ@ë‚2:Ûµxö¼‚µŒ^Etª›ÉŒðŒz­ `=¿öq¶tMbð¯ïLËolf½GÄ+Óü=. –á”´vè@b=I<(÷$W€øÆÏðÿᾡ3ÀEæ¡u‹eÆõ † þuÀÞx‹QצvÔ.¤»n+» O¿­'¡Q\ÎÇYñ/Æßð˜H‹4â_³±ò¡¶âG®ãÌŒ}xçWW77/ºââYˆ #–8ükSËMƒ>˜ÅP¼U »GÖ’•Ù¤©¨¢%)UŽŽG†T–6*èC+ Žõõw¼Gÿ ƒ,õ9È¿quþøè߈¯“ëÛ¿g»ÇiuÝ8±òÚ$˜ÁÅ {ë2G×Ö dýkBDÁ?ΫÖ´ ¬WšnÞøü*ÁŽ1L*(¼Óvÿ>•1ZB=‡4ÜQ·üŠ“8¤ÅFÑúT`ÅY¤d @SÏãFÑŽ)åHã·­š3½ézT½}è*úR¾Þÿ&0MM·Û&šV …!Å?£ÿGŽ(ÇëOÇJB=©éHEIMÆ?úôÌ~4ÜqRþE4õ dgüæšG*B8éM"‘À¦žàõ÷©ÃïÒ€#==*3ÔñùÔ§ßšŒóÚ6p8¨ÈÇ•3t=y¨ÏéHeÂõ¥‘Å/NM&1THcÓµ'¿éNïÏ4ØPqÍ&8§uçžýh>˜¤Ç`ißZ9â€~U")€sÅJ£­á Ê“¹ÖÍà;Zñ•—Œ¯îåèö÷ j@|gŸ§¥ušœ¿nðî©ö‹n’E'ÊéÑ1Ö¹Øõ›ã?†m£²u @ò1X0?®:VÞ£w}£êéñE,ÆH¢|¬Oš’—™áž×|9§ø7UÒn.Q%º¶ôꊽûL}jOxgÃzgÃ#UÓ¬e†âêe|ƒˌ¹¨mäð¹ xG‚îtýäV~`À|ä3ã<{V¶¹§üEø™,rèS­’ƒöxÒ1j?gëNÚܯã^öÏøkYÒ­÷ÚZ[ˆÜÆ8HÙTƒôÏzð›y|·íÏs_J隇Ä}Â0é×>²¾†Õ,p Jž›~•Æ_ø_ÃZä×^×tsV[¯™ èjÏ2‚'¹—åŒur:ý*ž c7"û¨0yï^š¾Ñ5ˆ¶i~*¶‘ºî?vÃÚ¹ýOᦱb$y>ü†©Q³.U.p¬9ÇZB1V®t»ë'"æÎxˆþúUI= ª"÷½Çöx°;\ÔˆÄk©<׉[[Íwsµ¼m$Ò°DE,OA__x Âð‡x2ÛL“jÞÜ‘ýóÛð¡Ó™pO­Ueâ¯NïÃòªl¸ç½j‰"eÇ|ñLÇÔØ¤*=(R5.)6õþ´ÞOõ¤ÇÒ¦#¦i¥OJGŽ=)qNï‚)vþ]Ã¥DÉ´Õ¿ýjB¹í@°iGbEHSsïLÛ߀F^ãñ¦ã¾:ÔŠiqÏ­E·ŸZn:u©JóœR@ȱHG&¤#Ši})ÌsM#§ò©éÏåMǰ w¦ãñ©1ǵ0ÇëHc­4ú~µ!ÃÓ‘H5FF=þµ)¦0ìh"=zS Jj28¤MÓ·áQ°©˜rGO­DøïH ÜžŸ­&1ëïKž84cðªßéKÛŠ\qéIß­&9õÍ'ãÎN1ýhê~´ÞþôzóKõ?•ýè;tü*dãÞ£¶jD oznÇ'?Zhüéà{SÈã?CM8©Hϵ7oL hÁÇ{S€üip 4Àn}r>”à3éšP)ÀŸP‘Å2[;K¿.;èR{`Á™¦G§€9äPÇõé@¯»×lï¡¶imàB.cP:~~•CÇ:µðNš¶švù5«…#“æXAîqׯ©¨[É{¥Ïc þùa{¨˜uVãñ®'áŒí㯈çPÖá‚â]2Ôl!1‡S¸žH•óÄžkiŸ’îA8ë@­íTq$»gg-!žsŠÑ´›^ÒÈþÎÖfŒv]æ«ÛÅäI" '?Z¶žh'jãÚËçÅþ3TÙ,ÐÜ™’5oçT/µoR’ãLÓŽF }•N<ÌüÊsè*eÉÛúúS» >ˆü-â;}vò:Ú契HÁaî+ê5+-_N‡P°¸K‹YÆRXÎAÿí_38TÐõIKsŽ=x¬¿‡ßu/êpƒ,²éÿéÝFR¾†„&}K0Ç5Y—⦂êÓS±‡P°.-.|R!àñ¦•â´D•ŠãÚ“Ô˜çÞ“â˜ã­â¤Ç=©6óŠ#„È8äÓZ=¹­Sñ½åׄ5XôéZ;ä€Ë¡ä:ÃùV€ückã­JJÇ«[—pc?ßÐÒ½‚ƹSÏoJöæ§xö±ôЛŽ:š`4!#v ¥4®8üq\~½ã·Ñ~'èÞB­i"ˆïÿyþïâ+¸š/.FCÔR¾ T#ži…1Þ¦#S¾Þظ© qŽi¸Çnh\qM#?JÃó¦ãž)=)¸ã5!ñHG½!‘‘ÓúSN “ÓHö ñÇÆš~•!éý)¤~”€ŒÓ½4õ÷§‘ôÅ4þy Ï=i‡­J!L9Ï¿­ "#µ0õèjB8¦ÿÖÈO§j…JÜqùÔmý(ïNx¤ÁƧíI‘»äsŠd‰Ú“ ;š1ÆzPZ‘KŽÔ{ö cz 1Úî;Ò~˜ ~f¥U=ª0*d ¼SÔñMôÍ;¯Ö˜ÎF Áã¯zCÓ=(#š`;¯§4„séIÏ­.x÷ B xô"š:Ó‡>ƒšxÇ?­àÿ:^hHªê?Ù8'Èá^eð"ýíüwygs¬·v…ƒz• ñíŠô뛑iàïÍŸ™-K}F y‡._BøË¦³ÍB. ¶ÌeŠºñúš—¹Húµq>4ðÖ§âM_IŠŽ-6=æêRß88ùvZí»Ö¯.¯¨n³Ñ%ŠÔ‰oå]þ_²/ñ7¹àRãô•Ó­4Yã»DT_!¬Æ{— :{×K"‹xU<ÁåÈܸãÖ½âW‚¦ÓekõÔu Fá[tí|2 õ܇ҼòòîçQœËr°FÒ ù“`}(QÛ/äp¬3‘ØÖ´p¨Ú6œãµe.çÓ†dç<Ž•/öŒ±@ p‰Y0-È4Š5E¸'=óëJ`$(ü)#whÕœíb2@ÅJº¾0xÏJ@Aª9‹Á×îöï\…»¤QÛ¬‘B3ÓšèüK*Çá8â—¹;€+Ö=·ð«’1OcLxøMãmSÓbð¬vãN¼ƒ- ùgîqïí^ˆË†#Ž1_)(—‘\ظ†îŠpA+èøêÃÆ)²¤:Ú/ï­Øæc«¯µ\Y-ì¸b)¥yÏ<ˆA÷¦mëžµd‘…çÒ‚¼óëRªóÓ?JBhamŽ_§¥|ÿñDÕ>xáø‡Á¶¶×ú•²5¤ÌKnõ¿ºi…ÊŠ¦WÜXë“Å:Qì.á½°v–Òã÷ªqÄÙÜ’ç,`ð=3VðL@(USÏ¥"©¼/âxz NÒá]€\ÇŸš9;‚;UùÁœýkÁ~ øŠÏAñdöåé&Èä=Ž‚½îÿˆ) 8zàÖ±ÔÍ• Ó«±n‘¬cÇ.„ûBðÇ*Œ˜qóíP]êrÁ|°’ I÷·Ýúšð?ˆúð×¼S$Š«ˆ—½NwÔæœ†oüR¿ñK <½®˜Ü /ûÄ*³¦XGi¤¦‘t#e0‰ç^ê[§>¸®WÁš8Ôõ¤–e͵¹ó?ÄGAZÑêfïÄÚš¼¡gZ•r:è+&îRÐÆÕ¼15¬÷­dßhµ´Ui$þè5Ïb½Z½¸³Õ-^å–(.âò®–5ùXnõƒªj3x’æ H¡µ¶`‰g¬ÝÍs•Ù|;ñõﵡ*–—Nœ…º·Ï ?¼=År—v“Ù\4”‘zƒPPÄ}µmsc­iPjºlË5­Â†FQúz‚Dǯ›>üL¾ð5çÙ®îtiÛ÷¶äò™þ$÷öï_KYÝØkzdZ¦™p·sÊËÛØúN/£ElÇÞ“mLé‚Ah"&#´zô§OçIú¢öëIïŒÓ±øR{tþT†7Žhè=)ØéIî(1ù:ãÒ— §@ÁG52Ž:SgŠ˜ô 1šP $b—Óø÷Å1«×8£9§Oz\cž(:QÜ{S±Øp(¸4Àf28§`þtí¹ô?Zv?ýTЧéG¯§­)é‘ùS±ŽÄš`F#iEÎç ò?Ž·:dºîkgt%¿´„Et©Ê¦: úûW°ÆÛ%RȸûÕä¾5øI Ù’MÜ^j²‘9—Ž£=|W¥_jq\\ÍbñÏ º²DeQ’Ò9®;zÖ¥ÌWÇoaaòAw;Ÿ©éH“á’æÃQ¸Ó5R°ÞÚ±GB{ÛÖ¯i×±‹º±É$1Ï¥}5qáÏ ÅôºE£ÞÂJÑrO¿zÀñî…á t—›WH¬ä… GqxÉïÇ_¥0<5çYb?6ÎùÏ¢¹….,ŒN ŽOö¹õÏA¬ÆghÞ\IJIcrö&´ßP‰¡b³Ã ÛÑN(ÿÃ=Mcâ ½¼²¼–v/ö‰›;€é_Fë:e·ˆô‹í+P\ÛÝ©RGU=ˆôÁÅ|µàÝ^çEñÖwk#6Q¨èÊxÇÒ¾§K¤óZ3‚GJ¤´%½O’õ­:ïúåÖï°z:ö#ØŠ»ml“"cš÷_‰>ƒÆZX¾´EY³\Äëÿ-Sû‡×Ú¾ŸY6òý’ÒÜ™”•mëÈaÖ¦Å&X’9lÞ;˜ÁY-Ü:°ã¥}i©G¨øjÓU†ÙÄsÀíù±Í|êÚ6£q§ ›‹ý¥¿å—µt>ñ•Þ…kªh÷â[­aiHòÆ@>R=2x«‹°¤Qñ§Šµ5K‹[KÆTpUÀ*já …în%屓üêKë§¾½šêO¿+#®*Õ†èpÌ”í\vÍKwcG}áè ÓìʡڊÜ:“ƒ“\LJol­¯u+ÛÙÚ8ÿ…QC3’OZÕšá­4ë“’6Âqïÿ׬¯ O¦XÂ×:”I!y‡+’¤ ç¥"™¯â8ZÐæÕ#áŽ(Ï÷NÍRÓa²ÐÞ[/—4ÑlüëÃŒvϺ û†ÝN|»¨ÝvºÜq\-«Hö·V H’ó¡9Æ ýïÌ*™g]h®õk[id諵æa?.µÎ°ˆ ­iÉqöè¼¹pÒ¼HzûŠÏ–'…ö¸÷ô½fc¼‡ì’ádÿ–Mïé]·Â_ßø_ÆinÌú}ì¢íØðôaï^p R8#¥wôù|IãËΆ`óóŒªóšÀ©o"òä gôª-×5¥{ó;~+9ú•h‰!aŽÿ…0ÔúzS^™ÄFG^sM#>Õ!ÿ"˜}?0ŠaSÍ4ûR3×üi¤ry§ž¼Šg½ ~Ÿ\S|ý*CÓ¡¦@F~”ÃíùSÈíLn)*Œ÷Çó©O^•žzP1ßÓ5v©O__jˆÎÍÒ’IíëL¾Ý¨üiݺqH‘ btÏj1Û¿…æ€Ó=)Às@ñOŠ*Š™Gµ0õ2®h}ùÏZx^i@Î)àw=½i€03Þ“i$ñÍJ¼ç4ry¦{{“FaÒ¤ÙøÓ‚úSÃß¡9§y­JRöÆ(2˜=é6g9íéRàÓ­#ëï@„<­yGÆ›bº†l¨ Ü>åÙÓ,@'ëšõí¤©à× ñ7A¹ñV¯§Ùèì²ÝhQ$בnä`x÷àš™»!£Ñt_i>ð­¶“$`h€™9þ9'õ¤í¢­¦Ÿ#2¬Ñ…\ú’y­+gWµ…×î”géUîêBV"_ùè˸þüjFyŸŒõȼ!dnç-õ —Ì…RfuÞlþ§¯¥aøsâî•5ˆ¸¾½6Ú¼¬±Ÿ0k‘’?Zí|iá¯Ýé±ÿm[ÝÞÉ“B¬óHÇ·¥|Ïâ}!íõ6ºþl¤áo«þ9¢À}A¤xÎÃY¸Ôoax¥[aò¶IÀzùÓÇ.|SvL‰ ±Þ™ù_óý+‘²ÔotÉZK+©mÝ”«Ûì}j¡$’IëÍWC›HÄo®Á<¶;Òݶ¶{~èVþ$øxlQ.ôI+¼`ÇÃyc3ùŸÊ¼¢¶<;¤&§¨F—O$6„áåEýèÖ€:Z[j?-’ؤz|R×í "ÜšöéüG¡ÀÏ$úÞ ›<ûWÃ/ “´z± ¹W2.{b®7Ã?{É$SüWO¥Zº&éůŠôBǯé›Ç#7W!âOhÚŽ´þ!Ñï´Ó$¿ñónnQU‰þ$ô>µøiá9!g}&x è¨SëÍy.¯¥è°^Ü 7P•­#cË¿L'¨÷¤ÁF¿bÖï'—0Y¡ÁrŽvŸCÞ¹=:ÚçWÖZÊ LqÎzù!JŽ¥½ªMÃw×ñ Zf·‰þ^y,>ž•ØÞYÚøSÃ.!LÜ].Ç|üÁ}¾´®ZG©Z[ϯ5¥„a-ЈÁÏÞÇV?ZЋF¹KŒÆ§h\ýÕ걚âì½òäŠêté•ÝØÄ~”†`ê32èW-ÙØ.U=2Ö9í4ï;ý_™!ü~¾œUß ‡N!p<Ù²@5§IæiHˆÁZåÎyÁ ÛC7ŸG)—ä\dÆÞÞÕÁ_™´ýY.vá•Îî:‘×ô5³ j&bóÿ¬Â‘ÓõŒm¼½ZùC)¬é· r=è1/¢·~lc÷RbHóÜÔ·[›R˜,1žê{ŠX‡Û4§\2׿_R„óùUJV5±ÐP",WGàmy¼3ã-3TÉÇ0Y}Ððk>޳ܘÕãüÊÝTŸJ¹=º´Y-ŒÜ¶p0Ýèì{¢“"ÍŽEŒ;ƒY²xëŠâþ ø¸x‡Â‡D»”6£¥üƒ'%âì}ñÒ»‰WŸÒ®,L¨zJaæ¥n§¥FȪÃëÅ0ô4óÀþ”ÓŒzÒ‡×5õ©½0Ð!‡õ¦?ZyÏÒ˜zûÒ‡Ûõ¦ž”óßõ¦éúÒ‡ò¨ÉüêCùLn”€Œ÷¨ÛðÅHÜš»õ¤26Ç?Ò¢nõ)<ûTFìiv£üiqÇŸ…Qß­&iphþ}h˜½þ´cŸ¯­(é@À ‘GaH:ÓÀ c”cžõ*õèiª9©T`PªœZ^¼E(É8ð }i€ Çjp¥U=M<Oõ¦€`ÜÒíöü=j@ŸäSÂuöõ¦!Iƽ(‹#žõ8L…8'9àö |£Ž•"Á’=je^yÍL‘”ÄEk,yØ¥±ô®áE¾§qqâ/ß«ÆúÉH÷© BŸOJôuˆŽõíݦŸoæÞÝCmè]‚ƒøTµq•§ñUž—®Ú郘~×Í£ã‰8åzpk|¼Ò«WnG1ãð¯;Õõß ø“O»‚×Tµk«UY"’^pIõÉÕxKÄIã [êÉää˜æT‡^=¡¨jÀÍ Ô·ŒµßLÔ(Dr7}ó\_‹t8^Ä6Ÿá µ™O OrPcñ9¯A‚Ò s˜ãñËžXýIæŸ$I(â·ÔP3ãoxP†íäºÒTUÈŸŸJä^6¶º•oB9¯¶u­;ÖÖÏy¬G pGóšv ? ×Ïüu¡ë)ý›á­2¬Ôâ[¶‡æÿ²{ ò°3^áàŸø./}’Kkh®íáÝq ÈÄܰî y?‡´)¼G®Zi–x2Lø;Ž:ž~•»ã?ìH.ΗáëfŽÒɼ¹.ˆóYCnnÀÅ2Ãâ>¹¥]\}žmö®Íå[Íó,@žƒÒµ_â_Œ.—|–Ñ,kÉn_zå|9dÏq=ÙD‘m€ÊÛŽ8¥×õ6‰J¶¹ó $Ï ö(»Ø¿d”yŸRÄÚÞ¿ã+Ø­n5)çžV*IÇåRhÚ=ÛÞ5Õê–ÜìòäLDZªžðûkzº4¥ã³·ýä²!ÁèõÍzmεmt-rÎK…QåÇyn6Ü(ì³ÑrR+[N÷*"+t§Ö±™éUãŠêô3îª2ÄžM{p'ŠÞʰ‰~ð³¤ÖÞƒ§K0h#ÈFPÓ 8P¿Þ'° c|¬ÞøGĶšÌ ùq>Ù”gï úÉg·Ô,¡Ô-=½Ê #eô5ó–¡áôÓt¯>b‚á$ •qìk¶ø+ãq7„/¦Ëbì{wZ¶ Ñé®0y¨[Þ­Ì…—ƒUXzÔ‡ŽÝ)Œ?/Zyæ˜G=¨„ûSÖžI¦ëHC ïŠaö4òy¦Ô€a¦Òœxi§j@0úÔmßµHi‡¥!‘µFÝ}jCÔÔmïŽ)ô犪F뚌õã½"3ÓךNÄŠ^ŸZOóÍY˜cÞŒ~T¾ôwâÄOþ½8QŠpúÐ1@>Õ"­0àþ5*ç¯Z=*”þ½1}*ESàqO :Eëþ4ð=(_ΞBŠ•G=?: ž• ^x¥ œU»{f”Ðeˆ·N*ÂZ÷9ú ä¼añG@ð…ÇØ#I5=W§Ù­Ï O@DZöëZ>_ëñÇ®ôkWù£°µ”ŽÛÜôú}ÅO0Xè<µVÛÕ¿º:ÔÑZÈã;vÿ½Ö®B±D<¸•F:ãúš–Ž`±M­ö©-"ªúšò¿‹­à»›+dÖuˆï£R-ÞÔýáÓ±ñÄoqi.‰¦¤²Hßëe¢ãøkƒÓíV­i{f/-˜lu—ûzÖr¨Öǯ‡ÊeV—<ŽÖÛÂè…×Ä3¬¤€¤ÄËï]ÿ+ë „žÚQ”t9²¼Uâдçx|äpHÈAêká炇€¼?-§Ûf¹šá¼ÇV?$gv÷®wâæ¬¶•·;ü‡œU£†Z;K©ø—N¿×Æ¥â¯õ‹0Ìg” .:QÆÚêu½+IŸÂ£ÄÚÔ1ZÙÆ1a¤[© # Kc'±9÷¯>ÓŸ<öÚ¿‰µ$6Pó ŒG|ÓcøH =É­{û½{âæ¾‚Þ´Òí~HÔq ïêØ¤_ µçû¤‡[m§æYÈàûú×S׬”AÊ'AÐýh!©Å r‰ lª[ÌAµ³ž™Ïx¡&Óa·Ôm#¿ú¿Ë"Ü0­;‹Eû-©?0''žõÅø‚2ñ|Ó•$úy fÞ©øQ´™-’êòÂPÅ–9€usŽ›‡AUä¿vEómÄ‘Œí–Ý·~8®bÑlmu£otD–r~íÜu@ˆ{ƒV “Ãó^X͹.¾GCÃÇèG?¹Ÿ-„ÒHí$™$Ÿ›ò5ŽêÚCª8ù²88¦Ow5ÄÞl–Æ3Šìþx^oxºÚFÓ¡mÞ°t_ÄñLG¶|)¿·‹á~ Ò[Ió81d3Њ‹Ä¼¯Nf›M¸Óî$b<ÛO»ŸR:UOàç†N—s{¤Ëœƒ¥—ò5Ï\|.ñ}›Ò¼h$PNÄH?Ÿ5JÄœ?~Mà*=GLÔç¼·¸&)WËÚÊ¿íc±¯1‘“þ5ïÖ>ø§6£4M¯Øf|ÒêÙìÚ§ãO†^<ÕlD× ¡]4#q60eo¯Ê3Rô(ó_ ÚÚê0Ééèï&Rûp;äÖŽ¿rÑxU¢³˜p’ʇæã >Æ´¼ ð«Qñ…µåÔš¢i‘Cº2„å‡÷—#Þ¸‹+•Óïî,îq%³±ŠP\)â^ë—WzŽ’íþfÎÉÏ]ÕFÊöçN½†òÒVŠâ©åH­vÂÚÕ-¦´?$Šw)ê¤V]µ¼·wQ[B»¥•Â"Žäœ ¯ü;¬Iâ/éZ¼ê{¨JBÂjËõÏJf‘¦ Ún’>õ¥º£½ŒšsžsŽ*ã±,aéךa>ÔóúTgÐbÇŽi§­8ýqL'ŠiÏøÓ <ý{Tdôâ ?äSO§ÿ^œz`þ”ÃÖ <´ÃÖž{ôô¦Ÿ­!‘œžj3ÂÔëÒ£=ûÒ3þsL#éOnÃ=i‡?×"š|})0i{úÑëLëKŒñGj^ÿã@ÀzÓ€¦Êž(áÇ~µ O˜*Eöâ’­H¿­F£¥L½1LcÔTƒÒ˜¢¤_§á@Qô©•xÅ1ù52.j„IEØw®Wâ§Ž—Á>û=“ƒ«Þ‚Ö1Ýÿ»C,:uŒ÷×,c.äú ù3WÖn¼sãÙ¯¦$ù’m…û¨8P*$Á#½ø'࣪êòx“SΎ݉ŒÉóo˜õnz⾄Ü]ЍúšÈðމƒá›;]¥P3ÿ¼zÖî*PØÕP«€+’ñ¿ŠSE´û·N¿(þèõ®¦æîÞÒ0÷3Ç ´3°šä|YðûNñ\ãPK¹­o‚Âw_B(w¶‡VÒU*ß çú ò[݉šGó\îxåé!Ç$Ô™´=`J¶’ý–õˆc úU+߇ž#¶fµ¶ú©ù’o3cFqé\íÿ†Õ¤\¨#š5ÚÈOîæõ"ºïé {pu‹˜^(A"œçæþõsÓµO]HšœN4ûWÃù¨CH}{[ÛòEþ ¨Å·vyùž5S±‡Ä÷~]¾e;Ë¥µ·yä8Tå‹ ùkâÏŒÛÄš÷Ù ›[bG†5éÿb†]2{ÝÄok*)2Z™s£ÛÐ×ÍlÅØ³“É5±óˆ,@OïÞŠ= ÂPÂcU„Êçyˆï^¢Û½^Ú-¹Á?A^Ó¯Þ=‡„/¦Bù;g¹â¤¤xÕ³ZÝë~n î-V–mŸy—9ÂûžƒëF¹«Ë­êMrñ¬Q*ˆà>ì1¯ÝAì?S“Þ¨%Bþ&·ü' 6¯zgX[gqïÚ˜ŽÛÁñ}‡AŽ)S“™8È㯯ô§ë,Ó[iФŒ©-ÐŽGQ†k"ËXûf¿z±çÈR!Ž@^Øÿ=+¢Ôb-`ÇÏ'Ë31ÙÆ>ö¥K{~mZÊÛ$d î}>µÂø†ýÞá b¡ùv_J½{©ýŠÂHÑ>Ù"~ÇÛÚ¹I$i[szRI'&¶%›û_HBØûm’m$žd‹·â¿ËéXÕ$Ém2ËéȠCUYØ*‚Xœ:“_Zü'ð`ðg„®Sð\dr£øSð©5忼§xƒÄSë×ÚÀ«EjNOš{‘ýÑÛð¯¢îãži¡3>á·1Áæ Ãv<÷ÍM!±ž}j=ÅT·÷FzV„ö $—·RGœ– 0}+Y[Èqpÿ,H]‰èªZ,l`óX‹r:f¹/‹Þ"}ÂË£Ùu]¼ˆÔux'úR“)I}mâ;» ÆúEÌ–Úd—‰#tgø±éȯ:¸Ê“†Üæ +ì ørÏDðEž=º•D õæŽÞÔ{âÎy¤1Ýù§-3'S€n9ý("Ô«õ¨—?L¼ö d‹S(æ¢^jQLd‹ô©W¯øÔkÏãëR/\PÈ3WíaÞÜÕÆH¬ßx‹þO__Æ@¹uò`ÿy¸Í6#Ͼ.xÎçW¶ºðþƒ 6°·r©ûäuQì+Ë>[KuñH…bóWÎ×8àw«þYnmï ŒÌ¬ÛßqûÄõ®ßá…ã‹âšê £ÈKg‘WÑÏ {BŠ(ÏÖŒÐñÁš¿‹¢Øßà ù&)3ó1ïÅqxwâG…ÛÌ´ó¦ŒrR„Šà&½Ó8â$à¤ÑcºŽ>¥8*vN=š<‚ßâ_‰ìrºž’iÃ3FbçÓ= ­{Oø§S{VÓü6%ŽFÚîìP벇[¹ŠâéÙÀÁí¢=¿ç£þߟ¥hÜK½³Êò‘Y V*¦*„–”’;ZUî±#‘«iö¶hO\ÛŽ{qSê— ª#†XƒK$£†…s—b‚!{¦lºÓà™#¹¼í@òñóuÅqþ<ñÅœž)½ðíÇš¿g]&‡ FOã‚*>RæwHá~.^fHàŸI{IÙ²&‰ŠAߥyEnj÷:ž¯~mšy¯¼¬˜ÂǸýk”«`AÔ€é<-ì—2œ*  ž™5í*ð¥¤ß¯5­KÌkÍlöˆò@PGrkʼ%m¶Þ@Û3å‹tëü«Ô>2x×L³ð­§…m^«ƒå4ê„ìU\§êE qô<[ÒmtûL·…›X¸Œ\]³º_ý\`vÂò}ÚºÝVøG<š}M(>{½Ÿâü=+‘ðÉŸ\ñ¤W32Ræb ôè?•vzüÁ—P”¢°D* —8ëúšW…Ûn³qÈ8\ã>õè“*Éksí cñüëÊtK¡m«[HÃ#;qŸZõ¼ŠE%p°’ÄôçO · \Y‰v ¨ÛsÈã½rž"ÕV&h—sIÖ7-’žÇõ®‚kÀšR2ÀÄAã^is9¸¸yOñqéEÁ‹qu5Ó‡™Ëô¨(¢‚¬éö3êZ„VÈ^iÜ"êjµv ®í¬¾#hò] hÚmœž„ô4ô¯€¤ðVƒcyáˆÔë6Qµ!?-òžY[ßÐ×O ø—NñU¸±r³Fqqm'ÂÝÃ-l]±\Œàú×®x.ÛPÔµôÛÙ´az][ ?ß RB:§-’GÕVû1Ù² Ý,¤FŠIÍrëÿ 1cD™,±á±ôªW–~+œ‡¸ñ¼0KÈ"ÒÔqôª¸¬uš÷ˆôèfçT¸UeOÝÀ§ç•½þµäž·Ô>'|F›ÅÚ´etë˜(#î õÇSWaø4uÍP^k:õõìYÉyxfœô¯X±±°Ðt¨´Í2Ù`µˆ`*ŽIõ>¦•ÆY¸œ³g½S7 œÖ›$™éP–ª$”ÌNyÁúô¨™óM'=ø¦@Çša<ÐHçõ¤'§¥ zJi§þM0þTiõ¦šqõ¦×ÚÆŸëLÏ?ÖžO^)‡Þ n”Óéž´óL=}3@ Ãð¨Ï¦:@iûÑÓð£ÚŠd8¥ã4ýiÙé@ÃŒô§®{S{~´áÇZH:T«ïÒ£SŸz•HÏZHµ2÷¨GAS/ó¦2EéÅJ¿äTKR/Z`Y„e…y÷Ç[ŒhzFž3ÎIô¯C¶?¼•å_÷oÃØ?"«‘éšLGáØE¥¬ÂUÃ3‘’x®†ÏU¸ÒçIì§h§EÿX¼ãë\íÆ¡¢¢:¨ÜFM_Þ Å*Á‡8þíIF®‰â)ü_ñÃKñN¢Í`‘3*™ŒJîÆ=+Ú¯-ô ½«,ö§báÞ•Æ:t5òOŠÑ£Ô ôâ¥CšOêZ]»9$J¯µéIí\L&±ñMü,[”[ˆÞ?ûäœÕY‹÷F²j–ú…˜—-ÅåG÷K¯onýëç´-fHbg`$/ïjxu/h3·Ôo"eÇI þtôóx³Ä) |+¾]¹ÄwŠWõŠäõK»Rs'Œõ¸4»$]6ÚBwàç.z°öå–ã#¸8úV‡‹þ!/‰ä±HìZ+hd2M»Í' Ã{ˆt}Z-GOIŸp#“øðÑŸ^ãò ÛÞê—þ)[ÏÇ,îwF¶ü0ãœzÖN¥öé5[ƒ¨‡[ÆùÞbí!³ÎE_×,äðö¾ñÛLѲ…–&FÁ@À0õÁgVº“ÄšBêÒüú…®"¼nò)ûŽ}ûvÆ>¸šm¼Vúsx× .8Ï|V ÓÉq3K+—v9,OZŽŠC¹×xþÒ¹¸ —>¦¶‹¤ÝÊ#Û9÷Vãô5ˆ¬Qƒ)Á ÓZ «¢æ­§K¤ê·63 <.Wê;Ê©Wmã›e½Ó´Çÿ/pæÇgQÔý¥q4IYŠ.êçwgqåøzÒ(ÙCÎÓÐû×7ª]<÷ ¯ÊƒîŠÑ±FºÑ!S€UíWSû>Œì¬7O+m\út5£#¥Éc×rã·­TÖe‘ï1ùU@Qè)‹dgI$õ4”QA!EPW4›C«ÙÚ³L©ùš§[¾ Bþ5Ñ” “w}‹:-•½¤|,1*=†+5›-Ç5¥©7ï+)ÏÌqZ-‰c‰?ZfOO—ŒzSoJ ãdóÓëA¦ç=èsÿפÏ£<ô¤Ïè¤ÏãA9ô¤ÏJ;zÓJZCùÒ?*iúÓ¸ÇÒšGãŸZO^)Œ)Ùâð()§µ9½1M?Z@4÷æ˜}:v§žOJŒžò¤šk`­)ô¤<Ð2&¾ýj>yãô©›žÕàô ÛÒ˜qž;ÓÈãü)‡ëŸéHeìñéÞ—4ÌúÒçô¦@üÒç×֙ǭ8ôñÏøÓ‡åŠŒ{ö©ç@Éó© ¨‡½H§Ú“/ùÍLµž*d<ñLd óÖž8ö5çSÅ0%^¿Z¸Š'´žÈ’2Ÿ˜ªKþqV­œ«Có!°)uya ØmîNÞ?ã\ö³¦˜5!åQQë^‰ñbÆ øžòþÖ0#½ üôÏzæ`¼MNÅ/¼° |¥;P÷)j‚üçE®$ĸm½Z¸&ÆN:v®Ä7WÛF¬†(Kd'jæè¥§Oq¤]ÁtðºÆã?:}½jŒµÅÄp ËHÁGÔšú?ÆÚ«ø'ÃÞŠÒ)5•#·‘—˜»³gó¡‰+s™îA·mÑÈ7.;W[ðââÇÚt“œÈàǸ÷ÍtZ§Á+í#O Úƒýì­žû=~•å7×¶:¬r$r[ÝYÉ»dƒ ¬¥"·GÚsÞ[Z¨k‹˜aSÐÈásùÖŒuOµk«[˜¤U†øØ0•y‰5Í3âWƒìæƒR‚ÛY´P$¶ÊÇ\à^¾ðÞ¨iñ ê•xÒMëTIÏXHn´ë½8üÌß¾ˆŸï¿˜¬Š³I©2äm=@«šî”ÚmÔR&ZÖê1,ㆨCÅ :ϧü$um'îl?Ò ñßçÝ+¢ð^¸4[\ËÿÒþêpzn Câí h¾$º¶N`fó!nÅ‘ŠoTJVe)÷Å £r¦àGLç9­Ë}v)¬åûDfa†%~ð®vâìÏ 1mÚ±¦>§Ö«R.æÅ¾°–ŒÏ{ä#·ðÖl¦Ispüîn¾õ 8»2…'ÐP+¢Š(¢Š(¯Vøáí6­0ÿFÓWÕÏA^S^Ïû>x‡ìšåî„ê6Þ§šèËÛò x¼mÎNy=«-þ÷¦;Õ«‚Džj“7ZÑ<öïMÈçòÒ¼´€=G<Ò¦híÚŒò}è3ך?•!â—94žÝiÖ”ŠCÛ4”úÒõ?ãMô =ûPzt¤4¹çÞŸ­&zçŠsza þY¦8ôÇßí1ºúSO×ÿ|gñ¦Š@0çŽÔÆã<÷éRJŒðxë@ÄÏù4Æäž¼R“ŒÒ3ëH[×4ÆÇj•Ç qÉé@ËyíšPGn)™öæŸzdÏ=(4Àxöô§é@ïON*!þEH J¹ÍH¾Õš•{P2UíR­D¦¥ZH=8tÿ `==iã¿j`H§šš6ÅW*E08ß‹ºbM¢Yjf â)<¹?Ý5ã¾T–"[$ÜæFzú+ÅÖ«}àmN&Ùq‘žEx‘[‹8™zÉÏ_Â¥¯Š.Viá ÇîäOjçë[ÄþÓrG¡õ¬šBgCà[?·øÛI·Û¸…$}9¯§$³óüc6§0ÏÙc[îÿx×…üÒþßãÔ¸eÊZDÒŸ¯júAûÆaÎI&…¸™bÆF$Þ±|Mà½ÆQ“yŒ-Ô\8úú­[ÝÎJ–'95VÏñOÃ}WÁÅ®%µ–Ü+œö‡já§»¶Q…sì+ìÛVób1J¡ãa†FWÌß´]Cñ‚A¢Ä y#ó.aSò#Æ=*yJæ<îkƒ)ÀWÒº]T°Ô´“áÍv_&ßq{+Â3öi\ÿ°{þuÉÑ@ [H»Ñ¯Öí#”‘NREìÊ{еª.©¡ÚGpÓ,¿v¬zÉéùUÔ®–Ø[4že¸9Ió(úz~Þø+á&©ã1µH. Óí7ì_03oÇR(Î*Ïöuà´{£m"À˜Ùp9é^ƒ¯øÛÀÞ/³Óä¾MAÍ¿Ÿ.cÚäàøf«øÛWxrÚÙXfY·0ÓÚØóÊ(«ºf‘¨k7BÛN²šêbq¶$'óô E:+èO‡Ÿ`ÒÔj¾+Š9®q˜¬O*žíê}«Ä?ü3â•{‹X¿³.@{uI÷Zi\W>\¤­Ïø_Qð–·.™¨Ç‡^QÇÝ‘{Xt5a…t>Õ›Dñ¦•|hIÔ7Ððkž§#˜Ý]N A¤Ú×û|Ýë÷\n_¡¬çàâ&ôj~Ò/”îóm“'Ü GéÖ©=Çǽ#}h^9¤Ï'×Þ˜hÍ&zQ@ zu¦gáøSO§ëHƒÚš}©4§ó šLàÓ9Κ{PqéIÓ4¾Ôžôœg¯ÖšqNϘJLóÓšiãó§v¦·­ y¦7ìóÅ!Á ÉÒ;gÞœxÏŸm¢i–zUš…·¶Aã¿©¯=øIàOøD´S©ßÆ«zƒ G1Géõ5èÐ>éûŠk¸®|ññVån¾%ëE‰&£‰@úWŸxŽVûD§†1»?1äÿJî¼|B|UÖ·œ®õvÏ Åy…ÝÁº¼švë#–úR)‘*–`£©8ö?‚ü=gá/XZÙÀ‰4°¬·ã摈Ï&¾EÑ­æ·clL“¢ãñö¥Ïî’8@â8Õ=…4&W–rIÉÎ:Öm•ÌöºÔ¶Çæ¶dÞ­è}*Ë6[’GsQíËþ•d•¼Yá]#ÇOØu%òæP~Ït£æˆÿ‡µ|»âïê¾ ÕZËR‡å91L¿rUõúWÖ û}*¶µ£é¾(ÒIÖ!ÀÃ) ûÑ7ªš,3ãj+°ñï€5êb9³5„ÄýšèzCí\¬4ìÀɬÆÏ§~Þÿh|,³ËnkiZ#ê=+¤—¿ø¯øâÔ²Ôn<3zûmï¾hôYoƽžæ3²°ÁSŠqì …œÓ›ð úTjHnzÓØñÖ¨BùÒzRçߊNþ”€\ã47cIœz;u ôÏÖœLûQšjCŽ™ þ´~€o¡Å'¨þt´‡¯ZOÂóÞŒàg4ŸL m#téKëÎ)¹=¨Óè 7>´æþTÂsßó¤xüª3ž´ãœ~”Ö9ï@ ½ªC×µD}Ž2(§¯^µúsO=Ö˜M ¥(íQƒžO4 þ5D“Î3O§ò¨ƒqÆiàú Ô •O¿jE?þº…zžjE<x¤¦SÏž}êT>ÜSuÂLkÃ¥kü!±[ï‰:bºåc&OÈTcéû¡äÁ ÿ«WòšüŸZ¿|å§sÏST'8üªÉ$‚2X×Þ¹ˆþ;‡Â3ÚÚ8:•^û­u–ϱÆxâ_¼#r5Óç’“ƒånè}¨ \òI^[»–‘ËI4­’z’MKö&K9.%;@mŠ=OzÜÓ´;»h^fB·/ò§C´§ëYZ¼ª'[X›1@6ýOsRU¬gª3¸DRÌÇÉ5ôÂß… ¥,> ñ ÝÚчÜôf¾Õ‘ðKÀQ\–ñ^­x!m¶q8᜾+Úg¤b[ïSH’I®<Æç5%»fUϨª9úÕ›wï·j >uøœqñKÄ @â0FÜæ5êŸà6ßõ#‹‹Ep}~LJòºwà íˆÚ,$d Ñì9¯«î¤Ý+ŸzùÃàE§Ÿñ g#"ÞÝßôÅ}+g¯JhL„·Íõ¤=¸4‡ ÁÍP œ7ó͈¤èFzÒw÷¦jº5Š´+Q@ÐÌ¿#㘟³zùUô·Ñu=RÆèåí]¢b:w¯®,8˜Úù#ź‹Ýëú¤§­ÅÓ¹úg,æ½Ä–—QÜ@å%‰Ã£Ä+ë] X"ðŽ™¬¶ ³DR?¾:×È•õÃË /áÖ™op É(3=Bž•+qô7ׯ¡§7OzA×§zszsT!¦ŽùÇáG¿Z%ôQø~†Þ’–“¿€©2;ôÍ)ý3FE'jNþôvRüÐ~žô‡9Î8ô¥ëM?Jiý) ý}éÇÛµ0â =ûSO^´ãþE4ö iõ§žœÓ¯·ZaëÏzªCøñÞ£'¿­ ÝH¨ÉéOn˜ô¦dó×Ö€ŸçOSÎx¨ƒsçN#Þ¨DÀô=}iãÓ5>ãšp`p;úR`y©Û­B  Í° )cèOA@ÁÿõTÊ{gÿ­U£!ùFâ¦Uoj`ZB}*PyÈïëU×pê1R«sÏ¥$éš\Ór1‘ÒØöÅI“øQž•py¥8z"¹?Œî«ðÅÕºµÌ` êQ±ŽxþUÄül¼6Þ´€&ñ<á[ÛÄÏc"xoÊtÂ8È"ºŸ€Ñ«|@Þz¥»‘\P¿3+Ã/ú½˜^:VÿÂe4oˆv&b+’`f=‰éúÔ}-t~v>ý*¡ëŒõíW.Ь¬¸ÁÏJ¤G÷~¸« ’>¸âªxŸD‡Ä¾žÆL y¸ê¬=*ʵf) Ÿ§^hçmPÉáý9Ú鉺9Dãçʦ{•Ry‘ÀÏÔ×»xïÂÚe·ˆµï-%º‚q…WúW”ø®í[P…aÑ”°•pOøÔا©õ^—e‘ám. VË€=qɦ1ç5ƒ©&¯àý"ýƒì@ÅLÇßùÕ"@sSDpGJ¯íšš.£ùÓÆþ<Ú¼O£ßŽ—†3õÿ¯^&F ôOÇËmþÑo;ÅtÉøÿÖ¯›ï­@ÏlýžlÁ¼×oˆÿWn±ƒõ5ìÒ= yoìûßë“cïÌ‹úW¨ÈrOzh ý(“ëF1ïIúÕSÚŽç'ó£ëÓÚ“¾(å–DÊ1Áâ¾JñÜQÛøãW‚ qÜ2€+ëKiÙd¹•€Ži ñ¾µzurþôœù÷&}‹I°êo|:ð«ø·ÅÖ¶eIµˆù· Ø ÿú~ä¦BD»"@ttÅyïÀýiÞ¼Õݸ¾“b×`®ùÈÉ8¤†ÆsÒ”õýi«êiIÁ¦!;w þ”fþT†¤þ†—Žÿ&zPùbƒÎNhþt{ôÍ'^™æ“òúRúð4‡éÖ€ŽÔÞƒ¦}i߇Jo>”‡ÂýzúQži¤þ”€3Å4úqJM4‘@}qL=?œO_ÖšO~(¤óïLnA§N)‡ÚÆ7'Ö£'’;ûÔ„ñž*6ÈÆhÂ{þ•?Ž)äöïïQ±?Ÿç@xÏzxÎ}{Õa ã?‰ëSÀ9È<ç¨5B&R§ç>¿áQ¯Íׯ½H9¤·`rFiÁrzsH1ÆqÓ¡§¯yï@EÛÈÀÍZ†_ï~uYEN¾â˜Ë¨Ct§J¯+ߊ²­»ÛŠNWšF<:ž)ßQÓ½rO­$kµp;ÓÁéIÐRŽ;sž´ðy®câ~ŠúçdhUž[7…^¤w®˜qÆ*ÄKñ=¼ 4r Œ§¸4ùžM2ÔÀ­·&}…sƹj"l8¸]¤zîé~#ÒÛJÔ5 %r:©éŠóßAöŸiЃ×h3ÿ©E³ëÛíÛ£-ËlRÄz⨖短_Ô óŠáP>¼V{(|qùU¢‘ïô§ƒÓ=ê!מ8  ÿØ›ý_,fhŽøÉ=÷®CZ±Óüaá(ÍéŒ_¤eF °÷¯DØ$V¹ 0sÞ¼òØGgª_XM J7ïHÇRh¤^ø?vÍày´×lËcrÈ{ñší##ç~q¤|CÕtÝ‚8o£óQ@ïã^‹0Á8nôИÁÍMÎ9Í@½qVàBÌÉ=©ˆó—‘Åá-"ÄfžèÈ…Gÿ^¾uq‡aï^Ùñòîñ…m½XÁ TvóÞ¼Rf 3²ýÒÄŠŸC|ð^¦Þ·CùW¤9äñ^_ðí&ðÞ±`§Ç2Ê}ÔŒW§Ižxâš0žzRŒóMݨíFsT!Àú´™Í4Ÿ¯=ªÍ¥±º—nð«ÜÒÄüNñ ºo‚îll²÷7£Ë!…;×ÍQÀòÜ¥ºÜ äâ¾™ø‡§ZÏ¥Þý™ÂI D™Hëí^#ðÏHþÛñþ› .èã:O¢óSqŸKèštz7…ôÝ1Fß&ÏûÄdÒÊ0ßZšåËHÍŽüsU·äàóM0 qŠ­/9Í0’€çf“œô¤Þ2AȤó `Iç4gÞ£bÌ:àSîúgóýÜúQ»’9¤YAÎiùsÓÚ€‘ž¦’O4ò¸sLÈÎ:ši'ž) އ"œH'µ4Ðdši=óHG§”Þh­&Ö)>¾Ô¹{SXŒñL>Þ´&ß~œSH=ˆ4‚LuïÞ”°až¿Ö ;‡nµÜ¥JzàTlAô÷¦E¹èM0•¸ª_³ãM:å™Ïï 2ûžœÓI#’j×,±Ÿ3õ¥þײbGš:ô ,hÈÅ5ºz§ý£lzL´ñ}8ó€%9=zÔ,01œzæƒu‘:ˆÝDIùÇâh[8ä`Q~<ôäÓšâ3ÑÇZa‘3Ë­9ÁâKoî3éÒ¤_Z÷HïŠà„ò úœSÅÃp e϶iŽÇ|¾"´<óïR¯ˆl³Ö¼ûí%py>¸"]ü d~#¥dzxŽÓ<M=|On1ò=kÏÓªœ’{qV劌¿#Œ÷¤;#¼*ƒ8 O+^Àg¥p‚R£;‡ÕE=Yº O¡Ð;ÕñIÇ )ãIJB¨>µÇBé¸+žÅjhØò¹ç¡¥qò£©>"¸c…ç½D|ArÀü㎵Ïù®Ðg¢¥&å\&½Ž(‘²u›¬Ò¿¥Fu‹¢ClVA’M¸@¡³ŒcRBVBVN8=:ÅöÕ'g ²ßÊœ/.¤Á ãø¹ªC¼Op£¯âj3‘~IÊï8Ò&y¦‰ÁÉ]¤C^!âH€Ôd`àüÄÆ6×´iм@‰çÉ_â÷¯'ñh uq Ç›»ÌïMØô êo¨xWN¦,è†)=A^+FI&\Wžùí\GÃ;¡q¥ßéÒ|§Y”wÁàâ»imW,ñÊÈàsÓÜÐÁl0Ï*žWƒ×yòn8õÁØ“b‘#ïuaß5$Œ€ç×= "‘ÉÎâËݰj¨Òañ•gxëåÉ 9èEHÒ¡bR#¿8$ðëV#—b«Æ’0’ G#ñ§Ã¶º6·§ÜÙOl±ùcª”àþu朓Šìü¨Üê-F¸Í{UUºué^™‡t6±¶¹]./%@Ƚ ÙÖ§ø[· ¼ÃEd‡2KŽ£Ð{ׯÚéÖú}¢ZZÛ„„¸§’GsëOØ-”Gj±(ÀDÅ4\HªvrÀŒñœPÝÆ’D­p¶ÑØî:cÒ¤f#rclrOJ­ö«„`J‚p9ÏÒÝßiaÈ#úzÒ%Åý¬DGÚÐiÕÁO#åŠv9 Ì|VÃ^-Õc¿Ð.ƒêòÌ |²_¨®"ïÂOq¨‘jê°žç ®»@ðÔ:*«¨/3rd8ÀÇò¡+l'«ÕQ䌼9cׯÖ£Ý ±Ž3™œm~*e%[).3ØüƪÍ]<ûa$Šq¿¦3LKäA‘”(6ÞBÕdšÒ@ω*ÕU*= ªçÿCº"ʉ[œm~´ »e‰‹ «g¨¨¯1“£ y©ƒÜŒ•ï‘òƒLID’pƒ®F·èÅ·¡Ê·nzšeÄ— 7FKàŽhg€ásŸ|ƒC™#IîhlœÛ½˜dûÔžLEöý¡·ž¸<éB\  û±·£• N|£°q‘&˜ "º>Ãð:2œ~t+ËåîËöjp¼e ¯³°\’=ª³_#àº’íœ qš±ç˹”¯ïáMgœ*í|ý8¤Â(oŸ¹Ç¥1ƒÚÎÖæ€î°Yý}E9f’Èã= 1Ûîï ŸLS$ 2€ê}   e‘ÕwÆCSßéQ›žˆÑÄü ”`±áF>ësù F˜ª‚3yùzPÛK¨')ê éõ¨wK» 7㺶)LÒµ”6ySœPÒ¬®èO$z¥4Mr™À§h:r[ò?­BG#ŠƒÈnÔŽS,㯨8Õ Æxé‚y¥:ƒàí‘îMQ¸^•ŒDËvýá.yä` ÑþÑž!óI׿j?¶T#d÷ÍPg`T:‰2zúT/šîb˜(êSÿÙROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/gradio_app.py000066400000000000000000000047721510465702400302600ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from txt2img import StableDiffusionMGX, get_args import gradio as gr def main(): args = get_args() # Note: This will load the models, which can take several minutes sd = StableDiffusionMGX(args.onnx_model_path, args.compiled_model_path, args.fp16, args.batch, args.force_compile, args.exhaustive_tune) sd.warmup(5) def gr_wrapper(prompt, negative_prompt, steps, seed, scale): result = sd.run(str(prompt), str(negative_prompt), int(steps), int(seed), float(scale)) return StableDiffusionMGX.convert_to_rgb_image(result) demo = gr.Interface( gr_wrapper, [ gr.Textbox(value=args.prompt, label="Prompt"), gr.Textbox(value=args.negative_prompt, label="Negative prompt (Optional)"), gr.Slider( 1, 100, step=1, value=args.steps, label="Number of steps"), gr.Textbox(value=args.seed, label="Random seed"), gr.Slider( 1, 20, step=0.1, value=args.scale, label="Guidance scale"), ], gr.Gallery(), ) demo.launch() if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/gradio_requirements.txt000066400000000000000000000024721510465702400324050ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### gradioROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/requirements.txt000066400000000000000000000026471510465702400310640ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### --extra-index-url https://test.pypi.org/simple accelerate diffusers transformers optimum[onnxruntime] hip-python ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/sd21.ipynb000066400000000000000000000465551510465702400274220ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Stable Diffusion 2.1\n", "\n", "The following example will show how to run `Stable Diffusion 2.1` with `MIGraphX`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Install the required dependencies." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Install dependencies\n", "# We need this version to run torch with gpu tensors\n", "!pip install torch -f https://download.pytorch.org/whl/rocm6.2.4/\n", "!pip install optimum[onnxruntime] transformers diffusers accelerate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use optimum to generate the onnx files." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# export models\n", "!optimum-cli export onnx --model stabilityai/stable-diffusion-2-1 models/sd21-onnx --task stable-diffusion" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use torch tensors for all calculation. Everything will be allocated on the GPU to avoid Host-Device copies.\n", "\n", "We installed the rocm version of pytorch, let's confirm that we can access the GPU." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import torch\n", "import torch.version\n", "\n", "print(f\"{torch.cuda.is_available() = }\")\n", "print(f\"{torch.cuda.get_device_name(0) = }\")\n", "print(f\"{torch.version.cuda = }\")\n", "print(f\"{torch.version.hip = }\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If it is not working properly, try restaring the kernel." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now it is time to load these models with python.\n", "\n", "First, we make sure that MIGraphX module is found in the python path." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys\n", "mgx_lib_path = \"/opt/rocm/lib/\"\n", "# or if you locally built MIGraphX\n", "# mgx_lib_path = \"/code/AMDMIGraphX/build/lib/\"\n", "if mgx_lib_path not in sys.path:\n", " sys.path.append(mgx_lib_path)\n", "import migraphx as mgx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, a helper method to load and cache the models.\n", "\n", "This will use the `models/sd21-onnx` path. If you changed it, make sure to update here as well." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "# helper for model loading\n", "def load_mgx_model(name, shapes):\n", " file = f\"models/sd21-onnx/{name}/model\"\n", " print(f\"Loading {name} model from {file}\")\n", " if os.path.isfile(f\"{file}.mxr\"):\n", " print(f\"Found mxr, loading it...\")\n", " model = mgx.load(f\"{file}.mxr\", format=\"msgpack\")\n", " elif os.path.isfile(f\"{file}.onnx\"):\n", " print(f\"Parsing from onnx file...\")\n", " model = mgx.parse_onnx(f\"{file}.onnx\", map_input_dims=shapes)\n", " model.compile(mgx.get_target(\"gpu\"), offload_copy=False)\n", " print(f\"Saving {name} model to mxr file...\")\n", " mgx.save(model, f\"{file}.mxr\", format=\"msgpack\")\n", " else:\n", " print(f\"No {name} model found. Please verify the path is correct and re-try, or re-download model.\")\n", " sys.exit(1)\n", " return model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is currently a compilation issue with MIOpen for SD2.1. As such, we can set an environment variable to use MLIR instead." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%env MIGRAPHX_MLIR_USE_SPECIFIC_OPS=\"attention,dot,fused,convolution\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With that, we can load the models. This could take several minutes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "text_encoder = load_mgx_model(\"text_encoder\", {\"input_ids\": [2, 77]})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "unet = load_mgx_model(\n", " \"unet\", {\n", " \"sample\": [2, 4, 64, 64],\n", " \"encoder_hidden_states\": [2, 77, 1024],\n", " \"timestep\": [],\n", " })" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "vae = load_mgx_model(\"vae_decoder\", {\"latent_sample\": [1, 4, 64, 64]})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To pass a tensor to MIGraphX, first we need to convert it an argument.\n", "\n", "We avoid the copy via allocating the tensor on the gpu, so we only need to pass the address of the tensor." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we need to have a mapping between torch and migraphx data types." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mgx_to_torch_dtype_dict = {\n", " \"bool_type\": torch.bool,\n", " \"uint8_type\": torch.uint8,\n", " \"int8_type\": torch.int8,\n", " \"int16_type\": torch.int16,\n", " \"int32_type\": torch.int32,\n", " \"int64_type\": torch.int64,\n", " \"float_type\": torch.float32,\n", " \"double_type\": torch.float64,\n", " \"half_type\": torch.float16,\n", "}\n", "\n", "torch_to_mgx_dtype_dict = {\n", " value: key\n", " for (key, value) in mgx_to_torch_dtype_dict.items()\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we need a way to allocate the torch buffers for the models." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def allocate_torch_tensors(model):\n", " input_shapes = model.get_parameter_shapes()\n", " data_mapping = {\n", " name: torch.zeros(shape.lens()).to(\n", " mgx_to_torch_dtype_dict[shape.type_string()]).to(device=\"cuda\") if not shape.scalar() else torch.tensor(0).to(mgx_to_torch_dtype_dict[shape.type_string()]).to(device=\"cuda\")\n", " for name, shape in input_shapes.items()\n", " }\n", " return data_mapping" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we allocate tensors for the models." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "text_encoder_tensors = allocate_torch_tensors(text_encoder)\n", "unet_tensors = allocate_torch_tensors(unet)\n", "vae_tensors = allocate_torch_tensors(vae)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lastly, we need to tell MIGraphX how to access these tensors." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def tensor_to_arg(tensor):\n", " return mgx.argument_from_pointer(\n", " mgx.shape(\n", " **{\n", " \"type\": torch_to_mgx_dtype_dict[tensor.dtype],\n", " \"lens\": list(tensor.size()),\n", " \"strides\": list(tensor.stride())\n", " }), tensor.data_ptr())\n", "\n", "def tensors_to_args(tensors):\n", " return {name: tensor_to_arg(tensor) for name, tensor in tensors.items()}\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since the tensors won't change, we only need to do this once, and cache it." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "text_encoder_args = tensors_to_args(text_encoder_tensors)\n", "unet_args = tensors_to_args(unet_tensors)\n", "vae_args = tensors_to_args(vae_tensors)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The model outputs will be called `main:#output_*`. We create a helper to access them more easily." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_output_name(idx):\n", " return f\"main:#output_{idx}\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import the remaining packages." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from diffusers import EulerDiscreteScheduler\n", "from transformers import CLIPTokenizer\n", "from tqdm.auto import tqdm\n", "from PIL import Image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Time to load the scheduler and tokenizer from the original source." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_id = \"stabilityai/stable-diffusion-2-1\"\n", "scheduler = EulerDiscreteScheduler.from_pretrained(model_id,\n", " subfolder=\"scheduler\")\n", "tokenizer = CLIPTokenizer.from_pretrained(model_id, subfolder=\"tokenizer\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we will define all the steps one by one, to make the last step short and simple.\n", "\n", "The first step will be to tokenize the user prompt. It will make a `(1, 77)` shaped `input_ids`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def tokenize(*inputs):\n", " return tokenizer([*inputs],\n", " padding=\"max_length\",\n", " max_length=tokenizer.model_max_length,\n", " truncation=True,\n", " return_tensors=\"pt\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Optional\n", "test_tk = tokenize(\"test tokenizer to see the tokens\")\n", "test_tk.input_ids.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We run the tokenized prompts through the `Text Encoder` model. It expects the `(2, 77)` data as `int32`. It is `2` because we will also pass the negative prompt." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Optional\n", "text_encoder.get_parameter_shapes()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_embeddings(prompt_tokens):\n", " text_encoder_tensors[\"input_ids\"].copy_(prompt_tokens.input_ids.to(torch.int32))\n", " torch.cuda.synchronize()\n", "\n", " text_encoder.run(text_encoder_args)\n", " mgx.gpu_sync()\n", "\n", " return text_encoder_tensors[get_output_name(0)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Optional\n", "test_emb = get_embeddings(tokenize(\"test tokenizer to see the tokens\"))\n", "test_emb.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The other input of the model is latent representation (pure noise). It will be transformed into a 512x512 image later.\n", "The last input will be the timestep." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def generate_latents(seed):\n", " return torch.randn(\n", " (1, 4, 64, 64),\n", " generator=torch.manual_seed(seed),\n", " ).to(device=\"cuda\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Optional\n", "test_latents = generate_latents(42)\n", "test_latents.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we add two helpers to access and convert from torch to numpy with the proper datatype." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_scaled_sample(latents, t):\n", " return scheduler.scale_model_input(latents, t).to(torch.float32).to(device=\"cuda\")\n", "\n", "def get_timestep(t):\n", " return t.to(torch.int64).to(device=\"cuda\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The UNet model will be run in a loop. It will predict the noise residual." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Optional\n", "unet.get_parameter_shapes()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def denoise(sample, embeddings, timestep):\n", " unet_tensors[\"sample\"].copy_(sample)\n", " unet_tensors[\"encoder_hidden_states\"].copy_(embeddings)\n", " unet_tensors[\"timestep\"].copy_(timestep)\n", " torch.cuda.synchronize()\n", "\n", " unet.run(unet_args)\n", " mgx.gpu_sync()\n", "\n", " return torch.tensor_split(unet_tensors[get_output_name(0)], 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Helpers to do the classifier-free guidance and computing the previous noisy sample." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def perform_guidance(noise_pred_uncond, noise_pred_text, scale):\n", " return noise_pred_uncond + scale * (noise_pred_text - noise_pred_uncond)\n", "\n", "def compute_previous(noise_pred, t, latents):\n", " # compute the previous noisy sample x_t -> x_t-1\n", " return scheduler.step(noise_pred, t, latents).prev_sample\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scale and decode the image latents with VAE." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def scale_denoised(latents):\n", " return 1 / 0.18215 * latents\n", "\n", "\n", "def decode(latents):\n", " vae_tensors[\"latent_sample\"].copy_(latents)\n", " torch.cuda.synchronize()\n", "\n", " vae.run(vae_args)\n", " mgx.gpu_sync()\n", "\n", " return vae_tensors[get_output_name(0)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And lastly, we need to convert it to an image to display or save." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def convert_to_rgb_image(image):\n", " image = (image / 2 + 0.5).clamp(0, 1)\n", " image = image.detach().cpu().permute(0, 2, 3, 1).numpy()\n", " images = (image * 255).round().astype(\"uint8\")\n", " return Image.fromarray(images[0])\n", "\n", "def save_image(pil_image, filename=\"output.png\"):\n", " pil_image.save(filename, format=\"png\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Feel free to play around with these params." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "prompt = \"a photograph of an astronaut riding a horse\"\n", "negative_prompt = \"\"\n", "steps = 20\n", "seed = 13\n", "scale = 7.0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now, to put everything together and run the whole pipeline:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scheduler.set_timesteps(steps, device=\"cuda\")\n", "\n", "input_tokens = tokenize(prompt, negative_prompt)\n", "text_embeddings = get_embeddings(input_tokens)\n", "latents = generate_latents(seed) * scheduler.init_noise_sigma\n", "\n", "for t in tqdm(scheduler.timesteps):\n", " sample = get_scaled_sample(torch.cat([latents] * 2), t)\n", " timestep = get_timestep(t)\n", "\n", " noise_pred_text, noise_pred_uncond = denoise(sample, text_embeddings, timestep)\n", "\n", " noise_pred = perform_guidance(noise_pred_uncond, noise_pred_text, scale)\n", " latents = compute_previous(noise_pred, t, latents)\n", "\n", "latents = scale_denoised(latents)\n", "result = decode(latents)\n", "image = convert_to_rgb_image(result)\n", "\n", "# show the image\n", "image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you like the generated image, save it with the following:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "save_image(image, \"output.png\")" ] } ], "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.10.12" } }, "nbformat": 4, "nbformat_minor": 2 } ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/torch_requirements.txt000066400000000000000000000025661510465702400322630ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### --extra-index-url https://download.pytorch.org/whl/rocm6.3 torch ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_21/txt2img.py000066400000000000000000000426661510465702400275550ustar00rootroot00000000000000# The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. from argparse import ArgumentParser from diffusers import EulerDiscreteScheduler from transformers import CLIPTokenizer from PIL import Image import migraphx as mgx import os import sys import torch import time from functools import wraps from hip import hip from collections import namedtuple HipEventPair = namedtuple('HipEventPair', ['start', 'end']) # measurement helper def measure(fn): @wraps(fn) def measure_ms(*args, **kwargs): start_time = time.perf_counter_ns() result = fn(*args, **kwargs) end_time = time.perf_counter_ns() print( f"Elapsed time for {fn.__name__}: {(end_time - start_time) * 1e-6:.4f} ms\n" ) return result return measure_ms def get_args(): parser = ArgumentParser() # Model compile parser.add_argument( "--onnx-model-path", type=str, default="models/sd21-onnx/", help="Path to onnx model files.", ) parser.add_argument( "--compiled-model-path", type=str, default=None, help= "Path to compiled mxr model files. If not set, it will be saved next to the onnx model.", ) parser.add_argument( "--fp16", choices=["all", "vae", "clip", "unet"], nargs="+", help="Quantize models with fp16 precision.", ) parser.add_argument( "--force-compile", action="store_true", default=False, help="Ignore existing .mxr files and override them", ) parser.add_argument( "--exhaustive-tune", action="store_true", default=False, help="Perform exhaustive tuning when compiling onnx models", ) # Runtime parser.add_argument( "-s", "--seed", type=int, default=42, help="Random seed", ) parser.add_argument( "-t", "--steps", type=int, default=20, help="Number of steps", ) parser.add_argument("-b", "--batch", type=int, default=1, help="Batch count or number of images to produce") parser.add_argument( "-p", "--prompt", type=str, required=True, help="Prompt", ) parser.add_argument( "-n", "--negative-prompt", type=str, default="", help="Negative prompt", ) parser.add_argument( "--scale", type=float, default=7.0, help="Guidance scale", ) parser.add_argument( "-o", "--output", type=str, default=None, help="Output name", ) return parser.parse_args() mgx_to_torch_dtype_dict = { "bool_type": torch.bool, "uint8_type": torch.uint8, "int8_type": torch.int8, "int16_type": torch.int16, "int32_type": torch.int32, "int64_type": torch.int64, "float_type": torch.float32, "double_type": torch.float64, "half_type": torch.float16, } torch_to_mgx_dtype_dict = { value: key for (key, value) in mgx_to_torch_dtype_dict.items() } def tensor_to_arg(tensor): return mgx.argument_from_pointer( mgx.shape( **{ "type": torch_to_mgx_dtype_dict[tensor.dtype], "lens": list(tensor.size()), "strides": list(tensor.stride()) }), tensor.data_ptr()) def tensors_to_args(tensors): return {name: tensor_to_arg(tensor) for name, tensor in tensors.items()} def get_output_name(idx): return f"main:#output_{idx}" def copy_tensor_sync(tensor, data): tensor.copy_(data) torch.cuda.synchronize() def run_model_sync(model, args): model.run(args) mgx.gpu_sync() def allocate_torch_tensors(model): input_shapes = model.get_parameter_shapes() data_mapping = { name: torch.zeros(shape.lens()).to( mgx_to_torch_dtype_dict[shape.type_string()]).to(device="cuda") if not shape.scalar() else torch.tensor(0).to( mgx_to_torch_dtype_dict[shape.type_string()]).to(device="cuda") for name, shape in input_shapes.items() } return data_mapping class StableDiffusionMGX(): def __init__(self, onnx_model_path, compiled_model_path, fp16, batch, force_compile, exhaustive_tune): model_id = "stabilityai/stable-diffusion-2-1" print(f"Using {model_id}") print("Creating EulerDiscreteScheduler scheduler") self.scheduler = EulerDiscreteScheduler.from_pretrained( model_id, subfolder="scheduler") print("Creating CLIPTokenizer tokenizer...") self.tokenizer = CLIPTokenizer.from_pretrained(model_id, subfolder="tokenizer") if fp16 is None: fp16 = [] elif "all" in fp16: fp16 = ["vae", "clip", "unet"] self.batch = batch print("Load models...") self.models = { "vae": StableDiffusionMGX.load_mgx_model( "vae_decoder", {"latent_sample": [self.batch, 4, 64, 64]}, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="vae" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False, batch=self.batch), "clip": StableDiffusionMGX.load_mgx_model( "text_encoder", {"input_ids": [2, 77]}, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="clip" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "unet": StableDiffusionMGX.load_mgx_model( "unet", { "sample": [2 * self.batch, 4, 64, 64], "encoder_hidden_states": [2 * self.batch, 77, 1024], "timestep": [], }, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="unet" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False, batch=self.batch) } self.tensors = { "clip": allocate_torch_tensors(self.models["clip"]), "unet": allocate_torch_tensors(self.models["unet"]), "vae": allocate_torch_tensors(self.models["vae"]), } self.model_args = { "clip": tensors_to_args(self.tensors['clip']), "unet": tensors_to_args(self.tensors['unet']), "vae": tensors_to_args(self.tensors['vae']), } self.events = { "warmup": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "run": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "clip": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "denoise": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "decode": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), } self.stream = hip.hipStreamCreate()[1] def cleanup(self): for event in self.events.values(): hip.hipEventDestroy(event.start) hip.hipEventDestroy(event.end) hip.hipStreamDestroy(self.stream) def profile_start(self, name): if name in self.events: hip.hipEventRecord(self.events[name].start, None) def profile_end(self, name): if name in self.events: hip.hipEventRecord(self.events[name].end, None) @measure @torch.no_grad() def run(self, prompt, negative_prompt, steps, seed, scale): torch.cuda.synchronize() self.profile_start("run") # need to set this for each run self.scheduler.set_timesteps(steps, device="cuda") print("Tokenizing prompts...") prompt_tokens = self.tokenize(prompt, negative_prompt) print("Creating text embeddings...") self.profile_start("clip") text_embeddings = self.get_embeddings(prompt_tokens) self.profile_end("clip") print( f"Creating random input data ({1}x{4}x{64}x{64}) (latents) with seed={seed}..." ) latents = torch.randn( (self.batch, 4, 64, 64), generator=torch.manual_seed(seed)).to(device="cuda") print("Apply initial noise sigma\n") latents = latents * self.scheduler.init_noise_sigma print("Running denoising loop...") self.profile_start("denoise") for step, t in enumerate(self.scheduler.timesteps): print(f"#{step}/{len(self.scheduler.timesteps)} step") latents = self.denoise_step(text_embeddings, latents, t, scale) self.profile_end("denoise") print("Scale denoised result...") latents = 1 / 0.18215 * latents self.profile_start("decode") print("Decode denoised result...") image = self.decode(latents) self.profile_end("decode") torch.cuda.synchronize() self.profile_end("run") return image def print_summary(self, denoise_steps): print('WARMUP\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['warmup'].start, self.events['warmup'].end)[1])) print('CLIP\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['clip'].start, self.events['clip'].end)[1])) print('UNetx{}\t{:>9.2f} ms'.format( str(denoise_steps), hip.hipEventElapsedTime(self.events['denoise'].start, self.events['denoise'].end)[1])) print('VAE-Dec\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['decode'].start, self.events['decode'].end)[1])) print('RUN\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['run'].start, self.events['run'].end)[1])) @staticmethod @measure def load_mgx_model(name, shapes, onnx_model_path, compiled_model_path=None, use_fp16=False, force_compile=False, exhaustive_tune=False, offload_copy=True, batch=1): print(f"Loading {name} model...") if compiled_model_path is None: compiled_model_path = onnx_model_path onnx_file = f"{onnx_model_path}/{name}/model.onnx" mxr_file = f"{compiled_model_path}/{name}/model_{'fp16' if use_fp16 else 'fp32'}_b{batch}_{'gpu' if not offload_copy else 'oc'}.mxr" if not force_compile and os.path.isfile(mxr_file): print(f"Found mxr, loading it from {mxr_file}") model = mgx.load(mxr_file, format="msgpack") elif os.path.isfile(onnx_file): print(f"No mxr found at {mxr_file}") print(f"Parsing from {onnx_file}") model = mgx.parse_onnx(onnx_file, map_input_dims=shapes) if use_fp16: mgx.quantize_fp16(model) model.compile(mgx.get_target("gpu"), exhaustive_tune=exhaustive_tune, offload_copy=offload_copy) print(f"Saving {name} model to {mxr_file}") os.makedirs(os.path.dirname(mxr_file), exist_ok=True) mgx.save(model, mxr_file, format="msgpack") else: print( f"No {name} model found at {onnx_file} or {mxr_file}. Please download it and re-try." ) sys.exit(1) return model @measure def tokenize(self, prompt, negative_prompt): return self.tokenizer([prompt, negative_prompt], padding="max_length", max_length=self.tokenizer.model_max_length, truncation=True, return_tensors="pt") @measure def get_embeddings(self, prompt_tokens): copy_tensor_sync(self.tensors["clip"]["input_ids"], prompt_tokens.input_ids.to(torch.int32)) run_model_sync(self.models["clip"], self.model_args["clip"]) text_embeds = self.tensors["clip"][get_output_name(0)] return torch.cat( [torch.cat([i] * self.batch) for i in text_embeds.split(1)]) @staticmethod def convert_to_rgb_image(image): image = (image / 2 + 0.5).clamp(0, 1) image = image.detach().cpu().permute(0, 2, 3, 1).numpy() images = (image * 255).round().astype("uint8") return [Image.fromarray(images[i]) for i in range(images.shape[0])] @staticmethod def save_image(pil_image, filename="output.png"): pil_image.save(filename) @measure def denoise_step(self, text_embeddings, latents, t, scale): latents_model_input = torch.cat([latents] * 2) latents_model_input = self.scheduler.scale_model_input( latents_model_input, t).to(torch.float32).to(device="cuda") timestep = t.to(torch.int64).to(device="cuda") copy_tensor_sync(self.tensors["unet"]["sample"], latents_model_input) copy_tensor_sync(self.tensors["unet"]["encoder_hidden_states"], text_embeddings) copy_tensor_sync(self.tensors["unet"]["timestep"], timestep) run_model_sync(self.models["unet"], self.model_args['unet']) noise_pred_text, noise_pred_uncond = torch.tensor_split( self.tensors["unet"][get_output_name(0)], 2) # perform guidance noise_pred = noise_pred_uncond + scale * (noise_pred_text - noise_pred_uncond) # compute the previous noisy sample x_t -> x_t-1 return self.scheduler.step(noise_pred, t, latents).prev_sample @measure def decode(self, latents): copy_tensor_sync(self.tensors["vae"]["latent_sample"], latents) run_model_sync(self.models["vae"], self.model_args["vae"]) return self.tensors["vae"][get_output_name(0)] @measure def warmup(self, num_runs): self.profile_start("warmup") copy_tensor_sync(self.tensors["clip"]["input_ids"], torch.ones((2, 77)).to(torch.int32)) copy_tensor_sync( self.tensors["unet"]["sample"], torch.randn((2 * self.batch, 4, 64, 64)).to(torch.float32)) copy_tensor_sync( self.tensors["unet"]["encoder_hidden_states"], torch.randn((2 * self.batch, 77, 1024)).to(torch.float32)) copy_tensor_sync(self.tensors["unet"]["timestep"], torch.tensor(0).to(torch.int64)) copy_tensor_sync( self.tensors["vae"]["latent_sample"], torch.randn((self.batch, 4, 64, 64)).to(torch.float32)) for _ in range(num_runs): run_model_sync(self.models["clip"], self.model_args["clip"]) run_model_sync(self.models["unet"], self.model_args["unet"]) run_model_sync(self.models["vae"], self.model_args["vae"]) self.profile_end("warmup") if __name__ == "__main__": args = get_args() sd = StableDiffusionMGX(args.onnx_model_path, args.compiled_model_path, args.fp16, args.batch, args.force_compile, args.exhaustive_tune) print("Warmup") sd.warmup(5) print("Run") result = sd.run(args.prompt, args.negative_prompt, args.steps, args.seed, args.scale) print("Summary") sd.print_summary(args.steps) print("Cleanup") sd.cleanup() print("Convert result to rgb image...") images = StableDiffusionMGX.convert_to_rgb_image(result) for i, image in enumerate(images): filename = f"{args.batch}_{args.output}" if args.output else f"output_s{args.seed}_t{args.steps}_{i}.png" StableDiffusionMGX.save_image(image, filename) print(f"Image saved to {filename}") ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_3/000077500000000000000000000000001510465702400255075ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_3/README.md000066400000000000000000000041051510465702400267660ustar00rootroot00000000000000# Stable Diffusion 3 This version was tested with [rocm 6.2](https://github.com/ROCmSoftwarePlatform/AMDMIGraphX/tree/rocm-6.2.0) revision. ## Console application To run the console application, follow these steps below. Setup python environment ```bash # this will require the python venv to installed (e.g. apt install python3.8-venv) python3 -m venv sd_venv . sd_venv/bin/activate ``` Install dependencies ```bash pip install --upgrade pip pip install -r torch_requirements.txt pip install -r requirements.txt ``` Use MIGraphX Python Module ```bash export PYTHONPATH=/opt/rocm/lib:$PYTHONPATH ``` Get models: Make sure you have permission to download and use stabilityai/stable-diffusion-3. ```bash huggingface-cli login ``` Export the models to onnx. Currently, optimum does not have the changes required in their latest release. Please install from their development branch instead. ```bash pip install "optimum-onnx[onnxruntime]"@git+https://github.com/huggingface/optimum-onnx.git ``` Once optimum is built, use the following command to export the models: ```bash optimum-cli export onnx --model stabilityai/stable-diffusion-3-medium-diffusers models/sd3 ``` Run the text-to-image script with the following example prompt and seed (optionally, you can change the batch size / number of images generated for that prompt) ```bash python txt2img.py --prompt "a photograph of an astronaut riding a horse" --steps 50 --output astro_horse.jpg ``` > [!NOTE] > The first run will compile the models and cache them to make subsequent runs faster. New batch sizes will result in the models re-compiling.* The result should look like this: ![example_output.jpg](./example_output.jpg) ## Lower Memory Usage Pipeline The entire pipeline is memory intensive, even when quantizing to fp16. The T5XXL encoder can be disabled alongside fp16 quantization to reduce total GPU memory usage to under 16G. There will be a slight accuracy penalty when disabling T5XXL. ```bash python txt2img.py --prompt "a photograph of an astronaut riding a horse" --steps 50 --skip-t5 --fp16=all --output astro_horse.jpg ``` ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_3/example_output.jpg000066400000000000000000002331511510465702400312710ustar00rootroot00000000000000ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?£šLÓy£šúƒæGRƒL¥(Ù£4”P¨¦ÒÐIKŠ1H—4”¹ —4”PæŒÒQ@ h¤¢€ŒÑŠ( ‰š\ÒQ@ Fh¢€ 3EPš3E.  (¢…t¢€ 3Gb€ Òæ’Š Q@ F(ÒÐbKE OÆŠ(¤¥¢€ (¢€ JpéIŠ3E-%QK@ 5Í3+Oð¨û=¤a‡ñ°ÜÇñ5¥åJ’–¸¤ÝÛ=N)Y"#ôª×60\ÆRhRD=C®jí4Šš&PGªø2ÕÃ=ƒýžN»(¨®6êÎ{)ÚˆŠH;v>à×°Ì€ƒ\þ±c í»G2ºÝÔûW¡C%£Øà«‡‹Õny¾)jià’ÞvŠLdtoïZнÓWGŸ(¸»1(§bŽ” ÂQNíF(˜£´™ Å.(¢˜(Å-Ú\QK@„Å¥£˜£¸¥Å%¼Ò%ìqIŠ%¸£ J)qJŠZZZm´PQŠZ(´´¸£®QKŠ((Å;b‹€ÜRÒí£m'b—QpS…˜£´PRÒb–€ J\Q@ì%´c4”v¥"ŒPQKŠZLQŠZ(¸¢”Š1@”PдRŠR)1TH”´´b€ (Å.((¥ÅQKE%¥¥Å6ŠZ1@ E.)TrqŽæ†ÒWcŠrvCh¨4ûèu;$»¶Übb@Ü0x85c¥(ÉI]¢âÜ^âbŒRÑLBbŒRÐ(%-cëž"¶ÐMºÌ#LOʽBަµ,îíïì㻵}ñH2§ú}j=¬\¹ÔÑÒšŠ›Z2\RRÑVf%RÒ¢–ŠJ)hÅ%´” )h¢€ J\RÐh§bŠLRS»RP ¤"—b€Râ’‹Œ(¥bQ@´PsEPfºhÂöñµ—0ÀÛc£?¯á\± Ä*Œ±áG©¯\Ñ,ãÓ´èl׺Pú·V?rbê8Õu:ð´Ô¥wÐ×QÅ:š¥¯öQ(5^î#qg<+#FÒFÊx*HÆGÒ– 5`x3F¾ðÿ†-ì5;Ö½¼BÍ$Ì峓ÀÉçŠÙ‘Àêj’3“3ðkúP ƒVî®B©æ¹ÝFô`ó]Ñ‹05¶ wÿóYƒ‘‘Ò¤¾¼±em ’¯¹'Нk"MèÙ]Aà©È¯F„ú˜}¤IIKI]' QŠu˜£ìQŠn(§RP¥¥ âŒSºÐE¸¥Å.(•ÀLQNÅ%(I@„ÅêNô JP)h À¤Å-˜£áGz&(Å- n(Ç4ê1@„ŧQŠCƒF)ؤ 4 Ju%0Š\RÐh§b“”RâŒPŠ(Å Š( ¤¥¤ Š1KŠJ3F( ŠZJ(£b˜¸£Ú)ÔPiE-€­F)h¦H˜£´¸ ÑKŠQ@ ŽhÅ:’€ (¢… QŠ&(Å(¥Å7ä¢ÚÆYXeXÉ"§ÅTÖ÷B¼†É#W››Wt°ÎÝt=\ž‚«‰WÙj`øáFŸy¦³~öÚrê½Ê7Ìκb+ÎEÜš‰RýFac²P;©ëþ?…z8d‘HØ28 ¬:{Ñ–bUZ*=Pf¸gJ³—F&(¥¢½3ÉBýà)j륰Ӯo¤1–ÔöýqI»&ÙQNRQG–øÏQ:‡‰gÁÌV߸O|u?žjï‚5ÖÓµa1Í­Ë`gø±^†¹k‡-1vl±%›êh…±*ž‡±µó޼½¯´GÖýZ.²g»ÁŪ u] ÖíïJí“ýáÁÿÆ´kèa58©#äêAÂn/¡--D†(¥Å ÒÒÑŠ1F(¥ bQŠ-Ó»Q@ÆÒÒÑ@„¤§bŒPQE”RâŠJ)qF((¥Å&(¤§RPRS% 4|<-×]ŠâéÕ`µFËtGþ…XñÅmÃE ‰.ud6Öä7—¸dn~ƒ¯½Ea_ðŽë³M²5»ÆC½•Æ?Zð Ùî£{;œ³LÜþxü8¯++Ô±í`i¯gvz”¿´­+”±Ñmc³JÎLTcã.Ü—O·Rå¹b?3^9í’銵o«M¨aJ”è[ßZ›U…_°{®â¿kQ\õˆU£Œº%T¸qÇjçn>(xÒÑŠ¦°ŒAþ;toé\÷ƒüe%®ª%»I%Ê´*Œîa€?˜®gYÇc_f™ëÞ"7wš9kYÉt’1#àþ_δ^ù¶î˜´2ƒå®8@lƒ\ƒ/.4ŸÜh;iƒyyìØÈǦEv.ât`£(þökB¼œÌëÐ!¯IN€qŠ;ײ™à´%´S(¥Å-6ŒS±F(0iih À£Š\R¢Š1@%- ÆhÅ.)h¸¢IŠLQŠZZ@7¸¥¥ Æ)1N¢˜ ŧ(1E.(ÅE;”€P1(§bŒPh§b“½%b—”RâŒPQKŠ1@ KKE&(—b€ LRâ–€b–Šm-PF)h ¤Å/¸¦qKŠ\Q@ £½éqHÅ¥Å-S¥¢Šd‰KEÀ\QE-%´R)qE-KEQKCj½Â åHnMZ"³î¤1“€ 8õ¯ =W§æ{Ù ýäÎc]ÓW(>\œO ëFÕ²îß Òoá?ÝúzVÝÕ´Óé»÷3Hù#8 £ëÒ¸ß-mæa##7#‘šñpX™áê&s‡…zN2=$ŒŽôÁ"´­°,Ÿ{³Ó5ÅYx©ôۨ羹ó­ÁÍ?>Ü’®£¹ƒŽ£ ¿ì,á¼Å,—7*¨_•ÀPNyàWÖÃNI3äg©ÑßžIÀ¬?L«áIÖ3ËÈŠO¶s\tßîØâ+4çÕºÔqø‚ãÄ}ì7¨Í­Q€½A'ô¬qxÊj“Qfø, Oo.‡*çç$úЇw^xË‚U˜{Ó«õ¯+£ß”í+›ðïPÝ%æœÇ‚¢â1ú7ô®å—å^‘¬¼Q¤Èbbbo£ …zÓ­{9uNz6ìxµ>J÷]JäRbžE7è` Z( ŒQE£¸¢€ŒRÑH¤Å;b€ŒRÑ@ Š)izPh¥£””ê(¸ ŧ (1IN¤"€Š\Q@Xn)⟊B( q=Ô:PŽ Ã,Ûgrí'íÈáŠyp£þzÿS^çy‡‘q7˜Àƒ’®= Ta‚EoêfuK‡S¶+ÄóWŒm|çžEcJŒ ±R>õç&zÍ&W`E&3ZVÖK2y²–ÚN¨äÒÝCknv*1>íG:½Šö·1š_‰Än¸àa…EQJÁAÚ{f¯Ghmå¹#–>‚¦r*•6‹:|!uúŠ:í¼3vrÏq"q.áݬ/i»÷\ËÔœ€{U]w_k;Û›kV%ͱ·b§…ÉË…b“œ¬&Ô!v?ÁSÿA}*±(¥‚¢–ÁÛ…ß^•èÚ–¡l5O°ÃrÞq&âdOàrqXžÒΉá¹5}²ð°çIQôà“øW/¦j†ÿS{‰hÙíØNëó39?xÿž+¾.i£‚¥^H®E7œž%ÑŸj‹¿,€ï‘—õÆ*ô6×_ñíußîH­ü«ßMXù©EÜ’ŠqQ¶™6F)Ø£®ŠZ\\QpF)إŠŧbŠm-- ¢—¸¥pF)Ø¢ÅéԘ☠KE.)”RÑŠ`&(Å-„'ÔQKE'J)i(¥ Q@Š(ÅŠv(Ç4ÀJ1NÅÌRí§QŠf(§â’€S¨ âŒS±F7S±F(¸£áK@ ŧQHch§QLÒÒÑ@ Š)qF(´bœ~””À0(Å´‚ÅJJu¦HèÅ.(¦Š)qE ¥¢ŒQp°QF(Å 1ŠZ(½)Žò mz“ÉüJ5Ïx§X}/NwÊ9;A ž˜¡»+Žr’Š(ø—\·Ó 1Êæ[‡Evá}È>˜É®.ÞÞKækÛ½ÉEa‚ç·áRÙÙF­ª'Ú'—æ·¶çmî»è?¥UÔunG“|¯ÔŠ¹Î¯UÎZžþЧ"«Î.î’Î2D,ß;w sÓµP¹!ÑB)òT ã'ëVb†TfBÈÑßJMFÜYÁ¸oÞ_ž‡Ò¹®t3)سRG zÚÐ0IX¦FìI»j(ÅÉÙœ%é„Œîæº ‰ZÞóu¶1Èé€j¦‡¦ÛŨ%Ä{Ê8;2:ëW5‹)–ÖF·b××ŽÕæÖ’•]VŒ\iÙœôZ8¾Ó¥x¤ÝrŽp¹àŠ–ÝÞÖã–2úÀÃU}JK41¢(ÉÉ÷­«½ZÖå0ÎèÏò®‰slö1Іës•—cêq]·…¼Uw´v,<ÔPB‚y 9ã×é\mÄ%ÈÏë)$Žâ3mpù z ì¥QÇXœ5i©{²=FüYköeEÞôlªÝ?N1^k¨Ú\hšžÔ˜†_ž9£8Èõ­ÜAr-¦šÚ\û¤„. ž™™¬G‘Ü嘱÷9­êb=¢ó9áCÙ6“Ðõß øæÎóL†ßS¸T¾S³-ÆñØúW_ ÍµÂæ)‘¾šùÇ*ŵýÝ›‡‚âD#¦ tRÇ8«Iµp1“¼]¢Šâ“åZ7Ä»Ûb‘j-Ä} ƒ‡¯KÒõKM^É.íeôõSèG­wS­ Ÿ 8*áçOu¡kbžF)µ¡ˆ”R‘E%¸£”RâŒPh¥Å.((¥¥ÅŠLS¨ CqE:ŠÃqF)ÔPqF)ø¤Æ(”£Ç4Ê– *—o`:š…î%Ôn.®<¥X"ÀAüDŸSô–.ªÃáç]í\5?mZ4—PûÌŒb`vãÆ»;+kMWB¹³Ôm÷ÛJ‚Îê3Î00¬?>„J¦ñ¬V®økíCÇMÇ üê´:ˆÐ/îg½?ñ+Ÿds¶?ÕkŸlîSé•ô¯˜O0¡*’IYè{p°ÂUQƒ½ÑášÏ‚ît¾©9H²~ËvÿêäBr®O¦8>„ÖF·¥Ï¥Ü¥½ÒÌщbÝÀ`zz•ôö©¢é^+°[IÈuË[]FÀ7õàþ6ø[â_ Ë$Æ5];¢]BŠ.xÜœ”ü2´å ³¦Z8ÄžXí„Q ‘ž9«÷KkÄÂÖEsÓ)%O¶+8O±ü© ùxç†ZÛÓeÒ üV?u²FÒ±iÄô)ÔS[ØÇŸç“1´N3Ÿ‘6â­Z9šåVÂó“Øæ«ÞÉf'cd®±g«š]>ÖÿSŸìúe”×s?Ž2çôþµ\ŽFsª¡Ô×½ñ†ŠÈ” oâsëì*xNMvô_ß#}†6ÜwËfôúzŸÂº¯ü ‘ê(¹Ž4?eWðf~?…w3=ŽdJª[ÙÆ¼dmÈÝ—Ú´„#Lå”åQœæ¿©®‰¢Üjmó­cìK gç^am{.¥‚xšñ‡ðçêj爵¶ñV¶d.É¥Úr »œzž€W7¨^¾¡zó• ÊŠ:*Ž€V±“O˜ÎVzðø3¶âذÏ&6þ†´#¹²»ÃÛ± @#W5o›äÅ Ô]$ @ ŸÆ·§Š›vz£)aánǨhZÖ±$¢Ò;[L¨Ë$h^D_\Ž¢»'cœwRE,hï°B¤àƒÐ÷漓HÔ˜²\Û\Kñœ«FÅ]±×{eâEÕŽ1ÐÈZYpA+mR{÷åT5 Ì©+îÝ)gçÓ8ÈÖÝÖ–ÑÛå…Ú0ž§žsT¦´–âWE\„c®e©ìm*/f`ªò3ë]‘­E§³Ä^B¥Gó®ƒFð͸ӚKÛ\13ŸåX'Dûf§4*Z4ÉÉ8 —V3veFŒ¡ª:»iqÚV‚ ¤…ŽEþyé֭ʶ:†’g[¨ËF°AçùÖU¿ÃœØ‰®n6ÈA;G{gÖ±5jM¹¹µy áÔTû×ëXÊ”'¢fÊsެŽêÞ4‘¥dÌRp㺟ZÂ’6ŠR¿Ãžï[V—é} ·˜âB1Ïz£ym$$£C[Sn/•™TQ’º(4ÄŒb1]î;K ç? þ­ºrº9dÝõ:;ÉžÎk+À GqG ŸÿV)—m¥ìŠð´ql» ñøQu/ KìÉháÀ?ÝÿõU C<#|xepk'tj’–ƒ&±X,ä2ü¬²(\õ9ëU¥¶Ûl½\°#Ó¶–[kÉ"kK#ddþ†‰tÓo¦Þ<À¼IƒÆ ù‡ò«ŒÓ26ŽuЩÈ´´]~ûD¸ó,äÀc—B2­ZÝÕt…”YpÍdd ëÐW5œ’ÚKr£ýYÁÏqŽkXÉÅݸ¦¬Ï`Ðüme«*+¯‘/M…³Ÿp:éÁ 2:•ó|m,R+Fì®ÊTò zw'©|d_õ±ÔÞÚkl;n`,ÌzÃ5CÅ4º¥¶¯¥§?nÓa©–6Ü£õ«z„· då_åS*ã‘ó-t7V¦x„°ãω·¡Cñ¥|nG$°òŠî}62>úlñ¯ø–ãRÐÖ.P_@».læPÊåx û`wÝYøžêÉBÏÐDýô_‘;—ó5ÅxÓáuäºÌšÿ„å]Î|ãg»ËÇ’bn™=ÔãœÖ§ŽµM%…—‰´ë«[…ùKKG»óàýE{1UžÿÃz»nÔ´黼¥~R(?­fÍ |=›$ø[NܸcÿÙ^¹Aã=T e‡ñ¢hw ˜?ï¡PåcUúûa1ù#V¶kúÔë4+·“Ûƒò¸Ü¿QÿÖ­ò+‡ÑuH´ýN9¯¼_wrõ\ñŸq]ÙÁ”†SÈ#¡ôxDkCMÏË3즶_^ÓÙìû‘â“â(Åw”RâŒq@ E.(Å6Š\\PbŒRÑ@„Ç RÒPE´”RÑ@ ŠLSñI@ÄÅ¥Å-7bI@ Š1KE&(Å8QLâ–—b€b–Š&(Å(¥Å’F((¥Å¤QKŠ1EÀJ)ؤÅ„¢—¸¢áa¸£ìQŠ.+ ÅìQŠ.; Š1KŠ1EÀm¸£\Ÿ£îQKŠ1Jà%¸£ZŒRÑL‘1F)h¦b“´´bŒQK@ Š1KE JZ1H¹ˆxƒÃ³æI" ç¨ÎHý+²AÏJã|g¤6¼É¶ñcŽv ä³{×&6¼)S÷º˜ «VñèyŽ›§½Û³EÉR2{We¥é1Á‰X†aÞ·|#ðööæË0ޱ+I$ðGë^ŸcðÖÂ4„ßI$¯»%WŽŸþªùšŠ¥Wîì}L%NŠ÷·<ÛKð…î»æ4HD ÓÆ}³ëÅfÞx?Rðá¸{¨·™ÉY•·`kè+›Km;MKKXV(W…T Àñ]ÆŸ„5õ)6BÑmßÃÜô¦¨rF×!b\å{hyÚîÉm·+åqó3éXö‘ËavnÖ¾uõ_j–Îò d n\ñ¸rk§´ÑãÕÂH$"`¸.jÂÝÍ75u¢¥ÚOhTÃ*º}¹¯1Õõ;‹MÑC11å;qҽˊËOŸNšt¬Œ`®Æ³Ó§jòÝFDÞë"ôbkD­$KÙ™Ë%† ø”f=x9þF¥y¤´>EÞÛ›sÂÈ¿xVlÑ£9(1PHSÍu(Üä“-^$Ns.x(qŸ­$ÿ&h­h±†‘ÛÎ’ýÓU§h²ã߈«××O6™m>í„°ù@ëï[#•»’Æ ±¶¸x¯b™e„ E$6O¶;V}µËA&ìåIäT Ö—½&®mjÂÛ2´÷Æp~•~Þý¡_*æ ñã€zb¹Ëk··o”ü½Åt*¢êÕe\ã®Gjç”yY×)¢å½í Ôe¹¹öÉ–ŠÜlç 5›—‡nâ\îqïÇኡsæ6C.FzŠŠÖýíƒ# –"FU¸?otsT…™=æžRò(× ç e'ŽqÈüÁ¨š‰ “)ÿuÇonhj70ÞÛÅ4 – xÁãü*½Ì¨ÖŠHܳîü03úŠÑ;4vÞñê-²iÚ³9™jKŽ«ïî+Ða‘&ŒI‡Fw¯ Ô쾩ݣbK2Ž;â´¼-ã ½|™÷MhçÊjô(bíîÌóñE/zžÍŠLU=+U¶Õ¬’êÙã*}êðäq^‚i«£Ì’qvch¥Å%ŠZ((¥Å ,%-.)1@¸öÉôÏZèÒêêÉ¥k¶ép«óÚ:ì|÷ùÏä+:Õ](sZçV „1•9ÍEwe JÞÕŠç͘Ë5?*Ÿöô‹wy=Ã9óÏ 8Uú /tû½*äÚ_Û´»žŒ=A­!<ŽÆ¾S«YûÚ#õ¬«)Â`àvþ×pÁØEGæ¹Ïðäþ4Ðù<Õk«¨¡FÒ¤ÛÑñ“YàéN­xÆ îçvcˆ¥…ÂÎu]•šõÓbô.^ÞB¸'Ê$Ã¥m|?¸V›P¶ßòËo½G®Ó×òa\ÚÜyraNùN?OÒ¸ûíNîÄÄ–“Í É#"¼NU±Œc"¾û9¦ª`êSoâ_ª? Ê“Ž!ItgÐÚ¤‰£ÛÏ0_2yíÔ÷”qùf»ôp·òÃÆ;ö?ʾWð¿u#¨Á>³s-柧ȗ/°Qò'?39®þïãÌ_j omk±z*W'>ÿ-|NY€ r‹w»>£YNI£Õµ©×NDó¡Im¥pŒŽ8䀷Z¥{q¤:;Ùç@ËŸ³\ì™Hú>r8ìkð_Åüg¨¶•öXÅÁ¤]ð°Bå±Öºitë}QüÉ4Øß`+ûÀ=áëž•Û$Ó³1Nèçgð?‚/NæÒ´g'¯îž#ÿŽ6*›|:𮥓ÿ_‘üë¯:@lÇc CQ>’Ŷ¦”Pc ±R3ŽÔ<àÿì÷†='H‡xÚ$†ÑäeúF cI¢é6‚; gQÃÇj˜û×£ F@RÖÃæC†*CàDZjizÔÑ»¬ è©%Ê®ï|¨8ªR°ny#él_|è±g½ÇÔv¨-ôH5OÛÙL‚KXAº{2¨BH¯NÖ4ÝFÊÕîuh5R<Íþ`z“ƒ\-Ö½§øgÆú²>ËkB!“¿£9ÉÓ¦GÔ.­áWFá_.1œcÓ¼>Ýb½ÔõMv(ü¸¤ÖÝGð©8Èú W x§â¦—?…/­ì®XÞ\BaD n=¸&¸-5ü!è¿þ„k.ÃJSJÉÝ.¦yÍy(¨ÅîÍ}Em–'žé•m q0½?}ÌfØ{V\·çû  •ÁèBÿ“[ìÀ£—\ï$’{ ú|Ï.y‚÷œvù‹‡sÿìYZ¤y£=û«hg CÖŸ$>PÜrÿ*ˆœ×Äb(TÃÏ’ª³?bÁc°øê*¶WLk Þðî¿ö)Êíÿјâ7?òÌú}éXxÉç§­FéÎwû§541£58æX ú…n»w^hõ6\ò)µÉøkÄ,²Å§]ɸ1ÛŒyÏ÷Oô®¹× Çzúü>&áÏñ\Ó,­—Wtj|Ÿt2Š9¥ÅnyÂQKŠ1@ E.)q@„¤Å<)1@ ŧ)h¸¢–Œf˜ E.(Å%¥Å ¢E6ŒS¨¤bŠZ1@ E-%0 Z0hŤÅ;b•Ça¸¥¥Å¢áa(¥Å.(¸Ä£¸£®b’Š1@ E;b€E;b€E;b¢œ¢à6–—b‹€Ú)hÅ%¥¢€Š1N¢€b–ŒP+hÅ:“½Q"bŒRÒâÀn1F)qGj.Š1K@¤!(¥¢†(Å.)È2ãëEÁns> ÖYU¬íd ây:œÿtS\¼wÓ4êêYÊœ œþ6¬èd˜A†ÞÛê9äÖ£½[•ZKÆòòð±lã¦:Õ¦gbîâ Ýu’Ø•YT½›­zï‡|Ii®YyÈÁ%P7§§n+Äg"[v_¼ŒPÿ1ýjö«Ï¡ê *¨øÞ£ºšê¡ˆpv{¸Œ:¨¯Ô÷ò´ÚÎѵxu;XÛî¹üEjÍz©Ý]<¢âìÈñF)ÄRb)1KE&(4µbÊÆ[ù¶#¬h>ôŽp«éÿê¤ä¢®Ç¹>XîdÝ]Aù±œwÅb1ÜþqÁ“vU»¯¦c[ÖƒªX]q(Nw§(ßøâ©iZUλ­&•ôfæiÿS©÷#§Ö½:S£ <÷M•hWöª6iß‚á—Å™5¿ôÛd˜Çm,£çÀÝx9ûW-ãÜø}ZöÔ´úq?1þ(}7zzö +m. ;XÄpB¡G`*Y¢ŠâŠTYÔ«+ ‚c_Š„+ÍÉ+\ûŒ³0¯jҺ껟1Íp!F‘ŽÆ©\;éºü7r%ŸsFëÇ,FA…už;ðULJõ8nÐD›ƒ¨%‘¿…Øv=þµÃø–àOg±:+dœõêäX5FJòßaq>p±õ©Q¦½Å¯Ïþ ½Ù:‡8@Ï÷°+–ÔîEÅט½ ¶*kíLÍil®6Ë~Qaü@t?‘ÇáG†ô‰üM¯éú=¨o6yJ–Ç ½K~×0Æ{H*hñp¸e 9„¼7eÃkĚͩž1Ú!ŒLÊp —sݪv·70ÑxO’èÂÂy2=›}z÷ÄK[-EÐ,ÒlÍkeë;( ~$’O¦k‘¸ñ¿‰b1Þ]]]iVrH¥‘wDÌÝ !{g¹pØyNŸ5Ò5­YFVµÎ£àûi=ìƒC³ÒõˆþSK";DpyW'ŽÕêO"ÚÚÍ+îÚ›œç¯%ð‰.õj «Ëc£o"Û<Œ ´‘? 0îAïé^·{šÒHøÁ>ã5ËZœ©Ôp“»GMã(FqVMsgñ£ÃŽ¿”m.¢Ie1-ÜÁV2Àã±Î3Þ½ 2bgŒd†NsßšñË?ƒ·v¾'‘ Ö¡]oõ®¤ ÊgîcÐkØ`x"%úëÞ²Üã? ’øÆÒÙd¹o’®Z$¬ä¨8$ôõ®ZKmAu¶Ñîl.¤¾BZÁ«HòŒàc sÎ1[¿f¾>;/gmaŠHÞ@ †Ã6’9ç#é\”¶ž${»íA–uººFûC£Ò«à²àœ«ÐÃã0p‡-gùïs9P­'x&u÷ún«£Ap$½ÖìX-ÒÛÍzÒm%Â2’ À×!ãk+}F÷JÖ¡—sjIäÜÛŠNQ‰#ð5Óx*Þñ5ÛxäÊÆZÛˈÈdTV.JàýܘÁ+ùסøÛÀ6ZüOt½‹ZBÏj"P Ëw?lqŒ{æ°uiͧ5äW$¡¤·>Yº°–ÞRs‚?*ì´Â£Ãf á‚£GºçüksI´ðÛ ‹(ïoï5[ûW…ãŠÑ 6îà1;‰…2‹©N6z­Y{C¸ûF«¥’såÅ(ü·uªžÎ[lKÊÿ´}~•Âx^E¤hÊIÆ¡ýl~5Ô\êFæWŠÙm "½è¢½Œ¾²t¹žíþˆòqô[«hô_©`Ìù%ºgœàVÞá]OÄ,ÆÝ<…ù^v;P¤sÉ8ô¬ÍI“[Öíl®šYßjħ5êXŸ` ¯¢t­*ÓOÓà±²‹Ê´„qêÇúûšàÎåF¤U9FòüW"Æbð5J²Ù®þâl>éÒgÔu1öûˆái?x¿»\ËÜýs^E.ƒwå}¢qäÄßwÌR gÓÿJú/Äñ½Æ…}©ÂÂXdpHç¥x6­¨\_H—7R[%KÐJÏ(ÂÓœ$œUº™æ¹Ž*uÕG7ÍÜÀ%ÅÄ¥4»¯>íFå·.»˜îœå^› ÝÜj: ¥ÅÜmÖÒ“+.õ$=ñšâF’om¥¾´{…¸²Ã¤ñä”aÎÜ}9â»/ ëM¬iÎ1y†;Œ|Ùàþ#ù ž¤¥KgКù…|]Ƴ»]M,RSȤÅhyâRS±F((¢ŒS¢—H¢—b‹€”RâŒPINÅ€LQŠZ\PqIŠ} ,3´ê(Ú)ؤÅ ÚŠv(ç¶(Ú)Ø£J)Ø£®¢–—Àm.)H¥ â—¸¢ÄÅ--ÜRâ–’€ Rb–ŠLQŠZ(£š(í@”´ÀJ)qI@ E- QKŠ1@ @§Q@(¥b¨€¤§ ())iqÍ7¢”Ñ@¥¢€å8 Ói€8¯X=޳¥6—J"—þ™¸éøñ¯LøM¥$:úƒ!O3*“ýÐþuÊø•WþÛ‰HÉ…–eÏL©W¨ø6Á´ïévòcÍòÈGvo˜ÿ:ðqXxÃ̺ŸC†Ä¹áy^é›â³õ}VËG°’âòeŽ<`dòÄöu›jä~•åu%ÔVèÀ¬ ä7¿çúV§É ›Q§í&¢Î?Ä7qj·Ò^<²6ÆçÛùWsvÖ×›ÒFÜ­ÀÏ5Òê×Ç`<¼r6;öüº×-”×-ÂHs’Ö¼úK™ÝžOv6‰SRÔšk”Îöh8§Úx†óNÌÖwRG+uÚzûcÚ¥—Â÷Q̯¨Ï ŒL2BXãØj 8Gî5[+•í?¨¯F $y³rl¯ªë7ºÄ«5åÜ×8Ïè*”+ór ·Œjß¾™vŽÑå‰üzR2«\9D ÈAØSrBŒ^ìr¹Cx¨HO´ªÈÄ!`’|{ÔŒAeQÖ«Âuw1‚^W¸õ'¢Š›èz>µ~¶~5[âå´½2à²i¡Þ_—ò2ƒ€NyÏ©®KIº{»Qî%yíe‘¤q’÷²¹ãñ®‡âUý« [2¦(R87Ëùk‚~™8ü+’M·‡î.[73,1’8*¿3~»*‘‘ž Mõ©dO*b£8íQÉÔHÓ¥ÀR£‘\u4”b$Ì'X¦;Hp=? §#´¬Î;8Ýž”hïªÐÊØeù”·B;ŒTóˆÜ–´‡rœŽçéXÚÌÛus5ÔIJJ 1Ú Õ_ïvÀ#µhJþXQ0˼ò5EÁYm7AŽÕ¢3’+2á¸éV,¯ÒmãHÁ¡¦>$ À ô «1e™†Á²1ˆ™·‚ÞÝ©×SyÐÚ2±•ÀöcçU•ËaXð:{R¯ßPyóLGKá¿Üh÷'ù¢ý“ØŸZöm+R‡TµóclŒ§ªŸóÞ¼ —ÎÔ^8‡Ëp‡n1úŠét v{yVâ9¼™ ˆ$‰Ž$Ûœøù{×fãîËc“†SW[žÌE7™¨Å©Ù$é…r¹)ŸÔzÕ²1^šiêy.-;2,QŠy˜æX"†IæŽWt²ª¾õßÙxNƒM6SÃÇš¸˜È2$?Jçü!l³ë3îýÄy\{ñÍvͼ²)xÑ>é#‘^n.«rå=<$£ÏÔã/ü/¯iÎ<-©,‘ì°Ô‰tþ¹É÷—‡"´<%áÿøG,v]L×ZÓy··XÎçÇLöQÐWBîÐnqºXÀåù‡øÔ6÷vZƒ—·˜ ¼†ÅMrûI[”ì’MÝ’O!YbM ÆùV9éÇ$dÇãoëTõ6dŠ7î¸É«®ãÈ ê8¤#.à­ô3G$jñ0*ÈÃ!—¯œ>#xiü-ªþè7öeæL ×Ë=Ðý;zŠúYÈŽ€£å=x¯)ñч^ðȰ¹Ûæÿžr/Fúv>Ù­èוÍ%=ÏŸ.ü¶XÚ3œ k×ÿgíµ5MvP6[D-Ñfn[€ýkÇ."–ÚFŠd(ñ¯bø?töV“Âû„2K¢—RÜ$‘ £ËÀ@qóÇ›¨Ç$Š.Äœ3tÈí]_Œ´»=3Æò]ÞG;iw®ÓâÞz¦O ú¡6}¨è·~#ŽÒ}. <<Ò¡²~ân9b;â­K@9ý6ãʾÜ82 ¿v©5´8ظHþà<óëîMp“[IÆÁÃËŽ¸=+Ú¾øU5BVñD¶Vh¬»‡Êóžqï·ùâ½,;ØÓ’kÐãÅa}¬“¹Úü5ð\¶uÝV"—· ¶Xs g©?í7A^œ«Þ‘šW†3Çzó«U•Y¹Ëvt åD3å'«WÏ~0Ò&ðþ³qgsdÌ)•HG8Æáé^ô%…ï]üÀRÜlÀ+¼÷8þtÏ2Úö)-Ú1ø~5ê¾#ð—„-£Ý{ GoùmjZ,¨â¹++GÓâ[Ý.ÝöÀ“\HÒH¹l‘“ÇÓõÛ,ÆNѵÌ>ªÒµîo²óMÅIêt#"’µLóÚ#ŧâŒS†b—´¸¢ã°ÊP)ÛhÅa;QŠ\RÒ QKŠZ.!´bŠ(¸XJJv(éEÀLQKK@ì7RâŒS(Å:Š@7¸¥¢€b–Š(Ũ aŠ)hÅÑŠZLP0¢Š(t¢–Œf‰Š1NÅ%&)p)h BqIN¤ £´f‹€˜£ê(¸ ŠJu%¸¥´”À)-€J)i(°¸¢–¬‘1IŠu%˜¥¢”((Å-\b–ŠC°˜¥¢” dÖ+ªCýžùÛpË z‘^¶ˆ#EEUvÀøbÛíäLGË ™× þuèW—Œk¦ >B­íÒYZMq)Â"“Ö¾uÖõ´ëW¯!™8ùH'Œí^‘ñ]$ >/¸ŒKØ%‡LzŠñmB>äĤ®8lsŽõâW¨§.^Ç¿…¥ÉgÔHf[‹¸å;¶îÏùÅ\LËw E ,IR2qß§Z’ÊÂ]ÃzÙÆLcùô®£K²Û ÌÑ‘• ¹Sòý}{ÔDÚH‚Sk¾HÕò¡¶p:W-©XZ\]¦1Â/_­t:œ« f´ƒ•R8¹ú×=pβ—(À0?2öÏ­iÌÌÜQx±ZÅÂŒžŠÏ»´é¿¡#8ôÍ[¶VÔuC˜÷E' íUuyí"1’Y°09úV‘LÆMnfÉ‘h%“p2}ÅÏoR*ï…míæÖîKâ÷+`©­këV‘}ºßFƒLŽæX£Ø²ÛHL¬Ú uéŠÎ‰[GÓ§«Cw»6B¤žƒ?NOÖ·OC‘™ZÏÛu$%„A±<€ñOÔnÖçÉ·ƒ"ÒÙHˆ0Á99,}Ïô rHÃïå‰ÁR3Æ1H~7bã–u³Got£×kFAŽE_„yÖsÁüJ|ÄþµœM4L´Ðp< u1 8PRØ‘K!VEmµÜ_eó­ÐÝ…`¿ÂÝÍaŽ•,R´q²‚FꆮkaÀ¹É÷Èä“M—~C·ÞnI3îŽÜÚ3É*yüj©f rO4Ф†Ò0ëÞŠLÕ1¸¥Ïb’™´"c¹'Ì¡{ŽŸãZztÑÉe¨É&7´QF¹þ÷ù×ò§Ç1HÊv$1¦€î¼ây4íHX]8’Ô–ÎÀ'å>¹é^³¤ß&§d³£nÈ œcrœá±Û8éõ¯œ­ãiÎÔ?¾Ü6Ü“ŒWwàzKEÒk–¢`“ÁûØÇ·ÿZ»0ø‡Ë#‡R\ÑÜõý¹©­,¤½»KxGÎÝû(õ5 ¼ÑÜÀ“@ë$N7+/B+¦ð½¹X.. üÒ0·súþ•ÙZ§$.Ž 4ùçfmi:4:D %å|o‘º·ø ¾I+ûäù©B UèìW‘)6îÏb1QVE ¹kiû²?>¢²šûM»ºßZµ½â¸ a›žªGÞJݪ²†vîø•«CgqÛO³có÷y\r¤zЈõˆt_éÑM1K ÅhÈÉ)» ƒè85ÑG¨ÛÛéïq,›¡Ãž+μGáíb-&âÂåäÔì1æ[^ºXXd€àví¸qëYn¸÷ÚlpI+yl¿6ÓžÅ&ìRÎÛSñF–çyç\”2àþµy¼úŒw7Wä1òeq4MŽiºµ´Â\,¢xº+pG¿½XðîŒ/'2ܯú4\ýöôú PR©.T9òÒ‡<ŽKWðœhêWpFÒÏ40Úùe%›óç]€íÔêVvй ŽÝÿJ‹Çw-%þ`ŸuÌTtþèý3SøRâ-'V´»ºWXà,ÇË,qÀúsZT‚§>TE)º”ùŸSÚÒWk‰NÞ<Â:tŠÊ›Yõ‡Ó¼¿“[áãÛ¥dËãf–ÐE@‘±Ø'ÔcŠ~†ÑëÚæ¢RHYP+¨9ˆÀ*zôÍHZÇ[i‘Sœ“Ÿ™ËŸÌÔäñ‘úÕtFòÄjçåeºša‚g8i¶j`J×*¼df˜dRs¹Í'ØÑNFæ÷4m ÛÉiÆôR§é~¢ªµðþÐrþîR»Ðvq߿ҮÍ7• * =Íe4q]j.‚¼–h²ò6­!×.-­tÓ=Ò$‘FÃr·|ñǽs($yfÓm¤{MŠäã”zu"Ÿ¨_[ëúÕ­“3C¥Ä¾t²Iòîàú•ÀúÖ—ü$:ŸvÒ%Ü[Û† ¸Œv¥'©HókG‡Y³’Ú`­ 9SÝOfõæ÷¾Öe´†VšÜ1)¸±ƒÕ±Ð~íºÝæwz'ÓLù¨PªçûÃ#½e͈ءËvŠ†ÚØÕ$÷<«Pðå¶a»,ò/ÈIþ/Sì+Ù¾ ßÇ?ƒ˜s²Ì¤vc¸7åü«…×–ÕË)Ü#¥GðóŰxcÅ7+y‘e|“´r ì$z‘D$ú„㦇Ѷ8÷;íT±ýª‘q«Ä‹-ªÌdÚãi‘q•úòsZ‘³¼Ñ$®7c: ÐÈK;,1Qc^%ä/¹=Íh.Ý¿.? ~0++Q¾{wÖ³y >i×/Ôibí ”-óü‚k‹ñÝ•¦„·¤P±>YŠ5I»§NàŒþu³m§E©(Ÿ€q“š§â­(K GfåšG¸L4csuíéÅ1u9-su¤[HG8Ú*áÖ~€¾^’"ë²F_Ö´{Ýà™ãUV›C1IŠy¤­ Ä¢–’€ŠZ()h¢ E.(ÅŠ( AŠ(¢€ (¥ÅŠZ1@„¥¢Š%.)h¤QKF((£¸¦(¢”R(¥¤ ÅS¨ Å.(¥ æŒQE¤Å;”\Å¥£\ŧQ@ ŧQ@ ŧQŠ.1¸£ìRQpb–Š&(Å-¦\QŠZ1T@b“ê( Åê)†Òâ—bØLP:ŒQp°‚—Fp3@Xë<MÜÿî þgúVþ¯~ºn›5É?2”zžÕ_ÖFÇG‰XbI?xÿSÓôÅr_uuHRÉYw'ï’O íÒ¼]d›‘ô*7Q‰äž#ÖnoµŽL‰wœóO?JÏòB¸Ì$¶8¹éš­jò_êÒÈA;}½ë³ÐôǾ”I"b(Y°yôöþuäÅ]žÝì‰ô%‘CÌ!Fîpr9 ÖŽ©p`·—i ‹÷îz`ÿžµ=üÑéq3’±Ú9ü1^iâ^{‰L1&Å|ƒƒ’=k]´3}ÂÞYµ+écM¥òÁNp;qß& ×oͼ2@ÌÜ×5¹¤ØÛé6º‡š73¿Þjæv©­Iq >DŸCT‘œž„š|M¥è¦Iù’çØv¬«yº}J~#·ùïÚ­jºƒê·Iol¡W;FÞÿZÂÕ¤e´†_2(¸ã¡jÞoVrÔ’Z#£°Õ.ëçúV9\¨#'iê3Ö€=·á÷‰-nmÁ›lÊ `“–9?z5Ƴ.ŠtIZ]–/| œvôŸ£s_/麄º|‘Þ@àH…×™\úŸÊ½ëÀºD*ðÓZòmBâxËÇ„·”°'§&º^zvg/±ä©ÌºžÔÅ#6+?D¼kíÒæE+$‘)`F8ä~u£Ú¹Ž‚=Á¸Þ>†²µ0ÜÆ°F`¸ä«Ý˜ìqÓëÖ´n_dLÞYr?…W$×?>¹o °’ivíÜ#a†R3ùzh–bkººÚiÒM›)h§Uû±±”tÔzæ¼›ByàÓ-¶€YTCïüë­ñF½s©^Ê, ydÐÊr' äcý¯›ÄNðÞ„ºeеÌj×-ÔBÀ{úШʴ¹b7^4cÍ"…¶Ÿ>¡0Ø Å’]Èà}=~•ÕÛC¬)k„AOÝÆ)5éaðÑ¢¼Ï/Š•g®ˆâ¼B¦_LÄgdÐäÒÀ[¡lg¨AÅKâ N¾ öAüÍVýöÇò”ŠÏ€¹$Ò¼ÚÿÅg«CøQ5UˆíŽ1½‰;G½z_ƒm„IŒ5Ë™9î½ü‡ë^ /‰î4/› }ív³`^ÁsîChT“‘ÐŽ}kèûŵœ0F ÅJ±¯¢ã€ QC“-ýæ ~Ö˜/ÊèKzP¯·4åÆX·òª ÁÕ¯õë[¨—L²†ê_›yÚAôÎjΟ©ÝÝ«GN–À§;¤ «}kBH¡B[ËSUÚ@~äA½È¤ W½ä#SŽ)Àü©ªpÇöv–WmÒÌÛ¤Œ=¿:UŸ0ã;vžõ³ ìHgð¦‘-žcâÖ1ël±‚ªÇi\ð{ëY°¼b]¿tç#$~5Ö$Ñ.¿{¨\ÇÅ´ËÊà6O⢼>ïÆï#œDÎìc†;eÛŒž¬N}}zT½Íbô=fÂÊãS¹H-ã.zç äö«ZšGdÞDSyîœ9pAW~n×¼%t‘\9œ¯›" ¶Ìr3Ú±ï¢ë"ÛÅ4Ыl*uþƒñ5JÑ=·9½~äÜD"‰pKsÖ¹«ë8]¯7&Wiˆ~µÖ%¬W7ëöëøtÈGYe¤Çä1Ÿ©¯Zð߃¼;¦YÅ-ŒQ^;|ÿl”‰Ϩ=áD¨Îš÷•‚5á/…ÜËøkÿ &¥¡CyâMê¢5H"eÚdþZI’IcÇ ®ü b‘F: lãŽ)ƒvlRNHPA çŠÌ½½– 9eTf`Á@éÇsùU"[+Þé^M&o"N­m¨Þ¸ôªW7S[èw—QI Ñ)ˆFýw¿Pxæ©é>6µe—íÒubj0¬Ox—ûRTŠÐ7“«.öÈÉ^Øô¤äŠŒ$Ù›áÝÇK”¶sç¿ZÓ5SM+þ˜|¦bߘÍ[=kÖ ïM>!Z¬†›N"’¶0ŠZ(¸ E-\BQKE(¥¢ÀJ)qJÜQŠq˜¥p QKE&)qEQEQE.((¥Å”QŠ\P11@ê(”b–Š.QKE!‰Š)Š.qF)Ô™ “¹£4b–“4´”RÑ@ Å´PRšJ)€QE€(ÅSÅ%-€¯Š1KŠ*ÉRÑ@)1KKJà7S¨¢à%´PšÐÒ,>ß¨Ç ËS½Ï°ªI×·<×1§ë𶣤bhc° –yP¥FpF íšçÄÕ䇛:pÔ¹çäs¸¹ŠÒÎIå!c xWÏÞ6Ôæ»ºg.ìNzõÅyö±¨QÙ%x7îÖÉQŽùÎ:¦u½f)üÇç9çÎ`çë^jN¥¬Ïz…eMêŽÇú{¿›!,¢G(=8¯Mµ…,´õˆ.ýÊ_?)Ç_ZòÇ«§GwúHuîðK‚rs­žì—â7‡ï¢‘"•àreØ>Gë\þÎP»hìU£-2¼E¨ç¨Ç1„¶Â~uÚØî=+k˜uÔ±4a<¿-™’AÁaƒ»¸¨eñžlMq5„L·Zmº›Øw!V+åŒõ OƲUOz‹›Â7\ púÒàã­/µEÍÒ±ŒÐ¤¾Ÿuq–AíÎ?­9‡ÄÆÓ.3æFWùéW ªå5½\²ˆL^-ÁY‡ÕP¾•:©X²%ª›2„FM…zÔ}iÒ1y›©ëMšhMê9N H9=[™qcäPø?Zˆõ© dc'•¡†´Ú}!F- ´Q@€Ži)àfšEhXÜÄáׂ+м-ãÓ¬d0\µ•üJZ2¤mfÎN3Çá^uE;ˆ÷¯ø«Å'Ö4K(¯ßO°GUŸÉ`˜|ÇsIlð¾þÕîvÚ•µãH¶ò¤Æ&ØþYÈVô'Ö¾6мFÖ÷štwCýÚ4”b¬Ç×·JõÝÇwðxHÁ§¬—ñ£W ¼6rp9> äãƒÇJcÜÙÔ¯*~†¸Ù Ý8ù.É)lÁ/÷ОÀÿŸCˆ5-kž —Z»Õ–òXö„ÂIbž3œûV Þ(^°Y-®cØèÎÑÃr¼‚=ˆoûê®6dÙ˜þ‚KÝFkùDqôp$#Ügñ®¨õ¬Ÿ jZ}åƒÚX£C$,L±¸äO9ï[¸¯Khyx©9TwèG@ëN"šEnsØæuø¶êáˆ8x—†iÚ-ÄvÚ…¼³8Xw…‘e<çZzݧÚl„ª¹’F;ŽÿãUü9 ŒŸj¼Ô`k‹Ku@ƒýlŒp«î:ñ^F&¨ßsÚÂÍJ’ò<íür|vÚkXÜñ|™Ðþïa`w“Œr?ZúBÊò[«‰×ì¯@Æ/œòqßéŽk–ñˆü›½fí4ëXdI­ôÛ5&Ó•Ü}xÿõWr\—ÉVÞÜŒVq.ZUçwQX^7ñøwÂw÷ð`\¢mˆ‘¬Ü~j]cR–Æú;kb²nMÇ Êýk3W¶·ñ>‘&—|æßÍûÒ‚2…Nr3Á¦IóÕˆµ{¿EæjWks+þîôÏ#’OÝ%AÆÆkݼâÛs@”ꤾ³”Á3À„†ÇF8ùü«Ï4á_xÙžæñµ;¸se ­±\;nf?.îƒ#=Mz?‚ ž/øŽfòü©\Lc æç$\dŒ÷ÅJe4k?Š4ÈÛkC;ã•ÅVÖ¼SeÍ–h¥‘AÛ•Öº»©l"ˆÏq䪯Wç\„ú^­êHÖpÜ‚ï’Æ2ñ7sœœUv+#µ¹[­h[† :ƒÓ–þf¼Fî;«F±ÜC#ƈªÌç,NÒ£ƒÉ=«ê»Ÿè’J’”x6c"ݶ#œšƒWð%àš÷Jd´¼˜~ñÐ`Kõ=EiJ1”’›²îEIÊ1n*ìç¾ør÷ÃZÛë¤>¡¨ÉòÚù€¸ÎôžÕèÖö2`›—OH Š==ëÃô¹¤ð†¿}.¬Î¶Çþ‘c»-tÄü…Aã àîìvŸþ$Oâ+ù4ÍN8R]¦HeFÆåî°õÆ9׈ÁT¤ÛޱîsÒÄ¢´´}ŽÞû@Óoã"[HKv!¼úêÖãáþ®o¬VOì×ùîíÃs̈;Ÿ˜uW¦¼ñ‡Äc{ƒÑ{}k+Ä–ðÞiÅߠ˰?Ü ‡Ø©?—µaJ¼×»-S.¥¿z:4ii×ñjË4n¬*r##XšF@.æ<^AðÃVk-v÷×ËfÒB‡9ß%ÐûàyƒéŠõî ]Æ‘êç8’EÂOB¦¯±Ö%’Üâ)ÓzvÃƳ”±á”"®f‘,N:Uˆ4ã3ž~\üÍXFõ$”NÆãJ ÈŸIÒÀ³õ–B߇AVÏZvЊxQÀ¤¯ ¥H(Ÿ3Z§=G!´”êJ³6&)1N£m.(Å Š\qF(‚Š\QŽh(§bŒPqKŠ\QŠ ¸¢Š¤Çµ:ŒPx£ê((§RPQKŠ'”¸¥¤ Å¥¢€ CÖ–Š…%´QE (¢Š%´S(§ LPQKŠ1@ KKŽ(ÛHÑJG4PRRÑLAER¢ŒQ@ȨíKŠ)’7S©)€bŠ\QHÅ´PQŠv)1Í-izpÕ5í$…2㺎£ñé^¼VФ0D‘Dƒ ˆ0íÀ®sÁö›ažñ‡.Ûû¿¯ò®§µyXšœÓ·cÕÂSå…ûœ§4#SÑdûnŸk3ç^1œŸCÖ¼¶?„zmå°’ÚþïO¹$8‘=¸8?­{½‰£(Y€B1Á'úâ³¢ ª08®7¹Ü¶HÜúâÆãµµüE?&Çôõ |üžp?ZÊñrèi2iÓÚGró)²tS؃ÔcÚ©&ÄÝ·>Y½Ðu;C½í þ8¾uúäf¦´ñ-ý¬&&ul.Ð]2~™Íz£á)4 5ïÅÕô ü úàƒšäo¬t­vââîÛPx¦X·¿šŸ)n˜Ï~µõ´ÑkkÅœÍåô—³ö«ú ¡¿»xT”)’™”cURÄàuéúÖFv ǽuÞ½°³Ó|@’;-åÅŸ•ìʪ7äç‚Gç5¦‰«z”YtÁ¡›k!+ÝÉ /<¸Aü _NÕštë¨bk™b+rþn2øÎ0zô®¯ÄºzÜøkN×"·Š2È’KuÂ:cä%{0ÁSÓ8½s³êwÅóŽˆ×Óüôü+;›ÆÄK-Ô­<ï¾F9,i Sð8'­.jnu(¤7£¤ÛÀ$qIŒóÞ•Ça¿z‘# ²Jý1µsëRF2Ý;Óîÿs§"ãærOçÿê¡=lg5¥ÌÄf­Æ¡­ÁœÕ<àb®ÙH¹(Ç‚8­$eI­Šs.×5žjõÒ`gÞ¨ã N.èÊ¢åú\RÅ-1 æŠZJi¤§šiªDÉ 4”ãÖ’™¢”ŠJQÒ‘HiÚy¦‘@˜•µ¤ø‚ãL¹‚U$¬N¬W=H<7ÔGãXµµ øgPñÄZª¤)÷çáWüOÒ”¤¢¯!$Û²;OüI»ñ­ªÙª½´,9Èv|ÍÛŒç¾ÐÚ\P_üÊÄ¡Iò2Çó"ºkï i¾±E¶WšîU¥?3 ’«ØsßÓ­yî£u$í 2d$Kµ±ïYR¬ªë\yV§µé°ÜI{µ¤}št(RX ìfÉÏ_PÀ5ÐÇâ<Ý-܆Æñ€" ±å“ô?u¿^Cà¿M¦k!e APÜcnqúñ]çŽ/ôý_EH¤hŽÕµß&…T_÷·dû õiÕ´t<Ú´y¥i£FAæ£"¸-:Ã^·Ñ ºð¾¶—#`y4¹ñ½AùC“œtÈÆpq]n…ªI¨[Co¨F-µ$I5«FÑ7SÊ«rG‘šéDÝŽIÐqWZ£DF8Á­ß øJ>ÞI'vo>UbÆ<¼ïÖ¨iVbïU†&ûŠw0ö×êZ¥Ž“n.oîc·„°]Îp =rb¤®‘Ó„‹³‘•âkÝb×M–kU‚œIµKHã*_ç^pú¤PÉ›9¸ýé`#Íz¼Z®¬C$:„ͳrˆÜn?*£qú½ƒ¤öPJ5"7ÉÁ$9è@Ï5ɹ؎;Âúæˆn'Q‡-ŒãÎt÷ñxrþÒY-æ†+•ˆ˜ä2ª@ã<ãf×Âí¥“wäÛ’I‚eÞ¹>‡ƒS]h²M±‹O’R§‡‡þ=h°\ùÆÞÆ÷VñtWsµùdÄpÄÙäpÀ¯>œ×к‚ú6ä¦BÙy]ó–êO¹'5NÃMðçt—ºX­ìÆ34Ç—cÜy>ÀTbÿZñ+CÚf™'Þ¾¹eeôŠ3È?í0À÷ { ivÒN³Ý¿Ú¦NSÌj{ªôϽOs™Hb#BÿWÓ¬í´è$°´g•¢Á‘Ý÷»1ç.ßÞöôÆ8¦Ü]C¡euFÏB4&O≼¦á˜à89ù½¥TºÕÞ¡n„ nÐÿ*N&¹ÏL¬±m™·912#ã'c¦1œ×9'ˆæÕôøDjÆå£˜î) þ"ÒÜ[Ø—âN’5?´G hu xLöîØÿH@>u§ùÍxÆ™®ÿcjÖ÷ðÝÃàÿ¨üFEz-î·5Þ¢oõ‡˜ÙDQwpc$`w#ƒ^3 3+yjÞcË„FI?ýj짘?déƒ´¹«áñ€™m–ÖØB·¤¨Îz« ‚?Z¥­ëûL¶æA#ÜDW%°±¯9é×9Æ+ŽðüZ’èš|wH¢hmÈQœï‚)ڣȑIq±˜Æ áy'Ûô¯2Unô;cFÈã|.óKñQKy6…]…‡÷AU?øîGâkÓïubÚ«OjY"ÞS)!·7VúI®'ÀÚ Ý‡Û5MN"—wÍ¿Ë#”\“Ï×#jéd»ÓãÞ^ò+Ëà‘ø 'S¢:KvM.¡u6äó$mÝK19¨`b ·Õ­.u,í£•üÆÇ˜Whžyí] ÛÇÈ\·©ëUG :ÚßBkâáCKjV†Ø¸Tôîjß »Ta}Òf½z4#IY%|DëJòh¥¤­Îq(Å-”Rⓘ¥Å¢€ )h ïE.(˜£´¸ £¸£ LQŠZ((¥¢€ŒRÒPF)qF((¥¢€E.)q@ Å.)ÔPqF)Øæ—ÌQŠ~)( Š1KEb–ŠLQŠv(Å-.(¥pKKF(¸ E-¢à7˜§QLCqF)Ô”ÜQŠuÜqKŠZEŽ)1NŦH˜£¸¢€ŒRÑJ㸢–‹€˜§EM*E˹ ¿ZLV¿‡`ݪ}¡†R'§ñõ¬êÔ䃑t¡Ï5²±´K+8­£û¨¸úžõ$’ª½ÏÚÀŠ­<ÁŽüàkÁ•[ê ;hgø‚äÆ$bw±ª–ò,±Žk7XÔa{¢ ÊN{ŸÒ›¤Þ¤ŽPJ„žˆ#Þ…r’7àÁ$Ž‚ªO Ý5̸"%À >µbêâ+ ‘˜Œ.{×?5û¦ždQËyàV±1“8?ˆ5ݼÊd"Øt^ù÷¯1[;ûØSOӬ䜖ÉH#,ÍõÇjéüK«Y¬³ý¾i甹ÛD(_«`þ‚‹ÏÞiZf‘g¢ÚÇ£Û_¢ÈóFŤ#qRKuíŸð¦ÚcŠg)á+­"H“V»²³™ÏÍ Ê]ãÿ| ;Yi²[J–úŠ©pm*?„ÔŸ éRøÏ\³Ö¼F÷:t;(cX-ÆÌUy»’NNO<Õ· qäiÅ®¤`3*¡ñ’v÷êqÚ¡–™ÚjÂM'áͶ—yÅy<€$'±b߆à8êOµpÛ01Š»ªÃ%¨Ž–“í‘gÌIá¹çüæ³ÕØÖMt­aåN|SŠ"°¿¥HÂãÞ¡ÈéQqÏJc ô©ʦ¶³žöâ+[XšYä8TPI?áS·d²Weh”í$gwN•_W†æ Á ÄDÊ£jÈ¥NÜdZöÏ øÏD’+«×w‹ÎÜ.3íê}ÍRñ߇—Ä×wM ³_YØ,‘ry!É+ø®k¾I¨óHòjãá)û8lx{rièH#h*GQMÈÕhîh+ ‚9ÇžëµÖ§Êò4û„ Q“R´fÓ\ѹPwjn0iÃ¥S0BÒRö¤Í,))E¨D²3ÖÓ6©HANÎ))èŒì™@´RCo5Ì¢(ci$=FMlXøvYHk¦ò—ûƒ–?á[°ÿĦ6ò!HbÇ.O_©=k ×KHêÍ#M½YCNð}Ë/Ú/$hrcÏ'뎂ºµ™t˜<¦khl‚€¡9Ç|(õ÷®nóÅ÷KAhج„!\ä×Ü9’ggsÔ±¬}Œë;ÕØ®hÃá6õŸÝjxŠ"a· ³nì³öôV ;HÃq%±·ša¥=y®ÈB0VŠ1rmêXÉqò1aÔé[öð5Þ£`fKæÃ¹²yAc·ÝÍ£…”9ç¯ò­=&ãË3]HÇ÷Q6Žw@þuWhV¹ë½Ó/í4­:üÃ,‚):‚Á×-•ô#®+§¹Ð­Õ¬fšÎT;•WÆ­ê#“ uÁ¯+¸±JhšÕ¶wÝÎc¶µ¹cº, Ìxõ ŽœŠö†`z8vÆÒGsÐþµéQj¤lÏ7zrºeÿ\_"K6¨a–èÈcFv+  ûÝi|KáùõÝI¥Ôî%ŠÒÙC[ÛCÈ›fÏr9ö®O[×5½;C’ GºŒ³ùyIÏʤìŒýìär3ŠƒL²øÑ{ËQ,â`>k,È?Rsø×uï´uÐø*ë~¾ÔtMJúÏHºÒ®´èÅͬãr4Фî9ì£"»¯†Þ!ÕÙ.±Õ݆0ž€pkæF·Ǽ£y‹ƒ†ÐW §/u¸tý&;™,RgEw|²ªã•1Èb†Á#ÓµŸˆ1èÒ.“áÛq¨_ÌÁa·‰ çsÎAý"§Ôšm+B³“^‘®¼AtØXb<$ªP ã=:×5-ÂÒøf&{‰,×ÒÒÿ´½;*ËÓ¯õ O[—Y¹½òâ yó¾î¼íù:•.EršÏ¨~òæòå¶%¼gpÇ+îN®{Fš[8ono.gmFù³o§ÁÎÂz½8ª7Þ2°“P‹JÓá2Y0÷2]‰ûØïÏsúWGá{i4mTêpãí1±ÚYwc#ÐÖzßSNš:O¿ß[¡½¸†Éna!Üç=r£¡öÍuV_ -tØ$’ ï:ý†iá/°ÁÈüêöã)î/ã³Ô0âf ²ÁV=3ìOÛI c©­-Œù¤™áÚæ—âýfi¯ÖÞÜŸ•ÒÝ^!ÿÇóÅsæo«oÛ9<Ü&? W´ë3Ò¬ B¯¤èÉ þ$ñ^iâ+û}Zt’ÏK‚ÀŒîœ—úŒG³aª’îr—cX•ɾ¾¹ð“µà1SX‡XÚ8"ÑŒÿhù²£*‘ \}áJÑ$­¾3²N9QÆ}Å5ا&[Ò¥ŽÒú+–…Ë‚XcãŠí©®üÅŠD)9 c³}+¼qÍzMš<ÌzÕ23IN4™®³€J)h Å´Qq IN¤Å0 )qÅ¢à%¸£\vb–ŒR¸„¢–ŒQq‰KŠZ(¸ Š1E-+€˜£´”\b–ŒQpŠ\QŠwíGJ1KŠ.c4b—b•Æ7¥§bŒQpS…\ChÅ;˜¢à&(¸÷£\¢—Qpb––‹€Ú1N¤ ÅS¨¢à6Šv)1L¢—¸ bŒS¢à3¸§bŒQp˜§QEÆCŠ1OÅ¢äŒÅ§bŒP(§âŒPqGìQŠ è´9íá·XŠer]ñúʹüqYº|ói¾0ž6É‚ä\ö8® ÂMSV=,¶*UÏFsÎ+'ÄË>‘4pÜd €ËÚŸý£ûÀŒ„g¡¯5ø«â »Ž ‰9@\.Aøˆýç^=7yÝHrÇSÊõsRÑõY"³ÕæcÅ%ܤÿ#]ÇÿSÄvV·ñ ºpcŽ:ý}«Ç“ÔäÖïƒb–_XN$ûL;O¡.+°àlúC\½Xn óU$%°¶1Ç¿ZóÝOPyZ÷Rº½d³…Ú£NW¯|N8­ÜÍ áò›çiYTž§ÀxÁmìl¬ô‹icrt¡NHn¼ýsè?J: #’ÔîVÿP0 LrÄqÐW]¥xºÚÇÄ:dí¡Çs-¤ÙnÍ”Ú@!znÁ9=ùú×#oµ¢Tê>\ŠßÓ|Bº»s"YÅ'Û" ƒòÉT‚ÿ?Ÿ“f‰Õ¤Ð^åçÓ´ËhVnT¼¿Q´ãó¬ë ^;4šÄ¦è¦\ 3“Ç=궤Ú{êÍ=¼ˆ‘†§>½¿ lmeu¬ÛáH„È7±8uÇÒ¡y—dV’öæêùË$Ú Éë€0) ‚G­Y—Ot`ÖàÉ™¾M£$žØõ®‰\,Ÿ~ð8éø‡ë_:ÞZMgw-½Äf9¢bއ¨aÖ¼¬E.IyÖ·µ…º¢ºœ•dùqÚ¢ÁÍ=N5Îu§a$œ¨¦gš•Š‘Žõ PL¬˜¹â’“u©ØžaÀÐi3($ú ¹•q €Œ{õ¤Ú[6ÊF•#y_dh]½­ÛMÌ“÷ƒÖ´ØØiQùo,JßÝ_™ÏåYÊ´V‹VR¦Þæ=¦‚m¿ì§'ñ=kG60’› »1Æÿ…fÝxÉ+iAýù9?—AYÏ5ÃïšFvõcSÉRˆ~ìv6®õØâÊÚ#ÏG€ïX—wOºyYý‰â¢4•¬aìŒå&Ä¥¤¥íVJÒR÷¤4Ä©"•“räìq†õê|K®n¾‘>öŠÙQ÷19œ¿Aøb½²1iáí&ÒÔîo*0‘D‡sÊã®=rsÏA_5÷¯FÓ>#¤:Dv÷V¡&†4Š'·Q’ cæ$ñëø×N¢‹ÔæÄSsJǬxOÃV£u&·ªÊout1âcˆ­A(_\÷»âŸ¯üVðΆ²Á¦]=õâ©T@çÈÜ;úúW‰Ýø¿ZÕDÖV®Öv—h!uÔ6îO×®;ÄŽÙ£‘Pà m­#6ò}ÀéMÒu$ÜuEÂ\‘JZ2׉|c®ø–w:­Ë” •¶O’5?î÷úšÓ‡TÑJ¶ÓÅ‹ê›,Æ%ÞAÉõÇ?¥aßÛË©jGoæÝO ª*‚Äô¥¿Ø]IiskpŒVUSŽ œpqýj'fÚf‘—2º'½¿IÇafžÅ½ÿï¦É¬ù/YSnã·°cýUšv`íŸ-OÌäs“T‹99'Þ²æ,²K]\,[Õw­ÂŠÒÓïîÅͽµ´±b8ŠFÀ?Z³‰¤Ÿˬɩ±¿óQÚ*Ž=óÉyÍSÑ4·¾ºyJihž}Ã〃·âx,†î±©ÄŽnì£Wfâ3ïëUÌ·š¶œ×W×Oöks‚€a9èI÷«•¤6v·vcq 3[7 ä~85kL±´¹Ó®tÉ ¬É3ŰWå#ס¤¬V½L È , æïvfWhlü¡}x¯qÓíe–(®^0]1ÎkÆ$ÒMÖìîexä”cl:Šõ-GıézlVñöÙSEœìÏñ¥)+Ž,Ò´Ö­´ÿZ‚¦X­7M6ñ;T~$WA¨jž)ñBbÓDºK2>UÜ#V÷%±»ùW?ðëÃÿÚ:¼o0/xšvaÀâL×´Þjº|&{ˈàŒ€O õ5¢V3nìðÍRÏYÒ²5-:[hó"aÓé¸pµeÃy FãîÇ&½žOIªî·Ð´¶½VÈiç]®y?•s:ÏÃ’Óí:|ÑÇ|Fé­ÀÛ 7qtüsøV‰®¤ÜàÍИ§?Ò¤òÎÐKG9Ï"³® ¹±»{k¨ž ˆÏÌŽ¸#üúÕ«+–•»œ“ÀoèiJ.×EÆ]Í>d ÛØ˜þeÏcë]¤oçC¸ûêóÅ£m´!†1]¥º³QÒº0m¶Î&ôš:p•}TÍX¯!Á Bºœ{WŽ|S¹†úæá£pÉ iGMʼn?ÈWQ®øƒcˆ¢–i!ýãg¥s6ºJÜèÓMpÝ6T7$úkçè;KSé+«ÇN§:›dhK€ª2Iößø/Ã÷:7Š´yoäŽ'’t³õl ‘žÃ§J½o§è~Õ$Õ/YbGÚÇÐzŽ?3\îƒâf“Æñ꺔…Ðy…7 „b¤'¾7b»ou¡çµg©Þø†ín®<˜f±ÇQÎs^q®N.¼ErÆA²Ü⺕ºšM>çU¾UŒ`² ê}³\0·iÂ* ׎[§AŸZW)"Kv7 åÅy‡J¤ÐÜÏ!ÆÙêÒ08ä×og§Úhút…̘¡Þì>ñì߇oôSûë)/bx”ýª(˧#æ^øÁãœzÔsuHÒ1W³g%hö6óí¹„É®ÖcÎÓê1WâŠÛ˸û5ŒW@d¸ ÁPÇït¤‹N¶hKùrNbúØýjÅœ×:’A¥Y¦¾ â7þ®|)³µ-½‘&ûL@|·Ž2# ÉÁÏaÇ­qw>Õôó›Í*ñîñ±˜šuóé²¥{…´yËoæä>lf·„¹$›FƒœSÜúQ¸'8SU¥½³€:öÖ<~eÖ¼)õ/ \Ê~է뱩þ(õÁ–¢û7¤9]K]‹ÚKXŸˆjëúßdq,·g´Mâ¿ÀH“\°vü«:ˆ¾ƒ êÂB;E·ô¯,ŽËÀjÙ—YÖXz-š/õ5¡ |.‹ýoöìäxåŠ_Y“ì?ªÁw; ‹>ˆ~å/§>ÐüÍyŸŒ|C§x‡Ykû­Œˆ¢G{7qӊꇉ>Y¨Þ¹¹#¼£üXÕ}SÆþÔ4©´û?[Àd\$¡ÕYb0¿Ö³©QÍ{ÌÚ•5MÞ(ó¬±=¿:xFîê?ÔÀSM†Û`ä¦íÍõÉþ”-¢Ž¨ƒë\Ni”h·©”È gÌð¤XžY"rzV«9V`?Ý\ÑÄvüÛÆ zA¸þ]){MÐ*E¦–”G+cÛ¥—e–I›¾NÕ•2}VöeUi@ c!?Py$¾îßSSy=Ù~Í.†Ç™eoÈGì¿ç5º´Iþª'»«þ5“´bŒQÈžábÌú¥åÂì3‹û‘£ÿ¯T©æ˜jÕºÐÓM4óMª2’i)ÆšjˆhJu6”P$)¦‘O ŠÑ%)â’™˜S€=r?>i´PÈn¦Yû˜‡ýfzWCecs¬›™íÑ„H¯,“…$G¸s׌×3© È D~úŒ×[àÿàÙÝ“K”åÑO1ä`=Á ŽõÓB¼¡î­™¦¥©Ùj?bðgƒà¾ÑÖ)n’æ ÒéÔ#œû dc¶kË“X˜}¨NítÛ¤fêIÎOë]fµ,W$z¥ìSÚÛÜ=×Ú…ƒ@<ŒsŸz­¥ø }sOº¸´¼ŒIm…PW‰\g·RÄAÂ<ïaRšœ¹c¹ÊÏ,+gåBÅ‹9vÜ0G`?R­ ­P´‡ÎžÒT‡qa.AÁçëTUK0P $ð®dÓØÕ¦·$·`²®c?º{×¹3Ið§Ã ; b0·´‚{ÕG Û@Ü©ŸO¸1þÑ®CEðõ¿„´èüC­íû^A´µ$rzûç¶}9ï\wˆuË­fòI&•œÉç©ø*å–»’Þ›:«Áy®Gsq4i§yg8AŒ…}¿ ’my­¬Öì­Îý¢eݱ°@ã×€ä8ÅZ¶ *I?3 Ëõýj„йÓ]k §\ZË©cXÐ œc¯ëÅ?MG’á®n¤Ò;rOµsštA¹#æÍz§Ã]Ý[í×øv–¿i›páœ}Å÷ägð­ck’äzv™,~ð´/äcÜõ?µzÊ"¢U ª0zRnÂI+h¨Š0ª£S>‚§Ç HLÁÖü=¦ø‚ ý¸ó")“‡Cì¡â¼s^Ðî¼+©Çên·‘öÃt£åocèÞÕî·$Œ8ìsU5>ËX´šÃP…eµ¹í=¨ô#­]ÚY⺶§™iöû˜ÞuI@1ÃÍøÕLø£áÍQ„rM%Œ¤ãmÊáï¡‘ùâ¼Ãâ^Xøª[yXb$;º°êß_¦+3[ߟުÁ9þ5+}Goªœœ~«ÔÒGÓ±\[Í’9ât#;ƒ‚)c^Û©÷WÍöZ­î‰(ŽTómØ«c‘U=««±º¶Õai,e`hïñ§S(o)åÑŸÚ=u )*^@ÄôAÍZqžÕáRÊÑÞÆ\wl‘Š»g¯j–^ÆöhÇ÷7Sÿ`ÞÞÕµmqªÞC³ù0Óî–ö¬ãY$m: »˜ºÚEy V01ñá7xÿœÖtZwÙîä¸P dGîŽ3ø×]kog ¯=ܱ¬Ñ©h£'ïÉÐWþÅ™äV óãžÕZÖÑu5§E_Qt«·ë=­Ò…ŠYK²‘ûA!b½AÒ9 ßß2‚¯=¶K©µk±‡ÎšÕã1;PƒÉ>ØÍz+XŸzôòåû·ty9›ýâ³1ï|5¢ÞÚ\Ú¾™kÎ…Y≔ŸâuªÞðêxw@þÊžX¯@‘˜bÆTž„WAI°Õßìá{ØàU&•®x\:^£áÞêÖz\—º}•ô–íÉÞ ä©œmaÏ#¥z…u}FÿÄ·ò?ÛÓJ¿„Ím ÷úÈÞ=ŠÀ³óqëÇq[ö_è+½Û ©D·0ö̱’}vìlzRÊDþ+ &Òȇ=peuÀÿ¾c'ñÍ \²½Î™Öæšè^ZŽXmçžÞG¤‘†þb¥ëF+­¤q&ÖÌÈ›Ã>¸9—CÓØúý™Gòª’øÂÓ}íØ¸]‘®‹˜©öpìZ©5ÔåáDŽߟì¶_¥ÄŸãBü7ðšŸùþõÄŸã]^)qKÙC°ýµNç?¼- èV¤ïîækVÛKÓ¬ˆ6ºuœ8èc…Tÿ*·E5®‚s›Ýžsñ+Ã>lG_µ\2á.ÑOt>á^ZɃÏë_K´QMÃ2+Å"•uaAê+ÂÎ[£—uɦ Á©ä^xÜvæ¹ÐôšÔFÈN:Ó>µ78¨Ý04&)"2)6ÓŽEjˆ°Æ\S©MFj“"HŒŠiò)*‘“C1HjLqL5fRC(¦P@áE¹È¤XÒ8¦b¤"˜i£9¡´QE2B¯X¼k•i6Ôn¬ö+­Ñêpø‡EºžibžöÑ&]ÒCå,ªO|°ÖÿŸí:eÞ£¨Œ˜žõ€Ž3ëµ­r¶ßm•Q>Ìóy„*%‰ì=MvúgÂÿ]®óáÙ#dyΑŸÈœÖ¸z¿šÄâ*Oª¹ÈëµÞ½$·×÷,÷ÄÚé…¥`wæºïxC_мÓ}£ÜAd”(dÿir+“‹kL›È žjëF1•¢îgÜn3¡©š7¹ §"’i<Ù™øñJлi³†ì»îIQ\ïrÐÛ‘ éÀc{Ö½Ãú¤ÃDþ϶ÜÏwr L€¯.ußn®̇NÕì?¡MCÄyc/ö(Y”ã!YŽü³ŠÒ,–{§…tEдX­Ž î|ÉÜwsý}+vš½)ÕV4 ’šzЄʲ¦ä+U'Y ñŒ´. UèJ¾â¨NB9RH‘ƒWЄ|ãñ»N6Z|~îö”{6Ÿýõ¯0–=¯•é^Áñ¾ýuvÒ58ã é`‰»²§'êw~ä- "¤Ð|mùR¯› <¡éøÆ“t–s¤öò²àåNÿëÔ$Óâ ’Ž~VïèiÞêÌiÛc¦O&£n°ßŽ`×(á¾£±÷« \|©é´ä\k£FåX`޵©£ê_go"VýÓt'øMrÔ¥Õt«ëiLr*€Á«Ö—7Z|žm•ÓÚLÿħäg^‡ëY¡XãëÁ­;x„ñ\þð ÉÏ_QXBr„®Ž©Â3¤lYüRk9¾¹fñ¸þ(Ð^1Çá]–‘â­[À²¾¿å›­ùò»»8.áû=Ú±ø\}è¨öö®GPÓîô[°¯–ŒœÇ* ú×§G'£2ððñ‚Éÿ¦Ûf[sêqÊ~?κ U89¬êAN.,Ò•GNJQ>g•J“AÐúT ?zGÄ }Š÷ûjÕÙ®[lè '¯Ðÿ?­yã¡áNœœYõTjª°SD`gHÉòôæž‹Ï|Ó€ëØT\Ó¡PŠCÒ¥e9éLÆ;U¦ga‡ša1šWñªL–ˆZi©b˜jÑ“COJiéO¦š¤Ì¤ˆÍ §w¤F=E´QH°¦7Z}1¨¶JêÖ› 7:„0Ü;G¸RÊ2EQšWeZr3#«) ©ÔšöÕ¬ï$ˆ; Ž„zÕz«3¯‡Æ6÷¡W_Ñíõ)¸OÝÌ}ÉÒoé:Ö¯¦Æ]í¢†C†¾;£^0¹ÆN:{{W ¼—¹7l8êDc`zV.”9®m“H÷½2(üUe{¤ÆÖhŸË·ÊM¿‡Y sô®Ïá÷‹o.®î<5¯0þÕ´ŠByž?\÷#׸úù—Eñ&¯áéÌšeì°nûéÕ꧃]Œ¾0žôéÞ%Ì:…„¡nQp ʸïƒó3ŽqÞ°µJun¾j¤ªÇ–[ŸFø®Öy´yç³]ćk'ÞÛß|©ã#ÔuS;ŽØ%$1( . DZ×ð¯ªü1â?ÅzRÜÙȾhQçCžP‘ŸÄƾqø‰¤eøÃY°xJ[yÂâÝ‚p†qôê? îNèã³NÌó©bxd)"•aÔV†”þbOjzH¹Q[ i£dmî ¡áfÇ8ãƒøV]Æ•w¥˜î”¬‘ñœãØŽÔ€«h|¹ÌO€§(Ùô?ýzõ/ÚŠé^3Ô4÷%dº·ÄyèJœ¡Î{W™j±¹Y“••CŠ×Ñ5‡Ñ5ý+^‹8‚Uópz¯CúfšöT3 qƒéSf³l§7pÇ"mh$PèÃÐŒŽ~•›âè¾µY5+’fqû«h†édú/§¹â‹ -%x}ÿÆ/ß¹]3L³Ó¢?uîØË'ýò¸Ö׌PØ »Ï=¿.õíš}ôªÏ›•º‚0Aï‘Øçµz´êsÇÌñkRörò-š):®æ"RâŠ(¢ŠZ.QKŠ( E- ¢ŒRÐQŠZ(¸Ä¥¢Š.  ÑE¢ŠZ.IŠQF) 1IŠv(Ř£´P; F)ÔP+ ŧQŠn)ih¢à7bŠ1@XLRbEa¸¥Å.)1L¥Å´€n)qKI@)0iÔ”ˆéh¥¨‘(¥Å hJ1šZ\PŠ1KG4\,&(Å.)qJà&(¥Å/J.F)Ô˜¢à7´¸£î1(¥£®QKF(RRâ— J)qF((¥Å\,%¢Š. RRÑEÀ))iqEÂÅkÛ(5M>{ ” èU½½ÿµóþ¯¦O¥j7#@ûOxv#ê9¯¢€æ¸_‰žûfœšÕº5¨Û8–±ÿ€ŸÐ×64yÖèõ2ÜO³Ÿ$¶gŽ‘€GLSÈ%8²('=ýhNW¼ƒè!‘03Žj ¶ã¦9ªäzUÅÐÐúR6=Bt‹Êb²&0¾b†+ôÍ&4UÚiñ¬‹*läw©šeU6%,ºŸ h+9‹R¸º‰•~tNNF~ðíÒ¢SQWeÂNÈõM>èÇ > 4J'~l þµ¯§£$'÷lv¬+]J+;‘¿ê9ίò­¨u*7ÄÝ@éZæ½ÏFÇ5qrñKåHC~V5ZæÞ-ZÁ¬e#cœÄäsÓ±sÅ‚ÒÇÌ.2¾«í\Í®¡åd“Æzþþª”ÚwE4š³9ÌMe4ðȤH»’HÏH?äפø;Å÷7¯d»BÜF½ntz°è}G5Éø¢õ Hõx1ç&åGqü/ýá\Þq¦_Ãyk!I¢`ÊG­wÑ©mO'Kx³ê;;Ø/­Öky)üúбX~Ô¬5ý.-BØ*ÌÊ ª‡œÿúò+p ûቱ{×´‡Ñu»­6LþéþFþòTþUÃk~5è?Zõ]6EÀ¸òH“Ýsòþ?z¸9tsžkçëC’mW†¨êÒRdgæô ”wíøÒ+4ÍZ çŠ1y§¾xãŠa´¹,ŽAŽ*Ö§~™­"e!‡Ö˜y©4ÜU£ !”q†ªä46ÒÒ5|=g¡«ÛYÎ †Y>pi )={WuÿF†ÃˆnW>—ü+ðTfOAŽª®ßøî?­z C«UŒ-ûÄ0'§¡çò®ŠQVÔÂwèrÞ)Ð,4o‡³¤¹Lù¬èÞ€WMzgÄFeÐ-Ђ7\ÑMy•DÒRÐiè.x¢KP4!ihÅÑÔY°ÕôÐ’ŸÞÇòïG¡®³ÀO¨ý³ÃÒJ>Õóí]Ž\üÃõæ¼ÿAºúŠ«œG/È¡ü몊[/U·ÔìdÙunß.áÃ…O±ƹj+>W³;©7()-Ñèmcw¥Áë+C=”Ë? FÇ/¸àÄÖÿ‰4¸umΉq!eÏÝ‘zéô#Ö³?á+²Ôôû;‹ˆ®ÚÌÅ<„(Ž x)ŒäÓ8ã9ï[ºKl…¬¥bí“ gø™ ø¦ÓŸQ\5!ÈìuF§Á¬ož',žÇÐûŠWK–'ȸ+ÿLßùs]7Ž4ăVÔm@!™ÔCÔãòÉÿ€×3g,s[4W(Dˆ•¿¾;gùf½J3æ‚g™ˆ‡,ÚDÄnà6`"I¿qÇצ½‰!kku_Ý…hÛ=ùÿõV]èû=ë ¨ 笭Ü×ñ"˂Ѱ;ÇzšÙÒ+[ÌpNå9WE¤kQéºl²íÊArBúdŸÒ³u(C4|¯LúŠØðÝ•¥õš˜í฻BDÏÀqž0{¨î –ßÄnרÁ$ñVÛAÒõukˆï¥…ˆÏ–¸`>‚¡ŸHðýã:o“H¼VÃE)ʃøÿc\C.…6"Ômfn£È’GåùÖ—}I²¦ØØ‘ßN$þì¶Ä UD1`!˜9=!$ûsZÐxÎá-Äk.?½ƒÎ–ïÅÊÚ‘ý£Q·é€(Ð:\vú|Ž…ă回99¸®ïDÒ­|]ðÚö΋ûJÖ?:lop¹$3ê¾=kÉÎ¥r÷©rò‘0ÏJôo†>(}U»²±Óc¼¤šRA‚<üÀýG©³»RÐÙ(Êêy•ÊF³ÊW=~•|UN5‡U»‰>âLê¿@Æ« ™=LÑ,£${ ˆWpšD:·„à˜F«x’Ž¿Ä?)üq Òc.i‚ûT‚¤BÃr#`•ïɯdÐ|% Ø\iÏmÅ7«y¡ø$ç cn0G§zóÿÚ¯ÛeºŠ»:’AàŸJî4ψÒéíôygÔb#b¶Wqõ‘èkDjÚ>É^åÓq»Lçu½3QÑ¢ó'I$€¾Õ™—½1Þ Ó5&WEglÓ<èuK/xž#.¡uµ©‰¦±ä€W;U»“‘Ô“Šá#b„p+R5ïîwR©Ìz(ÙªiòXÊFð„¡ôÇÿZ¼ºG0ïŒðCcJêìµr…_yFL ¯CX>"H†©çÁ.qæ}z#©sЫgtcfŽOšW_PzÖEÍ»ZÝ<-ü'ƒê;¹«ZŒçJ†õqæ@|©Gr§•?Ì~U´ŽjѺ¹Õü*ÖLz”š+Íå5Éó-¥þì€r¿F§°¯h´äݪtá×·±ƾZ²»–Âú ¨X¬°È²!ˆ9¯§-n×WÓ-u[` ‘qüJFJ×£BzXò10³æ4¨Å67Æ®½dSë äŒQKLÅRâ—®cš)qE–Š\PRâŒPQKŠ1@XLQŠZ(¢ŒQ@RþJ)hÅKKI@R \Q@XJ(¥ ¢—bXJ)qE(Å--6ŒRÒÑqÅ.)qE+€ÜQŠu%;…†RÐ.(¹"QKŠ1EÀm-.(ÅKKF((¥Å ¢—b€Š\QŠJJv)1@ E;b€KKŠ8 ¢Ú’€ŠZ( E-˜¢–ŠJ(¥ Å¥¢€š¥¬ji£iRßI“n#]ɯâjö>jó_xŽH|Cht.$SÑça„é‘úÖ5êû86·:p´=µEO=ñ…ô·:¬‰y9šìHL¬>ê·u°ð¬ë)Ã/–ǧJÏÕ§yõ[‡’A#³’Î?‰»ŸÎ›o!×5åÊQ»Ü÷)ÕJ|¨ØaƒÀ¦ÅE,,ËŽôæX×&«Fwo©®8ïQ°ÇQV™O¶z…Ó­2Z+ºàgÖ¡aVYqŽjÖ‘fRD;i©HãšiV‰™4FE1†jN1M"©Ñ¥;˜ÕFLhbAÁõ2ßÞG÷.ç_¤Œ*)*‘‹'¸¿»»EK‹™¥U9I þ5^Š)(§Rbœ)˜¥Å(H» Óœò+³‚quaÿyœaøèïø×ŒŠßÐg"ÎX{«ï˜"±®¯›áß,­Sm;\¶ŠG?c»G*“Âç£xÏJ÷y쎚ö×µ’UtŠs‡SŒþGé_9^¨‘0½AÈ>•ô6™4z·€ôËå|«Æ\„­øîÁ®JéJ•ú£§URÝç­ZÏR°Õp²#×#§åº¼ÆvKsm(`-¸‘ú0¯hø—ju/Kt£2[ºÈßUm­ýkÂïef³€cøËgðúVØ)^™Ë‰^ðºœ&+„ÝÈúŽÇò­-.LÑ©V¦0yÍS¸V¼Ò ™~ò-¾ƒ‘Y‘É,q²£²«Œ0­vœŒÙ–îbdG` £?Z©¤êcJÕ’æ Âíu=Jžµš(ïBzˆõ+í+MÖaI¥Y‡É4{¾µÍ^hšŽ’Ak4Ô,—Ly {ã‘Oð®¡4ÖÍf“–.Tm)íô5ªEÌr³µÔ¨G#,ý]k£I+‘±—nš8‰g¾ðÄÑBN<Ñ+…'ñ"«O«éqÉÿí$ÁáäRß¡Íny®<“Is>>ûå±ôô¨î¯æŽù‰byéO’ÁsjwLÚlrDÇ|ŒøZz4æËT°žÖ?³ÛÈæ&–*Ù9=ÎEe^ë2Ÿ"?¼zœUø›ìšm¾î^9’CßÔéXÕvZXH©Nϳ9ÿ[›m~þ#“¶vëèNk9zŠèèê1YW’qFÔ4Žrâf¸íØTw}¢ÔÆPçð5¯"¦µ@Wk}Ò®U¡Üõ) ÂÖ–ž<襷o»:ÏôýqY‡—Ðâ´-O–¡‡¨ªnÚ™µ}  1Vp~µîß 53yáVµfËÙÌcý“óë^/­Ãäj²»D˜÷†kµøC©5¯‰%²$ywqr1üKÊŸæ+¶ŒµLòñ¼Z=¹s¿qŽ@þéÿ —½Bç HíÍL9ÜyŠZ-0”QŠP( QŠZ)\v (¢€ŠZ)Ü,%¸¤ÅQKŠLQp )qG” J)ÜRb€ŠZ(¸ F)qÅ\Å--\BQKE G4¤RŠaa(¥£„£4¸£ÚZ1KH QN v”ê( ¢–ŠÃ-£È )qIŠÁIKŠ1@ E:ŒP; E- ÂQKE°”bŠ(‚Š( V (¢Ø))hÅa(Å.( ,¢–ŠÂQE‚’I@XL9¥¢XAKE l²¥½¼³Ê@Ž%.äúM|áâmJk§y$$=Ôu&G©;Gà?{'Ä+ó‡—o;Ras€åÏ¿¯ÕîÍÎ¥rÅv®ü*ú(àʼüKr¨—D{.Zt%'¼Œç94øæÛëQT;\‡°¢ýëš0ÊclŠÒŽU™G=ë%HÅI²0 ×4ásÒ§;§ œô4ÇZHgYÒ9=(9ä{V³:/r¸¨\`U“Ö¡qžx­"H‚šÔö¦‘ÅhbѦâŸM=êÌÚx jW5 <ÕDç›’Š1VbÀS±H8RcŠ P--– (¢€­m&åÐs5”kè»K,¼ìT*O©=«:Ÿ 5¥ñæ) +Ôþê¢÷AÖ<;+~ñž} ?ÁükÊf%büjÿ…5÷ðߊ,u5$D¯åÌ=Q¸oÓŸ¨®x+èú—G±<‘jV—V€a»ˆ¶bFÙ×85à:¥‰´Ó¦·â{;¶‰‡¨ óùŠöý]Åž¡8Œçl¿h‹OÞÔs^aâh£‹Å*·¹¸îÀޣܑ\Ø6áRTرQRJk©€%†$†8’)#Rè[wÌÉþuŠÀ c äUý]VÖémà˜M#lr€FáœçõªBX’«ŸQ^º<Ö+[J‘ v-º68¨kJWɱû7’æÎIã銫w";絓z‘5gÁ×-i¯ÃÇe)û¸×jrUWŸóœšê¬´û[ŸÜÞË'›ÚÈ£±EÚ¼õ##År_¼BnäÓ´ÍÌ\!–Ve+¸íýk˨ù¦ýOn”yb½Æ)1Š—½FE37 Fù«g𤤆«Q>F D‘ÑFWÑ’£”`A­¤.îøæ³H⟦6ÏcÆ+Fç\ebã“Q°T‡2­ÅDÃo|Ô"ØÂœœô¨Xb§ÜH9ÉEh™› 5On3UÝ5¢G<åa³QÐk_Ãú)Ö.ÈvÙo Œ:œôÞµŒnìŽIJú”ÛO4Ô¿u+ Ë妈ã$ÕQÒ»ÏGz ”p¢¤QM…Uì6šáªœy]… u RÒâŒT$–ŒRÐU„¢—4£¨Ïº{5’ÚÍbHñÆ[Ôžõ‰§EæÝ§Ë¸/ÌGÒ·wìc”^ONõËZ]×F6Ô¡4‡åü¹ëUÜ©uÈê*KŒyŒ¸ÀÎj¹aœRŠÐ¶wþ×Uд²fóN_,ç«@xVÿ€>„Um«R3¬w0ŒöÏSø×ow>™å³mu=úЃêâºwöÛ«<©ÏÏäÇì}G½EZv’©!+§NG1}k5´žTë†*Àä¡ïYçŠï.쬯4—y­ö\ñ·càO$ ån´yb;¢>bút5½Df¬ÎJ´ZwFh¤§22} 2ºNah¢Šê¼?pú“ižb¤ˆ2…†A\óùزÑRÚMÒÌf~xÆÕã\­Ì¶w1ÜDpñ¶F{ûjî¬u(õ<ô™”Cø~¦¶¦ÓÜ–‰Lq«€qš«"‰®-"QË]q×¥Z2 åFïŠM&µj –òÐÈ}™ºgó•e_YEØGËN¤¼Œ¯ˆ)Ÿ2§'Ɉ`»TÖÖôøÀƒtã$¸lsõïV5çŠÿÄšËN¬¢8Ói$Æ}±Šlv€È­÷926Ð.qJrIŠ…=lK­Ì-nbºhX­ä#Ìà‡óëÐÕÛkØî´›˜‘\%Ý£++<èùÈúŒÔZç•w¥Amjâg‰òXtã·¥E£A%†žd¼B±-Ìdsœ+«+!Nu£cI´s$æ” Ô×v²Z^Ko"íxØ©ØÔ“Y\Ù+‚aMoEw,ž{Üa !çî¶pËB~£Þ²dˆl вÄQ¤³åU~èÄk9NÈÞ4ï£,DŠÌ ƒÀSÖªNéžsQÜÌóÈ]'·¥Ufù«% êm)[AêxIéV«GÉç¥ZŒn"ªZ"»ÄvCþ™·ó«>‚;ÏAa1Äw±ÉlOûè@ýqU¼CÃZ/¤GùÒxZçì~*Ò®DºŒŸÌVô~pWþ#='á|ް]Y’†E‘# 'U#Щ‘=«Ôm‹Æ|ͽÃï^{a¥I ø]¸‰1 wñ\Gï€îø÷é^ŽÀ¬­´ü²þð{0ë]ÔÞ‡XêL4´€‚2)k[˜òØ(¢L,–’–€ 1E- Žh¢˜‚–’ŠZ)( vŠJ(RÑEQEQE°gŠ)i( -b€°Rb–ŠÂRÑE (¢…Q@Q@£½-RÑ@ÆÑ@Ðуèh¹6bf–ŒCF¥; ³ì%¸>”moJ.‚ϰ™¢—iô4loCJè|¯°””»[ÐÒìoCEÐr±´S¶7÷M[ÿvŽdŒnh§yOýÚ_%ÿ»K™É.Ã(§yMéKå5È9a”fŸä¿¥KúQ̃‘Œ¤ü*_&LÑä?¨£™#ìEøRÔžCúÒù ëG2g"**_³·¨£ìíê(çAìäEEKövþð£ìçû—:e"*³ïÑöcýê9Ð{)QSýŸýª>ÍþÕѱ‘ >?õ‹õ©>ÏþÕ*ÀC›½Ò#öRìs+{¤\ÈK^Ügþþèkž"Ý›ŸjK»+nÂÝyè`þ¹¯Oøu¬Ggâ-WÂÓ¶ÙᾕáÏGBÄ‘õÖ¼_Äw?kñF­pD—r°>Ûy{Ìõ¢ýÔŒâ3ÍFEL:SȦ™¤£r1N°iXqQô4Ì~^F'ŠLóÍCñÍIÁ¢ÇTgtZ·›iÚ~é©äÛØƒYêØ"¤iGLÖr†º©hLçmBÇ=é­&}ê"õQ‰.hWn*³sÍHÍšŒÖ‘G%I\`®óÁÑùz$Òªw”ð=€ÿë× ܑ׵wžI£ÐXºŸ-˜Ý†{}k¦‚\úœÒM«!ã‘G÷•@à‘ÛŸå^̰|±¾}áŽÔw­)ž=NÖHKGæ¹C·§§åÍ ^JFЪ㌮§ ‚Éž¹Ûó~t‚F™wvqÑiÛãþ÷žã­1'†6ÌyÝY½Kæ’V¹·oû¥UÅ\¼¾{<éñüÆFG#— ~­ULÖqZ«}©àƒÍE Éö½iöç_Ëø‰»&Â6ºBø‰þÔÖ7epÒÚª1õd%N+%#ÑxŠŠÞ0S ö†dofA‘ù¯ëX1/̽J•à™Óê]†tÝ'Ý&™<ÆV`ô©&`¨‘/_¼H¨fȘã>µ%ªfÌV‹C6‰r*ÔçiÅ\·_ž³›.(ÎñÍìké ÿZÏ´Åu ƒø[ò5sZo7W•@ûŠòXÄQ£8á°G½uSÒ(ó*ë6}-}Ê$”ËͲÇížHý iÙJn4›iXb@ŠHô#‚*;f¸Ð´×|ÿgˆ°ô!EhXÚˆ xûl~<ÿZìRHä”[Æ8ïȧSÒ‡ …qÞ«ìÛ ¥©ü”ô4¾JzΟ´Aì™^гäÇéG•¥Ñ²elQÍZ'¥.Äþè£Úز¦(Å\ŸÝOîŠ^Ð=‰Nй…ô¿/ ü¨öìJ…>†®`{QÅÐ~ħƒèhÁô5w"“J= ½‰P+z67¥\âŒZ^Ð=‚*loJ<¶ô«yqG´±EO-½)|¶ô«\QÇ­Ð=Š*ùméG–Þ•k"Ž(öŒ~ż¦ô£ÊoJ³Å¥í{$Wòš%½ªÆhÝG´cöH¯ä7µ/’}ªÆE‡¥ч²E$Ñäš±¸zQ¸zQí{$WòO¥Q«qíG´aìQ_Ê4yF§âŒŠ=£bŠæG”Õc"ŒŠ=£±‰”ÔyMéSäRfŽv?e¢–”QQs[!1E-]‹• KÍQv; ¤¥¤¥p²h¢’Åo!h¢’•ÇahÏÒ’–‹ŽÁFi(¢áah¤¢€Š@ihQE\aE%Z(¢€ )(Íai)h¥p rõ¦Ð4Æ|ÿâ=A¾'ê§NkK¦’n2áð?OZóù]¤™ä“#¹f'ÔžZúrK {ÝêGÚ/b xÆÕ=Éê~ž•ó5Â4wSFà ²2‘î s½Î…g°Ðh¤^´îµ&¨iZ”ÒP'ÈAÁ©f‚¥3•4Ìõ‰2œÔNHsÍ8r)ËQb¥-î⛜ÒsGZ ælJFÍ?Ò)“$3¸ê^çÂÚŹeC‚å ç1ÏáÒ¼´ Ò;ñ^Ùð·Jh´K‹«€\H >òFz} ùÚ+C§›x¾ÏU·ñ%ëê´sK+6à§c)åvžãü«×ÓZõ‚x‡Ã÷šlÄ9Æ?Âã•?˜óL±µ‡Ð¾±§©¨ˆã¯Ì*š°-FjvŸfÔ.­ÇH¦t@ÄViMmë2 uýA‡{™ÿ¾R–&˜eTƒÓëX)3§Ø¢5Ðø% xž(ÏFFò¬¸à1(•Ô…'ƒ]€âÝâ)'Û‘dþf³¯%ì¤Å¸´uŸ4¨í4-.ê5`ÏpÈÃ<}Þ+Ï¢yü+Ô¾#η~ ÒBŽEãüp×”»díì;W.Z(ìƒêJ’Ç’iGLb¢šx'>Õ³4@WŠh^õ)Ž)€hL T` ¿efÔÕç­hÛRLåšçéYL½‘ËÝÉæjWžs#}*ëÁ%Ÿ|Á©Ûž0@8ýk20ÒL»~uÑx–%·Ö§‚7ßÙIî6Œô‡‘»lúÂníákÜ壈FIïŽ+b2rÇÞ²|/ÿ ;bNw.ïÏšØÀ²ØÍî8 Pæ›E?y¥ßL¢˜ßFúe-;u©´QpºÔÊ9 n¥ÝMíE ºÆ’Š.ï£}6Š`;u.êeýÔ›©¢—]Ôn¤¢€u©? (‡n¤Ýõ¤¥ 4¹¤¢€4dÒb–Ã&Š3Eh¤Í-£Q@-”´”Q@ Z—ÎoJˆRÓ$“Í4y¦£¢y§ÐQæŸJŽ“4À—Í>‚4úTy¢’y¾ÔyžÕ€“Ìö£Ìö¨è  wûQ¾£¢€$Ü)w ŒQ@É7Š7ŠŽŠ“p£põ¨éhû‡­‡­GEI¸zÑ‘ëQÑ@dzѸTt´~isQRæ€$͵hÍIšLŠe€áõ‹×Æ7±ÉÁDb÷\rGãšñ/ ñF¢aZRëÿ澓Õô+ n$[È›|y1M”’?¡ôöØ©”í$»‘èÙÎYEÍ(ã •ú殦”“YÈðÊMÒù8ÎõÿgÞ³mØùʹÆò?zEªE£iör¦@Ìà Çëý+ddpgNÕ,cûA·š=úË­K&©4ѧ˜‘Œ0þ•Ù^LeÓØÉ‚J‚Ìp—霘þ^ÇÞ€ÕÒ´ººó®š( O˜FÒ ¿Æ3üñWá{câ+ƒ¬y„¯N9~UÌ[&A-•tqšéü5j«~Ï·~"~?!ùriN|°eS‡4Ñ:4÷·RM*ï21v€ ç­t6ÚÁ²EVï*Œõ=éòj––¯åFâ~ÐÀ7Ìp)‚-wT;[ËÓà<|¿<ŸŸjñêT©-ß*=UË3+Ä‘éÖÖfÞQò½Yð¶ÑÛ]c?iÞ7ƒýÞØýkfÛÁºLp*Isr~óJÝ}ý*Ø´ _ÛêH '˜¤‚!Ÿ4ÈëÆ9¢5!({Ûl¢n\̋ƃËá(nÇy4©Ïð´Ö¸0OZ½¬jÒë«# B»-á#LççÔÕØWl)¨ADp¹ ÎjDÆ)Š*AÀ¤ÍêoÒœi椢hºb§Ôdò4Yë#þ¦£r@¨LJ¤žž”ÁŒ{ÔŠF+&ÎÈÅIiQzH»Xc¯ Œ~"ª2“š¶zT sI0”Pe|„BWïàr;sB‘ŒSZ*Ù)Xvrh8ïMíHÇ•Џ„ÓsHÍ@ª±›wc‡Jzu¨Á©S­&R'é ™ª“_‘vÙŸVª$sSÍ Œ%WPà£"¬ÈÛc&ª§w¿jÖ'%gªFž‰]xŠ;XÈ[~)Liÿ.Ñ¿ŸRiÞ°û=…Æ¥ ù¤\CÛ¹ÿ>•‹îTZÚÚÿùÚ¹ùñ .…EZ“¹Èµô¯LÑl.¼M©[ØY¢ï1¬³O)>\IÉÇSí޼ν£át¶–0¢jR"Þy2>ÕG½pÔŠï9 [Á–ZdH†úæúàŽ#0íŒã=Bäãêk•¼ðÞ± ÈžÖk(vÁDaúäs]Þ¿â{‹‹™R&&F7!.ßQœ(ôÉÏÖ°ž6¾Aö›‹ˆÑ±¸D6c×§'ó¦6»aAn@¸‚DÝæƒˆK“ÿ,ÊŸóíWü!c ņ¥o'̹ˆ\±#?P+®¹ð\úµ±‡D»·¿u]ÞMÚ˜g_R§£~#ñ¬¿ éwúUÞ¯a©Z=µÂ¤NRO¼Fâ21Áî=«EÕ6ÑtŸ¼[ŠÞÞÉ6Á .:…¤Yg•°©±G|V ´Ltɧ%ºƒœWι_Vz ØÏ¹¸M3NšîâC„\òzŸAõ¯)¾¾›Pºk‰˜’~êÿtg¥t>7ÖÍö¡ýŸ£Ûœçï?ʹPkÖÂPöqæ{³6ù™ §ƒÍ0)Ë],Õ©Å;Šnis@ š\ÓsFhìÒæ›š3@\vhÍ6ŒÐóFi¹4f€š ⛚3@Åͤ͠.;4f›Fh ŽÍ¦æŠ;4f’Š\ÑšJ(sFi( Í¢Š£4”P!sFi( Í¤¢‹š3IK@.G¥%¹£4”PæÑA Å¸¢•ÂÁIKF(¸XJ1N NÅK‘J7ŠP¹§m§ŠNCQŠ1OÅ4Ôs(ÅO­:—s‰ÁâŠyÜU©â%(ŠQÍÀ¢&Ú1NéIšNEr†(¢—¹ƒ”J(¢Å`¢Š)¦Š(§rlQE …‚KŠP¦—0ÔDŧâ“ÅòŒÇ4î”cšZNAÊ4ÜœõÍp~2øm¦êÖu¥Á Ž ]@)òÅ)b§'¨ß ྠxÉü7¬h–í{6sq:/ ûH öëùQpØñ FÆçJÔ&±¾‰¡¹¶È‡±þ¢«ã=ë×¾+h¶z÷†´ïiL%vù7 æHð[ŸuÚÙϽxœs²79"§–檵ž¥ò§ÔÌ ñK‚EÏjyŒŠŽ…gªZl‹˜~4öPH ¯¨¦˜IhÑWN¹6š„ÿÏ7ý;×§c'+§¡ÍyI\zôè^èðJOÌ«å·Ôõ±\¹„o1eò´Ü Á+ñ§“©%ÀMÏûÃúb»|sY^'²ûf†î£2@wŒzt5«ÉY_©Û‹¥ÏIùsнlwÆqTˆ©í¤Ê÷f®“å‘puÅ(ÎiÍ´Ž´ÒO÷±X䃕ëÍDãŠD%9Ï53&qÇZ[r Óڜ˃Ҙ*Ó$ZÍHOjÛÑvNM*“šAOV# 5FIŽÆ*t^3P ÷©£j‰@µr?ДûФ8õ«’Öe}óUxÝ׊ˆ½ ™Vá¾r?J×Ð4 µiT¸)j§çsü^ÂªÙØ‹ýJ8W>Xù¤>‹^‘dR8‚F (èàVxœO²£»9T9¥v,ö°ÚZ¢Æ@Šó bôꔳƒò“…ú ìüe¬k%±¿y0Ë{/ÿ^¸;N¼Õo£²±·’ââCµcA’Ï­N›åu%ÔŠòû(‚ÖqsCø›“]n—¡Ï­ø‚ÒÂÕ¤Ë0PŠzñÎEÆ&½+Â_m­mVÿÅ®’¹­à`kÜ3úãÓó¬_Ú]E¨ê7Ö#Ê3‡·³vü˜ÉÝ¿œ€9É5ès$sÈÈ|]u¤èÖ¿j‚*ÒF}rùqÈ<œžEm^h"ٕƦ°Èx "ŸMÀc½]°°²ðÆŸ$6ï(ó|¯#’Ò·©ô5GP×®2 Æv…eûßðÉóIÝ;FQJÍ\ÊÔ#Ölá;ÚVŒ ï·ïíõ¬Öñc¢¥Õäžuͪ„S¸³É™r}8aé]=˜¼ s-¼Ez:ìEÈÉ<ô ýAáK 9/uM`[ƒÛ!FN°†?ð/èh4½Ùj‡%¹‘¡i,wVÑOnŽE ¬;ƒY~-ÕãÑt)dŠERŸ.!žA=Oà?¥pzÕÝþ¨\éÏ*[[ÈÉhÇ3•÷#³µ8nO±½û,‘ÚÊ4‘ŽCȧæþb¸#µK·¡³•Ñ” 9$’O$šQINè ÓÅFµ&jYªŸzv¨ÅH:T2Ö£Ðô«q ¯N*²u«Ñ2D­;Œ¤j\\t‰Åg+·bôJ죫›¨ÊÛÒ}OøqYdYèGãWá“u•†&šëûmÛÈ?‰´úv ìÃ|j…Fzåð~¼:ëŠåI\åÍ'&P!óéÎk×õ[v}SA¾UÊÅDàõfÁãó¯ NXç€Å{e­±‡·Ø/*Z,ŠzgiÎ? UDˆˆõc͈7â?‘ppXzóYÖó-Üz}êÿËDãþ¹þ•£ß5¢$QES´”´PKŠAE-Q@RÐEPEPE¢€ ZJZ(¢Š(¢–€b–Š(£4PF(¢€–’–€ (¢€ (¤ ¤¢Š(¤¥ Š(  ÑŠQEaÌoÊ7 ZZ|â䢗 S±PäZ‰)Øâ–Šžb¬% SœÒÒäR¸ì&)1N¤4\,4ÓO4¦’„ÂÂÍ?  Z®byFâšiæšEO1V”RRŠi‰¡i)ÄR®bl%P9íM1XJ5"Æ{Ô»@Å`¢W iÞ[TØ¥¥Î>B„~8§ša5›‘J#i)I¦æ‹ŽÂ÷¤£4”\@Ä$níÑFMx_Æ•õk6õ† Àè7ü€ü«Úõ:]̇;U ::×|Iեޥmu˜Q¢xU¾r Êàc±ëYJ¯-XÇ¡JÍœ^Ÿâ«ý3HÔt5—̰¾@B98úî_Lò­s0Æ´g²š[q4HÎ#s•SÀdÕ w¨5ÖõF.6vbÆJô&¯Á*ôqÇ­RQƒSÇŽõœìΚi¢yãÚÁÊš€ŽjÚ©x{¯"«²ñ‘P™³E —飯éü«uÆ>”ÛK—³¼Šá6 +IÁT¦âÎ8ÉÒª¤EþÕ‹8Xn_ýØOõ§G}q+„]:ApÍ+*ñߊ³Éq s'*ê~5( b¼8AÛ—Uæ{iJj÷<·Pµ’ÏQžÙ×kFäcùTb8Åoø®-šóHF¢¶OéYœ:ä^ì*sA>çŽèûíÆE–=)èi9#µI~냡ž;SÌáã Ó’£UfûÝ)¥q7bÉ皎8©bj¹n´!·aÄÔnF:sJiŒqVŒe!«šv›s©J#¶¤mÀqêj™Ç\×§ü?û—i𥩓íZ2GÝãƒþ}iT—*¸ ®Ì[Ÿ & Žîty@ù¼¾vë\ýňƒîHŒæ½ÄÓ›É ífPO 0ËùW!r!–9 ¤ã¦+†YIêvJ)# $Ì.§ð¨¾| œãN_¹M\‹¸n•ÛsM»š>èÓm°âVû¬ {Y“ÅFÊ-žI’ð®¤mT#Ž}k%u+&ò)ìÈûH?+ À÷«Þ † sU:Žœì%ºA%Ä!S¸ù€ìA9#½déBOš¢9å6´F3Þ>©~ÓÞ¿—Ú¸ãÐ`qøÖ½Ž¡ö[J3Z³ ¬Ñ7Ï·Üç?–*•Ž›}§]ý¢KvØ8l7P~•Ò K+ø(§<ú0üh«V0²[yœo¹¥/Š|G{ &êúIíŠímφofn¤{µ¢§Ú¬Ú "Óïcå¥g2«?…ºtôük4Ëë@ µÀ’/ùå7Qô"¯Z_Ïc:Êa(ëÜò?:ä–&Káw7ö1}…tkˆH“P’úéñÒ)>Cù`ìMA{âGJ?ñ.Ð’0×|g>ûyüÍKk¬j·dyI¤Zð½Ò÷!>ˆ?­rçk7ó4éFîæX‰Ú*+©åÇ:byjî/Ø•=Ú´žÓà»ñ –S‚ÐË»yÆ ©aÿ þ´Ï :'‰´×“î,ùaëž8£=¤š7₼‰Ú4fûÀ®àÀþ5ÐqŒÊêU°ýkÝüt— P²†kHg±Ô¡âZš¿`kPãÄê+ÓþÞ™´]oKèFÓúâ#Ð<-!“ÂtŒrcú èûW3àÒdð®T ÄWʺD9O°«Bc…-%-2BŒQE-%(¢€ (¢€ (¥ Š( ŠZJ`RÑHŠ(¦EPÑIE-”R¥¤¥ Š(¦IEQEQE (¢˜QHÒÑ@ KGj+‘csÍ(<æ’•EL‡"M\Ó©1 ¦“JN)„ñR;l E$Òšrð)ƒHiM4“EÀCHii) vhëIšQNâ°†Š^¦—€M¹§*RO€·=h1æŸE†éÁš2h¢áaI¤¥¢˜XJ ¤&š›•aI¦) ¦@>ÔR”´”S±ILV!ºÍ²¸Œôh˜~•áeÀÞ¤§$ñ^ô9üW‚êÖ¯iy¨Ûì ¤Ì‹ì3\x¥ªg^ªÂ¥V[¨™ÖhÎF9ÆZâµ9´Íb{m»Sväÿtô®ž9šÆùdŒp¤ zã­Mã¾ÓáÔ¡ûñ(ÈÐÿ¢…gÙìʯI8Ýt8¥Œ)ê¸lT ì8Í<1õ®æ™„Z.‚Sæç¦*7Ú@Ú1D3†"93ÐÔSæ&+Ú¡'sG- Ó6N*0Õ,œŒÓTn_¥o*Šìèü5­y+öÛ NccÛÚ»;…y`àzØÓu›«c‘+:pœcé\8¬´|ÐÜìÃb¹$ÍO*™íŽÓ»i»ž•ÌÑË]¥|š¦ŒÏÕâpy+X[r¸àý;Ö˜tãMF]ZÒ›q,Ûª\Å„ÀuþäTW¢ ãš<Èä ›ƒ˜«ÂXî£ .LuìjÞŒ”ô°Ñ¶æ=ë€ÃªÕGL]²ÛNHÎJÌ'^ÝéMhº8²¸5S9«2–Èž¤š¯Š¨“1)¤S˜àSr?´c"Þ“§É¨^¢$Nè¬ ›GA^Ê¥môÄŒA·$]J• væ¹ßéVË m,L%oœ±ÎøV߈o÷LÊ“3\ŽGzóqu›Ñ”)¨«³ öW`Pt'§­`ëÀYi±Ûð%¸;›Ø Ù³æœÌêå’GJãu­@ê:œ“)ýØùÝX9KкÎȤ)XŠG=”cëM'm£ k!a•\1½«Ò½Ži|,©¦\ƒªÀó”62wV6¿éÅÈù™¹#½q¶6%ãwdbòt ¤ãšïtÔ™bCä(/_÷Î1Ku¨A¥ƒ-ÕÜvøè¹?Eë\®§ã¤w"Êݦ`0$Ÿ…üUÂ…Z¯ÝFŽInuˆL@Êøä…çj;ñM†ž µÐ–Oùå Þ>‚¸ ýkQÔò.nXÇÚ4ùSòEG5ÛO/ŠÖ£¹.mìtZ‹õåh¡ak"3—#ݿ±­­f»¸ŽÞÌ“LáVbp)±ÆOQÅz7Â-!.õëÍRT ,”,Dö‘³Ïà þußN»c:–7g¨xoD‹Ãš®™ ‰s#âòÇóýh\ö“§÷£aú”õ¨®Ûia#]ËDyÎíÜù9† „Šå›Ýó šæ;bH¢ž)«Å8sPÍâ8Tª2*%ëSD»˜ ‰‹mnòéH ½".Êü{OjÓÖTù`ŒÚ¸nÄ?–+Ò|Yã5Cx­¨Õ$…±Á¡ªÛ9ËÜÑ÷&tõV tÚM7pGZ¸°3>•j~aòúÒugq K)—à¥DŠFTŒ`çp¯]øqàû›ÇÔõ1-G3™÷ðX©è1Ç>µÐê ü3|Í"Ù›G=íd(üäP Ú¹¬bìx3HZ=Œ¹þuXŒsè{W­^ü…¹³ÕÜÐOÇæ§úVü)íP¾?µìBöo-ÉþTrH¯ky¦ã¯Q:˜Êȇ½zœßnvƒµÝ^Qøc5Ÿ¨|#Ö ¶Ò{k¦êcV(GÓwš‹Bö‰ž)Þ7zÕc[zž…ªèª±êvÛs…g_•¾„pkE#µ%¡ri«Þ§Óà7zŒ¨b]ÀÀ5]ÏA[Þ ]Þ)µl}ÀÄqœu«›´[2޲Hõ; Y4ýR_+2ªF+—½bä$e³éŠèµ{çhÕGO¼:šÌÓìäžs$˜1©äžÕàÔª›=XÆÈÍÕ§}Ãr)•„³|Š1ëÿÖ¯>­ÿê¿Ú©Ž6Ì6ãbûžæ°1ÍzØZnõÝœU¥Í!è9º¿ZÇyªI²ùhmØ–À=õ®UkÒþÙGs®^Ü˺ÁlîÃ3€5u!í).J1mšÿØ_Ùš=æ¨mu Û{u/±vÄw8ê@®Vo‰%Wm¶‰m>îùY¿ ¯z0ÚÀ2A¡¨¯<{ásáŸËJ~Å>e¶cÏÈz¯Ô?*‰`©/‰\ç„ù™^ïÇzÅÑ%´ ÿr<ÿ2k6_kS¡GÔfU=Dx_åY‡ƒIÖ´qÚ(»1Y™ß{³3¥‰&œ)´êÐi NQ“MSÓ!ºRe¢Ò &I¯høGn"ðÌøù¦¼lœvUQþ5ã ÷kÛþHÀÈ£-Ô þ`Ò¥ñˆøÒ¡»Ù\ÔÄøüKL”&O÷ùWYÄ|˜3NiÞo©þt¢¹ÙÕ ‰óR¨È5ÔÉŽ*$oÁn ldŠ0O¶ü5§ÿkx“M°þ'Rÿî¯Ìß 5•îìjí¶{®ƒ¥¥Ÿƒì´©û(IWÃ-ú“^káDŸMÖ¯t ]éÓnÍüK“ô ãþí^¿Üšò¯ˆ1Ïáïi^$O“&!¸Ç|v?UÿÐk¶ÚRwfßtô×¼{4QˆæòV\?Û8>ã.¿c|<¹ƒTðµÞ1 wk¾£<ˆ÷àŽ¾Â»«x"»²™䑞C+sùuüëÍn4i<3ã)'´ˆ#ÈæKg-€Àýè[¶@ü¨¸Ò¹Â_ÄÚf½qg9ØYÂÌ;8ÏÐõüjoÇ·Æüd(rk­ñîi«É¦k6w ^7ÙÙ$<¬ƒ€‡éÓ?OZç<Ét¨äB³y’C0<îc§ú~„} aÈT€U{,ý•C}å5f­ ÁKIJ($)h¢€ (¢€ (¢˜QHŠ( RÑE`¢–’€°´QE0 (¢€ )i)\Š(¦ÒQEQE褥 Š(Í´QEÀ))h¢ã°Râ’–¦àQE)ÀSE>¸Î±£´´\â––’€E0ŠÒw c@¢F)ˆiÖ›‚*\`S¤1€dÓšUZpÜqNZ 9zx£½)ZLÒM&ÇRRn¦—ô ,9›cšiji4ËRn¦æL9¥¢Š@QA¦Š\Óih¢ŒÒQ@›p@+ýáùW™2ὫÙ<]o×…ä’<…w†#lúv¯ ;Jû×­‚~ë8±Jííã5bÞïÊ8‘7¯ëPŠJìµ÷0^î¨ÙI,§û’ìoF©£g˜? ÄŒsš½ 3p£q=îk AÑØè>ÕõøRæÔC›1y[:áG&½?ÃþÒt"—{z¼‰§)ÿez¯&¶t;¦h}P¦Xïc'õÍ^Ík$rÔ«); NM¤£5¡ˆ¹£4ÜÒ“LBæ“4RSÛ+MJÑí/m£¸·q†G_cï^?㟆©¢ÙO«i3YÄ7Ko)Ë 'SÜsßšöoZÈñDðÚøOWšàÚH=ò0æE&ŠRhùu¾ñÝü8³ùµ ò§(‹By? ®ç<שørÝ´Ï @:Iq™ŸñéúW>|”Z]N¼$yê\Е^æçËB2N=;Å7ñxÃæ4neZÜ÷5oK„D­yqŒ(,3ÛÞ¼ÇÄúÜšæ¯%ÁbaC¶%ôµäá(:ÕS{#ЯS’:¹ÉÉæ”Si½óÎC׃ï^íð¯F“MðÓ_N»eÔHê#/ó'ñ¯+ðo†fñ6µ¸V‘÷2v ýß©è+è¨Ñ##B¢ª£ €*¡¦U¥§)(¬/øfèRY¶é?ym)þôú†·¥ÎkV®Žtì|§wk5¥Ì¶÷˜§‰Š:0åHê*©ì¿|*³ZÿÂEgócÂ]€>òôõáé^:G­¡×s+¥¤£¥"…§ƒŒS)Ù¤ÊE´9ZõƒšŠ½†§¦3 ñJ'AþË ÔμŽ6ãšè¼ ­aøÒÒVlCp~Ï.zm~3øPÒBª¯èZŽàâÞcé#R RÕæú&£9é´ù)®“€ùdòíSüéE5OêÅPØrÔèpjS/¢GDK°ãÝü-±7>+’ð®VÒÝŽïFo”~›«€F { öÿ†ZCi¾r®Ù¯›Î9êpƒòÉüjiÆò¸«ÎбڃXž/ÑF¿á»«@¡§Uó!Ï÷בùò?Ùõ8`kªçžŽ+áþ¨gðå¼Rçt f>ßw?Q[úΗ£jYããSŽGk#A°û¹«[Ƹ€J¡¶1?Oå]LG?‡†ô9›6Þò#û„†í£%”–\{^¢¼ÂóI–ÒÞó_Ž#Ö›©¤›}PãÌ~µí_f@Ðí£$!=Tö"²n4¤¹‘£tÊÎêó¡ämWùiˆÙ±ž;«Hna Ç2R=È«5Íø4Ék¥I¤\çéÒyþ$ê‡èGò®uÍ4&--%--QEÂÁE-%;ˆ(£4f€ (¢€ )i(´RRÐÑIK@Q@¬QEQEQ@¦ ¢ŠZa1E´€J(¥ ¢–Š(¢ƒ@QHaKE-%f’€)Ù¦ vkŒìš)¹¥Í-ÜÑšZ)3EŠJ\ÒO–Š)ÔÚZiÊi¥É l}4š3M&4ÂiI¦“@Æ–¦¡4Ây¤PüÒf™šQÖáIKLAA¢Š  (¢‚–’—4À(¢Š@.hÍ74f7Å+=úM…øàÁ9‰¿Ýqþ Wb@³XŸŸÆ1^©ãÛoµø#R\dÄ‚aõVùf¼‡N•Ê41œÆÝ·uâ¹kÆúT%ec ¸¸K­ –g'aQ0{Ÿ_þµyC‚’2 ’ z~šåÒá„qó– 5çúä+«/–—'̹­0rÕÄX•ÔϤ¥äÑŠî9w%Œq]¯Ã½ûcÄöá×6ö¿éú”~'‘®>÷b½ßá–„ÚO‡Mäɶ{Ò$Áê#t3øÖ{ȹ>X¶i(¢µ9B’–’˜˜RRö¢˜‚Š(¦\'ÅËÃkàWˆ«˜âüXÿè"»ºòo×…môkxf’fL(þfS¤Ø6§ªÛÚ.xãqô^¤þU먒T†%G`·Ò¹i¢Yuæ”ãíòާó®ŽKøtË9õKžB F¿Þ=…x˜êŽ­Og‡±„‚§Ož]J>7ÖEŽ•Ÿ¾™A—“ÿ¯^nzâ¬__M¨ÞKwpÛ¤•²}‡¥V&»ðôj'=IóÊâÍZ³³ŸQ¼‚ÎÒ3$ó¸HÔ¤ÿJ­ÇS^Ëð¯ÂfÆÔë×±ââáqn¬9Hÿ½õoåõ®„®Ì§.Tvžðý·†tHtûp¥ÀÝ4¸ÿY'sýµkQšQZ#•ݰ¹¤¢ÄGum õ¤Ö—($‚d(êzkæÏè3øs\¸Ó¦ÉÙŠOïÆ~ëžà×ÒõÉøÿÂÃĺ™mmBЄ÷uþ$ü{{Љ««šÒ•žH¢¤t*ÄAŒŒÔŽi™A4 2xÏÍEÎV@ëÁ ŽÔÕ8Ÿ/Í>œTu)꣼#¬xRÊü¶é¼½“{H¼è­ãË£gàMbLà´Xú± ýkÏ~ë¦×W¹Ñ¥oÝ]¯™{H£>£ùWSñzáãðX‰O]F­ô?ÌVéèpÊ6•ÆêAÒœ*˜¡è*ª D‚§ⲓ7ŽÅíM}cY³Ósq(F>‹ÕåšúJ(Ò’(×lh¡TÀ ò„ú_Ú5«­MÇËi–™þûÿ€ó¯`kZkC–¼¯+åäâ™Nè8­ »8ËIwtKƒ·Ü/Ò´“ïq‘H‘ˆÐ*ô;ǵ0c6ì‘Î2ùÒÇÖf<±êiýM( F5ì?aÖmï“)—ìóþyFüøük\dKˆ#¹áeX`Ó`WXQd;FÒßÞÇzH-v¢âQ@¢‹ŠÁE-îRÑEÂÁŠ( Ñp (¥ÅŠ1E-QEÀ(¢–‹ˆJ)h¢áa1E-\,%/Z(¢ã°QE¨¸X))ih¸¬%RÒ¸ÄïE­-;P)€v¢–ŠW(¢–€’IL,–’Œ×Ö:š3HhfŒÓiiáKšh§PæŒÒQ@…¥¤¢€4RP(‚M%-)¤ÐM4œP1ÔlÔ¬i„Ô²Òh¢€8¡ S€ sKŠd€¥Å´ÀJSÅ-6€ ÑIK@QL¤¥¤4Bh¤¢¤eMfßíz£n:Ém*®Ó_;Ùܬl›‰ä‘ÔWÒ ùOCÁ¯™&ȹžÕzÅ3¦Om¬EEHÝJVgN’Dð'•ò¡\·Í»qñÚ¹ŸDëä9úcµ\Ó.¥W*§Œu¨|JRxÖUÀ|üÀvü+: –¡­WxœÈ5"Œšjx†Z»Ùͯð†ÛÄ:ôq:“i$¸nÛGEú“Çç_AmUPŠP0è+šð€4 [£¦Û«‘çÍê è?Öºj˜w"£»ÒŠv(ÅY•†ÑÍ?bÂÃ1F)Ø£\,&(Å-% Šðߌ,÷ž7²³Œ–+i…ô,̽ÐWÏŸõ}Ÿn®í3[… UÁýMöÐZ]\êm,’ÞÂ+h› p:ã­p~,ÖMý𳉿ѭÎ2:3V¶½â7ƒÃðY¤è/æ¸Èa\ $·5ÆÃrÉÎ{õ릔"IœÒ­395bži(¼ŽÁQ@êOWc1GUà ÂK¯/ž„Ø[bKƒÙ¿ºŸò¾*…P@À€+Â>ÆÄgoÞ\8þ)_ÀtJÜ«HÂr» QE2Š( €H<?¥SǾ(xAlî·l¢ÞvÅÂ(á$?Åô?μŗÍ}Qugý¤Ö—Q `™J:„ùãÆŸÃ:Ü–n­Ûç·”ÿˆèk)+;åufs}è&œE4ŠF£”ñVS¾ÕW¥I›g§CS$Rv%Ó¯åÒ5›[øOï-¦Yø=+Õþ+jö·Þ Òž¹ofYâ#û¡Nô +È®“d¤ön•j}NâçE²ÓårÐZ<† {nÁÇè:¸½ 'zåO˜)ëIšEcQŽ~´ß5Y§¶VxÎ0)úN.­«ÙéÐäÉq(?Ý©ük4®\¥Êhøe§ CpÀ†»‘¥o÷~êþƒõ®ÞªÙÛEg±ˆâP±ö@ÀþUi +ýáú×BVG ww)ÔÜS©ˆ("ŠZ(¢ŠSIŠ)h)h¢€KH)hh¤¢€ Z( AEP (¢€“´Qp°”RÑL,QK@ E-QE†QE1Q@RÐQKI@(¤¥(¥”¢€½HG4 LPih £ŠZ(´Rô¦šägR ÓI¥4ÓHbæÖ’”P1ô£¥4uKIFh´´”PÒÒfŠ3KšJ3@M5æšhƦšsTdÔŒ3J9¦äS—­)ÔÑN¦K ZJZ`-%PQEQE-„ÑHh(¤4b€§ó‡Š!6Þ,ÖàQ·ŽÃèN­}+çoHõ yS??÷ȪJêÂNÌ¡¦K徿Ë(êª~j—Ä"_±Fø'?ÃÓ5Ÿap‘Þy2y˜'09úÔšô®ï·Ëò×<`ž(Tí+Õº±Œ•ÕxFþÚñU«&èƒù’ñü ÉüørÑŒšöïƒÚ(ƒL¼Öd_švò"'û‹ËÄÿè5s%;#Ó 6–Š›“`£´fÂÁE-% i´¦’ªäØZ))E yÚi[lq‚ìÇ “úWÊ×W‘Ýk·šƒ¹pÒ¼©¸rı#5ïõ±¢ø&éU±=ïú,ð/¼ïœþuó€5¢ØÊNÌtŽÒH]Ø–'$šUÏ8¨³ÍXr pÕ‹Ž+¼øY¢SÅ)w"fÞÀy¬Hà¹áó?…pE¾n9ö¯¢þøpøw°Ç2âòë÷óú‚G ø×4¬[´GUŠ\QEQŠ(¥¤E-Àn1@¥¤¢à-bø£Ã6¾)Ñ䲟 2üÐMŽcð= mQšL±òÅý…Ö—¨OcyŽâ*ê{Qê*³/úWºüEðWü$6_Ú6?´í“îùnƒø~£·å^IV#‚_¡¬Ú±Ó Ýž43O~£N¦„7¹nožÜ7qU¿å˜÷©‹þè¡ô¨?„jIbŠš5¢Sƒ|´2¢ìM#dðxÝü"ÓM׉î5\¥œ$þÛð?MÕçëÄlkÛ~éßdð”—Ž0÷“³ƒþÊü£õÝUgZZã®X0ûÃùTœ0æ›KÚ´9‡ ZAK@¢’Š@-QL”RP(h¢Š@-Q@RÓ¢Š(¥¤Í-QEQEQK@ KEQEQEQEQÞ–—µ%QŠ`u¤¢€ SºRRÐw¥¤§v¤!3EP1)h"Š(Åv ši§i®C© IKši4 p¥¦n¥Ý@ì:—4ÝÔf¥¦Í;4ê)3EŠJ3@Å4ÓKHhÜÑHhÍ£5)4Ý´¬2*pÍ?e.Î(°\@i¼Q@…¢Š)€´”Q@4RRÐFh¤ ¦šZLÒ8Í:›KšrŒ+æ?Þ¥ÿŠõ;¼åd¹r>€à*úoxÏAcøWÉWwï'•ŽFaø“ZÓFSv&…˜ÝîS´ýj{ÐíÜKŒ{VZÎË qÔØnmFM¨rw{Š×•™s£:1†ÇJú›ÃÚxÒü5¦ÙÚb·@Ãý¢2ß©5óo†¬…ÿ‰´»Wl×q«{Ã5õ+沞æëT2Š3M& c³Fi¹£4Æ;4SsK@€ÒRšJ\Rã&X>5ñx[Â÷:ˆÁ¹8ŠÝOyCør jí’ôG‹üXñ!ÖüTö0¾lôâbLŸÆ>? àè’I%•¤v,îŘžäõ4ô…™I#ÖºR9Ôhd‘gÔUcò¶*bÙ‰÷ÍK4ƒ²:߆Þÿ„ƒÅP´ÉºÎÓ÷ódpq÷Wñ? 5ôWzâþhCGðl3È›noÏŸ!=vÿü¹ük´ i EPŠ)i(QKHh”QI@ šm-&(€àד|MðCG$ž"Ò ÜÍä:ùèóüëÖqíFHÈ=ý©1§f|© RŠGCÖ¢Æ+¼øáuÐuqqk[ ²YFý×éÜW rG½fŽ‹Ý “9ȦÓ[ óKš£1œœ“Lç¥>,nü)‡ž sí_HxFÜZx;G„v´ŒŸ©󯛟˜È¯¥ü8âO éL:8¿ôU*Æ­(¦æœ*̇KMH£½%´´”f€Š(¦ÒÓihh¢Š@–RÐEPE´À(¢–€ sEPEPEPE PE©h1@¢–€ QEQÚŠJ(¢Š`QE¢’”Rii)h %:“”R‘Å! 1¦f‚i¹®3°Zi4´ÜÐæŠLÒš )Ô€RL´”´Ä-”RÙ£4Âis@ š3Iš3@¦šZi S©£4ñ@¥£¥Åp¤8§SM h ÑŠRÑŽi LRÓ±@ëLàÓ±A8ar‘RŠæ”Qp°¸¤"A¥qØÃñ.‡ˆtK>l ã1¿÷t?çµ|é©Xͧ]ÍisŽâ*êxéÞ¾¢zá> ø9uû{iŒ Ø®Qü'Üv¨oSHèx7ÞcKS˜JÏu­@zÖ‰ŠÀ*T8¨W­MÐ`ReÄwÞé_@|=™æð6–\’U}°¥xùTWÐþ ³ké0¸Ã˜„zn%¿­T ê쎆œ)£¥8UØp P(´´ÚZZ)3KšZ)(¢â–’–€ ZJ(ii( ¢Š(¢–Š)sIE0 ÒÓihh Q@Q@(¤¢€E©(¥Q@RÐih¢€ LRÑ@ Š)i´´´”ê(QH¢Š)€‡¥!§”ÚcEsGjSEqX뛊“QŠvŠ=iةآ€õ¥¤ ¢Š)ˆZCÍf€4RÑH£4QŠ3KŠ@)Ô‚”QNÅ%QNà-4Ó»R´€m- q@ÄÅ/>”´´”†–Ši¢—Ú@úRh¢ÄÍ-&)Eq_6¿ƒM«4ó _ªä×ÎòFÉ+# 0à×ÐCÊEû«u†üTâ¼~G…”³ÅÎK/&ºi|'_ˆåÈÁ¥©.7•™ªçQŠÔÄžÓoÛ ÷L‹»é‘šúôBôô¯WŽ{z×Ôž Õ¿¶|¦Þ †!Ÿï/Ê–ƪ:(›èÍ%‰Ð;4RQ@ IEÄ-”´•ó·Å¸D?.ØËXâývý+è¼W‚|Z1^xøÅ°/•o;/V8Ïè+J[™VøO ÖÔ™Í[¹L’O4JiFsÒµ0D¡>Jö?‚Z¸’ÏQѤož6„ðß®ßμx0+øWCàitØ]Èû`wòf$ðø'ð8?…D•Ñ´]™ôÎ))ئšç:D¢Š1@ HiqKŠ4S…S±@ÄÎ|çñ.é$øª¼'; #ÿP }ª2|«â©ÚoêűŸ¶KœwÃÖ–æ5¶2åºiWi8WõÍÑ[œ¡]¯Â‰<¿ˆz?ye_ü†ÕÅw®¿ásñIÛý÷ÏÓËlÒ–ÅCséPi˜¼SÅr\í°ê(Í\bSiM!¥p°Rk6)Qph~(¦æ–­2ZIš(¸XRx¨˜ÓÚ«Èp ¤Øì2I0zÕ Ë•†&‘Û £'4ég¹®KÄZ“L~Í!Þ>µ›e¤yÏŽ®Zÿ]ŠëFSjgÐåd Weâ˜Ò<Ì|ÑH}Ä3d×D62“¶ƒô§®MF1R…àvüêÚ³ÑþhÉs¨ÜêÓ dµÄqn$<“øç^µÈx3LmÃv¶î¸™Ç›/ûÍÎ?]\MÀ¡ÉÝ–VMSÅ.i’:ŠJZQKMÍ.h´RRÐ1h¤£4\Ó©”¹ QH ¦¨”f€E&hÍ--% ¢ŒÑšZ))h¥4”PÑE-RÑEQK@Š( @iE!¢‹EP ¤¥¤ aKE-%-Š))h ¢–’€ (¢˜‘IŠ}%rhŒŠJyéLÍ OÀiÀÓAahÅ.híNäØJ1KŠ !¥æ“#ÖŒŠ.1@§Šn®!M4Šq Ši4\&“>”3;µí”2««#ª²°ÃzzŠq—+&QæV>?&ɯ`ñ÷´†uoÄÅW/5ç¹ÛýŸËÒ¼„®;WD]ÎW¦šz{f“µ 5Asèo…Þ1MDM2êOø˜Ù _˜ó,c£qÀ?®óòF©]é„ÖS43ÄÁ‘—×Óéí_Bxâ%‡Ší…µÓGkª/ o–OtÏò¬gº6§Q=ÚbÖ½â_ìè¤NµkûÔëÝ¿ÀWÞ.×íá[á3¼¥ñ-¬±ƒ%@ÆG B‹eº‘NǪR\vñ+BÕcÌÂ{Þ#&uÊn<¼p?W`Žº0da•e9T= M=‡ÑšLÐM!5òߎ,ÛNñ–¯lúwáŽáúú‡5ã4Me¯BŸ+³ÎG÷‡(,ÀV´ž¦5—»sÇ%+q@®“¯Hø5§ŸËzGÉgnÄöŸå¦êóµíßtየê|׈”ÿ²ƒ?Í¿J‰»DÖ”nÏPáIJ:×%ÎËRPhÍ5Ž)i 'ëLÏ4÷¨‰Å!’©§f  O T™-æ”Ô{©KñUq´f’æ—59¥Í1Î)¥©¥ª6lu¥p°æj@ÕII¿Ö•Çb}Ô¡ª¾ñJ‹…‹>e.ê€{Ô€ŒS Í=j iàÓ%”P 4™Í.yÅ.ÚCŒRâ–˜†âŠZ1L¥¥¢€E)¤ ¢ƒ@ AKŠ)Ô€gCE8ÓiŒ(¢–€ â¼ãÇ ­µ¤—SÑ¢Hu/½$#„Ÿéèß¡ïë^IÒš“D8¦|‰=¼¶Ó<3FÑÈ„«£ #±µ\ñ_Cxÿáô^%‰µ9R=U# ¸±ÿkÐþ¼ â -¦’ch䌕uq‚¤uzÖñ•ÎyA¢¸ Št2´$ˆH*Aâ£"éVgs×|1¯ÚNée6BJ7[NÇæSÝ÷ç¦koTŽäH.7´DnÇF\“ïÍy·ƒmáÔ¢¾±¶¸ŒI ʰ=Ez‡â$™¥ã¶Eò=$ÇóÍC(µáï ÚG£ÝCw诤b¿…yÛÏcïT¼/¯\xjúM2þRl¾Òafcþ©û0ÿeºŸzÔ¼ÔŲm‰¤µ8ùp»÷®+^Õ¡Õ'¾!* MƒÁaÑ…e%sH»çš3\—ÃÍyµß Û¼Çuűò$$ä½â1ùWYY5c¥;«‹œÖŒ4®xGR±Û¹ÌFH¿ß_™qùcñ­ÌS€ç?¥ ÙƒWGÇ­÷ ½ã=)t_ê–ŒGå£ÿu¾`?#X`|خձçÛ[SŠúsÀš_öO‚t»v]²4BY»ßÔWÎþÓ—WñF™§¿Üžåý×<þ•õ`UU €±¬ô±ÓAj7¥&áHü ‹~ rÞÇU‹š\Š‹xÅÆiÜVÍQ9§1ÈȪ®Å[&“cHŸµ@í‚i¦_CQ3Ñp°¾a¦WÍSÝóSÑù¦˜4[-Fú„½&þ*ˆ°ön*¬¯‘R3f ”ñL û¹Dq³z ó}^õ繓žz×Uâ]KȌۡùØsí\\pµÕÐÀùAæ„ §®Ûð£ÈܱáÍpâ½Æ åxdF8 2È^s]Tö8ê?xxä×»ørÌiþ°·Û´¬!›ýãÉþuáút&æþÞ9’E_Ì×¾£ÂŽ€cð¢eSW.ÂpÀŠÓ‰¸Ž’b¯[͸óÔT"Ù¦ž«â¤Vª`zp5jx4üÒæ™š3ÍIšZ`j\Ð!Ô´ÜÒæ‹€´´Ú\Ñq KMÍ-0Š( ¢ŒÑ@ KM—½--¤Íaii( ´”´RÒfŠ-:›Kšu™ .!Ô”fŠw¢Š(¢Š(RŠJ(h¢–€ ZJZ@RQL¤£4P0¤4´”ŒšLÐM75Æv*2içša††š!â“4ăŠ\ƒQb°ò)¥7R­;Ìâì7ÉAךkàp¢”¶zSw~t€„“špàQ‚OJv iã4Õ"ÓBcÔSÅ4Sª‰4áL§Š8 ZAK@RÐh¥¤é@RSi§Š2i &RŠJQH,8 u ¥¦M"ŸM"€KERb–ƒ@ 'Äøçáí§Š¢kË2–Ú²Ž$ÇË6;?¿£Wli*“¶¤¸¦µ>MÔtËÝ&öKKûi ž3†WÿõŠ©Šú·UÑ4ÝrÜÁ©YC:•*׿L÷SÔð?ø PðöµöD{irÖóã”zúÞµD÷9åJÏB:ê|ìU&Œ8à©=çZ*×z}òý¦1>X°|}îq‘Uô½.KEu™y\äö9í[-ÂLn®dÌ09ØŒ~lã—:‹6|A}ø}J±VÚ—†þþõæ·3=´ åHÛe89=³ë[Zަ׬áoÜ ž}ÅsWN®¾Z’FiFWe8YïÁíu-5éô©Nôÿ¦‹ž?ŸÇî@WËÓk·Ôe® âÑ­g`Ý;Zô¯Œ*á4²aÉ6Jñö® R ¢’>bvCá8¤½ãKáÒïø‡¢ûOŸÉM}3Í|ÑðÜñG'þz·þ‚kéRØψvhè ®ˆfÈUŸ­<…f\É·8®Fúi¼ÑŽ´yœõ¬•¹<ŒÓ–çžµ<år› (#¨&ldª&+ÔÓÃùÊj¹®M¬5[,h$â4‡ÅZ$ˆ‚O­=r=©Ê9§ÍRD641"ž)àÓªÄ!KP¹ŽÊÒIå8U=MZ¸¸ŠÖšg Œ’kͦ€Fv£u%íÛ9ÏÌ{ö­‹7É· Œ±5‡¦¼SßÅŒ-É5ÝTM£ â„Ï=ø‚Â-6Ê…åfü—ÿ¯^{]çĹA¸ÓâV7sø?¥pg­uÃá8ªüF×…"ó|O`½q&ãø ×²FäçŸzòßC¿\–lgÊ€œú@ÿô¨e°j*nkI{¦ŠµY…ðÀÖz¿5:>©E3n7ÊÔèk: r[I=ê‰-«bžµH K»n¦fŒŠ5.jÔàÔ04¹¨Ã p4Àx¥Í34 Ð!ô´ÜÑšÃè¦f—4‡w¥Í4Z.4¹¦ÒÓsKšm->ŠniÀÐÒRÒRiE6ÖÇÑH(¦¨¤£4®!sKšmÓ¸Í-6ŒÐŽ)i3K‘Ná`¢ŒÒŠ.+-%- RЊZ( E¸¢ã°˜£´Qp!"šEIÖ“µrDx¦•§šcRÊDl8âšW‘R€OJÆ1Í*ÒÔí¥BÊEFÙ¦†"žFh»”† “šV ÔŸfnæ£xØpi‹ACŠre5"-×¥OåícqÎ)ê8¤ñM(¥Í743NáasO^”€gµ< Ó³ëH1KLBÒÒ Z)¦Hh†’”Ñ@ĦÓ% Ó… §Š`¥-%(¥¢€E:ŒPi ?ÓHn)äSqLcq\Ï]mü2×HÐL…v»ž >ƒùWQŠŽhbž !™H¤R®¬2=E<`k¶f ÒY¹=Ùy¥e_ê&è|’y0ô 3ƒÿ×®ÏPøg{g,×ò˜ÎH´˜sî†èWœ\Q¤FR®¬UÑ—£wv5¤= wil„|ƒ×5AÌjÙQ¸úš´ÎCa{â‘- ®1œúâ¶‹¶ærW4ìB¥š†êNà} zÂíGk^iŒp$ÿI‹'¿FÈþÄøKB—Y×­­6†7Yg' H'?^Ÿz½ÒSäÜ`㊉Jå(ØïE!4 äRPQá$óüu6OZEúœ·õ®àùÉ’y^ŸJî¼`Éwâ}qŠæO´ìVöU ŠåÚâÓÙÞ?˜¶9í]ÔþqT~ñØ'4RNÚ–¾‘–ò J–Æ+æ VƒÄºdÈ~e»ŒûèW»ê²Ê“¸äsÅrc.¬uáw: nQ¢mŽ2+&æùBŒ×2÷Ó#ðäšW½2'Ìy¯?˜ïå6$”1ܧŠbÜàõ¬¤½Âæ¤ivÈ0x4\,tPOæ VáʾàÖ=“çº h@õµ7s)è9‡ËÅVbÊÕ Ž*¼È:ÖÖ2¸Äæ§Ç `RÍyol›ç•Q}ëDC$"¨_êvú|&I\dtAÔÕ[ßéðBZ7i°Ú@®RÔÚêi$f;˜ç&˜¯mË:î¿6¤v–Ûû¨ÿ¯\½Ã7$u¨î/ c´dÔèÈ8öô£”ND±?Îá³Ò½O¹vKžvóõ¯;ˆÆëó 7­v;-'†sT®p;¹ûOŠ%Œˆcüq“úšæJö«úÍȹ×ï®ä<îGÓÚ”OÏZb5ÒLãš™_5—Ý9⬤¾ô€¾ šd8àù¢áb\ÒƒQƒKºÄH H¦ Í9Z€'œ D <~sKž) Òæ€E 4´†8u¥Í4RƒLV)i-0 ZLÑEÀZZJ(sKIÒŠAaÔRQšvii™¥€Fi3Fhê)¹£4îôSh€J)¹¥Í1 )ÔÚ3@¹¦fŒÐóFi¹£4€vE-2—4\P)3FiÜbÒQšLûP+ ¤4üSH®C¨©µ.ÜÒùt b)ëRí¡F8§b Ú)†0{T¸¥ *9Å;oµKŠLP#"˜P˜Ži´ÄF”†¤Å&ÚGŠJ­4­!Œ+žô*`úÓ°zQÈÅ»©ø4«Ò¶˜†âNÅS©´´À Ú^)( %8ÒR¸ ŧR@ŠrŽiq@†bŠy„P1´PzRf€Eœ(Êi§°¦%&)iG4Àn)6Ô˜£`Zä¼Oà~g½·Zj|ÒÊIþðõ÷ØâŒR¸¼ðˆ4Ùͧ=Âç‰-˜à9ˆ«z?u­A”‹3i<Íp6à{/S^ÝŒQŒš–“)3ÃÞ³ð噂Ût’9iÜ|ÎGO ö­ )ø¥Û‘@67Ró¯8§Š£«\ýG¾»Î<‹y$è¤Ð¶<;S"îþöåO\K&}As\½üăF2 tFÒf²†8Ø©ÀÜ߆Mf\[í ’O%ÏS^‚VGÝ̯ [|[¤BË‚÷‘uôÜ+èMbÓ~X ñFbñæ„„^Fük艡!g5ËŠW±×†•®yÅå¹\‘Y»ˆÈjì5=<©8W=sbI=y²ƒ=3/Ìà ±5Ø ¨ÚU“8ÏZK«veÈQfU΃O»Î9ê+§²œºÎ=+Ïtë¿)ü¹8®ëÉïvc8Џ=lDÖ—5gž;hYNŽë—Í-ÁGV#ÐÈük¢ñ<8µH7OQ\Ñëšèo[-UΑudAó·ÐšËÕ® þ|¼ùk÷qÜúÖc¹`jXdeâµD5©—,eX†pkæbRžŸJë¦Y3Žr eÞ w\€@öªNÄÉ”¶ÃÊÝ,ÙúUH‹5ÁSÔWP¶ñÇ6ò¿/•ªw3jÂÚ[Ks0 8Ï-é]<× ¦hׇT G×ãU-ŠÃUª~&¸) HŠ~iYS£©ªKRdô<àœ’M.î1C£)Á4ÚØåfÇ…Ûoˆ­ûD~†½›…}0W“_(éa®¼I`Éw×p¯¬S\õú4:™×–êýFkîËkcWZêéYÓ@EqÉ\í‹9±¨~W4²XÆÃ”•n=¢Žj”ã s•Դż*CxïG†uw¶ÔR$n8Èìk¢ŠÄÝ6 œU›_ iÑ\¤¿gýæìäšÍÁ·tW=•˜ífig _q÷ÇÉ]“¹ê+Ðu°"Ó_ Û\9±–áäèkMS3Z­ Ô>cñWR3Šª‘=µÎ×ÇáZÔ.Hæ´Œ‰h}˜ŽI<¹GËëPê6–¨ÄDÄ·\öª×WÉo½k ¯%2–ÜÜž•w &w´mÔqš¤‘íŸZ›f÷,GZŽFœf¶”‰÷„'¹mFý¯/1Fp£±÷­-Jë6æ$lŸAX²À¾@(rÜVÈɲJšd0à9Çz¡5œðD$‘6«sZÖÀ)Cµâ´o-ΡeäF›¤*AýOµhcmJ¶^K½:Þëíl ƒ,¡:WUÈ Ž'j(QŸj­h†ÎÂ+rrQqMyMfõ6ŠIšpÇZ€Ü6j³9õ¨Ì¤v¡ /¬í×5rÚð£k 8©È¥aÜë ¿jÔw¼õ®R+œ.3VRìŽõ- Z܃ެE(cŒ×/æ{Õè/€#šé–¬G&:šÈ‚÷rŽjÚN ëH `r)¤â Š`0 â5Äq!$Ó‰–J•dæ°ÓQÞÇiÍL—Ä8˜a³NÝYÂñG$Ó[RGQ@€Ó¬˜µ%vÆE_IC(9â–A§n¨Cf”¶X4áQEÈÍH3H,:–€)qŠE ER¸XCMfÚ)Ƙç½'+ DfñëN=ê)có+ÁØ¢•Žüis”´(ÍF7FÛ_ó©*”‰hZ3A8ß›µ µ†ïRÅ4ÂÁN4©Å Å%<Óh¸X@)hæŒP+¸ïF)Ü,%-b‹…„¥¤¢•Æ´™¤ÂÃÅÌÒgšW óINŃ5E&)Æ›šW ZnáFê.“Q–¤g¨‹Rli¦äÔ{ýèóE+ŽÃÏ4)¡ïNëNâ°r©¥TìjUGJ¡1›M!âžÆ˜i63KÚ›N‹……Í8R pÄ4œÓé1LÐE?»hÚMµ.Ú¤`S…)Ô•7¦šh&’‹…‡ wJAN§q46“ê(´…*Ji deqF)äÓi\`(¥´î+ ÍQšQLAIÅ)¦÷¥qØ;ÒÒFh¸X(Å”\Vš ¤Í!57^ã=Lßøæø»‘·•ÇÝÛÀüO'ñ¯|'¦kÉr[šäĵ4Wµ\¤Âk.ÝMJ·ÅÛ“ÅqñÞc½YKò;š\£¹Ø-ê®0kBÓWÛò–®õr•FãWrs…[ØÓåcÚ-¯’AË~´OªZ[HVäö¯Äw°·úöϨ5#ø¶v\¾ù_ÔœRä`¦uµÔá—–´c–)>é¯Çõ´âQ°(*h<â_wÛ>Å͇´GÐØ׆о%LmkFúÃS§Åß§2[X?ý³#úÔû9¦iš\WÇñ—QQûÍ*Õû,£Ÿã.²ãýL±‹Ý÷7õ¥ì¤W´‰ì› R‡zðyþ*xºRJ]ÚEì–ËýsTŸâ‹ä$nEÿr4_ý–a&/n‘ô2GR•Âà_6Iã?Ë÷üA}ÿ“oòªßðklw6µ¨“ëö—ÿ¥‡}Åí×cés=Gá@‡JùÂ/x–ˆõÛð=æ-üéÿðšø§?ò0_ßÏþµÁ‹Û®ÇÑÂ&ô&œSb–|"ú±Å|Ëqâ_]¯×5Ÿhaüª„÷7W#ÝÜL?餬ßÌÓT½±ôÓkš4rym«X‡Ân?έ¥Í´ª;ˆdSÝ%R?|¥±@ÆÅ?…è1ô5^ÃÌ^ØúÆ7YFcep?ºAþU(F=å_'ÁqqlÁí®'½b¯ò5j}[U¹fÕo¤í\?øÒö,~Ôú†YbwM,q/¬Ž~µ‘wâï YÇÆ¹§¡ô†?¦kæ—_7™¤÷v'ùШ‹ÑT~Õ]cè þ(x: ãTóý1…ÛúVUÏÆOÄÙ쵃Û÷j€þ$ÿJñ^ô=*•$Oµ‘êÏñ©‹+ÃÃgm÷|þ‹P7Æ{캨÷-þæ9âŒÕ{(‹ÚHõ oS‡?kðüe=`¹9ýEiAñ£JfÄú5üCÕYúŠñÊ){(ÚH÷˜>+øJ`<Ë«˜Ò[fãòÈ­ë/xRìšÍ”„ô_8+~G¾g<Ò¨ð©tQJ«>®P®»•Á_ïÅ^9ë_/éúÆ¥¤¾ý>þâÝ¿Øùt5Ôiÿ¼UhÃÏžÞò1Õf„)?Šâ³tŸBÕeÔ÷r¼Òô¯)‹ãO ö“ÜÇsü²¿Öµ øÇ¡2ƒ=†£ï…WñÈ©ä—bý¤{žŸša8¥&£f®vl…&šO½4½1œT•aKzÓ|Þ:Ônã cRÙiÍ!lTñPµÐ^µ.V)"Ë9˜yªë} ’-ж&4”‡Ê$N{œ ·ÜxéPGg×ëVÓ 8«‰ ˜ }hf¨ËÒâ®ärŠM4œÑš) UëIJ´ §Ôbž1LC…-%(¦!Ô´›©3š.šCE4š–1 ¨É§1â£&¦å!Û…8Š®ZÉŽô\v,î”5Uó©Â_z..RÕš=h3Z|ÁÊJZšZ¢ósÒ½MÇÊ9ž›æsLcšg'¥‹JplÔ(=j@*“%¡ô¢NQqX)¦œMDÍŽ´î y£<ÔeÆh&¦åX˜RšhàRž”\Bf“‹éõ5QWdÉò«’øïÜÁn”ðXgÿ­VâÐcëµ\s…\øÓtØ­aùÛ /luö§^^*¤… tÀÎ+¦)#ŽRrÜÈÕtRÀ´F;Œ€€Ãð¯!ñ{ϦÌbšÍ£eAu ô«K)1i–*Hcž¡ô…7ˆ’W’Þú8®í¶Œ‰"ÜО¼~µ¢fm3›{yy ¥­¼“Í+m‰d±ö¯£ü ádðŸ‡#²bîSæÜ¸é¼ŽƒØtüëÏtY´Ÿ x¶ÛUµÂÙ]fÞES½cÝŒ0=G#‘^ѧžÕ•i;XÞ„UîAÖºMJv¤¡!Ç¥róé{ä"UÝëYÊV5ŒL SZ¶{£À$W#ëOsæÈÛŽs]_‰4…6{UP paÞm§”Ï®M6Ó=KIkk»Xåd€ÆiuVŽ VqÆ:W=¦ß­½ŠªžzâªkÃ=±‰ÈÇ^´$ïa]X©{âI6£P3Á5Î4‚W,Àzæ ‘ewùTNjÄzmÔˆY##ë]°ŠŠ9dÛ+:Å#tÁÅ7ìAú>½A-»í‘JœÓÑÆzÖÉ™1F•)û²!üi¯¦^ ÈMÃÕNjüŒšÐ‰þ^j“dò¢¶’“Cjâ`@fÊØU·jF|“ŠŒ¶hÃYùëQæ†`)…ºP!Û¨Þ@ /"”Œ cBy‡Ö¹¨Ø…æ£i8¢ÂlŸÌ÷¥óxëUKÒ4X.Jf9Å0Êj2ôÐÞ´ìI.êz¾* —x¢À[Y fŸ¿GôÖŽQóþÐ|¯p5Hî=é ñE„äK%ÌŽxn*ìxÜhÖ”­U‰¸ÌœòM=M S‚“LKH8¥ ,¤ÎM;˜¤bgf—~Ä%(éKŠ1@X3IšR=¨Ç„ëE/JJb°fƒÒŠLPš1Å\ÓNKä´s½™á³ãÐóÖ»φ77zxÕ<+}±`ÃpPÁeOb=GáRä–å(œ(¥Í:â¬æh.á– ”ऊTƒô4ÁÒš}„Ó Ðh4˜4î ÍÍ@y ) "ŒÒf€ Òçµ4šinØ  2(5rÇDÕ59-,g“wp‡ñÅu ð§Ä¦0-Øû†^~1Pç»4P“èq\ÒšÞ¼ð?Šl2eÑg‘ñA‰?–MaÜ,–’ywPËo'÷fB‡õ¡I=˜œZÝ úÓÃT"x¿ç¢þtáÏ8èê W#¸?ð*®gØžT^2 ê+?Ë»þèÿ¾ª6KÁÀ‹>û¸.l|ˆÖƒÞ¤YAï\ÛÝ^ÆáMÁ>¨™O åÉÈ6³©w'_¥56'Lè7Š]ãÖ±ÅÔÿóÆOûäÔ‚îQÿ,eÿ¾jùÈä5wJ e­Ôì2°?üb§Iæo¼˜úšw¸r—³Å4š€HqÍÍ+‹”žµ 5½éÍ&RÍPI.É©Œ{‡,L6ˆç’ÄzR³)X¤n~`3SÆìçTƒO€>ì¦x«céE˜îˆI+ÔÔFm¹ÍXh·Úêì°¥f ÂÇ)fâ­/jª–êœ)jF†V?, }sJÌ4-3” P3œš¢t÷~^åÿV`…!gúÓW‘n$-Ö§£¥AË +bއp9 Üœà!üÅ]ìF¬˜ã¦hªÅ‰9 J%+ß57‰œ`uªNLÒ»ñ±±U%‚F(Ϲ©r}—q‰;<¸íW†c›-GÈcUõ/Óô«Y^6 \ Àš”ÙM#Q\*P3T–ÙÔßÿ»NÛ.փ튮wØžTKu ¶µ–àž#BÕÂZ\Ï=Ä‘æÊÆBî:{WM®Ã}>’Öö°¼Ï3!GRI=ª­ƒd@qv7’¡sƒõ®Š[\欛ÐÖ‚‹JJwœm#=?>¼×©ëoÙ¥QxÜTãük¶¾Ód’ÈE¡Y{’p~¸®^ïFÕ}’;¥è:Œ~u³’0P‘ÄêúíÏÍÈ®qÎñÆ?g¦¤ñÛí¸·lê Üg½nê¾t`–ò‡À8uÈSÎG¿梳ðÝÇÛÖv·šH¢ìÚÇò"ŽxÙ˱ÀÝF‰®Û[Z»µ«ÜF 1Ç;…}6ÝÀ¯œuËK«oÃ)µ¹T3#1èsÇêmñ'N‹ïZÝcÔmÿδ¶±µïs¸Ö—!k‘·øáéS3\M³ÄOòÍE/ÄŸ †dw<Ù[†¯dot·;¼^j¤šŠ÷®Kþ_\çn«IU“ùŠ€ø‡Jf&=NÑÀô˜f¡óö-r÷;)nÃŽ A,ÅЊåĺ~ì-ìúyƒüksK»†õŽÙ£qŽŠÀÿ*Ÿ{ª+Nå«)7Ë‚2+FM6ÞOŸšËgc šæHmâ^F ?3PÉ«ÚÛìOöÒqm‰éÓñ¢ÚÚ¹‡®}m'PÝp+ʵ-1ZbЩ۞†½þöóY–hbеž3†-ƒgð®hi(y½ÆOÌÛ?™­! "g(˜–º=ÝÁUL¨¨<s:å†q].§k–Ñ,·:ª{5 ÃuiLúáìÚ%ÓƒÛä_æÕ¦¤YZøNhˆÂ})Lk•ÆÓïZš™ñ|*d>ž(æDe™‡üI5ƒpž$Ô¥ ™+9”~gT£&C”QÞ•Ä%=뗼Ю-7GÙ‡o­z&™ðç^»Œ¾¥¬.žÄñ qù®¹ÈöæºH¾éB.ãPÔçb0ÌfUÏáŽ+E.^¤òst O÷¾Õƒÿ â«ÿ Ð<ÍÂÿV ýß5?žÚ=´ìfxóJTgkë´â£ûH9³^ïÃ/ CR—’{½Ódþ\T«ðß äéîçý»‡?Öoö2< XÈáK¹èª¤ŸÊ¬›ö˜¤ Ó*úoMÑ´Ý"?/O±·éšOÔõ5syÛúÔ¼G‘Kæ|¿ýŸ}å~Áwåãû;ãóÅVœÃ>„×ÕEœòIçÞ¨Üé]îMÞ›i9=å[ùŠ'Èoæ|ÄqëMÅ}¨ü9ð¶¤2ÚbÛ¿÷­XÄ!ÇéU!øWáHGÍiu)õ{¦þ˜­"=ˆt$x@Z]£Ò½ðü3ð—ýØÿÛÌŸãOá¿„Óþ`á¿Þ™Ïõ£ë1ì/«Ë¹àE C8D%Ÿûª2*ú?‡þŒäh6Äÿ¶Y¿™­«-/NÓP-½ªŽÐÂõž%tCXwÕŸ1¥a”}coð¦|Û‡ÕH¯« SM;[‚ ýE/¬ùêþgÊckœ'ÍôæœÊÊ2ÈÀz•5õJÇò¨ª}€â7#ÓµYòÕüÏ”<ظýâßTá"’Ãñ¯©e¿y²µ/ýã çóÅ$ö6W[RâÒÞp§*%ˆ0ÔdQõŸ!}]÷>^ób#;ÀúšxÁ\‚ú×Ôijñì{X:mh”ËžþðôŽY´=8±êEºóGÖW`ú»î|áëHHHZ÷;¿…¾»¹ó’;‹eÎLPÉ„?ÐéÞÑ4˜|«=2Õ9>Xfo«6I¦ñ_W—sæ°TŽ¥â¾Ž¸ð‡‡nœÉ6‡bXòH„åQ/‚|.œ®ƒcŸxóGÖW`ú»î|ì¤HásÀU$ý+M|;­°Èѵ?ëÙÿ¾‰µÓ¬l@–P@üò‰Wù ²NGõ©xžÈ¥‡îÏšdе˜Æ_GÔWÜÚ¿øU)£xËž)"îJ…Oäkê0O¯åUï´ÛNõ¤7)é4a¿/J'º‡]òÿJ+è Ÿ†þºR˜"'¼22ãõ®GZøMÏ€aZƼYœ¨É[š^ iê~Öô‡+}¥Ü¢Ž<ÅBèÈ®›Ãÿ ÖìVíÓÿ,…£ñõ-ŒÕº‘Jä*roDp¥x«ºMüÚmÚÍo¨ÝiïŸõ¶à“ø€FkÑ¿áLÈO:èÚ×ÿ²¢ƒÍã\Ì`ôH0OëPêÁ¢•Ü’ÏþoZö_³<3^@ñ8ÿkœÖ>øŠÓ[iíÎvZÝù€£€ZöIµÐ4IJµ,Ƚ۽^fµaíšØßØ§¹òôúv¡g7•waqŽÒFE94ÛÙŽ"µšB{"_N’­ò²†„f™-Ì6Iqåñ–+ycô“Uõ—ØŸ«®çÌw:V£i™qcqyÆçBçTÅz<}©ø™[LÓt˘ì÷~ð˜X¼‡Óâ¹½/Á>#ÕäÙ—<#©’åLJã×ð­ãSKÈÂTõ´Li`;×¢AðƒZly÷Öqú…,ßÒºÍáfe6 >Ù8çw*¿•)VŠ*4$Ï-Ð|#©x‰‰µM±²0â½oÿôí&ÚîÞ‹€9r½ë®µ²µ±·X-mÒ(×¢ ÅK\Ó®å±Ó Q‰Ej8Õt §Š õ¬nÍv:TsÁÊí¸‚9—ÒD ?Z”sBabÑ´ w.™d¯Ù“ü*dµµîZÀ¸þìJ?¥XÍ!5\ÌVB݈£qÇ&G^µ,3FOµ(€z,QKŠC¸Í;4™£žhÀ Aš:ÒŽ;QŠ¥Æ)?^=i€œæ–Š9 ›ŠqæŒP1´šSžÔ€æ¥Á¤âÆ•‚âð)sIIÓµ;Çu¥¦ƒE.hϽ&i9'´R`Š9ïÒE.iÒ–˜4g4›ÆqNÍ6ŠvhÇ”sŽ”™>”dg§4À“éI“œŠq?4äšVFGSN¤ÅûQ`žØ giGÖ—@\@¹ÏáNÛõ¦šL“JÃ$õ§j Nh,GCE„egRYîPøzù‘hñ þñ%Æ>•½ç‰'˜´ºUµ´]—í>kþJ1úÖÎF4”8¦1°ê¢~öÊMßì2œþÍKý¨BåìnÉÿe3úM ê(2§÷… èM\ϸñ=¤O¶H®aÿ®°:ÏI¼u¡Fý«Ì1ðû½€$¹­Ñ6xÔÍʹ* P)Ü\§'/Ä "F*‹y#ƒÂ-´„Ÿo»RÙ ¨ð^Ç# ùOk&GÔm®¡]ˆà>´»Ÿ¡sþÕÇ).¶ò2ÛKÔn '€¶Áü@?pWšV¹«ø‚àÛø{SŠÝùG’ cöˆ­{FIäœþ4sïM4…$Ùá—þ ñ4òˆì´ ļÒÇ’<OµøiãÐ,ööà· “ø.kÜ3íKÛšµVÛ#7JýO'²ø=<ާTÔ  üKj„±öÜÜÊ»=3ÀÒ£Û“Îx/p<Ö?‰à~º\ñÁ `óJU$Ê8£xbàæ]ÀŸöcÛü©mü%áËE+o£ÙÆ\!$þ$æ¶ð(ãÒ¦ì«#)¼9¢H¡dÒldPrÇãZ°ZÙDb³µ‚Þ>âƒùTÜÔÏ•XÿJC±!v= o×gÞ†a@âMÉ#¥;‘Ú‹´¥Ž>öiƒ=èP&€9¤Ç°¥â—#h c¸£ŽÃ`Nix< LöÍ)Ïjoȼ“Í.î8æ˜ séMǶiy"ŒóÍ=èÍšNMÎhÇ4ÝØ¤{Š)b  ‚)(sŠ3šo~h¶(qƒKšNMv BîÉÅÅ&(ÀîGç@˜CKŸj@qK­„ã®izRçŠ`&¡N)ö§h Žý)¸ô¥Ï®hÎ)t¥¢Œ aq3Íç‘J;Ò7ôuêi¥IïÇÒ…p 0$^œP =Î>´€Ž˜£4„`Ñ“Fîh=sš,Š1IÔõ43Å--%-!Ç',=Çpyê}sI»Ž˜¥4b— Íi>”uíE-&hhâ›øQ@ ê)i)hÇ4”½éÅ `ñNÅ%΋€F)ÇCKH0^h qÖŒbŒÐi´™ P(f—4f€ ;Òfž)€ê3H -'%»â–ÑHŠ(È¥¦vŠÏANëGj&xÀ”àE CqNÅ£4QFi ÇlÐ0£­Ô´„'>´¼}i;RÇ8¦10.ô¤Å/„½¨Í ëHp9ÆO°§gŠN´ h#Û¯Zg'±©Z;Pn0i ?xÔ„àcš7g¨4€M¸QƒÜþT…ÎqŠ<œíL¾´Šx┨ê£?ZE'8Æ(Ù4˜öäÓ&OjqqŽÔdЭøQH[iÁõ&Çc=é£>”¹4´ý&(<ÒŒ÷þtÂâ œ~…›pÚ zxüŠMàú¨>äþ‡ÒŽ4m'œÒϧ•àb|¹¥É4)õ¥Îi§Ž„Ð8úP¶LÓNàx PsÓšQ“Ú˜ œäb—=M)?J2{ @9¦àƒÔŸ­<äÓpsÖ€’})Ãü)xõ¤Z^JC»µ&ÑNÆ(€HÇ“¥< výhÏÿªŒö4„ŒSWpíOÁ(9# w8ÃÇ4¼ž´gqгFEÁ›O­˜¸>´™ à{Q»‰¥Ç·4½(Œôüésê)`ë@<`Ssôüi3FÜž´¸Ç^~´P@=@¥Ï ÅZ4(€î­„;PâŒR)hG4…€ Z;à~tS¢e›´´†[<Z1ž½(À À£ÎM&1ÎhÈÆ/jB}( Açë@ ¸ÓƒôÂ{“Kƒì(ù‚p)¹£$t¤’~”PNE>”™ç¥gµ;Š`-&(<ÑHéÖŠiÏJC¸ð­ð¥ÉÍ Èµ=èã9¥Å!>†Žƒ­.}hé@éš ´žæŒŠQ@:Ð2Ø98=±KÍÐ=Å(Z\“Å)ÍTžip:b“æ¥Á cs“ŠŸ\Ó±ëKÛÚ€ÐÒçëH~´ghI=©7 NN)ƒ (>´­ÒšyûÜŠS€8é@ ŠQÇZ2¤àöÅ8°©»ñÏj] ÓHëÖŠXQÀâp=h#Ò€@4)»G© 9S“M'š\RÎ(¹Ò†íI€Ã§_Zzœ{PÏÖ“8ç9 ž)Œç\“Kœ 3ŽÔ„Ö€€ÔÜïKÈúQ€E ëKÏ¥qA4¼ÑMÍ.}©€î)¼çîÑÞŒ¶zñé@íKÍÍ!8 4¤Š^´´nã¥'>Ô˜$s@ h4Þ”‡“@ h㔇'i¸Á¤'Ž)ØæŒw mâœ8ïKŠhVÎsÅ0·¾9¤År(2(àð@?QKÁþSôÈ r ŒòsÒŸÆhãµ! Í&i½.3@Z\SFsN Š( AKH=hÉ ŒQš(¥Å!¤üiŒZ1IŒœæ—€ÑƒJ(Í:Ñ‘ëKŠ0) QA‚Š9 Ç4sš_­æy9ŒdÒÑ@ J) £4§šn0sF;óš^{ÐgކŽ{­-/J`(¤4 æ—PPIíF((„žãò¦–#<˜0=)rz®?xlñR;ŠLÒ‹âmÅ 9¥Íæ€Æ‘ˆ#­LB.6õ§sŽ”ÜqNɤ13íF}©sGZšL \ RGJc PAíFx§t¤@ç­Rši¦ÇZBsGjÀõ¤ƒKG u ÀÆi3Aéý)3L@¥¹ÊÀÓ¹›¸¦ç'ŠCisM+»®:;wn¿JMØëJÀÅê p4”í v ( hÁ>´áA B=éÁEÐ1@h¤é@0(ÍÒ(sìi~¦›»4w¦ñëKÆ) âšzRãÔS[›ÔRŽ´ ƒÉÏÒ—éÅ 2zÐŽäšfàN9 $Òã4Ä4Q»ôì S:)îÝ%'jP{Pö£´P?*3øÐsIȼfžx¢”hÉõ£$wÍ |§Üf€·fzÔÙ˜íH8§Q@‚Š3š3Í}hÍqIŽzŸéG§g€ÆiGJR¤¸£ð¤Í.i€RRæ’)i§Àu”@¤È¤Ï”´´ÜÑÓ¦hO½øis@-&}©sÅ)£¤Í;"ÿÙROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_3/other_impls.py000066400000000000000000000575361510465702400304260ustar00rootroot00000000000000# MIT License # Copyright (c) 2024 Stability AI # 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. # Some code in `other_impls` originates from HuggingFace and is subject to [the HuggingFace Transformers Apache2 License](https://github.com/huggingface/transformers/blob/main/LICENSE) ### This file contains impls for underlying related models (CLIP, T5, etc) import torch, math from torch import nn from transformers import CLIPTokenizer, T5TokenizerFast ################################################################################################# ### Core/Utility ################################################################################################# def attention(q, k, v, heads, mask=None): """Convenience wrapper around a basic attention operation""" b, _, dim_head = q.shape dim_head //= heads q, k, v = map(lambda t: t.view(b, -1, heads, dim_head).transpose(1, 2), (q, k, v)) out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False) return out.transpose(1, 2).reshape(b, -1, heads * dim_head) class Mlp(nn.Module): """ MLP as used in Vision Transformer, MLP-Mixer and related networks""" def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, bias=True, dtype=None, device=None): super().__init__() out_features = out_features or in_features hidden_features = hidden_features or in_features self.fc1 = nn.Linear(in_features, hidden_features, bias=bias, dtype=dtype, device=device) self.act = act_layer self.fc2 = nn.Linear(hidden_features, out_features, bias=bias, dtype=dtype, device=device) def forward(self, x): x = self.fc1(x) x = self.act(x) x = self.fc2(x) return x ################################################################################################# ### CLIP ################################################################################################# class CLIPAttention(torch.nn.Module): def __init__(self, embed_dim, heads, dtype, device): super().__init__() self.heads = heads self.q_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device) self.k_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device) self.v_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device) self.out_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device) def forward(self, x, mask=None): q = self.q_proj(x) k = self.k_proj(x) v = self.v_proj(x) out = attention(q, k, v, self.heads, mask) return self.out_proj(out) ACTIVATIONS = { "quick_gelu": lambda a: a * torch.sigmoid(1.702 * a), "gelu": torch.nn.functional.gelu, } class CLIPLayer(torch.nn.Module): def __init__(self, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device): super().__init__() self.layer_norm1 = nn.LayerNorm(embed_dim, dtype=dtype, device=device) self.self_attn = CLIPAttention(embed_dim, heads, dtype, device) self.layer_norm2 = nn.LayerNorm(embed_dim, dtype=dtype, device=device) #self.mlp = CLIPMLP(embed_dim, intermediate_size, intermediate_activation, dtype, device) self.mlp = Mlp(embed_dim, intermediate_size, embed_dim, act_layer=ACTIVATIONS[intermediate_activation], dtype=dtype, device=device) def forward(self, x, mask=None): x += self.self_attn(self.layer_norm1(x), mask) x += self.mlp(self.layer_norm2(x)) return x class CLIPEncoder(torch.nn.Module): def __init__(self, num_layers, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device): super().__init__() self.layers = torch.nn.ModuleList([CLIPLayer(embed_dim, heads, intermediate_size, intermediate_activation, dtype, device) for i in range(num_layers)]) def forward(self, x, mask=None, intermediate_output=None): if intermediate_output is not None: if intermediate_output < 0: intermediate_output = len(self.layers) + intermediate_output intermediate = None for i, l in enumerate(self.layers): x = l(x, mask) if i == intermediate_output: intermediate = x.clone() return x, intermediate class CLIPEmbeddings(torch.nn.Module): def __init__(self, embed_dim, vocab_size=49408, num_positions=77, dtype=None, device=None): super().__init__() self.token_embedding = torch.nn.Embedding(vocab_size, embed_dim, dtype=dtype, device=device) self.position_embedding = torch.nn.Embedding(num_positions, embed_dim, dtype=dtype, device=device) def forward(self, input_tokens): return self.token_embedding(input_tokens) + self.position_embedding.weight class CLIPTextModel_(torch.nn.Module): def __init__(self, config_dict, dtype, device): num_layers = config_dict["num_hidden_layers"] embed_dim = config_dict["hidden_size"] heads = config_dict["num_attention_heads"] intermediate_size = config_dict["intermediate_size"] intermediate_activation = config_dict["hidden_act"] super().__init__() self.embeddings = CLIPEmbeddings(embed_dim, dtype=torch.float32, device=device) self.encoder = CLIPEncoder(num_layers, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device) self.final_layer_norm = nn.LayerNorm(embed_dim, dtype=dtype, device=device) def forward(self, input_tokens, intermediate_output=None, final_layer_norm_intermediate=True): x = self.embeddings(input_tokens) causal_mask = torch.empty(x.shape[1], x.shape[1], dtype=x.dtype, device=x.device).fill_(float("-inf")).triu_(1) x, i = self.encoder(x, mask=causal_mask, intermediate_output=intermediate_output) x = self.final_layer_norm(x) if i is not None and final_layer_norm_intermediate: i = self.final_layer_norm(i) pooled_output = x[torch.arange(x.shape[0], device=x.device), input_tokens.to(dtype=torch.int, device=x.device).argmax(dim=-1),] return x, i, pooled_output class CLIPTextModel(torch.nn.Module): def __init__(self, config_dict, dtype, device): super().__init__() self.num_layers = config_dict["num_hidden_layers"] self.text_model = CLIPTextModel_(config_dict, dtype, device) embed_dim = config_dict["hidden_size"] self.text_projection = nn.Linear(embed_dim, embed_dim, bias=False, dtype=dtype, device=device) self.text_projection.weight.copy_(torch.eye(embed_dim)) self.dtype = dtype def get_input_embeddings(self): return self.text_model.embeddings.token_embedding def set_input_embeddings(self, embeddings): self.text_model.embeddings.token_embedding = embeddings def forward(self, *args, **kwargs): x = self.text_model(*args, **kwargs) out = self.text_projection(x[2]) return (x[0], x[1], out, x[2]) class SDTokenizer: def __init__(self, max_length=77, pad_with_end=True, tokenizer=None, has_start_token=True, pad_to_max_length=True, min_length=None): self.tokenizer = tokenizer self.max_length = max_length self.min_length = min_length empty = self.tokenizer('')["input_ids"] if has_start_token: self.tokens_start = 1 self.start_token = empty[0] self.end_token = empty[1] else: self.tokens_start = 0 self.start_token = None self.end_token = empty[0] self.pad_with_end = pad_with_end self.pad_to_max_length = pad_to_max_length vocab = self.tokenizer.get_vocab() self.inv_vocab = {v: k for k, v in vocab.items()} self.max_word_length = 8 def tokenize_with_weights(self, text:str): """Tokenize the text, with weight values - presume 1.0 for all and ignore other features here. The details aren't relevant for a reference impl, and weights themselves has weak effect on SD3.""" if self.pad_with_end: pad_token = self.end_token else: pad_token = 0 batch = [] if self.start_token is not None: batch.append((self.start_token, 1.0)) to_tokenize = text.replace("\n", " ").split(' ') to_tokenize = [x for x in to_tokenize if x != ""] for word in to_tokenize: batch.extend([(t, 1) for t in self.tokenizer(word)["input_ids"][self.tokens_start:-1]]) batch.append((self.end_token, 1.0)) if self.pad_to_max_length: batch.extend([(pad_token, 1.0)] * (self.max_length - len(batch))) if self.min_length is not None and len(batch) < self.min_length: batch.extend([(pad_token, 1.0)] * (self.min_length - len(batch))) return [batch] class SDXLClipGTokenizer(SDTokenizer): def __init__(self, tokenizer): super().__init__(pad_with_end=False, tokenizer=tokenizer) class SD3Tokenizer: def __init__(self): clip_tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14") self.clip_l = SDTokenizer(tokenizer=clip_tokenizer) self.clip_g = SDXLClipGTokenizer(clip_tokenizer) self.t5xxl = T5XXLTokenizer() def tokenize_with_weights(self, text:str): out = {} out["g"] = self.clip_g.tokenize_with_weights(text) out["l"] = self.clip_l.tokenize_with_weights(text) out["t5xxl"] = self.t5xxl.tokenize_with_weights(text) return out class ClipTokenWeightEncoder: def encode_token_weights(self, token_weight_pairs): tokens = list(map(lambda a: a[0], token_weight_pairs[0])) out, pooled = self([tokens]) if pooled is not None: first_pooled = pooled[0:1].cpu() else: first_pooled = pooled output = [out[0:1]] return torch.cat(output, dim=-2).cpu(), first_pooled class SDClipModel(torch.nn.Module, ClipTokenWeightEncoder): """Uses the CLIP transformer encoder for text (from huggingface)""" LAYERS = ["last", "pooled", "hidden"] def __init__(self, device="cpu", max_length=77, layer="last", layer_idx=None, textmodel_json_config=None, dtype=None, model_class=CLIPTextModel, special_tokens={"start": 49406, "end": 49407, "pad": 49407}, layer_norm_hidden_state=True, return_projected_pooled=True): super().__init__() assert layer in self.LAYERS self.transformer = model_class(textmodel_json_config, dtype, device) self.num_layers = self.transformer.num_layers self.max_length = max_length self.transformer = self.transformer.eval() for param in self.parameters(): param.requires_grad = False self.layer = layer self.layer_idx = None self.special_tokens = special_tokens self.logit_scale = torch.nn.Parameter(torch.tensor(4.6055)) self.layer_norm_hidden_state = layer_norm_hidden_state self.return_projected_pooled = return_projected_pooled if layer == "hidden": assert layer_idx is not None assert abs(layer_idx) < self.num_layers self.set_clip_options({"layer": layer_idx}) self.options_default = (self.layer, self.layer_idx, self.return_projected_pooled) def set_clip_options(self, options): layer_idx = options.get("layer", self.layer_idx) self.return_projected_pooled = options.get("projected_pooled", self.return_projected_pooled) if layer_idx is None or abs(layer_idx) > self.num_layers: self.layer = "last" else: self.layer = "hidden" self.layer_idx = layer_idx def forward(self, tokens): backup_embeds = self.transformer.get_input_embeddings() device = backup_embeds.weight.device tokens = torch.LongTensor(tokens).to(device) outputs = self.transformer(tokens, intermediate_output=self.layer_idx, final_layer_norm_intermediate=self.layer_norm_hidden_state) self.transformer.set_input_embeddings(backup_embeds) if self.layer == "last": z = outputs[0] else: z = outputs[1] pooled_output = None if len(outputs) >= 3: if not self.return_projected_pooled and len(outputs) >= 4 and outputs[3] is not None: pooled_output = outputs[3].float() elif outputs[2] is not None: pooled_output = outputs[2].float() return z.float(), pooled_output class SDXLClipG(SDClipModel): """Wraps the CLIP-G model into the SD-CLIP-Model interface""" def __init__(self, config, device="cpu", layer="penultimate", layer_idx=None, dtype=None): if layer == "penultimate": layer="hidden" layer_idx=-2 super().__init__(device=device, layer=layer, layer_idx=layer_idx, textmodel_json_config=config, dtype=dtype, special_tokens={"start": 49406, "end": 49407, "pad": 0}, layer_norm_hidden_state=False) class T5XXLModel(SDClipModel): """Wraps the T5-XXL model into the SD-CLIP-Model interface for convenience""" def __init__(self, config, device="cpu", layer="last", layer_idx=None, dtype=None): super().__init__(device=device, layer=layer, layer_idx=layer_idx, textmodel_json_config=config, dtype=dtype, special_tokens={"end": 1, "pad": 0}, model_class=T5) ################################################################################################# ### T5 implementation, for the T5-XXL text encoder portion, largely pulled from upstream impl ################################################################################################# class T5XXLTokenizer(SDTokenizer): """Wraps the T5 Tokenizer from HF into the SDTokenizer interface""" def __init__(self): super().__init__(pad_with_end=False, tokenizer=T5TokenizerFast.from_pretrained("google/t5-v1_1-xxl"), has_start_token=False, pad_to_max_length=False, max_length=99999999, min_length=77) class T5LayerNorm(torch.nn.Module): def __init__(self, hidden_size, eps=1e-6, dtype=None, device=None): super().__init__() self.weight = torch.nn.Parameter(torch.ones(hidden_size, dtype=dtype, device=device)) self.variance_epsilon = eps def forward(self, x): variance = x.pow(2).mean(-1, keepdim=True) x = x * torch.rsqrt(variance + self.variance_epsilon) return self.weight.to(device=x.device, dtype=x.dtype) * x class T5DenseGatedActDense(torch.nn.Module): def __init__(self, model_dim, ff_dim, dtype, device): super().__init__() self.wi_0 = nn.Linear(model_dim, ff_dim, bias=False, dtype=dtype, device=device) self.wi_1 = nn.Linear(model_dim, ff_dim, bias=False, dtype=dtype, device=device) self.wo = nn.Linear(ff_dim, model_dim, bias=False, dtype=dtype, device=device) def forward(self, x): hidden_gelu = torch.nn.functional.gelu(self.wi_0(x), approximate="tanh") hidden_linear = self.wi_1(x) x = hidden_gelu * hidden_linear x = self.wo(x) return x class T5LayerFF(torch.nn.Module): def __init__(self, model_dim, ff_dim, dtype, device): super().__init__() self.DenseReluDense = T5DenseGatedActDense(model_dim, ff_dim, dtype, device) self.layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device) def forward(self, x): forwarded_states = self.layer_norm(x) forwarded_states = self.DenseReluDense(forwarded_states) x += forwarded_states return x class T5Attention(torch.nn.Module): def __init__(self, model_dim, inner_dim, num_heads, relative_attention_bias, dtype, device): super().__init__() # Mesh TensorFlow initialization to avoid scaling before softmax self.q = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) self.k = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) self.v = nn.Linear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) self.o = nn.Linear(inner_dim, model_dim, bias=False, dtype=dtype, device=device) self.num_heads = num_heads self.relative_attention_bias = None if relative_attention_bias: self.relative_attention_num_buckets = 32 self.relative_attention_max_distance = 128 self.relative_attention_bias = torch.nn.Embedding(self.relative_attention_num_buckets, self.num_heads, device=device) @staticmethod def _relative_position_bucket(relative_position, bidirectional=True, num_buckets=32, max_distance=128): """ Adapted from Mesh Tensorflow: https://github.com/tensorflow/mesh/blob/0cb87fe07da627bf0b7e60475d59f95ed6b5be3d/mesh_tensorflow/transformer/transformer_layers.py#L593 Translate relative position to a bucket number for relative attention. The relative position is defined as memory_position - query_position, i.e. the distance in tokens from the attending position to the attended-to position. If bidirectional=False, then positive relative positions are invalid. We use smaller buckets for small absolute relative_position and larger buckets for larger absolute relative_positions. All relative positions >=max_distance map to the same bucket. All relative positions <=-max_distance map to the same bucket. This should allow for more graceful generalization to longer sequences than the model has been trained on Args: relative_position: an int32 Tensor bidirectional: a boolean - whether the attention is bidirectional num_buckets: an integer max_distance: an integer Returns: a Tensor with the same shape as relative_position, containing int32 values in the range [0, num_buckets) """ relative_buckets = 0 if bidirectional: num_buckets //= 2 relative_buckets += (relative_position > 0).to(torch.long) * num_buckets relative_position = torch.abs(relative_position) else: relative_position = -torch.min(relative_position, torch.zeros_like(relative_position)) # now relative_position is in the range [0, inf) # half of the buckets are for exact increments in positions max_exact = num_buckets // 2 is_small = relative_position < max_exact # The other half of the buckets are for logarithmically bigger bins in positions up to max_distance relative_position_if_large = max_exact + ( torch.log(relative_position.float() / max_exact) / math.log(max_distance / max_exact) * (num_buckets - max_exact) ).to(torch.long) relative_position_if_large = torch.min(relative_position_if_large, torch.full_like(relative_position_if_large, num_buckets - 1)) relative_buckets += torch.where(is_small, relative_position, relative_position_if_large) return relative_buckets def compute_bias(self, query_length, key_length, device): """Compute binned relative position bias""" context_position = torch.arange(query_length, dtype=torch.long, device=device)[:, None] memory_position = torch.arange(key_length, dtype=torch.long, device=device)[None, :] relative_position = memory_position - context_position # shape (query_length, key_length) relative_position_bucket = self._relative_position_bucket( relative_position, # shape (query_length, key_length) bidirectional=True, num_buckets=self.relative_attention_num_buckets, max_distance=self.relative_attention_max_distance, ) values = self.relative_attention_bias(relative_position_bucket) # shape (query_length, key_length, num_heads) values = values.permute([2, 0, 1]).unsqueeze(0) # shape (1, num_heads, query_length, key_length) return values def forward(self, x, past_bias=None): q = self.q(x) k = self.k(x) v = self.v(x) if self.relative_attention_bias is not None: past_bias = self.compute_bias(x.shape[1], x.shape[1], x.device) if past_bias is not None: mask = past_bias out = attention(q, k * ((k.shape[-1] / self.num_heads) ** 0.5), v, self.num_heads, mask) return self.o(out), past_bias class T5LayerSelfAttention(torch.nn.Module): def __init__(self, model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device): super().__init__() self.SelfAttention = T5Attention(model_dim, inner_dim, num_heads, relative_attention_bias, dtype, device) self.layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device) def forward(self, x, past_bias=None): output, past_bias = self.SelfAttention(self.layer_norm(x), past_bias=past_bias) x += output return x, past_bias class T5Block(torch.nn.Module): def __init__(self, model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device): super().__init__() self.layer = torch.nn.ModuleList() self.layer.append(T5LayerSelfAttention(model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device)) self.layer.append(T5LayerFF(model_dim, ff_dim, dtype, device)) def forward(self, x, past_bias=None): x, past_bias = self.layer[0](x, past_bias) x = self.layer[-1](x) return x, past_bias class T5Stack(torch.nn.Module): def __init__(self, num_layers, model_dim, inner_dim, ff_dim, num_heads, vocab_size, dtype, device): super().__init__() self.embed_tokens = torch.nn.Embedding(vocab_size, model_dim, device=device) self.block = torch.nn.ModuleList([T5Block(model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias=(i == 0), dtype=dtype, device=device) for i in range(num_layers)]) self.final_layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device) def forward(self, input_ids, intermediate_output=None, final_layer_norm_intermediate=True): intermediate = None x = self.embed_tokens(input_ids) past_bias = None for i, l in enumerate(self.block): x, past_bias = l(x, past_bias) if i == intermediate_output: intermediate = x.clone() x = self.final_layer_norm(x) if intermediate is not None and final_layer_norm_intermediate: intermediate = self.final_layer_norm(intermediate) return x, intermediate class T5(torch.nn.Module): def __init__(self, config_dict, dtype, device): super().__init__() self.num_layers = config_dict["num_layers"] self.encoder = T5Stack(self.num_layers, config_dict["d_model"], config_dict["d_model"], config_dict["d_ff"], config_dict["num_heads"], config_dict["vocab_size"], dtype, device) self.dtype = dtype def get_input_embeddings(self): return self.encoder.embed_tokens def set_input_embeddings(self, embeddings): self.encoder.embed_tokens = embeddings def forward(self, *args, **kwargs): return self.encoder(*args, **kwargs) ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_3/requirements.txt000066400000000000000000000027561510465702400310050ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### diffusers==0.30.3 einops==0.8.0 onnx==1.17.0 protobuf==5.28.3 transformers==4.46.0 tiktoken==0.8.0 sentencepiece==0.2.0 --extra-index-url https://test.pypi.org/simple hip-python-as-cudaROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_3/torch_requirements.txt000066400000000000000000000025611510465702400321760ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### --index-url https://download.pytorch.org/whl/rocm6.2/ torch ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_3/txt2img.py000066400000000000000000000530621510465702400274650ustar00rootroot00000000000000# The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # Copyright (c) 2024 Stability AI # # 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. from argparse import ArgumentParser from diffusers import FlowMatchEulerDiscreteScheduler from other_impls import SD3Tokenizer from PIL import Image import migraphx as mgx import os import sys import torch import time from functools import wraps from hip import hip from collections import namedtuple HipEventPair = namedtuple('HipEventPair', ['start', 'end']) # measurement helper def measure(fn): @wraps(fn) def measure_ms(*args, **kwargs): start_time = time.perf_counter_ns() result = fn(*args, **kwargs) end_time = time.perf_counter_ns() print( f"Elapsed time for {fn.__name__}: {(end_time - start_time) * 1e-6:.4f} ms\n" ) return result return measure_ms def get_args(): parser = ArgumentParser() # Model compile parser.add_argument( "--onnx-model-path", type=str, default="models/sd3", help="Path to onnx model files.", ) parser.add_argument( "--compiled-model-path", type=str, default=None, help= "Path to compiled mxr model files. If not set, it will be saved next to the onnx model.", ) parser.add_argument( "--fp16", choices=["all", "vae", "clip", "mmdit"], nargs="+", help="Quantize models with fp16 precision.", ) parser.add_argument( "--force-compile", action="store_true", default=False, help="Ignore existing .mxr files and override them", ) parser.add_argument( "--exhaustive-tune", action="store_true", default=False, help="Perform exhaustive tuning when compiling onnx models", ) parser.add_argument( "--skip-t5", action="store_true", default=False, help= "Skip the third text encoder. Small accuracy penalty but large memory savings." ) # Runtime parser.add_argument( "-s", "--seed", type=int, default=42, help="Random seed", ) parser.add_argument( "-t", "--steps", type=int, default=50, help="Number of steps", ) parser.add_argument("-b", "--batch", type=int, default=1, help="Batch count or number of images to produce") parser.add_argument( "-p", "--prompt", type=str, required=True, help="Prompt", ) parser.add_argument( "-n", "--negative-prompt", type=str, default="", help="Negative prompt", ) parser.add_argument( "--scale", type=float, default=5.0, help="Guidance scale", ) parser.add_argument( "-o", "--output", type=str, default=None, help="Output name", ) return parser.parse_args() mgx_to_torch_dtype_dict = { "bool_type": torch.bool, "uint8_type": torch.uint8, "int8_type": torch.int8, "int16_type": torch.int16, "int32_type": torch.int32, "int64_type": torch.int64, "float_type": torch.float32, "double_type": torch.float64, "half_type": torch.float16, } torch_to_mgx_dtype_dict = { value: key for (key, value) in mgx_to_torch_dtype_dict.items() } def tensor_to_arg(tensor): return mgx.argument_from_pointer( mgx.shape( **{ "type": torch_to_mgx_dtype_dict[tensor.dtype], "lens": list(tensor.size()), "strides": list(tensor.stride()) }), tensor.data_ptr()) def tensors_to_args(tensors): return {name: tensor_to_arg(tensor) for name, tensor in tensors.items()} def get_output_name(idx): return f"main:#output_{idx}" def copy_tensor_sync(tensor, data): tensor.copy_(data) torch.cuda.synchronize() def run_model_sync(model, args): model.run(args) mgx.gpu_sync() def allocate_torch_tensors(model): input_shapes = model.get_parameter_shapes() data_mapping = { name: torch.zeros(shape.lens()).to( mgx_to_torch_dtype_dict[shape.type_string()]).to(device="cuda") for name, shape in input_shapes.items() } return data_mapping class StableDiffusionMGX(): def __init__(self, onnx_model_path, compiled_model_path, fp16, batch, force_compile=False, exhaustive_tune=False, skip_t5=False): self.scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained( "stabilityai/stable-diffusion-3-medium-diffusers", subfolder="scheduler") self.tokenizer = SD3Tokenizer() self.device = "cuda" self.skip_t5 = skip_t5 if fp16 is None: fp16 = [] elif "all" in fp16: fp16 = ["vae", "clip", "mmdit"] self.batch = batch print("Load models...") self.models = { "vae": StableDiffusionMGX.load_mgx_model( "vae_decoder", {"latent_sample": [self.batch, 16, 128, 128]}, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="vae" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False, batch=self.batch), "clip-l": StableDiffusionMGX.load_mgx_model( "text_encoder", {"input_ids": [1, 77]}, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="clip" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "clip-g": StableDiffusionMGX.load_mgx_model( "text_encoder_2", {"input_ids": [1, 77]}, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="clip" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "mmdit": StableDiffusionMGX.load_mgx_model( "transformer", { "hidden_states": [2 * self.batch, 16, 128, 128], "timestep": [2 * self.batch], "encoder_hidden_states": [2 * self.batch, 154, 4096], "pooled_projections": [2 * self.batch, 2048], }, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="mmdit" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False, batch=self.batch) } self.tensors = { "clip-g": allocate_torch_tensors(self.models["clip-g"]), "clip-l": allocate_torch_tensors(self.models["clip-l"]), # "t5xxl": allocate_torch_tensors(self.models["t5xxl"]), "mmdit": allocate_torch_tensors(self.models["mmdit"]), "vae": allocate_torch_tensors(self.models["vae"]), } self.model_args = { "clip-g": tensors_to_args(self.tensors['clip-g']), "clip-l": tensors_to_args(self.tensors['clip-l']), # "t5xxl": tensors_to_args(self.tensors['t5xxl']), "mmdit": tensors_to_args(self.tensors['mmdit']), "vae": tensors_to_args(self.tensors['vae']), } if not self.skip_t5: self.models["t5xxl"] = StableDiffusionMGX.load_mgx_model( "text_encoder_3", {"input_ids": [1, 77]}, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="clip" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False) self.tensors["t5xxl"] = allocate_torch_tensors( self.models["t5xxl"]) self.model_args["t5xxl"] = tensors_to_args(self.tensors['t5xxl']) self.events = { "warmup": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "run": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "clip": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "denoise": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "decode": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), } self.stream = hip.hipStreamCreate()[1] def cleanup(self): for event in self.events.values(): hip.hipEventDestroy(event.start) hip.hipEventDestroy(event.end) hip.hipStreamDestroy(self.stream) def profile_start(self, name): if name in self.events: hip.hipEventRecord(self.events[name].start, None) def profile_end(self, name): if name in self.events: hip.hipEventRecord(self.events[name].end, None) @measure @torch.no_grad() def run(self, prompt, negative_prompt, steps, seed, scale): torch.cuda.synchronize() self.profile_start("run") print("Tokenizing prompts...") prompt_tokens = self.tokenize(prompt) neg_prompt_tokens = self.tokenize(negative_prompt) print("Creating text embeddings...") self.profile_start("clip") prompt_embeddings = self.get_embeddings(prompt_tokens) neg_prompt_embeddings = self.get_embeddings(neg_prompt_tokens) self.profile_end("clip") # fix height and width for now # TODO: check for valid height/width combinations # and make them member variables height = 1024 width = 1024 latent = torch.empty(1, 16, height // 8, width // 8, device="cpu") generator = torch.manual_seed(seed) latent = torch.randn(latent.size(), dtype=torch.float32, layout=latent.layout, generator=generator).to(latent.dtype) self.scheduler.set_timesteps(steps) timesteps = self.scheduler.timesteps print("Running denoising loop...") self.profile_start("denoise") for step in timesteps: latent = self.denoise(latent, prompt_embeddings, neg_prompt_embeddings, step, scale) self.profile_end("denoise") latent = (latent / 1.5305) + 0.0609 self.profile_start("decode") print("Decode denoised result...") image = self.decode(latent) self.profile_end("decode") torch.cuda.synchronize() self.profile_end("run") return image def print_summary(self, denoise_steps): print('WARMUP\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['warmup'].start, self.events['warmup'].end)[1])) print('CLIP\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['clip'].start, self.events['clip'].end)[1])) print('mmditx{}\t{:>9.2f} ms'.format( str(denoise_steps), hip.hipEventElapsedTime(self.events['denoise'].start, self.events['denoise'].end)[1])) print('VAE-Dec\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['decode'].start, self.events['decode'].end)[1])) print('RUN\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['run'].start, self.events['run'].end)[1])) @staticmethod @measure def load_mgx_model(name, shapes, onnx_model_path, compiled_model_path=None, use_fp16=False, force_compile=False, exhaustive_tune=False, offload_copy=True, batch=1): print(f"Loading {name} model...") if compiled_model_path is None: compiled_model_path = onnx_model_path onnx_file = f"{onnx_model_path}/{name}/model.onnx" mxr_file = f"{compiled_model_path}/{name}/model_{'fp16' if use_fp16 else 'fp32'}_b{batch}_{'gpu' if not offload_copy else 'oc'}.mxr" if not force_compile and os.path.isfile(mxr_file): print(f"Found mxr, loading it from {mxr_file}") model = mgx.load(mxr_file, format="msgpack") elif os.path.isfile(onnx_file): print(f"No mxr found at {mxr_file}") print(f"Parsing from {onnx_file}") model = mgx.parse_onnx(onnx_file, map_input_dims=shapes) if use_fp16: mgx.quantize_fp16(model) model.compile(mgx.get_target("gpu"), exhaustive_tune=exhaustive_tune, offload_copy=offload_copy) print(f"Saving {name} model to {mxr_file}") os.makedirs(os.path.dirname(mxr_file), exist_ok=True) mgx.save(model, mxr_file, format="msgpack") else: print( f"No {name} model found at {onnx_file} or {mxr_file}. Please download it and re-try." ) sys.exit(1) return model @measure def tokenize(self, prompt): return self.tokenizer.tokenize_with_weights(prompt) def encode_token_weights(self, model_name, token_weight_pairs): tokens = list(map(lambda a: a[0], token_weight_pairs[0])) tokens = torch.tensor([tokens], dtype=torch.int64, device=self.device) copy_tensor_sync(self.tensors[model_name]["input_ids"], tokens.to(torch.int32)) run_model_sync(self.models[model_name], self.model_args[model_name]) encoder_out = self.tensors[model_name][get_output_name(0)] encoder_out2 = None if model_name != 't5xxl': # flipped outputs for clip text encoders... encoder_out2 = encoder_out encoder_out = self.tensors[model_name][get_output_name(1)] if encoder_out2 is not None: first_pooled = encoder_out2[0:1] else: first_pooled = encoder_out2 output = [encoder_out[0:1]] return torch.cat(output, dim=-2), first_pooled @measure def get_embeddings(self, prompt_tokens): l_out, l_pooled = self.encode_token_weights("clip-l", prompt_tokens["l"]) # stable-diffusion-3-lite-onnx has swapped outputs for clip-l text encoder if l_out.shape != (1, 77, 768): l_out, l_pooled = l_pooled, l_out g_out, g_pooled = self.encode_token_weights("clip-g", prompt_tokens["g"]) if not self.skip_t5: t5_out, _ = self.encode_token_weights("t5xxl", prompt_tokens["t5xxl"]) else: t5_out = torch.zeros((1, 77, 4096)).cuda() lg_out = torch.cat([l_out, g_out], dim=-1) lg_out = torch.nn.functional.pad(lg_out, (0, 4096 - lg_out.shape[-1])) return torch.cat([lg_out, t5_out], dim=-2), torch.cat( (l_pooled, g_pooled), dim=-1) @staticmethod def convert_to_rgb_image(image): image = (image / 2 + 0.5).clamp(0, 1) image = image.detach().cpu().permute(0, 2, 3, 1).numpy() images = (image * 255).round().astype("uint8") return [Image.fromarray(images[i]) for i in range(images.shape[0])] @staticmethod def save_image(pil_image, filename="output.png"): pil_image.save(filename) def CFGDenoiser(self, x, timestep, cond, uncond, cond_scale): # Run cond and uncond in a batch together x_concat = torch.cat([x, x]) timestep_concat = timestep.expand([2]) c_crossattn = torch.cat([cond["c_crossattn"], uncond["c_crossattn"]]) y = torch.cat([cond["y"], uncond["y"]]) copy_tensor_sync(self.tensors["mmdit"]["hidden_states"], x_concat) copy_tensor_sync(self.tensors["mmdit"]["timestep"], timestep_concat) copy_tensor_sync(self.tensors["mmdit"]["encoder_hidden_states"], c_crossattn) copy_tensor_sync(self.tensors["mmdit"]["pooled_projections"], y) run_model_sync(self.models["mmdit"], self.model_args['mmdit']) mmdit_out = self.tensors["mmdit"][get_output_name(0)] # Then split and apply CFG Scaling pos_out, neg_out = torch.tensor_split(mmdit_out, 2) scaled = neg_out + (pos_out - neg_out) * cond_scale # scheduler step function requies all tensors be on the CPU scaled = scaled.detach().clone().cpu() scheduler_out = self.scheduler.step(model_output=scaled, timestep=timestep, sample=x, return_dict=False)[0] return scheduler_out def fix_cond(self, cond): cond, pooled = (cond[0].cuda(), cond[1].cuda()) return {"c_crossattn": cond, "y": pooled} def denoise(self, latent, conditioning, neg_cond, step, cfg_scale): conditioning = self.fix_cond(conditioning) neg_cond = self.fix_cond(neg_cond) return self.CFGDenoiser(latent, step, conditioning, neg_cond, cfg_scale) @measure def decode(self, latents): copy_tensor_sync(self.tensors["vae"]["latent_sample"], latents) run_model_sync(self.models["vae"], self.model_args["vae"]) return self.tensors["vae"][get_output_name(0)] @measure def warmup(self, num_runs): self.profile_start("warmup") copy_tensor_sync(self.tensors["clip-l"]["input_ids"], torch.ones((1, 77)).to(torch.int32)) copy_tensor_sync(self.tensors["clip-g"]["input_ids"], torch.ones((1, 77)).to(torch.int32)) if not self.skip_t5: copy_tensor_sync(self.tensors["t5xxl"]["input_ids"], torch.ones((1, 77)).to(torch.int32)) copy_tensor_sync( self.tensors["mmdit"]["hidden_states"], torch.randn((2 * self.batch, 16, 128, 128)).to(torch.float)) copy_tensor_sync(self.tensors["mmdit"]["timestep"], torch.randn((2 * self.batch)).to(torch.float)) copy_tensor_sync( self.tensors["mmdit"]["encoder_hidden_states"], torch.randn((2 * self.batch, 154, 4096)).to(torch.float)) copy_tensor_sync(self.tensors["mmdit"]["pooled_projections"], torch.randn((2 * self.batch, 2048)).to(torch.float)) copy_tensor_sync( self.tensors["vae"]["latent_sample"], torch.randn((self.batch, 16, 128, 128)).to(torch.float)) for _ in range(num_runs): run_model_sync(self.models["clip-l"], self.model_args["clip-l"]) run_model_sync(self.models["clip-g"], self.model_args["clip-g"]) if not self.skip_t5: run_model_sync(self.models["t5xxl"], self.model_args["t5xxl"]) run_model_sync(self.models["mmdit"], self.model_args["mmdit"]) run_model_sync(self.models["vae"], self.model_args["vae"]) self.profile_end("warmup") if __name__ == "__main__": args = get_args() sd = StableDiffusionMGX(args.onnx_model_path, args.compiled_model_path, args.fp16, args.batch, args.force_compile, args.exhaustive_tune, args.skip_t5) print("Warmup") sd.warmup(5) print("Run") result = sd.run(args.prompt, args.negative_prompt, args.steps, args.seed, args.scale) print("Summary") sd.print_summary(args.steps) print("Cleanup") sd.cleanup() print("Convert result to rgb image...") images = StableDiffusionMGX.convert_to_rgb_image(result) for i, image in enumerate(images): filename = f"{args.batch}_{args.output}" if args.output else f"output_s{args.seed}_t{args.steps}_{i}.png" StableDiffusionMGX.save_image(image, filename) print(f"Image saved to {filename}") ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/000077500000000000000000000000001510465702400257705ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/README.md000066400000000000000000000122501510465702400272470ustar00rootroot00000000000000# Stable Diffusion XL This version was tested with [rocm 6.0](https://github.com/ROCmSoftwarePlatform/AMDMIGraphX/tree/rocm-6.0.0) revision. ## Console application To run the console application, follow these steps below. Setup python environment ```bash # this will require the python venv to installed (e.g. apt install python3.8-venv) python3 -m venv sd_venv . sd_venv/bin/activate ``` Install dependencies ```bash pip install -U pip pip install -r torch_requirements.txt -r requirements.txt ``` Use MIGraphX Python Module ```bash export PYTHONPATH=/opt/rocm/lib:$PYTHONPATH ``` Get models with huggingface-cli ### Base version ```bash huggingface-cli download stabilityai/stable-diffusion-xl-base-1.0 text_encoder/model.onnx text_encoder_2/model.onnx text_encoder_2/model.onnx_data unet/model.onnx unet/model.onnx_data vae_decoder/model.onnx --local-dir models/sdxl-1.0-base/ --local-dir-use-symlinks False ``` ### Opt version ```bash huggingface-cli download stabilityai/stable-diffusion-xl-base-1.0 vae_decoder/model.onnx --local-dir models/sdxl-1.0-base/ --local-dir-use-symlinks False huggingface-cli download stabilityai/stable-diffusion-xl-1.0-tensorrt sdxl-1.0-base/clip.opt/model.onnx sdxl-1.0-base/clip2.opt/model.onnx sdxl-1.0-base/unetxl.opt/model.onnx sdxl-1.0-base/unetxl.opt/435d4c0a-2d32-11ee-8476-0242c0a80101 --local-dir models/ --local-dir-use-symlinks False ``` Convert CLIP models to expose "hidden_state" as output. ```bash # clip.opt python clip_modifier.py -i models/sdxl-1.0-base/clip.opt/model.onnx -o models/sdxl-1.0-base/clip.opt.mod/model.onnx # clip2.opt python clip_modifier.py -i models/sdxl-1.0-base/clip2.opt/model.onnx -o models/sdxl-1.0-base/clip2.opt.mod/model.onnx ``` ### Turbo version ```bash huggingface-cli download stabilityai/sdxl-turbo text_encoder/model.onnx text_encoder_2/model.onnx text_encoder_2/model.onnx_data unet/model.onnx unet/model.onnx_data vae_decoder/model.onnx --local-dir models/sdxl-turbo/ --local-dir-use-symlinks False ``` ### Running txt2img Run the text-to-image script with the following example prompt and seed: Set `pipeline-type` based on the version of models you downloaded: `sdxl` for base, `sdxl-opt` for opt, `sdxl-turbo` for turbo ```bash python txt2img.py --prompt "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" --seed 42 --output jungle_astro.jpg --pipeline-type ``` > [!NOTE] > The first run will compile the models and cache them to make subsequent runs faster. The result should look like this: ![example_output.jpg](./example_output.jpg) ## (Optional) Refiner > [!IMPORTANT] > requires `Console application` to work Get models with huggingface-cli > [!NOTE] > Only the opt version provides an onnx model, but can be used for all 3 version (`sdxl`, `sdxl-opt`, `sdxl-turbo`) ```bash huggingface-cli download stabilityai/stable-diffusion-xl-1.0-tensorrt sdxl-1.0-refiner/clip2.opt/model.onnx sdxl-1.0-refiner/unetxl.opt/model.onnx sdxl-1.0-refiner/unetxl.opt/6ed855ee-2d70-11ee-af8e-0242c0a80101 sdxl-1.0-refiner/unetxl.opt/6e186582-2d74-11ee-8aa7-0242c0a80102 --local-dir models/ --local-dir-use-symlinks False ``` Convert CLIP2 model to expose "hidden_state" as output. ```bash # clip2.opt python clip_modifier.py -i models/sdxl-1.0-refiner/clip2.opt/model.onnx -o models/sdxl-1.0-refiner/clip2.opt.mod/model.onnx ``` Run the text-to-image script with the following example prompt and seed: Set `pipeline-type` based on which version of models you have: `sdxl` for base, `sdxl-opt` for opt, `sdxl-turbo` for turbo ```bash python txt2img.py --prompt "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" --seed 42 --output refined_jungle_astro.jpg --pipeline-type --use-refiner ``` ## (Optional) FP16 Quantization > [!IMPORTANT] > requires `Console application` to work Quantizing the StabilityAI VAE model produces NaNs in the output, so we need to download a modified version to fix this. Download and export the fp16 fixed version: ```bash python vae_fp16.py ``` > [!NOTE] > By default, the path being saved to is `models/sdxl-1.0-base/vae_decoder_fp16_fix/model.onnx`. This can be changed by passing in `--output=/vae_decoder_fp16_fix/model.onnx`. Run the same text-to-image script as before, except add `--fp16=all` to the command. For example: ```bash python txt2img.py --prompt "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" --seed 42 --output jungle_astro.jpg --pipeline-type --fp16=all ``` Using the original StabilityAI VAE is still possible while quantizing the rest of the pipeline to fp16. To do this, set the flag to `--fp16="clip,clip2,unetxl"`. ## Gradio application > [!IMPORTANT] > requires `Console application` to work Install gradio dependencies ```bash pip install -r gradio_requirements.txt ``` Usage Set `pipeline-type` based on which version of models you have: `sdxl` for base, `sdxl-opt` for opt, `sdxl-turbo` for turbo ```bash python gradio_app.py -p "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" --pipeline-type ``` This will load the models (which can take several minutes), and when the setup is ready, starts a server on `http://127.0.0.1:7860`. ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/clip_modifier.py000066400000000000000000000040041510465702400311450ustar00rootroot00000000000000# The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. from argparse import ArgumentParser import onnx import os def argparser(): parser = ArgumentParser() parser.add_argument("-i", "--model_path", required=True) parser.add_argument("-o", "--output_path", required=True) return parser.parse_args() def add_nodes_as_output(model, nodes): shape_info = onnx.shape_inference.infer_shapes(model) output_nodes = [ node for node in shape_info.graph.value_info if node.name in nodes ] model.graph.output.extend(output_nodes) onnx.checker.check_model(model) return model def modify_model(model_path, output_path): model = onnx.load(model_path) model = add_nodes_as_output(model, ["hidden_states"]) os.makedirs(os.path.dirname(output_path), exist_ok=True) onnx.save(model, output_path) if __name__ == "__main__": args = argparser() modify_model(**vars(args)) ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/example_output.jpg000066400000000000000000004011451510465702400315520ustar00rootroot00000000000000ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?©²Dç÷méEG,ò,~­1Q×ôª7×ÞaŒö÷4Ììbê7¤1È^§ÔÖu:G,ēɦU \ÑIFh´f’Š\ÑšJ3@ š))hsKH*T‹pÎq@ zت$`‘SBåW  ò˱x?1éQ@…•Û<ô«—,Ùnµy$Xa]Çñ¤1‚ÞRzŒ¼Š±%º˜v*øsüé‹}0PXçÚœ÷‘ƼçÓP"²Ø9=vVSS%µÌ )‘Ýútw±ÊÛQH8úZ˜ÌËü ¯øƒ@É¢Ôn!l\!lúŒÕ·¿‚àWð°Åc¬Á×hRAìGË¥¬5•Š”nyÿáF=tر¬ŒæCŒ •NêÔ[±‰Îkk˜• iP|«•ï¸T1#T~´à¿º$c¾)PqÍ!‘ðxý)*¨*9œ–ã·µ@Iõ¦2qpÁ³ÁÅ$˜Û¹Oiw¸Ïz3K“Àô¤½  f†1ŽåW8V# ÷®8•Íi0y—aºæº>*X‰sŠpsŒuY¦ ~QšŒÌǰ¥a\¶Ä TL¸ÔÅŸ§11‡=Ã¥=úû åÃcýjœ(É`>µDضO~õVy63UšèžTçßý*%r9ô KóÔŸ­ªÑ8<ã-ëSoÏó@ÚsHGÜóŠ\{æX†XC޵VdaÿÖ«„×õ„ Qq”M4š–UÃqÀ¨M2…Í4ÒÒPfDj^1JBª½qL/è)i0OAšBs@$w–ÞŸ8@OqHb¬ØëÍH.qÛZo;š< ?Š Éû ŸÂ”30ëP;ÿŸÎ€äç4 ·œš…Ÿ?Z•\qMx»­F¬C y9EÞ¤n(£ƒW,îL~SÔURc“ßå gJŽ®€ŽA¬ÛøJ‚8ÇQþË+²Œ#sòžÕ¥"y±íü±S°Žs§(&¬MŽC’éQ³*޹ª¸„Ó¸¨ZbzÔÓ<Æõü¨ †3V-n ¼ë `÷Åfo=ÉüéñŒœôè¶î“B®„a‘Šç5í#É&æÂ–_JMWò yîØð} uL‰4EXeXb²Ö,×âGš’9Ý3‘îkOZÒÂc"‚acÁôö¬ŒzV©Ü̱ö†êO«0=Oæj»tŸn9Ï•·‘Œd÷«xô¨ à š1ËÏnô¹¥¦ãÒ€Fi( ''®GÊ9ÍHp2i…€'Œ“@Èö»“€zóŠnÐ%P1ÔT›%nØmÛ*üÜýOøÐ”™Tc$ãó®ÀDŠ1\•°êç¢ÿ:ì+*SZg+¯ÛîÇ ùÊŒúrk$6HÇ'Œ-oø¤‘kãur©)C ¸;¢dµ6c_d ÷?çüju㟥fÅx¬¹èGnçéZP>>`yîùçñ¦É°­f’ý?ÏåUæ°hù^G·ZÔVÜ›»{ÑÎ:â•Äbª;|ß&GBÄgùäÒ烌}:V¤w†ôõªrÄÌIä7¿?ýqúÓLB»çzÍÔnI<²ÙÙÁ÷5£q7Ù­‰ÎXŒ­`;I&‚†šJ))€QE´RRÐIEZZJ(ñV£`ª«WT|ƒé@™PÔ‰Âæ˜iãîŠIËN²àzRÁŽXö¨ÇÎüœgÖ€ “×—-óè*ôv£`Ãdús7ÏgØñJâ(Å;Bû‡åZv—°Hʲ¨œ`ŠÉ’7¶¸ ûÓ2A¦f±Aü(¼t©A*pI ñÖ¹Ë-Y£9²WûÝOó­A¨Cåoó¯áŸÊ¦Â5TáQÏùüjÆIqøVdK%¸”sÆpzñRZ_­Ä`/±4¬Q|"ŽO'ÔÓ¾_¯ëþ5\î’ÔÿE"\¿ q·ßnž -œtâš@ÿõŠÍ{+ÇñüÄúróªi·yëæc¾ÿ\Ña›§gû4ÆU+ÜÒ¹¹,n`ƤuåþkUØÜBr\wFû)r§«iÁâ2G‚GLW5ҴƧ8L„ƒÙ†ÀÖkXŸZ´„%L¿,yõ¨ELü1ž”*å¹7_¥>5êh¬¸5-¾Ï5wò¹æ˜ç'§J“@ÑÏ?Ï¥XŠé]¶çÔƒŠÆ¶µš\•Oz¼–G¹ãéH P¨W$äù¤) 쟥VŽÕdaôâ®Efæó%×{súÒEWŠÕTúuÉúU«K†b‘ˆEÆ\°±$9!yõ<š«pÊ!ˆíŒãü*orÖ‡A<•ãµQ½¹hdت0W9Í.•9’Ø‚Äí=ù¦ê æO“‘Ò¡+3VýÝ ‹2°÷Rö#>N­¦Ëü,3õ¥k}«ÉǨÏÿ^­I8˜Ù÷v€?là´ ~Vôö5³¡k_iQmtàHÊÄýï®OZÛžîahä]ÈÃüŠÍ^,¶¹:<ö©ãP{ÔúžœúuÉB2Ê7Lþµðô­/s2Ü#åÍIL‹”%2D'÷¦Ÿ½øSda»t¡w¹ÎÜqÅJ=/Ê‹–#ëþ¥0ÊÍ÷R(ÿ)@Ëè)7 QŸ÷Fi[9 1õfÏô ‡#–– íü+®*˜Ì§ þ_ãO1ƒ÷˜Ÿ©¦cmÂÒ€Eë^/ ÿ®‹üë°"¸Ûs‹¸9ÿ–‹üë²&²©¹µ=ŽsÅñëûßÒ¹k«ñkfGûF¹#õ«†Â–äŠÜŒšÔ‚ùW‚¤ ÖdQ³ ç«öút²€@ ý?™Ê›$ÓŠö7 ÀûÕ´}ÃüóT"Òd$Ióô©‡™ •p ûÀñšµË †xƒ¡8ÉžŽ:pOó©G4 ÇqçNBŸ•xþõLš3Ißž•`%ÒŠ`QE (£€(©…»ÙÉÆ=z~}*@ @¤¥ ­^ÇËøU$û´6ä~Ê&ŸØ})í£cëOKvaÔPP’sP޵bH% asôÅC岜#𤢶ȳè*Ho#› 7¡ªÒ¶Íô¬¦l1Å:)mã0Êõô¬‹­:HyL²~´¶úŒ±`7̾ýEkÁu Âü¬=ÁëüèšäS•Èï[·zds‚Éò?¯­cMk, µÔCëBOJ» ˜\ðÝ?*Ô³Óá’î\6â88â¹h]£pG×Qg:´ î*=(h mb„îO0ãÕÉ—J”ÍõeüÅe´pÜœ¥Áb;nÝüè°ÇžÓ?ãQašFæüiùƒPɨ['(ÿ «ý˜ÇÎÄ}?ÄÔRh¡—ýi>Åúôh¯í S÷.SóÅG,Ðä$ŠýGëYMÌ9!w¯ªji!l®ä=ùÅRH ×v)&Z41·§8þU“,/ ÃÆµa½FP¯Ãc“Ž?J–EIPƒÈ=9¦#Tñ.õäò:Q<Kãªö4Ôr¤{`7aÎ1Ï¥HŠÊyFüF*ÈEo˜õ®„ŸçH.fˆüÃè}Ƶ­íaù]½sš®‰˜öªw7!¸@Arn4±Æ9eÜŠ«>£)Ýî?úõ†]‰ëVôëcwv¨OËÔŸj`tÚj™ H€dü s‘ëW¤•#BÌÀÜ‘Q4‰<¨ƒŽ:cð®~òõî¦åŽÁ÷Tö¨µÊ½µ>A¹¦:ýMGoŽ.zšY0Ì9ôÍ;{ššC‚ò(qéÒ®_8ŒE!8UnMghÍ‹™9ùjî±ÿ ÷#·"³´~ø`y¨ÝщFLô®nßÄEcÄ‘ÜTÖž¨& d\maØúPãborËÛN1CÿêªòÛî§ãVdg·Á1ûv¤óC®ä*ãò5Iâd¼J G¨áQ˜Óû£òÿëU»©76 dà:ÕR@ôhÌnÔèWô§PŒí–(P¤óŠš€ kdj…ôôlpjíÐ@ˆ)ê)Ô¤dsQõ]´âs¶@GÖÜΣ’ëp!Wñ&©fŸœŠbÕµÃE l’;ûÖÀetƒ\îìö«öW{~FSëíPŸ+±m_Sš„búQ#ì&¤”ˆ~YÖc½V;´„c·5¡™" 3rO_J†[°¿*r}qþsPÏpOÊ;œUeVv ¼“L rdMîrsÔÕŠ¯» ÉoÀUŠÂò1KHN75ÇÂb¥úÔL~ƒ?•-ÛÇäõÑv¸ûøûƒþº-v8¬ªnkOcšñ`ÿG„ÿ´k˜‚ÚK‰F»‰=+©ñS/‘’\ñøT:òzÒ€%»ÓÌ ½2ÉüªÖž²=£ª`œàŽ•j˜®£89õ·ó¨`í/7 f‰¸$dIØÜ†@_}Àcõ­ ⢄pœõüúÕ‘2’`sÓÿZfB)Ú8éþ®2¯Ú¯¡åá(îõS×Týe¼©ÏeÏøUÈîcp Œ{ž?ZÁ£*T~ŸÏ¯ë@ÊÑß[KÂȹôcƒúâ’âÞó '׿çÖ’ãNV\”Ž˜'úµS1Ïk’›ŠªyÇê¥VëKdÆw/¡äÿŸóŠË2¼$ŽˆÒCt’‚ÁAªš€¸MñàH;zÓL“kƒ*€F¨A箬ŽU 8 ÔB$cžEPË? “ÃuúÖŠPcÒ³#O–zƒšÓŒ)~”‰dyË0ïëŠÍš,Hp03Ò´qU®b˜¼¢ t:liajÒH@fcžßdñ£žpig»’sŒ…'¨Éï5¹8*Ó¢Ö3#®GËœõ¨ ˆ;óÓ©­8îÈ(` zT{”ݪC¦ — =zóÚ´f&ì³g,¤Ö¦¦»´ùGû'ùV^–BݧN•¯{ƒi 'Íîm„à”+CM¼{+…‘OÊxaŽ¢¯¥œ ÅH¶pÏ5>ø­ ®t*#ž À) 3œV]ÜFÊ@ÊË=Ç CWì`ÙÓoz ’ææ‹lª}Íd´f»«˜¦îBÐŽà°þF²/$BøŠ]Ë×8­>‡m&Lr:LîB] eÉIco®Ehš3hŽ­ã9ªÂÍŽƒüþUB[;‹™ÐÓpä:`:ƒ¥Y h_CùQçúŸÖªŒzÈRðzÒdK¸àÿŸÖ•ˆ«ƒš~íÜö¦· ð8¿ÔEðqÔú †PÌ9l{v šxûG¡ÿ ¢÷c9T\ú‘D‘’y|ãÚ«H u4‰…Ìöý(ûTÙæWüÍTߊBäŒP.‹¹ûJçñ¤7R‘‚ÙúþL9 bfº'øþùÃ9?¿‡ÿ®¡ÍÄ™ÝÍ ÐcšEû„㟥9žÜPG¸4 “NÙΤ`p(3úR‚(# Ò›õ ¯=¿Zvi«ÃT™… ‘IœQòö8úQÏ@hAõ¤![ž>´Ör;f¢'åþt.Ö_ºÔÑ3£4Ó#)ÇÞšNãך˜:7= ;'×?Z¬F)Êå~ž”7~i¤”ÇjSÈÍ3œR7nz q÷©#„0Ë; 6(Á$¸àS¤¹Ç Î;ÿ“Nö®ÄüHÿõÕ3Œñ@!ÛÉ9bI÷ óÒ™OS@)àñLïJ(ÝO4àJši^2*HÔ6TÐëkË´žzrzÒîI´°ô•DgzËûCŸz@ÄQ¸åIàñQlÜ}=ªÌRnO)ŽAèOÿ^¢ÚQ¶‘ÿצ€zŠxßÄSÕsÒ~lÕ…¨Õp)ÙÀÉ  ÖÉʈþbz¯¨­¢ó‘Áþé úuü³\ìj¶s‚Aã±ö­6ûÍ&[.½÷‡øÖrF‘ìgjök&n¢8Ëlä7¿Ö²%$[qÎkµ’ßz³FÍ÷‡cïõþu4 Á©¨#··Z#.‚’ê`­¼ŒFÇ=ñW­íÄIþÑêjú®óòóߟþ½DT†*$ÕÈØ…5¹ixqSJ«~N*¬±f=MbznsÍ ÒŒ`b˜„'Ž•U´êIç=êÑéUÎß5 ¯~´ ã7vÿõÐ:닾خFßþ> ÿ®‚·5 Á©!¾fásYÍ]šAÙî¶^^4¯’#ùǹ¬¨¯>Í3á$å@ÿëUÄc…D’&üdüãëëXÒäÈÍÇ'<i å©/¥‘ÎÇ``z °57H¨ŽüãúVW$`Uû;‡"›°ÕÛ%šß9 qôíüêß/NÜç’OãÔ ÙKë!9>ô’XG—ž•ȾFa›u=ùëV,ÖX7„”à‘ŸOóúV”ZvI.äsü ÿõ©/-Ò0¡0Ò3q–Š9ú#±ÄŠ(¥­L„§¢å†O¢2FEM¡#žOJcÆAëßò4“»híW ÿƒøUyDDœIúV¤¥4” (¢Š)E%8P°ÿ¬_­iÿ ¬ØÖ/Ö´ÇNh%™®rjhxŒ}{ÕŠGµH¡Ò™²¹4°­yôõ<©" [g†MÄqê9 .-Ûb5Q%dmÈÅO¨5bí²@•Lñ@6š˜8I°?ÚÖ´®bÚÁYO ×-š¹i-³pK/u&•€uî—%¶]rÑúÈúÕÅuv·Ý&€nêÏ5GQÑÁkaTÃü(¸Ìˆ¥hœ20®ŠÆñf‹©V{#úõ®`‚§`Ô‘Lñ>ä$n(°—QJØ.ß)8<ÿõéÑܺœ+ù£Ñºþ}J϶’ –ØËµÍ^ŽÕc`ÊçèiX =¼ŒÇ—!ü?Z˜$‘r­¸{ñâ©Ë  ÃÐãúÕkùmÆùdÎÏO¥nEwûß)í‘ά0Wá‡>õM 7‚ ²öè?—˜[ C’ðö?Ýüøü¿*V·v³$GkŽxÿ#5F;ÒåÍÁn?Ò’ú4+ó¤u9àþU‡«Ê:”`IA¦/mVå7¯ß‚;ÕUÀrG=9½óEòžSÓÒ§Žtw¯9˜‡lV5mÀFŒ~N"|àv§^ÈË*y4,Ü:œ­$ª}úÒ9ó"àvÈ¢!º=§4ÄV‘ GɵGe›õ©Ò3›rzãÚ¬ÅÕcÔ稠5¦’MX·BzäõéLo•=ýêhÆÄ=)òrqéT÷o‘ß?ŸJµ&V#×'ùÔQƱÆ7΀&ÓçÛ{ôËb·®óöY1èkµ jàc :zæº9Îmä¶ž•œ·5†Æ&]=±O·›Ìàýà9Çz@ü*P£ï\íî*̺›z{Ÿ4®x"®\EæÛÉ÷”ÇzÁ†÷È*íÈÇõ­5ÕíÝz²öÃ/øf³kSHµkòjSÅÃI»Ÿÿ¯ÍYXVá×iÿ>ŸáUnV ¯fØ>]Ù]§ÕŠ >èosÍidEÚ.É}±8lŽøÅfHTŸ•F3Û+GRpzŽ*â<–éÛŽi¤&îN °ÏS€ÞªñÊdbGqÔÔ›˜ûS$ªOZP~QUŒ È˜§^[>ãÍê¨ëÓ4 Ü Äsë@ ÈØäþµ úйÝÔõëMmÄsÐPtQE%Q@:’”P±}õúÖ˜éYqýáõ­UPK3Ûï­XóLj¤UÛï­9ÏÊ´ ¿ â· Á««†š¹i;©ÆâT”„Ñ¡-œR™F}{ÖmƘé“Ü=Zºo•\©S€qsV#™$VQ`ÔæÞ6C†R¸¤®–Kxåu{Š¥6”§˜ÉÐÑpæ2’FF ¤‚:J׳ÖÂN2?¾?fMi,ç^=GJˆP3zöÆ´3@Êóx?ZÃxÚ'*Ãv5=µÜ–í”'¨ÉÁ­äj1õÛ ºŠ6™ny<ãŠÑ‡Sh‰G”wÏ?Ϊy ²ŸÌ¿­@êÁ‰aÔÐIÜS‘ùîò¨ol¼Ðdˆüþ™ëX ##RAÅkÙj»ˆŽbsýïñ  pÜKg6å$Ã/­o[^GuTàÿñÅT¾²K•2E ôïXÑM-¬Ù\«éøRKU´1ÇæÄpŸÄ½«‰5Ñ%ÔwvìÆFW;*ì‘—Ž?Ï4Ð äž*Em z÷ÉÿGÍÍH~Q¹I˜ìØ›• –÷©nŠ’tÞÇô¨ôïÞ]®âNëQÞö¹2O—Q­d(¸©QJH}+>ÝÊL§Ô⵸%ONÔÄÆ•ãÒ¥ÁòúuŒ™\~Tÿùgϧ4„Bs½W¶sVä`ž3PF33Øb§‹îƒëÍbK‚ÀÂòi; çŠo,ìr1ô¨®t‰=yn´<#iÏRNk }¿'ªÿJçb8 WAͲçºÔHº}Lň4jÊ{Þ”|à©Ï[¶T/SS2|À‚2Z*$~[´M÷O#5JáÌ ƒŸoqZ²§1¶9Ϧj½õ¸–#Ç̼ÓB2oÞîÉžy«xç?­TŽ ÒpGåV˜ã©éëT!® ÁÏ#×5Tžy©^qü#&«î%¹}(båǽ=ØŸ~57–ùÇQÒ¬(Ú9åZS•˜T¥!§OJÈg˱õ5©lÀÀŸJÉ€$gŠc =N}±ÿשTü£éQÊ äÖ[–VÕSW¯‡Ì* ¤£4”ôf€‚:ŠZé‡=\Š6EANSƒÅK‚Edf”Ž(èÃaé{PàŽr 4pzP1æHÅ.8ÇçJ1Žr3éRѺ¯ù4¤ÎNy©<µ”n QNÆG‚2)mlu½!äb˜-Š•†icL¾ïjYbŽ*‘5bå¹ >µXÐ4*¶Ó‘R¬¾£ò¨8P 220E;nzÏÿÕU‘Êž*ÂJ^ 1 #ýlÓ„jiAÍ/j6²Ž:SA æ”?¨§ÜÒÚdqNPAÁ¦¯ÊjaÍÍI Úv“Ö˜8~´$ÈGï¨ûþTÀÁ€"§R3cTæSºqHbî󠩇k´täÓÉÀ”¿3M¥ëMèy¦™ ¤¿¤j&Ú_³ÊO–Oýò³3ƒÅ$ƒpÏsI«;¬ð¥Ô t#¨íé\f¡bö“äÉþëVÞ©yÈ-¥9‘~écÔUíBÉ/튰ÎU‡cøVjñf])E^€~é~•ÌO€‡SŒ’5qÈö­L™g´€ñÿÖ¥;Ð!¥I9¦4dŽ9æ¤cÍ( c¢âhqýñüëJï?g¼¸üÅfÇÌñsücñæ´oØ%©?í/ˆ©–åGb ¤À95˜˜ÁúÔÏ!v'·aUÕ¸lzšhLIÛ Ÿ^1QC(GÉQ?cQ¢î`sŠ`kE†PÃiÏ¥H݆Á œ¿ýz†1±¯NÙ 33c£ùÿ…HÉﱆ ŒœôãùÓ» a×þøÏõ¦ ÊÜ*ö#‘ïV7ò(Ê•Ìa»çš©ò)ÊÙB)«ÃT!Wå¡§7úÜg©ëI»'š~ù‘Ú€ÑA½ñLp:gŠ–Oº½1†p*®F %9±šm%´POŒ)a¸)”áÔP"ìvñžŽ1W‘8ëYJOÿ®´-Hsë@˜5š·Br…¬¤ìÊJ•žC+瞘aUñ– ni Í6ÒàÏÓš–Þ7Rr¤qW<ÈÇV_çR!Bs€@öÀÏ•%rp¼gÓüjË d†SùVèh›¦áþ¾Tl1c@®gÛê$aeÿ¾…h¤«"‚¤íPµ„-ÛLT-dñÐHAô ZY >µ™w¦uxxõZ•/]>YW§qþ®E*J>SH5G7´©Á>õnÊ6Mň Ïü+NêÆ;¹xQýj²iŠH1Ÿ_þ½"̸aÁàã?þºxD(P+B×É9Q‚Nx¬ûø$·ùâÇéÔŠI™{n"}ÉÊŸÒªî«NÒ¦ÖþuSq@jXjFÝHNÎÇÒ§ÔmÖhüø±2qŽEan«ÞË2•#£”¬1‹3ÄÙF*}ª"Ù œœÒSéÖ•ÎqMN 9¥bíþ}hGHæäŸE5 ã7R8Ü}*ÆŒ?{!ÿf§[h™Ë*ÙÏÞ¥ÔB¤i´ª=8"ŒžÜTª  b‘”)à~Ó&â‡ÁÁ–(c”?Öšw§ò‘@ˆ¢ÈIÞ§gòâãéÒ™íR?Úô¤œ€ÎzÐ1 ª±ÏÊz¯ ù…¤b2N= 1¢Z㎠öæ¨éþ  Ø#· nµ^zŽH®J YN7{×Qlãìqãå2ECFT³8lCüêá8tϯôª6§øŸóúT×yléÏ€–àf#˜¾0}E8΂0ùÀëTã›fWøZv%•¥¼íëÁ¥A~]‰ÿdp*| å‡CEP›# t~TÆŠ1ÎÑìqR³`f¡f-Ééé@イ7¥8Ì yïŠYùCíT]ˆÏáAC3ÍiY¶`Ç¡¬¬óZ-û¶ô4†1M‘ÀRN*2à ž•Ÿu9b=(s/˜ÇTÐNNiÖ‚€sNüi Ó†O­à £fmå2§ú£ÔqòÔÅô£Ô§Aõ£¯½¡™äñNQòŠN™§ c:#þØþb¯êÃ<yGëTS&Xñýõþb¶§·Yã &vä6|TIÙ—Ž|/ÊXñš…y>¦­ß „~UR4ùCg¯8ªD±®»”M‰ ¶ãÔ*\@ÀÒà_ÀÓ£“’9''ŠLR‸Hß½Z´_ŒÖ©‘—]Õ>sž¦“„‡{ÒŽLö¦ xfäÓíßAøâœ?ÂЃM°ëÓñ§d7ð >\äšM¹ö§€»nÝñQã#()2N{v¦TÒÓ½BÃ('š‘V3ÕENQ’é@Xã?Qýju³‹9Üj$€3Vc]ƒ‚qéÚ ödÇqøÔÑFˆ™ÞqŸaLÁ8úÿž”¤ך@8ÈW!¤Žµ¯ÇÎIôü*Â0RœzÓn!VF*>n´V'pÜ8V*~¼Tky¡~£2OýÇVúÒi#ûë‘ýáO ¬8 Š“ ô9¨ÙFr:úŠC=ºÊ¾þµžÊöî>‡Ö´ZPƒ-Ó¦G4„¤ÊG ;ó@Ñ%´ë: Œ0ê*ImÒa‚9ìGQYÛ¶äb¯øÔÐ߸‘sîøÐ2uš{ Uǘ™áº…Y,Ë•äw?¥Gé:’¹ãƒ‘ŒU[ô?ÞFË·ºR°^éÛ’sýÑý+Á r9ï]<3¤ÈNAíU/ìp]8“é֚Р)ïFåXAäe1‰ESè3ŸLw¥`8ÿ?Ö˜)Ý;þ´€ÖчÏ7O»ŒÔ°y€`œ`úšf‰÷¥>Ú±œŽžâ—Q2Èè9 ¨>´Ô`V—rÓ 6â u§fŠ0dÆ‘ÆX “íIŠå+Äi6€ ž*‹FTàŠÚÀ¦4HÿyAü(ft0ðzé¢ÂZÄ3ü qY ª„;t­^¶ñóØ*™¢ŸöÈ¥º`p1Î2*2Øgöcüè»<)~• àsüê¾ãžµ.ìóTK¾‚ãßÊa;Ž; F‘˜ç”ÒçÒži¬p2h´®qƒÒ©J{U™˜ä·8÷MÛ$š†SÛËå–Éã\Òf–ÞíÛ#¾•]›qã¥34Si(¢UÏJ™@QQ 1NÞ}(J3šEòOáO `dÐyöÖ`;Ó “@ñKÁÞØ¤€<Š^ôÇ9jq41äRÓëM'-øQžEƒOSáQ·Zº­HÙRð§LîZñJ w.$T'*Hô§“µóÚ‰FF}(«0a¶A‘Øõ4æ·ÈÝ‚=*¾iÈì‡*H ‚§`ý*XjzÈ’ 8ÓÒœñŽ”:°HóÓðªòä1=>vùƒÖ˜Íû€{Ð"{i÷b9ÏCœÔRX2»Ÿ^qøÔ ! OçW.gó¬Ô†ÃpYh´’€6G÷}y¨Â»¸ÀëšUÆqV!s޼š%9稧2†\t©ÖŽÀSÄJ:þ´ æp„±Æ*t¶qôô«œÇéQÉ(AÇ_Jäb#žMµ9•X v>´†(=±N< Nœ 7Þ˜‚Žô™¤à“@£¾GSB‘Í(>´€µayöI±Ärp~¾µ¼ê³FQ¹z×3Ã~×PxTE&Y;1<Î¥¢âú®­ÚÒ]‡%OÝ?Ò«±=ºÖåÐ[ˆàç¡Ö!$ âœY2Vc N^”ÖS‡J¡C‰ž›‡_­m3Ë(ýÒàc©ÿ ƃ›˜¿ßκ2Ör.;ΫnÑ:±±=ryªñœD¾˜­pm1}{ÖJ–ÀéÖ®; “ a4,=ØÓüŒŒ¡È}iˆ{wõ§‚xÏ~Ô÷·tÁ+ø‘NšÞAÎ2­+Œ…ŽåúsR# úv¦cž~´ˆp¸}y  `x§qëúTt´À™wÐT¸ç¥C=Ž*p4àŸNõ§l'˜c½ìçã·Z‚B½DjVã$òMEü¨)E%-L’8ÀR~jÔnÀeðS• MźœÐãr?‡“SÄĨcÞ³P€Ã#"´"fg¦fðzTññŒõÆj%w§FÁI^é ²ÎpÝéÒL˜_=ÄŒ R9Ç,W$çx{qLE&´™q…${SU%G‡õª³FÜ¥<zs@\ÎŽúxø'>ÍÏõ«ð_$¿+|­ïÒ¢{^xüºT&2‡@  6EsƒU^ˆå9çüõ¦Cpbã?/¥^ŽU‘70}iZ9Ã|Ž9¦ËiüQ°9íþfKug¡õ¦$RÃÆÒÈ»He%•à Ë+dg­W¸i$bJä1ãßùÿ:×’×Î^ƒ‘Ür*ƒÚKnNåc¨Æ?úôî2œRÉo\ÐZÔ‚í&@AÁî*‘ ’yé“Ú¡!¢}Êqó̓tøþUPóÚ¬ÈÛØ¸iª ©úÓŒ‘žœf“Õˆ|ÙÕOcÏ9é‘@ Òä“Ïõ£¬ ` ØÑ~ìäút©Ä±Ì2àj „¸>³D¤6Aæ—Q3khìsõ§gµfÅz@Ãuõ«qK¼ƒ»Šd´Yëš\úÓCÐÓ²($\Rsÿ릞ËBœðhÔb’–€ˆ1Ë=½*ù9⨃Í]Î`OëS"àf;âiÜñSÏÌ ž¸¨]1[™>µt¸{XÈ8Êô¦2µ=[åÆ:TT¹ã¦HæbÇ >´+NAÎ(-ùPsPJý‡ãRÈøèyúÔ†GÚ1Ѝø$š»&ù†}*³ ÿ*ˆ))M.ÜŽ£ó ci)HÁ¤ —µ%ÐÓ—¨Í6аXj €3P†"’O<Ћyæ€i´´2°#Þ†8æ¢ÏuíšRÙ9¥2{~´Ü6·ùÅ.îIõ£qÎh9ì*_(ç’4ÇšsRˆAïŸÊœ!'¢gñ¢à5sšRpr)ÂÚSÂÂçè¤Ó†Ÿv~í¤çéÿ WA¡°Çiªø<Šœé×£­œãë…FÖ—àÁ >…hºî]È(§˜dSÌn>«MÆ:ñLvˆÆI?SU¡Ì|vj܇b@™ºP;t¥á ~5yÏãSºåw­ ¬¬'LmõÏèmÃ7åWÂ¥¶WŠÕPsÉ«   ùS°>´¹ü©’7¦šì¨3ŒS%˜/¯¥V-¸äÒ‡¼å¸~0ï½#r)Àq@ ƒÚ“º• <øÓÑóÖ£UlÏéR¤aäñ@ËvÒ_,ŸuÏO¥W»ˆ$›Ç õ©Ò¢ÁõÏ5, H„’º|RgÒœêUˆ=©µD@OÚaûâº2}ëµ ]EŸï 踨™¤LMpñÔÖJ¾©=«_]Æ"úš§omæ¦Õ=ÍÏ动½÷"Š6vŽW?tõªüAP¸ÜúèûGÉee€«PºÀ=©6 ¢J„AÓ~¡«ª¨ÃŒ`ûR˜Pôþu4ä1M±gp½ü*uÒ0Fé8ÿeqúÖŸ–ª1Jˆ@Gƒüéó1r{J)(­LÇ‚@©V@£Ž?ϵ@*ÔG`ê >Ô©),$gœšV ÈÏ'ÚžXçõúÿ…2Q!GCØPsóÆš·=<óÒ•Ø~4 é)ïŒàtSh§($àriQ Ÿj°¥b9ÿ>¢€ |Ò=ªÚœ®ÂÕ4g‘†:UÅùF(üT2g9^”ç“øGãMP @OÌOriŒ~SWò‹÷¿•!ª¤z(754Rº0ÃzU¿²Ã'+‘îÿ®›ör¯Ÿ¨ÿ ²“ã¨Àõ tÁõªåOqLÎ=©íÐò¼Jloä¶[8?ZDœŽ"¤ßƒXW•ãߥH“cƒùãü*‰Fˆe3îœóLiˆ!ˆ8î?úô ×FWVéך3k2+€e8?•]Špü•©RæÔóòü¸ãßýlPy‹Œ©Çµ5@E9ÆzõÍLÃ~ÝÇ·øæ˜bêO¹ÏøÓˆ¸BHçéMeÁLvçùRî òîúóOá€#¡ÿëÐ_+·’JÉ8ëV6ãæÎ01üé’6ÄÐýâ+“ì9üë+<õüëcKUH.ÛëXçž(BôþU,R2†¨TyM3ŸúéÿìÕ"ë/þ«J•Çý3ž#ügÛßÇrv‹û¤“¦Ç”«}0EXkrÇ›‰Éÿi”ãóZv#˜¶5«„âM'PAžÊù5Jž$±V rg·'þ{ÆÈ?PGëYfÞé9†ïÍ è)‰u¨Æv•·™AÁ1ÿ.(°ÔŽºÚöÖéAy÷F?0qù°Ñü¸+/¸ÿGë\Bý‚Iy‰ì.‰À’/Ýï?Qò0ÿ8­x5+«!¶ñ|ø{OàþÐÌõé2¹iì,f’ÊÝýrSü«÷ÃzUÊ‘öEºe2¸þ•®—h–H$FädŽŸZŠYœáO¡â’Bv<÷UðôÚvç‹3Cߘ}qüÅsó/˜£v?ã^…¨j°Æv<ª‹Ó9u÷ã§zæ59H7vª¸Bã€N*Ñ'3ŽqÏ¡oÊ:UM»¤^9'¥hÆ*’9yç'ØSGÌIì?q98ã¯É9íU柲þté¤Ê'NäS¸xþúöϽ9cfè*| á>þ”¥ˆ!Aùj5 þõJ©Æp8ÎiñE±ry'­HiŠäa-Ȥ̜£µ6bY‚ŒS¢„\@ A¥¸¢ `8´ìQר¤oJC¤È<ÓH·4ÖA@Å.Ó ÇJfi´ ‘_|dwNi™â£Üc}ÝjPÙµbÚ\ŽG|£š|Rm|`ÐÐ"ÕÌ{†ñÔU2ÜúÖ’>ôÁëÐÕ  Ù' zRLm lÄÜÄG÷‡?oI:Æ…˜ãƒn6ÜF9ûÕvFó§9û±ÿ:OV4ìW¿vÇ#drp=AÈ¢€,¤Æ"HÆÝ5"ê)ücœö9þu\ úÓ>wuϰ¤Ò.2f’^ÀØÄйõ« U€(A¸ÿëVÂÒ6Õ{t«>\‘òÇè2xüªFªGE¸­Œ¤óÿúª1SG.Þ$}hUdô#ëÇó©T|¥ŽyàÓMÇ*ãßÿÕDjŃöõ4ÅçóŽ´2"’sÎ}jW ¤¶9þuXäòÄ“ï@Ç'Šs×ëOH™ÏCа–¬~ñÀöÿ?Ö€ ÞÀ¥Dg=?:¶!Š1Î1îx¦¼ñ¯ 3ü¨TX×&ÌçÀüj##9öô§*“ÔÓühõœw5d£ÞšŠ±¡&£f–^H_J,’8MÉÛߥ9-‰9|b§F03úŠ©žù#Þ¤K‰ø³õ«ÕO ‰­6ž2?ϵYY²0Ãñý‘¹ãÕDR¨y¥f!G€²mÔô¨ÚÜŽœýj%¸`@š²“8Í@ ‹‚>”4Ñäq’zã­ZÀ#«%±ß•¦´]CŽNà}™®sOÖ¬õ¨¾Ëu¤ÀsÈÿ²@ÏàQR.ôbd²g¸±zI*;•ÆLnô±¨é—€™"ĘÀ‘FÖÇ"°å³¾ÓOÊÆâÝØ~_Ó?Jéa¼Šò–%OPx*}Í#à‚ Œ¹Çõ¦´!œä7q϶ •8Ïÿ_üô¦+‚ç€íŸÀš±«i𪢙!~¼žý99÷ÏÚji y†˜ýÉ<óøŸþµPçPèRDWª°ëøUD2ØåðÌmó`}3ÓÇ> õ©üÕ=‘ä |„n÷æÓ'¶r¹º±‘Êõ°ðŒóüýi·í=Õ±òaÿ} {r9ÅSóœ«wD.x¿Ý9äãëÔ~"®— /U !$òôë׿CHft:2"#»î— †Fe)þï<ÿZt–.b(­½OUÿelÈŠ»Ä¨Ž¼{óÛß=©Œ§¯àyþtÅs‹šÉ­odˆ«|§+‘ÔSÔdv­n"6\cNŽÇðõ¬ÀsúÕ¡0‚z)¯ÐŒãéLC^UO”uìBËì=;~iË9ÉÈî=iÒÈ"_~‘CùXUåsRÁ–77.j;të+õ>µe[pÏnßç4 ŠÌn&‘NTÞ góf ¦g Ë aaÛFsŽiÝ©¡¥$û{P!zQŸJLæŠ4œRÓàqÞ€Ç&˜zÓ‰ÅDpy'õ¤P¥…Çjk6ÚëM<ŒŽ´àC:Src¥‡#Þ€Û[>†š>Wö4ŒrÝs@‰<ÓØR‰ Î{T{Óó§ªô pèpM5ˆ^¤T€``T œŽ¢ìšpÎx8¨WÓò©G#"˜¡) žƒS˘¸ïëT"#îþ"¯E&åÁ<Ž*XÑHŠU8ùéVaÏ–Cg=NE2évºÈÆy©O\Ð7\Åÿ\8~\T÷êOÅU¦!Åò:T–ì¡ñëÒ˜‘ÉéRmEê?[ÈÒÙ8ü긛<#ôÉ©`b$ËäsI¢‘n„`¹ê{ô"—{‘ÁùxÀ…0?˜Ü—ùÒK/DϽF¦Ú$r&݇§ài†6^«Š¼[Ši#šÔÀ£ŠQžÕeâ È5OåP§/ùSšf#=¿Æ˜¨[œàzÓ÷$c€  F¡Î©¥Û6æïØÔ +‘Œàz a'½Z7J¿tJ®dn‡J‚ŠyfcÔ“ëR,`}óïQGCúÓ”l ’}(È’5áW'Þ­E¸¨fàúTPÛùcsã=¿É« ¥×Ðÿç„@ó ýxŸjöÍNÇcžÙÿëÔÌ3µÁ(üÿõê)$_—ËúP21òr§¶i›Çl`ûÿõêF—aå€ûÝ­D‡Ê <þTíØƒúÓAÍlòÄ}ýt¥ËsøÓ·oÅ”C'¥ŠU]–qgŽ&i"^ãK¯­4È;R²ÜcZbíÉ¡_9¦6AÀJhl”€ÝE(HÎ?J)ËÒ Ø§Ž˜¨Ú$œÔÄàçµ#ô_ìà‚jÝ–¸¨‹T–äù§§OZL¥¹£þ½}×úš|¤&”¼õÝ@~õ?ݧݚlcÔåAF1Îx5sêjC€y8šñ‘ÈäSbz“FãŒdÒ)((¢€–Š(`ã=¨£´ZZ6“>±¨Çi?1Ë·e^æ³À¯ZðFŒºvŠ·.Ÿt¶{/ðëøÒl†î•¥[i6)kn€*¼y$÷$Õüàu¦vÀæ—·lT½I¥Í4RÒÙ£?ç”Rrj7$ž´êˆ°Éæ™HG\Ž"£ù‡s@­œu qPM3o![è(ì:}¦<“ϵfÉ’NN#üûU‡rü±ÍFj’!Ë[£šÕ4A“=’˜ä_›bŽ~«þŸN8´/±e³Ô s„˜‘ó{‘Ï¿ò­I8àä®zf¹ÝsKów\Û¯ÍÕÂçæ÷ǯ­-¹¿whöòµöš‹¸ÿ¯…G^Þ÷ÏÓž¯7kqm¾Û,þC/ÿ_Ú±¼9¯‚Ø]ÈLª11Î@íÉëüêæ ¢ÉÌx6î|œ`ï qõìh®T•"”æHÒFõu \Ömþœ“&èÇ*ò0Øâ´%p‘§Ó¨™ÇÖ¬ËcÓÍpvHREùYNF?Zppf$ŸsþÚù7 r ~WùÿŸj™{¯<ÔP2ͳ<{¡”+$œòÇÇåÈ«¬ínöÛÛ|G ÅÝê§úp;¬êJðç#•>‡·jX¦jŠ@Ö~í€ý3øä~5,i—£6yÇ#®ùÍ<·½V'dädí,zð_çSö  ÔBâÚHˆÎà@ú×)»[ï ëœá8®GU€Ùݳ#îSéê9ª@+8^¼}iƒr0~•œdiå ÎÚ²Ò¬€°C±4²ˆ—-ø « ™å.ÙÀÿ8¨ ´ÒsžzJ¶X[Ä9=… µ‰ÞMαŒû‘Ú–yvGÇ~Cl0 ¶w7©¦13ÏŒü£¿µ0±=ºa7{ùRg›BZŸ aš^‚Ë/”„÷è6ÞBÑ’G~¹ª·2ï“ â­F6 ‚ÛB}ãpQ×¥ª¶ïæJîzv©Œ€8^¤Ð+f¢‘†iÄÔ,rs@! É¥À¦Òœm‹A€OJ‰ÔçRnÀɤÃ9ÝúRvÇ4¢2j²ˆž@íŒÑr¹H c?Z°‘®2?ÏëIÔJ­ŒŠLíZEÿõÓ o¥H‡$ <'ËÈ ƒžÆ¦fÇZNƒ'‚i+¤g²cÅHœÿõªV‡Ö‘rÍY˜H9àU˜Ü+ëÁ¨Cf€ølô˜"ô‹¾2§ð¨Pæ5'­>9 éÇí²²tjFÆÏþ¨ÕdžjÌŠÒ®ÔüûR-°noʘ†‚Çî€OåOØ1ósíÚ€¼¨¥”¡b˜fU‘z‹ÏŽ8= ^Iºšˆçµ; Ú·2ñP4᜶sžEU‚à¬Lœî 7™©KRœ´)E5H"Š¢Çjk -üÍ?¦9¤rv〠yEàTY§¿Je2qëCŒ1§Ä2â‡*Iã>”--KP À 8ù<Z²bÀ¯^µ˜íÓƒíSG¤8ö  ’AžOµ6Y˜pÏçQ´ÜlŒ`S”¬JIëß4Æ?,Ç犌í^Iϸâ™-ÁcÇò¨²IÉäúšZPŽ:ŒúSöíá‘U¡lHµi™<üõ=~Рa˜fÍ;Ï™Ì?5¦#K·‚p8ÁÓ¼ö^?¥º,…X Mû7 t§ï†Nª?/ð¦Çå%yõ e Ñ°ÀúŒõ©À2­U0GåbƒƒŠ ,‰ŠðÃõ*°aÁÍWó_œdzÓvo‰¿´€šKd“¨çÚ©Ég*¡Ïó©–ì¡Û ÿŸóÖ¬$ªÿtƒëL5FZ´±6NAýÏçV⼇üÇÿ[ü*ÓF’  V{'äl}y .OòÈ Œ~†«MjܲdŸJzA,î *t`øö"²oç9ôÿ&šNî8 ïÞµî-–U$pÞÝë&XŒdñŠ Nä[‰îqNS“þGò¦¼ÓãuúÐQÐʤZ Õ\6±vÁb\œsT•·ãÖ’%“õ¥úšb·8§S$kô8¨ªb7pzÓ6×4 r­<f£éÀ¤b}hó"œŠdc­@HëŸþ½*¾ ±0<âŸbu<Н֕8•{Ð qÚ‡Xþ†—R;-!ô¥ëŸ+_éI­©úf¤³Ûæ"‘_zR7#­6¨B¸æÊSM aEPK@®ßÀ>‹T¸–öò!%´'j©Æúò>”›°$q^L›wyo·×m&Ò9ô¯uÕ>Ë¥éÏ*ÆŠ¨0‰´O`: óË5m[SyÛ;²ËXô=}³×W·yæc±•áÿÜêz¼Ë ‰n4­Œ|½qõÿöT@ŠP  ŠÅðõ§‘hgc—¬TW'¯äžO×òµs«YÚeZMÎ:¤cs~>ŸŽ)jÈ‘¥š\ŠÄ[Í^ìi`‘!þ;†<þÖŸý›®KÌš¢EžÑB?™Å •›9©sX¿Ø7Í÷õÛÏøõ¤>¹=5Ëÿûì)¹¸ l“$C.Ê£ý£ŠÂ>Ô|½zë?ísÿ³UyôeFEÕ­×´È?ˆPñê4)ªú°”²Y§œÀà·ð©ÿ'Ú¡"Y0g·ûîÿ‰ý+2[ÝVŻұñºÝ² }øõ>jÎrÍÈ‚\¡ÿDZŸÀš-q= ?3hÀã°ö¨É¦î¥Í;ÝÂÑA¦HÆ© Á õöïW A.7íI—r:Ý›ZÈ.íòŠ?)û‡Ûʶ´muKÁª6ʤà6{ŒŽ‡ëK<‘M¤ˆvœŒ7CùW(d“CÕ–Dfh[Œò2½Áõ#üõ¡n2µ¬lÀ1±ÈãÓž¸éùS TׇϷYcÃ2üÊGqøU|Ž ç<çÖ©Ëq²(‘B1U­d*…ï¡Ç5hÕ9—wìãõ¦¼³)$} E8;W¯ßOfýp*sJi œyǧzV4§u–fLa‡ó€ÃŽ>¸«JÛÐ6s‘šÌ¶oø“Hƒ$Ä]AúéV–c0$ðxÇ&•†Xf Œ‘×Ö²/­Åý«ÄÜîCè{U¹¥Á~ƒ¢Ž•è@r †Ûy”ppAëUžF‘²sZ:ô{/Áu×v8ǧô¬Ô]Ì©ª)­PdlJ9¸›¾ßä(ákÐu©àŒF™<1õ žM‘í^§€=)ÖÑìL÷oåU×ý"rNvåVd"“LLY$&dAØäÒÏ(D$uéP[ÿ“ÉïPÜÈYöö~´…wË“ÐsV'“ldzñM·P‘dõ<ÔºLƒŠCêYµÀ8êh¼ÉËœàt¤så[ã¾1Il0…½MXwÀÇzŠ•Ødç·ÜŸ  H\Òe±Ò— RÝÀÊ€¤?•3¼Š<1è*@{T!°sýje=ÍK. ßwÚ¢,Fx§<ƒ ¨ðz“M ½@“ÎOÒ¦¦GÓ¥CFî0(h-/''ÙµBÒœ`` j±'®iX.Išo~=h'£'ðÍQ#óéüèê8ëP3sÐzT  sϽX‰¹æ‰²@#· ¨†@ó'µJÄ4|w†Iæ.ÁÐ {T-;dãúTïKE„X¹VF,ÄóŠ\’ äàõ© E2®yJ"²y@'å_¥ZNsZ€|¼6hnaœth¸+ 0ùT“ߥ[9 äõ5¨F9çñ¦1ã­ à;x5]²‡}MNfÀ÷÷¥.Ž0qL »°r8¤'5$‘àät¨O8 ß99éM©sO† Ä“‚(p½)†¬É´P0@úUc@XŠ#*džLÔqǼäô«pŒa9  BÇ çõ5 ÈÒ°8ì)ƒt„4 'SÔЪ¡'“ú:©,†CíVÐn‡’I#©ªF ¥Î;ÓOéE‡ )ìNãÏÕâ¸ç4r'bp™è:Tžk§®=ÇøTɰ©ê1ÈÍXYãnä}E0ù‘rGZ!ów)ÏlwÈàí#ê?úÔÈåòÐŽÙ E…‡>ôã` Ÿ¦9¨ÁIG#4ͯÊ’G¥HU—µ@ÌѾåã>•b;€p`Ô†4gžàP r®ÈÖœ`ãÐu¦½¡þ9ÿ>”ÄŽhÛÓñ¤âIbÀdz“þEYIƒŽÿ ª³Œíl wÅ<ª¿Ì ±˜š-uê3õ¦W§Ê IÞù‡ùô©ÒUqEÇ_þµCsʹ@è*Õ'á@ü±4mŽÔëTÝsõaZw‚ Aý*­´%ob8ÝH´ÍKÈÃùc< š¥#mùE_ºÀeÉíT‘7“#ô¡hl.M=_#§ëOÚ®sÎJŠR ê8÷¦"OÆ—5 ÉÐxjÂ6TñH´ó‚*]§ü(>QÇ4|£§4ŒÜg*?7žMOçŒT ò§=ÅSó¶sùUÙL­.\®æ„z·øUmtþõ·ùþUoïMoßæÿëÕ=] —Co$ÅIFAÍ73Bè8¨ÊÕiâ›R‚1L>ÔÚ(¢O½^Óà"Û¶‹æ.çÜç$u&¼Uk¬Óï_ì0G¸íUãžjd†ŽŸÇZß g ‡lc|…sÓôÏçžÕOD²+¥†¸fFndaŒØ$sÇM£œ`¬-w¨¬?9@w8øT õ=ÏÙÛ[[ÛÂnï¤ 9ÎåÆù2rÜœàBôõ㊋-,ww°$K/“lÁ\Ãü±VâS£*¼òÃ}àÒ7Ì}ÕzþKøÖ ÚÖ£ªN­aÉ̲0 Žç®@ϧ>â±./<=§™L÷Rjw²Û2ÈOÔœwêKÕX›MÏ4¨dk™3Œ"ŸÌçÿªãmI¹·ÐæŒ3ôA\„¾4˜)M?O·¶Lñ‘“ù.Õý*¬Þ!×€¯ZnžR$ú§Ê3´ÿ„¯ÄŽ2š/þ™Êi?á*ñ2 ¾‰Ç´3pM¬êíËj×§þÛ·øÓâÖups­y‘ÿO ýM£;£ãÛøOúVŒÊ?ÞdÿЇõ«vßl$âk{˜L…?CŸÒ¸ˆ¼Qâ(W#Uv’4oŸÏ53x¢iOüLtÝ>ïý³Fÿ¾”ÿJ9Bç¥Úx—L¼p°^Àd?ÂÌQ¿&Ú>óHÓïÁI¬ã/꪿¡?ˆ5æ yáÛ¼«Eu§¹ÿžneOÈ`þ•§c«om YŠêäÇ»…ú£dÉir…΂ßiÅ›J½ÜƒþXNÃéž?ô­¶a—ÈÔ­ÞÚoï;OõÇæ=é–¾8¸´q¯dÑžã$gþIÏàß…t1Ýizå©ÚÐÏꬄíú‚FÔ Ñ.)$«"BOBA§ÖuÎ…s¦nŸI‘ž3ËA'n1üÖ‹R²cÁŠuá¢~}½4îfâÑ j´¬fô9n2k>îR" }æ8ãšD¤àc§æ+;RµVŽ€|àe~µ`ÞG戎â猅ã>œt¥o~)lY¢Ýy–žC·ÍÇ'·j±¥“Ž:cÒ³cËYu !Æ¿#õ­9~ðlû¤KBUKÞuV«]ê ‘ºÝǶi’†ƒž”u¨âlƼö©%²'ÊÔ#,rNáÓºÕ”lƧÈÏOƨ۱[›•ïF¿ÔQVáÇÙâ8Ç”†,“yÂOÓãMY|ÅÎíƒ×4³œ¨)–ü‚=ÿ€1üD Gœà1_Ï#Xñ´‡>‚ºmbÍ®lö ˮќdçÖ¹éôëëum¤¡@É! E&%¸2JIÇ&§¸…Ú1“éPÚ°HËÈÓáýô¦Fè:SxSˈœžMW¸¼Óñ©§›Ë\ŽXð*µºå‹óLE†"(ºðZ%ó%þ4³É–ØNµ5ªá zÐ2W`ŠIì*œ@¼ÀžNsÅIu&O^M% 3~\¶v¨úš±íE‚©g̸öÎ? ¶Ïµsúf€y4™ÉÂõ¤Vܤž‡µz€3ïÚ€O4¹ô4¸é“œcœþt®; ¥¥*1œâ u&€°œúþ”¥ÛnüiQÚ£ßódP=‡€)è .3’)˜ÈÈèjá㊑ £¯ZnÆ=K¹G=O­0ËèÒcojnÖ^Ùüj]ÍŒ±â¡y7dp(BÌyÇbi‚L1ã?Ò¥S•£‘3Ȧ ùþUd(éƒT…N1€hÆ;RDħ?B ‚idž#š@)S¸ÉÍ)‰€íùÓÁr°9Ž >Fë@/ËÁâ¬[Ì‘Ï=ê3Š#áÀ¯P‚äcŸÐÿ*°FAȤ1úgµKµIÒ‚Y–1ןAT䙸ÔŽÙ<óõ¨ aˆ${f˜Ê[rãq9½!ÉoQNeÀÈúsÀÆ7'ôô¦2†éSã®G©„úÿ:®Gjž$ÂS zS̸ Ì8cóÿëÔ(€¶[ õ©BþÐx y;/˜ãMëR e­@û€ 7¯½D >fÚ½ZˆP…¹Ì U9†Ù{Õ‹S•aPÝ KøS%n@i(¤¤P´£­%:|Ç­L«Ï;úÿ:ªÅh›û¦€±oàÔKŽenÃô¥Ž\à0Ú} L¹'õ —;ü8æ¡ûÿî}:ÔòǸrN{TR¸p94 ŽVþüqÚ À_oÆ‚ìOLÓ•wŽAÁô4Ä@NXœu§¯Nj_%lýhb«Ç·­74íQ“ž´›ˆé@ ”ûUg8n*ãa—9çÒ«È ŒŽÔ ƒëV }ËŠ§Þ¥‰¶¶h¿†x ÏéT¯[ý1ý€«Öy!O¢zý+ñ÷]ÊÚ5+q½€ž8ªrŒ7Z‘ä uüê“pÆ*„ÊCA¢‰E- ØÓeBÇcÇù÷¬p*x]¢|ɘ—†äeynÕ|ˉßdã ï$’0%yú{U­gX†È2NÆ[Ä'ËEMǑߧ®#'­r€½ÌÍ$­»?<ŒÍÉò{õú’jR§qy­xŠÆŽÐ–ÉØ”“žy=9<Ö,±´r¼lȩ̂ üý+_Qמh–ÒÒ(à·Q´ìP7 }p99îNd$Ž:ôªB;]QÑ4»A6¾eÛ ´œ1-l?*çõÍZ}^ý¤“rÆŸ*G¸¿:ÄC «<²ªd•A,G…Ak,øáTœñÓ% 4mãX¹ ƒ$צi¾ÐmbQ ŽYù·É“ùgÇZ^ýž%ŠÝñÉ'¥jC¨Ìå†/ð©i½Ä΢]@mò-¾€ÿõë ÷Âz[4 ÿ°ÍútüiŸÚ—È`{c“üÿ•3ûV6È1•qèóÿëP·~Œ))*œnfÆR?_²æÑ.ìåY"—æ^U•€#èAþUéZ×¶ÿk¾°ºŠHýz⟪øu„M%©%G% 9ýó÷§vG¯Ýª}N!q0K®>§?ð ~¢«½ÄV²­Þ|AùR)¾Ã¨?ü+RúÐH¥eBx†1ôôü+”¼€Á!%O­4u£xÚÙÝ!ÔSËsÒTAÄGÔ~B¶õ-*-Rwg$k8’xÈ9ä ŸÇ#Û¸ò$ƒƒÓó®ƒBñÞ0RL¶ìrc'§¸Ï?• èS]–Ô›}B&R‡Æ2~¼œþ…\Þ%$bNå: µsocâ;µZ:yàpsÏÐô8úò+ÎéìækkÜÃO¾:þgõ¤KE¦‚6bÙ9Îrqú~•VòìYF |îÝ¿óéV§aÏšJó€ÛNåÓñ¤·" u ÷Î2ê°?Ï^”0î„×%fhÊñ€Ø ÄñWšT{|;®HÁäu«º6“%ù×#£´ç×8éU®-d†öhäUHTåA9>Ä~H§ÍÐ †pÑ.ðÛÀùŽÃ××¥,’¡‰€`xÇ™#ÛFÙi ,>îz{àE2HÒxó¡ˆç– úç#üñNäØŽ„t5>j²°*ÙëïR¯ïr£8Oô¦í7Ò1=cãð"¯AÄ 1Ó#õ5NÂ_¢ç;¢oæ*ì?tޏf©¤&ûØëþ•%·ÊäddúŸ¥ZØ É\Ÿ¥8.ÈÒ½Ñe¶vS‚£pü9þ•°"ŠTÈÛÈíý+.]¦SÝH÷éZ6Î ¬8'”SÔú LÊx—JkB.!Œ,-Ãý:Vd;RAí“]ýͲÞBðÊÛ‘‡B:QŽõçWhö¬ö®>dr¤öâª,d2ÈdryÇ@*Ú@Ýj¤ ¾Aè95-Äœ錇%ß<’jøÂ(€ª¶ë“¸öþt·gäûñ@ÈûܵMùPd}ãÎj¸˜Þ¥›BŠ,'çŸÂ,¹l €*8ˆÉ' ÀI8õ4u[ŒúSZpˆoJŠI6®z…›qÏ9ý(L%bÛ‰'m'VRIé×=ª”`Çò¦–![ߎ”ËÆáuëëOÞÏjËÍ.iåÖ—qÀÆ>´ÜÔ`ìNM3yÏŠb4-Û9R:–PÞ¤ƒëUQü”ùÍh8áé¸T²–¥6Èi±ÏCD§3MÆ0ݪ„/'“š7ô)E§æúÐäÅ3 w©Qw ÄP2%ˆ°Ï¥Á^©¶8üUûk|~ñˆÏ^zUKHˆr8Ï~„V²íè1ü³@›שÀüÅCpÊ©ƒÏj’w¦q‚zb²ÝÿÈ¡sÊÏj‡ysßéÖ£b]ý…X6ŽùïLd'•☯¸c©§TMÇAÅÍ‚G!$ñÏ­?Ý?•ˆâ€#ôô1žÔ­ƒõ¨ÎE.FrGJ©ÿëÓ¾ö@¦ôäP8?–)ÆMŠHûÍúSO<Ôg“@MŠ[³ûÌ=©/̧ڒÐâ\{T—‹òïA=JF’”ÒPPQÒ” =èÍ(4ÚwO­J€N;ñFîxéšbŸ”ô§zçµ-Å!'úsR‰FüûUr¤S£ïÎhs·N>”"|žF:Ó@ eNJ­ž¼ !B0r=êD‘çQ¼{†AäR¬{W­<ÀN)…Lx,¼zÒ¬¥28Ï©ÑÕ½(ئ%¶ƒ“èÏó«!»ƒPùj>e¥E,íÀúÿúéi£GÆ kf^PäzT1^0nE[Žd“¡çÞ€ Žqú~•n,"wÇ^iJ«cŒã½/NhyUqéÚ©d|’NjWVc—õã¶)V0œçŸz°ÁŽ£š})ª77XÊ'âi‰jI5Ò¦Bœ‘þ}j¡—{tä÷¦*´‡¿øTë}Mezô¥ÏÆqŽØ¨Ä™8í@‡Úi­ÃÔSô§1Ì}y]×k`t¤^ 9òy¤Ztsfßw šÄÿz䞬{Ö¾žálŽxÍ`¹%‰Í%¸Ø4¸ÇçQR“Í%0 (¢Rš9OlñWláûeõ¼$ü®ÁNAéßôÍRUÍkøy@× 'øw7_@i=„?ņ}a¡1Û(‰FsƒÕ»ú“ùVLÎQ<€Úp Á?Ooñ©Ì÷o<‡?3HÙç'9þtË{i.îR%Ég8Éþ|ÒØdj‡ar>QÁúúS|Æ‚GÒµ5…Ž VÒ.#„`Ÿï7ñéXùç½4ÂbiÁ÷'¥K½ÇµUb¼cõ£?JvQ.Œ§f;·—'ëÿê¬A!éD­ëJÀt‘ߌ1Ï¿áVìÈpk–YØt&´tÛ‘ö”ó…úÑaá^Á „6òaу§¸°È!î9¯0ƒP`¡ŒÇ8ã9ãžß>Muí¼r7˜O8Ïùþ´…soƶ¶öð%ʸIY°Pn¢¼»Q‘^BxãگꚕÅÓ™%”»“Ôœâ±'˜HF3BC#$PåGªúS3@98ÍPÍ'Z¹Ò®¼ÈŒdò¬ÝGøûÿN+§ÔÌzªjP’£~Ñ÷‡©¸÷íô®";}мˆÙhðYqü'ŒÿUtþ“δ¹³sòã#=àð}ñR&+Jò&^F}ªp8ãÞ¶î¥I¬ímíÝvÈU1ÓüGåXXÉÎ=Mnh5Æ«¥ÆNB“&=”ê)HGcµ´[`]ŽB¨§·âkñMÌqê~P\$Q sþ¸¯C1‚Ù,O·8þuåþ.ÏöÝÒΉÇÿ*ˆî6e$q„ß0ÜòÍ»¦þ¹¦Üì…¨Á1Á*ÀgéüÆ*äp—!@ã¾8ïíÿ×éQ›%y…À38cŽ˜ÇZÐW+ÃLÂV / jú  `{UKFͲçt©ò]¶‚@êqL–H û|z0<û žÝ$kʹDc¸ñ‘?¥º•¶}3ô5§fàËp¿íçèÂMnfEÚÅpsíïP5œpnØ{•AýjËÆ®s´~B ¸hía.x=‡LŸJä³Â`Þ;dŒ(e½¸<ÖÌ1ùPÇd@¿—g É?iœÀþí$Ö´–ç‘æÏqþëIeIÝíÿë®ÄñÖ¤oï€ß¦?¥w +•èpxõ®7ÅkÿªSMn4gÄ0äž¼š¦ì]‰9§<…Ï\JgR ?Z¢‰ŒžZ“Pæ’NM€,@£%½)“6d<ô⦻‹ž¢ª“šb3ïREËdóŠˆTƒ+{Ð;nb{RuéÍ(\Ô™Dè úŠ œŠcc'oJ МQ‚z ÐjH×''¥O©`‡ËÚ’`ížüP w4q›Øn?Zb.æ ëéV.>êª.s×€!y0Û÷kcM“ͶØNY•agš¿§Îb¸û§ƒÅ)- ‹³à”ö8¥^T}*Χ$ø5YW½$î‚JÌ6nÇ`i 9ì*uñIަ™%Nõf1ò pHä`šP›F:Ð)Ý©>”d‘å@Æ(àýjO$zU†· Œô梻1¤€V\gw½Z´´|ì@_OZž8U ŠÇ×5b%º@Ï|œ~8 $Q*§'EJTAÇÔ Œ0õúÔM(ÞP’nh&ÂJûŽ Èª3cœ qWÏçÞ³¥`I ÒšÎv玵:° …[’§ùСö¦H»‰¤Äh9ÇÔÿõêº3ÛéP¼¬ÇúPÃ2>¤qúTLÄs‚=Žj¹lt¦î4 ¹4ÝÄ÷¦fœ­íŸÆ€%Žà¥Yªƒ,sDr¬qtÉ<ä vòË–oƆã2X‚Ãgêq^¬ÞhkR\GÊÞ¹<‘ÓúŠ~¡x—r™g•»9Îxçük9Võ n`þ¾Â„’ìlVÙØuÚ}}*Ë©]‰ýÑÇà¬å˜ˆž6ÆwªgŽõ«SÜ*1~Æìþ¾žÃó¦I¬;¢áð68÷>µd AŒä÷5@Îö˰áAÃϽZŽQ*=G¡¦&G0Åõ£s÷ˆ«¶ëþ™2÷hú¯ÿ^¨]¾D„ýÉTÖŒD ðìáFÂð¤>…ÙeX¢ÞÀþ]}½?ZŽwyòû öÿ>ÿ§Æ›ßÍ|Œ}Õ=½þµ1ç’M"H™sµIü?ÀR§¸?J”ã©5•õÐ-Ñ Å!ƒŸ—NÕÎxª2³[¶sF*è–B¬:óÇùü«Ĩ v=‘ŸÃÿ­MŽP)cC –Yù#à{TEK åóéÍEš•\$xxЧ“'hÎZ†Š\PS´x 3Àæ¡Qš¼ŠBáAÉ9  §Ò•#gè [[aÕä*A(áE"[dæ^~”¢³÷?yÿÇáNP@äP1ʼn\Glâ«aÙ‰úþ½XÅdÐ"íÂ6ìƒéÅ^ ç¿­T,IÕe%HàŸjL¤Qk6y¡AïPª”|È5©pŒÐî„HÇoQYÇ,Äœ“Þ„ÀØr.tàßÄ£œò*œc þ>–û·ÀÝÆG?Ÿz˜YNéWÓ“Q{3F®“+*àzpO\b­­¤@s)ǸÅL¶‰Œƒ‘õ£™ÈÊ;sÒ$¸ñZÔŸÈSZ28Ýüèæ)KÈUìÇð&šÇgHÛþù«m‘üõà‰¤N„øS¹6(É3–ãlõê'•Øèx#5 Þ[{þ¿Ò h#'¦? b"K’£  û⦆ì}ÖúqŒ~µÙ¿ºh[wC‘Ž;枀Yy˜©ØXŸ­2<¦]ÎO­8džAQN1™ŽüR¸ì&f•7+¦G5NxÞÄO©­!…P@1PMžBçP˜ŒÄ6îôâÁº~˜­Oìø¿ºOãT.­|–Êò=4ïqÙ¿J‰›Gzc§ø~µ̬‚5`ûWœŒœçž ý{T•š-ì wŒv$~f¤DÒ¤’킸9cÔ zÿJ¥píˆêrãðãÛØþU,w(#)6ßLžÿ§Z¯5ÜIUtÉ#Ïsè=èzA Ç¼Ž¨yߌ¡5!ÝcC•qÇL~ïüª•¼Ó\Ž ÁW«g_ò3ïZpB¡îcË1ïMÆhPnÔ° ‰ØvíþÏJscÎO÷Oô§d7¥x¤Ù±î¤Ö¯E²K¸à‚7~™ª—µ´ƒý“RØå¾ÊÇ?w¡¤ cd‘Ž”çùS7*ç$ õê&—ø‰ÂŽƒúÒF“rñò¯©ãò¨ÕÉïƒÎj®1º@¤ž™çô¥Y~\¬/ÏRþ‡?ΘɼÁ³#¯^;ãšÊñ0Á €ãß±«¾dN L)'ö'Ðÿk/\˜6•gï,˜<úgó Í“NÉ¡›°à:¢ÆñÐ~tb”iÁ{wô BSGnÎè=éñÆ«‚Ø'Û0,Ç¿é@q¤gî±>¸Í]uŒ/A×YQ³À?—ÿZ¬¸ÞˆI翊@78GçI–ïǵ8[¹äGÖ)ô# °'‘Ò—f*dÆÐ; RíÍŠì¸4ÖªÎÂW õ¦ˆËx÷  Ž-ù<àTÂ%nT¯ù4ó•M£Œ÷Í>ÞÞI¤@#¹ôü©6HaenX‘×®?.œÔê‘«î*îÁqŸ¯z°mqã'å’?¥VŽeÎ×çžùÿëT7sT­£-.ðO® ÿëÒÊêOÌÍõ¨B /Oîú…;‡LF9 ›ä9V÷?ìŸð *çSQ˜Æ3Á>ôܺŽÿži“rf,£!¸£ü*2͵}ê2äŒddÒžHö¦&Áß ‘ŒûÔm"°ù×4JÜ€;{T@8¦‘ uBxÖ™…_ã#Ú¬ÙÎÑœæœ-ÕzòiÜ-rªËŽ€7Ôbž&ÿgõ©˜ªô8¨Ôy€‘Ðw , éíÖ¤%ÇAƒþ}ê³8€3ëJ³sÀ ÓpHXm‘W¹ÿS¼½¼¯Jª· «}?¥9.Sp\'©§×¬­ËÜ€}3P͸ÞöÿZ“ îpxÏ4¢aŒqœ‘H ‡fÆY¿—ÿ^©Ë0 ŽsõÿõU·hÁ8ô¤ åŸÌÕ’E7±åÛ7>XüAªw‘¢•ØúSE½ê¯•ŽAÁöâž%‘¨üÇùü©ï²ìmÉÿ× £3üàúRÇr׃SŒÆ  Â/ö©ãlcœcÔÔ…Gz©6áNáéÿë “]pBz‘U‰ÝÉ%½³Å+*“÷vŸoÿUFQÔzŠ ¾”ÂAéSGg?G)žë@ˆÒ¢5)¼SÚ˜1ƒšÐ°8å—œYÀdÖ“'L»Rc3ÉËëVÏnW­VïRÄÛFr¿½3oÍOuÚÄ{ÓHäbâ­ÒýáJÜ@ 4½8šw·ëI(úšÓ³ 4Ë㎠ Öh,zZÀ"ïœÊ1ŠL Gÿ® #š³ƒŽõ‹ƒŸZ`Zêú#Ê>ôràý+4õ­½?÷¶W–ÇÌŒH¼ã‘Ǩö¬R1IÚ(£¥0 (¢€Š)h)À‘БM¢‹J1žzRRÐ@µdŽ@÷4dž¤Ó‡Ö›YC‘òô>Ƥ Tç…41‘Ð sY÷R«úÕ@Áoº( 1<'?€¬¦SœÕ¢%¦€:ô¤f$QÒ“Ä%bŒPŠpcM })qô  â—o¥N¬[TÇáW-— »9µa‡r’ €{U™Td0Ç>õ‰”Ï¥ ˜d*M4ÆW>‡½4§#ñ§4…Xcߦ!¬>QÅ7¢Ó™²£ÔÓð( H˜#ý3QS”x dʼ“RÂ8'Þ’0‘Š‘(ÆîôG-DNjvSŒf«?jrž*âœÄ8ê{U%è*·î×§0p$94Íûº­C+eµGš¹çªýÒOãW¬fÝNö¬usV­&Û0ÚÞÇ4«q”du8"•YgŒŒ€ß…Cu&ÕSœóІ;¤0? ÕwuçéÿÖ¤(W•b*ì‘­Ü!×ïŽØGËxÎŒŽ£8þtz¿@ÃJ§5v™ü)êË•Êþƒü(Å ê 3Fr†*EfQÙ‡­I˜Û¨Á÷ãüþtŹ==Aÿ"¤Û€ÜzS<„ÎàsPÎísŸR3@ÉÙOJbÚ6îH"›Û3)¸ž>SÍ^b ˆ•ê§½& Ÿ?*Œ…½~•X¨Qè=kJX£·ˆ°s{V[e¹'4ÐÈÙ·tÍ0ð=éä ö¨ÉÉö¦£Bò*Žç{RlyqƒÀ¨´è·Ü‚@!y¦ß>û§ôRê ¦—=0i¤Òf˜‰œï»Ó:Štg9_ZNTÐ0èiØõ£šH=è,=hÜ/ùÍÔ”n'µ_…öEÈÁ凷¥Qßè ^ŠLiRçûãúR`PßÀS‰l`Œ­eô'ð¨Ì„õþTÀ¿e+#Æè7I éœnCÔП׿Jn³da¸3§0Êrz÷ê±éK¦Û¼Ò;¤YsõÎ1œà×…c¸·6Û gï[™ Þ6ïþééÛÐÈÎJŠÕ›F‡{IÈ‚‡å‘}ŠŸÊ³^6F+"20êpZ«ˆe(•{Ð<Òíô¹8ÿ=©7`<Ó圚¹¨YGmtɾlyá¶‘làf¡P"å¾÷¥0ª?—5©£Zù÷&wây$ôÏùæ¨ÚZK}/R3žƒÿ¯íZ×2E ºÚ¡òá_¼271÷ôþyíŠBd—¾lÄ®vô¿O~õN[ƒ÷T’Äñß5 “€p®rxãüþ¿¯fÀ#œŽ\ÿŸåõ ,?8S÷$öǧù÷¦J|ùV%åG-Î? ¼¯åZ®ùø†N=ýÿÏZ±v‹§ÃöHKßï°9È#9Ï_ÏÞ€+[æmADlUs€G ÿõV•ËG‰¡uÈ)ƒ’§üЧ¦Ã1“˶ËLùùG^xÀíÍu%¼ æ\:Ï!9!ŽF~„óøçè*[°ív`ÛÃu“r¤ö?Ÿñ«ñèS6 ÒìI9üñW®µˆa[ÚÆ'—;BŽ>ÝÏÐU9T¸cæOä¨á–22qÎ ÇüƒJíŽÉ—D¶Œ~ówÔ¨öH‘‘jØó…9þ_áYöúCê^T ò±gb0rOÔ^ÕÓÙxOO·Ä—Ó8ì*‰¥{ç;ÚÅ&ÛˆŒmî¿þªÖ·xÝ!Vºš×–ÃLXÙ¢0##å?¡8­`\i—V²¼¶6ÎŽ±ï-þZ¤Éq4a˜n1°Ú;gù:‘Õdeç…瓚Ɔeœn2|êì©üêïÚB‚>lž™#Ÿüz™ ‚æLõÀþþª7ì'9+üªª\ªñ»–8#“ý*ÎîÓëA61õÝ-/mÚX³ç' ã–Ý5ŬnÇ ¤Ÿa^¦zp ®Z{U·Ô..lƒìy¦iÐÅŠÕÈ?.:äUŸ%!L㚺ȱ隥+ŽTPYÉ9Ïæjý¤N<T0@OÌT‘Ú­ žüÅ‹b»ΗÈ÷ÿ?DÓ—-Ÿn*s9é€?Ý©‹~@þ÷éG—Ž7U‘®¤# ×ýÚQ÷äϨ(‡ª(è)ØR€M?ò¦Iaž=1Jç·SðU¸•yPà{†q—>Záp[éœVC±f%‰$ûÕÓ<ŽGÓ?Ë4Ü(áÇæqüé@Õ‹VT%Ž3œjÁ·‰ùŸjŒÚlÎÓÍ L‘§ùInƒŸZ®×n[Œ~4Ù÷äQüê ÐîoYN·6ņ^§?•Gö&BŽœúþ½WÓÁX‹ =sL¹_.S×Ò¦Ú—} û£‘w)ç¿ÿª£*GQÏÒ¨Ç!GË­^I<Õà çNĈTvæ£9Ï?áR”#¥DìTFqL’¼ÙfÚ)£d|± £ý£UgsŠ`X;ˆqïOÜÖ¨+`æ­«3@G9 ÜÓsM9’qÛ4ã†ïÇÖ¸” äg&“@²o<M'ÙÛ»Sn°OoJ‰§'€?tÜ1’cϨýOøVEÎ¥=ÁûÅWÑsüéði72\ykþÞsùV”:-²dËžù8•gx£{NG8äl*–cØrM]‹C¼™7l@çºDµEùbÚ‹ŽËŠºªœàb“¨ú4—S”_ ÜpYÐøÉ5¡oá«B¡¤’G=Àãÿ¯[„ªõÀúâ£7'Y~"¦òeû8"’øN^°“õcNþÁÓü»ÌÿNÚ•²ç÷ëúÔrkh9›òÒ´‡îy þÀÓ¿çÜ~gühþÂÓÿ瀉ÿ…¼GdÏ=—ÿ¯HI•¾¹ãU$Òo#ê÷öy«ÿð’Ú“÷_þùãJ¾"´=wªÿõê—9 C¹ÎKm,LC#qê*"§nÁ×K.¹c"è\´™ý e]]XI“ „÷ýj´ßTfâº334c$}(&œ£$}j‰ # ;ÓGÒ¦np=)¬ÀÇå@Äp R5E=˜tÆhP[fìvëšfÐÝZA!Û´Œb6J8Äÿ4 cüiwäç”vQX°ÿÏøTÎR%Éc“ëþÀæ(@v˧ޫ|Ó6sŸsþsHc÷1rÀ‚Or§?¥EBî džôRCÛ©ÍŠI3µÿ˜·L$?/šIÃ{‡.¡{ Êyr‡2QO>Í´7ëùÔp#OÕòĩɣŽz~G¥[7žj}–H¸ÃA(Þ£éœ0ÿǨ0ž%Éòò¹ÿ?ÈTEvãÖµ¤²µ”‚`„uýæ@üðGãUšÊåy ®?¼Œù¡€‚¥» išriÐí%D­Ë¿‰ô9Ú³u]Q¯'û ¼¯å‰,K@9«:ö¤Ð@-`cæJ9Á<)ôä~•…‹[[¤Q`]Nf){ž·¸ô©HgAŠÆ%™¦@À>Ä. ޏˆëÇBxªó\ì¶’.fYˆâŽéÏ·ò­%òm"Hm“æQ«ù•éõ'ߊ­œ—’9•·!Îõàníè1ëž=ê¬BgG¦ÚÁ¦YâÚd+ûÉ7(ÜqþþAô©ç‘QP\ò@s…§ Z‚Jk„e$^|ÎÄ\n\R§Ê˜b]º—$’OåP]Ƥ*§<³œœO©=iágÛñÿI"îsÀë’?­D|ë…ÀT~­Ôý4Ägêš}½ËW1] ùÔu=¿Æ°$½»¶•¡¹‹s¯6GøWg+ùWžìG'ôÿë}*¶¥§Å¨BT€%änàþ¥4ì-9õ9vGy$ñúâº+VŽ[u’"¹*7'`{ÏÎÜéÆ+q6s†ÁÏóøzîØ¢5¼SÛ™PÏëÇëVL‰ß óÇC°uìÇ,STŒ~G?Ô×Hè&¡RHÏÿ^°õø·Ù òUÏOjBŽç4ó=Ãü …ºÔ±C½€ëOŽ0 \“íŸþµYŽÝÙ‘€QÛ©¦kk±ñÁ}FOrqNsÇȯøúªq¤çê¥<$kÙsô¨5å3ÌnøÊ3~ú š%’3‘?Oþµ\óÕ:©ux")OΞ¬V±cÌl ÃüûÒ9çŸÄc‰™ßïM]ŽQ ·ëE¬Mî]ÜÔRî>•Qî¶( n¸É¨Ž¢XÿñáþК4FH$Žÿ=«ñ¿ÒåíZ0ê Ѹ*UN:ÖUÓîrÝG\Ò[ èvì%‡~¦žgN3ÆqÁ8ª äð3Š’é,H\f¨’à1H~R7{piÛ\‘²= ÿ?Ò«y*GÊÄ}F•NªéÈáˆîOùþ”BßÂ늂H:”?…Ne=%Lý)BP@œPmáUR7“Ú¤˜‰¡¿_ñ¨¼Ç-§55¸fܹz`Ò±Hm¼Y>a=jFÀl=êv qÀ¨’"ß3 ¹mš î̃®j½äMÐr3NY 2§vÿ?ýjÑ;'ƒk¡‡#ÐÔÝ¢”SG7#Œžj“I$V•õ“ÂK+oŒwãëYÞâ­;Õ†f§†NÄý)x=i ‘L dž óG™”çùõê ÀóøÓᤑ²Élþ¬Ñ¨êÜŸj® ×Ú’C’9  K+}ÖÁ÷)SÛÑŒuçëJI+‚sõ Ãxð?JkΑ™€¬gÔd~Œþu,ŽK;uíY¨º½iuu\„÷5BmRíÇT{UWÊàbš¼¿=*ÒFnrdRÝ\—gä÷ÅDgsÔŸÏÿ­WvÚ ’' 9ö£BH­žM+I'B¸üêÔp,c¦O­2d{qÍ0+ð¿OzBØ ãŠVÿv¡‘»S¬å‰4ÌÐiB’q@„ͧymíùÓH ã½HÔv4ÃÁ¤4­×ŠÒ¤\ocŠ‹µ>1óPüd“LÎöö÷¦±fÈÏéR*í‣PÄg§4äCO0z†ž¨àäT©=}é9â€#hÊÿJO-oάnÃJT0;Ö¶`ã½\OÀ~¤P.÷ÝOq! v–,GN§Ž}÷ªÑ3ýƒ$>g;Ž{ŸoAŸÒšFlž,,R]ɇy~îsÀtsU§H£‡*Š2Å€$þy«7M±"G¾3Øvýk/S•Œ"%ûÒp:ôÁjQ†âÒK ùÏBîãëðªwÒ-Ôñä€?¥m¸ˆ[€’@ÛF6È0GŸåXövžbKrê˜Ý´§Óÿ­øPŠ+\Â-7Œœc çíSEx ±„(t"6Pãðú©ü*wº·–âU»#F[g=ÀÇ\ýET¸Ò."_2ÄÜ®2r?àM0$?ÙR_00þ(Ýø?B¼~tkY2c¿V·ºÿìÄÿJÏóB±I Ža’?>Z‘M£ýá´{ýCP…bñ±|.à+õOñ¦F^·ìè¿û0¤ŽËNr ]ªzåñÿ²TãMÒÎsª {MÿØÓ_ȲÏ} 9è ý7S¼Ý6>U&¿ÙPæWúUå´ÐaÆëö\HçôQýEÚ>µ'Ë´iˆîbP7.GåH Éqy2µ´X£è$u-ø|£ðÅ8iÄÿ¤]Ìe÷'åÿ¾›ÿB©?µïµÿÝ1WEŒÊÃþG¥/ö¥s/™¨]‹ÏI2Çè$ý8 žþÚ0¤ö 2GОGàAú†¡#-¤ ÌOÌÃ’¹'#ó¦šn—bx¯4ƒ“ç?ï€sÿ}*;z]ªªTtPQô\øõ0#µðø#λ™Ys’#l&}ÜŽO²‚}énµK+V†ÕU°z(? Ïü Ò²%»¸ÔnR1½ÙÎÐ ÝǧÓèh%¥œ, Ó™Yxòc?§ñä)c:k‰îˆy#8P=`=…X´ÒežSçf8ÔncŽHúuSŠÞÓ´T '¹F »*œd}yϧHñý®óÈ…±ÆüaGIÏëïJá{……ªË)òZÄÙHðFöÏSÔõïô©5mBâÌÂÉp­.IaÐÆ9 çÚ´ÛË…7¨ˆ¼rÀ‡jå.$}Jð±È p3žŸÎ’Ü¡lš).%»ºvR²•Ï^{¥tvÿãâ@Dó=c^Ê9ÎA䎙'°¬í:ÜIpÒ2¯“ £%» ã>ŸäVä@±.Û±Ðg¡¿ò™œ™¡m`.f|ìÉÎÓ×<õõÏ~=…Kin-à 2XòI?Ïüõ5s0•³áìõýx?§­X-‡QŒƒÖ‚FËmæêå\t?çúRG â°ÌѲô9@OþƒýjÀ?çÉ®c·MÒ:ªŽäÿõ¨“'Š%Œ† ³ŽŒÇ§Ó°©·ŒgŒ{ú«ŸþÔº¿—ÉÓ`ivÛœ{ã<~8«qøbòóæÔoHÿa>oð›Hµ2äš…¬Gq ŸBâ£þÖ³çQç×<,TñxWMŒ|ÑÈþìøþX©‡´ÀIªc½¿Æ§ì™’« Ä/÷]Y›A“ß8éT4¦û<“Ûþ­øÏÿ?Öµî´#fÓÝÖQÕä8ôÎ?Ÿæ+ ­Ú»o_-ÙvºŒã#ê=»ÿJ¸»™Î-yÇ==k/]8Ó$`:sZ*àŒ§­SÔ”K¦Ü.9ÚzS3Goä^jÿÀÆ¢kÛ†ÿ–®>† 4•F×%72¿Þ•Ïüÿ¿Úüê,TñEœ:ô¢À:5Üx9õàTµ0«ž:ÿœÐíå'z Ž5ó{š@[ƒ….Ý=ùªóÜ[Œí-̸ýÚôïU³L ¶×AsŸpúóΤkgêÛzž@µgn4¼M•cô$cò¥aß¹;E*uB>£Ò¦†Ü\Å"ƒ™!{‘V-nÁÀš1ÏqÅi$1J«">0x>Ÿžjš.0¹Ë´eIpAÅYXO–±yjÚº°ó (cœ¿ýzÈxæ9íÏ_ò)©\‰E¡bˆznM1%ÞáOsÆHþ´Ÿcþf¤ŠÍƒ®YqŸCÍQ%•·bìcÓüâ¦wÀÛžOò©zv¨ Ç=€ŒÂ§š‘—IîÜcó¦ Ã,vúµn#‘A÷ÆOëÍ}ªÇn íþqHÒ•^ >æA…Ç\æ©K/BO(Ä/óncÐÿŸJ[«±å˜Ðä½ÇoóïYò\|½3Þ«3’rMzÖ·Èê-çPËÑIÇA¨XyDË1žÞ•œŠØ°½'‘1ÎF'¯·4­mQ_ŒÆÅ8Žr:Uûû!÷ÇÌdöíU6îÜU's6­ Á÷MH¹ ™´•ÈéšB­×šS’N3Šcž}(!‡¯çHU±¸ž>´ÀA“Ûô§o+ÇoCÒ¢ÎiÛóÿë  4Â.ïþ½iè@ü8þ• ]àþþºrFäepG¹ÿC'FÞBàóR›\r¿–)±ˆüÊÙú»m(R÷?áR݆•ÊÓ½OŒÒ±0sÒµb³‰â2Þ¤[P=ªûÆ—s)4rÖMÿ|ñ©†‘l£æÞßSþn{”‰ fSþ•sÚŽ¸d;s€z¶9¡s2š„PšœÖVùŠÞÚN…ˆÎßϽa“ŸZVbÄ’I>¦˜Mj•Žy;±ËŒýiN)”M1,™õ‘»Šni3ë@ JzZm)Ïz*T;lÔB¤Q“þÈ  ãq8§¡.N8õ¨Y·6; (€&V*x—zƒ÷søÕ]ÿ0=§y¢€&i˜ñÚž„íÉÍW-“Å7Îã¨V;‰=)¹ýzAó 3Où¶ãí@‡)|u?ÿ^ƒ7ltêqQ—cØíFâ:óøf€$!qõ¥ùHê1øTE³ØÂ;~¿Þ¤w“"}Ç4žÆ¯+)êÄqO Ý[õ¢â)Få~^Ùé[Vw „qÇlœU?³EÓhŽ+BÄ.Ýœ‘Ó“-‘¿2° ê¿ýjˆÞy-±òØîµe ùrWÐó¥Ewl%O1>øãJEN$ˆ•`8ïXwŒËò.Inâ¯Ä<·äàt#8¦\Û™èÀ$ñŠb1Œ{YXŸ¡¨B‡nxîjô°²grcê1ý*¹GaŸ¥12í'Úœ­Ö€Gµ&޹4ÀA׊ظ³éëà_ÏúÖ}œbK…p95.£6éB÷zÔ½XÑOq9£#:PÇ&”Ãó5Bã½”6&‘€ÉäŸÖ®ivëý“Ž yrçàä÷ƒÆ)IèTÙŸ¯[½½™c2:¹Ú2˜?ÌŠŽÞÕb‚Îl˸Rsþ6¬¢çW´¶ùJ ÞøÿõÃ×½O~Å9@â7óþ”/1Ër¦»¥}¡ÒXï$; €çcß·|×?ç\i²2Fï ÿäøƒôî3]û•p²¤†é¸sÎsíU.ôØîBÈF<ÔÈ=QÇLþ&#’þÕŠPòÒÚQŒn*Tþ‡åŠUmSóÁ4îNóÅY¿Ñ¯-Õ·[$댇‰yü@ÁüÁ¬fŽ%8ud#±^¥;ª¶:œ¬÷Š?ü”ÿ:i:1ñ÷w÷ÿ¬R‘Ÿ0~ ÿ¥Ùy”ÀOÿLFâéš"7-y ô?/þÉS$º5¡NœŒÃø¥“?û)þ@×=‹aÖb~ˆ?®)Á­»,ÿ|Œÿ:,7n<@ ± jtúÉ€“j½„o]îb§žýN™ü+*1)ÿQ§1>­¸ÿ *ȳÕîábíùäþB•¥†I7|@'åAÓòÍWÎ3üRŸcæ ýnÛxVIuĤŸ\ãüOò­ë=Ò×(-ê?Ç“úÒ¸îsv÷˜À¶Ñâq“ùšél4{{5>cÿyO éúVª¬qªª¿@*ª3Àwõé]‰”/n>ÍKƒˆ†@ ã#>ØÅA§(ŽÔ<ºipX*’}‡ÿ9¦\Â&{{D*|É7¾ÉQÉè;ý{V›ª"îmÛTt$à~©l¨#]ÔYm£-–8*Ã>àzUKh<«W›o/€™Áãÿ×ú ï/ä¹a˜˜«Ó¿ŒœUÍD‘ "õ'éúâ© ½lZÓÀ6‰-ó îyéŒùb´$å(á¸8ã©ÿƈcX¢XÓ Àb¤ÏÌÛ»£hè2zãÖ—ÙÇ>´ÒãpQÉïíJÌAP:“úP!“ζð4¯’lu>•Ÿ§é7õÁžá-Q¸U8?Oþ½IwÞÞGj ã=ýOáýGjë­¡ŠÎÙ#R"òOOsíëQ'ÐÚ”Õ‹kimg†Þ%D€ëî}~µ,’,jY˜*’IéY·z¸‚6tÕ‡Èpyý?ÄŸJÇežýƒÝ±ÛÔ!íøtŽOÒ¥C¹¬ª(š3ø†Ûm¢’å‡E;?ðªÇVÔŸ•±‰G£ÉÏó"$cåPLõ?™¡¤ ¹«QF³{ÖnUÀ¸°b?½ƒúýkWžØêÝÛ7R7¡YHë‘Ôdcò­F›wÁ=½¿ÊÖ`i`ó‚‡9ÇoÆšI ËFh©*Ù'ƒM”GÞZŠ 7[DÝr£<ÿŸCR1ùJdžu*•×ш¤ ìãW/Ç—:ÿ¶{ÕpÄœçTh† üªÊ /#¯µ"FÎqœ×ùÔÂØõ8 eWùÜò0:w©ãù#;Xûb¥¾‡õ©¢ôü)¤-%lž?ÏáKö&[ò«M#œ~‹H{ŸÖ˜ÝT}îiÑÛJH(¿ˆ­8­@äS…O§SJãEX ‘ybƒè*Ê\uüýtÖ©Ýr~ò’:ž6€ã¨4JÃÛüÿJ’ŠnªÄ†’zãþ LóîÔŠÑ‹îmbIÙ£Ú\Ñp!òÔ 9ãúU Ý>9˜Ô1Æj{ˆçÆwîÓÒ KÆ|ÃÔS Ô¡*AÈ=μÖÝåš]' ÝýþµŽÈÈÅXG¨ªLEÛ ÈȪ2±v.O$æ´1åiãý¡Y¯ÉÉ¡¶ûS½…7ÔÓ¡øÁäR€:Ž)½z zäuéõ  ¡F•Ö$R]Îõ&½FÑ¡Óì­âU Äw«ã#¾9öæ¸éÖ÷wÆéÔÿ£ÃÓ8㿯?…wZµð·¶ò‘ÇŸ?È  ž§ðœîÝŠsžÕ¦kÍUïbÁ[r3êTœ‘ø×D'[‹mÈr®¿–k Âv>›7–ím!À哞=ǧù4šÐQž¦UØ1´r1â;˜ŸCŒõüjîÁ¼“ÉäsF£jfŠåryúwÿ?ZŽÞa5ºIŸ¼9ö8æªä² S `ý³úÔô´ÞÏÿ×K¬É‹dEêÍÆ=‡ÿª‹Ÿ»C íôâuRÉ•z·Ê=óþMt1B!µD€D ‚?Zæµ.ðÁ 7Žqõâ´ç­¼³½Ô¨B“äÇòÇëS#HhŠvDO¬^N2UpŠO>Ýp=?ZÑ–!4MwúV^‹Ca»$™ by9þu¢g õ¿ýTÉ{•-&¿3 14QÈ£j¬¤(8ô%y­/±kéÀŽØgCýV±µ6%D‰8‰€÷¿OçNƒÇz̬8·˜*í ÈKûå‡ò§¨XÖh„ãôÚ*¼Èž[:ß]ÈÃøLŸ.?B'¯?Ze茒s·©ë͹{FµBg¹‘If"1¹‹gz“ßùSµÇYc­3l§sÓÒ¯YGäYC9 ëÔò{zûV©rdÕÚJB¸'Мzv¨Z³m¢Ma$?+Î9>ßýz| 5õºžs"Œcßÿ­PØå®œí*N;dÍOsò][¿ë·ûCükC¦Ç~j)çXc-ùRÊJÆ}È™ÅPù×pÃü?xÿ?éH”\ƒ*¸n]¹ojp“.矗åöÿ?áMÈÜr®ìßd\/Ï!À[ÿ×úP=Í-ÜKq5áQ?¯ôüëBúá%Ýüµm¢4827âs€}½ûT¶b+=>4 òªç<ò¯>‚²Öst'¹ãòß…f•Ù»—$Fì/'Ÿ9Ý78$’ØgŸÇ©§i•rvüÇÛšiwoº¸÷?çúÖ‡3mêÇMFÂ68%OåKå“÷›#ÓüÿõéÀÒ=º,™Sôãü*͆HH FsWO5ZC±aß `Œý/æ·0“óFÛsíW]B.f³Ôý›Vs¶UÈÖ­™ H¤hÎGWu)OcƒTƒñ×ùÿŽ+OY‡7Á³Õ­QXöò*‹[ žid~u"¡êXÿ*þøüipÇÛüýhÁÒ¤níUœl8© “rcŽ=鉨ç%†*ÜhÇ}+5®%V# >ôñ;äÀÈì;Ð= @cN\ÄŸÄ?JÉ2nln'ZPØ©å+šÛ#aoSþÚs´Ÿ¥fÁ¸ Þ½Xqó=óK•Ú2ø¹ŠEÃ0ç±¥0Å"ü˜ÛVqQ×¥ Êœç¹{Úw4V9¡?)¾€ÿŸçI4Ir‡, taƒURîT?{wךµò†ÓúT´Ñ¢”^†$û`¬ †žj,}yÿ?oÞ[E{AÀùNk›¸íä(ë‚|V‘w2œyII 8ïNÀÿ"©ù„Zp›§J«_ˆŒxÏ¥;#9Ï"ªC8(~½È©üÅ 2=ÇøÒ°É„¤p:w˜GF"«‡ÉÁãùS‡ŽE!¸dàŒ\óJ.c#¨¨˜åO5_Š,e¦‰$Ë# úv¦!x\2åHî:T àäQS$ÀŒH?E‚æ¤rGw»ŒÖUÜmlpA+Ù±Ö§¶6øÛò5iü«ØJ¶þ¢§bþ5æs¯óÎsQ¯Ì1ŽzŠšæ7¶¡õàúÔ¹Èàúb¬È^yä~¢¤\2ãŽÿñ¨ àqëJ“'õ  kn\ à{b¬*¬c¯>¦«Ér¥RiÝŽI4 ÓbÁŸ§ÿZšLIÓ'ó¬ß=û1¥2ÔQ`.™pITžøÇêiŽÍÕˆßÿ¯U ÃúãéÅ"«È{ýh°îLÓÆK} ÿëT-¾_áãü÷©J$CççÛÿ­M,ò ¯ þÏ…—óô¦ífè*eÛ>æ‘ßiÂòϽ1²•<ЭƒHy4”+üËšˆž=éÁ»v¦ÐQOEÜØíNhÀŠŠ”QGz~àFZRÍ0w£'¡é@^¿…/VÅ5;ý)Ë’hűÀ<ÿ*ã“L$“C6OSJsIØSy>´”õã¨Í R}þ”ðÛÆœú:z±ç#_ò(Ì0õ§G÷©ÄŒP2y  v‡ Z’ö6kŽÆZ«nÄNþaV¯ä5B¶éù‘ò:{5Oó'ÞP}ñÏéTXIœç?4¼‹÷³zÞ‡QðàûóWŠtÈ*ÃÔsÿê®TMê*h®6¸*̬;ƒE€ê–2ÑÕÈÇBµY 9,Ì¿]ÃüþZÎø, ò¸lŽN?Ãü+R9c‘ žùÿ‚vJÊH##Û4Ö‹iÝ@ëŒVaô=²3^v‚R’d®x'šŽxüñà7qØÓ/îçÚ•àŸz–Êbñmíã­1ZȰˉðyéO¾²óÓ̈« dc®?­Z’”g¿®3üÅF%¶A”?ÅØPmÿËH=;Ök[:ª‹(ÇzÆg$ò*˜Ìg¯4œô·Ri6g¡¦ Þ; ·n? C©Tcµii×—–Ln­äeT 9ƒìG~•ÚÛOs|ëupêNÀ)ã¥rZd$Ȥ,ÈA8üoèRnöÌFè[;œÒdIèmqQLD8t9T£¥¤@±\¬’üëÔž„V$sÉnÃÍFEc‚6ð¤Ÿ_Nß•ZyM´Æ6ÊÆÇ!»Nq•(pCpAÇ?§4’±¥îgj¤±·¡oǨ§ÊÃí]¾Uã󪚒ÉŒ“¹†ß›•ö÷ZœÊíŸààþtÀ©vKýŒGέ]ÔïãKiaÞC²í Ž¿×µg^`‡;[¸éÅ[µ´¶·‚[Ë„›À`{&ŠNȯor©n¨¢RíOüv¤…â¼ ŽàïvùH'ßoõ÷Õ\Ÿ”¯û­»ñ ?¥iÍ«Ïä¬rÃó$€œsÔóŽ˜äÑtË^n§gçï<€gò+üÍG¿D¶%ÖÒ)Pu7Ó¹cïÚ«E«Cs|<çuFŒq¹pÙ‡§ê*Å”ŒÒçT?.-“žO9à~µ6î3QîtW}°é°Hdû„G>ÿŸZeÈÓQ”>‹ ËNÕ~ãM³]"„± Õ€ |Äö#øG¡Å7ÎKÝTF²yNéÆ{g±£@, 7DÀ-`°È®?—û;AVýû#S¥¶”«ÿÝyà6?U©Ñ‘ŽäŒz|ÛSçN¹Léú ÎN?º™ÿÙj¿Ù´”rF_¬C×Ü­ &ÐÃòÂ|ÍŸäæ¹YoàK‡À‡Ëß‘ŽxÎ}騙¶VÐclGû ÿ ÓdK}¤­Ó± þËX‘ê¶ë «¿#ƒojhÕcUnFâ8Â÷Æ9ý(°Y/8BY„€vg¯û•½‚ûÉÀ9ÿã•6¢“ ý=5 Ý ”1Ý€sÀ´jU‘¼íßË 9;@ ÇÛ³T×Z}Úµ¼RG„yÈ$߯ãùSt­FÚóU·$º2;ŸqƒÎ{Öí컵Ã.ÝÍ)Dzàv÷¤ÛB¶¥‡ÚˆÝ0µr¶¬$ig=dryÅoj—,g*Nvã¿~?­bÛ!F=2Îi@&Éìeß—Ùr þ£üûU­A A‘Ôtª#Mæ€rbãôúfµ&HYAÉ#Š£6Jòy°¬£¡ÿµN׿¾‘ð¦ÏÓÿ¯Ke&ûvˆÿÆ=OëùS4þn'çŽÜûš»)ýÙ^íò^xÿU™MACÇæÇ!-ü‡\sï×51$ß.B¡ë׿ô¹ÇáÍXŽHð@U<•‚}Iî~´ ;šIï£1nò¡îŸËóÜHHõw8ýN*#‡[\ÿ¼”Æ™‘GV¢‘ƒ}ëUi¿òÎæ0z|·sÿT¦Ü°Ê\1~WýpOë@ìgêd£Át£˜Ø=ùÿ&¦fÃsïëI*É6,gk(ÉÈàñÈ89§cTíî Сç…ÚIïIö)jÿ~6ú޵ž=kCT9ûUIAêjŠ[=h'&†`£ž}ª6Y$è0¾ô†DÛ¦}¨3øâŸxf êF*rÆñ§˜½õ<Õ×E– Sc#\f|Ã2ñÜ WíNgˆ6\üÃØÑö˜ÇÐÿ…=#b7tô§d''Ý¢7‰ïù9nwýÄ'ðÿëиU£û܃S0ܸ'© &Æv€=ñþ¦Y¿º?3þ€´¬±Œ}h2 Ù'Ц^Nò ú±ÿaÉÿ–Ñþÿ¯@TîëùŠx–?ï/æ?ƲŠ7üöOΚDƒøÐýQ`5üï$‡GûÔóC£o» H>•ÏçééV­o¤¶“$;ö¤ãظˣ)ÍÃ!WR¤`ÿúêÜæºÛhõ q<, €qŒsí\ë)RAЍ»ŠQ³ÌaS¤¡¸ïU (8¦IxÔ~• •‡^~µR9sÁëëS NfN FZ™E?ÊŸš‡<špz™_mXŽ_ãF*ERßô¥•9šf”Éü;Xí” `O ÛÊcpsŸÎ´Ò@ÀH„©¤*|¾]ôE\…™Ï­JЧ^(äóš|±4NQ‡#Ò•!’P|´,3ŒŠ£2&ÉäŸÆ›ŠÛ¶SçÍ…r{ãŸåŸÖ‘­,'å$(Olÿˆ¥ríØÅÁ V£hÒLN®¾Ùþ”‘és©Ì‘ýÁ§Ì…fS† ß3çh§<áFȆ¯J±5½Àûð°QÓëMH02Þ¸•ÐX‚( ÎMJÌÚ½{ñÒ’I†B õôªîà.ŸÆ€$iŸ*œ™õ¦yõ&ƘtUÈšŽ¦q“ΘÇq¦!””´”å896ìŽEANW+@uã¥7½)9äÐ'ÿ¯ŠLÑJ@Í‽ /lPƒå>õ(@<ÐxÏÒ”-?>”ð¼?#@„õ4»Hê*b£Óô£sÈ Áõ¥'ð¥ ÎGOz6Ž´†zæ¤Üùæš6Ž1OÏ8 B£`Þ‡¥Kq)«uã‘éPäÑ“ë@Àóاÿ[˜>¸üOøÐh CHoZoJxÍ99'<ŠX\‹$>†¯Øß}œõÌdr£µSÙºÔÇ9梂MŒA8çÚ¤©$@ËŽyÿµ~æháufÈ :€MBf·ç(`xÉÇóКw—* „7?­H’$ëÆŸN”×Už27dz­RhæµmýGMØàûP÷ËC€ì~‡Ö¹é—c:WK*­Í±ÀŽLç&ݹƒ}à{Óˆ™_wµ.êi¤Áª*¾;Šž7€GZ¦«6p™®â‹¦ö4Ù¥œ~Dj£GÓŠKhšÛT Ý%\:dž•< ñ W`ÅxÏƤv #¶ëëHÈÐÚŠb¿iUÕÉøN "Jz”B[rz㯷ùâ³­.J·‘! ¡ÏÞâ+jL(ÝúVÄ@9ŒÆsÐúPRd“ó¨ •À”$„¡9ÎcÏZ‰äóW$ÅpIÇøTvÒ~á7|…#L±.†û` æžëõ5!ÕöÙ}Q‚ìϘ¼¥C3¹Ž™úf¯}¦Ý#MñÈQÈÛÜçëÁ¤RØÌ7×XáQsÏ`?^*Hîîš -Äk¼ä18äÔ—–qÜ^[yj¡eê¿N_Ò·“HÓ”¡?ôÌæ´\Yb¹PØu•éøÐ ”üÛ¢È9å×ük®].Á:?ù-U[as«Åik4¾QæFß(isÎmÛ.Mä_ð)”ÿZAc4ƒòÜgû¯œþ@×wý‘fWþ^ˆõódÿ⪼Úe²¬3´}4²g¨ÿkµ @qߨäœ5ÊçÚ)þËO_ïl-ÐϦÎ"À×Vú]†CfVÆ ódþyª—¶V)nR8›) Ÿ¾bsÿ}S¸j`Â/#t¹?÷ïÿ²¤”ŸõäÿÛ?þ½vˆ  zcúRïN‡çÖ‹².Î9| äá§oû÷ÿשÓÂ:Ì~»úõÕǵ4拱]œ»xH€q:g°!«6ûE’Ìu‰Ï`ŒIüˆ®Æyr )?QY…¢û@]ƒyÏÍž?ÓCMœÖŸl²Üí‘N6œ`÷­{£Šù•QB¢p~¸êúÔja` r€‡ ƒŽsÁíQÚy¿ÚRmÚŒÈ9~qþ~´µ/êd›@™,8õþµYZÕÇÔ—ëû˜;9߃۱ôâ ‰PnÈÉV<þ´2kuß dg'ð«¶r†„&~dù[ú~•R݆Âf?çüšeÌyÑ0ÃëÍ2IÚUµ½-œ+pþ½ZÒ„–B9fßäÖÅðž,2í‘{Áÿ•^6ð™'"(ž£Þ‹ÑlHn" &ysÏä_¯O­Y@± UÎîzÔ19dÎJÌ„³+´žÈãð ËNw)_^+ŸÔµ)¬á·Ž,¨‘9 c‘Ç^kuNPc#¿5Íø‰†6lv{ŸþµGsmAÙ‹2îbzžOærj/·Iž~f”F$*öãéÿÖ¨ mb§µ¤âùÏT¼ië{È>^ßpø ",Ê=Or;s¼dp9Ç¥_´Ô'NЏ#l™çë‘ßÚ–Á™£br)oVN£Æ›·™Ç‚ãëþ• TÝF3ü]* ›òþµnõ ³¢s€¤Õo²áñŸ—ùӰ嘰È\s×4ɧ(§Ôñþy§°¦O@*„Žd|ôô~Î_2Ôq×µMlÛ B{r>•BÙŒ)a€x«Ò¶d^«ÁæÊz„;dóàõª5µr{cŽxȬR§8¦€’f“åšµö€…`0{ÄþcŠ®H…6Ž\õã¥DXPÑ$Ór ÔäÿOåH`ùŸ?_ÿ]Uå8ù›õ©’ÖC×êýt/“¨ÿ?;ˇýŸÓüM"ÚÕ€ÿ?çµ8[ þñ C|¸½GùüE.ÑÙ¿ñãþ4ï&1ØÿŸÂžŸçò ØÁ<}M868Ü?þ½4½³þ O/Ïä)rÒé­äá¾CÔ­?P$ýúsýà*†Ð;þ•nÒääÈACÀê• u..êÌ R3ƒƒøúê3ŸQøõêÍÔb)ˆå=2øUsƒÚ©°Ð‹žè?Æ’£ïÈSvzr '$çÀ_4ÏáÅØ÷ãéK³y'?@äš@Ç §O“Fðxþb¯¡Çµÿ&…*OQš2GQøÐ@<ô÷ ‘f1çiëÚç)!Á`áÇOçUÂq‘õ§ˆ”žXþ€Š\ TIíÆj8Ë[¶Ö\Ò„^ š_0¶\ëHfh4å<àSy©áXþÄZƒy–S¸zÿœÕ„Õ.!|<™ÇfÁçñÿ£òœŒàt"¢‘¿xHéž(°Ókc£‡UW½yî¼?ñ©‰Ó®×Bçûß/ëÓõ®Ye(Aa/>eãÛ5.%󾦽LJQ—u¼¸ô k.ãE¼„ŸÝ«ÍIá‹-ºzàÿ‡õ«ñjòª4+¯®pN){È=Ös¯‘œ20#¯+²6WCïëÿרn4[º6òÉ鎟çñ§ÏÜ~ÍôÔäñFH­K­ îß%PH¾©ÉüºÖk!S†>†©4ö3i­ÆàjB¾ôìPHLDtS‰É¦ÐÓ—<ã4Ê‘XüèzŒ~¢”°"›é@¢œu< Oôªû‰îi3@7¢ûý9¥óö8¨U¶ÔªÀÿõèØ=H:9¦=z P!¼–¤+Í< (@¥ÅåÀ ñøÐ8õýhÈíüëB$‰ÆBƒìsþ5)³‡M¹ô'ü)\ š;Uél2W‘ùåôoþ¿5Enœ})<°¼güþtÆ\†øÆÇòjô÷¨rZˆžÆ¦ˆïˆÄÍÇLÿ*@f0Áâ—µ:D*ÅOcMéÖ˜€ ½¤ƒý§oŒ}ñTwUÝ1ö…¾NñÏã@=ŽåTaïŸóùS&È…ñØfž£’sŒö¢D/HÇL⑉*œêED²”ä’yÅ dŸ¾1Îãò¨å– mÞÇÐý !–˜åF0HèER¹Pãp0çSÇ.L¯ëÿרåu'*F{Ð,øoÏ ÔwúžÎ*ŠŸ.W_áÎåôôÿ Ó1½J¼6zzVEÑ&=Ãï©çÔzŠ C¯÷-ÏBj¸¸j¶cRŽ3úTsO¾%Rß1àöõü*Τë•2"Q¸‚y#§ãL¥¡£edúYË•v1Ò´ÿ²ø“òˆñU«,P`qÀ§ 7 ‚qïRO1ƒ-œ1™[v:yJözŠÎÎ輓ÁqöxÔa¥`ǧrONnOºAˆ ÀsSÛFŒQÈ]«þ®>»G¯ûßÊ“v.:˜Gz‹ËÏ"gïDÏàA?¥]@’X‘Þâo˜g"ãô­(¢KÝFI·ÅÚ£g¿ëÅiö㥠ŠNÇ:<5šyÒAÿÄT‹áËeç÷™õó?ûÜ&›Ÿ¥2.ÌŸì;uyƒþÿÖ‡O ÷e”ÛFÿâ«I¥ˆB ì*L53$[›pYdi?‹#úšj]™ÀUÈ8äg¯øqš¼øÇ"²5b€Ï+‚Kc·¿øúþ\vêGÊ‹ï¸äŽÃ×Ò ºŽ%\`¸ׯ õ5Ô#‘HØ|ÑÛ‚Iúõý(„¬¬Ød}Þù?­1 Õ0lßž˜?­U´™“P€†$È€¤þ5~ñwZH?-bœjÎJmmŒ{Ž}èc‰µ©°FÄ€ÂEÀÏ>õLenv#?—ÿ®¦¸°€[I…o3œ“ý*¦âaŠQÉ“ú”S.Fê…†áÈÎ?ÏáNcsßü÷¨õ©ª$¡y ¢‡A‚N nC*[X†c…UôçŠÂÔ%*wÎM[žo=Ö Çb[ÜúP ÛC&¡/Ú."Sû¤ÿóÍiÈå™!R>n¾Ãð¨|Ô† ÍŠ8ÇáOÓÔÈrÿyÏÃÒ‚YuFÕ1Q†ÃÈ£¦Oò§ ÜÔ_/žëžp?‘ „ÌÈ3Á>Ù¬v2Æ^>ëçðÿëÖÂȃ×¹çúÕ=IwÁ #ª"–çnaß¿×ÿCùÔó[y’(û£8Éqþ­'ÙÚ&$ò¤zôÿ늼›C·'ƒõÿõþT•R-‡sŽ¿çñ«§||Ç®jE…GB§ùÿõÔâ wðö¢â €|¬{Þœ£÷R§R¹~ƬCQ¹\åŽЃ“þ}M"НÍã‚z(þ´jƒ]t[<ãž‚¯öôÀϾî3ާХíV¯W’Qš¬'â˜áÄ©‚2GZЋdŽEfÇLJSõëZ02çpÇ<)1ŒvÈéÎ:J¢ðy;äaß ZRŒLŒ8‚jÒ™\FêMfY¹«ÀŠ7J@„â§òÒár}OÑfÉ}?ÏåLBùñ  Š[Ø O>WáT~Y¥"þñÚÝŒb5Ÿô».«‘øãùPa#ï¿æÆ€³ÉÔíü.i¼³úP!»#ÅšM¨z~”ýЯ÷sùÿ5® Ó<²{7éG–;ŸÌŠcLÇøJŒÈǹü 'П­)(܃ÓëU·Üšr0wõ  5T¼µØXy«ßŽk4©RAÁÇAV-g1LL÷ë@ƒ/ô¤#ÓãûÞô ôàÐc€qJ¼Ô¡>L{SBž„~´)P@ ŠEÎxý)UÁ4ú ÏÒŒRæŽh8©)ë‘þ~”Ð 8©’!ÜãòÿëP2HÔ§Ì•î8þU~RUÑÔ¢«îI'Ûýz™0r†ãÛÿÕRÀ¸09B@ôÁÁü¨hà—åe!¾„~µ $Œ¨ýà3ýižnÞ¬£ðáBC-€SÁÀ¨M¦:0«ñΧå,õ4Ùå- š/” ÔLŒµe>¿¥+nõüþ4 Ž>¹«¡ºpjB½³ô¦oÃr4ƒýž´§žôÌâ˜9õÿ?7ýA«å>_ĆHãu8ïÜPBOöwÈêÊOÈàäcéV³Ðœôõ¦ÝBÁï:ÏfÈ ]1ÐýåÿëS®X™ún#®:ÖmÎÝì;0äUÓr®¸Èn;VuÉå‡C€=?ÏëH¤ŠúNœoî~lù(Ar:Ÿjì, ›†@UAXÔc ?Ö³4é!‚Í#GRHË•RrO¾1úö­)Õm‰l+»³`ûž?¥!»—«1%‰ñ“š¶$äÒ¢B€$9íÏ_ÀÒîòÈÜÀ€=¿Â‚Iò*¢fmêÅ[Ôqý)~À ûTBs)"<¹B ßËÌŠ"{\ŽÒ:gëŸAN“Wš7òÄAåÊ€søŽ£üñU.e0ÛÉ$ŠB/É'Ž(<ïê+˜»Õ/&hq°#å_Èc'Üæ‚‘Ú¶©v±—kÆq€ÿ‰4©–LÜtØ_,‘AÏÐWeu Jö»ˆä¬Yùxö÷÷®‹N×DƒË¼•n­úyŒ¸dúO~¢X¾oíÃyaÆáÆr=º};N[¸‰qlÿ3þ5 âhs0ªä .Ôߨÿ–xA)è'¿¨?¨ .g éùŠÉÔîw!ËŸ˜ôéÛÿ¯ÛëRÎ¥n.ã¼IXÏAŸð튯"†Ü1²øúã¿~§ãM†(P¨À"1Ñ:ø~\z}jI°#;±žßZHäX8å¹çúÕižFl“Çè?O§sLDÎë%£åIúqYWjq2ƒãàÿQW£eû3ªçã?J£rs4DýÖQŸÏÿ¯@Ñ· ¿iXœî^~¿çÚ²Sí‰à9Ààd~5kJ”ˆß’ccÓ=?ýy¨µ+qéx½3ý~•K…†ÓÉ^:þU,“¢˜äžƒŠ¤/¢’`b ðwp§sOŸ2²•Ž^jÈe[ß}º“Üb®Z±y¢Vçsn9Ç×ÓéUf…™xÁ#¶ÿëÕ›•¥W9ÀSÏåš¡vážêò+HÉÆáŸ­t±ÆUGE¿ã\ΆÞv¢òð¥‡âqýMm¼¦g1)ÂŽY²9ö ™.„ï3HÞ\ pyO¥="XÁÆNz“Þ˜²Gqœ}ÑÖ¦S½sŒŸjd.¥r žãüÿW“È@ùyÏOåŸÒ¯íPKm£•PøRfa°PG´Œ|åséýáþ{TfËçæêÁ—ßý¿zÐ{wQòË шþB«K Ü‘+þ,ßÐP_1²rx¸‡oBþ.¤sÿÏH½;óù1¨^ÒvÆHl÷nšš‰­¦RXÛ£èÿÐ;— µÒAˆû ÕŸ¨£ªƒ)DÁà–P?-õHܲý˜î‘…ÿâ*+›@ex˜GÆ>~œú(·s#9byäçõ­iCG´àúÕLxü=*xqàÒ‚‡O'˜Ä`:ªPç€kemA•Sÿ¶Wà*þŸáJã±B 6Ó±ÇJ°¢`êr´é,€É{u¦§š©‚7/LŽH  nC+Ï#¡¨™vçq Ïn3øÒÅ&"þ,/­V‘‹òçú  ’á„\ŸZ®ÒM!ÀÎ=¬ˆK ¬yÏ|8À.ßץ1<³ßŸóõ¥Uáð«Eâ©9?Ê–#æÂþ½0+„œñæ0›¨û9ÏÌÙ>æ§.Š>öÏåH»äûŠq@ù@vÿ?”Ê\‘ÿ×  ÄŽÊ@‘ˆ#H¨]piêØ#ô3 P@dš1ŽÔ«SÉT7q@ ±Ê €Tðs‘øÐJa¸ü*`>_œc×4þ“LÉß éš@ Ëð¼Z _Z_º1ŠajbéHr;~TÆvÔß8÷€e”ß4ŽE#J¤r3ùTE†x dérÈr ~´ïµÈOùYrMLŠÌp>Ô(¸”ŽÀTˆò9Çà)ÑAƒ—<ûuüê`ñÆ1•"‰±÷˜üÒˆ€9ëþi¹Aß?…3íkéH qÈW ÛÛµ[I §½dý©ÈãSGz«“ùPÐ$=8¨ˆöÍ>I‡—»š¯ç‚;ÒCcŽßOÒš@=è2¡êÃñ£åÆAö¦"ŒŽqÅW`qWIíüé¿FûÐ=Íõüiw1ôÿ¾ªCmp:.câD þT\i?Ýü(Âç• zŠMÀõ$Rí'£çë@‰£ÆÑ±¿úÆ’å70‚*% Œ28=ûUµ $.¾œŠWQÀ©ôã!Ô ¥Žà0øÕ]ùíí[ú K vW;²»¸öä@žÆêÂɵ“‡`1õõ?çÚ¬G(a‘‘ê‚*/a˜„VÃÿu†áž¿…HñäïBC¼àèqHÈt¯‚€w'ÇÿÔj6¸`>mß^ÂØ¢ñ2slÃiê¤ÿœ~•Q‹ë¸a$Œ·ÍœñŽjv¹Ç@Xœ¸çùU›­ËË*ÊIL*…ó’HÀãÛ½ ¢òiG,qÕ±ÿ³5L-,€û±îÏÏáPG­BxHKsŽdÿâsùb¬G©;œ›XÕ{eßú)jilŒ1Ç p?˜ª×)4`!e'¨~5`´3Èß+A)O—aÿÓÇlö42Ï!P„ðW<)ÏnÿA i$²¢³ ,9\c>¸õõö§}³s¼ !w Âí «Ï»åUàdl&”£clž9ê:sPÜ^Z[@ 3H˜ãŽ˜Àí×Û­E]f}šeŒ#øÆâ9ôï\¼¯çH_ÃÓÖº]P´« Áb q¥sJÜ Ž:І‰ííÃ6Aº1‘òã9úrJÒ‘R|¤hÎyê:üç×ÒªØ]Ç0ÈXE»vW¹ô<ã5Å÷Ú#XSyûÄžƒœœSÙé2´ºTQÈ@™s‘ÁÀëÇ@*•¯ˆc”*<ÎÀIA‘µJ¨t´žÊ8K°òñ’¸ÎG_áú÷§[hâUÈí°‚ã¾zóü胮I7wdàe ãÜgúÖmô­±P–Áï‚xéZ—‘ˆî%oïEÏäk$m‘жñÐÿSL I•USÀØ;uÇZ¨È«7Ž9ÈíþqWÈÎÇ4…=ÏÔ“A7)·x?Ýþ•Bø$@Çò­)ð¨ÿ£k¨?ãLhKK $ä¿rL¢¶™dÚë•=_”~gò¬d„6œ1‰ÇãZ:}Ð’ÕT$ŒíbÇ üMC-z“X\aKl'änÿO­Io*ºŽp+~êÞ;»vGÉìÁíÿUr—VòYÜß9gŸ¥4Á£D¸“Ò¨ý %Ä›FoJªÎíԱǮMIo.I ×Þ˜’6ü>æ8î%à*Ž˜ÉÎ+^YL ‘Æšçc9>¾µ‰ ºŸ=“¹XqõÖ¶-^62_JÌ#_• w?‚%¹~.ÒØ¼Ò,ÄŒùÓb¾{©1÷›œÿŸÆ©„k÷7,ñÀ¿r"q‘ë“ò»M*Fdˆº¤¨?‘Éüè‰e»†ß‰®"Œú1þ]j¤ÚÕ²®UžAê±¶?3W"±‚ùcAŸî€¿ËŸÖœæ[ª·\cæ?ÌÐ# ]mŸ!-äú•àjªÌåÕˆíòþ&ºMÎØHsë€?_ð§„n§¯×?ÐP;£šMRNöRp¿ýjœj‘gFéõâõ®€4…r61銘Å2Áp¹G‡B:ëU7û¤c“ïèsþEh\éP¾Y6ƒí…Áö æ+"yd´Cq’§…c×üÿœP4a‡9?J»j0¤j©s…¹“Á9\·ùc?Z Q ¬v ŽÔD¸Ö¢ÜB>•0<Ÿ”T6OºÌÀŠ­ 1÷§]Ë€cžµ^9ƒ‘’}©¡=É\ïã8óÎM*ž>½ÿ:‰Ü’00}3NŒ€?•1 –vݵúž*¬û‰ÌŽOÖ®IÆGOz¦ð•lÒ˜†vð¹ýGõ¢4–v©¿ž+MÄÈiåHl`;çR¸Ò"KH Á“ç~ßóùÔ›Éð?»QÆ1Éo—¹ëSˆI?Ž(»²pO¦jd@Î2zÓKG díúsTÞóq 𾂀4Ü g;ñÇëWapð)ÎxÇ5šìsI µn6ÄÎyÏ"–éð9Ò•Û>ƒúSØ/ëÐþ½1ù(}éL ž‡ÞšÙnƒš`Î{š`iØÜ…m¬x==Mr ãîž§5›Ã7å[m¸·Ãc= KÐ¥ª±Hœô¨ÜàN\«pÇRãDºýh”ãT‘®Ô$÷¨XÙ'Š`<’©×šŸb…Ÿ\SLò3¯Z\Ò®IÅ Æ:s@  çíÒ­Ùæ.ì r ªëœW>àÔÑÊK®$@i1–™Á5Q”g9üêÁ'«²zŸÖ™(oÊ?ýýz7ŽÇüþtÒ§²Ÿóô£iî(šU|qÚN Sd@‡å¤Ãk Ž”mö¨öžµ2°#Š`)÷¤À#Ž”ìÒQ@›ƒòÔ/]ÝHBqÏ­R Ži¤`Ôíóþ•›ÌÒ¸c½&¥=:ûPGåV¡;—mBé›Z#b#š|±m9)Ñ€³*›r„þ´™ÜÙö¿ÌÜCCÓ¥#0<“þJiU=?•4§ù⛇åC)*2Hë×Þ˜.ã’¹r}(.i¼š3FiËsÆ)Jílg¥4TâvhG°¨Õ (É«QÚgÿ!@]º±#КP’¶>˜­á@1´ sycø‡†?úæ•ÂÆw–þ”›qÜ~ufK˜—¶~¼ÿ:¯?º¿©¦ùSÐe‡¦j&˜òsN„³J¸õ  I›l&©ý*Ìç(MRpSÓßþº”686:âžÓôªÛ—iêÜ}ïÖ¨EÕ›Ô{dS‚«çkm?‘ªÇ¨üéÂAýáǽ+§´„mÇ9ÿëјñ†Lê8ÿ ¤/Nâ_»*äS¬£©ö¤8%*î¼*§qdÐÊw ô<¨­<ò§±?ÐÔ±²0 àÀçúf€0|Æÿ8©¼ö‰Iï)qWît½À¼-Ï÷[—,oU”©àƒOp"ÍoxvKd’O<' m.¤ðcZ[µÕÔp)sŽqÅwpiv±Ú,PàO®@ëøÐÈ“í-®² óº>Ÿ§Áç[[Ì®rNׯóúÕYì'±fžÕ·!Éd|>„Œþ¹¦¥ü¬˜\žÇùÒ$ÑÊL¡‡_ïȦJªP¬Ê¬ŸLÄb«ƒ#)t#‚TƒÿÄšp»(‘F}Gùþ´šlrŒê;Að9ª3Ù"@ÀuýØþŸáZM¨[s‚ÄsŸLu'üñU{uùeŽæE'£à~Y?Ïò jåkh¦žá!ŠÜK)Ã(UéßœZÚÿ„{U•®šcË XƒÓÐö©l Ó®Ï³£ãaË0aÇÝ#ŸÖ­ÛˆÞ¼ŸzHÈ ¶Î€»½sÛê[- ‰#´Ä76ˆTùd=J§àšãJ†X|ËwÆà½¹ürjudº…„¹.÷Ï˻ч<ÓÞ¡·½ùä]ÜdçæÇ=øÏÈ?-@ÊieIBž_¯¦AùÔ°IûÉC1ûÙäõÏãì*½Ë–º”Ž~q×ëÿë¤mÃdðÊ?ÏëVC.Ùܬ7«;l$6·ÔÔ^¼—olѱ( ÏÜœ~ý P–sÐÊíàûý©¡LòO¶Üä‚>^£œÒ±HÖû —MCFN yü¸?çÌÝX!w.Í/màŸsëõ#Úº8g¼ ÖeêNcñÿë~5.ëA‘Tœcw§¶á‘øf\–ëÓænù±Ž¼ÿµZ%”—7>`Oݧ9=Ïn{úý½t ¢Øo ´œsÆòþµ¡Å í¯·ù4jCREå«süêBp?ÄÓ ËÛ?çòª— ó9d`Ÿ.î:œôú~TA©0ó[Ú3ýk,_åŠÐž&H¦.åÛË<óè}~¿¥fÀxƒ½j‘EçrªçÑæyýjûåÛè=)ÄŠ6q¼íÅÆ}*µÔ;­¶çxÅ:KÄÙv¹=0¢š·Ìʧ*TçæÿëÄû¸6ž6åPH­lëq®W¨#­Nß½—º§ó§Ò)hZ‚å." #<œ`÷ÿ?Ê™wiâme»69ÿ>تT–òùÖíï&p iÛΗ*6‚0=¿úߥKУ—¹Ó¦¶—c€W³”„b WZñ,ñq¹zÿCëô•`_i3 ŠÛ«Ê£$¨^Wëþsì)§ÜV3!™¡}ÊHÈ*~†·’·<±6!Î^ÿãø×?$nµÔ«È#¶4y6JŒJð½Ç®jÄΪ8SåʶÔjä€þµdÈ 7n‰¬ö»Š(÷È@„d“銊+«›À1äÂ;àdÿAúÒ±•‹òJ%¥àuU8êzþgðªé{±`äôŒgùqúÔREn‹™È<ä?Ë?ÐU9'º»]–6r2çÝv¨üúþ$ý) #Gí3¿…ÿ{æ?’ši$ó;豨ÿ º§qƒqv¨¸è l~Öœ|9­Ô9ú/õjcÐÐÏŽ.$Ǻ¯ÿN */ü|+ö”LVpѭב}qÿˆŸýZ²ñ¥.HÇÎOO¡?Ò€±ª®Å~uÆ»Ÿÿ]PÔíRæÑÀ ·ocëíG•yæx¤ãø þAOëR$Ž\¤ñìcÀ#$~g§ëHI;|äî>µ¡ MÕ­Ä7í´a óþx¦Æ$`£=M3TZ߀¤ p:'SQˆ€\““ïS( ÐcéJÅ\®ccÉÈϯZ”B£¯4¬ß7ÒšÎs€}éˆzmÉ8ÇáOÈü*Jަ—qaÈ ™2>P)bTݹù5éÁÅ7“Ô’?ϵ /*ü½0;sþãL¢Xóõ5¸ ¼çŠÍžá¦=NÑÚ’EßBÊÝ vrÇSL}@’véÈÿ §·+’?Jk uýj‰$Ï)Ë15êrÆî~TcŸAVáÒ/%ÁòJR1K™-Æ•öÖc‚‡§QL9:V”Z •ÐU‘£B8iñ"§É.Æ;NåG~´ÆšVrö®ƒû"ägñÏô¤þσ£F9üè9Ít=ª[|ol÷è•lz¦=À¨ÿ±"?rR¿^hçAÈÌGRÒ±ŒñW-å1IÏÜ=jãhΣä‘[óV{£?21÷s&­jMyo様 œuÇ^æ¤ke-µUGj³3?˜­O±EŽøt61ÈÀ!äö4\v3m·v3ŠzÆI­öÓÀ·(9#¡÷ª #ëíIJãqh®°” άÆóžüÕ¸àR‹Ÿ—#¡ÿ …ãÉ€ÐGEéJáköü*6^ëÖžzAŽþµdËŒàõ¤Ëvéþ}êWÚz úÒ*借¥—?3sÿ×§žüiî@ùTc¹ÅDXž¤šC"uÉÉF7!«¤1ƒÒ­‘þ4ñî*¥yäS•ÇN‡Ò˜#üšoJxqÜPOcÒ€+² Œ)Ž™Á«8üj7<΀+ºÒ/!™·Š˜0aÎ2{R,_1'îŽy¦ ,À)Å=ؙܱÇ|w4€quÎx°5MÎJ G׿ùÍG°“íL.ÙëI¸ö?¥ ¸8ÒPć¡›=8¦“Nzs@ XàrjAÎ;Ô±G…,EXŠ0>b9?¥E³ËÞˆáÜyU€Ätéš‚Œ±À÷¤ˆ±®üèiBŒñŠ­%Á?p~5\±c’yúÓÄ·YàP› Àý2›@hÍb€V-²f_­@5fÕœ¤Ž>”bíÊìçž¼US)o¿ÍOwþ°oZ‡ÊV?>”Þä/Ï"“$zÔÆÝûsL10ê´Ä3'ÔÓƒ°î:n  9©I*TƒŽ*« 2£¿4W²¦bßïœÕÈõÀ W½qþ:Ì`TñҀ䜟jVin.P°Àè9Ö˜÷M'@ îOÄsúVP@sëŠy™ÊI ÿxE€ÑТûTæc¨!\3íÈ5Ø'N— ÃÑãüÆ+•ð×–o&Wo¾˜=yýk©[} yLcp£îþGúbƒ)n9¤h°$N ûÀä~¸ª,!,"‘CFÿup¦;õi2†M§ô¬‹Ë}ñ²0Ô9À·ÓùR% –ÊE­ä8È!\çBAüª<ŠÁdL|çÇëSÚÞ ‡Iăƒê*žE( çÐŽ=ˆ £&öE Ú|½ÙŒž½úU(rÒ Ý´ŽOã¿*»yŸl0H–ÜíuÔ ) ü™ïì”wçÍCõaþ5Eî‰'k|¹íNÀ‹‡RÔíÔgøbÇþËJ//[¹ˆõÜ£úŠË’wqŽÿLSRyUp‘ü©Œ×72l–àì#2u­D÷il'GJÍóæÉ$~8ÇòÅ.Ó³|¬\ŸáÝÆ}ù E¿íÊ¡~8=¿?þµF.îîNØüµO ~¤Ô1†‘Ç©ãq8ñ=+b#‰Dj¬™=IÎ}Î +Œd`Aæ€ã¿ÍòÅNâM,QL‡‚võI>l}'ñ•îW® ŒJ« L«£EÏOQô롤2<Ënà#1ŒôÉàŸlU£p ‚€äúö¨.™Bº¨uõÿdLJX‘ïé@Y†7ÏjGvAæ«7™ØƒÉ¦†UÝŸ¦ÏãB©s¿häq×ß@~Úé] }õnzwöϧZ×¶Æñ?™²àø‡õ÷®Ri|ÇÚOÊ=ùý— šHÙâÁÆP‘Ç¿nž´šÍk˜á»º‘ÈÞª<±¹rxëßßôªojm¦8V(ySžž½Ï"ªA©,h±l$âSŸçVð¼@±nù~c”Àúf˜™f(CN¥Ì!e=gðëŒþ:Û'1Ï·Ù:ãôÀþóPZ°‚º¸àž¤õ¯çßëTdžãS“lHí~âðX´ÇõìÌ÷/,–©&cÏ—©(<ÆåÊ?ZGÕ_¡šöy™Ûò@>i‘~Õ(U#ŒóãòëWSI´U ²F±‘¿À WFL—íÕ¥/ô„ãór•Oe<×NB4AG]Îü…­/ìÛ<ä@»½A Ö•¬bÇ˼}[?Ï42!6¨Ü9˼Oój‰ì°?vå€çibGäwÒ®ýœM¶2 Á råíOm9£Aå;8뵎æIËëJö*)µs)'X™RE)»îçÄ|¿cp$ýGåRÏj“ÄãnFV^Aë‚:þ¿JÈ‰Þ ƒo)à}Æ8?­Hѱ9áGçÿÖý*ÓR{š®òÓhúŸéY¦kkhBÖè~öâ~µYívä¡ãÒ­±–|ýOáMƒÓôÅRl—T?tŽ)äg¨©\+°n„sœãúPqœãUȱèséžô†0y^ <àƒÚš šÃA#†ì/b)²nÆyãüúR=FG¯Zb½†´ œŒg×ùõª—Û̸cÈèy« y¼B޾ôÓ±-\Å–¸ö¨’½:ÿ?­eÏ @åqòžEPR~U'=xýz€ÝHÎvpÿ­LHT6ÞsM Êç±µ,ÛÜrB¶O^úÕiuz©ÇÔÿgï“%¿Õˆ­Ë f={S%–Ç+üÛ²Ç×ÿ­S}†4'p8õÈÿ ª±Ÿ´Fˆ¥‰a€;œÖÀ¶»`߸Ÿx }~zL ù¢‚(þU‰àqúRZÅ«†,Hlu?ãQ\3 „G*ÉSÆ Idø–U9ëï@X±ö8²>Sùš ¬cø1SHHõÉ(ÝĉI©ÖUúT°XͨLZ=¢48ÜÇ?Ö¯ÿbÌæž0>þ™‚%§ß²‰æÚW<äó[ØrŽ“Æ}8j§`?:È£8à0Éó×v¥q¢ $6«±Ð‚z÷úßJ€Ë±ÚèÜ`ôÀú~•¡©&m»}áéî?­bÈœ «×Ž)!’½Â >£û¦¡ŽV¡Ü@ :CG´g Œàc½,|Hª¤œ|Ç?ŸøS=ÃïsäúõéMiBà3¢ÐpOó¦I(„;ƒûÆ;pO@;ÓíôÛ«°¬ T'†v?LŸä) …®#ãg#*1úT¯©È'“üJ8þ•n}î$gHd€ä‘ø•b:ÝéMXEØq7̇j^ß_þ°¨¶+e›…-Ÿ¯çV,È6û¯#=ª+—XÆÔ8ÏþµTfË’¼µ´ˆË ¹8f$ç¸^qùãÖ² ^x©­/žÚ\ÆÏìt²Æú…è·ÞiË{ÿŸÇó­˜`‚Ú‘¢ˆÇÈÁã©=ÿÕ-2¶"By~fcÎ=Ç_ÇÖ¯âÞ2äBýA•>¼gúPdLJµÇü²<ã nþ• ?O/òõv`Æ=¸~¸¦°•‡È²!댂ÔnþT…aÞ_CǦr?ÏÒ“p#¸#‚=*$™Z_*XöKؤRÈngià÷þ”ðê¤9í9àf¯++(’ûǃøÖq9íùþµ5K£–BFzƒÀ?\Rh¸JÚ.S‚Á¶Lƒ›Ž=2:©öéù×3¬¶ÖIÕ6ȇ‘ŽAçñë[̉ÀqÈ8\sXÚäWvùù âLtéëBE¶AxL–R~òçëYÈÁb¸UÐûì ú!_ËüŠÄ6”ù?ÕœôÅSFÜxÇNGùý)$¹'…¥'\€Ü ° °â²ŒÒ®:VSÔã½j·–Þ„|Ô|¬*hŽxþ´†2$Üê ÷½§itàpu5_IÓ¾Ù(Úp©Ëòk¬H£…1grëõ5•IÛDoJõcaŠ DÛäô'¦~§úSÁ²À1Âì~øÒ²Á"$‘ÇÊÇñíüé»—÷އ?©®s©+h8Ü™õr?§­gqÀÏÐqú?1ä†]ˆãø˜óùšÌ¸×àCû \úãÌÕ(·ÐNinÍM²·VUŸø €2ÎqþöéŠçN§¨] Åò¯ªcÄÓ¢Óç¹p×3³/·?©ªä¶ìi}‡¼²ˆó4A½ŽOéÍDÚ­±û†Gÿuÿ:K}6Ö#÷ “Ï9oÐqSï‚3µP}QÃ"–ƒ÷ŠçQâ “ÿÇþÍIöõn¶÷ÿ.öj°gQÑGæ?¡4†P1øÿ…?µî@.a=L‹þôl?Æœ­ýÉc>ƒpþ¸§™AêAþÂѰç}3ý(Æë·¦Æ™´ƒQƒoŸ—½üK¼…?ñcýr(Ô,†gkŒSÍ7pè@>ãƒþÊœŸ7}0ÏéL›É÷Gnj&Wså¨<Õå¶Éûv!UQ1Š\Ãä¹R+5Uù¹?¥9¬ân‹‚=ªÏÞéÍ.ÒzRæe¨#2]<…ïþzõéé¦GŽ )ÇR*ú€Ä“AR\ôý(æböhË}8¨';Æ9ã?çõªRiñ1û»OO”€,×FHöúS„iŒ– m ÑLó¢ÙÍ>0¯Jˆ)=M8·^ó®Ã„°²ª6ü´æé¸w늨¶*ÔLJ…>œR°Ñbwpyo\Ó¦E”r¹aÒªýÖÈü*Ür ÿZ–RwС€’1ù…=âÅX“nî@#éLÀ+ƒÇiˆ®‡c{RŸÝ¶UˆÈ§¼ªãÞšP•Áê:Ð!EÃ…íaèE!’#rl>£ýjŒƒ‚oZfx§a\’HB呃/ <Ò(>[Ùô¦`zç4õmÀŠo§¨W;[…$dÓH#±¥F·¥ —"œëYÌÛ˜šžá÷1Ÿz¯M2qR0Î:RFOÒœùÛ@‡)Î) `äQL~U&7¯¸  C}³OfÆO¨ãl)\ƒÅ?Ë$Ìyô¤ñÔäúøÓCHGÊ€~u0Wµ;Ú˜™º¾*E‹Ni凨¦—aÐcÜÿúèUU…;ÍŽ?¼@>™ª/#ãüÿ^™‚{ÿŸÂ&ñcøÿõȧ è}H>¸ÿëçõ¬Ðœã5nÖñæ8È4bß*îWܧÔˆ¦ÏlÒ.®þ䟕W–ýSˆñé‘ÿê©¡¿G·Þ–¥eÓç‹’»‡¨§Ø©Ë’9Î9­„(yÿ?Oð¦²ER8u<õ©6Á-Nr|œžäÔcoj{¡É4ÂéTHqíI·Òžƒo â*` ‡ÿZ˜(«=@¨Ì*(µ.jì6ñ°É綦ÜÀˆ»”mö•ÂÅ`Ø…€¨æŠ)“pÀ=7ÿã@ŒÇ”•²ÿcøï#ñVy#eBÌUƒ¼÷GùëZ@m³ ·PO þu Äe£#CÙ¹ãñæ˜ÌIÃÁŸ¿Êáÿ?‘ïT®%ó2pAÁµ[»)èŽLmÐg;ÏùöÍó2rrON´Kx×ÈŒ’£åõþb« e’y€|`úuýkMj¾býÐ>øôÿz©Å ÏïvïH¶Æ1÷ò>˜«ªSíëÿרçmÒc9À©â;¡±LOamÑ<öžC"ˆñ´Æ2sëÐŽ+Eäu…¼¹¯ˆ ŸÞ6?B§L¡lÑ¡38 cõ« Lv®ò~ñÀÏ9Ϩ÷¤RfTe¦Ÿy,s»¾*Ka‹•Éù› ð8ý)öçžÇjŽ-äÆê„áòp9ÅP?+œïoÃáKå rXý\Ó|áÙXþ_ãJ%c÷c'ñÿh&ƾ€èÐK9 î{üñþ}êÅï™ Ür“Ê`@cßëX¶FxæY"U 8Ë>£Ÿå] ÏmulÝP²çæ#(}òGO¥KWVdHöI„e"”œ†äuãÞ³.6ÇpÆ8ؤ§xëúõ¡çÀ FdVÛÁ†ÇQ‘׿ éUo [ÛWhÛqˆåX÷ðÿ8 h«rÂhÂJ]G¹Ç?‰ÿªDqíl‹ÜœöÇ^ƒõªÄ´G™T}Ê¢i£É9iâ1øÐQ,‡¬ÇG)’¥8,j´24»e8š2‡Ôt«!•‡ Ÿ¥7Œ`ô5 ‹Ëù¡ãý’p?J¡%Äâ"ªv¸èè¨Q¼ØÙ\aÔá³ëSG(z0àär)?Þyƒ# ïHÀG\m0*6‰NO ¤“÷.dP08aœqëõ§úÀý V1ìl3¹?LÖeÕ² £…ÀvõéZ³´àdŽŸZ§>U”ñÔ LÄ·8ŠT<ò+%"1úsZÎV;Ë…ÿ{ùf²Q7ãæÇÅ3D#È\ñŸÎ™äÈz)Ç·57–Wù‡Ö­¤¨üÏ¡ÿëÐ37ajh FÇ'=êä+ãUü·FÏëþ­J‘a—w+žkR=>9ÊÜ£ : zÖ°ýÐY$}±ƒÀcúÔHÒ ìèlíb´¶X‘@=óÜýM…@Üã'$ŠÉ›\µˆ™ÿ²¿ÔÖEÖµyw•ˆ“×95‚§'¹ÔêF(ߺÔí,Si`1Ð/jç®üK4„‹pz‘Y†ßy,ï¼÷?ýzCIÛõÿëVÑ‚F«'°’ÞÍq"™°Ï955«ï¸\`±è>ñëP“eÏáÿë§Ç9R6&G¡ä~]*š2¿s©‚Ø„W¸uNÀ ýr?*±%Ì _÷¤?â+•mFñÓ`—ËU#•T',X³zžk?g}ͽª[#©›Z·±Øtüº~•JM}1ò!#·8þ•—¯š2rùÕ¸¬|É‚ Ú;ŸAO–(\òõÕnîî z’qùð*Xÿ´¦81ŸEÍ\0¥º"Ç\áWԟƵ …m£ÁÃIüMý*­±¤`Þ죗rpn.ÙÙAÏçSI…¢' ïJųô*¦§¯Çk˜àÃËОÃòëô®yïUºêòÈ£ú J-ë&œc¢ÔßmnÜ·—.S¦x¥]†Ñ¦Ä‡åB:Áý*=7GŽÌ %ùåýçW¤¸Eãv>•-ô‰¤SÞB­´H9úӷƼ\zTk„<Ÿ×š7‚3ž*lú•§BÉ„ê C$«»5YÈëœSƒ©áÔq‘Ua\IÛ” z\±çبCªý×ãÓš_=@É#ßýj,4ÉI8ÆMT{—Ý”ÀâœóoRª§$w¦%¼­ŒŒSMYnDÛz"Xn€uW ?þº»‘Ø(üjšÛªýãÏ|R›¨S†•­ÏëS%}‹…ÒÔà=94­‚8R)ÀãùÑ!'ëŠì<ṫrj«V¡8Ò†’õé1¶„ðjœöö©VTqבD²€PJˆ}Úx;AQÊúf¢1ÅŠò)#g©Å2VÀÐÛ‡Ò »Ú“jœQŠxLúS FzŽGµ Áǯ1G_á>ßç4‡ê;ÐAŠð1Jpqå“÷yíÍ 9)0LMõ eVŒŽAȦ„ ò?:”ñÓô£?)'œ÷¦"!ò¶yÅ=¹SAéŠîœúPàæ¦Î>aÎ* š, ö   KõãF÷=¸¤Ì¤`>ƒy.ÝS@ 3Ô~tðÀÿŸþ½1a©ÍHQÅ(ϧó¨Ú6=ùý)Lª;çN‹Ÿºqêh«N¡¿*‰AùÖ¸‹#Œ})­h­Ó•ÀÈ,Ç‚ÇéRùòyaÀqVÞÑÀÉMÃÛš‡ìÁB>”[q ¥}jãiSCøŠgØ%\’§çÞ,’E›+èkVIL– „;œt¬¥ƒÑsž+Lí ˆ9 ïIï «Ö7ü3ÊsÑò5´¬:œ}r)ÛÇ©#Û4®¦Fê>ãªÒ)ìqŠßàòr??ðªóÚÇ.HÚðþ˜§på2I uâ’}jÄ4\À=QU[(HaƒL’E¡àãÖ’G2}æe¾#nùükE­ãV,ªTž¥sÏåýEDw&sÈõü?ϰ¦$Ì´ÑÖ €û³Ãr*RFcëÏ?˜«%ÄxãwcÆj¢;B|¹ó2 =GãA[ŠìfP7rAÇB*\‹sæƒÔãŒ~&¬0PÅ€çÆ*†§)UÚ; ;Ðɾ›|dñ–Àª¼zrió¶H\ð)å\í Ï¿/Ù£`6.mŒßÑgYY™MÃ,¸ñ÷sŸÖ¶Y$Œ>Æló÷ÿìµi¨Gh“$ˆûÚLá@ÿdw5½ÀF!²Î8?­Ki“¯ùíQMr.æó€·Ò¬ÚÆÁxÇ|ÿŸj¢^¤RL\8àgÒ¡—íSmV¨¸ü´ìd§?‡ÿZ {¨W#¿#…ÿýhl®PAnrFãÆzóRXªªnÁ*IcÚ ‘ÚâUuû£¯ãßÿÕZ cŽ5Q¸ÀùOøS¡&õ“´{ñFTŽ~œÔo¶Eí§ré°Aì„~¼ÔW9òÕ”ýÓœzñSÂË Î2{Œt¤*²™9aŸÇüGä äF»½væ´Lgø1ŽàÓ[=<£õÈÿå Œ+¸Ô*nÃ!cw^#çWrGü°'òÿ7ÎxXÀÿy¿Â€¸Ëk±ùg(Êß!<ìü¿pA«+¨ùHB´e{Î1ùëT“±ÉuëÁ4†À É—ØcúÒ±\Ä·ŒŽ¿(_\Oó¢†ÞK¤ÞÇäÃÓ8öÿ&›ªO) ¿jœÇN3ZJ‘‰ŠUÁä`ztÈÿ>´=W?ºN€¼`Ž?#ð¬Û©Öá eçÆ¦¼²Ñ†ãqû§©¨P"d±]Ç©Çõ¡ ¸í‚8þAœ +GÓìé»åKc©öõþ_ZÎÓmRæï2íÅó1#‚{ë]J\£…H@#Oo õ†K$UHîaóõl߉?Êšf›9kwü7ò?ã@š\gËV‚Uú~`:Qq?1)îãþ'Ö‚‚æ2Ø!‘½qþ(n8?­,˜#*}¸5VAösº•(hT .qþÉ?ÌVCê(¸ÚIÏ(ÇøTmqæ î_æ\ÒäîRš[Fîèp}±ý*¥Þ°°Ç˜×qÎ:ñ®zy|HrqÓšž)¢¹Ú“¿J¥µ'Ú·¢]Zæc€Ì¦qü±S,pâ3*‘ ˆÝЏ°éñ!+$wnŸ¨\'dÏÈ9 >”Zä¶Ö¬©äîÏ=©¯ÃŸCJ­ÔïMi7›v­L•aÎ=E(c÷sOI íœS·t9õÉ $ùs¥•/•JFk9Ÿkäàúš·opXm'vöþt1’t$2ñïK”“¡Áô©HI# î1U]ç‘H°dê?iœ1ŒûR¤Ä 7J~A”8>”À®ðœtÈö¨Àô«`²rÃ#ô§˜ÖUù8oN”\,fà¥8`Ž*v\¬0j…>†˜†${†sŠTp>µáp:š1Á<Ð¥Ô´àK ôõ]½1ϯùþt¹+ÕÉúâ€%ç×ñ¨Ú'<îÏÖJsÁ>Õj$wê1H û¤Ô±Àìzõ­…Wïb¦R€`ùñ¤ÙJ=ʱÛ2õqÿ|ÔÊ„0þµJÄíÀ#?Qþ5Rf<ç‘ë×úÒ¸4‘`¼jpÌ£êj¸·îÊà9ª.qØÔþÔì+š£Qƒ8,#þ52ÜE'!³ïÏøç\ùniRFFÊœ~4Xw:")˜ÕNsþy§ ͸HGÔõë2Þö@ ÀÀöÍ\ŽüBÊ$ dªÍKLjŃm°de€î2­V’æl0$öÆ´|Ìã8#ØÌU{»U¸ˆ°ÆáÐŽ´“îS]Œ÷¾ˆr±Ÿåü0êÕPàŸï¬Ñ:’ƒîqQ²í«²"äÒj,à-MRy ž!ÐSXüÇÓ4ÚdÜ(¢L4”¹£4¢»Ÿ YBºZM·÷’K!\DhÎáT“^—c‘eK‚@ÈCüø¤gQèM£Ž@p3LÙ¿8 ‘Æq‚?‘6H8'ú:†xO2ÇÃϸôíAš ޹÷¨¥*FOPzúPÌC¤Œ?¼?Ÿfšq³‚O¾Iþ´‡b «'äç¡ÿ>¿Î©ÌèêT’Ž:vÇ¿ÿ_ùU·ùáÓ<ŸÿUS¸‹9pØ#îœÿŸóÖ˜Ñs*·+ž§±¬mF}Ò•ÈÀ9«WW) 7IÆ0?*È»3Õ³Å2Ò*±ÜÄÔ‘©# Œúã¥D3šXŽ6Á‡øÒe5ƒ¡±ƒ6Ó.D\|â°u(ü«ù)@yÚF1šèôi‘ô蔯̌Tñžùþµ‘âQ|/?PýU ê6P€!@ç úÖ¬©P;ûU$Œ'åP@÷ÇáVLn@-…×?㊳6:{—U ¹çÿ­P¥¹q½²^¿çùTñÄŠä°èOjeÄüL‘üXþT Ðlg.ìÅHè‚Ôö{a‚U<~ëÿ­S Ø‘D€äõã«-n­"Æ{{Sžd·ÆJ¯ýûÿëQ°°mõû¿ýjÓ6èÝQqôªmo'©Éô w+ºÂÊFÑÏÛÿØÓDP0û¼÷!AÊ®ý†ÿ,—ò¨¥²@»‘#œ}ºP+}‘îÿ|ãúÓ¢‰ãŒ ‘Á ~)…""E_Ý7QŠ˜Â¤­€yä/õ w]mùQXÊÛÏÚ'+¼ô*RelFˆìKÈG@AàT˜a‚GçšW—wà ?Ò™fæåbòŒ®ÇîŒä¨õÆHÿëÕÝÑ´©®Qe‘V8IݸŒ³ösùg§ èVÂSÀz±þœVhþÖh+E €‚4{e˜ŸÓ𠮨?hû±è¿Î¤“PÚ²:c -ŸçŸÓ #*°‘ASÔ '?Q×ùÕQwwn?ÒmÁAüH1úGþ<*Ü7PÜ'É(ÈûÀŽGàOÿZýš0Àûãå ƒøcšvæQ¶@O—üô'éNhѲÈX;rY_¯?ŽjX˜m>f7v*¼×#õ  ý†ÞO”ƒ ô#øOøõ©gA*åN$«£ÿ­íVÇ2²$`œÕ` cË$’:ÜUÌç¹`Œ²Þ9=>•ò b n^ô t?Ò¦ÕbVaŒ­RËÛÉϟ™] [°2{üë)Ävò¿ñg×J»pÑUIÝ ÀÁç¥cÊÞ^!€rݹ ¨ ˆ ¹§ ªD ²D'våSÛšam£¦{b¡f9  €©ÒŸl›¤è8çRäŽÞ•b9ŒM”©¤3Gr ÚN{àT\ÓåsQ r ÎsÍFÇtœ“ÇjV „o!}ÊrMX 'RÃè*$ÿ[žzw©ei$œ¡ª„üÃÒ¬Èß)¨‰¿Ž´1Û¶.*jýÂÍŸZ\nùŽGl lçl$§Jb*3n{Ó$›d.XÕvlškiw3†cŽ ô¦j:«\1H¸×ֲà æŽi[[•Ìí`©ílç»}±FO©ì?ÑÒ´W»Ä²‚°þ­]*Åme0ªƒüÿœÔJ§D\)¹jö2¬tãPÓåÛÐdñ5¤%Š/’%\Ž8Ç×·ó¨^g¸$UGnçëéô¥NB 'Ø…G›5VZD‘€ù€cõïL J®¨ÿþ½N-Šä¶Ó$þuÜ‹mÉ´°Qž?¯4&&žìaRy9Çùÿ=j6•?ÿWõ¬‹JYyC³×¹üê'º…Ñ‘ÍiÊdäj¼Íƒ°Çn*&te$œ7FõÍV·“÷8$œqUÚà¤ÏŽçš,>bA(ó6òsÍ17+•P÷¤?0Îyê=êMá€luóþzS _(7' úñ¥$m…ãÐñªÍ1$íàTÐÊØùúgZvÅ¥ñP\‘Ù½>µ^ö . CsÀ§Én¬»ÐmõÏJ|#†R ìOòç#ßA±éWS˜ìÔŸéW`ÐâeÜ}Ž)wm8ýÝàŒÿ´»R?­3Ê¼ÆøîLƒÕ%Ïõþ´õ3±£ý“mŒý3Q¾k`Üÿ‰ª"îþ RGûkýúõfg€%Œu9ÿ?`Dú3¯* ý Uk9#?pƒî+ †òøIŸîž¿‘©Š†*¤zQv0É‘ó¨8úSy±é[òÙ+d«íTf´xòY\ w4:¸ æ«€VAì}+@Áò>•^{fO™NET™q!¦¦ëíV.1¦(M‡={u¦˜\õ##×<þucnõëž:¿­gFäqßëVâ•“·šb=»d•\wÅ*!‰2W“VÑ–EÏqÛ½2UßÎ8ôëŠWˆÜÇ?ýz@ÐçýÓÏåÿê¡£›ó5`§‘ÍP‰d]ëó©V j¤pFjì áœôâ–â ²î¥qØËtÁ㥶€*ÁÁsøÔàí#'ÔS$ 9î5P»c$“îi¬ûOÿ[ÿ¯Q æ˜ãò¡êAojy»ÀéõÿõVf÷õ4Üž¦_m#Ÿñ§ PŽ ŸÄÿeQE€Ø²÷FüÏøÓŽ§ ħêõÍc pëÖ‹¤Ú„g¤(> UynŒŸÁú(ÿ šÂh‘öÈ=ÿ×Z’X[N2Àd÷Rý)^ÃHçI4ƒ$õýky4»d8,Y½Ïô¤’ÆuÚ6äã¦hæ+)ÛDYAäç=ª9I21'½jKHÝð0«þ*Í\°~IÀõ¤ÆÕ‹ö3‰#òØòÚ­«ï\V34Rs[‘°š5e=©4 •¯ Ü ªyïŠÍ#¦G9ükpcv5—s”äcƒÒša$fIÂ{\TUzHÕ—9?J¦ËƒŠ¢ö£µ-'SL¥ØsJ¼œP¿‡m¼ýU€V!¼ç§µwÈX ÊŸåüñ\¿†¢kx]÷ yHÀÁb??­uˆPoù›××ùR1ž¬PxäqFÞÃò4¹>”½:Ò$ mÌs°ÚÆ7ç 5Vkv†LFÀÞú`þµ®Nƨݲ’ª0Hêh)¦fæ9Pƒéœ†úg¯æj=ÙR„äŽ3žÝ¿ÏµY•VT*Ã?çüöª, 3 ò§ßçŠa¹‹ªHépÉœ)çV|æ°l`c¡ª•{µr95Goµ3E±éÿשv)ÑÆ¹©| ò^¹ç4™F®‹n¯É,k•Áù3žçéPëÑ$~_—³ßhù~•McŸP1È~UŒ¡'ëVu‹8›l3)œ¾1ÇÖ¢ú¡ Š›ry#®?Ï¥\‰Àgù}ze½¶Ð²¿Þ `ÀéV]Äxe»(«FlŽAÆÕÆâ=¸Ùv•PÇ,r}qßœZ°0™rÝzš‚9ÕæbŒ™Ú8Àñ÷Í1[G›—8AƯ¼>†£JÆ2rO$ÔÀ0}ô <)>‚ª*æUà ÕÉ8¾•9>Ô ]´…21N‚x8É ’’Ǻ&ˆœí$}=)q˜˜r: ²²nÚUOŽÿçúUk„h¤¨úñH¢F §|§¸ÿë ]¬W†?¯øÓ°%OPi›v1RÏûʘKŒçÛÿ*Kd3Œã΃åOéEŒ#TÔÜÈR!̇vwÚÒ¹sKÓ®oÛí‹ ˜Q±qÝýìg$~ûë¡jº€G\pA÷ÇõÔ‹ˆ ¶ ¶`Àc€¹ýk†ñúêÚˆDÈŠ1Ã1'ðçŸN*Û/c,H÷2™äl°ë ä~µ› „³•ºà_îÕ«æ'N˜'ÿ¯Y#Ìœ hñ†9sÇOJhvЭu,“¶ô UÇLU*ëa³H­ŒXê0Oùæ¹I«{eEÜrò¹'š—ÌÀÎjøŠzÆò?ƒJOOÖ™¸žMMöWÇOóùÓ^ŠOo­1de«K—ÁôªÁI5q,Üs¹#@v!ÂÁÏËŽ¾Ý:a8å_óþxª’]áWZ¥‘Þ4ì÷—ÔŒ{Õy¤  v¨p3´iŒìxè=¨#îý>ŸãP°QÐæ‚M&)€˜­ýDûA÷Cîï~TšŒnßϘ~åOýô}+¦yƒ8'¥cR}½*w÷™÷ goÜ^2=}C÷“°–c€9UôþTmk«¬¨bÝkN;Uç˜äúrÿÏHšë"¼6¯9XýH'òp´6‘à)Ç©ÿëÔ7…FÔ?\ÿŸþµeÍu‚ìÀý)¨·¸ÛQ.ÏxH'îz~µÏ_ÞyÀªË»'üý)'¹{ŒäŸÝ×Ö Rs‚ƒ+XÆÈçœîWéRˆÝâA8Î}¹­8ôÈ%·>ÎFYL€.sCÇò¦ä%cÆLÆ  ±­û»aöbˆ„áH\ŸÒ°ãBw¡;„•´aöã¶{Ÿ…ã~‡üþµ•’zäÕÑhǸëÞ$gJ6È}êPv[‚88þu%ÄD¨?Ä=*9Æ8§qX"œgçr•±O”)ªüÝŽG_ǯçUOLʧ]Æ óžØÀ¤2©P²ÊJŸPØýk«M*Ò!þ¥N:–ù¿ bN"ˆ1ôP? ¢ä˜p^ܦ0Uþ¸Ïæ0jÏÚR^%´ÉÿʦÀQº³a—ˆ}EQo™H8ϦÄWA´0È Zν³ÆeAþðÓ`Ì¥OðÔb ¹òN9*É@]ŽEA3d`tÍP(9éþ}jXF÷@úÓP|¹§#1éè\!aPª£yîiÊá½3éUÃ%Ò‘ŽÑ¸~tXw,° Ïz¯"mëÏû^ŸáNY7œúcÈëÁ¿Î(@xú{ÕË{ÃcŸ~õT°cŒm>¿úÔÎQ³È>´X[Ü'—&GÝnƒÒªÌþ•yYgˆ«`øÅV+Œ«G h¥ÇN‚¢>Õ4‹µ±Ú¢5BŠ1“ÅH‘—lUœ,kÇ€*õ£nzsS…2µN±õÅVKryn*ebRa½ùü(ÚÝÅ!ÓwçÿÖ«Ö³I’~èuïôÅSG_åÿÖ­{;Ûq_20}7Ž )\qZŒû<Ñ8Ì\ÉÚ1úU£ù‰È“þ:²²®3Á÷”2ínqœô¬Û}MR3/—bGb:~uå«Vþ–4UŽy5E"h˜‡V\ú‚*¢D·3Š¿§Üío,ã§J¯2&üŽsMSµ÷/Pj‰ØÝq›Ö©ÞŒÆúÕ¨K>ß•V½8·oPy©E=ŒðÕnAéš«8¸Ÿ­HÇúÔnÛÏ=jÌÈq“€) ©Xq´~8¨Ü`àSµ< ¹»þ[²tIâ/ÓÌ\ý2(¸Òá6Ð,(¥ÝF qDZ'ôÖˆiSªƒøÿˆΰnõø­ˆ13+mÀp}‡$š`×õ\nM%Êöù$ÿ “.VÎL1†]¾øÀÿ x#·ç\âøŽér&ÒgQܨoþ&§ƒÄ|­ƒ+@窸ÛüÆ?:,.Sfb2N? ¢ûNGQN’a(‚½r;úœÔm׊¡ ©…&³î\y,XŽ·záð¸îkV¼òâ1)Ã?…1ÄÎiŒÌÒ3e7ŒÕ=ÜÒïqüF™¥‹aÂûš–ÕKH\“Çò*€rO5vÚ@Šzr~Ÿáüé1—c•´ëÈo¶7Üíàð~¿—jè/ä2ØÌ€¾g ñë×å\uÕÀ˜¨tsÓÿZÑŠe_ÝD@ÆFH?ú ŸÖ¡Äw.mÎÕÉSŽõ,x|Ç#Ïùý=út¬øöÁràñ·=rqWmSí,|ÌŒ…=Ç¿ùïVˆcãV»r3…¯ùäÿž•n5cEO¦y4æù!`¼q´ ãØRGq²íURêiOM$‡^¸ åN¤*`ŒÐ$ìǾ Xí3a ÏZiLî?4¥‰$±É>”•ÆÚh ä`ô¨™ºœÓü‘Og p¤ý?úæ£óOxÜ~ÿ#L0©ÎG_¥3qˆl•w!=úÔ­:(ËnÝHþ”Òa ç°ÁÏèh!.‘1) û¯‘üé­y>óz! S}•—“òþ´ÓŒrÌX(’O`‘Z25Š{‡Cþ±¸À8Ú=N=«r=6ÞÖÜÈûÀä²çÍ¡Ï\å:£»,`wWÅKi™ç9”ôöt(Sž3ëT‘.Z•âˆEÕ“’I9>ý*͵‹^HIp"ŒüÀŸ¼}?Î*)Ý`‰°8àŸZm¡Ô®cXíò©uçýí¹ü¿J‘Õ@þT* eFí}1þ&¬yápåÏ÷²¿Ì äÿ±–Cþ‘©E¼õß"|ŸÊ®G¡Þ[){KèØþùÈüÅN…òãcdN‡ëÒ ŠBÊU‡Ì§î=à 픂1óT)Fàà~õ²–Cëýh¸™ääz‡9¨æ¸"ÝŠ€Olÿ…Tiw\•L…uÎp2;´Ë뀰¬jgcŒŒ÷'šc°—W9´eLüͰzŒÿúê{–ÀÇ$g½fÇ ¼¡]óåŽXަ´#XcëåŸæ3LLÐ=+’½M·rÙtê à£¦súEsÚ’âùÉÇ>”7*©éœ`{Õ¸ž5^£š†U—$ž¾¸j(c#‘‘õ¤j5ç'0I=8¨ü‰$?¼aùÔÒ*Æà¨ñRgœŽ8  ÿeUç5qr@ÆOáæsÈ`ÈPdÃ’ÞCÔb¥X£N§8÷ÿ ‡Î|düәة¾†–£Ð›z·Ýút¨$PìÈIÆ>ßæSÁëéJèþfà00) Ÿ‹d…W¥W#EkÏ ±ÜœQTðÅ$ûÕ&"(eòÏBWëWDPL Œsè•RhÇQÍ,nc9¥O-’ã(}*]+IkÛ­ÄkËŸéRCOä¹ÀW@4Ë\ûõ>½ë9JÚ#Jp»»ØuÍÊ[¢ZÃÀ\ž0?“É2Ä!ŽBT»}8?¥U±µûC™ˆ²HÀÉôãšÓšx­cÉeUsþ¬^öGLuW{Ž8í¢z`“ÔÿŸN•“y©;!a·¹ãÛùûwª7š£Ý°Ž&+àŒuÿëT ìN¹ uÀq…µdJ§Hy¤i¶#~íNXƒÖ£yœcÚ¢žäò¶MSgg?ýjÕ#²ãÈ«ÔΠyž9ü*%ÔäãüÓZ2G"¨“wL^ ”ãž1V&¼¸P É9Æ+œŠg…·#5rg¸lmÝÏ$ö¨qêj§¥‹×w,òß(Ã"¡‚5˜ÀýqVÚ< ã? ©ò‘Œ aJãµ÷!û{qÎ}HFñœvý YóîÇéšxVuÛ(_¨<þT‚È¢Q˜ŒçÚžmI° º¨¯þ¹ª’ÎY±’=)¦&¬Vò¢_˜}j»…ó2²}xýsJò3£ç< ª]”á²?J´ŒÛ;˜´öpáØîœ~?áWJCo,Uu-ÿרÚèNïéüëšÔõG»“b7î”ôõ÷¬Òl WÔc¹¸òmÔÇ-޵*ª¨,ǹ'ó®b ¥ŽOÝ1RÜWGck…ÌÛäê í¦Õ€°‘¸oÇõ¦½³H§«žä“þm'åçüûR9P~vÇ·êjn2_LÁ$ÜùñU$q­¨ùç,‡ûÝ?SR\]3“ źd“ÇëÅÙÛ Ý<©+ž¿8Çóª¬ $F ‡“‚9´¹W\‚=ÿÎjQå.v[§·Z…€¸,Q¡8?‡jC1ïìŒdºc=@ç•2 d~5×°pÃëÿ׉f#%ãá{Ú©1$…ÊiqÛ&‡P¼â˜¤xçÞ¬ À¨"œŒq“ëPÂÜcÒ¤ “õ¤ciÜŸ¥L¥eLŸ­W Jò9§€Àî^Ô †XŒMÓŽÔ#ÿ ä_Jº@–<J¢Ñ²±\ÈP"dÊ0`~_éRÜFìqUãb­°çó©ÄŠ#Øä…=ZŠrÀÕp¹8m¸8Ô |Ç"š$p"5ëHˆÓ6IàTÎaWUB®Oóï@ÈÕBŒŸ/Ü}(‘Õ@ïÏ®­Vwy9Ǧh <è½9>ÕYîº}*Ä1ÙnAãš‘ìQŽwü¿Æ‹…ŒÒIä’i3Š·5™IÉ>õ[ÐÖĄ̊ŠÌ»˜+¡¸—Ë0íb2ã8ô¬]5A»ŽœÕÛé û¢¥­KZ"ýòxÏCƒžÿZ•JL ãƒŠH¤RÇ‘Q]66…Èx©E>âËg ©ù@÷õ«*æÕ­ÛŽTô­ˆ&ó÷‡zKˆ¼ØHä 4ì&®Œû¶„ç<ŒÔº†>ÎݳçT*ÀާÞO¾Ý=OJvÔ›èP-ØÐ04Ö4¤ãвœsÜÔNsJI&šh*H÷3*¯RpFHµ<.ðKÀùÛÎ1@¤ÐiñˆÐˆä#æfRò·üghö9©Ž§‘’.úb¦Ú§c¤Ü]ħçsp¿‚Ž¿SWÿáV_šqžÛaAKC;Š5tUÁ$¼¥ô,ÔSÿ´ ¸]²Qé×ù ¨_Bh†Væqôb?5NM=ÿŠä¸'åÞ zò3@hi[}‘]¾Íˆ¤c“Lþ~b¬‰H`%OOcøÿ“XðÆWÓ?urGýòÙý*k}@àÄä4g®ÿ˜ }yŽyô V¹¡;†bAsíÿ×®N÷Ë’€Ì~c‚Nknêsi …Ë0Û”lçð&¹¶fv,Ä’NI¦TPÎvE2–—ƒ×ó ±iiúeÅëeªgÏOþ½T†λˆ+ß“];ÞFÖ‹º4 «†Hôã'üûÒlFGØ"H›Ë€0­ÓŸ^¿Z»e0{O%¾üm· {~-ý*I£>Rʨ2Ÿtãüÿ#Tä¬%ûLx,ëµ·góÀÇù˜&K¨BÈRàvõÁÎ?,ÿ:T}Á$_½œã©#ùÓšQu 9`Ã#°ü2¥W´}²48^œ¤a±‚‡?+œ}EY“LˆÀ|¦tcŒÌqÏÖ¨ÛÍ_$¿t‚OÛÚ´âÔ,£‰3s`0r¿à)JãŠV+•Ǧ3cÒ¢¸Ô!,|’Î=HÀýic}è‚ õ«FMXvIö¤¥¢‚D¢ŠˆÈKmQŸS@Çœ(-Øz ¬Ò$‡ýAcêè?­O°0ùŽG¸®ˆÁ7ÈÇUF•!¾D|/© àç°àŠê´­ Öâ|¼Ï ËÓ9÷ü±X|QCyÝ6CáNåàg°q޹­+«èôØKÃ>ÁŒˆÁܧóÎ>£œµ4JÄ> 0ZÛ°f,èÿ(ÏÊyÇ~3ø åacrâåÇʧ…<}é×2KªÎÒÄ@äd¿O\Tè± "5ÜÙè?*¤„ßBÂÜHA$‘ÇŸÏß>FR@äw?˜þtß3O˜öhh%–@ ç‰ª"È«4¿»=˜ãþ°þµ?Øu à¦âfã¡làžƒÀâ›$P(_¨ ’î:û{Ví¦Ÿ5ÀY.Pà ÀsõcÓè9úT²®d®Â ÏôÂÓuL{f/ ì…ˆ&1ø«ì!„qF¢1ÐnfýqO>\¿ë"ŽŒFqøŽŸ¥vs6ú½Ê¡Šè‰•†>eROáÑ¿0~µ$¦%ˆOj¸@2êì'ØpTàOƯÞèðJ…±œõ*#úçæŽ{R@$‰¸Ãt?ŸCíÒ€FÜ~í68cŽc-–ü=<ASr úúÖ”¾e±‹­• GÊ;c?çú܆œ£’{Œqõÿ>ô ¢v“làñ¨ Íà `=9¥aÜt×Ñô˜þ•,†G,F*É1¿ð(Ï¥BÑGÛ#ñªD‘£}ªÌq¬ÄûUb˜ÎkWOµ žc»¶;RcJ榟®Gʼ#ÿ­R°k» ¡›hãè;žµ› µqÓò«ÛV&‘±\vü«'ÜèVØ·=ÌvwàŸð®fêæ{¹KÈÇgð®p\»¸3d`‹ÛŸñþ•DÍ”dúÕB6"¤ï§A‹#F6¬j³Ôr´’®e„qùŒÓÚäöæšeÝÔ)üªÌЬŽ:Œý9¦çÿ¬*ÙPÊI#Ž‚«:ƒ–ïLÚrr XXË('==é-!2É´Aü…mEpð£$ûÒnÃJæt:w;¤ééœVŠFp0£ÚŸ“œtöêiØÇ-ǹ5š%a»~¿^iU»‘êpFn¡ UpÍ×üšîŸ¶ ¢Ó0Q’F=úUiõ¡^¤úÅg]]l’XÕÌŽYÉúÓQ%ÈߎåfMÃŒuµV—–ºÖlS4OÁ>â¬I†S <7¥; ÊèhÃDH<ûU\öÕf#…8뚬FŒ~•H†oj7Åÿu?•fO®MJì1Ó?ŽiÖñy³"rCrilý.ËsyÎ83[a°xQˆã 8T7wLXSó7£v=‰ä¾ýà‰¼} .îæ²~hYŠîÆ{{ÿ…]´‘®XœöïÓüi´!e²IÎçóÐ/õÏè*htÕ $mÇräÈV’D±Œ…9õ&†'jn35 ó#%!!º†ä‘øí?ΜŒ³ÆÈè*iØ8 í‚j±™#“;×ЂØ?ÌQ` < R%óràãð#ùR;$À«†FÇGU×l~¸=}ªV 㡽ˆÍ;ˆäî­LNGUö¬ð6IŠë®íŒòž>•Í_Z5¼Ã=CV˜ŠèvIìjF8¼R2Œç½5ØÇjcó‘ØÕˆß¿•Q ƒŸJ™Ûn0}èú§Øõ¦Ì¹‡QLŽ@ê~ô»±…=;fÈdÚß…8a“ É¥ Wò¨ã` ÐõÍ*?7#±¨å޾ՠèpy_åT^æm$`wõ¦˜w ƒåU­ ˜œed>»ÚÏá¶ŽÔÌÐ5VÖ99Iãb{gšwØH?Èÿú«'}MܱµÛ¿*,;£GÉaÕOÔþµXaœ“þ~µ¦©áfU ë´ZјtÐý*Ñi_c< ïŽ{qPKeŒ”ûb· ´,8} ¨$±p ³ìM HN&=¥¹†ìdäm8¨§rn$<}ãW\µ¼ùuéøUåY$. ŒõÉïMм÷FØ#àx?ýjs:ÎÞbƒU.¹µŒý?•6Èü®3ÏsNÃo¡z9 N•p 0#i磵d·} ‚îHû‘Çj«LÑ+KKÓ¤½œ,´C®_o_ÐÕ­BköO”··¾žÞõÚCpB"‰B ÀàR¹.V*[´6Ñ,M•” ™UÛÍ: ´È@’u ùsþy«ûéôãùUfò¢'k®=ýoåH‹˜+¾ ¦‚n§¿cøôÿõT $Ã÷Ai>Ÿ‡Ò¶5Væ ѱ¡ÈìßLuÿ#µg2™íwŒS¯¯Gù”˜–8{P„±t;I~ŸÓ¯èJó¬}ò Ç=‡éU庖Qå@ŒYÎÐÀÿ€þ´ÃRÅ’Û £q2e"8@Ä|ÍêKÕ¿«ÐDxГúìÎ¥Ó|5 ´)ö‡Ý&9qÇü±ZCL²EÀ·ŒVýMMЛ)Guo7Þ?ôÑ©ãõ©y±ó©$°´ÁÚ‚2xÊœgúÖ¡Ú`y+úÿ‡ò DW–"½+.ä,£c Î0AçóþU§$ªIÃtãJ匎çvqŠrÓG%…ê¸-³:Œ}x Ìi_—¦+?Ql$V;‰=WZdŒqÅgjgýoS»ŠŽç8y 7#ëR¸\tö53RÂãv{Š›sñŸjG|eGãÅ53qß4†L Ðóòn*©G2Š@}~”9lueþtÏ7°vü8þµ=À\Q½@ì b%Ç Ãñ¤21yÔšÌã·â) –ïùÐòÞ¿˜Í›¦“ w©a]ç ` @MmsÊ’ZÐhéL… ã¯41þ•%"hÎææ÷ÅIw±(òI彪+uËn#§'ŠŠYy=Éç,ò¥mK½¢Tef;˜Ô%ל Às“M(Þϧ5fE1³ÆGÓåIóžNqîjG(¼uö÷$}(‚ºy¤Ž5%¶ ü«*çV… òþsÓÓõªM²\wf{ZmáÍØö?ʈ³ ùrr¤}j)¯fÉãÓ±Íæ.Ç9=‰ÿëÕ—ã²óƒÓ¥ÊÁÏÓv?¥CirÑ?”çåìOjÐf$wÇÖ‘jÌÍe$3t«ú4BK£“Àýj®—·>Õ£áåùåcŽƒúÐÞ†hÛo•:ðS·A<í3r«ò¨"§»“d$§Š–¼¨U}5lP!vQÀî;NL«JÝXñþÏJ«©JREÆ]€ü?Î+J%D¨½Åâ²õÐŒb^£¯\f´$G9èk¿½,îÙ'Öˆ d—WÅWòOEþU˜Ó¹bKc>†¡wbIc–?¥5U¤`¨ '°­,I×éS™¬7$q’Ïò«Èÿ»<ô9¬í-UÁ-ß'Zchž¤úÔ1OóÇ×§Ìò®jÇ&Ó&Þÿç5¬¿gtqþÐZ‰DÖ,»-ŬƒlŒ¿B cÜÛÄ <2n™É¥_Ýi¹AþëíþF˜ëk&Fü“Æ|Ìÿ3B®S”fÕ ±åư†D Æ~ZŠ@{*{a›u§N~¾ôÉ{Œ` ‡œsP]µÖ¬ &UŒb ¸Q´nÁ昊­ÏZ­!;¸8íÅX'ž*»Œjb9““MÞsŠQÂç¥7õ ¶úR¢ÀöÍ7ç°¥ÝïúÿõèAfNÍŠÕÑ.êq;·¼}?úÕÍþs[> 5uv# Œy?‡õ¢ÂoC¸äçòÍW¸Û*ÊîÆhù§Sµ@Œ÷=þ”’¤’©`<ûþtŒLo ƬÊH OÊG'ü÷¢Iö¨Ü0 Ž„fžÓ.Å^@}å qø{~µVá÷˜×#i` OôXF Ó®3üêTBÌúš§ªHxn­H×Ê©äçµ2YÎx†ÓiIÀçîš«¥èÒ_5ˆH”ðJ“¸þã[Zã¡·Ž8ÜýÎ?ÏZÒµ“O ±$°p0ÆqøóHwv:„q…Žt*¼a¡p?­)¼»€4 È:´g8ÿ>àUƲ·sŸ-3ë´úbšöŒ ´r¸?Ý-‘ù°$~…2o}Êm$À¸~:Æ0?B9üȨ¾ÀðÖò ^ þ‡ò“Z£>\y2ŽC© ŸÈŒþ@ûT\:?“9û8î?L~X = œH#™LmëŒøtªØX/ Ë)¹R:f´f‰&L0ÉqúVdë"&ÆÉe;£\vúÿ:/ X®Y0KÊà >Âõ¢o³OÁ/§ô«·±}ªÀLƒ.>`{ûZÏ’1q ¾:ŠF‰š2ˆîbòþðcÛŽAüê¯Ù®m~BHºå[${cŒþ ãZÉûÀXc‡%GùúV¼w1ʤ¬ŠÃ¹ÏéÏõµCj妃8(lr‚??•8Ý@<ÔÏ 9?¥X$ŽUÕ_g*øú~´å‚Ô><˜ð½ˆÈü³Š|Äòâ³ßÓý:þ”å·½¸#v \ó×wä?Ä}*ét  z…@è? I¯"ˆ|í¯Ï]²”P–ÖPZüÃ-'B팟ÇúEÕôP(,qè;þ¸ý gMªÉ)Ûm-žþƒù¬-†í÷2?Üþ½ h.n®/ÈÀà?™¨ ¶Ó <ŸïzŒñúUß!¥\0Ùw‘ùôö§®ÅLÙGõª!ÈB¨P8 ¤.vD\=ò îœíV+ëÏÞö«Ʊ'ÊíÀÿ kg=Øç¯×ÿ×Lß,®Ëã°úöüêÇ’o¶FLõüOOçRý²Þ4 ‚B müÉÐPéJ£Ì¹}ØäªôüúÖ†…f—O{åbŽ"NÍëëßü⨼ò]í¶ˆ È@'vF?üx×_j±ZÚÇoœ*Ã)ϯF©cWêIºb2B¨÷ŸÔŠcÉ´æ|ÞŠôýj¦§¨µ HcOô‰N#ŸíŽŸU†6†2f•šC‚ÌùäãÜ~ÝûR ¥¸X±»vXõÁ'ê}©ªë¸0#ÔëPB‹ÌÇ–oºpËøcëùR¼!ŽîU½FAÿ?\ÓNt 3r¬Ç‘þ}¿/Z¦\6Ö9üÿžÇ·¥h:e 6ÿþ¯þµeÜ(S·îÃóù´Á^ôõ®vù±vûN[úVäÓ!eo¼¤¨õÿ>õÍË!–áØg“ÇÒ‚âä¨íÖ°ÎsÚšÌ2qùÓwòúôÊ'+Îk¥³‡e²eÛ€+• }¿!]UŽÓ$ ÷ ™À#¡8ö4ì§NäãÓ?ãM%¸ïëô§  s×4Œì!B• ÖN§ Ê!9éÒ¶v®;çñ5­ `}îh*;˜î›˜œõ5J‘‹/Qý*7`išŒÁ§>™§Ž½i¦@½9 Ç'—øÒ à þµrM7æ#¹ýhB}N}©³LÁˆü( Œ\’pµǦjÌIŠXàbF{ö«ÉnàÐÓQ–1–>Õ"»·@¤2U2 ù“#ÛšBI8'¥‘Ô’}…M8RG¹äŸÎ¦å!"¡s»¨Ó :Žc#êBÖƒƒ 3íÖªHÁ:œgÐü©&9"»d.v¨÷ÿ" bXöÍ[XÚC¹‰ éœæž°n;#^Ooñö§q(™†Ü³äÕÛ[›,2ÝvüÍ^¢?”ÉŽIùüjÈ pG<óPäiw#”³&rSF?*­4„O†דHeòå.F9úTWö–ZœƒÔŠô ™v!Ry=3Q[«H¤ó‘À$â¦û (ò䌟J»)A{¦û Ͱ³²ŒªÈçvyÇjÐÛLVlš„ŒÝŽËYók29ýØUúœÔò¶Z”bnË$(¥œ QÔ+&ãTeÜ AŒðHÏéYrÝM)ËÊÍÎy4‚æB ãÕGøU(X‰Tob+‰æ™‹I#Ç¥V9Í^k™]˜ÿv 2ÙéìhȯNFÚàúSÛkò B[»òôÀ‘˜nÜ?:¹ks–C8­B–§ÿ¯E‚ö+›ƒä©$psëZúʫʙäŒâ¹Ô“œdåXýG4+”÷*÷qG¸u+PWkpa¹ŠBIÚÀó]äN²(e9f¢JÃ(jq4Ó E$Æ›¿_ÇÒ´íîVh”änÆÏz7²·÷TMºƆRJ7÷— Ÿnàýj|†G­]‹k" `·ÆI#;g<öÉé[-a{«Ïæ;â1ò‚Äp?éZ¶zµ®³$˜ê{}*®ŽzÓGžãùHú䎿çÞ¶í¬á¶ÂD¹r99ÉüO§Ò¯N¡Wdkó{vÿ?B¶˜ûÌ}qÇZW¸­ªp]ØtÃÓ2ÄGF$z¨bEÂŒìþU"[H@&Sô'ü(r½¶œûÒ@¡¢ä}iÉQ#“êØ4ØÛìîVQò±Èj@R¿Ñ⺔m“Ôwúú×+ue5œ¥$R=è~•è{AŠ­wa Ô%$PAïÜjV xË‘ÏJ Ò´µ=*]=É ´Dü¬?­e·Ò´Nâ%ó)"lzT]±NR¦ò Õ «È5e<`ži’Ç”sÍ *е Ý_Ö¡1…{~U“4K­‘ïÁª^dµbbãÊN¢­Ú Ûƒ×Õ]Я¥M ÂZ É½¾Ÿþª’ºš‘[(ß©÷©vCŒaOñ¬fÕIäO­'ö›üXüÆ—+25&±†`r¼úãúÿõë÷Nx2W,Ÿž*uÔöœG¾*uÔᔑ€ú÷þTY¡hÌ2Ç#ð¤,OU‹ï+ÏýÑzñÓùÕu8$ö«$Pt\Ÿ¥;çþáýiÙž¸b ‰C;mU$×AáË2fšI¨c®ýUœˆqÔõ­m‰¸’5b¡×œ('óíA2Øés¸„CÎ:g ªšÌŠšs&p‚Ï'ôž À¥ƒ±ôb0OÖ±5EgºŠß"|ǰ'áJÆkrYJªŒ€¸þUŽîò]n\3œgÒ‰f•îÉèpFsíPDß¾‘·g€xõ4Ê.Ú –ú0ò3 œœÓé]$‚}{çšæ-ÏvÀ9 Ä=?ƶش."ÇŒãÓÛŽ½=()ßÇÆ¡i¹,zà‘ëZÊÏn<ô…#ÿâj¦›^j÷JòÔ*‚8ýk¢XÑ@À‰“±‘«À§ì×ñü+•ÿâ—ô©ÓPx°·HSý³€ëƒø~B´vFx!OÔ íã`F1ž¸â˜¯}ƾ%LH<÷þb³.bm›dåGÝ~8úóåV7°“懺€xúuÇò©Ã¬Ñ‚TúÐñNTˆ¤9nÍëEÊ«¯ z[‹aȓ߷ùÿµÈTìwÝèh(m‹àIv9ô=_çTDÛ“Ào—¥[‘¼›„˜dpßC×òëøTz’çŠär3µ¿Ïÿ_Ҙ¾֡è?Ã𦵪ä:„t9ÿ?Î¥•Y•ç äóþÍ΃ 1Û?ýq@j4 ¸ÎC‡Î[¿çNóïúâ<úœ/ÈäîqNóPçç_ûè3"e¼“™.B¯ûü1M[8e÷J}êcµ½¬’-[#ï0þy®xòx¦iƒ4dÐ)A ¡ÊMu–¥-íP;(ÈÏÍ…®Züé’<…Üq“Úº»Xb‰AH·œ}à¤þ¸Å"&<ÜFzÞê¤ÿJA,yŸ˜ö ‚–jrÇ2=Áÿ cìtÁ_Ì…\Q&G@@õ¬KùL÷˜êb¯ÈL±bJã‚NHöÿëÖ<;³u4+ƒš¦äg8Ç<â¬NÛGóâ«„ÜrÔ#F4³9Àéè)D_Þ4¦EQ€3LË?| b”SÀçó¤ó è?3M ½Y…sŽ :8Ü¿$ôÇjO)œ6~¸«+NX¾„“äQ€?ñ©¸Ê‚צÏãK°‡ódG*½C€nôÀt@(û¹!û Ó7áqÔŽ)O$Ò '8'?•hY‘›Ò³7€p¼š¶×D[Ž ÷¤ÕÊŽ…‹‹ ñòŽ3T”ƒ $œ“Þ£L»O5# >(µ…{–Ðïp©‚O|ÕÜ%¤c úÔVÅm¡i$àú‘ú:§=ãLwdã°ÏJ‹6Í/Ê‹1\l”‚Ãæå˜®9§M(–X€v< ëT ˆÊÀöžÿÊ´ÐmP(i-´TŽÅ™íçf­$Â>Q|ÔS_÷öMfK¨Ë3lNý… 6&Ò4g½Ž#´ŸÇŠÍžg”ä¾@í‘ýI¨eF+“Ú«™6ã9äÕ¤f䨝&_Ϲÿ&¤U;~l¯ÿ®¡pç'¾iÍÏD’m„uaúT[ö“ч®)Vl`Û5elÕd=:€qþJWʹÁý(0 P¹ÏoJ²^=»xõÇJ±lW€9ï»ü¨íb– Ÿ­HÛXpÿ^§h¶™€Sþ5Tc€F~”\v+˜ÈéQ¶IéVör¨eŒ!Èè{zSB0Á§+‘Å0 “Ê OZbH cŒöÅmÛjïe`aÐÿ íþ„N; VbÜp@¤Àê ÖâÛ+•rìÄ…í­\¶³šõ–âóåA÷#qþÌi—±ÚÜ«ËõÈ ÿtúã¥v0ßEqøäj}‹Qœe@À t¥5Z C3 Õª€!eÀ<ñUåvU>ó=ªÌ¬K7zÔq´»§qßwØ~tÐ ˆ"ò>cÉàÔ¸úþF“È èO¾)ßgyÛ“ëÿê¤qߌ©"”`=ªž¡{Ÿf?1û¨ŒþµËÉ­^I6Dº‡U%p:øä6¯²PZ3÷_ÓëW7&॔ÐÉÿDZ–çP¶È@¨ßÄÇúœÿ!õ«AÞÒè)á,¿)±þ?…&2ìöÑÜÄÑItaÈ5Ãë:,ºt¥—/?+c§±®ía1€Œg¦ðß¡Á¥’磕ACÁSþ¡; Ç•‘NNN+s\Ð[OËZÜž÷}«5>g¶9­S¸‹1dPq»¦I£ ¥õ÷éÇJS„ÅÜX7`*kïR±Iôé@ËyªóÏŒªž{œT›ŽÀG'µqŸ¸§ÜŒ:ù¤ƒÍ!¸™¸ó\ÿÀZ\·%JyÇ·µ),3IØûäâ•­eUÎAö´TP8>õ ¯“´~4‡c5‹)ÁȦùŒ:1üêIÊ™1úSJ¡ æ7÷H³¸ãqüé6¯qúÓÂŽÆ [œ:úUfµlýìýA«qǰ砩0ÛéH©£ŒÇœóŸj—Žê=¸©ÌYþ•^)’E˜Ï` 0¨ô©]C©tª„ÎÓ@ÇŽW¥Fjupÿ^ôÇ@GJp‹ŽO#Ú¦BØTyÈÍ?£ükr7 ÀÈõÍemí·ç'>fÒã¹úæ·€ ‡ü?×úV/‡›|—²Ògwâk{4-ÄÆFw6mƘCއ?ÿ®œØ#ƒóSg8$Á C ‡vÓ×1Ûó¨š&Vß_âÈ53.áëþ~•J}âHÏ_ñÿ&CŒgê*…Í *ÛËÝGoqVç’<uÉý9?Jg˜‚?ŽAüdžŒÇ]ñàòÃïùÒô›œ²ü»¯cüª{›s’Ñ’­Üõ»ÕbºÁ;I;\/§?Ͻ jÆKrþ±:t"¦Ê“ž€þ‡ŠˆíŠûpû²úõ9A–äb&‘è3ëŸþ½C²#‘å‚{üƒü)EÜqŒJápAÍF×ÉÈBøéòp?:bUD€¶øS±Ž¤þf«¬²0Äqqùÿõ¿ZV.€oaž˜Qßó¦Žá}Oãþ51ÁfÀÒ€Vê{z~5ZwÞþX8QÉ8ŸJ!0fù¾êgŽyÿë:z"© É<ý?çÒ§µ¶7\ƒˆ—«ÿNxúÖÕµ¤1 !1È<ŒdÿSõü¨v)YéÅŠË(9 ŸåùóÓ[F\€½zÒ4É98é0=}‡ùªëºð€ˆgÚs…ÈŒ}IÆ_¥-…fÈæ½2e-ciêêÇãÐ}OåYòÛ̳!šM€!oî#$OùÑ&3àÍ>Å!ÿ}ŸÈ Ã׬䳸Ia‰ÌEpÎrÜç»þ.F‘I­gRäÌÀå€-ïYâ$’æLgjñÞ®=â»o ’>c–sÀþµ%µ£CyŠ¿<Œc§­4ЂÙÒÖúfÉö×M©ýÉc#ÔôüÇ­s6± l¹\d/¯_JѸ(c^Ò…bNWƒß°¦KF΢‘+†dÜ3òîãYPƱÚyŽÛžP ÉéŸÇùÕiܽ´ò¹i$'náž›ˆþ@Tñl:|9bNãqàävÍ µ—I]B¬Øô£ðþtʣ̌°! aŒÔΩ|¬ßóËŽ¾¿™éQ$Øšà„ÆJýãíéÍ4!Ö¦ßå"ž§udÞ¦»¸3ÎXœŒ`T9ȦZZ ¥”¢šú j×ÅØgjœpzþÕ‡VÀÏàsýkF·û5§™€í&6çˆÍhý­z=¼‡·Ê¡‡ùü)ËVYéþqL‘T‚Hç{Ô&ê 1²Uÿ¶lè*6½‹¦ïÀ©þXþ”b®¢ªm$ÇP3‘Ås‚UEÆyÏ¥mjs‘e!ù€c´n~•Í“šfÐØµ½ÎOÐS[{ô#œÕlÓÖR=Å £-Š› >•IÏ4A?JIƒ?J‘'aÞ«­Án%ëTAÉ eÈ.™ÀVÁ>Ü‚Ê0r>•H[*e_ËÖ­Fp¸ó~F¤¢Cç¯cMäÚ®|®0vŸl ‚H‚)À;OlP&ˆädžž´Ö“'NûÜu§.ÑßÚ˜ˆÕ±ƒ¥8¶IïK(ä–%w´†*>_¯Oñ§€H.T°ê@¤qÀ<ýsSH6ÄzRe"&i’H…:÷áØü¿^´Áv1Ni‚)>ý¿ýTz©kÌT\aT®/7dã=*¼²É)Àböÿ=hKrynŸ­ Äß#|€`q“ÍXDؼóO `ô¦–É!yÇ¥2H¥Ëp*# ¯8É«ÎqíQ;þÖÙˆld`{Õ¨Ñ$ÚÄðO&ª;î ÷¥IYrâ†3Qž(—ÆzÏ©ü*&P·±à¢ŠA! Îjp¥Žî÷ö¥`nâ**=:Õ”b©‚i…}‡ähy¶‚­‚Iw‚š˜É=1Pïî‘O˜n<±-Ç~Õ*Û»Œ0À÷°IÏ­6{†?*2¯õ¥©VêÎv5ËJ‘”’OQDj–$~^ôð ‘•Ç­Q™UxÏáLÃõÙ×ÔU¦ù“ƒ·Ô ÿW$ïúЀB£¶åþ5bÚæâÙ³‘뎕I“Ò›æPCa¬¢É‰†Â{ö®’…‘+‘Ûÿ­^z³•ú}úõ³¤j±@Æ7ùUÎsØΦJã:+¶óf†Øts“ô­ò¨p8°"¸kÑ€xÆû5Ð 9°¨jÀHU÷=k?SÔ¢Óà.à?u}Jžòé- ydÀUú×}{%õËLíì£ÐzSŠ·w’ÞNe‘²OAØ ÛÑ<>n6ÜÝ‚"ê¡oð‡ty/Ú§\‡€ˆ×hœ¥Ñ†¬jˆ@ Uï¬Òî€Ã”bzÆ­““Òš¯• ;Öc3ì¯&Bme ÒGÔHøÈõÿ>õ¢Œ²&cm§=Lý?ýU™©C4l·–®UãûÀ¼;ð?ZrŸ´À—–sbP>dÀ?ðŒùj`hI–6Šu[ƒÜÏúþf¸[Fm>ë|`˜Ý=DZ®ÆÊþ+ÌÇ€²¨ù“¯åê?ÉÅ>îÖ9íÚ9>hØc'µ ´Á£Ï6w4„0*íõ›Y\˜Ûîÿ¸ª„V×$…RA94²rÏåOÚwTMçŽhÊçËy8õ¡T(ÉäúúSQ¿v§§ƒæA¿Œô¨gá@  ’{þubÞÖI• ÷50VÉPÃÞ­Ãy`þèíð ?³¦õCM6SáÏÓš¶š„-¼¿_ÿ]L.anŒ§ñµ‘^ÝH·Úr:ƒI ˜IéÈ©ÉÎxç= Ret;Àà!½‹¡»S[¯4Ä`vµHÜ úsA:¥'Wµ^?J©0Éžô +r‡"¤YU¸n?Ͻ!À£wôªpÙOÒ6OŒgTîRFOáJ$aßó  „ÒAµ]n=EH²«t<И]^!9Wƒ´T4Ï÷™Î>¼žß•WM‚PX§¯zžS±Õã Ž@?þ¼w¤M‹I"˜<ãÜvü{T6ð5đێíÉÇo_çIæ+'ÈÊ[®_ËŸÒ¶tksoºât!˜aG}yúPKÑßih„°$QÆ{ÖSK{mû¹#Æ:sù÷®©$F näö=Z˜Þƒ5&Œ­Å­­¤RC\V©_¥µb,-ÄËF˵a8ÈÏùý) )› ¬É¹3€@þŸáTî¡Y‘š7åGÞç zWØoÓ5ZhƒŒå•ÇF§¶}=¨*¹i¬ƒšTêp:гo –pyïT£f‚r¸ãèú⟠y7%w.NžÇò l´ðÆYX㨧˜âdt8¥eÞ¸=>´Ò¯ýñõ+Ïó¦!$}ª{žƒŸÖ˜ˆA,Ä–>ý)ëÓ’KSÚœÏ4Êó?–„õ' ªöñ¤1#œu‘‡|öÈ¢î@ýÁœ{ž•¥cŠ NLÉ>¤Ð=‹Öè#·8;Wv:ôÀÿëÿœÓÚååaº³ШÉ#Øvúž9¥†Ùî Á† ˳™=úßþ±µki”Ec\ÎNI>çüj–Á"…¾ò‡º%ÏÞ1/ÝÉõîߎ?Ñ.#Œ,k‘Œ*¢ñørëP\Þ[Â|²<ÉO"5œzã çóªmö‹Œ=Ì»ô†3Áÿxã'éJ̫ػ>£8*ÓwEl·ä?Çóªw7(UÇž„Žžù9?õA lŽ%OeP£üþ Ò;ª}p3ùÿ…4…ÍØÇ:JA¸rø¼ç>œÐQvÆÆ=×ÐûUöš>v–÷CE¬Û©@“^ÑŽryïØóúQ`»eøw!—%Y ;çqÖ£†í$+hFÂ[NxëýjÊÛý¡Ä®Êä+Œû‘šì¨/[iÞÉW¢‘ͼvrò<ÐÄçwûG·çI†KE NwpßÓ&ª\Îò4Ç;‰c€Ø>¾½? º'$ cetÀÀ_¯·é@5¡Zè$7'` ¥b äúÕº f>þ:tÆ*Kɦó@`'Žy8ëYä÷=i!IÉ m(¦Pµ-ºî†EGŽ1Ò¶´;–FšEʯ Ò7dt1:2ªž[oäEJJÊñî?Æ«›XXcbãÓåÿ iŠ+q»—þ÷ËÖ‘‰;2ã üÇøÔO!ÆOj®×±¯IK}Ÿý–¡{ø2Fá‘ëÇó¦;Úä£÷q¼šÄ5jú>åØt ­‚z~”­Ú*O%é… ž˜ bQE-0ièåXG¥2Š@Líæ1bNM4’\Ó4î  y£{óWaº¡\~‚³)êÄæšÊTäûÓ@ôªi1CòœƒVÑË€Øâ¡ yÎqÅHåL`ãœæˆ@9§´`ã¯êYH#]«–“ÚŸ!Æ2ö©ã‹jîldŽž”É“räu7.ÚŽp)›FrM=Ê¢5\ÊÇ ÇëVˆ,¨8çëLk€:b¡Ç—jxسïÖ\BìýKb¦R Xsƒž)žvzÌÒ‰öpI 9§ÌAL(IËvp3Ú“~ýtÇ ?.i-ßÀ~¸©£;†êiûO   B&C‘ŸÆ®Á"• ¤u¶>´ÈŽ€zŠ´_~ ¸Ï­+sQ7š`8°ªRÀ)5 }iÒ7@>´†‰£ÝŽj“\>âCœëš¹[rsÎ*–O­4±"•º*A†7Pjžx§‰äÑbIÕŠ7'ê=i%È;‡# ¦–Þ¡»Šr¶å* nì¡Èÿ8¨Zw+¹OãLêßZ`%(4¾[u=y¤ s@ìoZÖê)Ž[c×µzÜÛ¬Ñ6Q†Gøó q[š®Ör}žL\÷?tÔJ7cÄ÷Åç[D?*ÍÇzų¶{˨àN®qô.¢$kù¤uûÌHúVÿ…lÂÇ%Û¯$íCíÞŽŽÚµ·ŽÆKšAÒ›#ˆÑ¸ 2k2ˆî±XSï7^œ •yTáWŒ‘úU+e2\¾rç¯üÿ*¸ŠB/@;Ð!ìFÓ»î÷¬uΕs¼#}–^YFNÃëóúV©p¸ã©ÅÑ£`¤Œô §{eö.­drŒ«×ñŸÇ55†¨³¹¶¸]ÒðÈÙçÜgšV]:L Ín{rv}9éíV.ì¡Ô`S¸(àgN?‘ d¶œ·ví‚¥qέ¡ §“]A¹¿Óp—q¡è&^qøñŸÇÞ²u˜a¹ívÁüª¢ÄÑŽ‚3M~\ý?úÔÞGCSF™P\äûÖ„Œ‚¸ÈÍ?*½Å.ÕÍ0Ƥté@ ¼˜?•!j‰¡SÒ¢ehúÖ€-d{Ôr|ÀƒQ$½›ó§“žiŒôãv«>ýª9Ç4 8SiEiZ1ò@Ïz$¸FC“ó1ŠŽÍ¿v@㚪ùÀç‚jlSعnR¹«Yܸ¬¨å(r:VŒníL‘¡³Çp*¬’5¸â§fäúU9$úЀ”2··Ö”©Æ{TJ¥‰©”<þ½0#•{Ôa ç­:esØÔM„Oz¯K𤠠‡$ÔŠÙþ"j+ ðöˆ÷ò´ÌvÅ;wݸȤØô¿Ý,)vñÌ2 ž@íÆàsZÏ›$GÝèsŸÕsúšÙÓÆŸ¿„?Š/—?ƒcô&œÚ•£Dáv#ªü›ƒøf¦ìVLÂ{…HÙš1…ooÓùНo«±qû¦H½?"?1ùV”ö–ú„ª–É"Û©Ë3ö\ÿLUϱFPG·åÆ1Ûò§rKqÏJ¯oöÇvÀ4@$€4!£VÂ7·§8ãüö«Yâ™ Qº+‘Ó¡ô©;ô¤<ÒX~é¶°'óëJêÆGCþEJã ƒÍ@w)¹ô>´À®[n¡Œýäõ¥¸å³zÔLÙÕzD{ûÔ“ŸÝþ"²³'¦Wèqš‰â'~o§ÿª¦>ÿÊš<Ó.â2êÊsò~kùôýE3>u¿<8ïèjýÄb`JH:2ÿ/þµeFíɺ·ozE#RÞ_2%$óŽ~µ)ªí‰^3þðÿ?ç¥] ¸SÓO sJO=G¨n¤ zž(„9¸¾|»·`8Öº(cf#g'¢RkŸÒ£ßrN3´wíÇÿ^º{>ÒÐV1“õ?ýlþt™OsjÚ¶„F¼w'ÔúÕiçšáš8G¯.~l÷Óëÿ뤞ô}ȳ´ÿõ>Ëþ=,@Dœ÷<ÇáÏ_ÇôéS`nÁ ´p!U^§%²rO©=IúÒ³òV!–pz}zšR$‘zìèO_QQ¸ ×j¯®éÓZdʬqæN@bãøÉõÆ*·šcF$åOË‘‚@÷ŒqÏ~µ§"n—·9Îïþ¿õªªËð»Žÿ EEÙ6›‚8ÃçÍbH…ŽTà×F‘m·82’¿QšÆž’íÂ)$óAI”°iÈŒç 2}«R GÁàzñéZÐX[Û¦Hõàþ§ŠÙ‘e¦4¤A[áVÞ@…;.Ö ŸaÅ=wËò Ú¿Þ9?áýEOQ `<?çô Í»”Êܺîiš$Alþ½¿Z¬v"8Ë9þ0„þ¤ŠÔ’á É˜ãü*´·'{ÿú©‚ehÏ–•ôàéT/e†ß÷p±gþ#œcò™¨4Çb”wÀÉý3YÝyͤ)9<Ó·Œt?¥GŠx :óAD‹*ûþTÿ‘¿ˆTj›º.~•f8Œ¶(ÔÓzŸÂ®€Êû¼`íò4‚ŲñœâšöÌ£<Zcìñê<¥eáŽ?:.2qŠ#¥[žÙ—æjªE0—4”¢€%\ít«Q\”ãhDZÅV¡ð«P0?#~†‹QÝ#`dƒïÈüêÂÈ ŠƒœúÕC œþJUVBõÏJ›sX°õò »sɪâiÇ#ߥ5”° ·^¾ÿ­+äC+ï /AéMPAÏ5!Œ/J@1V@ÖoJ‰y~ÇÖ¬òyT-»$×ÚÝL*[“ÍFr~ñ?]†Øm${P óÈ©ÿtJ´-ÓŠCn¸ã9\,@ƒjUpÊHé¤1•ä(ÉW!FqÍEå2à©éøTŒIp=.ê0’xÆ¥'–Ç“úÔÊ{v¨$—$ªÆXÖ/”uô¨Œ¤äí4á ïÔö©ÒÔd~‚˜7bº‡™|µ­XO9êì0$# rj^)ØÍɽŽDRŠ@ ©ÖÚVØqïAcQ°y§ômÃ¥;첎€ÄS3‚¤l§æã¸¦Py<ÒP±ÊÈF9†­+G*nþ!íÍT†3$Š£¹­„¶fÜqH ©•ü…01 «÷xË&qéÞ³ÈÁ  {I⾌[\6ÙÝ_ϽvZe¸µ°† AÀ䌓^l©à⺠3įmÃ:ïE ÏëÅL•ÆvµR÷sùp¯WnO°¦Yêv·€yR©oîô?•[EÝ1îŒ ‹ TQ>êŒSÆr~´'¬¥˜ úšB8.zžiG!¸îjm¸íQ=:Ò¸YT£ôÅT¬fÃdÀÇ©ÉÚÏùÏ[Yî:úÓÙRâ-¬ wÊ댂ö5•y¡E2³[%ÈM>è¡Fû­ZFW&Å2OÖ’šòmƒÓ4¡³V! õÇ«ÈÏŒгøR´£ÏjPíÓÿ_þ½"6ᣮ~lõµt1#­gJ0ŽÊHVɶƈ®ž0‚WqŠÍ#(?àES»˜0ëi²È0ÀŽ£ŸQP]0óüè44‘åÁ$ÄOjèm#1Ç’>cËjÄÒã.ÇÆÐ7ŸóŸÒ·ÙñéÏÊ) î>Ö=ªîKnvÜI$Ÿ¦jÀÈ`Gåþ­5@ô¦Ë>•‰çŽŸâ}¿•Î7Œ±è?ãJ¶ÏÐzS#Rƒq!˜ŽIÆÏä)w±éÀPñÚ>´¤M ƒœÿ*7 ¤ÞAèãÿÖ§qÖšAçÿ¯@d ¸æ1ŒõÇÇTÀ*1ž8ïC]F'hÉ Tãæ9“9+•'’GCõõ eY#äò}GZÌEhÉÉ!sŒã¾qZÌBÿ¬sߨ?U`Ž®‡‘¼û*™œ§÷ÒÂýî_§Je¢ËæÊ與æÏo¦i'q §qþ{gòú·d6[¨ 䌜{óAO@Œ]ÊØ‘–Ðm'óÏò«)hw1ÞãÌwþÃ1lçß ÿ?O΢lÉ÷ÉnsƒÈü¿ýf™,°×H0ç>Ãϧó¨Úi_«mН_ËZk˜`ÿXê¡95™s«±â´xõ¢À“{7q['$g¨sü«êþ[ƒ‚v¯ ªÒHÒ1gbÌO$Ó  ÑFÂŽM)9íLÍ( ¡Ã9©ã‡¡cøScP£s*q”Ÿº?@‚ª1À ?(üzTqÆïó6qÚ¤p‹Ÿ¥!Ž/´uÉ¡º¿Moi,®vì}1WE…Èÿ–aK™ &Ê*™‰úSÂŽÕ;ÆÑ¶Ù«~UR ÏëEî fvR=ê‹%iÉlqÏj³™ˆ·H2ÄwíEì%Ì §ÒŽjÝ͹†R¸ãµBµUÄ5I*`Ùç©Ü® ~”¬ c•ÆM™åg¯~¼Ó<æ ÏO ©â?»_¥Wq²SŽ{qHdŸiãåýiDŽ sõ§8 „ST‡ž´ðêË‘Éô©¢q'·­A¬D®Þ¿çš™. ¸?Ýì~” ãj°Ï8¦Û(ËÒÎáÙ÷”ZŸ¼>”ƒ©#Û£sŽE; ù†)X)sš·éA¨É’§ŸLSMÀS‡àú( Ž' ÉëMa‘ŠFzQ»4ÄWó=G9f=qSŸza‡,0Ý{SäËÂÙçÜÔðG@ýI *GÓéЂc'oaœÒµÁ»j*Æ3»½X…?‹J‚Bà žÝÏj§¡ W¨æÎy¦±ÚyoÒœßxTLwJÙâ€ØÌ‰aQ˜Ô~ÿãRYë!^†­Ex€È>£­"‰ö“ÿë¥ò7 ô©ã™dVÏãRu¤3"{"¹dÎ=*ž1] /¬ë»aËŒüéÜEkWÌôéZ †ý+Êsü-ùT‘™¢€úSPj£umÕÓÜU˜O™fÇ>œ˰}ÞJ@b3Vn`(w/*j©¦‰#!Ê’é[z‰níHI[ÎÑúþþºÀ§)!¥`=+NÖ-5['÷ çÿ¯ZjÉÎsé^S®Ž¬¬A­uz?‰ˆ+éÈè$=G××ùýk7n*Ê;±?AŠ@98Ü:{ÓŠ‚}꣯—'qøÕ˜Ûp½n9êj+äåzUšK”/úÐ5±•ELÑj,SŠ–9 ûJjF\ûT~T:J¬)%“õ¨l0@9õ§€[‚å@æ¤Yê2>•b;u^Xf¥òõQùR¸Ê ×nßÊ(%l å½ý»T‚;Ÿ¡5ÑxjÆ/7ÏdÈêW8ôëô4\–ì6ßRmˆ¶öW¨ ‹þ¿YþÓ¹^[K» :ÿÄWT™ã$ãÞ¦P=¿*žc3Žþß·O–X§„ãøÓÿ®*å•呌$WÌNNâA'þÒºgMˆ”ö<Ö]Ή§Ï’Öq+¬ƒaÿÇqEÂÄgŽ£¥!µUm{Lý‚öLvŠlýôZŒ]Í ˆîàhØôeË)úõ³øSºˆKjñ%¨ z²r¿.ôè.cv2ðÊØά,Šê0ìG5ð ~tb’Œ ‡©ˆ›p=üé UIÜ0Ž’CÓÃ~Ê¥;»Ò‚BG*§š¥! Nì‡ýýTé’EmÙ,¤òjˆ•a•={ŠBû†wž;“@÷9§äAê:Óc§OJcMcÏÿ^”ÓME/*ßJ¡;%ˆãÞ´€rkŸ½»ùV$ÏNOáÒ‚’$Vå‡Eà`bœÒ¨3õÅe œŒÓIby&‚¹K²OÏ–OÐt4׸óä`Š©šp<ÐUŽ¿B*ñÈã=…kýéãSÐ|ßáX^aöIÆwJÝ· Ìíéý­#'¡i݀ؽOOsJ‘ùjqÉ=ÿ§Ò˜¸l³gž€ö©‡sÓÔÐEÆ„c÷<)™,zàS¨ô†£xÙ›"FÊ£ò¹$þ7ZB9¨¼¢>ëÊî½FG±ÿŽ{h¦ÌMÌ:79Z¨Q­TlbñÇ~=?I\qÔRÜÊR!·9'U$g-]‹@ƒÞœ0+*;™¸‘ž„æ´Åì8š@Ëö©Ù¦¯© .sy§BDÎphEj°à¸Üçµ+šC§  ~•£˜æ9ü)ªX÷ÇÒ¤Œ’) S¹¸Î?  9$ùTçÞ?¥C#* –϶ s³ïÏÿ¯Dr Œ{Ó”'>ÜRy~S†QÇj`YŠ?Ý€Ÿ¥+© QÇ6|~t€¦Ñ¬±”9 ô¬™£1HTö­¶ 9P1ÜT[‰±¸óê:ÓŠrŽ ·-‹§*r=:®ªUÁ#\V­½›_Zy°e¦‡©#Ôwü=«$ŒV–‹~lo•›˜Ÿå‘OLzõ)1šÚ.¸ögìó–x<•?ÔWeo$r¨u`ÀŒŒÞµ…¢Ûê™`+äp{7×®~£õ¬í;P¸ÑîÍ¥Ø`¹ïÐQíô¬Ý˜ÎÓ4Üäg×¥F³,¨»O Ür1RdT ÂËüC5NØ•»º@Fwûÿ]Ks(Yâ'»mú½QO8• Ÿ¨æ¨ ÊáÇN{ÐzRùxåzÓO#ÓëHG?~DZä-ê?Æ®¬ÊÙç§zÍñn×yjX”=½s¾dÐ? $d{â®×´"”0Ã*{W5g­ËªÎw'BqÏé] 3¤È¬„0#ƒšŸ¨xzÞéL–øŠ_@0§ð®fK)­®„S!SŸÌWx­´àr=*®¥7İ—ØÉʄژ8ÐûÓXŒgõ©…±sò¤¯þè#ÿA֘Фd—· ÿ´ýE]À¥9#ð¤‰Y˜aXZ»çÀƒøWè§ü5¯aè ?ðþ½0WTþ)“ÍW7ÉèOáÿשE sÛ4™Q)Ü#‚AB£Ô÷ªåO¡­ešà2ŒúœTRÄ‹‚  ¦…b² EÅ)#8Å;9éR¬!F[­5Bç8ãÖ¥TTúÐΪ2Hý*œŸ»À  ÚUZgŸžß­WõÍ(,ǃŠ,‘1éÀÏrzW]§\Cf=×å ¤ÿ!è\W -,ÀmßíÖ·RÚöuUP0ŸÏò ‰ÿðûcŒ1÷aü¹ý)ɮܞ¼?‹È?œuŽºc²âIØûvý¤R/Fqøþ&‹to®±0 =ªãÕ&ÿB S®£€o =7 Ìd~µÎ¥¤ñÿ«—·B£ŸÈ­H²ÜDIx±êÈIÿëÿ:VΉ›´€vãŽÂ¡FÚY=:Sji4Âæ«Ý\‹x‹Éè=è¾¥x"O-yb+€¿3Ÿ˜÷§I.ùG9n«6I$œŸZ RÒqÖš_ý‘M¤ bæœ)´¢›ú ò.ôÀ({çœý+©³|Â[œ3õò® ÎéíeœŽêzïl‘ üè1š,Ç×/SŒÓ#\žôð¸$ð3éHÌpt¥¤¥ ·×Ç÷ˆüªFPÝqL(1ŽÔÏf?Šÿõ©b8*ßOÿ]%íÇÒ˜Q89úÿõóý(+A΀§=¸éQ1–B~ñê>´4Ì©ó ŽóøÓþúz©èŒr6î?J¥ŸýjC~'ÿ­G(ù™£4ÈÀ óP4ÀÈ•qôªEÇÓSX²}ðG¥•ÍK™ÀåòªéËÛ­T2å²àB¤Š@»Øž§`nìf¥&B úšÎçúÔÒ¿›#9èz{ fRØÉ»²ô@2œžj¤ñÜùU¸Ø Šoõ€ð(Ø ÊÈsÁúQ¿qÉüªÜÉÆqõU£GåL–XŒå*LñUcb‡§J°¨É3òÔRC©n0IühÅl²ŒsQGQOÈÇãL Ô´¨íÖ€yÏçMƒžÔö\sŽ((ÃuíRmàUd8àž*O;a .=ÆÕ&k9cÏjƒç”Wh«–ˈÏÖ„Lž>"GªV<õ«×ùGëP.Ø£±fÜmˆ Šé ºãÒ¬Ä>AÅ?`Ý»½2/fPö¸ øÑ* mƒ|ÉÃuúÔàÏ\P);‹H=M-5z©¦$C¤cqAHv9'<ŸZ‚I76;ja‘Td‘ŠƒbÖÜPOÒªCp^M¸ÀÇd69ü¨(s6Þ€ßÚªK¹äÐTÿ™¦'ñ7­"&Þs¥I’F Î)hX›¸º–>Qr“EÔ«u¸ä~•wQÓÖú´¸û’ãéùqšã4M@Ø_£äìo•‡µz“äÎÖaŒà ÖrVc0t{é-.…Öw•Nz{gÐÿ…tlû¸;ÖN©kì¡`—(2 ’¤L§½;H¾V¾[g·©c ÷ÿM´_öóS]%ý[ƒôê´¬$Ö"DjI«r¯› ‹ê¦‚@9Í5ºÔ62ù¶q±<ã¦läÔ•©&n­Éø‡òªsYG2•aÁ÷ÿõÕýE~x£JЍG?>„FLoø?§øSmRöÂL*ù‘“ó*žqôàþ•Ñx¨e\/)¸zqüî£:ƒëÚœÌ zb©™áS‚æ&•cùÿCU¤Ô• ‡œ§z,#¡ûD*€cŒoúÐ%FáYOÐçùW:u†ã÷mú…>;èå#}±úˆþX£”w5ç²µ¹\K MîT>µ…á•Á{G ÿqŽúõ§_Ý4ˆ} p?&_ëVbi‰ù”ëÓüGëFÀpÃ%¼†9P«Ž ŒTÌ?qøw®³VÓîܺ®$Q‘Žÿ•s;0 ‘‚85IÜhÎÝRÃ#«a3L(=iS†zUº›Ë+Ž{žyý*iŸ vdµ:AúõªC÷r?ϵHÌÆ,NNïÆ‘zÖ‹B¸?JˆÚ³ aF}ÅUÄTåÎ3R¨ 3ØSþÆÃŒŒŸzoÙ¤i–5Û»¯4½¦¨·¶ÕažŒ?¥_K”¦Ñßå#úVUŽ,§s‚A\éž•j.”¨1€>„ÿL* ¤Ñj9cuʲ‘ìÀÔ¼¼z¤tóÔ+gÔHßÔo—Àv§Op×RòAÐS¢`¼®Ð}Æh-+ŠÇ<ÉùTæÎÖ5ü©VsŽWòÿJx•Xà};Ð's>âÀ(Ý~™ª,…IVã~÷>ب%·Áàê1¦db–Ÿ,F7*i‚‘E‹tÝ"“÷w ×~× ‘¦>f`£ÿ×\5£æhcQÉq“ø×al«>¤ÒÄj?>h"f°`)P‚Š}EFÿêÛÓ“OQµ‘âÁA$๨Zí3µAbzvÍ:^ao¥b}Ñõņ ¦nDxúçÿ­I™Ït‡ÿ^ž_Ð ¨ú„QIµæŒ]Ý>¸éøÐ;ƒ0ûÃòÍ8°;€Àá”Gµ)Úq( G\äž=ÿ Œ‚½ËjG‹’ѱV?¯ù÷ÍB.J6É—±ìÔ eë)µ'¯¡ã¬«¢|‡cËã’>•¥zU¡'<õéYWCÌ´`;éAhÆc½Ccºõ4à€2y÷¨3òì'¾Aô£Í`Ê[·Z °òN;lM°²õ=Aõö¨å”`}ÅBe$äqŠ·ny2¦ÿsžÂºHvñPµËZÜ´—P2F9­‹[{‹Í²\9BŽ3ùv©’4ƒ4 ÎòV27û=?:zÛÏ/úÇ ¾‹Ÿñÿd·PÚ ÞBŽÙÿõVtÞ"Uâ4-õâ§•ô4m-Ù´–°Æ9ǯøt§—TÓô®aõû—áQâMU›T»—ƒ)ÿwþ½Œ^Ò+c¢¼Õa¶, v s\õÖ¥5ÉÆv¦z(ªE‰$ž¾¦“5j)ÊnC³FãÖ‘T¹FOµ^‡NfÁ€=©èIT;vfü OÚ_”y3ZQYÃû£>§ŸçS£Ž1þ}©\«R;Õ´€û1Ïô5"Ë:Ÿš|zõ¿Â¬4Ñ©å”}MVšæ§óþ”µÿ>ݳ¹Yr;~TÆ€L¹ŠuaéÿêªwÎC×ÿ¯M3Dç$Þ½ÀÓ°®LñK,™£š``~¾”«s"t—pì?Ïšqš)Ö&ÖõÄYž9¦LyÔdæ'¾”Çœ8È#Ö•†ZÎèñUûúQ¨V¦KzœöïH /+úPNIÅ"ôÿb&=Rr:÷Í bx4Œ}ü¨ ŠXS@ÉÉ dÐjúÔÉó.n* Š-0üíÚ”A¸Œ“íR:^œöª¥I8Éö†íWlܼm““š ¨*å¡ å}EËbÌ©¾2¾µF2Cc¥iU9ãòåóGCÚ›&¡nõb¥ªvÒî©ôâ­Ð‰–âÐÏZ¯<’!”ûSbœ³#ñ¦-SS¡úšZjÖ#!ç ¹îzUZBç$“Q–,rNM%I±” ¸xSô¤Q„“;”ý(ÜŽyÅ!ŽÜZ7ú ˆœžj6™ÜûP´©ëÁëVTî@O¥dyÌǃíZq"©Ï†d;…S¬ÿZYgur00zdl]ËqëÚ€,îäÓ²ÝzwªÓ\dáN¦¢Y˜}ã‘E€·¹dU)b(Þªz™›x = §+‰AWûÝÇÿ®€*©Á®ëÃúÛ,„Lq$_)r;Wñ˜ÛÔzÕ½2ñ¬¯UÎÜá€î)5q„~eÚ@eô"±oÑôýA/“%†Ï?_ñ­HÝ%dFl0ÈÃŽæunñ>÷¨SƒùVc)i³ ‹»›žvœ*çù~‚µS>Rä`‘\Þ›3Û]½´™F3Ñ¿Ïѳa@Ô1‰§žt_Ýl¥\=+:'X¯Ô“ċƴMK¥òæ$>Ž*¡"­ê-¶Í˜ô ëY2ݤk¹›°ªBe†p “€­d]ꇔƒý¾ÿ…Aw¨´Çjð¾ýêƒ9=ÏçV„‘‹3;1cêNjÕ”BXû“ž• ½¬·’ˆ¢RÌÏ­vn˜–VȬ£Ì#æÿ?祕´)iëv¦ß9F~èÉüÅj£³»—pê2EQ¼ÓÃL$F(O¨ÞˆbKåJäLœqž;ƒÍfÀÑùÇðþF“æ=Q‡áš†&ˆçÊê:+`åV”m#Ú=ÿEsÚΛ喞ùOÞº| p?QËÍ#AçúИdc' þTÖR¸u­mRÆM>å䣕‰Îk9†F1[! ŽFCòñWaž|gv¿?Ò¡Ž0€zúÓ÷ú`Ðâê|ಟ^(iå=[éPnÇÖ˜e¿õ¢ÀX.ØË1Ç|Vž‹aÄ®ôQ‘“ýkÍ2Gœn8ÿ8®ÞÎKuU-ŽHÿõPLÞƒ£ŸÎÀŠ2qüL6ñý)û.s(O÷üsü©mTˆ9ËIõ©^Tˆnv =IƒožZY×ÑEþoÄÿúéÆé+¹ýÔý)>Ðɱô?á@ˆZGFV>øû-TšÚMÛÚtd#?¦ó«ÿhFìqô?Ò£’r ´Ÿöò?˜ z™È÷1´¬žÌ0ߦèjA~C–ÝýAþx©ŒI"åÙ˜z7#úÊ((£Ûÿ¯@î‰ ÝX~àMFÒ$ˆsÈÏÓüi†VˆfL2ÿxqúñ¦‹YðL©¹yÁ8aùò(¯)·?1gûç’£ßÛßóªQæWlŸÝFØQ޵aØFG— qè:þCúcñ¬÷_&]ñ«sëõþ?þº^÷¤¤ ¸:Áª×WIlI=H½i’’J±™ˆXöǧãY7mrù9:-)ónX¹Ê©;°)¿g >\äPj¢Eæà` jIµ³×Ú¤H l1Ö¤6ÉïùÐ1䣀ÿëTÂíÁÇãþMW6¾ùÓÝמ ¡yn#€çüö¨Þí‡$Õ"z‚)2iÜVBÊ娓֣ õ¤ ¢î˜jäã šë,'\Ü)R 6öãÅDæ9ƒ‚Aô®®Þd¹“¹”09èïô¤D‘¼X•#¹Sã²+zÖ ‚Ed¡îéU§ÔR#@\aéõúPebÍôæf ‚Ç€3ëY׬íþ¦ ˆzI' ïùT g{‚¸ó'cÓ ì÷=vsø ¿‚¦B]ÈäøŸÆ‚´FaŠêèâIÙŽ~èÐôÿ¾jtÒ§ÿžò/ óX~€ Ö/ ´”GbBŠŒÝÛtó¢?GãH.ʱXÝC‚/¤ÿtüÃõÿ °ÊdþªGò'ùS¼èˆÊ•>ãšO=}Gç@„3̹ߨÀÿ¨þ׿)àž0Àúšy¸@q¸èN?˜¨'Ž9þbì  þT ià• ”ôÏO¥S6#…û„`JDˆ¡7=É#õ?çÖšä ÀóLhäË‘‘Ò™¿‚)eûíõ¨¢æŒÓhÍ<èdÕ¢†Ö2¿;•éõë\æhÍ&®R•‰§¸’âBò6sÛ°¨³IšJv$plt£4ÚZZ–(ŒÏOZlqï>Õ3L^q@ËQª Ú0¥<Þ* )Çnµševî zFÎp$úR –ßQ$ü‹øš¬óÈÿyÎ=n2i>ðØ?Úëùf®G¦[/JKz•+¡Ù³ó¥ íÑIúWJ¶¶°ò"Ðó¡Ý¸:qÇõ¥qòœß—&>éü©67·ã[²Ä²ƒ¸sëTÚ0Œ@Sÿת¹63ü¦§¬G &­…Éã©€?­™ŠTçþÓ rG5Ô?P3@‘¶¶søS•»•ìÈåçPºíàŒ`.y¡ÍIIÞ8=èjF<C ­顽h-šb?•N§#]Oñ•Á€°­œNµ ˃¸S·t óéO8eÇ­"mqÈçÞ¦‰6ȤqÍUŒ”rÒ­Ž†Ë¸¦ÈЩ¤‰÷Æ~õ%Y†Æ`& Ær0kLŒöªW±t‘G=*KIw¦ÖûËúÒEKUrÃ(uà ƒQ-¾×Î슞Šd݉M_¾ß§Ó¥9ŠZ)UI©:Ï5"‚{S”ÀïRþî.z“é@DZ8þfÞß5®@?(ÏéUšFòN=*Xay䌱Ù¤±<ÃŽûâž¶M԰ǵiAg)A»_ò*ÂØ ;˜óJã3"µ îfǧÿZ®¥UŸ³Ä¼æ3ü©’C ;=ÿú⋈̖ê"OÉ“ïþ½Dn" B>˜ÿëU™l¡?ruŇøÕI,¤\‘†ìóL2Û¿*åO¡ÿ?ÖšÐç9^ÄT$<ƒB»'Ý8 oÇ ‘Ö¤ Ÿ™Nt¦ùŠÿ}yõ^¿Î®Ñ•9¢˜A&~µ_XƒÚ…†ÝÏ¿½K( ƒHföƒ«Å´Ç+ÑO§·Òº-ƒ~àH'“ކ¼ñ£Ew:}ع´F' 5@PÕíLn·äFìzö5£ÀžrqÏùÍM*,±20ʰÁ¬8gkun甓Ðÿž)n‰'i&i”åb|jè<ÕØÎkšÒÈ–Öd=ÛúTäѤVáˆ]Ÿ68ÍÔ.hjú¼&‚"‰äŽƒükœ’fåŽjýóL,qÒ­+˜œõ¡m§n¦8ÝãY)#«nVÁõ¬Çt:6暴Y¦GsŠ*“Z2ܪ.ç‘Bú“\l:•ä$Ÿ‘Iã¥2{û›“‰fb`1üª9Gs¦›SµØÃÏSô?JÏ“QŽXÖA2-Âú8þ}ÿ>õ€@cÇ4l=; ¹ÔZë)"Ÿ2uB¼|Å9ý?Æ­.¥nqºò!ì$Qü…qe$ èN2{Všh“8Ïž›}pi4€éý™#ý&&>òƒüÍN—p°ùg³ƒýk™ÿ„z~¢å?&éGü#S ¹ŒÀI¥d3gXµKÛ&ã.œ©®#+ä’}ë¡Ã·°"ô)õT9þu^çÃ3$lñÜ s´® ýj¢ÒˆÒç…àw§/ʼœT-#•u!”à‚;ÒOQŒUˆ{³?Lâ˜<+mû¤ŸÎ”E!8 ~”ÀÒÐíüÛÝç¤c ûöþµÔK+.Ô˜cƒÖ²ôà–6‰`ÌçqA×üþ•§îr>b‹°öÿ"‘”ž¤s_›|B±ÀXðü5ôœ†–PÏí¸cÿþµ|ÚÅ#‘C±îEH! Š>‹ÿÖ I¢(aˆÁWw¯SüÍL@îåMg 9ÏÓ©?…Wu¼˜‘XûÍ÷¿J¹$òCæR€µŽjŸÚíBœz¤Ð pÒ¢t²<êN?úÿ­=­mbࢩ=2?ÐhhØnBÜþ¢¡‘Æv¡]ÞœqøâžÐÛ1É;OféúŸñ©%LìbsÉ$õ EWÛ îo™ñÔÿLÔ>C>›Ô p?ÏáSÌwÜ„äSúŸÑMد沓¼œƒùÕkˆ€BÑ”òËý@õ©/./–»YÏcYÞLçƒ&Ÿº À ´‰Róe¸Üãôÿ?F-ÚgÍÔt_Jš(V.@ËzÓúš.RŠCHùsëÜÐcFè•8€rqÞ›’ÜOò¤2”ñåäðÜçŠnΘaù ¼ð´‰µˆØVtðËnr¿ršb.=ÿ:…çaÁéIö‚#õ¦<ªÝGéLiU†Å3`#ƒÅGš7x¦Ýi(''$óIHc­;÷…U3Ðð¥fSÕ¶œÐ&u°j¡ÙnIfç 9çò«0¢@±ÏÞn§Ÿ_ÿ^>µ›¦¨µ³ÊKH8œúÏR8üªôv†hƒLä.2"åãHÉŒ]Ac&;Dó$cÉ_›õÿõsBÃ{uþ²R«ÝTçù?2kF«†/]£ÿ¯V‚€Àã¦h’èd&‘啘÷çúþ´­¥ÆßòÇÿÿkUlf£yBpÄ®i‹™™K§ÜÀ¸…ÕWПþµ ×è0|¦‡ÿZ®½Î>ï'Ø…GûÉWægAéŒfóÍÜê0ñ©ÿtƒýOò¤ûhï¤@ÿAV¤dò3õü)¦Î3éÿ|ŠIÅêýÒN{|Œ(fY*ÀŽõ0¶HÎSƒôÇòª7±´_½A†Ä´¬Ìau‘Æ Á#š„©«“;=Ä€ <ñŠnÒ㑟­¥:J±%»JŒj€ŠJ)qEg<â’—”RÒ Q@ @Àà{Rª–<jŽ“K†ˆãþó3úÖÅ¡´¶ÇHßßb¹?­K•ŠQ¹RÓGš\4ƒbûŽkN8¡³ùBGñ7Sü¿)¿—nVÙÈúÿ†j'¼ºl¨¶üÁ?Ò¢íš$‘dÜП×üjqæðÇúâªÞFÃß‚û¹È§a6J&c( OÓÞ¤-ù§¿æ>½sS¯Ì;p)ˆqú~•aÏ^JàAU3@2³ƒŽ 7 xV˜äœéÖšmHÉVüÅ2@g¨ãðÍßšO.UíŸÖ‚Ä}å  ŸOóíCF`óL%O4¢L S»ÂWîþ]ê*½¹XsúÔ2ÅŸ™y¢â+õZCÀö§Š6‚1LCw ö¥ÈÎ)Ê1‘ŒóHˆKjU¨ÑYGãUòsгÜëÞ˜˜†Øâ˜3|ÃV©’ +ÏjBL¯ F8©²¢šØUÏçQÜéõ ´[·”G)Bxj½šÇ䌟¼*äjF$lÜ÷¦™œ£ÕØ\†³~kYøíúŠÒÎjµä[ÓrýáC_BÌr@Êx4úʶ¹16Ö?)ý+I]X"šÔMX}1¾øúu4ýåü¿J 1Òßc…n ê9ü¿úÕbæ#ƒÌ Û‚3ôÇÓIoˉïb¨Ï¢Ã'(Xc¢’HÿëV<ÇIÍ"É#Œ3À Ö¦‹,Ç3å¸Éþu¥ °´`Y1Û /ó<þµxp6HØúê)¹Š‘hö‘cÝ뿟ӥ,’ÅìTàã€ùô$¹vÙßÏÍ€áœU«kXâùŽ äÿõéJ(.e`ÌOÓÿëÕųsÔþ ¼»ã§øÔ “Ð~¿ýzW—&–%\X{ÿ]Q›C™A1²7×"º@¯oóïHG?ýþ½b8©ì.¢Éh›ºò?J ÿ!ä`úp+¾?h·~_ãQù`ù Œÿ¼¬>`8F—pùÆáî3úÔE¹FDZ5Þ>•k'Þ¶‡ê#üUTŸÃV²P4mÛÿñTù‚ÇC)Á 1µu &{|ÅÝèëÈÿ?…eÇU'qáº`J–6Ìl‡­W©#}­ÏJÇÆ78ªõµô¶“ŒàÊö5J27åNšî­îc¹·Yc<ÈãƒéYìGäGû-Ò³tÍA¬åÃbnþµÐKåÝZ•-•uàæ¢Ö`fhòa¥\àùüêeݪÐ;Ú]óÁVÚßJѾO5 ãÛ•> f‹-cƒ'_aþsRãË!Ëœ¨ö#?˜¤-ÅÄÆ@H*˳ӃšÔòK`’õ?áüé6+)§Ä ôçüi²éŒI)’b nˆS©«ÿ:‘c@>èÇû¿ýjW ™²ydý4}‚Cÿ,_þø?á]vÕôëI•éòÑÌ; ÒçnOÿ|š‘t;¶è¡GûX®§ vÇáIæ¦q½1G3 ÖÚ §úË€¢ƒRþÃ8V'cýÒ2p}ˆ#jÓk¤å ÿîêp*'‘.£Æ@>¤d~´®Æ5ZT•ܧœ‚?úÙü «1ʤq×Ô_Ψ[ÈÐHa˜å?…‰ÿ?­[hUÈ=t9éþ~´c4„qUó,]O˜¿¯éþ"̯Âð}Á€ÇÖt…¸âQÔâÿë×3³‚:×|ÍÇ_Ö¹ÝbÁC›ˆ»ýà*âÄÑŽ½ºÖ†— M3—S€01šÊvãŽÿJеµ”ÀqÎF«d´t°F‘.0ôR?¥LqîøV[\6?xÀz€¿à?[Oîò¹‡ôŒÜQyçùiÃvÏOÔTËȰ3Uã‚8ðBóêG57\sÒ˜¬LÓ<ÏÒçÛò @Ë»¹BcUè˜úð©R½@ü?ýTÃ:§Z…ÀÇOóøÿõê•Ô­møÉ\ óíVd”òNOÐÖMÜ¢`ÌH1&q‘œš D¶r"yœ³Snï+…ÁcéY±ÜÈ#òà“’Ät£g<¶XžI¦RŽº“)8 Çs^Ù©= F¹gãµH{T–)¦žçž´ê‚Y{wÅH¸U\‘ùR qŠÏÏ9«p>åÁê(ûXõ8Ô×YH#Šš£,3Ûð4‰wBç®ÓÒªݹÎFSjÄu*ÅHÁÐ ¤¥£©ˆJ)J‘ÔRP0§ÆÛ1Çje(  ­&àÉ{–fÜ ß™®ŠËdp}þY®2ÓËûJy¬Bƒžk¬‚}È« 1QÀÚ0â¥SFˆ™»ßRýI¼‘è~•Y7ãæ#>ßäS™Â)fl(êN( ‘·Þf>Ù8ü†*?)Tª ÿºGô5¸’n!PWûíÓòëR`FéI>Šþ™ ,5¼Åþ ŒÈÿÝ?‚ý*Á|“ƒÓÒ›–ô?Ž®(¹yODû÷çFdî§QýXÏðóùÒís×çò i`ã‘L‘UЫ ƒVÖÙœò@Õ™æ©Éáô³JŸzè$,ÏåÇÉîOjT³Eùœïj9Ø:iô9fÐãþ‰úþ5 š$ªp‡>Çüšê敊F¥ßÐÇÖ t”/ÎÀg°àU)²4r§H¹S£óãQ¶pWòÍu&ÎGêí~Ÿ®)‡OÛéúU)àrÂÆ~èjhPÛ±/'±`Æ·L1ç >ã#úŠCkPGä?Ÿ0¹J]ÀŸ3pØç¯&¯Ãy‚zsügþµY­ 9ù>Ãü*µŒ†+ÿ¥dƤѡ$êÒ'Ì1×®}¿­)òØg#'¸æ²³•lpi$ÑðGÔrœÔ(Œ‡cdPJ¨ë¹ÂƒÓ­U[†P8ôÍMop\ž•;XWL”ZHs–úð§]ÍÈìÿÚ#?Æ?JŠG ÷[§qþ¥¨ô€NIŽ”É]ÂgP—tLn÷ÅCœ“øS°®_FGµT pÝÃü(10ÿY3cÓv?­-1©늈Éþ%üÅD°Åž¹ÿ?ZpŽ%ì?`W‚ä©ü3qE\܃¡üÿ^›¸zÓ$­¼zþt×n:Õ¢Êz‘øÔm7@>¢€+d‚y¥âœÖät¦…9ñLD±ÆÒ7 X[&ÉÉj4X× RS±›“*-Œ`ä’MJ Uõ©©¬vŒÓ&í‘2¢÷çêj»¿øš{d’I5Ž­"Ò"r~´Ì08§à’i $û¨HõÅ"®FŽIÆ9¤(ÊzgéVE„~Z œÀº~§ÿ­E…̈âºxð§‘ééWRâ77çÅgÉo* °˜ÿ Ÿî£ûS¸šC¯!ØÞb”öô¨b¸x›åb=ªâÚ\l*B€z‚úÕ^KS$&G·4‚è³ ú·ÁõgÍ+:¥b•e<Š–9^3qM18®‡uŒúþgüi*?$ÿ{ôáO ´u&°5È`ÕW¶1±x‰Ðúêæ(ÅTµÌlÒcs1ÉàçùV”rîPNG¶OõÅVcïSõµr9(eý(`9Y‰àÌÿõêA¿”ÒØô÷5—°E÷æàBË`ŸSþ7ùüÍdÉ­Ø®q1b?º„ÕI|EÿW·ûÄñ§f#¡Ýžô›€ãwášä¥ñ%Áû‰þÿ3ý*ŒºÝô™ÿHe‹òÿ*| w/2F2ìª=Y±üꜚ½„|¨Éé€sü«ƒyžC—rÄúšo9ëO.z]Ú^«"É ŒOþµsš¾…åîžÓ-tä‘ôõþu¯We#¡Šê´½T]§•)ÄÀsÏÞ;X)—­ ®Ÿ]ÑÔ¡º¶_÷ÕGê+˜éÖ©;ˆ·m•°ÇŠmT–ë€x¤±`¨Oš•®#\’ê?a¶ÇÝ?h[ܼ8‘þ¬×¿Œ}ܷኅ¯Üð@÷æ‹§|±È|ø˜0<6*̉mÏ,œ¯oéX]°bŽÇcðsÛÞ¯YKµÞ&èAïÞ¥¡šp¶–Šx'ÔÖ•¥È’Ò6Ï;pO½rÚÁy„y8QÏ=ÿÎ*Æ›{0·0Å’Û¸ gò¥`:g»Ž5ÜΠz’R—Y„±«HÞŠ?Ïò¨-´Ç–c-ßÌ;!=~µ§$K„UAè  Z@ÜjsÝÛÁï'ÿ_ü) ¦ ÿëoUG ÏÿZ´séü¿úõ$ôý?!@œËÞ»eýz[Ç~i_ýöoð¥—P‚1÷?‰þdUs#©1ÂÄzåþ4Y .p™+Ïlñþè®TD¦I1ÿkŒ÷1Æ0zOó&Œcž1þ}éØf¼óÛË2®ñ÷H9Á¦Û߃ÌNW¿?Ò²ü¼òIü©ÞZ‚€ê3ŒÑd¢ß ‡ËøçÖ™-ÈUË¿ÿª«$qmÝl ÿlÿCTæIU¿{’Þ´XDÒj2ôBÀ}ýu^K©¤yŒG~ŸáQ³œÏj«‚UÛ('¡9­ûFÿFýÑü«•™y÷"´í›e HP:“ÛëC4<ÄpÔ@¹eéó~_ãý)‰sKµ@"«O(•‰H£ûªÌÔŠÅ£|WøSþúãL7òg¢þµGÎ<ŽçM/ÜŸÐUj;"éÔ$ÿcò jt~UäôÑóŸAúšdjGpd~TâÄÿjœ Œœ÷ÍJdÀàæ™ j,‘‡!*ʾTBN@çÒ¯´îAùc¾ÿþµf—Jï×ÿZ5*…1Bœ±=‡•ŽÑâ/­„±”šyéHƒ ;~´€kŸâ£ ”§·)ô©"U ³ΫÄ#gš}!˜ªÏrªpò"}HÏüB×ó™ bÄA@ì^'oÞ8úñ@d?Ä¿˜¬ß¶3³ë·üÿ:xšáþè ÿüUK**Ì× íL3÷rßîô¨!“Ì8‘[Û8Çó« JÁ«ŒÖƒ3rN ÿõÓ„IŸÊ—€¾Â˜‹æ¿˜çåtT—ac_.<ž äšæâ0v÷oZ±Œœšç@ìWŽ˜w<Ò… Ï%RzÓ¥u ;*¨êMfI{$ïåÛ.÷ˆÇùþuI6DšE¹n.­ÍR–ûvpN=³þÒÂIp$óëþ?­(·–~_Æ{×ñïT’Fm¶S’â3×ùñ¨ ¨~èoóùÖÂY@ƒîŽíÍ)†ÿ,ÿÀªæ'‘˜o!+‚ñçúS…zVÄ–¸;SaõZ¡-£¦I\QTš!Å¢¯š}MqõÍ8ƧŒãØÓLAzà}ýtÉ"p¬s>‚›òóõ©0™ãŸ ©¸TÏùö4\v Ž‹øÿ‘Nó%=~DÕÔ°¹~BïƒS &V?;=ÿɥ̆¢Ì‡‘˜íÝ“è?§…JrÄçñ­eÑãñì(þȶC©ÿ RæCäf[Ϊ2HÇãUÍÁ'Žž™ÇòÅngÛ¡È… ÿw?ΕbDãhQì¸þT]‹0ƒJç! ü3þ4ï*èÿË&ÿ¾?úÕ»´v<}úô‹ØýøSæ)äÜ÷‰±þçÿZšQ×ï§è+hÚÜ•lö€‚ ®žgÍŸéG0¹L\qô4cý£øâ¶ÚVáÙ?ô¨N'ÓðþFŽd.VPÇB?/ð¥ÜHù“>ã?áSIa"ž>øÿPeOá銫ŠÄñNSŒ¾àÕ„¸»àûÖq,¿yä)D20>Ÿýji♩¼„TLûþ‚ª ÷HÀëVG¸¢÷LûF šŽ+I%åø_qW1Ì2•HwÜPK}ˆã¶Ž>Ù>õ?JnáK¸S#qsM$žsKרS€`SÏ,½ÏÖŸEQŠ) àrh In’}åõÇ5J[¹+È­žãò§R°Ôš6óHHëYCQžfÄq*çŽrÇúUÈ`‘†ë‡Éôÿ<:ÂÇA81 ßJ!ÆXÂàtÀì)’33mC€:škȈqœŸcI_©t®ÓŠ*ò>´ê`L4ø Ë‚ÿï6jAel8Çø¨?Ò™ ¤a[§A×ò«\Ÿ_óùÔ°µ=m¡ÿ¿kþÓ¥Ù¶°Ÿøÿ…Xú·òþTðxï@I±ïk?ìð¨›CÓÛ­²í•­^ÁRyiØcéÇò¢àb·‡4öäFãèçÿ¯Q7…ì‰ÈyGüé[Æ/FüÅ4« |ÿøÑv0…¡vyÔõéÃÏ ŠãæƒÈþF·÷×­?Zw`TD+lÀè{ƒ\ޱ§ [¢Tò§ž=«µ-ê¬ýNÕo,Ø _™HõúÓLÑÑ;í?•FI­xÙXmqÁÏÕ?/$ŒàŠÐD4T¯ˆ7;}GJ4ÀÅMÃ$ŠýÆ9(  d—Ì•Ÿ¦Mhè·" õÜp®6ÖRóÀÔˆÅ0à©ÏÒ“@wÁ³ô¡œ*î$I8RÚé%³K‚x#·R:£Ý¿™rJÇü1ëYŒdš‡˜ÞU¬fgõåçüšhÓ§¸æêr÷·çý+I#H*(U€Å>‹R+ xGÉÏ©äþµ?—‘ÔÓ‰½*£>;Ìÿõ¨¤Öi/$àúŠXô‘Õ܃ôÿëÖŠ C9õïþ4ý؃½+J=.9b[Û ÿ°-¢P@Eû “zûÓL¾ƒõ Ï–o&2J7#'íJQ$L2äzVgvhOË’9ÿõªŒ—&8|À¸ëÍ4"),aÉ9 zfª\ì€ÃÒ¦f¹’=îåû¨¤~ Í}¬w`Û<Õ!–`W g8Íè±›é’8¨¼±!ëN(îM0,,3œæœ¿òªâŒƒíNÜc_™óéž´•‚ã’3õ¥ ç5´òœF¸¸«O™±¾ãLÿõ¨°›H} 4œ{}jOì¥?zg?@)˦[ŽNöúÿõ…:+yè ¤N•9'’LmL®pO$ËÖ®‹h#éþ#üj›¨`_˜òz.2OçT¹®Cw)Š·#w*šd(ê­ÍÛO(ù0ªx犲§4‹JÁ'@*|a1è*2ê*ÓsùÐ1F0)$_jxRç âœì4ä Zs !±LCÇSùP½n:Ô2J…ÁÅü·ð V–écàe¦hÂñÉïúÒ´ƒv_J£–屜/|žjôqˆ×~4ß,·.sÇÿ­úRH/R?[vü¿¨XÜO\~8ÿ¹ oP=>•@ÍvIgÀ÷`?Æš“Ü¡ËDƒ?íÿõªe ›Ò©ÊÍmñžƒµIY'Q‘8òWðjgöÜjÛeRŸdàεV,ØÍAqp–é–äöQÞ ŽþT²H¤CP%s,ÙÛü#Í.Rœ´ÐˆG-ôål'ð€—øÕµSd+ÇBsù÷?JFÝ(ù‰Uþç<ýOzOºëÔ`vãL‹Ç_¼Û‰çœñùÒ»ª“Q´€© [>£µBÇœ/æ(¡#94ÆoòjF\}Gøš¦ÒI3mŒ3צJ¤‰l·-ÊFpÌôïQý¸ºác$÷-ÅIo¤æfǵiÅk*€^¦“’M˜ÂÆæç’ŠŠ{íÚ?Z³…æG-ì kî±úSd^ù'¶i96>D·+&™kü²Sþ÷5:Ĉ0ªö¦´ÎFV68ö?çùTçs€ª éÓüM!è¶,’>µÈŠ:€>¢£6ó>7Ëè9þ|~•D©p|Ãæ/«œãúSÐMk¥'je¢ŒÔrK*òË·ëÿë«ÿ(=J;âQŽ8¦‰–ˆµnÁÓÔŽµ)D<ˆ¬ÛIvK´“ƒÆ+CÌ ÆGÓ4šÔqÕ äF¹$`w©"t ¨d›Kª:ñTþB¾\ €8ÏCý(Jâm#BYÒ.¯ƒéÞ©IrÒ°€r{€M,e¾yXóØgùæ¯)HÆÕãØS½¶ YKìîÃ/pãÛhõ¦5œ„Þîí(Çè*ÛÝE ÁO 8ªr߯>VXöÊÿú5q;""³FpêJÆÎ”Æ‘9 äBÄZG»•ø-ì?úõu=3Wc6û žUXÉWbÙã úõKpêpWv{ùSJFz üª‘,jŒ>•$eI$öªÎØá{Pó ô÷¦ˆe¹%§f«ý©| ŸÒ¢¸m¤gj!"“¸¹ IhX7NzçVm‘˜ äö¼~aÏEÅ_š"O¢E%ȚΨ9 TrÌ"¬{TI¹Žç'4bS'ÊXœ/ëJ™#'Ù}*%>l›‰ù¦jÆh--6—4cF¢€íA–îsÉúš¶ ÷ÿõT6éû°Ý3È8éȬÒJÜÁþU@€¦w}ÁŸ~ßýz@8¨Å5€UÏéŠQ9$ša;ä<-Ò®£×þ™«Ðj6÷'JŒ}:ÈóYך sйG?ÂzŸÎ¹û‹iì¦òåR¬:8?Oþµ=É|Ž=꬗w6Ù-–?ï.AüGJçì5ÉmÈIÛÌORyø×G ÑÜIJDÊÈÂ1I« -õkIøó67£ñÿÖ«ÁÁê+ PÓ–`dPD‡©gükI¯,¤+æH‡Øâ®vyãõAÛ#ùWˆ/"#s¬ƒÑ¸?˜Åj[ø’ 0&CõÆáG+dä}áøö¨d@sëëIä ”taíŠVÆ>SúR—Õ@´ºÞT•““Z¤ÞEÏñí~ùükg]ˆËf[+Ï_ñ®S5¢Ô“D$ñ€$ÝÏçHRŽLoÓ¦?—øUXç‘8Àè5i.÷€$PG¨ÿ `1´÷?êÎïÏü?¥DläÆ=?Îjò¬rѶ}ˆùÓ]üµù²çüÿ¡  ð¦7Ž•;¨#*G–9\àþ¿ãQ9Ú¤uãŠè´Cfy;Õ°A=+cwÕÆé×’ZI½y†_Zè"¿ût‚ mÊÇÌ-¢eQŽE9¤çG©§Á‰öÝÈ\TœQqDU>õ-3 RÄg ¤¾cÓÒ£9'qìOjQ•“@4Æ&”š£w3 „ƒ#õ•”nÝäàtnjX¯dt ådàãùñL†‹ŽLoóþ5Z[¥A–$SŸëO’è$eœ2Œ{äkâáç|±ùGAØP $ójçlGhõÇ5EöòI$ž¤óšõÅI~i,Xah+DV˜Dä’O·< cò¨¯ý Ÿ”sšm£òWñ eй‘*ÉT ˯֭ Lc9"œwãùS][ø@?Vy¤VÚBƒÓ¿øÐ¶å ·cUÃ…Sü…Êï• ê?úô»2:ãðÍRšwo•x­2v”÷ÛëW…²–Éúÿ…Kò ù°;€F `TÉ‘AÉUüÉ¢(Ã|ÇŒv«CŸÿ] (If‰ódœžž´Â§cð©æ2JÄp«î9¨LKœ–úž(6LV}üXÃ÷éWß ?vý*T¼d84ŒE%HêUˆ=©•@,rcƒÐÔ4¹  ¥ÁPÃjLž™ëI@&•r’ÛˆÌÝF>X³úæµQ]€Å¸L÷!GøÖ‹x,‰#ás ?È[KªEÇ–Žçý˜Ïùý(0’Ô´ ‘º˜Ç¯ÿ…8Z熙ùì˜_ä3UMåäƒ÷V„g»“ýqJ-îæ?¿º*¿Üüäh‹B X›-³w¬“úÔŸj·^Ñ~*¬z}¼|ˆƒÔ¿5eT€(ö‰ãq3æ.?ÞQïã`|¬ÉÎ>\ÔÒÏ–˱#°=?.ŸÎ¢È=³@ÒB'sÉT_LÂôäšRj_£©ý(ÔdØuªpÉ* ¸,·J¹½ù4Ò â¥³h«)ÄlA ÓïõëNÖæ}ÀM¸‚1»­H#Uå°Ôð¦3AìOПëRi£ApyÊ€}@ÿ •!ó8@1ê1YUÏËþ½Zˆ\¢b41¯×oõ©±¢‘ª°1ÿŸZdÍ —8ÿ>ÕšòÌ>V”`ÄÿZ¡$,˜ÎFp8ëIDnie™®¥Øœ)è3üëFÖ8­×¦æîÔËKay?|õ?äÔâ3*äéš®£ÅÐ<&?1þ5(.yÏ_LÿWû(í#þ4$„çª÷ïý)›%’GI#L6Ö<š™pŸv0??ð¦&×LŒjq¤1wó‚1è{S^"y^¾”Ö`¿x¡"«›¶$¢(#Üò(HMiÝ” ?­1åì$çúÑìNFXõ8<ÿ*Ž{”„ ÙÉèæ˜½E‰Ê-úŽ•B錗’8â›utJ—û»zÖ£Šå {ú³u9æ­#6ï¡fÚÔHÃy÷ÀçõéZ ãëXrÜ»°IäçO’î@¹y[®?¤âØÔ’,_0–Al88#Šh–ÞÐdÿddÖS\‰ŸåW«1=}ºÑöˆãhÏÓU¨ç­ÍÔ&õkêÜŸÒ ye~$•¶p?* ÷nx}¿Æ«5ÿsóéMD—&Í&‘#êÀTMt?…Q“Éü*cÒ™7×mØ S<ù[øñô¨ <)Šã™ß,OÔÒ+“×­!äS æ€$ƒÖž$`AÐzô©cr® {b3R%€ ’¨¤Œ9Üy©–~ÄS¹›‹,TrH#BzÐ$R85JêMÏ€zSµ7ß!fäÓ¥“€£<õÇ¥UIJ7œX1-ÛÞ‘vÔ°’g¯~§Ú­UFI”% ’8>¸£{1êqøÐ˜œn_{µtdÓ|÷>ƒðªê0)ô\G]Æ:=©¬êƒ'èri UÉ<}i‘)ùÓøG ¬ €dåºv^ß­J8¤Æ;Ód“`ÇSØPHûxM9Þ›G簾Ùêh)Ž£¥-&AéÍ[ƒ¨`3‘DÁx†'ã©RÅRHÐÌ7 ƒÓ=­8ʨ€éþzRc3¿°lƒnXGгüé¢Ò{S˜‡È?„ÿü«dzRà.HS͈¸Ôr S¾ÒRæ2P0ÃùVÆÚ1ùQp<òûKžÉ‰Áhý@þuC$wýkÑîmeÁPAêà×9 +x>Vþééþ~µj}ÅcžŽg‰Ã#•aЊÞÓõŸ7OÞØÖ öÓ[9I”ý* Ä VàvW8šÙÓÕOzã"Êwgh=¿ýuÐéw4f) ,£ ó’+ùv\ºûЄìì>óóøÒ„„ô—_ÿ]SÍ.xªúÆÀ‚Ž¤Žœšd,¥%B3ü@VViÊÌŠO,F>Aܽˆ¨²zdÞž·. È÷¤gGä ¤zt câ` SÅiiןdºWÈô9¬¢ ¨"•=O &€=2³ tl« ƒO޵ÉézïÙTC6Lc¡äâº+}NÖç\ÈÄöÏ?—ZÉÅ¡—¨ì3ëMð>´ TŽ´ÖmÇØTŒi¦š_\ÔrHI4dòùk…váE%µ¸…K±Ý#òÍD’L²}ãÓØTÌM0Çÿ¯M Á-Ôþ•"©êÝ}=)N;æ€99¤yžr}B$`:Õû´Í ^™8ª[3Î1ô­…ó8÷ô¨]›×­Kåb‘co0ÐP'˜”äaP‰Éí'éÿ׫{}j6µCÐcô¦ Š9$p qÞ¬$­ áÃÿ¯UâI`?#dz05dJÄ`Æzá Nyü×''¨¹#­Ykxœä £ØÓM©à#“€ t ŒÈ@Q’~*qˆ¿+.Ð;­In†È‘@=Î8üé/f lBã-Ç”{³VݹRi‘¾Æv¡îÿ4Ë6!qò‘ÈëWÇJõ—§ð­˜4cÛŠ–¢£1.I99õ5&i­"ެ3éH ÎL@ã"œƒå¦JA”78úTÁwp) “m;W–úRÃ3 %$ŸJ™aDì),jpªúñ  jÞ¹¦Ëqü)–õ š«Ã[ò)û¸õúÿú¨I‘»IåË6jocŒäú £.£+ýÒ{u Fž#A»¯ÿ^¡’xXm ŸNk'2ÌÿÆíùšµ•È!Žýãý;ä9;×$޵B·Š|¤“ߌ Ê»·1HH)éíBc*ÑKŠJ`¥¤Å=cfè3@4Ç v2@Èïë]LS£;ˆÇe'ù W+ •Á!‚‘Ϛ؎yÕBË '¾y4\ÎQocU®‚UÁ÷2)VéJ†'éêk5flîH†½´éJ^Ryl~úÑqr3Gí'û£ó¦½Éæ!G¿œ0çi‘˜ŸáÞßÈT«cœ0~»yýi\j†êÿ-ðçùTmv™á¿à?ãRýŽLõÀþT«d3‚È>¤“K™©•M럻üH¨¼é2qÉëÏÿZ­L`ƒ#~öPÆ©M{åõB¾™üh½Æ¡`i嘿,ÔÑî;C½º/J¢—fV;þèíŒæµ ]©¹-ýÚL¤‰ÒÍHÎìöôëS¥¼iÒ4ðŸçPPïRIô$óR‰Œ¿,DÜž‚³fÊÄŒB€ öÿ×Ung0€ßnƒ9©Ë$JÌO¹9æ±æ›{4ÿêüé “°Ù¥`»w·¿j³§Aóy¬§hû¼u5JÌÒo»Û£¶è™ÔÆyªd-]Í$̇ƒ<ôýjpíÅAe,oTà¯Þ_CV±íY3x£ŽG^hÍ"ÈÚ0 d8=ýÿ _˜c8?JvMBîÀ’¸+ß-ÓéMj' À’¼…Ÿ*™àú,“GoÌÛ@÷?ãT.uUS²&ËtéYŽ­3n™‹çÿÕV£ÜÅÉ-‹wzÔjH‡æçï`V_ÛÁ“s©aøf’âÝ-F{UkD‘”¤ï© ÷ë"m ŒžsQ­ÙEÀœõ$ÕE¯àö >#NÄܰ׎ǷëL’vO¶*2 ¯â)”ì& 6:žOùÍ5äì¹úÓ3ß­%N¤£ 4 f€‡ëR»I´¯Ñp=MY[Ìÿ•mOZUaÒ¯ýŽ1×'ó©Þ 1´Sc˜¢¤Û“€9=©Œ¥zãó >JñÔr*ÔI°ã=ÿÇÿ¯U©`Ú³l~U¹_c@#Mv¸Ü¤ê9§dŽõSìÅN蜣ã¿ùüi<눾ò¬ƒØàÔ”^ E=_=EQKèXáŽÆî⮤„v  ÍC,AÇ"žô‚)•wcÊVHÕÐúŽ•Ï^xyÁÝjá‡÷Xóùô®Í¹VXGðñŸóôªLGh“ØÞ –6'0Æ~•GS?éÒŽŸ5wLv°ëùW­ª®©(QÇéZEÝ’ež´”â3H½qVOQÈÍ7S×Ô÷§ôâ“56Alÿ u¨Ý šˆ=iÛ·{Ž”“uõr;RG&Þ"§£Œ­ .ØêW0°Ã³ ãaæº ]N)€]Ûû§Ö°¡EŽ0£¥L0{Ò¥¡›»…šhBòd‘Ç5“ÓÅ»+èOJÙ‡kÆ r<ñS`Í×Úž"Ï^•&Õ‡åJàV-“…>Õ¡ÂeŽÐz2OëW$F…ŽqUÀi™ äýÕôÿëÐ39m™ËŽqÐóPO¦ç拃éšÓbUcŽ~ZqJw$æÞ9#l:àûŠnâ;WA«Åg6,Rîà¨G_Ê©1 Ü œõ\ÎÊä…^~¼Ôå7ž§h=9æ«NŠŒ1Ôô 2eaÀçéW#EQrO|VtR¬k‚õ$u«1ËÆàF߯üÿúÅ14[#šÈ¾?½` Nõ¥æ©w8ïíX÷!¹Ë;ÜZ‘9B**vp¸¤i–9©u·bûÇ^£5†+WOl*CŠLf›!aŒâ™äÔš“zúÑ»##¥Híª3Š™AUœÒã'4Œ{ `2@HÀ8ö¨Âíãôÿ“ëUî'òþUûÿÊ€,‰äþ]gÍtÒÂú °–“Nw>@=ÏSW!²Š.ª õ<ÿ:w*9ç …!OñŸýzЇJ‰d%¦p*öäL@>ä Pêz~††¤IíTP=£|±ÂÇ ÿ?J°¦S<0'ÛšC䞤óôÿõÕ=B!öv=Ç<Õ©/[j£ôù`\ûô« ¤ð€=(¹I¢Ó‘[æþ_áVV ` ŸoåV–#ÕºÓðvôûÖØó’ÞßçšbT(…#J«€9'°æ›†~Xí_@iÙ²\’ÜVqœ ÉïMçïû<` €; \ÕXÍͱñ,£vU•›#¶*¢òyéO/Ø~µ2.žd –À*RUP‚@\sô¬…™¼Ãå’1Æà*­ÝíÌD…èßÄG&£–æÊV4/u8­cÙ7c€×<óIq!y±<’j2Y›'9÷«V° _-÷G_z¤’w'´·,C0ùqš¼ ¦6ãûÔÓÍ©¤+ŠeyŸhÂŽ‡µ\öcz¬¬6ç=M2YLkyíõ¥b“³}s¹¼ HUå¾µG{íè‹éÐÒ;œpIf9<Õ›xÂ(½=»²Í¼[Ø"ãÿ­ZyTN¸P>¸¨-”$[ŽrÜþˇ2‘½y¨z³E¢*œNÒFÛrzð«ÐßðˆAõúTÐØÄ€d>ùÅXðþ­@ÿtT¶‡±‹qŽN}Å6[˜a\É"¨'õ4II!@îsŒ~F°înWÌ% T^™bP•Êrh×–î$„HçƒÐcÖ5æ£5ÎcˆíC×°üê“N&±nfëHŘñò¯åšÑFÆr›ew,¯ŒäƒÔTÉ>å õIïL•w Ž¢«ä©ÈëVd9¤`ù,NsQÊn:”®Aù‡CúSIÜœõ_å@†QIE1V ÓˆÈéQÓ† ‘ß­ZP3JˆÎÁTrkNÚÉcŸ¿•nÅX,Z\ùWéÍhEk]'ßš‘c\œïTf¿'ˆÇâE;w"í—ÕNõ5Yïc2ÕAäf9bI¦h‰eîÙº)­!c‚zT Ö¥AžqA@IëÒ1‡F?ÀT$“H þÓ èíùÔ¢y6çwçU?¿áZtrOâk ”5°Ug2/ÝïïO–Bçü£­FH„!ÀcŠ:TEØýÕüy¤eleØþ}0$.½¿0“žæ ÞdlF7{ö™þ•:@«ËÍèÅ ù¾ï?Jdªû!‡"­Aèh†è:)#“Ôdgüþa]\|£Žøæ©ÀHñ?̤äS˜ÈåÇŸþ¿ëHbË r®l•BX.m²ÖòÝoðïW<í§)SëœÏüj@r:ŒP}eC¸SžÝkU'Y2Ê{ÖmæŸÊ>WõÅb‰n´É°rèiîVdÛÁéíA!‡PAôïYözŒwk´üÝO¥LÁãË),=þ£ÿ¯JÀ( “èk…Õ¤ó5)› Î+·3£©†ˆçð®é·ÝH{n5p¦èÃzpj0jÌ©ïÒ£–=­ü«BHÅIéLÒžö ô5aáÆõÇ]AÚsëW¡ÿT¦Í/NGµEŒV« 8#Ÿ\TR[úžôŸšš)67µ+Zºä’§èj ¾ÔÀÔŽrî>µ:JXeAÇ©5™ `v‘ŸNõi]ÀÀÚìXž¥•C ö5,RGë@'dœt<ÕÛÌxÍGqó*°äg­IiÎTò""ÐÝ&2J§§­?Î|§‘Û8¡Q˜‚§Ö™q3ôÒ'Úœ ã>•2•‘r9µŸLò¯jÒDظÉüM&ÁïH!‰9 úãüh’Uˆ|Ä}=j¿ïîx$~§<ÿ:k¨ã;ToAŠŒ%ÄÜ»yh„u«[$]9oSÖ¥ jU[8GP[ëN6ð¨ÉSøqVi­"¨äÐ!¦8Ùw¸皌ɸb0éžÔ¾Xò ¦?Oñ©‚àPS–ÁoËôÿs0zTŒÓÎ=ê5EžO© ¤?g­8¤ ­Ðô÷¥¤1°:gØS0Í÷ŽÑè¿ãOÎ)¹«HÉɈª©Â€>”ìÒQLh¤£é@ ¦9Æ~Q×Þ‰dò×åêxàR«(Ž?†nŒëØv•>édÜjÜòùÐR@››v8SªÙ9ùGÔÕØcò£ Rô¤÷íHdlå[îœR4Sp9§Já&³Ã“=ºš_I0ñPË&ç'<-DfÛÜç úÓY¸ ?|yw$ÕèÁwTþñüª¤# õ­ 34˜àp?Î)2¢‹ËÇ,?¥;ÆP€3ž´Ö”"nÿ>”Zƒ;4Ï÷G+3Rs;ŸõJ¹?ç4™¸êÌÑqR—P:þµ›¨ê ùq°Þzò8¤µ)èŠ÷÷ŽçÉW%ˆç­dLLŠvŸ‘}ií6F“Ÿ¼}}ºÔm)ÚFÒ¨ªFw+îaßõ©’l­×Ö n¾”ÜÕ\ªò.ÒqÒ…¸`0Ù#ùSò²/ Çni[$SrEHé´öÇÖ£5BŠ( AOLæ˜*Õšƒ0'^NhJÚ…2GÌzÒÏp±¼ö —Yâ,žÙ¦¥Œ²¶çã>¿ç4ÌíÕ•e™¤mÌ ¯jÙM…@Ý–?\TÂ+xÿ‚1õù»6nŠOÐT‹gpý"oÊ·ÖE<(oÁHÒ”ƒž4ÅÎÌUÓçþàR)ãNŸmÿZ¶Jš0=h31™7\©?ZaÓç?C[Œ@ úSC8*EÌÌ´™~ômùS<¶t›¦´JÿyAú€h™Îô4àäq𨒯è˜ÿwŠ©&œÃ;>Æ‘JHª7^´åR¼©ãÒ‘­äN¨~ f–1(<+}0h*㤠Ҥœàž=sSc#ç\CIä+”~š@FèHÊÔG=êìpIŽTéCY¾rª>ƒÿ×LW)·Üü*JÝ‚Öù‘¦áÁÈ'Øm›þYÇø W*ƶ"ažiœûÖÔšT-÷¯°9ªÒi/(èÞƒ¡¡4+3¼(Ï|vÿõTJIÚ§ëD³``OJ„u$“Þ±±LPcži b¢.Ò±óïLBÉ0N÷4Ô·’l4„ííŸð«[,csœµ=ߌ>”Ð|«Ó¿'üi(£4€Zc± ª;õúR““ÐTVÇÌ7÷ާJ`$åJ²`ã¡«k# å¶Ðÿ#Unc>ÕfÊ@öàœsIŒ•YdÈ÷qþ~”ϳmæ7+ì:óøTŒ¡±:{Sv²ýÓŸcþ  yæ™=¼wpÒ§ Áê=iHÏ8ÇҘ唶‡NcÏÊˑʴ´íedÛäìØëZ²Â²¡WPÊ}ErÚ–œön]yˆž§Ö©;ÓLëöyS^}p¿éçæ5·© ²–bNÓµ‰ÏáX%ÛÍ,}yqV$TF,ãÞ¬0 €Ø-î* ûP=óMY9úUòXR¡rp9üjUEr0{Ô‹@@ Wå‚y§ç ÐR6Wû½é‰•ê:Ð…ËFH= $q“†ÏçÍŒPZ™ˆÀÇ€$6p)6!l‚xÍXò³ß¥(Ø¿wö敼¹G'>•iv‚Pîx¦Ž:çLóq œöÍ ,1lc}y5BM²ÈxÅÇ43–êI¦c¸ëô§` aЃúRÅ!…È`vž§ÆüáÏ­HP0Á†( ÆÙSŒt­ }jx™~cÇ_ñªƪN8ãÖ¡ãšVÙØê‘Θ‘•dÏÐVÈy8ÍqIÏ^kwI¹vvW“ä О•-ªq猀šl³,Q–n™I°@’+öûÏr þì8¤Þ\™œÈç :Ú.ßÏœÏ'ÝSÆ{š¤[Ï”($F¼œúëE/1„:ÿ‡øÕØ2* çZï(&³¾Ñ期¥.åô‹Ox • Aõª—†|. ô§œ7çð¦ÜZù–Å1óuÆšv1Q Ý©§!óÞ¤FÛòŸ×µ=ÓxÏz¡: ’4!Ô`úÕT;sžzzK´qÈÏzNÃå*Ý=©Ö+ûâíÒ…ep©YñÛ€ÓœÕtr?úõ:0aÅ!˜ûÎBqîyý)Å‚Œö]®ÿÕ®F~÷Zz@ŠrrÍžüÓšXÔò@ú?­SsŸõ’dzgú ‹Í]ÛcBIôãÿ¯E„_3®>ðüé ëœO  BÇØe&¦UTô¦&ÀÑG·'ÿ­N ˆw½êzþµ×qEÁ9>ƒšÏ–þG?)Ú=ºÑ`5žâ8Ç$(úÕwÔ#Îý*„6ÒÜÝþ&ÿ<ÖŒqŃŒ·©h„"ª}y£lË>>•7N´Æ4>AÜ žA=jx¤YÊÜÿu† U|;ç#;Ž3ü©›£Ü[zçè¼~B‘HР㑃Mö¨c½Œ€Œù÷ þ¼T$cî°?Ji‘(õÉèÄTfVC‡ˆ§fàŒÅQ#Ãdd`Òõ¨ùchô÷§xó¼zõÅ&Tc¨ã9Ÿ~iž` µÁÞ¥WV}h’4• °È©4 ku9Á?9SjUØËjØ?2‡š³« ã­ç­4œ)Î?‹°ëPÉ öµT»—-´vëP!À'Öšì]Ï^Mä…)€ðÛ¹ÉÀéJ¹fúÓ ©bàg’O@(Vl ½m*Cn77$äriö:ÝÞEò£=Û šé-t[kp 1Çñ??¥e)£hBOS› w¨0Ã'–;íêkNÞÂób©dŒÄ’!þ5¿€£ÈéÒ¡dÎN[þú?Ë¥G;fª·3þÂÙýåÌŸEãùäÔ¢Ú¶Ii=ÙÿúÕ£$lW øÿ?ˆý*›ÅtÚãþùÿâHþT/QI.Æ|ºfÂv±eù4Ág场+ö¿úøþueåºïF{ ÿ1LKà…~ßáZjeî’Çeg/XÆ{€ÃÿЧ¶‰bãæ„þcü([˜äà9'ÝøÓ¾ÚÑœó1ÆAÁýj]Ë\¤ áË&û¡×éðª²øiå”ĽŸèkHjqs.?ØÏò§ NÙ¿å®ß÷Ôæ(¼ƒ– æçÐïcÉ ²f?ãYòØM߉—üûšíÖê>äѶÚ¤dz§ÔäK¦º3ÏZ&^ÇçÞ›´úì¯7È7¸à…ÉþUR=$–KO¦Þ?Nµ¢‘“Žr(%”üˆÍôz2BG™…ÇS[ßgÚ0@Øÿõ*kFI<_þµ5©DVŠˆ|ª3Üõ5 Waòô©‚¨ëÉ÷§nª2l¯öbOÎÿ…=a{gñ©9=ñþ~´…3ügó?ã@`('yc¹ÏÔ“ýi»S=?A@†—\ýáùŠMÀôþ¥íš(O§òÿ\·Ö’}iŒp2Oç@€ý)*§SøRìó=‡ó§¤Hgé@¼âÝŸÂÞž‘ÇüŠ»šBhÊžT§’Ÿ¥4Û–;Y>˜Ïó53í…üêE]‹Ö€½ˆ’Ö4ßËúT†!Û"ŒÒP+1‘Üá×4ê)…ƆÏzvO­!En¢›µ‡CŸcHwAÉx£M Î:B)Ù÷§a]£oœîcóNÞÔ“Ò˜Îdð)"ŒÜ|Ç!ãÞ¹ÎQZàü¼'¯­[DHW ÔÒ´tâ£v%”Êîw`޽Å,erTô4†’º¤©†³eˆÛÉ•Æ;*á +ÊúzPûgˆŒþ4Ènƒ†¨nÐìf_ƪ墌àƒÚ¯“EƒÜ`â€3=ú pR¹«kfÌáPcÀ šè´¿"šèn=BGãJSQ*0rvF.›¢\ß°m»"Ï.ܺË-ÖĨÇñ0ɫꊊ€ cò¬íJþ{8‹%«¸Åž?OëX99Q§+³@Ȩzõâ ³I剢/ýÐêOó®÷W¼»,²JU;¢ð?* \ƒžõ~Ϲ·cÒÌê}êµËJÃ1ìÃ9®WN×%ÎK'@Ç’õ®’)ÒT¬=©q±j|Ȩ×3ƒ‡ŽÝ?©©q(ã¨ê)÷1n]ê9p:ÕŒ†S†ê‘›ºz–Ù aéŽj»¨n8#ÐóŠ–9Dª@àŽS%<޹ªDÉu+5ºžƒÔR$– [#ݪzPpi™Ü ²°Ã#ÝWü*°N¨0}‰_ä¥N.H#'žÜõ©C)êqìx©ÔÑ$Ìãhz!Ä7þËRÛĈÀ†n?„Æùb®‘ŸÿU1”‘Šw¸­mI„‘°Á'#±?ãP¼¨¤íÚßðþ•øS«ŒŒª0wgëÓÒ›ÍU˜½D¥¢“4\ÒqHM1ƒ0àâ€$Ü óùÑæ«˜ŸûÿÏüi¿gÏWý?úô ±æ+´¤ÔH9îhfÛÉ 3â…RycôØ×ø~ƒÒ¥ M€¥¤¢˜‚™!íëNf 2x¨”’Ký½)‰ =M!9¤ëÖŠQšLŠ`-™¤Ü=h £4ÍãÔ~toÛi61ïM ·<ä}i¤äýï“8ïH,lÛÄnO˜ùÙØtÏÿZ´Bãµ25Ú£8ü:T€ƒÅ`n#p59&¦”ð*hRM&²5mL@†›÷‡©ô%qjú™f6ðžÞ>þ•›ej×wIþ'Ú«d“Ü“]n‡`-mÄŒ¤ÊýªÞˆ HÕ-¡TPQПçHwÈ3Œ/¯52EÑ›’9ßçÖ‰mÀïéYÜef ÎGòu/@Oó­&¬«¶ò¯b|ztª´¬z_¥G´•àûsü¨ó~ç¥HÇ2§µ0Èðœ_P2“ð£üô B¤‰ ÊG¨Å)8<~<ÕI!do6ñ(èÕ,S¬£ àÓ°ÇË-¾•ÆæºÙd ìk’T-Ó§­\ b“Å.À£.qzs²@¹cYóÜ™pÀŠ¡XžK #ÅG³3zúÕ\±8ïVãëš`L) úúÐzS C£WÖžA"‘NE.?¥BÀœZR¡—ÿ•*¨ÎÓÁü³@…Õø$.98ëHdÈ¢ädzÓüèGñÖ«²';r_ëL0Éê?:[ûDXûãõÿ ±¯æ?úÕDÆAåÇëL`‰Õ‰oLPùTÈ##ó¨ÇNµDL@ÂŒ¯ù`JC§€hgBü玵ž5vWf‰‰ôª]MkDD™ùGQMh>Lýi‰á@ÛÛ¸â%Ç8• #i#dlŽ8©lð¨N:š­rXaIÎÏ¥3Ë8Ói›ˆÔá˜T‘ÞÁŸ¿“é·ÿ­Ua´DûÃqúqVB(íÒXœÝÅêï“þ¢êïÇŠ¬Ïu<úP ÛŽ3ë@rši$rœSSS«Ü}k9Ó>ø©’R1ŒãÒ‚\K›·Ö«][Çr„89ìGQRîª ‡;v/Þ4·1Êí&&ç=꣦Â};Ò½b+÷^*«"çA¡xÜ:÷©ƒï U¹Vþ”¨û[®E0.<…pçYùlšU>vy'¯µMöGÝ€íšC}ãs .}jêƒ ôÿõÓ”{@'µMºF>ZBËg<“ùT«Qßó4„z>˜¨Ú ßy˜ýq@4ˆ¿Æ?:…ØHWÇû´ÆŠ1ëþ …š%åXäz˜D©ÈçÜÔr]Ï\ŸAŠ‚K“å>÷¨ªeùÍ4øîV98Ç¡©•ðA'¿¥Qeaò ÜÕĶQó9,}ÏÜGýáøsM3à/~ù©Ò%þ}?úÔçùp?+‚½•¿ïœR|Ç¢þýj›'°ÅÖ€#X›$TRº!Æri×3ìàc=ª‰lå‰ÏÖ‚‰dœšj’>lóëéP¯ÌÛŽxéOÍ0/£$©ÛééR[Å I‚sÁÎ*ŒRlt¯5lç‚à9ÍKv,=µ«ªŸ]ÿãY²¡ŠB¡£)àÕ½ÇÖ YÔc9Í 5Â;ŽŠÿ)Ìo‘÷OQèjº‚ËÏùÓÕ²»Nqï@\mÚ|ÛÔu늷¤é×L@R±ç–#VôÍ.[â ‚#N:×S QÛF#‰z qÿÖÿëVr´Fôésjö!±Ó ³N.z±ÆúÕ¢1Ò  +ã£üÿžõ*Æœ’kêuÅ%¢qMd  úÓ³I¼‡õ¤Q‹¨xrÒë/ò¤ÿg€k—¾ÒÄ“"¹QÝ@Åzàj9bŽe*àxÁ«ŒÚ1(½Qæ;“TãÜŸéWì5 -_¹CÔV¶¯áÀ¥¦´îSƒùW6WkÃqÈК’9šqgg é,aÑR=ª«¨Žb ü§‘XÖ7æÛ†ÜTöäÕ™5,ªÃ<1Š\¥9¦‹9†P᱑ÓÖ”] 9\ΩË9˜Œ'u$TAžCˆÊõäM".\{´SŽ õÍ7÷²à–ù{óÅ$P„ “ëÍ,’¢N ö§p°ÿ)±òÓýjXæ*DRd?Ѐj1oq/Ì_B@ÿ ­4`9vY}õ©hÃcLI´ã8ö8ÿëSÊ/Þ*?àB²’GŒmW$~5&íØ,Ì}=(±\Åóv‡ÓþúãQùÊßýj¤dUÿë“M7*;Фg$™p£5š×~‡üþT϶7f?çéTfáØÕÍ4Þ³ÒyØd7äHndN¡4\—åOí­Ü'æi è# žàÓ™v’¨4éõ­ùš‡í?ëX~t\|¦™8¦¢—;J© 4ÇýaezÕ’áéúQpåeFj«LÇ¡À¨ÚLò[Rh¹<¥Ã"Žãó¦…§ðªfAÛŸóïL-!”}3þ\j%¿7Í=0£·­+L«Ôõ5@ƒüNOãÅ'È: .W)m®Ðt9ú ‰¯e'òÿëÕvl”Òh¸r¢W¾ZˆßËØ¨üA#ôÍEš.;"Ë^M×pü¦ý²cügüþåE6سö¹¿¾:As+o?—ÿZ«TŸqp>ñ¤&727Òž—®'?_þµC ãv0¾µ^OZÄšd‰{8©u¼ÙÜŽÖ‚ö§†ò³œ“Ö´[ÇË;JÙ&£Æzše?#µP‚N?J™kŠ„)ŒÔÜc?‘ DÊ}:zS÷SËnjÄOÝàP!ѾXƒøTÛsP$,HÇ_ZºRFàéòo œqR u œñ×'Ê=?JC*(cßhöáNócA´¾~¦¥p‡øÏçUd…[î䟭rC,'€ËøUvÊ1þbšÐÌ3ÛƒUCsžhg‰“¶G¨æˆÛñDR¹<ò=iÎp `÷j†nï s9*E2e'³@£™$!ùÉSíN™4¾\E»öúÐ"„‘o‚…ã'Ö™µ”á†jxXùêy§õ¦$‰Ã ìGZIgv¨ç½"Eƒ¸qOMŽÄ`ïŠV ª¼œðûG­H#ãÇ•.ÔF~‚˜é“¹zzVÄŒÅXƒŽôòì¤.9?Oñ¨†3òŸËüûU¨@U,{÷ö .69GUÇ~*ue†Iª’2Ògásÿ×ü)¥d nœö'õÿ a׿<~Zõ<þ’³lmޏ#ÛùÖ¼p€¹#Õk»A"—žÔ&l‡'#}ÜÓJ•bäqJÜ(P4÷ÄøÏbµCc©Å`#`GPx­XÄÍŒÈ}ð!—<×֤ ªé÷‰÷©†ÀéHg 2N*´“±û¼_óÅ,›ØäcÍU‘å^Ø Pç',Ùüjzk3¤Ÿ©¥Cm,}0FòEõ5f+Xã ·$ôÏùþ”«ž¡}ûþ˜§¬%ån¹f8óì=ê` cÚ¢YQ‘ϵ”ðÅNAçÖ ÆrJu1K>½~´ª~m§–ÊŒžõsOÒÍܹ`D`òi-mMĘ裩®‚)c·Û.uÇj‰K¢5§ êö,«Åm‰Q‚…â¥K˜Î@úãühR޽˜_!=àÇük¨”Œƒ‘íŠBØæšEÚ½+'QÔ~VŠç£0íô¤£vS’Jãu-\D|¸H-ПJ4ýHÜ&Çaæ/ê+ŸŸ9ÝœŽ”[Ü43+©èk~Ec“Ú¾c®2ç¡9ö4«+‘G>¼ÿ,Uœ2îÏéJd9²îî§¡¬ùM¹Ë¦sŒ2çèýUÏêö)q™£P®:ÐÖ‹L¨¸fÿǺÕ)¯Tœ œðj¢šØ‰´Ö§6Êû¸ÇµORî{“ÿש.­Ø¾í¥U9íPåÇÓ¹èOáZîs’<…ŽÈÏËÐã5b<"ÐUFhãÆ@õT~t³‘£ëŽ(m®ZWÛŒ±=p*ÜÅj<ɘ4§Ü~™¦D‘YÁ¹Ødõcžj½’RÝžã¯éKr¶Õ“\Ý»ªÛW¸­gµÖŸ­Y[ d;¥p?_óùÔɧB½rߎ*•ˆr¹šn$=0? rGs7Er>œVºÃ}ÔP}†M; €3êqORy—S:=.áú•_«†jÊhêÏ&}€ÿ?•]I[¡éA}Ý:RÔ9¢•Ê£O·_ï“õÒ”YC»„>¹5b—¡éNÄ©]‘ H€áqøâ£kØîÜÃõ«©Û…-Jm=Š_a_ùèߦ9Oüµ?÷Ïÿ^®»Œdœ{æª=ê»Ï¹àP ßM=¥_Åj³Y•}¾b\gй¾IÀ;†Üöâšð¿|§\v<¸T/Ýýèi 8ãòÿ®ÓCp ÷Ç_ÄÔ |z"õ§a—~cÔãéLgŠ3–aŸsÏøÖkÜÊý\ãÐqQ)‹CEï£Sò‚}ê½cÑ@üj¦hÍ-$²;rxö§‰>r§‘ëPÁКüÔ`TŠŽ7þøSQû˜ÃkqøP¶UŽ:iÁöö©F%_ö‡Z`‰à~tE¡à6ŒòzU˜¬Øœ¹ÚµamQÆK{÷ W)Åsœp;ÕÈ­QynOù÷©•UW À¦O&ÄÆy=( ‘;4²yIÂç“V€Ú¸¢¶@ªXõ51 LÅŠB¤zWe¦ê+wi¹ˆóa«‡¯XÞ5´ÙÉØÜ0ÉéRÕÍ´ÉÕÔšç5Kï´ËµOȽ*KíUZ/*'wÞ?Ò²4²A–c€z°®ZÓìÚúíb^«A]Õ´)m¢ *Œ ¥¤iËalæFå_vÏ·ZÎNì¤+>zS3IE! IE#f˜jޤ…ìß‘ƒWI5 ß4Lph®™![Qìpy­5*ë‚Ö²´¯¾ËíZ@Ï)1‹†ƒ”£î¾ŸJ™Y]C/ ÐŒT2ÄÑ,ýåìhb2:T,Û0’Fÿ’9¨zƒC®àTô=±@‰[7ëŒpŸÔÖ&kwÄÑï#ÛýÏËšÃUà’¨­£±"jÜ0£ '5ÙäþH>™«0G*ŒBûÓ­m•*¯"|µ¤ƒ‘P^ Ú¬:ç*&K:šÒ^j­´Æ ¶(4¡ˆî:Ji|P"]í¼i2Xg?©¨”™I&ÇÛÔP;b©¨^dóQ|îpM8Z³}ãÖ€± Ò–)ÛÍWÁcÖ­É)ÛÏóH!\Œf( Ÿ\p)Œ 9<ŸÎ®ž7$ô¨fvóàŒ÷ÿ:@@)Ë’zæÀõ\{Š1Ç#Ö˜Í+{°ß#ü¦‹™w:§çU¡A"ÝAãÖ¤µEy›=½h0`{Š™c&”Û£}}EYÆÙÿ>ô$²m\ɨʶE:P#9bN}é6®J[ ² æ›Å@­´ðj7’@'4†Y*3€GµZ+‘°ghâ¡UùN:w§®bI÷Çõ©Ä!ÆqØ`#M;ЧûÄ.)JÈã¶ÄšrG±víŸZÃý*@¼|ß•8Ф‰o±“¨Ùƒû䎢²Y² è(Îöÿ­s’cyÛО)Ž kZH ¡ãž cÖ…žBè* 6§Z¦¹'Š”ªl›ŸðªÎûÎ}©íºFÆxö§ª„ZdæOÈJ-£püiFOOåÿÖ§ìîOò D~V>ëÄ F`§æ TrÜ…8æ>§¥DYäuãÓÂÄ­:ÏÐTrÜ2(Æ=;Ó<ÈÐdã°¨öI3îØqý( mÁeó\’ONj@C6ãÐt£¨P¤?úô“n …úœS'©^æC+c? ýj«œ •ƒ/j"ØÿõÐQ ©P€zSs¹¹Š^IÅ,lô"ÓÐŽ:*Pèh$ˆ¡A ñi™ÃÁëÆ}*Î §§¨­&’å²§ -&ìTUÌëFÉeãÔE[HâUDÎìöíïV$ÓåQ¹–Qœdþ5¥in,­ËɃ#uçô©r)AÜ•ZB"Rr{‘úу뚅¤,I=sMó<ôú ƒSBÚäÄÁéZ`ç§5Îy„ðØÇÐhi³oÊ–;WŒqQ(šÂ] ']ÊT÷ïX³éJI1–Sÿ}ñ­œT/wÂLŠASÔmTÇ} ©kjs×V“D8ܾ£·øV~qÅoÍv’++Œ‚ëþ"±]Tn±üë¢7êrJÝ ¶Ä……”’:µ IÎT·áÏôÍs™ÁàÖÅœë,X8Þ9â“EEô&+žvg=È?ÔÔEÂE'²ü¿Ó5+F˜û«ÏûTß•Ò̽B@JäyªrK±¨Éúô­ »ÍËp?¼H ȧb7/À£5Rnr"¤'j“LV$Ͻ< %*sš¶ŒCh‰sKšfi Q’@›*1$ȦI:§¹ª²\3p¼Z®_–8÷5%¤‘,²—–?J£»žOãN–aŒúÿõè†ooº;úѰɠ¸#`Ä ÷棚õÊ‹ƒž9â£sà0úuÿ¦P8$ÿŸÂ€*±9ç­0š¥ ×õ¨$ã¥Pƒ47Þ¤¥o¼h(¢Š³Üüi­÷©b`Kå´’aF~”Ú2XÔô«QÙ3!Ú¤&qÆ3èÍUŽ'  õ5v0ŠB¨Ë:¬’<Ò’xïŠÐET\øúÐKR9cÏ ÿ<ÐÍÈúÑ$’i‹¹'“É CɪR9–l™À©§“dgO ¶—>” Ôa@¦;vM ÁFM5IêǓӚbF`€)ýãmö©#xÇÈ~¾•WviA¤YÑE§Å"†+)Bçÿf«v–P[N%Æâ:qÓõ5ÏZßI[åþïjׇV†$ÂÇÊ¡¦4n‹µ*‘ùS…ÂƳ£¸ŽAòº·û§5(p{þµ6xL‡×ñàê{Š¥»ü⦅†à3H 4´ÑK@„#5^\ò½ªÉªò÷ÍPÒÿãäöMm`‚+KÿÌ{Ûx¤ÆEƒuÎju`Ã4mc­4  ò)µ˜¸ÄÝG§éRïR ƒ‘ëS2¬‘àŒƒY·Köd˜7î€åOJkPg9¯±¸Ô §%¬b8#fêS,…ùËØô¦nÞ¸lnìkDHC;Eî= hÇ"È»—ùVGCÓô䙣9_åT#b«Ü6⩞& [ïï/4ï0Î9ÏJž1„¤ÕÁA,Jh\ñC¥(‘ËaO|P4LQ~RI=j?³©bÌIÍY *ŽrqÏÖ˜M T`ŠBÀcÊz)üi£ëLzÒm$Ç ¤ “ÇO_Jy.(‡ ½ø¨.lœüËÏÒ´"M«œrE?®s¤Úœ‡¾½ëb{4›'[ÔV|–Ï ä¡Å2“Â% ©äúÓRb“–\ŸÒ®EƒÈ“Ôf¨9Q3£< C6 ¹YF7Øéš•ÊpJÄY2Ò¬ÇvÀaŽà?JdØ’y7pÀž¦G&ÃÉÊÔ G0ܤÞžŠW‚( ˆ@ÆG+ڀ݀9éRp=1P°ØëŽ™¤4Xˆ?˜0F=8«[7sƒßsUa‘6æ÷5`Ï®L‰¨¢Âm“€)Ù³$ÕxA“êxªRêS?FÚ=©Ù Í›r\G˰_©¬ëX ˆ†O©¬–wsÉ$š…å¿*‘#Êò’ò7ëQ3ôì)ËSh(z °§d™SŸ^õB%eÃö¯o´&GCH À*‘qVã çú@g~ñ¸ #NJÇ‘­_Å‹Œ†8„c“ëC¸^?­8¾: ûT,:—={PI ¶ãò¯> cõÍGä’rçüþ4ùn!Ï‚¨½Ô¸Qê:Ó K E$îMN¤qY¶Ñï}ìzs“Z àdÓ!’¨Ï¥8ŠE?(â‚qÖ™#AŒÖmË"¶Ô5nâáQN0MfXž¤õ¤\P€g¥H£iãB€JzrÃ43ÈëH®ÈpsÇcS¨Ýyç9 ”ÉR@yéúVì–çÜÁÍdªsVí-ÌóªqŒóô¤ÊM§¡ÑZÊŒ¦y>]Ç€F1ô© ‚BrßCPFªNÐUã­HÇ+#¡=ÕÙKýóŸÂ¦1Ú‹ƒ*z ±m‹æÈ9ô5(G@)XÅÃð~”®RM‘´Ý‡õ]Ü“ÎsVÌ8Æå?γgEB±+î)\§’@£'Š©$ųƒHQ‰ãšzEƒ÷I?ÊÂÃQIÃ>vw÷¥šà¸Ú0¨:-Mä+ríÏÓüi†Á'?—õ®; ÷¦øÕÖ·³gÛüš­ +‘·¥4É"'?Ze)¢˜.2=Å%(à‚()iê¢C…ûÝÅ[[T…wMƒí@¢˜ƒƒŽõhܤcjŒûv¨%¸/ò¨Ú¢ a®e~Œ@ô^?•,1™[,NÞõ ©fIÅjE –Èm FH•vª§ÉtÃÖ§bIÚÖ2Ô»ŸhÎOnE>ÕÒr}(¾LÔÇÐK¦ù•}¨ Z¯9ÌÇØUˆ豜 šAД~õóü#õ©€äžÂaTÇ ¥rceI;¡#ô«ù¨° j TçÚ¦¦>6šÍÓ ƒèks†"±4î/F=ër†2-Í a²S±î*MÃnî1Œæ••] 0kKƒo3ÛHÇcwíùÿŸÆ„®€¼ÌràÓ5—â èÖØÛ+”HàUMRá­â]¿yÎ9ô¬™î$îÌÇó5J=Er{;6¼ºÇ;Þ9©/ôÓjC.Lg¾zÞ±¶¶¢2>cË{š’XÖxž6<ã¾)ÜG+uoþ㣠­Q®¥í Ó¼–…®YÆÖ#¸â©f§ŒäœàT(»ÉëV6È4ÄÙv3ò¥Æ€E(íómÇ>„f³ùŽzæµÑ±g¸ô MSH”Ľ\šE¿ U85a“$=F*&Á<ÀSಀsô5e._<ýj’¹SéNßßPœw ÃiãÖ†!”©9>¢¨+ãZ•e㓊=Hq±#§½=`R¿6ãžÕ{®~ Õ¸ÜH›ºzÒ(Ë”Ø<ƒÐôþu]† ÇJѸe1ã#>ÕH¤±ê: cDeJuÇó¦g5'ßl³`{š6¡^Où÷ úžXHÄjÆ}I$J]ÈÜ;˜³ncHd›‹œœÚ´­d Py‘œšžÞo.@{w FôDí?Z†&3ØÒ´˜àuô¤™ñŸnæ£å‡N>”(ÀËT/9b6r¿ÞþºÈ©Àä÷æ v'’xôíH[¹?­E#uäzb*Í'ïOãÖ¢@Ò¿=Ï8§ªœ°òI«q*B½r}s@\|h#@*T9~Gø¨wäñÓëIÇôæ™6.4ª3íPIpHÀéMù03"þu ²Fí;çL”Šò1‘ú“éN ‘ŒSPc“øSòF1Š lr¤ í9íFñøÓ•Õ‡\~4†H0@¥+¸b‘Çÿ^¤üæ‚H•p+ZÒ1oò™'LvRÒ/6L‘”^M] çH_ v¦F]Kp ±Î¤…²›ºçš®eȯ$ñ‘Ž*È!Sè:Vlè‰bÛ&7·õ¡ƒe/°úÒYɺ7'ûÆ’ðî´”q§ùTõ4û'ølç­BñàŠ‘Ž ÖšÇ Öç!µ Ýl-nÄ`ò¿ZÝs€V¸È%1H®§ŠëíîDð,‹ŒÏNµœ×SjoK¸[´g½{v¬9FÒAìkSÎ͓ӧ^ßçùUmJ0§Ì_ºyÎhƒ"¢êd^ÿ«SïT„˜9üêíÙß>â³ sZ¢Ú±Vö«HÒ\Çä ÍéÓåç½gÇ( #tìkBÑ42  íÂŽ)2‘ubSÉL²¯ß'¹ô¤žqylû_Ò›%ÂC©`O'¯'óÍUXZáüÉŽ â’A&4O<¯ò¹슰¦ä}å\{ñS ±®mÒ¢y‚Ž ~"®æC¶;}éýy1gçmÇý¦Ïóªr]Âu©#ˆEóLpÀôÏzEX¼Tt¦5¯LU+‹£Ù°?Ïz¢g9ÎZccí+ÔšÊ1àýMe'*$ííÞ•£í~èFðqÛ8öª÷;[•#pöªRNë (r°ª°ÎV@0x=?¦ƼÄÅÈ8ô©£‰nFYˆ= éHIq¿þ•5ºyJØl‚Ïz$/—ºN´Å…ÖHrO8'úÒÆMÄ›Çú±÷@}ø5>ÜO·'üjJJä So_öF•R’|÷úUÙÙv‘’O 9?ÏŠ‚;HÙ ÜŽŠÝ?Jh™KFãû§ÔÔ«)Áæ­KhTå&â«’S*àèj‰*·4Ú{õ;zS{Õ%IM+aEI¹“¸_Z’[„…|¸š›|Vh@ùœõÿëÕ)fi[sÃÒ¢.Iäþ´›¨sNšfiñò~´£cåÈéÀ«Œ8Ͻ$*5\tÓ™”)çðÍ3=ÙV|¤ªÔ¶ï¹ß=O4ë¾cFíëU¢—d€öèiе*BµAIùã±­ÞõJäa·‡ÒDŠCûÓVíOîÏÖ¨³n úŠ·hü0üh)–Ç\úU{—!p ëSnãç5MÏ›7ô¡‰ÿÙROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/gradio_app.py000066400000000000000000000107421510465702400304530ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from txt2img import StableDiffusionMGX, get_args import gradio as gr import sys class PrintWrapper(object): def __init__(self, org_handle): self.org_handle = org_handle self.log = "" def wrapper_write(x): self.log += x return org_handle.write(x) self.wrapper_write = wrapper_write def __getattr__(self, attr): return self.wrapper_write if attr == 'write' else getattr( self.org_handle, attr) def get_log(self): return self.log def main(): args = get_args() # Note: This will load the models, which can take several minutes sd = StableDiffusionMGX(args.pipeline_type, args.onnx_model_path, args.compiled_model_path, args.use_refiner, args.refiner_onnx_model_path, args.refiner_compiled_model_path, args.fp16, args.force_compile, args.exhaustive_tune) sd.warmup(5) def gr_wrapper(prompt, negative_prompt, steps, seed, scale, refiner_steps, aesthetic_score, negative_aesthetic_score): img = None try: oldStdout, oldStderr = sys.stdout, sys.stderr sys.stdout, sys.stderr = PrintWrapper(sys.stdout), PrintWrapper( sys.stderr) result = sd.run( str(prompt), str(negative_prompt), int(steps), int(seed), float(scale), int(refiner_steps), float(aesthetic_score), float(negative_aesthetic_score), args.verbose, ) img = StableDiffusionMGX.convert_to_rgb_image(result) sd.print_summary(steps) finally: log = ''.join([sys.stdout.get_log(), sys.stderr.get_log()]) sys.stdout, sys.stderr = oldStdout, oldStderr return img, log demo = gr.Interface(gr_wrapper, [ gr.Textbox(value=args.prompt, label="Prompt"), gr.Textbox(value=args.negative_prompt, label="Negative prompt (Optional)"), gr.Slider(1, 100, step=1, value=args.steps, label="Number of steps"), gr.Textbox(value=args.seed, label="Random seed"), gr.Slider(1, 20, step=0.1, value=args.scale, label="Guidance scale"), gr.Slider(0, 100, step=1, value=args.refiner_steps, label="Number of refiner steps. (Use 0 to skip it)", visible=args.use_refiner), gr.Slider(1, 20, step=0.1, value=args.refiner_aesthetic_score, label="Aesthetic score (Refiner)", visible=args.use_refiner), gr.Slider(1, 20, step=0.1, value=args.refiner_negative_aesthetic_score, label="Negative Aesthetic score (Refiner)", visible=args.use_refiner), ], [ "image", gr.Textbox(placeholder="Output log of the run", label="Output log") ]) demo.launch() sd.cleanup() if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/gradio_requirements.txt000066400000000000000000000024721510465702400326060ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### gradioROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/requirements.txt000066400000000000000000000026541510465702400312630ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### --extra-index-url https://test.pypi.org/simple accelerate diffusers transformers huggingface_hub[cli] onnx hip-python ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/torch_requirements.txt000066400000000000000000000025621510465702400324600ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### --index-url https://download.pytorch.org/whl/rocm6.1/ torch ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/txt2img.py000066400000000000000000000636411510465702400277520ustar00rootroot00000000000000# The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. from argparse import ArgumentParser from diffusers import EulerDiscreteScheduler from transformers import CLIPTokenizer from PIL import Image import migraphx as mgx import os import sys import torch import time from functools import wraps from hip import hip from collections import namedtuple HipEventPair = namedtuple('HipEventPair', ['start', 'end']) # measurement helper def measure(fn): @wraps(fn) def measure_ms(*args, **kwargs): start_time = time.perf_counter_ns() result = fn(*args, **kwargs) end_time = time.perf_counter_ns() print( f"Elapsed time for {fn.__name__}: {(end_time - start_time) * 1e-6:.4f} ms\n" ) return result return measure_ms def get_args(): parser = ArgumentParser() # Model compile parser.add_argument( "--pipeline-type", type=str, choices=["sdxl", "sdxl-opt", "sdxl-turbo"], required=True, help="Specify pipeline type. Options: `sdxl`, `sdxl-opt`, `sdxl-turbo`", ) parser.add_argument( "--onnx-model-path", type=str, default=None, help= "Path to onnx model files. Use it to override the default models/ path", ) parser.add_argument( "--compiled-model-path", type=str, default=None, help= "Path to compiled mxr model files. If not set, it will be saved next to the onnx model.", ) parser.add_argument( "--use-refiner", action="store_true", default=False, help="Use the refiner model", ) parser.add_argument( "--refiner-onnx-model-path", type=str, default=None, help= "Path to onnx model files. Use it to override the default models/ path", ) parser.add_argument( "--refiner-compiled-model-path", type=str, default=None, help= "Path to compiled mxr model files. If not set, it will be saved next to the refiner onnx model.", ) parser.add_argument( "--fp16", choices=[ "all", "vae", "clip", "clip2", "unetxl", "refiner_clip2", "refiner_unetxl" ], nargs="+", help="Quantize models with fp16 precision.", ) parser.add_argument( "--force-compile", action="store_true", default=False, help="Ignore existing .mxr files and override them", ) parser.add_argument( "--exhaustive-tune", action="store_true", default=False, help="Perform exhaustive tuning when compiling onnx models", ) # Runtime parser.add_argument( "-s", "--seed", type=int, default=42, help="Random seed", ) parser.add_argument( "-t", "--steps", type=int, default=30, help="Number of steps", ) parser.add_argument( "--refiner-steps", type=int, default=30, help="Number of refiner steps", ) parser.add_argument( "-p", "--prompt", type=str, required=True, help="Prompt", ) parser.add_argument( "-n", "--negative-prompt", type=str, default="", help="Negative prompt", ) parser.add_argument( "--scale", type=float, default=5.0, help="Guidance scale", ) parser.add_argument( "--refiner-aesthetic-score", type=float, default=6.0, help="aesthetic score for refiner", ) parser.add_argument( "--refiner-negative-aesthetic-score", type=float, default=2.5, help="negative aesthetic score for refiner", ) parser.add_argument( "-o", "--output", type=str, default=None, help="Output name", ) parser.add_argument( "--verbose", action="store_true", default=False, help="Log during run", ) return parser.parse_args() model_shapes = { "clip": { "input_ids": [2, 77] }, "clip2": { "input_ids": [2, 77] }, "unetxl": { "sample": [2, 4, 128, 128], "encoder_hidden_states": [2, 77, 2048], "text_embeds": [2, 1280], "time_ids": [2, 6], "timestep": [1], }, "refiner_unetxl": { "sample": [2, 4, 128, 128], "encoder_hidden_states": [2, 77, 1280], "text_embeds": [2, 1280], "time_ids": [2, 5], "timestep": [1], }, "vae": { "latent_sample": [1, 4, 128, 128] }, } model_names = { "sdxl": { "clip": "text_encoder", "clip2": "text_encoder_2", "unetxl": "unet", "vae": "vae_decoder", }, "sdxl-opt": { "clip": "clip.opt.mod", "clip2": "clip2.opt.mod", "unetxl": "unetxl.opt", "vae": "vae_decoder", }, "sdxl-turbo": { "clip": "text_encoder", "clip2": "text_encoder_2", "unetxl": "unet", "vae": "vae_decoder", }, "refiner": { "clip2": "clip2.opt.mod", "unetxl": "unetxl.opt", }, } default_model_paths = { "sdxl": "models/sdxl-1.0-base", "sdxl-opt": "models/sdxl-1.0-base", "sdxl-turbo": "models/sdxl-turbo", "refiner": "models/sdxl-1.0-refiner", } mgx_to_torch_dtype_dict = { "bool_type": torch.bool, "uint8_type": torch.uint8, "int8_type": torch.int8, "int16_type": torch.int16, "int32_type": torch.int32, "int64_type": torch.int64, "float_type": torch.float32, "double_type": torch.float64, "half_type": torch.float16, } torch_to_mgx_dtype_dict = { value: key for (key, value) in mgx_to_torch_dtype_dict.items() } def tensor_to_arg(tensor): return mgx.argument_from_pointer( mgx.shape( **{ "type": torch_to_mgx_dtype_dict[tensor.dtype], "lens": list(tensor.size()), "strides": list(tensor.stride()) }), tensor.data_ptr()) def tensors_to_args(tensors): return {name: tensor_to_arg(tensor) for name, tensor in tensors.items()} def get_output_name(idx): return f"main:#output_{idx}" def copy_tensor_sync(tensor, data): tensor.copy_(data.to(tensor.dtype)) torch.cuda.synchronize() def copy_tensor(tensor, data): tensor.copy_(data.to(tensor.dtype)) def run_model_sync(model, args): model.run(args) mgx.gpu_sync() def run_model_async(model, args, stream): model.run_async(args, stream, "ihipStream_t") def allocate_torch_tensors(model): input_shapes = model.get_parameter_shapes() data_mapping = { name: torch.zeros(shape.lens()).to( mgx_to_torch_dtype_dict[shape.type_string()]).to(device="cuda") for name, shape in input_shapes.items() } return data_mapping class StableDiffusionMGX(): def __init__(self, pipeline_type, onnx_model_path, compiled_model_path, use_refiner, refiner_onnx_model_path, refiner_compiled_model_path, fp16, force_compile, exhaustive_tune): if not (onnx_model_path or compiled_model_path): onnx_model_path = default_model_paths[pipeline_type] self.use_refiner = use_refiner if not self.use_refiner and (refiner_onnx_model_path or refiner_compiled_model_path): print( "WARN: Refiner model is provided, but was *not* enabled. Use --use-refiner to enable it." ) if self.use_refiner and not (refiner_onnx_model_path or refiner_compiled_model_path): refiner_onnx_model_path = default_model_paths["refiner"] is_turbo = "turbo" in pipeline_type model_id = "stabilityai/sdxl-turbo" if is_turbo else "stabilityai/stable-diffusion-xl-base-1.0" print(f"Using {model_id}") print("Creating EulerDiscreteScheduler scheduler") self.scheduler = EulerDiscreteScheduler.from_pretrained( model_id, subfolder="scheduler") print("Creating CLIPTokenizer tokenizers...") self.tokenizers = { "clip": CLIPTokenizer.from_pretrained(model_id, subfolder="tokenizer"), "clip2": CLIPTokenizer.from_pretrained(model_id, subfolder="tokenizer_2") } if fp16 is None: fp16 = [] elif "all" in fp16: fp16 = [ "vae", "clip", "clip2", "unetxl", "refiner_clip2", "refiner_unetxl" ] if "vae" in fp16: model_names[pipeline_type]["vae"] = "vae_decoder_fp16_fix" print("Load models...") self.models = { "vae": StableDiffusionMGX.load_mgx_model( model_names[pipeline_type]["vae"], model_shapes["vae"], onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="vae" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "clip": StableDiffusionMGX.load_mgx_model( model_names[pipeline_type]["clip"], model_shapes["clip"], onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="clip" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "clip2": StableDiffusionMGX.load_mgx_model( model_names[pipeline_type]["clip2"], model_shapes["clip2"], onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="clip2" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "unetxl": StableDiffusionMGX.load_mgx_model( model_names[pipeline_type]["unetxl"], model_shapes["unetxl"], onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="unetxl" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False) } self.tensors = { "clip": allocate_torch_tensors(self.models["clip"]), "clip2": allocate_torch_tensors(self.models["clip2"]), "unetxl": allocate_torch_tensors(self.models["unetxl"]), "vae": allocate_torch_tensors(self.models["vae"]), } self.model_args = { "clip": tensors_to_args(self.tensors["clip"]), "clip2": tensors_to_args(self.tensors["clip2"]), "unetxl": tensors_to_args(self.tensors["unetxl"]), "vae": tensors_to_args(self.tensors["vae"]), } if self.use_refiner: # Note: there is no clip for refiner, only clip2 self.models["refiner_clip2"] = StableDiffusionMGX.load_mgx_model( model_names["refiner"]["clip2"], model_shapes["clip2"], refiner_onnx_model_path, compiled_model_path=refiner_compiled_model_path, use_fp16="refiner_clip2" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False) self.models["refiner_unetxl"] = StableDiffusionMGX.load_mgx_model( model_names["refiner"]["unetxl"], model_shapes[ "refiner_unetxl"], # this differ from the original unetxl refiner_onnx_model_path, compiled_model_path=refiner_compiled_model_path, use_fp16="refiner_unetxl" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False) self.tensors["refiner_clip2"] = allocate_torch_tensors( self.models["refiner_clip2"]) self.tensors["refiner_unetxl"] = allocate_torch_tensors( self.models["refiner_unetxl"]) self.model_args["refiner_clip2"] = tensors_to_args( self.tensors["refiner_clip2"]) self.model_args["refiner_unetxl"] = tensors_to_args( self.tensors["refiner_unetxl"]) # hipEventCreate return a tuple(error, event) self.events = { "warmup": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "run": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "clip": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "denoise": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), "decode": HipEventPair(start=hip.hipEventCreate()[1], end=hip.hipEventCreate()[1]), } self.stream = hip.hipStreamCreate()[1] def cleanup(self): for event in self.events.values(): hip.hipEventDestroy(event.start) hip.hipEventDestroy(event.end) hip.hipStreamDestroy(self.stream) def profile_start(self, name): if name in self.events: hip.hipEventRecord(self.events[name].start, None) def profile_end(self, name): if name in self.events: hip.hipEventRecord(self.events[name].end, None) # @measure @torch.no_grad() def run(self, prompt, negative_prompt, steps, seed, scale, refiner_steps, refiner_aesthetic_score, refiner_negative_aesthetic_score, verbose=False): torch.cuda.synchronize() self.profile_start("run") # need to set this for each run self.scheduler.set_timesteps(steps, device="cuda") if verbose: print("Tokenizing prompts...") prompt_tokens = self.tokenize(prompt, negative_prompt) if verbose: print("Creating text embeddings...") self.profile_start("clip") hidden_states, text_embeddings = self.get_embeddings(prompt_tokens) self.profile_end("clip") sample_size = list(self.tensors["vae"]["latent_sample"].size()) if verbose: print( f"Creating random input data {sample_size} (latents) with {seed = }..." ) noise = torch.randn( sample_size, generator=torch.manual_seed(seed)).to(device="cuda") # input h/w crop h/w output h/w height, width = sample_size[2:] time_id = [height * 8, width * 8, 0, 0, height * 8, width * 8] time_ids = torch.tensor([time_id, time_id]).to(device="cuda") if verbose: print("Apply initial noise sigma\n") latents = noise * self.scheduler.init_noise_sigma if verbose: print("Running denoising loop...") self.profile_start("denoise") for step, t in enumerate(self.scheduler.timesteps): if verbose: print(f"#{step}/{len(self.scheduler.timesteps)} step") latents = self.denoise_step(text_embeddings, hidden_states, latents, t, scale, time_ids, model="unetxl") self.profile_end("denoise") if self.use_refiner and refiner_steps > 0: hidden_states, text_embeddings = self.get_embeddings( prompt_tokens, is_refiner=True) # input h/w crop h/w scores time_id_pos = time_id[:4] + [refiner_aesthetic_score] time_id_neg = time_id[:4] + [refiner_negative_aesthetic_score] time_ids = torch.tensor([time_id_pos, time_id_neg]).to(device="cuda") # need to set this for each run self.scheduler.set_timesteps(refiner_steps, device="cuda") # Add noise to latents using timesteps latents = self.scheduler.add_noise(latents, noise, self.scheduler.timesteps[:1]) if verbose: print("Running refiner denoising loop...") for step, t in enumerate(self.scheduler.timesteps): if verbose: print(f"#{step}/{len(self.scheduler.timesteps)} step") latents = self.denoise_step(text_embeddings, hidden_states, latents, t, scale, time_ids, model="refiner_unetxl") if verbose: print("Scale denoised result...") latents = 1 / 0.18215 * latents self.profile_start("decode") if verbose: print("Decode denoised result...") image = self.decode(latents) self.profile_end("decode") torch.cuda.synchronize() self.profile_end("run") return image def print_summary(self, denoise_steps): print('WARMUP\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['warmup'].start, self.events['warmup'].end)[1])) print('CLIP\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['clip'].start, self.events['clip'].end)[1])) print('UNetx{}\t{:>9.2f} ms'.format( str(denoise_steps), hip.hipEventElapsedTime(self.events['denoise'].start, self.events['denoise'].end)[1])) print('VAE-Dec\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['decode'].start, self.events['decode'].end)[1])) print('RUN\t{:>9.2f} ms'.format( hip.hipEventElapsedTime(self.events['run'].start, self.events['run'].end)[1])) # @measure @staticmethod def load_mgx_model(name, shapes, onnx_model_path, compiled_model_path=None, use_fp16=False, force_compile=False, exhaustive_tune=False, offload_copy=True): print(f"Loading {name} model...") if compiled_model_path is None: compiled_model_path = onnx_model_path onnx_file = f"{onnx_model_path}/{name}/model.onnx" mxr_file = f"{compiled_model_path}/{name}/model_{'fp16' if use_fp16 else 'fp32'}_{'gpu' if not offload_copy else 'oc'}.mxr" if not force_compile and os.path.isfile(mxr_file): print(f"Found mxr, loading it from {mxr_file}") model = mgx.load(mxr_file, format="msgpack") elif os.path.isfile(onnx_file): print(f"No mxr found at {mxr_file}") print(f"Parsing from {onnx_file}") model = mgx.parse_onnx(onnx_file, map_input_dims=shapes) if use_fp16: mgx.quantize_fp16(model) model.compile(mgx.get_target("gpu"), exhaustive_tune=exhaustive_tune, offload_copy=offload_copy) print(f"Saving {name} model to {mxr_file}") os.makedirs(os.path.dirname(mxr_file), exist_ok=True) mgx.save(model, mxr_file, format="msgpack") else: print( f"No {name} model found at {onnx_file} or {mxr_file}. Please download it and re-try." ) sys.exit(1) return model # @measure def tokenize(self, prompt, negative_prompt): def _tokenize(tokenizer): return self.tokenizers[tokenizer]( [prompt, negative_prompt], padding="max_length", max_length=self.tokenizers[tokenizer].model_max_length, truncation=True, return_tensors="pt") tokens = _tokenize("clip") tokens2 = _tokenize("clip2") return (tokens, tokens2) # @measure def get_embeddings(self, prompt_tokens, is_refiner=False): def _create_embedding(model, input): copy_tensor(self.tensors[model]["input_ids"], input.input_ids) run_model_async(self.models[model], self.model_args[model], self.stream) clip_input, clip2_input = prompt_tokens clip, clip2 = "clip", ("refiner_" if is_refiner else "") + "clip2" if not is_refiner: _create_embedding(clip, clip_input) _create_embedding(clip2, clip2_input) hidden_states = torch.concatenate( (self.tensors[clip][get_output_name(0)], self.tensors[clip2][get_output_name(1)]), axis=2) if not is_refiner else self.tensors[clip2][get_output_name( 1)] text_embeds = self.tensors[clip2][get_output_name(0)] return (hidden_states, text_embeds) @staticmethod def convert_to_rgb_image(image): image = (image / 2 + 0.5).clamp(0, 1) image = image.detach().cpu().permute(0, 2, 3, 1).numpy() images = (image * 255).round().astype("uint8") return Image.fromarray(images[0]) @staticmethod def save_image(pil_image, filename="output.png"): pil_image.save(filename) # @measure def denoise_step(self, text_embeddings, hidden_states, latents, t, scale, time_ids, model): latents_model_input = torch.cat([latents] * 2) latents_model_input = self.scheduler.scale_model_input( latents_model_input, t).to(device="cuda") timestep = torch.atleast_1d(t.to(device="cuda")) # convert 0D -> 1D copy_tensor(self.tensors[model]["sample"], latents_model_input) copy_tensor(self.tensors[model]["encoder_hidden_states"], hidden_states) copy_tensor(self.tensors[model]["text_embeds"], text_embeddings) copy_tensor(self.tensors[model]["timestep"], timestep) copy_tensor(self.tensors[model]["time_ids"], time_ids) run_model_async(self.models[model], self.model_args[model], self.stream) noise_pred_text, noise_pred_uncond = torch.tensor_split( self.tensors[model][get_output_name(0)], 2) # perform guidance noise_pred = noise_pred_uncond + scale * (noise_pred_text - noise_pred_uncond) # compute the previous noisy sample x_t -> x_t-1 return self.scheduler.step(noise_pred, t, latents).prev_sample # @measure def decode(self, latents): copy_tensor(self.tensors["vae"]["latent_sample"], latents) run_model_async(self.models["vae"], self.model_args["vae"], self.stream) return self.tensors["vae"][get_output_name(0)] # @measure def warmup(self, num_runs): self.profile_start("warmup") init_fn = lambda x: torch.ones if "clip" in x else torch.randn for model in self.models.keys(): for tensor in self.tensors[model].values(): copy_tensor(tensor, init_fn(model)(tensor.size())) for _ in range(num_runs): for model in self.models.keys(): run_model_async(self.models[model], self.model_args[model], self.stream) self.profile_end("warmup") if __name__ == "__main__": args = get_args() sd = StableDiffusionMGX(args.pipeline_type, args.onnx_model_path, args.compiled_model_path, args.use_refiner, args.refiner_onnx_model_path, args.refiner_compiled_model_path, args.fp16, args.force_compile, args.exhaustive_tune) print("Warmup") sd.warmup(5) print("Run") result = sd.run(args.prompt, args.negative_prompt, args.steps, args.seed, args.scale, args.refiner_steps, args.refiner_aesthetic_score, args.refiner_negative_aesthetic_score, args.verbose) print("Summary") sd.print_summary(args.steps) print("Cleanup") sd.cleanup() print("Convert result to rgb image...") image = StableDiffusionMGX.convert_to_rgb_image(result) filename = args.output if args.output else f"output_s{args.seed}_t{args.steps}.png" StableDiffusionMGX.save_image(image, filename) print(f"Image saved to {filename}") ROCm-AMDMIGraphX-46524e8/examples/diffusion/python_stable_diffusion_xl/vae_fp16.py000066400000000000000000000044661510465702400277630ustar00rootroot00000000000000# The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. from argparse import ArgumentParser from diffusers import AutoencoderKL import os import torch def argparser(): parser = ArgumentParser() parser.add_argument( "-o", "--output_path", type=str, default="models/sdxl-1.0-base/vae_decoder_fp16_fix/model.onnx", help= "Path to save the onnx model. Use it to override the default models/ path." ) return parser.parse_args() class VAEDecoder(torch.nn.Module): def __init__(self, vae): super().__init__() self.vae = vae def forward(self, latent_sample): return self.vae.decode(latent_sample) def export_vae_fp16(output_path): vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix") vae.eval() os.makedirs(os.path.dirname(output_path), exist_ok=True) torch.onnx.export(VAEDecoder(vae), torch.randn(1, 4, 128, 128), output_path, export_params=True, do_constant_folding=True, input_names=['latent_sample']) if __name__ == "__main__": args = argparser() export_vae_fp16(**vars(args)) ROCm-AMDMIGraphX-46524e8/examples/migraphx/000077500000000000000000000000001510465702400201755ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/README.md000066400000000000000000000004731510465702400214600ustar00rootroot00000000000000# AMD MIGraphX usage and utilities - [C++ Parse, Load, and Save Graph Programs](./cpp_parse_load_save) - [Exporting Frozen Graphs in TF1](./export_frozen_graph_tf1) - [Exporting Frozen Graphs in TF2](./export_frozen_graph_tf2) - [MIGraphX Docker Container](./migraphx_docker) - [MIGraphX Driver](./migraphx_driver)ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_dynamic_batch/000077500000000000000000000000001510465702400236245ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_dynamic_batch/CMakeLists.txt000066400000000000000000000032131510465702400263630ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### cmake_minimum_required(VERSION 3.5) project (cpp_dynamic_batch) set (CMAKE_CXX_STANDARD 14) set (EXAMPLE dynamic_batch) list (APPEND CMAKE_PREFIX_PATH /opt/rocm) find_package (migraphx) message("source file: " ${EXAMPLE}.cpp " ---> bin: " ${EXAMPLE}) add_executable(${EXAMPLE} ${EXAMPLE}.cpp) target_link_libraries(${EXAMPLE} migraphx::c) ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_dynamic_batch/README.md000066400000000000000000000067051510465702400251130ustar00rootroot00000000000000# Running ONNX model with dynamic batch ## Description This examples demonstrates how to run a graph program with dynamic batch using the MIGraphX C++ API. ## Creating dynamic dimension objects `dynamic_dimension` objects are used in MIGraphX to specify a range of dimension values from a minimum value to a maximum value and optimal values that the tensor can be at model evaluation time. A dynamic shape is defined by a list of `dynamic_dimensions` while a static shape only has fixed dimension values. For example, a `dynamic_dimension` with `{min:1, max:10, optimals:{1, 4, 10}}` means that the dimension can be any value from 1 through 10 with the optimal values being 1, 4, and 10. Supplied optimal values may allow MIGraphX to optimize the program for those specific shapes. A fixed `dynamic_dimension` can be specified by setting the `min` and `max` to the same value (ex. `{min:3, max:3}`). A dynamic shape specified solely by fixed `dynamic_dimension` objects will be converted to a static shape during parsing. This can be useful for setting a static shape using the `set_dyn_input_parameter_shape()` method discussed later in this document. ## Parsing ONNX graphs [ONNX](https://onnx.ai/get-started.html) can be parsed by MIGraphX to create a runnable program with dynamic batch sizes. The dynamic batch range must be specified by a `dynamic_dimension` object. One method to set the `dynamic_dimension` object works for ONNX files that only have symbolic variables for the batch dimensions: ``` migraphx::program p; migraphx::onnx_options options; options.set_default_dyn_dim_value(migraphx::dynamic_dimension{1, 4, {2, 4}}); p = parse_onnx(input_file, options); ``` Another option that can run any ONNX model with dynamic batch sizes uses the dynamic input map where the entire shape of the input parameter is supplied: ``` migraphx::program p; migraphx::onnx_options options; migraphx::dynamic_dimensions dyn_dims = {migraphx::dynamic_dimension{1, 4, {2, 4}}, migraphx::dynamic_dimension{3, 3}, migraphx::dynamic_dimension{4, 4}, migraphx::dynamic_dimension{4, 4}}; options.set_dyn_input_parameter_shape("input", dyn_dims); p = parse_onnx(input_file, options); ``` ## Compiling Currently the MIGraphX C/C++ API requires that `offload_copy` be enabled for compiling dynamic batch programs. Here is a snippet of compiling a model with `offload_copy` enabled: ``` migraphx::compile_options c_options; c_options.set_offload_copy(); p.compile(migraphx::target("gpu"), c_options); ``` where `p` is the `migraphx::program`. ## Saving and Loading A dynamic batch MIGraphX program can be saved and loaded to/from a MXR file the same way as a fully static shape program. ## Executing the dynamic batch model The compiled dynamic batch model can be executed the same way as a static model by supplying the input data as `arguments` in a `program_parameters` object. ## Running the Example Your ROCm installation could be installed in a location other than the one specified in the CMakeLists.txt. You can set `LD_LIBRARY_PATH` or `CMAKE_PREFIX_PATH` to that location so that this program can still build. The provided example is [`dynamic_batch.cpp`](./dynamic_batch.cpp) To compile and run the example from this directory: ``` $ mkdir build $ cd build $ cmake .. $ make ``` There will now be an executable named `dynamic_batch` with the following usage: ``` $ ./dynamic_batch ``` ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_dynamic_batch/add_scalar_test.onnx000066400000000000000000000002011510465702400276350ustar00rootroot00000000000000add_scalar_test:h  0 12"Addadd_scalar_testZ 0     Z 1 b 2     B ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_dynamic_batch/dynamic_batch.cpp000066400000000000000000000052171510465702400271220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include // MIGraphX C++ API #include int main(int argc, char** argv) { migraphx::onnx_options o_options; migraphx::dynamic_dimensions dyn_dims = {migraphx::dynamic_dimension{1, 4, {2, 4}}, migraphx::dynamic_dimension{3, 3}, migraphx::dynamic_dimension{4, 4}, migraphx::dynamic_dimension{5, 5}}; o_options.set_dyn_input_parameter_shape("0", dyn_dims); auto p = migraphx::parse_onnx("../add_scalar_test.onnx", o_options); migraphx::compile_options c_options; c_options.set_offload_copy(); p.compile(migraphx::target("gpu"), c_options); // batch size = 2 std::vector a(2 * 3 * 4 * 5, 3); std::vector b = {2}; migraphx::program_parameters pp; migraphx::shape s = migraphx::shape(migraphx_shape_uint8_type, {2, 3, 4, 5}); pp.add("0", migraphx::argument(s, a.data())); pp.add("1", migraphx::argument(migraphx::shape(migraphx_shape_uint8_type, {1}, {0}), b.data())); auto outputs = p.eval(pp); auto result = outputs[0]; std::vector c(2 * 3 * 4 * 5, 5); if(bool{result == migraphx::argument(s, c.data())}) { std::cout << "Successfully executed dynamic batch add\n"; } else { std::cout << "Failed dynamic batch add\n"; } return 0; } ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_parse_load_save/000077500000000000000000000000001510465702400241665ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_parse_load_save/CMakeLists.txt000066400000000000000000000031771510465702400267360ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### cmake_minimum_required(VERSION 3.5) project (PLS) set (CMAKE_CXX_STANDARD 14) set (EXAMPLE parse_load_save) list (APPEND CMAKE_PREFIX_PATH /opt/rocm) find_package (migraphx) message("source file: " ${EXAMPLE}.cpp " ---> bin: " ${EXAMPLE}) add_executable(${EXAMPLE} ${EXAMPLE}.cpp) target_link_libraries(${EXAMPLE} migraphx::c) ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_parse_load_save/README.md000066400000000000000000000043311510465702400254460ustar00rootroot00000000000000# Parsing, Loading, and Saving MIGraphX Programs ## Description This examples demonstrates how to parse, load, and save a graph program using the MIGraphX C++ API. ## Parsing Computation graphs that have been saved in a compatible serialized format, such as [ONNX](https://onnx.ai/get-started.html), can be read in by MIGraphX to create a runable program. ``` migraphx::program p; unsigned batch = 1; //Or read in as argument migraphx::onnx_options options; options.set_default_dim_value(batch); p = parse_onnx(input_file, options); ``` ## Saving An instantiated migraphx::program object can then be serialized to MessagePack (.mxr) format and saved so that it can be loaded for future uses. A program can be saved with either of the following: ``` migraphx::program p = ... ; migraphx::save(p, output_file); ``` ``` migraphx::program p = ... ; migraphx::file_options options; options.set_file_format("msgpack"); migraphx::save(p, output_file, options); ``` ## Loading Similarly, graphs that have been previously parsed, and possibly compiled, and then saved in either MessagePack or JSON format can be loaded at later time. MessagePack is the default format, and can be loaded with either: ``` migraphx::program p; p = migraphx::load(input_file); ``` ``` migraphx::program p; migraphx::file_options options; options.set_file_format("msgpack"); p = migraphx::load(input_file, options); ``` To load a program that has been saved in JSON format: ``` migraphx::program p; migraphx::file_options options; options.set_file_format("json"); p = migraphx::load(input_file, options); ``` ## Running the Example The provided example [`parse_load_save.cpp`](./parse_load_save.cpp) has these features implemented to allow for comparing outputs. To compile and run the example from this directory: ``` $ mkdir build $ cd build $ cmake .. $ make ``` There will now be an executable named `parse_load_save` with the following usage: ``` $ ./parse_load_save [options] options: --parse onnx --load json/msgpack --save ``` The program will then attempt to parse or load the graph file, print out its internal graph structure if successful, and optionally save the program to a given file name. ROCm-AMDMIGraphX-46524e8/examples/migraphx/cpp_parse_load_save/parse_load_save.cpp000066400000000000000000000103161510465702400300220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include // MIGraphX C++ API #include char* getCmdOption(char**, char**, const std::string&); bool cmdOptionExists(char**, char**, const std::string&); int main(int argc, char** argv) { if(argc < 2) { std::cout << "Usage: " << argv[0] << " " << "[options]" << std::endl; std::cout << "options:" << std::endl; std::cout << "\t--parse onnx" << std::endl; std::cout << "\t--load json/msgpack" << std::endl; std::cout << "\t--save " << std::endl; return 0; } char* load_arg = getCmdOption(argv + 2, argv + argc, "--load"); char* save_arg = getCmdOption(argv + 2, argv + argc, "--save"); const char* input_file = argv[1]; migraphx::program p; if(cmdOptionExists(argv + 2, argv + argc, "--parse") or not cmdOptionExists(argv + 2, argv + argc, "--load")) { std::cout << "Parsing ONNX File" << std::endl; migraphx::onnx_options options; p = parse_onnx(input_file, options); } else if(load_arg != nullptr) { std::cout << "Loading Graph File" << std::endl; std::string format = load_arg; if(format == "json") { migraphx::file_options options; options.set_file_format("json"); p = migraphx::load(input_file, options); } else if(format == "msgpack") { migraphx::file_options options; options.set_file_format("msgpack"); p = migraphx::load(input_file, options); } else p = migraphx::load(input_file); } else { std::cout << "Error: Incorrect Usage" << std::endl; std::cout << "Usage: " << argv[0] << " " << "[options]" << std::endl; std::cout << "options:" << std::endl; std::cout << "\t--parse onnx" << std::endl; std::cout << "\t--load json/msgpack" << std::endl; std::cout << "\t--save " << std::endl; return 0; } std::cout << "Input Graph: " << std::endl; p.print(); std::cout << std::endl; if(cmdOptionExists(argv + 2, argv + argc, "--save")) { std::cout << "Saving program..." << std::endl; std::string output_file; output_file = save_arg == nullptr ? "out" : save_arg; output_file.append(".mxr"); migraphx::file_options options; options.set_file_format("msgpack"); migraphx::save(p, output_file.c_str(), options); std::cout << "Program has been saved as ./" << output_file << std::endl; } return 0; } char* getCmdOption(char** begin, char** end, const std::string& option) { char** itr = std::find(begin, end, option); if(itr != end and ++itr != end) { return *itr; } return nullptr; } bool cmdOptionExists(char** begin, char** end, const std::string& option) { return std::find(begin, end, option) != end; } ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_hip_kernel/000077500000000000000000000000001510465702400244055ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_hip_kernel/CMakeLists.txt000066400000000000000000000033211510465702400271440ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### cmake_minimum_required(VERSION 3.5) project (custom_hip_kernel) set (CMAKE_CXX_STANDARD 14) set (EXAMPLE custom_op_hip_kernel) list (APPEND CMAKE_PREFIX_PATH /opt/rocm/hip /opt/rocm) find_package (migraphx REQUIRED) find_package (hip REQUIRED) message("source file: " ${EXAMPLE}.cpp " ---> bin: " ${EXAMPLE}) add_executable(${EXAMPLE} ${EXAMPLE}.cpp) target_link_libraries(${EXAMPLE} migraphx::c hip::device) ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_hip_kernel/README.md000066400000000000000000000012501510465702400256620ustar00rootroot00000000000000# Custom Kernel using MIGraphX API. This is an example of a custom operator implementation using MIGraphX's C/C++ APIs. It also demonstrates how to use this custom op in conjunction with rest of MIGraphX operators to build and run MIGraphX program on GPU. Kernels can be written in either HIP, MIOpen, or by using RocBLAS library. This particular example uses **HIP**. To build the example, ensure ROCm is installed at `/opt/rocm`. 1. `export LD_LIBRARY_PATH=/opt/rocm/lib:$LD_LIBRARY_PATH` 2. `cd $MIGRAPHX_SRC/examples/migraphx/custom_op_hip_kernel/` 3. `mkdir build && cd build` 4. `CXX=/opt/rocm/llvm/bin/clang++ cmake .. && make` 5. `./custom_op_hip_kernel`ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_hip_kernel/custom_op_hip_kernel.cpp000066400000000000000000000130161510465702400313220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include // MIGraphX's C++ API #include #define MIGRAPHX_HIP_ASSERT(x) (assert((x) == hipSuccess)) /* * Square each element in the array A and write to array C. */ template __global__ void vector_square(T* C_d, const T* A_d, size_t N) { size_t offset = (hipBlockIdx_x * hipBlockDim_x + hipThreadIdx_x); size_t stride = hipBlockDim_x * hipGridDim_x; for(size_t i = offset; i < N; i += stride) { C_d[i] = A_d[i] * A_d[i]; } } struct square_custom_op final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "square_custom_op"; } // flag to identify whether custom op runs on the GPU or on the host. // Based on this flag MIGraphX would inject necessary copies to and from GPU for the input and // output buffers as necessary. Therefore if custom_op runs on GPU then it can assume its input // buffers are in GPU memory, and similarly for the host virtual bool runs_on_offload_target() const override { return true; } virtual migraphx::argument compute(migraphx::context ctx, migraphx::shape, migraphx::arguments inputs) const override { // if compile options has offload_copy = true then, parameters and outputs will be // automatically copied to and from GPUs' memory. Here assume that `inputs` arguments are // already in the GPU, so no need to do Malloc, Free or Memcpy. Last element in the `inputs` // is output argument, so it should be returned from compute method. auto* input_buffer = reinterpret_cast(inputs[0].data()); auto* output_buffer = reinterpret_cast(inputs[1].data()); size_t n_elements = inputs[0].get_shape().elements(); MIGRAPHX_HIP_ASSERT(hipSetDevice(0)); const unsigned blocks = 512; const unsigned threads_per_block = 256; // cppcheck-suppress migraphx-UseDeviceLaunch hipLaunchKernelGGL(vector_square, dim3(blocks), dim3(threads_per_block), 0, ctx.get_queue(), output_buffer, input_buffer, n_elements); return inputs[1]; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(inputs.size() != 2) { throw std::runtime_error("square_custom_op must have 2 arguments"); } if(inputs[0] != inputs[1]) { throw std::runtime_error("Inputs to the square_custom_op must have same Shape"); } return inputs.back(); } }; int main(int argc, const char* argv[]) { square_custom_op square_op; migraphx::register_experimental_custom_op(square_op); migraphx::program p; migraphx::shape s{migraphx_shape_float_type, {32, 256}}; migraphx::module m = p.get_main_module(); auto x = m.add_parameter("x", s); auto neg_ins = m.add_instruction(migraphx::operation("neg"), x); // add allocation for the custom_kernel's output buffer auto alloc = m.add_allocation(s); auto custom_kernel = m.add_instruction(migraphx::operation("square_custom_op"), {neg_ins, alloc}); auto relu_ins = m.add_instruction(migraphx::operation("relu"), {custom_kernel}); m.add_return({relu_ins}); migraphx::compile_options options; // set offload copy to true for GPUs options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); migraphx::program_parameters pp; std::vector x_data(s.elements()); std::iota(x_data.begin(), x_data.end(), 0); pp.add("x", migraphx::argument(s, x_data.data())); auto results = p.eval(pp); auto result = results[0]; std::vector expected_result = x_data; std::transform(expected_result.begin(), expected_result.end(), expected_result.begin(), [](auto i) { return std::pow(i, 2); }); if(bool{result == migraphx::argument(s, expected_result.data())}) { std::cout << "Successfully executed custom HIP kernel example\n"; } else { std::cout << "Custom HIP kernel example failed\n"; } return 0; } ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_miopen_kernel/000077500000000000000000000000001510465702400251145ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_miopen_kernel/CMakeLists.txt000066400000000000000000000033071510465702400276570ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### cmake_minimum_required(VERSION 3.5) project (custom_miopen_kernel) set (CMAKE_CXX_STANDARD 14) set (EXAMPLE custom_op_miopen_kernel) list (APPEND CMAKE_PREFIX_PATH /opt/rocm) find_package (migraphx REQUIRED) find_package (miopen REQUIRED) message("source file: " ${EXAMPLE}.cpp " ---> bin: " ${EXAMPLE}) add_executable(${EXAMPLE} ${EXAMPLE}.cpp) target_link_libraries(${EXAMPLE} migraphx::c MIOpen) ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_miopen_kernel/README.md000066400000000000000000000012561510465702400263770ustar00rootroot00000000000000# Custom MIOpen Kernel using MIGraphX API. This is an example of a custom operator implementation using MIGraphX's C/C++ APIs. It also demonstrates how to use this custom op in conjunction with rest of MIGraphX operators to build and run MIGraphX program on GPU. Kernels can be written in either HIP, MIOpen, or by using RocBLAS library. This particular example uses **MIOpen** library calls. To build and run example, ensure ROCm is installed at `/opt/rocm`. 1. `export LD_LIBRARY_PATH=/opt/rocm/lib:$LD_LIBRARY_PATH` 2. `cd $MIGRAPHX_SRC/examples/migraphx/custom_op_miopen_kernel/` 3. `mkdir build && cd build` 4. `cmake .. && make` 5. `./custom_op_miopen_kernel` ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_miopen_kernel/custom_op_miopen_kernel.cpp000066400000000000000000000155271510465702400325510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include // MIGraphX's C++ API #include #include #define MIGRAPHX_MIOPEN_ASSERT(x) (assert((x) == miopenStatusSuccess)) #define MIGRAPHX_HIP_ASSERT(x) (assert((x) == hipSuccess)) inline miopenTensorDescriptor_t make_miopen_tensor(const migraphx::shape& s) { miopenTensorDescriptor_t t; MIGRAPHX_MIOPEN_ASSERT(miopenCreateTensorDescriptor(&t)); // Convert to ints auto s_lens = s.lengths(); std::vector lens(s_lens.begin(), s_lens.end()); auto s_strides = s.strides(); std::vector strides(s_strides.begin(), s_strides.end()); miopenDataType_t d; if(s.type() == migraphx_shape_float_type) d = miopenFloat; else if(s.type() == migraphx_shape_half_type) d = miopenHalf; else if(s.type() == migraphx_shape_int32_type) d = miopenInt32; else if(s.type() == migraphx_shape_int8_type) d = miopenInt8; else throw("MAKE_TENSOR: unsupported type"); miopenSetTensorDescriptor(t, d, s_lens.size(), lens.data(), strides.data()); return t; } inline auto make_miopen_handle(migraphx::context& ctx) { MIGRAPHX_HIP_ASSERT(hipSetDevice(0)); auto* stream = ctx.get_queue(); miopenHandle_t out; MIGRAPHX_MIOPEN_ASSERT(miopenCreateWithStream(&out, stream)); return out; } inline auto make_activation_descriptor(miopenActivationMode_t mode, double alpha = 0, double beta = 0, double gamma = 0) { miopenActivationDescriptor_t ad; MIGRAPHX_MIOPEN_ASSERT(miopenCreateActivationDescriptor(&ad)); miopenSetActivationDescriptor(ad, mode, alpha, beta, gamma); return ad; } struct abs_custom_op final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "abs_custom_op"; } // flag to identify whether custom op runs on the GPU or on the host. // Based on this flag MIGraphX would inject necessary copies to and from GPU for the input and // output buffers as necessary. Therefore if custom_op runs on GPU then it can assume its input // buffers are in GPU memory, and similarly for the host virtual bool runs_on_offload_target() const override { return true; } virtual migraphx::argument compute(migraphx::context ctx, migraphx::shape output_shape, migraphx::arguments args) const override { float alpha = 1; float beta = 0; // MIOpen kernel call takes raw buffer pointers for the TensorData. These Buffer pointers // must be accompanied with Tensor Description e.g. shape, type, strides, dimensionality. // Following `make_miopen_tensor` makes such tensor descriptors to pass as parameter to // MIOpen kernel call. auto y_desc = make_miopen_tensor(output_shape); auto x_desc = make_miopen_tensor(args[0].get_shape()); // create MIOpen stream handle auto miopen_handle = make_miopen_handle(ctx); // MIOpen has generic kernel for many different kinds of activation functions. // Each such generic call must be accompanied with description of what kind of activation // computation to perform auto ad = make_activation_descriptor(miopenActivationABS, 0, 0, 0); miopenActivationForward( miopen_handle, ad, &alpha, x_desc, args[0].data(), &beta, y_desc, args[1].data()); return args[1]; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(inputs.size() != 2) { throw std::runtime_error("abs_custom_op must have two input arguments"); } if(inputs[0] != inputs[1]) { throw std::runtime_error("Input arguments to abs_custom_op must have same shape"); } return inputs.back(); } }; int main(int argc, const char* argv[]) { abs_custom_op abs_op; migraphx::register_experimental_custom_op(abs_op); migraphx::program p; migraphx::shape s{migraphx_shape_float_type, {32, 256}}; migraphx::module m = p.get_main_module(); auto x = m.add_parameter("x", s); auto neg_ins = m.add_instruction(migraphx::operation("neg"), {x}); // add allocation for the custom_kernel's output buffer auto alloc = m.add_allocation(s); auto custom_kernel = m.add_instruction(migraphx::operation("abs_custom_op"), {neg_ins, alloc}); auto relu_ins = m.add_instruction(migraphx::operation("relu"), {custom_kernel}); m.add_return({relu_ins}); migraphx::compile_options options; // set offload copy to true for GPUs options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); migraphx::program_parameters prog_params; std::vector x_data(s.bytes() / sizeof(s.type())); std::iota(x_data.begin(), x_data.end(), 0); prog_params.add("x", migraphx::argument(s, x_data.data())); auto results = p.eval(prog_params); auto result = results[0]; std::vector expected_result = x_data; std::transform(expected_result.begin(), expected_result.end(), expected_result.begin(), [](auto i) { return std::abs(i); }); if(bool{result == migraphx::argument(s, expected_result.data())}) { std::cout << "Successfully executed custom MIOpen kernel example with MIGraphX\n"; } else { std::cout << "Custom MIOpen kernel example failed\n"; } return 0; } ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_rocblas_kernel/000077500000000000000000000000001510465702400252525ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_rocblas_kernel/CMakeLists.txt000066400000000000000000000033221510465702400300120ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### cmake_minimum_required(VERSION 3.5) project (custom_rocblas_kernel) set (CMAKE_CXX_STANDARD 14) set (EXAMPLE custom_op_rocblas_kernel) list (APPEND CMAKE_PREFIX_PATH /opt/rocm) find_package (migraphx REQUIRED) find_package (rocblas REQUIRED) message("source file: " ${EXAMPLE}.cpp " ---> bin: " ${EXAMPLE}) add_executable(${EXAMPLE} ${EXAMPLE}.cpp) target_link_libraries(${EXAMPLE} migraphx::c roc::rocblas) ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_rocblas_kernel/README.md000066400000000000000000000012671510465702400265370ustar00rootroot00000000000000# Custom rocBLAS Kernel using MIGraphX API. This is an example of a custom operator implementation using MIGraphX's C/C++ APIs. It also demonstrates how to use this custom op in conjunction with rest of MIGraphX operators to build and run MIGraphX program on GPU. Kernels can be written in either HIP, MIOpen, or by using RocBLAS library. This particular example uses **rocBLAS** library calls. To build and run the example, ensure ROCm is installed at `/opt/rocm`. 1. `export LD_LIBRARY_PATH=/opt/rocm/lib:$LD_LIBRARY_PATH` 2. `cd $MIGRAPHX_SRC/examples/migraphx/custom_op_rocblas_kernel/` 3. `mkdir build && cd build` 4. `cmake .. && make` 5. `./custom_op_rocblas_kernel` ROCm-AMDMIGraphX-46524e8/examples/migraphx/custom_op_rocblas_kernel/custom_op_rocblas_kernel.cpp000066400000000000000000000130711510465702400330350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include // MIGraphX's C++ API #include #include #define MIGRAPHX_ROCBLAS_ASSERT(x) (assert((x) == rocblas_status::rocblas_status_success)) #define MIGRAPHX_HIP_ASSERT(x) (assert((x) == hipSuccess)) rocblas_handle create_rocblas_handle_ptr() { rocblas_handle handle; MIGRAPHX_ROCBLAS_ASSERT(rocblas_create_handle(&handle)); return rocblas_handle{handle}; } rocblas_handle create_rocblas_handle_ptr(migraphx::context& ctx) { MIGRAPHX_HIP_ASSERT(hipSetDevice(0)); rocblas_handle rb = create_rocblas_handle_ptr(); auto* stream = ctx.get_queue(); MIGRAPHX_ROCBLAS_ASSERT(rocblas_set_stream(rb, stream)); return rb; } struct sscal_custom_op final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "sscal_custom_op"; } // flag to identify whether custom op runs on the GPU or on the host. // Based on this flag MIGraphX would inject necessary copies to and from GPU for the input and // output buffers as necessary. Therefore if custom_op runs on GPU then it can assume its input // buffers are in GPU memory, and similarly for the host virtual bool runs_on_offload_target() const override { return true; } virtual migraphx::argument compute(migraphx::context ctx, migraphx::shape output_shape, migraphx::arguments args) const override { // create rocblas stream handle auto rb_handle = create_rocblas_handle_ptr(ctx); MIGRAPHX_ROCBLAS_ASSERT(rocblas_set_pointer_mode(rb_handle, rocblas_pointer_mode_device)); rocblas_int n = args[1].get_shape().lengths()[0]; float* alpha = reinterpret_cast(args[0].data()); float* vec_ptr = reinterpret_cast(args[1].data()); MIGRAPHX_ROCBLAS_ASSERT(rocblas_sscal(rb_handle, n, alpha, vec_ptr, 1)); MIGRAPHX_ROCBLAS_ASSERT(rocblas_destroy_handle(rb_handle)); return args[1]; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(inputs.size() != 2) { throw std::runtime_error("sscal_custom_op must have 2 input arguments"); } if(inputs[0].lengths().size() != 1 or inputs[0].lengths()[0] != 1) { throw std::runtime_error("first input argument to sscal_custom_op must be a scalar"); } if(inputs[1].lengths().size() != 1) { throw std::runtime_error( "second input argument to sscal_custom_op must be a vector with dimension one"); } return inputs.back(); } }; int main(int argc, const char* argv[]) { // computes ReLU(neg(x) * scale) sscal_custom_op sscal_op; migraphx::register_experimental_custom_op(sscal_op); migraphx::program p; migraphx::shape x_shape{migraphx_shape_float_type, {8192}}; migraphx::shape scale_shape{migraphx_shape_float_type, {1}}; migraphx::module m = p.get_main_module(); auto x = m.add_parameter("x", x_shape); auto scale = m.add_parameter("scale", scale_shape); auto neg_ins = m.add_instruction(migraphx::operation("neg"), {x}); auto custom_kernel = m.add_instruction(migraphx::operation("sscal_custom_op"), {scale, neg_ins}); auto relu_ins = m.add_instruction(migraphx::operation("relu"), {custom_kernel}); m.add_return({relu_ins}); migraphx::compile_options options; // set offload copy to true for GPUs options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); migraphx::program_parameters pp; std::vector x_data(x_shape.elements()); std::vector scale_data{-1}; std::iota(x_data.begin(), x_data.end(), 0); pp.add("x", migraphx::argument(x_shape, x_data.data())); pp.add("scale", migraphx::argument(scale_shape, scale_data.data())); auto results = p.eval(pp); auto result = results[0]; std::vector expected_result = x_data; if(bool{result == migraphx::argument(x_shape, expected_result.data())}) { std::cout << "Successfully executed custom rocBLAS kernel example\n"; } else { std::cout << "Custom rocBLAS kernel example failed\n"; } return 0; } ROCm-AMDMIGraphX-46524e8/examples/migraphx/export_frozen_graph_tf1/000077500000000000000000000000001510465702400250345ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/export_frozen_graph_tf1/README.md000066400000000000000000000123671510465702400263240ustar00rootroot00000000000000# Exporting Frozen Graphs in Tensorflow 1 ## Description This example demonstrates how to export a frozen graph protobuf in Tensorflow 1.X that can be used as input to MIGraphX. Specifically, this is an example of exporting a frozen protobuf of a tensorflow BERT model. ## How to Use this Example In order to support bert from tensorflow's official [repository](https://github.com/google-research/bert), a serving_input_fn for the estimator must be implemented in [run_classifier.py](https://github.com/google-research/bert/blob/master/run_classifier.py). In this script, insert the following function after importing all libraries and setting up flags: ``` #... flags.DEFINE_integer( "num_tpu_cores", 8, "Only used if `use_tpu` is True. Total number of TPU cores to use.") # insert function here def serving_input_fn(): label_ids = tf.placeholder(tf.int32, [None], name='label_ids') input_ids = tf.placeholder(tf.int32, [None, FLAGS.max_seq_length], name='input_ids') input_mask = tf.placeholder(tf.int32, [None, FLAGS.max_seq_length], name='input_mask') segment_ids = tf.placeholder(tf.int32, [None, FLAGS.max_seq_length], name='segment_ids') input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn({ 'label_ids': label_ids, 'input_ids': input_ids, 'input_mask': input_mask, 'segment_ids': segment_ids, }, default_batch_size=1)() return input_fn ``` Since we are passing dynamic shape placeholders in the serving_input_fn, the default_batch_size value will essentially determine the resulting shape in the graph. For inference, we will focus on the "probabilities" layer's output, and we can name this layer by modifying the following [line](https://github.com/google-research/bert/blob/master/run_classifier.py#L608): ``` probabilities = tf.nn.softmax(logits, axis=-1, name="output") ``` Next, we need to export the saved model after training: ``` def main(_): # ... with tf.gfile.GFile(output_predict_file, "w") as writer: num_written_lines = 0 tf.logging.info("***** Predict results *****") for (i, prediction) in enumerate(result): probabilities = prediction["probabilities"] if i >= num_actual_predict_examples: break output_line = "\t".join( str(class_probability) for class_probability in probabilities) + "\n" writer.write(output_line) num_written_lines += 1 assert num_written_lines == num_actual_predict_examples # insert code here if FLAGS.do_train: # optional to attach export to train flag estimator._export_to_tpu = False estimator.export_savedmodel('saved_models', serving_input_fn) # ... ``` Run bert with the suggested arguments: ``` export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12 export GLUE_DIR=/path/to/glue python run_classifier.py \ --task_name=MRPC \ --do_train=true \ --do_eval=true \ --data_dir=$GLUE_DIR/MRPC \ --vocab_file=$BERT_BASE_DIR/vocab.txt \ --bert_config_file=$BERT_BASE_DIR/bert_config.json \ --init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \ --max_seq_length=128 \ --train_batch_size=32 \ # change to appropriate size that fits on GPU --learning_rate=2e-5 \ --num_train_epochs=3.0 \ --output_dir=/tmp/mrpc_output/ ``` When running, search for the following lines in the output: ``` INFO:tensorflow:Restoring parameters from /tmp/model.ckpt-1603 INFO:tensorflow:SavedModel written to: saved_models/temp-1564086017/saved_model.pb ``` Note the ID followed by "temp-" (in this case, 1564086017). A directory should exist under saved_models/ that is named with the ID. We also need to record the name of the output layer in bert. This can be done by inspecting the saved model. ``` saved_model_cli show --dir saved_models/1564086017 --tag_set serve --signature_def serving_default ``` The output should look like this: ``` The given SavedModel SignatureDef contains the following input(s): inputs['input_ids'] tensor_info: dtype: DT_INT32 shape: (1, 128) name: input_ids_1:0 inputs['input_mask'] tensor_info: dtype: DT_INT32 shape: (1, 128) name: input_mask_1:0 inputs['label_ids'] tensor_info: dtype: DT_INT32 shape: (1) name: label_ids_1:0 inputs['segment_ids'] tensor_info: dtype: DT_INT32 shape: (1, 128) name: segment_ids_1:0 The given SavedModel SignatureDef contains the following output(s): outputs['probabilities'] tensor_info: dtype: DT_FLOAT shape: (1, 2) name: loss/output:0 Method name is: tensorflow/serving/predict ``` Here the output name is given as "loss/output:0", but we will strip the ":0" from the end, as we are concerned with the node only. We will use tensorflow's freeze graph utility script and the information gathered above to create the frozen protobuf file. ``` CKPT_NUM=1603 MODEL_ID=1564086017 OUT_NAME=loss/output cd /path/to/tensorflow python tensorflow/python/tools/freeze_graph.py \ --input_graph=/tmp/mrpc_model/graph.pbtxt \ --input_binary=false \ --input_checkpoint=/tmp/mrpc_model/model.ckpt-${CKPT_NUM} \ --input_saved_model_dir=/path/to/bert/saved_models/${MODEL_ID} \ --output_graph=/tmp/frozen_bert.pb \ --output_node_names=${OUT_NAME} ``` The final output should be a frozen protobuf that is compatible with MIGraphX.ROCm-AMDMIGraphX-46524e8/examples/migraphx/export_frozen_graph_tf2/000077500000000000000000000000001510465702400250355ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/export_frozen_graph_tf2/.ipynb_checkpoints/000077500000000000000000000000001510465702400306265ustar00rootroot00000000000000example-checkpoint.ipynb000066400000000000000000007422751510465702400354130ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/export_frozen_graph_tf2/.ipynb_checkpoints{ "cells": [ { "cell_type": "code", "metadata": {}, "source": [ "# The MIT License (MIT)", "#", "# Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved.", "#", "# 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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exporting Frozen Graphs in Tensorflow 2 \n", "In order to use a trained model as input to MIGraphX, the model must be first be saved in a frozen graph format. This was accomplished in Tensorflow 1 by launching a graph in a tf.Session and then saving the session. However, Tensorflow has decided to deprecate Sessions in favor of functions and SavedModel format. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After importing the necessary libraries, the next step is to instantiate a model. For simplicity, in this example we will use a resnet50 architecture with pre-trained imagenet weights. These weights may also be trained or fine-tuned before freezing. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model: \"resnet50\"\n", "__________________________________________________________________________________________________\n", "Layer (type) Output Shape Param # Connected to \n", "==================================================================================================\n", "input_1 (InputLayer) [(None, 224, 224, 3) 0 \n", "__________________________________________________________________________________________________\n", "conv1_pad (ZeroPadding2D) (None, 230, 230, 3) 0 input_1[0][0] \n", "__________________________________________________________________________________________________\n", "conv1_conv (Conv2D) (None, 112, 112, 64) 9472 conv1_pad[0][0] \n", "__________________________________________________________________________________________________\n", "conv1_bn (BatchNormalization) (None, 112, 112, 64) 256 conv1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv1_relu (Activation) (None, 112, 112, 64) 0 conv1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "pool1_pad (ZeroPadding2D) (None, 114, 114, 64) 0 conv1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "pool1_pool (MaxPooling2D) (None, 56, 56, 64) 0 pool1_pad[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_1_conv (Conv2D) (None, 56, 56, 64) 4160 pool1_pool[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_1_bn (BatchNormali (None, 56, 56, 64) 256 conv2_block1_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_1_relu (Activation (None, 56, 56, 64) 0 conv2_block1_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_2_conv (Conv2D) (None, 56, 56, 64) 36928 conv2_block1_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_2_bn (BatchNormali (None, 56, 56, 64) 256 conv2_block1_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_2_relu (Activation (None, 56, 56, 64) 0 conv2_block1_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_0_conv (Conv2D) (None, 56, 56, 256) 16640 pool1_pool[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_3_conv (Conv2D) (None, 56, 56, 256) 16640 conv2_block1_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_0_bn (BatchNormali (None, 56, 56, 256) 1024 conv2_block1_0_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_3_bn (BatchNormali (None, 56, 56, 256) 1024 conv2_block1_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_add (Add) (None, 56, 56, 256) 0 conv2_block1_0_bn[0][0] \n", " conv2_block1_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block1_out (Activation) (None, 56, 56, 256) 0 conv2_block1_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_1_conv (Conv2D) (None, 56, 56, 64) 16448 conv2_block1_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_1_bn (BatchNormali (None, 56, 56, 64) 256 conv2_block2_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_1_relu (Activation (None, 56, 56, 64) 0 conv2_block2_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_2_conv (Conv2D) (None, 56, 56, 64) 36928 conv2_block2_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_2_bn (BatchNormali (None, 56, 56, 64) 256 conv2_block2_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_2_relu (Activation (None, 56, 56, 64) 0 conv2_block2_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_3_conv (Conv2D) (None, 56, 56, 256) 16640 conv2_block2_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_3_bn (BatchNormali (None, 56, 56, 256) 1024 conv2_block2_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_add (Add) (None, 56, 56, 256) 0 conv2_block1_out[0][0] \n", " conv2_block2_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block2_out (Activation) (None, 56, 56, 256) 0 conv2_block2_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_1_conv (Conv2D) (None, 56, 56, 64) 16448 conv2_block2_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_1_bn (BatchNormali (None, 56, 56, 64) 256 conv2_block3_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_1_relu (Activation (None, 56, 56, 64) 0 conv2_block3_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_2_conv (Conv2D) (None, 56, 56, 64) 36928 conv2_block3_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_2_bn (BatchNormali (None, 56, 56, 64) 256 conv2_block3_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_2_relu (Activation (None, 56, 56, 64) 0 conv2_block3_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_3_conv (Conv2D) (None, 56, 56, 256) 16640 conv2_block3_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_3_bn (BatchNormali (None, 56, 56, 256) 1024 conv2_block3_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_add (Add) (None, 56, 56, 256) 0 conv2_block2_out[0][0] \n", " conv2_block3_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv2_block3_out (Activation) (None, 56, 56, 256) 0 conv2_block3_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_1_conv (Conv2D) (None, 28, 28, 128) 32896 conv2_block3_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_1_bn (BatchNormali (None, 28, 28, 128) 512 conv3_block1_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_1_relu (Activation (None, 28, 28, 128) 0 conv3_block1_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_2_conv (Conv2D) (None, 28, 28, 128) 147584 conv3_block1_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_2_bn (BatchNormali (None, 28, 28, 128) 512 conv3_block1_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_2_relu (Activation (None, 28, 28, 128) 0 conv3_block1_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_0_conv (Conv2D) (None, 28, 28, 512) 131584 conv2_block3_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_3_conv (Conv2D) (None, 28, 28, 512) 66048 conv3_block1_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_0_bn (BatchNormali (None, 28, 28, 512) 2048 conv3_block1_0_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_3_bn (BatchNormali (None, 28, 28, 512) 2048 conv3_block1_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_add (Add) (None, 28, 28, 512) 0 conv3_block1_0_bn[0][0] \n", " conv3_block1_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block1_out (Activation) (None, 28, 28, 512) 0 conv3_block1_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_1_conv (Conv2D) (None, 28, 28, 128) 65664 conv3_block1_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_1_bn (BatchNormali (None, 28, 28, 128) 512 conv3_block2_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_1_relu (Activation (None, 28, 28, 128) 0 conv3_block2_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_2_conv (Conv2D) (None, 28, 28, 128) 147584 conv3_block2_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_2_bn (BatchNormali (None, 28, 28, 128) 512 conv3_block2_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_2_relu (Activation (None, 28, 28, 128) 0 conv3_block2_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_3_conv (Conv2D) (None, 28, 28, 512) 66048 conv3_block2_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_3_bn (BatchNormali (None, 28, 28, 512) 2048 conv3_block2_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_add (Add) (None, 28, 28, 512) 0 conv3_block1_out[0][0] \n", " conv3_block2_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block2_out (Activation) (None, 28, 28, 512) 0 conv3_block2_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_1_conv (Conv2D) (None, 28, 28, 128) 65664 conv3_block2_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_1_bn (BatchNormali (None, 28, 28, 128) 512 conv3_block3_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_1_relu (Activation (None, 28, 28, 128) 0 conv3_block3_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_2_conv (Conv2D) (None, 28, 28, 128) 147584 conv3_block3_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_2_bn (BatchNormali (None, 28, 28, 128) 512 conv3_block3_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_2_relu (Activation (None, 28, 28, 128) 0 conv3_block3_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_3_conv (Conv2D) (None, 28, 28, 512) 66048 conv3_block3_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_3_bn (BatchNormali (None, 28, 28, 512) 2048 conv3_block3_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_add (Add) (None, 28, 28, 512) 0 conv3_block2_out[0][0] \n", " conv3_block3_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block3_out (Activation) (None, 28, 28, 512) 0 conv3_block3_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_1_conv (Conv2D) (None, 28, 28, 128) 65664 conv3_block3_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_1_bn (BatchNormali (None, 28, 28, 128) 512 conv3_block4_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_1_relu (Activation (None, 28, 28, 128) 0 conv3_block4_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_2_conv (Conv2D) (None, 28, 28, 128) 147584 conv3_block4_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_2_bn (BatchNormali (None, 28, 28, 128) 512 conv3_block4_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_2_relu (Activation (None, 28, 28, 128) 0 conv3_block4_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_3_conv (Conv2D) (None, 28, 28, 512) 66048 conv3_block4_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_3_bn (BatchNormali (None, 28, 28, 512) 2048 conv3_block4_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_add (Add) (None, 28, 28, 512) 0 conv3_block3_out[0][0] \n", " conv3_block4_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv3_block4_out (Activation) (None, 28, 28, 512) 0 conv3_block4_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_1_conv (Conv2D) (None, 14, 14, 256) 131328 conv3_block4_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_1_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block1_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_1_relu (Activation (None, 14, 14, 256) 0 conv4_block1_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_2_conv (Conv2D) (None, 14, 14, 256) 590080 conv4_block1_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_2_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block1_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_2_relu (Activation (None, 14, 14, 256) 0 conv4_block1_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_0_conv (Conv2D) (None, 14, 14, 1024) 525312 conv3_block4_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_3_conv (Conv2D) (None, 14, 14, 1024) 263168 conv4_block1_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_0_bn (BatchNormali (None, 14, 14, 1024) 4096 conv4_block1_0_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_3_bn (BatchNormali (None, 14, 14, 1024) 4096 conv4_block1_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_add (Add) (None, 14, 14, 1024) 0 conv4_block1_0_bn[0][0] \n", " conv4_block1_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block1_out (Activation) (None, 14, 14, 1024) 0 conv4_block1_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_1_conv (Conv2D) (None, 14, 14, 256) 262400 conv4_block1_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_1_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block2_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_1_relu (Activation (None, 14, 14, 256) 0 conv4_block2_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_2_conv (Conv2D) (None, 14, 14, 256) 590080 conv4_block2_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_2_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block2_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_2_relu (Activation (None, 14, 14, 256) 0 conv4_block2_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_3_conv (Conv2D) (None, 14, 14, 1024) 263168 conv4_block2_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_3_bn (BatchNormali (None, 14, 14, 1024) 4096 conv4_block2_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_add (Add) (None, 14, 14, 1024) 0 conv4_block1_out[0][0] \n", " conv4_block2_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block2_out (Activation) (None, 14, 14, 1024) 0 conv4_block2_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_1_conv (Conv2D) (None, 14, 14, 256) 262400 conv4_block2_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_1_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block3_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_1_relu (Activation (None, 14, 14, 256) 0 conv4_block3_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_2_conv (Conv2D) (None, 14, 14, 256) 590080 conv4_block3_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_2_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block3_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_2_relu (Activation (None, 14, 14, 256) 0 conv4_block3_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_3_conv (Conv2D) (None, 14, 14, 1024) 263168 conv4_block3_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_3_bn (BatchNormali (None, 14, 14, 1024) 4096 conv4_block3_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_add (Add) (None, 14, 14, 1024) 0 conv4_block2_out[0][0] \n", " conv4_block3_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block3_out (Activation) (None, 14, 14, 1024) 0 conv4_block3_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_1_conv (Conv2D) (None, 14, 14, 256) 262400 conv4_block3_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_1_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block4_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_1_relu (Activation (None, 14, 14, 256) 0 conv4_block4_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_2_conv (Conv2D) (None, 14, 14, 256) 590080 conv4_block4_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_2_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block4_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_2_relu (Activation (None, 14, 14, 256) 0 conv4_block4_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_3_conv (Conv2D) (None, 14, 14, 1024) 263168 conv4_block4_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_3_bn (BatchNormali (None, 14, 14, 1024) 4096 conv4_block4_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_add (Add) (None, 14, 14, 1024) 0 conv4_block3_out[0][0] \n", " conv4_block4_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block4_out (Activation) (None, 14, 14, 1024) 0 conv4_block4_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_1_conv (Conv2D) (None, 14, 14, 256) 262400 conv4_block4_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_1_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block5_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_1_relu (Activation (None, 14, 14, 256) 0 conv4_block5_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_2_conv (Conv2D) (None, 14, 14, 256) 590080 conv4_block5_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_2_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block5_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_2_relu (Activation (None, 14, 14, 256) 0 conv4_block5_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_3_conv (Conv2D) (None, 14, 14, 1024) 263168 conv4_block5_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_3_bn (BatchNormali (None, 14, 14, 1024) 4096 conv4_block5_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_add (Add) (None, 14, 14, 1024) 0 conv4_block4_out[0][0] \n", " conv4_block5_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block5_out (Activation) (None, 14, 14, 1024) 0 conv4_block5_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_1_conv (Conv2D) (None, 14, 14, 256) 262400 conv4_block5_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_1_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block6_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_1_relu (Activation (None, 14, 14, 256) 0 conv4_block6_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_2_conv (Conv2D) (None, 14, 14, 256) 590080 conv4_block6_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_2_bn (BatchNormali (None, 14, 14, 256) 1024 conv4_block6_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_2_relu (Activation (None, 14, 14, 256) 0 conv4_block6_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_3_conv (Conv2D) (None, 14, 14, 1024) 263168 conv4_block6_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_3_bn (BatchNormali (None, 14, 14, 1024) 4096 conv4_block6_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_add (Add) (None, 14, 14, 1024) 0 conv4_block5_out[0][0] \n", " conv4_block6_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv4_block6_out (Activation) (None, 14, 14, 1024) 0 conv4_block6_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_1_conv (Conv2D) (None, 7, 7, 512) 524800 conv4_block6_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_1_bn (BatchNormali (None, 7, 7, 512) 2048 conv5_block1_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_1_relu (Activation (None, 7, 7, 512) 0 conv5_block1_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_2_conv (Conv2D) (None, 7, 7, 512) 2359808 conv5_block1_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_2_bn (BatchNormali (None, 7, 7, 512) 2048 conv5_block1_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_2_relu (Activation (None, 7, 7, 512) 0 conv5_block1_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_0_conv (Conv2D) (None, 7, 7, 2048) 2099200 conv4_block6_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_3_conv (Conv2D) (None, 7, 7, 2048) 1050624 conv5_block1_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_0_bn (BatchNormali (None, 7, 7, 2048) 8192 conv5_block1_0_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_3_bn (BatchNormali (None, 7, 7, 2048) 8192 conv5_block1_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_add (Add) (None, 7, 7, 2048) 0 conv5_block1_0_bn[0][0] \n", " conv5_block1_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block1_out (Activation) (None, 7, 7, 2048) 0 conv5_block1_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_1_conv (Conv2D) (None, 7, 7, 512) 1049088 conv5_block1_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_1_bn (BatchNormali (None, 7, 7, 512) 2048 conv5_block2_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_1_relu (Activation (None, 7, 7, 512) 0 conv5_block2_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_2_conv (Conv2D) (None, 7, 7, 512) 2359808 conv5_block2_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_2_bn (BatchNormali (None, 7, 7, 512) 2048 conv5_block2_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_2_relu (Activation (None, 7, 7, 512) 0 conv5_block2_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_3_conv (Conv2D) (None, 7, 7, 2048) 1050624 conv5_block2_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_3_bn (BatchNormali (None, 7, 7, 2048) 8192 conv5_block2_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_add (Add) (None, 7, 7, 2048) 0 conv5_block1_out[0][0] \n", " conv5_block2_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block2_out (Activation) (None, 7, 7, 2048) 0 conv5_block2_add[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_1_conv (Conv2D) (None, 7, 7, 512) 1049088 conv5_block2_out[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_1_bn (BatchNormali (None, 7, 7, 512) 2048 conv5_block3_1_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_1_relu (Activation (None, 7, 7, 512) 0 conv5_block3_1_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_2_conv (Conv2D) (None, 7, 7, 512) 2359808 conv5_block3_1_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_2_bn (BatchNormali (None, 7, 7, 512) 2048 conv5_block3_2_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_2_relu (Activation (None, 7, 7, 512) 0 conv5_block3_2_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_3_conv (Conv2D) (None, 7, 7, 2048) 1050624 conv5_block3_2_relu[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_3_bn (BatchNormali (None, 7, 7, 2048) 8192 conv5_block3_3_conv[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_add (Add) (None, 7, 7, 2048) 0 conv5_block2_out[0][0] \n", " conv5_block3_3_bn[0][0] \n", "__________________________________________________________________________________________________\n", "conv5_block3_out (Activation) (None, 7, 7, 2048) 0 conv5_block3_add[0][0] \n", "__________________________________________________________________________________________________\n", "avg_pool (GlobalAveragePooling2 (None, 2048) 0 conv5_block3_out[0][0] \n", "__________________________________________________________________________________________________\n", "probs (Dense) (None, 1000) 2049000 avg_pool[0][0] \n", "==================================================================================================\n", "Total params: 25,636,712\n", "Trainable params: 25,583,592\n", "Non-trainable params: 53,120\n", "__________________________________________________________________________________________________\n" ] } ], "source": [ "import tensorflow as tf\n", "tf.enable_eager_execution() #May not be required depending on tensorflow version\n", "from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2\n", "from tensorflow import keras\n", "from tensorflow.keras import layers\n", "\n", "MODEL_NAME = \"resnet50\"\n", "model = tf.keras.applications.ResNet50(weights=\"imagenet\")\n", "model.summary()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SavedModel format\n", "The simplest way to save a model is through saved\\_model.save()\n", "\n", "This will create an equivalent tensorflow program which can later be loaded for fine-tuning or inference, although it is not directly compatible with MIGraphX." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WARNING:tensorflow:From /home/amt/anaconda3/envs/tensorflow/lib/python3.7/site-packages/tensorflow_core/python/ops/resource_variable_ops.py:1781: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.\n", "Instructions for updating:\n", "If using Keras pass *_constraint arguments to layers.\n", "INFO:tensorflow:Assets written to: ./Saved_Models/resnet50/assets\n" ] } ], "source": [ "tf.saved_model.save(model, \"./Saved_Models/{}\".format(MODEL_NAME))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Convert to ConcreteFunction\n", "To begin, we need to get the function equivalent of the model and then concretize the function to avoid retracing." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "full_model = tf.function(lambda x: model(x))\n", "full_model = full_model.get_concrete_function(\n", " x=tf.TensorSpec(model.inputs[0].shape, model.inputs[0].dtype))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Freeze ConcreteFunction and Serialize\n", "Since we are saving the graph for the purpose of inference, all variables can be made constant (i.e. \"frozen\").\n", "\n", "Next, we need to obtain a serialized GraphDef representation of the graph. \n", "\n", "\n", "Optionally, the operators can be printed out layer by layer followed by the inputs and outputs." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--------------------------------------------------\n", "Frozen model layers: \n", "x\n", "resnet50/conv1_pad/Pad/paddings\n", "resnet50/conv1_pad/Pad\n", "resnet50/conv1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv1_conv/Conv2D\n", "resnet50/conv1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv1_conv/BiasAdd\n", "resnet50/conv1_bn/ReadVariableOp/resource\n", "resnet50/conv1_bn/ReadVariableOp\n", "resnet50/conv1_bn/ReadVariableOp_1/resource\n", "resnet50/conv1_bn/ReadVariableOp_1\n", "resnet50/conv1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv1_bn/FusedBatchNormV3\n", "resnet50/conv1_relu/Relu\n", "resnet50/pool1_pad/Pad/paddings\n", "resnet50/pool1_pad/Pad\n", "resnet50/pool1_pool/MaxPool\n", "resnet50/conv2_block1_0_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block1_0_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block1_0_conv/Conv2D\n", "resnet50/conv2_block1_0_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block1_0_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block1_0_conv/BiasAdd\n", "resnet50/conv2_block1_0_bn/ReadVariableOp/resource\n", "resnet50/conv2_block1_0_bn/ReadVariableOp\n", "resnet50/conv2_block1_0_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block1_0_bn/ReadVariableOp_1\n", "resnet50/conv2_block1_0_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block1_0_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block1_0_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block1_0_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block1_0_bn/FusedBatchNormV3\n", "resnet50/conv2_block1_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block1_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block1_1_conv/Conv2D\n", "resnet50/conv2_block1_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block1_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block1_1_conv/BiasAdd\n", "resnet50/conv2_block1_1_bn/ReadVariableOp/resource\n", "resnet50/conv2_block1_1_bn/ReadVariableOp\n", "resnet50/conv2_block1_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block1_1_bn/ReadVariableOp_1\n", "resnet50/conv2_block1_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block1_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block1_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block1_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block1_1_bn/FusedBatchNormV3\n", "resnet50/conv2_block1_1_relu/Relu\n", "resnet50/conv2_block1_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block1_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block1_2_conv/Conv2D\n", "resnet50/conv2_block1_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block1_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block1_2_conv/BiasAdd\n", "resnet50/conv2_block1_2_bn/ReadVariableOp/resource\n", "resnet50/conv2_block1_2_bn/ReadVariableOp\n", "resnet50/conv2_block1_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block1_2_bn/ReadVariableOp_1\n", "resnet50/conv2_block1_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block1_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block1_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block1_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block1_2_bn/FusedBatchNormV3\n", "resnet50/conv2_block1_2_relu/Relu\n", "resnet50/conv2_block1_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block1_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block1_3_conv/Conv2D\n", "resnet50/conv2_block1_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block1_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block1_3_conv/BiasAdd\n", "resnet50/conv2_block1_3_bn/ReadVariableOp/resource\n", "resnet50/conv2_block1_3_bn/ReadVariableOp\n", "resnet50/conv2_block1_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block1_3_bn/ReadVariableOp_1\n", "resnet50/conv2_block1_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block1_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block1_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block1_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block1_3_bn/FusedBatchNormV3\n", "resnet50/conv2_block1_add/add\n", "resnet50/conv2_block1_out/Relu\n", "resnet50/conv2_block2_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block2_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block2_1_conv/Conv2D\n", "resnet50/conv2_block2_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block2_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block2_1_conv/BiasAdd\n", "resnet50/conv2_block2_1_bn/ReadVariableOp/resource\n", "resnet50/conv2_block2_1_bn/ReadVariableOp\n", "resnet50/conv2_block2_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block2_1_bn/ReadVariableOp_1\n", "resnet50/conv2_block2_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block2_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block2_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block2_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block2_1_bn/FusedBatchNormV3\n", "resnet50/conv2_block2_1_relu/Relu\n", "resnet50/conv2_block2_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block2_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block2_2_conv/Conv2D\n", "resnet50/conv2_block2_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block2_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block2_2_conv/BiasAdd\n", "resnet50/conv2_block2_2_bn/ReadVariableOp/resource\n", "resnet50/conv2_block2_2_bn/ReadVariableOp\n", "resnet50/conv2_block2_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block2_2_bn/ReadVariableOp_1\n", "resnet50/conv2_block2_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block2_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block2_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block2_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block2_2_bn/FusedBatchNormV3\n", "resnet50/conv2_block2_2_relu/Relu\n", "resnet50/conv2_block2_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block2_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block2_3_conv/Conv2D\n", "resnet50/conv2_block2_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block2_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block2_3_conv/BiasAdd\n", "resnet50/conv2_block2_3_bn/ReadVariableOp/resource\n", "resnet50/conv2_block2_3_bn/ReadVariableOp\n", "resnet50/conv2_block2_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block2_3_bn/ReadVariableOp_1\n", "resnet50/conv2_block2_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block2_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block2_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block2_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block2_3_bn/FusedBatchNormV3\n", "resnet50/conv2_block2_add/add\n", "resnet50/conv2_block2_out/Relu\n", "resnet50/conv2_block3_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block3_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block3_1_conv/Conv2D\n", "resnet50/conv2_block3_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block3_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block3_1_conv/BiasAdd\n", "resnet50/conv2_block3_1_bn/ReadVariableOp/resource\n", "resnet50/conv2_block3_1_bn/ReadVariableOp\n", "resnet50/conv2_block3_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block3_1_bn/ReadVariableOp_1\n", "resnet50/conv2_block3_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block3_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block3_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block3_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block3_1_bn/FusedBatchNormV3\n", "resnet50/conv2_block3_1_relu/Relu\n", "resnet50/conv2_block3_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block3_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block3_2_conv/Conv2D\n", "resnet50/conv2_block3_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block3_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block3_2_conv/BiasAdd\n", "resnet50/conv2_block3_2_bn/ReadVariableOp/resource\n", "resnet50/conv2_block3_2_bn/ReadVariableOp\n", "resnet50/conv2_block3_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block3_2_bn/ReadVariableOp_1\n", "resnet50/conv2_block3_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block3_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block3_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block3_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block3_2_bn/FusedBatchNormV3\n", "resnet50/conv2_block3_2_relu/Relu\n", "resnet50/conv2_block3_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv2_block3_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv2_block3_3_conv/Conv2D\n", "resnet50/conv2_block3_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv2_block3_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv2_block3_3_conv/BiasAdd\n", "resnet50/conv2_block3_3_bn/ReadVariableOp/resource\n", "resnet50/conv2_block3_3_bn/ReadVariableOp\n", "resnet50/conv2_block3_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv2_block3_3_bn/ReadVariableOp_1\n", "resnet50/conv2_block3_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv2_block3_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv2_block3_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv2_block3_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv2_block3_3_bn/FusedBatchNormV3\n", "resnet50/conv2_block3_add/add\n", "resnet50/conv2_block3_out/Relu\n", "resnet50/conv3_block1_0_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block1_0_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block1_0_conv/Conv2D\n", "resnet50/conv3_block1_0_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block1_0_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block1_0_conv/BiasAdd\n", "resnet50/conv3_block1_0_bn/ReadVariableOp/resource\n", "resnet50/conv3_block1_0_bn/ReadVariableOp\n", "resnet50/conv3_block1_0_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block1_0_bn/ReadVariableOp_1\n", "resnet50/conv3_block1_0_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block1_0_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block1_0_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block1_0_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block1_0_bn/FusedBatchNormV3\n", "resnet50/conv3_block1_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block1_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block1_1_conv/Conv2D\n", "resnet50/conv3_block1_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block1_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block1_1_conv/BiasAdd\n", "resnet50/conv3_block1_1_bn/ReadVariableOp/resource\n", "resnet50/conv3_block1_1_bn/ReadVariableOp\n", "resnet50/conv3_block1_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block1_1_bn/ReadVariableOp_1\n", "resnet50/conv3_block1_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block1_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block1_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block1_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block1_1_bn/FusedBatchNormV3\n", "resnet50/conv3_block1_1_relu/Relu\n", "resnet50/conv3_block1_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block1_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block1_2_conv/Conv2D\n", "resnet50/conv3_block1_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block1_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block1_2_conv/BiasAdd\n", "resnet50/conv3_block1_2_bn/ReadVariableOp/resource\n", "resnet50/conv3_block1_2_bn/ReadVariableOp\n", "resnet50/conv3_block1_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block1_2_bn/ReadVariableOp_1\n", "resnet50/conv3_block1_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block1_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block1_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block1_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block1_2_bn/FusedBatchNormV3\n", "resnet50/conv3_block1_2_relu/Relu\n", "resnet50/conv3_block1_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block1_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block1_3_conv/Conv2D\n", "resnet50/conv3_block1_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block1_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block1_3_conv/BiasAdd\n", "resnet50/conv3_block1_3_bn/ReadVariableOp/resource\n", "resnet50/conv3_block1_3_bn/ReadVariableOp\n", "resnet50/conv3_block1_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block1_3_bn/ReadVariableOp_1\n", "resnet50/conv3_block1_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block1_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block1_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block1_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block1_3_bn/FusedBatchNormV3\n", "resnet50/conv3_block1_add/add\n", "resnet50/conv3_block1_out/Relu\n", "resnet50/conv3_block2_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block2_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block2_1_conv/Conv2D\n", "resnet50/conv3_block2_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block2_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block2_1_conv/BiasAdd\n", "resnet50/conv3_block2_1_bn/ReadVariableOp/resource\n", "resnet50/conv3_block2_1_bn/ReadVariableOp\n", "resnet50/conv3_block2_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block2_1_bn/ReadVariableOp_1\n", "resnet50/conv3_block2_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block2_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block2_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block2_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block2_1_bn/FusedBatchNormV3\n", "resnet50/conv3_block2_1_relu/Relu\n", "resnet50/conv3_block2_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block2_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block2_2_conv/Conv2D\n", "resnet50/conv3_block2_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block2_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block2_2_conv/BiasAdd\n", "resnet50/conv3_block2_2_bn/ReadVariableOp/resource\n", "resnet50/conv3_block2_2_bn/ReadVariableOp\n", "resnet50/conv3_block2_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block2_2_bn/ReadVariableOp_1\n", "resnet50/conv3_block2_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block2_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block2_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block2_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block2_2_bn/FusedBatchNormV3\n", "resnet50/conv3_block2_2_relu/Relu\n", "resnet50/conv3_block2_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block2_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block2_3_conv/Conv2D\n", "resnet50/conv3_block2_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block2_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block2_3_conv/BiasAdd\n", "resnet50/conv3_block2_3_bn/ReadVariableOp/resource\n", "resnet50/conv3_block2_3_bn/ReadVariableOp\n", "resnet50/conv3_block2_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block2_3_bn/ReadVariableOp_1\n", "resnet50/conv3_block2_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block2_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block2_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block2_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block2_3_bn/FusedBatchNormV3\n", "resnet50/conv3_block2_add/add\n", "resnet50/conv3_block2_out/Relu\n", "resnet50/conv3_block3_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block3_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block3_1_conv/Conv2D\n", "resnet50/conv3_block3_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block3_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block3_1_conv/BiasAdd\n", "resnet50/conv3_block3_1_bn/ReadVariableOp/resource\n", "resnet50/conv3_block3_1_bn/ReadVariableOp\n", "resnet50/conv3_block3_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block3_1_bn/ReadVariableOp_1\n", "resnet50/conv3_block3_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block3_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block3_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block3_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block3_1_bn/FusedBatchNormV3\n", "resnet50/conv3_block3_1_relu/Relu\n", "resnet50/conv3_block3_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block3_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block3_2_conv/Conv2D\n", "resnet50/conv3_block3_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block3_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block3_2_conv/BiasAdd\n", "resnet50/conv3_block3_2_bn/ReadVariableOp/resource\n", "resnet50/conv3_block3_2_bn/ReadVariableOp\n", "resnet50/conv3_block3_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block3_2_bn/ReadVariableOp_1\n", "resnet50/conv3_block3_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block3_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block3_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block3_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block3_2_bn/FusedBatchNormV3\n", "resnet50/conv3_block3_2_relu/Relu\n", "resnet50/conv3_block3_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block3_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block3_3_conv/Conv2D\n", "resnet50/conv3_block3_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block3_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block3_3_conv/BiasAdd\n", "resnet50/conv3_block3_3_bn/ReadVariableOp/resource\n", "resnet50/conv3_block3_3_bn/ReadVariableOp\n", "resnet50/conv3_block3_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block3_3_bn/ReadVariableOp_1\n", "resnet50/conv3_block3_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block3_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block3_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block3_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block3_3_bn/FusedBatchNormV3\n", "resnet50/conv3_block3_add/add\n", "resnet50/conv3_block3_out/Relu\n", "resnet50/conv3_block4_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block4_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block4_1_conv/Conv2D\n", "resnet50/conv3_block4_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block4_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block4_1_conv/BiasAdd\n", "resnet50/conv3_block4_1_bn/ReadVariableOp/resource\n", "resnet50/conv3_block4_1_bn/ReadVariableOp\n", "resnet50/conv3_block4_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block4_1_bn/ReadVariableOp_1\n", "resnet50/conv3_block4_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block4_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block4_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block4_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block4_1_bn/FusedBatchNormV3\n", "resnet50/conv3_block4_1_relu/Relu\n", "resnet50/conv3_block4_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block4_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block4_2_conv/Conv2D\n", "resnet50/conv3_block4_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block4_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block4_2_conv/BiasAdd\n", "resnet50/conv3_block4_2_bn/ReadVariableOp/resource\n", "resnet50/conv3_block4_2_bn/ReadVariableOp\n", "resnet50/conv3_block4_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block4_2_bn/ReadVariableOp_1\n", "resnet50/conv3_block4_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block4_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block4_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block4_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block4_2_bn/FusedBatchNormV3\n", "resnet50/conv3_block4_2_relu/Relu\n", "resnet50/conv3_block4_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv3_block4_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv3_block4_3_conv/Conv2D\n", "resnet50/conv3_block4_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv3_block4_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv3_block4_3_conv/BiasAdd\n", "resnet50/conv3_block4_3_bn/ReadVariableOp/resource\n", "resnet50/conv3_block4_3_bn/ReadVariableOp\n", "resnet50/conv3_block4_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv3_block4_3_bn/ReadVariableOp_1\n", "resnet50/conv3_block4_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv3_block4_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv3_block4_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv3_block4_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv3_block4_3_bn/FusedBatchNormV3\n", "resnet50/conv3_block4_add/add\n", "resnet50/conv3_block4_out/Relu\n", "resnet50/conv4_block1_0_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block1_0_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block1_0_conv/Conv2D\n", "resnet50/conv4_block1_0_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block1_0_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block1_0_conv/BiasAdd\n", "resnet50/conv4_block1_0_bn/ReadVariableOp/resource\n", "resnet50/conv4_block1_0_bn/ReadVariableOp\n", "resnet50/conv4_block1_0_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block1_0_bn/ReadVariableOp_1\n", "resnet50/conv4_block1_0_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block1_0_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block1_0_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block1_0_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block1_0_bn/FusedBatchNormV3\n", "resnet50/conv4_block1_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block1_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block1_1_conv/Conv2D\n", "resnet50/conv4_block1_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block1_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block1_1_conv/BiasAdd\n", "resnet50/conv4_block1_1_bn/ReadVariableOp/resource\n", "resnet50/conv4_block1_1_bn/ReadVariableOp\n", "resnet50/conv4_block1_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block1_1_bn/ReadVariableOp_1\n", "resnet50/conv4_block1_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block1_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block1_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block1_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block1_1_bn/FusedBatchNormV3\n", "resnet50/conv4_block1_1_relu/Relu\n", "resnet50/conv4_block1_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block1_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block1_2_conv/Conv2D\n", "resnet50/conv4_block1_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block1_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block1_2_conv/BiasAdd\n", "resnet50/conv4_block1_2_bn/ReadVariableOp/resource\n", "resnet50/conv4_block1_2_bn/ReadVariableOp\n", "resnet50/conv4_block1_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block1_2_bn/ReadVariableOp_1\n", "resnet50/conv4_block1_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block1_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block1_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block1_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block1_2_bn/FusedBatchNormV3\n", "resnet50/conv4_block1_2_relu/Relu\n", "resnet50/conv4_block1_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block1_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block1_3_conv/Conv2D\n", "resnet50/conv4_block1_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block1_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block1_3_conv/BiasAdd\n", "resnet50/conv4_block1_3_bn/ReadVariableOp/resource\n", "resnet50/conv4_block1_3_bn/ReadVariableOp\n", "resnet50/conv4_block1_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block1_3_bn/ReadVariableOp_1\n", "resnet50/conv4_block1_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block1_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block1_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block1_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block1_3_bn/FusedBatchNormV3\n", "resnet50/conv4_block1_add/add\n", "resnet50/conv4_block1_out/Relu\n", "resnet50/conv4_block2_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block2_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block2_1_conv/Conv2D\n", "resnet50/conv4_block2_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block2_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block2_1_conv/BiasAdd\n", "resnet50/conv4_block2_1_bn/ReadVariableOp/resource\n", "resnet50/conv4_block2_1_bn/ReadVariableOp\n", "resnet50/conv4_block2_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block2_1_bn/ReadVariableOp_1\n", "resnet50/conv4_block2_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block2_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block2_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block2_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block2_1_bn/FusedBatchNormV3\n", "resnet50/conv4_block2_1_relu/Relu\n", "resnet50/conv4_block2_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block2_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block2_2_conv/Conv2D\n", "resnet50/conv4_block2_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block2_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block2_2_conv/BiasAdd\n", "resnet50/conv4_block2_2_bn/ReadVariableOp/resource\n", "resnet50/conv4_block2_2_bn/ReadVariableOp\n", "resnet50/conv4_block2_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block2_2_bn/ReadVariableOp_1\n", "resnet50/conv4_block2_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block2_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block2_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block2_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block2_2_bn/FusedBatchNormV3\n", "resnet50/conv4_block2_2_relu/Relu\n", "resnet50/conv4_block2_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block2_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block2_3_conv/Conv2D\n", "resnet50/conv4_block2_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block2_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block2_3_conv/BiasAdd\n", "resnet50/conv4_block2_3_bn/ReadVariableOp/resource\n", "resnet50/conv4_block2_3_bn/ReadVariableOp\n", "resnet50/conv4_block2_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block2_3_bn/ReadVariableOp_1\n", "resnet50/conv4_block2_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block2_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block2_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block2_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block2_3_bn/FusedBatchNormV3\n", "resnet50/conv4_block2_add/add\n", "resnet50/conv4_block2_out/Relu\n", "resnet50/conv4_block3_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block3_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block3_1_conv/Conv2D\n", "resnet50/conv4_block3_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block3_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block3_1_conv/BiasAdd\n", "resnet50/conv4_block3_1_bn/ReadVariableOp/resource\n", "resnet50/conv4_block3_1_bn/ReadVariableOp\n", "resnet50/conv4_block3_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block3_1_bn/ReadVariableOp_1\n", "resnet50/conv4_block3_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block3_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block3_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block3_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block3_1_bn/FusedBatchNormV3\n", "resnet50/conv4_block3_1_relu/Relu\n", "resnet50/conv4_block3_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block3_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block3_2_conv/Conv2D\n", "resnet50/conv4_block3_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block3_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block3_2_conv/BiasAdd\n", "resnet50/conv4_block3_2_bn/ReadVariableOp/resource\n", "resnet50/conv4_block3_2_bn/ReadVariableOp\n", "resnet50/conv4_block3_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block3_2_bn/ReadVariableOp_1\n", "resnet50/conv4_block3_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block3_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block3_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block3_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block3_2_bn/FusedBatchNormV3\n", "resnet50/conv4_block3_2_relu/Relu\n", "resnet50/conv4_block3_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block3_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block3_3_conv/Conv2D\n", "resnet50/conv4_block3_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block3_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block3_3_conv/BiasAdd\n", "resnet50/conv4_block3_3_bn/ReadVariableOp/resource\n", "resnet50/conv4_block3_3_bn/ReadVariableOp\n", "resnet50/conv4_block3_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block3_3_bn/ReadVariableOp_1\n", "resnet50/conv4_block3_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block3_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block3_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block3_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block3_3_bn/FusedBatchNormV3\n", "resnet50/conv4_block3_add/add\n", "resnet50/conv4_block3_out/Relu\n", "resnet50/conv4_block4_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block4_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block4_1_conv/Conv2D\n", "resnet50/conv4_block4_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block4_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block4_1_conv/BiasAdd\n", "resnet50/conv4_block4_1_bn/ReadVariableOp/resource\n", "resnet50/conv4_block4_1_bn/ReadVariableOp\n", "resnet50/conv4_block4_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block4_1_bn/ReadVariableOp_1\n", "resnet50/conv4_block4_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block4_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block4_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block4_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block4_1_bn/FusedBatchNormV3\n", "resnet50/conv4_block4_1_relu/Relu\n", "resnet50/conv4_block4_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block4_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block4_2_conv/Conv2D\n", "resnet50/conv4_block4_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block4_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block4_2_conv/BiasAdd\n", "resnet50/conv4_block4_2_bn/ReadVariableOp/resource\n", "resnet50/conv4_block4_2_bn/ReadVariableOp\n", "resnet50/conv4_block4_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block4_2_bn/ReadVariableOp_1\n", "resnet50/conv4_block4_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block4_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block4_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block4_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block4_2_bn/FusedBatchNormV3\n", "resnet50/conv4_block4_2_relu/Relu\n", "resnet50/conv4_block4_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block4_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block4_3_conv/Conv2D\n", "resnet50/conv4_block4_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block4_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block4_3_conv/BiasAdd\n", "resnet50/conv4_block4_3_bn/ReadVariableOp/resource\n", "resnet50/conv4_block4_3_bn/ReadVariableOp\n", "resnet50/conv4_block4_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block4_3_bn/ReadVariableOp_1\n", "resnet50/conv4_block4_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block4_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block4_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block4_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block4_3_bn/FusedBatchNormV3\n", "resnet50/conv4_block4_add/add\n", "resnet50/conv4_block4_out/Relu\n", "resnet50/conv4_block5_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block5_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block5_1_conv/Conv2D\n", "resnet50/conv4_block5_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block5_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block5_1_conv/BiasAdd\n", "resnet50/conv4_block5_1_bn/ReadVariableOp/resource\n", "resnet50/conv4_block5_1_bn/ReadVariableOp\n", "resnet50/conv4_block5_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block5_1_bn/ReadVariableOp_1\n", "resnet50/conv4_block5_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block5_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block5_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block5_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block5_1_bn/FusedBatchNormV3\n", "resnet50/conv4_block5_1_relu/Relu\n", "resnet50/conv4_block5_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block5_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block5_2_conv/Conv2D\n", "resnet50/conv4_block5_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block5_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block5_2_conv/BiasAdd\n", "resnet50/conv4_block5_2_bn/ReadVariableOp/resource\n", "resnet50/conv4_block5_2_bn/ReadVariableOp\n", "resnet50/conv4_block5_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block5_2_bn/ReadVariableOp_1\n", "resnet50/conv4_block5_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block5_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block5_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block5_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block5_2_bn/FusedBatchNormV3\n", "resnet50/conv4_block5_2_relu/Relu\n", "resnet50/conv4_block5_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block5_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block5_3_conv/Conv2D\n", "resnet50/conv4_block5_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block5_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block5_3_conv/BiasAdd\n", "resnet50/conv4_block5_3_bn/ReadVariableOp/resource\n", "resnet50/conv4_block5_3_bn/ReadVariableOp\n", "resnet50/conv4_block5_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block5_3_bn/ReadVariableOp_1\n", "resnet50/conv4_block5_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block5_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block5_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block5_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block5_3_bn/FusedBatchNormV3\n", "resnet50/conv4_block5_add/add\n", "resnet50/conv4_block5_out/Relu\n", "resnet50/conv4_block6_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block6_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block6_1_conv/Conv2D\n", "resnet50/conv4_block6_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block6_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block6_1_conv/BiasAdd\n", "resnet50/conv4_block6_1_bn/ReadVariableOp/resource\n", "resnet50/conv4_block6_1_bn/ReadVariableOp\n", "resnet50/conv4_block6_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block6_1_bn/ReadVariableOp_1\n", "resnet50/conv4_block6_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block6_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block6_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block6_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block6_1_bn/FusedBatchNormV3\n", "resnet50/conv4_block6_1_relu/Relu\n", "resnet50/conv4_block6_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block6_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block6_2_conv/Conv2D\n", "resnet50/conv4_block6_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block6_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block6_2_conv/BiasAdd\n", "resnet50/conv4_block6_2_bn/ReadVariableOp/resource\n", "resnet50/conv4_block6_2_bn/ReadVariableOp\n", "resnet50/conv4_block6_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block6_2_bn/ReadVariableOp_1\n", "resnet50/conv4_block6_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block6_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block6_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block6_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block6_2_bn/FusedBatchNormV3\n", "resnet50/conv4_block6_2_relu/Relu\n", "resnet50/conv4_block6_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv4_block6_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv4_block6_3_conv/Conv2D\n", "resnet50/conv4_block6_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv4_block6_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv4_block6_3_conv/BiasAdd\n", "resnet50/conv4_block6_3_bn/ReadVariableOp/resource\n", "resnet50/conv4_block6_3_bn/ReadVariableOp\n", "resnet50/conv4_block6_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv4_block6_3_bn/ReadVariableOp_1\n", "resnet50/conv4_block6_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv4_block6_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv4_block6_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv4_block6_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv4_block6_3_bn/FusedBatchNormV3\n", "resnet50/conv4_block6_add/add\n", "resnet50/conv4_block6_out/Relu\n", "resnet50/conv5_block1_0_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block1_0_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block1_0_conv/Conv2D\n", "resnet50/conv5_block1_0_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block1_0_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block1_0_conv/BiasAdd\n", "resnet50/conv5_block1_0_bn/ReadVariableOp/resource\n", "resnet50/conv5_block1_0_bn/ReadVariableOp\n", "resnet50/conv5_block1_0_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block1_0_bn/ReadVariableOp_1\n", "resnet50/conv5_block1_0_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block1_0_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block1_0_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block1_0_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block1_0_bn/FusedBatchNormV3\n", "resnet50/conv5_block1_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block1_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block1_1_conv/Conv2D\n", "resnet50/conv5_block1_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block1_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block1_1_conv/BiasAdd\n", "resnet50/conv5_block1_1_bn/ReadVariableOp/resource\n", "resnet50/conv5_block1_1_bn/ReadVariableOp\n", "resnet50/conv5_block1_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block1_1_bn/ReadVariableOp_1\n", "resnet50/conv5_block1_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block1_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block1_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block1_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block1_1_bn/FusedBatchNormV3\n", "resnet50/conv5_block1_1_relu/Relu\n", "resnet50/conv5_block1_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block1_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block1_2_conv/Conv2D\n", "resnet50/conv5_block1_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block1_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block1_2_conv/BiasAdd\n", "resnet50/conv5_block1_2_bn/ReadVariableOp/resource\n", "resnet50/conv5_block1_2_bn/ReadVariableOp\n", "resnet50/conv5_block1_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block1_2_bn/ReadVariableOp_1\n", "resnet50/conv5_block1_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block1_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block1_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block1_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block1_2_bn/FusedBatchNormV3\n", "resnet50/conv5_block1_2_relu/Relu\n", "resnet50/conv5_block1_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block1_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block1_3_conv/Conv2D\n", "resnet50/conv5_block1_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block1_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block1_3_conv/BiasAdd\n", "resnet50/conv5_block1_3_bn/ReadVariableOp/resource\n", "resnet50/conv5_block1_3_bn/ReadVariableOp\n", "resnet50/conv5_block1_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block1_3_bn/ReadVariableOp_1\n", "resnet50/conv5_block1_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block1_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block1_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block1_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block1_3_bn/FusedBatchNormV3\n", "resnet50/conv5_block1_add/add\n", "resnet50/conv5_block1_out/Relu\n", "resnet50/conv5_block2_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block2_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block2_1_conv/Conv2D\n", "resnet50/conv5_block2_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block2_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block2_1_conv/BiasAdd\n", "resnet50/conv5_block2_1_bn/ReadVariableOp/resource\n", "resnet50/conv5_block2_1_bn/ReadVariableOp\n", "resnet50/conv5_block2_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block2_1_bn/ReadVariableOp_1\n", "resnet50/conv5_block2_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block2_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block2_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block2_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block2_1_bn/FusedBatchNormV3\n", "resnet50/conv5_block2_1_relu/Relu\n", "resnet50/conv5_block2_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block2_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block2_2_conv/Conv2D\n", "resnet50/conv5_block2_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block2_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block2_2_conv/BiasAdd\n", "resnet50/conv5_block2_2_bn/ReadVariableOp/resource\n", "resnet50/conv5_block2_2_bn/ReadVariableOp\n", "resnet50/conv5_block2_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block2_2_bn/ReadVariableOp_1\n", "resnet50/conv5_block2_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block2_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block2_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block2_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block2_2_bn/FusedBatchNormV3\n", "resnet50/conv5_block2_2_relu/Relu\n", "resnet50/conv5_block2_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block2_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block2_3_conv/Conv2D\n", "resnet50/conv5_block2_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block2_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block2_3_conv/BiasAdd\n", "resnet50/conv5_block2_3_bn/ReadVariableOp/resource\n", "resnet50/conv5_block2_3_bn/ReadVariableOp\n", "resnet50/conv5_block2_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block2_3_bn/ReadVariableOp_1\n", "resnet50/conv5_block2_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block2_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block2_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block2_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block2_3_bn/FusedBatchNormV3\n", "resnet50/conv5_block2_add/add\n", "resnet50/conv5_block2_out/Relu\n", "resnet50/conv5_block3_1_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block3_1_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block3_1_conv/Conv2D\n", "resnet50/conv5_block3_1_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block3_1_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block3_1_conv/BiasAdd\n", "resnet50/conv5_block3_1_bn/ReadVariableOp/resource\n", "resnet50/conv5_block3_1_bn/ReadVariableOp\n", "resnet50/conv5_block3_1_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block3_1_bn/ReadVariableOp_1\n", "resnet50/conv5_block3_1_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block3_1_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block3_1_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block3_1_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block3_1_bn/FusedBatchNormV3\n", "resnet50/conv5_block3_1_relu/Relu\n", "resnet50/conv5_block3_2_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block3_2_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block3_2_conv/Conv2D\n", "resnet50/conv5_block3_2_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block3_2_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block3_2_conv/BiasAdd\n", "resnet50/conv5_block3_2_bn/ReadVariableOp/resource\n", "resnet50/conv5_block3_2_bn/ReadVariableOp\n", "resnet50/conv5_block3_2_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block3_2_bn/ReadVariableOp_1\n", "resnet50/conv5_block3_2_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block3_2_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block3_2_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block3_2_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block3_2_bn/FusedBatchNormV3\n", "resnet50/conv5_block3_2_relu/Relu\n", "resnet50/conv5_block3_3_conv/Conv2D/ReadVariableOp/resource\n", "resnet50/conv5_block3_3_conv/Conv2D/ReadVariableOp\n", "resnet50/conv5_block3_3_conv/Conv2D\n", "resnet50/conv5_block3_3_conv/BiasAdd/ReadVariableOp/resource\n", "resnet50/conv5_block3_3_conv/BiasAdd/ReadVariableOp\n", "resnet50/conv5_block3_3_conv/BiasAdd\n", "resnet50/conv5_block3_3_bn/ReadVariableOp/resource\n", "resnet50/conv5_block3_3_bn/ReadVariableOp\n", "resnet50/conv5_block3_3_bn/ReadVariableOp_1/resource\n", "resnet50/conv5_block3_3_bn/ReadVariableOp_1\n", "resnet50/conv5_block3_3_bn/FusedBatchNormV3/ReadVariableOp/resource\n", "resnet50/conv5_block3_3_bn/FusedBatchNormV3/ReadVariableOp\n", "resnet50/conv5_block3_3_bn/FusedBatchNormV3/ReadVariableOp_1/resource\n", "resnet50/conv5_block3_3_bn/FusedBatchNormV3/ReadVariableOp_1\n", "resnet50/conv5_block3_3_bn/FusedBatchNormV3\n", "resnet50/conv5_block3_add/add\n", "resnet50/conv5_block3_out/Relu\n", "resnet50/avg_pool/Mean/reduction_indices\n", "resnet50/avg_pool/Mean\n", "resnet50/probs/MatMul/ReadVariableOp/resource\n", "resnet50/probs/MatMul/ReadVariableOp\n", "resnet50/probs/MatMul\n", "resnet50/probs/BiasAdd/ReadVariableOp/resource\n", "resnet50/probs/BiasAdd/ReadVariableOp\n", "resnet50/probs/BiasAdd\n", "resnet50/probs/Softmax\n", "Identity\n", "--------------------------------------------------\n", "Frozen model inputs: \n", "[]\n", "Frozen model outputs: \n", "[]\n" ] } ], "source": [ "frozen_func = convert_variables_to_constants_v2(full_model)\n", "frozen_func.graph.as_graph_def()\n", "\n", "layers = [op.name for op in frozen_func.graph.get_operations()]\n", "print(\"-\" * 50)\n", "print(\"Frozen model layers: \")\n", "for layer in layers:\n", " print(layer)\n", "\n", "print(\"-\" * 50)\n", "print(\"Frozen model inputs: \")\n", "print(frozen_func.inputs)\n", "print(\"Frozen model outputs: \")\n", "print(frozen_func.outputs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Save Frozen Graph as Protobuf\n", "Finally, we can save to hard drive, and now the frozen graph will be stored as `./frozen_models/_frozen_graph.pb`" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'./frozen_models/resnet50_frozen_graph.pb'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tf.io.write_graph(graph_or_graph_def=frozen_func.graph,\n", " logdir=\"./frozen_models\",\n", " name=\"{}_frozen_graph.pb\".format(MODEL_NAME),\n", " as_text=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Assuming MIGraphX has already been built and installed on your system, the driver can be used to verify that the frozen graph has been correctly exported. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Reading: ./frozen_models/resnet50_frozen_graph.pb\n", "@0 = @literal{ ... } -> float_type, {1000}, {1}\n", "@1 = @literal{ ... } -> float_type, {2048, 1000}, {1000, 1}\n", "@2 = @literal{1, 2} -> int32_type, {2}, {1}\n", "@3 = @literal{ ... } -> float_type, {2048}, {1}\n", "@4 = @literal{ ... } -> float_type, {2048}, {1}\n", "@5 = @literal{ ... } -> float_type, {2048}, {1}\n", "@6 = @literal{ ... } -> float_type, {2048}, {1}\n", "@7 = @literal{ ... } -> float_type, {2048}, {1}\n", "@8 = @literal{ ... } -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@9 = @literal{ ... } -> float_type, {512}, {1}\n", "@10 = @literal{ ... } -> float_type, {512}, {1}\n", "@11 = @literal{ ... } -> float_type, {512}, {1}\n", "@12 = @literal{ ... } -> float_type, {512}, {1}\n", "@13 = @literal{ ... } -> float_type, {512}, {1}\n", "@14 = @literal{ ... } -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@15 = @literal{ ... } -> float_type, {512}, {1}\n", "@16 = @literal{ ... } -> float_type, {512}, {1}\n", "@17 = @literal{ ... } -> float_type, {512}, {1}\n", "@18 = @literal{ ... } -> float_type, {512}, {1}\n", "@19 = @literal{ ... } -> float_type, {512}, {1}\n", "@20 = @literal{ ... } -> float_type, {1, 1, 2048, 512}, {1048576, 1048576, 512, 1}\n", "@21 = @literal{ ... } -> float_type, {2048}, {1}\n", "@22 = @literal{ ... } -> float_type, {2048}, {1}\n", "@23 = @literal{ ... } -> float_type, {2048}, {1}\n", "@24 = @literal{ ... } -> float_type, {2048}, {1}\n", "@25 = @literal{ ... } -> float_type, {2048}, {1}\n", "@26 = @literal{ ... } -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@27 = @literal{ ... } -> float_type, {512}, {1}\n", "@28 = @literal{ ... } -> float_type, {512}, {1}\n", "@29 = @literal{ ... } -> float_type, {512}, {1}\n", "@30 = @literal{ ... } -> float_type, {512}, {1}\n", "@31 = @literal{ ... } -> float_type, {512}, {1}\n", "@32 = @literal{ ... } -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@33 = @literal{ ... } -> float_type, {512}, {1}\n", "@34 = @literal{ ... } -> float_type, {512}, {1}\n", "@35 = @literal{ ... } -> float_type, {512}, {1}\n", "@36 = @literal{ ... } -> float_type, {512}, {1}\n", "@37 = @literal{ ... } -> float_type, {512}, {1}\n", "@38 = @literal{ ... } -> float_type, {1, 1, 2048, 512}, {1048576, 1048576, 512, 1}\n", "@39 = @literal{ ... } -> float_type, {2048}, {1}\n", "@40 = @literal{ ... } -> float_type, {2048}, {1}\n", "@41 = @literal{ ... } -> float_type, {2048}, {1}\n", "@42 = @literal{ ... } -> float_type, {2048}, {1}\n", "@43 = @literal{ ... } -> float_type, {2048}, {1}\n", "@44 = @literal{ ... } -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@45 = @literal{ ... } -> float_type, {512}, {1}\n", "@46 = @literal{ ... } -> float_type, {512}, {1}\n", "@47 = @literal{ ... } -> float_type, {512}, {1}\n", "@48 = @literal{ ... } -> float_type, {512}, {1}\n", "@49 = @literal{ ... } -> float_type, {512}, {1}\n", "@50 = @literal{ ... } -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@51 = @literal{ ... } -> float_type, {512}, {1}\n", "@52 = @literal{ ... } -> float_type, {512}, {1}\n", "@53 = @literal{ ... } -> float_type, {512}, {1}\n", "@54 = @literal{ ... } -> float_type, {512}, {1}\n", "@55 = @literal{ ... } -> float_type, {512}, {1}\n", "@56 = @literal{ ... } -> float_type, {1, 1, 1024, 512}, {524288, 524288, 512, 1}\n", "@57 = @literal{ ... } -> float_type, {2048}, {1}\n", "@58 = @literal{ ... } -> float_type, {2048}, {1}\n", "@59 = @literal{ ... } -> float_type, {2048}, {1}\n", "@60 = @literal{ ... } -> float_type, {2048}, {1}\n", "@61 = @literal{ ... } -> float_type, {2048}, {1}\n", "@62 = @literal{ ... } -> float_type, {1, 1, 1024, 2048}, {2097152, 2097152, 2048, 1}\n", "@63 = @literal{ ... } -> float_type, {1024}, {1}\n", "@64 = @literal{ ... } -> float_type, {1024}, {1}\n", "@65 = @literal{ ... } -> float_type, {1024}, {1}\n", "@66 = @literal{ ... } -> float_type, {1024}, {1}\n", "@67 = @literal{ ... } -> float_type, {1024}, {1}\n", "@68 = @literal{ ... } -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@69 = @literal{ ... } -> float_type, {256}, {1}\n", "@70 = @literal{ ... } -> float_type, {256}, {1}\n", "@71 = @literal{ ... } -> float_type, {256}, {1}\n", "@72 = @literal{ ... } -> float_type, {256}, {1}\n", "@73 = @literal{ ... } -> float_type, {256}, {1}\n", "@74 = @literal{ ... } -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@75 = @literal{ ... } -> float_type, {256}, {1}\n", "@76 = @literal{ ... } -> float_type, {256}, {1}\n", "@77 = @literal{ ... } -> float_type, {256}, {1}\n", "@78 = @literal{ ... } -> float_type, {256}, {1}\n", "@79 = @literal{ ... } -> float_type, {256}, {1}\n", "@80 = @literal{ ... } -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@81 = @literal{ ... } -> float_type, {1024}, {1}\n", "@82 = @literal{ ... } -> float_type, {1024}, {1}\n", "@83 = @literal{ ... } -> float_type, {1024}, {1}\n", "@84 = @literal{ ... } -> float_type, {1024}, {1}\n", "@85 = @literal{ ... } -> float_type, {1024}, {1}\n", "@86 = @literal{ ... } -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@87 = @literal{ ... } -> float_type, {256}, {1}\n", "@88 = @literal{ ... } -> float_type, {256}, {1}\n", "@89 = @literal{ ... } -> float_type, {256}, {1}\n", "@90 = @literal{ ... } -> float_type, {256}, {1}\n", "@91 = @literal{ ... } -> float_type, {256}, {1}\n", "@92 = @literal{ ... } -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@93 = @literal{ ... } -> float_type, {256}, {1}\n", "@94 = @literal{ ... } -> float_type, {256}, {1}\n", "@95 = @literal{ ... } -> float_type, {256}, {1}\n", "@96 = @literal{ ... } -> float_type, {256}, {1}\n", "@97 = @literal{ ... } -> float_type, {256}, {1}\n", "@98 = @literal{ ... } -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@99 = @literal{ ... } -> float_type, {1024}, {1}\n", "@100 = @literal{ ... } -> float_type, {1024}, {1}\n", "@101 = @literal{ ... } -> float_type, {1024}, {1}\n", "@102 = @literal{ ... } -> float_type, {1024}, {1}\n", "@103 = @literal{ ... } -> float_type, {1024}, {1}\n", "@104 = @literal{ ... } -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@105 = @literal{ ... } -> float_type, {256}, {1}\n", "@106 = @literal{ ... } -> float_type, {256}, {1}\n", "@107 = @literal{ ... } -> float_type, {256}, {1}\n", "@108 = @literal{ ... } -> float_type, {256}, {1}\n", "@109 = @literal{ ... } -> float_type, {256}, {1}\n", "@110 = @literal{ ... } -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@111 = @literal{ ... } -> float_type, {256}, {1}\n", "@112 = @literal{ ... } -> float_type, {256}, {1}\n", "@113 = @literal{ ... } -> float_type, {256}, {1}\n", "@114 = @literal{ ... } -> float_type, {256}, {1}\n", "@115 = @literal{ ... } -> float_type, {256}, {1}\n", "@116 = @literal{ ... } -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@117 = @literal{ ... } -> float_type, {1024}, {1}\n", "@118 = @literal{ ... } -> float_type, {1024}, {1}\n", "@119 = @literal{ ... } -> float_type, {1024}, {1}\n", "@120 = @literal{ ... } -> float_type, {1024}, {1}\n", "@121 = @literal{ ... } -> float_type, {1024}, {1}\n", "@122 = @literal{ ... } -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@123 = @literal{ ... } -> float_type, {256}, {1}\n", "@124 = @literal{ ... } -> float_type, {256}, {1}\n", "@125 = @literal{ ... } -> float_type, {256}, {1}\n", "@126 = @literal{ ... } -> float_type, {256}, {1}\n", "@127 = @literal{ ... } -> float_type, {256}, {1}\n", "@128 = @literal{ ... } -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@129 = @literal{ ... } -> float_type, {256}, {1}\n", "@130 = @literal{ ... } -> float_type, {256}, {1}\n", "@131 = @literal{ ... } -> float_type, {256}, {1}\n", "@132 = @literal{ ... } -> float_type, {256}, {1}\n", "@133 = @literal{ ... } -> float_type, {256}, {1}\n", "@134 = @literal{ ... } -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@135 = @literal{ ... } -> float_type, {1024}, {1}\n", "@136 = @literal{ ... } -> float_type, {1024}, {1}\n", "@137 = @literal{ ... } -> float_type, {1024}, {1}\n", "@138 = @literal{ ... } -> float_type, {1024}, {1}\n", "@139 = @literal{ ... } -> float_type, {1024}, {1}\n", "@140 = @literal{ ... } -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@141 = @literal{ ... } -> float_type, {256}, {1}\n", "@142 = @literal{ ... } -> float_type, {256}, {1}\n", "@143 = @literal{ ... } -> float_type, {256}, {1}\n", "@144 = @literal{ ... } -> float_type, {256}, {1}\n", "@145 = @literal{ ... } -> float_type, {256}, {1}\n", "@146 = @literal{ ... } -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@147 = @literal{ ... } -> float_type, {256}, {1}\n", "@148 = @literal{ ... } -> float_type, {256}, {1}\n", "@149 = @literal{ ... } -> float_type, {256}, {1}\n", "@150 = @literal{ ... } -> float_type, {256}, {1}\n", "@151 = @literal{ ... } -> float_type, {256}, {1}\n", "@152 = @literal{ ... } -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@153 = @literal{ ... } -> float_type, {1024}, {1}\n", "@154 = @literal{ ... } -> float_type, {1024}, {1}\n", "@155 = @literal{ ... } -> float_type, {1024}, {1}\n", "@156 = @literal{ ... } -> float_type, {1024}, {1}\n", "@157 = @literal{ ... } -> float_type, {1024}, {1}\n", "@158 = @literal{ ... } -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@159 = @literal{ ... } -> float_type, {256}, {1}\n", "@160 = @literal{ ... } -> float_type, {256}, {1}\n", "@161 = @literal{ ... } -> float_type, {256}, {1}\n", "@162 = @literal{ ... } -> float_type, {256}, {1}\n", "@163 = @literal{ ... } -> float_type, {256}, {1}\n", "@164 = @literal{ ... } -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@165 = @literal{ ... } -> float_type, {256}, {1}\n", "@166 = @literal{ ... } -> float_type, {256}, {1}\n", "@167 = @literal{ ... } -> float_type, {256}, {1}\n", "@168 = @literal{ ... } -> float_type, {256}, {1}\n", "@169 = @literal{ ... } -> float_type, {256}, {1}\n", "@170 = @literal{ ... } -> float_type, {1, 1, 512, 256}, {131072, 131072, 256, 1}\n", "@171 = @literal{ ... } -> float_type, {1024}, {1}\n", "@172 = @literal{ ... } -> float_type, {1024}, {1}\n", "@173 = @literal{ ... } -> float_type, {1024}, {1}\n", "@174 = @literal{ ... } -> float_type, {1024}, {1}\n", "@175 = @literal{ ... } -> float_type, {1024}, {1}\n", "@176 = @literal{ ... } -> float_type, {1, 1, 512, 1024}, {524288, 524288, 1024, 1}\n", "@177 = @literal{ ... } -> float_type, {512}, {1}\n", "@178 = @literal{ ... } -> float_type, {512}, {1}\n", "@179 = @literal{ ... } -> float_type, {512}, {1}\n", "@180 = @literal{ ... } -> float_type, {512}, {1}\n", "@181 = @literal{ ... } -> float_type, {512}, {1}\n", "@182 = @literal{ ... } -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@183 = @literal{ ... } -> float_type, {128}, {1}\n", "@184 = @literal{ ... } -> float_type, {128}, {1}\n", "@185 = @literal{ ... } -> float_type, {128}, {1}\n", "@186 = @literal{ ... } -> float_type, {128}, {1}\n", "@187 = @literal{ ... } -> float_type, {128}, {1}\n", "@188 = @literal{ ... } -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@189 = @literal{ ... } -> float_type, {128}, {1}\n", "@190 = @literal{ ... } -> float_type, {128}, {1}\n", "@191 = @literal{ ... } -> float_type, {128}, {1}\n", "@192 = @literal{ ... } -> float_type, {128}, {1}\n", "@193 = @literal{ ... } -> float_type, {128}, {1}\n", "@194 = @literal{ ... } -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@195 = @literal{ ... } -> float_type, {512}, {1}\n", "@196 = @literal{ ... } -> float_type, {512}, {1}\n", "@197 = @literal{ ... } -> float_type, {512}, {1}\n", "@198 = @literal{ ... } -> float_type, {512}, {1}\n", "@199 = @literal{ ... } -> float_type, {512}, {1}\n", "@200 = @literal{ ... } -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@201 = @literal{ ... } -> float_type, {128}, {1}\n", "@202 = @literal{ ... } -> float_type, {128}, {1}\n", "@203 = @literal{ ... } -> float_type, {128}, {1}\n", "@204 = @literal{ ... } -> float_type, {128}, {1}\n", "@205 = @literal{ ... } -> float_type, {128}, {1}\n", "@206 = @literal{ ... } -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@207 = @literal{ ... } -> float_type, {128}, {1}\n", "@208 = @literal{ ... } -> float_type, {128}, {1}\n", "@209 = @literal{ ... } -> float_type, {128}, {1}\n", "@210 = @literal{ ... } -> float_type, {128}, {1}\n", "@211 = @literal{ ... } -> float_type, {128}, {1}\n", "@212 = @literal{ ... } -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@213 = @literal{ ... } -> float_type, {512}, {1}\n", "@214 = @literal{ ... } -> float_type, {512}, {1}\n", "@215 = @literal{ ... } -> float_type, {512}, {1}\n", "@216 = @literal{ ... } -> float_type, {512}, {1}\n", "@217 = @literal{ ... } -> float_type, {512}, {1}\n", "@218 = @literal{ ... } -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@219 = @literal{ ... } -> float_type, {128}, {1}\n", "@220 = @literal{ ... } -> float_type, {128}, {1}\n", "@221 = @literal{ ... } -> float_type, {128}, {1}\n", "@222 = @literal{ ... } -> float_type, {128}, {1}\n", "@223 = @literal{ ... } -> float_type, {128}, {1}\n", "@224 = @literal{ ... } -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@225 = @literal{ ... } -> float_type, {128}, {1}\n", "@226 = @literal{ ... } -> float_type, {128}, {1}\n", "@227 = @literal{ ... } -> float_type, {128}, {1}\n", "@228 = @literal{ ... } -> float_type, {128}, {1}\n", "@229 = @literal{ ... } -> float_type, {128}, {1}\n", "@230 = @literal{ ... } -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@231 = @literal{ ... } -> float_type, {512}, {1}\n", "@232 = @literal{ ... } -> float_type, {512}, {1}\n", "@233 = @literal{ ... } -> float_type, {512}, {1}\n", "@234 = @literal{ ... } -> float_type, {512}, {1}\n", "@235 = @literal{ ... } -> float_type, {512}, {1}\n", "@236 = @literal{ ... } -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@237 = @literal{ ... } -> float_type, {128}, {1}\n", "@238 = @literal{ ... } -> float_type, {128}, {1}\n", "@239 = @literal{ ... } -> float_type, {128}, {1}\n", "@240 = @literal{ ... } -> float_type, {128}, {1}\n", "@241 = @literal{ ... } -> float_type, {128}, {1}\n", "@242 = @literal{ ... } -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@243 = @literal{ ... } -> float_type, {128}, {1}\n", "@244 = @literal{ ... } -> float_type, {128}, {1}\n", "@245 = @literal{ ... } -> float_type, {128}, {1}\n", "@246 = @literal{ ... } -> float_type, {128}, {1}\n", "@247 = @literal{ ... } -> float_type, {128}, {1}\n", "@248 = @literal{ ... } -> float_type, {1, 1, 256, 128}, {32768, 32768, 128, 1}\n", "@249 = @literal{ ... } -> float_type, {512}, {1}\n", "@250 = @literal{ ... } -> float_type, {512}, {1}\n", "@251 = @literal{ ... } -> float_type, {512}, {1}\n", "@252 = @literal{ ... } -> float_type, {512}, {1}\n", "@253 = @literal{ ... } -> float_type, {512}, {1}\n", "@254 = @literal{ ... } -> float_type, {1, 1, 256, 512}, {131072, 131072, 512, 1}\n", "@255 = @literal{ ... } -> float_type, {256}, {1}\n", "@256 = @literal{ ... } -> float_type, {256}, {1}\n", "@257 = @literal{ ... } -> float_type, {256}, {1}\n", "@258 = @literal{ ... } -> float_type, {256}, {1}\n", "@259 = @literal{ ... } -> float_type, {256}, {1}\n", "@260 = @literal{ ... } -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@261 = @literal{ ... } -> float_type, {64}, {1}\n", "@262 = @literal{ ... } -> float_type, {64}, {1}\n", "@263 = @literal{ ... } -> float_type, {64}, {1}\n", "@264 = @literal{ ... } -> float_type, {64}, {1}\n", "@265 = @literal{ ... } -> float_type, {64}, {1}\n", "@266 = @literal{ ... } -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@267 = @literal{ ... } -> float_type, {64}, {1}\n", "@268 = @literal{ ... } -> float_type, {64}, {1}\n", "@269 = @literal{ ... } -> float_type, {64}, {1}\n", "@270 = @literal{ ... } -> float_type, {64}, {1}\n", "@271 = @literal{ ... } -> float_type, {64}, {1}\n", "@272 = @literal{ ... } -> float_type, {1, 1, 256, 64}, {16384, 16384, 64, 1}\n", "@273 = @literal{ ... } -> float_type, {256}, {1}\n", "@274 = @literal{ ... } -> float_type, {256}, {1}\n", "@275 = @literal{ ... } -> float_type, {256}, {1}\n", "@276 = @literal{ ... } -> float_type, {256}, {1}\n", "@277 = @literal{ ... } -> float_type, {256}, {1}\n", "@278 = @literal{ ... } -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@279 = @literal{ ... } -> float_type, {64}, {1}\n", "@280 = @literal{ ... } -> float_type, {64}, {1}\n", "@281 = @literal{ ... } -> float_type, {64}, {1}\n", "@282 = @literal{ ... } -> float_type, {64}, {1}\n", "@283 = @literal{ ... } -> float_type, {64}, {1}\n", "@284 = @literal{ ... } -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@285 = @literal{ ... } -> float_type, {64}, {1}\n", "@286 = @literal{ ... } -> float_type, {64}, {1}\n", "@287 = @literal{ ... } -> float_type, {64}, {1}\n", "@288 = @literal{ ... } -> float_type, {64}, {1}\n", "@289 = @literal{ ... } -> float_type, {64}, {1}\n", "@290 = @literal{ ... } -> float_type, {1, 1, 256, 64}, {16384, 16384, 64, 1}\n", "@291 = @literal{ ... } -> float_type, {256}, {1}\n", "@292 = @literal{ ... } -> float_type, {256}, {1}\n", "@293 = @literal{ ... } -> float_type, {256}, {1}\n", "@294 = @literal{ ... } -> float_type, {256}, {1}\n", "@295 = @literal{ ... } -> float_type, {256}, {1}\n", "@296 = @literal{ ... } -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@297 = @literal{ ... } -> float_type, {64}, {1}\n", "@298 = @literal{ ... } -> float_type, {64}, {1}\n", "@299 = @literal{ ... } -> float_type, {64}, {1}\n", "@300 = @literal{ ... } -> float_type, {64}, {1}\n", "@301 = @literal{ ... } -> float_type, {64}, {1}\n", "@302 = @literal{ ... } -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@303 = @literal{ ... } -> float_type, {64}, {1}\n", "@304 = @literal{ ... } -> float_type, {64}, {1}\n", "@305 = @literal{ ... } -> float_type, {64}, {1}\n", "@306 = @literal{ ... } -> float_type, {64}, {1}\n", "@307 = @literal{ ... } -> float_type, {64}, {1}\n", "@308 = @literal{ ... } -> float_type, {1, 1, 64, 64}, {4096, 4096, 64, 1}\n", "@309 = @literal{ ... } -> float_type, {256}, {1}\n", "@310 = @literal{ ... } -> float_type, {256}, {1}\n", "@311 = @literal{ ... } -> float_type, {256}, {1}\n", "@312 = @literal{ ... } -> float_type, {256}, {1}\n", "@313 = @literal{ ... } -> float_type, {256}, {1}\n", "@314 = @literal{ ... } -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@315 = @literal{0, 0, 1, 1, 1, 1, 0, 0} -> int32_type, {4, 2}, {2, 1}\n", "@316 = @literal{ ... } -> float_type, {64}, {1}\n", "@317 = @literal{ ... } -> float_type, {64}, {1}\n", "@318 = @literal{ ... } -> float_type, {64}, {1}\n", "@319 = @literal{ ... } -> float_type, {64}, {1}\n", "@320 = @literal{ ... } -> float_type, {64}, {1}\n", "@321 = @literal{ ... } -> float_type, {7, 7, 3, 64}, {1344, 192, 64, 1}\n", "@322 = @literal{0, 0, 3, 3, 3, 3, 0, 0} -> int32_type, {4, 2}, {2, 1}\n", "x = @param:x -> float_type, {1, 3, 224, 224}, {150528, 50176, 224, 1}\n", "@323 = transpose[dims={0, 2, 3, 1}](x) -> float_type, {1, 224, 224, 3}, {150528, 224, 1, 50176}\n", "@324 = transpose[dims={0, 3, 1, 2}](@323) -> float_type, {1, 3, 224, 224}, {150528, 50176, 224, 1}\n", "@325 = pad[mode=0,pads={0, 0, 3, 3, 0, 0, 3, 3},value=0](@324) -> float_type, {1, 3, 230, 230}, {158700, 52900, 230, 1}\n", "@326 = transpose[dims={0, 2, 3, 1}](@325) -> float_type, {1, 230, 230, 3}, {158700, 230, 1, 52900}\n", "@327 = transpose[dims={0, 2, 3, 1}](@321) -> float_type, {7, 3, 64, 7}, {1344, 64, 1, 192}\n", "@328 = transpose[dims={0, 3, 1, 2}](@327) -> float_type, {7, 7, 3, 64}, {1344, 192, 64, 1}\n", "@329 = identity(@328) -> float_type, {7, 7, 3, 64}, {1344, 192, 64, 1}\n", "@330 = transpose[dims={0, 2, 3, 1}](@329) -> float_type, {7, 3, 64, 7}, {1344, 64, 1, 192}\n", "@331 = transpose[dims={0, 3, 1, 2}](@326) -> float_type, {1, 3, 230, 230}, {158700, 52900, 230, 1}\n", "@332 = transpose[dims={0, 3, 1, 2}](@330) -> float_type, {7, 7, 3, 64}, {1344, 192, 64, 1}\n", "@333 = transpose[dims={3, 2, 0, 1}](@332) -> float_type, {64, 3, 7, 7}, {1, 64, 1344, 192}\n", "@334 = transpose[dims={3, 2, 0, 1}](@332) -> float_type, {64, 3, 7, 7}, {1, 64, 1344, 192}\n", "@335 = convolution[padding={0, 0},stride={2, 2},dilation={1, 1},group=1,padding_mode=2](@331,@334) -> float_type, {1, 64, 112, 112}, {802816, 12544, 112, 1}\n", "@336 = transpose[dims={0, 2, 3, 1}](@335) -> float_type, {1, 112, 112, 64}, {802816, 112, 1, 12544}\n", "@337 = identity(@320) -> float_type, {64}, {1}\n", "@338 = transpose[dims={0, 3, 1, 2}](@336) -> float_type, {1, 64, 112, 112}, {802816, 12544, 112, 1}\n", "@339 = broadcast[axis=1,dims={1, 64, 112, 112}](@337) -> float_type, {1, 64, 112, 112}, {0, 1, 0, 0}\n", "@340 = add(@338,@339) -> float_type, {1, 64, 112, 112}, {802816, 12544, 112, 1}\n", "@341 = transpose[dims={0, 2, 3, 1}](@340) -> float_type, {1, 112, 112, 64}, {802816, 112, 1, 12544}\n", "@342 = identity(@319) -> float_type, {64}, {1}\n", "@343 = identity(@318) -> float_type, {64}, {1}\n", "@344 = identity(@317) -> float_type, {64}, {1}\n", "@345 = identity(@316) -> float_type, {64}, {1}\n", "@346 = unknown:FusedBatchNormV3(@341,@342,@343,@344,@345) -> float_type, {1, 112, 112, 64}, {802816, 112, 1, 12544}\n", "@347 = transpose[dims={0, 3, 1, 2}](@346) -> float_type, {1, 64, 112, 112}, {802816, 12544, 112, 1}\n", "@348 = relu(@347) -> float_type, {1, 64, 112, 112}, {802816, 12544, 112, 1}\n", "@349 = transpose[dims={0, 2, 3, 1}](@348) -> float_type, {1, 112, 112, 64}, {802816, 112, 1, 12544}\n", "@350 = transpose[dims={0, 3, 1, 2}](@349) -> float_type, {1, 64, 112, 112}, {802816, 12544, 112, 1}\n", "@351 = pad[mode=0,pads={0, 0, 1, 1, 0, 0, 1, 1},value=0](@350) -> float_type, {1, 64, 114, 114}, {831744, 12996, 114, 1}\n", "@352 = transpose[dims={0, 2, 3, 1}](@351) -> float_type, {1, 114, 114, 64}, {831744, 114, 1, 12996}\n", "@353 = transpose[dims={0, 3, 1, 2}](@352) -> float_type, {1, 64, 114, 114}, {831744, 12996, 114, 1}\n", "@354 = pooling[mode=max,padding={0, 0},stride={2, 2},lengths={3, 3},ceil_mode=0](@353) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@355 = transpose[dims={0, 2, 3, 1}](@354) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@356 = transpose[dims={0, 2, 3, 1}](@314) -> float_type, {1, 64, 256, 1}, {16384, 256, 1, 16384}\n", "@357 = transpose[dims={0, 3, 1, 2}](@356) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@358 = identity(@357) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@359 = transpose[dims={0, 2, 3, 1}](@358) -> float_type, {1, 64, 256, 1}, {16384, 256, 1, 16384}\n", "@360 = transpose[dims={0, 3, 1, 2}](@355) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@361 = transpose[dims={0, 3, 1, 2}](@359) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@362 = transpose[dims={3, 2, 0, 1}](@361) -> float_type, {256, 64, 1, 1}, {1, 256, 16384, 16384}\n", "@363 = transpose[dims={3, 2, 0, 1}](@361) -> float_type, {256, 64, 1, 1}, {1, 256, 16384, 16384}\n", "@364 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@360,@363) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@365 = transpose[dims={0, 2, 3, 1}](@364) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@366 = identity(@313) -> float_type, {256}, {1}\n", "@367 = transpose[dims={0, 3, 1, 2}](@365) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@368 = broadcast[axis=1,dims={1, 256, 56, 56}](@366) -> float_type, {1, 256, 56, 56}, {0, 1, 0, 0}\n", "@369 = add(@367,@368) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@370 = transpose[dims={0, 2, 3, 1}](@369) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@371 = identity(@312) -> float_type, {256}, {1}\n", "@372 = identity(@311) -> float_type, {256}, {1}\n", "@373 = identity(@310) -> float_type, {256}, {1}\n", "@374 = identity(@309) -> float_type, {256}, {1}\n", "@375 = unknown:FusedBatchNormV3(@370,@371,@372,@373,@374) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@376 = transpose[dims={0, 2, 3, 1}](@308) -> float_type, {1, 64, 64, 1}, {4096, 64, 1, 4096}\n", "@377 = transpose[dims={0, 3, 1, 2}](@376) -> float_type, {1, 1, 64, 64}, {4096, 4096, 64, 1}\n", "@378 = identity(@377) -> float_type, {1, 1, 64, 64}, {4096, 4096, 64, 1}\n", "@379 = transpose[dims={0, 2, 3, 1}](@378) -> float_type, {1, 64, 64, 1}, {4096, 64, 1, 4096}\n", "@380 = transpose[dims={0, 3, 1, 2}](@355) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@381 = transpose[dims={0, 3, 1, 2}](@379) -> float_type, {1, 1, 64, 64}, {4096, 4096, 64, 1}\n", "@382 = transpose[dims={3, 2, 0, 1}](@381) -> float_type, {64, 64, 1, 1}, {1, 64, 4096, 4096}\n", "@383 = transpose[dims={3, 2, 0, 1}](@381) -> float_type, {64, 64, 1, 1}, {1, 64, 4096, 4096}\n", "@384 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@380,@383) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@385 = transpose[dims={0, 2, 3, 1}](@384) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@386 = identity(@307) -> float_type, {64}, {1}\n", "@387 = transpose[dims={0, 3, 1, 2}](@385) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@388 = broadcast[axis=1,dims={1, 64, 56, 56}](@386) -> float_type, {1, 64, 56, 56}, {0, 1, 0, 0}\n", "@389 = add(@387,@388) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@390 = transpose[dims={0, 2, 3, 1}](@389) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@391 = identity(@306) -> float_type, {64}, {1}\n", "@392 = identity(@305) -> float_type, {64}, {1}\n", "@393 = identity(@304) -> float_type, {64}, {1}\n", "@394 = identity(@303) -> float_type, {64}, {1}\n", "@395 = unknown:FusedBatchNormV3(@390,@391,@392,@393,@394) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@396 = transpose[dims={0, 3, 1, 2}](@395) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@397 = relu(@396) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@398 = transpose[dims={0, 2, 3, 1}](@397) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@399 = transpose[dims={0, 2, 3, 1}](@302) -> float_type, {3, 64, 64, 3}, {12288, 64, 1, 4096}\n", "@400 = transpose[dims={0, 3, 1, 2}](@399) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@401 = identity(@400) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@402 = transpose[dims={0, 2, 3, 1}](@401) -> float_type, {3, 64, 64, 3}, {12288, 64, 1, 4096}\n", "@403 = transpose[dims={0, 3, 1, 2}](@398) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@404 = transpose[dims={0, 3, 1, 2}](@402) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@405 = transpose[dims={3, 2, 0, 1}](@404) -> float_type, {64, 64, 3, 3}, {1, 64, 12288, 4096}\n", "@406 = transpose[dims={3, 2, 0, 1}](@404) -> float_type, {64, 64, 3, 3}, {1, 64, 12288, 4096}\n", "@407 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@403,@406) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@408 = transpose[dims={0, 2, 3, 1}](@407) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@409 = identity(@301) -> float_type, {64}, {1}\n", "@410 = transpose[dims={0, 3, 1, 2}](@408) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@411 = broadcast[axis=1,dims={1, 64, 56, 56}](@409) -> float_type, {1, 64, 56, 56}, {0, 1, 0, 0}\n", "@412 = add(@410,@411) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@413 = transpose[dims={0, 2, 3, 1}](@412) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@414 = identity(@300) -> float_type, {64}, {1}\n", "@415 = identity(@299) -> float_type, {64}, {1}\n", "@416 = identity(@298) -> float_type, {64}, {1}\n", "@417 = identity(@297) -> float_type, {64}, {1}\n", "@418 = unknown:FusedBatchNormV3(@413,@414,@415,@416,@417) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@419 = transpose[dims={0, 3, 1, 2}](@418) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@420 = relu(@419) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@421 = transpose[dims={0, 2, 3, 1}](@420) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@422 = transpose[dims={0, 2, 3, 1}](@296) -> float_type, {1, 64, 256, 1}, {16384, 256, 1, 16384}\n", "@423 = transpose[dims={0, 3, 1, 2}](@422) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@424 = identity(@423) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@425 = transpose[dims={0, 2, 3, 1}](@424) -> float_type, {1, 64, 256, 1}, {16384, 256, 1, 16384}\n", "@426 = transpose[dims={0, 3, 1, 2}](@421) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@427 = transpose[dims={0, 3, 1, 2}](@425) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@428 = transpose[dims={3, 2, 0, 1}](@427) -> float_type, {256, 64, 1, 1}, {1, 256, 16384, 16384}\n", "@429 = transpose[dims={3, 2, 0, 1}](@427) -> float_type, {256, 64, 1, 1}, {1, 256, 16384, 16384}\n", "@430 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@426,@429) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@431 = transpose[dims={0, 2, 3, 1}](@430) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@432 = identity(@295) -> float_type, {256}, {1}\n", "@433 = transpose[dims={0, 3, 1, 2}](@431) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@434 = broadcast[axis=1,dims={1, 256, 56, 56}](@432) -> float_type, {1, 256, 56, 56}, {0, 1, 0, 0}\n", "@435 = add(@433,@434) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@436 = transpose[dims={0, 2, 3, 1}](@435) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@437 = identity(@294) -> float_type, {256}, {1}\n", "@438 = identity(@293) -> float_type, {256}, {1}\n", "@439 = identity(@292) -> float_type, {256}, {1}\n", "@440 = identity(@291) -> float_type, {256}, {1}\n", "@441 = unknown:FusedBatchNormV3(@436,@437,@438,@439,@440) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@442 = unknown:AddV2(@375,@441) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@443 = transpose[dims={0, 3, 1, 2}](@442) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@444 = relu(@443) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@445 = transpose[dims={0, 2, 3, 1}](@444) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@446 = transpose[dims={0, 2, 3, 1}](@290) -> float_type, {1, 256, 64, 1}, {16384, 64, 1, 16384}\n", "@447 = transpose[dims={0, 3, 1, 2}](@446) -> float_type, {1, 1, 256, 64}, {16384, 16384, 64, 1}\n", "@448 = identity(@447) -> float_type, {1, 1, 256, 64}, {16384, 16384, 64, 1}\n", "@449 = transpose[dims={0, 2, 3, 1}](@448) -> float_type, {1, 256, 64, 1}, {16384, 64, 1, 16384}\n", "@450 = transpose[dims={0, 3, 1, 2}](@445) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@451 = transpose[dims={0, 3, 1, 2}](@449) -> float_type, {1, 1, 256, 64}, {16384, 16384, 64, 1}\n", "@452 = transpose[dims={3, 2, 0, 1}](@451) -> float_type, {64, 256, 1, 1}, {1, 64, 16384, 16384}\n", "@453 = transpose[dims={3, 2, 0, 1}](@451) -> float_type, {64, 256, 1, 1}, {1, 64, 16384, 16384}\n", "@454 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@450,@453) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@455 = transpose[dims={0, 2, 3, 1}](@454) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@456 = identity(@289) -> float_type, {64}, {1}\n", "@457 = transpose[dims={0, 3, 1, 2}](@455) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@458 = broadcast[axis=1,dims={1, 64, 56, 56}](@456) -> float_type, {1, 64, 56, 56}, {0, 1, 0, 0}\n", "@459 = add(@457,@458) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@460 = transpose[dims={0, 2, 3, 1}](@459) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@461 = identity(@288) -> float_type, {64}, {1}\n", "@462 = identity(@287) -> float_type, {64}, {1}\n", "@463 = identity(@286) -> float_type, {64}, {1}\n", "@464 = identity(@285) -> float_type, {64}, {1}\n", "@465 = unknown:FusedBatchNormV3(@460,@461,@462,@463,@464) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@466 = transpose[dims={0, 3, 1, 2}](@465) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@467 = relu(@466) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@468 = transpose[dims={0, 2, 3, 1}](@467) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@469 = transpose[dims={0, 2, 3, 1}](@284) -> float_type, {3, 64, 64, 3}, {12288, 64, 1, 4096}\n", "@470 = transpose[dims={0, 3, 1, 2}](@469) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@471 = identity(@470) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@472 = transpose[dims={0, 2, 3, 1}](@471) -> float_type, {3, 64, 64, 3}, {12288, 64, 1, 4096}\n", "@473 = transpose[dims={0, 3, 1, 2}](@468) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@474 = transpose[dims={0, 3, 1, 2}](@472) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@475 = transpose[dims={3, 2, 0, 1}](@474) -> float_type, {64, 64, 3, 3}, {1, 64, 12288, 4096}\n", "@476 = transpose[dims={3, 2, 0, 1}](@474) -> float_type, {64, 64, 3, 3}, {1, 64, 12288, 4096}\n", "@477 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@473,@476) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@478 = transpose[dims={0, 2, 3, 1}](@477) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@479 = identity(@283) -> float_type, {64}, {1}\n", "@480 = transpose[dims={0, 3, 1, 2}](@478) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@481 = broadcast[axis=1,dims={1, 64, 56, 56}](@479) -> float_type, {1, 64, 56, 56}, {0, 1, 0, 0}\n", "@482 = add(@480,@481) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@483 = transpose[dims={0, 2, 3, 1}](@482) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@484 = identity(@282) -> float_type, {64}, {1}\n", "@485 = identity(@281) -> float_type, {64}, {1}\n", "@486 = identity(@280) -> float_type, {64}, {1}\n", "@487 = identity(@279) -> float_type, {64}, {1}\n", "@488 = unknown:FusedBatchNormV3(@483,@484,@485,@486,@487) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@489 = transpose[dims={0, 3, 1, 2}](@488) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@490 = relu(@489) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@491 = transpose[dims={0, 2, 3, 1}](@490) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@492 = transpose[dims={0, 2, 3, 1}](@278) -> float_type, {1, 64, 256, 1}, {16384, 256, 1, 16384}\n", "@493 = transpose[dims={0, 3, 1, 2}](@492) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@494 = identity(@493) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@495 = transpose[dims={0, 2, 3, 1}](@494) -> float_type, {1, 64, 256, 1}, {16384, 256, 1, 16384}\n", "@496 = transpose[dims={0, 3, 1, 2}](@491) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@497 = transpose[dims={0, 3, 1, 2}](@495) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@498 = transpose[dims={3, 2, 0, 1}](@497) -> float_type, {256, 64, 1, 1}, {1, 256, 16384, 16384}\n", "@499 = transpose[dims={3, 2, 0, 1}](@497) -> float_type, {256, 64, 1, 1}, {1, 256, 16384, 16384}\n", "@500 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@496,@499) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@501 = transpose[dims={0, 2, 3, 1}](@500) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@502 = identity(@277) -> float_type, {256}, {1}\n", "@503 = transpose[dims={0, 3, 1, 2}](@501) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@504 = broadcast[axis=1,dims={1, 256, 56, 56}](@502) -> float_type, {1, 256, 56, 56}, {0, 1, 0, 0}\n", "@505 = add(@503,@504) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@506 = transpose[dims={0, 2, 3, 1}](@505) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@507 = identity(@276) -> float_type, {256}, {1}\n", "@508 = identity(@275) -> float_type, {256}, {1}\n", "@509 = identity(@274) -> float_type, {256}, {1}\n", "@510 = identity(@273) -> float_type, {256}, {1}\n", "@511 = unknown:FusedBatchNormV3(@506,@507,@508,@509,@510) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@512 = unknown:AddV2(@445,@511) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@513 = transpose[dims={0, 3, 1, 2}](@512) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@514 = relu(@513) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@515 = transpose[dims={0, 2, 3, 1}](@514) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@516 = transpose[dims={0, 2, 3, 1}](@272) -> float_type, {1, 256, 64, 1}, {16384, 64, 1, 16384}\n", "@517 = transpose[dims={0, 3, 1, 2}](@516) -> float_type, {1, 1, 256, 64}, {16384, 16384, 64, 1}\n", "@518 = identity(@517) -> float_type, {1, 1, 256, 64}, {16384, 16384, 64, 1}\n", "@519 = transpose[dims={0, 2, 3, 1}](@518) -> float_type, {1, 256, 64, 1}, {16384, 64, 1, 16384}\n", "@520 = transpose[dims={0, 3, 1, 2}](@515) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@521 = transpose[dims={0, 3, 1, 2}](@519) -> float_type, {1, 1, 256, 64}, {16384, 16384, 64, 1}\n", "@522 = transpose[dims={3, 2, 0, 1}](@521) -> float_type, {64, 256, 1, 1}, {1, 64, 16384, 16384}\n", "@523 = transpose[dims={3, 2, 0, 1}](@521) -> float_type, {64, 256, 1, 1}, {1, 64, 16384, 16384}\n", "@524 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@520,@523) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@525 = transpose[dims={0, 2, 3, 1}](@524) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@526 = identity(@271) -> float_type, {64}, {1}\n", "@527 = transpose[dims={0, 3, 1, 2}](@525) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@528 = broadcast[axis=1,dims={1, 64, 56, 56}](@526) -> float_type, {1, 64, 56, 56}, {0, 1, 0, 0}\n", "@529 = add(@527,@528) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@530 = transpose[dims={0, 2, 3, 1}](@529) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@531 = identity(@270) -> float_type, {64}, {1}\n", "@532 = identity(@269) -> float_type, {64}, {1}\n", "@533 = identity(@268) -> float_type, {64}, {1}\n", "@534 = identity(@267) -> float_type, {64}, {1}\n", "@535 = unknown:FusedBatchNormV3(@530,@531,@532,@533,@534) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@536 = transpose[dims={0, 3, 1, 2}](@535) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@537 = relu(@536) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@538 = transpose[dims={0, 2, 3, 1}](@537) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@539 = transpose[dims={0, 2, 3, 1}](@266) -> float_type, {3, 64, 64, 3}, {12288, 64, 1, 4096}\n", "@540 = transpose[dims={0, 3, 1, 2}](@539) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@541 = identity(@540) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@542 = transpose[dims={0, 2, 3, 1}](@541) -> float_type, {3, 64, 64, 3}, {12288, 64, 1, 4096}\n", "@543 = transpose[dims={0, 3, 1, 2}](@538) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@544 = transpose[dims={0, 3, 1, 2}](@542) -> float_type, {3, 3, 64, 64}, {12288, 4096, 64, 1}\n", "@545 = transpose[dims={3, 2, 0, 1}](@544) -> float_type, {64, 64, 3, 3}, {1, 64, 12288, 4096}\n", "@546 = transpose[dims={3, 2, 0, 1}](@544) -> float_type, {64, 64, 3, 3}, {1, 64, 12288, 4096}\n", "@547 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@543,@546) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@548 = transpose[dims={0, 2, 3, 1}](@547) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@549 = identity(@265) -> float_type, {64}, {1}\n", "@550 = transpose[dims={0, 3, 1, 2}](@548) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@551 = broadcast[axis=1,dims={1, 64, 56, 56}](@549) -> float_type, {1, 64, 56, 56}, {0, 1, 0, 0}\n", "@552 = add(@550,@551) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@553 = transpose[dims={0, 2, 3, 1}](@552) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@554 = identity(@264) -> float_type, {64}, {1}\n", "@555 = identity(@263) -> float_type, {64}, {1}\n", "@556 = identity(@262) -> float_type, {64}, {1}\n", "@557 = identity(@261) -> float_type, {64}, {1}\n", "@558 = unknown:FusedBatchNormV3(@553,@554,@555,@556,@557) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@559 = transpose[dims={0, 3, 1, 2}](@558) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@560 = relu(@559) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@561 = transpose[dims={0, 2, 3, 1}](@560) -> float_type, {1, 56, 56, 64}, {200704, 56, 1, 3136}\n", "@562 = transpose[dims={0, 2, 3, 1}](@260) -> float_type, {1, 64, 256, 1}, {16384, 256, 1, 16384}\n", "@563 = transpose[dims={0, 3, 1, 2}](@562) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@564 = identity(@563) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@565 = transpose[dims={0, 2, 3, 1}](@564) -> float_type, {1, 64, 256, 1}, {16384, 256, 1, 16384}\n", "@566 = transpose[dims={0, 3, 1, 2}](@561) -> float_type, {1, 64, 56, 56}, {200704, 3136, 56, 1}\n", "@567 = transpose[dims={0, 3, 1, 2}](@565) -> float_type, {1, 1, 64, 256}, {16384, 16384, 256, 1}\n", "@568 = transpose[dims={3, 2, 0, 1}](@567) -> float_type, {256, 64, 1, 1}, {1, 256, 16384, 16384}\n", "@569 = transpose[dims={3, 2, 0, 1}](@567) -> float_type, {256, 64, 1, 1}, {1, 256, 16384, 16384}\n", "@570 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@566,@569) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@571 = transpose[dims={0, 2, 3, 1}](@570) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@572 = identity(@259) -> float_type, {256}, {1}\n", "@573 = transpose[dims={0, 3, 1, 2}](@571) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@574 = broadcast[axis=1,dims={1, 256, 56, 56}](@572) -> float_type, {1, 256, 56, 56}, {0, 1, 0, 0}\n", "@575 = add(@573,@574) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@576 = transpose[dims={0, 2, 3, 1}](@575) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@577 = identity(@258) -> float_type, {256}, {1}\n", "@578 = identity(@257) -> float_type, {256}, {1}\n", "@579 = identity(@256) -> float_type, {256}, {1}\n", "@580 = identity(@255) -> float_type, {256}, {1}\n", "@581 = unknown:FusedBatchNormV3(@576,@577,@578,@579,@580) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@582 = unknown:AddV2(@515,@581) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@583 = transpose[dims={0, 3, 1, 2}](@582) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@584 = relu(@583) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@585 = transpose[dims={0, 2, 3, 1}](@584) -> float_type, {1, 56, 56, 256}, {802816, 56, 1, 3136}\n", "@586 = transpose[dims={0, 2, 3, 1}](@254) -> float_type, {1, 256, 512, 1}, {131072, 512, 1, 131072}\n", "@587 = transpose[dims={0, 3, 1, 2}](@586) -> float_type, {1, 1, 256, 512}, {131072, 131072, 512, 1}\n", "@588 = identity(@587) -> float_type, {1, 1, 256, 512}, {131072, 131072, 512, 1}\n", "@589 = transpose[dims={0, 2, 3, 1}](@588) -> float_type, {1, 256, 512, 1}, {131072, 512, 1, 131072}\n", "@590 = transpose[dims={0, 3, 1, 2}](@585) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@591 = transpose[dims={0, 3, 1, 2}](@589) -> float_type, {1, 1, 256, 512}, {131072, 131072, 512, 1}\n", "@592 = transpose[dims={3, 2, 0, 1}](@591) -> float_type, {512, 256, 1, 1}, {1, 512, 131072, 131072}\n", "@593 = transpose[dims={3, 2, 0, 1}](@591) -> float_type, {512, 256, 1, 1}, {1, 512, 131072, 131072}\n", "@594 = convolution[padding={0, 0},stride={2, 2},dilation={1, 1},group=1,padding_mode=2](@590,@593) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@595 = transpose[dims={0, 2, 3, 1}](@594) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@596 = identity(@253) -> float_type, {512}, {1}\n", "@597 = transpose[dims={0, 3, 1, 2}](@595) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@598 = broadcast[axis=1,dims={1, 512, 28, 28}](@596) -> float_type, {1, 512, 28, 28}, {0, 1, 0, 0}\n", "@599 = add(@597,@598) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@600 = transpose[dims={0, 2, 3, 1}](@599) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@601 = identity(@252) -> float_type, {512}, {1}\n", "@602 = identity(@251) -> float_type, {512}, {1}\n", "@603 = identity(@250) -> float_type, {512}, {1}\n", "@604 = identity(@249) -> float_type, {512}, {1}\n", "@605 = unknown:FusedBatchNormV3(@600,@601,@602,@603,@604) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@606 = transpose[dims={0, 2, 3, 1}](@248) -> float_type, {1, 256, 128, 1}, {32768, 128, 1, 32768}\n", "@607 = transpose[dims={0, 3, 1, 2}](@606) -> float_type, {1, 1, 256, 128}, {32768, 32768, 128, 1}\n", "@608 = identity(@607) -> float_type, {1, 1, 256, 128}, {32768, 32768, 128, 1}\n", "@609 = transpose[dims={0, 2, 3, 1}](@608) -> float_type, {1, 256, 128, 1}, {32768, 128, 1, 32768}\n", "@610 = transpose[dims={0, 3, 1, 2}](@585) -> float_type, {1, 256, 56, 56}, {802816, 3136, 56, 1}\n", "@611 = transpose[dims={0, 3, 1, 2}](@609) -> float_type, {1, 1, 256, 128}, {32768, 32768, 128, 1}\n", "@612 = transpose[dims={3, 2, 0, 1}](@611) -> float_type, {128, 256, 1, 1}, {1, 128, 32768, 32768}\n", "@613 = transpose[dims={3, 2, 0, 1}](@611) -> float_type, {128, 256, 1, 1}, {1, 128, 32768, 32768}\n", "@614 = convolution[padding={0, 0},stride={2, 2},dilation={1, 1},group=1,padding_mode=2](@610,@613) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@615 = transpose[dims={0, 2, 3, 1}](@614) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@616 = identity(@247) -> float_type, {128}, {1}\n", "@617 = transpose[dims={0, 3, 1, 2}](@615) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@618 = broadcast[axis=1,dims={1, 128, 28, 28}](@616) -> float_type, {1, 128, 28, 28}, {0, 1, 0, 0}\n", "@619 = add(@617,@618) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@620 = transpose[dims={0, 2, 3, 1}](@619) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@621 = identity(@246) -> float_type, {128}, {1}\n", "@622 = identity(@245) -> float_type, {128}, {1}\n", "@623 = identity(@244) -> float_type, {128}, {1}\n", "@624 = identity(@243) -> float_type, {128}, {1}\n", "@625 = unknown:FusedBatchNormV3(@620,@621,@622,@623,@624) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@626 = transpose[dims={0, 3, 1, 2}](@625) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@627 = relu(@626) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@628 = transpose[dims={0, 2, 3, 1}](@627) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@629 = transpose[dims={0, 2, 3, 1}](@242) -> float_type, {3, 128, 128, 3}, {49152, 128, 1, 16384}\n", "@630 = transpose[dims={0, 3, 1, 2}](@629) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@631 = identity(@630) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@632 = transpose[dims={0, 2, 3, 1}](@631) -> float_type, {3, 128, 128, 3}, {49152, 128, 1, 16384}\n", "@633 = transpose[dims={0, 3, 1, 2}](@628) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@634 = transpose[dims={0, 3, 1, 2}](@632) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@635 = transpose[dims={3, 2, 0, 1}](@634) -> float_type, {128, 128, 3, 3}, {1, 128, 49152, 16384}\n", "@636 = transpose[dims={3, 2, 0, 1}](@634) -> float_type, {128, 128, 3, 3}, {1, 128, 49152, 16384}\n", "@637 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@633,@636) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@638 = transpose[dims={0, 2, 3, 1}](@637) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@639 = identity(@241) -> float_type, {128}, {1}\n", "@640 = transpose[dims={0, 3, 1, 2}](@638) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@641 = broadcast[axis=1,dims={1, 128, 28, 28}](@639) -> float_type, {1, 128, 28, 28}, {0, 1, 0, 0}\n", "@642 = add(@640,@641) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@643 = transpose[dims={0, 2, 3, 1}](@642) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@644 = identity(@240) -> float_type, {128}, {1}\n", "@645 = identity(@239) -> float_type, {128}, {1}\n", "@646 = identity(@238) -> float_type, {128}, {1}\n", "@647 = identity(@237) -> float_type, {128}, {1}\n", "@648 = unknown:FusedBatchNormV3(@643,@644,@645,@646,@647) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@649 = transpose[dims={0, 3, 1, 2}](@648) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@650 = relu(@649) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@651 = transpose[dims={0, 2, 3, 1}](@650) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@652 = transpose[dims={0, 2, 3, 1}](@236) -> float_type, {1, 128, 512, 1}, {65536, 512, 1, 65536}\n", "@653 = transpose[dims={0, 3, 1, 2}](@652) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@654 = identity(@653) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@655 = transpose[dims={0, 2, 3, 1}](@654) -> float_type, {1, 128, 512, 1}, {65536, 512, 1, 65536}\n", "@656 = transpose[dims={0, 3, 1, 2}](@651) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@657 = transpose[dims={0, 3, 1, 2}](@655) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@658 = transpose[dims={3, 2, 0, 1}](@657) -> float_type, {512, 128, 1, 1}, {1, 512, 65536, 65536}\n", "@659 = transpose[dims={3, 2, 0, 1}](@657) -> float_type, {512, 128, 1, 1}, {1, 512, 65536, 65536}\n", "@660 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@656,@659) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@661 = transpose[dims={0, 2, 3, 1}](@660) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@662 = identity(@235) -> float_type, {512}, {1}\n", "@663 = transpose[dims={0, 3, 1, 2}](@661) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@664 = broadcast[axis=1,dims={1, 512, 28, 28}](@662) -> float_type, {1, 512, 28, 28}, {0, 1, 0, 0}\n", "@665 = add(@663,@664) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@666 = transpose[dims={0, 2, 3, 1}](@665) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@667 = identity(@234) -> float_type, {512}, {1}\n", "@668 = identity(@233) -> float_type, {512}, {1}\n", "@669 = identity(@232) -> float_type, {512}, {1}\n", "@670 = identity(@231) -> float_type, {512}, {1}\n", "@671 = unknown:FusedBatchNormV3(@666,@667,@668,@669,@670) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@672 = unknown:AddV2(@605,@671) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@673 = transpose[dims={0, 3, 1, 2}](@672) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@674 = relu(@673) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@675 = transpose[dims={0, 2, 3, 1}](@674) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@676 = transpose[dims={0, 2, 3, 1}](@230) -> float_type, {1, 512, 128, 1}, {65536, 128, 1, 65536}\n", "@677 = transpose[dims={0, 3, 1, 2}](@676) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@678 = identity(@677) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@679 = transpose[dims={0, 2, 3, 1}](@678) -> float_type, {1, 512, 128, 1}, {65536, 128, 1, 65536}\n", "@680 = transpose[dims={0, 3, 1, 2}](@675) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@681 = transpose[dims={0, 3, 1, 2}](@679) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@682 = transpose[dims={3, 2, 0, 1}](@681) -> float_type, {128, 512, 1, 1}, {1, 128, 65536, 65536}\n", "@683 = transpose[dims={3, 2, 0, 1}](@681) -> float_type, {128, 512, 1, 1}, {1, 128, 65536, 65536}\n", "@684 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@680,@683) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@685 = transpose[dims={0, 2, 3, 1}](@684) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@686 = identity(@229) -> float_type, {128}, {1}\n", "@687 = transpose[dims={0, 3, 1, 2}](@685) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@688 = broadcast[axis=1,dims={1, 128, 28, 28}](@686) -> float_type, {1, 128, 28, 28}, {0, 1, 0, 0}\n", "@689 = add(@687,@688) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@690 = transpose[dims={0, 2, 3, 1}](@689) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@691 = identity(@228) -> float_type, {128}, {1}\n", "@692 = identity(@227) -> float_type, {128}, {1}\n", "@693 = identity(@226) -> float_type, {128}, {1}\n", "@694 = identity(@225) -> float_type, {128}, {1}\n", "@695 = unknown:FusedBatchNormV3(@690,@691,@692,@693,@694) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@696 = transpose[dims={0, 3, 1, 2}](@695) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@697 = relu(@696) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@698 = transpose[dims={0, 2, 3, 1}](@697) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@699 = transpose[dims={0, 2, 3, 1}](@224) -> float_type, {3, 128, 128, 3}, {49152, 128, 1, 16384}\n", "@700 = transpose[dims={0, 3, 1, 2}](@699) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@701 = identity(@700) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@702 = transpose[dims={0, 2, 3, 1}](@701) -> float_type, {3, 128, 128, 3}, {49152, 128, 1, 16384}\n", "@703 = transpose[dims={0, 3, 1, 2}](@698) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@704 = transpose[dims={0, 3, 1, 2}](@702) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@705 = transpose[dims={3, 2, 0, 1}](@704) -> float_type, {128, 128, 3, 3}, {1, 128, 49152, 16384}\n", "@706 = transpose[dims={3, 2, 0, 1}](@704) -> float_type, {128, 128, 3, 3}, {1, 128, 49152, 16384}\n", "@707 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@703,@706) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@708 = transpose[dims={0, 2, 3, 1}](@707) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@709 = identity(@223) -> float_type, {128}, {1}\n", "@710 = transpose[dims={0, 3, 1, 2}](@708) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@711 = broadcast[axis=1,dims={1, 128, 28, 28}](@709) -> float_type, {1, 128, 28, 28}, {0, 1, 0, 0}\n", "@712 = add(@710,@711) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@713 = transpose[dims={0, 2, 3, 1}](@712) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@714 = identity(@222) -> float_type, {128}, {1}\n", "@715 = identity(@221) -> float_type, {128}, {1}\n", "@716 = identity(@220) -> float_type, {128}, {1}\n", "@717 = identity(@219) -> float_type, {128}, {1}\n", "@718 = unknown:FusedBatchNormV3(@713,@714,@715,@716,@717) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@719 = transpose[dims={0, 3, 1, 2}](@718) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@720 = relu(@719) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@721 = transpose[dims={0, 2, 3, 1}](@720) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@722 = transpose[dims={0, 2, 3, 1}](@218) -> float_type, {1, 128, 512, 1}, {65536, 512, 1, 65536}\n", "@723 = transpose[dims={0, 3, 1, 2}](@722) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@724 = identity(@723) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@725 = transpose[dims={0, 2, 3, 1}](@724) -> float_type, {1, 128, 512, 1}, {65536, 512, 1, 65536}\n", "@726 = transpose[dims={0, 3, 1, 2}](@721) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@727 = transpose[dims={0, 3, 1, 2}](@725) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@728 = transpose[dims={3, 2, 0, 1}](@727) -> float_type, {512, 128, 1, 1}, {1, 512, 65536, 65536}\n", "@729 = transpose[dims={3, 2, 0, 1}](@727) -> float_type, {512, 128, 1, 1}, {1, 512, 65536, 65536}\n", "@730 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@726,@729) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@731 = transpose[dims={0, 2, 3, 1}](@730) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@732 = identity(@217) -> float_type, {512}, {1}\n", "@733 = transpose[dims={0, 3, 1, 2}](@731) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@734 = broadcast[axis=1,dims={1, 512, 28, 28}](@732) -> float_type, {1, 512, 28, 28}, {0, 1, 0, 0}\n", "@735 = add(@733,@734) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@736 = transpose[dims={0, 2, 3, 1}](@735) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@737 = identity(@216) -> float_type, {512}, {1}\n", "@738 = identity(@215) -> float_type, {512}, {1}\n", "@739 = identity(@214) -> float_type, {512}, {1}\n", "@740 = identity(@213) -> float_type, {512}, {1}\n", "@741 = unknown:FusedBatchNormV3(@736,@737,@738,@739,@740) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@742 = unknown:AddV2(@675,@741) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@743 = transpose[dims={0, 3, 1, 2}](@742) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@744 = relu(@743) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@745 = transpose[dims={0, 2, 3, 1}](@744) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@746 = transpose[dims={0, 2, 3, 1}](@212) -> float_type, {1, 512, 128, 1}, {65536, 128, 1, 65536}\n", "@747 = transpose[dims={0, 3, 1, 2}](@746) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@748 = identity(@747) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@749 = transpose[dims={0, 2, 3, 1}](@748) -> float_type, {1, 512, 128, 1}, {65536, 128, 1, 65536}\n", "@750 = transpose[dims={0, 3, 1, 2}](@745) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@751 = transpose[dims={0, 3, 1, 2}](@749) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@752 = transpose[dims={3, 2, 0, 1}](@751) -> float_type, {128, 512, 1, 1}, {1, 128, 65536, 65536}\n", "@753 = transpose[dims={3, 2, 0, 1}](@751) -> float_type, {128, 512, 1, 1}, {1, 128, 65536, 65536}\n", "@754 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@750,@753) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@755 = transpose[dims={0, 2, 3, 1}](@754) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@756 = identity(@211) -> float_type, {128}, {1}\n", "@757 = transpose[dims={0, 3, 1, 2}](@755) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@758 = broadcast[axis=1,dims={1, 128, 28, 28}](@756) -> float_type, {1, 128, 28, 28}, {0, 1, 0, 0}\n", "@759 = add(@757,@758) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@760 = transpose[dims={0, 2, 3, 1}](@759) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@761 = identity(@210) -> float_type, {128}, {1}\n", "@762 = identity(@209) -> float_type, {128}, {1}\n", "@763 = identity(@208) -> float_type, {128}, {1}\n", "@764 = identity(@207) -> float_type, {128}, {1}\n", "@765 = unknown:FusedBatchNormV3(@760,@761,@762,@763,@764) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@766 = transpose[dims={0, 3, 1, 2}](@765) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@767 = relu(@766) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@768 = transpose[dims={0, 2, 3, 1}](@767) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@769 = transpose[dims={0, 2, 3, 1}](@206) -> float_type, {3, 128, 128, 3}, {49152, 128, 1, 16384}\n", "@770 = transpose[dims={0, 3, 1, 2}](@769) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@771 = identity(@770) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@772 = transpose[dims={0, 2, 3, 1}](@771) -> float_type, {3, 128, 128, 3}, {49152, 128, 1, 16384}\n", "@773 = transpose[dims={0, 3, 1, 2}](@768) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@774 = transpose[dims={0, 3, 1, 2}](@772) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@775 = transpose[dims={3, 2, 0, 1}](@774) -> float_type, {128, 128, 3, 3}, {1, 128, 49152, 16384}\n", "@776 = transpose[dims={3, 2, 0, 1}](@774) -> float_type, {128, 128, 3, 3}, {1, 128, 49152, 16384}\n", "@777 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@773,@776) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@778 = transpose[dims={0, 2, 3, 1}](@777) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@779 = identity(@205) -> float_type, {128}, {1}\n", "@780 = transpose[dims={0, 3, 1, 2}](@778) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@781 = broadcast[axis=1,dims={1, 128, 28, 28}](@779) -> float_type, {1, 128, 28, 28}, {0, 1, 0, 0}\n", "@782 = add(@780,@781) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@783 = transpose[dims={0, 2, 3, 1}](@782) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@784 = identity(@204) -> float_type, {128}, {1}\n", "@785 = identity(@203) -> float_type, {128}, {1}\n", "@786 = identity(@202) -> float_type, {128}, {1}\n", "@787 = identity(@201) -> float_type, {128}, {1}\n", "@788 = unknown:FusedBatchNormV3(@783,@784,@785,@786,@787) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@789 = transpose[dims={0, 3, 1, 2}](@788) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@790 = relu(@789) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@791 = transpose[dims={0, 2, 3, 1}](@790) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@792 = transpose[dims={0, 2, 3, 1}](@200) -> float_type, {1, 128, 512, 1}, {65536, 512, 1, 65536}\n", "@793 = transpose[dims={0, 3, 1, 2}](@792) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@794 = identity(@793) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@795 = transpose[dims={0, 2, 3, 1}](@794) -> float_type, {1, 128, 512, 1}, {65536, 512, 1, 65536}\n", "@796 = transpose[dims={0, 3, 1, 2}](@791) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@797 = transpose[dims={0, 3, 1, 2}](@795) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@798 = transpose[dims={3, 2, 0, 1}](@797) -> float_type, {512, 128, 1, 1}, {1, 512, 65536, 65536}\n", "@799 = transpose[dims={3, 2, 0, 1}](@797) -> float_type, {512, 128, 1, 1}, {1, 512, 65536, 65536}\n", "@800 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@796,@799) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@801 = transpose[dims={0, 2, 3, 1}](@800) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@802 = identity(@199) -> float_type, {512}, {1}\n", "@803 = transpose[dims={0, 3, 1, 2}](@801) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@804 = broadcast[axis=1,dims={1, 512, 28, 28}](@802) -> float_type, {1, 512, 28, 28}, {0, 1, 0, 0}\n", "@805 = add(@803,@804) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@806 = transpose[dims={0, 2, 3, 1}](@805) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@807 = identity(@198) -> float_type, {512}, {1}\n", "@808 = identity(@197) -> float_type, {512}, {1}\n", "@809 = identity(@196) -> float_type, {512}, {1}\n", "@810 = identity(@195) -> float_type, {512}, {1}\n", "@811 = unknown:FusedBatchNormV3(@806,@807,@808,@809,@810) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@812 = unknown:AddV2(@745,@811) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@813 = transpose[dims={0, 3, 1, 2}](@812) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@814 = relu(@813) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@815 = transpose[dims={0, 2, 3, 1}](@814) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@816 = transpose[dims={0, 2, 3, 1}](@194) -> float_type, {1, 512, 128, 1}, {65536, 128, 1, 65536}\n", "@817 = transpose[dims={0, 3, 1, 2}](@816) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@818 = identity(@817) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@819 = transpose[dims={0, 2, 3, 1}](@818) -> float_type, {1, 512, 128, 1}, {65536, 128, 1, 65536}\n", "@820 = transpose[dims={0, 3, 1, 2}](@815) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@821 = transpose[dims={0, 3, 1, 2}](@819) -> float_type, {1, 1, 512, 128}, {65536, 65536, 128, 1}\n", "@822 = transpose[dims={3, 2, 0, 1}](@821) -> float_type, {128, 512, 1, 1}, {1, 128, 65536, 65536}\n", "@823 = transpose[dims={3, 2, 0, 1}](@821) -> float_type, {128, 512, 1, 1}, {1, 128, 65536, 65536}\n", "@824 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@820,@823) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@825 = transpose[dims={0, 2, 3, 1}](@824) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@826 = identity(@193) -> float_type, {128}, {1}\n", "@827 = transpose[dims={0, 3, 1, 2}](@825) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@828 = broadcast[axis=1,dims={1, 128, 28, 28}](@826) -> float_type, {1, 128, 28, 28}, {0, 1, 0, 0}\n", "@829 = add(@827,@828) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@830 = transpose[dims={0, 2, 3, 1}](@829) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@831 = identity(@192) -> float_type, {128}, {1}\n", "@832 = identity(@191) -> float_type, {128}, {1}\n", "@833 = identity(@190) -> float_type, {128}, {1}\n", "@834 = identity(@189) -> float_type, {128}, {1}\n", "@835 = unknown:FusedBatchNormV3(@830,@831,@832,@833,@834) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@836 = transpose[dims={0, 3, 1, 2}](@835) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@837 = relu(@836) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@838 = transpose[dims={0, 2, 3, 1}](@837) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@839 = transpose[dims={0, 2, 3, 1}](@188) -> float_type, {3, 128, 128, 3}, {49152, 128, 1, 16384}\n", "@840 = transpose[dims={0, 3, 1, 2}](@839) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@841 = identity(@840) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@842 = transpose[dims={0, 2, 3, 1}](@841) -> float_type, {3, 128, 128, 3}, {49152, 128, 1, 16384}\n", "@843 = transpose[dims={0, 3, 1, 2}](@838) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@844 = transpose[dims={0, 3, 1, 2}](@842) -> float_type, {3, 3, 128, 128}, {49152, 16384, 128, 1}\n", "@845 = transpose[dims={3, 2, 0, 1}](@844) -> float_type, {128, 128, 3, 3}, {1, 128, 49152, 16384}\n", "@846 = transpose[dims={3, 2, 0, 1}](@844) -> float_type, {128, 128, 3, 3}, {1, 128, 49152, 16384}\n", "@847 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@843,@846) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@848 = transpose[dims={0, 2, 3, 1}](@847) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@849 = identity(@187) -> float_type, {128}, {1}\n", "@850 = transpose[dims={0, 3, 1, 2}](@848) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@851 = broadcast[axis=1,dims={1, 128, 28, 28}](@849) -> float_type, {1, 128, 28, 28}, {0, 1, 0, 0}\n", "@852 = add(@850,@851) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@853 = transpose[dims={0, 2, 3, 1}](@852) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@854 = identity(@186) -> float_type, {128}, {1}\n", "@855 = identity(@185) -> float_type, {128}, {1}\n", "@856 = identity(@184) -> float_type, {128}, {1}\n", "@857 = identity(@183) -> float_type, {128}, {1}\n", "@858 = unknown:FusedBatchNormV3(@853,@854,@855,@856,@857) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@859 = transpose[dims={0, 3, 1, 2}](@858) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@860 = relu(@859) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@861 = transpose[dims={0, 2, 3, 1}](@860) -> float_type, {1, 28, 28, 128}, {100352, 28, 1, 784}\n", "@862 = transpose[dims={0, 2, 3, 1}](@182) -> float_type, {1, 128, 512, 1}, {65536, 512, 1, 65536}\n", "@863 = transpose[dims={0, 3, 1, 2}](@862) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@864 = identity(@863) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@865 = transpose[dims={0, 2, 3, 1}](@864) -> float_type, {1, 128, 512, 1}, {65536, 512, 1, 65536}\n", "@866 = transpose[dims={0, 3, 1, 2}](@861) -> float_type, {1, 128, 28, 28}, {100352, 784, 28, 1}\n", "@867 = transpose[dims={0, 3, 1, 2}](@865) -> float_type, {1, 1, 128, 512}, {65536, 65536, 512, 1}\n", "@868 = transpose[dims={3, 2, 0, 1}](@867) -> float_type, {512, 128, 1, 1}, {1, 512, 65536, 65536}\n", "@869 = transpose[dims={3, 2, 0, 1}](@867) -> float_type, {512, 128, 1, 1}, {1, 512, 65536, 65536}\n", "@870 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@866,@869) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@871 = transpose[dims={0, 2, 3, 1}](@870) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@872 = identity(@181) -> float_type, {512}, {1}\n", "@873 = transpose[dims={0, 3, 1, 2}](@871) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@874 = broadcast[axis=1,dims={1, 512, 28, 28}](@872) -> float_type, {1, 512, 28, 28}, {0, 1, 0, 0}\n", "@875 = add(@873,@874) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@876 = transpose[dims={0, 2, 3, 1}](@875) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@877 = identity(@180) -> float_type, {512}, {1}\n", "@878 = identity(@179) -> float_type, {512}, {1}\n", "@879 = identity(@178) -> float_type, {512}, {1}\n", "@880 = identity(@177) -> float_type, {512}, {1}\n", "@881 = unknown:FusedBatchNormV3(@876,@877,@878,@879,@880) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@882 = unknown:AddV2(@815,@881) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@883 = transpose[dims={0, 3, 1, 2}](@882) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@884 = relu(@883) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@885 = transpose[dims={0, 2, 3, 1}](@884) -> float_type, {1, 28, 28, 512}, {401408, 28, 1, 784}\n", "@886 = transpose[dims={0, 2, 3, 1}](@176) -> float_type, {1, 512, 1024, 1}, {524288, 1024, 1, 524288}\n", "@887 = transpose[dims={0, 3, 1, 2}](@886) -> float_type, {1, 1, 512, 1024}, {524288, 524288, 1024, 1}\n", "@888 = identity(@887) -> float_type, {1, 1, 512, 1024}, {524288, 524288, 1024, 1}\n", "@889 = transpose[dims={0, 2, 3, 1}](@888) -> float_type, {1, 512, 1024, 1}, {524288, 1024, 1, 524288}\n", "@890 = transpose[dims={0, 3, 1, 2}](@885) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@891 = transpose[dims={0, 3, 1, 2}](@889) -> float_type, {1, 1, 512, 1024}, {524288, 524288, 1024, 1}\n", "@892 = transpose[dims={3, 2, 0, 1}](@891) -> float_type, {1024, 512, 1, 1}, {1, 1024, 524288, 524288}\n", "@893 = transpose[dims={3, 2, 0, 1}](@891) -> float_type, {1024, 512, 1, 1}, {1, 1024, 524288, 524288}\n", "@894 = convolution[padding={0, 0},stride={2, 2},dilation={1, 1},group=1,padding_mode=2](@890,@893) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@895 = transpose[dims={0, 2, 3, 1}](@894) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@896 = identity(@175) -> float_type, {1024}, {1}\n", "@897 = transpose[dims={0, 3, 1, 2}](@895) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@898 = broadcast[axis=1,dims={1, 1024, 14, 14}](@896) -> float_type, {1, 1024, 14, 14}, {0, 1, 0, 0}\n", "@899 = add(@897,@898) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@900 = transpose[dims={0, 2, 3, 1}](@899) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@901 = identity(@174) -> float_type, {1024}, {1}\n", "@902 = identity(@173) -> float_type, {1024}, {1}\n", "@903 = identity(@172) -> float_type, {1024}, {1}\n", "@904 = identity(@171) -> float_type, {1024}, {1}\n", "@905 = unknown:FusedBatchNormV3(@900,@901,@902,@903,@904) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@906 = transpose[dims={0, 2, 3, 1}](@170) -> float_type, {1, 512, 256, 1}, {131072, 256, 1, 131072}\n", "@907 = transpose[dims={0, 3, 1, 2}](@906) -> float_type, {1, 1, 512, 256}, {131072, 131072, 256, 1}\n", "@908 = identity(@907) -> float_type, {1, 1, 512, 256}, {131072, 131072, 256, 1}\n", "@909 = transpose[dims={0, 2, 3, 1}](@908) -> float_type, {1, 512, 256, 1}, {131072, 256, 1, 131072}\n", "@910 = transpose[dims={0, 3, 1, 2}](@885) -> float_type, {1, 512, 28, 28}, {401408, 784, 28, 1}\n", "@911 = transpose[dims={0, 3, 1, 2}](@909) -> float_type, {1, 1, 512, 256}, {131072, 131072, 256, 1}\n", "@912 = transpose[dims={3, 2, 0, 1}](@911) -> float_type, {256, 512, 1, 1}, {1, 256, 131072, 131072}\n", "@913 = transpose[dims={3, 2, 0, 1}](@911) -> float_type, {256, 512, 1, 1}, {1, 256, 131072, 131072}\n", "@914 = convolution[padding={0, 0},stride={2, 2},dilation={1, 1},group=1,padding_mode=2](@910,@913) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@915 = transpose[dims={0, 2, 3, 1}](@914) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@916 = identity(@169) -> float_type, {256}, {1}\n", "@917 = transpose[dims={0, 3, 1, 2}](@915) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@918 = broadcast[axis=1,dims={1, 256, 14, 14}](@916) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@919 = add(@917,@918) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@920 = transpose[dims={0, 2, 3, 1}](@919) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@921 = identity(@168) -> float_type, {256}, {1}\n", "@922 = identity(@167) -> float_type, {256}, {1}\n", "@923 = identity(@166) -> float_type, {256}, {1}\n", "@924 = identity(@165) -> float_type, {256}, {1}\n", "@925 = unknown:FusedBatchNormV3(@920,@921,@922,@923,@924) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@926 = transpose[dims={0, 3, 1, 2}](@925) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@927 = relu(@926) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@928 = transpose[dims={0, 2, 3, 1}](@927) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@929 = transpose[dims={0, 2, 3, 1}](@164) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@930 = transpose[dims={0, 3, 1, 2}](@929) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@931 = identity(@930) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@932 = transpose[dims={0, 2, 3, 1}](@931) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@933 = transpose[dims={0, 3, 1, 2}](@928) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@934 = transpose[dims={0, 3, 1, 2}](@932) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@935 = transpose[dims={3, 2, 0, 1}](@934) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@936 = transpose[dims={3, 2, 0, 1}](@934) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@937 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@933,@936) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@938 = transpose[dims={0, 2, 3, 1}](@937) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@939 = identity(@163) -> float_type, {256}, {1}\n", "@940 = transpose[dims={0, 3, 1, 2}](@938) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@941 = broadcast[axis=1,dims={1, 256, 14, 14}](@939) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@942 = add(@940,@941) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@943 = transpose[dims={0, 2, 3, 1}](@942) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@944 = identity(@162) -> float_type, {256}, {1}\n", "@945 = identity(@161) -> float_type, {256}, {1}\n", "@946 = identity(@160) -> float_type, {256}, {1}\n", "@947 = identity(@159) -> float_type, {256}, {1}\n", "@948 = unknown:FusedBatchNormV3(@943,@944,@945,@946,@947) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@949 = transpose[dims={0, 3, 1, 2}](@948) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@950 = relu(@949) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@951 = transpose[dims={0, 2, 3, 1}](@950) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@952 = transpose[dims={0, 2, 3, 1}](@158) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@953 = transpose[dims={0, 3, 1, 2}](@952) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@954 = identity(@953) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@955 = transpose[dims={0, 2, 3, 1}](@954) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@956 = transpose[dims={0, 3, 1, 2}](@951) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@957 = transpose[dims={0, 3, 1, 2}](@955) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@958 = transpose[dims={3, 2, 0, 1}](@957) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@959 = transpose[dims={3, 2, 0, 1}](@957) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@960 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@956,@959) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@961 = transpose[dims={0, 2, 3, 1}](@960) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@962 = identity(@157) -> float_type, {1024}, {1}\n", "@963 = transpose[dims={0, 3, 1, 2}](@961) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@964 = broadcast[axis=1,dims={1, 1024, 14, 14}](@962) -> float_type, {1, 1024, 14, 14}, {0, 1, 0, 0}\n", "@965 = add(@963,@964) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@966 = transpose[dims={0, 2, 3, 1}](@965) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@967 = identity(@156) -> float_type, {1024}, {1}\n", "@968 = identity(@155) -> float_type, {1024}, {1}\n", "@969 = identity(@154) -> float_type, {1024}, {1}\n", "@970 = identity(@153) -> float_type, {1024}, {1}\n", "@971 = unknown:FusedBatchNormV3(@966,@967,@968,@969,@970) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@972 = unknown:AddV2(@905,@971) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@973 = transpose[dims={0, 3, 1, 2}](@972) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@974 = relu(@973) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@975 = transpose[dims={0, 2, 3, 1}](@974) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@976 = transpose[dims={0, 2, 3, 1}](@152) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@977 = transpose[dims={0, 3, 1, 2}](@976) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@978 = identity(@977) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@979 = transpose[dims={0, 2, 3, 1}](@978) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@980 = transpose[dims={0, 3, 1, 2}](@975) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@981 = transpose[dims={0, 3, 1, 2}](@979) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@982 = transpose[dims={3, 2, 0, 1}](@981) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@983 = transpose[dims={3, 2, 0, 1}](@981) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@984 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@980,@983) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@985 = transpose[dims={0, 2, 3, 1}](@984) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@986 = identity(@151) -> float_type, {256}, {1}\n", "@987 = transpose[dims={0, 3, 1, 2}](@985) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@988 = broadcast[axis=1,dims={1, 256, 14, 14}](@986) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@989 = add(@987,@988) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@990 = transpose[dims={0, 2, 3, 1}](@989) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@991 = identity(@150) -> float_type, {256}, {1}\n", "@992 = identity(@149) -> float_type, {256}, {1}\n", "@993 = identity(@148) -> float_type, {256}, {1}\n", "@994 = identity(@147) -> float_type, {256}, {1}\n", "@995 = unknown:FusedBatchNormV3(@990,@991,@992,@993,@994) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@996 = transpose[dims={0, 3, 1, 2}](@995) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@997 = relu(@996) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@998 = transpose[dims={0, 2, 3, 1}](@997) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@999 = transpose[dims={0, 2, 3, 1}](@146) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1000 = transpose[dims={0, 3, 1, 2}](@999) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1001 = identity(@1000) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1002 = transpose[dims={0, 2, 3, 1}](@1001) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1003 = transpose[dims={0, 3, 1, 2}](@998) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1004 = transpose[dims={0, 3, 1, 2}](@1002) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1005 = transpose[dims={3, 2, 0, 1}](@1004) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1006 = transpose[dims={3, 2, 0, 1}](@1004) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1007 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@1003,@1006) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1008 = transpose[dims={0, 2, 3, 1}](@1007) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1009 = identity(@145) -> float_type, {256}, {1}\n", "@1010 = transpose[dims={0, 3, 1, 2}](@1008) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1011 = broadcast[axis=1,dims={1, 256, 14, 14}](@1009) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1012 = add(@1010,@1011) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1013 = transpose[dims={0, 2, 3, 1}](@1012) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1014 = identity(@144) -> float_type, {256}, {1}\n", "@1015 = identity(@143) -> float_type, {256}, {1}\n", "@1016 = identity(@142) -> float_type, {256}, {1}\n", "@1017 = identity(@141) -> float_type, {256}, {1}\n", "@1018 = unknown:FusedBatchNormV3(@1013,@1014,@1015,@1016,@1017) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1019 = transpose[dims={0, 3, 1, 2}](@1018) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1020 = relu(@1019) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1021 = transpose[dims={0, 2, 3, 1}](@1020) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1022 = transpose[dims={0, 2, 3, 1}](@140) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1023 = transpose[dims={0, 3, 1, 2}](@1022) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1024 = identity(@1023) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1025 = transpose[dims={0, 2, 3, 1}](@1024) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1026 = transpose[dims={0, 3, 1, 2}](@1021) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1027 = transpose[dims={0, 3, 1, 2}](@1025) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1028 = transpose[dims={3, 2, 0, 1}](@1027) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1029 = transpose[dims={3, 2, 0, 1}](@1027) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1030 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1026,@1029) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1031 = transpose[dims={0, 2, 3, 1}](@1030) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1032 = identity(@139) -> float_type, {1024}, {1}\n", "@1033 = transpose[dims={0, 3, 1, 2}](@1031) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1034 = broadcast[axis=1,dims={1, 1024, 14, 14}](@1032) -> float_type, {1, 1024, 14, 14}, {0, 1, 0, 0}\n", "@1035 = add(@1033,@1034) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1036 = transpose[dims={0, 2, 3, 1}](@1035) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1037 = identity(@138) -> float_type, {1024}, {1}\n", "@1038 = identity(@137) -> float_type, {1024}, {1}\n", "@1039 = identity(@136) -> float_type, {1024}, {1}\n", "@1040 = identity(@135) -> float_type, {1024}, {1}\n", "@1041 = unknown:FusedBatchNormV3(@1036,@1037,@1038,@1039,@1040) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1042 = unknown:AddV2(@975,@1041) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1043 = transpose[dims={0, 3, 1, 2}](@1042) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1044 = relu(@1043) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1045 = transpose[dims={0, 2, 3, 1}](@1044) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1046 = transpose[dims={0, 2, 3, 1}](@134) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@1047 = transpose[dims={0, 3, 1, 2}](@1046) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1048 = identity(@1047) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1049 = transpose[dims={0, 2, 3, 1}](@1048) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@1050 = transpose[dims={0, 3, 1, 2}](@1045) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1051 = transpose[dims={0, 3, 1, 2}](@1049) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1052 = transpose[dims={3, 2, 0, 1}](@1051) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@1053 = transpose[dims={3, 2, 0, 1}](@1051) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@1054 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1050,@1053) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1055 = transpose[dims={0, 2, 3, 1}](@1054) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1056 = identity(@133) -> float_type, {256}, {1}\n", "@1057 = transpose[dims={0, 3, 1, 2}](@1055) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1058 = broadcast[axis=1,dims={1, 256, 14, 14}](@1056) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1059 = add(@1057,@1058) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1060 = transpose[dims={0, 2, 3, 1}](@1059) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1061 = identity(@132) -> float_type, {256}, {1}\n", "@1062 = identity(@131) -> float_type, {256}, {1}\n", "@1063 = identity(@130) -> float_type, {256}, {1}\n", "@1064 = identity(@129) -> float_type, {256}, {1}\n", "@1065 = unknown:FusedBatchNormV3(@1060,@1061,@1062,@1063,@1064) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1066 = transpose[dims={0, 3, 1, 2}](@1065) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1067 = relu(@1066) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1068 = transpose[dims={0, 2, 3, 1}](@1067) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1069 = transpose[dims={0, 2, 3, 1}](@128) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1070 = transpose[dims={0, 3, 1, 2}](@1069) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1071 = identity(@1070) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1072 = transpose[dims={0, 2, 3, 1}](@1071) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1073 = transpose[dims={0, 3, 1, 2}](@1068) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1074 = transpose[dims={0, 3, 1, 2}](@1072) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1075 = transpose[dims={3, 2, 0, 1}](@1074) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1076 = transpose[dims={3, 2, 0, 1}](@1074) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1077 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@1073,@1076) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1078 = transpose[dims={0, 2, 3, 1}](@1077) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1079 = identity(@127) -> float_type, {256}, {1}\n", "@1080 = transpose[dims={0, 3, 1, 2}](@1078) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1081 = broadcast[axis=1,dims={1, 256, 14, 14}](@1079) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1082 = add(@1080,@1081) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1083 = transpose[dims={0, 2, 3, 1}](@1082) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1084 = identity(@126) -> float_type, {256}, {1}\n", "@1085 = identity(@125) -> float_type, {256}, {1}\n", "@1086 = identity(@124) -> float_type, {256}, {1}\n", "@1087 = identity(@123) -> float_type, {256}, {1}\n", "@1088 = unknown:FusedBatchNormV3(@1083,@1084,@1085,@1086,@1087) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1089 = transpose[dims={0, 3, 1, 2}](@1088) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1090 = relu(@1089) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1091 = transpose[dims={0, 2, 3, 1}](@1090) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1092 = transpose[dims={0, 2, 3, 1}](@122) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1093 = transpose[dims={0, 3, 1, 2}](@1092) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1094 = identity(@1093) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1095 = transpose[dims={0, 2, 3, 1}](@1094) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1096 = transpose[dims={0, 3, 1, 2}](@1091) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1097 = transpose[dims={0, 3, 1, 2}](@1095) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1098 = transpose[dims={3, 2, 0, 1}](@1097) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1099 = transpose[dims={3, 2, 0, 1}](@1097) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1100 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1096,@1099) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1101 = transpose[dims={0, 2, 3, 1}](@1100) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1102 = identity(@121) -> float_type, {1024}, {1}\n", "@1103 = transpose[dims={0, 3, 1, 2}](@1101) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1104 = broadcast[axis=1,dims={1, 1024, 14, 14}](@1102) -> float_type, {1, 1024, 14, 14}, {0, 1, 0, 0}\n", "@1105 = add(@1103,@1104) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1106 = transpose[dims={0, 2, 3, 1}](@1105) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1107 = identity(@120) -> float_type, {1024}, {1}\n", "@1108 = identity(@119) -> float_type, {1024}, {1}\n", "@1109 = identity(@118) -> float_type, {1024}, {1}\n", "@1110 = identity(@117) -> float_type, {1024}, {1}\n", "@1111 = unknown:FusedBatchNormV3(@1106,@1107,@1108,@1109,@1110) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1112 = unknown:AddV2(@1045,@1111) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1113 = transpose[dims={0, 3, 1, 2}](@1112) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1114 = relu(@1113) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1115 = transpose[dims={0, 2, 3, 1}](@1114) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1116 = transpose[dims={0, 2, 3, 1}](@116) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@1117 = transpose[dims={0, 3, 1, 2}](@1116) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1118 = identity(@1117) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1119 = transpose[dims={0, 2, 3, 1}](@1118) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@1120 = transpose[dims={0, 3, 1, 2}](@1115) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1121 = transpose[dims={0, 3, 1, 2}](@1119) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1122 = transpose[dims={3, 2, 0, 1}](@1121) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@1123 = transpose[dims={3, 2, 0, 1}](@1121) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@1124 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1120,@1123) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1125 = transpose[dims={0, 2, 3, 1}](@1124) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1126 = identity(@115) -> float_type, {256}, {1}\n", "@1127 = transpose[dims={0, 3, 1, 2}](@1125) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1128 = broadcast[axis=1,dims={1, 256, 14, 14}](@1126) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1129 = add(@1127,@1128) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1130 = transpose[dims={0, 2, 3, 1}](@1129) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1131 = identity(@114) -> float_type, {256}, {1}\n", "@1132 = identity(@113) -> float_type, {256}, {1}\n", "@1133 = identity(@112) -> float_type, {256}, {1}\n", "@1134 = identity(@111) -> float_type, {256}, {1}\n", "@1135 = unknown:FusedBatchNormV3(@1130,@1131,@1132,@1133,@1134) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1136 = transpose[dims={0, 3, 1, 2}](@1135) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1137 = relu(@1136) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1138 = transpose[dims={0, 2, 3, 1}](@1137) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1139 = transpose[dims={0, 2, 3, 1}](@110) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1140 = transpose[dims={0, 3, 1, 2}](@1139) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1141 = identity(@1140) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1142 = transpose[dims={0, 2, 3, 1}](@1141) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1143 = transpose[dims={0, 3, 1, 2}](@1138) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1144 = transpose[dims={0, 3, 1, 2}](@1142) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1145 = transpose[dims={3, 2, 0, 1}](@1144) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1146 = transpose[dims={3, 2, 0, 1}](@1144) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1147 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@1143,@1146) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1148 = transpose[dims={0, 2, 3, 1}](@1147) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1149 = identity(@109) -> float_type, {256}, {1}\n", "@1150 = transpose[dims={0, 3, 1, 2}](@1148) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1151 = broadcast[axis=1,dims={1, 256, 14, 14}](@1149) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1152 = add(@1150,@1151) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1153 = transpose[dims={0, 2, 3, 1}](@1152) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1154 = identity(@108) -> float_type, {256}, {1}\n", "@1155 = identity(@107) -> float_type, {256}, {1}\n", "@1156 = identity(@106) -> float_type, {256}, {1}\n", "@1157 = identity(@105) -> float_type, {256}, {1}\n", "@1158 = unknown:FusedBatchNormV3(@1153,@1154,@1155,@1156,@1157) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1159 = transpose[dims={0, 3, 1, 2}](@1158) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1160 = relu(@1159) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1161 = transpose[dims={0, 2, 3, 1}](@1160) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1162 = transpose[dims={0, 2, 3, 1}](@104) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1163 = transpose[dims={0, 3, 1, 2}](@1162) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1164 = identity(@1163) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1165 = transpose[dims={0, 2, 3, 1}](@1164) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1166 = transpose[dims={0, 3, 1, 2}](@1161) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1167 = transpose[dims={0, 3, 1, 2}](@1165) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1168 = transpose[dims={3, 2, 0, 1}](@1167) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1169 = transpose[dims={3, 2, 0, 1}](@1167) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1170 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1166,@1169) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1171 = transpose[dims={0, 2, 3, 1}](@1170) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1172 = identity(@103) -> float_type, {1024}, {1}\n", "@1173 = transpose[dims={0, 3, 1, 2}](@1171) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1174 = broadcast[axis=1,dims={1, 1024, 14, 14}](@1172) -> float_type, {1, 1024, 14, 14}, {0, 1, 0, 0}\n", "@1175 = add(@1173,@1174) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1176 = transpose[dims={0, 2, 3, 1}](@1175) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1177 = identity(@102) -> float_type, {1024}, {1}\n", "@1178 = identity(@101) -> float_type, {1024}, {1}\n", "@1179 = identity(@100) -> float_type, {1024}, {1}\n", "@1180 = identity(@99) -> float_type, {1024}, {1}\n", "@1181 = unknown:FusedBatchNormV3(@1176,@1177,@1178,@1179,@1180) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1182 = unknown:AddV2(@1115,@1181) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1183 = transpose[dims={0, 3, 1, 2}](@1182) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1184 = relu(@1183) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1185 = transpose[dims={0, 2, 3, 1}](@1184) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1186 = transpose[dims={0, 2, 3, 1}](@98) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@1187 = transpose[dims={0, 3, 1, 2}](@1186) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1188 = identity(@1187) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1189 = transpose[dims={0, 2, 3, 1}](@1188) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@1190 = transpose[dims={0, 3, 1, 2}](@1185) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1191 = transpose[dims={0, 3, 1, 2}](@1189) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1192 = transpose[dims={3, 2, 0, 1}](@1191) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@1193 = transpose[dims={3, 2, 0, 1}](@1191) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@1194 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1190,@1193) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1195 = transpose[dims={0, 2, 3, 1}](@1194) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1196 = identity(@97) -> float_type, {256}, {1}\n", "@1197 = transpose[dims={0, 3, 1, 2}](@1195) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1198 = broadcast[axis=1,dims={1, 256, 14, 14}](@1196) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1199 = add(@1197,@1198) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1200 = transpose[dims={0, 2, 3, 1}](@1199) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1201 = identity(@96) -> float_type, {256}, {1}\n", "@1202 = identity(@95) -> float_type, {256}, {1}\n", "@1203 = identity(@94) -> float_type, {256}, {1}\n", "@1204 = identity(@93) -> float_type, {256}, {1}\n", "@1205 = unknown:FusedBatchNormV3(@1200,@1201,@1202,@1203,@1204) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1206 = transpose[dims={0, 3, 1, 2}](@1205) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1207 = relu(@1206) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1208 = transpose[dims={0, 2, 3, 1}](@1207) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1209 = transpose[dims={0, 2, 3, 1}](@92) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1210 = transpose[dims={0, 3, 1, 2}](@1209) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1211 = identity(@1210) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1212 = transpose[dims={0, 2, 3, 1}](@1211) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1213 = transpose[dims={0, 3, 1, 2}](@1208) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1214 = transpose[dims={0, 3, 1, 2}](@1212) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1215 = transpose[dims={3, 2, 0, 1}](@1214) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1216 = transpose[dims={3, 2, 0, 1}](@1214) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1217 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@1213,@1216) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1218 = transpose[dims={0, 2, 3, 1}](@1217) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1219 = identity(@91) -> float_type, {256}, {1}\n", "@1220 = transpose[dims={0, 3, 1, 2}](@1218) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1221 = broadcast[axis=1,dims={1, 256, 14, 14}](@1219) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1222 = add(@1220,@1221) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1223 = transpose[dims={0, 2, 3, 1}](@1222) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1224 = identity(@90) -> float_type, {256}, {1}\n", "@1225 = identity(@89) -> float_type, {256}, {1}\n", "@1226 = identity(@88) -> float_type, {256}, {1}\n", "@1227 = identity(@87) -> float_type, {256}, {1}\n", "@1228 = unknown:FusedBatchNormV3(@1223,@1224,@1225,@1226,@1227) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1229 = transpose[dims={0, 3, 1, 2}](@1228) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1230 = relu(@1229) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1231 = transpose[dims={0, 2, 3, 1}](@1230) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1232 = transpose[dims={0, 2, 3, 1}](@86) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1233 = transpose[dims={0, 3, 1, 2}](@1232) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1234 = identity(@1233) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1235 = transpose[dims={0, 2, 3, 1}](@1234) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1236 = transpose[dims={0, 3, 1, 2}](@1231) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1237 = transpose[dims={0, 3, 1, 2}](@1235) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1238 = transpose[dims={3, 2, 0, 1}](@1237) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1239 = transpose[dims={3, 2, 0, 1}](@1237) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1240 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1236,@1239) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1241 = transpose[dims={0, 2, 3, 1}](@1240) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1242 = identity(@85) -> float_type, {1024}, {1}\n", "@1243 = transpose[dims={0, 3, 1, 2}](@1241) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1244 = broadcast[axis=1,dims={1, 1024, 14, 14}](@1242) -> float_type, {1, 1024, 14, 14}, {0, 1, 0, 0}\n", "@1245 = add(@1243,@1244) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1246 = transpose[dims={0, 2, 3, 1}](@1245) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1247 = identity(@84) -> float_type, {1024}, {1}\n", "@1248 = identity(@83) -> float_type, {1024}, {1}\n", "@1249 = identity(@82) -> float_type, {1024}, {1}\n", "@1250 = identity(@81) -> float_type, {1024}, {1}\n", "@1251 = unknown:FusedBatchNormV3(@1246,@1247,@1248,@1249,@1250) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1252 = unknown:AddV2(@1185,@1251) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1253 = transpose[dims={0, 3, 1, 2}](@1252) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1254 = relu(@1253) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1255 = transpose[dims={0, 2, 3, 1}](@1254) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1256 = transpose[dims={0, 2, 3, 1}](@80) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@1257 = transpose[dims={0, 3, 1, 2}](@1256) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1258 = identity(@1257) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1259 = transpose[dims={0, 2, 3, 1}](@1258) -> float_type, {1, 1024, 256, 1}, {262144, 256, 1, 262144}\n", "@1260 = transpose[dims={0, 3, 1, 2}](@1255) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1261 = transpose[dims={0, 3, 1, 2}](@1259) -> float_type, {1, 1, 1024, 256}, {262144, 262144, 256, 1}\n", "@1262 = transpose[dims={3, 2, 0, 1}](@1261) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@1263 = transpose[dims={3, 2, 0, 1}](@1261) -> float_type, {256, 1024, 1, 1}, {1, 256, 262144, 262144}\n", "@1264 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1260,@1263) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1265 = transpose[dims={0, 2, 3, 1}](@1264) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1266 = identity(@79) -> float_type, {256}, {1}\n", "@1267 = transpose[dims={0, 3, 1, 2}](@1265) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1268 = broadcast[axis=1,dims={1, 256, 14, 14}](@1266) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1269 = add(@1267,@1268) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1270 = transpose[dims={0, 2, 3, 1}](@1269) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1271 = identity(@78) -> float_type, {256}, {1}\n", "@1272 = identity(@77) -> float_type, {256}, {1}\n", "@1273 = identity(@76) -> float_type, {256}, {1}\n", "@1274 = identity(@75) -> float_type, {256}, {1}\n", "@1275 = unknown:FusedBatchNormV3(@1270,@1271,@1272,@1273,@1274) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1276 = transpose[dims={0, 3, 1, 2}](@1275) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1277 = relu(@1276) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1278 = transpose[dims={0, 2, 3, 1}](@1277) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1279 = transpose[dims={0, 2, 3, 1}](@74) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1280 = transpose[dims={0, 3, 1, 2}](@1279) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1281 = identity(@1280) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1282 = transpose[dims={0, 2, 3, 1}](@1281) -> float_type, {3, 256, 256, 3}, {196608, 256, 1, 65536}\n", "@1283 = transpose[dims={0, 3, 1, 2}](@1278) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1284 = transpose[dims={0, 3, 1, 2}](@1282) -> float_type, {3, 3, 256, 256}, {196608, 65536, 256, 1}\n", "@1285 = transpose[dims={3, 2, 0, 1}](@1284) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1286 = transpose[dims={3, 2, 0, 1}](@1284) -> float_type, {256, 256, 3, 3}, {1, 256, 196608, 65536}\n", "@1287 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@1283,@1286) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1288 = transpose[dims={0, 2, 3, 1}](@1287) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1289 = identity(@73) -> float_type, {256}, {1}\n", "@1290 = transpose[dims={0, 3, 1, 2}](@1288) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1291 = broadcast[axis=1,dims={1, 256, 14, 14}](@1289) -> float_type, {1, 256, 14, 14}, {0, 1, 0, 0}\n", "@1292 = add(@1290,@1291) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1293 = transpose[dims={0, 2, 3, 1}](@1292) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1294 = identity(@72) -> float_type, {256}, {1}\n", "@1295 = identity(@71) -> float_type, {256}, {1}\n", "@1296 = identity(@70) -> float_type, {256}, {1}\n", "@1297 = identity(@69) -> float_type, {256}, {1}\n", "@1298 = unknown:FusedBatchNormV3(@1293,@1294,@1295,@1296,@1297) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1299 = transpose[dims={0, 3, 1, 2}](@1298) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1300 = relu(@1299) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1301 = transpose[dims={0, 2, 3, 1}](@1300) -> float_type, {1, 14, 14, 256}, {50176, 14, 1, 196}\n", "@1302 = transpose[dims={0, 2, 3, 1}](@68) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1303 = transpose[dims={0, 3, 1, 2}](@1302) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1304 = identity(@1303) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1305 = transpose[dims={0, 2, 3, 1}](@1304) -> float_type, {1, 256, 1024, 1}, {262144, 1024, 1, 262144}\n", "@1306 = transpose[dims={0, 3, 1, 2}](@1301) -> float_type, {1, 256, 14, 14}, {50176, 196, 14, 1}\n", "@1307 = transpose[dims={0, 3, 1, 2}](@1305) -> float_type, {1, 1, 256, 1024}, {262144, 262144, 1024, 1}\n", "@1308 = transpose[dims={3, 2, 0, 1}](@1307) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1309 = transpose[dims={3, 2, 0, 1}](@1307) -> float_type, {1024, 256, 1, 1}, {1, 1024, 262144, 262144}\n", "@1310 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1306,@1309) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1311 = transpose[dims={0, 2, 3, 1}](@1310) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1312 = identity(@67) -> float_type, {1024}, {1}\n", "@1313 = transpose[dims={0, 3, 1, 2}](@1311) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1314 = broadcast[axis=1,dims={1, 1024, 14, 14}](@1312) -> float_type, {1, 1024, 14, 14}, {0, 1, 0, 0}\n", "@1315 = add(@1313,@1314) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1316 = transpose[dims={0, 2, 3, 1}](@1315) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1317 = identity(@66) -> float_type, {1024}, {1}\n", "@1318 = identity(@65) -> float_type, {1024}, {1}\n", "@1319 = identity(@64) -> float_type, {1024}, {1}\n", "@1320 = identity(@63) -> float_type, {1024}, {1}\n", "@1321 = unknown:FusedBatchNormV3(@1316,@1317,@1318,@1319,@1320) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1322 = unknown:AddV2(@1255,@1321) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1323 = transpose[dims={0, 3, 1, 2}](@1322) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1324 = relu(@1323) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1325 = transpose[dims={0, 2, 3, 1}](@1324) -> float_type, {1, 14, 14, 1024}, {200704, 14, 1, 196}\n", "@1326 = transpose[dims={0, 2, 3, 1}](@62) -> float_type, {1, 1024, 2048, 1}, {2097152, 2048, 1, 2097152}\n", "@1327 = transpose[dims={0, 3, 1, 2}](@1326) -> float_type, {1, 1, 1024, 2048}, {2097152, 2097152, 2048, 1}\n", "@1328 = identity(@1327) -> float_type, {1, 1, 1024, 2048}, {2097152, 2097152, 2048, 1}\n", "@1329 = transpose[dims={0, 2, 3, 1}](@1328) -> float_type, {1, 1024, 2048, 1}, {2097152, 2048, 1, 2097152}\n", "@1330 = transpose[dims={0, 3, 1, 2}](@1325) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1331 = transpose[dims={0, 3, 1, 2}](@1329) -> float_type, {1, 1, 1024, 2048}, {2097152, 2097152, 2048, 1}\n", "@1332 = transpose[dims={3, 2, 0, 1}](@1331) -> float_type, {2048, 1024, 1, 1}, {1, 2048, 2097152, 2097152}\n", "@1333 = transpose[dims={3, 2, 0, 1}](@1331) -> float_type, {2048, 1024, 1, 1}, {1, 2048, 2097152, 2097152}\n", "@1334 = convolution[padding={0, 0},stride={2, 2},dilation={1, 1},group=1,padding_mode=2](@1330,@1333) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1335 = transpose[dims={0, 2, 3, 1}](@1334) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1336 = identity(@61) -> float_type, {2048}, {1}\n", "@1337 = transpose[dims={0, 3, 1, 2}](@1335) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1338 = broadcast[axis=1,dims={1, 2048, 7, 7}](@1336) -> float_type, {1, 2048, 7, 7}, {0, 1, 0, 0}\n", "@1339 = add(@1337,@1338) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1340 = transpose[dims={0, 2, 3, 1}](@1339) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1341 = identity(@60) -> float_type, {2048}, {1}\n", "@1342 = identity(@59) -> float_type, {2048}, {1}\n", "@1343 = identity(@58) -> float_type, {2048}, {1}\n", "@1344 = identity(@57) -> float_type, {2048}, {1}\n", "@1345 = unknown:FusedBatchNormV3(@1340,@1341,@1342,@1343,@1344) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1346 = transpose[dims={0, 2, 3, 1}](@56) -> float_type, {1, 1024, 512, 1}, {524288, 512, 1, 524288}\n", "@1347 = transpose[dims={0, 3, 1, 2}](@1346) -> float_type, {1, 1, 1024, 512}, {524288, 524288, 512, 1}\n", "@1348 = identity(@1347) -> float_type, {1, 1, 1024, 512}, {524288, 524288, 512, 1}\n", "@1349 = transpose[dims={0, 2, 3, 1}](@1348) -> float_type, {1, 1024, 512, 1}, {524288, 512, 1, 524288}\n", "@1350 = transpose[dims={0, 3, 1, 2}](@1325) -> float_type, {1, 1024, 14, 14}, {200704, 196, 14, 1}\n", "@1351 = transpose[dims={0, 3, 1, 2}](@1349) -> float_type, {1, 1, 1024, 512}, {524288, 524288, 512, 1}\n", "@1352 = transpose[dims={3, 2, 0, 1}](@1351) -> float_type, {512, 1024, 1, 1}, {1, 512, 524288, 524288}\n", "@1353 = transpose[dims={3, 2, 0, 1}](@1351) -> float_type, {512, 1024, 1, 1}, {1, 512, 524288, 524288}\n", "@1354 = convolution[padding={0, 0},stride={2, 2},dilation={1, 1},group=1,padding_mode=2](@1350,@1353) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1355 = transpose[dims={0, 2, 3, 1}](@1354) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1356 = identity(@55) -> float_type, {512}, {1}\n", "@1357 = transpose[dims={0, 3, 1, 2}](@1355) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1358 = broadcast[axis=1,dims={1, 512, 7, 7}](@1356) -> float_type, {1, 512, 7, 7}, {0, 1, 0, 0}\n", "@1359 = add(@1357,@1358) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1360 = transpose[dims={0, 2, 3, 1}](@1359) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1361 = identity(@54) -> float_type, {512}, {1}\n", "@1362 = identity(@53) -> float_type, {512}, {1}\n", "@1363 = identity(@52) -> float_type, {512}, {1}\n", "@1364 = identity(@51) -> float_type, {512}, {1}\n", "@1365 = unknown:FusedBatchNormV3(@1360,@1361,@1362,@1363,@1364) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1366 = transpose[dims={0, 3, 1, 2}](@1365) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1367 = relu(@1366) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1368 = transpose[dims={0, 2, 3, 1}](@1367) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1369 = transpose[dims={0, 2, 3, 1}](@50) -> float_type, {3, 512, 512, 3}, {786432, 512, 1, 262144}\n", "@1370 = transpose[dims={0, 3, 1, 2}](@1369) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1371 = identity(@1370) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1372 = transpose[dims={0, 2, 3, 1}](@1371) -> float_type, {3, 512, 512, 3}, {786432, 512, 1, 262144}\n", "@1373 = transpose[dims={0, 3, 1, 2}](@1368) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1374 = transpose[dims={0, 3, 1, 2}](@1372) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1375 = transpose[dims={3, 2, 0, 1}](@1374) -> float_type, {512, 512, 3, 3}, {1, 512, 786432, 262144}\n", "@1376 = transpose[dims={3, 2, 0, 1}](@1374) -> float_type, {512, 512, 3, 3}, {1, 512, 786432, 262144}\n", "@1377 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@1373,@1376) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1378 = transpose[dims={0, 2, 3, 1}](@1377) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1379 = identity(@49) -> float_type, {512}, {1}\n", "@1380 = transpose[dims={0, 3, 1, 2}](@1378) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1381 = broadcast[axis=1,dims={1, 512, 7, 7}](@1379) -> float_type, {1, 512, 7, 7}, {0, 1, 0, 0}\n", "@1382 = add(@1380,@1381) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1383 = transpose[dims={0, 2, 3, 1}](@1382) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1384 = identity(@48) -> float_type, {512}, {1}\n", "@1385 = identity(@47) -> float_type, {512}, {1}\n", "@1386 = identity(@46) -> float_type, {512}, {1}\n", "@1387 = identity(@45) -> float_type, {512}, {1}\n", "@1388 = unknown:FusedBatchNormV3(@1383,@1384,@1385,@1386,@1387) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1389 = transpose[dims={0, 3, 1, 2}](@1388) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1390 = relu(@1389) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1391 = transpose[dims={0, 2, 3, 1}](@1390) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1392 = transpose[dims={0, 2, 3, 1}](@44) -> float_type, {1, 512, 2048, 1}, {1048576, 2048, 1, 1048576}\n", "@1393 = transpose[dims={0, 3, 1, 2}](@1392) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1394 = identity(@1393) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1395 = transpose[dims={0, 2, 3, 1}](@1394) -> float_type, {1, 512, 2048, 1}, {1048576, 2048, 1, 1048576}\n", "@1396 = transpose[dims={0, 3, 1, 2}](@1391) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1397 = transpose[dims={0, 3, 1, 2}](@1395) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1398 = transpose[dims={3, 2, 0, 1}](@1397) -> float_type, {2048, 512, 1, 1}, {1, 2048, 1048576, 1048576}\n", "@1399 = transpose[dims={3, 2, 0, 1}](@1397) -> float_type, {2048, 512, 1, 1}, {1, 2048, 1048576, 1048576}\n", "@1400 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1396,@1399) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1401 = transpose[dims={0, 2, 3, 1}](@1400) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1402 = identity(@43) -> float_type, {2048}, {1}\n", "@1403 = transpose[dims={0, 3, 1, 2}](@1401) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1404 = broadcast[axis=1,dims={1, 2048, 7, 7}](@1402) -> float_type, {1, 2048, 7, 7}, {0, 1, 0, 0}\n", "@1405 = add(@1403,@1404) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1406 = transpose[dims={0, 2, 3, 1}](@1405) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1407 = identity(@42) -> float_type, {2048}, {1}\n", "@1408 = identity(@41) -> float_type, {2048}, {1}\n", "@1409 = identity(@40) -> float_type, {2048}, {1}\n", "@1410 = identity(@39) -> float_type, {2048}, {1}\n", "@1411 = unknown:FusedBatchNormV3(@1406,@1407,@1408,@1409,@1410) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1412 = unknown:AddV2(@1345,@1411) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1413 = transpose[dims={0, 3, 1, 2}](@1412) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1414 = relu(@1413) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1415 = transpose[dims={0, 2, 3, 1}](@1414) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1416 = transpose[dims={0, 2, 3, 1}](@38) -> float_type, {1, 2048, 512, 1}, {1048576, 512, 1, 1048576}\n", "@1417 = transpose[dims={0, 3, 1, 2}](@1416) -> float_type, {1, 1, 2048, 512}, {1048576, 1048576, 512, 1}\n", "@1418 = identity(@1417) -> float_type, {1, 1, 2048, 512}, {1048576, 1048576, 512, 1}\n", "@1419 = transpose[dims={0, 2, 3, 1}](@1418) -> float_type, {1, 2048, 512, 1}, {1048576, 512, 1, 1048576}\n", "@1420 = transpose[dims={0, 3, 1, 2}](@1415) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1421 = transpose[dims={0, 3, 1, 2}](@1419) -> float_type, {1, 1, 2048, 512}, {1048576, 1048576, 512, 1}\n", "@1422 = transpose[dims={3, 2, 0, 1}](@1421) -> float_type, {512, 2048, 1, 1}, {1, 512, 1048576, 1048576}\n", "@1423 = transpose[dims={3, 2, 0, 1}](@1421) -> float_type, {512, 2048, 1, 1}, {1, 512, 1048576, 1048576}\n", "@1424 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1420,@1423) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1425 = transpose[dims={0, 2, 3, 1}](@1424) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1426 = identity(@37) -> float_type, {512}, {1}\n", "@1427 = transpose[dims={0, 3, 1, 2}](@1425) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1428 = broadcast[axis=1,dims={1, 512, 7, 7}](@1426) -> float_type, {1, 512, 7, 7}, {0, 1, 0, 0}\n", "@1429 = add(@1427,@1428) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1430 = transpose[dims={0, 2, 3, 1}](@1429) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1431 = identity(@36) -> float_type, {512}, {1}\n", "@1432 = identity(@35) -> float_type, {512}, {1}\n", "@1433 = identity(@34) -> float_type, {512}, {1}\n", "@1434 = identity(@33) -> float_type, {512}, {1}\n", "@1435 = unknown:FusedBatchNormV3(@1430,@1431,@1432,@1433,@1434) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1436 = transpose[dims={0, 3, 1, 2}](@1435) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1437 = relu(@1436) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1438 = transpose[dims={0, 2, 3, 1}](@1437) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1439 = transpose[dims={0, 2, 3, 1}](@32) -> float_type, {3, 512, 512, 3}, {786432, 512, 1, 262144}\n", "@1440 = transpose[dims={0, 3, 1, 2}](@1439) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1441 = identity(@1440) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1442 = transpose[dims={0, 2, 3, 1}](@1441) -> float_type, {3, 512, 512, 3}, {786432, 512, 1, 262144}\n", "@1443 = transpose[dims={0, 3, 1, 2}](@1438) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1444 = transpose[dims={0, 3, 1, 2}](@1442) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1445 = transpose[dims={3, 2, 0, 1}](@1444) -> float_type, {512, 512, 3, 3}, {1, 512, 786432, 262144}\n", "@1446 = transpose[dims={3, 2, 0, 1}](@1444) -> float_type, {512, 512, 3, 3}, {1, 512, 786432, 262144}\n", "@1447 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@1443,@1446) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1448 = transpose[dims={0, 2, 3, 1}](@1447) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1449 = identity(@31) -> float_type, {512}, {1}\n", "@1450 = transpose[dims={0, 3, 1, 2}](@1448) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1451 = broadcast[axis=1,dims={1, 512, 7, 7}](@1449) -> float_type, {1, 512, 7, 7}, {0, 1, 0, 0}\n", "@1452 = add(@1450,@1451) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1453 = transpose[dims={0, 2, 3, 1}](@1452) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1454 = identity(@30) -> float_type, {512}, {1}\n", "@1455 = identity(@29) -> float_type, {512}, {1}\n", "@1456 = identity(@28) -> float_type, {512}, {1}\n", "@1457 = identity(@27) -> float_type, {512}, {1}\n", "@1458 = unknown:FusedBatchNormV3(@1453,@1454,@1455,@1456,@1457) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1459 = transpose[dims={0, 3, 1, 2}](@1458) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1460 = relu(@1459) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1461 = transpose[dims={0, 2, 3, 1}](@1460) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1462 = transpose[dims={0, 2, 3, 1}](@26) -> float_type, {1, 512, 2048, 1}, {1048576, 2048, 1, 1048576}\n", "@1463 = transpose[dims={0, 3, 1, 2}](@1462) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1464 = identity(@1463) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1465 = transpose[dims={0, 2, 3, 1}](@1464) -> float_type, {1, 512, 2048, 1}, {1048576, 2048, 1, 1048576}\n", "@1466 = transpose[dims={0, 3, 1, 2}](@1461) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1467 = transpose[dims={0, 3, 1, 2}](@1465) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1468 = transpose[dims={3, 2, 0, 1}](@1467) -> float_type, {2048, 512, 1, 1}, {1, 2048, 1048576, 1048576}\n", "@1469 = transpose[dims={3, 2, 0, 1}](@1467) -> float_type, {2048, 512, 1, 1}, {1, 2048, 1048576, 1048576}\n", "@1470 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1466,@1469) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1471 = transpose[dims={0, 2, 3, 1}](@1470) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1472 = identity(@25) -> float_type, {2048}, {1}\n", "@1473 = transpose[dims={0, 3, 1, 2}](@1471) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1474 = broadcast[axis=1,dims={1, 2048, 7, 7}](@1472) -> float_type, {1, 2048, 7, 7}, {0, 1, 0, 0}\n", "@1475 = add(@1473,@1474) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1476 = transpose[dims={0, 2, 3, 1}](@1475) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1477 = identity(@24) -> float_type, {2048}, {1}\n", "@1478 = identity(@23) -> float_type, {2048}, {1}\n", "@1479 = identity(@22) -> float_type, {2048}, {1}\n", "@1480 = identity(@21) -> float_type, {2048}, {1}\n", "@1481 = unknown:FusedBatchNormV3(@1476,@1477,@1478,@1479,@1480) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1482 = unknown:AddV2(@1415,@1481) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1483 = transpose[dims={0, 3, 1, 2}](@1482) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1484 = relu(@1483) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1485 = transpose[dims={0, 2, 3, 1}](@1484) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1486 = transpose[dims={0, 2, 3, 1}](@20) -> float_type, {1, 2048, 512, 1}, {1048576, 512, 1, 1048576}\n", "@1487 = transpose[dims={0, 3, 1, 2}](@1486) -> float_type, {1, 1, 2048, 512}, {1048576, 1048576, 512, 1}\n", "@1488 = identity(@1487) -> float_type, {1, 1, 2048, 512}, {1048576, 1048576, 512, 1}\n", "@1489 = transpose[dims={0, 2, 3, 1}](@1488) -> float_type, {1, 2048, 512, 1}, {1048576, 512, 1, 1048576}\n", "@1490 = transpose[dims={0, 3, 1, 2}](@1485) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1491 = transpose[dims={0, 3, 1, 2}](@1489) -> float_type, {1, 1, 2048, 512}, {1048576, 1048576, 512, 1}\n", "@1492 = transpose[dims={3, 2, 0, 1}](@1491) -> float_type, {512, 2048, 1, 1}, {1, 512, 1048576, 1048576}\n", "@1493 = transpose[dims={3, 2, 0, 1}](@1491) -> float_type, {512, 2048, 1, 1}, {1, 512, 1048576, 1048576}\n", "@1494 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1490,@1493) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1495 = transpose[dims={0, 2, 3, 1}](@1494) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1496 = identity(@19) -> float_type, {512}, {1}\n", "@1497 = transpose[dims={0, 3, 1, 2}](@1495) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1498 = broadcast[axis=1,dims={1, 512, 7, 7}](@1496) -> float_type, {1, 512, 7, 7}, {0, 1, 0, 0}\n", "@1499 = add(@1497,@1498) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1500 = transpose[dims={0, 2, 3, 1}](@1499) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1501 = identity(@18) -> float_type, {512}, {1}\n", "@1502 = identity(@17) -> float_type, {512}, {1}\n", "@1503 = identity(@16) -> float_type, {512}, {1}\n", "@1504 = identity(@15) -> float_type, {512}, {1}\n", "@1505 = unknown:FusedBatchNormV3(@1500,@1501,@1502,@1503,@1504) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1506 = transpose[dims={0, 3, 1, 2}](@1505) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1507 = relu(@1506) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1508 = transpose[dims={0, 2, 3, 1}](@1507) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1509 = transpose[dims={0, 2, 3, 1}](@14) -> float_type, {3, 512, 512, 3}, {786432, 512, 1, 262144}\n", "@1510 = transpose[dims={0, 3, 1, 2}](@1509) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1511 = identity(@1510) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1512 = transpose[dims={0, 2, 3, 1}](@1511) -> float_type, {3, 512, 512, 3}, {786432, 512, 1, 262144}\n", "@1513 = transpose[dims={0, 3, 1, 2}](@1508) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1514 = transpose[dims={0, 3, 1, 2}](@1512) -> float_type, {3, 3, 512, 512}, {786432, 262144, 512, 1}\n", "@1515 = transpose[dims={3, 2, 0, 1}](@1514) -> float_type, {512, 512, 3, 3}, {1, 512, 786432, 262144}\n", "@1516 = transpose[dims={3, 2, 0, 1}](@1514) -> float_type, {512, 512, 3, 3}, {1, 512, 786432, 262144}\n", "@1517 = convolution[padding={1, 1},stride={1, 1},dilation={1, 1},group=1,padding_mode=1](@1513,@1516) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1518 = transpose[dims={0, 2, 3, 1}](@1517) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1519 = identity(@13) -> float_type, {512}, {1}\n", "@1520 = transpose[dims={0, 3, 1, 2}](@1518) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1521 = broadcast[axis=1,dims={1, 512, 7, 7}](@1519) -> float_type, {1, 512, 7, 7}, {0, 1, 0, 0}\n", "@1522 = add(@1520,@1521) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1523 = transpose[dims={0, 2, 3, 1}](@1522) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1524 = identity(@12) -> float_type, {512}, {1}\n", "@1525 = identity(@11) -> float_type, {512}, {1}\n", "@1526 = identity(@10) -> float_type, {512}, {1}\n", "@1527 = identity(@9) -> float_type, {512}, {1}\n", "@1528 = unknown:FusedBatchNormV3(@1523,@1524,@1525,@1526,@1527) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1529 = transpose[dims={0, 3, 1, 2}](@1528) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1530 = relu(@1529) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1531 = transpose[dims={0, 2, 3, 1}](@1530) -> float_type, {1, 7, 7, 512}, {25088, 7, 1, 49}\n", "@1532 = transpose[dims={0, 2, 3, 1}](@8) -> float_type, {1, 512, 2048, 1}, {1048576, 2048, 1, 1048576}\n", "@1533 = transpose[dims={0, 3, 1, 2}](@1532) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1534 = identity(@1533) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1535 = transpose[dims={0, 2, 3, 1}](@1534) -> float_type, {1, 512, 2048, 1}, {1048576, 2048, 1, 1048576}\n", "@1536 = transpose[dims={0, 3, 1, 2}](@1531) -> float_type, {1, 512, 7, 7}, {25088, 49, 7, 1}\n", "@1537 = transpose[dims={0, 3, 1, 2}](@1535) -> float_type, {1, 1, 512, 2048}, {1048576, 1048576, 2048, 1}\n", "@1538 = transpose[dims={3, 2, 0, 1}](@1537) -> float_type, {2048, 512, 1, 1}, {1, 2048, 1048576, 1048576}\n", "@1539 = transpose[dims={3, 2, 0, 1}](@1537) -> float_type, {2048, 512, 1, 1}, {1, 2048, 1048576, 1048576}\n", "@1540 = convolution[padding={0, 0},stride={1, 1},dilation={1, 1},group=1,padding_mode=2](@1536,@1539) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1541 = transpose[dims={0, 2, 3, 1}](@1540) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1542 = identity(@7) -> float_type, {2048}, {1}\n", "@1543 = transpose[dims={0, 3, 1, 2}](@1541) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1544 = broadcast[axis=1,dims={1, 2048, 7, 7}](@1542) -> float_type, {1, 2048, 7, 7}, {0, 1, 0, 0}\n", "@1545 = add(@1543,@1544) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1546 = transpose[dims={0, 2, 3, 1}](@1545) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1547 = identity(@6) -> float_type, {2048}, {1}\n", "@1548 = identity(@5) -> float_type, {2048}, {1}\n", "@1549 = identity(@4) -> float_type, {2048}, {1}\n", "@1550 = identity(@3) -> float_type, {2048}, {1}\n", "@1551 = unknown:FusedBatchNormV3(@1546,@1547,@1548,@1549,@1550) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1552 = unknown:AddV2(@1485,@1551) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1553 = transpose[dims={0, 3, 1, 2}](@1552) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1554 = relu(@1553) -> float_type, {1, 2048, 7, 7}, {100352, 49, 7, 1}\n", "@1555 = transpose[dims={0, 2, 3, 1}](@1554) -> float_type, {1, 7, 7, 2048}, {100352, 7, 1, 49}\n", "@1556 = reduce_mean[axes={1, 2}](@1555) -> float_type, {1, 1, 1, 2048}, {2048, 2048, 2048, 1}\n", "@1557 = squeeze[axes={1, 2}](@1556) -> float_type, {1, 2048}, {2048, 1}\n", "@1558 = identity(@1) -> float_type, {2048, 1000}, {1000, 1}\n", "@1559 = dot[alpha=1,beta=1](@1557,@1558) -> float_type, {1, 1000}, {1000, 1}\n", "@1560 = identity(@0) -> float_type, {1000}, {1}\n", "@1561 = broadcast[axis=1,dims={1, 1000}](@1560) -> float_type, {1, 1000}, {0, 1}\n", "@1562 = add(@1559,@1561) -> float_type, {1, 1000}, {1000, 1}\n", "@1563 = softmax[axis=1](@1562) -> float_type, {1, 1000}, {1000, 1}\n", "@1564 = identity(@1563) -> float_type, {1, 1000}, {1000, 1}\n", "\n", "\n" ] } ], "source": [ "import subprocess\n", "driver = \"/opt/rocm/bin/migraphx-driver\"\n", "command = \"read\"\n", "model_path = \"./frozen_models/{}_frozen_graph.pb\".format(MODEL_NAME)\n", "process = subprocess.run([driver, command, model_path], \n", " stdout=subprocess.PIPE, \n", " universal_newlines=True)\n", "\n", "print(process.stdout)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "tensorflow", "language": "python", "name": "tensorflow" }, "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.9" } }, "nbformat": 4, "nbformat_minor": 4 } ROCm-AMDMIGraphX-46524e8/examples/migraphx/export_frozen_graph_tf2/README.md000066400000000000000000000020011510465702400263050ustar00rootroot00000000000000# Exporting Frozen Graphs in Tensorflow 2 ## Description This example demonstrates how to export a frozen graph protobuf in Tensorflow 2.X that can be used as input for MIGraphX. The method for accomplishing this has changed from Tensorflow 1.X. Please refer to [export_frozen_graphs_tf1]() if you are not yet using Tensorflow 2. ## How to Use this Example If you do not already have Jupyter Notebooks installed, please refer to this [page](https://jupyter.org/install) for instructions. Once Jupyter Notebooks is installed, you can navigate to this directory and issue the command: ``` $ jupyter notebook ``` From the browser window that is launched, click on `example.ipynb` You should now be able to run the notebook from your browser. To use this on your own models you wish to save, simply edit the first cell to include any additional libraries and modify `MODEL_NAME` and `model` to the model of your choosing. Additionally, training and fine-tuning can be performed before moving on to cells 2 and beyond. ROCm-AMDMIGraphX-46524e8/examples/migraphx/export_frozen_graph_tf2/example.ipynb000066400000000000000000000151661510465702400275440ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exporting Frozen Graphs in Tensorflow 2 \n", "In order to use a trained model as input to MIGraphX, the model must be first be saved in a frozen graph format. This was accomplished in Tensorflow 1 by launching a graph in a tf.Session and then saving the session. However, Tensorflow has decided to deprecate Sessions in favor of functions and SavedModel format. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After importing the necessary libraries, the next step is to instantiate a model. For simplicity, in this example we will use a resnet50 architecture with pre-trained imagenet weights. These weights may also be trained or fine-tuned before freezing. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import tensorflow as tf\n", "tf.enable_eager_execution() #May not be required depending on tensorflow version\n", "from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2\n", "from tensorflow import keras\n", "from tensorflow.keras import layers\n", "\n", "MODEL_NAME = \"resnet50\"\n", "model = tf.keras.applications.ResNet50(weights=\"imagenet\")\n", "model.summary()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SavedModel format\n", "The simplest way to save a model is through saved\\_model.save()\n", "\n", "This will create an equivalent tensorflow program which can later be loaded for fine-tuning or inference, although it is not directly compatible with MIGraphX." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tf.saved_model.save(model, \"./Saved_Models/{}\".format(MODEL_NAME))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Convert to ConcreteFunction\n", "To begin, we need to get the function equivalent of the model and then concretize the function to avoid retracing." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "full_model = tf.function(lambda x: model(x))\n", "full_model = full_model.get_concrete_function(\n", " x=tf.TensorSpec(model.inputs[0].shape, model.inputs[0].dtype))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Freeze ConcreteFunction and Serialize\n", "Since we are saving the graph for the purpose of inference, all variables can be made constant (i.e. \"frozen\").\n", "\n", "Next, we need to obtain a serialized GraphDef representation of the graph. \n", "\n", "\n", "Optionally, the operators can be printed out layer by layer followed by the inputs and outputs." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "frozen_func = convert_variables_to_constants_v2(full_model)\n", "frozen_func.graph.as_graph_def()\n", "\n", "layers = [op.name for op in frozen_func.graph.get_operations()]\n", "print(\"-\" * 50)\n", "print(\"Frozen model layers: \")\n", "for layer in layers:\n", " print(layer)\n", "\n", "print(\"-\" * 50)\n", "print(\"Frozen model inputs: \")\n", "print(frozen_func.inputs)\n", "print(\"Frozen model outputs: \")\n", "print(frozen_func.outputs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Save Frozen Graph as Protobuf\n", "Finally, we can save to hard drive, and now the frozen graph will be stored as `./frozen_models/_frozen_graph.pb`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tf.io.write_graph(graph_or_graph_def=frozen_func.graph,\n", " logdir=\"./frozen_models\",\n", " name=\"{}_frozen_graph.pb\".format(MODEL_NAME),\n", " as_text=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Assuming MIGraphX has already been built and installed on your system, the driver can be used to verify that the frozen graph has been correctly exported. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import subprocess\n", "driver = \"/opt/rocm/bin/migraphx-driver\"\n", "command = \"read\"\n", "model_path = \"./frozen_models/{}_frozen_graph.pb\".format(MODEL_NAME)\n", "process = subprocess.run([driver, command, model_path], \n", " stdout=subprocess.PIPE, \n", " universal_newlines=True)\n", "\n", "print(process.stdout)" ] } ], "metadata": { "kernelspec": { "display_name": "tensorflow", "language": "python", "name": "tensorflow" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 } ROCm-AMDMIGraphX-46524e8/examples/migraphx/migraphx_docker/000077500000000000000000000000001510465702400233435ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/migraphx_docker/README.md000066400000000000000000000003541510465702400246240ustar00rootroot00000000000000# MIGraphX Dockerfile Instructions for building and running the MIGraphX docker container can be found [here](https://github.com/ROCmSoftwarePlatform/AMDMIGraphX/blob/develop/README.md#using-docker) in this project's top level README. ROCm-AMDMIGraphX-46524e8/examples/migraphx/migraphx_driver/000077500000000000000000000000001510465702400233675ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/migraphx/migraphx_driver/README.md000066400000000000000000000571561510465702400246640ustar00rootroot00000000000000# MIGraphX Driver ## Description The MIGraphX driver is a tool that allows you to utilize many of the core functions of MIGraphX without having to write your own program. ## How to Use this Example The MIGraphX driver is installed with MIGraphX and can be found in `/opt/rocm/bin/migraphx-driver`, or in `AMDMIGraphX/build/bin/migraphx-driver` after building the source code. See below for a comprehensive list of commands and option arguments, as well as some usage examples. ### Commands | Command | Description | | ------- | -------------------------------------------------------------------------- | | op | When followed by the option --list or -l, prints all operators of MIGraphX | | params | Prints the input and output parameter shapes | | run | Compiles, allocates parameters, evaluates, and prints input graph | | read | Loads and prints input graph | | compile | Compiles and prints input graph | | verify | Runs reference and GPU implementations and checks outputs for consistency | | perf | Compiles and runs input graph then prints performance report | ### Options | Option | Description | | ---------------------------------------- | --------------------------------------------------------- | | --help \| -h | Show help | | --test | Test MIGraphX with Single Layer GEMM Model | | --onnx | Load file as onnx graph | | --tf | Load file as a tensorflow graph | | --migraphx | Load file as a migraphx graph | | --migraphx-json | Load file as a migraphx JSON graph | | --batch | For a static model, set batch size. For a dynamic batch model, sets the batch size at runtime.| | --nhwc | Treat tensorflow format as nhwc | | --nchw | Treat tensorflow format as nchw | | --skip-unknown-operators | Skip unknown operators when parsing and continue to parse | | --trim \| -t | Trim instructions from the end | | --optimize \| -O | Optimize when reading | | --graphviz \| -g | Print out a graphviz representation | | --brief | Make the output brief | | --cpp | Print out the program as cpp program | | --json | Print out program as json | | --text | Print out program in text format | | --binary | Print out program in binary format | | --output \| -o | Output to file | | --fill0 | Fill parameter with 0s | | --fill1 | Fill parameter with 1s | | --input-dim | Set static dimensions of a parameter | | --dyn-input-dim | Set dynamic dimensions of a parameter | | --default-dyn-dim | Set default dynamic dimension | | --gpu | Compile on the gpu | | --cpu | Compile on the cpu | | --ref | Compile on the reference implementation | | --enable-offload-copy | Enable implicit offload copying | | --disable-fast-math | Disable fast math optimization | | --exhaustive-tune | Enable exhaustive search to find fastest kernel | | --fp16 | Quantize for fp16 | | --int8 | Quantize for int8 | | --fp8 | Quantize for Float8E4M3FNUZ type | | --rms-tol | Tolerance for the RMS error (Default: 0.001) | | --atol | Tolerance for elementwise absolute difference (Default: 0.001) | | --rtol | Tolerance for elementwise relative difference (Default: 0.001) | | --per-instruction \| -i | Verify each instruction | | --reduce \| -r | Reduce program and verify | | --iterations \| -n | Number of iterations to run for perf report | | --list \| -l | List all the operators of MIGraphX | ## Usage Examples The examples below supply a simple MNIST ConvNet as the input graph. Models of higher complexity will have considerably larger outputs in most cases. ##### Example: op ``` $ /opt/rocm/bin/migraphx-driver op --list ```

View output ``` @literal @param @return abs acos acosh add argmax argmin as_shape asin asinh atan atanh batch_norm_inference broadcast capture ceil check_context::migraphx::gpu::context clip concat contiguous convert convolution cos cosh deconvolution div dot elu equal erf exp flatten floor gather gpu::abs gpu::acos gpu::acosh gpu::add gpu::add_clip gpu::add_gelu gpu::add_gelu_new gpu::add_relu gpu::add_tanh gpu::argmax gpu::argmin gpu::asin gpu::asinh gpu::atan gpu::atanh gpu::batch_norm_inference gpu::ceil gpu::clip gpu::concat gpu::contiguous gpu::conv_bias gpu::conv_bias_relu gpu::convert gpu::convolution gpu::cos gpu::cosh gpu::deconv gpu::div gpu::elu gpu::equal gpu::erf gpu::exp gpu::floor gpu::gather gpu::gelu gpu::gelu_new gpu::gemm gpu::greater gpu::layernorm gpu::leaky_relu gpu::less gpu::log gpu::logsoftmax gpu::lrn gpu::max gpu::min gpu::mul gpu::mul_add gpu::mul_add_relu gpu::pad gpu::pooling gpu::pow gpu::prelu gpu::quant_convolution gpu::quant_gemm gpu::recip gpu::record_event gpu::reduce_max gpu::reduce_mean gpu::reduce_min gpu::reduce_prod gpu::reduce_sum gpu::relu gpu::rnn_var_sl_last_output gpu::rnn_var_sl_shift_output gpu::rnn_var_sl_shift_sequence gpu::round gpu::rsqrt gpu::set_stream gpu::sigmoid gpu::sign gpu::sin gpu::sinh gpu::softmax gpu::sqdiff gpu::sqrt gpu::sub gpu::tan gpu::tanh gpu::triadd gpu::triadd_clip gpu::triadd_relu gpu::triadd_sigmoid gpu::triadd_tanh gpu::wait_event greater gru hip::allocate hip::copy hip::copy_from_gpu hip::copy_to_gpu hip::hip_allocate_memory hip::hip_copy_literal identity im2col leaky_relu less load log logsoftmax lrn lstm max min mul multibroadcast neg outline pad pooling pow prelu quant_convolution quant_dot recip reduce_max reduce_mean reduce_min reduce_prod reduce_sum ref::batch_norm_inference ref::convolution ref::deconvolution ref::dot ref::elu ref::im2col ref::leaky_relu ref::logsoftmax ref::lrn ref::op ref::pad ref::pooling_average ref::pooling_max ref::quant_convolution ref::rnn_var_sl_last_output ref::softmax relu reshape rnn rnn_last_cell_output rnn_last_hs_output rnn_var_sl_last_output rnn_var_sl_shift_output rnn_var_sl_shift_sequence round rsqrt scalar sigmoid sign sin sinh slice softmax sqdiff sqrt squeeze sub tan tanh transpose undefined unknown: unsqueeze ```


##### Example: params ``` $ /opt/rocm/bin/migraphx-driver params simple_graph.pb ```
View output ``` Reading: simple_graph.pb x: float_type, {1, 28, 28}, {784, 28, 1} ```


##### Example: run (onnx file input) ``` $ /opt/rocm/bin/migraphx-driver run --onnx simple_graph.onnx ```
View output ``` Compiling ... Reading: simple_graph.onnx @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1} @2 = hip::hip_copy_literal[id=@literal:1] -> float_type, {784, 128}, {128, 1} x:0 = @param:x:0 -> float_type, {1, 28, 28}, {784, 28, 1} @3 = reshape[dims={-1, 784}](x:0) -> float_type, {1, 784}, {784, 1} @4 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1} @5 = gpu::gemm[alpha=1,beta=0](@3,@2,@4) -> float_type, {1, 128}, {128, 1} @6 = hip::hip_copy_literal[id=@literal:0] -> float_type, {128}, {1} @7 = hip::hip_copy_literal[id=@literal:2] -> float_type, {10}, {1} @8 = hip::hip_copy_literal[id=@literal:3] -> float_type, {128, 10}, {10, 1} @9 = multibroadcast[output_lens={1, 128}](@6) -> float_type, {1, 128}, {0, 1} @10 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1} @11 = gpu::add_relu(@5,@9,@10) -> float_type, {1, 128}, {128, 1} @12 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1} @13 = gpu::gemm[alpha=1,beta=0](@11,@8,@12) -> float_type, {1, 10}, {10, 1} @14 = multibroadcast[output_lens={1, 10}](@7) -> float_type, {1, 10}, {0, 1} @15 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1} @16 = gpu::add(@13,@14,@15) -> float_type, {1, 10}, {10, 1} #output_0 = @param:#output_0 -> float_type, {1, 10}, {10, 1} @17 = gpu::softmax[axis=1](@16,#output_0) -> float_type, {1, 10}, {10, 1} @18 = @return(@17) Allocating params ... @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1} @2 = hip::hip_copy_literal[id=@literal:1] -> float_type, {784, 128}, {128, 1} x:0 = @param:x:0 -> float_type, {1, 28, 28}, {784, 28, 1} @3 = reshape[dims={-1, 784}](x:0) -> float_type, {1, 784}, {784, 1} @4 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1} @5 = gpu::gemm[alpha=1,beta=0](@3,@2,@4) -> float_type, {1, 128}, {128, 1} @6 = hip::hip_copy_literal[id=@literal:0] -> float_type, {128}, {1} @7 = hip::hip_copy_literal[id=@literal:2] -> float_type, {10}, {1} @8 = hip::hip_copy_literal[id=@literal:3] -> float_type, {128, 10}, {10, 1} @9 = multibroadcast[output_lens={1, 128}](@6) -> float_type, {1, 128}, {0, 1} @10 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1} @11 = gpu::add_relu(@5,@9,@10) -> float_type, {1, 128}, {128, 1} @12 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1} @13 = gpu::gemm[alpha=1,beta=0](@11,@8,@12) -> float_type, {1, 10}, {10, 1} @14 = multibroadcast[output_lens={1, 10}](@7) -> float_type, {1, 10}, {0, 1} @15 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1} @16 = gpu::add(@13,@14,@15) -> float_type, {1, 10}, {10, 1} #output_0 = @param:#output_0 -> float_type, {1, 10}, {10, 1} @17 = gpu::softmax[axis=1](@16,#output_0) -> float_type, {1, 10}, {10, 1} @18 = @return(@17) ```


##### Example: read ``` $ /opt/rocm/bin/migraphx-driver read simple_graph.pb ```
View output ``` Reading: simple_graph.pb @0 = @literal{0.0136018, -0.0839988, 0.0375392, 0.0613085, -0.125795, 0.176185, 0.0761055, 0.0093384, -0.110057, -0.170587} -> float_type, {10}, {1} @1 = @literal{ ... } -> float_type, {128, 10}, {10, 1} @2 = @literal{ ... } -> float_type, {128}, {1} @3 = @literal{ ... } -> float_type, {784, 128}, {128, 1} @4 = @literal{-1, 784} -> int32_type, {2}, {1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @5 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @6 = identity(@3) -> float_type, {784, 128}, {128, 1} @7 = dot[alpha=1,beta=1](@5,@6) -> float_type, {1, 128}, {128, 1} @8 = identity(@2) -> float_type, {128}, {1} @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1} @10 = add(@7,@9) -> float_type, {1, 128}, {128, 1} @11 = relu(@10) -> float_type, {1, 128}, {128, 1} @12 = identity(@1) -> float_type, {128, 10}, {10, 1} @13 = dot[alpha=1,beta=1](@11,@12) -> float_type, {1, 10}, {10, 1} @14 = identity(@0) -> float_type, {10}, {1} @15 = broadcast[axis=1,dims={1, 10}](@14) -> float_type, {1, 10}, {0, 1} @16 = add(@13,@15) -> float_type, {1, 10}, {10, 1} @17 = softmax[axis=1](@16) -> float_type, {1, 10}, {10, 1} @18 = identity(@17) -> float_type, {1, 10}, {10, 1} ```


##### Example: compile (on GPU, quantized for fp16) ``` $ /opt/rocm/bin/migraphx-driver compile --gpu --fp16 simple_graph.pb ```
View output ``` Compiling ... Reading: simple_graph.pb @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {456}, {1},id=scratch] -> float_type, {456}, {1} @2 = hip::hip_copy_literal[id=@literal:0] -> half_type, {784, 128}, {128, 1} @3 = load[offset=256,end=1824](@1) -> half_type, {1, 28, 28}, {784, 28, 1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @4 = gpu::convert[target_type=1](x,@3) -> half_type, {1, 28, 28}, {784, 28, 1} @5 = reshape[dims={-1, 784}](@4) -> half_type, {1, 784}, {784, 1} @6 = load[offset=0,end=256](@1) -> half_type, {1, 128}, {128, 1} @7 = gpu::gemm[alpha=1,beta=0](@5,@2,@6) -> half_type, {1, 128}, {128, 1} @8 = hip::hip_copy_literal[id=@literal:2] -> half_type, {128, 10}, {10, 1} @9 = hip::hip_copy_literal[id=@literal:1] -> half_type, {128}, {1} @10 = hip::hip_copy_literal[id=@literal:3] -> half_type, {10}, {1} @11 = load[offset=256,end=512](@1) -> half_type, {1, 128}, {128, 1} @12 = broadcast[axis=1,dims={1, 128}](@9) -> half_type, {1, 128}, {0, 1} @13 = gpu::add_relu(@7,@12,@11) -> half_type, {1, 128}, {128, 1} @14 = load[offset=0,end=20](@1) -> half_type, {1, 10}, {10, 1} @15 = gpu::gemm[alpha=1,beta=0](@13,@8,@14) -> half_type, {1, 10}, {10, 1} @16 = broadcast[axis=1,dims={1, 10}](@10) -> half_type, {1, 10}, {0, 1} @17 = load[offset=20,end=40](@1) -> half_type, {1, 10}, {10, 1} @18 = gpu::add(@15,@16,@17) -> half_type, {1, 10}, {10, 1} @19 = load[offset=0,end=20](@1) -> half_type, {1, 10}, {10, 1} @20 = gpu::softmax[axis=1](@18,@19) -> half_type, {1, 10}, {10, 1} output = @param:output -> float_type, {1, 10}, {10, 1} @21 = gpu::convert[target_type=2](@20,output) -> float_type, {1, 10}, {10, 1} ```


##### Example: verify ``` $ /opt/rocm/bin/migraphx-driver verify simple_graph.pb ```
View output ``` Reading: simple_graph.pb @0 = @literal{0.0136018, -0.0839988, 0.0375392, 0.0613085, -0.125795, 0.176185, 0.0761055, 0.0093384, -0.110057, -0.170587} -> float_type, {10}, {1} @1 = @literal{ ... } -> float_type, {128, 10}, {10, 1} @2 = @literal{ ... } -> float_type, {128}, {1} @3 = @literal{ ... } -> float_type, {784, 128}, {128, 1} @4 = @literal{-1, 784} -> int32_type, {2}, {1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @5 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @6 = identity(@3) -> float_type, {784, 128}, {128, 1} @7 = dot[alpha=1,beta=1](@5,@6) -> float_type, {1, 128}, {128, 1} @8 = identity(@2) -> float_type, {128}, {1} @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1} @10 = add(@7,@9) -> float_type, {1, 128}, {128, 1} @11 = relu(@10) -> float_type, {1, 128}, {128, 1} @12 = identity(@1) -> float_type, {128, 10}, {10, 1} @13 = dot[alpha=1,beta=1](@11,@12) -> float_type, {1, 10}, {10, 1} @14 = identity(@0) -> float_type, {10}, {1} @15 = broadcast[axis=1,dims={1, 10}](@14) -> float_type, {1, 10}, {0, 1} @16 = add(@13,@15) -> float_type, {1, 10}, {10, 1} @17 = softmax[axis=1](@16) -> float_type, {1, 10}, {10, 1} @18 = identity(@17) -> float_type, {1, 10}, {10, 1} @0 = @literal{0.0136018, -0.0839988, 0.0375392, 0.0613085, -0.125795, 0.176185, 0.0761055, 0.0093384, -0.110057, -0.170587} -> float_type, {10}, {1} @1 = @literal{ ... } -> float_type, {128, 10}, {10, 1} @2 = @literal{ ... } -> float_type, {128}, {1} @3 = @literal{ ... } -> float_type, {784, 128}, {128, 1} @4 = @literal{-1, 784} -> int32_type, {2}, {1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @5 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @6 = identity(@3) -> float_type, {784, 128}, {128, 1} @7 = dot[alpha=1,beta=1](@5,@6) -> float_type, {1, 128}, {128, 1} @8 = identity(@2) -> float_type, {128}, {1} @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1} @10 = add(@7,@9) -> float_type, {1, 128}, {128, 1} @11 = relu(@10) -> float_type, {1, 128}, {128, 1} @12 = identity(@1) -> float_type, {128, 10}, {10, 1} @13 = dot[alpha=1,beta=1](@11,@12) -> float_type, {1, 10}, {10, 1} @14 = identity(@0) -> float_type, {10}, {1} @15 = broadcast[axis=1,dims={1, 10}](@14) -> float_type, {1, 10}, {0, 1} @16 = add(@13,@15) -> float_type, {1, 10}, {10, 1} @17 = softmax[axis=1](@16) -> float_type, {1, 10}, {10, 1} @18 = identity(@17) -> float_type, {1, 10}, {10, 1} @0 = @literal{0.0136018, -0.0839988, 0.0375392, 0.0613085, -0.125795, 0.176185, 0.0761055, 0.0093384, -0.110057, -0.170587} -> float_type, {10}, {1} @1 = @literal{ ... } -> float_type, {128, 10}, {10, 1} @2 = @literal{ ... } -> float_type, {128}, {1} @3 = @literal{ ... } -> float_type, {784, 128}, {128, 1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @4 = ref::reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @5 = ref::identity(@3) -> float_type, {784, 128}, {128, 1} @6 = ref::dot[alpha=1,beta=1](@4,@5) -> float_type, {1, 128}, {128, 1} @7 = ref::identity(@2) -> float_type, {128}, {1} @8 = ref::broadcast[axis=1,dims={1, 128}](@7) -> float_type, {1, 128}, {0, 1} @9 = ref::contiguous(@8) -> float_type, {1, 128}, {128, 1} @10 = ref::add(@6,@9) -> float_type, {1, 128}, {128, 1} @11 = ref::relu(@10) -> float_type, {1, 128}, {128, 1} @12 = ref::identity(@1) -> float_type, {128, 10}, {10, 1} @13 = ref::dot[alpha=1,beta=1](@11,@12) -> float_type, {1, 10}, {10, 1} @14 = ref::identity(@0) -> float_type, {10}, {1} @15 = ref::broadcast[axis=1,dims={1, 10}](@14) -> float_type, {1, 10}, {0, 1} @16 = ref::contiguous(@15) -> float_type, {1, 10}, {10, 1} @17 = ref::add(@13,@16) -> float_type, {1, 10}, {10, 1} @18 = ref::softmax[axis=1](@17) -> float_type, {1, 10}, {10, 1} @19 = ref::identity(@18) -> float_type, {1, 10}, {10, 1} @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1} @2 = hip::hip_copy_literal[id=@literal:3] -> float_type, {784, 128}, {128, 1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @3 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1} @4 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @5 = gpu::gemm[alpha=1,beta=0](@4,@2,@3) -> float_type, {1, 128}, {128, 1} @6 = hip::hip_copy_literal[id=@literal:1] -> float_type, {128, 10}, {10, 1} @7 = hip::hip_copy_literal[id=@literal:2] -> float_type, {128}, {1} @8 = hip::hip_copy_literal[id=@literal:0] -> float_type, {10}, {1} @9 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1} @10 = broadcast[axis=1,dims={1, 128}](@7) -> float_type, {1, 128}, {0, 1} @11 = gpu::add_relu(@5,@10,@9) -> float_type, {1, 128}, {128, 1} @12 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1} @13 = gpu::gemm[alpha=1,beta=0](@11,@6,@12) -> float_type, {1, 10}, {10, 1} @14 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1} @15 = broadcast[axis=1,dims={1, 10}](@8) -> float_type, {1, 10}, {0, 1} @16 = gpu::add(@13,@15,@14) -> float_type, {1, 10}, {10, 1} output = @param:output -> float_type, {1, 10}, {10, 1} @17 = gpu::softmax[axis=1](@16,output) -> float_type, {1, 10}, {10, 1} ```


##### Example: perf ``` $ /opt/rocm/bin/migraphx-driver perf simple_graph.pb ```
View output ``` Compiling ... Reading: simple_graph.pb @0 = check_context::migraphx::gpu::context -> float_type, {}, {} @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1} @2 = hip::hip_copy_literal[id=@literal:3] -> float_type, {784, 128}, {128, 1} @3 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1} x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1} @4 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1} @5 = gpu::gemm[alpha=1,beta=0](@4,@2,@3) -> float_type, {1, 128}, {128, 1} @6 = hip::hip_copy_literal[id=@literal:1] -> float_type, {128, 10}, {10, 1} @7 = hip::hip_copy_literal[id=@literal:0] -> float_type, {10}, {1} @8 = hip::hip_copy_literal[id=@literal:2] -> float_type, {128}, {1} @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1} @10 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1} @11 = gpu::add_relu(@5,@9,@10) -> float_type, {1, 128}, {128, 1} @12 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1} @13 = gpu::gemm[alpha=1,beta=0](@11,@6,@12) -> float_type, {1, 10}, {10, 1} @14 = broadcast[axis=1,dims={1, 10}](@7) -> float_type, {1, 10}, {0, 1} @15 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1} @16 = gpu::add(@13,@14,@15) -> float_type, {1, 10}, {10, 1} output = @param:output -> float_type, {1, 10}, {10, 1} @17 = gpu::softmax[axis=1](@16,output) -> float_type, {1, 10}, {10, 1} Allocating params ... Running performance report ... @0 = check_context::migraphx::gpu::context -> float_type, {}, {}: 0.00057782ms, 1% @1 = hip::hip_allocate_memory[shape=float_type, {256}, {1},id=scratch] -> float_type, {256}, {1}: 0.000295ms, 1% @2 = hip::hip_copy_literal[id=@literal:3] -> float_type, {784, 128}, {128, 1}: 0.00027942ms, 1% @3 = load[offset=0,end=512](@1) -> float_type, {1, 128}, {128, 1}: 0.000232ms, 1% x = @param:x -> float_type, {1, 28, 28}, {784, 28, 1}: 0.0003206ms, 1% @4 = reshape[dims={-1, 784}](x) -> float_type, {1, 784}, {784, 1}: 0.00033842ms, 1% @5 = gpu::gemm[alpha=1,beta=0](@4,@2,@3) -> float_type, {1, 128}, {128, 1}: 0.212592ms, 52% @6 = hip::hip_copy_literal[id=@literal:1] -> float_type, {128, 10}, {10, 1}: 0.00085822ms, 1% @7 = hip::hip_copy_literal[id=@literal:0] -> float_type, {10}, {1}: 0.000382ms, 1% @8 = hip::hip_copy_literal[id=@literal:2] -> float_type, {128}, {1}: 0.0003486ms, 1% @9 = broadcast[axis=1,dims={1, 128}](@8) -> float_type, {1, 128}, {0, 1}: 0.000299ms, 1% @10 = load[offset=512,end=1024](@1) -> float_type, {1, 128}, {128, 1}: 0.000234ms, 1% @11 = gpu::add_relu(@5,@9,@10) -> float_type, {1, 128}, {128, 1}: 0.0416597ms, 11% @12 = load[offset=0,end=40](@1) -> float_type, {1, 10}, {10, 1}: 0.0007548ms, 1% @13 = gpu::gemm[alpha=1,beta=0](@11,@6,@12) -> float_type, {1, 10}, {10, 1}: 0.0733071ms, 18% @14 = broadcast[axis=1,dims={1, 10}](@7) -> float_type, {1, 10}, {0, 1}: 0.00088142ms, 1% @15 = load[offset=40,end=80](@1) -> float_type, {1, 10}, {10, 1}: 0.000408ms, 1% @16 = gpu::add(@13,@14,@15) -> float_type, {1, 10}, {10, 1}: 0.0410144ms, 10% output = @param:output -> float_type, {1, 10}, {10, 1}: 0.0010222ms, 1% @17 = gpu::softmax[axis=1](@16,output) -> float_type, {1, 10}, {10, 1}: 0.0385636ms, 10% Summary: gpu::gemm: 0.285899ms, 69% gpu::add_relu: 0.0416597ms, 11% gpu::add: 0.0410144ms, 10% gpu::softmax: 0.0385636ms, 10% hip::hip_copy_literal: 0.00186824ms, 1% load: 0.0016288ms, 1% @param: 0.0013428ms, 1% broadcast: 0.00118042ms, 1% check_context::migraphx::gpu::context: 0.00057782ms, 1% reshape: 0.00033842ms, 1% hip::hip_allocate_memory: 0.000295ms, 1% Rate: 2866.1/sec Total time: 0.348906ms Total instructions time: 0.414369ms Overhead time: 0.00348144ms, -0.0654627ms Overhead: 1%, -19% ```


ROCm-AMDMIGraphX-46524e8/examples/nlp/000077500000000000000000000000001510465702400171475ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/nlp/README.md000066400000000000000000000001341510465702400204240ustar00rootroot00000000000000# Natural Language Processing Inference Examples - [Python BERT-SQuAD](./python_bert_squad)ROCm-AMDMIGraphX-46524e8/examples/nlp/python_bert_squad/000077500000000000000000000000001510465702400227015ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/nlp/python_bert_squad/BERT-Squad.ipynb000066400000000000000000000200041510465702400255470ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# BERT-SQuAD Inference Example with AMD MIGraphX" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial shows how to run the BERT-Squad model on ONNX-Runtime with MIGraphX backend." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Requirements " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip3 install -r requirements_bertsquad.txt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import json\n", "import time\n", "import os.path\n", "from os import path\n", "import sys\n", "\n", "import tokenizers\n", "from run_onnx_squad import *\n", "\n", "import migraphx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Download BERT ONNX file" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!wget -nc https://github.com/onnx/models/raw/main/validated/text/machine_comprehension/bert-squad/model/bertsquad-10.onnx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Download uncased file / vocabulary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!apt-get install unzip\n", "!wget -q -nc https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip\n", "!unzip -n uncased_L-12_H-768_A-12.zip" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Input data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "input_file = 'inputs.json'\n", "with open(input_file) as json_file:\n", " test_data = json.load(json_file)\n", " print(json.dumps(test_data, indent=2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Configuration for inference" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "max_seq_length = 256\n", "doc_stride = 128\n", "max_query_length = 64\n", "batch_size = 1\n", "n_best_size = 20\n", "max_answer_length = 30" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read vocabulary file and tokenize" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "vocab_file = os.path.join('uncased_L-12_H-768_A-12', 'vocab.txt')\n", "tokenizer = tokenizers.BertWordPieceTokenizer(vocab_file)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Convert the example to features to input" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# preprocess input\n", "predict_file = 'inputs.json'\n", "\n", "# Use read_squad_examples method from run_onnx_squad to read the input file\n", "eval_examples = read_squad_examples(input_file=predict_file)\n", "\n", "# Use convert_examples_to_features method from run_onnx_squad to get parameters from the input\n", "input_ids, input_mask, segment_ids, extra_data = convert_examples_to_features(\n", " eval_examples, tokenizer, max_seq_length, doc_stride, max_query_length)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Compile with MIGraphX for GPU" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = migraphx.parse_onnx(\"bertsquad-10.onnx\")\n", "model.compile(migraphx.get_target(\"gpu\"))\n", "#model.print()\n", "\n", "model.get_parameter_names()\n", "model.get_parameter_shapes()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run the input through the model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n = len(input_ids)\n", "bs = batch_size\n", "all_results = []\n", "\n", "for idx in range(0, n):\n", " item = eval_examples[idx]\n", " print(item)\n", "\n", " result = model.run({\n", " \"unique_ids_raw_output___9:0\":\n", " np.array([item.qas_id], dtype=np.int64),\n", " \"input_ids:0\":\n", " input_ids[idx:idx + bs],\n", " \"input_mask:0\":\n", " input_mask[idx:idx + bs],\n", " \"segment_ids:0\":\n", " segment_ids[idx:idx + bs]\n", " })\n", "\n", " in_batch = result[1].get_shape().lens()[0]\n", " print(in_batch)\n", " start_logits = [float(x) for x in result[1].tolist()]\n", " end_logits = [float(x) for x in result[0].tolist()]\n", " # print(start_logits)\n", " # print(end_logits)\n", " for i in range(0, in_batch):\n", " unique_id = len(all_results)\n", " all_results.append(\n", " RawResult(unique_id=unique_id,\n", " start_logits=start_logits,\n", " end_logits=end_logits))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Get the predictions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "output_dir = 'predictions'\n", "os.makedirs(output_dir, exist_ok=True)\n", "output_prediction_file = os.path.join(output_dir, \"predictions.json\")\n", "output_nbest_file = os.path.join(output_dir, \"nbest_predictions.json\")\n", "write_predictions(eval_examples, extra_data, all_results, n_best_size,\n", " max_answer_length, True, output_prediction_file,\n", " output_nbest_file)\n", "\n", "with open(output_prediction_file) as json_file:\n", " test_data = json.load(json_file)\n", " print(json.dumps(test_data, indent=2))" ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 2 } ROCm-AMDMIGraphX-46524e8/examples/nlp/python_bert_squad/README.md000066400000000000000000000026071510465702400241650ustar00rootroot00000000000000# BERT-SQuAD Example with MIGraphX Question answering with BERT using MIGraphX optimizations on ROCm platform. There are two ways to run the example: 1) Install MIGraphX and Jupyter notebook to your system and then utilize `BERT-Squad.ipynb` notebook file. 2) Install MIGraphx to your system and follow the steps executing the python script `bert-squad-migraphx.py`. # Steps 1) Install MIGraphX to your environment. Please follow the steps to build MIGraphX given at https://github.com/ROCmSoftwarePlatform/AMDMIGraphX 2) Upgrade your pip3 to latest version ``` pip3 install --upgrade pip ``` 3) Install the requirements file ``` pip3 install -r requirements_bertsquad.txt ``` 4) Install `unzip` and fetch the uncased file (vocabulary): ``` apt-get install unzip wget -q https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip unzip uncased_L-12_H-768_A-12.zip ``` 5) Get BERT ONNX model (bertsquad-10.onnx): ``` wget https://github.com/onnx/models/raw/main/validated/text/machine_comprehension/bert-squad/model/bertsquad-10.onnx ``` 6) Run the inference, it will compile and run the model on three questions and small data provided in `inputs.json`: ``` python3 bert-squad-migraphx.py ``` ## References This example utilizes the following notebook :notebook: and applies it to MIGraphX: https://github.com/onnx/models/blob/master/text/machine_comprehension/bert-squad/BERT-Squad.ipynb ROCm-AMDMIGraphX-46524e8/examples/nlp/python_bert_squad/bert-squad-migraphx.py000066400000000000000000000100461510465702400271400ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import numpy as np import json import os.path import tokenizers import collections from run_onnx_squad import read_squad_examples, write_predictions, convert_examples_to_features import migraphx RawResult = collections.namedtuple("RawResult", ["unique_id", "start_logits", "end_logits"]) ####################################### input_file = 'inputs_amd.json' with open(input_file) as json_file: test_data = json.load(json_file) print(json.dumps(test_data, indent=2)) # preprocess input predict_file = 'inputs_amd.json' # Use read_squad_examples method from run_onnx_squad to read the input file eval_examples = read_squad_examples(input_file=predict_file) max_seq_length = 256 doc_stride = 128 max_query_length = 64 batch_size = 1 n_best_size = 20 max_answer_length = 30 vocab_file = os.path.join('uncased_L-12_H-768_A-12', 'vocab.txt') tokenizer = tokenizers.BertWordPieceTokenizer(vocab_file) # Use convert_examples_to_features method from run_onnx_squad to get parameters from the input input_ids, input_mask, segment_ids, extra_data = convert_examples_to_features( eval_examples, tokenizer, max_seq_length, doc_stride, max_query_length) ####################################### # Compile print("INFO: Parsing and compiling the model...") model = migraphx.parse_onnx("bertsquad-10.onnx") model.compile(migraphx.get_target("gpu")) #model.print() print(model.get_parameter_names()) print(model.get_parameter_shapes()) n = len(input_ids) bs = batch_size all_results = [] for idx in range(0, n): item = eval_examples[idx] print(item) result = model.run({ "unique_ids_raw_output___9:0": np.array([item.qas_id], dtype=np.int64), "input_ids:0": input_ids[idx:idx + bs], "input_mask:0": input_mask[idx:idx + bs], "segment_ids:0": segment_ids[idx:idx + bs] }) in_batch = result[1].get_shape().lens()[0] start_logits = [float(x) for x in result[1].tolist()] end_logits = [float(x) for x in result[0].tolist()] for i in range(0, in_batch): unique_id = len(all_results) all_results.append( RawResult(unique_id=unique_id, start_logits=start_logits, end_logits=end_logits)) output_dir = 'predictions' os.makedirs(output_dir, exist_ok=True) output_prediction_file = os.path.join(output_dir, "predictions.json") output_nbest_file = os.path.join(output_dir, "nbest_predictions.json") write_predictions(eval_examples, extra_data, all_results, n_best_size, max_answer_length, True, output_prediction_file, output_nbest_file) with open(output_prediction_file) as json_file: test_data = json.load(json_file) print(json.dumps(test_data, indent=2)) ROCm-AMDMIGraphX-46524e8/examples/nlp/python_bert_squad/inputs.json000066400000000000000000000021521510465702400251160ustar00rootroot00000000000000{ "version": "1.4", "data": [ { "paragraphs": [ { "context": "In its early years, the new convention center failed to meet attendance and revenue expectations.[12] By 2002, many Silicon Valley businesses were choosing the much larger Moscone Center in San Francisco over the San Jose Convention Center due to the latter's limited space. A ballot measure to finance an expansion via a hotel tax failed to reach the required two-thirds majority to pass. In June 2005, Team San Jose built the South Hall, a $6.77 million, blue and white tent, adding 80,000 square feet (7,400 m2) of exhibit space", "qas": [ { "question": "where is the businesses choosing to go?", "id": "1" }, { "question": "how may votes did the ballot measure need?", "id": "2" }, { "question": "By what year many Silicon Valley businesses were choosing the Moscone Center?", "id": "3" } ] } ], "title": "Conference Center" } ] }ROCm-AMDMIGraphX-46524e8/examples/nlp/python_bert_squad/inputs_amd.json000066400000000000000000000024051510465702400257400ustar00rootroot00000000000000{ "data": [ { "paragraphs": [ { "context": "ROCm is the first open-source exascale-class platform for accelerated computing that’s also programming-language independent. It brings a philosophy of choice, minimalism and modular software development to GPU computing. You are free to choose or even develop tools and a language run time for your application. ROCm is built for scale, it supports multi-GPU computing and has a rich system run time with the critical features that large-scale application, compiler and language-run-time development requires. Since the ROCm ecosystem is comprised of open technologies: frameworks (Tensorflow / PyTorch), libraries (MIOpen / Blas / RCCL), programming model (HIP), inter-connect (OCD) and up streamed Linux® Kernel support – the platform is continually optimized for performance and extensibility.", "qas": [ { "question": "What is ROCm?", "id": "1" }, { "question": "Which frameworks does ROCm support?", "id": "2" }, { "question": "What is ROCm built for?", "id": "3" } ] } ], "title": "AMD ROCm" } ] }ROCm-AMDMIGraphX-46524e8/examples/nlp/python_bert_squad/requirements_bertsquad.txt000066400000000000000000000027061510465702400302440ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### tensorflow onnxruntime tokenizers==0.11.1; python_version == "3.6" tokenizers==0.20.3; python_version == "3.8" tokenizers; python_version > "3.8" ROCm-AMDMIGraphX-46524e8/examples/nlp/python_bert_squad/run_onnx_squad.py000066400000000000000000000574471510465702400263370ustar00rootroot00000000000000# Modifications Copyright (C) 2022, Advanced Micro Devices, Inc. All rights reserved # Copyright 2018 The Google AI Language Team Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Inference for squad/bert using onnx. This is going to do the samem as 'python run_squad.py --do_predict=True ...' using a squad/bert model that was converted to onnx. Lots of code was taken from run_squad.py. You run it with: python onnx_squad.py --model $SQUAD_MODEL/squad.onnx \ --vocab_file $BERT_BASE_DIR/uncased_L-12_H-768_A-12/vocab.txt --predict_file $SQUAD_DATA/dev-v1.1.json \ --bert_config_file $BERT_BASE_DIR/uncased_L-12_H-768_A-12/bert_config.json \ --output /tmp/ """ import argparse import collections import json import math import os import sys from timeit import default_timer as timer import numpy as np import onnxruntime as onnxrt import six from tokenizers import BertWordPieceTokenizer from tokenizers import pre_tokenizers RawResult = collections.namedtuple("RawResult", ["unique_id", "start_logits", "end_logits"]) Feature = collections.namedtuple("Feature", [ "unique_id", "tokens", "example_index", "token_to_orig_map", "token_is_max_context" ]) class SquadExample(object): """A single training/test example for simple sequence classification.""" def __init__(self, qas_id, question_text, doc_tokens, orig_answer_text=None, start_position=None, end_position=None): self.qas_id = qas_id self.question_text = question_text self.doc_tokens = doc_tokens self.orig_answer_text = orig_answer_text self.start_position = start_position self.end_position = end_position def __str__(self): return self.__repr__() def __repr__(self): s = [] s.append("qas_id: %s" % (self.qas_id)) s.append("question_text: %s" % (self.question_text)) s.append("doc_tokens: [%s]" % (" ".join(self.doc_tokens))) if self.start_position: s.append("start_position: %d" % (self.start_position)) if self.start_position: s.append("end_position: %d" % (self.end_position)) return ", ".join(s) def _check_is_max_context(doc_spans, cur_span_index, position): """Check if this is the 'max context' doc span for the token.""" # Because of the sliding window approach taken to scoring documents, a single # token can appear in multiple documents. E.g. # Doc: the man went to the store and bought a gallon of milk # Span A: the man went to the # Span B: to the store and bought # Span C: and bought a gallon of # ... # # Now the word 'bought' will have two scores from spans B and C. We only # want to consider the score with "maximum context", which we define as # the *minimum* of its left and right context (the *sum* of left and # right context will always be the same, of course). # # In the example the maximum context for 'bought' would be span C since # it has 1 left context and 3 right context, while span B has 4 left context # and 0 right context. best_score = None best_span_index = None for (span_index, doc_span) in enumerate(doc_spans): end = doc_span.start + doc_span.length - 1 if position < doc_span.start: continue if position > end: continue num_left_context = position - doc_span.start num_right_context = end - position score = min(num_left_context, num_right_context) + 0.01 * doc_span.length if best_score is None or score > best_score: best_score = score best_span_index = span_index return cur_span_index == best_span_index def convert_examples_to_features(examples, tokenizer, max_seq_length, doc_stride, max_query_length): """Loads a data file into a list of `InputBatch`s.""" res_input_ids = [] res_input_mask = [] res_segment_ids = [] extra = [] unique_id = 0 for (example_index, example) in enumerate(examples): query_tokens = tokenizer.encode(example.question_text) if len(query_tokens) > max_query_length: query_tokens = query_tokens[0:max_query_length] tok_to_orig_index = [] orig_to_tok_index = [] all_doc_tokens = [] for (i, token) in enumerate(example.doc_tokens): orig_to_tok_index.append(len(all_doc_tokens)) sub_tokens = tokenizer.encode(token, add_special_tokens=False) for sub_token in sub_tokens.tokens: tok_to_orig_index.append(i) all_doc_tokens.append(sub_token) # The -3 accounts for [CLS], [SEP] and [SEP] max_tokens_for_doc = max_seq_length - len(query_tokens) - 3 # We can have documents that are longer than the maximum sequence length. # To deal with this we do a sliding window approach, where we take chunks # of the up to our max length with a stride of `doc_stride`. _DocSpan = collections.namedtuple("DocSpan", ["start", "length"]) doc_spans = [] start_offset = 0 while start_offset < len(all_doc_tokens): length = len(all_doc_tokens) - start_offset if length > max_tokens_for_doc: length = max_tokens_for_doc doc_spans.append(_DocSpan(start=start_offset, length=length)) if start_offset + length == len(all_doc_tokens): break start_offset += min(length, doc_stride) for (doc_span_index, doc_span) in enumerate(doc_spans): tokens = [] token_to_orig_map = {} token_is_max_context = {} segment_ids = [] tokens.append("[CLS]") segment_ids.append(0) for token in query_tokens.tokens: tokens.append(token) segment_ids.append(0) tokens.append("[SEP]") segment_ids.append(0) for i in range(doc_span.length): split_token_index = doc_span.start + i token_to_orig_map[len( tokens)] = tok_to_orig_index[split_token_index] is_max_context = _check_is_max_context(doc_spans, doc_span_index, split_token_index) token_is_max_context[len(tokens)] = is_max_context tokens.append(all_doc_tokens[split_token_index]) segment_ids.append(1) tokens.append("[SEP]") segment_ids.append(1) input_ids = [] for token in tokens: input_ids.append(tokenizer.token_to_id(token)) # The mask has 1 for real tokens and 0 for padding tokens. Only real # tokens are attended to. input_mask = [1] * len(input_ids) # Zero-pad up to the sequence length. while len(input_ids) < max_seq_length: input_ids.append(0) input_mask.append(0) segment_ids.append(0) res_input_ids.append(np.array(input_ids, dtype=np.int64)) res_input_mask.append(np.array(input_mask, dtype=np.int64)) res_segment_ids.append(np.array(segment_ids, dtype=np.int64)) feature = Feature(unique_id=unique_id, tokens=tokens, example_index=example_index, token_to_orig_map=token_to_orig_map, token_is_max_context=token_is_max_context) extra.append(feature) unique_id += 1 return np.array(res_input_ids), np.array(res_input_mask), np.array( res_segment_ids), extra def read_squad_examples(input_file): """Read a SQuAD json file into a list of SquadExample.""" with open(input_file, "r") as f: input_data = json.load(f)["data"] def is_whitespace(c): if c == " " or c == "\t" or c == "\r" or c == "\n" or ord(c) == 0x202F: return True return False examples = [] for idx, entry in enumerate(input_data): for paragraph in entry["paragraphs"]: paragraph_text = paragraph["context"] doc_tokens = [] char_to_word_offset = [] prev_is_whitespace = True for c in paragraph_text: if is_whitespace(c): prev_is_whitespace = True else: if prev_is_whitespace: doc_tokens.append(c) else: doc_tokens[-1] += c prev_is_whitespace = False char_to_word_offset.append(len(doc_tokens) - 1) for qa in paragraph["qas"]: qas_id = qa["id"] question_text = qa["question"] start_position = None end_position = None orig_answer_text = None example = SquadExample(qas_id=qas_id, question_text=question_text, doc_tokens=doc_tokens, orig_answer_text=orig_answer_text, start_position=start_position, end_position=end_position) examples.append(example) return examples def write_predictions(all_examples, all_features, all_results, n_best_size, max_answer_length, do_lower_case, output_prediction_file, output_nbest_file): """Write final predictions to the json file.""" example_index_to_features = collections.defaultdict(list) for feature in all_features: example_index_to_features[feature.example_index].append(feature) unique_id_to_result = {} for result in all_results: unique_id_to_result[result.unique_id] = result _PrelimPrediction = collections.namedtuple( # pylint: disable=invalid-name "PrelimPrediction", [ "feature_index", "start_index", "end_index", "start_logit", "end_logit" ]) all_predictions = collections.OrderedDict() all_nbest_json = collections.OrderedDict() for (example_index, example) in enumerate(all_examples): features = example_index_to_features[example_index] prelim_predictions = [] for (feature_index, feature) in enumerate(features): if not feature.unique_id in unique_id_to_result: print("feature not in unique_Id", feature.unique_id) continue result = unique_id_to_result[feature.unique_id] start_indexes = _get_best_indexes(result.start_logits, n_best_size) end_indexes = _get_best_indexes(result.end_logits, n_best_size) for start_index in start_indexes: for end_index in end_indexes: # We could hypothetically create invalid predictions, e.g., predict # that the start of the span is in the question. We throw out all # invalid predictions. if start_index >= len(feature.tokens): continue if end_index >= len(feature.tokens): continue if start_index not in feature.token_to_orig_map: continue if end_index not in feature.token_to_orig_map: continue if not feature.token_is_max_context.get( start_index, False): continue if end_index < start_index: continue length = end_index - start_index + 1 if length > max_answer_length: continue prelim_predictions.append( _PrelimPrediction( feature_index=feature_index, start_index=start_index, end_index=end_index, start_logit=result.start_logits[start_index], end_logit=result.end_logits[end_index])) prelim_predictions = sorted(prelim_predictions, key=lambda x: (x.start_logit + x.end_logit), reverse=True) _NbestPrediction = collections.namedtuple( # pylint: disable=invalid-name "NbestPrediction", ["text", "start_logit", "end_logit"]) seen_predictions = {} nbest = [] for pred in prelim_predictions: if len(nbest) >= n_best_size: break feature = features[pred.feature_index] tok_tokens = feature.tokens[pred.start_index:(pred.end_index + 1)] orig_doc_start = feature.token_to_orig_map[pred.start_index] orig_doc_end = feature.token_to_orig_map[pred.end_index] orig_tokens = example.doc_tokens[orig_doc_start:(orig_doc_end + 1)] tok_text = " ".join(tok_tokens) # De-tokenize WordPieces that have been split off. tok_text = tok_text.replace(" ##", "") tok_text = tok_text.replace("##", "") # Clean whitespace tok_text = tok_text.strip() tok_text = " ".join(tok_text.split()) orig_text = " ".join(orig_tokens) final_text = get_final_text(tok_text, orig_text, do_lower_case) if final_text in seen_predictions: continue seen_predictions[final_text] = True nbest.append( _NbestPrediction(text=final_text, start_logit=pred.start_logit, end_logit=pred.end_logit)) # In very rare edge cases we could have no valid predictions. So we # just create a nonce prediction in this case to avoid failure. if not nbest: nbest.append( _NbestPrediction(text="empty", start_logit=0.0, end_logit=0.0)) assert len(nbest) >= 1 total_scores = [] for entry in nbest: total_scores.append(entry.start_logit + entry.end_logit) probs = _compute_softmax(total_scores) nbest_json = [] for (i, entry) in enumerate(nbest): output = collections.OrderedDict() output["text"] = entry.text output["probability"] = probs[i] output["start_logit"] = float(entry.start_logit) output["end_logit"] = float(entry.end_logit) nbest_json.append(output) all_predictions[example.qas_id] = nbest_json[0]["text"] all_nbest_json[example.qas_id] = nbest_json with open(output_prediction_file, "w") as writer: writer.write(json.dumps(all_predictions, indent=4) + "\n") with open(output_nbest_file, "w") as writer: writer.write(json.dumps(all_nbest_json, indent=4) + "\n") def get_final_text(pred_text, orig_text, do_lower_case): """Project the tokenized prediction back to the original text.""" # When we created the data, we kept track of the alignment between original # (whitespace tokenized) tokens and our WordPiece tokenized tokens. So # now `orig_text` contains the span of our original text corresponding to the # span that we predicted. # # However, `orig_text` may contain extra characters that we don't want in # our prediction. # # For example, let's say: # pred_text = steve smith # orig_text = Steve Smith's # # We don't want to return `orig_text` because it contains the extra "'s". # # We don't want to return `pred_text` because it's already been normalized # (the SQuAD eval script also does punctuation stripping/lower casing but # our tokenizer does additional normalization like stripping accent # characters). # # What we really want to return is "Steve Smith". # # Therefore, we have to apply a semi-complicated alignment heruistic between # `pred_text` and `orig_text` to get a character-to-charcter alignment. This # can fail in certain cases in which case we just return `orig_text`. def _strip_spaces(text): ns_chars = [] ns_to_s_map = collections.OrderedDict() for (i, c) in enumerate(text): if c == " ": continue ns_to_s_map[len(ns_chars)] = i ns_chars.append(c) ns_text = "".join(ns_chars) return (ns_text, ns_to_s_map) # We first tokenize `orig_text`, strip whitespace from the result # and `pred_text`, and check if they are the same length. If they are # NOT the same length, the heuristic has failed. If they are the same # length, we assume the characters are one-to-one aligned. tokenizer = pre_tokenizers.Sequence( [pre_tokenizers.Whitespace(), pre_tokenizers.Punctuation()]) tok_text = [] for item in tokenizer.pre_tokenize_str(orig_text): tok_text.append(item[0]) tok_text = " ".join(tok_text) start_position = tok_text.find(pred_text) if start_position == -1: return orig_text end_position = start_position + len(pred_text) - 1 (orig_ns_text, orig_ns_to_s_map) = _strip_spaces(orig_text) (tok_ns_text, tok_ns_to_s_map) = _strip_spaces(tok_text) if len(orig_ns_text) != len(tok_ns_text): return orig_text # We then project the characters in `pred_text` back to `orig_text` using # the character-to-character alignment. tok_s_to_ns_map = {} for (i, tok_index) in six.iteritems(tok_ns_to_s_map): tok_s_to_ns_map[tok_index] = i orig_start_position = None if start_position in tok_s_to_ns_map: ns_start_position = tok_s_to_ns_map[start_position] if ns_start_position in orig_ns_to_s_map: orig_start_position = orig_ns_to_s_map[ns_start_position] if orig_start_position is None: return orig_text orig_end_position = None if end_position in tok_s_to_ns_map: ns_end_position = tok_s_to_ns_map[end_position] if ns_end_position in orig_ns_to_s_map: orig_end_position = orig_ns_to_s_map[ns_end_position] if orig_end_position is None: return orig_text output_text = orig_text[orig_start_position:(orig_end_position + 1)] return output_text def _get_best_indexes(logits, n_best_size): """Get the n-best logits from a list.""" index_and_score = sorted(enumerate(logits), key=lambda x: x[1], reverse=True) best_indexes = [] for i in range(len(index_and_score)): if i >= n_best_size: break best_indexes.append(index_and_score[i][0]) return best_indexes def _compute_softmax(scores): """Compute softmax probability over raw logits.""" if not scores: return [] max_score = None for score in scores: if max_score is None or score > max_score: max_score = score exp_scores = [] total_sum = 0.0 for score in scores: x = math.exp(score - max_score) exp_scores.append(x) total_sum += x probs = [] for score in exp_scores: probs.append(score / total_sum) return probs def main(): parser = argparse.ArgumentParser(description='onnx squad') parser.add_argument('--model', required=True, help='model') parser.add_argument('--vocab_file', required=True, help='vocab_file') parser.add_argument('--bert_config_file', help='vocab_file') parser.add_argument('--predict_file', required=True, help='predict_file') parser.add_argument('--output_dir', help='output dir') parser.add_argument('--max_seq_length', type=int, default=256, help='max_seq_length') parser.add_argument('--max_query_length', type=int, default=64, help='max_query_length') parser.add_argument('--max_answer_length', type=int, default=30, help='max_answer_length') parser.add_argument('--n_best_size', type=int, default=20, help='n_best_size') parser.add_argument('--doc_stride', type=int, default=128, help='doc_stride') parser.add_argument('--batch_size', type=int, default=1, help='batch_size') parser.add_argument('--profile', action='store_true', help='enable chrome timeline trace profiling.') parser.add_argument('--log', type=int, help='log level.') args = parser.parse_args() sess_options = None if args.profile: sess_options = onnxrt.SessionOptions() sess_options.enable_profiling = True sess_options.profile_file_prefix = os.path.basename(args.model) if args.log: sess_options = onnxrt.SessionOptions() sess_options.session_log_verbosity_level = args.log tokenizer = BertWordPieceTokenizer(args.vocab_file) eval_examples = read_squad_examples(input_file=args.predict_file) input_ids, input_mask, segment_ids, extra_data = \ convert_examples_to_features(eval_examples, tokenizer, args.max_seq_length, args.doc_stride, args.max_query_length) sess = onnxrt.InferenceSession(args.model, sess_options) for input_meta in sess.get_inputs(): print(input_meta) n = len(input_ids) bs = args.batch_size all_results = [] start = timer() for idx in range(0, n, bs): data = { "input_ids:0": input_ids[idx:idx + bs], "input_mask:0": input_mask[idx:idx + bs], "segment_ids:0": segment_ids[idx:idx + bs] } result = sess.run(["unstack:0", "unstack:1"], data) in_batch = result[0].shape[1] for i in range(0, in_batch): unique_id = len(all_results) all_results.append( RawResult(unique_id=unique_id, start_logits=result[0][0][i], end_logits=result[1][0][i])) if unique_id > 0 and unique_id % 100 == 0: print("at {} {}sec per item".format( unique_id, (timer() - start) / unique_id)) end = timer() print("total time: {}sec, {}sec per item".format( end - start, (end - start) / len(all_results))) if args.output_dir: output_prediction_file = os.path.join(args.output_dir, "predictions.json") output_nbest_file = os.path.join(args.output_dir, "nbest_predictions.json") write_predictions(eval_examples, extra_data, all_results, args.n_best_size, args.max_answer_length, True, output_prediction_file, output_nbest_file) if args.profile: trace_file = sess.end_profiling() print("trace file written to: {}".format(trace_file)) return 0 if __name__ == "__main__": sys.exit(main()) ROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/000077500000000000000000000000001510465702400215315ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/README.md000066400000000000000000000023241510465702400230110ustar00rootroot00000000000000# RNN-T Speech Recognition using MIGraphX optimizations on ROCm platform. This version was taken from [MLCommons](https://github.com/mlcommons/inference/tree/master/retired_benchmarks/speech_recognition/rnnt) ## Steps To run the RNN-T, follow these steps below. 1) Install MIGraphX to your environment. Please follow the steps to build MIGraphX given at https://github.com/ROCmSoftwarePlatform/AMDMIGraphX 2) Require the python venv to installed ```bash python3 -m venv rnnt_venv . rnnt_venv/bin/activate ``` 3) Install dependencies ```bash pip install -r torch_requirements.txt -r requirements.txt ``` 4) Download helper files ```bash bash download_helper_files.sh ``` 4) Download RNN-T Model Weights ```bash wget https://zenodo.org/record/3662521/files/DistributedDataParallel_1576581068.9962234-epoch-100.pt?download=1 -O rnnt.pt ``` 5) Run the inference, it will compile and run the model on the first sentence in [hf-internal-testing/librispeech_asr_dummy](https://huggingface.co/datasets/hf-internal-testing/librispeech_asr_dummy) ```bash python3 rnnt.py ``` This will do four things: - Create the PyTorch model - Export the PyTorch model to ONNX - Use MIGraphX to read the ONNX model - Run the MIGraphX model ROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/download_helper_files.sh000066400000000000000000000032761510465702400264250ustar00rootroot00000000000000if [ ! -f model_separable_rnnt.py ]; then wget https://raw.githubusercontent.com/mlcommons/inference/c7db1c3f9a0ae0623200e05580d77a8759644812/retired_benchmarks/speech_recognition/rnnt/pytorch/model_separable_rnnt.py fi if [ ! -f rnn.py ]; then wget https://raw.githubusercontent.com/mlcommons/inference/c7db1c3f9a0ae0623200e05580d77a8759644812/retired_benchmarks/speech_recognition/rnnt/pytorch/rnn.py fi if [ ! -f preprocessing.py ]; then wget https://raw.githubusercontent.com/mlcommons/inference/c7db1c3f9a0ae0623200e05580d77a8759644812/retired_benchmarks/speech_recognition/rnnt/pytorch/preprocessing.py fi if [ ! -f helpers.py ]; then wget https://raw.githubusercontent.com/mlcommons/inference/c7db1c3f9a0ae0623200e05580d77a8759644812/retired_benchmarks/speech_recognition/rnnt/pytorch/helpers.py fi if [ ! -f metrics.py ]; then wget https://raw.githubusercontent.com/mlcommons/inference/c7db1c3f9a0ae0623200e05580d77a8759644812/retired_benchmarks/speech_recognition/rnnt/pytorch/metrics.py fi mkdir -p parts if [ ! -f parts/features.py ]; then wget -P parts https://raw.githubusercontent.com/mlcommons/inference/c7db1c3f9a0ae0623200e05580d77a8759644812/retired_benchmarks/speech_recognition/rnnt/pytorch/parts/features.py fi if [ ! -f parts/segment.py ]; then wget -P parts https://raw.githubusercontent.com/mlcommons/inference/c7db1c3f9a0ae0623200e05580d77a8759644812/retired_benchmarks/speech_recognition/rnnt/pytorch/parts/segment.py fi mkdir -p configs if [ ! -f configs/rnnt.toml ]; then wget -P configs https://raw.githubusercontent.com/mlcommons/inference/c7db1c3f9a0ae0623200e05580d77a8759644812/retired_benchmarks/speech_recognition/rnnt/pytorch/configs/rnnt.toml fi ROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/requirements.txt000066400000000000000000000026111510465702400250150ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### --extra-index-url https://test.pypi.org/simple hip-python librosa soundfile datasetsROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/rnnt.py000066400000000000000000000322221510465702400230650ustar00rootroot00000000000000# The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. from typing import List, Optional, Tuple import argparse import torch import migraphx as mgx import os import sys import torch.nn.functional as F from rnnt_data import librespeech_huggingface mgx_to_torch_dtype_dict = { "bool_type": torch.bool, "uint8_type": torch.uint8, "int8_type": torch.int8, "int16_type": torch.int16, "int32_type": torch.int32, "int64_type": torch.int64, "float_type": torch.float32, "double_type": torch.float64, "half_type": torch.float16, } torch_to_mgx_dtype_dict = { value: key for (key, value) in mgx_to_torch_dtype_dict.items() } def tensor_to_arg(tensor): return mgx.argument_from_pointer( mgx.shape( **{ "type": torch_to_mgx_dtype_dict[tensor.dtype], "lens": list(tensor.size()), "strides": list(tensor.stride()) }), tensor.data_ptr()) def tensors_to_args(tensors): return {name: tensor_to_arg(tensor) for name, tensor in tensors.items()} def get_output_name(idx): return f"main:#output_{idx}" def copy_tensor_sync(tensor, data): tensor.copy_(data) torch.cuda.synchronize() def run_model_sync(model, args): model.run(args) mgx.gpu_sync() def allocate_torch_tensors(model): input_shapes = model.get_parameter_shapes() data_mapping = { name: torch.zeros(shape.lens()).to( mgx_to_torch_dtype_dict[shape.type_string()]).to(device="cuda") for name, shape in input_shapes.items() } return data_mapping class RNNT_MGX(): def __init__(self, x, onnx_model_path, fp16): if fp16 is None: fp16 = [] elif "all" in fp16: fp16 = ["rnnt_encoder", "rnnt_prediction", "rnnt_joint"] compiled_model_path = None force_compile = False exhaustive_tune = False print("Load models...") self.models = { "rnnt_encoder": RNNT_MGX.load_mgx_model( "rnnt_encoder", { "input": x.shape, # seq_length, batch_size, feature_length "feature_length": [1] }, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="rnnt_encoder" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "rnnt_prediction": RNNT_MGX.load_mgx_model("rnnt_prediction", { "symbol": [1, 1], "hidden_in_1": [2, 1, 320], "hidden_in_2": [2, 1, 320] }, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="rnnt_prediction" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False), "rnnt_joint": RNNT_MGX.load_mgx_model("rnnt_joint", { "0": [1, 1, 1024], "1": [1, 1, 320] }, onnx_model_path, compiled_model_path=compiled_model_path, use_fp16="rnnt_joint" in fp16, force_compile=force_compile, exhaustive_tune=exhaustive_tune, offload_copy=False) } self.tensors = { "rnnt_encoder": allocate_torch_tensors(self.models["rnnt_encoder"]), "rnnt_prediction": allocate_torch_tensors(self.models["rnnt_prediction"]), "rnnt_joint": allocate_torch_tensors(self.models["rnnt_joint"]), } self.model_args = { "rnnt_encoder": tensors_to_args(self.tensors['rnnt_encoder']), "rnnt_prediction": tensors_to_args(self.tensors['rnnt_prediction']), "rnnt_joint": tensors_to_args(self.tensors['rnnt_joint']), } @torch.no_grad() def encoder(self, inp, feature_length): copy_tensor_sync(self.tensors["rnnt_encoder"]["input"], inp.to(torch.float32)) copy_tensor_sync(self.tensors["rnnt_encoder"]["feature_length"], feature_length.to(torch.int64)) run_model_sync(self.models["rnnt_encoder"], self.model_args["rnnt_encoder"]) x_padded, x_lens = self.tensors["rnnt_encoder"][get_output_name( 0)], self.tensors["rnnt_encoder"][get_output_name(1)] x_padded = x_padded.squeeze(1) x_padded = x_padded.transpose(1, 0) return x_padded.to(torch.float16), x_lens.to(torch.int32) @torch.no_grad() def prediction(self, symbol, hidden): copy_tensor_sync(self.tensors["rnnt_prediction"]["symbol"], symbol.to(torch.int64)) copy_tensor_sync(self.tensors["rnnt_prediction"]["hidden_in_1"], hidden[0].to(torch.float32)) copy_tensor_sync(self.tensors["rnnt_prediction"]["hidden_in_2"], hidden[1].to(torch.float32)) run_model_sync(self.models["rnnt_prediction"], self.model_args["rnnt_prediction"]) g = self.tensors["rnnt_prediction"][get_output_name(0)] hidden = self.tensors["rnnt_prediction"][get_output_name(1)].to( torch.float16), self.tensors["rnnt_prediction"][get_output_name( 2)].to(torch.float16) return g.to(torch.float16), hidden @torch.no_grad() def joint(self, f, g): copy_tensor_sync(self.tensors["rnnt_joint"]["onnx::Shape_0"], f.to(torch.float32)) copy_tensor_sync(self.tensors["rnnt_joint"]["onnx::Shape_1"], g.to(torch.float32)) run_model_sync(self.models["rnnt_joint"], self.model_args["rnnt_joint"]) result = self.tensors["rnnt_joint"][get_output_name(0)] return result.to(torch.float16) @staticmethod def load_mgx_model(name, shapes, onnx_model_path, compiled_model_path=None, use_fp16=False, force_compile=False, exhaustive_tune=False, offload_copy=True): print(f"Loading {name} model...") if compiled_model_path is None: compiled_model_path = onnx_model_path onnx_file = f"{onnx_model_path}/{name}/model.onnx" mxr_file = f"{compiled_model_path}/{name}/model_{'fp16' if use_fp16 else 'fp32'}_{'gpu' if not offload_copy else 'oc'}.mxr" if not force_compile and os.path.isfile(mxr_file): print(f"Found mxr, loading it from {mxr_file}") model = mgx.load(mxr_file, format="msgpack") elif os.path.isfile(onnx_file): print(f"No mxr found at {mxr_file}") print(f"Parsing from {onnx_file}") model = mgx.parse_onnx(onnx_file, map_input_dims=shapes) if use_fp16: mgx.quantize_fp16(model) model.compile(mgx.get_target("gpu"), exhaustive_tune=exhaustive_tune, offload_copy=offload_copy) print(f"Saving {name} model to {mxr_file}") os.makedirs(os.path.dirname(mxr_file), exist_ok=True) mgx.save(model, mxr_file, format="msgpack") else: print( f"No {name} model found at {onnx_file} or {mxr_file}. Please download it and re-try." ) sys.exit(1) return model class GreedyDecoder(): def __init__(self, model, max_symbols_per_step=30): self._model = model self._SOS = -1 self._blank_id = 28 self._max_symbols_per_step = max_symbols_per_step def run(self, x: torch.Tensor, out_lens: torch.Tensor ) -> Tuple[torch.Tensor, torch.Tensor, List[List[int]]]: logits, logits_lens = self._model.encoder(x, out_lens) output: List[List[int]] = [] for batch_idx in range(logits.size(0)): inseq = logits[batch_idx, :, :].unsqueeze(1).to('cuda') # inseq: TxBxF logitlen = logits_lens[batch_idx] sentence = self._greedy_decode(inseq, logitlen) output.append(sentence) return logits, logits_lens, output def _greedy_decode(self, x: torch.Tensor, out_len: torch.Tensor) -> List[int]: hidden: Optional[Tuple[torch.Tensor, torch.Tensor]] = None label: List[int] = [] for time_idx in range(int(out_len.item())): f = x[time_idx, :, :].unsqueeze(0).to('cuda') not_blank = True symbols_added = 0 while not_blank and symbols_added < self._max_symbols_per_step: g, hidden_prime = self._pred_step(self._get_last_symb(label), hidden) logp = self._joint_step(f, g, log_normalize=False)[0, :] # get index k, of max prob v, k = logp.max(0) k = k.item() if k == self._blank_id: not_blank = False else: label.append(k) hidden = hidden_prime symbols_added += 1 return label def _pred_step(self, label: int, hidden: Optional[Tuple[torch.Tensor, torch.Tensor]] ) -> Tuple[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: if label == self._SOS: label = 0 label = torch.tensor([[label]], dtype=torch.int64) if hidden is None: hidden = torch.zeros([2, 1, 320]).to(device="cuda"), torch.zeros( [2, 1, 320]).to(device="cuda") g, hidden = self._model.prediction(label, hidden) return g, hidden def _joint_step(self, enc: torch.Tensor, pred: torch.Tensor, log_normalize: bool = False) -> torch.Tensor: logits = self._model.joint(enc, pred)[:, 0, 0, :] if not log_normalize: return logits probs = F.log_softmax(logits, dim=len(logits.shape) - 1) return probs def _get_last_symb(self, labels: List[int]) -> int: return self._SOS if len(labels) == 0 else labels[-1] def decode_string(result): string = '' for c in result[0]: if c == 0: string += " " else: string += chr(c + 96) return string if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--onnx_model_path', default='models/rnnt/') parser.add_argument( "--fp16", choices=["all", "rnnt_encoder", "rnnt_prediction", "rnnt_joint"], nargs="+", help="Quantize models with fp16 precision.", ) args = parser.parse_args() model_parts = ['rnnt_encoder', 'rnnt_joint', 'rnnt_prediction'] check_onnx_files = [ os.path.isfile(f"{args.onnx_model_path}/{name}/model.onnx") for name in model_parts ] print("Getting data...") x, out_lens, transcript = librespeech_huggingface() if any(check_onnx_files) == False: from rnnt_torch_model import pytorch_rnnt_model from rnnt_onnx import export_rnnt_onnx print("Create pytorch model...") pytorch_model = pytorch_rnnt_model() print("Export pytorch model to ONNX...") export_rnnt_onnx(pytorch_model, x, args.onnx_model_path) print("Read MIGX model from ONNX and run...") migx_model = RNNT_MGX(x, onnx_model_path=args.onnx_model_path, fp16=args.fp16) rnnt_decoder = GreedyDecoder(migx_model) _, _, result = rnnt_decoder.run(x.to(torch.float32), out_lens) print("Transcribed Sentence: ", decode_string(result)) print("Ground Truth: ", transcript) ROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/rnnt_data.py000066400000000000000000000031171510465702400240570ustar00rootroot00000000000000import toml import torch import argparse from datasets import load_dataset import numpy as np from preprocessing import AudioPreprocessing def audio_processing(waveform, config_toml='configs/rnnt.toml'): config = toml.load(config_toml) featurizer_config = config['input_eval'] audio_preprocessor = AudioPreprocessing(**featurizer_config) audio_preprocessor.eval() assert waveform.ndim == 1 waveform_length = np.array(waveform.shape[0], dtype=np.int64) waveform = np.expand_dims(waveform, 0) waveform_length = np.expand_dims(waveform_length, 0) with torch.no_grad(): waveform = torch.from_numpy(waveform) waveform_length = torch.from_numpy(waveform_length) feature, feature_length = audio_preprocessor.forward( (waveform, waveform_length)) assert feature.ndim == 3 assert feature_length.ndim == 1 feature = feature.permute(2, 0, 1) return feature, feature_length def librespeech_huggingface(config_toml='configs/rnnt.toml'): ds = load_dataset("hf-internal-testing/librispeech_asr_dummy", "clean", split="validation") waveform = ds[0]["audio"]["array"] transcript = ds[0]["text"] feature, feature_length = audio_processing(waveform, config_toml) return feature, feature_length, transcript.lower() def main(): parser = argparse.ArgumentParser() parser.add_argument("--config_toml", default="configs/rnnt.toml") args = parser.parse_args() return librespeech_huggingface(args.config_toml) if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/rnnt_onnx.py000066400000000000000000000050731510465702400241330ustar00rootroot00000000000000import torch import os def make_directories(dir_path, model_name): if not os.path.exists(f"{dir_path}/{model_name}/"): os.makedirs(f"{dir_path}/{model_name}/") def export_rnnt_onnx(model, x, dir_path): for component in ['rnnt_encoder', 'rnnt_prediction', 'rnnt_joint']: make_directories(dir_path, component) seq_length, batch_size, feature_length = x.shape inp = torch.randn([seq_length, batch_size, feature_length]).to('cuda') feature_length = torch.LongTensor([seq_length]).to('cuda') x_padded, x_lens = model.encoder(inp, feature_length) torch.onnx.export(model.encoder, (inp, feature_length), f"{dir_path}/rnnt_encoder/model.onnx", opset_version=12, input_names=['input', 'feature_length'], output_names=['x_padded', 'x_lens'], dynamic_axes={'input': { 0: 'seq_len', 1: 'batch' }}) symbol = torch.LongTensor([[20]]).to('cuda') hidden = torch.randn([2, batch_size, 320]).to('cuda'), torch.randn([2, batch_size, 320]).to('cuda') g, hidden = model.prediction.forward(symbol, hidden) torch.onnx.export(model.prediction, (symbol, hidden), f"{dir_path}/rnnt_prediction/model.onnx", opset_version=12, input_names=['symbol', 'hidden_in_1', 'hidden_in_2'], output_names=['g', 'hidden_out_1', 'hidden_out_2'], dynamic_axes={ 'symbol': { 0: 'batch' }, 'hidden_in_1': { 1: 'batch' }, 'hidden_in_2': { 1: 'batch' } }) f = torch.randn([batch_size, 1, 1024]).to('cuda') model.joint.forward(f, g).to('cuda') torch.onnx.export(model.joint, (f, g), f"{dir_path}/rnnt_joint/model.onnx", opset_version=12, input_names=['0', '1'], output_names=['result'], dynamic_axes={ '0': { 0: 'batch' }, '1': { 0: 'batch' } }) ROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/rnnt_torch_model.py000066400000000000000000000031041510465702400254410ustar00rootroot00000000000000import toml import torch import argparse from model_separable_rnnt import RNNT def load_and_migrate_checkpoint(ckpt_path): checkpoint = torch.load(ckpt_path, map_location="cpu") migrated_state_dict = {} for key, value in checkpoint['state_dict'].items(): key = key.replace("joint_net", "joint.net") migrated_state_dict[key] = value del migrated_state_dict["audio_preprocessor.featurizer.fb"] del migrated_state_dict["audio_preprocessor.featurizer.window"] return migrated_state_dict def pytorch_rnnt_model(config_toml='configs/rnnt.toml', checkpoint_path='rnnt.pt'): config = toml.load(config_toml) def add_blank_label(labels): if not isinstance(labels, list): raise ValueError("labels must be a list of symbols") labels.append("") return labels dataset_vocab = config['labels']['labels'] rnnt_vocab = add_blank_label(dataset_vocab) featurizer_config = config['input_eval'] model = RNNT(feature_config=featurizer_config, rnnt=config['rnnt'], num_classes=len(rnnt_vocab)) model.load_state_dict(load_and_migrate_checkpoint(checkpoint_path)) model.to('cuda') model.eval() return model def main(): parser = argparse.ArgumentParser() parser.add_argument("--config_toml", default="configs/rnnt.toml") parser.add_argument("--checkpoint_path", default="rnnt.pt") args = parser.parse_args() pytorch_rnnt_model(args.mlcommons_inference_path, args.checkpoint_path) if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/nlp/python_rnnt/torch_requirements.txt000066400000000000000000000000741510465702400262150ustar00rootroot00000000000000--index-url https://download.pytorch.org/whl/rocm6.1/ torch ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/000077500000000000000000000000001510465702400207445ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/000077500000000000000000000000001510465702400223715ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/imagenet_classes.txt000066400000000000000000000270361510465702400264500ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### tench goldfish great white shark tiger shark hammerhead electric ray stingray cock hen ostrich brambling goldfinch house finch junco indigo bunting robin bulbul jay magpie chickadee water ouzel kite bald eagle vulture great grey owl European fire salamander common newt eft spotted salamander axolotl bullfrog tree frog tailed frog loggerhead leatherback turtle mud turtle terrapin box turtle banded gecko common iguana American chameleon whiptail agama frilled lizard alligator lizard Gila monster green lizard African chameleon Komodo dragon African crocodile American alligator triceratops thunder snake ringneck snake hognose snake green snake king snake garter snake water snake vine snake night snake boa constrictor rock python Indian cobra green mamba sea snake horned viper diamondback sidewinder trilobite harvestman scorpion black and gold garden spider barn spider garden spider black widow tarantula wolf spider tick centipede black grouse ptarmigan ruffed grouse prairie chicken peacock quail partridge African grey macaw sulphur-crested cockatoo lorikeet coucal bee eater hornbill hummingbird jacamar toucan drake red-breasted merganser goose black swan tusker echidna platypus wallaby koala wombat jellyfish sea anemone brain coral flatworm nematode conch snail slug sea slug chiton chambered nautilus Dungeness crab rock crab fiddler crab king crab American lobster spiny lobster crayfish hermit crab isopod white stork black stork spoonbill flamingo little blue heron American egret bittern crane limpkin European gallinule American coot bustard ruddy turnstone red-backed sandpiper redshank dowitcher oystercatcher pelican king penguin albatross grey whale killer whale dugong sea lion Chihuahua Japanese spaniel Maltese dog Pekinese Shih-Tzu Blenheim spaniel papillon toy terrier Rhodesian ridgeback Afghan hound basset beagle bloodhound bluetick black-and-tan coonhound Walker hound English foxhound redbone borzoi Irish wolfhound Italian greyhound whippet Ibizan hound Norwegian elkhound otterhound Saluki Scottish deerhound Weimaraner Staffordshire bullterrier American Staffordshire terrier Bedlington terrier Border terrier Kerry blue terrier Irish terrier Norfolk terrier Norwich terrier Yorkshire terrier wire-haired fox terrier Lakeland terrier Sealyham terrier Airedale cairn Australian terrier Dandie Dinmont Boston bull miniature schnauzer giant schnauzer standard schnauzer Scotch terrier Tibetan terrier silky terrier soft-coated wheaten terrier West Highland white terrier Lhasa flat-coated retriever curly-coated retriever golden retriever Labrador retriever Chesapeake Bay retriever German short-haired pointer vizsla English setter Irish setter Gordon setter Brittany spaniel clumber English springer Welsh springer spaniel cocker spaniel Sussex spaniel Irish water spaniel kuvasz schipperke groenendael malinois briard kelpie komondor Old English sheepdog Shetland sheepdog collie Border collie Bouvier des Flandres Rottweiler German shepherd Doberman miniature pinscher Greater Swiss Mountain dog Bernese mountain dog Appenzeller EntleBucher boxer bull mastiff Tibetan mastiff French bulldog Great Dane Saint Bernard Eskimo dog malamute Siberian husky dalmatian affenpinscher basenji pug Leonberg Newfoundland Great Pyrenees Samoyed Pomeranian chow keeshond Brabancon griffon Pembroke Cardigan toy poodle miniature poodle standard poodle Mexican hairless timber wolf white wolf red wolf coyote dingo dhole African hunting dog hyena red fox kit fox Arctic fox grey fox tabby tiger cat Persian cat Siamese cat Egyptian cat cougar lynx leopard snow leopard jaguar lion tiger cheetah brown bear American black bear ice bear sloth bear mongoose meerkat tiger beetle ladybug ground beetle long-horned beetle leaf beetle dung beetle rhinoceros beetle weevil fly bee ant grasshopper cricket walking stick cockroach mantis cicada leafhopper lacewing dragonfly damselfly admiral ringlet monarch cabbage butterfly sulphur butterfly lycaenid starfish sea urchin sea cucumber wood rabbit hare Angora hamster porcupine fox squirrel marmot beaver guinea pig sorrel zebra hog wild boar warthog hippopotamus ox water buffalo bison ram bighorn ibex hartebeest impala gazelle Arabian camel llama weasel mink polecat black-footed ferret otter skunk badger armadillo three-toed sloth orangutan gorilla chimpanzee gibbon siamang guenon patas baboon macaque langur colobus proboscis monkey marmoset capuchin howler monkey titi spider monkey squirrel monkey Madagascar cat indri Indian elephant African elephant lesser panda giant panda barracouta eel coho rock beauty anemone fish sturgeon gar lionfish puffer abacus abaya academic gown accordion acoustic guitar aircraft carrier airliner airship altar ambulance amphibian analog clock apiary apron ashcan assault rifle backpack bakery balance beam balloon ballpoint Band Aid banjo bannister barbell barber chair barbershop barn barometer barrel barrow baseball basketball bassinet bassoon bathing cap bath towel bathtub beach wagon beacon beaker bearskin beer bottle beer glass bell cote bib bicycle-built-for-two bikini binder binoculars birdhouse boathouse bobsled bolo tie bonnet bookcase bookshop bottlecap bow bow tie brass brassiere breakwater breastplate broom bucket buckle bulletproof vest bullet train butcher shop cab caldron candle cannon canoe can opener cardigan car mirror carousel carpenter's kit carton car wheel cash machine cassette cassette player castle catamaran CD player cello cellular telephone chain chainlink fence chain mail chain saw chest chiffonier chime china cabinet Christmas stocking church cinema cleaver cliff dwelling cloak clog cocktail shaker coffee mug coffeepot coil combination lock computer keyboard confectionery container ship convertible corkscrew cornet cowboy boot cowboy hat cradle crane crash helmet crate crib Crock Pot croquet ball crutch cuirass dam desk desktop computer dial telephone diaper digital clock digital watch dining table dishrag dishwasher disk brake dock dogsled dome doormat drilling platform drum drumstick dumbbell Dutch oven electric fan electric guitar electric locomotive entertainment center envelope espresso maker face powder feather boa file fireboat fire engine fire screen flagpole flute folding chair football helmet forklift fountain fountain pen four-poster freight car French horn frying pan fur coat garbage truck gasmask gas pump goblet go-kart golf ball golfcart gondola gong gown grand piano greenhouse grille grocery store guillotine hair slide hair spray half track hammer hamper hand blower hand-held computer handkerchief hard disc harmonica harp harvester hatchet holster home theater honeycomb hook hoopskirt horizontal bar horse cart hourglass iPod iron jack-o'-lantern jean jeep jersey jigsaw puzzle jinrikisha joystick kimono knee pad knot lab coat ladle lampshade laptop lawn mower lens cap letter opener library lifeboat lighter limousine liner lipstick Loafer lotion loudspeaker loupe lumbermill magnetic compass mailbag mailbox maillot maillot manhole cover maraca marimba mask matchstick maypole maze measuring cup medicine chest megalith microphone microwave military uniform milk can minibus miniskirt minivan missile mitten mixing bowl mobile home Model T modem monastery monitor moped mortar mortarboard mosque mosquito net motor scooter mountain bike mountain tent mouse mousetrap moving van muzzle nail neck brace necklace nipple notebook obelisk oboe ocarina odometer oil filter organ oscilloscope overskirt oxcart oxygen mask packet paddle paddlewheel padlock paintbrush pajama palace panpipe paper towel parachute parallel bars park bench parking meter passenger car patio pay-phone pedestal pencil box pencil sharpener perfume Petri dish photocopier pick pickelhaube picket fence pickup pier piggy bank pill bottle pillow ping-pong ball pinwheel pirate pitcher plane planetarium plastic bag plate rack plow plunger Polaroid camera pole police van poncho pool table pop bottle pot potter's wheel power drill prayer rug printer prison projectile projector puck punching bag purse quill quilt racer racket radiator radio radio telescope rain barrel recreational vehicle reel reflex camera refrigerator remote control restaurant revolver rifle rocking chair rotisserie rubber eraser rugby ball rule running shoe safe safety pin saltshaker sandal sarong sax scabbard scale school bus schooner scoreboard screen screw screwdriver seat belt sewing machine shield shoe shop shoji shopping basket shopping cart shovel shower cap shower curtain ski ski mask sleeping bag slide rule sliding door slot snorkel snowmobile snowplow soap dispenser soccer ball sock solar dish sombrero soup bowl space bar space heater space shuttle spatula speedboat spider web spindle sports car spotlight stage steam locomotive steel arch bridge steel drum stethoscope stole stone wall stopwatch stove strainer streetcar stretcher studio couch stupa submarine suit sundial sunglass sunglasses sunscreen suspension bridge swab sweatshirt swimming trunks swing switch syringe table lamp tank tape player teapot teddy television tennis ball thatch theater curtain thimble thresher throne tile roof toaster tobacco shop toilet seat torch totem pole tow truck toyshop tractor trailer truck tray trench coat tricycle trimaran tripod triumphal arch trolleybus trombone tub turnstile typewriter keyboard umbrella unicycle upright vacuum vase vault velvet vending machine vestment viaduct violin volleyball waffle iron wall clock wallet wardrobe warplane washbasin washer water bottle water jug water tower whiskey jug whistle wig window screen window shade Windsor tie wine bottle wing wok wooden spoon wool worm fence wreck yawl yurt web site comic book crossword puzzle street sign traffic light book jacket menu plate guacamole consomme hot pot trifle ice cream ice lolly French loaf bagel pretzel cheeseburger hotdog mashed potato head cabbage broccoli cauliflower zucchini spaghetti squash acorn squash butternut squash cucumber artichoke bell pepper cardoon mushroom Granny Smith strawberry orange lemon fig pineapple banana jackfruit custard apple pomegranate hay carbonara chocolate sauce dough meat loaf pizza potpie burrito red wine espresso cup eggnog alp bubble cliff coral reef geyser lakeside promontory sandbar seashore valley volcano ballplayer groom scuba diver rapeseed daisy yellow lady's slipper corn acorn hip buckeye coral fungus agaric gyromitra stinkhorn earthstar hen-of-the-woods bolete ear toilet tissueROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/images/000077500000000000000000000000001510465702400236365ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/images/bird.jpg000066400000000000000000000657251510465702400252770ustar00rootroot00000000000000ÿØÿàJFIFHHÿá(ˆExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:22:13 Ðè Ànt 'ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ïµCˆmÛÒæ/ý UɆëyªš§ªô0}%ŒþL*÷Þõ®¨‹L}úe«ƒÑ)ý),8šðùìj ‰Ðtüõèýò*[#þ™|=%ú¦ä8Õ ÷ª¯‰?ä‡Òhÿô!Vf8Õm}Õ¿•Tñ9ÆŽO¤¨ñáMn‰fà<ÕM%³§Gõ#õ«C­TÒð,€™‡ëL ‡ö´£þ™çQê'›_úîŸÎ„ÿÌ¿õÄ:MGþ]¿ëº1@»Ö~ˆâA§×´ú«ÕCDÿÿ^Ñÿè"€ü….þ‰ü…Ç÷¶ŸõØQü„®ÿà?ÈR_s5§ýuÄ]Só ÏÐŽtz˜þ‚¯€O¥RÑxдð;[Çÿ Š´9Õ/ýŠütU??—á‹¶Î1·ŸÄUË?ùjþš'þ€+3Ç„¯y$ ~tÖä½+”ùvì80ý1[ B¢Ž˜8çµfKngß gseF=Û´ÙÆ=8­™šÁELž¿Ê©ß¶ëÙõãÜÕÌþòqœûÖn¦Åt9ÿ¼àëÞ„6a넯‡tè‚ÎìxïÉ©5IÅ¥œq'”?)úŒÑà šº"G”„÷¨ÿ¿øþ‚)/OúE ÿ¦¿Ò’ÿ+Áþçþ‚(»æîÌÓCü©ˆµ+b>Šj 0mÒít¨ý*Kƒ‹iOûùSlF,-Ǥkü¨2Ëþ?/ý4ú¬Ïs :Žrz}9­;/øø¼?ô×úVOüJq÷œÒ©nKØàlcÅÞ쌮[ÿÍYaÁqüqŸåUìÛ8£Æ}r*lâ4¹úÖ¦hˆnÇÈ›€'Û5‘ªÊéñùƒåi2£ûÄUû™(%œœ‡v1Ôí¬-QžK 9å‘‹Óšie/£¦»i  DàõíRøÀ¯4gª¨4í`nñݼ`*Ê0y¨¼HLÞ+¸¼˜ûÖˆƒÝ5ߦܧ÷¢aúTöíºÞ6õPj)ÎëyÕH¨ôé7iÖÍœæ%?¥pt:ÃJùa?»s/þ†jHøÕæ±Pé퇺_I˜þfœ¬F²Þ†ñêb&¼ÿ›3ž’J¥â±»ÃWƒý‘üêÍë~òÔ礢«øŒ†Ðn‡û4â Ü<úÕ='‹YAís7þ†jhßä_¥TÓÜŸé2ÿèf€%_ù?ý{ý —Péoÿ]ÓÿBñý²Oý;ÿìÔjÅ¿ýwOýS럑¾•OH?ñ%±ÿ® ÿ ŠžGùéU4§ÿ‰=ŸýqOä(öÇþ&—ßTÿÐE‡ý6ÄÓFÿÐMGlÿé÷§ý¤ÿÐE6éÁ¿±í?þ‚hr퀲œú!þTëQ‹X‡¢åU/䯛tsÒ&þUbÛ@(a÷GÖf¬_6,-ë·?•jiïÅÉÏü·ýÖ'Œæ"Òßþ5QÜOc‹±ù]’FÁŸû⦼¹ŽÚ œ.œqUb™míd‘ñ…AÓýÞi¾[MæÉ7Ü1¡T#5©˜äµs-ôÒ6íTLöÝŠ¥­)y´¨÷¸t²kVrÛˆqÃÍY×l_ÄÚ,`œ*DyéÐU-ÄfÅn×?„ fcxuôª2fçÅD)Éið1Þµü>RïâeÄÀ,Ьì ç8¬ E׉á/–\œ}kBOsûm³øø‹ï «¤ÝºM¢<ÈaU °ã¡þÃÒÿçÂßþýНa£éÒZ+=œ,Üä”Á¥Ž­M;ÈÅÞg`#攦î«#ùÑãÉ#ïZÍ·ÑôÓyuYÄB²íG¨éN}MÑ'ØáÚTœmÍ=SFöò6ø™&SÃZ‡]»…ôk„!%pê«w¤iшJÚD3*©ÂŽA LÕ4›4Ù¤ŽÖ$d(âš°Íèïí¼µÿH‹§÷…V²¿¶Q87ç9å‡rj¤.™äF~Ãv஦³ÏºÊÄ„  8h=KÇQµþÖ ö˜±äc;Ç]Ô—ú¡u >ro2*›i:oö‚§Ø­ÊùdíòÆ3ž´Ëí'MHâ)al¹š58ˆr E SZMNÏËoô¸z|U{JÍtëu7QecPrãÒ }#LØâ_kÓþy/øTvE„Ö–ù±·y’c“Š4 Köó(žæ]êRF]¤¿( åì1^[4“**–ÉcŽÕµe¢¬™Øãl`p£Ò®4pÉFñ‘‚ ×LKƒÚ褮rzŽ­e&™uwQ3´,'dkzr¨ÿMƒë¼Sµ}Á¬æ6ÖpÚqµ?…VþϰÿŸ;ûö+¦•XÔÑ-4ÆØëZ|PÌd¼…IšFùœ åXÞ)Õl¯ _"á%òÆã±³·žø­+;+6·ËÚÂNæÆc3ÅcøŠÚÞ6Äè‡ÊÉØï[­È{Ño;ç*¤øcš³9[ $|ØëDjGŸ¸‘ò9ÇÓ5#æYN_ƒy·pÞµd^Ê7lóíÈU!G¬ÒVp XôÀþTNÿñ'v,ŸñõϯÝþUÇgŽõ 2§’²AÀàvªBf_ƒ]‡ˆõ+­ÄypÊÇCPx>1.¾ŒÍ€ ¶sŠ›ÂýÄ~Yb¶äoãåÝ‘NðT‚ ›ÉøÊ[¶ÜúàÖˆ“Ø!mð£z€j 4ÿ¢°ô‘Ç䯒Å÷Ø[¶zƧô¤ÓŽuôÿV5ÀuÂâcqîþ”“j6þá¿•63R_xÁ¢rF¡h}KütÓÚƒmŠÿMãÿÐ…7X?ñ)¹ÿpѨ‘öu' ‘þ<)º±Ù7Yéå·ò¡.[6mb>¨?•EiÄ×_õҖͳeõŒ*ŽÐ‘quŸùè?•=Û”cÖ3LÔîböš?ýQ!Ú1¸išýÂÿ×TÿÐ…1œüô©´!‹($9À•QšåÖóHü*ަ·4Û7ƒGH‡íãéS-‚êæ<Úü³\4hJÅÓŽµ“§iZ½¬-·o0gÈrsÅ_ÐÜ'ræ¶ša`¼õZp|¶ÜîÅF…“¦gMǦÎåðå;}* Õ›¨^ÊÒʨqt_ÌW7W¡šº8¤È¬_ý HîIýk^|ÌïîqúÖ–žÙÓ n™@k\9¸›¦D5¬w3{²©g$ŠI ¯€Çê8§"õImŠo±¹3ÇÒ ¸]–»–`}>õYvî,±eIžNœœZg*™tûxKæÜîÁúb“N—wˆ±úÒÞq-±ÿ¦¢€Q8³cèAýiuNt«¾å‹*f¨véÓE&Ÿƒ§ÜƒÓËoåM§é–§þ™/ò¦Ûô»Áèëÿ ŠGlè–'<˜ÿA@Ø¿¼êñÑ@‡ÌâaÑ¿•VÖ.D6™çvàGàjY›ý: ú7ò¬=Vñ/®V v)Ásý)¥v&È Ôá…$7(Í3œ‰äƒÛ±á¿Íö¿.啾îkžàä’pGzôèBÖ3uul½Q›°­V êá¹1°æ³o¬)ÃŽ•ËL/aÊG+`©ã'Ö¬Mp¡ã©®:”‘jG¨À!؃¼ñŸÉ…Y/„cè*¶¥ {ˆ²娧Jßèòsü&·Š²H±4ÛuJåX:Ô‹ö뜶 Àã½o[œZEþàþUÎj¬Måþ>HqÜ™l3»pNÝÒôî~zˆrqì*”ºÈ6/u *ª„ëYþ$¿I5u]›–Æ}Ï5q«!x‚©d|£¹ô¨iX¸EÉÙ—:ÕÌòƒ‘#ga ûÔßð‘“jñÌ™fR2+'\–g×&yQP²•{V\Òþìã׬ﮇ­4åI¶µ=* ­be`T¨ ŽõÌê’7Ú¯€p\ÖÒáÒ-’L† 2jçõYAžû$c‘ÏÖ¶†ç•QYØ–7`Àà‘æç§û­A#âÍÁØá`\{gúŠWá÷(!·néŽÙÅW™ÿÑdÁ,<˜ÆsÓ§¡éåòÚf Ä-–ÃÇL¦1XŒaðTDd¸$|¿‡ZÕ¾“÷:›íyÅcê i°¶6’ÍÄÕ! âkÇ]/G$Ë€dqÁ"¦ñ ÷VþÑã.1îUÇLóK­A÷¶‘´ l·@G¯®ßØG,V‘Üo`©‰ã"©j_k.¦.nBÅ!ÆrÞ¢§ŽmMu#þ sMý³T¼3,’ý§Ìbß2ò~€VÐãTCë­r‘Mqª}ªÕ¤9 qûμSµ PÚ’öq‰= [¹?½µoIëNÕdçÑI¤iîuVÕ¬#Á8”Uh®µ&°I´Æ0|ÑÓ³)Ìl=EQÓ›ÍÒm?z?¥ š5Ýúé0,vjèï"¦[Ëá!ûݰdoºgE·Ç¡þt—wBÚægÈ å|¾ç4ú‹¡߉o#¸˜Ø­…Îqþ½UðÌ77Ú©1³*6T•íX¬ey„y9a­wþÒþ̯pKÙ};Ö«Dg¹Ø¶öéã 1Y:ÞÈö©Ë·S¯ï’ <ŸN¦²|ýŒfœüç¢úTI”‘RúÖÝ¢ÚÊ3Ôµs6Cyq$páY%^ ÆkWP–ká4pòI•»(ô¬ˆã¸·±Šè˜¼òqÕžj.¶}Md—21õ}>ò=VHP<À±ÀÉ&¥ŠÚ;{6éó´˜áÝxS[Œ¥n-w¶X±õ;MK{ÿ§ÜúÕ$Š•Y[•lBugãýïðŒ×:×+qsrYY îù¯â+¯' +…¹mÚ£s“–Å\Ld]f^rŽü ¹,¶î¿)Qµ3œž69s,¿sÿeþtŽì͵³ƒ(ÉÇ9« ‡Rdû çï~uRîug§Ä¨w&™¨ÈÇM—Ò Œõ«Hà<`©R°(ÁúRnÈßMT¨¢ËÚ††o.Zí.DcÎ:Sîm§iËEsn»€éõ©$ýÔ$Eœ2ò§Åsmª½²³Qžõ>Õ«ñË ÜSoUs£Ò¬ÅÔ‘u-cü«AÎ5sê~•\jŠ}bÇëRNؽ³oö˜ûäÖGžY¾8[vôž1ù°ºÇÍ£^×É|cèj-@ÿ£)þì¨ß“ –ûæ°¸_XØ~”l¶Fk;LiVƒ="QúU˜Ÿt}T­¦Ÿø—Â= «  hñŽÁ˜~LjRÆInàr_;öQÜÖ…-Œö mÊ$¯Ÿ˜ñ[3ho©¤²‘ĸzŠiûÄ=ŽNMaj—q+3Dá˜cµÖiÓËo¥'›F9$‚rx·3oahı¨ËZåu=dKºV8Çú´¥*èDÍ‹YuÔn^ðÀâ1ÙG ª“6§«\¬V8WqœžËëZP[Áqmr@ìrìzÓ,l×J‚î{kÒá£Û½‡Ü{v§º)EØ[˜,æÓ|ÀcRLÓwæ±­¦kMòÛ’1…öZi®ÅÙ°ñ Å”äõ«u·Ù$–2rxÉü)F-6äUi©É(+E¤?éVÿð/åN½?¸Oyã¢fÿK‹è•­”ˆÓdÿÐ…Q²~q\ :µþsœçŒðMwe°Ùö®y’7$ù€pqWdY`l2 ƒŸJdí¶M¼nNN0NjAµ‘‚ö c=9ª×$»)aœ±íןJ² ýEÙ}wH*ÒÍö‹ÄGíU¤•Wlr±ŽŽõ!ˆ-Ûͽ‹îÀ sŠVº4…WMÝTºV© ‚Þ=œÂ«¶B8ÁéKý‡ª´àiw,Œ»ƒì8#ÖŸ©x¶]KG‡M6ÉP±‰UŽp¸íøU¿øMM0Eýp ç>¸®'BŽÊ÷þºž„3s][kÝñ¨@sÔ0ý*[§[f'¤€~uJâ@·6ÏèÇùUÍRÛ$ö¶p;èQ¹ç¹Xè5 Óå`rgò«¶`uõS\l×Éu‘Åpѳ©\1à×Mot.­RLcpäzâ‡erÍœ›¬`9ëŸÒ›§dÛ*¯'q~5OJ”&О¾Jçò­ ¤ï4ÜbW ûÆ¥”tÚM„zmŠÛÆ=KRy5vYҙ݂¢ŽI¬«=AÚÕî.”¤ª»Ú©Cpú¾¹Ù‚S±úÔ$É ji.,#š\¢ÎÛaŒñò÷'ð®nõªÒ…ùU†ßζuû¦Ô5¯&Ü-ޤ÷¬«:îúf³·pª£ Ý@¦œV¬¥5î«”/µ9®]­l9vùw/z¹dÑh¶2‡kæNT íŸÃK$aÒYä\–=Vª[»—æ–Pî‚)ÊwK”Ú„i¥)Tz®…¸- I Ð"¬n71žÕ6.î?à?ÊcòZìÏÝv­B§ý*ãê¿Ê¬Å»»’3¦Gþ饹l˜Gý4_çP“þ˜ŸîYÛç‡þº ‹r6já†ßœpÙTbT÷SÜSšå“ˆE^*V; çùb#þz\ðWŸÌ‘y2Ç5§wÀ‰I¸È)ä{ŠÉ’Kqå¤JÞ`6î™ö®>†¥iFã>Õ×è1Ñá-üY?­s^^è¾ažx­»Q¨ý–-“A³`À*xªlIYššsm±‰}*Ä7†ãvဎPè+ÉïÄ$„€ì:Æ–ÙïƒLˆüç56·o©Ë~Óoác¢¨ìoéò®Ÿ¤Üßœ Ú‡üûצI{æ]„Ž2|öÏ=ó]mé7RÚiJØXÔqëÞ¢£²Ô‡FÔà³°Ô®œp T=ÆGÿ^§ÑåœhװǹËjåuKÔH|˜—‚~èï[’jm§x^-2!™6æfô'’)Ô§•Ípõ§88C¯S–ºšûR¹“‚wrÏé[6*‰gÑuúUT{¸ *¶`Œu9¦Z]\ HGÙ[ õ«ècË·Ê[¶oÝ?ýtoæjÏúEÏûËÿ Š¯ow(‰³m'ßnƒÞ¡Ží¼ëƒöiyaÛÚ‡rîïôÕÿsúÒÎß½ƒýú£öÂ/0J0ŸÝ÷¥žù|èsƒ Ý}¨°®_ºôyOûùW ܽO%»WOy©Â-e|”?Âk–€ç`÷5q&F†ÿ˜üÛóŸÆ©ÌܧO»ÛéSƒœgƒž¿…TsŒxÛÏ5D”§`/­Ï(&­ÙÆ\ ä–9ôªµ©'?»8­;2Þ~÷’ÍÒ¶¢DÊðN±\I;mn¹ç5§&£k2‡„²&8>•‚ÅM¬ÎW$¹ôæ›§–LôÅUI;ÉyŠ+DzìŠ`1ž­ÐVd¿¾˜K3@Trݱ$—ëÆj©”¸WFìµ,À )A[šdÂKÈ9ÀÅaYéWè$D$õÇ&·l!u¶ ‘ˆÀ†“Ifp²újçÿ4ëvýôãý±ü©¶°Ê^e±!Ïj|Mö»•òœœ©Æ=©XcôëÆo»Åó§µóÛÉ%ÞNæÉ¤´í-5À¬“Ë€;ã½A©Á(°án/j\y¥¨¥&£t[Ѭ­.t›Ûûã;SÛŠ¦.Y’feëóW|?dãK{i‰W3e×éU/ ˜\^I°ùl‡njSåìuÝÑ ¡ÕþEÉä?J­hСÿp*¸öÓl'Ê|cûµVÊÞw±€¬NFÅè=ªìs[ŸÜŸ÷ÛùÔ1ŸßOþðþUrÖÆêHNËyæ=úÔqi×<à[ÉÃ#oµ;1\«Ÿôïûgýigoô‹~{ŸåVwöò¾D›¼¾˜÷¦ÜX\‹»uhX¶ÔY…ÑNý¿Ðæÿt×! èxÈ?vºžŸu< ªrMq0&P3ëWb$Ë<8‘ý* pûƒ¯ÒŸ¸ìëÆ;ý*æ|æ¨EÓrx N>•¤å’ÞÝA8ò‡ò¬’Ò&b a8éZWD#íkü«¢‡Ä½QMŒì—²àä³t¬YÈÙ]yô5 ¯Ío>ð9æ¯Äw1flaºcXÊWl¢ÉrÇqéZº›^Þù˜ýÊ}âGSè*ž—¦Ï«N#BRpòc]–‘lcÒàXö®g¶N9¬ms]‚Èì´)b1K`˜ŽÞsÿèFŸd’™F Yuô&h’™DlHœïE€[e uuÎáŸÊ–5ø8!•Gå½¹Ê0$©éì)ќ߿û ššr’N#^p9üzÕ¤{xõ8 ¸ç§"²acý±t2áX¤ÔÐ\¤@2‘Ú’ÓPܽ­Þ}ƒYº’ùY·œ~µ¥-ތۈ;Q÷gñ«)E·¼`¿u׊ϖÕ,tØÄ.\L„œûÔFܪk©º”«MÆ}ätül#¥SÓqö ~3û¥þT±MçBsÁ±ªúqaalsÿ,—ùV¦Ý9Õ!¾ãÐûÒEr\\gvƒUl‹žäή$œƒÕéÜDÿ)ÔÛ®cùÔ7;Mü^€ŸÒ£Yß¹Ï!@¤[íÑäó°š­¯O縸%CΊØ;ý+®ñ íÒ&ù¹ô®?J!n"cÓŠ¤KxžUÄ€ ¹ÉØôª3Ò9?­lj‘I$\“ò”DüvçšÇ”®Æ'®zÐy ÜL‘ü U»›ƒ$s;vÿJ¥<éqýì –àâÎRzœŠé¡µÿ­Œ¦«J€œ“Š·jKª’g“š¨¬ˆ <Å8+Ö®Y9HF@ÆÞý«™š‘¤Éäi–ñª(5Žø©tùÊÛ¨Àà•ýk*¼…5±ùxÀn)Ö×7#+ÛIìxô'" ³JÑÈóyÏïœþlM>Ú]·WïåYÞL.lçùœœâ›ã ¹d{I†H*J8ÅkYŸ²ßM´±Ýƒó6M>k€oÎâFè±É÷Åe.¤¢í˜Ç*‚£øM=µ(>Ò®wŒ.3´Ð¡(üÍ´a^ O©hc(ªxè1X§U¶¹rä3´úÕɵ{Y< $é*çƒëIŽ.Îå fîIâgs¹À­¿ðôlAQ{qøV…®i,z•Ù`Å„^ÃŽµý£V’[¶à’zVRz(ÅЦÕ9Õ“Ôèbµ—xfòj¶•ý.Õƒã÷KÁÒ ´Ô ·Ž8àoÍÅ.•i¶ Ê™X”[Ú·Ðç-éöŒöÊC¯~¼Sm¬¥y'ÚPâCüCÖ2þnƒÌ]ØÏ^ÔûKÅß1ÿ-[¿^iè+‰´Âþ}°‚@œqPIÈÔ€0>áGõ*܃{sèvÒݨÔC dE§4YÌ? SJ“̔ԌW'¦àȤBk³ñ癡ùa‰ù‡zã´¸|ÅcЀ~'V$Ôh^ãCØIVVߟUW92‡<ä×Ug6Í6E+ÿ,eÁ>áÎê–rXÝÉ ™b­×ÔzÐ4p~ù|æ‹ Ùˆ<±üél”ȃԓL¹oÜ"ãø…tS~çÞg-Æ^^0xùkIX‹9ØB;VkÄÒÝ…S÷qœš·+ºY>!¸&¹ßc[;\ëW[œŒu✚´«± žZ±Æ€™ùoá4áá×#+u ¨Ðz›ÛR(åüjD×óÕGçXC׫þ®HÏ=š¥>Õ‡M¤{IF©·ý¼giü H¾ rcÒ¹öÑutêðzC§jê?ÕÊÑdfËkhÚœr0#÷eOõ|kvî„KcŒ­r& N'PÖÏ’p3sZzE¦¡qv"{R>©ŠNÉ\¨¦Ý‘Øè°É…î òad á}ë’½/>ØmÔ¹?(É­›¹¤X~Ï#‘œØš§áÆÚ²Ý,0ƒ¸šŠq’¼¥Ô‰Ê3Ÿ,6FŠO¦”U›ÉÞx¥WÑòE¨P8 ¨®WSÔ]ɱ\“Ö© å=céèkD9hìwK—³jù} ˆÛiª6¢. Ï \X»?q©âêî6=±N¹֭¦ž®Ø·7'i¦ÖÑdߺD'å9®OípžŠÙ£íñ¯GaëÖBæ·‰ Ž+d‘Ø“Ž[5Ÿ¢™A3Ïû©Ï)¼RRXvlÕ­=VÚá8b9ÕB6D™¶p,Oÿ šÎñj³j3ËŸ+xN;ž¸­Qc[°OÌQ×?F"“YØÚ ÄŒyöÒ¯JàíHHÐ’G_ºj;ŽLA½œT—¡€/ ¸*³`Þ`FzÕ©Ú6%Ç[ŽÞRéÌr”üzÒÜ;U•ëŠÀk‚G¯jl¤<ƒw£©w{§™4ᡌí~ê)Ðip'Jð÷µM “=Âo`„«lŒã­,pÿ¥\ ç;OéY”@º]±¹™L ôþTÔÓ->ØèлAÎzU…R/9å3Å*ô³Ž†0ZbiV†ób£ò÷`9ëšyÒã ŠòS8 zÓ”“~ý^?Z•™–ö0xùH F}öœêËÊ»¥ÇÞö­&mx<¹$šW!cr7ÎÔx–̓`‰sÏÐÖ‡†n.òIÌ%Â>c'¦j*[—ÞØ¸ùn]×4´Ë%iegžcÉÍb½´ú`”$=yëõ«>$¿šòìùÄîŒ`¨è+ íiŽhÛ$Æ3ëŽ+>YI¦¶7uiª<‘ÜÓžÖáayq“‚¹¨Ÿrñ†ia$Œbµ.9²“ýÃü¨‹#€+ å0¡Ó¤ž(äh­Xº†ÿT;ÒE¦™cÜlíÎOM V½Šÿ Áÿ\Çò¥±RmÔž3Í1‰¥ ƒí°·!X½Ž”Á¤BÓ:¶˜I\²CÜgÖº )8ÿZøüÍ$Rº¸oV_åLFöÎ+[¨ŒVrA‘ÕŽsU ÌÌFÖò­J~× AÚÂ°ç •'Ôf˜ŽŠL›5ºßï*khR÷Q‚ÌrÞ1ö=i–Ëæ[D‡•%Y3ShøÝ´j>äÎÁ½zÐ ªY q,xÌÑLQˆôÿõÖZŸô¦+’ÙÀ®ZIZâ[Œ~îy§àÕË[±726 <(c§$óÍ 3œsÐÔvmºa»‘ZºE¤wº‘I—r`’3Šq‹nÈR’JìÿÙÿâ°ICC_PROFILE lcms0mntrRGB XYZ ç acspAPPLöÖÓ-lcms desc @cprt`6wtpt˜chad¬,rXYZØbXYZìgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ öÖÓ-sf32 BÞÿÿó%“ýÿÿû¡ÿÿý¢ÜÀnXYZ o 8õXYZ $Ÿ„¶ÄXYZ b—·‡Ùparaffò§ YÐ [chrm£×T|LÍ™š&g\mluc enUSGIMPmluc enUSsRGBÿÛC     ÿÛC   ÿÂà¨ÿÄÿÄÿÚ ê~_t¡  Ð7'&æF €€h IÂÅéŸ4Ö-Ók1@í}7ä÷ ¥‚pÔjÅ?m<&¹¬.™áu“S¬^““ôÇ‘è³ÕŽá| æ !‚ÔWœšF¯ŸLueè‰$ô'4©óœ ôTOgBó~‚y½__ÌÎ"A£tμ‚r®{¥{[«nš¹ìæÕž'œtb`SËçMƒ3-¶¥ºó|Ñ3Nï –ç*Þ;|Æ=¹÷‡6sH×;ßž]G^wB+ë<[eng©Úá¹è²H°®ž˜¸z𾟑h´mÚ‰®y¾'"bõäOVWü½Z/ZuIq¦— ãkoËyÞ{îK#·ˆ¶ŠA Ì6øŠa§Nsub'ÝGƒªŠä¤hb¤2çŠêýîOAwòà`¬€^emN_\ä ”çêÌNŒcâÛuÉЭ“ÐË{d±²ç÷«º¹½:€/«*ý&׃BòßVžŒcÀŒœÚÀœþˆ›rÙ^JÔô¢(Î3qV8O߀˜Õ-ì3ÛpÇ!¡0 ‚€ÇµìXmæ¹j1xyªŒÍÈ)Ðý.`ø¶³ÅõÜöxJ @ 3Õæ:OPîï6fHÒ4*2mdZ,1ˆ_Wš«Îè·Ìé¹iáÌ q„*­y»2}—WœÌ{Q2&³-Vë9Œ‰éò×ùÝ¥¢þ„Ðs$Uk:n8f“º+X¬”H zE2ÞŒl™]á¸Z ²c¬áæ¤ ¼/y·ž¶­‰§ìäMj1A@ ðÐ2™ìã-áXa³'z]ynµ†4(+Xö¨YªdAÊ3-uÇÿÄ+!"$#423%&15DÿÚkì9:Qï¨ÇUÇ;–ÂOùIÏŸ.ôû}cÿ ñR|ŸZ[A|zÖ5!í¦,÷•1í~Ù¯‘1åþjΩà§ü¥ì_vß}¯°ï×cë‡øãþºÛÖ½apî¶œt‹¾Û˜è'J=õ_ÈGu1³®?:âýÇ>m¾û_aßàÜ訿š?]OßÕýH 9s=¬–*<´ëŒõmeÿ¯’1ìFà/:‡Ñi‹Ru(§ý}æ¾Ãóã»:%_úGë)ǪçÌ_ë4ooF‡¶¹Iܾb`™—§Äs¹8ÀFú¤ŒøØNÜz–ùÇ?èÚÞs3ä?:ƒ#oóâÜ#o;vú¢Ñ/ó`+òçnPž&B›ßoÈõSóà’w [ê¶>tA+|›h˜-óÖþ}¯æžß3s¸oßTù±Âð‘4r½HX÷ƒMºÎVúãËOùZ>W©KŠD^dß%œ31Š#J¡i¡©š¥j+[Jƒ¢íߺÖÐÑ}´Ý·5Žíí},¾gâWõZz­=øÙïºë^ÔEë_N¡G(¢lÆ@eßqîÆP ˜´mµ¾KÎÐÚwe…zWÓ¶(éäçJ'<+=Ë`t>>~}|ÌΛñˆCbñÞÊùv9I«j,,¥¨#\ºUXÉÞwÎý,[|s}®È¦pŒ)sâ—zÑe_šÝ|†Ò#=rQQø£‘¯¹3=ý5Ìv/}üÜ2 Œ¿0uCq~QAÔv‹Å¦t/wmT\}è}˜BØ´[¡Ãö)>]gÍZty“òM­ÌOO«ìÑi½lË4ˆ±Jåïs.Çð$éQ[BZßí©W¿@ÛmpíÇ‚(gʼÚýÿþÕÿôr*ÍÉcEZ9–J™)…¨©Å9[ù&ýZŸ…‰øƒ=Ó®þáß–1¬ b*ôŸ0s£Õ×G5ó1À“富£uÚ"“•â(Zæ§A^T–xfu£SãŠvÖÓÅç Ê6(Ó "?R0oÑÉcc#n…voænóCF\€ñ•ø.ã5S‚6•«°vg¼³Á§£“ã õµ¢8·ýô¬É-½ Év\ŽeWíîïo0w­ µ ññÍvš;aÑEÖ÷ËÓN]Ø·ÈYê^¶ŠŠÝ··S5ôšKÊðáíh$rø+F­¬šÏSÜUáþFO·ÑåUsq”"€±ZÜÆ¤…®â’5È’=˜g‹Zxbt ÑÚÄùK0RpÜÆÈs]4ü„Ÿ”}ÙR9Êmt ËPK#˜jWy‡NaD=æ÷d@*¬)âg†gA//¸ŠW¡X›M‰ÀP%$µ·ù¥„óT¿´Ä‚߯8¯( Ì2†æCs¡y™uŠ4i-nÏÄ9ÚÚÕî¶é㥡f£Q1 Ž xã˜5É—sÛ2lÏ Ìo¾œÃZ.BÅjFtçz€ºã‘6–žŒþƒ$í¯wîá,5ØðX§ÀämÅzՊϽv›‡žµ=Ñiíœ4ůé~…&²cWçõ–:vÝ¡ò®ÜîšÎ“[EF´í¢öñwó`š&kê i 6ŸpÏx™X§âÄ¥hÎÙ½éNq„>yHfëÄ·ê€rV{rbÚ&:’'‰Ÿ“¼{HŠr`sA^’(µvZaªÞ #5 -I°´=áÉ“3î÷ØÍInvlk0~¨™¹‚ ÷†à‚îYÐzÄAI^W—of8÷âž*ÞœKQ2³<û‘¾^=P¢œ±+1—4GåТB|?¸Pï!AP»OÀ)nm!¯Q¿eÊ+C^@G+«««« UÓðcƒ lSËÌò£Nݼ‹¯¼$€†ðmeÄ¡‡hmXø>R¹]¹I·”ñ‰ÿ+ôÛˆEò…{]¡´ê‚”—ÜK´ÜRø‚t(q•Š ÆJRz8Q/·¤ç`– v×tív»]«+SâvÊßéûE]s¤"8|Sbú±]®auÞy.!HÉùœo¥5ã…ý¶P¢ŽéH£—‡£Œ¼/Éw,®íLvVBbîϲ]ÛÃË-°º»»ì}ÛË¡wghulYYv.ìû.íá廳´:º—v}»K»y±wgØÈut+)3SycllÔÍLË2ʳXÃ*F¤~ä|—UO ’ôVwðÐê–”A<··,‹çdå~iÛ‚_Ž kÉ©Žº0Ì3 Ã0Åæ)gʪyll—.ßIMå‰óËe8ñöW©#Ë`Ű$K)6|FÝ.Eùn[)õL©÷Mnð}Q‚)IÊ«·„`K!ÜGÙͳ±òK M!SQý¨ðÿèçÂ9ÉÆçÂÉN†j}i¶ 0-ŸVV\o­ªQÓÍÅpR¨ê·›gþ J‘¤¾á/©¢= ²ú½8j?ÿÄB !"1AQaqr±2Bs¡²Á#Rb‚‘Ñ3CcÂ$S’¢³ð4“áÃÿÚ?ÀÏ꟔GõYÅH¥Ý)ºØ°mÕ7XTûæ›Õ!`]iÿ×ÄßRn•èOë¤ñ•†õ£ÀÕ‚ %ÞHvžêQ t«þü–r—=¡`±Ò •ƒŸÕò(j’3ýáaC OآܳÍOÖ>ë<Ó:³Ä, ­?ãzø›â ]Ò™°#ÖIã+ ×7ìjÁ}Ûg¹>]‡yú#¬UWÜ'Ši='Ô° ròUQõ¬âŸª‡½a-Ó‡rÚ Õ7‚Áûx•„užAM¸ÏܙջˆXZ|_.LêÝÄ,¬>/ž §?¦î&vñSu…D=ÖøŠé¯rs²æXt®8Å¡´Ñx^‹Q=é­–©5^§Xî kVM¼¼ïXgZ< SõQñzgVî!`›çÂPë#ñ…„õNáöE±IÖ;ŠM‘â@ç-­4â§Èü¦ÅŒU‡œ•7¼ý¡bý6eÖSÀ¿×Òý«êÏíaEº4Fr]Å çqXNðð…7TÎ.LêÝÄ,xøJnXÏS,+*‚þ€N¿óâ(äÍòM{²€i¨\ˆóÑÞ¦péÏõú¯GGs¬¶9Úes“éÊdþc“o–ñü¡y2T°ë„ÓY?ì*µ“œáø‡J$ÛçÏ*Qu?0§6Ë©`Äv´ÆØu ÚF½jG^oõŽÑµZ„r~óœJ°æµúˆD²  8Ñ2ÔXÔ¾¤ª¾MNržØ™e äøB9:C¼&dÿ“MÔ#ÚÜÕWÉqu5K#…l±æõµªî =ˆm ˜m\!çåEèØ½¹‰ïzjjócœ×²‹ÒRšz¸3j¥ƒ‚„hmÚx£¾ÿOßw6©\¦Øß5ðy¨Üã@ëþI®{„La«mt“]\™(¬©4±r ¥ ©¯È÷xUu ZeŸfWšì?Eèhòãdø×§&elòDµËÒ¯—1ê¡´CQ#½8~£üEIÖ;Š—|¬+¯r›qœ\†çškMˆGn­n¤Çá ÅÑžŠ VªV•¤"£Ý swâ¿Â˜E9í˼S þCø•èðEDq‡S²«Ó2Š–4|Ôï%´tá£ä¶9ü§ ˆ©:Ç)úÏ °‘¢OÚמ?«o­g°ióR:Z8ºëJ½Ì™Wz¶åo´a»B{KÅ­ å¯â™±I¦ÛÏrn5–‡.N4½ÕUö0_þky'vµCí:b{Çê;Š”~¡Sy…uŸµ« ÞûBÃzß Wæ¡'%ê³pŠÔ÷–ª)ZQ†‡Z/µR¢1È^é+Êhª*;)j¦— ]þõRn0/H»ÑÜ¡ŽØÆ˜Ü°8‹FµÊ¥v˜ãæMü<ÜüÍÔü<÷¾¼Ït)½Fyðõ)}N}?(¬/ÕM{ü3¡W“—™ü²Ÿ!æ*Ÿ ¤NÅA™:7A•2WÉPVR;5eŒ²Ù9š@ ZÐÁ1Õé-T>ñJ^Ö÷/IßRqvÞ°VÂ*[W D@Ö^[™N\jmåøBÂ7î ~ÃܦØß50÷û–5·‚µúgˆ\ž•RyׄX̼*Ö‹"ÕI½‰~[Le ŠvÄêij͓赙äç”^ƒÔ‡*Â]µKÐ×qú)†¦ž*aî0ø”ºãg):¶ñrÃ~ H8 èÎTSÄÂ÷4Ð…bæÊÖ]žšÓcüÌîrƒ –@úšÃ âSbZsÍ_­7}¼PÞoˆ)7J®ÅpÿÛÕkž´@‘ù¥"67‘mo/¥ôBq s#BqÒÁÄýTšão®1Ä£­žhîy¬+Ù²Ê÷¦I!«X.ÕÆèµúDÕįâ†\mÊ7F_Ìõg7þ(^ãY ¿äS7‚nûx©w mÚs&æ'ê²Ñ0».Z'`m’ÌOÊË#:û»p€Ö·­äÚ|“5°ñ:„¶Å*…“ùf£´&ëaâu°ñTé:òƒF4®æ3JŠ7sS•‘Úó×V²^™…á&Ó{õC•5 ›ì¨w¼ŠfòfðRvù”NÕšÒq‰­êÝªŠ¦;QU´T6¹Õ *#î¸pM”œ`Ò~Iεk{rh ï2XšI+ Ã¥8vM'0N§k^ãxª@ÏZd +êâãSØTÿ´¨÷“7‚—bnÏ%ßÅ;aQT‹rŒS5é¶.g½™3Õ¸eàW&l“–­¼¨ÞCœke¢¥Dy9FQ|eFhü‡ Sôä‡GZdqß&{‘„>¶Ÿh…ƒ`×rÀó¡EÉÈÚÖóØ¢õƒ.Jvó´ê*Ÿ¾r›j9©ETùÝp4²<Ðé6ÿˆ&×Ûoÿ’kzE`®“˜$XQ„â¸Õ`Îqÿl•ÿí*Þ §‚€p>J³ž ÛÊFÒ®¥È­´TÌMS·Ñ9oÎ< †Y%€gÐ¥uMÌ%8×"m\ÛÞÚИM9Ã:ÁräsSÚlÛª4½Ž¥TlŒP ìX>ÿí*.Õj‹µD3Ðù/‰ áâYkVµß0 Õ|Õ¢* 7*‹… ¢k+Т7Ô:?%!÷ N­nÓµ øÍñœöáMðO ü³ŒV #¯}hJÁ®‰£Ýr‹J‹-(s¨€s€²ï%$7±ÙF¶¦zËV¡výo9y¿&13oØÝªMÕsÈøÕžYÍ¥ˆ³ïPŠsÕ>òõO¼»úVüE}g±’â¿ä ˆ<Õ„¼ ß%r(ãµZæLƈÙuxýSO«º¹ c¹1@I1܆@zcRi0:¤\à£aeùÕ.O{T¡×€Ç»Àš«Jb(­shˆ¦\Šøý%~ þ’±˜W5s;Ðç ܬ‡½ 6Ù¼¬"ws£æ¹POÜ®•¿%ÏbçF³v& 94'Ôd¯—Ñç-s¸8¶Ü™M £*w¬–çS*•}jP.kf¼ÐŒ¿x­j†0«iŽÛµfÚoCZ|PÝ1·’ƒ¦ :C[!G6šûìä½5¿u©?¨¬œÂêÝ*³baur‚©Iëº-ÊÝè•!}FvÑKïÀ©4ÔÓþÅ÷’w°¹t«¾É…«­T|‚7ôÝÅRºB:ãòUN¿;|A9ÎŽÛc ª®ºÅÖt(œqQì*=×y/…?q¾jB=–Ž)›™ö 4Š'¼å¨ÿ#” h½ÓÈþ)»~ÀÃ[+ÿÄ&!1AQaq¡‘±ÁðÑáñÿÚ?!¹Ñ zpäû‹7ù&#õSµ‰dv׉㸙Ŕ!Ú˜ëðb?É´V¬{º#ûï.×µ¥ZUž§êMrh÷™%ŸbÈ|ž9ÕžÀ,æz@òs'ßÙ#¿~¸,:¢7vk¯åQ·Äë+\#8Çø¶˜áVhàœ‡êo¸Œ`›ßòN¥‹N0¤¯ öšš+ìkæ[˜\Ë/CÜŸs]ÿgî™G]f×+o¶zdKîX?ÅÿèÏ7!`ãá˜GÄ÷I-O/â•o9ã¿øŒsüæd¹²¿¹k­¡®ð>aV§QÄÛRîYÆyBûsq)x^O€iì!šÇGÍ÷0Tç.”ŠŒþ•'”uÛŸ)å79úà¨,KØi+èoÙÁ ÖŽêÒ+¥%Èõ& ìà”§ØÜ=-cºWoü!s á5|UžXº!ƒëN…çÿBe.·'ò»¥ž©y–ýÚ0lýDߣ7¬4ËÄ©_O5YÞ@ìBÖüT³{ l¾·x²ÛV‡·0øŽ7 õ5—ãp+nŒì>óΘæÊU5ó/ê§QßÂix¤³µùËúG+€‡]Ÿ´Óôü¦K¥ "k(­ÆÐèÈsö†×ªó;m~&w›|Á·“ª¿_RÌ™F-luû‰X;©]ɼX„çéN?hj²ûA×Å)A2ýÙðœBŸf&'ƒó˜=Óǥ|ÉyðüÅR{¤/jvL‚8%3/“(kª°j·Šÿº!-ø9ÄŠAeìÛÒh†m¬B\¶ ªÛù+ÒÐâ€ý‰u·En:Ñ‹GŽª'î Oð -ÆéAÐñò_ÜÅp¾IûޝÜÃu¦³ó¦¢ˆrÒ ”ý5ÿ%p×!§Ð–¤f}S0#@Ö63~ð¥å¼EG£â;=½6™À‰ŸE¹À¦t}ÀÔu¹ú[1è1_¨t®v'L<­û•œ\ýËùOÉ÷ Å$»ÿú”‡|µ|”y >“u¥A¯I‚ôš *àr}8Qæbv(.Y‡ùXÔWÅG@õ¢i/¬ZŽž«Óçæ]P¬jËzÆpÝuØ~ 0 †lÑÌYv¼aœK»+êbp| 0Å}T m9™«ÕSN×§˜ëµSê5éhk 0×#N¨¢›5—fRÍ+§ßˆ.•îÍ}!0Te&ìLiOB(%nwzÇ «l^­B·MC‰œšÃ²që `(~s3ÚxΦD<Ú@õMñ~“v¾¥pP¡inY¹+ËÔõúއ—â}Lo[óÒ[Õ‰ÞJý^®eN Æ>­hˆø¯eáºÕÝxîÄj娫&F¥ ”ÓcT÷´yžªÄbºJº?e­5g]¾ÑlÒÚîc"7ŽÓ ÆEm°úÌŒßÄH¯!åi~ÓüJ?hÿÚ -ùðÿHh:?/ä»1ià 4ùýžðÙ3üM'¡wøµX®Šƒø,”Ô¯ª‹â’ž¡û”á¼C^Gn,Iœ£;:Æà ï%úåÒg|Hê,ÐÏðXøþÐXoø¬±Áý&K‡ÂþÏv~_ØcNJAãV¿PšÐû[ï.½Ò½B£:J´×V˜Û8ê—d—©0ÅCüÕ•þLB¤×lÙé )×\©ÖëpÖðéZÀ'jÎÊ^“3€ ùMcOúü„‡7¸K›(RWô•žþô™­ÑýšBfy•?>T~u ‚ègó YSêR õÔzyš=€†•érˆl½¹`~îWßE›«ãü”±Á¬+ô‰¯ä‡Í U†°BÜÄé?Ãõƒx4•c£¹¥É?eý@CÖu¹zW˜·¦kßÇlÆË×Ëœâÿ¼Pb€¿išåPà0TØI »ˆþ y2¹Ê!~Þ¨N´¤ꋌÍâTPØkxSiMq/ˆO›ÍÔ¨´ÆÉ±¥Ê‚«+‰Ñ‚.ݺLváÏCùÔ Êåûdl˯Mª¼KŸÖM[ûÃ<•».R‡\ßrmÐ×òæ/;ý‘¾sÑšMŸ´Àgk^ð7þ³ 5º~ÁÒ>I‰¶ú€ õXŠ=ËW<¹v[ Ò/Ò£)Â=³Êk÷¼5ÔHöSÉa–$¾óŒåò‰æ~IÕ§g)[ÖcŒ©Û#ާÔT憥•zï.Ôè­ÅÆä€ :õ¹bÖHÅv±Qn-nùEéFu¬Ï8ÈJ²5ExQE[•§š¢Å¥›ÐÒpiCkÔ°ƒ-T“­[(JÝ[ÎP`ºr ¡z²X<2Ù× }b_·Ñ4¡ÈIp=WTÐè hí)‰#©!¡6µyõEÃAò€§‡îO¹©ƒ›ê‰OU}Œ|±•ÉØ¤‰J£R8b5޹#4õAZ£X`ç¹Õ`^©±0‘S¶Ñ(Â9>å@i_È€˜µºné/‡7Ÿk—IY.ŸRïNª]ZzB  Ásupk³(cY’ÚhDSlnÔºh{­?ŒRÆàÒ‹ƒâUp7Ïxä:¨ß[ðÊM^&íÝâjK³ùëXïáð•Æú.]CÞP² óo á ¿Q*åï6³]°)Ø©€~ÇOOå˜ xEB´zKÃAcª‰UÔ|Ê™SbêfÆtOäÃEÕ1«ŠñÞeà²ûzKôBì~Œ"Ô1êKOa´ V ÉÝA¨^ºßõG®Öq/E¸aÞ[K­&ÎÑÒév•§i ¼MRRQÄfhq¨ýLXÈÒ]„Bs)4nk#Ž)¿bØË€ÆvÍvÍ…EÕÍrMãÄÇ:þMÇøÊ©×¤5ñq݈AmÍÚ-å n”©¡2[®š’Úîß–]éH¡[6•÷RoK¼êp«Zæ#—¯ùZ¯÷‰~é­ÌìÿاÕÞåïÖg¢øF3þaò‘¡SêiÖöþÎi믙ÙçœA´X×l°¨t}7ì§OÛIHµ\qŸŠ™¶Ž„É:®ÉX¾©’ŸšŠ AÓf¾£Ãg¼T>¸x1q†®7j2¶ðÛý˜Ò€#@á»æBY²¦ì¢‹9ÐXœÉÖf üJ–…ét'Ülm¡D~Œê¬ -iTèÌ&ÿµ*hÞ>ý¦Î6Ö*ôwë}%¶%a¾w½!}ñ»ÖýƃZ×ÙIjôα{•4ù"lný ÐPfšÿ²çµiªÙ«¶-栯ä&‹¿ºlÒ°}áÖªû&Â(ñî5™•ùâó[݉°#w´—ã&t,øŒ––Ÿv ¼j¼Žj§ÿÚ ö¨ô•P {ṎçÖ#W½mgì”´"ðý2™‚:‚5 8‡SHOŒG?žìý§wå°4¹ñƒþØ´F>Êa©À?õ¨{LF„ŠÁù›"+ŸЇ ,|<8gÙ±RÀ_¢•lÁ"÷|ßô†@Öq%»KHSš‡^â¾ø+›D,=ý"{Yœ­:%-ôÁ…-lL织ôñUC»Õ D²×Ñ,á–@lJb$ó~ëÐNʰxó=xÁ>qвš†Nþâ&y2IØH\¥–;nhÄfÜDoÿÄ%!1AQaq ‘¡ÁÑð±ÿÚ?Ò:1a€…¯†‘à°S¶u4t>Æ„ŽƒÑ°Õ Çaèõ èZ΢wÃÇÄ™FƒSCÃìì=£Ò»(аï„Ë£#Æ<”Y#!k;cÑê0a“X#bÁ»:C!‚S:0Œp-gaèõ4 ‹¶Gˆ#AრYØìnÐÒ ŒÂhí}F8•¥"bÈŒ²F]™ U¢ø<¥ hNV…J‚èv¾CѪ¡éÑ„a0dí¨?xdбbQ.s<fGÈnÑdŒŒá—ñ¸vH$Ld•’o…†ƒÏïý˜õ!鑃&8w÷;,t'Ë…†ÃÁè”Æ¬jÌ0.ŬïïÂdȸalÌŠw! ,<;÷ÔjÎÌ ³°µ‹Y·Â6!p*V)ß"§6¤ÿÓ4GƒVc‘=еBƒtiÑ·D ÑÆð• oNcØv,KÇ¿ÈCû5ˆfÚØíð…¡lqv•´K‰Å¬ZÎØ´h4¤Ñ vÅäo'KÍoÈš—CÁàɱ*=àB´ôßcÊáþ†Ðµ‹X´-p%D*K[Œ.XÍý/Iþ‘ღ7Êu•ú½Ì}U"hµ×¬ðì\‰†˜‚}\mõ౎<™ÐÀðWÌhcf™ìḊÕ~Fà5:õñ³,ÄÈØšemúˆ¤»]Q¤1¢žN棲m$†„,#v†kÄ5%'¾ÝŽ¡×Õ±8Y/$%±ÓC赚rðu ò)¸1'!¹"ðQÈìBHH”© tSRž{d¦ *Æ’¬dàoàºãY¿¨©9Hx`¬>‡«ƒ³ÓýàÇ¡Óì2¡ *†•B•9cóð|ñðm±¡(²FðN\’i4=ëWÌwÙ©CIJ±¥XÝ)¨åàÐÏÐH dÒi c"¾DUC~ƒŒtÆøZå–{D„ ¸¢h‘»Hƒ{'bLŦÏ дgà DE ©çYôÎ×ȃ6?Çì±¶K$H“…ÇÜ xB#ÔŽˆ!–)•"áò~ô=Õà±Ë,¾À´ã°È¬ž?C؆Y/‰$І3X”ß÷¨‰Œm§‰-ÐÜòŒ§ÈÇuÄC/†ƒ=‰oì¤KY´Anðâ d|J&É’ !¢ÌhÍßMáò†7b™ÿÄ%1!AQaqÑ ‘¡Áð±ÿÚ?Ñ·mQ²wlDMhÜîÚt]­.î–÷¢í»ÿ-Pól¶OS¢ím£sm:.×§ˆ†-ߦ3ˆ»·}ÛdÕŽ!Ý´l™Üè ïÁË·þíq²vÛ®—Pdq!ÂÈÃâ ŒxÙµÇýýÇîÔ‹»d9m®“uúºxqf Ÿ›/:ž°á¯Äµò6Ö×Iêt]~¼11'VAÅ•Ëc¼NíQhˆÔ¼].—IÑ:-Oþ«`†l™ìYù´"ëÁÌeÄë̇“Àäž%ÌÙdlñ²Ææ\, Ã,ö؃è'¨1áW‚Ô.xŠ%fòXGÑõ#›ˆÄñž ³ÄL’faò¥‘|~£Öd¨°Ç>Dz»8€^°Æ£ `Ÿ%ÍÌé÷&0 瘉Œ;²&ÂÈ'¨7Í·ÇŽ£ÆþŠÊÙ$ã›7 îÓ1:Ž0Ÿ8xju‹‡«ŒZlq»k3'Ž'ðÛm’1îÌWÕô²z²z„Ë’|œÜs¸²{³ž3àÏð(åÎ+Áí –}¬žî;‰ÞlÝG%ÝÝߌÅrØ­LN ºGƒøC½ÿÄ&!1AQaq‘¡Á±ÑðáñÿÚ?B´zCûF”b)‰A2¾ó±"iÝòFò>Nkü¹duÏ‘ýÁyhù?¦F\?ø0‘)Ï#}±®ÿgdaž?ŒYÌ€Ö®À¤bå%=w5¬‘fZzï÷Ö„Ä«@ˆÙ~˜º0$ Q‡RãŒp‹v¶õŽ2Ì¥– ½ã!7_ħèrŽ”orþ3™¤ãºss÷FÚ£Ó–ÕÜ0½·Þ7!‡ýÞùÛÄr'”HëõàñKéd?WŸ¼ ç!Üd BND¶¥®ãÈE”‰ª‰ßy"CdA·=ð~bˆB¥ö×¶)8Y"8g<Ì–’à ÊìüäîÄHÇï¿Øuœ9xy,•èc̘®äë¶Ü|`üÂy¾W÷$·MóýXò§>ÿ~¹¡Ãò°¿^£GôÇ-ü“~âiKñdY/TöXÅJâWÑ\N"ðQ vOÿ\+æX‘ª}âp»@ <ëЃ+8pˆ ÿ9rC+ԧщ«øÀy–g2ýðÃÆ €üÊuž.³€ò†ùþœ¨œ0&vO÷8ã%~K>2޲ôþ12;_‘1*äPö‡æ9åÑBäGayèKr%ô›¿õÄfI"&lN9®1M‰Љ¼ÌüÞ<´ææ7ªMw†SÀÒ±|FF;øGñµ»XV $gœÈ=[#1DA~dÂßÝ_¸SÀ8Ÿép¤°@]Žmáë™7Ã9”3 h¬¹m•áN©Ùà¿2,’ŸùÀºR9 "{2w›y%ÒR‰oâ a¶(B‰Yç…c¬a”Šƒ½É§ÁÏQ\Ì¥Õd爵DŸÆÄ\ŠÂm¯œ9½5{æLÏâD:þo›;âüÈ·_Ñý¸>}Ù‹Ô6Ìqþã†BE`“ì é’xiI–GÞ¬ž™æb&RâJøæ£>Œ‰©ÄÌÁ¼}r4íÅ ©™O—X³`NˆE’ÕÏ}äw±”N¸O6qŠ`‡ q½¥·ïXÊG%:ñ‘°BE,}æ)]ø€¨Ð²’fŠ3NÖ0e†¡$D&-ÛmããLÇ£P «y¾ðáLÀÐgi{ïDÐ$DÊM)MÆÙƒàÖq«f¨˜fæ¢åH­4dI´‰ÃáФ ²RAãë@¾ò›1W™CÀ£Ug¡5ŠHDef3uÔнöÊxl‚e´½šçj04>}b{œ BÑi¨‚Ö•lâ‡(vÂß„FiÔ€vôAzÝ…ŒU…•9°ûe½¡ùÇ[|Úc vJSó4˜ˆGüÈ?U_Ôeåýó ÖÙø˜"Ä+ñý¸‡ C꿘åår~lÇ«Jr`Š;EÅe¬¢xÃaÇc,U•a€Š@Yå?¹¾â o­b+ ˆá"trÚD²LÅ5jö "kÕÐ’¨jíÌW HtPîñ ­¤NBi¶Í`»Ì¦_ E¼Cúg‚z$Öm¾úŒ‚'é·î(¡þyÁ¶ÛýŸÌ?ÁÿY6ñ ¿ÓÙ˜£nç¶(Å"[¬¨’²¬8` Ÿ_ç ZéIÍ­Q¡×¾GI ™.cÓw¬4ÛQOi±Ñ,m’‹6ïBv͘¨ª"6a£¤ÍÝàC§ †lµî°Gr&–[ŽAâr`Ÿ¨LáLô2LxyϤʴ!uÀ²s¥ä_¸dȈž§äÀ;įæ@çKô~ã"ÍŽHçiJ‹GRC!‘®„G¬ãQpDOXfI?‰ñnyÚêúÄ®CLxdKHë!5žz¼]kx•HŸLx(ah&k“³ˆœ‹ufA³%sÎmQàmaHºcÁ‹É¢3kOøÓÛ! LÂgú4¼ gùýdèùÄ…û_ÑKÐOìÂ1Ú¥Ÿ¸C(E¡2®] 3ýmÈŒlÑ¡mŸ8d!0G`бI˜ÕwƒÚPXvtÿ8)ý1°Ð§ÃŸZߎ²U¨á$Ò4@ž&xʱ1òŒ^#ÜV˜ÆpÅØ x›2D0ƒ±bÙ`OuŒ|’­–°ÎDaµ#逄Ìé¼÷ÂI G‘ý§E/Î(ÅÇÛ8 æãû2Ð&ô°À¡¤3‡ ï…$Ío£urÿnHh=ò*’#äÉ·R—84s7°BBåæðãð©÷‰÷Ãhc fT..JoÕXƒ;l nÑ+n "gy”M„]3žõU1'­ˆ#K ­Ú‘’³Z‰)ÏfÓ$Ïì›HÄUÃn³~©™&3ÞÇÑ2gäž“n}&¡ )Þ"Žþƒ¼i<—.`k"­‘SøÜáÆ€Ú9ÆÖÄa<àÁ³pf£­v»3…‹Ã€0‹ ÖPñجêEB H£î`¢€í&@¬€e\¡·ÒÆ›Ø ‘â»çzÈPž„W_[wˆQ}Œÿóx¾ËÖÚ&Ç6!¤Ò2]äá¨a-ö8uC]Ïú=²üýaŒUÏçÇ+gÉbÚ±žç8V)˃aDd«úûãa ‰7y4ñnI3Ì›”÷tcÉ„khvˆn0nA ,­·<„šŒiä'ãž_1?rIÒý`’RÛƒë¢B¸r ƒ0‚± ^-7“(©D;·jŸ]桚ï,IIé—„øPeG´r¬â¡Ø[¤l«+Œ¨s΀àPÚ£Ö ; Ü–Óļ“ÿ&,’FñˆR2Í|£øÅD—5Ø&â={#it‰ >£ò\”’ 8S…RKã›r$YÆ ~5`0Î ÙÖ¢]5 4a¢­£~gå|ܦÿ´`¬JE˜^_ƒÙ ´žE6³þ¬­s§«>õ†FLRpx¾0¦"Ô+Öð‰b"¤Ô ²w“:éé•Ô­þ5Áå‹‚\4ð¤ß×ÁK ˜+8e%‚ÅIê7‹èŽP¢GÏnB2‘çAÖµ‚H[7ê‡Íã™Ô, 7r&ž£»É(dG¿ðgRÿ~7ÇæÉüØådl´,•ÜR_œ´*BÁ’oZÕxÆb6 hˆäzŒAeIP·Û GKHZ£ÜbR¬j ³ÌÏ3YRMšœ AdCÍÃk1ê&Fù`$ÙaHQÆ!a&ØÀA‚ *ËÐyÿ˜³ §nNºhžì©h ì„ú@¯O\l1ŒE+6‡¦õ…º¡Fe%"øÀÆ;_ö9xWñó3ÅØl1aö¾ÃÀ÷Œ0‘qu•§È¦W¯Ç"‘·pÑç ³‰f`VDã]N@r¯Ç¦”…ú&Z(XÛ£÷#…"~Äf;7‰þf+dñï¨t¢á4óOõe è–F¾%Ç!„ÂÙO|0æ0³ªœR[Ojx`säCôÀKÈ>OÌ0ª6Iûà}³ç"HÜ&ä÷0þP“iJ}“$^ck ¢/5Füƽz|`)1‹ a®ÃàÁBÊHH^p$ÞQ"žqaˆ¤-þë*¡4SWÙ(•—¡ûüâé$-Œ»lƒábÕh÷ÆQ´d‰ÜsXâð`hñó‡ŠBˆ%ðùȹÁŠ@%óg¾U´ý1 ‚™£]ÆÝEKåöÁ¢"$»˜uÖ'Ê&Ò¾þ0%eÂÂÓèk ØL…ô` èÂÅà¬@–‚KÆuÈZJõÆÚ(¯õ®\é‘„ºçˆƒ'LÂ>ý¨<‚2'Â×Ï81ÌI°$öß÷?"™ š”^rMûb‚Þ±¥jˆPH£1ùŽÖ"‰ñÿL8¢¼2wQ}a\HÙ$WIÈ+O¤Ø*u¤ÈþäA eš{-ƒ5©:ø$\ ö›iîÌä>Âã;Þ>¼dŽJÔűæû`Xà“µÃµXKAí<â ª¼#÷@ážAZPºgcR% 5íüde…NÁ2Óä=0aPtY©/¦M¢E¥µ|pc8”å ŠkIǾ§1QôB˜ÛàÙB×ÂB€³ƒ" IÐð¼>0Z‰hzA8‰Ãj*ѤÎܨ vo èA´³Þ=€ pT 5!÷“‡~H&O$`ù2`„YRçƒPÄ4À!5ËOÜuuÅë&wNKGŸE_¸÷Ém7&HžW¶Yà@gÀ9Ãæ€/ÅhšiyšÞ0ð€ôˆnû€A]ª 7×ÀÎâÂÈŸnnÔžž T-YnâOlî$â„–MP@Ú¼]˜tvœËÜÙ‰Ý@$òÊW…ÇN÷ˆ3âþN$ž “Á[ÌI¦hø„=®ò=¨&Âxþ) dó~1H…scÿ0–‡)1 ‰vS)ŸbF?†Š: ïŸÜÁ'ŠŒeã]û౸4š$yë %=OoyÒ4‰}ÅâöR‰(ˆ±Æ* 7t «­}àˆ¿;VÖäÞxÄ¥±†EÛÆZÀÅZ-è¥çpß †ÇŠjÁQ- hpCؾÙX€NC N¼cTò¨ïâ½ç#“Ëþs(dP b±xtd´¦rèÂ#¡îòr÷ÂG¢'¦u–ŠË@u8AÂ)3ïÞ(d& Ï¡&«œ†¥¢‘Ù¶õ‚W2ˆŽ)õÀ3}#Ó÷%@á_XT0ÖŒa Øáb­2Y„Üš—QJe„¹˜UâHDig€©j×Ò¨)2=^o%#”“£8BY™ìÁ±ä‘øÁ`(˜àdY’Õ¦¡Ö5bÌ/ù zEFd–k÷Iy5øLÂN$ÅOã’:4Pú¿L!À““SWÁAª¦@ eüªÖ*XjìÚ­Û€§#aÐÄ¡øØÛ͵ÿ\Òê›`ˆ+V?0Rå1 ]D½ŽðÇ$jÃÌ»ôÉÞ¢¨ñKRpdm:†,¿PKÔ¿ë$ƒ(÷ýNXhø;x‚0ò`3é ÙÉßœ0+ +…zú3lPSë¼j mŸg× ’EÞú€)iŽ3ÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/images/guitar.jpg000066400000000000000000000633451510465702400256460ustar00rootroot00000000000000ÿØÿàJFIFHHÿá¬ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:26:22 Ðè ¿nt0ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ¿"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?†ƒ(É­ y¹Æ:W-k{ÐZ±O¸ dÑFòÎHÓÖozÊŠcýïÖ¬,ÙëHf€“ÜÓÇ^¹ªK6H$8ÎhJÝö6V´íç àúÖR=*å¼Á’0i¡Rä}áùS˜ñË“øU8pwp}*Ù*@À9ª$‰>cŒÒI#Æ=iÄ`c¡¨Ëq’ã@‰ö8;ºŒ­Y BçøÖ\ŒüÜU«KĘìÜ7ØàÓÎâGÞýi3 9úv¥'ž8¨Øíï@à1úÔdr;ÒŒÀýiŽÀ }ñŠ«:H½:à÷«qüñŽ=«6êPËmUM+YÍóZª Rܶõ  ÆL¯£BÜqVbAsl.6 I7QPD Î*œ¶û¹Ú>¸¥}JÔg‹þúªÒë6*9¹ðæ€/iemæ<Œuçüý+JKö=«“oØE "Rþ!ñU—8güt/\~•”cŸå\Óx¦Ï°r}À©4Ýj-SQ†Ê&òÞR@géäWGp6©¬üŽ$Ò’)„3í­®r5‚’â¬Ç>;ÕXI S ƒZPÜ`šæ#¼©«)¨(þ*†‡s«Iò:Ôë"ú×-¬«Ó'ð©Æ®í÷br}”Ò°îtÂEëžiéu±øàzæ?´¯|¶íøŒS|íJCþ«oâ(°ªß‘Éj•u·?\שª˨ÿNò5û× }vGR\d¸÷Å4êŒfQù×#ý›rÿzåñô éLÏ4†Ck0E 8{àÕÄñ¤‰<~5Ã˧D¨~ù÷ÝV¼5á¶Ôï$i%³EÔ¤úS«}zÜ ‰ãý®µŸuâ‡Lˆ`w>¡I­ t»kvÙ J£ªíjxü¨¸XÓ̬‡ÀÊ; •^MG\›ƒ¼~ Vù€éKäîþ9¢ác›Hõ†;Œ¥~¯Oú£}ë¼÷t& ÃÀ÷¦˜FG=J.0xÿzóŸ÷MF4™™¾k¦?Eÿë×I±F^¼Ó6rÇžh¸¬sߨ¹<ÜJ~€Qý†„ó,ߘÿ Þ1·|PA :.ö#«HàTõÑ ’Œ~®keŠæ·Lb‹˜4{\ÿªZ•4Ø#`ñªÊrA«¡òyÏåC2ŽÿbÛ^Õ­dù¦ó“û²ëÖµÅp²âehÏ·5̼轪RM¼òxö¦˜®°ÛŽ1ÍgˆŽI~5³w”ãÖªÏñI2ÌëdPTÿÿZ†+d=F߸Œ? +<Áå¹ÛÈâšb°Ce]‚´àÓã8ýØ…KglX+cæµ’*[Š)b€à(…ZKUÏ¥\XWõžéÔàý1Wæå‰Ç#Õª”²gúS©Qœcqžµ!sŽM0ò~l{P"&#ŒÖ˜[ŽÔ³HªI#ïT¥¾@0ƒ?rÉ|rH«=ÈÁÀÍR{—n§õ¦ ðH¢â-}¡‰9)<Üœ‘P 2¸Hþf?Â9­;}(ð×þ¿ãJäN¤a¹Z òŸÝ¡'Ú®G§±Á‘±ì+A#P¨¡@ì(bO1Çkfý–ÿÑk’zTBPÃ;©êŠŒ§"EËsùÔ¦ÖÜiöarÇØTfy%áæ¬õFÈ£©¨£¶ûD ª½ÛÚ®Ák¼æL±÷­ UU1*®à U”L)ê„*@ƒ'8¤0]ÄÓÉ}Ù&‘@÷Á÷§gŠ@H ãµ(ãµ0+Á§S@*Œg¿z;ÓTŒsùg­8ݨ’tÇZ¡r3Æ h9 šÏ¼q·¾}sL ©c2J±ãï°_ÌשìXíÑ0ÕqÓµyŠyº½¢rs*ÿ:ôɰßLP)Ý åW5N@E=§Îà’A?C Ñ¥Â4™ $v´ NÊè‘l&›Ž=sÅ?û$A˜†ö¡± ‚H¤Þ»@NGåKƒÎqØRFP”e×Sž½Ð V”ÜÉÿ ÖUÆx«¹>e#ëÿ×ý+³ O­;k+€6“LÓ—±ç¥Ý‡ÚPïWm4¥ ÊÜz(þ¦»FCÔr)†2;PO+}LY-­ô«6a„cÀÀÉ&™n¤B —q<äÕÍZÉ®áC ½~‡áYȖϹY ²œ—¡éý)Ùu"Tà–¥[­rÎ×R†ÊF‘šSëŒ/¦jÞ¢ÑÙ[Grûö¹ÂœŒ~µRóI²½š)^ЇŒü¬­·ùT—Öfþ ƒjƒUùþTï.jK±jÖX.`ŽxÂíqœâ¦ûBE;+0 xQ“Ó®*µ»ZÛ¤²¬h0_þ½JP‘†‘ÈüèN¼Ç1ý¦ºûBO›‚H`2¥lið’¬\{õb6Ò&8UIëïV‚0L¥s •”•‘ÇÅhG8«‘ÀG=±NTŒþB¦P@Î(=AÑ  ˜1Æiª3ƒOèn­.н)2?Ȧ–úšw½H«óÅO›®q@A sŒ}(ó9È"©ù„JQ'{R𔓎Ò—vO<Õ7œätô§ý¤Ö€-HÃÕ—vÙœSå¸'8ÀJy2NE?G8×­ vlþ†»»«Åä‘éƒ^¥êAð¡"¶d»9ÆïΘ’̸'5JIûæ¨5É#–ýj›ŒH–^[ÉclÇ#¨ÏLæ´-|IsnTL¾rgž0@ö5Ι>´†aÓ4Êœ^­Ž%Žx’┑ASÔ Á$ç¶sXº%ÒC§ÛÅ#sÈ#ÆXŸåüêÌš²GrÐ@ÇÊÁºÓEÓmÇRéGãB¨!†zŽ¥WŠëÍ=êI“ œzÚ‚Æ66‘ѱÆ{ç£&;›‘# ÂBIµu9tùBäqœƒƒ\ÌÊŒY›w©Ëš¾†cÏîš^FóU›T‹8 ™ôÝÍqÂýÚö9TÞ2Œ$ÿL~u»q$Šð=º+nʅήM',2îhMXáOèjT¸wïXÒ=ÇÙðÑÄ[ª189ç·½^µn XÆ­5djÄIÔÕZV*Nf`…çü*u\õ¨²›Â³Ç &Ÿ•N¤~&ªç»aù#Ò†b¸ÜÏJ£¨»›FX›nO,Næªé¤º†œë.ó,'†n¬¦šÕ\ŽÆ›IÀËb«I8é¸þu JÄ9ÅE‚NqšC,ùÜwëÜÒ‰Žj¸ŽBrÔ‘Å+Àéš\È|¬œËòÓƒôàñP"3 çç¥ Êî'¢äÒçCä‘9f#€iyÿ‡?#œ¶p1ϸ£vcÈÏÞóèi{D?fǶàGµZWà€3ô§ÌÁ^,Œn@Næ§|ë“Ó‰z}—?ýŸ˜Í5’)&–_”m ¼u'ÿÕSK8,ÁC«»§lgùUWŒVRxÞ¹ù½š¤|›ÿSÿ´èçl9†RJñÞõ;mÜFÇóÿ _˜½¹SÀÈlŒ÷¦JÀH>h9üés0åCÜ•' ÒB¿€¦%\6>gïéŒQ*nwãlää0ÏøPc"`–GÇ_ojb²ìjèwn6‘Š”Ï9«32E¨ÂCÈÙÝÃvéÒ³ôP>Û€~bœ`q×Þ®]« Ûs´“¸ƒŽ{VØÎz=†ÎBpG v«åêàæBNy¬Ë8Éí8Ï¥hà¤}¹¦‰e}Ka±/Þ(Àñ\†©#-Œ… ïa´ïÅv7 ÉŠÁ²TãÅpÚÜŠ6nA8v8àþx§ÔÊ_¥Â’ê°•`U7HùGÊ¿Óò­½@È¢#;ÇÌÃ8ªZ6XИð±î*8à}kJö'šÎEU,Ç ÝNCM"¤¢S›¤L©çåÆO·<ÕëCòЍ4ù<¹T¼ŒÊ¿)8;¸þu§ec*Æ7¿Z‰˜‰+—!<Õ®Õq÷4úƒ³KWÜ¡q‘ºŸá5DØdiJŸ™…Xo´-âl#È “ŸZqþÍ!šÜ0fÁQƒÆp¥K¦Ï£U&Ÿðí•‹+X’¤µŠ>$ŒãæÉ ÇçÍj¼vï$2ÈŒW?tãÛ§J€Ø ÀžP~RÿÝôã¡ÿ ZṲ́Õî‰-§ìo<´!ÑUIÛŽ§×½Fëˆd `ìoåQÉ|>ž‰çHÛLÌ ˜9ëO3‚W`äfŽFìÐ9¥tÄ àÿº•G&EÃd`p=:³·älJ|q‚AÜx>´Õ6'Qc\¢ª‚N¦,$$„–O­k,¤|¹ ŸÂš¦K¨cùo±° <{wZAˆð6qøJØhñÉB? …Æ1Š~Ì^ÐÍ’; 0y>¤ÿZ†I9e,x}øù}sïïZŽrµdÝ.X’iò!{FkizDºÅ„²Dê#I¶ãŒ;qïSO¡É6é•ÛÆ»jÙðRy~•³‚ó±Î=€þ•5â’[Ñ>D'6r‡Ne#'$dŽ ô÷ö¢+(Lг‡ò·e¶(Ï_­lȤ°Ç…D!g”F¹%ŽÅ.TD§+þõ;'Žy%ŠVÜr6ž¼ŠÔ_ [¬ùÚ¦#óÆÁÉÈ=Ï¥+"Æ«ª  úûÔö÷’[¸ ÷Oo¥LZ¾§½*üE+-CrjìÍ£­ht,Ì뵊ú8ü©€0ª[êðJ£Î‚HϪ¶GåWÑ#¸·™d?Ýèß•&™Œ×ÄGINed8`A÷¦Pf“4m4MÍ&h lú~4ò¼ò*\vþ”Æ^ZÐ÷FªT‘®?‡ó¥…ëNÆ9¤é¶œ#ãßÿ—’ `f˜ù-ü, !W½ªVÁ^N>‚›Ç÷ˆ¸¦'ž§·ZŽßʦÈòSH]¹"€!$ŸÊ£ «ã…ÍFï419¬› Fzq[ ÷¬{£Ý)éZzÐlÔc%ïíT®Áå ýkN(öé¶ëŽDK×éY³azwëL ù0yaßÒ 9Á犷&Ü÷üê«3ÉëÞ‚Y$Z‰HÖ)×z¨Âž…G§Òœo-ˆÊ—úTd\’G'5]•ƒczžTÎyaá'r{I†D)Ï©¬k˜înÜ™%«Ì¿61MhÈÎ=hI!Æ„bg¦Ÿœ²nõ$ÔÉ^‚¬ª6:Šå\‚>•F¶#0iáù8aÞ‚Iôõ¦ðH¤ 1j· LVdßëùÕ…½³á‹Â}ÆáY#$÷âšÀsE+Ë ›ŒÇ,röXS ‘ÔV#`0»Ž­ôÍ.S…}´Ì©{ÕR:ÇÚ³ä Årìyîj#û?Ž)òŽ8^ìêäá$süªA’qŽž”l'œΨ¥<(ÛÉ¥ž†ž¿+` ELŒž©åàþ4î÷4XŽ(‡Æ0E#ãqõžcȦwèxõ ÇCŒþTôâ¤íÀéêi®»†pFC8¨[=•XäsÚ aÎzûb€(]+sÚ²'P õaüëZ쓞Ռù{˜”²(ýhÕ¦ù-£L0 b²&Ló‚qZ÷D"(##»VMÃ(S»¯jQ|ØÁàÔ ¼ž£ð«2…ÝÁ5]Êäƒ@ÈW¿¡ä°ùªf{ãœSH#yÏ8 EvI%x¦uä©úTÄg9==i¸cÁ Ð+ rE5‚‘Á©6§Ša.)ÃJŽÃšk{})Ü©¥Ýž:æ Æ?‹ôëHsž£ž0W§çL=H#gŒÒ©#'ñâžÃކ—-0±ŒôÍ8ŽO&¤nqÈëÜRùCÍtjIz“§lW1‹¬\|Å`q÷*qâ½=›bê 7øQfU· éJ¤ûVDzݬ£ärOÐÕ•»FŒþ4¡ƒÓö¥2?¥TKÄÝÏ~:U•œ?*RÎOåL*A |’tÇOqQpÜŽ´m9Æ3šGCŒí4àäŒãŽ”àsÁlíªìpØ=*æÑ¸€A"yëõ  ›¾èk$`^Áÿ]WùÖåÌ~bçîÏ•q=œÈа^DY;d~µ‰:•b¶ò¬‘+Ã(=+2è6ÄŒ©W9ÃsUÌEsÏ\š¹2±\€j³)ÉAõÅ +‘–a‚y¨¤‘Œ{U•M£v÷Í5ã.N@PgiçšpzTŒ‡oÊ}©‡~Hô÷ ž™ÅFW#=zñR9Ç ÜT[~^1žyÅ7<æ‚v÷©åäúf˜Ø(;ЃIÁlàÓ—cž”ÝØüè„}~”˜ c'Š“¯AØÒ2@8 žGJFÆî()އšVàLPÿÙÿâ°ICC_PROFILE lcms0mntrRGB XYZ ç acspAPPLöÖÓ-lcms desc @cprt`6wtpt˜chad¬,rXYZØbXYZìgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ öÖÓ-sf32 BÞÿÿó%“ýÿÿû¡ÿÿý¢ÜÀnXYZ o 8õXYZ $Ÿ„¶ÄXYZ b—·‡Ùparaffò§ YÐ [chrm£×T|LÍ™š&g\mluc enUSGIMPmluc enUSsRGBÿÛC     ÿÛC   ÿÂà+ÿÄÿÄÿÚ ŸzèÅÂ.1[6jï.XeJBG:Ô,¡'| ü¿lgNC¡‚Ã4囚šºÏŠhPÊDU#0è쬫0Ì4ú†©$Ï•cDìitü5»kdô7+Aƒ ÓzÏM¨Ê W¬Y:*Øx\Ç8“¹:ã,LL@Í2Îî¤J:3xøö_VÆu¡–”j«1pæ}Žê}ÊP£<Ç_ELõJ2Ñ“£¤¡@T¸¸ ápN¼Ù?,gXËë¥6h嵚àB27V=bÊ Þ°ˆXȦÁ ‹"b¢4œT¡{>ò­•OÍ\úó2¥ËÓZR¿–žOÍ7,–¦à•PV>N¦Bb¨bC…B’åƒ,+fµ3gϹõÁÜæ·†aÉ]•™YÉ’äŒK`…É\û;ªÛÆ’@ID°$¥H2#ÕäTÏÔXWqw0÷‡3­ÕÍÐΕˆ<‰£Ü…s¥ÄÖ~õ¶ç"ÅAƒ)eqT©P“›ùñ1<öœè™šôsûöQíÌ©1ì›w±vs[ 1 D²ôœd³íq‰I‰²‚Ȩ„‹ž%¬~v¾<f‹@¯hÎo§Ò©`…hGM:2.I[XDõ¨Æ5ºv}A2ÌáVFà³X1›z .ogɧ½t¥?.”éçÒãù;ã çcšéõ…¶ñƒ¼jciR¤’¥–ؤΦj3ªçAQKmâyî:s_4›ÈÒ²õÞ¯"·=nu¥¬øã3åùÏn^6 h;á¥Çòw³ÁÙ‰³”ã÷ÖTÏNkÕåë|þ‚ؾ58Ý*ù£)¤Å.ab¥=¼W:õÈ‘‹ÁÝiªé½|[žÇ4•S“ϋן¨Æ&bÓ“Øóªasû‚sè°¶ fµµŠrè0}16ZËë0zæš™÷+Ùõ¶yË3ÐåÓòü].žŽß¨[”Ög'?çùæg?'’+ÄY=×ëËç±g§±Ó3·zrl¸ÈCÀìZ\’Õõöy˜FJN{Ü>xlµ¾Ït÷×^“^¶´läÏ>gÅ,Ì$$$W–-Îëõ”kU(råR…‹*åŸR“ ‘ÎYÇŒü¿ ¤%j-¢ù! ‚© ­¨¯_¬ô ±cÔcÅË•¨(V2 {>Œ`Âi†ŸÀ^J%iuÂéèVöÔMŒù}ž"¶ ¤UHXUû}Uó`º-eµ`’ (HÈŽ‡SºŒHKY^e¾~òüåÓ§£™éí¦½JÈY ùôóâbyWJ-RŠé¥Óé"!J] ªCÅ T¨#>2£S²Œ •dRE®.ÕK/ˆ+%ÒÀÙjp£˜LýuNöï³éñ `–! ÉZ a¥Db‰Š d^ÁƒJ¯Š@ˆ=eâ€ì¡B«ã³u©báBƒ%Wê\FÌ‘bhL„üQ«Uº¦šÞnâS%&]©ñ=>·všU¸›X“D¾/Q3[ [.‹dõ7_µ“'I2q.«röøzKqE“É)pÎŽ¾NYL6º®+¸…ÅZ¨¹ b‘¼¶Y4:‰ÕÔÔ¤9eNY/£y¡^»·)ÙÎæ>K#’Ê“4Sñ]+w´ß‘Ï†Æ‰à¦æ îãÊyNÿ[zÜ5º9p•v³$YU¸+´S<†H|]:¼¯p4Y)¬6M”ê÷AÜ;Ç`ïX²øêW5ÖôØ}kL>»§D½EB%ê:‰z’$½G#þAiùûØúÎ¥”õ=B»I¬ÓΗ3Üm,ŽãW3!i HXBâ6 Å…b<¤5ÔàZÜþ1?'ZRêõbzºßçé’}[r¿¨jæMë¬=žªBévÈüVÅA ¦RÅÓ*Ïãé=G²¬ö±<GÊrQS°¦Pá!.D†×lö|–..hôªþkdN\ÿ›P®-ÓFÙÙÒ)´ü$§A Kß‚-Óqºz›*ôüõP¿¯j¦µ5Xü^XãU£Öju•iy=Í’6Ù2ª0áE !`Éœ‰NX“ÆË'…tJ¯íî°œòye£Mê8°”P 5ÆÒqS…Ðö–76lQ .ë=¿-íõw=²•‡ÛÜnËÏ;™½¨îâRâvGí~yqNº“Û¦‘Î#–i8ÑQÿoNghþf¡bûÿ’z(ƽ.·t-ui²Õu¾òsm]¸óÊF³Mf£P©rªZÙF>£á×ÊÌ¡ÿŽ> ýwGuráÏ›$¥æ’~8ÿ²5IÖôÖyN¼]Slt½*Ui4:W¨Ô×§Q6d•H×ÁGT½?]–- ôQ´5¦®öÆ)i÷Z£{4møŸÿÄ.1A !@2Q"0B3R`aCPpÿÚ?þdÿ†¿Å‹-÷_Eù\¿E‹,X°—‘bÅ‹yšŽ¤¸¥ø‰¨ÌÙü™³ù#ÄÉ{…^Δ”{²§´Ûîú-ä6–¦lv1Ф_6ìJµûSîU„ïõS{ ‡› ÷g¦_&D ¨"µ%è¿‹)aW)Ã5c˜•¹4ž¦Tv2Ÿî2÷w#»’+¯“½ÊÕÔû/JêÃuh{{¢*_â&ÇVoqɽ|¦‡õ$²8šôñÑS“ØTdzy£\cò¸eˆ+E.š~é DŠÞÑÅÂÌ Ë2Ì´Œ¨ßA;w/wÑÔÄ2§´}wê±™lËîew±—ØQWÔüGª/ªÐØù#ýŒ]å|‘±= óÊZ ’ܲÙ/;3%leYØtâ¡6HZ³sqên®jŨ·?ê“C6ÔÄž†$‡Ó7†¦!ñ?Ðø·²1ñ{—o£[î+˜¬®ÆÑÞ½û’к’BšFb¹˜®f™†c1±Í˜™wÉ•’„¾“‡¯o¦]oBZý¥RHÌZ´FP½ÉÎïí¾l—erRÄïʧOB%縒I.%oÝ[é$œði–²ÆXXX‹PÖÝg›Á#ë$¢§ÆâìmÁys.d¿EV„2ÖZËcÑ'˜ŠŠvì”\‹Ë‹™sÈir5ºò"¢•›ð¥ŠK‘z;™ÇüŽ$u8*z°´dhÅÌœŸtz`ç£9èðwÿ'Â_,¿üJ;ßQÝ•"ü­±×QT¶=´8‡ ZAŸ°´gÃé"E­<Âòâì6ÕJ„8ø[9!Nƒ‰*†²)YÇKeŒ±ÁÛpvÎßm"ÔB#¦C(«‡²tRÎÛÒ–5\AM -’©¡V‰X%-ëÓÓµa´´´óRC!–‰a‚1G¢„B!Ö<üeÆÚ GÿÄ8!1A "Qa02q‘3BR¡±#br‚Á’Ñ@cásÿÚ?ìtW ù‚M®­Ÿ\änTîJ#U–»h°ÙN™rÊŠç-z®ªÊ%^UÆRó Caõ:«•Ó;ä×9ݺº %QŽôTa\¼×|\o¢÷ŽUsŠîÏš ÙMöŒ~,CPˆçelìkm—hTåeÝ ÙÛxÕ5ƒâ0š)l§={h*¦¥wWp.²Tlz"FUS®VÞ¶g?foê•tk•s¾ìIS‰è€å›z\ö³ò°•q ô\òˆ(ð•ÝÓ8 qÈùEÑÂcb.U¢îª‘Íw½l¸’œÆ“´>eXžŠùó+TC~ÕÎç/h}la¢äÂdƒÅh\#gà i¡M¾ªãüõ¡ Ū“8’Sÿ aÑB\63qoÇ¢„'á’S è诊ï7Þù!í9å@©Ï]ãEl«¹Žï×Dj2¨2„þ[¹h©ÁNŠJ&ªUÑi±Xl`ù¯%Þô ®rÔø•ݨWÊê¹rȧn*78Ihàãñ:8]ÌrP­#Ô*,7‡9Ó§%W¨áäµôU;ú«g®WCv”WWXu"’¯º×hÙ?DíK)Éwv]Éí]Jpsâ)­kvdvz'a½åͽã5)á€8»ˆÉ²l†ìÒ Lö\•Ñ®ö»˜bb5WP§ªwA+ø÷B”ô\’±t™¿È:It©×’Åü¶v\;¸Üý–&!^ùæ€Ãh2ÝJ°Ù+ºö/hü)ÙQ½b¬¦—…æ›ÖtðMý³õFˆoÙcÛfHñ_È}Šhƒ]•!²dSÕbþÑý!µøgûM˶t ~±|ŠËL«TóX£ªsÎ'Ä꘽@ÖÙrœvaµžhn;©öPÖ’z¦ŒF5˜@Ò©‚l"J/’_²= $“A(#aÄ@^Hx‡4™^í ªº|Gz ø&[¹¯‰NÖ°5 qiâ¿ø‡"˜i¦©­hâ‘H=Qàwwäè˜v\lh9¡ ½ 70(±Öðςյa f [aÇ`™Ùš-€Cj Z‰ßªýWƵÆ•\'òù'—6teSðÝ9”Ip“j/ekÞqqTýxJänñB9!Í šuUñP¼ öv8íb5§Õ0°ý‘RACñNÙÚá&𛇈Öí0ÎΊ-ý¢ZÙÄ©~ˆ†êo òUV0æÃöC°ÃÄžçÕ9Ûf°E²ã´óµ¥.ò¤Â•&$·š/Å%ø0 ÊÛ—ÝöoÞ­ôʨ m2äå/¼öQsHsš$ J¦ý×½àˆøŠ-Á{~Tû#ÕâÛ‡fWZ­w.3ºÁ<¤ý#×6›Aÿ—®ÿ†?di訡a¾$(‹MÉq…Â%{©\x.o_—‹_•ÔUÛQZU·¬ŠÆw&nU~(–‹8\.FŸ¢«‚Œ6ÏU.Ÿ5Y%S8¦ü®\M8G¥Bàsq< ¨Îö啳ÕT##=Lý¬ôoöº­‡k*6ÏU@FÁq;-w­÷zeínæ@VÏ–Vìï‘Ë^ÈUsQ‘ÊËÚºâJù_w–ý·Giu}ÜSyÅ?`ˆVVTܶWÝ¿c}Ê…}ËeÓ#É>!]³F¾ãYÞÕ¿”EUù6¯‹‹x/(ÔòSk¸èpÌ/èœæë£þ˰ðL´`oáÌt]zyúfuOáâftw)¡Íb5¾%}§CrDaÜ0¯î'i"ßkèŠßȰÐúe‡‰cú*‰ux–Ö˜JÐAŠ5Ö£G-bX‹~Ø?Ó‡=» k•·Ë)gU²UßÌbC,(ÏûÄÌóˆ{Äšó-6¿ RsyKkã'¨ÞŒæ®\e–[ˆ\1«y•ƒ1ÙKj`m˜š8ÔÄvÔféåq¥K‡™T-SÎv,ÃôþN3îQŠSò—µ•ñî¾'„vT­´URhÑ1*R¹7Ô¡k.;ù§¨óZá™A,hòÜqþ²ãdÁLp€Ù)Ó$~G倃TBm\^æ\Íÿ1‘tu¤Øx!Ã70ï÷)mƒCñm¯ÀdÿuêYIhÜ â ÈqæbG™§éGD€Ý.ç~e8w©sxyŒÄÏkñ*ÔÑ Å@Å7-MF\ŒtÍÂE©KY ð›úë;k÷Ôn Šîÿ¨—Ú¿¸‘vþOþ&u¿¿läˆ w ¤‡A†…׉~¡Ôbµ &!ÛÖeÂ.N©Uû!èÅ\tÿÛ—œÜQyßí¸=Â]7Å„u5ýu(ÝEȹñlå_šƒS[¾å†âìYŽ–§£ …;ž íQ´p~ÇúŠ/y¡§Ïõ­Ø¸qÿP÷fy±üA¢tƒÆãeÐmºýB/X5£˜§¥ú®;DÐâ/ƒ”RýeFÞËæf€<ž¦ éÐKâ.pãP'ÛSX_bÿ™‚;ãÉ:¼ô ciŒ§pÞå©cê&ð® Ž–d5Cœ±e0l¨úpì±”P7þsteã_ˆÄhPÞK|¬ÖF%²ÔUUñqg쀶q*سÝ[ýDq8þYÄÚ(õ.µHú?¸ù5Öx‚–°- 8{›ai@Ox¨_°Áá?€3FU9ùDè>IŽ:Œž«×¨,\0¼ Vw oÛá[AvÞÛ…Êëžýy•ròEÖ®Wû.¶»@±g»«”ÛO´¥yÄÀ +EX7j%Ù!—eÚCPŸf±HâUGôÊa¡…›qüø€M4¼5âí¿~g5Í{qvy™ŠKñ/ø„±\~b2E«¢·÷'x÷>ά”d¥˜]\[g–/–_a·Ôí!|¬:c®RÞ¿ÝÁV#Gû‰XWTTR’ŸÄ,÷Æà=´ñÄÛÉ3ª\Ï1 ¿y"TóWn/!7'Ä£p³“—0ŽÙ‡/„¢°ÈôÇà5PLjWÿV(R¨òRþãÇÆP5¦[…Õ¦C_pºÙ[î4èäZ¦ hŸŒaQ:nq~É›MBÕĵíÌ 1ù¸8#*ñ43iW Ñό̽ž`†ß)A2+G™Àº¾|ʘ2 çÉp—¢'VÀ±Ù¼ ŽH± *§ê8µXÀõŸÉ2mþþ0Ø?ñ@¤ìˆQ,¦³*%$Pê³óS¶Ï9+n˜¿ÆVÙ]+;¸Ìd#·7(9ˆµn““‚ßsù†)¸¨Ã\Ô-ÔÊ&U-_¨ïôF\¢ïŸþÑý.\¸"ÞR¡¶æ5RUGÈAç¹äüMJ Qx”Øå:5ÌÑ¢"q†e˜»ˆRÒ-žËßöŽÖ­¼'dçÌßÁf½Jåä +L_ÑbqŸ3žn"ðyöy-%óR˜ðþërâN32¨’S†`æàÃfüâ!øi{–ÞÉERTmªa3tz˜]Ïøeäpþÿò=»&ÓdÁ´"Úàæ?øõòGé"Ûé‡ßÀÌ ©vþ¡¸ çúM½Åêsërùé÷9(YŸ7ÔF‘y?E†-j:8LÌÜ ëætO¸eå(WP‘S^"5-1·ÅÌŽÎÈ–Wc?‰AC‰‰ü%—›‚Ëˈ ì+÷ÿ˜.æfJ·©n/¨ñ‰eâ½LÅãÜO/ý™ŸˆùW¸6wL+±ø:ÃÔK1x˜ÂíùšÖžÉ!ÑŽSø– òœšŠ?ö-1ár‹ª€]56Èû‡…$ÎÃ(8ø!ØYÜ^L¦vnoÿ&›Ëø‹\?¡†¶þeÊ΋Åÿr°`šÄyÌhcs1 ªó «Dñ µ¯¨S„°õ…<Ḛ̈‘+xñ®&W¹Ë^T]ÝKõÄöE¹Æ|J 0¿ˆ^5õ9°@V¯ÜAÁñ0Æê.¿ è©òf6>Ø{•tI[?ˆe×Ú h„µ¿”©ÍÞx‹ «˜yöN½“6k>`MðÆÎèMÂã³röAÅLÁ¨%3õTÆA3ÿ=‰Ö\àLõËJçöa«/Ôg~¡ Î Ï M1Dnø~*-s3N?„§kæ íܲ«w ¸fFªwúúÁª%¨¼Ä7ñ>ê -p8…;×ÔÀQ~%l ˆ"ÚD¶b8Çóý¦š¹“u8bæffóR¨â Sd/¯Äl½J,Yĸ¥ßRËâçBåUÑ*[˜9tÄY=ËUìæ-™ô†òu)n ÀàªÝ${ãAi¥´ã1™XxšâÞ὿i“ˉ¸HºÚÇR†³›œ×ñPG3†5Á‰n¸ÕÊÔ@%לû€UÕ˜[T—ºþ`x±ÌlùŒàFc¸^Nf›<Å4ÕÑÜ£Ž]Ë´jÏj¸j§,06¹æUq3³ˆ‹%™Ý?Õ,ÛX¥¦ÅÊDfDüA³Tï¨ –»ŽúkUÖŽe¹«Ç,FHÕVyT U‰EÛt[¬ó).#SÑÄV°Än'“ĹÿÚ {e¶Àk@ƒ ¤X-¢ˆS-,$›€À%r›+:™P É%#È}Û>%ØAÛØIsì½"®l è£w2ÞÒbÈ€V0Ó6aMvàžœ±¶P6²{AvÐ"ù!o}Ü,¥4u)d~%všI™€Öª(3¦p7ñ‚q“?kiÕ lz0Ȫ¹ÐÀ[dAb  ”™QߢNBÃ@†ð4¨T_ðM—¼Ýt–C‰zeÜ(J%ÄeÂ[F[Íòýoò?ß®?’»—Ý^Û„4ÿ¬öÿˆaœ%§ñ'ÈCYæs^v¼qo8£ ܸßÀ©›€#ý è•)_b¡-•’¸Ù¼ ®½8b”Y¿.üCÖáAûhóP6“oÃeݽOüØ{o*<8= ÚNÇóñ!ÉB, ÜÍ›ò£­—Û9Ñà™Ð«²µ#c_´ûm‚ŠÆö?Ó]/ž]½ë© ÿ ÷—êI^É 2ÏîÖÀå5aí WØe’RtZŒ“ dA½ód”SÀ¤£ÛHHHGÿÄ'!1A Q0a@q¡±Ñ‘ÁðÿÚ?J| šŸÀÕßâ¿:Q;ðG‰ùi9ø&øµøÝFp'òˆLÂH„'ÍJ_ „ìLN Ü.|„ÆóQKìM##ôQ^üIB. âñ< E˜\•›dl¢Êr@Ї8ž/Bûó¹¥ËT‚ö H$ ñ0Þ½bø²æŠëÁ}%8Þâ[Óžà"·Âw„bø>±ÑÐÎŽ2ؘÝ‚ Ä3ƒùÒ¼íèLÈLBabûÇ,…p׬?µ„Ç6ý"ÚðΕ2ˆJô5¥_Ñ9µl\f9%Á'•Șô!+Áª#ŒLLl~yS[¢FûÂéH‘NGì/ÐúXº³ý™£(„ØþŠìwbØ×¶6ÏîzŒ¨¦È¼&YÖ_>spÍD„‹_¢¨«^¢)ÓC²9g9!(ê)Iâü—Žˆv<<y2G ¤¨Ð‹G±iàC] ÜŽ D·ä_€Æ?c•——þÆ8é#‘©ÆS›Ù«R vÌþWä“k5ÈU7躓dÊßpk?¡5›Ù x6ìgÙ¢gv:ig1œ-åþ)ž’\HUI–‘6BFbA.ê èoô×ù:_cEo±Š’£}=¹\ëþŠMàä>… nºhŽÌj錌T(û{(Ø\!mFp„»£CB]£}Ÿú~‹ô6åý «ý•ûÒrÏ¡çòboN´h†ôo¡¥µçÙ;l¶\݈¹v£j OýΈ7èUæ¤&Эi¦\˜r;löIj°¥É8ØÙÖr‡¸†•4†õ8;Ôm¨Eišy;û´Ód:A[Ñt[²Îpþ²µáæ’ÙÚàÐQ9ª˜­_ð6È·±£øð&L˜Ü{÷Žq¯F¢¢SLm² ;~¶XÞ^‡‰|g“½œæëÀƵ%βø„hû†ÛòbSÃhfËqÂñÓ&ÃËhCÞÆ;‘aᆰéŸ,Mp/¡4ø#dðHˆgDÑ1gÆùÇèë'þMcE¨7ÁA mœ?hÙµ_ØÞ1x‚Mð&+2É>V·–A¡ìI ˆB 0ªŒßéú}‰/g„¬Oñ>s2„ð„!BŸ"‡~<ÑA,B0Ÿ‰ß”ÙvJ¾Iø(ïÊßÇ<6”m"üH|‰æÁ;‡pøÃÿÄ!! 10AQaq@ÿÚ?lxœŸ‘ù/à&.Wñ¬<,\5àL¸¹¥æ™x÷à¢ú45Å2‹+„Ê\Òˆ¾oæ$ºJR‹(œe.fo WˆFG‹r˜Þ††ˆ5‡Ææ22?Dbh¨Žî ?œÔQEe)^…âA¢‹,Ÿ¤OeSó?#ù+ácJi b”¥.©ƒ ·(¬eá¾§ÍÑFÊ7;z²þ”„ÞÄá”OÐú.wÁá k„ÂCeK;>w¼vt-ð¥Ãzâñp´]ë+hë6vCbB>âaÖ‡°&z'"º•YD(÷ÂñHþØ–²õ±°÷Þý!öO‚bžÏ¤-ï';D½1¤7Âb:?„ú¼Þ¸Â{Ɔù¬¯øŸKœÌá ™¾+ÎÂã<—…–7EžŽÐÖf`‡ßçÿÄ&!1AQaq‘¡±ðÁÑáñÿÚ?žÝ;Â'Bítâî éw÷Àe;@f]Œ®´òÜHjvüâØ¼íºƒ–ð‹«×wŒ~@àK®®h!² q&sJÓçþc-ö:ß§T-QקǾ ˆiXq"³ÊAãµ»7÷Y¦=uÌýýï"©XØæÓçvïÏ(…|Ñ¿ã*µw{g¦U㪿_lui6D_&n4phž9ÖZ ”cçOÆ0L! uùãÕ ºØ¾Ú~øN°è.¾ï¾YªIȧcÒ©ÃjcØÜ¢¥`å”Ã.Ý~1E7!|u€BÝŠ.ý±uV£‹o2Ûß5*0è—|X“°3 &ï7¬}§^_¾JˆÞiüa$³Éˆ’¯RcÀØsµnD6Ó¡“•äž5¬;PwÐ~/çÓ4&¢«ýÉZŽ*ÿ0–4ë÷ÄTQ³¿Ï´É+O#§úkT Eäú^1 IXkH÷ßL¼u¹æû~2‚¡òF_8£ B‡ç7ÙÒ«£ÛÌDí}§d"añòa•kýýX9=X¹ÍYé‘ÈÙ¤‰Ç®8záîsÏîðCŽÚÅ~¸à‚ñ6;ãÏyW„N÷¼@Ò‚×]þï(T-ÃÚÎc€ËMʾžØ¡ ›4é—‚z{ : i¤È¨’÷oÆL¹8gß…o¿û—bž¹-|®-$.W¼r){C+~Mç.†ä3@¦·?}ñ䟟ç&–ên|ã^ ¾‚øñô÷Л³lö*¾ûw‰÷ų¡!®êŽåxÔë—ƒFØ#éÞ.hJ<ÄõýÞÅrÌeCåž9Ä“Ñqÿ&¶ î~øÀÍ;W«ùÉŠBÈ0›ß_÷lôh<ê·¬@$E¸Sƒñ”¾Ò|¥=&5ÆßUþ~0ô¤7sM9Ygcиå `θ…=™õp\M´¢¿ÎÚSN¸ú\ ² ò¢Ÿi)]5/“'.¯‡ïNJ`j5 k[_¼ÆJ•Âöýr˜‹½ÝcˆÅééÄÙ…•g¯YÈlæ†)B_|èÿ3cWäúL&s~»ùÀH.phcäi¡¢¼o °]R¡éÅñí¶hX*”ãÞ÷Õpl¼z¹S¢r=üu›ÄCNïøÁåð[ësB­P‡š®§­pl.‰`óþþ¬¶Œóù‡8’¨_~ñÚ°·xþþ1V¦îgýo *·*hQ5~¦ @M»^ñAh/lýúãì(‚cÓºÖ|àhe3É€T½kÆ;$l{=oóï…½œu£¿åb곃Ÿ÷âGÎ%A¨ž0$n ä!ö~ç7 Ÿ>uÇ -[±Sñ€ÔlN½Ý8r+žþ¹°à!ó‹ŠÄœŸ8Ð)Ξؕ Þ¿‹*Qó³ñ€B=‡ R/3— ðÞ·÷ŒvTäQŸÎ9ô‡zÞ4Jù7€!;í‡FG˜pᶃÈqxÍ@=.Ç£ñ†ÀÑÁwósP&ÝrL†›öfÊ=9ôÞQA4g½ôÁ`·çÌ·ã9˜‘uñŒ«a±ÿŸŒÝÂw?äÅRÅéÇŽ\h”³Ss®×|®œ@TjܺlNøücb«Ñ\ø¶?¬ÄiPÁ3”¹$JR$»wGtç›Õ„Ñ¿]àˆ6º½}ŽJoÊÓ›Þ2WŒÔäa`¾u†)œgYÃCã»c5ÁôÄ”,É,¢3ÜÉ•  ˜Ï¥GøYˆ£•!ûâ°v•zøÉÁ¬à×ã9°Ôþ·Ïó€ oóÎhD$ö²côîçoe>øz<–Ùí©ö¾&¹òLÐRèÏËŒƒR‰ s”ë_€Ä•°ÉõZÿ80ìž§÷„ˆd²_Œ rz„ü`î ÍðÉ]ËûraÁæuŒ W ÄÜÈUŸ§.$4§ùË< 1À¨Àu7Šã§Ã’Ƶœ ;dz‰áÎëêyÀÂs’Þ(~r¾ƒÝþÙ_`çúîB¡â¯àÂý^Oä 8'­…< óàÁËV+>Öc…ˇ“ûÉc¡5»B:)¾a#\?½5Îå Š$îþ9ÀìjOßëj5à8LH^t?›•h“ü8"ìG?Iéœ8ûýcÌBЇõŠ*Wz3{3^p6á@þúⳓÁÇÛ Þ%Õ¹ U®ÎoÚÙ#¢Þ¶â.Æþ¼ào0‰‘‚¨Œ¿=è&¯oLhSÈ%O®j•ókˆ^QqñÐÿp™kÛ8ÏÔÖ 8ìÈ#”@ÀÀIȼ¿L¤¡¤Å!(µÅkœ4„”ZÃ7çõ¡&¯\yÅûxú½|¡¢üúõƒQ¢ô:ËȺs½ûâ.ÐTT÷Æ„)¡?Ç*‰–x›7àÁK¡e.€áõÁMñÏûˆÐ«­ï!ºçˆ÷¼I·ºW&,¼áY¢ã–>¸ÙÓò¾Ý*—µGíŠâ©¼„ ÷1µíßÉûÞ2žÍ¯œ¶Eb8IpŒƒrbõ¶h6~ë Û©‚xÑ2÷/@0j¢3”>sœ“H.}õôÀ€žZœgi¾qµ’M/Û @ ^;ßA§Û`{qç® zuå™(ªR€Ç4ñQþ.D« øÿq°^w׌ €†,,þs a çb”h=.:'Öÿáë…:õ__à}px>®mÁö à›í„ثˋ«ZäýñømSÅ=½òæjKÛñ”Š|7Éêÿ. ¯&ܳv´W÷ãÒzúç^êˆý1X'±þ24qÈÖ7´v‰©ç,oxƒÎp@J}¾pŽ‘ áìĺn ®èÓ|`’ìÙH~WãT0 %ò'§×îIMƒ/ÞüÏLZ@©¬ ì¾³9 ÓÁ\ˆ ¡oÎoÅ.§ Í|¹Etû‰î\¥°lDÙœ¥ú#œPj¾†&õëõ£@èBJ*‹Ï[zCë1Nâ/æç9Îø:>¸›O„~õÔUX@¯¶*ŠtGE]{;Æ$4K²£òü`d×Ó8>õ¬tÂÎñtõŸoów½¿ë¤ÃWÕmÆ Ñ¼B‚¼£ üA#9Ù_"k-*•l´ ¿Þ Œw|¸kRuÆc¡´l¶¸47 f½2üÈ_Þer2ð­`ö~V2ެ³øó’WPŠvž®! j{íÇáoòÔ5A|dPa…À$Uàeßé‚ÖQlޱ7ÃÞBGa¹Å7¸&²ªõɧÛÐŽ‘ºWï°œáBîmŒä ÂQÉX LŠÆäBø¨YqqB—¶ÉyhâÒ¯•¡vÀ#O^ùc-”ˆP?+‰ZUMÛ‰îm»d°Áƒ@;.¯çµÞq‰©>–°au”¶*‰±O8±ÍËÇŒ;j€óÎQ;ð¼_lu8ˆ­PC¯ßûš´P©ƒ¡·½Õ¸“–ué×®9¨éM¾ï¶!¥âŸž{ÇV…%ÔñŽÇN÷‹UE¼`Ñ.¦Ãûñ‡‰F¢)IÖo4*»7ˆÜ B†ÇŸl$&`л_Ku¹‰½Ð~;c ½I«ì}0æ‚4ùõÅqO_¦›ܼ… ÆÿyÄ0EpÉýñôÉW¡*ºÃ›ë8Ëùû®*_e`þ}€}ÒNæáÅAé¬8 éÿ•E4¬‰SoLÛ¶ámº¼Sß×C}Þ2SÌ.¾ÙÙbkøÀOàIa¾ áõ[Îæ±êÔG™yFÆcènžMw0·I€5Ǿ)À[+¯|¥qO_øßz]†»¦ëîáJ ‡ ¿¾J%¤È4®×x^vâ϶%@«q?(ê•؇+¸cn@jhñ9ñ«§-À/XâRºzõ׆oiŠr&åž“°vw@îúã°¨ƒÝtfÑwcLDá*Ï<ì0d«(‘u7óŽ‹Ë<ŒTÊ`!Tô¹Æ+êÿ¹FŽ[U˜Y œC„ˆ8 ?œ-aM‘zÀg¾0¢šNq[Ï kí‹©Êîúq‚@Ö0ç$ tÓøÄPHñ)5ôÅÔ O—ÅÆ›¢ÇŸ®'‚¶tíNžø‡\¬ý§i*ˆç4§S{õèÄ¡·f¨Ù^À¨\}´ ò=;#ÎîsZ5é¦þ¹±;k'œ×œ7@Ç—8äúb‰<ÿÌ¢èe±Pvûà2Ð’ké±½¦„\§!ig8E{΢@9jë©ò®T“Q4¸ä3¶ûåÏñ‹_üŠ Ù«pÈΜA síPG9KFÅ!øÊ€£`\œ˜˜= ¥Ê)XlC.‹ÐIJ¡.¦œaبøÅý‘'Ó'°bpñ‚èJjOŽ\nñ°šßý3aÕ +V¡£°~™¢Ã‡é¼ªMB¿ 8^z~?5ÊÑ,ã–gX€ðEÖäûbíÔ¨lSé鄼<…Öî[p$Îý²Ç>½‚59îoǾ!é»ó¡ñ'Îm´%*»Õ…ÍÅ/C’ñÌG$eáhÙOeÇbÒ [tð£Îß©à¾Ja”kß˽ºË1kíÿ޵O5L[>HİË,^µ«e6‚Ó­OœS±) ÄÞÎ&>…㢽&®%•0!l/¶0› 5/äúáe¦£\Å;ð8Ú%ÙSõêbýé£Ä‹[ý9®i4ÏðÀP‰·¡u¡$“`«¯•WÕpI€+µâuŠ7“Øç™±®xÆØ'3Úž2s‰Ó”Þ Í[ŽÞ  ¼¸qo8wa›O ØÄÌ–¦#<7&Š ÑÓŽ¸ï¨Û~½²q¥÷¹4¾yÁhmop·”Fë p´|oä   †W^¸å³’×hÕÞÐУNmšž0@¸vj–MCWK‰°¨8Ä \œ2bá!:‹üLI*’kÂ/@©7ð㼞@‚KxÇ:Ó1à°•tzß\?E÷Iã¾u# &øOL] ÒoëzÝ€ëK ×E¡ÖþµõÎ)œ ®¥BëûÅMHŠ&µ)ªôˆ¡A(óCžzþpþM!=BÿrGu£ccúù - 柭ôÍO$ÍÇWzShÐåÆÅ˜álzá¸~Ü dM OàŸøÚNRA„x9ú¨èø£€Õ¨2©¸% !VÐNå¯ðJÁFz1Ô‰°ê¯^®v8ÐOmŒ`µXTÛåkp©©OM~óš£7àÏL7|&’<‰¬•(òKØã2Hn¯dm-/† KzÙÕÖ£éŽÑ‘­¯¢°Ù輪Jì½Åç-ë hØ'÷ã´ƒeòQŸyÃÔ i m±Ë±ìšŽarkOþ&>ùn#ÿÄÔsT•ó d( 1%±Ú7öÀÑ48(@ fR_aîµMVZÌo'C!ªð0zÇk X¨ea_ŸmR:™Eœ-}ñî÷B÷ÏÅÔ]QאּVðèuß?¦ ˰Ÿg$.âô|áèkb ÆÄ¯‰×xòò”Ÿ¦$؆1,l„ÃÛпOãäív¦Ï…äÃúãL§‡?&¿?øP4èè^1QV‚’)ôŶ ptë REÖøóèŸÞ d!6*>u÷ÁÑÀ+ÇwNL±ƒ dJI¦ŽÄ‰±ÈÛÖ<ãÎ8²ç_ø &†;­zf¦ Ec/°ïg¦qp¦KÌ Ž„Ü§ñz1ð1³€çÞæä ZYð¸RZ'½âJ NK°otdhYÆ%DG4bä7pÒþÖ(±âðà"ï³ù20d©ÙðÜ\NéøÏp)4üâÇï‹+ zöì0#bŽôÜ¿ÖM¼’¯ÛQ”5ùÍ„u?7 <¶âû J>T褞¿|««¹½YõýÖæ©!üÜ aSÕþ°‚›%oŒÄ¿¶BÊ8ɪ©Òw›)ïøOôÊ5£~þÜ àÞøñpÝkùöÉ …´Œúæ—2 +t^U×%׌·î8~ƒÄôUöÂp“Æ608ìßXBÞ ÷öÁà`¢Á‚i7²s”‚ÖŸÆ75ú]Ÿ ›w;¯ë9>øÎ:î·žŽA uÈd<¬Ç©U)ˆ[ëŠ'¬«v×§Ó €C\át»s”‡³kXU¬ÛG|@ZhºÄ^€†4œW¾òaiWÙ¯9¤"óŒÁ*¼FqìkéÞ\ÂñöÁx |eÝO’‡¼@ž·Ä=óAÒˆÇ\ë¿l~ÃԣΠVfÓóí!ÕÞ6çF‚œç+§$ëa³ <´5¥Àª01ý˜ùyå^ð9³;ñí‚mUÖ^Žã f°§m6÷š](D~¦ ¡õ\Lí' ~¸:k8²õU¯›ÁéDM¾ÿ¼ç0ä4kTûàvl8|`á½£WþýröÊÇ¿UÚ‚ û¼Q²ñœs¯”çyØ2¼kÛbpôŽUERÂ)¾Œí Á§¿÷à£W×xáM|¼zd è‡WŸ9 —I턱’È9ÓÞvp"š14ºzýý¹FiRëûȸSÓ{¼§ë•œñ=½0 )ÇŸLa-ãarï@7óèŒö»€Y0å¸ûo%)¶Uæ1£ZÒ÷ûÞ=QÂÏ8£T{M9ÆÕ@Tö8ï7aˆ¾òä…ÿ `¢ªùüå†e³¼çu¥*‰¾×çxP~-ö͆îMqØ“kÇŒÐE&°·¼šR*«=pݪè@ýµauϯ'% ;óƒ(‚ÑLˆ òTôÏV+óVûÆÔK+‰[íŽ" FÕ½ºÍ[=» *Ag'µÛ­Ïc÷ç<¥eG pºëýÀþC¼k˜»+F…°:Ä:¡3„èÚ¾r;]QŽ=2 ˜ly5žK½ú]a¶é ޽¾ÎHÇ×~Ù̪ Ù‡v”CoϧY eحצ Å“ç¤IϾ¡72Œƒ–®²Á›~s…‰Ò?öƪTée?Œx­]2ºúg)h<‰o'N±€(G $óÍ0(">~~øÀ±¸¤€/ŠÝûc,ÉK$¸R‹m#÷ŒÜ‚„½úzeÕíßÁ"zqÎjP‡eÛ  iûbC`«]¸cÝ]O×@èaHâ [»ñ¬äº|ã$=ƒx±ÄÏ·¯X ªãUè¹t—ø=±¢‚"L³m.Âß&Îð[J’šöo-ìX_Û—j©°oëƒ@U"h.ò±°AÙ¡‹¬)ç)»‚ñÇ?³¥ $<3»‚&½Xúc 8zŇ4M¿¦1?rœÖÑààÉÅn£]uðb{l#78ú|c@ûð/Þ0V€‰ãÁဦÅhÞ?}3~Ýqô|æEÖ_0£­`†eƒåð*PŽ(jŒpráצn±M»ã@´šçç÷¬BÓ‡Ã~Øã•äœkA5ÆÉ„g‡ ë9~†ÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/images/guitar2.jpg000066400000000000000000001270651510465702400257300ustar00rootroot00000000000000ÿØÿàJFIFHHÿá>$ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:24:58 Ðè Ànt§<ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?æõøÄQ40º ä°äŒsPA¦ÞO øœ(.N1ŠŠòÚ{{8ÅÔ7}çï8®¿AŽÆFtº*0 "ÈßÃŽþµw´n)+;\ʶŠ=æÂé$·HÜltgÚXt$Ô:•šêÚöÊD ~Hœú棚8šì” #) ©3Ú³ïlᙲvÂßì€3P¥Ô«+ÙŒœ3¡µ—|) ümÇ×4ûKh¡³ŠòwVr[ìáø G®}êå‘‹RŠIo¢Žyò˜J«¬ÚÀÑYÅR6L…'î‘Çõ«U4%Ç[éÐÜjOq$ØvmÞc0ÉñZë 4±Ã Ãb@€3t#oñ¬=*îKh¤~1‚Î7`œÕ¶56h!Ž£P `c½EWï;¦£ïm.'‰âHàÓ–8ÙÜ’08÷æ¹mFìj¤“ Žß n0;fº&ˆ¹mçýÔWvÈ©#—psõ¬£^´+&Ü‘"ƒ’? ÎwêtPåWgK¬hE²]?QŽYå0b qž9®SZ¹´N°{'Ý" YLa±ŸÆ»‹C§Á§j©z²¼ž[,l@ËtõÄjVömáx ¢ðÉûÖÇSӟ΢dJm=‰wk:Ý›Ù"ˆGÜ3¹°q|Öœð]é©,j¤¡]Àã‘Ò±t«;9ôk÷Ð\ T¶ >ÿҺíCIÕÖçT¾…ÊÛ1·ÍéÎxôÅi3HMËFVÑ/·¶‹,^w–á—ž€ŸJêo®îÖy§†YTHÙòÑñµsÀúb¹oåÙØ™ñˆÐ –úV•Ö¡©Ù³ÍÓíþÒ&½ò]ü¶Ú\‚Ÿ\÷¬˜¼/yB<¶I¥šO»…çó®ª}6[_.UŽ-ûU™@ÆÞ3N–`®b2HB’I#ø Voc™Uq~§-ã8á76ó"#ƒ¸ŒŒÔžÖ ‚Ýl§o+kn?‹œâµ5=ÖbYq´–ÉÛJäï¬í´ã$+9’é—€¸5Q’²:#$ãÊΗZÐVú'¹Ž)F£!È€õÀ8ä}9®N8¦Óõ$Y¢d–'R:Wu¥ê_ÚP¬–V{äŽ=³y˜ IÖ6µbJ}¢í¶K>öêO¦;UE_BcUÃÝ‘Ï뺊jQÅp-žòÈ,O ô¤¶»žÖ&MBv ¨<Š_[,Êð±g,çnyü*®¥%½Ý„²Ç"ðÃå'¿µvEìŽ)$ö6n!†õàKxîcœ(2MÈ ×ßÚ³&ÓlVè ­Ynœà²Å×ï)tJÒÎŒ<†êqå ~POZ˾¾76î­û©"$2 £‘ÞÀ§mxõ3Ì©ei*“ä+GãÐÓ5W»¼¶µ¸*¹‘pí´F;Ã}BK‚‹xÜý퇭jZɺdòn¶œmfS‚§<Spµ®+ßTYðΟ£uåK ÍV'-´“Íj›Kf¸ù Ž^Þ®jÛN˜™­B¹¹*ª™ëžk£"H¯å1Èv€?xج*ë'©¢Ø¿4QÜZ¤E£EHSÌYåa·¯= ?F/´û‹¨&_2ÕÊ™rÑŽ™>¸¬íVi`¸µŠGw]Š˜Æ1šÜÒtá¢hsiîè×W|:nLŒdÔÊܺ‚¹Š¶ðÜßÛË 0M?˜7yMóF{ä ÉÔ4µi"™ô{‹—+²í·" ùAaÓÛž(ŠÐu™SÃúDSÛM<’ÜüÀnÇ9à×g{xoôíFÞš"ó)cõéÛšå<5£[RT(Ï*%(§aŸsV.uWûZX^õ²òüÑÇ.äú{ÔÉ]•I\Ê·¹’Ù·Fv¿fGãW!¸–ëíiY·FÌwäŽj›(–à¬C‚ß-wWFÃñEy§Ú+ [h±9^ø&–Ìí©4—›8˜`3Ì‘c“V¡óô}J)5/†AÅt3é¶z¦›qªÛŠl—ú…d=ž¥©N­$M’>û Ξ–µæzìtÖ~):¦©sŸš|+¶0íŠè ¤Q•”¹”°kzt¯.1}žóÊ‘‚•l1â·µÿGxЭ“·–¼±`G9Î9¬Úkc9ÐM®SkXžîÚbñÊðð´ã뎖Oã›iäœgõ­Ay$ò º›ýDZ<ž™ÌVu¼/<èñÈ#.ÁY‰ÀÖ´ŠJ:Š”YØxnâÝ´³”adQ†,œïÇ\Õ›ï3þû“3p#.¬ÄgwQøçŠž[; -áHä3žb`¤4Û-ÜU®ÉZÑnã•vpXvÇ®k5ËC7-npÚÈÕ¤çK:îuE)ú Ç¿gòŽÐ QÀ®¯Ä2Ûÿk£Û SÁ± >µ‡{§_}š¹àسd¦’=k¾”šßcžI=·4ü;q!Ó®?| eù|×\&ßBÝãY>"¼‹VÔ]áábyw¯¥oKyöuº½Ô v껆$íÁ;V¾“©i’øuDp †Ú.Ám¸ì{š™>YsØVÒ×97²‰4æ’8Ô²AŸ189ÇZ¡¦Zy˺H$rÍÃH5Zùmįc!—;Î+ ÐL3Fc™Â ÆNHc&´wŒ/}Åv_¶¿±Iœ+6×YeãŒ`džÿZÙic†Æá]²|Ô(›=1Îkl«ysh.VÕömý$ ÖÔrÜÞÏ#Ê‘,@¸Æð;}q\’WßCE¦ÆU¬Oi­Ük$­µ¤„/ý4# “¨jú”*5ÜßgŸ'¾V¬kÏ{ª\Es;}šÞC…þï4º<©j k¼A‰,€ŒŽ;bœõfð"ºW1%²»¶…gx]#n0à×S¦ê0iž°wÓàœ—pLp\Ôþ`ñ;˜5;éJÆ• ŸLÊ¢ºÓí-t³¹»drÊå õžÞüRöfu¦¥êFÚíµöx–ºTVrG²<.~_›=+>ÛKºoôÈ¢,¥7®ÎJ’8úSìí-#±ÔEøºs6™N=y¢ÛXm5lãž0JÉlqÿê¥+§tUÚËs6ÒD·»ŠI£*6Yjô[{»_[þþÝÆ 2Ù8ÇZàtô·¿Ö’7O. …ÏOj³{¤Ia©-»>ä#vÿnõ“³vêuÔŠ•®ìoMªXA¢ÉbÖ͇sGŽ@9àúÖ=Ž»=‘µ H€îwð𬹥Ý3H€¨'"kÎû¶žÕ£Õè¥ÃÞ'¿¾}BñîdU ÝBÔ*7°ÏBy®’ÃÂMuh×LÒ,1#Ž ‡ãY¶Kg(U“x#ŸjV*`ýÔurxfÂ0‘ìE¬ØIè=:Ö·Ùmîn FÂ7·HÃ+JöιqâY¯£^˜ü¥Á |Ät§ßêëÓÒU0‘±¶ýäÇOÂ’ƒjç4”¹¹Y³â;‹Yd[˜ ¸ ¾YçàõÅrwÚ­î©xnŽìDw*ŽB Ô²;j "åüÇÒ5†e1[?‘‘6i2Ny5n l(ÍAj®ÎzÇJ¶Žö[˜¬ˆQÝNUcÇCZ-¬ëZ^š°‡ÈoZæúÒÞ'ذv”¦ÀS¡'=±ëPxl#Ѐ†>bøóƒ+ùÇN+h¤ÚOS’WÜζÒâ½3µÃ%´EDŽXªÇ¢‚jiô›yôÅE·š)cù[Ì\6*¾™w –žVèD"™<ÅóîÄ­l§Šîl4¸í㾋ÊPùK†#ùŠu9Ó²yZ×s—·±“LWžHâפªFJÔ¢öÚ)ÃØDR LŽ:íEƪ.RHm­üÛ›—%Ø®I'ÓÞ­Kᛨ4ÓÍ·+66ûSUõôøK_Û¶ñÜßZÜ ß¡SÉ9ôüêóê3Í¨Í €Tn?7`§z© %¹Ò·ÁëÄ^NÐxÆzö­i¡‚W¿‘!.Ñ1Hû§8ÍsUI;Zæ‰Ý~#Ó-ÞgÄÇ'˜LÜåO¿×Ú¹m>öM3R‚ò0 BáÀ=ñ^¯Ú¾¨Ãpb…Äφ׃Î3ƒKâ @Óô˧l—Q9Âu-è©TåÒ[3‰mNÆoI=»ý™œ¿”˜ÍtÏjÞ!Ól·—‘„bÚ$Î!\º½ÂöBEÓå’=ð­¾ß¼=pzŠ¨Ý ’fÒÛHmARæ ­Û`I•Ÿ8$ ç‘ÄÓã8Î:Ÿ­w×6ÖòjÉ25³[-´‘„ŽEn1ÀíH–¶Ù—}²:3™ àwÉõÔÎ[\T'ÊŽ#•H,[#s]]ä××öð²À<Ñ@8oaZ_ðiгqä•|µ¤9…biZ´‘O› •7aOu¡[INn¦©lP›ûMXùÆeÚ»¾lWt› KSWeGu X38ïš½®yÇNa`uqžqU¼/âÒîŠLÌÑ4lŠ¥°š|í÷éÝ$v4FÛÁðj)$³M¸”]ä*¶{þ56©öÝv(Ù­cžÓ¶Æ9Ϲ¨MÎu£+¨WžØ,S$m÷Ã| úw¬ ýnÎ „†;B¾_ ŒzTîÌdÞˆåçuûK˜”¢gåéC+±2¾InI=ÍOx¡¯â_’V%1é[÷škMáË3mnw¡Q ÛóF úf‡¡ßÌ’^dš]›ÁÄp¼¨ymßñ÷­ ¥’¿Ú•#!¶¸~½:W9üú|/f!T‘[–ïœ×P¾*ûO‡æœÌ"ÔÔ®xÝÎ3ùUsìqÔ£.fúý´š.¢mRs6ÔXÛÏåñŒÕ¥2êÚ„60Ò—Ø$'4º¼×ÂêèÏ7–¡Ã}èÎFTÕÛ_";Ý2Âu(Õe3•c9ôϵuA¨Ç›©Êã)Jƶ…àE]L.ªé5²’A Ÿ|vúUûï èZÛ ‚Ç-ä8-«½Tg°•R-Vñ$»ºº–ؿʔüùֹܪ=dìkIdÑí¼Ów¦”Žî&.ЦO˜ÉtµIsn#ÓÚU}¶éu^àãŒý;U>ÞãJÒ„¥spÓÜ“Ôc ƒXw:¡u¤Ëuç(TÞl“Uåñ2%î½4Û Ë«rlí™Ñ ”uQÁö®’çIxæ¹¾CHøaÓ¿SXÞÔF“ ޶-3Ȫ‹&H Hë.¯"WV±‚ÎÓ7˜ûòªÍaý§$eã9ÜAË(Èì*/}M©ÃU)1g±µ½´Y,mÊMŒˆÕËnõ÷Ùø{Î†ÎØTo²$l V\œ1*}«·Sg¦,’|¨êåòpXòþ"º};A*ÅSk d À9Ç\VÉ#í½:iz9±Ö—eŽÕÕ²ùÁ+ÍkØêâ{»†Õ¦³ò<´I«Ž¿JÉ»…’þFYã]»üž`$üq׫§êvð YÅö¨›W9ú{V5ÅM^%g²—N¹´‹‡ÔŒ2€yÈÏô®2ü5®­3C; ~` œ×{¬Ïyö95ˆü©/”ª1 çñ^težúô´²4³JÜ»’M+õ:ðñz¶:+˧ySs¹œ`NrHÅB`™d(Q·¼W©xcÃqB›L%Ýïp}3Wá*4¹£•\+$ĆRNTö¨RodS¬¢ôG¡u$s‚9÷­L¶Ô.?Ò¯ µ„ vŸ ®“ŰYé’Ëo5ˆH¢‘q­q'*G ñš«¶¡.xÝho_McöÇþÊVŽ+p&nKêkªÑu™õ[7¹”"¼-´î•Ö—4-Ôáæä–…Ûë›â°_j:ÙÅ»«ªºs/<õíZÚ^¡ì3Il÷romŸx†\òyõ­+4Ñ|c(·Ì†,³.ÖS©¬½[ÁiñÞM¹à¶.V-¿ž3šäºZ=6ROBýÅÌ-¥†—t¾^d9$ƒ’sëÇçL¶t>r¬Q%g®ñ´‘ƒíUì¬ÖËL’ I´¶,CÈRs\¾¡¢\&šóG6b@X¨ÈܽGqŒe£vÔÎnÏCsIµ‚katY%mÑû„=+Y.û™ä.²–&4'œúzÖ.xöÏm3ƒ+¢°+•ÁdƯiïv÷­ÎTO¼ßZu/Ìì ivrº¶éu˰0 •‰öæµô}ylÆ 0å€Ý·nz€;j˾…¦–òhU‰.Iä)C×@ü«Ë÷|œæ½Jž+[8çiŠJš|cîä(*9úÑ,Lm‹:¤?fºšþ#Äm\*ï‚ñYþ*±›I=–ûL6#nONsE<׫îBÁ#J±þ:ý+—¶µžî_*Þ'–B3µN*g¸a¡EÜ¿{ªÉy+ìfŠBG¸œ/÷sÞ§ðìjú;à žk"hd·™¢‘Jº2úî<'sh–©of%{éFéÊsÆjº;'%{¥í_Zp Ç`²ÚÄK¹sJåµMT_È@B"G>Nã–Tì¹ö®‘tfÖ€ÿZ¶u-rÃOä$$[_lÄÐÆSjÅ=)¯ì|›½ü©‚›'xükRKín;rnä„dä»TûRé ö‹›+vˆevÇæ«Gì=?…oyººT>e„öî QÞø>´ªÔWØq…–ç š¤×z„I«ÌÑ[Ÿ˜¸Ó v®¢îÖxìå˜NZŒ‡r0½8#=¿¥bj³BÚ/“pñIr[0ª˜)‡¦ìÅAs.Q¾(Õ <šÙã Û\:õçšç#FkOÿ٠s#êìþLcrÆ\úWcm¥øk]‡n“ ‘΃;1Ïֳъj’µŒ øš"ò/™P ¼ 3Ã#K¼¾•5—a¼ Œ3Éü)_Ã1Å~Rmʼæ>„ÍÓîWM¿HÕùîtÿõªå•ÌÔ¡;ò®#Ò`µ&ßÍ{iÎUÉgnÊOQX²éq¶­ö{hÄm¹p –ÔÕ‹kí8ØÊ\Ãvì–@Á0:šŸX¹±ð®‡åÛ¯úeÄ[Uº·N¹íYÂmzœÜœÎÈçïoâKb÷Ÿ½r›QÂä’;gÒ©ZéW–æy4Ë’ÊÈUСÃCØÕ+ÝVm@¤6ÈKH»Z0¹Ë{V„WiöŽ'ÃwnTá‰ô­9´¹º„ ¬f¯ˆ5Xÿwq1p0—+¼ 5¿¨Ø¢\i[ÛˆïŠ$×*‹€¤àäŽÕƒ¡hž [éak«`à•—œÆ·0Õ-am>KX¬®áQHÖQëŠMÍ»F×LM­ÎbÊî}6•­”Ü9»ÿúV•†íJæ)ï˜Ò)ó‘¯jÌÕ´³gºäÜù¬gwROó­m.ö¬%häd.ƒ;"ãý 0;ò*çghîÇO}M ;Wˆ]Å‘eIf ±Ã¨nõ©mª¥ÔöÐi1p„c žµŒºqº¹iVÌ•¸_ ‰3‘£ÒŸ§,PD±—Üȹp9Ð⛋z—m42uM}l ÊÛÌÛ·ÇoZâõMV]Vd–ddPA(¸ÍhiºN¥qi$ñYÌön¥ZUB@ǰn"h%hßï)Å )-ªqJM=ÉmðÎPÿÇã^…¦Û¡·³•$¼Î‚(ØÄ@R¸m&‘Hãg8Àô¯]ð½êæE¤m2A°ùGÈ­¡±Ï‰i»£/VŠ=9¸i|øläuÝ‚¹zã½yÝį3—åÛ©5èºÍÅÅôò¤oFa‘pK>A#Šóѱf_3•æúTOsL'ÂÍ|?~°Gw<^E´Šf—åRÈÁ«©6ƒ~`´”Âñ²:¶r{×W¥ÛiW‰l–ÑÅ3#ŽY–ÀÆyÍrÚÛÇ ¶d‘.àc–èTtüzT¦šÓsXMÎ\²Z‹§^Xx¢ß(·s|Ò¾r¿ã\w‰¬šÑQ@U¹+ÎŰÕ/ôødKIž%˜|ÅxÈ«Òßjz½¼Vó'8Yv¦œ[Ø_WäŸ2ØÏ…. O$aʇ`8Þ¯Þê×:ÅÌ-1`€&qÐWWá+}.=î+›ëO2à˜öÈÀ`tã=ë–›ì2êÂ" Tm­$c?SÇZ„î͹“–Û§§ƒî<›ÒÞ±Ýç?TCØ{‘Px«GžMNMNÝ ö—-½]2FO5£«Y CÄÄH>Ñ –ÊVTmá¤Ò$Ö4£ž¿mYßC7kk¹©£Í¨Yi"R–·0óŽ N@úšåµénojac™¶ì‡ï­cHkÍ2òKHmfÚÐÉ$ëò¨ Ã=p3ŠçîôI®ïno¬ä‚ñ#fu[V ÆyÚ¦Š—3+•¤`ËÍM¾Çz7üÀªŸ¯µXÑ<÷»’ݧ/kk’Igj“zŽhí/Ê–®eÀŒ#îcõij¿d²³ŸL³™RVK‰vñ' {V·OE¥ÿ«‘k´ýn®í®ÐTÜyˆîrÎ3À©tËIío·K9uQƒüªMQc­ÁwA.ض·pÝÇn•µð{XŠÌÈêD¤ñJãîìm}59 u>\yæ8±ÎGÓùÒKšZ):T×qÚ¥ô6k§ZÙL©[F ”¹ÆzjýŽ “Bö­ûË”PÒ(@U¿>ƒ<Ö ¾—s©ÈdÓZ„)êÞõðÞ­vó\y,1ó²‘ÎßZÚTé´¢ÞÇ<¯{×ÄI¦ßÂѸH‰Éêö£Y2êÑÛ.Ÿm ±II$»–cÛëVn4k8ìÞêâýÚÑ“rºdŒ€3úVzÙ\G Œ¶À­Ãg.€²©9àöâ”eÇ-Q\¼ÒµÂêÒÞÓÅqA +mL¸·øû×Ykaksá«É I2$Œãû¼dŸJào–[ ØÖ£®þo›ÛÔV•µû][ƲyÉ#s {Z$›Š³2ûW­­|ù@Ž+ Ç&FPNOlÖ}Ìpi×l³»û5Â)eXÈ>•·.™iypÖ…å­²K&Òcœ•™n4Í:áåPÚ…Ò«ûJ¹õŽÞõ½¬jšN÷:;UQ§Ä«e ë•ÌXÚ„óÓ¦s\·Š­ MCQ;ÿÕ¹·SŒãïéV Ö5+{Hž[·DB¾fã´Ê;€ã¨ª>#·Xd’çíu¼¿ê1Üã#±ª„_2ÔÎö½Íï iVS[ ù¡Ù•›ÄBé\ßÙK•»¦™kçÞ,šyckI&ï/Ž£Ÿå\T&™q¯\ÛI¨¯–”!Æd?SÖ¨Ýxàºd³˜ÉjHßÁ_4wjèæi’âÆâ%?+ˆåp äð)r¶ôw6¦œå¥Î®ïI°þÞ³’%X\1;!L¸=ëÃ[¹¼ñkOqndó3ÆH^›k u{¹n­ï®ZYÚ೑È玕Ñß_IÊø‹JA]®Æ$cŽýu­%§+Ý‘ZެéoŒz%«Mag"JÙTd†R}qÚ±t½{Rö°ï’e,Iƒœ}Oj—LñvÂæ^Y.&ÎÈÐ1Ëç½:ËÄúE½‚[Û٪ʤƒ á½XÕÆZ5s™µb sM±³Õ|¸§Kdf!•`sØÿ*ŠÝ’ÓO¹¾HQg…Ê"|°1ÿ×§ÜivsÛLÐêi5ÚYå•On@ô¨­o×ûNÁ¢a¹c9$Ž2~”ª{Ôôw±¥5iY”5+õþݲ,qDë‚W`çœãµbk‘ÃöKK¨fV.8ò*ýþš²_̬òIåHF%Èfúæ¹¹ÝÅÉ<§ƒÓJ)´îc+¥kå…ÓI Í•cy0bdóv3çîŠåu;×K•»`Ñßo>|N¤a»þ» 7U[ ÚâüÜA4JHó‰éÀädô&°¯´ýI®·ªÙKo”Øû¼ð0zÔÃI4ÖŸ˜žÆ*„žñ®o H£ÁʳÜ}«OM¸‡SŠûL‘öÛLKÛ<Çý\G=³UµÛû}B¡¶•¥q&q´ð+JÂÕÒÒ%iËycåêQõÅTåhó=E\’ëXÁm¶„¸ÀŠ=«÷ñÆsߎj©…-íÒ%ä(ÀªÚ|;ý°³årª§ 9íOÙÄ›9*Œß€®)½M-­‘Ýèúœwz n Š<·.yqùž¿S¸V|m1ä÷ô¬ÝÄ}*YKm në9Ø+zröìl÷¼l|,Ôc -{¬mMÞ§ko"Y]UW 'ÚÛgÎëŽd>¸®gO NÞñ™Ü@êÅqÔž+±þÛ†FÜ–Óc;»zæ¥J~Î>ÍØãÄEûFÚ)Â=7OO9¿Æ¼«ÄöKa¨É1,i®# rFxÉï^¯sâx b¾¤U˜|0Mq7™}èŠ`ãÜŠÓûlk:¬“€‘Âæ4‘ˆUxš§¿i&œÊA"“ÀLàz][.SŠ1ƒnæLºN¡-ìvp¤rFUÑB‚½Z¼xtÔ].ëí6òÄG̘)¸uo^´–~'Ky§óà&ÖXŒ~\gƒëRkßV¶‹P.Öö±Æ#†<‚ØV–wQšÓ˹<ÖwLκº•¤¹c?Ú§~V\çvêj”ÚmÞª[\_ÀÑÆ›$r‘ýÁ×§­u2¥ž£%ÒZ¬_iEÝ ¾~÷óªzî·¬2 ;‹ØV9P†Dq†©ÈïQ ý‘9wØÎ»Ömmàw‚Oµ4®puP=yë[:ÏŽÐi¦KpÆîàå¶œX×ÈÍfZéÖ¯ ¹™Yä™™ -»ËG§¯âOJ4ñsxFþ^âÆAÇaÜý*[§tšRÞçÖW—¶ö¥cÉɈçþU½it[M–Ú6,=YXtçÞªÝ ´H¢»ÓžHâ¸äÇ"ð3YŠÓM8Øù‘Žæ~ÃÓs—µÏÀ·RH¯ Nê1’N+¦µ¸º·€Gö<ó“Ͻ<9SsÐÃY7h˜îCÈsÓ#¼îí ë^‘¨Á9r–$œ`âu+ mÆ%P­ÏËžF=GjÛ e)ך’V2´+•´ÕÂ9 ÀÆÄöϬT·¹þÓk)†.ìÚOJÒ[+‹»¿*Ú2Ò`µt6zV³ck.Ÿ%äP¯ÈN7¾9ç=ë²SI¯2)9'tmé¶›¡:jÖË ;Á#'•Éø’Q-â…QG GaÇ:ë’ÝXÍ,²ñ»3€§éU--¤ÖõÓ–g 轇§½l­«&ÜùÞÈbkwI¢.—)“y ò{ÕÏ ,w*µ’òdؽŒ§ƒ­u:7…à°Ô-%ÃË Ž ÈÀíe=íšÅñg†î,õÆx"&—Ìxõô¨Mlmí#&â´¹jM&kÏ î²¼—í Õ‘9üª{_@.áÒ¬­!þÍb#ehÆdÏV¬½\ŸKi´Ù!iØîŽ,”b0qTŸÃ÷±¬?Ö¸ÜS¡O­m)ó%s8ÒŒ[R;oxJ;© À¾TqF"‚(€Û§>Ù¯9¸KÜ›9LÛäñ÷}kGCÒoµÛõÚÍåÆA–fl¹­ ½UgñeÃÇ)xn$]®ƒ•ëÖ·¡RJë{V¦¢Òl›MEŠúùoçH"VÄo…Üzö¬oK`g·K$‘0 í-”#¶äT©}%®§"cì±3;cŸ›ÿ¯^f3©.U»= 5i+½‘«~òÈʧ¯?•U´IÆõäej)—sŒFÍÉõÅ_²Œ®žÃ­xÕ+óžš§ÊŽA‡lÑ€¿zEÍtŠƒv1à îk›´’[hÑãÝË`9«ÃQ½eSÀž¾Ÿw8R4æÝÑâÔŒ•GcMÑI9V#®6ŸZóoÂ>Ý1Çñ7ó®¦ioŸ?é {‡¬NÊhV`d”çŒf¹á%†¼÷F”é:–ç˜_( vñž*ç‡]‚H·œãÒ]ZA "keÞ;2úÕ(#Š6Gq‚Ý_®¦a ´ÜRÔ•9©7¡™âM3‘ªD¡wN=Iþ/ƶ¼¡ê6ŸjFŒ@íÀsùš<ˆï!–ж|å*0¼g±ü5Ïè7Mv–ð,²ÉŒG61]øJÞÚ—½º0ÄÑpºŽÛž“.—,±y{¤ò"ùv«—ïÓ“XÓkhÒ’¤fœhœÄÔô©ï„Ü®šð-Åäh™ÉR«ŽrÞ³tË›9ˆ”Ú¤‘Ì1å°à:Œõ"½F<ºžl”®dÞZ:Ì—$q[l-w'fŸw®YGha¶A,…Bs£ëÛéYºÔ¯ Bò™ãƒÓ€qŒWE® t]EŠ(mÚ7·"KoœŒœƒß­ZNO”貄S‘•àÛé­üA¢–’Þ弩bê¬"®jGj/tÉ–9ÕÉ‡ÞÆx«ÚmŸˆ5‹vj’Ê>X㌢~zÖ=´¦ß^XÜF%YÈ@ÍÀ“ ÈÔÖI7®¦5gíÒ±CS´Û¯Amk ÌŒž W¡¿—NDƒH…–nL°ËÌžÿJʸ’HµˆçŽ&R’Û'QƒÞ¶-¬~Ø"¹ÑîÎ2É,¹1ƒÆÜg¥:·ök±ùy÷(ÜÛ]k×\•¶š&á™p£'Œ Ö:E„vAþ†á/˜NÉÇ]¹äóéX÷÷·š%ña8–àåg,2­íƒWÄÑ¥œef>w–‘mVl`çõÎ½´,ëIÔ!‘H.!U~@ÇëUµ-25Óí$Ù݇˜§^hÕ'‹Î–ee(¨aÜɨu_i,1G$@L@[å'ÐJ!6š1º»;-*(b± fC#ç/o­CzHr»öžÛ†3ïšnŒeƒÃödĪÎ7˜Àþ•ÒH:3/qƒ_9™MºÖ=ì¤@¨„ïÑkfÔ»žÇõ¬¸íÇ” 2OÝïW×÷L Œt95çs¦÷;46 Ô…”›|øÕ˜s»¿ÚVñ¦<ÈPQÆ*½¿öK@MÜ1µÁS­H‹¥OØXúåÍz”ùe ¹#É©s»@†}n$N.ÎÎVlš„W·Ì,Ã’OøÕù¦°T,šËþöqQ^jV—‚F3±“\¸ŽK?zìÞŠŸ2÷lfÜ|¤©gäd(Ï?Jä5Çœ‘‘Iù¶óVmu;¿íuóY•%ÎSÓð­[Öeı¡ñÏSN•7…«u{›¹{zo•Øn“²Y[Êäy޽}«*¶Åâ¹ßL'ÏŽG(êjÕ·RB+.Щ'ÓŦ[hw—ú„14·’gÊv;ús^†¶æúع((®¶ÿ#ÍgÖ¯nn.f’_Þ\²•ãpô¨ãÔçX"…h‰Võ£c6?•2€øÏ5“:]ÆÈ6á€Ý zÐd8ÅÆé'Y`¶µf@TÊzƒžk¶Ó/¢ñ‹=ÅÓCÝ”þtfVàŒçi'·8ªwsÛE§¾hK…ëí\Y.ÌX’I9&µ’±Éíã¯CÔ.‡µ{¤4vßh Àg5ÏʲŒ°ußÓ\µhNœ½í.k ‘š÷D“ÉRÓ¬HeõÜ?ç|IåÄH±–‰sœØ÷ªšd÷M®íœåÎwî'“]„`Í*¦ø÷g…ÍuòË V/âf)¬E7ѶrO§iQÞNŒžL ®âyb>Q®+"oê[¤K'”þyŒT¾"ÕEüËkn‚#Ëâ>µ§¥x"[×Ky–H¥ ;±èªyþUíaéòÃÞZ½YÇVpNòùœ“I<§w|pI98®†÷ÃvM£SI¿7Kı¿ ¼ã§Zšÿ›"’[.Œå¢;Á;^•ö·:TByWi“…\þ¦º¢¼ÈöªIr½{ll¥½Ô­í¡€Íy€:ó“]Í·„ôÂÌÑYD"Sƒ$®H÷ë\÷ƒ5 – ˆ›jÁ™]¶‚Ç®yªÞ#Ô5‹t²·‰’4s"¤C …)sI™J2¿*ÒÄzÔº7Ú±§ ÂýæB@>˜Óx‚DÔü/§ê¥ø\¬ª8*܃ӧ5Êø{Óëz˜·EœI!tú}jí¾·y¢HtûH¼ËU¸có.|ÑÓð­c+?B*Á;(»²íµà1Ý}šA-íÄž\¯$gÓÒªøƒG¾ÓYo @BŽ˜«Ý«by"¸S2ÈH™xç/µYfÕ^âr³ÊÝÈÈ'Ö·^OÔák«-Ü눖±ÁU½ å´ñ–QÀç±ÅV³²y_͘–'žjœ:}Ñ‘'O½Ü] »`"4R«ž?ÕœVròw´F»GJTVûÊÖ´%ÓҨʬ"±cE!M¥FTz ÛÑú?KãQ5/sÍŽÃ Ùçµ|ññ3ªâZñ@ˆº‹K!Üu®[(8¶Tníœ3ë”™w¢îçñ†ö\fP[|ù^èÚj]Ú×eÌO¯©û3?ÒÎ&z9!ô<™2çñÌ Ì¢q'ˆ÷N~.w.ØasÞ)ãóæS¨×‚­ {¢ri){=d×P0xd ›ê¸ÿNÇ®yÉiF<ÔÛÚt_gMr½³'iêq²cÊ3ùE“<–¡—ͧ€6û?Ò/;“v¨ó¯èÌU&”ÀÞÿ‚ü§”tŸ@Ù,ê[éêÍ4þ^~„cõlŒ¤„ç™EžìÌúM¤|º»ÓèìÉÌKMÄ®âÞ3l¼ÔþšÀ bt¦Ô3ZË@æÕg±ô¾ÊtÈÕ\ü½Œóq¹·¬ ®cGV*ÎU)°Ñœ)ôG$I®‘°íd46Œ²“¶}’ÙºS‚-Xîhûé’~ÐlÔ‹j‘{~ï'.xVø=ôœÃÙï1ôŽžÊÜMb.÷Ð|ôÜÖÅò#G>—S?×Wá°ü§.3æÈëâ|8߀™Þ34é°·ÐŽË?¦ù™•² Îä«ÈQÐ&ÈŸzQÏ¿¡d²zØù{”ê(:ø…¸û­àoÊçÖÓS ë³f  änºósÏ EUéÕk­ _¬Q3QËÍžlª+žó=Ò•â&¹ë7,¡×eò;Õ{¿)W•ìßPsÏl¤itUfZȵ(L ¾™ÕOBZÓa7§±äfûâYÕ6h¿ô¬¯Ï{£Ÿ”µRÚ3v[ød_KÄù½¹ÞØ,<¨9lòØ‘¼k5ä3ÏSœæ‡ÜeêuùÝw{x/+2‚ÓüµvÓr´.W<#íkåDÎÜ®¨ävŒ£¡9øÉ¸Å/¢ËÐg¯ï<ôí¿”g”تKÞºÝkíõyEÙ$ùŽÜ¼_In,¡I¼u¨ñH¶F««Nø­B‡P*þ‡åüÆj£¾Sè'ô9×F1~« úä»ì\Æœå¤HšŸ9Ú ½æÉHâ¶^oÂý‚áõ(Ï ùïª "¥V¼ÎÔŽD¥þÃ2q”PU–`v»±pêâï’¢'=+|Š|­VßõþRÃ< õ²¼©‹b¢WNQøöVÎOÑbæÏÒo^y¨?õu`š"-–›>¨ñ÷+yh´üÃîC&åõ¦Ä*à¢[^qXŸê2ÖjÇÈÜÌçÐæ6uÔn„ª3fµ²?ˆ+è—un>¥WÈ}9ŒãO¡ÍüÀ›ÁÒŸ`tÕ‘Ðgéüʹ‘wè\öm ˜‹ÄÐÚ˜ÂW¹ùNކ=^ ^-)O}Ÿy«k€§çD*Ñ3°¡ºZç.]J^ ‘XV_ÑÿÄ'!"#$%123ÿÚÔˆ› ž ©É§¦¡Éqy6ð×ð†'×9¿9´ù‹·vn#úèYIílÆ›u4Ï ³5¶XÛÌy®þs,,`Rç6Ø>+Ñ Æw¾•ªö¬¶í;¦Â`nœ"ÿÊX‘îÒ¶r0å+]esEª5¢ªé4kŠæd¬s¥ô9áQò+R J‰¨Nž`æ®’S!rJ{կͼҬê“£WH•Uéj§;'zyôüiŽ)£!hÒ•r+ çå²Å¡?C}þV:u句ÑU‘51ßǰ–K\°R²Ï¾lÜ\u#†µ&œÿEêf+ÒØÒêTÇqÜoy·+ßú8•ϚŠ^ŸÈX 8U%0üaœgçKkLRgAT!‡jºt³ÿ.‹L8Ù°kÞFáãÚ¨1¿:󡣚ݚ’:TvqvÚÇ9Ø™½GrsóŠâh «qÇ»t¦ ýuNì_E•OO‚ÌsQfLÍF°h&«ý œ»HØËl@¦±æÆ+’BÚÎ׌·õ‚׬ñZÛÖzŒeÔT`çºò<Ĥñáá!cÚÖäÔeƒ t6ÆÁU@úeÈV’oHí/§ëÓê,”³Cî„NµÂªBåpV›,ïÌÆÏ—§`wQöU©44#H9 C#X–Ïšl‰¬ælÍ+;ï©ÓÙ Îm,ø¸’•èÙ×/™jÕUƒÛ*¶m z­öçýÜ yëËékŸ5ˆ‰çúÊаÝUÖW+*Òy{¯ÊÊ0ÀÙÐe<`ùͳì.#d;w»S©´Bûv±B"Gè±JS52‘mÈ0…[2Ê [9u´“)öM{¸¤ZÌh¦:¬]“cc1™¡´Á”EM‹6&óŒžÈÌôiü g>³k‡Üßðâ;ES˜äò×–Å|ן7›šÐ6w3O8ÅZZ‡—œÇj®ü7õPÒl`†JÎXÇ3©] \í$ú™9ÍoQ 7×G9Ÿ™ô»ðRÑ!¸»SSw_úàGJìéhb…qfë8Ãhwc'1²©=T­=U´äi’ë§*ÉèŠr£'ûÚM:ú8ƒ£iè™Fõ«B 攣̳Y$ñf× 1¬­Zå¾f¯ÒLP0‹#žfà¹Vm¨UÕœÃÉM¦1YhŒÚ»¢KÆ†Š¯\f:×Ò—VЛª~ÔÎçÓY+šbÎWXƯ›kö[a™%×/t¶4†àç9¼Þ¢a£›}:²:'3§þôÞ(“®Bå–wÌø÷yÂíS&F&%[fƒ¥Zm0¯üN® '™¤#«Y†0árgm¯EØC]¬ð4Ré_Id‡‘›ø«¬Óki¹z×2VHD8È)GÞ;¢J„aI¯—ºùõw­×Ò.Ó´¯÷-°¢ÒÕOW’q­QÉj2¬qy·bwÖ÷iç+ù5CJÀ£z›è5úfLÓCÏÈWÅ9²ŒZ&¶ËQ¡J0yT¸¸rìEUZ‹±å[V¼X]=K ,Û`™, »Lå¡fΡÒ_¦=‡ø“G‹¯7噃ì¸ý ™úkÖ™‘ ]Àü¸®UÛbåÒ€‚ë’UZŸÄÒ×´ïPÜ·òéÅÊZFÈ)L]UCôRÁfŠç“!⽪zT!ÿ(ô¡ÓkBöLn- ›Ú<µ«Eè?.vT¸â ’dfŽk¬Æ|,OIoä.G22Õ3}€ÔuJgXhMDzEh ’Àv¦†ìä¶îª¨­ž†uªC»ÆjÇi“+\–ºž5›tÜP|ö/êó„âǼwÝg¹bb BÓK1µg*l¾:Š…;’ê½pÁHÑ|%5è7Rm*éõÕДˆ1>{¬;:oËôïÚò¯o'Ä™CBϾSôzukg7Ø-ëî[Jð"ÇLD,¸ÛéÑ;´P/·]Ò¼lÜx+fÇùÝ3åjÈgÕ†t ô“?6˜ëAk{#ByºKšJ¹ƒk’ÈÅï¸VVO6úÄLÖùËÝœÙ~¶Ôào¨R¾x¤"‘/œ%Ö]'‡ª)X•c¦ój˜´±.°§¾]?Å5íN£X/iÌo(¾Êq[áæÍdХܴèVh­R³²î¤mY›¼Lº#õ®‹·7±¨OŸ)9Æï¿ïuiÛ‘&­Iåh8EA“øáEä‘V–é}%6§ÓGÝöSSø©c²è\Gñ?ƒ;¥[¶`ÁÜ—¬&˜cåéûÅÖl•ò§hväY¨‹Ëœ•Þ$¡Œº™ä~cZ’|¦C¨±KÜgšéÎÖ·¯ö&÷¦”Ö¸¾¾°ãÊÓH˜ùûÒ©ÞÖ7zñJYÁ !’Ò–c-†Î]ô±é¡¤ÎÃ@dJ¥bû„È ƒîèùqD«H2AžR,´ælÄÄŽiÁÛÇ’zZ°õ@fØ“\ÉÔ×¾ÓºwWôFÍØPŒá%üîCgAp¬™Y*F´’é—ˆéZà½?½ ËrõïŶF´ê@^+º­æÚ Œ·5õçà#=ëRy_å—¾Ùœ2Ùv¿!;„¨RÇG}©»ÿÿÄ-!1"A2#QBaRq 3‘ÿÚ?9g0®Ð§ó1ô"œC–1fÂñÙx0y ÐÊCOŒk²q‡ÜñÆ[üGÅ'®"XU²gɳþã >6Ä®8–—1HÏûy•w¨ØÆã”;Cgñ=™™Sc1—jñ ª•â**~3Ê6c3‘ÌPì¸^¥È?&ƒ`v0@m¯¿û¦qÔNsÔê`=ÅŽ1úV üD¤pe®¼¸öl™¬ó÷Pú€Äø˜ŒbWž„l>à!HÌ9HX™®FaäñúƒŽaŸ‘‰I&^‡m¦eR=süÌœó+Éa¬c`:Æ@ÝË1Š×ÂÆ;¶D›Ú:îÙ“B5äJ¹n`ûÌ,vÄT¹û3{*õikúc? E”‚©í Vù²SXE†ÅSƒ>t'¹ueñˆ1ÄTçÇØq¡I‹jë˜.ÅL\U^q¶1?ˆFøæ¡‚Î9Ÿ*€ û+:‚aöæ5Ù˜»ç”p¸ý*±R¨žC7qéÛØw4(~#.³QªÂLFÁ‰û••‡ÇcÌeeâ3mÌofâ%`&~æ¨ßP“YÀ•7ÙŒæÛ‘+üDò²LÊNY€%Ca?«ê&¶Ä½z0â-%‡ÄoRÒ¦8"ZÄó``L={…œ]à]úâÂÆ"(ÉÑb)UÁžIÄ3Æ\.Ó×û£¯Ï×Që*‰ãþòãøàYeŸ®%N½¾ãÛ£j¹Á90Y¦3̾бîgyA<Å$;šã“ASn3/u±r îxßúåá‹s¥g*1ù1xn`9:ÍÈâùYÆ?O†@ 5U¶ÂyW|¾‚|fjW¹ãx•ܳ<ŸEk÷Z“8‰k)ç©n><Çr c« †îxç*V1 úÂI<àæ&"±ú£&RJY¯ó2Î,h–OôÛ(a•ÍŸü–'ȺÏÜJÙOªÏ)†x•u-([ÿÄÜ'sAP•Ú6 âS‚¥3Ü9èÅàƒ—±f2'‘€3(;Ï,üJÜÖr% Gòo^§ŽK×–û‰@^~ç‘ùJ©ß¨ÕŽ£¼B ÿ™¿8hkØÁZž¦Ì cúxµêwiò äy?&TJI¯¹sîÙýI¯aõmfÿ9†í˜4+]‘áõhEX6 ÄǶcw¾:ŒÖ}ÌÔQüŽ @Øu¨s<  {©äÖ½ÇñÎ þ%uzäõ<£Î ÉìÀ¡=ÏSà}xu8æ/\Ï 3rÅf#(râ/“ÈÚ•¤ó<ÚÐ Ç]<]Oñ>OÜ!ȳãÅj&G’§Q,¬ÖyŽ}p%Ä´ã>%“SÜðÎ-LÀF 9F\ÃáÒÇ•‹áRãމÈאּX òî¨â^ûxþ¿Ý+ñß¹G´òª62ó­ ¡3åªÎ ³ÙŒõcp|’{™ã·×¯©M«bXXÂÂ5ˆ¿”WÜ °â[åëoƳÇKW«7§â°ù(:ëµbV%ÿ©£ÜsµA,¯s”OÂ*sÌf ú¬ñ<³ãžFAŠëbì‘Äø ž ñ‰P‡êV4]e´Öuþ¥×½ãZλu¯7¬on"ð L- >V=˜–ц²Ça ÏþÆC•8‹æÛýÜÏ땉æ>©?¬k?ij{[ s‘±ÊÀ“»?‰MÝdò<‚„*F»tŽå•”80 ÿÄ1!1"A2Q#3BRaq ‘¡±ÑÿÚ?¥Ì'„s?k ˆÿ tÂÖ[öÚ ˆþ³¸æg÷1ž–È^„ðdÛx ‰Ì¥Ìe$ÅpÞ1â1%á@naîP"3x‰‘ÏÉY1°>Ûc¸ˆÀ’²©(Ãb1?8Ç"TVí´FÆæÕ¼þ“"2G¯ÄCNýÐr«lÈ€xöeû†ˆ$Åq…£v ¬#‡½æÒªƒ=CyOº÷”é¹ÔÖ .¨»Í˜õ#n1™å*w™hÞÂâ<S|ÈÚvõ ¹½áœLüAöDBDŸŒFé¦ó>j[xÖ8Ô•;)Ùa¿ø˜ d&ìn|F|9‰ôyö7ñ2¿2ýU;df$ï- 4Ȱv,¶æ#ãâ%6S{í:ø©ÛyJ­0¿S½Î,6ûŽ ‹Q¾WÚ)ÌmØ™eìXñíI:Wc.¿PX Ýlá™›Z#Kø3kf˜vn%Jl{„*—Z ™XåkKÔ¿ôŠÛZ Žþ –lï}¥Boa)¸$í´$Þðóíß´?½Þòä6Ò˜ÌÏúd¹©w~Í ­TeG˽D©²®PT¸ÄÆ8aKq8™.d4?ï9Â@æd>ý”›Ê‰½Ä染yUóùq.E¡sÏTê%f FçÄPIÞ› ^ÀC*sh‹üÂ1ó Nã ‘Ì m)óí- ­4ä\™]z]Þ !ÔB‡çO¬ŸŽTLõ6Ú(òc°¨;„_cϳ-ÔË* ™@n! Þ~ís#˜Õ‘ññÕ§ß¼}+u/Kˆºz·´uj}´Ì¬n´ê1!˜öì¾Ä\Ï86"ÆTÜ K¨ùJxð#0Ù·V²ãiM©îTo<|ÊC¤†ò«Äê"Ü!E®¡øš…ÆÀ@ိÒ]áÜÄûöcc/‰´D¨vØDknÐT7¹ó ª)‹¿2‚çµå ¤ïw¨vMu¥*tJd'ä§»·øŽùn`˜ªQ²ñìß/k[hµ-±· ;Îc¡C‰—štcvSgiþ<` WñÞ*9»nLÔ]€1‰ÞuI²´|e¢‚¾ÍÌÌ¡ŒÁû½¹XŸ#yNÙ¬«R™’P§LÙÁâuƒ0éo34ш”ÝðbñR‘9™Š=ƒL¯ua°Ž´ë²À%àkû9³ØC·²Ø›µñÇiæ"=³Q@Ô=£‰Jó›JatÄæwˆSœf6"•ÖfXói–o…ï+“”ºµ­+WJ"îl#몳~.&нg«j‡ÙÊàF¦®nÑ€#a:Ž&¸)m}ijl±Œ³d³Töÿ˜í1Ü›À6´Ä¹í…Yß%”N%jWQÒ ¥M%%Ãûžûî m ¥¬9ö¤Ý^Ö*[‚8§g|“‰ªQ{D Rͦ–YI«;ÚUªÔì«*°Aa †jW$ÈøÚv•‘êS\~¥JUljéªé–cêz¥3e¨³KG£L)æ+bo P;€ƒq)Œ˜ ¨fv âRZËL²J VëSþb¡¨.#T%,¼ÇJ=¦œ=4îâj.!Ü,½¥oÝ7öŒlßSKÞÄMM0œMô÷Ž‚¢â}·½„©Lb%ù,eJ¤]`%=Q¾*«·Reá¸1)$©U±°6‡RÁF[‰ÓJi´c±´M[d¸h´ÓRZ]ÌÔÖ§PÜ4ÒU²›Ì§P­\[ƒÄPª/Åàv¼Ë{B@µüÌ…¯)©4L?SR)ÇJdÛ+D§D›åG.wŽ m5ZŒN"Tª̃4­¦©©E)àJº:é¹Yé÷P׊ÀÏR#>f‹S×§ÝÈbx”Ó!¶–è‹óÌKx2ˆk+Jk’MMsH„§>s˜à¹ˆr}桰ɼJ•œß/3-·”šæ CÓ¡†ì{Œ®T%”Àì­Ûx¡ë Ï÷šjUñ>a¨Ê,%2Ó.â"|Åø•k"i âÖý@éÿSjDˆÄŽåKÞpF0üÖz“áI­&;KŸ�Þ-6àG£÷*iÓ›ÊZz•A¨ÓÓuTé2jšÌÔíqì"ÿ1Œ»Zó°ü¥äo)›%¦¢›+vq:ŠÜ˃ħU g©‚iµ„ÞñЂo•˜šº´Öêcëë¾pê¦ÌÛÍ7¨%="¼OO Ú‡&û B‹~¦Íâ]¿†\ LŒcÔ[Å'Ä¥òaxÌUEànÀDd æ&|F"byólG7'Ë„5-”ÌmÌ&fÂÈXÍn‰uKØlDej.Qï)½Œ]jºâÂUÖ.f¢ù•­UóæPÔT_ÁLÜM/§9Ôæ*÷ne%é±#`!”¨L…°a,)£ÿ¥é%Mœ^7§RåvŸ °Ù§ìÂFïÒé¯-xšd¤mLZfm®eKyeS wŠ»Ü\eoÿÄ>!"1A2Qaq#B‘¡±Áð3RbÑráñ$‚4Ccs’âÿÚ?UB Ý d?:FÔÙ›.·­FœêS†$~/ÊŽ¢9uð³®ØÜþ!i W)7¿ÝVV\rm°ZˆESv>íK<²«CBKcµéµ­k¹ÿŠiáy'Ò,–é¾ê|ýE2ìBµºWH×Ü ¸Çp¢ÿ‰¦ŠE¹f±CçFT&<€+-ZI[–b7óôü¨(|ZؾšŽqãqóoJÖ¬ö³Ý°òò®%®) Û–ÇN]O¯ÛH$0³lv¨¢‘ÖLlq#asRÃkÊäó oOÎoòÉÜRð2ñsùZ¤~ܯݨâH‚öØÖŸ.[°oRñÉ™~ƒabEDÓÜÆÒ§{zyÖz ÒCÈÄH§®þtÿÓÄʳþ4Œ EÛð¦q±b·¿Ùz’Xõa¤Øü£k~ïCˆí%……ÏJ(`|NÀ옆;'÷¿z!ãõµ+/Ε·¶BÃË¥|*ª‡>.$•¦‘¾q¿1Z…53¾Ëf®õcxï{÷¡¨SS$¬@ ÉÓn¢š(€‰AËCúÔ±™˜´ª9Û{ñ½)n"¬môø»Ö["²ä…‡áWdâ4q‚¼Ý«I67–åéµ|ï™Í¹¦õŸÅ—ÿø˜Äø±©ÆÎ¶ÞÃ¥qä+xW @anµ›â£ÓjÛÓÎûÔ1£d`ÖÜcïÞš.3¸-̸ý•óa# í½iôbc•›¹_ØŸö¥“Jüd?-’OÇéE™9ÏÒ/J±+¼·k*ím¿~ô‰g,QLݬf¦ÓéÐÄ8…¦õ;þUÄ|Þ?©¹ý+OªƒO†—жRà°ß½J˧‘Ñ·º­û ×qTXÙ‡ßVÓººÉb1½pp\ŽÜí0ú¶®è·ØTÂV‘Cãü¿Ji5#(ƒ[ ºï°ü©y9¯†âßm#í'/¯Za4®gXò´ž½©-¢“O©° '‡Ûj‚}L­Ácˆi $Ò–n> Šÿ¤W97Õ Öža “ƒ|¬Ø¿æ¯ÎD±ÿMIƒf—5­\Sø&ŠÂݘt¨£›PI3*¬Võó®'ùUØ_éâ¤Kb6¸éÛ½_½iä1±œ)~µü•ÛÞŒ‘Lj¾7í½.ŸQ„º$˜~=ë5 Gh›°÷¬Ûó¬Ã®Çqßî«‹§l—kR,e#P¥¸ä[·]ý@ûëMÀ™œãÔ^Æ¡þW1‹ Ý|¼©s¸6_!møÔšœ8Qþ~ƦŽkñå^ ÿšvD‰º§aQÇ$|Hõ'‚=hpF›>l»*ÓÿªõN›„T $[yí½BÅ/ Œ¿S€¿z$à÷´†ý=ºš:fù˃t4,¾£Þ›M©ÆhÛØ ãqfÔG‡.ƒ¥¾ðk¬ŒÈ×›íûµjy “—ûû¨–ˆäëµö©Zd⢠Yop2|F ™ü)Õ>V+{žõ9Ö4©ÌÌ„ÞÔøcŽÜÃÎä}µÁyq!yd;OÒ×5,zl ‚äÖòéM?\UTïýµ©Ôë ÜJV(þÞ¾ô’$ÇP:I×δó%–;Ÿ«Ó·{ZÆ´ÿ=£ˆ"6MÜ/§½i"ŽPÚ>+a˜Ùâ—WËŽc`˜‘øM¼‹'^a‰©>]h“ …íV5Èÿ«~ö*±ÿšBÀi[ší¶;^±fN0Ù&F}Å$Y¬ˆ˜©íJ¼ÊF7kÜwÜzP‡Oµß5•|ý:SÞк›saø¨\8!H$3’ùúQIäkªX€7ëRˆ±2F_*xºtðµ³`¹¶;·/CLb©'»_zŽM<Ë=Ô3…Ú¸(ùxù†öìµY¬.Ñb@Ûn´¬›¡Eè? oOÇÔ4 ²Á½h(êíÚ±|:r‡å÷Ri8CM,—ÇÓ~àÓÅ"ª˜Y—”u¤ÅCoѺUã —^½$h9œØVWÂàÇtëïo²¢P,†Å<ìoÖÔD,!œä”/;{oRp‘æ@>dŒ—˽ÿ‰.lNù¨TN :Þ=Ï^â¥`¢sÁì~íR¨`]»°ßo/ßzÔ9í!·½èbM—¢ý7ö¤vþâýMGÇÚ9TøwÚ‚,Œ#ÅXüquÄk[*YvTMîÝéLsÍñb‰áûª”|ààÝêxœ‰&Tâ¶ý|é]wå,},`Þ^ÀóÚö¡ Óΰ"àÎEšCõFCøÝÎ~ôcÒ®+WQ âç×Ú¯¥ù2:ùuû¦ƒøy5Yð^¾µÆ%Ld©¨QÛÒŒú˜ò[l ‹ªzûõ­F‘%h¯˜Ù/å±§‘d vÄï½<-šù[köÿ?…§åõ4¤~CüÐ ô ÷âmXå‰|Š}¿,w²!%FÞåY ¸V[ðä7cKÉ´à–á†Çñ­F¾ÛǦso"+ް´ÉsvÆô$x™Ýee[z_©©âðJE’ý½íÖ´ò²eƒXw©$·Wg,¢åüïøVŒÏ¨ãhÝñWAöyV_ÉÂñTÝzëOó#`«¶Pó²E&yS”“ýß…­Ú‚I‹I´ûÚ¥Ó=µGp/so³Ê¤13‰ÿËØ}=})ÈþS¸·^ÿ­ Béå;rº}ôêÃÄyT+Óv-íO/…”–­ÌGKôô¨ :±§‘Qpí–ÃkÔzF ÆV#m½ï÷Vi43Žü&½ªKrª©$ùŠt—‡ÿ¬~tˆ­yš&ú|Ï¥|(“6‰rî=éÕà›#uÛñè)mÉâ7½«KˆÍ¦Š0b{ò³ËS&P””›ÄýÔ"Ñi#I0Ô1Ým¹¨"஢¯6þ‡Þš ~Qú¸{Øë<-üVêiÐØÇãvQºþïùW1'Œ sz[aRDGà'­;Æ¥PôµEc‰³‹ÿãLï4L‘¥‰½:ÜRFy°D9ªê(`¬Iþš<„nÐSO¥’6r¼Ñ Ú‡(Ø·Þ³oŸÊG1óïSNÓ$qÆo+7QëQM¢‹‡Á••˜xȰ±&Ž¡µ"Äáó¯zÓhÍçøwbä­»ô·ï­MÀá˜M°ÞÇýèMÔ¯nŸ•.¶(–Uo,x±9ßÃÓ½NIƒ$ÈìÀmO¨i-žÇ„žÂ˜&XŸ«)¤#nczUdâuåóÚŽSó¸ ߦþFÕ$:t½Ûd,ºä#‹ŒAõŸ[vNä«r!ºúÕàìAØ]ª2 ‹"ÍÄqâïëöRðNM½¶Ç¶ÿ­,š‚"% å<ö>tºi¾,â¸,‰^[ízáuP=ü+ºŸîË¥4‡JcšF<½ .÷·ù¦ Ì„øX\Îõ6xãeëÖŽ¢AòÇïí¢Igÿ±2'ô©4ÂÖŽü©“ÄÏ#Òôô¯…šq§°âY“c¿ÓLƒ„·9dVçËaM.)6 ‘Žø•ò&²€°ÓÌô{ÚŒZX r¶_-–×û¯LÍ/bç‘ò³oZišQà+Ð ô¡ ëÐs0¡£)%¦ü²Öûêâ8¬¯”‘.[/‘£)ËžöãlW–´Úuƒ)cÝ£êc_Ìâe$Ó¢ieäW6ÊÇ­iÈ›â»0rþßuJdG!— Prõ=h@ ìîZöåߥ *p…;t¿zøœÇ<0ÄííRƒ‹Ýǧá½7$V?þ_:‚T[0[òsuÞ·7©çY%Ó"ÙóoÒµ!¤“PÎn"p.ûzT«$)bXJ{zWñ¦ ˜¿BóWôóµGº¥É÷^¦Ý™ŒÌ"ßøÔzB¢ib‰—A¿sûëR¢œ_P2Œ›öíöRÇÄ,0-×¦Û àä hÙzÞÔ‘¬¤ê årª\µù…í·ûQ™Úó˜µÿÍ$2ªD²±0?hØÚæÝÕˆ1Ä_ß•I/w;{WaŸ6æ†R\v‰µ? KØo‰éûµ-¾ÿÿªVÄCËÕ·-¾ã©ó¤›à`2¿ñ{T³JÆ«€ˆmë&+óò°ô¦ù2pÔ"1¿K~´‘"*Hs+ÙHî(ÉV…˜IƒÛé¨%I¤’I?JJªmãŽ@ÙÞõ«"'$åýÞ­Â"á·ÊÛ[í¥yY—éÉv5— †÷ÿ¶‘e'/ZyeçbÝCUÜ;[ζÛ,s~†ÖïVp ¥#›[óùÖá®=«UÄ+%HÈõÜÖòÇ÷*ˆDèä&ÇÚ¤Œûô¥Ó/DÝÿÕþßæƒ0º›ÎÔ8lu'0x m{úö±µqõ1p¸Z£ ùS2ÇqË}îÃì£ Äl×!•C§Â¿Ò=ME.”G˜[ÈÚcd_+TäÄ‹.>‚¥ª©U6IHëöíGÃiÝ1ÎEºõ7üiþ7 œ¦êäw­"䧨ÛcëRË)·-Ǧ‘ù«ZþÇzÛeDbß—çj?WÛI÷ZßôÛÊ¢vðeÍíR@ù1YÃí¡6¬Ë ¡·_1G„8amk”lƒ‡ˆù÷5§œ–“F@pª6üê=_ñ µL.ÆÂý}*)´y|ßšL‡dZ:_ šæš]¯ .ÃçéF)cÓ®&áP ÞÞ}êHãN3ø%2rÛÔ t½2Ææ9Ðf0æ…`¡×nA¹5Ô5Éè ¨4Úd¼–±ÅmsFûÈÛ³ƒ[ßeêÿJõ¾Õ¶•ºÑwˆÆ-õR¢‹±6¡tÏÿPŠù†o¨Èϸ`1ò§rÜøn6&µq?¶Ã1c\MðRc*ݘµqà£ÓÅ¢ô¥ÐÇ$Ù®Â5~”%”à±4€^äuü«RfŸ?ÆŠ$M;­”t°ÿ$Òê´°ÈÎÂSjÖ@†9` dõOJ߈Öb-°&ýmRjµ±—rn£~ŸeiàÒÄâXÎ%-{*Ô†ú£Žk?ÚŽ*Ø Âõ>æ¡n`då°QBßm¨tý5Ú••€C²ýÕÍ)ü©Û‹“ôÇõ­ÙMdì/ÖÂ¥Ž×Évß¿QRdȱÅÎ×ñj†C†S¶; ãÒäþúT š<Àõ¯â2+™€Ã½”Ý¿JQüBy>¼%¤&¥ÄÿÛ›FIæ·C÷W‡cËnàÔï<ÿ@ålqâzÚ–f¶©‹q2|Jû ŽÕ1^pW[_Øwµj$6¶w½­múQ²Æ1VÚöë[ýô,7®›ùWþÑý,jÿ Öõ¢xjª:ÜÒFÌÍf íåµ@…N^~µªÕ½ãᲪßê7ÜW•3ßÌ^ûÞ¡iNI¾q)ì…NÿÃßrü<û…¦&¹¾Ž& És{~&!‹¥È âëRI«à®®ø^Om¶&¡y%>[sOÆ9ñ>¯ARÈ’cRºŠžåQÎ=˜‹VÖûè[ozðФ?`«aÿ•5°=V–eq¹þ(wf>Ô“žTQÄ4±¤Žùa†ôÜÞOE©,–p7fíé_‘®2pMÁö8?ZþÁ8Ñ9.V¹;ÿš˜ ÂT;qNö>ÇÒµJüÀïzÁõ1 A‰M·÷µ ƒÂ©DæqæÔz‡`›e·/»ð£×ñ¥å>à×zð“íW×c¥bTåí\¼¦Ê×h#-ý@¤Ò@-kgú .©Ê¨d&ý ÑÈc”otj©,íÆá Ãú ¬6ïëûí@±$µ‚ƽ>Ê—øgñ ñAäß ºÔŒTM#Ë‚"7—{Ú¸’éZ>kªŸ«~‚žŽ­â–MØÕÈë^=ªñ9R:P‹W‰n‹)üxÿÛ[и#Û¥LëÌX ­Ò‰ÙX÷’RÄ=M ‰y?¥z-e(wN ýµ®—OŒpG6Dª_=ï÷Uälµ(·Æ8òöö¦sÔ›Ðk.mo°ÚŒÜfØ-¶ æj}N¯M·.-ò>]+â t£ø‡†ÛÓæL²±bN4rK¯š‹ÕÖöõ®ŸõÁ[‰ô>ãý«æE,gÍ[/ñCæIìÉBBÏbmˆQΚEÒ¹Xú»¶5$ŽU$kª"Š9 î;Õ‚"úÔ£,K\Žn¶Ç•,qpźj&PTžÌ?{Ýp¾ÅwµFc°»¶æ¤S.J=-_ÿÄ&!1AQaq‘¡ÁÑð±áñÿÚ?!f-†ƒ`}¾°_nѽ'ñ‚i¬[†\»Á¨à°‘7ôŒy“€IàU/ÇxÂM‰,~óˆðd›Ž9#ÆU4™!i·ëDÿÖð kÄDFa>2õ‚ÖÞ†”æ{Á õª“ür"0 Žá~\\ËO’ûÞ@¢Ùrž<ÏÞÜh&D2xæ0ŒLAéL¦¼dJŽò‡gÖ3ÆrD› ïœ 8g<”ˆÚ^q7˜SU“ &\†Ï÷xâ[B¯—’±åˆ¬ºl{¥¯à~†×2Ë Mr8ÈŒ‰ËðŸ8ÊS² ÷ =\k^vŽ£(©aSo¬t¤ ûÄŒN0¹„ï Æ¥¡(â19ç&‡GF"€tQ_Ÿ8޼AUrÿŠÎÕQaÌŒ‰ùÁ. ¤Wja5Èš cŸûŒ|ñp%Ó3­IÅeûË«óï"-ÍŸÄáÃ2j¨|»îµˆ œ^ñïä[ˆ–ÕOñ‡AÑM`µåx–áw§j~âQºBï¯x‰T*®—øœŠ‰ØÇ‡XÖT€©¨ñ¯Ü0k‘Œò7“¶Gí5‡h!4gÚe—‡¼Ÿ€ÑóÞ,ÒÀ„óˆë„h„]Fõ¨¸ËYÀE EOXÉ ³êh:œØ4 Bùç¼p;9¿KÕâ!9ØoÅ[ùYÔnòy!¦¿û7iHèiÁR;±¥SÈ1\xQìhu»ÞHïBxÄrfÛ´®¸Â¨}2_‘ä,†dE¢§èp-Ü6haʹ\i¡óû8»:`ÒAB¼oœÕd®¬ 3rÔwn…#š AÇO?™# ™Î¡™žÅŒ© PpîÁB‘ì«âsfÉÀÄ®ÂïÖÑÌL]îB[Œ9, èžFÍ@DcRO: á…*P"œ8¥'NÏ’ 0=3A ™cƒ¶=£c˜eôÜe{º0¦‰ya0zdLñ:ÊsÁteìîÑ$;7ù…$ðм3sP,Â_¿¼$<ˆTÚÿ8d±18 H+wüŸy0üÌ¥tëåÉòL)mHÝ=±”Jk”Œ»Þ Î6yp9Œ—2ù¢lÉ TŸûŒmN¼KcKç¼x±¢‚M|ä*™3Ô‡}â¡Jç±ã h“:0¼ÇœU*9:|]wuY/ÓNÓÅÎ=Eˆ]?'¢»`¸”a$%ÂS(a\Ó„FðélOîñ$ )0Ð^°ã¸B9$Â]¾2 û"_™¼†A5aåßÖ ŒR¼»Â˜s3ÿæOKåTX? þ—À«g!Ð!äÜRÜÌäYÁ‰Ç'ÿ0 Â=¯'ýó€Tõ¼MÃØñáżޭú¸°Â‘ÄX ÓëX~éœ1aù$‹Ùx(ˆÕ¡-mÅûñ$L[ÚhwŠþGÑT “ƒS'ô Š|Šx²FÆ{Ö 1 9$pdô§’V¾¯å‚JHè°þÏë6ªJ¡ßÙù‘×Ș}µ€¥³™HR“¤Å“VjoŸôá'tâ%ß¼%¹¢‰4¬)Á[Œ“b7ÔK(y;'Yðõ–Â67Üeÿ'Ozo¯ À Æ„#^9Þ-ÐFíi9sSd;‚Ì­×ɰȈø¹¦Gž˜ïÈàÍBÈñë ¾íÌÏæ;„ð ,œ8X£ .öã&¦Q°ׄ"I§¶Á3¤ºßçù1Ni÷¹Gÿ?ßÊÖûðz¼Óþ'—0s°~çŽSIH_ùØa¤ÁüøÃÀ‘Éœ¼e²©GNr<H–È=`A£hŽÛØdžøÁFð‰?|¿y!# {~rÑf!L>æòÑà•†I5 j‘Ÿg5´;ÙʺcLE™=AGuç&†=¶ßŒJßÈÊ“!‡c^2aËn½D'¹Ô˜ڪHžÇÅ1Ö#g%U[âò¼2!‘Ua·KÀ¨iö]ô§H¨B¬ÕÕ~㻤˜›Ó啾îóZ.öÆN" S& ˆ//SÒïy]­0@Øä3ìÙÙ1ãó"y¹l“oÑ’³ñŽ÷€™H ­ÞåšÃçã¸}âÜtzv‰ÛsðãÏPŽ ’E±R0Cº0ØOXd­‚ ãÞ[¢BÜðš.°„Ð)<Š,äëèFd@/Æ(·@¸zo“ ¢({aÓn,¬Š Y`{À¦”i¿ÂßÜNW ‚džfýd'I=ûôü$RóÆD7aQ«2ö8äÈœØÈ±8uŠŒÔ $òAÁŽ ‚ÑMÓ¸~0ìb-–ãøãE#32]á«7ýBñš‘-ð0W¡ë|d“RDjþq—Ì’ËôÃH!•!ìÀ$N"C®#xîÙ೘©ãÖAsxØ—›¿ë…"RFÐþN\Sa׉åx‡æEÍ(…Æ8ø5tzÁX6ü^W6ùÉôÚy ˜h€iBç½ç@\@[íÈN•UdLpâQ ½h¨1…ê_ "‚ ÷Y0A™“—/6a=R“å,Ts— ìA-`‰>„kžÁåè‰ÔÃQÅ÷8$6†Ÿ‹-eaKŽã²XXb2ßN$6XC€’OQÖ”\Ù°ÔÅÆAÌrVǨ¼ã…pAÁ*¸ó:ÄYîü°”º˜«qwpA¬í¥2ªó‚ @x¼M7ÒHÝ¢MF°cBP-º7÷´È ÀAxR}ý`³í´^ÿˆÄ0@dj¾ðf v/_ãÑà™ôrñ£o2ms'ücH5’ì(€ïYx9Å|ýãP8º†QÿÇ!z‘aŠhtS÷¨tѾ IxçAYk?цÉݺ`?G~2ãͰÞ#ßCQ¯µ|¾ò$./dñÎ2z!W!P¼g¡€žM”ü¾1à…¶q½>q·E(A-›W7cw~S}#â²úñ;ÎÎB 7·vNVËjÍ8iG¬“öêÅæùs„lŒâf„{#L¶EåΖ·üXc$ôåëw”7¬—ÀgžGÆ_¨òááªî¼ßÖDŠÌ'ÃZñ¬T¶Žµ_¬x¨À.Þ˜>À¤”Úšõ“tÐ>#¡yÄÆYQ¾údá~T^•ã08MÇѾð,ÃŒ—8Mî¨7@Zð ›t‰ôáç• šŸ†1Ø–c-·‡nûo.E»q:þþ±)¬{ “¹.%1Ì÷1‚µ¢ªÕ6GËéŽ~r25M%SaY°¹Ð O?ù0<¾ jXŽ5e±€I¡Ñ ÉS]V¶G€µNBËä'‘ºÍ΀f¹^zÀâtã¦P¶á8TP*xˆÚk&ŠR¡Ë >@`JÿÜoS²ó¹×ÉÁÖ´ÔÏßÞL!ºóã [ò€ÚŸ¹©££;Û?$’tÂu…FOò³LZm¿y¯¤GŠ÷‘dØd—ï?ÆSš®Þò@ÝW+…{ÇØ/’!ÈÙt&¸ÈÞ Hh·yÉ7À<ƒy⾈丸HíÄ2$BŠõeiÎ7À‡à`Ϙç°L~}â+Ò$Lì.i¦Åcz#F¬EDF[S~X‚6Eè/•àZÄÍïx¼i`±/hj¬¸wü2aD-paTÁõà#0¾X¶Ê”«;ëó5)A{0lj‚I{Ânmö*øË}áÀ48?¦v‰Ù$Ò 0uؾž—{Ÿ,9R²ˆÍWc¨„|,k¬[GŠ 2n¸ºÈh¨-ƒ£o^^Bˆðã÷*Â-"zÁÎà‚ÄíÏ´Úžã— ™iz<ó›8q„–‡!5ÿ0¹* Ï®ŒT·ß©Þ+8éËÆ &4b¨ ÜG¬aOA¨7ËÏÛ„N[*–œOiމ‰Ž{ÊhhŸçèÅä}ĉJ@ósœ'ÁðÄbAíti3µ`øw‰¸é°®‰ÖEÚ‘±É9ù÷„Z@ó*ñU4!~±qšbxR!ýï.Ô·tತ„š=»|GœµªÑ·(”Øf …zÇö·GœlÁI©Ìd”–震¹óñûÇÙç9¥SìNŽqŽhËÄP]C‹sÀ` t›UxÙj˜V,>ÂV=IÒœ1Î9âa(¤„¨–²Ñ¼‰ ÎB»QiÕõÆñIÔ²çoçÍXà ¨xN`N}^ìR öëÄäÂc6×øÇ@dÊßYD\º,ídzë !ÕCËèøÎ¤¿†QBð©{ÊdB—ì`¹MœÚ ea(õaij]Ê©PˆÅݬ˜€šífüó–¶ ¦ ]²<:Äžc¿ƒtl²E¤úÍ´W’t›5‘VÎHz +|µ™4–y¼UgÊÍT2±²#æóÕAPÏRéÁ25…‡g¿UG‰S’ k¼D´ùç'ÎîG÷Þ"t›~±ç3޳ä×Î r˜ Ló¯¿0ÿ¬ ËRIо±|¥‘§GosÆ*á,BF¸>™r³<è^¯]ázIÈÚx8Õ'C±|âR)–ûHGÆ8´NÓô^ýá1²|ú™Ä(²÷œØæäÒTã©w’‰#pò›ÆÑG8r\1÷ƒÈ Š?r 0ÒÏç $+?Þ$ŒÉƒÐž™l§¼×h™!èwÌ\`"u:$e]+k|¼`ÔeÏAñ•ÕÆD©寜<€Ñ­,ñ]ˆ$A9³ó7‚h‚“5‡#ŒRå€ùd ¸i2ö·‰Ì¤¸Ðátêr"°j+ðKØÄ¼H¹?Ŧæ!†¬¨d}ýB(T{ q™ÂjêëÖx‡ TkNPÓQnŒ©¯ E¸l;}ŸâlÔjއd¸k“QƒÔ6ôs°™©\äÌÄ4À~¦ÂÎéÄKƒ#Öoà®0´+Š".3 7lGD&òè©IÏxLD³ä¿h3qb¶ó6F:s(ëu3%#ëÖVænï§Yq—æ#e;Jðð›ó2ÜéãÎÈö´fÏfŒ«XQüÂAR6a„ßTûжÃp1s^&C‚½å 0Ô~â;:ýJx]‹W\’—ƒ¬»oë•O53EшšÎc–[¿=CÀ KMì—+ãˆl±/,¢ö’ebÒ~& ùFIÖ½¢ÉL<­ÅEw„oG<³2! w™ZÅ”AÂ)^0íwýQêN%LËM@²üýD¡šÔÅF""%¢âX–ÇžåC¹œãØìÊ4@ ’w¬yüÅIÇ€·ÂáŒBôm³qÙrqù‹À4õÄ7 Fɔ*"wD°¨ T¯b u0TE ±+JQYN."tÆ.’¦+® £C7õ™P®a‡€KðHBR_Ÿâ^³jô…“¬«x:™ô²Ãë>ƒÛó0ôÙËù”¶„‹™B£ª©réÖ}7}³‚Ý:Du¨ åŠ8B¨Ë‚ŒeÈYÚ¿‰ˆïà€Ë7L~WW/´|†î²´ì˜é}#з’SpY 3¢þ¢Á[`TW·Ü¢y&;vó… :B 4äëÞ8»Ç…ç(; õ2¶®W±gÜÅ¢ua†Rö1ýåÕ~mJ‰8|¥¼MV®åŸˆ~Õ¼Z ¯§¬ ú¹b_‚úW´fªëþy%¨™Ö`úl—ŘYóœ ) ï2Ĭ8Øý}D ÝùM‡Wæ3r3ã¿©Ò x|ÜøT½€ߨ8—D ¶7õ+~·˜k£lŒß ±öà «s4_©ÜŸ08Ëé,zæÙùe:Ž“*ÈD~gdÉêJòî>·¢lŸÄDkOꄤn +ž^&V­Ä/)-Wä«ãˆªÜ~!ÜaÉž3¯Ûí6åCäÄ&…Æ&V%EÅÝÿ!»PÎŒ7N 8Û›ædЫmpbrÀó×±Ìã¤BˆÅT028†ÕÊ”­£có+qšîõòù€WÙ¶ñk:骢["©i˜ËE²vêy|ED¥±Ì,%àúÔÌËWQ‘€:C12Ì,V¥Qˆ*[`ófŒ‰“·êdtWþ¸óy•ú~f¨‰ŠÀ ­ŒzáÃìß”%1¾* \/õé›Uîðǩཥ+ e`_"U`=gãûî+«P–t‚þÏÇøB „fd=aêƒÖ1Wp‚ SòœKÍïÆ¢¥Ýúb #ƒW…Ĺ֕—˜(çéú‡}$çìF\ÔÎLKÕL"zß0A ,ò’hŒSô “¦¯Ë§ñ +á­ß¬<@{=.»L¡ß1ÁÁöúLÚ'n¹Š †®Ÿb",â P¬ÕEîCdf5)m nh©%¨MíG5aÜ<å ‚Á|`â|uP 0QÝåú<ã­%¹Dï yË|þ%‚quÜXœæÇ2‹u; ¬©v®*lˆNdrw>ÈJ„zKJb¢úñ׼Њ_ÌB-TL*{«SÆ(ëàtïéhº§õ0Š]ÂÑ•ýÖ Duï™i¾ð&ã´•­Ãtñ5þ‰.ß}`€§¨~Ïh5¿sþE˜í}0p¼×ëâP ué¯i—Ò²R˜ V£Î!ʪûˆWfvàoÉX‹ƒœ[Ç?¨“Š POÿÄ&!1AQaq‘¡Áð±ÑáñÿÚ?­*Œ ‰h ºU¦ÐTB¸„)ºç¼JïYºÇ~" |4Úü%*š¡³2)0_¤à8š‹Ÿ¼Ë…Ú±Þ"äç2͈%ÄBCn¸(·™¯ÌÜk†`h[ ¾ sÚQcZÄà )e¿ì*a’ª*Æèº ªŽK-¢*)‡–‰­GÌÀMí­PËLT3>ŸÐ!²–S˜ÅšúD ukïhFÚÿ?ìpå­b8Öq SÒ‚…b,t—B\1&ë/d­†‘­` K65pí©ßå— Ö&‹ªÖ;Î?±{(Vµ³1Ôȳc'7Ì…Ø ‡Ã§˜ëU}âµ(Ya5¤X/1ŒÇþ^кÛ÷LµUÁL5no?:JNM"¹%B6»qÝý@!¹TVÑ¥FÆ$\gn°»y çæ:ÛÍbXÂ5ÀÞÞª·æâ}cÙ–– Ïï‚ ¬ßÜMDÒU£t—ïQl~æÝk5+¢«†€Ì!Ël1zlG«úG9èlÿe†"`*Þúÿa Ëøâ$BQ,f"ƒ0"6k5¼MûÌ´_M&ET ER¶”.Ǽ¡aaG‡‰‚ð– 0¸µ¨*xÚƒÕ3ô-ù€ÍͶ€nj|g‰šóÇOæ‚3аggΑ).ßsm¸ãv™~ë*—§–ÿP-È*[Z™EÓ÷\-a‹m¬\šx€­…>ñvÏx*š¿;ÍM"R.yÞd‘7Ž› `ÿ–Cb8Zâ0yeÙµ [3ƒˆðLé@TµJâ¥cê‘~¾Ñiÿ“H••7bÍB¨ócæQ)¯ö"4ÌØÔDB.°õØ—@¹Ñט"—æ;]‡K·ÛO˜ m3þ\ceŽðÁ„ ûŠ*H¼Ë¬’¨>!P';Å¡tlF¸¿€ÙI€ýÊ‹‘sæWvÆ"@oüÿˆâ“,av_$QÎ#6µ”hq§^=3™VXñ´I V1,ûfx¢ëÍq&‘ÅóÉVݳgý‰ †úcü„ÆëÙf‰SÄJj¸>ø•ÄQwu®ed5,×§É/¶ï öJ¸2fvÖ5sœþåP‚&¦¯âƒwB ËüÜI„V‰e¹S܃Ÿ-¬ÃXjw´41Òæ5퇱õƒÔ¦ÿvŒlë5TBâ痪祷ӉJ ¦{ÄKšŠüFàa™§˜ÍÔhšý i^«– F s¦¾t†A¿¶ £V׿IBå÷q(G]4à–…Ê]tvñmˆý¿ˆ+u«-ƒ…A–dΰ´¸C¡%a¾w˜DìÚ"PD3¹O~eù²ûF‡‡5¬ÌØÜJ®†‚¸…¨ÖaÁÐÐ?†IK×ë5Nš‘Ë©Íó-¬^.<Œèæ»|Îöä`'U«…ã-ð~âPMÁstCë± CK€g¯ˆaqÖK[‘ç*»Æµ,¶—„YPZo㈶õY¢¿ø¾Xsr†Ñ.­þ'TèÜrÙ¼ ƒJŠÇ0Ý•åþF‘b®²»Åâ°(­ÙPåH æ­üKøÛ‹ézŃO줢aæ+G9tí2£í=µí¦x]@Ï5÷ÖRÀKËWêÊð<‘A2Ûk€ t……Ùzç÷*B.ÏÚŠÙ†ôüÃ{÷˜€YO®°@*¹©¨e7.jO00åôF°«XÒãÏdAžMß·öu§´Có3QëþE¬W®ý/lÅ¢n¶®y†a€±‚×§ù7Öƒµõóù¹OH­þþå‚éwÓY§qoÜ.¯Þ5Ø:ËÅC¢Sž`€ã3FàªWqJwnùa4g¬U¬«û¯ íe{ÿ~&O,Ò¦¸½»=¾ÜyM(Û´Í‚Õ8Öû»JpÆþM  hw?–½aDÅÄ É‘øó9¯¯ò#Ï8˜¨ÕŸ1Q® ¦pÐÉ·Ö:˜±¨Öh+rVo#[Çxº³gæ/ZçóZyŠYƒÓi€8™)xïøˆ(ÓÇYBÊö:Ä6Ÿ»ëãÒ[éfº×Û…AÑ,¾ÐA¨›¸¶Wf¯·DÝfÊëÌ¿é¦+ÄZ, •YĸTL;aõ—~±.êoKÜ¿Bå:·ž’òÝñŸä'¼PÁ¤¿zAD`ïQ®Ôá3Îq¯X² qzéWNÙ™:Æûhšb庫•ø#˜µ-.kDZ«¦f§ åÄâ/©¿h@zàŠ­ %¦¸ùšL›>IP$KG´¸‡±ü†Ñž7C–ß¡4¡»é€8Q¾ÿé¼—\„0¹°‘9ݾÍòàé leƒÉŒï ² µø™pç§_X¥pì4ŒÄ6CJižz?G„''¸ï)ÑVÁ¤N Bš*Šæ^3-qHœCë¯iFk°ÒW™¹T+ ŽtÖXXµûsy±+MŒõbíUw5XŒ=1$Hš#©ø‹Uz0Ÿ¿yv ûWî#ïtqbðóœnîùÖ`ºÆÄNŸ= +Þ¦à$H¦·¡§~î`ÙlÿÄ#!1AQaq‘±¡ÁðáÿÚ?šN©ˆ€ua׋ìîÔÐ7ãÞGg ;i›•2hFÌvÿÖrÚ9VŽXz®pû&õ2KN+Béï¦`ófF•Äà»ÊôÂè¡Ñ§ ‚Á¬"*:Œ±³…ˆS—‡¤Å^¦áâö ¦çVÊŽ€t€e«}+¼ HQóTëí8ˆ°iÅë+Ù¼#‘ˆÂ®Á¯ï ºÚÁú¯Sã€àf ‚ŠÉTë¸[é$û"jzÖ¦È j*GNœ%L¶lx¾!y%J )IDÚ½@ÎΚ K³]ƒóð“}; ƒªµ…Þ&ð„¥(Êz¥:ã[Ée†Ý- }®îu$(¦4¶žÒòµ!®OkÑV λD·Q8™‡tP¬š»h7<ô¬ü@ —¶Þ*bÊXÅMëüå¥þš2i£¢Â–Ê ýTüï œ­WcѶ!Òúåá1(%2*QW±ä D8L9(kÇÇÌн!û“g-‹–%6©ªS¶u´%ÎñèhÿAæöp@ «¡ÆRN¼‚`<*…«Ht îgµy%uT94. <*…âi†‚š¬œ¢ŽUFž­PWàŠM¶ÆS­÷=°åL0‰šOâóÐ|‹ŒWCsôêæ‹nð,”iM2²"UÕ>o|y‘hxú^bXã¦;×=h£d‘E†‹{ ¢òª©|Ç5¼JcHokÄ.«DR›‰œqÖüÀC̦@Þ±|H˜Õg¥N'ëa"vJ)1CŽpÎ .ËRÞå+aßhŸçÌiLž”¹œ @øà.Ÿ¼M¢@ 0:±ýS௠s=¶µó®PÅ;ýùßbÊ ‘Ð7>8^m£ !¯P,[.€ 8@iAÛÃ1<•µSQÛïÂRÀ4À„€¾ãùƨSÖ—àÑÿcÂLoÓÄ4S#ºÞÜ )ðhˆ8Ž¡c6ñ*&¯ôâ!;ñ=®é×¼L¡·%xשÂG*u*£WÓMiqêU‚i*ªÔaS˜êGíìLõ9¼Ã](èM÷>åÑÚ²ˆa½§~_€M¢fŽG¤áµÄ‰"$ÿŸï  ™ 7RøQâø2tUûŸ5忥7`*ïùŽi2³†Tÿ·wÛ— Iœ,Õw’s"ÇE5¿ÿ®]kJ%,‡EiÁŠÊ_t`3x‚„âêDQ Ë+HÄí µž¨ªÂi·—¡f2@ íS°õËI*%av€ýG³‰Y°©KD–¨Ô!Ì »/=F¼ô ¤kQS¶pGxIZ`*'}îñ cĸ#ù¡½¤d /êAò@NW°lçHƒM©¨!Tle®ˆfqòQ"jÅZп–Š,Äál [Ý©“Z´Â®ÝÒ¢ öùÁ¸ýq¥8j†å“5 u¥°_Âè^•ˆ›ÐNäÖ=áøk6«‚hþ„Þ@ŒdÜB” ã瀾¢Á;£xñWÈ OzöÅœð•^""¬`h³x!Œä¼PÈ=¹Z}‘`ljÐä¬ ™0Buƒ$4A4B{næÒÔ"I‹s‡S6ž ¡ŽÔæ’ãe‚_‰ˆSqAh„ Ð¥/2*msoº€N†šŽ`ªé, 1_dây7¹F8z8<Ír ­^ËÇȨ>¶uû× §VÿûÞ5)BIpY¡a ·QõÖ‘ïøG”Ñã—Á¨û/jAN2iOH¸qèöŽ–uFL)=‘Ø¥sŒã¦»„²·Z4e¯lÍN@då¢ Y¤s¨KGAÐL‹Œ†©UCöƒÍJo6ÃÝg/KwÑr4 $6ÆSŠ+¼º…Æûz'¢qHB„cG!ÖÆ­bé:8IàL޲ÔÙ_G &§Â§BÒáJ$r•Þƒ–ôTº0À+ù9KDmrf²£5'¿A[Òá¡{d]Çð}á¬aCuá9M¦T¢Qí ùÓó‘"2¬¬¬èxfÝO-(Tˆ|p!ßñÄxe^Ô®(5tCˆÏ‰q ZÛ¤S/¸%J‰áë‚í§Xe ·¦`$èi#±ç±` €!ƒÜ!òv½? sÈ¢ªHçoðûÅJ1BàZ4ùÅ(£Ñ"t„—'› òt¦” [ªY ¸C,¼Y */OúÛ^-Âí —ÐÑS +'¾]†Ä>kUã4Xô†x§÷Œ¶Ùª¤ ÿ¡8µƒ×Öš¿cg<®À[˜ ‡—Þ ¼˜‚¤ç(‰·Òpï¦h9 ùœ†S†Ý1Id8D \Ž,ð_9^Òj4mq ^ÁÕTÆ^‡„°¸)Ù%RZ€R9zq#w„ïx.>ɬуAƒX´Ñ,Œ6‘v†Y”~•=í'V—N;&S¥‡Æá®:÷ð〔XèwSB)Pȇá@äcx£  Î /ã²¥yP‚DÄÞk˜šôŒŸ§£×|‚e¶´Ô(6ýàûêqµ]Ò8ñì ¤V$=| «‘Ar¥³ãË @½>ÌÓyí—–AÓÙÜáÆŠ;*o}ÂÓ²æ…úû]ÝU¶‘-ùJžÁJZØõÀ4!¢aÙ³/Õi‡A?ЈJ1l×6SŬ;{¾\( @ôà¿Ç ÆI¥{÷?ª6•A&Q89ê¨k=Bö°zçCI%ÿÌ"²JíDa‘jã´”½E4ïÊ«˜JáE;›ÑÂa‘·€Õg¥âh/Kì죰7Û9_èªÕHÿôä˜{]Dõz¾¼HV¢ÃQ,o©Ç_Dá š(­J¶àɰ%É´"Ø ïƒÁ÷¡eHKPo} ìÃJtlåºK-5„"†šÖráºRÀÔAÓ3®2Ê”f˜:ØIÆhéhÁÂèWœ1@Ý0ˆ£i}eüÐ0;üHŠ®â­!ìÔüœ±Üáë8/KÑĈŽGî‰Ò$¡àcHX!‘®ËÖ‹Š „ ø—ñÞøc’–º*Œ¿÷¯¯*F‹Œ$WxnTZEgäN°f¬=”>¼Rž-ám‰û‰E€QU‹ nL­Š:ƒ‡¼L¥Ñà­&4˜â"„p‹8ZÀ*¤Ì2Äë…ú%*nw±¼ø ° F*hg\M{xN:£ ›yÏ¢&ÁX*Ysƒýì| €õ£MÚ9³ ¶½Îì*C¤âé ˆ) Û|}åÑh`@¿ :^$½à oŠ4‚,“Î@M¢æ³èÄGЊèd éÚ¿{ÀË$Ôý¯ç%’½>ÒRêð”M‹\F!!ì·ÕšDdÙe쨪Ɋ AhÏB2½IQÛ¸ÀÌN%–oÛËFVµeE2Ý‚C÷jçCÿ‹¿d¯n]Q´$ áëˆ @uºˆžÞÙÄi$…¢ÿ§§ ÕcHðƒi °p是 ¸Å§óΧ›+@óujñH&Ca쇿¿ßÞ.)GGà)KÔ奖a §B¸ª‰k… ‚ ù'&."U†¬ü¾ViÐø`r£S5 Aô58ÀÞ‡aQ.J…:àc÷â ¾AB›9¾LýºThõ0B˜Â/‹[ rŽï¡^‹À"@/ìâšÓ,az¤×Dú[yŒB “àžqÔƒÐ*› @6ðANèLüêõ¼Åk»i”äjŠÈˆC" 7Ô¶¤™P…H•ük6+CåÊôP'µT?îßx·ŠíE,˜•: ¬ã8_ÍÔ§`<3Ìc\0èa¬GÖA¬ÈŠ [X¨3$'LàÕ›Ä ©ÄòFHRÔ|.tð•æI«D-0ˆúŽJ‚©ˆÖ|eòÌ)Ž× e l¥N)÷Ÿ£š÷ÔzõñèÞ=`è@-uH1å‘!ÃÀU6žtn3ÒÐAìxFP E|TUP=!›«ÚxK °îï!ÖloäÁ†YNqþºÁºx½ï´‘1”M:¡cþkìW•wÎRe(¬ý­+ HŸNÁƲJ|=`ÎöÛ V‡Õž^3t¬O.õ£i¢¤ưucëæÚ?gûÀkmÝDH!¥š¼Ñ™›`œ¸‚EFˆ# °,ùRQØ #~puúšÐ Ô¨'ÿÙÇ`ÉÖ\uàrö`%D(i†$Î6¼‰‹ ,½(´$È<ŠD‰©‘­…l ]ã`Q¬„@­;KÎËò”²Z“±O|G§[¬CBD’tó×KŒiþËu!Æ™•zMÕM Ä’¥´P=CÿW&t;Õ]êÄbe·¨¡¨í$‡3ÝD{V‘çg_Æ´2PDúï†Á›à‚ªZo|@C ü€¯JÆíê/Hba<½·…˜Nd ؼO¼)cXïøÿ¼k:¨Š&ê+Ðñȧ:‰×Ÿ÷„à}ÀÞ/Â]×Z…¢˜DÄO(‚ËŠP´ñÂl%Rµ€+y\.‰"ˆUÁF<+0O¦¶;]3šÖoAì~¤%OcýÝ$, `¥·ƒK‹d ›aXí þ‘3T5 ¥Cãe#Ù±€¤õuŠʉßÏVYP+–YØy¼è¼~6M•öƒû‹@<ïgj˜Ð«¸²üã¾=“îR+ýó–µ „/c[ÇŽã é³ûÇ~ YÛÆÊ²‚"ïùÁõk”³M@"dÇÕ±ã¼ÔØ îN Øk`ô‡·ís£¾X(Ž´æQ˜A;ÈÈW¥)T=ØÜ©Ú;ó3œ7“¸cø‡4¬þ:,—‘Ü`v\›° 1$É€ûP¨¢÷éÅâÁÏT„"y5ÅËaõ@6Ö%d¢'<6b%G°WiùøIc(èB…ï{ögù×r1£EEƒÞH g²QR|eå]±UMS»Lv}8ª‹ú5G Å^«y]*ê U‡ø+sg):Ƹ^ö%(Ð$«C+Q,ߨ0î  ;ŠÄN :‡°•àÖöñ†)'^(nìtÍ8x©JJÈÜ Z  N—ÿÎ ÐH3øÔŒÂú¹ ëvðÏþñcÐHMžÓäþ¼v!­6›õŸßÍä"”‰l/v}Î1¨”±¼‡@B DÁþq’¹ùWAH ,~÷¶ƒÇšŠÍŒ-4ÁÎ[°2ÚŠBƒ_¬sŠ·¬÷Ê($ICáøBR‹p.µ¼ÉÝo€‡ˆ&\”fuõ ÿœ% –Fˆ"½½®%œÎY›Å«Úá£î&&¥HpÎøîôÐ%‚€<ð³®,zfZ‹ì0¥é£ÿ¡}yp Wédÿ¿ï.s`DÐ:ÿœü½½¤ßö¨cÊ1ƒ  %ÿ}ã*!ì’—V qû÷¶¦ç?¤§øuÎóÑú|ñêñtép= +d|¨¬üX€W 7g¼FNe@B¬F‘¼¶ —-°D?š’ kÓ힢…? ’€Á§"(†È$͇S¸RPßoI¯ Äås>gg¢BöJƒªLà IPŽ èQ ô•Œ(£Cªp·D@¥1Yq©pÿAîüLˆüãèˆÕíeþÞ6-©+ð ”.>r=h ?Ë5ÄLeÃ;Ùö<—EƒAc¥Ãþ¾ƒP(`'c.ã¦R¼/ Êà„t}\y$¤>³VÓ™GŒ$F ‰ûÉjŒ0'Ò­ýÿ† H–¢Zwß×Qp†0 # rà6 „è1:뤚 ÈÞûœ?Œ§@ BD`Œ¢´gÂû¤.Å !à)ŸõŽÒàº{SýpmNÍ n>q‚!›„½­g\Rá]Ãíú¼%bíþ ÿœq§ý?ÿuÌ‘„éÿo®ñM¬‘ßâA=þÑÇ÷9• APÛ¸~Ñö³¨É*üáxSKe‡iT *fða uÚ’ÜMqx!s*x^ =G‚éà0è¨sY{ ÷ß|Z‡uŽË › ù”xK6P)AB*Rm‰î2B0Dó™UÐÞ§³ñª×©È׆‹¶ÄP±ÒVN2Þœ¿™Ë}á“%+BÔ >½ã3–6¿qéìG3ÝåO‚/ü9œUuGô)&DÿËïÍŒ|Jò†¡`;Ø®Î0­"‰ =ª=qqÆ–Ðø?ñýåŠ(,l½u/¯±â`m+öoñ†8ã„¥ *£4œªò,—9^GÀ§¢Àª8ŒÛí%cØÇÃU§®xM˜Š¢j?ˆ2@xìPÄYºòCÄD'(X¥fyƯôZÐ =Ò]^ƒrÙ`#ç„Ói¿Áœ½Ú£[ æôÞ߯%’u¢ Ï=3\”†’èŒ#ç D k?7ç1*Bx0{A+Ç,™`[g¼_µi“O¹…åóãÚuý¿:y|Zm|‘>ãôEÁVËв÷ïxÔ44¶to,z"ÀÍé‚w+*e Êð0.°$h~’Ðíàøˆ…p„€Å5ÑIú{Q‚:¹Ñì#GdÔÎç2ª‰ ô0|‡Z#bÖ ëüOùËŽtJoà?ï3§¦œHÔþñÓõœdGO[Zìöð;tb#Ñ@»©ûÆ}Nºoþó­q4Ѝ )oÓ9 ­ìpŠ…‚%눔€Aµ¾+ƒ×­j“u&&×Úâ§B¡]äca˜¹v% 6åÔ Ç%²,ô>p^¢ò”úzœÄTMÜ.T}tpy}ñã= i™žuÅCbˆk¹Ò«ÿœÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/images/guitar3.jpg000066400000000000000000001155231510465702400257250ustar00rootroot00000000000000ÿØÿàJFIFHHÿá7"ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:23:42 Ðè Ànt¦5ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?Ê–ÚÜÞÍ ˜¨’g88ÁÏB(VƒIxÊÜHÞwüµ‰¾RýjÔpÅy²Û¤g¸ˆù×ûÛG~õ¹¡Ëk«éïaÈclÅ““ŒzµÌ§mZ;¥41´¯ìDšO´<c(åðx<ôõ©tòë%»4Π‡ ‘Ô Ê])-uymy¦ÞL¾LÕºÓÒãg–‘‰ŠF†CüG?ÈVÒPOs$å"–ª–Zf Ö–Ž&I¥A!‡jËkؤ]­©–EÖÖƒ·°K²<% Êp '‘É­­tÔ¸–[ÁwŠÎ­(Bƒ¦F8ÉëT¹bõÔM¶´f —VK§m·bó”ùŠ« ÇúV·§ÆÛ²+‰¶òŒ…ÏZ­¢Ç¤Cotó3KäJZݲ;ã¦ÅiN« ì0µÃ˜ä·Þ¡Û©nyõ<Ò›¶ˆrÕ–µÍ6Ý`h#(PõŒQžGëàÓ^Hîí¦‘‰F*åõ⺨ÞKkO€Ü3ÌÓî›'NEf_ÞØM¤\[ÅjÑê! æ‘GÉçžõ1}ÜZÕ…. Û=¬cdË‘‚A«á®"‚äŒM XiB?Ìø=ÿúÔËaö­Éà‘¦BŽNoÒ©›é팆7ù°$ˆ0鎼}=j”“•ŠœU΂ãW·-Îlémäî‘RqÎ~•^ïÄ--‰´6‹öt!¾vÆåÎG_Zw‡¬Mï‘n’Ç­ýñA¸}_jeìv÷:µ¢J«å¼áZ$WnÊk–öfM4®†iÖ:½æœ×[yÐ4„†ÝÉlõ÷­›o}†ÖXVñ~Òù!”/áF©âVµ±Yb *F‡G­Q•µ2IBϰ®KäçzšÎS“ÛB£O¸¶ÍÚ˜g¼†U“"5ôïQj†-å,HŒP*ÅÐFx n‡oi1¶¸3?#Ê\øýk:êÓm$´l‘_!ÆzЍ»ÈSJ*ÈÝÓìek ÄyˆŠÛ‘ÈÏ^‚£Öc’+€’³;† ³’itýU¥¶‘,Œ¨¹f^¼ô9ê)š¼Ï1ŠgFS!-ÈÆsYësH|Hô?8o B¹å]ÿô#]=ë—ð†ÑwY®¥†}1LÊsÒšrzÓ°$ÒqžÔxüPêÙñ\y¨cò¸9Ç^Ÿ£Áu£©¸2F ŠÒ<ÃhÀÇ85 ÷úH²•d–f„&b;ˆÞO§|ýk7FÔá%¡yü°ä¶UO¶jy‹gKž¶"’þÎ}~I‘„p±Ár1¸úŸJ×¼¸ººT†iH—lÎáØÑwg>ްÜ9› å¨_˜çÔwâ° µi®—ìþte>O0‹Ó?•jãÍ®Ö!;hjZ_Íôë–·ž(Ûp£ óþ4딋RÓÞöñ¼Ë™"2—/üX©bѬ V7%–Ü„Iÿ –]ÞúÒ$G ©ª* 7nzQÍÁÁ£ŸÓ®^6òž"ò >X+Ï®+§:…å’Cu2ȨF “ ôÝè+/dzT,ø½•F\ ÷'¾*;Zòr±±cr qíeôü©ÎòwA46"Ó/`‚YðDÓq-® ǰ®_R{‰ X|²'\ïu8gûUóª_FîÑÜJ2ûšFRYˆéƒQKo~’-Òµ¤Ì ‰c?)®W×éShêÆõзáØY¼;+oF]Œ¥GZ¥6øÁŠâ-Æ J³œ±Ƶ|=r±èrÀ†<ªœ²ŒúŽõ_X+ø¸¶wcp< {t®UVÕmÜî•6á¯B¼WȶÂicŽUó"¸L“ƒÎ¤¶Ôtûíf&rЬl&b0Äz±¯![[ù *¥oBíÑOnjí¼P¸Ã-¼pžŠ€gó®å¥{žlœ¶±ÓÜÁw©ÄñÜÇ+C ‹Ï˜[ÌN }*µZÚ]\òMòÅaûªz‘ÓN-EtKÈ-¼ñöl˜äRû°Gú{Ð|A¦5Á¶¸Y§ˆÉ–•;†x>µ—,¯¢/u:_ hÑEg8–̉ՉGÇ`Myõì’]‰ng•¥›#æ-’¥t3ø"Ñg¶°šI¦q¹çù—f¿zÄÒ´É5 g(ÉÁeBÄþ]*éÅÆò‘œÝ‘ÐéÚ’®–,æ†GQ¹Án˜ÉÀÔš•À¹·†QÂãŽsÇJµ§5¬v2ˆ|·e‰ÌŠH䀯z¯©´ cBP‚å^«ì}ë&µ5†èì¾tû¤ÿ¦ ÿ㢻ø®áÛþæê?ö®èüÇÞ‚*ülg~:Ô½½©O:Š;H4žy¥ÔB±CnpðýkRâÆÖK1E6ü’ ±ÇrEZmõäY̓H›rÃÌ‘ۑڡm¯m’Wž\;$˵}ºÖJoC¯ÌƒOÓm­žYo¢‚X%ÀP ¤}i±hñ^]Êçìë¸ÌhØ=øÕèŒ0Ck°à£g$úÔ6÷q$þTND™ç€±éùU{Áwйq¥[ËjbXìã wGˆø¯=ª®•kcndK˜ín|É 6ìœôt­5\”uñ峂IþµRÍ[ífÞœ_ ·õ鞸¤¥£¸;쌸ô¨â½c<–’å™Lcè}=ªÞ­2¡Šg‡ÍŒåXÂí´zz*ñÐñz@.wãÌè1×o¦)×ztå6܈+wRJ7R7U7k6G5Êš]”V¶ ¼Ap3½d0ÝçðãéY²éi 3É3°'åÀỚÜ\ÆêÊØCÀËŸ~‹,[ÌhøÎp9¬Ôî÷)ìrÚMÊæ±n"8X®î§ƒC9’ÖBÄ>ÙÏBFk¢Ò<-½,“Ìó&á…*Ý«\|3Ó™Ôy×~j™T¦´{š)Tm7©Ài–Í­Ý­´ŽÁ±‹'Œõù¿½y*éwæ"±]ÄD ùFëVô{{¨ tÙ‹™ s‡‚Ó‚¥,°Iu%Ò]Ë,×!w‚ü–ùyÇ®+ª/SÞF6ÏíeRiUTÿ?§Öº-J±-$2Zc «.\\Ü·À¦Â[X˜ ‚/ÔúÖ߇tMRH Ù¹òüÌ‹}Ç+#úÚ®¥Ú½ô":imFß[Û9Ù6¦çÚ%P¡dÿ¾>jLJ5¶ÐY¼Ÿ3j „õížÕœúm̲‘çG[«KÊÀŽ ©<ž:ÒëYG¢ÞX+Ãq{,¾Zm qéŠçç¶MDŽàbÉ-#q¿ã¨SŠÃ¶Ú­½Ë¢02å¹Pzœzb¦4âß5Ι]]3±3Ãs²ÞfÝ8?3c!|ßÚ¯Ü "`1Îy'ÒµµëËmGDžÖßR[ƒq"Åh,OÒ±¯nu N´…ÐÎÊ‚<Æì¸ÀàqÁàu¤“’ÓF I=M+wòàò Øb8#ÐOJ´³]¤×Jʯɔu5ËSâ)Z—qÛÜOñåÆÀ•ëŒvü*»êOs*:Œª…tà ã®?Õ¸ŽÍu’EFg.²mnƒž¾õÏß]é–ºŒ‘D!:lÇËo(cïzäu¯Bš¾ÈÂOC@.õd†UY9!_9ükE5[ŶpЉ@8݇ëÚ©]éòiáìü‚÷'8tܾ¿LT?a[6WuÞHdc~ŸZéÑ£os³¾Ó.4ý"ãdò À×;ñ¹Ï•‹k}y{Þ,þaX”ª…U† ¬Ôï¡Þ©«ï¡pÚØ5•Íþ–gÛJ¥$Èè:ŒVµÝ¶©¯H¥- š@]ƒn#'©¤Ž+ï [¦úm­4p©÷õþTÍ+ÄGOT±1¼™‹O.Il“ÓϵjœÚº0šŠ–ƒô϶i?ÙSÜ+†m± l€Á³õ5ÑBV)¢¸H’O˜H>Nzç¯jçn-umGLx$Ò–0X¼r4¼ÆsŒÒ<×QÙ¥¶­¾9—;YˆùÏlç­Dâ¥ïu]´ètpŽEÏ+»'Üc¡¥·Õ­êöÞ}°ylùŽw`öê$’!S,ˆ¹T–=†=kìÉÅ¥Ö¥i5¸Aû z³õ9ük–1çn沑Ð[ê0]]}‹Ê¸&8Ëf~Cc¿?Öªµ´‚ä<"¹?xùv¤ÒnaYVÚxþËx˲8Ûæz{}*Üû]˜˜Œ‘’£Š©>Y[¡6º5ßJÇû+I¥µÃjqÜZ¨ÄÆÞ<º¯BIçû­Um –ÒÞMðíß»¿cÏAÞ‹;è)m¨izeºÝÝ@Ah•ð°î?2ç#&ºyí¡k{©ÎT™oâ¸]<Þj¬ÂÒ;¹ * m*}Aõ®œ¬ò\Ì ˆÈX«/SŽ€úÒ©¸Gb†€æ-rÈ‘ÊL§C^Öp¯ ¶[êh<#îü«ÜŠíÎM"ëôb}ÞØ§ÁúSO8§uÏ&„sž5ŽބgžxLdXâa¸Îßþµsš¦³i¨Ú4~\žb‘å–ÆgZè÷·.v@΂õ½)4?jv7’¦˜öÒ[BÙR6d㯔½ÝY襵Ôåì£ÿŠrÒyex­¢v–FS‚ÄýX·X.œM£y°$ÌòÈòº¥rŸÚGlöpÜJ-\äÆOý+¨ÖÄ7ZkÁ¤›3mdTŠ}Òmêr½zU¥waTнbªjÚtVrÇeÜ]Hû·\Ä­‘ÜjƒU¹½’ÆÞò£…a*dû jéöc¶ò"ÜìS´É÷›ŒSb¸6%ËÛ§›îÜWåSü#5ªå‹÷NwÍ%©Üi—)g–V(Ê6Žr—Ö«^Ûi÷>\W#ÃyãÓ5ÉK{v`/Ñ…‰9œ~‚¬«ÞÁM©Un‡~õÞ°tž÷+šÌë d¶P4ø‘#À8„uQM?½RˆÁœö^pON~´–±f¯åº)MßÞ7¿~M,›Í¼ï ¦Øò€Þ¹ù-¾æ—FǃíZÞ]CçFG“~:í5Հʀ¿.1ùW)áKƒ4N¬àðûØÿ êWsÆHb1þ”÷<ûZºŽ/I ÑŒ™7)÷>•Î]Öõ»¸·h­¶y’nrÛ”qþÔëpEˆŒ’©wyTúýkþu‘¤QåÜmÜcT#ÓµÛKEte#’¸»“JóËåN­û¨Æ(øU´J741†É89$w«ú´+k†S ?.*ÄqÈ–¶ˆ[a,Ìçj¨÷5×t•úµ­[)õIôù\Ø4–RÂ#D/óñƒø‘\å£Z¶¯q,†H£Ã:+˜û}k¬´ký5!²Õ!X¤Gà `Ç5Ã^ÛÜÁ|•i†Sn>„R†·ukÖ· jsfaÜ¿êüÕÎHï]zËÞ[Ç)ØÍ {ÉşƱtt·°¹ 2üì×'hç+¢šæ5¿W@¾XÄ£¿ÞµŒÒé©¢Ósž, Ë€î?›r1å”ú׆H1xGÈ5íš[™´‹)WxQ¿1I•WáE¼œç4Räw4FsLç>wÔåÖ´IEâIÆýÜ|„ vÕ¥®xþçXÓä³KXàŠ@7c“Mñˆ-uQ µ­Ä· “½ÆŠÄÒŸN†â;‹Èˬ<¼]ŸÒ³å¾¬õVM¢=ÜK«Ø‰¡ß“*G 3Ò´.õˉng‚gNJ–?œ¯÷AíNoIy¯Z^Ý"Ç.1C„¦’t¾.—|Û¢™S*êzkJv¾¨Ê½Ý†ZMsO,p+Ël‘«®A p1ÜÔjîcÀ6ñÈ À¹çõ§éDj3ÜÅ"Kålf ¨çÜÔçB³Ô4Ľ±í#|Ç•òFrzÑe{ÌͶ׺ŠÐi­-ôÖîe’(Ð0X‡ÎþƒÞ¤Õ¦Y´i&ÇŸxÎúŒ JÐÓn¬.ä˜Em-¬kû÷B^NÄ⨠F[½nÒÚÆ&b®ÙG.Ìy$~4âÜ¥~ÄIrÆÏsFD»Ð­lneݼ‰••N8Ïz¸·ö†‰Ëœm’B¬?:¹ªÜYYé¹ò mñºØŒ¨'±ôþ•†æ½Ód¹x’)g!’%Lôì•帮ÖÇcá”E²º å Øv“9®ŸlJ p2qóW-á(Þ+ Œ¿¸OËžB€Eu‚HÎâQ»ÿ ®I«HÑ3ˆÖôô½ÎW£îåŽq t»¸EÌ‘—‰DŸ»`Hlän®ÛÄ7f=}!ÚvÈÈyŽ•ç:½ííÅ k©zaR0Àg½uQMØÎNÉMc>¯!a"íBwHý¿n›«ÞXÞ=µ¾.må’0¸IW¸>ÕNæŽç–7 /%×£WAkw§%ÔL±Ç5¹P8ÛiZè“åVݯ{Qº…ưoÌ÷P™mJÈíˆvät"£²Ó_»†æâ{{eRÊ2|îpMnK&ž×åìmÚ8˜~øËüŒr¹9ÀÅrztku®[Û%¹“»rrWëÛëS¸é åî³F·½Hã»sj³’u''¦ºË}@Ú»—’(œ#Îx=ý8¬ Jµ·´Qà pDD¶âNr*Ê Ë{[h$¤vemÍû‰8'ñ¨•ÞÃZ-J7˜MFP:Å{'†ä/áË ž¨€ÅxÕñÅè)VÇÍž¹¯[ðŒ¢o ÚßwrãèH¥ÐºšÁ»ÉÁjC€ß.)NâvŠ@20:ólÒßÃ Ì ¨ÎNk¡–ÆÉ.Z­"(ÐJuük¢ÔtËϳý·`1È‹!u Ÿo­Gg?•g°”;˜ª©ÇÍÏJÊUW;ùå$bÆ–(Ó}¢Ö­Q2FÑÍs:棧Dbµºtû¹àW`ñÀ÷s‰áQ$”v  W<-nÊr6åXt5¥®U9]{ÇMe7Œ5+X§¶¹ºx¤,•Ïn¿Ê¹™æºH· &és‡'“êkÕí¼Y£È!»[ö†(ÉýÔŒwއùW™x‡T‹VÕžî™c*Y_s {äÔ­ö rmìoh¶ñÅ¡§}ªæás”V)ônø¦M¤ÞOy ÃRó.çfódFÊ¡úŠæmâWÝlqÛÞ¦µ¿¾ÓÌ‘[ÜË ±ù•€j×4uDN rjû*.™©\‡´ºû3Ä2èñ²…þ/oÆ ÄšÒIÎ0ùX `Ó´‹kÍ!ž[¸VßädAóÙsK¨¸›í6ð½¤F?Ÿ)ŒŸ ª„º3ž¬R~éÞxÞíté§šmÞpÏÍÉâ»`$Ë&9þuÇx2êK»Y‘©Ž 6Uà®+®‰ctbÐdr9W7/5MA»GCŽñ1µ‹eš\“†íŠâµÉa}>$7•\ÿyO¨#§^ÕÚøªÑTµs”Nwqžk‚×ìM…²Âgó7.ý›HÙ“ž½ëzv¾„»òêh^Ï2[Ëö¨£š/GËÿtsÔôæ± ðÂÚGæ]°’P1«=ò*Üqjºs[KpÛÀlmßžÝ XÔ-ç•íï!—ÈÞ†F8È8{Õs8«& );²ÕÇÃéç·–Gռ㠃®¡÷®;ÉÔ,¹·’Ü ^D dv5¶ú¦»cpmF1\HšW³ÇZ·w¬iúìOí»”(UŒä·AN3’zê‚TûÜkŒ¦r÷ò†F ¾‡“Í]þÑ´Hw”fa>ÖVØq‚1ôô®2xÒeŽT)`Àä0õ»i4KKk8D†àyî‹óŽ”TŠHPwÔÎÕr×ÊXJ瞼שøBÞGU‘³ø’kÌ5ˆ„7(0ãh ‡ëÒ½áÜ¥ôkˆÉåeÇÐ…±¬þ¯ ƒœÐÊ3އ½8Ž2j6a‘ÔÒ‘Îyœ÷š…ÛIl‹u*`ä¨,‡ðô¦k:[ܯÙàC"†$‘í\…÷Šo"×î§³¹‘`,UT7j忎äÓ®KEÆsX¨Élw¸6Œ-Yomn¤µ¸gô#nï©ÆwÚÍIöë]sE?Ä-Fi„Ñ[É ˆÝÅ"øi´èÚÞ³Èùù›¿ Ò3W÷ŠzFÉjqì›-cV8.Ûÿ Um¿5zQðfon÷7z‡žû3qœt W¥éoªkY »à‘Øw4)'±jjÅ­'A¹Ôm’l`RI™ÎÕë×ßÛ¶Ñ¢aYn¥Æ<Õ_Àu¯Bñ4šmš,74,a2¹¸Ïi1ë!‰Z ‘M(€{RöÎÚìg |Òl–úh.mìïn¡dŸÌa1‹98ÏóÅH»uƒæamF>R(á¸ÇåïUnuY¬|E©$E¾Îóº´ áJž1O“ZE¼Ž[KuUX„A%€cŒÖ—²±³oSµø|¯ „ÅädÙU#cÞ»ÈÝJ`\s’{~5Çx^i¥µ¤ÌX_qÉùryúö®±Y¶avüê!yM™MY#–ñ~é/,vʯÆ#Žk„Õ'¸ŠT{¶‘ãF%#qÀ^£¯DñKyq[/™‚H Üúö¯?ñð}ŠXFNFà>é¦j©tA-{Xº„Û[~ú92ûƒ¸#‚qO7¨¶Ö¯½¢ó²¤Rû§i\úW=k¤%œ)s3»”kF dþuS±P†)T6Š¥Ð+vÏ·­6¢¶[{•µÍRÞ3=˜YŒ“ Ü­‚‹Ÿê*µÔw2CÚ4vñž'H÷+`qÓ¨®ŸFðEœ›¤‘H&Pð±l‘߬o[±µ°»±³vóãQ*†”}æ”u)É-L-a¶ÝAkµD‡FØ›S$ö¿c¦ÍöÙ¬rh÷d±ÎqÔf¹¡a$±oÌ/’aë]¶¡ Ì2Î7DåD…Tl*3œƒÓ>õ­M†PwÜ¡¬Ç"ùfg’IÏ',yï]×ÃgÍ­ÚïÇá\wˆî–õÍÊýÆ#Ùã>µÑü5“7—ˆ:Ád¶5=«`ç4Œr;þT2‚2qøÐsŒph9Ï—­-f¼G în8¦ËA<‘?ÞF*q^ec¼2…+`2…Tޤw®Ĉ°ß¤c#jðü1Q}lzpªç-FÓmíI$Þnåà§§Ö·íoÍ„;ÔL‡˜Üœ’Äp{f¼÷F×[K•Hrdg‘ŠÝ!†êÞIšáÁP{žÕ2‹lÎq’w6ï&‘„ÎWË…ßo’­€ŽŸ•p’ݽ–¤gÓî'aÃFø#=FEXÕ|M-ÞØÔ© 3Ï¡¬yªŠkr¡ îzßö­•ÖˆºÍ¼,¶U^¨sŒœRx^ú ÿÛÜÇ“`·c^ko©]Ce5œR2Á><ÄÈ®ƒÂ·&Þâ'VÃDùžkž´T1µ zµäÊž'µ{?jQA GК¹¡¤°ÙÖDo0¼èÆ? ë>%hk7•â Q˜äP&Çlô5Æé÷¦Ÿ46Å¡c–ul6{zéº{ûÀí¼<—–se$ á@¶;Wh¬Y8D%sÁk“ðŰ·7k»$Ê—nHùWK$ÁcEm™ ûÇ4S”\­Ü䨥cñ´$ÚY²D2ýÑÏC\¿hlK?ÚI23Ȩùx<æ»Nñi–R"*°˜–¯C\¯%íÌ’Ã<í³Ìq oÓ¸«‚åvè=ãrÔRiâÈß½ØÜìdgáÚÉ®~Úêõg•ôÐÉhd%b“•ç± TX¦Ùå“ :-mXÌÐY3M ADhÈÆü`šÖMAœGb&’ÿrÊ5Q ¬›@ ±Tc§¥dßÙ›]±6|ÅÿXØï]´úÂ\é±$PCml§Ñ8F_pßç5ÏêEu åŠÜ ©6ª#‚ HéÈëõ¥N}ÈœtÐßû=¶”ÂÚõgch≰ c“ý{ÒiNýÞÇ<²´‘ìhØ9àÔvž}¡¶‚òÐZùÐìÈß1`0¹ÏCU´¦¾ ˜ežX"†Q´¶6ÁÀõ ¥©gS´ŠßM‰Q6·üµbIÜÞ¢º†Ó„ÕäŒïús\±y%Ò›Ì-”áA`Õµðý€ñ$÷V¥%~¥«r3×ç?Z@PN~´¬Fî9ãšoû {T˜>\øÊîd+ Cónãžkœ–g™÷HìÍÓ$ÒÉÄåJ°êÅIk8¶“ÌÙ¹€ùO¡¥cÖåŒWº‰lôë«Ãû¨Ž1OÔ÷³ÚX˜¤Œ†ß¹¸è>µÒÙÏ$¶ñù­*¹®ÜãÓ&ªêñ›ˆÌ1Ü£„:ïƒÔSG;¬Ü¬ÑÍYYO¨\­½º†‘½N¦´oôD±š;d»Ž{§m»t>æµ¼5¦]ÛiwZšÛ4ˆèV&Nyæ°¯¬oô«—±É­‡Ãõ©Öæ¼é»&tzFƒ+<ŒCÍ(àã¶qбB°ï\†­,0Ã2ÏóÌdÊ n[žÃéÆk_×ôKMx#¸q9ÀR?rïouæåvÊ$HÌG'Úº#^4ݧ¡1Ãʬo JÖ+t­k_i€Ëç¦ië¶ë§Á“H%38ç/ßõ§ÙMwi!›’S&<ÿË0}+Q¸–iœîn)μ*Z0w:¨aåMº“,ßxR[}¼‰ÊÍ s7ÊO^*“ÚÛÛ@L- ¸‚É0†ÊõŸ¦T¶š¦­®ˆ´¸îBÚì 9Q—ÔûÔž!ßi¥*ÙNæÜ·•&ñóð1‚};W|[¿,™ä;nQmgQÕôølæw’0ÄÈì~÷ÿ¨WM¢ÜÄöÂ]#t]Œ¹cÜõçŠà´ùZ‘ð6ã’{}+«ðÕ½ÅýìE,^U¼Ë)…˜n õÇ·4T•ºZ~¥ë›˜n4Wa"yÙ9Œzzþ&¤ð\âYœr\(üióØGš%‰¢`îQ‚œ•`zÏð¼†-vÆAÉYÐþµŠ·CXÞÍ3Ý>VáØR÷ÏNh$œÓ]ºŒý=¨9Ï Ô<-xd¾sܳaâ‘cëƒéZÚöm‡ôÈ#Ž&ºŠ½#HûÇük^ÚýÞá¢wq¹ð9;F{`ðj°K¨Ê@FMÙ;YðœñQï-οhäp_j–8Ì×1‚v‚ç³Á5¯à”7zÇÉT ¥1òžÄç“XºÚFÓ$И¸Î²Oë£ji8A"6mªßZÑídiÉÍ õ;»ÏÞ6¥uknCFÎUKEŒÆ~½ëžñ<º|ðÚI…K‚»®1ÂÍßœ|ßZFñ¸·²a å‡Ežµ7Z5-ÞľXŒîhÉåLéR’‰O™_C&ËQO—"ìS…\t½hEâ-üØ>iGîÙFy®düéÆ2aZöº)¸Ò`žîsÁ©›þcWN ÜßÒ'Š`Ò¸œž+¡a ÛåN߯¹]Ñ£ýÙRÛr7)Åu&$Helcºæ¾Ook£=Ø/qoü½$™àOŒÔI„}Üžü umkf¶êŸ0“•ïŠ|zu†Iñ·S÷+ÛþÔ¥¦ÓâV’xgUŒr úvÍfMâ›}.þO)–ÔÈÇÏB-¾¾÷žk: ˆ•Ï–Ïѹ#ʼáRY¤c†w<žæ²Šr~÷Cº4Ö·.j·±êÅÜPùI+—Ùœàš£É=*æ›wœí$±o8ÂCZÖ¾»Ô^ë³å[ÎÛ‹ú/©ô­šIór»t)i÷çK&DG‘˜‘¸véLÖuuÕŒL º ;×Cá¸5Pè²ÂÉUÑå5ÇÍ Á+Å*ítb¬cY¾Vî8YêtÞÒ¥Ôä(Y gè{šì.— ék8ÿIec(r™í\úÔÖziPf1ÒºœÏcUô…’gçqÐVýÔäË£IÔš:}.8Ô®Xàõæº)6‹pVrûÖv˜©ûŽêHâ´®Aå·û ×ÉbfçPöíb+ýˆþñy'Ú”K˜qƒÜƒí\¥çˆçeAq!Q‘Û[þË×Iœ, ¨Ê{ô–YZqV±çI‘_ ‡ñÛµ‡µ'^—·&Ûçc#müÆ+H«E˜Þò¹¡™õ8¦’7dhÖB3Ç#¯½f‹óÛ-ŸÎº—¿‚KõÝ"CnöèèÍÇðçË\š‹LT&úšÃsÞ|>Þo‡4ö',m£Ï×h­1Ò±|*âO Y¸=o_N?¥l!ñI˜µ«>Yf­öÖÉæšàI¿f#òØ‚áýk=,n&™cmŠ…ËÍ5­åˆ¨’6BÃ#pÆEžœ¬ô¹ÐkWÚ`1‹Hâžbs4¥8Ï õú×A©YK'†ì¬Œ±Ç3yq¨'–.;ûמ±äü»}½+GL½kyCª––3æÆrx`(KT»Σx¿†ô–Ò­¦K‰!Þïˆþ`qصÂkhcÖ®•¤0s¹Áê}ëCWñ9ÕcHŢĩÈÓótÍ`Ë‘LѲaÁÁ^õšOw».œZwe‹±™apÞŠyOzè´¸PÅ6’~êAw¥Ed¶Ñ=½ÂÎÃ!e\`gœVÅ”M•O/é^fe_NTz8.^sZЉ|8÷ÍO4ÞkŸ÷Í"áSØLU bIK»u“æH\ñס¯œŒ}¤×™Û7dÙÉjZeéšl@Ø2³ŽÕNMý`L@Fs»?ZÌ7S\„’g,ÆE “ž1ÐTr^ ÓÕDœí<÷«í!N¢Š\Ëîÿ‚|Äå+Ûñÿ€j¦‘tO*öæºÝ-–==,çPFÍ®§¸¯6Q£’_æàŽÕ½á[Ù›TÅĬCF1–ÍrcèJt[“Û]¿à8J±D’ßC~ÃE³Òî$¸‰ØôÞ~è«òÈ­ì¤v5´’If­/"«†tï-V°?¸’YaX¹eˆW–¢êSöÒ•ÚÒÇ¥*söqZsI•ø½ÜËåüÀ¬Ã ««ªÛÏ¡Ãmw9ŽW"`Ò‚Aéï‘Ipð%ÄsP+{š«\õÿÈ[®~ë¿êƺMƒw=ëøy/™áùc—˜Ïõ®¼±Á4™œþ&y妃ý‚°¼“ Óå˜Z@r¸Áý+‡ñ Û&©,VŠë…VSó&G©­ë;¸4ŸG8gm …ŒëèqÔš}öŒuý{Q½™<˜<‚ð3½Ž EÙÝ›u<ÿÈŒœµÊ}zÔööÆ9d‘Q8n£éK6˜öúŒVÒ0B0AíS§‡oˆ‘ãT^‡vjÛW7¿»¹¥¡iÇ5ÕÔè­G ¸dzæ¹ëëÓ>©%Ü,ÊKîVî=+Vê»],Ú<ãÉ,0Àãð5Ï•(J‘È8¬\}ç"éIKscM»šâñ¦¸™Éå˜ä×cc2n}ëŠÒ•át•ìü§Öºûi2Bæ¼Æ7›=œ;^ÍX×ww\‚Ï­fê‚K«˜Bá¥r1Sü»OÌô ¤ˆþY:W—MrÉ4\µVg“ßCsar`v ñ0 ƒß¥R’m2¡?u²85Ðk²ÀšÌþ}§Ú #{/òªK¨i±Ê$M €ó»È×ÙÑ—4ŸSæj®Y´Œß³º¨mñrGÆyÖxkOxå[±"ºÚkŸ»¿K™EcºŠ9®§B.öHÄ"’IÀžÕÏŽ“T¬º›àãz—}Ÿ÷»2²5-M­fŠˆ4²tÉÀ«e¤U$ÖF©§ÃxÌÜt#­xØxÁMsìz•\Ü_&är—6ÂDäeȨtÖŽÚÑßdL$b²©lõà½ÇzD--„jÍ…Fwoç]E&È\̧${⽜ ¯$¶8±·tâÞä„0¦¤áˆOÌ»9=ǵhÃ$%yž4Þ77B9ëWô£ý¡.uK·7å‰ÚÜ©úÕyìÕ÷ÝÙª¥³Í²Hê7zúf½îõèy‰ÛDtºB[ZÙÛÛL.m·’’ôÊ“»’+#YŽ(nÈVXCuêåÆ¿y5¬’\”2Åp ÂáGAYú”†[{i‹«–‰^™¬µë±J×='á³Ó/#éûÀA]Ê‚xë^sðÖlÉy#åVñÅzÂŒ–äö¤É¨½æy>•âk¸3ÝÉÈ#ó~SË0è1õ¬ýcÆ\Eu$RÄßnˆÂcîŠF?•q¦•âŽTŽße©nÂÎKû•NR}vS܈-$uýãF§<òp;×ky%§™å3®Ý褎öâtŽgUqózÒa89²ì7Û¬Þ‡0çbí]EΤE¸E/ÚTǼ±ÆåúZÇðÌYî<4L3cÛ¨­«M>mCQWEv¶FˤY%G¡Ï4¤ú&DÚOBž¦‘²¶ƒÉòÝœF;u'Õ¥Ä3 ä䎵Ñß^æj³Û.–C¾)·¸®GY²}Tx!Rázü냇sשلĨû²ØÛYJÆBËŸ­V’yCdk-Iä<¸§µÊ’Hs^j âõ="ëJþh•WuCQº’4i$>l®v®y$Ón3»"¨E(¹¾i•„ýk¦œ_]‘ÍRËmÙ~ÊËì À,Äw©„¬ò5RiF?¨d‘\äÓåswlJÑVF隯;’F+;í ŽY‡ãQ5Â÷Í8ÑÔ|Å‹¹ö!ùŽkCO¸‰`6èѬB"nchù“Žy?¥bGÅÓ$n~¨„g°­ m5ø,ÚêhfŽÖ2 Fr~êá©(­Yæâë]ò¢5„Ú¼PùNB²¨Ü\zWI%¼SGie2}™Vq@…CrN=+•°¾û·ͩػ±¹àõÀ®—X¿{Ý4¤¾Tez&ËŽàò5×471Ù =Sʉ~F›G5—¯Ûˆ<¥Ø€Û}J–+É/ío­’o:(U8'¡ªÚ£ÌÖ¥®%ód$O8™¬-­Í³IwÃi6êS®3º!üëÒ÷8ÛœW”|:”mì#;£<}z¶å`0¤Â¯ÄÿÙÿâ°ICC_PROFILE lcms0mntrRGB XYZ ç "acspAPPLöÖÓ-lcms desc @cprt`6wtpt˜chad¬,rXYZØbXYZìgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ öÖÓ-sf32 BÞÿÿó%“ýÿÿû¡ÿÿý¢ÜÀnXYZ o 8õXYZ $Ÿ„¶ÄXYZ b—·‡Ùparaffò§ YÐ [chrm£×T|LÍ™š&g\mluc enUSGIMPmluc enUSsRGBÿÛC     ÿÛC   ÿÂà¨ÿÄÿÄÿÚ ]O_{ Í­ý¸ÀÞÛl6A‘YŽß[”áI¸á͘T¸ËÌ;Õ•rj³¤gy=w;-¬fà_t+s•¨²Ð ö%lâܦ€§rÚEyc‘œ]”¢ëª=¦x2ß@vg‘ßOs_“Ðǘ#j…0Ú³Ûú91š½ãk§êäàʬ«HÊbW< FÙžŠìÏ;½žæ{tÇ·é”,ˆ÷¤bWêLÆf¢óèÆ<¤ƒ`‰¤ã2ó}'˜Ì}Ý΀Ð.jJ—†ai˜Ê“ Y0ÆL´š8ÖY ÐØy ˜á„-t¼fe=÷E6Žˆ³Ví8$'’aGèØ2ÚÝu¥i5Á%E’Í[#溳¦Í=îåk?•„Þ 5V¸3Æ;ˆ=k P-¶°ûs,¹2ܹuC§$jG”Ç0ÕñrSDW]á&¬‡Hv­˜¡µv°~Ð’â'Ü9Œ¾U‹fË4TÚ9‰}–)óJ Þ÷D˜ƒà¶Å­aŽ£ÉZ‹T"Ú•±qA@`%’6-Y ´-í£•×ÝÅc²ÃYV„Î¥É*B+VÃÞ*­!©8´;ѽk¦±Fõ˜”YÖ¥‹Ê}1Ê#Ýo^Ø´Óç|´©!sX-¹bÙªÎ6Š‹L+SMÀN—X$¸¬I4µƒÓhr~wrÈ÷q$]|¬ÁZo <ËHê,”ºòÙzÓÏQAR‹ÌXi@`&Þ;·^ÖÜ'ôww¡aåð©`9ˆ¹õ$»”HV‘#»1ÚÌ´§Ÿ¢ù†"üh|ãe\Ó)â^íª_wQàÑÀ0— ]£¾úB)I6 ÿaè ¦6è)Jó l¬aW5OC †mêÍ~×?Tu<ޏÆQGmÖ6ÙLúbodx‘…HÉIB2å‰ïT[JØ —E˜èšçãÇ´Þ!X;Q1ÀЈk!`+ÆŠÓè]ÁL³¤ìEvLF>¯ÞZu.Ò±†\ý3Lü"y2»aföš©Èµ+“4Á-Ÿ¤W7g-t s‡^‡«K®}Lt\!•ZTh¸l®Sõ=s±‰ C£TßGq\5O?ªá–Ý‘QW­Rè|Ö–¤X•&†S?f¸þÞ…)S@83Ô•ÍÙÄé§pû9î´-ÓŒž#OÏ=”Ñó×°òSŒTÍÀl_!µæÇ“YhzG•ä/1ÒЉ(ŽG®á©\1*ø1GéA)YT‚>/×™ÎOÒ¼^‰ƒþKÒÊÜEõ™:}’ÙaÄìÅ ¥stG(Á¨†ÈX½HmZl º®‘PQ¯ÉúF¬ ô˜ù¦byo@#ï¹óÓ60§p×J7¥H½ew 6«É%b¿3;{Šƒ·Ål¸_‘õŒx݃ËíD¸õ 7ʾ»(¤ógè9ÇXO•-uðì- mH·/`@ ±å¦ì]«GÄjåÊV_CòZoFO=³’ìL˜q㊕5–̹¢Ô–¿VË 8ŽÞ„ÿ_sˆÔS–W¤Î~/Õ:ùËŠÑF¶÷¾F$qœ÷œ|Æôgj½è³6íù°W—åﮄp˜·ê¯/ΪÑdXÝ•!š±&,˧ˆôg²ê‰éqêÏqå`Ú$„—wÏý½`»}>·Æ«š®ª£µKvÑJ¢ŽÕ,Ø6J£‚³1ÓÓ²üfÁtjAzSè¾'mÆS'RÈò^‰kg;A Õyb£©šÎ––û¢TU4Úà8„$ä›nO—ôåш ‰_o$; Ià 3Ð'DS½˜Öe™Ö,Q^uØè¡¡Å„ßÂ8…k÷CØÓ7J¤¥¯£'_êÖ ë¸tͽ˜ÔXÓ«—7Wm,E**èç:ThÿÿÄ)!"$1#A%34ÿÚ½EÜ“¥ ”-“¸ [‡`B®ZqXÖDvåmà1Pá0Ùø£fWæjmiHrª¼,a‚G¼ãVj>ž·Žß?ÔóÕy¢šØ˜Ë|®Z„#§P€:µß%j:SÖCëiÜôuÂä¿ÊѸ~%(r„™#`¹üMIk˜÷ðØú‘UÆ•&¿®ùܱ‚Ü,CÅÅÜÎQtÖ8ƒÑëwòRìÜ+Œ–ŠœOpðjš… ]=_ß•ù|KõÕíøíßèË/ö (QŠ0ÒY㹞é=%a*‚‚JYZÀ`hÛF(‹6ˆàãÍV|j)Ð3Z5cã"pˆc׿¥þݾ¶ûËçí5gÈLOb\Ö#?‘䥯•é]1A 8:,,æS+™»|TUru˜öË6b¥ap.Í`±>ME¼«ýó¿~.ÑX]Ùôūⴰ-oZÌP=qhòÿã¡N±ù•1dº¦kJ´@M5oí*z…Å®ˆØ IšYÐ Âçþ¤IíGèAÚéâpóBµôþEÛ±þGÉ'kÔH 5G<´øÎWèŸRV¤[F  |¤Ž66ebµM +ðñ“âÛÏ'èE‰BØë« Xè™,‚bZ ÉÓÜ&ÒeÖ–à}ãÐV²¦xdà0á{OKèeíù”ÃÚ­(ÇL[¾7?Õ1©Ee! ö+dÉFÝ<ý¼«_"`ã{Ø­slX©oŒ«û3ºŒrD_~*Åø)Ë~Bô–zÍF2×*‹Ö.¿–W§Þ|Á¢¾ˆëòdî°Í”dq ó9ªæîô½¢òX´VÑ—¦ROüspÉù}mK« ©?7exÉJ÷°”è¹þÓ6¨Õþ¤Ô rñÿNEëC´:s½ébЍ®†€ÏSþãB-ð·­1žw¬½5ÉØYD Nc!CÀâgµ2FbðŸD[½®\ê¥U¤ 8:Œ‰z­C&Y"ñeª3¬ îþ]g/¹ÖýQÒlv;h°Ð« ?™Xº:‘,ßÛßq¯zÝnÆú(Úr2–™0©˜t±¶FQ¡ŽR®j:i±îN #!Ì¥å1Ä eÇèT”ƒo+Qpa»eٰܱ½==´~ލjsôøºkRêô«‹*ÇTéªÐ!@è2¡’­kUÜÎ,‘=AEi; +üº¨Ô”±o¬m"c™kN“VVµŽ/Ž¿&yü†Šæ-Ë&’yÏìyŽge•ÊêRM{O,½˜®Wq"ì÷QÊš‹F€~×ój5Ÿ~Œá4ÝqË­ÙRV½3o^Äò¢jtEÔÏ‚Êe Ú•ÇÏPÐ5wÄjcææ‘­ã3pjªFŠ|¸š€“ú0ýF%Ö_ÆÖ‡Œ…þŸÞ, œö>u<2/ÛVyÞ8®%¼CÙVÔµL´±$Wªk`íz”éíKS©Q”·=vûf?å/£=M*-Á+m 1‡U1zu¼{>B¾ÐÐßÐúë(Ïïdlpó¥¹b³“E }¦s¼ªÊ¾¾QZ‰+¾E…ZŒLw§Ë„G[_ðЭìÑQ†Ò4CXj±îÓkàÆC‘#–%S)-[÷ž'iôyDñë]r zë»RÉõÆèö{‹°ìvÆoÃy6òËþ¡¼²Ô»yÅtŽÈÆ”®’%[A5 ^®€7Í´^º3"Wåšµ!¯Ì‹ùÁý`‡Ï댆þÝ͸½¸Âžš¯¯«Sü®ž'·cò'fÜmk\ DÔâÝóªZhÓ·n+fÊ« †—Zo9ËS‚ñž%xábŒ z'vÄ=J3[‹’¥dZU¨Ùé;ybóáÚ¼/TWÑá{Uoû‹°B©ÓŠ]z‰gR#†üA Þô-{{6oñ?B+z¹þ¯¸õ Wî½YºoÖG^ˆ¿|¾n§Ædb‰íÈÄŠÙÊŒËÕ’.–h-%Cñ:d°Àmf¼¯ª×–‡dÅÚ¿ÜY’• [l¿69ª/vÎÒ5N.„¿’‘nOÔ*ƒON>äªk¾ÇPi ù­d‹ç Ô¨ÿXÓ8¨¼J¶<Ê•”_Yk±à>A­x2æ3ã`k|‘ ,åéA톢_ íôC§>xœR¿ °^Ï”WbøŠ;Í¢9ÕSüoÂß&Ü<‰i¡À! ³b½´“LrKd£!üÈBÛ³ÑùâÎWA…JNA‹â©f ¦ÝÛi9ÿ:³Ûžsâík`ÐU彋xƒÐý««BkYª¡{°~ÀUÅÐø—)FE:$ž:‘<^sNMÉؾÛGF•pìâsš5@±ü«î´TŒysíªÄ[<$d+ KA籉=™'êX¥–ËõZOÚk)P¯Òsã¯3õù—…ìR>R–ƒÐhsDR“{Ï–cŒ5"ÿâË~Cå˜å(c\ãdÁвᗾ!Í쥺hž:¾]¹ÿÄ0!1"2AQ a#3BR$bq‘ÿÚ?Q“]ŽRƒØòÆÎmîÉåþä}Û%7ñ8ϵ[9ÉhMÍØ±žŸs9&ÑW³'öÿeªf8ªå"zj‰¾¬È4Êã4äš2AsßÙï¦OqvB:F“¢_hÈÛÒ%¼³šù}œœ—°O†¤d8É\˜—ù”º=ÉÛ"®iÿ&òÆHÍq|ë²Y\ŒrBû¢O»Ú>´/íþÆ›Udb×LxùJ›#ìTq!㽡cÈá³Xæ™<ðÁ¾Ï‹N–»'¨Ú$÷Q8Í»dÔ—ÈR+ýHî²äÞž‰Kz'½Åœ´;“²n´‰{·']oc„æey8¨Ìáí8úrìS·Ðäæj mDÇÐüþɽ¢={:òÜ~#B‹Lþ„Ù6Ÿc_ü2>I+qÐíDŽJT--˜º$©ù凧Ó?†éÚ1åäèvÄ´4Iì²=þFõfŸbTöqo±WeÖÌNú2|Ÿœ£(JÅ…É[û% •#ÓuhŒùPºòâªÓÞ‰«ìŸúœjÈ-lO“¢q¢;Ñ‹³7Íùúʶ)9Fâdr“ÙÍ£…>Dd¸œ\ºÕ"iý Üv)R¸ô_Ñ\´…ñ£âÌÿ?5…%î!Á|E$ÜŒ™ÔD“DxÆ4Ø¥¢Çü «dºÐÆøôUvqÕ¦Bn$¨Æšgˆùyµ•«lÅݳ;Š^á(9ë¢8éé T©p‘ö1nŠul–¡L}žË¢>ÇÈU-˜í:gˆúóÃ);=Vô–Ïm$ÈËÒÙyxÿ ߸Ë$é2IKvEÉy>»äæÛÙéý±¥\„Ûö¢5ôFK–Œý/>K¨’É»³S%áà¶ÅÕ"rQTÉEݤNKø96+­Ž_CÖˆÆ.$ßD”ÚÙL]ûEIèÍð󊩎£¸E35ÊšèÅ™¥Â6.3I™e*™z±.[?í*þ¾Š£$/hÆÅ]£.ñùêÞÅÑípò̹è†9EU™;´J2k“n&=ÄJ–Å%Æ…î[%Ð䢬ÆÇvOûoÍcöÑé/ÈçþÝc«d_&c¨ãœÿŒn4Æ‘'P1)%¾–ÈÆÑ'¦ZD¤æ(Pä–ð~sκ‰nqѾTÆ®èŶ̑§hÇúyÀÆùFÌÝ“é¥ýGÑ] |tGšrg§ÑÓf–ÙƼãŠD#ÅPávcÇ7å9(vx,¼ÛBðRÇ9WCð™dýÈž>2qü åÈø¡ã¥gÅqd¤Ñ\»§Hùúý“r_<Ó³ùBÈû¶dnvxlžžh¿&Œéú²8¿Á“ÁKŠœQ‹ÂæÝ#8ø|T9_FI}£Ðãtш—Éù,‰"9ö³Ñæý¥nŸ &eRɺ#(ôb¹cRgŠn8dÐäÛÛ-Ÿ¥¶ã+òñóPÄÿ,¾?D­ìáÆ˜´¶bfEï~\Ûòƽ¨–)_´Qq©¥±Ê™M1¬‘p‘ÿ/Ãbý;‡Ããðê±–~¦ù8Äâúú$µ³•hK™ ¾~^;=)7cœbé™5G!ŠrQIh_·ÇâY!ËðNþ…׸ÉW¯(kF——§Åë¡ÍEÑíææ9Ê]—-3\ÛG‚/ê ñ Ú=|¿ìzù?ÙŸ§d”¥%&?%ŸÒ­ rFx¼9´q½«!.F~ל±òwd°¦¨Œ®ìÀµÌUê×äǤ3ÆN0Äù}Ñü³úÉásâÃ/j{&¡ê4`ñ1ή'êXÜ¢¦‰\U—"ÅÙ›¥å,ÎôG,§×”u6….Lðعfäμ¿SþÒÿÈÑD5$I)Ç‹1c‡†Ofeêcq$›D!ÈZ"ÛÛ2üW“’ºF•”äÌuZ<+÷ùæJPi¡›<÷–xÌRʽ¦$ã)u)"^Õh[¢×HÉðònõF8óù#7£.~ã—TL9œr&˵¢É;Ó?à±Ãcy8Çÿd1C¸£‘dçÆ6dÖÈìRÙÅvOáåJïËŠ¾F±­ò);G†ñ\=’¹ ™§Â6cŽË9+6¸Ä•ý˜ßäŠ7Kàü¿ÿÄ4!1" A#2QaBq3‘$R¡Ñðb±ÿÚ?¸ûEPûʨ ư‹Fþñ©_`êTaÌ4Á9^™n:žZ4vJ`¾ô5’X/‰¼ËÛÒÅùU.²»•6ˆF,LB[K[˘o{x*ãx¯¸OË”ïõ˜ˆÄ« Cô“;y>•Èœ} ·Ìó{ÅW¤m}FlªcT$Èñ.ÇBeÛaÅ7Sõ}D¤Še@È>óØ^]Æ¡1±úSËS•£s•¢Ô  #6Fðñ½Îç›iæÛV•9ä5c:wÖù‚£1´D÷i•5[(¼§íŒI{qÃzWK¾e;ãÝ̧¡Þ&6‰e2ÑÊ'gÖ&‰ÔMÙúŠi£)TF$¤Fî¼$¸âñéæ8Îçú…¦7:ô‘omÊ@w.iÿR;Š€}§™’¯leB›ÌmÁŽ¢ò€?°Å-o̧«˜TUßY¬#¥Íá´°qè¦÷8¾åKâØJd`CÿêT¥‚Þñ@0¡'8jo´ãPÞT¶S§½û%0I±‚ã¶d8Y{6„kçu1&Ð hú«/–E¦\cí<ÀËß²"×Ví¶£Òò˜´w" gD{ÓÛL³[™R×9³jRææ3wØLÆ¥7î•5ÑÝ‘R%õ©O1¼µ&Ûžnc N¦™ó1p¬Â÷¼ý1ôelJvËQ—&˜“ø‡ó?§¶• ¸7‚;‹¯´öîö”é\žü Y­L—ˆÔjpÚ‹LdDb¼ùe(ß.þ"X}1W1h=ÄgïÅ„©H>Ì©¢``Ç^† "í²ÒÄ›ÙafÍ­,jušiR‘K”€XqÊJ¹ìÁn¤;¥@@ì| ´s’ãâ\¯BìlƸâRÓ‹Ãz¦)§Á•-ÂÊAöë)9GÀßïž" 7'–-h*èªÊdÚÑ©¨ïh×#ºFÏ£ó;¾ÓøÊc«~ÑqÅŒR·üÏ%TÞ=²ÔU ä"·ìÜÇdÄ©Jø‰T]w n Éß 0â-G5?ê6Œ Šæ*äm û7Ä'$´·u„-‡l ”o/?Ì·9ÂåSM*ØZdÄ€G¢Ö<ÁVÖ&enþ§— ßf'º6ö€©{Jºx Ì(ÞeàĈ£ÞS…•8?Ûè·n&yikZêt!ªÍ¥ˆ ¥O¯»QÞøªÃ ¢€Õ8ÔêI€X›ÇwÂ4ö¼B*5…¥:BÈ…®&'gÐt'ñ]Þð3]u[ˆôóܬ¡*! lb}m+¯Ê ÜÀXÆL¬cUqXµšó”‹¡hsãÐÀ´xõ£_™O¯ £uTm¦‚¦]×ÿxz“åùp6q\ñ+Ô7¹‰LçPäd¶K“sˆ¸·ô[W¼"ÆÑ=Æ50ë©^˜t"u¤×%a÷”Hò—~Ó!÷ÿ¿ÚRêÑŽ7؆ºrL.kT¼Ze;›‰H_º2µæXèÁâ È´^ñ¹rqCq̱Ác® ø€ÅÈ|7êTªè a>5ÚèWR“Ô«£:,Âò¢yš¼£a `ªZêDa½ALj¤ƒfêa2Â÷†Ã‰ÌgÚ|AÆDΞ³Ò©šCñN¤/þ‰þ/ÕŸ´êúŠÝ^ê'J>p“+Äl îŒøn)È_ÄÖÉq¶â&:–&aAžqþ˜äß#:Çf”ÿ KXëZüŒ{ÌgO÷: ¥{|ÿ¶Q¿¼$¸k‘ã‡ÌÎ^ј¼Ùï^"¾{…nëê`1Výç@ŠõQ_{Ÿ£¡þ?I@~Áý§Æ(ÓJjU@”þOô¾}÷kÊ0”›ÌLÁƒ¸þbœ4aÀ¸kÐUO3`Ye\é…¦§™‰§Û{Æ6[ή¯˜Æñû ¥R¥eòý·¸?SöÜÿÄÿ5ÿûγ¦­Ô'}µ(æ~J}¥n™ºf½çê¨2‘Vkˆi±©s*qq€ oÀñ¦dÚu&ÈL¯PîNí> >qþ?â ›STêIV³õd]x+q©L-6•ýÀÂ1>%,—ˆß´ó3„‡ÅO,ZucåÊÂþÓyÓ3+‚¦ ©ñéèÃNætR9LƒUºÀ¼AT;ciP} Ví¦,¦¶áŸˆía±*œ×Rµ;‰å­âÓ¶ÄuX:š‹O6<ñ³Öc0ˆ“§K˜¢ú•;x؉™cb!ð¿…¥ `Óf2µSqº|`§)RÖ•{Ú*¬òÄZ‰Bšª.'è•Òû†üK©Î'ÿÄ>!"1AQ2aqB‘¡#R±Áð3bÑá $Sr4Csñ‚²ÒÿÚ?ÆI¤uç•Í“¦¿*1¼rˆ\1ð¹çX-·£ÄBjI|†Æ%Eúšìö¸÷×­2˜ß}{Xôøÿj|†Z6ñ|?±[¥¿â%Tô¡q\&òàØšYU¿•OÐ+ÈÃ(æ¿#Ûʸb¶9¹¾ JvuLmf'‹Òœ<øí2‹è›ÀWÅoß:]áellö±åúWÝ»H ’÷>ÿìTxbбi‰ ÔŠàÚ]XF2;ŠÖ3–Rt¨£ŦHt4‰&‡-tµüªü¶úÿ&gµ™—º_éGìVÔ… ùs£ ú«\‰a4®"-98…ð‹Ô’É{«aÓZ2Äx™íç­e) ÑõÈùÖ#uUËO—÷¥±Ìªçz»! Øk+6¾ï+Òµ¢D½Øh7ZF}™ îÌ~åúZ™âX®½“ÅÓ‘øV×yXÄ÷ u÷Ee#˜¢,ÙŒínUþUüÿÉ&ÈÌÛÖ°u}5ÓáX»$ªO½Óë_kiAmn·üêåb)âH÷×°¨£Ã¼Ë“HpÓ•úSÀ›˜$awhß™=2µ+¥—ÅfÖžq(¸½Ç½ðÒœbûµ·!ÓÒüª7"÷VÐzÔÑ5ðmVúëûÓã_xª„ ßO:PŒ´#{yþU™™ nv¿¥²²Yp-(K3L-VÖ×÷×¥$ˆœJÍâSÄ,5"¯øZþÝ*ÿéÑ{î×úPnÄzr°­øqµrbG?•/èè­Igd[ÜË_ë\O~Z±þôÜò¶G}ç ·â#"†³èjµÇ†wÇ~UÄŠl–\Á{¦•f˜DÈx8n>5>î6 y 'ë\*R<ÎL¶É»Ÿ•éWfÚh!R¦>v¨‰QÀpB·nžtÞt§ÊþЂ6—x,²X#Ö¶IGl˦y9kÞŽí÷±1ÖÆôƒ(¿•&q,‘®¡®oÞÞ”’*‘qNXÑUC’Øß¾•‘%ýyúV±=Ô\T|ÖúÖÎκqÊ–?âcˆºóM)¬Ç°²ß­¨! ´äÙ&\'*’IyýåÖȧ‡= žjpü<ÿ ¿qzÙÛñF§éífÈÇbݦ¾%õç~½*´Hˆ¨˜}A·+Šˆì­ ˆãáâmRE»hÊ6£Nöè?w©ž%8˜éߊ߭kâä1éRI“É.š±÷GìT{Õû°ÚKÔp‡MÖ ,®9_Π ¼!t±¨÷œ+~þT“Gmÿ2ŒÝö¯´.RE'ðzØþI&,÷è×WÞmm.[×yÎãCækcé|[•ëA¾¿ å{~´¬§%饫e?ÉoiÛ7´!‡x7§ýó©¡šò™šS&Í–uròHÑuòçM-,†ú[NúRtÍx¼ʃŌ…´á‘º·j9ÛÐÖ¶tYyv©0u'ʉXŠJ˜e—½'—£x^Ã…›ZP1 ù‰:VÏ׃¥JB\Üu·ZD:HEîzŒ¹PCϨ=û|( ¶S&œ6ˆ[ùjž €(ïÂ{Úõ;D†Hã¹à7°­Ð‘Û ÛÂïëK—/wO:ÚÓþ?¯µ7n¯¼Œ5ìJi6¹÷[:‹Ý5-ä+m܂ʚ^ä.´¹H\k_÷¥4ŠðfvgûËÎÕ³4Í»Ÿ\c(Iu·=)vRJ£Ë‘ ©],>µ³¼ónŒh|÷·*ÙŒœqÊÁ/*@½læöºtåΤg9/ ¶ŸŠ·aK9³ærÌó­î*ßË%FûDƒ'dú/¥J@œCªnºþ•.ÏñÅ[ ¯/2EÝ1|ZZüíÖ·í÷&¶Ô¥ý©] u"Šç&*zQmP®ÍŽíõÉ|ſᑻžŸò½$Âövà â_6Úv„È7@±×È ‹iƒeX²¹‰¹›uµ¦Ù³¡Þ:–‚/OJ@€ÞÖ:ÖϽ‹ï-ÅËêa»µvèœ.=N”#}Ÿ,Åò¿__ßõÌ;dz^ñå©#¦•,òCF‹Çk]…K€qï:™—¥a‰>êékj+iD7³1åMöéí²=›1Ë_­»S&áçÊ£#ø¹X\ó©7ÄG9¶éÙFœúÖÏ /½‘»¨Û];ÖQÈñ?í›|dÕ¹–~tÿi€},êsoAqY¢cwTj|ª X‡µN8¿†*ÞïÊ0 ëÖ®[=ùk­E,6Œn­á°·õ­Äö³o Øëûµ+líg‹©ƒä5çRDYÓ;܄˷:Ú™Xœˆ¨GF¸úiGVx‚ÙIÓÐò©†È2Ouã9Q’é¯Jh4ÌûìÎW6R4m¥Æ›Ö6*‡kmŸ9#ËzWÃ{é• íÙFššÙÕÙîª9Š˜fÚÆÜÇ•x™VÇ0:ÑLÀq¦EJˆ±ÝŒˆábœ­Û×çGgQÂ̤¾Y(ô¾ÚQalÊë×ëSÙqȱռ7îjY2c…ìxolÿò·¶]© ²|­ÒŽêlû iQO&Ñ"mM©M8·Ç1@tÖÂô7,1[EZÙ¡ÙÀÚqÃp³uÖ¢;R¼qÃ{é[c k3ÝX\k{ƒî$l˜ á5ß91~ôçŠøž•’Ž3‘5g)ãf6×  ’i:\<ü»õ©óš5b¹îÈÕj†#äeÒçÒ‹«&íÆ>?+Ö"t×ÈVÏå8ÿíífÚ â´Š& uè8¡åŽ–¦Þí ñÆzþV¬çozõ6Í4»´U/ É»|j ¦µÃÓ·ìÖØ„h_x¾‡Z„4FípŽ/eû½lß‹וu×Ntrd/c»Îý…L­•Ðw­ìÄÖ²¯Ê£m¢5Š3ÌärfרòŒH¡°Æú}+d’Xã‚%r7n.óÅ$Bª-Òö¬¿ ËÚö™’tF½;‹ØŸzˆYAæ¦\죇‡KÖšÖ(.m…Ï%-mt¨%B×µûùVÜžÆ×Ò P .#¬Ï;ZÖ¥Œ½Xs¿J Ú#ñ½i¥ˆ¶êO^_Þ ‘ +1ÕßUøQ“üA&ÆùDEòëz;&ÊA„I¼ }ÓÔ~ûVÌ"vF€êyÿzzFî£Û÷¬yÚÀÒ¨ƒ¯;ê+Aˆ#½ ‡g!#QgdæÆ¢ÐY8múÕǃ¢ôQÓãZcZ­ª+r·=k™ù‘*7ŒÙr;Z’+<[Þ¢þ!Ac\bqD&˳ìí¿r¬2áT·AÚ·JNlM¼þtákÜãQÈ¥’Tw Xkò£ç[!çx–ÿ/d·vûÀ"jC–@Äëëz¶nŽnx@¹5¨6ùcÞM—ñÈ鯮kh*©³òtPdútò«©=(,¥w‘¯ ô¯ʘ†Öß½~cÞî(YÏ!Ö¥Îì/ëW]~á»VðžIx'‰¥!hŽR÷`(:Éšx}4¨œ«§ÙØ/ÞI©áêhz ÙO‘#odˆt祴©f’á.  “miÁ7×½êŸhÙ£½œ}…ºzÔ›6¿½ýè¯[ÓH4ûØ·`,h#&âÞ_:bÛ£‡—÷¢‹Ž!Ô™;óùÿz® yÒ¢‹~~Ã)—>é[ió©‘#Íf¶ûw¢B’P)Iä8¨ìïÊöåCsŠõ÷ »\‰ ò>•ïZ…ë[7o!Z…j¿JÐQÇ“ [½w2mžt›íÝò/>u7ÙÉ1YïùQ9!.½‰ëj„0±Qnw©ÿsôà ; §—-´Æ;:_SQlVÝ#_6Æë½žC†ð ãÒ£Æ]âÉ{\XÓÆ†Ë?áWao&¯ ŸJ•‘p!kIZö½+M¢NCÞò§ÞHïaM³›[y†6âõ¦½*í{>úAâu}Ù°zn¢H¦4u¹aov²ŽFÝÕNiöeFBɼfçÆ9TNªGC~ýki^ÄÏú{wj¡ã¾EO](í;VM}lê]­¢e+.\ú~/΢+ G(ñå@7 ßÊ…x¾tâgÅ_†â“Ž^kîëVÊSò¡`Ïe±v–UÈ/Œ**Q•ÇCB8ñI¦G ëAdÝH«Â®¤Ýy\})UA,Ëc~šŠ^,ÙЋƒá&Õ…ÑZÍÒ¶¥î£õö69‰7y.BÀ›êH58Øóö!מTEÍäs¨ì*š3.DÞKž…tZRå‘ GBµß—:Rul—Zhä$‚5ÆšÏâ:³šÉ̺ФPÛÆ@us­®4€,›®0ÄøyiéYáåùÚ¤^†#ùŠí_eÚ@’5Š­ûÒ¶­¦+ÉÌPf‹\•Zæƒîná(³Œm +Äk•0hï§Zÿ©<œ7ÿš¹Û¸÷V": éÊÕÊ£xœ„õ qZµB̼<6>ôv™XFU òÖ¥†c(iYun÷4ë6AH?ûùW*yö˜ÀHÀw¿¼y¥"MË´»Í,F=ªG÷Ÿ™¬@¼–çK´´èRúâ[ùSª¶réÌu¹¡g·•wøÑ 4¦ÿNýK>´#,Æoô¯»K÷ög,ÄGIr.âÝ(4²>(á¹ó¦yšË¾¸Ta£Z¦(źi[/©ü[Ú±)ÅG;u¤Ã.W:TqBKü—·ÿýëcÞlòmèÌÇ[µŒeÌ{_ÿuÒ¼TO^•Îìu'¹¯sµxèMéçSKö¨wP¿‡†ÚÔÐWåbqúÔò+)Èçº6>"o¨­¢#mÎð¶=ëeÿ˜Ö¿ÿÄ&!1AQaq‘¡Á±ÑðáñÿÚ?!xš%j&™N›šÀ„ÐÀ÷ŒÖ”I‡eµò8VÚÛ¸6ô½÷‘R}JòuŒm@äÇíš–VUe§üùÀ©Ø8DÍN2Áe 48xé5‘ ²ŸÃq”¬AY”®ªç7"4ÙBd›ˆf7‡´H€.—sn·ŠÞPÔ÷DŦü`@‘˜óo\î½a c9J£º? äqµ_'Ro ½°ýãøÁé–§]–‚íÚ‡òÃj‰Èfüä{LÔÝ<|s€ô ±ÒE#n“ްà‚#H¦Î.¥SŒJ%4k¯1ˆšJÍ"¸$~q‡-BŽäΰ8îºß€Püzë&´–,›¨Ám¥‰C(ˆâ_[É0på…½z~xÉ^v/Ë4‰œ{'Z ¸üå¥t ×`NPDB9kŒ™N—F¢<¹.jGÚ¼ÙU€<äc»d¯ UGã$˜‘UVBï'm%¥à’Œ ¡,íÉa˜(Lª1Hb¼¨?91®‹ÈXäKr7Ësc}o#E ð&ù伎K…k¼z%¨KzHæ2&taÌ ïÖ&®slâKZ˜ÍOWÂJÖŸÞ$>Z=/Š8k™w3ë¼(•d@”ƒ­GŒOŠ¿†2&ñ€ÿFh\DD«Ðääþs)câMk$½šFïþŒ™€R G€õSÎòIÐ@jÏœÙ!wQ¿x“œ>ˆ~Ÿ¸›+~ƒhgÇ9^OJoò$À´¸J€—/ÆÝØ‹¯ìͳ Âe.µ÷“¹h’ahÌ]*©Áô¼¦hEÆx€FìZúÀ¬i ›J“]S¦nÙ\£™ëÕÌœoéç'â2øcaaÉ;÷eúó‘œ&´o¼žfÐÚ1ýà!%Ö¼¦ªãóŒÍ‹qø#y("t’n>Þ°„“,Të_m`éq Ê òoïÊ$?V÷„ËZ‰ÉO%JM|™8Š’Y3óÆY›¶òÍŠÀPiYáE¥JtrÞvÜæÂ¢øð¸ÇDJÈ«eü5‰/^iÇ”jòÄç ¨„Dðâ篂\ÊÚ=·L  M¤«œ:$oÕ‰Üó›pHGÿ¶q^cBá:mÏ7$kI™&8ïævf‰ò÷ŒEP¦•¸ãèT®çMdßš‰òåº` ó}°ýøÉé´•AD¾X/ "B¶lŽ'Î)³Ÿøï v¤‚}a!‹!KMñü5‹ºCíxZ¶çÈ“•ÞA/}™aà”9ÐÄᦰÑ`¸á€szzÀQÔ¤L°Ó o&ë`–4L1ƒEÓj#üŽòÅY úx½äh€ k¬$l½#¶ÑËs’¤¡r‚ZóŠY«"¹ÁZîÒ•¬‘=[¼ a÷=åHÙzTr‰]å!„^‡s€´±od}äE@bVž±á2 \Ûk;Ä^RÃ0ü~«õ•4’a§ùÁD1 ¨Âç S¦2OJò\½ñX¹ÝLi…«Õ=±ú]êËx6F³2ˆÊ§«ø—Œ›q_HWÁCìM ›À@5(VžNûüb¤GTLÐÀ¿9n@¤Ú'˜÷÷–(Wþ.°`¢žH€svç-ŠPRÝôçs‘”ðD³ª*M’F ä "K> ,ÜgdOM¸­£ÿqÐyL  KNÞ'æþóWäOÇ!Œ€¤Pšž»É[6ßK´;2j±É˜i¾ бgÁ˜à‰—{çÒêCmBÞ‘ÉÐä^gVþói¦ø´·–HøÉäöU%‰Zn㟺Æ*EÌ2OýÖ0’1zÝ$àFÀ$jrQ.4LˆyÇÙãiá—“ˆÓ'œ3ÞòGI•öÖP1mšñ¼©X”IjïZË<3’EÇŒ®“íþ\Oïˆ\'ÈÃKàyV?Õ9½a†ñ€­˜Š@}t;pÒ#µ’FÈ|áŽM…töë“ÈIi$Ôü8<á¥ÄhÞËÀÓs}¬ÄKó†Bí|?&Ì¥Õ3gíy œ"O3ç­«‘¾¥‰”‚ž`/Ë‘Ý`²ÅOÎ ?ÂBЉõë#z³…âÝäæˆ·GÉ^pþ1C"ùþ£ /0ÿ}daã£÷ƒ`JžN¾Uê1h- ;«ë,3ž£>o †!øiêÃYP…IRz& Œ%å=Ç0±Ï¸ÆN$âGj™ 2s5VP™ŸÅ}\] |w‰§@H£Ö•e 8r%§à_]瀗„yÞZ‡ÀŽ WXH9š…(h.ͷα. áDÝ ›gÄäwYƒGa._|á߈Ņ…»âÊ5›Ö ¾Ÿû”Lмa)ç¬lîÛÁÞH- Ž=bÃ¥2qFlu6OLÑìÃÄÍSœíi[ŠI0tplFžwþºÆQ¶ TëˆÃ[… L‘G<äe¤Ó¡9!´¢>²5HnRÈ-sÅíÅàä¢"u§”Ʋ* •¯Ïy_B&äÛqZĨY´l~ž±«¢®õ]^^½S‡¯>25Èwåþ±`ŽŒg¼`•ðBš¾ÏüEöÞ§.OÑrAS8ŸPŒ¢MšÞNU4pÙ$ñŽÔVUN«ÝY >hÞF:Åñ. Šȇ°â‰´|c2ùIŠÈz[æSlÕgJèUkœ/(ÖD*ñ‘PlÔ/^Ðc 3„ QÀœ‘RÀY-3ä`3®.’8j9¨ÜªçóOó~²ãÖ\äÆ(0/Øi>²7RŸP™Æq…_Là ÉÇËüäÁyW Sî¦/þ^Mž&™“_·ã%^() 9=ñ3ïÏ) ¯ ¬paAv 0Ê ô±d:ÚjGz}e×"kiKæò]¢ñS&ÔÂ?†[Iˆe4JMÔ÷•a6®Ð‹´WÆ¢&‚ICù¬¤*n.¾÷Ѽ"¬G?b~ðv‘ï>žòpáDÈ'Ö4¦=¢2C‚¹p¸1©º?Œ0ˆÅI;Yùþ2Ò½‚1Ó^ñæÔ€ØëÁÕÜÁ'ÆJ‘·NúÙ€(bÀæ5¼º-‡ú²@’3!knÏï ä*4,ãÕáÜ@¢¬­ñDg!hí•×¶Àbk>—ÏÞlš‰,˜˜2`Õ œ#lùë+•&{¦Í?8”Yã˜"ýRGÇøÈ‰¢r­GŒ‡°àBU<¼“×¼(Ctn§÷…Æðe+ål~T[œ'A~Oý_X#+"fâ™ÿ”P‹¤ˆÁ¦¸ÉKN/Þ5±s£`G:Þ ”bØ™M57>Œ*E $$õã5¯‘´èvñïYGó>Ãó…R1ˆEözÞYÚ’Æ<þ²0šÇó“Ï}›Át‘ó8)u‚}iŽï‰3“ÌJXó›)ƒ“æ žƒ?œf)Ú“Ç'Î8”B62z‚ñ<¯›>p%%¡Éèœj>é+øÈYü„ŒK€CçÒy”xˆ9÷üá߬rĦ<~˜\´5T…1‚Éìbuñï'0‚ª¦’«Wˆ8n2joé‚?¬·¦GÙ9½V^NÌ0D~ã*/a;™ÀrÂ3§ò8Õ åKñùÉÙ8ÄJmñùÈx\§cC…ËåÁ‡S¥’ÿxƒm¤œK1Ýâ‚. ×ç4°}§Î½ä#‰5ˆñH!¯”²>rj—j ÚבásKA§‡¸Éïí – 2ñ‡ßN–_´'8γ[ñQ¤{q÷„ ³É«(àY'G‡=ï =ؼ0‘³Ùñˆá¹d×e:-3xCdàPW|ç Œg‘õóˆB¶1n©_ çå6åþò\‹c.Þ0ŒA.•š‘º8aHt•Ž‘ NG÷¸ç•c¾&ÒtÜ.òž¿Þ>b„\<…k<ºsÆ1}Ÿ¦m-dˆä†Æ…cÊwƆ„S¨™\”—îÇp7HÑéɬ1G²$íQ¸çã¨L$g$ªQ=à7º-æ²ZÚsZÐ÷à á‘Þ»’XëF@[µ‡R‘Œ/XÁÌë[&õ’€”SÑXRa›‚JMªyÛëаž%Ýì_éŠ*T‚ È+öŸÞngë6?jÂS®»È«U­9· ‚åQ¹ Å@0ÄX@’ú·þ2}à)ÍØžpñ…Ê'"˜U9!!òNü»{Ä1'£¥ ä2(©9ÿ¹¼ÔÛ¾|aˆ$T‡[ù;ñ€‹7Rh‚wÖ†²¨_meò1nÿ4ãýûÀ9¶Ì¨Í¾*³ÄǨçÉõd°+Þµa•*R¯iæ'R/{÷ž4A±Q÷–5xöüᬳ ãËX%JÓÎ*ø€Ü’\'†·(u¢ËËÏ~")á¸äË@ø¢ÐDå©19‚ljp9»aÖòxd–Âíâ²_œ¿ èþ+&]ô¹ˆ2ð²ê²Ä™>?F&"´á/¯ÙrsÂzÀÈu‰>2:JŽMF ‰`è6Oy7yäúÖU¶”¦¢c S‹Ö’a)®D¾•þ¼2©„‚Á«eÿ¹"ÞpB’?œ”¬ôe à˜¾Ãk[Êj+×ç/’×ôÿl5ÞH<CʵAÇ<Üýð'4^FÏåúÁ\¢:€üä äD/}sñYÞL’o!†ÍäëÜb×x²YANŒyðÁÖò&“øÄƒŽDr/Æs @mÇÜFø§’W†­öIøÀÍ´§ÃàÆ<¥—Éÿ\ ‰XGj«vÉÛµÉêÀH&\Ñšt¿üÇ¡}¦ž²íFA|yʨ‰ÂcÂDúÈïgPIŒ[* d¹˜Û(;«-ÎÁþ¬l.’ËÞl«…G‡úo&·Ï¢B¾ž—>è­xgN ¢y 4¨FÃæ2ÜWÄáFlwŒëê6Kîú3¦Qb(#ßñ“·›Ïƒï'½«*/y¹2ˆaÞ5f“À4{ðåZqX•X}˜b¯„âZm‰ßHþX²mÈøÀE”†KÄEŠ‚þ3Z•àÍœ)ŸH³ÕÛÊo&9J|yÃ\‚Þå*·’”9)†¾iÂ‘Óø¿x,&²á%‡ŒHÉüRò[(aFòæ“ æq!¿ò5‹Î C4‰žŒOøó ú|bó$÷Á;0;sŠöVÌD`Æß äVl^ˆ0öäÉÓWß¼<*Ø*剦7‘ŽÂ€t4 1]k%Á”¥®=Tý¸j~å~ðKgÿÚ O‡:ŸQÔ€ÂÓ1&hñeÀ´¤Æ¹,êOQ !Üž>ŠôØMhÑHþj³¿øâ8ËÉjÒ „°º¤9ÄÃ:34¡LæùèS¾µà1@(ú½·/ÒoÁÂvmZ.ÓI‚)ëÓFÔò’ìÄ»9tB%V9Žçî °8âÅç49~^ ]#†Ù•-i,kâáq";ÿ ™Ðïá´$(«êâ]&}I+7+±õÇ3¼n^^j]¹Ô!-Jí©-Ø_rÒAö2r@,Xó\ívÒæRï‘‹©íoZíšÏÿÄ'!1AQaq¡ð‘±ÁÑá ñÿÚ?Î(ø%g„U‹©EHé(G{¨âÙaPÜ¥Ä#qÖ÷¬SG&f㈕ËFܰwH¢8j Oø·Xˆ˜bÈÓû³óÄ£ „ëïÇ:ŠTÚÊçW]3ï j†ݦ>Õµ¬DPjXœbflÌÎ PAiÚWª aD÷B>uÅBŒÞ pê_kW®Xq­–Ùé Ì4ÇòÉÝX~ºÊ„„{˜¿XPDdk·ò*øfP(6ˆZ8Ì—ø>‡öžÄ{øÿfœWÇû7Yû|%;öýÌ ÓRÊ+Ÿ™¡ê2ä`§ãõÿ²…à÷{>tï+ÆjàAš*0Zá X°¨{?ðêÈÖVD­ZÌ¢ QЕÈ{ÅnžgaH8ðkQÊà(Ž‹Ø~g##¶%²ýB€77›ïz‚`â)ÉÛ´Ë&Ù%¿Hæ¶øŠÚ~ÐÊS–ëûTÕÁ¢¸•íÒÖe.<îf¯p ¦+·y`tõUÅ ËàŒ<;„詉W)›J–Xéø€[¾±,V‡Xë¦+ªþŠŒ +­JS[‹ Ôº@žU…ºùQÔYçxˆXÉÒúE¤K,uŒÊQ—*˜$tPÅJu¨îk……U)áÈb¢Cí~=4Ń˜ÊÃô”§® nª"®³0q,£†\Ti˜Vó e™€Øë ¢¸]¦+ÏîÙAÉ…†°ä%'¡"´@ðñË ¹xçéÌ„-\ÈEŽS˜Ì­ã+;¦"±¾°UV& äÂËÇ*™ Ñâ>Àv©€qš²Ÿh¼ßÍË`Ïæá&§^`n\hšÒ\wf6Ó.k´±§ >i¨ÅÜHw‹sy”„°–g 0‚2ฒF)¨ú¼Ã¸°[Qisÿ„ÒŽ+õœ˜ßi®dùQ{qB ›ù™ô¬´–}ª‚ØÌˆûÖ©b P½L¸êš‘¤Z}óMÌÀ 0*ˆÑX‡s(ÛK„†×goFìBÜÃ-ÝvéÖËŒcET±­ÃM˜§7ĵ‰•˜ûËÏŸ¼Àr‡L:±¯ ¬M{Zï0ÙÙš ÷J˜Ã+#t¡â0©|’°^ @WX+c+™j]˜”FÓº¦‡ˆòäPK]\D.ø™ÑA¢Ãʽ¥Kz5ÙfXðKEæT›Jó˜W$fG5 éâ ,ÚEM±PcXcs~LËÂç!ø½à-Ô üô‰Ôeü¦ÿ|øÂ5!J´n"¾Õõ‰^a+åíÞ0 ÜVü@^Xlê=HÒ7T`N¢…rçù@.¢‡ã9*lùí4œa:r54zMU×Ô¸éï /vÀXáÇϼÇÕ?™Eõ@(­0a*š†"÷FQ{2¡JeÊÍôÜF¾ò”ÌJš/çÀ³  tj!ñŸÌÈšµ`ÐɈÕj …±ìj¢h¸h;ÍÁ6æ½åªï™Jì“ø… ƒƒï™`r#DÊm¤%ùÌîKItŸˆ¡²ûöJT òJ\ªXJ¼‹‘°¤eUA¥Ì³y‚Y‹_s•‰£†Íž{§ ~ñ€U÷¸Ò\Ïßáþ¾ÔÑÜ2•.˜D„z+( 0Âe‰Qê ƒ\zoËù ‘JƒPÈ3ŸÓŸïÒTÏÍOª€B¬Bب1{…@Åë*Å·‚`¶+ZúÅê m>ùƒƒ\Mb×H9œÊûËðýæ6ÛƒyWœ/¤W«ˆ£GP"¥Ëƒ'h‘ÄãA€É3CZ–ªUÍCš_Œ3-L Õë oPîp`òüfB03]e‰¬æ³ðÍð|ìÈ5Cl¹©fϘÅ0`÷•@1K?¯ÔJ J”ÁššK‹âèË}ý¬a¥ŽY”¶K½j Vm„|sw—`ms5…ß–>Å8zFì®àírÚE2÷©mª÷„ÀãöM`^™¶0˜ÂËOx…)šƒPÙó(ˆíJޝø¨¹Bþ–pøKJ ®ß¿ù¯hâ’ÁFð‘Fî!»n³ÌÉGåÁ|̪Ì[bT½£2š‰†Y<æ4%Kði‚,bK3¾<º›ç._1ð‚¬Äb»=btÈŠi ï˜ ½\̦ðÏÿÄ&!1AQaq‘¡ðÁѱáñÿÚ? Ä)}:F᪗6^ÕžØÈ©õÚþ`1ªæn.{Ê7º³Í®#a>ÝÍÕï_‰¹»Žƒætÿp`iŠŒ?‹‡L\(©š+´¤¯™V°bXbpÝìé0ãx<˳Yižµé¬Ì ã0² GÿPÙwœ¨¹×0.…õˆ-3ÄÔþ.,ÑÑæT›ï¿òoGQBÓ•åìt„Pm–£|bRªéí¯^ñg[ü3SŸÃ?!¥N]î¼EM«Çc´ ·|×7û‚QéÎheæ1c2¹ÁÅ˲lšˆ]vƒVG¸žÌ醆½?즃 _˜ŽB”ýë$cïYQº¸ÔÝ ÿqkl1Uª—‡¤*{—þÃyŠøWXûæ ¡¸jh—b2¾¯Ç1.Ý~‘´¢úÅí~ÿîe |§ç¤U«Å~ñâ4 |c߉N@írbpÂ/Þ5í©ŸX@Ûû”Zg-Lf§yÉâ7*Ìæ¾-q2{ExœÀ¢¥Ã3s¤º{n;Ð5Yíæ8‡9ÍLŸ̢·ï¬TZ—æSÏ·õý¨s€ø„e׿ê@\NÉ”+öøoœ|BúÏš©1ë+‰ÖeŽ%ÆžMøôà…[D£Kºöù„ºË¶ûûsÖ \¿l³'MQ¼AA—û‚êãŒYÒ•ø”;Û] æ—1£…v:åÿ’‚¹cŸ¿0q¹»¯¾»˜]»G X^xŒ{Þqá]ÿr‹<\«*ëu÷qAE¾ýâb×+ÈD¥7¨‘ÜuEF®XÃUÁ=±årÔZ¸gÖüË#C¬Ç +“·v /q•–ê±W2ÕGÁ- ƒÄ«…aní®XÀ¨­(hë䊥PäŠYa.¢´:ÂQwŽÑ¹»/ußñ)Óu«>};‘Ä”¬ô©j6f½!µçóæ ,ÉýÊã´-tÝ|L ãã÷4uMú@Ÿ˜„š^° |LШ]a…²ÏHDÚ⸲½AH(2ýë ‚å¬k/¼KüE×ïîýþ „4jÛ×7¯1î¶ÖR5U¾Ò™áÛMÂwUÎ?òL Î%ÚÝÀ8N9‹7Zãá¯^eDfYlf$0•òê3ª¡Õ|ÝJç=QâÂø|õï/ 9Þ}`:jV“¸¢·}s*mšóQ 4ïÍÄþÈ* n[,%o48Ö Ÿg\AK±fR¢®ŸyÞ§-1c¤28¤¾¡v<õ‚d2®òÜ¿¢11RºÌFÜn9Ì:¯Q+›%ÜÍS®Ó/N|ÜL­ó,Ϙ¢,¸ÔÔÊÍT@_wÒT™£2ŒÄKo âADÀ\`Ê<ê#F%5\\!­í¿S.âºt&¼Gÿ^b*i_÷´j¯+ íš‚“y„FÆ|ÅKWÛgÖf:LˈHR”|#/´0 ‹ßäý&Fe=Î/§xÔm.M{_¼VJËr`Ϭ»hôMñ,Æä„Å¢Á¥¥PVWâ$Ô„zÒôu”ô¹h2õŒyÜ2¥â OFFËù‡ùŒÑkÇŽÓ·9äÈĽKs…ãï¼D84‡´hpºóü%R¢1Õ~e1rvÄ8Ü{¾#SÔñà ¹Öw¨°øXHî”zÚù‚ßL±‘† ËaWh)µâkâZ‘o¸=áƒÑ,Ë%˜cÒ2¥5íþŽîÍY¢nÈVÜ¥¨Ë8žà¨Õ „¥_©Ê-âZ«øÚØRÿìIQ:÷óÿ"¥j¸Þ>âŽ!cRŽ …®|E´Áƒ±2±pNŒ;j£¸Y VÙ…R8Ϥª­Ô¬‰ÿÄ%!1AQaq‘¡±ÁðÑáñÿÚ?Þ·PE† 2 + ´ÔPÀÐ/b~8w€¨¡1“€à"q"ôA’¦ÀŒ"œÏ*(…„Ãøþºá”P€$Œc¢à]g÷ja¡‰\Œ…a(‰ÐQ:«_@„ÞÉeê?¢DF8F‹’ X5…ÕöÔÆÅ$þn[UÝN†KŒJ{MÌ(ª©BÎ.¡ƒQ„TÅ&òó[|×B˜ k^ rù°ƒXRÀIÇ¥Y¤š{Øå ã·ˆK†öŸï.=8ñÌTTàB]%LŠt¸Gç ®I4Mh¼ {—½œ3ô;/ÉbÈðEÐ;dw^CeQ’3Òá£Vq©Ö©¢@`—ÈV;ᚃ¢RXpTÝkÅg¦€cqJˆðkßX!Z%¯N . ¨B¡ô¢½cáW.!Y€‚$6ÉýH6¸í«žÊ²,‡x„‹ÕuAŠG"¤ n ¨3W‰ Þt‚/YE7„‹âKGÖFþF3s£Ìœùù×;¢Î¿÷\Ûãï\_FÑÛÙ@èˆ[8‰ƒtÆ*QâE V"¸²io 2T;{аØ(ô¤ïB˜z!FÐ ¶§þ(‰¤[ }H¸ N [Z¬.L[UP2™Ùßê!Û»Xb˜ƒ$o fÌ"œìHþ§YÈ«1«},AãÔVv²=øs€G!@@Ý ½?‚¢dX žÃÒõÑ ð}§ŠŒ’Œ‘~Çn´ãñ]„9ÁÑ]–ô¿õÉ ±÷ˆ‡¯N=áGÇ¿?弊w„œS~åDPda&21&<+*"„ Bß@V#×%ðW:ŽÓ4æ‚:!%pu%Í+Ë@˜E BǰÐ{‚\ TÙÄ¥'ä$M‰•· D_®Í]‰Ðd4³€ ´2ªR©:beP‚°Vwìç/ —À0€Ú“OÐ!‘@á¿£}-jÛL:*zÌ"ì`‘H,(«ek×6¨­K®Uif }q§Á:ïxž¬ŠÏöñ„(•{'®.ô„C/h%tQ¬/ˆE04¸·¢ð;@zdâùª9ÔØp`´&Ö8 ó x=!]½ ŽT!•1¡Z¨éâTŠ‚atIZAu‹ý¨â$ƒ²ôw^ O¸Á{Aò|áÇf$ Ýît—ó8j ŒF”V„ÿ—1‚z;Í%œ4Ýt®h|‚ °™P²Äò%J’K(IÙ•9@›(¨a Gˆ5„Ô‹­ì¤bqë ÷è|ÿ?ŽM)@â©ï÷Ì$v~9LH >xò+¨´EPb.*[5x»+¨4)‡Ggº©!‹)E¡H8RI©FjxJeâ$CÔã¼éÒQ0ƒ^È@’h¼„"ß‹cckF°x§ìaQZuS³h6§<Ò0– ¢Î·x.š_ ×]òW:ÌÑVŸ/³|jbNˆˆêp….¤}Âk¶¼êst¼Ú&¬±N’”ùRÄO¸Ø°8!ˆ {é^Ëà~g*˜X1SL):rö{ ·zwwËÍZv*GóÄ¡ŒìgþœFÈu| ÿñáXq¯Æ„\€”C¦\Ò;2Îo—ì 4Êõq x3F<¡Ó»ÍB‚,H1fBÃð)ÀyÞ¸l Ô•8fŒ¡´:K½þ8£Ý aԉܘ¼Ñ>Uˆ!‚ƒ®¡7œÈRñÆ÷èaI6/cÔ‘ëƒö¿‰Ýz¼ý%JªD% AD ´")…`;zD§ljo¤\XÛ:‹Ç«ˆMÞÑârÓ-ÒD:%ìD‚OJ!oÎWD^»þ¹E( ÖõÅE4{<¦ Þé@±FØJvèe"+¢?-lœ+à·ðÕE»³ Äìx•¡É£V˜Ëѧ}ðFãòù8e&‚DJ`bhó/1QøzCÇwT”ÀŒ­À€jPXÙßUÈgšûêÈUf4ÔÓSƒË`Qö-›Å+á©Ç4£á¨€µ\º÷Énƒ ¦^ã$BK©‡+cMíI`rèâ¯`Ä*Ü€!†2qÌ»ÿcÉM‚‹vÿéɶý¼Döº'd$²…"¾“´ªåΆˆ4÷€Ë¡å#°€ˆVãK*V¤ð*c\Fi|L°{9Opºô”’ÍèæòìAY)Að²C:ã…XfÀ&VàmÓW­teýíÈ}a®ˆ %¡Õ8úcÕ)«ar§¼f ÀùšæÒ‰úØÚÄQQ rºã±7±@Z„Kú¹!8ˆ[J…2D¡¸ìUèàùä.'±hÕÐܼ­6ǰé(36W£w¯cÔóOëŒ"Êö×óÈøÕëÏþpÏçå™AˆkBk‡qž"€•}ÞdãE@@!)ÉGÎsð—³(+¾%˜„…@ E`&c';÷h è€$!¯¡(0„êeúJšü$FÙ,nOArK‚i ‚‚/I› ù&ŒªÜíqhƒ§þøe+Ô<-+ÝïÞ21:iDU(¾K.-Â\KRxåCOþ|â–ôÊ KQGbU¤m"/É$„*¾Õ2Çáa*ŒUêL“®¸ñÁÎÉ¿8Ñ\‚ßó˜ì}œŒbIB(‹Z8HpLÂ! J?@ɇ³a@Ž#*qN2T¡.ó)e: Ã0]9 êÅõ߃ЯF2F_† æÏZ Âá–â\‰ÖD3:Ârn/ ¥±¶bB¼Êf÷>½ÚÈ*F—E'$׫t8½’(p~"‹[f4`¯GÎ7‘ÚEs UPd÷F râ£)uÂïuãrhœÅ‡Ëú>F":Å1Ä‘´,´D&áMfÔŠ‰½ùÌ´ €8´Hå[üq¡T€ªDª5pÍWeüˆµÐW7Ä«ÀJ¬ª`SéÂrÀ PSA«†cÁ_ë¾"Å‚©j,ÙÁAÒN§%+šIeCµë–ËÓ`@kZìG†gv÷Š»¯W‡Ð& ìê~x`ð]!D>¼3 è"=¶5ù/@ì/P †ü¼)Ѧ"]¿_ãxÂiÚиP•há:_äЩ@æ+õ©"£4ÞBäQŸn(:uÄŠ¢°Á({׈^"þ^tK/œÙ.I¸eêqТ3z+â¤÷§Ž ¹:P5A<£ŒxdkZJ©1G7?¾bŠnÁ %žÎS¦8˜…G@@0FpKœ”į:BÊH—ˆùÇÊ4«‚Pn0 ƒc^•“ª @Ij…;?Þ)™àG¥8ÿ]ñêv6¥Jе՗Œý‚ƒ1@0Fpñ!s$(5£.p ñj”CƒÞjq¾Â·ÃNÁÑ4"wÇ ¡µ-KiƒŒ¬r…¹qIaKâ¢ê:9õÇ`F¦ðûò‚¶²Íh0wÆýå“¡Šn ¦ß›®¨ ˆóRWêë¢8ëÒ†xNð]˜`–i—ˆr˜uÜŽV–-©Õfõ¬9ªí‚*Eg³FV¤**…Ë”·¶B@¿P?þqÍ"DO¡p?‹À$4ˆ©D/jKôúÀZ í T®SàÉ-¬¢€•H‚;Níä¢Ð0Ý¡¬®qÇÙ*@·-»¥3áZ4ì?¾tdº˜Ü$¥í9,tõ'ú4ŸÏ ¥‘Û åtµ,ý¾quøQ¢ªˆKÒfñà—(3²‹(^qYZ´pÄBÉØNÓi#ó‚f7 4p*vð¡K±ùÑÈZɵ ðdŒÄu‹GvE'YŒnщõÃÛyØëÚǤ×ɼ"—ŽA&dlæŸxö8oµ0‹V’[j 3 ÑcY¤¶¨" ÊÒX‡N^«0Àdv´—sÿÄhΪ˜ÅÐGÉ–MD*X+{9G×n6 ýñE@¤;Oߨê¸2:&rà?¨d´·ÃÌã~Ízœ^ØþN3@Y€¡Ÿ¾¥C@QX룓Ø42Ñë¡Þ ‡c„%I)õ Êäd%~%]ƒá'ñ+.„µ:|â–(Y{bÞ߯Ò§òèŸýrQhGJ–î/Á¼ßCsMGl"ƒÜòDê¬ÿõÖ a €–Þ™˜‡‚—L=@3ƒKD \%q3GŽc’‰XKΧhItü¿Ä9@Vþ÷‚ )ó·/—å‘a1¶²õʇ­„ŠbŒc„´¬{ ˜ǘ‚Fßd…Cg\È´ˆ¸Gòr%x!,·Á©ÇÌ>RÒ½­_œ_‰ äû~×*mJY+ÑE™N p5î)Ÿ²GIД•«pPýçó}~ ¨3#¦üáÑϵšFÚàŽ|wà˜!)|A}Í4Dk°BŽ&eåtë¥"QRÅÌ‘F]˜¬„Vi"!Ê×p ªCœc›¨m1yO±ÄøßãþYQý9N Ïe)j¡¯Ë^¡ƒ¸2¤/äá=ipËol\¶ÖoyM2BÅè¯Áᆇû}¿×+¸¨ì‘MŠ=^+w$^ÂvÚÎ¥Ó\k…×€kÛÑ4Œ0Eh@Ööð»Â•DϺôˆÐq`‡J«ÓªÉÀ?1“ù¢_«ŒBKW(@Ž67•xSàH0[©§ÿ€´0hýó½Á´xЬ@ÔG¥1‘$HâÕ=T?íÈDW¤ùÿ“l¯ƒ›¥T€óÏÙÃÉ:W,Ì!ºu¼ºp¥Z®ÿÄ$zrˆŠØZü`˜ÈT6QZ¤-o¹síƒH.—áŸä?Òq ÁH9üŸþðØx1>÷·Éɳ0¥‚kp|¶áHÊeŒ„‰xS©Dw@ˆ¡W‹êp´:ê êö½ª¯ ²Ý¡lp’Epð—ôø ^Âe£ /$ô-ñŽˆs(èß´WGMO0èë”péý^K‚#‰0E T°_.vœ“\ä¨ÄHAþƒÂ›,wué>‹ÈéHA)8‘ ]NaY„(içgp¥%TöÜx*{ÀPB«.®}ò¸Üo ›ïœÒë~øoEí:P¦ýÑÆC­¤?zÿxyK*¿9Ô夵?vo'xÑu7xp]$HuˆQ\¢9ë¬4Ó>ˆoí’Òž*Ž?ÚJQR¯Èp JQCÚÇÞù}¢ð{ÿ~òËŸ®2¸®‰ð5syp”Sœ<`é×óõß|¿$ìTµ–„œ™pvª­Tejå113¿ÉÆø‘( .¹Þ±þµ  "خ }Êo¼m¿]JÒŒ^è±õÌäêˆÅçVâ*ÿ^BʼnMŸÇ ðE•W»¶Ãõx‹Š²rõŸï<l%ýÿùʬôhìA÷x.ä`®Á¬=Îÿ¢)gZ+çtOÃD_8Õ‘`Whj w` €DÔÇã¶öpS–T—ôÔK ø A3ê*ˆv˜ñ篬èÿÇûp EÎE=ÆPW3 <ÞZ&ž¥N˜1/Ð=9R ltŽi|äº £KPíÓ;5ƒDEî÷OÇÎYIOÿ÷qR™ë8Rmn®ð¥ 'ÁÐ#)c8vÂ]ºå§WCŽ ×Òß„‡~5 ²œYX:†w`õÓ÷‰ŸRnÿßÔ¹)êjúq?ÝÕ…áDZ‹ó€¦’`k¶ñ–DQÑ·uiù{Þ‘ýŒTßÂâ Ûÿ|ã¦`e öêôx RÇpòäÄL˜üp'-΄¶ƒÒ¯Dxâé´nõÇ?ÜÛæ83@B#"ýÉÁy01~`ÜGŒž˜?ãJ¨ÿÔ'f'\:%ñ $뇿à&ëÒS]šþÎV*GóÌšOèá&7\œq¾M¯\1 þ:øQÖ MÔŧ®XïY rÔk‰è%Mà/ÈtTLé‹Û?ÙÌý†ŸôædZÅ,ÿ—ûã§åàã©(­îœAØFå_Õ×ïïV•u!‚¦Óž*£ Y à=&²agrå=9ZŒ6;ÄŸ¢Î6ŒÂÃýâ½it/÷ÇZ>ð’°îyš«ñ8¤™¹øA‹ÏW‡A½Ø(–T÷ËÆ ôx¸!š8êVh C„ÒêHê&Ǥ<G¬«þx,åb¥ŸÏ?ÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/images/scope.jpg000066400000000000000000000577771510465702400255000ustar00rootroot00000000000000ÿØÿàJFIFHHÿá@ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:20:49 Ðè ¿ntÄÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ¿"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ôÝÔ›ª-Ô…ø®sBBÔÒÕjazW!zaz‰¤¦¤2bõIQ¨Ùø¦Wo”o¥rÚ¯)ÿnº+†È5Îë[Ÿ÷…Tw!›¾˜6Ž€ºÌ?ZÙó=ëð¥×Éq=a]/™ïQ%©qزdæ¹­¶ÛH}eoç[FNkN;-O»·ó4º îl¤Õ:ÌGzÌT«-M†kG6EN­\ÍÞ£4Œ€1œâ«ÿk]ùlÕ”¥fvÓÃ9$Û;hœƒV‡"¸%ÕîÆ?~Õ(Õï1ÿùÖNfë~§v½;5Àÿj]Ïwüéµ.ÿçâOΧÚ2¾£ýã½Í(5Ài]ùx“þú£í÷'¬òßF—´e,÷@zÒäzŠà>Û9ÿ–Ïÿ}Cu)ÿ–¯ÿ}Rö¯±_ÙÿÞ;òËýáùÒ¿ÞÀ‰Ÿ»±üiÉ<ƒ£°?Z=«özþc½ÀÇZC\$º…ÌDmÇÞ­½/X’tQ6 ÇZÖæ8±8eÖæùjnêŒHd3Vq™ÆAH’“Ц©´Þõ~Úí,->Ñ€ÓÉ÷sü"ºÚ1¹'ö}óVÙÈ>à3Lm>øˬŸ¥B|Eyž ‚_ÜÃòŸZ›1ܰÖ¿óë/ýóH¶W “%¥É‚!ªßð˜L?åªõÇj?á1˜^;R»ÃÚI´í²½¶c⪽¥î?ãÎãþý7øTâ넶е!ñ”Àg+¥…ÊÇM¾qk(Ç÷”ŠÏÔ<9wÀ›s’øVÒøÆf$¤ŽØ§ÿÂ_?üó_Êäº Cž°ð½íž¢nQ†Â»Y6‘ž?ÇšØ6³Ž±š³ÿ „£þY/åKÿ Œ¿óÉ*MÉôЦm߸aT¢ÒZ4جO$óŽç5³ÿ ‹÷…*pñ‹ùd´½îùºlž¼Ñö ×Ò¶?á2ÿ¦K@ñˆ?òÁ){Ý‚ç;u£Ü\0duÅFºä}ò~‚ºqâå?ò/ü%qÿϲþU6}•yÚÉœÐðüÿÞoû枺ÿ¤Àk}üXªF \jOøKG{uÿ¾håòÖ*1ˆ4?òñÿŽÓ¿áoùøÿÇkXø¾#ÖÿïšOøKa?òïÿ€Òä_Ê?¬Ôþc0x}¿ççÿ©äõ¹ÿÇj÷ü%Pùvþù£þ«b1öX¿ïG"þQýj¯ó‡†ÿéçÿ§ gþ^¿ñÚŸþ{SÏÙbÿ¾E'ü$¶‡þ]"?ðG³_Ê?­UþaƒÃÇÙÿ¾j9¼<ðBÒ‹’ÛFpV¬ÙÿϤ_÷À¦M¯ÛOBË1‘Ú“¦­ðŽ8ª¼Êò9«·çð­ 1¾D¬­ALS'8ÜVŽš~Qô¬è«#¯%&š:›w8Õ ÕnÜ ¸ŠÐó™Í<¾õbW/o ÏðÖdW£mÖ0Ÿ¨ýk±œãIª7ÐÅ:¨–B€ð*á5  0äTØ ¦µ¶Ã¥°'¾ß`==…5ííOK²9Èã§9þtºŠ­d•Ì£Šç¡7ªþTÔ.V:¦– §Ú‘܃Q3[•ÛçGõÁ®\ê3³ùS£7û?•R…¶'˜êÒXcbVhÆzç52Ý÷æ™3í\iÔfÿgò¦FoEü¨åc¹Ú}®ßþ{'çGÚ`ÿžÉù×ý£/¢þThËè´¹\í~ÓüöOδAÿ=“þú®+ûF_E£ûFOE¥ÊG_+Æì Ï=Ïÿ^•\!ùf‡§9jä?´$þêÖÆ›þ•n²0ÁÉRi¡§s¡VR2¬ö4¹ç½E¬1K2y.¹‘_píÚ ¢9¤«HÀ£'Ø®ñ9À$A§ÄL÷BÆXÊ‹û‚dft( Œ)î}h@B"´ÀÌiŸp)|«UÁ§>‚˜ñFO>™¡£ °‡4Ä=bµ95éIåZÏ$ü…1"^v°?JCyå×èH  ~Ëlï) ã8ÅH¶¶ã…Eì*³Gã%F3LÈ_“òúb—~ÍtG5­¹Œ¿–¥‡z®ÓHœrz;S|ç=qÎJC¨¿ï}ÔÖŽœzV5óæñ†zm ­;0Eb·geO†+Èê-Û¥[ Y¶òt«a¨g9ÉHõ¡hùÓ—Ùd;VƒfŇ£Ÿä+±ìs¢bj7î3T¯-¥Ãø3 xãß›Îq€„¡&,OQ¾A×üûT½)+Í`?0úUøOÍY¶çŸÂ®ÂÜÔÃcjïß5"n*µQ¸« Ô̽K§6/G¸5 ‡X·úr{äWKØÀÚ4ÂiIær@Î+2Æšáï,çûlÁaüíÑO­z4vÈK®~´ò±÷Óó¡NÂq¹æ`»=-¥ÿ¾ (Ó/OKYïƒ^šLCþZ'çH^ÿ-ó§í_a{4y¨Ò/¿çÒ_ûæœ4kóÒÒ_ʽÍ„ËE¤óáý`£ÛK°½š<ëûQ?òç'å@Ð5#ÿ.úW¢˜?ç§èi¦êÜèhöÒìÍ}ÿî¦åÕ¿)G†µ?ù÷ýEwæîßûÇò¦›È=[ò©ö²³G<7©çýHÿ¾«­µ¶’ÖÊŸï"8õ®Ø=ò¤7ÐAG#è*e6÷)E#Q´[©>bAªÖöIlK)$ŸZ¿4äfQØT.p§éNîÖ -ÈT²F1ÏáLi$9ãôÿëÓä~Rx$¨­ôB]¬áYº{~4õÛÄäî µºô¦I-ÈB ’*o4ya°NqÀªWw 6Ü€=OA@„»uïJ‘¼LJ9ê* [Ȥ„$xƬ±Ž9)Ï3òíÏ¡j•SrăYr_ª3Ž›@ýjä3‰,Ùãô¤¸Õ?†Lý5WT}°Dƒ»ùúêýÖdàœóùÓuW̯ 'üþU2ØÚ‡Æ‚üªÜmƒT®-.í å‰âKŒùe¸Ü\{sIvØ*½€§èçï³­´Ðõ9âYE«¤MÑå!ëV†‡t8Ûè&ZàVWQ…fØÓ–i£~|ÕróK«ÈÄ/j[XLw‘1=Ò«ÜÝ rAÈ,íRÚ_+bÁÈn}@­v"êæÛM0š‡íq7>`üé¦êùè¿fY14ÒjyüõZi½ƒþz IfT‡˜ ì Å5n"w —›Ð6sUç6ÓL%i#©t üþ•Üé«Ð7çQ·Šµ»~tr°æGDH¨ä8¾•Ο“Òßõ¨¥ñ,®…V3êiò1s#xǾ0:Vtš\fq&[ƒ½³YðÞ€‰ùTg^½=“òªåbº:E Uyí’\îèkëwçºÂ£:¾ Œ~ŽV+£n;á åç'¹§¤'Ý=1XQê7ŒØ–FÛíRý±»Ë(¢Ìw5^Æ,¤š°‘(M˜ùzb°üòå»þtyÄËWüM+çEkfÓJ[Ä]ÜáT óZ/¤øv`óªj°XúÅ×ûưôÝvM*Âu·„ý®àmûAnQ=v'Ö² in77SÏ4œYTÚ¾¬Õ¼Ô®õQ&»”»ÀèzQÜ(,}i¶‘âvoAPÈå¥?Z´ˆ›NNÀ«°[BmZâo1”6ݱ÷$ƒO³³·šÕ™îf<ªç Ï9«cOe,–Óº9È NÝÀ¿J‰Ml ,5oá>õ‘¸©È$V¾®ÌûÖ)5ж!îZMBhøbµVãÔá“ò‡瑟­c“L&†“ ̬FÜ}G"•¢–NEA\âÊñ¶QŠŸcW Õ^6ýê =ÁÁ¨qf¿öU܋̄ƒèj±ðÓž­]´Ö­Ü&(þÅlÇz¥G˜ªÊzj’*Éœ¡Ðï`?/+þÉþ”Öµ—Lj¬\Ú»dky~ëãØÓ¥Óã•~xÃZ\ï¨r£ÏŸOcÉ"˜tÒz‘]œÚ br¾ÍÍP“J¸‡%£Êú¯4)‡)Ífz°£û0zÖÙƒE'•O™”ÅþÌ_Z_ìÄõ5³å<Ÿj\Ì9L_ìÔ/ötcµly"“Ê£˜,d ÃKö%þíj˜€äÕYîa„`œŸAEØY>ĿݨeXbô&™q|òpÑíTÙóUfKd(?tQÏ4ÂÔÜóëNÀ+Ü,cé#¹vöÅ»#ïz ¥Ì×fük{N‰~Íy9\¬1¾§Š{ rŒ—ÒÌæGl³“ïKåæÃxVpr¯éöÒµÊË"íAÎ[ŒÓ°ØÜ¥»žçŒÓb0‘[kŠ–%Gˆ+>? q·Û÷dFƤcdž|eÖ7íÊÓ#Ô R3¹?Ýb¸­ˆ¼3«Ï§-ìVŒð6H9=qYsYK>l,¬>e"•¢Çvm_F%ùÎMfO¦0ɉ³ìkVèáÎ9ëQ ˆûË‘ýåæªíŽvX¤ˆáÔ¯Ö¡&º¢±LŸÂÃÐÕ 7ÉŒ”?˜¦§Ü\¦¦“Vî,. Îä$z¯5LÕ)nìg©mï®-OîeeÝêáQ_Éò³ògv=ê,Ð3 ´ñ–ß¾ˆ‚F F¡­Û-u$PmçÙå3ƒ¡®šMăÍK‚`›=Z Z9@§=ñÁ~&‚õr ÿu¸5åúÕå·g˜½6¿5¯kâH[ !{wõûÉþ"²tŠS;Éôè¥Ï›Ï÷‡ó¬ùt>ð¿àÔË e·™ÂÈ%AÜ jÁ«ÛK4{÷“ü+;4]Îzk àûñ6?¼9OjíãHg†Tn†³uOìëD?kP¯Ž~ñ¤›Ë”ÇZ©sw ¸;˜gÒªjZ¡3:@¥ŒUºEA²KD\.78³äe‚I,çŒg8@zóþðû}næº)¤½Uˆ­‹okVã ¨Íö›wó¨öl|ÇMvqϽWêw¿ÞOê*{Ï»PáXѽEPÆýï›ÿn>¿ˆ©WÇ‘ÙëùSXÄnÿixoþ½7ïØßŽëà @YGIAÇЃUn4¸'ù¶íoï-Mg?>ï¨ÁüjPØ¥°ÎzóH¸ ]0ãØ`ÖS£FÅ]J°ìEwûÂ£šÆ ¥Ã¢·×­RŸqrœ9¦“]ç‡HÉ·|²Õ‹yhöê»ã‘_£dqø´Ó!¦ˆçhBÇä–É_Ÿ=ùçñªù¤¤¦"h®$…ÃE##z©ÅlÚøžê0åVáGBxaøŠÀ©–ÚVµ{–Œœó“I¤3¸ÓüGc"mûCÇ&r<Î1íšQ¸7!“~qóg=«ƒÝ]ŽÐ?àgúT8ÛQ©t#¼?é TÙaž:Õ‹Ãû÷ÍTbú.:S¬yé–Ç^ÔÂy=ÏzV8Sž;Óßô¦!¤ó×?ÒšO­)4ÓïùPž• ÿsŽÕ#5Bç4 +æškpi3VH¹£zZ¤ÄÖ~{èä«  _‚=[Ø¿Tòú2zó¥¬ÎuCXßÇK˜·±¬>Ø»ÏZ¸¼ß\éóÕ¹p5ÛÍõuû|­vus,ÉÊý/)GIË¢D™:Ÿ8ôðR*– ¢Jø(™t³~§ãôgtÍ[)tåWYÜåÛÙ³*.bi²×³ÒØZœýy\}ú½¾f›:Có8_©á»vþ~©fYó?WT±`D¾ R\kê>_F^u®iiWSªãÒÆjåU“(Âõ=4ØËåîÎÇ«G·ÎÓ˜¼­2=Þ3—ªå´Š—;Yù«ÎªP($@±¤»õôíÉ6^Ʀ¬ÊY¡-mKxªª;—±¿&/¥K=4;x4¦-ꟷÈÔòè±Rӳ垿2èdЉ!bX”Íž}>—ãôct‡!B—_žê˜¹ôz ätÅÌéVcpú¨/uðè³k1Ó+÷ùëgŠ´!Ê=žP=©)ä…ˆölÍlóéô?/§šëÉQeE+ŠmMdó¶G©ÐøÅóý^w:ø¯ÌêHÑ>ß53¬áÐ 8µ ”à ¢B–WÒAåMsý2¥­e$u˜0¤­Šúš¸^¯î¾[]¼l²2M.Ò‚uœzL¥” àQDÊ0€@¢Ô ¾˜ÄÒÌ—¥ÃÐ,¹- Ë™·âÇ?£—ßçÓß*vkž».©¾[𨙗4µ5¹îÌ¡ A”V A@¥•7U\‰^’,ƒÊqØÇ Ïê'Óòìk•Bî5c:Øôs¥¬nñég:Z¬e„µŠòV¥R…ÒÑH±TŠ„‚UÒ¿5’ÈíFØŒw×מž›^^ÖóNY÷ùùΜÁmæÞŵ{5’É]*R–² å‚bWÑâV"RJÍjîkï.¸*•’¦t=±ÌuÂÀNn„t|zïòÕìÛÔLšÌÚ±øòD°E@³+xÇÞ+ص sµ êý˜:ÎÜtÜõê¹ê­k\î󛸔p ­¬Ý¼Þ—–¶ùëS–¬çYÖei›ª¥ˆX»1zc¯:º‰S.fñ½ì"¬ÐÍ]}{ͽœéff§èÊ¥xÅ隈V¹ytõÒã]š¼ë¥Ì2tâ»g›ïÎÊéH@$V RìðÉlFÆoK‹µ›jUuQ–dtÓ%ÊÖp:b†ò‚Oz^‹žº^zé±w8ôùç|q}¹¢’)‹´d:PÞDÇ¥€†0û_.´"WÐ1vhâœÿLáôÂ.V@¢[¹t\÷w7™é” EÒÀ€H¦~ó‘^&R#îœ{S ‰_+ ÆJ³žéϘíŒÝf"̵lïøôæ5Š—‚*ÔU=áuâñ$äK÷ž=³Ææ’"ÇçG)£‹2º0ºg—Þr5¬"É^ûŽùeb¨@…"õR$¯¼«SÑâExñ'Þ¼þŒät¶±ijg:«ˆ·›©ÉõçËvÂlî¸tæ·EZ"lP… Ô@jç·qE °—ÿÄ-! "1#24$05AB3ÿÚÙ³fÍ›9‰Ëã+ùfü_>Tò‹æÔÅ1X)‰‘–„ö/ôèצ͛6lÙ³c‘³äM—|Aþ÷I¿¹Èr'ÿ+ÈSÉõÔ½Þbë ­X‡Ö®g½^{Õç¼äñ{¶Adáy£zX¾©,…¿]›9G!È„'hñ®%"&l9åþŸÈ>O’UÏÌÔHùFLõv¤q¹š™©œfjgœ,8Xp°á`¹'|¾ì)iSoÇ/G2´îšÄƨì`>ñ0Xðp¤½·öÌ6{NíXg´ãׯg öŠÏfƒ=–±ôX3Ù">Š™ì±=žöˆžØ{{< gb;6»N[ãù|áþ)Ìkû4ÊÇ&äÉu ÆåÔÛ=ÎCÉŸ6Í{„Å}‡~ÓÈ´òm<«O.ÑæÚr˜³­ifÚy×Í·^UÅFêߺÜ{¥Çº[¯u¸÷kPº½¬÷KEÕ-Üz»œ²×q _Ä_ÆÉL¦\±ÆYÙÛž:ñ*˜ÃÊ©MG•QçTyÕmG›QæÔ<ÊZWRBê“RÚÙ"»§+]‹’½1Ü‘“;éÔ£àåm3*_½ŠTþ6JF$·W¤ÖÎ¥ºªòl<«*Ã˰ò¬<».Ã˰ò¦ys<¹ÊŸ*ª[il;2•𕸖:ŠÒ­nQD`8Dšø•Œr“­Û"“ÒÝØò)—‘²R0$oÒGWþ³cfÍ›6lÙ³fʾm­N1JÚäîrVË9Úå6ç ½ÊOq›K”ˆÌçœNp+ÔíåÊTH¦bfÉ3_¸ýÕ¿¨ÆlÙ³fÍ›6lÆû²#gU)ºh»¹˜“ŸiQ\á㵉So ¤Þ5hìEÆ­¥D±àÔcTŠl!aÌ“0_ï¿FuOé1±¿M›6lÙ¿L?í-™¹N™Q›)Ù›®ç2aT»–]ÅBþn»9Ùt+ŠY¬Æ±]f_n1”ÿb·óS239–X¢`ä©fKÒGQùÃcÓ³~¸܉”ª)³7ûÍŽ›„¹9ÌK‹”}Éšû»råÊe¯XÕ¿˜2¦FG3„ÙÓ¡(æËÑ™ßÔcÿWMþî‘›;'d,Ïqòßa¨òÝsPÔ£ÿ¥>¹Ê?úV8®|–SÖ-˜2¶&r1þ2%ù—ýf?õtżï“7*UY‰—+-v(ºr¥r…«±Ôiî–ÕÙT¿¶6×Û²q3ž««óó&l‘‹%ùc2éFô×¢:_÷¶Œ›jŒ©rp?‚vñTä*ñç“Þ‘£-*T¦£gÌUN6Ê1:„¾êˆ2 LÙ#zrüŒ±nÓG‰Äâp;lìLñì:UyÆ^/zÌ|^Ì¢Ú¤ŸpNÊËr¥5­S­Ë"s ö*Ù§¬éÿ‘YA•ãÝ5Ø´‘&ohÖÈÐ{V9í¸ç·P ”x•5gbj'm´p8h²µ¦þDô§wÝ lz,·æùÚ'wÍv}Ù7J6ÁËYOy]Î Ù¸,>»—‚¥ú“¨Í¯Ô™ÅŒ“+{¤ŽË™åL–l“óf,™Éw¦;¦wfw$w$9³‘±¿FÎ?nW8˜NÎù/šÆVUËeÔÎF©Ù\§bÚx½W¾£v5ù·í½zx—³áÉ(ÍCÿ–þŒ«¥T¥—|#Oß›6o×fÍ’—Ä$¸ÉÄåwkõ"¡dR*£Ë¨ó++Tôœ\Ω‘Ôò1×+­ŸÝÿxòí]fœò­•%9Š.œäãæ&<Ä4ó dÆDú½k®ÇO¬Z>¯y.³húÍãëêÙ#ê™'¸ä?$ó2ò/’ýÃŒÎÔŽËtm “)AA@÷ÙKôÇûh·ÒÒéM±tö¡h]-–v–NÖǃµ³àílø;[>ÒÙýNÚÏêvöSèÚýGÓÙÔ»ÒÙ_ÊaÂß|”<~qEªm¦Îæß“¹·ä]U¿'woÉÝÚòwv¼Ý¯'ygÉÞYòwÖ|õŸ'gÉkª·u套Ù_#ÜŒ#ÙÆÇ¼¾1èXüâý 'D’I$’I$’f3™ŒÆc1˜Ìf(¸èr«õmgÄ’I>êNJÐÉÂI$’I$’IÓoÒÊ•¡ïδ¹/z[KCÜ\áR‘Rä„ù"‚0‚Aµ÷"ûû0zЏÜ\áUP*¤u*y>­+ãDê±ëqGâ=+ÅÎ>E|¿]*ÿ!Ôp=¥<è|n.p©IM0>vúOÈêxCÙ{”ó…N jÎrΧà{/nJyÁ´&´,V†t¿‹:ŽGì`†C!”¯\2*cB'èNŽŸð:ŽqˆD"=ÅŸÅUQQ™™™™“Šö-_YP’\—]N¯»FW°Ù>Öq³B¤ºæ¶ñNþŒLU‰$œ3ŒÆc1œÌÌìÎÌÌÌÉd³× tߪ•?o«=¥D“†S)”ÊAAA©ë“331ëL’wZX‡³I›iÔ7°ôe8ÕI™¡9ÒÙ;Òffc2Ք˰ª3y$n}Üv$Íí^Ó¤ï'Làé"=ÒÓê¶ tû_ÿÄ& 1!0Q@A"B2ÿÚ?õ®¯øße–Ye–Ye—ø1&¼ëþvJOô\‹‘”Œ¤e#)K锾™Kérú[ú[2—Ò2½w±pu¿­[;r;l})©|;2øveðìÏáٟó?‡bÄÏãÌŸJPVΞcðddËfL¶[2fLÉ™³6fÌŒÌÌŒŒÌÎá™™Ü;ˆî#4wÜGq£®ÿ¡ÓÝ%ei~,ó¤HŘ£‘Š1F(¤R1F(Á3¶ŽÚ;hí£¶ŽÚ;hÁ馅e[Ÿ:â``(Ñäòy<”Ê(£ÉçmiEzýî{"R(¤QHÅŒQEVÕ/>¿õº[#Ϲ¦5­²´|çt¶GŸcãH:‘mpe2Ë2fL²ËfL²OÁw=‘çØøÒ1²Q¡A˃´ßïF'£шŸ9ÜבçÙ.4WúþÈyT(׫©ÁwK‘çÙ.4„¨”¬ãàYhýBžÅϲ|iÙ(¤¶6'c,NǯP‡©{'Ɖ7ÀÓ\ì|‰QC^JÓȉòCð¬´Z-j´Œ¨”¯gƒ[$CSfLÊFLÉ–ËeîOÝ#¦¬HÅ£ºŠ1EŠEzÐôCó«Ö¬Š¥àZåº;\îH¡úoÑçe3’Bñd²T.8‘«LJ(­0fÁ´vÑ„L"bŒQH¤x<‹,nöÑ‚cÓ©ýÕ&TŒº‹Áh¡£ƒ#334Y‘‘fE–Ye–^ê²·R1Gm u%¥™—¶ö¨•±É¶‘z従1NÙŽ–̋ڢ%¹h¼-¨ìÛÉÁo[þ‰ÞúGÇ +KlQ¯EV´8£¶ŽÙÛw)'èpLq0Rô¯ÂºþúLíüõ/ıLM=—í_‰Z)³+÷¯cõVŠBŸÒïÛ~ŸÿÄA1!2‘ "3AQaq0’#BRbr¡á@±Ñ4Cc‚ÁPSs¢²ÂÿÚ?îÞ˜x´WH—þDz*.WWL~¡d6+ V¬-^È^ÊörS,C%è¶‹jVÕËhTÁðB7ëTZ[7eYAœB¤¡þ ¤¤U+¬˜·rÂV’’rÂrXNK ²X’Àì–d°;%Ù,Élßå[7ùVÉþU{Þ¢¡¢Ù• WÛ=`§ÿ"±ýVÐæ¡ÛÖÝÞe·vcô[wy‚Æ3W=¿E¾+hÜ‚º™¹µn_u´nKhܖѹ,mÉb WR…µjÚ1cbÄÌÔèó^Ç›ì½7ÙJÍöWµžû)@Ä"†#Æ#ª£4Y|P;®Þ„ï Ôjû¬Kı,Kı¨ÚX–45–5¹Ý•'•{~U*þ'•_lI_ÄñaSvJnɼ8µÞóJp܆‹º×}y¡ê¤¶EY p Û^Ú"Þ·ä¦rS9)œ–#’Ú9m¢* Â6„.ÏI½kšd8§iRøhÛ¯X–%‰bX¾ŠE555:„§PCX:<Fôuš „”å58+ŒV!šBx"*`%?æ¨hRêïó µÈšÕ|OT˜Qtb‰[Öõ3’Gò[ò_eö@ÊȲ,îCKÃB“ü¢ù‚’ 2QŒ$¦|¨tVdµ]­ÁGu¨ ØÁ@>à'B § 7zÀ$©Lcr7¦´FøþZ½;ʘUë!âµ báÍCÿAx(´ÀÔQ_Qi‚½Ru‚TDñÐ¥ù{Ê™IEŠ.)¤¹«ƒ#ÉnÉ>Ýþê \ªt[®ŠÂ#ðŠŽ¨&3pŠ AnÓ£ù´)~SÞPõ[•™(Â~¨&;Õômð *Ëb@æ¯FpFÅD‰I_8(˜—FAjðTC®›zèR|§¼¡ëýªƒ¯="µ.ðOëX‚!Á•ÈÛ©É¥Ô€ÂÊ-ó ŒrîÝÓ¹’ÀrX’£6ÕÅ9nÍBåÉBU\nà¡}Ëõ ×A¡Ñ<ŽM['ùkÞ¶-[ä¶,Él›’Ù7%¹,#%„d¤¥\c â¿MÚ5AB15¡Ð¹”åtÊš… ØÞ}›cœ"¢}%Ë÷ŠO5l?ªåº¨e¿%5‰b+Íb9¬EOIÆ&ýÊ-Œ¯4xÂÙóDºî\Q‚%Ù(Â7ArOô¯K잇Ûtßò…FßD¢ìèØ_7s:PÒyUëP‚™ÓE¶XOOgF!üКùDFä˜X‚Ä0±…‰MLä·ä™é^’ÎÒžFŠßörí)Þ]ÀnšØòÛA¦0DþÑf*-F7(bè¤V°, Y‘E½…¥užªêólÃz©&­ÊabXÖЭ¡[G,NÍo®Kõ[–%nA¨ÒRÒšGî59ËšíD\w,0l]¶óÁ:“³$^ÐEÚëu^áæ¿EŒæ®‚½j»4Nþ!D)))))Wz»CVkXÞ᡾«ÑƒÊº*MÉ;F-0Zíæ.+—|W+àB÷U׫ÛzÔvkY°îoZªó_¼¦Ña°©Ïp³ÂÒÒ¸‚9îÆ…ô–gd+4¬,< ¤ˆQÊû´µ@à½c`}æ!Ù¼8äU—ÀõS²y®!aäµ ®«Y¤h^ ÛÕçC’ࣹQp´"©)»Q`È «›â¹õ\U½!Ô§p‰°d» ;OdcjÅ窷J  -›ùïGZƒÅ‚¨oWˆ­CàV³a[n±‘á¡ ]£x>õF‰Ç|´Ãi¼hÌT^ËCˆZ¯àåxˆR²y"àádq¹@^Uæ¾ ê¹Õ(õªuL©¯WéŒù\Býà»æ¯[èÔž*ÿðëÿÜ«ßUªèü.Pv©æ¸­]B¥hrWè&UêÓZx…®(È­­‡p¤M7ø„îÒŠ.r×uÛš$W.*ÿÀG‹Wúƒê  ±î¹jžÌû®’¾«ÛâXmrPpªé§=Ó7èzºBÞ[‘m-ç{j(nä„. åwàx!k cˆšºƒš”:×z„•ª#ÀÉê;#á–ƒE+û6otÒ¬ÁsB3ü%ËY¾;ÔãX.iùÛ¹E¤R·ê gÁÓ_ª•ƒÉ\-ŽKb¢¿¿yj²Žœ*wÞ*¤²îz×i¢>ódµÚ)›ÄMzªK_ Õ—‹%Á^Ðy¯VcȨ=¥¥òq­£‰ÒÿÄ'!1AQaq‘¡±ÑáðñÁ ÿÚ?!é=&aë3¸øO¡)ýñÔ«âòSÓ~eý ’§A._C¥DècN‡þ†^‘— Ã32_7?µ\ÕBÖFë8CÞwË6O?Aè á½ô‰q¯Ø‹Çú“r<Êÿ¬ŠûÒŒ¾–^èG¦µ(IN²ÙŠ(æ’>±©¶høÉ|ÞÑ?i&nð±#y“ æ õÐþRQÌgôpüæ[÷ðüÞaÏ%}rÄÌñ7L,KØtÜŠ³»HÑ]ÇïÌ[që›úí4a×÷Sø¯´dæk³³ö'¨•Bò†%Ýèÿ±]é?Nb­ÏR±?¾`[œÿs‚Þ¸6}:°Þy¸÷%3E”Ãû ¯î¾Ð#÷í?¢Šv÷ ½Šò£¾¿"U×`= Ë`u{ê¨ð~±²k9J»×5 y.‹É(äj-B1|†^ðdäÍeÓÌÇë£ /þvÖt tåmjòüåçq“w „ÚUçgö0AZyPt N×»74¸×0ȰQæ1w†‘ó¯èð”…Öxù:(Ã]:ÂJæÍ1)¦Äâ?œ ×ß­§˜ÿ,îÿÅŽ1M›AÜz·,böHEf­+ 8% ƒ8HÊD3ïÔöe†€nÀ\‚9Ê—4-ã¿~%­6@÷3°Mrï@ø‹I‰1Jtª+ë,8yHÕ}#ÌöðþB!øö úDì}‰Ø{NÛÚv^Óº{EV6”˜ˆ® Ä®WÜ*˜`åhBö€Êš—èh/M¦„X7!y¨ ðˆp5å•BjUF¨¨·\lTµ†WIëaó+®‰q+Òõ~±…è^Ñê,0ÿÞGGù†N8Ñ”Ù-H Nk¬-—Ù.©ÞÔÈ~&Z Õüïû£Ü÷E ƒYï„ÈLÇÖyú"‹Ë·a‡þêrúÓŠŽF‹„KÖq#¦¿ÉÝ»_ï†+CFð„i¼¯=âþe kVTÕ£–_¯êåT@¾.9^Óf-4EX¦ÑlÂϺX΄ÓèÓ¡Ý‘f,s.Çú"ê ú"åÚÿf1³ÞD—i|KÏ1H€M˜Q|Ìv»ÿS5b–Ô•ñIl£Û í_5aì .~4ŽÑ­`Û˜YÖfVoôê>C¦cµlz¢Ë)úNcŠ(±z_RàÏ×s21æx^vƪšƒi˜\SGÖXjá«*Z‡àˆ. Åñã/ºic[}寴ÇHöžr>]IÊtHm_ÌÐÃìf¦/NCÝ]ôz™xÐMG´¹!µ, P6˜ð¨œ4#­s¥ó{Bžt„GF†¿t•Ñ[9ÕJ³Ø˜ÅX2ŠSl4G>'ïÒ zhªzùbëá‹!ÿ&òŸ^“6?µuõ®¤'ü`r_¸Ã¶nY¦ˆ©}¬Þѱ á~³rÂíÄ*mÓî–¶”YÞ^­awÎe ȶÛyšx:® •ùÌ ÊÂÁ^ð¹ºdß3Õ×Ò(ÀëGIÛàt—Møš£'CШÅA>Sê‡5=ãzˆ>»Ò`vd×_Iêä[Zýcà=ž"ìñQ“¢ÇÌÎf¦Ä£6î`1’xMÿ}¢Ë3uc Ðx™ÃÑ@åC˜Jt±á/ѾM=Ô?%ƒQZUc ·|ù`ø¬DGt±QWwsO”Rõ5üKš®Û·¸* s£¹ÆOc²5ÊHD8!vþàp‘ë*zy¹W¨>]Vk’âÁ:3#-ü9²0ûg+Ž™(é¡ÛûNÃÚWƒÚ mG“‰r‹ V“ Ñì¿TʾMãº.Ý¢ß7 ÔoM`b[àjˆ”â qÙ5@!=š|E€2K]TË<”ýŽeí|5WÂûJe·;´Ÿ—*zÏÚGœö™q^Â?Η¡žIÞD}î–gŸÈNKÞ*=ÌT§f!¸[¾¾÷g5ÂöÁ¬ñP´S þܲ¸Nf¯N‘#S>ÒÂÑØT:÷@â¿SΞbëkU¹È{(RThÇ)´²[„í1'`F\¹dGT;#ˆÂÒÇÚ7K©Ñf&gÑrã 0nQ_½ÒR<«êxš,¼ßhÙpJÎ ƒüˆ’_Ù ÚBˆA×ʹ@jµºmë*7Mʲ´« ¨FÅwûeªè’Þñ|½ãcxÒöšn°è‘ÄÙb0ºÝÁ7îƒÒWðü}"¾ÔGH¶ÿh´F Ð÷™}Ä·tW™~ÒèIJñê?¿Ql&FÀ®5n±èÍ}srd8¨;b&SD¶ECEþþÔ*ëŒ7f.’±FiЦKÌrÊÆžÑLƒ§?xPT<‡Ö3)û²$j ´¦œø˜´ÞÈ*•œúFZ™ÌùÜè]R«l£gAã+Ä&èL1‰+ x5È@m‡‘Ý•wÝp~frÇ$¬ùx‚l^`@¨Ä,XÅ–âû1Ñ`ïÅ(bÝßä^·ü›Mÿ?ô/KþEî"ÿx_ï±æÈÉÿ’^?ÔHñ>àíTG©ýÅ7 j)K†%ˆ¬L}˜‹ìì1­D""&}*|":QøEê9ùßÁØ5wƒÅEÆÍ‘”O˜LB‚ee-ñÂ3x„(ŒFëãLzÞ:*6Ä=@£Å¼EVVVQe–_²ÄÅ‹"‹e–'/_mÛ_ס‡ÁtñO£ð?ÙP¦)S4h׳F˜²ÒK_¤»C~Ñÿ|‡G…‡ih¢²Š(¢‘EPš‰aècÐ\/„¶wÅÿÃ˼³¯ê.ІÒì†>òÛÅ…Be(š7ÐØ ÑâòõÍÅåŒ=4)Llö-!"}b‚¾D‹H‚ê^Ö+qšpìN%ÅKÇ£ /h=ÒÓ°Ø©ìOcÃá X¶5­ ûºaØñFà݇®w‡Vb4­!„×ö Bbì…<â²Èÿ#©Ââ#IxwåÔAýF5c¡¦!}Ç‘hªDQk?†ˆÉewãiqJ,S£h‡6ÇÛTBCÒ•$B5ðÑøÀñr±àè<2•LNƒ« 8Ç:oˆ[¢Œ¦<‰'³M ô}2œ0úËÆÈÊ~è>GÈt¹‹.,c#£u¡;¡¦,ëû†ª1ŸEY\>GÈŸD^ˆˆˆˆL.Z'£K¼&ž_xÕCTý™ Äѳ¼JŤÄX£T~£Mwμ‹Ð])E8% ÞoÑJR•‰°/lï ¯8hú/Ðbìô1:q®#®/Eã×,5cMwÍ2è·ÙxL1aó¥.Hk+)&zFË1‹Û“ÃׯѣC7à¯È¼2à›>ŽÄ2ÅàØ²½³¾‡ÎOº%KÙŸÎÂûÃF3ÀÓ]ðó¬uÉ¢hK¸Ë˜ÏÿÄ%!1A Qa0q‘¡@±ðÿÚ?x¥Cw)lX¹<·ý±2¡²òxÓƒÃÌÄÂb{8ra¸Ÿ±¤È4\Ò Ÿ¸iìúŸCé„òú«þHvìGb^#ËirO³‘,<£áÁí2Ö¤|ëëÂ$ó¨ùŸ1МHNÑ.! XÙí!z‘ð.‹ôH룮Š(±¹úQ~ÔGÀøbað?n%·ÿBpðó -J‰™¶ŽOüŠ÷:¿Ñºjb‚04#Â(ÄýGÈ^3¡nDCÛû=^4,1ŒkÖ&H%\1Õß%ûăl EOA¿A·èxD%F›Y!à»°Æü9¯®3äN)ñˆjA#X6rÄè–…ÊTË 1là”pé±mßÒG%øiâÖ lFø)r„ªG (‘”Fk±¶-xp_Žž ä³QÔ[Û*ì¿,¶í/Ø”Ü)¶ËéŽ{<Í g‡„ðž °ö)D¥í!ÂH>#!Íì‰#D2ì‰*;KC»³R› a+ÀåYg`ÈO.L<©¬Pãö7Ù¦EÁtUÙ.=3CqlµHT‡W–p Âk/øBÆ%"7Vì4WBM3¡ªm Q£ŒŸéÁøpX¾/:>•–V!¶6%ð9äà`Ó¢½•NÂưøb㜖ð“%!3Ï…t°­Š)°à‘m i,B]Bi‹ O\XÄRŠb¢¡§³è?qöupÕ„rD-ì© 0‘:Ç¢•!´TsÀ†~ü$ŇJ>çÐúØ}¡^Êßex¥SŒFóc –‡Û&m‰cv?†_-aªI8$~£àB|z"&9ÑŠ®ƒ.ÆM¡¡4¹2ÁŽÛdÖÈ?‚pÏ öhÚ)iaJ\..6FGèž„ddx#h6¢X²®6„íÑ¿²}ŸO)kæ|O˜“Ö BLh”‚E¤‡Ì:£IVt¡Wnõßòp œ±ÈÕí•Co Ø¡?e4Å<PÜ¢Ë,¢Š(¥bM’aPh£Z Ðöñò=hsŸƒ¼ºc—{Ä6XµÀ½„¬¥)p¥(“c;" ÆN‰Ñó‘í Æ$ìçÉó§-ÑMT6D‚P‚¬Ñlg,BÌÄP|¢ ³xA§ЪD|°ÓBeµ€žmyZ÷É¢±ý-Œl†ËEò'šlt!]Ò5éÏþ>ŠÐÕȼÃIéœhõÊþ £n§Bãð&É™Iø8ø1ýÊ~iñ‰ãÊ¡¢äA~^PžcÑ¡è|å‰ÎÎå´|aŒ_à&ÆUÙ®˜ÖUèpb´ % b˜cÂðDϧ…ëÉd‘!a§ÆGëCú|CÂNã Ây*(‰±U‹1OÿÄ&!1AQaq‘¡±ÁÑðáñÿÚ?hâe*ÔdO0ï¼éb_¸9âd@>Ä@8GïH k¢c¹O$èâïÐË…8™LúÂ!åÔÆìJ†s-±Ïq"öÍFÜõƳ»Å™ÊÙ—rèo9™Ì{郬¾ƒžIpêŸÞЮm¬éz>*da˜.çdGßþKy¨†/zÆñŽÐ=¢Z0zUðþáX^Ÿ´JüD¯¼8w¹` ñþ埫ƒÁç·èޱ{â+«Å‰‚×^&…=1ûÃÈum/Õ…·i‚DGÄMïég&áQzâuÙÝŒN£M×K‚K‡t™y0NÌÉN¡‰X ÷pù—•è0!…<£t·Ý2?€²™Ó\bm©ó˜z:Ú/‘. ”zØCDnÖ‡-ÿdzúéþoˆ“ù=¥¥ÿGˆnsøâ¿Ž‘†ß蠟šÿKûü~¸˜º?Ž!C§ îÊëÕÄ9IJç[ï/$_ïÐ-Êö!Ge;\}Ö9R}Ô×øÙ44˜QH±2_æÌXæ9[4ûñi§iÍW÷.ðìªÁáÙ7¿Ô•µœã÷ÄÃÑj@ £;"Ýá H¬ý"|BøHÐ'ÄUluo¶ ÎpüÁI\Ñ„2–Ø9žÂÌÕ´y·õ?ŒüAxo!”y”Ä$ÈÎò|€LÒ=Ò4B™Ö1ÇœiXŽ(‰+ »”Þe±¨-yG´xA½Å"Àh¹Ya¡Ùͬ@Ò²‰‘ 9ĽÈ¸`´ã)gy1AC ßkø¬; }ÏH›E²˜‰¬½7ýš;fKÅþâ\ÿ½g0¿½f—×÷+äú°&Þèµ`+–¢’ Q:â*4¬m¯iUÂß[ž_Ì8Ü6³ôq†® ’ù§ä8Ÿ¼w £ï¦f h‹­Ãó?ï`©G)ÝcœDZÇM‡”6×[ð0 rÕXöÎ}#ÆêÁ–ºÿ\uVwÜTGÚ#mÒ ðÃÄs?`F TN²ÝúÁà;å Gz9ÌAXúqNçx–ÇÏûû?Ü‹×7ÔK…Õ¹°1ßïà\ui¨ñ¯‹>©gÔþ š&êL°-JÁ~•T¡{ ·ÔY{cy`\·Š¥²Y¨M”®."¨o²~HðE…gP:®ÛWc1s*‡9(j”ûbRqCƳô#îMù¦:3e7‡ö†ßÓÍg}ŸÁô(ÅÎ[ÙDÀp¼T.MçI>Œ•…¬ÚUGÕ…]®/ØùaLÐWYžEÊ(ó/Lð:ûÒ€Ys€¬¾cpX¿‚ Œ˜óµ™Ü5Û³ÕŸïPºþÞ%þ^’ÑG nºô£Þ^ÛŽ«r·èÌôܼ~ ëÕÌóAöý’…¦d…lz¯šüľҥ¾YjÂQaÝ2Üè0iç`Àõ,ûJñPSy¬‹ãæ5 Âï¡ »g $t-Î#”‹c}Y‡Ø«PÙiü÷ˆÕÞ®1îø`c«!M›†”u½å}%ŠA£É»û¾%Å¥L‰wÒæ/CJTé¾ Ö„j«®“™Ð… íSræåT`7ë-aVæâP?õñ38§éfs¨$öʈ‰Sóô]fÐɆÂ¥»¼!FCšýê< 1bR¨{îøŠ’ý¾Ù~º+Wê”jÐ+ EK«UÕÕWÝ—>%µ·±[3UNk¼§7” 7mÝö•lòQÓÜ–úì«£pÓUr;nó›8Ô ÷(5g¼a'ì(h^~>ñ,( •vMzʉR\©˜YjTn\$K2~%hÓ,Nì’ôvLQq.}–DX´n^ȵf*NÌq(jeÓ¶(¬,s-_9gˆO7΢è eëj7Ö^VÒ6 Î3X8÷ #*ÀÙ£o_” %ç”,<-Ù—¼½($WbŸh‚jÏÑ(©`fB;Ë,ÂÁÏRüÅ›Rï3„éOì\±fè«Y>…©qDÊÛ/7Ý™e½J?™P³/Ñ‚»‚mcÁ$„‰´·ÍRbó—JïWë*BzίxQr‚æîŸåÂ쎀ÄõæêV¡¶ýSôË-6“6ºªÇ¹«Ö8ów¹`B—¿WtQºR‡ß¼@©.ü±ïï0†‘'Ìô¨z¥)Ä®³[n%{Sîqx—3­ßÇ»šL§]È‘¤NßL3a-Õé0F;Thà…M±&ùí_2À» +˜ZÛU Ú Œ,¥ù½#p²aP+àþbôåyÀxÊD”[A´îåפ°äoµO1¬»±Òª¿iu‚…we)ðñs8ÅJ®÷˜ÑfÆE ›ë!8D¦¨¹`t¨- DPºCåëÍ@5A‚çh€‰|ACЩÒú¸öAV Rvã†1j°è^Ä´Ø"Ø"ÿ2ûÊUµ‰{qëgÌ}QU¦;ïÚzŸögÙ^ch`o0¨ r#MçîKY/Xn}èÓcå‰Cc¾à­–ðÛ2µvŽ^ñ¼¢ Kôcâ—-è¡é „¦J³í7e¸®—é/(À`ªï8SŸV@ª ø”d0aFõÓŽen§fk}Ç´±<.°Fª×½Mù÷Tuƒ›w!ì|Fm lλâUØ(¤:RF·ý|ˆ;*9Rý+>‘ÊùsXó+‘«£ýÀôø¸>žÀÞ gGÑ.ÐÔtÀ¦©öŠ”¤2Uü¨ƒb{²þZÅ{Mü JÈáöeýÁ=˜å®:c*D¦Û»´˜®Hž­RObôt  hªRÝ´~åˆQË8eÕ«Fc¤](˜…»rÒUfú°¡‰Â4M,Å™ò¬ Q`-™Íqpø™1œyI5‰´D§«ÞåËêùÄÅ5ÏÍ%æçA–êÕ^ .Åæ%u½J@Rõà°ˆªíqÎXó3šJæ×Ë%míd¾%[^ÒñAj ²ÏX›L×WÌ·v\(µh>j#{õ–pöŽT;²ÎßxÚ¯¡µhêÅa´gpù¼"‚ËrêÙiT×N=f“Þ€B]—BÛøŒ¡è€*—ÆŠáÞjù௅+W­×Øédëà0üÍßM×ѪÍP¸Â©(¤vcŽ˜Ùº­£§=ï1«P“Ò-Ï‚.93Ák¸»RlþC~²è}½>çê(¥zoÜbRÛ83/ªÌ² ÁA¬ncj—F?ÔVÑØS9 Ž)—qk­!j\ŠÆg qì™_޾‘:¤?'-™ãQ”œ”v:ä”Àõò°£–ŒFšwžkO©à'Þ´üLÀuyž1¨Áâ-:²¥½±/¤qÀôÖ ªÕö¥IE–c•ïi•”@ï¼y×0Kâà¬Ê&¡ÏBcIáùzEÀ¡–Kï4Ž]‘ÓñPyZ /[ÔÁD EI°¸ nPŸu¬ Bð¡ÜSMY# 1†É|SMË‘^Ñ^È´—Œž!"Áô}âôWm¯XÞ"Ó9÷G²YU¤[ÎΈ¹”A:(š‚ØðSÓG£Òá(c«‡PîÈC¶Ù:W0’k^½f†~òüÇ '%$p9iˆ$UD0òµòKKN+CÖ;0ù±ó¸e¨d3ÖaV@[‡o´KjîóÖ¾Ó<˦—D°Uæï*Åb,p€ºñ Ft _qfûÞc‡ÝTd»X>ƒ·Ciݳæ?jÜ)¢6¶Ddb„me»&,c!ìbU{lGÃxlÄ|Eø$Ñßâ ÎåúŒ8®+k¾Òü@®S'5}_A±¾U # • s’9¤¯rrLDÐSê_y~‚¡ÕgAv´Ù¢nÂàc=ìÂÊæ<* _ir*_²gåÌF×—Þ]  xÿ%K|§O÷Únã 5ÜtfÚ‡{¯ÜZñ9Œ¯ ÌTXâ0ÒTAÜc˜GȆ+Ã#ÍÏ(a(»œÇ Ö…x˜»kµ^/ñN€7nÒ‚9=rKa=‡ÜüË_ýM?—M CY‹ Ó‰¤ƒ™u -t#]K°’V½¹+´*O­NWîRÌ2‚é’ø—rVõã~?2ÖØ›Ð¦k÷)L2Gœ·ó1¼—N·ùÄpye·¼V…²Žòør— ìŽq¼f…>pïë,úUÉJ_2çÿ¤( µñž}"œ8ýHÅ[Ý‘b»HĆÞ~jŽ nrl_‚ÖRJP\’‡Õ•z ¬ ´Z/ˆqe¼‡_ÍKK&Ð6 gØ”´ |…ýáˆÙ½@`Lû óÌâXŒÜT+z˜KX•ý7+Š™wp5 ”3¢ÃÁm<±ê1 ÌW¼få"_KíîÆÐÅxÏà ôlCü?yÄŽK÷ó‚ º}¢‹>j˽i”l8Î. a G¼60¸Œté÷•®yˆKh×Gx¥âeo‚d”E,ËÖ*Ñ—¤<óèé-çÒqqõniàˆÚ9åëŒAžqôJö[´ìùŠ]ª4ÙÃ(u~%(î+QàÒ¶2—Ã+Í0 Ú7j¸ Mº‘¼:M(B-H6_iT‡¥÷+­D`)ƒÁÖ·ìÔµvõØ7=C¿ ÷Ÿ8€wìüŸ2¡—¹<´"•ÎF­GâÜT>}'ÅýåëÌ,ÿ²Pc]¥†°a„õ±-ig Ö«é÷”t­ý=èsÄBñæ7g ®uýM²ŽÏ=¥„)”ÄDI˜ z“ÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/dataset/images/screwdrivers.jpg000066400000000000000000000457451510465702400271010ustar00rootroot00000000000000ÿØÿàJFIFHHÿá4ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:18:21 Ðè Ànt¸ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?äÆ1Ó4£J1G5Ìj/JNM.3ÒŒ`1‡õÍJiŒ1@š¯)ùªf¨­0!a“MéR7Zct¦"6ëQµ9ŽiMa¨ØsRSóTDÓ §M0Ð!½i¦œi´ÀiàÔlqOcQ·J±Ï\Ô&¦z‡½D‘õ«QõªÑŽjÌ}iÝöõ¤§Rb¹Íu šæ¨Üê)Aj`[>µY®¢ ·vO ¬‹½} ¼‰ùÈÀ¬˜nîÈb¨­ —Ú"Mô:à Œ¨5±²œƒéTloîiZÛ¸¿Öh$…Ve?}xÈúV’Tœ}Û¦Bu×c4Q·¥L£lV&„£5!¨Ú¨ž)¬iI¦Lš…<šaäP!‡¥6žÂ™L0¨ÛS0¨Ÿ¥Wj‹½LÝ**h‘ñÕ¨Ç"«EÖ­Æ9ÝñGjP(¬ ™^á±WªÎÆFæ»KŸõMô®Tÿ\ÕQÜ™‰z½r¤S*¬ø¾õkEh~ÏæçÅoëc'm._³lÖõŠ©Y7p®+²n@®‚ÔíFÁêý+(î[ØcŽ*»qVØf«H RYé„S¥eA–8¨\jG 20f0,ša¬Ë{Ù¥o™ÿJÑü°Ç¥1OÊd’y4Ï=Én))wtúT1ȳ¿ïr±Ö˜Šþì#¾jyºF•ãÌÙ9¨\V”—#dÇk’z}jÍ»ÛÌÑJ0Êi§r% .e±M‡z°Ý*,sTd>1Í\ŒrVˆsWa\° ÚƒE!¬ J×_ê[é\&©þ´ýk½¸\ÆØô®U½&ª;“#:.¦ºÿøô®z*èåÇþý+ª—Ú1ŸA–GæÐ@p¾Ø®vËï{×In»£ü+njÇ0çÚ«ÉÞ¬6EU”õÍ!˜šÄ…bµ… 2\KµMië2æ@™àU+v¸Pw5¤Q5­4ÉÐhÉ«ÍMs:à «vž~>"–…fx†úÆGD )ûàwªp‹¿F‰R‘™$Í#ájí…“]ÉÏÜMgÂ2q]m¢Goj6‘µFI¬*K•hz8”¡!c1Ú~î‘[Y[—aÇgÄF¶ÖòóûŒVΧ†ÚÀg5‰â›¸ßPñË*ÝÆêÑ#‘7©Ì·zŒjWSQ± ½n¹uõRWí—2®:æ˜x ÒR\æ¤7ò›é\&ª1!5ÞÌ Œó\6®˜ÕGrdeB:×B¼ØÛ?éXw®‚ßæ²QþÆ+ªUäaS¡Ÿß®¢Èf¹«Oõ™Çå]¶f^ɤ=æ¹ÖæÏc,¾[Né¶FXž‚®Éœ sXzÅÇ—QÔÒºÏpÇÞ¶´|3JGXq.çÉ®®þͦшýMtÒV|Ï¡M­Ü†v’@^3‚OZÏÔ$ §JÙÎé¸5~á¶[íYý.ÜÕËÖ±rmY÷5Q³(B½ëFáH€zŠ}¼1°Û…޵¡º!{f¥²íetÈ­ã ƒ]N‡£K©QsíY–¶~aŒW¨øv;=D–êrUI.G…©>HåõéáÒDHBÝH6®;zšóv­Ÿëkz¬—,O—œF§°¬v«]…4 ‘…4 O«ö ù©Ž¹ªPŠ¿iŸ=1ÉÜ)±M!lBi+œØGåq\eä„jdt ÕØçŒ×¨/üL&=Á¦‰fUÖSQ˜üf¬E#´1Ǧj=YH¾fÆ7h·bØ5¢zÔÚÓ / ÷¯_±°ŽÇÁ³Ü°çg•yF–ÃÌ^{ק꺀_,!ˆ'zçŠ!ÔSèyüÌk’Õ§2\ WK{(ŠcØWäÉ14Dl¹¦Ûù·˜ã<×CtA‘#Æ@䊥¢ÃÃJGNYVó%wíž+i{´½L—½SУª>ØÏ^+gUð×ÙtMXfb÷p][^?:çµ6ß* ç½z½ï†¯uû¿ØZ‰o§¡‘ñÀÈZ曲;(rsþócÍVÚKWá0§ø…]Úe+Úº[ûk{[¹´¹YeØJ3ƹ2âÚòKWa´ =*!>m×ÁÆŒUZnñbO|aVÅ^Ÿ[¿Ô´È¬Œû¡NJ†ûǵs¥MÕÐU?-k›Xc€±PbºáNé³Ëu9]—R££FØu ûÔMZ¶òü›%Q,~ÛéNÕto²Û¥í¾ZÖCŽz¡ô4r¦¯sYÙ˜DRÍH™ޤdñUûL‹ˆÈþðªPŽ+JË‹¨Ž3ó è ¦“Å.x¦çšç6­rwH_Q˜JꘌW5wòên}E8’ɼi¢ 2ßH¹Yw‹Ëo3§NGë\í¯ÝãÞ»¯'Ú|á{¾ñ£ÂOâøšâ-ÔªƒW…/ˆÚÓÉYGï]Uý÷›§ÅÏýk’±S‘€I5ªÍ*¦×¨LLÉÖgÛÌç5…å²kGX´¸ªã‘T¶:{p ÓJþ¦–(˜B[b˜t«»?(ÜFUdPÊ{[wéocáõE`÷W(QÔ r—=¼…òüÎM-&Ô/ÉBزŽõî~/ñT>ð¼1ZþÔ¹cOU@É®~ÛÃqøcÁL×*?µ5<"®9U=Gåšä~'ÊÆwˆú¼GùVZJF›#–]Zæ2NíÌ{ž¹ªÛÞGfbK7Rj4\’Þ•nÖ2U_^µªŠ¾:³”R“Ñ´û}‰æ0ÁnŸJšâMÎt^µbB!‡#Š©lžµÑYòEAÔ×4¹™rÖ=Ì£­í\ _ ËpeeäxüUÑ­L÷jïSøâTŽê (ñû¥ËqÞ¹âjÎ1é€sR50 `X„V¥ƒyw¸ìàÖlCŠÒÓñö¨³Ð0¡ˆ×ÏÒØ¨Ëæ·Îl9›½sš ?nu­ÖnÕ‘x7^&j™Ðj‘5çÂ+&êmoÊŸ¡ þ5ÇÚB“°BË;»^‡§D.>kvØÏ•õußh¬[ÛÅiÔ)Îjµ¸™è¾"_x’Îm ˜ÿtý+‰€âv¯áÍy4çœ]—0´nìŸÖ ½´³‰Ñ¬¤y2>|Žh©4œ®C¨›Hé4ß›áÖºia?øúמê2ÊŠôM à/)þÿÇ–¼úþ,¶k8—#:Ö-ͼŽõ­n€œÐTZ£5QÜ™l]ððÿŠĹÿ¦Ík‡¹ù¸®ãÿò"ø›éóÄ`¼ßJ”TŽ—ÀZTw~%‚I€ò-‡Ÿ)=‚óXÚö ú¶©u&wO!|Ùí]f˜‘ðÿR¾8ß·Ù£Ïuèߦêá®ÒZ»ƒÚÆ=ÁùñD9YáìëT`³‹û3Í#çÆsN{)$Ó&ºŒ€ÐààÑ(Y¤ú‚•ïcfúÛHÔµ{»Í>ßÊÓØ*ë§úV}´×0ØÁ|»@^ „0ÏZŽÎKNßÊžP°þ­8Íz5¦“¦]øAa Ýl#¯'Ú¶SWJ= ¹]›—S‚}y ÕÖ{+.Õ‚™aÏ»‘Y—·Bîö{…ŒF$rÁEÏjK»Wµ£px5+9;»šF)!§&šÈMJ)Ô†U1œÓÖ2;UŒ t\ñU*„.qN¨˜Ó­âiæX×¹  ÅŽ}ªÊ^ªkB8#†0ˆ¸þµYåʠϽ%¨f£4ó֚±F…[ƒò¥sänšº ‘òÖßUÄ–h[Û¹Ìå«§¯Qêj½¯6{µ¦ô”=óZN6°¢ïtz%Å·Øü ­»L¾Gð5ÉhúÆ›i¨Žâ#üF8üë¿ñ()à{¼]a8ÿ­yõä&ÊÂÞÑÆ%ÿY =‰©’ÖÁ¡–Ãå'"¨Ý}ÓWÚ¨Ý}ÚC1Ø|õ¡j¹ zÕ,|ÿiØ.fŒz°­,IÓ]ÝľõÄjað¦™n82ä~uNås,kùUÿü³XZÿÏ(\V•~6eOáG $dþ æ7v­Ž«¼U‘©Ñhz‚Å$r ^ƺۻt¼„]ÚòÀdã½yŒ=¼€¯Jì´-dDÃq&6ûËU—»-™‹~ôwCnÓíj.MsóÂa£v®úöÎ7kƒOÌ@ïï\Ö¯O&ô\R”\˜á%%ts­É­="1—~ãŠÏ*rkSKá{Ðöܽ3‰ˆëНkXU¦]ÊEIafU>´D$s†šÂ¦#5Ås#bÀùM`ãWC8àÖ ƒþ5¤Hfå€ÝhÃëSس¡÷¨t®c`j{a¶UúÖÕ>³8|Lö{Ø‘¼=ó°ÆŠïÆx9ý+Êu ƒwy,Çø› z õ-FeƒÀK<€öí쩯'e=jg¸á±Y…R¹^+G ¼oÎÜ󎸪7#Š”S2•yZú|`ÞÄãp¬Ô_ÞŽ+JÆC êàd©èkH´·!ìu¶öæãW·ˆ ’ÀS|jûüMp£¤`'å[> ˆ^ëöó62§%}±Ö¹­j_µë7—‘$¬GçDÝÛ}Åk#$®i…3Ú¬”¤ÙšƒB“EKo&åéVÊÒ¢h©‰N­lg§ '¡¨%ž;™äˆ 8ÉéŠç#f…òµÑéóÃ6 Ó*ä`àrjµj̹FçN>Gž8%öýi– ¤…Oq[·ag*ª»#_ºµšÈämíC¶È÷e•Z±eX2õ¦FœÕØcÍ$6Î8úTn*|Ôn9®dlT”V$鉎=k~Eâ²§2Ö‘!—t£ËjµÄßFªº_Ë>¥^ ¶àÿ½[ËàFkãg­jPùÿ ¢õP§ò9¯3xx¯[Ú_áÃ01^rbRÇ> ÔÏ áÔÂ’#ØVmÈÅuÍh­vçéY×:J¸'‘š‚ŽLZöWo¢8÷—D¶à¸ù”p) ¶s”Õ©5±.)îzG‚¾Uû5¬Ds¹º~UÈÞÂEä¹çç<ÖŸ†´Ë«ÝB(¡b„õ>•kÄ8Ó5ImD‚]Xçµ9MÊ:“¨ËC™(zRy~Õuá"£1c­A¡We1’®ù~Õ ¦)ˆ¦ÑüÕ4?!â—g5*FOJ¢KËxvð2Þ¦ˆP³î=i!·>•~bqFˆ "J¿ t¦Ãl8­KkUãŠó¢1Luüªç•íÍ'[±®Tng:qT¤‹/[OlÇ€*³Ù+ž8¬7"E XÂ]¦ *ùOôœ{ÔW Á4AàsVQÖiÃ:V²k’Æi>kžÇj¢†­‚î†s\4PdW\nS¶»Í,Ÿ®ã8cä8Ôà⹇äaŒŒpi1Ä«Ƨñç¥1­~_º3ǽ_† ̹Á|ô©V"F1ŒsŽy©(ÊË‘”Æz÷¡¬cWÏ–õ [QÛ‚oðGçN›·’J…烌,#*š%ÂHTêqJð3üûKnµ¬m×iÚݾ”ö¶`û )#½h˜¸³a’A¯Ö©˜0px®ª[L®HÀì§µQžÀçîŠ@aN*´±â·M¿`U ›r§8¦™b<ž•zr;Sí  (V¬pÚªâH« éWb„Š™Ú­E{R,ô5§oN*(béÅhÁ1@l°‚zf¥á<ûTˆ™Ç?­L«ÕH¬M ÿdû¸#4bãæã¥_DÈàu8©‘Œcž¿J´K9ë,ÜB†BUwäjnYK\±>›Zºf· rðØgÛøZÙeVK‚ŠyþuÓEßM>fU—_‘ÝA·Fðœ3Y¨q{”‡)ÁäW8¨¤…' õÀ«ïy»Nµ°@Í99cÔý)‰2$ç ÷¨«+ȪQ´v"UW‘X Þ˜« pÙ Œ’8NGSÎÐ?¥Jªƒ†|§ãžZ_s™o™îî^øz$ælI[fó×Î7›¡^(Îz•¦\*Ø’*íXhºo|}<´Îg¶e:ñ·Ë¶˜H ôEÒ~êÁ‡+†Ü-M)4F™µaFU™¬´YÜ]\Òç?ÒjZbο@tsc¼ýæÙ¶÷r÷æwž˜ùþš.“¾W·Ï™C³¸š°"é¸ñôòÁ.T)/m Ö­®9Žzªé½œÔ¼þÝ3-cô9çO—Ç¥á”HÏ.á­—d ¡£§–†5+¬³l"Ú\¦Ò§IÁÕ:ã¦Ó€hõ¼úþ7¢ûR@°²#Hcãéá”Yóh;àÝÕÌ;ËñÚܽ)ÖD^Ìsè¦ù‘Ò.ðôéeŽ*„,´nBÜ}<²­(Gy2Ɖ· fr,Â’¾hCîYØ­ÌdÙé,ˆê-qôÇJ¡CX®ÍæÚâ.“t HÑâÊ "Ú E>¤çÓŽ/–µ6­áuÅÕC)éÉû£þnsy”Û»ážrõÛ»“CéÊk‡Ù£ZiÕj0Ë*ñõEIràuµA,õΪ^ó¬óH_Ò &jG¡Ãî[Š¡ž¿óV˜¨ñôÇH5H 'ELí áó-3]²¤·ï‹6: Êô›Ï>v}B& ÈɼT©_Ö[¡èƒUEä#i4—n®f¾^…üìCMéé!¨ð#hÌ» “xh p/I?7®T+çkÕ!) Ò^zùZ8ºsq×kE”Ŷi˜î×ĸe ýñÁÕ0zD=—¸‹/ËA, Ðý!篙Ÿ¥ ]2g G«—Ü6ÏÕú&\ï­óTóúã¤â·NL nÎr®m°5Mmaß§§“¡4àPR'ò£3»µ)\=PÚ p?|Ú6Ïn•sôVzUõÍÒ¡Á4,wäÑVÛ1}\ÛV˜‚óý ÓºÔÁ3VÃÄë˘ ‰Y@ª µŽóoa«V§¤'¯™ºó÷‹²Ó- Å" Zjà¤N¥ÉkzIÙ Í\s¹ÿÿÄ)!"12#3 $4ÿÚÿÒiú:téÓõu%/˜üÇüº’’u$éÓ§déú:’’Ìú±~ l ªÅGKü:týIIEAl¶VÍÚ…Œ™9räÕ%É«ï8É”“§OÕú:’—Ì:å?™~Ág›Vö½WÙI”“£[€§4Ôn¹sPŸ’ ãOóCo)ÖWù“íPþÀ¦¯ž]Áe›· <†<§TUÄ(*5؈ðí‘þ`„Þzeõïø‡ö®ÛÂH®¬Ë¸j"+ ý¸N¬>hŽ! 'ß »ŒP§L²ç$4÷ôÊ"}ñê?Þ›o}Ö@½±B<çÇ´—¢û,F V‡¨¯w¬K ÐßÓ$ߨÃvT¦Ñvòl=7%b:Ê”ñ¡äi¾äû–¼vB*Ð,¾lxœ3©tÍéÓ'æ¡c½A?ŠqÞTÚ4ʼnqŒß¸\|;@hð•Ä\ ñ|àÒäaÞtJó„_ ÕvÜ©ÖGÿ,æ¬/µ9m#]Û “7<¤HöÃqø‡Ca[/…ÔÊ3ÉټĹP\æYnøæv.V´*Þt5V;›¥ï5ôÇìCouo·²säJÞ%Z3¸iâ ÌdËø <þ\cäí…O„©Ü{Æõ6PÕVÜ»§ugÈ´\öÔãñ'›Ý«±¼û›9Ä¢ï\oˆËr>žŠ«Å¦iózãÝX‡ã4ìº SmÏÉ;¢¾ðÓòìe3Ô˜9Í86ƒL¼XÜ%8l©äý½KŒ°âà Žkq„m‰·@%§±¢Ö»½²ý¨A‰ktòS§ÂzÁ¶ÔZwËÞ–Í^ èpÝŽÎÅÒºŽ8µ™À“ (Ct²ÁT«zƒ$4Èò”Ÿ Ö6-+®Z±ÙkQùQi¨¸òáÉiºsµ‘ÌZªŠG!#æZw0#SÊàψÈS£7XÈÄ%!’tìË‚ŒvXxóÉ;§YÝW}Ö›Œ m?6.hÞ_Þ†‰þæ°ÞOÃÙ œí×(æÇJý‰[\Wm0Ó ÕAöL]úýØCp.–m²­î&[þ-§ðßlK~ê0bä‡Yé,nr"§ªÐ/VuC’têãþ°ý¨“„´ëm–ÃQ|…ýAq¯em|CíˆìÂG•ýA–õª—eǘ‰E圕jœá! ú^h#ºU“îðÚtÁ®ß…'âcš<úcºsK Š%g— ðv÷¬¯U·ÙCöyuÂüw^È Ì¯Éu,êz—IÙJë ([û“Ú,Somõ·ƒ_h«]5¡ƒd>Èì6§ õ‚=ˆÕí¹G«ˆ{QLÙ¼AmÝ¢3azH nÊ[sKÜö#òD&Ž‹ÿ(§1¶ãC“žÃ<“¹M࣠v«‚oÛžÉôœÉ~@ì¶M࢟˵Ô1ËŽGm8#&Hœ…Çd Ÿ°Lá6­À+¥.“ê X4¦°0b5Z¥îx4vÛVݘNfFèvM o4\§s^£Ä#³¤hÓ”Ó¹PÓ¨ô½UùcõQÉò {?ĨüP?Ô©¤»½¡!¢hÈ?#R B(i)Þ%EâêivóË´àÖ;0œë7*š”xPðŸä4nVQß%ƒ‰#V9nätšCíIÎ’ÆžBcq¿pÒ?"ŸÎ«o}–ÔhÑg”þÑC°Sç,ý¥Í)ñ°CnÉC³3¥e¾6ÝGó¹ÙH,;W§4ÿÄ+ !12"AB0Q@a#3PÿÚ?ÿ΄"÷“z_–W¡k¾œ²¿„‘&Kf-ÿ‚„C»ê>w#øÁ‘<ëB:o’~LWGdšÙ^RíYb”¦íhGMòOÉŠèÄÔ;Ó=ER8¨·ýÚ£r–ǵbËZÓ|•<™#ú:d ²RŒRí¥Ý鿢ÞJ˜rx ¬Šî™ÔKº¡ú‘ýiwBäcѽ/e73’yÊÁêáâVHíQòØ”]Ëu­41ŸQ[¨}”ÔTm=‡Eà„13§†^N¦y}¥9¸<¢XÎ×wDþ,Ï‚&pòNn£Üx‚Ë!îyv[ìmF˜Þw"´>4Tñod…Yü¨N‡˜‰}D¿¤.–¬s,mj.*Y‘ÔTRÙY,-àL“âìÇè‹\"5$ž3vÈ,²_­‚ì‰Gì¹#îÝ^1Ë#Ɨň¸/"5(úN›þÉoEgj”ªCeî¿¢ž&²*}þ;§xŒ§É>m-ÚWÃoá”c ^ìŠO.Q'77—«7C#É.mÛwéüÉoQ’Ýà„ûV?¥ðIáXX¿MäÎfA|’BbÐØ·¼NF°Il˜Öl­Ó}™›ÃµOßÁVŸ¦ò¸³²W‰KͼØ×úî­Gjrdxwk%‹þU8*Òt¤!ñex”üÑ_Ì[Óº¨3롬ó©M‘‡¿³THù#¨ò)øÝ 7ÀãW¸'“Óƒ»µb$t«u §ÅÕ½I®R]ÛêÆ¥jÞ1#ÆŒÛ;cVu#à›Nš#Çâ_ ‘éã?ºD©Ñ¥¥–=81t;çUÒ–}Y`«èEb“ÏàÀÑÁÍБÿÄB!1 AQq"Ra±23B‘0b¡¢@c#$CPSTdrs’²ÁÑáðÿÚ?þnÀ¯šø EËý‹"+³Í‹G_ØðP©¢=70êwg*Žè± Úxý‹‘ë¹@V*8ùª¬–³Öâ×{Ž)Ìðš^7\×p­ }$Dà ׎7bʺ’«ÁŸ3ýÖ„IÔæMãuèõ¼ÝaPšÐ8Ý•ÂN Ùã?ÁD~®Üo]Ì-Q©¢ÉI%2·aCÍ%h?rˆd´MÆê&Y -ö‰XO»çºÞ·½'pSÉó@‰D§>îÙžVM˜i<*õŽŸ"ŽX\54*ÉŒz¬^ó^ãw#Ðb•d—Á3}UµŸ¿ªíR—Óp’{¾jÍÚÌÚ,–†Žõq`:P¬lˆ5h4ª”Eðñe{1dÕ ‚Ü³Šºž/ýë¹N…À^Uy:ªÐ|E®ú´oئÿMÕUÑfb¶·mÙ–œ¤g‡ï+£~ÒÊcÚC3~fßNˆÙìÅ=;Îð"ççÉIÎå‰ÊÉcÙGf‹ωXjµÑsxnbyË–üöžV9Çéÿ¥<¸já š×x\tÜyVé¼0”IÔÜݦmYg‘{t¹¢êÆ¢§wî%oé)¡Â”ãü8nHU­Þ75—ù-Œ¹´èœ×f…y&ÜãnsN™É9ãÝá^W”þªÈÝ ²—þ[žJ”ÇOªnT@Ý^;ƒª¦P*bïÓ;´»$XâÊèfÃÁ×ñ;£’!£ u'R€CpÜÕmgˆ]¥â’=½¬Îv:šïdªnÓuŠÙ^GÑe­Ú ù¬M8|©PÙ¢ƒS'ö®è›ÍZ£æ?ÙbʺõZd´ü‘UáÕ«–AdW×EL<K›‚´Zv›H[F»gªÊO%ÝE~(šù&ÓMQãw’­úoí6˜Ä¥²âÚmH­4Uã%C¢åŸåv‹J´¬Æ[¿ÿÄ&!1AQaq‘¡±ÁðÑá ÿÚ?!=@øèƈóˆmšèÚ„QÌßWét·•4ʼĮ0cÏþ’Íõú6†7œt° ÝYÐv©…A‹"6n£½]: ´›Á“ €fYa…¶§/3Ì2b”_bÿRƒ¼È‹¥G¦Q!›taɉ‰¶+|NåµN+%ÝŸg YŒ¸Gœ8‚]'¹I˺ -Z—ÐîâÔáìà d–¯ÃÑÓãûß™G¹1œàÛí/˽‹ô¡ésîb§ˆŽVx'm¡ýÅyZxéÏ­ts(¼óƒíϾtë̼ù–-„RÐê÷ŠìËÉH‰.ÂaŠÌ¯}˜›”÷ L°f]OxF¡½ ûæŸkÚŒØâ*f åÄo2ÇÄY‹ö]ÊŽæs@{GC´nÜ%¹[#ñ¯¯Ef ˨Ý:S-q;7r¯,F:¬¼FeÔ¥7)é}ND·šÂ ƒáæ . Ì9„%¢WÛ1n9‚·ˆS§Ó½‰ø™¬_.æuæoŽ«jæß8›À)®õõÙ1rt|¢Ã€”!¼áõbZÛ™¯ÁkÚ-¥Øž¸ûGpæUü3ILTüOô¢=ÌœËZö½q lECûñ ¡0>^\³ö‰–OzüŸëczTw _HVÛÚ3-QˆKìgÈXzHwßÚú+=ð€§ú?…ÞWÙ*M¯PøúM½ÄW̵lµÄÒÏGxÑFøúï1KÃèâ*"VY±an{D¾>==,~cÛÐú4ôènúú9˜~ßÉ‹Aµ/isHKZ\Ä·‹fî?ÓþKU“{»Ëw “9wÔ‘xóÀ©Ú¹X`g£@׸¢€¯,wlnˆa4¿&¥t€ˆ<ª+DòåúCŽiªƒæ/CòÏŽ#‚¥¹”óÝöô±r³ŸÃ©š!ù×xX aƒÚãHÚ»Œb¶#䘆â‚^âV¸T “<™pJåöœ.éÙE_+âÌt¢¹$Dà‹kCc¸b!Ãyãp pµðÄÕZ½š?GL£nŠ ¸‡-3^#©¤ JÙ23»àÄ›x!7úªdLķĘ(ˆ„}JÂvì¤ÞewÚ7)P„ ªù”ÁŠ)T-0ÅÔ‘ ô‡/´Ð~¸àÇ”g©Eù¢ŸèA\»ï<â8,!iÔí–VÅW¹uB`´"•®—¥eW‚3j7|¹‹hÒʱ#ˆýÏ%yíÄL¡•{Å[(Þß=åú8ž B£§+úí4ì8ý¾“HzSáÿÈÅá)ù·ª‡›$›ø•î»%ö¡µæ%'`f'JÉÇÛ)^ˆ4+ØnÅYó¯˜Wë`Š¡ÐßbZ»Çn€îýÅËxè¶[ Ù´¦{`x9›Ðå‘€0#„rç1ñFBÌâ5ü!fÃï¥x nV@…ÜÁÈŒ„ÉŸÄztÑ-ôeµ‚™aÛÄ>ežŽ‚t.éqß¹<ƒñ/Ç~&FÉR¦"<„ðÚßvUÖªeÔØêZK|Í8”™tå–a{AgĨå2nÊñX«ìoÊj¶9}e ažù¯µà¹¸§ÙÃãŒÄÜÍGÂ3Kas²‡ŒJ«‡—^&ºm\£‰LTKÚ?=#â€ßÉÃFÓóýÚ7¶­Ö¯|ÑK¸Ö®–mØïò'¥ìy”viS›h-ÀÐJ¦cÓËÚ T\F̈¶[åsW!¢~æ´Ð£'õÍ™LFA•M[f·6[»o~~a(+ÊÞÿö¥¨1…ºõ‰[­ÁNÝ^¥5š9þÜ®M~ÿ[‹„ NÏ7‰v”¤Lgõµ®ÒÅè6 ¼|võN(!_bSêÍœ<åû{—¤pDŒqêŠq¶'KM‡Å{÷Õ#yõ/±ÙöAéýýPÍÖI ªºïãûÜçFâÿ„âQ^üÔ¬˜X&\iÿÿÚ ` ¯öiª3 >üy º8J7é(œÿçAÙâNs 0‚ßXNÂiV¡¡±I¤¤€IcLñ É«u´"íô(¼ÿªî;Ó©ÂgoÂé*Š¡qùW$|ˆ0–Ù»þ`?†2j]U•¾~™Ôˆ%Î!_š-*ͤ×9)ú»ÚƒÔpOê§&Q´UÏ¡ ½y)½b¶¼~ÅÀ| þd7ŽôÔК ”¢£˜ó˜pßо{Sk!QTerì]CZ äâÉb2vŽ o!"DY/’v™Ýê .‚ý!ËþŸ¾žŸÿÄ'!1 AQaq‘¡Á0±ðÑñÿÚ?éz‡¡þ/ŽŠÁžÁ¦Âléáö~ÿÌpäµÞ]. ÛÒõ<Ç7¬¹J)r¡½`ÂàÅã—± ÔG¸·ùÔPku~ Ùü=œ="ø‚ oàóÛÚRŠÿÉËîYjOúr{„@Øuq8¸fŒ¼<Žû‡ìÁ--~¾“…žÇƒýÌâ"^Ç1Ù“ áp~&j‰t\3ûîAÞƒW… g‰¸B— Ì©¬+—þå½Ã¼¡I¯÷×ó h‰©aרîûø;y~¶pÈï’^4@j-ý™ÙÙÅéÿŒjtœ˜º´BúðÛŽ\,áÔ Ü9p° ¹‹©¥½Ã ÿ©FeGÍwúÍg¼¨Wyÿ½O‰A±„ï3sõjUB¸Ðže~/@ï8ðî(”üÐQDu¬8¼¼NIé”/bY~¬²Uפþç(é¸n$<˜@íî©(PÜâ:8zYÀJ5œÃD§]ÄYõ‚©8^q—…AöCLŸ‡ûšB¥G™Øf¢&ÒXß ¸#Ä"Ôa½Dp.O1Ý?dÉañ­í—pštËjk§Po,`F\q¸æµa;ñû0܇ ‘¨%„¦úȤûC¼¼Ö —)¼~'ìÆ‚Þ*ú§Ø @\Ajž·œp„G£×ìšæ+´aÌz=Íâll +Ëb„yÌ@¶,Ÿ@~ÉQeàErí{Žá©s¾ávHº†Ú—úyÙ€#ÉÐ9Ÿêy›† NàNe¤ääÖ}Ê;2àc‡ˆ©SSâ ¾Œ1 Ôï} ¬pÞxÈÛ"ßç9cÜs‰qõ¼±%§HN8;JËêv'KÃ=@´}¡0ñ–,`§}¹¨ÍŽYX*—PŠ®‹ó8G˜ž9 #‹‹/¤{¸ebâÆõ4W|ýAgÕsöô]K–Cp—9ËT²{ûªûÍZ.oÕÆq‹‚!çc’‹?ÿÄ'!1A Qaq‘¡ð±ÁÑ0ñÿÚ?ôW¤‡¨Áè=d!è=¬";H~ €?äû!ŠÁÿ‹`°^A`@Š0dÉè#õ8–Ø.^ÞQÕ¿øÁX2ágpo%ª{ ¾‡˜ýÆq“}›HÔ>#(&Û;aÏS‰Ì~ã9Á4 µ@—|]y}>ñÀï!¸5+/3Ôã ùpfÌ¥¡ñCXR/¨â1*Yš¸’âå„{™C|7-“†£¯å6› ¶sOOê*‘ÛÃDßÿ¿Öuq€ïrô0FªxãP/ˤ´!þý½!Ž¢á Y6ʶ‰AÎáš[ZþYY[cI²ð†ÄÿáôöbM49áa…¹¢¾Hâ+H%ˆx…غàDùJ?Ü P:ÏÀÿ1’¥›ÊNÁ<ÄDòB~çý”¡«ùðâ,H‘ì ±òûÄ7~ÌXȈö­M†Í`[ú—þÒ¡¢m啕¡%pé=åj:”M„f†Í^ „¦ý Ù÷“ˆËá~þ1‹â3µ ×Y0öÈ[9 èyì‚’ýÿî•06¿(Fmþr BÈ.–àé)"«¼—ª0N¦ÑXOû ûd jE±˜Éñ+WYäv¼]GS¨#¨å÷ð(ü¹BTU¯>c&Ù*„'SDù†‹ñ?çÐv£IUîH2‹¸–õó,îT ÁÉÜ4ÜÙø'|¹rÎßÌ@&Ø>ø!‡³˜²äîV¦Éò±Éi¶6È8!· !;7²wœ•T©Sï‚ø%Ë—€4–ýOŏ׿+TÉ*mL? Ò\³p„ Ê2{h¿¹TÅåuø%˃ê¶©uŒXQ?ÿÄ&!1AQaq‘¡Á±ÑðáñÿÚ?UólEd»ãïS–Ù[VÕñ*ø&{d{o}Æì92»‘ÚîX_W»‹TVì]¿1e'dvqAù”µ_€°cæÃ=Á6¥arƦ2qІ¹š0ëîjµ,¡ÁÔZ`}á·ÅÃEÛæàÿñ*IzK¬æ**Q¢vPdLCv®è~Ì•uù‚†Ü›¿R¯:ÊÄȞ󸄼fÜOña¸¢E™vST¾` Ýsp]ŠsæPX2 éŒR]Ü1YIä‰XùåR©,{ŽËÙÕÔHÝOÇ7t[Üx§gþÒ‡Ô*¸U=A¯ÜÇ5fÅ—,£æ:©¦§‡¹ÏL$a•gETªn:Æ$øR?™yî² Ñ›jæZ5ál¨ÉWŒûË&ê;ÕyêönñÅ`Ÿ|›±W éçç¨÷Ç2å.1þy( î]Ê>!ª˜Zsžå\®©üÏþäcÛ*È™Gq ¹Ù›µ8J5V’aÚòœ°-bè!ÞoÍÚêka?…-J ¾ 'oP»:Æ“Ù82 ‘O´˜®3Ym+ïËøo5tš[ñ£¯h“ýZÄn¶5^Ó¸Oi[±{u@ÒXqÏP_ÖûéI¶vâùêaHƒt×À~奦èÿs/Ê/D Û[–øjƒÅÛ’”žÀ2«ùê1+Ílpò¨ÌÁŒhFŸU9ye —ÏùˆßÜÁ2Ŭ ©Ò…sPaÓ¨b‹¨VB·ÒkpßyB\~"ó¬"«<2´@¨´W0ççþË.£¾–vph#¿s9\€}å× `·Š½ä2-é#žxçê T­ ì½æ­É^¾ñ`$8\êf°óKÂBl¥ƒ SÃül8ô¯è˜¯ ó×ãù”u´ÖŽ{»IQ{y]Ô ÊxKž‰âFÀ]L6¯†½?9º .€þâ& ¼ÊXWØÏÔ»%»«S×§Jqð‚¯tÜ$AšæUWAõç bê²¾p²ßê'-lka@y÷ЪpÿÍ‚¢˜-š`b1jeUè× HËC5úS¿ÆÍ;ôžìzGç7ø§üÁ…AµÃI÷ èü·"‡ðWšÆÔæ¸jà”ðŒé ÛCK×0ARË«„¶¼…–{ŒÝŽ÷jRýÔ@‹pzÓõxlû¾?¸bvbõþ÷ö *;4–*GÝúM+Ô?,ò…hE¤du“žåc¥}r±]s¤NH½/@áqL+–ý1?&ت¶éê±ýËÔTüªýeÑVG™(ï÷” Þ)DæØ¹üÄK9Ôèò¤—ô‚ÍýeÄOÛøÒþ‘Sò¸‚ÈãEœ%÷ö˜L|}¦—–qò`µ@ нYg(6”;[ЃŸ)·J×ÄÀg) ÊÐÕ[Þb¬)¶®åz;.Lx-Ć՛gÜVûC½XF5wÔ~ã`Q.»ŽÊXm˜Ì—‡—™G—ÂbÐ__`Š™€î˵Ö¸/Ô+ÇúüÇÃ'´0«6¾±=ê4Sà5¶ý|„™öœ#£ë™‡JfìL3¡TmÉñþê *ªË-¾¨ÅêõÀ'¬#òYÐoĦCxXßÊPmÏǨ¡Ö4öÚ‚ã/ëÌVãkâ;ˆ9N(åá^©uŠ6׈¦ˆƒähɼȨ–Tº9X¯}GD@ ˜q ‹CÔÑÀ£Å,WÝ÷†hè£SNêø÷}i•›«¬¸Jd¾òºœ Å™9S/ó;•å?çïq¡ÒÝYÀ ÉRG±íú†¡Ð®¼Ê-ÆgGþ")(  ‚Õ®/¯K|„`˾I`¡-?)Ο‚6˜yý˜ª ³½U¾¶üÜÁ¯Ì3Gd¬¼‹ø·îže¡ö}×5.¹¯-´ñÛSpôŸáÿ†EQø}‘ e4ÃXˆX¥ÉÇ_RÞJñDäáŸ|Øï>¼|’éå!sÁüFŸK~j8E‚‚ò뫬¨j5|'/Wxäé{“Œˆ5o²Ë•1ÂRXc,!nœÔ FOB8ß]¼×ÖZjJy—ÐåÊ­ L˜çò œÅ¶"6w= q†ïüëv?–Ai¿WHÈ¿ˆh-Z åqÏÓl–­VF¹l²‹xB¢èj—©W"¨¨p5™Þ%†îm ‡ ðC-!PC|IñN…¥ó\ù‚˜nBnõ¶¬Š¬Õ¢0ªûØ–q¶KkÑ_âXTª…·E ƒ+· ‰Çlek¸fDåséÄŠÑO#ÀðQÌDœVqQô~„¾ãî l¹T4y«–¤Î¼¥ÔÈ!;µôŠ?Ìýn¡€QmÅú¾H$$ÄŽ³·—ø¯¤ºµ(XM}iψvù¢Þ)¡ÿ=Á(¬¤˹‘\Ä·­­õu„¥IJÂjήLj«?âm”©¦ÌZ€Ê¹pâ9üÌÀ7 [r!ãþÅ¡u_¸4ÊKMŸûó$¢íÃ8ž¡ñe¿´(¢ÛÞ Ìçx`š Ë‹uuª/—¾•)L_ø,¡iSŸ­h)`5`8°¤´pü@¹*4ñ‰eZùoԥɅŽóÅóâÁÛC¨e}ÇÝcûc†ëóZtŽ$îkŒ E6 Ér ¢•æ((,¤Þwây~R5Ôî2Ë·š×êçòðÒ".Õšž‘â•Ôá+9P zìÏYqf¦¦Økç9½*‹‹ß$ø_eK]Äá`Z·µnþí‚é©.ÇÀ9ò/Qa3A<P¢¼¥FWBÌ1ôôy¬ Syub­ø_®#åìæ¡ŒŽ,†£¦0hþl®ZÂ"•ñQ)Æ o‡ýÄÙ±JO/pE+‹ï5}_YþÊÞM³„j}¨ø•@jØZñ¯“ÄUppÞ,°p¥oH©zMåSÏ|Öù¶Ô4  lá·ÇÞÜŠì,UX³ƒGßÞl„ª/¨q–æþæ¶® ÓN9ãRËŽ+hÛR×\õ™§æ4…Vݺ --group-add video -it migraphx ``` The dockerfile will install the latest supported version of ROCm with all the dependencies needed for MIGraphX Once the docker file has been installed and you're inside the folder run ``` $ rbuild develop -d deps -B build $ cd build $ make -j$(nproc) package && dpkg -i *.deb ``` to verify migraphx has been installed correclty in the docker run dpkg --list or dpkg -l ``` $ dpkg -l | grep migraphx $ ii migraphx 2.7.0 amd64 AMD's graph optimizer $ ii migraphx-dev 2.7.0 amd64 AMD's graph optimizer ``` ## Running this Example This directory contains everything needed to perform an inference once MIGraphX has been installed in the docker container Once you've build and installed MIGraphX as a deb package, go to the examples folder, run the pre-req script to build and install onnxruntime and then install the appropriate version of pytorch from project root. An example command build Onnxruntime is found in ./prereq_steps.sh in MIGraphX Root to build onnxruntime. Pre-build Onnxruntime Wheel file builds are also valid. Ensure the wheel used is using the same python version you're using on the host system. ``` ../../../tools/build_and_test_onnxrt.sh pip3 install /onnxruntime/build/Release/Linux/dist/*.whl ``` To run this example then do the following ``` $ $ cd examples/onnxruntime/inveptionv3 $ ./prereq_steps.sh $ pip list | grep onnxruntime $ python invepctionv3.py ``` ## Example Output: For each run the target image was changed. Stock images in the example folder which contains three in-class images types and one out of class image. For guitars we show three different variants of the same item in a class with different backgrounds as well as background shapes For the tools (scope.jpg and screwdrivers.jpg) these are both in-class images For the bird.jpg image, the imagenet_classes.txt generated doesn't contain that of a cockatiel and thus the model attempts to find the closest match to the animal found in the image using bird.jpg. Image of an bird which is in the imagenet class labels defaults to african grey African grey 0.3860151 inception_v3, Total time = 801.04 msROCm-AMDMIGraphX-46524e8/examples/onnxruntime/inceptionV3/inceptionv3.py000066400000000000000000000216021510465702400257610ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### # Inference with ONNX Runtime import onnxruntime import time from torchvision import models import torch from PIL import Image import numpy as np import argparse import os import subprocess #Use most upto date weights inception_v3 = models.inception_v3(weights=models.Inception_V3_Weights.DEFAULT, progress=True).eval() # Download ImageNet labels #!curl -o imagenet_classes.txt https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt def parse_input_args(): parser = argparse.ArgumentParser() parser.add_argument( "--fp16", action="store_true", required=False, default=False, help='Perform fp16 quantization on the model before running inference', ) parser.add_argument( "--image_dir", required=False, default="../dataset/images", help='Target DIR for images to infer. Default is ../dataset/images') parser.add_argument("--batch", required=False, default=1, help='Batch size of images per inference', type=int) parser.add_argument("--run", required=False, default=100, help='Number of runs for each batched inference.', type=int) parser.add_argument("--top", required=False, default=1, help='Show top K of inference results', type=int) parser.add_argument( "--QPS", action="store_true", required=False, default=False, help= 'Show inference result in Queries-Per-Second QPS instead of inference duration (milliseconds)', ) parser.add_argument( "--verbose", action="store_true", required=False, default=False, help='Show verbose output', ) return parser.parse_args() def get_image_list_in_dir(dir): proc = subprocess.run("ls", shell=True, stdout=subprocess.PIPE, cwd=dir) fileList = proc.stdout.decode().split('\n') fileList return fileList def softmax(x): """Compute softmax values for each sets of scores in x.""" e_x = np.exp(x - np.max(x)) return e_x / e_x.sum() def run_sample(session, categories, latency, inputs, topk, batch_size, verbose=False): io_binding = session.io_binding() io_binding.bind_cpu_input('input', inputs.cpu().detach().numpy()) io_binding.bind_output('output') start = time.time() session.run_with_iobinding(io_binding) latency.append(time.time() - start) ort_outputs = io_binding.copy_outputs_to_cpu()[0] #Get prediction for each item for i in range(batch_size): output = ort_outputs[i].copy().flatten() output = softmax(output) # this is optional top5_catid = np.argsort(-output)[:topk] if verbose: print("Prediction Outputs:") for catid in top5_catid: print(categories[catid], output[catid]) #Filter Images based on image preprocessing def preprocess_images(image_dir, image_width, image_height, batch_size, verbose): if verbose: print("Read from dir " + image_dir) fileList = get_image_list_in_dir(image_dir) if verbose: print(fileList) #Setup input data feed input_batch = torch.empty(batch_size, 3, image_width, image_height) bad_image = 0 # Bad images skipped in data set from image list bad_img_list = [] # Keep track of bad images we can't preprocess img_count = 0 # Number of preprocced images i = 0 while i in range(len(fileList)): img = fileList[img_count + bad_image] if img == '': break if verbose: print("Preprocess: " + img) input_image = Image.open(str(image_dir + "/" + img)) preprocess = models.Inception_V3_Weights.IMAGENET1K_V1.transforms() try: input_tensor = preprocess(input_image) except RuntimeError: if verbose: print("Skipping " + img) bad_image = bad_image + 1 bad_img_list.append(img) continue input_batch[img_count] = input_tensor.unsqueeze( 0) # create a mini-batch as expected by the model img_count = img_count + 1 if img_count >= batch_size: break if img_count < batch_size: print("Warning Batch size " + str(batch_size) + "Requested but only " + str(img_count) + "Images from dataset") print(bad_image + "Images caused RunTimeErrors during preprocessing") if verbose: print("List of bad Images:\n" + str(bad_img_list)) return input_batch def main(): flags = parse_input_args() if flags.verbose: print(flags) if flags.verbose: print("Reading in Imagenet classes") # Read the categories with open("../dataset/imagenet_classes.txt", "r") as f: categories = [s.strip() for s in f.readlines()] if flags.verbose: print("Getting Exported Model from Torch") # Export the model to ONNX image_height = 299 image_width = 299 x = torch.randn(flags.batch, 3, image_height, image_width, requires_grad=True) inception_v3(x) torch.onnx.export( inception_v3, # model being run x, # model input (or a tuple for multiple inputs) "inception_v3.onnx", # where to save the model (can be a file or file-like object) export_params= True, # store the trained parameter weights inside the model file opset_version=14, # the ONNX version to export the model to do_constant_folding= True, # whether to execute constant folding for optimization input_names=['input'], # the model's input names output_names=['output'], # the model's output names verbose=flags.verbose) # Quantize the model if flags.fp16: if flags.verbose: print("FP16 Quantization Enabled") os.environ["ORT_MIGRAPHX_FP16_ENABLE"] = "1" # Enable FP16 precision else: os.environ["ORT_MIGRAPHX_FP16_ENABLE"] = "0" # Disable FP32 precision session_ops = onnxruntime.SessionOptions() if flags.verbose: session_ops.log_verbosity_level = 0 session_ops.log_severity_level = 0 session_fp32 = onnxruntime.InferenceSession( "inception_v3.onnx", providers=['MIGraphXExecutionProvider'], sess_options=session_ops) if flags.verbose: print("Preprocessing Batched Images") # Preproccess and batch images input_batch = preprocess_images(flags.image_dir, image_height, image_width, flags.batch, flags.verbose) if flags.verbose: print("Running samples") latency = [] for i in range(flags.run + 1): # +1 for warm-up run run_sample(session_fp32, categories, latency, input_batch, flags.top, flags.batch) if flags.verbose: print("Running Complete") print(latency) if flags.QPS: print("inception_v3, Rate = {} QPS".format( format((((flags.batch)) / (sum(latency[1:]) / len(latency[1:]))), '.2f'))) else: print("inception_v3, Average execution time = {} ms".format( format(sum(latency[1:]) * 1000 / len(latency[1:]), '.2f'))) if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/inceptionV3/prereq_steps.sh000077500000000000000000000032721510465702400262240ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### #Install most recent stable version of pytorch pip3 install torch torchvision torchaudio --index-url https://repo.radeon.com/rocm/manylinux/rocm-rel-6.0/ #Install preqs for this pip3 install onnx # Uncomment to install onnxruntime manually. Run these commands from MIGraphX # Project root #../../../tools/build_and_test_onnxrt.sh #pip3 install /onnxruntime/build/Release/Linux/dist/*.whl ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/000077500000000000000000000000001510465702400224115ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/README.md000066400000000000000000000073051510465702400236750ustar00rootroot00000000000000# Resnet50 inference with MIGraphX and Onnxruntime ## Description This example demonstrates how to perform an MIGraphX Python API resnet50 inference through onnxruntime. The model used here is from Torchvision's pretrained resnet50 model ## Content - [Basic Setup](#Basic-Setup) - [**Running this Example**](#Running-this-Example) - [Example output](#example-Output) ## Basic Setup Before running inference we must first install MIGraphX using any method then download Onnxruntime into the root system directory Starting from project root: ``` $ cd AMDMIGraphX $ docker build -t migraphx . $ docker run --device='/dev/kfd' --device='/dev/dri' --group-add video -it migraphx ``` The dockerfile will install the latest supported version of ROCm with all the dependencies needed for MIGraphX Once the docker file has been installed and you're inside the folder run ``` $ rbuild develop -d deps -B build $ cd build $ make -j$(nproc) package && dpkg -i *.deb ``` to verify migraphx has been installed correclty in the docker run dpkg --list or dpkg -l ``` $ dpkg -l | grep migraphx $ ii migraphx 2.7.0 amd64 AMD's graph optimizer $ ii migraphx-dev 2.7.0 amd64 AMD's graph optimizer ``` ## Running this Example This directory contains everything needed to perform an inference once MIGraphX has been installed in the docker container Once you've build and installed MIGraphX as a deb package, go to the examples folder, run the pre-req script to build and install onnxruntime and then install the approrpaite version of pytorch from project root. ``` $ $ cd examples/onnxruntime/resnet50 $ ./prereq_steps.sh $ pip list | grep onnxruntime $ python resnet50.py ``` ## Example Output: For each run the target image was changed. Stock images in the example folder which contains three in-class images types and one out of class image. For guitars we show three different variants of the same item in a class with different backgrounds as well as background shapes For the tools (scope.jpg and screwdrivers.jpg) these are both in-class images For the bird.jpg image, the imagenet_classes.txt generated doesn't contain that of a cockatiel and thus the model attempts to find the closet match to the animal found in the image using scope.jpg. Image of an oscilliscope which is in the imagenet class labels oscilloscope 0.99869776 radio 0.00051740184 screen 0.00044122382 monitor 8.556517e-05 tape player 5.1727424e-05 resnet50, time = 61.98 ms using screwdrivers.jpg. Image of screwdrivers which are in the imagenet class labels screwdriver 0.96512324 carpenter's kit 0.03258186 hammer 0.0007571124 ballpoint 0.00058690814 can opener 0.00026641905 resnet50, time = 109.67 ms using guitar.jpg. Image of an non traditional electric guitar shape (Telecaster) which is in the imagenet class labels electric guitar 0.4413027 acoustic guitar 0.14725313 Band Aid 0.14059556 pick 0.076821454 rule 0.020968033 resnet50, time = 77.01 ms using guitar2.jpg. Image of a les paul stype electric guitar which is in the imagenet classes electric guitar 0.7114628 acoustic guitar 0.23226906 banjo 0.044191252 pick 0.0056983875 stage 0.0013321621 resnet50, time = 43.09 ms using guitar3.jpg. Image of a super strat 7 string style electric guitar which is in the imagenet classes electric guitar 0.71952045 prayer rug 0.08914763 banjo 0.078091994 acoustic guitar 0.056061186 violin 0.021640636 resnet50, time = 71.71 ms using bird.jpg. Image of a cockatiel not contained in the imagenet class labels African grey 0.5883207 kite 0.06284781 goldfinch 0.01847724 macaw 0.014789124 fire screen 0.013297303 resnet50, time = 88.89 ms ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/images/000077500000000000000000000000001510465702400236565ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/images/bird.jpg000066400000000000000000000657251510465702400253170ustar00rootroot00000000000000ÿØÿàJFIFHHÿá(ˆExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:22:13 Ðè Ànt 'ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ïµCˆmÛÒæ/ý UɆëyªš§ªô0}%ŒþL*÷Þõ®¨‹L}úe«ƒÑ)ý),8šðùìj ‰Ðtüõèýò*[#þ™|=%ú¦ä8Õ ÷ª¯‰?ä‡Òhÿô!Vf8Õm}Õ¿•Tñ9ÆŽO¤¨ñáMn‰fà<ÕM%³§Gõ#õ«C­TÒð,€™‡ëL ‡ö´£þ™çQê'›_úîŸÎ„ÿÌ¿õÄ:MGþ]¿ëº1@»Ö~ˆâA§×´ú«ÕCDÿÿ^Ñÿè"€ü….þ‰ü…Ç÷¶ŸõØQü„®ÿà?ÈR_s5§ýuÄ]Só ÏÐŽtz˜þ‚¯€O¥RÑxдð;[Çÿ Š´9Õ/ýŠütU??—á‹¶Î1·ŸÄUË?ùjþš'þ€+3Ç„¯y$ ~tÖä½+”ùvì80ý1[ B¢Ž˜8çµfKngß gseF=Û´ÙÆ=8­™šÁELž¿Ê©ß¶ëÙõãÜÕÌþòqœûÖn¦Åt9ÿ¼àëÞ„6a넯‡tè‚ÎìxïÉ©5IÅ¥œq'”?)úŒÑà šº"G”„÷¨ÿ¿øþ‚)/OúE ÿ¦¿Ò’ÿ+Áþçþ‚(»æîÌÓCü©ˆµ+b>Šj 0mÒít¨ý*Kƒ‹iOûùSlF,-Ǥkü¨2Ëþ?/ý4ú¬Ïs :Žrz}9­;/øø¼?ô×úVOüJq÷œÒ©nKØàlcÅÞ쌮[ÿÍYaÁqüqŸåUìÛ8£Æ}r*lâ4¹úÖ¦hˆnÇÈ›€'Û5‘ªÊéñùƒåi2£ûÄUû™(%œœ‡v1Ôí¬-QžK 9å‘‹Óšie/£¦»i  DàõíRøÀ¯4gª¨4í`nñݼ`*Ê0y¨¼HLÞ+¸¼˜ûÖˆƒÝ5ߦܧ÷¢aúTöíºÞ6õPj)ÎëyÕH¨ôé7iÖÍœæ%?¥pt:ÃJùa?»s/þ†jHøÕæ±Pé퇺_I˜þfœ¬F²Þ†ñêb&¼ÿ›3ž’J¥â±»ÃWƒý‘üêÍë~òÔ礢«øŒ†Ðn‡û4â Ü<úÕ='‹YAís7þ†jhßä_¥TÓÜŸé2ÿèf€%_ù?ý{ý —Péoÿ]ÓÿBñý²Oý;ÿìÔjÅ¿ýwOýS럑¾•OH?ñ%±ÿ® ÿ ŠžGùéU4§ÿ‰=ŸýqOä(öÇþ&—ßTÿÐE‡ý6ÄÓFÿÐMGlÿé÷§ý¤ÿÐE6éÁ¿±í?þ‚hr퀲œú!þTëQ‹X‡¢åU/䯛tsÒ&þUbÛ@(a÷GÖf¬_6,-ë·?•jiïÅÉÏü·ýÖ'Œæ"Òßþ5QÜOc‹±ù]’FÁŸû⦼¹ŽÚ œ.œqUb™míd‘ñ…AÓýÞi¾[MæÉ7Ü1¡T#5©˜äµs-ôÒ6íTLöÝŠ¥­)y´¨÷¸t²kVrÛˆqÃÍY×l_ÄÚ,`œ*DyéÐU-ÄfÅn×?„ fcxuôª2fçÅD)Éið1Þµü>RïâeÄÀ,Ьì ç8¬ E׉á/–\œ}kBOsûm³øø‹ï «¤ÝºM¢<ÈaU °ã¡þÃÒÿçÂßþýНa£éÒZ+=œ,Üä”Á¥Ž­M;ÈÅÞg`#攦î«#ùÑãÉ#ïZÍ·ÑôÓyuYÄB²íG¨éN}MÑ'ØáÚTœmÍ=SFöò6ø™&SÃZ‡]»…ôk„!%pê«w¤iшJÚD3*©ÂŽA LÕ4›4Ù¤ŽÖ$d(âš°Íèïí¼µÿH‹§÷…V²¿¶Q87ç9å‡rj¤.™äF~Ãv஦³ÏºÊÄ„  8h=KÇQµþÖ ö˜±äc;Ç]Ô—ú¡u >ro2*›i:oö‚§Ø­ÊùdíòÆ3ž´Ëí'MHâ)al¹š58ˆr E SZMNÏËoô¸z|U{JÍtëu7QecPrãÒ }#LØâ_kÓþy/øTvE„Ö–ù±·y’c“Š4 Köó(žæ]êRF]¤¿( åì1^[4“**–ÉcŽÕµe¢¬™Øãl`p£Ò®4pÉFñ‘‚ ×LKƒÚ褮rzŽ­e&™uwQ3´,'dkzr¨ÿMƒë¼Sµ}Á¬æ6ÖpÚqµ?…VþϰÿŸ;ûö+¦•XÔÑ-4ÆØëZ|PÌd¼…IšFùœ åXÞ)Õl¯ _"á%òÆã±³·žø­+;+6·ËÚÂNæÆc3ÅcøŠÚÞ6Äè‡ÊÉØï[­È{Ño;ç*¤øcš³9[ $|ØëDjGŸ¸‘ò9ÇÓ5#æYN_ƒy·pÞµd^Ê7lóíÈU!G¬ÒVp XôÀþTNÿñ'v,ŸñõϯÝþUÇgŽõ 2§’²AÀàvªBf_ƒ]‡ˆõ+­ÄypÊÇCPx>1.¾ŒÍ€ ¶sŠ›ÂýÄ~Yb¶äoãåÝ‘NðT‚ ›ÉøÊ[¶ÜúàÖˆ“Ø!mð£z€j 4ÿ¢°ô‘Ç䯒Å÷Ø[¶zƧô¤ÓŽuôÿV5ÀuÂâcqîþ”“j6þá¿•63R_xÁ¢rF¡h}KütÓÚƒmŠÿMãÿÐ…7X?ñ)¹ÿpѨ‘öu' ‘þ<)º±Ù7Yéå·ò¡.[6mb>¨?•EiÄ×_õҖͳeõŒ*ŽÐ‘quŸùè?•=Û”cÖ3LÔîböš?ýQ!Ú1¸išýÂÿ×TÿÐ…1œüô©´!‹($9À•QšåÖóHü*ަ·4Û7ƒGH‡íãéS-‚êæ<Úü³\4hJÅÓŽµ“§iZ½¬-·o0gÈrsÅ_ÐÜ'ræ¶ša`¼õZp|¶ÜîÅF…“¦gMǦÎåðå;}* Õ›¨^ÊÒʨqt_ÌW7W¡šº8¤È¬_ý HîIýk^|ÌïîqúÖ–žÙÓ n™@k\9¸›¦D5¬w3{²©g$ŠI ¯€Çê8§"õImŠo±¹3ÇÒ ¸]–»–`}>õYvî,±eIžNœœZg*™tûxKæÜîÁúb“N—wˆ±úÒÞq-±ÿ¦¢€Q8³cèAýiuNt«¾å‹*f¨véÓE&Ÿƒ§ÜƒÓËoåM§é–§þ™/ò¦Ûô»Áèëÿ ŠGlè–'<˜ÿA@Ø¿¼êñÑ@‡ÌâaÑ¿•VÖ.D6™çvàGàjY›ý: ú7ò¬=Vñ/®V v)Ásý)¥v&È Ôá…$7(Í3œ‰äƒÛ±á¿Íö¿.啾îkžàä’pGzôèBÖ3uul½Q›°­V êá¹1°æ³o¬)ÃŽ•ËL/aÊG+`©ã'Ö¬Mp¡ã©®:”‘jG¨À!؃¼ñŸÉ…Y/„cè*¶¥ {ˆ²娧Jßèòsü&·Š²H±4ÛuJåX:Ô‹ö뜶 Àã½o[œZEþàþUÎj¬Måþ>HqÜ™l3»pNÝÒôî~zˆrqì*”ºÈ6/u *ª„ëYþ$¿I5u]›–Æ}Ï5q«!x‚©d|£¹ô¨iX¸EÉÙ—:ÕÌòƒ‘#ga ûÔßð‘“jñÌ™fR2+'\–g×&yQP²•{V\Òþìã׬ﮇ­4åI¶µ=* ­be`T¨ ŽõÌê’7Ú¯€p\ÖÒáÒ-’L† 2jçõYAžû$c‘ÏÖ¶†ç•QYØ–7`Àà‘æç§û­A#âÍÁØá`\{gúŠWá÷(!·néŽÙÅW™ÿÑdÁ,<˜ÆsÓ§¡éåòÚf Ä-–ÃÇL¦1XŒaðTDd¸$|¿‡ZÕ¾“÷:›íyÅcê i°¶6’ÍÄÕ! âkÇ]/G$Ë€dqÁ"¦ñ ÷VþÑã.1îUÇLóK­A÷¶‘´ l·@G¯®ßØG,V‘Üo`©‰ã"©j_k.¦.nBÅ!ÆrÞ¢§ŽmMu#þ sMý³T¼3,’ý§Ìbß2ò~€VÐãTCë­r‘Mqª}ªÕ¤9 qûμSµ PÚ’öq‰= [¹?½µoIëNÕdçÑI¤iîuVÕ¬#Á8”Uh®µ&°I´Æ0|ÑÓ³)Ìl=EQÓ›ÍÒm?z?¥ š5Ýúé0,vjèï"¦[Ëá!ûݰdoºgE·Ç¡þt—wBÚægÈ å|¾ç4ú‹¡߉o#¸˜Ø­…Îqþ½UðÌ77Ú©1³*6T•íX¬ey„y9a­wþÒþ̯pKÙ};Ö«Dg¹Ø¶öéã 1Y:ÞÈö©Ë·S¯ï’ <ŸN¦²|ýŒfœüç¢úTI”‘RúÖÝ¢ÚÊ3Ôµs6Cyq$páY%^ ÆkWP–ká4pòI•»(ô¬ˆã¸·±Šè˜¼òqÕžj.¶}Md—21õ}>ò=VHP<À±ÀÉ&¥ŠÚ;{6éó´˜áÝxS[Œ¥n-w¶X±õ;MK{ÿ§ÜúÕ$Š•Y[•lBugãýïðŒ×:×+qsrYY îù¯â+¯' +…¹mÚ£s“–Å\Ld]f^rŽü ¹,¶î¿)Qµ3œž69s,¿sÿeþtŽì͵³ƒ(ÉÇ9« ‡Rdû çï~uRîug§Ä¨w&™¨ÈÇM—Ò Œõ«Hà<`©R°(ÁúRnÈßMT¨¢ËÚ††o.Zí.DcÎ:Sîm§iËEsn»€éõ©$ýÔ$Eœ2ò§Åsmª½²³Qžõ>Õ«ñË ÜSoUs£Ò¬ÅÔ‘u-cü«AÎ5sê~•\jŠ}bÇëRNؽ³oö˜ûäÖGžY¾8[vôž1ù°ºÇÍ£^×É|cèj-@ÿ£)þì¨ß“ –ûæ°¸_XØ~”l¶Fk;LiVƒ="QúU˜Ÿt}T­¦Ÿø—Â= «  hñŽÁ˜~LjRÆInàr_;öQÜÖ…-Œö mÊ$¯Ÿ˜ñ[3ho©¤²‘ĸzŠiûÄ=ŽNMaj—q+3Dá˜cµÖiÓËo¥'›F9$‚rx·3oahı¨ËZåu=dKºV8Çú´¥*èDÍ‹YuÔn^ðÀâ1ÙG ª“6§«\¬V8WqœžËëZP[Áqmr@ìrìzÓ,l×J‚î{kÒá£Û½‡Ü{v§º)EØ[˜,æÓ|ÀcRLÓwæ±­¦kMòÛ’1…öZi®ÅÙ°ñ Å”äõ«u·Ù$–2rxÉü)F-6äUi©É(+E¤?éVÿð/åN½?¸Oyã¢fÿK‹è•­”ˆÓdÿÐ…Q²~q\ :µþsœçŒðMwe°Ùö®y’7$ù€pqWdY`l2 ƒŸJdí¶M¼nNN0NjAµ‘‚ö c=9ª×$»)aœ±íןJ² ýEÙ}wH*ÒÍö‹ÄGíU¤•Wlr±ŽŽõ!ˆ-Ûͽ‹îÀ sŠVº4…WMÝTºV© ‚Þ=œÂ«¶B8ÁéKý‡ª´àiw,Œ»ƒì8#ÖŸ©x¶]KG‡M6ÉP±‰UŽp¸íøU¿øMM0Eýp ç>¸®'BŽÊ÷þºž„3s][kÝñ¨@sÔ0ý*[§[f'¤€~uJâ@·6ÏèÇùUÍRÛ$ö¶p;èQ¹ç¹Xè5 Óå`rgò«¶`uõS\l×Éu‘Åpѳ©\1à×Mot.­RLcpäzâ‡erÍœ›¬`9ëŸÒ›§dÛ*¯'q~5OJ”&О¾Jçò­ ¤ï4ÜbW ûÆ¥”tÚM„zmŠÛÆ=KRy5vYҙ݂¢ŽI¬«=AÚÕî.”¤ª»Ú©Cpú¾¹Ù‚S±úÔ$É ji.,#š\¢ÎÛaŒñò÷'ð®nõªÒ…ùU†ßζuû¦Ô5¯&Ü-ޤ÷¬«:îúf³·pª£ Ý@¦œV¬¥5î«”/µ9®]­l9vùw/z¹dÑh¶2‡kæNT íŸÃK$aÒYä\–=Vª[»—æ–Pî‚)ÊwK”Ú„i¥)Tz®…¸- I Ð"¬n71žÕ6.î?à?ÊcòZìÏÝv­B§ý*ãê¿Ê¬Å»»’3¦Gþ饹l˜Gý4_çP“þ˜ŸîYÛç‡þº ‹r6já†ßœpÙTbT÷SÜSšå“ˆE^*V; çùb#þz\ðWŸÌ‘y2Ç5§wÀ‰I¸È)ä{ŠÉ’Kqå¤JÞ`6î™ö®>†¥iFã>Õ×è1Ñá-üY?­s^^è¾ažx­»Q¨ý–-“A³`À*xªlIYššsm±‰}*Ä7†ãvဎPè+ÉïÄ$„€ì:Æ–ÙïƒLˆüç56·o©Ë~Óoác¢¨ìoéò®Ÿ¤Üßœ Ú‡üûצI{æ]„Ž2|öÏ=ó]mé7RÚiJØXÔqëÞ¢£²Ô‡FÔà³°Ô®œp T=ÆGÿ^§ÑåœhװǹËjåuKÔH|˜—‚~èï[’jm§x^-2!™6æfô'’)Ô§•Ípõ§88C¯S–ºšûR¹“‚wrÏé[6*‰gÑuúUT{¸ *¶`Œu9¦Z]\ HGÙ[ õ«ècË·Ê[¶oÝ?ýtoæjÏúEÏûËÿ Š¯ow(‰³m'ßnƒÞ¡Ží¼ëƒöiyaÛÚ‡rîïôÕÿsúÒÎß½ƒýú£öÂ/0J0ŸÝ÷¥žù|èsƒ Ý}¨°®_ºôyOûùW ܽO%»WOy©Â-e|”?Âk–€ç`÷5q&F†ÿ˜üÛóŸÆ©ÌܧO»ÛéSƒœgƒž¿…TsŒxÛÏ5D”§`/­Ï(&­ÙÆ\ ä–9ôªµ©'?»8­;2Þ~÷’ÍÒ¶¢DÊðN±\I;mn¹ç5§&£k2‡„²&8>•‚ÅM¬ÎW$¹ôæ›§–LôÅUI;ÉyŠ+DzìŠ`1ž­ÐVd¿¾˜K3@Trݱ$—ëÆj©”¸WFìµ,À )A[šdÂKÈ9ÀÅaYéWè$D$õÇ&·l!u¶ ‘ˆÀ†“Ifp²újçÿ4ëvýôãý±ü©¶°Ê^e±!Ïj|Mö»•òœœ©Æ=©XcôëÆo»Åó§µóÛÉ%ÞNæÉ¤´í-5À¬“Ë€;ã½A©Á(°án/j\y¥¨¥&£t[Ѭ­.t›Ûûã;SÛŠ¦.Y’feëóW|?dãK{i‰W3e×éU/ ˜\^I°ùl‡njSåìuÝÑ ¡ÕþEÉä?J­hСÿp*¸öÓl'Ê|cûµVÊÞw±€¬NFÅè=ªìs[ŸÜŸ÷ÛùÔ1ŸßOþðþUrÖÆêHNËyæ=úÔqi×<à[ÉÃ#oµ;1\«Ÿôïûgýigoô‹~{ŸåVwöò¾D›¼¾˜÷¦ÜX\‹»uhX¶ÔY…ÑNý¿Ðæÿt×! èxÈ?vºžŸu< ªrMq0&P3ëWb$Ë<8‘ý* pûƒ¯ÒŸ¸ìëÆ;ý*æ|æ¨EÓrx N>•¤å’ÞÝA8ò‡ò¬’Ò&b a8éZWD#íkü«¢‡Ä½QMŒì—²àä³t¬YÈÙ]yô5 ¯Ío>ð9æ¯Äw1flaºcXÊWl¢ÉrÇqéZº›^Þù˜ýÊ}âGSè*ž—¦Ï«N#BRpòc]–‘lcÒàXö®g¶N9¬ms]‚Èì´)b1K`˜ŽÞsÿèFŸd’™F Yuô&h’™DlHœïE€[e uuÎáŸÊ–5ø8!•Gå½¹Ê0$©éì)ќ߿û ššr’N#^p9üzÕ¤{xõ8 ¸ç§"²acý±t2áX¤ÔÐ\¤@2‘Ú’ÓPܽ­Þ}ƒYº’ùY·œ~µ¥-ތۈ;Q÷gñ«)E·¼`¿u׊ϖÕ,tØÄ.\L„œûÔFܪk©º”«MÆ}ätül#¥SÓqö ~3û¥þT±MçBsÁ±ªúqaalsÿ,—ùV¦Ý9Õ!¾ãÐûÒEr\\gvƒUl‹žäή$œƒÕéÜDÿ)ÔÛ®cùÔ7;Mü^€ŸÒ£Yß¹Ï!@¤[íÑäó°š­¯O縸%CΊØ;ý+®ñ íÒ&ù¹ô®?J!n"cÓŠ¤KxžUÄ€ ¹ÉØôª3Ò9?­lj‘I$\“ò”DüvçšÇ”®Æ'®zÐy ÜL‘ü U»›ƒ$s;vÿJ¥<éqýì –àâÎRzœŠé¡µÿ­Œ¦«J€œ“Š·jKª’g“š¨¬ˆ <Å8+Ö®Y9HF@ÆÞý«™š‘¤Éäi–ñª(5Žø©tùÊÛ¨Àà•ýk*¼…5±ùxÀn)Ö×7#+ÛIìxô'" ³JÑÈóyÏïœþlM>Ú]·WïåYÞL.lçùœœâ›ã ¹d{I†H*J8ÅkYŸ²ßM´±Ýƒó6M>k€oÎâFè±É÷Åe.¤¢í˜Ç*‚£øM=µ(>Ò®wŒ.3´Ð¡(üÍ´a^ O©hc(ªxè1X§U¶¹rä3´úÕɵ{Y< $é*çƒëIŽ.Îå fîIâgs¹À­¿ðôlAQ{qøV…®i,z•Ù`Å„^ÃŽµý£V’[¶à’zVRz(ÅЦÕ9Õ“Ôèbµ—xfòj¶•ý.Õƒã÷KÁÒ ´Ô ·Ž8àoÍÅ.•i¶ Ê™X”[Ú·Ðç-éöŒöÊC¯~¼Sm¬¥y'ÚPâCüCÖ2þnƒÌ]ØÏ^ÔûKÅß1ÿ-[¿^iè+‰´Âþ}°‚@œqPIÈÔ€0>áGõ*܃{sèvÒݨÔC dE§4YÌ? SJ“̔ԌW'¦àȤBk³ñ癡ùa‰ù‡zã´¸|ÅcЀ~'V$Ôh^ãCØIVVߟUW92‡<ä×Ug6Í6E+ÿ,eÁ>áÎê–rXÝÉ ™b­×ÔzÐ4p~ù|æ‹ Ùˆ<±üél”ȃԓL¹oÜ"ãø…tS~çÞg-Æ^^0xùkIX‹9ØB;VkÄÒÝ…S÷qœš·+ºY>!¸&¹ßc[;\ëW[œŒu✚´«± žZ±Æ€™ùoá4áá×#+u ¨Ðz›ÛR(åüjD×óÕGçXC׫þ®HÏ=š¥>Õ‡M¤{IF©·ý¼giü H¾ rcÒ¹öÑutêðzC§jê?ÕÊÑdfËkhÚœr0#÷eOõ|kvî„KcŒ­r& N'PÖÏ’p3sZzE¦¡qv"{R>©ŠNÉ\¨¦Ý‘Øè°É…î òad á}ë’½/>ØmÔ¹?(É­›¹¤X~Ï#‘œØš§áÆÚ²Ý,0ƒ¸šŠq’¼¥Ô‰Ê3Ÿ,6FŠO¦”U›ÉÞx¥WÑòE¨P8 ¨®WSÔ]ɱ\“Ö© å=céèkD9hìwK—³jù} ˆÛiª6¢. Ï \X»?q©âêî6=±N¹֭¦ž®Ø·7'i¦ÖÑdߺD'å9®OípžŠÙ£íñ¯GaëÖBæ·‰ Ž+d‘Ø“Ž[5Ÿ¢™A3Ïû©Ï)¼RRXvlÕ­=VÚá8b9ÕB6D™¶p,Oÿ šÎñj³j3ËŸ+xN;ž¸­Qc[°OÌQ×?F"“YØÚ ÄŒyöÒ¯JàíHHÐ’G_ºj;ŽLA½œT—¡€/ ¸*³`Þ`FzÕ©Ú6%Ç[ŽÞRéÌr”üzÒÜ;U•ëŠÀk‚G¯jl¤<ƒw£©w{§™4ᡌí~ê)Ðip'Jð÷µM “=Âo`„«lŒã­,pÿ¥\ ç;OéY”@º]±¹™L ôþTÔÓ->ØèлAÎzU…R/9å3Å*ô³Ž†0ZbiV†ób£ò÷`9ëšyÒã ŠòS8 zÓ”“~ý^?Z•™–ö0xùH F}öœêËÊ»¥ÇÞö­&mx<¹$šW!cr7ÎÔx–̓`‰sÏÐÖ‡†n.òIÌ%Â>c'¦j*[—ÞØ¸ùn]×4´Ë%iegžcÉÍb½´ú`”$=yëõ«>$¿šòìùÄîŒ`¨è+ íiŽhÛ$Æ3ëŽ+>YI¦¶7uiª<‘ÜÓžÖáayq“‚¹¨Ÿrñ†ia$Œbµ.9²“ýÃü¨‹#€+ å0¡Ó¤ž(äh­Xº†ÿT;ÒE¦™cÜlíÎOM V½Šÿ Áÿ\Çò¥±RmÔž3Í1‰¥ ƒí°·!X½Ž”Á¤BÓ:¶˜I\²CÜgÖº )8ÿZøüÍ$Rº¸oV_åLFöÎ+[¨ŒVrA‘ÕŽsU ÌÌFÖò­J~× AÚÂ°ç •'Ôf˜ŽŠL›5ºßï*khR÷Q‚ÌrÞ1ö=i–Ëæ[D‡•%Y3ShøÝ´j>äÎÁ½zÐ ªY q,xÌÑLQˆôÿõÖZŸô¦+’ÙÀ®ZIZâ[Œ~îy§àÕË[±726 <(c§$óÍ 3œsÐÔvmºa»‘ZºE¤wº‘I—r`’3Šq‹nÈR’JìÿÙÿâ°ICC_PROFILE lcms0mntrRGB XYZ ç acspAPPLöÖÓ-lcms desc @cprt`6wtpt˜chad¬,rXYZØbXYZìgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ öÖÓ-sf32 BÞÿÿó%“ýÿÿû¡ÿÿý¢ÜÀnXYZ o 8õXYZ $Ÿ„¶ÄXYZ b—·‡Ùparaffò§ YÐ [chrm£×T|LÍ™š&g\mluc enUSGIMPmluc enUSsRGBÿÛC     ÿÛC   ÿÂà¨ÿÄÿÄÿÚ ê~_t¡  Ð7'&æF €€h IÂÅéŸ4Ö-Ók1@í}7ä÷ ¥‚pÔjÅ?m<&¹¬.™áu“S¬^““ôÇ‘è³ÕŽá| æ !‚ÔWœšF¯ŸLueè‰$ô'4©óœ ôTOgBó~‚y½__ÌÎ"A£tμ‚r®{¥{[«nš¹ìæÕž'œtb`SËçMƒ3-¶¥ºó|Ñ3Nï –ç*Þ;|Æ=¹÷‡6sH×;ßž]G^wB+ë<[eng©Úá¹è²H°®ž˜¸z𾟑h´mÚ‰®y¾'"bõäOVWü½Z/ZuIq¦— ãkoËyÞ{îK#·ˆ¶ŠA Ì6øŠa§Nsub'ÝGƒªŠä¤hb¤2çŠêýîOAwòà`¬€^emN_\ä ”çêÌNŒcâÛuÉЭ“ÐË{d±²ç÷«º¹½:€/«*ý&׃BòßVžŒcÀŒœÚÀœþˆ›rÙ^JÔô¢(Î3qV8O߀˜Õ-ì3ÛpÇ!¡0 ‚€ÇµìXmæ¹j1xyªŒÍÈ)Ðý.`ø¶³ÅõÜöxJ @ 3Õæ:OPîï6fHÒ4*2mdZ,1ˆ_Wš«Îè·Ìé¹iáÌ q„*­y»2}—WœÌ{Q2&³-Vë9Œ‰éò×ùÝ¥¢þ„Ðs$Uk:n8f“º+X¬”H zE2ÞŒl™]á¸Z ²c¬áæ¤ ¼/y·ž¶­‰§ìäMj1A@ ðÐ2™ìã-áXa³'z]ynµ†4(+Xö¨YªdAÊ3-uÇÿÄ+!"$#423%&15DÿÚkì9:Qï¨ÇUÇ;–ÂOùIÏŸ.ôû}cÿ ñR|ŸZ[A|zÖ5!í¦,÷•1í~Ù¯‘1åþjΩà§ü¥ì_vß}¯°ï×cë‡øãþºÛÖ½apî¶œt‹¾Û˜è'J=õ_ÈGu1³®?:âýÇ>m¾û_aßàÜ訿š?]OßÕýH 9s=¬–*<´ëŒõmeÿ¯’1ìFà/:‡Ñi‹Ru(§ý}æ¾Ãóã»:%_úGë)ǪçÌ_ë4ooF‡¶¹Iܾb`™—§Äs¹8ÀFú¤ŒøØNÜz–ùÇ?èÚÞs3ä?:ƒ#oóâÜ#o;vú¢Ñ/ó`+òçnPž&B›ßoÈõSóà’w [ê¶>tA+|›h˜-óÖþ}¯æžß3s¸oßTù±Âð‘4r½HX÷ƒMºÎVúãËOùZ>W©KŠD^dß%œ31Š#J¡i¡©š¥j+[Jƒ¢íߺÖÐÑ}´Ý·5Žíí},¾gâWõZz­=øÙïºë^ÔEë_N¡G(¢lÆ@eßqîÆP ˜´mµ¾KÎÐÚwe…zWÓ¶(éäçJ'<+=Ë`t>>~}|ÌΛñˆCbñÞÊùv9I«j,,¥¨#\ºUXÉÞwÎý,[|s}®È¦pŒ)sâ—zÑe_šÝ|†Ò#=rQQø£‘¯¹3=ý5Ìv/}üÜ2 Œ¿0uCq~QAÔv‹Å¦t/wmT\}è}˜BØ´[¡Ãö)>]gÍZty“òM­ÌOO«ìÑi½lË4ˆ±Jåïs.Çð$éQ[BZßí©W¿@ÛmpíÇ‚(gʼÚýÿþÕÿôr*ÍÉcEZ9–J™)…¨©Å9[ù&ýZŸ…‰øƒ=Ó®þáß–1¬ b*ôŸ0s£Õ×G5ó1À“富£uÚ"“•â(Zæ§A^T–xfu£SãŠvÖÓÅç Ê6(Ó "?R0oÑÉcc#n…voænóCF\€ñ•ø.ã5S‚6•«°vg¼³Á§£“ã õµ¢8·ýô¬É-½ Év\ŽeWíîïo0w­ µ ññÍvš;aÑEÖ÷ËÓN]Ø·ÈYê^¶ŠŠÝ··S5ôšKÊðáíh$rø+F­¬šÏSÜUáþFO·ÑåUsq”"€±ZÜÆ¤…®â’5È’=˜g‹Zxbt ÑÚÄùK0RpÜÆÈs]4ü„Ÿ”}ÙR9Êmt ËPK#˜jWy‡NaD=æ÷d@*¬)âg†gA//¸ŠW¡X›M‰ÀP%$µ·ù¥„óT¿´Ä‚߯8¯( Ì2†æCs¡y™uŠ4i-nÏÄ9ÚÚÕî¶é㥡f£Q1 Ž xã˜5É—sÛ2lÏ Ìo¾œÃZ.BÅjFtçz€ºã‘6–žŒþƒ$í¯wîá,5ØðX§ÀämÅzՊϽv›‡žµ=Ñiíœ4ůé~…&²cWçõ–:vÝ¡ò®ÜîšÎ“[EF´í¢öñwó`š&kê i 6ŸpÏx™X§âÄ¥hÎÙ½éNq„>yHfëÄ·ê€rV{rbÚ&:’'‰Ÿ“¼{HŠr`sA^’(µvZaªÞ #5 -I°´=áÉ“3î÷ØÍInvlk0~¨™¹‚ ÷†à‚îYÐzÄAI^W—of8÷âž*ÞœKQ2³<û‘¾^=P¢œ±+1—4GåТB|?¸Pï!AP»OÀ)nm!¯Q¿eÊ+C^@G+«««« UÓðcƒ lSËÌò£Nݼ‹¯¼$€†ðmeÄ¡‡hmXø>R¹]¹I·”ñ‰ÿ+ôÛˆEò…{]¡´ê‚”—ÜK´ÜRø‚t(q•Š ÆJRz8Q/·¤ç`– v×tív»]«+SâvÊßéûE]s¤"8|Sbú±]®auÞy.!HÉùœo¥5ã…ý¶P¢ŽéH£—‡£Œ¼/Éw,®íLvVBbîϲ]ÛÃË-°º»»ì}ÛË¡wghulYYv.ìû.íá廳´:º—v}»K»y±wgØÈut+)3SycllÔÍLË2ʳXÃ*F¤~ä|—UO ’ôVwðÐê–”A<··,‹çdå~iÛ‚_Ž kÉ©Žº0Ì3 Ã0Åæ)gʪyll—.ßIMå‰óËe8ñöW©#Ë`Ű$K)6|FÝ.Eùn[)õL©÷Mnð}Q‚)IÊ«·„`K!ÜGÙͳ±òK M!SQý¨ðÿèçÂ9ÉÆçÂÉN†j}i¶ 0-ŸVV\o­ªQÓÍÅpR¨ê·›gþ J‘¤¾á/©¢= ²ú½8j?ÿÄB !"1AQaqr±2Bs¡²Á#Rb‚‘Ñ3CcÂ$S’¢³ð4“áÃÿÚ?ÀÏ꟔GõYÅH¥Ý)ºØ°mÕ7XTûæ›Õ!`]iÿ×ÄßRn•èOë¤ñ•†õ£ÀÕ‚ %ÞHvžêQ t«þü–r—=¡`±Ò •ƒŸÕò(j’3ýáaC OآܳÍOÖ>ë<Ó:³Ä, ­?ãzø›â ]Ò™°#ÖIã+ ×7ìjÁ}Ûg¹>]‡yú#¬UWÜ'Ši='Ô° ròUQõ¬âŸª‡½a-Ó‡rÚ Õ7‚Áûx•„užAM¸ÏܙջˆXZ|_.LêÝÄ,¬>/ž §?¦î&vñSu…D=ÖøŠé¯rs²æXt®8Å¡´Ñx^‹Q=é­–©5^§Xî kVM¼¼ïXgZ< SõQñzgVî!`›çÂPë#ñ…„õNáöE±IÖ;ŠM‘â@ç-­4â§Èü¦ÅŒU‡œ•7¼ý¡bý6eÖSÀ¿×Òý«êÏíaEº4Fr]Å çqXNðð…7TÎ.LêÝÄ,xøJnXÏS,+*‚þ€N¿óâ(äÍòM{²€i¨\ˆóÑÞ¦péÏõú¯GGs¬¶9Úes“éÊdþc“o–ñü¡y2T°ë„ÓY?ì*µ“œáø‡J$ÛçÏ*Qu?0§6Ë©`Äv´ÆØu ÚF½jG^oõŽÑµZ„r~óœJ°æµúˆD²  8Ñ2ÔXÔ¾¤ª¾MNržØ™e äøB9:C¼&dÿ“MÔ#ÚÜÕWÉqu5K#…l±æõµªî =ˆm ˜m\!çåEèØ½¹‰ïzjjócœ×²‹ÒRšz¸3j¥ƒ‚„hmÚx£¾ÿOßw6©\¦Øß5ðy¨Üã@ëþI®{„La«mt“]\™(¬©4±r ¥ ©¯È÷xUu ZeŸfWšì?Eèhòãdø×§&elòDµËÒ¯—1ê¡´CQ#½8~£üEIÖ;Š—|¬+¯r›qœ\†çškMˆGn­n¤Çá ÅÑžŠ VªV•¤"£Ý swâ¿Â˜E9í˼S þCø•èðEDq‡S²«Ó2Š–4|Ôï%´tá£ä¶9ü§ ˆ©:Ç)úÏ °‘¢OÚמ?«o­g°ióR:Z8ºëJ½Ì™Wz¶åo´a»B{KÅ­ å¯â™±I¦ÛÏrn5–‡.N4½ÕUö0_þky'vµCí:b{Çê;Š”~¡Sy…uŸµ« ÞûBÃzß Wæ¡'%ê³pŠÔ÷–ª)ZQ†‡Z/µR¢1È^é+Êhª*;)j¦— ]þõRn0/H»ÑÜ¡ŽØÆ˜Ü°8‹FµÊ¥v˜ãæMü<ÜüÍÔü<÷¾¼Ït)½Fyðõ)}N}?(¬/ÕM{ü3¡W“—™ü²Ÿ!æ*Ÿ ¤NÅA™:7A•2WÉPVR;5eŒ²Ù9š@ ZÐÁ1Õé-T>ñJ^Ö÷/IßRqvÞ°VÂ*[W D@Ö^[™N\jmåøBÂ7î ~ÃܦØß50÷û–5·‚µúgˆ\ž•RyׄX̼*Ö‹"ÕI½‰~[Le ŠvÄêij͓赙äç”^ƒÔ‡*Â]µKÐ×qú)†¦ž*aî0ø”ºãg):¶ñrÃ~ H8 èÎTSÄÂ÷4Ð…bæÊÖ]žšÓcüÌîrƒ –@úšÃ âSbZsÍ_­7}¼PÞoˆ)7J®ÅpÿÛÕkž´@‘ù¥"67‘mo/¥ôBq s#BqÒÁÄýTšão®1Ä£­žhîy¬+Ù²Ê÷¦I!«X.ÕÆèµúDÕįâ†\mÊ7F_Ìõg7þ(^ãY ¿äS7‚nûx©w mÚs&æ'ê²Ñ0».Z'`m’ÌOÊË#:û»p€Ö·­äÚ|“5°ñ:„¶Å*…“ùf£´&ëaâu°ñTé:òƒF4®æ3JŠ7sS•‘Úó×V²^™…á&Ó{õC•5 ›ì¨w¼ŠfòfðRvù”NÕšÒq‰­êÝªŠ¦;QU´T6¹Õ *#î¸pM”œ`Ò~Iεk{rh ï2XšI+ Ã¥8vM'0N§k^ãxª@ÏZd +êâãSØTÿ´¨÷“7‚—bnÏ%ßÅ;aQT‹rŒS5é¶.g½™3Õ¸eàW&l“–­¼¨ÞCœke¢¥Dy9FQ|eFhü‡ Sôä‡GZdqß&{‘„>¶Ÿh…ƒ`×rÀó¡EÉÈÚÖóØ¢õƒ.Jvó´ê*Ÿ¾r›j9©ETùÝp4²<Ðé6ÿˆ&×Ûoÿ’kzE`®“˜$XQ„â¸Õ`Îqÿl•ÿí*Þ §‚€p>J³ž ÛÊFÒ®¥È­´TÌMS·Ñ9oÎ< †Y%€gÐ¥uMÌ%8×"m\ÛÞÚИM9Ã:ÁräsSÚlÛª4½Ž¥TlŒP ìX>ÿí*.Õj‹µD3Ðù/‰ áâYkVµß0 Õ|Õ¢* 7*‹… ¢k+Т7Ô:?%!÷ N­nÓµ øÍñœöáMðO ü³ŒV #¯}hJÁ®‰£Ýr‹J‹-(s¨€s€²ï%$7±ÙF¶¦zËV¡výo9y¿&13oØÝªMÕsÈøÕžYÍ¥ˆ³ïPŠsÕ>òõO¼»úVüE}g±’â¿ä ˆ<Õ„¼ ß%r(ãµZæLƈÙuxýSO«º¹ c¹1@I1܆@zcRi0:¤\à£aeùÕ.O{T¡×€Ç»Àš«Jb(­shˆ¦\Šøý%~ þ’±˜W5s;Ðç ܬ‡½ 6Ù¼¬"ws£æ¹POÜ®•¿%ÏbçF³v& 94'Ôd¯—Ñç-s¸8¶Ü™M £*w¬–çS*•}jP.kf¼ÐŒ¿x­j†0«iŽÛµfÚoCZ|PÝ1·’ƒ¦ :C[!G6šûìä½5¿u©?¨¬œÂêÝ*³baur‚©Iëº-ÊÝè•!}FvÑKïÀ©4ÔÓþÅ÷’w°¹t«¾É…«­T|‚7ôÝÅRºB:ãòUN¿;|A9ÎŽÛc ª®ºÅÖt(œqQì*=×y/…?q¾jB=–Ž)›™ö 4Š'¼å¨ÿ#” h½ÓÈþ)»~ÀÃ[+ÿÄ&!1AQaq¡‘±ÁðÑáñÿÚ?!¹Ñ zpäû‹7ù&#õSµ‰dv׉㸙Ŕ!Ú˜ëðb?É´V¬{º#ûï.×µ¥ZUž§êMrh÷™%ŸbÈ|ž9ÕžÀ,æz@òs'ßÙ#¿~¸,:¢7vk¯åQ·Äë+\#8Çø¶˜áVhàœ‡êo¸Œ`›ßòN¥‹N0¤¯ öšš+ìkæ[˜\Ë/CÜŸs]ÿgî™G]f×+o¶zdKîX?ÅÿèÏ7!`ãá˜GÄ÷I-O/â•o9ã¿øŒsüæd¹²¿¹k­¡®ð>aV§QÄÛRîYÆyBûsq)x^O€iì!šÇGÍ÷0Tç.”ŠŒþ•'”uÛŸ)å79úà¨,KØi+èoÙÁ ÖŽêÒ+¥%Èõ& ìà”§ØÜ=-cºWoü!s á5|UžXº!ƒëN…çÿBe.·'ò»¥ž©y–ýÚ0lýDߣ7¬4ËÄ©_O5YÞ@ìBÖüT³{ l¾·x²ÛV‡·0øŽ7 õ5—ãp+nŒì>óΘæÊU5ó/ê§QßÂix¤³µùËúG+€‡]Ÿ´Óôü¦K¥ "k(­ÆÐèÈsö†×ªó;m~&w›|Á·“ª¿_RÌ™F-luû‰X;©]ɼX„çéN?hj²ûA×Å)A2ýÙðœBŸf&'ƒó˜=Óǥ|ÉyðüÅR{¤/jvL‚8%3/“(kª°j·Šÿº!-ø9ÄŠAeìÛÒh†m¬B\¶ ªÛù+ÒÐâ€ý‰u·En:Ñ‹GŽª'î Oð -ÆéAÐñò_ÜÅp¾IûޝÜÃu¦³ó¦¢ˆrÒ ”ý5ÿ%p×!§Ð–¤f}S0#@Ö63~ð¥å¼EG£â;=½6™À‰ŸE¹À¦t}ÀÔu¹ú[1è1_¨t®v'L<­û•œ\ýËùOÉ÷ Å$»ÿú”‡|µ|”y >“u¥A¯I‚ôš *àr}8Qæbv(.Y‡ùXÔWÅG@õ¢i/¬ZŽž«Óçæ]P¬jËzÆpÝuØ~ 0 †lÑÌYv¼aœK»+êbp| 0Å}T m9™«ÕSN×§˜ëµSê5éhk 0×#N¨¢›5—fRÍ+§ßˆ.•îÍ}!0Te&ìLiOB(%nwzÇ «l^­B·MC‰œšÃ²që `(~s3ÚxΦD<Ú@õMñ~“v¾¥pP¡inY¹+ËÔõúއ—â}Lo[óÒ[Õ‰ÞJý^®eN Æ>­hˆø¯eáºÕÝxîÄj娫&F¥ ”ÓcT÷´yžªÄbºJº?e­5g]¾ÑlÒÚîc"7ŽÓ ÆEm°úÌŒßÄH¯!åi~ÓüJ?hÿÚ -ùðÿHh:?/ä»1ià 4ùýžðÙ3üM'¡wøµX®Šƒø,”Ô¯ª‹â’ž¡û”á¼C^Gn,Iœ£;:Æà ï%úåÒg|Hê,ÐÏðXøþÐXoø¬±Áý&K‡ÂþÏv~_ØcNJAãV¿PšÐû[ï.½Ò½B£:J´×V˜Û8ê—d—©0ÅCüÕ•þLB¤×lÙé )×\©ÖëpÖðéZÀ'jÎÊ^“3€ ùMcOúü„‡7¸K›(RWô•žþô™­ÑýšBfy•?>T~u ‚ègó YSêR õÔzyš=€†•érˆl½¹`~îWßE›«ãü”±Á¬+ô‰¯ä‡Í U†°BÜÄé?Ãõƒx4•c£¹¥É?eý@CÖu¹zW˜·¦kßÇlÆË×Ëœâÿ¼Pb€¿išåPà0TØI »ˆþ y2¹Ê!~Þ¨N´¤ꋌÍâTPØkxSiMq/ˆO›ÍÔ¨´ÆÉ±¥Ê‚«+‰Ñ‚.ݺLváÏCùÔ Êåûdl˯Mª¼KŸÖM[ûÃ<•».R‡\ßrmÐ×òæ/;ý‘¾sÑšMŸ´Àgk^ð7þ³ 5º~ÁÒ>I‰¶ú€ õXŠ=ËW<¹v[ Ò/Ò£)Â=³Êk÷¼5ÔHöSÉa–$¾óŒåò‰æ~IÕ§g)[ÖcŒ©Û#ާÔT憥•zï.Ôè­ÅÆä€ :õ¹bÖHÅv±Qn-nùEéFu¬Ï8ÈJ²5ExQE[•§š¢Å¥›ÐÒpiCkÔ°ƒ-T“­[(JÝ[ÎP`ºr ¡z²X<2Ù× }b_·Ñ4¡ÈIp=WTÐè hí)‰#©!¡6µyõEÃAò€§‡îO¹©ƒ›ê‰OU}Œ|±•ÉØ¤‰J£R8b5޹#4õAZ£X`ç¹Õ`^©±0‘S¶Ñ(Â9>å@i_È€˜µºné/‡7Ÿk—IY.ŸRïNª]ZzB  Ásupk³(cY’ÚhDSlnÔºh{­?ŒRÆàÒ‹ƒâUp7Ïxä:¨ß[ðÊM^&íÝâjK³ùëXïáð•Æú.]CÞP² óo á ¿Q*åï6³]°)Ø©€~ÇOOå˜ xEB´zKÃAcª‰UÔ|Ê™SbêfÆtOäÃEÕ1«ŠñÞeà²ûzKôBì~Œ"Ô1êKOa´ V ÉÝA¨^ºßõG®Öq/E¸aÞ[K­&ÎÑÒév•§i ¼MRRQÄfhq¨ýLXÈÒ]„Bs)4nk#Ž)¿bØË€ÆvÍvÍ…EÕÍrMãÄÇ:þMÇøÊ©×¤5ñq݈AmÍÚ-å n”©¡2[®š’Úîß–]éH¡[6•÷RoK¼êp«Zæ#—¯ùZ¯÷‰~é­ÌìÿاÕÞåïÖg¢øF3þaò‘¡SêiÖöþÎi믙ÙçœA´X×l°¨t}7ì§OÛIHµ\qŸŠ™¶Ž„É:®ÉX¾©’ŸšŠ AÓf¾£Ãg¼T>¸x1q†®7j2¶ðÛý˜Ò€#@á»æBY²¦ì¢‹9ÐXœÉÖf üJ–…ét'Ülm¡D~Œê¬ -iTèÌ&ÿµ*hÞ>ý¦Î6Ö*ôwë}%¶%a¾w½!}ñ»ÖýƃZ×ÙIjôα{•4ù"lný ÐPfšÿ²çµiªÙ«¶-栯ä&‹¿ºlÒ°}áÖªû&Â(ñî5™•ùâó[݉°#w´—ã&t,øŒ––Ÿv ¼j¼Žj§ÿÚ ö¨ô•P {ṎçÖ#W½mgì”´"ðý2™‚:‚5 8‡SHOŒG?žìý§wå°4¹ñƒþØ´F>Êa©À?õ¨{LF„ŠÁù›"+ŸЇ ,|<8gÙ±RÀ_¢•lÁ"÷|ßô†@Öq%»KHSš‡^â¾ø+›D,=ý"{Yœ­:%-ôÁ…-lL织ôñUC»Õ D²×Ñ,á–@lJb$ó~ëÐNʰxó=xÁ>qвš†Nþâ&y2IØH\¥–;nhÄfÜDoÿÄ%!1AQaq ‘¡ÁÑð±ÿÚ?Ò:1a€…¯†‘à°S¶u4t>Æ„ŽƒÑ°Õ Çaèõ èZ΢wÃÇÄ™FƒSCÃìì=£Ò»(аï„Ë£#Æ<”Y#!k;cÑê0a“X#bÁ»:C!‚S:0Œp-gaèõ4 ‹¶Gˆ#AრYØìnÐÒ ŒÂhí}F8•¥"bÈŒ²F]™ U¢ø<¥ hNV…J‚èv¾CѪ¡éÑ„a0dí¨?xdбbQ.s<fGÈnÑdŒŒá—ñ¸vH$Ld•’o…†ƒÏïý˜õ!鑃&8w÷;,t'Ë…†ÃÁè”Æ¬jÌ0.ŬïïÂdȸalÌŠw! ,<;÷ÔjÎÌ ³°µ‹Y·Â6!p*V)ß"§6¤ÿÓ4GƒVc‘=еBƒtiÑ·D ÑÆð• oNcØv,KÇ¿ÈCû5ˆfÚØíð…¡lqv•´K‰Å¬ZÎØ´h4¤Ñ vÅäo'KÍoÈš—CÁàɱ*=àB´ôßcÊáþ†Ðµ‹X´-p%D*K[Œ.XÍý/Iþ‘ღ7Êu•ú½Ì}U"hµ×¬ðì\‰†˜‚}\mõ౎<™ÐÀðWÌhcf™ìḊÕ~Fà5:õñ³,ÄÈØšemúˆ¤»]Q¤1¢žN棲m$†„,#v†kÄ5%'¾ÝŽ¡×Õ±8Y/$%±ÓC赚rðu ò)¸1'!¹"ðQÈìBHH”© tSRž{d¦ *Æ’¬dàoàºãY¿¨©9Hx`¬>‡«ƒ³ÓýàÇ¡Óì2¡ *†•B•9cóð|ñðm±¡(²FðN\’i4=ëWÌwÙ©CIJ±¥XÝ)¨åàÐÏÐH dÒi c"¾DUC~ƒŒtÆøZå–{D„ ¸¢h‘»Hƒ{'bLŦÏ дgà DE ©çYôÎ×ȃ6?Çì±¶K$H“…ÇÜ xB#ÔŽˆ!–)•"áò~ô=Õà±Ë,¾À´ã°È¬ž?C؆Y/‰$І3X”ß÷¨‰Œm§‰-ÐÜòŒ§ÈÇuÄC/†ƒ=‰oì¤KY´Anðâ d|J&É’ !¢ÌhÍßMáò†7b™ÿÄ%1!AQaqÑ ‘¡Áð±ÿÚ?Ñ·mQ²wlDMhÜîÚt]­.î–÷¢í»ÿ-Pól¶OS¢ím£sm:.×§ˆ†-ߦ3ˆ»·}ÛdÕŽ!Ý´l™Üè ïÁË·þíq²vÛ®—Pdq!ÂÈÃâ ŒxÙµÇýýÇîÔ‹»d9m®“uúºxqf Ÿ›/:ž°á¯Äµò6Ö×Iêt]~¼11'VAÅ•Ëc¼NíQhˆÔ¼].—IÑ:-Oþ«`†l™ìYù´"ëÁÌeÄë̇“Àäž%ÌÙdlñ²Ææ\, Ã,ö؃è'¨1áW‚Ô.xŠ%fòXGÑõ#›ˆÄñž ³ÄL’faò¥‘|~£Öd¨°Ç>Dz»8€^°Æ£ `Ÿ%ÍÌé÷&0 瘉Œ;²&ÂÈ'¨7Í·ÇŽ£ÆþŠÊÙ$ã›7 îÓ1:Ž0Ÿ8xju‹‡«ŒZlq»k3'Ž'ðÛm’1îÌWÕô²z²z„Ë’|œÜs¸²{³ž3àÏð(åÎ+Áí –}¬žî;‰ÞlÝG%ÝÝߌÅrØ­LN ºGƒøC½ÿÄ&!1AQaq‘¡Á±ÑðáñÿÚ?B´zCûF”b)‰A2¾ó±"iÝòFò>Nkü¹duÏ‘ýÁyhù?¦F\?ø0‘)Ï#}±®ÿgdaž?ŒYÌ€Ö®À¤bå%=w5¬‘fZzï÷Ö„Ä«@ˆÙ~˜º0$ Q‡RãŒp‹v¶õŽ2Ì¥– ½ã!7_ħèrŽ”orþ3™¤ãºss÷FÚ£Ó–ÕÜ0½·Þ7!‡ýÞùÛÄr'”HëõàñKéd?WŸ¼ ç!Üd BND¶¥®ãÈE”‰ª‰ßy"CdA·=ð~bˆB¥ö×¶)8Y"8g<Ì–’à ÊìüäîÄHÇï¿Øuœ9xy,•èc̘®äë¶Ü|`üÂy¾W÷$·MóýXò§>ÿ~¹¡Ãò°¿^£GôÇ-ü“~âiKñdY/TöXÅJâWÑ\N"ðQ vOÿ\+æX‘ª}âp»@ <ëЃ+8pˆ ÿ9rC+ԧщ«øÀy–g2ýðÃÆ €üÊuž.³€ò†ùþœ¨œ0&vO÷8ã%~K>2޲ôþ12;_‘1*äPö‡æ9åÑBäGayèKr%ô›¿õÄfI"&lN9®1M‰Љ¼ÌüÞ<´ææ7ªMw†SÀÒ±|FF;øGñµ»XV $gœÈ=[#1DA~dÂßÝ_¸SÀ8Ÿép¤°@]Žmáë™7Ã9”3 h¬¹m•áN©Ùà¿2,’ŸùÀºR9 "{2w›y%ÒR‰oâ a¶(B‰Yç…c¬a”Šƒ½É§ÁÏQ\Ì¥Õd爵DŸÆÄ\ŠÂm¯œ9½5{æLÏâD:þo›;âüÈ·_Ñý¸>}Ù‹Ô6Ìqþã†BE`“ì é’xiI–GÞ¬ž™æb&RâJøæ£>Œ‰©ÄÌÁ¼}r4íÅ ©™O—X³`NˆE’ÕÏ}äw±”N¸O6qŠ`‡ q½¥·ïXÊG%:ñ‘°BE,}æ)]ø€¨Ð²’fŠ3NÖ0e†¡$D&-ÛmããLÇ£P «y¾ðáLÀÐgi{ïDÐ$DÊM)MÆÙƒàÖq«f¨˜fæ¢åH­4dI´‰ÃáФ ²RAãë@¾ò›1W™CÀ£Ug¡5ŠHDef3uÔнöÊxl‚e´½šçj04>}b{œ BÑi¨‚Ö•lâ‡(vÂß„FiÔ€vôAzÝ…ŒU…•9°ûe½¡ùÇ[|Úc vJSó4˜ˆGüÈ?U_Ôeåýó ÖÙø˜"Ä+ñý¸‡ C꿘åår~lÇ«Jr`Š;EÅe¬¢xÃaÇc,U•a€Š@Yå?¹¾â o­b+ ˆá"trÚD²LÅ5jö "kÕÐ’¨jíÌW HtPîñ ­¤NBi¶Í`»Ì¦_ E¼Cúg‚z$Öm¾úŒ‚'é·î(¡þyÁ¶ÛýŸÌ?ÁÿY6ñ ¿ÓÙ˜£nç¶(Å"[¬¨’²¬8` Ÿ_ç ZéIÍ­Q¡×¾GI ™.cÓw¬4ÛQOi±Ñ,m’‹6ïBv͘¨ª"6a£¤ÍÝàC§ †lµî°Gr&–[ŽAâr`Ÿ¨LáLô2LxyϤʴ!uÀ²s¥ä_¸dȈž§äÀ;įæ@çKô~ã"ÍŽHçiJ‹GRC!‘®„G¬ãQpDOXfI?‰ñnyÚêúÄ®CLxdKHë!5žz¼]kx•HŸLx(ah&k“³ˆœ‹ufA³%sÎmQàmaHºcÁ‹É¢3kOøÓÛ! LÂgú4¼ gùýdèùÄ…û_ÑKÐOìÂ1Ú¥Ÿ¸C(E¡2®] 3ýmÈŒlÑ¡mŸ8d!0G`бI˜ÕwƒÚPXvtÿ8)ý1°Ð§ÃŸZߎ²U¨á$Ò4@ž&xʱ1òŒ^#ÜV˜ÆpÅØ x›2D0ƒ±bÙ`OuŒ|’­–°ÎDaµ#逄Ìé¼÷ÂI G‘ý§E/Î(ÅÇÛ8 æãû2Ð&ô°À¡¤3‡ ï…$Ío£urÿnHh=ò*’#äÉ·R—84s7°BBåæðãð©÷‰÷Ãhc fT..JoÕXƒ;l nÑ+n "gy”M„]3žõU1'­ˆ#K ­Ú‘’³Z‰)ÏfÓ$Ïì›HÄUÃn³~©™&3ÞÇÑ2gäž“n}&¡ )Þ"Žþƒ¼i<—.`k"­‘SøÜáÆ€Ú9ÆÖÄa<àÁ³pf£­v»3…‹Ã€0‹ ÖPñجêEB H£î`¢€í&@¬€e\¡·ÒÆ›Ø ‘â»çzÈPž„W_[wˆQ}Œÿóx¾ËÖÚ&Ç6!¤Ò2]äá¨a-ö8uC]Ïú=²üýaŒUÏçÇ+gÉbÚ±žç8V)˃aDd«úûãa ‰7y4ñnI3Ì›”÷tcÉ„khvˆn0nA ,­·<„šŒiä'ãž_1?rIÒý`’RÛƒë¢B¸r ƒ0‚± ^-7“(©D;·jŸ]桚ï,IIé—„øPeG´r¬â¡Ø[¤l«+Œ¨s΀àPÚ£Ö ; Ü–Óļ“ÿ&,’FñˆR2Í|£øÅD—5Ø&â={#it‰ >£ò\”’ 8S…RKã›r$YÆ ~5`0Î ÙÖ¢]5 4a¢­£~gå|ܦÿ´`¬JE˜^_ƒÙ ´žE6³þ¬­s§«>õ†FLRpx¾0¦"Ô+Öð‰b"¤Ô ²w“:éé•Ô­þ5Áå‹‚\4ð¤ß×ÁK ˜+8e%‚ÅIê7‹èŽP¢GÏnB2‘çAÖµ‚H[7ê‡Íã™Ô, 7r&ž£»É(dG¿ðgRÿ~7ÇæÉüØådl´,•ÜR_œ´*BÁ’oZÕxÆb6 hˆäzŒAeIP·Û GKHZ£ÜbR¬j ³ÌÏ3YRMšœ AdCÍÃk1ê&Fù`$ÙaHQÆ!a&ØÀA‚ *ËÐyÿ˜³ §nNºhžì©h ì„ú@¯O\l1ŒE+6‡¦õ…º¡Fe%"øÀÆ;_ö9xWñó3ÅØl1aö¾ÃÀ÷Œ0‘qu•§È¦W¯Ç"‘·pÑç ³‰f`VDã]N@r¯Ç¦”…ú&Z(XÛ£÷#…"~Äf;7‰þf+dñï¨t¢á4óOõe è–F¾%Ç!„ÂÙO|0æ0³ªœR[Ojx`säCôÀKÈ>OÌ0ª6Iûà}³ç"HÜ&ä÷0þP“iJ}“$^ck ¢/5Füƽz|`)1‹ a®ÃàÁBÊHH^p$ÞQ"žqaˆ¤-þë*¡4SWÙ(•—¡ûüâé$-Œ»lƒábÕh÷ÆQ´d‰ÜsXâð`hñó‡ŠBˆ%ðùȹÁŠ@%óg¾U´ý1 ‚™£]ÆÝEKåöÁ¢"$»˜uÖ'Ê&Ò¾þ0%eÂÂÓèk ØL…ô` èÂÅà¬@–‚KÆuÈZJõÆÚ(¯õ®\é‘„ºçˆƒ'LÂ>ý¨<‚2'Â×Ï81ÌI°$öß÷?"™ š”^rMûb‚Þ±¥jˆPH£1ùŽÖ"‰ñÿL8¢¼2wQ}a\HÙ$WIÈ+O¤Ø*u¤ÈþäA eš{-ƒ5©:ø$\ ö›iîÌä>Âã;Þ>¼dŽJÔűæû`Xà“µÃµXKAí<â ª¼#÷@ážAZPºgcR% 5íüde…NÁ2Óä=0aPtY©/¦M¢E¥µ|pc8”å ŠkIǾ§1QôB˜ÛàÙB×ÂB€³ƒ" IÐð¼>0Z‰hzA8‰Ãj*ѤÎܨ vo èA´³Þ=€ pT 5!÷“‡~H&O$`ù2`„YRçƒPÄ4À!5ËOÜuuÅë&wNKGŸE_¸÷Ém7&HžW¶Yà@gÀ9Ãæ€/ÅhšiyšÞ0ð€ôˆnû€A]ª 7×ÀÎâÂÈŸnnÔžž T-YnâOlî$â„–MP@Ú¼]˜tvœËÜÙ‰Ý@$òÊW…ÇN÷ˆ3âþN$ž “Á[ÌI¦hø„=®ò=¨&Âxþ) dó~1H…scÿ0–‡)1 ‰vS)ŸbF?†Š: ïŸÜÁ'ŠŒeã]û౸4š$yë %=OoyÒ4‰}ÅâöR‰(ˆ±Æ* 7t «­}àˆ¿;VÖäÞxÄ¥±†EÛÆZÀÅZ-è¥çpß †ÇŠjÁQ- hpCؾÙX€NC N¼cTò¨ïâ½ç#“Ëþs(dP b±xtd´¦rèÂ#¡îòr÷ÂG¢'¦u–ŠË@u8AÂ)3ïÞ(d& Ï¡&«œ†¥¢‘Ù¶õ‚W2ˆŽ)õÀ3}#Ó÷%@á_XT0ÖŒa Øáb­2Y„Üš—QJe„¹˜UâHDig€©j×Ò¨)2=^o%#”“£8BY™ìÁ±ä‘øÁ`(˜àdY’Õ¦¡Ö5bÌ/ù zEFd–k÷Iy5øLÂN$ÅOã’:4Pú¿L!À““SWÁAª¦@ eüªÖ*XjìÚ­Û€§#aÐÄ¡øØÛ͵ÿ\Òê›`ˆ+V?0Rå1 ]D½ŽðÇ$jÃÌ»ôÉÞ¢¨ñKRpdm:†,¿PKÔ¿ë$ƒ(÷ýNXhø;x‚0ò`3é ÙÉßœ0+ +…zú3lPSë¼j mŸg× ’EÞú€)iŽ3ÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/images/guitar.jpg000066400000000000000000000633451510465702400256660ustar00rootroot00000000000000ÿØÿàJFIFHHÿá¬ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:26:22 Ðè ¿nt0ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ¿"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?†ƒ(É­ y¹Æ:W-k{ÐZ±O¸ dÑFòÎHÓÖozÊŠcýïÖ¬,ÙëHf€“ÜÓÇ^¹ªK6H$8ÎhJÝö6V´íç àúÖR=*å¼Á’0i¡Rä}áùS˜ñË“øU8pwp}*Ù*@À9ª$‰>cŒÒI#Æ=iÄ`c¡¨Ëq’ã@‰ö8;ºŒ­Y BçøÖ\ŒüÜU«KĘìÜ7ØàÓÎâGÞýi3 9úv¥'ž8¨Øíï@à1úÔdr;ÒŒÀýiŽÀ }ñŠ«:H½:à÷«qüñŽ=«6êPËmUM+YÍóZª Rܶõ  ÆL¯£BÜqVbAsl.6 I7QPD Î*œ¶û¹Ú>¸¥}JÔg‹þúªÒë6*9¹ðæ€/iemæ<Œuçüý+JKö=«“oØE "Rþ!ñU—8güt/\~•”cŸå\Óx¦Ï°r}À©4Ýj-SQ†Ê&òÞR@géäWGp6©¬üŽ$Ò’)„3í­®r5‚’â¬Ç>;ÕXI S ƒZPÜ`šæ#¼©«)¨(þ*†‡s«Iò:Ôë"ú×-¬«Ó'ð©Æ®í÷br}”Ò°îtÂEëžiéu±øàzæ?´¯|¶íøŒS|íJCþ«oâ(°ªß‘Éj•u·?\שª˨ÿNò5û× }vGR\d¸÷Å4êŒfQù×#ý›rÿzåñô éLÏ4†Ck0E 8{àÕÄñ¤‰<~5Ã˧D¨~ù÷ÝV¼5á¶Ôï$i%³EÔ¤úS«}zÜ ‰ãý®µŸuâ‡Lˆ`w>¡I­ t»kvÙ J£ªíjxü¨¸XÓ̬‡ÀÊ; •^MG\›ƒ¼~ Vù€éKäîþ9¢ác›Hõ†;Œ¥~¯Oú£}ë¼÷t& ÃÀ÷¦˜FG=J.0xÿzóŸ÷MF4™™¾k¦?Eÿë×I±F^¼Ó6rÇžh¸¬sߨ¹<ÜJ~€Qý†„ó,ߘÿ Þ1·|PA :.ö#«HàTõÑ ’Œ~®keŠæ·Lb‹˜4{\ÿªZ•4Ø#`ñªÊrA«¡òyÏåC2ŽÿbÛ^Õ­dù¦ó“û²ëÖµÅp²âehÏ·5̼轪RM¼òxö¦˜®°ÛŽ1ÍgˆŽI~5³w”ãÖªÏñI2ÌëdPTÿÿZ†+d=F߸Œ? +<Áå¹ÛÈâšb°Ce]‚´àÓã8ýØ…KglX+cæµ’*[Š)b€à(…ZKUÏ¥\XWõžéÔàý1Wæå‰Ç#Õª”²gúS©Qœcqžµ!sŽM0ò~l{P"&#ŒÖ˜[ŽÔ³HªI#ïT¥¾@0ƒ?rÉ|rH«=ÈÁÀÍR{—n§õ¦ ðH¢â-}¡‰9)<Üœ‘P 2¸Hþf?Â9­;}(ð×þ¿ãJäN¤a¹Z òŸÝ¡'Ú®G§±Á‘±ì+A#P¨¡@ì(bO1Çkfý–ÿÑk’zTBPÃ;©êŠŒ§"EËsùÔ¦ÖÜiöarÇØTfy%áæ¬õFÈ£©¨£¶ûD ª½ÛÚ®Ák¼æL±÷­ UU1*®à U”L)ê„*@ƒ'8¤0]ÄÓÉ}Ù&‘@÷Á÷§gŠ@H ãµ(ãµ0+Á§S@*Œg¿z;ÓTŒsùg­8ݨ’tÇZ¡r3Æ h9 šÏ¼q·¾}sL ©c2J±ãï°_ÌשìXíÑ0ÕqÓµyŠyº½¢rs*ÿ:ôɰßLP)Ý åW5N@E=§Îà’A?C Ñ¥Â4™ $v´ NÊè‘l&›Ž=sÅ?û$A˜†ö¡± ‚H¤Þ»@NGåKƒÎqØRFP”e×Sž½Ð V”ÜÉÿ ÖUÆx«¹>e#ëÿ×ý+³ O­;k+€6“LÓ—±ç¥Ý‡ÚPïWm4¥ ÊÜz(þ¦»FCÔr)†2;PO+}LY-­ô«6a„cÀÀÉ&™n¤B —q<äÕÍZÉ®áC ½~‡áYȖϹY ²œ—¡éý)Ùu"Tà–¥[­rÎ×R†ÊF‘šSëŒ/¦jÞ¢ÑÙ[Grûö¹ÂœŒ~µRóI²½š)^ЇŒü¬­·ùT—Öfþ ƒjƒUùþTï.jK±jÖX.`ŽxÂíqœâ¦ûBE;+0 xQ“Ó®*µ»ZÛ¤²¬h0_þ½JP‘†‘ÈüèN¼Ç1ý¦ºûBO›‚H`2¥lið’¬\{õb6Ò&8UIëïV‚0L¥s •”•‘ÇÅhG8«‘ÀG=±NTŒþB¦P@Î(=AÑ  ˜1Æiª3ƒOèn­.н)2?Ȧ–úšw½H«óÅO›®q@A sŒ}(ó9È"©ù„JQ'{R𔓎Ò—vO<Õ7œätô§ý¤Ö€-HÃÕ—vÙœSå¸'8ÀJy2NE?G8×­ vlþ†»»«Åä‘éƒ^¥êAð¡"¶d»9ÆïΘ’̸'5JIûæ¨5É#–ýj›ŒH–^[ÉclÇ#¨ÏLæ´-|IsnTL¾rgž0@ö5Ι>´†aÓ4Êœ^­Ž%Žx’┑ASÔ Á$ç¶sXº%ÒC§ÛÅ#sÈ#ÆXŸåüêÌš²GrÐ@ÇÊÁºÓEÓmÇRéGãB¨!†zŽ¥WŠëÍ=êI“ œzÚ‚Æ66‘ѱÆ{ç£&;›‘# ÂBIµu9tùBäqœƒƒ\ÌÊŒY›w©Ëš¾†cÏîš^FóU›T‹8 ™ôÝÍqÂýÚö9TÞ2Œ$ÿL~u»q$Šð=º+nʅήM',2îhMXáOèjT¸wïXÒ=ÇÙðÑÄ[ª189ç·½^µn XÆ­5djÄIÔÕZV*Nf`…çü*u\õ¨²›Â³Ç &Ÿ•N¤~&ªç»aù#Ò†b¸ÜÏJ£¨»›FX›nO,Næªé¤º†œë.ó,'†n¬¦šÕ\ŽÆ›IÀËb«I8é¸þu JÄ9ÅE‚NqšC,ùÜwëÜÒ‰Žj¸ŽBrÔ‘Å+Àéš\È|¬œËòÓƒôàñP"3 çç¥ Êî'¢äÒçCä‘9f#€iyÿ‡?#œ¶p1ϸ£vcÈÏÞóèi{D?fǶàGµZWà€3ô§ÌÁ^,Œn@Næ§|ë“Ó‰z}—?ýŸ˜Í5’)&–_”m ¼u'ÿÕSK8,ÁC«»§lgùUWŒVRxÞ¹ù½š¤|›ÿSÿ´èçl9†RJñÞõ;mÜFÇóÿ _˜½¹SÀÈlŒ÷¦JÀH>h9üés0åCÜ•' ÒB¿€¦%\6>gïéŒQ*nwãlää0ÏøPc"`–GÇ_ojb²ìjèwn6‘Š”Ï9«32E¨ÂCÈÙÝÃvéÒ³ôP>Û€~bœ`q×Þ®]« Ûs´“¸ƒŽ{VØÎz=†ÎBpG v«åêàæBNy¬Ë8Éí8Ï¥hà¤}¹¦‰e}Ka±/Þ(Àñ\†©#-Œ… ïa´ïÅv7 ÉŠÁ²TãÅpÚÜŠ6nA8v8àþx§ÔÊ_¥Â’ê°•`U7HùGÊ¿Óò­½@È¢#;ÇÌÃ8ªZ6XИð±î*8à}kJö'šÎEU,Ç ÝNCM"¤¢S›¤L©çåÆO·<ÕëCòЍ4ù<¹T¼ŒÊ¿)8;¸þu§ec*Æ7¿Z‰˜‰+—!<Õ®Õq÷4úƒ³KWÜ¡q‘ºŸá5DØdiJŸ™…Xo´-âl#È “ŸZqþÍ!šÜ0fÁQƒÆp¥K¦Ï£U&Ÿðí•‹+X’¤µŠ>$ŒãæÉ ÇçÍj¼vï$2ÈŒW?tãÛ§J€Ø ÀžP~RÿÝôã¡ÿ ZṲ́Õî‰-§ìo<´!ÑUIÛŽ§×½Fëˆd `ìoåQÉ|>ž‰çHÛLÌ ˜9ëO3‚W`äfŽFìÐ9¥tÄ àÿº•G&EÃd`p=:³·älJ|q‚AÜx>´Õ6'Qc\¢ª‚N¦,$$„–O­k,¤|¹ ŸÂš¦K¨cùo±° <{wZAˆð6qøJØhñÉB? …Æ1Š~Ì^ÐÍ’; 0y>¤ÿZ†I9e,x}øù}sïïZŽrµdÝ.X’iò!{FkizDºÅ„²Dê#I¶ãŒ;qïSO¡É6é•ÛÆ»jÙðRy~•³‚ó±Î=€þ•5â’[Ñ>D'6r‡Ne#'$dŽ ô÷ö¢+(Lг‡ò·e¶(Ï_­lȤ°Ç…D!g”F¹%ŽÅ.TD§+þõ;'Žy%ŠVÜr6ž¼ŠÔ_ [¬ùÚ¦#óÆÁÉÈ=Ï¥+"Æ«ª  úûÔö÷’[¸ ÷Oo¥LZ¾§½*üE+-CrjìÍ£­ht,Ì뵊ú8ü©€0ª[êðJ£Î‚HϪ¶GåWÑ#¸·™d?Ýèß•&™Œ×ÄGINed8`A÷¦Pf“4m4MÍ&h lú~4ò¼ò*\vþ”Æ^ZÐ÷FªT‘®?‡ó¥…ëNÆ9¤é¶œ#ãßÿ—’ `f˜ù-ü, !W½ªVÁ^N>‚›Ç÷ˆ¸¦'ž§·ZŽßʦÈòSH]¹"€!$ŸÊ£ «ã…ÍFï419¬› Fzq[ ÷¬{£Ý)éZzÐlÔc%ïíT®Áå ýkN(öé¶ëŽDK×éY³azwëL ù0yaßÒ 9Á犷&Ü÷üê«3ÉëÞ‚Y$Z‰HÖ)×z¨Âž…G§Òœo-ˆÊ—úTd\’G'5]•ƒczžTÎyaá'r{I†D)Ï©¬k˜înÜ™%«Ì¿61MhÈÎ=hI!Æ„bg¦Ÿœ²nõ$ÔÉ^‚¬ª6:Šå\‚>•F¶#0iáù8aÞ‚Iôõ¦ðH¤ 1j· LVdßëùÕ…½³á‹Â}ÆáY#$÷âšÀsE+Ë ›ŒÇ,röXS ‘ÔV#`0»Ž­ôÍ.S…}´Ì©{ÕR:ÇÚ³ä Årìyîj#û?Ž)òŽ8^ìêäá$süªA’qŽž”l'œΨ¥<(ÛÉ¥ž†ž¿+` ELŒž©åàþ4î÷4XŽ(‡Æ0E#ãqõžcȦwèxõ ÇCŒþTôâ¤íÀéêi®»†pFC8¨[=•XäsÚ aÎzûb€(]+sÚ²'P õaüëZ쓞Ռù{˜”²(ýhÕ¦ù-£L0 b²&Ló‚qZ÷D"(##»VMÃ(S»¯jQ|ØÁàÔ ¼ž£ð«2…ÝÁ5]Êäƒ@ÈW¿¡ä°ùªf{ãœSH#yÏ8 EvI%x¦uä©úTÄg9==i¸cÁ Ð+ rE5‚‘Á©6§Ša.)ÃJŽÃšk{})Ü©¥Ýž:æ Æ?‹ôëHsž£ž0W§çL=H#gŒÒ©#'ñâžÃކ—-0±ŒôÍ8ŽO&¤nqÈëÜRùCÍtjIz“§lW1‹¬\|Å`q÷*qâ½=›bê 7øQfU· éJ¤ûVDzݬ£ärOÐÕ•»FŒþ4¡ƒÓö¥2?¥TKÄÝÏ~:U•œ?*RÎOåL*A |’tÇOqQpÜŽ´m9Æ3šGCŒí4àäŒãŽ”àsÁlíªìpØ=*æÑ¸€A"yëõ  ›¾èk$`^Áÿ]WùÖåÌ~bçîÏ•q=œÈа^DY;d~µ‰:•b¶ò¬‘+Ã(=+2è6ÄŒ©W9ÃsUÌEsÏ\š¹2±\€j³)ÉAõÅ +‘–a‚y¨¤‘Œ{U•M£v÷Í5ã.N@PgiçšpzTŒ‡oÊ}©‡~Hô÷ ž™ÅFW#=zñR9Ç ÜT[~^1žyÅ7<æ‚v÷©åäúf˜Ø(;ЃIÁlàÓ—cž”ÝØüè„}~”˜ c'Š“¯AØÒ2@8 žGJFÆî()އšVàLPÿÙÿâ°ICC_PROFILE lcms0mntrRGB XYZ ç acspAPPLöÖÓ-lcms desc @cprt`6wtpt˜chad¬,rXYZØbXYZìgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ öÖÓ-sf32 BÞÿÿó%“ýÿÿû¡ÿÿý¢ÜÀnXYZ o 8õXYZ $Ÿ„¶ÄXYZ b—·‡Ùparaffò§ YÐ [chrm£×T|LÍ™š&g\mluc enUSGIMPmluc enUSsRGBÿÛC     ÿÛC   ÿÂà+ÿÄÿÄÿÚ ŸzèÅÂ.1[6jï.XeJBG:Ô,¡'| ü¿lgNC¡‚Ã4囚šºÏŠhPÊDU#0è쬫0Ì4ú†©$Ï•cDìitü5»kdô7+Aƒ ÓzÏM¨Ê W¬Y:*Øx\Ç8“¹:ã,LL@Í2Îî¤J:3xøö_VÆu¡–”j«1pæ}Žê}ÊP£<Ç_ELõJ2Ñ“£¤¡@T¸¸ ápN¼Ù?,gXËë¥6h嵚àB27V=bÊ Þ°ˆXȦÁ ‹"b¢4œT¡{>ò­•OÍ\úó2¥ËÓZR¿–žOÍ7,–¦à•PV>N¦Bb¨bC…B’åƒ,+fµ3gϹõÁÜæ·†aÉ]•™YÉ’äŒK`…É\û;ªÛÆ’@ID°$¥H2#ÕäTÏÔXWqw0÷‡3­ÕÍÐΕˆ<‰£Ü…s¥ÄÖ~õ¶ç"ÅAƒ)eqT©P“›ùñ1<öœè™šôsûöQíÌ©1ì›w±vs[ 1 D²ôœd³íq‰I‰²‚Ȩ„‹ž%¬~v¾<f‹@¯hÎo§Ò©`…hGM:2.I[XDõ¨Æ5ºv}A2ÌáVFà³X1›z .ogɧ½t¥?.”éçÒãù;ã çcšéõ…¶ñƒ¼jciR¤’¥–ؤΦj3ªçAQKmâyî:s_4›ÈÒ²õÞ¯"·=nu¥¬øã3åùÏn^6 h;á¥Çòw³ÁÙ‰³”ã÷ÖTÏNkÕåë|þ‚ؾ58Ý*ù£)¤Å.ab¥=¼W:õÈ‘‹ÁÝiªé½|[žÇ4•S“ϋן¨Æ&bÓ“Øóªasû‚sè°¶ fµµŠrè0}16ZËë0zæš™÷+Ùõ¶yË3ÐåÓòü].žŽß¨[”Ög'?çùæg?'’+ÄY=×ëËç±g§±Ó3·zrl¸ÈCÀìZ\’Õõöy˜FJN{Ü>xlµ¾Ït÷×^“^¶´läÏ>gÅ,Ì$$$W–-Îëõ”kU(råR…‹*åŸR“ ‘ÎYÇŒü¿ ¤%j-¢ù! ‚© ­¨¯_¬ô ±cÔcÅË•¨(V2 {>Œ`Âi†ŸÀ^J%iuÂéèVöÔMŒù}ž"¶ ¤UHXUû}Uó`º-eµ`’ (HÈŽ‡SºŒHKY^e¾~òüåÓ§£™éí¦½JÈY ùôóâbyWJ-RŠé¥Óé"!J] ªCÅ T¨#>2£S²Œ •dRE®.ÕK/ˆ+%ÒÀÙjp£˜LýuNöï³éñ `–! ÉZ a¥Db‰Š d^ÁƒJ¯Š@ˆ=eâ€ì¡B«ã³u©báBƒ%Wê\FÌ‘bhL„üQ«Uº¦šÞnâS%&]©ñ=>·všU¸›X“D¾/Q3[ [.‹dõ7_µ“'I2q.«röøzKqE“É)pÎŽ¾NYL6º®+¸…ÅZ¨¹ b‘¼¶Y4:‰ÕÔÔ¤9eNY/£y¡^»·)ÙÎæ>K#’Ê“4Sñ]+w´ß‘Ï†Æ‰à¦æ îãÊyNÿ[zÜ5º9p•v³$YU¸+´S<†H|]:¼¯p4Y)¬6M”ê÷AÜ;Ç`ïX²øêW5ÖôØ}kL>»§D½EB%ê:‰z’$½G#þAiùûØúÎ¥”õ=B»I¬ÓΗ3Üm,ŽãW3!i HXBâ6 Å…b<¤5ÔàZÜþ1?'ZRêõbzºßçé’}[r¿¨jæMë¬=žªBévÈüVÅA ¦RÅÓ*Ïãé=G²¬ö±<GÊrQS°¦Pá!.D†×lö|–..hôªþkdN\ÿ›P®-ÓFÙÙÒ)´ü$§A Kß‚-Óqºz›*ôüõP¿¯j¦µ5Xü^XãU£Öju•iy=Í’6Ù2ª0áE !`Éœ‰NX“ÆË'…tJ¯íî°œòye£Mê8°”P 5ÆÒqS…Ðö–76lQ .ë=¿-íõw=²•‡ÛÜnËÏ;™½¨îâRâvGí~yqNº“Û¦‘Î#–i8ÑQÿoNghþf¡bûÿ’z(ƽ.·t-ui²Õu¾òsm]¸óÊF³Mf£P©rªZÙF>£á×ÊÌ¡ÿŽ> ýwGuráÏ›$¥æ’~8ÿ²5IÖôÖyN¼]Slt½*Ui4:W¨Ô×§Q6d•H×ÁGT½?]–- ôQ´5¦®öÆ)i÷Z£{4møŸÿÄ.1A !@2Q"0B3R`aCPpÿÚ?þdÿ†¿Å‹-÷_Eù\¿E‹,X°—‘bÅ‹yšŽ¤¸¥ø‰¨ÌÙü™³ù#ÄÉ{…^Δ”{²§´Ûîú-ä6–¦lv1Ф_6ìJµûSîU„ïõS{ ‡› ÷g¦_&D ¨"µ%è¿‹)aW)Ã5c˜•¹4ž¦Tv2Ÿî2÷w#»’+¯“½ÊÕÔû/JêÃuh{{¢*_â&ÇVoqɽ|¦‡õ$²8šôñÑS“ØTdzy£\cò¸eˆ+E.š~é DŠÞÑÅÂÌ Ë2Ì´Œ¨ßA;w/wÑÔÄ2§´}wê±™lËîew±—ØQWÔüGª/ªÐØù#ýŒ]å|‘±= óÊZ ’ܲÙ/;3%leYØtâ¡6HZ³sqên®jŨ·?ê“C6ÔÄž†$‡Ó7†¦!ñ?Ðø·²1ñ{—o£[î+˜¬®ÆÑÞ½û’к’BšFb¹˜®f™†c1±Í˜™wÉ•’„¾“‡¯o¦]oBZý¥RHÌZ´FP½ÉÎïí¾l—erRÄïʧOB%縒I.%oÝ[é$œði–²ÆXXX‹PÖÝg›Á#ë$¢§ÆâìmÁys.d¿EV„2ÖZËcÑ'˜ŠŠvì”\‹Ë‹™sÈir5ºò"¢•›ð¥ŠK‘z;™ÇüŽ$u8*z°´dhÅÌœŸtz`ç£9èðwÿ'Â_,¿üJ;ßQÝ•"ü­±×QT¶=´8‡ ZAŸ°´gÃé"E­<Âòâì6ÕJ„8ø[9!Nƒ‰*†²)YÇKeŒ±ÁÛpvÎßm"ÔB#¦C(«‡²tRÎÛÒ–5\AM -’©¡V‰X%-ëÓÓµa´´´óRC!–‰a‚1G¢„B!Ö<üeÆÚ GÿÄ8!1A "Qa02q‘3BR¡±#br‚Á’Ñ@cásÿÚ?ìtW ù‚M®­Ÿ\änTîJ#U–»h°ÙN™rÊŠç-z®ªÊ%^UÆRó Caõ:«•Ó;ä×9ݺº %QŽôTa\¼×|\o¢÷ŽUsŠîÏš ÙMöŒ~,CPˆçelìkm—hTåeÝ ÙÛxÕ5ƒâ0š)l§={h*¦¥wWp.²Tlz"FUS®VÞ¶g?foê•tk•s¾ìIS‰è€å›z\ö³ò°•q ô\òˆ(ð•ÝÓ8 qÈùEÑÂcb.U¢îª‘Íw½l¸’œÆ“´>eXžŠùó+TC~ÕÎç/h}la¢äÂdƒÅh\#gà i¡M¾ªãüõ¡ Ū“8’Sÿ aÑB\63qoÇ¢„'á’S è诊ï7Þù!í9å@©Ï]ãEl«¹Žï×Dj2¨2„þ[¹h©ÁNŠJ&ªUÑi±Xl`ù¯%Þô ®rÔø•ݨWÊê¹rȧn*78Ihàãñ:8]ÌrP­#Ô*,7‡9Ó§%W¨áäµôU;ú«g®WCv”WWXu"’¯º×hÙ?DíK)Éwv]Éí]Jpsâ)­kvdvz'a½åͽã5)á€8»ˆÉ²l†ìÒ Lö\•Ñ®ö»˜bb5WP§ªwA+ø÷B”ô\’±t™¿È:It©×’Åü¶v\;¸Üý–&!^ùæ€Ãh2ÝJ°Ù+ºö/hü)ÙQ½b¬¦—…æ›ÖtðMý³õFˆoÙcÛfHñ_È}Šhƒ]•!²dSÕbþÑý!µøgûM˶t ~±|ŠËL«TóX£ªsÎ'Ä꘽@ÖÙrœvaµžhn;©öPÖ’z¦ŒF5˜@Ò©‚l"J/’_²= $“A(#aÄ@^Hx‡4™^í ªº|Gz ø&[¹¯‰NÖ°5 qiâ¿ø‡"˜i¦©­hâ‘H=Qàwwäè˜v\lh9¡ ½ 70(±Öðςյa f [aÇ`™Ùš-€Cj Z‰ßªýWƵÆ•\'òù'—6teSðÝ9”Ip“j/ekÞqqTýxJänñB9!Í šuUñP¼ öv8íb5§Õ0°ý‘RACñNÙÚá&𛇈Öí0ÎΊ-ý¢ZÙÄ©~ˆ†êo òUV0æÃöC°ÃÄžçÕ9Ûf°E²ã´óµ¥.ò¤Â•&$·š/Å%ø0 ÊÛ—ÝöoÞ­ôʨ m2äå/¼öQsHsš$ J¦ý×½àˆøŠ-Á{~Tû#ÕâÛ‡fWZ­w.3ºÁ<¤ý#×6›Aÿ—®ÿ†?di訡a¾$(‹MÉq…Â%{©\x.o_—‹_•ÔUÛQZU·¬ŠÆw&nU~(–‹8\.FŸ¢«‚Œ6ÏU.Ÿ5Y%S8¦ü®\M8G¥Bàsq< ¨Îö啳ÕT##=Lý¬ôoöº­‡k*6ÏU@FÁq;-w­÷zeínæ@VÏ–Vìï‘Ë^ÈUsQ‘ÊËÚºâJù_w–ý·Giu}ÜSyÅ?`ˆVVTܶWÝ¿c}Ê…}ËeÓ#É>!]³F¾ãYÞÕ¿”EUù6¯‹‹x/(ÔòSk¸èpÌ/èœæë£þ˰ðL´`oáÌt]zyúfuOáâftw)¡Íb5¾%}§CrDaÜ0¯î'i"ßkèŠßȰÐúe‡‰cú*‰ux–Ö˜JÐAŠ5Ö£G-bX‹~Ø?Ó‡=» k•·Ë)gU²UßÌbC,(ÏûÄÌóˆ{Äšó-6¿ RsyKkã'¨ÞŒæ®\e–[ˆ\1«y•ƒ1ÙKj`m˜š8ÔÄvÔféåq¥K‡™T-SÎv,ÃôþN3îQŠSò—µ•ñî¾'„vT­´URhÑ1*R¹7Ô¡k.;ù§¨óZá™A,hòÜqþ²ãdÁLp€Ù)Ó$~G倃TBm\^æ\Íÿ1‘tu¤Øx!Ã70ï÷)mƒCñm¯ÀdÿuêYIhÜ â ÈqæbG™§éGD€Ý.ç~e8w©sxyŒÄÏkñ*ÔÑ Å@Å7-MF\ŒtÍÂE©KY ð›úë;k÷Ôn Šîÿ¨—Ú¿¸‘vþOþ&u¿¿läˆ w ¤‡A†…׉~¡Ôbµ &!ÛÖeÂ.N©Uû!èÅ\tÿÛ—œÜQyßí¸=Â]7Å„u5ýu(ÝEȹñlå_šƒS[¾å†âìYŽ–§£ …;ž íQ´p~ÇúŠ/y¡§Ïõ­Ø¸qÿP÷fy±üA¢tƒÆãeÐmºýB/X5£˜§¥ú®;DÐâ/ƒ”RýeFÞËæf€<ž¦ éÐKâ.pãP'ÛSX_bÿ™‚;ãÉ:¼ô ciŒ§pÞå©cê&ð® Ž–d5Cœ±e0l¨úpì±”P7þsteã_ˆÄhPÞK|¬ÖF%²ÔUUñqg쀶q*سÝ[ýDq8þYÄÚ(õ.µHú?¸ù5Öx‚–°- 8{›ai@Ox¨_°Áá?€3FU9ùDè>IŽ:Œž«×¨,\0¼ Vw oÛá[AvÞÛ…Êëžýy•ròEÖ®Wû.¶»@±g»«”ÛO´¥yÄÀ +EX7j%Ù!—eÚCPŸf±HâUGôÊa¡…›qüø€M4¼5âí¿~g5Í{qvy™ŠKñ/ø„±\~b2E«¢·÷'x÷>ά”d¥˜]\[g–/–_a·Ôí!|¬:c®RÞ¿ÝÁV#Gû‰XWTTR’ŸÄ,÷Æà=´ñÄÛÉ3ª\Ï1 ¿y"TóWn/!7'Ä£p³“—0ŽÙ‡/„¢°ÈôÇà5PLjWÿV(R¨òRþãÇÆP5¦[…Õ¦C_pºÙ[î4èäZ¦ hŸŒaQ:nq~É›MBÕĵíÌ 1ù¸8#*ñ43iW Ñό̽ž`†ß)A2+G™Àº¾|ʘ2 çÉp—¢'VÀ±Ù¼ ŽH± *§ê8µXÀõŸÉ2mþþ0Ø?ñ@¤ìˆQ,¦³*%$Pê³óS¶Ï9+n˜¿ÆVÙ]+;¸Ìd#·7(9ˆµn““‚ßsù†)¸¨Ã\Ô-ÔÊ&U-_¨ïôF\¢ïŸþÑý.\¸"ÞR¡¶æ5RUGÈAç¹äüMJ Qx”Øå:5ÌÑ¢"q†e˜»ˆRÒ-žËßöŽÖ­¼'dçÌßÁf½Jåä +L_ÑbqŸ3žn"ðyöy-%óR˜ðþërâN32¨’S†`æàÃfüâ!øi{–ÞÉERTmªa3tz˜]Ïøeäpþÿò=»&ÓdÁ´"Úàæ?øõòGé"Ûé‡ßÀÌ ©vþ¡¸ çúM½Åêsërùé÷9(YŸ7ÔF‘y?E†-j:8LÌÜ ëætO¸eå(WP‘S^"5-1·ÅÌŽÎÈ–Wc?‰AC‰‰ü%—›‚Ëˈ ì+÷ÿ˜.æfJ·©n/¨ñ‰eâ½LÅãÜO/ý™ŸˆùW¸6wL+±ø:ÃÔK1x˜ÂíùšÖžÉ!ÑŽSø– òœšŠ?ö-1ár‹ª€]56Èû‡…$ÎÃ(8ø!ØYÜ^L¦vnoÿ&›Ëø‹\?¡†¶þeÊ΋Åÿr°`šÄyÌhcs1 ªó «Dñ µ¯¨S„°õ…<Ḛ̈‘+xñ®&W¹Ë^T]ÝKõÄöE¹Æ|J 0¿ˆ^5õ9°@V¯ÜAÁñ0Æê.¿ è©òf6>Ø{•tI[?ˆe×Ú h„µ¿”©ÍÞx‹ «˜yöN½“6k>`MðÆÎèMÂã³röAÅLÁ¨%3õTÆA3ÿ=‰Ö\àLõËJçöa«/Ôg~¡ Î Ï M1Dnø~*-s3N?„§kæ íܲ«w ¸fFªwúúÁª%¨¼Ä7ñ>ê -p8…;×ÔÀQ~%l ˆ"ÚD¶b8Çóý¦š¹“u8bæffóR¨â Sd/¯Äl½J,Yĸ¥ßRËâçBåUÑ*[˜9tÄY=ËUìæ-™ô†òu)n ÀàªÝ${ãAi¥´ã1™XxšâÞ὿i“ˉ¸HºÚÇR†³›œ×ñPG3†5Á‰n¸ÕÊÔ@%לû€UÕ˜[T—ºþ`x±ÌlùŒàFc¸^Nf›<Å4ÕÑÜ£Ž]Ë´jÏj¸j§,06¹æUq3³ˆ‹%™Ý?Õ,ÛX¥¦ÅÊDfDüA³Tï¨ –»ŽúkUÖŽe¹«Ç,FHÕVyT U‰EÛt[¬ó).#SÑÄV°Än'“ĹÿÚ {e¶Àk@ƒ ¤X-¢ˆS-,$›€À%r›+:™P É%#È}Û>%ØAÛØIsì½"®l è£w2ÞÒbÈ€V0Ó6aMvàžœ±¶P6²{AvÐ"ù!o}Ü,¥4u)d~%všI™€Öª(3¦p7ñ‚q“?kiÕ lz0Ȫ¹ÐÀ[dAb  ”™QߢNBÃ@†ð4¨T_ðM—¼Ýt–C‰zeÜ(J%ÄeÂ[F[Íòýoò?ß®?’»—Ý^Û„4ÿ¬öÿˆaœ%§ñ'ÈCYæs^v¼qo8£ ܸßÀ©›€#ý è•)_b¡-•’¸Ù¼ ®½8b”Y¿.üCÖáAûhóP6“oÃeݽOüØ{o*<8= ÚNÇóñ!ÉB, ÜÍ›ò£­—Û9Ñà™Ð«²µ#c_´ûm‚ŠÆö?Ó]/ž]½ë© ÿ ÷—êI^É 2ÏîÖÀå5aí WØe’RtZŒ“ dA½ód”SÀ¤£ÛHHHGÿÄ'!1A Q0a@q¡±Ñ‘ÁðÿÚ?J| šŸÀÕßâ¿:Q;ðG‰ùi9ø&øµøÝFp'òˆLÂH„'ÍJ_ „ìLN Ü.|„ÆóQKìM##ôQ^üIB. âñ< E˜\•›dl¢Êr@Ї8ž/Bûó¹¥ËT‚ö H$ ñ0Þ½bø²æŠëÁ}%8Þâ[Óžà"·Âw„bø>±ÑÐÎŽ2ؘÝ‚ Ä3ƒùÒ¼íèLÈLBabûÇ,…p׬?µ„Ç6ý"ÚðΕ2ˆJô5¥_Ñ9µl\f9%Á'•Șô!+Áª#ŒLLl~yS[¢FûÂéH‘NGì/ÐúXº³ý™£(„ØþŠìwbØ×¶6ÏîzŒ¨¦È¼&YÖ_>spÍD„‹_¢¨«^¢)ÓC²9g9!(ê)Iâü—Žˆv<<y2G ¤¨Ð‹G±iàC] ÜŽ D·ä_€Æ?c•——þÆ8é#‘©ÆS›Ù«R vÌþWä“k5ÈU7躓dÊßpk?¡5›Ù x6ìgÙ¢gv:ig1œ-åþ)ž’\HUI–‘6BFbA.ê èoô×ù:_cEo±Š’£}=¹\ëþŠMàä>… nºhŽÌj錌T(û{(Ø\!mFp„»£CB]£}Ÿú~‹ô6åý «ý•ûÒrÏ¡çòboN´h†ôo¡¥µçÙ;l¶\݈¹v£j OýΈ7èUæ¤&Эi¦\˜r;löIj°¥É8ØÙÖr‡¸†•4†õ8;Ôm¨Eišy;û´Ód:A[Ñt[²Îpþ²µáæ’ÙÚàÐQ9ª˜­_ð6È·±£øð&L˜Ü{÷Žq¯F¢¢SLm² ;~¶XÞ^‡‰|g“½œæëÀƵ%βø„hû†ÛòbSÃhfËqÂñÓ&ÃËhCÞÆ;‘aᆰéŸ,Mp/¡4ø#dðHˆgDÑ1gÆùÇèë'þMcE¨7ÁA mœ?hÙµ_ØÞ1x‚Mð&+2É>V·–A¡ìI ˆB 0ªŒßéú}‰/g„¬Oñ>s2„ð„!BŸ"‡~<ÑA,B0Ÿ‰ß”ÙvJ¾Iø(ïÊßÇ<6”m"üH|‰æÁ;‡pøÃÿÄ!! 10AQaq@ÿÚ?lxœŸ‘ù/à&.Wñ¬<,\5àL¸¹¥æ™x÷à¢ú45Å2‹+„Ê\Òˆ¾oæ$ºJR‹(œe.fo WˆFG‹r˜Þ††ˆ5‡Ææ22?Dbh¨Žî ?œÔQEe)^…âA¢‹,Ÿ¤OeSó?#ù+ácJi b”¥.©ƒ ·(¬eá¾§ÍÑFÊ7;z²þ”„ÞÄá”OÐú.wÁá k„ÂCeK;>w¼vt-ð¥Ãzâñp´]ë+hë6vCbB>âaÖ‡°&z'"º•YD(÷ÂñHþØ–²õ±°÷Þý!öO‚bžÏ¤-ï';D½1¤7Âb:?„ú¼Þ¸Â{Ɔù¬¯øŸKœÌá ™¾+ÎÂã<—…–7EžŽÐÖf`‡ßçÿÄ&!1AQaq‘¡±ðÁÑáñÿÚ?žÝ;Â'Bítâî éw÷Àe;@f]Œ®´òÜHjvüâØ¼íºƒ–ð‹«×wŒ~@àK®®h!² q&sJÓçþc-ö:ß§T-QקǾ ˆiXq"³ÊAãµ»7÷Y¦=uÌýýï"©XØæÓçvïÏ(…|Ñ¿ã*µw{g¦U㪿_lui6D_&n4phž9ÖZ ”cçOÆ0L! uùãÕ ºØ¾Ú~øN°è.¾ï¾YªIȧcÒ©ÃjcØÜ¢¥`å”Ã.Ý~1E7!|u€BÝŠ.ý±uV£‹o2Ûß5*0è—|X“°3 &ï7¬}§^_¾JˆÞiüa$³Éˆ’¯RcÀØsµnD6Ó¡“•äž5¬;PwÐ~/çÓ4&¢«ýÉZŽ*ÿ0–4ë÷ÄTQ³¿Ï´É+O#§úkT Eäú^1 IXkH÷ßL¼u¹æû~2‚¡òF_8£ B‡ç7ÙÒ«£ÛÌDí}§d"añòa•kýýX9=X¹ÍYé‘ÈÙ¤‰Ç®8záîsÏîðCŽÚÅ~¸à‚ñ6;ãÏyW„N÷¼@Ò‚×]þï(T-ÃÚÎc€ËMʾžØ¡ ›4é—‚z{ : i¤È¨’÷oÆL¹8gß…o¿û—bž¹-|®-$.W¼r){C+~Mç.†ä3@¦·?}ñ䟟ç&–ên|ã^ ¾‚øñô÷Л³lö*¾ûw‰÷ų¡!®êŽåxÔë—ƒFØ#éÞ.hJ<ÄõýÞÅrÌeCåž9Ä“Ñqÿ&¶ î~øÀÍ;W«ùÉŠBÈ0›ß_÷lôh<ê·¬@$E¸Sƒñ”¾Ò|¥=&5ÆßUþ~0ô¤7sM9Ygcиå `θ…=™õp\M´¢¿ÎÚSN¸ú\ ² ò¢Ÿi)]5/“'.¯‡ïNJ`j5 k[_¼ÆJ•Âöýr˜‹½ÝcˆÅééÄÙ…•g¯YÈlæ†)B_|èÿ3cWäúL&s~»ùÀH.phcäi¡¢¼o °]R¡éÅñí¶hX*”ãÞ÷Õpl¼z¹S¢r=üu›ÄCNïøÁåð[ësB­P‡š®§­pl.‰`óþþ¬¶Œóù‡8’¨_~ñÚ°·xþþ1V¦îgýo *·*hQ5~¦ @M»^ñAh/lýúãì(‚cÓºÖ|àhe3É€T½kÆ;$l{=oóï…½œu£¿åb곃Ÿ÷âGÎ%A¨ž0$n ä!ö~ç7 Ÿ>uÇ -[±Sñ€ÔlN½Ý8r+žþ¹°à!ó‹ŠÄœŸ8Ð)Ξؕ Þ¿‹*Qó³ñ€B=‡ R/3— ðÞ·÷ŒvTäQŸÎ9ô‡zÞ4Jù7€!;í‡FG˜pᶃÈqxÍ@=.Ç£ñ†ÀÑÁwósP&ÝrL†›öfÊ=9ôÞQA4g½ôÁ`·çÌ·ã9˜‘uñŒ«a±ÿŸŒÝÂw?äÅRÅéÇŽ\h”³Ss®×|®œ@TjܺlNøücb«Ñ\ø¶?¬ÄiPÁ3”¹$JR$»wGtç›Õ„Ñ¿]àˆ6º½}ŽJoÊÓ›Þ2WŒÔäa`¾u†)œgYÃCã»c5ÁôÄ”,É,¢3ÜÉ•  ˜Ï¥GøYˆ£•!ûâ°v•zøÉÁ¬à×ã9°Ôþ·Ïó€ oóÎhD$ö²côîçoe>øz<–Ùí©ö¾&¹òLÐRèÏËŒƒR‰ s”ë_€Ä•°ÉõZÿ80ìž§÷„ˆd²_Œ rz„ü`î ÍðÉ]ËûraÁæuŒ W ÄÜÈUŸ§.$4§ùË< 1À¨Àu7Šã§Ã’Ƶœ ;dz‰áÎëêyÀÂs’Þ(~r¾ƒÝþÙ_`çúîB¡â¯àÂý^Oä 8'­…< óàÁËV+>Öc…ˇ“ûÉc¡5»B:)¾a#\?½5Îå Š$îþ9ÀìjOßëj5à8LH^t?›•h“ü8"ìG?Iéœ8ûýcÌBЇõŠ*Wz3{3^p6á@þúⳓÁÇÛ Þ%Õ¹ U®ÎoÚÙ#¢Þ¶â.Æþ¼ào0‰‘‚¨Œ¿=è&¯oLhSÈ%O®j•ókˆ^QqñÐÿp™kÛ8ÏÔÖ 8ìÈ#”@ÀÀIȼ¿L¤¡¤Å!(µÅkœ4„”ZÃ7çõ¡&¯\yÅûxú½|¡¢üúõƒQ¢ô:ËȺs½ûâ.ÐTT÷Æ„)¡?Ç*‰–x›7àÁK¡e.€áõÁMñÏûˆÐ«­ï!ºçˆ÷¼I·ºW&,¼áY¢ã–>¸ÙÓò¾Ý*—µGíŠâ©¼„ ÷1µíßÉûÞ2žÍ¯œ¶Eb8IpŒƒrbõ¶h6~ë Û©‚xÑ2÷/@0j¢3”>sœ“H.}õôÀ€žZœgi¾qµ’M/Û @ ^;ßA§Û`{qç® zuå™(ªR€Ç4ñQþ.D« øÿq°^w׌ €†,,þs a çb”h=.:'Öÿáë…:õ__à}px>®mÁö à›í„ثˋ«ZäýñømSÅ=½òæjKÛñ”Š|7Éêÿ. ¯&ܳv´W÷ãÒzúç^êˆý1X'±þ24qÈÖ7´v‰©ç,oxƒÎp@J}¾pŽ‘ áìĺn ®èÓ|`’ìÙH~WãT0 %ò'§×îIMƒ/ÞüÏLZ@©¬ ì¾³9 ÓÁ\ˆ ¡oÎoÅ.§ Í|¹Etû‰î\¥°lDÙœ¥ú#œPj¾†&õëõ£@èBJ*‹Ï[zCë1Nâ/æç9Îø:>¸›O„~õÔUX@¯¶*ŠtGE]{;Æ$4K²£òü`d×Ó8>õ¬tÂÎñtõŸoów½¿ë¤ÃWÕmÆ Ñ¼B‚¼£ üA#9Ù_"k-*•l´ ¿Þ Œw|¸kRuÆc¡´l¶¸47 f½2üÈ_Þer2ð­`ö~V2ެ³øó’WPŠvž®! j{íÇáoòÔ5A|dPa…À$Uàeßé‚ÖQlޱ7ÃÞBGa¹Å7¸&²ªõɧÛÐŽ‘ºWï°œáBîmŒä ÂQÉX LŠÆäBø¨YqqB—¶ÉyhâÒ¯•¡vÀ#O^ùc-”ˆP?+‰ZUMÛ‰îm»d°Áƒ@;.¯çµÞq‰©>–°au”¶*‰±O8±ÍËÇŒ;j€óÎQ;ð¼_lu8ˆ­PC¯ßûš´P©ƒ¡·½Õ¸“–ué×®9¨éM¾ï¶!¥âŸž{ÇV…%ÔñŽÇN÷‹UE¼`Ñ.¦Ãûñ‡‰F¢)IÖo4*»7ˆÜ B†ÇŸl$&`л_Ku¹‰½Ð~;c ½I«ì}0æ‚4ùõÅqO_¦›ܼ… ÆÿyÄ0EpÉýñôÉW¡*ºÃ›ë8Ëùû®*_e`þ}€}ÒNæáÅAé¬8 éÿ•E4¬‰SoLÛ¶ámº¼Sß×C}Þ2SÌ.¾ÙÙbkøÀOàIa¾ áõ[Îæ±êÔG™yFÆcènžMw0·I€5Ǿ)À[+¯|¥qO_øßz]†»¦ëîáJ ‡ ¿¾J%¤È4®×x^vâ϶%@«q?(ê•؇+¸cn@jhñ9ñ«§-À/XâRºzõ׆oiŠr&åž“°vw@îúã°¨ƒÝtfÑwcLDá*Ï<ì0d«(‘u7óŽ‹Ë<ŒTÊ`!Tô¹Æ+êÿ¹FŽ[U˜Y œC„ˆ8 ?œ-aM‘zÀg¾0¢šNq[Ï kí‹©Êîúq‚@Ö0ç$ tÓøÄPHñ)5ôÅÔ O—ÅÆ›¢ÇŸ®'‚¶tíNžø‡\¬ý§i*ˆç4§S{õèÄ¡·f¨Ù^À¨\}´ ò=;#ÎîsZ5é¦þ¹±;k'œ×œ7@Ç—8äúb‰<ÿÌ¢èe±Pvûà2Ð’ké±½¦„\§!ig8E{΢@9jë©ò®T“Q4¸ä3¶ûåÏñ‹_üŠ Ù«pÈΜA síPG9KFÅ!øÊ€£`\œ˜˜= ¥Ê)XlC.‹ÐIJ¡.¦œaبøÅý‘'Ó'°bpñ‚èJjOŽ\nñ°šßý3aÕ +V¡£°~™¢Ã‡é¼ªMB¿ 8^z~?5ÊÑ,ã–gX€ðEÖäûbíÔ¨lSé鄼<…Öî[p$Îý²Ç>½‚59îoǾ!é»ó¡ñ'Îm´%*»Õ…ÍÅ/C’ñÌG$eáhÙOeÇbÒ [tð£Îß©à¾Ja”kß˽ºË1kíÿ޵O5L[>HİË,^µ«e6‚Ó­OœS±) ÄÞÎ&>…㢽&®%•0!l/¶0› 5/äúáe¦£\Å;ð8Ú%ÙSõêbýé£Ä‹[ý9®i4ÏðÀP‰·¡u¡$“`«¯•WÕpI€+µâuŠ7“Øç™±®xÆØ'3Úž2s‰Ó”Þ Í[ŽÞ  ¼¸qo8wa›O ØÄÌ–¦#<7&Š ÑÓŽ¸ï¨Û~½²q¥÷¹4¾yÁhmop·”Fë p´|oä   †W^¸å³’×hÕÞÐУNmšž0@¸vj–MCWK‰°¨8Ä \œ2bá!:‹üLI*’kÂ/@©7ð㼞@‚KxÇ:Ó1à°•tzß\?E÷Iã¾u# &øOL] ÒoëzÝ€ëK ×E¡ÖþµõÎ)œ ®¥BëûÅMHŠ&µ)ªôˆ¡A(óCžzþpþM!=BÿrGu£ccúù - 柭ôÍO$ÍÇWzShÐåÆÅ˜álzá¸~Ü dM OàŸøÚNRA„x9ú¨èø£€Õ¨2©¸% !VÐNå¯ðJÁFz1Ô‰°ê¯^®v8ÐOmŒ`µXTÛåkp©©OM~óš£7àÏL7|&’<‰¬•(òKØã2Hn¯dm-/† KzÙÕÖ£éŽÑ‘­¯¢°Ù輪Jì½Åç-ë hØ'÷ã´ƒeòQŸyÃÔ i m±Ë±ìšŽarkOþ&>ùn#ÿÄÔsT•ó d( 1%±Ú7öÀÑ48(@ fR_aîµMVZÌo'C!ªð0zÇk X¨ea_ŸmR:™Eœ-}ñî÷B÷ÏÅÔ]QאּVðèuß?¦ ˰Ÿg$.âô|áèkb ÆÄ¯‰×xòò”Ÿ¦$؆1,l„ÃÛпOãäív¦Ï…äÃúãL§‡?&¿?øP4èè^1QV‚’)ôŶ ptë REÖøóèŸÞ d!6*>u÷ÁÑÀ+ÇwNL±ƒ dJI¦ŽÄ‰±ÈÛÖ<ãÎ8²ç_ø &†;­zf¦ Ec/°ïg¦qp¦KÌ Ž„Ü§ñz1ð1³€çÞæä ZYð¸RZ'½âJ NK°otdhYÆ%DG4bä7pÒþÖ(±âðà"ï³ù20d©ÙðÜ\NéøÏp)4üâÇï‹+ zöì0#bŽôÜ¿ÖM¼’¯ÛQ”5ùÍ„u?7 <¶âû J>T褞¿|««¹½YõýÖæ©!üÜ aSÕþ°‚›%oŒÄ¿¶BÊ8ɪ©Òw›)ïøOôÊ5£~þÜ àÞøñpÝkùöÉ …´Œúæ—2 +t^U×%׌·î8~ƒÄôUöÂp“Æ608ìßXBÞ ÷öÁà`¢Á‚i7²s”‚ÖŸÆ75ú]Ÿ ›w;¯ë9>øÎ:î·žŽA uÈd<¬Ç©U)ˆ[ëŠ'¬«v×§Ó €C\át»s”‡³kXU¬ÛG|@ZhºÄ^€†4œW¾òaiWÙ¯9¤"óŒÁ*¼FqìkéÞ\ÂñöÁx |eÝO’‡¼@ž·Ä=óAÒˆÇ\ë¿l~ÃԣΠVfÓóí!ÕÞ6çF‚œç+§$ëa³ <´5¥Àª01ý˜ùyå^ð9³;ñí‚mUÖ^Žã f°§m6÷š](D~¦ ¡õ\Lí' ~¸:k8²õU¯›ÁéDM¾ÿ¼ç0ä4kTûàvl8|`á½£WþýröÊÇ¿UÚ‚ û¼Q²ñœs¯”çyØ2¼kÛbpôŽUERÂ)¾Œí Á§¿÷à£W×xáM|¼zd è‡WŸ9 —I턱’È9ÓÞvp"š14ºzýý¹FiRëûȸSÓ{¼§ë•œñ=½0 )ÇŸLa-ãarï@7óèŒö»€Y0å¸ûo%)¶Uæ1£ZÒ÷ûÞ=QÂÏ8£T{M9ÆÕ@Tö8ï7aˆ¾òä…ÿ `¢ªùüå†e³¼çu¥*‰¾×çxP~-ö͆îMqØ“kÇŒÐE&°·¼šR*«=pݪè@ýµauϯ'% ;óƒ(‚ÑLˆ òTôÏV+óVûÆÔK+‰[íŽ" FÕ½ºÍ[=» *Ag'µÛ­Ïc÷ç<¥eG pºëýÀþC¼k˜»+F…°:Ä:¡3„èÚ¾r;]QŽ=2 ˜ly5žK½ú]a¶é ޽¾ÎHÇ×~Ù̪ Ù‡v”CoϧY eحצ Å“ç¤IϾ¡72Œƒ–®²Á›~s…‰Ò?öƪTée?Œx­]2ºúg)h<‰o'N±€(G $óÍ0(">~~øÀ±¸¤€/ŠÝûc,ÉK$¸R‹m#÷ŒÜ‚„½úzeÕíßÁ"zqÎjP‡eÛ  iûbC`«]¸cÝ]O×@èaHâ [»ñ¬äº|ã$=ƒx±ÄÏ·¯X ªãUè¹t—ø=±¢‚"L³m.Âß&Îð[J’šöo-ìX_Û—j©°oëƒ@U"h.ò±°AÙ¡‹¬)ç)»‚ñÇ?³¥ $<3»‚&½Xúc 8zŇ4M¿¦1?rœÖÑààÉÅn£]uðb{l#78ú|c@ûð/Þ0V€‰ãÁဦÅhÞ?}3~Ýqô|æEÖ_0£­`†eƒåð*PŽ(jŒpráצn±M»ã@´šçç÷¬BÓ‡Ã~Øã•äœkA5ÆÉ„g‡ ë9~†ÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/images/guitar2.jpg000066400000000000000000001270651510465702400257500ustar00rootroot00000000000000ÿØÿàJFIFHHÿá>$ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:24:58 Ðè Ànt§<ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?æõøÄQ40º ä°äŒsPA¦ÞO øœ(.N1ŠŠòÚ{{8ÅÔ7}çï8®¿AŽÆFtº*0 "ÈßÃŽþµw´n)+;\ʶŠ=æÂé$·HÜltgÚXt$Ô:•šêÚöÊD ~Hœú棚8šì” #) ©3Ú³ïlᙲvÂßì€3P¥Ô«+ÙŒœ3¡µ—|) ümÇ×4ûKh¡³ŠòwVr[ìáø G®}êå‘‹RŠIo¢Žyò˜J«¬ÚÀÑYÅR6L…'î‘Çõ«U4%Ç[éÐÜjOq$ØvmÞc0ÉñZë 4±Ã Ãb@€3t#oñ¬=*îKh¤~1‚Î7`œÕ¶56h!Ž£P `c½EWï;¦£ïm.'‰âHàÓ–8ÙÜ’08÷æ¹mFìj¤“ Žß n0;fº&ˆ¹mçýÔWvÈ©#—psõ¬£^´+&Ü‘"ƒ’? ÎwêtPåWgK¬hE²]?QŽYå0b qž9®SZ¹´N°{'Ý" YLa±ŸÆ»‹C§Á§j©z²¼ž[,l@ËtõÄjVömáx ¢ðÉûÖÇSӟ΢dJm=‰wk:Ý›Ù"ˆGÜ3¹°q|Öœð]é©,j¤¡]Àã‘Ò±t«;9ôk÷Ð\ T¶ >ÿҺíCIÕÖçT¾…ÊÛ1·ÍéÎxôÅi3HMËFVÑ/·¶‹,^w–á—ž€ŸJêo®îÖy§†YTHÙòÑñµsÀúb¹oåÙØ™ñˆÐ –úV•Ö¡©Ù³ÍÓíþÒ&½ò]ü¶Ú\‚Ÿ\÷¬˜¼/yB<¶I¥šO»…çó®ª}6[_.UŽ-ûU™@ÆÞ3N–`®b2HB’I#ø Voc™Uq~§-ã8á76ó"#ƒ¸ŒŒÔžÖ ‚Ýl§o+kn?‹œâµ5=ÖbYq´–ÉÛJäï¬í´ã$+9’é—€¸5Q’²:#$ãÊΗZÐVú'¹Ž)F£!È€õÀ8ä}9®N8¦Óõ$Y¢d–'R:Wu¥ê_ÚP¬–V{äŽ=³y˜ IÖ6µbJ}¢í¶K>öêO¦;UE_BcUÃÝ‘Ï뺊jQÅp-žòÈ,O ô¤¶»žÖ&MBv ¨<Š_[,Êð±g,çnyü*®¥%½Ý„²Ç"ðÃå'¿µvEìŽ)$ö6n!†õàKxîcœ(2MÈ ×ßÚ³&ÓlVè ­Ynœà²Å×ï)tJÒÎŒ<†êqå ~POZ˾¾76î­û©"$2 £‘ÞÀ§mxõ3Ì©ei*“ä+GãÐÓ5W»¼¶µ¸*¹‘pí´F;Ã}BK‚‹xÜý퇭jZɺdòn¶œmfS‚§<Spµ®+ßTYðΟ£uåK ÍV'-´“Íj›Kf¸ù Ž^Þ®jÛN˜™­B¹¹*ª™ëžk£"H¯å1Èv€?xج*ë'©¢Ø¿4QÜZ¤E£EHSÌYåa·¯= ?F/´û‹¨&_2ÕÊ™rÑŽ™>¸¬íVi`¸µŠGw]Š˜Æ1šÜÒtá¢hsiîè×W|:nLŒdÔÊܺ‚¹Š¶ðÜßÛË 0M?˜7yMóF{ä ÉÔ4µi"™ô{‹—+²í·" ùAaÓÛž(ŠÐu™SÃúDSÛM<’ÜüÀnÇ9à×g{xoôíFÞš"ó)cõéÛšå<5£[RT(Ï*%(§aŸsV.uWûZX^õ²òüÑÇ.äú{ÔÉ]•I\Ê·¹’Ù·Fv¿fGãW!¸–ëíiY·FÌwäŽj›(–à¬C‚ß-wWFÃñEy§Ú+ [h±9^ø&–Ìí©4—›8˜`3Ì‘c“V¡óô}J)5/†AÅt3é¶z¦›qªÛŠl—ú…d=ž¥©N­$M’>û Ξ–µæzìtÖ~):¦©sŸš|+¶0íŠè ¤Q•”¹”°kzt¯.1}žóÊ‘‚•l1â·µÿGxЭ“·–¼±`G9Î9¬Úkc9ÐM®SkXžîÚbñÊðð´ã뎖Oã›iäœgõ­Ay$ò º›ýDZ<ž™ÌVu¼/<èñÈ#.ÁY‰ÀÖ´ŠJ:Š”YØxnâÝ´³”adQ†,œïÇ\Õ›ï3þû“3p#.¬ÄgwQøçŠž[; -áHä3žb`¤4Û-ÜU®ÉZÑnã•vpXvÇ®k5ËC7-npÚÈÕ¤çK:îuE)ú Ç¿gòŽÐ QÀ®¯Ä2Ûÿk£Û SÁ± >µ‡{§_}š¹àسd¦’=k¾”šßcžI=·4ü;q!Ó®?| eù|×\&ßBÝãY>"¼‹VÔ]áábyw¯¥oKyöuº½Ô v껆$íÁ;V¾“©i’øuDp †Ú.Ám¸ì{š™>YsØVÒ×97²‰4æ’8Ô²AŸ189ÇZ¡¦Zy˺H$rÍÃH5Zùmįc!—;Î+ ÐL3Fc™Â ÆNHc&´wŒ/}Åv_¶¿±Iœ+6×YeãŒ`džÿZÙic†Æá]²|Ô(›=1Îkl«ysh.VÕömý$ ÖÔrÜÞÏ#Ê‘,@¸Æð;}q\’WßCE¦ÆU¬Oi­Ük$­µ¤„/ý4# “¨jú”*5ÜßgŸ'¾V¬kÏ{ª\Es;}šÞC…þï4º<©j k¼A‰,€ŒŽ;bœõfð"ºW1%²»¶…gx]#n0à×S¦ê0iž°wÓàœ—pLp\Ôþ`ñ;˜5;éJÆ• ŸLÊ¢ºÓí-t³¹»drÊå õžÞüRöfu¦¥êFÚíµöx–ºTVrG²<.~_›=+>ÛKºoôÈ¢,¥7®ÎJ’8úSìí-#±ÔEøºs6™N=y¢ÛXm5lãž0JÉlqÿê¥+§tUÚËs6ÒD·»ŠI£*6Yjô[{»_[þþÝÆ 2Ù8ÇZàtô·¿Ö’7O. …ÏOj³{¤Ia©-»>ä#vÿnõ“³vêuÔŠ•®ìoMªXA¢ÉbÖ͇sGŽ@9àúÖ=Ž»=‘µ H€îwð𬹥Ý3H€¨'"kÎû¶žÕ£Õè¥ÃÞ'¿¾}BñîdU ÝBÔ*7°ÏBy®’ÃÂMuh×LÒ,1#Ž ‡ãY¶Kg(U“x#ŸjV*`ýÔurxfÂ0‘ìE¬ØIè=:Ö·Ùmîn FÂ7·HÃ+JöιqâY¯£^˜ü¥Á |Ät§ßêëÓÒU0‘±¶ýäÇOÂ’ƒjç4”¹¹Y³â;‹Yd[˜ ¸ ¾YçàõÅrwÚ­î©xnŽìDw*ŽB Ô²;j "åüÇÒ5†e1[?‘‘6i2Ny5n l(ÍAj®ÎzÇJ¶Žö[˜¬ˆQÝNUcÇCZ-¬ëZ^š°‡ÈoZæúÒÞ'ذv”¦ÀS¡'=±ëPxl#Ѐ†>bøóƒ+ùÇN+h¤ÚOS’WÜζÒâ½3µÃ%´EDŽXªÇ¢‚jiô›yôÅE·š)cù[Ì\6*¾™w –žVèD"™<ÅóîÄ­l§Šîl4¸í㾋ÊPùK†#ùŠu9Ó²yZ×s—·±“LWžHâפªFJÔ¢öÚ)ÃØDR LŽ:íEƪ.RHm­üÛ›—%Ø®I'ÓÞ­Kᛨ4ÓÍ·+66ûSUõôøK_Û¶ñÜßZÜ ß¡SÉ9ôüêóê3Í¨Í €Tn?7`§z© %¹Ò·ÁëÄ^NÐxÆzö­i¡‚W¿‘!.Ñ1Hû§8ÍsUI;Zæ‰Ý~#Ó-ÞgÄÇ'˜LÜåO¿×Ú¹m>öM3R‚ò0 BáÀ=ñ^¯Ú¾¨Ãpb…Äφ׃Î3ƒKâ @Óô˧l—Q9Âu-è©TåÒ[3‰mNÆoI=»ý™œ¿”˜ÍtÏjÞ!Ól·—‘„bÚ$Î!\º½ÂöBEÓå’=ð­¾ß¼=pzŠ¨Ý ’fÒÛHmARæ ­Û`I•Ÿ8$ ç‘ÄÓã8Î:Ÿ­w×6ÖòjÉ25³[-´‘„ŽEn1ÀíH–¶Ù—}²:3™ àwÉõÔÎ[\T'ÊŽ#•H,[#s]]ä××öð²À<Ñ@8oaZ_ðiгqä•|µ¤9…biZ´‘O› •7aOu¡[INn¦©lP›ûMXùÆeÚ»¾lWt› KSWeGu X38ïš½®yÇNa`uqžqU¼/âÒîŠLÌÑ4lŠ¥°š|í÷éÝ$v4FÛÁðj)$³M¸”]ä*¶{þ56©öÝv(Ù­cžÓ¶Æ9Ϲ¨MÎu£+¨WžØ,S$m÷Ã| úw¬ ýnÎ „†;B¾_ ŒzTîÌdÞˆåçuûK˜”¢gåéC+±2¾InI=ÍOx¡¯â_’V%1é[÷škMáË3mnw¡Q ÛóF úf‡¡ßÌ’^dš]›ÁÄp¼¨ymßñ÷­ ¥’¿Ú•#!¶¸~½:W9üú|/f!T‘[–ïœ×P¾*ûO‡æœÌ"ÔÔ®xÝÎ3ùUsìqÔ£.fúý´š.¢mRs6ÔXÛÏåñŒÕ¥2êÚ„60Ò—Ø$'4º¼×ÂêèÏ7–¡Ã}èÎFTÕÛ_";Ý2Âu(Õe3•c9ôϵuA¨Ç›©Êã)Jƶ…àE]L.ªé5²’A Ÿ|vúUûï èZÛ ‚Ç-ä8-«½Tg°•R-Vñ$»ºº–ؿʔüùֹܪ=dìkIdÑí¼Ów¦”Žî&.ЦO˜ÉtµIsn#ÓÚU}¶éu^àãŒý;U>ÞãJÒ„¥spÓÜ“Ôc ƒXw:¡u¤Ëuç(TÞl“Uåñ2%î½4Û Ë«rlí™Ñ ”uQÁö®’çIxæ¹¾CHøaÓ¿SXÞÔF“ ޶-3Ȫ‹&H Hë.¯"WV±‚ÎÓ7˜ûòªÍaý§$eã9ÜAË(Èì*/}M©ÃU)1g±µ½´Y,mÊMŒˆÕËnõ÷Ùø{Î†ÎØTo²$l V\œ1*}«·Sg¦,’|¨êåòpXòþ"º};A*ÅSk d À9Ç\VÉ#í½:iz9±Ö—eŽÕÕ²ùÁ+ÍkØêâ{»†Õ¦³ò<´I«Ž¿JÉ»…’þFYã]»üž`$üq׫§êvð YÅö¨›W9ú{V5ÅM^%g²—N¹´‹‡ÔŒ2€yÈÏô®2ü5®­3C; ~` œ×{¬Ïyö95ˆü©/”ª1 çñ^težúô´²4³JÜ»’M+õ:ðñz¶:+˧ySs¹œ`NrHÅB`™d(Q·¼W©xcÃqB›L%Ýïp}3Wá*4¹£•\+$ĆRNTö¨RodS¬¢ôG¡u$s‚9÷­L¶Ô.?Ò¯ µ„ vŸ ®“ŰYé’Ëo5ˆH¢‘q­q'*G ñš«¶¡.xÝho_McöÇþÊVŽ+p&nKêkªÑu™õ[7¹”"¼-´î•Ö—4-Ôáæä–…Ûë›â°_j:ÙÅ»«ªºs/<õíZÚ^¡ì3Il÷romŸx†\òyõ­+4Ñ|c(·Ì†,³.ÖS©¬½[ÁiñÞM¹à¶.V-¿ž3šäºZ=6ROBýÅÌ-¥†—t¾^d9$ƒ’sëÇçL¶t>r¬Q%g®ñ´‘ƒíUì¬ÖËL’ I´¶,CÈRs\¾¡¢\&šóG6b@X¨ÈܽGqŒe£vÔÎnÏCsIµ‚katY%mÑû„=+Y.û™ä.²–&4'œúzÖ.xöÏm3ƒ+¢°+•ÁdƯiïv÷­ÎTO¼ßZu/Ìì ivrº¶éu˰0 •‰öæµô}ylÆ 0å€Ý·nz€;j˾…¦–òhU‰.Iä)C×@ü«Ë÷|œæ½Jž+[8çiŠJš|cîä(*9úÑ,Lm‹:¤?fºšþ#Äm\*ï‚ñYþ*±›I=–ûL6#nONsE<׫îBÁ#J±þ:ý+—¶µžî_*Þ'–B3µN*g¸a¡EÜ¿{ªÉy+ìfŠBG¸œ/÷sÞ§ðìjú;à žk"hd·™¢‘Jº2úî<'sh–©of%{éFéÊsÆjº;'%{¥í_Zp Ç`²ÚÄK¹sJåµMT_È@B"G>Nã–Tì¹ö®‘tfÖ€ÿZ¶u-rÃOä$$[_lÄÐÆSjÅ=)¯ì|›½ü©‚›'xükRKín;rnä„dä»TûRé ö‹›+vˆevÇæ«Gì=?…oyººT>e„öî QÞø>´ªÔWØq…–ç š¤×z„I«ÌÑ[Ÿ˜¸Ó v®¢îÖxìå˜NZŒ‡r0½8#=¿¥bj³BÚ/“pñIr[0ª˜)‡¦ìÅAs.Q¾(Õ <šÙã Û\:õçšç#FkOÿ٠s#êìþLcrÆ\úWcm¥øk]‡n“ ‘΃;1Ïֳъj’µŒ øš"ò/™P ¼ 3Ã#K¼¾•5—a¼ Œ3Éü)_Ã1Å~Rmʼæ>„ÍÓîWM¿HÕùîtÿõªå•ÌÔ¡;ò®#Ò`µ&ßÍ{iÎUÉgnÊOQX²éq¶­ö{hÄm¹p –ÔÕ‹kí8ØÊ\Ãvì–@Á0:šŸX¹±ð®‡åÛ¯úeÄ[Uº·N¹íYÂmzœÜœÎÈçïoâKb÷Ÿ½r›QÂä’;gÒ©ZéW–æy4Ë’ÊÈUСÃCØÕ+ÝVm@¤6ÈKH»Z0¹Ë{V„WiöŽ'ÃwnTá‰ô­9´¹º„ ¬f¯ˆ5Xÿwq1p0—+¼ 5¿¨Ø¢\i[ÛˆïŠ$×*‹€¤àäŽÕƒ¡hž [éak«`à•—œÆ·0Õ-am>KX¬®áQHÖQëŠMÍ»F×LM­ÎbÊî}6•­”Ü9»ÿúV•†íJæ)ï˜Ò)ó‘¯jÌÕ´³gºäÜù¬gwROó­m.ö¬%häd.ƒ;"ãý 0;ò*çghîÇO}M ;Wˆ]Å‘eIf ±Ã¨nõ©mª¥ÔöÐi1p„c žµŒºqº¹iVÌ•¸_ ‰3‘£ÒŸ§,PD±—Üȹp9Ð⛋z—m42uM}l ÊÛÌÛ·ÇoZâõMV]Vd–ddPA(¸ÍhiºN¥qi$ñYÌön¥ZUB@ǰn"h%hßï)Å )-ªqJM=ÉmðÎPÿÇã^…¦Û¡·³•$¼Î‚(ØÄ@R¸m&‘Hãg8Àô¯]ð½êæE¤m2A°ùGÈ­¡±Ï‰i»£/VŠ=9¸i|øläuÝ‚¹zã½yÝį3—åÛ©5èºÍÅÅôò¤oFa‘pK>A#Šóѱf_3•æúTOsL'ÂÍ|?~°Gw<^E´Šf—åRÈÁ«©6ƒ~`´”Âñ²:¶r{×W¥ÛiW‰l–ÑÅ3#ŽY–ÀÆyÍrÚÛÇ ¶d‘.àc–èTtüzT¦šÓsXMÎ\²Z‹§^Xx¢ß(·s|Ò¾r¿ã\w‰¬šÑQ@U¹+ÎŰÕ/ôødKIž%˜|ÅxÈ«Òßjz½¼Vó'8Yv¦œ[Ø_WäŸ2ØÏ…. O$aʇ`8Þ¯Þê×:ÅÌ-1`€&qÐWWá+}.=î+›ëO2à˜öÈÀ`tã=ë–›ì2êÂ" Tm­$c?SÇZ„î͹“–Û§§ƒî<›ÒÞ±Ýç?TCØ{‘Px«GžMNMNÝ ö—-½]2FO5£«Y CÄÄH>Ñ –ÊVTmá¤Ò$Ö4£ž¿mYßC7kk¹©£Í¨Yi"R–·0óŽ N@úšåµénojac™¶ì‡ï­cHkÍ2òKHmfÚÐÉ$ëò¨ Ã=p3ŠçîôI®ïno¬ä‚ñ#fu[V ÆyÚ¦Š—3+•¤`ËÍM¾Çz7üÀªŸ¯µXÑ<÷»’ݧ/kk’Igj“zŽhí/Ê–®eÀŒ#îcõij¿d²³ŸL³™RVK‰vñ' {V·OE¥ÿ«‘k´ýn®í®ÐTÜyˆîrÎ3À©tËIío·K9uQƒüªMQc­ÁwA.ض·pÝÇn•µð{XŠÌÈêD¤ñJãîìm}59 u>\yæ8±ÎGÓùÒKšZ):T×qÚ¥ô6k§ZÙL©[F ”¹ÆzjýŽ “Bö­ûË”PÒ(@U¿>ƒ<Ö ¾—s©ÈdÓZ„)êÞõðÞ­vó\y,1ó²‘ÎßZÚTé´¢ÞÇ<¯{×ÄI¦ßÂѸH‰Éêö£Y2êÑÛ.Ÿm ±II$»–cÛëVn4k8ìÞêâýÚÑ“rºdŒ€3úVzÙ\G Œ¶À­Ãg.€²©9àöâ”eÇ-Q\¼ÒµÂêÒÞÓÅqA +mL¸·øû×Ykaksá«É I2$Œãû¼dŸJào–[ ØÖ£®þo›ÛÔV•µû][ƲyÉ#s {Z$›Š³2ûW­­|ù@Ž+ Ç&FPNOlÖ}Ìpi×l³»û5Â)eXÈ>•·.™iypÖ…å­²K&Òcœ•™n4Í:áåPÚ…Ò«ûJ¹õŽÞõ½¬jšN÷:;UQ§Ä«e ë•ÌXÚ„óÓ¦s\·Š­ MCQ;ÿÕ¹·SŒãïéV Ö5+{Hž[·DB¾fã´Ê;€ã¨ª>#·Xd’çíu¼¿ê1Üã#±ª„_2ÔÎö½Íï iVS[ ù¡Ù•›ÄBé\ßÙK•»¦™kçÞ,šyckI&ï/Ž£Ÿå\T&™q¯\ÛI¨¯–”!Æd?SÖ¨Ýxàºd³˜ÉjHßÁ_4wjèæi’âÆâ%?+ˆåp äð)r¶ôw6¦œå¥Î®ïI°þÞ³’%X\1;!L¸=ëÃ[¹¼ñkOqndó3ÆH^›k u{¹n­ï®ZYÚ೑È玕Ñß_IÊø‹JA]®Æ$cŽýu­%§+Ý‘ZެéoŒz%«Mag"JÙTd†R}qÚ±t½{Rö°ï’e,Iƒœ}Oj—LñvÂæ^Y.&ÎÈÐ1Ëç½:ËÄúE½‚[Û٪ʤƒ á½XÕÆZ5s™µb sM±³Õ|¸§Kdf!•`sØÿ*ŠÝ’ÓO¹¾HQg…Ê"|°1ÿ×§ÜivsÛLÐêi5ÚYå•On@ô¨­o×ûNÁ¢a¹c9$Ž2~”ª{Ôôw±¥5iY”5+õþݲ,qDë‚W`çœãµbk‘ÃöKK¨fV.8ò*ýþš²_̬òIåHF%Èfúæ¹¹ÝÅÉ<§ƒÓJ)´îc+¥kå…ÓI Í•cy0bdóv3çîŠåu;×K•»`Ñßo>|N¤a»þ» 7U[ ÚâüÜA4JHó‰éÀädô&°¯´ýI®·ªÙKo”Øû¼ð0zÔÃI4ÖŸ˜žÆ*„žñ®o H£ÁʳÜ}«OM¸‡SŠûL‘öÛLKÛ<Çý\G=³UµÛû}B¡¶•¥q&q´ð+JÂÕÒÒ%iËycåêQõÅTåhó=E\’ëXÁm¶„¸ÀŠ=«÷ñÆsߎj©…-íÒ%ä(ÀªÚ|;ý°³årª§ 9íOÙÄ›9*Œß€®)½M-­‘Ýèúœwz n Š<·.yqùž¿S¸V|m1ä÷ô¬ÝÄ}*YKm në9Ø+zröìl÷¼l|,Ôc -{¬mMÞ§ko"Y]UW 'ÚÛgÎëŽd>¸®gO NÞñ™Ü@êÅqÔž+±þÛ†FÜ–Óc;»zæ¥J~Î>ÍØãÄEûFÚ)Â=7OO9¿Æ¼«ÄöKa¨É1,i®# rFxÉï^¯sâx b¾¤U˜|0Mq7™}èŠ`ãÜŠÓûlk:¬“€‘Âæ4‘ˆUxš§¿i&œÊA"“ÀLàz][.SŠ1ƒnæLºN¡-ìvp¤rFUÑB‚½Z¼xtÔ].ëí6òÄG̘)¸uo^´–~'Ky§óà&ÖXŒ~\gƒëRkßV¶‹P.Öö±Æ#†<‚ØV–wQšÓ˹<ÖwLκº•¤¹c?Ú§~V\çvêj”ÚmÞª[\_ÀÑÆ›$r‘ýÁ×§­u2¥ž£%ÒZ¬_iEÝ ¾~÷óªzî·¬2 ;‹ØV9P†Dq†©ÈïQ ý‘9wØÎ»Ömmàw‚Oµ4®puP=yë[:ÏŽÐi¦KpÆîàå¶œX×ÈÍfZéÖ¯ ¹™Yä™™ -»ËG§¯âOJ4ñsxFþ^âÆAÇaÜý*[§tšRÞçÖW—¶ö¥cÉɈçþU½it[M–Ú6,=YXtçÞªÝ ´H¢»ÓžHâ¸äÇ"ð3YŠÓM8Øù‘Žæ~ÃÓs—µÏÀ·RH¯ Nê1’N+¦µ¸º·€Gö<ó“Ͻ<9SsÐÃY7h˜îCÈsÓ#¼îí ë^‘¨Á9r–$œ`âu+ mÆ%P­ÏËžF=GjÛ e)ך’V2´+•´ÕÂ9 ÀÆÄöϬT·¹þÓk)†.ìÚOJÒ[+‹»¿*Ú2Ò`µt6zV³ck.Ÿ%äP¯ÈN7¾9ç=ë²SI¯2)9'tmé¶›¡:jÖË ;Á#'•Éø’Q-â…QG GaÇ:ë’ÝXÍ,²ñ»3€§éU--¤ÖõÓ–g 轇§½l­«&ÜùÞÈbkwI¢.—)“y ò{ÕÏ ,w*µ’òdؽŒ§ƒ­u:7…à°Ô-%ÃË Ž ÈÀíe=íšÅñg†î,õÆx"&—Ìxõô¨Mlmí#&â´¹jM&kÏ î²¼—í Õ‘9üª{_@.áÒ¬­!þÍb#ehÆdÏV¬½\ŸKi´Ù!iØîŽ,”b0qTŸÃ÷±¬?Ö¸ÜS¡O­m)ó%s8ÒŒ[R;oxJ;© À¾TqF"‚(€Û§>Ù¯9¸KÜ›9LÛäñ÷}kGCÒoµÛõÚÍåÆA–fl¹­ ½UgñeÃÇ)xn$]®ƒ•ëÖ·¡RJë{V¦¢Òl›MEŠúùoçH"VÄo…Üzö¬oK`g·K$‘0 í-”#¶äT©}%®§"cì±3;cŸ›ÿ¯^f3©.U»= 5i+½‘«~òÈʧ¯?•U´IÆõäej)—sŒFÍÉõÅ_²Œ®žÃ­xÕ+óžš§ÊŽA‡lÑ€¿zEÍtŠƒv1à îk›´’[hÑãÝË`9«ÃQ½eSÀž¾Ÿw8R4æÝÑâÔŒ•GcMÑI9V#®6ŸZóoÂ>Ý1Çñ7ó®¦ioŸ?é {‡¬NÊhV`d”çŒf¹á%†¼÷F”é:–ç˜_( vñž*ç‡]‚H·œãÒ]ZA "keÞ;2úÕ(#Š6Gq‚Ý_®¦a ´ÜRÔ•9©7¡™âM3‘ªD¡wN=Iþ/ƶ¼¡ê6ŸjFŒ@íÀsùš<ˆï!–ж|å*0¼g±ü5Ïè7Mv–ð,²ÉŒG61]øJÞÚ—½º0ÄÑpºŽÛž“.—,±y{¤ò"ùv«—ïÓ“XÓkhÒ’¤fœhœÄÔô©ï„Ü®šð-Åäh™ÉR«ŽrÞ³tË›9ˆ”Ú¤‘Ì1å°à:Œõ"½F<ºžl”®dÞZ:Ì—$q[l-w'fŸw®YGha¶A,…Bs£ëÛéYºÔ¯ Bò™ãƒÓ€qŒWE® t]EŠ(mÚ7·"KoœŒœƒß­ZNO”貄S‘•àÛé­üA¢–’Þ弩bê¬"®jGj/tÉ–9ÕÉ‡ÞÆx«ÚmŸˆ5‹vj’Ê>X㌢~zÖ=´¦ß^XÜF%YÈ@ÍÀ“ ÈÔÖI7®¦5gíÒ±CS´Û¯Amk ÌŒž W¡¿—NDƒH…–nL°ËÌžÿJʸ’HµˆçŽ&R’Û'QƒÞ¶-¬~Ø"¹ÑîÎ2É,¹1ƒÆÜg¥:·ök±ùy÷(ÜÛ]k×\•¶š&á™p£'Œ Ö:E„vAþ†á/˜NÉÇ]¹äóéX÷÷·š%ña8–àåg,2­íƒWÄÑ¥œef>w–‘mVl`çõÎ½´,ëIÔ!‘H.!U~@ÇëUµ-25Óí$Ù݇˜§^hÕ'‹Î–ee(¨aÜɨu_i,1G$@L@[å'ÐJ!6š1º»;-*(b± fC#ç/o­CzHr»öžÛ†3ïšnŒeƒÃödĪÎ7˜Àþ•ÒH:3/qƒ_9™MºÖ=ì¤@¨„ïÑkfÔ»žÇõ¬¸íÇ” 2OÝïW×÷L Œt95çs¦÷;46 Ô…”›|øÕ˜s»¿ÚVñ¦<ÈPQÆ*½¿öK@MÜ1µÁS­H‹¥OØXúåÍz”ùe ¹#É©s»@†}n$N.ÎÎVlš„W·Ì,Ã’OøÕù¦°T,šËþöqQ^jV—‚F3±“\¸ŽK?zìÞŠŸ2÷lfÜ|¤©gäd(Ï?Jä5Çœ‘‘Iù¶óVmu;¿íuóY•%ÎSÓð­[Öeı¡ñÏSN•7…«u{›¹{zo•Øn“²Y[Êäy޽}«*¶Åâ¹ßL'ÏŽG(êjÕ·RB+.Щ'ÓŦ[hw—ú„14·’gÊv;ús^†¶æúع((®¶ÿ#ÍgÖ¯nn.f’_Þ\²•ãpô¨ãÔçX"…h‰Võ£c6?•2€øÏ5“:]ÆÈ6á€Ý zÐd8ÅÆé'Y`¶µf@TÊzƒžk¶Ó/¢ñ‹=ÅÓCÝ”þtfVàŒçi'·8ªwsÛE§¾hK…ëí\Y.ÌX’I9&µ’±Éíã¯CÔ.‡µ{¤4vßh Àg5ÏʲŒ°ußÓ\µhNœ½í.k ‘š÷D“ÉRÓ¬HeõÜ?ç|IåÄH±–‰sœØ÷ªšd÷M®íœåÎwî'“]„`Í*¦ø÷g…ÍuòË V/âf)¬E7ѶrO§iQÞNŒžL ®âyb>Q®+"oê[¤K'”þyŒT¾"ÕEüËkn‚#Ëâ>µ§¥x"[×Ky–H¥ ;±èªyþUíaéòÃÞZ½YÇVpNòùœ“I<§w|pI98®†÷ÃvM£SI¿7Kı¿ ¼ã§Zšÿ›"’[.Œå¢;Á;^•ö·:TByWi“…\þ¦º¢¼ÈöªIr½{ll¥½Ô­í¡€Íy€:ó“]Í·„ôÂÌÑYD"Sƒ$®H÷ë\÷ƒ5 – ˆ›jÁ™]¶‚Ç®yªÞ#Ô5‹t²·‰’4s"¤C …)sI™J2¿*ÒÄzÔº7Ú±§ ÂýæB@>˜Óx‚DÔü/§ê¥ø\¬ª8*܃ӧ5Êø{Óëz˜·EœI!tú}jí¾·y¢HtûH¼ËU¸có.|ÑÓð­c+?B*Á;(»²íµà1Ý}šA-íÄž\¯$gÓÒªøƒG¾ÓYo @BŽ˜«Ý«by"¸S2ÈH™xç/µYfÕ^âr³ÊÝÈÈ'Ö·^OÔák«-Ü눖±ÁU½ å´ñ–QÀç±ÅV³²y_͘–'žjœ:}Ñ‘'O½Ü] »`"4R«ž?ÕœVròw´F»GJTVûÊÖ´%ÓҨʬ"±cE!M¥FTz ÛÑú?KãQ5/sÍŽÃ Ùçµ|ññ3ªâZñ@ˆº‹K!Üu®[(8¶Tníœ3ë”™w¢îçñ†ö\fP[|ù^èÚj]Ú×eÌO¯©û3?ÒÎ&z9!ô<™2çñÌ Ì¢q'ˆ÷N~.w.ØasÞ)ãóæS¨×‚­ {¢ri){=d×P0xd ›ê¸ÿNÇ®yÉiF<ÔÛÚt_gMr½³'iêq²cÊ3ùE“<–¡—ͧ€6û?Ò/;“v¨ó¯èÌU&”ÀÞÿ‚ü§”tŸ@Ù,ê[éêÍ4þ^~„cõlŒ¤„ç™EžìÌúM¤|º»ÓèìÉÌKMÄ®âÞ3l¼ÔþšÀ bt¦Ô3ZË@æÕg±ô¾ÊtÈÕ\ü½Œóq¹·¬ ®cGV*ÎU)°Ñœ)ôG$I®‘°íd46Œ²“¶}’ÙºS‚-Xîhûé’~ÐlÔ‹j‘{~ï'.xVø=ôœÃÙï1ôŽžÊÜMb.÷Ð|ôÜÖÅò#G>—S?×Wá°ü§.3æÈëâ|8߀™Þ34é°·ÐŽË?¦ù™•² Îä«ÈQÐ&ÈŸzQÏ¿¡d²zØù{”ê(:ø…¸û­àoÊçÖÓS ë³f  änºósÏ EUéÕk­ _¬Q3QËÍžlª+žó=Ò•â&¹ë7,¡×eò;Õ{¿)W•ìßPsÏl¤itUfZȵ(L ¾™ÕOBZÓa7§±äfûâYÕ6h¿ô¬¯Ï{£Ÿ”µRÚ3v[ød_KÄù½¹ÞØ,<¨9lòØ‘¼k5ä3ÏSœæ‡ÜeêuùÝw{x/+2‚ÓüµvÓr´.W<#íkåDÎÜ®¨ävŒ£¡9øÉ¸Å/¢ËÐg¯ï<ôí¿”g”تKÞºÝkíõyEÙ$ùŽÜ¼_In,¡I¼u¨ñH¶F««Nø­B‡P*þ‡åüÆj£¾Sè'ô9×F1~« úä»ì\Æœå¤HšŸ9Ú ½æÉHâ¶^oÂý‚áõ(Ï ùïª "¥V¼ÎÔŽD¥þÃ2q”PU–`v»±pêâï’¢'=+|Š|­VßõþRÃ< õ²¼©‹b¢WNQøöVÎOÑbæÏÒo^y¨?õu`š"-–›>¨ñ÷+yh´üÃîC&åõ¦Ä*à¢[^qXŸê2ÖjÇÈÜÌçÐæ6uÔn„ª3fµ²?ˆ+è—un>¥WÈ}9ŒãO¡ÍüÀ›ÁÒŸ`tÕ‘Ðgéüʹ‘wè\öm ˜‹ÄÐÚ˜ÂW¹ùNކ=^ ^-)O}Ÿy«k€§çD*Ñ3°¡ºZç.]J^ ‘XV_ÑÿÄ'!"#$%123ÿÚÔˆ› ž ©É§¦¡Éqy6ð×ð†'×9¿9´ù‹·vn#úèYIílÆ›u4Ï ³5¶XÛÌy®þs,,`Rç6Ø>+Ñ Æw¾•ªö¬¶í;¦Â`nœ"ÿÊX‘îÒ¶r0å+]esEª5¢ªé4kŠæd¬s¥ô9áQò+R J‰¨Nž`æ®’S!rJ{կͼҬê“£WH•Uéj§;'zyôüiŽ)£!hÒ•r+ çå²Å¡?C}þV:u句ÑU‘51ßǰ–K\°R²Ï¾lÜ\u#†µ&œÿEêf+ÒØÒêTÇqÜoy·+ßú8•ϚŠ^ŸÈX 8U%0üaœgçKkLRgAT!‡jºt³ÿ.‹L8Ù°kÞFáãÚ¨1¿:󡣚ݚ’:TvqvÚÇ9Ø™½GrsóŠâh «qÇ»t¦ ýuNì_E•OO‚ÌsQfLÍF°h&«ý œ»HØËl@¦±æÆ+’BÚÎ׌·õ‚׬ñZÛÖzŒeÔT`çºò<Ĥñáá!cÚÖäÔeƒ t6ÆÁU@úeÈV’oHí/§ëÓê,”³Cî„NµÂªBåpV›,ïÌÆÏ—§`wQöU©44#H9 C#X–Ïšl‰¬ælÍ+;ï©ÓÙ Îm,ø¸’•èÙ×/™jÕUƒÛ*¶m z­öçýÜ yëËékŸ5ˆ‰çúÊаÝUÖW+*Òy{¯ÊÊ0ÀÙÐe<`ùͳì.#d;w»S©´Bûv±B"Gè±JS52‘mÈ0…[2Ê [9u´“)öM{¸¤ZÌh¦:¬]“cc1™¡´Á”EM‹6&óŒžÈÌôiü g>³k‡Üßðâ;ES˜äò×–Å|ן7›šÐ6w3O8ÅZZ‡—œÇj®ü7õPÒl`†JÎXÇ3©] \í$ú™9ÍoQ 7×G9Ÿ™ô»ðRÑ!¸»SSw_úàGJìéhb…qfë8Ãhwc'1²©=T­=U´äi’ë§*ÉèŠr£'ûÚM:ú8ƒ£iè™Fõ«B 攣̳Y$ñf× 1¬­Zå¾f¯ÒLP0‹#žfà¹Vm¨UÕœÃÉM¦1YhŒÚ»¢KÆ†Š¯\f:×Ò—VЛª~ÔÎçÓY+šbÎWXƯ›kö[a™%×/t¶4†àç9¼Þ¢a£›}:²:'3§þôÞ(“®Bå–wÌø÷yÂíS&F&%[fƒ¥Zm0¯üN® '™¤#«Y†0árgm¯EØC]¬ð4Ré_Id‡‘›ø«¬Óki¹z×2VHD8È)GÞ;¢J„aI¯—ºùõw­×Ò.Ó´¯÷-°¢ÒÕOW’q­QÉj2¬qy·bwÖ÷iç+ù5CJÀ£z›è5úfLÓCÏÈWÅ9²ŒZ&¶ËQ¡J0yT¸¸rìEUZ‹±å[V¼X]=K ,Û`™, »Lå¡fΡÒ_¦=‡ø“G‹¯7噃ì¸ý ™úkÖ™‘ ]Àü¸®UÛbåÒ€‚ë’UZŸÄÒ×´ïPÜ·òéÅÊZFÈ)L]UCôRÁfŠç“!⽪zT!ÿ(ô¡ÓkBöLn- ›Ú<µ«Eè?.vT¸â ’dfŽk¬Æ|,OIoä.G22Õ3}€ÔuJgXhMDzEh ’Àv¦†ìä¶îª¨­ž†uªC»ÆjÇi“+\–ºž5›tÜP|ö/êó„âǼwÝg¹bb BÓK1µg*l¾:Š…;’ê½pÁHÑ|%5è7Rm*éõÕДˆ1>{¬;:oËôïÚò¯o'Ä™CBϾSôzukg7Ø-ëî[Jð"ÇLD,¸ÛéÑ;´P/·]Ò¼lÜx+fÇùÝ3åjÈgÕ†t ô“?6˜ëAk{#ByºKšJ¹ƒk’ÈÅï¸VVO6úÄLÖùËÝœÙ~¶Ôào¨R¾x¤"‘/œ%Ö]'‡ª)X•c¦ój˜´±.°§¾]?Å5íN£X/iÌo(¾Êq[áæÍdХܴèVh­R³²î¤mY›¼Lº#õ®‹·7±¨OŸ)9Æï¿ïuiÛ‘&­Iåh8EA“øáEä‘V–é}%6§ÓGÝöSSø©c²è\Gñ?ƒ;¥[¶`ÁÜ—¬&˜cåéûÅÖl•ò§hväY¨‹Ëœ•Þ$¡Œº™ä~cZ’|¦C¨±KÜgšéÎÖ·¯ö&÷¦”Ö¸¾¾°ãÊÓH˜ùûÒ©ÞÖ7zñJYÁ !’Ò–c-†Î]ô±é¡¤ÎÃ@dJ¥bû„È ƒîèùqD«H2AžR,´ælÄÄŽiÁÛÇ’zZ°õ@fØ“\ÉÔ×¾ÓºwWôFÍØPŒá%üîCgAp¬™Y*F´’é—ˆéZà½?½ ËrõïŶF´ê@^+º­æÚ Œ·5õçà#=ëRy_å—¾Ùœ2Ùv¿!;„¨RÇG}©»ÿÿÄ-!1"A2#QBaRq 3‘ÿÚ?9g0®Ð§ó1ô"œC–1fÂñÙx0y ÐÊCOŒk²q‡ÜñÆ[üGÅ'®"XU²gɳþã >6Ä®8–—1HÏûy•w¨ØÆã”;Cgñ=™™Sc1—jñ ª•â**~3Ê6c3‘ÌPì¸^¥È?&ƒ`v0@m¯¿û¦qÔNsÔê`=ÅŽ1úV üD¤pe®¼¸öl™¬ó÷Pú€Äø˜ŒbWž„l>à!HÌ9HX™®FaäñúƒŽaŸ‘‰I&^‡m¦eR=süÌœó+Éa¬c`:Æ@ÝË1Š×ÂÆ;¶D›Ú:îÙ“B5äJ¹n`ûÌ,vÄT¹û3{*õikúc? E”‚©í Vù²SXE†ÅSƒ>t'¹ueñˆ1ÄTçÇØq¡I‹jë˜.ÅL\U^q¶1?ˆFøæ¡‚Î9Ÿ*€ û+:‚aöæ5Ù˜»ç”p¸ý*±R¨žC7qéÛØw4(~#.³QªÂLFÁ‰û••‡ÇcÌeeâ3mÌofâ%`&~æ¨ßP“YÀ•7ÙŒæÛ‘+üDò²LÊNY€%Ca?«ê&¶Ä½z0â-%‡ÄoRÒ¦8"ZÄó``L={…œ]à]úâÂÆ"(ÉÑb)UÁžIÄ3Æ\.Ó×û£¯Ï×Që*‰ãþòãøàYeŸ®%N½¾ãÛ£j¹Á90Y¦3̾бîgyA<Å$;šã“ASn3/u±r îxßúåá‹s¥g*1ù1xn`9:ÍÈâùYÆ?O†@ 5U¶ÂyW|¾‚|fjW¹ãx•ܳ<ŸEk÷Z“8‰k)ç©n><Çr c« †îxç*V1 úÂI<àæ&"±ú£&RJY¯ó2Î,h–OôÛ(a•ÍŸü–'ȺÏÜJÙOªÏ)†x•u-([ÿÄÜ'sAP•Ú6 âS‚¥3Ü9èÅàƒ—±f2'‘€3(;Ï,üJÜÖr% Gòo^§ŽK×–û‰@^~ç‘ùJ©ß¨ÕŽ£¼B ÿ™¿8hkØÁZž¦Ì cúxµêwiò äy?&TJI¯¹sîÙýI¯aõmfÿ9†í˜4+]‘áõhEX6 ÄǶcw¾:ŒÖ}ÌÔQüŽ @Øu¨s<  {©äÖ½ÇñÎ þ%uzäõ<£Î ÉìÀ¡=ÏSà}xu8æ/\Ï 3rÅf#(râ/“ÈÚ•¤ó<ÚÐ Ç]<]Oñ>OÜ!ȳãÅj&G’§Q,¬ÖyŽ}p%Ä´ã>%“SÜðÎ-LÀF 9F\ÃáÒÇ•‹áRãމÈאּX òî¨â^ûxþ¿Ý+ñß¹G´òª62ó­ ¡3åªÎ ³ÙŒõcp|’{™ã·×¯©M«bXXÂÂ5ˆ¿”WÜ °â[åëoƳÇKW«7§â°ù(:ëµbV%ÿ©£ÜsµA,¯s”OÂ*sÌf ú¬ñ<³ãžFAŠëbì‘Äø ž ñ‰P‡êV4]e´Öuþ¥×½ãZλu¯7¬on"ð L- >V=˜–ц²Ça ÏþÆC•8‹æÛýÜÏ땉æ>©?¬k?ij{[ s‘±ÊÀ“»?‰MÝdò<‚„*F»tŽå•”80 ÿÄ1!1"A2Q#3BRaq ‘¡±ÑÿÚ?¥Ì'„s?k ˆÿ tÂÖ[öÚ ˆþ³¸æg÷1ž–È^„ðdÛx ‰Ì¥Ìe$ÅpÞ1â1%á@naîP"3x‰‘ÏÉY1°>Ûc¸ˆÀ’²©(Ãb1?8Ç"TVí´FÆæÕ¼þ“"2G¯ÄCNýÐr«lÈ€xöeû†ˆ$Åq…£v ¬#‡½æÒªƒ=CyOº÷”é¹ÔÖ .¨»Í˜õ#n1™å*w™hÞÂâ<S|ÈÚvõ ¹½áœLüAöDBDŸŒFé¦ó>j[xÖ8Ô•;)Ùa¿ø˜ d&ìn|F|9‰ôyö7ñ2¿2ýU;df$ï- 4Ȱv,¶æ#ãâ%6S{í:ø©ÛyJ­0¿S½Î,6ûŽ ‹Q¾WÚ)ÌmØ™eìXñíI:Wc.¿PX Ýlá™›Z#Kø3kf˜vn%Jl{„*—Z ™XåkKÔ¿ôŠÛZ Žþ –lï}¥Boa)¸$í´$Þðóíß´?½Þòä6Ò˜ÌÏúd¹©w~Í ­TeG˽D©²®PT¸ÄÆ8aKq8™.d4?ï9Â@æd>ý”›Ê‰½Ä染yUóùq.E¡sÏTê%f FçÄPIÞ› ^ÀC*sh‹üÂ1ó Nã ‘Ì m)óí- ­4ä\™]z]Þ !ÔB‡çO¬ŸŽTLõ6Ú(òc°¨;„_cϳ-ÔË* ™@n! Þ~ís#˜Õ‘ññÕ§ß¼}+u/Kˆºz·´uj}´Ì¬n´ê1!˜öì¾Ä\Ï86"ÆTÜ K¨ùJxð#0Ù·V²ãiM©îTo<|ÊC¤†ò«Äê"Ü!E®¡øš…ÆÀ@ိÒ]áÜÄûöcc/‰´D¨vØDknÐT7¹ó ª)‹¿2‚çµå ¤ïw¨vMu¥*tJd'ä§»·øŽùn`˜ªQ²ñìß/k[hµ-±· ;Îc¡C‰—štcvSgiþ<` WñÞ*9»nLÔ]€1‰ÞuI²´|e¢‚¾ÍÌÌ¡ŒÁû½¹XŸ#yNÙ¬«R™’P§LÙÁâuƒ0éo34ш”ÝðbñR‘9™Š=ƒL¯ua°Ž´ë²À%àkû9³ØC·²Ø›µñÇiæ"=³Q@Ô=£‰Jó›JatÄæwˆSœf6"•ÖfXói–o…ï+“”ºµ­+WJ"îl#몳~.&нg«j‡ÙÊàF¦®nÑ€#a:Ž&¸)m}ijl±Œ³d³Töÿ˜í1Ü›À6´Ä¹í…Yß%”N%jWQÒ ¥M%%Ãûžûî m ¥¬9ö¤Ý^Ö*[‚8§g|“‰ªQ{D Rͦ–YI«;ÚUªÔì«*°Aa †jW$ÈøÚv•‘êS\~¥JUljéªé–cêz¥3e¨³KG£L)æ+bo P;€ƒq)Œ˜ ¨fv âRZËL²J VëSþb¡¨.#T%,¼ÇJ=¦œ=4îâj.!Ü,½¥oÝ7öŒlßSKÞÄMM0œMô÷Ž‚¢â}·½„©Lb%ù,eJ¤]`%=Q¾*«·Reá¸1)$©U±°6‡RÁF[‰ÓJi´c±´M[d¸h´ÓRZ]ÌÔÖ§PÜ4ÒU²›Ì§P­\[ƒÄPª/Åàv¼Ë{B@µüÌ…¯)©4L?SR)ÇJdÛ+D§D›åG.wŽ m5ZŒN"Tª̃4­¦©©E)àJº:é¹Yé÷P׊ÀÏR#>f‹S×§ÝÈbx”Ó!¶–è‹óÌKx2ˆk+Jk’MMsH„§>s˜à¹ˆr}桰ɼJ•œß/3-·”šæ CÓ¡†ì{Œ®T%”Àì­Ûx¡ë Ï÷šjUñ>a¨Ê,%2Ó.â"|Åø•k"i âÖý@éÿSjDˆÄŽåKÞpF0üÖz“áI­&;KŸ�Þ-6àG£÷*iÓ›ÊZz•A¨ÓÓuTé2jšÌÔíqì"ÿ1Œ»Zó°ü¥äo)›%¦¢›+vq:ŠÜ˃ħU g©‚iµ„ÞñЂo•˜šº´Öêcëë¾pê¦ÌÛÍ7¨%="¼OO Ú‡&û B‹~¦Íâ]¿†\ LŒcÔ[Å'Ä¥òaxÌUEànÀDd æ&|F"byólG7'Ë„5-”ÌmÌ&fÂÈXÍn‰uKØlDej.Qï)½Œ]jºâÂUÖ.f¢ù•­UóæPÔT_ÁLÜM/§9Ôæ*÷ne%é±#`!”¨L…°a,)£ÿ¥é%Mœ^7§RåvŸ °Ù§ìÂFïÒé¯-xšd¤mLZfm®eKyeS wŠ»Ü\eoÿÄ>!"1A2Qaq#B‘¡±Áð3RbÑráñ$‚4Ccs’âÿÚ?UB Ý d?:FÔÙ›.·­FœêS†$~/ÊŽ¢9uð³®ØÜþ!i W)7¿ÝVV\rm°ZˆESv>íK<²«CBKcµéµ­k¹ÿŠiáy'Ò,–é¾ê|ýE2ìBµºWH×Ü ¸Çp¢ÿ‰¦ŠE¹f±CçFT&<€+-ZI[–b7óôü¨(|ZؾšŽqãqóoJÖ¬ö³Ý°òò®%®) Û–ÇN]O¯ÛH$0³lv¨¢‘ÖLlq#asRÃkÊäó oOÎoòÉÜRð2ñsùZ¤~ܯݨâH‚öØÖŸ.[°oRñÉ™~ƒabEDÓÜÆÒ§{zyÖz ÒCÈÄH§®þtÿÓÄʳþ4Œ EÛð¦q±b·¿Ùz’Xõa¤Øü£k~ïCˆí%……ÏJ(`|NÀ옆;'÷¿z!ãõµ+/Ε·¶BÃË¥|*ª‡>.$•¦‘¾q¿1Z…53¾Ëf®õcxï{÷¡¨SS$¬@ ÉÓn¢š(€‰AËCúÔ±™˜´ª9Û{ñ½)n"¬môø»Ö["²ä…‡áWdâ4q‚¼Ý«I67–åéµ|ï™Í¹¦õŸÅ—ÿø˜Äø±©ÆÎ¶ÞÃ¥qä+xW @anµ›â£ÓjÛÓÎûÔ1£d`ÖÜcïÞš.3¸-̸ý•óa# í½iôbc•›¹_ØŸö¥“Jüd?-’OÇéE™9ÏÒ/J±+¼·k*ím¿~ô‰g,QLݬf¦ÓéÐÄ8…¦õ;þUÄ|Þ?©¹ý+OªƒO†—жRà°ß½J˧‘Ñ·º­û ×qTXÙ‡ßVÓººÉb1½pp\ŽÜí0ú¶®è·ØTÂV‘Cãü¿Ji5#(ƒ[ ºï°ü©y9¯†âßm#í'/¯Za4®gXò´ž½©-¢“O©° '‡Ûj‚}L­Ácˆi $Ò–n> Šÿ¤W97Õ Öža “ƒ|¬Ø¿æ¯ÎD±ÿMIƒf—5­\Sø&ŠÂݘt¨£›PI3*¬Võó®'ùUØ_éâ¤Kb6¸éÛ½_½iä1±œ)~µü•ÛÞŒ‘Lj¾7í½.ŸQ„º$˜~=ë5 Gh›°÷¬Ûó¬Ã®Çqßî«‹§l—kR,e#P¥¸ä[·]ý@ûëMÀ™œãÔ^Æ¡þW1‹ Ý|¼©s¸6_!møÔšœ8Qþ~ƦŽkñå^ ÿšvD‰º§aQÇ$|Hõ'‚=hpF›>l»*ÓÿªõN›„T $[yí½BÅ/ Œ¿S€¿z$à÷´†ý=ºš:fù˃t4,¾£Þ›M©ÆhÛØ ãqfÔG‡.ƒ¥¾ðk¬ŒÈ×›íûµjy “—ûû¨–ˆäëµö©Zd⢠Yop2|F ™ü)Õ>V+{žõ9Ö4©ÌÌ„ÞÔøcŽÜÃÎä}µÁyq!yd;OÒ×5,zl ‚äÖòéM?\UTïýµ©Ôë ÜJV(þÞ¾ô’$ÇP:I×δó%–;Ÿ«Ó·{ZÆ´ÿ=£ˆ"6MÜ/§½i"ŽPÚ>+a˜Ùâ—WËŽc`˜‘øM¼‹'^a‰©>]h“ …íV5Èÿ«~ö*±ÿšBÀi[ší¶;^±fN0Ù&F}Å$Y¬ˆ˜©íJ¼ÊF7kÜwÜzP‡Oµß5•|ý:SÞк›saø¨\8!H$3’ùúQIäkªX€7ëRˆ±2F_*xºtðµ³`¹¶;·/CLb©'»_zŽM<Ë=Ô3…Ú¸(ùxù†öìµY¬.Ñb@Ûn´¬›¡Eè? oOÇÔ4 ²Á½h(êíÚ±|:r‡å÷Ri8CM,—ÇÓ~àÓÅ"ª˜Y—”u¤ÅCoѺUã —^½$h9œØVWÂàÇtëïo²¢P,†Å<ìoÖÔD,!œä”/;{oRp‘æ@>dŒ—˽ÿ‰.lNù¨TN :Þ=Ï^â¥`¢sÁì~íR¨`]»°ßo/ßzÔ9í!·½èbM—¢ý7ö¤vþâýMGÇÚ9TøwÚ‚,Œ#ÅXüquÄk[*YvTMîÝéLsÍñb‰áûª”|ààÝêxœ‰&Tâ¶ý|é]wå,},`Þ^ÀóÚö¡ Óΰ"àÎEšCõFCøÝÎ~ôcÒ®+WQ âç×Ú¯¥ù2:ùuû¦ƒøy5Yð^¾µÆ%Ld©¨QÛÒŒú˜ò[l ‹ªzûõ­F‘%h¯˜Ù/å±§‘d vÄï½<-šù[köÿ?…§åõ4¤~CüÐ ô ÷âmXå‰|Š}¿,w²!%FÞåY ¸V[ðä7cKÉ´à–á†Çñ­F¾ÛǦso"+ް´ÉsvÆô$x™Ýee[z_©©âðJE’ý½íÖ´ò²eƒXw©$·Wg,¢åüïøVŒÏ¨ãhÝñWAöyV_ÉÂñTÝzëOó#`«¶Pó²E&yS”“ýß…­Ú‚I‹I´ûÚ¥Ó=µGp/so³Ê¤13‰ÿËØ}=})ÈþS¸·^ÿ­ Béå;rº}ôêÃÄyT+Óv-íO/…”–­ÌGKôô¨ :±§‘Qpí–ÃkÔzF ÆV#m½ï÷Vi43Žü&½ªKrª©$ùŠt—‡ÿ¬~tˆ­yš&ú|Ï¥|(“6‰rî=éÕà›#uÛñè)mÉâ7½«KˆÍ¦Š0b{ò³ËS&P””›ÄýÔ"Ñi#I0Ô1Ým¹¨"஢¯6þ‡Þš ~Qú¸{Øë<-üVêiÐØÇãvQºþïùW1'Œ sz[aRDGà'­;Æ¥PôµEc‰³‹ÿãLï4L‘¥‰½:ÜRFy°D9ªê(`¬Iþš<„nÐSO¥’6r¼Ñ Ú‡(Ø·Þ³oŸÊG1óïSNÓ$qÆo+7QëQM¢‹‡Á••˜xȰ±&Ž¡µ"Äáó¯zÓhÍçøwbä­»ô·ï­MÀá˜M°ÞÇýèMÔ¯nŸ•.¶(–Uo,x±9ßÃÓ½NIƒ$ÈìÀmO¨i-žÇ„žÂ˜&XŸ«)¤#nczUdâuåóÚŽSó¸ ߦþFÕ$:t½Ûd,ºä#‹ŒAõŸ[vNä«r!ºúÕàìAØ]ª2 ‹"ÍÄqâïëöRðNM½¶Ç¶ÿ­,š‚"% å<ö>tºi¾,â¸,‰^[ízáuP=ü+ºŸîË¥4‡JcšF<½ .÷·ù¦ Ì„øX\Îõ6xãeëÖŽ¢AòÇïí¢Igÿ±2'ô©4ÂÖŽü©“ÄÏ#Òôô¯…šq§°âY“c¿ÓLƒ„·9dVçËaM.)6 ‘Žø•ò&²€°ÓÌô{ÚŒZX r¶_-–×û¯LÍ/bç‘ò³oZišQà+Ð ô¡ ëÐs0¡£)%¦ü²Öûêâ8¬¯”‘.[/‘£)ËžöãlW–´Úuƒ)cÝ£êc_Ìâe$Ó¢ieäW6ÊÇ­iÈ›â»0rþßuJdG!— Prõ=h@ ìîZöåߥ *p…;t¿zøœÇ<0ÄííRƒ‹Ýǧá½7$V?þ_:‚T[0[òsuÞ·7©çY%Ó"ÙóoÒµ!¤“PÎn"p.ûzT«$)bXJ{zWñ¦ ˜¿BóWôóµGº¥É÷^¦Ý™ŒÌ"ßøÔzB¢ib‰—A¿sûëR¢œ_P2Œ›öíöRÇÄ,0-×¦Û àä hÙzÞÔ‘¬¤ê årª\µù…í·ûQ™Úó˜µÿÍ$2ªD²±0?hØÚæÝÕˆ1Ä_ß•I/w;{WaŸ6æ†R\v‰µ? KØo‰éûµ-¾ÿÿªVÄCËÕ·-¾ã©ó¤›à`2¿ñ{T³JÆ«€ˆmë&+óò°ô¦ù2pÔ"1¿K~´‘"*Hs+ÙHî(ÉV…˜IƒÛé¨%I¤’I?JJªmãŽ@ÙÞõ«"'$åýÞ­Â"á·ÊÛ[í¥yY—éÉv5— †÷ÿ¶‘e'/ZyeçbÝCUÜ;[ζÛ,s~†ÖïVp ¥#›[óùÖá®=«UÄ+%HÈõÜÖòÇ÷*ˆDèä&ÇÚ¤Œûô¥Ó/DÝÿÕþßæƒ0º›ÎÔ8lu'0x m{úö±µqõ1p¸Z£ ùS2ÇqË}îÃì£ Äl×!•C§Â¿Ò=ME.”G˜[ÈÚcd_+TäÄ‹.>‚¥ª©U6IHëöíGÃiÝ1ÎEºõ7üiþ7 œ¦êäw­"䧨ÛcëRË)·-Ǧ‘ù«ZþÇzÛeDbß—çj?WÛI÷ZßôÛÊ¢vðeÍíR@ù1YÃí¡6¬Ë ¡·_1G„8amk”lƒ‡ˆù÷5§œ–“F@pª6üê=_ñ µL.ÆÂý}*)´y|ßšL‡dZ:_ šæš]¯ .ÃçéF)cÓ®&áP ÞÞ}êHãN3ø%2rÛÔ t½2Ææ9Ðf0æ…`¡×nA¹5Ô5Éè ¨4Úd¼–±ÅmsFûÈÛ³ƒ[ßeêÿJõ¾Õ¶•ºÑwˆÆ-õR¢‹±6¡tÏÿPŠù†o¨Èϸ`1ò§rÜøn6&µq?¶Ã1c\MðRc*ݘµqà£ÓÅ¢ô¥ÐÇ$Ù®Â5~”%”à±4€^äuü«RfŸ?ÆŠ$M;­”t°ÿ$Òê´°ÈÎÂSjÖ@†9` dõOJ߈Öb-°&ýmRjµ±—rn£~ŸeiàÒÄâXÎ%-{*Ô†ú£Žk?ÚŽ*Ø Âõ>æ¡n`då°QBßm¨tý5Ú••€C²ýÕÍ)ü©Û‹“ôÇõ­ÙMdì/ÖÂ¥Ž×Évß¿QRdȱÅÎ×ñj†C†S¶; ãÒäþúT š<Àõ¯â2+™€Ã½”Ý¿JQüBy>¼%¤&¥ÄÿÛ›FIæ·C÷W‡cËnàÔï<ÿ@ålqâzÚ–f¶©‹q2|Jû ŽÕ1^pW[_Øwµj$6¶w½­múQ²Æ1VÚöë[ýô,7®›ùWþÑý,jÿ Öõ¢xjª:ÜÒFÌÍf íåµ@…N^~µªÕ½ãᲪßê7ÜW•3ßÌ^ûÞ¡iNI¾q)ì…NÿÃßrü<û…¦&¹¾Ž& És{~&!‹¥È âëRI«à®®ø^Om¶&¡y%>[sOÆ9ñ>¯ARÈ’cRºŠžåQÎ=˜‹VÖûè[ozðФ?`«aÿ•5°=V–eq¹þ(wf>Ô“žTQÄ4±¤Žùa†ôÜÞOE©,–p7fíé_‘®2pMÁö8?ZþÁ8Ñ9.V¹;ÿš˜ ÂT;qNö>ÇÒµJüÀïzÁõ1 A‰M·÷µ ƒÂ©DæqæÔz‡`›e·/»ð£×ñ¥å>à×zð“íW×c¥bTåí\¼¦Ê×h#-ý@¤Ò@-kgú .©Ê¨d&ý ÑÈc”otj©,íÆá Ãú ¬6ïëûí@±$µ‚ƽ>Ê—øgñ ñAäß ºÔŒTM#Ë‚"7—{Ú¸’éZ>kªŸ«~‚žŽ­â–MØÕÈë^=ªñ9R:P‹W‰n‹)üxÿÛ[и#Û¥LëÌX ­Ò‰ÙX÷’RÄ=M ‰y?¥z-e(wN ýµ®—OŒpG6Dª_=ï÷Uälµ(·Æ8òöö¦sÔ›Ðk.mo°ÚŒÜfØ-¶ æj}N¯M·.-ò>]+â t£ø‡†ÛÓæL²±bN4rK¯š‹ÕÖöõ®ŸõÁ[‰ô>ãý«æE,gÍ[/ñCæIìÉBBÏbmˆQΚEÒ¹Xú»¶5$ŽU$kª"Š9 î;Õ‚"úÔ£,K\Žn¶Ç•,qpźj&PTžÌ?{Ýp¾ÅwµFc°»¶æ¤S.J=-_ÿÄ&!1AQaq‘¡ÁÑð±áñÿÚ?!f-†ƒ`}¾°_nѽ'ñ‚i¬[†\»Á¨à°‘7ôŒy“€IàU/ÇxÂM‰,~óˆðd›Ž9#ÆU4™!i·ëDÿÖð kÄDFa>2õ‚ÖÞ†”æ{Á õª“ür"0 Žá~\\ËO’ûÞ@¢Ùrž<ÏÞÜh&D2xæ0ŒLAéL¦¼dJŽò‡gÖ3ÆrD› ïœ 8g<”ˆÚ^q7˜SU“ &\†Ï÷xâ[B¯—’±åˆ¬ºl{¥¯à~†×2Ë Mr8ÈŒ‰ËðŸ8ÊS² ÷ =\k^vŽ£(©aSo¬t¤ ûÄŒN0¹„ï Æ¥¡(â19ç&‡GF"€tQ_Ÿ8޼AUrÿŠÎÕQaÌŒ‰ùÁ. ¤Wja5Èš cŸûŒ|ñp%Ó3­IÅeûË«óï"-ÍŸÄáÃ2j¨|»îµˆ œ^ñïä[ˆ–ÕOñ‡AÑM`µåx–áw§j~âQºBï¯x‰T*®—øœŠ‰ØÇ‡XÖT€©¨ñ¯Ü0k‘Œò7“¶Gí5‡h!4gÚe—‡¼Ÿ€ÑóÞ,ÒÀ„óˆë„h„]Fõ¨¸ËYÀE EOXÉ ³êh:œØ4 Bùç¼p;9¿KÕâ!9ØoÅ[ùYÔnòy!¦¿û7iHèiÁR;±¥SÈ1\xQìhu»ÞHïBxÄrfÛ´®¸Â¨}2_‘ä,†dE¢§èp-Ü6haʹ\i¡óû8»:`ÒAB¼oœÕd®¬ 3rÔwn…#š AÇO?™# ™Î¡™žÅŒ© PpîÁB‘ì«âsfÉÀÄ®ÂïÖÑÌL]îB[Œ9, èžFÍ@DcRO: á…*P"œ8¥'NÏ’ 0=3A ™cƒ¶=£c˜eôÜe{º0¦‰ya0zdLñ:ÊsÁteìîÑ$;7ù…$ðм3sP,Â_¿¼$<ˆTÚÿ8d±18 H+wüŸy0üÌ¥tëåÉòL)mHÝ=±”Jk”Œ»Þ Î6yp9Œ—2ù¢lÉ TŸûŒmN¼KcKç¼x±¢‚M|ä*™3Ô‡}â¡Jç±ã h“:0¼ÇœU*9:|]wuY/ÓNÓÅÎ=Eˆ]?'¢»`¸”a$%ÂS(a\Ó„FðélOîñ$ )0Ð^°ã¸B9$Â]¾2 û"_™¼†A5aåßÖ ŒR¼»Â˜s3ÿæOKåTX? þ—À«g!Ð!äÜRÜÌäYÁ‰Ç'ÿ0 Â=¯'ýó€Tõ¼MÃØñáżޭú¸°Â‘ÄX ÓëX~éœ1aù$‹Ùx(ˆÕ¡-mÅûñ$L[ÚhwŠþGÑT “ƒS'ô Š|Šx²FÆ{Ö 1 9$pdô§’V¾¯å‚JHè°þÏë6ªJ¡ßÙù‘×Ș}µ€¥³™HR“¤Å“VjoŸôá'tâ%ß¼%¹¢‰4¬)Á[Œ“b7ÔK(y;'Yðõ–Â67Üeÿ'Ozo¯ À Æ„#^9Þ-ÐFíi9sSd;‚Ì­×ɰȈø¹¦Gž˜ïÈàÍBÈñë ¾íÌÏæ;„ð ,œ8X£ .öã&¦Q°ׄ"I§¶Á3¤ºßçù1Ni÷¹Gÿ?ßÊÖûðz¼Óþ'—0s°~çŽSIH_ùØa¤ÁüøÃÀ‘Éœ¼e²©GNr<H–È=`A£hŽÛØdžøÁFð‰?|¿y!# {~rÑf!L>æòÑà•†I5 j‘Ÿg5´;ÙʺcLE™=AGuç&†=¶ßŒJßÈÊ“!‡c^2aËn½D'¹Ô˜ڪHžÇÅ1Ö#g%U[âò¼2!‘Ua·KÀ¨iö]ô§H¨B¬ÕÕ~㻤˜›Ó啾îóZ.öÆN" S& ˆ//SÒïy]­0@Øä3ìÙÙ1ãó"y¹l“oÑ’³ñŽ÷€™H ­ÞåšÃçã¸}âÜtzv‰ÛsðãÏPŽ ’E±R0Cº0ØOXd­‚ ãÞ[¢BÜðš.°„Ð)<Š,äëèFd@/Æ(·@¸zo“ ¢({aÓn,¬Š Y`{À¦”i¿ÂßÜNW ‚džfýd'I=ûôü$RóÆD7aQ«2ö8äÈœØÈ±8uŠŒÔ $òAÁŽ ‚ÑMÓ¸~0ìb-–ãøãE#32]á«7ýBñš‘-ð0W¡ë|d“RDjþq—Ì’ËôÃH!•!ìÀ$N"C®#xîÙ೘©ãÖAsxØ—›¿ë…"RFÐþN\Sa׉åx‡æEÍ(…Æ8ø5tzÁX6ü^W6ùÉôÚy ˜h€iBç½ç@\@[íÈN•UdLpâQ ½h¨1…ê_ "‚ ÷Y0A™“—/6a=R“å,Ts— ìA-`‰>„kžÁåè‰ÔÃQÅ÷8$6†Ÿ‹-eaKŽã²XXb2ßN$6XC€’OQÖ”\Ù°ÔÅÆAÌrVǨ¼ã…pAÁ*¸ó:ÄYîü°”º˜«qwpA¬í¥2ªó‚ @x¼M7ÒHÝ¢MF°cBP-º7÷´È ÀAxR}ý`³í´^ÿˆÄ0@dj¾ðf v/_ãÑà™ôrñ£o2ms'ücH5’ì(€ïYx9Å|ýãP8º†QÿÇ!z‘aŠhtS÷¨tѾ IxçAYk?цÉݺ`?G~2ãͰÞ#ßCQ¯µ|¾ò$./dñÎ2z!W!P¼g¡€žM”ü¾1à…¶q½>q·E(A-›W7cw~S}#â²úñ;ÎÎB 7·vNVËjÍ8iG¬“öêÅæùs„lŒâf„{#L¶EåΖ·üXc$ôåëw”7¬—ÀgžGÆ_¨òááªî¼ßÖDŠÌ'ÃZñ¬T¶Žµ_¬x¨À.Þ˜>À¤”Úšõ“tÐ>#¡yÄÆYQ¾údá~T^•ã08MÇѾð,ÃŒ—8Mî¨7@Zð ›t‰ôáç• šŸ†1Ø–c-·‡nûo.E»q:þþ±)¬{ “¹.%1Ì÷1‚µ¢ªÕ6GËéŽ~r25M%SaY°¹Ð O?ù0<¾ jXŽ5e±€I¡Ñ ÉS]V¶G€µNBËä'‘ºÍ΀f¹^zÀâtã¦P¶á8TP*xˆÚk&ŠR¡Ë >@`JÿÜoS²ó¹×ÉÁÖ´ÔÏßÞL!ºóã [ò€ÚŸ¹©££;Û?$’tÂu…FOò³LZm¿y¯¤GŠ÷‘dØd—ï?ÆSš®Þò@ÝW+…{ÇØ/’!ÈÙt&¸ÈÞ Hh·yÉ7À<ƒy⾈丸HíÄ2$BŠõeiÎ7À‡à`Ϙç°L~}â+Ò$Lì.i¦Åcz#F¬EDF[S~X‚6Eè/•àZÄÍïx¼i`±/hj¬¸wü2aD-paTÁõà#0¾X¶Ê”«;ëó5)A{0lj‚I{Ânmö*øË}áÀ48?¦v‰Ù$Ò 0uؾž—{Ÿ,9R²ˆÍWc¨„|,k¬[GŠ 2n¸ºÈh¨-ƒ£o^^Bˆðã÷*Â-"zÁÎà‚ÄíÏ´Úžã— ™iz<ó›8q„–‡!5ÿ0¹* Ï®ŒT·ß©Þ+8éËÆ &4b¨ ÜG¬aOA¨7ËÏÛ„N[*–œOiމ‰Ž{ÊhhŸçèÅä}ĉJ@ósœ'ÁðÄbAíti3µ`øw‰¸é°®‰ÖEÚ‘±É9ù÷„Z@ó*ñU4!~±qšbxR!ýï.Ô·tತ„š=»|GœµªÑ·(”Øf …zÇö·GœlÁI©Ìd”–震¹óñûÇÙç9¥SìNŽqŽhËÄP]C‹sÀ` t›UxÙj˜V,>ÂV=IÒœ1Î9âa(¤„¨–²Ñ¼‰ ÎB»QiÕõÆñIÔ²çoçÍXà ¨xN`N}^ìR öëÄäÂc6×øÇ@dÊßYD\º,ídzë !ÕCËèøÎ¤¿†QBð©{ÊdB—ì`¹MœÚ ea(õaij]Ê©PˆÅݬ˜€šífüó–¶ ¦ ]²<:Äžc¿ƒtl²E¤úÍ´W’t›5‘VÎHz +|µ™4–y¼UgÊÍT2±²#æóÕAPÏRéÁ25…‡g¿UG‰S’ k¼D´ùç'ÎîG÷Þ"t›~±ç3޳ä×Î r˜ Ló¯¿0ÿ¬ ËRIо±|¥‘§GosÆ*á,BF¸>™r³<è^¯]ázIÈÚx8Õ'C±|âR)–ûHGÆ8´NÓô^ýá1²|ú™Ä(²÷œØæäÒTã©w’‰#pò›ÆÑG8r\1÷ƒÈ Š?r 0ÒÏç $+?Þ$ŒÉƒÐž™l§¼×h™!èwÌ\`"u:$e]+k|¼`ÔeÏAñ•ÕÆD©寜<€Ñ­,ñ]ˆ$A9³ó7‚h‚“5‡#ŒRå€ùd ¸i2ö·‰Ì¤¸Ðátêr"°j+ðKØÄ¼H¹?Ŧæ!†¬¨d}ýB(T{ q™ÂjêëÖx‡ TkNPÓQnŒ©¯ E¸l;}ŸâlÔjއd¸k“QƒÔ6ôs°™©\äÌÄ4À~¦ÂÎéÄKƒ#Öoà®0´+Š".3 7lGD&òè©IÏxLD³ä¿h3qb¶ó6F:s(ëu3%#ëÖVænï§Yq—æ#e;Jðð›ó2ÜéãÎÈö´fÏfŒ«XQüÂAR6a„ßTûжÃp1s^&C‚½å 0Ô~â;:ýJx]‹W\’—ƒ¬»oë•O53EшšÎc–[¿=CÀ KMì—+ãˆl±/,¢ö’ebÒ~& ùFIÖ½¢ÉL<­ÅEw„oG<³2! w™ZÅ”AÂ)^0íwýQêN%LËM@²üýD¡šÔÅF""%¢âX–ÇžåC¹œãØìÊ4@ ’w¬yüÅIÇ€·ÂáŒBôm³qÙrqù‹À4õÄ7 Fɔ*"wD°¨ T¯b u0TE ±+JQYN."tÆ.’¦+® £C7õ™P®a‡€KðHBR_Ÿâ^³jô…“¬«x:™ô²Ãë>ƒÛó0ôÙËù”¶„‹™B£ª©réÖ}7}³‚Ý:Du¨ åŠ8B¨Ë‚ŒeÈYÚ¿‰ˆïà€Ë7L~WW/´|†î²´ì˜é}#з’SpY 3¢þ¢Á[`TW·Ü¢y&;vó… :B 4äëÞ8»Ç…ç(; õ2¶®W±gÜÅ¢ua†Rö1ýåÕ~mJ‰8|¥¼MV®åŸˆ~Õ¼Z ¯§¬ ú¹b_‚úW´fªëþy%¨™Ö`úl—ŘYóœ ) ï2Ĭ8Øý}D ÝùM‡Wæ3r3ã¿©Ò x|ÜøT½€ߨ8—D ¶7õ+~·˜k£lŒß ±öà «s4_©ÜŸ08Ëé,zæÙùe:Ž“*ÈD~gdÉêJòî>·¢lŸÄDkOꄤn +ž^&V­Ä/)-Wä«ãˆªÜ~!ÜaÉž3¯Ûí6åCäÄ&…Æ&V%EÅÝÿ!»PÎŒ7N 8Û›ædЫmpbrÀó×±Ìã¤BˆÅT028†ÕÊ”­£có+qšîõòù€WÙ¶ñk:骢["©i˜ËE²vêy|ED¥±Ì,%àúÔÌËWQ‘€:C12Ì,V¥Qˆ*[`ófŒ‰“·êdtWþ¸óy•ú~f¨‰ŠÀ ­ŒzáÃìß”%1¾* \/õé›Uîðǩཥ+ e`_"U`=gãûî+«P–t‚þÏÇøB „fd=aêƒÖ1Wp‚ SòœKÍïÆ¢¥Ýúb #ƒW…Ĺ֕—˜(çéú‡}$çìF\ÔÎLKÕL"zß0A ,ò’hŒSô “¦¯Ë§ñ +á­ß¬<@{=.»L¡ß1ÁÁöúLÚ'n¹Š †®Ÿb",â P¬ÕEîCdf5)m nh©%¨MíG5aÜ<å ‚Á|`â|uP 0QÝåú<ã­%¹Dï yË|þ%‚quÜXœæÇ2‹u; ¬©v®*lˆNdrw>ÈJ„zKJb¢úñ׼Њ_ÌB-TL*{«SÆ(ëàtïéhº§õ0Š]ÂÑ•ýÖ Duï™i¾ð&ã´•­Ãtñ5þ‰.ß}`€§¨~Ïh5¿sþE˜í}0p¼×ëâP ué¯i—Ò²R˜ V£Î!ʪûˆWfvàoÉX‹ƒœ[Ç?¨“Š POÿÄ&!1AQaq‘¡Áð±ÑáñÿÚ?­*Œ ‰h ºU¦ÐTB¸„)ºç¼JïYºÇ~" |4Úü%*š¡³2)0_¤à8š‹Ÿ¼Ë…Ú±Þ"äç2͈%ÄBCn¸(·™¯ÌÜk†`h[ ¾ sÚQcZÄà )e¿ì*a’ª*Æèº ªŽK-¢*)‡–‰­GÌÀMí­PËLT3>ŸÐ!²–S˜ÅšúD ukïhFÚÿ?ìpå­b8Öq SÒ‚…b,t—B\1&ë/d­†‘­` K65pí©ßå— Ö&‹ªÖ;Î?±{(Vµ³1Ôȳc'7Ì…Ø ‡Ã§˜ëU}âµ(Ya5¤X/1ŒÇþ^кÛ÷LµUÁL5no?:JNM"¹%B6»qÝý@!¹TVÑ¥FÆ$\gn°»y çæ:ÛÍbXÂ5ÀÞÞª·æâ}cÙ–– Ïï‚ ¬ßÜMDÒU£t—ïQl~æÝk5+¢«†€Ì!Ël1zlG«úG9èlÿe†"`*Þúÿa Ëøâ$BQ,f"ƒ0"6k5¼MûÌ´_M&ET ER¶”.Ǽ¡aaG‡‰‚ð– 0¸µ¨*xÚƒÕ3ô-ù€ÍͶ€nj|g‰šóÇOæ‚3аggΑ).ßsm¸ãv™~ë*—§–ÿP-È*[Z™EÓ÷\-a‹m¬\šx€­…>ñvÏx*š¿;ÍM"R.yÞd‘7Ž› `ÿ–Cb8Zâ0yeÙµ [3ƒˆðLé@TµJâ¥cê‘~¾Ñiÿ“H••7bÍB¨ócæQ)¯ö"4ÌØÔDB.°õØ—@¹Ñט"—æ;]‡K·ÛO˜ m3þ\ceŽðÁ„ ûŠ*H¼Ë¬’¨>!P';Å¡tlF¸¿€ÙI€ýÊ‹‘sæWvÆ"@oüÿˆâ“,av_$QÎ#6µ”hq§^=3™VXñ´I V1,ûfx¢ëÍq&‘ÅóÉVݳgý‰ †úcü„ÆëÙf‰SÄJj¸>ø•ÄQwu®ed5,×§É/¶ï öJ¸2fvÖ5sœþåP‚&¦¯âƒwB ËüÜI„V‰e¹S܃Ÿ-¬ÃXjw´41Òæ5퇱õƒÔ¦ÿvŒlë5TBâ痪祷ӉJ ¦{ÄKšŠüFàa™§˜ÍÔhšý i^«– F s¦¾t†A¿¶ £V׿IBå÷q(G]4à–…Ê]tvñmˆý¿ˆ+u«-ƒ…A–dΰ´¸C¡%a¾w˜DìÚ"PD3¹O~eù²ûF‡‡5¬ÌØÜJ®†‚¸…¨ÖaÁÐÐ?†IK×ë5Nš‘Ë©Íó-¬^.<Œèæ»|Îöä`'U«…ã-ð~âPMÁstCë± CK€g¯ˆaqÖK[‘ç*»Æµ,¶—„YPZo㈶õY¢¿ø¾Xsr†Ñ.­þ'TèÜrÙ¼ ƒJŠÇ0Ý•åþF‘b®²»Åâ°(­ÙPåH æ­üKøÛ‹ézŃO줢aæ+G9tí2£í=µí¦x]@Ï5÷ÖRÀKËWêÊð<‘A2Ûk€ t……Ùzç÷*B.ÏÚŠÙ†ôüÃ{÷˜€YO®°@*¹©¨e7.jO00åôF°«XÒãÏdAžMß·öu§´Có3QëþE¬W®ý/lÅ¢n¶®y†a€±‚×§ù7Öƒµõóù¹OH­þþå‚éwÓY§qoÜ.¯Þ5Ø:ËÅC¢Sž`€ã3FàªWqJwnùa4g¬U¬«û¯ íe{ÿ~&O,Ò¦¸½»=¾ÜyM(Û´Í‚Õ8Öû»JpÆþM  hw?–½aDÅÄ É‘øó9¯¯ò#Ï8˜¨ÕŸ1Q® ¦pÐÉ·Ö:˜±¨Öh+rVo#[Çxº³gæ/ZçóZyŠYƒÓi€8™)xïøˆ(ÓÇYBÊö:Ä6Ÿ»ëãÒ[éfº×Û…AÑ,¾ÐA¨›¸¶Wf¯·DÝfÊëÌ¿é¦+ÄZ, •YĸTL;aõ—~±.êoKÜ¿Bå:·ž’òÝñŸä'¼PÁ¤¿zAD`ïQ®Ôá3Îq¯X² qzéWNÙ™:Æûhšb庫•ø#˜µ-.kDZ«¦f§ åÄâ/©¿h@zàŠ­ %¦¸ùšL›>IP$KG´¸‡±ü†Ñž7C–ß¡4¡»é€8Q¾ÿé¼—\„0¹°‘9ݾÍòàé leƒÉŒï ² µø™pç§_X¥pì4ŒÄ6CJižz?G„''¸ï)ÑVÁ¤N Bš*Šæ^3-qHœCë¯iFk°ÒW™¹T+ ŽtÖXXµûsy±+MŒõbíUw5XŒ=1$Hš#©ø‹Uz0Ÿ¿yv ûWî#ïtqbðóœnîùÖ`ºÆÄNŸ= +Þ¦à$H¦·¡§~î`ÙlÿÄ#!1AQaq‘±¡ÁðáÿÚ?šN©ˆ€ua׋ìîÔÐ7ãÞGg ;i›•2hFÌvÿÖrÚ9VŽXz®pû&õ2KN+Béï¦`ófF•Äà»ÊôÂè¡Ñ§ ‚Á¬"*:Œ±³…ˆS—‡¤Å^¦áâö ¦çVÊŽ€t€e«}+¼ HQóTëí8ˆ°iÅë+Ù¼#‘ˆÂ®Á¯ï ºÚÁú¯Sã€àf ‚ŠÉTë¸[é$û"jzÖ¦È j*GNœ%L¶lx¾!y%J )IDÚ½@ÎΚ K³]ƒóð“}; ƒªµ…Þ&ð„¥(Êz¥:ã[Ée†Ý- }®îu$(¦4¶žÒòµ!®OkÑV λD·Q8™‡tP¬š»h7<ô¬ü@ —¶Þ*bÊXÅMëüå¥þš2i£¢Â–Ê ýTüï œ­WcѶ!Òúåá1(%2*QW±ä D8L9(kÇÇÌн!û“g-‹–%6©ªS¶u´%ÎñèhÿAæöp@ «¡ÆRN¼‚`<*…«Ht îgµy%uT94. <*…âi†‚š¬œ¢ŽUFž­PWàŠM¶ÆS­÷=°åL0‰šOâóÐ|‹ŒWCsôêæ‹nð,”iM2²"UÕ>o|y‘hxú^bXã¦;×=h£d‘E†‹{ ¢òª©|Ç5¼JcHokÄ.«DR›‰œqÖüÀC̦@Þ±|H˜Õg¥N'ëa"vJ)1CŽpÎ .ËRÞå+aßhŸçÌiLž”¹œ @øà.Ÿ¼M¢@ 0:±ýS௠s=¶µó®PÅ;ýùßbÊ ‘Ð7>8^m£ !¯P,[.€ 8@iAÛÃ1<•µSQÛïÂRÀ4À„€¾ãùƨSÖ—àÑÿcÂLoÓÄ4S#ºÞÜ )ðhˆ8Ž¡c6ñ*&¯ôâ!;ñ=®é×¼L¡·%xשÂG*u*£WÓMiqêU‚i*ªÔaS˜êGíìLõ9¼Ã](èM÷>åÑÚ²ˆa½§~_€M¢fŽG¤áµÄ‰"$ÿŸï  ™ 7RøQâø2tUûŸ5忥7`*ïùŽi2³†Tÿ·wÛ— Iœ,Õw’s"ÇE5¿ÿ®]kJ%,‡EiÁŠÊ_t`3x‚„âêDQ Ë+HÄí µž¨ªÂi·—¡f2@ íS°õËI*%av€ýG³‰Y°©KD–¨Ô!Ì »/=F¼ô ¤kQS¶pGxIZ`*'}îñ cĸ#ù¡½¤d /êAò@NW°lçHƒM©¨!Tle®ˆfqòQ"jÅZп–Š,Äál [Ý©“Z´Â®ÝÒ¢ öùÁ¸ýq¥8j†å“5 u¥°_Âè^•ˆ›ÐNäÖ=áøk6«‚hþ„Þ@ŒdÜB” ã瀾¢Á;£xñWÈ OzöÅœð•^""¬`h³x!Œä¼PÈ=¹Z}‘`ljÐä¬ ™0Buƒ$4A4B{næÒÔ"I‹s‡S6ž ¡ŽÔæ’ãe‚_‰ˆSqAh„ Ð¥/2*msoº€N†šŽ`ªé, 1_dây7¹F8z8<Ír ­^ËÇȨ>¶uû× §VÿûÞ5)BIpY¡a ·QõÖ‘ïøG”Ñã—Á¨û/jAN2iOH¸qèöŽ–uFL)=‘Ø¥sŒã¦»„²·Z4e¯lÍN@då¢ Y¤s¨KGAÐL‹Œ†©UCöƒÍJo6ÃÝg/KwÑr4 $6ÆSŠ+¼º…Æûz'¢qHB„cG!ÖÆ­bé:8IàL޲ÔÙ_G &§Â§BÒáJ$r•Þƒ–ôTº0À+ù9KDmrf²£5'¿A[Òá¡{d]Çð}á¬aCuá9M¦T¢Qí ùÓó‘"2¬¬¬èxfÝO-(Tˆ|p!ßñÄxe^Ô®(5tCˆÏ‰q ZÛ¤S/¸%J‰áë‚í§Xe ·¦`$èi#±ç±` €!ƒÜ!òv½? sÈ¢ªHçoðûÅJ1BàZ4ùÅ(£Ñ"t„—'› òt¦” [ªY ¸C,¼Y */OúÛ^-Âí —ÐÑS +'¾]†Ä>kUã4Xô†x§÷Œ¶Ùª¤ ÿ¡8µƒ×Öš¿cg<®À[˜ ‡—Þ ¼˜‚¤ç(‰·Òpï¦h9 ùœ†S†Ý1Id8D \Ž,ð_9^Òj4mq ^ÁÕTÆ^‡„°¸)Ù%RZ€R9zq#w„ïx.>ɬуAƒX´Ñ,Œ6‘v†Y”~•=í'V—N;&S¥‡Æá®:÷ð〔XèwSB)Pȇá@äcx£  Î /ã²¥yP‚DÄÞk˜šôŒŸ§£×|‚e¶´Ô(6ýàûêqµ]Ò8ñì ¤V$=| «‘Ar¥³ãË @½>ÌÓyí—–AÓÙÜáÆŠ;*o}ÂÓ²æ…úû]ÝU¶‘-ùJžÁJZØõÀ4!¢aÙ³/Õi‡A?ЈJ1l×6SŬ;{¾\( @ôà¿Ç ÆI¥{÷?ª6•A&Q89ê¨k=Bö°zçCI%ÿÌ"²JíDa‘jã´”½E4ïÊ«˜JáE;›ÑÂa‘·€Õg¥âh/Kì죰7Û9_èªÕHÿôä˜{]Dõz¾¼HV¢ÃQ,o©Ç_Dá š(­J¶àɰ%É´"Ø ïƒÁ÷¡eHKPo} ìÃJtlåºK-5„"†šÖráºRÀÔAÓ3®2Ê”f˜:ØIÆhéhÁÂèWœ1@Ý0ˆ£i}eüÐ0;üHŠ®â­!ìÔüœ±Üáë8/KÑĈŽGî‰Ò$¡àcHX!‘®ËÖ‹Š „ ø—ñÞøc’–º*Œ¿÷¯¯*F‹Œ$WxnTZEgäN°f¬=”>¼Rž-ám‰û‰E€QU‹ nL­Š:ƒ‡¼L¥Ñà­&4˜â"„p‹8ZÀ*¤Ì2Äë…ú%*nw±¼ø ° F*hg\M{xN:£ ›yÏ¢&ÁX*Ysƒýì| €õ£MÚ9³ ¶½Îì*C¤âé ˆ) Û|}åÑh`@¿ :^$½à oŠ4‚,“Î@M¢æ³èÄGЊèd éÚ¿{ÀË$Ôý¯ç%’½>ÒRêð”M‹\F!!ì·ÕšDdÙe쨪Ɋ AhÏB2½IQÛ¸ÀÌN%–oÛËFVµeE2Ý‚C÷jçCÿ‹¿d¯n]Q´$ áëˆ @uºˆžÞÙÄi$…¢ÿ§§ ÕcHðƒi °p是 ¸Å§óΧ›+@óujñH&Ca쇿¿ßÞ.)GGà)KÔ奖a §B¸ª‰k… ‚ ù'&."U†¬ü¾ViÐø`r£S5 Aô58ÀÞ‡aQ.J…:àc÷â ¾AB›9¾LýºThõ0B˜Â/‹[ rŽï¡^‹À"@/ìâšÓ,az¤×Dú[yŒB “àžqÔƒÐ*› @6ðANèLüêõ¼Åk»i”äjŠÈˆC" 7Ô¶¤™P…H•ük6+CåÊôP'µT?îßx·ŠíE,˜•: ¬ã8_ÍÔ§`<3Ìc\0èa¬GÖA¬ÈŠ [X¨3$'LàÕ›Ä ©ÄòFHRÔ|.tð•æI«D-0ˆúŽJ‚©ˆÖ|eòÌ)Ž× e l¥N)÷Ÿ£š÷ÔzõñèÞ=`è@-uH1å‘!ÃÀU6žtn3ÒÐAìxFP E|TUP=!›«ÚxK °îï!ÖloäÁ†YNqþºÁºx½ï´‘1”M:¡cþkìW•wÎRe(¬ý­+ HŸNÁƲJ|=`ÎöÛ V‡Õž^3t¬O.õ£i¢¤ưucëæÚ?gûÀkmÝDH!¥š¼Ñ™›`œ¸‚EFˆ# °,ùRQØ #~puúšÐ Ô¨'ÿÙÇ`ÉÖ\uàrö`%D(i†$Î6¼‰‹ ,½(´$È<ŠD‰©‘­…l ]ã`Q¬„@­;KÎËò”²Z“±O|G§[¬CBD’tó×KŒiþËu!Æ™•zMÕM Ä’¥´P=CÿW&t;Õ]êÄbe·¨¡¨í$‡3ÝD{V‘çg_Æ´2PDúï†Á›à‚ªZo|@C ü€¯JÆíê/Hba<½·…˜Nd ؼO¼)cXïøÿ¼k:¨Š&ê+Ðñȧ:‰×Ÿ÷„à}ÀÞ/Â]×Z…¢˜DÄO(‚ËŠP´ñÂl%Rµ€+y\.‰"ˆUÁF<+0O¦¶;]3šÖoAì~¤%OcýÝ$, `¥·ƒK‹d ›aXí þ‘3T5 ¥Cãe#Ù±€¤õuŠʉßÏVYP+–YØy¼è¼~6M•öƒû‹@<ïgj˜Ð«¸²üã¾=“îR+ýó–µ „/c[ÇŽã é³ûÇ~ YÛÆÊ²‚"ïùÁõk”³M@"dÇÕ±ã¼ÔØ îN Øk`ô‡·ís£¾X(Ž´æQ˜A;ÈÈW¥)T=ØÜ©Ú;ó3œ7“¸cø‡4¬þ:,—‘Ü`v\›° 1$É€ûP¨¢÷éÅâÁÏT„"y5ÅËaõ@6Ö%d¢'<6b%G°WiùøIc(èB…ï{ögù×r1£EEƒÞH g²QR|eå]±UMS»Lv}8ª‹ú5G Å^«y]*ê U‡ø+sg):Ƹ^ö%(Ð$«C+Q,ߨ0î  ;ŠÄN :‡°•àÖöñ†)'^(nìtÍ8x©JJÈÜ Z  N—ÿÎ ÐH3øÔŒÂú¹ ëvðÏþñcÐHMžÓäþ¼v!­6›õŸßÍä"”‰l/v}Î1¨”±¼‡@B DÁþq’¹ùWAH ,~÷¶ƒÇšŠÍŒ-4ÁÎ[°2ÚŠBƒ_¬sŠ·¬÷Ê($ICáøBR‹p.µ¼ÉÝo€‡ˆ&\”fuõ ÿœ% –Fˆ"½½®%œÎY›Å«Úá£î&&¥HpÎøîôÐ%‚€<ð³®,zfZ‹ì0¥é£ÿ¡}yp Wédÿ¿ï.s`DÐ:ÿœü½½¤ßö¨cÊ1ƒ  %ÿ}ã*!ì’—V qû÷¶¦ç?¤§øuÎóÑú|ñêñtép= +d|¨¬üX€W 7g¼FNe@B¬F‘¼¶ —-°D?š’ kÓ힢…? ’€Á§"(†È$͇S¸RPßoI¯ Äås>gg¢BöJƒªLà IPŽ èQ ô•Œ(£Cªp·D@¥1Yq©pÿAîüLˆüãèˆÕíeþÞ6-©+ð ”.>r=h ?Ë5ÄLeÃ;Ùö<—EƒAc¥Ãþ¾ƒP(`'c.ã¦R¼/ Êà„t}\y$¤>³VÓ™GŒ$F ‰ûÉjŒ0'Ò­ýÿ† H–¢Zwß×Qp†0 # rà6 „è1:뤚 ÈÞûœ?Œ§@ BD`Œ¢´gÂû¤.Å !à)ŸõŽÒàº{SýpmNÍ n>q‚!›„½­g\Rá]Ãíú¼%bíþ ÿœq§ý?ÿuÌ‘„éÿo®ñM¬‘ßâA=þÑÇ÷9• APÛ¸~Ñö³¨É*üáxSKe‡iT *fða uÚ’ÜMqx!s*x^ =G‚éà0è¨sY{ ÷ß|Z‡uŽË › ù”xK6P)AB*Rm‰î2B0Dó™UÐÞ§³ñª×©È׆‹¶ÄP±ÒVN2Þœ¿™Ë}á“%+BÔ >½ã3–6¿qéìG3ÝåO‚/ü9œUuGô)&DÿËïÍŒ|Jò†¡`;Ø®Î0­"‰ =ª=qqÆ–Ðø?ñýåŠ(,l½u/¯±â`m+öoñ†8ã„¥ *£4œªò,—9^GÀ§¢Àª8ŒÛí%cØÇÃU§®xM˜Š¢j?ˆ2@xìPÄYºòCÄD'(X¥fyƯôZÐ =Ò]^ƒrÙ`#ç„Ói¿Áœ½Ú£[ æôÞ߯%’u¢ Ï=3\”†’èŒ#ç D k?7ç1*Bx0{A+Ç,™`[g¼_µi“O¹…åóãÚuý¿:y|Zm|‘>ãôEÁVËв÷ïxÔ44¶to,z"ÀÍé‚w+*e Êð0.°$h~’Ðíàøˆ…p„€Å5ÑIú{Q‚:¹Ñì#GdÔÎç2ª‰ ô0|‡Z#bÖ ëüOùËŽtJoà?ï3§¦œHÔþñÓõœdGO[Zìöð;tb#Ñ@»©ûÆ}Nºoþó­q4Ѝ )oÓ9 ­ìpŠ…‚%눔€Aµ¾+ƒ×­j“u&&×Úâ§B¡]äca˜¹v% 6åÔ Ç%²,ô>p^¢ò”úzœÄTMÜ.T}tpy}ñã= i™žuÅCbˆk¹Ò«ÿœÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/images/guitar3.jpg000066400000000000000000001155231510465702400257450ustar00rootroot00000000000000ÿØÿàJFIFHHÿá7"ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:23:42 Ðè Ànt¦5ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?Ê–ÚÜÞÍ ˜¨’g88ÁÏB(VƒIxÊÜHÞwüµ‰¾RýjÔpÅy²Û¤g¸ˆù×ûÛG~õ¹¡Ëk«éïaÈclÅ““ŒzµÌ§mZ;¥41´¯ìDšO´<c(åðx<ôõ©tòë%»4Π‡ ‘Ô Ê])-uymy¦ÞL¾LÕºÓÒãg–‘‰ŠF†CüG?ÈVÒPOs$å"–ª–Zf Ö–Ž&I¥A!‡jËkؤ]­©–EÖÖƒ·°K²<% Êp '‘É­­tÔ¸–[ÁwŠÎ­(Bƒ¦F8ÉëT¹bõÔM¶´f —VK§m·bó”ùŠ« ÇúV·§ÆÛ²+‰¶òŒ…ÏZ­¢Ç¤Cotó3KäJZݲ;ã¦ÅiN« ì0µÃ˜ä·Þ¡Û©nyõ<Ò›¶ˆrÕ–µÍ6Ý`h#(PõŒQžGëàÓ^Hîí¦‘‰F*åõ⺨ÞKkO€Ü3ÌÓî›'NEf_ÞØM¤\[ÅjÑê! æ‘GÉçžõ1}ÜZÕ…. Û=¬cdË‘‚A«á®"‚äŒM XiB?Ìø=ÿúÔËaö­Éà‘¦BŽNoÒ©›é팆7ù°$ˆ0鎼}=j”“•ŠœU΂ãW·-Îlémäî‘RqÎ~•^ïÄ--‰´6‹öt!¾vÆåÎG_Zw‡¬Mï‘n’Ç­ýñA¸}_jeìv÷:µ¢J«å¼áZ$WnÊk–öfM4®†iÖ:½æœ×[yÐ4„†ÝÉlõ÷­›o}†ÖXVñ~Òù!”/áF©âVµ±Yb *F‡G­Q•µ2IBϰ®KäçzšÎS“ÛB£O¸¶ÍÚ˜g¼†U“"5ôïQj†-å,HŒP*ÅÐFx n‡oi1¶¸3?#Ê\øýk:êÓm$´l‘_!ÆzЍ»ÈSJ*ÈÝÓìek ÄyˆŠÛ‘ÈÏ^‚£Öc’+€’³;† ³’itýU¥¶‘,Œ¨¹f^¼ô9ê)š¼Ï1ŠgFS!-ÈÆsYësH|Hô?8o B¹å]ÿô#]=ë—ð†ÑwY®¥†}1LÊsÒšrzÓ°$ÒqžÔxüPêÙñ\y¨cò¸9Ç^Ÿ£Áu£©¸2F ŠÒ<ÃhÀÇ85 ÷úH²•d–f„&b;ˆÞO§|ýk7FÔá%¡yü°ä¶UO¶jy‹gKž¶"’þÎ}~I‘„p±Ár1¸úŸJ×¼¸ººT†iH—lÎáØÑwg>ްÜ9› å¨_˜çÔwâ° µi®—ìþte>O0‹Ó?•jãÍ®Ö!;hjZ_Íôë–·ž(Ûp£ óþ4딋RÓÞöñ¼Ë™"2—/üX©bѬ V7%–Ü„Iÿ –]ÞúÒ$G ©ª* 7nzQÍÁÁ£ŸÓ®^6òž"ò >X+Ï®+§:…å’Cu2ȨF “ ôÝè+/dzT,ø½•F\ ÷'¾*;Zòr±±cr qíeôü©ÎòwA46"Ó/`‚YðDÓq-® ǰ®_R{‰ X|²'\ïu8gûUóª_FîÑÜJ2ûšFRYˆéƒQKo~’-Òµ¤Ì ‰c?)®W×éShêÆõзáØY¼;+oF]Œ¥GZ¥6øÁŠâ-Æ J³œ±Ƶ|=r±èrÀ†<ªœ²ŒúŽõ_X+ø¸¶wcp< {t®UVÕmÜî•6á¯B¼WȶÂicŽUó"¸L“ƒÎ¤¶Ôtûíf&rЬl&b0Äz±¯![[ù *¥oBíÑOnjí¼P¸Ã-¼pžŠ€gó®å¥{žlœ¶±ÓÜÁw©ÄñÜÇ+C ‹Ï˜[ÌN }*µZÚ]\òMòÅaûªz‘ÓN-EtKÈ-¼ñöl˜äRû°Gú{Ð|A¦5Á¶¸Y§ˆÉ–•;†x>µ—,¯¢/u:_ hÑEg8–̉ՉGÇ`Myõì’]‰ng•¥›#æ-’¥t3ø"Ñg¶°šI¦q¹çù—f¿zÄÒ´É5 g(ÉÁeBÄþ]*éÅÆò‘œÝ‘ÐéÚ’®–,æ†GQ¹Án˜ÉÀÔš•À¹·†QÂãŽsÇJµ§5¬v2ˆ|·e‰ÌŠH䀯z¯©´ cBP‚å^«ì}ë&µ5†èì¾tû¤ÿ¦ ÿ㢻ø®áÛþæê?ö®èüÇÞ‚*ülg~:Ô½½©O:Š;H4žy¥ÔB±CnpðýkRâÆÖK1E6ü’ ±ÇrEZmõäY̓H›rÃÌ‘ۑڡm¯m’Wž\;$˵}ºÖJoC¯ÌƒOÓm­žYo¢‚X%ÀP ¤}i±hñ^]Êçìë¸ÌhØ=øÕèŒ0Ck°à£g$úÔ6÷q$þTND™ç€±éùU{Áwйq¥[ËjbXìã wGˆø¯=ª®•kcndK˜ín|É 6ìœôt­5\”uñ峂IþµRÍ[ífÞœ_ ·õ鞸¤¥£¸;쌸ô¨â½c<–’å™Lcè}=ªÞ­2¡Šg‡ÍŒåXÂí´zz*ñÐñz@.wãÌè1×o¦)×ztå6܈+wRJ7R7U7k6G5Êš]”V¶ ¼Ap3½d0ÝçðãéY²éi 3É3°'åÀỚÜ\ÆêÊØCÀËŸ~‹,[ÌhøÎp9¬Ôî÷)ìrÚMÊæ±n"8X®î§ƒC9’ÖBÄ>ÙÏBFk¢Ò<-½,“Ìó&á…*Ý«\|3Ó™Ôy×~j™T¦´{š)Tm7©Ài–Í­Ý­´ŽÁ±‹'Œõù¿½y*éwæ"±]ÄD ùFëVô{{¨ tÙ‹™ s‡‚Ó‚¥,°Iu%Ò]Ë,×!w‚ü–ùyÇ®+ª/SÞF6ÏíeRiUTÿ?§Öº-J±-$2Zc «.\\Ü·À¦Â[X˜ ‚/ÔúÖ߇tMRH Ù¹òüÌ‹}Ç+#úÚ®¥Ú½ô":imFß[Û9Ù6¦çÚ%P¡dÿ¾>jLJ5¶ÐY¼Ÿ3j „õížÕœúm̲‘çG[«KÊÀŽ ©<ž:ÒëYG¢ÞX+Ãq{,¾Zm qéŠçç¶MDŽàbÉ-#q¿ã¨SŠÃ¶Ú­½Ë¢02å¹Pzœzb¦4âß5Ι]]3±3Ãs²ÞfÝ8?3c!|ßÚ¯Ü "`1Îy'ÒµµëËmGDžÖßR[ƒq"Åh,OÒ±¯nu N´…ÐÎÊ‚<Æì¸ÀàqÁàu¤“’ÓF I=M+wòàò Øb8#ÐOJ´³]¤×Jʯɔu5ËSâ)Z—qÛÜOñåÆÀ•ëŒvü*»êOs*:Œª…tà ã®?Õ¸ŽÍu’EFg.²mnƒž¾õÏß]é–ºŒ‘D!:lÇËo(cïzäu¯Bš¾ÈÂOC@.õd†UY9!_9ükE5[ŶpЉ@8݇ëÚ©]éòiáìü‚÷'8tܾ¿LT?a[6WuÞHdc~ŸZéÑ£os³¾Ó.4ý"ãdò À×;ñ¹Ï•‹k}y{Þ,þaX”ª…U† ¬Ôï¡Þ©«ï¡pÚØ5•Íþ–gÛJ¥$Èè:ŒVµÝ¶©¯H¥- š@]ƒn#'©¤Ž+ï [¦úm­4p©÷õþTÍ+ÄGOT±1¼™‹O.Il“ÓϵjœÚº0šŠ–ƒô϶i?ÙSÜ+†m± l€Á³õ5ÑBV)¢¸H’O˜H>Nzç¯jçn-umGLx$Ò–0X¼r4¼ÆsŒÒ<×QÙ¥¶­¾9—;YˆùÏlç­Dâ¥ïu]´ètpŽEÏ+»'Üc¡¥·Õ­êöÞ}°ylùŽw`öê$’!S,ˆ¹T–=†=kìÉÅ¥Ö¥i5¸Aû z³õ9ük–1çn沑Ð[ê0]]}‹Ê¸&8Ëf~Cc¿?Öªµ´‚ä<"¹?xùv¤ÒnaYVÚxþËx˲8Ûæz{}*Üû]˜˜Œ‘’£Š©>Y[¡6º5ßJÇû+I¥µÃjqÜZ¨ÄÆÞ<º¯BIçû­Um –ÒÞMðíß»¿cÏAÞ‹;è)m¨izeºÝÝ@Ah•ð°î?2ç#&ºyí¡k{©ÎT™oâ¸]<Þj¬ÂÒ;¹ * m*}Aõ®œ¬ò\Ì ˆÈX«/SŽ€úÒ©¸Gb†€æ-rÈ‘ÊL§C^Öp¯ ¶[êh<#îü«ÜŠíÎM"ëôb}ÞØ§ÁúSO8§uÏ&„sž5ŽބgžxLdXâa¸Îßþµsš¦³i¨Ú4~\žb‘å–ÆgZè÷·.v@΂õ½)4?jv7’¦˜öÒ[BÙR6d㯔½ÝY襵Ôåì£ÿŠrÒyex­¢v–FS‚ÄýX·X.œM£y°$ÌòÈòº¥rŸÚGlöpÜJ-\äÆOý+¨ÖÄ7ZkÁ¤›3mdTŠ}Òmêr½zU¥waTнbªjÚtVrÇeÜ]Hû·\Ä­‘ÜjƒU¹½’ÆÞò£…a*dû jéöc¶ò"ÜìS´É÷›ŒSb¸6%ËÛ§›îÜWåSü#5ªå‹÷NwÍ%©Üi—)g–V(Ê6Žr—Ö«^Ûi÷>\W#ÃyãÓ5ÉK{v`/Ñ…‰9œ~‚¬«ÞÁM©Un‡~õÞ°tž÷+šÌë d¶P4ø‘#À8„uQM?½RˆÁœö^pON~´–±f¯åº)MßÞ7¿~M,›Í¼ï ¦Øò€Þ¹ù-¾æ—FǃíZÞ]CçFG“~:í5Հʀ¿.1ùW)áKƒ4N¬àðûØÿ êWsÆHb1þ”÷<ûZºŽ/I ÑŒ™7)÷>•Î]Öõ»¸·h­¶y’nrÛ”qþÔëpEˆŒ’©wyTúýkþu‘¤QåÜmÜcT#ÓµÛKEte#’¸»“JóËåN­û¨Æ(øU´J741†É89$w«ú´+k†S ?.*ÄqÈ–¶ˆ[a,Ìçj¨÷5×t•úµ­[)õIôù\Ø4–RÂ#D/óñƒø‘\å£Z¶¯q,†H£Ã:+˜û}k¬´ký5!²Õ!X¤Gà `Ç5Ã^ÛÜÁ|•i†Sn>„R†·ukÖ· jsfaÜ¿êüÕÎHï]zËÞ[Ç)ØÍ {ÉşƱtt·°¹ 2üì×'hç+¢šæ5¿W@¾XÄ£¿ÞµŒÒé©¢Ósž, Ë€î?›r1å”ú׆H1xGÈ5íš[™´‹)WxQ¿1I•WáE¼œç4Räw4FsLç>wÔåÖ´IEâIÆýÜ|„ vÕ¥®xþçXÓä³KXàŠ@7c“Mñˆ-uQ µ­Ä· “½ÆŠÄÒŸN†â;‹Èˬ<¼]ŸÒ³å¾¬õVM¢=ÜK«Ø‰¡ß“*G 3Ò´.õˉng‚gNJ–?œ¯÷AíNoIy¯Z^Ý"Ç.1C„¦’t¾.—|Û¢™S*êzkJv¾¨Ê½Ý†ZMsO,p+Ël‘«®A p1ÜÔjîcÀ6ñÈ À¹çõ§éDj3ÜÅ"Kålf ¨çÜÔçB³Ô4Ľ±í#|Ç•òFrzÑe{ÌͶ׺ŠÐi­-ôÖîe’(Ð0X‡ÎþƒÞ¤Õ¦Y´i&ÇŸxÎúŒ JÐÓn¬.ä˜Em-¬kû÷B^NÄ⨠F[½nÒÚÆ&b®ÙG.Ìy$~4âÜ¥~ÄIrÆÏsFD»Ð­lneݼ‰••N8Ïz¸·ö†‰Ëœm’B¬?:¹ªÜYYé¹ò mñºØŒ¨'±ôþ•†æ½Ód¹x’)g!’%Lôì•帮ÖÇcá”E²º å Øv“9®ŸlJ p2qóW-á(Þ+ Œ¿¸OËžB€Eu‚HÎâQ»ÿ ®I«HÑ3ˆÖôô½ÎW£îåŽq t»¸EÌ‘—‰DŸ»`Hlän®ÛÄ7f=}!ÚvÈÈyŽ•ç:½ííÅ k©zaR0Àg½uQMØÎNÉMc>¯!a"íBwHý¿n›«ÞXÞ=µ¾.må’0¸IW¸>ÕNæŽç–7 /%×£WAkw§%ÔL±Ç5¹P8ÛiZè“åVݯ{Qº…ưoÌ÷P™mJÈíˆvät"£²Ó_»†æâ{{eRÊ2|îpMnK&ž×åìmÚ8˜~øËüŒr¹9ÀÅrztku®[Û%¹“»rrWëÛëS¸é åî³F·½Hã»sj³’u''¦ºË}@Ú»—’(œ#Îx=ý8¬ Jµ·´Qà pDD¶âNr*Ê Ë{[h$¤vemÍû‰8'ñ¨•ÞÃZ-J7˜MFP:Å{'†ä/áË ž¨€ÅxÕñÅè)VÇÍž¹¯[ðŒ¢o ÚßwrãèH¥ÐºšÁ»ÉÁjC€ß.)NâvŠ@20:ólÒßÃ Ì ¨ÎNk¡–ÆÉ.Z­"(ÐJuük¢ÔtËϳý·`1È‹!u Ÿo­Gg?•g°”;˜ª©ÇÍÏJÊUW;ùå$bÆ–(Ó}¢Ö­Q2FÑÍs:棧Dbµºtû¹àW`ñÀ÷s‰áQ$”v  W<-nÊr6åXt5¥®U9]{ÇMe7Œ5+X§¶¹ºx¤,•Ïn¿Ê¹™æºH· &és‡'“êkÕí¼Y£È!»[ö†(ÉýÔŒwއùW™x‡T‹VÕžî™c*Y_s {äÔ­ö rmìoh¶ñÅ¡§}ªæás”V)ônø¦M¤ÞOy ÃRó.çfódFÊ¡úŠæmâWÝlqÛÞ¦µ¿¾ÓÌ‘[ÜË ±ù•€j×4uDN rjû*.™©\‡´ºû3Ä2èñ²…þ/oÆ ÄšÒIÎ0ùX `Ó´‹kÍ!ž[¸VßädAóÙsK¨¸›í6ð½¤F?Ÿ)ŒŸ ª„º3ž¬R~éÞxÞíté§šmÞpÏÍÉâ»`$Ë&9þuÇx2êK»Y‘©Ž 6Uà®+®‰ctbÐdr9W7/5MA»GCŽñ1µ‹eš\“†íŠâµÉa}>$7•\ÿyO¨#§^ÕÚøªÑTµs”Nwqžk‚×ìM…²Âgó7.ý›HÙ“ž½ëzv¾„»òêh^Ï2[Ëö¨£š/GËÿtsÔôæ± ðÂÚGæ]°’P1«=ò*Üqjºs[KpÛÀlmßžÝ XÔ-ç•íï!—ÈÞ†F8È8{Õs8«& );²ÕÇÃéç·–Gռ㠃®¡÷®;ÉÔ,¹·’Ü ^D dv5¶ú¦»cpmF1\HšW³ÇZ·w¬iúìOí»”(UŒä·AN3’zê‚TûÜkŒ¦r÷ò†F ¾‡“Í]þÑ´Hw”fa>ÖVØq‚1ôô®2xÒeŽT)`Àä0õ»i4KKk8D†àyî‹óŽ”TŠHPwÔÎÕr×ÊXJ瞼שøBÞGU‘³ø’kÌ5ˆ„7(0ãh ‡ëÒ½áÜ¥ôkˆÉåeÇÐ…±¬þ¯ ƒœÐÊ3އ½8Ž2j6a‘ÔÒ‘Îyœ÷š…ÛIl‹u*`ä¨,‡ðô¦k:[ܯÙàC"†$‘í\…÷Šo"×î§³¹‘`,UT7j忎äÓ®KEÆsX¨Élw¸6Œ-Yomn¤µ¸gô#nï©ÆwÚÍIöë]sE?Ä-Fi„Ñ[É ˆÝÅ"øi´èÚÞ³Èùù›¿ Ò3W÷ŠzFÉjqì›-cV8.Ûÿ Um¿5zQðfon÷7z‡žû3qœt W¥éoªkY »à‘Øw4)'±jjÅ­'A¹Ôm’l`RI™ÎÕë×ßÛ¶Ñ¢aYn¥Æ<Õ_Àu¯Bñ4šmš,74,a2¹¸Ïi1ë!‰Z ‘M(€{RöÎÚìg |Òl–úh.mìïn¡dŸÌa1‹98ÏóÅH»uƒæamF>R(á¸ÇåïUnuY¬|E©$E¾Îóº´ áJž1O“ZE¼Ž[KuUX„A%€cŒÖ—²±³oSµø|¯ „ÅädÙU#cÞ»ÈÝJ`\s’{~5Çx^i¥µ¤ÌX_qÉùryúö®±Y¶avüê!yM™MY#–ñ~é/,vʯÆ#Žk„Õ'¸ŠT{¶‘ãF%#qÀ^£¯DñKyq[/™‚H Üúö¯?ñð}ŠXFNFà>é¦j©tA-{Xº„Û[~ú92ûƒ¸#‚qO7¨¶Ö¯½¢ó²¤Rû§i\úW=k¤%œ)s3»”kF dþuS±P†)T6Š¥Ð+vÏ·­6¢¶[{•µÍRÞ3=˜YŒ“ Ü­‚‹Ÿê*µÔw2CÚ4vñž'H÷+`qÓ¨®ŸFðEœ›¤‘H&Pð±l‘߬o[±µ°»±³vóãQ*†”}æ”u)É-L-a¶ÝAkµD‡FØ›S$ö¿c¦ÍöÙ¬rh÷d±ÎqÔf¹¡a$±oÌ/’aë]¶¡ Ì2Î7DåD…Tl*3œƒÓ>õ­M†PwÜ¡¬Ç"ùfg’IÏ',yï]×ÃgÍ­ÚïÇá\wˆî–õÍÊýÆ#Ùã>µÑü5“7—ˆ:Ád¶5=«`ç4Œr;þT2‚2qøÐsŒph9Ï—­-f¼G în8¦ËA<‘?ÞF*q^ec¼2…+`2…Tޤw®Ĉ°ß¤c#jðü1Q}lzpªç-FÓmíI$Þnåà§§Ö·íoÍ„;ÔL‡˜Üœ’Äp{f¼÷F×[K•Hrdg‘ŠÝ!†êÞIšáÁP{žÕ2‹lÎq’w6ï&‘„ÎWË…ßo’­€ŽŸ•p’ݽ–¤gÓî'aÃFø#=FEXÕ|M-ÞØÔ© 3Ï¡¬yªŠkr¡ îzßö­•ÖˆºÍ¼,¶U^¨sŒœRx^ú ÿÛÜÇ“`·c^ko©]Ce5œR2Á><ÄÈ®ƒÂ·&Þâ'VÃDùžkž´T1µ zµäÊž'µ{?jQA GК¹¡¤°ÙÖDo0¼èÆ? ë>%hk7•â Q˜äP&Çlô5Æé÷¦Ÿ46Å¡c–ul6{zéº{ûÀí¼<—–se$ á@¶;Wh¬Y8D%sÁk“ðŰ·7k»$Ê—nHùWK$ÁcEm™ ûÇ4S”\­Ü䨥cñ´$ÚY²D2ýÑÏC\¿hlK?ÚI23Ȩùx<æ»Nñi–R"*°˜–¯C\¯%íÌ’Ã<í³Ìq oÓ¸«‚åvè=ãrÔRiâÈß½ØÜìdgáÚÉ®~Úêõg•ôÐÉhd%b“•ç± TX¦Ùå“ :-mXÌÐY3M ADhÈÆü`šÖMAœGb&’ÿrÊ5Q ¬›@ ±Tc§¥dßÙ›]±6|ÅÿXØï]´úÂ\é±$PCml§Ñ8F_pßç5ÏêEu åŠÜ ©6ª#‚ HéÈëõ¥N}ÈœtÐßû=¶”ÂÚõgch≰ c“ý{ÒiNýÞÇ<²´‘ìhØ9àÔvž}¡¶‚òÐZùÐìÈß1`0¹ÏCU´¦¾ ˜ežX"†Q´¶6ÁÀõ ¥©gS´ŠßM‰Q6·üµbIÜÞ¢º†Ó„ÕäŒïús\±y%Ò›Ì-”áA`Õµðý€ñ$÷V¥%~¥«r3×ç?Z@PN~´¬Fî9ãšoû {T˜>\øÊîd+ Cónãžkœ–g™÷HìÍÓ$ÒÉÄåJ°êÅIk8¶“ÌÙ¹€ùO¡¥cÖåŒWº‰lôë«Ãû¨Ž1OÔ÷³ÚX˜¤Œ†ß¹¸è>µÒÙÏ$¶ñù­*¹®ÜãÓ&ªêñ›ˆÌ1Ü£„:ïƒÔSG;¬Ü¬ÑÍYYO¨\­½º†‘½N¦´oôD±š;d»Ž{§m»t>æµ¼5¦]ÛiwZšÛ4ˆèV&Nyæ°¯¬oô«—±É­‡Ãõ©Öæ¼é»&tzFƒ+<ŒCÍ(àã¶qбB°ï\†­,0Ã2ÏóÌdÊ n[žÃéÆk_×ôKMx#¸q9ÀR?rïouæåvÊ$HÌG'Úº#^4ݧ¡1Ãʬo JÖ+t­k_i€Ëç¦ië¶ë§Á“H%38ç/ßõ§ÙMwi!›’S&<ÿË0}+Q¸–iœîn)μ*Z0w:¨aåMº“,ßxR[}¼‰ÊÍ s7ÊO^*“ÚÛÛ@L- ¸‚É0†ÊõŸ¦T¶š¦­®ˆ´¸îBÚì 9Q—ÔûÔž!ßi¥*ÙNæÜ·•&ñóð1‚};W|[¿,™ä;nQmgQÕôølæw’0ÄÈì~÷ÿ¨WM¢ÜÄöÂ]#t]Œ¹cÜõçŠà´ùZ‘ð6ã’{}+«ðÕ½ÅýìE,^U¼Ë)…˜n õÇ·4T•ºZ~¥ë›˜n4Wa"yÙ9Œzzþ&¤ð\âYœr\(üióØGš%‰¢`îQ‚œ•`zÏð¼†-vÆAÉYÐþµŠ·CXÞÍ3Ý>VáØR÷ÏNh$œÓ]ºŒý=¨9Ï Ô<-xd¾sܳaâ‘cëƒéZÚöm‡ôÈ#Ž&ºŠ½#HûÇük^ÚýÞá¢wq¹ð9;F{`ðj°K¨Ê@FMÙ;YðœñQï-οhäp_j–8Ì×1‚v‚ç³Á5¯à”7zÇÉT ¥1òžÄç“XºÚFÓ$И¸Î²Oë£ji8A"6mªßZÑídiÉÍ õ;»ÏÞ6¥uknCFÎUKEŒÆ~½ëžñ<º|ðÚI…K‚»®1ÂÍßœ|ßZFñ¸·²a å‡Ežµ7Z5-ÞľXŒîhÉåLéR’‰O™_C&ËQO—"ìS…\t½hEâ-üØ>iGîÙFy®düéÆ2aZöº)¸Ò`žîsÁ©›þcWN ÜßÒ'Š`Ò¸œž+¡a ÛåN߯¹]Ñ£ýÙRÛr7)Åu&$Helcºæ¾Ook£=Ø/qoü½$™àOŒÔI„}Üžü umkf¶êŸ0“•ïŠ|zu†Iñ·S÷+ÛþÔ¥¦ÓâV’xgUŒr úvÍfMâ›}.þO)–ÔÈÇÏB-¾¾÷žk: ˆ•Ï–Ïѹ#ʼáRY¤c†w<žæ²Šr~÷Cº4Ö·.j·±êÅÜPùI+—Ùœàš£É=*æ›wœí$±o8ÂCZÖ¾»Ô^ë³å[ÎÛ‹ú/©ô­šIór»t)i÷çK&DG‘˜‘¸véLÖuuÕŒL º ;×Cá¸5Pè²ÂÉUÑå5ÇÍ Á+Å*ítb¬cY¾Vî8YêtÞÒ¥Ôä(Y gè{šì.— ék8ÿIec(r™í\úÔÖziPf1ÒºœÏcUô…’gçqÐVýÔäË£IÔš:}.8Ô®Xàõæº)6‹pVrûÖv˜©ûŽêHâ´®Aå·û ×ÉbfçPöíb+ýˆþñy'Ú”K˜qƒÜƒí\¥çˆçeAq!Q‘Û[þË×Iœ, ¨Ê{ô–YZqV±çI‘_ ‡ñÛµ‡µ'^—·&Ûçc#müÆ+H«E˜Þò¹¡™õ8¦’7dhÖB3Ç#¯½f‹óÛ-ŸÎº—¿‚KõÝ"CnöèèÍÇðçË\š‹LT&úšÃsÞ|>Þo‡4ö',m£Ï×h­1Ò±|*âO Y¸=o_N?¥l!ñI˜µ«>Yf­öÖÉæšàI¿f#òØ‚áýk=,n&™cmŠ…ËÍ5­åˆ¨’6BÃ#pÆEžœ¬ô¹ÐkWÚ`1‹Hâžbs4¥8Ï õú×A©YK'†ì¬Œ±Ç3yq¨'–.;ûמ±äü»}½+GL½kyCª––3æÆrx`(KT»Σx¿†ô–Ò­¦K‰!Þïˆþ`qصÂkhcÖ®•¤0s¹Áê}ëCWñ9ÕcHŢĩÈÓótÍ`Ë‘LѲaÁÁ^õšOw».œZwe‹±™apÞŠyOzè´¸PÅ6’~êAw¥Ed¶Ñ=½ÂÎÃ!e\`gœVÅ”M•O/é^fe_NTz8.^sZЉ|8÷ÍO4ÞkŸ÷Í"áSØLU bIK»u“æH\ñס¯œŒ}¤×™Û7dÙÉjZeéšl@Ø2³ŽÕNMý`L@Fs»?ZÌ7S\„’g,ÆE “ž1ÐTr^ ÓÕDœí<÷«í!N¢Š\Ëîÿ‚|Äå+Ûñÿ€j¦‘tO*öæºÝ-–==,çPFÍ®§¸¯6Q£’_æàŽÕ½á[Ù›TÅĬCF1–ÍrcèJt[“Û]¿à8J±D’ßC~ÃE³Òî$¸‰ØôÞ~è«òÈ­ì¤v5´’If­/"«†tï-V°?¸’YaX¹eˆW–¢êSöÒ•ÚÒÇ¥*söqZsI•ø½ÜËåüÀ¬Ã ««ªÛÏ¡Ãmw9ŽW"`Ò‚Aéï‘Ipð%ÄsP+{š«\õÿÈ[®~ë¿êƺMƒw=ëøy/™áùc—˜Ïõ®¼±Á4™œþ&y妃ý‚°¼“ Óå˜Z@r¸Áý+‡ñ Û&©,VŠë…VSó&G©­ë;¸4ŸG8gm …ŒëèqÔš}öŒuý{Q½™<˜<‚ð3½Ž EÙÝ›u<ÿÈŒœµÊ}zÔööÆ9d‘Q8n£éK6˜öúŒVÒ0B0AíS§‡oˆ‘ãT^‡vjÛW7¿»¹¥¡iÇ5ÕÔè­G ¸dzæ¹ëëÓ>©%Ü,ÊKîVî=+Vê»],Ú<ãÉ,0Àãð5Ï•(J‘È8¬\}ç"éIKscM»šâñ¦¸™Éå˜ä×cc2n}ëŠÒ•át•ìü§Öºûi2Bæ¼Æ7›=œ;^ÍX×ww\‚Ï­fê‚K«˜Bá¥r1Sü»OÌô ¤ˆþY:W—MrÉ4\µVg“ßCsar`v ñ0 ƒß¥R’m2¡?u²85Ðk²ÀšÌþ}§Ú #{/òªK¨i±Ê$M €ó»È×ÙÑ—4ŸSæj®Y´Œß³º¨mñrGÆyÖxkOxå[±"ºÚkŸ»¿K™EcºŠ9®§B.öHÄ"’IÀžÕÏŽ“T¬º›àãz—}Ÿ÷»2²5-M­fŠˆ4²tÉÀ«e¤U$ÖF©§ÃxÌÜt#­xØxÁMsìz•\Ü_&är—6ÂDäeȨtÖŽÚÑßdL$b²©lõà½ÇzD--„jÍ…Fwoç]E&È\̧${⽜ ¯$¶8±·tâÞä„0¦¤áˆOÌ»9=ǵhÃ$%yž4Þ77B9ëWô£ý¡.uK·7å‰ÚÜ©úÕyìÕ÷ÝÙª¥³Í²Hê7zúf½îõèy‰ÛDtºB[ZÙÛÛL.m·’’ôÊ“»’+#YŽ(nÈVXCuêåÆ¿y5¬’\”2Åp ÂáGAYú”†[{i‹«–‰^™¬µë±J×='á³Ó/#éûÀA]Ê‚xë^sðÖlÉy#åVñÅzÂŒ–äö¤É¨½æy>•âk¸3ÝÉÈ#ó~SË0è1õ¬ýcÆ\Eu$RÄßnˆÂcîŠF?•q¦•âŽTŽße©nÂÎKû•NR}vS܈-$uýãF§<òp;×ky%§™å3®Ý褎öâtŽgUqózÒa89²ì7Û¬Þ‡0çbí]EΤE¸E/ÚTǼ±ÆåúZÇðÌYî<4L3cÛ¨­«M>mCQWEv¶FˤY%G¡Ï4¤ú&DÚOBž¦‘²¶ƒÉòÝœF;u'Õ¥Ä3 ä䎵Ñß^æj³Û.–C¾)·¸®GY²}Tx!Rázü냇sשلĨû²ØÛYJÆBËŸ­V’yCdk-Iä<¸§µÊ’Hs^j âõ="ëJþh•WuCQº’4i$>l®v®y$Ón3»"¨E(¹¾i•„ýk¦œ_]‘ÍRËmÙ~ÊËì À,Äw©„¬ò5RiF?¨d‘\äÓåswlJÑVF隯;’F+;í ŽY‡ãQ5Â÷Í8ÑÔ|Å‹¹ö!ùŽkCO¸‰`6èѬB"nchù“Žy?¥bGÅÓ$n~¨„g°­ m5ø,ÚêhfŽÖ2 Fr~êá©(­Yæâë]ò¢5„Ú¼PùNB²¨Ü\zWI%¼SGie2}™Vq@…CrN=+•°¾û·ͩػ±¹àõÀ®—X¿{Ý4¤¾Tez&ËŽàò5×471Ù =Sʉ~F›G5—¯Ûˆ<¥Ø€Û}J–+É/ío­’o:(U8'¡ªÚ£ÌÖ¥®%ód$O8™¬-­Í³IwÃi6êS®3º!üëÒ÷8ÛœW”|:”mì#;£<}z¶å`0¤Â¯ÄÿÙÿâ°ICC_PROFILE lcms0mntrRGB XYZ ç "acspAPPLöÖÓ-lcms desc @cprt`6wtpt˜chad¬,rXYZØbXYZìgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ öÖÓ-sf32 BÞÿÿó%“ýÿÿû¡ÿÿý¢ÜÀnXYZ o 8õXYZ $Ÿ„¶ÄXYZ b—·‡Ùparaffò§ YÐ [chrm£×T|LÍ™š&g\mluc enUSGIMPmluc enUSsRGBÿÛC     ÿÛC   ÿÂà¨ÿÄÿÄÿÚ ]O_{ Í­ý¸ÀÞÛl6A‘YŽß[”áI¸á͘T¸ËÌ;Õ•rj³¤gy=w;-¬fà_t+s•¨²Ð ö%lâܦ€§rÚEyc‘œ]”¢ëª=¦x2ß@vg‘ßOs_“Ðǘ#j…0Ú³Ûú91š½ãk§êäàʬ«HÊbW< FÙžŠìÏ;½žæ{tÇ·é”,ˆ÷¤bWêLÆf¢óèÆ<¤ƒ`‰¤ã2ó}'˜Ì}Ý΀Ð.jJ—†ai˜Ê“ Y0ÆL´š8ÖY ÐØy ˜á„-t¼fe=÷E6Žˆ³Ví8$'’aGèØ2ÚÝu¥i5Á%E’Í[#溳¦Í=îåk?•„Þ 5V¸3Æ;ˆ=k P-¶°ûs,¹2ܹuC§$jG”Ç0ÕñrSDW]á&¬‡Hv­˜¡µv°~Ð’â'Ü9Œ¾U‹fË4TÚ9‰}–)óJ Þ÷D˜ƒà¶Å­aŽ£ÉZ‹T"Ú•±qA@`%’6-Y ´-í£•×ÝÅc²ÃYV„Î¥É*B+VÃÞ*­!©8´;ѽk¦±Fõ˜”YÖ¥‹Ê}1Ê#Ýo^Ø´Óç|´©!sX-¹bÙªÎ6Š‹L+SMÀN—X$¸¬I4µƒÓhr~wrÈ÷q$]|¬ÁZo <ËHê,”ºòÙzÓÏQAR‹ÌXi@`&Þ;·^ÖÜ'ôww¡aåð©`9ˆ¹õ$»”HV‘#»1ÚÌ´§Ÿ¢ù†"üh|ãe\Ó)â^íª_wQàÑÀ0— ]£¾úB)I6 ÿaè ¦6è)Jó l¬aW5OC †mêÍ~×?Tu<ޏÆQGmÖ6ÙLúbodx‘…HÉIB2å‰ïT[JØ —E˜èšçãÇ´Þ!X;Q1ÀЈk!`+ÆŠÓè]ÁL³¤ìEvLF>¯ÞZu.Ò±†\ý3Lü"y2»aföš©Èµ+“4Á-Ÿ¤W7g-t s‡^‡«K®}Lt\!•ZTh¸l®Sõ=s±‰ C£TßGq\5O?ªá–Ý‘QW­Rè|Ö–¤X•&†S?f¸þÞ…)S@83Ô•ÍÙÄé§pû9î´-ÓŒž#OÏ=”Ñó×°òSŒTÍÀl_!µæÇ“YhzG•ä/1ÒЉ(ŽG®á©\1*ø1GéA)YT‚>/×™ÎOÒ¼^‰ƒþKÒÊÜEõ™:}’ÙaÄìÅ ¥stG(Á¨†ÈX½HmZl º®‘PQ¯ÉúF¬ ô˜ù¦byo@#ï¹óÓ60§p×J7¥H½ew 6«É%b¿3;{Šƒ·Ål¸_‘õŒx݃ËíD¸õ 7ʾ»(¤ógè9ÇXO•-uðì- mH·/`@ ±å¦ì]«GÄjåÊV_CòZoFO=³’ìL˜q㊕5–̹¢Ô–¿VË 8ŽÞ„ÿ_sˆÔS–W¤Î~/Õ:ùËŠÑF¶÷¾F$qœ÷œ|Æôgj½è³6íù°W—åﮄp˜·ê¯/ΪÑdXÝ•!š±&,˧ˆôg²ê‰éqêÏqå`Ú$„—wÏý½`»}>·Æ«š®ª£µKvÑJ¢ŽÕ,Ø6J£‚³1ÓÓ²üfÁtjAzSè¾'mÆS'RÈò^‰kg;A Õyb£©šÎ––û¢TU4Úà8„$ä›nO—ôåш ‰_o$; Ià 3Ð'DS½˜Öe™Ö,Q^uØè¡¡Å„ßÂ8…k÷CØÓ7J¤¥¯£'_êÖ ë¸tͽ˜ÔXÓ«—7Wm,E**èç:ThÿÿÄ)!"$1#A%34ÿÚ½EÜ“¥ ”-“¸ [‡`B®ZqXÖDvåmà1Pá0Ùø£fWæjmiHrª¼,a‚G¼ãVj>ž·Žß?ÔóÕy¢šØ˜Ë|®Z„#§P€:µß%j:SÖCëiÜôuÂä¿ÊѸ~%(r„™#`¹üMIk˜÷ðØú‘UÆ•&¿®ùܱ‚Ü,CÅÅÜÎQtÖ8ƒÑëwòRìÜ+Œ–ŠœOpðjš… ]=_ß•ù|KõÕíøíßèË/ö (QŠ0ÒY㹞é=%a*‚‚JYZÀ`hÛF(‹6ˆàãÍV|j)Ð3Z5cã"pˆc׿¥þݾ¶ûËçí5gÈLOb\Ö#?‘䥯•é]1A 8:,,æS+™»|TUru˜öË6b¥ap.Í`±>ME¼«ýó¿~.ÑX]Ùôūⴰ-oZÌP=qhòÿã¡N±ù•1dº¦kJ´@M5oí*z…Å®ˆØ IšYÐ Âçþ¤IíGèAÚéâpóBµôþEÛ±þGÉ'kÔH 5G<´øÎWèŸRV¤[F  |¤Ž66ebµM +ðñ“âÛÏ'èE‰BØë« Xè™,‚bZ ÉÓÜ&ÒeÖ–à}ãÐV²¦xdà0á{OKèeíù”ÃÚ­(ÇL[¾7?Õ1©Ee! ö+dÉFÝ<ý¼«_"`ã{Ø­slX©oŒ«û3ºŒrD_~*Åø)Ë~Bô–zÍF2×*‹Ö.¿–W§Þ|Á¢¾ˆëòdî°Í”dq ó9ªæîô½¢òX´VÑ—¦ROüspÉù}mK« ©?7exÉJ÷°”è¹þÓ6¨Õþ¤Ô rñÿNEëC´:s½ébЍ®†€ÏSþãB-ð·­1žw¬½5ÉØYD Nc!CÀâgµ2FbðŸD[½®\ê¥U¤ 8:Œ‰z­C&Y"ñeª3¬ îþ]g/¹ÖýQÒlv;h°Ð« ?™Xº:‘,ßÛßq¯zÝnÆú(Úr2–™0©˜t±¶FQ¡ŽR®j:i±îN #!Ì¥å1Ä eÇèT”ƒo+Qpa»eٰܱ½==´~ލjsôøºkRêô«‹*ÇTéªÐ!@è2¡’­kUÜÎ,‘=AEi; +üº¨Ô”±o¬m"c™kN“VVµŽ/Ž¿&yü†Šæ-Ë&’yÏìyŽge•ÊêRM{O,½˜®Wq"ì÷QÊš‹F€~×ój5Ÿ~Œá4ÝqË­ÙRV½3o^Äò¢jtEÔÏ‚Êe Ú•ÇÏPÐ5wÄjcææ‘­ã3pjªFŠ|¸š€“ú0ýF%Ö_ÆÖ‡Œ…þŸÞ, œö>u<2/ÛVyÞ8®%¼CÙVÔµL´±$Wªk`íz”éíKS©Q”·=vûf?å/£=M*-Á+m 1‡U1zu¼{>B¾ÐÐßÐúë(Ïïdlpó¥¹b³“E }¦s¼ªÊ¾¾QZ‰+¾E…ZŒLw§Ë„G[_ðЭìÑQ†Ò4CXj±îÓkàÆC‘#–%S)-[÷ž'iôyDñë]r zë»RÉõÆèö{‹°ìvÆoÃy6òËþ¡¼²Ô»yÅtŽÈÆ”®’%[A5 ^®€7Í´^º3"Wåšµ!¯Ì‹ùÁý`‡Ï댆þÝ͸½¸Âžš¯¯«Sü®ž'·cò'fÜmk\ DÔâÝóªZhÓ·n+fÊ« †—Zo9ËS‚ñž%xábŒ z'vÄ=J3[‹’¥dZU¨Ùé;ybóáÚ¼/TWÑá{Uoû‹°B©ÓŠ]z‰gR#†üA Þô-{{6oñ?B+z¹þ¯¸õ Wî½YºoÖG^ˆ¿|¾n§Ædb‰íÈÄŠÙÊŒËÕ’.–h-%Cñ:d°Àmf¼¯ª×–‡dÅÚ¿ÜY’• [l¿69ª/vÎÒ5N.„¿’‘nOÔ*ƒON>äªk¾ÇPi ù­d‹ç Ô¨ÿXÓ8¨¼J¶<Ê•”_Yk±à>A­x2æ3ã`k|‘ ,åéA톢_ íôC§>xœR¿ °^Ï”WbøŠ;Í¢9ÕSüoÂß&Ü<‰i¡À! ³b½´“LrKd£!üÈBÛ³ÑùâÎWA…JNA‹â©f ¦ÝÛi9ÿ:³Ûžsâík`ÐU彋xƒÐý««BkYª¡{°~ÀUÅÐø—)FE:$ž:‘<^sNMÉؾÛGF•pìâsš5@±ü«î´TŒysíªÄ[<$d+ KA籉=™'êX¥–ËõZOÚk)P¯Òsã¯3õù—…ìR>R–ƒÐhsDR“{Ï–cŒ5"ÿâË~Cå˜å(c\ãdÁвᗾ!Í쥺hž:¾]¹ÿÄ0!1"2AQ a#3BR$bq‘ÿÚ?Q“]ŽRƒØòÆÎmîÉåþä}Û%7ñ8ϵ[9ÉhMÍØ±žŸs9&ÑW³'öÿeªf8ªå"zj‰¾¬È4Êã4äš2AsßÙï¦OqvB:F“¢_hÈÛÒ%¼³šù}œœ—°O†¤d8É\˜—ù”º=ÉÛ"®iÿ&òÆHÍq|ë²Y\ŒrBû¢O»Ú>´/íþÆ›Udb×LxùJ›#ìTq!㽡cÈá³Xæ™<ðÁ¾Ï‹N–»'¨Ú$÷Q8Í»dÔ—ÈR+ýHî²äÞž‰Kz'½Åœ´;“²n´‰{·']oc„æey8¨Ìáí8úrìS·Ðäæj mDÇÐüþɽ¢={:òÜ~#B‹Lþ„Ù6Ÿc_ü2>I+qÐíDŽJT--˜º$©ù凧Ó?†éÚ1åäèvÄ´4Iì²=þFõfŸbTöqo±WeÖÌNú2|Ÿœ£(JÅ…É[û% •#ÓuhŒùPºòâªÓÞ‰«ìŸúœjÈ-lO“¢q¢;Ñ‹³7Íùúʶ)9Fâdr“ÙÍ£…>Dd¸œ\ºÕ"iý Üv)R¸ô_Ñ\´…ñ£âÌÿ?5…%î!Á|E$ÜŒ™ÔD“DxÆ4Ø¥¢Çü «dºÐÆøôUvqÕ¦Bn$¨Æšgˆùyµ•«lÅݳ;Š^á(9ë¢8éé T©p‘ö1nŠul–¡L}žË¢>ÇÈU-˜í:gˆúóÃ);=Vô–Ïm$ÈËÒÙyxÿ ߸Ë$é2IKvEÉy>»äæÛÙéý±¥\„Ûö¢5ôFK–Œý/>K¨’É»³S%áà¶ÅÕ"rQTÉEݤNKø96+­Ž_CÖˆÆ.$ßD”ÚÙL]ûEIèÍð󊩎£¸E35ÊšèÅ™¥Â6.3I™e*™z±.[?í*þ¾Š£$/hÆÅ]£.ñùêÞÅÑípò̹è†9EU™;´J2k“n&=ÄJ–Å%Æ…î[%Ð䢬ÆÇvOûoÍcöÑé/ÈçþÝc«d_&c¨ãœÿŒn4Æ‘'P1)%¾–ÈÆÑ'¦ZD¤æ(Pä–ð~sκ‰nqѾTÆ®èŶ̑§hÇúyÀÆùFÌÝ“é¥ýGÑ] |tGšrg§ÑÓf–ÙƼãŠD#ÅPávcÇ7å9(vx,¼ÛBðRÇ9WCð™dýÈž>2qü åÈø¡ã¥gÅqd¤Ñ\»§Hùúý“r_<Ó³ùBÈû¶dnvxlžžh¿&Œéú²8¿Á“ÁKŠœQ‹ÂæÝ#8ø|T9_FI}£Ðãtш—Éù,‰"9ö³Ñæý¥nŸ &eRɺ#(ôb¹cRgŠn8dÐäÛÛ-Ÿ¥¶ã+òñóPÄÿ,¾?D­ìáÆ˜´¶bfEï~\Ûòƽ¨–)_´Qq©¥±Ê™M1¬‘p‘ÿ/Ãbý;‡Ããðê±–~¦ù8Äâúú$µ³•hK™ ¾~^;=)7cœbé™5G!ŠrQIh_·ÇâY!ËðNþ…׸ÉW¯(kF——§Åë¡ÍEÑíææ9Ê]—-3\ÛG‚/ê ñ Ú=|¿ìzù?ÙŸ§d”¥%&?%ŸÒ­ rFx¼9´q½«!.F~ל±òwd°¦¨Œ®ìÀµÌUê×äǤ3ÆN0Äù}Ñü³úÉásâÃ/j{&¡ê4`ñ1ή'êXÜ¢¦‰\U—"ÅÙ›¥å,ÎôG,§×”u6….Lðعfäμ¿SþÒÿÈÑD5$I)Ç‹1c‡†Ofeêcq$›D!ÈZ"ÛÛ2üW“’ºF•”äÌuZ<+÷ùæJPi¡›<÷–xÌRʽ¦$ã)u)"^Õh[¢×HÉðònõF8óù#7£.~ã—TL9œr&˵¢É;Ó?à±Ãcy8Çÿd1C¸£‘dçÆ6dÖÈìRÙÅvOáåJïËŠ¾F±­ò);G†ñ\=’¹ ™§Â6cŽË9+6¸Ä•ý˜ßäŠ7Kàü¿ÿÄ4!1" A#2QaBq3‘$R¡Ñðb±ÿÚ?¸ûEPûʨ ư‹Fþñ©_`êTaÌ4Á9^™n:žZ4vJ`¾ô5’X/‰¼ËÛÒÅùU.²»•6ˆF,LB[K[˘o{x*ãx¯¸OË”ïõ˜ˆÄ« Cô“;y>•Èœ} ·Ìó{ÅW¤m}FlªcT$Èñ.ÇBeÛaÅ7Sõ}D¤Še@È>óØ^]Æ¡1±úSËS•£s•¢Ô  #6Fðñ½Îç›iæÛV•9ä5c:wÖù‚£1´D÷i•5[(¼§íŒI{qÃzWK¾e;ãÝ̧¡Þ&6‰e2ÑÊ'gÖ&‰ÔMÙúŠi£)TF$¤Fî¼$¸âñéæ8Îçú…¦7:ô‘omÊ@w.iÿR;Š€}§™’¯leB›ÌmÁŽ¢ò€?°Å-o̧«˜TUßY¬#¥Íá´°qè¦÷8¾åKâØJd`CÿêT¥‚Þñ@0¡'8jo´ãPÞT¶S§½û%0I±‚ã¶d8Y{6„kçu1&Ð hú«/–E¦\cí<ÀËß²"×Ví¶£Òò˜´w" gD{ÓÛL³[™R×9³jRææ3wØLÆ¥7î•5ÑÝ‘R%õ©O1¼µ&Ûžnc N¦™ó1p¬Â÷¼ý1ôelJvËQ—&˜“ø‡ó?§¶• ¸7‚;‹¯´öîö”é\žü Y­L—ˆÔjpÚ‹LdDb¼ùe(ß.þ"X}1W1h=ÄgïÅ„©H>Ì©¢``Ç^† "í²ÒÄ›ÙafÍ­,jušiR‘K”€XqÊJ¹ìÁn¤;¥@@ì| ´s’ãâ\¯BìlƸâRÓ‹Ãz¦)§Á•-ÂÊAöë)9GÀßïž" 7'–-h*èªÊdÚÑ©¨ïh×#ºFÏ£ó;¾ÓøÊc«~ÑqÅŒR·üÏ%TÞ=²ÔU ä"·ìÜÇdÄ©Jø‰T]w n Éß 0â-G5?ê6Œ Šæ*äm û7Ä'$´·u„-‡l ”o/?Ì·9ÂåSM*ØZdÄ€G¢Ö<ÁVÖ&enþ§— ßf'º6ö€©{Jºx Ì(ÞeàĈ£ÞS…•8?Ûè·n&yikZêt!ªÍ¥ˆ ¥O¯»QÞøªÃ ¢€Õ8ÔêI€X›ÇwÂ4ö¼B*5…¥:BÈ…®&'gÐt'ñ]Þð3]u[ˆôóܬ¡*! lb}m+¯Ê ÜÀXÆL¬cUqXµšó”‹¡hsãÐÀ´xõ£_™O¯ £uTm¦‚¦]×ÿxz“åùp6q\ñ+Ô7¹‰LçPäd¶K“sˆ¸·ô[W¼"ÆÑ=Æ50ë©^˜t"u¤×%a÷”Hò—~Ó!÷ÿ¿ÚRêÑŽ7؆ºrL.kT¼Ze;›‰H_º2µæXèÁâ È´^ñ¹rqCq̱Ác® ø€ÅÈ|7êTªè a>5ÚèWR“Ô«£:,Âò¢yš¼£a `ªZêDa½ALj¤ƒfêa2Â÷†Ã‰ÌgÚ|AÆDΞ³Ò©šCñN¤/þ‰þ/ÕŸ´êúŠÝ^ê'J>p“+Äl îŒøn)È_ÄÖÉq¶â&:–&aAžqþ˜äß#:Çf”ÿ KXëZüŒ{ÌgO÷: ¥{|ÿ¶Q¿¼$¸k‘ã‡ÌÎ^ј¼Ùï^"¾{…nëê`1Výç@ŠõQ_{Ÿ£¡þ?I@~Áý§Æ(ÓJjU@”þOô¾}÷kÊ0”›ÌLÁƒ¸þbœ4aÀ¸kÐUO3`Ye\é…¦§™‰§Û{Æ6[ή¯˜Æñû ¥R¥eòý·¸?SöÜÿÄÿ5ÿûγ¦­Ô'}µ(æ~J}¥n™ºf½çê¨2‘Vkˆi±©s*qq€ oÀñ¦dÚu&ÈL¯PîNí> >qþ?â ›STêIV³õd]x+q©L-6•ýÀÂ1>%,—ˆß´ó3„‡ÅO,ZucåÊÂþÓyÓ3+‚¦ ©ñéèÃNætR9LƒUºÀ¼AT;ciP} Ví¦,¦¶áŸˆía±*œ×Rµ;‰å­âÓ¶ÄuX:š‹O6<ñ³Öc0ˆ“§K˜¢ú•;x؉™cb!ð¿…¥ `Óf2µSqº|`§)RÖ•{Ú*¬òÄZ‰Bšª.'è•Òû†üK©Î'ÿÄ>!"1AQ2aqB‘¡#R±Áð3bÑá $Sr4Csñ‚²ÒÿÚ?ÆI¤uç•Í“¦¿*1¼rˆ\1ð¹çX-·£ÄBjI|†Æ%Eúšìö¸÷×­2˜ß}{Xôøÿj|†Z6ñ|?±[¥¿â%Tô¡q\&òàØšYU¿•OÐ+ÈÃ(æ¿#Ûʸb¶9¹¾ JvuLmf'‹Òœ<øí2‹è›ÀWÅoß:]áellö±åúWÝ»H ’÷>ÿìTxbбi‰ ÔŠàÚ]XF2;ŠÖ3–Rt¨£ŦHt4‰&‡-tµüªü¶úÿ&gµ™—º_éGìVÔ… ùs£ ú«\‰a4®"-98…ð‹Ô’É{«aÓZ2Äx™íç­e) ÑõÈùÖ#uUËO—÷¥±Ìªçz»! Øk+6¾ï+Òµ¢D½Øh7ZF}™ îÌ~åúZ™âX®½“ÅÓ‘øV×yXÄ÷ u÷Ee#˜¢,ÙŒínUþUüÿÉ&ÈÌÛÖ°u}5ÓáX»$ªO½Óë_kiAmn·üêåb)âH÷×°¨£Ã¼Ë“HpÓ•úSÀ›˜$awhß™=2µ+¥—ÅfÖžq(¸½Ç½ðÒœbûµ·!ÓÒüª7"÷VÐzÔÑ5ðmVúëûÓã_xª„ ßO:PŒ´#{yþU™™ nv¿¥²²Yp-(K3L-VÖ×÷×¥$ˆœJÍâSÄ,5"¯øZþÝ*ÿéÑ{î×úPnÄzr°­øqµrbG?•/èè­Igd[ÜË_ë\O~Z±þôÜò¶G}ç ·â#"†³èjµÇ†wÇ~UÄŠl–\Á{¦•f˜DÈx8n>5>î6 y 'ë\*R<ÎL¶É»Ÿ•éWfÚh!R¦>v¨‰QÀpB·nžtÞt§ÊþЂ6—x,²X#Ö¶IGl˦y9kÞŽí÷±1ÖÆôƒ(¿•&q,‘®¡®oÞÞ”’*‘qNXÑUC’Øß¾•‘%ýyúV±=Ô\T|ÖúÖÎκqÊ–?âcˆºóM)¬Ç°²ß­¨! ´äÙ&\'*’IyýåÖȧ‡= žjpü<ÿ ¿qzÙÛñF§éífÈÇbݦ¾%õç~½*´Hˆ¨˜}A·+Šˆì­ ˆãáâmRE»hÊ6£Nöè?w©ž%8˜éߊ߭kâä1éRI“É.š±÷GìT{Õû°ÚKÔp‡MÖ ,®9_Π ¼!t±¨÷œ+~þT“Gmÿ2ŒÝö¯´.RE'ðzØþI&,÷è×WÞmm.[×yÎãCækcé|[•ëA¾¿ å{~´¬§%饫e?ÉoiÛ7´!‡x7§ýó©¡šò™šS&Í–uròHÑuòçM-,†ú[NúRtÍx¼ʃŌ…´á‘º·j9ÛÐÖ¶tYyv©0u'ʉXŠJ˜e—½'—£x^Ã…›ZP1 ù‰:VÏ׃¥JB\Üu·ZD:HEîzŒ¹PCϨ=û|( ¶S&œ6ˆ[ùjž €(ïÂ{Úõ;D†Hã¹à7°­Ð‘Û ÛÂïëK—/wO:ÚÓþ?¯µ7n¯¼Œ5ìJi6¹÷[:‹Ý5-ä+m܂ʚ^ä.´¹H\k_÷¥4ŠðfvgûËÎÕ³4Í»Ÿ\c(Iu·=)vRJ£Ë‘ ©],>µ³¼ónŒh|÷·*ÙŒœqÊÁ/*@½læöºtåΤg9/ ¶ŸŠ·aK9³ærÌó­î*ßË%FûDƒ'dú/¥J@œCªnºþ•.ÏñÅ[ ¯/2EÝ1|ZZüíÖ·í÷&¶Ô¥ý©] u"Šç&*zQmP®ÍŽíõÉ|ſᑻžŸò½$Âövà â_6Úv„È7@±×È ‹iƒeX²¹‰¹›uµ¦Ù³¡Þ:–‚/OJ@€ÞÖ:ÖϽ‹ï-ÅËêa»µvèœ.=N”#}Ÿ,Åò¿__ßõÌ;dz^ñå©#¦•,òCF‹Çk]…K€qï:™—¥a‰>êékj+iD7³1åMöéí²=›1Ë_­»S&áçÊ£#ø¹X\ó©7ÄG9¶éÙFœúÖÏ /½‘»¨Û];ÖQÈñ?í›|dÕ¹–~tÿi€},êsoAqY¢cwTj|ª X‡µN8¿†*ÞïÊ0 ëÖ®[=ùk­E,6Œn­á°·õ­Äö³o Øëûµ+líg‹©ƒä5çRDYÓ;܄˷:Ú™Xœˆ¨GF¸úiGVx‚ÙIÓÐò©†È2Ouã9Q’é¯Jh4ÌûìÎW6R4m¥Æ›Ö6*‡kmŸ9#ËzWÃ{é• íÙFššÙÕÙîª9Š˜fÚÆÜÇ•x™VÇ0:ÑLÀq¦EJˆ±ÝŒˆábœ­Û×çGgQÂ̤¾Y(ô¾ÚQalÊë×ëSÙqȱռ7îjY2c…ìxolÿò·¶]© ²|­ÒŽêlû iQO&Ñ"mM©M8·Ç1@tÖÂô7,1[EZÙ¡ÙÀÚqÃp³uÖ¢;R¼qÃ{é[c k3ÝX\k{ƒî$l˜ á5ß91~ôçŠøž•’Ž3‘5g)ãf6×  ’i:\<ü»õ©óš5b¹îÈÕj†#äeÒçÒ‹«&íÆ>?+Ö"t×ÈVÏå8ÿíífÚ â´Š& uè8¡åŽ–¦Þí ñÆzþV¬çozõ6Í4»´U/ É»|j ¦µÃÓ·ìÖØ„h_x¾‡Z„4FípŽ/eû½lß‹וu×Ntrd/c»Îý…L­•Ðw­ìÄÖ²¯Ê£m¢5Š3ÌärfרòŒH¡°Æú}+d’Xã‚%r7n.óÅ$Bª-Òö¬¿ ËÚö™’tF½;‹ØŸzˆYAæ¦\죇‡KÖšÖ(.m…Ï%-mt¨%B×µûùVÜžÆ×Ò P .#¬Ï;ZÖ¥Œ½Xs¿J Ú#ñ½i¥ˆ¶êO^_Þ ‘ +1ÕßUøQ“üA&ÆùDEòëz;&ÊA„I¼ }ÓÔ~ûVÌ"vF€êyÿzzFî£Û÷¬yÚÀÒ¨ƒ¯;ê+Aˆ#½ ‡g!#QgdæÆ¢ÐY8múÕǃ¢ôQÓãZcZ­ª+r·=k™ù‘*7ŒÙr;Z’+<[Þ¢þ!Ac\bqD&˳ìí¿r¬2áT·AÚ·JNlM¼þtákÜãQÈ¥’Tw Xkò£ç[!çx–ÿ/d·vûÀ"jC–@Äëëz¶nŽnx@¹5¨6ùcÞM—ñÈ鯮kh*©³òtPdútò«©=(,¥w‘¯ ô¯ʘ†Öß½~cÞî(YÏ!Ö¥Îì/ëW]~á»VðžIx'‰¥!hŽR÷`(:Éšx}4¨œ«§ÙØ/ÞI©áêhz ÙO‘#odˆt祴©f’á.  “miÁ7×½êŸhÙ£½œ}…ºzÔ›6¿½ýè¯[ÓH4ûØ·`,h#&âÞ_:bÛ£‡—÷¢‹Ž!Ô™;óùÿz® yÒ¢‹~~Ã)—>é[ió©‘#Íf¶ûw¢B’P)Iä8¨ìïÊöåCsŠõ÷ »\‰ ò>•ïZ…ë[7o!Z…j¿JÐQÇ“ [½w2mžt›íÝò/>u7ÙÉ1YïùQ9!.½‰ëj„0±Qnw©ÿsôà ; §—-´Æ;:_SQlVÝ#_6Æë½žC†ð ãÒ£Æ]âÉ{\XÓÆ†Ë?áWao&¯ ŸJ•‘p!kIZö½+M¢NCÞò§ÞHïaM³›[y†6âõ¦½*í{>úAâu}Ù°zn¢H¦4u¹aov²ŽFÝÕNiöeFBɼfçÆ9TNªGC~ýki^ÄÏú{wj¡ã¾EO](í;VM}lê]­¢e+.\ú~/΢+ G(ñå@7 ßÊ…x¾tâgÅ_†â“Ž^kîëVÊSò¡`Ïe±v–UÈ/Œ**Q•ÇCB8ñI¦G ëAdÝH«Â®¤Ýy\})UA,Ëc~šŠ^,ÙЋƒá&Õ…ÑZÍÒ¶¥î£õö69‰7y.BÀ›êH58Øóö!מTEÍäs¨ì*š3.DÞKž…tZRå‘ GBµß—:Rul—Zhä$‚5ÆšÏâ:³šÉ̺ФPÛÆ@us­®4€,›®0ÄøyiéYáåùÚ¤^†#ùŠí_eÚ@’5Š­ûÒ¶­¦+ÉÌPf‹\•Zæƒîná(³Œm +Äk•0hï§Zÿ©<œ7ÿš¹Û¸÷V": éÊÕÊ£xœ„õ qZµB̼<6>ôv™XFU òÖ¥†c(iYun÷4ë6AH?ûùW*yö˜ÀHÀw¿¼y¥"MË´»Í,F=ªG÷Ÿ™¬@¼–çK´´èRúâ[ùSª¶réÌu¹¡g·•wøÑ 4¦ÿNýK>´#,Æoô¯»K÷ög,ÄGIr.âÝ(4²>(á¹ó¦yšË¾¸Ta£Z¦(źi[/©ü[Ú±)ÅG;u¤Ã.W:TqBKü—·ÿýëcÞlòmèÌÇ[µŒeÌ{_ÿuÒ¼TO^•Îìu'¹¯sµxèMéçSKö¨wP¿‡†ÚÔÐWåbqúÔò+)Èçº6>"o¨­¢#mÎð¶=ëeÿ˜Ö¿ÿÄ&!1AQaq‘¡Á±ÑðáñÿÚ?!xš%j&™N›šÀ„ÐÀ÷ŒÖ”I‡eµò8VÚÛ¸6ô½÷‘R}JòuŒm@äÇíš–VUe§üùÀ©Ø8DÍN2Áe 48xé5‘ ²ŸÃq”¬AY”®ªç7"4ÙBd›ˆf7‡´H€.—sn·ŠÞPÔ÷DŦü`@‘˜óo\î½a c9J£º? äqµ_'Ro ½°ýãøÁé–§]–‚íÚ‡òÃj‰Èfüä{LÔÝ<|s€ô ±ÒE#n“ްà‚#H¦Î.¥SŒJ%4k¯1ˆšJÍ"¸$~q‡-BŽäΰ8îºß€Püzë&´–,›¨Ám¥‰C(ˆâ_[É0på…½z~xÉ^v/Ë4‰œ{'Z ¸üå¥t ×`NPDB9kŒ™N—F¢<¹.jGÚ¼ÙU€<äc»d¯ UGã$˜‘UVBï'm%¥à’Œ ¡,íÉa˜(Lª1Hb¼¨?91®‹ÈXäKr7Ësc}o#E ð&ù伎K…k¼z%¨KzHæ2&taÌ ïÖ&®slâKZ˜ÍOWÂJÖŸÞ$>Z=/Š8k™w3ë¼(•d@”ƒ­GŒOŠ¿†2&ñ€ÿFh\DD«Ðääþs)câMk$½šFïþŒ™€R G€õSÎòIÐ@jÏœÙ!wQ¿x“œ>ˆ~Ÿ¸›+~ƒhgÇ9^OJoò$À´¸J€—/ÆÝØ‹¯ìͳ Âe.µ÷“¹h’ahÌ]*©Áô¼¦hEÆx€FìZúÀ¬i ›J“]S¦nÙ\£™ëÕÌœoéç'â2øcaaÉ;÷eúó‘œ&´o¼žfÐÚ1ýà!%Ö¼¦ªãóŒÍ‹qø#y("t’n>Þ°„“,Të_m`éq Ê òoïÊ$?V÷„ËZ‰ÉO%JM|™8Š’Y3óÆY›¶òÍŠÀPiYáE¥JtrÞvÜæÂ¢øð¸ÇDJÈ«eü5‰/^iÇ”jòÄç ¨„Dðâ篂\ÊÚ=·L  M¤«œ:$oÕ‰Üó›pHGÿ¶q^cBá:mÏ7$kI™&8ïævf‰ò÷ŒEP¦•¸ãèT®çMdßš‰òåº` ó}°ýøÉé´•AD¾X/ "B¶lŽ'Î)³Ÿøï v¤‚}a!‹!KMñü5‹ºCíxZ¶çÈ“•ÞA/}™aà”9ÐÄᦰÑ`¸á€szzÀQÔ¤L°Ó o&ë`–4L1ƒEÓj#üŽòÅY úx½äh€ k¬$l½#¶ÑËs’¤¡r‚ZóŠY«"¹ÁZîÒ•¬‘=[¼ a÷=åHÙzTr‰]å!„^‡s€´±od}äE@bVž±á2 \Ûk;Ä^RÃ0ü~«õ•4’a§ùÁD1 ¨Âç S¦2OJò\½ñX¹ÝLi…«Õ=±ú]êËx6F³2ˆÊ§«ø—Œ›q_HWÁCìM ›À@5(VžNûüb¤GTLÐÀ¿9n@¤Ú'˜÷÷–(Wþ.°`¢žH€svç-ŠPRÝôçs‘”ðD³ª*M’F ä "K> ,ÜgdOM¸­£ÿqÐyL  KNÞ'æþóWäOÇ!Œ€¤Pšž»É[6ßK´;2j±É˜i¾ бgÁ˜à‰—{çÒêCmBÞ‘ÉÐä^gVþói¦ø´·–HøÉäöU%‰Zn㟺Æ*EÌ2OýÖ0’1zÝ$àFÀ$jrQ.4LˆyÇÙãiá—“ˆÓ'œ3ÞòGI•öÖP1mšñ¼©X”IjïZË<3’EÇŒ®“íþ\Oïˆ\'ÈÃKàyV?Õ9½a†ñ€­˜Š@}t;pÒ#µ’FÈ|áŽM…töë“ÈIi$Ôü8<á¥ÄhÞËÀÓs}¬ÄKó†Bí|?&Ì¥Õ3gíy œ"O3ç­«‘¾¥‰”‚ž`/Ë‘Ý`²ÅOÎ ?ÂBЉõë#z³…âÝäæˆ·GÉ^pþ1C"ùþ£ /0ÿ}daã£÷ƒ`JžN¾Uê1h- ;«ë,3ž£>o †!øiêÃYP…IRz& Œ%å=Ç0±Ï¸ÆN$âGj™ 2s5VP™ŸÅ}\] |w‰§@H£Ö•e 8r%§à_]瀗„yÞZ‡ÀŽ WXH9š…(h.ͷα. áDÝ ›gÄäwYƒGa._|á߈Ņ…»âÊ5›Ö ¾Ÿû”Lмa)ç¬lîÛÁÞH- Ž=bÃ¥2qFlu6OLÑìÃÄÍSœíi[ŠI0tplFžwþºÆQ¶ TëˆÃ[… L‘G<äe¤Ó¡9!´¢>²5HnRÈ-sÅíÅàä¢"u§”Ʋ* •¯Ïy_B&äÛqZĨY´l~ž±«¢®õ]^^½S‡¯>25Èwåþ±`ŽŒg¼`•ðBš¾ÏüEöÞ§.OÑrAS8ŸPŒ¢MšÞNU4pÙ$ñŽÔVUN«ÝY >hÞF:Åñ. Šȇ°â‰´|c2ùIŠÈz[æSlÕgJèUkœ/(ÖD*ñ‘PlÔ/^Ðc 3„ QÀœ‘RÀY-3ä`3®.’8j9¨ÜªçóOó~²ãÖ\äÆ(0/Øi>²7RŸP™Æq…_Là ÉÇËüäÁyW Sî¦/þ^Mž&™“_·ã%^() 9=ñ3ïÏ) ¯ ¬paAv 0Ê ô±d:ÚjGz}e×"kiKæò]¢ñS&ÔÂ?†[Iˆe4JMÔ÷•a6®Ð‹´WÆ¢&‚ICù¬¤*n.¾÷Ѽ"¬G?b~ðv‘ï>žòpáDÈ'Ö4¦=¢2C‚¹p¸1©º?Œ0ˆÅI;Yùþ2Ò½‚1Ó^ñæÔ€ØëÁÕÜÁ'ÆJ‘·NúÙ€(bÀæ5¼º-‡ú²@’3!knÏï ä*4,ãÕáÜ@¢¬­ñDg!hí•×¶Àbk>—ÏÞlš‰,˜˜2`Õ œ#lùë+•&{¦Í?8”Yã˜"ýRGÇøÈ‰¢r­GŒ‡°àBU<¼“×¼(Ctn§÷…Æðe+ål~T[œ'A~Oý_X#+"fâ™ÿ”P‹¤ˆÁ¦¸ÉKN/Þ5±s£`G:Þ ”bØ™M57>Œ*E $$õã5¯‘´èvñïYGó>Ãó…R1ˆEözÞYÚ’Æ<þ²0šÇó“Ï}›Át‘ó8)u‚}iŽï‰3“ÌJXó›)ƒ“æ žƒ?œf)Ú“Ç'Î8”B62z‚ñ<¯›>p%%¡Éèœj>é+øÈYü„ŒK€CçÒy”xˆ9÷üá߬rĦ<~˜\´5T…1‚Éìbuñï'0‚ª¦’«Wˆ8n2joé‚?¬·¦GÙ9½V^NÌ0D~ã*/a;™ÀrÂ3§ò8Õ åKñùÉÙ8ÄJmñùÈx\§cC…ËåÁ‡S¥’ÿxƒm¤œK1Ýâ‚. ×ç4°}§Î½ä#‰5ˆñH!¯”²>rj—j ÚבásKA§‡¸Éïí – 2ñ‡ßN–_´'8γ[ñQ¤{q÷„ ³É«(àY'G‡=ï =ؼ0‘³Ùñˆá¹d×e:-3xCdàPW|ç Œg‘õóˆB¶1n©_ çå6åþò\‹c.Þ0ŒA.•š‘º8aHt•Ž‘ NG÷¸ç•c¾&ÒtÜ.òž¿Þ>b„\<…k<ºsÆ1}Ÿ¦m-dˆä†Æ…cÊwƆ„S¨™\”—îÇp7HÑéɬ1G²$íQ¸çã¨L$g$ªQ=à7º-æ²ZÚsZÐ÷à á‘Þ»’XëF@[µ‡R‘Œ/XÁÌë[&õ’€”SÑXRa›‚JMªyÛëаž%Ýì_éŠ*T‚ È+öŸÞngë6?jÂS®»È«U­9· ‚åQ¹ Å@0ÄX@’ú·þ2}à)ÍØžpñ…Ê'"˜U9!!òNü»{Ä1'£¥ ä2(©9ÿ¹¼ÔÛ¾|aˆ$T‡[ù;ñ€‹7Rh‚wÖ†²¨_meò1nÿ4ãýûÀ9¶Ì¨Í¾*³ÄǨçÉõd°+Þµa•*R¯iæ'R/{÷ž4A±Q÷–5xöüᬳ ãËX%JÓÎ*ø€Ü’\'†·(u¢ËËÏ~")á¸äË@ø¢ÐDå©19‚ljp9»aÖòxd–Âíâ²_œ¿ èþ+&]ô¹ˆ2ð²ê²Ä™>?F&"´á/¯ÙrsÂzÀÈu‰>2:JŽMF ‰`è6Oy7yäúÖU¶”¦¢c S‹Ö’a)®D¾•þ¼2©„‚Á«eÿ¹"ÞpB’?œ”¬ôe à˜¾Ãk[Êj+×ç/’×ôÿl5ÞH<CʵAÇ<Üýð'4^FÏåúÁ\¢:€üä äD/}sñYÞL’o!†ÍäëÜb×x²YANŒyðÁÖò&“øÄƒŽDr/Æs @mÇÜFø§’W†­öIøÀÍ´§ÃàÆ<¥—Éÿ\ ‰XGj«vÉÛµÉêÀH&\Ñšt¿üÇ¡}¦ž²íFA|yʨ‰ÂcÂDúÈïgPIŒ[* d¹˜Û(;«-ÎÁþ¬l.’ËÞl«…G‡úo&·Ï¢B¾ž—>è­xgN ¢y 4¨FÃæ2ÜWÄáFlwŒëê6Kîú3¦Qb(#ßñ“·›Ïƒï'½«*/y¹2ˆaÞ5f“À4{ðåZqX•X}˜b¯„âZm‰ßHþX²mÈøÀE”†KÄEŠ‚þ3Z•àÍœ)ŸH³ÕÛÊo&9J|yÃ\‚Þå*·’”9)†¾iÂ‘Óø¿x,&²á%‡ŒHÉüRò[(aFòæ“ æq!¿ò5‹Î C4‰žŒOøó ú|bó$÷Á;0;sŠöVÌD`Æß äVl^ˆ0öäÉÓWß¼<*Ø*剦7‘ŽÂ€t4 1]k%Á”¥®=Tý¸j~å~ðKgÿÚ O‡:ŸQÔ€ÂÓ1&hñeÀ´¤Æ¹,êOQ !Üž>ŠôØMhÑHþj³¿øâ8ËÉjÒ „°º¤9ÄÃ:34¡LæùèS¾µà1@(ú½·/ÒoÁÂvmZ.ÓI‚)ëÓFÔò’ìÄ»9tB%V9Žçî °8âÅç49~^ ]#†Ù•-i,kâáq";ÿ ™Ðïá´$(«êâ]&}I+7+±õÇ3¼n^^j]¹Ô!-Jí©-Ø_rÒAö2r@,Xó\ívÒæRï‘‹©íoZíšÏÿÄ'!1AQaq¡ð‘±ÁÑá ñÿÚ?Î(ø%g„U‹©EHé(G{¨âÙaPÜ¥Ä#qÖ÷¬SG&f㈕ËFܰwH¢8j Oø·Xˆ˜bÈÓû³óÄ£ „ëïÇ:ŠTÚÊçW]3ï j†ݦ>Õµ¬DPjXœbflÌÎ PAiÚWª aD÷B>uÅBŒÞ pê_kW®Xq­–Ùé Ì4ÇòÉÝX~ºÊ„„{˜¿XPDdk·ò*øfP(6ˆZ8Ì—ø>‡öžÄ{øÿfœWÇû7Yû|%;öýÌ ÓRÊ+Ÿ™¡ê2ä`§ãõÿ²…à÷{>tï+ÆjàAš*0Zá X°¨{?ðêÈÖVD­ZÌ¢ QЕÈ{ÅnžgaH8ðkQÊà(Ž‹Ø~g##¶%²ýB€77›ïz‚`â)ÉÛ´Ë&Ù%¿Hæ¶øŠÚ~ÐÊS–ëûTÕÁ¢¸•íÒÖe.<îf¯p ¦+·y`tõUÅ ËàŒ<;„詉W)›J–Xéø€[¾±,V‡Xë¦+ªþŠŒ +­JS[‹ Ôº@žU…ºùQÔYçxˆXÉÒúE¤K,uŒÊQ—*˜$tPÅJu¨îk……U)áÈb¢Cí~=4Ń˜ÊÃô”§® nª"®³0q,£†\Ti˜Vó e™€Øë ¢¸]¦+ÏîÙAÉ…†°ä%'¡"´@ðñË ¹xçéÌ„-\ÈEŽS˜Ì­ã+;¦"±¾°UV& äÂËÇ*™ Ñâ>Àv©€qš²Ÿh¼ßÍË`Ïæá&§^`n\hšÒ\wf6Ó.k´±§ >i¨ÅÜHw‹sy”„°–g 0‚2ฒF)¨ú¼Ã¸°[Qisÿ„ÒŽ+õœ˜ßi®dùQ{qB ›ù™ô¬´–}ª‚ØÌˆûÖ©b P½L¸êš‘¤Z}óMÌÀ 0*ˆÑX‡s(ÛK„†×goFìBÜÃ-ÝvéÖËŒcET±­ÃM˜§7ĵ‰•˜ûËÏŸ¼Àr‡L:±¯ ¬M{Zï0ÙÙš ÷J˜Ã+#t¡â0©|’°^ @WX+c+™j]˜”FÓº¦‡ˆòäPK]\D.ø™ÑA¢Ãʽ¥Kz5ÙfXðKEæT›Jó˜W$fG5 éâ ,ÚEM±PcXcs~LËÂç!ø½à-Ô üô‰Ôeü¦ÿ|øÂ5!J´n"¾Õõ‰^a+åíÞ0 ÜVü@^Xlê=HÒ7T`N¢…rçù@.¢‡ã9*lùí4œa:r54zMU×Ô¸éï /vÀXáÇϼÇÕ?™Eõ@(­0a*š†"÷FQ{2¡JeÊÍôÜF¾ò”ÌJš/çÀ³  tj!ñŸÌÈšµ`ÐɈÕj …±ìj¢h¸h;ÍÁ6æ½åªï™Jì“ø… ƒƒï™`r#DÊm¤%ùÌîKItŸˆ¡²ûöJT òJ\ªXJ¼‹‘°¤eUA¥Ì³y‚Y‹_s•‰£†Íž{§ ~ñ€U÷¸Ò\Ïßáþ¾ÔÑÜ2•.˜D„z+( 0Âe‰Qê ƒ\zoËù ‘JƒPÈ3ŸÓŸïÒTÏÍOª€B¬Bب1{…@Åë*Å·‚`¶+ZúÅê m>ùƒƒ\Mb×H9œÊûËðýæ6ÛƒyWœ/¤W«ˆ£GP"¥Ëƒ'h‘ÄãA€É3CZ–ªUÍCš_Œ3-L Õë oPîp`òüfB03]e‰¬æ³ðÍð|ìÈ5Cl¹©fϘÅ0`÷•@1K?¯ÔJ J”ÁššK‹âèË}ý¬a¥ŽY”¶K½j Vm„|sw—`ms5…ß–>Å8zFì®àírÚE2÷©mª÷„ÀãöM`^™¶0˜ÂËOx…)šƒPÙó(ˆíJޝø¨¹Bþ–pøKJ ®ß¿ù¯hâ’ÁFð‘Fî!»n³ÌÉGåÁ|̪Ì[bT½£2š‰†Y<æ4%Kði‚,bK3¾<º›ç._1ð‚¬Äb»=btÈŠi ï˜ ½\̦ðÏÿÄ&!1AQaq‘¡ðÁѱáñÿÚ? Ä)}:F᪗6^ÕžØÈ©õÚþ`1ªæn.{Ê7º³Í®#a>ÝÍÕï_‰¹»Žƒætÿp`iŠŒ?‹‡L\(©š+´¤¯™V°bXbpÝìé0ãx<˳Yižµé¬Ì ã0² GÿPÙwœ¨¹×0.…õˆ-3ÄÔþ.,ÑÑæT›ï¿òoGQBÓ•åìt„Pm–£|bRªéí¯^ñg[ü3SŸÃ?!¥N]î¼EM«Çc´ ·|×7û‚QéÎheæ1c2¹ÁÅ˲lšˆ]vƒVG¸žÌ醆½?즃 _˜ŽB”ýë$cïYQº¸ÔÝ ÿqkl1Uª—‡¤*{—þÃyŠøWXûæ ¡¸jh—b2¾¯Ç1.Ý~‘´¢úÅí~ÿîe |§ç¤U«Å~ñâ4 |c߉N@írbpÂ/Þ5í©ŸX@Ûû”Zg-Lf§yÉâ7*Ìæ¾-q2{ExœÀ¢¥Ã3s¤º{n;Ð5Yíæ8‡9ÍLŸ̢·ï¬TZ—æSÏ·õý¨s€ø„e׿ê@\NÉ”+öøoœ|BúÏš©1ë+‰ÖeŽ%ÆžMøôà…[D£Kºöù„ºË¶ûûsÖ \¿l³'MQ¼AA—û‚êãŒYÒ•ø”;Û] æ—1£…v:åÿ’‚¹cŸ¿0q¹»¯¾»˜]»G X^xŒ{Þqá]ÿr‹<\«*ëu÷qAE¾ýâb×+ÈD¥7¨‘ÜuEF®XÃUÁ=±årÔZ¸gÖüË#C¬Ç +“·v /q•–ê±W2ÕGÁ- ƒÄ«…aní®XÀ¨­(hë䊥PäŠYa.¢´:ÂQwŽÑ¹»/ußñ)Óu«>};‘Ä”¬ô©j6f½!µçóæ ,ÉýÊã´-tÝ|L ãã÷4uMú@Ÿ˜„š^° |LШ]a…²ÏHDÚ⸲½AH(2ýë ‚å¬k/¼KüE×ïîýþ „4jÛ×7¯1î¶ÖR5U¾Ò™áÛMÂwUÎ?òL Î%ÚÝÀ8N9‹7Zãá¯^eDfYlf$0•òê3ª¡Õ|ÝJç=QâÂø|õï/ 9Þ}`:jV“¸¢·}s*mšóQ 4ïÍÄþÈ* n[,%o48Ö Ÿg\AK±fR¢®ŸyÞ§-1c¤28¤¾¡v<õ‚d2®òÜ¿¢11RºÌFÜn9Ì:¯Q+›%ÜÍS®Ó/N|ÜL­ó,Ϙ¢,¸ÔÔÊÍT@_wÒT™£2ŒÄKo âADÀ\`Ê<ê#F%5\\!­í¿S.âºt&¼Gÿ^b*i_÷´j¯+ íš‚“y„FÆ|ÅKWÛgÖf:LˈHR”|#/´0 ‹ßäý&Fe=Î/§xÔm.M{_¼VJËr`Ϭ»hôMñ,Æä„Å¢Á¥¥PVWâ$Ô„zÒôu”ô¹h2õŒyÜ2¥â OFFËù‡ùŒÑkÇŽÓ·9äÈĽKs…ãï¼D84‡´hpºóü%R¢1Õ~e1rvÄ8Ü{¾#SÔñà ¹Öw¨°øXHî”zÚù‚ßL±‘† ËaWh)µâkâZ‘o¸=áƒÑ,Ë%˜cÒ2¥5íþŽîÍY¢nÈVÜ¥¨Ë8žà¨Õ „¥_©Ê-âZ«øÚØRÿìIQ:÷óÿ"¥j¸Þ>âŽ!cRŽ …®|E´Áƒ±2±pNŒ;j£¸Y VÙ…R8Ϥª­Ô¬‰ÿÄ%!1AQaq‘¡±ÁðÑáñÿÚ?Þ·PE† 2 + ´ÔPÀÐ/b~8w€¨¡1“€à"q"ôA’¦ÀŒ"œÏ*(…„Ãøþºá”P€$Œc¢à]g÷ja¡‰\Œ…a(‰ÐQ:«_@„ÞÉeê?¢DF8F‹’ X5…ÕöÔÆÅ$þn[UÝN†KŒJ{MÌ(ª©BÎ.¡ƒQ„TÅ&òó[|×B˜ k^ rù°ƒXRÀIÇ¥Y¤š{Øå ã·ˆK†öŸï.=8ñÌTTàB]%LŠt¸Gç ®I4Mh¼ {—½œ3ô;/ÉbÈðEÐ;dw^CeQ’3Òá£Vq©Ö©¢@`—ÈV;ᚃ¢RXpTÝkÅg¦€cqJˆðkßX!Z%¯N . ¨B¡ô¢½cáW.!Y€‚$6ÉýH6¸í«žÊ²,‡x„‹ÕuAŠG"¤ n ¨3W‰ Þt‚/YE7„‹âKGÖFþF3s£Ìœùù×;¢Î¿÷\Ûãï\_FÑÛÙ@èˆ[8‰ƒtÆ*QâE V"¸²io 2T;{аØ(ô¤ïB˜z!FÐ ¶§þ(‰¤[ }H¸ N [Z¬.L[UP2™Ùßê!Û»Xb˜ƒ$o fÌ"œìHþ§YÈ«1«},AãÔVv²=øs€G!@@Ý ½?‚¢dX žÃÒõÑ ð}§ŠŒ’Œ‘~Çn´ãñ]„9ÁÑ]–ô¿õÉ ±÷ˆ‡¯N=áGÇ¿?弊w„œS~åDPda&21&<+*"„ Bß@V#×%ðW:ŽÓ4æ‚:!%pu%Í+Ë@˜E BǰÐ{‚\ TÙÄ¥'ä$M‰•· D_®Í]‰Ðd4³€ ´2ªR©:beP‚°Vwìç/ —À0€Ú“OÐ!‘@á¿£}-jÛL:*zÌ"ì`‘H,(«ek×6¨­K®Uif }q§Á:ïxž¬ŠÏöñ„(•{'®.ô„C/h%tQ¬/ˆE04¸·¢ð;@zdâùª9ÔØp`´&Ö8 ó x=!]½ ŽT!•1¡Z¨éâTŠ‚atIZAu‹ý¨â$ƒ²ôw^ O¸Á{Aò|áÇf$ Ýît—ó8j ŒF”V„ÿ—1‚z;Í%œ4Ýt®h|‚ °™P²Äò%J’K(IÙ•9@›(¨a Gˆ5„Ô‹­ì¤bqë ÷è|ÿ?ŽM)@â©ï÷Ì$v~9LH >xò+¨´EPb.*[5x»+¨4)‡Ggº©!‹)E¡H8RI©FjxJeâ$CÔã¼éÒQ0ƒ^È@’h¼„"ß‹cckF°x§ìaQZuS³h6§<Ò0– ¢Î·x.š_ ×]òW:ÌÑVŸ/³|jbNˆˆêp….¤}Âk¶¼êst¼Ú&¬±N’”ùRÄO¸Ø°8!ˆ {é^Ëà~g*˜X1SL):rö{ ·zwwËÍZv*GóÄ¡ŒìgþœFÈu| ÿñáXq¯Æ„\€”C¦\Ò;2Îo—ì 4Êõq x3F<¡Ó»ÍB‚,H1fBÃð)ÀyÞ¸l Ô•8fŒ¡´:K½þ8£Ý aԉܘ¼Ñ>Uˆ!‚ƒ®¡7œÈRñÆ÷èaI6/cÔ‘ëƒö¿‰Ýz¼ý%JªD% AD ´")…`;zD§ljo¤\XÛ:‹Ç«ˆMÞÑârÓ-ÒD:%ìD‚OJ!oÎWD^»þ¹E( ÖõÅE4{<¦ Þé@±FØJvèe"+¢?-lœ+à·ðÕE»³ Äìx•¡É£V˜Ëѧ}ðFãòù8e&‚DJ`bhó/1QøzCÇwT”ÀŒ­À€jPXÙßUÈgšûêÈUf4ÔÓSƒË`Qö-›Å+á©Ç4£á¨€µ\º÷Énƒ ¦^ã$BK©‡+cMíI`rèâ¯`Ä*Ü€!†2qÌ»ÿcÉM‚‹vÿéɶý¼Döº'd$²…"¾“´ªåΆˆ4÷€Ë¡å#°€ˆVãK*V¤ð*c\Fi|L°{9Opºô”’ÍèæòìAY)Að²C:ã…XfÀ&VàmÓW­teýíÈ}a®ˆ %¡Õ8úcÕ)«ar§¼f ÀùšæÒ‰úØÚÄQQ rºã±7±@Z„Kú¹!8ˆ[J…2D¡¸ìUèàùä.'±hÕÐܼ­6ǰé(36W£w¯cÔóOëŒ"Êö×óÈøÕëÏþpÏçå™AˆkBk‡qž"€•}ÞdãE@@!)ÉGÎsð—³(+¾%˜„…@ E`&c';÷h è€$!¯¡(0„êeúJšü$FÙ,nOArK‚i ‚‚/I› ù&ŒªÜíqhƒ§þøe+Ô<-+ÝïÞ21:iDU(¾K.-Â\KRxåCOþ|â–ôÊ KQGbU¤m"/É$„*¾Õ2Çáa*ŒUêL“®¸ñÁÎÉ¿8Ñ\‚ßó˜ì}œŒbIB(‹Z8HpLÂ! J?@ɇ³a@Ž#*qN2T¡.ó)e: Ã0]9 êÅõ߃ЯF2F_† æÏZ Âá–â\‰ÖD3:Ârn/ ¥±¶bB¼Êf÷>½ÚÈ*F—E'$׫t8½’(p~"‹[f4`¯GÎ7‘ÚEs UPd÷F râ£)uÂïuãrhœÅ‡Ëú>F":Å1Ä‘´,´D&áMfÔŠ‰½ùÌ´ €8´Hå[üq¡T€ªDª5pÍWeüˆµÐW7Ä«ÀJ¬ª`SéÂrÀ PSA«†cÁ_ë¾"Å‚©j,ÙÁAÒN§%+šIeCµë–ËÓ`@kZìG†gv÷Š»¯W‡Ð& ìê~x`ð]!D>¼3 è"=¶5ù/@ì/P †ü¼)Ѧ"]¿_ãxÂiÚиP•há:_äЩ@æ+õ©"£4ÞBäQŸn(:uÄŠ¢°Á({׈^"þ^tK/œÙ.I¸eêqТ3z+â¤÷§Ž ¹:P5A<£ŒxdkZJ©1G7?¾bŠnÁ %žÎS¦8˜…G@@0FpKœ”į:BÊH—ˆùÇÊ4«‚Pn0 ƒc^•“ª @Ij…;?Þ)™àG¥8ÿ]ñêv6¥Jе՗Œý‚ƒ1@0Fpñ!s$(5£.p ñj”CƒÞjq¾Â·ÃNÁÑ4"wÇ ¡µ-KiƒŒ¬r…¹qIaKâ¢ê:9õÇ`F¦ðûò‚¶²Íh0wÆýå“¡Šn ¦ß›®¨ ˆóRWêë¢8ëÒ†xNð]˜`–i—ˆr˜uÜŽV–-©Õfõ¬9ªí‚*Eg³FV¤**…Ë”·¶B@¿P?þqÍ"DO¡p?‹À$4ˆ©D/jKôúÀZ í T®SàÉ-¬¢€•H‚;Níä¢Ð0Ý¡¬®qÇÙ*@·-»¥3áZ4ì?¾tdº˜Ü$¥í9,tõ'ú4ŸÏ ¥‘Û åtµ,ý¾quøQ¢ªˆKÒfñà—(3²‹(^qYZ´pÄBÉØNÓi#ó‚f7 4p*vð¡K±ùÑÈZɵ ðdŒÄu‹GvE'YŒnщõÃÛyØëÚǤ×ɼ"—ŽA&dlæŸxö8oµ0‹V’[j 3 ÑcY¤¶¨" ÊÒX‡N^«0Àdv´—sÿÄhΪ˜ÅÐGÉ–MD*X+{9G×n6 ýñE@¤;Oߨê¸2:&rà?¨d´·ÃÌã~Ízœ^ØþN3@Y€¡Ÿ¾¥C@QX룓Ø42Ñë¡Þ ‡c„%I)õ Êäd%~%]ƒá'ñ+.„µ:|â–(Y{bÞ߯Ò§òèŸýrQhGJ–î/Á¼ßCsMGl"ƒÜòDê¬ÿõÖ a €–Þ™˜‡‚—L=@3ƒKD \%q3GŽc’‰XKΧhItü¿Ä9@Vþ÷‚ )ó·/—å‘a1¶²õʇ­„ŠbŒc„´¬{ ˜ǘ‚Fßd…Cg\È´ˆ¸Gòr%x!,·Á©ÇÌ>RÒ½­_œ_‰ äû~×*mJY+ÑE™N p5î)Ÿ²GIД•«pPýçó}~ ¨3#¦üáÑϵšFÚàŽ|wà˜!)|A}Í4Dk°BŽ&eåtë¥"QRÅÌ‘F]˜¬„Vi"!Ê×p ªCœc›¨m1yO±ÄøßãþYQý9N Ïe)j¡¯Ë^¡ƒ¸2¤/äá=ipËol\¶ÖoyM2BÅè¯Áᆇû}¿×+¸¨ì‘MŠ=^+w$^ÂvÚÎ¥Ó\k…×€kÛÑ4Œ0Eh@Ööð»Â•DϺôˆÐq`‡J«ÓªÉÀ?1“ù¢_«ŒBKW(@Ž67•xSàH0[©§ÿ€´0hýó½Á´xЬ@ÔG¥1‘$HâÕ=T?íÈDW¤ùÿ“l¯ƒ›¥T€óÏÙÃÉ:W,Ì!ºu¼ºp¥Z®ÿÄ$zrˆŠØZü`˜ÈT6QZ¤-o¹síƒH.—áŸä?Òq ÁH9üŸþðØx1>÷·Éɳ0¥‚kp|¶áHÊeŒ„‰xS©Dw@ˆ¡W‹êp´:ê êö½ª¯ ²Ý¡lp’Epð—ôø ^Âe£ /$ô-ñŽˆs(èß´WGMO0èë”péý^K‚#‰0E T°_.vœ“\ä¨ÄHAþƒÂ›,wué>‹ÈéHA)8‘ ]NaY„(içgp¥%TöÜx*{ÀPB«.®}ò¸Üo ›ïœÒë~øoEí:P¦ýÑÆC­¤?zÿxyK*¿9Ô夵?vo'xÑu7xp]$HuˆQ\¢9ë¬4Ó>ˆoí’Òž*Ž?ÚJQR¯Èp JQCÚÇÞù}¢ð{ÿ~òËŸ®2¸®‰ð5syp”Sœ<`é×óõß|¿$ìTµ–„œ™pvª­Tejå113¿ÉÆø‘( .¹Þ±þµ  "خ }Êo¼m¿]JÒŒ^è±õÌäêˆÅçVâ*ÿ^BʼnMŸÇ ðE•W»¶Ãõx‹Š²rõŸï<l%ýÿùʬôhìA÷x.ä`®Á¬=Îÿ¢)gZ+çtOÃD_8Õ‘`Whj w` €DÔÇã¶öpS–T—ôÔK ø A3ê*ˆv˜ñ篬èÿÇûp EÎE=ÆPW3 <ÞZ&ž¥N˜1/Ð=9R ltŽi|äº £KPíÓ;5ƒDEî÷OÇÎYIOÿ÷qR™ë8Rmn®ð¥ 'ÁÐ#)c8vÂ]ºå§WCŽ ×Òß„‡~5 ²œYX:†w`õÓ÷‰ŸRnÿßÔ¹)êjúq?ÝÕ…áDZ‹ó€¦’`k¶ñ–DQÑ·uiù{Þ‘ýŒTßÂâ Ûÿ|ã¦`e öêôx RÇpòäÄL˜üp'-΄¶ƒÒ¯Dxâé´nõÇ?ÜÛæ83@B#"ýÉÁy01~`ÜGŒž˜?ãJ¨ÿÔ'f'\:%ñ $뇿à&ëÒS]šþÎV*GóÌšOèá&7\œq¾M¯\1 þ:øQÖ MÔŧ®XïY rÔk‰è%Mà/ÈtTLé‹Û?ÙÌý†ŸôædZÅ,ÿ—ûã§åàã©(­îœAØFå_Õ×ïïV•u!‚¦Óž*£ Y à=&²agrå=9ZŒ6;ÄŸ¢Î6ŒÂÃýâ½it/÷ÇZ>ð’°îyš«ñ8¤™¹øA‹ÏW‡A½Ø(–T÷ËÆ ôx¸!š8êVh C„ÒêHê&Ǥ<G¬«þx,åb¥ŸÏ?ÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/images/scope.jpg000066400000000000000000000577771510465702400255200ustar00rootroot00000000000000ÿØÿàJFIFHHÿá@ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:20:49 Ðè ¿ntÄÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ¿"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ôÝÔ›ª-Ô…ø®sBBÔÒÕjazW!zaz‰¤¦¤2bõIQ¨Ùø¦Wo”o¥rÚ¯)ÿnº+†È5Îë[Ÿ÷…Tw!›¾˜6Ž€ºÌ?ZÙó=ëð¥×Éq=a]/™ïQ%©qزdæ¹­¶ÛH}eoç[FNkN;-O»·ó4º îl¤Õ:ÌGzÌT«-M†kG6EN­\ÍÞ£4Œ€1œâ«ÿk]ùlÕ”¥fvÓÃ9$Û;hœƒV‡"¸%ÕîÆ?~Õ(Õï1ÿùÖNfë~§v½;5Àÿj]Ïwüéµ.ÿçâOΧÚ2¾£ýã½Í(5Ài]ùx“þú£í÷'¬òßF—´e,÷@zÒäzŠà>Û9ÿ–Ïÿ}Cu)ÿ–¯ÿ}Rö¯±_ÙÿÞ;òËýáùÒ¿ÞÀ‰Ÿ»±üiÉ<ƒ£°?Z=«özþc½ÀÇZC\$º…ÌDmÇÞ­½/X’tQ6 ÇZÖæ8±8eÖæùjnêŒHd3Vq™ÆAH’“Ц©´Þõ~Úí,->Ñ€ÓÉ÷sü"ºÚ1¹'ö}óVÙÈ>à3Lm>øˬŸ¥B|Eyž ‚_ÜÃòŸZ›1ܰÖ¿óë/ýóH¶W “%¥É‚!ªßð˜L?åªõÇj?á1˜^;R»ÃÚI´í²½¶c⪽¥î?ãÎãþý7øTâ넶е!ñ”Àg+¥…ÊÇM¾qk(Ç÷”ŠÏÔ<9wÀ›s’øVÒøÆf$¤ŽØ§ÿÂ_?üó_Êäº Cž°ð½íž¢nQ†Â»Y6‘ž?ÇšØ6³Ž±š³ÿ „£þY/åKÿ Œ¿óÉ*MÉôЦm߸aT¢ÒZ4جO$óŽç5³ÿ ‹÷…*pñ‹ùd´½îùºlž¼Ñö ×Ò¶?á2ÿ¦K@ñˆ?òÁ){Ý‚ç;u£Ü\0duÅFºä}ò~‚ºqâå?ò/ü%qÿϲþU6}•yÚÉœÐðüÿÞoû枺ÿ¤Àk}üXªF \jOøKG{uÿ¾håòÖ*1ˆ4?òñÿŽÓ¿áoùøÿÇkXø¾#ÖÿïšOøKa?òïÿ€Òä_Ê?¬Ôþc0x}¿ççÿ©äõ¹ÿÇj÷ü%Pùvþù£þ«b1öX¿ïG"þQýj¯ó‡†ÿéçÿ§ gþ^¿ñÚŸþ{SÏÙbÿ¾E'ü$¶‡þ]"?ðG³_Ê?­UþaƒÃÇÙÿ¾j9¼<ðBÒ‹’ÛFpV¬ÙÿϤ_÷À¦M¯ÛOBË1‘Ú“¦­ðŽ8ª¼Êò9«·çð­ 1¾D¬­ALS'8ÜVŽš~Qô¬è«#¯%&š:›w8Õ ÕnÜ ¸ŠÐó™Í<¾õbW/o ÏðÖdW£mÖ0Ÿ¨ýk±œãIª7ÐÅ:¨–B€ð*á5  0äTØ ¦µ¶Ã¥°'¾ß`==…5ííOK²9Èã§9þtºŠ­d•Ì£Šç¡7ªþTÔ.V:¦– §Ú‘܃Q3[•ÛçGõÁ®\ê3³ùS£7û?•R…¶'˜êÒXcbVhÆzç52Ý÷æ™3í\iÔfÿgò¦FoEü¨åc¹Ú}®ßþ{'çGÚ`ÿžÉù×ý£/¢þThËè´¹\í~ÓüöOδAÿ=“þú®+ûF_E£ûFOE¥ÊG_+Æì Ï=Ïÿ^•\!ùf‡§9jä?´$þêÖÆ›þ•n²0ÁÉRi¡§s¡VR2¬ö4¹ç½E¬1K2y.¹‘_píÚ ¢9¤«HÀ£'Ø®ñ9À$A§ÄL÷BÆXÊ‹û‚dft( Œ)î}h@B"´ÀÌiŸp)|«UÁ§>‚˜ñFO>™¡£ °‡4Ä=bµ95éIåZÏ$ü…1"^v°?JCyå×èH  ~Ëlï) ã8ÅH¶¶ã…Eì*³Gã%F3LÈ_“òúb—~ÍtG5­¹Œ¿–¥‡z®ÓHœrz;S|ç=qÎJC¨¿ï}ÔÖŽœzV5óæñ†zm ­;0Eb·geO†+Èê-Û¥[ Y¶òt«a¨g9ÉHõ¡hùÓ—Ùd;VƒfŇ£Ÿä+±ìs¢bj7î3T¯-¥Ãø3 xãß›Îq€„¡&,OQ¾A×üûT½)+Í`?0úUøOÍY¶çŸÂ®ÂÜÔÃcjïß5"n*µQ¸« Ô̽K§6/G¸5 ‡X·úr{äWKØÀÚ4ÂiIær@Î+2Æšáï,çûlÁaüíÑO­z4vÈK®~´ò±÷Óó¡NÂq¹æ`»=-¥ÿ¾ (Ó/OKYïƒ^šLCþZ'çH^ÿ-ó§í_a{4y¨Ò/¿çÒ_ûæœ4kóÒÒ_ʽÍ„ËE¤óáý`£ÛK°½š<ëûQ?òç'å@Ð5#ÿ.úW¢˜?ç§èi¦êÜèhöÒìÍ}ÿî¦åÕ¿)G†µ?ù÷ýEwæîßûÇò¦›È=[ò©ö²³G<7©çýHÿ¾«­µ¶’ÖÊŸï"8õ®Ø=ò¤7ÐAG#è*e6÷)E#Q´[©>bAªÖöIlK)$ŸZ¿4äfQØT.p§éNîÖ -ÈT²F1ÏáLi$9ãôÿëÓä~Rx$¨­ôB]¬áYº{~4õÛÄäî µºô¦I-ÈB ’*o4ya°NqÀªWw 6Ü€=OA@„»uïJ‘¼LJ9ê* [Ȥ„$xƬ±Ž9)Ï3òíÏ¡j•SrăYr_ª3Ž›@ýjä3‰,Ùãô¤¸Õ?†Lý5WT}°Dƒ»ùúêýÖdàœóùÓuW̯ 'üþU2ØÚ‡Æ‚üªÜmƒT®-.í å‰âKŒùe¸Ü\{sIvØ*½€§èçï³­´Ðõ9âYE«¤MÑå!ëV†‡t8Ûè&ZàVWQ…fØÓ–i£~|ÕróK«ÈÄ/j[XLw‘1=Ò«ÜÝ rAÈ,íRÚ_+bÁÈn}@­v"êæÛM0š‡íq7>`üé¦êùè¿fY14ÒjyüõZi½ƒþz IfT‡˜ ì Å5n"w —›Ð6sUç6ÓL%i#©t üþ•Üé«Ð7çQ·Šµ»~tr°æGDH¨ä8¾•Ο“Òßõ¨¥ñ,®…V3êiò1s#xǾ0:Vtš\fq&[ƒ½³YðÞ€‰ùTg^½=“òªåbº:E Uyí’\îèkëwçºÂ£:¾ Œ~ŽV+£n;á åç'¹§¤'Ý=1XQê7ŒØ–FÛíRý±»Ë(¢Ìw5^Æ,¤š°‘(M˜ùzb°üòå»þtyÄËWüM+çEkfÓJ[Ä]ÜáT óZ/¤øv`óªj°XúÅ×ûưôÝvM*Âu·„ý®àmûAnQ=v'Ö² in77SÏ4œYTÚ¾¬Õ¼Ô®õQ&»”»ÀèzQÜ(,}i¶‘âvoAPÈå¥?Z´ˆ›NNÀ«°[BmZâo1”6ݱ÷$ƒO³³·šÕ™îf<ªç Ï9«cOe,–Óº9È NÝÀ¿J‰Ml ,5oá>õ‘¸©È$V¾®ÌûÖ)5ж!îZMBhøbµVãÔá“ò‡瑟­c“L&†“ ̬FÜ}G"•¢–NEA\âÊñ¶QŠŸcW Õ^6ýê =ÁÁ¨qf¿öU܋̄ƒèj±ðÓž­]´Ö­Ü&(þÅlÇz¥G˜ªÊzj’*Éœ¡Ðï`?/+þÉþ”Öµ—Lj¬\Ú»dky~ëãØÓ¥Óã•~xÃZ\ï¨r£ÏŸOcÉ"˜tÒz‘]œÚ br¾ÍÍP“J¸‡%£Êú¯4)‡)Ífz°£û0zÖÙƒE'•O™”ÅþÌ_Z_ìÄõ5³å<Ÿj\Ì9L_ìÔ/ötcµly"“Ê£˜,d ÃKö%þíj˜€äÕYîa„`œŸAEØY>ĿݨeXbô&™q|òpÑíTÙóUfKd(?tQÏ4ÂÔÜóëNÀ+Ü,cé#¹vöÅ»#ïz ¥Ì×fük{N‰~Íy9\¬1¾§Š{ rŒ—ÒÌæGl³“ïKåæÃxVpr¯éöÒµÊË"íAÎ[ŒÓ°ØÜ¥»žçŒÓb0‘[kŠ–%Gˆ+>? q·Û÷dFƤcdž|eÖ7íÊÓ#Ô R3¹?Ýb¸­ˆ¼3«Ï§-ìVŒð6H9=qYsYK>l,¬>e"•¢Çvm_F%ùÎMfO¦0ɉ³ìkVèáÎ9ëQ ˆûË‘ýåæªíŽvX¤ˆáÔ¯Ö¡&º¢±LŸÂÃÐÕ 7ÉŒ”?˜¦§Ü\¦¦“Vî,. Îä$z¯5LÕ)nìg©mï®-OîeeÝêáQ_Éò³ògv=ê,Ð3 ´ñ–ß¾ˆ‚F F¡­Û-u$PmçÙå3ƒ¡®šMăÍK‚`›=Z Z9@§=ñÁ~&‚õr ÿu¸5åúÕå·g˜½6¿5¯kâH[ !{wõûÉþ"²tŠS;Éôè¥Ï›Ï÷‡ó¬ùt>ð¿àÔË e·™ÂÈ%AÜ jÁ«ÛK4{÷“ü+;4]Îzk àûñ6?¼9OjíãHg†Tn†³uOìëD?kP¯Ž~ñ¤›Ë”ÇZ©sw ¸;˜gÒªjZ¡3:@¥ŒUºEA²KD\.78³äe‚I,çŒg8@zóþðû}næº)¤½Uˆ­‹okVã ¨Íö›wó¨öl|ÇMvqϽWêw¿ÞOê*{Ï»PáXѽEPÆýï›ÿn>¿ˆ©WÇ‘ÙëùSXÄnÿixoþ½7ïØßŽëà @YGIAÇЃUn4¸'ù¶íoï-Mg?>ï¨ÁüjPØ¥°ÎzóH¸ ]0ãØ`ÖS£FÅ]J°ìEwûÂ£šÆ ¥Ã¢·×­RŸqrœ9¦“]ç‡HÉ·|²Õ‹yhöê»ã‘_£dqø´Ó!¦ˆçhBÇä–É_Ÿ=ùçñªù¤¤¦"h®$…ÃE##z©ÅlÚøžê0åVáGBxaøŠÀ©–ÚVµ{–Œœó“I¤3¸ÓüGc"mûCÇ&r<Î1íšQ¸7!“~qóg=«ƒÝ]ŽÐ?àgúT8ÛQ©t#¼?é TÙaž:Õ‹Ãû÷ÍTbú.:S¬yé–Ç^ÔÂy=ÏzV8Sž;Óßô¦!¤ó×?ÒšO­)4ÓïùPž• ÿsŽÕ#5Bç4 +æškpi3VH¹£zZ¤ÄÖ~{èä«  _‚=[Ø¿Tòú2zó¥¬ÎuCXßÇK˜·±¬>Ø»ÏZ¸¼ß\éóÕ¹p5ÛÍõuû|­vus,ÉÊý/)GIË¢D™:Ÿ8ôðR*– ¢Jø(™t³~§ãôgtÍ[)tåWYÜåÛÙ³*.bi²×³ÒØZœýy\}ú½¾f›:Có8_©á»vþ~©fYó?WT±`D¾ R\kê>_F^u®iiWSªãÒÆjåU“(Âõ=4ØËåîÎÇ«G·ÎÓ˜¼­2=Þ3—ªå´Š—;Yù«ÎªP($@±¤»õôíÉ6^Ʀ¬ÊY¡-mKxªª;—±¿&/¥K=4;x4¦-ꟷÈÔòè±Rӳ垿2èdЉ!bX”Íž}>—ãôct‡!B—_žê˜¹ôz ätÅÌéVcpú¨/uðè³k1Ó+÷ùëgŠ´!Ê=žP=©)ä…ˆölÍlóéô?/§šëÉQeE+ŠmMdó¶G©ÐøÅóý^w:ø¯ÌêHÑ>ß53¬áÐ 8µ ”à ¢B–WÒAåMsý2¥­e$u˜0¤­Šúš¸^¯î¾[]¼l²2M.Ò‚uœzL¥” àQDÊ0€@¢Ô ¾˜ÄÒÌ—¥ÃÐ,¹- Ë™·âÇ?£—ßçÓß*vkž».©¾[𨙗4µ5¹îÌ¡ A”V A@¥•7U\‰^’,ƒÊqØÇ Ïê'Óòìk•Bî5c:Øôs¥¬nñég:Z¬e„µŠòV¥R…ÒÑH±TŠ„‚UÒ¿5’ÈíFØŒw×מž›^^ÖóNY÷ùùΜÁmæÞŵ{5’É]*R–² å‚bWÑâV"RJÍjîkï.¸*•’¦t=±ÌuÂÀNn„t|zïòÕìÛÔLšÌÚ±øòD°E@³+xÇÞ+ص sµ êý˜:ÎÜtÜõê¹ê­k\î󛸔p ­¬Ý¼Þ—–¶ùëS–¬çYÖei›ª¥ˆX»1zc¯:º‰S.fñ½ì"¬ÐÍ]}{ͽœéff§èÊ¥xÅ隈V¹ytõÒã]š¼ë¥Ì2tâ»g›ïÎÊéH@$V RìðÉlFÆoK‹µ›jUuQ–dtÓ%ÊÖp:b†ò‚Oz^‹žº^zé±w8ôùç|q}¹¢’)‹´d:PÞDÇ¥€†0û_.´"WÐ1vhâœÿLáôÂ.V@¢[¹t\÷w7™é” EÒÀ€H¦~ó‘^&R#îœ{S ‰_+ ÆJ³žéϘíŒÝf"̵lïøôæ5Š—‚*ÔU=áuâñ$äK÷ž=³Ææ’"ÇçG)£‹2º0ºg—Þr5¬"É^ûŽùeb¨@…"õR$¯¼«SÑâExñ'Þ¼þŒät¶±ijg:«ˆ·›©ÉõçËvÂlî¸tæ·EZ"lP… Ô@jç·qE °—ÿÄ-! "1#24$05AB3ÿÚÙ³fÍ›9‰Ëã+ùfü_>Tò‹æÔÅ1X)‰‘–„ö/ôèצ͛6lÙ³c‘³äM—|Aþ÷I¿¹Èr'ÿ+ÈSÉõÔ½Þbë ­X‡Ö®g½^{Õç¼äñ{¶Adáy£zX¾©,…¿]›9G!È„'hñ®%"&l9åþŸÈ>O’UÏÌÔHùFLõv¤q¹š™©œfjgœ,8Xp°á`¹'|¾ì)iSoÇ/G2´îšÄƨì`>ñ0Xðp¤½·öÌ6{NíXg´ãׯg öŠÏfƒ=–±ôX3Ù">Š™ì±=žöˆžØ{{< gb;6»N[ãù|áþ)Ìkû4ÊÇ&äÉu ÆåÔÛ=ÎCÉŸ6Í{„Å}‡~ÓÈ´òm<«O.ÑæÚr˜³­ifÚy×Í·^UÅFêߺÜ{¥Çº[¯u¸÷kPº½¬÷KEÕ-Üz»œ²×q _Ä_ÆÉL¦\±ÆYÙÛž:ñ*˜ÃÊ©MG•QçTyÕmG›QæÔ<ÊZWRBê“RÚÙ"»§+]‹’½1Ü‘“;éÔ£àåm3*_½ŠTþ6JF$·W¤ÖÎ¥ºªòl<«*Ã˰ò¬<».Ã˰ò¦ys<¹ÊŸ*ª[il;2•𕸖:ŠÒ­nQD`8Dšø•Œr“­Û"“ÒÝØò)—‘²R0$oÒGWþ³cfÍ›6lÙ³fʾm­N1JÚäîrVË9Úå6ç ½ÊOq›K”ˆÌçœNp+ÔíåÊTH¦bfÉ3_¸ýÕ¿¨ÆlÙ³fÍ›6lÆû²#gU)ºh»¹˜“ŸiQ\á㵉So ¤Þ5hìEÆ­¥D±àÔcTŠl!aÌ“0_ï¿FuOé1±¿M›6lÙ¿L?í-™¹N™Q›)Ù›®ç2aT»–]ÅBþn»9Ùt+ŠY¬Æ±]f_n1”ÿb·óS239–X¢`ä©fKÒGQùÃcÓ³~¸܉”ª)³7ûÍŽ›„¹9ÌK‹”}Éšû»råÊe¯XÕ¿˜2¦FG3„ÙÓ¡(æËÑ™ßÔcÿWMþî‘›;'d,Ïqòßa¨òÝsPÔ£ÿ¥>¹Ê?úV8®|–SÖ-˜2¶&r1þ2%ù—ýf?õtżï“7*UY‰—+-v(ºr¥r…«±Ôiî–ÕÙT¿¶6×Û²q3ž««óó&l‘‹%ùc2éFô×¢:_÷¶Œ›jŒ©rp?‚vñTä*ñç“Þ‘£-*T¦£gÌUN6Ê1:„¾êˆ2 LÙ#zrüŒ±nÓG‰Äâp;lìLñì:UyÆ^/zÌ|^Ì¢Ú¤ŸpNÊËr¥5­S­Ë"s ö*Ù§¬éÿ‘YA•ãÝ5Ø´‘&ohÖÈÐ{V9í¸ç·P ”x•5gbj'm´p8h²µ¦þDô§wÝ lz,·æùÚ'wÍv}Ù7J6ÁËYOy]Î Ù¸,>»—‚¥ú“¨Í¯Ô™ÅŒ“+{¤ŽË™åL–l“óf,™Éw¦;¦wfw$w$9³‘±¿FÎ?nW8˜NÎù/šÆVUËeÔÎF©Ù\§bÚx½W¾£v5ù·í½zx—³áÉ(ÍCÿ–þŒ«¥T¥—|#Oß›6o×fÍ’—Ä$¸ÉÄåwkõ"¡dR*£Ë¨ó++Tôœ\Ω‘Ôò1×+­ŸÝÿxòí]fœò­•%9Š.œäãæ&<Ä4ó dÆDú½k®ÇO¬Z>¯y.³húÍãëêÙ#ê™'¸ä?$ó2ò/’ýÃŒÎÔŽËtm “)AA@÷ÙKôÇûh·ÒÒéM±tö¡h]-–v–NÖǃµ³àílø;[>ÒÙýNÚÏêvöSèÚýGÓÙÔ»ÒÙ_ÊaÂß|”<~qEªm¦Îæß“¹·ä]U¿'woÉÝÚòwv¼Ý¯'ygÉÞYòwÖ|õŸ'gÉkª·u套Ù_#ÜŒ#ÙÆÇ¼¾1èXüâý 'D’I$’I$’f3™ŒÆc1˜Ìf(¸èr«õmgÄ’I>êNJÐÉÂI$’I$’IÓoÒÊ•¡ïδ¹/z[KCÜ\áR‘Rä„ù"‚0‚Aµ÷"ûû0zЏÜ\áUP*¤u*y>­+ãDê±ëqGâ=+ÅÎ>E|¿]*ÿ!Ôp=¥<è|n.p©IM0>vúOÈêxCÙ{”ó…N jÎrΧà{/nJyÁ´&´,V†t¿‹:ŽGì`†C!”¯\2*cB'èNŽŸð:ŽqˆD"=ÅŸÅUQQ™™™™“Šö-_YP’\—]N¯»FW°Ù>Öq³B¤ºæ¶ñNþŒLU‰$œ3ŒÆc1œÌÌìÎÌÌÌÉd³× tߪ•?o«=¥D“†S)”ÊAAA©ë“331ëL’wZX‡³I›iÔ7°ôe8ÕI™¡9ÒÙ;Òffc2Ք˰ª3y$n}Üv$Íí^Ó¤ï'Làé"=ÒÓê¶ tû_ÿÄ& 1!0Q@A"B2ÿÚ?õ®¯øße–Ye–Ye—ø1&¼ëþvJOô\‹‘”Œ¤e#)K锾™Kérú[ú[2—Ò2½w±pu¿­[;r;l})©|;2øveðìÏáٟó?‡bÄÏãÌŸJPVΞcðddËfL¶[2fLÉ™³6fÌŒÌÌŒŒÌÎá™™Ü;ˆî#4wÜGq£®ÿ¡ÓÝ%ei~,ó¤HŘ£‘Š1F(¤R1F(Á3¶ŽÚ;hí£¶ŽÚ;hÁ馅e[Ÿ:â``(Ñäòy<”Ê(£ÉçmiEzýî{"R(¤QHÅŒQEVÕ/>¿õº[#Ϲ¦5­²´|çt¶GŸcãH:‘mpe2Ë2fL²ËfL²OÁw=‘çØøÒ1²Q¡A˃´ßïF'£шŸ9ÜבçÙ.4WúþÈyT(׫©ÁwK‘çÙ.4„¨”¬ãàYhýBžÅϲ|iÙ(¤¶6'c,NǯP‡©{'Ɖ7ÀÓ\ì|‰QC^JÓȉòCð¬´Z-j´Œ¨”¯gƒ[$CSfLÊFLÉ–ËeîOÝ#¦¬HÅ£ºŠ1EŠEzÐôCó«Ö¬Š¥àZåº;\îH¡úoÑçe3’Bñd²T.8‘«LJ(­0fÁ´vÑ„L"bŒQH¤x<‹,nöÑ‚cÓ©ýÕ&TŒº‹Áh¡£ƒ#334Y‘‘fE–Ye–^ê²·R1Gm u%¥™—¶ö¨•±É¶‘z従1NÙŽ–̋ڢ%¹h¼-¨ìÛÉÁo[þ‰ÞúGÇ +KlQ¯EV´8£¶ŽÙÛw)'èpLq0Rô¯ÂºþúLíüõ/ıLM=—í_‰Z)³+÷¯cõVŠBŸÒïÛ~ŸÿÄA1!2‘ "3AQaq0’#BRbr¡á@±Ñ4Cc‚ÁPSs¢²ÂÿÚ?îÞ˜x´WH—þDz*.WWL~¡d6+ V¬-^È^ÊörS,C%è¶‹jVÕËhTÁðB7ëTZ[7eYAœB¤¡þ ¤¤U+¬˜·rÂV’’rÂrXNK ²X’Àì–d°;%Ù,Élßå[7ùVÉþU{Þ¢¡¢Ù• WÛ=`§ÿ"±ýVÐæ¡ÛÖÝÞe·vcô[wy‚Æ3W=¿E¾+hÜ‚º™¹µn_u´nKhܖѹ,mÉb WR…µjÚ1cbÄÌÔèó^Ç›ì½7ÙJÍöWµžû)@Ä"†#Æ#ª£4Y|P;®Þ„ï Ôjû¬Kı,Kı¨ÚX–45–5¹Ý•'•{~U*þ'•_lI_ÄñaSvJnɼ8µÞóJp܆‹º×}y¡ê¤¶EY p Û^Ú"Þ·ä¦rS9)œ–#’Ú9m¢* Â6„.ÏI½kšd8§iRøhÛ¯X–%‰bX¾ŠE555:„§PCX:<Fôuš „”å58+ŒV!šBx"*`%?æ¨hRêïó µÈšÕ|OT˜Qtb‰[Öõ3’Gò[ò_eö@ÊȲ,îCKÃB“ü¢ù‚’ 2QŒ$¦|¨tVdµ]­ÁGu¨ ØÁ@>à'B § 7zÀ$©Lcr7¦´FøþZ½;ʘUë!âµ báÍCÿAx(´ÀÔQ_Qi‚½Ru‚TDñÐ¥ù{Ê™IEŠ.)¤¹«ƒ#ÉnÉ>Ýþê \ªt[®ŠÂ#ðŠŽ¨&3pŠ AnÓ£ù´)~SÞPõ[•™(Â~¨&;Õômð *Ëb@æ¯FpFÅD‰I_8(˜—FAjðTC®›zèR|§¼¡ëýªƒ¯="µ.ðOëX‚!Á•ÈÛ©É¥Ô€ÂÊ-ó ŒrîÝÓ¹’ÀrX’£6ÕÅ9nÍBåÉBU\nà¡}Ëõ ×A¡Ñ<ŽM['ùkÞ¶-[ä¶,Él›’Ù7%¹,#%„d¤¥\c â¿MÚ5AB15¡Ð¹”åtÊš… ØÞ}›cœ"¢}%Ë÷ŠO5l?ªåº¨e¿%5‰b+Íb9¬EOIÆ&ýÊ-Œ¯4xÂÙóDºî\Q‚%Ù(Â7ArOô¯K잇Ûtßò…FßD¢ìèØ_7s:PÒyUëP‚™ÓE¶XOOgF!üКùDFä˜X‚Ä0±…‰MLä·ä™é^’ÎÒžFŠßörí)Þ]ÀnšØòÛA¦0DþÑf*-F7(bè¤V°, Y‘E½…¥užªêólÃz©&­ÊabXÖЭ¡[G,NÍo®Kõ[–%nA¨ÒRÒšGî59ËšíD\w,0l]¶óÁ:“³$^ÐEÚëu^áæ¿EŒæ®‚½j»4Nþ!D)))))Wz»CVkXÞ᡾«ÑƒÊº*MÉ;F-0Zíæ.+—|W+àB÷U׫ÛzÔvkY°îoZªó_¼¦Ña°©Ïp³ÂÒÒ¸‚9îÆ…ô–gd+4¬,< ¤ˆQÊû´µ@à½c`}æ!Ù¼8äU—ÀõS²y®!aäµ ®«Y¤h^ ÛÕçC’ࣹQp´"©)»Q`È «›â¹õ\U½!Ô§p‰°d» ;OdcjÅ窷J  -›ùïGZƒÅ‚¨oWˆ­CàV³a[n±‘á¡ ]£x>õF‰Ç|´Ãi¼hÌT^ËCˆZ¯àåxˆR²y"àádq¹@^Uæ¾ ê¹Õ(õªuL©¯WéŒù\Býà»æ¯[èÔž*ÿðëÿÜ«ßUªèü.Pv©æ¸­]B¥hrWè&UêÓZx…®(È­­‡p¤M7ø„îÒŠ.r×uÛš$W.*ÿÀG‹Wúƒê  ±î¹jžÌû®’¾«ÛâXmrPpªé§=Ó7èzºBÞ[‘m-ç{j(nä„. åwàx!k cˆšºƒš”:×z„•ª#ÀÉê;#á–ƒE+û6otÒ¬ÁsB3ü%ËY¾;ÔãX.iùÛ¹E¤R·ê gÁÓ_ª•ƒÉ\-ŽKb¢¿¿yj²Žœ*wÞ*¤²îz×i¢>ódµÚ)›ÄMzªK_ Õ—‹%Á^Ðy¯VcȨ=¥¥òq­£‰ÒÿÄ'!1AQaq‘¡±ÑáðñÁ ÿÚ?!é=&aë3¸øO¡)ýñÔ«âòSÓ~eý ’§A._C¥DècN‡þ†^‘— Ã32_7?µ\ÕBÖFë8CÞwË6O?Aè á½ô‰q¯Ø‹Çú“r<Êÿ¬ŠûÒŒ¾–^èG¦µ(IN²ÙŠ(æ’>±©¶høÉ|ÞÑ?i&nð±#y“ æ õÐþRQÌgôpüæ[÷ðüÞaÏ%}rÄÌñ7L,KØtÜŠ³»HÑ]ÇïÌ[që›úí4a×÷Sø¯´dæk³³ö'¨•Bò†%Ýèÿ±]é?Nb­ÏR±?¾`[œÿs‚Þ¸6}:°Þy¸÷%3E”Ãû ¯î¾Ð#÷í?¢Šv÷ ½Šò£¾¿"U×`= Ë`u{ê¨ð~±²k9J»×5 y.‹É(äj-B1|†^ðdäÍeÓÌÇë£ /þvÖt tåmjòüåçq“w „ÚUçgö0AZyPt N×»74¸×0ȰQæ1w†‘ó¯èð”…Öxù:(Ã]:ÂJæÍ1)¦Äâ?œ ×ß­§˜ÿ,îÿÅŽ1M›AÜz·,böHEf­+ 8% ƒ8HÊD3ïÔöe†€nÀ\‚9Ê—4-ã¿~%­6@÷3°Mrï@ø‹I‰1Jtª+ë,8yHÕ}#ÌöðþB!øö úDì}‰Ø{NÛÚv^Óº{EV6”˜ˆ® Ä®WÜ*˜`åhBö€Êš—èh/M¦„X7!y¨ ðˆp5å•BjUF¨¨·\lTµ†WIëaó+®‰q+Òõ~±…è^Ñê,0ÿÞGGù†N8Ñ”Ù-H Nk¬-—Ù.©ÞÔÈ~&Z Õüïû£Ü÷E ƒYï„ÈLÇÖyú"‹Ë·a‡þêrúÓŠŽF‹„KÖq#¦¿ÉÝ»_ï†+CFð„i¼¯=âþe kVTÕ£–_¯êåT@¾.9^Óf-4EX¦ÑlÂϺX΄ÓèÓ¡Ý‘f,s.Çú"ê ú"åÚÿf1³ÞD—i|KÏ1H€M˜Q|Ìv»ÿS5b–Ô•ñIl£Û í_5aì .~4ŽÑ­`Û˜YÖfVoôê>C¦cµlz¢Ë)úNcŠ(±z_RàÏ×s21æx^vƪšƒi˜\SGÖXjá«*Z‡àˆ. Åñã/ºic[}寴ÇHöžr>]IÊtHm_ÌÐÃìf¦/NCÝ]ôz™xÐMG´¹!µ, P6˜ð¨œ4#­s¥ó{Bžt„GF†¿t•Ñ[9ÕJ³Ø˜ÅX2ŠSl4G>'ïÒ zhªzùbëá‹!ÿ&òŸ^“6?µuõ®¤'ü`r_¸Ã¶nY¦ˆ©}¬Þѱ á~³rÂíÄ*mÓî–¶”YÞ^­awÎe ȶÛyšx:® •ùÌ ÊÂÁ^ð¹ºdß3Õ×Ò(ÀëGIÛàt—Møš£'CШÅA>Sê‡5=ãzˆ>»Ò`vd×_Iêä[Zýcà=ž"ìñQ“¢ÇÌÎf¦Ä£6î`1’xMÿ}¢Ë3uc Ðx™ÃÑ@åC˜Jt±á/ѾM=Ô?%ƒQZUc ·|ù`ø¬DGt±QWwsO”Rõ5üKš®Û·¸* s£¹ÆOc²5ÊHD8!vþàp‘ë*zy¹W¨>]Vk’âÁ:3#-ü9²0ûg+Ž™(é¡ÛûNÃÚWƒÚ mG“‰r‹ V“ Ñì¿TʾMãº.Ý¢ß7 ÔoM`b[àjˆ”â qÙ5@!=š|E€2K]TË<”ýŽeí|5WÂûJe·;´Ÿ—*zÏÚGœö™q^Â?Η¡žIÞD}î–gŸÈNKÞ*=ÌT§f!¸[¾¾÷g5ÂöÁ¬ñP´S þܲ¸Nf¯N‘#S>ÒÂÑØT:÷@â¿SΞbëkU¹È{(RThÇ)´²[„í1'`F\¹dGT;#ˆÂÒÇÚ7K©Ñf&gÑrã 0nQ_½ÒR<«êxš,¼ßhÙpJÎ ƒüˆ’_Ù ÚBˆA×ʹ@jµºmë*7Mʲ´« ¨FÅwûeªè’Þñ|½ãcxÒöšn°è‘ÄÙb0ºÝÁ7îƒÒWðü}"¾ÔGH¶ÿh´F Ð÷™}Ä·tW™~ÒèIJñê?¿Ql&FÀ®5n±èÍ}srd8¨;b&SD¶ECEþþÔ*ëŒ7f.’±FiЦKÌrÊÆžÑLƒ§?xPT<‡Ö3)û²$j ´¦œø˜´ÞÈ*•œúFZ™ÌùÜè]R«l£gAã+Ä&èL1‰+ x5È@m‡‘Ý•wÝp~frÇ$¬ùx‚l^`@¨Ä,XÅ–âû1Ñ`ïÅ(bÝßä^·ü›Mÿ?ô/KþEî"ÿx_ï±æÈÉÿ’^?ÔHñ>àíTG©ýÅ7 j)K†%ˆ¬L}˜‹ìì1­D""&}*|":QøEê9ùßÁØ5wƒÅEÆÍ‘”O˜LB‚ee-ñÂ3x„(ŒFëãLzÞ:*6Ä=@£Å¼EVVVQe–_²ÄÅ‹"‹e–'/_mÛ_ס‡ÁtñO£ð?ÙP¦)S4h׳F˜²ÒK_¤»C~Ñÿ|‡G…‡ih¢²Š(¢‘EPš‰aècÐ\/„¶wÅÿÃ˼³¯ê.ІÒì†>òÛÅ…Be(š7ÐØ ÑâòõÍÅåŒ=4)Llö-!"}b‚¾D‹H‚ê^Ö+qšpìN%ÅKÇ£ /h=ÒÓ°Ø©ìOcÃá X¶5­ ûºaØñFà݇®w‡Vb4­!„×ö Bbì…<â²Èÿ#©Ââ#IxwåÔAýF5c¡¦!}Ç‘hªDQk?†ˆÉewãiqJ,S£h‡6ÇÛTBCÒ•$B5ðÑøÀñr±àè<2•LNƒ« 8Ç:oˆ[¢Œ¦<‰'³M ô}2œ0úËÆÈÊ~è>GÈt¹‹.,c#£u¡;¡¦,ëû†ª1ŸEY\>GÈŸD^ˆˆˆˆL.Z'£K¼&ž_xÕCTý™ Äѳ¼JŤÄX£T~£Mwμ‹Ð])E8% ÞoÑJR•‰°/lï ¯8hú/Ðbìô1:q®#®/Eã×,5cMwÍ2è·ÙxL1aó¥.Hk+)&zFË1‹Û“ÃׯѣC7à¯È¼2à›>ŽÄ2ÅàØ²½³¾‡ÎOº%KÙŸÎÂûÃF3ÀÓ]ðó¬uÉ¢hK¸Ë˜ÏÿÄ%!1A Qa0q‘¡@±ðÿÚ?x¥Cw)lX¹<·ý±2¡²òxÓƒÃÌÄÂb{8ra¸Ÿ±¤È4\Ò Ÿ¸iìúŸCé„òú«þHvìGb^#ËirO³‘,<£áÁí2Ö¤|ëëÂ$ó¨ùŸ1МHNÑ.! XÙí!z‘ð.‹ôH룮Š(±¹úQ~ÔGÀøbað?n%·ÿBpðó -J‰™¶ŽOüŠ÷:¿Ñºjb‚04#Â(ÄýGÈ^3¡nDCÛû=^4,1ŒkÖ&H%\1Õß%ûăl EOA¿A·èxD%F›Y!à»°Æü9¯®3äN)ñˆjA#X6rÄè–…ÊTË 1là”pé±mßÒG%øiâÖ lFø)r„ªG (‘”Fk±¶-xp_Žž ä³QÔ[Û*ì¿,¶í/Ø”Ü)¶ËéŽ{<Í g‡„ðž °ö)D¥í!ÂH>#!Íì‰#D2ì‰*;KC»³R› a+ÀåYg`ÈO.L<©¬Pãö7Ù¦EÁtUÙ.=3CqlµHT‡W–p Âk/øBÆ%"7Vì4WBM3¡ªm Q£ŒŸéÁøpX¾/:>•–V!¶6%ð9äà`Ó¢½•NÂưøb㜖ð“%!3Ï…t°­Š)°à‘m i,B]Bi‹ O\XÄRŠb¢¡§³è?qöupÕ„rD-ì© 0‘:Ç¢•!´TsÀ†~ü$ŇJ>çÐúØ}¡^Êßex¥SŒFóc –‡Û&m‰cv?†_-aªI8$~£àB|z"&9ÑŠ®ƒ.ÆM¡¡4¹2ÁŽÛdÖÈ?‚pÏ öhÚ)iaJ\..6FGèž„ddx#h6¢X²®6„íÑ¿²}ŸO)kæ|O˜“Ö BLh”‚E¤‡Ì:£IVt¡Wnõßòp œ±ÈÕí•Co Ø¡?e4Å<PÜ¢Ë,¢Š(¥bM’aPh£Z Ðöñò=hsŸƒ¼ºc—{Ä6XµÀ½„¬¥)p¥(“c;" ÆN‰Ñó‘í Æ$ìçÉó§-ÑMT6D‚P‚¬Ñlg,BÌÄP|¢ ³xA§ЪD|°ÓBeµ€žmyZ÷É¢±ý-Œl†ËEò'šlt!]Ò5éÏþ>ŠÐÕȼÃIéœhõÊþ £n§Bãð&É™Iø8ø1ýÊ~iñ‰ãÊ¡¢äA~^PžcÑ¡è|å‰ÎÎå´|aŒ_à&ÆUÙ®˜ÖUèpb´ % b˜cÂðDϧ…ëÉd‘!a§ÆGëCú|CÂNã Ây*(‰±U‹1OÿÄ&!1AQaq‘¡±ÁÑðáñÿÚ?hâe*ÔdO0ï¼éb_¸9âd@>Ä@8GïH k¢c¹O$èâïÐË…8™LúÂ!åÔÆìJ†s-±Ïq"öÍFÜõƳ»Å™ÊÙ—rèo9™Ì{郬¾ƒžIpêŸÞЮm¬éz>*da˜.çdGßþKy¨†/zÆñŽÐ=¢Z0zUðþáX^Ÿ´JüD¯¼8w¹` ñþ埫ƒÁç·èޱ{â+«Å‰‚×^&…=1ûÃÈum/Õ…·i‚DGÄMïég&áQzâuÙÝŒN£M×K‚K‡t™y0NÌÉN¡‰X ÷pù—•è0!…<£t·Ý2?€²™Ó\bm©ó˜z:Ú/‘. ”zØCDnÖ‡-ÿdzúéþoˆ“ù=¥¥ÿGˆnsøâ¿Ž‘†ß蠟šÿKûü~¸˜º?Ž!C§ îÊëÕÄ9IJç[ï/$_ïÐ-Êö!Ge;\}Ö9R}Ô×øÙ44˜QH±2_æÌXæ9[4ûñi§iÍW÷.ðìªÁáÙ7¿Ô•µœã÷ÄÃÑj@ £;"Ýá H¬ý"|BøHÐ'ÄUluo¶ ÎpüÁI\Ñ„2–Ø9žÂÌÕ´y·õ?ŒüAxo!”y”Ä$ÈÎò|€LÒ=Ò4B™Ö1ÇœiXŽ(‰+ »”Þe±¨-yG´xA½Å"Àh¹Ya¡Ùͬ@Ò²‰‘ 9ĽÈ¸`´ã)gy1AC ßkø¬; }ÏH›E²˜‰¬½7ýš;fKÅþâ\ÿ½g0¿½f—×÷+äú°&Þèµ`+–¢’ Q:â*4¬m¯iUÂß[ž_Ì8Ü6³ôq†® ’ù§ä8Ÿ¼w £ï¦f h‹­Ãó?ï`©G)ÝcœDZÇM‡”6×[ð0 rÕXöÎ}#ÆêÁ–ºÿ\uVwÜTGÚ#mÒ ðÃÄs?`F TN²ÝúÁà;å Gz9ÌAXúqNçx–ÇÏûû?Ü‹×7ÔK…Õ¹°1ßïà\ui¨ñ¯‹>©gÔþ š&êL°-JÁ~•T¡{ ·ÔY{cy`\·Š¥²Y¨M”®."¨o²~HðE…gP:®ÛWc1s*‡9(j”ûbRqCƳô#îMù¦:3e7‡ö†ßÓÍg}ŸÁô(ÅÎ[ÙDÀp¼T.MçI>Œ•…¬ÚUGÕ…]®/ØùaLÐWYžEÊ(ó/Lð:ûÒ€Ys€¬¾cpX¿‚ Œ˜óµ™Ü5Û³ÕŸïPºþÞ%þ^’ÑG nºô£Þ^ÛŽ«r·èÌôܼ~ ëÕÌóAöý’…¦d…lz¯šüľҥ¾YjÂQaÝ2Üè0iç`Àõ,ûJñPSy¬‹ãæ5 Âï¡ »g $t-Î#”‹c}Y‡Ø«PÙiü÷ˆÕÞ®1îø`c«!M›†”u½å}%ŠA£É»û¾%Å¥L‰wÒæ/CJTé¾ Ö„j«®“™Ð… íSræåT`7ë-aVæâP?õñ38§éfs¨$öʈ‰Sóô]fÐɆÂ¥»¼!FCšýê< 1bR¨{îøŠ’ý¾Ù~º+Wê”jÐ+ EK«UÕÕWÝ—>%µ·±[3UNk¼§7” 7mÝö•lòQÓÜ–úì«£pÓUr;nó›8Ô ÷(5g¼a'ì(h^~>ñ,( •vMzʉR\©˜YjTn\$K2~%hÓ,Nì’ôvLQq.}–DX´n^ȵf*NÌq(jeÓ¶(¬,s-_9gˆO7΢è eëj7Ö^VÒ6 Î3X8÷ #*ÀÙ£o_” %ç”,<-Ù—¼½($WbŸh‚jÏÑ(©`fB;Ë,ÂÁÏRüÅ›Rï3„éOì\±fè«Y>…©qDÊÛ/7Ý™e½J?™P³/Ñ‚»‚mcÁ$„‰´·ÍRbó—JïWë*BzίxQr‚æîŸåÂ쎀ÄõæêV¡¶ýSôË-6“6ºªÇ¹«Ö8ów¹`B—¿WtQºR‡ß¼@©.ü±ïï0†‘'Ìô¨z¥)Ä®³[n%{Sîqx—3­ßÇ»šL§]È‘¤NßL3a-Õé0F;Thà…M±&ùí_2À» +˜ZÛU Ú Œ,¥ù½#p²aP+àþbôåyÀxÊD”[A´îåפ°äoµO1¬»±Òª¿iu‚…we)ðñs8ÅJ®÷˜ÑfÆE ›ë!8D¦¨¹`t¨- DPºCåëÍ@5A‚çh€‰|ACЩÒú¸öAV Rvã†1j°è^Ä´Ø"Ø"ÿ2ûÊUµ‰{qëgÌ}QU¦;ïÚzŸögÙ^ch`o0¨ r#MçîKY/Xn}èÓcå‰Cc¾à­–ðÛ2µvŽ^ñ¼¢ Kôcâ—-è¡é „¦J³í7e¸®—é/(À`ªï8SŸV@ª ø”d0aFõÓŽen§fk}Ç´±<.°Fª×½Mù÷Tuƒ›w!ì|Fm lλâUØ(¤:RF·ý|ˆ;*9Rý+>‘ÊùsXó+‘«£ýÀôø¸>žÀÞ gGÑ.ÐÔtÀ¦©öŠ”¤2Uü¨ƒb{²þZÅ{Mü JÈáöeýÁ=˜å®:c*D¦Û»´˜®Hž­RObôt  hªRÝ´~åˆQË8eÕ«Fc¤](˜…»rÒUfú°¡‰Â4M,Å™ò¬ Q`-™Íqpø™1œyI5‰´D§«ÞåËêùÄÅ5ÏÍ%æçA–êÕ^ .Åæ%u½J@Rõà°ˆªíqÎXó3šJæ×Ë%míd¾%[^ÒñAj ²ÏX›L×WÌ·v\(µh>j#{õ–pöŽT;²ÎßxÚ¯¡µhêÅa´gpù¼"‚ËrêÙiT×N=f“Þ€B]—BÛøŒ¡è€*—ÆŠáÞjù௅+W­×Øédëà0üÍßM×ѪÍP¸Â©(¤vcŽ˜Ùº­£§=ï1«P“Ò-Ï‚.93Ák¸»RlþC~²è}½>çê(¥zoÜbRÛ83/ªÌ² ÁA¬ncj—F?ÔVÑØS9 Ž)—qk­!j\ŠÆg qì™_޾‘:¤?'-™ãQ”œ”v:ä”Àõò°£–ŒFšwžkO©à'Þ´üLÀuyž1¨Áâ-:²¥½±/¤qÀôÖ ªÕö¥IE–c•ïi•”@ï¼y×0Kâà¬Ê&¡ÏBcIáùzEÀ¡–Kï4Ž]‘ÓñPyZ /[ÔÁD EI°¸ nPŸu¬ Bð¡ÜSMY# 1†É|SMË‘^Ñ^È´—Œž!"Áô}âôWm¯XÞ"Ó9÷G²YU¤[ÎΈ¹”A:(š‚ØðSÓG£Òá(c«‡PîÈC¶Ù:W0’k^½f†~òüÇ '%$p9iˆ$UD0òµòKKN+CÖ;0ù±ó¸e¨d3ÖaV@[‡o´KjîóÖ¾Ó<˦—D°Uæï*Åb,p€ºñ Ft _qfûÞc‡ÝTd»X>ƒ·Ciݳæ?jÜ)¢6¶Ddb„me»&,c!ìbU{lGÃxlÄ|Eø$Ñßâ ÎåúŒ8®+k¾Òü@®S'5}_A±¾U # • s’9¤¯rrLDÐSê_y~‚¡ÕgAv´Ù¢nÂàc=ìÂÊæ<* _ir*_²gåÌF×—Þ]  xÿ%K|§O÷Únã 5ÜtfÚ‡{¯ÜZñ9Œ¯ ÌTXâ0ÒTAÜc˜GȆ+Ã#ÍÏ(a(»œÇ Ö…x˜»kµ^/ñN€7nÒ‚9=rKa=‡ÜüË_ýM?—M CY‹ Ó‰¤ƒ™u -t#]K°’V½¹+´*O­NWîRÌ2‚é’ø—rVõã~?2ÖØ›Ð¦k÷)L2Gœ·ó1¼—N·ùÄpye·¼V…²Žòør— ìŽq¼f…>pïë,úUÉJ_2çÿ¤( µñž}"œ8ýHÅ[Ý‘b»HĆÞ~jŽ nrl_‚ÖRJP\’‡Õ•z ¬ ´Z/ˆqe¼‡_ÍKK&Ð6 gØ”´ |…ýáˆÙ½@`Lû óÌâXŒÜT+z˜KX•ý7+Š™wp5 ”3¢ÃÁm<±ê1 ÌW¼få"_KíîÆÐÅxÏà ôlCü?yÄŽK÷ó‚ º}¢‹>j˽i”l8Î. a G¼60¸Œté÷•®yˆKh×Gx¥âeo‚d”E,ËÖ*Ñ—¤<óèé-çÒqqõniàˆÚ9åëŒAžqôJö[´ìùŠ]ª4ÙÃ(u~%(î+QàÒ¶2—Ã+Í0 Ú7j¸ Mº‘¼:M(B-H6_iT‡¥÷+­D`)ƒÁÖ·ìÔµvõØ7=C¿ ÷Ÿ8€wìüŸ2¡—¹<´"•ÎF­GâÜT>}'ÅýåëÌ,ÿ²Pc]¥†°a„õ±-ig Ö«é÷”t­ý=èsÄBñæ7g ®uýM²ŽÏ=¥„)”ÄDI˜ z“ÿÙROCm-AMDMIGraphX-46524e8/examples/onnxruntime/resnet50/images/screwdrivers.jpg000066400000000000000000000457451510465702400271210ustar00rootroot00000000000000ÿØÿàJFIFHHÿá4ExifII*bj(1 r2€i‡”%ˆ¦HHGIMP 2.10.182023:09:28 12:18:21 Ðè Ànt¸ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?äÆ1Ó4£J1G5Ìj/JNM.3ÒŒ`1‡õÍJiŒ1@š¯)ùªf¨­0!a“MéR7Zct¦"6ëQµ9ŽiMa¨ØsRSóTDÓ §M0Ð!½i¦œi´ÀiàÔlqOcQ·J±Ï\Ô&¦z‡½D‘õ«QõªÑŽjÌ}iÝöõ¤§Rb¹Íu šæ¨Üê)Aj`[>µY®¢ ·vO ¬‹½} ¼‰ùÈÀ¬˜nîÈb¨­ —Ú"Mô:à Œ¨5±²œƒéTloîiZÛ¸¿Öh$…Ve?}xÈúV’Tœ}Û¦Bu×c4Q·¥L£lV&„£5!¨Ú¨ž)¬iI¦Lš…<šaäP!‡¥6žÂ™L0¨ÛS0¨Ÿ¥Wj‹½LÝ**h‘ñÕ¨Ç"«EÖ­Æ9ÝñGjP(¬ ™^á±WªÎÆFæ»KŸõMô®Tÿ\ÕQÜ™‰z½r¤S*¬ø¾õkEh~ÏæçÅoëc'm._³lÖõŠ©Y7p®+²n@®‚ÔíFÁêý+(î[ØcŽ*»qVØf«H RYé„S¥eA–8¨\jG 20f0,ša¬Ë{Ù¥o™ÿJÑü°Ç¥1OÊd’y4Ï=Én))wtúT1ȳ¿ïr±Ö˜Šþì#¾jyºF•ãÌÙ9¨\V”—#dÇk’z}jÍ»ÛÌÑJ0Êi§r% .e±M‡z°Ý*,sTd>1Í\ŒrVˆsWa\° ÚƒE!¬ J×_ê[é\&©þ´ýk½¸\ÆØô®U½&ª;“#:.¦ºÿøô®z*èåÇþý+ª—Ú1ŸA–GæÐ@p¾Ø®vËï{×In»£ü+njÇ0çÚ«ÉÞ¬6EU”õÍ!˜šÄ…bµ… 2\KµMië2æ@™àU+v¸Pw5¤Q5­4ÉÐhÉ«ÍMs:à «vž~>"–…fx†úÆGD )ûàwªp‹¿F‰R‘™$Í#ájí…“]ÉÏÜMgÂ2q]m¢Goj6‘µFI¬*K•hz8”¡!c1Ú~î‘[Y[—aÇgÄF¶ÖòóûŒVΧ†ÚÀg5‰â›¸ßPñË*ÝÆêÑ#‘7©Ì·zŒjWSQ± ½n¹uõRWí—2®:æ˜x ÒR\æ¤7ò›é\&ª1!5ÞÌ Œó\6®˜ÕGrdeB:×B¼ØÛ?éXw®‚ßæ²QþÆ+ªUäaS¡Ÿß®¢Èf¹«Oõ™Çå]¶f^ɤ=æ¹ÖæÏc,¾[Né¶FXž‚®Éœ sXzÅÇ—QÔÒºÏpÇÞ¶´|3JGXq.çÉ®®þͦшýMtÒV|Ï¡M­Ü†v’@^3‚OZÏÔ$ §JÙÎé¸5~á¶[íYý.ÜÕËÖ±rmY÷5Q³(B½ëFáH€zŠ}¼1°Û…޵¡º!{f¥²íetÈ­ã ƒ]N‡£K©QsíY–¶~aŒW¨øv;=D–êrUI.G…©>HåõéáÒDHBÝH6®;zšóv­Ÿëkz¬—,O—œF§°¬v«]…4 ‘…4 O«ö ù©Ž¹ªPŠ¿iŸ=1ÉÜ)±M!lBi+œØGåq\eä„jdt ÕØçŒ×¨/üL&=Á¦‰fUÖSQ˜üf¬E#´1Ǧj=YH¾fÆ7h·bØ5¢zÔÚÓ / ÷¯_±°ŽÇÁ³Ü°çg•yF–ÃÌ^{ק꺀_,!ˆ'zçŠ!ÔSèyüÌk’Õ§2\ WK{(ŠcØWäÉ14Dl¹¦Ûù·˜ã<×CtA‘#Æ@䊥¢ÃÃJGNYVó%wíž+i{´½L—½SУª>ØÏ^+gUð×ÙtMXfb÷p][^?:çµ6ß* ç½z½ï†¯uû¿ØZ‰o§¡‘ñÀÈZ曲;(rsþócÍVÚKWá0§ø…]Úe+Úº[ûk{[¹´¹YeØJ3ƹ2âÚòKWa´ =*!>m×ÁÆŒUZnñbO|aVÅ^Ÿ[¿Ô´È¬Œû¡NJ†ûǵs¥MÕÐU?-k›Xc€±PbºáNé³Ëu9]—R££FØu ûÔMZ¶òü›%Q,~ÛéNÕto²Û¥í¾ZÖCŽz¡ô4r¦¯sYÙ˜DRÍH™ޤdñUûL‹ˆÈþðªPŽ+JË‹¨Ž3ó è ¦“Å.x¦çšç6­rwH_Q˜JꘌW5wòên}E8’ɼi¢ 2ßH¹Yw‹Ëo3§NGë\í¯ÝãÞ»¯'Ú|á{¾ñ£ÂOâøšâ-ÔªƒW…/ˆÚÓÉYGï]Uý÷›§ÅÏýk’±S‘€I5ªÍ*¦×¨LLÉÖgÛÌç5…å²kGX´¸ªã‘T¶:{p ÓJþ¦–(˜B[b˜t«»?(ÜFUdPÊ{[wéocáõE`÷W(QÔ r—=¼…òüÎM-&Ô/ÉBزŽõî~/ñT>ð¼1ZþÔ¹cOU@É®~ÛÃqøcÁL×*?µ5<"®9U=Gåšä~'ÊÆwˆú¼GùVZJF›#–]Zæ2NíÌ{ž¹ªÛÞGfbK7Rj4\’Þ•nÖ2U_^µªŠ¾:³”R“Ñ´û}‰æ0ÁnŸJšâMÎt^µbB!‡#Š©lžµÑYòEAÔ×4¹™rÖ=Ì£­í\ _ ËpeeäxüUÑ­L÷jïSøâTŽê (ñû¥ËqÞ¹âjÎ1é€sR50 `X„V¥ƒyw¸ìàÖlCŠÒÓñö¨³Ð0¡ˆ×ÏÒØ¨Ëæ·Îl9›½sš ?nu­ÖnÕ‘x7^&j™Ðj‘5çÂ+&êmoÊŸ¡ þ5ÇÚB“°BË;»^‡§D.>kvØÏ•õußh¬[ÛÅiÔ)Îjµ¸™è¾"_x’Îm ˜ÿtý+‰€âv¯áÍy4çœ]—0´nìŸÖ ½´³‰Ñ¬¤y2>|Žh©4œ®C¨›Hé4ß›áÖºia?øúמê2ÊŠôM à/)þÿÇ–¼úþ,¶k8—#:Ö-ͼŽõ­n€œÐTZ£5QÜ™l]ððÿŠĹÿ¦Ík‡¹ù¸®ãÿò"ø›éóÄ`¼ßJ”TŽ—ÀZTw~%‚I€ò-‡Ÿ)=‚óXÚö ú¶©u&wO!|Ùí]f˜‘ðÿR¾8ß·Ù£Ïuèߦêá®ÒZ»ƒÚÆ=ÁùñD9YáìëT`³‹û3Í#çÆsN{)$Ó&ºŒ€ÐààÑ(Y¤ú‚•ïcfúÛHÔµ{»Í>ßÊÓØ*ë§úV}´×0ØÁ|»@^ „0ÏZŽÎKNßÊžP°þ­8Íz5¦“¦]øAa Ýl#¯'Ú¶SWJ= ¹]›—S‚}y ÕÖ{+.Õ‚™aÏ»‘Y—·Bîö{…ŒF$rÁEÏjK»Wµ£px5+9;»šF)!§&šÈMJ)Ô†U1œÓÖ2;UŒ t\ñU*„.qN¨˜Ó­âiæX×¹  ÅŽ}ªÊ^ªkB8#†0ˆ¸þµYåʠϽ%¨f£4ó֚±F…[ƒò¥sänšº ‘òÖßUÄ–h[Û¹Ìå«§¯Qêj½¯6{µ¦ô”=óZN6°¢ïtz%Å·Øü ­»L¾Gð5ÉhúÆ›i¨Žâ#üF8üë¿ñ()à{¼]a8ÿ­yõä&ÊÂÞÑÆ%ÿY =‰©’ÖÁ¡–Ãå'"¨Ý}ÓWÚ¨Ý}ÚC1Ø|õ¡j¹ zÕ,|ÿiØ.fŒz°­,IÓ]ÝľõÄjað¦™n82ä~uNås,kùUÿü³XZÿÏ(\V•~6eOáG $dþ æ7v­Ž«¼U‘©Ñhz‚Å$r ^ƺۻt¼„]ÚòÀdã½yŒ=¼€¯Jì´-dDÃq&6ûËU—»-™‹~ôwCnÓíj.MsóÂa£v®úöÎ7kƒOÌ@ïï\Ö¯O&ô\R”\˜á%%ts­É­="1—~ãŠÏ*rkSKá{Ðöܽ3‰ˆëНkXU¦]ÊEIafU>´D$s†šÂ¦#5Ås#bÀùM`ãWC8àÖ ƒþ5¤Hfå€ÝhÃëSس¡÷¨t®c`j{a¶UúÖÕ>³8|Lö{Ø‘¼=ó°ÆŠïÆx9ý+Êu ƒwy,Çø› z õ-FeƒÀK<€öí쩯'e=jg¸á±Y…R¹^+G ¼oÎÜ󎸪7#Š”S2•yZú|`ÞÄãp¬Ô_ÞŽ+JÆC êàd©èkH´·!ìu¶öæãW·ˆ ’ÀS|jûüMp£¤`'å[> ˆ^ëöó62§%}±Ö¹­j_µë7—‘$¬GçDÝÛ}Åk#$®i…3Ú¬”¤ÙšƒB“EKo&åéVÊÒ¢h©‰N­lg§ '¡¨%ž;™äˆ 8ÉéŠç#f…òµÑéóÃ6 Ó*ä`àrjµj̹FçN>Gž8%öýi– ¤…Oq[·ag*ª»#_ºµšÈämíC¶È÷e•Z±eX2õ¦FœÕØcÍ$6Î8úTn*|Ôn9®dlT”V$鉎=k~Eâ²§2Ö‘!—t£ËjµÄßFªº_Ë>¥^ ¶àÿ½[ËàFkãg­jPùÿ ¢õP§ò9¯3xx¯[Ú_áÃ01^rbRÇ> ÔÏ áÔÂ’#ØVmÈÅuÍh­vçéY×:J¸'‘š‚ŽLZöWo¢8÷—D¶à¸ù”p) ¶s”Õ©5±.)îzG‚¾Uû5¬Ds¹º~UÈÞÂEä¹çç<ÖŸ†´Ë«ÝB(¡b„õ>•kÄ8Ó5ImD‚]Xçµ9MÊ:“¨ËC™(zRy~Õuá"£1c­A¡We1’®ù~Õ ¦)ˆ¦ÑüÕ4?!â—g5*FOJ¢KËxvð2Þ¦ˆP³î=i!·>•~bqFˆ "J¿ t¦Ãl8­KkUãŠó¢1Luüªç•íÍ'[±®Tng:qT¤‹/[OlÇ€*³Ù+ž8¬7"E XÂ]¦ *ùOôœ{ÔW Á4AàsVQÖiÃ:V²k’Æi>kžÇj¢†­‚î†s\4PdW\nS¶»Í,Ÿ®ã8cä8Ôà⹇äaŒŒpi1Ä«Ƨñç¥1­~_º3ǽ_† ̹Á|ô©V"F1ŒsŽy©(ÊË‘”Æz÷¡¬cWÏ–õ [QÛ‚oðGçN›·’J…烌,#*š%ÂHTêqJð3üûKnµ¬m×iÚݾ”ö¶`û )#½h˜¸³a’A¯Ö©˜0px®ª[L®HÀì§µQžÀçîŠ@aN*´±â·M¿`U ›r§8¦™b<ž•zr;Sí  (V¬pÚªâH« éWb„Š™Ú­E{R,ô5§oN*(béÅhÁ1@l°‚zf¥á<ûTˆ™Ç?­L«ÕH¬M ÿdû¸#4bãæã¥_DÈàu8©‘Œcž¿J´K9ë,ÜB†BUwäjnYK\±>›Zºf· rðØgÛøZÙeVK‚ŠyþuÓEßM>fU—_‘ÝA·Fðœ3Y¨q{”‡)ÁäW8¨¤…' õÀ«ïy»Nµ°@Í99cÔý)‰2$ç ÷¨«+ȪQ´v"UW‘X Þ˜« pÙ Œ’8NGSÎÐ?¥Jªƒ†|§ãžZ_s™o™îî^øz$ælI[fó×Î7›¡^(Îz•¦\*Ø’*íXhºo|}<´Îg¶e:ñ·Ë¶˜H ôEÒ~êÁ‡+†Ü-M)4F™µaFU™¬´YÜ]\Òç?ÒjZbο@tsc¼ýæÙ¶÷r÷æwž˜ùþš.“¾W·Ï™C³¸š°"é¸ñôòÁ.T)/m Ö­®9Žzªé½œÔ¼þÝ3-cô9çO—Ç¥á”HÏ.á­—d ¡£§–†5+¬³l"Ú\¦Ò§IÁÕ:ã¦Ó€hõ¼úþ7¢ûR@°²#Hcãéá”Yóh;àÝÕÌ;ËñÚܽ)ÖD^Ìsè¦ù‘Ò.ðôéeŽ*„,´nBÜ}<²­(Gy2Ɖ· fr,Â’¾hCîYØ­ÌdÙé,ˆê-qôÇJ¡CX®ÍæÚâ.“t HÑâÊ "Ú E>¤çÓŽ/–µ6­áuÅÕC)éÉû£þnsy”Û»ážrõÛ»“CéÊk‡Ù£ZiÕj0Ë*ñõEIràuµA,õΪ^ó¬óH_Ò &jG¡Ãî[Š¡ž¿óV˜¨ñôÇH5H 'ELí áó-3]²¤·ï‹6: Êô›Ï>v}B& ÈɼT©_Ö[¡èƒUEä#i4—n®f¾^…üìCMéé!¨ð#hÌ» “xh p/I?7®T+çkÕ!) Ò^zùZ8ºsq×kE”Ŷi˜î×ĸe ýñÁÕ0zD=—¸‹/ËA, Ðý!篙Ÿ¥ ]2g G«—Ü6ÏÕú&\ï­óTóúã¤â·NL nÎr®m°5Mmaß§§“¡4àPR'ò£3»µ)\=PÚ p?|Ú6Ïn•sôVzUõÍÒ¡Á4,wäÑVÛ1}\ÛV˜‚óý ÓºÔÁ3VÃÄë˘ ‰Y@ª µŽóoa«V§¤'¯™ºó÷‹²Ó- Å" Zjà¤N¥ÉkzIÙ Í\s¹ÿÿÄ)!"12#3 $4ÿÚÿÒiú:téÓõu%/˜üÇüº’’u$éÓ§déú:’’Ìú±~ l ªÅGKü:týIIEAl¶VÍÚ…Œ™9räÕ%É«ï8É”“§OÕú:’—Ì:å?™~Ág›Vö½WÙI”“£[€§4Ôn¹sPŸ’ ãOóCo)ÖWù“íPþÀ¦¯ž]Áe›· <†<§TUÄ(*5؈ðí‘þ`„Þzeõïø‡ö®ÛÂH®¬Ë¸j"+ ý¸N¬>hŽ! 'ß »ŒP§L²ç$4÷ôÊ"}ñê?Þ›o}Ö@½±B<çÇ´—¢û,F V‡¨¯w¬K ÐßÓ$ߨÃvT¦Ñvòl=7%b:Ê”ñ¡äi¾äû–¼vB*Ð,¾lxœ3©tÍéÓ'æ¡c½A?ŠqÞTÚ4ʼnqŒß¸\|;@hð•Ä\ ñ|àÒäaÞtJó„_ ÕvÜ©ÖGÿ,æ¬/µ9m#]Û “7<¤HöÃqø‡Ca[/…ÔÊ3ÉټĹP\æYnøæv.V´*Þt5V;›¥ï5ôÇìCouo·²säJÞ%Z3¸iâ ÌdËø <þ\cäí…O„©Ü{Æõ6PÕVÜ»§ugÈ´\öÔãñ'›Ý«±¼û›9Ä¢ï\oˆËr>žŠ«Å¦iózãÝX‡ã4ìº SmÏÉ;¢¾ðÓòìe3Ô˜9Í86ƒL¼XÜ%8l©äý½KŒ°âà Žkq„m‰·@%§±¢Ö»½²ý¨A‰ktòS§ÂzÁ¶ÔZwËÞ–Í^ èpÝŽÎÅÒºŽ8µ™À“ (Ct²ÁT«zƒ$4Èò”Ÿ Ö6-+®Z±ÙkQùQi¨¸òáÉiºsµ‘ÌZªŠG!#æZw0#SÊàψÈS£7XÈÄ%!’tìË‚ŒvXxóÉ;§YÝW}Ö›Œ m?6.hÞ_Þ†‰þæ°ÞOÃÙ œí×(æÇJý‰[\Wm0Ó ÕAöL]úýØCp.–m²­î&[þ-§ðßlK~ê0bä‡Yé,nr"§ªÐ/VuC’têãþ°ý¨“„´ëm–ÃQ|…ýAq¯em|CíˆìÂG•ýA–õª—eǘ‰E圕jœá! ú^h#ºU“îðÚtÁ®ß…'âcš<úcºsK Š%g— ðv÷¬¯U·ÙCöyuÂüw^È Ì¯Éu,êz—IÙJë ([û“Ú,Somõ·ƒ_h«]5¡ƒd>Èì6§ õ‚=ˆÕí¹G«ˆ{QLÙ¼AmÝ¢3azH nÊ[sKÜö#òD&Ž‹ÿ(§1¶ãC“žÃ<“¹M࣠v«‚oÛžÉôœÉ~@ì¶M࢟˵Ô1ËŽGm8#&Hœ…Çd Ÿ°Lá6­À+¥.“ê X4¦°0b5Z¥îx4vÛVݘNfFèvM o4\§s^£Ä#³¤hÓ”Ó¹PÓ¨ô½UùcõQÉò {?ĨüP?Ô©¤»½¡!¢hÈ?#R B(i)Þ%EâêivóË´àÖ;0œë7*š”xPðŸä4nVQß%ƒ‰#V9nätšCíIÎ’ÆžBcq¿pÒ?"ŸÎ«o}–ÔhÑg”þÑC°Sç,ý¥Í)ñ°CnÉC³3¥e¾6ÝGó¹ÙH,;W§4ÿÄ+ !12"AB0Q@a#3PÿÚ?ÿ΄"÷“z_–W¡k¾œ²¿„‘&Kf-ÿ‚„C»ê>w#øÁ‘<ëB:o’~LWGdšÙ^RíYb”¦íhGMòOÉŠèÄÔ;Ó=ER8¨·ýÚ£r–ǵbËZÓ|•<™#ú:d ²RŒRí¥Ý鿢ÞJ˜rx ¬Šî™ÔKº¡ú‘ýiwBäcѽ/e73’yÊÁêáâVHíQòØ”]Ëu­41ŸQ[¨}”ÔTm=‡Eà„13§†^N¦y}¥9¸<¢XÎ×wDþ,Ï‚&pòNn£Üx‚Ë!îyv[ìmF˜Þw"´>4Tñod…Yü¨N‡˜‰}D¿¤.–¬s,mj.*Y‘ÔTRÙY,-àL“âìÇè‹\"5$ž3vÈ,²_­‚ì‰Gì¹#îÝ^1Ë#Ɨň¸/"5(úN›þÉoEgj”ªCeî¿¢ž&²*}þ;§xŒ§É>m-ÚWÃoá”c ^ìŠO.Q'77—«7C#É.mÛwéüÉoQ’Ýà„ûV?¥ðIáXX¿MäÎfA|’BbÐØ·¼NF°Il˜Öl­Ó}™›ÃµOßÁVŸ¦ò¸³²W‰KͼØ×úî­Gjrdxwk%‹þU8*Òt¤!ñex”üÑ_Ì[Óº¨3롬ó©M‘‡¿³THù#¨ò)øÝ 7ÀãW¸'“Óƒ»µb$t«u §ÅÕ½I®R]ÛêÆ¥jÞ1#ÆŒÛ;cVu#à›Nš#Çâ_ ‘éã?ºD©Ñ¥¥–=81t;çUÒ–}Y`«èEb“ÏàÀÑÁÍБÿÄB!1 AQq"Ra±23B‘0b¡¢@c#$CPSTdrs’²ÁÑáðÿÚ?þnÀ¯šø EËý‹"+³Í‹G_ØðP©¢=70êwg*Žè± Úxý‹‘ë¹@V*8ùª¬–³Öâ×{Ž)Ìðš^7\×p­ }$Dà ׎7bʺ’«ÁŸ3ýÖ„IÔæMãuèõ¼ÝaPšÐ8Ý•ÂN Ùã?ÁD~®Üo]Ì-Q©¢ÉI%2·aCÍ%h?rˆd´MÆê&Y -ö‰XO»çºÞ·½'pSÉó@‰D§>îÙžVM˜i<*õŽŸ"ŽX\54*ÉŒz¬^ó^ãw#Ðb•d—Á3}UµŸ¿ªíR—Óp’{¾jÍÚÌÚ,–†Žõq`:P¬lˆ5h4ª”Eðñe{1dÕ ‚Ü³Šºž/ýë¹N…À^Uy:ªÐ|E®ú´oئÿMÕUÑfb¶·mÙ–œ¤g‡ï+£~ÒÊcÚC3~fßNˆÙìÅ=;Îð"ççÉIÎå‰ÊÉcÙGf‹ωXjµÑsxnbyË–üöžV9Çéÿ¥<¸já š×x\tÜyVé¼0”IÔÜݦmYg‘{t¹¢êÆ¢§wî%oé)¡Â”ãü8nHU­Þ75—ù-Œ¹´èœ×f…y&ÜãnsN™É9ãÝá^W”þªÈÝ ²—þ[žJ”ÇOªnT@Ý^;ƒª¦P*bïÓ;´»$XâÊèfÃÁ×ñ;£’!£ u'R€CpÜÕmgˆ]¥â’=½¬Îv:šïdªnÓuŠÙ^GÑe­Ú ù¬M8|©PÙ¢ƒS'ö®è›ÍZ£æ?ÙbʺõZd´ü‘UáÕ«–AdW×EL<K›‚´Zv›H[F»gªÊO%ÝE~(šù&ÓMQãw’­úoí6˜Ä¥²âÚmH­4Uã%C¢åŸåv‹J´¬Æ[¿ÿÄ&!1AQaq‘¡±ÁðÑá ÿÚ?!=@øèƈóˆmšèÚ„QÌßWét·•4ʼĮ0cÏþ’Íõú6†7œt° ÝYÐv©…A‹"6n£½]: ´›Á“ €fYa…¶§/3Ì2b”_bÿRƒ¼È‹¥G¦Q!›taɉ‰¶+|NåµN+%ÝŸg YŒ¸Gœ8‚]'¹I˺ -Z—ÐîâÔáìà d–¯ÃÑÓãûß™G¹1œàÛí/˽‹ô¡ésîb§ˆŽVx'm¡ýÅyZxéÏ­ts(¼óƒíϾtë̼ù–-„RÐê÷ŠìËÉH‰.ÂaŠÌ¯}˜›”÷ L°f]OxF¡½ ûæŸkÚŒØâ*f åÄo2ÇÄY‹ö]ÊŽæs@{GC´nÜ%¹[#ñ¯¯Ef ˨Ý:S-q;7r¯,F:¬¼FeÔ¥7)é}ND·šÂ ƒáæ . Ì9„%¢WÛ1n9‚·ˆS§Ó½‰ø™¬_.æuæoŽ«jæß8›À)®õõÙ1rt|¢Ã€”!¼áõbZÛ™¯ÁkÚ-¥Øž¸ûGpæUü3ILTüOô¢=ÌœËZö½q lECûñ ¡0>^\³ö‰–OzüŸëczTw _HVÛÚ3-QˆKìgÈXzHwßÚú+=ð€§ú?…ÞWÙ*M¯PøúM½ÄW̵lµÄÒÏGxÑFøúï1KÃèâ*"VY±an{D¾>==,~cÛÐú4ôènúú9˜~ßÉ‹Aµ/isHKZ\Ä·‹fî?ÓþKU“{»Ëw “9wÔ‘xóÀ©Ú¹X`g£@׸¢€¯,wlnˆa4¿&¥t€ˆ<ª+DòåúCŽiªƒæ/CòÏŽ#‚¥¹”óÝöô±r³ŸÃ©š!ù×xX aƒÚãHÚ»Œb¶#䘆â‚^âV¸T “<™pJåöœ.éÙE_+âÌt¢¹$Dà‹kCc¸b!Ãyãp pµðÄÕZ½š?GL£nŠ ¸‡-3^#©¤ JÙ23»àÄ›x!7úªdLķĘ(ˆ„}JÂvì¤ÞewÚ7)P„ ªù”ÁŠ)T-0ÅÔ‘ ô‡/´Ð~¸àÇ”g©Eù¢ŸèA\»ï<â8,!iÔí–VÅW¹uB`´"•®—¥eW‚3j7|¹‹hÒʱ#ˆýÏ%yíÄL¡•{Å[(Þß=åú8ž B£§+úí4ì8ý¾“HzSáÿÈÅá)ù·ª‡›$›ø•î»%ö¡µæ%'`f'JÉÇÛ)^ˆ4+ØnÅYó¯˜Wë`Š¡ÐßbZ»Çn€îýÅËxè¶[ Ù´¦{`x9›Ðå‘€0#„rç1ñFBÌâ5ü!fÃï¥x nV@…ÜÁÈŒ„ÉŸÄztÑ-ôeµ‚™aÛÄ>ežŽ‚t.éqß¹<ƒñ/Ç~&FÉR¦"<„ðÚßvUÖªeÔØêZK|Í8”™tå–a{AgĨå2nÊñX«ìoÊj¶9}e ažù¯µà¹¸§ÙÃãŒÄÜÍGÂ3Kas²‡ŒJ«‡—^&ºm\£‰LTKÚ?=#â€ßÉÃFÓóýÚ7¶­Ö¯|ÑK¸Ö®–mØïò'¥ìy”viS›h-ÀÐJ¦cÓËÚ T\F̈¶[åsW!¢~æ´Ð£'õÍ™LFA•M[f·6[»o~~a(+ÊÞÿö¥¨1…ºõ‰[­ÁNÝ^¥5š9þÜ®M~ÿ[‹„ NÏ7‰v”¤Lgõµ®ÒÅè6 ¼|võN(!_bSêÍœ<åû{—¤pDŒqêŠq¶'KM‡Å{÷Õ#yõ/±ÙöAéýýPÍÖI ªºïãûÜçFâÿ„âQ^üÔ¬˜X&\iÿÿÚ ` ¯öiª3 >üy º8J7é(œÿçAÙâNs 0‚ßXNÂiV¡¡±I¤¤€IcLñ É«u´"íô(¼ÿªî;Ó©ÂgoÂé*Š¡qùW$|ˆ0–Ù»þ`?†2j]U•¾~™Ôˆ%Î!_š-*ͤ×9)ú»ÚƒÔpOê§&Q´UÏ¡ ½y)½b¶¼~ÅÀ| þd7ŽôÔК ”¢£˜ó˜pßо{Sk!QTerì]CZ äâÉb2vŽ o!"DY/’v™Ýê .‚ý!ËþŸ¾žŸÿÄ'!1 AQaq‘¡Á0±ðÑñÿÚ?éz‡¡þ/ŽŠÁžÁ¦Âléáö~ÿÌpäµÞ]. ÛÒõ<Ç7¬¹J)r¡½`ÂàÅã—± ÔG¸·ùÔPku~ Ùü=œ="ø‚ oàóÛÚRŠÿÉËîYjOúr{„@Øuq8¸fŒ¼<Žû‡ìÁ--~¾“…žÇƒýÌâ"^Ç1Ù“ áp~&j‰t\3ûîAÞƒW… g‰¸B— Ì©¬+—þå½Ã¼¡I¯÷×ó h‰©aרîûø;y~¶pÈï’^4@j-ý™ÙÙÅéÿŒjtœ˜º´BúðÛŽ\,áÔ Ü9p° ¹‹©¥½Ã ÿ©FeGÍwúÍg¼¨Wyÿ½O‰A±„ï3sõjUB¸Ðže~/@ï8ðî(”üÐQDu¬8¼¼NIé”/bY~¬²Uפþç(é¸n$<˜@íî©(PÜâ:8zYÀJ5œÃD§]ÄYõ‚©8^q—…AöCLŸ‡ûšB¥G™Øf¢&ÒXß ¸#Ä"Ôa½Dp.O1Ý?dÉañ­í—pštËjk§Po,`F\q¸æµa;ñû0܇ ‘¨%„¦úȤûC¼¼Ö —)¼~'ìÆ‚Þ*ú§Ø @\Ajž·œp„G£×ìšæ+´aÌz=Íâll +Ëb„yÌ@¶,Ÿ@~ÉQeàErí{Žá©s¾ávHº†Ú—úyÙ€#ÉÐ9Ÿêy›† NàNe¤ääÖ}Ê;2àc‡ˆ©SSâ ¾Œ1 Ôï} ¬pÞxÈÛ"ßç9cÜs‰qõ¼±%§HN8;JËêv'KÃ=@´}¡0ñ–,`§}¹¨ÍŽYX*—PŠ®‹ó8G˜ž9 #‹‹/¤{¸ebâÆõ4W|ýAgÕsöô]K–Cp—9ËT²{ûªûÍZ.oÕÆq‹‚!çc’‹?ÿÄ'!1A Qaq‘¡ð±ÁÑ0ñÿÚ?ôW¤‡¨Áè=d!è=¬";H~ €?äû!ŠÁÿ‹`°^A`@Š0dÉè#õ8–Ø.^ÞQÕ¿øÁX2ágpo%ª{ ¾‡˜ýÆq“}›HÔ>#(&Û;aÏS‰Ì~ã9Á4 µ@—|]y}>ñÀï!¸5+/3Ôã ùpfÌ¥¡ñCXR/¨â1*Yš¸’âå„{™C|7-“†£¯å6› ¶sOOê*‘ÛÃDßÿ¿Öuq€ïrô0FªxãP/ˤ´!þý½!Ž¢á Y6ʶ‰AÎáš[ZþYY[cI²ð†ÄÿáôöbM49áa…¹¢¾Hâ+H%ˆx…غàDùJ?Ü P:ÏÀÿ1’¥›ÊNÁ<ÄDòB~çý”¡«ùðâ,H‘ì ±òûÄ7~ÌXȈö­M†Í`[ú—þÒ¡¢m啕¡%pé=åj:”M„f†Í^ „¦ý Ù÷“ˆËá~þ1‹â3µ ×Y0öÈ[9 èyì‚’ýÿî•06¿(Fmþr BÈ.–àé)"«¼—ª0N¦ÑXOû ûd jE±˜Éñ+WYäv¼]GS¨#¨å÷ð(ü¹BTU¯>c&Ù*„'SDù†‹ñ?çÐv£IUîH2‹¸–õó,îT ÁÉÜ4ÜÙø'|¹rÎßÌ@&Ø>ø!‡³˜²äîV¦Éò±Éi¶6È8!· !;7²wœ•T©Sï‚ø%Ë—€4–ýOŏ׿+TÉ*mL? Ò\³p„ Ê2{h¿¹TÅåuø%˃ê¶©uŒXQ?ÿÄ&!1AQaq‘¡Á±ÑðáñÿÚ?UólEd»ãïS–Ù[VÕñ*ø&{d{o}Æì92»‘ÚîX_W»‹TVì]¿1e'dvqAù”µ_€°cæÃ=Á6¥arƦ2qІ¹š0ëîjµ,¡ÁÔZ`}á·ÅÃEÛæàÿñ*IzK¬æ**Q¢vPdLCv®è~Ì•uù‚†Ü›¿R¯:ÊÄȞ󸄼fÜOña¸¢E™vST¾` Ýsp]ŠsæPX2 éŒR]Ü1YIä‰XùåR©,{ŽËÙÕÔHÝOÇ7t[Üx§gþÒ‡Ô*¸U=A¯ÜÇ5fÅ—,£æ:©¦§‡¹ÏL$a•gETªn:Æ$øR?™yî² Ñ›jæZ5ál¨ÉWŒûË&ê;ÕyêönñÅ`Ÿ|›±W éçç¨÷Ç2å.1þy( î]Ê>!ª˜Zsžå\®©üÏþäcÛ*È™Gq ¹Ù›µ8J5V’aÚòœ°-bè!ÞoÍÚêka?…-J ¾ 'oP»:Æ“Ù82 ‘O´˜®3Ym+ïËøo5tš[ñ£¯h“ýZÄn¶5^Ó¸Oi[±{u@ÒXqÏP_ÖûéI¶vâùêaHƒt×À~奦èÿs/Ê/D Û[–øjƒÅÛ’”žÀ2«ùê1+Ílpò¨ÌÁŒhFŸU9ye —ÏùˆßÜÁ2Ŭ ©Ò…sPaÓ¨b‹¨VB·ÒkpßyB\~"ó¬"«<2´@¨´W0ççþË.£¾–vph#¿s9\€}å× `·Š½ä2-é#žxçê T­ ì½æ­É^¾ñ`$8\êf°óKÂBl¥ƒ SÃül8ô¯è˜¯ ó×ãù”u´ÖŽ{»IQ{y]Ô ÊxKž‰âFÀ]L6¯†½?9º .€þâ& ¼ÊXWØÏÔ»%»«S×§Jqð‚¯tÜ$AšæUWAõç bê²¾p²ßê'-lka@y÷ЪpÿÍ‚¢˜-š`b1jeUè× HËC5úS¿ÆÍ;ôžìzGç7ø§üÁ…AµÃI÷ èü·"‡ðWšÆÔæ¸jà”ðŒé ÛCK×0ARË«„¶¼…–{ŒÝŽ÷jRýÔ@‹pzÓõxlû¾?¸bvbõþ÷ö *;4–*GÝúM+Ô?,ò…hE¤du“žåc¥}r±]s¤NH½/@áqL+–ý1?&ت¶éê±ýËÔTüªýeÑVG™(ï÷” Þ)DæØ¹üÄK9Ôèò¤—ô‚ÍýeÄOÛøÒþ‘Sò¸‚ÈãEœ%÷ö˜L|}¦—–qò`µ@ нYg(6”;[ЃŸ)·J×ÄÀg) ÊÐÕ[Þb¬)¶®åz;.Lx-Ć՛gÜVûC½XF5wÔ~ã`Q.»ŽÊXm˜Ì—‡—™G—ÂbÐ__`Š™€î˵Ö¸/Ô+ÇúüÇÃ'´0«6¾±=ê4Sà5¶ý|„™öœ#£ë™‡JfìL3¡TmÉñþê *ªË-¾¨ÅêõÀ'¬#òYÐoĦCxXßÊPmÏǨ¡Ö4öÚ‚ã/ëÌVãkâ;ˆ9N(åá^©uŠ6׈¦ˆƒähɼȨ–Tº9X¯}GD@ ˜q ‹CÔÑÀ£Å,WÝ÷†hè£SNêø÷}i•›«¬¸Jd¾òºœ Å™9S/ó;•å?çïq¡ÒÝYÀ ÉRG±íú†¡Ð®¼Ê-ÆgGþ")(  ‚Õ®/¯K|„`˾I`¡-?)Ο‚6˜yý˜ª ³½U¾¶üÜÁ¯Ì3Gd¬¼‹ø·îže¡ö}×5.¹¯-´ñÛSpôŸáÿ†EQø}‘ e4ÃXˆX¥ÉÇ_RÞJñDäáŸ|Øï>¼|’éå!sÁüFŸK~j8E‚‚ò뫬¨j5|'/Wxäé{“Œˆ5o²Ë•1ÂRXc,!nœÔ FOB8ß]¼×ÖZjJy—ÐåÊ­ L˜çò œÅ¶"6w= q†ïüëv?–Ai¿WHÈ¿ˆh-Z åqÏÓl–­VF¹l²‹xB¢èj—©W"¨¨p5™Þ%†îm ‡ ðC-!PC|IñN…¥ó\ù‚˜nBnõ¶¬Š¬Õ¢0ªûØ–q¶KkÑ_âXTª…·E ƒ+· ‰Çlek¸fDåséÄŠÑO#ÀðQÌDœVqQô~„¾ãî l¹T4y«–¤Î¼¥ÔÈ!;µôŠ?Ìýn¡€QmÅú¾H$$ÄŽ³·—ø¯¤ºµ(XM}iψvù¢Þ)¡ÿ=Á(¬¤˹‘\Ä·­­õu„¥IJÂjήLj«?âm”©¦ÌZ€Ê¹pâ9üÌÀ7 [r!ãþÅ¡u_¸4ÊKMŸûó$¢íÃ8ž¡ñe¿´(¢ÛÞ Ìçx`š Ë‹uuª/—¾•)L_ø,¡iSŸ­h)`5`8°¤´pü@¹*4ñ‰eZùoԥɅŽóÅóâÁÛC¨e}ÇÝcûc†ëóZtŽ$îkŒ E6 Ér ¢•æ((,¤Þwây~R5Ôî2Ë·š×êçòðÒ".Õšž‘â•Ôá+9P zìÏYqf¦¦Økç9½*‹‹ß$ø_eK]Äá`Z·µnþí‚é©.ÇÀ9ò/Qa3A<P¢¼¥FWBÌ1ôôy¬ Syub­ø_®#åìæ¡ŒŽ,†£¦0hþl®ZÂ"•ñQ)Æ o‡ýÄÙ±JO/pE+‹ï5}_YþÊÞM³„j}¨ø•@jØZñ¯“ÄUppÞ,°p¥oH©zMåSÏ|Öù¶Ô4  lá·ÇÞÜŠì,UX³ƒGßÞl„ª/¨q–æþæ¶® ÓN9ãRËŽ+hÛR×\õ™§æ4…Vݺ= batch_size: break if img_count < batch_size: print("Warning Batch size " + str(batch_size) + "Requested but only " + str(img_count) + "Images from dataset") print(bad_image + "Images caused RunTimeErrors during preprocessing") if verbose: print("List of bad Images:\n" + str(bad_img_list)) return input_batch def main(): flags = parse_input_args() if flags.verbose: print(flags) if flags.verbose: print("Reading in Imagenet classes") # Read the categories with open("../dataset/imagenet_classes.txt", "r") as f: categories = [s.strip() for s in f.readlines()] if flags.verbose: print("Getting Exported Model from Torch") # Export the model to ONNX image_height = 224 image_width = 224 image_chan = 3 x = torch.randn(flags.batch, image_chan, image_height, image_width, requires_grad=True) resnet50(x) torch.onnx.export( resnet50, # model being run x, # model input (or a tuple for multiple inputs) "resnet50.onnx", # where to save the model (can be a file or file-like object) export_params= True, # store the trained parameter weights inside the model file opset_version=12, # the ONNX version to export the model to do_constant_folding= True, # whether to execute constant folding for optimization input_names=['input'], # the model's input names output_names=['output']) # the model's output names # Quantize the model if flags.fp16: if flags.verbose: print("FP16 Quantization Enabled") os.environ["ORT_MIGRAPHX_FP16_ENABLE"] = "1" # Enable FP16 precision else: os.environ["ORT_MIGRAPHX_FP16_ENABLE"] = "0" # Disable FP32 precision session_ops = onnxruntime.SessionOptions() if flags.verbose: session_ops.log_verbosity_level = 0 session_ops.log_severity_level = 0 session_fp32 = onnxruntime.InferenceSession( "resnet50.onnx", providers=['MIGraphXExecutionProvider'], sess_options=session_ops) if flags.verbose: print("Preprocessing Batched Images") # Preproccess and batch images input_batch = preprocess_images(flags.image_dir, image_height, image_width, flags.batch, flags.verbose) latency = [] if flags.verbose: print("Running samples") for i in range(flags.run + 1): # +1 for warm-up run run_sample(session_fp32, categories, latency, input_batch, flags.top, flags.batch) if flags.verbose: print("Running Complete") print(latency) if flags.QPS: print("resnet50, Rate = {} QPS ".format( format((((flags.batch) / (sum(latency[1:]) / len(latency[1:])))), '.2f'))) else: print("resnet50, Averaage Execution time = {} ms".format( format(sum(latency[1:]) * 1000 / len(latency[1:]), '.2f'))) if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/transformers/000077500000000000000000000000001510465702400211035ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/transformers/README.md000066400000000000000000000001541510465702400223620ustar00rootroot00000000000000# Transformers Inference Examples - [Python Whisper](./python_whisper) - [Python Llama-2](./python_llama2) ROCm-AMDMIGraphX-46524e8/examples/transformers/python_llama2/000077500000000000000000000000001510465702400236545ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/transformers/python_llama2/README.md000066400000000000000000000030331510465702400251320ustar00rootroot00000000000000# Llama-2 This version was tested with [rocm 6.0](https://github.com/ROCm/AMDMIGraphX/tree/rocm-6.0.0) revision. ## Jupyter notebook There is a dedicated step-by-step notebook. See [llama2.ipynb](./llama2.ipynb) ## Console application To run the console application, follow these steps below. Setup python environment ```bash # this will require the python venv to installed (e.g. apt install python3.8-venv) python3 -m venv ll2_venv . ll2_venv/bin/activate ``` ```bash pip install -r requirements.txt ``` Use MIGraphX Python Module ```bash export PYTHONPATH=/opt/rocm/lib:$PYTHONPATH export LD_LIBRARY_PATH=/opt/rocm/lib:$LD_LIBRARY_PATH ``` Llama2 requires logging to access the models ```bash huggingface-cli login ``` Get models with optimum ```bash optimum-cli export onnx --model meta-llama/Llama-2-7b-chat-hf models/llama-2-7b-chat-hf --task text-generation --framework pt --library transformers --no-post-process ``` *Note: `models/llama-2-7b-chat-hf` will be used in the scripts.* Run the text-generation script with the following example prompt: ```bash python txtgen.py --prompt "Where is Szeged?" --log-process ``` *Note: The first run will compile the models and cache them to make subsequent runs faster.* ## Gradio application Note: requires `Console application` to work Install gradio dependencies ```bash pip install -r gradio_requirements.txt ``` Usage ```bash python gradio_app.py ``` This will load the models (which can take several minutes), and when the setup is ready, starts a server on `http://127.0.0.1:7860`. ROCm-AMDMIGraphX-46524e8/examples/transformers/python_llama2/gradio_app.py000066400000000000000000000043641510465702400263420ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import gradio as gr from txtgen import Llama2MGX def main(): # Note: This will load the models, which can take several minutes llama = Llama2MGX(1024) def gr_wrapper(prompt): if prompt == "": return "Please provide a prompt." input_ids = llama.tokenize(prompt) result = llama.generate(input_ids) # trim input prompt from result result = result[len(prompt) + 2:] return result with gr.Blocks() as demo: gr.Markdown( "Start typing below and then click **Run** to see the output.") inp = gr.Textbox(placeholder="Type something here...", label="Input prompt") btn = gr.Button("Run!") out = gr.Textbox(placeholder="The result will be displayed here", label="Response") btn.click(fn=gr_wrapper, inputs=inp, outputs=out) demo.launch() if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/transformers/python_llama2/gradio_requirements.txt000066400000000000000000000025161510465702400304710ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### -f requirements.txt gradioROCm-AMDMIGraphX-46524e8/examples/transformers/python_llama2/llama2.ipynb000066400000000000000000000247071510465702400261010ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#####################################################################################\n", "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the \"Software\"), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n", "#####################################################################################" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Llama-2\n", "\n", "The following example will show how to run `Llama-2` with `MIGraphX`.\n", "\n", "Install the required dependencies." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Install dependencies\n", "%pip install accelerate huggingface_hub[cli] optimum[onnxruntime] transformers sentencepiece" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use optimum to generate the onnx files.\n", "But first, we need to login into huggingface to access it" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Please be careful and don't publish your token anywhere\n", "!huggingface-cli login --token YOUR_TOKEN # from https://huggingface.co/settings/tokens" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can export the models." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!optimum-cli export onnx --model meta-llama/Llama-2-7b-chat-hf models/llama-2-7b-chat-hf --task text-generation --framework pt --library transformers --no-post-process" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, it is time to load these models with python.\n", "\n", "First, we make sure that MIGraphX module is found in the python path." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys\n", "mgx_lib_path = \"/opt/rocm/lib/\" # or \"/code/AMDMIGraphX/build/lib/\"\n", "if mgx_lib_path not in sys.path:\n", " sys.path.append(mgx_lib_path)\n", "import migraphx as mgx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, a helper method to load and cache the models.\n", "\n", "This will use the `models/llama-2-7b-chat-hf` path. If you changed it, make sure to update here as well." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "# helper for model loading\n", "def load_mgx_model(max_seq_len, shapes):\n", " file = f\"models/llama-2-7b-chat-hf/model\"\n", " print(f\"Loading {max_seq_len} seq-len version model from {file}\")\n", " if os.path.isfile(f\"{file}-{max_seq_len}.mxr\"):\n", " print(\"Found mxr, loading it...\")\n", " model = mgx.load(f\"{file}-{max_seq_len}.mxr\", format=\"msgpack\")\n", " elif os.path.isfile(f\"{file}.onnx\"):\n", " print(\"Parsing from onnx file...\")\n", " model = mgx.parse_onnx(f\"{file}.onnx\", map_input_dims=shapes)\n", " model.compile(mgx.get_target(\"gpu\"))\n", " print(\"Saving model to mxr file...\")\n", " mgx.save(model, f\"{file}-{max_seq_len}.mxr\", format=\"msgpack\")\n", " else:\n", " print(\"No model found. Please download it and re-try.\")\n", " sys.exit(1)\n", " return model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With that, we can load the models. This could take several minutes.\n", "\n", "We set the maximum sequence length at load time, if you change it, please reload the model as well." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "max_seq_len = 1024\n", "decoder_model = load_mgx_model(\n", " max_seq_len, {\n", " \"input_ids\": [1, max_seq_len],\n", " \"attention_mask\": [1, max_seq_len],\n", " \"position_ids\": [1, max_seq_len]\n", " })" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import the remaining packages." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from transformers import LlamaTokenizer\n", "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Time to load the tokenizer from the original source." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_id = \"meta-llama/Llama-2-7b-chat-hf\"\n", "tokenizer = LlamaTokenizer.from_pretrained(model_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we will define all the steps one by one, to make the last step short and simple.\n", "\n", "The first step will be to tokenize the user prompt." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def tokenize(prompt):\n", " return tokenizer(prompt, return_tensors=\"np\").input_ids" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next step will be to convert it to match the model input.\n", "\n", "We will generate the attention mask and positions as well." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_input_features_for_input_ids(input_ids):\n", " input_ids_len = len(input_ids[0])\n", " padding_len = max_seq_len - input_ids_len\n", " input_ids = np.hstack([input_ids, np.zeros(\n", " (1, padding_len))]).astype(np.int64)\n", " # 0 masked | 1 un-masked\n", " attention_mask = np.array([1] * input_ids_len + [0] * padding_len).astype(\n", " np.int64)\n", " attention_mask = attention_mask[np.newaxis]\n", " position_ids = np.arange(0, max_seq_len, dtype=np.int64)\n", " position_ids = position_ids[np.newaxis]\n", "\n", " return (input_ids, attention_mask, position_ids)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use these in the decoding step." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def decode_step(input_ids, attention_mask, position_ids):\n", " return np.array(\n", " decoder_model.run({\n", " \"input_ids\": input_ids,\n", " \"attention_mask\": attention_mask,\n", " \"position_ids\": position_ids\n", " })[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The generated tokens will be decoded with the tokenizer." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def decode_tokens(generated_tokens):\n", " return ''.join(tokenizer.decode(generated_tokens,\n", " skip_special_tokens=True))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally the text generation part.\n", "\n", "With each decoding step, we will get the probabilities for the next token. We greedily get best match, add it to the decoded tokens and unmask it.\n", "\n", "If the token is end-of-sequence, we finished with the generation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import clear_output\n", "\n", "def generate(input_ids):\n", " start_timestep = len(input_ids[0]) - 1\n", " input_ids, attention_mask, position_ids = get_input_features_for_input_ids(\n", " input_ids)\n", "\n", " for timestep in range(start_timestep, max_seq_len):\n", " # get logits for the current timestep\n", " logits = decode_step(input_ids, attention_mask, position_ids)\n", " # greedily get the highest probable token\n", " new_token = np.argmax(logits[0][timestep])\n", "\n", " # add it to the tokens and unmask it\n", " input_ids[0][timestep + 1] = new_token\n", " attention_mask[0][timestep + 1] = 1\n", "\n", " decoded_tokens = decode_tokens(input_ids[0][:timestep+2])\n", " clear_output(wait=True)\n", " print(decoded_tokens)\n", "\n", " if new_token == tokenizer.eos_token_id:\n", " break" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now, to put everything together and run the whole pipeline:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "prompt = \"Where is Szeged?\"\n", "input_ids = tokenize(prompt)\n", "generate(input_ids)" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 } ROCm-AMDMIGraphX-46524e8/examples/transformers/python_llama2/requirements.txt000066400000000000000000000026031510465702400271410ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### accelerate huggingface_hub[cli] optimum[onnxruntime] transformers sentencepieceROCm-AMDMIGraphX-46524e8/examples/transformers/python_llama2/txtgen.py000066400000000000000000000147041510465702400255450ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from argparse import ArgumentParser from transformers import LlamaTokenizer import numpy as np import migraphx as mgx import os import sys import time from functools import wraps # measurement helper def measure(fn): @wraps(fn) def measure_ms(*args, **kwargs): start_time = time.perf_counter_ns() result = fn(*args, **kwargs) end_time = time.perf_counter_ns() print( f"Elapsed time for {fn.__name__}: {(end_time - start_time) * 1e-6:.4f} ms\n" ) return result return measure_ms def get_args(): parser = ArgumentParser() parser.add_argument( "-p", "--prompt", type=str, required=True, help="Input prompt", ) parser.add_argument( "-l", "--log-process", action="store_true", help="Print the current state of transcribing.", ) parser.add_argument("-s", "--max-seq-len", type=int, choices=[256, 512, 1024, 2048, 4096], default=1024, help="Max sequence length the model can handle") return parser.parse_args() class Llama2MGX(): def __init__(self, max_seq_len=1024): model_id = "meta-llama/Llama-2-7b-chat-hf" self.max_seq_len = max_seq_len print("Load mgx model") self.model = Llama2MGX.load_mgx_model( max_seq_len, { "input_ids": [1, max_seq_len], "attention_mask": [1, max_seq_len], "position_ids": [1, max_seq_len] }) print(f"Load AutoTokenizer model from {model_id}") self.tokenizer = LlamaTokenizer.from_pretrained(model_id) @staticmethod @measure def load_mgx_model(max_seq_len, shapes): file = "models/llama-2-7b-chat-hf/model" print(f"Loading {max_seq_len} seq-len version model from {file}") if os.path.isfile(f"{file}-{max_seq_len}.mxr"): print("Found mxr, loading it...") model = mgx.load(f"{file}-{max_seq_len}.mxr", format="msgpack") elif os.path.isfile(f"{file}.onnx"): print("Parsing from onnx file...") model = mgx.parse_onnx(f"{file}.onnx", map_input_dims=shapes) model.compile(mgx.get_target("gpu")) print("Saving model to mxr file...") mgx.save(model, f"{file}-{max_seq_len}.mxr", format="msgpack") else: print("No model found. Please download it and re-try.") sys.exit(1) return model @measure def tokenize(self, prompt): return self.tokenizer(prompt, return_tensors="np").input_ids @measure def get_input_features_for_input_ids(self, input_ids): input_ids_len = len(input_ids[0]) padding_len = self.max_seq_len - input_ids_len input_ids = np.hstack([input_ids, np.zeros((1, padding_len))]).astype(np.int64) # 0 masked | 1 un-masked attention_mask = np.array([1] * input_ids_len + [0] * padding_len).astype(np.int64) attention_mask = attention_mask[np.newaxis] position_ids = np.arange(0, self.max_seq_len, dtype=np.int64) position_ids = position_ids[np.newaxis] return (input_ids, attention_mask, position_ids) @measure def decode_step(self, input_ids, attention_mask, position_ids): return np.array( self.model.run({ "input_ids": input_ids, "attention_mask": attention_mask, "position_ids": position_ids })[0]) @measure def decode_tokens(self, generated_tokens, skip_special_tokens=True): return ''.join( self.tokenizer.decode(generated_tokens, skip_special_tokens=skip_special_tokens)) @measure def generate(self, input_ids, log_process=False): start_timestep = len(input_ids[0]) - 1 end_timestep = self.max_seq_len input_ids, attention_mask, position_ids = self.get_input_features_for_input_ids( input_ids) print("Generating response...") for timestep in range(start_timestep, self.max_seq_len): # get logits for the current timestep logits = self.decode_step(input_ids, attention_mask, position_ids) # greedily get the highest probable token new_token = np.argmax(logits[0][timestep]) # add it to the tokens and unmask it input_ids[0][timestep + 1] = new_token attention_mask[0][timestep + 1] = 1 if log_process: print(self.decode_tokens(input_ids[0][:timestep + 2])) if new_token == self.tokenizer.eos_token_id: end_timestep = timestep + 1 break return self.decode_tokens(input_ids[0][:end_timestep + 1]) if __name__ == "__main__": args = get_args() llama = Llama2MGX(args.max_seq_len) print(f"Call tokenizer with \"{args.prompt}\"") input_ids = llama.tokenize(args.prompt) result = llama.generate(input_ids, log_process=args.log_process) print(f"Result text: {result}") ROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/000077500000000000000000000000001510465702400241655ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/README.md000066400000000000000000000033561510465702400254530ustar00rootroot00000000000000# Whisper This version was tested with [rocm 6.0](https://github.com/ROCmSoftwarePlatform/AMDMIGraphX/tree/rocm-6.0.0) revision. ## Jupyter notebook There is a dedicated step-by-step notebook. See [whisper.ipynb](./whisper.ipynb) ## Console application To run the console application, follow these steps below. Setup python environment ```bash # this will require the python venv to installed (e.g. apt install python3.8-venv) python3 -m venv w_venv . w_venv/bin/activate ``` Install dependencies `ffmpeg` needed to handle audio files. ```bash apt install ffmpeg ``` ```bash pip install -r requirements.txt ``` Use MIGraphX Python Module ```bash export PYTHONPATH=/opt/rocm/lib:$PYTHONPATH export LD_LIBRARY_PATH=/opt/rocm/lib:$LD_LIBRARY_PATH ``` Use the helper script to download with optimum. The attention_mask for decoder is not exposed by default, but required to work with MIGraphX. ```bash python download_whisper.py ``` *Note: `models/whisper-tiny.en_modified` will be used in the scripts* There are *optional* samples which can be downloaded. But the example can be tested without them. ```bash ./download_samples.sh ``` Run the automatic-speech-recognition script with the following example input: ```bash python asr.py --audio audio/sample1.flac --log-process ``` Or without any audio input to run the [Hugging Face dummy dataset](https://huggingface.co/datasets/hf-internal-testing/librispeech_asr_dummy) samples. ## Gradio application Note: requires `Console application` to work Install gradio dependencies ```bash pip install -r gradio_requirements.txt ``` Usage ```bash python gradio_app.py ``` This will load the models (which can take several minutes), and when the setup is ready, starts a server on `http://127.0.0.1:7860`. ROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/asr.py000066400000000000000000000166341510465702400253360ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from argparse import ArgumentParser from transformers import WhisperProcessor from datasets import load_dataset from pydub import AudioSegment import migraphx as mgx import os import numpy as np import sys import time from functools import wraps # measurement helper def measure(fn): @wraps(fn) def measure_ms(*args, **kwargs): start_time = time.perf_counter_ns() result = fn(*args, **kwargs) end_time = time.perf_counter_ns() print( f"Elapsed time for {fn.__name__}: {(end_time - start_time) * 1e-6:.4f} ms\n" ) return result return measure_ms def get_args(): parser = ArgumentParser() parser.add_argument( "-a", "--audio", type=str, help="Path to audio file. Default: HF test dataset", ) parser.add_argument( "-l", "--log-process", action="store_true", help="Print the current state of transcribing.", ) return parser.parse_args() class WhisperMGX(): def __init__(self): model_id = "openai/whisper-tiny.en" print(f"Using {model_id}") print("Creating Whisper processor") self.processor = WhisperProcessor.from_pretrained(model_id) self.decoder_start_token_id = 50257 # <|startoftranscript|> self.eos_token_id = 50256 # "<|endoftext|>" self.notimestamps = 50362 # <|notimestamps|> self.max_length = 448 self.sot = [self.decoder_start_token_id, self.notimestamps] print("Load models...") self.encoder_model = WhisperMGX.load_mgx_model( "encoder", {"input_features": [1, 80, 3000]}) self.decoder_model = WhisperMGX.load_mgx_model( "decoder", { "input_ids": [1, self.max_length], "attention_mask": [1, self.max_length], "encoder_hidden_states": [1, 1500, 384] }) @staticmethod @measure def load_audio_from_file(filepath): audio = AudioSegment.from_file(filepath) # Only 16k is supported audio = audio.set_frame_rate(16000) data = np.array(audio.get_array_of_samples(), dtype=np.float32) data /= np.max(np.abs(data)) return data, audio.frame_rate @staticmethod @measure def load_mgx_model(name, shapes): file = f"models/whisper-tiny.en_modified/{name}_model" print(f"Loading {name} model from {file}") if os.path.isfile(f"{file}.mxr"): print("Found mxr, loading it...") model = mgx.load(f"{file}.mxr", format="msgpack") elif os.path.isfile(f"{file}.onnx"): print("Parsing from onnx file...") model = mgx.parse_onnx(f"{file}.onnx", map_input_dims=shapes) model.compile(mgx.get_target("gpu")) print(f"Saving {name} model to mxr file...") mgx.save(model, f"{file}.mxr", format="msgpack") else: print(f"No {name} model found. Please download it and re-try.") sys.exit(1) return model @property def initial_decoder_inputs(self): input_ids = np.array([ self.sot + [self.eos_token_id] * (self.max_length - len(self.sot)) ]) # 0 masked | 1 un-masked attention_mask = np.array([[1] * len(self.sot) + [0] * (self.max_length - len(self.sot))]) return (input_ids, attention_mask) @measure def get_input_features_from_sample(self, sample_data, sampling_rate): return self.processor(sample_data, sampling_rate=sampling_rate, return_tensors="np").input_features @measure def encode_features(self, input_features): return np.array( self.encoder_model.run( {"input_features": input_features.astype(np.float32)})[0]) def decode_step(self, input_ids, attention_mask, hidden_states): return np.array( self.decoder_model.run({ "input_ids": input_ids.astype(np.int64), "attention_mask": attention_mask.astype(np.int64), "encoder_hidden_states": hidden_states.astype(np.float32) })[0]) @measure def generate(self, input_features, log_process=False): hidden_states = self.encode_features(input_features) input_ids, attention_mask = self.initial_decoder_inputs for timestep in range(len(self.sot) - 1, self.max_length): # get logits for the current timestep logits = self.decode_step(input_ids, attention_mask, hidden_states) # greedily get the highest probable token new_token = np.argmax(logits[0][timestep]) # add it to the tokens and unmask it input_ids[0][timestep + 1] = new_token attention_mask[0][timestep + 1] = 1 if log_process: print("Transcribing: " + ''.join( self.processor.decode(input_ids[0][:timestep + 1], skip_special_tokens=True)), end='\r') if new_token == self.eos_token_id: break if log_process: print(flush=True) return ''.join( self.processor.decode(input_ids[0][:timestep + 1], skip_special_tokens=True)) if __name__ == "__main__": args = get_args() if args.audio: data, fr = WhisperMGX.load_audio_from_file(args.audio) ds = [{"audio": {"array": data, "sampling_rate": fr}}] else: # load dummy dataset and read audio files ds = load_dataset("hf-internal-testing/librispeech_asr_dummy", "clean", split="validation") w = WhisperMGX() for idx, data in enumerate(ds): print(f"#{idx+1}/{len(ds)} Sample...") sample = data["audio"] input_features = w.get_input_features_from_sample( sample["array"], sample["sampling_rate"]) result = w.generate(input_features, log_process=args.log_process) print(f"Result: {result}") ROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/download_samples.sh000077500000000000000000000031061510465702400300570ustar00rootroot00000000000000#!/bin/bash ##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### DIR="$(dirname "$0")/audio/" mkdir -p $DIR wget -q https://cdn-media.huggingface.co/speech_samples/sample1.flac -O "$DIR/sample1.flac" wget -q https://cdn-media.huggingface.co/speech_samples/sample2.flac -O "$DIR/sample2.flac" echo "Samples downloaded to $DIR"ROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/download_whisper.py000066400000000000000000000071601510465702400301130ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from optimum.exporters.onnx import main_export from optimum.exporters.onnx.model_configs import WhisperOnnxConfig from transformers import AutoConfig from optimum.exporters.onnx.base import ConfigBehavior from typing import Dict class CustomWhisperOnnxConfig(WhisperOnnxConfig): @property def inputs(self) -> Dict[str, Dict[int, str]]: common_inputs = {} if self._behavior is ConfigBehavior.ENCODER: common_inputs["input_features"] = { 0: "batch_size", 1: "feature_size", 2: "encoder_sequence_length" } if self._behavior is ConfigBehavior.DECODER: common_inputs["decoder_input_ids"] = { 0: "batch_size", 1: "decoder_sequence_length" } common_inputs["decoder_attention_mask"] = { 0: "batch_size", 1: "decoder_sequence_length" } common_inputs["encoder_outputs"] = { 0: "batch_size", 1: "encoder_sequence_length" } return common_inputs @property def torch_to_onnx_input_map(self) -> Dict[str, str]: if self._behavior is ConfigBehavior.DECODER: return { "decoder_input_ids": "input_ids", "decoder_attention_mask": "attention_mask", "encoder_outputs": "encoder_hidden_states", } return {} def export(): model_id = "openai/whisper-tiny.en" config = AutoConfig.from_pretrained(model_id) custom_whisper_onnx_config = CustomWhisperOnnxConfig( config=config, task="automatic-speech-recognition", ) encoder_config = custom_whisper_onnx_config.with_behavior("encoder") decoder_config = custom_whisper_onnx_config.with_behavior("decoder", use_past=False) custom_onnx_configs = { "encoder_model": encoder_config, "decoder_model": decoder_config, } output = "models/whisper-tiny.en_modified" main_export(model_id, output=output, no_post_process=True, do_validation=False, custom_onnx_configs=custom_onnx_configs) print(f"Done. Check {output}") if __name__ == "__main__": export() ROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/gradio_app.py000066400000000000000000000041701510465702400266460ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from asr import WhisperMGX import gradio as gr import os def main(): # Note: This will load the models, which can take several minutes w = WhisperMGX() def gr_wrapper(audio): data, fr = WhisperMGX.load_audio_from_file(audio) input_features = w.get_input_features_from_sample(data, fr) return w.generate(input_features) examples = [ os.path.join(os.path.dirname(__file__), "audio/sample1.flac"), os.path.join(os.path.dirname(__file__), "audio/sample2.flac"), ] # skip if there is no file examples = [e for e in examples if os.path.isfile(e)] demo = gr.Interface( gr_wrapper, gr.Audio(sources=["upload", "microphone"], type="filepath"), "text", examples=examples, ) demo.launch() if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/gradio_requirements.txt000066400000000000000000000025161510465702400310020ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### -f requirements.txt gradioROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/requirements.txt000066400000000000000000000026011510465702400274500ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### accelerate datasets librosa optimum[onnxruntime] pydub soundfile transformersROCm-AMDMIGraphX-46524e8/examples/transformers/python_whisper/whisper.ipynb000066400000000000000000000266401510465702400267210ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Whisper\n", "\n", "The following example will show how to run `Whisper` with `MIGraphX`.\n", "\n", "Install the required dependencies." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Install dependencies\n", "%pip install accelerate datasets optimum[onnxruntime] transformers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use optimum to download the model.\n", "\n", "The attention_mask for decoder is not exposed by default, but required to work with MIGraphX.\n", "The following script will do that:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# download and export models\n", "from download_whisper import export\n", "export()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now it is time to load these models with python.\n", "\n", "First, we make sure that MIGraphX module is found in the python path." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys\n", "mgx_lib_path = \"/opt/rocm/lib/\" # or \"/code/AMDMIGraphX/build/lib/\"\n", "if mgx_lib_path not in sys.path:\n", " sys.path.append(mgx_lib_path)\n", "import migraphx as mgx\n", "\n", "import numpy as np\n", "import os" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, a helper method to load and cache the models.\n", "\n", "This will use the `models/whisper-tiny.en_modified` path. If you changed it, make sure to update here as well." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def load_mgx_model(name, shapes):\n", " file = f\"models/whisper-tiny.en_modified/{name}_model\"\n", " print(f\"Loading {name} model from {file}\")\n", " if os.path.isfile(f\"{file}.mxr\"):\n", " print(\"Found mxr, loading it...\")\n", " model = mgx.load(f\"{file}.mxr\", format=\"msgpack\")\n", " elif os.path.isfile(f\"{file}.onnx\"):\n", " print(\"Parsing from onnx file...\")\n", " model = mgx.parse_onnx(f\"{file}.onnx\", map_input_dims=shapes)\n", " model.compile(mgx.get_target(\"gpu\"))\n", " print(f\"Saving {name} model to mxr file...\")\n", " mgx.save(model, f\"{file}.mxr\", format=\"msgpack\")\n", " else:\n", " print(f\"No {name} model found. Please download it and re-try.\")\n", " sys.exit(1)\n", " return model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With that, we can load the models. This could take several minutes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "encoder_model = load_mgx_model(\"encoder\", {\"input_features\": [1, 80, 3000]})\n", "decoder_model = load_mgx_model(\n", " \"decoder\", {\n", " \"input_ids\": [1, 448],\n", " \"attention_mask\": [1, 448],\n", " \"encoder_hidden_states\": [1, 1500, 384]\n", " })" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Time to load the processor from the original source.\n", "It will be used to get feature embeddings from the audio data and decode the output tokens." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from transformers import WhisperProcessor\n", "processor = WhisperProcessor.from_pretrained(\"openai/whisper-tiny.en\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we will define all the steps one by one, to make the last step short and simple.\n", "\n", "The first step will be to get audio data.\n", "For testing purposes, we will use Hugging Face's dummy samples." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datasets import load_dataset\n", "ds = load_dataset(\"hf-internal-testing/librispeech_asr_dummy\",\n", " \"clean\",\n", " split=\"validation\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next step will be to get the input features from the audio data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_input_features_from_sample(sample_data, sampling_rate):\n", " return processor(sample_data,\n", " sampling_rate=sampling_rate,\n", " return_tensors=\"np\").input_features" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will encode these and use them in the decoding step." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def encode_features(input_features):\n", " return np.array(\n", " encoder_model.run(\n", " {\"input_features\": input_features.astype(np.float32)})[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The decoding process will be explained later in `generate`.\n", "\n", "The decoder model will expect the encoded features, the input ids (decoded tokens), and the attention mask to ignore parts as needed." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def decode_step(input_ids, attention_mask, hidden_states):\n", " return np.array(\n", " decoder_model.run({\n", " \"input_ids\":\n", " input_ids.astype(np.int64),\n", " \"attention_mask\":\n", " attention_mask.astype(np.int64),\n", " \"encoder_hidden_states\":\n", " hidden_states.astype(np.float32)\n", " })[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following parameters are from [whisper-tiny.en's config](https://huggingface.co/openai/whisper-tiny.en/blob/main/config.json).\n", "\n", "You might need to change them if you change the model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# model params\n", "decoder_start_token_id = 50257 # <|startoftranscript|>\n", "eos_token_id = 50256 # \"<|endoftext|>\"\n", "notimestamps = 50362 # <|notimestamps|>\n", "max_length = 448\n", "sot = [decoder_start_token_id, notimestamps]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To kickstart the decoding, we will provide the `<|startoftranscript|>` and `<|notimestamps|>` tokens.\n", "\n", "Fill up the remaining tokens with `<|endoftext|>` and mask to ignore them." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def initial_decoder_inputs():\n", " input_ids = np.array([sot + [eos_token_id] * (max_length - len(sot))])\n", " # 0 masked | 1 un-masked\n", " attention_mask = np.array([[1] * len(sot) + [0] * (max_length - len(sot))])\n", " return (input_ids, attention_mask)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally the text generation part.\n", "\n", "With each decoding step, we will get the probabilities for the next token. We greedily get best match, add it to the decoded tokens and unmask it.\n", "\n", "If the token is `<|endoftext|>`, we finished with the transcribing." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def generate(input_features):\n", " hidden_states = encode_features(input_features)\n", " input_ids, attention_mask = initial_decoder_inputs()\n", " for timestep in range(len(sot) - 1, max_length):\n", " # get logits for the current timestep\n", " logits = decode_step(input_ids, attention_mask, hidden_states)\n", " # greedily get the highest probable token\n", " new_token = np.argmax(logits[0][timestep])\n", "\n", " # add it to the tokens and unmask it\n", " input_ids[0][timestep + 1] = new_token\n", " attention_mask[0][timestep + 1] = 1\n", "\n", " print(\"Transcribing: \" + ''.join(\n", " processor.decode(input_ids[0][:timestep + 1],\n", " skip_special_tokens=True)),\n", " end='\\r')\n", "\n", " if new_token == eos_token_id:\n", " print(flush=True)\n", " break" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To test this, we will get the fist audio from the dataset.\n", "\n", "Feel free to change it and experiment." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sample = ds[0][\"audio\"] # or load it from file\n", "data, sampling_rate = sample[\"array\"], sample[\"sampling_rate\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "input_features = get_input_features_from_sample(data, sampling_rate)\n", "generate(input_features)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The result should be:\n", "\n", "`Transcribing: Mr. Quilter is the apostle of the middle classes and we are glad to welcome his gospel.`" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 } ROCm-AMDMIGraphX-46524e8/examples/vision/000077500000000000000000000000001510465702400176655ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/vision/README.md000066400000000000000000000003721510465702400211460ustar00rootroot00000000000000# Vision Inference Examples - [CPP MNIST](./cpp_mnist) - [Python Resnet50](./python_resnet50) - [Python Super Resolution](./python_super_resolution) - [Python NFNet](./python_nfnet) - [Python U-Net](./python_unet) - [Python 3D-UNet](./python_3dunet)ROCm-AMDMIGraphX-46524e8/examples/vision/cpp_mnist/000077500000000000000000000000001510465702400216615ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/vision/cpp_mnist/CMakeLists.txt000066400000000000000000000031771510465702400244310ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### cmake_minimum_required(VERSION 3.5) project (CAI) set (CMAKE_CXX_STANDARD 14) set (EXAMPLE mnist_inference) list (APPEND CMAKE_PREFIX_PATH /opt/rocm) find_package (migraphx) message("source file: " ${EXAMPLE}.cpp " ---> bin: " ${EXAMPLE}) add_executable(${EXAMPLE} ${EXAMPLE}.cpp) target_link_libraries(${EXAMPLE} migraphx::c) ROCm-AMDMIGraphX-46524e8/examples/vision/cpp_mnist/README.md000066400000000000000000000150201510465702400231360ustar00rootroot00000000000000# Performing Inference Using C++ API ## Description This example demonstrates how to perform inference using the MIGraphX C++ API. The model used is a convolutional network pre-trained on the MNIST dataset, and inference is performed on a random digit selected from the test set. ## Content - [Basic Setup](#Basic-Setup) - [Quantization](#Quantization) - [Compilation](#Compilation) - [Preparing Input Data](#Preparing-Input-Data) - [Evaluating Inputs and Handling Outputs](#Evaluating-Inputs-and-Handling-Outputs) - [**Running this Example**](#Running-this-Example) ## Basic Setup Before running inference, we must first instantiate a network graph and select a compilation target. See [this example](../cpp_parse_load_save) for more information about working with MIGraphX program objects. ``` migraphx::program prog; migraphx::onnx_options onnx_opts; prog = parse_onnx("../mnist-8.onnx", onnx_opts); std::string target_str; if(CPU) target_str = "cpu"; else if(GPU) target_str = "gpu"; else target_str = "ref"; migraphx::target targ = migraphx::target(target_str.c_str()); ``` ## Quantization Optionally, graph programs may be quantized to fp16 or int8 precision to improve performance and memory usage. ##### Floating Point 16-bit Precision To quantize using fp16, we simply add the following line: ``` migraphx::quantize_fp16(prog); ``` ##### Integer 8-bit Precision Int8 quantization requires calibration to accurately map ranges of floating point values onto integer values. To calibrate prior to inference, one or more inputs can be supplied as follows: ``` std::vector calib_dig; // ... read in data migraphx::quantize_int8_options quant_opts; migraphx::program_parameters quant_params; auto param_shapes = prog.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { quant_params.add(name, migraphx::argument(param_shapes[name], calib_dig.data())); } quant_opts.add_calibration_data(quant_params); migraphx::quantize_int8(prog, targ, quant_opts); ``` ## Compilation Network graphs saved in e.g. ONNX or protobuf format are not target-specific. In order to run inference, we must compile the graph into a target-specific program. Two options may be turned on when compiling: - `set_offload_copy(bool value)`: For targets with offloaded memory (such as the gpu), this will insert instructions during compilation to copy the input parameters to the offloaded memory and to copy the final result from the offloaded memory back to main memory. Default value is `false` for offload_copy. - `set_fast_math(bool value)`: Optimize math functions to use faster approximate versions. There may be slight accuracy degredation when enabled. Default value is `true` for fast_math. The following snippet assumes `targ` has been set as "gpu", and will compile the program without the fast_math optimization. ``` migraphx::compile_options comp_opts; comp_opts.set_offload_copy(); prog.compile(targ, comp_opts); ``` To compile a program with the default options, we simply call: ``` prog.compile(targ); ``` The targets "ref" and "cpu" both compile the program to run on the CPU. The target "ref" is primarily used for correctness checking. The target "cpu" is under ongoing development and has more optimizations enabled. Additionally, the "cpu" target requires MIGraphX to be built with the `-DMIGRAPHX_ENABLE_CPU=On` flag. Specifically, ``` CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIGRAPHX_ENABLE_CPU=On .. ``` ## Preparing Input Data Now that we have a compiled program, the last step to perform infernce is to prepare the input data as program parameters. The first step is to read in the data and store it in a `std::vector` we will in this case call `digit`. Next, we create a program parameter containing the data stored in `digit`: ``` migraphx::program_parameters prog_params; auto param_shapes = prog.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { prog_params.add(name, migraphx::argument(param_shapes[name], digit.data())); } ``` ## Evaluating Inputs and Handling Outputs Now that everything is in place, the final step to run inference is to call: ``` auto outputs = prog.eval(prog_params); ``` The output layer(s) will be returned and stored in `outputs`. Our network for this example returns a single output layer with the shape (1, 10). The index of the largest value in this output layer corresponds to the digit that the model has predicted. ``` auto shape = outputs[0].get_shape(); auto lengths = shape.lengths(); auto num_results = std::accumulate(lengths.begin(), lengths.end(), 1, std::multiplies(); float* results = reinterpret_cast(outputs[0].data()); float* max = std::max_element(results, results + num_results); int answer = max - results; ``` Other networks may require alternative processing of outputs. ## Running this Example This directory contains everything that is needed to perform inference on an MNIST digit. To create the executable: ``` $ mkdir build $ cd build $ CXX=/opt/rocm/llvm/bin/clang++ cmake .. $ make ``` There will now be an executable named `mnist_inference` in the `build` directory. This can be run with or without options. Executing without any options will produce the following output: ``` Usage: ./mnist_inference [options] options: -c, --cpu Compile for CPU -g, --gpu Compile for GPU -f, --fp16 FP16 Quantization -i, --int8 Int8 Quantization --cal Int8 Calibration ON -p, --print Print Graph at Each Stage Parsing ONNX model... Compiling program for ref... Model input: @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@%=@@@@@@@@@ @@@@@@@@@@@@@0+. +@@@@@@@@ @@@@@@@@@@@0+ .. 0@@@@@@@ @@@@@@@@@@+ .00 #@@@@@@@ @@@@@@@@@% .0@0 #@@@@@@@ @@@@@@@@@- .*0@@% #@@@@@@@ @@@@@@@@@0+#@@@@@% #@@@@@@@ @@@@@@@@@@@@@@@@@* #@@@@@@@ @@@@@@@@@@@@@====- -@@@@@@@@ @@@@@@@@@@@#- .0@@@@@@@@ @@@@@@@@@#. .* =@@@@@@@@ @@@@@@@@% =#@@. %@@@@@@@ @@@@@@@+ -@@@- +* -#00@@@ @@@@@@+ =@@#- .#@@#* .@@@ @@@@@= %@#* =0@@@@@%--0@@@ @@@@@ .. =@@@@@@@@@@@@@@ @@@@@. *=0@@@@@@@@@@@@@@@ @@@@@@%+=@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ Model evaluating input... Inference complete Inference time: 0.022ms Randomly chosen digit: 2 Result from inference: 2 CORRECT ``` *Note: the actual digit selected and printed will not necessarily be the same as shown above. ROCm-AMDMIGraphX-46524e8/examples/vision/cpp_mnist/digits.txt000066400000000000000000000172401510465702400237110ustar00rootroot000000000000003ŸýŸ20îüüüí6ãýüïéü9 <àüýüÊTüýz£üüüýüü`½ý§3îýý¾rýä/Oÿ¨0îüü³ Kyýó2&¥ýéÐTýü¥²üðGýüÃ9üü?ýüÃÆý¾ÿýÄLöüpýü”Uüæ‡ýº Uü߃üáGUü‘0¥ü­Výárîý¢Uüù’0U²áýß§8Uüüüå×üüüÄ‚Çüüýüüé‘€üýü%|ýÿ?`ôûý>ûûý>DìûÓ<äûû^›ýý½ýûëB Íýû~hûý¸PðûÁ ýýýŸ—ûûû'0Ýûû¬êûûÄ ýûûYŸÿýý0äý÷Œ@ûýÜ@ûýÜÁýÜ dz!—Ðüüü’(˜ôüýàÓüè(˜ïüüüØ%üü<`üüüüÙ%üü<µüüܧMüü<€:düü<üü<nyzyÊü 5³ýýÿýýä#6ãüóäªòüüçuNüü};ÐüüüüW‡üü´Ëý÷­üü¸B11ˆüñj5ÈüØAH£ñüüßiüòXIªôü~Y´´%çüõÍØüüü|Ïüüüü²t$ ]y&+iÿýýýýý®+‹àâüýüüüüüüž²üüüüýüüüüüüü;müüæ„…„„½üüüü;âüü¬UóüüX½üüü[Ô÷üüüÌ }ÁÁÁýüüüîf-Þüüüüýüüü±-ßýýýýÿýýýýJ{4,,,,üüJüüJVüüJK bòüüJ=·ü\ïüüóAÐüü“††††Ëýüü¼SÐüüüüüüüüýæ™1üüüüüÙÏ’-gëü¬gCè'>Qx´'~£™Ò(Ü£þ¢Þ£·þ}.õ£Æþ8xþ£çþŸþx£þØŸþCV²øþ[ŸþU/1t–ñóê³ñü(–ýíÏÏÏýþúðÆ[éúw±±±±±b8fþÜ©þ‰©þ9©þ9©ÿ^©þ`©þ™©ÿ™`þ™~ˆ¯¦ÿ÷$^šªýýýýýá¬ýòÃ@1îýýýýýýýýû]RR8'Ûýýýýýƶ÷ñPœkýýÍ +ššýZ‹ý¾ ¾ýF#ñá lQðýýw-ºýý–]üý»ùýù@.‚·ýýÏ'”åýýýú¶rÝýýýýÉNBÕýýýýÆQ«ÛýýýýÃP 7¬âýýýýô… ˆýýýÔ‡„&Þá“êü°ÅýüÐ&²üýuA9üüýY&ÞýýOƒü³ÆöÜ%Oýü‡ŒýüvoŒŒ ¿ÿý8rqÞýýÿLüýß%0®üüòÖýÇ müä‚&¥ýé¤1?ýÖIüü~²üð”,×ð”wüüÅüü?9üüŒ‡ý®0åýp&Þýp‡ü­0ãüžâêÉ 9üü9hðüüýéJ3òüýüüüüð”K½ýüüp?sy¢ýýÕ?kªûüüüüúÖÀââñüýÊüüüüüáDßüüüüü''Aàüü·ºüüüõl5–üüÜFòüüÞ;²üü¹üüÂCZðüÂCS;yüüÑM÷üøjýüüf†ÿýý'·ýük füý£ ¨üün)üüÙ(›üÖ¥üüj+³ü–'‰üÝ'CüO Ëå //_þ× -š¹¹ßýý…¯ÿ¼nýýýö¡äýýþ\€õýž‰0éýé‹þß$ªþôj7Ôý¡ ²ýìq›ýäPßýýmýýýþýšnýýýþ³&«þþþ³«ýýýý²{þýËœýÈ]ýþy ]ýž@ïýL Ûý~…þ¿lêþj„ý¾Uýìš™ý©ÀýýMpýýþì vó¿q7”ÒýýqW”7Wèüý½Òüüý¨9òü¾A ¶üýt`üü·\üüá„ýü’×üüO~ý÷° Nõýèü°$Éüü© üüwÅñýüûMçüýüüüâãüç7ëýÙŠ*Àü>ÿýmGýüýüGýüjýü-ÿýÚü8`ü½*¸üª “ü*ROCm-AMDMIGraphX-46524e8/examples/vision/cpp_mnist/mnist-7.onnx000066400000000000000000000635261510465702400240770ustar00rootroot00000000000000CNTK2.5.1"ai.cntk(:²Î b Parameter193 Parameter193_reshape1_shapeParameter193_reshape1Times212_reshape1"Reshape2: « Input3 Parameter5Convolution28_Output_0 Convolution28"Conv* kernel_shape@@ * strides@@ * auto_pad" SAME_UPPER * group * dilations@@ 2: F Convolution28_Output_0 Parameter6Plus30_Output_0Plus30"Add2: 4 Plus30_Output_0ReLU32_Output_0ReLU32"Relu2:  ReLU32_Output_0Pooling66_Output_0 Pooling66"MaxPool* kernel_shape@@ * strides@@ * pads@@@@ * auto_pad"NOTSET 2: º Pooling66_Output_0 Parameter87Convolution110_Output_0Convolution110"Conv* kernel_shape@@ * strides@@ * auto_pad" SAME_UPPER * group * dilations@@ 2: J Convolution110_Output_0 Parameter88Plus112_Output_0Plus112"Add2: 7 Plus112_Output_0ReLU114_Output_0ReLU114"Relu2: “ ReLU114_Output_0Pooling160_Output_0 Pooling160"MaxPool* kernel_shape@@ * strides@@ * pads@@@@ * auto_pad"NOTSET 2: w Pooling160_Output_0 "Pooling160_Output_0_reshape0_shapePooling160_Output_0_reshape0Times212_reshape0"Reshape2: ^ Pooling160_Output_0_reshape0 Parameter193_reshape1Times212_Output_0Times212"MatMul2: E Times212_Output_0 Parameter194Plus214_Output_0Plus214"Add2: CNTKGraph*›P "€Pª»=c³ø=RÌ®=Nû=†?ʽ†¡å½š\½%ŽÆo¾?È=ÝoF½•- >]>Iõ½”U>¢•h¾|¿Ø>‚Wp=ÝT+¾9ݽ¾]ÚD¾ê*Á=}á¿=êù*¼d l>|¨ç¾±ä#;V½K¼Ø)=OÌ›¾3«“<øéÆ<ï“> />1\˜½{»<¾Å×½¸Œ§½ûCu=³>ø961¾t ÷=Ê|Ü=åsÁ=Á ½ôeÒ¼(€ß=ïXª½5©=Ãì3¾14,¾’S×=E2¾–ƒ¨½óí> å÷<º)#¾i_L>xïνr4ý¾æ¿¨>"Å5>ΠÒ>lpü=ý/¾¾î¾ñ¾¾ßü«¼­¾"çF=¼iÃ>Í‹m¾ÖÐ>í¹<"vŸ=2‘+¿Ñ“z½þŽ?=ê½j©>(ÈÔ<ļ²7>¶ ½K{Þ¼gl>6V:½ß[=(þ²I|2¾Ls)>/½¾Lç1¾±ë½ C$=yD>í%.=ÖÁ'¾{Bâ>† =³ྂ©J=«mq¾­:ɽö™Ï¾Àü¾¾¹‰y>M‡¿ ? #?+ßÕ½0Y8>‘ûè½h"½S«å½9W»¥ N»xÒ½2û›<õ¾“½½ì€½Æÿ±=äÑs=·8>§ïÜ=i¸H¾W<££=C…r<€<“½1âÚ=‹íz¾äy<«¾˜L½ªã"¾?Ћ–½y?¦%>¨8M¾ÞZ°=㑈¾õ ÷¾1¹%=9 Ô¼VY™>EÞ^¾@9¦=™zˆ>P’”¾•ˆ>ÀìO¾Çþ½‘yÃ;]¿e>jÑ >§™å¼–Ô¾Tçº<ÖW…=ꋾE&Ñ=&‹ ¾ÓËe=æ ¦½T é;ÿ¥S¾ªÕç=`u8¾::¸>cTk¾ûÄþ;àÁY¾ÒïѽÀº=¡îŠ>W†¾`§²=–˽< >ÁÊð=ÜIǻĽҰ=Ì2B=ó½ü>§=z¬=Zú >½0¸µ;RKž=þÉļØB—½|x> M>•¾"lÀ¾^Áƒ¾“T.>ô픽&¸ß¹Ÿ½õ+>͵—>9)5>pqоpŒ7¾þGù¾ ??2½èÕ&½¾Ûg=†"‘¾RŠ=š¯Œ¾/4—¾q£>+U̽ºÁ·>®e ¾¢±x½„¨¶= ¿ý½#{ö¾€Ç÷½m•š½¯Î? –%> =02T¼Eá=܇9¾ïV¾JÌ.>ŽuU>nßÔ=¤æ(>UÀT½ü{˜>òbõ½%‹5¾r¾ïD>»#³ ¾ú˜z½Æ<> %Œ>.‡¸½÷qò½Eß<¥,«>ÆÄ¾¥â“¾ ‘‚¾ê8ǽÆè?ø#•¾\/>>dÈn¾¼:>Fñ)¼òE½B—¶½…†%?l+>gØ>ŽÈ½(N[>Æp‡>å¤\=ˆoB¿vÆO¾âY²>·Ä ¾Wß’>Fï[>xÊÉ=¶0v¾é^#>©6h¾-•Œ<$Ä:<ød3>~L=M•ž=d;½-^È<ƶR½=ú¾µÄ†½nNÎ=Gd‰>È#½È=ß*"½ßÑ›½ç©Ú¸bâ=²¶Ÿ¾m¶=W”¾/È¡½L~>Éí'?`F”¾€S±>ïÚ¾I€½.’)=3½ô¾ZV=]î¾{9<*Ð+½Y·Ï¼Š5¾¿…G=ÏKé;ú½Í÷½™ë=6lI¾dÞ>ÿHˆ=wCÚ<’›:¾ÀM¾ò“}>s&>ºêN¼ >¦´”¼Ù²=¼òè= ±ª¾¤=¾ ¾Í`>ÿ,”> €>ª$2>ÖÐ=v%ƒ¾¬s彨3r½‰5=×6'½Ì µ½ÕZ¼¬~=ÔÓ½#.Ž=–c>ILˆ=„*¾~ˆ‡=Ë|(¾#(Ñ>Ñ2€=‰H‚½XZ‘¾B‡;>+ý>hï—>׋¾=ñú»OJþ¾ã¼¯>ê»Â=7ܾMºE>Qõx¾}?d>)õ8>nN®¾Ÿ€É<,ö½ÏǪ>`µs¾«lû<2Ѫ<‹l½õWK¼>ê¾K™¬¾ä þ>¡ðÎ>Cˆ¸<½e¾Z;_S£¾²ôò‘[>(ˆ>ýKà=Bð¾*ŧ>c>ñÁ¾¬˜,¿ 5Ù¾Yá3¾ˆhc>òž?)>ÿ>oõ;¿gg”½¦!½U6?¯ôŒ>§í9¾Ò0@HG=óŒD>̪¾{ÓÀ= ¾ÔžC½Ë€ >†é=(¤»jóFZâÉ=ßñð»Ð#»½çŽ>¾ÄÙ¿=?%a>ÞÍa<Ùß—<¤?Í=xŠk=ÿ²=(µ„¾ŸQ=-„=‚Ûš=°\¾ep=¾{щ>ÏÝm¾˜ßu>†ßZ>‚¿K¡½ÎÛX>u¥.>¨H ?eML¾ V¹»_éG¾!þ½ŠfŠ=¯¢ù½"n_=!š¾èº ž/>ó.E?ž³Š¾Nq= ó¾œNÁ¼F¶¼B«o¾í0¾-SÎ=Ð+¾ñ ¾Å>V) >O«½Ê>54ß=Éi཭ù—;ñ0¨=×bi½‡½2û*>Ïî½$x>¤bí=(¬=s3¾H/Ž»i³š½È3š=¶>ƒÕ•½ä(¾™QE½^øé=–)¾Îh˽öK1¾ÕÈ=PnV=Oë=g7‡9šâ½´¢=Uƒi¼¤K7= «Ä½p…½@Þ˜<¾¦Ø=ÿàå=·a1>5G†½Uì=O}‡=ÂïÒ½5ß:½»ê¹½t¯=8ñŒ½ÖÔ¨>îV ¾÷€½Í'0>NÄk½M¶¾‰Ž¾Þšn»êŠ,¾ª@Í>3™×=#9ö¼³Ë ¾¿íÖ»`Íš¼»7½¼ ðT¾Âbî=ÆE<½P¼ø=;°ñ=Qå¶>œ A¾å¾$4]>É‘½Íæv½Ænj¼—æÈ=Uݽv±ý<_çǽ—ÄÌ<®Ív¾¡ à<› ;0Oô=цÆ=U£>o0*¾TÙ'>%ü~¾_¨="¢Â=Î~‚=—QÉ>Éj̾V8B¾NQy¾Í˾>Øê´¾Àe®;Û8½Šo=´?†¾Ú;r½D¶¾ÐîS>[Š>™¨\>·¾ð¼M̾1¶`maO¾˜©Ž>!Û¹E¾Z¿">ºL½¼'•æ<ܧî=óš’¼7hg½P磽Q—½Ð'Ô½ºÇ.=ë°>[¶¾1„=µ¾½î=$“[>^“>ÊS¸¾&ë ¾¢6´=z­Î¾ÿßI=,X…>µÌ:½€M®½õÅ>ê€>øMö<@D¾,a+<û6(¾¶‹Ï=,˜=ç”’=SA ¾ºXD¾©D–;ìòS<G‡¼ËŸ2;+~¼¿ë=…LŒ=¬ÓÓ½öí=Gx<¯`áÙ/Ú½è…í½+ ¾:O¼RnÕ=|ᓽ¼ß.=볩=F‹<,ùƒ>/9â½ÿ¾ž…´=Ül>À%½Ì¾™½­¡¾9ƶ¼§Ì¾‡”½_*"¾‹Z!¾ꇽLµ>øá;QÐ=r‡¾‡™<´L©<{ÈŒ=€N>0+Å=[«ñ½Gxö=èm¼¤‘O=Œ¤ß=_Y.½íæ¼Ècc>JS> X½Ä@ÿ=†Â°=IþJ=»F>,q§¾U=R¾@üs¾ Ǿ¨y=gÂG>- £=ƒn¾ ŸN¾/ÿ ½­¼ÒS?D ¾Eº½>ؽ«Í”>yóe½^-m=·©½#מ<ç­>Ÿ*½ó3¾_=>~´n¼k#ñ½ñuŒ=ÖÈ.½Â)>Uk‚=pE¾ÆDè½kFF¼ÕŽÛ=š>q¡ÿ½v»½s –>I†¾¨[½ ®½Úßø=ò¼Š—¾ø*§=ÇÄ>‚ͳ='Y†=ÐÐ>÷X>ÉÇc½C×€>.ɺ­ž8¾ ˜(¾ÃÀð½;í½ËRÛ½{,*>Úµ=¼ê=%?F¾¾úTe=¶3 >Gý¼ßJ¼€¶¨=p…=¶št½°´¾ø>â z>”ž+¾fÖ¶=da=Ùì¶½/P<»ºI ¾—zl¾ü=C+¾öè6>å3Á½¶Ñ™¼®S >šÿ¤¾lÕ^>ú©>­„ó¾Ç$å>Z¡¾Å[¾A:>Óüµ½ã)>’'*=A;>®Î;;¿ü›¾I>æ’Š½’Æ*¾µú ¾:1²=¦%½/}½FD9¾ %(=˜’þ=¹§-½n|‚½]ã ¾¼y¸=žh>£¡I>‹Žø¼&;=ų†=¿A½Q„¶=ÆG%=MV½\„¾LçD>†® >L@¼LF=eá¼&N¡<"Jœ½-L'=ØÎ ½"v½FL½ïõ¾šÈN=Èåw¼ŽÝ=§,Ë=dß ¾Vò%½ª;º½Yq>šô„¾ëÐ5½&/ì<T=¯ã~;(« >~qÏ=_eT¾”À½I…‚>m;¾ž”Á¾ë!>ǨÒ=íX_¾;ÞeºÈî½èº=&?ZB=>b]>²½‰›=›³u>™=ç¾y<;ŠÕ˾̾ç¶Ì¼DD=ñ]*½E{†=:ø½÷·?=ˆ®M¼œí½ªÊn¾ý¸¾»S_¼’¶•¾Ý;ðß»¾¾A¬>ØNN½¾$+½†…¾c~™>æCà>6zt=e„¼uPþR`¾Yç€>~Ý1>±®2¾¡f¾ï+9>Þ*·>†g3½¢/¾>³ç¾ýÆ?>ìÃ>|>ÏL#¾mó=޼vc ½Ê3¾/=ß= ˜™=Þ>Ó)½Ί=‹¾¾¯x'>Ri>‹ä?=ÔZô¼ýÔ=>@ȸ½̆>q¼‘¾é%?èj ¾OM„=ÙŒö<šÚ>Wc¾$è$>o•u¾!Ëe¾e®i¼!é>j¼ÞÆI¾CŒ¾{5> é“=À¯ô½²B“½½Pß=K`3¾ÌY¶½=Ò/> ôs½›…é=˜/)>™´¾ < ½pð½þ[ ¾hõ#¾:‡>0ß=ò$·½œk1¾5‹^½/Ž;=ÏG§=ÁO®=”F¤=w¾˜¾>¼Î½Uq ¾ªŠP=ƒ’L>ñÄz¾ß ><É;5Tÿ¼dŠø=è!B¾‘¬*>£Ÿù<+Ä>ÈX½E7×<™êh¾™ ¾ý„œ>,k¾½1=¾Œ“Ì>üb¾wK0>ÆÅò¼/8á=zñ¸½üÆa»i>–QS=/>Y §<è'Œ¾ön77>M0Õ¾˜pÂ<Â0¾*Ÿ¿ƒÌü=[¡œ¾h÷=¾BE>ò>Q´Þ½ÜöÇ;âJ>½b]ù¾€)%=‡l­=7…?ªE†½nê>w$½D4]¾XÇz>óB ¾´UÖ=æ 8?ì3¾‘+‡¾æZû=u(f;ôE¿ÌÊ‚=NÑð<¬>òŠñ¼&Hß=¸Êç=`3O¾bÓx>€iß=šz¥¾ÅT¾hÿƒ½×»L>rø=\þA¼kϽ?W±>bŽ’¾ t‰>ù¬V>ÓéC¾[>©>_d›¾Šü)»¤$¾ÃUs>“¶{>sM>æ½³G¾65è½Ñ@¨¾BAZ¾w3š½èQÅ>yir¾RêÓ>N²ò½gé%=a[p=ˆWÛ>®>ª¾â­˜¼ìi™¾~"¸½ÄÔ½`.Q;N¹¾ è½Ç{ѾHg¾<Ã37¾š¤>ÿ±‚¾LKã=ÞCÍ=ì|>>vJ;@v>TÓ½òFk>퟾é>1T=Ùk9>p'œ½Žƒ<¹(y¾è|›>?-Ú¾„‹>‘"=¦F<²k¡>b®¼ÓH=ÊV¼9]=ÓµÄ;ê:¾ÊÏ·=;„ì<½1_û=„Ê~='èß=´gº½¹->wb¥¾JÙ¾âу>ã§ÿ=ƒû[½õ Þ;J;î<n¼âË;>[O<âs½_Úç;F%>6§S=?©½zO¾e0+½»\ÇRID»8ÈнQŒ/;@¢½a·Ã='v >EëÔ=(>QK¾vM>÷¦~¾±Ž‘<8hÛ=aü>$‚‰¾jDø½ß ¾ UW=¨î‰¾óEܽYJ>\xU>ë÷@>ý™ë½(Oœ¾—Cï½}²¾WN?~Iñ¾pn¾ÖV>¥ƒ>ï<9>±Ä²¾oƒª½ã¨½HqO>Ù?/>Þ®4¾îë1¾†iŠ>ãÕ¥½öö‘=µïæ¾’ô‹=›ò}½  ‰>üQ½=Ý8=Øè.ö=yV½‰£d=&Ï“:ŽuȽ$·=Î0v>ß§*¾Þn'¾6E8>¬¼8Öç¼,»¾eß=­ ¼ðÕ>$YS¾m°¾Öš >¡»•ºŽ¾ A”¾ô=û½Ù¾¾·Gö=¾¤¼0ó<ð½$ß®=êþ»AÒ>¹J¾½cÝ&=eÙ„½;ð‰=^ŒÐ¾á5>çBM½Ðã¾Ñ4>ŠXM»q«¿ÍÚ´>râ¾á”“=m¢®>h^€¼¥ó›<bá½Pƾ E¨=t¡>ùî¢>· »¹ =6Æ–½}zm¾‹½©=½7©>¸ï½‘û ¾Êxð= àò½Ûy>Êr+<žô&¾†ŒË½Yѽu±½"ÔK¾‹\=>Ï+:Wè4¼óS>½à9>pU¾þ/¼¦tá= ᤾„a½Îò”>0KM>ª½—¾”ô“½ågD>à¾÷¾ÏF“>Ðì>m%>—£½$¥=³/¾ÝËþ½Xnо†#Ç>À`³¾…g˽$.Ñ>¦á'>8‚p>pœ¨½‹o¾Îšv>/T=ÿÞˆ>Yò˽­¾¥ë"¾^,<=?ó=ty¾Í¸¾¬¡T>öû<¾éf">Ç*ÿ=ÎÐÙ¼ºPM=c¾¨<ª)m=¬Öƒ½Ew¼X,>ócÖ½Ûw¾&%¾$Šå<£(.¾D›!>¾¤ö=Yà(¾a¿¹>€ˆ¾’¾FÃ=;ª=¦íÙ¾nî¾½'¹Š=¥Ô=%\µ>Z)d-¾þ~¶<®ßà½Òo˜½X6¿¤ µ>³÷=qhR>4°½™˜>q(r¾Å¾½¦¾ªÞ=ìò¾ “@½ 2{¾nóì½™÷;¾$Ó—?¼PW¾•| ¾!­½ð.Ð>ùÀ,>`{é=ìü=éµ¾øfD>Œv=¾´O†=M>ƒ§½¢VD¾£­½°·®½ P¶»00V¾¹B¯=@°R;pò=S×=sõø½jþÕ=Ê!À<Ñ[á½`ap=‰Hc½y N»Pɽ“+g=ªQª½„ñ‘½©¾.úX>@ç ½úC>‹ >†’Ù=Ø„r¾+Þ=†kŸ¾äÃ->"Çž½–°?˜%ñ=gp;>C¡/¾øLW>E蘾#ŸÂ>JzT¼¯j”=V.‰¾Æ±N>Ò[A½¡ þ=Jý‡½>¾Š=I[†½oùe=·½¤-=½sÐä½NU¿»“ÉB<ë뫼üB€> ‡à½Nô¼cÛr¾Éõˆ»º¹W>öÒ½i®½{9_½l2>9ä†>ÂÒ ¾êé[¼Ëw ¾ëûŒ<á=Xšù=ß§¾àô=ê$=ùZ>h¦6¾Ÿ%²=8ó¾Ck½ïE=.3è¼ÙR=;K]½ô^#=€QF>ÿͰ½áHÃ=šo¥½~[>Ìfß½oG–¾Y¸½Jûá½Ïj¦><à=´2,½=>Ëü³½»õQ½«»=8Ç•½AkܽìÖY>&“^>;Ö®=¿vw¾€ä~<[s>ÿp>=º½Ì¥>«A6½=© >ξ&©H¾4€§>Bð½¾Ë¨¸¾øÕ'>»J¾òò[¾ú\S>³v¾Š E½i(ó=Ἄȉ=â§o¾kî†;Øs²¾á‡¾Ñ~¾޼£e+½j)Î>UT5¾Uv‡>PË5>ß§=œb/½wWm¾&rr=±Ê¾áHi°—¾†È¾">Ýi>ÛäÖ¾€Ž_=ôl¾ˆ†f>uVª=TM&¾ö?¹½AB=Õèþ=½w³¼¨Þ¹½áO¾GÌʽú<>)˜™=;~¾¨ÃŠ>Wñ½"t>ñw½S¾›=É1¾9>@Hœ¾LÄÜ=–eÿ;9O²=\„¾AS¾ÐJ¿«Øf>4R>£‹>†=/½á^?¿ÄQF=*q/>þe„¾ÛÎE?#'̾/ÃþŸe?Ä‚¾Ñò5?¬¦€¾À›¦½Ø/>ï”y½¢çn>˜ê<FÁ½Y^ë½¥ =°C>Úè½®hI½†OŒ½ûGT½5„4¾c>¾Uµ½¤å">Æn=Ù©=©‘6=ây×¼[½½¼€Ý=ôÊ¢½"?V=w¯i>Ælœ¾SÎÜ=5ç`=Ý:Ò>ÉQ9+•º‡e-¾åò:=rA¾jÎÆ½{íÚ½œw‚=t‘I½…½¾¤¾€CÄ<ÖA²rX@>}Cø½yu=Ѿ“ün¾xö•½7S‚:¹ y¼÷BK¾¤¹A¾Zu >ئX>«º´¼Ÿ‰¼dò >éÍ=«Ðè>Ò¥'¾Zj¾R\<‰„Z¾ä²Ç½øÒN>D¢…>Äq\>ió>7Þ¯;Áhú=LŽþ½¼ô‚<²Ö¾Q<®=ÞJe¾G®=3k7½˜Å(½–! ¼J“ܽïé½È"¾2d>@«ì½‰ô0¾‚V™¼:ƒ‚¼NN<+È ¹*>’}X¾¸p<»qF¾^]¾¸.>Ý™=v¿ó½‚3˽g„=CŸ><Ľѽ‡æ>±\F¾À€t>h.¾¿ Ü<æbƒ>7…K¾­¾k½3®Ù½V½7K=9k½1¥½a>av©=T¨=×þ f±<Ãû£½û ‹<ŽH5½(L›=£¿ >¿o>L]£¼“â‰=qÕŽ½’zN=ýÛ=53ú½¼Ž²½s‚½êÐ->@/â½ä×q=ë>‡k¾ÑÄ>É*>ÆWœ½d%š¾~X½¼l>Дe¾MDÞ½ ©A½ÁK >N2¾x—>œ§½îÉ6>Ê™q>c„8¾Õf=f:>gµx½®Ä=À´«½ ¼¹Ø½á&ν3—= e&>ßq&>Δ=kðˆ¾é7‰=‰UH>]#2>Áª¾è{Y¾ù °<ή%½0®©=rK½ç6-½_—[½ÈOp>ä>~H=Ë¥s>p<לнޭ=Œ‡Q½‡ÓÐ<òÅ̾?°>)˜½£5É=7©>ÎAk¾æÏ=>Æœb=äâ¼S¦À½ôYâ½å„ª½­‘)>xrE>Ãð%¾Q¸‘¾Usc»vüß½è‡>‰Á½¿¿½Þ+¾„(‘=Ñ …>Ûq0>§’‚¾³:h½7Ε=#_u½[„¼½Õ×f¾ (¾Ô™º*¨ˆ>‡><ü¤>{¾Z-(=+Œ¾Ôfï>K€¾¦m¥½¼ ¾€N—=ÔÆ¦¾WнZ†ü;΀¼r>×í>,ÆŽ¾pؾ{6½ÄQq>t*S½:“?‹K¿H<3¹0¼”Ä>®!¾QMÛ<Ÿi€¾_í>Xgõ»¨=î>N½-%¾n”>X¾´ÉÐ=ƒ°¶½⣽`"á;õ]¾°<ò½ã±=Ó¤ü½ êÇ>5‡I;¹K½f`¾»É>™ >m{ì={޽Â×½ö·V>a(켪¡g¾æ0Æ=4Áa¾®Úû=p¾\0=G©Ê½“°…=ãðY{]¼E*̽³²º½‚³ƒGL×½*mĽ}†-=ƒ/O½ªÃY¾6ñ=q_Y=z¾#MÛ¼9¿=ûÛϽ&‰Æ¼ÓM="9¼2Ù=¤¤= ¾†©“= b¨=-š¾½§ú¾=æ ¾š+ ¼m{&>…:=E»½Íp>¾;¢–;éüS¾ì²h=@æ&>ºh!¾áͽSŒ=ú¬«½~=Š…¾7;¾ÛZ¾Ý¹b>Dk˜>Pw>*&Ý<'A¾OVÕ½é å=W6E>þß÷=Še~;>+½/2¾yÀÏ;èþ½9\½'¹s½Ü¼ =2!Œ>%‡Ý»íɽ_û¼^sÏ=3}=J`®¼-ÎS¾¿($>ë?? Ïÿ={ó뽕÷f¾ìk¼V¦º=òø¾W0V¾Jýx«¡¾Gœ!>Hà ¾eâ=}1I:†v3>p(“>?%>]‚?¾F®½i‘‚¾¿ÍÍ=vÖµ½¶°‡);¾èÕw>æ…T>É3½^c„>έ5>ä >扣¾C̼'O}¾'èi¾ M–> *"¾’2”»N“=2³¤>Ì’=¿‹»É,Û½íÀ,>†ïuºûxŠ>äÛ½8ú‰¾ZH¿½3µ¾*š¾…U´>Ö‡½â”¢¾Ý3i¾µ+b½àó>QJ¡>ó½ï;ԒžC:νL >&Éþ<ä®=j÷·=}ϼ٭ȽΪ…> ÓI>p>û»?ûü>D‡;¾$ÿã=>㸽B©+> Ün¾`¾x©J¾æ ù=ô‹=I!>ßZa¾b>½Ñ©¼Í¡§:…Š>]u€¾)}<ϼ°>’V±¾ü0«<Æñ>U3p=Ð>=8´b>.þ½·#Ÿ½–!™½˜çÛ½„˜½Ÿ¢½ ƒ=’?è=£Ò ¾.t*>æX=¹nÜ=_F'½^"»â>c,¾Æl‘¾æõ¶>í,>þ:>¸°>ï£8¾@áy¾þ‰½Ïí>½âÿÁ¾q¼º>jª´=€–”½ĽZþ;=”Û¾>q¼½,È>-‡Ÿ¾Vqë½u>è§=Áé¼=|^¤½÷õнø˜¤¾ýX>å¾äµp¾\‡7¾ÕXy=¯/½“Z^>©7S¾GâM>Xb¥=e>n"¿oE½)dr¾F«¬>ƒ>ª ˜>°þ‰¾ýO=|-u¼®á ¾¹g€gR«¼ºà=j‰¾ƒ¹+¾d>fðG>ÕݼTl`¼0«>§Éá½$‰¾³¤.>Ääž¾fp¾˜îI=‚í"¾,ó¼ ÍQ=~#>Ã×½´X¾ÜÌ,¾;½RB€>ÔD»qC6¾•\H<”…k½:â½õŽÒ¼3““=è>^ܽí·½ª—¾hñj¼ æ½$À¾>“ýU¾t§>ÜãÞ½°iS>×¾ ¾ µ¤¼/Y\=á=ë=k s½áÀ’¾Ã J¾º„>Z88½ƒ>@½m Õ=fíÖ½u ¹¾¤ÔоNj?Yâ¾b9ò>¯ºl>4;¬¾ÅÏ>ší!¾Ö!ǽ£Æg½HÁ¬½P’½Wï>Jã9>]++¾þªÁ½çc÷½ÌPš<÷Š&>´ïнío=_M0½–ë>D&¾V>¾t½t=) ‰>8³ã¼±¾U3>|S¾<ª>Ÿ®.¾šŸ=Ï’¾šDú=è³½½¬ >áÛO¾9f;¡ˆ@>¶céÈQ&¾ÿ0Î=k ¼‚=‘½a‚,½¬0ò;¼X½8í½C¾yu=K¨¾­ïÕ½ž )=ëï=èõ½s@4>¦ƒ3¾"öƒ>È),>•9¼ûùD¾N¾'>Ãgr¾Ó–Ý=šFX=Aõ¾|ÞÉ>{só»D÷½S¾öòǽG=:M0¾àÚ[= (‰=Nšµ<7õŸ¹7D½t½[‚ˆ=ž%=„“½û²m¼ö°Ê½wn½ïz޽cå½$Dà½³Ë >éÙ=x .¾PÉD¾rKŸ¾a›ˆ½NJ‰>•”>¾¥šË>”¿<¤?l=‘}^>`—…>A`ˆ=¾7¦;óú¿©l½ÎH>y]Ž>Ú7=࿾).N<µ(”¾U‡>ºõC¾Ø;ƒ=¡Ôß>g„À¾Iñ½­õŠ>ú =°wÙ½„•j=ù’ͼ6V½S¢É½÷¥/½Ñ‰=™ñž¾Ð=ï$Ê>ía¾OC©¾£Ø„¾àÒ¼/@¾iš$=©„?C½w•=ùχ½y6½>‚ï°=^¾[·§¾tBP>Š*=ÄÔ½Gƒ>‚¨…¾V˜>p¾¾‰²>xÕ̾ekü%>ð‚³¾‘Í>&:¹»žû‚¾õû¼9„ø½ëÔ¾´r=p™²½@¹=5Õ¹ý¾çÉW¾g`ˆ=u?&>æB…½Uã¶:÷F=¾òüæ½=÷ͽXÉ=j!M¾'¶;3Nº>§oæ=¹\í¾'œ{='U¿¾Ó?úà>wò½,X¾ŸÎT>_M‹¾.Š>¨,c¾‚¾p>ýžçù=B Parameter193*šd"€d ãF½\eº½¯7×»1|¾ß=φ¬=Hn,¾ƒëоMÛ›¼ˆÙ=œÑ"=î@¼1i½{¾ûHæ½?=‰‡I:‰·ª=Ed9¾*³ò½EŽ];ª€>CF>÷S_½57¾ÝÙ¾FоŒÖ„¾!÷¥=í)´=<<ïj¾#ƒ½x»²<¥GK½¦ˆ->Dq>û5Ÿ¼F ¾DX/½>B‡½›>>U×9>ù\¾öœö<:4¾o©è=m#×=²R=²l=gɾß1 ¾ì‚¡=P•î=r ¾›ÿ½òT¾Ð¾ü¹{½³­½uûA¾jìa¾ËÃ]¾‡±½¤í„=­Â„¾‹ÛÔ½ {ª¾Ÿá ¾+Ÿ½x²ç<ík·=§Þ½ð?¾.e¾ØQÓ=gþB½Ù¼<°¼NëµÔ=FL´½¾ü`½Œ=2gØ<ûƒ¾æ«‘¾"¡U¾¯’<ÝŠ=<¸ ½úô›¾í¬¾hÎ*¼„zð=÷->(W‰½~a=þ3>,†½mQ¾Íž¾÷Ö½ y½="”™>Öfz=Ce¾Ù÷^¾<—±½ƒx = ‚ù=Ùܾ³º¾`µØ¾c½½ÊY=h'Æ<–§ƒ¼™ö½U&¾Ÿ"ƽ_=½’]<ÌN>_|¡ßpç>ø‰>¾­ÿ½~_¾Í½¾â»›½P׃>ðÒ=O…=’‚×¾itŸ¾Ï„½«?=I…ê=9Z½Vm ¾ý]ˆ¾Z.Z¾ï¿ý¼Ûš^=Ïž.¾/[”¾Ÿ§)½5|¾ _Ë=RîG> ɽ’}о]¾ºA¯½/•;ˆˆ]=zz?=Áñ$¾Ú´¾A`x=p:Rï4Q>Ó¸½){}¾•»>OÒT=@ļ_ÍÌ=ˆøi½¾X›½RLk½ÿâŸ=ûÔü½ã¾1¾Ìi™=Bà[>ÄÚ˜¼X>¼S!¾Ôò ½öj¼¨!½Ð >å€>C'3=† É¾àŸ½e“>G!S>lP¾ŒÇE¾ƒš1½sȸ=GH^> ¬½ßO¾ž¹¾ƒŠÌ½.Gáºñ'¼¾†Çž¾c/Ó¾õ¯½º¿¾Ýoª½Ÿî„½¸"ˆ¾_èq½U‹Ñ½8í=·j >‡½B"¿½¾â;—Ú¥<ˆnk>Ô¿¼ÓH/¾(¼8¾̽<<¼¾DV¼Îý½Ï„®½¬?c¾x¾Á(=¾ãKf½ç™Ã=ÎXÔ½3Û-¾›#!¾=Ùî:?FÈ;ò“Ä»ÑV>mÝ‚=\)>H¢>f}k½e6²=“F>=º >#H¼Ëâ>Ùϼ⇟½Wú½¬D¾’¯ >Fö<˜b’½w#!¾>£½š#Ç<Ö›×;¾7Pè=$s>$Y‰½´P¾yƼ»ä^>³Jv>¶®i¾ t ¼#ãH¾Çä¶½9·6¾™èK¾urž¾†³â½Æjô½Ä*=+<<凹½…\%¾†~%¾yÁ¿½ÕDÏ=èl>#«>¬‰"¾iFå¼Õî>»Ô=÷P>xYÜ=œ8>„ŠØ».ê¼ÿ$5=@L!>l-¼=çh>«¥²¼÷ID>:ë–=•fH=Ü™ ½‘º@½ij¾U½«®)=Lùž½ç±…¾|Ǻ½¹1¾;t¾Õ®Õ=`uÿ¼)ì‚»†UL½ÿº >às^>V}M½Ñð+½Më½!½Ñï <ty>/: >ë½6¼ \¾Ë`ä=ÏÇ•=$ʳ½}-I=?½ñ @¾üc*¾ãP½â”­½ÂÛ<*#±½Œô>¾h7ñ;o§ ¾êv¾½¾ŒÙ½©)Wº=ž½§æ=ÚçÈ=‡F"½"!>Þ>³=—tŠ>zŒO>d€T>ô#=Üܾ¨¿½sáܽhbG¾m¦Š¾Pp ¾ÒÚ¼L¸Æ=–Z¼aRw¼Òò7¾p½l¢½ˆùa¾…YÜ=ƒD¿t¬ž¾sÖ¾Ød,s/¾"´ >§hc>Õs¾Þ.¾Œ:_¾àAK> û,>N-|=[/½Ài‰¾’̇=¡u•>ÒÌ=›Y+>cÏ¥¾WŠ„¾Z·”½žH¾v‘=‘g•’›>çœ#>–yð=‹2>)t^>FµÉ¼}T½Ð«¤¼PÿÙ=úvì½ðÑ=.‚½=›î½ ¼¾딽X =«¡<.|Ù=ÀT©= >›—¾ºD¾P®•½ÅC=­N>GÒb¾NU’¾RÉɾ}1¾+YŒ½+.‚>ÓÇ=U–o½ÚŸ ¾éƒ¾êÚ‡>Ûå9=êø~=#Ý7>w3>6Nµ=ÀX½<Ƚ½ùîŸ=Ϲ>½Œ¬½ZW„½Oˆ¾ö"¬½@¸h<|=ä—(¾Ú€z¾;#½¾ÇÕ8¾ó»>…ÁÒ==¿Ì½|¾¿ŽÍ¾±¤=gPî=ÞüG= âS>~Ö=s˜»*w;(ZA¾Bw»=‚£µ½ >>½¤%¾ÒN½ª3G½ˆT½tü†=̳<|£¾4IF¾­Hо^LÁ=ªÃh>ØLÃ= Í&½!£s¾?k¾i:$=øñ[>è/Ž=.ü›>ßkн–#w½Djc¼”§d>c©ö=Í€½Ê1Ÿ½‹Ñ;½B‡½š¾¾6b¾_2%¾rY‹¾Ñd‘¾¹Î\¾= —½Î¦H¾å‹¾1h†¾VÄ>¾¾Ûf>$„­ óƒ½[=b¾‰>åö°>ùdQ>m½Ô¾! |¾ ÿÕ½‡7½Â Ü<¶P²=wÌe¾~j¾CÕ;”¼õ8¬>!;S=³£Ñ½ŒÖl½þ$¾“è¼?V¾¼ì²=¬CʽSÏ ¾åIP½O#¾af¦=(òo=¯g½$²=¼¼¾]>§ì½ì€§¼ƒ©)¾Ås©¾›˜e¾)•<Ÿci¾¼Ž=˜Œÿ=ËZþ=¬žM½U¾:hÀÇ=Ëá…>gö-=í»½56ˆ>@TE<1‚=ù]•>kØV¾(Û¥½ó¾Ô»Š<‰×<P“¾^¾ðRí½>ì)¾ÑG½/P»½éQ¯¾€î¨½Ãé¾™¿|=¨’ƒ½«±×=)„=®v$½Új+½Àí¾½w>=¼0D¾&ǽ™>Û¼¿¶¼„Ø;¾Ú=‡Ú`=5ug=rS¹=‡*•½¶ÿ&=ø‘<ÁŽ<ì®=h–=MO—=µ‡I>ï>˜¦½qƒ¼½­¼‚…=)`ˆ¯€‚¼ÒÇ?½6-@¾2ؽ'ÄĽi4w¾Þýá½£ÿ½×F¾Ø3X¾¸·½¹å½ïƒJ¾ãµé½ct½…-Ê=R°G»Ö?Â=Ÿf¾„p¾ñÛ=«é*> ¾šÈͽig½±u-¾…â=Ðþi¾|<Žx½Ð½Ñôѽ+oº½¦PʽŨ¾÷§½Ï‡ ½‘ÚP¾­¥¾o*f¾Ä «½rtÃ<{º$>ø¨!¾¸r¾h?¾ÇÞ©=Äáü;wB¾²Ü½8’¾ªë;½ C=Éß¾\})¾~ö=¾T‘k¾«Ž6½Ç)T¾½ËÄÙ¼£ö¹½³ì¼éZ¾.¾6êN½†t$>czÖ½¼<¾Yn§½–ŸÅ<Ùé¡>쨼m˽W<‚¼§ÜŽ=éÐ>FÑD>ź¾²ø¤½~B>«Û‚>Ù¨¹;†9‚½  À¼¢½é<ß&ݽŠ ½¹çâ;Tvõ¼L€P> Ó=ç_{=<ëØ½Î=-éˆ:7ÒD>9Ro=ž-V¾¬Éâ½HN>X½Âç]½Kƒf½:;->1>á-“½ Œ¾rEÇ=é­°½¤=E¶ø½—£4¾Ï^½Ñ+ø½ M…½»ï=¼Ür>¾]Å3>†ˆi>Imæ=«=lÐö½Õó/>Þ£Z>?`=ÏUƒ>ùå8½¢ ½|ñ¼%Ã>Š>ð=š»<”7¾K"¼y×=éPç;°Ju½¥›’»R˾µËˆ½»/G¾dOD½¹fÊu‹>6Õ>Ë/ß½ØÉ»`?n<ï<¬-½ž3¾›¼‡¾sO¾Âem¾"D>¹>#³¾†œa¾âR–¾„Ù“½çnV½CP=n+>9ï&>v¶9½¯V)¾<5!¾;›°½×–¼yÁ…½M×ï½v”¾;þ§;½´N–½Î™@=eÄ=‘À><¥‹È:À/‰>“[Ô>»ì©>vY=ï麽ˆ}º½q뤽VÑž½žÕ¾*8޽ú#¾<^k‰<¸ƒÕ½Ëj¾­ ¾Øa¼+ß¼Ú÷ø=ÈþJ¾ð[Õ¼†Ò<ýÍq½ ÿf;ÒNÁ½ZÕ¾„¤q¾§àܽà ‰¾ G½ù[Þ<–>‹ x>gè=ŸÆÂ=|O”»Ë€=3<ò<õ½dY«½Ö ½K =f?v½Pý¯=A~½¯À=«ç=Ñ,•»T½‡½®×½d ’¼›Í]¾HЇ¾uÏo¾áa ½çxÈ=kM;>é$>B3{>€B3=öJ>2Ù–=G•e>“Üu½@Û‹¼Á?ƽ œÃ½(Å;6¾ë9Æ=Êh$>3‚½þ§‡¾/ãP¾»ua<F=ñ⿽*!¾Ê0’½Î?Û½Üç½=øÝ¸½H¬½qÞ¾ÊÆ¾nä;¯eU=ÚÖª<>æ=ãRÔ½`,=ÓN‡=ŠÀÞ<1b<,ã½Âýl¾-Â;=㈨=ûÚf¾ÛÒ½™#ß½fȽª‚û¼¥¥A>"Þ5=Á‹š¼Oˆ ¾3@/¾|9>UVÝ='΂¼` ¾h_¾uÚò½U¸½H¾Ñu½3¤¾½»Ô(¾¬½Týf=O€¨=ºç¾êü=§:§> #>ª%½Àâ¾½9†>¿ô£>'sï=¾—>ŒÔÒ>EÓ‚½i>Å=NÂн”½ ¯‘<ûù½ªõŒ=Çû¤¾Šå¾OCY¾ 3½7R¾d1N¾ÈÅg¾Zj¦¾×=½ú¾@à5¾ðK¶½ÿ¦¾ÖîØ=â–>ÇÓ¼=DܽO5ï½på=T}$>2«C>ÓŠ¼’º½¶<…F´½+jb>Ô×¢>ðó ¾T¯w=—œ$<’jf=ùe>­Ÿ'=ºI„»ãI:z>sô<—L ½±oR¾»1]¾|ÎÒ¾w>J¾L–]¾ ¾%ôc¾Nn¾eS‰½îï¾:Èå >ÂU= ¿¾ÅÎ3¾ !Q>8Çc½´ä¾µQ¾¶6=ÖP)>ƒ–Ÿ>„:<¥Av=¤t¾Nà<ü.=½Sv˽=3¹=µú½5Ýf½KFʽWyæ¼—ÌÆ½S‘ë»68¾Åƒ½N‡Þ¼TµŠ¾?z8¾’¾¾Nõ{<a¾º¿“¼xe¾^*¾[¿¾òо(½_d">òcå=kô¤=4‚I¾°ÿµ¾®Á >Q^_>°U÷<±“:¼Tñh¾v;¼HB->ça>å€=4 ¾’P8Ã=Þ8¾=o²º‡É<=ÎrÞ=&ÏŒ½Ñ’=hÃ=”zƒ¼A¶ ½˜¾)¤ÿ=·šV>ÎC ¼²9…"‰=›¾ÊE=Ý">ßW+>Y ñ½u¾‡t½Ì‚ü=zͽP½w’½VIQ½-_\¾K}!¾cÈï=;·)9©Û>ížl¼Ç=¼#Õ½…쫼âûü=+\>€Vž=Ü;¶=]ª\=J=;S½ «$=˜v=<¤=`¿L>qü<> £½Sqx¼à·Ê=yO>²ê½B$¾±,l¾©z‹½³Ö›½Ù›=Þø2=ãrv½ˆ)]¾ˆ¾«5K=7Îa<4Ð ¾Íl>H`»=½x­½8s™½x¾<ä’>÷}”>ÚÕɽ# ˽MKE>Ç~|¼¥çŒ=ñ?6¾ûQe<žü¼ÀÚξeº¾3É/)¾dyª<îŠC<¬Ãˆ<Ò|ü½X‡1¾%ÔÚ=Ì=S¾ÙA'»GM¤½e‚¼c@_>÷¨J¾C`+¾Æ#ƒ½5¾5\>Ò6¾OçØ½ÂE†½ËÅÄ=úBá>Žrì½ÕðC¾Ê{)¾R,¾Ãr½(#R¾'Ò™¾9à¾~ßR<öš%>¡eg=æP]¾<¾ËOF½…=[w˼}—V¾Áø¾u9U>쩘>|«Ê¾ol¡:3|$>üË>x0¾vŽe¾Å°ú½Žöû<"û®½èu¾q-T¾=ç½MŸ½(„û¾|z…¾èvÀ=¯Hw½(0¾<½ÍRq>@ðô<âõ¾z-ï½ s‚=AF>éF•<Žjæ½ýo¸½†Àt»j *½p¼â<©P‰½ôUݽ •=·ÝÄ9ì¾ >UB£½”µM=û´˜½¢ =˜Å‰=ð¼ <Ú²æ»b!»î>–¸½þñŒ¼ZÞB»æ•&=»v>@E¾åÀ=eš›=§½³‹P>»–_½âû¾dÜ.¾÷&ª=T>=_>àïg=|¾Ñ§R½[Ì<6Â=¤³=ï<¾”¨§¾nw0¾Y¸>ÿúã=–bú½‚ˆ¾¤Íc½ÅF­>R0¼?Ê ¾9/¿¼e"¾—>¾—*¾^=±º>ÙO­»îþ¶½ŠÛ+¾¸Ô> ¬=1è³»è0¾¬ö5¾ê4‚¾IrD¾­\¾¥Íø<‰'¾‘¾õ'¾ë¥¹¼È¹"¾¯¢°¾HʇhÑÖ>`5¾ Ý&¾r>q¼N=?Â-==§¾§<¢¼·Í°=hÔ8<ÖÄ1½Ë Õ<ôå¼=*Ð ¼8”»¼°à½ÌŽC=£Ë°=¥!²½©ôÓ½íÒr¾s%y<€Ö›>áí="Ä+¾ óÜ=qS>!">‘нÍg>ŸVœ=RP=b¼ˆÆz>#†2=ã>Tnî½öZ@=½¤¾ –¾á™„>™Â>¬Þd<í-9¾`:û½jä]>E> æ}¾ÆÓž½¯Ø=‘Ѱ>oõ>³¶8¾Õ:œ=—Ë%½ë”¡>nJq¾[Fʽ;-ª¼%À>ÿnÿ;Hd½­&½MG”<ݳ›=¶ð½jF¾Ÿ@Œ¾)8p=q ¢;À ǽÐdI¾‚©½çb=3½±Aƒ¾‡$>YÒ‹=%À ½Kø\¾FM¾c{2>‚!¼¼?1¾òö<—½×–=’Ì>g>ê*À=ŹËLò<æ]§=Õ}4¾›Û‚¾}Í=N&ç½]5i¾Yƒ¾ J>˜>{ ¾Å[.¾–[=¾Ÿ§r=Ìt©=‚nú½¹ ð=°z>½×­>{¸$¼æ«R¾ ὃ¼F¾°½9`¾:机£tƽv’¾×F¾u—¾ÆŽK¾£}¾²í„¾ªÙ½íq¾îؾlý±¾fØ´¾„°Ü¼R9½>û >µ.†=¬Hð=ú”<>ŸnQ>ÔT¾Wd ¾Â¨=½‹‚=—=J=7)9¾éx½Cj¾[ˆã½Kmª½ØÔ6¾¦ÛŒ¾ƒë1¾A³k½T₾Ì\°¾ª¾v—¾CY¾ž×;½ã—8¾& ¼ñ ¾=€rž<¢æW¾0E»ëÂ=ÒN-»1޾,SǽõÄ>=ß=-b‚½0ʽ=M¾¢û~=M|¾éU>´Ž= ‘¾Œ× =cd=ªÛ™Û£5>m>QV7¾©0–=­s>h{#¼%¶Z¾™H½©A>5zºã¬x¾¿!¾/\½Þ®Ø=6…¡¾‘£¾S;È¥>½‚e¾½›¹Y¾xK.=’äº=B,¾Pê绻百Ó]r¾^[A¾o}½€}…=anŸ¾Ÿ3+¾cre¾+‰À½ckÁ½GüS>?\?ñÍÒ=µ"¼§àc>}YÅ> =©õ?=nOu¾:£>ˆ†„=z„=>¤³=˼(¾Ù½ÃN>põ“>lùß=$¢Þ¼¼*¬¼ùqˆ½€ên>9ü>ä@¾TŒŽ¼´ëØ=÷Cоg4¥½k4Ø=:œ6½²…¾Î:¾×Áß=¼Œ%¾d5=¥sÏ= !Ä=„#K¼1üâ½{Mz½¸Ž›<ô„Ľ=t¾¾¾÷=\_!>«˜>> ×S¾_›L¾Œ{O>Û=ã¾:;о;¾²›½½ˆÓ½¼Å™¾X¢y¾a‡¾ù±\¾W–<³C=¬Ñ¼%ö¾ 2¾ÞÆQ½¾õê<мîû3¾šC¸½m²Ë½½˜¤¼*ƒ½¨Æ]»±C¾Hñ;ÇU=¡t@>™£À> .>¹#l>r‘Œ>jD>©ñœ½þþ¨¼‚éÒ=áwÛ»š{Þ¼Ì “>нf>+Â:>è¸#¾‹†=ìBÖ½„>¾O‹•½©Z/½¤ «=®¥¾¹‚Å=±Š¾`ï.=§Ì¾Ì«`½Àñˆ½‹¢2½ $<¼°¼à½ä–§¼áÿœº`ʼQ™½Æ¬ >ÅÒ¼w1>‰#->‰2_=Jo>Š„R==§½×Œ¡»ŒËÕ½oº½;„¾"3Ê<¾=²·½}‹^¾Éµò½%9«=ã¾j™¾pq½T>9¼jR>Œ <\=­sÛ½&œG½<_…¼1ë=Tõ@= ´²½»}¾ç¡¾séY¾YÓˆ¼ÄÕ¾aª¾ ò¾’á¾hœ”¾ò(°¾¸=¼¾Ée¥¾E]Ò½% ñ=ö4K¾—j¹½ˆ"i¼ê¦½Ýc½ï%ýKqf½*±=%¾I!Ô½Ñ_>>&>`7Ó½ 3°½éK¾ÓMʼó†½å5M=s Ó=Ò7>¸;C¾ÒÓ½ü¡>œúÊ=õQÕ<"@t¾r´¼÷o޾ŠW¾‰o'>Õ¹=&a=y™Y¾Ì3=l_>[ß>`@;*Yؽ¼K>ò€4<ëu>0%ª¾qc¸½Q±¾0_¾«–<¢ŠÌ½ƒ–»¾í¨Õ¾HTî½B«¼^Nó=í´3>¦B =Á9¾ ¼Æ;a8½Œò=¶rÓ>pß½>ÛÒ= ï!¾¼i>š˜>9²¸<¥¾æò »™C>Õßæ>è÷“=©¾= ¾Zkc;©CT>mö<àÐ=”‘¼¦…Ò½µS‡½‹T±½…ùù=ù’E>Ô¼ùЖ¼I·4¾¡¬½¨Tñ¼#:¾#Vh¾÷%¾ñ¾]zÌ=ð|•<™p>Ô6Ý=Œv ½®tU½W’½ý >‡«(>‚vF¾H£ñ=~ݳ½É~?=îq¾Ý›÷½~ê¡<Ôœ(¾i =´Š’¾¤ ƒ¾bϦ¾ =Ù=Gëí½¯ÃÂ½Ž†Š¾¨Ðï¼}&0>Yó¶½çóî½Þq¾j}¼ºÚ¾>£KÙ½¾•½²Í]¼GÃ<^>Ca¶=;{W»ã™ž¾I·N¾âV¾sfê=QŒò<ü5Z½f ޾¥mH½ù%´½Ÿ×½Ûß•½¥V¾À¢¾IÍ?<¡2¾©»¾ßò¾þ9í=±ø¼ô±8¾ѽÿ£ƒ=¶2>>2IP¾ýc¾ŒÙ´¾a%5¾Ðz>DèÆ½Zí[¼úòU½ñ„½=A•>äö½h#>:@*<Û="/¤>pÑ=Ú”> Ž>¨#>Ãò»>˜@>.=á¸Ç=Lp¬½ŽÏ‘<Ø—Â= d½½xº=Ô½=žk3=MC½z ^>í{¼>E¾O3™Ý8¾½•¶¥¾ÁIY¾82*¾†;B‘x=…( =Þ½T g¼‚ݽkdÜ=÷>‘¾"¾¦3ĽÛ~½iH½Ø¾r,Ê<¨ÆÞ<]~÷¼­§W¾Û°Ø<Ë >EÄì=ØG½HÏ=Ý%‰>5AÐ=ì>©Šª>j&œ<ß”½]Ò¶½Wœ ¾T¬K½ß½Š=MS¾úZ§»ää<ŠQ4½HdÔ=’él¾óǽ·˜½ž·r=98>&R§¼œ~U=e=€›½ÔBû=Ï[<=‚Âå=œ¥(>âvN»öS¯½GŽ/¾aæ9¾ÂÁL¾Ò¥á¼ Ö½~/!¾êy ½€\½Ÿ8<*j8½ö¹Ð¼,o{½Œm®<¬~†=\!¾‹/ž==n)>ó=Z¯k¾ñR>š¬i>Â-£>-?¾ÊAȾ‰Ç˜¹£M½I)뽉÷ø½Iåý=å;c"½–ã.½ª:=Áv3¼µ@~=Ï Š½Í*k½byB>7\Q<Ÿ5 ¾ØóM¾‰•á½éV#¾M½¡}¾Ú3'¾Ý>r=Ç8D½Å:Ÿ¼s-¾Az¹;±íš½c%,¾d€´½ý*¾{äá½n)½ B,>'ÿX>šm=Xe½C§Ò½"×Ú=eû?=ID½—¸C¾ž]ð½óC¾ù3¾ëC%¾‡hŒ¾vé½µ·»ÅÈ ½Èƒ,¼]%¾xæ ½]žIë¡‚»³¯õ½êÑN;K=]< ½0yƒ½Zü˽¦=¢½’}̽6™º¼ú=ÔQ½Yš=­½½º?o¾À4¾Rrø½×ò=4_Î83-¾Î ¾IB¶½ÓW¿½$b¹½&À™½Ø—¤½‹êļð~ç¼w.Ÿ<ÐlC>….Q>Íd–:ˆwï½­1q>3Vb>Üb³=о‘A£½¬^u¾rý>ª_j>¬s5¾Wƒ½ôe0¾ÂF½¹¾½û‰½­3ù½Ò§w½go™=Z«½0 $¾‡<Æ=RAÖ<ü(í¼ý‹æ=‹4­Å‡ >@0¾\Mƒ>ðé>7·¾Ã¨»fØÖ½•¿=éT¾Vƒ<`ù̽ÝF'=ûç®=¦g¾õnš<žp»ê™©¼S½Ÿ¼·=¨ö½tø‹½ÎŽ›½ðÚ;ÀZÿ=ÏQ½U•޾Ôñ´=Í¿=$”S=¹ÅQ>˜â’>^‘(={§-¾Yĵ<ï ½à\Ÿ=•/ög[Ô½Ù”s¾!ÿ<“t=x=I>ØRt<¢ÂR½ñö<©Ä<ƒ ¾ìÒh¾áÙ2¾îK²½–Õ§½ÍÍ<#ÌK¾-Ÿ¾Ù^†¾8U¾6—¾úÀ¼”’¾À»¾Ú¸Ñ½°~Ð=m/#=Òÿ(>3b½"]!½¦{ã=Øeo=á,Ê>m= ‚Ð<S>aÆÑ>[äð>gøõ»øß<ÁYÆ<¥!Û¼³¾›ÃÝ;G£Ã=‘öx=ÂÔ²½±å§¿·6鬽î÷Y½@m~½´8º=ˆê<Æ-=Œi$¾Áo,¼67V½ò®1>­yM=~νOL¼ 4I='å¾®dc=8tÉ= ¾#Ö<€2¨n’½¶:D>R\X>2ó»p)³=ZÚ=JŒÎX>⪌=#‚¾Èì$¾ä;<ˆý/=ê·Ý=Ï&¾÷n¾y¤:’뻽m`‘½¢Ï½ù»Y¾ËJk=1¸c<ÁòWºªÖ¾› ]¾™RÚ< m#½HpV¾£Šû½ÿÐ<ÜM=^±ð=?³õ½S%.¾Ò|ǽÇýP>OìA>,C*½Î?=Äe`½ž š½  £½ýlÞ½¾ºAœ¼Z{U¾ä ¾ÌAT½G= ¾;ƒ¾ÆüA½úˆ=¡%¾íA¾fs¾G À=S„=o…0>©ë>ιˆ=;/Ž<%p~¾ =ÎÙ©=Ã>½>e¹/¾‚^Á½jÏM;žÜ@=)t=l{¶½³=K|1¾–øÿ½/çõ==k¼¢²½;T¨S¾®¨=þZ–»_|¬½¾#D=pY<9ð(=iŸ½…Ä5=Zâ=Ô†»Ùd7<8bl>;_=D ¾FœÉ»‚wâ=¢#>_7³½äK¾oAK½r^źæy½4è ½©d<—JU;O?¾§Í„>Vbö;“`s¾(¼S¾®¢I=—Té=$¾®“¾ÀF=¾ðŽ×¼ Rf¾TB¾r*S¾´`¾‹d£½CB¾µß ¾Ö ¾¡ig½Þ¢¾÷`D=Ï~n=¬«¬¼) ƒ<~z>>Ò9\='@ŠªPÿ;õ4¾ ¼›6[¼ n½žz_¾ª›5½Ž ½­q5¾‰@ˆ;qî½Û/Ã=" t½†/½“ê½T‹¼|_<.oB=ã[7½ – ¾/½CÒ½¸q(½aWð<å‘v>ƒeN¾nq„»ðÕ>cž6¾ª@»+u¼fF>9–=!†½6%V=Óé€>ï”> ä½:Q½Ñ¿¾ª«Š½‰ób¾îY˜½¿ú<7òO¾Jïc¾·œì½ñ¹ß½q67>‘F>é–=3rf>ŸZ„½çgÐ<°¦=îm„>̇ >¼W™H<xÏ= 9p>;?ǽÚ݆¾@•”½Xs|= \ý,ú@¾Xží½Áf¾ÂĽYb·½•“”<$Üȼþ³Î½D­V¾€¿=nB½ZgT¾@*H¾a‡4¾3*ð½í%>à«7¾ø%c¾Wfj=î¾’½ãÛÔ;Q>‘‘?oRL>‡ˆ¾O””>)î‰>W© >O´î=ÀûÔ=½¡=y:…>x^%>µ³H½¡ß»½#'g½ˆbK¾±W‰=Éw=Üü¥>§ >ôh¼uL>ã3¾i >áí½¾/¾¬)_; dè½Ó*=êl¾õÝV¾šž<³ –¼Už½«Ä¾!³e¾Ã ù=ØlÀ=¿=v¢“½ô›¾Á!À=Ch>£ô½.ñP¾5ͽ2Pª=oŠ•¾‡4¦¾àã ¾iŒž=Ærh=ÜzE=pö*¾¿Y,¾¹¨ƒ¾vB´j§=‡x“½Óн=-ÝX=XÎÑ=éí1<ŒónâQ<5ï >$tì==Ä:¾õA™½êü>·jD=éã³½Ö%¾ð½™ºà;"°Q¾^/­½~aÁ¼ €¿½hѽ(¾rÖ½(Jî=Ô#­=ÂVsÈ Ù<©%+<…”ù=rHù=®Š½üSF=äcê=ÛΖ=­I=V:ª½9ê½VôP¼º÷ù½¾U´½Cã)>÷(¾G >̑нAà‘¾äðI¾:ž>^’ï½@U"¾?zq¾;F¾ÍÕ¨=B-Ë;éý<’0V¾ãºG¾šc½u!¾,AJ¾Öª¤¾Cs¾ ú<øßŸ¾J*ª¾a¶¾…à•=¯ÎP>£',¾t}¾ZÄ]¾cð”>ÕÌ>úø½Š3=ô±§>3¹>¾©ÛÚ=*༬qI¾âN¾ H‚¾D*Ç=Ú¯l>,—“=ºý=\¡ó=3C!>w ÷<µ¤´½ÚÚ.>Ì~">“†¾¼ÜT¾(dM½þIÔ=1¾×O>㼡½­&¾½`Óý¼6è±½¤šY>Ûv=wê¢=LF½ÕŸì¼ô½•…ʼì½ÞB#=¾Ù½®÷s¾¯¤ ½ôǽ&àˆ=H$‹¼‡-Î=‰òÖ½;%¾‹„¾ù»¾ø£ >FY+½Ž®Z¾jº(½ã¾Žv½E= D¶=·ÍÚ=¹…n>ç/'¾Ãl¾Ÿ¾aœ½aC™»°b=‰<™½Þ\ͼ‘G-> «=06>:ö[=…øKË=IV‡>¡ =5G ^>^š½Eò >LÉ¢=ølW>ÒLa>=øÈ½þ:½ô»"¼ 9¾¸•H¾¼$<¾Wpà½Bí›< ö>ïC*>g޽J”ѽIKs½>½5½Ò½;Šv½¼¾¾FV¾ïF‹¾:¾ÍŽ+3*=]wŽ9M ¹½¹Æ¶½Â¦­=ä¶¼˜>ƽý:ð;GÅ/=4’¾ÓŠ7¾×éA¾Mÿ ½“sø=…±<Ã眽ÞG¾6XŽ‹Çy>â0;>EΙ¼õ"€¾GÍn¼T¿ð=áÛ–¾€“¼<‹A½z(¾D{ˆ½G†•=†>½ø4ß½’‚;ï§®=ã~,= !@>/¿L¾a¾8Qɼ”ÈW¾ÖD¶=\j_=!µã½Së4>øˆš½IáE=´Þ[¾ê+ʽËÑ=>=¾É5©½Ñ«Ú¼ËÏܽäûν—>Y=&Ç!¾$킽üôÉ=Ióô<Ù"Ç:Ju…>&f=Ën¾ºqž;%ÿ2¾ŸöI>2j‹=ši…: T=óJý½0¡¾$°¾×j<¾¶>Õe?¾^2¾a‡;è·¾Œ3^½]»<ÂÆ_½‰4-IÉ=`ð«=Í€½C§×½Ùý::å”>Yi¿=Ïÿ<½F¬<^À£=;¿> +V=2d,¾$§=©ïè=ͤ>qÛp>““?>l >C=í½2í¾Tï]¾J'¾z€j¾ÏC¾v=Ƚ’*½“æR½{î¼gʽzˆ¹½¿J¾Ç–‡½³ 콘Á§=áØ=&Õy¾‘õ¾´Ô½HÕ¼+/=ƒÏ¾°=nÝ¢¾E¹>yÍ^>Ž}¿¼ßyJ¾¾jv0>gé1>ߨڼµt½¨q>mZ5>ÈKg½(G¾l‹æ½çÐ= Wƒ=¾q·<VG½ôŸ‰½ß?$¾8™>>sÀ=ׇT½d R¾ н¨„À>9†‘=|P¸½ð¸µ½8á=ÛFŸ>x#½ô0¾‘©´½&G¨=ÂJ5>ýB’¼šý…¾‹^&½91>Z怽‹o¾2+¾ †½L­©½™S<hû<§'8¾H<Å6M=yÄݽŷ:¾«Š›>HŠ£='ƒu¾§N6¾²&‘¼ôx>‘8}:#R¾ò‚«½¦––>«Žr<Œ«Ù½Õ*s¾¯9…¼«²¾šÆÕ¾÷s ½ö¼Éj%¾7 ¾¯¾ö”z=ºˆì;Ú½Y¾2X½è.=¡ðP=Y¶½Yºœ½Ñ‡¾K7=.Z"=9µ¾0{ž¼ÖV´½…3=.£å½“ƒa<6=¾Yj¾ö¡¾0k¢¾X)ɽ•ÝD<áS°½¶Ù¾+7Ó¼ä¿!¾K'<ôš€<•?2= ªk¼OÀÓ<Ž•> 2¾¢X¼eä½{¾è=Øø‹½ƒ;¾x†½Tå½ÞϽáš¾¬±ö½óši¾:w¾¡³½c« ¾4¶Â;k޾TZ¼µÕ=ô¢Á¼z%Ç=÷ÞQ¾À=/=5->…¼=bC?¾ªN>´‡*>a½>¹!éƒP>:Ã=~æïI>Î_ݾCŽ=ìn…½²q¾ÇøÀ½.ѽ§I»åºÂìU¾?½{’U¾hòR=ž¾’½_<Aé½B Parameter87*¹" é¼Ý—r¾#B¿÷8„½ 7>À‹¿›Xó¾H!J½Ç©D?éä†>kÈû¾VÏc=nm‚??Ö!⾪]#¾ »?K“?Xè–¾Zù¿ø°=›qg>9Í_¾ÐRô¾ZZ•¾¬[#½¡Ø_>'Yÿ>‚ëØ> ÚF=^8ϽǾµ%»Âô?³‹Î>@u¾çD§¾(Í"¾Nη>ʾ>BS¿ |žDÝ1¾Ÿ‚i>^ª>Õõ¾³üj¾¯¨‡½Bn¼ÿß‘>Œ¼æÖ=mÛ>:¹>ëâ0?.z«>ü€Õ><Å>p¨¨>éØx> *ð¾U›½[þ¾eº1¾p±|¾£y¿³27¿OÈ ¿Iå¿p›å¾&ÂK¾Ÿ­ª¾)º²¾gоŠ?‚½%8—¾d2ª¾èî§¾½7¢½ùÄc¾Cô¾ýÅW¾”£k½*«'>›Ü%>LȾ] À¼sü=¤«-=²…i>ržÁ=¦{¾>§`>m7¼=†9>¢°§>†Å³>1î<_>V>rr®> °[¾>ôù½P»+(g¾G%¾Ì^¶=5åQ>¼ ¾Òg¾­å¿ˆ¸v½$Ò>ø\m¼X)ó¾ðe¿ê«ì½×>¶°>?|.=²9=¼ Ý1( ?bê.?y¼q>ë*¾½éª¾xÈC¾Ý [=üø>B;>O ¿M¸;¿²˜¿¹Ä¾ P ?Ž7±>,¾m±¾&úW¾)oa>¯®(?þĺ>Žz>¾`ཅ°v=›>x¬r>°4ð>ï¦d>G¥È=d:?”[¾©Tê¾äèX¾ì“º>E`ù>V»Ð¾iÀÛ¾|}¾ŒqÔ>X›™>qê¾Ðš›¾Y`·½4]Ž>ÝZê=s0¾%´¾q­¾ø„²>ˆA>>-‘=“e ><*=½&í´¼H,¾øÅ»=V*m>°ò­>Á)>qÂÀ>îµG>ú]¡>"0?W¬ñ½z¯H>‘¼>ý¿C=ÑÙ>ùuè<Ž}¾üY÷½äEJ¾Þ•¿q»½KbB¾æÚ¾ˆÊ‘¾ëB¾B Parameter5*6" «j%¾µÞ¾w®»=© м•,…½Kæ¾µB§<¤ø½B Parameter6*W"@r¨½˜öÞ½¯l¾9ÉQ¾Vo7¾á›\¾2 ¾lH¾&X‰¾d4„¾¶÷›½¿¥Y "(ôº7½1Qÿ;qx‹=oµõ<‡q¾•>lrb½¯FJ½ ±¬=Åe_½B Parameter194Z Input3     Z$ Parameter5     Z Parameter6    Z% Parameter87     Z! Parameter88    Z0 "Pooling160_Output_0_reshape0_shape  Z& Parameter193      Z) Parameter193_reshape1_shape  Z Parameter194    b" Plus214_Output_0    j0 Convolution28_Output_0     j) Plus30_Output_0     j) ReLU32_Output_0     j, Pooling66_Output_0     j1 Convolution110_Output_0     j* Plus112_Output_0     j* ReLU114_Output_0     j- Pooling160_Output_0     j/ Pooling160_Output_0_reshape0   €j( Parameter193_reshape1  €  j# Times212_Output_0    B ROCm-AMDMIGraphX-46524e8/examples/vision/cpp_mnist/mnist-8.onnx000066400000000000000000000635261510465702400241000ustar00rootroot00000000000000CNTK2.5.1"ai.cntk(:²Î b Parameter193 Parameter193_reshape1_shapeParameter193_reshape1Times212_reshape1"Reshape2: « Input3 Parameter5Convolution28_Output_0 Convolution28"Conv* kernel_shape@@ * strides@@ * auto_pad" SAME_UPPER * group * dilations@@ 2: F Convolution28_Output_0 Parameter6Plus30_Output_0Plus30"Add2: 4 Plus30_Output_0ReLU32_Output_0ReLU32"Relu2:  ReLU32_Output_0Pooling66_Output_0 Pooling66"MaxPool* kernel_shape@@ * strides@@ * pads@@@@ * auto_pad"NOTSET 2: º Pooling66_Output_0 Parameter87Convolution110_Output_0Convolution110"Conv* kernel_shape@@ * strides@@ * auto_pad" SAME_UPPER * group * dilations@@ 2: J Convolution110_Output_0 Parameter88Plus112_Output_0Plus112"Add2: 7 Plus112_Output_0ReLU114_Output_0ReLU114"Relu2: “ ReLU114_Output_0Pooling160_Output_0 Pooling160"MaxPool* kernel_shape@@ * strides@@ * pads@@@@ * auto_pad"NOTSET 2: w Pooling160_Output_0 "Pooling160_Output_0_reshape0_shapePooling160_Output_0_reshape0Times212_reshape0"Reshape2: ^ Pooling160_Output_0_reshape0 Parameter193_reshape1Times212_Output_0Times212"MatMul2: E Times212_Output_0 Parameter194Plus214_Output_0Plus214"Add2: CNTKGraph*›P "€Pª»=c³ø=RÌ®=Nû=†?ʽ†¡å½š\½%ŽÆo¾?È=ÝoF½•- >]>Iõ½”U>¢•h¾|¿Ø>‚Wp=ÝT+¾9ݽ¾]ÚD¾ê*Á=}á¿=êù*¼d l>|¨ç¾±ä#;V½K¼Ø)=OÌ›¾3«“<øéÆ<ï“> />1\˜½{»<¾Å×½¸Œ§½ûCu=³>ø961¾t ÷=Ê|Ü=åsÁ=Á ½ôeÒ¼(€ß=ïXª½5©=Ãì3¾14,¾’S×=E2¾–ƒ¨½óí> å÷<º)#¾i_L>xïνr4ý¾æ¿¨>"Å5>ΠÒ>lpü=ý/¾¾î¾ñ¾¾ßü«¼­¾"çF=¼iÃ>Í‹m¾ÖÐ>í¹<"vŸ=2‘+¿Ñ“z½þŽ?=ê½j©>(ÈÔ<ļ²7>¶ ½K{Þ¼gl>6V:½ß[=(þ²I|2¾Ls)>/½¾Lç1¾±ë½ C$=yD>í%.=ÖÁ'¾{Bâ>† =³ྂ©J=«mq¾­:ɽö™Ï¾Àü¾¾¹‰y>M‡¿ ? #?+ßÕ½0Y8>‘ûè½h"½S«å½9W»¥ N»xÒ½2û›<õ¾“½½ì€½Æÿ±=äÑs=·8>§ïÜ=i¸H¾W<££=C…r<€<“½1âÚ=‹íz¾äy<«¾˜L½ªã"¾?Ћ–½y?¦%>¨8M¾ÞZ°=㑈¾õ ÷¾1¹%=9 Ô¼VY™>EÞ^¾@9¦=™zˆ>P’”¾•ˆ>ÀìO¾Çþ½‘yÃ;]¿e>jÑ >§™å¼–Ô¾Tçº<ÖW…=ꋾE&Ñ=&‹ ¾ÓËe=æ ¦½T é;ÿ¥S¾ªÕç=`u8¾::¸>cTk¾ûÄþ;àÁY¾ÒïѽÀº=¡îŠ>W†¾`§²=–˽< >ÁÊð=ÜIǻĽҰ=Ì2B=ó½ü>§=z¬=Zú >½0¸µ;RKž=þÉļØB—½|x> M>•¾"lÀ¾^Áƒ¾“T.>ô픽&¸ß¹Ÿ½õ+>͵—>9)5>pqоpŒ7¾þGù¾ ??2½èÕ&½¾Ûg=†"‘¾RŠ=š¯Œ¾/4—¾q£>+U̽ºÁ·>®e ¾¢±x½„¨¶= ¿ý½#{ö¾€Ç÷½m•š½¯Î? –%> =02T¼Eá=܇9¾ïV¾JÌ.>ŽuU>nßÔ=¤æ(>UÀT½ü{˜>òbõ½%‹5¾r¾ïD>»#³ ¾ú˜z½Æ<> %Œ>.‡¸½÷qò½Eß<¥,«>ÆÄ¾¥â“¾ ‘‚¾ê8ǽÆè?ø#•¾\/>>dÈn¾¼:>Fñ)¼òE½B—¶½…†%?l+>gØ>ŽÈ½(N[>Æp‡>å¤\=ˆoB¿vÆO¾âY²>·Ä ¾Wß’>Fï[>xÊÉ=¶0v¾é^#>©6h¾-•Œ<$Ä:<ød3>~L=M•ž=d;½-^È<ƶR½=ú¾µÄ†½nNÎ=Gd‰>È#½È=ß*"½ßÑ›½ç©Ú¸bâ=²¶Ÿ¾m¶=W”¾/È¡½L~>Éí'?`F”¾€S±>ïÚ¾I€½.’)=3½ô¾ZV=]î¾{9<*Ð+½Y·Ï¼Š5¾¿…G=ÏKé;ú½Í÷½™ë=6lI¾dÞ>ÿHˆ=wCÚ<’›:¾ÀM¾ò“}>s&>ºêN¼ >¦´”¼Ù²=¼òè= ±ª¾¤=¾ ¾Í`>ÿ,”> €>ª$2>ÖÐ=v%ƒ¾¬s彨3r½‰5=×6'½Ì µ½ÕZ¼¬~=ÔÓ½#.Ž=–c>ILˆ=„*¾~ˆ‡=Ë|(¾#(Ñ>Ñ2€=‰H‚½XZ‘¾B‡;>+ý>hï—>׋¾=ñú»OJþ¾ã¼¯>ê»Â=7ܾMºE>Qõx¾}?d>)õ8>nN®¾Ÿ€É<,ö½ÏǪ>`µs¾«lû<2Ѫ<‹l½õWK¼>ê¾K™¬¾ä þ>¡ðÎ>Cˆ¸<½e¾Z;_S£¾²ôò‘[>(ˆ>ýKà=Bð¾*ŧ>c>ñÁ¾¬˜,¿ 5Ù¾Yá3¾ˆhc>òž?)>ÿ>oõ;¿gg”½¦!½U6?¯ôŒ>§í9¾Ò0@HG=óŒD>̪¾{ÓÀ= ¾ÔžC½Ë€ >†é=(¤»jóFZâÉ=ßñð»Ð#»½çŽ>¾ÄÙ¿=?%a>ÞÍa<Ùß—<¤?Í=xŠk=ÿ²=(µ„¾ŸQ=-„=‚Ûš=°\¾ep=¾{щ>ÏÝm¾˜ßu>†ßZ>‚¿K¡½ÎÛX>u¥.>¨H ?eML¾ V¹»_éG¾!þ½ŠfŠ=¯¢ù½"n_=!š¾èº ž/>ó.E?ž³Š¾Nq= ó¾œNÁ¼F¶¼B«o¾í0¾-SÎ=Ð+¾ñ ¾Å>V) >O«½Ê>54ß=Éi཭ù—;ñ0¨=×bi½‡½2û*>Ïî½$x>¤bí=(¬=s3¾H/Ž»i³š½È3š=¶>ƒÕ•½ä(¾™QE½^øé=–)¾Îh˽öK1¾ÕÈ=PnV=Oë=g7‡9šâ½´¢=Uƒi¼¤K7= «Ä½p…½@Þ˜<¾¦Ø=ÿàå=·a1>5G†½Uì=O}‡=ÂïÒ½5ß:½»ê¹½t¯=8ñŒ½ÖÔ¨>îV ¾÷€½Í'0>NÄk½M¶¾‰Ž¾Þšn»êŠ,¾ª@Í>3™×=#9ö¼³Ë ¾¿íÖ»`Íš¼»7½¼ ðT¾Âbî=ÆE<½P¼ø=;°ñ=Qå¶>œ A¾å¾$4]>É‘½Íæv½Ænj¼—æÈ=Uݽv±ý<_çǽ—ÄÌ<®Ív¾¡ à<› ;0Oô=цÆ=U£>o0*¾TÙ'>%ü~¾_¨="¢Â=Î~‚=—QÉ>Éj̾V8B¾NQy¾Í˾>Øê´¾Àe®;Û8½Šo=´?†¾Ú;r½D¶¾ÐîS>[Š>™¨\>·¾ð¼M̾1¶`maO¾˜©Ž>!Û¹E¾Z¿">ºL½¼'•æ<ܧî=óš’¼7hg½P磽Q—½Ð'Ô½ºÇ.=ë°>[¶¾1„=µ¾½î=$“[>^“>ÊS¸¾&ë ¾¢6´=z­Î¾ÿßI=,X…>µÌ:½€M®½õÅ>ê€>øMö<@D¾,a+<û6(¾¶‹Ï=,˜=ç”’=SA ¾ºXD¾©D–;ìòS<G‡¼ËŸ2;+~¼¿ë=…LŒ=¬ÓÓ½öí=Gx<¯`áÙ/Ú½è…í½+ ¾:O¼RnÕ=|ᓽ¼ß.=볩=F‹<,ùƒ>/9â½ÿ¾ž…´=Ül>À%½Ì¾™½­¡¾9ƶ¼§Ì¾‡”½_*"¾‹Z!¾ꇽLµ>øá;QÐ=r‡¾‡™<´L©<{ÈŒ=€N>0+Å=[«ñ½Gxö=èm¼¤‘O=Œ¤ß=_Y.½íæ¼Ècc>JS> X½Ä@ÿ=†Â°=IþJ=»F>,q§¾U=R¾@üs¾ Ǿ¨y=gÂG>- £=ƒn¾ ŸN¾/ÿ ½­¼ÒS?D ¾Eº½>ؽ«Í”>yóe½^-m=·©½#מ<ç­>Ÿ*½ó3¾_=>~´n¼k#ñ½ñuŒ=ÖÈ.½Â)>Uk‚=pE¾ÆDè½kFF¼ÕŽÛ=š>q¡ÿ½v»½s –>I†¾¨[½ ®½Úßø=ò¼Š—¾ø*§=ÇÄ>‚ͳ='Y†=ÐÐ>÷X>ÉÇc½C×€>.ɺ­ž8¾ ˜(¾ÃÀð½;í½ËRÛ½{,*>Úµ=¼ê=%?F¾¾úTe=¶3 >Gý¼ßJ¼€¶¨=p…=¶št½°´¾ø>â z>”ž+¾fÖ¶=da=Ùì¶½/P<»ºI ¾—zl¾ü=C+¾öè6>å3Á½¶Ñ™¼®S >šÿ¤¾lÕ^>ú©>­„ó¾Ç$å>Z¡¾Å[¾A:>Óüµ½ã)>’'*=A;>®Î;;¿ü›¾I>æ’Š½’Æ*¾µú ¾:1²=¦%½/}½FD9¾ %(=˜’þ=¹§-½n|‚½]ã ¾¼y¸=žh>£¡I>‹Žø¼&;=ų†=¿A½Q„¶=ÆG%=MV½\„¾LçD>†® >L@¼LF=eá¼&N¡<"Jœ½-L'=ØÎ ½"v½FL½ïõ¾šÈN=Èåw¼ŽÝ=§,Ë=dß ¾Vò%½ª;º½Yq>šô„¾ëÐ5½&/ì<T=¯ã~;(« >~qÏ=_eT¾”À½I…‚>m;¾ž”Á¾ë!>ǨÒ=íX_¾;ÞeºÈî½èº=&?ZB=>b]>²½‰›=›³u>™=ç¾y<;ŠÕ˾̾ç¶Ì¼DD=ñ]*½E{†=:ø½÷·?=ˆ®M¼œí½ªÊn¾ý¸¾»S_¼’¶•¾Ý;ðß»¾¾A¬>ØNN½¾$+½†…¾c~™>æCà>6zt=e„¼uPþR`¾Yç€>~Ý1>±®2¾¡f¾ï+9>Þ*·>†g3½¢/¾>³ç¾ýÆ?>ìÃ>|>ÏL#¾mó=޼vc ½Ê3¾/=ß= ˜™=Þ>Ó)½Ί=‹¾¾¯x'>Ri>‹ä?=ÔZô¼ýÔ=>@ȸ½̆>q¼‘¾é%?èj ¾OM„=ÙŒö<šÚ>Wc¾$è$>o•u¾!Ëe¾e®i¼!é>j¼ÞÆI¾CŒ¾{5> é“=À¯ô½²B“½½Pß=K`3¾ÌY¶½=Ò/> ôs½›…é=˜/)>™´¾ < ½pð½þ[ ¾hõ#¾:‡>0ß=ò$·½œk1¾5‹^½/Ž;=ÏG§=ÁO®=”F¤=w¾˜¾>¼Î½Uq ¾ªŠP=ƒ’L>ñÄz¾ß ><É;5Tÿ¼dŠø=è!B¾‘¬*>£Ÿù<+Ä>ÈX½E7×<™êh¾™ ¾ý„œ>,k¾½1=¾Œ“Ì>üb¾wK0>ÆÅò¼/8á=zñ¸½üÆa»i>–QS=/>Y §<è'Œ¾ön77>M0Õ¾˜pÂ<Â0¾*Ÿ¿ƒÌü=[¡œ¾h÷=¾BE>ò>Q´Þ½ÜöÇ;âJ>½b]ù¾€)%=‡l­=7…?ªE†½nê>w$½D4]¾XÇz>óB ¾´UÖ=æ 8?ì3¾‘+‡¾æZû=u(f;ôE¿ÌÊ‚=NÑð<¬>òŠñ¼&Hß=¸Êç=`3O¾bÓx>€iß=šz¥¾ÅT¾hÿƒ½×»L>rø=\þA¼kϽ?W±>bŽ’¾ t‰>ù¬V>ÓéC¾[>©>_d›¾Šü)»¤$¾ÃUs>“¶{>sM>æ½³G¾65è½Ñ@¨¾BAZ¾w3š½èQÅ>yir¾RêÓ>N²ò½gé%=a[p=ˆWÛ>®>ª¾â­˜¼ìi™¾~"¸½ÄÔ½`.Q;N¹¾ è½Ç{ѾHg¾<Ã37¾š¤>ÿ±‚¾LKã=ÞCÍ=ì|>>vJ;@v>TÓ½òFk>퟾é>1T=Ùk9>p'œ½Žƒ<¹(y¾è|›>?-Ú¾„‹>‘"=¦F<²k¡>b®¼ÓH=ÊV¼9]=ÓµÄ;ê:¾ÊÏ·=;„ì<½1_û=„Ê~='èß=´gº½¹->wb¥¾JÙ¾âу>ã§ÿ=ƒû[½õ Þ;J;î<n¼âË;>[O<âs½_Úç;F%>6§S=?©½zO¾e0+½»\ÇRID»8ÈнQŒ/;@¢½a·Ã='v >EëÔ=(>QK¾vM>÷¦~¾±Ž‘<8hÛ=aü>$‚‰¾jDø½ß ¾ UW=¨î‰¾óEܽYJ>\xU>ë÷@>ý™ë½(Oœ¾—Cï½}²¾WN?~Iñ¾pn¾ÖV>¥ƒ>ï<9>±Ä²¾oƒª½ã¨½HqO>Ù?/>Þ®4¾îë1¾†iŠ>ãÕ¥½öö‘=µïæ¾’ô‹=›ò}½  ‰>üQ½=Ý8=Øè.ö=yV½‰£d=&Ï“:ŽuȽ$·=Î0v>ß§*¾Þn'¾6E8>¬¼8Öç¼,»¾eß=­ ¼ðÕ>$YS¾m°¾Öš >¡»•ºŽ¾ A”¾ô=û½Ù¾¾·Gö=¾¤¼0ó<ð½$ß®=êþ»AÒ>¹J¾½cÝ&=eÙ„½;ð‰=^ŒÐ¾á5>çBM½Ðã¾Ñ4>ŠXM»q«¿ÍÚ´>râ¾á”“=m¢®>h^€¼¥ó›<bá½Pƾ E¨=t¡>ùî¢>· »¹ =6Æ–½}zm¾‹½©=½7©>¸ï½‘û ¾Êxð= àò½Ûy>Êr+<žô&¾†ŒË½Yѽu±½"ÔK¾‹\=>Ï+:Wè4¼óS>½à9>pU¾þ/¼¦tá= ᤾„a½Îò”>0KM>ª½—¾”ô“½ågD>à¾÷¾ÏF“>Ðì>m%>—£½$¥=³/¾ÝËþ½Xnо†#Ç>À`³¾…g˽$.Ñ>¦á'>8‚p>pœ¨½‹o¾Îšv>/T=ÿÞˆ>Yò˽­¾¥ë"¾^,<=?ó=ty¾Í¸¾¬¡T>öû<¾éf">Ç*ÿ=ÎÐÙ¼ºPM=c¾¨<ª)m=¬Öƒ½Ew¼X,>ócÖ½Ûw¾&%¾$Šå<£(.¾D›!>¾¤ö=Yà(¾a¿¹>€ˆ¾’¾FÃ=;ª=¦íÙ¾nî¾½'¹Š=¥Ô=%\µ>Z)d-¾þ~¶<®ßà½Òo˜½X6¿¤ µ>³÷=qhR>4°½™˜>q(r¾Å¾½¦¾ªÞ=ìò¾ “@½ 2{¾nóì½™÷;¾$Ó—?¼PW¾•| ¾!­½ð.Ð>ùÀ,>`{é=ìü=éµ¾øfD>Œv=¾´O†=M>ƒ§½¢VD¾£­½°·®½ P¶»00V¾¹B¯=@°R;pò=S×=sõø½jþÕ=Ê!À<Ñ[á½`ap=‰Hc½y N»Pɽ“+g=ªQª½„ñ‘½©¾.úX>@ç ½úC>‹ >†’Ù=Ø„r¾+Þ=†kŸ¾äÃ->"Çž½–°?˜%ñ=gp;>C¡/¾øLW>E蘾#ŸÂ>JzT¼¯j”=V.‰¾Æ±N>Ò[A½¡ þ=Jý‡½>¾Š=I[†½oùe=·½¤-=½sÐä½NU¿»“ÉB<ë뫼üB€> ‡à½Nô¼cÛr¾Éõˆ»º¹W>öÒ½i®½{9_½l2>9ä†>ÂÒ ¾êé[¼Ëw ¾ëûŒ<á=Xšù=ß§¾àô=ê$=ùZ>h¦6¾Ÿ%²=8ó¾Ck½ïE=.3è¼ÙR=;K]½ô^#=€QF>ÿͰ½áHÃ=šo¥½~[>Ìfß½oG–¾Y¸½Jûá½Ïj¦><à=´2,½=>Ëü³½»õQ½«»=8Ç•½AkܽìÖY>&“^>;Ö®=¿vw¾€ä~<[s>ÿp>=º½Ì¥>«A6½=© >ξ&©H¾4€§>Bð½¾Ë¨¸¾øÕ'>»J¾òò[¾ú\S>³v¾Š E½i(ó=Ἄȉ=â§o¾kî†;Øs²¾á‡¾Ñ~¾޼£e+½j)Î>UT5¾Uv‡>PË5>ß§=œb/½wWm¾&rr=±Ê¾áHi°—¾†È¾">Ýi>ÛäÖ¾€Ž_=ôl¾ˆ†f>uVª=TM&¾ö?¹½AB=Õèþ=½w³¼¨Þ¹½áO¾GÌʽú<>)˜™=;~¾¨ÃŠ>Wñ½"t>ñw½S¾›=É1¾9>@Hœ¾LÄÜ=–eÿ;9O²=\„¾AS¾ÐJ¿«Øf>4R>£‹>†=/½á^?¿ÄQF=*q/>þe„¾ÛÎE?#'̾/ÃþŸe?Ä‚¾Ñò5?¬¦€¾À›¦½Ø/>ï”y½¢çn>˜ê<FÁ½Y^ë½¥ =°C>Úè½®hI½†OŒ½ûGT½5„4¾c>¾Uµ½¤å">Æn=Ù©=©‘6=ây×¼[½½¼€Ý=ôÊ¢½"?V=w¯i>Ælœ¾SÎÜ=5ç`=Ý:Ò>ÉQ9+•º‡e-¾åò:=rA¾jÎÆ½{íÚ½œw‚=t‘I½…½¾¤¾€CÄ<ÖA²rX@>}Cø½yu=Ѿ“ün¾xö•½7S‚:¹ y¼÷BK¾¤¹A¾Zu >ئX>«º´¼Ÿ‰¼dò >éÍ=«Ðè>Ò¥'¾Zj¾R\<‰„Z¾ä²Ç½øÒN>D¢…>Äq\>ió>7Þ¯;Áhú=LŽþ½¼ô‚<²Ö¾Q<®=ÞJe¾G®=3k7½˜Å(½–! ¼J“ܽïé½È"¾2d>@«ì½‰ô0¾‚V™¼:ƒ‚¼NN<+È ¹*>’}X¾¸p<»qF¾^]¾¸.>Ý™=v¿ó½‚3˽g„=CŸ><Ľѽ‡æ>±\F¾À€t>h.¾¿ Ü<æbƒ>7…K¾­¾k½3®Ù½V½7K=9k½1¥½a>av©=T¨=×þ f±<Ãû£½û ‹<ŽH5½(L›=£¿ >¿o>L]£¼“â‰=qÕŽ½’zN=ýÛ=53ú½¼Ž²½s‚½êÐ->@/â½ä×q=ë>‡k¾ÑÄ>É*>ÆWœ½d%š¾~X½¼l>Дe¾MDÞ½ ©A½ÁK >N2¾x—>œ§½îÉ6>Ê™q>c„8¾Õf=f:>gµx½®Ä=À´«½ ¼¹Ø½á&ν3—= e&>ßq&>Δ=kðˆ¾é7‰=‰UH>]#2>Áª¾è{Y¾ù °<ή%½0®©=rK½ç6-½_—[½ÈOp>ä>~H=Ë¥s>p<לнޭ=Œ‡Q½‡ÓÐ<òÅ̾?°>)˜½£5É=7©>ÎAk¾æÏ=>Æœb=äâ¼S¦À½ôYâ½å„ª½­‘)>xrE>Ãð%¾Q¸‘¾Usc»vüß½è‡>‰Á½¿¿½Þ+¾„(‘=Ñ …>Ûq0>§’‚¾³:h½7Ε=#_u½[„¼½Õ×f¾ (¾Ô™º*¨ˆ>‡><ü¤>{¾Z-(=+Œ¾Ôfï>K€¾¦m¥½¼ ¾€N—=ÔÆ¦¾WнZ†ü;΀¼r>×í>,ÆŽ¾pؾ{6½ÄQq>t*S½:“?‹K¿H<3¹0¼”Ä>®!¾QMÛ<Ÿi€¾_í>Xgõ»¨=î>N½-%¾n”>X¾´ÉÐ=ƒ°¶½⣽`"á;õ]¾°<ò½ã±=Ó¤ü½ êÇ>5‡I;¹K½f`¾»É>™ >m{ì={޽Â×½ö·V>a(켪¡g¾æ0Æ=4Áa¾®Úû=p¾\0=G©Ê½“°…=ãðY{]¼E*̽³²º½‚³ƒGL×½*mĽ}†-=ƒ/O½ªÃY¾6ñ=q_Y=z¾#MÛ¼9¿=ûÛϽ&‰Æ¼ÓM="9¼2Ù=¤¤= ¾†©“= b¨=-š¾½§ú¾=æ ¾š+ ¼m{&>…:=E»½Íp>¾;¢–;éüS¾ì²h=@æ&>ºh!¾áͽSŒ=ú¬«½~=Š…¾7;¾ÛZ¾Ý¹b>Dk˜>Pw>*&Ý<'A¾OVÕ½é å=W6E>þß÷=Še~;>+½/2¾yÀÏ;èþ½9\½'¹s½Ü¼ =2!Œ>%‡Ý»íɽ_û¼^sÏ=3}=J`®¼-ÎS¾¿($>ë?? Ïÿ={ó뽕÷f¾ìk¼V¦º=òø¾W0V¾Jýx«¡¾Gœ!>Hà ¾eâ=}1I:†v3>p(“>?%>]‚?¾F®½i‘‚¾¿ÍÍ=vÖµ½¶°‡);¾èÕw>æ…T>É3½^c„>έ5>ä >扣¾C̼'O}¾'èi¾ M–> *"¾’2”»N“=2³¤>Ì’=¿‹»É,Û½íÀ,>†ïuºûxŠ>äÛ½8ú‰¾ZH¿½3µ¾*š¾…U´>Ö‡½â”¢¾Ý3i¾µ+b½àó>QJ¡>ó½ï;ԒžC:νL >&Éþ<ä®=j÷·=}ϼ٭ȽΪ…> ÓI>p>û»?ûü>D‡;¾$ÿã=>㸽B©+> Ün¾`¾x©J¾æ ù=ô‹=I!>ßZa¾b>½Ñ©¼Í¡§:…Š>]u€¾)}<ϼ°>’V±¾ü0«<Æñ>U3p=Ð>=8´b>.þ½·#Ÿ½–!™½˜çÛ½„˜½Ÿ¢½ ƒ=’?è=£Ò ¾.t*>æX=¹nÜ=_F'½^"»â>c,¾Æl‘¾æõ¶>í,>þ:>¸°>ï£8¾@áy¾þ‰½Ïí>½âÿÁ¾q¼º>jª´=€–”½ĽZþ;=”Û¾>q¼½,È>-‡Ÿ¾Vqë½u>è§=Áé¼=|^¤½÷õнø˜¤¾ýX>å¾äµp¾\‡7¾ÕXy=¯/½“Z^>©7S¾GâM>Xb¥=e>n"¿oE½)dr¾F«¬>ƒ>ª ˜>°þ‰¾ýO=|-u¼®á ¾¹g€gR«¼ºà=j‰¾ƒ¹+¾d>fðG>ÕݼTl`¼0«>§Éá½$‰¾³¤.>Ääž¾fp¾˜îI=‚í"¾,ó¼ ÍQ=~#>Ã×½´X¾ÜÌ,¾;½RB€>ÔD»qC6¾•\H<”…k½:â½õŽÒ¼3““=è>^ܽí·½ª—¾hñj¼ æ½$À¾>“ýU¾t§>ÜãÞ½°iS>×¾ ¾ µ¤¼/Y\=á=ë=k s½áÀ’¾Ã J¾º„>Z88½ƒ>@½m Õ=fíÖ½u ¹¾¤ÔоNj?Yâ¾b9ò>¯ºl>4;¬¾ÅÏ>ší!¾Ö!ǽ£Æg½HÁ¬½P’½Wï>Jã9>]++¾þªÁ½çc÷½ÌPš<÷Š&>´ïнío=_M0½–ë>D&¾V>¾t½t=) ‰>8³ã¼±¾U3>|S¾<ª>Ÿ®.¾šŸ=Ï’¾šDú=è³½½¬ >áÛO¾9f;¡ˆ@>¶céÈQ&¾ÿ0Î=k ¼‚=‘½a‚,½¬0ò;¼X½8í½C¾yu=K¨¾­ïÕ½ž )=ëï=èõ½s@4>¦ƒ3¾"öƒ>È),>•9¼ûùD¾N¾'>Ãgr¾Ó–Ý=šFX=Aõ¾|ÞÉ>{só»D÷½S¾öòǽG=:M0¾àÚ[= (‰=Nšµ<7õŸ¹7D½t½[‚ˆ=ž%=„“½û²m¼ö°Ê½wn½ïz޽cå½$Dà½³Ë >éÙ=x .¾PÉD¾rKŸ¾a›ˆ½NJ‰>•”>¾¥šË>”¿<¤?l=‘}^>`—…>A`ˆ=¾7¦;óú¿©l½ÎH>y]Ž>Ú7=࿾).N<µ(”¾U‡>ºõC¾Ø;ƒ=¡Ôß>g„À¾Iñ½­õŠ>ú =°wÙ½„•j=ù’ͼ6V½S¢É½÷¥/½Ñ‰=™ñž¾Ð=ï$Ê>ía¾OC©¾£Ø„¾àÒ¼/@¾iš$=©„?C½w•=ùχ½y6½>‚ï°=^¾[·§¾tBP>Š*=ÄÔ½Gƒ>‚¨…¾V˜>p¾¾‰²>xÕ̾ekü%>ð‚³¾‘Í>&:¹»žû‚¾õû¼9„ø½ëÔ¾´r=p™²½@¹=5Õ¹ý¾çÉW¾g`ˆ=u?&>æB…½Uã¶:÷F=¾òüæ½=÷ͽXÉ=j!M¾'¶;3Nº>§oæ=¹\í¾'œ{='U¿¾Ó?úà>wò½,X¾ŸÎT>_M‹¾.Š>¨,c¾‚¾p>ýžçù=B Parameter193*šd"€d ãF½\eº½¯7×»1|¾ß=φ¬=Hn,¾ƒëоMÛ›¼ˆÙ=œÑ"=î@¼1i½{¾ûHæ½?=‰‡I:‰·ª=Ed9¾*³ò½EŽ];ª€>CF>÷S_½57¾ÝÙ¾FоŒÖ„¾!÷¥=í)´=<<ïj¾#ƒ½x»²<¥GK½¦ˆ->Dq>û5Ÿ¼F ¾DX/½>B‡½›>>U×9>ù\¾öœö<:4¾o©è=m#×=²R=²l=gɾß1 ¾ì‚¡=P•î=r ¾›ÿ½òT¾Ð¾ü¹{½³­½uûA¾jìa¾ËÃ]¾‡±½¤í„=­Â„¾‹ÛÔ½ {ª¾Ÿá ¾+Ÿ½x²ç<ík·=§Þ½ð?¾.e¾ØQÓ=gþB½Ù¼<°¼NëµÔ=FL´½¾ü`½Œ=2gØ<ûƒ¾æ«‘¾"¡U¾¯’<ÝŠ=<¸ ½úô›¾í¬¾hÎ*¼„zð=÷->(W‰½~a=þ3>,†½mQ¾Íž¾÷Ö½ y½="”™>Öfz=Ce¾Ù÷^¾<—±½ƒx = ‚ù=Ùܾ³º¾`µØ¾c½½ÊY=h'Æ<–§ƒ¼™ö½U&¾Ÿ"ƽ_=½’]<ÌN>_|¡ßpç>ø‰>¾­ÿ½~_¾Í½¾â»›½P׃>ðÒ=O…=’‚×¾itŸ¾Ï„½«?=I…ê=9Z½Vm ¾ý]ˆ¾Z.Z¾ï¿ý¼Ûš^=Ïž.¾/[”¾Ÿ§)½5|¾ _Ë=RîG> ɽ’}о]¾ºA¯½/•;ˆˆ]=zz?=Áñ$¾Ú´¾A`x=p:Rï4Q>Ó¸½){}¾•»>OÒT=@ļ_ÍÌ=ˆøi½¾X›½RLk½ÿâŸ=ûÔü½ã¾1¾Ìi™=Bà[>ÄÚ˜¼X>¼S!¾Ôò ½öj¼¨!½Ð >å€>C'3=† É¾àŸ½e“>G!S>lP¾ŒÇE¾ƒš1½sȸ=GH^> ¬½ßO¾ž¹¾ƒŠÌ½.Gáºñ'¼¾†Çž¾c/Ó¾õ¯½º¿¾Ýoª½Ÿî„½¸"ˆ¾_èq½U‹Ñ½8í=·j >‡½B"¿½¾â;—Ú¥<ˆnk>Ô¿¼ÓH/¾(¼8¾̽<<¼¾DV¼Îý½Ï„®½¬?c¾x¾Á(=¾ãKf½ç™Ã=ÎXÔ½3Û-¾›#!¾=Ùî:?FÈ;ò“Ä»ÑV>mÝ‚=\)>H¢>f}k½e6²=“F>=º >#H¼Ëâ>Ùϼ⇟½Wú½¬D¾’¯ >Fö<˜b’½w#!¾>£½š#Ç<Ö›×;¾7Pè=$s>$Y‰½´P¾yƼ»ä^>³Jv>¶®i¾ t ¼#ãH¾Çä¶½9·6¾™èK¾urž¾†³â½Æjô½Ä*=+<<凹½…\%¾†~%¾yÁ¿½ÕDÏ=èl>#«>¬‰"¾iFå¼Õî>»Ô=÷P>xYÜ=œ8>„ŠØ».ê¼ÿ$5=@L!>l-¼=çh>«¥²¼÷ID>:ë–=•fH=Ü™ ½‘º@½ij¾U½«®)=Lùž½ç±…¾|Ǻ½¹1¾;t¾Õ®Õ=`uÿ¼)ì‚»†UL½ÿº >às^>V}M½Ñð+½Më½!½Ñï <ty>/: >ë½6¼ \¾Ë`ä=ÏÇ•=$ʳ½}-I=?½ñ @¾üc*¾ãP½â”­½ÂÛ<*#±½Œô>¾h7ñ;o§ ¾êv¾½¾ŒÙ½©)Wº=ž½§æ=ÚçÈ=‡F"½"!>Þ>³=—tŠ>zŒO>d€T>ô#=Üܾ¨¿½sáܽhbG¾m¦Š¾Pp ¾ÒÚ¼L¸Æ=–Z¼aRw¼Òò7¾p½l¢½ˆùa¾…YÜ=ƒD¿t¬ž¾sÖ¾Ød,s/¾"´ >§hc>Õs¾Þ.¾Œ:_¾àAK> û,>N-|=[/½Ài‰¾’̇=¡u•>ÒÌ=›Y+>cÏ¥¾WŠ„¾Z·”½žH¾v‘=‘g•’›>çœ#>–yð=‹2>)t^>FµÉ¼}T½Ð«¤¼PÿÙ=úvì½ðÑ=.‚½=›î½ ¼¾딽X =«¡<.|Ù=ÀT©= >›—¾ºD¾P®•½ÅC=­N>GÒb¾NU’¾RÉɾ}1¾+YŒ½+.‚>ÓÇ=U–o½ÚŸ ¾éƒ¾êÚ‡>Ûå9=êø~=#Ý7>w3>6Nµ=ÀX½<Ƚ½ùîŸ=Ϲ>½Œ¬½ZW„½Oˆ¾ö"¬½@¸h<|=ä—(¾Ú€z¾;#½¾ÇÕ8¾ó»>…ÁÒ==¿Ì½|¾¿ŽÍ¾±¤=gPî=ÞüG= âS>~Ö=s˜»*w;(ZA¾Bw»=‚£µ½ >>½¤%¾ÒN½ª3G½ˆT½tü†=̳<|£¾4IF¾­Hо^LÁ=ªÃh>ØLÃ= Í&½!£s¾?k¾i:$=øñ[>è/Ž=.ü›>ßkн–#w½Djc¼”§d>c©ö=Í€½Ê1Ÿ½‹Ñ;½B‡½š¾¾6b¾_2%¾rY‹¾Ñd‘¾¹Î\¾= —½Î¦H¾å‹¾1h†¾VÄ>¾¾Ûf>$„­ óƒ½[=b¾‰>åö°>ùdQ>m½Ô¾! |¾ ÿÕ½‡7½Â Ü<¶P²=wÌe¾~j¾CÕ;”¼õ8¬>!;S=³£Ñ½ŒÖl½þ$¾“è¼?V¾¼ì²=¬CʽSÏ ¾åIP½O#¾af¦=(òo=¯g½$²=¼¼¾]>§ì½ì€§¼ƒ©)¾Ås©¾›˜e¾)•<Ÿci¾¼Ž=˜Œÿ=ËZþ=¬žM½U¾:hÀÇ=Ëá…>gö-=í»½56ˆ>@TE<1‚=ù]•>kØV¾(Û¥½ó¾Ô»Š<‰×<P“¾^¾ðRí½>ì)¾ÑG½/P»½éQ¯¾€î¨½Ãé¾™¿|=¨’ƒ½«±×=)„=®v$½Új+½Àí¾½w>=¼0D¾&ǽ™>Û¼¿¶¼„Ø;¾Ú=‡Ú`=5ug=rS¹=‡*•½¶ÿ&=ø‘<ÁŽ<ì®=h–=MO—=µ‡I>ï>˜¦½qƒ¼½­¼‚…=)`ˆ¯€‚¼ÒÇ?½6-@¾2ؽ'ÄĽi4w¾Þýá½£ÿ½×F¾Ø3X¾¸·½¹å½ïƒJ¾ãµé½ct½…-Ê=R°G»Ö?Â=Ÿf¾„p¾ñÛ=«é*> ¾šÈͽig½±u-¾…â=Ðþi¾|<Žx½Ð½Ñôѽ+oº½¦PʽŨ¾÷§½Ï‡ ½‘ÚP¾­¥¾o*f¾Ä «½rtÃ<{º$>ø¨!¾¸r¾h?¾ÇÞ©=Äáü;wB¾²Ü½8’¾ªë;½ C=Éß¾\})¾~ö=¾T‘k¾«Ž6½Ç)T¾½ËÄÙ¼£ö¹½³ì¼éZ¾.¾6êN½†t$>czÖ½¼<¾Yn§½–ŸÅ<Ùé¡>쨼m˽W<‚¼§ÜŽ=éÐ>FÑD>ź¾²ø¤½~B>«Û‚>Ù¨¹;†9‚½  À¼¢½é<ß&ݽŠ ½¹çâ;Tvõ¼L€P> Ó=ç_{=<ëØ½Î=-éˆ:7ÒD>9Ro=ž-V¾¬Éâ½HN>X½Âç]½Kƒf½:;->1>á-“½ Œ¾rEÇ=é­°½¤=E¶ø½—£4¾Ï^½Ñ+ø½ M…½»ï=¼Ür>¾]Å3>†ˆi>Imæ=«=lÐö½Õó/>Þ£Z>?`=ÏUƒ>ùå8½¢ ½|ñ¼%Ã>Š>ð=š»<”7¾K"¼y×=éPç;°Ju½¥›’»R˾µËˆ½»/G¾dOD½¹fÊu‹>6Õ>Ë/ß½ØÉ»`?n<ï<¬-½ž3¾›¼‡¾sO¾Âem¾"D>¹>#³¾†œa¾âR–¾„Ù“½çnV½CP=n+>9ï&>v¶9½¯V)¾<5!¾;›°½×–¼yÁ…½M×ï½v”¾;þ§;½´N–½Î™@=eÄ=‘À><¥‹È:À/‰>“[Ô>»ì©>vY=ï麽ˆ}º½q뤽VÑž½žÕ¾*8޽ú#¾<^k‰<¸ƒÕ½Ëj¾­ ¾Øa¼+ß¼Ú÷ø=ÈþJ¾ð[Õ¼†Ò<ýÍq½ ÿf;ÒNÁ½ZÕ¾„¤q¾§àܽà ‰¾ G½ù[Þ<–>‹ x>gè=ŸÆÂ=|O”»Ë€=3<ò<õ½dY«½Ö ½K =f?v½Pý¯=A~½¯À=«ç=Ñ,•»T½‡½®×½d ’¼›Í]¾HЇ¾uÏo¾áa ½çxÈ=kM;>é$>B3{>€B3=öJ>2Ù–=G•e>“Üu½@Û‹¼Á?ƽ œÃ½(Å;6¾ë9Æ=Êh$>3‚½þ§‡¾/ãP¾»ua<F=ñ⿽*!¾Ê0’½Î?Û½Üç½=øÝ¸½H¬½qÞ¾ÊÆ¾nä;¯eU=ÚÖª<>æ=ãRÔ½`,=ÓN‡=ŠÀÞ<1b<,ã½Âýl¾-Â;=㈨=ûÚf¾ÛÒ½™#ß½fȽª‚û¼¥¥A>"Þ5=Á‹š¼Oˆ ¾3@/¾|9>UVÝ='΂¼` ¾h_¾uÚò½U¸½H¾Ñu½3¤¾½»Ô(¾¬½Týf=O€¨=ºç¾êü=§:§> #>ª%½Àâ¾½9†>¿ô£>'sï=¾—>ŒÔÒ>EÓ‚½i>Å=NÂн”½ ¯‘<ûù½ªõŒ=Çû¤¾Šå¾OCY¾ 3½7R¾d1N¾ÈÅg¾Zj¦¾×=½ú¾@à5¾ðK¶½ÿ¦¾ÖîØ=â–>ÇÓ¼=DܽO5ï½på=T}$>2«C>ÓŠ¼’º½¶<…F´½+jb>Ô×¢>ðó ¾T¯w=—œ$<’jf=ùe>­Ÿ'=ºI„»ãI:z>sô<—L ½±oR¾»1]¾|ÎÒ¾w>J¾L–]¾ ¾%ôc¾Nn¾eS‰½îï¾:Èå >ÂU= ¿¾ÅÎ3¾ !Q>8Çc½´ä¾µQ¾¶6=ÖP)>ƒ–Ÿ>„:<¥Av=¤t¾Nà<ü.=½Sv˽=3¹=µú½5Ýf½KFʽWyæ¼—ÌÆ½S‘ë»68¾Åƒ½N‡Þ¼TµŠ¾?z8¾’¾¾Nõ{<a¾º¿“¼xe¾^*¾[¿¾òо(½_d">òcå=kô¤=4‚I¾°ÿµ¾®Á >Q^_>°U÷<±“:¼Tñh¾v;¼HB->ça>å€=4 ¾’P8Ã=Þ8¾=o²º‡É<=ÎrÞ=&ÏŒ½Ñ’=hÃ=”zƒ¼A¶ ½˜¾)¤ÿ=·šV>ÎC ¼²9…"‰=›¾ÊE=Ý">ßW+>Y ñ½u¾‡t½Ì‚ü=zͽP½w’½VIQ½-_\¾K}!¾cÈï=;·)9©Û>ížl¼Ç=¼#Õ½…쫼âûü=+\>€Vž=Ü;¶=]ª\=J=;S½ «$=˜v=<¤=`¿L>qü<> £½Sqx¼à·Ê=yO>²ê½B$¾±,l¾©z‹½³Ö›½Ù›=Þø2=ãrv½ˆ)]¾ˆ¾«5K=7Îa<4Ð ¾Íl>H`»=½x­½8s™½x¾<ä’>÷}”>ÚÕɽ# ˽MKE>Ç~|¼¥çŒ=ñ?6¾ûQe<žü¼ÀÚξeº¾3É/)¾dyª<îŠC<¬Ãˆ<Ò|ü½X‡1¾%ÔÚ=Ì=S¾ÙA'»GM¤½e‚¼c@_>÷¨J¾C`+¾Æ#ƒ½5¾5\>Ò6¾OçØ½ÂE†½ËÅÄ=úBá>Žrì½ÕðC¾Ê{)¾R,¾Ãr½(#R¾'Ò™¾9à¾~ßR<öš%>¡eg=æP]¾<¾ËOF½…=[w˼}—V¾Áø¾u9U>쩘>|«Ê¾ol¡:3|$>üË>x0¾vŽe¾Å°ú½Žöû<"û®½èu¾q-T¾=ç½MŸ½(„û¾|z…¾èvÀ=¯Hw½(0¾<½ÍRq>@ðô<âõ¾z-ï½ s‚=AF>éF•<Žjæ½ýo¸½†Àt»j *½p¼â<©P‰½ôUݽ •=·ÝÄ9ì¾ >UB£½”µM=û´˜½¢ =˜Å‰=ð¼ <Ú²æ»b!»î>–¸½þñŒ¼ZÞB»æ•&=»v>@E¾åÀ=eš›=§½³‹P>»–_½âû¾dÜ.¾÷&ª=T>=_>àïg=|¾Ñ§R½[Ì<6Â=¤³=ï<¾”¨§¾nw0¾Y¸>ÿúã=–bú½‚ˆ¾¤Íc½ÅF­>R0¼?Ê ¾9/¿¼e"¾—>¾—*¾^=±º>ÙO­»îþ¶½ŠÛ+¾¸Ô> ¬=1è³»è0¾¬ö5¾ê4‚¾IrD¾­\¾¥Íø<‰'¾‘¾õ'¾ë¥¹¼È¹"¾¯¢°¾HʇhÑÖ>`5¾ Ý&¾r>q¼N=?Â-==§¾§<¢¼·Í°=hÔ8<ÖÄ1½Ë Õ<ôå¼=*Ð ¼8”»¼°à½ÌŽC=£Ë°=¥!²½©ôÓ½íÒr¾s%y<€Ö›>áí="Ä+¾ óÜ=qS>!">‘нÍg>ŸVœ=RP=b¼ˆÆz>#†2=ã>Tnî½öZ@=½¤¾ –¾á™„>™Â>¬Þd<í-9¾`:û½jä]>E> æ}¾ÆÓž½¯Ø=‘Ѱ>oõ>³¶8¾Õ:œ=—Ë%½ë”¡>nJq¾[Fʽ;-ª¼%À>ÿnÿ;Hd½­&½MG”<ݳ›=¶ð½jF¾Ÿ@Œ¾)8p=q ¢;À ǽÐdI¾‚©½çb=3½±Aƒ¾‡$>YÒ‹=%À ½Kø\¾FM¾c{2>‚!¼¼?1¾òö<—½×–=’Ì>g>ê*À=ŹËLò<æ]§=Õ}4¾›Û‚¾}Í=N&ç½]5i¾Yƒ¾ J>˜>{ ¾Å[.¾–[=¾Ÿ§r=Ìt©=‚nú½¹ ð=°z>½×­>{¸$¼æ«R¾ ὃ¼F¾°½9`¾:机£tƽv’¾×F¾u—¾ÆŽK¾£}¾²í„¾ªÙ½íq¾îؾlý±¾fØ´¾„°Ü¼R9½>û >µ.†=¬Hð=ú”<>ŸnQ>ÔT¾Wd ¾Â¨=½‹‚=—=J=7)9¾éx½Cj¾[ˆã½Kmª½ØÔ6¾¦ÛŒ¾ƒë1¾A³k½T₾Ì\°¾ª¾v—¾CY¾ž×;½ã—8¾& ¼ñ ¾=€rž<¢æW¾0E»ëÂ=ÒN-»1޾,SǽõÄ>=ß=-b‚½0ʽ=M¾¢û~=M|¾éU>´Ž= ‘¾Œ× =cd=ªÛ™Û£5>m>QV7¾©0–=­s>h{#¼%¶Z¾™H½©A>5zºã¬x¾¿!¾/\½Þ®Ø=6…¡¾‘£¾S;È¥>½‚e¾½›¹Y¾xK.=’äº=B,¾Pê绻百Ó]r¾^[A¾o}½€}…=anŸ¾Ÿ3+¾cre¾+‰À½ckÁ½GüS>?\?ñÍÒ=µ"¼§àc>}YÅ> =©õ?=nOu¾:£>ˆ†„=z„=>¤³=˼(¾Ù½ÃN>põ“>lùß=$¢Þ¼¼*¬¼ùqˆ½€ên>9ü>ä@¾TŒŽ¼´ëØ=÷Cоg4¥½k4Ø=:œ6½²…¾Î:¾×Áß=¼Œ%¾d5=¥sÏ= !Ä=„#K¼1üâ½{Mz½¸Ž›<ô„Ľ=t¾¾¾÷=\_!>«˜>> ×S¾_›L¾Œ{O>Û=ã¾:;о;¾²›½½ˆÓ½¼Å™¾X¢y¾a‡¾ù±\¾W–<³C=¬Ñ¼%ö¾ 2¾ÞÆQ½¾õê<мîû3¾šC¸½m²Ë½½˜¤¼*ƒ½¨Æ]»±C¾Hñ;ÇU=¡t@>™£À> .>¹#l>r‘Œ>jD>©ñœ½þþ¨¼‚éÒ=áwÛ»š{Þ¼Ì “>нf>+Â:>è¸#¾‹†=ìBÖ½„>¾O‹•½©Z/½¤ «=®¥¾¹‚Å=±Š¾`ï.=§Ì¾Ì«`½Àñˆ½‹¢2½ $<¼°¼à½ä–§¼áÿœº`ʼQ™½Æ¬ >ÅÒ¼w1>‰#->‰2_=Jo>Š„R==§½×Œ¡»ŒËÕ½oº½;„¾"3Ê<¾=²·½}‹^¾Éµò½%9«=ã¾j™¾pq½T>9¼jR>Œ <\=­sÛ½&œG½<_…¼1ë=Tõ@= ´²½»}¾ç¡¾séY¾YÓˆ¼ÄÕ¾aª¾ ò¾’á¾hœ”¾ò(°¾¸=¼¾Ée¥¾E]Ò½% ñ=ö4K¾—j¹½ˆ"i¼ê¦½Ýc½ï%ýKqf½*±=%¾I!Ô½Ñ_>>&>`7Ó½ 3°½éK¾ÓMʼó†½å5M=s Ó=Ò7>¸;C¾ÒÓ½ü¡>œúÊ=õQÕ<"@t¾r´¼÷o޾ŠW¾‰o'>Õ¹=&a=y™Y¾Ì3=l_>[ß>`@;*Yؽ¼K>ò€4<ëu>0%ª¾qc¸½Q±¾0_¾«–<¢ŠÌ½ƒ–»¾í¨Õ¾HTî½B«¼^Nó=í´3>¦B =Á9¾ ¼Æ;a8½Œò=¶rÓ>pß½>ÛÒ= ï!¾¼i>š˜>9²¸<¥¾æò »™C>Õßæ>è÷“=©¾= ¾Zkc;©CT>mö<àÐ=”‘¼¦…Ò½µS‡½‹T±½…ùù=ù’E>Ô¼ùЖ¼I·4¾¡¬½¨Tñ¼#:¾#Vh¾÷%¾ñ¾]zÌ=ð|•<™p>Ô6Ý=Œv ½®tU½W’½ý >‡«(>‚vF¾H£ñ=~ݳ½É~?=îq¾Ý›÷½~ê¡<Ôœ(¾i =´Š’¾¤ ƒ¾bϦ¾ =Ù=Gëí½¯ÃÂ½Ž†Š¾¨Ðï¼}&0>Yó¶½çóî½Þq¾j}¼ºÚ¾>£KÙ½¾•½²Í]¼GÃ<^>Ca¶=;{W»ã™ž¾I·N¾âV¾sfê=QŒò<ü5Z½f ޾¥mH½ù%´½Ÿ×½Ûß•½¥V¾À¢¾IÍ?<¡2¾©»¾ßò¾þ9í=±ø¼ô±8¾ѽÿ£ƒ=¶2>>2IP¾ýc¾ŒÙ´¾a%5¾Ðz>DèÆ½Zí[¼úòU½ñ„½=A•>äö½h#>:@*<Û="/¤>pÑ=Ú”> Ž>¨#>Ãò»>˜@>.=á¸Ç=Lp¬½ŽÏ‘<Ø—Â= d½½xº=Ô½=žk3=MC½z ^>í{¼>E¾O3™Ý8¾½•¶¥¾ÁIY¾82*¾†;B‘x=…( =Þ½T g¼‚ݽkdÜ=÷>‘¾"¾¦3ĽÛ~½iH½Ø¾r,Ê<¨ÆÞ<]~÷¼­§W¾Û°Ø<Ë >EÄì=ØG½HÏ=Ý%‰>5AÐ=ì>©Šª>j&œ<ß”½]Ò¶½Wœ ¾T¬K½ß½Š=MS¾úZ§»ää<ŠQ4½HdÔ=’él¾óǽ·˜½ž·r=98>&R§¼œ~U=e=€›½ÔBû=Ï[<=‚Âå=œ¥(>âvN»öS¯½GŽ/¾aæ9¾ÂÁL¾Ò¥á¼ Ö½~/!¾êy ½€\½Ÿ8<*j8½ö¹Ð¼,o{½Œm®<¬~†=\!¾‹/ž==n)>ó=Z¯k¾ñR>š¬i>Â-£>-?¾ÊAȾ‰Ç˜¹£M½I)뽉÷ø½Iåý=å;c"½–ã.½ª:=Áv3¼µ@~=Ï Š½Í*k½byB>7\Q<Ÿ5 ¾ØóM¾‰•á½éV#¾M½¡}¾Ú3'¾Ý>r=Ç8D½Å:Ÿ¼s-¾Az¹;±íš½c%,¾d€´½ý*¾{äá½n)½ B,>'ÿX>šm=Xe½C§Ò½"×Ú=eû?=ID½—¸C¾ž]ð½óC¾ù3¾ëC%¾‡hŒ¾vé½µ·»ÅÈ ½Èƒ,¼]%¾xæ ½]žIë¡‚»³¯õ½êÑN;K=]< ½0yƒ½Zü˽¦=¢½’}̽6™º¼ú=ÔQ½Yš=­½½º?o¾À4¾Rrø½×ò=4_Î83-¾Î ¾IB¶½ÓW¿½$b¹½&À™½Ø—¤½‹êļð~ç¼w.Ÿ<ÐlC>….Q>Íd–:ˆwï½­1q>3Vb>Üb³=о‘A£½¬^u¾rý>ª_j>¬s5¾Wƒ½ôe0¾ÂF½¹¾½û‰½­3ù½Ò§w½go™=Z«½0 $¾‡<Æ=RAÖ<ü(í¼ý‹æ=‹4­Å‡ >@0¾\Mƒ>ðé>7·¾Ã¨»fØÖ½•¿=éT¾Vƒ<`ù̽ÝF'=ûç®=¦g¾õnš<žp»ê™©¼S½Ÿ¼·=¨ö½tø‹½ÎŽ›½ðÚ;ÀZÿ=ÏQ½U•޾Ôñ´=Í¿=$”S=¹ÅQ>˜â’>^‘(={§-¾Yĵ<ï ½à\Ÿ=•/ög[Ô½Ù”s¾!ÿ<“t=x=I>ØRt<¢ÂR½ñö<©Ä<ƒ ¾ìÒh¾áÙ2¾îK²½–Õ§½ÍÍ<#ÌK¾-Ÿ¾Ù^†¾8U¾6—¾úÀ¼”’¾À»¾Ú¸Ñ½°~Ð=m/#=Òÿ(>3b½"]!½¦{ã=Øeo=á,Ê>m= ‚Ð<S>aÆÑ>[äð>gøõ»øß<ÁYÆ<¥!Û¼³¾›ÃÝ;G£Ã=‘öx=ÂÔ²½±å§¿·6鬽î÷Y½@m~½´8º=ˆê<Æ-=Œi$¾Áo,¼67V½ò®1>­yM=~νOL¼ 4I='å¾®dc=8tÉ= ¾#Ö<€2¨n’½¶:D>R\X>2ó»p)³=ZÚ=JŒÎX>⪌=#‚¾Èì$¾ä;<ˆý/=ê·Ý=Ï&¾÷n¾y¤:’뻽m`‘½¢Ï½ù»Y¾ËJk=1¸c<ÁòWºªÖ¾› ]¾™RÚ< m#½HpV¾£Šû½ÿÐ<ÜM=^±ð=?³õ½S%.¾Ò|ǽÇýP>OìA>,C*½Î?=Äe`½ž š½  £½ýlÞ½¾ºAœ¼Z{U¾ä ¾ÌAT½G= ¾;ƒ¾ÆüA½úˆ=¡%¾íA¾fs¾G À=S„=o…0>©ë>ιˆ=;/Ž<%p~¾ =ÎÙ©=Ã>½>e¹/¾‚^Á½jÏM;žÜ@=)t=l{¶½³=K|1¾–øÿ½/çõ==k¼¢²½;T¨S¾®¨=þZ–»_|¬½¾#D=pY<9ð(=iŸ½…Ä5=Zâ=Ô†»Ùd7<8bl>;_=D ¾FœÉ»‚wâ=¢#>_7³½äK¾oAK½r^źæy½4è ½©d<—JU;O?¾§Í„>Vbö;“`s¾(¼S¾®¢I=—Té=$¾®“¾ÀF=¾ðŽ×¼ Rf¾TB¾r*S¾´`¾‹d£½CB¾µß ¾Ö ¾¡ig½Þ¢¾÷`D=Ï~n=¬«¬¼) ƒ<~z>>Ò9\='@ŠªPÿ;õ4¾ ¼›6[¼ n½žz_¾ª›5½Ž ½­q5¾‰@ˆ;qî½Û/Ã=" t½†/½“ê½T‹¼|_<.oB=ã[7½ – ¾/½CÒ½¸q(½aWð<å‘v>ƒeN¾nq„»ðÕ>cž6¾ª@»+u¼fF>9–=!†½6%V=Óé€>ï”> ä½:Q½Ñ¿¾ª«Š½‰ób¾îY˜½¿ú<7òO¾Jïc¾·œì½ñ¹ß½q67>‘F>é–=3rf>ŸZ„½çgÐ<°¦=îm„>̇ >¼W™H<xÏ= 9p>;?ǽÚ݆¾@•”½Xs|= \ý,ú@¾Xží½Áf¾ÂĽYb·½•“”<$Üȼþ³Î½D­V¾€¿=nB½ZgT¾@*H¾a‡4¾3*ð½í%>à«7¾ø%c¾Wfj=î¾’½ãÛÔ;Q>‘‘?oRL>‡ˆ¾O””>)î‰>W© >O´î=ÀûÔ=½¡=y:…>x^%>µ³H½¡ß»½#'g½ˆbK¾±W‰=Éw=Üü¥>§ >ôh¼uL>ã3¾i >áí½¾/¾¬)_; dè½Ó*=êl¾õÝV¾šž<³ –¼Už½«Ä¾!³e¾Ã ù=ØlÀ=¿=v¢“½ô›¾Á!À=Ch>£ô½.ñP¾5ͽ2Pª=oŠ•¾‡4¦¾àã ¾iŒž=Ærh=ÜzE=pö*¾¿Y,¾¹¨ƒ¾vB´j§=‡x“½Óн=-ÝX=XÎÑ=éí1<ŒónâQ<5ï >$tì==Ä:¾õA™½êü>·jD=éã³½Ö%¾ð½™ºà;"°Q¾^/­½~aÁ¼ €¿½hѽ(¾rÖ½(Jî=Ô#­=ÂVsÈ Ù<©%+<…”ù=rHù=®Š½üSF=äcê=ÛΖ=­I=V:ª½9ê½VôP¼º÷ù½¾U´½Cã)>÷(¾G >̑нAà‘¾äðI¾:ž>^’ï½@U"¾?zq¾;F¾ÍÕ¨=B-Ë;éý<’0V¾ãºG¾šc½u!¾,AJ¾Öª¤¾Cs¾ ú<øßŸ¾J*ª¾a¶¾…à•=¯ÎP>£',¾t}¾ZÄ]¾cð”>ÕÌ>úø½Š3=ô±§>3¹>¾©ÛÚ=*༬qI¾âN¾ H‚¾D*Ç=Ú¯l>,—“=ºý=\¡ó=3C!>w ÷<µ¤´½ÚÚ.>Ì~">“†¾¼ÜT¾(dM½þIÔ=1¾×O>㼡½­&¾½`Óý¼6è±½¤šY>Ûv=wê¢=LF½ÕŸì¼ô½•…ʼì½ÞB#=¾Ù½®÷s¾¯¤ ½ôǽ&àˆ=H$‹¼‡-Î=‰òÖ½;%¾‹„¾ù»¾ø£ >FY+½Ž®Z¾jº(½ã¾Žv½E= D¶=·ÍÚ=¹…n>ç/'¾Ãl¾Ÿ¾aœ½aC™»°b=‰<™½Þ\ͼ‘G-> «=06>:ö[=…øKË=IV‡>¡ =5G ^>^š½Eò >LÉ¢=ølW>ÒLa>=øÈ½þ:½ô»"¼ 9¾¸•H¾¼$<¾Wpà½Bí›< ö>ïC*>g޽J”ѽIKs½>½5½Ò½;Šv½¼¾¾FV¾ïF‹¾:¾ÍŽ+3*=]wŽ9M ¹½¹Æ¶½Â¦­=ä¶¼˜>ƽý:ð;GÅ/=4’¾ÓŠ7¾×éA¾Mÿ ½“sø=…±<Ã眽ÞG¾6XŽ‹Çy>â0;>EΙ¼õ"€¾GÍn¼T¿ð=áÛ–¾€“¼<‹A½z(¾D{ˆ½G†•=†>½ø4ß½’‚;ï§®=ã~,= !@>/¿L¾a¾8Qɼ”ÈW¾ÖD¶=\j_=!µã½Së4>øˆš½IáE=´Þ[¾ê+ʽËÑ=>=¾É5©½Ñ«Ú¼ËÏܽäûν—>Y=&Ç!¾$킽üôÉ=Ióô<Ù"Ç:Ju…>&f=Ën¾ºqž;%ÿ2¾ŸöI>2j‹=ši…: T=óJý½0¡¾$°¾×j<¾¶>Õe?¾^2¾a‡;è·¾Œ3^½]»<ÂÆ_½‰4-IÉ=`ð«=Í€½C§×½Ùý::å”>Yi¿=Ïÿ<½F¬<^À£=;¿> +V=2d,¾$§=©ïè=ͤ>qÛp>““?>l >C=í½2í¾Tï]¾J'¾z€j¾ÏC¾v=Ƚ’*½“æR½{î¼gʽzˆ¹½¿J¾Ç–‡½³ 콘Á§=áØ=&Õy¾‘õ¾´Ô½HÕ¼+/=ƒÏ¾°=nÝ¢¾E¹>yÍ^>Ž}¿¼ßyJ¾¾jv0>gé1>ߨڼµt½¨q>mZ5>ÈKg½(G¾l‹æ½çÐ= Wƒ=¾q·<VG½ôŸ‰½ß?$¾8™>>sÀ=ׇT½d R¾ н¨„À>9†‘=|P¸½ð¸µ½8á=ÛFŸ>x#½ô0¾‘©´½&G¨=ÂJ5>ýB’¼šý…¾‹^&½91>Z怽‹o¾2+¾ †½L­©½™S<hû<§'8¾H<Å6M=yÄݽŷ:¾«Š›>HŠ£='ƒu¾§N6¾²&‘¼ôx>‘8}:#R¾ò‚«½¦––>«Žr<Œ«Ù½Õ*s¾¯9…¼«²¾šÆÕ¾÷s ½ö¼Éj%¾7 ¾¯¾ö”z=ºˆì;Ú½Y¾2X½è.=¡ðP=Y¶½Yºœ½Ñ‡¾K7=.Z"=9µ¾0{ž¼ÖV´½…3=.£å½“ƒa<6=¾Yj¾ö¡¾0k¢¾X)ɽ•ÝD<áS°½¶Ù¾+7Ó¼ä¿!¾K'<ôš€<•?2= ªk¼OÀÓ<Ž•> 2¾¢X¼eä½{¾è=Øø‹½ƒ;¾x†½Tå½ÞϽáš¾¬±ö½óši¾:w¾¡³½c« ¾4¶Â;k޾TZ¼µÕ=ô¢Á¼z%Ç=÷ÞQ¾À=/=5->…¼=bC?¾ªN>´‡*>a½>¹!éƒP>:Ã=~æïI>Î_ݾCŽ=ìn…½²q¾ÇøÀ½.ѽ§I»åºÂìU¾?½{’U¾hòR=ž¾’½_<Aé½B Parameter87*¹" é¼Ý—r¾#B¿÷8„½ 7>À‹¿›Xó¾H!J½Ç©D?éä†>kÈû¾VÏc=nm‚??Ö!⾪]#¾ »?K“?Xè–¾Zù¿ø°=›qg>9Í_¾ÐRô¾ZZ•¾¬[#½¡Ø_>'Yÿ>‚ëØ> ÚF=^8ϽǾµ%»Âô?³‹Î>@u¾çD§¾(Í"¾Nη>ʾ>BS¿ |žDÝ1¾Ÿ‚i>^ª>Õõ¾³üj¾¯¨‡½Bn¼ÿß‘>Œ¼æÖ=mÛ>:¹>ëâ0?.z«>ü€Õ><Å>p¨¨>éØx> *ð¾U›½[þ¾eº1¾p±|¾£y¿³27¿OÈ ¿Iå¿p›å¾&ÂK¾Ÿ­ª¾)º²¾gоŠ?‚½%8—¾d2ª¾èî§¾½7¢½ùÄc¾Cô¾ýÅW¾”£k½*«'>›Ü%>LȾ] À¼sü=¤«-=²…i>ržÁ=¦{¾>§`>m7¼=†9>¢°§>†Å³>1î<_>V>rr®> °[¾>ôù½P»+(g¾G%¾Ì^¶=5åQ>¼ ¾Òg¾­å¿ˆ¸v½$Ò>ø\m¼X)ó¾ðe¿ê«ì½×>¶°>?|.=²9=¼ Ý1( ?bê.?y¼q>ë*¾½éª¾xÈC¾Ý [=üø>B;>O ¿M¸;¿²˜¿¹Ä¾ P ?Ž7±>,¾m±¾&úW¾)oa>¯®(?þĺ>Žz>¾`ཅ°v=›>x¬r>°4ð>ï¦d>G¥È=d:?”[¾©Tê¾äèX¾ì“º>E`ù>V»Ð¾iÀÛ¾|}¾ŒqÔ>X›™>qê¾Ðš›¾Y`·½4]Ž>ÝZê=s0¾%´¾q­¾ø„²>ˆA>>-‘=“e ><*=½&í´¼H,¾øÅ»=V*m>°ò­>Á)>qÂÀ>îµG>ú]¡>"0?W¬ñ½z¯H>‘¼>ý¿C=ÑÙ>ùuè<Ž}¾üY÷½äEJ¾Þ•¿q»½KbB¾æÚ¾ˆÊ‘¾ëB¾B Parameter5*6" «j%¾µÞ¾w®»=© м•,…½Kæ¾µB§<¤ø½B Parameter6*W"@r¨½˜öÞ½¯l¾9ÉQ¾Vo7¾á›\¾2 ¾lH¾&X‰¾d4„¾¶÷›½¿¥Y "(ôº7½1Qÿ;qx‹=oµõ<‡q¾•>lrb½¯FJ½ ±¬=Åe_½B Parameter194Z Input3     Z$ Parameter5     Z Parameter6    Z% Parameter87     Z! Parameter88    Z0 "Pooling160_Output_0_reshape0_shape  Z& Parameter193      Z) Parameter193_reshape1_shape  Z Parameter194    b" Plus214_Output_0    j( Parameter193_reshape1  €  j0 Convolution28_Output_0     j) Plus30_Output_0     j) ReLU32_Output_0     j, Pooling66_Output_0     j1 Convolution110_Output_0     j* Plus112_Output_0     j* ReLU114_Output_0     j- Pooling160_Output_0     j/ Pooling160_Output_0_reshape0   €j# Times212_Output_0    B ROCm-AMDMIGraphX-46524e8/examples/vision/cpp_mnist/mnist_inference.cpp000066400000000000000000000163171510465702400255450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include void read_nth_digit(const int, std::vector&); int main(int argc, char** argv) { if(argc == 1) { std::cout << "Usage: " << argv[0] << " [options]" << std::endl << "options:" << std::endl << "\t -c, --cpu Compile for CPU" << std::endl << "\t -g, --gpu Compile for GPU" << std::endl << "\t -f, --fp16 FP16 Quantization" << std::endl << "\t -i, --int8 Int8 Quantization" << std::endl << "\t --cal Int8 Calibration ON" << std::endl << "\t -p, --print Print Graph at Each Stage" << std::endl << std::endl << std::endl; } char** begin = argv + 1; char** end = argv + argc; const bool CPU = (std::find(begin, end, std::string("-c")) != end) or std::find(begin, end, std::string("--cpu")) != end; const bool GPU = std::find(begin, end, std::string("-g")) != end or std::find(begin, end, std::string("--gpu")) != end; const bool FP16 = std::find(begin, end, std::string("-f")) != end or std::find(begin, end, std::string("--fp16")) != end; const bool INT8 = std::find(begin, end, std::string("-i")) != end or std::find(begin, end, std::string("--int8")) != end; const bool CALIB = std::find(begin, end, std::string("--cal")) != end; const bool PRINT = std::find(begin, end, std::string("-p")) != end or std::find(begin, end, std::string("--print")) != end; migraphx::program prog; migraphx::onnx_options onnx_opts; prog = parse_onnx("../mnist-8.onnx", onnx_opts); std::cout << "Parsing ONNX model..." << std::endl; if(PRINT) prog.print(); std::cout << std::endl; std::string target_str; if(CPU) target_str = "cpu"; else if(GPU) target_str = "gpu"; else target_str = "ref"; migraphx::target targ = migraphx::target(target_str.c_str()); if(FP16) { migraphx::quantize_fp16(prog); std::cout << "Quantizing program for FP16..." << std::endl; if(PRINT) prog.print(); std::cout << std::endl; } else if(INT8) { if(CALIB) { std::cout << "Calibration data: " << std::endl; std::vector calib_dig; read_nth_digit(9, calib_dig); migraphx::quantize_int8_options quant_opts; migraphx::program_parameters quant_params; auto param_shapes = prog.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { quant_params.add(name, migraphx::argument(param_shapes[name], calib_dig.data())); } quant_opts.add_calibration_data(quant_params); migraphx::quantize_int8(prog, targ, quant_opts); } else { migraphx::quantize_int8(prog, targ, migraphx::quantize_int8_options()); } std::cout << "Quantizing program for INT8..." << std::endl; if(PRINT) prog.print(); std::cout << std::endl; } if(GPU) { migraphx::compile_options comp_opts; comp_opts.set_offload_copy(); prog.compile(targ, comp_opts); } else { prog.compile(targ); } std::cout << "Compiling program for " << target_str << "..." << std::endl; if(PRINT) prog.print(); std::cout << std::endl; std::vector digit; std::random_device rd; std::uniform_int_distribution dist(0, 9); const int rand_digit = dist(rd); std::cout << "Model input: " << std::endl; read_nth_digit(rand_digit, digit); migraphx::program_parameters prog_params; auto param_shapes = prog.get_parameter_shapes(); auto input = param_shapes.names().front(); prog_params.add(input, migraphx::argument(param_shapes[input], digit.data())); std::cout << "Model evaluating input..." << std::endl; auto start = std::chrono::high_resolution_clock::now(); auto outputs = prog.eval(prog_params); auto stop = std::chrono::high_resolution_clock::now(); auto elapsed = std::chrono::duration_cast(stop - start); std::cout << "Inference complete" << std::endl; std::cout << "Inference time: " << elapsed.count() * 1e-3 << "ms" << std::endl; auto shape = outputs[0].get_shape(); auto lengths = shape.lengths(); auto num_results = std::accumulate(lengths.begin(), lengths.end(), 1, std::multiplies()); float* results = reinterpret_cast(outputs[0].data()); const float* max = std::max_element(results, results + num_results); int answer = max - results; std::cout << std::endl << "Randomly chosen digit: " << rand_digit << std::endl << "Result from inference: " << answer << std::endl << std::endl << (answer == rand_digit ? "CORRECT" : "INCORRECT") << std::endl << std::endl; return 0; } void read_nth_digit(const int n, std::vector& digit) { const std::string SYMBOLS = "@0#%=+*-. "; std::ifstream file("../digits.txt"); const int DIGITS = 10; const int HEIGHT = 28; const int WIDTH = 28; if(not file.is_open()) { return; } for(int d = 0; d < DIGITS; ++d) { for(int i = 0; i < HEIGHT * WIDTH; ++i) { unsigned char temp = 0; file.read(reinterpret_cast(&temp), sizeof(temp)); if(d == n) { float data = temp / 255.0; digit.push_back(data); std::cout << SYMBOLS[static_cast(data * 10) % 11]; if((i + 1) % WIDTH == 0) std::cout << std::endl; } } } std::cout << std::endl; } ROCm-AMDMIGraphX-46524e8/examples/vision/python_3dunet/000077500000000000000000000000001510465702400224705ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/vision/python_3dunet/3dunet_inference.ipynb000066400000000000000000000472761510465702400267730ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n" ] }, { "cell_type": "markdown", "id": "fee8cfa5", "metadata": {}, "source": [ "# 3D-UNet Example with MIGraphX\n", "References:
\n", "https://github.com/naomifridman/Unet_Brain_tumor_segmentation" ] }, { "cell_type": "code", "execution_count": null, "id": "09ceec31", "metadata": {}, "outputs": [], "source": [ "!pip install SimpleITK matplotlib scikit-image" ] }, { "cell_type": "code", "execution_count": null, "id": "bb22bcc4", "metadata": {}, "outputs": [], "source": [ "import migraphx\n", "from PIL import Image\n", "import numpy as np\n", "import os\n", "import SimpleITK as sitk" ] }, { "cell_type": "markdown", "id": "cb973c63", "metadata": {}, "source": [ "## Fetch U-NET ONNX Model" ] }, { "cell_type": "code", "execution_count": null, "id": "1928662c", "metadata": {}, "outputs": [], "source": [ "!wget -nc https://zenodo.org/record/3928973/files/224_224_160.onnx" ] }, { "cell_type": "markdown", "id": "1a64a616", "metadata": {}, "source": [ "## Load ONNX Model" ] }, { "cell_type": "code", "execution_count": null, "id": "53928a98", "metadata": {}, "outputs": [], "source": [ "model = migraphx.parse_onnx(\"224_224_160.onnx\")" ] }, { "cell_type": "code", "execution_count": null, "id": "27e8587f", "metadata": {}, "outputs": [], "source": [ "model.compile(migraphx.get_target(\"gpu\"))" ] }, { "cell_type": "markdown", "id": "2f6014a4", "metadata": {}, "source": [ "## Print model parameters" ] }, { "cell_type": "code", "execution_count": null, "id": "9e73728c", "metadata": {}, "outputs": [], "source": [ "print(model.get_parameter_names())\n", "print(model.get_parameter_shapes())\n", "print(model.get_output_shapes())" ] }, { "cell_type": "code", "execution_count": null, "id": "a4cac52e", "metadata": {}, "outputs": [], "source": [ "img_type=['FLAIR', 'T1','T1CE', 'T2']\n", "label_type_shrt = ['background', 'necrotic',\n", " 'edema', 'enhancing']\n", "label_type = ['background', 'necrotic and non-enhancing tumor', 'edema', 'enhancing tumor']" ] }, { "cell_type": "code", "execution_count": null, "id": "b65f9297", "metadata": {}, "outputs": [], "source": [ "red_multiplier = [1, 0.2, 0.2]\n", "green_multiplier = [0.35,0.75,0.25]\n", "blue_multiplier = [0,0.5,1.]#[0,0.25,0.9]\n", "yellow_multiplier = [1,1,0.25]\n", "brown_miltiplier = [40./255, 26./255, 13./255]\n", "my_colors=[blue_multiplier, yellow_multiplier, brown_miltiplier]" ] }, { "cell_type": "code", "execution_count": null, "id": "0e175ac5", "metadata": {}, "outputs": [], "source": [ "from importlib import reload # Python 3.4+ only." ] }, { "cell_type": "code", "execution_count": null, "id": "530e4f97", "metadata": {}, "outputs": [], "source": [ "import visualization_utils as vu\n", "from visualization_utils import show_label_on_image4\n", "reload(vu)" ] }, { "cell_type": "code", "execution_count": null, "id": "865c46a2", "metadata": {}, "outputs": [], "source": [ "def show_img_label(img, lbl, modality = 0):\n", " \n", " if (len(lbl.shape)> 2):\n", " lbl[0,0,3]=1 # for uniqe colors in plot\n", " lbl = lbl_from_cat(lbl)\n", " vu.show_n_images([img[:,:,modality],lbl, show_label_on_image4(img[:,:,modality],lbl)],\n", " titles = [img_type[modality], 'Label', 'Label on '+ img_type[modality]]);\n" ] }, { "cell_type": "code", "execution_count": null, "id": "1e926482", "metadata": {}, "outputs": [], "source": [ "def read_img_sitk(img):\n", " inputImage = sitk.ReadImage( img )\n", " inputImage = sitk.Cast( inputImage, sitk.sitkFloat32 )\n", " image = sitk.GetArrayFromImage(inputImage)\n", " return image" ] }, { "cell_type": "code", "execution_count": null, "id": "0b620138", "metadata": {}, "outputs": [], "source": [ "# ima files are of the form\n", "# BraTS19_TCIA04_192_1_flair.nii.gz \n", "# BraTS19_TCIA04_192_1_t1.nii.gz \n", "# BraTS19_TCIA04_192_1_t2.nii.gz\n", "# BraTS19_TCIA04_192_1_seg.nii.gz \n", "# BraTS19_TCIA04_192_1_t1ce.nii.gz\n", "\n", "def read_image_into_numpy(dirpath):\n", " \n", " img_id = os.path.basename(dirpath)\n", " np_image=np.zeros((4, 160, 224, 224), dtype=np.float32)\n", " \n", " ## Flair\n", " flair_img = os.path.join(dirpath, img_id+'_flair.nii.gz')\n", " if (not os.path.isfile(flair_img)):\n", " print(flair_img,' not found aborting')\n", " return None\n", " np_image[0] = read_img_sitk(flair_img)\n", " \n", " ## T1\n", " t1_nb4_img = os.path.join(dirpath, img_id+'_t1_nb4.nii.gz')\n", " if (not os.path.isfile(t1_nb4_img)):\n", " #print(t1_nb4_img,' not found')\n", " t1_img = os.path.join(dirpath, img_id+'_t1.nii.gz')\n", " if (not os.path.isfile(t1_img)):\n", " print(t1_img,' not found aborting')\n", " return None\n", " np_image[1] = read_img_sitk(t1_img)\n", " else:\n", " np_image[1] = read_img_sitk(t1_nb4_img) \n", " \n", " ## T1CE\n", " t1ce_nb4_img = os.path.join(dirpath, img_id+'_t1ce_nb4.nii.gz')\n", " if (not os.path.isfile(t1ce_nb4_img)):\n", " #print(t1ce_nb4_img,' not found')\n", " t1ce_img = os.path.join(dirpath, img_id+'_t1ce.nii.gz')\n", " if (not os.path.isfile(t1ce_img)):\n", " print(t1ce_img,' not found aborting')\n", " return None\n", " np_image[2] = read_img_sitk(t1ce_img)\n", " else:\n", " np_image[2] = read_img_sitk(t1ce_nb4_img) \n", " \n", " \n", " ## T2\n", " t2_img = os.path.join(dirpath, img_id+'_t2.nii.gz')\n", " if (not os.path.isfile(t2_img)):\n", " print(t2_img,' not found aborting')\n", " return None\n", " np_image[3] = read_img_sitk(t2_img)\n", "\n", " return np_image" ] }, { "cell_type": "code", "execution_count": null, "id": "2fb66f17", "metadata": {}, "outputs": [], "source": [ "def read_label_into_numpy(dirpath):\n", " \n", " img_id = os.path.basename(dirpath)\n", " np_image=np.zeros((160, 224, 224), dtype=np.int)\n", " \n", " ## label\n", " label_img = os.path.join(dirpath, img_id+'_seg.nii.gz')\n", " if (not os.path.isfile(label_img)):\n", " print(label_img,' not found aborting')\n", " return None\n", " np_image = read_img_sitk(label_img).astype(int)\n", "\n", " return np_image" ] }, { "cell_type": "code", "execution_count": null, "id": "558d47b9", "metadata": {}, "outputs": [], "source": [ "def bbox2_3D(img):\n", "\n", " r = np.any(img, axis=(1, 2))\n", " c = np.any(img, axis=(0, 2))\n", " z = np.any(img, axis=(0, 1))\n", "\n", " rmin, rmax = np.where(r)[0][[0, -1]]\n", " cmin, cmax = np.where(c)[0][[0, -1]]\n", " zmin, zmax = np.where(z)[0][[0, -1]]\n", "\n", " return [rmin, rmax, cmin, cmax, zmin, zmax]" ] }, { "cell_type": "code", "execution_count": null, "id": "1405e186", "metadata": {}, "outputs": [], "source": [ "def lbl_from_cat(cat_lbl):\n", " \n", " lbl=0\n", " if (len(cat_lbl.shape)==3):\n", " for i in range(1,4):\n", " lbl = lbl + cat_lbl[:,:,i]*i\n", " elif (len(cat_lbl.shape)==4):\n", " for i in range(1,4):\n", " lbl = lbl + cat_lbl[:,:,:,i]*i\n", " else:\n", " print('Error in lbl_from_cat', cat_lbl.shape)\n", " return None\n", " return lbl" ] }, { "cell_type": "code", "execution_count": null, "id": "24eb472f", "metadata": {}, "outputs": [], "source": [ "def show_label(lbl):\n", " vu.show_n_images([lbl[:,:,k] for k in range(4)]+[lbl_from_cat(lbl)],\n", " titles = label_type_shrt + ['Label'])\n", "\n", "def show_pred_im_label(im, lb, pred):\n", " \n", " vu.show_n_images([im[:,:,1], lb[:,:], \n", " show_label_on_image4(im[:,:,1], lb[:,:]),\n", " show_label_on_image4(im[:,:,1], pred[:,:])],\n", " titles=['Flair', 'Label', 'Label on T1', 'Prediction on Flair'])\n", "\n", "def show_pred_im(im, pred):\n", " \n", " vu.show_n_images([im[:,:,1], \n", " im[:,:,0],pred,\n", " show_label_on_image4(im[:,:,1], pred[:,:])],\n", " titles=['Flair','T1', 'Pred', 'Prediction on Flair'])" ] }, { "cell_type": "markdown", "id": "d15f788b", "metadata": {}, "source": [ "Multiple image inputs:\n", "- Native (T1)\n", "- Post-contrast T1-weighted (T1Gd)\n", "- T2-weighted (T2)\n", "- T2 Fluid Attenuated Inversion Recovery (T2-FLAIR)" ] }, { "cell_type": "code", "execution_count": null, "id": "7a7aad87", "metadata": {}, "outputs": [], "source": [ "# Resize input images\n", "from scipy.ndimage import zoom\n", "\n", "def resize(img, shape, mode='constant', orig_shape=(155, 240, 240)):\n", " \"\"\"\n", " Wrapper for scipy.ndimage.zoom suited for MRI images.\n", " \"\"\"\n", " assert len(shape) == 3, \"Can not have more than 3 dimensions\"\n", " factors = (\n", " shape[0]/orig_shape[0],\n", " shape[1]/orig_shape[1], \n", " shape[2]/orig_shape[2]\n", " )\n", " \n", " # Resize to the given shape\n", " return zoom(img, factors, mode=mode)\n", "\n", "def preprocess_label(img, out_shape=None, mode='nearest'):\n", " \"\"\"\n", " Separates out the 3 labels from the segmentation provided, namely:\n", " GD-enhancing tumor (ET — label 4), the peritumoral edema (ED — label 2))\n", " and the necrotic and non-enhancing tumor core (NCR/NET — label 1)\n", " \"\"\"\n", " ncr = img == 1 # Necrotic and Non-Enhancing Tumor (NCR/NET)\n", " \n", " ed = img == 2 # Peritumoral Edema (ED)\n", " et = img == 4 # GD-enhancing Tumor (ET)\n", " \n", " if out_shape is not None:\n", " ncr = resize(ncr, out_shape, mode=mode)\n", " ed = resize(ed, out_shape, mode=mode)\n", " et = resize(et, out_shape, mode=mode)\n", " return np.array([ncr, ed, et], dtype=np.uint8)\n", "\n", "hgg_path = \"/code/AMDMIGraphX/bratsdata/MICCAI_BraTS_2019_Data_Training/HGG\"\n", "np_image=np.zeros((4, 160, 224, 224), dtype=np.float32)\n", "tmp = read_img_sitk('%s/BraTS19_TMC_30014_1/BraTS19_TMC_30014_1_flair.nii.gz'%hgg_path)\n", "tmp = resize(tmp, [160,224,224])\n", "mean = tmp.mean()\n", "std = tmp.std()\n", "np_image[0] = (tmp - mean) / std\n", "\n", "tmp = read_img_sitk('%s/BraTS19_TMC_30014_1/BraTS19_TMC_30014_1_t1.nii.gz'%hgg_path)\n", "tmp = resize(tmp, [160,224,224])\n", "mean = tmp.mean()\n", "std = tmp.std()\n", "np_image[1] = (tmp - mean) / std\n", "\n", "tmp = read_img_sitk('%s/BraTS19_TMC_30014_1/BraTS19_TMC_30014_1_t1ce.nii.gz'%hgg_path)\n", "tmp = resize(tmp, [160,224,224])\n", "mean = tmp.mean()\n", "std = tmp.std()\n", "np_image[2] = (tmp - mean) / std\n", "\n", "tmp = read_img_sitk('%s/BraTS19_TMC_30014_1/BraTS19_TMC_30014_1_t2.nii.gz'%hgg_path)\n", "tmp = resize(tmp, [160,224,224])\n", "mean = tmp.mean()\n", "std = tmp.std()\n", "np_image[3] = (tmp - mean) / std\n", "\n", "print(np_image.shape)\n", "np_image_tmp = np_image.copy()" ] }, { "cell_type": "code", "execution_count": null, "id": "d7e5b3c6", "metadata": {}, "outputs": [], "source": [ "vu.show_n_images(np_image[:,100,:,:], titles=img_type)" ] }, { "cell_type": "code", "execution_count": null, "id": "19117da5", "metadata": {}, "outputs": [], "source": [ "np_lbl=np.zeros((160, 224, 224), dtype=np.int)\n", "tmp = read_img_sitk('/code/AMDMIGraphX/bratsdata/MICCAI_BraTS_2019_Data_Training/HGG/BraTS19_TMC_30014_1/BraTS19_TMC_30014_1_seg.nii.gz').astype(int)\n", "tmp = resize(tmp, [160,224,224])\n", "print(tmp.shape)\n", "np_lbl = tmp.astype(int)\n", "print(np_lbl.shape)\n", "\n", "print(np_image.shape)\n", "\n", "img1 = vu.show_label_on_image4(np_image[1,100,:,:], np_lbl[100])\n", "img2 = vu.show_label_on_image(np_image[1,100,:,:], np_lbl[100])\n", "vu.show_n_images([img1,img2,np_image[0,100]])" ] }, { "cell_type": "code", "execution_count": null, "id": "facdea15", "metadata": {}, "outputs": [], "source": [ "def get_pred(img, threshold=0.5):\n", " out_img=img.copy()\n", " out_img=np.where(out_img>threshold, 1,0)\n", " return out_img\n", "\n", "def prediction_from_probabily_3D(img):\n", " \n", " int_image = get_pred(img)\n", " return lbl_from_cat(int_image)\n", "\n", "def get_prediction_for_batch(pred_batch, threshold=0.5):\n", " \n", " out_batch = np.zeros((pred_batch.shape[0], 224, 224),dtype=np.int)\n", " \n", " for j in range(pred_batch.shape[0]):\n", " pred = get_prediction(pred_batch[j])\n", " if (pred.sum()>0):\n", " print(j, np.unique(pred , return_counts=True))\n", " out_batch[j] = lbl_from_cat(get_prediction(pred_batch[j]))\n", " return out_batch\n", "\n", "def get_label_from_pred_batch(labels_batch):\n", " \n", " batch = np.zeros((labels_batch.shape[0], 224, 224), np.uint8)\n", " \n", " for j in range(labels_batch.shape[0]):\n", " batch[j]=get_pred(labels_batch[j,:,:,0])+\\\n", " get_pred(labels_batch[j,:,:,1])*2+\\\n", " get_pred(labels_batch[j,:,:,2])*4\n", "\n", " return batch\n", "\n", "def predict_3D_img_prob(np_file):\n", " \n", " np_img = np.load(np_file)\n", " for_pred_img = np.zeros((160, 224, 224, 4), np.float32)\n", "\n", " # Normalize image\n", " for_pred_img = normalize_3D_image(np_img)\n", "\n", " mdl_pred_img = model.predict(for_pred_img)\n", "\n", " #pred_label = prediction_from_probabily_3D(mdl_pred_img)\n", "\n", " return mdl_pred_img\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7f7fe7ee", "metadata": {}, "outputs": [], "source": [ "#Remember the MIGraphX model inputs\n", "print(model.get_parameter_names())\n", "print(model.get_parameter_shapes())\n", "\n", "np_image = np_image.transpose((0,2,3,1))\n", "\n", "print(np_image.shape)\n", "print(np_image.strides)" ] }, { "cell_type": "code", "execution_count": null, "id": "dfc47b53", "metadata": {}, "outputs": [], "source": [ "def normalize_3D_image(img):\n", " for z in range(img.shape[0]):\n", " for k in range(4):\n", " if (img[z,:,:,k].max()>0):\n", " img[z,:,:,k] /= img[z,:,:,k].max()\n", " return img" ] }, { "cell_type": "code", "execution_count": null, "id": "f990cb50", "metadata": {}, "outputs": [], "source": [ "print(np_image_tmp.shape)\n", "np_image_tmp = np_image_tmp.transpose((1,2,3,0))\n", "print(np_image_tmp.shape)" ] }, { "cell_type": "code", "execution_count": null, "id": "24c3736d", "metadata": {}, "outputs": [], "source": [ "np_image = np.expand_dims(np_image, 0)\n", "print(np_image.shape)\n", "print(np_image.strides)" ] }, { "cell_type": "code", "execution_count": null, "id": "1aac6285", "metadata": {}, "outputs": [], "source": [ "input_im = np.zeros((1,4,224,224,160),dtype='float32')\n", "np.lib.stride_tricks.as_strided(input_im, shape=np_image.shape, strides=input_im.strides)[:] = np_image #getting correct stride\n", "print(input_im.strides)\n", "print(input_im.shape)\n", "\n", "#input_im = normalize_3D_image(input_im)\n", "\n", "print(input_im.strides)\n", "print(input_im.shape)\n", "\n", "result = model.run({\n", " \"input\": input_im\n", " })" ] }, { "cell_type": "code", "execution_count": null, "id": "5848b63d", "metadata": {}, "outputs": [], "source": [ "output = np.array(result[0])\n", "print(output.shape)\n", "output = output[0]\n", "print(output.shape)\n", "output = output.transpose((3,1,2,0))\n", "print(output.shape)" ] }, { "cell_type": "code", "execution_count": null, "id": "ab77f7e9", "metadata": {}, "outputs": [], "source": [ "out = prediction_from_probabily_3D(output)\n", "print(np_image_tmp.shape)\n", "print(np_lbl.shape)\n", "print(out.shape)\n", "print(np.unique(out))\n", "ind=[100]\n", "for i in ind:\n", " show_label(output[i])\n", " show_label(get_pred(output[i]))\n", " show_pred_im_label(np_image_tmp[i], np_lbl[i], out[i])" ] }, { "cell_type": "markdown", "id": "d2862d81", "metadata": {}, "source": [ "The possible prediction discrepancy is due to the not-perfect resizing 3D input image, as BRATS dataset has 3D images of size 160x240x240, meanwhile the ONNX model utilized here requires 155x224x224. This example is representative for how to utilize MIGraphX for such an application. All data processing should follow and match the model requirements otherwise. " ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 5 } ROCm-AMDMIGraphX-46524e8/examples/vision/python_3dunet/README.md000066400000000000000000000005511510465702400237500ustar00rootroot00000000000000# 3D-Unet Inference with AMD MIGraphX This example applies image segmentation to 3D images using AMD MIGraphX on a given AMD GPU. ## How to: 1) User will need to have access to the BRATS dataset. Please follow https://www.med.upenn.edu/cbica/brats2019/data.html for how to get access to the dataset. 2) Follow the provided notebook `3dunet_inference.ipynb`. ROCm-AMDMIGraphX-46524e8/examples/vision/python_3dunet/visualization_utils.py000066400000000000000000000115431510465702400271670ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import matplotlib.pylab as pylab import numpy as np params = { 'legend.fontsize': 'x-large', 'figure.figsize': (6, 5), 'axes.labelsize': 'x-large', 'axes.titlesize': 'x-large', 'xtick.labelsize': 'x-large', 'ytick.labelsize': 'x-large' } pylab.rcParams.update(params) #----------------------------------------------------------- def show_n_images(imgs, titles=None, enlarge=20, cmap='jet'): plt.set_cmap(cmap) n = len(imgs) gs1 = gridspec.GridSpec(1, n) fig1 = plt.figure() # create a figure with the default size fig1.set_size_inches(enlarge, 2 * enlarge) for i in range(n): ax1 = fig1.add_subplot(gs1[i]) ax1.imshow(imgs[i], interpolation='none') if (titles is not None): ax1.set_title(titles[i]) ax1.set_ylim(ax1.get_ylim()[::-1]) plt.show() #-------------------------------------------------------------- from skimage import color, img_as_float from skimage.exposure import adjust_gamma # Creates an image of original brain with segmentation overlay def show_label_on_image(test_img, test_lbl): label_im = test_lbl ones = np.argwhere(label_im == 1) twos = np.argwhere(label_im == 2) threes = np.argwhere(label_im == 3) fours = np.argwhere(label_im == 4) gray_img = img_as_float(test_img / test_img.max()) # adjust gamma of image # print(color.gray2rgb(gray_img)) image = adjust_gamma(np.abs(color.gray2rgb(gray_img)), 0.45) #sliced_image = image.copy() green_multiplier = [0.35, 0.75, 0.25] blue_multiplier = [0, 0.5, 1.] #[0,0.25,0.9] yellow_multiplier = [1, 1, 0.25] brown_miltiplier = [40. / 255, 26. / 255, 13. / 255] # change colors of segmented classes for i in range(len(ones)): image[ones[i][0]][ones[i][1]] = blue_multiplier for i in range(len(twos)): image[twos[i][0]][twos[i][1]] = yellow_multiplier for i in range(len(threes)): image[threes[i][0]][threes[i][1]] = brown_miltiplier #blue_multiplier for i in range(len(fours)): image[fours[i][0]][fours[i][1]] = green_multiplier #yellow_multiplier return image #------------------------------------------------------------------------------------- def show_label_on_image4(test_img, label_im): alpha = 0.8 img = img_as_float(test_img / test_img.max()) rows, cols = img.shape # Construct a colour image to superimpose color_mask = np.zeros((rows, cols, 3)) green_multiplier = [0.35, 0.75, 0.25] blue_multiplier = [0, 0.25, 0.9] yellow_multiplier = [1, 1, 0.25] brown_miltiplier = [40. / 255, 26. / 255, 13. / 255] color_mask[label_im == 1] = blue_multiplier #[1, 0, 0] # Red block color_mask[label_im == 2] = yellow_multiplier #[0, 1, 0] # Green block color_mask[label_im == 3] = brown_miltiplier #[0, 0, 1] # Blue block color_mask[label_im == 4] = green_multiplier #[0, 1, 1] # Blue block # Construct RGB version of grey-level image img_color = np.dstack((img, img, img)) # Convert the input image and color mask to Hue Saturation Value (HSV) # colorspace img_hsv = color.rgb2hsv(img_color) color_mask_hsv = color.rgb2hsv(color_mask) # Replace the hue and saturation of the original image # with that of the color mask img_hsv[..., 0] = color_mask_hsv[..., 0] img_hsv[..., 1] = color_mask_hsv[..., 1] * alpha img_masked = color.hsv2rgb(img_hsv) return img_masked #------------------------------------------------------------------------------ ROCm-AMDMIGraphX-46524e8/examples/vision/python_nfnet/000077500000000000000000000000001510465702400224005ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/vision/python_nfnet/README.md000066400000000000000000000060171510465702400236630ustar00rootroot00000000000000# NFNet Inference with MIGraphX ## NFNet NFNet: Normalizer-Free Nets. An image recognition model that can be trained without batch normalization layers. It instead uses gradient clipping algorithm to provide same affects of BatchNorm. **Summary:** - SOTA on ImageNet (86.5% top-1 w/o extra data) - Up to 8.7x faster to train than EfficientNets to a given accuracy - Normalizer-free (no BatchNorm) **Paper**: https://arxiv.org/pdf/2102.06171.pdf **Colab notebook**: https://github.com/deepmind/deepmind-research/tree/master/nfnets ### Why not batch norm? Batch normalization has three significant practical disadvantages: 1. It is an expensive computational primitive, which incurs memory overhead and significantly increases the time required to evaluate the gradient in some networks. 2. It introduces a discrepancy between the behavior of the model during training and at inference time, introducing hidden hyper-parameters that have to be tuned. 3. Last and most important point, batch normalization breaks the independence between training examples in the minibatch (batch size matters with batch norm, distributed training becomes extremely cumbersome). Instead: - Authors provide Adaptive Gradient Clipping (AGC), which clips gradients based on the unit-wise ratio of gradient norms to parameter norms, and they demonstrate that AGC allows them to train normalizer-free networks with larger batch sizes and stronger data augmentations. - They design a family of Normalizer-Free ResNets, called NFNets, which set new state-of-the-art validation accuracies on ImageNet for a range of training latencies. Their NFNet-F1 model achieves similar accuracy to EfficientNet-B7 while being 8.7× faster to train, and their largest model sets a new overall state of the art without extra data of 86.5% top-1 accuracy. - They show that NFNets achieve substantially higher validation accuracies than batch-normalized networks when fine-tuning on ImageNet after pre-training on a large private dataset of 300 million labelled images. Their best model achieves 89.2% top-1 accuracy after fine-tuning. ## Inference with MIGraphX using NFNet ONNX Model There is no ONNX model released for NFNet, as of June 2021, however a PyTorch model is available at: https://github.com/rwightman/pytorch-image-models. We provide an in-house produced and optimized ONNX model, which can be parsed and compiled using MIGraphX for AMD GPUs. The ONNX model file can be fetched using the Jupyter notebook we provide. ### Requirements: 1) AMD GPU system with ROCm installed. 2) Jupyter notebook library. ### How to use NFNet for image recognition: Please utilize the notebook example provided: 1) Install jupyter notebook to your environment if not already installed: ``` https://jupyter.org/install ``` 2) Connect to your jupyter server and utilize `nfnet_inference.ipynb` notebook file. ### How to compare MIGraphX to ONNX Runtime for NFNet ONNX model: First install requirements: ``` pip3 install -r requirements_nfnet.txt ``` On your terminal, invoke: ``` python3 ort_comparison.py ```` ROCm-AMDMIGraphX-46524e8/examples/vision/python_nfnet/nfnet_inference.ipynb000066400000000000000000000205001510465702400265700ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# NFNet Inference with AMD MIGraphX\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Normalizer-Free ResNet is a new residual convolutional network providing new state-of-the-art Top-1 accuracy of 86.5% at ImageNet dataset. The most important feature of the model is removing batch normalization. Instead of batch normalization, it uses adaptive gradient clipping to provide same regularization effect of BatchNorm.
Details of this network: https://arxiv.org/abs/2102.06171\n", "\n", "In this notebook, we are showing:
\n", "- How to optimize NFNet ONNX model with AMD MIGraphX.\n", "- How to run inference on AMD GPU with the optimized ONNX model.\n", "\n", "The NFNet utilized in this example is the smallest NFNet version, F0: 71.5M parameters (83.6% top-1 accuracy on ImageNet)\n", "\n", "Please make sure MIGraphX Python API is installed following the instructions at Github page." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Requirements" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!apt-get update\n", "!apt-get install ffmpeg libsm6 libxext6 -y " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip3 install --upgrade pip\n", "!pip3 install -r requirements_nfnet.txt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import cv2\n", "import json\n", "from PIL import Image\n", "import time\n", "from os import path " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Importing AMD MIGraphX Python Module" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import migraphx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create NFNet ONNX file\n", "Following repository provides functionality to create NFNet ONNX file from PyTorch model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!wget -nc https://www.dropbox.com/s/u4ga8zyxtppfzxc/dm_nfnet_f0.onnx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Load ImageNet labels" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open('../python_resnet50/imagenet_simple_labels.json') as json_data:\n", " labels = json.load(json_data)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## Load ONNX model using MIGraphX" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = migraphx.parse_onnx(\"dm_nfnet_f0.onnx\")\n", "model.compile(migraphx.get_target(\"gpu\"))\n", "\n", "print(model.get_parameter_names())\n", "print(model.get_parameter_shapes())\n", "print(model.get_output_shapes())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Functions for image processing" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def make_nxn(image, n):\n", " height, width = image.shape[:2] \n", " if height > width:\n", " dif = height - width\n", " bar = dif // 2 \n", " square = image[(bar + (dif % 2)):(height - bar),:]\n", " return cv2.resize(square, (n, n))\n", " elif width > height:\n", " dif = width - height\n", " bar = dif // 2\n", " square = image[:,(bar + (dif % 2)):(width - bar)]\n", " return cv2.resize(square, (n, n))\n", " else:\n", " return cv2.resize(image, (n, n))\n", " \n", "def preprocess(img_data):\n", " mean_vec = np.array([0.485, 0.456, 0.406])\n", " stddev_vec = np.array([0.229, 0.224, 0.225])\n", " norm_img_data = np.zeros(img_data.shape).astype('float32')\n", " for i in range(img_data.shape[0]): \n", " norm_img_data[i,:,:] = (img_data[i,:,:]/255 - mean_vec[i]) / stddev_vec[i]\n", " return norm_img_data\n", "\n", "def input_process(frame, dim):\n", " # Crop and resize original image\n", " cropped = make_nxn(frame, dim)\n", " # Convert from HWC to CHW\n", " chw = cropped.transpose(2,0,1)\n", " # Apply normalization\n", " pp = preprocess(chw)\n", " # Add singleton dimension (CHW to NCHW)\n", " data = np.expand_dims(pp.astype('float32'),0)\n", " return data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Download example image" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Fetch example image: traffic light\n", "!wget -nc http://farm5.static.flickr.com/4072/4462811418_8bc2bd42ca_z_d.jpg -O traffic_light.jpg\n", "# Read the image\n", "im = cv2.imread('traffic_light.jpg')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Process the read image to conform input requirements\n", "data_input = input_process(im, 192)\n", "\n", "# Run the model\n", "start = time.time()\n", "results = model.run({'inputs':data_input}) # Your first inference would take longer than the following ones.\n", "print(f\"Time inference took: {1000*(time.time() - start):.2f}ms\")\n", "# Extract the index of the top prediction\n", "res_npa = np.array(results[0])\n", "print(f\"\\nResult: {labels[np.argmax(res_npa)]}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run the model again, first one would take long\n", "start = time.time()\n", "results = model.run({'inputs':data_input}) # Your first inference would take longer than the following ones.\n", "print(f\"Time inference took: {1000*(time.time() - start):.2f}ms\")\n", "# Extract the index of the top prediction\n", "res_npa = np.array(results[0])\n", "print(f\"\\nResult: {labels[np.argmax(res_npa)]}\")" ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 4 } ROCm-AMDMIGraphX-46524e8/examples/vision/python_nfnet/ort_comparison.py000066400000000000000000000053221510465702400260120ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import numpy import onnxruntime as rt sess = rt.InferenceSession("dm_nfnet_f0.onnx") input_name = sess.get_inputs()[0].name print("input name", input_name) input_shape = sess.get_inputs()[0].shape print("input shape", input_shape) input_type = sess.get_inputs()[0].type print("input type", input_type) output_name = sess.get_outputs()[0].name print("output name", output_name) output_shape = sess.get_outputs()[0].shape print("output shape", output_shape) output_type = sess.get_outputs()[0].type print("output type", output_type) x = numpy.random.random((1, 3, 192, 192)) x = x.astype(numpy.float32) import migraphx model = migraphx.parse_onnx("dm_nfnet_f0.onnx") model.compile(migraphx.get_target("gpu")) print(model.get_parameter_names()) print(model.get_parameter_shapes()) print(model.get_output_shapes()) result_migraphx = model.run({"inputs": x}) result_ort = sess.run([output_name], {input_name: x}) result_migraphx = result_migraphx[0].tolist() for i in range(10): x = numpy.random.random((1, 3, 192, 192)) x = x.astype(numpy.float32) result_migraphx = model.run({"inputs": x}) result_ort = sess.run([output_name], {input_name: x}) try: numpy.testing.assert_allclose(result_migraphx[0].tolist(), result_ort[0][0], rtol=1e-02) print(f"Test #{i} completed.") except AssertionError as e: print(e) ROCm-AMDMIGraphX-46524e8/examples/vision/python_nfnet/requirements_nfnet.txt000066400000000000000000000025231510465702400270600ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### opencv-python onnxruntime imageROCm-AMDMIGraphX-46524e8/examples/vision/python_resnet50/000077500000000000000000000000001510465702400227335ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/vision/python_resnet50/README.md000066400000000000000000000011601510465702400242100ustar00rootroot00000000000000# Performing Inference using MIGraphX Python Library ## Description This example uses a pre-trained Resnet50 V2 model to demonstrate how inference can be run in Python using the MIGraphX library. ## How to Use this Example If you do not already have Jupyter Notebooks installed, please refer to this [page](https://jupyter.org/install) for instructions. Once Jupyter Notebooks is installed, you can navigate to this directory and issue the command: ``` $ jupyter notebook ``` From the browser window that is launched, click on `resnet50_inference.ipynb` You should now be able to run the notebook from your browser. ROCm-AMDMIGraphX-46524e8/examples/vision/python_resnet50/imagenet_simple_labels.json000066400000000000000000000434211510465702400303160ustar00rootroot00000000000000["tench", "goldfish", "great white shark", "tiger shark", "hammerhead shark", "electric ray", "stingray", "cock", "hen", "ostrich", "brambling", "goldfinch", "house finch", "junco", "indigo bunting", "American robin", "bulbul", "jay", "magpie", "chickadee", "American dipper", "kite", "bald eagle", "vulture", "great grey owl", "fire salamander", "smooth newt", "newt", "spotted salamander", "axolotl", "American bullfrog", "tree frog", "tailed frog", "loggerhead sea turtle", "leatherback sea turtle", "mud turtle", "terrapin", "box turtle", "banded gecko", "green iguana", "Carolina anole", "desert grassland whiptail lizard", "agama", "frilled-necked lizard", "alligator lizard", "Gila monster", "European green lizard", "chameleon", "Komodo dragon", "Nile crocodile", "American alligator", "triceratops", "worm snake", "ring-necked snake", "eastern hog-nosed snake", "smooth green snake", "kingsnake", "garter snake", "water snake", "vine snake", "night snake", "boa constrictor", "African rock python", "Indian cobra", "green mamba", "sea snake", "Saharan horned viper", "eastern diamondback rattlesnake", "sidewinder", "trilobite", "harvestman", "scorpion", "yellow garden spider", "barn spider", "European garden spider", "southern black widow", "tarantula", "wolf spider", "tick", "centipede", "black grouse", "ptarmigan", "ruffed grouse", "prairie grouse", "peacock", "quail", "partridge", "grey parrot", "macaw", "sulphur-crested cockatoo", "lorikeet", "coucal", "bee eater", "hornbill", "hummingbird", "jacamar", "toucan", "duck", "red-breasted merganser", "goose", "black swan", "tusker", "echidna", "platypus", "wallaby", "koala", "wombat", "jellyfish", "sea anemone", "brain coral", "flatworm", "nematode", "conch", "snail", "slug", "sea slug", "chiton", "chambered nautilus", "Dungeness crab", "rock crab", "fiddler crab", "red king crab", "American lobster", "spiny lobster", "crayfish", "hermit crab", "isopod", "white stork", "black stork", "spoonbill", "flamingo", "little blue heron", "great egret", "bittern", "crane (bird)", "limpkin", "common gallinule", "American coot", "bustard", "ruddy turnstone", "dunlin", "common redshank", "dowitcher", "oystercatcher", "pelican", "king penguin", "albatross", "grey whale", "killer whale", "dugong", "sea lion", "Chihuahua", "Japanese Chin", "Maltese", "Pekingese", "Shih Tzu", "King Charles Spaniel", "Papillon", "toy terrier", "Rhodesian Ridgeback", "Afghan Hound", "Basset Hound", "Beagle", "Bloodhound", "Bluetick Coonhound", "Black and Tan Coonhound", "Treeing Walker Coonhound", "English foxhound", "Redbone Coonhound", "borzoi", "Irish Wolfhound", "Italian Greyhound", "Whippet", "Ibizan Hound", "Norwegian Elkhound", "Otterhound", "Saluki", "Scottish Deerhound", "Weimaraner", "Staffordshire Bull Terrier", "American Staffordshire Terrier", "Bedlington Terrier", "Border Terrier", "Kerry Blue Terrier", "Irish Terrier", "Norfolk Terrier", "Norwich Terrier", "Yorkshire Terrier", "Wire Fox Terrier", "Lakeland Terrier", "Sealyham Terrier", "Airedale Terrier", "Cairn Terrier", "Australian Terrier", "Dandie Dinmont Terrier", "Boston Terrier", "Miniature Schnauzer", "Giant Schnauzer", "Standard Schnauzer", "Scottish Terrier", "Tibetan Terrier", "Australian Silky Terrier", "Soft-coated Wheaten Terrier", "West Highland White Terrier", "Lhasa Apso", "Flat-Coated Retriever", "Curly-coated Retriever", "Golden Retriever", "Labrador Retriever", "Chesapeake Bay Retriever", "German Shorthaired Pointer", "Vizsla", "English Setter", "Irish Setter", "Gordon Setter", "Brittany", "Clumber Spaniel", "English Springer Spaniel", "Welsh Springer Spaniel", "Cocker Spaniels", "Sussex Spaniel", "Irish Water Spaniel", "Kuvasz", "Schipperke", "Groenendael", "Malinois", "Briard", "Australian Kelpie", "Komondor", "Old English Sheepdog", "Shetland Sheepdog", "collie", "Border Collie", "Bouvier des Flandres", "Rottweiler", "German Shepherd Dog", "Dobermann", "Miniature Pinscher", "Greater Swiss Mountain Dog", "Bernese Mountain Dog", "Appenzeller Sennenhund", "Entlebucher Sennenhund", "Boxer", "Bullmastiff", "Tibetan Mastiff", "French Bulldog", "Great Dane", "St. Bernard", "husky", "Alaskan Malamute", "Siberian Husky", "Dalmatian", "Affenpinscher", "Basenji", "pug", "Leonberger", "Newfoundland", "Pyrenean Mountain Dog", "Samoyed", "Pomeranian", "Chow Chow", "Keeshond", "Griffon Bruxellois", "Pembroke Welsh Corgi", "Cardigan Welsh Corgi", "Toy Poodle", "Miniature Poodle", "Standard Poodle", "Mexican hairless dog", "grey wolf", "Alaskan tundra wolf", "red wolf", "coyote", "dingo", "dhole", "African wild dog", "hyena", "red fox", "kit fox", "Arctic fox", "grey fox", "tabby cat", "tiger cat", "Persian cat", "Siamese cat", "Egyptian Mau", "cougar", "lynx", "leopard", "snow leopard", "jaguar", "lion", "tiger", "cheetah", "brown bear", "American black bear", "polar bear", "sloth bear", "mongoose", "meerkat", "tiger beetle", "ladybug", "ground beetle", "longhorn beetle", "leaf beetle", "dung beetle", "rhinoceros beetle", "weevil", "fly", "bee", "ant", "grasshopper", "cricket", "stick insect", "cockroach", "mantis", "cicada", "leafhopper", "lacewing", "dragonfly", "damselfly", "red admiral", "ringlet", "monarch butterfly", "small white", "sulphur butterfly", "gossamer-winged butterfly", "starfish", "sea urchin", "sea cucumber", "cottontail rabbit", "hare", "Angora rabbit", "hamster", "porcupine", "fox squirrel", "marmot", "beaver", "guinea pig", "common sorrel", "zebra", "pig", "wild boar", "warthog", "hippopotamus", "ox", "water buffalo", "bison", "ram", "bighorn sheep", "Alpine ibex", "hartebeest", "impala", "gazelle", "dromedary", "llama", "weasel", "mink", "European polecat", "black-footed ferret", "otter", "skunk", "badger", "armadillo", "three-toed sloth", "orangutan", "gorilla", "chimpanzee", "gibbon", "siamang", "guenon", "patas monkey", "baboon", "macaque", "langur", "black-and-white colobus", "proboscis monkey", "marmoset", "white-headed capuchin", "howler monkey", "titi", "Geoffroy's spider monkey", "common squirrel monkey", "ring-tailed lemur", "indri", "Asian elephant", "African bush elephant", "red panda", "giant panda", "snoek", "eel", "coho salmon", "rock beauty", "clownfish", "sturgeon", "garfish", "lionfish", "pufferfish", "abacus", "abaya", "academic gown", "accordion", "acoustic guitar", "aircraft carrier", "airliner", "airship", "altar", "ambulance", "amphibious vehicle", "analog clock", "apiary", "apron", "waste container", "assault rifle", "backpack", "bakery", "balance beam", "balloon", "ballpoint pen", "Band-Aid", "banjo", "baluster", "barbell", "barber chair", "barbershop", "barn", "barometer", "barrel", "wheelbarrow", "baseball", "basketball", "bassinet", "bassoon", "swimming cap", "bath towel", "bathtub", "station wagon", "lighthouse", "beaker", "military cap", "beer bottle", "beer glass", "bell-cot", "bib", "tandem bicycle", "bikini", "ring binder", "binoculars", "birdhouse", "boathouse", "bobsleigh", "bolo tie", "poke bonnet", "bookcase", "bookstore", "bottle cap", "bow", "bow tie", "brass", "bra", "breakwater", "breastplate", "broom", "bucket", "buckle", "bulletproof vest", "high-speed train", "butcher shop", "taxicab", "cauldron", "candle", "cannon", "canoe", "can opener", "cardigan", "car mirror", "carousel", "tool kit", "carton", "car wheel", "automated teller machine", "cassette", "cassette player", "castle", "catamaran", "CD player", "cello", "mobile phone", "chain", "chain-link fence", "chain mail", "chainsaw", "chest", "chiffonier", "chime", "china cabinet", "Christmas stocking", "church", "movie theater", "cleaver", "cliff dwelling", "cloak", "clogs", "cocktail shaker", "coffee mug", "coffeemaker", "coil", "combination lock", "computer keyboard", "confectionery store", "container ship", "convertible", "corkscrew", "cornet", "cowboy boot", "cowboy hat", "cradle", "crane (machine)", "crash helmet", "crate", "infant bed", "Crock Pot", "croquet ball", "crutch", "cuirass", "dam", "desk", "desktop computer", "rotary dial telephone", "diaper", "digital clock", "digital watch", "dining table", "dishcloth", "dishwasher", "disc brake", "dock", "dog sled", "dome", "doormat", "drilling rig", "drum", "drumstick", "dumbbell", "Dutch oven", "electric fan", "electric guitar", "electric locomotive", "entertainment center", "envelope", "espresso machine", "face powder", "feather boa", "filing cabinet", "fireboat", "fire engine", "fire screen sheet", "flagpole", "flute", "folding chair", "football helmet", "forklift", "fountain", "fountain pen", "four-poster bed", "freight car", "French horn", "frying pan", "fur coat", "garbage truck", "gas mask", "gas pump", "goblet", "go-kart", "golf ball", "golf cart", "gondola", "gong", "gown", "grand piano", "greenhouse", "grille", "grocery store", "guillotine", "barrette", "hair spray", "half-track", "hammer", "hamper", "hair dryer", "hand-held computer", "handkerchief", "hard disk drive", "harmonica", "harp", "harvester", "hatchet", "holster", "home theater", "honeycomb", "hook", "hoop skirt", "horizontal bar", "horse-drawn vehicle", "hourglass", "iPod", "clothes iron", "jack-o'-lantern", "jeans", "jeep", "T-shirt", "jigsaw puzzle", "pulled rickshaw", "joystick", "kimono", "knee pad", "knot", "lab coat", "ladle", "lampshade", "laptop computer", "lawn mower", "lens cap", "paper knife", "library", "lifeboat", "lighter", "limousine", "ocean liner", "lipstick", "slip-on shoe", "lotion", "speaker", "loupe", "sawmill", "magnetic compass", "mail bag", "mailbox", "tights", "tank suit", "manhole cover", "maraca", "marimba", "mask", "match", "maypole", "maze", "measuring cup", "medicine chest", "megalith", "microphone", "microwave oven", "military uniform", "milk can", "minibus", "miniskirt", "minivan", "missile", "mitten", "mixing bowl", "mobile home", "Model T", "modem", "monastery", "monitor", "moped", "mortar", "square academic cap", "mosque", "mosquito net", "scooter", "mountain bike", "tent", "computer mouse", "mousetrap", "moving van", "muzzle", "nail", "neck brace", "necklace", "nipple", "notebook computer", "obelisk", "oboe", "ocarina", "odometer", "oil filter", "organ", "oscilloscope", "overskirt", "bullock cart", "oxygen mask", "packet", "paddle", "paddle wheel", "padlock", "paintbrush", "pajamas", "palace", "pan flute", "paper towel", "parachute", "parallel bars", "park bench", "parking meter", "passenger car", "patio", "payphone", "pedestal", "pencil case", "pencil sharpener", "perfume", "Petri dish", "photocopier", "plectrum", "Pickelhaube", "picket fence", "pickup truck", "pier", "piggy bank", "pill bottle", "pillow", "ping-pong ball", "pinwheel", "pirate ship", "pitcher", "hand plane", "planetarium", "plastic bag", "plate rack", "plow", "plunger", "Polaroid camera", "pole", "police van", "poncho", "billiard table", "soda bottle", "pot", "potter's wheel", "power drill", "prayer rug", "printer", "prison", "projectile", "projector", "hockey puck", "punching bag", "purse", "quill", "quilt", "race car", "racket", "radiator", "radio", "radio telescope", "rain barrel", "recreational vehicle", "reel", "reflex camera", "refrigerator", "remote control", "restaurant", "revolver", "rifle", "rocking chair", "rotisserie", "eraser", "rugby ball", "ruler", "running shoe", "safe", "safety pin", "salt shaker", "sandal", "sarong", "saxophone", "scabbard", "weighing scale", "school bus", "schooner", "scoreboard", "CRT screen", "screw", "screwdriver", "seat belt", "sewing machine", "shield", "shoe store", "shoji", "shopping basket", "shopping cart", "shovel", "shower cap", "shower curtain", "ski", "ski mask", "sleeping bag", "slide rule", "sliding door", "slot machine", "snorkel", "snowmobile", "snowplow", "soap dispenser", "soccer ball", "sock", "solar thermal collector", "sombrero", "soup bowl", "space bar", "space heater", "space shuttle", "spatula", "motorboat", "spider web", "spindle", "sports car", "spotlight", "stage", "steam locomotive", "through arch bridge", "steel drum", "stethoscope", "scarf", "stone wall", "stopwatch", "stove", "strainer", "tram", "stretcher", "couch", "stupa", "submarine", "suit", "sundial", "sunglass", "sunglasses", "sunscreen", "suspension bridge", "mop", "sweatshirt", "swimsuit", "swing", "switch", "syringe", "table lamp", "tank", "tape player", "teapot", "teddy bear", "television", "tennis ball", "thatched roof", "front curtain", "thimble", "threshing machine", "throne", "tile roof", "toaster", "tobacco shop", "toilet seat", "torch", "totem pole", "tow truck", "toy store", "tractor", "semi-trailer truck", "tray", "trench coat", "tricycle", "trimaran", "tripod", "triumphal arch", "trolleybus", "trombone", "tub", "turnstile", "typewriter keyboard", "umbrella", "unicycle", "upright piano", "vacuum cleaner", "vase", "vault", "velvet", "vending machine", "vestment", "viaduct", "violin", "volleyball", "waffle iron", "wall clock", "wallet", "wardrobe", "military aircraft", "sink", "washing machine", "water bottle", "water jug", "water tower", "whiskey jug", "whistle", "wig", "window screen", "window shade", "Windsor tie", "wine bottle", "wing", "wok", "wooden spoon", "wool", "split-rail fence", "shipwreck", "yawl", "yurt", "website", "comic book", "crossword", "traffic sign", "traffic light", "dust jacket", "menu", "plate", "guacamole", "consomme", "hot pot", "trifle", "ice cream", "ice pop", "baguette", "bagel", "pretzel", "cheeseburger", "hot dog", "mashed potato", "cabbage", "broccoli", "cauliflower", "zucchini", "spaghetti squash", "acorn squash", "butternut squash", "cucumber", "artichoke", "bell pepper", "cardoon", "mushroom", "Granny Smith", "strawberry", "orange", "lemon", "fig", "pineapple", "banana", "jackfruit", "custard apple", "pomegranate", "hay", "carbonara", "chocolate syrup", "dough", "meatloaf", "pizza", "pot pie", "burrito", "red wine", "espresso", "cup", "eggnog", "alp", "bubble", "cliff", "coral reef", "geyser", "lakeshore", "promontory", "shoal", "seashore", "valley", "volcano", "baseball player", "bridegroom", "scuba diver", "rapeseed", "daisy", "yellow lady's slipper", "corn", "acorn", "rose hip", "horse chestnut seed", "coral fungus", "agaric", "gyromitra", "stinkhorn mushroom", "earth star", "hen-of-the-woods", "bolete", "ear", "toilet paper"] ROCm-AMDMIGraphX-46524e8/examples/vision/python_resnet50/resnet50_inference.ipynb000066400000000000000000000277321510465702400274740ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Resnet50 Inference\n", "\n", "## Description\n", "This example performs inference on a short wildlife video using a Resnet50 V2 model that has been pre-trained on imagenet data. The labels used for each class are simplified for readability, but still reflect the correct index-label pairs in official use. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install --upgrade pip\n", "!pip install opencv-python==4.1.2.30\n", "!pip install matplotlib\n", "import numpy as np\n", "from matplotlib import pyplot as plt \n", "import cv2\n", "import json\n", "import time\n", "import os.path\n", "from os import path \n", "import sys" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Importing MIGraphX Library\n", "Sometimes the PYTHONPATH variable is not set during installation of MIGraphX. \n", "If your receive a \"Module Not Found\" error when trying to `import migraphx` in your own application, try running:\n", "```\n", "$ export PYTHONPATH=/opt/rocm/lib:$PYTHONPATH\n", "```\n", "For this example, the library will be added to the kernel's sys.path.\n", "\n", "If you receive \"cannot open shared object file: No such file or directory\" , please make sure `/opt/rocm/lib` is included in $LD_LIBRARY_PATH\n", "```\n", " cannot open shared object file: No such file or directory\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "migx_lib_path = \"/opt/rocm/lib\"\n", "if migx_lib_path not in sys.path:\n", " sys.path.append(migx_lib_path)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import migraphx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If this is your first time running this example, you will need to dowload the model and sample video.\n", "\n", "The following cell will ask you for your sudo password and then install/update the package `youtube-dl` if necessary. It will then use that tool to download the sample video." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if not path.exists(\"./sample_vid.mp4\"):\n", " import getpass\n", " import os\n", " password = getpass.getpass()\n", " command = \"sudo -H -S pip install --upgrade youtube-dl\"\n", " os.system('echo %s | %s' % (password, command))\n", " !youtube-dl https://youtu.be/TkqYmvH_XVs \n", " !mv sample_vid-TkqYmvH_XVs.mp4 sample_vid.mp4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following will download the resnet50 v2 model from ONNX's model zoo." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if not path.exists(\"./resnet50.onnx\"):\n", " !wget https://github.com/onnx/models/raw/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx", " !mv resnet50-v2-7.onnx resnet50.onnx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Load the simplified imagenet labels." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open('imagenet_simple_labels.json') as json_data:\n", " labels = json.load(json_data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Model and Video Capture Setup\n", "\n", "The ONNX graph that is loaded by `parse_onnx()` is a generalized representation that must be compiled for a specific target before it can be executed. For this example, using the target \"gpu\" is recommended. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = migraphx.parse_onnx(\"resnet50.onnx\")\n", "model.compile(migraphx.get_target(\"gpu\"))\n", "model.print() # Printed in terminal \n", "cap = cv2.VideoCapture(\"sample_vid.mp4\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pre-Processing Video Frames\n", "Resnet50 requires some preprocessing of video frames before it can run inference. \n", "\n", "The model will expect an NCHW tensor with the shape {1, 3, 224, 224} and the values loaded into a range of [0, 1] and then normalized using mean = [0.485, 0.456, 0.406] and std = [0.229, 0.224, 0.225]. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first step is to square up the dimensions of the original image by cropping the longer of the two to the size of the shorter dimension. This will help to avoid any stretching or compressing of the input image.\n", "Then the image can be scaled up or down to the desired resolution of 224x224." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def make_nxn(image, n):\n", " width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n", " height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n", " if height > width:\n", " dif = height - width\n", " bar = dif // 2 \n", " square = image[(bar + (dif % 2)):(height - bar),:]\n", " return cv2.resize(square, (n, n))\n", " elif width > height:\n", " dif = width - height\n", " bar = dif // 2\n", " square = image[:,(bar + (dif % 2)):(width - bar)]\n", " return cv2.resize(square, (n, n))\n", " else:\n", " return cv2.resize(image, (n, n))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that the image data has the correct dimensions, the values can be normalized as described above." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def preprocess(img_data):\n", " mean_vec = np.array([0.485, 0.456, 0.406])\n", " stddev_vec = np.array([0.229, 0.224, 0.225])\n", " norm_img_data = np.zeros(img_data.shape).astype('float32')\n", " for i in range(img_data.shape[0]): \n", " norm_img_data[i,:,:] = (img_data[i,:,:]/255 - mean_vec[i]) / stddev_vec[i]\n", " return norm_img_data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run Inference on Single Frame\n", "\n", "The above pre-processing functions can now be applied to individual video frames and the data can be passed to the model for evaluation. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def predict_class(frame) -> int:\n", " # Crop and resize original image\n", " cropped = make_nxn(frame, 224)\n", " # Convert from HWC to CHW\n", " chw = cropped.transpose(2,0,1)\n", " # Apply normalization\n", " pp = preprocess(chw)\n", " # Add singleton dimension (CHW to NCHW)\n", " data = np.expand_dims(pp.astype('float32'),0)\n", " # Run the model\n", " results = model.run({'data':data})\n", " # Extract the index of the top prediction\n", " res_npa = np.array(results[0])\n", " return np.argmax(res_npa)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Inference Loop over Full Video\n", "\n", "Now everything is in place so that we can run inference on each frame of the input video. The video will be played and the predicted label will be displayed on top of each frame. If you are working on headless server, please execute the following cell." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "while (cap.isOpened()):\n", " start = time.perf_counter()\n", " ret, frame = cap.read()\n", " if not ret: break\n", " \n", " top_prediction = predict_class(frame)\n", " \n", " end = time.perf_counter()\n", " fps = 1 / (end - start)\n", " fps_str = f\"Frames per second: {fps:0.1f}\"\n", " label_str = \"Top prediction: {}\".format(labels[top_prediction])\n", "\n", " labeled = cv2.putText(frame, \n", " label_str, \n", " (50, 50), \n", " cv2.FONT_HERSHEY_SIMPLEX, \n", " 2, \n", " (255, 255, 255), \n", " 3, \n", " cv2.LINE_AA)\n", " labeled = cv2.putText(labeled, \n", " fps_str, \n", " (50, 1060), \n", " cv2.FONT_HERSHEY_SIMPLEX, \n", " 2, \n", " (255, 255, 255), \n", " 3, \n", " cv2.LINE_AA)\n", " cv2.imshow(\"Resnet50 Inference\", labeled)\n", "\n", " if cv2.waitKey(1) & 0xFF == ord('q'): # 'q' to quit\n", " break\n", "\n", "cap.release()\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If script is run on a headless server where .imshow() experiences problems, the following cell for histogram can be run to verify functionalty:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "output_labels = []\n", "while (cap.isOpened()):\n", " start = time.perf_counter()\n", " ret, frame = cap.read()\n", " if not ret: break\n", " \n", " top_prediction = predict_class(frame)\n", " output_labels.append(labels[top_prediction])\n", "\n", "cap.release()\n", "output_labels = np.array(output_labels)\n", "plt.hist(output_labels) \n", "plt.xticks(rotation = 90)\n", "plt.show()" ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 4 } ROCm-AMDMIGraphX-46524e8/examples/vision/python_super_resolution/000077500000000000000000000000001510465702400247075ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/vision/python_super_resolution/README.md000066400000000000000000000103721510465702400261710ustar00rootroot00000000000000# Super Resolution with AMD MIGraphX This example is based on [ONNX run_super_resolution_model notebook](https://github.com/onnx/models/blob/master/vision/super_resolution/sub_pixel_cnn_2016/dependencies/Run_Super_Resolution_Model.ipynb) and modified for MIGraphX. ## Description Given an input image, this application resizes the image to 224x224 and then scales it to 672x672, thus it is useful for upscaling low-resolution images. ### Model Utilized > "Super Resolution uses efficient [Sub-pixel convolutional layer](https://arxiv.org/abs/1609.05158) described for increasing spatial resolution within network tasks. By increasing pixel count, images are then clarified, sharpened, and upscaled without losing the input image’s content and characteristics." [[Reference]](https://github.com/onnx/models/blob/master/vision/super_resolution/sub_pixel_cnn_2016/README.md) Model in PyTorch definitions: ``` self.relu = nn.ReLU(inplace=inplace) self.conv1 = nn.Conv2d(1, 64, (5, 5), (1, 1), (2, 2)) self.conv2 = nn.Conv2d(64, 64, (3, 3), (1, 1), (1, 1)) self.conv3 = nn.Conv2d(64, 32, (3, 3), (1, 1), (1, 1)) self.conv4 = nn.Conv2d(32, upscale_factor ** 2, (3, 3), (1, 1), (1, 1)) self.pixel_shuffle = nn.PixelShuffle(upscale_factor) ``` ## How-to If you have jupyter installed, you can simply use the notebook given. Otherwise please follow the step-by-step guide. ### Jupyter Notebook Run Jupyter notebook server on a ROCm and MIGraphX installed system, and run `Run_Super_Resolution_Model.ipynb` ### Step by Step 1) Upgrade pip3. You may skip this stage if you already have latest pip3. This step is needed for OpenCV installation. ``` pip3 install --upgrade pip ``` 2) Install requirements. ``` pip3 install -r requirements.txt ``` 3) Import required libraries. ``` import numpy as np import matplotlib.pyplot as plt from PIL import Image, ImageDraw, ImageFont from resizeimage import resizeimage ``` 4) Download ONNX model. ``` wget -nc https://github.com/onnx/models/raw/main/validated/vision/super_resolution/sub_pixel_cnn_2016/model/super-resolution-10.onnx ``` 5) Preprocess the sample image `cat.jpg`. ``` orig_img = Image.open("./cat.jpg") print(orig_img.size) img = resizeimage.resize_cover(orig_img, [224,224], validate=False) img_ycbcr = img.convert('YCbCr') img_y_0, img_cb, img_cr = img_ycbcr.split() img_ndarray = np.asarray(img_y_0) img_4 = np.expand_dims(np.expand_dims(img_ndarray, axis=0), axis=0) img_5 = img_4.astype(np.float32) / 255.0 ``` 6) Import MIGraphX, parse & compile the ONNX model with MIGraphX. Print the model. ``` model = migraphx.parse_onnx("super-resolution-10.onnx") model.compile(migraphx.get_target("gpu")) model.print() ``` 7) You can check the model inputs and outputs with the following functions. ``` print(model.get_parameter_names()) print(model.get_parameter_shapes()) print(model.get_output_shapes()) ``` 8) Run the image throgh model and get the output data. ``` result = model.run({ "input": img_5 }) data = np.array(result[0])[0] ``` 9) Post processing image. If matplotlib is installed correctly, it should show up the image. The output image will be stored with filename `output.jpg`. ``` img_out_y = Image.fromarray(np.uint8((data* 255.0).clip(0, 255)[0]), mode='L') # get the output image follow post-processing step from PyTorch implementation final_img = Image.merge( "YCbCr", [ img_out_y, img_cb.resize(img_out_y.size, Image.BICUBIC), img_cr.resize(img_out_y.size, Image.BICUBIC), ]).convert("RGB") final_img.save("output.jpg") print(final_img.size) ``` 10) Measure the improvement in terms of PSNR and show the both input and super-resolution image: ``` import cv2 imgIN = cv2.imread('cat.jpg') imgOUT = cv2.imread('output.jpg') imgIN = cv2.cvtColor(imgIN, cv2.COLOR_BGR2RGB) #BGR to RGB imgOUT = cv2.cvtColor(imgOUT, cv2.COLOR_BGR2RGB) imgIN_resized = cv2.resize(imgIN, (672,672)) #Resizing input to 672 psnr = cv2.PSNR(imgIN_resized, imgOUT) #dimensions need to be same print("PSNR Value = %.3f db"%psnr) fig = plt.figure(figsize=(16, 16)) sp1 = fig.add_subplot(1, 2, 1) sp1.title.set_text('Output Super Resolution Image (%sx%s)'%(imgOUT.shape[0], imgOUT.shape[1])) plt.imshow(imgOUT) sp2 = fig.add_subplot(1, 2, 2) sp2.title.set_text('Input Image (%sx%s)'%(imgIN.shape[0], imgIN.shape[1])) plt.imshow(imgIN) plt.show() ``` ROCm-AMDMIGraphX-46524e8/examples/vision/python_super_resolution/Run_Super_Resolution_Model.ipynb000066400000000000000000000150371510465702400332450ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Super Resolution Inference with AMD MIGraphX\n", "This notebook is inspired from: https://github.com/onnx/models/blob/master/vision/super_resolution/sub_pixel_cnn_2016/dependencies/Run_Super_Resolution_Model.ipynb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install Dependencies" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip3 install --upgrade pip #needed for opencv-python installation\n", "!pip3 install -r requirements.txt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from PIL import Image, ImageDraw, ImageFont\n", "from resizeimage import resizeimage\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Download ONNX Model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!wget -nc https://github.com/onnx/models/raw/main/validated/vision/super_resolution/sub_pixel_cnn_2016/model/super-resolution-10.onnx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Import MIGraphX Python Module" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import migraphx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Preprocessing Image" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "orig_img = Image.open(\"./cat.jpg\")\n", "print(orig_img.size)\n", "img = resizeimage.resize_cover(orig_img, [224,224], validate=False)\n", "img_ycbcr = img.convert('YCbCr')\n", "img_y_0, img_cb, img_cr = img_ycbcr.split()\n", "img_ndarray = np.asarray(img_y_0)\n", "\n", "img_4 = np.expand_dims(np.expand_dims(img_ndarray, axis=0), axis=0)\n", "img_5 = img_4.astype(np.float32) / 255.0\n", "img_5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Run Model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = migraphx.parse_onnx(\"super-resolution-10.onnx\")\n", "model.compile(migraphx.get_target(\"gpu\"))\n", "#model.print()\n", "\n", "print(model.get_parameter_names())\n", "print(model.get_parameter_shapes())\n", "print(model.get_output_shapes())\n", "\n", "\n", "result = model.run({\n", " \"input\": img_5\n", " })\n", "\n", "data = np.array(result[0])[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Postprocessing Image" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "img_out_y = Image.fromarray(np.uint8((data* 255.0).clip(0, 255)[0]), mode='L')\n", "# get the output image follow post-processing step from PyTorch implementation\n", "final_img = Image.merge(\n", " \"YCbCr\", [\n", " img_out_y,\n", " img_cb.resize(img_out_y.size, Image.BICUBIC),\n", " img_cr.resize(img_out_y.size, Image.BICUBIC),\n", " ]).convert(\"RGB\")\n", "final_img.save(\"output.jpg\")\n", "print(final_img.size)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## PSNR Comparison Output vs Input" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import cv2\n", "\n", "imgIN = cv2.imread('cat.jpg')\n", "imgOUT = cv2.imread('output.jpg')\n", "imgIN = cv2.cvtColor(imgIN, cv2.COLOR_BGR2RGB) #BGR to RGB\n", "imgOUT = cv2.cvtColor(imgOUT, cv2.COLOR_BGR2RGB)\n", "\n", "imgIN_resized = cv2.resize(imgIN, (672,672)) #Resizing input to 672\n", "\n", "psnr = cv2.PSNR(imgIN_resized, imgOUT) #dimensions need to be same\n", "print(\"PSNR Value = %.3f db\"%psnr)\n", "\n", "fig = plt.figure(figsize=(16, 16))\n", "sp1 = fig.add_subplot(1, 2, 1)\n", "sp1.title.set_text('Output Super Resolution Image (%sx%s)'%(imgOUT.shape[0], imgOUT.shape[1]))\n", "plt.imshow(imgOUT)\n", "\n", "sp2 = fig.add_subplot(1, 2, 2)\n", "sp2.title.set_text('Input Image (%sx%s)'%(imgIN.shape[0], imgIN.shape[1]))\n", "plt.imshow(imgIN)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## " ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 2 } ROCm-AMDMIGraphX-46524e8/examples/vision/python_super_resolution/cat.jpg000066400000000000000000004356231510465702400261750ustar00rootroot00000000000000ÿØÿá*÷ExifII*JR(i‡Z´,,0231‘ 0100   ¤ (Û)HHÿØÿÛ„   ÿÝ ÿÀ  "ÿÄ¢ }!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùú w!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ý¶‚Øw­yµ>FkQb ¶ûÙ•£·áZ–öã€iñ[ó’+N†þ”âÒ!„Vá€Ç´#‡Ò¥H½*ôP†4ù€Š8â®%ºŽHãÒ¬Ão“Íi$!E;§°Šp[~ìV„Qmkέ>#øsPø€<§\,·C$’mäBLôÈÏ t¯[ŽqM…·),bŸäZ{ôÅ=bµR$¢ f†ƒµÄ` ®àÅtXÎZ.ÍeÜÆ |£šßž÷±Ò±§A’£¥m’¶±‡4`|Ýë.u r8ÅoÜǵ>JÈ*«šô°é;1WΰD]«Ä|[|¸g'ŠõO߬j@8ò¯Ä¯A§YË#¶8®å¦§/²MÜÿÐýÞ†*ÔŠ"0jPV”1© gëŠ Ùb;Ö‚D§M‰@Uä^ëL‘c‹æÖœQ㊢ŒwdUÄcžh·`V4âNŒ+Îþ/xÕ¼àyõKP^öà‹{DQ’e“ùsÛ…wñ\B¿!5ñçÅi~#ý¢|9ðž9CÿgD×·)œ Î?vÑW5†.¿³£'öýÜ·ªÖQ{ºÖ«â_„Þñ7ˆgC§Ír‰s…¦;AŒóÉç=kôÎxîíc¹„å]AWŴƵ¥ëÚŸƒvy†CÌqdmÀË?¥{¿À/Iâ/†:l÷@‰` m b Ýlê29Vxò{§^kãí-cÚSaâ¬^¢£M£8Ú}+×ó퇬ù¶óîjã•+ÅeÈûyì+¢Þú•§ùFßÒ²$SÍ^“ç8íU&¦»iS“*üÛE`j3ˆ£8ãŠÞ˜íC^qâ@F§'®Ø®‚nÈóê‘DZn•ùÃñçÇ^ZImãÒ¾®ø¥â¨ôëX78¯ÊŠ~$ŸV¿uS¸±ÀZãÌ1*å*œÿÑýô·R+ÆzF­©xfõ<=9µ¿7Ù䜗#Óµt0¡ÆJÖŒùz}kh# Ÿ'ü>µñÍ´Ñh¾ –ïU² —b陑¤Ø—ËùU“…Çn¹¤›öŸ—áþ§&•ñ“N’Êß`½¶F’!ýÝØ^ãÐqëX~(Ôçø?ñ¾öþ3¦¬Â“³ÊÙi!µÖ8p1–$ý+À?hÏŠžñž‘=‚¤Á“<çÿ@OÓú×~+•(ÊM*±nÍh~’xKâO‚1Ö„19cŽ+ÒüGªlV9¯þ-øÁ,m%PølVÒ—*¸Xù[㇎$•ä·ø¯’ü)¡Kâ-wûFš(ú}k¢ñ–¥uâ `ÙÁó4‡·¥{7‚¼-•a*Ž™¯–ÅVu$wP¤’ÔÿÒþmÀ¿sžÝª…¾Ò£Ú´TñŠÝ#˜ùÇö¢øiÿ á̺®“ ·Ö4LÝÚK‚rfH›n[k¨ìø§âoŽž Òü q¥Ï‡U¸„\ƶó«Ç*J ù]èGm®EEúŒ°A§Ï=˪D‘±flŸÂ¿ŒoÚOmWâ—ˆu%4=!¤1O9»6Åàõ'…QŽ=1ZOãMXîÁÁIòƒ›â7Œ[#N1&Ð a÷©"ñ–› ç§¿LWˆÅòkmêtÒi3öƒãmσd×í´ÝÍž —¶z•¹Ã‰¥Þ¥Y_ ÛÇÆkõKÇŸl|ÿññ¿¤ÈñÞ*ÚÀ|ƒ¶b—¤ ^Cß¼‚Aàâ¿”ÿ…5¿[Üx+Ä÷rK}¥ÿÇ´íòI'—"Xð0ñàãÖ¿W|ñzËâÀ)>|W¾ŒYjĦdhá)pmÛ _—èA¥yù¥D½ãÕÁSÑÛþø+ö¬»øwªøÖÿMðeÄ:‡„¯l±H"Œ}–ä8*û±¼œžû†+ìߨjêpZë‚K›[9.D$I‚‰»Kc…ÜŠùûÂÿ²÷ög‹%Ò¼u‹m#Igt/#Ç.qŒd7¥~Ÿ|6“CЛJðWƒü[ge­4;ííc„Kv ýÐIcBpÄqÚ¼*Õ!C KeÔéʲ¹ûGmO}ø‘àí'H×´ÿ\i¨÷¹72C'–«Žž?“Ù{žkó{âŠ[Á¿´šøkÁ7 ¨‡T*‘n,®zîÁ<ð®óÆþý§|qã'Ò|M¨}Š'¸‘<åÜlÃÜê6,@tíÈÆ3Wt]CÁ_.æðÎ#jÞ%¹•üÉ®6m¬Ao•6¤kŽ0§>æ½Õûað-< ðëIÔïô9Vúöîìd¹É ËtQ·=q^î:1ä†ÇˆÃB¯½%©÷s3‡Ä£®Z¾Æc^=«–ÿ„ú/ì„kƒºVæFL[û«Ž€zÖúÍÜorP é󞘯 Ë±Îråg‹ŒËÝ$šØàSàÍó(ì \Ø´î—CÕÂ|(óÿ~Ê×ÄWû_\Ðî$'6–pÆIe8Â3³n>„†¾jñ¯ì)áË™Ž‹úUΘ¾mëDâE‘Ó6sÛ­~æxcöƒø©èVþ²ÒSFÓ×I©8?hÚÌ`ˆ£rGL©¸¯­çð/Á¿ˆÿ ‡ÃŸݬR¸Ìn¥ž^¬ìûH.[¸qÀÇo=Í'n‡© 7µÏä»àÃhm~"êcÊ‹Q—S·xŸg˜H,›ß -·¶;W®ë_u?„~ºðÇ´MKLñ2SA—Vh®­¦y™wH‰0€xÚr@Á'Çíå÷ìkà=&»¸²T¿êÈ-‚¾ å@¥Ø1!NùAE~o~Þ¾+’ãÁ‡BðÝÄ“Þiî‚^žFÂqŒäî÷ä×™Pöõcðù;KÈú|¯õJsœRæjÛ_îìüÑàuøOg£øGOºkÇÒaÈ8ÒË·-.2WbŸ›Ÿ»€+‰øNº³éú—‰nu9 ¾½éšD’9%ž9„~Zó:6CÞ9¾Qð®³â}q®<]ãMAõ‹è$ŽÉ㹈¼k ¨_peé÷Bãóãôž‘ñ¯Cð&zuv¸š+¹ˆ-öÉ$lAî\ ÆøÚ¸ÁÆ:ô¯â,Tšúµ%sî8B…(µV÷O­òŽÆ¾Õ|~žžM@¼yím¥hв•™?NOáÇã^G àgNwÃ6¦·Okc(Õ§þÕàöksô¹>=økJÑ4ÁBk]fÉy"|¢á%ùX±aÛ@WÜ? ~$üT{3|í9o2Êo¼ ÀÁUÛ’=¹¯çKKÖµ­wÄ–¶Þ lÍ4ÊÛIÀRq¸Ÿáù¯ÛO†7ž °Ð§­Oqyz±yVmrb"a€w&Tl_öïÒ¿_ÂÞtÓžŒüR¼c ¼±zWk?m![Y¹i-t«u¢XG„G•ÁåôçÓŠú+àWíùâo/Øü[BÎOôvW‘5¹†*ø-ÎILŸE=«Ñ~Xø÷â~›…®i:n³cä„yÔÉ çn eÚFJÿw­ï~ÂþðÆ­ˆ4Ç88xãùþl–>Òx>ü=«NkkIá×È¡â?âÓh*fº·>c†ESѽm!—·lf¸¯è?b”"G¾m¼¹õ8Q€9­ xÿÃþø‰d¦Aml²6~1ÕùùUqþÏðóž1[~;ñ·†ôOcwp±Ë*‚±/a!Àaµ~mÜ}+ju®ýæy•°é+ĵ¹DË çÿ­Dß"qÅMÅå­Óc§·á[º˜šÿÚ&·¶Æ}û½+\òîÿÔýÚ³¼ mS]½áeÃsøð¯1³½Ü@Zêm/)çÖ·KC‹™>þÖ â_ƒÆ\”‹•D;I?…~·ìßñKñ ÷þ˜hz|2I“py$þèä³·· ýì þ–§µ¶Ôáò®S|kÛÔúWˆk¿ 4Ûß[jó.ð»ÄPµT‘žëîk«êJ´SêŽü+—Ýgâ>³ ü~¶ÓÑ&xƒÄ¶ ¥ø‚ô[ò å¨;É$ðHÎ{`b¿.õ¹5ŸêGÄž,»g,ÀÝ¿1”} Ï8®ßÏ­|öcQáiÞûžÖW‹­Ë¢=ƒÃŸkñ>‰oá[›Ø4û©¦1Ú‰“&«¬[†TÞÀ2ŒŒqõ¯ÇÏÚözð׌SQÖ4;Ë —ìªÅpä ã§áôÅtà³àe®«ñ<|Æ•<Ʋ|­/ûwî? ¿b-'ÂÉûUx[Nñ¥›^YMtcXv’1Bc.£ï( :tÏ¿¨öÒ> øžÿǪZÄJY#DQ°®:DÂs×°¯ÆO üñž‹ûCx>ëÂöÎ':¥¾fea¤Â5a˜ûÄ‘ÇJþÊ| .—¤Y¥´¡À2œ¯=q_¯ðîy mâ¶?!ân–Q‹w¹Kö[ý›ìþh±Å«Ï+ï V>TÆv>ñ>¦¾Ó‡ÀžŸH–Âú12ç§Þ®xsG2Cö>fÆIï_:~ÜŸ4_ƒßµÛxuHluýfÖ[=1 (“Ì”l2"÷òÃgëŠö¤Ô"å-‘òö•ª(Cw¡üÔ|cÔ~øsöÃÕtO…ÅçÐmïäÕ$•váŒùj„”Ü‘ü#½ywÄÿ‹%Ót/ÆÒãM»‚Ø´@>xódïí#ŽŒ×“k_ œuü«ìp-ÚÇÆcŠGÿÕý}´¼Ú»õàVýã±ë^!o¬^X8KÅ#ûWw¢kö»„¤ð¿Ìt®ˆœ6=•u$DÝà~äÔ‹p&™.È1þ×Zó(u!)ó ­xuR‡ïb®2kTBeZiúg‡ä’æ%hÙŒ²ätg·ô¯Á?ÛöÁÖ<) ÏáßZ¬2]f8|àTû–èBq÷F3Óž•ûÁâ»¶¾ÒäŠGV]¿pŽ¿Ò¿“¯ÛÃÅ—~4jž 𲥎‘ˆ®/”+ »ë*àü¹ ûWV&­?cÍ7fGR¤Ÿ²òøÏVøâÄñÄ[£uý˜†+8p»L eŠB§hÇ ’yî+3]Õ5Miü«»9!šTPŽ›<¸X|ÄÁƒÆ22@ì+ؼEðîßáÖ°-nã7pi6»¦h##/*«¸ åIÃí¤×'¡Má»9á—Qµ]_T¹âÎÆlcgÀ €£ƒ»>õø¦Æ¥Ne¯D~ïœ:èÒ垟×ôdøGàüUñu¼!·3ˆínäÿ¹€±ß‚€à8ÎæÀ8î8¯èwá_„|;ð?áý¯€tû‹Í•à6NA=I88t¯+ý‚~Ýø+átÞ"ñu²Ûkz¸ÍSi‚=¿*‘œt#ƒõ'‹¼$Ÿ5˜ÞBÿÎ:WÉe´áVJ¬žÛv>¿:Ä:w¡Úÿ×K_·íâ…:>—áï 2úïtï½ö¢F§hgö'<×¥yGì¿ûXü@ø“â;m'Æv6wqÌÊ»­7,¡1ò–eo¦Üú^eÿ øK¨Í{oãmWtVÑÛC‚9.€ª+çïÙÃİx²×Yð¼ml¡bipr§ð0=ºWØÕȰµpÜÎ:Øøü&eVž#’úv?¥¿|<µÔyšÝ|½‚51‚2GFë^ÿðŽîÏàî©©O,3ÝZ\¦øí|ͱyã¡Î̯ ~•_ᦹªjÞ·“ÄÖþV¢ F›hù$%@G9ÁÞº]DÃ)?.ÔàûcÜʾ7‡–_Vô·GÒãëÇJ¢ÐàÿjÏø)‡ÄÿƒBÚ?†Ž}0[ æä3‘.?Õ‘p ã½?¿´§íkñ[ö«ñ?ü&¾"€Z_N‰ vÈ…b@ŒFd“»ø7´ž=ë÷ׯ^Òu›"xCà®=¿AúWæïf{{oŸiöm3…â&òØÃуcp\qÎMzXÎ)ÅI8ÔÕy )áÌ >YR,»ÿ[3ÞøÚÓ¾±ÖEÄ·—sìbˆw‹x ·˜¦L02ó‘>l`ôjzž…¬ÝÞxWX/t­KtaŽ7hÚ  °ñ¼‘Ëgvþ˜¯½Ò|]ðÓLÇH¹žÞ;´1[Fv˜ÛË"¹ åxÏNF¯1ÕujÞ%>/•^K«¥† < `ŽF¸psЊð'¦íVœíóþ¬}*Ä=iԅצ–=]> hzuÓëÔF¥hîŸñ'Ô÷ó@>K}¡A`1òm~äþ‡ÿcÿÛn‰ß->ø¡§µñ6‘ ÛÈn6ÆÓÅÔ¼( ùaCp+ùÝÑ>#Ábºu¦­yäExÒ f #£’UqÐyjHÛŽ éŸ¾-øŸáÇŠôÝcÁ‚ ϱ<–£{âù ”8ܪÊç)¸sœc÷ö+¥;qç‘Í«iÇ»”w«mâ4•¸®¡­ÕºŠåõMÆlÈß!õbµƒÐ<£ãçbðoÂ-ÄÖòE–¶r2™s·vܺA¯åÛ^yç¿ÖfžB/$†xáA‚ªý` Ìä‚2@TäŸOèöþñ6›á?:†…¨^ÆŸÛ °E90UQŒ€9à~Uü÷imâŸÇo£Û%Ä®BÁ B"¨òùGò3ÀÆ+âxÓ6*~Å>ŸúO‡™±}­´Ù…â«gÄMw«Éu%Æ«w+]\•Grp„¬¶1ïšýÿ‚p~ÈñxßÄ|gñŽ›¶Ú[ùÏ€þdÃÊO—®1Å|MßkZÖàí;É“ÄÂD–û‰hIº|ç¼`‚¿ª¿‚ 4ÿ…? ´_‡¶H¬mWÎ)Ÿžf‘¹Ï%ºgÓ·øJŸÖeì“ÓwéÑz?ÉÒY‡.—µ·½²ý_ÈîbÒ¼ØÉuå` gLbµcÐmæ@Ìcï uü?JÖ†ÊÚ #-¸F0=;TâtŒ+êuëùÿŸÂ¾’<±GçÜ÷gÅ¿¶wìómñƒá2ØÙCº[+˜î™@ù¼²ÓÕå?³/À½?à‡Ž¬M·i¨]µ˜Éu´pÇ$kæ*ƒ™Û†Æg'¨¯ÓFŸzírž1Ž žs_*þÒ .™a¡øƒOW·†Þ_&I‘ö¸ŒýÒúer@þòŠô.øoeÛ_øŸ^‡¾ª/CéoÃ–ß ü)ö<«©LÈóZÚ#ѤϹb’V4$„ x\Õs᎟âh:”^1†oB‰íãˆg9e-ÔñøzU_ê^ ×¼)añä ky i-ÍÖ#Ý2 £p8Ú2¦k´øñwáÿ‹¯õ8|?ÚÛMµ•¥TìÜÜ ã N+ø?‹8»ÄŒçÄJY6U‡öXl<¡'ïiRõmõ¼~ÊÚÇèÙKByM\To9%«¶kúùöQ%8(tÓþ ú÷ûþÍ Ö¤?hïÚ¸ÕnãØ9fBzåw6çpO5ú»1º¬™ëÔséÏùý+ŠðniáÏ YxrÒ³¥´a¸p0N{çô®ž;‚e aƒtã¶xÆ=;ñ\YÙR\ßßô_-‘ÅÄ9£Äb/´KúîuÉ 9œlHèyãóÇ›bo•pw3tçüðk<\$»T|‡¼ÿœT3Nˆªe!˜žqÏ ñÃŽ•ì×§cÀEÖ¼1ıH6wã‘ý+âÛãÏÂ]Ãñ|0Öµ˜ÿ·gž˜l"Rò-ƒm™Ùòô-Žœ+ì+û¶U €xÜwÀöõ¯å×â÷ï‰4ø¯ñBá5 Bó›1 —˜¼Òùd'îFœ¤``p+ôŽðê¶m€Æcáv¨%¤wnZ/$—Sâ8¯Œ(eøÌ. ®žÕïÚ1µíçØýŒø%ñkâ×Çm6O„Ø·‡Ã³ÀÄy%dLÇ´–@ äÚ¾žý™~xËàOˆ|U¦x–ê¹ %š† bÙ󺑞ã¿å\¯ì¯¡išnŸi¨i`¶Ñxã ²EP¤dž6ÀNµ÷×ÄOhþ&øm­i¬y—ëm„”9ýÑ*2[iŒdãߊüƒ-áÈhýiC÷‹Dÿ»Û_Ñh~å†ñž <¾Œ(VJ2Ó_'ëµü´>hÖá6ÊÒۯ̘-ôü+Ÿ·[«/rŽNsÉuǦxß¼OñËÀr[é1/ˆ,­y‚Q˜ò©ê’DFÑÀÅ|«uÿñV›t¶:„-¬®!ÌlH™ ïÊ·Ë“õ¯'ˆñÿÙøõ†¯J\­]IYÇÒ׺+yŸâeü9††&¶"3Œ¶åÕÛÍioN‡è¥ÔhÒùñç•ÀÇNƒ§òª ].{òìç=ëó£]ý¼¼kkáÛ½_Âú\O5ºcÈP‹–=pp1ׯµ}¿ðWÇºÇÆÏ¶?üQ¥ P˜˜$„ð²¿}zör̽c°õ1T÷5³i?’½ÏˆàOrüö£§„‹IwqMúF÷ü¾f3e}ß›§nâ²nt›w¸óC«üì[øŠŽ£éøtG&ßÝ2’qÈ篿lVõ±VDÈÜJàƒÓÇÝ+ǽڹúÚmlÿÐýçÕü¯ie¾Ì>ÑŽ©ÏJñ½wÂ>Õ$/}nmçòÖ/‘³øWÓVþ?žÚ8ƒmºE_õ‘|Ž3Ôߣ«ø7Å Rö4yOý³C^²¿cÍ{øºÒóÀ: ÔŸGÓ¡{‹‡“åxâŒncÇt¯å³ã'Å­_öƒøï}ñ*êà* ¿³éVrà.Õ#zåW–Ï&¿Tÿ૵>¦_\~Ê? å’è#j÷y"[pWƒómßĽ/Cþù]Nöþ(n•ŠC)pvF®L’òGr üË35?ÜSz/Ìý{ü‘Ãýª¬wÛÓúüÇâ¯<9àØm<¡iÇTÔ.•Ó"¡ù¡‰Ô€¡yöàõ¯Ýø'oÁÍSáçÃÖøƒâ |ûßB;`˜ÉnU0:ïå_Žß²ŸÃ[_Úãö—µ½Åî¥Ldši1å:¡mÌy8ð>¸ÅMV/okYYmH!]Šª®ÅàŽÇ¥~S,'5HÓ·Á¿›ÿ€~ÈÌ% -§ñíÙGË×ò=. °­ˆ$ܱŽaôÅ_Ž`PÍÈà£ß&¸˜.cݨF3€§oÔUˆ/Ü6éÊò2÷Ï^?.ÜW»;¤|¥º£Må ~7ò:ž;ñøT-zò7sŽ}GÓÚ¹S©ù¤"¹ÚÜà`öäÑ6¦þY“êXc+'-55ކÄ÷AßkHF?ö\w>•üò~Þº ö½ûbC¡xì²ß¥œl®g!y@wb¤€Þfí tùOµ~ó\jJ±˜Ï®Þ=«ù›ÿ‚ƒø¾ã@ý­üEâ]i#½²·ÓÑ|©#·ÉåÑ+ÀÊô¯Úü ÄWXÜE*2²•=WGïF×ô¹ù_ŠÔ)}RY¥xËGÛG{°Þñ‹t›¿ èz„Oolé5œq±WU%GæÎ…'®kö÷áš|eð×þÒÊ,qŸ-äÆåòÉU §ƒ_Ηì±ñ[âÏŒ¾xÆz…ÝÍÝçÛf‚ÙÎ0QICÆý¨Nc_»_²GÄßø—DIl¤“Ͼ™¢XÖ,1ýðG§½~qˆÊ£†ÍªÂw½ÿ«ì1Ò­—ÂpÚÈåügை7Ó\Þ|'Ô"…ìUÞ™$dÅ0eã*UÖDç+œÓ¾h×.´Ùîx¿B³¹ƒ%Iš8Ë>:å—æ8ïŠý^†Î_øõ£™A[–Ç<òpÎ+ò›ö˜ðcü6øŸ«éÏŽÒêOµ[`õR Ž8ã±Ç¥yÜa }Z5f•¢ìôû…‘dùnaVX\u(Í5¥ÒqŸ?‡ÿf™tÙ¡ºðU³ùüºE,ˆ#‚9=ñ]ˆ|{‰4Ý?FÒm!Ótí*/* H[åOrÄ ±ïÇWÎñêæib ^Œ0o‡¨Î¹ÅnZk?™Ÿ¶®1éùq_˜,rÕ(¤Ÿd‘õ¼=á^E•b~¹‚ÃF-k®Ç¨Å3ܰlœzƒ_å]*̪‘•+•\c±ü=+Í´«·h£mÛÎ0Hã‘ÓûbºnRáãwË~{õ±[Ókd}´©lÿÙÿá œhttp://ns.adobe.com/xap/1.0/ ÿâ XICC_PROFILE HLinomntrRGB XYZ Î 1acspMSFTIEC sRGBöÖÓ-HP cprtP3desc„lwtptðbkptrXYZgXYZ,bXYZ@dmndTpdmddĈvuedL†viewÔ$lumiømeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ óQÌXYZ XYZ o¢8õXYZ b™·…ÚXYZ $ „¶ÏdescIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view¤þ_.ÏíÌ \žXYZ L VPWçmeassig CRT curv #(-27;@EJOTY^chmrw|†‹•šŸ¤©®²·¼ÁÆËÐÕÛàåëðöû %+28>ELRY`gnu|ƒ‹’š¡©±¹ÁÉÑÙáéòú &/8AKT]gqz„Ž˜¢¬¶ÁËÕàëõ !-8COZfr~Š–¢®ºÇÓàìù -;HUcq~Œš¨¶ÄÓáðþ +:IXgw†–¦µÅÕåö'7HYj{Œ¯ÀÑãõ+=Oat†™¬¿Òåø 2FZn‚–ª¾Òçû  % : O d y ¤ º Ï å û  ' = T j ˜ ® Å Ü ó " 9 Q i € ˜ ° È á ù  * C \ u Ž § À Ù ó & @ Z t Ž © Ã Þ ø.Id›¶Òî %A^z–³Ïì &Ca~›¹×õ1OmŒªÉè&Ed„£Ãã#Ccƒ¤Åå'Ij‹­Îð4Vx›½à&Il²ÖúAe‰®Ò÷@eНÕú Ek‘·Ý*QwžÅì;cвÚ*R{£ÌõGp™Ãì@j”¾é>i”¿ê  A l ˜ Ä ð!!H!u!¡!Î!û"'"U"‚"¯"Ý# #8#f#”#Â#ð$$M$|$«$Ú% %8%h%—%Ç%÷&'&W&‡&·&è''I'z'«'Ü( (?(q(¢(Ô))8)k))Ð**5*h*›*Ï++6+i++Ñ,,9,n,¢,×- -A-v-«-á..L.‚.·.î/$/Z/‘/Ç/þ050l0¤0Û11J1‚1º1ò2*2c2›2Ô3 3F33¸3ñ4+4e4ž4Ø55M5‡5Â5ý676r6®6é7$7`7œ7×88P8Œ8È99B99¼9ù:6:t:²:ï;-;k;ª;è<' >`> >à?!?a?¢?â@#@d@¦@çA)AjA¬AîB0BrBµB÷C:C}CÀDDGDŠDÎEEUEšEÞF"FgF«FðG5G{GÀHHKH‘H×IIcI©IðJ7J}JÄK KSKšKâL*LrLºMMJM“MÜN%NnN·OOIO“OÝP'PqP»QQPQ›QæR1R|RÇSS_SªSöTBTTÛU(UuUÂVV\V©V÷WDW’WàX/X}XËYYiY¸ZZVZ¦Zõ[E[•[å\5\†\Ö]']x]É^^l^½__a_³``W`ª`üaOa¢aõbIbœbðcCc—cëd@d”dée=e’eçf=f’fèg=g“géh?h–hìiCišiñjHjŸj÷kOk§kÿlWl¯mm`m¹nnknÄooxoÑp+p†pàq:q•qðrKr¦ss]s¸ttptÌu(u…uáv>v›vøwVw³xxnxÌy*y‰yçzFz¥{{c{Â|!||á}A}¡~~b~Â#„å€G€¨ kÍ‚0‚’‚ôƒWƒº„„€„ã…G…«††r†×‡;‡ŸˆˆiˆÎ‰3‰™‰þŠdŠÊ‹0‹–‹üŒcŒÊ1˜ÿŽfŽÎ6žnÖ‘?‘¨’’z’ã“M“¶” ”Š”ô•_•É–4–Ÿ— —u—à˜L˜¸™$™™üšhšÕ›B›¯œœ‰œ÷dÒž@ž®ŸŸ‹Ÿú i Ø¡G¡¶¢&¢–££v£æ¤V¤Ç¥8¥©¦¦‹¦ý§n§à¨R¨Ä©7©©ªª««u«é¬\¬Ð­D­¸®-®¡¯¯‹°°u°ê±`±Ö²K²Â³8³®´%´œµµŠ¶¶y¶ð·h·à¸Y¸Ñ¹J¹Âº;ºµ».»§¼!¼›½½¾ ¾„¾ÿ¿z¿õÀpÀìÁgÁãÂ_ÂÛÃXÃÔÄQÄÎÅKÅÈÆFÆÃÇAÇ¿È=ȼÉ:ɹÊ8Ê·Ë6˶Ì5̵Í5͵Î6ζÏ7ϸÐ9кÑ<ѾÒ?ÒÁÓDÓÆÔIÔËÕNÕÑÖUÖØ×\×àØdØèÙlÙñÚvÚûÛ€ÜÜŠÝÝ–ÞÞ¢ß)߯à6à½áDáÌâSâÛãcãëäsäü儿 æ–çç©è2è¼éFéÐê[êåëpëûì†ííœî(î´ï@ïÌðXðåñrñÿòŒóó§ô4ôÂõPõÞömöû÷Šøø¨ù8ùÇúWúçûwüü˜ý)ýºþKþÜÿmÿÿÿí%Ë-ˆKÊê)sk$0¨·…Õåb@·Ñƒu î DˆiÁ…Tֵ喸åZ¸±o » µSŠ´N*·l*´â®ØÚ­'$®ØªÆ4ÅPÓ6@\HwÅR=Fä;à)b·÷c•K¢ŒÍ/¶F’ÿÖê`aB኶ñ«¡V¨ÅCŽM4ÇdÀ¨–i=iZ·Â:ÔÓ¶„¦Sè¶Ò§©ôä&¼¦ýPµ³žQx²OXí±­ã…^=¯ù^ó–±–Öì#V&—¨5=¨ÈH²w—´î.YäP²š¬Œ»lMŸ~Y½ I°Ô´ý:0±«ÇRñsÅk^Ø8[“Û}F_Lr‹àzP ý²k¿Ð0ÚQO®­¬QG(PŒ>=…ó~¼*:ÏÌ–—PÙ™U­¤DôÚ£’Tt|”d×(ud°Ý[LŽEjô¡ë–[_ ¾SXP´â«IÅZ' ¬®*Ù8ªÒwÉ+Dâª2¶ANýqT®òj)ßUŒjw5$•’–?;–jw9Yd:ÂØ*Wšÿ×êƒ.\0«ó}ª-ª^1HÖÒ8&ƒ½üGèÉÀ-¢ôZ;˜h¨î€ LÀ*»¿ðY:B [–߃ T«×+!y?œUT‚&î&µñ¡9YK\×Í“Ú[_ÏÌ Â±»*µÙ§†IRY£šæUvæVqÉÔV¡«ûÁO[äM-&ÃJÑõ­‹·Ö6àõ*½~ñ‘´ˆü¿ÔSOÖn-ܬp“Ñ ÓâU&ŸN3Xa¿O$[ê¶<F¿·eœò ÷§ÚdBwå}`j^^¸»¸ªG*qܲð]ÎýñZCé:íÛ"ƒnÍ&hÅ7eÞ­_zà¶@!µë›˜¸LÀ†¡.¤|*Xu5ŸÃHKmô³u(¼KÈýFUøw&›l~Œ »-Ó¯o FpÑ8P¨ÀÑOO°{à¶TÈ óÝ IŠý†ëO䄨˜'^e³¸_Þá– µ˜&‹*H) “Y œ!TÎV°Ò¸ïŠµÓ ¬sL4¨Y[@\ɱÅRJ怊äIK½ž¤œ¬•¤=¤I9ÆIÔ1ðÅ/ÿÐê£ Š®b… BÑ.­&^dƒÄMûo†&•G«]qx–í‰Y!Ž¿ ;¹ÿ²Ó$…?^öä¯e«·ÉIØdSÕÞÚ²%Ä•$n®­ø6ç †=Ž·×aápY7ŠŒ+˨¥(ܾkŠRí~[«9Ñ–/B@êeJÖ€2üÿkC'¾òÂÜiðjRÖPª$‘:)©sO JI¨i÷Vº¾—«ô–cèÊà +Ô° íö°-=ƒÉ’G-´6¢5ôä§Ø§§JßìkÆŸëd-°_2Á£ygH–4ˆ·Öæ<-SíÏ3 ð²mñåÀMsKÌ/|Õç Z´ /¹6«Æ”vÿe\ˆŸY•$_§µ/3Ü“¨³AkÒ†„È:}2i#vcåýIàžœÍ ëQ–-½h Ù ft'¿Ò¤ô.Ѧ‹ªH¤“þ·¶HuΣˆJˆÀ©©-Ö¾ø—Ùëî×dHi½‡\,YH¥V )ß œfÆPeP\,Èw®^ A é’CXÚµZmе¶P•÷ö¨)ä능W³€§|J±­Fà’wʉJI 2ÉAßà YÒÎß‚ƒ„%«·L(ÿÑꢘ¡xÅ\+ß  RÁü÷¦ i£Õ­¤XÝ !p8³ÚZü<éÿ“½”$G[‚Ú BÞdÄñ±ôÿ &¬_Éq;|'…{õéZàU¶–Z„÷ˆñHbt¡ˆJ0÷?g"–p–vz•¶Ön €.(T#¡#ö·î1µ»¥Ó¢Ò–Ú)AtJ+¯íp¸8BHaúÖ¤÷--µÔi*¡" AðôèµãѰ¦eä;µ·†(ØQñg,kE¦üOC•I°-üÃ×íí¼ù¢Û\‘õQo?&" I5¿AÊóDÊ4Éå¾hK8<Çmi`SÆî5j~%Ãü£‡F4Õ¹IâZëˆ^>´¦yˆRhh8(|¶Lâô¯/Y"½ºÊ^F¦Y*zñ_žSmÔË®¡•tæ’‰.äF»–#° i–%-µ?Yµ)r† H@GËs^}5ÛÛk,r *  ñïL$"Ùþ…$âÚ7“s¶ßf Ÿ€’Éôï1¤ ”Ý“cOl´J˜J)Éó%€ s;n{äøØp#­æõ£(4nƒ½0ÛPz× êr@¢–»móÄ!%|’¥÷2R»áTƒR¹ ;år)c—r’ÇÇ"Éu±?äU5Ž>ƒ «+ÿÒê«Ó ãß·Š»IüÙcîyo"Ô:mâÜ0÷%j ®_´·jc›÷ËEeS@¬6aÇçß+!°-.xÿIÿ¥È«¹«}ý1µ!æ8Zé–b-•¯¦ýÒßv4Œ¨ .ÿëK*Rúv•ö&­@ÞwÈ Z‘HË/0jÚAA4¯=ŸB¬jÉîJ2´Úyqz·¸…ÁFESbMÿv)”ùKÌ0ýi ¶íÌ"¤á(E€H~Õ+OÙÈÈ$!2ì®5‹Q³å-¨‹Òoæ@’ìGÙ¯!Œ©”…°Ÿ«É"Ç䑨I_’õVƒþ%L)’y@kyQîmÿzjËêVµîh+Ë#)–Q‹5ƒV±´¸.±Ésǹ_°?É ,Ðú¿Hi,qEÛ™äô?ñX«»,°‘ no0_]ó‚Ú9¦™ú,k»Ð|D·ÃþÇ&"ÆÓMËVvÀ\ÞÀ>²ß—*zôÄ¡<úõ©ŒÄ¤!Œ‚h@ûÿ ! ]¦¢·:º[[(,íÔò$Ò§®Ô`f3ò®’ež{«ÔW"@Ìa¤Ìc–8øÃÅ/}ðÚ*Õ&eBn݆Pb†–SR+°Ÿá– ÚˆBÏ'Â)ß&„¢öàÞdIH jú~Dä J[4Òû ŠSˆ!£”HQ’Bî#ÿÓê«…/¥¼PìU&óf§ †‘38%œ@w8A¤€ðñ¥‹«ÙîJ«Ä¡äsÞ§¥F@–`$óiÓjRKs am¤P ¥@Û"È¥·¶:J$m aºˆ†W²vaÕOŽHrbBÛ«¸/QA";¿zøxä¤5©‹‰¬[Ó÷qŠFGÉAÜäc,ég–$¼)q FÏ͇UZ×zäÖ,—C•#»”!-E,çù~}2¶À»?2iw7"ÎìS¹ß‚†;‚Ãn½òºH>J0hÖ6ò›¦‘Ä@Õ ïUê7¡ÉÇv$&ÖÚý•²›é”‹54†‚¯!² ïÛ#0Σô_3ŨŠIl–62ž>¨4bIØâÃ)ñ+›‘à õ/-h±ÜLQ@¸f¬R0g‹vý¤Oüœ_’–ÆÈÛxa²Ó¤ âIŽDâˆ@"´EO,º9AåÉ¢X¸FüÒ õ™îîšÅ XÐÒvÜViã–†•;Ë™‚­šP9Š _Øâ©ÿ”ÒÞÂVÕ®ï& )N.§¾+lÞÇYXàž:€µê6Ø¥e{2ѱy[`Çøb¨—šRP‚w$Óç¶4•HàнJŸëe‘ÙªaBð•SËá#í Ÿ cºÀâOE®ÿÀdJi ŸÔ‘Ûô¯o|ŠickE¯ß‚’XÅ0…_L(qÅ!ÿÔêƒ/®«±C±WŸþckA*ÛËÊI ¥7ÀYņ »q¡¬|]¹æX€<BÒ¯¨[Gµ¨å0›pSÇ®õÂR“¿—`P..ŒIýʃG¨¥Øà´ÚçÊÖ†Ü1…’BHH‚•"§¨®ä|°Ú)'›ËòÙÄZVgå]‰&„€áHGùRÏêׅ扫Ò44{œº%ÔXì²¾§r‘´´ˆÌÌh¤ šã‚r¶Ìp¤ê[¦Ó…Ó|XX)*ŒSrHܳ!ëÇ1eÍÌ$ßW×$^7±ÆÓF¨Jƒ¨]Ÿ˜'©§ücˆïMÆ@ rêÅÌáž5"²BÝ °¾#tëÙ[Î×JHs±Pô©ïÌüYºü¸+p»C¿ÓayÚäñ!b>ÚžöËíÆ(XµTŽøÂ’™aB]QR{á]“­+Q¾—ÖGW’7s&ívïNŸ³LVÑ7~hÕ"¡²O‰@Q!$áJ <Ë­4‹-ÅãÂè@e=ƒ”ß›çmMx°š6‚-ßpcsµ)SÉqI¦Mþ!±ÔmyÆJpÁÙñ§·¶]ãä#SArŸ èJ¿2CŠÍ­ï§tA”“GZ½X Nøi!”hOoxòDЈ'E¤–ò ®àü[1eI†£å±yo$QBŽ)UY­jhWK ]çN¦Lh‹FõmºV„î>ŒXS#Ñâ¸uPÌ (>˜û*ÈEJ«þÇSз¸6Ò’ˆ@ô¹×• HúEÊÅÕï¬b·‰âPÛžGvûé’E(¡ 4¡'jõùábÝ}É>8Pï£~¸«‚4²ˆÇû/–*ÿÿÖêK… ëŠ Uñ[H¢ wp…x§™límïÚI¤±ãÈò5ö¦DÛmMÒo ?Ú‡í'àÙBæŽîÎnB’OYGžiÿ…\ ©couÄsÞÜKu,d0‹í(?2KɼŠ@¦mgækVVwQØz~?Ñ© wzE„¥'†Ñ.d#˜šføV»×£bÌ+ZãÎÎnpÅ¥·S¾Ø@búÅÕ¥š„Ž>.±ŸL%·;Õ‰­2,Œ‹Ë<×·>¬¦¥‰¨]ö­2Ë ×V€ÓbˆÛ_ /:FL2Ä(­6?Ìr‰ÜŒp=Èëk† ©a€#Fä’j°$V´ûYD¹1*OæK›Xc‚ÑâÚ5%*µ<ƒqfRwcŽÑ,ÄrDÛj0ÜÜÅ$ÖsÜ\ªzf¤2ðìÎX‰Aø~ÖŽ“ßEO0M§[éÂ4«5(ÊÂKS~ù@ÞéÍË`Âgº@ ÑOÃNÿ¾gH»ëãàô¯Šìiv˜h&KÑu#ÒÞ.äW‘€ËbÕ ôM/RŠÚ—qJô 2–ä{(ˆ}Î!i1°)¨\<÷o'¬zÉDD$}‘^€ï‰,g^_Ðo‚÷RˆúÈ_pÀÎÓ©´‹^™µéV3×6jVQ³{”ŒªÄk¸¨>@8ªF58ÅŽó»"´‘¡&•$tÛùºàbÍ[P‰­MÛrôø•1š‚¬6$×å†Ô„ªÛUŠwX£f~B ðîFJ؆´ž8X¸µ=ü0Ò¢íbôâäßm÷¯ÿ×éêØXª8Ú¯ òÅTî‘5:Ója ó/3è²KpÎîW•NÕ`0H6ÃdíæÚ!—s+üMôUì¹eiM¬õ}^Ù\܄ԢÎi?Õ]¿à›áÅ-6·©]ÃYƒÚXš”õeÿ)Ÿ`‰þ_üI¥8õ9C‘iÊÒP™½î?Ù|_µé® ‹Íw‘ÒÞÂáá (>>T-ZòoøUÈ{™“͆`í}$´ÙŸ•A>ôü26S³w:õ”дMI.äŽMON™``Cϵ+¦7Ыü(¥¤wßáðé‘™g£ln–êÕB+-ŧ%™ø•fb¬›˜?ìs!s1”­´»ûøÄ“»Eò¬¤OJ€v¢ø.Rrɼc'š:ÓL³3Gom˜A¡‘êÏÌíðÓìòï‰Ë\Ù C¢a©yZ[_RxÈ„| º†$°™˜€cwš……•Ãƒ2F8¬´.i™…¸¹2Q¢•\-¥äÂJp‘þ'JmObh2èìâʤ•Éø2Ó­·Ž\ AôïÊÆ{[‹«µÌ…V8›ì #­ÞÞù*d U#âXñQJS­;óÈÈ%Zµ³:„¶ä|»Ÿl®LÁcÃOi$_‡j’¦•®A˜L Ó4ôgžK¯F( JÒ„Žµcø DX“L#ÍúëÏXìCfµäiʽɯÚï’¦°Ëyå³OVvâ;QNÀ-|NQ¹†ÊöQ­¹¬ªªñ’ÃpGÃSòl£›”6Yq,ÓL>ª¤sÎÞ=©ò91g“ òÌFîýSUˆÕÎàW¥)¶ù‹‘͉ [ÛmÝVÚ%]¸ ‚½jeñØ8R6U•Õ‹#§^§°Â7()v¥x¼L °«ØvÁ"õ-"Œ²Æz…; ô©¥Ñ”¶‰1kí8´Ló¨šEV1À¦Š H$vÉÄÈî™~Vi·’ù¦FrŽ"ï¼J± Ã2°ÊË‹œPÝîÐÛˆÑG‡|Ìp+n¾S4„‚4…V* –4T“„xæÑë¾i‰Ág†Ü­ÕM#aJ–v³¾bj2ÖÁÍÒâÛv{£À—Ke hÑ™ÂU ½•iJ.Fƒ[²–›}“+ÔÕÙ³35Ã#ÂêAéñ?¯.†A.N<ðÊ(O1^C¹èŒI ¨SÑ,jÆ•ßw"„öã—†™$ÑêMé,¡8$±*; Ç=«òþæ ÒHãd0…ÔmÓ¶L1“×§m›)ôaƒÿÒè wÛ® œûb«Õ±Ux›öA°÷8ªþ[oÐõ8UNjÈ¥MjÞÂ$ª7°$Ünƒµ2Wl­¨Ú#ñÜ Tø2‰In%µ´CÇm˜o#aïáfdÀ¼ÍæÝ9'KWjª’#²‹zŸaûGÛÛÕ/¯5»ã÷vŠˆãÜ¿­ŽBf“Ú¼ßWHÔGW* aöP„‹\Ã%Ï€Ù1‘îaY‡©êPÔšZ öÀy3tÚÞî;FQúP"ÕZ³1î+ØeU|Û¯„'>X´ºÕu(®ëõ{'–‹þÍ(±K/ü6U”Æè6ÂRá²õ•‘‘UTEÛm‰§¿^ÙxŽÎ!-É©¡gb¨,Cßbç󖚓Ÿ­H±Ö ’Ew?}2˜˜GØjºuý½ÌsV©£øw¥zår‰ €„TÚM­”a‹PGCï× ["²Ò ´K!õw‘xž†ƒqZå˜î<‘9 lSß._êwõ;³ëG%L2FZ‡Äff‡‘póba›YCéÎGÜší¶e8e wæï.[IÂKøƒBr§Î•ÀH %~byúÚâÔZibufáuWu#½>*d2Om›°âßw—Øê^¥Ôê¬dXЙ*Høìõíövñ,ÄÈv?$ñ' }y¢ ‹ûÒÆ‚:Ê •b:šŠí˜äŽM”TŒé§J’Ûþîòz 9½6û[7ù8L‡z[®è¦Ê½™º”d„d©‰èjßgì•ßìæN=Ml\\º`w ê>Ÿ(·¼·f¹ºZÃ'X‰SJ­ ÿ6ÌØLHXpg‹Õ?(¼Å…ôvâW¸‰%G£z»e±-Rz¾©¨”ŒjﲎÿNZ[ÿÓž#ƒ…Šðت¢»¹Å Ÿڃ§…qV„•;õÅU~=qUÀ÷'K5f(¯-4!AñËBCËüÉyyÊì 5j4ù{ddÄóK£NOñ,ÒK€Ißì¨ðÛ+d«§9µã« $©n*X†.Á~Öcä–îN!K¦€“!y)4Œ¤º€uÚ‡*%È«xÙ¤>¬¦cΠ’xŽÕï¾DɲN´"óU¼[Q“Óó;TžùDsæ^³¤iiºtVÑĈñŒ ¦» òPÇßÍ¢y íÉVIØ©5äA4ö˃U±Ï9êRÚè®#oÞÎÂ5ï×rkàNv2/(V„\D»ñ¹êrи™g–ý'•B’"‡*œC~2^»igtðÁêü^  ²ã¸>ã1'ˆÛ$u·4ñ§CצÝ0€À•Š“E(š2C¡ Û¯L1$D··ë7‰ë”•×ëóØ“ß}²ß±ðÃ͵ÿ/jq4Æ3G áÆ¤* HᅥT&AÝÈ"5³x/­#_®³*“Hßö?z2Á+ÛC›¥ÍÙôãvUXVZŽ]ÙäyP›Ù<«ªO%²Áz Tƒ­x–éÓöM2“ɽ’EH$í¾T…'ôÉ4 ÀïôbÉgW‰¡qÛ®(A^[£ìw¯ojÿnD³‹Ö´phT²R«-*E?”TTä}ÌÁHu‹+DÓŠ,-X$ ¨_ÝÓã;‡þjÆÛ3T—Íz¶öª·U’ÖV ­}3ø^•§Å^™|E´JT¡{z÷ÖI}cåbH ÃéO³üÙ³ ²ÚvŽhxPÇÅʲ€*´ ûqþ\VÊ:ÚaõKxª}Dj ÁYI«ƒüÔ C J*ééw²r–!`uâ•ÚƒNoÿÕ›4 F &Sðâ«„Œ†ŸÊ7>øUzÏU–J÷«ùŒUÜÈ÷ÅPº„ï´’•¨E'¦ÕúHÅÖ¯£¿Öþ³{Fiœª" 5  ár¹ø„f—sié7p`DÇ‘f<>Õ:ñÌ,†ÜüQ/šXVY­ØIt¦ŠÄ½ýúÓ+%¼ ˆÑá³rI©P¦€Jr>ÞûÔeù}äw¹•nuz\¹|[³7ßï”Ê|FƒlaÀ,ózÅ­¤P±T¦Ëð °²ÈC…¢sâVT&‚€}¿‰6 fÝ«B[j“í×Ç*Ô4H¦·d Ä¯Ä 64ÎJ2¢ÆBÞ+.q{¨7#ÄÊìÎô; æ@-4Í<Ÿå9/¯#ŽŽÖÕ#å(3ÌJšȼ¶¦íö²©M˜4ô+k¨ÞI£\¨õZ?¬ZÏø]¥75¿µŒ ì[.÷ ªv èzTå2Ù] ^¯"¬  «%A9AÏ.Ý‘°XPNý}úô9rmAÊÛr6'û2$¤4Ñ­Ó=Iîp$æ—ÊUS~[oüvß"YÆõO.Ø(‘M$<˜ ×~4m—Ìy¤Ë¥Bg•™•mä‚0¢þÎëïþËdÙ”`ýÕͽ³}\ªˆUxÇ" Ácñ5 Ü‘ðåÑ´ÊbÐók }ÁlÀºü2O²ñŒº£,¦%òU·Õ®Y SIû—Q”õ Ô ¿‡N9Œ3LâÕ gi鯼I çðÊ¥ m·ÿÖžñÛ ¸÷ïÛXÑ)­wÛQk Ugï®IW,øx›n¿~*Ç|õuoƒq͸–øvùïL+OŠUÇxÊB@¶f (•ãÌSöºœÅÉ.Ž^ ¯Ç4¨ã÷JT™X’d ½BŸÙjצbL¹ø£e^@²‰¸/ì‘Ø×mÈß+D•ü¿ É}¨ÛÙÅ'.l ¿ *hGΙ“¡KŽ'›ÜtÍ*->Ú+XQ(‡Á·Å¸ÜíýpâÆG6¬³â(ô@kÀãÓ.imPøOív­p*ñâ¥Iû4ç’)pU*¼×¯ì×¹Áe”ucJ£34êŽ û×2bx¢Õ-‘–òÚÝ}^#ð)uoˆ+-×ölÅ!˜;3k KE²y.$_RÜ›ö’ _»ˆ²XÙè‘ê‹wtÉoi Ž&f¥Iùg1ÚÝ®/†g+wGyzÍ,g«ܶrw&¹ÌÇXFXÏÍÍÍaJšŒb¦ƒz5xøÓÛ="ˆ#«¬ˆ@È굃ÛßmÎÅ2äEwa·8¦ é‘·Ò+÷â¨Yà8è@ùbÈ–÷Ëv× >ÜL­ÕX¨ðÚ¸ Y 1]cÉ·2\´iK†¾·>‹SUÆ3 ¯,`h‡N—êÿWô߬ūAÈÑh{ÿÄs d¶³¹F n£¼Xe^0‚ÉFZµ)·QSö²\Úù+¬ ø€J‘MÈ®àxŸlu]ú?ÿןS Î*Õ1VˆÅV”UBKpwUAã‘_à °ÏÌiÌšj[UÚ¬´Ú€îMr¹Êƒ›v?ÞxóÍOk`É“ 4ÏAZcohÙ黀vÛ<ó Ñψ²)a‰Aaλ Ûi;&Z À֦̃åzV”* rñÎ犘pBVI /ÄkÞ½²Ô¬Ø’GÙ ¡;°ZVŠöjÞŸ?ã$n§'Äy)©ëÓo§lü‡}÷ùSH+þZ¯jPã‚–Ð7:e´êÊéêT T{ö8¦ØŽ¯äÈÑ ¶ ? J·…7‡L˜’(?7\ôM×ì,ŠH4¨9#$ÐèÿÿОƒ…ƒx«±K±CŽ*±ŽP‘ª(Xኼ‹ÎÚ寥¬ÉcªÛ–UqFæÔ “ôýœÅÍ;s°b­ØËÙ­T‘ (¨¢’ñmöG†ù‹n`†éö›¦ÁŒ·3ÿ|‡÷lÀn)_²>þY‰9ܩΌxCZZ]kšºD±†6 h¿ ¡н:䲌`›'“Û¬-£³´ŽÚ?„F´Û|·h8yeeŒ>ËuÜT·ðËŠ¡,H'§Ý…VšÙëí-PÔhÆýp*ô'¨¥AÞ¿wC„ ­Ý…åMè?V´—ͪ‘XGw@Z: ½<¨Ã[0µ¾MGõUšNq0øTŸ…Hì>‚2© ™5ë"Y#† b•k h{ýÇ9½f’Ë`l]†Š|ÊUäínçRÖc[‰ ’Š3‚( .o»>víuX# DÅ?Ôä­ä¬(w$WÛlÉËÍ碀P¨hhOaâ2«fNԥϹwcø×øÓ¥MKüJ[Ç‹ïß¶¤jC TŽÕ¯_ž6—lÕP‘µUiSÄ0ëÙO]ð¡ qpf=@ÙAßèÀK0R}KË–×EYAI¢:Ò žø‚’ÿÑÂÁp8ªêâ«kŠ·QþÖXÅ{Çcþmó ¦‰¦Ipä ÙOÕã¨4ê}†Br Ï ‹Å¢úÍÔÄ\s;8"¤±ÞŸåf %ÙF+ŲA*Ä¡¥GÂX€jz×nÿ³”ȹPžG}4Þ…„hUUO¬Á(9·ãâsÜog¨yC@²µ¶­Ä€q*´mZøá‡â-YòW¤22IbÛøÓüüs,8K“Z/Åá^›t8J‚Ø5 ÈVƒâ8ªçe CÐ~¿–;!ªžF£ˆ¦þ< h; Ú•‚Iã_U TïÚ•ßÃc¾oÖ´ˆ¬ÚÖâíc»~-dü|‡Cć-Ž9aªY4Ä×[¾Z¥›ðˆ°äüeÈâxÇ8Óàrev•{¨iJóÜO<Îô^Z4T®ÌO|¦ZxË«‰Km‘>VòÍÞ‡­=ã¿©ŠSf®ûWõe˜Æ{ÜŒÚÈåÅÃÕ>½_NR\š’M=s—KÍÒÁ‚óRz<·¶VBBš—¥v&´¯ö`I*±V›žB¤}ÂJÚ(mÔmJअ†¥vè‡JहjPÒ¢¿xÚ†€Më³w•¡ IØñè:ïÔí†(·ÿÒšÇ*8ªF °«|·À®®*Ño¿ ¬yB#;ìŠ 3m@ç®xœu¥Õõw™Üú JÛ+ ¯íA˜Y%eÙb‡P²·¶Ž8Ì`pw¸Ô¿ÚÊIrà\ 1 ¹äUR7­Ó¾Ùm»Ê<…§Ã©Ý¤ˆ¿ š‚ÿÊ:ñÓ1òU6‰Ômëj„*- Q_a™@ JËe¨£¯*Wä+Ó$ŵãPÀ×jê+Š•BX ¥M[¿M€Û$PÐf­:­>]NA+[ââôÚŸÛLRÛØo½~Œ4„ƒÍúÄšN‡5äT‚*Š€ÍûTöæf‡OâNº8Ú¬üÛ›Çô8nµ;ùïf¬¼gÔ“SB{Ö½)›Ò—…›‰¢ì³_.éÖ“Â.}䑊º¨* õJŸ–h2 w'=šGÑäŽÖhQœ1Rü!yPÇ+„iŒ£µ;9n˜ãU’µ`Õ¡=GL¼.<¼,zM}â¾hnÒ©ËÒ¯U 6Üž™hóvÓÆq±Í¹õ+×á$†%5 øüˆÍf¯Rpãéj–’C’²Üi hï¡bIø  ñ®ù >º9<~l±Æj[-{í3Šp•Nœw뙞,\C¯€Rm[KF*ìÎÀ¨G¶#$ZeÚ‘½‚½Õ†£ iA™ DŠü‰“FBÀrpjŒú)ÌŒ ôSZuú|sq ¹ÂKU‰jÏ_‘%r¨× §ñÃHl@ÔŠô ÕÅ/ÿÓžM¤@ç”,con™6Y!½·ûKê ý¡ZK¨ÛbxŸ*§*â­òÅX?æo˜E­Šé±JIh÷DvŒìªÖl£4ú9:|vl¼ÂÞÔóå$m9§5F¢Šo¸5ÿ…ÌG<h"Ž:!B ’C± öºòÈHÛ|nªÚeÅíÇÕNÑ#‚¶éJÃqã3á „8Îïcò¯—F§¢L¼e`)Æ‚›v ¨ûò¬p$Ù\ÙØ2!ö©JÓzx·LɧÝËsM˜R‡ä:a ¸r¥A°8B’ß (ѽ0ªâECqߨ ô®D¡c,@齯Ë'=ƒ¸ àzb©›l…æ—ÂHŒˆ­ÉÑwëPsyØ“ˆGWQÚÑ< ŽAåwÒÛéÑ^Ùiëé]ÜðhØ/ÚT©ãÔÓ—lÚvŽ’ þG’T|Ù—n%mÚ)>7pÒ94þð| 3˜ÔýtîðŸK>ŠþÊÖõ$7ß—ŽbÊ¢Þ.L˜ßÅ5«Œª¨mºÚú2øH™D†/¨-¥ÃM"˜¤4f„·ZïC—Mص‰¹Žj>N¼høÙÑ#5eæv?,£HBÌ5âqO•)òÌs*m ã¨zƒñM¿³©^­[n™$?ÿÔé²ÚÍ£2Æ \ˆëTg³´œ|kCâ68Ò %Ónbø ~cù[ îõ±¶–æñ}%…KzÀ{œŒfžª]Ýkš¤×28iN~—* þÊ‚OlÁ”¬Û´†: –™n.ÙŒì"¶‚2VJuuÌYËzrã—jšíâ‘FMĨó.MnÔsïA•ùB¸’âFbåK‚"êh:“Z‹9iÉ£oX%™È¨]¶íÚƒ2@pÉ\ ]Zt-Nã%lWo@7Ú½ÿ â@[9Y-¨¦žøpqdA¸5kKI-4ëczþð‘@;Ò™(J1äߥ챌ݥ|èÜyW}ò£¾îÞ—ïE>Ûâ¡V*ni×­p€¥}J’ÀV¢Š:tÄ"ŸÿÕìÃŒ±òæöO\“lme­?v߆T¾çLš0JŽKâ1µ@1tjOlPóͯ1+IŽ”ãõn[¿*|(>ýó<º9Zxõy¬O7†Ü²‰HõXÓ‘=©·Ùß1Kšw» m¬lÜ3(¡'µHë·l«ƒ{oãÙ+Ò4•¿ÖøÚ «‘UaûU¡49,¹*,qâõ[Ýô-6=/OKUQºÈäqRÄtÃ)Ç œ³²˜†þ];žÔ¯L¸•ü¨ 6ßr{丬 E?WÝ€‚•ÁÀ^@R•¨iãŠwbÔiíáŠVšéØP|RÙe¥ Mü)^ÃSf-U©¡è@;â‹Xï¾ëì\1±—'ƒ¶­so¯]ÜHÓ4"i$%j 14øø?ÉΖqØéG;/FÐu½ôHZÞè¢ú‚ŠI,)¸_–jµ0ïsp˹7Ð Ò#Áå -ðòûQOl׈EË2,÷ËóÙQU™Yb;•é_a–À†™×–)Äm$ëøä¤ˆ­Ó­!šÑàóŽJ‚c†öbM #Pym¯fµhÓfeû lÜjTçã¢-²71¿ÙµÈ6Ù—r €Åzi\’l @&´gèûò,©WÔÛ—* uéï¸ÉêTmÖ½+…U•ÁØ‹°ÂKsbÞ'·mðRCÿÖê^¬EWÑ<}¬›ë2m ä"ð–mBäÜ_È}I9IÉ·5zŽf¿å Ã. •bŠ"#UÓÓBä7Uo·ú¹ 6€®o¡·Š+Keî™À•ÎÿèGN؈u(–At¿å¦‹uëBá¤`€hkÈÖ€Ÿcš2o>˜îô~kZ q=@ùøœ½Æ%z¢ƒcM’µ¯ÃKkÑêµ®ÇõãKn* b~ƒ¥~Œ‹oÔ]鸧^ÞØâä·@õ{áKa×fîz}8¡k3ÞŸí`*³š‚|Zâ¶¥$œCcÄöÉÀn.EóíäÕ’í£ø%õßââãS,†î‰<ü·ë°ˆÅx#úÌßgj 9…«ˆF^„Rçë3ȪV©…óLcFÝ;3,Ü“f.*3·§Ç%ˆìà fBÆO¨–eàçâQ—ˆµ í]¢¸1Ö š«vß¶ìPw ôÓÒê¨&P§Ã’ã”ê!ÕÈÓO£2ÑúT¨è7­3ÜÚ\dj©]‚š²ŸÚ68¥PµI×ÇÛ‚»ÔCPÛòØ×j}UŽxÈ+Ô×ja@TC^ÛTxõ ®VßeéØm¿Ž©ÿ×ÿÙ8BIM%ÔŒÙ²é€ ˜ìøB~ÿÛ„ÿÝ2ÿîAdobedÀÿÀÿÄ¢     u!"1A2# QBa$3Rqb‘%C¡±ð&4r ÁÑ5'áS6‚ñ’¢DTsEF7Gc(UVW²ÂÒâòdƒt“„e£³ÃÓã)8fóu*9:HIJXYZghijvwxyz…†‡ˆ‰Š”•–—˜™š¤¥¦§¨©ª´µ¶·¸¹ºÄÅÆÇÈÉÊÔÕÖרÙÚäåæçèéêôõö÷øùúm!1"AQ2aqB#‘R¡b3 ±$ÁÑCrðá‚4%’ScDñ¢²&5T6Ed' sƒ“FtÂÒâòUeuV7„…£³ÃÓãó)”¤´ÄÔäô•¥µÅÕåõ(GWf8v†–¦¶ÆÖæögw‡—§·Ç×ç÷HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ ?ºTˆ’Ò ÚÜ0Õm' ÿ°÷}G¦|SéÔ”ˆ5Ž‘À± Xi½¬/n Ü_Ä^¥Å’.¢ÜqñæßCoðöÑbzdšõ%c‹oøSé°á`maïzÏZüº“!Qé[ýò?×w_ƒ¥ h«×?”X ^ĕߞ8±÷J‘æã=gXÀç@ÛOôý€üû§Zë’DĹÒZüþØ_Û±ä…ëÝK†5^×ãúiB9ãéíÖq׺”6aÀþ—<[úéïÝ{©irý…ïcÇüWß½zõ:Ìû!EìGö€ßà·ü{Õ+Ž·^³E– YI±b«¤X¥¬ ÚbA ëXÿWú¿Õþ *‹ªúmøƒp>–à½*–¯¯^§Y‚®®Ø~ øè??ën*éóëÝd sŇæÖþ¿ÒÃÝþζ gYEšÜXþ.R8ú’£ý‡¿qÇZë(P$„6ë¯ùÿoí–b ^ë’¨`,#‹±úÿ­î¥‰ëßgRÊŽx"ü[ü¡÷t>]{ç×¢[¨° Q{Aý9öï^<:ž!Óquä_€/{ýOæâÞõæåÍ:æ ¹6úÿ‰_§ûïðß›V¦:”ˆO-ÇÖ÷ýâ÷÷ï<ôêµp:¤~môÿ_ýoÇÓߺ³5Ó®@­ý\ñúp=Ô­j›×óëÁ¿Q:Hôþxú=ø \ŠõïzõÌ¥È noøÓéÀíí‚´ðëÞ1ùúÿO÷Üý}û­øI×- ~€?7¿øk÷¿~Ò Ö¼(ó×^.y?ŸÅþ¿ï¿ÃÝÄj^ðÓ×sT…_ëo­ÿÛ{·†<ºß„wà<ü'èl÷*uí ¢£®´„%}6í¿Öÿb=ûªø•¯XÞ׸Ÿ¨úXr.8½¿uV}@Ó¬.¤éú\5Ïï—­z÷åÔKÊz¸ç€mø ňö¨GÀõãÀõÙ ¿Xþ¼ ý?÷¿{j+ǤõëŠü,nÿÄ÷v¨¦8uGElž#¦j¶’ ‚Ö°?‹‘ô uóè­õ?oHL”£[ܳpxF ÞÄ(à_úûp ô²&ÖxôƒÈ̲âä)ŸOÔý Uçn„SÇ¥]ÿл¤5ËZäjÃHÿéíßúô—©jž”Ò8á¬x6?P¸ {¸ëȯRÖ;é-fR´}Zl›ýy'ýá?Oyu›Æ·VÓN¢.ãënÛûßøzi—E)ÖKe‘nªÇô?à,þóï`‘Zu`úAHÑÈX ä)±úýÚI{×T9ÇR!Uý$cctE_§õUR×÷`„ñéÕŒ¬¢5oX ‘¥~„‡×ëîÊšHêÞzu![úò.,O7ú} ííÂzi†ƒAÔØÑ4†(.~„}x'mï}jž}INIüi?^>ŸAsïYëÇ®j`Ãê ÿcõÿP÷î½A“Ô½ Ë«‘n@×üØ{£!$Ðõà1×1ÈáþŸÒÃü8úßý÷ôÚ®œÖ©åçÖDRô[ ¸ÿ.>¾ïÖúÌj¹ÞÀùû"Þ}û¯Ô äý/Êÿ‡ÐÚÃÞ¸Ôµ×%ERœÓÇûïé϶Ìy =z•§YR/PÒþ¤ÚßOõ‡¿xDŠW¯Rf(àZßOõ¾Ÿ_Ͻ…*rzðˬ°@„úøßáý=¹Öú—ãñ^úÿÓ{ëÌ Š޹¤ ØÞÜÿ@>‚÷¿×ý·½tÑŠ?>¤ }-Ŭä[éþ>ýÕÖ4®ì-úxüþOû×û÷—[dPõ”"‚ÚßQ{Ûý~>¶÷ïϪxIÖEžmk~/oè?¯¿S¯xQž¹è×èoô゙ýN¬± 5^=v@6[¨7¿ÐÜþ,,8÷ïŸVýÓ^tê3~«…›õ‡û}û¯qq&Âãú¡µ¿Þ=î•§ZQÄúu…˜0åGõëÉÿ‘{tF§O <«Ö'Iü/Ç×éïÚË­QÓl¿E°¿<¯ôÿcþõîÔ¯Të 'ö€boÈéù·ÓóîÉÚkòëÝc”p­äÛñý/îÐtoŸ^ê)%A²‚Ÿñàrûﯵ/z|º×QnÅ’àX·spÛý½Ç†kÖë×Af[«øÕÇ«úÞÞÖ€z«p놟êEî>¿KÀþœû÷IÏÔØ f,@Ók†µÉüëõ÷¢*k׈¨é%[Tª\]I"ÈØý…æäû°6c®zFädbIUb¤¨þÐ$saÏ¿¿,ut·Ò ù× ÷%4cSX©¹úz¾—}=¨^­Ò˜ëÿÑ»Ý u6qôQõ±ä·ûoN’üº‹ôäroùäÞçñþóîý{üIU›i+poõf'ëÇŽ=¥éÑ%Fz±}ÓÒEýCëÅÁ°úŽ}ï¦Y4ƒ¦5QbO~'­¿¯¿׫Œuœ.—ú©ôÚãÇâãúÿ¼{¸Bz÷Ù×aH7Pì-Ç#ëøçÛÂ1¤p¯N‡ÓަE¹#û_í7¿ÖãÞŠç«x‚˜ãÖt@ÂÊ¿_­ÛõÏÐO~òé¢ug©1G{*¨ÒõüÿOëo÷¿{¯UêR!KXX†äýMˆµ‰ç‘ÿïÝ{©!Té°õÁ·ôúþ>ýŒã{YÕ5\^֟žŸìM‡¶™È$žTs×-x[7ô–ú­ù÷âíO—U®:Ê)¿ß[ý9ÿcõöät#=n€ç®ZÛKý>œßëqÀãÛºSËZ=LP Ž>‚Àq{ ÿu pëÞUeD7 c`¸Kþ?ûiÚ€¯^òáÖsNÄê¿ô'ülãß‘«Ö‡YcB ˜XqnmýÖçÛ½o©À}&ÀZâß›Oöÿ_~ëÞsÒIà ñõ¿Âߟ~ëÞ}sTrMíoÓkÿ±7÷îµÔ˜ã 9àêûãïÝ{ÐuBH E×ê/oÀüX{×Ù׺ôqZ÷ã“Ź° [§ûïé¾´Æ‚½eÒŸÔqþÛé~=ûª+ÔÒµëŠFçý‡øÁ÷ãÓŽÅ@§]ºz£Ç?SýI·àû²ñ2ÏQÇÏ®K ¸üÞãü?Ž}½çÕSâI(¤’GדõúŸö?_÷ßëxyôäœ:ëÆ¼_úqk°ú‡½ôʵ zÄT‚@(ÿ÷ÜŸz=]š£®óo§×ýüG½Ó¦êz2ck µ¸úßú¥Ç¿ÕƒÖ뎢–·ÑAÓýqþÃý÷ûÍü?>½ÖÏúñøúOnù·Ö&o¨·ø\pmýmk~}û¯yõ †] ?Ôþ/õ7úýoþ·½uî±—Ò··ÖߟêmÇûõ1^µ_—QÊ þyCÏé±âßÖþÖÀÕS^¶zƒ#%Ñoú¯õO§·kÖ©éÔv7·?ì4ýú~G¿yõã×o2žokxÿ¯<{PGZo‡¬ ¼ÀàX~·7æÿãù÷ªS¤þg¦Zɨ1_¯Hµíï~c¯R§¤M{ØÉ¦Ív&ÌäÿŽô燊ùôˆÉTµ˜¼¨C"›/ Üaqsý=ûνyHè3ÎVÂ×bw7#A¹Cý=Ü>:ñ޵Ï_ÿÒ¼d½‘¬ëµþ€ÿ° ?ï¿6 }zMùu%#Shì.á C~·øöêútêÄSB Çè® bÃ÷ßÕŽµá/R4€/pmô“õþŸã¡÷î›eÑ×ZMÁçê¶6ƒý7þžýÕÄC׬€I$\i'ŸÉè/îÁˆ¯Ta£êÿWú¾ÉPÄ9 Öç…&ßAù:G½u_˜êLipE´‹ÚÇêÄ{ÔxW­ƒéÖM+ O þXX‹­îñš‚:÷SaP9qùZßõ¾ŸOz,A un¥h€°±sþ'ém>õ­º¯Ë®z®T}¯úüñÏý­¼ú×ø:ÊŠH±±ú¯B/kŸñ>ü¹'§M"”ë=ˆdRŸ¥Àãëõÿý=ÛÂùõV:ˆ=g–6* ¾¿ë[éþ¿ºCO>µ× ŠäÙJÛ€ÿ·}¯½uî¤F„%‚Ø\ßúУéï_áë]HX,·°¿ô½…À#ñý=ìŸ[ÿWú¿ÕþÄ”…m"àiàSGãîÿÚ`õî³ib,M¯ùçý~?Øû² SÖǧ\€°fÔP·¹ÒÜ?>Ü={¤^;qRåw e4‘Ê´´´Ò’¼ò•¸ëâUoù Çߺ×KÔŒ† rt¨¿8ÿxõ׫CéQ®›‘ê$>–üý}ï«zc=fÒ¿‘ÏøùŸöý×€á×|kXqþñïÝRNÑZõÚ®®¿×'éþñïÝT'mq׊ØòEøÿÿ[Þߟ~ëjšI5럌_ÇçéþñïÙê캔¼cGõ½¾‡òÖ·Óßé³OY„~‘n¨ÿ_Ÿñöúž¼# ž²ü~?âŸÓÞúÜŸõS®% Î,ÇèAϽðÏL*ë=cpIçñúMŇ?áoz$us¬,ú}?ýû׿™>`u],O?_ð±þ‚ÇÞÆë`õ•ßR8ƒþØ{QÖëÖ'ŠçÓÀPoùÿqïÝ{¨rÀþ¨?¥…ÿâ€{×^ê#DnqÏÛûßÚ:ÝzóD }? ÿ±6÷£Ã­u†aa«ð—É?ïV·ôö¢‘׸ôÐ# m\A[5üE‡ûïõÕS¯pòëÁb[èHÿøÉ÷ï1ש×N ô‚ö7B?ÂþÔ/¼Ü:…VË`‹)7çú¼qïÍäô©¤•|ú‰ÿ®or~§ëî§€éÁh}:Ed*oÇѹ7`yÿ`l?Øû¸à:¸è>ÈNÊok†cêý ¦Êº¿Ä‹ïÝR«7ÛÐ5ºr‹r)YSR ”/¥l–ÒF‘èÕsôãÝI¦z{´`õÿÓ¼äT"Å•ˆý:@<\ŸöþýÕ<5ôë8æâà›r8?BG$ööŽ¯Ô¥CÍØ]”­È Uî/ô÷®½ÖÖé² lÀÛ‹ýÀû÷MH¾}s5\€,x^Ûý.}û«¯Â:”± ®’ÔHƒô#ò>ýÕLjzÍãÓnnþ—ÿˆ°úûØ©é¦]HÐ8n?Öä‘þ6âö÷®µÖO_Ôýë,Oßá{{°$TN½Ô˜+èA§Òo{ñ$[ÛÞº÷SUÀi¿[¸°ÄŬ-ÇàÜý?ÌÙ@*|úB¹Ô|ÇS„$X€,GúÄ›~mô÷®¬0xuËÇþ õ‰úsø÷¯Ë­ê㎸2•úŽÐóϽùÓ¦ä$§>²J®Ÿ¯^?â/ïÝ2¤ °ÇXô±$pH6küqoͽû§UÐ’ÏYtÿR?Ûÿ¾þžõÕè³*õµ¾¶½Ïúööàˆ^·Z㬠,?ñ¯nAN·×2úÁÿx÷¿Nš“áëò?¯øqþµ®>Ÿí½ûNªtšõ€½‚­¯cÿÏû{)@:pµGXJØ·ãëÏôþžê:l.£×·çÓÇ+ùÿaovøsNð‡¯XL-ë%€ éþ¿ÒâÞî¼bzƒ(!d7Ä{Ÿõ¾¿Ontßø:€WQ½ÈOÇûè=ìµóë#J¼} ^M¿þ½”Ç^냀R÷ä¥þ¶7÷R ¨§^ê4 +ô$Úöà\[òI÷hÁR¸ëÜ1ÓaEð~ pn>–6þž×ƒ\õìúuÂBT J9·Óñôúþ=ûÌuêÖ=AøQõâÜk=¾(@¯Zn'k¥bM˜ÁQÇÓŸÅϽÏIè+^‘ùÁSfJ R?įì:¿‰N‘*²U…ì¿M@ŽJþl©÷±ÓfàútgëÖv.«¸¿çQ ÜÚç–ü_~4êðµI>½ÍíŸh¢¨¹ñØýòF¥@¶¥¿?ñ´³Jª)Ó¥~ õÿÔ½4Pž=ãþ 9ßÓýð÷^ë2 8²«>¥±ë ·ß_ëïÝ7â/R€k½´·6±ä­‰&ÿá~ëÞ*zõ• Sè¡8o¯‹ßý~êŽÞKxk‘¤ÜZß›­ýû«ƒ¬¢×"Üu'›ÿCþ«ßºßˆH@/¨[PúŸð7XßÚ”(¦Ü‚pqÔ…¿M¯ýO׃k°öÃéÔJðê½gT*¢ÿ‚Ðð ùýaï\zÐùõÈ)$‹¯oé~/ïÝoÓ¬ö>’{þE¿Þ}û­u?O<øÿ[óþõéÖúÎ"GŸÏ [óïÝ{©#èÖܰÿcïÞ}{© I*Wõ÷º½Ö` ±Þ>§‹ýæÞý޵ÖxŘ‹õ·%¿ ƒôçߺßRíkc~G?RÞ=½œêëØë!à­íô^/íÞÖ­8u°º±LtŽß›ª—¶+³s ž¬Iˆ¡WE›%—«½>:… ÙFº©˜ž±áM‘ߺÛBîxS§à¶R,u©è”c÷Ý&ÖÈä›kãSqö>èËÏìݳ"UTŠêªiª*)é§ee ÂaéhaŠš i/!ö.Ò”“ŠW¡„V­m¨=.|mýÇ_EI¼ªñ¸¬ÚHƒin Ž,Æ.x'Xä0“>&º£üžhÉÒË"óô!\`˜‚zGw—¡8èàm—ñ y•¦R®4×ãJ°ÅeŽ6oSÂ, 0á‚=ˆÖc&z\"ÂÆ?ÅÒ¾ÿÐsý×ãúûULé?•|úòò/{ ½ùÿ[ð}û­Öž]sÐ䇸ýOÓß¿>´H=r o©æËôþªoþóïßgMH{>»×ú}noÇüEýì œôÚ¦§‡]•,xü±?ìx½»x_gWñÓ¬¢;Øð?7üÿ_öþÜ‚Y]XÐ õŽ??ï[­ï}YЍõÒ-sô°üþ> {÷ŸM3« ±È4ÆßQýH'Ÿ¡ÿ}ÿØø‡MW¨‚?íM¿r?§7·üO·ÙuR½o¨î×m,¶·ŸÅøú_Ûb1¨uï?—X•¾—ãëþ¿àß÷ßíœeÕƒåÕƒ=ciÐôúþ-ùãÝ|1^­¨Ô.=W¿ÔÿÆíþÇݾ]7ëÔK\ýÇòoþAîËÄuïN¸K¦À^ÿì?ÓŸoàž½ž£2 '›úMÍ{y l óë}FÔ¦ãò8?Cb'ño÷ßë[Jù½Ž±h"ÃXýt‚l#ý{­zõS`U´³×þ½‡ú{÷^鮪}:­Á+Á§À[{Ø‘%G^ ÒVºg´¤ÅlÖÜ7ÒßÖÖöåkJ€?Õþ¯õpoÂóé ‘‘†³qù#ðokýï¿Ø\pë^é—™B½ÞÖBoé>¥úÂÇ–÷^·á§Ÿ€ý×™,ˆÌƒé`~–ŸR’ -øöÜ Õ–5SQŽŠ>öÍ9‘ÔÍm(ÄjoXR“"HãB§äÛÙ$ÒT“Ò‘ÐW¯ÿÕ½u ô­š÷ I&ÿF–mBÿë{÷^êR¦•k1 Zàô?O ý÷úÞé)òë««þ&Þ÷Ólºxuž1qoO¤þ$ ±ú{÷ZêZ¥Èn ïůo÷³qîÁ 1׫ÖdU~º¹Õͯþ¸üû¡˯`ðêR¢ªpUM®I#ŽÛ{ß^ë2XØÜE­ôµ¸¿×Þ¿Á׺ÈÒãú\srGú׿»…'¯uÍP&¥6+Ç7Õo¥ÈúÄ{߆}G^ÉÏ\‚©$¡aõàÿ¶üpß~(E:÷¯S‘>l>„Ÿðúÿ·÷®´Öp¢ßÔÿ¾ŸÐO§¿u±C‚zÌƒÒ ·ðµÁ¿äñïÝ8±‚2zÏþÐ#Õk^Öãé§½õ¦]4c¬Èƒú Þüßêâ‡ß€­êŸ.²by¿üT¶öèZ ½Œ×‡Yô”@FŸéb.u1ôÇ7ÿyöâ¨)8ëzéðžÞ«Ïåd¶cyÐuæ¦TÊã#ŠU;Ë2²®³Qž¬™×Ó­€˜QFkýÉôØûó&ã­¾zl;|*%¸qF §Kî§ë¨±¸fc#äßîjÊ©Fk%eò™c‘SÖ²Ãñe6Ò8°ÚÕZÕK ôky*©pèµw¾Ô;;)aË]ZN:†ºJôSY㊞e§…¡ôÔ2Â…¸Dõ3õö± P I“åÑ7Ž¡ˆ£ÍñÏ}Ro}™‚ËÆMUG%¡ž7˜Ë‡ÅK$© „“Ñ=Kÿ›µÍ½¹c+øÏn¨eW/Ç£, Ÿõ/§‹‘ÇßOëb#E ¾½õßãü?Þ?Ö÷î¼Ìsž¹‡6°·ûØ[ý~ÏMx‹ü=e¸°ÔÀ.Gûî>¾ýžªÎ¬(‡¬Šà}µ¾¿à9^GÝÓåÕ1×0ÀÜ‹ñSþ7ÿaíÞ½JõÞ£k•¸ÿy?ñ>÷SÓ°¨,EqN¼­sm6¿ûk‹ÿ·÷êôûÆ„qsÿ‰±ÿ_ý~¿yt•ÓMë „2•½‰ãóõ÷ï1öõAŽ£éÐ ? ý~œr?Ûûp¸&ž]o¨¥}V¿×ëþÿ[ëîǽlXW®/þ~ ÿK}{ò]Ým…¨“‹XoÈú}G×ý‡×Þþ}kìêe}Eõ/ÇÓü/Ï·VCUz×ÙÔb£Ré&Àkƒþö8÷­:.½ò댑ý aÇâßì>¿áõ½ùÍW‡^ê,Ȩ€Eþ—þ¼žEíÏ¿@s×¼ºˆ*$ýGÓëô‹r}®üºõzÀåÁmVrÐØžÃëþÛÞúßP¥m$²‹+© ÞßKÿ¬½R¬:÷L5³(M‡Ôßú‹}Àñíà¸ëG=#ëªY¿S+ \þ«Ê¥¿ÃÝ@§ú¿Õþ¯äГ4é“© ÒúŠ­‰]*M´ÚêÖä¸ÿ®=¹NéÑàŸqäcHÝŠV%Ç:Á—µˆ½ÿ>ö@¯^$SçÑcÞ™¢Žëq“v R2’n¤Xýyþ¾ÐÏ*€kN©¤ú碓»r‚i&)rÚMÏëÒ@,Ë· d®Á‰#¥€QG_ÿÖ¾õE˜\ ¥ÿÖür=û¯u.¤Wê··ø€opy·Ð{ßI[© éCkÿ®>¾õÒ‘Àu 0µe‹Û’GЯúÞî±útÃåϧYE”-n?Þû©ãN©ƒÔ• 0#†b8ürxüÛ݃Qh:tD¾½NosaôþƒñÇø}}Óòꌺ)ÖR z~ŸB8ú¯ÓéïÀú޵å×–;qªúX“ø<ééov OÙ×¾Ïõ«ý_9  È¦Ãè°äSïa©ƒÃ¯uœ‹p,?Ø(ý÷ö]H4ëLjô뵈‹›}/õ<ŸötéÕŒSS<ÚÖúÏõÿ}ý}ëª2iÀàzåp}Øñþ6ü{÷V-8õ”8qnÞÞ=û«‚©E¯YQŠÙˆ ¸[É·"÷_Þþ]4ÍSŽO ~.?Äñ¿õ÷xÏN´=zò0ÕkAþ¿O¨ü›íÓï $÷ü=0ï ô{_içw ˆ\áñ5™áT.j'¥O5=8E!‹M*¨#ëþÛĤjÒµ(N[Ãõ¢S¶½V6ÁÁcwgce3õ ™Z܉¦|²¢jš¸«$ÍÕÉT“;µl½ZCh¾¢§Q`Di~ž-Ô¤{±ÔooàÚ-Õ›íl´ô4±Ì!†œAäûXc%!ª~©YîîÊBþBãØšÎ½]Üëv_0z)ß*0T•8³Ž’®§I—òSVI+UÖ?¥ô•ÓUJQIô¨Úü[ÚÉ¡)^4é-´Š$7ŸEÏàþéªÀeóg_Ÿ8øúªšªZŠÜqÇ̉ ’KBEd•5/3Ítk>–?Ž}¢¶gf#¶½.¼ˆIhRM<…Õþ¯ÊÝhÝ$‚IQ$BꎺÁ<ƒr±2²º©iÐBj#émcÀ7°ãý×ÞúNì¥{FzàÕk¯"çò?Þ=ûìꨡ:ȪAú[ñþ¿#ý‡¿ufH¯ŸYmfþü~MþŸì=Þ><:o¬ž¥äé·ôú_þ+Àü{w¯`õØk› [pØÜ{ð=xTdõÓ)b,G¸·ÓóþµÇ?×ߺ¾² O^Ötßý÷ûõ÷ïË­³jZ£H知î-Ïõ÷î¦Â†9ë ¤’A6·NmÇ¿‘ÓžÓXWƒÏêO×éôŸ÷ßïJ„Sª ë’jþŸ÷¾>¼ÛŸuQ¤S«¹àuWâäÜý9ü_êøû¿¯MõZŸHAcýy?×ëíH!E^ë‹ /Ö÷µ¹ò8«û¢º—ëǬRX'ÔŸø§äù·µ ˆTã=xÔVmKoö÷µÿÆßŽ}µZZ§­pë@µ­øìG×éíM|ú·Q¥kºÛ‹j¸°!ÿ‰½¹÷®k3VHGèoô6úóøçß:ñ•é-W=ɨSm|>£ú“ÇãÛæËÔt‰ÉÔº)6S@ØÜØ 0·÷çÒc]\:rÕÚRW/e&âÍê_Ç$~¯öì:Z¹QкòñE¦ÖÓ«Yã•QcÅšæä^ä{O;QpG¼ÃH¯ŸE;|æUÃã ŸR\?Wö8.‡I½ˆ?áì†I 18±ƒ¥º,{мÌò®µ/¥Œl€#iPăÉk‡`¤ý‰>ÙõéÑ×ÿ×¾e_¦›Ο 7ÿ÷“ïÝSÄ^³"àØýx·ëo{éƒëN¥ "ËaéÇ×þ+ï\zx:zu”@ þB‹›ÿ ßÝÕè¸êÚTŽgŽ-DÆãžÆ×ÿRO×Ýze—O¥…BX©7R 8#ò?WÖÿï¿®ºq]h:”ƒ‡boÂŽnß~é§`MAë–‹ê±±àÿ[óîÊ ë^_.²ª–þËrÖ‹O{©¯N,`Ž8ë2ikð@6éaý=ת2é4ë¶f_Àжãóï\:×»‹‹ÜÜÿ±ÿ_Ž=û§Q€=fÄX ù½ÚßOø{ê¯BOY–ãú±ãêÔócÇ¿*–êÈÀc¬‰k°ú_IõÖK_Wçú{ñ4ëLÉz“ÓÁ·…îßߺhc©±ú–ü}~‡õÿØ›{Ú¶“òëc×®Øéú ñÿÿcîþ"œiëÄLtV~\ÇTÏEKC’ÎU•‚ž§sÁŠ¡¬Èα$̪Wï㤊Kú|r¶®/ì»s¸1@Ä|?ñ]ì,·%éÚ:¾)Qc¾Æ—7 ÔÒRÔSÆ™ º¸Z*©êþæC ²¡™g] Þ4@µ”·'Ô]O,Ÿ= îØ,Kyõ`ÔÙòÑ´Xü}S€ Yª !d½”ÂÃw?ÒÞŃH AÚ:M$ŒÄÔW¢ò½+憶=ëåIõEI ¨—pè<„š˜8ZåT?é½­ôzIÀÆ:¢BK¯oES 5.xa· .¿K%m7ñJ52 …VE"ièjj('XãM$Zê,9>ØfDÓ£òð“>­¿TÕXÊ:ˆ¦ÓÔAð‘ú s*º&¥%@Œ5…‰°üû8·`ÑÔ‚·èVS«,zP£ Øn@:õý½Ò²€>¤ ‹‹ý8þŸÓ߇‹âë»Jƒccb¸ä ¯þóïß.­)íãŠõ•VÓsý©°þ¾îœ.“ýdãƒÅ…¿¡úqõþ¿×ÛÝx}sÝma͈ÿ¯ZÇzë}bîyþ—ÿX[éo{ÇZÎzàã€E¬?×ëÿ÷®½Ô9Pãè.ý~ŸãïÔ¯VkÖÀߟž¿§ÓÝÂPŽœ,À뎫~A_¥Çÿ·µý½¤ÓgPe²±äXßëþó{)ûz×Qø%F ,y¸þŸ’OèW‡^ë¯ÔÇÈ Í­o¯ôäž-íßiëÝtt…f¶’‚àpÃéÀ§ÝVšÔ޽ޡÊÖRM…þ–_è?Ööcä:Øê!àþ?@?Öúsþ>õÖ¸õÆCeýJâÄr@Cùÿ_ß¼úßM3L=&×úø?‹±÷ìÇ­b„ôÁ]7 ©ÓýÛþÃÝôŽ=4ZµÏI ù›‹)ú°-kɸúŸn`ñ=1Sª‡¤Zv´Àk€Oà~œÞä€? ÿŠXSË¥J«Šôî,œP«)b¬ðnIòA{¨b?§>êÄOŸW :,[Ë2ÇSº×}f1úËZþ7&àqþ>ɧœê#­5 =Ù“’£ÊFh¤]N ò¢êÞY<Œ£J3\’¦àñì¹Ê©Ô¯JPМ®Åy#PÅü…̓,eYu•±Ô¼§º‚¿Ä:½@óëÿоeHþ”$ òºnI?^ >ýÓ~ž©(º€ ܘÀb>—ú“ÉáÓý‡½ã3OÙÔ„! '‘ýnÂß’ Üßý½ªêÅzõG¯Yc¨†RÊ®ŒÊmÈPAþ…Xê¸ÿþßÞˆÏ=â(à: …‰}a¢ßàyÒ·þ—÷¡Óo’Hë» ~.?­ÇúßÓëíØéCÖÇYãp,AÓøþŸì ú{£ãÖ¾}s3‘{XŸéøãúû¡jÖ¿>¸SÖy0o¨`TØi*H ƒô7÷cŒùuê´u*:åk¥†²>ŒtÛ›}Vúÿ½Tzã§C T¡ó—¸Fçî vÍÁÉ ”;vª ›_[÷ëAU^µ­Ï<1£Mœ= ¢·ëƒ’Âê%Þæ[†u¨ôèKËÚZW<þ]ÿ‹•غݧM˜†åX§™£šŠ‚ž•ÝedŒ™|UYÚ€"yec¨é°$ûCÇ*ÈÊ´= /@ìèÔe÷±•‡Â°ÖIE&‚è#ª–%ÒŒ‹é. ý³kQâ!H¯AÝÞ a©gCä-é%ì9%Ià‘øæÚMÇôö³¢YéÀêH`Ãüükþ)ï~Q)Ï^üÜž/õü~ÇVwR03×5cÈëóo§ôþžî˜=5×!õ*¬¤ÜðA?ì?×öï^ûG]ò€— òô -Ź6üû÷^õëH mÅÿ}õ÷î½Ã®‹jú~/qþÛýü~]{òê,ÏÀQ¤ƒÅþ–±ü}y÷±Äu×`¤“o¯¿€G>߯—[ê+«… 0Q{<ý87÷°@Í:×åþ¯õ«Ò3¯Ðñ`.oþ'ü~œo¦—Ï^ë­ºÛIéýEþ„Øž?Ûû¹Ž5Zb*zãõ©.¼·¤“ůaªÄÿOm~™4=lu‰˜sÉú’šÞ/Ï>ÜXhAëÝD’̸üßëoÅ­ôúûSå×±éÔfU³-Å×›6ãø÷ª^·òê<–Òüõ¯Í¯Å¿§½všg­ƒ¦zÇè.I7?^xµ­þûòæŠç¯7Ò>ºAwýë[ú¶£ÇÞî‘é5sÒ*¾¯Ip_ò@ä‹ÿ¬½ÐzôøT4=ylŠªÊI_õ•¿ÓèG«W÷@3^­@O tîÌ׌NÉ*"Ü€FŸH%½`aÍ­÷e×é=9 tS·†X9’1:¢f20ÈfrÝM#ò~ŸOer‘ƹëb1‚:/9úÕ-¥ŸÆT°^C¼@)@¡ÞáŽ.S–Ù\ÎÔ éÔ «¸ùtˆŸ%XËw1;)$‹ ¾dþáZÚ×?_l¬ÄÏ«ˆÁ$ׯÿѾxËá-È "ÄÛ†à_ýçÝ´üÇM‡O!N§ ·éaÈçúëî¸üúe¿—R£PWY€ßNEÉXƒr}ÑU•««m™k…ê5\ 02F¨'ÚàM: ~±÷~µÑ|ì^ÔÉõ´´ôY Èk D‚–GõYi¤"ä'b·4ôàNEÙQ€'Ú ±ˆµÐV8±bµè Úß";·´r9,oYì<>B“7ŒoŒÎvm·† ž6µF/-ô²ùê≮³Ò4ˆÀYáF—-ÒÉ!UC§ùtêÚÈù%Bô¦&|Kû-P>öžEHÙü𥠍¬„4Mujãéïk2€C-Ϥ³mÌZ±œt¼ Êã2´éUŽÈQäiªðOIQб‘T‘³+›ßýo~¨¥kŽ›6ÍЩ¨ùu(6®"ü’"ÿ^F Gû{¨9¯M`uÌ‘ ØñøµÀÿb=û¯uíKnHçüGבþ°ÿz÷®½×z”kR@·êÿk–¿ø{Ÿ.½×(eV]` MÁ}Ts{ò?Ûû¸!A^õ뙚5à›@ünÞÛùÓ¯}¼:QQâŠIµi§‚ÄŸ  ‘ùüßÞ¨+_>¼à"³nÍù…Ö]/»0[w|åž‚Ù%X¦ªx(ªàm½Y¹šrºd fÿÔ.¥”.šŒt燩 ¨û:/_)šoCü~Ùƒ!ÜxÝõ¹òT mdé+–y&YRšª¾X\Er„,ìàès`7Yb(IôëH¤:êCN©Y÷–KsAA¸«2«“Îå¢Ìg÷F:Ö‘ê÷>ðÈ­V{íª^š’)1ª´Í:â8–Äè Hï¸o+ЖÂXâ@‘‘¨õoŸ ;R¯%³ñøªŠ¸èbSÜÖM*BÑÓÎrY¦¤c)]Xøuܺ,R4¡ÖB‘û$¶eØ ¢×¡,§ÄŠ:0ÕNæSymò25S<•ãdžµæZ‰>æ¶hŒ´´Ê"ð¬¤ÅP ¤wu‘Ê·©ZÇi.°E’ÀèEYséÒ-…Ê 9bµ¨ŠH¨)êÞ“MB³ÂÒH‡„¢ŽºtÕãSåžvñÈïuhÀ—ÒG#RӅ®š~}»¯¢ò‘Ž+ée¡Ç «¬š–¿'ŠÇS¬’\ôòÇF)eŠHÖ(K; sqf^ÚYK>1Õ"-eW|N7ÆD‹d⦿QÅ_‡’|¬MELøÛ¦‹’-@¦I§Uufêgsé&[z-¾°î>¦ï`\°œ tvh–EUYÜK®¢A<¥Ý‹èÆ?êc@?ÞŽx|º ¹[íéÈe¹ú³¯þIÒ‡úþõŽšÀãÖDB~®IµÈ6 Áþƒ›û°:×Ytðµùÿyླྀ©CZuêç®|xãÇû×öç^ëú¯ý ãüöíýëÒo<:޾†<‡‹Ûúÿï}z½{[ɲóÍ­þÃéϽ^½Ç¬.ÀØ[…à[ósý}ÛEë}cÍ“ƒ~GPO+ôÿ{r™ë]a”›‚ZàÞÀØúÖÁXÛß^ê!~JÜñ§‹ýyôóþÃü}Ý áž¼G]§ø5ÊòtßéÏÙÿ¼û®³^´à1ë knE¿Øõçò»Gñ¯[ôê­A¾£õ{rÓê>×õ¾±( ó¤ÿ¼Ž9½¸ü~ëß.±È¬MÃsckŽ>¼ê>Ûñ|¼3û:Ö:ƒPê——PÒ-oø1?O{TÓ?³¯tš®œ€CóoÅ¿¯7ãÚ¡Àuâ3Ò3!2 à©‘{6þŸŸ§½ztÉLŠpé—­6s _Óý¢ŠTý›óù÷ï*tâ°#Ó rå|jÊ…n¡ý@)³¹!5.»À¿º5W$õlqóè°ï]Àlö:Œ¶½ Å…õp÷ç‘f¶¯Ç×Ùl¬®ÄS=X6sÑ\ÜÙGiPY?Î=z[MÈ×$ŠÅ‚íkíõµ®[#¡ ´ÈÇO¯ t åêZiD‹’# ÃX³±#aÅ­bx½íí:é¬+Ö‰^ÏJý›wÁîà–•#s¨_…ršÝ@ý-Åïoi^Xƒ=)‹·'¯ÿÒ¾¸Ê¶«py'Ž@Õp ½¸>Ü £ðô—©jÊ?W:”XÛ‘Å¿Þmî´ò¯}KˆÈú›ƒÇôÿ~¾í£æ:÷R‘£~ ^lÅM­ù·6÷¿ú]k ä7ZÐö\îŒD¯›Q-â_DUt±4ð´r‰".¥·0#n! …‡JRšW ‹‰Û{w®ú«bK˜¢¢¯¨ÃãNJ‚Zì|4¹¼…j,³ËQV*¤×γ!#Â#ð ð=»g‡oN@ 8è_«êæÔ4¬‰ÉVFö¨Æ%ˆµGM@”ràÐW«=ê]Ó[²ö‡Zí˜ðÓcÛ!f»/]YC*PÃ%5Y§§¨ÈËÛˆi1T™UÒVæü©[Ú8%ª4ô"ŠèUÁ§Ffö®Ü«/]ECUUöUû›2“IK šùu„§«¤¼DÏ-}fNš4‘cT_.¯íûv³©\uv•düºbì,v_C–“ÇÌÅŠœ­$Ê⦦¬¬¨„˜ÅC›±q+F)]m V·þ›< ¦:è{>„ÆB´8ü4Óbá«‘Œ´Âê*¤‹ÁR‡S ­tªYbº/t \N¨5AÓJ‹ri §OØ ”{Áâì¿4ÔÙÜvߪ¸x¾ÀVea–6xg­©–&©¢Z6‚ÊžFUÔǃõOìR+85#ªÜß5ýOŸC‡Q÷Þìj*˜èóT¦zzì-F¨«i¥¥“Ã>•e„I°¹”.ƒ~³» ß©S¯t½´–!Ò‚½I¤7?¦àØ“o¥ù7·_§³J­1žŠÕ5ÔáÖUd6¯ÓŸÇâãÝ£5o„ŒuR3§¬ÇŸ¥­{›}Ö¿çÛäÐ|ºÙ¸?¨zHRoýÞ¿Ç߸äuZŠõˆE´êQ¨’O?ñ_~¯íë}G`n~–äŽÞ?¥ýî½hÐu†B¤qÁú[ú¶¿ÓÝ—ˆë`|º­?¯'ý¤’×çÛ¾}o®UN l¯Ðý? üû÷µÔ'©üxú¯üoÞú÷\C7à_?KýØöü(H©ëÞ}rb@°ly$ÿOí\Ûý÷û†Ž=%µ½óë {†RÀÜ~®ëõ¸éT†¦:ßøzŽOìä¯ÏÐ{YZuî¸Ên¡PªÍÂëo¯ÖÞýQÇ­uGP¤r,@<_“ùÿ Ûý‡»Õ ;zßL5ÓkÔØ_Òmcþßýowҧ˯t‘¯¨â÷`/~X-§ûGúûß^#!²u.Â÷ýJ.Mÿi[~ôé¢Â„žƒ\ÍqH¥.æßV7qb¸]?_§úÞü̪¤ž=6˜'íè½nüÅ’p[Ô¢òn¬ÑÛÒÁoÉ¿Óéì¶{ššyt¢€žŠ–ïÌ™‹J¬¤Œ½Ø‚.cB\öö‰¥P —çñÇ´ÓÂ\i<:Ú¾‘PsÑ>ß;HsM‹Ã2ÖEǼTQ-ª¾Þi**€Œ›ÕP jðHî핃*ztºÞåÖ”8ê«»‡gŒ~K!]M†ûºƒØìcíÌ~ö‹!"ž¶‘å­¤¯‚–i'•dj¦•²ÿ©öP,U*ߊ/mMKž‹ÎÏù­Ù¢¡¢¯Èn dUm@hªó55ô”Ù"ŠièrT´y E¤hÖÉq­]tvHÜtšYzꬮ›?Se°‰[Ø´Ùm²øúXÎc-‹¥›?ŒŠª’9–ª lRd¨jfŽpZ/_U¢sìÖ ªã="úqåÃåÑ:ù¥üÔvltöáÙý Ÿ­¨Hrø¹÷æu)«(çÇìì†V :¶/Ȩ*g­Ì×G Q¡£‹Qk¥ÇÀLcË­¤Ah õRÝmów²ú;-WžëÌÞF¾<{ӇĿjæ’›)ª¨vžYÝjfñWùu‰ˆù½ý—$²-u)§N¸NÑN”›§æßËL‚â²»sî|#qVÕnZLp…vÉV¤k ÔJ²MQG%HH©XÝ$äèÁp¹.)ÓRÅQ¨ tN»[µûSº7EføíÍË‘Üûž#ü7MO¢:z\}ŒŠ;„§‚6Ô4³zµ~=¹<å”8§TtÎd?G³áPu×pï ys»~©¶ãWmˆÞž»MRâr^jªÖyÂ7ù5ldg r /$2 Zƒ¥¶ì3‡£…7DQî‡¶º³C‰Ú;cS¼è· CO-,qSŠx«q54:«VäkåIÜÊÒ(VU>ëâÆ ‚µ>½<`i:ü=žÛ7nÑÇ69©êð)‹“gVEDR¦¶æÊ¬ÅÈ”)Ž£–yjâžcÅM2 ×+¢ÒÎÕÇÁþ¯õ«¾ˆÀPEz<ôØ­µ¹6&òÚŠ´™(ñ;hlÙ¢²âç£JÚiñyšÄv‰˜TTF¥ÔÆĈAbÖX’+!íêÑ4Š>ž¬ `àݘí¯G˜6¨Á®/p­AÆìÚ5J¶IDebÊäë µDßPiá{²)%1³H(äí= ©µñ•ÙL•M-CÅ /ŽÁÒýÊ5--N+ÙÉXÃîM,ÐÕEODd[g ú¤iʨøOJc™Ö§O@Ïove=ß©šÔÔŽÉaÛ5l-MHi#†t‡!£k%–$ Ú’¥Ó£"d–n ¤7­Ë+©uz é~AîÞ£¢Ý¹êi)rÒÿvØÐCW‘£üdñ §¦¨§S;<¾´zxAzŠÉVRÒÚ‘C€«PzÜ·Qxåè)Ð5Õ_0w„{Ðï q•ô9½¥¤¬¨¨†8¥Í<Æþ£‡U>*Ž)•—+vÔ îð‘àøÓ«ÜÉ Ê€ª+ÕÀlŸ•Y¯áØjü´°Tdóè³ÃN¾EWåX^::y’–I’$¡Ã[ú{0ƒpaM] ýÜ<2T/äVÙfdš:ÚÖ±ñþ¹WTkP4#0ONŸÏÌ`Ü_ÃN‘¸«ÀènÆî¼V w¤ÈS±©Pþ7*³G­u*4^BÂR¼ØØ{2KˆŽGD³ÛsE4éýuº­ÇûÈâ×ÿaíBé|Öƒ¤º hÜ:Æt¿ kÜ›ÚÿOÈÿñ÷ªŠÓªàW¨îlÄ_‘÷Ÿéoð>÷çÖXE‰#›}Gï?Cý}Ùxõ°kÔ&@¹· soõÈæ÷¹ö÷Ÿ^êrEˆú’ÿZßO~8ëÝa—QÒ.'ýèý>—ãÝ•CuîºTf½¾Š/öÂß÷¯u={¬ž´ ØÜXr9ú[›éõRH_.½Ô]f×ášúJŽ>¿Ú°7{_èZuïðuÂ8É þ ò¤/?›“Ç· ˆëÝc7[oOÔ¯äpø\í¿4Ó×Mõ Kj\zxçú}x>Þxõ¾é+[P¾¢Ì.O§ëÈüð7öí(1שÒC!T4ŽXM¹½Åù66ØûЧŸ[yôåjÀãƒ~MÔŽ ‡í¨Jg­éO>[“(¨“6.A@ARô«×úþ-ôöŽrW5Ç^+è®ï<ë7Cj¥írÄf¹¸Cb=–I"·–z­F E¯pd ’2±á‚…oÓb°Fm,y½Šßú{/˜Œô¡|½z 2 5]BÅŒùX,†²Ô7QpnlÃëùö–TÔ*8ô¯Ë¡;h`åŒ!“Ç© Ò7eÈ\ýtª¨ ,HÔXóǶÀà–£‚ƒèp¢£DH#Œ0VšìHgRÚË:%ÙWAV°:¿ÃôÝøÂ‚U$W…:ÿÔ¿U qqÇÓ‹aõýï¤HSúÅM¾¿¡~G½|ºØê\h õ7ÖßQô$½[Ý´·ëÝIP ¨YG/«úÜþ ýº«ÛO>½×0¡ ê`Aæÿ›X^ÄŸniTpëÕ}¯“=Á²òÔç†Ü¢¢?Æ'ÊÇ‘¬Çd)ÒtÕŠZªX*ë¡‘Ôx#gb@·³}º5Z?Z,  èŸôví°ôøÞœÚÝãQCz\žo}TK·ÕR» +Í-bÏ_¸*¤–ú5Ʋ²°!T›{6jHu íë^"©¡ÉêÁñ»§7”ÅÁU™(ê%ySÕ =))©Ð­ÇT݇ûM"qëÆE§ŽÇܘÊPòWT)»GÜUMS,r¶•TŽÕO®æÀ%ˆ¿±K ñN1Òˆ²µ:¯nÞÞ?Å(133 2ÔÓ5tR¬A™bÅP(¢¯‰efw ÝmøEwвút®:Sª=ù7=|óÓÍJs¸Ú_.B–óxò4‚:Šu©©Gœ9©’˜…Su,Õ…Ô‚ØHñùôåZùt_¶Îã]”^¦ÌŽ .Ú¦wš¨UbG‘%¬0³¥«ñ¾G3BJýÄ>HÙ´HÁ—äzÒ÷Н™s{BmÚF®xêªúë±ñXäÈÑH퉠Äçbй‘d— õ4õ´-(_$,²›Ê“nI„jZõ¾Üg= i6–OœÈb*id¬ªÚù†ŽO¶§ª~Þª–©ª­¡¬ðFñƒvO­HûJgfVVð×â4èï`;Ë­³•{S [€ÅÓÐa)Nº)£¦†¦¦¹±Ó¬Œ‹ðܺÚ#R óeÓ4À×zPV2”§—Aç|ôÕ _ÉÚh’Ô‘ÁM$p¥l¹ ¬‘ãòRË–¹~X#éïÐHÒ2¡|t”¬`ðé÷ùxwxÚ]«‚ÙUÕ•T9úÈ¢£ŠUzzØ+éâ–žžT‰S!_#N¹Ö´Ô¤ÖfUSZ‚µ QÒ˜ «ý_êòµ_=ÄÝ÷ò[½bÇfé÷ígð’ã•UF.¯#EWO d®Æš &RZ%TÍ €X1ö[áÆ„­:1Œ)^š›µþ$w-=vÓ«ªªë¼ïŽ»,2±®¥–|¢RTJÐË¢¡&ÊB!¹!K}!â±Dº˜ t™ìZIAVóêÎ> îÉ÷·InþÀÌåg|–á’:i«+digÇÉEEOYVëP‘¤tëDû'I>yd•Áõ_hÑ´d§®í,_¦ÊE»»pï.ß“nõÝM58Éi+qô´ŸÃÐQ¶šŒÅ^l,”ëáÈÓÕ'Thš)$ f>Eç,„éZÓN[¸|8§BÖ°7öR Í ƒ Ðe‰ÃTTÊRX)é%†·Gö1ÉH&©«¡“AÔQ!‰ ©a÷Xܱ½-1(ò铸é2Pâ(ç8©3[投rQÖ×âéÍ&•)$¥Ž¢¶:’ |jÞIº½D‹ $LŽ °é<ȬEè£l¤Û›‡qÖâ·¾J¦Y©è%û¼µEL4Ë]– UÔTþ_´£–ª˜ÅF’ SM !t“pǪ̂F€+Òi @¡Ûн^ØÊX0;šjšé£­§ÈA_[J²n¬z)±xß;KQ$X:Qz–»ê2 í+;Ÿ!Ӧݣƒ^†š ^þ뜆’¯’Ü8GŽSî¨ Ä3ÓT,U"°ùÖ¢–‚)ÏÕJ©cqÈ'Úf™‰¡+€±:Xytið¸VSASMYX©!’«±EHÅ%ñÔËd„ÑH.C0?N9÷A;òëf^§áéH7¢íjŠ9pù9›#û”xª#Héc‰<1yj*$ý¢ÎIÔ 1°ü{Wé8ê’ZÀV¥sþ¯õ«#.Êù'^• W-,ëtÅæÔÓ“M2ÙǤ¬V-ÀþG³(7E ESÙÆÕ¨ Ѯ۽Á¶³ÒÓÒSÎðÉ$A=H……lZ šƒ%ïÁÿaõöuÜr€*+Ñ4¶rj Œ|ºÒ²Â:È®d•T‚Ú-pt«kj”«V„S¢çR„׬Äú/ÿaÿ÷`ÃPSÇý_êÿV()Äu§ôIÿbM¿Ûñôö¤ õ¾›ä`ÚEŽyÕoÍ¿§Ð{ÙŒšc¯|©Ö#o¡,-Ï"öÿ¯ÓÚ˜ãÑZ޼qçþ¯õ«Ó¡Éý|ÚÿãýÃéíͯáÉë}uú­ê'›¯êú“ý}Õš4â¹ëÝu`Óô‚¡·7’»£‡¯uŠW$(mp¡GßR ¿Ö÷|ïuŽRT^Á^Ãëoööï¾¾ÝÒ‚¹ëÝ&+',¬§õy?M‚@æþ탯t’­šÚÉ%mú~–úŽ@üûñ={¤VR¨‡k3_ž/ýZç‘oËz¥A Ï^è/Í×”®«‚ÚI7åEÏé×Úc­NO½ÑuÞY ‹IubÀZÜ›êçëþÓNÇ=l nŠÆéËù$”+‹{nÔ~.~Ÿï~ÊÞuS‘Ó¢ƒN€ÜÅhe]FUvf™ô•X’Ö-ôü {M,ªåHÆ:q¬ê6ÝÅ}õO•€NA \Ê¡Õ4]Ú?ë{kRúç¥@Àtað¸¨à*X]PÚ( éXÕˆ} ù›ýk}mí°µcŽŸz^ÒB¢6v@S`<¡ÞÚ¬}$¯øŸ¨úwÔ£ëÞ"Ž¿ÿÕ¿p5Ê­† Xð<ñqþõîÀpé%?gRÔk a}6‘Åÿ6?O¯½Púu¾¤*0XÜ‹5ñ_¯úþÞŠŸ^ë,~ž È>•¿>¡ø¿»Tzõª’z˜’¹F(Öçýãݱðõ_}:KoÝ­ðÛõ˜ºÆ`©¥žži*•äŒRÔÅ¥Ü,`ê*à>×Y»ë ^Úõá*qòê¯p»¿­úÓwgú÷NStcI$‚ë†ÃŸTHðÉIã©ežpäwiã'Ø¢9í€ÒM;¡béú·º7ukMU4ñSM4ÿÃhqð¾íÝ• ^·Šš‚H1oýg­œ¸*T\ ûMu4b¦£OK¡dUÅz ·ec2µ^*ŸïãÜ#x08“Q’ÇP43áÜ }4”‘´l x⑉]bìe-G:—áéãÇ@¾õÜŸ0¯36*¹a”K·¿»æ(7 +Þjj© gŠÉ 0”]œi&Ķ銒íëÅ_ÈtL»'hâ3Øè埑ٵTõnÑœÃS±Ù”•ãoDç¦ZˆÇ"dhf…Ö6[-ýÒ(âeÖ¼xõEg ~ÃØù< ŽGÔòMj©öé⨖žŽI §Fi)ÄŠe‹Q 3/6öãŠiÕÁ>G¥6ÊÚXJmœýy¾ª¥«ÀI Sì¦YbŠ,unH3ÄÈÖ·íÒ!­«-DíqTÇÇ4žÓ<‘•£SÖÀbA# vöû#xⱕt«E›ÇIOvFºœÇ[?ÚGö´ðÔÆ†ÑÖSéñȾ¯Û É@.ãÑF¥K"eotv+~;ì~Öê}ÇØ[[CCØ{¸ã©)b¡§+ÏR¥FdÔÓгF†l¬Îë<ŠÊâÄÛ†ƒ¤‡C§oZ‘™ £e¦çÛ›Š·eï\/ñ^¿ Æãè2ÉòRP¶|ÖPTQÐÒ#,“ÍœšÓˆücH·ûR‘Y­­Ð‰»ºdÉ«Šç¢ÉQ·'êO—˜ qêI)²2íýãµñòM- ‰_5Êb©+j Å?!Rôfž¢PDúÁÉöò`~êÐuS¨2&l5ñ:]¥I™ÉoAŒ¨Àæ2¹í­µ÷®6º®…?…æ28(*0ÙÜ"Å$ž£\0J´{-LóuhAìªE£êòèÚÝŽŠÐõh»ã¦0]ãµ·ØÍÃES[IU66‹#UM²Òåþã7ÁWKø£ÇRæ—µåhƯظ˜²©étOFRØAñä|léÚ½‘„¤|ÆëÉd3Ù5|ˆi–9*©qÔÓTE'Ž8ᤆ™ïêm'‘¬ûO I<’9 =8ü©ùÓòéÉ!2JM0z«âïî¨èÍã˜ëí¢‡µ{§‰—ÂMGÂÒæëêà\®3?¸ë«˜Ó ûÝ%ꀂK±…áB“f)!Ž?ÕáÓ $’7a¡è:ë_Ž’3ØÊÛÙF‚²¢¦ªgž¦3j¯*¥bzµ™ã¡Ž—Z£1g’T$‚ö_=ÜE¨½:‰vqÕ‡l^ŠìÍ‹OQ¹°ù)·¼xZzƒŠÆg«U³,4Y*…–ž™F¸¬QXÜ}}°·êÏFjgQR ô-lŽèÎ⥗ ¿3U5Y(D¸šÌEl8¨t¡bYü驤¥”?‰` ‹jçÛ5$šŽ·ÌíFtiã’ƒ!H°`÷.7A2ÓÑ%5/ðˆLJV’G>ðJ,„C­ÅìÖ6÷â@=(¨Ûª: È*è2ÔÓ¤TqUÍä–:*ºŠP Ìuøšr]Xº‘M½íupëÕSPËAÐ]žîzH*Ó‚€ÔëB’Âô´æ’jUFkLfˆøé¤ro $ o+­I§—Iž5cÑŠëüòÌ­%yñQÓ¤z’X„‡ÌÒ˜Xq4…SSd[ýo{ŠîH¦\ÑzÛZÂéJwu`eÛ4‚ WPÍ;SFZ1 ²˜!%ôÅ£I<)¹·±®âç íîÜ{Œb‡£eŠÜùZt–É.1•Â/¤ÞÄ©ç’O±&ñÐ~H$Šºé¦½8ÊC#z€°_¨?‘kÚÜ{x5OMW¨ZC†¹R,ãMÏ?ëßÚÁñ׺ƫrÆßà7ä^ÿë{¹”) õïðuà °ÕÁÿaù¸ÿX{×^ÅzÆ B“qÈŸ¯?‘om82J:÷]™.AOP¢yc¡§ø{õi׺@ekYÈ$°c«‘¤\ ~¼ÿ¶÷eÇ^¯¯@ŽçÌøD ÈHRT›ƒý@úrxÿ[Û3Ðz°ZùtX·~iÇ܆u*$ÔB‚ª8` K“õ·×ÙD²ñÏNF4ž‹–á¯G2ë äŒ?5ýHú!p¬¹¿²¹F¬Ž=+Ö˜è40ÔWÔF¢­ ÖJëfç”é!ú_óíƒ* yõ¨ãbKxô6m|#D!q(]+û`jà hçYþBçÞ‘KšŽ• = t´qª\…6¶¢AyÚö6`UTlÿn÷LézžŸR°!€ ¡cP•õ 'ék‘øü=¸±¡G :5S e{H`R¿¶ÀJC‚Xí£W¦Ëg:¯­åÙ[î‚1Št˜T®`\-èé`z?e|„Ûtsbæ†,Õv>ªž£²ã«qÙ¸kR5‚¶<­²ÓÔÁ,ohÍ\PÊ€)!||Úxµ•ÑÇ­.z¬Ÿ”Ÿ{‹eRc2»§&N]½œ£ÅI»$VûïáÑNi©¡ÏB±&ªÇ² ‚µI†ª#¨0õ'»Ü¥BÐùux騊uqßv¢£-‰™Éâ ðѬ³ei„RG_\ÓÆÈõF”5$•8íQU6:M¯íp¶ªÓ(¹b¬§Ã:iÒ_qôV÷ìlž+_KK·±û« Ø2dj„SIXc§ia–¶žÆEÅS×Q—G¬¡"ÃoÈ•+Ói*Ô_›]MQ…Ú•„ÃäâÌìWËÔç²pÀÉE™£Æ¼pæ`ÇÉY*˘š,³ëHAÿ1xבÂ7Gá=<í¬z6ßwÆ7°d—š¦ÅC.JjlDtXªÉij÷3#†­–†¥žª(¤§Ëbë)Ö©b”¤«“@š¿t‚_túEGÃÑ­´qâ[|g Z>VÙ’LµnO+˜ÜrÈÒTKM(²Ó–¤¤ñÉuÒ²É­Ô ð’ÝÔ’§X.@è™2^Þß½°v/[õTðÃÛÝù»(¶>6÷pØjÉyü” Œ"¦D—&4žI¬J4Ü'K+;›ØH-ÖŸ˜ö^¯=Àµh*Íê#íþ¦ë5‘ÎbåÉí\5fêÜì¾,äjò¹ŒýÚ¥yVf¯y¦EÔ4…‰,}ÅÛO2¿0ß´o6…@¼z(»hÖ&20ÖÔ§D6“!Ö_"ñiŽÃíº\>OsUŠ*!šsC_$r¥EVFš’ ªarebŠÁ•"Âþå¨v‡± uÖ: ÈÇWmxt |ÀQm=ɼVº%§ÚQfhëtЧ§¤œ3@X°d‹‚}$‘ϵ¡ñèæÚU!jsÕõkC¸¨é31êûdÎC^”¹õíSSsÃJ¦rÍ,PãÕ@Œ"IÖ·>ÈYAséÑâeA«+ê®ìʦÂy?Œcéjaiê %ûšAøS\iöôÔºœ–¬E€¹÷QU#Èt ‘üº1=×[W²ñÿÅv8¨Ý 4¹úŠIŠ×âi’0¢Ye’ÑÒăüÊ ½`¿ŸkÐ#­ÅÒD`’TôVÑFãÁecĵ}r•Êj+æYg£©¨f¨˜Ï IöæªH½" ÌîT~›ÛÝ'ЉZf½*K•SU¥iÒ¿{lÅAІI³xŸâ‰Ef˜›Ó·™àu‚H¢ŒUÍg+9ò¹7Ò¢€jG[–áåM @k^ˆ.ðÊ˃ÍS«ÎÒÔ;·–O<R¡g”<Ô±¹xTLnÜ©bÜÜ{P¨Gpé:— YS£Ô{·!JÔµY:º ÑHiÍ.9c‚ ¤¨i HáT“N†XÑZFü·âÞÒÉ]xéLd1êÃ6F맆²†vd¡Z*e«¦ŠJM7Œ“O; 9Œ?*§õýGãڈݖRxƒÑéë-ñ>Fo ²ÁM™uki%Œ¤cÌgyXD²I½Š¨ãê=жûÂ|º _ÚÔ ×£?AYE>µc($¨#”bKOü~žÄpé`œôd1štáèKpºõÞÿŸñÿjC©až«^bs+rÅ ÷¿Óè þ¼ÿ·¿ºJAjùuãJõN¬ŒÉcm/È.ňA-϶I§Ö‰ Ç®1g[ír7HàØ±·µp2‘J窇Ri^»ÒY Ø-É<Ÿ§ÐøÕøöéuI"½_õë É¥Y¿-ý§üú¾œ>ŸŸw„u¾“µÀµïê ®·6ZÃ9öá`kž½ÒR¶ ¦±äUá¹:ÉXZ×öߘë] ²uÖQîÀ]Ézxµ‰ªçÛ¾•ë}yì¨Dp¬¼[ži²Ý<ý=íʪœç«*ê®:.û¿. `\‹ë ·ô±ÿÉ6üû(¸¸$é§cP*E£såÖW”4²;2² Ù5sãô­À<›ò9öS)v®‘Ӿˠ_'Q!•¢]$8ÐÈt‘wÔÌÈ-¨[ާ>Ø«i8îëÁ21ŽžöƧa;Üêˆ*@ ¤Â’¿[ý=£ÐuñélD($ðè|ÆcDBSMÄV¸®ñ錛ŸÂÿ­íl#OoÄP[¥=,DËvpÊ?¢ÊFŽ$A{Ùâzöµ'Ó§x¢@A †Pk‹X© ÿ 'ý‹êR€Ó5ùuÿ׿ØYA)¤‹Æ«ŽOÿ[ߺKÔ´!T}G$ÜÐý÷¼që]I SkX~[ñÅýë­õQÉ$þH* ¸ÓÏûov¶½Ô E€MÿU‹§è.?§·€ R™ë¾]säýuéýA7O ÷¿Ë¯tšÞ[_oo³—Ùû¦€d°yÊ9¨«iä•N™áÒ5%L,nŒ±ÿ ûSm)RU>#׫Nµüì>§Ü]YØTÛcgnZ]Ç·&Èf#ÛÛ–³+6;ŽÊR§®ÇõÉŒ§E%DD2þ⠞ݾVŠ$(*Äçý_êÿ2Ø .”sN—û½{ªê+±›‡vm]ã´b'¸68æÌÏPβIU+RÖÊq´Ñ«H]$u…ƒ©…Ó™ô•RhiÓÞÒ£àõè³wï|Öïé$©ÅAK‰™ÒªTŽš’:hP$ML´Õr|„%@ºNÚʃè3#g¦+BÃW¶C1Q’ÉGCñdóB0šfH(Ùc3¥© ¡!/RÁ ”&ú®à†\¼:~)¡Ôu;+‚’§)I…ÞÙ©^ŠhÚ8!–·Á$´@SIHÕX‰©j+id©˜Ó_\Œ¥½—É«T/wJ$™À§Ë£yÑ=)K²&Å-áÝ8úª÷û ÍEŠ’‹UH‰• 뎆’±©¼ïåe§´CK †ÛÆ2¬Ž˜RdÈZ®Ý…·&רٽ¶›oxìÌ+ãeÝxuYé+⧉U~þŸ)U´ó´ÅØ5(–jYú',Ån¢©F>](€”Váé˜c²µ9XèÅD±Å#HØÜêéÜË `,eÉRºÃ 2štnè@¦kÕÿ6~óþçüÝøQ%\°`ú⮫sæ1ÑÈŸÏ_›ħœ!´mö4.Š$• ½·¸Ù½Ýœñ­|7Sþ¯ÛN‹w]0-r:“òorõÖíÆÏ_Z(3[k?¥§Ìãê©]>O†VŸSO©£x+&óÇq$…ôêºÇËÕr¦SI’Aµüº _M+HŠ•ê…zgwV|kìŽßÊc1÷¯Îã·†?aTÓ"EK³Žô§Îá Í≡¦®ÙøÜ¦ºH]<ðB*I÷6Gº[\FŠ® P=ÍÕg³’M*:BVf¶eÃ` Ë›ßy¼VG%8V‰bÙØßØÆÃ;*µ+XÆTÒi¡¹á×Û3 @Ÿ.Œ- QÄt}úhuÕd¯]œÜYŒõn5 ƒˆ¥ŠHpç5[<oQ=4q¤™ xñ“y/ ³é]H@¸ ŒM:YÑ–‡«nèÚ¼–CuRãåÇã¨ñLéN´r@­äJ1ÔÍ;šSÔÈZâ!Uû+í¹MT-Òï H§G_jmÃ5V~ª‡qâñÔQÕT˧ Šg©¬© L•uÌÕRà $q€#“«R€.‹îPF*qž‰·jîû¶wLì^§?Ž“'›rÌÔ+SN‚&óÍÓȆ‚h•l¬À4ˆMøçÛ÷|<>›€ Z˜vÓ¡Oh®31K[>w-Ee#NÔÏX¨ñI …¿”Ňg³zŠÝt­Ï´O˜éC2Sz§ß—ÛcŽß5U˜é+±0–‚ôSWĈiáx¦3OBe:_Ç«H¹"À+u)Vôé —Kø^“}[¸«²Õ´øº*ÌÅ5QÇ PÆ®šìÅR@µH*ÿpE=;©Ô¨ËX +2D¡°3Ò˜äZÕOW×pæáÁÃUNô‚j@…ª2²U½\0-?ꦥš +f£%ô9‹€÷µ·‘©D=nâáBÐ6zh·Í} A¨® Jjj*z)º:˜©V_ÉL ÒòU7’S¤EK¨òÒ¾Íàý4]B„t™#I×»âèÏì/;{$ižš½f Ë$ˆìÈÅV•ª)]ÀÔPÇò-bLã¾)E-AÑEÎÜ¥ôéè`¨ï ™þÞ¦–¥V*ˆU!uˆÊ1…¥fñÊÌ çJßèmuú Edé+mE` =NÅöM6宇GYßøšjú¨o<8ºu ¨ @ÕTM#YoÍþƒú=ä.B«wt‘áPhV‡¡nA%’‰ÙmÿI1¨Y¤*Œ;Çԟǵ2€jzi­È¥:à È*e§¾¹54jùñ\z¬ŒH$‹qoj¡ž:T0¯Mv¥piÔ¹%X‚Ǭ’u;…PÂíôfþ—·º3±p|ºf”§¯LÕÒøÆ’Þ©4•ºéAdbHÿþ¿·ÖR]Aë]%+¤aèô²} pÎÿшàK{[ÖúHWÌTH­(ÿõÂHëïÞ}{ ã+Sê± ¶,Ä«~~9öðp<ú×@ŽèËIœMhÜuå \0à ÉÀç››~¥÷7*1ZŸ…IÕAÑ_ݹ‚ÎcIN€ì_Æ oÉP­ê$3}oôÿ_ÙK¸'Uz¿9êÑ!°e:ÛDkúH_Ô@ÔÚ#Ž~§Û:Àóé_ú¿Õþ¯öØê9r×ea5¤€àzƒ~¦#èyú{KâÇR5ç­éoN‡¼b†1bA£ßô«8½ÔŸ§ô¿¶†XÓ­Që@¸èA§‚ÅH] `N”ÔÓg³0_Є¨þŸí‡µAIk§Ôˆ®L¥Y>ª”’Ú›•äjSn¾¢×½·¥½:÷R²” Í®BÓ`ìX/Óû.mÏ×ýozúzçÅëÝÿпؔ0#û_R.-{¼p}ÛI" c¤zÔyõ1j]"Æ×7¹·6ÿoíH…J׫bŸ.³"ë7€úsŸçñs«Û M:ðH?²xQÇêµ­ovJµHÇ^ë›,H·êƒÍ¿{pñ$pëu#t.þºµýžE…øú‡¿u£Ö6ŽWæ9J·¿é*mkƒ`H??ô BHã×½z¥oæ ×±ûcª{«mÖœ\uÛ£ƒì¬u1¨Ž<Þ;7§b¤Z¨!ËÁ!H¤"ÔÃAvµ€ríd’ÍfÔtƒ“ÒëxÐÐÕE÷îçÍ.⪓ ©¬Çá'¬_º¨Úéñ¥HÑ dÙh2§œ…i%+‘céG²‡šŒ´jŠti†m {kÐ_Úu¼@—$)©)á“EdÕµ"JJ‰äDf)<¬Ê€Äê²z5) XÞÔÝ7u fœ:º[…Žª†±³õ³ÐÐËŠÚ»‹uÍJb6Š*<Èe"‰B©˜‹5œðGµ…ÔƒCÒ@šXc£Ë¶6[2’šãšš˜Ç[]ü^]Ѱ2$AÖONøY7nB:_%ÚÈô*ÙIQpÜJL.­ fI)J¡=’?Ì’—ko Í¥Ò{VЦ¹"Xê2ubª<ÕO k49‹¥¾_$zÙ‹¼…Uu½‰Ò´ f¹½*¸DµU®ÿ«Ë¢QYó ænq§4[Ê,“}УÄb`‹Å¶¡‚•gcjEµþ¥à}³õ«©ºe’¥xЭ֟Ì+µ¶þ Ú”Ðä13NÈ7 °¥IýتéÓ3[Y-þ }}¿À¨¡ézi§êšF;¶¨wöÚÆÕã+¡© J¼uZU#½ÝÔÚ€Á+ ­\†­j,?¢‰å%FsNµ0‹OcUº7¶f ½–&Æýü{&ÀÔ$”ñ¤²&:¡c¼TÉ “µFI—T’‚Î\¨B>¾Ñ«Tž˜·s³$xèÃô&äƒxu†)„Î=æªß‡Ý[~«0²æÐP`éij7#Äõu;~jšÊY|€ªRºËê!ÔÓ¨Œt©¯×Th!ÏW=ч;[´6íF*¶®ƒtm”ÈŠøò´RM‘I„Ðä)éYã–l•#øj¹'•ѧfD î1Hdñc£»GŽRž)¢ÿ±òùõGÿÍ+S½~AÐfóØO¹lÿVå°Ô˜‡¬ ÇÕVn·ž«¬–¢‚¾A-/ñX%È%A¦eY%…µ ÈÏj¿R†+…áÒÆÕŒ‚özõ]{#x÷ÞäÇ&.çÆcR*FS%FŠ2(V¢’s;"@áÕQÕôp+skfó;¬b‡Ï¢¯ EKGR:LnÝXÚÈq;ñYmÎ^†ÐÛ1Í”¦ƒ ÒI§ûÕ‘‚!÷K—YÇSGy #žÚ‚Õb•¤WǧVv!–‡Ë¥×^õ.Cxäêg©ÌQÐes²ÑÏ ~hÔQšêjÙÚ›ËKNôñÂË4iã†@ˆ‘‘¤ÛƒI78!I¥?ótä¢$`à׫™é^¹‹díù«6v"]û˜Ç}¾&£5„‡OD¦*?-xdej6‘Õ@‘ 3ÈU}†®·fb+AÑõ²('ÃÈèêõW^a¾õ·rG£­™ÞUÁ5{³â)_Õ/íÁjyjb¿îH(UZáŠØZ=4Ê5~æéSUGCãÞ›;cCQ‡ƒ)ACG[TÇ5$Eétv–yúÚ¤ Žf  éŸkdŒtšæ=qê¦+Ñ)ì®öØŠŠ¬Œ_¶¦¥º!Ê,Øê8êŒ#ü±Ë«ƒe+pN¡íDrIpÞ‚M+ÑG‹Gð× s5ß9qÈ¾ÙÆn,ô•Ôs´ô,^*¢:µªKM‘Ï5.*Ha0x—Ã#±P¼ _Ú¨íä Þ˜üºÚÌifê¿r½k¿;Ç|TLÛ‹X¢–i¨¼­_&2)êâO¿®†©é0ó •Â*ýá'AÒ }¯XÔ-ÏZÕ’¬qNÏM|[Æl÷\¬™O¹Q"<µPÑE@c¨3Vë–Œ“S/š(CÔ<±8_-­@$î%y”áÑÍÍ‘à¼Ô"‹3]U=0«£ |®j¢¦x ¬š8?ƒ°–§4n¢¬tËø¤Q}6ö`Š¡E8ôŸê%Y dªôvWqý­8Ín\N ˆœÍ,¹Ùé0õÔ) 0A‰z¥¯†’—íý h…žÁä2TP㥱ްLJž£u–êÂI$”´šyð߯J¯7½å®¨†hæ†*\ŽiñÔÙ0XÑ‹ –b/í;[“ÂLô¡Y¤mzkÒû«±™^èÜ› Q¸—Yæ›pÕäR?¹‚¶š¦² ¤)Œ Ž†¶Ú§¦§›ö e×f–Ö CUNËq¥H0ÿƒ«_ëž®ƒi ,%D‰aÇGW’¨‰Ï¢š!Q4Ï;Êþib¨à Òuÿ¬H1‰‚:–§E$R ”£tb<°ÑÉ$k%Ë#´BfÈõqª)Ò¬ò0¿×ý}¨7ƒCšôŸÁY*iÔH)Zž(…<Êg“Îõ’ĜŠºuº€¶2]ä_¥­îér>.î™k` tÓ%ULó¬’HÎÂ5¯Ô«H‡@FõÛú€öijí*’Fz(¹TP¤Ýmz/Œù›„E ‹†ÒäqÂÆÉê?j—P‘5‹ÔJ:aý!ä%œÃ$¯aÀ–8À:xÎ6ödéáüú@d*5JÊ¥ˆåÃØÙ€7¿6çÞü³ÖÍ: syÕåÒà ¨aË%!”+p¤JE¿àßà}´ÒÆ„jn¬#w¡Qˢݺó MB¡:T?n;Ž4°¸õ,¼}}”ܺ»&½,YCjíÃ_#¼¬$PÜë&À­õëCÉ7±6×Ú7u BsÓdÑÉè(¯˜ÕNðF|œ*)PÞ­L „€ú®âÖöƵõéýjM+ž„]§„A4©©´© KGyž, ·øð}¢ÐKO>”‚( qÐÑŽ¦h¢Yu.Q˜“!~=-ú\/«‚EãÙ‚¢¹ÏT2ÇSÝþ”Òƒ~,©¡,ìl¬4’­hþ¼·öð pé¿ðtäÊ »jÙUcK”] ‰¹ü€?>í׺æˆÚã ê}*¤Ü£¬ ’uÈäXïßêÿWú¿Ø÷§_ÿÑ¿ØB“¡‹]¹ºÚ÷[Xþ­íõø:+=L X¡<-ïø½¹ã€Eÿ߯íRAÃ¥ šS©›Pz'OÓúÇ»þ]\Ö[ÝžI6 ‚>–æÖúØû×ZùuÈ•-ý¯­¹ç‹·¿¿½×Ž«[è>ŸOÇõÿmïZ×ׯc®*MÉ!øl§úB>ü" ã¯ygªïùý„\§]W½JTÔÒý‹ÅWI¢_ßS4FôõÞ—¥ª£©§Ic‘]l êü«e;]ų¯p©åNIÒ:j9oZ±vgcæ·~i)7±7-%L´õ9,cãàÄnXƒÉ ùŠXÆfFUW$…é©mJ¿hªê:0ŠéMX|=4c6Ïñ\ÿÀ®U¨ÄEš C%w)¤¨ÉBoS:Exâ&WmAE‚sDÁ=F#tätëÖ?$1Ý3I“¦Êij«Ÿî0b`È#’žyRx2Ô¢ª9•Z9 \;€º.ÚNž%–XIÒØCŠôŸíÿŸ3n™—ÄíHˆg‚J–y'*|þ $H×ê5ÎÅ]ø½É-’f%´/ö:n±ÅCÔt]¾=ì2{ksvFN«]¸ês-¥§®ie™éüÕWV$iOS irºÉhÜ9ô 7°wy’h¤P PqÒèní$B·Sì?æé‡tîNÆÚ[öŽgV@äñÕO_K‘©Ããèj©``2&ÏÔc©ÒŽúlÿpŽ ŸO³m¶ÒG†¤TÓåÃöô]5Ä~-#þν.ª6$]»|t¬(«1ðTÀ$’¥M]?“KÓÊUåÓus{ð Ù†ñ£ÑAéö,ñ–^4è$êþÉÎtžø‡SYWQ´¦¯ðÔP–bÔ€xd‹ÌÖ²Ék/’äX<'Š6ÕÄtÄWÞ ™0OV ¶óTû¯+Ù•ñÔ} ¥¡‘©©ãþ#UQ‹Cï4‘SÅp ,‡^²ÍÂJM8t~“Àц:JásÕ»o.µªÁ•ÈVç¯êž¢žš–¶¢®šŽŠªL _âwVX¥™ÄsïMM-ÒWD‘µi¡òê׺g²7ÌÛ0Tm]ÛŽ&§ZàÓ­L4WR%mK¼!‚\„‘ã§ÉQ ±ht‰ g%¼¥sÒ˜ÖDÒ@íüºwùO±zã½ð›z›{䨰}ƒ˜þ¼6.Vž¨¤ªû¼{Òd’ªl|ÔóÏ›—$ñ¬’Ä©O3«®‘h(Ì2Ê\ÐêÊÒ¬Ú –:ZÊU§€¯ËbòQ±Ž¢¡žóä(œGQO†9òDê²úlO ¸P¦«R:,h•X“Ò»©:O»2ñSA*Æ2 R`ÌVO)U]ê>Z’¢ž–²½£ 3Š¢"ž¥ƒâñ³ >žáYh2Â7e Ç£ñ·º+´é±‘aòϼ%­©£ŽJ´ÇU5Q¦§œå*~ç-‘'"$3㨑“IY¬”ÞI"ìð~ÎŒ¢µV*îèÕÍýýÙ±‹Ûãh—1[;G>:²²Tý¡‰ÎŠ’ˆé]ˆj¿©Hñ•öJ!ŽGc¯õ=(ÉÑ„vð=7Vuï}ÅŠþúÄÛ‚Š’j·ª\›EÑ@ñ2ÆÔ³åc†’®h€dZЏau¹¹6tY°p5ùšž] ­Ez%½£Ü» gemâ7×açTSÐL6ìñmm°­¨Hjëò«nQKäýúzw­¥tÿ5-ÏK8-Ä`;Ôþæè¢çržš?—@÷úFß›·pɵ¶Lxý½ULé«hízŠÍÏH)ªa&Ü´Õ›—|VRÔÓV; HÉe² ý>Ì![d“ôÛ¾ž‡‡ìéŒ\ê’=?˜?àé{ŽøÝ߬rsv>ë­ÄbkòتœfåÈÕÕä«¢_¾J¸i¶Õ5FKwa*¦†²=0Ôâ©•Õ­ Ÿkh~/.™f&¯/³£ãÕ]!Ö=;·¢ÀáaÈ䦂T†¯ 鋯ÐË•‚b´TÕÙZ©· ’XæÌm.ÜU][á=oÄJè?Ÿú¿ÕûwmávJÑPç76Ó£§”³K·öÂe7Mj:¬”´òäcÎ&?çSÌ|ÔµJÑ:k`Ê€:YQ¼@iެÊÌ0:·»')[¶ó5Õô–§mÕÃC%>C-&STRRŠš|N8$8úé+¦ö7ÿaïZ××­ŸY‘¸úôH'ýbE¯îÀ‚*8uEˆyý$~x&ß_÷¿{ëÝs½Ém@ÚÅ…¬Gôµ‡>õÖ‹¨$W®Aµq¨Ÿé¯úäX_l²µM[¨§Ë®ÕP >¦° >¤8[×öâŠ-êÁY¸ª£ù‚wÖ‡ Ƀ/¨dƒ'IV1¥Aƒ"C$Yxª?Pðé 7ö¤\<1éL6Á©¬yõ¯]vרÝÝ™M¶°•4Tq%l­*Ô¡¯–¢+R=$Õmਫ©ÄÆÒÂ_IÒ ŒM,óxŒ)ÙÒÑhqÀu½³CE±6óâ°™|œQÅRøŠiÜ„ò´XèW+=*¹O;¸a¢HÚ6ReKdìÕóÏFb(Ú!Y)ûz/Ø®’Ãa6ô{‹wWÇ\Õòµ=Cb š´bäâxÊCY>"hj)ЧIƒ$mê–Á—NŸ>šÛO¿‘ÿ7K ž×é궸]çAM‹®Èc |ïÛ”ØêŠ:ê ƒIŒ}9ÍQä"UþÒ³!V6œ(³xÑ›Y =&»HG@VϪn›š\ ¹,^då25_Àw$Ôõ´¡)Z82â7RVO B¢ÞÅŠ¸ T7–Iq#éŠT0ÏN»ÿ&í¦¥—(‚¶ ejZºUŠ9(£iž8g‘R?±¨‰È`Ó&«ê<”éq-¢i»ü<±“Ç©óîJ]µ…¢£­’ÉXqØüF,¾R¾¬ÓÁã…" =Auú…ÖE¿P’¸°k›‘p­F­iѬB/N¬ÿ«åÑ3ì:lÔ™Šƒ¸i†1¤†jˆ(ï •¨Ó™†¬DÚ)æUãI$¨úûBBªN@é-åbòèÃôGcd›oeðu“ÖÓ&R®LÔJÁIÇÓ$EªÒ’ÞJ6€€ÅÀŽ"·bªo9éøUôd*è±’ŠÁ «$TsÓÌc|”O PŒ²2¬S¢zµ¬wf1¬M(Qvü”—d¨í8èÎÞ"ÝÔèåì­ÕAQ¶qÓë‚I(ê1ð¤q4FVðSºÆøÖ¥y qÓÂäl `¡J5ÃóÝ´$Æm_Ë£µÓŒÄ1Ð…³¾SõÌÛ¦›lnŒž"·1Òã±™|ŒtÎÔF'¢Šš·()^DX¢z}2ªÔIJ¤€Uâ)$°™GÔäŽ=3÷mmÝxùé?æéÇ¿úsge÷mgM¹<=ù qÕ©öO½™ë‹ÇOC$O š‘4¤¾EWma¿sSö÷²+”1ã«Ê#˜kN¨{­zÏcTÐÕd³õ9ªZS&ª ®à¤o+:Ëá\Ž_5TFj§–ѼLϬT¾Ÿg‘4d…Ïú¿Õþ¬–ŠÜs^„½±Ùsî Ù.;KcBËö¹?âõÁ(œR$sÊ•T²4”3G‡q®VE'Sr}•ÞA0 ν£ìÿ?Kl®ÛÆu+Ÿ.—{»çç\õ•v¯zújMñ¸èª(qÙjú™¥j4ÉWΆq•džš®ž™šæGR6I>Ã7“ÚRKqú6Í/U¥¹:kÃýC¡Ã¥»\ü€ɼðU;ZŽƒ7\¦¦®…¶Üéäj$£jŠHôc¨«¦A# T–\Ü[ÛvÜÉ9`·® *›¥×ÆLCSzS ÿ´>?týùÍ¿bìŠlÖ2§(ZÃe§lt¡ †HòÙ: ¿YCK]¦EÒT¨‰¢:˜VŸ]s7…:(aõÿcIâäë{Ûw•±%xtAÒ»æLæZƒfv~gôý4Ò×Ð`º£gPmºŒŽ5b1Õ@¹Tg§ï A3Ô“U&¸ËY°íüÙe]VâÆÎ °Í)ù‰ogwyÙŒÔU•Qͯzù ÆÎ§/‘”dT%&G+SY3ËYPñÚÖ!,–-ã<ìR4žÞ‚¶òDêí(¡¯ú¸t–ØUyÅ6_yn%WÌTÔš|;½ ÔÕ0Ï÷9’8¢§_+¼‹Ë$n_`_Фc¦$”¢žÞ™;o´ê¨¡› óýèž¡†"¹&ÉJgZjU§U ¤û¢­,Ÿªú-sgxueEaSïuvÂÈöT}{Xß¹M5=Zmì”õ¸Œ•>áj¬¶w/µâdñ¼-O M5‘ý½]+Å4δqåÕK8áÕÉõNåÛÛ+¨p;õ ¦6‹ Ž«Aß3®B MJV-qˆ¥]m<ˆò•+r.c'ÅéÄ«ý_êôÒÉZ4þ©íš&Ìÿ ®œÑh¼_`åéÅ;cÝæ’ž Y´ØùØ$Н! ub-a¡ 8¯JWÂ8\ަ®b3.àÈgsL´™l”†*O+Óšzjy’VÓ³y']AšÒ+Owüº°I…z]Óøè1‘A ÕKicVC…‘•C©Z}O¥#{‡#ëkƒ¾l° äôã]_YŽ¨ŠŠ”Õæ«r‚š™ôàÑåRЙP„§o¡ÕCN «…rJ¥Ô:µ@e$ã¤æ9ò9/dc¦¤žò¡ÉãÅ×B­Ñýƒ:°Ðå–'c"¨`  ³È:óÕ'*áˆ8é]„ÄËç¨i$ž´A=Lª!Xâx™Úo³¢ÒYQQН$¼Ú˜ßÒ=‰íN€u`ŽƒPÕ°3Ö*¦¨vYkâðM .#ôˆâûV– oú•`¤]El,ìËøö¬Üñîé‰É¦žmÅV)zɵMSŠ|u+±Ðrj¹ôò·$ LúG"þØ3±$zôð„㷢ݻ2ÉRDS$j¯__<Ž‹Åû1G4¦ÑZG¨±°-äàBžOçÕ•0¨ÇEßräêfóG’(*UÚZéôR¥e8,WÂ&xc†‘Çù¸Á%€ÔÄž1e¡é@šÐhh'¨–)%h £%k)›Óä.ˆ’%R×±b …϶º´b:’OCÖÁ4 IO B´nÀ³ñ» tê`o`¤lhoN•—ŒSУOŒHôÆcf˜›ý²‚VL²H©¥Eù°S`~¾ý2³:d²ŒùÒ–ÝYM'#Bz¢Wuj,xvP, ·ú_ð´:àW­kZñë0ýFI R].ÌXYË].Å@ëô&ß®¼DÔ½Ý3ÖF\± vU ¥@Óë`.¬WÆi-p­ôäû¿Ë¯g¬y$\© †È©€Uuu*B/a~}×Zúôú¤c¯ÿÓ¿¨ª3͇×òãñþõííkëÒcŽ=IïÃp§M¾·ý7$‡¶5/¯ZêJ™8¬Êí:mjÕ1×½z’>¿‹ZÿOÏ¥®â}ØM+ž½ç×ꈸkŽ,Æì9ú×ÞÎ1çÓ%X±4Ç\¬SŸ¡¿'ú/þÃߺp|=0î,²bp¹¹T¿Ž–YXz*¨$jy-«ê$Øz?>(‹´­|ú×—äíL;Ë-–Í*Ôy`•ê-;R³‘K:J:Y’js"žcI›Q7+{òÈ(iãUPO’µ¥ÁmÍÏ¿÷;#KSŽjl\™*yé`’$§H*dª©¨o¶ŽJºkyžžM!M”’èY”ƒSÕ«‚kÑfÚ=emwMuuDðdqx—Ⱥb³53RÂÔ4ñ!’jZ“ƒ«ž—Md¾EŠ¢8£óÂN­LhfÈêþ#è p§Oý­‰Û¸‰ª6ôI<4‹_‰€UèÈ××=zEWUeNÜ™±"´’FŠd+.… QË.Â0&£¦âpY :!;×­rT³åj^y÷ BÕ1Ðã žº¿,®«56J)èñRŠjˆ£²Ó ŒÓÜÅ®n¤+ ô¸˜¥^ÓSÒp|kÈÅ‹ÇîºJ­¹AˆÍ$1dpâ­ë«a– )—©$ÕSj3™ iN™P³G Ó~Ô¶ qNK+`te:SzíjÜÈÄÃ.B–žªäÁV&^,jO58–ýÍî„#c8Œ‹€ÂÁC&4”Öƒ¯(*(xôÅ·7þäÛ™é(1³p™h¥‰jòÔ˜šú‰Æ}R<õ9úúzp¹¡Š›±_u6ÑŒùŽ”ÆÀ+× ùŠÍnz¼ž_,iž®a’¯ìéd8ß%Hw+M­$(V¬H X^Ú*PÑ…:y­«SâV¿Ë£Cð·“£‹7†¥ÂRgðÛ–Šv¨œ¾:ªž†®¾Ñ¦jƒO-m9*IHÑ9Q!Š¡öÜ…cî|µàˆ¨æ¸ëË «”êMáµ1”ô5²Vfñ¿Æ(qÔ‘É=MD”5Ìd’Y UÆ +ËS”«ÅÕ³TÓSÖU(¥Ju£zçTI]jª%Ã÷î¼á¶Û³**³y`ÿ›©“—}«¿¿¶ŽâíäÜAÇY÷o]õ/BšlÿsÒR)ÜçIQ$yl¶F¯$ñIW>N¦®r„¥Š…šy¼$9p‘¨ Ä„;FûºoSÈ–ŽN#íûh?ŸK9““¶M‚Ú”Š8Á¥xóÇú°a~.M¸²˜͸³UUµ.¿ (voñÙ+26¶:&¦Ã¨ŒÊôó²Ix®¥–éÕÈè™ òãú‡Q}Ô(˜rž]%{—wÕÖnl],‰‹kÕæ"ƒ3Z!hk2”šM Š"ƾžŠ²JÝ,†jˆØ–gfþč޵è?p­38 $÷lͶ¶vGªz·6:ªj‹eNB(ë$“™rÉI=C}ºÃR„:Üé úˆgs–3€rÏўϞFzvôüƒ›ÞÛ–£´ª)(M%Nzº]O)Š’¶j¬}VU"«JS‘†RE¥e¬Âà+Ù<8É*(:“¶ú¢ÆkÑ® înÀÉæ:ê5ÇæðU»Mš‚·4ÒTãð™:Ÿ3Î%‹>$ÉE3¥]]5[TVOã-t%f*ª ¨˜vÄßB…˜Vž½B|Á³Ëµ\#áWOùå¢ÛØÖÆýå´tusˆ§‰r1-=<”‹z¨|SRL$v%;éҮʷ hè<¨uÑÅN=)ÕïÝ4™S“ Âbsó<´9]Ý´k3aéä£Iéêé«hòTíé$rƘBž ‹ÌÚHõ©I qêÎ$\ÇB.°kz‹»0Ùl¥5=bq˜¸èrr•ðWâiš? }Ýž/>>¡æ£§©pµq€‘JÊÁtïM‘4ž—wÛfw°ßuÕPÑ>A2l‹üQæÐ]àje×,+SK5ãDÒ £é}võ²Œ‡¸S¡[ª~Cc²8õºjdX³™½ÇC†š*z÷ïãd¨ŠjÅ$!ž $, üŠíêI{øé§Kch@ø³Ðç°·ÖB …N+5RJâq”ÿÃ3tŽ*aÉcê´ù稂‚SQ rØÞE¦õ• bUmA"p®z²°,= Uß+úïªã§ž¯vmùë£ÓÉ5„ÔFÚ¯%-¼Ž Ⱦ–ƒ+Þäò¾ïƘé¹ã:öç¤>oùƒíÚ þëmcë+*d§¨ÈK[Šª•ê%”õš„äñŠ®àÓª*V ÚoêÙÁ§IÙäè_êÿ›8Mý$ùŠ:h²óѳe¶¥LpÑdg’ª‘r¸h«"ä-eåc.VUeVþÍ ²EM½)J2g‰èÕ`¾QuòÜôYs0ã·F9©ªòQJÆœGKI‰ä¨ŽuÄD¹«`N²6<)‚þP{Ť“Z–8^…¬ûÒTA&iþʶ‚®HeÆÖQIøééé |uq—F§4ÑGrvK `Z/‘²ŸH•¨4è¥n·5ղЉ$†¥©„Ê,«S9ñTDig(Ÿs«•‚O*:B)ÖECh˜‡EÁ4#‡Iºw¶3I±Ø%”4•¹ZÅ’+Y ÔÐÌ29‰¦ÆãõããGfŠžV $Ž  54õãÖøâ™èÜí<;L\5›³!9Y«k%48XŸä^¦Š³#<Õ VSA•G Ïh¼cSœô¦PCtõE¶%ž£ A ºo³ÄGU’jzf0SÕWÊÒ˜æÈÍÔMá†4±j™efcÃ}=ØHõë~RsЧGIAŽ_òzªÉj,SÖxRuRm¢W8芞d`Õ$ŸHŒ¾Õ.š‘xS=L©WŠ8Ê–CR@åL†F–YEÅËj‹~ªG×µP)ÔµÔÈ$iPBºZ×ÒPé7õZÜÛð/ïz#£¦ŽkÖDU³-›öÆ­>‡%˜ž·ÑOÖ÷ÓÚI‰Õ€Å?ËÖ¾}v›HlÈ‘²ê6d ¦âÖ³X ’ ¿ÔjãÖÅNõ«ýYÀP‘ ³fV@_ôÏ¥H-`¿´ì§9éàêEs×ÿÔ¿x͹½¾ŸŽtÛCý«ý·½ô˜Šõ&`à-`M®n>œúþïá|ú¦¥}HŽG,£’¡…®,87°_nE ãÖëŠõ(?VÒ“p¿ÔÞÄ{Ò =Oµ©GŸ^›)'@±$ÇæÃêIÿ[ÝÏÄOZÖ¾½rÁ¬ ‚/vÛê~ƒoþûúk­–­qÑ{ù/¸»=c›Ê.@Ði¦tô*HáIR¢1ò,}@é<‹\û݈ÇJ»Ãaíaûc»7>ZjÚœ¤»ÕÎV*dzG0¬ŒéK5Tþ{¨Ü€×·#’UsqFe£€U£R:+;~¯ÓX\ v^4¤ž$U‡ÄÓ™eFjy1ðÍWâ¡}™Ø’Ê£I`Œw‚kÓ$ èVøÛ’Ùûw–ÏÒãÆ{-DÙ ª²´5W}³ÉK!¡˜Såj0QÇvѬ[; $ªÝWµzS¨TW ¾¯kîÎÍl–W;M…¥§ ÍÔéÈÕC©ÇDe¨«ž:*IàŠ¯!P±§–GŽËƒ!@̬IJ6¬ŠuI9¨=)ñ]sˆØX¬…vB‚–†I JªŒi¨¦¯Àe ­ßÔƒ!5žŸ?:uæ ¸YØL-E^æ®Ý°G˜Àxʵ6F ip»¾ ˆ©h¤YcÜ+LŠÞSª6_z˜,òïî(å™Ú¥©^‚œõÍ_¿"¶³û8É5õ«þ¬smHz“eÒm¼vÌû‡J8hÞº‹'˜ÇË /¦ž_Ôu¾jˆ"…–H¦F›¢+»¸ <Î8Söô¶U"-#˪¼ìÆÄíÊš­É—¾è©òÏ£hÕ£LzE’TÏ 5CIöòM÷‚/(bäûUÑŽ«LÜXøó:“ØiÑm¬Ë&îƒ%¼çÌÏE&.ŽŠ––šJÊjuÓ]’ŠžJDŽ'¦šXáMHŒ2êg¹ ‚êi$Q@h8ô}ii ª""àôg"Çõös³²[‡oUÔ=gö4SWV­NêÈŠ\w(õmWž9#*ñ“ÃJ\^3O+C·’e Ú‚ùt’,™bŽôÈÈH­8Ç£ø/‚œ1öt_ºú£¾¶Ïiå°™Z:m±U“©–±ëDiðá–ª:ê¹W‰b™d1:™£(WëϳÖÛàžÝaÒäƒZt“ugºdv¢Šô-ÇܸgÊdª£§ËáõQEöŒóÒ%KÅQEEDTOAQM%!K*96`êж§Œ0"ŠzTw"i ­iÓ„•Þ‘Ðí#™Š:œ® ¢*lÌðQÖQÐV½íOEÏN¤C^‹öãêM÷ž¢Åbëß5E-T•U8©«DdS6‰Õ'§£’š)’xÕÅ9:ÊèŸo S#¥¢Q)Æz…A³3Û^²­éiüq Š"ÐK4õä]F5FU.ÛMÇ3p„޹c{S¯ª 8ìæ* ]ÁK]Tqû‹G#.––X©ªá 9à—Béx%‚&ºë̳Zž¼’ª'Ï£;µ÷U4xZœ•vs3¹³sÐa1õÿeD§9G 4¸¼í>J‚ZŠÓS—¤|®«£®§Šœ‰ HÈfo60Ǫ‹¢Xhz:ÝCò»rÇ@vvûy²}e’ÅQÅ„ìÌ]mm"RVÓCü7‘¥¢’C€ÜTÞhä©’°EK2ƶÖTy³¬dé'?#Òß $Câùt;Toü/3]‡Ý•iO¶i¨¶ÝNÙÈoò¼Y¥zLsÑ -%G÷š®¥V«%57‹Î!V¦i•ÅqAN“5œT¥:…»Þ·/‚ž‡ì!ަ†–Š¢›M2=ªéê*jk *¥ETx¼XYر¼õ.c<=©¦¬êè¶h¼G³=~*‡;OS_˜¯ªÇVE†Š8•žy)¥Š•Y5J:R,òÍ"Ú5‰À…ÝÛÑâzÖ² ÓýNDW×Õš)ÃãR¥`£Jh´Á8£T¦j“"¯ž²WðêÔSŸH±>õ@ˤÆF­éú–'Š–fEp‚Jm:ÑL„9¶€¥H·ÔØðä{x2Ö•ëzÐñ=9RPVI¨ðHyЇUT¾“…—Ièoð>ÕƒŽ¼Y¥zrINÒH¾V¸˜CG’„’í%ÝÕíþ¤è}û¦zŠZ"ÂàÀª­¤+;úµXnž»ÿ^ýN«VPuÆÁY™YÝt ,}kv6úsËÞãë킬J-†…Ž_%6^˜WË%CÓyñØÚ?JV©Ê ‰ÔFÄ?(Mz!ðØWˆêð4ÐȬ§ViñË´°ô¸ùpÛŸU$´·¤ª†£Ï1hªHèáñ¨’Xªå§’(Ãy%23• Ü}~ío8dbV¼:íòÉ4+¤Ÿ:t¦ùu×I¾vä™¶ÈÃÑåéieŸü.i3T™»‚[K6:X*1ÕµTòUA'êv”¡ ³nÞÈeWйé«í®IãËËTu´£ÝÕ;Ó)=vßÝÔ9ZLþm&žŒÒdb‚†¹–* Xi©) F÷ðyã[¢‰ ~ÇU¹…R€Žaî`•ÖZ‘^ew®ßŸgÉ%Fä¬5µ”)|·Zº,²¬qùê¢z)ª,‹-eª 7½•µŒQ9m:èÊ;™û4’:«ÜEUwkö….nâiæÛ4«“E§JÖŒG#Jk+ëx©Ä2<²Œk•G j%èÖ0-dª—B; {™Žª“Zp9ín¾Úû‚¿tdóûOsÁµ¨qÛW;ˆÛ˜ ÅÈæ‚¦HÐd2*‚Zs§«’†X®ÒA¼JÁÂrîö€·dÿ“¡¾×rLêÂ?Zc£/Ö;Kaö.À8n …º°—)»6Fg5ޤ\v_F²Ò­]= ;†0h<îÒ†ôi×*¾½¤E… èKµíÑ C3Qzw6Ò’¡¤ÞŒ­náÜT©H'¢Ä¼Xº¿¸”}¥Y<½+R>íãaæ§Xçô’Ò+Ü’’âG˜ÐÑköt8𢎯©Ðc}Ë“ÃìÜsäz·½7–;mVVE$¯5.v–§û­45”Y*º‘23BÁœ¯í!-äÛÒ×Ç’†EùWüŸáè~»¼×&8ÔˆIòÿ7øzûCØ»S9Z7-ïÃæ1ÐáâËÕGYMYK"¬–)©žXrøìԤܬ¾Hf ‹úA=Ù¦ƒvMF‹CéÐSz7d€Ìsþ~Š–ºoï\®cmÅm ]>#%‡ÍÁR1ò¥LuM” g4òÑWÖBÒ!o„ðÊ)¹Úchj(qÐb-õÞàDX…òãǬcå%¦ÛµqgFòíØLÙi‡7û \ƒS¦FŽ4¥‘dŒ,,DˆY¬, ¸ÚUŠ5zÚÝJÊ‚Gíé÷yo­¸1y*l†6žV¤©§¥¬Ûµt²²Z:©ï6jž5™v‰i”2—i–1©Ã^ÊÕ”B{Éê÷¢ÞºÈÔ½Œöõ §¬ªÆæ©e8_ߦÃTAVƒ(Èë¢ **$‰æ1ãá™F†`DE™Ø³6ûã«èº}3³*Ešú±ÔZ õ)Rš›!G[@T¦Aªe6ñÕM šÊ$ˆi†¹ÔGŒìæÇ©ÍM:]EI q<:8ÝA›zß²XóÃ!ê2‚“)MI<1´j¾lt”u)W 6„«ýX\‚º ãÒ9"poG£¸¶F›ì ©¦H£"*…ûƒŠZžÓÇ1qƒ[³ÖŒG\ÞÑÂçOÀ H èdëNÑê‰!‹AO-"âZ˜jaÈH­]$ê¨V“Wî1$ºýO½˜ÑMIérÕ²@#£Ùµöä”{n(2ô‘¯Q•˜:Š´¦­Úy$_µðK ²NrLŒF¦°T°m`ÛtàNù^¿4kÔÅÁ¸ñ•µ•‹âž®$ªÆÍ[L'šZ¹&fZŠ7bæßæÀ[ LlY§W$ ‘Žªû¿:WnÖe«§ËmÎÛjvJ,6F²«7AC_ œÀôÔ”1Sùi¨Ë¼E…2LWR•Ò–AáJN‹æ…]µ!Ç@Ç]à·ORävý>#ŽzZŠš‘†¡ƒ-xŒµ9oâu”¢71±—C¥™=Ú@¦‡Ë¤Â6VŽŽ^ÂÈnªŒ­V?+mú ÓW“Æ ÐUŶ·&>±cH9J)³˜ª ª ‹ÞIaSh„†ÕcàFz|JàÐpèÂM´2|müŒ3.ãÂTVæöUfæš*\evhÖË[%>Ž©èsü9Ì>pÂ6‘ªG4¬æ¬ !+ǧüm4¯ŸCÿ@¾7(ù ^::£Êae¡Îä+¥&lr㣯¬7¨eJÝrÃ+xã2+¸Uf,ŽîÜ^ ©n‘ÊAj®OK}××±ÉQ¹òT²ÝS@qøð¬iihò2VEŽ­¨ð:K>VŽ–¥DIä1/P*S-VtÁ¡:iÐMY„“ñÅ'Š—Ž¡£¤¦?± ¼…5ÔM*Ç _å3<·tu,¤•Øp à:£„N#§Œt”Pã'­…ÒšÖÍ º´Þ'ðÃGLOê¸åžë~t{Úƒ¬c¤í$T¦Šõ4•„OVÏ4‹X¾áľ%QÄQp½–À~öµXL+޹‚€«)e˜"€Š]yb¡Ov¡ôë]v$ƒ$&Ky¸ú± j÷ê·×k) ÙJëB p XGê=êƒÏ¯tÕMM.c' ªžYQ¯SÅÀÔ9 Ü“ï|8u¯N¿ÿ־؜ú².’ 7_éø÷º|ºKÔÈ Ë~A±¸½Áú~.¶ÿmïß>½SéÖe`4ŽHas¨ ýÿ[ý÷ôö)׺’ ŨàÉäoõ¿â}¼¿Zíòë*:` –çOПö&Àö>í×¼AŒu-9§ ª«‘„1ų÷`ll@PÊ\ '“îШvÐxu°qZc­{¾of+3;–²BµùkË+$2=31¸K®¢d€/rWñol_(ˆ• :<Ûî!D> ¡§§Ug·úçue7@Ÿ= ÐÐÏ3ZXš¤È"¹‚*é‘¡„+ „º§Ê@$`u¶š7bGÃÑîÆe;€¢Æí}§†ÆaŒxúãY’9dJV(•u WE=M^BMšUM`Á ïeO˜=4fZÐt›ÛømûÜy\Û`Ó'‘«¨&†Ú¶‚$‹E$eéq^SPÂI¬}Z€×on©ÕÁ¯—Vk×?#öÎÎÁTAºò”[vJ8EvJšc55õS™¦“ãŸî*æ™"Ž5XÊò0U³i ³9LŽ­ª‚ õ‹w|õÚ™\Pã±{“CM>>¿wäévÎ2¶¼LéSOM%kÑÖK*i2GÒ!RAÌSO®U$ŸˆÔ€8ô½øÛñónvfGÛ“Ü’¾uA‰Ä5CÆQÇ/™ã1n(©«&š£Ñ"À‚A¿´м¾$‡´DÅ#¡5üº³ùðXz ÂÑ®3OKM.6žšiDa)S$E4zWMÄl-Ïןkt*нܾ²(3Õfö×Yç³=‹—Þ{wgä÷&N†›øv.“'U${c1µnV¶¦¨<TTÉh®t¡®j¿áRª¥åfzhã ñ•G†G‹0ˆ›™ zÍ!±,M¼ú­Öê‹ Fªu¯ïÊ*ón²HòÙ –©b¬LzE-SŠj–ž–¯u¤CSÌì¦ÄT1ZÄ`·Œ;ÔÓ åIJÏUlW¢—§Ýyæ¦þ9."„ÕÖdçe™•§‘§31H p»pØj(°³w·i’ËåÓè—§@F¡=¼vÞÜ]+µ±»ëq`ªç¤Ü±c©qÂŽj(êkàÉÉ-I¢heZ¼’˜#X•f¬€\ßG¸™#ÓŽ‡ûxºÛ¢ŒÊìÀúŸ£‰ÝؽÍ->¬5Û1—ž‘°UÓÐ}ŽF‹ 'z*lU%u,ðÅhR‡BùÌ’ù±! «TŽVc¡í†ìd‰cOÏ=*:ë#[¶û³jÓUds{Fª–»•Ÿ'Ú*…E&n¦ØæŠZèê—KÖÂnÒEt ^ÑM-#e+žà;ªÆŸ§ëÑ•É`±ðdñ¹¹¾¶ÞóÚ8º§§‹#Y˜YwRŽŒRTÐåèê'FYìm’ØÔ«)_W>ÈÚÞGW-Ïú¿Õþª‰æ„0êáÒ²;U6Ý/ñŠº]Ñ–À»¬Àæê`ûs+AJËüG%E0z"Xbð•“DŸÛ¬ÅFÝqŒPÿª½MºN­ú,k^4ÿc w|uïaöÞÕØ[‹±Ò𢻽ÕÞuµóÔeW óMEýïz/$¹šªxUR6r¬A"äŸkãÝ Ú°ƒòèž÷lŸxms©=]ñsC†Ž£bäÿˆ®G;I¯šĵ0UQÑUdr‘Ѳy íPâݵXŽxÙó:ÝÂ"CÜzî´¶mâè!肦¶y`޲êè0¸ÿ >F‘#|ƒýÌ1-l´â ¢¥¥¨†{ÄIÔ®œrl4À>’ ùt_%ù†ÜÙŸáé‡ ØÙÊæ*ª&\¬ÕåM~nzè¡–ªFÔ lUZ§† Æ²v’4¹nXûU.Óþ.„µ}>G¤qïNX†$õ3p"Šªú:*ñ 2ÏTжš²d™EDu1oÙx É`㟯´ö°2I¡Ô…ìu¹îb•KS=YMÁJþJ˜Á ©§}––ÇÆëu Õ JI“}6·ãúe¹TPµè¹K¥É C^¿Å—ÍnÊ©#‡+AŽÇ˰ÖTVÁ ËW@<ïÉ*ÍN”‘úÂqõ$p!vð£‰ØvӢĺ3ÕŸõv/eWÌ6ý.Ïž¿*–íÛY‹¡ªjÆi<`ÔÕd&U‚‘ŸüÔtkOÉö†I‹°dø:Sõ/€Mz6½§Ð½#Ö»,þ[ I¶©òK«5°÷Ûž:¹…8ªÂV(‹%òJ¥¡ˆRoíZFLeJá¸^Ö.—ù3½6VÙZ†ÎÜ™lm$‘ãÍ~GµÅKNR×KM®@³ÖÀª iñ£ƒÉ#”Õ Ç–¡W:GG¯>Jõ~åñäó‘SКz‚ÕI[[?žXýVQÐÒ•y¿§JŸÇ¶‹†ÇJBèUÝØþµîJ ZA„Ü4ñ‡ié«§¤\BÏ,°¹…?{]Dtò¬¬4)ÓÀ•L@ãƒÐ¿>*C_‹ ¦rôz£§‚<3Uä”»+;Rd"0UDðÔNÅ%$+äC!ZeN¤6ÖéÑ‚¦‚*ú–†žŠ®xŠä1’Ã5DTÕU3FŒóŧ´zõP+0ÕO5k<°ÓÓÁ µq$p  a"_TŽ8Ê`x·õý¸ jž­cYQÙ‰þ©î·sÅC¸ŸŠ£Ëæ«hê]ä§ÆI‘­ó½ƒUÏ&:9(©¤SÉVbJð=–ÆUT“äàVY_BnŒ$øªaŸ©††ª¨6œVf¯ŠÏNPSµ:JóWG, ÅÙ €ö:ìʵÜqÔi©éø"ÖA§Ceezêµt`§Ènœ•DH«ŠÃS(ˆ›4MðÒE+PÃkjžE:“’@¿¶ Ú7ÏìèÑb:'tïhnÜ´är1õÆ6z™éd‡oâ±Ù½í]K*¬kOS“ÎÅÄa*a)Ž¥RrK+ÀiÝ[» u¤‚ öžŒ­v^ëÔ[W¸÷“À°Õî¼ä²ï=óQ+ÒÃ÷•¹o<è§Š%Sî˜È]-¬Á˜pJ“Ò´‰cMext:í®ÁÍbkmÜ G="d²³æ·F7È®)(¨ù XÆ"³µ<[jû§Š²‘ùud *GFƒnü”Ãbg¡ÆÔÕÓ,d%l>J,¶ë†œØFù¸ã#¶à§© 1²Üù×ÔùjKY%" у‡w͹¶´™>6,…sáï4®•—]%¡Ši#,à9!Y®`yõK7ϯBÓÄë­»+Ð º:®›#ã—´rÛñâj¸+ßfcÖ—œ ˆ¤€æ&‚ Xå †U¸¢c! ,-ÈqP7Œ/.•¢1št嚥4&,¶#«¢Âg"¢iáÌæ*è5Ó–Ó44´ñ7j§@N‰¥Xsí¹ FóÏM[È2:(½—ž­‡lîXót©&³5S:ÖO|t–†\TËNÐÉ#]uDÏk¦Xÿ×Bl'§Ù´Œðê·{C{ì´XØ°ÕØâæ1di+OöõXxÖZi¨ê¢×6©9OÜ'Ç+/@öô0x\¢û¶RËN=Uÿríj?·Çà U6Ji°â”Ö’Œ—ÁL¸¸' .QÌ­O Ìëm~x™I½½¬žc .œƒÓpè¨ £òè©æññEAÉSॊ»kjÚ†´šˆ³TB¨W™f¤ÕåhTÌ‘žm§óõöCyz¥XL1ÑÄmÔÒ!Œš6òÊv.ïé®»ÜÙl~?—ÂîQžþMNðRÁŠ‚®Š“áÅÕ´¶¥ž*ꉴš ÁTÜ=Ä;4b‡ý_ä=ìvÛùcû´ù¡¥}UP¯¯Ï5d9õÍc§ƒräpsBb¦’™ª—=ÕU5u2N=jî.ÖÞÊ..VCÇ£Û{;…!fˆûz~Äö./kÑmŒVF¦ªŸ|d²U8t«–™h¿‡âwªŒYA)Hê™Ùq&™%[ý,+ ¹ÑhsÓ×7k·ÆðÇÜO§E{;UØ4)˜›W“\–3t9­†YÍTï6,I%$Ò ½Bši)@’0Æ"ãP÷ö-µ±·X@–n.÷y.KFͧ£·{¯°º÷m`ò»ãAØ9íÕU÷”»/+ 6­jGÙ5Ff©(ä«¡Hš`Æ-RDŽ£É%×[Nß1#Ä¢ñÇGv»žéPÈžc£ƒ°7_ra²rá;›llxvmp¡¯Út{6Žzz=²’Í-L´ŸåUUËJí„`Æål#yÚíd IÉ›P<)ZùŸ—§ù‡;eöå"+HšVŸÏö}½%{~Š,]VÉÙ8ÔÅY_’Y’<œU•õ-W[ˆžªœ†H¢G—ÃrO‰Ûê¥BrÞÝ«Æ÷4ãùŽƒüÎ׳Fâ6$Ôy|U©S—ɽmôqUSTSªÊ‘Ôša<™)tK3G7ÛÇ(‚X¤%Œ ºØ\²û•„Öæ8ã„ŠŽ¢ ˆwË™kJô‰ªÎýÛ-‚*š ÔÙc 4Õ *ÖçD…Á$ðº´ýOµñ“§â¯Dí<ÑɆ¡¯O›ìqôxÜ}\Á ©“ ÑUF^'0²J޳"¤TÀxcÄ!A$ކÝßC-·é²Ö †@ò¾3aâró#´’UÖæ²1Åöô‘I"iMNB·çú6ª…xt¤Â•^šéjv¦K;âÁmGŒzúJÊ[dêj£¢‚´ŠüMT審Dý´Ò¢ãê@öä×Z¨áÕt¾;OV×»¥òÔ”uÃi’‡%W6*& Q…Ñ*hj,é‰#gM Èf?eÂ`Ô¯FP T7G÷©ú?Á[I]U*ÐÒP½I š')ûs´@GYYBjOÏÜÀûzX^u/Ñܡ٘m¦9+1ìTKM"ä¾ÝPhÖúKO‚;­ÿè=î dðéƒ3µCôÙ_ 5LqG†ÅC‘ÇA’E4P¬sFʬ5¨ÝU, ±?×Þµ¡ óé¿\šôÕmѯÉ<˜¬³Q¿µ.Jš–§ bjwòP‘M¦= ¦DVvoQôójåÕG˜ÏUµÜ[›7²û-ðôtUUÂV–¦E)ž¦ššPñÔ®0T3Ödj‰Vàt0$d`ôšbZˆ¤†è=ìÍÝ‹ÛÛ:ŸxM‰£ÎQǸ6¶Z8+ëR© išš!KLhÖ¢¦’µ YoÜwK:©)É¾ÇªÂ®ÖØèýínÇš»Âäè°5xŠ]˵ ÑN¹6ÏSS™žŽ–&5lÚ±òC¦RŠbI:$b*n@=/«~]¾ãí†Ù™Œf,\béæþG’—rc(°Äï4ø¨(„K4\ÑFÄŠuŒYU¬lô3‚kSÑa„-AéŒíy«røìFØÁŕЦíU_†U8×§qOj&/]S’õRzdÔp°\‚hGT0Ð|=•ÈÕSA>àû¯$RVK7Š¢hɨñúÔ_Jt j$ûR¬‡EŽfÇŸSÅT¾- Þb¨ˆÌe™t‚ÆF‘ÏrOøÛÚ‘ z¯]¤«øÝD~£9ú »’Á…¬‡Ú÷¿ }z÷Yvæ.Lî_îYXĆè¬=$_Uÿ ý}è ½ÐÉQ!ŽO †T‰G¡Q8$XX³X{Rµc¯uÿнhå°™™›è-ʠ㟨'ÞóéÒ GOO"Æ-3 Aÿ¸Ü<ÇV½Iލ9mpF×½7R£ý¨ƒk÷¿wÖÁí醵@êhš™íjr–ôI#¯×ê ^ÂÞö EGWŽœ)ž³³ÀJ¡ŠFŒ†:UØk‘vAqïÝ=SéÔJ—…`’F§‡JBöwX‚¯êi5iK¿#ÛñS¦ã 9'‡Uò–ƒ/ž5q­=Úø¤­z¢ª¶Q<§ÔÂ_Ô´‡·ôúúï*G‡5òêžwwWïLô•4T™<äT°ºŠ—‚j}»*î%F3Ñÿ ¦¦º1*³Ô^KúQ­`A%ušÔt¶#œ¯A^c³úê6¸s›¿&jLÿÂ6Í{ÑÒ×Cd¹íÜa\ÆFŽI$ÐñФ2G!u"Å­q€+Ǧ̠IDSN—{cäOcíÊZZ¹FÜÀa j¾#oaqÕ4çÄ¢Jú•?q’É´h¾Yds„}ûê!ztLçáèÛõ¯ÉÅìò‘QíŠOàØÊSQÎeR ^ µy¡›3–•¡Š„âÃf¢—²7-Fk²r’Ö¦ÎÚ¦–Hö¦=&g«¯¯|T¸Ø*éC$Sfó“ÿ £«ñ­$2´‘I%þŒë3щáÖ‘ÔÏ©GhõëÞíŠÏ÷[{¥qxì½,•káì_-eNÄ ¬b¾gpda!Ø;ŠÙ‰–Ó c URA\öL©Ã£ÕŸLtÓÐõ¶{W1Ô”Sో“ÍnJH~çpîLŽI©ñ[f@e5CÉ5.ÝyñGK›"çPPФ«|,ÇË1 ÎÀh4¯§Sé¾q|€ÎÖ®mgqbcÒ|þ_$­XÕÈ’º(<Š””ÐD§HgiJݘ–°(q’7`TÓ¥¦Ê4P\ÙÒjO‘=šÛï¼²ØøjM¢­tŸ#‘©0…yk¦uñÑ@±€V(À¯¡E½µûÇTˆ ãÕ¾ @û:1½“ýáš 7¦IdÁÉ_M&B‚(PI,Í¡êâ7ôË,ÎE¿Ï)ô“ìCnñ4aµŠôZïRÀN‹?Èz§µñ9ÀT ˜ç–T¥¯IBRÏ ³Öãêèâ¹ü4ÑV°:aíÒSÊ'C½OÕAw6좃·qÓÍ÷ÔÏ51X•f‚„TÉTª!,JļèM­,Ÿê‰¾öoKã·G’6kÐÍÓY uOhQí Þ7ºc¤ëmùO²f«Ž— 㮡Øõ…«¥ÉV8\=dô±K ­«I_¥ýƒoç$JGòûzíd­Ä1·Âß/•z•”«¬ÚÙ\î*zíÉÑQÖW¦gf âêß µµA<”mÞÖÈÂD»©™dkzl–_f¨'åÔ–©ôñ† ‚@G÷ŸsîT‚%zÍýž®–†³=5D1GqOœáä¥U§t$i;žb…‹}Agdúk4L3þ¯³¦OpúcRÃìés´>5nÇYWº»C;]†¥D†tÌÔRÔdš\œ.Çìp4äÑI,мihLjÉ}7<„£™cÌVêGô©Ò¸ùejó\q>G¡+¶¶NäÏ×®ndäܘaGCžÝjèñc5+}¤ÙYÒŠ ˜!«¦¥1p$–Ê‘{ýçrĻσåþ¯õ‘Øöke=‘ðóéÿzô>2,Æ>zl^J¿Gµ1¤®–Yò³¬bV¯%‰ŒÕÍQ¢q¨éö_6óqø‹yS£‹-–Ò縕:DÅŒÝ<µM^ô¬ÝplÜ|³VWÓ¼5±ÄÕ3Òš²>9Y飧Ja«Õ •½…ùr ÒáÃj¤¤uëÛXvøuêÅ@§Løm¹±±²dóØ¡U»ià–j¼¬1 jŒv>¡á+Eí_,)Z!¤!Œq]—Ë©G΢µ¼:YI1ô¸½µrUœNnÅ£Û½“YSS€šŽ‹/¦¥yi) „7ÞTUKQGâXä’y)â–5v'Ò4Ûè“o2ÂÁ¤cŸêýwV´œ´jÃEŸ)° fA±ËE%>A©¿ÊêU§žhªD¾t`ÐÏG56Š"pg$)ö)¶¿”5=o6ˆ3)Ò.zi$§¨%ôéA1r¨ôî)—L0ÝIŸK©!@Ùy±öe«R‡§ŸD®|/>„.£Ãî=üqÛgk4tÙL®Zš z§ˆ¼ÐùŠ ­šQ*Y@ŽGµ‘°*:z)CàŽ¶˜Ø?ú«¯z¶8su»—sOƒjÜŽr¹×ïæª–‘$µ<…¥„1%yÔ¿BM½˜¤(#2= ðhÒi4zõ]›³¬7žW-C†¦£­Â`žL¶J'P²VÑ<”®µY4§”ÇIHëNV%’ìRþžO´Qž¨c¹OCcŠ\w·1|¶[póÖd!ŠFÆM”È4”2ÓЀT}¾ šA#3j]NmÊ“í;°PKäõ´YC­[èóõ&=0Xlknèž\•}FXjéå4ÔŠŒ´ëE *5 +IGú¼p @\“r$©é_µïõöø”51Òk‡Uz5Òìú¡/)á–ši’8êiÀ]P5 1©ÒìP‘É_nštZ.xW¥†+b@Œfjhây¡UŠO!:ÙJ( ¥¯È?Ÿt…mDõ湪zMoÞ©®ÍQƘ<¥n6ž¶'¦®¢§‘–št–2’ªZE’9,u£·ï¨úõ´ºÐ¤‘Ö±ßÌ7ã–OeöÇ ùͶjÂeŒÙ<¦F]·˜¤£¨ŠZȱ¹Šhg zZˆ…JSÔ • ú±XAö¶GÝqÝÖí®ã’V,0>]^Ó¯j훲ºõ²TuÙ#]Bû˜C:R-2HqóPɘƒ#jY‘Ϊ2È ÇÒJ—¾i y™”ÒU†z?xüýnÙëηlÞà”ËÓÊÒFÑcàƒ%Q‚¢x)á­š²†®º»í†o:«Ç;“§Y+E€J*xѬ@ùô‡íúÉ·WVasB£ü}%Z˜ªsÕ•t3RíºäŽ’z êæ–g§_+Ëbº€b¤²(T Ó7Üzº‡½^}ô­º©]6ÎÔ…Zi1††–޶¸RM$ÏKC¦XWÊñÃåfsôØØÆC‹¼gRAL«ý_êÁÔ¥íMŸº/_€–i ˜Ù)éèÙ§…ebÂeŽ™ ©œð¡ôÈG:}¨‚¹&rX“£.)¢¨š­g®Yâ2ÓÆÐÇžuÕbÉ;hP• k:ÛÐHüñìÅXŸLà׳»§\Ž6V’|¥) …3Sf™.àÅN‘s&–õ#ëÇ>ö2Õ&ßÖEuù§FöRÚ‰¤i .?Óúþ¿¶.ŸSQ^¦,—ä^Ü­¿<ñqÍÉþžýë@z¶±ÔèfÓ¦Á ¤Ü¯×úX~}¸¤hé¨þ,u-gqêÕÿ&ðOô>ï\WË¥`¬IªÒH)ãcx–å‚-8æâYÞ@@‰6ìlø»á^“H{‰óè³öNÎÇ5dò_!ÒSQËF$”8Š–žjªŠ˜@^Ýc™š¦fÓ¥€\¨ €TÐhzPò†%Cƒ|^‹eÑÒî.«€f+Wàºßo™®ˆÄ2»ž½K®ìÚV(õÖ2–(aq3‹mËFkÓöúOqù~´í®Þ’¼©§Š—fÑ@²íþ½ÚÔk‰Ùx¾¬%²ÙeXâ¢ÉWTÖS=E|®ÓM)ñ•Ê3L³)+öô¦FðÆ¥Çú¿Õþiûk£¨ú£ø®3a$5[žOº¦Íw-= MyÄŸRåv÷Jaë RËWŒu[†qF¨šä²›hD`»1ñJc¸i£—A~c­è«á­ÄN­Yƒ«uËî,e6â©\nv¾ž§/ÜÛõb¦ÈoºÚ:7®2‰¢ÂЫ$ ä§hcDrܹ”‰3éÓ š$­1^‘-ýœÆã–«[&ÖÙx©Vƒ!½Z† eFAÒC¯lõVÕБÑÂÈú|þB6,ífÑí™/J#ÓÓ£¸xô Ww%uUv†Â´Xè2gø /½­å’A%VW7_S©³™êŸT“ÖÔs$(  ’ŸÉ cëÓáå èF®ïvÚ”2Ða¨ÆNªH’³5-@Jt‘är˜¼]4è­¢:ú¨¼…lH§‡Ô KíL¥œ S=XKrYjOLX^Ö‹r-LSäÿ‡îœ~ºØ(di)©36Љ£¡”‚Í_Žæò¦#Æ€„û@òY¥c:‡Ë¥ Óµ5Þž?ÒÞö1\˜,Ö,¼ôÓW×RÔÇ EMs2E<͆hçŠb!tfY&ÄuRêËDj|ºf_ ”éëÒîlþáÚ5´»§]F˜š¹b†JÄx]1Qê’–)˜â®yô:Œsh½‘³(Y”†’£¤Hª%U}\î#½÷v:¾<œ5ƒìæóRʯLôµqyI§z7yð,AÌ¥ôi?N8&¿¾@ì“Ð¢ÂÆ0ßåÖ ×µû8q;‚£pç*·+6C œÅ}ŒôXýTÔ‘>'Hc– )’¥\ËÄm6ýÖål‘¸#¼ðÅz?·Ûæ™ã1?mz99üNÞ]µ·°Ø<åUná«ÆÔźVZz< }×h²9êšÇ«¾NœddIdñ(:Å‚ë.Hy.eä·_3ùu!Áo*CS=N+ÒÏeug\í>¢¥ÝÕ{š¦<Ì4y ËÂWQÑeò´1šøñÓQT³ Å=4Ea™\¤‹ ê J7 «Ç·žS¥xñ¡ªYí–ÂTT„z«ý_È5ÝÝÃ6{'›Œ©£Yjå£ÇÊÔú|oL±SbqË53HYƒOV±ƒ#F5‹f‘Ú%œN•¯¯ûêÿa£z.åR…¯§FÓâï_e·þãÀ® ˶#€¾G1S:5FZ?Û“P©TŽXæ1k`¬OÓ¯°•îápn–+y;«Lt"hm,-ŠHÐIKŽ 4¥Éà¿7üìì+VqÖîÂ_Ú7cj9áÕFì­£¡Ãǘ‚*Ã+ø¨j¦§¢¢‹$'#_ÒBZ¶Rˆ©«PE è>å -ÞÎuJƒ'§PÞï±î-vít~æé>v¶soâ1ô•;¢²Ñª«Mn–`ëŠ99 P’Ì#R=` U _Q+…ä2 jµùŠÿtÍZbj>Þ³%e=V6,}{Qç)éé]*òòÔEj~šŠºzJ” -m­f‹ZZÀÛKYøf]cJÒ9hú7 1WƆ ;£Ï‹¹`L›€¨ ž·Ù»‚ƒ{íš ®_¼ÇUàñÒSTé?åzE•¤¾›xä^ã€xÏŠæVJÇ!%Oœºë©©·:æ ÝX˜ªdÌÔŸ-T)ëZxd›íécab´þ (ËÀs¨_ÚeÍ1ךäÓ€èÍ¿ÇMµ‚ÁÓÓá#R ¦‚c‰f™g.ó,kªB¶Ð/ÆŽ=§¸Œ=!3·ˆ¦‡B—[t|9|ý-~CU%KÔf§C ™XKªt…„ÄT[òO×Ú(iž—OphG¬jâ(pñ¤1RÓRÑÒ±@ðë`Ÿ¥RÂÄ›ßýk0Ÿ:t^òxˆu“^—•Ÿ% ¦Eñ1uaúYÑ}'O#ÛJN¬Ž$4éæ‘…$iô·ëÀ¹ÿoœzØéÒAB«ÇÓéý9¿õ°ä{` ·Uzén©_ùžöVÚÆbÓgUš*ºª\t»‚£,ie¡i’ˆ»ÂJýÄ)²Ë$jVSR[ŽA«Ä<¯TJ"–ë[.»ìS¹¾@WnJúZ‘³6´yYŠšUü)R(â–ždId‚Ÿ/C4‚3èÃÙ5û4&©Äô¶Ü Ÿ>÷wî¶«Ìᛀ̈é6¥&߆ž8*j—ýýí>?'YM.Ã/ÜÓ¥˜¢xä’$@ #Ua¸4èÏ€8õ;U&µ'Äÿs©ê&Ëuµ4” 2µ•¸ijç‚ZuSüf™ZXìR£Ä—ýM!V²È§î18œPƒŽˆ g`¶'väöîƒÆfñ¹*œ|ó¶2žrJT¼1SS¨EYhä„aô«(¿¬‹jµW=™Y[ ž÷ÆÎÕÛõyQeEL•PÍ%5=4²QCCMà†¶‡DÇTñé²ëmJ‚üÞå£6œ×âdxf¿gVKA^qæi1‘¸øhÅE6j¶­á‚L²ê2ÔjúO«DñíØ.(rkž¨MMBô¼Ùvþ''–¥äw”TU7¥bùC¹]”¹¸¹úqk“pǤŨÄîéy¦Jº÷D‘ÿq]#UÕX©bEî[ŸÀÕ â™é)jTŸ_õ«ý@ù|bêY²¹ Le)t¦9T²úd’þ¦µ€²~?¡÷q)Q@H醸‡¯ÿÒ»ˆå傃buZæÆüóÏ>ýÒ^]MŽpªƒÖʪ9Äóõµþ¶°öúœV'$‚iðôä¯Àc{_€­{ÛéÅù÷|zu_ŸRe×̓õ‚©úEçÛ RkNžB âéÝD/8*®·EVyxüZ¢K~H¿<ȺE:wPë2ÌeO°¨6YÂÍÌž¡ä7üŸWøû¿³¤ÎjkÒ uS;Â"‚&Y¤Œ³W"Bÿc ¥Q?ˆ*%X!­j&ó×ÃN˜iW‰oà‹BzdÄñôª7ÖÀüú·ŽÉÀ>ÝþSO.'Z*Ø)ëcjLÎé©Ya©®¦_ãqOæ1C´#Ž—[-EB—ºj:´ôb¬´è®î‹ŽF†ª¹)«eŠ+L ’“à‚…j%œÁOW¨1ƒ}Msí;Â8ÇN« ŠtG³¨èÄåé(Ó'’VJÈâ¦Yja†YcUÁÃR‹4òTå%T˜ÏŽ(Ô&–²Ð@Ÿ†„ô©e  Èê g\bÞ¹i’¾©¦y*òõ ¦©&«‘R:QÀ ðÇC -<%ÕôF¥dlÌžXXj¬g*2BöÎøåµê^/”Û‰˜¡\„REU©Çâ¾×M™ž¢lä˜Úii¢gH©žbÑ\£º›ÛíË—qÓ3ßQhN:÷žåëý‰þA&6±é©§¤›ÇSÔbé&‘b–]sBÞ4©hÑ^7v.Tþ–>Ì("CDè»ë´ë“ÕN÷Gfå·îw#„Ûum=ªéâYšGS K/ÚÆ²ÃâŽ1~šP †¦RKÛ¢êà taa‰2K¤é'ü¼Å=N‹†lµ&ÜÝ5Ú©hí‘–ƒNB©”ÿš\]FDÓ䜨˜ÕÍ»,J…S´„ÛЭ¤.#R)ON‡íµUI·6‰Êå·M=@CG=*QÑÑåc܃7G[‰£2xÚÀ±SC5DncŒÉQy !B¼A$•öuðÖqé÷¦¶F?·²‡7¹;^=«†Àcq;qj3“%]u,ÓTCGTµÑ®j¶¦¢Œ©¦ÊîQº JWsvûtDÀSûzZÂ&¼Vnüݽ¾7Ìr&fÖ6c Y.[”ʬ›¬ôØÿ±L|M–©0¹ ¥™†TgO—Åqk4Mq0¤‡ìãÑ…ü޳G#Ã.„޽èÎÇÞ[ïmÒÕmù°±n TXÔ45Õ°ÐTELŠ+à«û82 Qª)ãt‰Ô„„°!ÝÇxŽY¥²çìô'´´‚ÚÁofZëŽ^§KümËìÕÛõªŒDìºÑ6?#JG“ÈVTÈbr°ùëi$ŠZf\„"þڲ幣ºŠâAŠ×οàè/»óTf3Bº°)ёܛKY(ÇvDcòLæj"ZHiªcû8σ55ZSK:I"þî°Âöê7¹„Ì«¨Ç@¸ç3² µkÑC쿌59üüظ!x6ÄôñÕ»%,”ôUÕ“#II;ªii›O¬¸ /}CØ6ãnÜ­'&ÝûOÛþn†6ô"Ì Të þ~ЇpümgâS+‚ÂÖU×C÷ÿsQBõ³Ò‰ÜêˆT g¦Š…¨)ÀHÞ8f…¤ª‰ï-˜I25}z}¯,ï•J7ø¾«3ztl¯qc¡›,v¶+2ej2/†yäž–]r ªtªŠYª¦œ•OdÎ#¼ŽWR°£mæ‚F¤üúå_Òøˆ1Øê-Å»©ñZ­´‰‘’–9 d5$t‘É9©Š8éßTŽ ]l}7"Ë#ÞØœ«çÒå˜L?¯ƒÈúÞ1¹Wˆ©–l1«ÇâàwžUY%?Œ‹MÓ%BDæ[´i¢UˆFÞßyf+Ç¢f嵉ÉZP}¿æëpØ lY³ÿM´m`b¥’‘Ä´ÏÈ)õÆêãÈdaskØÿ_c†–-F½7áËáiáÕ”ìm¿·±J)c†G©ÆevŒ)2º]ô¹$1$ûVÄSz ”÷R†½ ¬SÈÎæ!+*$k¨gÕ`m`¤û`±óëÕ Þ‡j6¾8bñ‘+åë#Y¤¨ñÃÓoì±uÖú{¨5ãÒoøê¦´éïý´QN- gg$ó#›êc¨‹{Ñn^f%È :]ãq¥ét‹€±Ô’x?‹ïzÇm:kéꞟ¶ ƒÇ¿7üókŸvjp}h8ô‡í ï‹ë¹wŽr¡)¨0x¹*žF–(ÿyÝ`¦€4Ì‘‰*ª$XÒç—p>¼D¥‡¯Z:˜Q±>ioò+ä>ïî]ÛØ°oœ”U»§rî¹qhQSÖÓöh°tùµ®“ÕŠÍA%f†xd¤Œ½?š3ŒÓO7¤¹ñéÐäuUV+¡ùt a1y=¿³¿QTÒgi±„Év¤x¸(è7 &ß ÜùŒ¶j’Yé+¨jÒY*¢û¸Œ1´qS²¬’P¤’!3—FqÇàÔ5èsÚ=ª½ŸÜt§oUOQ×ÐÏl4+Q3ËAMÛǘê™cÒ7òÂ"5ˆ¡—L«ã$´!P8u¦¸+Z‚Ï¥Æ<æqö?`dÕ¾{pã«ih+(›+QCC+Ï8‹=o?[_ûí<åd©ÆR¤Mc/‘’(îÅxlœ‘N“1 MWÏ£ÁÑ?$¶fc6xÏK™Ê¥Fº1sÔIU“®™ü4T4”Õp@ÒF…Ãùä‘ùeXÖçÝâŒjéZä-Ë«ÃTK.:ËÒ”à ¬J©oóQFÊ"ÍvgÓÁµý˜C]TãCÑe×ÄBçXmË›¦‚(Zi&™"½µ8Wt21õª€ŸöÍ—cè¶whz»®®Ù°m<-:Äo¶ J©[/Õ‰æ×>ÙŠŸ^ž\ªýÿÓºXåk°+¥mùÿ¨qþ?íýï¤zÖNŽ_J…[ ÿª¹¿û-éíõøiçÒsž9C)b  Y5?Sj²…äŸ{ÿ^éæ'ZR iznª¡4BEζÐóŸéΟõýûòë`ž²E3;’Ú¿&ů¨Ÿñ'ë~.½SÔä.·?Uµô†RA€B±?퇽õ£ÖG%{•ˆÏ2TT¬zQ΋éàD¿@’M‡×ÛñvÓ­ƒŸ—M¹êJ$ÇÖÓ­!š¡RfH‘ÃU ´,R¡dFl‚,¢ßñÉZÊAf$î .sÓ‚R3Ñ^ÝÝ]>X­v^–é¥x¾ÂÚ¡ò9jÝG† Y£•¢†Z‹D|´óO"’ެAöÔ–¢G2kèÆÞs£PéO³ö.ÜÅO˜£rôðÇ„£ÉG&¹°Ôôõ.•PTÓR¤Óx™aŠ’(©VDÖ´pËíƒmBqÛÓ¦Rõ×ÐS‘¡©ƒr¬…U¶1!]C쎿DÙJ=ÁO·¼U?m¸`…嫬©/㬚Šk"iáb /©ƒ5ø‘β”Àôè¿ÄWbd Õ^d;Ç=ÈA²voñŠÇÊ×CKÊTQIG%<îôõ2E%utÕ¸èõ2ñƯÍ͵„w¢7ÆiÒÛ¡™üÎxt…ËÑd¿„äå¢ÇI?[U^} ¯|„t湪¥Ê‰ J¨ê&)¦2àHňæàË«§V|t>²†•#Ðkötåµ¶V!¯!Aåi©ZzÙ६ÌÔ/Š:ÌË.dRÀ”ÒÓcéå¾¹"'ˆ6R+‹Ç©TCž…ØÚŽÀëèoÝ8ÌD»r‚›C[c0t´×Õ|c ›ì²0/¦Ž_)¦(­¨Æ´Æ5ô€\”Ê%Ö/â‘m¢À?*t Sv 5>R‹)kköåJá¨Å$*hi*êiªjkë‹NŸå"FbxÕcAºƒí=í–¸‘ŠÖƒý_êÿPsoÞØ]€àˆÉôèÀáwfcs?ð¼Væ\[d¢†ªlsù×·"¥·Ùi–9fš¾²yV•Oí¬ºƒs«ØsL‡I¥?gRe”–÷ÆÅ àxyu}¶Ž;Eßy ëòrÉ‚¤§ÆŒÎ*†9~ùa’š§.1ìÓEÆ/‘æwEÕáÙSl6«s9¹+çùô—wDm†ÎÕÅugóÿWú¼¬^j±F!•Ú™èjª–¢ŽJªZ:%Š7²RÓJ‘B>æ ¯˜<ˆoêÓr*ÈÔŽD®c_ TõÏN5²ꉩᕕ¤,Î)äŠ|‚c+Ë=Q’» SÃÇäzÕó#Ñ;£ul]O+Á8:ަoê­³{JŸø&~‹xUn¼}55nmòu-Á9«l… e§L¬”î†#TVAý  ê-”=VVñèBšôÏÕù¹µ»|Á·¡­3}ÝWîjáÐÁO5Tž)jZ8Öœã#4Á–žò³w‘Ë3Š ‚)Ó°É£u±ÌЇ7…ÁOˆÈŠÌ|”tðÇ ”8y‚]Ø0Š6’y˜X-žãŸ ¿³{zÐPt–á꣫Wø¯ÔÑSÓÇž®¥0Ý”¢È¼5ÁÖ,ʤ€úÿë{S,åF€3ÑYˆ]̽jPRŠxÙUœhPúAãÓô#ÓôöÜQ1?>šÕLW‡_ÿÔ¸õ©P<~WU#HA¤Pu,ÄNš•aÉK•–|…BÓÉS•ÊäVc©$ˆGö8êŠhü²<óÓ¬>pÊìÒ2(Dp%2õS¥)>•'¶ö=+ñÕtðR™p±ÃUÂÍ9ëò+ ¦‚„ÏUçHc¡QQ Šm¥¤.„ôò\ ÏE“xM•ƒ7IŠÇ,"¯ «ÈÊk–žKËRÊØZIiÑÜRËK8*¹•Œ‹ dBS «1¡§K¤ü†7›Ø¢Æµ¡èYË{|í(‘Ó þ] ˜:dÝ9-ÀÈí|~KlJ ¹|uÙª:ÜþRw\¬êÊÉò*,U|4‹"Txš¡<¢à«¦‘Ù™z’!Š1%]2:e©¬¡ÙI£Š– —ÞËM1Åã«ÃÔã$§¨3O-TËRWÄÕQ¼0<щQCD…ñøº¿Q:8–êB!×OOözm¥Üu{Ç%»d¡|ÙL8­ÙÔy §ÝTÁEެ¨ž¨IP“JiçÅã§ñÅ5ç)±•59qtuxI£ž*Šúùªõ™šC÷T’ÓC<øß¸ÄŒ³¥˜+kWÓì's¾STL¤~_ìô=±Ù­\Ã+^„~«ÛUجõuVü8Ü.Ö¦|””šUhŠúO"ÕSÏ3Ï4©«Äò³FßÚ@× ß?Öxªqöõ!G¼VÄBHêüþ(nø3˜¹³ÊX*(è\UML¸t§˜(+Ÿ—ùzÚÄ×2GjN¢F:Ökä÷ÉÝ·ß›ç¸(³Òâ*³Ðæ±8ŒTn•µ1RÀÕPàêâ/OÃ-DúÌñÈÍ$A„´õ22ª¬iÎ;“îQøp5TÿƒÓìÿW”ÃÊ;eÆÑá–¶=ÔÎ?ÕŠôâiöÍOSï»-eFÿßCc.ä¨ÌÃ=]EnV§6µ{ŸƒŠ£r¼ôëGCQ]H•TÞYk¤xõ± c„\ܶà!eìõêbð—èQˆ£ô]ªºÓzî ÿµ÷ 4µ{r ã²h÷FæÀʳe³ùœŽ±ñu™:ùiçÆc+Ffh>ÕªKªžDÄS=ѳ¶TG Óè£éVP5F§üÞ}-{ƒ©±X¼võ̳ٙc÷3Ó®B’—)_ O=.f«3@Æ êêìVf?0y¦«¤ž'£‘"¿ºØs6ãhÀ–&"E~΋¯¹Wo¾ ¦.êõ«þ-ól|QÚÛUöXK¼§n*åjöpÉEWK䇓«‹U&¨#¬„×U4KcöÔŠdÒ2#±÷UÒÆƒZ›ÏWãü´ö×aîÚÔ¹}ÃQ—ÚôB –¥§Z2ñJÿuCIOSU_‘º›+x‘âôz¤¹ mÎJ^‹îO.¶ÑÚTtÛmRÆŠ‘C /J md,›‘ý¯Ïµ3‘Ñc=ž’9­Æ¡æ«œÞÊ<Ø*¢ä$éQ§ò}š[ÀHÇIÉ5=ÿÕ·Èší£Jú‡7$[ÕéÔ/ÀÿmíWEyþÓ÷_3‡ñÄÖ `I½MLÀ“ã .mcþ?×\:÷¥Š¢ìZFfrÖ‘›S3©ÕÅ—P>ü&˯H­RÒTÿu°X–bA'èäÝÄsïÂâ¾]oOϧ*p®ŸËðnyàêçŸz ª´ë](pág–jª¦ÑAB‚iˆ ò5Ä£+°ãýoÇÔ[¯u8T5SË7‚oÒxá¦2S)%#‘IÐiḸ[!Ë{±÷êŸ.½×¼æž9b e®•[îj…AaþKFå…WΚnE‡ êud 8õî›s qkMDTTIM]:+¬¯%KHŠ™bòF­ ”)@À"Ø3p ‡c¸!‡=z¼O\±"º tpH‰$³<È©*ÄéFò¸yc O",²6€Ø ­˜±X·P­iÓ°ü`üú@W`â­Ì¤ôê>Õ*ä5>i㨆åh¸ÒLèÆ¡Ò;Ò]#$V!•]bpŒô­¦E¨Ízż0ÔRbâ¥Ò’LRZø"’H…)Žš3N¾$‚œhXÜêvô¥”'¸E iéÈ'­)ÑxÊlüu¦ÍT¨ÉTP Ž"‚àiçñøåž®¦ªAãòdë©ÚÑFÉãNr å·ëOõ~ÎŽíçjIÇœÅGŽx·žZ,E4¬Õ‚:9¦•ZÕFøä’¢¦¾¢ªJvXtÆžf‚O¨5ôã:³Þ*àŒôD;ûºhê±”3fÛ®:ËàÆˆÚ ×¸µS‰iê2µu¿iO€´Å…4r%ì<“}SÞÊÓ½"¸¼ñT z®,çʬ&¿ÖXß¼\eÕdóVÒÖV2§éÇÑÔâ]ÐKXîŒñÁSMzä{îµ¥Ez/® §ªÒaº;˲kê·OÜTTy³y< Ò¨Zd‚غTŽ4#·Œ‘f•‰Ùfãp°ÆôôéU½»LÀ€xõpÿWøMFÐÛû2‡nãEñLí~K“Äãq¢o·4X¼di ÕXêz¶¦©©ÍÔWÈó¶–á Q5F·ó¦'çÔ³°Eà¨ãZƒÃœŸ#ŒÛc Œ“ngöVR m|ÐWE«peªh(Tâm ds1X’³î|Æ&vQûnKàÄÊ8ס0 Ë/HMÐaÚ;‡)ŒÄá¢É-M}$UG+SÉC%4 ެ¥ðÕQ§Ÿ)[7B¨]Ž‘ªÅDš^2TŠ‘åÕ%uB¡pOF7gevþÜÄasÙ(ij·"àãþŠlueN6:É]ˆ%¹¨Xe•Þ,‹;D£üë\œÂT•´»h®kоÚÝ|, £ðtë²7>+aíï⃽¥Ê¬y:lÆ_•¹)«±†¢Jt S´øªl†fie“Hä eb\XÛÚI$r=°p8št–þúH¢{q!F4§íë6ÙÀï®ÿÌÕvöà.)¾þ*·I -³‚4 Oƒ¢QRB!Æã18èh"Br ©².lÜvKa4iIM(qŸ³=r²nLŒÓLξ\iüúØÃã®É¡ë.´ÛmT“Ôæ£%!u§ÇV°1SAL´ÐÕÈôôèt¨g‰œ’Ü»—"¬+0Z’z-æ‹ù^å!uÊô5E’òÖ¤õ“=|²-]bÓd¦E†¨Žýý¤ÆÆÂÞ€RÅO‘·Òþ!^# Œ²~%P:u£ÜRÍÏKœQ£WRͼ±E,+KI3Ê E)IoóqÀÅWÔËΡ¦lh ž’*—“==ëÛ•[O‰ «d“'Û¤ÏL)Ì•yQÿ¿‡E…yÈK_Ðü§´²©u¦Ž”Åp‘8qÑp¡ïì=mm[eå¨Z*…–:Z”/ÆRTV,Õ¥fŠ–”pºË3•eTP|dóm²¹.‰Ÿõ|º;‚þÓHÕ'ø:q{¾fÈÍ´÷6 ’¦–J:øã¥žŸ"¹!Š "ÂTA3ÑÔÇ Õ9F– Úã™]@êÍipbÐÑf½/ŠòÝš‹ZSåþ¯õ~Á†–ž¢ª—Ç‘ªæ4‰åÛ²OWE}f#Ȩ®iä© 4îì°Ñ*†.ήOáLŒQÐÒšÁ$L±Ç\kã±ôر5o==*= |© ¨JzªÉ^«Ò /SI±$‘¹a¤ì–xÈ Zž—Àu²¹øzUìMŸŠÉSUÑNõtõU?KA¢ò-é&µ™߸tòýÙY\ÌÑ!Y.ÇU“mibô¦|úWu»½¡ 5êfC`  š<|ÙYÓ(L&ÔSÃOöïEOCOWKOà–î4F?Q!…Ä–¸"âž±*ŸótQ%Ý´ÌÏ4cQãÃü ô èÍïØ½[ºW]™ƒaç2øØN/RäðõUí+Ua!¥¦jU¢•©‹32Ï<ŒìUɸۗ·kë9RQ:ó&Ë·_Äf…r¹ò¯ÿ/Vû„À×Î!¯ûRb-¦ÂåíõV›ý=Ë!ƒG¬ OQ¹·‘âÑP§Ë¡÷mÓ­$rL…NµäصƞHýqÇ·ã§8ôQq'ˆÚ‚šu“qo ·„Ôe³8Ü\*­ªJêújeM14·c,‹ké?ƒÀ÷bÈ¿Ú u¸’G®˜Iüº@Ôw?[GA5jïm½$Q‰“ReiZÒÅ »¢¨'• Š,EÉeàßß–æÍ]Iq§ý_êÿV\6—, f |úÖóùŠü’š§íÜ–ÏÝùLFBœçq-š´`ã¥þ)‰¥Ÿ‘X ”ýÜ´DSÌ€ÍG5PeI„„9—scáÅlN†lýI<™ËÖj­&å§UÔ8ÿ.ªWxn¼T;‡=…Ÿq$&¾‚¼nzIÔ×HùZŠmÅ>£I’™0﨧í- 7«ó9…Ì@Y„ 05ϯCx™"uŽÜúãË£K´÷Îå‚»eÁ’ÚÕ§#Ø”u;BޝK¶håƒ Ž¦ŸlWÐUÔš<~G3·p¹lŽ>Y^*ª£ö“ý¸xˆ:ä@—.Á8t,n~™EkÒ‡YM-V*vÆ®otõ¶yñØÜð¦†L>a²»•ö½'ßÇ”‡Á."Ji#¬43<*”Òê+é—Ù<Ó[ÏÁ‰9ÇTÜT ©ãåÒž(»2jÚÍ׎Z,$™–ßU9‡ÇUììÝV:!•Åäð•Sm˜ú寥]1ÉY2¤R,ó¬It“DXAC«ý_êáþN¡„Ä…¦QÃü!°ý›¼)7T8 —º²³åhj7¯Â´ÔžLÞÉ×áêr±QÐQÐÅ…ûE¤x5ÔSM‰WíjÀF„?Ëé$Kj]¼EÔ8g¤Wsõ®Îù'·&Æghhv—h`pÙŒv)ƒÇÒÃŽÍSÇ--AÈä(ó³âóòKŽ’x$!–GÐ]±s½ÎÙvä’¿—ùHèÌ<‘k¹jžÒ!â¨útF±ÿ{'£ö½ec¤{ƒh³æ†oã[wmǤš(å-[v¦‹V£×á¬OI”~/-mœÓ¶îŒ¡¤ÒçÖŸä=Cû§-ïlÄýx~´Çù:‡YÜø.ÈÁavÎÚ‡5‰ÇÂðãæzo e2°$Tð.•ia†¡MþÚ(#§ܱ3ƒ©2§= ¬Îñ6qêÚþö}Ç|ÞrÇŠÊçéêê1xæÛ4µÔÑêšš*_.6†ln=¤Žynf­™ Y“Ö…ËIš+€ ’§¤ÓFtÔ/[pmýÿS¹6æ"¢±©âÈÕc ­þE;J”T"´Uèñ!-Ñê¾>»¯u‘ëÞRª`­{Æ(i]8‚7qäÈ2±‡é–-î½Ö8äoÛò] ¤+¤¨ÓŹü{÷׺™€T½TŠ„©ôFßSrDü.…õõ#IúûðÇ^ë/žÂIVY u :€ÄFEʕפõ ÈäûµO—^©#¬cé™ã—Ät¤ˆæ0ÀªÎaûw‘Ëv¸ýLxüûr)˜zÙ$ñ龪ê#©‚:9å§emNc¦Wœ5MüM$šª"‚œ]˜4©úØYH[ƒˆ˜:TŒôäLu-8W¢‘Ü]ÃÇuÌË6J²–Jz:H´Œm,scH •õßk,G 03¼„–/&¯AnhÂOF±LPp=V7böíM%5fnZÊÈ@ÞTf©…ØG¦Z“mDÉ Q@?¶ O®‹¢’ª¤Šž#‰uTò%œH,èãK¦½A¹#… fÉèeøwÀÊŽ“yíÓ_´·EáÚ’U=Ôã)é*sIIWYŒG<52çaÇeb§8Ô‘ÅM•‚“7ö‘`·•‹ò¯OÜ]\E¨ø ÕôϘùŽ™ÎjNÍÆl 2ÑVÉWâÃÈÔ¯ƒZj‰áŠƒ#ÌŒ¤ï8§XØTEô ¨VÔ°Çná?çéEœ²\´eí™O̲zð“­5nJ3O$õ µPæ«DôóÖ¬rÑÃ^FžI⊕*C«<б`†Ì €µé}EQjCèƒl â颃®ë·Ee5.ål "å&¤Ž”CSQM=@D§_:SG¥ªoNÛl‘ï%%‘{«ÕóüOø»·¶O^Qç29ªíÁ&^O]Š4½YQ§EGGL…ˆª<~Ãñ¯Øb^^<ÄßWs]({?_õ–\oPrëýe€ùŸ£Š®Åí ? %L©tôùjŠ:¸áZЦ«®&1 :Å[H„mU%–m{`³µŽ%Æ:îWãqºk‚xž²e7H¦–¡! M,ÑTB㨑e7š7«’†JèX²E?«Ò8Ÿø”d“Ñké$jwL±SÕVÖÍWC‹¥–’¢_)­£¤ªªjT€ÁŽÕF‘¸5 “$©6r /¶RßL •é’ú*ÃT‡óOänûû•ÛÛJ­&ÈÓ•¡¨–Žž®Š†"ˆ#)¥–ª¢ܽÀR¬Ê.B‹1Ûm‘"ŠtGròHåP]Wž×¸ªd“sg2ÙŠ™ôÇ㩪™©¼EVYa†‘[íüJH)ûd–7ffö´m|4¿ÉÓq´Q6©%£zt<õÎßžšT…i$_+븖ž!UÒÁvY½Jpmõ>Êov…Òh”Ïùú:³Ü£g£iþoŸWñ—´÷Ž|f'r×eò»b™)(ECC¨¦¤¯.²¬Ê·Há)ª$’™úU‡ÍÃlÒ²µ= ,•ŸKqXZÃŽ©«¦ËÇ%}V%EAXÔþI ±´òÕS¶)1TW!©ÐõRT7Š5ò5¤i@ˆòéfGeHΪô|§N€ŸBNÃà  ö‚:ü‘вJ˜¿ÜzVùá`*§ÜÌøê ¦šFŽX£ñ¼RH¢8årçHa:ô–ôB¤ŸKÀÐEA+&H¡ÈU†‚ei+`«b±øà‘ãzƨǼ ¦Hª%uf.#U ³¨" Zމާ‡L­Þ£¨¹\Éw9`hâŸÏ®÷—ËöS©²ôðb+bÜÏ––9±^8ݲ1F–Õ/55æ*êtÃϨ[ߡ栖î̽àc‡ùúO(9¾X‘+¯Ÿùº×/±>@vÞØjÃ]¹3¹5ÊÑ×äq9=Á ³ÖabË%v\6R)bƒ¥•ç¨LË ±&¥e ¿9O}pÐLæ”ÿ?R¥¿![Ål’@©¥z)›ëº÷F?uäv=d’å1{‡(ãü"©érÛG3µŽêå—6³ÓRBZ²‘jêÄܪ¨P=šÁ;Ë`ÍO/õW ôûd6×%V,}EÆïʉ ¤§ÚõŸÞšeFs#—–LÆáÇI !.¢‡9SŠ®‚cŒšj†ƒËQTÊÎArÛà‡ÏJ4…W ¤`zÿ«ý^aFgo.áz­ÝIž‚´ÛÑ)©g–§pypù8cÜ8Ìõ\t´1¬tz'Ž9âGšYWöÕ£c$Hgš8Þ”ãÓöNÚ9 þ]YWÇݳìýç€ß»î=±ÃìüXÀlê}-FÝ—5R"YÛÏËËSàép õÒ<¦ž:“(D2}Ì Ý-§¸”–ŠÇˆáв+¸íu,„’:lî>èÀPõfèÂa7 ^Õ‹%º³ ŒÁPl³]¹^9²Ut¯I˜šHÖª·=^¡ëf…ª¨èé­ Züe¥r>^·²t™êuçÒwÜþ«TiQNòé/Ó›‹¾ºƒsõÖáÎb`¦£ÙuT”Xšéîiéå›ìéåÀ.§?4™Š•©¨”"£ð¯‘YY™I÷}½cq< O¯F–·ž%·=J>Ÿ©vU×û:fãÂm¬uVÝÅK¹óYi#ÈnŒT4ÔtòB˜ŒÄxÌOð¬VB¶H’6z9ĆPTÁ¸ZiÿÐóþ®=TGkP ž”[g+µŽßÁÉKG³§þ OE—§Çö&i6ý~'=CS=[ÐÔäfÇQÓäÍE+}Ë ´õšÊ™YÉÔÈ·g¹Nƒöÿ›ùôú˜¡+5z‡[ò&§på2ûËm×àë2çrÔÁ½(³”ݬ6Cldi£¤‚J %SâdZ::8ÞEL.*ÙC4¨¬.E’Þuž ùù«ý^LÝiÜ kV@AâH4Ó|>øöÙzõÖÛ°u…6õ¨y<™È¼;³9:v×%¦yjð8ºùÙȸ(ŒpÃ]’­LjÕÉS5©)r«6Ìë¤ Ü­ügÖ\]  ¦«‘ž˜¥Ícª2¹÷©†z¨ª«g\E$4IŽ¥¥ÄÊÒWÖùM<ªj:)s<ÕÜ ûFÊ#©w"½+…/ Ä õ WbV(è&ÇmúŠšZjŠziéé¡–ºª“'ÒœM<‡÷)©äºÄˆŠÎœ]WÙlûŠDh’òèCaeq2ë–S†:rR¦·É®>ªš¦ºzJ Ž3E]n?',1d1‚²jcã–w­uû­z½$êÕ{{/¹Ü%‘O‡yèEQÞZçÿ‰;c)¶ñÙ]Îjki覬¨¦Ç˜ç¢§zØÄÁ%|u ô©®‰ØÆ†6b[ÔB1{]ÎÂнkqÜßk…"ŒQ½GVÅRàiñØjjÅá!ž®\YyªÚ)j)î¯I[0£…£©s/)?ŒAP *±Ò£«hí!ðFxußM%õÄ“K’O>’µùH•¢zšúYæJiibHb†žHÉ$+ÓÉ$S*9Y@2¡¾ ® ንÇIBȤ¶f¿6¤¨–˜TTMâo¶‚¢_¹r,ð”³Ò8*bb—Gý@ŸnÇ)&ƒZ‘Ž:JîÄv¾ÚÜÛ¢Ò¶öۯɺ‰Vy£¥’8¾êD¦ UÙ€hÑÈQ½¨ˆÖDÆ+ÒI¤+4óë[½ñ’©ûÜÎáÉ‘_¸³µu9:úÊ• "š¥ÚWu –fÔ¨° ãØ¸P*QqNˆîذ!†õx¬¦Aë…S™%:š6IùҠƯºé[ 7?è¹cMzz)XÌ’i‘±ëÑØ[†¶\… {=Ê"¬Q%¥m²ƒ¥ ²‹ÛÓùöMst²3 XÙ(”cZ›«—øÅö †.Zè>êOš¬5‘d†%ÔÊ"ܹ[/7üaˤÑq…l zô?µ†xíõ#± W[•W[Rb±œþÛ¬Yvþ㮎š–u‰l6V°ÅKöÒ#êXå2ˆÂ-ô:¥OÓØwwå¡På ÿ%z[·ï-3iV˜óãлœZH^C66Œê Z‚†L{ã|4o*¡­¨ôÔÈC±g•UÁc¤¯²T¶!4æ½15Ë4’*×@?Ï×ìë<”ð%<“+ãã yÍ3Ç妊š§ÁKà-_/\TE:Ý78-ô}UÖ-r:d¹Á­MzmjIn¨+¥‚¡ ZHl$*”.јVƒ\ËK+Ç6rÀ6 ûN‘Á™A5ëLÌiÄ‘sa¿Ê%ŠÒ©Ú8üÉTÕqQ–S«L€­T4ï+)p·`.@·ÚÐÔ ž8¬c Õ8鲦‡ÐqõFXL° £ CLŠ]ž¡*|pưÀà…I¿h’º›ßo20+¤é==¶>$luƒ\—¢‘Û€Ý×Ôd°?g’ˉ©"Ëáçg‚)eSQ“jšš¢DEM¥ïèm$€Rö1C7Ô#äþÞ…+Ì2ý*Û¹:‡˜ê¹·gÃó ’QPÈ“MWžaK”8ŠZW‹‘GN)rµ ðÕS=ÿÍËúG’Fck•ðÝï-”¤Zˆ¯V€Ù_®™x£ÏÖ½.ÁÀÖuNKoìÍ­ 2ÁAY”š,þ7 ÕÐWø‚ÔRK \´”²Ç˜Éäi)P˜§ª(¼‹ª65³Ýçu^=3siJè»åÐ…×û?fäå]û6gtf“iÑ@2{j}»[XØþâ8òøÚÜåpíøêñÒ♪$ ©RciÊÅÝíd“BzjÖ3dªÿ«=5õWwçsUSàÙ28¼5.ðÂíj|µËᩞº®º_½­¢¬Ÿ›ÈeiLòèg…ÖIØ[ÌŽr°¦C§ìè=t×RNÇGhèrßû&ƒichó«OCY‰Á ŽWj2T¹F¨¢› h*+S'ESI÷_q$ˆÊÄòµÀ‘M%˸M¡ÿ'K­4†ACÓwQuNosl=÷Úyœª©­ÍÓÓlá6/EŒ\%5>**l¥FNlTmQI6/,a“L•¨m*î}±4ò$†-ꇇFX Úxt oíàvËSÉSqÉ´ñø­µ=võ¬¬Ìîí\³,´¯KK‹ûŒ¤ô4« Eà ´ñFÇJ¡ú»º©2x`ÒV™ÁíS^–[1³i Ä¦ûÅîYzÛtäip™\•º=éFÞuÇE˜ÉRT¾/QŸmOHó; ÒºE͉¯­&š¢4©û8ôee;þ0<¿èWÍÒA»SsPeÆ`vØÈe+6Å¥|^b,–ίÇÑŠŠ]Ý”¢€SRa¡´4‘¸Jy'ê7dBÚöÐRE&£ý^ŸêþkDÑÈÀÂ)Ò¹`Çž¯©Ú[¸ÕOŠÉ¤¸œ~?(­©ÂîJ” ‰ÇaÓÏE™¡§œGËtÑI]Iµ#ÊÇ4‹’1‡Ÿ5‡™(â’SJ4º:?@A¿Þ[išUÇD¯,m»¬Oo4`>H4ÏKzÍ©›ÚY8*>:î¹Ö–¶.Rޝ'n©ðøù)©vüìM.cTª<¾"gRƒXÒÜI[9Á9Xnª—ÙëÔ¼òåÓMnXÄ8q§ø:¸ŸŽÝ‡Êl,}va2ž§ b|¢±òæj‘üÒÓy›ÏR´4Ú¼’°õ±[õ2îËt·)Wû)鎣}γ¸xÝo³ý^ÿбzl’®¯Ôª¤ +1cèb‚÷åx$_þ*¤Šú~¦­EUPÌsbm ·ä¢B-kþ=Ö‰éÒ€{J jÀúB¸K[Qkéý±6'ÛDäÐc«§§X*–1æ/}ƒÉ¹:Ki-`©ýGÔð}º_.µAü=+ñr%5-TÒ-äž/’¯*f M0T§'¥¯ý}Þ§ˆëÔ_á饪 ޝeH“JF·{*/ Mì7°¿¿ô©åÔ…›E‰MÃè, ȵى÷ªñê¤Ô¯9bª¦÷ ÚàzE¬4rE¾ƒŸ~§^½HŽag¢Þ@rIF${¯[êJT’…¬WòÄñàž ~ëÝezÊ¥F(`uüËÓ¥Wè5È,U‰ž@öâ€A{€é_>P­L4ÔTÑEQ/ä}rS*€·j€!•y·+›I°öüncpúŽ‘×ªÈèßÇ!=tu¹¼,ë:S.;n=dÂm ÄS ¨Wª§iÅRŽSˆ7DXô~:ñë~&‘ƒªùŸÛWiWÖa¡¯Èn<úSÏ>C¸~û”Ô”þèôb©©[Õ5bPÚƒ4ÑZÏeãÚ{Û¢àÕùt®Ù‹4ÇUš›?Ù»’²ó6×™*éj2ùêÙ!v•Ï†Ž™E bZÖM ±*„  { ÞL©i^áѤ-q* } ¯³ëv’bh¦¡¯«Á¼*Âjc:ÕÕÕxª ,Ë?šF‹ž}`ß`-Öv}AªGR^Çfc@ PúôÆíL®_´'Û[Ê¢LN;Jõ”Ò`ê<õ­ãš|¬xü¦D¬rI"[LB+Ù d1D:ùú<´…¾¤™*W§®Ó®ÚX:¹ð{goÑå÷~* luúˆIASÏ«"D)ä›%M>5|³j‰Òé Οeó2\«„"[É[u,<úƒ²±8ýË[‰ÆUÕ£›2˜¼nS ¬jµ§—2&û9JךjØm ÑÆHcoaMÂÿÁ “«&¾yèq¶Â>›ê%Aû:1õ=yÔÛO)MšØÇt?ܼPÓeié*«`Jº–•qóÒAVzgEò:’MZG«Ý6û1rÞ$œz¥þùolDQq?gRz6¯ö†á§ÜmQ­©©¯þ„Û‘ Ê\M,µñ4´ôp⦩”ºã…<²Ø²2Æê¾ë»Ü¥šµZÏä?Ãü¿Õèe·º\ÃõLiAþÇ[*õ^¨Ø}u€Ûÿ`Òn´ë+é©Ã@j²:”VAP<ã2”ý¦APtX—¶ÏÛC<ÉIXô ÞÛŸ$14éã!†‰5/Ѝ(1’J$¨ªŠÊË竈™iè©Þ†™#·‚1¨3+ 1âË¥«æz&ÒPxô”ÜxÆ‘¡†¦:‰`–ЦVv¨Žz°Œþ:È Ó¢,ßnDjŒ)m½¯ú…µéÊTœc ®³ô´ôÕPTÀ¾e4RIL22TÐÅM<µ0ÒU•…à‰%_Jƈ=<±coj¥&Ä„6ÓÈÐ:9bàxtc:ie(´cûz5”ðTšÓ–ž¢c ©b­igZ××O]NÔ*bŽ—±ˆ%¤“[;UÒ¬Ä> gq§Ï«:ùõ?îT´ÕT´²ÐF#ZÖm5L RŒ“×Ò™*eŽ)†¯¶a§’}*6xô¹Àó¯II ¡©ž:d§xÄu—ZÑ*¤Å'h‘"H©[Ç•ˆ…P@HÑ?P>£ <ú^hÕž›d§žž¦Š±+älzVÈ**~ÐxÕtG÷Ðr²’ ôD‚xçÛòDU=6ò $5ëÓ!ªÔä)^¢¦Jm "PÅAöéVÞ8^#$ÑUG(³6»5ÍÇø6@¡õé.¶]çA˜ÂQaqxÙ·-eT4ôÑÉTòBE ±Éã2´D%µ("êlÕm¤hä¤àÿ«Ë«ÝÅX×NXäׇF¢þbÖì,¼tt¹Œ~䊓ßíÚè** É“öíÂäiÒ¢(«*jàP‚Ëå.ÇéìG´óží²^¬K©¬ýkÀþß@:oœ“c¼Û—X•g>tëÿѲ œ-¤’@ØrÏ~´þGæþÕtWÓ1‚®šGu¸ÓrÆäý \[êútþ¯´Ý9ª‹N§Ód]ldD[ñ¨±êWè¶_É_o§Ëªê=*1y•Ú¥â*xÑäW¥ØÛÃÔ,ÅÞ×üéÿ[c†:÷ˆþ½(“0e¦Z¡ç©”úJ\O¥#¸QøÞ뎽­ýzÍ b4››ª¥ØÛ©7çߺ©®zqŽ¡B‚Ä Õb@þ‹øü 9§ZÒÇË©)U©œ¢‘qɵÉ_õ_€?ëÿ¶u(G¶:È%±©ÊR÷"öµÈ·ÐÿOéí¶Ç½×8«XÕõ!Sm#êÌyà­ÿÞ½ëòëÝH5 kÈžOÔIfÐÂ÷f¿ãëþ÷íèú÷©êfM3G%¦C …´*»B"¼ºëo¨÷~š™ü5 åÇ¢sò|`úçdnLîO;µ°¹3Ž«–„W=M9’f‡Ã,UTõE˜þÛ5±ao¨öÛ¸AP½>šn‚ \֦݉‹ÍöWcTîj̈ÍI>]çÇb6“Iœyü³iA’š«öñÔ°Ï*êó’4ÜÃÙl÷D!rqÑÝ­±¨ˆ&z2qWµ7Ó“ÅÇÊHq53ÒѬ…5}HF¤ANþŠR”„°b,°®éº•†FC¡–ײJÚ]WÏ£ÃÛ²åq»¬ú—nPâ^£)²3{£!—§Ò±qK¥ˆe*ë&D‡è*X+¬¡:A î=]áî¦r”×ò§åçùõ(&Ì ·ˆßAÑ;Ú›+MÜØìz®KÖc°¯SɦB‚ªM@fÈȳ Ç;Ó$£x‘ƒªÆ@ö›ëH‘ª{kÑœ[Bé§¿ [ ‘ÌCŠ›{ÑQPÑÔc²ÔIQ L(iü…%ÉÖCÄ +Ülåc`Êou¸˜ÈƒGÄGI¬mg†þ@b&*ç¥ÍwmìÝ‹‰ÉÅæ„lLQáêåjy¤—)-3S×ährMKFja—J)ò´AuékdPm¦{Ÿ•‰®óÁüºî»Ò[[Gj€k"”é×]w¹û2D«‡%_5ed‘ÁM¶¦ZÉ’ž¶±(ÔÈòRÅIt‘€ ä“ú1öaw¸Gµ#u8c¢Í›e›u™žXëšð=lñã‘é-¶7V.k'UWUQµ£š…¤Ž“>)%YggƒC43„ÒÙ__!E½Çßî7Wë*Btjþ]ÞÚÊÂÖKb@`:¶Ð*²Xú‰'û¨`¢©«‘ÄSÉUU=LÕxú¿¶‘㸈1ò£¨%’0£QâIµ•žÚ-kCN¢›òËs&„ž¹iž t“5EýÜ^Ц%l„cí¥x×Ík,´Ÿpü‹©ŠCS{T‡‰aŽ’·“y˜)ªRŽE§’JJ„?kQ SK%CG Ï;;ÈÂZ‰J†Ó¤pG·†NMË+¦½!òû2y)©ÍuNA †H#Xg¦f˜¤˜QOui™lc ·æëM¤ ¹#=•;*1ëÄÑRTã†hüóUÔµ+,ŸxD±ˆ Å;YõH\}Ú&%Cù«ý_ànæ?Ñ:5ê§;ïhcvÒܹêúZºYN*†šCÍ]<α‡¸B"j$\[ýàeÝ"¤ŒOOóè7úÐÊ[Ãò=T¶q²r½]\~Z‰˜[÷1I7'%jä(ã=\™es+Ÿ"-R¬d¨6Ac{Øœöme$êI=930jÓ´žŸeUF5r<ÒÅ-<³IMO$) ªñʾFЇ†V_quÕ{óí(lÒ½P‚XiNšê`DS®“š‰mJ†‘! ñ@j Dþ‰ IZìBµ¿'ÝqçPz_>î5ëâ“7’¦ ¯¬Ž²²ÍX¬ŸÊ7‰L’D©2#ƒovÔÇÔŽ·E´ÇX–ž%¥i!‚H©ç Y'“ÌÈ“ÍV2¯†T½£B@ú~š Ž+Ž­¡}:eÎ-< IA‚¥!’‘ðX:ÇSöÎ!@ÀFTÉ¿´4­øàpãÒ5´#®§ªŠlu)ž¤<µxè¨ájV…éçÉV êIQu­}–A+%Ê0bÊlà„®*z§¨ÖΖ‚¬8‰¤ÀÓš˜Þ²¿°ÓUet2Ã_u@ý×ÚÉLL`®¢¦KöóZº6µ\ô¥nè@cމ¦ýø÷+× ÄÙz ]‰€j¬FÖÀ$TX¼½iÊc„¸le*¸¨©Š¢ª’˜Õ¤ò,ˆnÞ6QoæB±µz_³˜ëSN‹·puîæÝ´¹mÁ¸°é²·-F߯àŸ¢Ž¢–«+-{dh2™ztû¨Zª¿K. åãQLK1ò…N`·³ òz%ºå¿ª á3Ä|ýz\äv.ÌÇEº³x©ªwE>g,Û_ømZ…¯¡ÈÒEC¸i+#šFÆ?Ž©î¬0[¨Ò¾Ì~½70tpévYV«W¨é×}íŒÌ›3cdv\TŃ¢–ŽvÇÊ´”4Uµ%e=R¹–º9#ˆ´’± ×,9˹ yͱ\þ]­¶æ’È Çjvþè£;6”dh+÷¦:г!˜ÉSÒbZX¶…iáª'¤¤¨cæ$Ô7é×Ï·…ÜÄöÔõ«ýYG$0ÛžÔ“û:‘´¦Ãçióµy¼›Ì\ÌÛ/w©5tØ÷ƒP©Ê#›j °yG˜JT¥ÃÒ‰Pz¹ìÄZ‘ûë°êv(Êã³54Õ}åWP5ŠlÖݧÉÊ<™]J3M2HÑ4RêÓª‚.n©vòѳ?TúÈ”éO.}¹Ü;&ŸIשECO¶óøl¦Õõ£ûÁ¹*j0µêÈåÇÕN”ð%l-#ÔÈRC"dîC·;4‹t—-iѼªa Ž' ûaÒáªöimü.ƒO“«—…ÂÕchvÆwø†ª­’i§.j%ÄÖ¸óºiû•0[u¾ŽàÉødPŸ¥ʯÚÙ>ílN£)µ7Ûãય«ÆùÆ î±ô¸,ŽÞ†³CÒÆE<MCòƒ®PKdY4Ì%‘ 3Òµu‡€Ç_ÿÒµÁN¤€ªÜú€b8%nI6°Ò>ÕtWÔ Š4cb Em`—ÀñõñïZWӭצŒ,l‡Ä—o r†Ä·7°úÄaô÷à)NµÔ,•%MiG0Dv–¥Èÿ9PÀþÓõOi·faøöðÓJÓ¯uŒWMÛÂFT(–úÙäò’/ø¸\ûm¾]{©”ù„mÀ`}>šþ€z¯Ë¯t¡¥¯\²€åt]†«’AÓM—óíåŠÓ­êjtíb¶«ßHµ0RJ€X›‡÷Jµþ¥ÇV¨@çм›ƒk}Mî4ûe½z×q?\„ˆxhðo¦êįôúÏ»€)^·Ýü=G’à÷":ê1Í7‰QoeSb p9ü›{¸¥*:ñàzo©¡i#2Rý¼¨Šu5UH¬§Œ¨-¨+>’Äþ>„}}ú¸=1qB‚£Q×óÝRT×c¶ôùÌ ”澜ä1˜Ìe9 VJõtÓ´b $*ÆQN±k›içnÒÏF[pUu`¸ÇUì]³Ùpí¡Žª©ÉÍ2ÐÑã^dަ®µfWjHVœÈ‘Í<9ÐϨe ¾>L§¡eºÿƺpiЛRûë Ù†£±Û)UA™ÅD‡?‘J„¨¨:MUmMuOš'TÕft¡+›ûŽw ˜—NæEQNSî’’Id“ûƒÒü\{21•U c¤LYªIëº*4§w£f‚æ’9%–¯ågŠ’I}b¢z ŽÅ‰H›Ž}Ô¿ëJhkçÖaLôQÅ[ ÇÈòÃUI$”8¸¢­û*§WeJˆã›î5ƺ£*§Ö]ˆú{W$uer¹lõŠŸUX«<‘}›ÓSAUH©R²ÓErÌ$I€ñÅR²h{…‘\\þx8qCÕI Ásת¶ÜòKtÇŠ„–¢œL‘ø£§˜šºN'1<¶¼ŠYùRH7e}?¥]]W‹pÑÂ1ùᚪ ªþÉj¡Žot/Y®1FjØhi×SƯ©U˜D,ÍP`ôFXÊôlðÝYüÛû—lö3ïü_SÐO‰Ý[{ Ö1c`J”Û´Õ‘n*ÚªšŠ+ l?‚7¨ËU#3ª $û n°„NkN—ÛÁ#:¸ë`ÏŽŸ¾:mN¸ÇlŒvÊÄÑâ·N%?½´:’jõ2CŒ«ÇSTçÔÇV?‡¬ùLnÕ·à·²ª[WWwÕ¦¹»µ¼FŽ# Š³ ëfGº:äfc¢jé$Ý=)šÛ¸Å××øD¸úâj÷ã”öãoÛ_q‹K¸S•ÇF²lÛœV—ž^©Y̸¼¬tp×MO “ÄÆ«.„9F«¶Õ‡ù ¬x"þåûÆÛrµƒq³5·•AçÇEVWB[q¨÷†é]0Š’–:¡:⦞™|ï"£‚ñ¼5³ý4;9’¿â©æ=,Y!@ÏIÂày1P-uŽ–¥ä¨âqOà"JØUêD,iH@‡‚ƒr@@e`¤¶GOWIê­ˆ¬r6<4QA Ç\byŒr;ªV>•ËR´ò™cî?p– WÝÒâ=$îëzÚ£¤¶_YIC^\ÖÕUÊÔ®`5^:jj¨Ñh´¥\#îR('¥³¬ ’ÒyiŒÆŒž]+‰°ÌznÈmØ@zIâÆGŽ(õô^V­¬˜Ë›þÃÞºß\£¦ ¦Wt"¨ žRÌa7aagúÿ‡×ëï}{¦é¨Ú@ÒHVB÷bímFI–{í}Iþ¾ö {í骿 }\Ækú_Æ>‚Ãü{©ëÝ&æÂÔ#–Œ–IP.ly&þŸKïØëߟPõϱ¦×-ÁSúMÍ€¹çýoo§Ã׺r‹"Äéak€$¯øþ¿áîÞ}{§8ò—1’Q\ í:‚@7¶’×ï>ôEz÷ˆ‰éÂ*ö$2°Ñ{Y¿</këÓªþÚ&‡‡^ñ*:2n¨£WU[ÑUˆ•™µp~Ÿë{qMz©5Ó¤®á‚’»Uxpð0§¨»UÅ’’HÕbõH~ÍéT(OëaoÉöèÈ­:ôÊ]ˆõ¬WÊ}ÁŒÛ›»÷E&s›’*¹18Mtþ|KnQáO-5o’®Lv*)âb¬í®_ò¤Ø³p&Õ^vä)§Pè·ìº<_dqûª¾Ž†£ç熛Y/I…|Ä53@VHóS˜¤MO0Xm&Äûn›ƒ¸dË©oµW‘%)À¦ç+ðXÁŽNÅ—pn Ó–¨§ÉVౕuTÕ;ªŠ8æ•ù,¾´ƒìã®K„Ñ®X¬¥J€LÚÈy߀èxŒ+H;©Ò÷kàptõ•›æšŠ%©£¨&B\ž:²¢7¨—:%=5]:é€LjTfA{…î®ÄŽV?‹íÿWú¿œ•´Yxvèò¥Ië•}^"¦‹!Ч ¬ÈÑËSKOIû\T¸¨"y`hþþ­"D@¤O×ÛG$‡SHiéÒ»·Š:ž‚½½²·´ÓWgâÆÅ/NB«pÔFWI¥©ˆSÓR¼äȲÄ@ áiX*¹ºÈ ˆéȦO§Aë“+:†C’:;>;çû›x&?9‰j»‰—ª©wŽžâj‹Ã DÂ2Í"Ë#Lå”kGÜÍv4Ø÷\¿án•zìŠl‘.f$GNh§ùzÙgâpû[ Cµñ»NsO´ë“‡RÓ‰ê«a¬L†>‡ ¦z§–š‡Ç÷UÅw‰Á ÄIcNZ°’ {ye‡õØçׇ@Þ`»·äDŸBE=-cä…tT^g––®’H¢©‚¢G¤ÕO)b2Ó’ðÂë:´Mõ¹j«)v%iN’"(ìX|úr)4 7Û¥}iŠœÃI4ðMM5pW¬¦2‡šQ‰=JŠ7s"üG6N0:áJi`–* óÍ+¼ÐÒ²Bõk´µUÉ©&4ÔÉÈžEŠÚ®Tܳý*uªã‡OÔ²/ÙèEaŠºZŠCÂ9j!HV¤ •â‰g»9m ¤“câB ”Ï[5ë,44¤f¨ëêYüWš6™I2x|®%A mHG¤AãÚwP½QÖ¢ƒ®éE=E,ÿo:K \ôµž=54qg…çZæi•–Sû‘9¸¸e*84ñ™sÕºkÃo*ôZ¾XõBo¾¨‰±ñ½mvÕÉ.j47•Ÿ)ñU¢¬ŸU8±P¬·çëìãdÜ™.Ö90¤òt–æ71œyõ]ÝÒ”Y-ïµéê ¡ƒ¹c®Ãdj&¬X.F³ž¤vF«5y£IKCþv©‹Çp?»‰e¶/«Ò½L<6RGŸB†;jÃÒòákr¸lÆ?–«}©Uµ*g8ÌÍv"*Ó3T;Ș½¹<ôrB—k@T.—â›È%’áÖu¢—£{9°=)ѯØ5™úú 6;rå%‡fT¥eM>6 b–J‰©i–L˜uUÓy'…ÕãºÀ¥‰UpU˜t5ºS<ÇH¢Uëþ~¬Wgõ+ƒ‡гÁ–û ­e3JÒý¾á¡™j Íc¡}iIS`2\ €,-ìòÊÞpVQ‘öÿÅôRÛ“Y,–šú‡Hë4~§1—•ê$¬¦’ªwDŠ:sP`«1´ñ³¼ÚPÍ¡^ê¬ð¹e ϲ~tæ¿–ö‹ýÎîáchb,kþsO>”X4ûeD$0ÿ/Uݸ+;c´³bL08 W‘' µMLD‘¦%CPÓ“ï”þèûõ'2_ÞGá`“$8 B„`‚ÙÔ‘Ë;ãeul.-K@ÇìôbzCd¥–¤©—U[I*4õ5ŒÂHíf$¯‘õ¿¼0ç.h›qÜ­¯#™„™Ùµ2éR”ûzŸ7¶W¶X¶šN8èîv¶ +hðûªŒ£<”_lÏä(‰>¤uµ½?×ý¾ÌýÖy³úÙíÎßq-ÈwHÂZðë¯íšÃv’Üb2Ç¢Á’t­é¦:\œj±Í$µ3MN+ >F‚Z£8´°ú…Šú˜\ñÎDÏ$oTUÈ$tam—¦:ˆ*ÖTSR5TÔ©W‹C¤r6iU˜/ñ¹übQb¥ãÚ2‹BK\öFtñé:í%T²U¶^±lZ¶–¢ XÅ<=£+*~ôVêNû>¯lé…AÒÝÝkò뫯£©r1y"©–©¥†$jd«†z_¹¬™u,¬±™ˆ)Qý«ûª;©$ŽÓÕÕˆ`´ê=*@|’ Xm4õ5ôQĵ-C4Ròˆô!ã–ó›.¢ŽIöæ¦:Zv`ã¦júO6Vš7ÇQU¬‚F‚¦ Ë§Š¨ôÔìˆí5]HÖÌͤ}º‘rWÝqN=+Ž^Ò ÿ«ý_êôBäé^¾8éŠÔºË&°ÌQ„ NÏ.„], ñ‚æK±úØ_\1N—C*¨# qõÛÍ !ŽVZÈ__3ùéV¾CâPóLaÆ*V4,Z®HfÙhµŠS§DÀB“ùôY‡ÅÚ¬^K1S‹ÝÙ¤‚*êjú 3µSþêÔŠ3M@꘹as«ëí"Û´,Ïùô´_Ut—¢¿úâ—nÒåãÜU{ƒ9º¡Â‰0Ë“ÅÙ5f6ÇÑc¢š9²‰P¤À€¬ŒƒR&ÞÞ³Þå‚_:Wü—HäÛÒçŒx= Ï›i7åVLµuTXÚÕ¸*÷=L8üek*IQP…ÆH›H-8g[€§›-ù‚iׇ¨èŠóa€Pªÿ.”´í·ê¨òØT£­wÅK]6S)0ÐmÚZuxMb) );éKNÒ¼Ä,r\´†¬rz+{Y""é SÙYÌ4GÁÅ=*Ñ®ßlZCª‚¢š¸U,òUó¥*¡/3r¤j ; é=íOC¦åÇUår˜ÍÇYˆÂà+×jà«Õ!«–²›!]­²T³E”x¹éêb•š2V7•@abHJK-|BiÓàµ=&# H)bÇÖÄ”ÑÏQ‚¯'VøØ±•3@R½¼‰ê–l…BÆÏÚ D¢ÃU€7ŠÆ ˜1¡é/ÖË „Ó¯ÿÔ·Aé$µÙXé`£IúXÚÂ÷P8ö§¢¾ºñ’¤¨[p–“ú¬ªÂ÷äŸëkŸ{ëÝep hô•Œ@b¬Ò²Ø‹sé…ý׿Õþ¯õ³ú‚¤X ÓÁ‹ý-c¦ßíýë={®2Â’±Vª­¨›‹­‰ ý5{ß^êS!kÜ‚ (%PÄê–¿×Þ¼º÷P%Ç#– ú—ƒoQà‘Çàû}>½Ón‹ºZÖô­˜~ cÇ»¼zMH'¤g[4€Ú€Cõ`u¨ HñïÀN© ¶JõÚå‹Xé K”W`ÜuãŸzÁëz@ÀêaÉC$äy †!VU>;1 t`µîæÿO~¥8uºS¤/håé趆r·#»ò[rX±µfñH–b†*zp•Ò«*¨KérÖØxÞ…3åÕ–µáíW·å6C²>@Éb¤Ímœ&^ž*ï½JtÇÒáé*å'ø$sΘ暲f‘ÕÄO4îÖ@°c{¾H­¤yžTèIµÁ-̉hIÇIì>~§sælô{ssz–5^¯!—JL‰Ÿ´´1ÎñPSÕÁM šãršµ)þãK«•oEjשcnÛnqÑ<‡J ^"¯pçvæàÝ4]õ…“/W_‘ÜQRädÜ”&ÓCYKMW->ÑNCþìÎÿTHn'–H¼$4':í›K Ä’¥iN•yšªg­¦¢ªÝ¹j‰·EU¬RP&i¤6û©#‚7IiÖD’Qá`òñ«ÙÀ±`¿©çÐòÞéB,,8pé¤e°»bL–*Üt›·xcm-p@¡ÏŽì’4uScåHiš­ÒXÚz†qÒêœS¬ðTÏV4¥;ŒA##ÐŤ–eÖ(ôèÐÏQÞjõãÖ S†-\fš‚$DªZ¸Èc0Š–¡®’MO$µI(ë -‹1ß‹%IÕÇý_êÿUr§*¹é–X’jp^**–x#iÕê詪EœÖNaJ¹czɤò©˜ #еgfâqÕ•e–x  GÈIŽ:·?äíPj£««ªŽuð},KQFYQÒÊK¨ÒÊ-O t—Ä`üqÓÅDQÒÉ šj„•©Šý)€,&GDiexÞZsWLQ ¼Àˆ<Ü;øJ?35 Ç\ë4;ýËÒK*ÏKJdó8ÕnFHD̡͈ È ^J9]\`wt鯗œèé’%¢¢†*Xƒ<0GC"G$-®G§‰YãK6©[ÄOÐkú·j­Ã¦Úz("¤š†¶*i›\W, G_ ‚ÊfóN³U¬ë)ŽÌÚtßÔÁG·•µe`zfer§5è ¤ë Ìß"¶LcÔlºœ¼5Ä]Ín¦)V¢š¾ )fŠ®8é¥S}i©+ Dq¶o6úE³üeiÇìùtC¹ÙÏôúÕjuòôœù ·rûxn]ËŒËRäæÃã"ÍÔÔPHï%&×¥J ž.ž® ¿º-KYKT ãvñµ fÑ'¤ïPi‘Š7“m³2¨ŽHή u§fm¿N•ÑKU]…×­µë¦£e›^fi²˜Ü¦•kž<¼ñI'Ëd} ÃÑèÐC§w­xto,3ÇášÈz:£º7'Zlšg§¡¡¬‘1¢Ž(fòUOUSZÌÉ 1H’C0¨õn4{{î=Ÿ¶¼§{¼ÜÌ¢Ch&œHýµÿ7Nm;o[ªZL(¨q_Ïˆè ªÊî|ާ3½BýÎ`É:SľâiÝÊBµ™Ïãóïžáýç¹»žaÝ,g.Õ.¡RÇIPÌ  Ó$€})^§³’ím¥Ñmª>és¶¶Cc±0UÖÇЍbž(µ\£:j¸CÀúóþ>ñOzÝ&ši¤¹ZNúKQðtŠ SçëÐÆ ³G€BŸ—JŠLôÌŠ ªLâPª¡]Wè«qaìæ–F„$%‘•JÒ£I§u1šyŒTdbÒ Ÿ.Œ5r]{‘ÇVæ’HýWp \#ÒÒÜÚÞúM÷Ý÷=¶Ö.Wº–©šPâ„–Ÿ Bñòáéó”¶æeŽ:wW¢Mm¨ËI[XµRKS+®>ž2<'LsTÕµ24m Å%\TÉ¡„é¨hé˜8¦™¨ÔM¢M,lÒ†%ý|$Yo½V®:ÉY‰5sãêšxª–B“A,SÒ#³´ÉŽša%Y#hÌú”¨‹*PW¥0¶“WÏM“ÐVCŽ‘ñ‘ijÇ–ZÊ¢Ã)X ,‰ Ç$n Ú0n ®‘í,ë$€Ó…:v‚µ' svuNÏÌåŽæ°XŠŒí,´&†®*e–©˜FÑ†Ô óÉ KÂko}WöOôl8Œ×KRéâIèí[}ã2Øé)ip¹Eš|%`ªZš:èå†AVÐ$ë{ƒÀcôöck9„Ò¸ëRÜËtH÷ÅÝÇÖôJüRVg0ØÜF\ö!©é$Çî\³ "/_šœý¥ D ­Ñ®@7¥:ƒqjñé¿¥·¹|-ÑÉu¼¹Éðø,Å(¤®‡)]WG¹°Õ8Ül3Ý.‡ïª*²t”s™) …{iò×r‘«?ŸA[ÛZ}x|¾}+¶}oJü• {|e£mP&GÍG–l„Ç"Ò°lŒtÏ$ÍLЀZ]1ƒíS\ ‚tõµ²¹Uc'çÒ‹nm ³¸I6­=ê°‚\æz c&¼ž:´iŒ®®¥ªËKUUT8éÛÍ¢#ŽI%IrÊ$`  =*;PÒ¥€­:ÿÕ·K].=$FžHrÖ±àjGE}gDAý2Xh$5ñÏÐIÿ½û¯u„¯-pÏ ©½Æ«‚y?Aþøo¯uØR‹}:]%€Uí¤^ã÷zëB¾t;‚džê¤þl·ߟ~üºð>£¬,‹ptp°¸¿¨úm¨_úû÷ÛÖúã¢0·eÔÃêê9±æßðSvÕA@:÷Ùþ¯õ«å…Ôª¹1ê“›¢ñu¸ÒG¦÷¸ä=¸„œ‘׿>šj1ðÉ ãõ:j#IuÛ‹¨<Ž?Û{£Ç[ÔGIJý¿ þò”Œê¹PΙ¸!>€±µ¸öâ’GµÇ¤½u=< Á÷¥•C´*"—Z%ä,^9~ä(dk.ž-Ë}=ï‡Ö«@h3Ñhùº0¸m™9lEerNxS,’Ç‘/dѽC$ld>XdžÂà³+7´÷}õ xâtOÕ+ZÓ o-’ÞØ©¨òÛë4»{reIŠÇÒ¤”Øy±².U•¯€µjä)iê|ÆE™ïð {&“ÀðÔ¬tyÿc¡}¬·SSä?Õþn–˜þÌ®ÙØÙ(·®Ô/>OýÆc÷ÖF:YUG–<^O T^ ì4 …¼Â5%µâÀ¢ êhzZ_] 5#×ýŽ—˜üÓSb«  ?QörÑEšš–ª¡±‹-E4í,-S2ϨýÔ Hü’9›»øíœø¤5.„ö[}Ì¥dt#=?n¥XuÍ6â‚™2¿-«£š†’®“íñ„䯙VJš†’E“DKãW‚}^ÐY:ÜLd-Û^*šêîÍÊÇW×Ó¡Ãà–Ä}ýÚ;j:Ì^_rIW»!ËeèŸ!S=+(‘>ïË,Ô‰Bd#‡:£(õUÌ—"[«{d-©€9èûmñcÛï7™htàSòõÿ'[TÅ.>墢ó²É#ε4éW41*zzH㞦šGxaŠšâŽ)B´¥Iô£K;6·†("EPió=CW—"içžBX³>Y먾ڶ)E>BžcöÐ7íkgzaSx¦¨Š¦HŠŽ™£ÝRo¥uQ¤Žî‹¤Ô1¥GYÞ’©ê*/•|s;µ =^*­éjiã«£häT¥YêtÍG3ƒ¼²"6½@ÈO£LÔðëlÝ„£g§E¤Ç6†žAŠ G2¼pG*ä˜ÔGlÔêŒ_TNŽúogum_‡h¼)Òuv`ÕáÖU¦§§£§š Y+"@R:êÉ’š8£‚'½@xâ¦m1ˆÕÌJÒÒRü]#â1Ó`3¶zæ#3Ä‚!URÚTD±ù)bŽe’2ãG®! ÙGç›V¥œ9Ò?gK• .zç5H‚*µz’ø÷tUÆÅüB i.‹¥ ‚Ž©þåЂQÝK}mª×k?ÃÕ]ˆë?ÜÔÓ8‡Ã#<&:à)ªŒY7Gf¥)-= 2xà¹Ó¯à‡V ¯»%kê#±lõŠ™«?i(jETpÔI;‰&¨r°SÇ)ýŸòøT¹pº”fAqg zváºP*|ºÅOOM48Úú¸ç›!,3xQ'¦¤©Š)žqW=T™gŽŸI`òÇ#¨Ñôq©‹q©ŠQ-{ºÔ•ÐCÞ]µ–Úû?cSîÆL‹ÒîQ‹Ì‰Fšln>/;SMP³Õ&QApbŽF`è]ˆ'"c>dmFŸæÿWú°E%±I_!ÒÊ’jŠHÒbƥخ€¶R,¤ž¸Çd´Ü7ÝÊZÄñ(Nºâ‡€ÓœÓÿK&¸XZI„µ_?.ê_„­SÀ“­’˜yXýˆì€9>ókÛß»–ûÌé±V-ŒØ9 pÇøz%—w—­A1ykÓ®Vª¢ƒ¯²õ˜ÉVJš¨ž(€‘UKÛô v½‡>«{è÷³vxý±±Ÿ~%ñÀ$N‡Äm:9ªéÅôp•ÉcS_—ÙÑ3sUDg¨2AL’4i=`D–9KÌ# MLdJ—]WºÿCîeI®®+-Jô_„U*(zc3I  |õ‘M#¤µQõ‘Ô»c÷sQS£ºÜ‚5K>£gÄŒ2˜óþ¯—Oµ+×6¯ZŽ(j–¦µ&ÑRh H!©ŽYiâ–1%OÞxi–FXâv… -`Y²ÍC^¬ˆC ÔHe£ zÈÙய¯ò€**g†¢j´¨b³AH±Ö*Ô°ÒšÐK"«C~ñM†éæ>dg¦Éâ’Ib§&É5:Ð@q>Jèj’E™oSI"KB·p€S~5 ÷áªj^=Q5»…òêEFM¡›NHý˜šªš*£K É$Õ3"c‹‡‰%’˜Š•WB…ƒ9QfºûÓF¹]8éq´E,+ÓðÅ»ŒÇš†jzlEG)Eå©©’žé3Ç;©”É ÁYUƒúPÄéóõê‚5¸ïcEëÿÖ·‘‚‘`lly sΠlIB®'Ü9£< SC%ÿ¥¥¨’DkÔ¯ŸO5:†í˜îS'˜®z>Ü®>ŠTP®:óÜOco 10âáÉbiDuØ6ØYé&“UN?*òKW!ª (>9¥- ú€ôƒ«Ý;U³8:kÆŸäè«eñ·I$Œ€Õ48áçÖߺ[ÖÛTæ(ÖŽ:êúzZ:x ‡ÇuLþS5ÕQÈ•9™¤Ð¨t»©öåU–ûs–îâ*(lyúùôß8^‹;oÝðµœüò<º>µ zäF§”$Ú²UIZÉ J?¶’ÑI$.IóJÑ›#xÃKS‰eeà@ôê!},¨…r¤šúשî2 *Þlb†…qóGQ"›ÅD’E_ ªjQ”èñ+‰VQ‰†¡îÀ!«3gª³2¡ÒzwŽ»ÉMqÑÑÓ¤Ãå… lÚO¶–ªFZzšò1XÊ—»3Fƒ…6 Ø Jt– qäz‰OQ’¦¦Ñj<µG£†jpðÊåá‹$†h)%4íJUC¶²¬\½†‹.”éQ\u‚¤­JMRµ1Ô¬‘µ‘ªjä–¨¥SCA+ÆV*zJË,LªU#Pˆ¬nƤ“‚:ª¨®S4*´ÍK•h!žH(Zç¨T­Ž:I^ ½ôÅ,u¤ñ¬o! 4ž-í¦{€éÂOåÔJ6Ê 'ðKC,³Ô2ÓAMRÑUT ¢ÓQ e O–_Û,di Ù™Iëâ7¯UÓ^=:@¢Z©ÜÈðMᨢŒù!¥ûi’Q¦ª?šuýÀÅÚ3q¤[ÞÄQN=U†JŒõÁêa“O>7÷`¥Q!­¨fòÇ • Z£IL|èmÂ)œÇeiµÒRLóD‘G3TFæ@ Pmùj)D’CÁì¯ÛþIo0+$I石?ìt;§×[z“zÔT¬xÉ¥¤Åå)âg¥S‰"”’¥×ÍïlAûÇû-µï¦ëšZjCPý£M„¡ÔÊ?n:ò–á<×S[i> áûFz.³òÿf콯^ÙŽ\žQuÇKº—ýB˜IhIÄþ=ão¶¾ßò/ß­ÇÐÅ$ÑÈ2@¥kŸ_OçÖDò n\Ë4q^K¦Ù˜ÿdyt_vÇÈ ÕØ›š,†J9ðøÌ#ÐEO/™ÒªrÚá„”,eòiS{/Ǿ‘r(¶½ŠÀÚÙB±> ~]LW>Ým|¹·J֪Ÿÿg«5ÞÞJ.¥Û´ÔðE“ÓÅ%}<ªÒÊׇÉ:ñ*<¢âìtóî{Þ£Hö½1©ž_gXOÍì²oòªç¢ž*qé÷ Þ©²<ðS´0ähÞ¡Vš¤x¤Rò7¨‚?¯¸©ƒB‚„ñé°F”§L‘È>5Þt§ag¥™åyZ$‹í¾Ì¼U'ÞSt¼Ò¸ã }^ÛggãÕê5Nšà«5•µ‚f–<^6 —±R0µ‘MþK ÒÓA‘³È¨AnIDé"1/ðž•ÉP±éÇú‡R1앵ӡZ¸ «§¡¬+RÓÐ-\RÍ÷Î&­«§pôfSSâeU{Ø5tMz­M>õ¶)ê%¥§¨ ä*? ¥d–š µKy£p#ªXP¢1tVMhI½¬VAJ˳ªi4é·7Q<8ÓQ -uVRJwœâ梤Үª'§£²2@κ'†kƒË’G¦WTVõêÞ=0+ûzËC_>FfÅ ºJpµ2Mªf©EúX#‰ã–4YWV`ÊnÖ÷}Ó·«×åÔÄ ÒK kÞˆQÍ÷"¢žлÍj’ÏQ_PuYTÙ},G>*4ë^£¦u‚¢wRÆÓIWRT»xÍWƒÔË<(áE®ÌÖ<ôöž/§Sb½WO˜Eó2¥GÌ®iã2–ZZz¦¨XY')J+$jÀ1U~ ~UŽêàÒƒN:Må±UH#žt¥Ž㎢H䨦Þ?$2OPyIJÚM¨XP×õú¿>—[È€ðÏAäëªM­a‡Ã4EJVªÆUÀ±ÅQ4Ò'€Tù<šÑµÉ#Áuº}¤Y] …˜éSƲŠ:ÔtH7·Ç=×µs5ûËfÔW Œt~*|kšèš7¬‚¢¤ya«ƒÍM%;ùd‡+fÖ@0¶¹Š@‡>Ë?l`ã¯ÿ×¶T%ާx>€}7Ú˜ŽA7}Oµ]õ)\è+~ `-oÍ—ëo~ëÝsC`ºAP¤‚ \ ¸üZÞõ׺ÈuÉsôÕȺŽoý=ï«a×Ís} I<_MÿÖÒ>žýÕI¯—\ )ékúÛõj Oø‚E½ë˯uÌJ·¶‚C ‚KqȽ¬£ úûÕIòëÝcr >›!<é~N£øp¿øŸ~¯^é²Z?¶êÈ¥m¥œ1°³«t°ç›o®uî™ê&`­%=gõLâΪš½cÉ,cŸÀ¸ï_—TÕR@é)œÎÉŒ¥¬É×AJ,}Ù ªÌ\”´”ÔÔQµDóMQLdH£…A.ò=£ ôú{M<Ƥ”ð¡ÿK!ˆ<ð%8°¯íëU/‡Û½¹;7[ˆU›ÉÏö¸Ü¬…jã˜SRã%)¦ÈÖ-(kM8Òƒä E{òÜÝ8â>Þ§M¥c±µ‚5‡A>]%wúÄí)ð4¸ì5NévÇm\¶+– #íJzzl›år«,c6Ñ5tfž5Žxcc´š»Û¢oÕ'ãóÿc¬ùÝn; Õ9¿¼ÊVj=¯Áàq{oG[7‘¦¨oJ«%44Bä^_ZG)%ˆ®çŽ(@ðãПlµñ&I„d5xtuºd"h޲પž*H¢Íe3uôT8¶ÈÓª¬gÓGiª-PÁÌ’K0W'S±'L[Ì3¼×(S'¯ù)þ_ÛÔ­` 6Ã4jtŒî:ÈÞyjünÛtN#û¹Q·LùXÞj¸!Н[YŒ¤)ô]¤zK$lAfägœ¾‚Â,©VâkŽƒÛ½´×r+^„ÿ„+òYd|¶'3—û:êÚ\âñÓÓTGY•óÖÑN+%¦žDÐÑ‚ëvÔu©9Íãî׉¶í ³¤Ðüò)O—¯Bª;>^±k‰À­zÙ €m©·¶ö€SÓ&?6ªç0ªI¡Ui¤£Jˆb­˜ F¤¬Èî¶Ó#lÛqÛìíÃÔeâA›þëûÏsº•IÒ<úwO$”‹©+E,§ÃLjf‚EXÝjfCCQ5E'’7•V7)-#‹ìI– Ôò膸5ãÖHê$þ ÈëŽcOCqN• u\ÒÖTÓÍ#‹PŸiO4p,%´Ó¶ ªF€.:Ö‚A§èY$vûšx"X ³GGM)©«š6žŽž‘&˜#)òeÄè]LQgUÒáëŒ3¥D&5ž"*k„ˆ*+*`§sUS5,î”òÖ¬4U($j¦@¨‘ØJ%O^.ƒÏ®%«óõÉ$é[O£ic¢Ç_Á«Ë‡Ž®Y#%D°Åe'÷ÞFð,Ãà¡ëFEüúÁ:§¨d£Ž9~Ýjªª²QÓ¤2:÷ª‚YeÈÓH´â™‹xÄÎTzT“Ͻa@Çl0=H”PIHòÁÇÓTA1a:šIkŠƒ­² ãæ†:ØÚȱH·7$2‚Ç^p£=[¨ücEMrÓRf)Òx©«¸¤i1pÓD³=>AQ¨ŒQRêi_QW&ÃÖ܈ŠÐ·MI¨¦z—[4KöRcÒ’¦ãíª%¨ª©ˆÇP¤ª5\íZÔ@«•Šžë!!Tǽ³=zn âJ· u† ¤…¥¨ª§•žiäŽqNÔð¤J"uÅ }­ª–FDÌyUU¹%Ax‘¤kêó†hè£5êpªbŽZzÄOÜQµsÅ[X^:e’¦YÞ“î©ÖxLeœ¼úKúˆ:tvòÂ…'×Ë¢™í®J‚1ž©Û·qì;Ësäñ0b”csj Þ¢ó¥lèéK‰ÊÐAâ–)#Ÿ+?\»"5î¾Ù¾`^‰ûzz+v ½.”ß›Vƒ·7]ETUzìViªa‰)6½Ê¡ÓÃM4¨„’.Cö½Ü¡²}r‚@>_ê=Gc{r¾²Ô‘Ç£I»þAôÞüÙù}­”Ëcè±ÒÒCÿq$b¢z…Ô`¬¥PÒè‘YI'Ñ!kÛÓÉ ó6í²óÉ}´îJßA( '*Á‡œ¨ôèÇgå½þÆþ»kR7q¡ Ž>\2—TÝÚ[~¢žlºÒåáÈÑÅ FB*ºy¢’áò Z§mkÕ¯ VK0nG¼G³ä¯£ÝïšØÿ‰É%V¤2uGF+åÖhûq¹Ý¤v«á‚`tœø»¼`Ü}Õ¶6uìñ榘VPЫÖÅFôú^ž:ÀcT¦º¨'A*K[ÞU{Oc}ä0 K@)Ì|úŸ·Û'þ§n—’Y)a ÈÅ‘ëbÞÓ¬hqr䂊ÌJø´‰cB$¿Š&t7ýÁÈúsï&9‚yB}/c®Wo’ ÷˶ F°?‘èªÏE,éYÅC-rW²Ã"‡ÅÄ2L†(Ð6£{ú,,I"Ö dui]%zež’Ž4¥¦§yÑë©g®cYNd„M º i^ŽªxéÞ¦z†+2‚"üû)c"°¡ÀëqµBÐg¬nd“ÈÑG4þ3c̱Èâ­Ú ‘¥kNòz‘(y™äe«fœ:YRt­:ˆ•fV‚‚•–8jb¼°‰ZYKˆj^ªú^Yt­×J° r…jQŒõI R1Ö8)Vžif£j\|PÉO J²ÔIQ2ibñÍTÚ@f)—Õâ[ÞÇ›¤ZhÝ_Z ãÔü„sÃŽ––†¢J š:ã%n™F™Áe2NÅ|rÓUÙ«³Ä…Ó .«*)¨zô^¤™YiQÓ-”Ö<°}Þ-~Õ&fjÉ †¡cŠ:jÉBùc§Eë'À–iB¿¥J$vuìÁ=,Ð1Ös=z‰àUŠd–¢*jjÊd–zº¤(¦h ñÄ%š £^¦kCWõfWÂS«5ÿWú¿Ô-ÃˬYA J´æš)ª‰èYâ–GS-+½]ê£BÏþl —#Ýe¹ž ·^ë%52ËSE4°ÓJä—íÚXQ†¨£íI)f‘H?¸–x7 -üYÖ¤iÿWåÖÿÃ×*¤¤LƒIQRôï] TkI2JšV–vyYdxÏ’i*bQ°ö¬FT°¯UÑ(à1ÔJ¹Ò«#NÕ4è’ÓÁtsDÉO2$cÃY²«Z)£`‚ßO¨'Ûy=]JæÈ꤂¢8¨i)é" Äí;PÓÉâm ×Ãöò$n]‹kMÜ uS8Sæ:RŽ’1áã¡«ÉÔW¬IY”Ë5DÕ+UXðÃPÔ°P/ž)ªßìàŸ~&„D‚]rX\‘Si mE¯·§¡•‹<:ÌûvŠ£= 57ÞÃHñGö±;”¨§XÝgž•éâ4Þ6j'QK¨•S­ÓNž"“N¿ÿе¥xÝM˜\“¨‹‹4ßAcÏûSÑ_RRv³8Ô×׫Ô_€Ò@úsô÷¾½Öu.þ@Œ~. :ìþ¦ÞõòëÝf]?@?U¾ºlTÍÀÕøþ¼{ß^ëÆÀu%”üXþY–Ãëý}ûªøµ ¯XËÜ›hqk€G ‘õôï\:ØÏ—^ 4«j¹ i:¿!/cíÕZ­zß]=TJ  8ÞEÕw©¨™ÓðF˜éÍ(RßK³=½Ñª:÷Q^­þ­KH VÐb&À Ó4’1¸6½ÇÓéíÄ©Z׳ä*znªª ÑO ¶£â©D·ªì j†`<Ý^F'J¦GϦô2–"šÈáèz«æ òƒ×[/×›V¦Ž åž…±yÜŒ¯QUQ†Æå©è°õ2U 7Ü+—XB ”×2î+mj"¾V ÇÛз”¶K«Ë¡=ÁÓ‘åZÓókÓö_yšLt˜£QŽ9ªYZ…©‚²šð³Í?oµƒDo*I+»1ò(,§…L–×ê‘u_gú¿.¦xSSGEãÇüÝ Ž­ÛiŠ®Üƒ=^VjúÙ“%\Æ(3‘ ÃÖS5-]m¶WênTt¯yu±K죲vÂkÇBÙ{V–ž¢s3-ÄȹHÄgJj¦W³(T‘xò<ŸËEfo®#óW5¯Q¯9s Ï3ØÚ±1ð¥xt"ÔÅO"ÒP5*µ42³ H²Ó:˜^/,R³QÔBËbu’N³r±ËNµ:ĵüMÖ¨š’<Ï“¬ ,”˜å†T ©x#«†#[hbHeÖñyÙÙÙ€,ÚU—BÁW#74Æ:ZŸ·§x++"R‹U°Ë<¥Y –šY*?‰ `¿ä©cã’ÆX m@^0kqÜÊPc­Ã3È¥¥:ãJjir•ÕU4Ë¥¼)ŒQPÇÒEâ%†Y*%CX$Y#™‚)…t¯ûiV€êêîYiޤÑHÀÔIUOMOË‚ž0`Y(㦚I’4’¢š(¼õr ªð)ÉíD ‚ÅkÓLuyu7 ÕF¥Š’šx©Þ_º1É"è†5Bôï¡ÑTòB²²»qªêÚ˜ßNñêÂôÛ"€g¨µ+Q ‰’(ç€C“UF¤y)ª ó·ÛÊõü“A)U_ÕomJTŒqéÈ•ƒ ðë á5 ¦ŠžBõq!ÑbY--9J€”Ï÷D†•Ä-­{b¥Go”õ”æiªj^|†.’ŒT´X禖Fµ<öu£ª†¦(á’ªQr4¿ +éå‘#4šO uªu”äéæ–(ëJ<õ•oö‹tišt†a 3ÓŠsNXHÈN£õ_§»µq>½ö¹Ó©Bc‚IèÔÓÍzê¨La¢‰]SÇP¢´jÀ%P{y‚”Ú‹ëJõG$ÆkÓvkŽ¡ªikhqpRÏU(Ée'|n>;<”µ•96­*(Ì%d‰e`C„厥ÖÙÂ÷©$Vj^M?åþ} Üïb´Œ´óáQÕN÷¿gõÖs¶¢Ý»#?µ³™Ú|rSçgÂdé+M=U&¨dŽu¢–h¦Mp cc%ô’]A·»o{Fã·Åk$°Ô(Iò¯ù:,Ùï!Þ&Ž+'ÔÚ³C_—Eo7Ý™¸3yì%M.7bã²§øcT„“$jåÕY¢câ¤rnºÃŽ=†ažíüyx^`.²ƒ“ùVá†K´ó8èÈ|rêJ>ÈÝØŠÝÉL{*¢sY,ZÖgx§‰)ñF榪æÊÚlT1õ\“ïRí{!eàB„Ð_ÙN‡»Öõ¶ìö[YF¢b´ÕÚiûU¿È~}íÓÐÛ¶wqö†Èܸ§û]»%vn¾“ ŠÇË‹í±xá·?‡4MŽUrϼókfv¹$Üùa¼a$a[Èê?å>½6Žim†Î+ÛÍÐ>·À¢)„Ó´À±èuïÃMÏÕ½†ÝðíÁ¸IÄuQâ–Jz ª)·ÜI¢x"‘`¥JxþòºE©.,Eý¼î UÁùô²&tœW¬”G^%*(OâU2ÓŠQPÓ¿ÿÑ´¡ RÊ¥€ú/}@kójA诩HÀ^O¥H¿XyÑ€·àûß^êP™Åõ½Çù·ÓéÈçÞº×YLÅ—Y:,G¨?_Õþ bØ“ï}xWϮĊÀX†‘aþðx±Ô~¾ýÕµ· ë ¤ñk'ú‚ÂÀóô÷®´I>]eòM#€ÒÆÃê.y·7ëû}jc¯g¬Â37Ø_êß‚AR=ø€|º÷\FÖJ !‚Ö³µÅ¿P¿6Ûf+]8ëU#€ÏE÷äl§Lõv{tÐÐKU¸g¦š—oÐJѨj© Ç-{Å­ Qââ%Ýœ¢(·»K÷K³mhî($Ð|þ]ìzî;ˆƒA P×çéþÏZ¦öîO)Ùyì­vôËdĹLŒ¹e¨©gªÎT}ÌÆš¬«=,-XÓD‹¢1ÊA 4ƒOwvï4Æ]HkOÏ©ÎÛlŠÞÖ(â@®8ÓÏ¥>ÉÙ9¬F%²óä&Ì2bª¤D¢‚½ÉWB NN²y*)ik&Ãaiž£EΘf±\÷ÓH{¸S‡B›-¹Pè«qèb£«Àíí¡„ÚÒSË»eŸïñWV˜1øj¼ýbGR±Åæ 'ßRÓã,ÊÅê‹Ë¥R×E3ëFQ†§¯ú¿Õü„vöÊŒ¡Tjèj\­$”Œí.FXòÏ2ÖÐcáY¡•]Te)þÛÁ2 Â%’pó‰”^Ö±&’>Ò­BÕã^Ž£ãp¦?Ï¡»iíìîã|n2’_âT¹`u‹[ß:\ïwαHÆÛ_©¥2=>}{¨v\8¡œÜõ‚ª çÇMT´éÇ$ÂKÅP¬ñ›7‘ï nI^oï}ËÞ¬¯7™v«]>mò*+üÿŸY%ì¦Á-¼'wž3àžùÉѪê틬Û4™º¬~Jº›)X•˜c ³ÓTÑÂÉ(”*ãQ©¸kÛž}ÀüǸÞÛK6e¼>ž|zžwNkñ&ZÜéU>G«Dé-›>6£dv #dZz|R›*º[JÁŸ]- Ó¶‘¤zBóÁ¿° ÌÛ¸Â5 è¦ër’êÞA<úÏ«dÅ%+SP¦â©Äädi „SÓÆŽž«U°5‰B›#°üsîB²‚G¶…æ˜p­+Ôu}qtϦ5g<µ}¼: ; ØÆÏY.ãx±yŒŽœ1ïÚZ&(±Dµ“ÄH•V^A`ÇéÂߤ¡XØñ§„»î!šÒ9lÅj+ÝþÇAŽÙí&ÀmlåFäÅç¥ÆÕÀ%ªšTþÈSìÝå!¼òƒèüƒìöÞY!¶úg£)ôèsº[ÇÌ»…­­­ˆE¦Z¼1Ç€¯]aÿ»{¶$Ž*ͽ5<Ò©ªâ¼îZS$®%¹Ç!¾›1>Ùþ¯-Äo8ÓQ ã_N‰7^M½ÚèÊÄ¥+Ãýž î »U@öJ’GS+Ï%tr¬‰Gî<0È'yßô E$3@A ÞÞíöS /#ŽîŸ `: KiyW{s¤ùôŒÈcêJóSOTz"Yjê£5TNG‘V™£->‹-˜+à û-2Ãw’h}pàÿ“¦âxYÂ<´cÔÅZqME$´y(Þh •n")„1G*Ô¤5h)t3;ºme°¹_jìîÑŽ ÈÞuôüºNÌÚïò§Lf&’\-IHªžº¦’DòA,b©BUžX*-¥tðJþ<›Ç(hÀëÒšoÇ Z=ßìtÓGdTª“5ET´Ó}­"ÔOzª‰c×QHõÅèvveR$CÀ¸>Ó}2ÆI2_NŒ ‘(?ª‹ùôçÜš¢»G‚ 5UÇÔE%M)ýï/‘#iM2’2Xlò×ÛÉgã@uè¾]ÚIAw?6N^xešÈŒµfzš±_AKMK5dNÑ!‚®!e2)B]—J8µ­k»ÆŠ ,ß.­÷nÂyÓ’uô%©Wxb¥3H(¦ICÌÚ•|«/­ £ «&¢ËÁÐMe¤5F“ѽâNÕ‰ƒ(]2ÕK<T;ÌkRÊ)GÛ½Tl¡›ÁË#-M4q¬®› ·&þȤgŠJ1ªto ‰À§¢%z˦ñkj+%f®y!–3/d(êj§‡˜‡,ôèR]O ¢,úõ³jªõu:Š¢š€ä%§–v4ɲý„щ©$toLÿ¸`¦V Ò1E}ÛÄ‘‘ŒS¤ó³U®)Ö䂾X_ÃKVêçî!žw’¨Ê9&¨IÜ…pcgK(üpˆ×uâAu>¬Ñ³TAZ¶Ž¢MkEpù 0o¶YoNtËP£ÔXðmomGñIÏ­}añÔ­Šâ5t³CçÄLå$H˪Í,Q¤…šö-¨òlŠ)PØ=oׯÿÒ°z ÛcV ©eÕãi µÔ}l~¬/íRÆþô²¦ÉARªôÒÄà"†±æú Ðzµ)ãüO½yup j.Ľ­ÉÀsôööœW¯gÓ¬þV(@ÓqΟ¨Òo¨“¦ßOéí£ƒNªµ Þ´yGú›p (ú‘}Gñú§{×V3 c®O0!‹é HI±úØØ\ûß[ |ÆzóHÛ‹€ ú@"ׂmø÷½mNµÖ9ΫÔ‘{·%}*9±+õöà$€i×¾~}7Ô,L®òÔONˆÝ®‰h땤tu‹Å©›ñom3xE¤“ N¼ÚE‰¬ÃåÕ|õù!‡ì¾Ç¤ëÌ&æYúï WK†“#R\ãäÎ$¶k(J+ÿ4áÂm¦3¦ÄßÜyÌû—Ô ÀhsåÔÅÉ{ZZۙ犒‘ÄõVPoŠÅËnŒ} Á×ÕäL[ˆ ¨(iäÅâä3ÕÇQ%,±S¤¯1–•ܯ¬XhÕ¯‡7ê–1àþÎŒÞì\9Ÿ7µFz¦l"c ¡®o³§¯ÇÖÖe©ÛïpØÑOhg¦«+W3lj?m›ÙeÜRÔ:Ù]‡‘WF)ÔêÍÙ¶†àØòÕcrÖÛ"–^Û¤(¸˜hg¦W1QL—ÆCœ†Yêž0×XãHÙˆd’BÅA5ÿWåо;ðÅÂá©Ówmo/UG‰ÉâÒܾá|Åm5 LtæŸò#MUI<*Å‚d¨ ¥×TfÚm`ig·™£.àÖo;»[²À„§BÄÌ.ûÞù¬~åÉ¥l‘QGOüšªh…L*£ïb®¡f+] rËÉ:F¢öç;èm­ÅœD¦zrɸTúÉ+Zu³ÿQPm¼ÆÇQã$û:ºœjMSHæ6Z-J‡È«,Jï23€âáCO·y=-VÑààœt çëë›§+cûØèXÿqòI‡I'©•«á’e[E1†h@V‰á,È%$<¶bÊë{rÆþ),ÇM§@`òPê]-Ôo¹–,~^užIžS4Õ3R<Æ’ZÂÑãôÀd‘æHL·Ó©,Ø,‰7+1"¹=G1O¡+‘c©ZDO½•jç…Ì”1T,µKQ¼2ĵ’[Ô¡-(* •e¡z¼* ž~õ*Þž¢8M \‚a&>®h*c©ôÇ šAãO ©ä DƒƒasíÀòÚ½zqBNñKG ;¬‹SQ!–XQËÄtÅ Šë!RÌwP³k‰aoÆd#¤ü:’©j8~Úžž¦I+20¬Jέ-5< Q&¢Ò5=:"‰ ƒ)+nuVh‹)#«¥j(3Ó^BaŽ¡«h«I$U3Í ²£I;N`cd¥Šb¨ a`£ƒí £)5:ϧOu€Rº¤jº¤¡…ÃCM"t‡Lò¶·šj¹¥FEÒº_PP÷Y›Jš¯UI5žArµ}ÅJSSGãV©‚B•T³ÓF«ã‘åK"6¤! þœûO«Y)¯NŽºšºi¨ä£§¨šhäHÚæ« KÂÉ<ÉSÆòS߯ޤ¨¿âÞÔ‰?U$^›ë}”4Ũ¤DšQR*a/ ͦ š¦0‰§@ÉëÒNHçÛñ9%œ•=3#²€BœÕoÿ3Ç¢ê<^!2•så+iâ1¤ÒGŠ9b†ª'$”ÀNÆá@ú_ÞZýÚ9n;Û¹w'´ cFñ¤ÏXÝï‡3µ›ÚØ™¨’:’+±ֶ˜l¬5{× G—Áæg›)¹1t¯…yé^¦2‰$jªf_9ñ”yO϶ö›ë¡,qAÔE³^Cuu ´DôÿêÓ»_ ‰ë:JH6囟qÕÑÒVÒlÕqEEƒ§™£MÃ\þXbÕM!Ó¿ ãß+gß7Þbݯ“ÅjŒWöuœw4Ùì\»·íÈàJTzã«êJíãë]ƒ¿èèêiðõ«¤Ü¸Š`Iã4GǾºkH²j î ý§Þ6Éí[KÓP>'Û.þ¾}@.}xts:ù÷%dÛ…z1®50Ñ=e>>bªa¢(ÖTU!HúþÜ1 ÙSBñèHÁ‘X,†•§åÑÀmƒ]½6æR)&Š&Ã>:®…ªX Å¥£acxß@­½¾öM,*²yç=#ŸrH-äŽ8ljæ~_êÿW¨ôzê¼Pec†ž)1òºÏNõ¿ËC(u'WÔqþÏ Úï”nÁåÑ@Üô…a'êzôF~IõÜÃT`v­5dõ¹—Ÿ'ˆ§ya†®ª%eÍID÷W.ŠÂÈy^}ˆm¬°Ò§©SÛ^oÓ¸H—·  GÄŒôNR§±zCpà²û•òòll€’ª™f–iaÅ ¶š šIÝ'Pl„'ú{=¶µe(…‚§©àÿWû-µî›9íRíqËÜÒ´"´öõ-ß¹çñMÛ²²Õf¥•Ò³]}rE ³*³ 3µÀUÔ=ãç¼^ÒÿXî¦ßy~üú¢FŒ)ŠZõkÎN‚ÚÔC=¬n dŒÿ“©xÏžz Jxwf—†Š¡²”l²S•o$m¥P»7ö?Ó7=»Ý½—skkÍÆG·×H9…*Ô?:Ó¨Wœ¶^´Ûî&³ùA jâ}:]f?˜·Vn‡zlŽÖÈc^t†ÑÇL¤ ðùPô<‘©¸[-î|ä^wÞm6øí/eoñqÿ6~Þ°ƒ=Ìß¹^ýâK¤-Iɧَ˜©þnìÚŸX|}53I5KÔK5TÍTÐ’}8Oæ ‚Ú{î“6ë›s×TITÖzeÇ!’à*…"%Õô ¯îeäîNæÙ–K»„ÐHò§¥þQäÞtÝ/CϺ·„#?Ê•óêÓ0[£iwfûˈ͔ªZ†].Ò:+²† B”Ò?éîIæOm.l­MÌ€<`ˬ£Û¶}ç–a¶kÉC#(©é˜†’‡\G’J‡ª$ÈU$ÍMORþM ÓA!C©G,GÓÞ?ï–)”Š:g= ìæk‡‡¯IzSCVóKCŽ1T27ÜVK+i'ƒÆˆ‹*ŰÀ1:Ë› ÿOaáU £€èÁ×$ÿ«ý_êôwD©ûj§òjûE†§Š ÅQ ëÇ÷©fðÑϯ‚¢Ì×ÄKCSú¿gZ”ëeÒ3Öiåš(qãí2‚ŸÉ,ôfHèªí$Qšy%>0¥ƒ—áØ¯¿kÚ:¡ñ#ø“(*iF.¶¢–*êå„;ê>ÎVˆø"*¬s¿Œ3³J¥£·Ý‘3Ó‘º·ÛÖdg0Âõxʈ䣨Š'WD©JšG†M@*4ª\é€öÁŽaŸùôùŒõÿÓ¸üç_ííÁ4”ÑÃ;.¡"(`Uú Þçü}šTŽŠý:2ÝI˜ÄÉ%V ©¥Œ"Â$àÞåž }}·N½Ò@æ3xwhòØù.„##©_ìðÆÁ€÷ºbƒ¯k>(¨7`VIP´[9[Z÷>Û(ÕÔzÞ½B…Ez~:­(`Eõ€Y ‘`§çýðÝ+׆8(ëÏ(ý/v V½Ø7ø¨ÿ[ÛdP‘NµZñë¸Ý®ªX•¢,›Ÿö›Ž·ÕA¯^ë°Ò¶¿òs; Ôƒø»p¶Sù¿ïJ.‘רiŽ=ž=ÖÝUÒl6)&÷ßtÒcq´tRkÊ÷T̹ü¸ÊÔÑ*Göë-Ô‡k¯éöæMÉmí „.„|¯¶Ëy¹FÆ*¢I0zÖgwá“7—‰k2sÈâX¡§Z&Pã顦aS\)©ŠiV(L*’eRÆþáù圻PÔÔô¶ñ­ªF˜•:VmÜÞlÐâÆÖo|_Ù¥4›Žl •xJ‰êZ³'ƒ ª?Ë$ý‰Ä«HÇ#W¶tL?R¸éLBédÏY6æ# ¶k±””1VÕ-M3çë³Õ˜y©[/]‘I#–’²S&>ŠZ£ª¸I ¤çœèMM:m6¬Ò‰<*Òî²Ï‚ëèrO‰¦¹Ì¼XJlå.5ê^¿Êä‚‘êh„¦š†œ%ØF±4€þU}‘ˆ˜È^˜¯Bë¦HÄj@ò?êÿWò ±½U>âËadÜ•YL´Ôm ¢¡Í<µ1ÐSUÕO÷í‰!§Š§*­êôºi±OvºÞßjjvô͇/ÒdøƒÕùü2è¾"Ÿ™ÜtYÌ|¢ŒÒ•(V {+Џê)å-Å8EÊÀêâØVmëw‘n!- lqáÐÃs–£mà …ùuhøüšº\5 · MRòUÏᨕäÖ¯:Ӣˬ)`ªÎWÐ{–,6ø,ILpêÜ·Éå”4ÃIéÎ9"§‘øyCv²ÔÑJôéæ+÷$TSºÎ²éRÊËY[Ò}˜é'ºƒ¢ I¦½O­ª‚šeÏá–)̪Ӳ_Ä^2‰0RèJyîÁ­ozÈ<3Öˆ jz?5¢ª–¢¡hr9ÅøYV'"õRefD 4öºG`AÙçÛ倠êðu&,’SÎÆUQ¼q‰Kù!î’9_3D€úGµF£Ï¦óùõ"*Øai-AQáŽU«v•u¢ÈX°7šE«ñª•"BO½ÑܯOÑ•j8õ0ÔSÅ­ª¨V8ÍO”CÏÜTI¦Lh4§™@Õȵ>ÑNH ƒÒ)^FjS=D’ª”㣨”h¥Ž-MP¦ÿ( *¼.¬òÏÆ»zUµk~>žü]t‚jéDÀU‡Lí.Aªj(fÑ'¦Øj©ÖV¦ªšf•’:^˜!ت†Õé"Öö–;‡’F”Pt¦½;úªâ›!• §4TUÆ‘á =EÙE\Ò¹hcXÆ¡Ïé7·½È€dqëjÚM@é:f¤ò–Ç⧨‚»*ÔÓÔÇ"øÝcÕm[# äX™/f/pϻۮY€¦“ÇY›Ä 㢋òçC_…£Êe©g’‡®‚sÖÖÏèš)aVey)iå%^ÌìGô÷ПºVã´Üíw»tŸ\CãL ÚGX;÷“Úî×s³Üdf[5 Wʤ z©-íG°vNðÚ¹¹[ˆÄä0›ŽJßá" *ž¶V‘*Õ*©(ä‡K®Dú}å—8lÐÏÊ[½œ xÒ!SCC¤ƒZÙÔÊ“ǾØ\[Ý—Un˜ÿ7DWå&ðÜû§µ¿ŽíýßÍlmù“¥É`:¡±ðUbã©ðɉ¬x¦I!ˆiu$+u°Á³Ë{G,í÷Vö–ÿ¬u1ÔuûzÊ++ÛÛÛ£=ÄÄÆ òl-Ò9¥ÇãðjŠür6ÛÆKŽŽ |?kIO(ZM¤êŒ¬ ê=â0Þ¼ûµÚ•Æ£Ž²h!vøZ* ÏG¿­!jŠY«êÓølù¨v,= ×<þûpöùu"ÅH¢Z×L[w½w&ݾº®<üº"=«ñã¹ðì’åñÒý”î̵p¬jÒÅ+˜ÇìË%¬Ëm pg4íV÷-âÍh kSNbï¼>éÇ4ÆMµÝ9&‡ìÇçÑYlúÆË‘Ûõø *M ѼNlJ”!Õê–O¾  ûަåË%›Æ…ÕVœ1Ö1n~á¾ägýâCWÖD£­¨™d‚z:šm,çô´1êÖ€]ÿM‹¯ãñonÁc'²u"¿.¡îóm»”˜#ió§ùºR Ã’ÅH ï,’êUGg”k“ÂC®«†Ž-aÇãÙª¼k§´t’Aã?ÏÒî¶—™À>;+K‘2ÒyͲ$Fh˜ˆOék±ãòÛXn)g"KT^ 9zùö²³E@* Å}z't=%FmI"Öœœ0d–O dÔ©)>¢B?ÂãÜɳûž ,’*¨§òêvåÿ|­vY!K×]8lÝðG¯7ÂÛù¸ZV¡ÅaÒªYØ4”“ÕTÄìË`¡ŽÀqy?Yo|¾àº‘ ŸåÖOÁî¦×ÎöY(ÖPðt%n|†6­EUôŠË;Q’Í/šA ñ§®0U}Mk{ÅÝúò©¥hèQÿCý¡^ZŒÓ ê«ø‚Ç$.aÖfj˜%ñêŽ ÂycˆFèV¢š¿äþ ý† ˜mèÛñg…zo§Ž8çÊfB¬•ÐÑÐÔVÒFéQU2¢Fj#˜7Û]Ä_@±Ÿ¡ohâÜ\)Šƒ¥h±¥ óéÀÒUEDê*édI*¸¢ÓDY#pc+ –D‹ƒb-ùöÓM&¢AëSºL4¯Ò…ç.j*Ñ’,ÔäÓatLg²L¶ /’Y9Õk§Fö¢&Ÿé€>¹ÁL’;UTÓ@ 2Çy5¡‰äœœ2ªºH±².Ì9ö¢xÔ€d©ÇNyõÿÔ»TšDÐ=^vÔ-ªçõÉ?ëû7Ð=z+ë,u‚äÅ´©aÎnŸ{Ò=z÷P«ñx¬´N•”°K ´_!¿<›ÛýçÛ$åþ¯õ«ÓuþžÇÏyq2µ¤—A{n åx·ãýo~ÏUeÏA]~xmiZG§–®™IäuôÚÆü]˜{ôé8i?>±Ro8üuÉK6´RdáuµcéSî…jjqÓŠÅ´ãÒ¾ô«ä²Ç#›[IëbÞ’,5ït §OkÔ*´§Xê«hqtùLÕh¤Æciª+kêj&jzjzJXÞiä™ËP¹$^ÞÚšu‚?¼…zÕ¸y®VÜ.M:ÕËæ'ÊŸfö6çÜ´ÑÕ³‰ö†Û ‚i%£Ãã%Y’8ÈÔ!kÅT$wk™‰þϸ›y½·r9~Ðx«ý_åšùz$ÛìÖ£Ó3q?äèèü5“!>ᮂŠ9iêDUY ^°ji§¡ÌÐG4‰P‹“OèÒ]Iö¸‘S‡—C› i_õê:𸬂ǙÚ{v®¡!52à#¡£U8±>Bžºž ÊI¤KŒÕeo§f†6mCZ‹o÷o3žêðùt/Û­@K§hèLÎu†7³)̹Ü=}# §¯©· )Z­ò¾²ô?n(‚ÚV³ >>ˆ#š{ŠH •=}U•š$ t^©²ûƒ­p™J˜³YÜta‡0Õ´R‰–”éi„­wˆ\K) )<“eèòå$Jc¢·šÚIÿOº½‡X9w¾äÛøºý½U6³+ di¥—ËW5UuK2ÈÍ=äÈ¥;iÕeÔHàûó0y®-á€Ô³dt-Ûnfµ±¹}:HCN¶_¦¡ƒká1xê*i–†¤). Š`¢^1ê’Á¾¡úO±ÆÕg ¬V &ÓZõn·÷Wf_bAn¸šØ),Z (Bš~æ jc4“þäãüž'yyY ú~G³eÞ¬½½äWz|¥‘)&®‘²&Ye¨0Ê$)q´‘‚d¡Ã D„XèâÿãìÉa…S·ãëÊÓ-1Q×qŸ¿z~d2³‰(zhâ3Éj–òTÄI­U8RäûK$,;ÏŸñ@ÉøºŒdŽ=IªeœC÷qù?Éa©–œ´–hÀ—Åe8(,B­è 5Tõ£%T€:p˜)éÅ7ÝGSKK<& yB,M#­Â»ÆdžŒ*$6›ÕíLÕ‡N‘ÒmjAÏ]Fõú&‰TµEQKDŒtµ+<%gõòÌ’S>BldSqf¸`Lõ\WõÜT‹“Ô ùžª¡žoJ5T£ŽHZâFÇBÉã ú]Ç • £¬í#P/RR²¦íOW4sEGO¥å†JhK,‰TT2£  N’^ß[Ÿh܃PEGN¬DQ›¬Ê“GþS’€U™éˆ–)bʉˆbQNV@ŠŽ s~ÚanµÔŒô¡V½£¨ÏSSužXb’¦©+ªäž %SH¡Ý)¸ *°Aý$ÿ·ÕQ4Ï^‘J±É“«Ž‡D³4è$’Yí9‰ÙÚ¤•*¡B¤ rl=¶ìuS¤†V M8鹫¿fXé*a§ª®™CÝÇÜA;Ë RÏ-^ªwS`ŒÔXñÏ,µH4#¥·p:Jgèk7.Ý8ùb¦“1œ+û:Rf³0æé zºJ½>iç "Jm6$•˜òz½J?¯>Ã’òY-þ.ƒí¯QVñì¿7Øñ,èjxyðé-›þ\äåYèꨫÅŸ¶†9š&0*„2¼ˆn%Pº‡ãéíü›»*á…:î¾Ýó„¢¶¬GØzyo€»©`¹¨£‹ícXh’’4‰¡‰ qoÏçýot¶å]Ñ .ÕˤÃaÞD ž+éÔ~“þ_™=“ÙmÏQÓeéZQèÎb:ÛhÒìl*CMUYKVµ¢š`« _€ ú}÷3ÆÑl[7‡¯%iCÖvû'ÉóF¶Ó]UtL|º!+Q:š¹¡ˆ£ ÕDè¦Iª>ñt±íëgÉúâ{Ù§‘‡ðOóë+ÄGžäª£¦š‘ÒDÁ*ê ’S"¡‘uÒ«ô[î±xÈ£ÇMz©ã^𥝆£íž <´ï$¹ 3FeUO5QÕªaâÒWA°e$ÛŸl‹b„°øJÕötïM÷uµæ™ ,ÔЊzµV%B,åÂ~шÛIúGçÛ°#êýAçך!GŸJ*Åy`OSÛC¯M,^(¤–)'¬„ÈÑçQ6kôàÂy "é"¤„’£¬8ÙºžG•_¬ÐãÌÒ@ Ë 8›M™g’+Žx:¯ô÷kfwFÔkÖ¿W…:ÿÕØG?Öµt7jVY"+¬³)¿ô }ÙÀpEz)rÊ+§ Â¿ QLJÈŒ¼Úê)&ä‘À#ñîÀŠVmI+R:e³Eq¨zE¬¼ƒoÏãëïEkþ¯õ«ùo¬‰`ÜÙˆ¹5íoèýçþ)­?>¶Mzíê©êch¦EÒ÷ºÈªG#Õm_Kû°Ç´) ÷ÀÀfÑ¿f8€1ɯ§ëqbN¯Ïº•Ϫ!öS£™²òØN¢é¼…FwS¸2+“€T᢫†yçÍIv2²´¦5)÷ÓÇ-0•dœKt@XØc)Ý7OéÔ—>„´‹¶Ú~’‚äðuZ{¿ärå7bŠl]=^f|4By÷w4æ®,#TZñâérUñOê@êI în³åè-íÑŽ“Zž¡MãzžöçÀ‰ˆ’´Ç]t3wö.{¯ÊÏ56¨HÕG4¤Á•ÑSãUŒqNé"ÿ›©‹ì Íû•žÛlï%ÿgH|Ÿku#Æ&JðãÖÍ] Ñòlêm»Yµ1íê -%&KûÓ›ÇAËTcKÖËZÉŒTõuòXGP­ªbmsqí¿Ywz·>¡ª¢µè}¾ÝÛ[Z´ÈÑžq¥XÍy2ÖµU]63øt ÕU†™Òb¬Kà #¥1×#;—doI<+ÇR"âNCsÜ<ŒáTi Ç§ÒØl}=L‹N¢––*©å¤ <UOÓ¬µqó+S¥›Éøþ¾Ì•FŒ ô”¼•áÓxŽš«ì«a„ä)£) ēøæ‘Ö¥*-$j‚oÅïô%G×Û:˜7x3‘NéäÇÏ(…§dj‰jcª£27–'ÓS¤2 !wŠK´‚ÖY\“îï#=TÑÇ9ëž6*xÓ!RâðUÓÕBLÌ ¨ÍO1Ž0jY5(‘nÃÔHkéš“Ž¬‚(:<¦cM‚u—OåAöôr<Õt±ÕUóÅ%Qt”,¨ÌK¨U÷I%’Fø{GNš “Ôê8æL…M-TÄdV*DZ—i!ž§SB<—ó¥ECªÑØ#­Ø’Gº©n$g¯*‡&¸ëª–™™âjx¥4U³Š˜HFY ––T§PÊiÖ©Ýɇ¨å€URø™‚Ò8¨‡ú¿Õþ¯œ6Y©ê:É<ñ¢š™‘ÚZÁPcSk«ÃI /¡t„k-k5“RW$œuÊ¢8¥K <’Å5EIÐZSuH&žžS"H :[Ö=glðé:˜†ë`Ó‡\fR&ðUÆb¯jxO¯Å¤o%¤a‘–}ß“omV„Ó­–-ǨÒNÏ㚎’xZ¢“WBf£1È^j¦h ‰42‚áN†»§¶Ú÷N˜1‚kSÖ44)“icg5fŒMLc®ÓQ%H&ž¶%Ò&§Žgÿ H/ô·´å"tùã«·h pª½0Öj¨§™¤(É5U3Nê£w’¤¤­"r¶¿à{[l!¸] ö¡¨Èùô˜Lë¦/Ã\üÅÈõ¯'ó ›-Ûû÷°h)áÍͱp;+n㙫jéjóôfª|ïÚªêž:$­ï©xçÞpr$÷OÉ;?ÔH΀ Ÿ³¬QæÔŠ.h¿1 E$š¯U÷EÐÝÇ›s5äÜ땨¦ûJ÷yôÓ±×4•Å$«¢(éá AÐyþžÅ7[­Œ62ÄÑ«9aé›I&šEP‚]RájòýV¸*¼½&{yacL¤02ÔJv Þ4­©æ§šVÀj÷…¾â#Mypð§aoò§¾S Ú(ÜŠÛÒ™r˜çÜIµ2ðÅC™Ìcb¥¯È6–%ºhÉeaþ¹÷ÜÂZbJdu"Y³LÃ㣯Ö{×ê¼~Ë®ª/QÎ"Ò „Ë H•MŽ¡g&ÀAìc³˜!ÛÒÕ|º'¿‚v½3†íóêÆö^øÞuŸÂ²+-=&”uF/‘®>ŸN=‹ì|fT(£¢ ´¶ï*{C¶c.íÄUdg£ZØå…8xô¼ï:Ðqn}Ÿ¬Z—õÑKO£G¨è½äéSµ¥{ESOT¦Eg v`±S²–Ô$Þì¥#´ô‰‘Ý™ÀãÐÛ_¶'~û€J­µÙQCvK7ØTg©¨’qöu­YÒid¶ Oø{3·¼—Àv-U^™×M¹'Eglú^¡MËTµy¹² SQOWC˜Ž¦¨âåi#H©ñ–}RËâp=JOÐÛØ+˜9¥ (QœŠüº¶ÍŠ4T—ů1Ôñ¸2KS*Yðô²MLb’ˆO4©GCU9‘BhŽ)%ªÐò¼€ HŠ­Õ„msuvòj'³Èô5µ‚8Ù4SWÛÓ¾ pei#eŽ¢²ªy"Tqiª¤ž­pÍ<--*†{€4È4ªñe¶—Ó ã^{r°K ¾>h}BEb×Ñ,pæªâŽY<5qÃt`?kSp‚_·°á¸P ·åoï)5ç·¢žV±Ü -i1û:SWwV[ÅŽªÅå+ê±ÔÑI2O=@¨4ä³*¹XJ¼ÄB1c{®£q{û»îp…ì9è;·»KÎËu¶ÇŸAÒ7-Þ½—›¨ž,föx+‚À'Jjs¥\±,ô¢ V9æ‰,ÜêP¶ä[ÙtÜÉ4”10c¥ÖÞÛòµ”âOݪOØ:Iä2Õùq;gkêê³µ%£ªIçIg¥¨…ôȺäfP*C‚ºX¿OñE>ãqtª·2’¾]m6ݾÆ%K;E~]6™«’ 1K_#ÐI ‹ÅNdm j‡Œ»22%øfý@úmí‚c¢øf££2ÉCEõ6¦«ˆRÕý¼ŠòéIÓ[ b ȯe:ýlASô_m,­S¨+Ò#I u—W„Í=-1Qb*‰45\¿©˜O$Nùþ¤ý-FšOFS§£͸tïMú Ž­áP,NS8‰>Ü,pN÷`Ó 7*ãÙ‚B ¥:Ó¸-B|ºv{4hƒÆ´”ó!š®Y +³¿Œ´%ÁŒ­LÊÆÄr¶æ×Hòé)r zÅSO<±OAKU-&>Òy' ªzz…‚¥•üLêž_M—×ôñî±ø‘‚`õdVŽ1×ÿÖÙ¶šµbKIM2ËF^W”³€$†$Ûýµzˆ赟P¡¨+Æ%Z$ŠPt¼¾0c}Gƒr SÇ?j=WÊ%³=aK\>6t(I` ‚ì@ z€>Ü g^ãŽìæÈÉâ¤bÔÁŠ‹»?Õ\p}ëÄëmŽƒÚ¸¦§ÔdRnY-`Ö×þ¾ü¢´éŸ1Ǧµ­tm:ìÜ*ƒeÿ ÿ½ûÞ¢xuef—û ?>šwNöÃìý·žÝ™Ú•¦ÆmÜUVZµÌé 1RÄó,AæÓËRë¡/{³¶nÛ@×3P'—W‚ä¹X4þ}iÿò7·%îÿÙ=¡Qö˜x³;)vŲGM-#˜GOQ½î¿ôeK ÙÙÊüNb|O;L~âj:ëÕÍBT?о9i¤»\"0²^êA[#ƒ¬ÿ«ý_êùŸø“—SàŸŸC7CSäqùÈò{‡4¹”TÅV™šuŽ(‰ÈE‘ÌIQå§1¥-“•bK,Ô4û Üí¥¸£FÁèaµ”¶î’B²‘åÓæìÌe;uf_#–I¿†å`ÛÐmŒQÈap²IA…‚½*MlU1ѵBÚIPyQ x‹*¡d{fßô+AñI­|ú_<ë*I⾬Wìè—oÍ›Š¨Ý¸¼†Ø‚‘+'¢i*é †6ŽŠyjjÛÄÐÓÔÔE8¦…›ô<†E‘y²Ø Nã,VY»©N-´,›‚É\Xõo_ ¾<ï.ÇÊКÁñ±9*ò¹¨é–Šd£§£H2TÔu:¤’•AŽK….üq02ï7ãl·}R)«›©soš ‹mú»”h¾ººØ# &ÞÇcð4i©ðXµ§j(õ—’ž0þ5<:ËåF‡ö·اo±‹n†Ñk(µÿbF—×ó^Í4ºÿD±§Sc’šž W¹§¤¦´Ë ÇTʲD‚žj8мQÉ ~¦’6:@Un}› 7ˆ@¯EThê#¬Ðª¸êj*e‹Æf2JµiÌÏ,ÍB$ŒZ@º =7ö®9M ÐPõínHª u>j”J_,2ÏG¬Í¤´°SÁK*+ ª$¼ 5‘9Ôu[ñ~–2EÈêÍ£=dœÔIO,°±Žv¦ÐVÀ*Øù¦‘"ÒþH Ä¢}#Œ‹ÞÇÝeIj«Ö”+f½O¥–™iÈ’ÁM¦¨x²+Ô3Iö©áh#œÕØ8.Ε²–>¯i)s%Vƒ?o^«%@õ!åO¶†Jlx¡…bŽJªZΕ¶t¨òkˆJÕ/S‡eUúÜê ¿QC(é ^³¬Ï-9|}L†HÅD4…Å,†j–‡Å¤ÞbÚåÔ#-è–æööÓ³iQÕÕ´Ö`ªwwòÔÈV†/0Ä­Pj+ÚHbÎïc û‰"hÑVæ Ï$yA`kÕ¼SQ׿v¥%"5=:xY&¢uk‰D¼Ò»@$:"+?Qq¨œÇZd|úz¼:…IC¨ÖLj+O"ÖÇXÕuu+Nf‰• wUŽYT"….¶à~xžR œúu¿N¦Ôˆœ´dø¢ÑÔ:<'S¨ Yü±‰"¿êG$ÞüûÛ¤‘ °ëÝ6ÔTWËV…kšUDj]lTѤ(ò»}Ô’¬/M,IûœÓê\ûª9'‡—M¼O›c––œÔÖ<¦ªšF§H’Eª§böthU˜@"š3o"êokûó o?>´³,CÖ¹£Z©jûÈÅ(™ÔÁãÐÏ4‰P¬¬®Y¢aûh/SÈ µÐ¥‹·ö‘Ó#C(&£üÇ­Z?˜VøÜt*wVÔÉd°S¬´¸Y"¥ŸU4ôô4É>mcÃ,d;pñÜ+§Ôt’öõ·äM 4Ãw-*È|ºÛxb6¨î§BGTáëqÒEXó‡Idt‘CÜ\WÓ >Ìv¤q*bQ¸Žˆ·…u&ååÐaò»h.× ;× Œ¡¬&JŒŒB:x$¬t³‚¶ŽV·ÿiö]Í{Dfͯ"5?g—B^R¿–ð‹Y¥5§¯ŸD{8Ô¶KY‹ÂSµ= /‚¥•¤¨¦Œ‰O"‡Œ¼Œˆä21]^âÈ‹8ðiN¥[MµÑÉN: ªkå4Õ2 †%z祒 ;ËKSöO÷F‡š–WUâM'‹-Á>êäÂB¦z7™ QžŸhª«V }6N£’Q+‡4þhØMMš„XØÌÉNB 7FSpuvñ*„=E3,ŠÃI W¨?Å*bZÊzÝÒTG[CúÅåXýܱù¤R²oÕaÏé \ý½€Ô¥:ßOÑLˆVl}qõúô‡Òª9æÃê?Øsí¢µëÝr“3éeÊR¬ñð ˆ6ú\Ý…þž÷GôëÄꯧIL¦ØÚÙÿ!†x¡˜ý´Å(k}>¬·…tüú§†žkОØSÒM?ÚÎ=/#±(¢j%ä,ꈀ ³_ïeéBM™é‰Vzª[Œ‘Õÿ1—»B¿ÅÑ{+'E• £¬–£}fé*9*"M6!o dP°Õ4‹¿!\X¸ó>ýE±·m^µòêCäí†i$[‹šÖŸÏª­ž¶²‡%‘®¬‹ŠIë Á´X™#ž©ª&c 4j±ÔÏ iQ¥}e‰j#èÀåjãÔ³ ž…RËZ•ø|EVw;ŽÅl¼=lð祧ž¦ ýhÆSãê*!®Ž±åezj„j¸4NÍ©C©`î@ Ï5R‡/D˜+ ÏK,Ì4ËI±6ÄÔùA‡Z<%Q†¦®A’§¤A§ÜÕÓäiÐýÚUùF§1æ8Ò]Ø¥elôg ¬÷¬Ž–)þÏK,ÎΓoÐ-&Bš„SÖQß!'ñÉgÌTî&i©Õ`¡ ÍÈ%‚H]á¼f‘GïûŠâRßÙO·§'V‹LRÉŸ—AgXõâç·£[XÒϸêéªÚ/ÓBæ¨4Q¬Õ-MLq¯í±šmÀX{(Þ7;ˆl®) }}>Þ„{D6×N¾IQÇg[JôVÂS¬ðËGŒs’¯ÆAU˜‚Hq8úlm|ÑOK@ÐP¤ÓQÀÔ1Τ«Ï£ÔćyobŸKn#xÌõ'=:CÌwò<ßI"'„£4ÿC£²Ò¬rcêé¡­¦2Š9êlúéo-E)•`ÒBøê¥² ÀÓìpb BIÕÐBªž½wA+I÷Re(«|y*¤•&gžH¢Œó0EE’pcWrúIââîˆT-zð#ÈtýuDžA#ý£ª˜Z(ŒH…U˜ô™cœ·¬¡Bp ïïH$VÈë|Au ƲQË¢Hctt’«îâ5-Yh|fë*!’Ž¥,ò\ƒêü Hô8ÕÓ šF½GªËœjÆÿÄb¥Ž2Í;×,°B²M3I ›ECÏ[“f6Cv Óg:Z¸¼Q|0GZEJ¸}Ħxâª6©«©<ÑV:㣚VQ†B'’8ÙøŽM GPêöÆgîANœW t÷ D<´¯44tÉf¶7š†¨™©Å®Ìµ¯9AB…©SU<´•1F‘¾ƒäòÆ#¿ °<DòêÊŽ¼ÊŒ8שuµpS²Ó¢ÑCî%}T*)Õ K厘f­¡áž¡&ñÄÈѤÐ}²¢ ‘º¥A³J‚Ò$J]uÍ•Ú83Ç©AáRH—Á˜äöµrùµù¿–…;*Ç÷šI#Ö– ) ¦X-*¤ ±wNú µM$<­±…þÌÂ?À:Ä}Âaó¹~šÿhGŸEwÒØ¹©k?»ë>觨š(ã–¦I±t’Ï,ƒÊV3+8U¼„(¸µþˆeñ&·pˆ2zH÷Œ’ªŠS£·ñk¶· geÔìúÊñIEØ‘›ÇšEZ4¥É‘!a)&G†þl?<Ã|ß`ÿ¨º;©Ô‹Ë;€…ã4S__ø¾­³síé0Ù¹]—Ì}ÎÓ¨§‚|‚É4²‰«iÕU¤ŒÒĺ§•üûÇMåï,®é«ô¼ú—vâ’FÍIèNëݪ‡ræè)j+;SGf/”1®…§R"œƒÃ~oôöŠéæC zPþÞ­5°Q ÍOú½:>] ™Ý2U:ä*>áê$cç‘Ì…À%‰fbZö?’~žÎöˆæ³´µ=î.D”^útui3;†ŠZzz:Ç– ‡T«Š7²ha͘Xõ¯ìA[¸Ú‹B¿>Šað&å¨5òÿd›w¶ß¦zñ5z¬_ KNMý-à’l}³$M+SìëÈÄ–1Ó¼ÉÓnÁ¯Z œ…¨Y¤ŠetCpñ~€}¯È··¬æ)( Ó7 ²3cU:;;GØ}Y¹°³ÿÀ¸1ód¨_Çä1ÕÓFdý¥(ÊÎʱ<û>Ücú±í‘A_>жK™l·H˜ _—TGæ ´;èxërt‹E,4ñM'–(áI˜k’x¤] +H¡:Øãä¿Qosp.cUPqJçíë"mYD-•F颺2 ´±°QVôðÔN© ÖT 4’Ç?îÇP¦”ë"; M‡ÓÚ /X tbÒ°GR£ªšZ#]%<0C1§¦‚'z:égI¡Ieâ$«Qk­M´°#I%Å%¸Ö„c‡TÅ#P§ÙÔ/º–ŽLbI•««’:Ú°°ËC åü‘U54SÊÆC$4jèÍ1xµ96ÞÉ "Ô×=*U Àt¡’¦*T’†»Í4šŒEÒ4U,ε)Tºª¥Žf ià 7.[«S=m½zmÓÓÉKEQ«0¤ÒÖS}Õ3Ç5-*Ǧ5­«Q4Œ}˜ìt±Ú” #צ|F€•Õ“äk'£Ž9õi*?ÛÂ92D¬è}XöÝñ^º ôÛ£=j)Ö ¿W­2*÷ÒULb§¥…{¬ ½LÑ5%:­µ±Ff"ýJÈŒ’ Ž™ÐÔ“Oõ«ýXPÑIF^DûÚרw©"Hè¡mGâv e„—!TPÖ"ÿÑ8 gðÙˆ.P¤:–®¾4Ÿ”¨Š%ŸS¤{NÖL¯úÔ'­‚㪦¡Çcé¡y¨ ±ÔôT14òÏöTñ< ûµUÑÈéã±ñG`‡C=ý# hl­á$ŽÞ¨Þúsuy,Äæ½bZ‘’½;Ï"ESST§í–#!4ejÞ*—ûÏe²ˆc”p  J×=JhsÓ¼’¼KYW˜¯1u•E$ËMI F·óE ˆ¢x`»úÍÁ\ÞÏÆ”áÖѨ>}s­‘ãx+)V¡qzii)©:‰©j1a%,TôRMPÒ}KO?$jDƒM:·ˆ}zu… X’¶_=Yš="Ž:™`H¶óH% RSxTY\¸ëkò}±ôÚjuŸõ~]hÈÔ¨½f–ªš6¢x$ƒÅÔ´Íâ†u«ò¤û·‚JRRX‘èÕb4axŠŸŸMË©”²ŽïåÖ)墠¨ž¾á©¦ v¦‚¤ÃTþY'V0˜é|žF‘§¨DV&DNyú´Ñ¢© dtÜÊaž²IY3;ÇCÒ³UÅ%\ÅGQ÷QD•oS.ª¯$©g)'šœ…Ô¨K1$?Õþ¯õy)©ñu¢¸UÒÖ¥$ó5ULõršeqNbû¸ßJš‚VÄ•(t*µ£àON*àzèù"¢š­d)GA¤ Na˜ž8x•©b ['ÉqkoN¬OS~ú™©|èð¢T-\µô†,§KÀU’ÝâGd: ­¨“¦Ì ÐùõGb¼:2%l”ež¥\I øÞ©qÿgÔmWãÑmÔrO ôt>í¬ÿcwVÀ£‡É ;f)ÏñjH$s ,¦E†yZ5‚¾«ßôû‰yü¬R>‹‡ò=6P¬uv >Ïóuz]ѳj³tôÛ_je›?Q‚(êæ¥8)«¢Êd‘ãVwk† ¼eæ; ®&<ú™ù~äÇàGϦìÎ rm}¯µé±Ô‘E˜¢M]?‘<ž8@ò‚.^£bln>žÃ›œÆÕpÄt"·™n¥&Pù³^¬CãÎG1•ëêÇR’%d`â^R([«(Y¿7ük¶4‘ÖkOòôY¾OTWý_—GÓ¥¶¦åÜ´±WÕTÉMFJI;Jìdq§QT ÅG±´S±š¯AÛ§´µUe$ŸŸüWC¯cíºiðôh‡Æ¸Ù#sQ"¯èA{4ŽÂÈmý}˜<j ôD.™žôòè±åc;‚Ü,‘2X"`•¨('‚ÄÅJåµ’ÙK¦\úÿ¨t¶’Qñ.?Õþ¯õTÃl‡‚º…YÕŒutì*#7*·GàÝ@#ñþ>άewŠ0ÔבÑEáð¤gU!Áǧø:ª?”ýsë­ÙSh£¢ ­ZœÆ*häa#RLŒj"†(¢’q,Ù g7çÜoÎ;zZø†ŸÉÔ³ÉÛÅÊŒ€SÏ¢G“¨Z Yš¦¦qGâûéÞ9Ù(æ“Îð%Lt“:ÔYî @Eae6°‡."hÝB±¦xþ_êÿV$ÕˆA'=w NPYšC<˜à‹R骈Z‰£Š$h$uU'Ƽ€Mþžö%$…éʼnV”&½s¬¥¥ÍIO>mš3¶¤ZÌ+«³½4•Ôµ8Ù$Z¢%§Šܰ:# H²Ÿü1‘§«Ó9?=oŽ’†JÙ’«![ƲOJi²U© ý˜sᎥ*Iu6ºþGÓß’R½ºEzaÝëEL†yÑ©¤ŽšZŠ‘EQQ ,Ó´rVÄUt™š–8¤§t:ƒF§7öï‡)î §M `HwüiR–u¢©¤¢†§ME}(ª‹ÌQde©Ôb,O©˜!acÉ?Ðëû:WS§ +ÓõHÃ2(¸€dL°eÕ$)3!5,«oYCX)“¸÷ 8$œaKŸ=+x¶§±²Û"& bplist00ÔUflagsUvalueYtimescaleUepochQ~vŽÞ;šÊ'-/8= ?ÿÿí¼ÿÿÆÓ6ÐÿÿÂ Õ q900nB1969C38-329A-4D13-8303-B2E8FD00555FQ·Cö*«35BF56C8-01C2-4091-814D-23969923D280bplist00O,€?€?€?7… y QÅÿç AppleiPhone 12 Pro Max back triple camera 1.54mm f/2.4 N RW j ‚ K  ŠM ’M š ¢ da/Odç   Aÿá Whttp://ns.adobe.com/xap/1.0/ ÿíxPhotoshop 3.08BIM?Z%G?095235>20210316720210316<0952358BIM%o{V(EfÈn£”áXDÿâ4ICC_PROFILE$applmntrRGB XYZ á  acspAPPLAPPLöÖÓ-applÊ•‚%M8™ÕÑê‚ descüecprtd#wtptˆrXYZœgXYZ°bXYZÄrTRCØ chadø,bTRCØ gTRCØ desc Display P3textCopyright Apple Inc., 2017XYZ óQÌXYZ ƒß=¿ÿÿÿ»XYZ J¿±7 ¹XYZ (8 ȹparaffò§ YÐ [sf32 BÞÿÿó&“ýÿÿû¢ÿÿý£ÜÀnÿÀ8€"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÛC ÿÛC  ÿÝxÿÚ ?þ=.gŽ-¨ñœŸ½Ÿ”^*“éžšFØ3 †¯jÕÆû•lj<;ó);æ¶}ãoü šµ«à]ZO:ËRû£‚.×Ë>Ã8õ¾;ÙMmøXgÚBÍØé:”Öî¼á›'ðãšëáñÿÄM0¾Þ¦?1V#ù‚?ß^Â.´Û¸.Þd.ñÇR+2÷Kñ’›ç€6sŒò8úzûÔ[ù€î ø•á}Q¶ëº{Ú¾Üî Ï=FOzÚ]?ÁºúçO¾Ž3–Ǧ+ÇÚõ {5@UÀÃz“õ¬³m¥^>$K7/} K‚}öÙ¾_GûÝ5tÎ~SqéXZ•ž¹j3bü¶Ñýp²xƒK7CÔ§·Eä)rËÇÖºëO‰Þ<± ¹¡ÔU1ž©#c®8"¡S×F ŽhZòÆË I´dŠ¡s£èÓ(šâÞ9˜…Âà䎾µÓ¯Äÿ êr1ñ5„Ö…£6Ü€}p§?tÖVÿ ¼JöV¢ì¦v6ï_›ò¡Æk ®‡L¸ÒI“÷×6$»nÿ£ÎGÒ·tü3tçZjvüoI•–B£±`Zô«ï…w~^í>èIìãoQÅyæ±à]rÕš¡Ê˜•V`úf´…Wöµª~|B°ñNž5½S Ñ(Yí·KjÃû¤ýä?Â}:×®Ëàm7Æñ¶¡a4KûèS*éÜ8 rAîFkóZÂã[ðv¯­§3ÚÜ)ù_iÚÀöaéìzWÚßþ-Zø‘ÒãEfÓõ›dÑ uÎ /v_QÖ¸q~k¸šÂv6¯þ^Ù¦4Ë’¡¾o*]¹õ𗉬ÚG‘#™;5»üØôÇ\×Ù¥§ø¶ÂK‹THï@̰“þ¬= æ¼â޹¤´w>™b»+¹3ŒAû­šòïfv'sË­ïåÓâû,ðJ…N3 (F~½ZׇW’Ôî›x!ŽÖ' ƒíè* Ú[ˆ:K ˆUx”žêO5Þé^/ø1âó›r‘ʧ€‡kÓ¥tÒr‹½´%£ï_ø'ïíáñöø¯oñgáµ³k6:æ†%)ý§§ò.Dkq 2ÑÛ113,rOïÛà¿ÇOƒÿµÿÁÍö…ø«G®hZ¼~d òåWŒâKiãl<3Âऑ8©Ršç‡<§¤ìö7M·!Š“Ücö×ë7ü¯öâøÿøø¡¢mn5x¢êñEœJqêñ¨ÏÚ-Ô*ÊŠšà4ˆŠÜYÆ_ m=¾ºþ†0Ò¿<_ÈþÜ5½'þí8K¦8·Ö,Ôµ¼„`–Q»àôltë_€ÿðVø'Õ§í!áÛ¿ÚcàîÉñ#A¶Xu­*$Q>¡¶æP€c3&âblüêv畯ÝwñF‘ãÏiß> ßC®èúŒ zfáÒæßBÀà¸W8`1Ö³ôg8Ãíû¤ã‘ƒÇOƒÂbª`ë«èɧ4Ö»~Gð/û~ÓqþÎþ4 |@¼i|â¾ošÇv™~d \¢‘¸$¤íº\¬¡ˆ ¼ŸÛý~ÖçE»Ðn +Åwk+R °v=0Gzùãþ ÉûÙøvÞóöÛýŸì‘¼'ªÍpþ*Ó™BÉ¥\OòËr@Òe¦nŠFÞ~BÅ~-ý€jÕòî—ñö_ØÏãïØ5[sÿ ź ¾§­¼crhz¥¼† 5Ž6ìt·QmPUD‹µßö寗0ÕM–¢Ý‹£ äœ6zŸ§¥r°øVÃ_šK}^ͤ³»¶Ã¨#ÊH#§Ð{ׯBv•™ÍZ<ÊÇ×R>¡àïYø·ÃÄL#E•²“A0UÔ‚§×šú»NÔ´ßéëú3ï·¹\©<2žêñ‚+ðoþ ÃñwľøSàïÙOãÕÌI}o ö~Ô˜àj:UЇM>C€êÖ"Z y–Ø–häÇêç‚<]‚5ƲÕ[ÊÓï_Ÿá†BpØzþuõ,Eº3ç3 ê}‡ÉÝÍW ƒZ—6ÆÞM„ç=Åf¹ä¯¥{GÏ‘‘‘ŠŒÆ;œT´P!®hdÇ55!PzÐf\÷¨±Î*ÛFAô¨Ì>ô "±QMdSVŒcÒšTš¢@8Zw æš4ÈU1‚ÿ5Jaa±íSyAþõ9bU÷ žFST~qÍ?kjщM08Í+)²sQ„!³VØ â›±j”Jï÷i¡2¹dÇéQ• õ¤YXdj/ït©Êæ¡'9 ¤Ê…NiT6j×–¿çÿ×Mhý(Èöü»E5ƒT¡ÔçL¯C´äã½:%aÅ98©b€Œõ¢” Ó¶eDPŽ•d¯ ¡c$Ð>¼Rpµ`Œ 3`šŒ"‘»½ ðj]‹AE4\)󚀯ÍÉÍYl•[Ž´Vp1´Ô@ØÅX“*JµFN(6¸œf‘MHpÚ‘ŽOJ~„J µ?`?7¥9@'€¤Ž”ãHb(*@6JRÌ>èâ€9¤Ç8öAÏz•cé·œÐÿzœsRùOžiâÁé@"§“ÉÁâž°*(lgÞ´U6ôix9‚›#D%sœçÖž#ÄdU¡O"‚è($¨"b¼sN1ï!qŽ*ðŒbœ`Æ  ñ8=*O#wÍŽ•i¡-V|Ÿ”`Ð_Í‚GJ•­Ã'ÍÆ+Q"Á© XZÅòTƒOH†:sZ¾PaŽôÕƒ&€(˜TR˜IúÖŒòÔŸeb3šÌû9+Ö…ˆ—½j}•±N[vÍ4®„·ÊÞԦ'݆kTÄHëÍ7É;Bç¥>V9µصaSÒ,x>Õ/SÅ&ô±,U]Çš–¿çÿ×Nµ•ˆÈÅBTжê1‘QÒL…\zÑå|µe“Ó¥2µL§$Œ÷Œ¨Ù08«Ž œTcƒPÊ+᪇µ]¨¶œôýi\.TÆ(«rpE ËRÕÉjåzvTŽiÆ6"Çê)r°åd+æGJRr:sSqÓ¥ ãùYq’ivƒÇsJAÆ=iÿt ÕŒ¢®1F:f¤ÀVÚ9£`ÉÍ5WžzTß3aA¤Tlb¦HÆ9 ŠqISãŒS<¿óþM5@<qLt§,Kåäa©I‰ì7hn•`F@â®1R+ÅKd'<Ôª¬Í´ñN@5!¦š`F½p{T€äf‚¡ºÐ*\;µ"9¦¨àԸ㹠EKåŽÔêZ°€OJ iõ(AÞQSíQK€:PI¥ØÕ5Æ£a©©ÈÅ0à?4ñÒ‘—'4áÀÅQEQE4EU߸Æ™æœW£¿J}×uè)j$}Þõ-Zw¡7?4ñ•m¢”1œËŽE8è‰ ÅB@Ý‘Nwšˆ¸íUÌ…ˆ=©/Ü^ eH*À÷ÏVV¹«A¡isÞÎŽÂ(Ù÷(ÈÞ“Å?ÚÎ8˜[ʉæm9Á2 îúÕô…õ_ˆ^#Ñ?´¢†ÖÐʤÂò}æ|§ð~µà>6økâí+9®ø3â‹O‰Z³|GÁÔ¤ŒÙCm#f[8”fG+Ù¤o—=—§5ÕxëQ—]¹Ñþ¼l.n.ÖæéIÆÛk\JIÏ©À¯6²ð.‡¬[/Ä‚´0ëZ,í3&[…EÜÁÔ‘–n~luç­yφ¾-x›Æþ<Ô<]ááÃÛiË`L“·4Ä¡Æ qàg¹5kkݰ|²Þx‡Uñ$yäHמȸÕíp“·'¡é^ àøz(LJt÷ÿVD¿,²;’ÌvõÏô¯XÔu ›”k=- ‘€ÛÁ¢‚\®´+xÆ–úëcj¸?òÌ6çÛùW=yâ/ý·ËÑìÒÀ² ÎA'Ú²—à=|IªDVMÁœ|ï!è ¬}+Æí~5kIÚõW·´¸˜3 $&K•ùAUÉÉã· ˜§sÔõ/‰<;ª‹[èm®@„Lâ4Ø‘‚N0Ü‘ï^Q¯üDøóâ¸ÛÅ_ ¼;ä’;¥iKº`„Ô1Î+ƒ–ÿã7‰ç–]N].9c(nïÙ™›whâÏnåñÏA\5ÄŸþ\t¯]éz3W˜ÅîXýâ2z𛣢(òŒ_|c¯iß ¼u¤k—VÙµ‚v•î­Ý†K{ ×À¾/²øï®I­/‹-¢,ÌVÏö˜mc••YŒg•gázÇ‘_§÷/¯üYÑu94߈]Äq[±˜_Á¾³w×"ØÂ7f8ÏjòŸÙëHýþ#Ø¿ÃÏ èv>(ñô“—•b‹í×BTþçݲ~@U1Û=®jŸCò¿ÅŸñ"¹×®&Õïv²ùŸfµ?;–åPœn@aÁóŸøÇÀÞð†±­þÏŸ u‰²iñ@×Þ1ñE›[Í&æèJ±dÎJ[ÙÁòHU#Ð~;#½ñ¥þ¿ð»À~—Åš»Z›íNÜI–í’_† À ››¹#¾wøÝðgÄ_<5¤k´7ôï øcÖ×SË®i¨--§˜íã2º‰¸,λÉÉEÁæ²æLÐÿÐþ>n,®¬¶È—b>_ÏŽ 1’ÏQB%‰pXdúr+¼¼Ô|eáÖèRǪڟ˜Cr>|Î×ìA¬Qñò/üVšÕÉÇ›j³Û<p=Jù)5u©õ‡|?¤Dßh¶i­äÝÃÀÞXãé]"ëÞ9µÖÓRI¢`Û˜Ãdö—ÔÛ¿Âl…Òµ3o,½P1ëô#Šüxq>$q°à³aˆô=E)¹'ª/Ë$lšþ˜.Bàn´ÁãܵZ-[áæ§/–ó>™7L\DF±Æ*üÞñƒ‘-”›W’WæP>¢²gUgòîÐñÉWµ*K°ÒøöõïG¾ŽõãäoOnõ™}£ê–y¹¸†HHŒ0­ïšÈO éërFœm*àæ7*pÞõ¿m©øÃAÙgc©ùä(]Ô{—?Süé5Ùýâjç;g—̹ˆFë#òÎA¬»Ý2ÊhËŸ˜p‚=1ýkÒe×µÙŠ´È®7`³8Wcêä~µEbð©"¬—/¦JŒNËÈʆÏ\0ì ëarœ†¨x³@˜MáíVâÛŸ•Y¼ÅçÙò v–þ#iÌW]·¶Ô6ß–?)ÿ§¥V¸ðF¢B]èwpê6íÉ­¸÷Ns\%õ†£¥¾å²¸P ǘ¤î'×Z))i$=QîPüiøq«BŒt›»6sóyKçFØïw }*Η¡x ÅSGâø,5LBI4`ŒÄý;WËÏ)›|[˜ç=AüaÞÛÙlò¥ ¯*ÍÁÏ×­i 1fRzŸ«ÞÖüW6÷÷&8¯­Ûx\<þë~ǑЊúóPÑþ&èï=Š´ÉR.-Peô™èqËÞ¿toxÓÃx_ k7v|¢$w(ÙSïÆkퟂÿ¢øŽñè×nº‰ ÎÔ‰Šý©FI1w,r çµÃ‹Ë\ÐÔÖ–#—I¿¯ZÚÙj_cÔÓ’„«cæz‚=»×¨xÂÛ››¸U%Š®âþ˜Ï¨C­nÄ2±&Aç‡\ãñ,^ ñ•xa.Ñ£©`w`7±§­W¹·Õm-ÒÖéYa-´ŒIäcŽG©ÍZÄO¨ÏÞOø%'üA¿d?ˆçá§Æå¹±ø[â[ˆäºûK"Ðogm¯y…Â[HNë¥$*7ïŠB?±ÊŸî¢øÕà¬ÞxVókÛ$pÀãp¼‹ýXÈ/·€§pã5þe:/Ší¬çKyÈ#œÃ#žAÍN_ðFŸø*Ö‰ð%ôدöŠ˜EàFX´ß k,ûàÒn§;cÓ®RÒf!m%#vû;_+w‹›dëR’÷×NëüÎZän¢ù£ú:ø£àèRÚçãŸÂ¸!Ö4Vø¨tb¡àÔ-JíyU~~aüj1ׯñ»ÿ.ÿ‚Aû5kcâŸÁ˜7ü+ñÚ¾ñG¼ü–rc;7ÿV$)R#Àd]ߨ.«¥ûxÈj6Û®¾k÷ Â2ÿØ·3·éÙØü£¢“°cåã‡À_ ?†uy4½/|;ñ]»G¬è Hg3Â8È ä ©ä{x6a<D›Ó¨áiÇ•üŸéê2ÿ±wí9¦~Ñ?‡„|U ³ñ׃àŠÚò6rP²åŠö%<ãä™OÌ’êUÝ)d'ÑF« (’6ТAÜF9Æ~¸¯ÄÚãöfø‘ÿæø÷kãß…wwš¿…u-׆5«³»ÎH°n,5 È·QŽ$.1,`Hs s_±³—Ç?~Òÿ<‰É‹{ý=_|–7#Ç ààç*HÃ. ~ŸJ¼+AT¦ô8êŧf|aûc~ÏÞ øß¦ü:Ótm^ëÃw¾ñƧiu¥l†kwhDHÈBãåÉm¤ØÚÙRE}©ðã݇íàÿèšÀþ2ð¥>ƒâKWKÉ|›µ_ùãwÞ8ù_|dåjïŒt¨¦¸’ òV&GF ¯Ž¹öñÒ¾,ø¦|SûqNÅ)Ȥ#w>•wnÓó Œ©?tf¤¢ÀíFàx«2§_8 p9¦®: “úPˆ:Ð4¤`âœÀ“À§2g‘@!ŸP†ç$TÁ¸È .sÅ4üÝi7­HŸz€qŽõ%0±¥;æ€ (É=i( RsÅN98©n[Žì(T ÕˆãoJ± ±'kF;DíœÓH &95cËÇ$Uõ²Ú 4ï!‰ôö§y^à`Q·<­izïæž±/ ”¤µJæz+‚)áXŒâµ±ÝÅL-¶¸¬Ù¢2c#Ž*acZ‚ÛýšrZ•$ã4+_Pe®Ã"¯Ei#r®$ «æKb¤1FsÁ­£Áæ•!+VcˆƒYsê4<.zT±¡æ•ô©‘H5¢w)±Ã§5*7cQž8§ ÉÍ3KMd$n§“Š•€ Šf´d)ÀqÏZ²ñœf£òÛüÿúè4Š+0ñM© ã½5Å„tð™ëÒ™JªM?iÏJiSÚŸ´úÒªí ¶µ.Æ©ªU\rh+æ¯Èÿø.¦³£Ø~Áþð¾¬&ø‹¥ÁÒGït»[IsŽÀÚçßï_¯^VþkðKþ,Ö&°ýœþxxGsãgS88æ×E¼‰&sŸjç©ÔÖŽçÍ?ðnF‰ç|qøéâß(ªÛxÃvñ¿Q¾óQ¾vdª Ž cÚ¿¨³¯ç‹þ ÆÓí&øgñËÄЀ$:¶ƒ¥HØÁ&y.@Ïû³ƒéŒWôKSIÙ•^Kb¨L€*e\rjJ‘S¹­ÜÎAЬFH©Tõ§S‚ÍG0 ¢¤(Ȩèæ¯÷j™þíCM"ùPÐÔ#œ´Ì•i‘(? L ƒS:yïúUyœC Îz ÉúTš"20qLØ â¹x¾ÏÃ0ê—JÒ@ì7ìûÛOqëŽõ׋Ë( ’¾b` žçwN=M&®&®6'¦1NòÉ«GçåW úÒyoLebsN¶3Vv¸&轨“QQ9â¯4ca±ï\äš­ž&·¨Ê"·…ÙÏL/ùÇÔÐÒúµ'–q€3\Þ‡â#Ä—Ks$D…ެqÀþµÙù%$òs’zP#8äÔA *ôä[¡’aòŒŸ®+Ö¼Ay§X@4ës<—Òbe#j1$ýOá@,SÇ*†C¸â¥úÖc˜¼9áù¯eÌ‘Û!b–cž‹îsÅRðωlüU¥ÿk鑹ˆ3®ÉÊuéÜPB9àu¢ÒXîáó¡åI#'¾*¾‰ª[êZkëV¤2Ü¡x8çʯh1Iq¦As0ȹBG,Äæ€c;zSŠ‘VTÚÝkY¶6Cÿתæ§¥ø[Ã÷Þ$×&ò­4øšY˜.v çôÇÖ“‘-– 5Hyßüyoâ¿Ûø™ÀIî`7I ‰Ëya»å€ãÖ»‘yöOÿoꀪˆ<æç d…©œ™&‚ü½j\¾µÉø/ÆšGŽìe¿ÒRH|¦Pc—¸ܭœÇÒ»=‡4à*2piÌœñR”còÓ>U;22+@(¢Š@X©A5J*^¨Í:Š+6€(¢Š@QEQEQEQEQEQERWÔêþÕ=hl oSÁ.AéP&£f àSƒÉuPK”ä˜N»€ÅaêZÕ®—nÂå—yjucëÅyö‹âÕÔn&·× ’ÉzÄ2ý2Gµj¬É LƒÛ­C±JNÒ=+‹{Ù4ù’D—|ü«—鎹üêö™t–·’Ë>r'¥&ì$îu±¾âk€ñÑI/´}!ÝÖ+ùN€è?„ýH5×Áªé×r•¶‘s‚Hï^]ñrêx4x5­9‚Íeç&¾køÓû<ønåˆü(.tÛ‹i<ø&ŠGÌ2® :Ç „Ž}Á«L¤ÏiøðÛÃÞ*Ó!×4xšVÕ•ã¹€š#ÐF !é†È=Ç&¾ ý~j~!ð¶­ñâø‡×u‹éf  ±A;E„ÏBvnÏAÐ*Þ‰ñëâañ]¯„|Ks]›GoÖCWIäÉŽ’gïŒ``œuÄ?±Ïü,½Közðg‰ô˜ÖïOhn®¦†"¾cI<ò:ÍÁ@¯SÖ©-=¦îgO \Fº¦¡sX0•–]Ç9ázd÷4Y|øn²\ÚjšÆ©uœâ½«BðÇÃÿG.§a§Úé¶Ò¦ö•ŽçÆ9v;±Œžká«Ï‰ â­ MøgðRM^úêG»º¶‹rFfB2Ò9Ú<µÉàWsñò_ˆ¾ðìž øŸ¯èv‹in4ZlFycˆõûÜ»•è6òEe&ÙÐAñöÍøO¡ëñøw·:Þ¥wŸ³Áf=†<¡+’³Ðu/¹Ö>4Üˤ5¬‹òá Ps¸­x¥ÏÄŸ‚~ ðý·Š>|;Ö¼Sâ=R/.ÆkÈÖÀћΜ™V6êÛ#éÒ¼¿Aø{ûkþÕ$Òü ­ÜYxÃ×RI6¡qg!}´Ÿœ¨M²]Ëü±X÷z¦Ì´‹ì…àÝ)¯®®uøˆÉäý†ÊÕµyÈí¬(±&ÓÁ%ˆÇò®©ñ[ö“±¿[ï é—Ÿ­ï­¢ ¦é–aõ hb$brUœû¨Ã ÿ9¯èÁ_gO€^Ž )¼¨CI=ÕÜ«ö‹©#3Lãu 3€+æHþëž>ÔîüYgu$7zìòËf åÂÜEgÔc’½Ž)ßKš>ðÁïÛŸâÏÃá­ø›Çšß¯„èŸh–ØÅlšæ¹!S•ŠKœ%ÙÆÜ/0¥ñþ ‡áŽ>0Òþ'ü^ñ‡‰õ CŒ‹ mKÎûOÊ["ÒLŒ¢E‚9r}ƒàŸYøûÆ_ð©nÅËB]xŽðO,¬D9Xì‘”òò± #ÉÈô¿‰¿'ÐØì#2DÔžçåäW.º,òæM&æ9ÐrB°Ÿ¥ůYÂ$ž"ÝÂŽN>‚³ç×Th™ÖÛ^ü)×¢µ¾0Ë&AöHìC Ö¸øs$ê³Y]G"…È w6ÞÙ#5åWÇJZ`fæBãG|v§XZ\Z¡“M¸’ßnX¤*õÇCô¤íÐ]NîãÁÞ!ÓöLUX1Û„Ãgo¿jç.Vé[ËhCarDªzç8­?øëF_´¥ò] 1ÝD ‘õ^s]m§ÅÓ=°*Ñ‹[`h:(>½È¤£.Œ‡(žZÚPy–í”Ûú˜¾_Ô+vÏPñ49h/ÞeN\4Øvüë¾¶Ÿá7ˆ&òšú&Sœ,®#ÑS¾}Íq Լౠ‡X¶ Û¤äÞ;¨\­Þµ…½œªG¡RçWyíÄž&Ò!™H#tŽCƒŒ\µÏ„¾j³f BëMy1weëØŽ9íÏZä.>/jòF¶Ú=¼hðýó2oã·|f¼ç[ñ7ˆõØó{u¼È«…På¸Nk¾ŽvÕØÂuO@ñW€-|=l÷m«Ù\"å„“ AìWÖ¼Š9æ]FÝÎã•9‘ŠWO¯8«P¼–îé/™¹°é]Ñ‚‡S;³î/…ßµ#E4gâ€^Ã&~×íŒ}æUà9=HJûoÿ>øúÞ;øõ›d¼ÚÈâ3´¹_ⱆìpz×ã.‘¦jw³=¬­'˜¸ã¨Ç¡éŠô]á/Н9%•-IÇ “ ãfcsžùN‡ð¯$øà_xJÐÛÚj¾|M·ÌŽW úôløW¯Ä—öÎîëæ †A¸Ÿ~:׉Z‡#åZ£Ô…^dtºgÅÿ‡r Oé¥Ë€7ÅMÖ@0?àX¯\ð¼¿#]½ÿ€õ¹ŠèÅÉ%í%n~Çu'Ný܇†3Îã_æ•áˆgÓí"Ьïní¢‚â;«p·2o‚â9!pÁâ’6£xÙYR?¶ßø$‡üš?ÛƒÀ7°÷í©5½ïí,Lš^¯òƾ$Óáyã*j6Ä=#Æî&ŒYæó¬žOC~«õ_¯ÞqÔ¼ÞëßüÏ®?k?Ù³Gñ”ºŸÂ‹VQøƒÀž<|–‘‚Oe{Œ¤Ð°\$öì7Â댮Tä þEôÙ~)Á1¿k­[À>6³Kg i¬XÄ¥aÔô‰Å¥í³ ¥È¯¾&9_î3· au'ì™ûFbëíhÍáýUþTÔ­ãäùgwx ™οÛûö¼ý¬~:ÌñÅñ# >£àM~àöØÈÄÖS0ÆbFÉPð®UÚñ£^Ì}”ý•Gî¿ÀÖREg¿~èù*ßÅ>ñ÷„ øƒá ±y¥j*$¶“Ê+“ŽAŒ÷Hü9®ÄzlZ€ŒÜ"Ϩ‘7 •qƒÓ'8ÀøÏû þÒºßì³ã»ÿ|T±»²ðž¯¨Ïa­i×M‰ü?«C)ŽY6ÊdR³"6ÆL„©Üß¼Ú•›Ø›ë#šÜCòº0Ã$˜ÚÃÔAv¯Ð¹íª8jh´>øAã}Kö@ø½ã;ßjpGð·ÇÞ"·Vfòÿ°u;‘¶K€À[[†Çž´#“('sWëö•©Ýü3ñ‚êþY}>áDð†Ü¯I.:ìÎô#;÷¯ÍÿŠÿ <;ñ7Àz—…¼U§¦£a~±‹»wû²ªüø t<†µ¿b®Ÿ©êÿ±ÏÄnü" #ð~©pÅÿ´ôtÌKI‰cub¹ç;¦…C̯·ÜÁc,“êy˜š<ñò?g¦† ÊÖì'ãprA¦–‚+È>ëÏÐø{ª±e”4Út®~a·æhyëܯ~ô¯au(æ7"¾ªU8¦•¯AÓv˜P•&ÓŒÒV§9L Ó*gûµ E…4¨"EÄpi*v ph)FsÅH˜Æ)ôÐ:Ô$œÔôÝ‚¢[’Ù õ¤Ú¦¦(;SJÕ$•N3Å%NPg‘M)@Q€zÓÊÔ›€…¦7 —cRÛ Ðp´aiøÁ«0F¥TÂÓ‡dÇŽE(BO4E<ÓRí piÙõ[-ÜRª)ëÅX$˜Æ‘¶‘‘ÍW1®x¦”9©;”õ¦‚OS@º61Qmjº3뚉† S’&=j=†¯±š®qÚ€ UÁéOòsÎ?J™“Rî ¸ ¤T1dcžVUÀsÍ7<àÐj¬R Ž *Æ;аPÅ(Pr(„IÒ¤H—9Å9œºbšªÌZ¥ç·zpL¯½"‚¤ rØ)=j]«KEHã¥H™¦ªî©m1A<ÔG|SÑ@ÔŸ 8"€ž1IJh=¹9"§p3LÇ8©T0hüŽ´â7¶GjE › ô ÐddÔ”`8Ž3@'žSŠg ŠRÄñšpOZ 6) f­#ÓŠi‰¦'®*tPSx¦¬iÈëSª3`Xǧݠ¨=E(]œSü¼šˆ&ìb­ÀX}îi‹N «±!õúP2@‚;Õ9m²1ßµm´L@ÏçQùpEMµ-ìsÍ•†}ê,žk£krOŠi¶r8DØç²ÍDÑÀz×Jlà 5Bmn­¼ŽZXÁéUB•WO%±^Õ›5¸<ƒ@d¾î8¨™ %ñpp2g²Ó”ÝŠÞ’}¯èÜ_ËgüY«Ë­þÒ? ü5í_xMãoQ¬ê0ȧ?ì›ǽc>¦ÔV§Øßðo>û|WÕ¥N5/‰‹nGߎÓBÓ“`û×ê ~ê…¯Èßø æ’Ö?ðM?ÏÍLjüwâ{É201crú|xÿ¶p.}ù¯×“‘QM}ÈpJ$ŒTÁ5@ì¨3!Àõ­YÎI±¨ù’”‹øW¡½†›ÿ ¿‹$KÝfO*ÎÃ!VÞ#ÄHàd™Ysö\ã¯OM»"Ü^]N©l <ÎG¸ë_:x—ÄÞøN,4=jýcÔµ3͹òÙ¾ÏoÖA Å¥p§~Iè+É<7ñnÿÄ^5Ô4íF&Ã1•ŠKyFÚ&â0HåÛ’¼áFr(S+•ÝŒü_ã­atý6á¦gT1à úÂNzäp;ô®“Ǿ-Ómt}3F–ًܲ˜¦c xL4ƒà {f¾:ÇQ|3µ4é฿šöMWYCµ¼…Šå”sç9Ï5ê|-x²Ó^Õ•5´²Bª²”Cby¶¯ ò€3ÓÍg*…ÊÎßÃßm—Nÿ„¶žxø-£a”yÙ6¡löS’HäÖwíjÿÃ:>½i¯Üµ­·†­¤–KˆŽØî/n"!õ9ÁRÂ…Îñ½VÿÄp§…¬'xöÝÛÚùh»K¢7(ã#‚ ǽ|!à_O¥_xgÅÚüÐΞjéÑ;6Wí«˜€zû´=:ÅzW‹~.G'4 h—K=”Ïsr$ -Õ]‚ÌÂã-€r1ž)û[(3Ð>=x·þ¯ÛÞØÜ4êwÖÖÃhÁû6ÒÎËé€ l~uËüNñç†5ÿëqi÷?kµÔå‚É|ÙwG,˜g·Q–ã®Ex¿í!4Z¦­«¦A¤-o¡´³,Ìb‡í±3LѰ<€«’¤ž+äCã_xKãÌžøu®C}à-VÃC¿¹ºUË æ“+»‰"Ȧ38*ËÆi:Ž4ßSô7âVŽúOÄGÐtbú}Åᵎ#a»ìvQŒÈøþ'€CÅ{íÿ †tøü›‰-¬4ò-v§ŸopB+n ƒÁ$sëÍ|©ñ[QÑ|qã›ßh—¿é:ö›&» µ£: kuPrçœ;0;pãŠò~ÖzÇŠu×ø cm>¹©x²ßM† T&-ôÔ°Sbd˜` Ç-Éì(R-#íσ3èþ¹QçׯB® ÅƇ=x Iõéšú¦9a”áXqœƒÔc®E|ðÆÛÅGâµ£ë¶I¤ag¶çYLÚ„K±C”ÉHÐHH<×ÒžÒ|I¤êZßÅßij=ô¬Ül*ƒs²cjã®*®C=!µ†·Õ¿²ïBÄwFìp{V&‘®ZêÚ¬¦Õ¿r™GfãXŽý?¨®gYñ§†õD7Mq•ä‚&Àˆo|7=§­y燥¹Ô|O¥­È"'¸y ÈÃÙ-êqǤ¤XúdzsRÈÍsÒx‹O“Z³ðü ïyyJ ¯Sï|Ý>^÷5¯c}iz¯öYVO-Š>Ó®:ƒïZ¿N]Øâ›O@sš‘XŠZL æ–“À(¢ŠË•QE“@QE (¢€ *?3üÿ‘G™þÈ  (¦îdTm!5D¯ÍF%$u¦£Œí'&®:2ʦ‰$*»n>*&e= FÛºçŠÒȹ½Õ¡§†ÈQÈ2|Øöã®óÆz”ù6¢Ú2H2¼ã¯ÿ×].µ©}d¶šeɵ&@dp9)ÜSm´M.!¾á>Ñ1໌œCÚª) ß¡ÇØx£D{±ö8ÚiòX`œzü«fçÅv2¾ÝBaÛP‘^ÿRñ…£j×TX.í.Aý?d¬+ˆ¥¿‡®î8ÎÆŽE^>®MKb³êsºœÒij·¶wâʼne;rÑ7¡Þ¸ªšf±âï´òhÍ À¤"K<ŠŠÄN¿•s>8ñU·‡ôû¿OgŸ¨éÌŠ`–L=Â÷Ú‡®sÀ\æ¼ÒˆÔg>/ñ6‰gmfü™ïÙ²daÉŠ>b;d ‚Ž›WƒÇþ¿XÕÝ=kà¿ê~"»ý®ñÀÁ+æ[?ƒj/éÞ.†KmÇék®éöת-üËÕÉž ûT7'ÜÎ)BVM1½t>ÑÑ~5xré5ë3ÓlmÅË%¼Fd,°¨XœýMxˆÿi¿x¡olþh^&¸· ·œp˜íPr7Krü]ªÏŠî<5àßzjÍyâÍ&Î[í6ËÏŠ;Dî³ÌIE$ÈK¹9õ¹ñ?á^“¦x>üb×mô+7UqáÛ ³:1ÆÓ+ ¥Øgæ_õkïÖ§ŸÈ!~}xÂ5¿‹pø#Åúýÿƒ´‹ë–Í4ne’%Áp&uf—¸*Fr¯«åý,u[ ¿ü5¸‹LÑ-­Åê.÷º¬ïŸ”I$…ŽO¡ãh®Qø› ¯Æ ZéQZê°éöw²EµÜr܈v"s°kêünÖîü;ªx;@ð¤Ð\ë“;IsM;Çйu89ÜsRÚݳÊt‡?|?`º'†­µ?j f$¿k§&K‰—„Y G<….8Áä×ÖŸ<)aðÃg[ñüñË­êè²J±œ¥´#•EÇìàƒ¥|Qã¿‹~ÌŸ 58µ9¿´Zæ[‹¤}÷2;*Æ„©glð,OA^{à/Œ¿´ÿÆË›^éðL¨¶òOl±ª,CîŠ̉å¦NL“/=…v»BMŸLüAøÕá˜uÛ?íÈlà‡Î‘,ÿ´›i,±uŒõ@=só`q_-éÿ¾1j¿o<#àYü9p—ÐǦÛÅ#\]Ãå wfŠ2»´gr Ðð_ ¾&ê? §Ö|}sÿ(ñÜ—/cý·âmVÞæŠå›É+#eSï6À¹_—Z¿íûйCp¦9®h®ÑöÄŽgÂ[Tðý­ˆµ øî5 4­GýÖêRK%ÌŒÐìmû‹’9Ïì?¥ñGíe¦ZYÅ©x‚Ýí®5 íšÿ™khblD³Ìe1×x]Åx<ò+ðÆÛöºýµþ+üxÒ|Eâÿ‡>ðg‡m Õ­–…â‹ «>Ëï>e&?í ¤` ˜0ŒõÊ|Íàþ?ñF½ûTëú߉>,|Zx~ÆØ¥Ÿƒ|5£Ïcmtñå^;]6Ðx2pÇÚ{PÖ…GMÿÒþTdâ½ÁÞ.¿ÐîE¿Ÿ²Øœ²HãÁ÷¯=¤Ë•‚’³FÔæãª>÷ðŠü#âáÓg—œ†BpVàçÔó^qð?Úݓj>¹}‡)šžàçŠüÅI¦‰Ä‘’¬§ ƒÈ5ïÿ~9ø‡ÃwpC}1 29²NÎêÿÞSÞ¼|F]%ïSg}Z٣߯þx“H“v‹{çïl8éŠÆ·¸ñþ6žC*Ãò5îš?ÆŸ‡zõœrêÚ……ÄùŒ˜äldaˆ~ ×…|Fø¥á›ûà|4ÒOp÷S«mØzŒ0ëƒë^°¬Ý¤Žï¬ÆÆ—MÈëzrÈ\ÿ­ åà¯?•_ºñ€¥‹ÝK§cøÈóôè&¼Tø‘ñ V±‘õ B9€O™ü¥Ü`?¸¯°µ²ïݼ8À`NFƺc€_iœóÅ_d}­üAðΕt¶z-ÉÔK(fpQ‘èq¡®&çâf½©ÓbD¶]‡ç^J§C^q u¶upƒÏüªí¶—u9c!dŽLaSæl}zWD(Ò‚½ŒeVL³=Íð-­]™¶ Š£ž:uÍcǧÃëÆ ¨ }8õ®çEð»®Ü˜4›W™n‘¤UlžƒêÚÁ«Š?øKuàG-Än ªãÔzÕ:élMŸ;Û£MHhr1·ÍØ{WQeá=WXž84¸]¤Ú|À«”Sùw"¾­Ð|ðçFÌzSAu) ª²8Ü~³]¼ñF•¶²ÒÞp’LJÂ~+–¦*ÏCxÂèð àV±,~v»t–AöžXÚzœå^éáßžÓìþÙæµàd»(P{ô®[Q¹”[«fu,w‰”¿èsýf[ØiòL±éÄ۳ߺ$p:gñ®Iâ¥$R§cׯt GÃqH|1¦F³p"` ²Œò@ïÅy?ˆ.üI;ªjJÇœ1'ÿw°þUÑéºçôû¶NÔÅËÆÙUºËƒFê? ×X~(j‹(-Т½G#v¯Ӿ2?ZÁÔlµ¥o‡ßa|7â·òdÆ"»xÊôFü:WÓ7&ïW¶Y´Çò5ubËèÃБ_1\ëüI˜µY4ÙXŸ¸&VìT”ãñ꼎ȋKYu¨÷¨ŠIw0P;ã Ǩ¨5-MãQ¡n>&x—Ni£Ö´õ‘mŽÙ3…ç°­ /‹ÿ ç ºûÞiS?Ê–îbúù‘†ë¤ñ–›â¤2L5TŒ¤EN#¸Ý|sèy¯Ÿï´Û¯ˆmõÛ_(’@Vçzöϵc,4Z÷‘¬jµÔú“EµðïŠ/|'ªZêh3‘k"¼£ðïÒ½«Âz÷‹<1©éúŸ†®®ô}_F¹K>î,Gskt2b¹œ.dÉŠ•e%23)üɺÐ|=¨Ê.!ܲ¡Øè}C)È>ãšì´xÿÃ#Êðï‰/Jr!»qyû7›¹±ôa\ÿWŠøe÷–êÝj£wì ûr|=ÿ‚ üÿ…#ñºX4‹º¼w× gû¯´ùdz¾œ¬Kß<—·v1±hÚ9dû“Áš¼Ÿ­/>üb¸[_ˆq4ˆ… ÄcýMõ¹8Ü’$øƒ)Áþlÿ ?k/Žñ¾‘ñSš…¥ŸŠ|52Ýé·ÐGä<2¯2‚w¤‹˜åPFô,8Í}ß±oímðçþ Ãû:Ù|NðÂÉà¿‹> ò–þжétëÙSrÉ€¢çM¼”}¸` ²¤Ñ²¯ÈçY4àÞ#:ØåRQi7§ä~kÿÁ]ÿà×ÿ´íSö§øc¥Èž:Ð-^6Ò4ø·lYĸ‡RŠ34öèeM²K +ó²Fµùeû~Ò[nmgˆú¤FÎúÝO†/¤`b`Œ ·dçzááÉ9Lÿhþñ×Å['70 ÇþÝí“𒃂ÈAûÐMÃ#AèyþN?à°?ðNû¿‚š­ßíwð;Nû'íJæEñ•¯îgð®§+îk’«À²¸“ $ƒÞ]²Ý»Éw措Xz¯^†ÓIÆýO¶®´Ñém e[k+¡þñçøí_)þÑõŸxj+ÿj¢ø‚ÓWÓu2ú5ÜÖ—–“†IHB ,±ž d~Å_µtGøQñ&_³øÿÃöù%þæ§l>_´Gެ8ó”}Ö ãk)?b^[<ÀÀ­“峊(¢€3Å¿JéZÛ#`ªíiÎÞ˜ü©Ïb þ4¥l´m?1ô·íŒPJf*Ûät¨¤·µt~F¬ë„<ÐQÍN˜E—ukÏ5CÉÛÖ€)í Tƒy©8âš©òÐâ<š´«ÜÔ N*üqúÐV6aÈÀ«p@ÌÙrqSÇn´£ƒ€êp§¥G{~aøÖÄ À-Èü©±BqÏ­[7á»Sæ°$Ëñ pyÍ8Æ œT‘'Ë…'в-É^ZÍÉšX¦°î\U¤‡)©£¶Ç&¬¬DšWb¶¥‹tÚ2:U 2)"…‘n àŠ¨“=ˆÑGÞn)NÑÞ¥;€¨zTfVÛ“Ö‘s‚ õ E éK±ªîÕ´KË àÔ´æûÔôÈë@ìERìZ˜#Òå­bsƒV@`QELŠHP2qRykþýtˆ¿ÄjJ‘ŒòÇùÿõÔL1Á«ÆLó@H+Ȧ“šž€zÖ†…rëL1úT̤sM¤Øš¹ (Rjj)6JNáœsJx•<}ª±MØXׂ§šþEà½z­ÍïüFÿGiZiß¼nøy'Ô§p=Ê•'ð¯ëÁÒIÇ!›å÷⿌¿ø/Ž-dÿ‚‹|T½k£Zh0¿?,oi¦»0öÀpO·Ö±¨ÎŠOè·þ d,?à”Ÿf¼U†÷Ä:-æ´ñtù¯®¤ˆö!ÁϽ~yMßòìEàˆ¾þÂß³Õô ~ð‘i)½°ÛF͸tàgôæ¿â+[¯Kâ "ä¬R¨d‘‡EÝÔâª;Q^Lè¶ò‘YúŒ@YÍ#ýÕF$žØ­¯éýt©æ <‘,ŠÔ€r+ξ$x•ôK½7I¸ÜÐê’-œˆ¸ù|æÆæÇ=:S2åÔÚðæ­ï†äñ>Âbî«Ý¶qŠ»o©Åu¨ßIͲLU¸# F? ò W‚ÛÄ·6‘r“ÙéÐÏob¬s“$8ã«ǰ¯øñ–ÓáÖ‰ª|C¼œ[i¶úQi·žbkuo:Nß <}qRäŠTϰôGÚéÂMÍ1ïÛócwLâ¼3Çž,‡Lø£›$Â]òÝ~Ñ!“®â~DNP2që^gñBð'‚bø¤'ص-!.Ñem¤1å™r~Q’x>õòž³ñkNðοÿ gˆõ«-SQÔg7ºe´ÒØypmùWq<õ'&¢UR4§éŒ?4C^2ßyb+yÄ6èȇ–ôǽ{&•ªÛkŒ³Ú6"¸ŠC³¡ÌLàáŽÆ¿0&ñ&©á¯7ÂÆYàÑá¶² ¨áV–ñ‹˜óÔÈS;Xð úÛÇ?&Ñüsià j[‹>Ü\†ViÂ…,‰þÊŒG;‡{T?bɼ?ã eñ†«­\+[é‚΃;VHØm,~Ž0{†¼[¦Eá˜u GÌ0GL\'ÈÞn6àýN+óãÆÞ¿ÖµhW†Uñ-ÒÅÚX“l¸™¯’ÊÄç¹õ¯º~jSø›áL¾–ý+[i,æ™csÚÞn{nB ì 5RäJ âŸé6zŒÉ-À!•ü7säwk篈^,»Ôü£xå™lÞökQiðe„ñóg‚ ®cÆž'Õ|YðÃLÔ,¥U¿¶†áÖÀ¶‰Ä7H*3Ç<ô¦x³Æ¿ð˜ø? ll`»µðåý½½¬ÎùH9bÇ8RI öªrHq‹¹ÄžÖü!­ÜÿÂ2öµt·‰¬ 9³™7yüfc” Ðý•5;-gá½ÇRÔ[鶺bß fàÙÛ)`"qýâË–¿Zð/|UÓ¼â»[ý(Éuáß-ι©y2ó@íåD¨:³‰IÝÛhÉàW‘ü6ø¼<#û;|LÒôù¡]VÿU˜]Aæ’‹©(–"\cäŽ0yÀÜÖR®‘¯³lýø7«jw:mÍÌ’}ò™µ{Ùâ´eŠy8à€0sŠüŠøWû_i–_u-sÅð_Oq­ÍXÇc(K9UVÚykœW®x;ãâi¾5‚Ø\^Eyg·ðßĨ§ðF­¬cRÔƒ¯ÙîxK©gGGYxʨè8är+ó·áÿÄßx§ÀÐk©[@ðœvÚ.”ñnkcvà™nf †Ú¬<µç'9ÅzE·ŽltoiŸu“^ë-e«C¨G©ÅÑ›3Œà[®^z0'¿:¡smáoüCе¨íþϪßÞà$ŒÜieÇ|«¡åädÜv˜¶•¬=«:=’>à°øúß®îT+{fI„ÏœH¬N6¨ùùÏÊꟴ—Ç/ê:îå’ÚÖkKÔ¤“ÊŠ8t–w‘7f8I1ÿx±Á9£žäòj}û@xÒòÏAÐ5§ 4Ze³5‡öŒ‰.ádGÚgŸ–B¡1žBý+Æt?ˆMñ/Ç:&ŸãHÍÖ­}e¦Ú^Å#°+;Wl‡)êpÌq^u«x·_ø×àk_kvQH—2ÚÉen‹²@ðœÚFà@Œ¾yN¬H$×ÌÞøcá—âæg›ÂS^k:„ ³FÖïˆQ#^s¸†ESÉíK™F™û=ñOÅ:Üÿ|àOÁ¥øbYêšEýÞ©3,âÓÌmB(‚bO—÷Q©*‚GPXãŒø'âßAM›J‰£Œ©‘PíÇæcÍ~W|Jø×á¿Ú?Á:—ˆ4ý+ÔZâ)uÛ‰¯õ9¯I GÚ0q€ãï?Èp{qоc>Sö»à÷ˆ"Ô5ϯƒÜßêF:„†Ý±ºâç8ÿU üª8.Äõô׉4Ÿ /Ás]Åi¥ÈDé4ò¢ç  }ço@:ŸJüƲñ”_ ôÙ4xoÄÇZþy™[ÉŽX¦#3c<0© cךÒÕ|C¤ËàÝã6“ys¨Û>¹ [¦?hµ19GóW$²r1ü SS2tÙÞüLÓ´ÿ†ºËxNÉÞkMFye·×cÃnX4‘ç¯ÝàƒÒ½†ÇâqâX4Mjit罈¨‘0 ¾ÍÛPm zŠñ?ˆ-Ñ>$xOWøÛ«Jú†«e²X¡@6ùò*¢”ëºx÷4 põ¯øíâ-GâÿÄëÛÛm—VúpÇs=³âÎÑ·EV3©l1ä¨íTª Óî}Ïð–=[Rñ^™âÉî¤ÔÓNIí­#”0–cu?9û£åŸÎ½û¾ŸÂvSÁ}von¯.%¼¸“nÕYgl”A×j ('®=+Æ| Ü\êv\$WpÁpùgçDEùߌ_—Žõì?õ›M{ÁZ~¡kæ6P¬!%¤uc—ÉêµtA™IXë•wTÀcŠ‘¾ö8¬{]±Ðm¢š÷$ÜL–ñ(ÆZGé×·'µib :(‘’1íEg)[DEQk  (¦y‹þýU}Æe" .«Ö¤ U š€NsPùƒ<Ð’ÁO4Ï3š¬ò.1UL§4mî#qŒâ«#«TМÓZaŒm¦˜ćÊ=êµÃvûTL«$eKvëUa±´†ÔC¹Ÿß“T˜\E'1¸54€;Ws§]¬jÒIÞ¼”ïõéRYkðN ŒA"œlsµº{Ö¢¾§N$ŒÒJÁr0MSŽT‘7©È¤y>RSEO0ÎO^‡XO2óEEš~ +¹9¯2>9Ô´¸_GºÒ/>Ô]¤‘c]ËÏNG W¶®¦Zp›RwÇÀÿmè¯÷'“Ë™‰eØpvŒd“ê1Rðï|5ñw<5«5´–ú²ß[½­Ì­¶+{hÛ|¼dä‘ÇÌ Æqíä:ÿ€Žâ>ý™¼Máé¼9ªéÚP•s#ZÄñ2œ¨‘ãsžw㎢‚¹OGø;ñ?ÇvÞ%ñ:Æú„6<xù¢…Áb8ãCׯ¡õÍâmêÇÄUö¥f"ËK Cå‘Îåö<õé_œZÇ…ô_…wöw:ÚB¾Ô_÷z…£«ËJ8Y$V±W9ãžõëž)ñ?ÅÙÖM/Q×u˜%ð¦±å‡ÔáÜYFxÚÈ™6$4œvôªHQÓÜYüSºÿ‰¯â Ñ£Zºm*Ä(Ûç#Θ€ïŸùæÐ8¯Ÿ>7|\Ó>|eð=õõ¼Vš-ÍÌ· }o‹²_)·‘ÕÁ 4y@}kìM[À“Ûê:W<ó_Z}™//#ô‰˜©9àgÀq_xþ?ü_ñ'Œü9àû¡&£X.­©ýª/Þ-¼d•c,6‡™ÆÕôô§sHêzÏÃÿÚ'öm¾øG*ê%Ó‘{¨KË5Â,W[hò£_™^„ö@5òwíñƒâ'Ä/›?|7ñf¿¡iº…½ô:µÕŠiV‘ØX¸˜˜!“åSnLaqógô§Áÿ…¿aø?àSáÇô;MbXŽ©,óZÄ“Çn…ó8'pé^µôÄj·‹´»¯¬^Ñog§Ae*Éu¨2áö © ƒÝÏLf¥Ì†_ßÿ|k¢ÁâýÅ–VÉ«ÛèA4ÛÄ“<Ÿ2ÆfÆÕ$ñ_ŸðWoÚ3àçŠþ xVÊÃK·ƒÄþÖSX·‡RPLö˜k{¥)“)â}À»œ t¯?h/Žd?‰Þ!¹ýœ~-èÖvrê‚ÚïCº¹†[›K‹ÅÜ?³mðKM&ç 0dc?%kðRŸÙ3öxøqã?„~ð7Š)I¾þÌ^/µÓôxñ¯{i†-­ååŠùú“ÚÝH\c÷‹(è2sŽwÇß¶wíßð»UÕ µð—„¯¾ÅÃÄÚÆ£-¨“ h£.¤ˆõ½‰¯ÕoÚÓö£ÒdŽ?‡þÑ¡ñ%Ü…Ek#\Ú¥Ó‘™$#lŒ¤çÊÀœs_<]ø÷Áþд¨|%á};Å3iÈÖó\j6̯Ø9“h(p¬sœöéYûR½¢ì|Gð÷öý¦¿l}kLñÆÏWZ£¤Fëû;D‚+²0PÛdabxÊá¸ùHé_Fx»þ µû!x'àÞ‹áO‰Ðx¿UÔp·ø)áÍx’é–EÃd!xõÇé[Ö2x;Ae›G¶†ðÇï2°ès^¡â½2ëE˜Ï,® +$À4góçšóO7C{ 5 TŒùŒ,xïœW”ñ3{³¹P€ÏøOücæKimŒ°Hpa‘ {n^Þø5q¨x~ô¯x~kgìf¨ÿ€°A]éÚÜ«<lŒÈL˜ãà~•øJòßtÖ×0HåvÈ ~]EJ«}Í=š[œz59‰Ò5Ÿ³È«ÚyeHõã¯Ò¶`Ð>!h(—Ú-ïŸ ¯‘'™‘냚e喡刯m‹DçÚ6ŸÇ·áY¶ööÐf}YìfŒí Œƒ?Bpi9 ßë5Ì@d}r9út­ý+⇂µ,K¨Ú\X¸$´¶Ò‚ úuà}){9-Dz?ÃoŠZ©áÿ»ÍlìÉÔŸë",xÝŽJóÔt¯¬K¶Öt°iBת ŽNbœ7ðçÔõSšøM´¯†*‘ÓÃ>!û<Ò6Ñ Âà†?í63ϧíŸmüuáƒ,j:N÷\[È%h'$ÅúJ‰ÆVÔ.Xœx"öõìïô©ôëˆÊ*·º§<`g=zS´ÏxÄV¦o jÁ¦Œík{© ÷Ç­z¶µáÍ#â®õ™mu«4LHP…—d¿£G~+å}gÀñůÜ]kµµÈ*X¶C0zwâ¹½Þ¥£Ö¢ð‹<=t×Ëœ¨™ÌdÀõS·§Ö¾»ý—¿j/²/ÆmãÿÀ+ó¥kÚQ 5µÌŽ4ýBÎFâÆñ0s ¤ÐÈ<Äçr·ÀÚ]犼5þ•áïÜÛ¸lH¤¡‰õí]Æ›ñ—ÇV×_Štø5˜€ä#“?ˆ õæª6Ø™««é‘û=~Ò¾ ÿ‚‡| ÓjÿÙ—n“ã­šËRÐï™Dö·±¨y´»Ò¹ù\0x&VGYP²7>¿ðÿâ¯ÃoÚ{ÀºŸÄ_‡–‚ò{i.4?xnò .xIìî¡n<Ԙ؂²ÆÊÊYMž‡ìQÿ8ñWìcñÇOøåð·íJ*Ce­h·QÈö:Þš³k)Eaý¤µŸ#É“åɉ¤CûI¯Á{þÍûbøwöÒýü-â"ÇPÓcÑþ$xræä›V´›{¸^ÞY-¤º°öc$±»FT·—ÅpÕE]ÖÂèºzöÿ‚›Š´µ±ñ¿üö3½ý€þ.øgÆÿ5fðˆ¯nn|©Âdó4Û¸·;i&6£Á0… , à*˜›wéçì¥ñïIý¦¾KâØŒK®i(-ukXØ”ÊÈP’ŽAõuÄÁliƒ¾ø_âÿìñ7Hñ‡þ&Þ-‡Š´;ëlÀ=®¤ñ?ÏcwÄBÕöùnþqÜ Œmþyf/ÛÅ>+ÂÔðVžÍ{k¶ºÖ‰##Ôôæl°œ,Ñ€"ã` •V,>³ íjж^ÿõ¹©Ï©ýJx³N‰¬ã–8·:FT‚3¹XaíÁë_"~Ξ;²ý‘¼}kû2êV1Xü!Õ-­WDÕ …$Ò5Ë©Lmm"•­nÝ]œÅ6I[åûÀ¾ükð&—ñá^¤š¿‡µˆL–ó¨*ñ²±WŽD`%ÁI€U¥xÏíð‹Dø±ðÃ]ð‰"Y,5ˆÄ7 Íå†á‡C·8#¥wák8;3)Ç™XýRøEñïÀZÃxYeþÌÔ%?fiü{Ýg”Éþ OcÑùï^Ó­¥žªÏ©em2ò0ñ)óùû7|oºñ캗ìÝñ^f,ðÌRg\ÎêN½£Zy`]ÂÙÞóÚù‘Ås»[µ«ôÓá¿dñv‘.¬È?¶tÅeÏÍ,d|²}{6;×ÖàêFVLùÌ|%̶.¯‚| ©³§Gy Ìw n‹*U`¶5$±êy«`¹¨Ûn f½D’<‰M¾¥-iX€0iÆ£“µ2ï ß—ÞœS=*:CœqA.LRàÓ[éš@Øá©wqA-†ÓœšuRd… gŠ0*ƒA(¢”‚:Ð1ÞƒŽÔªpsHNM7 KEQE=S<š@R(#©§Q@=:ô§ Í<§i ’€*2ÛªF°¤##æ ©¬¹§àši`h»gUfªÕ4 4[o¨^1š»åÿŸòj¼ƒŠˆ.:R•B3ÞŠhÝžzP€ÇQNU-À Ýl8(<ö¥W¥/ÝùZ”(ä8-Jl55;ÐÕ#Ò€ŒO&LO»R¸ñ@ ‚zRò*@¡y4ú‡¨T£wCÒ›Î*]¥@SÉõ íöÅ"gT¡@L hB€Óš 8À§æœ0à… íÝÅ8dRªz¥QŽƒŠfÐ[&®C·Þ˜ª·b­Aж•€’8Ã7­Yh½E9€ÉÖ¬“†¨&ì®-÷Ž 5¡#ŠÐ/PcÏÖ‚”.eyE¾õD!fÎ;Vá‹8· ¢ÄâÖÆ6Í«´U¨Ð…ÇãSyR)*@æ¤X˜ mèR]Ç"¹©Öݺšz*2…Æ [Ž ¶:šW-$2(È>µ¡| õ¦y.£**ÚÇòŒõ©lTÛOÆ?™TdT»j‚¥k¢í š“c)ùzTÊ¥ˆÏj{’£Š nC´z•F[ÓÖÎÐËÔÓã³Í5¸þì®XVlч(àÖ #š®ê3š¦í°ÞÆHµÇ ÙïùHëZÑE$²¬1Ìä¹=+់¿ðS/Ù‹özøÆÿ üCi«xŽ}à&§>–‘­gˆ‚ð‘#©vQËRæÑ¤)sIø«Æ ¼«E xãÄzN:ïK{ûÈ •—8ܘgŒâ¬éÞ*ð«2E¦ø‹H¹y™ca¾ŠFw~TI'¶ß´Ä»¯‰¿õ߈¾ ·.ÚÆ©=·Ú1<ÿbk‡ûHÌ[æK}‚’¡·bº¯Ø¿R´Ól߃wV©A¼g¦ÄÞ\j†@áò¿*Œÿ{ßoµG¶m›*I#ûm¸„ÂJ‚54kŽzWU~›õ Ý<é1ôÜk"XAÈï]F¶Ðäeƒ/Å@aÝÅtm äàUcn7nÆ(%¤rïÂi¢PWC%®[¥1mqA&\P©_zԂ؃“Œb­ 2Fõü«f r(”vÊãÒ´Ûä߃íéZYžùíÚ¯ÇTù‡àjdì;™‰Õ†I«‰ÂÅYhzâ­Á 'püj/qÜlQ‘íWgÓÖ­" £å©kŠ­FŠŠ•Pp <®*dCŠVÀ­¬dóÚ£‰>lžÕh·ËCv&Da6¶E)@NiA'¯´)b" =i¬0qRm<ûÓJœZ] ¡”SÂÓö-C‚]HU2r*}«@p)hº†ykþýtú)Û£˜CjDç4ÝOE4®€dâ¦Õ\riôTN0sRÓqš¨coZM¬>õOHFF)sŽì„Ç‘¢¥W»¼¶Ómd¿½.(UØöUäšæ˜¢!Ьñ6äaGB+šÒà´¶°šÈÈïl“±•ÎrìäaG~ü×y³yŸbDÁŒ„ؿÞ‚‹•fcì'¥Jª–nŠ ?AT[Y°Ž[T“åûK•Áê0 þ•™á¯_kãN‹R´HbÔ4ùnÜî8P¹Ï''5-’âÎçBHïµ[8£mÂic G}ǵŸ¿ücÄQøŸöÓý«5ûPˆo`RŸu…ž‡elHú#x¢ã}Ä>'ñÿŠe+!,ì—> ¸DˆŒŸùf5^˜ÀéXÕgU¹ýö[k¶ºìßá}IF’?i:U­Ú>Pù’ÚÅR?º¡·\Þ—â¨_öþž`l4­\Þ2Ÿ“æ“1óЖÁ“¬xçL¾Öü]§I"6©(|µlcS\ÌÇŒ,Q"/«we©Åz¾UÄÜ]HÖqB ®!ĸ'…8ã5mÉånLû*?è~'ñ5Ö»¬_°iÖ¦$€f<‘ÓÐ(ÕÃ~ÑŸäðn·£CÕo,ažþh€’ò;dã'<ޏç¥x÷Ãïˆ~¡i m@šTþ+¿€.á4ˆæÚÚ“(Á刯š¿j_6­àÖô«Ám®x×íº}Äm&ÂÍ0ÌJ#ä)Š=Êp>^ýi9Ü¥ìëÞðR·¸Œx†ÏT–ÚÔ°+³Näàç»Èç­|Ñâ xïâV±­þÉ_Ùã^ÓâÔ.o¡9e·’L›<¹ð\úWŒxã?¾+[\]üK»·ð¦—à9íná{<ì»·Y>ηR)Î$iWbŽFÓ»ày?‹>2ê¿üQâõ_ìÿˆ:n§w¢[Z[Hb”5¨2£ÈÎx.Ò¦H„塺‡‘ë2ñ¾ƒðãá÷Ã/|DOíh´»íu åä….yrÞXW9Æy¾!Ócð—ˆÏÆ_ þøGBð¶·x–¾"ÐN´–Q¦ N«x‘]Ü:Ž#|¹!øç4ý¡§®üñØÓ¬õ¿ŒVS‹]&/õ›{Û…3—k´ÚçÔyÒä¢(éɯÔÿƒún¡µ Cz©âoD×k+–âá·A¯@×?1fÉ€:Wà‡‚ÓQÕ~3ø‹@ðEÊÛø~âãþÈAf>”ñF*g!I»ŠJä‚ë_²ßt_øOPð¯Äÿ‰u¿ÅwueâHà``¶‚Õ‚AC…s²>@ÆÀæ®3Ô‰­÷Æþ/²Ð5âÆ•bñ꺖7†nQ0ÇS}¤q’çpzWË~&ñ3~͵N¤|UšâÉã´™í„hÓ!|¥ó2wV-½FN3Ó5Ò|xŸÃž&ø“ãÍ>'EYm²±Ô´¯hmo ¨’Öt;¼·PÁ_¸e¹O#É6ΨAhwŸ5 [FñÇ„¼+qwý”u{ˆµU™FÈíá çwð›”íÖ¾øiñÓÁ¶Z•—‡<‘Ý_i:<—K³$[­¡Ötä³BÈÁGps_ŸŸ~0Âmàï‡þÖ,ŒZÕ²ÌÒ\«n™×hDÀÈ‹p%O'ìñ©Ì<}áï ÛĶ‘Ç«5¾¨HÞ³Æ!%Õ›²ciÀÆN*Š”WcôOâWljÀ¿ͨ“¨Á{y:To#>.ƒyòJU~rB¯Ëœ“޵îÃÃ~-oÙ¦ãà~…j÷¿|~Ú~¦š€gжO-º—,#ij»}æßÁ¯‚~j–þÑ|uñÇ’ÊžŸÄZ“s1ó¢‹P›”ɰ2±X‚|£½–óÅÞ:ð熴 ˆnŸK¶øg>§ë·&ûsßO¨œF‚EÌÜ‚¹öªRó2qò=Ãã—ÄûÝÂ~øðÌ=Å¿ˆõX­4ÍSÌÙpú¬(ð]9ÊDŒd=Xí_1üIñm·Ãÿ]ü*ÔlEÕ¦ƒ¦¥¶¡2|‹=„–UûJpxÜ1ŠÂû;kºÿ‰¼æ]Øè:ĉáO ’—M#™o·Éß»=°%ƒWñ§âͺxGâ ²kPj¾´£šMy†Ô‚"~`¶ÄôÉÇ'5|Á®§Šü&ø·sð»ÁZœúZßÝx‡N´³i$;'°¼‚C'œÜÛŠ•lncŒ×±|ñ,öÚ‡‡|a|ÓÚé„—Zèö0Ͷ|¶<ÛK1Î×€Ydø3â=å׋mljtíbÎßQ…­Îcš%vîQŸ»ÿŽšûËöiø‡¬?ìÑ©x®þñ†¥ð²Ùô Û$*ÊEû}ªòKÂ3æy6î‚9 R‰ýæÍ.`”uÑõ¤ø³ÃÕ>ê>ÒÊÒ¼+¨]^jqÌbón®™)}¿Å%Kw9àgñÛ@×®Ò{„·qÁ>,Ox·Æ©$ìee'9f”#$×Ö~ñ ^ð‹<®ÝµÝœz5Åžœ/ßaY¤š5dèAØÜõ;Ñu Ò]KFðdvšÞ¡4eóá–!­»‘щrXsµF>ˆ·_õ«¯êVöÞˆô«¥ÿ_ 7[–X¢^3fbêy'5ç üM¬þËZïÂû8­4«½wP–îeœù÷3‚+(q’€CrpIâ±¼/{®ü¿i­e·¹ŸÂ6XZÞ\Ä.~Óy/ï'; ¹7tã¥Teæ¸øSÆ8×üIá_øšÂÝá÷ˆž)--w2ëBÆ?*$Û‚Ucr“6Ë.Òq’~ŸøbŸ¿hÿ Ü|gÒ¡àOëÇYÖ­ß÷vª7hb¹ àNr›#åxÁù¥á}kþ}.ÛMÓõ ﯵ®õ[i#’¨N×7O\a¥‘Žô*à WÕÿ 5½KýšÆ Û‰¥ñkøØèÑ;F°ÞßÍ!D >CGh˜yˆ\+ƒsŒV‰™ÈõßÚãæŸñ㦭¥øoY»Õ"š{Ï ÜyhÑKwO‹È!rTFp˜V_˜òóÈì¾Ù~Ï?³ä·ûnž‘i¾ŽþX-¼¥€ò¡²¶IÜ™(©ÎFX‘Î|ãá/Â|ñÏ‹|uðto|DÔü%w5¾¥qpÇLðíåüÎ5 F(<¿ßÜuO:l°bDAD’)òßËkðKöeÓí|y ÜÜø†kë…Šþþ šê†óF$ýâ†+° X([£;#ꃖ~,øÓñª÷Åþ!ðôw©à¤xàÓFæ^lQeXœˆÚRHÁ~ج߈ßWH¿¿øsàÏfkú½Ü²"Ý\ËìÄG$o#ˆs¸íl`瓊ô¿ üW²ý™þèð©g7‹¼[¨ ÍsYâ]Rö&uŒ6FñkÛ ËŽkäÙoá¥çíñÊ÷Kñ·‘é¶—ë"ÔYZÞf0ʾRÏ$€ñ†›¥ø{AÐl“Íkx-Öxãÿo$¶{ŽçÒ¼#öˆñmÜßà¿.ÄwzÜ‘èâg#q)ÐŒ¾gbz(õ¯h³ð~‰ð7H‹í·òÜ}Ž hu+§bHõÂ(íש'5ÓcEѤZ;És®Ítò5Ü›D%÷$)TtçïÖ¼ÿXÓ¼=âÏiò]”Ø3I lÙŒl™ð– zZþ:¹»¿ñv—o¦2>˜ÖS\K;P«Œ#`rÄz~5‰ðƒE´‡G“ÄHs%˼Q¨Š"A vÜÜýlæbãcÖÙAËsÍ7ހ৥Fd#ƒY¶™#ZnE4¸ÅE“šžfŒä 6–Ų́ÙÈjöýóÖ•À’iL@‚9ª0uã­C> Ìyüj±‘ÛŠ@Z$·‘ÜÇ€j¶öê#!n¦€,;±ÉÅVóÂrFM!s·e31•É &£k3yyÚÞ„zT¦FuÏûÕK‹h%Q½=«!ìgCºÖmžÄf€7‰oóÍ=åÜç^+˜‘5uR²Ý¹ì+{çûÆævôcÇôªˆã]ZÄ^d‘÷‡z¡s&vH¸X\÷œ© ƼâóE·GS{bÛ÷wçÍQºð÷…õH½¤Ú^‚G'‚;žjî'ñ³á]d³µ×MŒû²ÜyüŸTý¯-¸ø­â¯ @ºƒÖa”ìEH Hç$ò8÷·áéüC _^è°²Ô'–rÂS̃Ë3ñ·½_ÖôM>òx“â>¿ç^Mò=žšÊ±môÛ—'ê@ö¦¬R±ä-5»Û—×.®íüÙ†ë+ó%hÔn ãî©5éPüqð4vÐZév7ztÈB¬³ÚàHf'úâºÛŸ„>Ö- ²Ñ!Ôtˆ¢å.<ð202œäwÁ5óˆ<áo‡z†µ£|KÕõKöžÞI­á3móA;–¸Ç'\ÓV*ÇYÅÍ+^Ô|ïG&£kæbÞÞ7EYÔ±Ùœ’0zpEoxƒÅ³çŠocKÛItý±à;µXeÈù™¸²õÍö(ð~…á{cÃf{;øÕ'm>ý¼Øw``AÝÏ\nõÇêÖÿôûù-ëZ7‰4;£á g^¿ðeÚ¼š&“qe©bB!a»x Ðùã 8ù•áŸÛ_Eý›­|[ð_ℵMjóÄzš%éŽì ·µBPZDŽI%˜àòNY€à®µ?Ù‹ãÖ…ñ/YÒ~x³Äká‚ò]jº}ž±=£iQÊ ³3£rBåü±·=s“ð3Ãß³À>ø[Éew.¡kiýŸl%×ÂX µä’±’c!¸ULž~`Xäà'«¸Ú]>ðWŽÿà§z/ßã€|-§øCJÕ,îMÓ|K¨B×&ÖÝȉFM0 «K4mÓr)85xªëþ Eñ£Ä¶ÓøÏÇÚW„µK¨ÖÊá<Æšk¼`®ðØD­,)ip]Êæ/ _´ºÆ¯®‘xu½>xu>w_ xy@ŽY]ðÂâîQŸ:oâ! a‰Üzy¥­ž¡û1_i^ ЃÉ'ž+ß]%§Ÿ©îG,M°ä©ÈËž8â¡Ä9ùQã~ÿ‚xüýƒu{_ˆ¿­|Eñoǯeö++ï[Ùì²’ñ†ë™!ŒA 2†“b¼ìƒ-Íz'ìÁûü;ÔÿÁa>|8ñƒáÚwáö¯à?‰öp,t Mq¤µã²î½7Š»m­\2ËsIµ¹]ß(ñÏ\þÒßü^mþüN_øVw_?SkÙ>ÏͶÐÐ[¦[“Kͽ" 0¹ ב~ÐßÆß þÍú—ìÛm¤|?ø¿}4º–žÞ#ñSH¶³ßn‘oîW™¡0DˆoãUÍCÔ·ÑúqûZü5´ø©áo|yý¨|HeI´ës –›-­ºÌâ;(-¥ËI‘þð$ÈÙ< (ø?á§Ž<3¤þÁrþÕ_üYáïé^½¼±ƒ@×ÞKÉõ)t¹Y8É}ÓÏrÉû»q·+œçø ¿l/Ø'ÂzŸ |[¶Öî, ÓÁZ|ßð l ¼X¦µY@È]¨©NÔ 21ÿeÛ“YøYñâãâ–¿áCãWާòãðêêr›ˆt{»¢K^ÙX ò~Õ1;<áe@q‚\šL^ÏÌþ gøáûsøÅ_uO Uºš 6ÓYñv—=¥ž‡a æM'DhâžêàþòIn§)dPŽê»Óþ4øSöMÿ‚}|5×~(üQ»ºÕ¾&øîÓ®¼WªHP»Óà:x£Œ3ɼq©_*5 ƒ‚IjøB?ÿÁb>+išŸþ=üB´ýœ¼«OihSB‹ûoÅÚĀ쳷Uó¿Óe'b$,Y>a´ƒ¸zÿÁïø&÷€¾øzïâçíÿl-4{«p÷v:þ¤5Oêð’òC©xлMebÞBe‡™°‘M´™zoíCûTÿÁI¿h=;öbý…¼Ÿ†¾ ñ2Í©êSÂ.N™$MæÍq:)ÐË­–d••šŠdÙõ޽ÿ»álþý’t¿ŽZf¯¦0—SÒ´mÌ[ÛX@_2ßêif;¾p6‰ça…eš¾¡Ö|g/í=¦ê“|"ð‡´Ï ùñÚ­„nºd6¿»„³Ã€Íƒ±qŽ„šõoØá¼ÿ²¦â¿øo\ðƛ㛶ŽûUñ£c5åýå¡a6q¼™XІW\¹ A©{èÿÔþ-¼'¬êòyV–òÈ1R8ç'qëŠö#ànµ8[Íd#TÄñ·=‡'¯­yEïÆ_KçJl — „å™}‘ÈÇJï~üWñ=î¹ý›âén!%V%œ’®z `ý+†TåkJWÐé/l~øýmîñ%ôJ` ·Ô7§~ÕÐ^øîI44ôÜíÔ<œóÛå¯A¹ŸÂ><‚}3PÚ‚‰^'Nw#'BÃ)â(ø¨ørOí3Þ@ÊsG˜ƒ¯#Ó­d¦–û–‘à~6›ÇÒ]Ë?ˆnf¹Žf,]\˜‰=xhú`W›{WºjÞ!»ÓÚm÷d7tÇ'*sÉ®RÿÃúv¥“éߺ•q„ù¯t¥ihz)©-±c¯]¬ ãsF 1õôÁâžž#ûXA«YÃ&z”MÏ©éY âé£aöäK¬ÚJƒ·×žùâµâÕ¼)ª!mVÞêÖSòî‹k¡¡ÅLIdð§…o (Å.pÃß'¾+J×Ã: ”]i-Æ `¶žÜãñ¬ÔЭ¯!+¥jäDŽˆüºÓÛÂý®.˜Ï:?º; íMňuÞ%¤rA¬Ù3rÊdMÀ9Æ3‘X±øGBŽOµéѶŸ(9ßhí××ikªjúu‡{;íQ…ó~TSêràW¨|iðž‘!’ñ-¯eB2miqõÎÁÿ­iB¬´Š’[›¶ºçÄ­6 ¿ˆ¼èIÀ†ú˜ôÆìþ5™-¥ç™Œü'otÇ—¼ÒþF 唑Œzf¼³\øï6¡tdðÞ±œ³^Iæ>Gp«„ÏâkÈuoxË^ÊêzŒŒ¬s"(œtÈüºW¥O =å¡Í,DvG±kÖ?³ý嬒hÞ º°º9gžÙ™Aô?&ïÈ׎Eâ?èÒIáMJêxÚÐÍ$;×HVJæ#¦+åŠ1ŸÒºÝÃ^!ñ]ÊAáí>[ð>P"ƒÝËú×b„bŽwRMÝHü<ý§¯áŽ;â$Fæ8ÆÔ¼‡‰Óc–çšúÿã—Áéê5ÝBWÎÕó6œ}xú×Éú/ìýâK,Ýøšæ 0+d‡ÌŽ£¿Ý'šõÏ ø7áõ¾…|Å·î>dÈcÙF>è=³^V"Ç]*’{í¦‹à-u…ç†<@ˆ$ÎÁr3¸öÉçñ§\øÇ–ÂîÖ%¼d{W‡Ç\wCÍzEÁOëšt“xZÔÙMyS㹓ƒîá\ð¡$¦ûÂzä1&â AÚ¡N9üýkÌtã}žc‡›TÕ´¥Ž+ؘ \•J®G8ô5ZK—m,2Æm¼ð|¹䑎pZômMþ&èÙ}ZÌÝ[ƒ‚LbhØ Ï¾‚²—Åÿ5•û/‰t­Üýé-”ÆFˆ­l¡dœE–—Ÿ;jž»šÅ¤Úd*Ä— À.xfÀ$Äà W¡išµö©¨Àí*Ûë–„½•Ú›“Îa“'å Ëלúâ¨ÿÂ+ðïTŒ.—âIm‚Ê“.dòsè=*¶¥ðkÆëþ•¦\ÛkJ¸x¾ÇqµÏu|žàò+H>æm§ÿ°·íä?g¼S>~jÓ|Ca"~óE¹'jß ^V,ü³da† ©ÏôÑqim{eõ‹Çwc}›oÿøëð³ZÕ$ðßÄ/‡—cKñg€õ u íS+ö–¡1MŒn†hÙ£”u*ݺ¸>|aÓ¾(økDý |a.“táìõ)Ø3Ø_@BÝYÌA ìn!”‡RA¹_OŽ};ì³"²™øGµ|¢uK_Ù3âçÅØãøÄ韮 Ž;mYù·ˆ²Ý¹xG”¡»0uÜecŸBާî]†¡§ëZd:ÎŽûínWr÷—±V™NA©+ç_xÊx•4zdþË×åŒ-Àå"¸qòHN1¶QµIr­}!<[Ja”`©Å}… ¼ñ¹ñøš>Îm-ŠL0qQ¿LÕ¢ óQº t­Žr¥=ªRƒSðJ¬=E.ÜsгƒQ犀¦Niõ.ÑÆi…yÀ¦¿xRöjrc4˜û¢[š½ªWµ>›´iÕ"QM1“ЖНEHc¦”ÇJh§nl憚98  û”zSp*déNðà01S‘œ qëQ¯Þ©³ÚˆP)\`ÒÆ~jWùx×X[§Zþâ£VŠTš‡BúŠùSãìûü{¿Ô|Mñà׆u=sS¹{Û­j ž«$ò(Wvº‡’ê¡[-Ê€: ÊJæô´gðÍðÃÁž#ý£i¯|ÑîÄŸŒum'L¹û,—1Ú‰¼²´1i(£yæQ´ríSŸ²Ïü÷àÏìéñ[IøÍñ£Ç7ÿ¯q¡Ã&n­bŽyRBóï´Ö Ö¼Åð¿‹cñ¾­;Äzp¶„$™Wž!T§ðd}ß¡®·Áþ3‚îüxFâé¥Õ`‚I®üÌÆû=X”¹‘J›;ûKËkûhï-[)/±ÎHÇ× Õ-{[Óü5¤M®j¥Å¼C]Í—`«îHäÍ·…4ëm2êê&6×Þ+Úßg{Êì#sª@#&¼“Äž/ñ·ˆ| k¡ø™ÂÏ©Íc ‚>f.UÀî©lûÔ‹‘ž…ñƒâ±ðLJ4•q{¬Ùo·V$¾íÙ¾E'pkPøm­ü/²oÅ,ךV™ms ŸÊÁgÝV$û×ÅâoüH¿Õx_Ä:4+tñDºýˆ'kÌ“´+  `…–\ž¹©{B½“>ºøAâÙ&½¿ñˬ¦S뚌c–ºÄo%¤lÝ$Qã x"¿ƒŸ™_2{Yõã/ÇG¼[#©uÏîíäWDêî4©ì)Dûóâ݇´Iµ‰wS}†ËÄ3Ýx‘‹gì¶VŽ[ìò(X bP1¸7Í|'ûV?‰¯þ(xSáWÄ#giw©øP³‚êÍBÇÒÚ™¶:6O›22Œ  äFè‰ô/„ïàoˆþ&±ºÕnmüJÞžÜLѬ:hµK« •Ø82fGÄvžkñóöŒñ‹¼âÛïxë\OÅà=v/é7­å›Ñ=• GvBíòå¶–Hí` îkADÖñŒ9ž+柋ÚUÞƒñž÷J›KþÆ´ÔuË=JÇÌbñ[XÝjÉsæœù¢#6däU×Ô?ðRøêÓâåÿÂ?]-ý®›w^\:a_» LĘä„Ýç…÷Íg)t.úØüùø•ñÆðk^5Ó<;{¨´šã\iÐXÛF'±6:’¤wï/Ì“ªD¬ç‘ׂkå![ßiÖºLëc§Ågw¥é—3±d-Œ¬¬çƒÆ9Žõôç>$Mâo‰q[XÚéž ÿ„Z;í'vˆÆXÖöv%®¾eU;¹]¡HPO5ò?Ãù´ÍàF‡=¼³o«Ü]ý‘ÇîÕ ˜“kc ½B³.N rÉêo ¾õ_Gð²‡úLnšµÝƇwịÄfu¶µ¹a2«ã¨x£`I9 õ®ÂOÙxwàOŒüe5¬¿d³ÓÍ­¬2I¶9Íóy0D ËGÈT¨©¼uðÓD€ÛÅ«êþ0Ö&Ò4¸•;‰µ{xHžOE†8˜ª–ëŒõ¯ý²µ=ÿÃ¹Ñî¼.“h~"š ǪßX¼sA,Ñ ùžÛg–¼’rÊB»)EqÁ>´?†~6ø­£é¯·Zézö±¹•þÏo}uv$RmU£ Næ Ž•ë?³wÇoø—Æ2h¼E.¡wi¨Û‰obA-œJIœíÁÞåã ¼ž+òæÿ]ñWÂ=wHð¿Ã[¸fÖõ[=*$kgÉp»"Î]2ÅŸŒ ç¾ïýˆ<©?Å;ÿأðYßø³Æ:•åψ/çCÛxn(c7~G ‰]ŸÊER óCãå5¥9jDà}oñ‹^K¿Ú>Æ/Û5çÃ]ÙÉâ?7jBš´R4ñ®ì6Æ·Da·€rFsŠù{ö†ñN«ñ;Àžøà‰ÿu¨Ü¶±¾ “Ùé:sHöQÊùæIßk#`fÏ5Œ÷Úׄ|_à››‹SÃëv6>Ž8Ú9Y´é&·wùgx¢ ö­qɯ8ý¢µgÁZÞŸ£ü>Žê}fóI·Ñµ[üÄÑê/z+K»6G&%XcxÕXƒ¸ò3ɺ’¹’‹GÈ:þû߇¾Õ4©„® ˜$ 5¬Ë ódcуäàŽ˜ÅAâÝzÛRðå¿Ãk)u5ÛØi³e·ŸRÜY­Kr¡‚åµs?u[Û)­>Ì z€c¹µ³»òÿÒ.•¤O ̧èíò¯LƒžÕäÞøØn|áÿ‡vV·VÑéÚÁÕìd„„Xu{—(nË,d¨8èIÅrÊGDQkâ4+¥jº,šÚ•†Îñí¯‚Ÿš&F+"€9[#è+‹Óõ}cUÔ.­<%röo>¤ïfɸ ¶!)Ç$*Ì3ÐT.îlô]}.¯F©zþ$Œ½Úcæ»îÎX÷5Ãü<דÃþ+ƒTŒ‚óB4Ÿ*¬›ŒvFè}«>cCô›Æ¿4 ÞiŸþÌu¿ xoZÓuk¹KybÿQ…CK8r7°g囦AëÍt> ]Sâö™âOëVbÃ[±Š)$´rcó/.eÚ«Ž2ÑmHÉ]Á}¯>,x÷Lðï…í4O‡¶–çQ¾°KšÝïe)’Xtó dö¯ÀM_Âÿ < âÏüJ‹V[/‡´“MÓ-›e®³¯êÅÖÜLFvÇ „ücq'Š¥"N×Åýï€>x³ö}ðuèµ>õ³ XÕÚéb{#îÊÍ&G\Œqšù«â—‰´öxÔu»êQEâ 7Öî +*4VûÖ>‰ d³³uãÓš_ø;Åþý¨|?¡üi$úöd¾&Š×iýÔ!n¤óä|¬ŽX†<ô¤ñoÄ-:Ò×âŠt˜¬`Ñ<[zÖG*Š‹ ïDXÓŒÈ'?y²}*¹‰å=^_ h–¿5Ø5[-;HøC¥éÚ^ ÖÀ³]\¢-ÀU—æeš&eÉ88ïÇ€ëÚ¿Œ~jZ¿ìãâøçÒ./æ—QÕ ÓÐßÛøŠßV’UÀÛna”äð@CÓ½U¹†ßGø³â?øy¦Ô4&Ô¯-mÊm+7”b£9Ù¹X1ã5f±ì;Ä“Oÿ æR³ßµœQÜ…Ê4wgí Ï>h gÚ¾ºøi©ÅðÏáý–¯aeœšDM£'Ìpw]H9ó%p:óõ"¼ á>‰?Çω·~'ñPÿ‰O‡šÓ]ñ=Í "âßGž_%š%ä‰ÀìÄs^¡ûIø“CÔ~0‡_ µh¯<XëVa# ¹ä…ùÜÆ%U,[‚ïÇCZtŽWA¹¼øƒñao<>&ÔíçÕå½[©Ø¬’Çf¡er$<(=8+Ü~.ü8Òmµ ÿ A«Mö«ýU´½2ÖvÍ-Íâï`±°ûê»›wP£&¼ïÀwÚ§ÃxkㆊdÔ­äÙ\²]É}v­vËÐÎ6ç‚1]—ÆH¼O£~Ú1Ù•Õ£ðćP7’(†5½Vy!p 9„F †<ôSFg·~ÑÞøWâÚ‡ÁŸ³Wìÿic® ,a[ÙdkzÜl­V>„E<ò± 0I<âOü=¹ñ×ö?ü,¿&? üÖì¼#©ÝYJn"Ôu[ÝIíî$„ƒûÆŽu`Jƒ^+;øÅz¯Š¼eã¿ÝÛèÞ!øÖ«§ù¦Ö]"BXṺ³A“ím†Õ5êN~áðŸ/5?iß ü áÛ øc@ñF“…a ˜Kk[+iÙ'¾RW|pÆ^T`ÀÏ(Æ@äôÃc)n}ã?ü#×>'ØiŸ<)=½î§*êòæØÃer’ÍçK=æIÌ6±Æ $›[qÈëšüàðÿ…/|}ûIëV>!Öàñ–‹q~|A¦ê×âCy®J,(Îcù™b û×£Cñ¸ø ¾!xÇÃ)KÿÿÂa{¤iš$°3Ýj­­4òá™Úk¢Í) ª°€  n?-Ýê~?ð-–­û?ü/ðž¹â¯Ž^4Ó¥Í昰µÆœ-TKöD%QìÅžF,¤o˜€1²bå:½ãÁ´'í£ׂôÛø^î|7§4û"Ôu +•¸íâK·“±‰ /,Æ¿J|?£üZøsñ¯Oý”.®,¼SâOˆVÚ߉uãk$QCs¨ù«/—‘ œ1…el¶zïl·ÍŸ±·ÃX¿f߀š§Œô[{Fñ#Åm ó„S[Ú3 ¶Ú|n¬Q¤‰ÉžäFJƒµyÛúM¨@ß>ÞØi²Èšž©fÖzv¡}²k½KSRk«¢Ç *[ «¼’+|ùñÓã/ˆSÃÞ2дiµôOkQø}õ°é¦£«D¨àÚªîó#·Ý±ÙŽ@ËË‚oðÛ_ñ7ÀßÜøÞïRûV—âÞsvå¼6+’È7ʳ:’Q„pkãoÙçAø‘âï hß~Ç+jº®·{%Ëý =Ÿœ÷ÜMÝ¡7û‰ýÕà€~Ö¹ð÷„þ:xËNñƒµE»øS¥Ë£éí¨\9h//ßtÛü² FKMZ1’±õïìÃá¿íȴψšÕ»F!y¨Dέ÷2óQøŒ{Nq‘“Ž+鈺†…ã!7Â÷K¨Yªý¥7ù&;Ü1þqíŸzÉaãm{ƶ¾)Ñ¢7†ô¸ÄZTa‚µÜª7HòȤmÚ8ëé\O‹õïiZ>£kbÓÏâÍkUMjäÁÉ”mû°¨Ç_¼xÖÖ2nå]#ÇpÂ!Œïó-ä¶÷6ñ(9‘#FeBl&>µî¶ºÑ“RÓžÍaVÎÆÏ˜è÷‡Rz•È^ø÷áÇÄ-RÞ×PÓ㻲’Ìr¡$±Ç1ç8õë\׈>jz…”°|2ñæ„d\2Ýÿ¥Â¨² íóUµqžð>+ßXè–—ž(Ò¥²µ#ØÐ0š7$t$ãz²ÿ|¨hK¨ÙM-ƒ°qhC6ï»Âƒ·9ï_&Û|;ý¢t]iĺ_ˆ´è€f¶¾¶hœÈ*C²œv¼Æ^"ý­ü-hÚSøEµ-%óa’ÂÒ)e¿„f}s¶¡Å\hí<% ‹þ-j^±¼‘Ö×ý;[¾gGÂà 󼃽T}ÁÏ¿šx¿ötøeðßöëøuã«Û_'Dñ$–7“Ú ˆG Ä‹0Œœ,lØBÀqÁí_9ü ø[ûWèž?¼ø³ð×ÂÚµ•ø’I¯á"Ô-ÚþâB1ä+HV5#aŽ+Ôü¯ÿÁW¼eâ|IñCáo…u]/^Ò'Ñ/,tíWìRÛÃ1g7™w/˜HUÚX/ñg <Œ³ôEðÏýKâf»ñsáÛÁ›¦Ú>…¦ºÃæÍ¨^Ç>@ÌK`å£qÀcÓ®Á=J;}Jãá§ÄM!RçT‡ìšuËáÍ$G#. €rpp+ãoø&/Çß|_ý™­,5¹­ì¼gà«‹íÄ~¸˜Ý&æÎw†Þ9U±Ÿ´D‘ʲ £È$uúsã5î—ãÏáþ±ÂîöÝf›ìÌRH¤‡-¾'•”±eû “Å+ÛreÏŠ¾2éqx:ïľ=ðûÜC®]´Ú6ƒwjZeŽÙ§U¤2! ˜6ã­zì«§Éà¿€óêtiµNÖŸÌÝ*«˜¬ßii#v_3s“·ÉÕó×Ãþ±øi«xWãŠ`°Ð¾X@²M§",Ú•ì£ýÒ‘iÀ Ê甞WÎÿ?hOŒÑ,ÿgÝ']ÔÑc[h! c¥ÆÇ%Ù™œ,Œ>ô™ÞÝb¾AømuñCÅcñ‡€t _ãe¤ÜËu¥Ù¥ÕÆ‹ Ó6Hó̱9‹ P=NÒ@Â5ŒQõt¾7ð}ާ7Ào…þñŒæÐ¥–Ýmî.b[Dd¨–IÏÊ"By(£8ƒŠë> ü3ý£þüUø§à¿|d²½’×į»S:‚~Ó‹Ëi·ZÁx@Bc!·:‚¬Ï“_¯>'ñ·„›LÓãçÆxwÅWš=½éð]”i§I©§Ùlài ±Èé׊ ÍÍgöSø³ðüE§ü]ñð³HñÆ5ŸÃòÚêšïˆnÚ2­”om#I•]§tNIÂÆ¹ù‡åìM7àšß¶·ˆ~jžšÞ]gM:~“¤øîX­õ+$¶ËÇ:É`“G$×IûµF • r¿oÿh/Ûcöý”ôÏ x+àVqã‹~(¼³³}Cí-&¡k Óã)}|I‰YÀVHÇËÎG×áíÑñãƒÿঠ~*jú§‚ô‰íw ¤VÚ=Ìúõ–‡wwy%¤j¥aûTÉ;Å—å*•'l™'pW?¡¿„>5ø¡ñâ~‘âÿŒN›ñ¢"ÓøÖ6æ{/‡Ú<ÀFf—ÍÆ5[´e72 ö°…"È[ìþÏ?ðšü@Ò> üR··øƒâm2hãƒTÖcÚÝQIU+Æ˱‹°Ë_Žþ)ø‘û>þÀÞ*ñ=§†l<_â{Å:Óê7~%Õí[O°Ô5²Ï*ï™-Þv. ®b‹ÊPàÀòÿˆ?ðTˆ>+˜ü2±ñná»ëÈ‚ªh—¾‰_ °M)"‘ÁØÿtŠ–›fn ½ÝŸ*ýŸ~i¶|8–iâ~pÚ‡•äÛÍ"ýöH­ÐL!p£«WåOíŸá¾1üýžÿcÓwsñ©î¯õ¦tßEð÷“å³jnCĶÍ1]»Ã3¸Ä`°$x·ÄŸ³ßìá{?ßÜGãx†d³²Òôë¨ïdV·¡µ›Ù›Eê{gÄ;-wÂÞ=}sÃìÖ©vVHÚ3…ÉP=G'$æ½'Âÿ¯l-/ÆP ({G”å:wÁÍq_wk ¼/âT$3›˜f†3€O±çÕàA$™>`GNsòFKStϺüW¦xâ^ŽÓY yÝI·žæí¸ŽCzç5ðÞ©£áýVãLyO™m!ŠƒŽù­Ï ø—QðŽ¥Þ+,m÷€$+uëî?ZÛø¯§Mg¯[ê@·öÑÌ€Ála¿\UÓ\¯”™»èp±èzö¯ÞZÂ÷ ’7) ÏáUî4 fÔº¶’5ç–kîû†ú/<;§kötýB{H¤Žæ*²‚¿òÜ3Æ3Œ×‘k~Öt+Æ_Y«Ä‡›¨€xˆe»®Ú÷§í—C/dì|®ÃóH+Ø5_Ù]“.œÛxÚ>S»¦{~UåW–63n‚+XMH‡‹Ú'ˆµÝ‹Ív…¸Î:0ˆèGÖ¾ÖøOûHèfOì¯éñL%ëU‰Æ|¶?p÷ÁãÐ×ÁýñV`µ¹›ñž¾••|-:ªÒF´«ÎCöj_‡ ¼k¦>©àu3y‹8’2Ý/PG·ó爼1¢hÐ=¾¯'Ù@ÉÜï¡xÎÞ ñφüQãOF·Zf¡5¬Ñ’Esœuã¿ó‘YÚ®¯­x†w¿Ö¯§»šF9³–=‡×Ò¼ˆåiKâÐïX¿#ݵÏxWFEáÛè¯ Ÿ! ßæ÷®|WñÊÏåé×Y‘½OÍøu¥y lÚÁ»Œsë]î‰à?ø‚0Ö6å!Bs$ß }R+¦8jT÷×Ô™UœÌ­_Ä!ñþOÐ|âoN–ZŒ·eÆå(÷×OÖ½÷Âß³.¸ð¼Þ)¼ŽÓnÒaDÒúòþy¯S½×u=IM¦•¬›5÷! ¹ìxÎ~•ËI¤øÂØy»¤u#ïE6àG©çò®9ã[ØÖ4ì¼ðãÁW dÓ.nç\n{‚¡N÷Æ+Ð[â&§cn-m!·‚ÝNQ&üy\^3ñmŒ©ÅIJ/B²Ä6ŸNHãó­8¼m§Ì­ý·f¡·æ6凨õË*íîn©£²»ºð޳qö›“uld¤tà >…z‘ô¬·ðFŸ¨£6¬ÚHÎ2Rã1–?ït>ØVzÞü=ÔœKìö,H<¯¶Héš±ÿµÕÌÛô&õŽv•‘C( ô¬å&ú””Qè~×|Mà똭µødháæ+ÞkOÆK/Ö½‡ÆÒ~&[Kªi¤Z»ßÛŒ*\ä:öVõò’ÚxËÃïö«xç‰G”–\ŸÇ{q[Þø™¥J¶ž.–WÝ´%ÀÀ’6 •n1‘Ï$T¸´=/¡™>/Ð.¦O kz†!rZå/°ë˜Øü‡Ö¶gñÿÄ:Câë-ÄêÙæêÜÛNGR©òý¯¤nü3¡üMƧ&ØuË+Â|¡v¸ßt6{q“^;­ø  “jºÄV ,Á{„·§ v4ãYìÑhó»Ÿ|*¾Á×´=wÃäÿ K¨AïÂ}¿ðµ£é>×îü2ñ~•3€KÁ¨\:ã@‡$£º)|%â[7p‹ÈÏà “ï·?ã\6¯ èú«2ø‹GóÙF7KÒú€OåZÂtï·õýy‹•ž‘0øáàø·IËnEâlòW'ÛŸ¥/…¼]£¥ä¾/Òãm7ÍØúΟ25¬‘Ñ_B§ à¶õê+Ç´ýÓì—~Õµ` Qa;lää|‡*r1]-§ÄÏ: âß]]é¾%!|²/m6M$=Jù¨F3Ó}Åj”^Òý åtY°ír¿¼%ið“âYcãÛ©[¥*ðktj6]#d0õƒhô¯»¼[á› kI¸µžsm"y3Dã"HÜÊã)‘Ð×ñ•ð3ã>­ðÿÆ:‹~NÞ7~nu óbÓï?ŠÂò#2Ölí$2°È*pr?¯ïÙ×ã÷ƒi†/ñÂð› »iÅž±¥¹ýæŸ}å1ÿ<ÛïFÙév¬§lû>^ÅàoRý•z÷Á8ùbµ²WÌÒÁ!¢}ÒÁ¸ãa1¨ ‚¿N¾ø¾]{C“Âô¦McC6wû×ý_÷‡Ýqž†¿<>2ü.ºñφ¤ƒGÔ%Òõ+9 ¿±¼‹††îуÄr¿0€ ¹ÃŒ†È$W¥üøË«|Gð©ñôP-‡<7'Ùu­%][mÎ2H·ºPZ&ã ¥G·—cj,òs /<\Ñr¦1ÇÍTÒ|O¡Zx§ÃÎd±¿ŒIàC!þ(Øvdl©y¢ÛÚ¾‘I3æ( ñFÆ«f>¸¨ªˆ"Ä€` hW=GëV0O4m4T Ï4@5+Xâ“k‘Ìc$`1j]‡8©UpxïI«›ZènÝ4†< Š”Æw X TV抜G•¤òÇzo—þɦ*z‹i94ìÇf7ƒPªÕ6’šLi1G'0¨@©W¦)]Šì‘>õMQ ïRR›F¦°ù1ڥ݊—uA…¤ ©¼¿óþM0ŒPLÅGV)¥Aæ€*È9ÝPÜjѶ¡Í\°•šö#)ÝóÉôæ(È8Íõ2© ‘I3¨…ÿäjBúŸ„Í'Ù?àè™Èÿ—ÏmÈ•ûœ7¿ïrs‘MntÖÙK mäàÕi÷=+RHØÆ³®•Ë[Çc•é±Á¸gŒµ{UÖtÝM¸Õµ 1 ´/pû~fòÐd'ð®[Æ:„‹à©VQfìÈ<+ÈCÇ'h¥cÍÜÝ@ú‹Å¾!°ñŠõIt1 ¡ê°Ál']¤›‰[ž àlטüø•mâ/?XW‚çW¨.e Í(µ!cRݤgØ8Ç5ó÷þ=hQø›_ø¹â÷K:ɽ½¸˜fÞ9n‹˜m“iù¥Kb`998⸭sÄ_ ô¯ê>¼‹S“ÅwšŽ‡m&£Ùjó™$I‹¦Ü©LƂΞµ´7-¤ußßøÄx BÌÍu«èú®µ5Á"8͵‘‰Z2Äàù~vvó’2;×áÿŠ7S|Sñ§tt}Z? Mõ±CË ÄLØVd’286kàüH›À_ õ?…¬ÒÞMà›Dð·†ä”¡Û#_íds…yFX¢Fà0ÜrXð?\7‘€A ¼ädÖïÅ?Ž?>Ũj6±µÜ“麽¤ˆùŠâÒ·É`»1åùlû~öry8?ø†öO Ù]Úi÷pÉŒm›XH®Îå¶’C4Irƒ»6¥’4ÎI%G¥bê#hR»;«Šú—†íôƒÑNš·…¾ø¦üÚ݃¶âêÖÊ%LfUH¿Õó‚Ê8èÕèÞñö¹mÿ /íª4ºo…äšçû64•˜ºÂàÛ¢¼—Âß?†üL–âi¬ž È…™\»¾‡ìî£#åVgVì8ÉÏZùcÁ6ž%ñÇŒ4oéwbÿ[¿ÓbðÿÚÝ~XÖÛ%Ñ`@ùz–È'…ÎM';‰S±ô÷еh&ý’¼mñ.ÎÔØéÚ¶â†òáH¹¸yü«4HÙ†à÷“#I2 ¹Í~mü1ñ7ˆ¼ñGBñ—‚m£¾Ö¼?t·U£ ,÷É‚Ù:àæW’ñ_«ÿ¶ÞáßþÄ:¯€´ ;/K´Öc}2;¦?h»³°{{¶”à¬WXã’=kó“öNmKý¥4 Äv©}”º–¤bq®–í:œŒ²23ß•‹½ô6†‘hýiøÏû=x[Å á/B»¶»Ôltë}J¸–wFÔ%ÖçX¸˜¨ù¤¹‘Ê@¬¥!8e¥uÞñ6…¥^|?ºÒ¾ÏxšM–‹¨YÞ0i?¶ü3ây…æoIÆDòd}ÌTf#v++ö[ð[|p×|Eâ«û»ßIðÖí%ðû݃=Þœñ3K¥Èç¬ó©‘ãe!ÃmÈ*?<~Ëš¶—«êÞ^‹ª¤Þð¼ŸÛÃK™Œ·¾ÔÑíÞÙÕÉ&Õ¢¸mÈÄЖZèR±ÍkŸ¬úÕ—‰ÿm‰?¼@dÑc¿½EKkl«}›V½ã~6¢InãÌÀ ±ZüeøËâ}görð6½oã 9/|e{á»x†!¶Y%žÚäM{xÏÚRß½ ãŽj[߉ž>Ó~%xã7ˆuI5ûïZjÖÅü¬ˆÚ9º0@dd+,FÍH}ûƒ4ŒÞ׿à "ðmö«ãÝ?E´a6â};L·»º“Ïk­:! ¡“©Ž;‚ê‡@<äUs‚§c°ø™ã|lðj—¾(Óµ«IüC§,“Z¨š(lµ’Û·¡·Bí.xTž9®§ö†ðŸtÏøßã4ÚºêÖ:UÔ^ÕPQMLÞ­®—+ÏvžÈ#ùƒC.sž<›áOÃ?‚¾/ø'Å‹)n4»øjòê4Še–æëT- y£A‰JÂgܘÿUŒò2='ö±¾Ò¾þÏߦÐ.gÖgñ/…4ïøŒ²<ðÞèZC¤¿jI+Zy–É#ޱ¹ãåÈͰKSò«Æ#¶Ðþi­`¶±ÖáÕõ ‹ÙçK 6,`‹Ëní:¸Ïm ‘\­mðçGƒÁº¼ÖÒÇ¢Ihn|Õ--4ŠQ·¸"ïÜäñ´^ËðWĺÃx“Å^$Ó_Y»± ªA>ЂÎ'šÅ[’§Ut q\ö…ðõ´;Æyûµ'Øu-Fá·¹hÝþÌø'¨xëÇ¥a#¦ Cõž¿øG«jþ•£ºñ/ƒæއtåVFѧ¶WšùW ƒÀ|V[«ŸøHëºf¿,“‘þt“û*Þ2>i|’©æ÷~o^~bñhðV¿¢é¿¾²j¶š5ÕµÞ©©4l×ûÁ·Š$B>hD¥Tä`“ëH³é¯…Ÿ~ü:¶Ô¼gã_ êW>2Õ4-~Ö=LF¹67ÖäEguo#ìòŒëó™Y‚Æ£ ·<ýÏû,š:4þÒ‚o5v±>oÚn!X‘T˜ƒŠö]Âÿ´Ù·àgìËã f¨÷Ãñ'GѼg¨xûÄ–óëúÕÌ“µä×7››»‹q ^k³$JGDŒ ò¿éVw_ü=¤X\§šuYP™~c%Ä eÈãå\ƒ²ñ^[ ê2뺞£ªë$ŒÏ©_Ë(åÚT%Ð/  ŸA]äsØÏ£éº¬p5¥õŽŸ=ò³ù‘ÃzÄ‹‚²ÕÚH½j”˜Ò>ÀðmÔ^ ø¬i„R^^ÚßÉe4¨BÄÒ,‰,ã™Y à;×ĥœÁâOiD½Öqå‡c„ÄÎJ¸¬ÓšößøïûkÆ«wáÙÅæ•©[¡V–Ï…ÛôPr{×Ëžñl¾ñ3xÖÏMK¥‚öI¡¶»$ZO'" $Ç_,“€z“Czò%¨ÏëÚ‡Žmâ³Aÿ}†h—å&'RÇ œ/ÝÈ9ªú³ëÄ [Á>ŠD/vÛ wˆd¼ ï 3Ô`3Æy¬™-åg“Æ¢”Ô%½š&³P#HŽæaèNã´vé?ü ªxÂÞúÓÂ7˦ê7ïðoŠþ-è?õ‹KOøSá6‰>—¦®Ÿ 6Z׊eyã“?,±Z¼½àËwe®³Ä2ëÄÿ‰±^ø£ÆVÚ×Z"LÏcf‡q2ÉM¢AÀç5é_²§ì±¦x3á×ÃO㦹¤xOÃR:Xh·éä]Ëmiî¤gvTŠ6\ ›w¹#$gù×=Ž‹ãï_xûÅþ/´Ñׯ_µèÚO2É.‘`¥.îcçŠ 9&÷Äe²ª~dÏDtFosØ~j_<;à-:Ë@•äÕü7×ìu¯^ÝKç=Þ­$´ýB™4 ,wWD³4D!,7(«0Jí¶{wÀïÙŸâwík6›ñFM#ÃÊAy¨xVÞòKK‹ýX¤Ü&5²|”² rÈBsúAà­oÂZüGŒ4m8h^¶½¹ÔªF°ÜÞjRmp@ÙH¡Š[cÓ¹>•ãÍ[À?³ë-FÊ{_ÅbòæK«‹ÙUP·H‰g–B€-#PŒ×ÍŸ¾"ÿ¯ðÆñ^µÝNm:Þ¸¶ŒÞylü¢ZF ,’HÇÎíšµ¹w=Ëã/ÅŸh׺/†<+eikâkû³ocgy/Ú¢A‰¥¹Uû°Â0HR2Ø^¼V_ÂçÑü?áŸÛi»5­JI]gZ‘rJË!‹û¸<ÓˆÇ\¶k˼à/‹š_޵ÿ‰ž&{µ/F…oïeK‹›q¹¤¶ðÅ•TQËs»žs_Züøkum¤fúÙ¿³¢&hVB<Ë»€é8ù$7kDbÝ‘ô=¤rÿb[\º¬Kä¡*¼àbšr;¥y~™¨ F "ãå+!-´“·¨=‰útÝh{¥Ò¢¹ åÁ'=¹Å3L¶Z{zJÉ¿‘Í €qŒÐÇ&˜sŽ)ÅH¦ƒšsP8ÉØ‡Š±×ŠnHé@Xmb1ŠªÒ(Q‘Š·:Ú¨ÊÅŽF}¨õ y"1—Õà Á†*Iì¡•QBgœŠ¥4:•²nˆ‹„ŒLÃ-ƒÐw¬-WMº’éní%jãiÍj>©dªà\õ?:» ¤¸12¿¸4‡¥Ý­æbÃ+‚5¦ ãÓֱĿcñKFÄ*Kåã©>¥«YéèN<É,qšÒŽÆÅ;mByŒæ©êV6â?–Í.ÊÒ… ~uÇ^x–ô[{Û9áA‚]0õ—w}`ÃZМýèßG~þU\Ì ‹O ø.mpXêöVÿh”eŒè` ásÆ ª7¿ ¼ â(ä¸ÒíÓO’6eIm–ÃoñzzІçNñ7má¶ðÌÚ}ËÄfë*Iêx¯9¹ø¡â?†ÂkˆzZ­º°K¨ŸÏ¶¹É*2¤ö T_)£â‰>¿v¶P·µ!¤¹ wù?Þ#=@ôÍ{÷†üK¤øŽÊ;Í æ+¨›£Bw#cÐúŽã¨¯–ü?¯é×70x£Çöϼ²‰Îwo·oºNò8®»Åš_Ák9·>ø™4íB` Afâhf•Ø`2r=AP4âÏ£nuÈ •¬¡u–ï¼Nú±è(‹Oñ Öí{$¢) “ä[à1N¸ ëŠù^ÛÇ¿4Ö´ñ†g¾ò$;ïìE|õÌNTð=7^Ïà‹þñ”ŸÙ:´v÷ð)i ¼©Â÷Ü®…ÎNÿá÷‚üIjÐÜé±ZK¿{Hy¬sÎ_#¯µ`øwá‹©jÓXøG^¹Ñnà‚îóUÁôy¸&½³Á~$Ò¤ÒšÝ-¢¿œË! IcŒàc¶x—Å+i´OÍâ 9 Ih¢é­v” ·’0;€õÀô§vjÑÌkâW†¼B­>¡¢kWºs”êð<ù8ì,2QŽ+œñ'íñ[ÃÚŠ½Æ‹c.¡¶†ËʸýÙ™R»sÕjõkÚ¿Æÿ‡mñáüÚ}´°:Ël?Ò-î#!v‚?ŽAÈõâ¾dø©ñßÅþ ]KFñ†‘fÚõÇ—0ÇpÍio`y&|Ð\žTà±` Õ%}I;‰>!þÓö7ˆ´ýKÕum̦ÛK$ìÝ’òÈc †ÚzS|Cñí/ i×?¾2øH‹E`íäX_Ï$»ðY T+£8ÕñÞ¯ûFü>°¹›Døñ{ª­† yzΙ+B­3œ1ŵ¤PpC‘Üùì?´ìt#m_ÒîüUW’K›©¦V¶ƒ‘æÇ ’î,¿ß Ú³“³4ÙøËñßÇ §þÓšŸÄÞj>¾Ö,ï¼Å[È'Ff‘¤DϘ„66FkìÚKã¿ü¯Á? ôkÏŒ†ƒeá{(¥¶Ö<=Zß\Z]§—æ]Hò7™$½U+»Ü`|rýª¼ ñ¯W²Ðco ÜýºÍonYÔmQïd¶Ž?-W‰¨l2I Uœ×ÌxÁ¾ñ5§ˆ?áhéz¦ˆÖM{¬é¾uÀkɈÜ-¢Iæ½ÂºÇäÀ8§U“<¯Áß$½ñ“Ç BÛÁ> ¶Ô^{êZD³Hrc‡í¾Å;ŽÔ lÝ7c¯´ü+ý´ôOjòøö_Ðn|Sãýa±áñk¦µžB"yÖËFŠ­Œ©l»®3^Åð?ö¦¿ø¿à χº„+§|BÔÄvUÌ^)¯$¡–9Ûþå™)å‚))“ŠýÌð·ìIÿ¬ø?½ƾÖ|G­Áu«øªk»v¼Žb¹Ýˆ·´EÂÃ8w1fËÈ›[Ÿ‹¿³¿üSƺe“|Gøe¢hÖµtÓo´Oë°Ø^Þj@úÕ²]Ê"wî²a‰ù[2ÃõïÅ_·¯…eo Ù~Ï¿µÁÄð·îÕ,.5ÿ‡÷ «Ú[Ï ƒ­¼Ç9Œcap`•Û’>eÿ‡°þÅzÿÁ%øC¢Þ[x†áãRÓnâ½ÝÇ™>\1o¼C3°ÜköLÿ‚”xïÃþÓþø/Â6ž Ó4¹'¸}FmóP½XË”‹È…â“Ë9@LÜt<Ói¥{ß›¡Éü3ÿ‚–Á0ì>0xËàWÄï‹–::$¯}£øÇNš[6—VKÀ]¯<WR¬àŽ Šø;öÊÿ‚‹|!>=ñ'ì~!Âm¦^é¶–vž'°T±ÔÕ\GõÙvûØ l»1ô¯hÿ‚‚ÁLþüxðæ•ðÿà÷Âm3 ¯ÞÎ5ßxªÆ¬ÜmP'•-÷‘G’SÌ»dHXˆÃWÍ¿±_ìéx2×Å?¶7ÁÝ B×"¶)£isx°¥òÙ[ s{´ˆG›) of]±¯ÉÎwg{Hü›ð‡Ç­#âŽî<;­_øiµ?ÞA§j¾ ÔcûEõ††8¤‰Š#@$±#æaCâ¿4¯Ùwöž‡ÁŸ uûHmü%&Ÿk¬xrÝmïK<¸‚9|È̈<¶fmèŹÒµ?í.ž)ñ†¿àI4ý'Ã~³’cb±i±ZK7˜ªgš9•~xÚA¸08Æ3Y à™?·?ÆŸ‡t]Dðß„µˆZñußëöúB-¶ ûT–Â9gH™Få,È9()ÄÕZÇéwì—ð?â¯üç]‡ÅŸµÇ‹îuøZî_ìŸ hÄZøÃY“SŒ’÷7ypÙÂB,ß(ó¶€2ŠJ×'ñ§ö>ðü«ö¡Ñ¿iÿøkÄÖ´ÚÝk¾šIõ]N !#’–æë¥RWŽáCLZXÃ"’@ñëÀ¶÷íCû9êÚÿÃ/ه㎗c=è†úïGxR F5ÄläíM°…ÚYppr'ÄïÛoöÝø»àMWáÆÿŠºßˆô­L,Z…äÂK{³¥ãØ¿sÅ'ï)¡)]êf´nÇõ“ÿÎø¡ðBïöÒ¼ð1´ Ï©kz$V±kj†)%¹¸ÆÄT #D€NêªÏ´`ã9®ã7íQð+Vø‹áðÏ‹'ñÇÅcqÙxÓâ>›wk௠XlÿI¼1²G ÷œ0··‰Ô»” "ŒµÑøËŦã@]wR½×4ÿ ¤K¤[_\JÐØ-¾ÓAY#ùшr£y`>œOÚ[ãoÅ» _†~(×Þ÷HÖu‹Kù EHD—vxxYØ`¬QóhÞ Ùl—ÿÖþ#”´]#K9ôÏ'’àG1N¸5~f 'ž¼T[Th}á[–ñÁ{Jl3i¼È}xUÜÖq\–†,û·SÓµ{À+ØåÕu Üa¢Õôù u~Aeå?ï““^7}fmîç·-Ì¡p§­c³hÙ÷ Ô\1’ü ;)ãò¯fø€Ë­|9ѼBGÌŽ`$ŸàaŸæ+ÆåˆlÆï¨Ç8®âÇÅÂOÉàk‹1¥ud’C·ËÚÛ²¾¹éÚ©»Ø“Óþüx¸ðniá­zÜÜéмƒÌRL‘£ö ü@Ùí_UX|Fø]¯ZM&¯§ZDbÊàIœò¾Yù«ónßOÚÅf}¬: g?I¤w.Z0¡TüìGò¬¥{”¤Ñîþ.ñØ$•<%÷dîeù-à•o˜g¯Ë^I«ßjaw4i Ê €k&²ÿe‡æbØÿŸJôMጧ޶uF×ÁŽö¾Ò!´¹ùJ\J»˜ÄcÏ¥Ù=.Mð§Ã?xºV}#N•£@ÄË/îÐgо3øWO/¼á ã_À³Åó{NàðzŒŒÿúë„ñ·Œ¾)ø…Y|E©Ü]À2¦5m±ã=ÑvŒzdògÜ®Bôö­£Nê÷#í øëàN~¿cÓÚåò¹’é¶1ä€3þzWÑ—CLÖí¾ßðý‚$C3Bù,UøV^¹ÇN+ò|:Wª|<øÁâ‡W‹%‹ ˆçÊ“±Î~Vê¹ïÔÆ+›‚sWƒÔÞ–#•Ù£î«yüQe<°j Ìü»@aÏëøÖ+>—4á籎Œ€ÒGû·Ãqߎ¦»_üløqñBÇìlmv„ìŽ@7 EÉ#ÜzVv³ew¦Ý‰LJÖ¹Þ bHÝç-ïÚ¾r¬'ZjÌõ#8É^'?s§è“B¡XHrŽC ~”±hZœ>`Òïm®7mâ7Ãûp}ª³j6ºÛ¯—»8 FyëíWt+ ±Ãz"ÜäÑ‘©‘Fæ­­iɶú”(ʤ¯=ò:ŠÇwð¦±pN­¥ÄŒ?å¬Êïž+rÖëVˆÿÄ’ídR0¾ðBvÃv«ÇZ¸1•×ôøeoá; }Èþ”/!\åfð‡„&"=7Sx ó¶XÉS‡ÁzÍ¥ò]éEx#~é¶6çþ5Û­¯‚õxJsa2s˜ÿz«P•R×o|+áXâÿÄv*0K#³ñÐ* œþVЧ)lˆm-Y—Ïü>ár,¥Lª}É9ÏáQÏâ«ËÜÅâ}*ÞéIÁuŒÇ!o¯·á^k¨~Ñöёᵹ—iÚ ÖѧV9þUå¾,øµã_˜¿¶ZGÙ—fAõ9Ïå]tðSzÈÉÖCëï |Mð‡„îÊ{†´µo™ávÞ»½TõSí_K_IàÏŒÞ7V:©Õmã;.Q· ”ÇÜ.pãH¯Æ·Ä¤ˆÔ³÷ ääסxGEø…ÚIáÿ´[8Æ%]Ð:åÇ8#¾+Ià£mÇÚŸe_i7ðœŸè–÷q!ç¶&U矘&ìàf‹oˆZÒo{4w…NJ\~œàŒWoðCÂ_S^µÔ¼mâXá±»Â2ÉûÈäÀ¡À60 œ×ª|A°ºÑu+ÅVË,Rd-Ê)<Œ‚äŒwɯ.ªPvl쇼®x ñ†5vë:4P£wÚ¶Óí€}úÕeðÿ‚î¾M[–Íä.¡Ü3é•Çë]»øcáõääIlI#wÙ˜2ìÚ³.>›‚ߨúͼˆü¬w(cܸè~¢”jG¸Ý&ÌM'Á¾.ÒI½Òâ‹ÄZuÉ]Ão( ,#Œìb0ËœƒÖ¾Þý“¿i_þÌ¿áñ6÷7Éjm.ì&ýÐ×ômÛ–7á~×lw˜ “’¡‰j_|}£·&™!¾o2ÒO8t#i ?.*ë×ÝEá¿êWË­n.7¬Ö·ÄlA»°r=x­ã$ôG, Öç÷Wà?ø+ã7ÃM?âWÃ+ÕÔ4=^=𻂠È~d’3ÊÜ‚+À>%iúÿÂEûBø ŵ;­6ÝâÖ4«n%Õ´£÷áOá$ñ)R@bkðÃöý¶5/€~(Õ4ßÅ(Ó/®"_iq€ÆPuKUàìàʉÃ(%AqóLÑØêú}¦©§Ïí•ìK=µÌ.$†x%£tuÈeu ƒèi©JM ÅIY6™ñ?Ä> ±>:øOwo©øGÄ ¡])x_ÍQûņÇI¯dVÝ¿ím®Äûnô]&vã>UãFï’?ùÂWq|%ñSü+פh<)âK¶—I¹o¹c©ËËÛ;»Á¢ÏIدÑ?¾ø=⟠MðçÇžÒîµ­·“q=ºù·VîIÜÌFLˆÙV瑃_S—â}¢Õž/ ¢Û±Ã[~Ö¶¨ø^@?éÞédü²{÷ƒXÿ?þº<µ©`âœTÍ2Š( ¥N˜¦¨­=GR:SåÙ#¥H¬ÙÅGNQ–¤Å9©Ó¦*¢çïìòw¹r±ýŸvܱè¥dÁ9"¾|½ø­sq>«§ÝêVÖkž°Æ'eTxîFï=|¶R½ùÇ­Cv)Sê| ã½wOðçüà+íA·[j?¥µX|¯-ÝçÌFr÷±ŒŒWì‡u½NïáTZãI}gy½É˜aËK0EÝ‘üDþUøûGê>"Õ¿àᆚ†l²ÝCðmÄGAy{,±ÚUB3žpÀã‘_mÜ~Щö÷Ö®¯,4_S¶¸Ó¯ÑƒÅZ„~M«ËÕw-Üd˹prqSÎŽ¹SæGè|tK¿ˆwƒ®—ÈŠú{«W”)\¶Ë†?ígŸjáî>1C«Ïqàxæ ªÏ(±¬Ž]…óÏ9!Hà׿Ÿ‰~'½¯ÄíÆò6ý.Úáô½VøHRsª82í7FÁ¯ïž¹CñïÆ:¯…õTðγ|òëWñÙjšmÔccGrÑÈçrJ›$®;5J¬ú˜û$~Áøã–…á½ÇYu½¾ÕÝäWó?×KÔ¨ç¢N²þ1|o‡Âº†”ÚŒ©^[yr[©å{LÉ9 >•øãÚWLÑþ'ü=ðŸ„gŽ,­n5–¸‹÷‰ouz¦ÕlØÐU™Ñ»qžÓã‡íâOülðŸá[›»‚(lîL3‹«k‡‰’lÀÙ")bàsŒ`ðj%YšFŸC÷wÃß4-zÚÆÚÊUŽa´“‰ŽYÉ÷ ¸cžÕ§â/èúŽítû‰ˆ‚K_6&Sò±$.[ýI濾|U·ñ'ÅÿGe3=­–¥Ÿ¥]»ILCqœƒ$¹ ÔôÏßþ(éºGü#7°jVúåö—}n°ª‰&\¦+üͶávpÙj§s'LûÓT×mt m~ìbÚÝÂÈÀ Ÿ¦H­…¹/’½Ž?*øsBø¥¤cAð³â4å'ŠæÉ5;†éžHP9Xˆá’VØGr3_G|?ñ]­í°†þ}Æ$>i|† ß2ýAR1Øâµ‹LÊÍ;·æ™Ï9Á¦¬ƒ§­p:—‰-´[ÍJèæH&[!‡¼l¦9ïœtõ®î4`‹æ 6G¡ïVß©6Y†ÑM ¡Bw¤!ÝëLaœS!ù~õ>ð=j@;Sh\È(¦¹À§Çó š†¡)ÁKRmj•…Ie )CSmj0sŠ,H£.jjøéÞ¶L¾„t¤ u©gð§t.`*}éjhfè*EÝüT®‚â!'¯Jòÿ‰?®|Sf÷ZMÑ·º  O–ãŽ>½«ÕPæEN9=ëÎtÿèV†k]Æ“k,övÅŽBU”…ì6äj±P9‰š×‡/¼!aöÀtë°ÆÍKá·‡Z W;ƒ‘ŽÃ5ÏêÿuMWÇ—¿nm£m@·2˰âiFÏyp»}+Ê´MÊм£üOºh ·j¹mÓ‡¶l „#lCäç¶}+̯uÝ7Â?5|O4‘ˆîZÖÖm¡ök¥VY s˜ˆê‚¡êj‘ÎøÃCñ ·ˆüàk{˜T×õ¹´¸¸Êò¬ay"èQ„j3ÃsÖ¼ÛöÁñ¦™â„:gÄ]RÚI4á,‹¨[À0_ÛÛKŒ ÌXÜ`:žOô—íq§x?YðU§ö Ó¯4 Ã>Ÿ·" ˆ<`cHùºgŠüÌñhñD¿³oŠ8Þ¡ÏjðŸÿtû޲üV×oä»·[íON´3üªluWû:¹'Úe—nru¯š¦Ôu_é>%ð=ΡŽá¿ Ÿi¶ƒhf—QœÆöñ7Q$ž[¶9ÁõĿޮSèŸø®ËÅ×^ñÞŸÚ|;âñ§^_i Ì—†“†›qÁmólfr9¹cZŸ¼Ez¿¾üEÑ/fÕµ«í[S²ÒoÌm‰ü=!!b˜»$%öDXîuÜFI8ðÿ‡~&ñŸ„þ#Xh~¼]6ÑòÿG €Ånn#Ž+§ÉFP:2ÙM9õ¿ë¾Ô|âK÷Ó¼=á jV> šÊVa§¦°x$*Ÿ;;É#» •ì+.tSƒ>ôý™¼i.âÿ |EñNÚƒ< ã{¸õ½8,‘hói’’ð"ðäݲ#¡êjó‹öšš—¼â«3e¥x3[°ð}ÄKò>ª¯Äêw#5³ÄÏ‚6:•\… ~qýž:ý¦tÏüNš }KN¸Ôd2ÅÕŒqùJàœœ—ž›”÷4KñûÄßußÝør(,'m>ëGžåd$ÿ¤(†å¡aÉ;†#n3Ø×x?TÒ|ã¯üHñM¹¶–Ág¹ÒP‘, J)`8rÊÄ|£Ö¸ÿ i·ß5k߆ÑÝAcyâFêÏO|­¸’F‚IöĪ€‰9à9ÅEÍ®{Žt=z?†’ߨHæ;Ûdº…lˆÊ[Ëš`œ±Ä6ýï˜t®Ãà_ÀŸŠöß>x;M°k/xßÚֳáè €&³¶€\4ŽßqåPœœçµzwÀ[ ?³÷Ä_ŠÚ·y¬ßGá†Õ|)ic'³iÿeû5‚B¬Ì·þiœ”]¬ØäEý©!±ðüÿ ¾,xC[¾Ò-to…ú çƒøR¾Ò¦˜I6—u|aÍj@™#'XðkàïÙÉè‹E\ObIs€2HòÇñG@H¡û]ü`ñ?ÆÏA㯖‘ÝI¥¥²”µŽ×O‡ÈF‰EeÆ3È.HQQþÆo|ñ Åw¶ößo›ÅÚfá ]=Kn¿’úù’5RQ­]Òåß.OcKží Ÿ´¾#Õ¬ÙcÄShÖ^?°Ô_V–òäñ‹e¼µ‹ílÄý££°  O˜üxð_€þ ~Ú¾ ñ_ìß¡ÞiÞñï‚×JñE«Ç´Òd–6jÀt쥶‚vÍWcÙþÍ |%ð‹á¦‰ð—öžî+Ï øÞOÚßÛÏ›ZæÚÊãPž[o)™üÙn™Z@ØfÄåpÕðöŸãŸ‰’þÉú¿Á<_kâ×'×5‹'šñéÚ\Iq±Þ3|¢)C@ p¸£’!#ƒñÕχ|5ñO]ðÔz7ö—…<#¦¶©u¦ÄäM>Øm—Pœî+!,ÌIÎã’y¯Eaâÿ…rü>ø3©µ¼º^§âÛ9t‹œ$«wáùf ´Ê[zÂÓˆ®~ñ×øëÆ¾»ø‘}ûF~ÍÓ$v^!ø‘£[軕¦š=]±·ÓÝ.#8l <ÙÌ.HÜ’ã‹ø…·|Kâ}Á:K?†ºæ• hVÒ&~Ég¤j1Ïe¼GåÝLß¿ÀRÑ ‚tW)õ—ì>$ø.ÂÏOÐá·¿šîiQÈ–ëÛÙÛ]Ékh²Ììmä`_èØ»O‹_5ÿø'ìßûDYjÅ7‹µ/øª&FòtitÙ¥¾³š& É ÌмŠ—vWšôO‡Íà];öÐðN…áŸÃv¿Ú7 ©˜‡—ö‰.¹,£%^ ]îï/ç5óíC®AàÏÚƒPð¿Â‰àðÞœoeñ€Ò.,Êé‰câLJ% íÃ*Ï ÊsŒå½P­Ðù/áÔ¾šOüLÔí¦Ö´Ë{½'Ãךn–¿¿¾‹ÄѶK{eeü¸‘‡;Aƒ^Ïñö x*/ˆ?|7¨yÚ³é*úôÑùÃIñ s5ɵh”‡v,· ¨bÜ׊j\ü&Ô´9|)ª[ÚI©x·MÔõ]08˜O¬xoS’â"BäÅnËP‚c y¯§lÏ…z§?h/xnöþÖíüM}mªÏªi ×ͬÉ2¬ñ’^6Š4Dbr“ŒÖ&×ÖÇž|vø_7‚> ê6>ºþÎø‡ñÄ6~Ь£ ò¶Ÿ%²Þ_K(nL7(y¨ãcß|7ûMj~)ñ/….ÛÃ:75½CÀRiÆ­¥jÖz|s‘b?ÞC'–Íç£,¾{Ѽ[â6ø©Åjñ]ÝøFöwÐÞBÒ Éìã’úàI *gû¬Fkµð?ÅKÏÛ|8ø‘ êÑÉâ=[Äv†úÙTù× áù$¸’öì•…Á,QöüÎTœŠ³6ÎûãgÅ?h¿lõûKÍTÑ,ì­>ŵ‰çÐZâ9£X¥IÀ1ù<×é?‚|'ð·âW‡4Oüvñlž;øfÖ¶šÝÄËö«áËÛ#q%ÂÈ„2ÙE;¢,¨À²+œñÏÌ?´ÇÄ "ëàt=ñ=¬0ø‡Çú®±áÍ3Á÷ ¦ÖãÂ~,º…§Ôžúm„òl €%<~ðc£øEñ²ÙÎÏ\ø ñsÄöÞ%¶øfói>ÕíHµ}86Ò9ù¬6àÈÙr™bX’Y”Ž7âOÇ-JÇâ§„%ø¥u{6·§iz=ζ"DBé¤%¬hY¼r¬øÈùÁÆ+ᩚÓA±Õuï^ýŸÍ—S–å²íêêK„hÐðWäçÔb¾œý¦~+ü>Ö’_|5ÒõÍââóF¹Yuônbû<–ðC?¼ i7Ÿ3ï*9éù£¡j×Mu/‡5+Å—Hšå⹸Xñ!Ia×x}k ¦ô£uxì|!¤[O¥ê^[Ýx]^FÙS-Ô¥¤õÜÄ ƒÈ ×ΚËj¤¾ÐÙ¡ÃQ(óYA;Éè2sŸjö?|8ŸâÞ x3HšÞÞ+Ý}lCÈÀF…ã‘йÎ9T$Žkç_ˆšÍ²øƒVÒ´çKˆdT¶–dÈWÅ—jŒã޼çé\ÍXÙ»亹ñtž‹Ë¸ÞðM¨/u,ê$ãÚ¹ãJöKo[Ð|)¥øÇÃàµÖ© hð+’|Ÿ²Êw\X¿t8¯˜ü+¦ÉâÍfÇF°ž6ïWÔ-mc»•ˆƒxùÃvÇlñ_Iøþ;?Åáß Áqý‹¤Ý*!C¹~в–y†NKJAbGô¬úd_t-/N¾'Šnµ­;UŽå0ƒ4ÈëØîëõ¯(¾ñãø½µ‹äu:õÍÂNU€2¬Ä‰Ž@ù‡äv®¨Ýëž5Ñ5½~Òo&ßL‘"væE7•`xÀ|ÙèZò} ]BâÿSÔ/Â}‡Ã:C _-·ÿ¤K.åvÏÞ%¹^GÆi$kw¦ë~–Ê &Äñ4(¸,ÌÓ¹#¾G^ý+¨¸Ô"Ò>ͦß3”’ÚîÒ £9¸·Ã«ì@Ͻox'AÖ|L4½dë¶v:s=áy nV”#hÎw…é¶¼SZñ«ÿm”ò•xä¼{xÕ”‚$Úw0=òI€õ­1ì'Ó­'û ,¼ÝRòÎ7Èò`Qò«äRÄõÎ xŒ÷ºV§¬êÉä¸K³n§lH»Ä‚%ŒÇzökÏa|"ÖíÎ…"ÉâT%Ž¡7):n pè¤çäo—qqÆEy7‡î4m3⦣~©qiå²É;cŸìø%2>èvP (Jâ‘ÑxçÃÐÄ[TÕ •u[­A‹AÉ&9"ÜÞ~ï+Âþ.›Kð&»áÝ=®m5¬LPÏù]®HWF=•9îqQø«â ò|[¼ø‰ewåYǨÞFˆWz@%L•99ªŸJ—S»Ó-™î,£Õ${q½äŸ9HpàwÇãZGB¢­¼G¦x*ûþm>ÕÏÁúŽ•inF^Ie„Uì psŠô´ Ÿ²æ‘{ñ“Àqi2xãZûl66š„Fktm@aî¤E+Ÿ!Ø ®ã‘œ×›ü@µ»Óü+ƒõ›Ë ®[W´Õí 9Ã]™:"S×8¯3Ò4½Oâ†ü!—gÝj°Í \HÛV(íî w<¾ Ž*ùŒ¤µ<ÞÂóÄúΛ§YÿhG‘ˆ†ö™¤H[iiˆã$dôë_a~Èÿ¾üEñΧøŠ×SKs­ßê0ê‘ÝyFÒ !QáŠUÈ- H¦f®à¤à‘_5=–•eâ¸<3n¢ÔYj0ÍuPìöö7 3ÅוU£bznÎ }=âêëáXŸOÓ¦³´–SuªOª©k *ˆ¡E¶â'šýø¯ûfxOø⯠ͬëZ¯ö Åîieó5¦£ÀÚ<»,³¬¥ŒîITf¾ýˆ¾k6¿ üâÿÛjÃ{zÒišt2ù>¢wÛ•º|ePªlcŒíµí^(ø—ðÛàß…|®|GÑt{ ¼C{ªëÛD"ŽÄè‚r÷h›Kan(à –0Àä\I’G¡i>?øÛ iþýŸížÂÚ/VÕÚëÅ>#Ö­åÔôÿè·6¡6J†çP“Âí¥$ ¢+~ƒ| øcmàŸ„ZgÂoƒ"}Foë𥿱«4m½¼žs-Õì±[ªÁm ºCå„ÜÇ_+þÏßõo„žÒ¼ûLér?Å¿ÜÞ]Ì÷w/¨5­›JòYÙ*!6ÿiHYV/vd Ùþ›x‡Tøað{öTð÷‡àŽŠ^ [KàÍåÌÓLB#È„FbAºF @qÅlg7¦‡;ûIiÿ¾ x‡Eø¡ñY¸Õ¡·ÓçÒìôÛ›…g½¼»„*Ú#óy"*¸"«ä“ÍzwÂËÍ'Àºœ&øƒy§ëß×H¶ŽßKÒ˜@¶»Œ—ŽÍJ«J ²\•+g +ó{Xý§<1â߈Mâ+m xÝtûÏx/NHlõÑæMªßêS>ï*ÞÖ(ãŠ=ª_x‘@&UÇÙ:µMe¯Ç€ïeñˆ#ÐmbÖ5€ÑDÌì HWF¬C6ÕË®I'“ZÜÍÄôÿ…:›#5¤·Ãè©%Ì×1®2P#±m ´rIÅ}yqâmkUºÑ¼MròÜM$6öÖ–è¸+¸àºqøåüQâmKþ>‘át¼‡Áš~‘j6¢ë—w*ˆÕ Ü–$åˆÎs^)¤üX¸—źˆÓg¸¸ÔQãHî$ùÙÖeÀ åç`ÚsÎ=ª–Ç4Õö>°ðÞ“$Zí×…ì™~ͦó-3ä0ÉëŠïôÝJÖÒ$°g ï½”¤Ïâ~ ×âÒí#ð熒KýQ°.HSˆ˜Hwn2IëÖ½“GðëÛÏý©ª{§Á¨=Ö™‘ÒÚÊf‡ÎÀ$¬¿“O\à{RŸj¬Ñ€iV.rj`F}éÅ€”[û´ÃÔƒÒ­§5¸š¤ã¡ØzÕ¼٦Ȳ)ùqÍAåô$~4ð$è:T¤ƒŠ„92Îå@O™Lw $±®ro DÍÓÝ­ŸÕOËùVÿÚ6\y,rñS‰Ÿ0ö 8Õü#âYãvÔbCÈ>VOæU|â;×óoµtýˆÿЉ¯W¨%‘ãËÜài·p<`|9ñe¥Ñ¶ðïˆ'2íÞ$žã=+ZÄ–ãMø£áÔ¼³$/Ûldع>¸Ã©ëеí?]½Õ Ö,¯—Hh”¡ó}Ê}‹¬kz$6ßeñ‰ùŸä0Úl^¾¤gùÕGb“<Ä^¿Ó$i¼ ¨X¼“86ð›ÉcuˆrÅä^N=}kšðÿ†<'w¬]GñCÅ·Qê‹åt‰ç ctÀmX î8XõÙ|1iá@ú×Áù#·šq‹›{°&ós”<í>€ñ]§«|,ñ…˜Ø[ƾ*„½Ûç‹”#w€¨ m#­R-"Üü-‡Á>/:¯‹ô{ÄðÛÆ‹§Ôeµ~¸x\ÿ©#°V#Ò½/Nømáïjž&¶ž+¯ À5þŠÝâ›·œœ°ÛÜà`×±xSVø/â?øßSºÓo"Ÿì³Û[Éå¤s©Ú@ ËüßÞW'©|ñV¬KãoÙÿ\†Úúæö»+Ñ›{øúᜠ#øœš±\Èíføw{kh-ô·²•6íþÒò\ä † \úäÞ¼†Y¼1rÞý£ôè ¬AÓXˆ‚ñªòš $@£ÜG5Èkß~øCÅQx;Çm¨ø[Q1¡a|“yQ¹88¸ŒŠžªÅÇ¥Tý¡>2| øc7‡d_h6šnª²Y\Í;£NðÈ nP¼¾ž9æ¦Ævw<3@ý¥<7û;ëPü,ø§ãHŒb}XŠ Œ3ÛÏ+ Ô«XÝHR ™NG·éÚxâˆ4±âï Cht«ÈðÓO0g¸€ñ`¶ä÷''µ|5ñKö–Ôô߆Zþ‹ð“À·~/²X Žd±’Ê €G™5äÊ© i× “À=+œño†k†úÖ> éþ±°–Ù$oYjS\ùå‡Iã¶Ùû¡#\nùŽrE)6j©·­Ë¾>øñOàœ7úßÀï¤Ú¬ó}á˜B¥Än¸íÝ ø8f. ¨àëOᇄõ‰ZmÔþ¹Ñü;ªÌo,%„Þ2@˜2·™&7>N0b1Çלü4ÿ‚ü9Ò¼MÿÚ@Kðe¦u··Ón­BCu; l7“ĉ4ŽùŽ:œÈ~Ñ?¾þÍ_´.‘ñÇCT>Õm律ìáb™¦œ",òÇ”PˆÙÜ@ÛÈ8ôµ±2ZŸ¡š_À€ÖŸ øs3x‚ÒÝîí§Œ‹‰¾×ØŸupÚ¼çŽkáß¿c?Ú.cáÏ xFóA† 2·pC%«¹dšâx@~FUŸjžvî®»á·íA§øŸÂGáçý[HÕtkÏô‰Yu¥º† I-µ‘‚sÜ3ØÖ‡Çø(ŸÂ„¿ t›í;ÃöZ|¢Õìt¹e‘æÿ–·-à’I#æËsÏl%ÌÍ’ih~zxãඇûkÚlÓÏ¥ØÚø£O½³K? Jn/c'Ëòâkœ¿!Y†21´×âÿ>üLð?Åi®­<$5m(]«c¤K+8fR· 5Ä\‚®›YŠœíÈ®öèýŸ|ñªþ*ðî£â­KL¿ŽM2Ÿ²¥¼hi¼ÜÌÁ¤‘Þƒ,ÁA­ÿŠ·—íAñ2 á?ƒl¯´¥/5ÕΟwl²Gy1UKCP¸òm¼¢7¼‚5‘•P ØS)ÛsZmÛSÂt]7þ ãokš×ím}ñGÂW:µ¼%n¯¬/-­ º‰UDfêÊÜÂyÃÊÄ6v†ãëßÿj/ø&wìþúÀ/x‹Iñ6€áî4Ÿkv0@ò]nÃAv…¼1"ˆãsâ«É?~¼Oâ¯Æ_øsLO‡ß?n/ƒÖúånn|3£éÇÄvë\±ÉjÇ þ¥ #8ǯ¡ÁC¿á0øG®ü)×ÿf¯ |M🰗Çðíìz…»ã2¤—6¿bGP¿élÎA8ãm ”.~ÒþÐ^*ýŸ5¯ ø7âGÅ/ èž.Ñõ$Vš™çÙ0Ž=áÝãŒ3¦p œ‘_š¿~1øæ^ê_ ´ëÍ/Hðäfêi2Ke2Ø¡U“b‹<`€ØÎÜüý ¾1x?F¸‹Jýt­àÖ¥*²ƒ\½1ÈîÌÎRÌ»ZƯ½³±Fç˸\û}þÖßðœ·ñMÕí…å ´™-mL·÷ªOü¼NË+`œ—$ç­C¾ÌQ…ÐÙ§Mø)ñ™þ!~Ñ?´ ½»_i1F‰g¨³E¥ØÁ(2¹ŽOõ—726P‡'`Û¹Ùøsñöø¹¢/ü.ÿxsᄼ3Ïg  –MSZr (ÓIð»`]ï!Æà çòoÂ_ ¾?|NÕn¼àï ø“ÄWW Ñ>“¤i—‚3äò£xã ÀÞYrx$ãô_ÿ|HýŽüA=ïŽþÝh^*š×ìÖ×z½¯ú`@@8ŽáNЧ¦;žž¨ÐÝøÛð£âÏíoðöçö‰›A‹À·UÒ´‹JVŸSžÖyYä hÛQ Ä7:ïΕwý±áØ“[ñ²üV×þê´„fÓàKˆ~:¹±Ò|5j…FZ)FÖû6 a8%]Ü)<ãà?ƒŸ>øÏ⟡þß_¼lŸ tÖžWÓ|"±Op·÷,ÃÌ(ÑH@ÒÂ+Ê¡‡”QC†ýEø¥ñÇþ=âχ·? ï¾$ü_ñ…g¦˜ªk:…ŽèbŠõUz#Oo Q0ޘškaÜôÿÁEkø6ÙŸÃð­¾xNÆ£K ‡Rá–Á‰žÂS,ù3周%{?öKÿ‚x+ö¢ð×ÄOÚ¯ÅQÅwàÙuÆ6ºß‰¥KYc²¶Yî P$‘ÈÈ"öƒód׿ŸŒÏücâ$¾ðÏÃûoé×6³<ÚõÝæ©%ä‚Â-åÆodš$•ßf|‘ IbWÌ·—ú×ÄOøáïì±qãÝá–±yÛèöQê:Ý´Åc ˜¬QíæÜàâ”oÈ)ó1QüBø}û;~о)Ÿá·üÛÀ—:íýÈ{ñN§w.›¦… º¾™§DŒ„ü…šV\ìŒH;â'üŸöÏø1ðþëãÅO ééáÝ2êÊÎv²Õ{ïµj"·H,ö,²1f]Ûö×,rÑþÈ_à£z·íGào|'øW¬kÚÇ/SWŠÏÄvMáÝ'Nsï¼–u…U%f ò‚ œ×¥xãCÿ‚°~ؾ%ñïí'©Zé6ÞøG6ª5iw)¦øOMŽÜnºû$óŸ:útPU¤HˆÎT:wRÔÿ×þîc)tˑߓE´ «O'#ÔVÍõ”jUÎ'𪠋òàõQÖ³7±¹ásQð¾·ˆt•S,,JoØþ£T¹msV¸Ô^4‰îÈcN×ÒŸc¥ê÷r­µµ¤“—(U$°=øâ»û„^${c¬É•hƒ–‹õéÆ}…D´æi$vêK ØOcÚ®iöW÷Ó™ å,TÍÔõà õ¥ºøCàËeš;™µÛµåDI²<Ž»œ€Ùɰ|qÔí¯`²ðý®hÌ»#2Êyçsäd~€ÍÑþø®ò{¨,Zl1°ËÜɰcÈìqïŠß}á‡ÒA­êOªÊ1@áW?T?®k‡ø­y¬\xªUÔ.dx¥Š)QYÉŒîQ’÷í^h±¡Ü]rC`ÿ“Kp=ÇXøÁ§é‹aðïLŽÅ2ÊŸUÎy÷5½ðÃâ‰5ËæºÕu9^â#Â06·…P:ƒšù´Û…HÛ]_ÃûÓ§øä%bvQŒò¸=3À¥('×6l,µ ´/Û˜LróЉ”m?Æz÷¯BÕ´}ÄV­yk¶s0[9RžßÒ¾>ø¯¤Ã¥xöåc—ÉŽæ(nW VDÏsŒ‚+‰ð÷ŒüIáˆï<3zðbÞI;¢ç¯Êxæ°TSØaøà_@ ¬éª^/™V ¿0R:àu½û×Ïé—*~Éûݱïï_Vø;âØñ—‡/tZ¼Œ†xGÝ/•ËàŸJðχz6ŸuâÈü;âCÅ)xX#èÙÀ ž£=EtS¼V¡cɤc•6;Ž•}Éâ߀‡KŽKÿ ¼WqÛ£™}ªÅ‡ œc½|£ªè¶æiýĪ~eÆ>„uV‘ª›°¥‘ÊX^Ýi×qßXÊÐÍ ’!Ã)Áõ×ÂßÚ{Ä:9]7ÄðÁyjp»JmË1ä’:1þðõò-͕Ŧ Àa¾é ý1RØè§fe9©ÄP…HÚHªU]Ó?X&Ö|ãkÍ"dFãa†ŠAÉ—†Èô¯>m6Ý‘î$‡È”NÞ£Ÿ~ø¯„ôÏøŸK¹[« "EÎzŒŒ}3ïÖº-sÇ~5ñ ÖµY¦r¨$"àzí'Üó^7ö]ŸÄw,Z±õ»âè;!¸¼ÆÑ!p™g%z—5æ÷_5x§‡­ÜGÑd¸|þK^ ’e·/3rÿ1ä×9®óOøUâ«ãçM[DY,fµ†-[ÔkRz¤P×~"xßÄòooJ޾LB™ó·æcõ5ÉGjÓ\ƒm20ÏrÄ÷àrú?Aø7£E™/nÍ݂̋"R=A5zU†o Á%¾¥ªî_”ÅóoÿsJxØCH"• KV|Ù£|-ñ·ˆ_v¢#!e‘c8ú+Ö4¯ÙÇP…šÕæÆ8Ìh»À#¶îsøW¤\[éÒ\µ´¯5´®‘’Ÿ)ÎFFsëÍéš••À“KÔÒP0ê‹)Œg¶³×§\Wñ³–Ʊ £¹{EðG<;Ç.œpzÜÄÄÉR§¶åÓ4[ô0iºËZ9è.”óí¸c›'мueÇ}a ݾpL°î$ö”õü*ñÿ„.&h5Í2HŒn†P=v7ë\“«)=YÓE- íMøá[á«è0Zk¶¬6ÜÁ༨O%s€¬:ç¿JûÀïƒ|qltÁ5»^[;»Å̶ÒGÇ•(oáÇÝaÅ|B“xQÄÚF´-åÎ190nkª±Ô¾%èÑ,ÚEÇöµ³¬Š ¨õæZΤ9—™´]¤üsà]#ÂWía¬éé ’ INCtaØæ¹s០N(µdÝ~U¹ùGÓpÎ wÞñæ…ñÃö¾ñÁ¹³ˆ(XlšÝ†põås׫;Ä? -´‹Ä´ûcÆ¥sæÊ¡ãprrúŠó¥ž§T^‡?ÿ çÆº^nôuYcqör‰ `þ•ä¾9»ñu¬Yë‰3B>`—hX¡Ô°zŽk×4¯ø’ܭdž/ìË(DW&)@逇 þ•Ùülðä^ª³½°#›˜Vâ3ŽÛº‘øÖÔª8³:°OC礻ÔtKí7Æš'Ôt¡ÉÎÉídâH›‘´’pE~êÿÁ>¿k}ᎧÁÞmðv­u³D¾v=ñÆÎB@)o#ÿª'ˆÝ¶ð¬µéðFÏø'ìñû}ü,øÝãÚ[ÃßÝXjVšv“­Ù]=¤Ú+›q3}šÛä$´r\SæÅ R q^Wð+þÓñsÆß±‡ˆ~(xoQÔo~'ø{VÔ´Ï|7Õ4èm,µkM:g„ɦË"£É-ŰYí÷»Á.ã* Ⱦ“«M­]½|Ï6P{³Ÿü¢ø³B¾ðŠ /ÜM ¡$— ®„t‘ Œ;ã•ð/âw5G? |Q3CñÀ¨.­µ?,­®µ§ª.?ŵ–;¨IÊH7••«ãÿØ#ö©‡ã?‡lþ |@äñN‡d#Òî§æjÖ +$‚@;ëP6Ï€HÀgGUúÓ₵­bÖמ•l|IáÛ¿µXÊA"BY-߯îæO”ûàŽEm„Ä:u¾†U)óÅÅŸ©ž ñΙñ©â,yU¼›ËVûöó¯ÞSìz©î9­)J“Å|ð‹âæ’×Câׂ#›ìS8°ñ“ &âÖà°eîÑì`0é÷sšû¾Y-¦ŽÝ>d¹¶¸E–£9Gº}ëípõ£5¡òXº.œŠ­Ï4ͪ*n\Ó üÙô®“ŒiŒvÿ?­8ÜRÓ€fâ§b5t§Ž´¥ôïM ÑH›bö§£µF»}*J†„˜ÇÎ8éƒV@'Mdæœ@¯€zÒlZuFƒ ÔÍSQ@#œT«ÆãO »Š›Å5P˜ÓÁïŠQÉÛR„QPÈd`Ú£eÜ}ªÑõ¨ŠñHD8æœWŒ •W=iûW­44І&#ŠJ¶G UBqIƒD*˜8aš dÔƒHqÚ…¡PE íÎ̯nÕåÚªÉcñwÃòH0š†}oŸö¡’7ò&½~¼Ÿâþ6ðè8Q½´'þ¾-˜ù­Zw6‰Þ¼Lüã¨üŒuíZb1ŒQ²ö5e?,cüÿ0N](­Å.À¼@6qób˜SæÈ5i”“Å&Æ  Ø4`š¸¨;Ò˜Æ}(;g>Õ ÕÆG=ê2¬8î[3÷iB2šxŽheb8¤ãÈlŠ•™ÏJbd }>6'­XAÎj%_â*Âã 5(`Ý*(h‚âôüŒâ©ÆÄÔÓP·4ŒËaÀÓU±‘UËy©ªÍ •óÅJ„zsUW¯&íƒm\ “Ïj•\0>¢ªFØ?Z”|¹ÇzpcœÔÞ•[#;jd?/Ò€,ÛÃSƒŽÕX6êx89  %ØŒS{P"–€,*9« ž9¨ÀÞ9íS'QRÙ¡eX-XTÈÍT«ñô&2%Ž7''ŒUøÂ­Q¸¹KK9n¤û±)cMŸP¶³²þѼcc‰À>¾ƒÖ­ ¯/ÂÊkÙ+ 4Ž»Pdþ‚¼Æ_Á{àmkT³•n&‚ET0l¬øòˆ=2z`õ"µ<¯YèÉ{£]³#˧JNÝÜê™ü¿ZøkƚߊþxßÄ? õ½8IáÏÁ –ol|±k-²îØ_þú•1Ëdjʤ¬oÜ“âLjt;Àún§ªË<š%ê_Â×±š¸ "$ñyª’)È<:׿GÅüF‹ºsCf’XëžÕc¾‡~#°Õ­¯-äKV^©;ÉñSϧ×?>!èÿ4?|'ñ\\Á¨Cm¤ÚND’i2 ­ÐŒñÞ(êPûüÈø­ª.ŸûAiÞñ:ê:-Σ{q§ê¦á¼ˆnå°fò%۽žß¼6ž'ßSxFÎÇQ¨|O²Ôà·ßõÿȾ¥¢Ågvò7É-ƒYk.XãïG *Äñ•µzö¡mâOàëü-Óõ zÕˆü8,IÕt;§–ÇŽ&GHˆYcžâ¿&u¯x³Å¿·ŸÃ;ia.¹¤[¦V+ºE—HûuÁe@Ä‘©eSÆ8ÉÍ~¡ü-ø¥ˆ¼ ñƒ]ðÂXxƒÆš5èñµÛYyië#+¹Ù~â»X¬Hd‡‡“ÔÐnÑç¾$øµá¯øGRñåý›Âž?µ»ÓuPˆÇ.—â*Ú]³JUmçŽ@F%E듎'^ø¡á­.ÖïVÒ¶¯ˆ4«ZОòð-½ýŒŠÖúŒXl™'·‘ãcÏÌ¡yà×Èžý¯<7ûY|A²½øà ãñ-…ýþ¼÷‚teÕt¹mIíæµ |É¡yZQ*YQ²XWCðîëÀ~øZÖÒHãºð§…õŸ[éÆ4´Ó$6":$¤«-´Äˆ÷m ´ì$esFyWÄýwÅZGƒ~!k^ÔWKñMޱe¤xf"Ëæ^[AnòTªÅË䪕o¸IÎp=ÿãçÄŸ†þý«4Ž¢OìýFÏEñ<šLÀC=­ÄöÆ ë XçW‡ÎÜ Vi8ã“áíþ“ÿŒ޳[ù-ô=*óÄÐÜþîa$¶÷²ÌHù¶y¨²2‚TìÈÚ2<Í,eñ–§­xÆõä´´ŠöÚk”1†ÑyÍ»$/—ç 8äffÍR?Cÿeßk^9Ö4ëˆ_NÓµ -wÆ7r¡Ž{d¸¿[ˆÊäàìâ=¹—÷¯íoñ‹Wÿ„»À–¿,íîon¦¨.Ò\-¼öÉq0m»ÂMn?u³žM~ZI¨7‹~OãˆïÞÞ_ xrþÏQÔ­OܧSl9ÜR ecÌ= Tø“ñÎûÅÒxÃö…Ö¥-4χVº޶)µLöïäIºð­p¹]‰»,\ô¨ˆÚ¹úeûDê>3ý©oõ-ÚþÁ§h—j¬YîÛÉ¢†á—€ ÑîU\eBõúWñ{ÄZÇÂý*Ûâ'†Y¶›«M.ÿòD·,#²y2JÅ~˜$׿ü÷ᖥᎾ0x™áÔdÒ-ôKYïî×t6‹²ª/ZÚ;˜—ížœ×Ø?~"ê>7Ñt¿ƒÖËomsã{øÄ°Þ¸_ô ×b¼ƾab8r¨:æº í±Ï8ê}CñÄPßüyðÏû á|šxµ½Ô>\Ë$[ÂŒà’è×Ö+ãm쉨IqòI”S¼ã$€2qÒ¿¿fÿjZN©®ø¾îbÞ ½Ónog»™þüñNmâ–\å¿vœDØŸJý-·ñŒ¾øF„É—S}—3™ÞÝHšR£þYªà®O,@­ãS]L¥çÜþjH‹ û¤×=jP2¿-x–…¬Ï¦øOS‘%Ý nŸŸši”w¨èÒ7JõMí±è®¤ÄÜ Êû¾öAïé[§ÔÅšÒ¦Îjót«Us¢Í(ãžÔå1©QFÃRPñS&qL&¥QY½ÌŤ#4´S¸ƒŠÁ=*ÃŒŒÔT6;•òmjdú½êé²7ïØ1Úq·®}?./ìt÷ŠKÉV/5Ö8÷¼í÷Tz’kå7[µÔ<5­xfêy$×u»©-cØ>uX¤ÜäúÀ$ôíHGÖª6Æ![ÁëY—:”+pÖ0æIQ<Ç 3±qžObG W„Ø|LÓ­ü[s¤ê¼ú†›jÐ%œ@«\Jƒªç¨ÃdžÆ½: fo ʾ¼òî5‚Ϩ²>RÔJBâFß(wÐÕA+jºçäÓ ºiÐX~ì¦q÷‰×±+æO kºœZɰÔmá•ü- Ö¡ePRxeúnî=Æ¥vÞ+ñW>\®¡¦ÚÅq¢ÞË>Ÿ¥Ü‰€i­âþ`9Êr¥\ ñxªmO@Ö¼O§¤ÑÆš@¹²¶U ³ÌÊé!SÁiDºŽ3€N3RÝÊI"ßÂû«ùü5>½ãöË/[¸¹c*ØÏ—Ës‡FÞè;·Ò¾\×Qñ΋‚,-Í´­«Ùk6+:ùŒñŸ— sóíÎer:WG§üLñ?ˆ>i·Ÿ ï`Õ5/ˆvžVŸ¦H<©íæ‚/.Y˜à¬pG»{ 2€yøïâŠÏæ¾™$NØi¿wq(”„Fy5Ç)\éä¶§ÉÚ†¯¢|Gð§…–kѯµvŠ+»£›o:äÍ*ï#Ê ‘žAæÞ#Ôü yâ_^]XYèx{[ðòX[N Þ› ÄÂG@ì ž[}ÝÅpz×uûHh-øB×¾ñ5¥ûø>ø4ml ö76Ì$n¢?ydŽP°`YpTäs^ ·š&¢ßøT¾´º×5ÿhÓAw,Œ†õ|¯(·üóQµ²¤`ƒÀ®)èÍ¢®z'‹o"ñî¡wáÔž YoàÑÄ’†l¤?ê&lœÌ8ù¹Ü9¯0ø5eãéVRx f¾ÐgRí#Ž .wÜdð%d(1Õ…i|.ñ ~ øâjyÚäA¡@ʸK‹i\JU„—EÏÎsX:Gˆ´ûk_ü Ò®whú†Š^ˆ€w¸LÜD&‘~îK²õùv㩬‹húßöAø¬<ñ'¼_uk¡xsâ>‘?†zøÝãí_Æ—6…ÛÃo¨3 &écŒ©y„gaÙÛÆ@×!ðçÃÖ~(¸Ö|#pÇv˜..à‘¶˜æXDCà(À$÷ÍUøƒö´ð%ÿ‹g¸’;«ýFÂâÚîD\L³«9ÄKŽõcX·þÇ¿ðç‚^UƒTšÞ[}Y“>Lw‰òøÁ!Õ~sK™–­þþÔž.ðwÁÿ‡öz6›m¨é^Ònlc„nŠr’MqqjA·Óù‡ ;B÷Í{GìÛ¢Ëâ?j_ ~,j6zÆ“©G™g½ Æ·vð#º4 å"@Tc$Œf¾øS¯øëJÖ|$ÑØ±Ôµ}q,ô›$Aö›w’i…Â.ßݬ+½C•^9À­-kÄšíþ½aàHnE¥ÕÝÃ[#˜eŠÉÂ<ªçï•eòÀ8p;R¹\§Î;ÕõícÃ~‹[[ˆPÛÏ”7ìxâGH™ùß$E•“ƾ—ÿ‚yü=ñN“m|4‹Ý> ½3QvUŠÎé˧(n¡U@2Ç5Å~×ú&£á¿‰še‡‰eæk{ýB+xŽõ‚êæHMìj@ù•$1 úçÔ~ƶöÞÑ|mñvÖæho¬&‡FÓã  ç½·<Âà б ê2c×Grd¼µ9uÙß⥵‡Žu‰ußx+Ä÷þ6þÔÓçQky.¥b¶örº6dû<ò©$‘… Ï˾êzÖàßê~kíB_ é:ä·Ö0‹âMm¤’k gåŠE󔌠*œîM+Àšoí-ûci_mfšßJÖ|5e£jÚÕÂ$¤šu¥Ìßht^Ü\yvÑ?)\þ|jB]2âËVøQ¢_øv+ý!EÕ”³—OÉms ÜNâÒ‡·ÊO«œˆ‰³¤ø7Å~ÿ„7Ãÿ|[om¤üTÑ­ôصքIö8ôð†Y•¾Úw%<:’"¾Žøƒâ?…ž?ø£ã›´¹Ñ´ GPðΙ&£¦'š÷úg„ã–åîˆ%ä»ÔXoÀ-$%@ää|ßÿ\¾0øƒ¦øGàî‘} 'D»¸Ñ´!: ÖÚÙDš‡—36ß5Ñ]”î;/L_TþÐ^>økñ#öµ’?ÙóʸðY×t ½9¬âÙ=¤VÌ˸c0ÅúÒ2 ÆN*T™E¿Ù#⯊?n¯øÏÅž›J¶Óo[ío§Ÿ6ÞóTŠÂêÆÞÜò^ݤrg]J±Ûº¸Ï‹’ê~$üLðgŒ#,ñv¹>5¾·ÆÆ±Ð|%v'Ö¬äˆmH ¶·VU2ä!ðNZ—ìáÿ 3Z´Ö»øÍ©xÓÃþIn4«ØÉ“BÐ|7¦_X”a8'xdy€#|±¡$„¯Oø‘âkÁ>>ü#е KÅ. £üRø™i6Wí u‚{™B¬qO6­äA»’E‰‰ ¨âª*ìÎRgçŸÅÏÙ?âgÂχžø1¨õ[6öÊMJîÒaäÿiê¶‘ÙI$yÀD€;y¸ÆÐ í¯¶>3xrËÇ_þøÅ]¯(Ñ>ø—ÄN²j“K¯-—öÓ§ÊeþˈI}Á ÎÀ*o•ÌÏÏßß¼âÿøOâ @´ô½#KŽÎqªèo%Äéx9’xцÐʹ<>ùø{á?´?íáátÛ}SÚãx/ÅÚ„ò8Ì_bšïVÔÑÃeC<–ÐG8QÈ`¸šù3ã6'ÀŸÚÖ„_ÛI<9ñfú캅ònì5 ÜHqÄñÚܬlx·¼Šà'ÚtÚÃÆºwƒõ(ô7†¼O™tëægIÒX"¬mÆe‘Ä’sÜÔ"Ú=Oöâý¥<#ñwÄßõßZÊt¿x÷Jñ'‡o^UT{+k x ‰Ê,²$Óª¯Ë™êM|-á x[Ÿ­ÿ´e’êÜØÇ Ú„­–™û´²ñ?†‡þþÏZm¸ÒüA¤x£_ÔïµFÚwÔÒeXÈÁFó€ cšùK^Ómf´Ó¯mXÃЪc,w¶@?NõÜê7QÜ=·Ä bI·`ðͳ%mÜ~ìÃîÈ¥zzô¯;ø¡wý£­½žœâ8-# 9Æ `0§Žk&®T‹¿ïmÏÄmÖéÛC$²K+&UM¼NêAËzöVÊãS½Ð­b¸’8µx"I9ÛpA2ötæ¸O…Q¬SXH ûu׈õt»8G îìí?× =5ëòè·Ðx£Ä~x–óTøW¨‹=I¢`lámI¶Å “ƒ‚íå§«;QÊ.cñˆŸá߃ô]6Ô§Ö':•Ô²‘­Á!cvÇÞ 9^¸; æž÷0(·»ÈŽÍ c¹ù2GðŽÙèkÕ¾$|3Ñõo‚Ð~ÐÓêE,õßÞø?AÒd|ÜA•jóÝ\Ê ãd²Äʸå(sœÇ|/{»Ý'UñÊÄ‘ZØA¹¸-þ—&Åàðíbîz†¼+£Üø‚? ø[Vxï ²°yHoó¹$p7)\wë^JºŽ¨èšÎ— æî8;›(ä?2’ÄH« œæºýê©w46ÊYå˜Y;ÈEfC‘ýÌœúf¹øEÔïn4oÄ,u+…üƒò±;埔°÷ëI &·Ô&ñ.“¤Yx¥þÉðí´Ø…IÞ^G-”äç,Fz{Õ1á(ï¾¾£)jz‹"ðí•‘]ͺKq2¶A*IúšÃÐõ}>ãÃ:}ޤne¸ÕgóbyФ/`‘àH­Æðî:‚GéšDº.ƒá¯ˆö·Ë¨øÃÞ/µ¸½²„‘2+‘Îw`;¼ F™#ä9Í&[ži¬ø ë>Ó"χVµÑBµ%¹¹híÐ3‚ni8#½u÷Ú4~2hžÒç ¬xu ®µ,AŒ1^D»ðª`çŒñ\§‰uý._ê2iä·þÖ×Ω ætWÚp ­Ôáºf…ãO Úxâ_‰¾3èçÆ¯‹´Kˆô{ÉÕZ+-j퀎ýãKJ‘ª¬xÁ]y&™'O6³§©-Ö´ Ür]MöiÜÓ!e@Ú|¸úÖ=¶­©èþ ŽÂ ÕL1IöhþìÐá‹íÝÁÈ<°Èéšé<%âmÃßcñ½¦å¸Ô#¼ÑáŠø©mˆGš01ŽOÔÚßÃï ÙGáMÄ^%[Ëë›f¹ÓdŒIäY(óäñ¹ƒ…ò¶ž>AÍÙÊÞÓ´è~êzƹ êíã}OWiZ¤»’ÄhÑD‹.#fYd˜»ä.Üž0_¥ë^#>Óô ÞÜê¶ÿl{¹tàv¡xs;ÆTÇÓ5ôø¹á/Œ_´¿ü-/‰ÖþwÃíÞâ]AŠ/.Ô ‹È´¶ÀÚB<ÿéÇ*vŠù?àïŒ5[›­7OñÓBñYXL.E’ŒÌfó6ÆÝvïÄŒRØM6}—ñz?†~ðí§Áø²KÙ|R,]×Lv!ìûdVàòþYÂïícŽHÅqßµ}SZÖïµé4¹4hvIý–'UˆêšŒ· °ÜNÏ"¯Xâd¶âFß ¹×oþ5ü]Ð.'EKKISNÑmcŒD–°É0ó‰à™$Û½Ï`®~…ø™áßhú曦ëWB{]TŽæÞÕ2<õ±n oùæ®FŒg¥o» Ö=âÃĺŸ‚¼caiñÅóÙxjÂþîC.˜¦xÔ6E„ü« sòÛæ·þ A?í•ñCÀ–6Ô¡ðׂ´ÝvöûL·14—³éº<«wlóÜÞ|·w+@Â2y›KmÜßøÚ?|Z¿ñ7Äßj0ê7:lÖ¶ÚUõ«²ÛÛD¼‚¥±‘Œ’zšý2ø ¬Ú>‚Ÿ¼}4É«ù6¶·sJ,¡ÒÔ`vù Ç„UQÕ˜ÿ:FM³&Ô¿‰0Óuß…Z_~$ÝC§ëÚψ¤Õ`†, 2ÚÎo1’.¦5™•Iq×=0>oý¸?iürð/†üHtÑ¡iAuiá­>ÑÚæóQ×L_c–HB ‘šÐHñ¢F3ºVl| ‹^2ý ~|1ð$Ÿþ.xfÛV½¶…m|3¦É2ÞLìŒH¸0í ”‡œ>rÍŒd_¢ÿ³š|a𬶿¶oí?†t^i÷V›¥êq”³ð†™½g»g¹Sæ>¡¨‰ï‰6¤I»Ë/'Z9ÜVçœü øMâo†^*ð7Ãk‹(­ußü2†ÇMÒ¤¶6Êú¦³4B[©×™ µo6W%гó3gî]Åü4øq£ zþÒÚûYÔ.ľTKÝ]Lζ3óŒ A#(Æ8Å|¡ð/â¿ÆïŒß¶GÆ^.Óî5½BÚïIðÞicu¬m³Ý}¤%–S2]çkå‰aœ1é_ÿj/xgÄW7—^ Ö<#-àŠÖÛSÔlåÕâñä›s)iÆ@m¾¸À­$v>ðwû@ëš§í`×®|%á½"Õ.uCæGú¤÷Êè®J¡XâA• yãÜ|8ñÃßÜÞÙx;T²³Ñä¼ÜßXǵÈÀÚ¸Q¼¿*Àñ_™ÿ¾%|5ý©> ø§ÇÿüD4Û{íJ#¦É+ÁÅŒI«œ’¹s+q°|‹É&¿L ÷„u±·ø—}y¡JËlÂÞÎÚÑäã9`§‘÷ˆîjÖÆ3I‰á_YY\ý²Î=Þ9ŸÈ2>ù¦‹qkÎ[9ç=«Ú¼=3[‹ëy¤xeUد××<×ÈèúïŽüGŽo#òQ…Ö©&æÜÈœ ãœr{WÛÖËRà"à/àT•ÙÊËf<·LRW¿50ÉMOCÍSaÊØ¬€jË `c € òÉéÅ#GŒžµ9fSòŠ6ƒÉ  ^B½Lx†>íhÛSÓÒ€*G&‘mbBÌ'Ö­0ŒóЉ™$Ð9«é3]ÛH¶{D½U˜÷Ï[k>*[a ú<"ŒnÜMvQÜvÚBòyÀãó¥óµiä‚!õ“ùPÄ E‚¤6¶*Vs)#ðÅN<%ª^ÈcT›ømÀŒ~|šéRÔ­†níIQüQ0l~4!Òg;ZQô“*Zä§øqàæ}í«Ý¾:Ï#ÉýqúSàøoà¿/3èÖ¾Ãgó¯A†hn>häVÄÕA5W+"#õéM09KßøJÞ×Í·ÒaPFÅn˜Ÿ\68¯4ñ7À‡ÚµºÝéNkÑ ònÌ(RpèI3׸ê+Û_ÂsÜÀâîQ.1ç;áv÷zb¸?x|x£§b×Ç5ÜÜuÝÿU>fRg‡|,ø_ámaÖ ÞÙiž6Òîeó´«•&Îär‚@€ÿ–@=GŽi¿ |ñ.£ðóÅëq£N"ŒH‹Üydƒ‰Æ˜¬x*g^ð¬§ý˜3Ev‰åIN¸l×'Œ×Ã~9ø—á~´ÒxJþÏâN¤f)%ÊX\Ç0 é-ÔjÑJÊrFKx/RÔ[>ÔÒ~;xkÅPo ëúÓîn›Us—>oÝ£þMy7ć²gà _|i¼·ÑuLƧ5Ź޿elˆ¼œ•$íX× tæ¾o³Öo¯‰¢øO@Ðf.Ѧµ¥M+ÅŽ¥Ub‡Ü-xÔÿࢯªÞ|\ø9qð·Á——¶Ÿf‡Ä¿Ø_é±!÷Ð[\3D“†ÁŠYŠ1Ìl8 Ö×V[ñGüÏö{½ÑîþxñµïÂAyiÚV•¥ê×[›ìɼ;ݲ?wå“‘Ø5èzoˆ~7è¶1k¿gûÝ~U£ñ_ˆãÑ®6œ4Q­ÔЬzïù‡¥yå—Ào|=Ó4ïˆ:5ÿŽ.þ YÙ³]kÐÛ¦¨eÖ¦Ü\Ç*¨&“•H(£\ª…ÉÇ+gû|JøsªÅuñcÂÂÿRµÛ\²Kf¡ÔàÉî‘cÁÏÎI«ARVرñ{öHÿ‚“~Ú Ö|ûK^øSÁñž}É›UŠêÎÑTaR8Ò$¸¸!xG’hF~mŸÃ_øëþ ð£ÃZÿ>8ø÷U³´¶OÓü‡µÔµ9ÕAX£†)¢+ ³q·tm#’ `0+íÏ~×^ ý§¼Iuuá+=A¼3£iÑÈše†MܳÀ–YŽ6ÇYÄhØgq¸Œb½2Çöø®|'•~ ü.¾Ö5ùmYëþ'Ô­#{;Ì—Åoö’6ùúnéóŠ®wk –úŸ‹ ÿ‚êÞðÍçŠüuât?Üÿi/Y’Ö Z;f$)6ÖÑPy æýwÖ±üeûÁ4¼-ð«P¼·ñ¿Ä"êÒ! ×bÖâÛUÔ` …ò­àcqˆÀxÎÖÜÙ#Úÿ|[øñ/áþð;â í•Ï‹ühÓ[?M ™bˆI-ÄÄfTŽYå0ŠÝÚ2d‹}ÀÃÄß´Ìv6–ú]²¬Ö·@h»É´3¢…i7£æbÎÇ=ð"úš#òwàçì}û(|Fñ–•á]á§ÆÏÏ’"Oi‚+çÕf;¤šêÿVvœið¾B Iá;d`û–½±ÿà¾5½øwÿ ïì¹á/xsDc¿¸ñN¿¬Yâpò¼»wÈÑ–DuI ¬9Ê×éïˆ~$ŸèšwýÚK"@!³×üSö½"ÎU þ«LÓ Qyx±à$ÏA]ÙÅ|Ï㯊³_ì¥q{ãø(ħYÓµá½T>P’ÚidÓ¥ùI+ºm2a¾eRA8¬ÝÍÇŒø¿öŽø©¨ü1ÑþþÃ?³·Ã|>kÀ"Öì´Á«YK2þT®ÑY«ÌÍÌ«ndàȦ¾Gý°?mŒþZüø½ñŒj·ž¹mK]Ò|/hÓ%¶Þ!·’XK­Ä‘9U–žTF*Úú'ö­ÐoOÛÏEÓþ+|UÒ¿áGü&rt¯øn8Ež¯ý™py–DŽE–ämi *¬Q€‰ÊÒÉÕþËß±×Âÿ„Úžªþ̳ñÞ»áÆyu?ë1‘kguÎI—åI û—ŽÚ6,ë)e1“g¯kßðCÿØ÷ÄSêß5/…V’h©s%ÓÜ鶉¥Áo§Â¼GŽHËFй!WkÀ5ø·û[Á1ÿb[+¹ü]ûøöò-/ÂÝê¾>%)‰£ikžRE'’.^îyÂǸvSb‘šý3ø­ãOÙÂ? ¤ñíiñgã/Äox’覤éÝi)­€ãÙº>™·e²©%VC’ of?5~]øÏöxÑÿj_x—ãïìŸàx~|;ðn¤t_ì¿êW:…ψõäxÙ®ï¬-å6±­›!Æóc;™¾n•HÞÚŸG~ÆðL/Ùçöðî•ûHÁZ>¾© xâÊÛþí?[¸‚âÓOÔeC'Ùnt¸Néd1åÌ“4¢=€ …›? þ&ÿ‚¼þÈ ¼9®øOàÇŠ Òì®. ÜK¡[y·‘ŸvÞÎÍ~Ïi¶3µF‘×!€ÜA¯ÇÙ£ÀŸÿkŒ(¾;ÖuÚSâì“A¦Û%Ρ ØC ÙžæEÓ,ãŒ^$òò¿JuÙ§àÏÆ¯Þüý¡ü!a x¯ÂñC©xÅÞ"’ m;FÓneÙm¦hzmŒždÏtà#Ípˆòî O*%« Fß„¿mŸÿµÁ˜¾Á9¾ _iºåÒï~$|D¾‹Lðý’óOg’âîB…‘e*á%;Ø6Öœý‹g¯‰¶7^^jd\_i×ÑÜËó*I”ÏШ=ºóÚ¾Yº*cÔ³CÈôÇÀø—Âþ/øzˆ¦[í4ÉêÅàþƒ óJKKŠç͉uj"V‘@ÇÏéì-åßÛ$>XŽ™8ã§Ò¢W{eU†5Î$óÚ‘µ™JȽ:~5v žãñ- ×"Ðuk,ÈdmË”d(*¿Zòë3U‚çk®ðœ6;žãð¯G°¼M{áìïÒçFÇBB(êk‰ñ%ë\êRO†5aÏ8â³b¬a<L Qœúj®žd‹^ˆ)+½Äl~èù¸<ÓbžêV+–eNœúÕK¨Þ"¬¹lõèkEâ>øÆ ¿Ó|;âi†Ö6òÙJ[&Üü¼LœWƒ2é²."—ç<÷¯k×nluŸ„ l¢{+ˆeÛIóÁɯƒõ¯’9±½QˆVàqÉã?•ci¾×/û5”„"Ÿ˜ޤàó]7†| ºµÏö}Ôè·A µ°a½•y;O :úT+'t3Í–‘¾Pd|nÁàçð­‹H'»con7\ª~\tÎ5Üè^ øecª›MRÆá•Y”»%{×îÕÿŠŒ ©à›¯.ÊãrJ‘mÛ£#šÓV+/„ ÓíÒëÄWqÙ$˹CŸE'>ôÕñ€ôØDv°Ëzàðì¡+‚æa²ò1 lœç’}j%Ñä»Vm1O-w0U,J¸¹dð׎tI,®tØ$a¶XÔadè »Osœ¥}5 h$Õ|Ét+‹Ma#@ïk¹öŸöíÛÖ¿98<ŸZíüñÄþÔSPÐg)µƒè=Ç"¹±8.ux=NŠ8…$}À ·”m®íeIpcp„v«v¹FßL½ò‰à£ƒëÇçŠê|ñ‹Ã4ö·ž±ÕáË>h å€À8Ï\~"°5Ä:?úÝ«HªK£É íÇé_9Z”éË–kS҄ԕѦ’xªX ^Çâ)Ê«'?…E¨Ía%Ê}ªËìòJªWfG$rvšå »†ÙÅżOk2÷RPž{ŠÞ³Ôf’#)¼^¿vç3ÔàòZ˜£KítÙoì½A®<Äû¼ôäU¯ìŸ¹m½äGøüsQµ»…ã[Dœ‚kyJã?Ï•¨êУ©ß.š qçù#ýÎkE _by‘B÷Á6Wgeí¼–R.IàùŽÝë%¾ÏÇ?‡µÆµ˜©a÷ãr{ ÑàcëÒ¨k?4»5û'‡RMYPòóf8Ûè-ùטk|s®Çä,‹¥[·Ë¶ÑpÏ»ûÏÔŸ¦+®ž¦ûê¥Ôõ=WÆþ\AuªêË}™eYÝnG@Ãñüëëÿ„´w¾ øq|ã;ˆ4æV8†ä‚UäêöäòÈŠ>JžzWæ‹£ßë×b &Ò[¹H9-– r>k×´O‚úÅı¯ˆ¯­ì^e?"œËéÓŸAN¶›Vžä´·Gé§áŸ G{,:^²’£r’¤E×nG“¡ô«:m¿ìek ë)zØ8H®6¸ÿ¶sdp=+æ/†~ð§†5åm~]MeË—ýÛD{…S´r3^ãã}ÂšŠ´íq6Ÿ f»Û•ãûÌ9 ê+Ç©‡P~éØªón}ýû~Ü?àŸŸáøãà»ÔtÛôŽÇÄÚ$Q5‹v`bvÅun]šÚF*„±ŽL+û“øoñŸà§ÆÏ„ºOÇß‚ºšë_|h æöÑJ]xQ ˺œI Ç(1ÜBëºÞQÈ œ›Ö‰â;‹ˆÜéš­®HÊ«\à·ûÊÃèkôþ ßÿ ø±ÿøøÑ'Œì-g×~xŒ¤^5𬇎úÙWËöHÑ`0ñõùL–¼ø¬7·…–“[“þ¿àaR-{ñùŸ­?ðWø'·‹|#ã=Cö×ø#Øk6"[ŶºR.–Ç1ø“LÙÿ-bãûFßæD7¨8xæôoØçöŒÑÿjo…_Ô¼¸çëSY\'Èñ°*pxªÏŽv‘Šø¶+Ú3_ñ]êš–©-¸»hì*VܶÕe)Ü¿Bxæ¡?n=!Ú3m~ÀË›)¹üÍ}\*ó#åªa\]´ºT‘÷®KÀWÞ,Õ|§^xîÑìuƒ[¸¤P­æ+’㑃ÁÅuê»kTsµgaØ­¥5›olæ¡[”v*œãŽz(¦«n©’3@_ATásL'<Ô¡ÞiŒŽMÅ\f‚«Ló?Ïù¢LñA *)qôü©ãž)¸¦€¸-PÏnµ7ðÔ5>õ €ŠabËÀ¨Æ{PôR(=Í<©Þ{Sƒ°£¡õ¥zQ`¼SºÒ ŽÂ—9ëPÓE!›yÆ–ŽÇBðöºAΟâM=øô˜´'ÿC¯_¯ øüÍÁ½kP‰w6šÖ—ãÛìÓ£ŸÐS‰¤O\¸ƒË‘£'€qU ‘Wå™%ÅÂò²Àû0ÍU JL—¹  íS–ª W*Æ)Â0zŒU•\Ó˜(À«å/ùýzh«4Ý‹@⊔ íIåÿŸòjy@õ¤ÂÔžYÿ?þº~ÔÕ¦ô«CkF˃Åp¥ƒ¸Òn8Å)bW\ h¨KdN XM ²,GÞ¤É=i¨03šRqÖ³dÛRÆ.FqQ‰A¥@s“EÙ¢cèû¼)ªêOÍEÇrhÏÍZ°_ª«)~ JvƒÖ¬c²sš°¸ïUÇZ(ÅX+‘U”úóR#m`hÊü§¤¨ ÉÍ=j½ùr MC|Õ ¾ ™ãÅC.è¼À§Œö¦Ž˜ô¤uVRdzR&ZœÄ »Ø¼¨Í¦dX³’x##<Ž˜ª÷忼©YØH¶w-Ã4w G´+1Ç*w/­p>6ñîá»K»H¡–ãjºÏbARÈF Œ ò¯“o¼y­xÆ)kz’Ãa©GåÀ‰8ycˆŒ™ûÑ6Ò›» ÃPgÒ|C¬iZ,pø¯É¼þα¸\BKKyhŠ®»‡U|dg£=ëæÿëþ ø³ðm¼ã=Våô+æ[+«—0Ï"C‰"Q"F0ɼ—ž ×¾xËÆw^ð½ÆBö÷z”³[»° Bae;q“†Vû½sÅ~}xïÃÿf}_Äú7‹´÷:.“ Ê,8»µ{+¤,ö·”’ØŸ”Èsž2œ®k ô<{ã×ÁÏ|+ðü'ׯ´[½´ŸEÕ ‰§6wÐlIÓ¢6ÃBÃ~ppA5Çßxþ ðÿTÐ|e¥Øëž1ðøƒTŠßEF­ ©2[º–`ó ܸe*åày{â†toÙçÄ¿<ã ë]sQÔô›»­"Ykxæ·’‹mª†Só¦K!ùÃn‚¾2ñ7ů~Íÿ4ßÚãöN×âKè'µm"âÄ\A®ÛÍ4W˜a±~Ftb8„î~zx—ž ñíi ½Åܶ÷Ï©A£ÛÝì{yJDÏL#>b\xOGÒµ¿üÒî/|9¡jÖú6¹¨yA“LƒY±Å¾ü‡ZO)¢$1 íÀL„Ìù®g|z{ÿ xúóö}¹±:wþÑì-í”yBÇHì²yeAŠAå:ˆœYÓxŸRÒÿ`WÁðjZ\o‹o¼_,6®öˆ[]:îiæ.µLCjù{‘à3U¿l_ÃQ|Hñ—í à™dµÔµ &ÇÃp´€î{› žYBÊû÷\¨u Yø»ðÇDøƒiâÿˆ´×Òü+¡ø6Ã_¼0*"ÚIv²[ܤ€Ö¶ï 4’ÌÎXò1NHÐû›HTøãàÿüð¥Ì^‚ÎÂÎ9î#ˆ´zޤe†HÀòpŒFYÎÀëcÇ “ǾøM¥ßÙiSxâu…Α¦DwAi£ØiñËs¨ÉÀÜ^ÛÍò»q sÀ©Š"[Ÿ¨ý¸~þÍ_ þ.øwÂóϯx‹Æ> ¿]<£·yTœ ‡9pA@„né^ðáŸÆ¿ˆvÚŸíuñ¶ìI¦i—z¾Ÿ¥iöÞT÷÷1Ijîy¦Ý„··„,0y30#h—ìEû-|!øeâ˜< o¦iþ8ø§á{Ý:ÿÆš›¤f±y±è¶Ï(t ¶è²H‹ó6àîJµOö€ñ¤?´ƒÿáŸ>øWQð®·âÿÞÚèö°¶Šk&•"»’éÃ.Õe ÝØø®êÞ6Ö5Åø¾÷>[)u}2Kxœy…íBÉ ŒBDÄÕbòfð]]ȆÐ19gi±Ü Ç5í^ ºÔ|®h^ñv°æÂòÉu;rNÔ™ci<°z1mÛ›kNfG×ùàUȇ5åŸ5™õ]µÛ6ø&»\äÉžÅñR1í]õž¯awòÁ '±è?Ý;™›T£“Šd`»ñÚ¬¬dT½Ä \Ó©ÎUyÐ8æ—jãy`óBW3#šccÉy;–·]ê2Ãp;É‹›E+w¾#ººñÍ׉ü5§G¡à邼²¶$»kÔðC•$ž§¥tÿµ]+á¯Ããkeu'ö}ô‚ÇR»)æJe¸E;‹u1É=&¹ú¾‰ãßkZˆ,Amá°Õ!S”RòùOµøfÀTŽväâ»O‰~,ðFš¾ ðn¯wÖ‡çÚA%’!’âæáÉUù¶” ÎÃŽ5 š$yü^3´Óü5¦I6§©h‚KmÙŽDvìã€V(Õœ÷8àÞÓtß‹w³ü;Ñ./#½Ó®MBÚܺ]Å Ò`ê¬>Ð…ÆÝU8®ÛOñ.›ð·ã¦ã ÛSy£jŽmm¬äg‚R5i=7Ã05ê1øª ö†Ó~(ü†ÛDÖoL÷‘]•2[Íga+Eö{0¤Ä78ÛƒÓ­+Ø«Eñ{Ãw⯈~Gµ_ü> -0ñÝØ]yËb6±ÉÆ\Ê1Êðo‡Zσ$ý™´?…Þ9[k}_Oñ;Ê÷òƒšm…ןmee/Mëæ:ùac©íÚ­¿„õ¿ø³öv¶Ó-ô_ZAw®kvS34z„³N÷&÷M¹Î×·žB†X2<¹È¯ç×ícYñ/Á)Ǫ -ŸW³¶¹–ÝVÚRTyñ`G.T¨=T×7û}Ûx—áT:ö©û;_[fGN¹ÒoìIž;KI¬¤™f¹;vÇqJѱ<qË0TÞø»Âž7ø'ð§Á¼=¡¢ë>.Ô,Ï XÝÇqw-²kˆc‰Z&=`¯“t×VøGá îK˜µ{;ßi"Æ Eºø»ÂñÌΗŒ£å’âß|q9ÀØîࢰº7lìÿl ~x{Á çød.¦ÔÎM6ÆêÂô,xùSí»Fù&Lîà°¡ñG |à ?O—T¾°ñL:UÛÅ ñyÛÄI2ÛAg9-JϽIÚÈW’W"¼~ÿFÓþ"~Ñ? ¾ hfI4{¿iÚjOÁ}}ÞÍŽßÌd¡ ìE}¡ñÊËáõŸÇo 7Š4vÕ.¼9â­nÆ÷Y¹Œ²Û ¯>Ñ"~÷–‰ãoªzùñ¯Ø\Ýø{Oø9ã I/ãð…ä’\,m‘.faåF½ X6Lšé<[w®kþ$j–6Ì Z¥…ös ,û4"i ÚzŒŒšOêÚ¿Žþ Þ|1¼±K¹>2øöË_‚ïÊÝu¢Nò•W"Ê2ƒÕXJëþ&èÖ“øºãâßÂ9Z×NñÝÛO­œŸºû6«æ41ÀWXÆAåKrGJ­äÚ—Šþ"ã¿‹î,¾Çâ?R–ãO{6 4ÓZÂË2¨“*±¸å«ß¼@°xâG‡<'¤j }à—´±×g[[ÜiÚôêXÑÈ;‚Ü[íaœ‚Ç8Ü+Égøum¥þκ‡Æm~{» ­#^¶ˆÜ¡Å jÊ—R¦ óçÌ#îíæ©EŠôφðõ»[Ü]jº¬K7–v4…л2LÃ>[É,‹° “Aw9ÚÅšŽ»ªøwÄ3ištÍcl - 77GÁ?3o“’rsŽ8ô_À^ÇðM¿Ö-íuo økÄZÞ¹u¥Ëˆåšåc‚) > ’?$eÐz×Ì?ašíôÍóYkÍGJ°m3ÌtÄH<Ù0ƒøŒ’0•õGÃk= Ã?°”ךΚd×5­fûCÒµ4rÐXE¨¡–R  ÛÄ’2eNàš¨“#éÏhßð–xâ¿ÄÏ_¾ƒ¢_ø‡NžÑL7÷W:”ÑÛé̸ù¾X„ꛟdÊÛA*O‰| ´ðÆøÿKñ™q¤Úü7ð¦áÿéÖÓ’Ö­Ëks â?ÌfUT¶LE 1l÷ÿ—ÅÍ©|4øQ¡\C ׉¼_áý~+Is$k°¾·º…”T9°Š)†FÔbG"¾wœø_Uý©µŒ´¤ø;Ä¿|C«ëÚeã³ÃuÍÝ´($FW{q¬²H#Î$9vájžÆHåõ½wâ„>5|IñOëÞ!𭮥}§D…’{Ÿ³³Z-£PÇ3 ‘IÇ5³ûBxWÃ?5Mgᇀ&ºk?x_N¶‹P‘ÔIª`FóêÉ++Á Q˜ØÒµþ.|ø§àøÿã?‹îwþÕôá¬Í¨”…MÞ£åÉ{ ¤¨ª¯çÆ›°›ål¸jóߌ ÓüðÓ㦃ٵ(ïl<;f[æÖ¸¹„ÄÄó «—$b5­OүدÂ÷Úþ‘ÅÕÔ¿áƒD{Ï[ÜNÞG“sG§Í+“ƒÜÏž Ÿºs]ìñá½ƺ÷‚>x«KµÓŸÆ~-ø‘àí_MÕЙ­â¼½þÖ¶±ròÑl!3*± ä…Ûʇ~ÌÚ•ñ»â7‚?eýW‹Tð^­âmÄÞ'ñã2ÞhžÞ\[£h&û`ã)cŒŠöŸÚ¯_Ó4ßÙoá¯íðzæïGñö»ñkÆÞ(šfgq“â µKm$ÜÈÙU™lͼP`XP @§C6ýãâß…ž8ñN‰âß|&øolt¯üL»Ö>hñ^BÖ·Öz¶OÛ5H&“­^ÒÀ­›'ÈÓÇŲîÙÁžø…á?_µ>‚"Ÿáv¥ãm#Ã:g‡õˆ “RÖæ`»Ÿc¸k‹ˆwÊØmªW'ç?ÚOàþ“®ü3ðž©à »[½_à‡À;}KR…¤õuÑu7¶Òg²Œ3…y!ûRã ÈÏ·-…#Ý¿cëhßô+}'K‹ÄÞÖµ¿YêVqDnn¡¸–Ýd[»$È]ÐÆ²DsÎü·ðàé ô"{bßüñÁÿØ—Eñ¥©i׊tm{Nñ½Þ¥¦ƒöÆðâÞÚÜCsqrXyÈV)mÏÊÛh"¿¿à¡ëñÂ~7ø£¯Ëm ý·Æ'Óõ¹'Ž&ŠÑõÝ6çƬ䨒æHQäbçþˆ|1û4|ý¦¾/x'á÷ìëã{[_‡¾†Û]¿ðÕ·Éi-¥º£ÚC4™Ü7]FޱHJ0‰Î9¯Œÿà©¿ |CâOÙ‡ã탬øvïÄ>ñ…¦•†otˆÜÍáý{Ã2uI£9Œi·ò ¦dR‰ZCå¾äÕÁ™ùOÿ\ð–…¢~ÔÚ†<'•©kZ„uÍvöæM—¿ÚÙ–-FÕ£JÓÈ `@9nÞyñ^ø7ámOÃäZî¡àÛ=fòí(®Ù5`xZH`r¿}í±ÃDåHÎs·ûq|DðÿÇoÙËEý¬¼|—?ñÿ‰4 SZ†ê14šn»¤4Vw÷*§÷‚ÚO³Àcˆ¦i:il½3àψ?k/xcöpñŽ“âßx§ÃÖ!³¹´ÄV‡QÄóÝ/Ê0²‰$EU2¨÷¬YФyö®u o„ºÏŠXÜøŠ=Q㻺ÀÝ,j(x‚ò;î=æ¾Q—V¸Ðµkí=í¾ÑÑ)³_îNß"Æ„uÎvàg‘žõõ— Á?®ü1à›ÿÁqu­=¤Zí•ô~Tº5ííÀ€Éq‡ÄŒ±?›Ý  È8¯—>øgÀz‡Æ¯üQÔ®`ðÖ•­êzëVà°ûU¹¸ŠÒtùXfs 2‚³Ï¹æºØúÂúf¥­þÆß¤ñëCáë‡Òøtxe&‹ì×ì÷zŽÛ¸T>Q³ù«*Ž3ÉÚÀ|‹{àû(ü ãÿøŠóþ—A±Ñ­â?%ôڬηr³à’ ƒc/#$‘Ïo¨~/x¿ÇÞ%ý”>xÓÆs«ø]Ðm¬5Åò0‰m¥O Ó¦`8Šfs$Oœ#*È?êK¡\øO´’y%Õ`ÕšI!r#„[ùD«±îQˆõÁ5A7sëO‚’…ÿÄÈ˵߂¯ô›>Û /e•P!À%жÆ(¿xõâ¹í'S‡ß´ox£S¶½»øë®ëšÎ» ó¼¯ ßÉQ¹¿âê–~1½’äK:€>r"òc•P}yòûsbž!o YA4^·“MÑ®ˆ&7¸}ÅäýÒ\‡Ü~èçÞ½×ãw5iþ#øÓÆ>‰üS¨Yÿgh°ÛÛÈ«§ØÛXÎéF (’(f—r·,Icè²ë?¼iñ—þ +ûx~[{È䇽;\‘ä^:µÑþxçXð—€µ¬¶‘ ÎÜL¦µÜùló!ïØ®Ñq¹ ¯Ž†nì4ûÍêAªêV²\_A÷ÃF$aÓý…=¹®ûÇ×Z^§ñ—Æzö‘§C§øoNñ6¹a•”ªñÅg¦Îö©åȸÝÁûÄŽ+Ëtm~Ê?´J|ølf‚B8$p?2zñPâî4î}sâk¯ øgư¥»´*Ÿm(ì´Ö2Ž.€É­€9=+—ñtÚøkYºÓ wzìSAlŽ3 ¼0—RTp͸c#Çq]g®|aáß„ÿþ%øé^×^ƒÂ:f•á¨íã½Ö'’6vJyq¤o•ìÄœöðïhøgÁúašöÞýï!ÂòÍ™7G÷PõíI«FW„Ž…7Å&ÿS=­‘7òX\‘åÝy#• ûá˜t8­ï 5ïÇOŒ~ Õô›d¶·¼›Ï··`« ²…Úª[€7…Èã?Ÿ¾øÀÞÓeM*÷[¸·Ó‡Å¥Ôu GRw\Ç¢iI ñ›s,åÕpêòä|húF§‚>!x¿V–M.kY´Í2Ñ-Ø6òóËUƒ 2BÄá³×&Oø#ðÿ^Ô¼+ão‰>>G´°ðu¥ýÞžC¬È·páäa®¬}yÆk´øÙñâ‰~+x§ÅWú4qxÎOyw|«²¤(–à2瀟9 ~óœµÔèZ÷þ |0ø»ðêî {‹«Ø4Í*ÛLžfc%­ÎD©®~rHy¤lOL×øgKžTÕ%×.F§©jͱœÈv¼ò (v'î(ÁžØô«L–ŽÏãü<ð½¬÷_ /µèòè¶pGxð›7oª–š(SËŒIm*¨à0f, 1qz„µ/Ä—>0Õ ³Ìéö9LÄÞI.&FãƒèE{¼' êß|'ðÏÁwë Ü šñ£Œý’Æ;^PS#&i]pA<ƒ“Ø1oê·7Þ “BHoõÛ«:ÿBÓdO–úüÉû¸KLQ²ï˜àô>”ù.O5¥g?Ù»Uñ—ÂÏlJÞ‡Gž{«ßjÊ~À–Ú|qÊT2a¼ç¸,ˆ m$ô zߊþ6ñì©í—㩌2k¥›õx¼¯& ÉDv6ñ©;Y\†Ÿ9$··í/†þ|´Ôt}âtº×‚<3¦nYèºiC‰!±Òµ1|;¤‘#XKªãA"*ån'@RÚ5±!n2kÏƺâxçÄ)áf·mÀ’Ýi±ÝÄäO«k“)œÒ:’ ¼Læ(Ôp[=²Ù\üJðo€µ$Õ¼IàýKZÖ,t4E:`½Úßï–åÜMûTHr2Cr^Çììûøã[_~Øß¶Í®†c×$ºþσ ]‰,vðåŒižª#yyg¸ €›X·_áŒý¦¼L5Ÿ |>[ê£RÒôë#Qj×ç2™.ek;hÂ…Ç1bAQÏÇ?~&|R[»Ù÷â·¬/|áOiR\é~óŠ[K¨nŠ ï3]O!ܸÃ8dùWw8ŸµÏ‰~"ñ­Ôÿµ(4í'Cðþ­-Ž—­ÁöYÞÚÝJI†EÌ‘ ØŠORx8v-LØÙYñ×ÅŸ\^|8¶Óü)kñ7Äþ$ÕTº¸ó¥hO&!n¨w³%µ²¨wù#Á 1_Kø8ü^þðVŸcjºf¹ ÄZ5¼ÎÏ,VÖ8w¼’WÿXÎuÍ|1ûêç„?e/…ºÇÃí*ËWñ6‘ðÖÆÏS¸†é#‚ÎêñwÝI6ÐFù;öí<|Ü«üUñêßáßµïÚ;ã—‡õ" ?A¸Ò<¦M ‰.±ndžçœyHΊ©ž !nCZÄͲØ®ÛáÀïÙ×ÃúµÍÝÅäšÕÿ‰5{»ˆí£"yµ=^êæË!!pû#Q÷Wh>§í¿XÞøŸV±ñ¯ÄhaI/®µ·B©5ªŽþZílõmÙÏJùïàƒ£|4ý’¾|ñ<^jz†ôF¿•“cÏ{ä,³1OS#œ`ðF;W¸xGÃ>/Õ.S[Ô –[;èÌQIæ^[´ŒŠ¶Ìg¹ì2.¥a®O¢µÜWN^ÚéUwy p89àãÕÚ^iéß0Ï]£’a“^q¥ø#ÃZš×[½¸{‡;€Da›°ç‘õ­+K?Â9q5íø‚îÞ'N ã¯ô æhítÝ[J¾„]K:D2@ñǯ­hÿk‰t1TSÔzæ¼ûÁ~ðu¼šäªgžEG<ÉÎ9ö®R²ñ$?ÚW¾!1A¨1Áä °'8ÜGOÆ‹ØqIžËâ/ÙhÚTÚ¤²¦Pary-øU _ÙK¢¤óÜFg¸&b¡òFþG^kÇü%áÍgĺÚõ;òñZ»|¬ ‚Ý1žàw=)ôõeÉù‡àkÌbð¼j:|âK{…Fê8e4Ëo }¶?;M¸Iã'Ðå3é‘Çjb=H²ŽæšÏ?ÌWžÖî2*޽j±ð®°GîØøè!М–ªòÅÃ`° Ü“\ZxcXUê3ìô×ðæºFú ÿpˆâèi7¨àâ¼ôxoYsóÉ,xõqа<9ª¯ÜŸ¨ÿížHQwèvš­»FÔ<ªÎ Gc\,Ú¼ nÇB*È»Ò xƒ½­À1+ʜĘ瞴 nW†_kBûdzÁ-Äñ¶Û=ìÖv‘ç² œxÍ{sû?xCIþϾº¹¿¹‹ýIhËŽ—L.}ë›Öµ±«jžWؤ¾*#AGVcËz‘Y ч23ÉN ~vø“Røßð·ÂZÆ™ñKãÄÞÒÂGæ4v°ŸõÀ²fýøt‡Ö¾"ÔôÿŒ­áÍGÆ^ñ.·á;Y–Q«ÉYé“BYçš`wno‘¼‚ÌsŒƒÈ›Éæ}Óñçö\øeájw_ 4;]FÕ.–´í-æ t±`‰Åµ±y*¾dn ùH*H¯Š¿mo‡?u? hþ"ø¹ã¯ë:Þ±4cDÑôki-.4Ý17¸šÚ[Ç‚öÜïœà¬þø§þ wñ7àεñoàøcàW‚¢D‚/Üèì×ú¤1®Zm&ƒ8ƒ‹™cd ¡‘sð¿ø'߇¾*|C»Òü1â ω“j ªx¯ÅZ•ãéšFçæ2ÜMóOyy31ZÚÆÈ¨¼ù  ,Ù-óöVñ×ìæš.¸º]Ňmt›1·bÚ}ÿˆµßÜ º®í9<¸ 8Ü#–PqÞB<1ûhiž1ÑôØ?fOƒ>$×마µûK_í©õ“£Ùh³ãÈc¸–O™O™» M“Ô,®q_¶Ü¶Wƒ|YáÝ/ö€ø¹ŸÄýNÞkí;Â>´óµ=- }£Q¾3E¸*N  o`îaÿ‚±/ìðÓHý±iÇão>$½–t¹Õ¯?¶µ'Œ4pÃ`²¼’Iqå®Ùo¦NLqìˆ*¡üû~Ä<'ƒ­<ãíB}?P×¼a­Ü\kšæ¯usl¹/§˜­”"‚¡‚"¨8-’~ëñ.Ÿû)þÈú5ö½­ÿÆú²¦—§ËÕŒe†Ë{4DvHÃí|¤.î€U$gí-¡ò½çÅ/ |rø¢ßi1âëPB"ðÿ†tk çÓô4l—–[…‰b’îl€Î[lj6 Îæ<Ç‹þ%~ݺññ…?gÿÉàÏ êw0½ÝÔº|z†§oo;'Ä ‚B;Î\rÙûŸPøÅð¯D¾±·ý¥¼xž±»d¶MG[gÒ¾×(ËùåÎíÉcØè3ÓŽø¿ÿbý“¾øTƒàÿ…æño†ta¾}y|½7Ãò3–½º(÷²3$̤œg €äÕ¶¨ï¡çø)ð–Ïán‘«iß¼KñKÆr<Ò[Ãâ9!û ƒ £Jtû;x-DŠضÇÉ#æÁ$õ´wÄ-oö:Ñm<5ûLëúná«­1[Ëðòù:œËu!‰-^I ;Èä6#ˆ®pA`£5ð§‚cÿ‚ƒøÓã‰ñ›â‡t¿žñeÄpé>ÒÏÔd”·™ÑXHŠò‰F\Ëtð9–Ým¯Ðo~ÂzvãßüLý§¼oñ#Æz¼pÚ¶±%ôÛEË·†UÉT–b`Šøkâ Íâ‡z»gÿ¿€u+{}/º4¬ÂÛM:®«u+ZÃ)¼6ˆñÄ'p ÇèÏø(oÄ›KöoÓ¬-¾5YjVw—Á/†>Z7ö&Ëý›ûUšC*n`RDˆ¹þq_AþÉ¿ fß…>5ñÿí ÉmâÝRÔF‰á6±’öý$µwK›Ëq) Vrå#p0 |Æ¿%?mÿÚ÷âgí³ûJøSàOÿ„|9àNâãM¶²›ìWr^2–êîáø’-ãj…mœ°,åB…+Ÿ­ÿ±—ì9 |?ñ߇~ ø_ÄÚ¿ˆõ­2?ík[‘,š;8ðDö]×ÄfmÏ++B‚•ø½ðûÀŸ´gÇ»ß|#Õu=:óÀ㬶¾y§»ÕµXîäŽH4ûE+º ÆL“Ü8cRÃw×íßì½ðñ´ok¾&ñ¦‡âiÈéÖ6w~#»Dk¶B³]ÝÚHL—³Iò¤M!ùNãö)øÃðOàì3á/ˆ¿ ´MFµÓ¤ðdžt‹æK}I±pF¡-ôÑ# [yeAÃ)$ ¥Ü(™MÅh_´¶ÂþÏ¿ÿiïÙ;à³ñ“⋼'ðSÂz=‚Á¤XØiqëšû´¬~uËI§Ú®œ˜’Òæä«yÄn7öTý“¼7 hׯ¯ˆkgâxöúKÿ|Dñ ­¬ê—×Ó¯’º~‹o¬6ñÃIyò)ØQ R<×ö±øÍàÏ[è?µ'ürM>óÁ~iN…à[LAc©^I©òm®dó.i æË‡pT äýwþ ‹ûLÿÁNtØ/þ ð²ûš¾£g :߉5 `µDÒŠº[›c|1ñ.¡=€¾Ýj𾳫kWW{m/JÒ'ž)§FDÝ5þ¤â(ŠÉ1‚8s·ô7ñ·ì½ÿvøÓ¥øgáž/íkûb_é~n•áE´_ìíÍ }°ÆX¥ŒKœ}¦íÞ}¤ èîý<𶟨Á;¼7¨ÁE?ॾ Ô¾%~Ò>>·HÓtÿÚƒ˜º}ÑhZr±@­ƒspؼ¯Wp±ÿÑþ"¶tuóv®Üù¶œúvÇךç|Ghmõ–‘p Ç8¸ïï]5¹œÀ'•Ž[…#’@㧯¡¬ÏÛ³µùØv–=O¾>½k•3 ï>|$Ó¾3øËþ[Gû6áíæš ™wÆd‰—†^¤m$ü§Ý·`=®O\L¤€3ÙÀ5iÍitTc}OÉÏŠÞO üDÖô‹Eg‚òAE!9Þ€zaX¼ï&¿¡mágÁŠºD«âoišŸÚ¾æ§hþ]ÚŒž#Èû¬=ø¯ƒ>$Á>µkrןõxõ$í²¾"ðû²}Æ' /Ö®˜5g£%Á¦|ðÑßQÒu-%¼ÅógŒ‘Œþ®Zå¸Üx$Ï·½z~™á|(økcãÛ­•Ä2ý¢«ƒ‘qµ‡|©#Ö_ô t}NébR­kpÌÀc˜ä;•‡±W÷´*'šÅezrѸ¸«ÑÚ¼‘¨™ŽÓ÷†:þ5µe`/-¤’K…TR1¹°N{ã‚~‚­yDArY³òàíÓ×ò5NZ îgm€@ÑÁŽ¿ÄúßκÍ/Á(Õ¡ÊÙ¤Q‚KŸ-yï–ÅuÞ ×4ýa¬/4ÈPA$`Ù, Æ;cœŠæ>'ÙjOŒo´ ïe{75ܲß”ùGcÓµgÖÂe-KÃú^… ¨ßÁ$ÉÇ“ žc}2?tP*ÓçVÑmÐ2û¤ù×rñÐñ^?=•º[3FÊÄwCÇâ+Ö~oü!{¦ÜîÛóÅòXnÏ?‘ªqV¸ îp·æ©3µ¥ýËl\ðŒvŒñÛª®‹zšv»eg!Añî;¶œ~uöëL¹‰ÒOß)ó3Ïu¶ ŠíŸìÊ.ÕËß\ÒŽã/üJÒ®4ÞÅ(fo>2:—æ¯ø}¿´|'ªØ2†’4Žæ.9æü1]?Æ{ý;Þ(y§,nsóo‰ ïƒ\ÇÃIcmul$…Ü3Ûê΄¯ê+T¸2` g ®ÏáËê­Ël÷™ˆ³À„†tN»@ûÄu Þ¹‚HÛÊa“å?Q×õ®“Àž0½ðŒ´ÿi£3YK¼ÜCÄRBZ.¥ám3Y…î#ÿC»ÜÀ¡ciçzžTûv¯*Ôt»Ý.c Ôds€ßÂ~†¿K$Ó<ãćT¬Wóe™X$¬­Ô1à>~¦¾eø›£/µDÒïÊ][KædÃuÁÉQÊ*)ÕÖÁ(Ÿ2Z]\Y\%Õ«´r!ʲ’¤~#¾ÆøQûNkz A¥x†vº³Þ7,í¹”w(ç‘ô'òµÎ—m,»ì‰1;wŒÊ•æóŽØª¯FUiF´©½ÐmSÅÞÔ4Öñ­éþ[;·žp—jsÀ–úŠòoü[ÒâÛM²eH c× òJñxcRÔî|ÍݧTÝÑNÆ9==IÖèì5«¥²Õ|Af¯œùHD’ðqɇ¨â¸^w±Ûõ—%dsÚ‡µË<\¥¹ª§S‚ëÄš¨*iòBÑFTãÙûÔø¤ªy~"Ð.¢Áÿ[m/œ€ž§iÃù×Ckia#»‹ƒ‚<³3×] é6£©jqéʧƒpÛIÏnI$þ roErì‘ÎǪøfldë­føÀŠët=ÿÚÏzúáïŽn.l‡„üa³Q³u?¼„ªøã•ò?ˆ¾*øR×6ÖQ[ëÈ\«Bç® zzò+À¼Aãi.uºÑ,!Ñw.,¤)ôÝŒ[Ç)«ì ¥Ñÿx DѶ_GMc?1ʬ7¦yáî“á›GDWÚÛÅå„lÆ<xÇoʼ+á—ÆŸói£FƒMM^ÍÔ£5êmHÀ>aÇò&½¾_xŠïN†óÇ^°Éƒc)Fºuàkž¦ÃshT¹ûcÿ±ÿ‚¯jß°&¿¨|1øö¯âž.¸fñ‚!ôiîï5+(ÆIRNûÛe“™âee›û^Õt_xëÁÖ^ñ úwü#ã=0ÛhzµÆË½;_Ò/£'û>ñþd“̈Ÿ&^Dƒïnü¾-lbŠs¬3Á?é#Œ¯Þù—¯µ~µÿÁ)ý‹~ø“öeø¯¤]üDø5¯[Ëqc i׉© j¹Þ“iRÌè“÷áåH‘r]“ÀûfªRvšüIwƒæŽÇÚÞ(ð§ÿàŒŸµˆÓµµWàGe’ K—o:EŠ>~Ï9o™5=)3Æâ×–CÍGG û¬iÞñφážÂ{}gDÕ Ya¸–X.­g^AqÅ~4þÝðWO‚Ÿ¶_ü"÷áެ®4š¦•mw¦]Y<öz´V—HcÖmî¢S W 2¼M ‘[|X‘6»|ÙÿÙý½,þ^ÛüøµrÃÀ¥Ò¢\£ F‡w9Ú.p0ÂÊg N£ˆ¼Â+èPU9´øˆSM¶Üσž&ñ—Âß[|:‡—­èû®¼ªHJý¶Å3i³1'|°®B©?¼‡ ¿26?S<3ã;/ønßÅr¼BrRh_ïÁ:ýøÛÝOOQƒ_œ?¾AãÉ joä\£GqeynÙ{[¨ÈhnauèU€`Ýã¡­/€¿5Û^åµûaý¿£¬vþ,Ò!7P|NÐt+&€2Lä ú,»org‹™á—4Ñ{… ÛçïTæ[Æ÷7.±GÜÌçj€;’käÿŒ´_tïßxmüC4}¬±\ÙM¦Ø¼åãxÕ”™*pK+8 ƒ^'âïx‹ö§ñ¶—à¯ü@µÐãE[›©5›y´Ø¤û;®(0²³¾ ¯Ž8¯së±áÇ'+Hýðf¯£|CÒ¯µ ]Åxºl¯ÄhàK±œe8+Ó‚x8¯štOŽV·üIáÝ?F¼C¥ÝÂ.bm¸Jƒ% ’7‚A*3•9kågÀ©ð~÷Æ?þ$ø†ÚúÛAŽïW’îĬ·@BŒB훈\àí*ÇÛ9®/àŸíàŸÙâÏJý¢/u&ñdž~"¿Ú¯%ÐíþÍ‘# ó[dŒ£Þ[€½«'YÞæÿWŠ?bÒÝ™ÀÀ`Qš´–Çè:WϾ"ý¢¼?£hþÔf‰ã—ÄéútvŸégì× U&”˜A«^QñÛ^Öõ=ÁÐZO®i‘ÆñÛÎÆ++•á²eÆP²®…Z,äö¾§¶µ³/©¬ëù-ì­e¼¼`‘À…Üõ!TdñÔ×Ìþý§¼Xÿ,þ |_ð£áùïÍżWqH.ÃËn>vPCÄ¿ÄÈIÅpº_>0x«Æ~*ñÀèô}ték%šM¨#µ¨{a“9ÚÊOÝuÈçx¨ž!-!IÜúBïâGƒl 2ßß%¼w-ˆLÿ¸22J+à¸õ*Új—š~‹hÚŽ£:Ç íùÈ;º~}«ð‡Æžø¿ûhüLðõ·Æÿè¾Õµg›ûVŽG”4èâm‘–Ú$IC8ù¹ÆÑ{á§Œ¿ioi¾#°ðÖ¯©xƒEø{©,—í}Š+kx>ë“3yвa”"ï àm®gŒ}Ž·‡O©û££Ý¶¯d5‚H#bv0 ܸúÕùÇ8¯ž¾þÑÞÕ<6º·Œ"Ô쮾ÉÓÁ{hñÌeœábUT‰ÚTc$מKûjèSxîçÁ6z ­ÂÞÉkkk{r–×7k Œì Ã~8®…Š…ŽYaåsëIµ x'6ò8¸%sÈÝÓ#Þ¬¬ŒFkâí ö€ðÿ‹þ;Á%Ïöv™áËŒW³ß¢¥Ä­(SlÛ$@d$wc¥}O}¬iÞµ‡Oñ˜½ÍÃAd"¥óA£”ù—¡$iñb•'°4òÌx5Èé—×w<—ù—w-œl¬Ñ¦z°Áõ&ºÚ}ÿ¿LqØ÷­S¹Ùy RoZ§¼õ£{S(¹½iÀ皬§#5 b!ðiÊÆ)ˆç5/˜{ÿŸÒ€òžirµ\°šQÏJ$ŒŒf¸ŠÚKk¿ <]¢Bp÷z=â!ŸË$~¢»~ØÇ4©f5—M~—1I ú:‘Q£=N_ÁÚœzÇ€´jÝæk(>¡£·ô¯ýžïd¾øáfùagö_¡¶vÿe¯^CƒNÚŽZ2Z°‡f«ÔÀäqTÈœ2ç4ŽÀŽ*¹<ži~èæ¦y‹þýTy‹þýULM;­W¥É(RBÑ»#ƒŠˆ’F3MfÅK@XPväœÓŽ•|/) ÀÍH KÚ£gÇÀÍš| MN_½QÇì­R@N\ ”K‘‘U¥(b)8h;zÓãç­UzÿŸÒ¤Î9¨µ€²Îz“Q+e²jÄv§ÆäE;\ qÉh©ª»¹$ B0qB‹/™Hr2´ð[ڨ¥Šåi´.bÖYyÆ*@sÍTó;ŸåOVÈȤQx84õ89ªJýLŠº’vj9qÒ³=jxœúÒåY"ªê«gjÒ¹ÀéŸLвà†T‚ö&µw#‚<ä–¬ŽxçQÒ,ð¯ƒþÞ< u$ïzCœ»¿xT²É€>àÏsÜüöµðg‰5Ëà?‰6É ]è·V/ys†ŠÖ0ÂX5ØM Ûw"çrök6neð燼Csá-gÀŸ‡þÕá¹RÅ5M!nIº³ŒÆës,23ûÁ¾0îŠøSö€ø©ãïøN¯>|OŠK]{ÁöpZÛÛkq°³ñŽŸ(x ’¡(ZXÂ!SÏœ¥¶ãå>»áí[ðOÄïY|RÔç°Ð>.BöSM ï´·¾ƒ*ÒÁqÊ·{w?»¶’éÛ͈2*Û¤dn„ˆfS°õÝPIøÇð¾ëÄ7¯<5¬êm67Òac¸y-<˜ï‘ÙA{pÁÕ3wgª~¿í!®øöcø¦ Þ!_ i^6ñBÚytÉncl>Ñ«¾ø¨c$ÏÀÀs_hž/¿øƒáŸ„ú/‚ã^.û<ŸîíRݧé6×m¦ß@m×¥Ô³L¤2;FÙUQ…þ‹~üÔ¬üã?~Ë¿ßÃ?<ãkÛ-M¹Ò¢½ÒR׉l¨Ò\I|¦¼†góšCE‚í!y¥«Ùñ^ ðÿì›ûtê^Òµ;Ûø'[¸Ñ.oX6ÂÊ ‹‰âák½>XA”sÜ5{ŸìÏ­xö«ý uïŒþð½×Â[-kÃÚ‡ŽnÂÊ-GU²7…óÞË)ešU_*6,J¨wb6¯æÆÚRø«ûXü]øóðJÇþË}[ÄÖº•ËC¿IFŸM·Òî¯÷JÈÒ¼Ó!„sæyŠ¥ #î_|:ø—ðwÃ~ýš¼mâV‡Ç>5Óψt» ±¼¾Ó,¯sÃSÞ¡ZY"‰[qábf¡nvÔý#ðŽîþü+øSâ< y­|BøÏm?õÅߪßY„Š ¦DÛgieˆ\Ìåxg€Ö5oü?ý”>øËâ§Æ‹ñã/ŠŽ©}¦jh!lº‹Ÿ2=6Ú"C%¬e*ÄuÉ.Õåß­<}û)ü=ñÂï„´šƒôhltááh¯©â«©bx÷Í©îb›ä ²Úa2®7lNSÁËðKNø‹ñ6o騚¯ƒ¼9¢Þ\j.‚ëQºñV¬—o#j·;Mkk©GfnWÉòÐ-£îÏ ý©.h]øÂ>?Òô¿xCìðxtÉ£Þ4ó]i‘ZµðKë‘A]Cå”%IÆöŠÏý‰|XšoÅ ÝwƽšÞ*]»}¨¤ãLX•Ûí2&EpFù·9ñ6¡áícöø‰uá[k«ý{DÖõ&Ð4û뛲UçÓíd½¹kFbHʲ$8F9Î}?ðÛß ü'û&Á⟉)w¦xßâU´1èöÎ’}’ [+…[W‚b¸`ðd,@vå@\b\îmÉ¡÷ìßâе ·ˆ®ü%íΉl`k)íÄ‚]>9·"ÚG™7²20{gÃÏŒ'Å~$Ñõ/Þµž¹©_XÛéÖâ0¸œBí©Ìí’JE¾@ßÜãð?iß|"ð߈¼ñ @›\^Ôîon$»o™õ b‚Òs*å¥i€0°Q€Š‹Àø‡À-ÅÖô‹9¬¢“Æö÷ž#Õ/. å"EµŠ;Án6¼H±ãïuãQ&O²gïö—ûHøJïÂw>-Ð/¿°ì…­æŸ Þ¶ÖY´ç'ùRE ©'æãµðãâN—i¡iú¡w} úZE}s£ˆ½wYOÊÉ <ˆ¯Å¯Ú↱áa¦x]Óãh`½ÓŸ ?h ø:FoËäÅck Iu+y9hn[`¹Aã$]‡ˆ¿oKÍRÇÆšv‡xn¬îÂM:òBd\G; _,px 8÷¬ž%w†}ÞürТ½»—Ež 9,2‡ùžF@~î9ùò£GÂt}câ'ü#rÎemkþŠádÜ?BpE0‹ûSêºö¯¢xid’-"¤¸†vYVéå%¥€îd#'Ó8é^½áÚnÃá·ŒLJ™–{x. ¾BÍ.ëù"R¬íчl7ÞÅ%MØTgôG¦~Ð^Oˆú;.8 iŒãÊÊ亹fäJ¯ûFü?Õ-®u9Vk{ª2¼OÈÜÀ_ήûOiÚýCÆþ#³{û[ÛÛ­'K¶ÛåË̹ÔrÅ¢d1Å»«zšä5ÏÚÏÆ þ!h¾ÔícŠM{ymæýür›ØÎû†E`H Ê@a³Wõ¥Ü>ªIþø—§[ü.´ø• ltø’c;B|òñ!c¼ïÎGQ^ â¯Ú7OñdÚS[éï¬&½¦Çs6—"›h¤³‚@P Ü‹ò¯ÄïþÔ—ÞðEî“á‹ ³êšcM>Ådn1(îm„ù²2Ý=kÌô/ÚzËÂzÞ¶¾3»S}â%´W¹g-ö$•mÈ)`½0A¨úÛnȹýø³âEŸ‡|&áËH4ønbò$·$NÇzB¡:±x'os_)ê.¾hþ&ñ†^Ö Ò[MMBY´¦Ü—vѰ!n,ËŒ6pM|ãÛbc¥ü@¼‚ÎOézlV†}íolîLòg B'šÅø¡ñSÆ8>,øS©2Øk—>šÛZ†%I iL /ÌÀ´ÊÎ<ûQíÛveª l~¹ü ø™/Ä=kPñ¤ÐÚØø±e²½¹WHæŠòÅAŽw\ü¥ÕŠ’1ƒÅz(Ð|Iħÿ„.Ê©WEŽMf™466ó1i!l€–sÇ5ømðl‰Ÿô?‡Ÿí”üIð÷‘¦Xy2ó®¡R×+8fòä¶¹Dyqõ }£ð/öÚñ„u-^ýaƒíº-½ômc$ëÚ¥ÌD™²$†)ò%ÈÈ®•QXÍÒÔû‡ÇÓx_ñÿ„¾.è:z5­‡ˆ%]b8˜Bö718ØY£`;v¯œ“ào@ò.õÝSÁÚ&µeo[›-K@¸qwªÛº|Èš”#f'*€¶I® ö«ÜÚeÜ^¶‹EÕt=bâöÚMVA-Ë[_âxm®ÐC¿î›BŸ˜’xŠß&ñ/Ä­3ƺ}ûiºö³¦Ü]Åo$&Êm2îÚ4‰–CÇ›±äBqÐW5Z«chÓv:ŸˆÞ¹ø_ãè^ÔK±‹Æ ¦éwˆ’{›šÙ¦ŠênFèÃn…%?y[©Áϼ|%ý§4|+ÒõOé¶Vþ!øYá[ÿYèÂVþßí—Q“w ïÄÂú(ÑþP2B¼¯>,üñ¯Æ+Ëëð³ëïà?ÃÚ¼þJ¢]O¥K=Äí\0óÜ3Oa_0|8Ôôèþ6ñEåÜñ?OÕÈ“sµìÇ6êÙ<ý™ÔHá¸+XûDh ÏvƒÄþ8ø5ðŸÃwþÜhÿ õíkX±ÓõH‹Ïa¦jÏ-•ݯÌüz­Á’¸pWfpN|‡âö™®|aøm¤xÃáÔQkúg‚´ðåþ¹¨Ëä=ëxc˸˜Ø¼y-ñ*Ãvç_AøSâö¥«þÏzÇÁe¯ˆµïk«âؼAup"m´é´Ö-(Ü·YÔ®ã“ÊŠùG]h>jµ;3m©xVÖ,uq—±_ZTërå2Aç?Zõ 'Løm ~Ï÷Zï…dÔmo´Ý:æM:Þ,;Åý‡d·°LY¤Ú<¨éœ×kâk¿xGÁÿc:¥Æms(Ïk5Îôi”Œn‰w( üî+†'ñe†™.™ol±BÖzÆ™æJ§k­é<ƒÚZ4R§×Žõ‹#Ô5ï ^èº(ømá‹™fð®‰áó£qÊIþ©«Ú §ó¸ 7¿ÜÀ zt¯VÑþ!k>)øa¤üø9 Ü×úµŸ„õËÃåyñjZͱŽêke™ðªQ­bIÅeFyòý;B»OÙb÷Ç¿a´‹Ãú‰ô«Kû¶-!Õ/Y ¤-|¨Š=¾ccX=¯¡?eÛ?áWì­{ãxôâÿB¶Yðk\Ç·²×®7Éw #miIÞ6‡{Ÿ•vü .nÃz#ó§ö¦¼ð®ñÇÇ7>3]ø~âúK‹”‘,O´ÜC0 mšÞìËáÀïšûWöBøG |Dø¤^üHÔmâðo…õ]O^šÖîãÊ…õ3kqoääa`·•Ém¹n›¹¯Ík¾)“ãWd×Ä· 20Ý¿¼Qu3`ñ™'™Ý³Ç8í_S|V¶ÕuøÁŸ5«x7âþ‘z¶–^"Š{ûB–‘–È{ºõm‹µ˜ãª‰5‘÷?„¼¨xáN¾>$Û^xwâ$6ë©i2Ép"–ÇOKoß•™7;~è• ¡eeÁ-_ þÉzëè?~øŽF¶’ÏÃw7¾!Ô-µHÚ{K¹¢I¦Kg˜y—S¼¯< ÛŠ\F²ýý´<_«|aøâ/èöqkMc-ž„5›M‘=ØÐÌP5¤19¸ÔZ;fL€ÛdÉí_ŸøSâ}ã>§à™£’ãRðÕ¢jw6ªØÆ£ÃäB¬„Œ¤Îs’¯sC3G¬x§Ç/ñGþ q®G ¼í¯ê^'KM*èn¼:‡ˆõ#tþs1ËKfY·w«dæ¾`ð$šÏ4‰š®³§£‡š'‡µk; Irfû:&×áâ–8B0\`·çÛk_è¾Ö<)âÛR²»ø%ÕÖ”¸'šÂ×fI>OÏ‘¼¼tô'>¯û é>ý£?j¯‚~.Ôt_x¯MÄñÀ ΰh–ÑÛ¶Á¿cy!äPe-ëJ÷e-ôwìåàψÞÑŸÇ^³¶þÜ¿ñ¶¥§EgaRÒg–ÆÞ0q˜$lrÊþçÿiïˆ_¿ážôß‚~ ð¸Ò5Ox†Óey®hÓJVßÈ ¸\†ÞåG>_¨é×|&ñÚøßQñÏ‚~Ýêð…xo^Õ#xCAÒt} ›XÕ4ÍDÁkj¡H$ dšé×NTå@Û÷oÃêÿ±—Ä‚¿õË…E†ãÄö×ZQ6ñC©xæÒ´«aÛ#šñ$2H>XâefÎwWÈ¿ õøçþ Ûo¯xBÂ;3ð¯Åº„Ö>m¸¶·Ñ´ùòˆK½ä“MßÌ-»;@Çèígà¿‚)ûPEñgTÑuÝKÄÿl¼¾)ØŸj´ÖY»IšYÄ`——GY¤»B"FÈ1œ0Ö ÌʦÇÛöÞ Òÿb½Cá§ìÿðBµïx©ìôß\‰ °hDJnµØeË¢ÚÛ§˜±Ú ¬nÈ 'ÎÇë¿Ûxׯÿ³'ÄO…ß5§|.³‡TðMŽœò}¦¡™Å”^\än¢Vc‚yÇ*xKNÖîüSgâ/$Á|–‘,ѳD´ù!•´›¼îH(¿£ÿø.OÃý½þ$üø1ðfÛìö³¬ëWtÒâÖæ1e¦]]I3—Æm‘aW ­/–æ¿›ß ürñGÄ¿ŽÚ¿íñ‹Ä©â]Vx<â/[ˆÖ K´±ÛÞi°ùCgÆF'¹®jѶ§L%ty‡…9MoÄwrê6×à‚ÙMÂ[ÙˆÌ?»$OîÉnzàÕΘÏÈöi>ñ7ÉVþ·Ôoôi|-y©Zê7*Ëj~QödI[‡ s4¬ˆÍÃÇàíUtÛË Å ¹µ»'O0ešÒ=ªYÈÏÞ~=«ö¯ã犼ƒ|ñ³Ã^ÿ„Cá_‰¡©^ƒhšêò6·–.¬±nbNV¿&mm­µ¯ëßµ[v[OÆÏd’åÚE5ì²j³ÁŒaŒ ¸ `QQ(èw>ÌøSà¿ x"?‰?¥š[ˆ´ bðÿ†ãd‰<ÏÜÛ@chA°˜^á#‰[å.rz çµ=CO?µ¿ÙwÀz…çˆõ8ü_{ux‘Ü #‚ÒÊÐËywÏÌ<íD‘˜ó’*ýͯſ ü ðïˆîgÑt‰¾!xÛí°Z¿Ë#]Þ>©O9<ˆãØeVF+ƒŠân–ëÅsxþÞžžÔþ%ÍŽtšÇB¼‘µ/2F|“M"ÁpW*J' ÕrÙâžðëxƒàŽ5‹žâÖ; SR“V{yŒ¬ìàà!ë´óŽkÓ?g¿x›ö`Õäø—àss êš·‡5-KH½Òöi sù“MÆÀ¯&º¿Ý]x'ö^Ó´ºéÚŽïõ”Ö% ·°Å [Hˆ`Ì"HP³ª`'ŠðO ø—]»ÓäðýƵ<0iÚ^o©2‚Ûèfè’’ Dó6û‰ÞQód T t{f±à;Ÿ†_²çŽ<84‹ ;/Ã(ì·o‹«-a¥Xl⋳ÜFHn@sœæ¼Ú'Ñüq§üpø…}¥Ã£>‰àK[@´GÙ+ æ&ÛÆ$2"nLŒHçïÿ¶®»ðö_ˆQi^%ÒÖòê-Ö&6„ÄfÔ.î]¤¹‘7m‘¥€ …E' Šò¿éúg‚uwÓüWÛ:¦§ßÚBÊckk ¨lPËò´L$ÚØ»ƒƒš¤—SE±ÖÂãOkþñÙÔ!”èÚ_ˆä·¹!†ýeK[“ÈG;NÅ|åâíNפðÍ´šÆòᢵ»¦,².$‘ã÷jOÏöGÄÉ|OañCÄ^4ø©sªë¼×ïà-°.nm˜D°Å"¶ iùXv¦ÕÚ¼dæ¼»[ð¯„ücñ»Ã¾ørŽú^œ‘‚u)#Ȩn.cãqŒŽ ®@ëQ"›5>0¯‡>x7À+ák·Ôlÿá^jëye<ˆ¾f¹5èfl.#Ž_,MÁÚH¼Ûà¯ã‡Þ>ÑiþÐÿ~ëÚŒ:ާðëáííæ¤¶Å$zˆ\½¥­¬³¨Ã: ŽÞNC2µâ¿²å–—ð‹[ð§‰>&xŠ{lí¥Öõ_Ý•žÓDûTŒ.§ŽÝ› ux)i‘Lß7¯ô¯ì¥a¢ü<ý¨¾%þÔ~-² ø_U¼»¾§î/4­0D¢Òä[°ö»»TFòä‘¥Û€Á³Ø¶Ðçê|gâ‰ÿ|ðËÂzφ5 «;G{¦[›5Ä+{c:[ß%ªüÅÊÄ|¼–9'5ÔxOHÑfÿˆš»øOÃSCâ ÎÓ´ûæ]Æ—VË!XÈ-1Š™\1'u¯ñ?ÄkÏŒ¿޳¨Åý x3L¾“ÃÚ}°¶ñyÿh%úqrvÈ%KÆ1Ç_s²Ðõ/€^8‹Ä—ÚÅ”×réj:½õʶ3Üe­•$l¼¥ÛÇÀ¬¤õ.ÚIðƒâGìëðwÂÚˆf¶µ–ïCaq šyþÑóܵÀ9+<’¸,¹cžwi>üDпgíCRÖ~êp\xÛ]Óѵ[¨÷>’×xF1Æ2¥¢‹ èÜÀëÃÏüBñ-ïÆÚ+RMkÃ>Ö¬í—JŽt‹RÔÍùF#ËM¸!³aÇÍœû͆³ðïâΩwkû/øRÏáÃï$ØAgxRþþûQ†FWjÁŸd.ÔŒ±ß¸´ fá«%ì}û3ü ²ðÏ…?áøuÔüo¬[é復£:‹tµùáidËÜ)&]¼žpNy7ûNxö€øýðƒãÇÇÿÛÁsàÈ<%v×7ÂiN¿p‘%ÅÞžŠòÅö„T À‰Æ7{k­<ü8²°ÿ…?àñ¬ø«_¶—û.$$¶×PŒK% ,¼°äd†!h?<'ðÅï¦Áàß|HÑo/´}9ÚMBäÎ.äIçÈiFèpã…*mÃ5Ûµ4¯x\×õýâÃËßü5Õ­í£[6Û|º…¶l‹(Ö˜ ²È]g’M¤ U$šôÚªÎûãŸì髸¿Æú·áÛ ™|%á¿ Ç¨ÎæÙ&Ö5-'š8 ÀG2†ü zu¯mðÏ„|OñS‚ÓÅšm”‚åd·Ðlîš8åšÝ*1îs]‡Æÿ…ž1:φzíô÷'ø‹ìV©pgŠÞ.Ò{â±Î%üqÚ¶‰Ï"÷ü* [álöþ½ø‡u¯‹-0Çt×ÑC<ž]»ˆUAP»_¡É"¾ˆðÇ„üa}àmõµ×²·²S4k$K–;O©í^CãoÙã]ðwÄíWÄWZk¾‹´Ïoo­,âyq“'tòöé]¦£'‹~ø›ÂþŽêKû šU“ïF€ew˜ÏCŽqDŒämkrêvyñÆî[‰tæÁv6‰R<4…G²òµÏŽ<)ãÛÈ?±¤EÓÞÍuå,¬Ê>ò/Üðry«>=ø£ÚhúšjÑÂt‹hãk‰bcæùsUs66Œtë_øÂþ"ð‡Žîüá_-ö®Oý¢÷–ò:>Ôݪ†;e!rwŽ$`š9‰öhú{ÆßïôÈHÖ­Ò)™D逆º68î8çµz3j«ãk+áÎé“I„E>¹zˆwþ÷îDO]Ò tZøƒÇŸ´‹Ë[ǺcË­ë‰ ŒÙ§62D¤F&…Øìqêx¬Ï ~Õ·¶_ #¼{‹-e’8îu㧆M—­,$n„œ?´›¸¹-±ú ã?Ûx7ÅZφì-b:m¾ŸX¬xÁ‰ÂÓ=ù9õ®#À¾$ðŽ·sý4Q<¦#˜üË=×üµH „àû×çÏŒ|Uñ7⟅ü+ñúîÖûÂ>¹½½•Þc•šMȳÉR<¼€T¹Î;Tžø¹7¯èÞÖ®,m¤Ô{ˆÊ7––Hß34ŽNÄ߀T‘“ž;P·‹>Òø/âk»OáÜÇ ®qu§˜¤`…Kœ¯ÿwîס|;Òäð$—Ú$sÓõMÞ9Õ@FÚ½}9Æ´®´¿êr‹›¥.Ëб géWãº{dò,f\(áH  Û¿ØL¶3Z-¤Ì~ì«Ë©®šâÓ—˜î`“œâ0?•r¾!ñ•qc%§ˆ4é\ƒ±|ÌŸU#šó /âÏ….Ä1ÊÆÁò6Ü£+Çì3Ö€=}#J±º2x'R62ÿ %wÛ»c¦Öçü*K+ÛKù°~6ÙCjòÜ/÷Sznžp¼b­¬:'Œ±¯hú‚YÝÛa•¡Lý@q޵Íkž$ð£ð ‚âöY’áÙøê:&HíœÕršÅ_é—†µ™.~›Sie"7ØeùVBÝÕú¯ãÅcø›âýþ—¬ÇÄ )ã’5ß¶–7L÷Î1ú×–éÚ¦¹­è Ö%ÒæbŒöÌÞaÚV$åÝjî%x¸§Ê‹å=WÇ?4FÔ­F±m©iºeÓ¤BÑœ\§d <ø‘\oÄŽŸ“N¸ÖtO ÿhëV¡ÊÒÚÉšw˜‘å¡iQé—Ç:VE“xÃãb\è2k3Xøjm’ýº7/[”Xa–Ç3çhϵy‡Ä­söWý—þ&høkâeÝþ½Ë5ÍðŸ_`û6§”˜gà30TRqŠWè\O ñ†u]_Ú{â'ƒÚæIv¡m­ˆÀùhYE”rã|¨υŸ–¼ÏÇß>-þÚ_eøÁñoN_‡¿µ…ŠÇῃng2ê:ÜÞb´ºŽ¡ÚlH¢Êíˆn|Ú=ÞßÄ?lÛx£Ä^[ øRâ{Ï èZŒ"ÆÕµ/,Âoµ( y.a™PX«Ú_~Ëz>¿«ë_hOkþ5Õå¶xá†ãQ1X[Fßzùh» ã5%sž1â/€·?íƒ%µ•ÿŒ¡ño‚4“ ÂÇ/ü#ºzZÄÀ5½º$R f1)ivAà úvçö{ñOÂ:kÿ€¾ð§€u®"¼•?µ/*8×Ê3µíäl.$1Šê«Àé^â_Šßÿdyuïø€.™ [3é^ç{€¢Y¦wŽ,i!~ƒå8Å|“ xoö«ÿ‚­|qÔô_øÒÚ? è–Íuw&¥o½·‘ aT¼œœÌê/ÞóAjgñGöá:|7_®tOxŸLÖo#±·ñ¬e‹¹ULvRI±e»’BWÍì`­»¾†ñ?‡|yãÏÙu>]üƒDð5ýÔ–v~²™ãYV&i¯'DkÛ»™æ\Èû‘\œ6s¸ü5ûGøÛBðíðÁÞ×â7‰|y1“F²UhMÙýݺ[΀+¬P¿˜YPEmf`XWí?Ãïƒÿ´ÿÆGá?ÚŸÇ:_Ãý"+S5·„þ_4×w–¤2Í%ö¹4i:ò³%šÛ¼l§÷¬¤Š9Ÿ‹¾ ~Ì? åðÔìß<_¨^Áqse«O&­n¶–ÅÇÙ£C$‘áR%åîlàƒì+ñ—ö­Ô<)yãXþø¦ãIkޝNÒâµ´— n#Žîq2<ŠAo2=㸿[g¯ÙŸöOø]ðïÄð—†íF¡,“®¯¨ÆßiÔn£Éò¥žþWi؈̙Ï<šù³ÄúÍ×À»¿é>5•¼[ðÂïT·6v§—¥ìàÁ%Äòí b²ãM¬ÙRÛ‚å´ŽÆM&|qðçàí¡«|>¶Óh]sÃ^³d“í7|ž$ñ\¶÷n/&[™…”ÒûÃöyÀ ½ÏáWü·à¥÷íþ&üP¾ñWuO›{¿ Ëk$vÓ[áâ¹ha·†Øº¾(ü¦ ÊÛ@ý¾µÐ¾)|uÓ~Û?±/Â}?Æ> ý±¿dýcÄ^.Öõû»ùõ½yzšÇs?Ÿö ÖGHÖÞa ²E#ªÀ8VȤ“eÅѧ‰à ÿ²—‚>é¾Õ>%ø^ÃUÑôYlàˆK¾šu€+C§Y®ÏÂy¤nQ’zþo>üyøiû9ø¦÷ãÏŠ¯õK¯êj¶™m “X2ÈLŽÏu"3œ`)\€êk‘øóâþÜ_t/Á2¿fÙ~økÂúeÍÕÆ©¼:yÔ5+Ù#O(ÜP0XÌ6ëö‘œ†jý³ø=ÿ:ýš~]ÂÆÿ‚ üH¶×àÑü'&­¬i—r.›á&ýþHažRÉuyääo*$a¸D¿*ªñŸÄ¯ø,ìÕ,qé_³.»â½Åw—ÐÍym¥ØÛÃ}6‹æÜE\#Ç?b@¸’ `ãçþ …©iÞðìÛû þÏWrx¯B¶– *ÂæÝ5–) ÂßOŒ’Í{ul™.(brÌϸ’Jþü@ðßü3àÊþÇŸ°Âû/Ù÷à<,^$ø›¦éqmø‘ì›mÌ%¬h ku'í3³ã.4`E~| ý“ÿgø$çìÁ¿ 4ýwX×>'[I{¬iív.5‹‹{ToôÛ‰#±ÚÚ!p!1À aPîÁMŠÉŒÿ‚Tx÷SøAãÏÛ§þ §â+ÿxïž¹×­¼Ú§öe¦™i ÐcR´Ä6é.ÀE•„kæ0&C1—ï¿€ºu÷Ÿø%G‡~~Â^"oi:âÇ{­üOÒmÖ c^Ô¥ùt½ÖRÒN­9[OµM˜¢Œâi{Åâ²wÅÿ~Ðÿ>_~Ðך/ìÓáÍ Tø¡âµ)üT7LZn±â˜-ÙØÜiÖ!Dq HÜå•c‹î ¾7ÁY|e{ûHx¦ÖëàïÀˆ¯6ø&H­Öø‡LE1N¥PlþÓ–÷3¡e·9†%vP‡tsÿþ6\~ÇžÖ¿dø'‹Ÿ‰?#+7Äj—¨Ã·ø¿Ž­îl<}moæ[8RcRQƒ¼G89êA-Íj+#æoˆº—ÄËEmW½wÓaVØyp=Ѝÿg5ä6W³ØÎ.-η¥~•êŸtKëF×¼+uNÕ‰I1œv ÏäE|SâÏ éCT¼ƒri·)!Û¹TaŽ{`zŒq[B¯2´‰wZ£Ø¾þÒÐh0Å‹=”€+#®õ^įñ/Ðd{WÔPiúŒ!þÜðlv·– öE&^<ç®>ïNø¯ÊµÒî$ÿ‡œÓŽõÕxcZÖ¼'t5?jØÝ÷àr‡‡ìr+€§=bìtC%º?A55ƒC‹Ì׿|?ußj üÿúõæ¾ ø¿à»u´ÒÕu‰Pá@‰•?Ûã?†kçMŸ~!j~uµµÖ£$‡qšváK÷.ç=ñÎ;W±i¿³Î¯`ûüavÖi€Y-À‘‰ÿe¾éüE`°té«Í›ªòžˆó­{⇉u ¶éeìb$ü°?#íYƒ¼qã‹Ï>ÞÖ}E›þZ6vóß{|¸ükéí¿ü34k¥é‚êUÎ&¼%ØãŽ@}€¯K¶ñÓÄQ‰Š0ØŠÀì1X!ðް˥ʹO2C¾2ÜaÈÇ|Ö¹ºšu40ê]0cÚrO}ÊF+ÂüãOÚZ ,Îó³ió$mYa)黈ô8¯­t¯ƒW§€-üI˜d¸yI4rØPz1FŽÙÅZÉÙ»“hóªÙØJ<ø®¬œïƒÁ­¢xìÙêò-F ­òIŽXO±0#†^…}ë¤ÿ…uâH%ŽM6údÍÌYGAž¼V&£á²}¤ÙÞ:œ,œFò¿7CúVp¬®7Mïcúÿ‚`þÜŸÚÃMýþ0ݳ¤íäø7U•ƒY”²é—R“ʰÙNÜ0Ká Ÿ«ü#âkk«/ˆŸÞ;/øt²Ú‰Xî¡>ÂãŽ!Ÿn#å`¬0@5üCé÷¶Vht…†êÛlK‚Ê‘„DˆwÆÊê)†Ž@H"¿«ø'‡í¼ÿµÛáŸÅB~ xzÙ_Ï|E&³a%d/ÚàÊ­È\ÊÊ¡Cì^µ+ë)Fú3é­e|'ñGÁ–ÿ|3oueo*É í‹²ý¢Æê&ÄÖóÒDÙðˆ5å‘ü6}öËR¸7~~ÃH©"Ç*܆+W¯ø®ÒãàÇ‹îþ-i ¼;«âñ]ˆ\–…FÄÔ#óÖÝOï°3$ŽY@­sð·BðWÅ­"Aii¨èzú¹µ‘¢I#pÑ—‘Ã+ 2¤ž†½z8Èò{Ç‘['RÑê|ɧèÚ‡äm_ÃñÛÉ;€ÛÚQÁ˜ÆpÊH+Üñ_…>"ý›?dïë^øÁft…þ.ŽßZÒµOšEÐõ]+Íc* ,ÖW²I *L#«6ÆŒ/Õÿü¥ø!¿ámxRÕ,´ÿ/ìÚíµºb5…Xy7йù|¬”˜ÊÇîטÝ|°ý¥~x»àÙž5÷H5= íþo'Qˆ6Î眠*8TŸJ질„áÍ*˜IÂ|¬õ¯ ~ן°ŸÂOh_ü/ñ6ÞÓÀå/<§ê‚Is¤Ï,ƒíÛ Ùùª}Äà}ÜžEw6ßðY¿Øn Cám?âσØF±K!Ôf‚â]­œ™6î\v=ëùe¶ø_ñcãOì]ûCè¶7-§¯Á‹½â΃:/™ãÍ.™¨¼o’c’í]$Qò8ÿx“ù55ýä.çÎ2+cï¨$\ÖБ”©ôgöÛí­ÿûӵ;RøÁ¢Ì÷bò;;…ñ‘M¥‹ÓºChà†Ff?3ó¸px¯¡¿e/ø(‡ü‹ö|øuŸ‡| smnZwŸS×ãóî®dbYÊr!Bp?„+ø Ð$m{X[Ap%R# 03ƒ×­rœ–~!¶›FžÕ&VEm€íÞ0ýᜃëJFR¦‘ý¢|HÖ>xŸân¬ß²Þ«âx3\¹²×g–ùÎÕÏÛ:wÇïk?²|_±?é÷ÉÚôïÂöÑé.ŠþrE:y¡§º}Íœmb7’é¸ÖçÉv¿µ^§ðcâÄ^×n|iªøm^æ ßÄ’YÉ}íQnÓLŽÊUr1Þ]“fì¸c^sâßüPÖtýCâ߇M¹×/'„ˆÂDÒÚÛ&ÓlVA•UÆA^[={‰¾0ézM·‰t$Ònn¶­ÓBÝíŒ÷D'ÌÌQ¶²’ÊÙàgœâ¹?x“ÅZŒàÓµoZë×Ú|F—qÄU’Xˆs?Bã*wsžýëΫˆ•ô:éÆèö¯ß´~£âý&ÓáWü9§K¥Ã)†ã\Òm//ô]FÂí x-­€cô2í0ÉoÉc¹}øâÏ‹¶½âÛÏøƒcßÞ„ó.6lÎÐP*ñ€o­GáxK¹¿µÖ¢‚ö×Ä1Éix¦vDa áYðH`ØP‘ž3^-¨~Ù6¾;»Ðt‹ý"MCTÒï¦ÓµÝ>Í£ŠFæÍY&–6. SC ýàçÍ+ž3ôT±*)\òªÐ»gêO”üš©qqœ/qtâ8Ðe™¸W“|Sø× øWö{¼ø k6Z$LvÌ÷×H.`¹ŠvUUµÃ)–VíNFà?þ xKö€ð,î›â^òÓU’åídÖ,–Ò9á±™á’Kp‹‚¢E ‡9}+§ëQ{ñ ÖçÛÖr¥Ý²]C’ ŒŒ}*ÞÆÆ3RÛۢ©‡PõZóÝ{Å6¶zÓhz„w¸ E‘AîÞ޵О—2;õ]´êåuXÚÅØÈ.6KW>_™üGÛ½UÕ¼k¥ZI XL“¡xÄ¿)=Xezâ—\xe4é.åŒiæ¶ýžH\bXfÀ &àä©PG÷®¯£Ëã]bÒ&XoÞ(äH‹`™ƒå±èl`×þ)ø¢Ì/L³»¹ðŤÆ÷V±Ü‹-Œ® sEórT92È¡Æ+móƒâ‡íÿÆ©~Ìuø&Îý¦“E¼ÕÃj.u$¹´W\¢HÒn¸·%‰$˜ ]‡ƒ|W®^xÃû[`œªf¿5´Œú^‡ãi|Yá í#àíãYÝØÝÜèÚ#j6:̈»š½»+¢À0Y0Q³‰2Ù>Ñð'á·Â‹^)Ö|_âko x‹ÆÚ¤Pêv:6¥f6ëÐ\ö†™qK¸6êû-€GØ¡\¨;©\gÇŸµ§Ž¿gþОñìÛ=©ð½½™–óû3ÛÝÉ*4Š r6°QóÅ€çn¯º¾übý—~2|Dø‹áO‰Ig࿈:Ï„l/>øšÈ\\ØnÈKž\x"šÜ@¯nÌKm”à~Ýß³§Ã;Û+ögý”ÿg+»ÑwªZj:2A&„tim òÛ5´F)cŒLÐìÏó(rÀ:ç+à/Àß ø/öœ×¿àžµ”xW_ñûþæÑ šêŽM»™7)o"\Àêâ?µE$e÷í%¸ÚÐô¿~Ô‘áKöuñ€¼=ÿÄ÷þ+Ònâ…®å¿Ó5&?ÚÊ–ÌTÈéxËz¨X%h”d»ö¯ý~þÁß~~Ü¿ 48m>$Åâ-3XÒÃrHºeÕ†ªæ «[èãgi&Ž œÇo&*X©K™–Wüß“ žÜ'ª±öïí9ûUøËáïÂkè¾?éÚRx×ÁzM凇.4è$Dñ&«,‹7 U~A„É\€ :ž |=i¡xÇÑhß —VÕ"·ñΑ©ÜkÖZ"£k:’Z/swr$V™ž7hÓ T‚ è+å´øó©ê´×Äv_ø®ÃGÔ/ßG¶´1Ø<Λ`K`ßr$e0á‹—ëZWþ!|Õï4oÙç⌚§‰õkkM'Ěťºnû«¸ÛÀò««$¬£bár ’ 4“b„Owð¯~xWáØÒüÒh–öÞ ¾Óµ˜>xßRÔu»Ûx{'ÈxåKŸÌ’¦ê(í§øã©èvÚ½íδڄ×K ·žG›4°,>FííÈ œçï^máÛ[}"ËMµµTg¹xæIæÚdŽû008æ¼òÿ[ðׇüG¯MÏö¥„ĉŒ^ZB "w–8úWIá+èAÅs—×Ïyâ/·k³/úßgˆòˆÌJaéߎÕe¤¬vzg‰µ+Û)$OÝ eo¾æç~`Ý3­eü4KOxÐÍ<¸ó´Ë4œe]2ätèzöã§YÑ40GæMºx|»”V#Ï·ÉÊ0û§®Ÿá׊.¼%¤Þ\Kr!´Ä—s°]Í-³€¿àðN1‚3MI™»ßÿ ]ƱñFâ B9‘eš)J˜å»µ <¶QÁFÆîyâ­èÿu=/Äz׉4T6·7–“Ù,‘°ßÚãØAÏ? x¯›uMsÄE¾©w®+ûNî ¸rA*¹E#±é]7„ѵ»=2e…ä—Qg$Íà\ŽØrqÜãÒš“êÉå?H>ø{ƾžÇÞ¼n¼Ðg»ûœÂÒùb…7Â|•|ÃsûÀ8Ín|LøÛàoŠ4_Ûh0h·>cÔ­ìḾkçËu9lbW–* ÃžÕæ:ÇÄ{Üjú_ŠVBþÝN—(Y 0ü­žA@sž:°çš÷ˆ¼)­ëú=þ¥&éôÍ* {¥upU­ ÃI·¯˜¥An¹×Lj»é_S±ð·Æ[ÍGÁšŸÂë‰ïo5Ý=/-l5uº ,ú}ÀÀ†8Ø`8„ynG,0ÙÝ^YáxkÄ_ô›kÑsi¥M¦C£/QqlŒ¾Kc,­•cÔvë^iá+ûÝGÅVz®‡`څ׆å]PX#˜Œ¶QHE `˜;Kuë\Ò^i>)ñgˆu΋íKs¨XÆòdÂÞo˜xÁ&4b€²9柶dû$~|BñgÂß‹^4Ö›ZòÇÄúYÑ.g¸Zm/û<Háf9y›i ë“´¨'_|Mñ狼I£iþ!ÖbŠòëÁ²Ez“#}¦ãO9E.“ËCûÆêsÀ¬g⺗…4_ jñÿiiVR–†T_"â+ìÌ>ón'æëƒƒš[{áüövöñÎ%¶7p—-½$‚cѲsíïÖ³”ï¹J)ž2¾¾±ño›yxëc¨Fù•‰Ú¾b€ã À¯#ð}ò ZïNרÇi§Ù>ÁÔ•aùãwõÎÏ^ÕØ™í®õ MæàÉocù¸9VdgŽ«ŽkÍ­n,-mMÀak;:ÆCüØÚÛ”óêx籬¹Š=CÄzü~1¼þÒÒ£‰ôä3ˆyQ¼…”íê0äžzô®CâoŒ.þ*xÅ~#ÉÙO0X§h‡-´mÞÊs÷±TÌw‹â›¹lîÒÎ F0²²`+s’„ÄóX ©%Õú[Ú¢ÆÓ1v;°˜ˆà+Ryª\€à¯µÏ jÊðLn-~ùidæÚ:õÝ+GÒ´íMñJËæK¢LbŽqæ¤ñ;aˆêÀéÖ¼šk=CRºµÒ ·HÞY/6Ü6•à„ÝÓ=qÍwVØß 4ý.ú2º›ê2¢`fG1°ò>ò:ŸJWcG üB»‹Eñ~¥¦xf9-­¼9c”,ý×w²y²®[‚bR[œgªkŠ~2Öü!¤ü$–Qý‡§j÷:ÿr÷·JPù’䡘°\p@çUÿ‹:6¥à½~†úÝìz¦§kµo¥ (¹‘÷Çœõϰ¯(‹R‹d±)V¾ÄÜùrAûØÇ¥eÙÑáKòúƪutû;ÜÄÖ&flsHF÷rG8õ®ÓWñ%—à _MñrI>•i=Ì;á®RlÜ“¸1ŒðjKèmî&Ôôò¶* ºÿÖ< mÁÁãœzâ´ÿ¶´í{Á6uë·­ÝÌŠìv²E-팑êhOQYU|4ñ\¿ï.>ëÅ­xiÖû*Ä[jZ£o!žÝY=Õóž+šý™|kૃ ã?IñM¯Œ,ï/§.’<–’ÞÆÖp[]@û“Ï„±™›9]‡½yìÄW~†m>O.ãY»7W«‰%IÑV%Om«ïÁãŠíf-V¶ý¤ü%¡Í}#IâK‹Ét›{|b]L«E ¨¬G ÎÞàdg­l˜¥±ð'Äo‡—òjž6Ð>Ýo}{ª\_é¢ýXí¸û;´ 9'5Œ’­~ÍüIð=ÇüûöÖ¶×ôè5O…?|k êðmô¶>Dw߇t•e„$€¢Ïzâc+Æ¡ŒpįÆBãRÖ®ïÞ0"¸ÓuBÝ ¹’ ©aËcø›g¾ ï_Ðg‹¼ka{ÿÞøqi¢Äº%ïÿiZƬÜj›u+‹Üϧ’]©&ž·Ÿ-d+q“c}™ËS¡åž#×~>øë ÿjk½L¸Ño¬ì<)ᯠ/ækh{ï5kóÄi-¬ÏR›ØÄϸµòGÅ/>3Ôi?|zðÂÚø&ßã.÷:°»vkKMïa6wx•Uƒ˜7J´4’ócz—‰>$øÏà7Á†_ þëø²Çé©ÿj¶›2Ëw¤xïűMk5¸•Éò¡òÌ’Ì2NfÞ¤nÁåþ6èÚ‡Š~ xAºÕí|C«xQm9¢-ªØørT Íë¯ÊÖ:G ›„WŒ“½©ÙØIžkñ[Æ>ðŒ|uáKxf¸ã=GPÓ5(£Ú…­ÆŸö&’O›%÷ÊÎ6ŽT⯨>ëºoì©ðçÂß~ÝË/Œï­<{¤E=Ó¢Al ¤pÁgò6uŒdF², ƒŠùóáÞŸ¢üFø1¨é-qö?hWËáûY'u½®¥©j !¹¹à¶µ„‰“Žk‚ø“«x¯Â ðãF¶Òྲྀ_i^2Ù -?Û¬u{—3â ý”¹AÇJÌÑ=ÙOø'ÁfñV©§xWöv×u_ø@ø2âãVÕeŽ;›G‹rÂÆÆ£qÑ¥{›‚Å*0}ñ´™?ô-Gö=ý½n¬t{íMGᥲy¥ÔZ„Nöw(%´WK’FßÂA¯èo]!ýŠ~9üýž´‹KÏ øinõW×à·ŠXl´2^ÛÎêX0doqÒs:°i#ñ·â¶ñ¿âF»ñöµŸXмQáïÍkâ—ÓàVÇàTuÍÊɶHìö±Ì!a¸' AQ¬Sd)êu_°ÍÄH-uÚâ•Õ¦’¶~8ž-BúÖ-——vï§\_jW«¼Äe³. 0ìpÀ÷‡âB·ác]jM0ßZ\Ëtæ£Z¡vD„…;™Eu?ác|Uø©ðcAµÕ4~m>ÿWñ<Ƨ'™a5¬z,Wj9•öÛ1[q’pÅA<Öeùëûr|Rýž­þxCà‡”÷? müâ¯øSJµ¢¸OXéðip+*|ðÁ,“\ÊåÁËàŒsøuá«ø£ÃÞð†…z=_ÌÕnmdm‘ü}•¥E9Äێøö¯tÕí¼9âŸÚÇ~%ø£ªxÂúç‰fšü´7cÂñÚB$G¤²ÝÌÆ8HRQXqŽ|ÃÃÚþ›áχ_ ¼) ­ž½àGÖZ÷WŒœjÒj×QÏ Ëù³lÊæû¤ôéQ"âš>Úýš5»‹¿4„~¥i:N½wj¶‘X¬úå’‹(ܩܳ]M9’Ycd,ÍÀÍ}?ðÂ_üWÿ2ðWÁkzW†þ ~Èš«c®j×× k’iÒHúÄ® Ư}|ê³r[u»O›òü‰û|+øƒs៶ðç‡ãßâÙµ Y¦°Ós-œ³+ O2K6Ü8ÈÍ}ñ{À>ý¥þüGý¸ü3·‡´?üaÐ<áØä€›è¶qÁomq !K‰ŸV’}¼Œ¯D2Šv&OSÌ¿f¿†ßkOÛ¿öuýüMgu¡|1·ð¼Þ+ñ’²‰ –Iï#°%pa‚%öò(Á±Cœ’>s·ø—¬x¢?xÃÁ^ƒÃüY¯jV:T¶ñÇoi ¢î,ô¨]r“d\±Èû£×¿g/ ø+ľ,_ë¼Ó|G¤èž-ñT¾#Ñîå³»ÔZöM3M´ò°Ï¨šy¢ÜqWÚûÉ\_…|ø©ðWöHý‚üÖ6õoøîóOC46_ð4±izr´ÒÙœI´þäÊ»n ö4bßxSÄÞ'ñÿí%ñN¿€|/ðhñTR^ï’ká7™ÿ߀<¨â‰Œr€UÉN¾oøŸáë þ Û]*+CPÄšî…=¼¿hŒÚ_äÛ"I/ÌÌ“áÉaó”æ¿l?à¤z¯ƒ>|3×¾ Ëcöø¯@K߉:Ü cµÑ´9ä#GŒ—Ë™H£EVc½ÀoÀˆ¾'±ðåö¨4ö¯¦ÙVö„‘iD³ºÀÄá e@»@v {Ô(”¥¡ú»ûJxà'ÂsÂìÃáë‹ÿøÂP[]]ÍpÓXÙßÞ[lKUiƽ’}óÜùEŠŠçq~rü:"µñ4>6ñˆf—FŸR‚f”(yäýÌhGXçqÖ¿]¿o#@´ý–ôø“@·µñŒuk ­ DðäÒ]XxkÁº;BÍ.¡ ;'¾½–E„¶ÕÙæ…¼¦wü-Ó¼>þ ’î gŽ7…Ë”©Hâ›tñ l…“ÉcÈÀŒñQU 3ñΓyáÿMðïNuºmÌumÌ3ªFŽ Dç*¹+ÏñÚ¾¬Ÿà”zWÂ=Sâí½Õ¼Þ ƒWµðzÄdÛu®ÞD—RÏn‰Ê[Z@K;“–el³-æ¾3ºð¿ÄŸŽ¾-ñà [è^ðÜú¦ºDlrúh’(ÞH;D—,’ J‚¸|ÕôŸíe£i‡Ä×ø5á{{=?øHõMFx4¡‹+o2(â‚8\a4V®ë*…Xà5–Åž ñsáŸÃ VëÄßÃÝNæêÏÄ3O¦ÛÙ¡^=9-”ÆfqžNGT Þ¹î¿h¯üBý >9iš?Ä;KÏÀÇO[M6"†öÚÆ;Qn.RIFÖ™Ð1PØ\`c‚Mï_xvOŒ:v±4Ò¿…lÕ. ~JÉ%¤|[ª`0,B¨$có“Zºµsñ{Ç~9ý ~3ÝOo6“¦Ác¢Á·Éu©¿Ý·-‚#±yôóO UØ-sêÏxkàÅßÚ¯Á^/ñ&›/€ü#=†Ÿ¨éÚÉ[#Ã𔳖ñÔí[«ÍAÄŽ2Á¡iï^3û[|PÒüCñÆ~,²½MrÎ+ø¡»Ôˆc×µXá:&I¶àŒs¹”“Æ ùêÏxÛÅZ§|?§Ïyy{e †™^åÊZ1Fò“’SyÎBá çŠö€³÷†uÏŒÞ&ðßí1umá¿|+ÓŽ«ã î÷I²y¢ teáŽX;¨`ʤnÑŠF/ìýðÇþ1¹Ó´?„º$ž!ñUý”šÈ=¤ƒœ£²€:‘“œ ¹}aã_‰ÿ£øm$Éñ Ä©0‚ÞËOŒ5µÜðÄLDV$†Ùr|Ç*¸€}[ZøÙã­7X[†îúŠukÒM©„J¥L@û‰¹ã&¾õýœ?c¯Šÿ¼5Ÿ€: î«ñ_ÑÆ£ªÃcn¯=¶Œ‹¶I|ùÝc UQe¤cØàùY7G ûÿÁ?ÿhˆÑêŸ[GÒ/m´–¹Ò¯/u›ÑöQ&ð²òÕŒÏ •Ldn¯¾|àÔ¾ëvÞ-Ž]+þ[™H¬ü”,Jd’âÚ¦FÛ?»Ç'®k„ø£®ü;Ó&·øû=ßßé–÷šOØ4Ëu¸1ÙiÿÙÑ:M©_€áv<Ä€¸.Òc‘/ìðšÀOxâ×ÁÿMâÏϧiú$zUò¥æÉå|Ü1™ðeƒ3Š|V°‰›Øú_áçÿ¼a£|cøo«MðkG³Ž×IÐïõõÍuo#‰•\ Ñ6Ò[ñÒ¼Æß³­×‰?kíöuð§Å¯øMôÏjú÷Š£Vµì‹¥C r–0`9v¼ µwqȯ­>iž#ýª¾h^×íU&ð½æ¤²êÒà·¹LÄ7 ÍÐïµ°1¹Hë^ð7Ã?üÿ7Ô¼ð+ Zøá}œ³«B²Ù_ê:µß™pܰe °$Ô0q‘]Q9¤÷>ùøEàßÞ·ÒõÄðχµq5œm™%´ž)IÚÂE|œ`u=+Éü}ûKxæçöÛøEc?‚Uàð熵ír[>á%q,î–Qº”Š\0?1 ÇC]}×ÇOˆú¶s¥/‰DúÖ­ª½¥´Zlb¼…R\¶Jާ®M|uàJêÿþ Kñ?Åš=ÒÃ…<3áÚ¬°‘:êWFKËÐÙà+C$ <äç­Q’?L|?ñƒUÕuíÝWÃÚͽÕÐrò”Ýl#s•Ì™Àduõ¯”.¾"øKÅ^-[?xŽ{ Ãu=µ´Dßjš2vZdmWû£¸ë^¥ñÇ\Òþ(ü*ƒàÖŸ¯Ïá ÙÃsq©øXEü–$±µµw˜ßÍ›¦Õ*¹'”~|[Ÿâ—†|E{û1øj}nÎHÓíZ„2C¥ÛÌPbXe‘TÌÌ>oÝ«(Ä*ÈJç¢øÛEñ§Äk”‰¬ô‡H¬õ{‰;{¸rH¾C’ÁW‘’2O#ŒWѾ$ñ¯Ã/ŠÞ¼ðHY%ùÐM¤ÄÖ±X,jH™œA#IÉÏJøfß>A¡xÇš÷Åïé!¯¤Ó´ÇHl´ë‹®V4 µFwcŽ{×Ö^ø=sâ/_øßö¾Ô£† £hEÁ³±†5)•¢+-ÔŠÌN…¤ÑGÌš\z×…ü&·úg‚-´yo¬ãPº¼˜‹‡¼r@‘b݇R§! SøA¢þÈŸ³ï´õñ5ÍÏ‹uËã,²iÖv’Ü\±ó dÚ¦6Ø>Q‘ž0+ׯ´_‡Þ2ø§ß~˺%ÂA;jº†¥<¿cxÕR‚rÄ ‚@ô¯¢¼uð»Äž#ðåœäè|3jæT³ðò‹W“Í\ï’|olwí„…t|sñ+âÿ¼eðnãGð„µßêw±›q¦È‹ œLHäiÝŒ6>•Ëü5ý‡ÅsÁ­þÑq Rþñ£•ôË<<~j¨Ìã~éÂC_`ø[áïÂûË8/#²’3`wÀ,‰9ã$<ÖÀÉ9ô¯e·×'ð^‡u©ü=³“X¼ž%›Ê«™~\ ãëj, ó8~xËÀú4²øCÒ´mG’#WqÒó<ƒÓœvªvð¶¯àÔÖüQ¦=ýØÌòFø`à#ŒcÖ¼ÿâ$?~">™ðûÄZ•âA©Ü-ÕÕ¼!`ÇÜcÞ †Æym}à;Ç:sKo⥂(V1K ÷xçß­Q„Ë> ·Óµ¯Ù]ZY­´NV,mXÂñ€A[‡Âzz.v.}=êÆŸ£ÜXÜ ·¹cn~ìöÏa]¤Jô¤fq­áˆb1Œ÷Üj„þ—ïE9C]Çš½¨ØüÜt 2›ÂZòö—ŸEñ¦aøžß'šàu1°#òàצ´Š„ã½gÝêÖ°››‰v*õ-÷hÏ#’ößp¹‘‚ï ¤J°ÚþnžùËc­_û£ãXûEü5ŽUvÉ(õÏð©õïU.~ xRIú`{9YÿÌ6E-¶¹á©' jpÿUÍB}Z´m6YèqÄ|± ÿtއ+_„²ÜÉþ‘z·x<$Ѭøò× }àóà‹ÅÔ­>×b¿{¶èÙ}Uúäz¤\Y†Öž$мKý›o%ö™mp¤Yܸ1ž:#cï¯jÝÕ~|V×t;…µ}7UócÎû­Ì\¨ùsŽ­éœbº}WYžêÄCá{ûfÚHËí¸‹x…ל–L6Gã^?iñú} E†›{¥Þ38ûd¸µÒdÛÈgžRª>ƒæ$`U\®fs÷l."ÑüJšœÏ«iÃ:eÁå >ÅB»†yÉn*/øÂÃàn¥oe¥èZUÆ­âŽÚÏN²dóߙ`(/#6­¿‰1þп¼#i“qc§Øê0‘Í®è®eú”‘в©ï`9è_ÿfÍGá'†míæo B–Ø–Sws5Ä’ÊKÉ+©yôÁ8b¥ÈÚçËw_5?x¾óÇ¿f]7E·!`ðÿ†nR@ þ•r ÈÙÆ|¨¶©õõõ;‹¿ þéi០ødøkKò•¡[{„¡sËmRwðâIè ¯TøµûF=¯„®ü7ªü?šIoîî´„Yí¶£[Î`®˜‚GãšòÝ!4#>‡ñoÄZl—\,¿cþÓx,à3…mÒŠqüGæëR ž‡àŸŽãÅú=õ‡Ã³ýžñ§úïÚ½„r—ÿžq`K>>ñÀU×̚ǂ~Öß·'Æû Hý<ã? xnçPš[+¥°HõÛ;WLvWzü¶¶PM³ Ò"ͰT$ÐTV§Ôž5ø9á„Z¶™ãoŠZˆ¾ÖVá®t…±@¯¦Z\2á“P¹ÜûqæĶN–"¼OEýj_ÚûÇ7>3™'øqáK â['J¸´µ½†mxüÕº½/Œ4³£þS»ŠŸPøû'þܺuŸü)‚žð'ÃË=.ùµËïøÏ[¾ñÖ¨./YÜì‚A k{#’òm¹‘@Îr0ý<øgÿµñÅÝ.ãÄÿ¶7ÆÏxÒÞþFÛ¡hwíá- ¬d¨fþÊd¼‘IÉ 5ÌŠ:à ` ´™ð7ÀèðNojÿ4;=¾¿YÜø«âD[ê·ÐDI3&c1Û–fhbakÉØ7`GñsöÌñwí)âŸøG?eß„ÚÇÇMTÉ º_Ž¡†O ø&Æ/1|Â5….¡M¤¿’’«´œäWèçdÿø&—ÁCñ#â< ¦Þé0¸Zñ%ÝøXÔŒ$÷Í<¯ÄmŸZð‰à©:ňڷìåÿßø{©üQ×ì`ŽÛPÕ5).4/èlÁ «Ù#>fèdGHmâfeÀ.æP…+Ÿ%~ÓÞÿ‚”ü´?>=|qømðóÁWRCy¬YxwF{É…Ž­Qq å̼,>dA°|¢>Sùám;Zÿ‚‚|J¿ð‡‹ÆO‹šV«3ÙiéâïXø[Âw( †’÷SÓ¡PèC–ºzÜ ±‡_×ÙßöJøâk]Wã?üVOŒ>=Ðõ¢–ê¶R¦‰aq2y:N–ìbHQ˜(º˜<²mÁ!­~‘øcÄÕ¾Õ¼Eo?Ÿ§iPGgo¢´Ä<¢òòfrÌŠA"¤1©¯£¾?|Jø ®þї߱Ήñ3ÅÚJiÚUÍþµã¿y©¤èŒJ©¶¶K41ÜÞ° ª ¼Q0Ãnû§òûâ_ˆ?e¿ëpþÌßðF(­>"5ž‰usâ/‰¾3€êVÚeÅîD‹hÓÆ$¹Ô®Ao1ßä…Ù¸³•‡UG©¼)ÊNÉjy_¯ø(oÃOÙã÷ˆ´/ xgQøïñ[R¾²‡Ãº‡e·š%›I\%½²0g`’î2¼1K°n-œõ™¼ÿ ñýÿj?ø)Æ´Ÿu½?_[kO†š†ìîmöÆ!º™Q„‘Ú¶ZíÑUå(чþ·ø[¦üÿ‚`þÁ¶³~ÉÚ\6_õ/<ÚZâyÚÍþ·í-s.ïô]*ÕËlŠ&@øf>‰ÿÇÿ‚„üý›>E¥ü|Òo5›­>fº¶º³{f®®‰yäœÍ$J$2u*¥~cÐŒÖK÷6–¤Uì}1û'|xñçÂSâë¯Þ7Œ5íB8<-§ÁfºN¥i7ë±åó¥Ù¬0">¬UFÐÎZ©ÁA?lÿèß²‡Ä¿ þÏ:ü~,ñ¦¡ —†<_ãm Þð®<¿dšÊ-N_ÜÉtz%­¹–f¸`òÇ?+ÿo?ÛSàçíyûMé?´Çćz¤ÿ<-ÕŠxPê?d¹ñÞ©3‰Ý4N :|M$’2L’6›€Ñÿko‹ŸðQoÛÂ#¸øm¢øáGÀ+;cAøw¦Þ2è+|‘˜mî/F%“ib€&"XöFH–BÄ«ÓZ¶(àªÍè¡>þÅ*×>$|4ø#ñÓÅ:†³ðûâ6±6­¬h×¶‹§«è>¶V³T+ X4ÍþH†Ì)‘‘ƒ\Ò:/õñGÞ,º’ÃÃ÷G,aU•Ç”¨¨6…@Fݪ8U`{Wó“û?ÿÁOüsãíOÄÞ?ð^ƒ£k›[k >6Ó¥h­íí=Ì©#óLä]ã"58àW¯ßþÜ_üWfmïîí­WÃm6¶1‘ÏQ\•sl=?‰”²\MM‘û»ðãPÓ|=u>‡ ÊÝ•çp¬€HÞ½«Ö<'¦Ã<óêÂ×lî“j·á‘é_ͧüEñSÔæ¸¸ñEÀ‰”™·ôÆÐ=)ž>ø§ãŸ ÙN²x–öéÆ<«DœÂ§w÷V!ƒïÀÅpK‰p©îwG…ñOÿÓþ%|Gî‡ãÝSF%Lbâi"6õ>Ç/Ôu”©Çv‹µÓ÷Šr;b½ ãþ™"Ñ|Z>åý·–sÓ|DméÜ‚k͘å\F|¢’QŽ ú× ÜÕËCŸñ„6~&v"Ì¢5ùAažùý ÿÁ¹³­¿íQñ_ÇG‰‰y£iÚìÅÌó³HðHYd¤d!r?:ŽÜÿ<^1O3MÓµ;`w”’2qއ#ú×Õ_ðN?ÿ~ ~ÕÞ ñ·ÂŸÞx#Z¹Ô“JmNÆP¥b¼ ysFùŽh%eDxÜÎÖuVЦçI¥¹®i;³ú¿øÕÿsøë ø[Ç5ާø¯Ã~#škÇ·Ó¤YÝ“òLÖ³ìËÇkdZü:ñ×ì7áëK5Óñn™­Ï—Ñõ= ä(øË74¶ù^–B¾•óñ8ºzt=GNGùÖ|]ý’>"|=øquñcS×,õ‹MNK³We»X7²G;G¥ÜÏA\ÿìÅl|gàO|-EYg¼Ó$šØ›t¶Ùb¾Ü_ÜÅÚËþ ¹ñƒF×~ø·áWŠÎ—¯)Žñ¡ŽÚ` ê鹄›²2Œ9Èæ¿‰?ÙãPð—…k¹¬¼÷çÃÿmÔ"Ó´T%ãÚtŒN‹…3æ1¸Jú,-YÔ¦ÜÖ§â¢ô>vÕ,Ÿ_ðõ­õÐó7+l…#~Q‘ŒzµÞ~Î_|aá=nãKµÕn–ˆ2Z³—„ª0,6@ùx®º÷ÂhŸŠþÂ:5Ìépr#RZ3è…yï_5xFãû Ç"·ùfW…ÙGÞ?©®¨¤ÓLÊç랯ûWxcáÇ‹›Á~1²¾²òÖ9a–&2G%¼ãpm½=GNk¬‹Býž~?øjê="{KûMY•î!³qkz³. åpppA¼WÂßµ5´Ï€>|O‰OŸ{¥^i2c \i³yåfÀö5ñ¥­åͤ±ÝÛÜ,ÃóÇ,l‹ôuÃÀÖ~Á4¥f+ßsîˆÿðOÝvÔM¨|,Õ†£X­–¢‚Òä>èf'lôû€ûWÆþ=ðOüugeã-"çK¸ˆtÂ>?ºã*Ý;^ïàŸÚÃã?ƒï¬­umV=JÁ¤X›íщ ÄÇçàœrrkïñ›Á áˆájÉmkk{u-‘†ê=¸Ú›²Ù Xp9ëWí*GI+“È·G忉—ûkJ7¶Š~pU±Ñˆ\×YF²Æ°|”á·Œ`ûšýøÉðïááðEï¾=‡Ù$ /“a%ÂwýµÆ µã>ýžcøááëícÂÚ”:f§dñ(¶»ì÷ "ƒ»ÌPv6xäô¢3[1Ÿ?hº\÷‘iÑ] YÈ@¨>o˜ãŒõÅ3X×mƒŒW¡x£àoÅ¿ƒšý¦¯ã}*Xl¢”bú˜ƒæÇ¾ÛÀ>ÕÏ~ѺL?äÔ­ˆhõ[[[ñ·r àŽÝÎ}kefF£¨Þë¾]Và†’ÖïË”.UBH¿&Tq÷Ö¸F…æ´`P:1¸ì+³ðÌÉyá}wF?}àK´þîmÎOèy®P0tƒGÞ#§#·­ ;_ƒs´ú–«¢m¾Óg'™@+ýkžÑ.âÓu]?Sa·ÈºŠFó>QµX}zw¬¯ ꚟ†õȵ‹abJž){ô4²˜ÍÛ?!ÎÕcÐÞô=Àì~!ÛŤø÷UT\Ç4¾zÝe‰÷5‹¡jÏá»×»x¼øîâ+ØÁÏ|TšO‡üSâ¹¼ûäº\cÍ™p¼Oôé]4>Ñt™ÒêÐÀK„0ÀL¬}‰Û“Œ“À¥~€`i>=ñ~ƒ+É¢j3Y,‡s¤oòg ‘œwÆkZÛÃ7ñ½ð™ášòiÈ&[“…;ϱé]ž¡ªèþ Ô^ÃÚlk4I»Î¹e¶G °…T¾ñ.»âoé×÷O) (™ˆÆô9 þðãk‰«›üÔôm2M_ÆÒ}š fE´ q)=8P€=I5ÂÞxŸGÑ¡Wø§G!MÛ¦»_2q·ã!yäà=+©ð'Ä/ÚÍ|×3Knöî|©\ç1r ÈÛŽµÉøò÷ÂZõ´^)ðr½¥Ü²b께d¿ ¸ù“$pº¯Ä/ꙆãP–8·BÆÁ?ì¦{7ÃÚ]ð½ºxw\ŸÎÓ¼Bì„ñžã^ñ­Ù-4%$ArxVÁQŸ\μøåÔâù·:Uy½ ÍOã‹õPÑZºØE)#L3û‘š¯¤ü*ø‹âÛ~lš fmÞ}ûy1ã·ÞËÁM}7sá»/‡wBÛGÓbÓ%a,„]LGvW~N~œv§¥Þ­#î§i ïbÝ»œXÕÄF“µ8›B.[³…Ó>xgI1Ëâ}N]BR7í“dG=·ä·ã‘ô¯UÐãÐ| |¿ évq°ÆYX­Ç×q&³-®5x~B¤¨0P_R1Rßø¯AÑyÖå[Bë\Ò­VnÌÕA-Ž’ÿÇQ²‘«Ã©&ÿï*àw vü+±øOûBéßµHl#»ŠãKÀ‘$á'ƒÁhÏ÷ONµóŽ£ñ›ÃÖ@ý†ÖKâxf$À£=³ÎGá^w¨x»Æž;¸K;XYavù ·@ÇŽ9|r1êEOÕÓæ6ö¼¨ý›Õì-5¼kàÍPKm(Þö¬ª z©SÊúçÖëÌ–·Åìäó?t¤ÔŸ§á_ÿ .>/ø?GÕ"Ñ u*ÉßFê§®=E}{eâ oÃ~¶Ôïµ­[‘ûû«xƒ€}ÀÉ ï^V'(koKÍ¡é%ø3áé· so%Äè›&²ºiS$õeôõÇ8¯£¾þÁ¶«ü+øyûb|Ó¢Ö-üw⋯xj &ôA­6«¦5ÐŽcÒÅjaš;)Ø9¹PðáXàWÉÚÄí%Y-•ƒ¬€;4e$þ•ýRÿÁÿoïkÚ?‡àšÿ´ 6óYéóÅwð»RºEXÒkgI™Ðà\[íg´“ƒ5¸dlÉ4Šz”¨Êi]®ž]HÄnš:ÙƒãÓ|wðž»áYI£x÷ÀšÑ¼[¡]Åä]Y\ªšH¶"çcxØtfR ìþB>øžÇà†·‡ðõô¦o Jãp²¼MÒI§+V92Z¯D!âS·b¡à¦°ßÄxÑà¢ß± °·ø³ Ú›_øe¿vëÔËÌ÷:¬ÓÃö_>».#q˜äS‘$l1ÆTA¯Œ¼ à­[àïÅ+éæoì¹­&¿Ò'l’¶±º‘ ±ÿ–‘gb‚~dÁõ¯Ó·|G¸›Âÿ¯dÓ¼máõTÔͶo¢rD7ÈÃ$Ã%ñÊI¹O4·~ Ôü9©Å ß=íº#®Ù@ãxöàpkЧ?gf]½¤•úüBø áý7öäo‰zLf-öðW‰¼â=0!›]µÓä¼±”€0LÐÀà³p ã«Wñ…eàÏi¾ Ò¼Cw¡ê°é—°¼W2iׂ)@w“É1²‚äÍ|^0Õ.t[¿ ]ƒ²ø¿Ã·(z…Y®VÒvöÂgϵ~x~οðTÿÚOà†®¿b¿…÷ZŸü!>.×¼1§>·§¼–vm¶¥r-ø ±n¶…€ˆò`++pO«ÄóÓ»Üóqô'§Sùqø[ ±øâ¿ÄèVâ[}ÖL¡ˆóeu?CŒûW†Üj:]½Á"îÁù”É*¨#àdŒÿõëöö¥º°±øï~Þ#¾‡ÄçOÕ¤º¸Ô…´(º£Z!c9KuX3$òŸ3ÊUFƺ=—þ ÷âoè—þ?:îŸ,m¨øoHðÝе¶·¸rûå–gŠ)Ò@òy²C¹\]êIêprŸ@Á ÐÁöz»ùWS£jp²ª‰­îÙ”‚Ê€m1€ÁHëÍyRWfðzO¢kÑÀÏm4Ž“:m;#팞?Ÿ­z‡4kzHÌZGæ:G!½W[%4î•cînâ¹V¾Ž×O‚ÂÝSΑ–9âÁVGc– ­‚õ#·¾‰øq§^èÞ¸º»×ãBÔbv¾´‰‚Üù2»X0+•aœ¢œi+›©³Öâ…¾鋨ü)½·Ò®¤º6:î’Xê:,¥@ho,¥RŒ#~Uô9qÏ‹[kVµec¡ÙC§izuÌ­å„K$óljsƒê+Ô|á߆K>›à JãK¶¿F¹þИ'Ÿ°(óm‚‘´~eQÉ5åZgŽ´M7Q7:m€´Š(]b•ï ÙÂÎÝ`y\dtíZÔ“îJ§vÛ=ûÀv’øçìcÅZ…ž« xjú]sN°ºýÝÔ—ûZ3æ;6ÖŠ6lªÀ'ú?û8~×GÃz]„¥ñ¥—†¿³ï§ƒPy-~Ól-ïŸ$AUfrYó¶1óËšüEÒtXµÏÚxsX¸¿h与›™;ÂÀ³ùhr$dƒØWêgÀ¯ÙãàGÅ}oÃgÁºˆ¥×î.nnlïl?ÑfómÑš{k»…òÊ,`4l£?6yâ»0•-¹X$´?¢OjhšhVU Ëß* ¨“Î8çŠÙñÖ“Qðwˆä[Y¼¿¼N%mo\sõ¯‰¼Wã+JøàoŠ6w¯¦Ú¤ÚMÅΡ‰ÕBìýü+µƒ«¢—¢º‡)âߊÑk?ç»Ya´YôÉ…¤ÆdYà@±ÈŠsò ç'kkÑm4g—ìÆ|i´¾øsðçVYï·ÄöpCæ‰1gq,K, ƒ€zgï µÈXÞ|3ñv›¢øWTÕRê=̤‡ÌrH¨ \y‹ÆÕ9ÈozøÇÿ´©ÿŒü ãkÑ¥êÓ O É¢,.m9÷™.™º‹Œðäm*pÄ`ŠóoÙÿö¥O†Ÿo¾j©Æ«­M.£ ²F©möR [Ëóùê™8?xœWñi;\è-7?Y|5ãÿxÛPágÄ [»‡J–—n³ì–WvxŠvRȃsç-ƒÒ»„~'ø/á6°x¦{S~Êê ÂÀ †C øjõô¯çãÁ_ÿâ­¬º~¹¬xw\±Ô_A2˜…Õ´ªÌBÊ ·˜’6Y‡?'#¾ªñíGñâ]OGý°þ&áÝ寍XÞØy3…º–#2ßF¿7‘/© q¸œsZRÆ-Ûé7#÷7Åþ*Òt [ý3QK´ÂÝE#¯‘(ž½ëÇ<]ñÂ?ønëÆ7²}®Ê6ƒìÁ 19Bõ16@c–¿¾#~Óº_Ä¿‡ÚÇ…¼W¯YØjž(Ö¥—]OlRMïm#Ç÷`gv“Âdâ¾Æý”?k«;½OÃüs•ε¯Èöp\,&ê&•rÑ®òXù%ü¸ë[¬jnɑգè?üjðÁý¢¼qâȱ跖þ¿¾Há?º•D¶Ó”dã¡Í}p¾!¾4]è÷6í$c†?ÝÎr¯èQÀ9'×äWÇ¿…׿mí5<©\Ëÿ ·©jÉer‘Ë1ÊÍin„i",6ÆøÜã5á³þÛ¶ÚÚê>½×uTK­=&ÓÄcÎH¡Ü"i¢ÿ]‘žYn^O8sÅr—ÉêýKâváé×–ÚúÿIñÒG¦ÜLcžkK©|„‘@åáG9 ®W5úuáƒgá¿Å«wçɬÅþ‹n È»áŸcdŒ•`vŽp+ùb‹â¼Y£Ú|_ÐíîµÑð«NžÞðyÍÌwÏò"Bÿ½ûî:&ÅíÉ5éú‡ísw£éžÓ¼+âE×lü3k.¡aåÁ,WšÛxgu %ù²ªB’È%å†-ÝŽtïk¸?¼G,ÿü/¦ø$½¸ÖµY4kb%bó‘êéåŸcÙnŒÊãÉågs(®_öÉyµ?,øCiâ!Žmü5ªhÑ¥ãYMwqm€‰šÂ2Àäžõðu‡í¹¥|BµñůÅpž,´ðÅÅž¤év76pØÜ†ì»J>FS÷‰"O—îàWÏ~Ó>-ð§Æ]RñDÖ>8±ð­ƒZé·z“5ÂO¢«2´¬œ–‰‡ ÇjÓëQ¾á]σÄðQ/‡^Õ¼E¤èß_ÂŽÒxwY–Ô\jñï8Y-vÇ%ÅÎó×϶jw‘O‰ð‡Æ_¶Þ­¡kÿdû/êúoçKvÿMžk×¶2ä°·’1¹FÙ„Hñ7? ŸÙ_ãÊ4º>•ˆ|ËKy©ë–öÐù0hò)AÉfk™Xr͆°À“öðí÷‡5Ÿ? ô{á¤Xø;⾺°[ùÅ–âãW‚×T—2CaîÛx`p+¦”ù¶fŽ)-›?à•ß´ígñòßâ]ïí-'ˆ/,t¦Ò“E›_ÒIuy|Óp‘î†)!c‚@Îs_ªÆf­ñ…Ÿˆ®ïôk)Þy4©;€À¥†Aô9ª ŸyTàô®ênèá¨îÅKT„ð±eV©ÁäJzË’qZ–©§ÍDž†šdë@]Å?<`ÕO0zÑæñÖ€'ó5 0æ¨XšUf<ÐÞ:Ѹž¼URäâ’G4лxÎiLƒëTiA| _çN/òàÖmYW!j@¶¬¤ªAýjuÉ  ßéSFýêšÉÎj@Aé@ËžÔÝÄsš«»-ƒN4 ²ÂœÕ°Ã5œçÏJ°åÇZM ˜º:dÓ„ÛET=ÍM±iëÜ9‰Ä¸äÔwMmqg-µàÝŠU‡±âª\_XYBd»pcŒ\6¯¨ÝÞ xGÅÿ¯|'ã­6ûVðÅæ¦öº|Ñ‹—·²–%ß%½¹bͼÈB¹.Åz“_YkÚß„4 [LÑußYYë:[ˆ¢½Œ[2±Ê½ÀPF“‘ûΙ<×¢|¹øßáDñßÂ-ûÅZ5Æ¿w&‰zþÕ9þÛfdòÈYr¬T+u#óëâOÁ‰ÿ õÿ^x#V×F¿©@ÓØh0éÐÞÅaöGÜŸkvS»glØ-Ê᱊¡â{? þÓ> Ôþ3üYÕôý ÂÛ_ÜxbºÐg½ºÓ£}óYËhÍçÝ£ûc9 p1“õ‡í&£á.›¦hz?€üVÞ9ø‘%̉ýâCª$bMó\˜!¸yÑ·Fe+kžœâz×Â?ˆzœV^ѼáßøOÉ‘56‡Ä3ë:Œ“ÊØYï&˜)ŠLŒxïÃß®¼[yðQüïkš‚A¨=½à]ö·Ȳ^¡Jn¸s/–ä,€üÕèßµ7ƯÛCã–ŸáÚö—z¾ª«'Âú,¢ý4%¹YÖú{xšæ8…»Gö‹.¬€Åˆ¯øÇ­|Ö|]ðžÎ‡W_ ü;m¨Ë¤øžÖiLºÛÜÞÚÇ$ò0ÌÀÑÑX¨øQØ~«YþÅ? ~ üMÕctøŸ ØøÃÇmk©x?Å^Ô¦–-oÃsÞyShwÈó²Å%›9’RÛH¤Ì#zˉ¸Û>Šø[§økŶúF±û0øVñ7ÄéÀxºC·Â~.¼‚û?ˆ Š}±‰d³IÕb±a¢ò¦ÎÁ#~.~Ôú£û5~ÍúÏ‚¼-ñxÃÁšWˆ´ÍE?á°–/ &—¬y®n4É÷ÉöšC4Z‰˜Iå‚o ~Òý¦?f¿ŸðF߉ßþ:Kã{Oˆþ ðŸ‹uM3JÐmà“O›I²Ô­¦7Ö]\³©‚áLžVò«Êœ¨Á ÿ [öuøgûs| ñì&¿â ø³Å:gü' ¼H#iÚ•Ÿoiñ´E tßfž3$7RD¹sêÔùïá猟ÅÿðUÿŒWŸ³Ôž+ð¯Ä-ÅjêðBLðhZŽ“§õ q\Í$q® Ûù(Añß‹_~x¿öhý¿eσ^+K›[ý3ļSc¨\Ãå~öíå´[„@mhŒª9váR=#öYñ‡Âσ?ðXSM>(¿ðwé<_ã¯ê—Q'اMâÖîâ]5–A'’R欃.1Œ¨a_;x;Å_¼ÿ•ñ§…-<%wâxóŘÜÙÈ÷þð¦•n-4÷¼¹XŒp\OwìŒ2,òÉò¹ú û|pý“?fÙNÿ;1Öµ‹Þ:}?R¼ðÇÃÝ>{íQtdùšËP¸HöÚÆ<Ñ/–²£¼œ üŠòŽ¡ã_ xŸáÿ…õá‘­ørÛÄSKªÅx±èùÜZÉç¨"òâ%óRÐüÜ3•îU¼OûZ|ø[ð7Á÷|M§éº,~¶‡Ãúؼí_T¼Ç¼,¨®ï4›–ÞÑÜ19|sûFø+âÅïÂÉ_â†ì‹¿h}SMNÐ/µE+áì« JÞ£ùYšâ{}óI3y_h !d2nÚ+³É>-鿼C«xCCø à%øuàívò i–7e®5{’ì‘˨Ο7‘ Î’y # ªŠQø£ðÏá¿Â¯èþ»ð¦•ákÍWWžóGp— §§Ï¨IvÜíi—#aÿ–„0±+ñLÿ¾hš^‰i¿þ#«‰|Musu5åä`"&Ã>JªÆË ÆH’M|Õk?5­kWºÔmôÛg’UK+ë©®# ¹&“,U¤—–º’+žu•¬m m;›ŸÚ]ij¶ƒaý™£½ôYi€7˜‚4ýÜ„rNäñž7òs\T>-GºŽÆêoé Ã䎣Ô÷®gÇÚ=¿€"û5½ËÜ[iò•Û²á±êYFF?­fjÚ&¹m±Ñä[§¸Šöî(ˆHlZ5‘Ûþþ§ýõíSÊQì–:Þ—sع²’ÞvÉÉžqÂçô¯3Ö$šmIX >ò18Û^¡Du†W:dÒHuö B§,cÞD˜=3›“\Oˆµ)uKÝ3E³dÓíaÓT¨ËÈûžFgõqÜšLãÃ>4!ñ¡á/5㎬áGçl›s0*GUê¸ÎqUu}böEøye;I‡r±©)òÌ¥ÃrÝÀÎ@=)ß îgð÷Ž/|aáqæßézl:¤ øÈ™exîÁÈ ÕO¨cޔߋ#ÑŸÄgPðôÞÊú`Ü ž_*\äòzf‹iq_[à[+Ï<—~OÙ˜“;‹³Ü|b²4ëÍ[Vе«It÷mB}PI`à ¿)†àx q^o=ö¢ãŸ6÷oC–ç¸Àê}kÛ¼eñG_›PÑfЬ¡ÒâŠÍl¯dK;»ÊÿhR½ +ìbyÀõ¤3Ä|o¯ZO.˜ëoö)àµÎÊrL’­t…ÕÆ—§5†­nñ—µXÝG——ǸîÏÆVbbÁ¢‘YCýæI³)è1ÆcUu£áRâMb&´e”Å#?ï˜ „Œ‚Ã?)ë^‡â¯ë¿ b7Ïtš¶¥Œ<(ß<-ÀýàÄõ¤ØXæ5ÿ„w\¾YÙš™¥’ÀÜ›dmÙ€ö®/Å–Â=mZÕþà2œíRØ ŒûœñQx›Æ·º…å¼ÚsiÒÛ¿˜#™ˆs’O@TƯjZËjŽ—¥ñ#É·büØ* ûÔ\,Œ³n4{ÅD ¤ƒr”ÈÝê=±U|U~oØîârmØI±ýÕWþdZí-aƒR…àÔ`1Éo3Y¹Ü~éÉçœý:V]ŽŸ>%Ô+5ˆÎARJ°={PG<#µ:„°Z9óŽÉŽ öç½: ;Xô¶’8DwZÈcCÒfÏê+Fñu“e«YÛy°netÛ† ;íçL¼ÓçšAªX38 UÈÊŒŽ@íÓ ÈòW¹ŽÂ×ì bd^.rŹì@Áô®—ÃQÞ'Šmµo0 Ò\ÄÎÛ€ò:çÎå—-œºt÷Á ¬Óâyg »ž™®ßV²¼:vŸÌQn"V¸mÜàœpAZÄž(ñ±ñ.·ªø¦öGŽæYVde}ÐAúäT^ñ é†ÏÄZÊáŸ7»ng†Aµ3ǩȫÛK¹‹R6 • >s1PT£ðIôÁâ»m&÷Nÿ„Q%hv³‰" Ç%`¸ö݃@Ó6¼?,RÉ%åÛ«™ìÚmÀg攜ç·Ê+LÔ|=«é:u¥´ã±y%i ÿil²Ÿ]¸ãëT>jwiÚ–¨Î`(Q °Ãg¿ÓÞ¯Y¡¹³1i¶ÊåîäHÓ;N7ý4Öã=;áÿÀý{âî©ká/mnó[±Ò´¢Ïå —P”ª6y$Ew+ŽUOµ{UˆµÿÙ«ãU¿kj_ õV¾³Ötñ²+˜ílît¹-àY3µD²³¶|Ä¡°¾]xOOø•á=?âzßiºd:•î±+iÄ­ôwZX ÕOšG޹â¼ÿö™ñ£k¾&ÐôK’ÑÞj— ž]ôËeE øáYç%ûäóZ)XÎNú|>ð·ˆî—Cð¤Þf«¬Ü˜-­á·I¯.ŸsJ«ž]°òy<“_®×> øQàoŸõ$×ß-~ÿjKo ±íÍfæÞkeºC¼ùÊ’ ì!@'þŸ\üxðŽ£¡j?ÙWVZÊ'%Ž7ýâã’ Ü éšýð„Jk·þ;ñ›e{¨ÝèðøëLÐ5”ܲl]l|=¦E—x¾ßxÓjGV v 힦2G!ÿÖøQ¦|Aø›ð¿öwø¨&›ð³@Ö4ÝG^Ó’VdÖ/ »4×/‰|‘4â#Œ’Nâôcûcüøñ_áç‹|qw¡ÝiúÅŽ…}¤é«¦LÖÄècLSåž‹‰#‘VMè èù7ûêþ<ñ‚4­â…ØŸøãÃþÕem–WÞ£ ÕßÛXm´“jmO»ÈþðêÿˆÞ—áω´Ï„ÿõíËã»í3HÔWmFâ Ï ÛÞj¯< *»l‘-c><³€§šÝls·ªGóÏû8EâþÔxs↬ºèº/ˆµ=NI hb›PÒ‘í7ɼnÙ#M9V9 Bà‘QþÏ>$Ôt¯_ ¯?iýKQÒt-AÑôiä· ú†‰eÄö@`ˆïdP僷–Ä?—¿ã/x¿Hø×ûCÛZx’ëÄ? #¹¿Ó†-áÕ݆Ø#vòâIzÊùÁ®óâ¥ðÖX<7¦ø£D·ñUÍçÃ= DÕ5'›j föiޝ ÀS“A8 °¨íšÂ]Ž”~˜|Qý¥áøÛà=ÀϬø“Àž3Ñ|Aeáï¶«ara‰!K{É• h’ÜJðÁ+žyÑ3_ü,MáOÅ[σåõ/ Û~Оø}{¨NcƒûÂWFEgLFÄ\CpØ FΨǻ|R¿ñÿÇߊž*ýœ~&|NÓ`Ð>ø[D¿¾»¼ ÝJX3.‘§˜ÓšÚíÝ¥t!”mê_åøSãN•?õ}_àûso+R(a†H¤¶¿×fò-´ùa‘ŽY§d+&3’Ükhh`RÞÕ4¿_5ÿˆZN‹áÛíGö—ø•®x–Úê;6žÕ4 ̺múwX_ʶ·h]I$ÂMŠIUñ‚?²Ç‹þü%·øçû0ß^Xkz‹.õ=ZÉžIžÜØÜÉ×—ùæ‚`¬°\ sn¯óo¶U~xOàž±ðzÃÁ~Ðôiô= Á^Ñt ÛêN¶zP&ê+É`dšpràøFÉU¯Mð×ÄVѾ èz†›,pk‡¼ó};m |bgqBX i@ÆpF8ë]J-s3ñ3öŠøõáŒß|7ñ#à­¨Keñë_Ñ´­gÁ0´Ötk¯ êJ×ój+ ¦)ªˆÐ>¤ÐÛ¡Tn>°ÿ‚ŽüAðçÇßiþ>ð¾¥5ž¡áÿˆzg€µ»kNÛÝ'_ž%hñ5»( êK°"¸ïÛ‡ö4³ø]ÿ´?†n¿·þ'|Ô|â [‰]lä×4O-ì#hP fºš[¤„˽c~¬¹5ôíñ«h_c ö†ø5¥Oâ-[Ãþ7Ñt»{+€š~³m#ݪK¦Ý:aVKV®âXåƒ]ŠÐüŽÿ‚»xÓÄÞ:ðÏÁ/…ÿžk_Û|xM>RªDOxżsÆÝ$DGÏ}Ûr3_ÿÁU¾øw–z¦ˆÖWWþ Ö®e½¸BVîM#Æ÷^rC(#-5¶£Hß;Eœ¨È¬¿Û;\ÕüOðÛà¯Åïk¦±ãïÉ¢ÁáËÙ7ê:0‰ZDµŒJ±Hf’îZÚy¾[Täý#ÿ2Ðü!ñ.ÛÄŽ5H|ã_xÏÀÞ×ô½E¼ÛKÍ$Iоa)æˆÚî;™fwî]¬*#æÏ„w¿õßø%>½ðÚÛRk¿øƒ\yõ]$ûû2çJ½[”»I -ÃÄ®G̤)Ë5|¡ð&ÑÚÛR‡\’ Æ×>É| X¬/u¿*E•¥Z8B6yðAû3öWøçâ?Ùxøãà›â½2ÂÌüJ²Ñ§Ö¡Û&’4»y渒o-ÎY.­äXˆڌr3·ŸðwÄß|Ò¿fígDÑïb×<%ñ3\ñœBXCÚk–vk¨ÙþY”ª¬Aº¢0b`N u0þ|,¿ø©ûLëž Ñl÷NºÑõKU³IÚ4M+@…du¸?.R ‘³p3_7hš>µ…l?j ýŠ_ ØøóMÐa·¸¸óÊ\j1¥È@§H–ÞbL»¿ÖsŽ+ëM/âçÅ‹¿´Žþ!$v~¹ý¡ué<~‘¹6¶º?µ›$½·I #mhæ![†#`loþ ák_ðT‹Ÿ|§[ÛøzÉoeml ˆØiöÊ®ˆ00¬…ƒcN §•n]úq|8›ÅwþøðCá>‰©¼ñv‰àx­T¤V©áë—ššJòeÞyáy B¤´q¶ðPÕî?ðR/‰Ó|@о9xgán©h~Û ë‹]Õâ…4Û«»ko ép@œ´—Ó[Þmb% #v¹Ÿø&âüRý¥¿cß|?›Ä6žÒ¾é(ñÆ—â½PîþÕñ}ò]X5µüî ,ЫÆ%etØHN|¯ÀþýŠþ+ü2ðŸÃ¿ƒ¶ðçáï…tø«Ç~)Õ[95&tû8äi‘H[ˉ®YdBÊË h»U;F:u<"?ÚŸá·Á†ºÆƒð{Jy|E⯂ðQÕ¦”Ám¢øŽâ)bñ庮 ¼h–5ŠX€ËÂ×¶ÿÁ!g}kâgˆßǺ<z6›-í8m&Hà¿¿•âîà®Ó² .Ö_=É'˜ƒ 9üUø•â?Ãz&§qà9KeÖ5»½;ÍÿVö_h˜X¡S‚)…º‚ àæ¿³«]öJøMÿ áû6xRÚË]øgà3áê¾"Aæµâ_-»Èè¨UW™pìã#c ¤Õ_[åd~^xçÀ~ø™ÿ-¶ð§ÄlÞü=Ó¼)§é÷"ã÷Ÿiס¿’ ª#gcË;4H£ó Šøçã犠øÇûpx÷ö—ñËé|2ñŒÆ$F'cg¨výûLkšÞ™û-øƒöáðÖ•&¤Ÿ5í+áÍì—qýŸûGÄú`»ÔuFÉ£-²9µiÁYÂF¨‡•W?3|E×>þÃz7‹¿dGI_ê¾ñ.’ºBhQ’@2Ðá±·ñ+áÄÙ3Æ·ÿ ¼B¦âö=áþ­âÝZôyw2ëÑDuìøŒaLÞ6duÏ:|“ðžFÒ¢ñGÄäžK[n5ù­#ÀiÔÒÊQC`Kmöô®ëöÄñïíKñŸâ×Ä/Œ…¾â_ü@°µ¹·rA¬ÞZGgo¬K“ ±ÓYŽFàyÃ…üZ³þÇø‰â?ÉvÂÂÇQ¶Ó YÔÆâ;=‘0p?Ü6}TŽõ…W¡´ÖÇÁ·¾ ²ø£§ø²ÆK»«Èôýáôq-Ì6×—‹ö«Kh¥(©,®Ó"Æ¥rX·Šõ_Ú&ÓÅ^ñ?†~kðÁ>±á fæMZþÒBÏ&¯©ÃÉ ÊNYb€É\ù†¬~É_¬|7ñ;Å:lj‘ô߇Þ’ûâ%îd±Ig6¡áëx­tàÊñ—fºeà ³ÜàÃѼâx¾Ú«âµ­”ú±ãt½V+)\ˆµmVÞ]AmcŒ‚ÎÖð ~n $äåê]ûÇû¯hšgÄ=CYø~|aâ­k xa®ÇŸe êz”…¥Õ§]û%‘ ¢[a7 ‚Ñ£³¯°¤>=ºý|û|[™#ñMÖ»û˜¥½ÑäÝ$²¸; e·Vºº å†Ô Òqòõæñ À~:ðGÃ/YêÚŽµñ>ÆÏ_—G³œÇòk×E4žGÃÇ,VhÍvOȬrI kôgáwì)á/k¿¼gûKÞèÞ#Ô>[XxWÁþ±¹›ÊOëb;Õ·™$ò„¶žSÛùΧco“£)ZÒ+¸œ’>øÃâÍ?ö}¿ñ¥àˆ/´ÝWMðž…eá(„ʲ´ÝÌ­ªÜnËi|„xĘlchRÜõžmž ‹öJø$¯ñ_Ç?U|OãMH]”K)´Ñ6w/"ZÂ1$ó³’JmÚEJðÚƒD³ÐÿimgÆ!×´ïî½gQm3÷‰gy¤µhZ5f+°Ä(±¸ˆÈÇú“ð’ãNý€¾ê:F¥lڇňº,'Äz<ð£C¦¶¦6ÛXµÌÉý†~~Ë¿±¼—?¿ifÛÄž7»·ìIo%ÒxÁ-¡ù¼¨€ù|ÉëšûÆ¿~ê^2Ô¼sû7躥ï†t+/ìûÛøç{IuÝw\}íom‘¼[Ûgæ(<° þï•? ü5ñ;Ç~8ð'¾+agä Œ@!O$õãƒûF~Õ¾3xæßPñ–‰¬ë÷—z—¤éZt–÷67–¦^d¸.§(îY£C¸±ç¡©þüdÑ|Oñ«^ñµáë]FëE¾Ñ’_N«›>FHS3É¢À5Qw?D¾xÒ 8m¬~èZޝá‹9£Ö¬õ3-†š5«³ûÙåCû˜§ýã¬|2œžEIû7x{V»ý«þ0ünøµ8Ö.Éé–ŒÙDÆäL¨¹;w¯c¼ý¬5_Ÿ4ï\Aáÿék¡k¬]^ÞfÒÆ …-6 ÌL{ˆeTT¤89¯ÏïÙâÿŒþ1|øŸ¡xÁZ·Ä+SÖu{½BãKŠ;+6¼ºi×s+FƒÖñLaË:)Bà6Õ4bá{Ÿ°´oí‡ð÷áÍ¥×Ä^›â³º‚æåO!-WêÅ>`¯óI·…QÍ~7|øò>$é¾>´Ò·â _Ä_íu}bûÃðÊÇR±ŠÖ6Å/F¾` »Ìâ<.:WÇRüg×¾5øÂ^-ø•ptÿØØ®•s§øyÞá.-—2™®¥Á.²±cŒg$Šûwö8ø÷à…?²þ|Ò®nü}%Ä·÷Ú]þËM2ÊãQ¾’U¹$àÎZØnP™Œ6Ò*¹ÁC—sëŸÚÏÆ>/|šïâöàº^“áÓdºÔ-’ ……Åú¤‡CE@)É Àׯx'ãoÀ¯Zê×u¯üB¾v¸’=&púu¢¬aUd(…‘±À$®Þ¢¾:øËâYiÿkÞ?ð½Î¥{q¬\I¦.»ªÄm=¡V‚kx# GºìVÆdNs_rè~ðÖ¨èþ,×!û Ì2ǶwegÔZæ5òÜHE.wÎÀ«ŒîŒåá]K¨éz^‡i}£ø;J»•&:~‰n ííÔ¿1‚2KÂÒÇ$óÒ™ðwáÆ¼ž9Ötÿ½Öµ§Ùe³]JãÏ–`«•cü;œ‘·¶1Y«7ŽeÖ¥ø‡ðûJÄÆš’Ea¨OnÑ­  –x¡U æÄ’HàsÖ™m¯j~ñƇà¯Ý­‰šØø£S”bi\´qd É)ù‚‚ØjšuÔ‹nº~£°¹·A•…0YXÄŽ cˆ-à ^\É1¾Ób’Þ5X»œ$h‹=8Î+äŸø³ÅßuOGø_km‡lîÄ ¬^ÎR—n¡ƒ;˜ŒàpzÖ…Œš}´÷žñ¨º ð´Q5«+ÿËv]ÄGeK(=A=y¦™ é©üFðEßÃ{{ |Åa¨yˆÉ’6ÇæÉ_åë^_­xÄøba¨ør+>+Ù#Šk©€>hÁäç[^Ó×RU×µ-:µÞC âÅ"‡xã”á1ÆrÀe½3^ûª|<º×Ã\Òï$´ŸO}ÑAp¾|!Ç<äãœñLç¡Ö5Š·šŽ°²#èÖ G0 ³^1>f;e½{ÎõV#9æ¼sá¬Þ/ÕuÏø›Ä% Õþª-‹¡ùBY Œ€?ÞÝø×¬½…3ܶ²Ä/4Òà¶JªîÿZ®Î àÐAi°*³ÈPuéU–vfùûT¹#4]£î.Äqù„nÇl×o¦\kÚ€¾ÕóöhÈ0Âz;ŸjÙÔ.¡±³k˰Æ5þèÉ'°ÅV´Ó¼U«!’wM:_ÝÆ£ÌŸq“Óü(ºŽÊ9ÂÎ@ ϶rº‡Å? YÍ&—¦Ü›ëôö±´ÎHìvŒÄÖ}÷‡>!ºghsIrTɽpø?Ý#Œýkд];FÓ xìâM˜±Yc˜¬RçÞ‹Ç«4T™åö^=ñΰvEem¥LN/ïaì»@ýMXÔ¤¼ÕlþÁ­YM«´ÊX¡%©`pz`‘í]‡‹5Ÿ EeqiâOH·b4³ÝÂ¥;$°+ÏB8¯Š|[ûPüðšø&óŶþ%Ý#?Ùt‰Y@@[`ö'&iԨћÚ,è¼Gàk‡zü~-}*õ›aðéÒnkiXð<•#ÍSžs’^·Ô¼Oã-µŸÚFÓ¼]áÈü­*Â/4Y¸âÉÈ/@’#9 sšüúÕÿà¦_ôoƿٺ¥¥Þˆà$ËoQ4™·Ê~f#®sƒÒ¼ÆŸðPO‹>¥â :oËâ9Z%K-9!Ž'‹¼’]¹Áf9 ÏLV/Mk̾óxaj·ð³êÜ龇Oð¨ñŸ/¥ßq O¨I`šçA½‹qÀTã^g¬ÿÁL?fÿ‡ªÞøkãO|Jø…±mcðö•¤>µwöÖ¢Û¤KkT;ƒfS÷2@ï_(k¿ô ~ÂêÏß ¢°Ö5˜£®\ÿlk¬ ÎëTpÉÓÈßäÏú¾¯¢~|yý°þ i?ið/…ü9m¦ˆUy šôó±{©±,¯+¬bß?@Æk†®iBÍž,®¼•ÔMmÁŸðW/ÚrÆÓJø“â¶Ò4{y'…4¦4L9Òê73:άöðGÈ8ÇAô…¿à‰¿²U¥Øñ¿íUã?xÞþx[@_]ß[¹9vgËá8ùvD#B:ƒšð_Á[¿i˜î.$‡M¼€›u‚ÓIaj¬¼,’;®TðBŽõñ¯Æ_Úö§ø²g×õ¯^Þ\1Å­®•vvˆGü »=ñøVk7¢þhòlBÝ«£ÃÑþËO xLø{á‡:_Š5VÒ¼1 øjÞÒ ëÕ•^PcX€mØViÏ8ÚÌZ·~/hi> [ø¹¾ÚÚÊ‹vû­/5;™\s nÅâ…!ŽÕmÝÙ@¯ç3Á_¾+xßX¼ø×ñ£Æýψ´é|‹kíut<Å!V9hÔ´¬aG%±’MPø•áÚøÇÂúÅŠ®îÞo"ÎòWk˜‘9i¦l2Gïš?µ£k›,šI.f~Âm|‹Ä¶’xßö¿Õî,à¸Ùå%¦‘ Æ„å¿y°2¿/˜c,{f½+^ý½¿àŸ? ôEðŒ?|wño3ȲÐtiæ¼v H <ÖpGöxóË<’(Á¯À=_᮹¦É­øÊÇF´‹peÓôÙ’F”ÈËòîG°¯¢ü{á«]µ?ü/𠽦K‰o`Y\&Öol€1Ó Ë[Yâµõ§ÃïÛÏáÁIà/øB/íÍÔÆê†K]6Ȩ"ˆp?3:–vÉÎ1_‹ZV­â‹Ò]x;ö|øa¦<ÛYußß”Ó- 8É–8ó;çîDœ’9À ֟¿ø'Ä }fóWø£â{MZKˆv)†ÞO)XðØûD’mϵrË?invG†ÛÙ¬¿พð†”¾ð“á}:âÚ1ºÚóQ[ë,òKFQ3ÔíÖ¿6|Wÿ…ø¥áufø}¯xCÂ6~RÜ^„µYwÈÙÝå#HÌ·3džpk7ÄŸ°gÀMCP‹E¾ðìzµÅ¤L@!رÎq²=ªG¾Þ‡šÉñ—üÃödŸáÕâjžÓ¼;)Vg¾0Ÿ.ÒVS™·j㎸X£ªÒz³ò]þ"þÙß¶Š~þȶZ§Â?„7Q›MCÅž"µ[MBê)I œ12É{~Q±•Ü¿?qüøGð—özð=ŸÀ߀±Lö¶h~Ó¨LíæË6G™<û@Ìm "F"ˆ¨î%»ñwŠDÝÝÇm½‚„ ÊË#æ@0«ÀWã]y< aÿõ[»·ÂË ndÁÝ$’_—„'‚qÆ+ɯ™bkÉF#Ó¥ƒÃÐWv¿sƾ=Ge>«¦³5ÒÍŽQs ¬(’0O–<æIö¨çÓô¯ÌÏÚûÀþ.øOð)> jíe ÙÇ4‹k=íÔª…Eþ]­&[ r~ÜüEÕî¾-Ãã M>FÒœZèÑ^Ê©¤ÛLR]9b¡¤ùˆŒŽ[¶+áÿÚ+OøE¯~Õ–ÒøÃYƒÅw¾¶°Öumnâá6»IKCal0Ãn5’@ƒä#,Y›#³ «}«œØ™Ñ’Ðí®>øWSøiáß~×7_ðèZž–žÑd¾–.È×}̶ñåŸ8Q&J«cåÝŠÁÒÿg/ˆß¯&ÖþêÍð÷ÁÂÞ5¸™x®o²D‘#Ã$ˆã!å‘ö ŠrXsÿ¿jMâ—Œ,|W®üIðÞŸo¦Ûé×2‹›Hf Hñ†ýü›†P‚ {L?ðU¿‚nm¢øÏQ:ضŽ(c›L¶ti6Œ4žTŽdaœóÈç«IÒÅkÈ®sQÄЄ—3Hô]àÄÿ ÏðÿQÖvQ-”ÚD-âKa´Fì}üŸ— g=½à¡à_^j¾ ñ< ÚÖšd‘%y7 #ÜUÀR 9Îå`W¸3¯üÇö:°½¸Ô|3áÿêsM€°[ié ¹=H.sß$õóWÄßø)‡<[ã¸>"xឯaq§ªúŒ¶Ë$ nÈŻȠ1cœ¶}:×#Ëqµ®œNïíL4´ÞOZÜÉfÖ6Š#µ‘WçÛ‚BwöȬø3Ãw÷âçËy.Ø—c±Fx}†kñ6ø+Wíq‹=áŸ* ÄM6§8r=Õ-ʦk™×oÏÛëÄ®ÏáÏ hºl‘1‚I¥äu›o·¿µb¸cßB¥Ÿáâ¯sÿÔþ>ü[x |Døwñ_â7…4]Ä0\ŨÝé©wk!.÷vëæÛ#wÅÄ!¶³pN9À¯ÁÏÚ¯áö¹û<~Õ¾&ð•œæ Í[¸Xe‰z«s a¿ˆ$ÛX ׉c4Ú5ã^Ø‹ˆdi#¸‚{y ‹yá`ñHŽŒ§(à2r¤dsYüY≗røóÆZ­æ·®<ˆno¯]¥šB£ ]›“·N0QØ©Tº¹õGÆm2â/ŽñøÁàò­<]¢Ùj IãÌŒyS¯¹@=°E~}üGÒäðÏŒæšÿ÷d‚Níªpê;væ¿A5ínûÇ_²ÇÃЉ.<5¬jæFvÅ}ûÈúgËP¾çògÆý-%–ëWU]Á Íƒßpæ=‰LûÃ^ Ðþ8~ÊÒxjîí­bÐZͥϋ4ƒm;·ÊºŽ‰2OiF7à»/9ÈÍ}ùñº|3ñ”ntç¬î4oÍ*FhâÎúãåÉoôg!ºåWÍ7'aŸÏ?‹ü'âmÚx]âT×­ï´ÆYäýì{“¯NsNRºOÌýŸïRîK ÜHΦRÀ)^AÈ$ÔþñÇÄ?…úÕÌ~¼’¶‘ ’A4R¬l@Þ‡Œuzä>=×…¾)K êxIStãÛ6_ujõ?ˆžº›ÆšŽ“áá<Ú†¹q YÁl,²ÉxFÄŽ8Ággc…U± ‰ÇÞ°Ö§Ô_¿m=-Piÿô“§+…Y®ôÿßBÇÆKgËzawâ»­_áÇìÇûD‡O’ÆãPŠ=±O£\ùWDN@xXì#ÑJs_–:‡…5¯kº&¹aq¥b§ÌµºF·ž#!¤‚P’ ès\î§xwñ?•tí(v² u`Žh>ÏR¹zŸWx³önñGÂ}N]WÃSbÅD°É§“q >q¸1ØÜrH#é_*AªºÀ,»xÀÁ¯aðßí%ñV{QáŸÝ®µ`Ê–¬·£|‹;xpC=[q=ëƒ×¬ŸJñ õœc*Ò—Bª¿={Ö‹™|D³š·†Â²·.ÌÍÐGÇ>õé:•Õ¿€"±eÓm|ËûUºŠâ`Ò1‰Ø§§ô¯#Õ‚‡K‚t8ÇZúãg†¼[gðãÁš—ˆ4yìšÆÈÚ}¢HØ,¯¬9lÁ'j„gk&Ô¼Má ?UÕ¥e’ÞæX&’/Ý¢®>^ã¾µâz›¥ºÂöÒhßxÏnxí^‰àq6©á=cAvÜ[ʺ_f9RsÛÏlW™éºa¾¸É‘ŽdçŽÔ££`{Ä ¢¹¶Ò|D$6ä9VÜœcÃÞ¸ÝÄöZ7‡ît™ y]î|èòÒ¸Á¸ÁªÚ?…õ;Öcò‘!îÄEõ$ó`:×]£x+ÂZޤúuî´¦s›C":€r?Í0<â ^êÚeº´“Ël:m0ýF:÷é[º?‚|In."mí€ÝçÜ01ë–ç¿¥wZ¶¥¦xVEµ¨ù$K‡RóŽFpI>ê/\OqñBî ¹L¶Ò”$ŽYvÈ™å‘Óµr&ËÁú{,z†¤÷Ímã*€“ƒ‡n¿€¯yñ Ñ<%6±ðÜGo5š!“xß6ÓÔá³ßŸJù6HäH€,àa²3È=³Ò½Q™¶{¯—yl…ˆ%O+Ó?Qô¬¤€‹Æ¾ø‡2Á®jÑOsÔbhåg)R3Æ0Ç`1^G,3[Êa™JH¼Üû3Àÿô­Àvþñv%á°O*Ý uT!s‚äÁ½Ysôí^)âÍfÏÇzˆkm:BHH–FÎÍ!žÝ…k hÅmnvŸ þ=Üè6£Ã¾6´MgNÀTóFéâ‘»ì8éZÿć–1™üu=иÃyÂð<9ê¬Ïò’=FkçXô{mGý¡”[Jm£œô彪²ø¯ÃV!ެ·LÇïLB¯×ŸÌ⹪aá7Ì‘¼j4z<þ>ñ~¹!²ÒC¢) lËJ¡aŒÖ­§Ãojîu"-–^­pÛä>¾½~µØ|2ø£àMU?²/z¦Ԡ[iè‡Ý'Ô×I¯|Yð¾‡pÖ¶÷Pj/Óm‘2?ïp÷®Zœ]£xÔMjÌ]/áχ,æYuE’åðnÌ`ÿº¼õõ¯P6šE½³D&‹Nˆ)ù‹@ø8=+ç½[ã_еlEáëìà 陦lñžxéÒ¹Èüã_Üý£^Ê!Œ—“Ïû'ŸåY:RzÕ•Šæí©ëšßÄŸ è(Öú캌‡ømrcÈí¹Æ1ô©>|zñ¿†õyWO²KËiv‰mT`¸=‰P@>•±á?‚^„nÖ.¿´%"Äq(> œçÛ5ÓÞë þÄmþ%EÀ¡äcÜ‚1ÐvϤ ×,ÁIÜô´K½cOo¾q¡ØÊû‡Ú¼©È-ýÜ@÷"»ÝU¿³š10òä´hf‚êÖWµš6‰ÖHä‰â!£–7U’9ƒ#¨e €kå/ ~Ó6ºªðhzuÖ¡dåƒC1ÞdB1ÂŒàôÅ}#á]/Zø¡Ýø³Âz.£ageµÜ^ÄÐ|ÍÔDT³:¨<œ`¸®)Ò•?y«#¦2º³?¸Ÿø#ü$þ×ÐÇû3þÒ“,?4k}?P`ƒÆZu¦2~]±Å©A¸cPªäùЀ†X¢ùÓþ 3uð¯þ ³ûWx/þ ðrigøeûG]>ãÍÞ?2ÐÝÛ@×ë6Š`º…c“í1/ä!¶mOJÕ/–Öe‰ÌÈ$†h¤-$rŒ…x¤A;]8ÏTïñǺg…,|«kzÕö¥ÝÏsg¥_ß^Íggwx\Ü\[ÛÏ$–ñO)w2Lв6÷$’Íž:Z4ëʽ7neªèß!8IiÐþäþ'xbêõ´O‹ÿ Ä–£¦"Þi·¸0j:uÒeà +„Ã!讪ÝjézæƒãÛÄš Åí/Õü¡6Deù^'S‚$VA¯Àø$oíÛið×T‡öCøË¬D<%©Éø^þyvÅ¥^ÌØ ±ÂÙÜHB¢ÿË Èyr*§îÄo'õõÍ‘m Y›ìÚ²(ÇØ.›ˆ®‰ãËCþ®^@ä{W¡dÕŠM£É>;hºîð«^½ŽÔ½Å›Ý[yxfY­œ¤m'©@x¯À/ÛƒÁ>$Ðmß‹—žÑîF™©ø¦Êö+øö¬I£XÞÊK¸ƒ½¸U9Ï×ÑGˆ|+¥éïq£i s Ío4Ey$VóQ“î1=3_Ïçí³{|hÏø³Æº¥å†ƒâO‡Z¥~lÝ‹-þ–n¬f¹•ÝU#Lõ+€NvåÉ]£ŸïÏ¡_‡¾7IüwñÄb𕶯eá»kˆbò¢q»ßÞª>@ËTåÈÆxÎáŠýJø3ñ'áÿìMû#O®_ü'×añ~§a'ˆu nöøÇ£{,ÑÂÎÅå¶´[vNWÉœŸÄï„Þ/Óô߇~6ðMŽ“.©&³¦ÞøcK[¦òÒÞïÄ7~[ê2@¹T² ùU~` #újøûO~Ì _Að Ô¶W3x'I)áÛ«»ùö¿ª}íçŠqtn-n]±C+º UH"‘ë?v-žuóTŒ_VzÃÚA¾$|ð_Ľ;À$Ò´?Z^ϩ˭·ÚôˆµPË.Ë+¥‘¥û6UÕ$`©&ʇߋ|YàÏÚ/ö©øËâ½/âî†ÿð€-Å­­ç‡"¾(ºjJ#Hîúj|óLË*¹B¨£)–û¯ö5ý ¿f~Íüñ66¹ð·ÁVRËâ)nôÆÒ¯Œ‚(-¡),Vð"#rQUpps^¹ûr~ÕŸ³_‚¿g /ÙwI›ÄVÞ2º·Šêïæ‚Åš¾dBT%‰—*2vprMr᫪ÑnÇ¥œåŸT”zI\þ\u‹_øUß>&|)ðÒ_鸚¯õ+„YŒ1.ÕºÊ Ÿš"Wï„Wêp7¾|ÑUd`W®rr1Ôb¡«šYî‘â'Ð<[m­è­rB!y3¶ÿ=TŽC.O=9Á¯¸ÿgO‹´uÈõ]ĤIhqn‘ÁÜ·´†<.õfÜÂL¨çpàä WÁÚ>• W·Ú (®#n‘Æÿ»ÜÄÿvFß=+ôŸà†‰i¥ø£ÄÖçS‚¥èÐ-Ìn±4—ï¾Vk}Ý<…$§ŠÚ”m©ÏWÈû»þ ñO~Êñ|$ÒõH¯|tN’Râ×i…ïVL†|¨]Ó Ä®>P =…x¿ÁŠ^Óü)ã(r]üÙ'žyq~ñ·ˆ$Ñ~)ø‡Ãzµuy£ª¤qsþp­u—De9fÄeT’IàWžÕ¾#躕·‡4=ç:,‘j1_Hwª°c—#ôæ½cAø«®x—QñŸb‚þ}RÕ"W)ºT–$ ŠIèŠç©ÆzÖE¯ŒaÐu­>ëûÂÛ([¯8ŠR¼œŽs»¡ã8¯:oSE ¾"ñW†>/é^+ø›­Aý“ññÇu µ™ò­u 2q8q€ÁåváÇn ®¿âÄKàÍÄ¿ue«xš tÀŽY¤šwEy9 ±*ǸŽNQÇ€|H´ÔµõÓììÅž’÷M?™jÈ냓‚rÒW®9â¸jzg‚>Ïámío-öÕž9¦úªœäÏQÉÍ•Ù.'Û_~|Wý¬þ!x2/…,Þ‡[Šâho|Aö:zÛY—–ᣠßjqþŽS*Á$ õÏ Íà†^=“ÄšŸŒ&Ò¼aàÍMÔ,¬d‡ìÖ—6ÂM—ÉX¹HØ·˜Ž– 5›û?þÜŸ´Â?…?µkOxBóBN›câSɳXPX¼q»Œ|‚9X±ëº¾ ø×ñgÆž3ñû|AÕTí¾sEh<¸c]Šª¹Ü©ã$“]PÓR$®¬~•~Ö|ñ£ö¨±ñ ÍÕ¾‡£éFÚÊóDrÖŸòÎHeœª™NUÙ@ ÷èkÁ|!wð÷]‡âZøåcŠK«¼=xÃì×Â;–”•’@H(Øó2˜â¾oð†l–÷Á>(ø¨X|:Ö5»M/SñÖÎ-4åÌN\¸Â˜~ûãb±È¬o‰>д/‰þ" ]üDð7ƒ¯Å²ø·Ã—CPÑob¸(ÖòyêÄÄ“£˜ÊŸ”JŒ›ˆ™:’f|§+àßþO4 ªjzM彜¶ÆêÒå­áÔ •ipÿ.@có'i5õG€~-hÞ¾Ñ>#øÀÙø^Î=.ÚÿCƒM´hä·Ô-”,ú"†óT®7*îÀëæ¿Sá·Å…Z§Âýz×ìºæ‘¨¬EmÄ’hÚ…¹M²™‘f·rÈUr…yµwÿ´7íãÚ_Zð_íñC_ÐtOø O°ðݶ™ƒ}‡Sû‰¾Õp 1ýꪆBNÅC¨µaØõ›?ˆÿ´'Ä(õoÚoÄl—šg‹5{íÝFü½6Úúíc³Ù'i›hVHÁÃÍù¯–îtYn´ˆ|j&M#Ì×o,ŸL·KgifÛ`¸vRU„Ü0‚`óšíþ=|Y¸øð¦Ëâ–—áØ|#мUgs{c¢_I}¦<¶a•/.SÍÚ°ŽˆàgÍuûõêí¡êóA ÕÌ×\"çmÊXpÎ3Ó3zšE+j~žþÇßµGо|]7_ïm®<k§ù­½²¯Û¶¹h·“’0ÌÌá, é^¹ð£ö†¿øCâ/øwĺa·Ó|cãí[Á`‹u*é×¶1½ºÛDFüâ$Œ¾à@¸¯Ê øÇìZµlÂIµ¹Í°°mÅJ92îQÃ3™Èš¹gâ«‹Ý_YÎg¾H㉥,CäO÷•y°­©Uk¨¥úOø ñ'Âvúõ¿†&ŠSªk}ªá`ºY#µ‘–-ÕÇ3Y¨!NyÆ}ñÇâ^…ð£Ã~!ñ}ÄÖ|Q¢´²FÊÅRÎ b„g<M~+~Êÿ´ÿ†^,ð¯‹üe£ÛG%Í”)¦j±£HÛ4¯ ÊÑ)]ÉRì{Œ÷¯Úóö‡Ð|kâ}áž© 'NѯÚûU;{iÊÛà\ncèr9¯Rž"ÐÜåto#믆õ6Ó,üWãé4¿éw—`Ûiæä5ÈûA/3~0W]È3—ç8¯¦í/ìu'·µµ“Ë‘OÞVê8ô#{Šþcþ4üz¿ñÅ»ÿ‰ºmSP¿Œ•^;g†… !•2r>f'½}'ð—ö‹ø™à(mu_j7hï?Û® ºsg<θ_•þð0Br¤Œqšºxä¤*˜vö?z¤t…A'‚BñêzT‰†'=€Èô5ùG ÿÁAl¼k“B³e¹KËaUfŒÛ!‰ÈÌa‚u¯ÐøûEñ•öˆ'V¸_0̦A!yê½@ˆï]ÄB[ò¢ÑêGŽsþ*nå<^FŸôÝkW‹Gðã Ú谉ІCóž?»Þ½2;•1çÓ‚Mo™“ƒEÒéJ¬Ý*¸“×¥H®:©«åDÝ–¯éS&1š„KsóQa¦X¦¶ïá§Ži ­1¶9T‘R*±\´È›cœ²w9¸5ðæ¿àøÇÇ7|Qâ]@Òmm¬mn­5)WO’ð’n¢Ó³ ]±)pã$ŒŒ^ëáßlÆ8sjÒ 1&Ùœ…ÎûäSžU u|`ÖþèCCø•©ëñM¿o5•þ›á¢°ƒO´YGØÕ‰•ž\,r° …9pñOŠ>4Cû2|.ÿ…cû?xþGr-$Ôôx ²[=oEšu/¾ ©­è…eû4¯‚Ò$o-Ž2’Ô¤î~zþÜׄu?‰5øÎûųèÖÉi<·ºtÖ7Q}–hÞ'ŽIT öò™w-íœ ÚñgŽ ý¯u]wà^«âº–§©ZÝNšöŸ »ZÏhñ¸¸Y­Ö9>} ›¥ëÙÿkþÊÚ·ÆٛßgãèmuoøvâÆK -F{}Í*\E·d½“•T ÀýûN|ñßkZÂÏO¬ü,¼Öm,üuâýsB‹EžÃL–U[ÉôÓ5¼p]E§ÚKçydÀˆ«ùŒÔŒù¯Ãß³7ÆïÚ[à÷ÄýÄ>=¹ñˆ®ZãD×ü cq¶m&ÊÝüÝ?P±ŠòR¦B8ÄT«´d§˜ÒG¶¾ âÏÄ¿Ú_áw€>$øoXþÍøÁû?ÙM®jÛ’Ö)ü?ᆊxuGŒÆ÷pÜKn%Žeæ7/»ö§@ýŸ<ñö•ñWŠ?àž:.‡mãŸÙî[M[Ìm­~$økRŠØ›‹« »-®nŒo£DÅâ,»"Åÿd¿…ŸðOÛ[þ ãñÁßµ_ø/Å~ ñÿö&¯s&•¨é*Ó$„êФ–“+Ä8ófBZF9F\º\ç×âοãxŠïâ6±f÷αªÞj•µ OPºy¥uÝŸõ×2íOBp;ûÝñ»Áž.ý‡?à–&ý—n/-õ«¿‰>ººÖÛÃð­Û‚·?è×zµûT¾d‘#Ï»+‘ÇâÏí-ð»Qý–4Ÿƒ> ¹ñ çˆÏˆ¾ø7â~.ÑK_¶ÝO<¶qìP$)ú®$uM'¡hètûŠIôm£k(‘ã[³ÎÃsdŸîdúê~-éÞ7øA”I ­ÙÔb‹Þ {Ö1«g,&h*:ô¨µ]øgÁ>"ð%ƒ@özåüW ;©’Xå·*@ŽLü¨áW(ÖÉÏS^?ˆáÕíæÒ’É-µ³¡c8Œ´/žþð Ò¹w=½î,4¿ é÷z7îukGg†DbÌ@8Û'';GC׊ä-¼Gkam}â-V'·xîÚîE„ Ùd1ž¹b[Û5ÏøÄ±êKk>†¦Ûf#¸<}IÀÁëÇJå¼lÐ\Ci=´ä/™ÄmÎs×½CÜ.Cðˆô-+TñúœóGi¨ønë2±\»,ðÆëÔ«•Ã:ZÅø‹g§ëÚ~§v)u].×Q–“"Õ¯¹òñÐ(Gø¯%ûMØ2j–îs¾&FncÝ+.Guã‘Üq_F|dñnñH¼7Gi£éÚ–-"à­Ê)lvòVBí·ž£¦(èOS˼A­Ë¡i«âMny ºG4®Ãÿ–2=hõWÃèúׯØe?`ë°Ë>…»OŠTÙsiÈQ÷Ê“Á9ëY—7±Û^Y¯™s™ÿvȪ¬%<+Ýwš^³ ]Ù.™åÜÜÁi‚ÒL£™ðY®I4ŒÚlñÁwdé ÝŒÑ[À³ ÆIçÔf¸}:èXiWO#&…¸SòŸ”“ë]†­¯Lê—¡ó>˜óÚ¬axÛÐmíó5yÌ÷vðÃip±˜Ý.Pòwþ4ôØõƒšÞa¡E Å—œÒN «ueDÎÃè÷­¿jú4…åÔ “ì­rÌ|e“æLöàã¼Ö6>·yZêÚG’^{1ÈR|WEá$kM}:ðˆ`º¼i `îUV-‚Ö‚ŽÐê·Z•ωµý5ÚÕ4ûCýŸ±·:ÿ2dþW–Ï­lð š{"Â×5ÆÜËq¸œ+ʧŽõÒëMg¢hF , ap’Ÿ›çs+|ß^+Š×¶á"•æ¾»*lÓ`1…Ï*I?{àS¸´9- ÃGþÕÐï-"š ›Œì˜eY<ØÞd#•8rr=kï~Òþ3Ò¾?xSö’ñW‰®õŸxWÄ ©BòÁ‚-;M·û ŽÔ*öF•9˜2¯Œ4ýadÔ-n¯„^e­»Ä‘!Û™"èížÞµÖé#R¼Ò¯á»€ÃolŒ ‡a"6ýœdã½56‰q‰ú+íǪiw|uªxŠ}Bñ‰´_xi5RIÓUÒ¾Ë/Ùç*¬"Ûme¬¸ØxÇÌ~Èý²ÿj 3áWÅÛ¯Ú@·Õô/xËá—‰!ðüðù3ÿe˨ý–Ön‹GóP¼˜Ç%Hê+ñOáï‡üñ‘5èþ$\¼7rhnÒÇÁx#Ùƒ‚õ`ø×â׎~!|4¸ÓþÕ¸ëš6‰öðÝwhJÆ? ì ‹œ6ÞG-žˆ×¾‡4©«ÜûÇIñuÇÇÚÇÞ5øƒ¢i3iýŸæÓ>Éu.ا’ÁV+ ™ÝúÝÌ^`A؆<ן~Çß׫Íw,cÈ/‹“AŒ’9XU²Co;|Ûö™x~|_ñß»Ȧóï~èz%ªÆDËÍ$®Ó±ïX‚’0àäœ×Ñÿ³Ãø‡ö{×´¯êÚ¿‚4‹ú­­Ö‡âK&·ŠãX–9“Ã÷¶(Hi"Kbžz¾È9]È 7óÊÌmòÄn©ð«â-ì‡ðÿÂ? |+gãKâ…ÍçÄ ìEö¯†ã¶KxÞ=ßhŠâÍöÜ;È"·‘™·yl›<ßö=ø_ þÓ¿µŸ€¾xjþkíNâ ïõ¶Ô-þËm Þè†{F+‘x‰2[AæFÌå.¬Â3ëkö|ø!gcÿ þø×Ã>øoá M@š0 ݧëût¨,ЍeTacu4°p¬eFa»5ø1ðà÷‰~þÝÿ>3þÊþµ“þMWâ\á÷i%—Qðö›­Ûiú…½Œ ¬p^‚7X Ç&L$*üñöûsûO#îÃ_¶ßì7£øÔÁ¯ÃñSÀR­´0xnöö{‹ÝRÕdAqºè5¡’áD‘TB›Y#ôûã…ñvO±Oâè¼=7ƒn`¶€Üx~ÄÁ{a%”¨žd¹s±X4«ƒ$¹5gÅþ+øKª|4о%ü±!Ó¾*ê6š¿ˆ”·›,A*8ÌrBd¨À:¿ÊqŠè®¾ øÃözðî•ñ›àIwµ¨Koâ\ܹ†kå,Ë&™$Ä­¼¨bÚ`8†u=TŽz]Œ†¾,hø±ûKêßõ«›yôÚKáþ¡ám>óíͦëÞynlœ43\%Ä÷jc!ñ ü¢¿:¿lïŽúÏÃoÙŸÀŸ>Ík h?5O 7Åô¹ž{Û/Åž խ຺Šr?{$ok$W¸‹÷Ö±ÆÀª¦õŸöø7û þѰè^%Ð YxÓâÃ¾¹ Yøÿâ‹ü¢VgÔîo§ÓDšu¤·[Þžh<ˆ%ÜO–ÈHÁÀÐÿ‚¿þËtŸˆÚgÄéz_‚~éÿ…“oiáëÒñO4—Ö©­k6ö\,7Vëq åCm;™³õWÀŸ„z׉~:þÌ_5¿Zj^ðïÅsAÒuë²kv7ºMƪ¦åFà“4ðV’" •b› íÓÍã$™øᯇþ.øWáÆðÿŠtˆ"¼›TºÐâ3J¬°êz רn¶ð©“‰2|ÌcŒõ>ÜxöFÐ>5Áâµ­{“é^ÒtÔ¹I­a·½v3ZW1K6Õ‘T±Æå5ïÿµö“á‡z:ø¿á¥œimñÏÆrxkV¸roï4ˆÍÇÚe,êðG¨ÂJ7*@Œ·ÎN|£án‹­~Ñz®“ðÎù"Óü=¨øÅ Õµh%ñ[ÜŤ]ÏnY¿†Y¦$#*gœW;“¾¦çįÙÇþíµ}ûé-/‹5»é¼1¡4®¿gd¼¿‰.µŽ&!P-¬›”œ°Tç'¯¥~Ù^ø9ªÿÁU‰áOêV‹$šÏÅO"óIÖîo$IG¦é‹x`–òjhˆß~øÞ+ÁZåíûÏ~d»’+Ïj~]óNå¶™%ŸíF9ö®Õ rEQ-_Cê_[xÃö®ý¢?eßø%€uË›xsL²ðì–zEq,މÃ_jS68ûR++•@ -º+ைÞø³ñök_êSøŸÅ¾ŸÆ’k:´h³Üêwš£XGq.K$vÈãnV0Î1_sþË^+ñþ‡ÿøáñÿ᦭¥ø"-RÃ^m.êh_jz=ÕÈÓEž¾b,WwGKÛ0ÆÁÂ0›+ð7ìñ?@ø+}eâW7ZÍׄ4¯ùíšM&÷ÆWËk©LŠÀšÉ|£n¥yv}ùÍ&ŠŽ†Ž·§ê.µý4í¡â_ÞøÒÖm5VCqqªÃ¨\Å}qy;±Ê÷i [ ˆÄ”ñç¾)ð÷Ä?‹_´w< á"óÄ~%OkÖ¶VvÛD·…þ¥*%º+Ÿ™£u(£ Œ· Í}ûYxÂ?²OÄŸƒ|á×´Õ¾jÞ£ÅäÙ•´ÝZCý˜³Ü ±”f;0|€˜ã)ŸFÿ‚?|6Ôü;û`üø‘ªiÓëwÞ"¿ñŸŽl`ˆù—É¥ZÜY+19Ü{§¹FÉbª šÅ롪v<×þ çû6¯í;ñOâ7ìÅáËR×|Gk§xa~Ût þ̳k¹‡ˆ.w ¡»ò!1Ûd°ûD{¶•VÏ/ñã&©ªx¦/ üÒÙMãŸ]ø[M² ZòöÙ†´™/fÌÒ7Î';V¡ýþ?üAø û1xTøu§Üj~<øÙ¬jwþˆ[îky­Xu]R[…# hÄq)òAÁãÄa?‰? >øÒOŽú½õσô¸[Âö·2²­ÝãÁ4HP¯–„>í¬vÈØ¦ôSìÿ€Ÿµ‰>&xïZ—WÓ¾¢ik¨I¨y×§þ« óÈ¥@žèÚìBØt¯,ã¯|sÕþx?áεca£ÿÂkâøÏ]¸º/RþÕTý²lŸ.ÚÂŽÑ%$“ËT ˆøû3é¿/¾ÅûC\[x/Â~>ÓüCâû­vÊd–òél \ˤ/½Y¦½Œ %ö `FGÏ?~(x×âŧ‹¾É*ÒÎò[bÏ彌Í"Ü;°ëpY“æhäõ¥³µ;¯Ø¯àü5±ã/‰>'¼·ð·„ü#§ÚkúÅüÁ`Šæ;V9d|"¸HÙÎ[†Û¸á‰¯Ôo‡>ÿ‚i\kmªüYºñNµªëÚíÝ»jÒJútJÊ"M=’ÙŒ·W3• •`Ŷ+*¨Zê?c?þοÿg]#Cý¢¯ô­'Àï¯I.¡£kÒ˜ßÄzôÑ¡Žþú TÉg`¤ ;w߈ÖfÒ6M/†Þ;øI«üY¼øýð[áLRëWמðuž¡vš^›wöxÙ$×" ;6[ýL„`Ü`j·Ø‰ŸLè?¶ð/Å/ˆ:÷ü'öâ ívÏA‰u+K›;a{„’s1“÷[¶ó’ç¡ý¡ìÙ |Óüñ¬ø·Ã:΄ÒÜiâúâGŸí΄\\ÛivÄÇyq>+ÆBo*›I9ÒÚE®ßøGA±³ñìÚÞ±â/‡ºtð’Í~¶ž\·Šámâ’4˜FI›l1p7@<ò¿Æ?ÚîùÞ(Ö¼ ¦ü:²¿½k$o¼C¨Ûi<$·jŠª"ŲÀ6î=Ož|e´ý¡þ9Þxwön×u½\^x>ÞÛT¹Òµh!´ÒôÍ8,®¯ܳ]^Oå“o™²_1öȈ o|оê‰ñÆ(:Þ¯_B$Õ¬ B#‚Ô¬f'ÚXŒ>J ,[,~’çÊ\bz‹µÿÙÏÇÿ |C­|BðÖ»ã?ˆ·ºd—sj:²­‚éúkåm"Xâ)¬î¦Ã朗,k•‡ö‚ý ¾ ü"°ý| ãK;{x~Y§ÑZd_³ÅænÌñ€ÂR ²Ù ‚q?´çÇï|Oø9§ü&ðêÇ£è04¦ëP‰7Ϭ͇µ*Äy‘C_)y‘ŠýÕR¯Ð]þÊ>øwk¨ø×ÅþƒÃÚ^s߈­ £ÌÖB%ÊBCÏrägüààÒö¾er™_~ëž?ø74¾?ñ¯…|)áó{öË0Ç ¬ƒ!$§;‹†(O”g¿5õßÃø?býWáÏ<áúoØÙÞE­xñÄÍ Z°"ÒÞ"Aóñ»n/,H'$WÄßtI¼aá /‹ž ‚/ÚZiÑéz^¥´r-Ñy÷B× €Ф*q¸“ú½¢xŸöLðøƒã‡í/Åz{«g»Ð<%¤oѬź6ÚîÝAñHnóCn_ºF@=²[±:R{#Ï®|£xK]>'Ðô‹Í @o²éóé ¨ÉzE»$™À/²·#fz•ú“ð']Ð>+èZ”¿ <#¥-æ¾!½[M_U%ž5ÊùæL<À1éÍ~~ø7ö¥øoáFñÝÄ¢öok:³jóÜèúj´Mr‘y¾…˜KG_”eK&¸Ï ~Ñÿ³v‡u~>h÷÷McöHRLÃl¬Ì囸<ž”þ»èİU%±û¨üKøÓ¯øºßàŸÀOˆË¯k6»uªiGo¦ÛÊ60…xyd'+ ù18ëÆ\ü"øgwàÛ [âþ¹â_kv—.ú+§ú34ª@C½ E’WYIQœcšùsDøƒ«îÒ<=g«^jï¼òNѪ‰9’¨ÜF:0Çõ¯ÎÍ ÓãO„u¦ñGÃ+í[Ãú†“©ËsdñjR•µ³(Ød$7åÊ ò+žyµ$÷7þǯØþ€¼k?컦-¿‚üoŽ~Do´ÎÖ×òRXß#tœçpç޻φþ×õ_\köZ äöq±]/X·µ1ÚÜ[°Û½·8šümð‡í#ñƒLñBk¶“Ùjþ-Ó_íúŽ©®Û Ûë›+cb]Ê„"ª¡@ëƒöúý­5Ä[­'ÆX²ºD– ÓâT¸ã †Too¼ õÁ îkTý£?iŸFÖ·^,KXølíc‰Ç²¾+ÌX·¯ÌIPxúšáüuãi| ¥[j6¶1_‰ n³ÊЈÕAù²ªÙÉè)ÑÍqµß,K«“åøhsJ7; GÄÿLrOªx§T“-—ÿI˜³;l*áùWŠø—áÆ›â褻ñSÏs}| 3]ÜÊÌTŒ–ræZ·ÇŠþ!’køzÆ×rífIœCŸâÁ ¼žÝ+ÏüUáÏøƒáå׈¼Uª¥æ¨·1ÛJ$p„£Ž¨™Up>µéSÂcfýéEL~Byû/üžš9íbž`šI£d]½GÎß1ý=+†²øK=ÍÜžøO⛘]nh-bû(Py8)×=8õ¯™ü}k¡xkÃß_ê:eµÒª¤fëQ‰FðFN ‚¾Ø^kã?x“Âð\M?ˆüsoU;íôÝVG9~¸X›þ×rÊë5¬Îu›Ð[S?a.?g?,O®üqñ=Ƶw û3ÇçÅnÅAÏ1Ä»†;b[øSövÑæº<3m¤Åq‘öˆ‹^ÞÈñr[q$Œpx Wâ4Ú·ÂÑôñc©Ü]4o+ÜLòI,îÝ !$‘è3Ò³Wâ¶™e{goá?íĶI –âm=&1)ËÆ œ1qòŸ­Oö=OæÒ¾?¡x‡À¾ ŠM/áþ“2^]÷Ú¦®Û0¹,[,ÞÈ+¨Ôük↟^»þÑר!¸Ži­ìWwÍ‚‡…=:Šü¯ð7üN‡—’k_¾kÓÉ$Tm®^Á YlrZ?<Œã®Ú»ã/ø+‡íK«Çm§i? ¼;¤¤ÌíåɬÏ4’¬Cœ±¶PµyÕ²n¹kó¿Wý¤|á=^]5«_Y±*Ð~îÒî ¬”ÿ'5ðÇČ߶wí%«Ûø§Ä‰t­"F0iºp c’/s•3mÀùÛ8ÀÀ\Ó<ð³ã×Çè^´ñ ¶—q«j°iv²²d”îÊ€3,k–|}ï]Ø|™Â>ûÔáÄç°“÷V‡Ýþðç¿h(oþ!x“Ä:—ƒü1æÆ•+ FõúbÍO?6vïÛÛŒéSöÖḕ<y«Ú[ÏÄš¥ã®£¼õ.N2O~q_?|iýŽi?‚ŸµŸ„úŽCÅÞŠÞÈjút?d‡Ë’%—˶C¹ãQ¸K'¿3ÔÿcŠ!´{ÿx»Ä:‚œÉ«]Ë#gœrøñÅu,žRÚg*Ï"·‰û5áÿÙ_à†­ílõ]ÊS Q­Î£¨Ü ¦'9Ý$ž¿Ýâµu¨ÿà~Y'ñ•÷„ôäƒ(ßj¾FÈ£n‚=±_Zwü§á£oZ]êÅ(×r=Öø²eÝìHÞi°ïìëá9 Ö¾±¸efCÓÜ7ãÈÅg.ŒŸ½6_úÈÖÑ?R5Oø)ì¢ÛæÙ|DÑå±µŒ­½®™ŸnŸîˆnqÇ­y±ÿ}ýŽôÕ6¾½Õµ©f,¾]Ž—9`ŒåÕPýÍ|a?ÃßÙËÂ_6£w¡XªŽ­q‚Gl§zÄŸâ/ì¡‚x}ž?ùf Èã<`RIôïT¸v‚êc.(¬öÐúEÿà­ÿôù&ºðÃê÷à4÷PÛZD©Ûæi|Ü{,gó¯(ñ_üËÆ~5ÓeðÝßÃy¯,Õ’ßP¹)TåpR"ùH÷¯/Ô>9~ËöW4™®5I€E¦Ÿ=ÀP;Û…>ÙÍXƒöŸø~«ö_ xc^½$Œl°0é‘)Ôõ®¨d´#´N ¹íyi}ÊÇöþý±íì-¬¼ðÏÚœ~TX¹žã ±b[+““Œ¥sþ#ý¬ÿà¢4u¹†}@Eû±C§4á}_{ËÁ>¤=+’“ö€ñ.³+&‰ðÛT»Ž'Æó4I–ÿw¸ñ‘P_ügø¾ÅRÏÀ0[Ùs{sæ¾S´=«©eTÙF?ÚÕ›øŽ TñGí³ãÝuoËœùMåAÆ=U‘ °@õ¯Ôü#ñ»Ä—OÕ> jzÄrIåI!–X„އ ‰J†Æ?ˆœ×¹ë_¾'Òtøî—t`Û2œŒã¾NÞke†§†(Îxº“ÞGüNý˜5GJ³ƒS¹}F}Je¶ÓìîÑ%i¦^Xa>E äÇì:Gì¢%½¬6SC<0ªÑ¯É£A#r-ä;3ÔnU;Øût>•󼳿·Šo4ê{±³íK˜½9ÜÃ#ÜþÖY~Ìî“5ÅŸÃà /Jɼc¯ÌNáø~µ¬#n†N«]Oc»?²W‡¢k}C^ðí¢ó”ó‘N}6à?á\5×ÅØ÷JÍ•¦³¯°F÷'¯EÚ¼{޵Õ蟲„ÜÇá.Õ‚%¸»„çØŒ—Åuvß5½1ÏÛ5? ih9ØÓ+m×+Çùâ®ÞD{SÎ4ŸÚöÒ'–};L×u†“´6JЏé+)ÏçW_ö¡Ñn#'ÿuÛ•aÀ½x-É#¡ÚŒÃߟQ]³x+Â:smÖ~%xvÇgU·QžÛ#ý*¼ú'Á™?¶üQ»¼~å¤0àO1Fâ}(hmOÿÕþ3gýSû'⃨¿ÍEa‚dˆð£žà“ïWþ!øxøsǺֆ?ÕKrnâç˸ÿ Ùü«ÉþëhºÖ‹â1iÓ2Œ¦UÕ—¤óÅ}OñïMYµÿx¢Å‹RÄÛ´ƒ‚ ²Ïp ËQZchðøe· Å‘°àއÔz×)â›h,|PÒ¼*é gëÕ¶ñ§­tñïTIQ»ïŸPz}*ïtø¦Ðìõh•ŸzzomëÐã½H¯‡R=dž¯,¬˜—²•f* 8I”€sß$†ÿRhãhn"i JJ¾y9ÿ >—¶ñ4údònûu¯–{«9_ÄóY:ÜŸh¾1±Þ#vÆ=ÏJÖ’æòˆf;IW;ˆ8ÇñǵaÎ$Ô¡–ÍIe`ã<ô<ñYòDŒÛ"„3‘‚[Ž{Ö¬«q"][¨HO›ø»dZ¨ôÿì½¾9ý’~'ü6$µÆ•juûU“öùǧrT^QñRÔô2ò8÷ÚÜ+…;~]®™=:šôߨ Q¶Ñ?j»ê3cHñJO£Ü®v£&¦¾NqÀÌT=>µ_Yðíæ™àkÏ\÷¿¹Ò°9.útòC“õTÉéÍfþ3e±æ°×ŽG¾&é ¸ÜÃFÖm$`¸ÿWtM¼„“Ø+“øf¿ -Wö¶ýœ¹q®M¦ÝÙ1™¤ºK»w‰¡Y²°{sŠçÌšV™m5Âm¾·WTi)vÏ y9÷έIXG &‘y$Ì¥·3Æ(ÆæPÇòükR-È‚6ºhíáÎÕiŽCÚ¶Ív Í”äFsïž•ãšÍ½®µ;}šÅmÜ’C§€ç$tÉï]uî¦ØxŠ 6ëæGhdÙÑvL:~MršÃÛÚé÷:m³*µ½ÓÄv䞣ÐSO]§ý–ª—Ñy+!826w àÖ‡u/Á¬Dž&†â{%ûâ'°ÿv»üEÑt?‡ºßƒuMûJöùĶS¾BBa Î8ÈÚkŠÓüâ~Ð^iZ]ÅÌoó‰&íxðqÓ­T¬Õ˜&}Nž&øWáëe¼Ð¯â†,îTTÄØ<óŒ“ïï\f¡ñÊfwF²ûAnVKŒe‰èBŒ±úq^“é:5Á]vÖRèÛ%Ä{JŸ^õôÃo|)Ôí¡† PVžá°²'Þ`àûW%L*|BÁ½ClyQ òb_RÞ&» àÖ…jÑIâÓy+6C¯q]†½ã¿ø}U"Õc¿™‰oôW…Ç@HûלÿÂÃñ—ˆ®|¿ Û!!‚JÈ}ÊþuÊùöJÈÙIû¡xgKð±–óFÙjˆ0w(o›×y9ÏÐÒøsö¾ø_âhµ ê=NÝ[7VÖ²¿)܂䠿:׌ÁðŸÅ>"o¶øêù–%Ú8ØÊì¸ëýÄü‰®¶?„> ‰#Ô¥‰gDWÛ)ód»„^? Y΄e£ÔÖ5l}½cñƒá7o[Tøxnb‚h÷´íÏßÈÙŒöÛ[ZhºÔI»fIÊü¤ónO­~sè_´Eå–¹ ƒ´¹/`•ö5¾í¡£õŠqǨô‡uÝkÅ?hñ žŠt‹;p71Ÿ~Þ~é§ýòyµðr„¹£¢:•dÕº¿e?ø&çÿl­â„þßiú|ü8<_}o©ÛÍv/Rwž³€E !çHº²Œ”çè¯öRÖÿhŸMgûþÝšJ[xÞÛÃPêž¿’õ¯íüIáöæDŽK‹«"+¤’10FG}ÙÜ›Ùößøßû|sÒh¿‚Oú¶˜²Ú_i7¯åÚë\î­5œÇ¦ò¡œ ÐÈ ɽû…ñ¶“ð;þ iûøSã7ìùâ¿ ø’Êa¬ø;VÙÔ|7â{1—²½Œ†Èk{¨C„š`ÝjÊx©SqM{Köf.üÇÆ—žºÒ&¸ð~± j7ÚL>v–í>Én¬ýÏ33Ã÷yÎW¿à¡mûë_,/þ(øR±×/ôÿø3ÁÚŤòJt­R#ö—Ã4{á2f@]j–u~¶þÍ´…ûs|(Õí¼´ðÏņ×óiž%Òƒe´½jÎG·y¢9ýõÄ‘H‡ÊÀ2œ2°™ðWÚ뿲ůÆ/ é&Ö?xÂMGÅV‘Ë¥ÝjZ{éÿky¼¦'î€ûúf½ü Ò—+Zœ¸¨·g}Ã/Ø›Áß > þÕþð—Æ¯§„<9¯HEåõ´<†hv,:t˜ËûiJËŽ2 |ÄýQÿ‚|=ñ—À-sBø?¯^Û\x#Æ-«èz5½ürý»I°¹qrVîõÛz̪Óåv6l²’GäÂï:׎¾øóâ§ÂÔàø‘ðŠ÷Mñµ¤À#øyˆ u$6é-.#y¸*Ž â¾¨ø…ñ_âwíáïüGøåâûMfÓÆ(.tíkíÿkA+—öÄÀdw”¸bK°$‚ÛUT{\ŠZ3Íu9ÍOÙŸöÐøýð7ã?ü  ¿±µ…¾¾‡Išêà­í¶¥§3m•ž0]š-þWnÇbE}¥ñßÄß uíNÆ÷á³w¥L«RYÝÚ6› yd® Ϊ@Ry €ÜWâç‰ôé<9ãï|B±¾M;ÄÞÕ4+ÈMðOqbÑÎ÷HXa¢ÜŠ0?»êH¯ÐßÚö»OÚ/[ÞxzÛN¼»ñMá!v$3ʬ£pgc‚t¬¡€P|Ôâk_4«^*5¥{lj|X¸ñ®þ’mn BÝ®ÞÖÍ|‰mZBü ~ë~8Ë»é5ø‘¦|6øA}û?Íiˆ.Y-%Ò5ˆd[[‰'cz›O$Ê„®wr WŠüiÔõOI øÝ¼3‡ôïÚ¸Óa…üôQhDN¶‚’ƒÎ¨úð=KÅ:T>.‡Âéy ÿgªÉ#´«æ¯ÌGBsÔ“ÆzTW§(èÑÚz£Õ<]¨MËèÖ¦Û*¤ÿglq‘‚G·¶3\¸ü³^kxöÞ!‚]CHh´çž#·RÒ6À%œñœö®&Ž¥S¡ëÔ/.~Í­ZÜ„$ù# !nq‚1ǩ滽NûP³Ÿ_µO-!1!º9YØÿ²ÄcŠñ?Ä:Ôžµ]6HãŽÙŠŠ }Þ¾„õÍz]Þ½â+φ± lÚ}OÙá†9?û¡ÁdáºÔò²¹‘ôƒk©éž›VXãšÏÆP%ªÊñîØ±+K/'€@l+Ø|EkkðYô]Ù$]acšêI<‚ÓË ÅX䀃‘Ö¾D‹Q¿ŸÃÚ‡ô[ËGLŽÙädmäñóã–=ë¸ÓõYî|ÿ ³¨Iäé‹-c»k–§¡UÁãU§¥ŒÚÖç“êom•¾ŸlÁ”…ñŒ¨ãhoCÁ®e/6Gµ8l79Î>•ÛÍgâ]#C‹_Õn¾!ÖCœ)USšó½b{]2T·‰ØøóHÎæ>ý+–KQšÖ:†§kªE¨Â|å+µ; ™~UEî>Íâ8¯/ƒiˆÊ[)¯Ê¾3ócÒ²¥¹–òâ+q*D³|«‚sùÒ,o4ÒË7œÄüÙ9$xc·jÍÀ 2꺷ÙR&–MA‡Ì‘B†ŒÀÆÑùן|Kð_.´;ÇÉyd÷Z.¯a¶Ù&Ý}³ÍV±mÁîbHÀ9®ûÌÒ ÙÅÈÉÜ8$Ç~?ɬ=+UÐìo!½Ô"7²¼ä §À1÷W¸ íøÔr5°žÇsÿ Œõ¿?ŽodŽK@ ¦•SÏd'Wî†^z(àV׆ øyqà‹Ïj|W~-›R}b?^˹¥Ó%í"ÁáÃ|¤í\w® Ä6^$Ô—PŽf·Lïä€$Œ¸;sž>ë§øyã?hW_døƒ¥=ÍŽ¡m%œ¢ öÁ¸Y\N9"¡Í£6ŽÏTý¦¾!xëáÌŠÛCáÿßè¬oy…£mª21æDO.¸Ï`¾ˆñôÏüøÁ¥I£xfÝ|g¡i³gÁ°6‰æ½„ÅŠÉhZA.Æ;Ägk«“ƒóñÏkŸ /m­¯<=¦ÍZ=ŒV°f*³ËodÝ… ì:dqëÍyÿÆ[M/ÄÚL:}¤âÞöÁàk%šs¸úƒúŠÍ}.âM.Øj·¡¦†5,¹Èã®úÕÓÌIìë— 4¦îèG Ú`ýãþ×½sv¾!¹–ùu ä 5“"Q±“È u<×k¯kvì!¸·YP.ˆÁQøð}½*-)´»‰šGfFY‹úžÇ¦+¡+÷,Ÿu;?‡¿ð…j¡e†ÑÞ ÃaÒ)&ñŽz>‚¹Oˆ:ÕµMba•îM­’DP± ñgp Ã$˜×˺–¡«ëÞµÕ¡)ƒd™`«g?‹Ú½ƒDI/ÒÎÊÊGFû4Ì8ãi'€qß4s5 «w«Ë®xzçÂzž.ÃÓɬK{ÿ-ZÖ=¬—ø€l(êzœV/‰¾0êþ.ðL÷3Nÿf¼¹YáŠG28æ%Ï!AçŠà¼?«j¾Ö/’b#”©†Dåt=‰sÞ¹ i Ó5hᕱlåV¡êvƒÉñW>­ýžµýfÝ4ýÃ:u»jö7q½ôÌ‘Lã m··'éô¯Ò?½#'E¢WöòéQÁ5Ú•IÈÁ#r3Í*,bDåXdÒ¿5>0ë7>³Ô´OøßTºÖôÝnÖÒ+drUÅÇ–e.pc@ã‘ʾ•ð§Å«‡–žø—©Â/t‹çŠc¸3GÃo‘½6“ÓœJÖ8ä÷2•ÇÓâ')…ýkŠ»Ö&µÖà°½&4™¤Œ6p(ÈëëÚº8µ›kß ÝêÚ|ÐN–!%™t~Y†Êž™ëé_¶_í gâè>ð€¹·¸Óî>×m« ‡Ùn„‡ËeTSÕ[hùŽsŽ1Elt`®˜¡FîÇì&­›èm7¦ÉîdhÊd|¸Ï?Ž+«¸š;h<ç?.íƒëé_Ÿ ÿim[áõµ—„"xö•¾ø•‡5Ëχ:¾’Ö&êâ·2é×*l7̤J7E»=3_xâûÅ/‡uO±§ÙÛýŽîê6„¾š¤yxŒó¾/âç‚k ã›vF°Ãw? ˆ4ÿ ëºnµq5»[ÞZJ%u-2!Šœ¯¸í^–š½·|3¤=”Œ«wy™•pvÊ¥ã_öHã9¯Ä¿ÚGÅžÔ¾¯¦ÐÞM>dºð÷‰tù· âÉåºÜùZ7à IèzŒ× û=~Ò?“A¿ðç…î`ÛñLffvŠâDëƒSnämàŠ!Š—Rž»?çºÕuëë0þSFrðã¬Ð1VlžqŠäåuU' ¬éÀc»ó¯‘?h¿ÚÂzoÅx,ȳ[^èðêZ]ÕºtýNسJ²6T…•BãšÝb±>ù³ñÇö/‰<-$‰££YùPÄ$På±×îö<×uáÝ|yV3^¡#¶bIÛ–tÝǯֿ ´¯ÚìüGñv£¯øoIšíÚ$‘"‘'Ž ³É!ÁÊc§Rkîïƒ|3ñ?áÖ‰â: ¯±ùVú~™m6Ó4Éd7\Êʹ; ªÇôÈ­iÕ‹{“::vÇyÌí ±‚ïa麳µ:K±#ÙHb‘py_ÄW+ái&šîG_"W…_i `œ}GJÀñÄèþMJ $ñ,U9gäâ!€0Y†N=+~dsò4;W·ÕãÕ-4ÝFù¥ò³**üîëÙªœò1_7êöÿâ»»ð툤‡KÔJ›Á¨Y.¤ÑÄùòYyC¿pÁ$œw¯¦u^k kN„Âî ‘Ê ‘8!ÉÏqž)Úíý¥¶£gº­¦›t²´Ñ!}ñãïLH*ç8ìzÖr6†Çã¯Ä/^+ø§]ü<\¾ø—{c}º÷D¹„Y2FªI.àT ’ð¥Žï»_üWø%á GºÔ|%£jl,îšÞÿN»Òåún‡‡í²§“4©ŽX98À÷?Ð_‹o4ŸêúôºÅ¬ÒxwÄR›x¤¹F@ë6š'þZ!ÎÞ¿/>õb×Ázn‹àxõ }/5o é*o£ÓÖÿítÂ…’KµaóÊçžy H®yF÷6R?—¯‡? Åèó[h÷~$Óm¤‚ââÞgû\¼‡”C::ºª– Y7–N+è_â‡Äÿƒ^9›KÖ<]Ÿá½:p÷V7ko|ַЬ¢Ú)å ÍlŸ*2•H,O&´?l-ká‹eY!ð¥ÃøÁ-¥ÔÛD‡ì’i÷‡7F1‰Ù”¼íUÝ“Ô üÝñ…u»­FÿáÕ­µŽµ¦yڽĚ½ì+nóOÉ#M2ZG(ÿ)ç9®Y;=”®§üYÿñýׇí|7£j:<Ïsk­ 3Jk;Ûæ”lò£’bHW¯œœsÁî>y²ø›†¤Â>%ðÅ‹k1Þ,ßÛZö oõ²‡s[ÚËtûü¸,£}±¿8ÎKgÌ|kðûÆ_/4_ øºÎÖáu[d–;»â¸R—DB䮿Sòàúô«Þ ð·‰ôýÆ~»²†ËB¹šÎ{™L×7V²,QÙÇt¶¥#lÑã÷¬Äô¹˜ùlo¿¾|xñWÀ˜ü5w­hú¨ñM†ŸâÝNK™/-m­ç¾·]X¸$™cÇ™…UáBN+ö÷ã×Ç‚ß þ k |3ûIë~1k .î]WNñ–ž5Í#Å1^îiÚIx¢¸·½(›&1¾Øã}À7ðþ~Òß>.|,ñ—ÃûýKÃ’øSľ"º¶ZJ"0ÞÅ2üOñíIÿõ>(Óáõˆ|SrR]"Kk™Â[i÷cÍhäöÝæK™8 ¸  v?·'ÅrÿÇz·ÀÝáE¯€<6e±¸³‚o+f æOµÚ±E’i³¶0CmÉ9,ðýö:‹âÇìËyãÛO¶“ãOÝO6§ k úQû%®â±4R¨’Y%ˆyð¹6pW‚2¯™‚<»öùý¡¼û@|KÑ<ý»<kÿññ'ŒÁ_ ´KýcâF»}§Z‰5Y®§Aimç%%Ú¤Cå]§!YUÍ\Qíÿ´_íñþ }«_êwzˆ²ðo€­âÒSÃZ%Œ·—ɢ鰨Édª[¬¸–rT)Q´)ؾøCâo|Høk¢þÌ©{u¡Xx²ÿLŸR…n±ŠîÇ2™”ÜÇ„\6ÖÂoÜúÕûC~О5ýŠ>h^&øw†ü#¯Ü4K=SLž9ãoí+$”É<)eHb.ñÊÅK²ín*ü'¤hž»–‚šCÞK¤iÖšN—æ×“6Ë‹«ÆÞJ¬S­´À N$ÝÞ¦vF¿ÄýáÞñ3VøSðqu(tûm= Ìz‚‰ï"¾Œ±uY&7F9ã7¬{KOxÄšV­ØA¯Åh¢Ö8vÞC¸ê÷*à ³9e$.Ez/üw¯ü4ñ´ö¿³å´~8ñ¾¥m9»ÕVÖ}]š,‘}š ¥’Xäf%F ¯r+¾ð÷ÃO€þðAøã_èSx’þÒÒÌk|¢ÿJ½²P·?l™ÑV8ç ï÷Õùçš‹"ù΋Ä_.ãñ®!u¦kZŽ#a`F'÷‹°îNÕ±á ‰RÞãL»d)"J¾fª–9$ý@⛯´—wúuôËi†;ðÃüªH:oÛíYjz¤Rª¼èá€RÂEÀoª“‘\†³¦´Z5£dŠÚñ£lœþï¯?…;_»Ò}g©ÂVI%Q_º1Œþ•zÊíŸJ¹žóÒÆà=¨*+å~máwpÛXôXØáüXô¯xÖ<«ø/I¼Õõ¨cÿFÓ®4ç UÇ›#nF$íÇ=²kÆá¹œC 3”'Z†û«Ï é[¶þ#·Õ´»Å·Þ²Ãx±D®ÛÁÊrH>ÔÕ|>ø‹â‡ö×V>B³Ï-¬òÌ—ºÅW‹$¥É9í]·Ç¯èžøÙ¨ü7ÓÞ;È|7¤Yi¢îÓËrM"2ü¤o}¯ŽŽ¬"¼›ÃÚƒhÔ^!˜,âÞ0å ªÄ¹Ï^+Œ·Õ®"Ô[O²HÕf{‡1ÆWtŒd `ã“TØ×sÝn­|I©h—'Éi§X•Ü>Vê=±È¯¥>Xiöþ+…¯äÅŒ“;Oïg“)#¾ oǨòÏ„.®ãÖå×Ì›$ùŸïnÉãƒ_Aè:Ö½ x[RšÝ$oªð¶\È¿,`ÇSɤn^ðWŒ´‹_Ä+­fµƒJ½KIp÷RBåãûÌÇ"«Ø[&ŒñézœM5Ó£‚[æFÀ+ÏlzW-àÇñT¾8Ó,¼U8¸±!®v£vÝÅHë‰0HéÅzÝx»VÔµ»|+0‘ðPÈqž=§ACeœÅÍ…ö§âk« YfŒ·Ë ÆÌJóßÉ>•¿"ˆ5èüë”e€ºo›£6P3ÔV·‡b¶Ò´Û›»5òÔàDP(ËsGÐb³£ÑõIõWÕ^ÙybÉ>a€7 <‘HUõ™üQ%ΛxËçÙ ªÂ6‚ªs‘“ÏáÍTþϼ¹d†Õ‰%Ù“,KvëúÕëÍôËqâK“o„-Ç\㓟_«iwö·K+Ý)ãO2²döÈî{zP†oï%¼³¿g·¶âW2&²pëœàúWK§ÚèPipj·R=ô.€BHóŒn*rØ߯µ¾·¤J,µVšK­UYvHA Œ†$àc'ZÄÔfO²A¦ÙÃ’¤ûn™d‡,xaúP¦núÔ× »ª³18%GU ÏçS[x¢ÎîÌiz\Ò„­¬jUO¯?ÅëéX·ú„ÓjL3†GΘéÀàëQøL¸¿»þÓ6ÿgµ2lMÃk3AßžOJÌŽ³U‹LÒͽœ€ÌѤr%À$ä“•]£9#¦j•â˪k\iÊ»îU×|g(ò/,=w•WO>å²Íóà%æv;#ô CíÜt¬+ûYü/£Ûkú-ÀûTú‰Œ¯¶+xŠWžO™Ð7842 ñåÕä—riöF¡´’"ç«lÀ‘Ï@=ª­®§¬]뾌ÑÅý‰å™ZdP¯ž00ps’O¥yψµ¶Mo%…Ä&_0œr¸,r=sœgð¯u´U½¹Óí4ß²¿†X& 2ìè2ª¾Ôg+áùôë[Èô‰ Á)d+—2uç¨Î=kPðæ¹¬C2YÁ'˜UÆWjœã©ÆM]Õnf}ZÒÈKi·ÃÛ¾Uû {Õý.'ñ^‹¯Lö!®A{ töÁ8ü(ÏZ¥ÌVÒèr ŠþåÚ1œÊ€r¼õÍi[ß^Yxr n¦"êîFŽ9e¯PÃÔ «s¨iº^¦'¶Ä·Jñ)* (é¸g¶:b±î4y¯4 $µ;d¶¹šb¤pãvOÐu ObýÅäÚ„©Ì¿»‰§W#'ä8U>™ùoobÒv²¹’9Dê„}̃ƒƒÜñV<5ãýÄÖ²­¬M4hüè¼È$ò¾î}@ïÚ¯xçÆ—ž#ñ ž&’£¹Ôæ¹6ã ¾N²€J9;JÃ\ñ¯S\!†DIS‚Áló^»¡ø¢K¿ÝØ´O¾ÕÏ’ À1‘ÓÝ}ëÅü7¢æž{vEnæ4VÞ¡I-þx¯Wµñ}£[kÀ­Þ˜Ð;EåÜÇÆOpãåeàœs@3oGÖõˆ~!\h­ŽóT¶X1ƒ#8aF>ñ`L ô¯‚ ü'â¿Yx7Ä—­aws¡okm‘ÐI9[È 9ÆW¯‘Í|FñŸÃ­âÂïÚ+ÁÖ·Ë®›{›ÿørcöt¶žÝÖíì ¢Ùài2IÉvÒMyËüBµÔ¼sŠ<*Ÿe’žK7þxÜÈÇnO9XäØ~¢®™ŸKü(Õµ¥µøí¢x²öÒëVðçÂ]Qïî¬$[‹Xo|;vVÖÙfi¥ŒŽ ,X0jþ€?cßÙö+ÿ„Ÿ²ŸìÇñc@е[ßøšçÄp´÷KpÑi~°kÛË´ˆ}ÿµËy .Í…ŠI êªù®Ö~4øçNø/À Í2Ôiþ"ðHðLZ…¼& –î„°ÍpS"b¬Ç%¶p\œ–5ûSûþÕ_²ÿà§ÚO|cc{oáÛ ê~†=,˨5ì÷ka QpàÇpòÍÀ'h»hµs*‹KŸÑgìÍâoxþ —ûBø Q[Ã~ðƒ›OFPñ ßÞMÏWEž&u ”FFÀßÏóïÿÖükñâ_Äo7úÜÖ–~4ûL²Z[3´‹TÖ¥Õ6B™&œôÁ‘bËt{—ü5OŒ<+û.~Õ´o‡uÛ(~#x”x‹ÇZe¼ð¤FÞËLoøG£²’6åÏöM¬8frv“]üÍðÊóá·ìá6ºÁq§xÏ\ÒµÈÄo²HítË -m1}É“#!‹+Ñ[œË©õ‰ìµ?Ù#ö°ñ øTØÜ|øÕâݯ„ʰCá¿ÜÜy‚ýøÊÇ©L±Ã$h.œH0Ò» ?jß‹Þ8ðï‹—Á ­é·曫Í~–KåYÊðÛ4­ŽdÂÃóŽ3Y_¶?Äoüqøg⟀ZÄká‰ü}&“j—v×#j¶—a•æ?ꥢÈåǽ|á¿ø³ö‚øïោ¿Ö×âïÁo x›EñT²ZÉ%¶§c¬½ªÙji,E!†k˜ Þ"'‡ó6ªKhæ¶%#éÏ×ZwÄo†ú‹ûr'tïëZÞO{ã=-/­[-²‹v¾Ýç$~ð² ŠüØ_‚ý’à¨>Ó¾ø2ßEø1ñÇÃ:öžº"FÑ%Õ#°ölìð¤­d’­²È–¥ö§÷çÆY¯çý£à–çì§àÿXjš¥ŸÃëË[‘¨Ú´SZi×KØÈÉ÷þÎC@[‘¶1œ’IÊåÊÖV? ÿl øãñ+àÞ±à(a¶Ò?áWxvÎ ,¸Ù¤ê2»%âIaP܉ó˜!'çÅ>x"ûÅž0ø‘ð‡\º™ôíµ]éšdáf¹Ðc#(½"?š£’và}ÓQñÂ߆ÿ?f¿Ø£öѳÿ 'µ}GTñSß¼“O(ðö©oö5ŽÍÈŽ;%‰LjQšY ,X“ôÿìÙá=/â·ìéÿø³öÙ-¼Q¥ÙYx«IžÚÞ[6‰`¿¼¼¹ ñ²˜’T˜[IFø†05Ï%©º~íÏŒ¾#üXÖVRH!µ $R@*@Uã­}±ñsö`ýŸN©â¯ƒž!¾¶ð®¥ üNø}à‹m2Þà­õÝ…í¯öœÄ¨ÞÀG4ó3ÿ +>r7h? ´¨?à•Ÿ¾)Ýkb¿ˆÞð\:J¬f-Na©qæ†Ý-Ì…Â&Bì%¸Í{'Žüwàøÿao‡ZOÄÛÛsÇ,ý¢5¯^xùmy.›¡Ø=“ªÉÃ…,cnvÀPÐ?#éÏÙÀþð·íÁ¢|o½‡J€º_Ž5»_ Ifë5õü>‹Q“M7… u7p¥Á¹Þ0ÑFÌ$×à~™â kâ‰ì¼yâ’óVñ#6¡qç¨W–}VF¹šM€¢Y§y0¿.Óò€+õ«âOǯ?|ðWâÃO Ø·Ä?xGÄ:Ÿ‰.íqö7·wvRÙÏHO6•y9 œ~xJëãÏÇ i’–s¤xfÒ÷Z–lÅæÉáÈÚ[¶q¶)@‚-Ä‚rFS¨¢U:nçô1ÿpøuªüMð§à¿ˆÑØÙè>Ô¼Cqà½fCöxÓVÕ$'QÔ˾uƒ°¶ˆ(#óƒÞ@üaøoñG\øQðÅt?‡V±kþ-j®ˆu¸qe-æ«$:,öpÛbIn$š'LnGܯÐKŠŸµWì•û øÆöŸðŽé^ñ>Ÿ<ðøj[’}^Ö£’æ;Ï€wa!Ó7`œ×ç‡ìáñŸFø%ñÇÀŸ´GŠbm~?ƒž–ÃÛCOs¬•u´iIù2+³;aS‚zdÄÁõ'ØN-Ýïû@|øû)xïâ_Á;QžÚ_‚_tý.Þêè§™â^³ù“[È âòà·‰íù”‚»HÀý‡>~Ì>×~|zý³ootßxçÅ>"Ôõ}FY¤·°›Lð„7MßÜ—û^(XQ¤áFå|±¨|Wøû@|KÖ>1üLŸþó§ÙZiš†¡qÛí;Ÿ0Æ]Ô ­$†%o˜–ÏMµ{ÄÞ8øâ|%ðö¬óh? ,bÕ´ 4*´pµ­ËHTƒçM4±ƒº\ä½ eKý”жf¹ñZø±¨üdø‹¥Í ^x×K>+‹M¹WK›-Ì–=<\!Üâf… “6NéL‡¦~“xPøáð ö`ñ¿ÆOh—¾ Ô~ ü&ð·ƒZ»ºH/?´|cwºqcfXÖY ?—½&DxË(Þ¹üôý¶>+j?¿iMwÆ7šÌž)TÐtý-®gÃ/•$Í ¡< ÷ Æ,Àð%¾ñ½ì™âxÓZÔ¯ò;mBÊÂîâI¢ºÖ®[ÚÞ2¹lÉmiE 1>Zp 1>Þ=‹öGÑ¿4ígà§ìñö˜Ô&µMS3üðl,í6§oxö¿k’kMÏû«vv+twÈ#3Að'ïv¿ðNÏŠßµ=T[üNÿ„«Bðï‚´›…*‰§Ï y¨Ãí2˜Õ¦@äùqˆºgv~fñeü¶þÐVyfÑVi/!Ï ëµž5ÎÅ8%<Åù˜d]GÅéì­lt FpbtÒ¡û=³£,«ŸÝ£÷XòqŒ¶){u{Ø=ž–¹õwŠþ.x+ö ý ´ü~ÖÇ…¾|2ð|Ò$6Ég¥éqBdEؽÕìÙ,ýJ¢§ÊÀ“ó=‡ŒµëØëVÜÉ)?bÓ-à>tpî&Þ6*NöòB+•ÜÖ¤Óìtm'§Fñ$wøÅzWÙõ[•!Ö$rÌ©ÿ U ÓšõÿÙ¶ýo9C ¶zýs_&üAý¦|uâÁûRë[5oè×2hþFM–:Zh¼Í‡&yœç;ˆüÍzçÄ_øwÀÞ'›à¿ì·Pñ\CíÞª³$vÑ/2K3É”2:·îâ_™FÞ•ð*k𗉮¼6¾ ´™tÓ« ^Y9$Œ¿8Ÿûçóæ¢å.¡*P]uƒFñž…ðÊ÷Ç~7ñL·š‹îõáÞís©j—L#D/œÈ‘¨ –8ãNW‹ð›Áúb^øâ¥6«3!¿FÑäÞ ó/— îNç8À'økÌâð>©©Í³âOiZ+Â×*¶1šKfíPÁ\Brvä ð{ײø{à…ü?á¸5½⦉¦ÏªCå^E°Ã¶Ø•,)`FY‰##ƒIÎM‰(¢WÂ:•ÿì>x–e¶Öm45íb˜}˜MåK¿*­´|Þø5ëú×ÃëËï Xjv·×úeœz…ìÛÌÖR_“ò<‡9w >V}ÌÏÆh~ü>qð¯S•Ù3ê Ò¡y¢8cíê¬x<ŽH­gÁ^&Õa]â³™¢µr¨ }YNÞFy1_›‡à·Çk›tÔüOñWŪ!…  'Ÿž8ÕÎOrsêMfY~ÇvZáûˆ0øi¸µ™î˜‘è#^}ˆÎk†Ö¿à§ß±“µ ñ&­«‚¤¡±Ð5”ÿÀ„ýkýœ!ýšŠš«[,òÛÃi!7³ÛoRý\ ØLý~U³øñû>Á¸ð÷†üA{?w"éqÂq؃#Ó¯Öô¸kßS qV+T¦/ÿଳíÄ;¼à¿x‚SÒ;]= ;s÷¿Ò1ô85ÇÜÿÁR/x‚_(|‰PÛ€SˆT}H>ÕÏ\üNøÛ|U$øŒ-ä<‘a¤¢eG¡`äcñ®nâã↲‡ûCÇþ&Ô‘úÇkƧו„cÜWE<%*oÜ‚G-\ÃQrÊ_‰º¿²޵¤'Å;ñËgp1Ý̧$ub ®¸5‰ÿ àíN#wâ_S Õõ[Ð@ Û+`“× ´Åø1©ø’?7R¶ñn ‡ ij@„ ¢ûÕ¿øe}*åCKàé\pwêW¦SøeÎ? ê±ÆªwAý•¿gFÒêWÞÒ$“´·ÖǶ÷É®Á|9ûø]c>‚Â#Ìš|¢ã$õÏ–Çî+*ÓöqµÒXE¢iå±þºåCÀ·?{ü.Ò´fóuxKNQÀ3\Ž>…SZV+ŸÌݶñ‡ìs¦¨¸ÓçŠWC"¶vbǰ0Gã\¯‰¾:|:¶Yá𶩩Ɖ‘4q23» S€*톙à;K1þ iaAÓl<Í u˱Ú}°+e¼?á_C%¹ñ^£qBK9ôÁ¶r1{CǬ¾1ëÎú­ç…5I­$HÇ•msFÐ3žzàVOƒ®5»¿·ëö·¾–{¶kIõ ÉiÀ#8Ï\ç×­ÇàÏ¥å¼Ò®­¨i6èFn“Êaò§ÜàŒñYÒü)Ôtû?ìÍCš…üs&ß´ˆÐ;0†õ ÷¬çÃÚ ÐõŽ®Ã>´Ð,"+›—"êØ Æxê;WÙ±GÂO‰:/í)eãO—ii¢irK¥Á¢ü®÷—22á×bE'Ž[wlsç?ôÏø+J¿¿O Tv:™,ßh’ep¤ªœ£#·¥}ïû>øbûþ“ªjŽÒ\Áe%©PUl‹n;H$ÖN›z´>Cý¯ü1ñ›Á¿u_ˆwZ~—â{—’ÆàIî6¢€ûË$©èÃcŠøžïÁþ6×Ûv³ãê¼ÿËÐÄÀú,JáÍ~¬~Ôþ$Õí>(XøoAÖì4Htm=VO´[ùÒK5Á/Ø_Äæ¼7‰®íÚãUñŒÂ'1´ÓÊF«N?Ÿ½uѦ’1u‹¬¿gMKW•–H¼]©uÜ5°qâùÔc×<ÖÔ±®•<[oþÕ€Þ] CgûÅÉÏæké–o îŽ Ÿë72ò”$±è»±·'¶jŽ ~i¾xñ¾½y,G ²KåÇ\íqÁ®`Þ¤:é)¦þÇÚf’žnŸàÿiÿt;´‘+ã ÈúqÍvð| ðþ‡—}®øOIE$þý–VúR9÷ÍoÜGð“ûDéÉà‹Édx|ò·7þk4=s°¹ãóÅb߇V:n›¬i>ðý·ö¤¬–ßhœ´²í8>Zàž½û‘íПð‡ü2ÓäÍ×Ä]6¼íµ¶2þê‚3ùž+]oƒVvûfñµÝìŒpV_+jž¼€yô5a¯m®-F¥¦éÞ´´µ»û#Ķïpþ{ýÀߺž$Õô ívÂÝ­mA@d’ÖÏ8f]Ü'·ãKêì~Ùwþ¾ó‰ÒµO€6p*­×‹µ(+Ìb\öF%Xƒ×œVWÙ>k½…¼%¯Ü.A&à¶ð¹ç2;  ûW¥ØüNñ …Ö¦G®ÞùúÔ2](†Ý`QxÉ2–Ø× 'Š5mVK=fßûemooþÀZoݤ¬[Ç}™ç4–±:éks¿ðÇ…ÛDœG¦ü,µ¹…%e¼¿_7?BzrEx†·ã/ŒÛóø2ÃBÑ´]JI%Œæy6lIUP1ó™Èükе ßZë¾$ƒÆ:•äºo†íüÙ>ÏpÁ¦¸äí¾Þõã~ðOŒõ¯As«ZF·º´™–ÒW-<Ê2cžqßÖ­a.OÖ|Ï¡Óþ‡‡á·:eׇôbñ¬K:ȇ ãvçÜÌWþù¬2çâ_Û¦¹,¼é_û2O1—=@TPp;W©iþ+Ã÷þ1ŠÚÑ£}HiJ4GiíRìsÔô} aá=bÏ⦧\êÃm¥iÆëR[Kuˆ´ÛN8õ­V’ñi-üɬ[ùz‡Äí~ü–¼Sç=·l?ºNkÇËRû.«®ø›QIÄ‘„ieRü üƒ®OÓÖ½ßCÖïþi¢ÚùÆ¥â]e·0ÀóŽ>ïO­zÔ¾Ò->.ë”òÈöþÑ䌩Ï&rÅIå µ†3xÔ|Ç/€>D8¼5­]I`µÉf>»¤Ç¿Õé>Ю®-ì´ï=ÔÓ¸DInWœ÷ù˜ž®1Zž’…žÐ#™ÞïÅ:ȹ,îÁ–òXžs±xÈöOx›á§„u{;ïEs§[˜žEŽÎ;Hêq„ –àúÔ΂JãŽ!Hµeû:ø’5Ù¦øCB°`Usu;I.}ITqÁïž+f^"ÓI›P‹Ãò‡(žs|½yqƒÎMr±þÐ_ n6ÛØXxËPA•P(Ç\@ü+~‰‹uKxt…úÔÞ{c}ì! ÎYжH„WØØÿÖþ<; ¹³ŸN+¸È¡YsÊä“ëß5÷%ý¼?f›Oï-6…ͯøÅ? µ…I-'µ›dL03/OsƒŒzéYUZ&h|ùœ…Ô2~í9ê2=kKZµo]["~öÎH¥F'å)Èf‡¢Òâ¸]Ò;‰ ˜DB?¾Ÿ)þUÓè6¶—w e¼˜¯ãhqÙ[ð¬ Ï.ð¦¥wá¿êI¹ãR㨠ÆAöï]—Š[}Fe^ˆ`ÉÃzW#W÷_c~LD‚{½ÿ­w)ŒÏ%ž¤à¿Ú­cmÄ`; †éЊ,,»f‰Uå9ý?Æ–ÞâÞÚX¢…‹& 8~õöö¨,.mä’k1†(¤€NÖŽ=EnÊ’=»¼09‰HFcè*}iꀇõõðwÅoøîÒvU´¹Ë7Ÿ½R3èÈ>£Þ¿C?im3ö ñ¬–~ÅâD°ñ†Á˜ä·Ô-•‡¡óc“9úõ5ù¥â+'þÂIàB¢)¢‘d<°ç_ª¯dñçÀ‚?&™^÷VÓµ j">ž}©ûE³7<-$?Þ<ú©jîiÈý£Á_ôeNï³\ýãò†ÚÙ˜8¯Ò?Ú1ãØ?áÿ‹nä3_xÅÚ·…§xÓå[]b?í#'ÑPôùˆëšø›öÐÒæMBÃ,Ù†1üù¯Ð?‚²XüLý…>/x¶ë‡ðÕ—Œ­PŸ›ÌФ 3¨ÿ¦`¨oUlÚ]”nmÝ/.$aãîsSïŠÒñ„ß`µÑü[_Mž7-Œyˆ[pÏâ1øÔ·šV aTÚ«Ñ™"ÚÛ”§b¾Ì+WS‚[φói÷ ºHÓ$¡ a×ÓÚ¦ QÝŸn|aÒ—Æ¿°îŸªA†“Á^#t¸2\Á¯DX=ÂHTúŒWŠ~ÌQÍâ_†^%ð®w›ý2HÕOE¸ÓÜ̘ÿh£uö¯uý˜&o‰¿³çĆå ô×þ “P³€pÆóC“ç?ÄQÓ¾iýux<7ñbOO&`’î5lr¾LÀ¡aŸf^}*mîòö œçŒEµÆ™jdµY’ä«+01<óžÇ­u^ø³ñÀò6£àvêÂ8™AEq$oÛ »—o=€8é]·Ä/^Ùx+[Ñæ„‹­òâØî*‘>c'Œx?|õ¥L&-ÆF\d{o\•“D¶Ïrø‘ûixÏÆŸ¼Eð/â&—iw6§yg¨YßÚ/`–ÕÁmñ1`C¨ àñšñ‡ÁuÄ>bX^iÿiP0ñ1€õéøW‘üEXeñ“Ƈk*•äãðâ½[àg•ÿ n‘ü®¤—l¸ê¥À`~§V¶Vîõ8fŽg,ódLg«¦Gui#ñ™tù‹€;ò²Hè|%©Y˦ëv“Œ½Œ¯oÓŒÆqÒ½àgÅ_| øágñSCF™’Úhå0K$ɱðNØ÷”—ºìi®§ëíóû6;oÀáùëÅ|+à‹%ñ×ì͇àÄ·vƒTÓ'Û5¾ïLÇ%>EͽÍ4¾‡ñnÍàÑü1®Ýù§L×ôÆd8Æo,æ1°I_d\®QX²ñ23貿Å_Œ-㋟†ßð“Lì¨Nš·rµÌa™À I}§hà0öÅ~ÂÁ"à§?³Wì¯û7kÿÿn?„­ñáGŠu¨uv½Ž5 ô»Ë«hmÝVÂåBÉ v’9CùŒ~BrÇñßÇú×Á~Ýšÿe¹î.>^kWw¿µsµ”Œ²y/&…ÝáQò #ƒš#óBß1ùš¿ï~/Máïˆþ(ÓâŠY¬å¶Ô#„³­Ï’û”ýñž=Ï5‰e¢|ø± ï†t+±¢]ê&Ê»U]âc°Û€A8m¸$qVáÒü'âø.×`ƒI×®Vv$QJ¨QÈ •îç½|_ñ#GŸNÕ¤šXBy²H°êëü¹÷­9}ë!ßC¬ø­ðóÅ 5ox¢8Ÿ*Ò[ÍvI<B1ÈÉǵçQò¥¸Œ’Ñ1Ðpv¯_¯_»› ëýkÁ7úÌ×ÙòÃ4+,&Å“(ÁKd€}“àËm ?évð‘ìn®–ÚàFB‘ça œüËÃŽqWnæfî©$>‡V²ž&m¹#€T{súÕׯ/ü?‡n­ÁòÞFÇ'ÌÇnµõ7Å€>ð¿ÃKïø\þÒ°ŠòÜy]{vùd–ŒüÙÈãj•œ×κV{po-T ytè–I¨D…OäaÁç¸ïÅZµ´D×u§ŠîHÊ›x+”„uõúŠ»7‡Î<—7ó+Jý¥Sï+î·½nê÷î|dÎÍçé÷76ä·”áõà€{v®U®µíUmn¤µR‘Â`VŒrÑôºžýM;zÏUk È–Ò•%‹‡Í'<ãst¯[ðÇ]{À >ƒ©1½Ób`…x1È[#Œ©§¯5ãgEò⬼vÀ|£Ì?xûcÚº×Ã:v©luksuÙ.QGÊÙü*$€Ññω-¾!k³ßiT‰íž~yq“…zs\Í÷ƒ#ðµâÁâFû,™_2ÿy2‚2§#Ö¬Ëâ›H-cƒH)"wæ>•o”ñŒñU/bÖüEvú¾¬v³ij¿‡<œø¢ï¨toxOO¼%ÒÞå7ó$ò`¨ÈÉTQ‚@íšöcâÞ—c3u¼·È0 tã,ÇÚ²¼ðWLñF™u¨ ~Î-cY@o•q#‰õ…yD^#×ü'­¡OC.CÀˆN}Hý+–Ô~#xóâ5Ükyw§éãÌ1n¬{–bXç·jôAð_ú‰×~!êitåD™ŒŽ¹.Iwêè+ ×woCxÉYü\ðÆš£Møwá°.~â-ãÜ®\þ8®—ÁZßÇ+?Eâ[=N ö¶axHÊž ´G;‡ûÍ‘\[üAÓlTi¾ ѾVÆÒ±íq€XóÓ5$š/Äkëawâ{µÒ-%ûþkíf>€ŸÔV.7Ù©3ï hšŽ|<šŽŸ¯-ö³nÍöØc Êzî Óg ¯Vøy¨üyøC®Aâo‚>:×¼#yô:˜þÉÔ§²ˆÞÀ»ymƒi¤T3,N |‡+€?'í|Eà¯jQ_hÍ5åä9O8;&ç$g ¸?–A¯µ<%ñÆþ!´ûvƒaum%ºš;²QÎì}Ey•hJ ¸Tæš´ªþþØ?cÿÚ ?Úq5x®µË·|K²É¿ˆ¬îå2ÜÅrÀ"‰ØÉÊŠSÇîË©þ´<7âï„ÿµ oÚàYƒÅž×ôɬ5-.tYE틃ö­6ò. ¶«àž@85üÂ~À?¶wÃïÙ»öÌðÇ¿ú ¦¹áK«mJŒL,¢Ôaû3ê1¤Ã=ª3—QŒÀòí‚©þ±k/ÙãáŸì=¢i_ðRئ=2?„‘èöÉñBÐÌqZjz Ûx‚ÇËýÔ—–A·J ¹·%A/hHW”yb·{ªr·f7ß´ì!û~Î77–|¬ü0ñ ÷†®µûP¼›PÓµÝ:p¦Kˆ¶å„Hå!ƒ+•Ýž+ò›öyŸÃ³iÿð€|B”ÙxgF5Yt{sžWž-ÿ¹›¦#r•8 p+úÛý¼¿d?†ÿ¶—ìóàÍVÒ]Äk¹àY’ÞÇV ǵÁÁ²Ôв0ÈÚù^Œ61~ÇgìÁâo‡Ÿ~(øŠÓÆþÖõ‰¼7«é:uœÚn©aqR2ÖYdM‰4aeCg;«é0ØŽh¦÷<ŒM 3Úg_ƒ:'íe¯kš§‹uýI´‰5}×Nµ³¹µtÙwWÀùä(Ù‹ ¶Ö$qòçþüIðoÙ­¼M&oÚ)¥1"Ë>Xo,*A<ãÞ¿J?gÿøÁ>'ž‰Zu½Æ©^¼©}e¶¶â[VV* }ƒ¾Hæ½/ö‡øiû ü8Iažp>¦¾ ñ³®x_ÁZ§„–áµ)£×.ät‚»-Ü©ÛÐïb>Bx sÍ}ã}À^ñ xkáÿ‰¡ÕtÙôÁ}Zâ%¾¡çc|ÑÆ¨±îT$íÈœõ¬§Šœäh©¥¢=fßãÑ<(þÿ„nÊKKûµÔ "ž)7+0FÏ(ê •ÇÊÍ|Gø¥xžÐÃ'„4ýí$Y㻳Gš?ºÈÉ’­ë»ƒ‘\N%ïˆ×î§l,¿4ñE6 ’8 9Ëtõ«ÍáM/XñݥƗ'öÆ—6dYe&ExÀ wÈ(À0Ú¹##Ç9^çLLÞ%­´–Û‚Û]¢áW«0û­žØÙ'‰n¬o-dKhÌ`#ï1žÜVŒôm3ÂÞ#†ù¾ÏŸ©¨¹HlßwÙ¢àðà^yÁäúW=ö«3$zVf·”~ä’@ ¾¼V-šBx7â÷Š<%¥Þhº3$×÷ë~Ïü^b Œ(nÊ—k“¿ñ­ä÷W¿ÚP ·ï½Þ?ùjŃsê9® N¹¸)qešÊ5ÄŠØù[Üç=êž¡zö÷ËcÃzG®~b¡À'ž>•<Ìj¼ÕçñV ²YËöw*QÆB*ð0=•fM§ê3ÑßC±Uûk:CòîÚsŸëšóý?Y»µŽÞ9’RÏSÉÇÞ´¯µ‹½íÈb˜³“ýξôšéö–:¤±_Êž\e k¾iàãÝI¬Û=:m:YRÞy‚Ûà;dòÞ±//¥ÖµHg¿>rHRW p>lôÇœTÒêp!š;™6$¼Èdêȼ"ëžÔÐŨB×kot¡Q‰bÊ?_ÄVíµ¼6Öïa'’цiõl~=ñÎk’Šúæi-o#Xч8LF=µG®Is¨Ê·ÿ;HX²*÷ŒqÛ¶L•м‘Øjw á¹ ’]]ÄÆ-„&¾ó¼ßšã.¿´µ(ícѰò¤ !9ÚÈ2í9Ȫ÷Úì:—¾É™‚À Œ„õ w®+QÓN¦ÜÉ'Ì%;„‘œ6O^=ëštµ!£§×µMI,ôù®n%òMÖè ¹Oâ8û¼ç"½*Ý®F[xÚÏl(nUÙ~vÁÃq‚£Ò¼áεá;OÜiþ7fÓæ’Ý–ÚíŽèÕ‡TryàŠõ+ÍN+Ò.—Q’Ñ¥ÔBÆV!ó7<â²p%”gÔ¼C{ ÇQxõ)š6'Û·jç®ÓÕÍxsâ–°ðâòít¨üŸµ8ùe•‡pkÅ®ÕeÔu¤H‘"XAŽ ‰W¯>ÞõêKÓ ª‡”4øBr­»Ðúþ•ÑJ!³‰ƒ[ñ?˜³@¾k-ÂýÖÉÈãŒu¬ˆ¼a¨Åvoä¹k©@*rVAÔÀô¯DÓôË«M:{k†6ñG¶#!ÙÔä+ŸÓe°O’Öö=°¢’`óŒOjè³[‹™•,¼_qilÿi·‘&ï%³ŸNµèÐøžöûʹ¶¸Xa»U…ÑÇ;‡b¹ÇÇâ+‡Ô4Ý&Ú$½’èCRî}}‡©ªºeå’ÄÒZ>äŠåd–ß«ìO~ô¬R=/:¾«4ÆùãÛåÚÙŒ:ã€>µCPñ\—‚KI-îér”å|ÃÔ€ëÜt¯5Ô5-RòKY¼Ô,ËŒ„ns’?•/‰/`Ь¼A¦Æ k—1«1Î> ríÅh•†})à/ø<#3뺱³ÕíîÄPé‘Âí$Ñ2%G ­”mÄ8õ©á¯üBОëÄ:›K«‰LpLÄ4‘¡!}œ†ìFE|ím§ê^$¾¸yl¶p‚ÁNä드qþµwW‹¿°­¿ÒHUU|ÑœŒíå¹==9¦·Ïc®xŠdOÌc»FW2«˜°în9Ï¿­} ð_Ä ð£ÆÚ~·©I&¡»°Þ¾[`èQž"F2 ò§g½|ÃmâËMXÜ4qƨZ3Õ±ÁúóɯHðw‹O™Ûk(Y·³¡ÎÓøÎ3J2w¢¬~ÏxâG†F“ᙼoª^iþñïÙÒòÆ$™ìæ¹`q,n2¸lJåz+z׿¦¾–ºu÷ƒŸPŠ nÊK™­¥tU³ÓäÊ_ÈW£ô(Á¿üãöÕ|¨i~!»o1¢´ûFòf…òZ<ô,¸SÁ8¾Ýðn§ñVÓÃ×ÿ´/`Óílníît»ëtË\Ed°¢¢ÉL™ »† ‚k~cÇšf¦ø&Çâ!ñ¿â~Ò$È€G`’)Èß±,yù€í_øûÇ~¶óøüjo<¡¡¼rÒF@  ¹ÎÕcÐë_k|Kñ¯Á¿h~%½‘·ÒRY«Çö©åp îø A=¡Æð¿Ä¿ ü+¾Ó´M'à¾zo Ó"µÔä‘“u),ûwrY ¸Âž‚¹k6]4}±áÏ‹ÚçÁ߃>Ð|ok|eûLö»Õ¨òÔîǹÏå\U÷ÄVÕdaöGqÊI ƒŒç¯^ý«‘¹=´`¯sÞãwõ-&ÛÃ^+¾b‰štW†å%XåðGð–ÆyWIoñfïP‹ívZ­Ì·@ám«° ƒ Ž Æâ;×Íš­Ö·¤[%åÕ²³7Ì’ï'ÿïSè“¶‘§Aã[Óìñ͉#ûÄ‚@bG8=kJwL®S쫽{ö…ñ·ÁÛícÄúµÞ±áœXܤ—Ã1%»ã˃&àÅX €Ã¯áMµÝ®“ow$’yE#9‡Ê¨@eçäúÔú'õÝ"ÇWÑô‡#O×@71¡!ÚÃf 'Ҫ˩½­¬ƒÎÛ"¶Fñ·ƒ“ß­nØšÐõ¯ˆ^9ñÜÿ í>jÛCáû‘æXK¿—+=«äÛŽ9<#Šðφºüé­ÜhVSñÇ÷ºs^ ãêsÃý´›u<Íçy­ù^;~_J–ßSÑgð]»[…fš]Ó?úù´3ü«È5”—YÕ×Hœ4i´§]¨„ôx×áÇÅ7øg³é>ÒvK§äÝ-÷Ú"g$ &Á@£€"¹g¹ÓxÂßٓℼ¬êôÛ(¼Ûë™VyÊ)‚[H‹¸‹iUŽFË,±±gfs_pøáÔÖ_±´ Û;Ÿ µõχUY,õ;9ó3j¨å›Ê›ý!B²° Œp+ãÝ3ã¾³à_€Mà·Ó6꺅ÄwW tánmË®mtHPlI;OµzWƒ?iŽR_Øáö¯§|:ðÄ{ï²è~ÒO™£GåîŸO·Ô]P‹gXç™ËB…ÌhÁ6âQg/ÿð?ˆ?g+OÙ×Â~1ñ”^5ñ¾ƒ®ëwÎâôÃ=ºÁäBä¶é£™¹*B ªà_kügñ¯í‡ñ·QÒ¿ki¾k~ øÁ éòéx—áõΙ»Sµš!ä%ö‘zgÛgÂ%7OF‘_™?ðR‡Ÿ > x›Àÿ³÷Á+kKm?B ~×F†þPX&.§i$W%œ’ISÚ¿Bü%ûcüY¿Ð´Ûÿ…:mœ~$°ÒmV}Buͽ”©†Bˆò*a±ÌŒz±8ãfg„|.ñìãáïX~Ë?´Ÿmmñńꚷ‰µ›á.Ÿ­^]L7°”•X—!¢á`.Ò¬y~Ͼ;øãûMØxGö9Õÿ´þ$èzv š…ïŠ5ynôÑ­AŒ[Çq"É#O#K‰ùäàù/âí!â?‹Kâ­Xð§†þÍñWUŒë7ˆ Õõ–—¢Ÿ)m!‘6$× ó¼‹õ›H;††>#\~ͼ7ñoöøµq¦x‡^Ôµ k?í +}§h<’BËpñI²Ú颌 ?04ªÆ YǦ/NÝ6‰öW…ŽçG´%Ièv²‘~lzWïOü“à—„µâ?Å\êo¡øGÁèV‹}&•6·­øVQ—©$M½”&6<’^mÀÏá—Ž¼Iá+OˆºÎ­àôy²^Øi“Êe–Òß˺y’äH$l“Ÿ˜s€ý•øÃð^OÙ?ö!øg®x“_µÒüMã⥼׻ˆæšÙ#†ÔÚá<Ão‘ yr¬§' Ù4“v²<àgÆ_…ßµµ§í]ã†ñ‹ð³HŸÂw:F… ®Ÿd/|Ÿ²Å{åÊNåYÝ2ÀdùwÃÏ üFñ?…ãñ;Íqckq|bÔ|Cë{ÜN|äIN Gç1D대rEy¿À¿|6ñ_‰<=¢|TÖäÓ4Ý_Zº—]¹˜·´´]âàK^J“Ÿ)$$`€köWáí'ð’óÅšîƒâEÐo| áßµ4¾%/t­GJU?³¢VX÷«dÉ•Ý(=2#w5äksà]SšoÁÚë^×çÒï$º»Ó¥·²½o>Ëù“m—‚ùlAN‹áöâÏjŸ >'x‚æþIÁþÖLoQSË JË&ã&w`yÏJlþ3Õൻð_/¬5ÿ-ýôÚe–¤‘Ismoq+:C#rîñÆDeŒ¸®s“YzïuŸÙÛZK ØÏ¦Z4bU…$„ð1Œסɤh©2ž½c{â[+ÝvïQ¸¹–Í¡Ó"šønþÒÓí²!xŠð<³WÔãçzåþµáÈü©cÉÒÅi¹‰Øäí팎iº%µí®•/‡á¼¸Y³ r®r#~øä §ô¥ñá»M3E±Óµ®µÛ·q?—û:¬[pJòÙvÜr1Ö¢{¡c‡XŽêÒF¿—Êí*ƒÇ½XÒ4»InîšövKu?08î:ã9&¹ˆïäµ»½Ò¯Õ|çc¼§ÜÈ<¡÷®óÃzÒuõK˜#dXÁ!8bà œô®{W1}oÅW¶N—>±‚-X+DÊ2ìÀcq>ü×.¢áôM@po·lq´ƒŒàúšéñ ×?¥ÞøóÅ\ÚW†T´V+î%c•xùŽã*Xoµ R×NðE²}®îæÛÎ.ƒgî# mç''<ŸJÆŸBþÂÑ%еÖiÚí¼Çrª¸<:t}('C×îïõ)-ïa¸–`ÂW·L«½Ø ž{ô5f=~ÏZ¿ eµX×-; EvbB…÷=êæ¯Oám&y4ÿ8[\ÇåyËÁ+Ð` Šæíä¼mNêÚöe†Ú0²´“&W P€w$ç9Ímx¿Æ¾³¬|bñFëiröê#PªvE$öäûÒkºEµ¾¡e¨é×j-¥‰aа¬ª¤©f?íu®;ø/!ó¯R+£{Ù|ÈÉ` î  r­‘ZÌñ%ÕõÌÖ:ö™{ÉDÒ<ˆxGÊ1ü;EÉž—©YXxuVÞåZg˜;¨@Wjç.z“ïX7ZLºußü%—ó0†ü«T™·Õ¾g`qÐÑÔ%·¾°²Ó#»K[Ûhßk·Í½HOoC\G‹µZxzK ˆÆ&“`y2ıÊöPHǽ\Ì]NKc¤lxªD[+GIn. ü…P:‘Ø œ×%âYj0&“4SZê6 12FbÈŸÄsÁ¬û‹×³³ÒôßDçJ1Üù‘¡h‚¨ÏÍŽœŽ§]FƒŠ´/‰|aÒ4[_d™ïn4×áÝãh„øPAXÃW’ ¤pZT1ͦ§‡žãìÑHÁi:œõDZ[ÿµÉõ-[þëÔ+,ݤá°ñ<'q×# Ãèzlý—öÌó,FH!‘!o”L»ÊI $« yÇJè­l´ÿ ëpêpKËv͸·H e—#ŽÇ4^§©_Z&±ªC¬é·‚“/›oºpr~„޵<—~’Öi¯¯¤äýõÌq`Fª¯ãÁ®Oñ²Ü6 Åż‹Ž1 îcÅr¶Gªë—WÅV=0)˱ÎIçhÁÆ3Š 9‘êr6‹ªw§(/)Á8Ú Žîü^µÛxjÛMÔuk­FÆí#Ó^&[†|fìzüÄàzדjz¦¢<¦øpÎ÷Iv/ÈmíÀ9äc ¬}#TÓ†½ªè¯îg›mÊ', ÊÞÇ èõ)ôký6uñ‡¡Ý®Ô•%å$$ô*y»c¥yšxvoëó‰×ʆ2ùª(ÎJûë^±caáM?ÀÑxµuW¸tœ¼±»Ë8ǩۑ^Qâˆzf™ebtø^Ê=XO-üÀüÖ͸–<Œà3w#¥; ØôézŽ­-î—cDö¶RçdÄog ŒxÃsžõ?ù<ö÷‡u‰¦—^º‚ÇOÐlm£3Ä×s;ù¬X‡E Œ‘׊òÏ k‹ñÏUÐWP¹±Ðù‘Ë÷±µ~S.8ÚÌç×èZ¥®¼5káÖ…sŽu¶½Ò?³Cyöw¦ËŸ-윱ùHäç֞Ǣx´ø›\ñ×´ìkžÝcœ*´­2æEY•m‡ºà×Ϻv‘~ ¬°Æ£ÉvÁßìrGP1_oxöÂø—mã_†?´-Ã~“ᾫ5¶˜·ºkÍæ]\ÚËÔ÷m3'Ú·–aÁNH¯…¯ñŒwÚ?Ù,5]ÚY$„Ímp§Ž1˜ÉáKÙWǾxSö„“Rø_ã8¯4/ˆÞ»G‡QÓ`Hõ)ìݘÚÈ+.àU†H;Ê1ÉCüñ¿ÅM_ãGÅO‡ß´?‡¤ñ>¹ Y¯‡.'0É ±Š+u„‘q%Í´èÁWƒÛžk’YvïèvÇ.¤Ÿ½#ïÝSöÐÕm|¡|døÅà WÁ·~#ÑM­ŸŠt±m>£Å©.Èçƒ÷Ÿh· Ю߻ÈÜrØæ¼¯þ %û[jÿ´7ìkâïÙóàtý?Ãzo†u+{bîì/Úæ´h¼¹í †2 Ñ<~lnò݃žs_5|,ñÖ¿ðâÃJýŒ¾/økQñV—)§x3JÔ6$rè2–U·er¦CnŸ!ß’I9¯…þ$kZçÃï¿|ÿ ;xEñLÚ¾Ÿw25ÙÔ‚ÒÞ2K˜¢;7~ì®@úÒXêíÚÚð4½õ:ÿŽ?´GÇ“ÿwñDwšf©àVòÇHšŠ—Ôô9â’!Üöµ»Iª§”v™oP6~üfø‰û4x§Çß ¼æ…/ÆŸ‡úmÎ…³æ+pȱª ÆŒÆàÂç\ùçí‘ñwÀŸÿc{«…×z[CâËM×XÐ,gçK×tÄÜ÷–c`Ýk}±RU;S~%3=}ñßö·øEáˆZ7Ä/ j¶ÚÏ‚¼Bš«¯îbÒ4ý·6Ye?»mΜ)e¹®¨ÎN>óÔæq‚vHùsÄÓé¾3ý‹¾øÛÅwÏ7ˆ¾|D¸øs§Æ±„Q iѵÜqœN<Ó!ù˜§®k™!ñÞ›û,|A¾Ò5¹ímüaâ;+VÖ9š!©$óxç BÉÄ2åG9ùëÇ_®oü ‡<3;Ã£ßøTñ4VljWñÈ’°aÉò×<3ךîôÛ­GRð›|<Õl®u-/Ãvòx¤ˆ‰âd¶0´²îÆèÖ2G\üÝ;ÔNM;¦JjÖ;ÝSR½ð¿ìclš„ŽËâýe¤ðâ% ¸¶¹Û|îÄnñ)eUæD5ä2|@Õ/µŸéúê[Å¡ø&öîÚÌs±×R»Žâòi™³ÖC¼Âlü*/Š~1Ö?áøià¹U-mô‹;¹­Šóö©o$GyJ0ù J|µ>ŒFy®SÖ§ñƒ|/á£nmìü-©¶|†{ƒª]­Ó³€:!]ª9àã¥f›î-/sÜÿi=:o|ñœ ý±£ëz†¿y«Å6øeðö§9[A ¯Í·Ë2:ƒüQûŠë¿g6ø,Ú?ÂI¤½»ñ§ŠR­ÜørXVóT¸’`3»ÛÆÌÈ7+ô9 W†|_ø¡®üDñ¯ŠüH,DZf§xzÆ;( QÇm§™L ©gË)ÞÙÆï³1ñÏõ_â—‡~ Ýø ãÚ w eRšÃ—¸…á¡RVÎ"ùB@dÆ;Û„¤´ÜµQEÜýÿ‚½xëáG>Kàk;¦¸ñ¯‰Ò*шtðÆ9nGîD®‘t.øÁà‘ù•û+ü ø_â‹ïøÛã®±qm£i¿m³xvëh©ö™¤žCÿ,Ü„‡Œg'ŒtùWã^§ão‰^*±øƒñÇ3ø³Å~ ˆßjªšæ;[o’Í3 ¢,EYØ @n=Ív¿æð¼!ÿ„AÕµ FÃí¶1 ¼Ò#0¬a¼±¹I!xæ°¥„tãfî]lb“ºG¹x/U¸ñßÁŸ83Û®›/ˆõß›FÙˆô˜,PFʬA ã’qÔšù#஥¢_éZ’øžñÒîãKˆX@ˆKÌFàI8ELdç¹÷ªÿì|7¡ÜÝézw:´ZÕüpÚMo–)¤HGšÆ\ ‘Çs\M”ká‹›¸e–Kk‹+™"fÆYݶ¸ú VþÉ7vsû_#Gâ_Š,ã¼ñŽ­m•w¬ Û§ÈÒ\²ªª€2Fk¢ý©> Úø›áÿ‡~ü5Ô^KKM"Ò[´)å©Õ¥ÎÈãR¿ê­†#“»'#åçç­6H¦ñ•Þ½j´¹i%û4„òÛ²?6zWs ê 6(×#bÜý®(¥+rÛA?ÅóÆ‘*Mž‰ûExóá·Š>.AðóÁ"ãH𦉥é:M”áVéä´F‘œÎ<ɘm Î’9æ·ÄoÉãO¶úýõºéÑGko‰Îöìã1)Ç©f'¾1šñkZõ¾µªøÏQ†7¾º•«”‰~lå³÷@}?ذ—XÔîe‚èùš¦½"Æy˜ùG\ÔãŠMj>fzïö¤ºÖ«ây¤ù§‘%¹/µðÇãÔØô/À?‰?¾Ù[ø?Ñ®ZÍjnšË\²’WºžF’¬ˆá¢U B01ÇóGˆõËùï,~xrÆ nt¹¼ÄŠ(ƒKs§.þ(Ô|-áØ¼3 Ì¦)`–È ‰n.Ü™'r>v}ÄòXñÀz͵û?+D³Õ¥Cn1äÚƒhÇ*ját­R}[QOZÄ#µ¸bm 2$|ç¶âÜûŠõ xu…‰†ÊÔMudèdYA9I‰ùÿ þýž¢uAð¿’]+Å::6œd†XfÝÚ'20çæ¡Ò|=ðâæh/µ ˜nˆŸr»€ãiëû1×nî>ò”7?t{ ÷¸¼ ã+ K¢6š—1Ý(™&y†ÐçæÜž£¨9í]”é¶´3•[ncØüEÐaðüÖ2xBcxbxÙZuM&v²ç“ÇÚªiµ›±YøwÂÖsAklRT½”+‘˪Žû×ic­ÛiÑÉ=Œ/(Ê6­æŠ=£‘Œç*¯Ú¬õ]5ˆ†a –ið[ÍnH<€ÜÕ*] ý¬{ŸBÛ|_øÇ%¬6–‘x?LA†[¢ªÝÂlï4ôøÍñïNº¹ŠßÄš-˜(«þ‰¦ïÎÑæ3`_Zñ˜äÔµ—Ežß¿'ô®‚ïö`ðôrEˆ´]#t®5ý÷˜Å{ýæoÄ⾨ðÁ¡ñCÃ’ø4=+F°ù¢WÖuI$ ƒŒ“ƒødã5èwìëàx½ñ'…cÚm§Ïfœ•듚Æppvd¬G2¹ò ¯ìáðÃDÄö¶¾ÓÀ;A}ŽçHÿ­}[ðHð|=©cÛh×÷ÚÝÌVÑI§Û'–«9uè9<šØ‡áÿÁ; ²Þ1ðü ñmh2GÑó[Z%¬/ã/Mðþ£¯§Û»CÅ•åO®)Ì~x)›Nð…ç‰ÙV+hQ¦H•ahíQ‰_,|¼sÆøŠÑøkY¸“Už×Ä“Ü]È÷Gljf%ÎÎÛó`qÇNÕú¨~ÔžðÕý÷ìët³ÜÜØXuäZ´…Rø8*fÊÆ$ Ÿ“$¨Á=Ey+jß!ŽÏKÓ¼/¯^K+¤K{(†2HÆw+Ÿ”uéWJ7•…)´®|³ÿÖ+ˆbðνw)”M*CŸOãÏÔÓ›Á°ˆÁÀÑ‘Æ?´5†v}6ç>õÞ_hºÆ£¯ë–ºZ‰c]Ò´›d³3,Êàðy(?:‡Ã¾±ñ%þŽú¡†+Sw«Ï$J¡±c§ˆInpÏÄz×£8jc­²9DðíÕ•ÒÙ.‡á}9Ê4€Ï7žBŽ98lýO&¯ZAâ˜ck˜®¼1lª~#NY “ÓæÍXÓ¼>Úw‡ì5™âJør÷V½Ž4ØÓ\ÜÉåÚ(~AŸLÖ®©à© Ÿˆ`’iŒÑiúV”ÀR/¯äØô¯j¿©,sìc£xÂ;h¦¼ñ§Ù£›çHít¸âb>¥[ðÏjŽiæ‚Ý×Pñ‡ˆ¥h˜,‘¥½¼K1ˆÔ°=JîO鋦ÂdÛ qôÈéÒ¬øwEXuoxDZÇM}R鿼Ò` Äý üê\W`öŒäãÒ|!áïx—Åúu¢Æt8Y[³fC‰V ŸSÈà ³áo ëÚäz.8k/ì1çÏ-ÁùåÈ=# €FxÍIg¦¶¥¥Ún’9.|[ªÉ8pAÙ£œxì¸#½÷ˆuUÓüyã +ÅËH“«+go-ž8=eb¹Ï-¸ÐfÕ¾_xâçyÔ¡}‘É)ósžåÈ•¢h—+šVÚdV¿¼/áK >Ïá}îÊÀÌÖú’ ç½qŒ×s|-׃a³#Ù£H™þãÙÿŽWYðÇÆ¾*ñŠ$º×>'MâkM&ººÓ­ÕcGê±TPIÎÎF+æx´Ñ8Ù¢ü5*O@÷q ûýáüëÛ|+mâ ü1Õ¶´H495ëûh¢… »¼0´ ?xžõåÍhz2ŠGÿ×þLò[¼R›cnàF?µõìñâõð/Å«ye,cÕV-€Uf‰·Äç¾F9àõòt‘$Û‚¹…zרh×pGw¢êß,pÚÝíÔ*JpIÿd¾ÕéoŠ>¸ð/Å_x\ló\¶£n1Ö ßÞ¦òàc¦+‹¶[m=`ºŠùAYbC÷ö÷Ç­}ûE[\_k ñéMÉ©hcO˜ñˆæÓ¥p3îÂSߢ׀j)»G4JC[¨¬€æ|i£Ûèž5¾ÓàOÝ.HÔž˜Ž=yÍjj©÷€mo7—M>ëÉ.£,«•8ôÝÅiüEŠ9ŸMñ¼´¶ïþù„€Ðý*Ç‚¬cÖ¬õ? ±oô›v¨ÆdO™HÏ~´ã–×/iª-ܲ±ä1a‚;cŒ ꮼM40yM"²·;WƒíÇCŠåí¢"O=BÈÚÈÜ®sþ4ûK >Ì%eÜ;1{Pµ´Òkw?e¸@þdlª[¡îzWß?µaâŸØÄšükak¢ø«XÓS…ŠãåÏR$‡ó¬‰‘â^2·O1n_å?sh9ÃßÖ§ðEëi2µâÆÎòÒì`üŽ:㜘­Ùƒl'RH@#;Šå<3¹ÔçÒ§muo$ ¯ÞV ~¼t­žÄŸFü]ÒVÏâv¨± E~Évªy†H÷ù¯+½D²¹´»Û€$ò_±÷?J÷O³x‡GðwÜ—PÓšÝÀ9[r÷<çé^Câ d›EyÂf[wó1Ó=ˆ?ᤑÚÅ-Äp3´jûFã†8ÁÏÓð¯­ÿe‹‹$»ñ^‡;†W];W‚2Û‘£¸?€Ûšù/V %ì2É(;¡‰ÿ1Ò½ïö_¸Qñ·J±™•†¿§]èç8| FAà}ÌÓE©~Óo‡<x¯nº ¹º… ‰e?ÚUTv ãÒ¾wñ|“i¦kzz’–Wà´„€¿?8ý{s_køBÕí¿iÏxkT?¹ñŒ)ƒÐ‰ hÿOƾ*×´Û³à í"ìù·€«á~:Ùʾ}·)Íth™õ·‹l­î¾7^Í£ñµVˆò±NÑ´NîÁ“’;µ} ñ§þ £âgþ ½¢ÁM~xãDñ}©¦—âoÍ®¥áÝ]®#±ùÞâáZO˜ü±‘ u"¾}›Q]CÁÿ |ulÄ-΃#?;!’(æ…{*Ç=«Ìþ9øZ;hL¶"H[ˆÑØ,ÒBHI2Tà;Àp)ZÍú;ü-´mGOÕ´€C=ݭăÔËθü«ˆ•AÅ£ûcœ1 òkÑ~ ]ŧxšrÃ÷sÃ)SüB\ÆÞߌŹÒ!±¼ºÐÏÊ,¦šÙp1ÄnÀr}©±s#¸m;LÔüc~–ˆêÚUäp´`óáYF}p¤×ßu ö—øNšŸÅ_ iºÝþäé"æÒ3 ã¢ÂŒNwœòÆàHÆq_ž¾–þ×[ÐõË„f·ûT*À”œg§ñs_GüÒ¾0êúðø?ðSR·Ñu­GW¸¸¶šêé,£y,b8…%‘]²¸Ž¸p§‘Љ]Ù"£fbüaø à/ øoVÖ|%ªÞ5­¬±Éy£êp0!L¨Ü#Të~µæž,ý—þ2ø+@ÄwOtŽXeÓ?zÍ ùuáÆ3ƒ€Ü×Ò¿´NƒñwþS¦þаƞ3Ó.¯<7¨ýŸÊ+'™l—°0’’Mð²¶àïcj@øÚoˆ?| wm?‡umBÄIl’*´’dV1±D“*cܬ¥“åܤg áEÊú–ÑåÚ}Σ4ök&ä?+. ‘ØçAëN6ÖÑÞê·ÖÄ´“€0qŒwê æ³pKbùÑÜønÇâ>¬³ ¥ÛøWO`«-ôè¾f gqœÿ²«øÒ øWU[ÝOÄ7š¾ªçå]9ÜßÝfRYèF åõ+›½~ámÕçqbj°Ø‰ûM»ýoØšÔït?ü 7Š~Zxâ§DSü.Ò5GÕÌð*>«d÷–ï–[Y‚ïµÛ!`Ñ–”q„Ç5æŸþ3é>øÇs©xWW’âk^Hç– ‹ËºX•”`¾CnÀ+Šûþ Eð§Sýž¿h‰- øÒÂxôùC%µ¶¡cseûÌÓçÚŽMë…óø×âÿhïàk/ÅבǩørîÇO°i†Û‰íüÅ+Ç.ÂŒóœ}kÚöŠKCËq±éß üKáÛ‹ÉtÝoNj—ìÓÅv"–wbJ¶yßèzWºxâmO_øzß³WÃ;©lôïˆÚ†—Šu+øÏh¶· ЋIALd–2å\:ðØšøSàÿ‰žûâÈ3ŽÓP¸H§(#PT?Øè} }mý§®êþ0Õv#+2’O~£Šõéß ´ï‡z§Å¯†šìZßŠí ´ž=ê8Ú®5‘ Ž-œ³åÈT± ’ØhùÄ —WÔ¤ûDB‚>ÖU÷ƒ+IÎ2 kêÿxÃZ÷‡ô hÒ§ö¾³ª[Ci?qfÞIfÜŒ«Ÿ.x,Ey'ü!ºï†´ôŸil.Ñîy“*7e”¢)8ÚOA]gÃO x÷Tñ\V¾±[GFµþÖûôŽ[›H9vŒHB³ª‘€JîÏŠçh辇=¡éº.›¥°µgŒ]]O« ;F>ás‚ÇQ\寖ÖR[H¬X&Là¶Ðp¼u½—ÀÚÞ=ðoü%Ú‚¶¼Cà÷°¾³–ÒÑn<ÍÊ6° mPwIžÝq޵ÇßXkvÉ$¶òÆW‚B±ñžÂ¡Åܲ}x_ hܳ|Íœ¯–=èqZºdºÎ‘£¿‰n‚Í£É!ŽIŠÌ‡¢²ŒÆx8Áö¯2†ÛT"Õ¥ÁMåªg¾8fø{WѺ»{¥BšN¿ikóÛùxCòÜÈsëô¦e„ú-þ‘l˘…Ân!•àZóyd7÷p\LÑ)ˆ´RE‘ž¸Aë“È» } ïòƾŒ4¹1Ž|µ'’=< ã|áÍ&ÿO¶ñnŸ#$’D–?¸p9È#­Cd6ix{àNãïŧø¿YmÏPId¶»! Ê`RÏ›²ûyÅx_ü©x:òÃÃ0êI©éwìÒY߀UÚb™‚L q äÕÚx#ŧÅö ñ<&àÄæOÞüëæR®墑€@òkþ"Ö×û7[”ÉÀ˜ØH˜©Ïl‘Ÿ`zT²&ô9/xšM7Ruˆa"lÎ?¼+§ðÝÌÚµí½¦V-͸oR›p3p}k#â¶‹¤x¶ÍÖÙ„WPe\¸r®0Oø>••‰!е»kë÷’DË2ÙSü9íR–¦'×¾ð¿…4[È^‰æ–X˜¸G"0Ï÷rzŒWGu5̺•­ÄHPmXËå‚ãðôë_/ÿÂÊ“[Õ!‚îÝìä‘_ò­³ÐŒò;÷ªº,÷þ$ŽïS¸ºˆF‡jÄNTpCZEê4{1ñëé3X6Ã$*ªcÇÍýAÁ¬4ˆÙyʼ&ìªçvÐÜq“øÖG†­­ã‘#¹qÊã¾Eox§L0%²ØÜ'—užžœuúÖåœoŒî5"}b)²Xà1ù2O¨ç¥`^ë7W1AvÕ²…ü–;›¿Ê1£®Mjø‡Ã—Ú¥’GbÃíör£Œ¸XßiƒÇJè“ÂWä2Co&\’’NXsAކ€wÃôÒüEtÖÂf1ª7˜¬pÁY²Nv•k]Ü]ë}Æ·º[¦ŸÌ1ƪ¢‘’Øà’{Ô:Oƒ5 9‹EHijI¸s¹€sîy­«Àºf‹ympå¾Ëlñ̸ÚHÛ’®iò°*é“kêØ­¸1Ü–k‰”ïUUÏbO®Æ+í(Li%ï/\ þoZñýÄ6Z\ºw†,‘äŽE`¨¼,¸nܸýkÕfÒd3Ë. æÕbMÈ¡g'ïþšFÔZ½ÏöŒtÉoÝn+òªcž;t¯PµÔ µ±’Ö˜Ãr¥_+…ç õ¯ì®æ–P¶X+†ì©ô®‡PÔKÞÇkc3"°`U—ålǨïI¢Ï®þë—VIˆuXÍÅ«HŒ!í`£å8ê:Šú‹Zñ¦©®èš­ÝÜr\¥Ìq,ò ¢Ë+¡A2 ¨éÍ|/á~â 8[ÛyÉKˆ¾PÊGÝõw®“Âþ3Ôæ´U‘äÿ¸%Nç8ùGûœð*š"ÇØpøÞÿìv°k‘E¬ ’»[(â-ÇæÆòxô¬#ñ[Ķ7i¨xi!„Ãuæ®#oGÎIèØäãŠùêÏ]ŽÝ%µ¹"ŽcÀÙËtluÁç·®ÓWÖ´a¢-¾™k5´A‚ ›†|Œ¶G×úVšEωÞ?Ö¼}47šæn-­’_2þÎ!îvãËEÆpzÚ¼ƒÀ_Hœä ù˜r ÆJœvéæh>1ðOá}ePݤ¬“›IDЦÎFXþöxàƒ^ðÛLðÞ–v› °(§¨rqœÔT•‘º0õ[Í^ãK‹]»½ò“P¹m«,3ÓŒV^•ñ[ÑWTð]¥âî¶¹u[¥åÚA•Ýœàçõ«¿ü[¥kzdShñ7ö[J¯4^N$Çu?7sŒõ¯˜ty´½?S™uÖá" æ%ÉÆH“íéšâuZb?D>ø§JµžÚçOŠ5[70£G˜WŽN 5×ë5¹øâK[›[èb¤Wžq¹ÙO'hxí_š'ŒõµÝiv­Å336N+èïƒúf‘⫤ør¼×%‰®á`)%Õ0 g#,uÚ·£‰“Ð-sõká÷íâxÊOè(šÆ¥žÖvBbY­£S€W.sŒ‚8÷¯b»ñgÅÏŽŸ³§‹?´¤¸Õu_ë6£Uº’B’iA°ªÂ¨@X‘“¸üøý™>%x/Åž*¸ÑõKi#›HˆíÃlYx£n‡lƒå<ç>â¿L¿jk]#Ã_ ï¼iû"鯋ptf½ÔôG„x­åeÌ—»üæTFHÑ2ê'úÔj]ja8v<Â^ý–ÿf/ÙüSø­u¬¼¯Öéu Ô‰§]›šU¸XþD,FåãœWÛ¶~'›ÁZ°ðÇxu h$“Á÷3âK‡ƒÊ +I4*Á’ NE~ZüSð'Ãï‰Xhös.¹¥ÝCý©Ü´°âT-.âÄ+ÄŠØFž+Ê|uûSk¾0Ò´Ï üH7>/þ͹ƒGšä#}’rmßzm©Æ3’1õ­]DS{ž±ûT|Gø¹ñ#ŶÖ×Ow:|—VetÝ< ³·“.FYRR>Wc†f¾*ñߎ<{â­gÃ^'øú²êVw6ÐÛØ}°.ùílQ£€SœO×Ùø_ãÞ•oâ=sCÕ®KØëz\Gö˜Þh¹1³KtŒ6UxÆ3žxóO‹^ºðv·á?‡ìöþ$³±In-ÖÖR3’¿:d˜[»ˆ#‘Åd枨Ò)£B÷Äü'¬XÙZx³ûNÆh’ãUŽÚËÏ….òCÀŽÀfMªœîÍr‡à¶ŸãŸÍñÚÓû6Þkɯ,™£ie’œ²’#ÁD`8e9^Ù¯–cÑõkû¸ãÒmíþÆþuÊ[«“”Üwqó0éG‡~+x“Áþ¸ðeÄ—Ë Ü܉¤Xd*¬W”e8È(@ऎA¥Ì,w?µö— ¦‘áiVPÙiÓHÖ0Ë%Á{בã$º4uߟ›¨5òæ­ª[\éóé·W3¼SF-¼©%o*X×d«œþ Ú{óT¾&xÓFñ¶ŠK½FíífÈ“P;Üí裯85›G«éwz¥ËE$VRG›uKy¼œäãÓš—$Ås¢Ó,u)ôI<[¤¯•gi2+lÆ‘  ð¬ çkéÙ›âOÂ/ühMGã¼sKáH¬o±äؾ ÿm H?s³‰ê9"¾gÐüy¯øql´Y¯¤±ÛíQÄÿ¸3F ÂíLv¯~øà}âV³w§Ol, • ‘.°uV9ù' О8¢-·¡j ì|ÃâÝ2?‰¼c«|8ðåõ熮õ}Sìj¶ìÏœ’»[,‘}ôÄL™|¸ÃŠú Løwñ‚ÿûâgÅ»‚ÑÙéÐèZ$ZíóÜÞ;XÇ— º1‘a·UÿV7FÑÔö_ü='Á­jóâ¢Za}¾9[å{p[r”8c¸“‘×W¥ë^(Ño|=†¬c‘4†|\~ñíßwÝÈÊ¡}1È­ä´Ôí†F7–æÿ„¼;§hÞ´Òôm:5To¶Ü´Èhe`?ÃÇÝ÷÷®GÄÇÀVñ[E¨è°J÷I=ì±  “Ì倌vÚ2¿^f=_[ºO´-´ðù,3ä‚%1Æp:œóë×Ê?ŒæÒu´cÉ!_Y¸Æy޾•ÆÙM+,¶Z=®—è)þJ€cìŽÇ¯CÖªÞiÑ-ƒÍ¨Ïö¥ºD‘dÝœíöÇhRyF$*°[kºÐîgÅÚƒªÍ)´‘˜ÎêÌÆ(z¼`ñúšà<4¶×ßÛÚtI-ÀŸ«çø‡Íòÿ*Ü”^Ç{ :ošÒ#4`I_0ÿ/AH´Ïdðlj´­vóÄ>.x'°:x0ÛG!¹äŽ9©µm5½ ©äœO:‚VA“´ýÕéŒçÓ­Q“U6ÖÒXÚMñ¢‡qBs÷»ç­QŸUׯ&°²±V[›¨ãƒn<·>àòc¯­sšÞ« Ö¶êÁÐĉ×$„‘·?ÐWi«.ƒ§i:jéò›†·Œ4ªGÈ]°p ë»Íyf¨ºÌ·š…ÌÒ}Ùv´!T“~8õçšÎŸUñš#é3C¿9*äve—#¢‚dQÔ|i¦ëö×6O°-æÄYVÜzdz·áËëuÒãñMóæ;du\ÈNä6wd“šÕ [^xf?Qµ´Ò•˜3̺RÑxã®}냑u +İøjâî»mRg±PŽ#,¸9²rÔŽL­GâÛØÅ½Š8ó̘Oï`ƒéÞ¤ðïÄ[Õ)¤ë5í½È9ŽUì½ã+Þµ—OÒuí’ióEv0ât6vêò;SmRÓ#ðî£W |Áš"§ª·U 󞇡¢â¹Ë讼7âQ4ëYï4 ¸žm8ÞÐ/ÎO ’qŠ_ßxŽ;“¦x;R1™ ky$«#ÿË1‚6’#µO¥jvÚ^µÿ¶ºñÉ<Š|©Øñòõ F>n™­y„×7Ú,/4nÁ¾Ø›Ê‹!<‚ <çÈóZ-†k}·WR‡MñQZ ‰bxH!ÚýœŒŽk&ÖëP“XMC™‰‰ä0)8;TrÄv㎵[ûB ?6ëWfžôJ¸‘ßrºç#çŠë/ ƒKñ4(ÓWlrœødqøšfRkòC%…”{ßQ‚ᘩÎçˆòIëÉ$[º´b²M,9Ž$D`Žx'8z÷¯#±¹…¼?g6­hѽ´³¤!yá‰ç¶0x­ f J¹uH¶HÊôì@õ¦>fz¶¥­Ai©iÏv4–Rùžꌟ”~š“ÀzÍÄ:Ö¹}oµîïÉÞª¹`²±%°xíÚ¼û]´‚êãí01Hð$\œàçÈ®ÇÃ7ÑK-Íþï²Âv1”óƒÓ^¢®áÌÏJ—Åw§TÓ<å¢húx’ÝYS-+\ŸÜv¯™hû›«èÚ¸‘`°[§FŒI+²ÇL…N>¦½[׋<Ï~ê¯Øc!rÝýr:â¼×ãí®—wâ•ñ%Ä-{µµ¶œÑŽ ¸ à¹ÿkw¥kI­EÌo|8MgÁ -þ(¬°Þ[ø†CDÔìå$‚cµ2²†VtñÜ×Ö?>;Ã6üS»ñ÷„ã yáÛÍ6ÚF`Ïo5ȉ٣VÊaÞ0œ—šðχÞÑ!ü2ñW‚ÚçÄ~1ñ¶›â}/SûD2Xè‘İý© RËspñÈŒUv4rüÄAøÓTÕ'ñ—‹´›‹«}‹ö—¶–#Ôg–éŽ=«µ CÄ×÷¦Cj#Ž m¶ì¸,Wðï\æ–môÿ‹št:££Õ“Í"Ͱ…nÿ7oj® 6'¼µ²kïh׃Ûh÷.$xÊ#¨’¥¾\Î+öwö•ø¿ñšïâŧÀïüð.”ÞÒ4½4{—Ô`oìûv¼Rî"VTb¡ Ü&¿üö{テÄ2fÖë^Ó¬µ"Hù¬¢½‰®ú~å=ñÒ¿Xuøâ‡í)7Ä} _»ÔÖòïP¾Õ¾ÍxV(fÔæd¶²e‘–ÊÞ!"Ž9<š¤´{,Ùßøê_ÚoKñ§Ã¿‡žð»áïÛê)£i:idJ˜ ÕÀ† /'šû¯à'í=ûg|@ði>ñ‚4'Ф62é–†­}j* ä¹_9FÅ8èsŠøöbð5·Ä/xÐ_ÜHöÚ\­µœIq++\ËjXà"{×о4ý˜¼®ZÌc—f·ibfÓCÆfÇˌ俖DZŒu¼0Í»µsâRÑ3еŸÚKö²Ñþ*ØxÍþ/\èž#»´‹J:Æk§Ù‹{ W’KtÈ·t!YäË/ócv8> ñÆÿî¼IâËkÇþ%ñ„Þ#½µÔõ^ÊúHî5ƒ Ãö›‡¶òØù1 …v`Q€0+wAøwà_ˆz݆‘Ưٔ³—O/dwª8Rð¹å[¸?…qÚN­«ø;Çú„VÖ:~¶óø^÷2*Åi<[•ÚeÚxYyö®d­°£Zú¶pÞ6ðý§‰&ŽÚÛW×µilÒÊóP¼»»šÙd¿•$Ò±àmaê0y¯-‚ÿ÷¿õxïtk´ÖÌamVFyÌ’…MæVc•¹9=zæ¿A®¼-7ƒµ[¯ëÖZn­áð±­ñ‘縣cý*ÜÛ2.>à$Wã¯Úzò…_5_évv7žñNi­[PÂ5”Œè\±(Â5`s““JTU‡õމžcñÃVmj-rk_Ûè²ÙZézƒÄ߸3C½’ënC2ž@àr+KÁòüHøðއke{¡i÷Öúæ«&^Y!¾ƒË·D€°D¬sÁÀŽ ¯«hÿkZçìâoŒWÑGk$ðý’ÚÝbUØ]Ê‹4’>wFô'+köeøsgwû êš¾ŠRÔõpk·"(ŽÑ`0ÙNÝ‹¸ç²•$'U½OÊ=KS?~(øbgodŸf³ÓRÞÜl€¤ ÌÒ²àgQó‘èjõ ßjÿañ-Õ¥ÚÛBÝô3YNpÑ(î@çñ¯!ø#§Éâ-jî™­îçÒn&’FSò„‰rûDôƽçàWìùãOÚOR¸ÐþÛýµ´? jÞ0¹eVc žœ‹€Ã †‘› Ç8>•Î×C[;\â~(èÏa¥Ùk³ÊÞ†öÜÌHÓ4a9÷NqŠXµû‹®Ÿ#%íÁX¶Ÿùf1# ÏL×9wãó ‘ÏÉ*ëz¿ï5½eûN¦a¦„ L×È-×p<*™†'‘ÏJûûãv¯áÙËãÅŸ‚? µÔü;ia¥i>Tc1¿š[xÕ#,RÞ¼Øã%±]4"EY3Áv~ ×üK¯üKD¶Ó´½Fôiö‘Çî–ÒÀ+ >¡BŸö÷ ÊÒí¤ÒWOðÊ´—:ÛøûJà<>[,7…Š#é·xÛØ(æºKŸƒôÐ|"ø ¦6uMgU·ÓuwÆWL¹¹ç¹˜1Nb˜ö,rM_ñ=×ÃÏxŸÄ^+ÿK»xîµ;]K¹ –òæ"–ÖjX¤™Œ|©Ú·œt0UòÁñaøwà¿é²Áue ×VÿeºR¢}'QW¹;º”;GAÉéU¯¼-¯[ü[Ã> ‘£¼Ö×íq‰wŽÛQ-q—#€æ0r;f´µ­ÄÚ'Áx[U|ù5KÏ ½ìŒ$2ÞË^2ƽ·+d‘ÎsÒ½ïáµ§Šþ?ühñw‰ü=¥ÊÏk§&¡ö M®ö+H¬¼„gÁQ(‰Û¶ŸÇžJÝM7>ð}­çÄè^ðÊïŸUÔÍ…›‘ƒ#̺K{Õx`šóÄ‘x Ýa¶Ðõ û!4‡wÊ·2yŒ}:dÕÚþÊšÖ•¦øïDñ\V÷qXËP“–ì¹-ª»€ÝÇA\/Â=oþ_ˆŸ‰õ[Uiõ{íB;XÇËö›¦) ÿ î&•–ÌÔ›OÖc¹’m6"Z-ÓyS—çÎ{õ«£ø«ÝÇã{v9šÞóOP`œéo»rÍ'#8=³\•ýÕ½’}¢fEû?œf\aIfÉÆ8<ôô®¿á\±iþ‹J‘Ŷ²^{ۣˢsµFsŒŒž)ÆdÊI#¬øqâ-/Ã-Õþ(ÜÁ$‹|—pXÉÂ1ópZf?Â0£§vÅz¯†luCÃV÷:Ù:xµ7qÀò’KHÙàÛ5ò½„S\ü2Óß™%ÕÔ·3mTl|ßÝðp=«µùÖu{—_Ú:³Æ¤$™@1Å·«F[“Û·JéT .Û=sÃÿôOß·¿³‚¸f°µu/eñûÅ^’UÏðŽkÃnÛÄõË­ÄNñZ[H ár" 3d\€@ãq5£q¯NºmßÄ/È©«GriH‡}°BX6ý¸Ìì§è  w̾ðæ¯¬x[FÔu“mg©«ÜÝ&Y•,}@¢aä Øí!HÓµySJgºX¶0T2m ãŒzW´ü,Ð~"øëÄèš‘ÛÛÝI²ÜºÆÀ¹ÚœóÏ>‚±m|3á‹/ ÇyáÕŽæibóbb®WÛ¹'ž9¯IøW¤hΑ¥j0®§+<ΑÇÜ©m¤äcÎkod’ÔÍJîÆ'ÅŸ ø£áÄ=[áοwiªjºkbCapê³BꮇwËÁ†ä~ sÁ¯>Ò¼K®ø‹OžëOÓ/`½²™`.SÎ •$*„ÉbNN< ×§Ø|0ðŸÄÏnÖ뛪ÝÂ×6öûžTØHv=X)ÀÎTž+ößÃ_³7¸ÿkCãwìã¦Yh¾ð§†t˨- ®ßjWÞtWË9Û:[( Ovâ®tb‘<Îçྉâ-Y޽­Ý•°Yd…Gš%Yæ )Áùsȯ{ðß|u¬Ùi:Ö«%Ô’ëZ”z>ŸkˆØG»–ÙÁ0[)f$ž•çÚ¯‡4_YÝøìóhj¾9°„*ùZ€±»ÕU–èGÁ ¼[÷q·pÇZô½ã]ÿÂo…«Ú¶«â_ˆ1É{áûY-Ìö6òk[[UB y=žÝ£ ì$<y× WÙ·us*´œµ¹»£üø«ãÝOÖþi‘j–~#–þçF†Öñ¼[hÉb±–\FUYãnØàŒøÆ‰¡ë®vúRÇnkc©7ÚÊi!ž# URÌØ?)Çêšþ‰¨xOÆ'Çÿ…ºî©Ùøoá~¥{iÑMms—†VÎÆ[yc®Ý¦ic8*É"1ÝÓƒñ…Ÿ€~+ø£Æ_>kÓêšWÄ Ë­ Àž6†Úê{]Ö%ài1Ú¸i—s |̪zg‰RÙXÎm¹‰â‡Ðøgáþ¿ñÅÒ¦›ý%ŽF>cÜ5´ u8U$âef|àŒW¼è²Ÿ´o‰ÚOÃωIo ê:ûC%¥ªÊ.[K»ÔéY~øÑ;yX߸mÅ`Úü"ñ.½û?x£âŽ‘¥ë:¾¸Ú 7z¶”Z8í´ÝÆ×QZY]ày·“ÚÀJÄ e|Ž˜µ³Ð|Y7Žüá_ÚbÇUð}÷Ãê~Óç:˜T“Tc¹Óƒ²³î¶±±2CvÁŠn}«‘‘YªÏ™h’³¹ãšG‰~ êz†§›¶×|6³iéºÌöâØ_É%>Y‘ј£+`äW¯øUmçÕtËÛ"Q//ÀÀ™Ø*{áE|OŠõ‰_ügñFêÒÞÞßY×5+Èm,þ[UD"$’]®±‡ÝüEò:×מ œ%…ã$F#‚+t3œ" °{Œç÷™mXK¬u>?2¦Õd“>°Ñì-´ÝÒMkWðU§îƒ0»ÔÏœ7dãå ry>Õ׊~Xuxßo E*HF=Øóø×ͶÚÅ•­Í´:]—‚!3gϹkvcï1/’O§­uÑø±ôõ"øjÛž°è®ê=rçÍ|^+Z²±ô4bÔg­ÂÃøw!Xlþ#é`HB¨³Óˆ-ÀÃ(ÇZú+á[[ê_ïe´›ÏO¶Š&Ý›²rÜvÉ^+ä k·þ(ñF—¡Åã¸k»„ù-´‘H‘í’ùÛJûGö|ˆÜk:·ˆßçkùåh±‚ªÈÈ3Û$ò+”éæÐæ¾7éþ ð=œ¾(Ôì5+«GPX%:8ærŠî-Ð(õí^iðº}ÄtÈ,|9â[á2\ ½VáEª²!e73ÙÚdäÖŸí}¬içVð÷†&“TÀ¸¼ÆœBŒ«À‘ÏSÉ+ߨ¯2ø7¤7‡Å?æ¶Ôâk 8Ck>§.àÏ#„ 1!FzŠß¯4c^K‘ž·á»¨tÍ?BñEÄ\'‰üU0èÆ;`b‡í*¾ÙÅPºÓKð…ΗïO𥥌xãmÖ·(óIï“Á˜§_Áž“yáHË;[xFðìnÄgÍÔ¥2Or0OÐVÚÉk­x–wº™kÅQ/ËÉ6º5¸] ‹æ/9îkÝô< =K>&ÓþÛ­júv‚—Ú–‰¡ÂH+þlÂIF:ôó«^[}k_´—åú׉î¯&''iJÀ~”qïXz· Ƴ¢xŠì°Q·â ±àíO*ý#4O%¯…|#´sh~7nŒµÖ¬I%‰ä1ê=¨¸âhx>ú'´ð^­©}é'Ö|]ps°«"ßx#=Æj–‰w&’4ÍhƒÐü3s«É½º\ßð„ÿ´9ü)ž$–ÏJΉdY$Óô='ÃV‡¹ ÊsêGSÚ—ÅM Ûø’3«¦xr!»D‡{¨ààlc»Ò¦è¢µ·™ámáŽ"­áÿ Ä"p>q=ðbÃýâp9éÍ'Š­æÓtÍ}íït iúAxh¿á€ÔíÞºkèN­röê»ÿá ñ9ÐìQù&ÞÜ4…GMÙÁ8®ŸÀÚ¼šŸôí0|AmE]¤“ɆÌÅ ”¾ÒøÇ8íš÷ckû.h·EÇÄ_BPácG¶Võãןz–mgáU¾›zÿüDuÛˆ 1ºŒIÜ8ùIÇ¥yS’lôãcÓ> é¢kF½¹bd½™÷º¼C2ôÆqZŸ´¾¡m |(¿·¸Žòå/æ†Ñ£ÓØG>3¸²±#o“\ÿüA¤|<øpÞ+ÕË´M¤·’y™ÙsÀQ’Y½=k•ÕÿjÏ‚¾'´µoiúõÌn<Ѿu.|À9>J0yˆ_zȇÄãCðŒäËgá vû+†{ëíìùçûíŸÄôí_TéZ[ü6ðwÃØmÅ”º– Ó½®ìù1GºS‘ÜãÓ5¬>4üÔdÝ¥|?ñF¢ïŸš+çØà¥yóx×ÄâX¼E‚V]£“]XI%+³’µ9N6GR5i\ŽÙâ¼oVñψüA&­o{ÿžŸ¸þeà“]·•åUHÆGN„Vlßï ¸–ïÄÞ,ðE„—V¢Öáâ¿IJÄ£œçn~j^2ŸsE‚›èz¾—j÷~Ò­.søÛÄ׉pwµ_÷Ž:[ºÿˆâ/ø¦Ù„R/Ùô <ËeðGb\Œ{b¾y²øõð6Æ ;G×þ-x.Á4¸ 6¢;:hÁx“ß5,´ì¦iïf~(G©Bó‰ËNšï|í€Hd\{ýk9caÜk/Ÿcék ´ñN•è~Ò9 ãóy¹8œt®wðM­|?Ñ´«…‘ÿá ÕVúdêD19‘‹Žá€ ½|ÿâÚÛöq]Y²ð‰õÏí[¨JZMý‡p‘Á.ÐËJ¡_n ÀÈ9ô¯OÚu/ ‰ÀTK¢Í¦øÏÂúXŒý›C³’ëqSƒ3p=;šüüÓ¾9|?Ô"¹1Â])¶¦˜.À¸îÀ‰ןZòmwöÙø¥Æ÷:Ÿ…|}yÏÍ.©m c×d¤òyçò¨úüQ¤rɤ~sCàYü<Çmî¿©¬“Ç/XàÝËr@{n»â+ÇÚ­õ»Æ¶ú”¶Ö,6<ó(<ÜäŒ}y5ø?}ÿýÐâËᦱr®¸s{­+ïÏ÷°Ìq\mïü á-ÈtÏ…vbI¸Õ%˜g#“øŒzÔ¼Áý˜ÏÝ!oáðˆïl¡ò/ î§æ]B ¹p Ôç’zU­{ã_ÃX¼aâOKâýÞçQµ†ÆÌ  Æ8UNsŒr2~îkðoÛ×ÂÖ“;h |9o+¶ =ÄÒù€Ý2~µ£ü#ÄÐDFðdž4ö}–{ÇA’P ¨y‚eÇ+·Cöu¿ižðÅ—‚—Åš{¤*£‚B®Ù?¼`Ø N=¾µÐé´¯ì BßMÔ?µgk¬f}EƒŽÌb?÷Hø‰ükö‹·Ä:=χ,#9À‹FÜüú‘¿•z_€ooŒ:Е>&øítˆ”,Øi0ç=Fé\õq\çU,#§²?bSö¢ý“ 1Ò<%©j;ǤΨI·þ¸®‹À7æÛÅ)nÀ„¹G†p ƒùÔµÔ¿m&“âìk‰&ô¯ ÜEp¬W'o&V¸vIï^;YÈŒÆV,­æ··^;}+Ú?cˆàñ‰â¿×„l¿Y¡U'å>j—ןjñ-Iï4;WfeÌ3v!Еlúò#­`€‘Ùïüwß2K c¹E®v·Ò«øít¿Ø^îÎdÆÑÏ Ç'ß5Öx2›T½Ò\üAŸF#p?†?Zò»)o´é⹞܌ŽùCÓõÅ09_Y6‡âGJˆîKyÊò0q×ôÍ"¤Q"K½Ñw‘³ïd÷kÒ>8éðYø§OÖã_Ýk:rÝ0ÿmaüÁ•yö‚ÅnËl¿6à9=»Ð® ÿfÝ+gåëéŠú;ölø‡/ŸÚ_Âß È‚kËt0#|EÊH3êÎ |ÈÚ”M"°cò±è=‡õ®ƒOÖ]´ˆüE`6M¤^ÆÉ“»!º1öÉ瘫zç ðÅŠŸ³ü(ÒYA«^ÅXk{¸ÒöÀY‚ŒzWæ/‡¤ŸÂ¿ü3¨]>Sìæ2 cvå9=:ãð¯ØïŠš˜¹øëà/‹¥6Åãïé×r²œ‡¾Ó ¶½sÛqóbôZüŠý¯|)'„|qu…þʽY`òr¶ðrzœãáØÑmcíOÛKF]Cögø ñYbß:iþ Ô.É&I¥ÑåVœž¸A1ò7{šüî…åŽá7ûœŒþ5úÅâ]*ÃâGü+Æ–©×Sø#ÄšWЬD Ì ¶Õѯ$ â2¯#±<)çŒf¿mC˜f?$lTÁ8>ôâ£øŠa£üMð_Š£‹™&[RGGY~A×ýãøW×ß¶®’¾)ý”þøþØÈçG°Ô¼vÌK3ÜiÒ‹˜ˆï±$n‡ë_øÕ¤Ôþí魲ãL%…Ôd¯’yúyü+ô^+]/ã/ìã 2,E6‡¯h¾2F,@X54]2r v8`AKS—FJgðKWO~Í—–¶`;icEÖu‘Ì$r2q“ù×δ—mmãÑpWþ?-М.?xœO~0kÔ¿`K³«Iwð¿TbÑj°jÞ¸ÇÍp‚hóîÈŸg–~`88î¯Wøg8ñ†¼iṈ+w¦E«Â£ø¦´ùY±ëò $zW#O´¤’–V >ééQ`<®ôKý‘cp‰¸…hðÏö?ÆÏø:î/+Ì»¹h¹Ï˜·nfŒ¯Ô?çšúÿö¨’ׯ_ | ñzÕHŽå´½YK§ âý’Qœü»d`qÏJùïöƒ2Zü\ð狦®³¡Z<‡<¬ “êASSmfG‚ãQýuYå‰ÖçÁÚÆ™©)îÚ\PN°½ã>‘ݳj —†åTo+&Ð1\ïì÷£ÇñWàäø7ú&­5¬r±™Z$žžOWZô;oüÐï¦]¯.ž\î'–|Ålú‘ŸÆ›)3ókÁqLÚ¸Ó-€¤ŽEùãýâäõãiê?lä6ŸX Ÿn^…t¤Ñ(8üV¸»H²¼Z³8ûJ±Ï$¬€“Û†Á÷¯jñ ºÏá­VCÀµ’Åûa­d*CŠ’N:ÊCÃ;…‰K:iç…rNê1ׯ¾‰Ð´=.ûãOˆ4«2ÂâþÖÓÊà4ñÜFã·Ë*’3œ×‰ø>Ý'·Õ,§ÿ–Ë sÉ+óãé­{Û—‡Yøyâé&òÙtˇë¹Ðµ³ƒø¶*e±­6]´ñ6¿ñCÃþ4ñ‰Ý.um7RÑ:×¾X>Š|­Ú¿ô÷lË$Y>«±ˆ™¯–þ([Ùêš Úop-u@þäè àöÎ \Ï>Öa–óBÑõ"‹”ó-ØvVǘ8äUOÚK5†«§„Ùsäy°²¶ ÍláÕÁŒg®k¤Ñ­b¿øyu3œ}†æ [ùfssÓ98ªþI£Öä‰6æX'‡iûÇÌ\? S%3îÝoÆñÿçÑghµ-v 9eº³¸A#odÁž)ƒÕ}Ækâw—à^»§A£ÚØßh †  ZÝÊŠGL–MÝN@澦øIðfòÜ~Ѷú½”PKªÇუlc}+OÌêÙha‚¸< çµ|Qy¤O៊pƒ¸í»1Ž8ÁÀ®*)» ³ƒÒ4Ÿji%†™ï ŠI\÷88>ýk3JÒ¯õ[K‹Ä™åE€ÜȰg_®ãë^É>˜ÞñÝ휭–wTqÔ$þã’2z{WÞ¾#ð·Ãü2‡ÅúÖ—bž!LOq¦ƒg3‚v·›vȬxä}ìVœÂ?.t«@öñ¶h…˳³ù™©>ž˜p]¥ü^uåÔÒ(ÈòãVç#ØsÒ½kÇ^ ð”¦o‡ÚIJD²l½Ó®àd{yà'>€ó^u¨xsYÒom¤Ô‘íb¹16¤Ž$ÆŽÙϵR;O¼»ðÆ©§¥Æ±H ƒæ Ç 0X¯¯¥^»òî‹]Û f–@Áå¸AjÄö8çð®z ùîíÁµòöó¹Ï™‘žro§JÜ{CU€´›œ"ŒòFž‡ü(µÀ"¼¾Ód†ÅÚ{¸î]´žZ•p}½=kÙôÈχ¼1‹´m"ÞÆÂâv†+Ë™cží¥O½ˆT¶Ð?¾xôâitÉEu’ܶҪ»9þçÒ½/CÒ4íGá_ˆ¾ÍqäêQÜÁ*ZE¦I1ž%ÚZY/Ãm…#³Œœe~õdÖ­Œëï]ê×ÿjÔÚIÈS´Ìp¾Ä ÇN¼b¬YÃâŸ1.|ùlâ@|°ÍöuÜC(ÀbJòã0µ„ÜÉ"Ì3»÷gÜ]ì¾(¶HüA¨­Ìú„¦+yš')7–?Üm#J“ž â³å6R>÷øCñ3Þ1χ>!M%†­V‰âˆ¬*ÎÌNAüÊ2Häw÷ïöQÿ‚Ÿ| øaû ø‡öý´¼#yãÿ‡–‚â kÞš9¯48î$k˜bhä–+˜šÊðFÖYîx×jA »ÿ QÏ!ºI56êsµ¥—af9Ç—ÎwgÓšú“Â_´'Âi¼7sáOŒVÌ—RÛý®ìµÄƒoÚ©ƒÙ†k’XY^ñZZšûE%iÙ?ƒ¬ücÿýŠ/¾~Ô¶‡ƒ¾4øFÂ-?ıëBÒõç+§ëÐÄȨÑLÉ™L`ǼÉâ1ŸåÄx#ÆZ/í'«Ú|SÒm´x=?±žÀþõ¢¼k’Ìd’"À݃Á5ý~ÅßðTOÚsþ Åñ«á¯Ã?†gdñÿ >[©kº”ÚU‹‹si}¦³˜_rÁ{*Æĕ&*qêðTïØÿÃ>ë?´/ÃØ!ð¿‹<'çéÓO©l·–Ñì–M'Qu,5‘‰`»ˆ˜äµÊ2ä’1¯ «£ù:ý—¼mð£Â^5Ô®¼ytÐÜø†F‚ÉÍ »É$˜|CJHþµú,ž´ñ~k§jZݦ‹«p–ñ_ y-­ó´] ùNØFps;Ê ~n~ʱé^$ÔÜE‹i¦7Q¬ÚeÜNÄ„]ªöÓƒÒf:øw‹’ ôk{ë``šÜG:€¤gØèkÍ|g¢OáK-#ÄWÂHbñe„š®–ð“º9m¥ˆÎA%—=ðsÔÆLÑ;Ÿ@éŸu=Sá߉þ ø›OÓ49u¹|=r÷Ö°Ÿ´Ã«è$Zܲ• uùg8*¡¹`@§~!x\Öõ ÛR’êÍöl97ÙÔ)twpý:×Ìvº¶¥nÄU•$äó‚s’Xž¦½ ᯮ4“ÜÛKÉÚ.SŒ… ´ŽAãžjy˜ÏE°ñÍLJ¬e𤈂òð³ùü|êÇ#½g\kVöPêèRâÂe;ƒ»œ¯\Jð!/Úï­-lãiZYR(\ºŒ1<µéþ#‚ïR»–5¶8Ôùë «ŒñO™Þ_k:1ƒLÖïžOík(¸…SŒˆQvöf¼îm.óHÑôøRC ¹WpG`?­\ºÓÚëI³xÔ I`pgˆ±d8È>õwT¹mlX:J­í6ªGÇ ã¿¯ãRØ!Ò›MIo`"áT„qµ²ßÅ‘Û?…n6™mpâX˜+ ÃŸáúg½T»ñdÑÝZéV¢fTdd,ªíÏáSZëúD±)2¢œc““ý)=„ö= D»ÕôÍ j*Q¡•ÚH,qÇCØšð߈éqáýZÎïV+N¿žL#ŽD±Œ° Ðdtâ½^Ó^Ž3&™¥ÀC¼/•lcstäö½kÁ¼Io¨ßX5޹(%/ yw…ەϳ`õí“PA•ñÁöú˜ž9ð½â:mŽo,dO0€¬¸ààžxàs^+«ëñ5¦¡4JËÊ¢B içq$ñŸÂº«ÝWhÄ(¦ QdP9 ž‡Øö®&Òåom¤Ñ6RÞF$Hw´ðNGÇŠOc;²è{6ÊòÜDArHɧÛÓ×7•õ‹¨hJçæPyàõéÜWEy«ióÞfÍø]ˆ²0ر±=W×ð©o.[LŸí>`™¥Ê,É÷x>ÿÞ¢$—…“IšÂ+UvÓ!$œg9ÀéÛŒŠËo ßYÍ$ÚwŒº“áóp0Iì+Íu][Rˆ‰ÛÉHÎìT +©ðç5{zݯ‰ŽãTŒ}Ó n¬ÄàUEkqÇsÒ¡³Öü5âdµÕ†â_ݹr¥‡ÝZê ¡2[[*–ùb?0Rýºtç5WCMkÄw³Ú9Äq•*XåJç±®îþ-$I牢xÀN8É^ÃH5³v4jÄ:õ½ŒvK9mË>ðã‚O?•cx_Ä3E}ýœ‚9&F$cø‚އ×ÐUrÖZ±¹ŽíÇ–ñüÊÇYqÏ==ýkwG²“Kxƒ ß(%Ÿ¶ÎØ8ÏZ¤ÄW×u›ÛkÈNi Ç»$ÎFj‘‹\»½¹ñ£„\³Î1Üw‡Ù\Ek “Ýá7É09`;×8¾6ÓàÝäk$»+‚§Ž8ôÓ)£‰°»Òï¼Rº©·xØF[aRÃêdM{QwÔn"K)T¨[y9SÆï¦+–úÊÖþKG üì 6Ñ·TŽ¿…hˆ§‚Io¯.d¸`¤–?{aö=ñM¡Äëc·ÔìBÁ£{édáæÎIè@¾”¹Fz^—­ioky3\Ëo(2FÈ@'Çqšô+MnûÂ’.µ¤ÚºË2•µGèI\:Œg®+Æ´û‡±Š;8^ãr±{Ù¾TŒvUNùã®ÄC«[ÆšÌúÄ×í(IÂÚÆ[ÌöÍK ýÄow•¡oo%œ§TÉp‘“/̃ÈÇ_zåqpÔ5[YôIdŒ1XP™X’c õÇë_;Ýø’æ}aÖÎÈ]ÚB¡®%‘3˜Î08 ã½lø¯Q×uhÞȉ;K$c |½3éÛŽs^â=_Å–ÚD6Çmûe~Ó9n™‚.{UAY’} eñ²_ è×¶V~Ã9̈Ñ+M»OÍ‚p°<6º“Ãu6•@ß¾K7|‰Wh"HÎ>òô ÆOÖº?Íâï éºF»¤À–›ÕJ¨-å8Î×F@'¨¬Qâ] ÛSE´­`é+Þ¸?"äyöé]jxßÅ þ(ü0Ѽá[K½ ]ŠæBâïsÅqå‡ 0̓&Ha´ ‚ z÷ÃÏxÆÞ·ðÇÄ‹[†þÊo±-ÄRmXs¹ð2dÜ0zàœz×ǺóibfÖoÙ¯,nZ7ˆ³»J㟔zŸ^•Óh ¨x;Ç7?üAé’ØÂu/ßá[U€ û7Q×iX ï‰þ#¾²ƒD‡Ä·M‚’XÙ›‰ŒÑÃj3å"³ì@äô´´y|;?‡,4{ð‘Ïoâü*(µ{.Ÿ¤²éºD˜[ˆVk™AËaÛØtö¯oø#â­gáf½¥ø÷÷ÑCOóQZãæUÊ”$g;[a ^ÕâV>$›O–ÖKeY$<`…vù½J©ogâ8¢×afÄŠ>ð8ϯ¯ÖÐZ_|CðÕ„¬´¯ıjí Ô.e“Ïe)feÜ>bñ¾ sÏ­{Ô?´Ä -ßÂ}åíøKˆe·}JáËlìØOÎ8ÜدÏí/O×uyf·Óaš"³”–Aæ/=÷-Œzb½?¾"¶½¼»²ñ[ˆ¡ŽTpÊäœ}B8®úU]…cÜßOŸÌxíP¬G°Éɵ|ÚÇÆZõ¼âá¥×aKuW!™1Œûäžµpþ"´ƒJ¾·¿±·o§i:B'šé$x¡&3²B„îÎ:Œ þµó”¾Ö´ni­¤–â ßÎßiÊœm\`÷®«Hñƹà½CÃWQÅo{áû„¼±ž'c+Çp L ŸCèqÍcZLÇÛ6õ=\jZÌÒ¼Ç÷#fL„ä‚8ãò¬=B gXf¹lÄÃ)Èù˜Ï­rÚO‰4‹m5-u[‰RI¶â76‚§œõö¬mWVÓ­ìVÛÃâK˜Ô³™ËvãŠäªo\O¬µ³ÏQFU·$rr1Õ»r}+ƒÔßʵîËÍ=œWg{º°%}ä}Mf[Ûÿdß}Ÿsì f㪞ž¾´<º¤–:dJíûÙ¤o1BIݪmZ‡GÑæ¾ºVóˆ’3ò’¤·5×xkÂVÚߊ4ÈoNèÞÖúöAœoŽ“o°.Ã?JóJ÷šÍͳ ¦h#“¢lL7àzO`>ˆøKá8¼I<ȰÝÿiBöó£F3XF“æ9U=kOÃÚ¼Z†°—HAkÏžOBøÇB1YÞñ‰´Í&ïÁ:dïko¬¯.n#ÿ]ˆ ò–5áVG^¾µËø~óÌv¶^[WS4ƒ€ ÛŒûÔ÷;=}L~!‡K¶e3³ˆÄ’dÙÆGóú×K¯O©ÀÑÞiRµÓ`’IfaÂ-¿þQžyÏJŸ‚?xûSŽ)vÙiÒC™Aò”LüŽ„œsïPk¾#»ð®«¬ÞiÓ°Óµ{icŠl½#åÉp I=lláhó~]š^¡¼ N+0duÁ`B÷<×EãO iw–‰.—rlo)ÉÜBÛŸ-܃À­mK²ñW„o-ìãíý;NšXà`ùŒ›H\€Øþ,})í¡ªgŽÝÜ\Øi‰gu‰Àgƒx+ŽT‘ùW Zê—‡ƒ&ŽI ³Äñª ìSÖ¸¹n4µøm§M "\›©"žLä±ÊîﻥjøvëO·°þܶ.ßÙ׊—*ÿ)pË•‘¢ vq­ßôËO*óÀ቞c'ò®tÃ$ú—ö¥ÚÇ&C€Fܨç ðr+±ÿ‰-·…¯µ Ó›½IU-÷ñùkŒÖ´û)ZWS'ìv›™8P œg‘ÅPú¦–Å-–Dˆ:+nÏ`süëÐ-çÓâðÊM¦Ê·I"…•ÔíQ!, ÓšóßXYÝè³jš<"ÞWDò¼ÄÎû‘Ù«ª“Ã7ˆn,äÕb[8ÀŽæÐuf+¹7Àõãñ  ~¹Ôtß iº}½Ÿžw ›rÎǯ׽yõäš—‰þ"ë/ˆHµÈg˜g‚=Èäž½1ŠõO iÐÜèÁüóåiåvDXn`1ÓÛ=~¸¯œFàœõ¯ï<;s­jv>ƒ|ze»ëQ«œÙÚ²‰™qýÝÝS^ÙðOÂú'ˆŽôx·Ê+]Ç ïŸÖ¾`ÿ‚^øvâ_Ùþz?³ßx×\¼Ö%@6ªG´AB€$xÖ¿BbÒô»[)'i‡$‚ÀÀc¯^•õx SW>W^J«IŸüJøK«mÉñ#à†ñ–‡Oi§ ªÆ™-fç ȉþ.ã­|Ýâ ïãnŸñ3Ä^¾E³°–ÏâƒbñZXmbIîào•Ll÷1ˉ Ü¥òzâ¿Kôýåµ)¦ÑÃNÍ–åDK~XäàŽkåÝkáÆû9~ÒÿnQàðÅø®|=¯*·KÔo[Ì…Ó*GpàÇ“€ep:šÒµ§~…aëÉ«u;ÿøq<9àè¾$|*Yõ{ ë+}F},ϽÌbG’ßÞQ‰ þ4ÎϘmoÌÛÃ(t/xÊ‹{í W·º´Ñ5 bAGŽ_°ÉèCl á€`B~œþÏ:EïÂm[Tý“>!ÜŸío LÚ—‡îÊ5=í¼Ådk'&ÒTw ǯ”?à ÿ-m´†:…¬Øèž*ñíÕÕÕ¥¸Ú-îZØÂþHû©ææI]q̹=I®\U(û>to†”½¯+:ïÚX³×ÿcè¾d’ÃKðýôja…‘L–q†o¾:3Ö¾Ný™ µÍ…凄¯u­zöØ2Ïy é3\#LÉ €Šsò¨W“%¡ê®ÇÀßáµÒJȹ•d”œyQ!$1 Œ'qïÇØø'VþÏøÇ¤êþ†;@—ÐÜX¸>o˜b Â]§®dÀ>‚¹m;Ã×_þ[螀›÷1^¶çØ¢ØËÀaß Ž;×}à/kúÞ¡¨x¶à„é6¦ÞÝÔ…U;—hxó÷­èîeRPä}ϳ~(|J? ¼eàx%²xjáÑ|#á=ëâoˆ®ãQÖ|?«j&%•í|›UòHx$žp;ñ]²‘ÁÊŒoˆ70Ø~Æ>Ö&EþÚñŒoõëò¨1ö»»YävSüEH“¾Áí^g£xÃUø£ëמ»œßÞ[¥´— )+‰wbTŒ¡«ë_d~Ô:&…ðßöøá#höºŒ:„z„òȘE“"´»Ï]Îë» ¯øñà?„¿?d/ ÛÙ‹ËxÞâÆêö9áÚ–‘*´ó:¾C:ª(É;O ÍsTÜÖ'ÃÞR·^ðÕÏØÅí£[/ôV+½7g# `ã½ixžÎ? x¥¬t+¢'šÝLai@ÃC¤Vÿ‚t"ð‰SÚ½á—ÃoÜiö¿´7í!­è:ƨ«=ž—}pÖöÖ-A$aH’UVÎÓ€=Oñsö’²Ó<-u/õ]'YÔu8Á•1•m”’Ûä%c Oü=+Ú£N“MÍúM|ExÉF ýÏÑ>^ücø—­ø“ÃÖp[é^X¥0»•V•ÎÏ'¸AΪžѵžÃP¶:eÖœ&hà•á¶€¥€ÈÏoÒ¾‡øMñ?ö]ø;à›o 'ÄŸO2¦û›æH×3»gb¹èÝô®[Å_ÿdßjÖÖ7^2’òìOˆM†“6Y¤$mFqµ'×g5²NýIö•œõZkª^Ü/Ù.¯Qo-grb–5*èPm`Ãàž§µ}!ðÇìžµµ×­ÜÏom¬ÞN”H¥T7psÉï^SmâßiêéÚ“ÙD%g’{D…ŠuV`[‚[#Já5ïÚCáÖ9ÿ„nÇS·[Ø|«¢¬‡‚ Üž:W›9&ô;é&u6ýÞ‰üLK“t$òŒlKFŸÂB‘ÁéƒÔWÒ´ü“Çž3øG¬|øe¤Aá륕¥ýü /Ú ·µC»ìämÛ$ƒäßʤœgðÞñgáæ¥4‰u ë¹|¬"2ÉàsòŸzë¯>"|µeºƒBÕßÎ]ÓÜBv…ãþYƒÏÖ¦U6öfOƒ¾2|SÑtÍ6ËL¸´ÓãÓ|?wá‹2ÖJíý¨ì7 ¹¸2?– HÀ•õ$“¿àß?´ø–æÂî.m5‹=b+³fx¯ôûacmS…>+&ñî±·wêöé®çýúáÙ¥wv,çŽw9$] ·í…ã@ïFðÞ—n m;4ÄtÇ\bx÷Íhk¶GÇ4d —N¶ÔnÝáøg¸qýjž&—ò’ðõzÈí/x†XdñEõšF!H¢THá&8FÔ$OHÏ`+GŽŸr#—Rì~¤Iû`|*´M‡ÇÞµ‰m~Ç·’yŠ@ m„’ 'µŸ¶§Â›y7|Hðô‰?–Ž-t¹æÄ0¹Úrv^+òçà.±s7Ûæ¾´ŽAÜJ¼‘ßÖ­‚Q7Ë>¹?Þ…ü8¨xê©k.¥Ñ«·¿¶?Ã+¹ÚðüC½™ÚO76ZœÈ¸Ã~ñpO)çÆý²þy«$¾$ñD¯#ù¥ÚÂÕw61¸Žìqž¸¯Ë»oÙÒÆê%wÕN¤È¥¤û±ü©çöuðÜA¼ë± É'¾ìzŽGáš—Š¨úšÇJ=Óvý¨âñ.¢mü­x¤<0K+ÚÂÖAÜ㪌¬GÒ¼óÄ·7ô;\ø‹Hø…z±€…§½²¶‘Ï &áž™À8¯ƒ#ýŸ<m7ž×¿ebß)ü¶ÇæáO¸øEðêžu¸Üw6â u9n”–"}ƨS}¤¯ÿà¡~ÌâÏáçŠ]Ûn_4`ïò³ô\W17üSáÒÇ$zÃbÛÊÝx‚í·ŸVÚŸ®ÒkÇl¾|)·Ë5¶Ð,Ì€Ͱ?t–ß ~ÚÆ÷BòÍÒ!¸”’#´{Ø{i÷/ØC±»ÿÐu]ß <9u0n®®ç~:ÛyÇ©—yÿñÒcLø{à½?ʯ›g5Øú`”ãבK'ƒþ ìi&žËË…CÊÍ*€ô,3òçÞª]Áû;é±sªéÈ&æ>ï’*^Ò]Çì£Øú /ÛZòMN¾ÓµßéwÒÚº´‹ÃÎÞ\‡ª¡2·áÖ™í›ñ'Q§Ðüf–çxTkm"V#WrAõWèð7Œl$¾ø{m6·m ”3XÆóD²zn¯·ÓØ|šûÅöž{w²–1´êAR'sÎGÓÒš“2tÒgñöèý±íí_LÑ>#Ý´3†ŒÃe⤜û>r}Ž+Ç-?loÛÂô±øãX„SÈ …è¤#ú×´|løv|q¦Yið‹¹Œ¡6l~õØa0y©äö5ô_޾x³áo…/<]âý&ÒÞÊÔdmu˜€UU@V#±8ëëQ:¼»šÂ’høVøÑûrxÂ/Vñž²c6^ FÉ?Þ…caùóX77Ÿ´ÕÜdjž'Ö¦bú¬§ z•“'êNkôWÃ_ õ¿XZj6ú\Os Ì€]D+1 Žù®‡ÅŸ>,xgÂÚ‡‰®eÒ#¶Ó­¤¹ÿˆ¦r±¡lmž¹¨X¨÷)QGå¼ÞøÏ¨HeºÕ5 ƒÇÌo®ÜŒxù˜üMw–^=¶ðDžÖ|1e¬G,Æf¼¿•瘆ÇÊ HR22sYmûH|[¿í6É¥Úv&k,¶HÏEpúÖlÿ´ÆÖtÔ´õã+` ãÛ“Uõ¨÷7X)ôF]÷Â]gÄ.‹o¤YZÉ÷]B Ž™Ü?,ô©¬>x¦ÊÜÈ4èƒöxÑ9í€*I>8|w–2]XžU-£zdƒÖ¹MKöŒøá¨£¼¸ÕZþiYU6`ëŒÒ~g ¥†”Ùëð| ñêE‹yž0ßÄ›A9­(¾|DhÃ]jwJvôÜûƒý+ç—ý²>%² {bÇ¡—sŸüwmt~ý ~$øÖöùõ)R,,n/$1åW(>Q‚Iäûš§&`{d³×Œf ö•À9ÄÌ:û“VÇìçâäÜÓj2¦F3ö†ñÁýkãïøjŒî’òÃGîú~f³ÛöøÑ©Ï¸ÕLnäF<¸ÔX€;h÷„}Ïið+\³ŽH×]0¬ˆ±¸30Œ¯\0R Ðÿ…÷pìÕuä„ü¤rÙòâ¾0ø“ñgâ·…üPÚ ÔÍ%¼h²X÷yŒ7g…éÍp_ð¼>1ç]™HôU'ßµ'v;Ÿ Ö³'ƒŠîŽPŠ£¢¨P1îA­¿g?GûËi Íž<¥]Íþ}…~nÜüZøÇxD³ë×Oòà|àp}±^­ð3^ñˆü|×Þ*Ö¯¤Ó´›Fð‰ˆÛ [ž›ÙAö¨m=Oºí?g/ü©#Ë*vŒ7æM]_‚¾Óe˶zgÌUçéÒ¿'¯|yñúynnµ›äûFeò㹑Uwó€7tÆ+ìÿ‡ßµWÀOøLðî£ðŽËÄš¬0)»Ô5Kï<øœ Àž:€` ‡Ím ¯©ôÃ|5ømïmå÷c <Äþyâ²åðWÃù£Ý¥m¹~»VQúc·ó¯.‹öøøgfÃà‡…­ž06¿šä¯?ö,êk­×?lŸx÷àïŠ5‹/ hÞ´²mà’Õ|×72±AuÛ¸9¨N}QR嶇Iÿ ßì~“Åo`"³0ÓÆJÈÎÛT/©nթែqßÇ W®Ge'`à ¼’À×oðŸÅŸþ*þÏÞÔÀýžƒ W϶òßJL+ü;ŠŽ? ú·Ã¶#^ð_ŠôA4°Ù5ÄQÿ-¯#=8çšùmvØÞ!MÛ‹î*Aõô5IRÛË·wó£B RÝ};Ÿjé<%g-þ™«h0¸]ÂZ&â¯hú«àÿÛê—"6@Þ\ˆ§8i8ö'ÔqCõEñ]׋b/…ÝD-àO^h7!>c©FÐÄOr¯?–qÜóÚ¼öàðdCN‚*HotÆ ?4¶¬1Œw ‚GµzWì½¥â¿Ù“ã¿Á+\½í†•»¦E´2µÎ—(ºÚ ÿ1]_Ç=7Æô¯iëó –VaÎwEµ·éYÇI 3Wþ jñüWðž¿ð.ù˜ÅñÀÚLJcr Íih<€À– c®+ñ†ÐjrF¹Ï®Ögc‚*à‚Ï÷ÏüSâEß˶pÎ#ÿ„{Åšlß¼m£ì×’yŽ`¿9íZ_´÷ÀÿøU?´‡ÅOž—¬ÚÞª»l2¬‹éÔŠá.lΟz¶E²ÑK(ÈÛÔœÿœújÏÂ:†¼7>ûéî,Ú_*\–@ü#õûŠÏ¯5ç3Òì[Ã6:ÍœM ÌWN·AH1°|¸±€8ã®y¦A鳄ʞ=ð½´ä´‰¾Ñ®3Ççþ‹Ç¥d&›{œtùóö»)d‚fa rŒTz¹õ®7áïˆ?áŠ}s!N5® IÄr3×ø3ù×Ò_t6Ð~0øŸO‹åŽKèîaÀhf·‰Y ýNiâ÷ºE¼ÚÝõ’ «l²Gp$Ço~9®ÃW³[êÖÓÊv«LÑ4gƒò¶xW´]ÇŸªè×ò°+æù Üwú ó^=®XJº¼Åý]Ã(ùºÜð)ú¤XOñ þ ôÖAá‹ gH`NX=ƒý¦2bÁF=ÍxÅ{xeׄ/ÖøäüÆ=>ì@ÜvýÑçÖ•Š‹&ø â3Ã?µ¦ƒ¨Þ!k}wKµ2çî“#Û'Ð)¯Sø5 ÜhžñÃk²Zo k:–”G<"Èñ¹í±†ÑýÜ_kZÆ£á3áo[&ËY¬'ä‘K(Ü;©QC_¤šô6z_íñDÒÑa°×âÓüAcÏßK»XòÄz‰#r}Í5±GäwŒ¬e°Õní¾ô‹òägªŸçÅ{*Ú¿ÃR¤dØjãÖ;´ÜxïóƒÇ­7㟆ƒâkËâ0³ê3ª€9Æþ½ýjoÃ>¥á=^ÊÞlz|wAOÞ-fÿ7OUa@®ŽwÃäÕPÆù7ò.HÆHÁ¸þUêp˜|3yt¥Üh>!y Fûˆ·¤€dv3nÁõ&¸;UƒHñ$wsX­¥\’½Ü´©úW¹ø?HšÖ_xnèš÷M¶»Ž.é-Ãoüã•GaQ(ܨÉ{鿲¿Æm{âÇ‚¿hÍ¥jÞ—ìú¥¯œÖ÷Ée©[”•>d1ÈçÍfÎõÆzüÕñ‚n4#Pð\ +é‡SÑ6gvßì«© ÷ákïj/ÛÀ_±ß‚?jïÙÿÅ7/á¸/î¼wá½:9.®“UÑ¢¸ŸíÛùS)µš(@.¦6@T“ƒ‘៴½Ö‹á-ñ >’u+mi ¸ ºÓ\³K‰ppWÏ2sžÝÍ%T¥sáO†p}¿Ã:æ†z\ÚÜcogƒÇ¡WÈ,µë[ÉP³¬±´ŸáÜ2qô¥ø­è·šï•o,™|n¼ùN|½Äôä°ïÁ®’}-íu+½:BDÙ’Üc·ÌG_^”5Ü‹Ÿex=îôÏš¶ í}Ä–·ïæ'Í$rÌ6nÀ!ô9Å|mûG艠|K7¶Ë²5™g <äƒê3“_vü(ð$m¼MàA¨ "çXÑaÔ¬.\n‰'²eù\d¤‘žsÖ¼Wöãø}7†|[s0™,∉Ӕ4c•>™ÎJŽ¥Ý=xúÂ8¶ÆOPS!^âúrêôÿ€^MV±»Øy —8ÀöË~åÜùÛÅ>û{Åa£Þ‘GÌLytÊ‚ØúkéØ¿Ã^ø¥â„ÿog¶Ñu«Ì41-Ô¢æÊ?9R8äÞɼ’0FÞ+ƒñþ…6ñD- ՕŹ`r Æ~PÃèN+Ø¿à›v6zíkðãDÖ¢Ýfþ$–)òpQn¬§„ê ¸=ͺ±Ôé-?gŸø$§ŠÑu ?öŽÔ´ÝÑ)d½Ò92{•ʤóÈÈ#ÔÖäÿ°_ü£V‘WÂ_¶hä SJÊsýæWˆŽ=;×ågÆ ¯€¾!ø›Á6IäÛèšÞ©§• ì×r _ P9=©Ïà/‰¶þ^èšå¼0Â&ɧ\ˆ¶¸æ>/$œ sÒµP¶ÁsõÄðJÿ…zÏÂ|FýŸ¿h¿üIÕ< Þx†}G°“íS[X®ç-Ì›7˜È-€z×䇅tý ×XŸÅž!ŸHµŽÌÉf°Å$ÐÞÎs²"U–(÷ |ó|¼ûWêWü÷[Ó.ÿo o„zä«4?|!âO ‚À²¿Ú­…À]Ãr-Ï$ñÓ©ÏÁ3<9ðë¿ðPoøoÇz+]ÙÇ«§­®¤ÂU‹S³·’ÝŒ‘‘±óåÎ6ãæ Œ ÂjÚ³H]»#òž6‰n×’ª]Úæ`ŠU„r7 ~uý/øþß%ÿ†´¿ˆ?µÄ«½rK½> ø´}46ÐGp»ÂµÔ ±Ê•æ¢Ï®r+ùÓø÷ðËþ§Æïü6eýªj–) Læ’G’Ë·Ûý¥~ʾ:»ø™û|:ñÎɤ“öVÄ@Vµ7SÉ8™\ö8¯?WØÓSSÔË0Я9F}ÉMá‡ì•ûþÓ_´?ŽÚÿð¯5ïéÐhúœ–Sj’[ÊäË4m,[çBÄ6çpª é^±/ˆ?àŒ¾8ð½ç† ×´+M'Seyí仺±Ê˜+ Þ¡‘ƃŽ+Ð?lŸÛx‹Ä—V“Åéªé’@#”nEhƒ;}Ër{Wæ?ìñûi?¾hŸ¼7¡&§6ŸbÇ_7s¸ØË!D’ |¤\ià±q©{pÇà{»béÿà‘zf©i­øâÏü#—¶yö—V^1x¦·—n‰œ†VÁ#rpHÏ&¾O|ø‘ðÆŸôÚç_ðÇÄiìŸÅ6ÒëVzÕýÖšQ­f2Üdº]ˆ’LAi£DI „L~x›á¿Ã¿‡´.› øöËìÞÖRÒ9u‹ÌÂärª¯´òxàñÇÍŸþEá‹ZÿÃæ…fMÔ¤´¶ÈY“$ƒÉÑÉ(ñõþ"k½Å=O6RÒÇôucÿûø=¦iƒÃþø©É` ²¤²é3¿ÌrÙhãF9<óϾ+¢ƒö2ðìväx{Ç1É) V[í>;µ8ëòÅ2ŽÀ¯ÊŸ~ξð]†±Õ¬:å–£¦iw[£¶“R`‘ÆsÀ.Ì¥²3†Rîžð–wÒø7T0é ¦ÝMlòK*Ž ìR3Îk7%}Œ9sÞ¿ü1¬–wkÔc.°2¨#ÊÎsœ+Ö¬¼cã_hZkhÆ¥si º@ͧÝ=À˜‚ÜåW#$±<ŠWE$û>¡¤x³á‰<ˆ¶¶°iz”QÍöïçÄÑÏÇB%8$uÇ ×øÿÂÞ3Ñ´kë2¤ðéNlvÉæEök–Þæ'á¶ï ýÞ•å~(üY»Ò"‡YÔæµ°±•íà´Y²"8ùU²¼Ÿ¡ÆM}‰ñWáWŽÓàd>0µ‘µ22ß^K”!öé×VãaàŸ$üÒÐtÏJÆf‘ZŸÚ=ÍÍ„÷R”ólÐ"•ç?7LwúÖç‡oo´íhl#Uy‘•ÜtÏ#ñ¬Û'»I¤µhšH,ؤŒ½É]Ùè8ÇzÏÒ5+È-î¯ííf’ËÌæDBÉçJÀF…þîó‘µs“žcÔÙ&’÷ z¸bÐ’‹Ð#¨÷Åw:~±¬éÉs.“† ˆÒ8;°yéßÜÕ+‡ž9Óÿµ`ÔôëÈ®,.a¶¸V·„2€F)W8Ü„®xÎF+Ö#ðŒmcÓ eô²Ý_›H;wÞ2… ?)ùFO§+2m#̈5#¡Ícl pC©èÇcÛÖª}²Sc¶îž{¼nÌ[¢§tÉýj¢Ëi-î©¥YɹL‘JU‘‘¶œõW ÈA j­¦ø~úfŽG‚[”š@ÐQˆ‘”ðç#¦y©Ñhš¶Ÿ§ßÜkq7úT²±eòÍ}sNíïçŠU·¹û…Œ‹ÀpÙ$ãiõ÷¯^ðÁ"ðA×¼áKËk†ªù b2‚7X)ùv ÁÁâº_‡ÿ²Ç?ˆ#T×4_ ê–öºf{ëÛûY¬­ìã(\Zá* ®Gzh™>‡Ï¶ž«lb^+VHùÚ;Çô­±£Å©é†ÓST¹˜’Ä…Ê{ƒï×§¡ü8ÖßTÑtMåÓµM|G>e©[Ëg%Ôs1/šƒ=Y2€IÆpï‹:Š~øÃQøq⫱ÔlÌEàV2È›’HØ È>éqÅ6»|¿ã=?Sð– Î|Áhê0ÎHfçåÁ=‡n˜¯×µiˆMí±;®T˜8#ßüóŠúwÆÚý÷‰´(luÍ>U{²&ÜT) :g^ãÍ"-?Rѵ\£ }’®2 ¥;ÿf€$dç~ÁógŒƒè+Öç“Áb÷ÈßpßrUbBÆN3Œâ¼;ÀŸþ!ÅðDø¿gs­kâ+‹´š8œµÞçFW9t‡ÎÌázó^›w§xSLðm—…´›‰›PºƒÎ†Ü;dS±FàÍrä±®[qf‰8Öt™œÛM%É™²ªÊ¸R¾œqXZ‹t‹Xn!1mŽÜ”|õ'çßÐצ|/øãÿˆ:ÍåÚ>¢øròÊË[¸Ÿä»±“PFhÔÙP_nÜä× ñwözÔ~xŽæÏ[ñFŸ{¤6™k«¥Ò¿—(ŽðɵNùQS-³‘€*Â÷8Ûo¶¯ëRÖ|AqäXØÙ£K$Óßòƹ$ìŽ9â°q{ö9oXëÚÙ…<Ç‘ieb-’FìŽÜŽÕÆÝx×Om!æ”Nä¿ï£å¡êGB\žA=+ô¿öýŠ¿h¿‹Þ¿ñÂ+k&Ò,µ­.kíJëÊRöR˜¥X¢Dw2Å *áö`Gj÷¨?à•¿´޼-©jžÖô õ­6þãO—Ãú¤²ÛÊ^ØÜnöº'˜4JT–2qÕ =K\æçísòwÃZoŒtM&/rEº8Y¥¢gYGb',OAšîþ" / ÂdñΟý™®h—pZ…u kqò•íål­å‚W8nyâOÁÏŒŸíÕ¾)x'Vð͹þÌûUôJö,¥ÄQ\#4re=qÁA㯠ëšwïkž*µ}Å\÷ºjH˜VÖeˆÆätpÇ(A£oÒ4¶×[w´…$ò¡²G=k‚½ö%•ð1’zŒ1E˜…®ã>ÞÏ^óÍͦZ9ÿ«Bz'© wªš–™ârÝ/,Žöˆ•ú”<7@@Î Ayu«húuÖ¯¢Ü™ ÍiVL}ãÏFà÷ªðø¨O~°‰e°Šf\,x;‡9÷=ûSå`wz¾¡á½S@Ó~ÊòA$,¹bwH»0A;}O•ÔßÛÿÂQái5ý-š)ã‘a¸óx]¬}=sÍy…¡[Ë+Q;æV›dŠp7Ùü±Çµtv:Ýõ£ë–¨#·[YÕ…¾H€Î³É8üé¦âfðÃm£[éú«,pÚ¦ç±É+Qè+*þ÷N·¶:z‡y,ƒ å‡^~•KJñ„ÑI©ëLžJ/–xÛÁÆÐzžßKw©ê-¬ýšÚ5‘6Ìëñ,8ÎxÀ­”Ùi‹qg­˜#x‚$Ù‹mËœnó®OËÕôÍZ}2KIg¸l${ÊÆI$g€+KÄÞ&htµŠø3ù I,‰mÏÌrG@9«zUÃxdzx·Àï3%»­²ê‚ЭĪ ü ºŸ—&º)· nõ+OêÖö2è¶VpM¥H Ê»ÔÌìpC¹û ‚AÀ«¾!ð÷‹ü7r£T°:l¥™e³Ši,¤¿Ö¨MßÞ©çÞ½Â_ |o¤ø6÷âˆt¥×|1 ©oríÍìÊ7fÞ$Ù@'t0 3^Ëþ=üUðö‘gàŸÙxkAÔ­äKHm.šæ)ÅŸË0ó¦EòØ/.…AÀ>æ´³2?>¼]ᯠ[xq5Û)eÔRkˆàŠ%¬Îv<ûW­·Àë¥Õî¬tòçHÙmî ÈGuÀ1³´`·^¸®ƒöŽðV£ðã᎓ñÆFúEÖ¡@)[)Ú8Ýä ±M™le}ëêÂÞð‡ƒåÒ„­±ö %ŠÞ6b/&™+ì®â:½]T¢·g]G¹òßí ðÅ>ð‡öYeÔšÚñ!e·V # _’C}:UÝ7Ã~ ‡Á¶ÇXÓÚïÅ¿»•,‹˜˜G‘Ë+d¹É#95Õk­áÿ-µÂïk¦["Ek ¯‘NC`üÁ‡ ŸLUÈÞŠÏÆÞ,²‚ïU³ÚÞVûJÚŽ ;ˆ'·5»GK„z3ÆüUeãËHgñeËÛéšt’¢ˆ¥‘YßyÇÌ äŒgÏZó¿ kÞѼ[wâ e¸µ³Ôm–ÓlàÉ`âE“´0z‚O¥}ñÁº/ŒôIµ]>ÎÞ h£·»û=ìÂßs!k,ŸÂ$÷¯3°±—ãg‹›UÖN³Ò„‰mèn²\3JÊ‘‰'#ljƒ“èk ÑÒç5jI+£ßÁ¾&ÔüBú§…â‹QyeýêÅ*âÙÓ†ÇPCŒSxëNñO†n‡Úìàò®_5qgî /#ëÒ½/âWìãáÿ‡7³Ò~xÏR¾…l.5 r[–EžÔÇ"*"½º¡rà±Úzc°â¼ÆúÒÊMWìw—3jžK¼…>eb1–QÓ±®W±ÌyÚéSÜA»-üÁËýžA´*)ÀÉÞæVÖK­&ÛTՕⱚV+ä©Xx?($ç’G_zìtiÞT÷Ús,›Ty×t•²ž?!Ò»;ÝFYtS6«8Mê‘ Åyä7=4ƒ˜ñ}:ÎK-~òÎæ6»3\4Ë>Âã†'|Ší ]Whm³${„/ð][ž éÁý+—±ÒüI:[ÝÉsnfRÿ†[<á—µvzgSÄž¸ñƱ5¶‘`Œ±H6#Ž`ÅB6ìà„Ë v4Zìs6—±é©$’¨3Ú·–öÙÄŒƒŽ{c¸õë]×Â]CWð7u­H¼{Yu;q¨ZM8Ë’£ãŒvõÍsx+YñOˆŠxmšæãV ßåJË€7Ã…êÜç Î+#_²Õ<%Žƒâ$wz¡ä\ËïÄ\`ã9tíJ¨®æ‘ó-ê1§‡>&^Ohßf”Q*ÊÚsRà VîÕüƾƒ$YX¶:}1]_£Ô<'ãíRy£E¹¶¼ŠõâWocI~Vþ w’>¸®k¨ôÛm2êÌîrVãU[!qÓéô¨w-3¬ÑµÆM^Åm¢k]:kGluó¤Ç>…k°øc>…ñÅ€Úê[.¥* 4ªÎì¹îØ }ë^i¤ê-i¯M«ê®©•¬ÓHò ªŠÍ¹‰õÀ9¯¦ÿgøsÂ_¼Cá‰ÚkÝÄtG\h¥Œ«Çö;mÈê[ËJ›¿ÝÇjÕ!IØècσwŸ5ÛNêͯ´m>,§Û7“åÏubß{*¨8õq_Íus[Ç \4’Ær®Ù8Ü'ŒqKaâûh®ÿX¶·h‹í˜™3·oÊJ޽¹•ã?ŠWúåî†ö–pi²D Q¶ù6©Ïœ ƒÐ°äWD5v8äÍøBÚââöí¦Gyˆ‘·zEÁ<Æ ¸`3Áë¤Ñ|QðëÀž"·‡ãVq£kšs ˜3%¥ÂÅ‚rê§9A$Ö½›â—ćßíìþ,hfÞMJêßH›Uh‡–·:…¤àά¿ÂdC`œ tµãoÚOÀ¾#øE øû%õFÏSÕuE¥‹>d×Êé Û;Äe€ÀGÒ´”b–¢Iže¼ãíDð†Å´i§Í©ÈjT"¥ä¾`R>÷ËŒ/°®7X¾»Ò~%ÛøÂÝ®PÈ›ÕãM«.qÏ 9(¶økðcT·Óµ]^ÖèhÍs-¤»&ž(Èʯ$þåšNµ¬xÆ×~ ’îæëI{iRÜÜ—µ¸Á'of,0H98®w»´tFZXöMNÎïÅw‹ |<ÒVøjŸi‘ÊÆ§#fðȧ‘^yð£N²¼·ñnšØgµÔàp$©Êg™µá/j^ û^‡¤´ÖÓ”k›yÐüë£Ë p@Sߟzê¾Z-·Ämz@6¥öŸe7` W#ÈR”ZŸ§Ú:Í—+·˜yûÏŒ}+Éþ=Xø®Ñ,õIíÝt9¬ryO(f »烴ƒŒŠõk aÖæiØìÆçynƒñç?5;Ÿø»Dø& †ÚÎ+¿µÏ:äË3Ü®X±=G•EsžµT£y ö˜eЬ¬¥MZ9$s < ÄnËòäw9®+ÀúíðTšÄÐÅ£ècý-°ùëPsµxÈ8Éë^ƒi ÝÌú¾1¸*H ¨ù‡=+€F}ö^jQ"h³›P¨~y##‡ à:Õ £ÞÃkyª­³ÝÊÈÉÈ©$‡Î{tÍrOFijü,·ñàƒû ßÉn|·Wz\~jˆæiäÝ5±§˜Ÿ¯Ò ñ‡´]wÇÒé%IJéú•³ëP8_Ëç ÆØ=yÕÝ‚øŸÄCOÒwO3L ³–;DŸ. Æ;×uý‡ªü-ñ­Å¥õÌ’ ½)展×ýjHûYÛÉ8ö«KBäfèþ0—Ãzw‡uËÝtÛ › ß ‹kÒ<ÈϨ$øW³ÃñvÃTýšüQáÝVcÔt¿é^Ðጨ–XÞwk«®ÄïCv _=ë:N§êè¦7km0.TŒáÈÇCßšl“xoMÑ#‹\š(3p4ûv˜ãSó)ÆO#¥\/{JÇíÏÂÏÚãà_Â?‚þ øW.½á»y¼=¢XØ\Å{«¤¬ðÆ–EBFæËúüÕ³«ÁAþéñîOxAݺ„Ôg›ÿ¹çŠüR“âWìŸk}Fye3– ¼M#898¨¥ø»û(ZǎݤV\¢­«*:``q^äqsQIcÁSœ®ÏØÿ øX¡Ï€õ¯ ßß).`W¾|©ûîJ©ÜG|â¼;ö‚ý¼þ|Nø9­|+ñ©a§[k©Ie£êËC ™&c±dŽEWGÁÚÃ#šüñ‡âoÁÑÚ4I0”ae2¤{”õõ`¦qXº—Ç_…Ú=âÅ{à±cŸõ‹pÙÇe c¿9¬>R÷S:#—Fšæ±öŸŒÿà¢z^¯á¿ øãÄÞ$6þ1ði{­òÇGmÓÅ2ùRE4ÌÇä¸P Ñ8°V0º¿Ž?µ½ÿÆ=+ÂZœÚÃO£êPë(­áŠH²CF$¶ö9.GQ_i?¾Ük–š^†tø%­|(#vuÞ¨/Œtâ–oˆ6zωn-°þÂcŽIÂ1 ²0sžp®=j%Z£V{iÅ;¥©ôÿí%û@Y~Ð^Öu(¬u}3[‚/±\jq †úÉܺÅ"Bùo*OÞB̹\°ÈÜknëþ 9¿à„³ž“áϦ‰‚žk/í5‹Oš”ÌöÀeÓ°Û5æ¾;ðœxVñ4%²êw6V© )U¤„I€z¿xíÿí?‚5 _‡vþK¯1b„Ü½Æ 7Ý? CйëÚ¹æô6QÔàæÙôý?N·bðéTHü3ø#ðà±õ¯Eoxü#â/é~Šä\Ù¤ÓÜÊì²ÚDç™cs»o®+Î5=ul/×ÂðD&ˆL[»HÜTõ硯`ÓµSÆ:ÕÍ•™ŠÚ{Ý$Ù’AŠÝ¾RqÔŒ“ÍpI£¥_sÉc»ºš; ;—ß½À¼TÀ![i#¡+¨ñ«$^ÔeÇœðœÎK /áƒøådÔ<9|ßÚþ‘’Æ–Ð3©S$±pøÈäfº°“Pðô—R‘ÝȤ.?å˜ÇJÎå—ÃßxÓàý¤~!økqoov |Ÿi·[¥{R ‘±ˆÃNŠæ¥¿ø‘áÝ.-'Â7ñÅ¥O(º½œ¦éqò=00½uVzn‘á)t׈ýµ§.eÏÉöp2pNsÁô¯½ø…®jÞ"’ÆÖqkig$rB6áu†|ާãÞº)= æN»ÿ„³W†]\<¶V1½âÛ€ªèeês€y=9éÅt×~1ño‰| muéXØx~Ö;k’Ò䵸†"àcj’6¯÷y>™ã ^øËÅÒÎÂ}7íqeþ6,GŽ6޵Ô|*ñN«®xGÇ>ÖbþÒ¹×n|%¢Ã#¥dŽùgÝ…c/ŽŸ¥lÓ33üg}ã›ë‹;j·Ú¥¬ÑǧZ]LòEo¹]ÆÇ†ÀÈ‘U.ÖíÄok2¨ ¸!‘›·pï^)û øJ kÆú Åä0µM_RÜËRÒz—˜m?…~üdð*é^øà-Ch•Qî¥V9Qç/–‡ÝJâ³h­¡ð…­¼ 6Ž&Ò"²¤2G&«9Èðí^Ùÿ ®7HNŸkåÄIWË·qÇ®OJøÄ–«¥x?T×d˜µ_ÜF…‡Ì#µ\d` C[zn­©x—â>“i§^]IilÉ+±•Õ–;W=ˆçØV3„¾Ë:£±úÁ O[Ñm5Km3íÂçxØûðJŒ:s^)ñcÄ–ÿµè¼5«xzk«éíc¼}òÑQÙ•AÏÏü Å~{OñƒâDQ9ÑüCj¢Y-¯%,Hù‚Ÿ©5é¡â_é֚׉u ½RöUÃOy3ÜJ#\í@ò;FI ÐOzͺWlÚ59YžÜŸ¡BßÁv wdn¼•›ê~N3øŠ¶ßµ¾ŽVâ÷Áöh¿3¿“9u*Y~o¡*+Ê%Ò?²3éôè+Æþ#ÝcO–(þ\m‹Á'9ÿ#ÒŠu¥'k›×ÃBº>ª?¶¯Õ~_LûGBQ ›—9úŠÕÖ?l] ÃËmþ”K-´WˆR0ÂLãqÉ==¯Í»+GÔob°·{‡X—¿2£õ5ê¿§ŠãâÆ­kb¡ ±h¬£AÈQm¡Ûp'ñ®ÓÊ–çØ¾ý­Åz”ÖúV† ŽÒÒ{¹¤žA…HF„3šã#ý½5Ôü#°3õÿ^TÉkÀ¾-ž™ðÇÇþ(¹\Ì–ºt#¾ëÙ°Øú*d×`2¿Ô~ÿÃ{ë?äZ€œÙ»nGàœWmñ'ö®ÖþøŽ? Ë Û^±µšPÓÈ›^â=ûTœm#¯>µùõàkÞ4Ñ´Sq{{m P3¸<ŠJï~>jÖú·ÆjÖŸ%5 ’0ÃømñA³\Ö¯„°jß|á sU³K¬ê7²B¶ìͲ6òHÜÜ’X1Œ\—íi£iz‰Ø3AeáØã@Ã41îi=Oú×Þ_²_ÃØì¼5ð÷G1¼¢xfÖY£a€“^Îxþ&i |ûnj1OákªÁ>ÙyÆ3ÁŽY6Óš’W“G“Çû\~ͶÐGoƒâå®90Ik[[ý þøe´?kZ>¦4½ZÒYmàO(LÁX*Ëó>òzœšü¤Ê¤ u8ãp羓ý¤’ßE×¼=à8® ¸Ð,-Ÿcò'™/$ú@ì eËÔבX·í{û3ÉjñËáß;À>t 7c®Æy¯™µ%W×ïµuó•/$2ÆKÁ;)÷â¾oðîžš®¿iaRe.3øçðõQ¶„ÆÆ(¹Àç'ÍZÑwG§ƒ‚i³‡“ÃÚm໎ܒܷç׊À¼²³´O´Y,®[ržO¿ZõH4Û%–<»/$ûWŸxŠÚhm/Ì4Ÿ»áK0-Ç dZšUgEh.]6>,ñ¸Q¾­})“±guQŸEWyàOxžæË\×µ­FîâßHÓ¤dNå<ù°‘ÿ=ú×áø†a¶ÞÂêEq‡ÿe¯Xµð¯ˆôÿ‚Ú…²è÷ŸiÕuXTnäýžÝ7tÆq¿¡ÅwÇcÈ–ç‡ùú“í^LÄ‚]ONkÔ|9nÉð‡Äž-½¹5îlôË0îXï´’ã'¦Á\Ä^ ñ¬‘—þÃÔX E¤¸çßmzõßÃÏ'ÁmBÒt{Û›»ýFëT¸Ž+y$x’%òcÞª ]ÃZb>qŽÁž0²ÄsÏ46•q¥zý¯Á¯Š÷ ¶j6>è‹ > EiEðã…Äâ(¼+¨lF\å€2ÇN´Ñÿgë­QaO?[Ö–Ì1?v#ÜzsàŒ{æ¼hiñ+nnHšû'Æ?þ+Ý|2ðoƒt"æíìc¼º¾H¶*Eqq&T1b2Ê„©9ǧåÐ~Î_IþÀ‘p `Íì/R™¸ ¥tIðâ¿Ç? kOÂÏ…Þ%½Ò­õ­VÞ/³Ú* Ò\º«±m¥þîsÏAé_Ð?ÃOE¨ø¿Äšåð2°ÔVv ³Ç`·¶Oç_±7À¯h_´Þ•âÚ­µ—…­nõ©†åvͼa`’U9äq_½>Òeð¿ÂCÄ—… Î%“h¼’ú“MncUôGåí­&áox¶Ïä¹óŒvΧ”rBÄc¯.©zó]ª#ž\@¬@펿³5¦ô>#)»-ÔjïÜã>äuϯZë| ¦Ú^ø†|´ Ç=}{W²·ì½ãÅ|ͨiß0ÈÄêÄ{@÷­O|%Ô¼!s=õÍÜÍ0Ÿf;°äƒ\õd£uP4‘©wk™ˆ‘XF:ã¦j¹µ™¤bà0Vüö2/îœa›.3:Õ”ÓÚæ™”Qœô$ òùnÚ¹( ¸Wláß%#V´µ"Î?Ú'Ò¾›C Û.H‰S†t¯+Ôü%ðÏ[Ô¤ÕµŸG`Ò‘ˆ¾Å,ÅTqÊBŽ™é]øG{žn5YXð—H`óŠö Æ4~!ñ `Åq¬ßÛéй˜P3ËN¸&¯'‚>Â7³Á `ãõ=+¸¼_€^ Ó¼Þ+¸ŠÚÂie¬’¼¯'ñ2íÀp1]§–Ï.r®±¬ƒ>˜ãê|:þ'ø›¥é’`‰ÄîÝ!‰üÀS^‡ÿŸì±–»ñV·+ýÐ#±?Þ½›áç…>økÀÿÄïêÌö–ªš{K*ˆ{’>E >f#iúSsè+x£Sx£PÖ.²MÄîËž»AÂçè VB[‰T cßôA‹öZÞIÕ[b—"ErHQ“Ü ûæ¾ÕÓ¿a¯‡vúU¦¥¬G{j×0Ç+bè(ŒÈæàðj¬W*?(ÿÖ3"¡Þ™¯¢þ²øCàŽüTÈþv°bÐb<`‡ÄŒ©9Ë{-}çì[ð#þ•5ÔŽ-ç_¨}„eqõÍv~ϳäÞ´ðΡoýcs-ò[}¿}¢EÚÒ;«`€1ÓšËÚGâÛ[]ù™u9ÇqŽ;T(¬ÍО=3_²º?ì±û&ê:œ:.”tûÛÙÑÝ"†ýår#ûι#œšð/ÚÁÿ³×À?é¾}R{ë#{"¥Ó1 •Œ˜Ù¶€ø$piÆWùÓ´dq€8ôlj­äð_ìÏáÿ€>Ýâ«—Ö™y,`BRrK£ñÇjÐÒþ!ü%Õõ{Oh ì'’þâXÖwhä2Ná „·SÛöäÞºý³¼1û;YøjÊå|!=¸ººuÞm㵌\¸‹ „Ž6ÙÜääÞ´°||øDþø2‡ hÐ[J‹‚<õ¹>å‰Ï½z§Æûˆì¼e¢(òÍÄÏ+™NOçÅz7Áý"ëUŽÿÆ·OÛ® |ÅwÝÉ5xí3®Á¤ßjFL´d,zîùŽÀç¢Zâ?ÿÒþiuïê~7Ò®ìo¦†æ)•†ã†qØÇ ûb¿1ÞKH…±Œî‰ž6`HÎ Þ¿D´Àû'þ é®hž ý¶ì´-VýfÒ¼@&Ñäv}ÉuöˆÚÝH= $‚Ò»ßøJúÇáoˆ~j1›Ã7:¦’‹(Ëÿ ÊðFÄú²¢“îkóê}bÿᯉ|1ãT’‹ÝþÓQqjÛÃ}Št¸P£˜˜±ø×íGÄ»=>ßö¿ñ„:¿öW4+ÅztŠ #M¦ç×r‚ØèXƒY=Àü:ðvšÖ?5Ey-•¿ˆôƒ*KÚeˆ¼L„ó¸:±u¯×OÛ·Uð´Ÿ~þÔW’Giañ[GÒ®5TF[ØãŽÙ®ü|…6$¡9¯ÊÏöVŸ ~/øÅ𥻬z^®`pNжѾåSìþ+ô»ö†ðu×ÅØ[À÷ºd s¨|:ñn©§KûÀö Z¸‰0 ÍUo”ö9«’Õ0<§ãg¯_x“Að—¼Qcw©‹}CQhm¥kÅU e oÜ0¤ç¸é_x~çY±ý¢4¤•6rÆKWN«æÂŒÉ‘þË(äµçþøcã++««Û]æY$|FeŽ& èB62|W[ñGRÖ¼+qáêpK½î= ÈÚ~÷#œc#ñ¢à}Kÿ:{½WÄ>ý tˆžñ7ƒ´ kpÿÄÆÈ¼3³ðm¦,ž§oµ}áMfË\øáÝrÁ²<=¬ÍfŒz˜5xÆxõ”75柴·‡¯þ%~Â~ÖlvùÞ×u? ÝgîÁäîÕ½””Hy«Šý’|I‰f={LMÓÍe¡E©B«Ë4Ú<™p©CŠ•±¡‚~è§ö’ñÿŠ5(tý +$¸‚I›™Zü•òùÿi#¦áœf¾!ñ’ÀtýkJƒd‘ZHc‰£åCG+l ý ý¬¼k ø“Ã~1Šfjvûà ¤ãÕM~nøÖÝoEõ¥š»Gæ™…<…9# Éë“D^¤´p¾¶‡QÕ®4iWdz´Öî;ãƒüëêÃÄOàÈ›Ž¿á{y¥lº{g  ?ì‡Çû@{WÉZÿfëZn­óü’FÇqãíÍ}{ -¨|ÓÙ²þñ=ѲŒe<%áAé˧åU"O.ÕmÒÏp‹¸Ã:L¬YpFHžµ·á-+á•ïdÔ~,é—š¦u4¶Ú|ÂÚá˱‘‰N25£kg4¶Ú„^IcqªŠAëìkˆÕnµ+AÒ¢¤¼¿eÛŸÚþÔwƒ¡½ÒôxRûM†ÒúA=ÄSÙ”žy8v¹Èbá\ÇÃo _iß¾$ü8¼‚ÿ„OÛ:‰#—ûBÔÈÈÉÑ”É ~{׃~Í<¼Ñþ4ü<ñö¸žDZ?Ь£žI‡ú%ñkRTcæR%#Œc_f|nÐàçíëàÿÜKå‹ô»±•ú+.è…lã«C(V1Š ‰ùƒâ‹¹Ð0r¯±¿ ¬i™ú†›%Þ™5¾óûèÏ ~^=AƾðÝü·ßô=^T úÿŸc¹OÊkfpOãç\‰´8ôjT±ÞÜF@è71eüÕ…kx~õì¬ü?¬HÛeЧŠäã¡[yBœÿ¼ŒAö4ý ÿÁ¾)Ká¤ñ÷Áé XêúGŽlâ’Åv‘隟¸–ª’9ÿj¿4ÿj¿‚V^ñ—оøO‰ é¾ QÛ/ ë2Ãi#(ç2XÜÂê»°‚3ô7ìQâ_ ü$ý­5_øšÇQÕ,n<â2]%Ó¥ò #zª«d†Ê’8 œ}-ÿ@ðmŒµ¶³â»¢dñ%Þ›m$ѨÃÿmxz rËçiñ¯¨<”æ{Aøomà¨õ/x7º Æ8ú’ÀI–d •n=·.85ÕxÖÌÇâƒ=±b…’|ã¨|7ø×¢|¶ÓãøKñwÂóLŠëVŠ]RÉ¢”œz•r¼wÍs0‚DƒH°Í%‘#ê··=…L·(úïöOŽÆÿâ…ôÝa{ù¯t«˜Ø`wŽVEÀd°OÀוümÒï–k‹nÖêÑïk&\3( –çµx×À;‘àŽú'‰7”[ {NÔXn m.cç¾1×Ôf»­>æãTý”4ûÖÜ&ŠÆÓí*z¬–åÊ;qÓµySXK?Œ£ŽÍ{}c"¦y˲‡œt#¡©å‘úóûZÁüiñÛãçÄ‹? þ#ø#E°ñ†§6¢º~¥,±\[›´FufÎ Œ!ç<⾦øSû~ÕÞñΗ⫟ø;X“ì–¶šÊ¯‰åÜ­^• xZ0ÎJå$Å~WÿÁb¼'£ø›Tø!ñüÃÃøãáµ’È&‰\$Ú<Â*Ç’\\Ù$<à½î z'°´’[DIÌ…Xœ :õ­â®‚çôQûÿÁÿnÙcö¯øuûBE?ïô¯ øŠ˨lu÷šu±“ÌŠHãómcY ”í]Ùr g#ãoÚ{šOìõÿiÖ­40[ée’ Äkg¼ã¬o¤Aè„u#5øï­Zͧi-6”M¤¶÷6æØ´kÑâEPØÈ`21Ö¿~à¶6Ä~ÔZÏÄÍ D…õox?Çzx ‡BÐOlìHàСǮ+éÙØÓ %íUÏÏ?ø*Ï/<9ûsx³U•Õ×_[=^=¼à`:p ¿ç_·?ðIÿnÿÁ;<=¡1-6‰©k00,IhŒìè¸>™Ú=ù§ÿŠðØ| ñÙu­xfÞ)eê Öå$D'Ùdväæ¾¶ÿ‚$kí{ð7₦”ÒüD³D |Ñ%Õºn ?ºdR}2}kÇÌß6ý¬}[îbmÞÿæ}ûIØC3è³Ý†+Ÿ´Y—Ç1«ÆX`}ð{Šðø$#h^ñ¯Â xn×ÄZtw–Ó_ÙHþMÊÛ»H±¼NO—ó:81¶Úa_O~Ò¶W-á‹)¦Éû¥– Å“$úóŽ=kÅ?à—Z_Ä»¯Û«Å?~YXß]ø›Fº¹·´Õ¯ÿ²ìä:xö=ҤΧ~ì,lNž„ן•NîÇ£œ/ÝÜíkاJý°?c½_[ø/£Cqãï ^ÝêVöN+Ñq “}bÁ²W΀nHΖ4#$f¿˜Ÿƒ:'ˆ>$|^ðo†¼;=™Ô¼K®i––’ê¬Ïd×2Ì¢rFÄì9;í8à×÷‹ñkáí;ðSÅÚïÇ cá߃<9©øgÃëú¶¢ž8È»ŽÎ_.‹}5Ã2WÄ¥NÁ3cÄŽ¥u⯇¶e®¹ã VÓüyi©Ühwb[q ’ê z¶²oE’5 *¡%7mÁÚôÐz%©ûcð»XðÿÄø*ŸÄ‰<_§_xÃþ²¾ñ_‹ô/,jÁyá> Yâ°1©7æa4 åït(¬xøCâoÄûGPøÛ¯]IaárïíZd20žc ÐTŠK–„´!ñƒ ™AÎ ¯pý´¿hŠß?m½7ödžËMð?޼M¥Ý*Á£†¾°KtìŽ]¥H^æy"+¼ˆªv'Éò ùŸàOü-Ž+ƒÅí¦ZÙ|>¿·š+½>ÆÚ1Kk˜Ö(•ù»6òØÚ@û£å¨v¸K~Ï¿±íûRÞÃâØô˜ì¾ÇêL‹;Ï Ý}²×Ëàb{i÷G*¸sæ/!Jç5ñç‡ÿlßÚcàG„-<+ð·GðîŸe¤E®É¬åkË(ÊJ¢…)¸Œ1nr½úÇ¿ðR_~;ðæ›«ü=Óì|'¬ÝA7 i³T•}à²Ì'$›€¦*Y7?4?k¿Øÿâ¯Ã-Ä®¬zÌPAæÛø—CVû,¬Š‘í$f–&Oâ]Σû݇Õz ¯ußÙ|LðMôæÐøuÓU°k¯5t­y ÛylW ˆn¤°`a¾cÁÆ|—⟌~=üj°–Ëâ_‰®¤TŽêe‰|¨ ²È¿2¿”pûG#ŽëϾøçAØ>÷àAa©ø—M¸¶}^Ý„S\Ý•>T·Yåš1À`ܘ”å±¼Så?YøÏÂa¥¡X§¿°&òÆèe„*ØÈù¥M¤øçÅšW€¬~øz[gÓõMVÏÄÏçD\¤öŒ‰n§·–J#眊­ñǣǺÿ‡üIå"¢D—1¦T«|E+7rX¯Jv©ñSYŸIð6ƒo 6x+ÃRx]g‰2.VgIGœq39y¨Mn\Ùú©£þÓÿ<7ûÆÚú{„ἊüKq4±£4$ccÂna—Ý‘ê°çí[¢ü<ø¥¤|QøçtÐø3OÖ|gª‹{¿–ßÅ! $oh¥ÎËFš1'5rï™IIÝ–¢ùOÕˆ¿i_ƒ´ÏÂ?ø[TŽÚ[‹?êú}ÊyGI³··· -¼hP‘*É4l©,7•Ú+ìKߌ߷ØÒµ }gHÔ¬õHÌwv÷g“íQº•>axcr0HÛœs_ˆÿà¦Þ"ñ—ÅÿøëáFƒac§x,ê‘YA©3Ouw¥Šo´~H••A@¥˜3ÎE_µÿ‚Âx³G¸‹þÿÚ\Y¶L‡FÔ$ªƒ²Gp‹ ׿‘éïSfº ÁüGê·Å‹øÿû>xRãÆÞ ÐôHÝk»Z+x¦½(gµ¸‰ š)í¼•ó¯‡\|å«~ÙºOÅ_Û/à¿íãý>AáI?±fq$­o4›…3,qà¦í )a†'5Q»ºêCÓsóö­ºÖ?g_ÛWâ?Ã{Kÿµ +RšÏuõÄFÆþÞá‚Fl6ñJ°_æfMç,ƾHñ^¥¡kžÔuëHå7~»‚k™w+[­£ü ’“ ÚGkõsöñðW‹4Ïø(gí/¢xrÒ[¤ñ?†R¹h#[…HžÎÒ9gçî˜äŒü˸…u8çœ<;âo?³Æ~ÑÞ4øacþ7xOûOƒXŽ)toJ•a·3¨IQ–SIç(uId ¸É^¨Ò'™?þÂ_l¿jÿÚú?€Ò_ͤYø›IÕ!{»hâ[u¶ŒJ¥†Á¸­»ÔÁý“>#x‚ڃ᷅õ+e†/ Ük:r¤q:Úê)$l Œؕǥ6Þçã·ü«ö«ñ†‡àÝn øÇÃP_iv—–%.V4Ô"ŠeDó“kî·‘#$ÆH#åäRþÀßdð¯Ä?ø¿ZéútpÏ1!ãkùöÌÄàe˜/ÌÝ@'ÔæœlIúµ‰¿f]Àº7Âoˆ6ºðgŠü¨5ök»Ù=›/ÚnþÎâ0½å²Ë¾{îŠè$€Ú‡ÁS¼ðŸà/í¥ãoi×7‡GÕü/¢kVRÆ‘µÄÒÇ4i+yh«‰cXòØ/<ŠúÛàΡð^÷Å?|ûRèZmú-‡ötÖ“Gª„¢Ù;|ÈÞ[Þ§ ÆE~Cü}Ñuí3ãß…<=­ø¼ø»Dðîl<¨?—$‘é–S,¶út¼Z¹aó;å#${§t†¨üaðOD³ý”müIñS„ëwñ.Ÿ£øjÍcK”…#\j‡É¸E’W$–À¯ñKªxGáõ§‰¯•ìüI>Ÿcr"M›|•yäÞ,†E‰°zœW3âߊV×?õ›=B[m?S¼ÿUwxÉpClíT'sc8®/ľ,#ã­†ˆ>HõÈ-uadÒel®¬_ÌpßÝ©@þ!žõ™g¸ë"Ñ>ø^jÓǦZ[,²Ç-¸Kˆgbæ'#‚#?*g¡«þ$üÓþ+øWãáµ¼Ôu/ã†&ÂÈ]Íiz˰Jcs„¸•9C9®ÄÚæ—{¬K¤ÞÙC} Ï6ß5…|Å @p@éœq]ßìeð“öyµøã/~+kz‡‡mW2Û>O.9®Þ7 4Š?.yíJ3ОWСu¨þÒ–ÿ>,øÁ[5}Æ‘ßÜZÞ{o3Açsº+æ'Œ75ú9ñïþ §ñÂ-_Áú|šâ:kÁ4Û[8´ôckz¶ß¸‹{Å% èÒ’0Øs\_ÄßÙGá?‹~2èÞ7ø+ñUÒ¼P|Ägá‡ÿ¼5ð/öñìý 6Ö¾#ñ¾—ãk»È’ÐÍ{,w†;[=ó.B ˆ)9;U@ÉŠ´&|Åñ7áoˆÿfÛÏ EñE¹Ñ×_ðüšÅ¬ Áç(P‘¹ù0ã¾kÌ|3àk™uû-û"k¿jb8ãÓ"W’çŒ#*b¤ ¶"¿]àµß´‹û^øWÀ-ÔõM"ûÄ×ZÔ:&¦ÚLÁ“¤ÜZ"·Q.O–’Heõgä€è?à¢~þÔŸ´íkñÁÚî“om¢x:ÂÎ)_Ï–ú‰kyLeRYR8 Ê8ÎáKÌÙþ)øEñoÃßäø;màýSJñ…ܱ¼ºý»%ÌÒtŸüQðî§6«~ ÕtÕ†h¢;j)ÜŠ2ò2,d‚0@9é_CüVý¡×Äß´ïïۂx<ïø‡Ä—:§ˆ º¸–M./ @¦(<= Ž/³l–P¡vg<6Úúûö.ý·e…Þ/ø¡ø‹áåö‡á˘æ‡Â«omäÑæiæhg,á<Ø„¢ÂÎ$ëÄB׸Ï`¿ ý¡?àžž!ðœº×‘ã/꺧‡íWËDÕ?´!ŠìGvèé>l‡HEù°JŸÐØ£àN±û<þØŸ >8|H°³Ó¼=á››ÖÕ5»Ži!iìæ‚96FÌä øg\àð3_ÿÁ?hŸƒ¿aßü/ñÎ'‰ü[‰ì¯lì—1ZKe=•¤—º!ãxåu +À8fø—þ 7ð»Ã:˜³ð¯Áý*ý¢_ÞÕ‚2>q´'-õÍkËk°wjÇè?ìSñàW4/ŠôoÂÒè?üOpæô4o,:ÜÃS·‘IP¬­ Êò9ÏPõw‡ÒÆÂâ(w£ü¸$€Š9Å| ûd|lðÇ-? x"ÏNk{ɾӭ__X­½ÝÕÄDgl–eebÙWc<Ñõ¸Ê6‰Œ”µ>Ó¼5ã?^\ø/Å0?^³¦£mæ 2€\H+ÈtîÊÀ×aᯆ¾%ñG‚õß‹1[£iPÝ®,­2'’í´|ˆÇ8‡Ì1Žõç>7þϰ¹W×5!}qr€´ŠÍ!s Õ‹1;@äž‚¿LÿàŸÿ¾Éð |9ñÂÞHt­WZžÍ¦&ÿQ•ØH"o–FHãÊ¥ò9R÷µeÙŸ˜1YëZx›Âþ!¹–eTo²\Ü+ªÍ0ËDÌ L€ ¡ +B[ h~–×]×76$Ïj#ŒýÆùXL>o›'åä WííUàgö©ð‡ô…²i:„~»’=N-Re·SfðyqùF%bJ¸lÀF ç5ø›ûNøÇ?¾"x¿àŸÄd·þÐI,Ä·P-ª\éæ0ðMÉ,‡s/^YO"£,ÌíJÏN¿ðM‡<y\ZߺF·)ÃÉl¿2†ByÝÁ1šæ¼WvºÏ‡ï|Aáèž-JñU™nòݰ·<×Ò–7ÅÚ[ÃZn½¢øCLðÞá˰Õ|Z±5†§rÛ71ã2O´ÄA‘AoŸÑ4[SÔäѤÕíMýŒÑ\,âòlãh(ŽÜ ‚Àz ~Îáfx¥Êà ¬Í-»&чl–¯<Õ¿¦Œ—z=©-m€ó±9pRqÓ=@ô¯3‹P¿ÖVãS¶”¬ŒBùdáv‘Æ98õ¯Að¾…m7‰ŸÃ>)aq{nÞL¢#2Ÿ-r2ç“Ær1ÜŒŠ§MÚáÊYÓf#YM/Mm"HQ—¾FU‡¡5èw:°¿Ó`³›ËwDijp­&ïácíŽkÓm?f sÃ_´÷ÿ‡~%Ô´ûû-jîÊ-J÷N,61^6ØV^K+³ƒò2Ý+ÆÁMcÄÚ¦‹iz¯‡õBÕlà24öštòE¶HáWâe_0ɺy5›§}GÊ|ñâMZËJŸû<[‹†‘~DÛÇÉßêsƒNÒuËøå•®mn¬mí`ÌD1ïÕ@'§õýüø)ð×â·ÃïÙ¿ö±øÁbº¾4XxÆËUÓTM6ë^Ð.%½Ð#8ÃF³ÚEq´…LùB90ÌŠÛÞø;ñ"ìM_ûWK×,ôŸx{R`¦yÛʯPÀ#£s]¿Š¬Ã_±ÇÁßA¯I}kqãm~éü5.¥,–öÚ†›u>nþÊXÇnÍãrª–iç­vRJ*Ö&lý+ø ðèþÙšœÿ²Ãe‡DÒôˆZ„Ö~,½••JU–â&ÑWbÚílçpÀàsÓÜfßÙÜÛ|Eñ/À ø.ÇZ¹ðF°“&}@Çacê™ ’\»–IÁ„©=kòá_¾!ø3Ið×ö.´Þ²¿Ž]ffYFÔó™É FUƒ1nJñÞ¿­OØñ¼ñ/áíLj^%¸ÓÌ÷2ÉYžæB¾k³ çksŒ€+¦M™·cðëþ )ðöxðWüCÃøBâòÃÅßü[màûý9n„ðèË©G%Ä–ÒF™Žoܬ2Bùfqƒòÿà¦ßt¸>)üñì:>¼úÆhš~±˜¶óÇt`C½B“€Pªevíuf&±Œ-ÒÞÏ<3ù>t„¿Ú žpJüø­¦xRÇâÿŠôä_øGÄÒ[¾—:6/Q#µw $8ÛŽsÅ\.j|ZøMàߎ Ûüeðf‰®ÞÙ[“c.­f—QAtŽ$À#vÅ+”qZ:(ãöí³ø³×µX5¿ M âLU^Ú|yÇ×ål|¼=ëCRÒu/[Ïã„¶‰« pÃQÓ—k^Ú³|Û$B~lºA ®1šþœ§ÿ‚2k_tÝKâWÄ}sG—â¡%ÝÅ®i-—‡ä´òÑV(ms#Âÿ)fÁ`䓎xøGöÅÿ‚9þÕ^ ðÔß¾|@ðɳðÔ‘O§Yi©sg¨›f*Z$•“pr²†áAÎMdé4o‘lü]“FðÖ£akâÝÝiÖ6ór“‰UíüÅÚTÂóÜdœRüO“âÄ:Ãâ¨b}#L0Å¢ØÛEºÍ-”#•è¯0 ½ÐuÅzÆ ~Ó?|,×? ¾+µÔOks}rE,ˆAx$|ÄÆF àtÍ|Ùáý?Çž.еK¿‚³O5¾™k$ãOYæØHÛcÃX‚»†8ÅDv;!럄úLñUŒ¼qfu-ÿŸ§ÚL-7ˆaØ:©Ø_-Ÿ¸ŒRüOðeÏŠµë±}–çÅVqèÖ¶±¨Å}cpÖÈ€‚Çr\$r`܆|Ï'‹ükâ_†:‰µ-ïȆæâò×XÃÇ2 iÝæPD®pK·çŠý{ðw˲×4ˆÿjφ‘|FøCã; t 7Äz És¬é¸ÅÏŸaD°û4˜"X‰eÛÃvv?!ñ7ˆ<.¥àÿØM¢.ÊC|¯Í´F]§Œ~#‚ _Öµß#ÁÍ é;ž ÿ's8>R\³ºo|¹¯Wñ¾¿áÚ3öÆÕ%×üTºÆ‹ãÙÙÿn-±´g¶’(m£¸ò˜eem‹ ™àË—ÀSîß þx7FÕ|Yû<üD¸ºKŸ…Ú®»«ÙÝ7lº´¾›,Ñ€7ØÞ/û,32D¹w?9µ‹ý6ÅÁt¸·I-eIF6lcž€wÒxgâ+xÙ®¬¡fš?•H9À”añþÙè@*—‰®ßź›ê·³Ü'ö­ÀPY׌]•O÷C±ÛíÚ°µ×Ôô‹[xäµ™aÔãy­¥òŽ%Åcc ±ÁVÇCøW“2¨Ñõ&¿ñ/Â~6ñ.®øÇ잟a´­pA7-;‡÷K`ñœäŠóÝC\†M&/ß)kû‹˜#€¸vùˆeìO#=+Çnü|mtë 6ÇMò{Ì>V`Ë©®ó[ú¬ºN¢ñ€ðÅ%ÔÝ2AÇ#¶ã€FOJÅ's$Ëv(&‹ì—R4ÞzíRÇ{‘Ûß“Žôªzœ°i—¦òÙ®'¶yâDoáŽ0§,qŠô‰ZXÐ>|7†8í~Ñ¥§‰>ÕwfAy’ãQŠh]ÛæD}€àq\R-Ü+q§©`í$¯)&Ñ×Ó&º:S9v!¨h—¢î#²ùDg’2%ùN=«îÿŒþ>ñ/Š¿f_¿®.$ûn«§x“Á7?¸."± çLòùdƒˆÿ…sÁ¯„uÛ¹.¬,4ës²VsÈ"5ÝÔúuú×»øŸZ:¿üËÂ0é7ó]j¾ñˆÔØJùŠ(®ã’d‘ó»•®U›±®ˆ-.D÷=kƺ¤iÓnäÔ¯XA3ß³ý§ÉÞÛcò­‘þa´Û~a˜lþü³ýŠÿjŸø‡RÕ5‹Oiš.ƒðúøJ‚!wq×ðɨlÊó$L@ å£$ý)ÿañoÂOÙçöɱøÃûS붺?ƒtÏ^øÿS:›~áoï–; 5X\ZE6ÈòFâ>\íÁ5x“®Çʾ-ý›~_xƒMðÃkArÚåä¡%·¹óÖËF´Ë<Í$Ùc$Äp#€kó‹â»ÚÙ|W¹ð¿.Ðé°Üm³’TW`¶Üä¸1wë_§W𷝬£ñ “\{âDÚ½ÛIsK;M»žYÐÈ0›2$[{9ö¯ÎÚ‚ÆÃá7ˆü.š=ª$M´Èé’$P¸$Žøúšó0òn«‰ïb)þá6ž¯üU©]Õµ]^âI|ÒѶÁµpJ¨>Ù¯Lø{ÿ 7ŒuÓüûRöêÞ6 #$‘•,ÌÙéµpk?VøSâ?‡ÚÍÕ¯‰ ¥ÍÖŽ–’Íh’/¦¢PqË.Ó¸cå½£àï‡äøW®ø“Å:Â&£¯kzD6zîÛ%ûÉ#º¸ êªêB žßCå/][è“6q£ÛË-¼Í{ŒÊ~lŒ¥y”¿´×^ÂÖâWxšrwye‰APAüGã]—Åo h¿n<9âïý—RÓîâ³HO’OÞ¶ÂpTíêqë€+ƒ> ð#xºá4Ÿ\j𡹏 j4ç„3‚Á¿xÎ@dŸ¦+¥RKS$}~øcVðCx£Á‘.¯oµ¶¶hË·–iîIeIrpOZ¦cM_O¾³–Ùô½wH€Z^éL0ðOù¶ãa³“õ¯\½·ÓµÍKñŽ¥!¶°Ó Ò¦x×…7îÀÎZñOˆ¶··óÜ|^’ þ}Y®moc\™ídb[#ø€CÆzã”®ìΩSVÐä¼=5ïŒu+=+Ÿ³­ËãÚôíϵuÿ´_‚/ü e¦kR2ɤì‹ÉkY0 Î<óõ®Ãöxð·ÂÏüaм9ª5Ö¦ÛÍÀ[iüØÆæÆNÐ2G°¯Mý¬¼©xr÷OЯ/æ“qptû n÷Öñˆ †)WU”sÎ0ïZSŽ—9¬Ï’Z õ oJ¼·&H’ÒI‰Î@P7uéÔqêk±ð½ÜøÃR¼…²—štrBÍÆ7çÐç¥xO×~†2]K,p;"Ä—*DN /óß½‡N&Ïá„^7·PñiäÁ#7ßÉÎBã-´ñÅa8Ù»Øé5V)pÂU®¯á@@Ïßþ«“Ai>._xÂãl©öLF'߯ç]‰®m´ÿhp±Y"¹ò.âuèæD qùþ5>¹w”Â¥½œ“nÖ 8ÅB“EÄítÏŒÐødxKÃ-à'\–Êþt[›Ù¤-?Ú³!ó'/á!½ âèºÏÂk|9§‹KÍ*ÎòçT¸# æ\ÊHXÏVÚÈO;kÀ5»'Kéuf³f’Ä!²œ9fe +(î3Œ€x5úûCø/Ã_?fýRÓKŠiÏØl Ì×.7ÜÝܲ#nŒ0ÅtÓ‚Ð眬ÏÈÿHt¯Xi6÷3Ú¸ß4nL„¶áÁÎJàúW–i†ïOñ”ÁHh%WL󷸯~K};UÒu ìÌ6WDäüèAc×­x§‡æ‰üoitñ%ÌqH¢”f9<µèøÍuF6¸™ßø†ÈÏa¥É#ÜMe2˜ro™†}9W J—"â+¨â7/lÅ‚±ÀuÙ­]7Ä·zþ´¶qèÚM±½;wÅ,‡9Î8ØOá¨ìm¯\Í“òðp¼qׯJóë«I#» ãvqºïˆ5? ͧøßÂ÷rÛ-ìÖáZ~V;Y[ƒÏ<ZîükãO|Oðvã+™¯f“Xû ©¹Æc¶iU€ÀI ×—xS›Q¶Õ|!:ŽÊHå¶*Éöõß^ËðkÄú—‡üSáˆ~2fy´­ZêîU‘€a†TU>ÎÌ h•‘Í7fzG†aƒÄr|Mñ=Âyf=zKx2¼…X`ö,>•ñ·ÅÛ‹]&Ó$8}Qâ“<à9>üšýÐ|/{á¿Ù?Mñ½Úê^:–óÄÈ ÝôÌðç¯îÎzñšøwÆþ*Ô4M?Ä:LJî^)lÞÎÉeؤå wÆàGÝ?m®B|ÊçËž,žÔiº0‰Bo´yØ~óIÇÏJÓÖ üU ¥ª’a·¶‰‚¡9ùw8ô5ú/{ñ ÆþѼ'ˆõ‹­"+«Ç\rqò£`Ðñ\'¾,øƒÇ†Æç@‹Ã:„šìZ`;M+â/1‹³*äm8­§6¢*1¼Ñä¶Ö ýž—,Êãž¿þªòiºÖ· XE+µ€Ž6s¸)ë´+ëë]Éy#Þ”"1…ù¾fÛì8¯ž5mW]ðÿŠBh—óY¤‘ÌÒˆ[ho˜"œóŽé\iÞ¡êc©œ5ç‡ÿœŸlqšó_ìV¹µŠÒïP#M±ži¥Œàa‚áOçÆ:V¦¡sªé /¼-%Ìu¨#¸’Û°W99#é^|·:ÇŸøcÃÇJÐ-lõÚYYÙU·zñÎ}«×áÕ|=y¢‰,–+8ÏÝߌœ}+‰‚m#Iµ[¸üï³ØÏ;ƒÇ*ǵYÐV=;EMbþ1¾á<óýÒïÂþB³ÝŒÜ¿‚_ø4ï ܯڮ®¢[¶’UŒ­²Òd¡€¯<¼ðn½f×¶újÛ»©åŠ’U6Hp§yöÆkÍu¿'Š~"ÙøQb%w¹ •X†#¡íÀ¤˜Ÿüb²†Ì¥i×®>ú¬vì²9Ë6Ìc½vÒ§¢3›ÔöÉ|!âm+Zñ7‚m/l¥¹Õtñ¦†‡8ÞaçnÖÎSšè¼ámOJÒgÑôÛ•žïP¾±–òû›xm\®Ø–6<–Êà1#'¥sžñG‡|Eñ^æçD²’ÎÖîý®.æœîy„œ·÷S°^Æ»mSMÕ4ŸÝi¾6]öß#Ó|Kbê>HmÒVòãc“ÈEù€àšÓ£3;Äš„š¿‰aVT¶`¯æ3ßm¤ÝÆÌ)šVÿx’?úÕÃ'©Ñ¡å¶zaml뺄±ÚØCiö‰>XŒ œsüëÓô |P“|<¹âíØ\¬‘®ÜärÊL(ükÁþ3ÙÏuã=+á&Ÿq‹(;SþžoÈÝR1è3_mM¦Á¤übMR"vÙiYl àªúpqžjkÚ1M=Í)E¶ô<~÷OÅ"K} áôÇ12ð–`ôêOJâ6içNðü±3‚VØl'ûÎø"§+Íb)¾KžCeðÓátÿ-çížFùš84ë™Ø¨äò™ß5³ãáŠüUw¯¿eî ÄÚdÍ„U>n?»À b™àWCÔuƒo hÂÊö 6ŠK„•–&ɳÁo¥Qñv·á{Û}T_èšµª–èÎÌ©å®F‚äßäWºxíâÏ |±ð„|=â__Ãa$wš¼Ù$Ëq(PJ’v‘´ØŽ}«–ñ_ÂïÙÿÁ¦ [V×3«XA©Ûì‚&ͽÆv€pÄ ã·z§ûNèóéß4†v(n'Ðt=3IEAËÌñïàz³H+_ö¶·ŽÇö‡Ö¼5¥á-4{-6¼[kx÷ÿfüêЙ÷ì¤x^ŸÅz—€–úí<=£A§D×a"f—Y¼ùŽRZ$ é_þ×1i¾øª½Áxí|9£F_Ë?:4jd8-ÆíÄç°5óoü7Á_ü9Õ5©âÊëþ.±´RÇãÒâ_¾õéðPƾø±âV šâÅI=œˆüNi3ñØü—ñ‚ü²ðo…tߦ¶ï¬Y\êÃÈxL€^LrX œ|¸ì9®gž0ø àÝUµ_Gâ9.L3DíŒ$«±€²Òp}jí¶¶?¯|3¦–Þ²°Ò­ð¸ÂC³`vù¸ükÄÌãiN@ÎäT³²öG ›oÙÙdS•¯£F¸„/<`à³`zŒöË Ç¥Ç®˜†;E»VùÝPô÷>µó‡toíOiÖ Í<ê g îr~†^x|$L@oØsÛ§¿Ò¸1•I\ô²ÚnI³È?²!ß±ˆÏnWùW‹ø–_è–|g¦M«Ç4ìR(å1 Ñ€ $sŽzz×ÖÓham%ÖÅò䀷$çü÷¯Šþ/CåëÚm¤˜`šz\0Q‚áÙ¯@¢£îͱþì,li^=ø¥\ëiž¹[¸dIª;"2œ«`¯ÌAèsV/|ð{PÕ&Õu‡kwspí+´š¤‘–vêvªç®+Ä~Ì-ß°f÷Õ3A¶AsxžXQ“ô$šô®xgØZ—‰¼%áÏÙÆßÅöþ±†×]ñ°­‰¼El¢;˜n8`GLsšì>øwáGÄ/ë?<_áM3M±Ó."‰|•f*DFy‘‚@R /­xÏí#iqáï†_ þÊ1q‡æÖ¦ÝÆ$Öî`l…\û@¯§üá;ÁÿðM={Çq®ê×pG,ŽB"ÈñÙ)¿·{ Ó§‚¼Uû"Þëp^øEyu(¸ŠÓ\`¦>un09É5·áÉdÿ‹8²øk¡xnÍo\»û!v²hÛtŒZI<ÌŒ0Päg'"¼»Cÿ‚s~Ôú奞¯áMúêÖúÝ%³¸²ýßÃ+·';HÇ-·pöæ½öø!oàŸÛKm{Q´Õ›ÃºV©x¶ÛØî"e)0$áÕÀ÷Ž)Ù‰µkŸµ_ž>ÏÆþ+’µ¼$Ä6±âØ6ç_Š?µ§Äµøk¦i×6öZÄ÷ѪÛßÇæÃ¶Þ6;Àsæ0 öÅ~Ïiò¶‰û1Þêsó&µqlnYgÉ÷Ï?…9¿·Ž²n>"è¾nÓ4ÍÒ¿s+7þ‚¢›2…¹®r2þØž4–5‰<5ádEþ¤‚3×92M>OÛâÜ¿hŸGðãÊÃivÓw6ÑÓ“&xíé_%¤iÎÞôટ>:TY›hxGã_Ž>*ÏuáíNÛN†Ê4Hö¶¾Kg<ÛØ‘ø×NÚ»F|ͧ’÷á\?ì½áÖŸÃúþµååex­cv!Ùóœ_˜WÑË¥ÆÇ÷‘‚Ùoâ$ËëÈÅTj£G¿‚£û¤Ïh!)>AòåF>õyG‰|[⟇ϯá{…±¼–fˆÉhÅ‘WŸ¾¬>Õôæµ§ ’º/–!BÃ#“´dçÞ¾RøÁ?ˆì4Y°>Ïh³IŽéÛv~¡Em…÷™Ïv-ÃûFü|‘6Câ)Õ,Q€’?ëQ~8 Œ²x–ùêPÆ£ô\W"m,àEdeG?äR=žQfæ#‘ž*õ-cÄnìéµ?ŽŸæÝñ6ªí"` ‰à *ŒäœW½~Ôž0ñç€þ%'ƒt-vöÕôO‚õmæ(MÓǾBûqór¹ôükÌ~x0üEý¡üàÄ*±]jö‚S·$CùÒûá TøÙâQñã§Œ6ãø~¿^ÕõwŒ¬×Á?±7…m!f7üK¨j›JãýJ l3ÎÒûH=1œT >ÿ‚køBúÓMø…ñáœÅ%½¯‡íŒ„¹i¦a< OV3êx¯Ú?‰ÖCß `Ñ-‹3^IèI>rzŠøGþ çà…Òÿf yðŸ;ľ!¿ñ¡Á‚3ö8x=—¹{Èë_yü{uºÕô¯ gå¦õÇ?¼8üÀ£V9$ï3ùÐÿ‚‰ø…î~3i~†O2ÛDÒ¡;`¬÷„¼œŽ¼*~µùúö–Á#P~•î?´gŠ%ñ¿Ç¿kÉ}N{xöv­äǃî±çZñX¦A øtöÍ#s8ÛÁ/Ê™ï•5÷gà þ߇v–òƱÜ\>ÀÁn€ŸZøŽ+ÍVò ÉwËvë gæs´tú×ê?ü#—V1[ØyöĹçGZó±õR‰ëe”œ›}9–Þy]>×o°†UÉãõ5¯—¸KˆÊdý?Ú»x¬fùŒ·d/Þ?‡ε#Ó|¤i‡˜xÙ É#¸ôå©Ó¥txŸˆ]ô«k½nx€ŽÂÝå,NO ë_Ÿ¢)$˜Ï>D™f=Ï'µ}íñ¦þm+áUì"É©]-¢mèFwõgðÓ(VpGž? ö°Q÷nx™„µ±WÊ!IA’yúš†A–Ï!$*’H8ü³ïZã ²0a·GQYóªc0‘’2Ëé]UîfÁn.n’Æ6¥ {f¾ûñ¶™càØÓÀò<78Ö®Ò/¼Fÿ'Ëç¨X F?í]Ç|­}ûl~ÌŸ´oÇÏ‹ö÷? <0Úž•¢i±ÙyuiÌ}ªäwÆIÀ÷¬¹¬ì3ðvm(¨Û$Që°ù€*©ÒK'ºEB¸Jý7¼ÿ‚n~Ù‘Šy<-il#R¹“Rƒ=˜Ã#Û5Uÿà›?µÅ¶’,gð̓[ÈrÓJ0¿/?Ým¦´R@u¿ðKO„øâOмwL¶ú]”v²€Ü^²°U 1è Í|íû`x–þÕ2¿Óqö]6ìipm9]¶ "aÏ_Þ¯Û¿ÙKá™ý?dÝcÅR8¯4ôÔ¼K©Gk/œŒð¯—jˆàvqß?ZþjouÛíböçPÕ¤ yw4· ýéfs#ŸûéD5“`}oûx"ÓÆ´¿†®õ˜£[o­Î¿vO.Å3?Ö2ò} }sÿú³oˆ~(þÑWášîé…„-’Û¯1ú÷±Ä£¨QÇW„þÎ-qðûöJøÑñ¾xÊO©[Yø;K˜¬f¼È˜FIà8/ŽT õâ¿D¿`/‡OàÙ«Áö–ð¯|cyq®Ê †s¹‚݈ äÏ' Þ*äÍÙ¨?4¸¼1à YnFÔµŽYXö 7¥~<~Ù>8›MøG­j¯úVµ9µÌ|ÒĆÈì'ØWìÄýA<9ðTŠ2ÎÒíÐ1¿|ÛOLrë_Ïü#Ç?dÕ¼9àInÙZ_UšÔ y™âF-ܲÚ3À'¦j䬎Jm·sÿÓþ_¼£x#ÆrÞiŸ>$éŸãËd»¾µžò9³ÎÕ:aŒüÄõÀä¿´·Âÿ…ž þÆ×¾üWѾ'5óÍ ïØ,f°¸´eºÈï½p}Ey´ºÍÂ6w’¤b0Si?\“\î¿,ºŽœxÄ“ÆA ª9Æ3×ñ®JM§bå©ÁY;Øê°]Û¶9‡pOCúWÙß²÷‰"ðOí–å„pkVʬ¸Î]X”P=ÁcŸjøÍ"¹ŽWîƒÆ·­z®«6«økÆë¸Iѳ‚@ù<œý+ª[}]®hSxGÇþ$ð”¿ë ºkȰzÁuûÅ#Û9Åeh³Ç£ø‚ÏYQ³É™wg²±Ã~5î´åüMð·Ž ˆ*kzØç‘~ì“Z6W§o|cÒ¼&öÝÜI$˜ 9$œt¨’ñ~›ý‘âÛý20T¬ï*°ÇÝ”–_ç_¥²—ÇÙ³À#ÓhoxãMÔ-ï®RÆ/ #<Ú¤,ìªLN‘Ëeät8ø3âeŸÚ¯4¿ ¿²œs™!8?5öŸÃéÎ6IozŠF~ãÿ€^ML€ý*Ò?m€^6ˆ¾.òäÉäÝÇð<ð[Íf±×Þ½5ÿnoØ?VgÔÕýµ<}û/üS¶°Ôÿgùµi¤_í0êšOöyAYPÆp7t*ÃÐ×Öv^'oˆ? ?fï‹w!…Ñ}cÁWŽÊaEym\‘ê¶¥dpüŒŠü”ðÞ¥`5%´ºÚ|ÝѲ?; Èëèsƒìkï¯ÙwT»ñGüßâo„ãºhu_†WzW‹--¶ŠÌ·J«‘†˜ “ÁÅKæø(GƒZmJëWŽ)¹°ƒQ §$nñ¸Áô@¥½8¯ÒïØâU–¿û;|@³×m£×_Yø‘,È 4ÒénñÜ¢1 ìÁ=“^!ûkxfÓRð&‘â»xüØ'ŽKVÞ†òÇþ[4~Ä¿ô9@i/tmı¢œ¨ŸÃó…œÛ Ýȯœ¿à:ÔuLÚ˨ßiÎq’-õ(ŒHTvRÀc¶NkÙ?à”z„7/Ó~êËæZ_]jÞ˜7¾× Ó(e=C2®=ëãƒ7ÏÁ¯—šV¡û}¥¢™z†{½çd½{±‰Žzç½W‘qØýŒñÂŽŸìü2o¢ÓµÞ<ÜL»à{ˆK@ÐÉŒ2î†ÁÁ Ž+â¯|¼Ón‡üT—÷V—&†(ðW~!pr§‘_£:çü$ŸñPxàC}föþ#Òä ?y¸ÚGB¬á•»žÕÌ|ø×ð»öÙðÄ¿¼DÑøoÆðÂÂÚ]™W8Îèˆ?FÏ@e ?0µ}ÿ»±ñ‡Ã‡ü2†òÝõ 3SiG-õÛ#lõÃ\(5ðæ›ÿ¼ækqy«{hÁ§U;Øma´œð}«í->%ø¥ÿ¥‡IG?lðÆƒªY9,/¼1x·6ññÎ^%Üv¡¡Å£åïþÕmjx>,ØömGÒµ+¥‘ñ‰|“o0˜òÀÇ®s\Â=êÙ«ãÂÄœ+øl¿ˆ¬nBÉ£Ý-Ññò<§¦sžkß|}maâ_ƒ¾øŽò$²©½ÒكĒ.ÛdŒúWû$´VßµÝÿÃËÉQì|u£&‡,Ú„[‘ŽØ(™#ÐzÒH»ží©xvëãì±{ è*n/.t«[‹h߬’ÛLß—œ°l.3_“ÞŽ9u™í®‘yñÌŒ®…$G!”«aƒ+®HÈ<kõëþ ×u¬‡W‡.7CªhRÝiR¯Þ+4;Â8ÏP²Ã°„Jü¦ñþ«ªøòïÄšÎÙ/¯/æ»»‘Õ7R3ÌpxRd,qëJÝg±øêÌ\ø’âëi"þÒÊÿ pwÆŸ»Í`é:m•í–±g1Š­%Š<.ïÞJ¸_À‘^®Ås?‡ôKëtáì§³ÜOðÙË€¤˜ Ç­i|&øgão‹¿Wá×4ã­jrXM¨Ãf&Kc"X”ó »¥€”\‚søÒõ÷ÁŠGßøgã·‰Vå´»•u³ˆÍpm/,H‘#LŒ°`¬1ÎWŠúÓö§øáðGãÔ'Åß}& H9ÜTeÛyçåùvãҼŗw‚,® |µ´×neÜì0*F5×xçÅ–ûEü8ø©¡ì…îîR€çî¤c!®»â.’4¿‰?¼&±JÛW{Ët?(Ì|ÀÞ…@“µE™I–>Ë<º¡´›“pœÿ¼‘ÓÓu}M¤Ýx;QñÄ{ ÜIy{qd·™BVÚüGµFH3*‚OC_!ü&¼—M×ÂÆ¼Ç+ª²v>äkèˆ|?…j›hºŒ±[|FÓo.¦R¸Xnô±‘Õ\8#<©Ý×<4ØøòËF‹Âÿµ¿ +y‘Ü •YFÝÑÝF²)ǧÌÃ> ×¤~ÍÒͤü\¸‚åAÞꥎHÀ.î;ã'µVø¯km¤üaðÞµlpš®†Éó,® S(8÷B>µ¥á˜Æ…ñºÙ­ŽÐgxÈäc!˜}A øUu^ ðºÍàÿ|3Ôa«êö.`œù §|eˆ÷¼oQ³¶ÒÏàŸZxÊÆê&†I„I÷a‡oLÖÏñÜÏ Ê%HÚH܃"H ~ûËûR隟Æ_اö]øé«7Ú·ðŸVðíôÎs$·šÌ&~>bR ¶çîå½M~2x»ÃzÄK,-jí!c.匪¨¹8çúõðj÷[ø“ÿð<3]¯üZ‹:¾‘-³Z-;]±–HWäEæÝåIo!xÇV>èAµ4Ñæ?ðP¹ìþ ~Áÿ>&N¦MFÒí´çŸ¦Q¬$Æþ bX”sè*oø"梚wlj:#H|‹ÝÊè)#QÇ_|š—Ç>Õ¼ÿ©»Ñ4ëGQðÏŠ¬a·¶´‰çœ•C…HÕ™±± Åy¿üúÏÅ?l»ÅÚUþ'ˆ¼9kjV3Ù¼nñº”Ǹ ÅsŒ€q‘Ÿ¼°’Mw>› Q}b>v?biT»Ôü3«Chs2¼RƤgçY`‘€0+â¿Ù6o é?ðQß…éãË[{ß xŸWþÃ× »D1½¶µo-ŠŒ8aòÏñ‡Ã¿IO-¶µ¦»GùÜX\E3í?ÂØBp ëÅx™t¹& ÆÑU)Ip~ÊßðGÚoÀßðP9<àO‡¾ þçÄþÔµ]OTÕ!Ž;Ø£u¹„ÂÂÚWyeµO6EcI%€¿)ÿଟ4_ˆ?ðU?Œ?üum©Ù›ûi­æ[Î,£¶PRDÈd/]ËéØ×î¯Áø,çìÁðÏÆ_¼oãïxúûYø¡ãyüEçi§L?g¶·´·±‚9~Ñs U„–U p ø7ÿÔ¾|{ý«5¯ŒŸ²_†æðW…µ‹ eÒõ£‚S¨oµÊ#ž$Y[kü§–ÎFy?[i¹ðR~%øÓãÇc_øÅã©ïµ4Ó½î¦ð³Øi‘JX[[ïP"BWw•CJA'$“_¾!ý—uØÓâůì«áý×ÇÞ©¦Á©iÚ7ÃÝ^_ê u yrµáx òÉ‘LCan;¶¡5ò¿„¿iÙvãþ ­ÿÁ;ïü®YxóZñ¬)ÔxmtåT×cü›D¦UL¿+†L($—rn~·|Oÿ‚y~Úöž$øSá?Žº'†¼8ÿ5Áá­Î=}¥¼°¹h$º0j ³G ¢a›w¸ýá žõ?Åø%V¿ðº OHýžðoöÅþ¯£Ë‘ÖÈ:ÅÔ—wŒ2I"´Hî«Êª¸ xÀ›v ü¯ø©xÃÆšß‰4ŠŸ üQ§Âr–~3MOÚžç!)4¶a^âH™aŽTœ×®~ò€¿h¿²øïÃ>!¸»M;]Öm Ž(Ö)orËm,ÐfG–"’`¨=Ív¿µüÇá·Šÿh¯ ~п³F—â øçÁ–W–6^-²–-?QÕ¬nÓä³Ô­œ:}†7"EŠà4ÊÃî/5ËÿÁ$h/Øoà'‹>)ü^ý®?á#Ô>0êúåÄVÚõž™w|—zf¥\\Å›Tû-¼Æï{3H#ÌeÂŽ2¯¥sjr}OÍïÛÏöoÔ¿fω_Ø:Ãj6î!»‹P·B U¿V £Y7DÃÆ0G\Ÿn¡‚÷ÀÚG†´ùÁ»Õo$y²ÿ*H?u=‹0=;Wí¯í·ûS|1ý©ã¶ðÜþ {M)R%…§g»¼—?h´wuÆÓ#œî#¥|ƒ©£è>±ð§‡4¯ì½)e’PdÌÒÆò`,À‘ÓNÕåÏÚ4nçì¥ÿüÛÅ5•®ªÊ<Èà…YÅÒG‘‘KR„ÉžGZòßþÄÿ ô2}OÄúö…f¶˲ýr‡ ÜŒÃq'Ž:ŸjüדÆÚÆ¡r ¥åÄR°f/3ãܹ=½+ÍõýGÚ,c^»œÇ#»uZV/ï·q÷9¬¾³}‰;ÏÚÏöUÑÏáï…:‡‡5ýoP„ËkxŠïÅ7²ÛÅ÷Ú;{™‚ÁË\÷¯ækOÔlVC&“²#å’ÜÅÝØ`¡^žµÐèž9ÒÆ¹ž»Ô4-F6Êjv ö0œ`…žÝ¢lA Ã#Ú»!_iËŸ£?´OÁ9¾þÍþ Ô|9ãZørËS¡-Ƨo£™4ò„Â}±È#ÂïS’g¥yg…þÿÁ<áÝCÂzÄÿ êV—qÍk(ñ/Å-GRå—ËlÄg(¸þê°÷¯äµ|}kw­Éâ6µmCWW1M©^§Úï`ên&2Hùþñ~OSPEñ‹N½¹d—ý2ì˜[×è0yˆ³Uý§½"déÍõ?±?hÿðMßÚÇ2øãÆ~9øâOáÓá+›ôÕdîô'+›;”ŠåDÈv ÁrÅr_?`Ïø'ÅŸ†¾øG©/ÁÍ_ž7O¢èïâ+ÈítãxÅçû(1‰es¹€'œÐWòewñ×ìÿdßè–òym°+*³žùÆÜAÿëUkˆ>w’ÓRÑ´éd“dPÂA`¡Œr|§œr}ÛqþQû>§õ¨Á"þøëÇÐ|Q×¼!ð»Æ—–KFš¯ŒnµCulq\=Í«—Œ¦WkŒç9æ¼³Vÿƒk~xçÇsøÃþñÂø/n<èÁ>/·Ölm_!‚‹MNÕSÉ Ñ7«€ þrímþÍŸ§ønÏF½¹·B]-ãr“ Æ˜{³Ä9ô7xzmIn! ,õ;Ë_Cû™T‘ÛšÎ#%¬CØIm#ï$ÿ‚;þÜ¿<¿´ßÂÛ OZxœjV£gn‹c}a}¡ßIc÷d¸ò¦YD ÎèFÆQ `Ëñÿíçÿ±ý©<1ðŽÃÇ–?üG¢7‡5Ó Ûiúl÷ï™ué$vµ2¿–² bV3Î2@ÜWÑ> ÁC¿h?ƒwöv>!x«Ã&ÚàÏ&ÆãOÞí¹ÃÛ݉£>k±2Ø[$çqÍK±ü ÁÿµWƒî~üsñ•·Á¯Œ×¶ŸdÒ|Iin«¥j2ºü³ÛÇwæG Û³¾Òf|uFaÈÚj55[’ÜãÐþ<'ã[É/ì÷ñnú(ü3â]wG–æçì’¿Òà•â8¥å ÉFIpe#v=}ûã}Àÿ|]µ? 露ҖiÍ•Èd­k Í€ØÚZ<õ*3Ö¿Tj?ø%í gâÏ|<>¸Ôü]áë ϵþp?³µ«[‹‡–]RÕçe îîþ}²ðÈàQ£‘ÿü;§\iw¶ž-ŽÂî.ì ½²fŒÀ.,/€– ” ól|1vÁ¥[EtuA\ö„þÐü_¥üO•õË-"k¯´µÈ/ux¸CwW€Þü×Û±ŸÃÙûÆVÚׇh=OÕ%’}=4ûë†Ù+ÞÉ…š …U+µâÏWÈô¯…õ_‡×¶>ƒÄÕ¾Ùa¨ÚÞL’< ’Ï’_‘æ •#Šð=[Sño†Ò×Q¶ÕDgRÌ®–I£ÆÂ™8ëéšæ§UKbÜOßÏ_°Wì±a¬Þi~Ó|YimqwoŸgy3Aaf÷™S#$™“¸vçÒ¾9³ý•¾é¾#Ô¼£j>#MGGÖ“Nko틈£š•A:£‘ùˆB•Sz’1_ Éûd~Ùþ¶}2ûâ¬öÂiâ+Wa9zB¤m=ù#¾MKðö¶ÿ‚¬xûÇ:¯†~|M‚u†§u®CdÉoon¢ä–g™‡$Fª…$ž+xG™Ù2ZocSÂ_³¥Î¥û3üiøí…yYYkºMä×cŠÓB‘ã¸w'Ìv˜6v}ìõà:'ÃûOøwÆÞ&¿K馳ðìú=½³•I"Ó.#}T>+û¶„ Ï©é_qx/´Ÿˆ~^~ÌíñìjZ vw–WÚF‹á˜ö\ZßÈÏq_Ìx‘Ù‹ªüõ¹OþÏ?þèk>ø¾þO&÷N’;n>ת#Žt"7“j²Ä¡Èàd[ªvû@¡?ågûTüÖ4σÞ'ø“¼²éºbZjv1#ÊÍ -щ$(8ÆiþÂñŸÄÙ5oŒZ¼×sxfÇ]Y¬4¨Ö!æ³ °†kÉCÞÐùH *¯–êªG|añïíýáß…¶_¾?ø¾O|#K¶µ}8ØÏepúdM=Œ *[ÇtŽ$D‚@Ø’+ú€ýc„~ýˆ>øn áÖ±qsödcqwª“s4€¹f öœ€8Àé…zU#Mò½Dî¾%cò«ã?¿Úã½áÿhz½…¿‡ä2Gmªj¤2Œ’5|g6ìs]v«àÏŽzö‡yàÝO–¤ß#¡´½º†õ",)c©CÊúôâ¿[µÙÓট}%ŠÞÇöС‹p‘ARçžøcð[ƧÎ%‚MÊîÑËVQ’»wòkÈPÄ­ŠI>§á§‚?b/‹¿mîcÅÇÚü¢eó á' ÃÏLžyª~&ýŽ>9êW+©ø~îfýÿÛoB–N¹ ÎkúÙÓà³yJþ&†Üm/µ¦šV¾Öäq†îdH`çÐÕRÁÆÕô.¤¡UsJ&¾·û~Ô_l»àß„ßfºååþÖ°¶*½ˆžç{ä ýkÐþ þÉß´ëwð/Ɔw)c-óÜLOkqW„ RÐLÌp8ÛŒsɯ˜®|/¡È?´õ{dº»C‘$à³åºœ“‘øtè?\é ¨jz™ G „˜Æ7@Æî=Ï¥hè.S†”ž¨ú¯Æ_²§Œ´íjóJÐ|!qªÛÛ•Ž;‹\ƒh?!fRÄg ‘€{â¾Eøßû>ütñ®±¥øBðw‰/üiqq™§Ø ´Ï"Þ)H¢‰Y‰db ü­±B—bª XZø6ådCak{´lñ4Þ2AS޾ŸZýKÿ‚0|7¼ñ—üSÅ?üq4ú”ÿ¾@ú)¿fšK{ÍvââØL®ÿ7™¼ÆŽß>ÉäiC Üæ­^šoùñ?üwß ´_…_ìíãÈôÂÚÒ(˜iYf\ój9êr2kƵø'/üâgˆÅŸŽþx›Á0˜šâ÷WÕ,MÌVÑÂA!a³’GžP2c‚Œ¸.¹ýÍÿ"ýºkË?Ûïâ¾üYñ‡ƒ4_¶¡Áe¢Ý[Åln>Á äÒž ó¸Ý(b~l®:uwìÉÿ£ý¶?goí´ŒµŠÿ/&û&³o¿ü$6I?È.4Ë‹H ,DïÉ™:++GÕârûgØþwhÿ„ÚwÀ/‰_ü/á{oÂ+w›?‡õ?è3èWw7(Sí+"Ion€ù›ŒjI\ kÞ>ë|ûVøcÄ>-ÑŸXðö£¤jšuÞ¥Ænî÷] ‹‚ç`Î~áþÙñ×Ä}Ëà/ÇŸŒþ#ñß‚þ%Xÿmü0ñýñŠKKäƒ }FÞ(aˆÏ:ÅqÊW÷‹µƒlü)ðOÃoüý²<#¦xÚ!¥êÔ%‹VµywÅÝœë‘>Ñö‹9œ.@#VUS¦º ŽHýøåãoÙËáÊxFÛჼGᘗÇ^Ô'½Ôt§¶†{{}V`¹™É!‘Y™Pðzc¿,>-|wøÃð7ãÅ-OàRÁ¢ØøÃÅÚ¼V÷–+=äÖ—Ó•BÌÊ'rÒ»UÀã¯ÖÚçâ~ƒñ§á~ð®Öæh4íWƾ´Ôc•ÆZ5+uʰc‚ 'ñ†§0‡Ã’Ë …ƒ•†ÂËísŒGnrù ’2kÐ4¿ˆWZ}÷Û­µ”µšX0ÊÌBÈsÀeèÃ#¡<ƒÏZöߎßõ þÐ!ñ÷/´ÛÌVyúÅŸÚ-THÖXö ·˜˜'xë×&½‹ö,ÿ‚]ü}ý±¾\üxƒU‡Ãz'‰'3[Í6˜Ñ «‹†igò¼Ö;lã$*HŠêÙÂd œ%­S* Ëd~wé¾=ð³ü^¾Q¥C§húÖ—=°±µ“}¼‡2yÐÄ+$˜r«¬3ÎI® Ã:޳âß…¾ø=äZ\\I®Ýj×—2¶Ù]’—u›ª)'Kô<ýdý¶ÿà”_?c¯ÙÚëö¡‹W±ñ*øBâÖÚm:ÚÌ©™uIª°lä•yð™í€kÊ´Ïø&·Šìÿh ü¿ñBÛ[ë>¶×¯¼Ig¡NÖöRÜ€RÌ %ØÓ0ûĺí”æ’••;§RNÉðÇÅþ/Ç—z߆ü9g®ÞÛØÛ46—K棄Âl\•»wçšýø}ûhÁA~jz~©§ü:T]<ͺˆ…‘nyeu•(gŽ*‡Â/Ø×Æ~ ý eøQgq}wªÄnôÛaìž×Kd¶a2\BåHh䌨`¬ÄI”àšûG^ýžügá[y–Ú=>ùâNöT ò0Pz»°è3Pñ2ƒÐÑà$—¼~SÿÁC?j_Œ¿?ex7ÆÞ³ð´>.ñ=ž¹6¡go%ºÜ^[£Ÿ#{¯±eÝòžb||ø¦¤jZ'‡ìô†6÷Áâ1+Aå›qÀòÕ¾ñÁÎqõ¯~ý»4ω¾/ìÏà«M6×NÓfŒj²_Þ²;Û®ÕÛ›äù™†×É¿ü!ñ\Ð&ñ—ŠF’²(†Ê[]*vž‘&Õ²G*N=ëiVsŠlçöqŽˆùW^»ÔüU§/Ä}^D·û]üZmŒQd´vštDË»žîHÏrEv^ \ø–ÞÝm§Š(ã&%’ BÇÆI,È»ƒÆNOlõ®O¾K‡Æš¢Mg jÑ-Ä©˜D°°‘ÈÏ ’Ÿ0# ~¬jß´oíwñúwÂO}¯Tð"1™,ȶ2ÇûÈ·J" <¶9D #ƒX»uí¹ùÙâ·ŠÁm.'ß¹™¢+áæ¾íÿ‚l~ÖÞ(ý”u¯kÞ ŠÅäÖ¿²VC¸Àµ2ùEpFÀÛ8äär¯Œþ;Ù˦Gi˜oÝ_ÉPxe$¡¯;ðߊuêúÕ£‰\£Ê$L¯î ã½Z—+¸ìm¿³üSüK§xÇɆµÍD²¸’BtÛ¹ø7ñäÍØ+d€“^ñSþ ᆟ¶×ÀIüu?èÞ"³ð†µâ%¿e½‹Qºt…§¶µXž9m¡’hÕ·H’²aræ‹Á¼W¦ë–ž)ѵ©.$…’xfC”‰ËGaI\p@àþ5ôãÇðëÇŸ[âGµÙÖçXÕ­5ËËH9µ+YD‘™†äTY e†Öñ¯~‚§…„®ÙýOþÙþøÏ éWž ´ñ>£xoÄ%¬þV}5´Ç·u‘¤’åÍ`Ñå~\g8lMz>…ã¿…6þ |1ñÃÓgiy vGMñ]¡K²È±ÁY#œÈTŒ°Á-_ øOCÓÿk¯·¿>3\ø³â¿‰4ËMfk[-/^6Wæê"Z;í-Ò8ñ2„ ;¶rrÙ×”x“ö5ÿ‚<~Èþ ømûYþÒ—^9øyt÷zV»aávýõMõ+9#¸iRÜÌmæU4`€Ìv’k¶Ñhâ³Lù‡öµÿ‚j~Ó¿üñ2_ê ´+»Yu?kºbxÑæÖ—O½ift6²iÈ¥ÙXyk檒€ :WàOìâß| øÙúÉoqcã6ÿG¶hH+4×B®y“ç矗é_Õ§Æ?ÙSþ %ÿ(øÛâÛ£[ø½¬è—þ=û4Œ-"°l͸€‘ý«\Ã:Æ>hÜðsòŒœÿ&~ø+á‹¥ðçÂYµ;Ÿ iºö¥¬ßÛ›)µ+¥c¹X”.Çxþñ@ª!W IRQÑ3¾^ìúcে¼ká_„Þ ñ¶öPi^#Õþj¶‘àZ@‹¨8}ÀàÎmÐ6r9y¯Òï‚7_¿fŸŠþ¿ø1ñ„xkÖ‹©Þãx«J“_Ðìf¸Ýæ‹xKw‰ggÚª’ˆóÀŠüãý”ü0Þ#ø0ž1ñ…•¯„¼ðóÆz‰5¹yoµ;‹ûvµK¶—÷÷#Í( ³Ê ©bT:-GÕ¼a{¬xCNÕô?Y¹XôÝûRây`FÄó™¾wûÍ-3…t޽Iž")Xä|û|vñŒ5ü[ð¼Ö–—úÌ×!ÜÜ ‹–¸’hš9KÚÆÙÄk“,yvWqýý›ôOÚCöný¬~!~ÒZ¦àŸˆ6Þ:ÓI’ËÄºãØ‘˜w3ˆ­§˜ Ž&¹ ¸à’+Îu¯Ùká÷ôHõ_‹~7Õ´FDÿI_·3®þÑǸ´²9ôCÖ¾>K ·É¤KªòB™®IvV?.FOÍŒŒŽ¼àVÿWKsâsWýƒ>)ÞêÅ÷ü%^ðý¾§y-ÄZm†¡#ÛYÀóÉ,VñŸ)2‘Fëð8Qõ¯GÕÿe >_ø~óâÇÄO ÚÝøOµ³ðÜQ^yzsì¸Ý=ô¯óʨU”ÄŽ+ݾ~Èú—ĆþÓŸàªÅ²*b9Uä=^3ýŽ< ­^x†?Žÿ¬…Ôí3C>«¸y–`²ŽyÀB„Þ´<)ðûáïÃÏé:ö·âßx‡M±P¥¾¿nbº•8›· òy†`ý§>Ål–žý¦ô[ð#ð²ÛFDc× qnÃôëø×ñëöý¢ü¯XÍðÇöðŸìõm#T¾´¹ƒÃ:3ýšïNEu†r±à‰Ã¸ Çi¦x(ØÆž1IÙ#ù1ñÂûŸü$Ò|á‹ÝközíýôÑ&­jÖ¿ÙwiˆÑ q!a"©;—hÇšÆOÙÛã•黚ÇGKÕ¹@ Z_[MíµóƒëŠý¬ø ÿcý»~'kh~9Ö¼%1Ô¬¾Ü"›Á:ræoÈw½TŽ7v5ÛxŸâž·ã&©ñ?áoÀÏÅ"Ÿ3û[áâ%ÉË‹{ô(ÙÇ;{Vo¾»mÑøsã¿„^ÓboXñƒªÃñ*ÃâešÚÛ%›Ï»@m$GpwG’ª·'ÌÎÜ1üF¾kø1©7Œ¼Ÿ¼?å\›¿nŽL¹-„DG§Ë¼\Wô=t¿².§ªÃqâ?‡~0øCrŽ ø‹á/ˆ%Õl`UBÎðî¬dà,Vk4‡Ëÿeoü5M;öÌÑ¢Ñþ7|?Ñ„ÞoŽü nöWºKL¬µ£¶n¬Ý˜ºÊ¬ˆr]™• DÚŽc&÷Æÿ-¿à~/мK3Éãï|K¸Ó¬[Y$I’  Í p #Am 2î`rFIõü‡Â?³ÇßxÃö´ý¢l5¦ð¦g£øgËÞ[__è–»ÞævNµ™”¥‚ gÉTÙùse‚ÿg CÁ>¼ÿ„ÃÃz·Š-üEk¨éìH³¶h·KÛ·ïci$åwœ¿£ðNÚ?Â^øC¤þÏ^6ñN™áH`Òu½rÚú÷hKË»‚÷OÊÇi!\ìNä9Î rWÒ-#¿½äÉ-îïF$WŸyN}Á-ŸQW¿eZÏÃ:ïŒ>9]¨¼²ðN•¨ø† d\5ãÍ+G hØ%r¡”°VØ=?H~ÏS]x‹ÂúçÄK”o¼I¯jWs-¿)ý ”…jnôäóÞ½ÚZŸ'QÚçåÄ›6Ô?m+Ö‚?³®â8óu/?À€àgƒíUþ"j6·¾-Öu,¬7×ÏÂu0ÙÄ÷ŽÇ­}#mðÖëÂ?®—[ò–æÛB6Þ\g1ÆÓÌÙ ï»æ=Ž+È©ÍÍ{µ8)-Ïϯ‚—š>ƒñÂæY÷Ñ^yLͰ´¡6ª)àîlž¯`ý³|qªø÷Á^I`6w·z¼Š±•;•VØÄNAçÈé^#û6xËãߊüGáMm ï¦i7K$Ár.6ÄùRAàÔ¿üA¬[Å¢èž+‘­üAáKŸ³Nñ'Ÿ±€š@qŒÈ¸'ž£ŠîQq±å;s3ļK>Ó¤›KݺbIbrF±øõí]Õ¯‚§ÿ†wo‰“\±kíZæÆ8[ð"(.é¶C´cƒ^uñ"ÿL»´‚}01…Ý„ÏÊ$'yb03ù×б¼Õ¾xRÎÝl÷3F©ŒbP]œÄ=zÔ8é¯s¢&†íb¹okÆ&Þ¶¸Áè$ˆ0ÉÇ9®Æv³êž·×åÊÜIŒ‡a….AÀïÀ¯Að£Î>è× …4ƲWQŸõÜË«|P²m/Á: ¯–]Ì6ó¶ßfóøVR·9Hßø[¡ø—âÇïøBt’yoVè$vñ!i¤aÆ~O‘sш5÷Çü6]/@øO{á?>MÆ©¦ÚÃÉ,RG#'Øc𯞿`M9µ¯ÚÊ ÁôNòîÀ¸h£\{Œ6®kÑÿà£÷¯wm£éàaç×.<´¬–èF1ôéï]°§esÏ«;Èüî°¾›MÐøÛáI<;âÿ xkÂw¶ÞÙô‹‘b’MÓ÷ðùŒ%À<`‚=E|/is§iº¶™5äþ]·ö‘®A`FÒ98ÚG;±ŽqQ%ï- ^ò¹ú«ûhøVãÃz…‡‡­­ä·Óí¤·³µÊíˆ$ ñ¯®=8⿼Ià/x纾•ákI/ÿ‰Úfk8‚3HèIÏÒ¾–ñwÄ­#TÕn|Cý­«NöÓÉqr5‹¹nž/(3rªFÔu5úIû/|cðgÂßÙ¯Â~×ü'ðóWÔ§†}NïU×´˜//ãŸU•îßɹ3G€†\CÃ2 PsŠÑF[ÅP…7¥Ir£ó‹ã×ÿê?^ºÖõ+ì:]šË§¢1 ûÙÎNsŒb«üRøqð—\økð×T–{«Û›«Ýræy¶1H›"ù•=~^N@>•ú5­x#öWø¡¬øãÃZ>­}q!’îâ;—¶i˜“õ2( År¿´w„?á.ø£|Gð½šA¦éºÙ¿z"(xÎ #å?x“Ÿ­Ô£ì<#‹¬£ÐøîÇB:aÈE‘Iê~`¾æ¿=¾ [±ñɉmAàwi·¿Zýko۽䩎Q;9áBŸò+ó¥´åÔ>(j7×j[Åo ‚ÍŸ~FkÊË_4Û=|Ù(ÓI>êZMÍÕ¤¶¶ÈRY”ÆŒAÛ½¸öïÂiÿo7ð”ìñØhú{§ÈÛxÓlÓ‚9IrOµr+¥[xŸÆžÐm`XÚmRÙˆEÆà²«zŒO°¯¤¿b[UñGíUñ'Æv©h¡Ôà¶8åÍÁ…~™­{³Ñ4¥fv±¯‚¼?ñ'ö€Ò¼%ãé=:å5&œÂÞS+Ij°9îGÿª¾_ø‘á? xöœÐþx8Êú…¢Õ‰÷Ê-®$e‹{`n?!ÁÇ#SöŸì«¤Ká_Ú7Y]V%Xü9ák‘°]=­Âç9—®x¯|M§jw_µ¦³­_$†=*ÂÊÐJãj³<*I\Œ$vw°“²4ƒæ•Ï&Ó,të…}>Ø+\ê÷K’3¶8ÎãצyÍsú½óëÞ8¸{hˆ #ºEÜçŽ=+è½kÂZOÃ7Õ|_­¿™©™§ ¡GÁ’CÛ' ôZùwÅ-m§x>ËRƒ|?hÔïO22'Б^|“:“Ôß²³ŽóBÔõ¹ÎmÔ¶HýéÚ1oZçöú¥I&Ͷâüð,>$ñÕ½‡ˆu(4u¾B÷WwåXÂd)éËäW¡ø'Ã~ðGÆk«-Ê»Òt%»½¸œ9tŸì‘“ÁÉÈ20ïØ×ŸÚ¦œÂKx2ooe]DpŽÝzž¹â»mÊÛGøyñé#Îtë[²›ËʼnԟöÔb»ÔNi½O2øa§YÛx+Ä/ñ"4éö[î<¶Ø]æbî¨ý˜ãëÅ}Éû`x/ øá®ŸlÖk¥xy¬ ¡}åÑe O;Šã“žçÖ¾Z—Ãì~Á¡Ç.Åñç‹aÓÒb~X․3½HV<ƒ€û{þ y5–à?èØi.|å·y2O˜ÒAh¬ÇØÉ¸Ž¼ŸJÐW?>gÒ.ü#¡øQy™çÕíofDÛ·ý"Y88;äåOq_Hü:øw~uKýRà0KkP$ûÄÈ3È8"¹ÚOEÒ|;ñ>?i#Xx'ú>„w.Ò'¶ÙsÁ ]=I>Õëÿ¼ñkö€ø·áß„¿ôëýcÅþ0VŠ +Q½ŸÊ¥rà$hÎÄžƒHÏœ.÷:©ì|Cð¢çLøñöMMmEµÜÓ<­ó©ûµ¤‘è} ð¶ÂÿâŽî®Yÿl^ºÅè,-[±ÙvͪÄÙ+âçüûâv¹ð›ã„¶>'L·¾h$]®±^™Øç-åö8ê}Ãû~Êþ?Eâ Oƒ6ÑÄ4 ]>ëT»Šg´³[¯—l*ÒK4Š yiÈL*I+Ðs²ŽÇeñûGˆx²ÎÎç[™ì•&†7تƒ"4úcñ?ím¶ËxÀX忆,Ž2‹HÝ}ÆkúYð÷ü›ã‡‡äñG‹|a¥Ú©|=ƃ6˜ ¡ ±7—nÄÐíSjüý­ÿe߃֦-Â_|â}OÔ$¹–Á®Q¬‹´ÄÁ°1Ÿ\ŒäÓ „”d›ØŒ^2œ¡Ëá/€^kòêâÖhe¾¸I­e¾É`åAÝŸzæïtKß||MþÔ!Öõí:ÔDèG˜²Í dínv°#Œ×з>h²E¤^I6‰q¶‡N–KtÇÞ´mƒyx©þø‡Ã>>ý°¼­èˆ'Ót‹éïŒÆ&ž +idi_æ_ 3Ó"½‹#Åg1§éV¿à¤ÉÂ4·5X< B‘™ž3Ï[ô÷¯~)k6Þ"øÃâŸ[9šCW¿‘±bQîiì{WÔŸ±ž«c¯~Ò:ׯ/³[[h:?ˆ¼Q&2J‰‚ûðg¾Š'Óì’Y ÌŒÊ3ƒøæ€gõ-ÿ®ð°Ð¾|:–X£­x†PÃ8 !Š=½?€/ë_4~Ñ·Mâ}'Â~ò–àøïÅÖBás¸-ç70'¶§¥}ïû0éW >XǪ°I<'àKk9¶6U..T»àà’Ê+á/ÁçíðÏK¿“Ë·ðöâ nà©PAjʤç§Ìzç4«Z‡ãÇ®dñůk“홯µ{Ù#`x(³2¦1Œ¨Ø5{¡‹Hšæe%eC0:V‚´Ín÷H·¼°³–áqû¿&'¸öƒô9šô{©ué­c‚M>éJ© ÆÜw2¾¼Pt6m|ð°Ô¾"X­È˦uÚ2~^¼{gŸ­~€É º®Ö||çæ?u†1­|Ëû*Ú½ÏÄ ëÙÆ,ôÙwï^GšêóÁëÖ¾ú¹Ð,e¹„¼ÎŠŒ? Ò¿¦ÚSÄúN«ãðº-úÜiÚ6—I(;‚´dÍ1QÆHP¹ þ]¿áñÏ5KÏhzeö /®&d[výà•ËÉÀùϦk‹ž=ΪP}O.³?–„eOQRÍn|–ÚyÅzÐø ñ˜)Ž/ j—Œ Vb~˜=úU«o€¿5ÐØhM"Jm!ŽìùÒucÜÙ'ØûöfðÝ΋ð.Êæð,miïÆá‘¶Vؤö„ÖA,28 †ÜYÁ訮ªSá_†Ÿ¼?ðïÆº¬70YZÀ¨Í·ÍxÇÍŽ<“Æk愾%±Ö•ãßÚ§Á:Ìf}2ÇRm^óËS³éqµÉ.;!‘QNz瘟· éV¿´v¯à}éþ ²Óü;h©ÂˆìmÑŽÑÆ™#à{f¾@û¦à¼ŒwžxäúzW xßÅw?¼{¯|EJÉâJóR!ÎX ™žDöT@8®E°® ’É¿O­+…Œù ŠÖ ®€ùã‰ä#Ô ÏZûCöÝy|{à‚÷ClÞðFŸ ±œŒ__¯ÚfÊö9ÛŸ\בþΟî~-~Оøsn‰"kíŒ3ï$")ÓtþYÄÕí^1º›ö—ÿ‚…¾£y–/øá-ÌlÛˆ°°œG°œÏrzc'ñ§=ߟÙËáõÏô x ð“Â^Ó4ùFwJcóÛŽ€î?­y¯ÇïYx~çÅž8¾|[hÖ7r»®[%íøãz×Õß ïmF—®øÒé”À× GÈ„³/¹Ú .kòöãø†äòMH#Äf@¼±æ¥†)]Ì„‚ÎX’G¿J–hŒð9Í;htžËû1ø[þ_zY˜œ²ÞJq„\/^Ÿ1öÅ~¡·†¤†p…ùŒ¤gq>Üšù3öð´7QøÅPÜo¶°‰›¬kŸ6FõÝ•B=+î;ËZé™í–ŒÇo?89õ?ýzù¬Î·ïy{Y”Pj—5·8{o ÚÄ¢á C6Ü€t'×ñªþ%Ò­¬ô¹5¨F&„fFbpT8éŸzìµ»?ÚGö°"X” ïáØž¼vÇ­cøÜ“¤Zév,¯6¤cE!KõÏAø×)7#Ò©&ÙùÛûNj¶1xgÁ¾å[FÔnùi+2©Ï© Ùü+æ `0ä¼ðkÕþ:kãÄÿµ‹›vV†ÎU´Œt ºô}Ù¯0»+»†Ç;¯85õ˜hrÓHø¬]Ni¶e4a†@;ORÝyíTe\1°n™ì~˜­;¨›¹ù½;b³ŒEæ@­¹¾èÇúÖ²9VÇÚ¿ðOo†©ñ;ö³ð&“«&tÛ É5›÷þíôÔ3#?ß=‰¯,ø¯ñ÷âwÅüK–gœëZ­Ýê;Œ3Fòa€!Té_b~Ŷ·>|yý¥’#ö'ÃøKLxÏ){¯º¦þ˜%O—Ï×8ùë©ZA¥ù±Ûå’Ö2ÎIX—®F9ïRh¶?£Ÿø#OÃ7IøSã_Œž$ ^!ÔâÓDÒcË:v“–v·ï™Ô“×híŠü!ø±ñËÇþ&ø‹â¦¿ªGe¬jwrÛÃäȉHÂ% Žazq_ÒF·igûÿÁ%F¹éúí·…Y ’FÖ"øá3;C¢i“k×Éü?iÕŸlD÷Ê¢IØ{Wâ—Çú÷ň¾øq¤ÄÒÝx†þ>5l®϶Ô,ÙíŠPVÀûßöð–³àÏØûà_ìãg…Õ|b÷ýägŒ\ßN±@\AáFGdÇ5ûÅð¿Âvâ+O éq%µ¯„´Ø4ëT‰p€Û¨\ß¡•û ð“OÞƒY¸PnïwJÒ7,7“ž{q×ë[@çÄJÊÇ!ñòhoåѼ"® ysLËÏ!ÙJ±ôÆ ©>þ•ü¥þ׿â7ÇÏë6ëþËY[1ûÆ+`#çÔoW#Ø×ôIñïâ¶Ÿ'Œ¼g©ºˆ-,å•$|¨6"úòÃåŒýkùd’sª_M|^i¤™Àä–徜ә4c¡ÿÔþ-ÒeûFÕ+# 'ý>´ËÆìò«º½@<’F@Îy—±[«›×?{¯œ×µ^{»‰äg|fb‘ì=úæ°QÔWІ4Ùà…`s»©¿5Öið¥ï†ïôÄûË•RH={×(³;³JØÁà(8u~ ”G¬œ7—81Žpkkh Ÿ¥wúñìu£øú"&ÿ„SQ²»gäŽù¾ÏrN?€ sÀ ÍyŽ£mö›BÁüäpNÖ9>žÇÖ»ØÊ%ñÿÂ/|¶=õŽ¥iƒ–Üè0ûDá}Åqz%Ì:¯…-|üîV7¨uûãð#ó¬ùÊ÷jú¿ÂØ.eѯàÁ0ÝýþUcá9ük•rwÛj0Íe:ñ†óªÏšÕð\Ï>· É’.¬Ýz¼G*úóM"÷û&{}FÕ2c™'Ï÷psúT·p<<%æ“=Î3ZO%» 9så1B~œU ©`KÅw-œä”ÛÛñë^‘ñûM´Ñ¾-êÓ B8uÈã r§*=pÊOã^U=íºÆbŽo,2Xg­KK¿¸Žùî  ß.0}«ôÇþ Õ{ KûSkß üW¦üMÑ.¼<ð6V9×RP$Ô¸#»ýkóÍÑcu-p í \3ש÷¯§¾ üI¾øgñÇáçÅí2u‰ío#W™”ºnôȇµ)+ú?©­ÿ?e ¿k´ÚŽ‚³X\d ´ÚEÃC&ý ò<²xÁ+ó_öZñƒü;øã¯Ùir¢I¬5Ø"Q¹—É‘L¬½É¸ýzý»Õ<5k¡þÒ¿¾IbÊïXMfÂ!ÆûM^Õ%‘¸ãs6Hïïšü(Õà_…ÿµ6ƒnè¦&–]*á‡Ë„v; Ç÷G¯¥Ù >¡ÿ‚¢øKIð¯íËã}BÅ6i>,‡LñE¶ݱj–ŠI‚VXæI¯‹>_ZZxžÎ;@‘¤Á¢L¶@p7(8éœ{šý ÿ‚ŒxYõÿÙ÷àoÇhÀi×L¼ð¥!vrï¡9–Ä‘ÈÏón cÎ@~HxsR·Óµ§RбÍ!óÏâ*¸`|ñçïڧR¼Óæh_éºåšg ^9‘¥Ú=в·N8¯Bÿ‚…è-ðËöûm_Hų«®¥mŽc¯|ù`x#Ì2…ì׋xít?^ ñM÷6zÄ3YJÝq•Úr0ÅŠàsšúkþ YáÍKÄ_~|n‘Yïµ/ÙÙê2˜ãÔ´¶¸@F~ìÍ»<ñWÍï"ÖÇêÁÍFÃQÖ¼;q|Ûâñ…î4˼œ —HœÂAõ%d$_Ïψä×þüX×<=§4–ÓxwW»¶‚@ÄKŠV0Tänˆ¦@îkö+öbñ$^)øM¢ø–ÎtA×!¹dÇËäkq˜)ôÃÕ¸¯Íÿø(Ï„Á_µÖ·vªb‡\³°ÕcãÀ§Ùß§1džNO5\¤WþÛÞ#¼øùûüý«&’ $±×µ?j/ŸµI(…¤ #Ê€²Ù㩯Íïø†o õø˜Ä4mWOÕ çîÄ“*ÍŸm„çÐW{ðûã̺Wì·ñ/öq˜³Úø«TÑ5Ý4 y7ÚdƒÎ$D¨2¹ç9â¼»DÒãÔ燮2ŸÚz}Ì>êJ’?Z¤Ú_ôŸì¿/ÒíFؾܷ6øÁSÜi'ð,×#c}¥hÞ*ðw‰uû}VÆ×Z†ÏP±¼ Ö×V“ȱ¼3ɉ÷ŒôéÏ®ßÅzìž1Ó¼ñn$ñ/‡•ga„šqàû‚Ä¥y/Š´‰uÿjÚŒžLò•0Ê ‡³gŽTàŽAÈ¥rý Mñ¯WøâWøwûFþË> ¸Ò]Ÿjèúp±šuRÄÌ“C7Ë´e¥‹ŽFzWÃ_ðNùô}Á~}œ >ÇÄgV†ÕäM7]ómDKŸ›(WúcÖ¾ûÖ¿à¿ÿ³?‡t]Âß¼9âÙé¶Býìí­îb’ARÈÌàb2=kóëö[øÑàO‹ÿðRŸˆ?¾[Oaáß‹þ½¹³¶¼A ±ÞiQÃ)Z’ªd‘%`3Ü‘Lq2¿àžü+ñ àÿˆ>|Z·Y¿áXþ(î¬n&²™‡9çbã§ó§í á_ÙGöìЗ»t¶!'‹„!œçÍb§¢â¼ÃÆ><ѾþÑñxñ¾jþÓ|Aá›6·ðî“dÅ"6%¥k«XdòË[º¹8Œ\×Çß´oíû2xëà埂þÝø‡Nñ‡wö«[ø#6ö·ß½óâ[¸|Ò’¤â.…•ÆAÁ`R@t??gŒñ'Xø%âÛùnµ=n•ù‚j‚;­Ô B¬¡Ú@Ü1÷A&¾Êðÿíëð2ãÂ×Ú‰¬Ga=¼qÔ A…a¿aÆA+¤ô=.VûNxgPð¦“ðïÄwÍo6îÑ›$ªÅ82¡cýâb úáêÿì.ü {{Ü:£éñÜjìht©ÞGØb}ò’§©L/'ŸŽo~$üB{{V¼ñmôÂ[Vüé<ÙãìÜÙ~§©$äsUØMxkPë÷¼·Ôy¤íó¶â¡NzyƾÙýu[(ÿe/ÚJÊòÍ éž.1Ûí_7û"vó÷&@#+Ö¿<ü1âŸj ­ìÔ-﮵[V9nÌrÀ’Φ5- Ȥ€0>ø¾ôñü_àçí¥ü ò~x®?øGVÒÿ³õiÕ¬åKì[ê1Êë#Ê!•šªP’5f!Aü­=æ¡«sɵ¢‹ç¦Ðq×±ü«èoÙ‹ÇZŸ‡>:x.ïPÔn£Ó­õÛ¸g‘SÊ{„ó¸À,ç±Ç9Ö5(®I$ŽÊZœ[{4h¿ïoYèHé1;˜¨Ul³"&3ݸÏzü€ø…t†÷JhmŒ2éwŽh˜œ¿¯ÙOŒß ïuMkŰiÚ“@ú]Ý¥­Œ}Ù‚F.D›Žz™‚9ö5ù…ûPxOPòx±‘Ö-UÖùˆ qÂN¸ÁÞ3Žœ×ÇR‹ŒÏµ•u(7}Ï4/|4ð¥„þñÖ”o­¯îd™ncd2¹ åN 0+ç?‰ºÖ—®øÓûcÃ’@ñKl¡VßýY!˜zzsï\ÏÇKQmñ[Th£‘å¡»@QŽCF ãŽpÊGÖ¼¨ê÷ZR­Ã\ImºÌ ÷Î0E{Ûså*ÆÒm^|#ð…·Š÷Ô—³ç¹åýa©ò´p·øsoâ=^Ñ,Å‚Ù1CŸ>Ù݆KD?ºp{W_oñ"ÏI‰ Òô§‚ å¤û$žRJq€å@àsžp)Ÿþ'~Ͼ.¸Ó[öRð›išZ¬³ê:Ž[T¿ºŸæ’gË0†3‘äÄ0 ª…å;¹|Vo%šÚå­Ñ3³Í™v°¸=ûœWÏb¢åîq¦u¿Ùi°®´æUO2Ò8Ÿ&aŒeäç¯ËÆ1E—u(ìíu•·†ïO‘p@™£˜+sÕ†:çâ¾*½ñW‡íõ‰þ×97(ŸºŠ5Ì6~~02ÞÞ•ç·:ωõ]nóÉa¨Ï `È¡ì[vW8•ÁõAû_#ôNOü.°¹“TÓ`¹[ò2âPÛ‰éž2+ˆÔ¼Bu]ašü¥¹¡,Jnª>†¾=o7†¶iš±ŸíH1t2¬Á‡PGAÜW|>#ø{jGªÃyncÌKeÞ8ÀûÒú²Cö—;­SÄ—ðݱ»½žêÞÞñÆ‹ÀàmÜp.kÐì|]ªÇpi1ˆì¤`ÞÆ3жyã§ó•×Ň0D‹§%Ô2FIe” €õ×¥mˆJš<71i·±$ñîG(²¦=NHéÇlRöDóh{þ¹ug¤µKX’Ñ!,òI`Ĥ¬x;÷uI—Áº¶™5¬:˜º³ 28% ™8;0Ö^ÍÈü+ļ5ñ«M𼌺Ô–¬ mçÊH¸; ‡Ç8<ý*;=gC³¼›PðýÔBÞåU-¾ÑµÃ¿uöá[ØŽGz~ÈŸhwöÏñ/ÃǪø;V¸Ö¬4¯‘ÞHÃOc‚d^—qÖ½žçâwÃFoã{L uP„I'ãuJŸF'¯¢ñEÕîŸ$¯ŽöwŽáRRždcœFÄô zWšÛ|@ÕÉE­›ÛÛ:ñ PdL‘‘'/>˜¤èß¡q«ä}ξ9øuq¬Ë“ ov•‘ù™ ’åºÓ¯¥gß\xSºCê¾!¶»™š)7”cŸ™º2:õÇ^ÕñÙÔ²5±¥ÙÚ[ÈJòñdÂàà Ÿ½ž†™¤üH¼Ð®%¸°¶·Wq: Ze2)ÇB¿+AœÖo ‹öˆûB=G\Ô,–IïÆ³8ˆ£ÄóFÄx\òϯãU–~ÕíícšI¦…v4Š^IB·1î1ßµ|É¢x§ÃDkLÑ®àñ´ •Iåœ Ž„ÞÐþ'ë÷ö·Ðd[Z©DKÞNUú¦äbU}2IÚ¡Ðì?h»J_§ŒcÐ¥Ñ.ä¶]6Gù‘†ïÍ)ç Uwðæ¢Ö3éÚƒ¥Í¼ŒKy÷ *ÊܤHP9Î:šð/â'ƒ¼;½•£››ÅvY#ýöÛqÿ<þo”ã’[ø®ÛSñ­[[Gröv>YóPªß0XõÒ§’QØi¦}åðÃöãý£¾ølü<ÿ„ïWŸÃ–¶ÓÚZÙ]]<ë w# ˆîw„`8MÛ}ºW›\GáÛ/ Á®Ç§â;m MƒÃö¶v£tÖ‘å×ä)ê{Šù¦æúÆçN’Kè7¨äNäÈ â¤ðŸˆ¿á¿:Î'K 9‰÷¤ªyMØAüJÚœæÝ›ÐÚ2GSàߊºg‹4ȵý&æféRe6…!\?tv`G^+Ôu+OÞE—ŽÚÖfK`ÊÉ“èyÍwZÏ‚| ®o|Aðu³ézžµbúÑ$ó - ò(^“9|c8ç>]ªKec–Ò•G4m÷xb3Ï=:â»KX¦{΋£ø:æý¬ü_iw$6ÖÞt Ž«æÈÀîÜŒt^•ô§ìóá߇þø‰+x~ÉÏÛ4›…Äíº7áÀÚ0N1ϯϯ |\‹ÄvB¨žtQÈ*¾™É=«êÏÙâ}ñsJÓåÞh´íF(`Ú[{7”ÁpbHRžÕ5£Ñß’ò¼e5=›?£=göFøâ¼³ð^›¦Ùx~ÃKµ»´ñ¬š”—ój¿ºó&)bް#ä4d>BÎ+œ±ý€¼®Cg­xCÆÖ i¨Ù$ö÷éÐ2í•w Íæôå!H!Æ+ç_ ücÐ<=ð·Nøs/‰¼Cà OFñ¥©_M¦xRãV‹[´¿ŸíÅ'—m$‘5º£°` ¨È=ý9ñcâüƒâ5µŸÆ}SÀ÷Úƒë7··Vغ¥¾£æF¬³ÛÀˆÑZ…Ý–dÛ1ÀäñY)ÉŸ¬ÔÃP¦í5Üüšÿ‚¦üðç ê>±ž[ë]/XÓÚ.cŽ ¥ŽäF¨á`ùvù²mVÆJdŸ~ãörÿ‚†Ãà‚žø1àËzæËÂz]¶e,°FÍ<6kåäœí$`ô`WûqÚü?×¾øÉü ¦^ÙxVÆKfÑbm6]8En&„Ī®ŠD᳟˜Ž¢¾+ð->xÆÎIDÚ­ËK10•‘"VmÊIç ôX¨£Ë¹ù÷ái¬B•;ZÝÔ?~Öš§c‚UÀä¶=ϵ)U”UÙòv{#ú‰ð—Æï ¶ø5;d…"Hž{õ¦tà³c9öÉËñ¿özñ6³…ãÔmíܬ©]ûØÀ#>€ó_>%øó¢ÛxFï^ÑuY.¤ŽØÜÍ#y¡Û‚0@eÚ~ðý+óÀ<_¤üKÓ~"èÿgCuyçyÌ­äÀ¬ÛÊŽp ÎqïN–"ïQ8MÖ¼÷ ¤[Moà=B>ï&âع}ß”©ôÁâ¿¿o?Ú'Zðíi¬h^6’þºn‘'ŸÄ3ZIp.%{=‹d›"DXF6‚Ks“¸žqÀùŽÏÇ !ŒCw«M¼ƒ¸ýŠe ŸÀœ{LÕ5ýR ?ß*[€I5=VV·ç?òÎ(ñ¹¾1ž*¬xòÅTjÇÕ1þÒ1Œý¯ÀÐÆ›‰>N§)9>›ã?NM{÷ìíûCþÅ1ø»[_ÚÏÁÿG{kGÒG‚5k{t’9¸ûHº’5’6Ë ¿? œ)Á¯Ë«Ïjú¼_¿ÙÞ@¢æÍÚDCÓ#F{ùV›x£Às…+­ÚÄRNRFÚG=¿J¸$`æúJühø‡á¿|xñgŠ~ßøÇÃ^Ôõ.4{OW”ÞÛY˜ã'û,ÞVöHÄ! +(ÎAý;ÿÁ³ö°j~ý¡õ½gWÔ$¾šûÂÚ0–yžîWXmï.–‹u”¯ÞÀ Wò'oâ­"x‹L*øÊ›ƒž1÷k÷Gþû`øà\?ü'u Ö¬5WÐõ‹vÓL.‚("žÞP|é#ÉSÆx5G,ãÌ~ÆÞþÇ?ðNø(·í1ñ[âÀÚCÅrxõ(¯üK¤é–:i‚ÆaX¦ÏµØ» BCõ㜟føMÿžðïìçãí/ãŽڽ]»eñ'‡,u6s°ìš+o²3¨“òÓöåðÍñ}bÛÁúý©™3C• 7—q¸úá«Ym *f}?ÄÐg?/•nßP³üêïYšŸ¿cÿ|Jø_ñö~ý³|Y§êþ Õu«}gÁÞ±}2ûKÕá$Ú´*ï:ÛÜJdÍn7Á(Ggf’:éüKÿoý‹þ)I§]|SøÁã^M*?.ÒàËkÛ¡`ÞX0Ù«mÜù³ŒvÍy£ûsøKŸQѵ«MºãV¹XaXs×t%Ä„g=kàoÛþ ðgìíãXþ~Ë~ƒâ-õ…«j¾©«M%­¬,x‚‰ Já~grÁáf'j—*Ü#ß*?Z~ ÿÁÿb >Ó~)xÂÈ}¦Öö;“qN³ÚH²ÄÊg´uᔘ¾ñ§üá¯Â‹Ïhÿ´=æ%ÜKn“x– :âÅŒŒ@mœ™–až}‡áß>3þÖ_tÝǼyàOë?ZKé<¡G<òøvˆJ#7Æé÷"oTb`¤?/Ê+äüdOÞ&¡âÏi7ò–.[›‡PHÁ` Àññ˜¸¤â‘úžAáõL](W©VÑ~Zþ%-Kà†¼+{smñã}ž·¡Znfo hé7*s’«pòˆ@Ámþ ×Q£þØð|=²ƒAýŸc •é#2yšù{ľø…áýëZø{¯ÜZjö±<ö“-ºÍµ×³Ç==zY~!N’ößS‘æ8ˆûHkz…ŸŽ~|I†ãXð¥å÷ˆ&¸žïV±šÔÁ©ÊXáÞ;yJ¼ˆ¬0|½Ù'Œ×Ž|nøÍðÓCø/ã/èBÇ[‚ÞÈÁw¥Ü£AøAäKm#ñËѸ ä°6èkìÛŸ Š'AøÝa¢ü@ÓZ=¹Ô1o}FlѨgÁþñõ&»e:vºf‹„1•^Ôøº÷öý¯4Ÿéßµ=Î3à¿Ããíj;½Mm4kù$X¥Fpâ9„À©ù°A> ðCã'ÇWÆ6¿õ_Ᶎ"¶›Nó#û1DQå ,a³ó Iã“_³:ÁüWgáëÖ¶Þ:ЗÚìz¶£ý¦óéo[Ã5Æãn±,Ž#HÈQ¼ñ_1ÚÁ:4Ï…þ0‡[ðíö©á¸ÔÄeÖ#:™8É  W;@9®HÕ]L±|ާk'ó> ø÷ŸI{¢$ßP‹;†Y‡ë_UÁ94_„ð|W½ñßÄ¿‚¾"øÓ7†%Óµ*×@Õ`²K)à•Ÿ7–×ÇÔr²©Aµö´gל/Úwökø¿àïÞxîþ+=wÃ/,W-¬h—)yj‚29‘Aù²¤Ô×Å–:F—}q¥êZŒ·IHÛäӮ䵔å3ó!*ÛCtïÅn¤™ó8œ-J–¢³?¢í?þañ3öÇøiªþÙ³4–¿ ë‘ǧéWË !~ÅŸµ¯…ô›moÄšíþƒ¬ieëA7‡.®aet£2½³FØ@v0$ät¯Ð-~+h¾Ò¾#üA•üI¯YiëZ¦««ÜkDg Ê‘›™$1!gj¹ç澊Ö?à¤ÿüoðÚ/\xZ%¾¼†;;»ÉL‹,–ä~ðŒo  ñÍ~|i‡Æz}’øvø}‡KHˆ —Äq·Èœç°Ç•OšµÁ(\Ô»ø³ñYÕçŽþícÃox‚*¨=Ž è=k×~ ü9½ý­üg=‡Ä_ɦø[À±‹ŸkwlV +7š5—5Ì€|ˆ§vnñf}GÁó3_‰´¡vöÑÊ×Ö²FËÓŽ®ÄxmÛŽÐOJý(ýž~#þÖ߳λ¯~Æ6ßt_ŽÞøQ«Iiª /O¸™}uÖñ¯&’:V‘#sA áT‚F=8i_Þèwá0мÔÜùŸâ§Ç㾩¡XxBÀøoá¿‚ìüáØ†|ˆTm7³Äƒkyh•Èfzå´_Œ×ž[‹ÏèsZc²+ëÄb°G´ƒ±™‰ÏP1ßµ}+ûiþÚ²ÿ~#k¾ýš~ XiI¢Æ–ÚúÔsÙj–Wñ“ço·ÌEFÂyR”bAÜ c?[|eñýäËokklتFØ{dþB½j5y–ÆYæUOR0…Nk«úë>$ñˆõs¯ø¶k½Røç÷“‡"0{"ãjþö§üwá·Àÿ¶Fðoö™?ñ(Ô4»ûI¹ºûŸˆõëAÙh—W,Ÿ¹‚÷tŒØd.!1ÊåOÎZwˆþ:jË=ަÅŒ:uáŸÐ×?âMâôñÍiâ ÚÞÛ«E#²d1&Ÿ£Ãjòôjy£÷“í îFÕZøWûCéÑn5‡Þ3Ó."·¶ËáýD"P®cÉÛŒáA8í_ŽºïíCûYÚÙ¯öGмl|Ýök B¨î²^>•ßxcöÚý©ô1 åÇŽþ$A8‡•ãmVE¶cšáÔš˜ÊÌŠ”yÖçôåì)ãØã„k¿<'¦*Á$³¾,ªË÷X3 V䟦~~~Ö_ t€Zg‡´oZxæKȤšíô­6hb³~UU‰ÜXȧ>Ø÷¯Í4ÿ‚”~ÕöAZËâÄ‹)d˜¯-î‰õɹ†Rk²²ÿ‚Í~Ú¾¶6püyø‹kä‰l4ɺs×û5ÿSTêS¸Êç‡üøáÝã.ž³^C™mÙfW‰±:m-†*:àWèÔ~6ðoÙ>Í­g…EÄrEÉ!C`×’¯üö£×“vµÂ-dgïIà‹EÜí…ÌdŸÂ¹ˆÿðQ_Z‡ï´«Ï„_xšHÜÿ³©ª`uÁâ¢qº*0²:=?Gýžà¡÷Z׈?dË?‡ÿ´5š\j:‡…ìsoá­¢4·2iòÊE†ª±£\ý…ȆpX£;+JŸ˜÷øâ¤VºŠKgá›ËØÖâ9ãG}6vl7˜ñœl/œ¾!¾÷Í]/ìßaãO…µN‹ûfø£Áú χõh5Í?ÂzTóhö qns™-Œï q·ï `Èd#cŸ-ˆ®ãõ×Å/Š^,ñÿö.™áè¼M¬Ýk?Øú\ÒIge%ãy’ÇLŒ!wù€åÈ(e* ­QÙJ£ŽÌúÃödø·âµµ¸ø-ñ’å'Ô4Äh÷ÒJ¯çZãQ˜ŸÞíÆcfùŒd–¼¯ö³³¸Ÿã…ul<É´­(Üå@Ìm |ŽØ8'¯Q_0GáKÉ•tø¢'ÊĨ(›zzÐ úÛà'ìÅûTþÒ&«ð×GmgJ¶ º½ü_cÒíÑ“÷÷l‹Ûý'*OAŽ<ÿìé*œÑ[žµLÑʳ™òÆ“c«Á:Ãáû´Ú–°Ý¤JBçOqs– ŒaIÛÉç±é_¯Þ)ø_ðÿá#øWðYmWO·(ÐY›ò5ÍÁ.Àç,îă’yÍ{·Â¯ø'GÃOµËøç\\]B^Xh,ÖšcˆÈeI®äy\nÌ!E}Ÿ¢|6ø[áI]tÍ [ æÞ ¥ŸÖI˜´Ó18ù¹ô¯¤Àpö&¶–²>gšÑ§ÖçàÏ‚¿à–ñOÄY|Qâ=c[ð׊5;«ËÖµòí¯6¹g弨™‘Nó#dù¯²¼ ÿyðþ‹wi}âˆúþ³äÆ‹äL‘Cl6૳paëõ¯¿¾(~ÑŸ ÿgÈ£Ó|c¨Çc¨j çA¢i‹Nçœnòc•2Gï%*‹œ“Šø?ÇŸ·Ç/£ÃðöÎXHp’JRûTqêäæ˜Fß3µíÔËpxXZ´®Ï68ìM])«/3Öþ ð"æËn¹¨\¤bw\ÊåŽX`¸=Ç~õ[âWì½û!è-Í¿Å:“c:y%íõ´chà.ŒŽ:×åçÅOkþ,I5믈挻êrL¼qáû…P+“ðׇ~øz n¼?áÍ2ÆG¾h­¢Rqß g?yS«„û4κoozqõ·Ãï€ðL¯‚"ÿ„«àWí àM;XáZ}M¾ØŒ€îÚ@•~V'æÚà ØU/‹²¿ìŸûHxVÖ4oÚá$z–·(•mì. ¨‰‘@&7,êÇ<í99ãí©4…‡•¯áX÷N¼ÿG¾´·’2¤Ð#gëk T§-à-;ß™Üä¼iÿBøë¯iS„úö—ãxÑÚ)tÝRÚøoÆÕFREq»C_8x‡àWÅô?xóE¾Ò|3ƒQM[DÒ Ò/¢"T½ÒÓn•óŸ–kc€ƒƒÃ ž¹¯¢´?Ú‡ö¬ð6‹Š¢øƒáè”gEñݲê±íC÷#½/#ÜúÇyˆÀ8=F},tB½Hõ?><~ÿ³Þ­ac'ìÑm­iTZs5ö•®Î·SÙÜ®ZVŠxÝÕá›  ÎåÁ$ €<“âV•«ØéšEö«*Í ôJÖpB<É–qÈe#‚Ozý9ñN³û|j[‹_ˆÚn£ð\Ô„—Ö캷…ç²3s±dƒ'n|è¢9;U›­x_‹ÿb/Žßµ?ŒÞ¸Ó¼wá²ï²Õü7+]ÆéŽf2 ŽÊ^¼úø §Ï NÈbbô–‡ÿÁ0õ[ _âç¼Wcû/Ø-ZÇ/Fåð¸îNß~êÛƒÅúˆ> i~Ó.ä’óGKË«ä(ã`¿dx÷bÁONWóÃþüIø±ðÃ[¹ñ/ÁíbãÂZž¡‚òæÆ8GXغ«`|ŒIàdk„øÇãŠ?5×¾0ëSxŽòœ­Õוö‚ËÏ-Q™µÐ—CѤ™‚N!Qs,¬ê Šgi*I8ì •µ9ª9_Cõ÷ÄŸ·üÓöc’ãß³wÃ/ ÜKd†Ö='ÁL `’öÍbéV9]NL‚ÏœðN3üÓøSÇZÇÀ?ÛÃÀþ4ð½¥ÍÞ‹â&Ôlí/!ûE”n#—j2e<ÄA)î +“Þ¿\´Oø&'íÛ¤è/â-txLÓ´¨¼ë„ƒVžá„6ë¹ÌIöHÁf…÷¯ËÙ“àOÄÛ›öûµøoðš÷KÒo Zž¬—zÔ¥­•œ`3È fD  $Œsd¯¡µí©sþ ñ×âoíañÃQø—ñvMj÷6zg†¢]LþζK[9e‘KA$×;åwÞÌå\@?¦«ÿ‹ý³//Tëš/ÂMbX×ɹð\¨†4P .ÍGƒ×«à?ÙWö ý¤nO‹~6Ò¼¯x~Ê?\‹{½[RYÞÒyey=´6ĹY„~b30Äl½I`?F¬à„ÿµ„ ÿ¼8M#Rþ³Žµ]' «v6aÿ‚«ÛëwŠ¿¿fï†:Ì.6És£Ý\i×`¥KY£Ï i÷ôoÁþ ™ûq'ö€¼)¨xÆ–!ç“Ã3êiú„ª¸ó$µ{iÚÖöܤˆ‹ì$Un+çÈÿà…ÿ´¤P´|aðœ98Ïö søµàï]‡ÿBò.m?ás|v‘.¡¸k›Xü#¦Aas¨À–)ï^âXgRN%iñÏ5¿‘„]Óãïü#ÅÚ‰ªEû?x”êÖ—q8ŠË[ Ê>á–5;pG ÑœçžrkùÑøðÃâ¿Áßiü;}á«ûˬF/£) £€ S.è¥ ;{àñ_Ün³löú€Ô.5°ÞÖViçSsrÑ S$åp¦F#-µ@$ð+˜¼“Àß|9sà‰vZG‰t;Á¶{MJ$º…Õ†0Tä|ÙÆHç¥sG 7(£ªxº•Sw±üVüQª|WÒl•û9»º-žC gذÇ9⾑ÿ‚`YÁâO\™Œ±_<&F<HÙÜøsžõû?ñ7þ %û.Ýk7>1øâßøWúݽŵ½„’C¥ÛË:í2G’¥ÄAFD³yj>êŠùëá7ì­~Ã^XuŸi>,kû·X¥ÒíÞ…aˆ…WY$s×'<)þzJÖÔÂ×>ðoÅ‹¯…¼QãM:-RM~=kž[¾ÄH¼C øÕØ1—\†Õ}ÈþÎ#¶ã<ýõ%¿Æ½VßÄw^ñ¶³r!IÚâ™"uîŽmёێ¼t&¼ ⇉ü7áiÛÀZÉ~º…ìÚ¦©py´ÝIæmëå–'&¸*T[#ªìŠ_ïu3©ÿhßHei®ž’v…G Û½yŸà ð¶-[o–±Ü›/_óšë~"ëÖú‘ÓÖ%$5åÀy, þ_Z«ã›E½øE¦hèoî¡G@|É'=°:Ö2ønT_¿cÚþ%øv- Áð“m4‹yþA{‚K{W†˜?µ¤} Bnú‘&Qß?¥}ñãV°¾´O¤±2[ZAjz¨Q´~'Ú¼­tèôßM¨€Î<€ðNòÁPéXÒÜêªy†"ûRÁò+·žœ}+Ð&¸†Ã໩##M©øÃKÒW–ÂÛûD’2 PÇô®DI¬æ’å÷ ®=yÏç[^9œhÿ<ÓF@¿Ô#ø~×Å? >êWØÝ®ûa©Å +fÎÀ=Ì›çŽ:Dº †çÄßm"ñßÇ/7,šÎ²ÌŒr¹"cƒü!SQ^‡àmgW‹âV±¯øĺ„om`1Ûk5Ü–°#ŽÏWRê0pFESýœ4k}_â}ÏüOf·ÖÐ5í~ío™e]2Q»g í$Â@ `N8®ãöC×|;àüMøÓñà÷…¾0hÞðîœ/­ýŸ ñg†<7®j-ï‡$žK¦‡ìÓ"ïìè?~­÷]™\ÆB0.Õüºý§Å> ¸Š´÷°µf!í¯íMºdòr² ÉúsŠþÙ¾0øûöÑÒ?kïx:mzÀj:†¡ö‹-*Â8ïì¬a•¾Ñ$)$ŒdóIðX‘ÀWÍßÿhY]G¡_kv>)¿‚I“Vº’ƳÝÀ@¥ Nrý}+¶*ÊÇH(ÉÅ;ŸÌ%Þ™àÍk±IáíI¨l ;[8˜Â’v‘ÈxÅ}û3i— ðçŽ>'BöÑÛè^Öųôc}:¤p²©ÁËgžs_|@¶ýž~!˜âñçô‰µG£’æÞÙmœ[…%H‘0êwzW‹Að_ᜠÔ<%£]êîc‹ùÿÑmŽ'nùØàý8ó ZÃaêWjü©³Jpr|§ëG¢hŸüUo$âÔk7@«2²o ".7ÁÚNGùŸñW†ü[ñ´WÆ5ð‡Ã˜4¨V#Á½×.k†þ÷Êœwæ¿®þÐÿ¼ñ'@ð~£ce=–‡ex—[e–I4ëfÜ« <²Ñò=køõñn¥¥k³ï¼ks"OwñSâD#M‰ ‡L²f¿XÀâßÌ'%@Ûƒ–¾K„ø›šJ¤qy9vó/„TZ>²ñ_Çm'N[m/P¾³°µF‰gq¢ÇÎÑdýwô/x‡ÅÞ-ð-Íέyq{{ì…ð¾q‰‡`gZöû]2ÞÉD÷r4æL3+‚@SÀã¡Ç¥pwøoÀךš‰ƒ^²|ˆ2»º09ɵ÷ ëSÔÿc¿^.âÝzðH’É$@H6–HÔÈN=dÀ÷ö¥¤p–Á# #“ó +Ì?g"cðÕuÉĆ9'‘å!rÍ !Ï9×¹ˆì "×!YðÉUÏ|zùuå]³ôL¶ 8t—>?ÞÅ è·z•Ïa³¹¹!OÝ!0âOç_’ÞÓdƒMµŠá8Ž%Vÿ9#=«õöþWðfµb%Xa±y\•UH …Ž0£h$jÉýŽ¿à—_¶ïí×v—_³ç‚%—Asóø›Zs¥è1Œ¸\J¦I׃o¹Èäv÷rªmSw>_=šUµ?;ííc¸2Õ #®0é_\~Å7–ý¡¦ñ„ÑM¨ÞxSÃz極ªI<†é­Ö“dj͹ÖYäsž+úÎý?àÜ_Ø—à}œ>$ý´¼Wwño^Œm7O/¤è12œ…)‹›ÀÌ“csò€H¯Öÿ‡ú?ìÑû;ømü5û8øFðVÞJ³Š-Òw/"¨%³ÎâN=kÔå>yâa{-Oàsö1ý…mmCâ_‚ÇO½xÌ~¥‡¶*Þ™ÿ³ÿ‚Ÿé’ ›Æ^Ô`²]xxAí‘xå\²§2½¯.ÇæÏÅi?³ŒäðÅO°øsYòRV°½¹…dò¦ÈVÁnAÁÜb¯ÜxûC¿²‰îåÀÀdL¡ÏaŽœñ_¬zOü'âÆ«'Øi/‚^øbù3ÜÛyv— {*[]C2±éó}¡~‚¯Z|cÿƒ~ÿi8ÿ±~1ü!—àæ±tïq4ú|wz jîFéMöžÂÑØž~r[Êã5É<4úǺŸ3x£O,ò¢ƒÀÜÀcü?J­=ÅÕÌ ^aàrά¼‘êxìköƒâWü«Âž:ÐÄŸØãl:å•éóáÓ<]å]Úê#‚þÅctÈ8ß4R œšüwøóû:~ØŸ²5ÄvŸ´gÃÍ[D±&E‹V³Œjš<±ÇŒ°½µó%9ùEÇ’ärOS¹j´YäRɤjQîÖ71 ÆIÔ3dvìxšñø7ðëNÖ?·´XÓO»Fóá‰rªÓóÇ'·­wz·‰4ívÆ-wHºV´¹Ë,±:•ÛÇά8+ï^h÷R]ZM›¦e+æ+áÀìx?§zˆÂkCHUqw øÕªjWÃ_Þ8ÛIûíR6ÉxÊœ^õâßlXY˜c+s»Ÿž5{Ë^Xê¡•Ôªp„¯§®A…e¼¤BžaÇÌAõ'AÛÖ½Sâ½¢‹Ñ},qù®áÚEçå0iz®£4Fœ=õè[¢­’yÎÈÔ{–`ÖºÞç$dϺ~3Ãuà¿Ø›àgÙaŠÚ}tëž0»ŒrçÏœ[Ú;²ÐŽ;Tÿ²Toàÿƒ>>Fí ΉáxôK ÆË­nSý½ª˜Ï6kÿµ´aeð×ÂLjr¤¬s˜Äp:¹ÞûGSÚ«Á9´o¾?'‹õhäžO øzûSd°]Bp!Œ»wf/)úƒU|'ü+ßø'gÄŸ(𠝉$Ó|)jàýûm/mäøøw ‰ëœzWÓ_ðKÿÏ€|WãK…jÖº4YS´Áb¦i ç¯Ï&ÓÛ#ªÑ•Gh³ö3ÄbüxlË!ž%Œ7|’©Ü¿Rs_ÏÏüÄ–—Ÿƒ<k)x­¬®/'’Êèª}x\û×ïípòhZW…­ÎÔyŽ€ávD˜Q_ÌÏíâþÑšÖžª$G†ßJSŸ¾Ð®ö?÷Ô„~¤N|>×>S2 väAçþ5 ò¨~çý†5ò¥BzñQ5•íÁ¶Êï<˜Ž0ç;SRE9Z×:b®ìÙÙ @ƒÂ¿³¾w|£íZÄÓjL1—ħl@tØ “ï^ëgkª^ößgD/‘ÁÜ?8í^£aà= ÂÞÓ¼§Æ˜Ó-­í@¸â½FïÒªß ím%† c c8 ;õëá1•\ªÊ^gé8**ÇÈóùô?\ÜDfšŒ°$) ªÀ‚}«Ïügm¤j’^Â<¸,m¦º¡à:ŸO ë_EØZÝý¥?´nQ HÀ~5ñ¿í{âUÒ>x¦ò6Ýj·6šU¶Þ¦DoßcþŸÊ¶Ë“•Da˜ÍFŒ™øèn仵{&–åüÂ3Ë$“úÔrÉ’;“Ÿº(c”¤]GóþzÓ0¶ê`äpTŒçÿÕ_gd~wQÝ‘\aV<œòAëŸnÔí>É®oC/9üiÓ´›·g$ñÈéõ­" Ë•’.7–æfX-Äk—iä!Q@ï–"‰õâ9‹á/üá—ƒm ÛjÀÉ®×þð¥¼kûhÿÂ},^e¯´+ÝX¹j—ýŒwÈAìõ¨{\ýÿ‚íxË@ð¯„|7ðÂ×|O­AÐ<«=ßÈ…ƒ÷YÙ[¸õÅ3·0åR¦W g<ñü±_«¿ðZ¯Š2øçöÏÔ|i+=‡ô»€.gAurGs’ñƒŸîãÖ¿"®IÎsÆîßJtÕ™­6•¨\Ü/±»ªŽIë_ЇüÓàÞ¸Ö~ x²+Pñ=¦› „0ež )wI!—µ›?wÑsšþtîcòíçXeùž&S2Ä §JþÇþ júwìóû'ø¿ö“Óí™ôæð³ø›N‘Àeû*Al<• £€rMEMt+¡üÚ~ßSã7í‰ñÇV3Ø>±%…¦Xà[iª¶‰Žx cgq–'½{OüãÃZÇí+sñgÄ%¡Ò~xsUñ]Äç>J5²¢.}ÃÉížÕùá¨}¢âeþÐf¸¹—t·.z¼òÒ7NìI¯ÓŸ–q|#ÿ‚Zü`ø¹06º—Äi¾ ±õþχðºÁäPB ­²HW;Ÿø'~Ÿ©x¾_‰Ÿ5”ëÄš‚é°ç‡Yî¥7×?îJ¨~•û¤×ðøOÂ7Z¬YT²µßŒnO~?>µùÝû|>ŸÁÿ³ï€ôk˜Œ7ñ\kwhÃæÍü…ã:‚# sÍ}ñkV~M-X§ÌÝPŽ?µ‚9+;ÊÇä¿ííâX´_„ñxi&ÅïˆïÒ6VÈvŽ擎¸à)íÍ~jsJ“ª€BŒç×?…~¦þØú´~/øœ¨Ù¢é¯ `À;¸bqÐf¿9’ÇÂ×72}¶éË!ÆÐ=zTTÞÇU-ÿÕþ!žÜÄŽ„ËÕ0~\üúwé&žG ÄÎÕR£¯ÔRÜ2M)RÌ\÷lc?AÚ¡ýŘd%Y0IÙ¬ÌË[9‘¬®TFÍß9Æ9´!‘¡ÉËxö²…åwßÓ5‘i ÀÅÆN21’k E´kŒE½"p„Ä6;V…Äû3öLñTžý£¡˜ Ö¡‚GHðS̉‹Ÿ]¹ð¯LñG†ãðwÅÏøB8¶A§%í˜(kkð·&sÀwtî2§é_èz•χ¼E xæÈÞÂö4’CÈÙ3y$°Ï Ÿjý(ý¢­£ƒâ¯ƒ¼o ªEâ ÜÛ»Œs.›t©€=—­e5Øgˆøfóû'Æ–×rªËää•—Zää};ÕÿŠ0‰uk ýÞ±l“d÷dNi$þÐ6¿lм)ãE;VXî´Öã’Ðmxó×.à+ç9Z×Ïw@]ÆÓÀÀ"¾·ñõ¡×~kª#6•sg¨¨B 6ÊÃþk㤹0ÝËntÉn1øæ¨ Ƹ§´I2§OçÚ½ÏÚþ\Án}6ê+“œ®íÎÔf¼ÈÇp÷c¹#8ù}Aúv5êŸ ¶³&§áW`Ô-YFá†Go¦i5p?¢5ñ-§Œ<[ð‡ã\s‡¶ñŸ†n´ †–C{`DÑ.ù[ñÏcÛ5øûÿð­ï„>&Þø’ÙX&Ÿ{kªFÊ»r²pÃ_Ê¾ÑøâÛÿÁ>4_–“PøUñÆôÃXìµtÙY±–Iž8#Và¦?âÔt}+Ä6Ìc][F¾·f27•±Ó#ß‚vú¨5V`?âÖß?à˜þ1´»f_x›Cñm‹D­æG´þEá“å2ìN0ªHè3_€¶±È/¦ŠDÚNààcƒž:÷ó¯Þÿø&°ÿ…Ùð#]ø$ˆ'ñÏ€|AáÒ'må”oöB{dŸ>¿,[¨ ¿vË:‰I^Fçþ¼{U(­@ú—â äúÿÀ]âAVóÓÅ;œoYùqÓ$¥~¤|gÓ4_Œ¿ðOdÕìPÉÿÇ‹ØòòOؼI ‰Bcªœ3ç€F{f¿0þÚËâÏ„^*ðƒ†‘ÖÞB™àWr{}ú5ûÜÝü[ý•5ƒJ˜ÇòþãU‹Êlޏ&Qøf¸Ÿþy|’ÊÞ_ö¢CdgrÝ©Æ~­€ xÇÃý[RÓ|'âÓ[mõª[kv¸yséÎ{öAZh?|>9èPü*ÿ‚†èþ#"7´ñ>—äO “uäȨ’Þp¼`€¤óž?®<m¢üDøïû?hЈì¬n/fÒ¡˜…·•…¸\’p±˜½ÀPy¯Úÿø({Z^Áð§ö’ÒÁš9µ{I„¨F~ǯ[,Gê>ШHéùWå§Äè?áÿ‚Ž]jwmºÓÅV‘\nË»>2ݱõö¤˜ÏsŸÅ×øOö|øÄ²>מ}äd(1ê0Gs’}VXŽq…"®Á@ü:–:/€¼pœ$º¦ˆp8Þì—QÏj9¯ýŸî¤Ôrý£;3Î:ãó©§û:LÆÏ>Y<ëUêDç5 íÒF‘\‡8µgg~×fÐH ¤¨¿x ‚{}+&š/ž5ázšßÓáI.¥” `4yìr8¤öúð燇Š?b éšAPuOËÁû³Ú<¬sï¹2kò+ö‡ðΩxSô&5Ôb[[˜“´K¤˜ê¼1õÇZý‰ýŠoN«û*xTM•m/\Ô´ó¸d,Wrs؉á_ é_´ü ñ'†õ˜ìæÐQfÂü½úVkv§ƒ~3ø÷áïìÓá?o°Ó¤<°†}¦ÒB!uŠ©-Ëäs“_~Ô~ñß„>9ø‚Ûâ6šú>³¨Oý§ug!Rð¾¡þ‘†ØJŒïÈœ~â|Ó¾øCö¶ð¿Ä Wö·š–‰ª]èI®Ý%¿Û®¾ÒüÆìJ/Ì ´ãvFkòËö·ñ^“ûLü]ôËí3Gé–V³‹ÝR¤’{T1³~c8«L‚áwVù ÚµžyÃü„0*«÷wzZ÷/‡¿³ž±ñ â.‹ðç@×´‹‹Íjs fÚàÜíØ#3*¨ CÔòp;×uñ·öqðÏÂøs⃾!xsÆöž%GvƒJyêÐF« óânŒ°Ê0` ¿ÊFi¶ÔÞ Òà¹ðŠÃÜ>@Ï+מǚû3þ 1ãôøEÿEøU®^N-bêûA“`j¶Ì«Ï¹Œ!Ç?0¯‹~ø“Â’|:‹Y¿Õ!µ’'xy’)-Âä Œbx÷¯Iønuo‡ÿ<ñWÂÖ³_®âMQ2[Á+)ŠÞö±v©@NI‘šI >ý»>\üý°~$|,ž×ìcCñ.©m c§ÙÚáæ€þ0È•ó.ƒ1¶Õ­î·ˆÄrÞ݃Ã`y¯èþ qû |}ñ¿üŸâ_޾ø|jš&­&—}oqos “MµI ä6ôf<Ãs?7þÿÁ=þ?]|Pðþ›ñ;—VžŸRµMFe+"­¨4»Š>å®qüU«nm-Ñýz|ðÿü,Ù/Â:—Š5§‚úÓ~™¨¼‘ùòÂñªµ´ S•€¨Þ~òàŠÇñ/ì©à_²x_ÄúÕÆ­Ô²_Zmcn©:¨Y#8™]yGzòï€þ4Óüâ\Mm¤xÊÖ ã´’ÞBš~§§ÊVÛq©Ýl|¶p9Ú})£øÿúŒ–w uæk˜¥S6áµ¹Á v?á_/V MžÝp½ÿ‚~ø+RŒ¤:Œ,ÄpÓ©vQØŠÏÒ¿àŸ~Y[CÕô«¤¼˜\Ûo+ÛûzãšûûP½žVòÎÅboݲeƒ'\¦y®ŠÞk¶kû  .sòçùsøPªµ°ró=OÌ‹¿ø'§ÛTµÏ‡ü%z÷Kت6ïBpqþõyÞ¯ÿ·º¿•.tÍ3IÐ¥ÚAq£Yž3÷£`ÊÓ¡ €xȯÕŸ‰<.ï£ÛÅ2 ùÑÁúsý+¹ðÏ´½WlºšÃh«Ì_#±ËuÇ®=é¬KLUŒ·?|[ûë±j~&ø‰ª]ê'Uøkc÷0Ù Wˆ­,R0e-ŽIù1§# _qÿÁ8~|<ÿ‚püløŸñgÆšÕ°ñ>£ðWUÔ<±w šâñäI T7M òÕ‘A.6ã85úgwàïjm©Í<‘2ø“O]?R}®«smeTlä 1Ô×à—üóÀú­‡íðóZ;ÓFñ'…ÓFÒç…7,ší#Eóð’ì•Z3÷™PžÕëa3“ýÛzv/NòÜþ|’OhúU†•©‰t]SDÓìÓì·ù/#$j£r¶7.U¾o\׌ßê×ú…ýÕÝíÔ­*K¹Öžå‰Âü"½Çö«ÔüPu«3ÅWS_ÝZۤРª¬X8 í Œà“ŠøïOׯõ ‰Å·—æ¨ÜS”¢Œ¹î?ʺ9›gV>…ðËÚÞÚÜêšçœ·SÌ^)c öbˆ0Á‹r§'·øÒL—º†—r"¸’ãJp­å ã¸ò˜aÉ?\W“è¾3ñEŒ‰¤‰EÞŸ*“qº‡+ÈÏrkÒtÍBytxµo_Æ‘Úec,ÃÌFú0ëÓ’>•œéµ©$ºbÝÚh#P×µ)ìôeÌQîŒÈë´‹ž¸dš’=Inä{h-Ùö°™ç;݈Âü‡žGðÖJKdR;Ï_›$²¸ùX“óœtÜMIaeâÏz¾vµ€êåü¹ICA A 0qŸ­bÕ÷±ðþ£¦ÜEë³0mÐ\Z¶Ç#$1õªzO‹/¬vÚlÓ3Á…“o’ œeHÇ=‡zá­—I½…¼[qze”LÑ]K((GQ3Ô‘LÕ‰ou4³·½}¢F&¶~ñvG'ŽOZ\¨ãQñ5¥ä&e [4…|‰ˆ)*ã%)ÜÊÓϦšVÐîžÜ:‡–Ú!‹w^ì€õqè1šÅŽÔG%ºëŒm×fv‚c'žýGj»5íå£G{–’ÆàÊ'DH뎣#¨;Öé†;é÷Z$Šð©iB+à!#’Iþ#ýÑ]%¼:eÜo Ä/²ÈI¼†]ŒKp0å°yÁ¯)³Óµ_Géö~œ±¹ß-Ô¢òö*¤n8Ö¡Ñ™¹r£ï…|Ç¡©h qõ}7fܳùhÒOä[yº?] …ùÖåǬæûåÌ×Vñ/î–­|©eÆ>xؽ뛼ñnµ>‘¾›©Å;¤*÷JË’G?–B侤çµr6÷ës¤‹MS|r4Þ`‘¤iM¾ÞŠ·¨êÓqV4=6_xGWX¡·¿{S,Œ«çÀÁU×$1lmýj椃p÷wK¶úÞ-&› ˆË$ç$ãÔs^o©jÑ5¥Þ‹p¶÷RLáí.˜‚ÄÊà¹ÇŠÍ…Ë&åŸì¶®O˜«´W F}9©ä]Ùê×z÷‡&žMnÉ¥|¤i2$™s,ƒ$:ñÇx›VÒÞ+m"ã0•Âù¡€9åˆ?­y柪E#é:­µšÃ*™âdmÒè@Ç™³<ÖÞDÓi  t#3¬mµ›©^{þ. è:gÄŸÚ[ÞX^[¤°o $œÆ› ë‘í[zgŠt½No2Á!pŒ¤ù€©{éøW—iöÐßÙ]Ç ?dŽæ@í*¿Þe嶃ì?Z†ÓÄÚZD^I¤7%Ìb6O›§J‰A.…ÆLú‹á¿5]*òE‘±l³$Q¼2‘Ëþ×§­~½~Ïß?c¿|&Ó|kâ È5YŒ«|-ŒŠ²>ö íŒå™@Å~ éÒÝÇ5¼%ˆ„²‘&OÊ · 'üšþ·àœ_³ö¿â/ÙÃ>${¸í[P{Ùc‰Õ²A›ï1ã<NÔ{&õLörÌe*Rýôn“ïeßÙ"teÒ¼âX–Rèä”d¸Üç­CgðOá/Âkë/‰Ÿ 4xwľ¸MSK¾Ôî¾Ùg Õ¹Fgˆ e zd_·V³,ì?ânó\Á)+©>Û²F=p3Y_ÿe'Ã^Gi$:”:UäÖÒyÒ¾Éa¤Aó¤¼åzR)'vzõ³Œ Ô(¤ûŸ;~Ã_ð]^;ñNŸáÏÚÿ@Õo´oê¶©â˜<1s£ÛiWÌÀ¬¥ÝæpŒ¬Ñçq%sÝ?ŽßCZ¬,ã&~p~Ø—Ú‡ÇOØÇº_•ÍšPŒšqÐïÊ3gI7R<Þ§ó©©~Çÿ/!‘n4Û­® œxŠâ2ÀõÎÕ<ûÖ³Åÿ‡´Ë}À±]Çk‚‘Ç©4â8×  <ëÈM÷ŽüC¦«ü› žÕTóÿ^çà×M¨^ìâÄcªNÖV?Ÿß?ðA_Œ_~x‹âŽ›ãŸøKõ XæÑ4íXeº¶‡èAqçÌÍ2C¹¢QÜÃGÓ¿´üÃá/í?ð+Àÿµ‡€yâŠX¼ f_•ÆTmbÖ¿Sÿà™_ ¼ñ/Æ—>+Ö/´Y­|+cnÓEyj÷]Y_™m¦û_õW 0]Ò²:g5ö>¡ÿrðL–2h:kÛØéÓòÛ¶¯¨ÍcÆ1¸ñ÷©¾ ÿ‚Xüdø#ãM'â'ì¯ñ.Çáî·¤‰±yl—WÒ7Ÿ€Ñ”¹rŒŒY]J1Á+¹TÖÞÒ=ÉågÏ?ðT¿‚ÿ þ2×Àvz™§è ÿmÖcu{‘ ¹y5I 3O9]޳92a°N[5ø áäÒn·&µu$aAçb€pqÁ5ý7|wÿ‚i~Ö´‹¦ñ§ÇŒºgˆ.\æ%á*!š5Œ–ù˜õ9é_.¯üÇÄvQÄ÷ì.>æ‹û.PæË&ÿ´ä;ã4{EÜ9Yø÷¢ø'ãÁq ͳ‚|ç]á¶ä}ß­kivº‡¼Mw ¥œ’ÆUbA&ÜF~èÏ^{Wîµü£Äšt+zþ•µ#Ž&uGnˆüø©fÿ‚dx®8>Éo®éî¼ðÖLÛû«9ýMgíP½™ø«}â½L›~µg$MÆ^H}?ÚSÏç]O†õ¯øIá–ïÁv:†¡½…µÝÈN3†1nÇàâ¿XŸþ —ãhp`ñŠ…3ÌöSe}ö‡'?ˆÏXÿÁ1þ'hwZŽ£áŸ‰ÃCþ×!¯"Ñ㻲ŠR2Ê“ÏzjªCò¡¾-é ²A{ªO"îÚñ=ÍÈáz«!b ûzìí¦i?þ*Ü3ì$_êÖÖë%óùvvöñEç!€ù`/Î0Cci_a¿üh]â¸ñu”…ÎàÏ «'ääóÜ÷®—ÀÿðL]oᶯý»àßé¢ä°Þ·p]Ø Œ­·9е/ŽŒT±©-“2þ4xŸãïÄ­OÄ~9ñä:ýúmŠ=SE•~Äè¸(‘E¢ª¦OPIîMx§Šÿk¯‰^´·Ò,/´õ˽±Ûª¦pIÚ¢ðX±Ú .\œšöÝWöý£ôyåÿ„^} YŽ~Ï|öà¶8]²Ær}FOzð߀ÿ³·Æ–ý¨4ø»ÃöºuÄ—wpé–ž*ÚÖîûJ„´¶Æ8³!“k™-ž<1×v>e,/½ï¬f¼q x%O ñ~G’ê?ÿk½Êkrj-eazÖwO*K•rJÐJ‡ç‰ÄgvªI+Ò%øŸ¨übÐâ×#¸žfâ6Uáã“ ¶à02}{Šýž³øiðóTÓo4‹/7…|[ã ¸ì5Džþ)ežùcŽË(§¸ß]´0ŽN`ù5üéYj~ømñsƾÒ.f†Ê×Uº³Ò¡Îì-®Ðyyóª€1ǦkJØDãî­O+†øÒ¼q<¸º—ƒî{Æ©­Å¥‰ ¸Õ'·“ vÉ3#Žx'§Ò¿¢ØwMð—ÅÏØ;ÄŸ |BךÚYZ9â»¶ÀYíåR6íʰ8'=kùö±øÇñ“Km¿°u]J} Ü:‚8ÀaïÖ¿v¿à“þ8ÆÿüA kÖ©£½ì”ö,†Ö@ŰXçÚÀ°ä0I¯‡•7ÔÑ\íâ¼ó Œ¤–i´\ÿ‚ÅéM¦|2ð7‰ì¢“Kxg²Ò£ º7Š/"EuUBŽI¿×Åž$´Œµ‹Åw*9™ð@7Ábz“Ú¿p?l?‡ú÷„¿àšðoÄ{É<[â .ïÝ/šþgŠÞð}•§™0åÚ  •šþ{õ_‰ ôøÂ_iWúVáµêY'9àÊŠ?™­(AÔmÒÕðÎB†’¼ìÏtãÅ;0"O\MÉÛ0GLž§;sý*‡‰¿k|=ðúêzÔ©w=Ë‘kP™#¯lm¤df¾noŠ_¦^a—øYÑóôÇ&°¡ÒüI㈚_ˆ×G–ïÂö·Öš\×71ÊšhžíÔ‹{›Œ°ùÙ Ý]cbÀW¥C +ûÇ^yÅTá‡o 4ß‘ôVû\~×wèÚÆ¡ÚÞZÚ<^t6öSI$kpq°Y<À%<)D;ºŽ+ꯂßðP/G¥_ø«Uð\—SSÓîj–ŒT‘´$Èì€ò¬ÊªÇ•nE}»á¿XxbÖçÇÞ(»ox‚{±·›N™õ!a´’$Ÿ,0y’y3˜W{™TWâ÷íIà߆_ÿl=gÀZ5å½¶›•k-ÄÆúKÔ{«Èw:lHí u“æÜä‘’k²µËîî|vKŬòâfÜ%½ú«¿íðWÆeÔ|'®§ÃÿŠFÝa¸œ(µ±º‘8Û4NØÅ*ðdÚÅÀÃq^V<ð½dÔ­5«¨­måÕš=²¤W·°E0œ.ìî°?…~mxoáŠ>'\ þAâÅ(È6zT‹éÖivÂ~»ÁÅ~žþÌ?±?ÇŸ…ÞÕ5}ZÖÍo5k¸nRÂêî¦T ¨  bIÈÝBkÉÅFjž›™ñ|5Eû­]÷"Ñ<>c‡þï ζv°î)j¡Ñ9.SêI'¦Iæœ|5-¸¿ší÷Ѝ ‘Ô~µí|'øésuO\[I A!ùºŒÅ+ëð»âv†‚ãÄ–RéÀUšê&9$ð¨ ý|ÕJ“‚¼Ï†öe±ã‘èzbÌu#ÄóWwN£œsôéTH´–_ìù-¢•$ DÄzr2=­{߇þ|\ñÜíkàÿ x£^™X–{]ñ¢+×å•ãX‚Ÿ®zFûþךŒ±ù_ õx0:Íu§Ã"ö\‘ojÃë‘în°U­±ñWmõ_[kÚŸŒ£“Y›Q´’K©î¤26èÓÄ瀠`(à⿵±÷ƒtßÛ¢ jM=N?µé‘.©so-ìÒX&šÞ"¦íCè²9 $íÎl´ø%í«ñWI¹Ðeð¥‡,.àx¤—QÔ òɼc;d$+wbùµu_?àÛ¿Ú§ö¢ñvŸã/Úãf›mm§ÁiZŠñÛÙÅ oæ\žK21<ŒŒ{¹fiB~Öv±éa#‰§$) ®>!ü|ø¡­xËÆšÍä¶%Æ©«Ë4Ø{ËÛÙZFiI$,î@tö­âü9ÒÓPÔî ½ª¯Ê"FyÙB‚Oãs_ÓGÃø5“övð¶Ãâ/Š.¿\2+Y-í"f=ÀX‡âÍõ¯§ü;ÿÔÿÁ;,„Mây¼Y¯”Èo¶ê÷$žsÈWE#ŒSWsâl$”¯òÿ†LºµWz‡ðï¨þÔ>šùìô¯jW°ŽC’ŠI?ìž•Œ¸»“%dÒêÏóÖ¹ñ/‡µ»dx5« Ç ®·Q’W±ÀnxèkÎdñçÈe~ÀÈ20×(Äž¡IçðȯôÞÒÿaïØÓO„AgðÃÃÉQëëÂöï^…§þγ暰Ôc·Rëewn‰fd#§Sò3Ïjýñǃ-%øs§¿‹Væç^½ŒÜØi±Ê ±²µˆã[tÂàd’2À5úA'ìu èм'â+‹?˜:¥Õ¤w(¬AäqùÕˆŸ³Oʼnúy°ño‰ô­\D›a•´Ñmp€t "·ùVUøÆX§ìéÚ)êTrY{9FR»{‰ú½§‹ ±ò4Í6P‰ÑB´aÅxůþÔZ÷…Jø ltRêL\__ÈÑI¾Þ|ƒ²UYI8ÜWÈæ¿c¼Gÿûø¦€6w<«‘óCv»‡à@#ù׆øö*øÝ¤xîõXÔ9Üq效ÚrÕúrÉSt¤ÜO“«ÂXÈÏž:ŸÏ¯ìcûVøe®¯G„´½J{ÙKÝÝk ›«‰Æ÷–UWfàrXœ:+š¿ý›ÿj«G/?íFF9¸žÖ@qßýjñ_¹þ øñ³D`Z‘ˆÏü|«ÅÛªž}³^i¨ø'ö‹²P,#Käÿ¦7 œ}ûu®hç¹%gyW×Ì©às*q·²?<]û:~Òóé’Csðï\”4§tQ¸Èÿi$a\ŽðWãÌV ý¡àvŸÍÙ± tú×íÇ‹½«sá‰>xOÒ|Oû2xãûÆz¼“sO´ºU°Y"‘e´8ŠBê\ä¼+ö[Õ~kÖÓ ÿ†žÔX©%d·‡güàŒWå÷Äø&oÃŽÿ$ñ¾£>“ðëBa¶=3Ãr:ŽYäs°“ŽÁQ<¢¥;8»›C2§RêzÕ¿ÄŸ€¿´ Ÿ—ûEøu|/¬3ù/â <ËgÞ¹·Œ»ÅŸï|É×$W›|Aÿ‚|xƒPððñ?ÂïéÞ(Ònƒ½ŒðRxGRFìñ‚+ìÝöý‹t K-Gð´vÊ [f½ŠæêÚæsÿY+C(Ÿïp1“Å}ð·à€þøfO|ŠÏO±–y.½ÍÔó·Ÿ)ùœ´¬Ì7Ú<<ª¯Þ­|Œžpé;S–žgá¯Á‰—Ú ðÌ:D‰4Žˆ``Éþ¨÷$õ«gâW‚|uài–óx_U†8.džI­l'¹XÛ7˜‘ñ߯û›®øãìÒAc¡jó|¿i‘Xû·>¼×†Yj_´ßu©oü9¤Ï¢9DKsö´ÇòÍðcŒ×ŸˆánWîL飹k4~,xKöžø…ðúÂü<ñ¶±¢E$ÿk¸´ÒîÖe*]Ôe•ö€¼ãå+;Døï㟠ß\êñF¹¥ÝÝ€.%¶»h%˜/BíÕ|›²E~ËüBÔî~)é_´Â_øáU2ÝY¾›|ÆŽæß%]rpW=ëã~Ÿ³olüSuðŸ_»’( ÑSùוV~õJš¼Qìöž"o†úÝÝ·‡uKýSiz•Ö›¤XØ­öibp¶9®ÒËã³m‹‘âï…bqæëÚ„€}7\ñ펕ñÿÆÍadñܱE>S$ “—sØsÐ òø ººÏÙ¤Ë)HQƒõæ´¤ÙX-ì~•[~ÐóÚÈ>ÙâMNsŽLšÕë=wÎÙü³^ÛðÓöŠøE“êÿožÓH‡d÷WªÓ\\„‹–XÛ%Ùœp¿s_“:m”îŒß<¹Á^»OÓŒ~5äÿ5ÛŸë:…­óçBЕ$¹òØ•’GùQIrOʾ‡&·æf\¨þ…íÿàµ_°€.&²ð§Âg]>ie¿º»Šã’ï ÇFQÞ¾ÌøCûnþËߴƈºÇÃyn4˸WΗK¹EûD<ò¡‘™]WÔ1õ¯åCIÔ›ânàüðÆ›o®“}k‰LÁÕ¡ši¥#EÎÌ:øßkzw†dÚO@¸ømã ;Å÷0º¬Ú#Hlìõ7â{xäl’"T‰”°ÆEL9Qý^xs⇼/®]ø‡Á&MJø¯ÛZk/­îvp«4S)Êàœ„d'¹©>3|kðÆß i¾ð7¯ øZOßÏuâ[ɦG·±†Û ¸—Í1tت»Q™Ÿ+ø“yûYø‰$™´í6ÃÌùÕäeÉïÉÏã]·ìÇã½'ã‡í?ào„z¼÷šG†oÍýïl¥¼k­3U´C2Içä’O)d+òò+9½.Ë‚{#Æ|pú|?!Ô´Ë‹k»K±([?›ä†Öî:ÿZðM{ÂZ†“ñÓûQâwÖj!–+±@üpÇn@¯ÑÛÛLð5ïísâ;O‚V–Všh‘ßD¶1mä½ ¸TRÜþìäpXñÐׂþÑsø"Ïö¥º´ø}p/,|!àßé×0WíÐÚ™œ‘ÀoõÊ ò;W̵;Tz%gû:xÿâ]§ˆ¼*–fXÀº¾KiÏ;ca–Éï1^kãÝ*÷CÖ4o‡:˜S{¦ß®ÑH) ÉØz22EN_³Â~Â>øàxòãÂ×ìš}´º§ö¼fK™/nÈyÈ!GÌ@U8¥8"¶ƒÅŸü[ªéñGŽŸ}«]Ø*ãoØZúDµ ÓåX Ï$c<ÔJ~릔“'ñ®”5ÿ 4.¨Š÷ 䎭ä‚G°ägšÎðŸ…>)|]ðµÿ‡~h—:欺U¬,–Æ4òÔ±-!i^4tÉ=+WÅ7‚ÃMDr,ngsž§í_µ?ðJ/Ùká”ÿ²û|Bøµ§ëÿß;Û´Ìê-ì´ÿÜÆ0„pî¯'<‚ج©Ï•Ý—%t~"'ì«ûsX£3ü;Ö¥Oúô9þùÍeüxø3ññü+ðÿÁöþ×´Ïý–í$©ýÕ˵ϜUJªî¹ã•Éô¯êþÿàoìí9w°ÒÞ¹ÜÑ\IÐ:wŒëšäàÀ›©]:=^iÚÆfRxõðqÛ9®Å]Žo«³òSö†øñçGý¼¾ø‹WðÍêéÚµŽ‹áíò8÷¥ä¶V“Ë U¸e› ®8È=‡ÀßüQy¦|eý¡ÄÑOâKë- C'&·dÞ¸<‘‘ÏÓ=+õ§ö™ðÜ?³×Äï‚~:‹ÄÚÝá´ñUÑ‚-NåœÛÅ…ÑKï!Wæ F9ãœWâg…tøu¿¦¬’N—W·º”ŒÎs+œ¬ŽI$–'qÉäÒž%5êJ§ÊÏRñ»Mà;øD­äh­l-;µ BË$ào ]Àà‚qŽ+éüXÿ„ þ ¤ü-Ò/,Œÿþ'jî­iµd’]¶ aiæ€Aý¿Ë™ò%Uq |?ñ“ÆBú ¯Y®çœÉ¹¸`¯³8ÇJO†šÔ·‚´Û}EÚ´†¹ƒ~<Á'žÂFùG¦¾j)Ï©ªI£öÏþwû%êŸ~4øƒö›ÕuÅÑm>ÄÖV dŠnõ^-ÌÛå8ÛÚœ îiqÆÞ¡ß¯Æ_øÂßÃ:Œüozªe†ÜøŽ˜C‚7´}Nã÷ˆg_Æ·ÂÏø)¯Ã_Ù³À’ü¸Ñ|P÷¶šŒ÷—wv7QX%̲ãc'Mêc‹lkÀÊŽsœ×…|bÿ‚¢ülñ¶µ$¾ñ­Òh— ZÓ´É5K4]Ø….¦' Ä3žrsÍz4’Hã©w#õ×öúø©â¿ÙŠ/köúþƒªø“ÄòfY]i‘¿ˆ3ŒXòòXŽž¾•øÙûx×Ç÷úçŒôj3]M ßC$ŒY‹1(1»æ÷Dø ð/Á0kˆôGÄNàózõKuÉQߎkõ+þ·à¢ú?>#»¨{Ëí;GÁ\²Çi^>;Í*‚?Ùç´WìI©üA¶ð_‡¾^é:¥¿ƒ´(´[«Û[ß²M4qЮ$¨i7ØÜkö¯öýüq©Ç¤|ø3àH`¹ŠÚ{Ù"Žr,íø_´ÞÞ„ó6åÎÛ¢«b¾>Ä{JW§«“KåsªŠ³æeïøCFøc¡øÓã[L.oííîçÓ£òö›Y.bi2?ò@=— Wó,šÏ‰ü[ðoàç€þ}«ÄZíÅωïn´ýÒ[û¢Ëy6ÿ¹L— Ó¿½‡ÿðL­'Xð¬ýªuÍ;âMêÍ-Õŵ¼cáâ¬ÇdÄ$/u)ÚÂWÙ&2Pt@iÿ?aØóH|3âÃÂÍCðtvöÞDl~è[U/äd‚ê;šîÊð0ÃÆëqbks½Oâ'ŸðKÏø+ïŽôõï| ñ2C*‡S¨µ†”ß0Êæ;»¸ä^?¼€Žâ¼×Tÿ‚hÿÁSupø7Pø%â{ËëGÞE·ØîaVôûDw&#Ï8Ýï_Þ/…ÿk­;ã=ij~϶×aµ½ŠÂ]_]»Y-ẸÁŒmS3Aå”ŒŠ“öšý¦>,~Ï^:Ñþ[øûÃÓëwV²\jºn“¦ˆáÓ06n”ÈÄ´ŒØ‘Nlt¯UÊÝNTÕô?›o‚¿ðLoø(ô t ÅðƒT°ºµ°f{‹»H¢~öO˜ÙçŒã5ésÿÁ!?à£Ú½¸¶²ðž…jèÁ”ÝkʈìÞ\6ÜõÀ?~·xÛöºÓü;á÷ñ'Æïɧ2Ü^y,Aäb%*Ì=8ü+áýCþ %ûhͤé¾3f1·–Ò½üqßî<‹'×ò®„„Ÿ3G¯ Ú¼*zOþËŸðD€ ¯m~%þÓLÿ|k¥êCªl‹ÂúmÎ0dÿ¤ºr·Èä¨Pq_¤|gñ/OÒVÎfÓôí:ÙÅ µÔCؤÀ W厙ñÏá_íá1ªx+]M~Âà`Dd3±7`60}A5ç§ÄÞð€N§¡Ùßå–I²®Ð`0ã¶k±TP\½z²r“>³ñÅnD’ZêWÒ0]æ}ÖèFgäqß°¯ÈÚ÷þ ðcྫY| »ƒâ—Œì"}÷¶Šfðæ'#{l+ö—CÉD/ý§~ ø¯öžø, -î³­_¼v¶²1ËÓôøK%¸8UÚØ®X³|ÕóW‰¼q£øÇÆš¦½ðÏáψ¼áûë“6• ÞX_]Ieoµ~F¹’LV~ ½†îí¯ÛíÓ3ÄžZ5ÆâG8’:×ß–ÿµ?¦ˆÇ¥k“µm•6L°Ïô©x…}ÞÚÇ¥—/æòôÿ øšêLd,Zû‚=±Ò}$ö®ÃNðÆ«è|Øüââ9Ü­áÝB6#·Èð?€5ým?í!ñVøì:´l‡’±¢†#Øàøs\Ÿˆ¿jŸxjÛíwš¥ùBJ ‚31ßèHVl{ÒúÂì%®çò»gðsöŠñ }ŸGøgãg î\øoP€{É …{‚?aø('¯VÃAøI®XDÀ•¼×fƒM²Õ˜Ë$Ã>žQ>¢¿¡¿~×>1ñ\ëÞÝ[Üä1•†8#p=ˆí]>“ñ»Å—ÐG$ï<ë·prä¨å¸ô{QòŸ%~Ã?ðKÏ‹¿³o‹?ácj^.’ÃR”—@ð´ÏŽ]¹/u¸4™ÎJªg½~áøÃ_¬m¦·KûY-îFË‹k L!þV>‡"¾—ãω ¡Àm!†1ëÈÏ×5Oþ5#¸Ôu»†Fg›)*W°`ÀV2÷¶GmñÓþmû"~Ñ×w^,Ò¼<ÿ |Ox|ÛSÀw1[ÛI)$³Í¥Ì’Y;¹'|Š‘ÈØ5|wÿÀüFyå|sÓ­ìC êÞîŠï^ÛPŠ9ü±(ükïë×sLf·ÔPLÄ©òçtcC¸Ez|¾8ø£F“A­ßÅ׿²QÛ«SºÙ•ÌÏæ'þ ÿhøÿæýœ[ã¿‹¾4xSÄ^êöZE¾“o¦Üéz•á¹oÞ4k©Õ¼”YÒ<µc¸b¿Ÿ/|JÖMëé—²V£AÆ=s_Ö§üWâgÄ?ˆŸ²Ä…Þ)𵿈¢ÓµŸ kÚˆ­!{½JKa!]BOϵ!!Id#!°Êî?Ç­¿ÃO‹!’ ë»s¹HÒî‰Çn‘×L$­¡2W#ñÝÍÅíà’ó…\|‡–9ã {f¾Œý<aã?ÛÀ–:´?n±Òo&ÖnØð«—Üo@²,x¸¯Ô<ñJæ15ׄõÐû‚³>•uœ}|¾µõgì¥à߈?¼Ó´øæ°™[½fD€lYgb¦Nrjî%âŸxÖãÆž0Öþ#jÌóϬ_^jò;ĮүSÇÈTÛí_eþÛzd~ðÏÁÏ€rׄ| m-úºÌæê\zò™bG9s_(ü9øK­x“Æú€'Ó¯m£×µ.I®-'Š(¡ºž8˜³2 »Pœ¶x9¯¡ÿà Ü~)ý²þ$]éé3Yiš”:-¬›|­.í³»v±FenÝ‘ÁcãÏ9]üÔH9 çQJ–ÆD’áöGÜÝ€’Iª«ªèyÃ^².Aí'êN0~¢·ü' X|Dñ^•à ;Q†+vþÓLÃÚo&HKc#;C“øR² ³î_ÛCO‹áŸì×û<ü BÑÜÿÂ1?Œ58\"ÝëÓïC*ðT„Yƒ”ûWêGìá[­ö}øw¤ÝFÞdÖºó‚0Ìڌţcí·¡î+òþ iã /‰ß¶¿4￟5§…ì!…L»Âí•r€à›†•yÇ`3ŠýØø]6á V=(Þ+_é¶ZE¾#ùVcŽœÈÀéEÑSjÆ—ÄÛûøN.5mObÒ­™æ' XÁfãÔŒ×ò/⟿ŒüYªxªO4Vî{±½þlJìTp„¿¥?ÚÃâö˜µõ]M.ÙoRwãåA¸üùú¶8­ÿ yÍà‘æ…iAÜýˆ?¡¯‡ž¬ý&åHâì’:Úæêõ·ùHÎÍ€ 21õ•øaûbë°j?´F»idèðh~F“ GÊ•³@²{‘+8ü1Ú½¬ššr»GÎçõ\b’>\W7_púv§‚‘©Ø ±äç¿ÓÚ¦X *>qÉÝž(d˜1žŒzè+ꌑJLmÃ9äkôþ §ðÂ/ж—Ãmò1-…Žª5Û²NÔKm% Á,xÏï<A$gŒ×ÀmïµdáAÃg©úõûÃsð§öý¢jx¤k;¯ x> i78áuo¾À±)/µcaǧ½L¶%\üòý£>*]ükøåâ.òÿÂI­ÞÞÆX䈷˜â’I"4\öôâ¿¥?ø Âm7ÁŸ³‡Œ~=k,m¤ñ&»ö@ùë¦è0¥'°fuÏÿZ¿”åŒZî;AŽÒ?0 Ã%c~@¯ë¿Æú¤±¿ü£Røk"Û\‹ÁÖRÉnJ¹Õ|U(eë"y˜Ý×5Øõ?–/Œ¿ïþ2|Rñ/Žd¿ÚÁ,†ÛÂz!ÔîS–V¿Öå,ß(êÊ‘`dë‚k üVÏÆœ[ÿ¥Ì>HÐ3hÆO¿8¯Ø_ÚÛÀú§…þ ~̰:m‡S¼²MX·S¸¥Æ½ {í²K†õkóŸökøO{ñûö„ðGÁ»<ßøHõËÃð‚ â[‚ý¬ !oa_­þ,ñ’üvÿ‚Áø¯âz¨»ð·Âï:Ø€òã·Ò-ÚÎ_½s,‡ºŸCL-¥ÏÒ¯hvÃÄzãi±¬v³+aEŽœàakÃÿi}uô Qk¿çµW#qêHëô¯xøIx4ïÛE©åîç“Εfn[#½|ûmøÂdÑ5›ØˆI%ùmØÿžµQ“9ù/+Ÿ’Þ2ñ··ú¾²ØÈ’¬„dÛp?˜¯înn-n#ò³GµTñÔú×ÑÞ"º[M:ì£ò€BŸâTë“èzWÌÐLdó._€ÎïúäRwêu$–ÇÿÖþFæ&}Ç=Û¡÷ükN×vrœàŒ8éééTâH^‹ ª£Ù çû¤qùÕ¤‚KunY¹Fsÿ׬ÌÁŠÆÊŠÌO ×>þçµtö±5½°B lûÀ⹨íYSÊrêA'v2knÔ»ÃgUä±Fã$q×Þ´.&Õ¼?Ú¾Õ--þFÙœ“ÈÇ'œ8Ïzý5ñ®ž9ýŒ¼3ñ= ½Ç„µ “»÷Î-§äsµK´ÿî¯ÍíQ¢“å‰Á…ÀwŸé_~Ç6Ößþxóö|ÕŠÜCw²†Éfpe@=Ãònµ3Øfˆí‰Ôæ1„‘0ügœ_çLÖbM_á=½ì¸ß£ê>Aò"¸RWò"«ø~þëXðnyx»'žÕËÝ 0#ÕH Öß„m“Qµ×|#Ãë/1Œƒ$n=ÀéY'as#šøs¶¯¬ÜxF÷æ¶Ö¬å²t?ÄYÜ~5ùÙ§µÔ ÖW*~Ñ )"°ÃoBU¸úŠû—Ã×òhú­ž·lK=¬‘Ü*úì9 øôÍxÇmJðGÇ/iP3ËnóEu Œ6‚—¤ùÎ9ÝÔcû¤u¬g‘H¡›Ï•¶1'íÆ+­ð®š‰¬ï$,"y¶¹NpÇ$þËÞÅ%ݸ[|0<©çb(Ú[FóX#Œ•ÛÉSóõÅT@ýŠý€t”ñ}߯_Ù©]·øÇ×Kj¡ñ™!_=0;6æm¤s‘_K|j¹ƒãŸìC¢|CÄ‚ãL[ɸÝå²³N_ý„wv^§‘_ þÆßâøûhxÆZZMyl¬&ÈB\“Ägï|“é·5úyð—ÂshŸÆ_Ù–æc4Z¯­éÏUf¹·wÄRÆr?g/ŠäÛSóþ ‡ã[Ï…ÿ×N½›cøkÅ6w-aòÛÏ2¬ƒ99GRÁ‡Luë_$~Û_ äøGû\üHð ÛR;jÕÚ µëý²ÇÜ(=¸ãÒ»/…£RðÇíI.ƒíÿ„—C–'d@ž~sÓÏâkéoø+§†mµß^ý£4x–-7âƒôýQâ_iål®7v €d9Æ_R‡?f½^;OåwG{ àŒ“ò…cßÖ¾îÿ‚cx¦…ß´ð.±,{l|Sžé“·ìºÐò~¥~fü<Õ£ðÏÄ-;PºÌ1£ª;/+¶BCžµõ—ƒ¯u‡Ÿµ½ÔVL°Ük–‰wfÒgi»°mêÔ!9íMìUðž­ðSö×Ô~¬2[F'Õôˆ"FÚ‹ý—q,–lpBÇL§¨ý+÷ÂëEо)øOǾºC-‹¼?¤2¿´­ŠJGÖtÎ:Œ×äßüûA¶ð§í=á¿P†¶Ò|M&•®ÛÞîʵ­ÄQÛ\àt|Lùﻥ~©üÖWû3Áœ¶ ÷$øà„‚C4Cžæ1Ðû⎉ü’ië%xES,Öþ$»{hcÂfÖäý©1œ».uâ¦oƒÞ&øwðçHñW‰íä´“RwÒhü¹cV'ËΉ“æÁÆ3Š.¹þÌú”vÿ4{æ ½e¨ø~DQ÷ÚXüø‰ÏM¦2=ó]íÔ4¸lfÆûvdãSåaùŠù—Á^#—Ã×VÞ ²Ê6•¨Y]©#•8›ðF9¯¹xø‰gý½áGsLšz]Æêö§p#וòÂx ?Ótk½Ï­Í£ó÷·ÄÌàV¾ù[(õ 2+™›7b[$|­æôç¥~rNº—ƒ<[¦Å|­Α¨ì™ýà{yaþéÇÆ‹ûº.?ápÁ¬5Ç_x'Aº¶· ¥'ðÝÇ™öì$g·¾ ý»®,ôíSà÷í)¤âæÄApÀæ?ôIQN9æ)fçÛ•úSÿó“JñÁ‰?uÿÞY®¿%ÊÛ“ÃYëQª9_o1˜ÐWæwÆÝïÄ_ðMÚèÿ¥ø Äk¥ÎÇ¢ó¬[#°,ëéN;ì¼;µñãÀÄ·‰tÛ©­öŒ™݉ÚÃ#ܳ¬^.ðÏØŽ›Òù>ªŽõö‡ìq  ¼Sâ…Rü±ø+ÆRÅ ž±»2!·²ì˜qÐTã–ƒkp|O Ë(–ê9¬Ý‡O_?¨ö/¥ƒRÕ ÕO̺¶•e¨ƒž6ÎOnX2œ×ÍŸ¼<ÿ þ?x³Àȯ#i^$¹!—¢M'ž z’ â¾™Ô!“Tð^«' w¶<®8·Ÿ|c?î1Å)î-û;øÃ>.ñGÆ-[Xe¾m=^Ê9'k‡†~ @ÂdIÅm~ÔEÇįø&Öã+çIµ Ýé«u&0äÈ­fìØîÏŒöÍd|¿ƒÃŸµN†øÄ>+ðþ£¢»/F*¢u>¥+éO‡º$3ý™¾/| ºQ,ÖöúºD‡²¦/mÏášÔæ¨Ô‘úTr»<Å¿¼sùóR ù‡½jÅ?èìäð~•¥¦<»7²–A‚½zâ³á]ÎàŽ6äSíY¢ÊpGQì &ïÏì¬=ßÀ=_D¸ÃZ+… ËÐI킸#ÔVWŒ¼3§é¼[à‹¸a3Þjº•¬N%gE™pÜíj¿ÿ¦ð¦£ñ#À:²ðñnÓfŠé¬”Ó¥Üb4 ’ùâaÈí^™ñ[à·¼a⫯ˆZ$þS 9­R¶iʹR¬àü¬: ƹäÒcI³¯ø;­øÁ_¾|6Ð'·†ûTðf›4Ü[Ãu²kËåY4]FØä%°2“ëVइíQû6|Ò¼]acýƒšÄ–RÜ[ÙXÉosc";@r±È¨Y€ ·8®kÄ>ñ^—ûJøÄ Þ_èÑ´Øç{5Bû—ljX|ß WS€U>Ÿ`~ÝÿüCûVþÎ~0øeðãá·‹.î5—·]6;•¶ŽÚ†EpÁVS†@ ù½ê¡%qò³ñÇÂüOáÛ×Àöž7Ô?·<®ÜéÆÞ(|©ôír/+ª«óŽ[î®NkÌÿkOü7Ñm#·‹À:¼‚ÖРƒLÖ£i AžY<ÄÚ¾Ë^©ãŸÙSö£¾Ö~üMð·‚5-@ø[@Ò-u uòâ’䖄ާ$ ƒÈ ç¥}kûo~Ì?þ0|hø©¯|0ð¥þµ[Ã×ÖrÀÈ“Ùo3§ï2!Æ õ&¯™ •ŸpÿÁÿi¯~ ý˜¿°>龚[&°e¸×ôÙ¯ž"m¼°3 ±cr¦Nsó_°Ú/í»ûX][*x²} –2¾•áùcŒƒœ(ó®d'ާצ+ð#þ ð[ãGÀMÄÞøËáOÃBxôãe5Ï”é;ÀÓùƒ÷R>6+!õ¯Õ+MOH´´RFÜG!#?Nùê¯3ˆ’“±ÕJŠjìõÏøºûÆž"¼ñ‰.Qo/ÊË?Ùbû:îU »W *ŒûÖrý–tù/d7ÌAÆ}ûuú× m¬ÙÝ·“"H2O;8ô­ëbmŒÒƒôÍyó¬ÞçT`‘ÑÃ9h €°#Ë’q׃Åuze•¶Ð·À’Tcž•ÌÚ ¤ŒÍón?.N:­uö÷zE±ßrrÙ9®~k»³h£¯Óí¬V¶%kXyêNõÇ|@Ôå¢Óm5(mÕ™ZXÕãøç<×/ÆŸAˆ¼3k-Ê?¿˜Sþèëøœf¸×Ôô-BàÞj0µÏšwf=Ø×ÔjÊRV7‹³=³ÃÞµ×.ü»[ø`bAçŒ)#·½}£è:‚ʤj6W¾o“å¨#¿PN+å?x—áE¥²C=Êʸ¶Ç;¹êI=ºñ]ëøÛáó1K™çŒç 2± =ñÎk3e$}aª*ˆÃÄÍÎï—'§éšùöÇýŽ´ŸÛ§à-ÿì÷¯\ÇaªÏu©áíJTcž­n ¦ý¸aèZ ¶B9 ‚3\}׉þ I&Û~{^¤mr?F¼ÅZOìë®»^Mñs]Ñdaó/I`2:aåúŠè¡QÂJH½¦¬?>üdð_Œµïø£áκºžƒuq¦ßF!¾¾ˆIk##˜£†Œ‘•e;YHaÖ¾;ñÝŠO&ý“ý—uní ’Ы«`ä{€kûqñ_†þÛê¶zÿíkã›{ˆ¤[0ž!³ìòÇ´²îk}̬ ?¼-ŒqŠü[ÿ‚‘þÏ?ðO-'Ãs|^øIñ»Sñïµ[¸áºÓâšÏS¹*‘œÝ\ÉFʹT‹+žªHàšú*uã-¥+#ñD´†òYò9þÕy´A ¨ÚÛ?7'Hø>½k_ÃZ¾¦k–“ßÙù7΋̈AMŒ3É9'Ø×g®øïâ}®¤ørÇP“PÓ4;fµµšKHíä†'!ŒjççeeIÉäö5ÊÙgjZ ÑÖÍ༆Spo@Ý”@÷÷­™ÏfwZÆ‘=ý¤~)ÑcV\•Ù&×eDùA*H9#œw¦³xwR»‡^Ðõ‰cÔbLÜF\åHì9Áù® ïI¡e»ºX$¶&Ew‘K7pÌAÆß^µ¡$ÐÙš”WV©À!¼èË)êpkŸÙê¬Þƒ]¼ðþ¯öG÷Oq «ÆÈ¸l`¡#¿^­nM xZöâ9ô½Iãšì¨x]DQÆW¶“ŽIëïXW¾4°žþU¾¿‚veUó …Æ1ž9ëU-að­Ì2[Ýjqùs3l|S -»öŃg¢ØøGQ¹Ž[KÉLé*1™ )ã?ÞqœäìáM'R¾–Ú7“ìS‰YÃíTPNáÐã¯V6›¡|=Ób„W®COö¸ƒE!û­‡è;çœU›}(iªu{?·ÎøýìÑv|Ù H 9 ­dáa¨Iô5õ½C@±ÓEĹ6Ú¾ã,Àï’)Tä•ÏO¯LRXM©Ú§ö\2Mshրœj?Ýú÷Åu2hz'‡õ ø§\Ðæ¹’3RØj‘Üyjz‡!BþJÙ²´øa«]ǤÚÜÙê1@˜YòXòInÙÇQÖ¥«-Gìå±À¾…âûk ¯¶Ãi§‚Ž`Ìòm=†ÕOM2i¾†ÏýI!@² ¹ PŒ’N1Ö»øþøßM¸–oHí ij-..•OÊU–&RqÁ “PØø⥼û4¿ jwò$žaC¥ÝÈ›Çr=(M=Šähó›hQ4º¦³ÝÇum%²¥±$ AùLkÔ/ûT§OñYðôZõ”ö¶¹R›™XoÇ”eÔÅ€Å}oà\]?ˆô¯k–z•Àc5´%ì‰æâ¼€}6І?‡?/o7Ââ^L+H4 ÷“i÷䊶—b\Yó¬vƒB¿9™^mÍ»…á§äk®:¾t5Ô¬¦FÓ®š HÞ8^¹¯uÙgö»Ô⿳ð– {sò]iwèÛu8²ßì’¢Ÿ¦þǵ´Öþ^ðë]¹FÚÄ‹c( Ä¨¬:dñYs.åªR¶ÇÎöiw{uÓYç$"?ˆð~ncÛ=+_F»šKiÝ­›s S— :€Ý+èíö>ý´$¾Û¥ü1ñ,³~Ÿ$HÙ<Œœúp+¤Ó¿àžÿ¶Ë+C£|ñ\m(ù’kxŠd’åŸz™+­Jœ/ðÕÁ³×À}ò[¤ ÌTnRˆ¹çòüëû›ý›.|Káٯῇ£…ç’ÏÃ:zM)ÆZSfkð FŒÊßd+”à¿–nUœ(çhçŠþ¸ügâšG…ì|1®Ko¥Ù[Zy³"Ûò#UåXäG¥MÜKåv>Ó$kZí„SgÏíÍíº·Ï"C!¶”ªî„cŒçŠý¨ðLj¾*Ée¥E¯Kj·FÕ¢ˆ›ž+‚¿2Œ•Sò³cÀͨ¥kB—.§¦Ë­\0Ñ».0T#Ó¾*”÷=ÔGíÉ&ÐÈ|¬äçxâ°gñݵ´ÅuOZ§Þ\‘„ýææ¯ÙøªÒéÅâ8¼¼¿ge8­p%s Ë¹ÐþÌ ÞièìÇ; `A÷eÇò®nëÁ .•lš6=ãóמzÌS]\(’ÏYv ÈÝ,}óU–ëRžco¼ûÇð,‘¯Ob*¹X¹µ>lñWÁ¿„L¯y>›3`,¯ÏýôêF?:ò Kᇆ4ô#Hø˜-2Ìe²†â´œXsšÚ–… œlÖÿ´œuWÓ'„û|ñ–PHï\T°Xþö][F›o"[g¸Þp?ºÑ×½~—ÞCàfÔ£Ôïn-…ÊdY<¶ôä!Ç#¾3M’ûÀÓ¦éÙÎp ˜¨ÇÓ4ú2?7cñí–›M-èS bœŸª ìtÿéwJD‘³œJÂí{}Üæ¾×ŸVø|%s$ðuä¹2ŸLf²Î½ð–VMNÖ02qóŒs鎿áG4;Ÿ6Ø]-öϳÃ)bÀ °0*=ò1]ðå´ònb™ýü®zôéÒ½z÷Å_ LÎñ1EbJ˜!w°ó®fo|)IÇâYóŒ[i}È$~”sHgžMá­><ÈÖò̃“ €}HéùU ­IfͲÃR^CÇЕæ½GMñgÃuš$§n KÇ‘êrƶÿá$øg¸Ç.µfý™]”cþû?¥Ò2<hºqæÝ<,ûWô¥g\éZ8¤’4*:4î úqÏÒ¾ˆ|(Lpê6)ÈÉR¿’ŒUkˆ?QH]vF81aú)#ò§yÙòÖ£g1¯ØÍ£ŽN¤R•ÅÞ[ø¦@[G] Œ`ýªY—'Øú×Õº‡Äo‡ñ¶*ŽÙÏ÷ O×8ý+™—âO†&ÄRxÏÎ$ñ»M? ¾f#â½kCý¥oe2xkPð}¨ÏÚb»Ÿ§÷B?Zù×ãGÁ_Úÿã§ ð«ãØM¦j¶šÞ‘ªXi÷ë{¦êm‘<$».â™v“Ö¿S¯> ü=ŽF¿ ãIÓdSŒz«cË_øÃ·éäZk÷úy^É!,¤ôrA¡¨5qœ”~%øûfêßçø}'Ä?iÏ<O©YxjH¯æ’òS=ÄîË!ŒO,…Ýc³€M|ëû&ÿÁj?‡“Ç_uŸë—öiäC¨ëºmзÓü܃,JÒcÌÀÀ|–QÀûÆ¿¢Ý?ã¿Ãý:$²H²—é 6¬õù˜¬û?Ž> }jo[ÜÞh÷®¤3ZM ‘²p˜‹Ç^A泩‰šŠ:)ÑŽ³óù¿e_ø*t¾9ºðåçÄÿ µ½œÏ Ö­i¢Ë4q̪aW¸BW€$í“övø‘ðçÄÍáŸÚ/Æš“ÇV«¦iúŒ:,–vv—?˜¶ó %”ù³†- –PJmÎâ þ™X|Vð_‰-bþÖñ›™ðYQí§W?Ä1òàwþ_›_ÿo¿‚w7Šþ øÏÃ_5 (æ’Æ{”ðŒ³Â|—!'†t §k$2ޏpkÃÍ0˜ŒnT#¥ÎÅ*qw¹ÙjŸïl~jü­ZéšïŒî"±Ó&ŽÃ÷vw–„Oö¹%šR"ΧŽœšåµ_Ø·öËÖbo´üwð]é,„ž ›“èA»#ô¯Ï¿ÿÁS/-üo5ûé~*Ôu 2M"ÂîÊòKº³Í4{K$²lMûF01Œû_àgí?ã¿zlÖZÇ5m󤵺ðýþ™”N>I'¶@ì;í8Ée˜¼¾„¡UÞîáZ¤&Ò‹6Óöý±Øù|nðœŒæÚG×íUÊxóþ ûIüHøsâ_…þ-ø·àÙôßD©¨“໘äy(I—ý7hš0‰²]¥—hÁ⾊ é׺Œºo‰~0Ms>Ðí—4ÓJˆ{; PûÚ‹áÁ6‰TñWŠ5iž<æÛŸmÎÃÚö–:ªÕ¯ÀÍS[ýçÈþÿ‚oþÓ>ÑÂãö‹ð£ê¢Öï|+$ºœPiû¾ÌžwÛY`ÛŠÝNs^[ðÓþ EsðgÅÚ÷Ägý üâx•÷^ê^%ð¬—×$’Y¼²ú†q9mŠ2ºªèz|ý—`_¶êúwˆgU2K ¸ ê0ÀþC5«à‡ß³ŒïfƒÂÕî-tð_P¾¿·û=…ª/yn™Â)=•Ioj‰æÒ[— ¿º~}xŸö3ø‹âöºßí=á8-ii: ÛPˆ>Úʃ>S[¿ ¿àŽ^øÑvbð‡µˆi!ßw Œkn ²\4mGR7ŽÕúeàÏøkRñ¿¾é:, 7øÅÞdv39"ÒÅÏyŒ¬”ć9R¾ëÕþ*üLû*ÙEâ{.Ê0DvZ$éÐ ¸_©'Þ³”1X•û¥eÝ¡T¯JšµF|/ð[þ ÷ðoƒµMcÇ^3ñ<¶ð4-âÓ,®?Ù™Ä}Ðúu#Šýiø}ûx7áŒ,žÓì49¶„kµ¯/=âñ¹úWÂ~"Öõ=l³jºÆ­|î9j·k}’T¥yÕç…´=EüÍBÝ®ôÏ<Ç×Ì•³\U¸KZέC(çt©éŸ´PüðÕ¤k‰.&ÔÊ€?Óuåçý˜C¢ÀWQa¤x3ÃÀYXK¤ØÄœ€'¶‹ëѿƿ&øeà«–-qáë ÜnšÙ$$zàš#øaà[VfµðÆ‘cµ„@€;gf1NŸF*Îd¾ ]Þ™5ŸD¬dñ“€~èÔmöãñb£Ox7nøu휠jüäC_7^ðå²”ƒFÒ£=qöúrœW)uá?M'BÓrO·÷ÆOÒ´ÿS þÛ)q ìEéâ¯\2Çm¬énÄtKûsœz!5<š•¥ÉÙowc1ìä$À54—Þ ðŒèa—ÃÚV3÷M„<ûçecËðßáýË›Ã:2ÆÀäñÎSùÖ2à¥Òl¥Ä+±ý9Ãg¨È|Ø"ŽLðH•~Œj6Ó|Bæ-¡ ‚3œï‘ŸÊ¿—{ß…¿ -^ÝøkHBrqÆåGR c¹§Úx?±y§éék0ñ4°¸¦7·Pk–| )mPÖ0|Ó‚â/‹­±€ j†^žÓ$Ÿ­mÙ~ÑßµÕ°òÓ⇈$’Ìv3~Úç\U8¾›Çˆ°ýQû«öö •“¦=iÏ­J1¸¿±ìZüH¶ý­ÿk;)ŸÇ1Ýç9º%œ«ÇºªÖºÛÛ«ö³*×éá M½Òn-œÿÀ¢¹ ß5æTàœÎ/Ýw:ažad~Ç&¿rœ#I“ØžÔÿøHõCÓ:û’OõÅ~IEÿøÉo"Çwà _ ¼ùW÷–ŸmÑK]–™ÿ>+øcp„ã'JÖ!”|N‘qÔáœÞºÍp’ê~¡Câ½b7ÇžütöúUõñ޶£þ>ˆö üîÓ?à¡_§˜C®xKÆ`ÿž«i ò ‹ÿãµÝéß¶ï죨þæ÷ÅWZ,ŒyM[G¿´Ø=Ý¡1çþ\2ÌÚ–ñfñÄá¥ÕoÅãY~Uu'ÝGøT£Æ—­÷Ñ÷@¯˜|9ûE~Î2¹[/üCðåô¬vˆÿ´"ŠBÜ‘•¿J÷++9¯‘eÒZÔ<‡µš9Áð5Á:¸ø|IýÆÑTššøºgñÆ*ì^*p~î8þá$¶¿¶`'·• #éUÒÄz@oNŸýzçy¶"Þ*T)µ¡é²kÖ×@­ÄI*ž?z¡ÇäEpÚÞðšü‘âÖ]ÃÖÏú Èúâ ŽðIò©ÆG4ÙoVÒ–F*Ø<äŽüŠÕgs¶¢XH³ŠºøðKTÍÒážÁŸ£ÛÊØúíaÇÓ¥yG‰ÿc¯k'ìòÞÛßEÈþÊ9›’2xÇá^Ïuâ æF,Ærsøû×?{âKkU¿•P“…@K¹?J¸çrNñ•Ÿ“)àbÖªçÂ2ÿ‚9~ɾ;šKýNÆ:ò_ùxÓ[B>¨’„oÅkäoÿÁ¾žÔ>Ó'Âo‹7ÖlÊ>ͳcÜïÉ91ùr°ç»×íMæ·5…·öŠ Šª÷Mä©WÅpwÿ´W€´[ŸìýO\ÑÄë‚QuspZ¾Æ¥;*s“^žm|› /Š)Ì÷àƒŸ·§…TÜxSSð—ŒÒ0rÖSͦÌä±3H9ì Ÿ~e|mýmÙøÉ/Æ…ž"Ó¬c?ñýc Õ­îsff‘îÒF‹ï_Þ.‹ñ‡EÕâè³[Ý»c˜¤<ôû¦½×ÇS,>V·i~Ê·š†±¦iÑêžÐn¢š[Ý1˜5¬/>æò ùQ£ìP6§ËÀüQøcd¶žñßnô{Ë$Ôe‚Þöy„w02,Ç d??.8*~^ ×5ðsöÙñÁÇÑu%Ò"Õõ-êv¥½•åÍXBÈ Û‰7°?tñÀozôh¾Ü[\5äún(”8eÚ¤Æ ãëôæ¼\ã¢;¥Jç5â/ø&¿ía¯êRkÚÓôû¶W‰'ñ, ø^Ͷ6^O`k˜ðüwþ á8üA7î¼%k&®ãΑ§dGÕ‘e¿à·Ÿˆ4»_xžoɦÉpnn®bÖíÌï#8 p”Œ[^ ÿ‚þÖž)“ì¾#ñ7ƒ¼3e¥_Ä#–óPYZæ 4ѤM’©ÂˆÉú\ü²î“}Zž—³x`+!kx÷HÉÆO9³Šè4Ï„Zg†|?m«EmHnf¸¨¾î@Æ÷j¾»Pµ‡ŽÇÓÓÁ|Emª%·ˆ¾?xB8Ñ•¦U±‘Ë…®ñ» Ãú™û7ÿÁ6|ðLÖltŸØø—MÕvIFÖà9-4ÊçÎ-À]›TÄó_†¯àͲÍsj’³’T}àÌ­Æ+E|'â Pí»yVÎ(’4Åä‘  =—8ö¬Þ&rÜj‚[ÿí‹”iÿ^xtAk§éáláhI‹X”0R2Ï»ÎO\⾃Pÿ…›ãÏêzLr[CâmgMðöŸ– ´ÜøÇ¤jiÚ´;ncÕʌ夷 Œç$ת|4°ƒížÓ–5m­æEc´³ß«€O®+é/~ÏöÚž¥i®êºˆpð1$+o©#$ü£®Ø|6žóKÕõ‹XlçK{©¤RÖëæd"™°QCc*„‚}ë±c“…‘¢ªxçí¡Ü~Ö~)·×>)I¥Câ"žS^éñy1Iò»×'%GÏ_N•Gá翈ßšÓÀ?®4g’¹K«º–I›ÄÊì»w0 ` ô˜>i~¶}TL å˱·…˜qœ6q[~ðwö>³u«j*Ñ´Ïò7!ÃË9ë\ÒÍ*År©³šlù›Ã¿°ÿÅï |T‹âN‰©i,ßo’ñá.U>bÌpárçŒ b½ò×Døá£Ù]h60K{ÞGZ¨1DÎÛŸaÚ¿Î+é+†»7Ëf·mqÚf@VPãçv®GUÓ¡´¹¶¶ðÔj]ÿw !CdsŸâíSý«ZZIäx®¯û,xoâ.©¦èþ/ñåô"ê ݬr¨~dàn$Öµ‡ìmð/ÁIs§øŸTñ#ŠW- P`•zÓ5ëo„-,'[‹–*£'–y À-Î Õ«iZ;jë£:‰§ä0b½@ãƒÇN=èþÓ©k7¡\çÓ tß‚pÝ2ÙÞ®›Ì¡]víEçîò:ôÎk©ñíùñ÷á~ðóà½>™á°†Úî­bš[ù†9dPÛJð9Èð+ç‹?†3yFÖâUI½I-—÷=k²´ð»èö¶:&ðÑÌq’ÏÉ=sÎ95×’—34U[V*øŸöäý«üY¦Íàø®þïHm©-¬@At˜Àb¾«œÖ°¼ âÍ>=-4[-2ÔÃy+K%¿—µ%sÔºóBz öQá 2ÞÚ$½ˆÜºäà‹c·©®WÅv™¥…Ò´-0Ëy!MÓÆ6¬d‘€Æ3ü«·ûvÄó^ ñ†á.þø[P¼ÑtMnéoum'EiÙæ*‘»Íåa(Š¿{øEw¾2ø×ð¯ömð‰¾03$[¸•#YIn® íÌIy<ãæ+|rñf£ã߈—šŽ¡q¬]4ÍI$Ð#È~HÈ,¨¸¸o x&òÂøC⯠\GÇŒ,àò‚séŠþ™,<)à½R 4Û¿ÚOmk6ß%ã(È*ü*=áïÂ]6Hï‡"‚Wq´…`ˆÇ¡Ác×ÛoŠ"´Q3??gÿˆ??e/Å Ü]ZøZ{·zd»˜g#/µX éÎ ‘ÁɯègÅßuOM —þm¼ÑÃ$r¢‘ºQrÃ’OÖ¸¸ü/à=>Õí,´‹‚áš7ÉQ¸7Þg'Ö»i¡Åk •¶Ÿ­¢,pªÄLá½+œI ÛAny͇Žç¸¾¹';Q€Œ8ý?*µÿ TÆFò¥faw1'9ô$]^—àéõ'þÎÒlƒM)åTnéÔŽ=MmOð‡Æ6±Â'Ó¡e‰™¿và³vôÃsïÓµBÏaÜj'/¦x®}ÀKu"ºd­ŒŸoJèÓÇz€@­}3ÈÞûò}8'‡qáJ®?°žcJá€ÏûÀZ£g¡ê×ÿCkyg*Ò ÂŽ¥Npi® …Å¡Ô'ŽõVD?m¸SÆì7̧Û5³iñ+ÄV(ßgÕ§P‡›úsY—þIgE½³òC¨@‘ÄјôûV—<*ëq¥K Ȭ­Ÿ™O°?™ÍWúÁK«‘§ÿ ;Åï2Ãý¤÷;ñ‚pÜwú}k£±øƒªÜ@ºV¡#º¢€d*~„Üû×7ÂëkXs¢4Ó8ùH<.×úU;øºG†D|/ý2ã[G; ÷¬pß´¯íàÏÙ_áü>(×­Rû^Ôĉ¥i &ÙnÍ,˜G g ØË7 ¯ÀŸŠ¶¿ímñžê{¿øÎçDÒËOÒe6F¿ì|ÂÀqÜõâ¿Gÿiߨ'ãÇÆÿ‰Wß ö(á²Ó¬§­ÖÞÕ»¶·Îd,ÌzÛ5óˆà•_¢ÒÑ-á³½¿TPö¿5 êê@Q´ŽÝë×£œ`’øÐևƾý¤?i߆ÚÝ·‰¼%ãÝbâî.±ÞÝ=Ô E(äöêA ï_Óçü—þ WªþÒß oôïÛ(Öô#¶¥¶x“!'‹ø„nAV Ñã'ð=¿à˜Ÿµ>—,0Úxwí±Üß%Úbôß¼««ôÛÀ¯°?àŸÿ³OÇÿٗ⧉õ‰’ßOÖ´U·Kˆ¤Ic,á†ps’:UÕÍ0­6¦ŠQ¹û¾¿ M©Zø‚Ò+ÛˆÚÚH$”làäô=׌éõÔµH£ƒU¶LeL38Pîã’¹Çnµ£ÞËo©Ãâ²3K¨gû<‡jL"pÅIŽ0xé_zkß·ÕųÂÚÂç¹’F2ï–åbWÊm¸é^l³h}™””çÍš_ŸEºXuÍZØcûÃϧŸr8­‹Ÿ€^0×ì‚ê¾%Õ.Ðà§·*æ?¦+k\ý¹¿hïÜ _‡¿ 4˜¦!û]ã8eìWä\c¾z×’ø¯âüKÄVMs®j:…4ìns¥Z,—ÿM$cгR³$þÑÐ+‚ß³gŠád¹ÄWQH ,v*€GNJžýxª2üñý¬!añ† ]GÅs€~µå~øYñ!/F±ã¿Š·Úœ%w477€«u$´uàkñ'ÂOØ_YüU{~’²…F‹Ï#ç½x­#˜_i„#ØÓáÄÛ’e²ñ&‹rýû JÜ\ÓÏÁ?Œ–¯!¶Ñ®œmpVÊÜ9qÊᶘ~5á7’øGE>\RŸ&%œy”€>•_Iñ·†î…¼ ÈEl”¹$Oªx¹½¤MéŸHYÍû^øz%²ðŸ„ôøã•‹Ý9Óí]årÙÜϼÀwí[Z‡‰ÿm„µ_'Ã6WÒn,¢k89÷Íê}óšò˜'•¡Æûì¬G*×X*}HÍw–×úƉÙWW7¥£Õ.S'®KŸ­GÖ&ú‡îûe}ÿ »¨ÜK©_xZâ'™¾ïÙ­íâëò¬„ãê¯#ºýuOˆÍÏŠ~,üðæ¯rÀµÍΙ™öŒ ²°,0=+íˆ>(ÛØÇö[‹ë±8à®ôv£pý+²µøË¡[F³o¼ÛÈÜdHÇï0$Ö‹V;0q§Øüê»ý€¾ÊXËû?h2“Éò¡ò€ü7 Ððÿì…¨ø\…ß íü# û!º‚Ŷ$æ,í$*@ÉÉ5úGoñ³M ˆoïÎw(}çÛ±úW;¯~Ó~Ò &©u¨o'âvNŒp*'Š«R.2zÑ䄹’Ôø~ûàGÇyUm£Ð®V8†à‚<'=ñÉüÈö¬ë¯Ùÿöƒº+åè÷$®yã ëÿ=1“Û5ö4_µ‰fZ\f6û¹´“~=úƒÿ뮂Ëãߎõqn‰0þ¶û[÷Éü8ËÊ—CÒŽ:§F~kø›öGý£5¨`˜é·0µ¼ñݨ!$Vh˜•Þ1øw¯:—þ Sû=ë³ë^"ðgŒn5é^âêHµˆÓJÅÝq–$Œç¯Zý‚Ò¾-|bÔuDÑt]õÉŸbª.ÔR£<“€:¶@ t~!ø¯¯xö=+âÆ¯¢è“åÛO:‰1í´‘ùÖô1.“|¨æ®Ýgïê~Ïÿ„ý—'œ¼¾ñÒ;dán„ÄzÄm?fIÿ†ý•Š˜môOˆL9 %ÄH=øÚxÇÒ¿sí><øR@±CâÍRÃ*¦åG°Ü®+¦³ømpŒmïô‰Ôà‚—vý 6y÷æºVa_¡ÇõzgóÃwÿ‡ý›mÝžSñ*Þ7þ¾Äñ€{h¿2Mz ïìoðöÛög½ý–¼sã(´­WÅþ"Õ.õ`óä6°,1Ħ8UTƬ ®s“žkúÓ|swpÊ¿j²ÚI$ˆIǾH¹qâ–1îymYˆ¼«üÏ­Ts ½Pžò· ÿÁ'þÁ®ØO­x¯ÄkaouóÃu¦DDÅ"³&Dx9ޏõâ¿d¿hËoÙSö›ð°øiñrÓ]›CŽö+å‡L2éÎò@…"2:ó&ÝÛ¶ãnð¨ý‡Äjå o4$² FVÜW¹$ÒË{kX®¬cŒ–Ë3·OÖŸö”ûê‘?Ÿ‹ÿø&§üâh¾×ã»+€Ír$SžãtgúV?ü;þ ˆÐù2x£Æñ:œk˜ËÜmòHü±_кÛxBð3^Ù@ÙÈ%BG^3ëXsÚx Ø6ýÈT ÷÷«þÓ¨/©£òëöLø9ûþÆZ—‰.>kþ Ô¿á-†ÎÒðë&û5½£¼›"ÇfBçqrÜ+óÿö ý„˜éôéŠrÌì®d𑾇âOìÿùð/ìåûDX|KñçÅŸ j ¥ÚÞ›8íPÇ*Ü]Æaó0ÒœŒÀtÀ$w¯´>ÿÁ9ÿdŸ…øš÷Jø£w©]ø²o?P¹¹–ÔL¯1P"T% ŽÄ«9¯«çøû:5ùÔÿ°ÚÜJß$I÷ tü?*›àìñpqÏhGaÉoluÁ®J˜÷'u#háÒVe]3öpøIwnWJñí‰Q‘e‘U’Cq_”´¯ÁO†úí”[¾!ióIÛU`BÁ²xå‰ë_®ÃöaøwcýŸu.¢Ë$l­¾WBCNò9àœ?²¶¯§¾•swâ¶|^ùj¸ì …`~¤Ö´1Ü¿"XDöGò…ñÂo¦ëz–ƒwþ¢ÈËs.f/ AòF7g‘Æ9¯¤…cd‘7N$8bËÀ÷ÏCšþÆuø"çì9¨Itƒ^ñeœs•b±ê2I’¼‚wî Aé‘ÅqwßðBoØòi ÑüCñ„ z/ú$ª¯Þ–9íŽEz 2¥³fO>‡ÿ×þGî¿d/ŠÖÁ¬h88XÜe¾€€1îMrן³GÆÛ_+AžCÊ„&r=‹ šú±/þ'»ˆÍäÕc 7¾*Èñ'Ä8 cÔd‘ÉeaÙ9ÆFA®OjÊäGÆ’üøÝ³OámC#’¢5fýð5Ê^øSÆžT>*Ónìa•Ê,“@Ñ®üg#÷“üNñô$Ÿµ‘~S½è1ü«Ï~*øÃÄ>?ðLšV²Ú £ ©VÊq“OZڜܷ&Ö>Iµ¸6ryĆtÁõüý«íߨóÅmá_ÚZÚ ™-µ‹Sv¤–P²Äxàn%Oä+áà³²…¿9ãêÞñ!ð÷‹¼)ãHå*öW¿e–N»c¸6ã§² i%t&}×â ÜøkǾ&ð½Ôoo¶§$öýô¹c7û¿9®sÂ7Í£xÇMÔ.J¬h1ËžÊãa­}û@$>.ðÿŽ¡ëZ41HÊO—çY¶Ÿ¼Èÿ§µ|á¨ØÜ-Ë] ›;Ç9Í`AËëZ ÑüO©h’òÖ—Çdê¿¡®Wö‚ð.·âë/üBðý¥Íãêz]Οä¡p³i³×v:î YF2z÷ÄË16¯mâ¨UPkÉ1dþ7PŒ÷Æ9®ÇÁ?5 |× Óíã¹m*î •Ž\±i;[n9=jú#óþüIEÄ~Ôd 26[H£ó#oëš[…ŸÃÿÂ=¨¨ÜÇ>Cœ‚1×÷_ü4׈–SèqîaŽŽçþùÚ9ük«Ñ?ho_¶Þ‘ñÝmN÷f~µš¨Ë²>AH¼Cà-Ãþ#Öín´Ëïݬ± £d}¥YT¨ÀbªHb=«ú$Öu­þ«Dø¡ 6ý+âÏ„ô[§÷lÚˆh®Xœ}æFˆuþWâŸÇëýâM„i¨i£È`rM‡xôHQÀ<œ×ß_¼cyãØ³áßÄ‹ùš[¿k+’¯Ï‹+Û,ï8ä´ž:tª½÷%Ÿ¿¶_†ÿáM~Õv:®ŒÊ‡Kñ/ÍŸg¾`Ì=Îæ¾úý§þj¿ÿàŸ¾Ô<l—WŸ¼\ÚjÆçéú„,¤nþÊyàíÂÁeþ2ÛüC·CækZ*J® ÃiŽ7òÑÈJàö¯TýŽ|M7ÅOÙwǾФ2j^$ð¼²YwWSÒÂ2¶Ð@Ã7 G%GZsÚâ?!ý”¾7X‚ߨÌáx'VÆ{îñ5Û|K¾Ô|%ñ#Àßu«w´›K¿ŽÆãyÉT•v;1郞 ~„xsNñ†·k ý•õ…¬‘6BŒx<}käOÛ/Á–¡àdßq0Ý)fÎøàlÉøí9â±¥UÉØÒQIEÿÁ@|*¿ÿbŸ…>9ˆÉ»ARðlã9 qmþ›7û-,ëó€8Í{ÿìãˆõßV:ä²He´¿Ñu¿0 Ÿ.x…Œ ç<#dœq5!ñÃþ Óã›K°f“M—Nñ|*zälµ¢Sê<׎Á35UÔ´íGá“aXuD9Æ÷ÝDAéTã¼~ŒÖ©§íëû!x·âwí9yã/ Ëo jze‹Î×4e'€É˜ cË‚§Ô{×Ï_¾üNð¯Â«ož,»Ö×C1H-n~cR¾ãó€åyé_­´¹>§ð»ÁŸì2’^Z­¤Ò ÇÍ*~ì1õ#ƾ0ñD“ø¯Áú§‡7™šëMŸï` UÏNâ°öŽö*1\·?2<3¦¶¬nô$ÿ˜••žÜòNÒGç_~êZœ&м㘆å×´(wdàï¶Túæ¾ð-ãØx£L½o‘`™ç³?Èsé†8úWÛ •¯~YZß Ïá}wPÓÏb"¸•š1þèGëZ³>c’Ö#º]6æâÌ`Ù\Ç>ÃØFÙô¯•k¿ ¦›ñbîîÝ6[ø—I³Õc`ĉ$œ2»FànÛÖ¾ÓÔ _´_鎎âßr÷K€úW‰~Ô:[ë |ãP™›L‚ëK¸aÈ[)ºzUE&4î}‘ÿÓñTÓ|Z¹–7-ˆ|ö³6Åò‡ƒÔ9!Æ9ÊúRøçáºÇ§þÔ?³üËæ=˧ˆ4ëUS…kÈ"¾ŽEãîùÛ×#?0nâ¾dÿ‚zøŠ=â·®®$Tg¾Ôt93üp꙯r}±Þ¿Mþ2$žý·üfñ'Ã[ø^ÞMQ¹¶ 'Þò•Ɉý"Œ>µäªH ×ë×ü?àn·àoÚ*Ëã]äže§Ä6-R%lnŽkEŽÚt tÆà’rÇ8Å~CFÞ¼VÕÊ4-°Ò•õjÜ.À¬rUðOãUbŒ›˜¹<úU¨âÂJ çw#ñ¦îgüºïP_ˆ5ÐѦŒI£Å,ˆ¬Ñ—Ì6F Úd'³žõû0~øØh’xrÚ(X¹‘•£¸Ãì[‘·õ¯Ã_ø$V§«¯í)s¦xzç½Ö4Æ)#íGXUòG@¤)©,}+ú5·“ã?°t˜²2w]ò¤×‘‹n3²;é%c›À^"’?.h¯aˆÄ(ŸËúc’=ø5R×àý¬L‡É¾Ï|jW ×Ô©W·Û¯Åµƒ ¦iÑñÉW$~xÍ\Iþ 5Îë‹ka¸m`¯¸qï\n¬msÊm¾YZ™å½ñä}˹H9ÄÙËrs]u‡Ã½2ãÈÞ/ض0Y\«0¯\qší›Pñ"\Úlwá0ù^=HïZ0êµÄêÄ`¯-¸à}j‰V“êTiÄã,~Å-ËyØõyäb;ÖºK_‡pÚn‘åýÜj¸?ÅÇ^L~µÚØ^É"ú3HãvyŽz×m§ÜC"“=³È`Ç9R=ù¬e6ËPW8K?HÔHÈdéÆY³ú­ušo…e@#lF!œeÈôÏAí]%«$¶ÄBŒÈ‡$€AÉïôõ®ÆÞ TÅñúzšòKâ߈4¹4ÍNU†Tew•ö²ïéŒ:âüu|ú§‹.œK'–ŒPîmüô;sÇå[º.ˆ¶Ö±ÍxëÁúãúÔÉ”‰<9¤&™d`w•Æ>÷?Ò»">ÊùÇï;F9óÇZƆ)àNë”BÙ É5¬¡¦ºHáŽL«ÆG|w®dÍ­c¹Òdð|R£ÜYM3‰»ô¯TÒbð&¡)…"– óa1ô݃œW™hú £"µÞŸtF@ :óõ¯QÓ¼=¦ÚªÉo%Ú¯L™7~cÍ ‚g`žð„ŒÑßi¶Ç=TV?QT®þ ü(¾–mDt-‚­eç×cô®«M»¶Ø!•œí·š9ãô®ž7±óv*§ ·Û¥56&„~"þÇß²_Åoˆià?‰¿ ´}F;K_¶Y<‘©ˆ H<µÁB8ÆxÇJñoˆß°·ü#àÕŽŸ«|AøC¢Xéú£¼PÞ >™?å”›It,)CzçŠûGâ´öÇï‡:‚„F×~ߢÉ9}¼y/Ë©x+Á¶¹9;ôY[ý‘|#®i“j>:»ÒõA$¿¹‹ÉÈXs”Þ¬Av#'Zö;öLø/nÐiúC‚q±¡Œ“‘ÆŸ¦3[:Ï«#Ù¥²>'Õ¾-ÿÁ|/oöë?øfù°~k? +·l®á®=«–OÚ×þ 9¥J‘ð†ÂRrUíü=eœwUrñ⾕ø‡à| ñ’ø6ÃᦑöÛ!yݸ³¢6ÙSÊ<“N¡å’ëŸmÎ/>éG ìàÃpî <þ¢» ïÌRzœ´¿·ßüûOÛý‰ðy¤PrDºF›ã¹ÎjÅ—üoö …VFø6-b¶!sÔÜø+ª·ñ¯ƒÖ úoÂK-‰€Béõ=ÉgSOZЇâÛØÆWJøošn$,`XÁéÀw8ϰ5~Í1sÅt"Ó?à¤ÿ²<ÑnÓþÀˆ2VEµ´|’9ä)9ú¡®ÖÛþ #ð9¬|íá½½¤L ñB«ÇlyGïŽkž·ø¡©Í›Ûïé™ÿžŸÙ a}äCÎ*Ô_µ{«‚Ú‚4Ù¥e*^;ÎÑêØ'õ“ç×ñՉѷíÕá™H¸Ñ¾i.àw,ÐîäpqäƒúŸ­hIûvk‰tÿØL9 ”ÞÌ cÚ¹%ñÇÅÄ&ãJðE¦Wäù- ¾äއØþ3xçöˆbE¯…,bÿE O§ƒQõ%ßñ®‰‡ííñ:vݧxKF¶ÚHg6Ìäc¶Ðñ8Íuº'íéñ>û˵ŸGÑĘáÒA'y<Ÿ­rßð—þÔ’J¦=O&/™ ÆëÛñ5¥oâßÛ Pc?O„‡Ì]߇ÊGæhx4R¬Ž£þ;ö‚iíâÓì%…\´~NšìYãÞÙ¬‹ïÛ_ö¯fÙ§hr¯ ¯—¥JK‰ñ#,‡öºÔá!åD£kb“ì•ã§ÝÞ­[x[ö±B7¸'Ìú“1 c€xú`ÒXT·aíbW·ý­?kÇÄ£]¨ôM6NHõ 7>£&·"ý¦ÿlU-áFPq‚–?(únÁ£³ð_í{§Û}“KYD[pUºb¥›¾Oüª_øC?k‘óM-š)?3y¬zýO?…/`»‹ÛË¢#ÚöÚÔ<1¨F;³Y©Î{85œ¿¿nk‰ÿ„jê×oF³)Ïû†lãêjY| û@侯®iБÁÌäœý>Õ±kàÏŠ²²ÄÞ+Óá ™cWÆ:çqýŠî/m.ÆÏÄ¿ø(.©r¢ÒÒH3Ò›A’}A”Ÿ§8‘sâø(µöѶ¶,rG5²7>™ ’=zaø}ã«‚Réò?2ìLû9ôÅi§€uèÑÓPñN›oÉämè?¼ ôüiªIuhÞçˆÍ¥ÿÁE']·ž7šÐž7=ݱlžøòÈ®ZûáüV"I>.Ïj­ÔGuO P+é["ÍÇt¡P;³ ô; }ªŠx[ÓFÆ_ÆQÜ`±føù«äˆœÏ›/þþß0¦ËŒñI*}ϵÝ÷÷*¸o§êj¤ÿ‚‹Jÿñ6øÃ¦$O´îK¬`eˆôȯ­"ð/ƒgÚŠîWºØ'SÐ`žÖ¦›À>wCâ+ÒùÇüyÄ€ãÜœÓ剡À|6OÚOÂzÓê?ø‹a®¬°¬,–Kwb§#ÍÞHoÀŽkA®ÍÿˆçÄ ËQ¶gc0e9ÏÈÀ‚}ò1õ®µ<)áø€í­Hö`Œ®îý¿­mÛéž ¶;Ž£vÒƒüH¹þU-hZ•ÎSZð$ÐÉwሓè³Køí’Ïí𣎙†ÚAóÖ©áŸÚnÏQ–+-yµDV\öp%›¸ÇVó³é¸××OaàÉ$iöï.~m›þuR[DÄ]_H‰—Ì›-žùÛúVð1‘òl:Ɖ!A«XkWr#q»p?ÚÜ þUxKÇlŸ7‡µ4ÏW–àÜdûßʾĊóáâæ4ºv¸Ù×óÎkb ÿ  —$cŽoü â·¼LÙñGü!¾7–@[DÕfÏìåã`¸ìr?Zì4ßk¾UÞ•¯Ç7Üÿ g$~Uõ\·^` _½AÆZì°þcP‹ EÛ5È…0>T”¶Aõ“IÉ Ÿ=ÉðóĦNÕÀÆ0ó¬€{}àJÏ›á>¿s…“IÔ'Î pw}ó?Zú6M'CŒo¶¾°9 7_|úªa©5¸òÒ¼`Œ‰"dà<çßš›\V>tO‚>2uó"ð­ê¯PÓ] Ç×÷¼} mÚ|ñÆÃÿË>>l›Ø±ÿ>M}+½4Ò¿‘ò©Q·j;ý>œÒ>½v‘;AÈ*CM¡&σ࿎$r¿ØVŽ2%¾Lgþš¸ßüFFûÛqóÜ·÷T þ•ïvÚô`·Ú!óÁ>ƒñ­KMs@¸4˜ó†"\õõÈÈü*GÌÏ ø3s>ki¹©wAú¥F>Y™Š_É¥7ª¬Nx÷ÏZú’ðã ’ÚËÁʻ䟯9«ÑèžCÇ¥[;w<¯æ‚‘¯þ hê@š|xቄãðÛY-ð>ÞPE¤šScâ21õÏ"¾Ç¸Ð´`“e²)í‚XgóÅq×:]ÜúŸ•käÁ9ùÈRÍÜcÓñ¥Ê€ù˜ü½Z8"Ñš¸ÿXcaø°çð¬ÓðsQˆ ¡“CCœ%Ñ ÷ÛšúsR×ü5¥]¾®@“PÙ†/7 ýyà÷¬IüYà+uIã@á³ÿ,sôê?:Zàñü2ÔÕ'Ô¼0­,¹˜¸ÏýóÏJÖ´ø{§DD²ê>©ÛŸmÃúW²Áã‡i i…¬l¼âH¶ƒŸ¨Åo/‹¼ !Ùo%› 1…éÆ)¦€ò{h4m5,†…$Š0|»2X/®s“è:âÅ/mü–²Ð§e q¥$ÈW±e8ïÖ½*ûÅñE>élLªÀÑǸeÆr=º á/{òwèJ˽oŽ·HÏŽõ¥‡8*ϧ°!Ãô¯Eµø¥5¼px¢íîÃì2ÅjÒ©?ÞÆ8úWGuâï†óšáÞ@8Ym eóÙrÏ©#ŠMjTfï¡ó ð¯Œm¦’âÓÄr[³dqf€¶{’¤ƒõÅO¥ÇñGžÞÂÇX•ãV"$TIÙÉëˆÊg’zdõ¯£ü4`ñÌ—:fƒiÍh¾}ÝÔ˜Öp¾óÉ¢¨è'µzg€ô Ýv¹ø?rÚv—.VãÆwqf{…Ñퟅ¨2 ¼¡¸®)Êï’’»:yœ}é½Ñ>ëÖÚÜZ/Åy›WÖ.q-·ƒô¸"ŽþXû=ôÉòZÛœ|Ù;ˆð~¿Ó~ÙýšÌü^’Òñl½‡tÅÛ¡é켂cÀ7Sùë(ÆFUEuÓü1ðÛB—Ãþ µ6Érå˫¹;Éq3eäsŽç À’úøâ<‡õ>•Ù„Êeí+êûf+5irQûÍ]NîÖ臸fÓÆ1ý=«œO³\¾Èâ$úžk¢þË´³·Þ°i3œgŠ…eŸU;´è¾HÆ7•}ô±óò“z¶d<6ÐäL±õÎ6úTQKqi<ô 1ƒõ®ª Êçߟ´0ç}øÔ“OfªB¨ @ã¦1Ú¥ÊÄ)³›OÔ|ä@˜ùw ¶Ò²®tèÃfîøƒÓj¦úõØO=›Ÿ5Ûi<ŽÞ‚°®¾ÊÊüá9sPØùÙÉÜÙxz%Ý& à3c˜¿É¬« )– øÝN>R™Éõàñ]~ m£™!ÜN ™Ïô¬YdÓepny>OøÐ˜syœkÚ‡›h8 vì伓ÄßìtS%¶ƒkõÔ+NÿꑇøÈü«èü-ñ޼5sáÿ¬zuÜÅDž~Q,üÈJœÃ¸¯¾ý•|oá.ã[ñ…ý¥¦Ÿf¾d¾Cn‘·¡P°ÀbxkX²âÏ2ðf?Œuyµ-i¾Ñºƒ)?u™‡È009À¯s–Î ‰Ä6ð 8ÇaYºChº]¢iz\?gµNœã’OROsR_FK6éqÔU¥ØnM= Aâ[T9ÎÁ÷"¤Ð]¾X–ô~ÞÜVsëZ;#,%£-œ•ù¿ÎkMuÒ3!9½ ý1éíRòîtSøgH‰~u#91~}k2O és‚ѾÕQœ“ŸjËÎdÚÀ.@ ŒïS¶¢óa€\÷ëì)¸ß {GÜCà»HÕ^Þëä|Ýþ•FOÛNYc÷gœãŒuæ® L«n ·€¼’Gó©âÔV«F|ÒäCö²îbKðóUQæ&@+xöëT'ðMúœÜÚ³õ<ä{ð¯Vµ¿½€ï‰þéäy5Úi·‰rŸ¼aÙä€>•)+êTkÉu>h—DÔÕ‹y$ÔmÿëTu‹uÛ“F½”;?\WØQA'ïeAÛ¨çëT%Ó㸕cxãt=r þ•¥£F‘ÅK«>4º·7Šc¼Tœ¿ 'Eï¸×3iàï é×}7J´°”þmŒb—'¾ûsÉõÍ}¦š/†.µìíRÚ ’| ¨Æ è}zÓu|3Ó5 }èÃu{u"Ä#É]»Îqêx¬eƒ Õœ´q²NüÇͺÄ‹Lx;Ç)ÒÈU‹Z¹–%ÿ¶W 4†ÜW¦øWöÃý«ü6É Ç‹íüM0x‡L·œ°ôYm¬‰ŸïÿJÓÖþÛê(ŸÂ1Z¼(#k‰§·•ŠÅ ’Xç‘éë_!x«XÒ|3ysu<ÛtÍ=$’Wl3¢]ÎN{¼ŒnC•9J¥%oCÕÂæUœ—,™û;û(þÓºßíáïÝø›ÃÖ¾Õ¼%¯&Nx™MÞOSï…ßþ3|Aðm—Å߇?³ƒ©è÷VI}§¢_Ûéío’CJŽP£;Éí]·ÃŸø(/Åoƒ²=®}ñ{ál–JžßT¸ñ¤j 7Ëqu¯ê—CM·y„sH³”@|·8v3ûÆ ªÞœQSF§ñ Ÿ¨G:oÝ•™÷gìíÿóø·©j0é£Å> ø›’ŽÃWŽ ë8Pwâp. šOAäÄ8<×íÀ¿ø,×ìµâË«?üp}Cáµrª©ŠÔG¥É#»bÕb/fÅŽ )‘Y‡ðõÇò×ñ¯öñ÷íçëzçG…4]'G¹wÓlŒkÔ/æaæÌ¨Â#XË“ÂùOžŒzWçGˆ|ûdþË'Óm~ÿjiÞÕR+o+T¸¦#™<²Ž’!1E»åfdLzãóøÎÁVo’<¯Èôhgµv›¹þ™3øCðÃã?…%:…µî›­ÀÁØ,wv‘J0D‰óE*²žHçëù”ý®àß/ø?_Õ~+þÈÒÅà]nöÆòÒ=3P’Yü<ív§q·œžÁÉ *·™ 5øû2ÁQþ&þÇ” §­÷Áÿ´1ÅLJjž Ô<—ufŸGrQcy %­)›ƒ»ýYþÉ¿ð]?ƒß¼/n¿µ¦¡i÷$Åÿ †1Ô|-.Iâèö1°¹ar†$(™xRʳµóá'ÍÝþG¡^¥Uf~|ý‰¼yûhzìÉñc⦗5׋æäx^Òc6ž º+‡¹Móy€î>DQ¡ ä3Ç[_ÿ‚kÅû}ßi-ðŸí!§êÞ!ð.¤–:f˜’[j–šy¶™Xù‘Ç$S •K† Ž£Šþ¿|Mû;xÇhÿþ M¦\MÉq¥\¯—qÅr˜“ì·)¸¢Êœ6ÂTúWò9ûTÁ>.ÿà6Ÿk¯ƒþÖ~#üC¿’òÛG{‹Tx6ßS.Òj jÒ\Ér:«¤jA}£{W·•q$+5K¹jlqâ²ö›•=Qü®~Ú±Ø/í/ñ"eº…öEtÚž…ÿâׯæÔ|=âoxjÐíÑ´õ¼$Ëy¬Iô¯°¥YDñªR¾Œñï ØÝxûÅ–žñ®,ôÛIR{»§qç4a²Rêò0UçƒÍ~·h²ÿÁFÆ-SFø…®x{ûARá­`¶I7”à¹àdó×8=+Ëÿg¿Š?ðNß„Ö0ÙxEõ}CS»aºþëMšâ}Ê6€ǵ?í˜w¯ÐßÝ|!ñÔRð½ö¨ ÚÁeµ(äžÙuçž+ÏÇV”´GN’†§ÍÉû!ü8Ü~Ïñ«ç^HžËo'¾L€Ÿ¥ni±Î“jÿlÐþ2iªÛH`ð`OðŸ3#ߌ×Úà®™¬“Iñ “BÝÛ…9ì{ ûqU5?Ù¯ÆßdZÞéšÈÉo*HdoœàœöÍy/õ:¹õ>h²ýœ¼Ufè¶>:Ñõ`Ð&»h°×i`y=óïW5߀´!g@¸Òµl7ˆÍư›Up#M¤õ â½£þ‹â¶ Ž–úLi·¼ddöá‰ü«û*üS·Š+‹9Ö6c‹Wé‘Ï>¸â³å‘±ò†³ðgöº„&¥§xKPÔ&Œ"„Óî­åBW†$<ènÕͯ‚i›[i4_x3]´±+»Èñ3+u#1Hü®+꫃ô–Sý‡¨A°àI{ç¶@ø‰`@¸ºÔíYAùgøïO¥;¾ |!«k*ÐÖ-;Ä~­¥Û±$ lnÌUìv¡ýz×3}­jÓê‚M^æ°DC,l =T•ÇQî8¯Ö?üGøàë_³Ç®6ž ïxÊA÷ùÏ=»×©xcãgŠïõøà¸m:úÙþS:„– òq¸îjGØj,üGÖ'²ñeòßøºo·\áDnYcÜ‹÷CgŒ T:ƒì-5Hµ‹;yžuʆYDƒËl“Âä z×ô!¥|JÖ.VK½[KÐî Rc%ôä,xz!´k@èGBÍö•v×<¢ÌÞosó|ñ6ÊD†[XEª6\É"Ÿ—¾Ò¤õ÷«—ÚõŽ™¦ÃŠªFO•Ý»žõ÷þ¡ÿçøç¦’þø—àýR(Ðâ9|ûEÉìTyû¿1Šó¨àžŸµ÷‡>Ðúað>°²Æ>T×&ÈËÛg邵a*S1xlj¯¼A¦MoUn2:{Šíþürøµð§Ãú—Ã? ÜÙÜxÆv‹k«ØßB'8 O«)ŽuÇrã¶y¯¥õ؇ö·Ö"óuïè× iúôN\Éæ(ËÀ“\§‹dÚƒL¶°ÓtŸ„zÔ¶p8½œö·2Uç“2œzç“ØËÙÍ;õ*Ðô¢ðÝÍÝû‹åÆy€)ùŠ÷Ú{úÓ/ç¾½¸Y彎â#æo¼ÞßZén~ü`²T¹ñ¯¨@Êzd¥]O|Æ\CX2Ãã=R8<â (àùVÞ÷D¼^‡/÷'v+'J§TcõYö(Ï®YYj s¡Ú9pÙ\œ—uÇÁ®ÛHð/‰µÛmNÀ…¶’:WaÌ@ç©=[Ú¼êóþ$ÐÞÞøÁ{c>µ_?öŒ¶î¨I_“"£¨\gc5¦ž5¼ÐtË˵ØEĪ»™$a 0Þä=zTJEºÕ§Øö¸¾I©¾©ªU›zá·uïÜæ¬]ü4ð~Œ3q7Úå|²O|Ã#žœàW€?‹õKR‚½‰ã•YcX_q’FÀoP=ó]&‘áß]\I ó›KèÐ0‡Ì\“Óœcó¬¹gØ>­>ǪÃ{áÏkºn³e\UX å̬>`íÓž*âÏįéÞ%½Ôt9£º¸’O—níªy?)'“õ®wLð_Žb¸™õ{Æ…‘Ky{²Ä!çü«zÔÙZÞ]jŽ¥ö69q,ÌH\­‘éÍLœ–è‰R”zÏñ«âUç‰ÃP·w±¹AŸ.5Œ†<0ãºuÏB+Ð5OˆZdw1ù’Æè± *Çfç=[?^•”×¾ñ-ü0ió+«)+tq¶7ƒœ «*;øz“U×b;¤_.F/˜ÐnÀŽ9ëŠçŸ=ö,DðÏŽŸÄ’O7š€ÂU21„UäŸS×.­ñF¶oqp9ÆÜ[Žkš¼ð‚´k)e[–†)—òìÜžÇ žNx®;þ .–è×Jy7.¿0Î3CŒãŠÅ©‹–G«i¿4m1âZ­¾`CŽHãwNMtú/mƒK5娺2£.…zGZðÛ-*š=ZxZh×pRª¶ßNœŽõxøÜMm¶ ?³¼lÀpr@ôëïK’]Pr³ÕõYiÚtH–ëæ+£¾ÔÊ—ä€z×Þº­â‹á»Û­:öÑœ$ždk·pnœŽ2}|õ©Ùjék­Ü¬K*ñ¿ªŽ¸ÇÎ;ú×ÚE¾³¦›«»ùÃ!#ËÛˆÆÞ€}3ëD¢»¨ú¢ËãTZn².-•’ ÀÜ~^˜À8çÐVöñ{U®u(áBX‘’YË3¼uÒ¾Iºð=âßGæj èÙ8Ï$g9É®^Í®´}B%Ö¯'òÊ‚ärúÖN(jLý?´; g‹ÊyØTÌ¡Uqßßžr +|J±Ö-Ùìá•&Ûè Yõ&¾3šçXI#[šSŒ¨è¬à ñúU]#XñO™åÜYH²lÆÖ$ãiÁà`f£Ùϵÿá`=²E ä‘‘"` MØ}æÇLÖUÞ¿s¯k‰< ~iÚ» 2Žûýkæù5 vk n/ÜZª!m§å ¿©WDñ¬ºlP[DVåŠÈàÉæéÓƒ†éG'‘:ŸGk^#ñ€NЬqm Ì’rÛ}ºþUËizÇĆß2ÝG6ãÂÿVÓ$õé\TÞ1–ñ~Ö±G¶EÁØÄ†ç‘“žõf}–DÓ˜quò/…\F2:ûÖ|š…ÙÞj:¿no£³´…ƒÜ&î²ú)=GJn—ñ ÅÑ]*á—‘¤bT<çŸÊ¼=¾3ÅcªˆK7•$ÞP$ˆÚì9µz_irJ÷r7“°`€Á[¿ã¿ÓñªPk`Lö™¾%xš!%ÒÇnÑG'–X©>»}ã5Øèïõ‚Ö[Pmä1U<ýÜuú_1ÙxÑ/,e²²¹_2GÏ_=É¢ÓÄ>5‰]’ò8Ñ÷Žp»N~^ƒVwÔ™ô¯ü&jÝO[ÊŽˆÌÌ õõ5²Þ1ð‚N5éï.®&DXâN€gŒ(ì=Oñ¦¥®köò@onÄ’ò8lÇÇcŠŠßÅ:Ëå¡ €B¸–?©ü)´×©.çß–¿t‹—¹…äû-Ê Q,Çå^Üb°üKãS:\š§Úmn¤G9P9É÷¯dÕu‰-¥¹»Û,žX,H,7ž8k˜›Ä+ŽûÉrÀÆÊ®¡ËíÐb®2š5U°âñ'Ãé¦][¨ûYXüÊ1޵@úêÉmpW¶Ó“¥|‰iªêòj¯z‘4r³ŽÿìúZÕ»¿×cSwg;“ï€r¤ûojÑÕ¨–ŒÙõ6 ~GƒMÓ”)1V_éÏzDZÿ„RâåN³dDäœn9ëŒu¯›çÖõßÏ;LÃËàœ…mèúäv¶—Zœ‰Xâ“ yûÃÞˆâ«-™Ñž­rß!¸†y,êa!g$ÊOLôª¶×^½–BlÌR|ø¶Î=pqù×ñ{hßkJT1gËcü⟠wâÑž$ Ù×®k¢8úýÁMž½b<¢Úÿ¡Ì9Ï*X ŽƒÒ³oµËHíƒ[Û-ÞÒ|½Í·o©õ¯8E²³‘Ñ•Y¸m¡±òžãÖ¬Ú^ÚçŠä,Ì\rr«·¹î}»f¯ûB±|Ìõ«i‘G%¸`óâ)†à6ÿtç¥}áÏÚËÃÖA"С¸™× ëÇ·þÞ¾ †÷Ob7—jó–ùYÿåžî:×UkâÛ{íoHm Y$ qè^+¦nåªö?Kì¿ko èÖbì8ZWŒó$ë»Ì>ªªåœ×'íâ `ùšžª–dð–@lŸ|æ¿85/‰:RÊšš£2 (¤p:j’߯zíÌ¿nºig²g!BŽy9úqZÇ0ªZÅ4}ýŲMÏ‹¯ 1¹\nÑ,¨Ã‚FÐ;‚z×=oâø‰ZèÙÙꪌd¸isg ËmëÍ|S¦|Fi|Û{}>6»‰%íuÇãZ>ñ$š]¥Äv7ÈO"<%ãŽGZÞ¤ãÓRž.ëSëÝjÃà4l£Å^ ´E™7|–êKŸ4œØéŽÕͶŸû&M&õðZ!b&*†ý£Ö¾|Ô>!êK:A.«<³òñ:Ȥ+žÆ=+óâN¿k+ ëÆ)c%TnüqZÿmϱ?XGØ:=çÂ{k„²ð½ƒÚÛÈOÈSdcßë]}½§ÃûˆÛs[#°ùNü¶sÜWÀ·>?×´ÃMªùQã n†?Cž´ø¾"ë—1-É™Õàa—W©éùâ®9ÛêƒëûÖ_xFåÞêï[kRF‡cp¿íd ãÚ²¤ð^›,O2xÊîÔ c¹ñ“»Ó·JøJûÇénZHï!xú‘dúþ~_‹û,ÎØÂðáa¶­ký°¿”¯¬ö>Ó?4iË üc¬9r*¥IQëSXü:ð”ûØxÃ_UB9[½?@kâÙ~6ÞB«suu FXˆåsüëSOý t+) ¼S *sTõ<ž; ?µ¢ú WgÞoÂ?ë?—ã_Ä~ëbü¹ÏQÕqùñ[·À xNÊK]øâ8®Àeæ68¢ F$ŸP+âÛoÚƒLÑôÉ¢ÓTï—,€€IXV_†h-#É[­UZ)Æl a׌~ƒKûFœ´fËcꦇá Ï> ñ^°GûPŠ2GP£b“é’>•×·Å uðôþðRYhq4d ©“̺SŸ¾$“*[x>ÕócþÐ_dšKŸ.´ ¶‚XrÁ'ð® ÿãçƒ&2Øßiò›œ¡RÿhœVðÆQE|7ñ6òe„ǤÂí"ÎB’?,þ5Áørc§ëÄI•ûDLÊzg?Bkµ;£3õ>]jÛâoìi ü@²u’]êÞâ\™-æo)Ô{…#>¸¯2×ímb™n#KÙ‹¿JÒý‰­áñÂ| ºs!¶±¼·\qóF¢hd^¿0'Œq‘XŸh“Sðü:Äx3²ÆJ°ÁçÓ½bÈh“U·M[ᦕ,Oƒ¥^I€U&äsõ«ÿ.Ä~8¹Ñ&—R´–(Ôãæ—¯ô«¾€êZˆü3y5¯Úc¨’.F=x¯1ðn¸º?‰tívC¶+yãrÝ~\á¿CRöî}FšˆEãYÍy¼1)V`ܰ<üØÀ>çë[öƒow³ù·|nv¹˜²ú”a!\ßõCIñv¥§Û8XÉYT7÷\ãÚ¸{^5ÉžâbI Ð`aí\¬ë:߉‹e¬øjM6+[xš97ÅXÀÃdõÀ\œÒÁ9$ƒâ?ƒ¾'þÉ“k½oKÕ.l<wÛs ¦A]ûÈÂàî‘\ºÌuy¡Ž7c ûÄ}³Ø×)û ø’„ÿ·¿†5=ru´°Ô‰° …9`NŽF:bº)3)­OÐÚRÂ?Úö ðÏk­:â;yâmÙ"ù2†ôÃtëäø$_®¼5ã¿x{UÙ0Òõ{ß]Å.0ÐÝ£§èÅF:ñ_¦ž ðŽŸ7†þ0þÏad'NÖõÌ[¾`†îV»·Ú0Dr¡\rƒ_€ÿ|V|ûCx§B» k%ú[jvÂ!¹|øY7ŽKoÈ\H>µ£wZµ>úñÚkÿ|cªü>¼§ÒµËHóò LDnGrT¯b¼;â­ß<3ymspÝÄÖ-&11vã^+íïÛ{NþЋñH‡—ã] Jñ|ä™ RÇ#÷JÇÝ«äýr 4im"A<Ñʬœà¡Ýúôö®:~ëÔÖZGüëQ²×!‹á'ˆ]^ßR´Öü?y ÈÞb4¨¬§¸`9õòßìÃâƒ_µö©ái³Ãwk¨ymÆæ·Ap3Žw ß^}êÏìEã+„?µF¯fÄÀ–º½¶¦"'“ÖcŽÜ¸#·ÛþØÞ _‡ðRX¯<Þ£~† F[êÞlA['+çy„sÔ‘]«vBì~¾øÃßڟ³ïü-ó.<+¨Ü]B£øY ¸€¯¢íf>¸é_ÚjÖæÌAv‡1ŽŸw§ò¯ÓŸ€’Çã[‹Í/P;‹ü7e)^ ÜÂ’C9 u<¡ý ~[E¥]é—½%n,§– ¾]¸hœ©ãêÐ×<•ÂFÌ¿iË¥xŸ_°*ÝHÑîã*z)ÇO^+íÿ€¦ßVOøJ"Ên­´Ýn&ÏO$DHÏR _7Ôz×ÌŸ4™4ωSɷ帆;ŒÜm#ÿñ¯Zý”u­þ)øbÚýËG¬Zß钃ЕS4@óýÐEtô!ìzUÓíÕmdp6K…-ŽÞ‡é\çÄ k/ø­xZÎ&¸ºÒµuš8â]ÌË> }EvÞ#·’ Û,ïŠSLÿ…uš¢=U´·s^ÙÃtpqó‚0ljù÷ðšÃÅÿ7êZÆ›}¥>“¬éÌr\[¼JEœêd ÿtåÐô¯ÜOÛ®eÓ,¾üeŠ?3þßµ«ÍÿNšÅ¾bÿwÍu€€OJøgÇÖ–Ú§…îôívì2˜ä*B©ó)²kî}CE×ÿi¿ø'¾• øWYñ$¶zÜ6–„4—Wš5Är8\¸E2D„d°‘“IÉ-Ê?)þ"xQðoí¡ñ+G»¶¸mÅs\%àCäËÜ(Çù…Ç<ñœWwðïÄì7áÏj—Û]xÅúI’PË„Í×ÙdcéòJOêAWì¿ì!ãÏ:¦Ÿâˆið”ÑÙB’Z~ê[Ã$YÚù@`~lž:÷߆±ÿì‘û6[ÜèÉoçKzÞtðjs}²)'ß¼Éäȸ’03Qí¢W+??þ8ü$×i‚?þË`÷ZŠ´ÓÝÏåÅ ŒÑ(xÈ_øræÿUÔ`FAqi“bË7cî%r+övÂ= Âßæø³ð‚M;µÓÖÄX\[k1W,$uùP:äíÛŸ~ÕÞk~5ø¿ñ&ÞÞOêÉ6¢ÀG1œäTQüýk­Ûc¥P?›ßüøÃ¨Ù]Ø[x+[º“Ê{ia#R22Ižy5÷¿ìmûQþÒ?³ÇìÕ¡þÏ^"øW¯êÏáÃwäØ@~Ïq+J‘:ÍrœÇ»hl0Çã_¦‘ø#U¾uóA0 C*•Èô Öž|b¤É*@%C‚V‘ŽÀ’j>¸º‡°Lüÿ‚†|4ý¯ÿoýWÂwÞøSÿÌ^¶¾¶+>¯hÏ;ÝˤíGhÓ>ÎwxÁüÙ›þ 3ûz¤¥bø{<ª~ëŨÙÝ:f`ï ¢¿²Ë_En‚ÞÜB „A´Ÿ¡ÀÏ©«vþ»‘¾L£ÎÅÉ¢8çØ>®ã—Oÿ‚GÁ@g!Ûá¼é´™õ N1ì“7éþÿ‚/þÝzÜ qwáÄ:•ÿL׬á`p¡Éˆ¯ë~ßÂÍ7Ÿ+žƒÏ¿Ò•|+¡—IÚß{7W’0Áõïš%˜+l_°Gá7ì)ÿèý§f¿ÚJø¥ñ|;§èö6w÷Ia©‹›–I#*UbÚw0]øaœLWítÜ+¶ùUƒ Œ®Ï?çß[iö3CJû C=‡éM“IF¸1GsŽ9¯>¶%ÍÝ›Â<ªÇšˆïK.3/€AôúSàƒP•üÍÎÛ7/Ê0ãÞ½:ÏN€Çæ"« Ý äÛ¡â­CmoèÝ#Ùxþºå”ÍÁAiªyIäáBa9õÉ­ËHµâHçˆ ÊsÇ¡5Ø&¦ê)ºMꤕU=ú ÕƒÃúl$‘“· ¸b2Z‡"¢™ÎÚÅºÑœÃæ¾AÎ?>Õ¼¶Iu&n!’lq¸0‘ÎN+v;-:±Â¤;täÏÓ5µ™ikm-Ì·j3»¿8 9úR[—Êq𯻡xNÕ5Oä ò"_œ§]ª:ÿZùÛÅ^<ñ‹n|›IÙtæ}â0‚&ƒ×’;ä×7â=^ø‚y^æGˆË•Þs·žsÆÜt­oø{r¬ØÉdRpƒ‚sÓŠ¦ì>Rþ¤[Z¹‘9LÉÇ|ÿ…w[#ß1åX`³ÇªR<+‚B8ÂuaÓ9­‹híèûw¿ãô®i6l’EË?³¬cyÀÈöÇÐXÜÛé·JöÈOïOåX¨!Y2G_— sŽÝëRÚ+ ®|˜fEr¹ù²1ªæn7=+DñÝÜ1¬syòJ'Êp;QÈ>µéú¾Ú¬[Å)V-€ë±‡× Wà¿hwJn./±#…´Ô©÷#5í1xucHÑ'k4ÎÎΩÏZÌÓNƒ¢”½ÊÀà ËŒ·PGJÚˆI³ËœaÀçÜTRhZbLê}û€g8¦9­8­l£µcç;HÃ9+Áô®#å?Ú'D‚çÅ |FeHއâĸ]ã;˜ÛÈW°'~2xÅ}I Xéðj­iu‰v3*FÙ w1‘Ï~õà?´^‘¤ÞøM¿Ôu±6“ªE{ àbvFˆœŒRH#Ò½ÓJº3kâàm)ÜÙÉÜsýmÇ·6:n…âIôýJY[I,r*v¶äöŽ+×ôÝ;M³‰õm.ú#(`’»3[ürÒí,®t߈etÍIn-"¹·ZµBÑÊVE!Zæ*X ‰GS]øz—÷NJð²¹Ç_ïœ]Z[Z\\/ͶM2ÝA>áŽ1ô®ÊÓö¥Ó4h–5±ÓmÂÏ:È?þcŸ¥yˆøïm$‰kk¤Û³tUÐ-Icž™#èp+©ãçövÃi6ÊÂÍ[óecÖ»”mÐàgAí}¦Ï.ɦ¾祃''¨)»Ÿjë¡ý© ¾n§¯4x«éÖŠ8îQŸ¡ÍqþÔ“;¤ s??ê´‹@9èvŠ1õ×CcûAxŠHÙíRb3Ë>ÉNOÕ0 ,»žïö¥½µ“|ç‰<(KKu>¤!ô¨m¿hïÞÃæYÞøˆ+>7b>¼ç;}jàý¡üE8PúÔxÇÊÖˆ‡Â2xúóZpþв ͩ߼Ÿ&ÚÚ02z‚SškÐ8|{ñx!-jð7 柗 ýáÆjYþ4xòà3ÛhZ´Ïw;±AÆ~‚¶.ÿiÍbÒà Õ¯\78h` þv¥f¿í)­Þæ[yïäUÆ1`]Û)ü€ÆŠ_.pSBž<µ¤w^×´í¼mñÍÛ ¥ k¬1=z†¯ûCx¶U>YÔçqÓbîöâ:„üoø‰x|±£jsÐ[¿¾ÁK”¨–§Öþ<\¦N‰ d‚ Îdäûëôªh´ Î%I€¦>ì² $ñZöÞ?ø©I›_ êeÛåÌñ˜Àú7ô®Õ/¾4]*Ío¢±ÀhmãדÇÖ³wîU=´5ÔŠÑh:\››+½Žzçæ#éW-þþÑQJi:\2òYÙ€éÕÎ×Ó]Åñ´¯™{àë‰÷Kß,žý7ŒŸ§ç\ü¶‚—ŸÁ×p«[Ì»g$çãoþ½÷ Éà¿Ú1 ¶Š'1D›}ò€äT àO“HMέ`¸î[®1ŽÇãÖ¨‰¾!Æ|»O 8RFA¹”Oª–~­}?âì‹çZhZu²ç9•Áÿ¾Nx£^ᩚ~üNÔ”Éÿ lV“)9ÛÔžpY·_|e¥@5xáͼi¦†(ÛÊø¶à~•ÒÜi߯mßIµÑйæF‰ö®:õ šán4ÚRÞ`llm9>tVªK`['òÍ-CS£µø}ö»U¾´ñ•ÕäS¶DxÓzŽä ·_jSðÿÂö‡uﯢ,pÄ;œ9ä!Èí\ÒøKö°¼µ[Gmo9<¼Ð$m÷¶‘Ÿ¯JуÃ_¶l2?Û4û+芀¦ ªPýB…>ôX´t¶þø=e»ñÅû²Ïû©¦È„(ü±W¡Ñ¾G…}gT½p?tñ Èí â¼ËPÓÿiéU¾Ó Z^ÆùšòÜÈŠ=Wo-Žäp+J/þÐ2Z¥Æ§®i†Àdxôó`z™‰ÁìI¢Ä¹•öO‚Švâòp:œþ|þ•jÒãá8SåXp7 u稯.o_ç¸c¯ëÂÑ—«yÇŸAóNk&o‡V3µ–­âøÇ PN~‡ëBLžcè%¼ð„„Ce¥Â@û¢NI?ðb¿HÉ'†læ+‘»kö©â¼çÂm &ûIJ˜Y¾\Ü ‚’Gä)ðÃðnÍ ±ø–ó8ÎÄ»O·îÀéWÌÈ>›°Ô£t×LÓ`ùxV ~8®ž GWŽ2E–œŒú±&¾H‡Å?!~´Òž‹'òÅkÁã‚¡ñou,çި㹠\À}FuJ5W·–’μf­K©FùIuŽ09Ç¿ø×ËŸð±~ Àí&˹ñµ¤W~ œþ†H~ÒÂHu_&Ž7 c§ýêËÛB¨c»ÔÎT´å‡×šðkO¾ -4…#‘ÄØãØ‘Çå[?ð»ì6å4˜Ó'æÇ¡8þw&G°2è²Á-²\ùdrÊŠWkõ÷Ís²<– Ïn^P§?p°lu¯>/ä; Šè»I9÷Èü«bÛ⦰JÈ]²¬1¢ŸçÔS$Ú‹]×dã¶FIò™0)·-Öcîí§6ÆJ~cš|F×n#,øbÃøãU8ú)ªŸð“ß]:6EcÁa‚3Ûéô i“ãB{bšUÄ{‘q'˜œçß5§qâ${ä±¶œ:#9ü¹ë\"ëWlQ/ár'äãêxªÛà_™¯È`AÜfÈ®(*èõ{o½ó,ºT€6 UnÞÈ8çÒ¯Ûh:Vª§²Û3ÔüŠ}ÏÀW’ÛM¬;fÇV¶œmáóþò‘úæ®OÅ–H·×,À—·•œô*G¨g®é74p5¬ò`gÎ*@øôªð£G¹ùîlm”†Œ¦Bח\üFÓ¬%[m^@&~v<$ž„·JÔÓþ!é×)¾ÝÖDÇÞµÿEcøÑkèñø-íHòÃb\LYÎ{ãÒ±†|_o8ó¼I-ÄiÀÔ‚Àž#üé¶~3ÒfgYÕ›ïåN}9ïø×Wkâm*tU»ž=¸ÀS‚ùõ¤]Üry;Iåü¡²~f>ƒ=©ªÓZiÈö·Æd9PS€yÍvRj~Úf€9^ÝÁª7GÂú„ñËkp’ ª·Þ?à_5¤VìµNOdp3xoÁ’¦æ·•¤Èû—cŽø'·žøyá].xâââÖÎòS¬ ›‹ÝB^ÐÙÂ2ÎÇûÿuG'šê5ûÝ;áî¶<%¢Ûø¿Pk]ÎØ¬ãaÄ×®3äÀ½pØgè ×MáO§‡5³ã¿ÞøÊâ/&]Q—d6±|‹(ºCî@ø‰¤¨Ô®ùi躲*Õ…z›ö1t‡2ø†ÂÞãâ~›¢ZIçÙx>Õó~«6¥"à\ÏßËÿT‡ûÄ^µs®Þ_Ÿ&5;™v)t ©¼2j L¤íìEhE6qá@ÔžkØÃa)У¿sÃÅcgYݽ;ZiòÚáÕÏÐà~y«W—¶/žc¨9öõ§Øéºˆ.~˦§ ÷ݾêƒê¥zf¢é~VkoÞÜ–Lϧ ®®dq9#‡µðuË”¿×ãiOðܯo¶§i­£Ksg±W R ­­bþgQ# óôJÖIR?6g.ØÉ¨æ2“V(Ü™ 1HÊœu'å®qâ.¸ {[w¢c3éXwËÀ¥V"w~_ýz9Œ÷جÚS¯Ë+ôéU—AŸÌ;r¿0àç5uâG2HÂ3ŒFk-|AsÚ“œpzÒr™Ó\i× ºŒtÉõ÷ª{/-çI"£©2qƒëXgÆ.©åÜ£J0ˆ3ßµd·´ùy±J@ã±úÔÜ£cTŠóV‚çO½»“7Ëæ$‡r0÷ó§ŠtøTÁeây%º FÍ×Db8ܨÇÿ,õ¯kŸÄ:,M<‚Hâ·BònQª2NkǼ ,ÿ¼wsã}°²`!‰ÏÇú´ô*ƒæaКÖ/CX½WT¼Ô´ùIò%ƒËÈ>bð¬íMä¼ÿ7p·'Ôñ_ljÁ>f²dõ :ý )¼¶à«‚3ŒƒùU:…sA=ÕÙÛeò×dlGò¨¾Ô##‚Ò0ìG­}§%ì‰I[ŽƒÛð¯—þ/ê–/‰l‘D–ÐpU@ß+s“ܸ戻±œŸžKáÛ“ÇYˆî · ùœu¢Àz‡‹®,¤j‰aè:ï\ú…v«â ¿³Å%¢‰¢=sî:ƒ^3G! åò=ŽÏZÖÓµ+­8ù–råR@£ü(³¥ÖæÕu Z_²È\¨Mʧ?^+wMñÞ¯omöOøjßTe äßmqÓ&5;±ýî­/øö=GJá$\‚Ÿw8þîx?JëEÒêà›W8 :n„u‚ÇüKø¡â7Â×·z‘û¤¡b‚Ò"B¼¯Âb70'€qȯ›f†ðüiøý ø+YV›HÒ1â=hgp{[)à ‡Òâçb•<:,€‚3Z´çŠZûÅÚ,Ø4zXûEÇ8Sq' »ýÄü‰¯¹àžßáøqñ‹Q‹f§ñT»˜|é¢Y–K'‘)i.°z}«áøïϲjŠÓYÏF!ó€ˆy8Ê0$úæ¼Ãâ„|U«øÚ×ÃÞ;—Ck;™Zàk:b´÷’ÑKyÒŸ˜Éè@b†M((¤‘ùDêóIÉŸœÞøgûAÉâ»Û¿?j‹ÂÚ4¶PO¨B‹½>М£ŒnÁûÒ  ÷&½³Æž†þ]KUø}ªiºèÒ’H–ßT‹Ë}NÂe!à•™v‰#8åzàtɯª> |uð'Âj>†ýõíGÅvöv‘jæp¶ñ\ÄYC̲áL1‚(ÎÐ0sœ×œüuø™ð†÷Á÷~øq;ê~%šò«ø–âQpmØ…’À ½IŽïV‰}ÏË_~Ï ñ_„ôÏü ñ4Ö~ KU¶‹rî-Ue‹BÀI•nA cå¯yýœþ4èþ ø•­øÏöÂðññþ¶|˜tíx[ÀÚ†—$*TÆð²F†?;4jÒê¤r#ñoÄ/ èÿíþ%ü'Õ~ӬޤT±L—‘¯–Q—só˜õUOx×HŠÊ_xçCÐÓ½½ÔAÚD”–·1fq‚Œ ¸ Ôüø¥ñJøƒgã߈kqgªÚ]­åÔà™'.Äo œˆÁÈý)¾Ó±÷ÿì]ÿMý£?cOXz ¿…³êî&’ÏP¶œx#Z‘˜³‹‹'Ütë†;óu`È Ÿ2hܵý¢þÉ?ðQÙ¯öó·±ø_ã8Á_¤µ3ä]Êí ƒ÷“i:‚¡nˆÏ˜ŠÊ&Š6;køêøûQ~Ì߬dð‡Å=j_Z_¼Lm.ÒH¬“‰U’TwÒ¿$n>-Áð ý->Ý]ë¿®/^ê×DÖšKY쮡´SØ\.&·œž)¡ÄŠyl¹Èñ³’†)s|2îK˜ÎÏTn·_ü÷ösñwŠnþ2éþ Ò¦Õ.Ë5ÅÂAäÛݱ9ÍÌqác›®%UÃ+ò7þ‹ö<ðî¡&¨x6×J¾±vŽhd· Ëõ;OÊz†kï¿ø&/ü¡ø—Ã6ß¿k]ZOè*A7Š®’4Ö4hö·-#U[‹\ãþ&VŠUæxÑc’Zýªý¦¿c/†ß¼?oñ3áÊAu$%͵͛‡YánAIñ·r@ìEx4±X¼º~Ï+ã=:”éâ5=$3Þøû2øSd^M?Lhº$qüØ=sÆ9®Ý<ðgMT †ÉTˆã`¿?¥{:þ ×§ð߉üw;D~Y-ÙH3Œ¦ìdýpk7þ/X7•?µ,€F&†&<{©5ôñQ­h;ždéÊ–JÇ‹\'ìÿlK]êú‡w™2ãýÎsP™~³/Øt«%Ƴ,Àúç9¯|²ñW€Yq„%¶ÛÈ[Æ}Îjôš÷ÃÙ#x$ÑãAÔƒ yÁçÿ®«®Ä_SÃ-üSàX&éÐÄ…˜|þSG³ê»±üëPx¾/aÎ ç ¤¨ìsÒ»ûOá+1ÙM$F–ñ€yàî=>‚±u oÚ²™®áÔ×å ^•?1V­ØÓ˜ÄÓüo êN×¶‘›†*6©^¹ž*]Sâ§a.ËMB+à¼3CiGâÀƒøUݳêÞ.ùõbÜÞ.1Ç$„#zÈð7ìí¡‡ÄÚsÁ[õds‰IÅ>„9Ÿ4_|e×íuŧŸŒ·ZU»®pÀ? ó|o´ñ†›ý¯Ø^½¹^b³µ†Ü?î`ŸÌWÖ¾ƒð\Ý­¬É° äy7»‘Hà—Üw5Ÿoá/…3KåXiv¶N¬Qɸ$–¾b*=šìW´ó>Fо$hºN‡<;o¨évP šÜO+BÙãêF}k¼ƒân‘so[é·ÆÕyØBÏŽû‡AŸjúe¼à{¸Ðµ´2ÉžI¾Øã>µ¡að·ÀËæK§G;£ç‘™Hî:ã½šì «èÏÑN½}f¾&UÅ¥«Ÿ³ÅwØ‘<ÃþÎy?ímí^ߣþÌŸ¼e _š—ÏIq1nGÏ#c$â»?ø+G_³ÚøGÃâ5`ë¾Ïæã<1݃ëëšÝ]vËO•òÖVÑqû•–EˆØ@Ø8¬]-v-V—s“_ÙGâç‡lfò,|Ig ‰†ß=…È‘}@FãžõâSIðÿÃÚ‡Ÿ¨ø†Kym\ÆÆH b7 «m9•ôõ¯ÄO i¤—1i-*¼K© úààf¸ù> ü.gŽî &µ“8µK<Ä;ÿ“‚{ž´½ŠìZ­.ç™Ûø£á“=¿‹4l«0òçÄþsúQ1ÒõË¥‹C×4„V@Ѳ\/–Ì;†p3ùÖf»yð\/Ûtÿ hó±$2Íb¤g¯`¥sÓøÏáÕÕ’Øßx[H–8P¬15»²`€àéIáSèZÄ>çO«x?ÆbÐ6“{ó)%Œ7Qtn˜ù²*¹¦iž4Ñ r]Z‰įÎñÉÏa…8ò¯ŸUðDlIðN‡Øà¥µÈ$}××5¡¦øÄ–—Bk+=7M +ÙO2äîTdúÖOÙ,E·gÐ.³jÅõ =ÉT,/†ÏÓ'¥lXøæ{8~uÔ,™€ÌŽƒŒWÿÂíñ†ƒ¾»Ðâ!w:yˆqìò7åM·ý®>#Y –ÇWÒšl¿û;w{ä¯J‡—ÍìWÖ¢}!cñ&éØlñ£œ¹¸qϰ'šÙ?üg?añ­Ë`cçeqôë_6ÿÃT|VñTH/u }¤nŠhl¡@–ÜÇë^bß´ÿÅ»;™îóN¸11\i°Í‘êvíÎj>¡S¸ý½>ÇßüMñü¬-ï|KØŒa¢t¤úAÀõÅO¬ø²îê=aÓîÖåšK€-áÌp'}ª¸$œ­|íO,Œ³ø£ÀþÕ˜àùfö¬ÇØ£–Û迵7Âä¸7©ð»I°¹8Ý6Ÿ«Ì’r ùˆxÏ8£êîÞ—cìè5§˜¤:~a{iB‘½¤J@9N7b±µ_†¿ühâü+ÒoDmò—…Q=ò€×’h¿þø†î[ƒá/Û^2†‘tÍNW±Û+EŒ~ úW}/Œ~k‰/5ÿè0p/,c–þË4{í\ÓÀϱª­HèÛöhý޵Ã'ö¯‚…”¤æÞâhHR1òº¸+ôÁ§ì7û!Yl:E¾³§(0¤WÆèMŽB¿œŸz¾Þ!øsw"ɧ|V± €—¶sGÉç™ÇrÙ§Ý Ó Ï<ÿÝø½£6¡©êŸ<=â;ÿ%Æ$\š|i)ÿžÉ5ÅÎW°ØQ†{ךkðN¿ÛÊÚÞ}?Wð®´ç/=ºê2Z¾ìõ Ñ2‘î@úWÜšï-ÄŸÚúTgnì)‚@ ëQ¯ÄmjÖÓtÐy* 0RÌTu#Ʀ¦ïÊgì(½Î oö ý³|E¦ßÂÏð…¶¥öXE¶iáínT,w4“´‚ðÐçåZoìûHü4K=R÷À^!Ö.Òv•£Ò~Ïwkü?zUb£mRkõº/Šz“Ê6Ü[8T“'“Ø“Ïà+ ·øÍâ‹iÄ*nzí•Ùò;}?k'‡_ÊKÁS}Ç+ï´túšj>5ð7‰kÍÊ»´é%‘œp‘èzåÀ>6ѯ¥Ð|O¤_iëo/–VkYÍßvÌpô$×îä´'ˆ#ˆ¥Ä€ïìE~~¼Ö¤_´ˆÕfŽ)#u\oR;g,8?©¬þ§Љ`)ŸÍ·ô TØ…¸ÓRh¢Y†Ù'‰I•[PÔw¯Vðì^ÓôhnuöX¦x€‹{L“Ž¥—=0yÇ•ûÝ}ñoM×qx‡DÓï¼°v¤öñN«ž¡K €{Ž•çZ®û6k7ísâ†ú÷CtSè¤sQ, ;õ‹ñëeNîÂK«dœ¢4hWlxûàc8ì aÙj¼\°jšw„õ« ‘pð^ËfÉo²ÜŸœ´˜`¤Cƒž29¯Ù| ý‡+“Ô¿fÿÙKYµMÑõoYZYÃ%¨Œë×2V9tù™ƒ{Ó¥PFO»Ÿƒ7,ø…ªÅ«âˆî7§™†Ö7œ²·*ÿ(86Åqk x³V×!×t«YVèâ%Rû2qÕðrÀõ¯èªËöyý˜£°þÃÑ5í_N2 í²Œ1óáBœŽ¤ç=냾ÿ‚küÖü?¤Úéÿµ}7Q³¸–æ]VU†i®£æ8¼²Q¬\Ú 9$’i<½Ë­¹øá—ñŽÐÁ¡ØGG”¸2N£Ëu8'wqF½­\xvühׂkÍAÎP™Hä7¸HWêf«ÿßÕ`yn´ÏŠZ-ÄìáÉm:o”ç¶V÷!E\Óÿa_:CÉu¤ßèKq(¸g."u^÷Çj…—®Æ+ÏÆÉ|K1ºm.çLÔÀŽMÐíÓî|½äòÈbÚÀ{¹ô®ö ˆ"²û-»Ÿ2rKI"ýÕÁàÿ/PkõëXøûLiÄ~1û-ê´Ú;à4_›póü°ÌO‘Í|ÏãOÙGãö¾·×Ÿ ntûÙ$/ÂInÑ䱑VfÛNÑtén¢³Ôl®bÓ˜@Q†Ì£õÆzà~µ­x£Â:‰Ó`'P´˜¡ÞŸ2<œžØ=»W¤ê¿hH¤Ãº¾«¦[Cr’éScÌäù‘+ôÏ5ÎÇðÎ? xJÖóUñæ¡k©\ÝMÙ8I§Á•¤bžhfcиÓ5Ÿö{3Ž]Q/†59/VêïPÜP.ù”àU8ûØÀ\~&µ/'ðª-í½Òù2;´J¯”B:ð­íÁž Öü?uaâ}}§ÖcF7ö µ6Ž>WMØ,ù0cÅq‹iû?hÚ]´v:­åÉ·ÊÃo,hº–(Þ¹ä _ÙÒè7ƒk¡ ¯Ä½Yì#YÞU9!×néϸ¬—ñÛ½ÕËÄ>Ï ™p*ã,Gñ â5ßøCN¾766÷nÒº¤€ÆHH”pÄŽrO¥qwúîµy©ÿgøsÃ׺‚²—þL7’HÀõÍdð„°Ò¾Ç»éÞ?·¾šìçLEƒ$’î|ÁG\þ•§©x‡M¶¸k÷´ ¸ÁFÚÄ·€¯=ðWÂŽZ뵇‡ü4‡ÉƒùÑüÌÊN 'åúW&Ú®½¥jvçÅ0º_ý"Ûvâ6l‚:dÖrÂL‰aåmzÔõ}1.Yíà >2ÌOúÁŽsÏ_¨j^+Ó…ŒÖ°@¦íc;a§v¯Õ.<]&¾CÁ%®Ÿ{4o ™•<¼ž:žAñßµ`x“Åð­w¢´­uåœ ’2pzŒðzX¼<Ñ—²h÷öÖ5íOHW¶’(Lqá‘Î0;ÇÒ«Úi÷×6K*6éÞc'Hõ$óšðý'Æ_ˆô£uk(wrUM’¬:“ìtº-Ë\­Ã™Îñ>ZC…ÙϯãT¨>Âp–Çw§Myuså><Ç<Š[WYŽåæó"]¹Î@ôÇcë\‡Šá’s#+£ (TàŒÿ{ÐT‡‰ÞXËÚÛÎÈxG9«Xyv%ÂHí]uµ‹˜ {›iP3×¹þ™§Ýê¶z~©NYnË l!§8ü+Ìm~)ê_gK[dÁ„.WxÇcžÃõ®nûÄ7¾%Þ.§‰mäÙ¬î:‚ÍÏ€­ÊÏ[Ö¼g§Z m­a ÷¹.ˆÜmê§Ê+3Cø¥K:é–W$Ü *XëÇLךj3éÖ“Á§ÙA¶Kaˆç$°êKöúSõ¹l¼;áë]CJb%œ³oPxnê8Ï)81r³Ø´?$W²ÂI!ÉÎWƒÉç8È®°x…®îVKx|Î2$œ³Ž+æû«-kX×öÒÆlÜþñó’¹DZ¤¼ðÕöšb'S™!óUÑÊð{ž(T®>WØú"ãÅ:mŠˆò.#ÆÜaw‡>Ô–^#²ÖSͿګyL΀ϧԊò'MðÞ—¢›ûÏ2à¾K¼³:}3éTïþ%hZE¯ØjQP¬6—= 'ŸÖ´öBi®‡Ò/¨iÚδìˆêàF ]?Äú×){w6“µ¸bV_0!#1ÆGQÇ^:Wik8b³Tî_áÏùëTõÿiú»6¥,Q9K§A‘Ÿj¯d+7Ðõ ÏìÉäóï‡Ë"íÁ໘ô‘ö+}R3u¬NaŠÆÂ²`õÁÏ­x5׋e¾¸k³|¯*Ì g$1ÏzÎÓï.•ã~ñj+Am¤†?fLª[¨'­uZUÛËu5Д²‡!›9œƒÓŠN™¢íZ-¥ÌS%Êm1°áIÆß¯½wºG‡<;u~êîAýÞðpOð{WÌ×%×´~ÛMlG ÊÈÒ©rþ„úWBÚžŸ¢[\j ÷1F(àd‚IàŠÍRwǾk>ÑlíÒz!h²˜\–$’sǯjæ­¼?áV{û‰.Š¢£ acž¦¾cÔ|U¬xšú;J¥Ço^8ü«¢‡ÄC‘2Hêà€ÂRO?Ý\gÿ­Wì˜î}¿‡ôë·ÓížC„nmž»Aüë—öqÜZø’òµ¸`P1äàä8#ß"¼ãÅ7³}´^xr\9A½YÉ7pqYº‡‰^híÚâwBŠÛ•ŽðØ3îj”d4Ï\^Yä¹ûXó2B…=èáž!°Õ"Ó¯Z’fÙ…ŒrÀ·]ÙëíŠÍ»ñl0ÞùÖ)±‘ƒ`¶à}k¦¶×d¸xõ8îXŒ–8 õÍuRÒEÅjÿÑüÐü1§Á§[Ås;¸?<˜Â!ù5t"ËÂk,™‡Ì¹$€;uþuãÑêšÅ½çöîŽKfhg²v2±V_Á§.£{5 ŠK‚¢2ÀÃ{ zW—stÏ]“VÐíaeY≠ˎÜ=koÆw_i£;FõÏM£·é^^Œ&¹Û/*>îF}>´B·"êX³ Ã(Q“ôÍ<{öK-i-üGgÛl|†ÝÔoô¯™­Ýí_Z’LL“Éœs× WØ¿t½GXðmþŸ4|$‹·>_Ìp|a/®‡Ÿ•v¯ zŸ§¥z4µ‰Œ·>òýˆ|a„ÿi&±SåÃâKX$PüT0“9=—ý+Ùükáàßë~~âöI!ǹ—æAøf¿<ü®¿…¼Wá&zuÒÚHìà³þïúðkõËö€¶K¯è¾.ˆdë–Y€þ;b® ‰ng#Àür4ïi’LBE4¢ [Ñ_ æ¼·ÄZ4š6©©èL›dæB½W'oèA®Òù-M$€Ž²eÙ=ýø«Ÿ£KŸÅ­mÜš¼åXqÓå9õç¥.[¢µ=Å:xñ!Ó¼yzçÊ»´·€…j›9õ8ëX–RØÛI‰8ãàzÓü1¨IðzûNPš> n<çAÔÿ½\tzך.1»gN9íÔz×4£©×Í¥ÏJmF±ùp*¨ œÆsÁî}ý«âŠï¡üSÐ|G bcö׃ÈÉ\ùŠp»ÎîÜ1ôå´ó4Lª`.>\àçÔWˆürÒæ:rk›vMm\E"vž#ŸÐŽ´EÙØMŸÐv«èq~ÙÐxËN'ì¼5§ë2r¥An¶rëåF¡S¼œwþ{?kß|ýµt󧯰£k¯fè:/¤I“Ñ<ÓB  ýpø]ã©üYðà÷ÄÛ P]x/X¸Ð®dÎKÁ©°™‰èWj…€2;ׯßðZ̾,°ø—§oÛwb—(ê„ö.±pA§vÏÍo ÌÖìú·ãìx“öOø_ñ)‘®gÐo¯|/w/Qç·Y;€ÏÅ'©8¯‹äž9bòÛpw¬}+êO†š˜ø­ÿöñö”·lòh¿Ø¾/ÇFk{ûTÛ[ m¹íÅr95ñÄÉrG9)ç‚­amG\ùÞóÌðŸíKá½jóæµñ±³`á%‹r£R ^;Ojû_þ ¥ž+ð§ÂïÚIC¥YÛ]HÊT‹m›¾¤HÛ³Ó ë_~Óñ¾—§é(Š /S†PÑœ­òœ‘Œ‘_¦ÿ, øÿÅ¿™‚ƒáÝ} ŠB7¹·ÖUÈÆHÚîÓÚº<ÇcéÙâ$:_ÿÛœ©»¹²|tòu$ÄØôùp÷ŽkÅjK|ûFxŸJ·ˆ„½˜j ó¶ì?†Aúד~Á¾+›Sýžü®Òi:n ŠÍ‚’î³(þé*q_gþÜ>ñŒ¾'øc\ð^•s«M­i •»ÌæXÛä(ùF×8,@ã­L¢Dw? hµ–ê÷M×£„áÑ¡=À Û?Ò¼ßáLjmü7â-[ŽaéúÄÇ`(ÿ»ažŸJý«ðGügãÅ¨á¿øÅz¾Ò‘Œ¿gW[›Ç-ê ì‹Ó«Ÿa_¢_à–²ßÃFŠx|'НàbRã\AvˆÆVÊ#ÛA"«™(ƒ]ÄïŠVgñ楉P²‘p1Óç@zþ=«‹Ðí’çÅ:-Ž£?ÙâÔ¦L¸™ós8P¹ا8ö¯ÓïÚ·ö øÊ¾>ÇÂ+ Ça2œ×£JŠÚ5¨$ 2ò6ªàñŒW„øwö øµwuçÄPún• ‹3]i·æDÙÎQni9éµ9íëYûX-ØãJ[ØýŠøWÿÍø7ðý’âÏBºñ%å¸õ nå™9m„=—¿<áïxAñOý_I‹_ÑÚX[L·„4C„X•ŽA qœôçÔþ"øÃÄ7²CñæFÖ_²ybH£Ú >vã'jŒÖöœw-t»mà+¬K冀›ñ'­qb1 »Dê§K«w®ü`ñF‘%µö­§XL [¤ŒÎ; €¸é’:åô‡¢o¼NRâþQóK€¨ð®r@õ䓚ë-ã·¶ŒF‡`Ξ¸ÕŒÉ"ƒ2¾¹ÜsÓ¥pÊO¹ÑdI ¦§¼kŠ"Ûœc¾G­]MF̱öÿ1a·o¸#¿ÓƒYjá˜twnåÚ¤—·/4`pÜþ5Ã5$× –fŽÏrù˜^:qןz¬5û/?-/™/~;úséҫı¸wX! XX‘øõäþU-¦•Ò”Fþ`$¹à9çБI1x#’Q³aÛ8ïŸSS€ÀÜ:*ƒŒ)Åa\/—be¶™ ©oŸ¯lw«fÒÊVÛÆd;H(HŽ:úž”œ+—#ñC=Âþè0û„§žO­\:í­Ä+sh ªùÜ:sÒ¹ôЦž'X¦TN2 ä«ÃéSQ”,’Ê£nelŸoëY¶U5=:ÅYü¿0U8ôîjìšâý›l+‡Û÷Y°Fî˜þuœºsÅq(*¹9Ú aýnE¥+°•ˆu`¼²à’:wèClc­õG¸+ñyŒY6tÏ©÷­è¥”…ÿVqœŒŸNƒ½> )Z°.2pXc$úŸ¥Y]"e‹dlTœüÜóïjE$K …XÀˆX‚3ÓµlÚ$O)7*€J†?3ø"©Å§LË›µóöã==ùï[ûV(ŸgÊpG§¡4¤X>D$Em匀£©#·ø×Í>1x‚ÚþãAÐ#m9|rÉ"+<€÷óŽÂ«üHøŸ%ÕìÞÒ#‘¼–òÚhå(‘’Só~uä¶6wz›µÚFÛ@ËO?S“šk¸Ëš>“up»¥È ßÛ¯ø×r+#ö» 'ŸoJe´¤žOÚ”Ä:¢¦I8ÆsW~c»œ’S –Æp:V3¨Í•6Zµµ¾¹D–âBÄ„;·§ ÅlG µ@òÊ}ýGh%Q½Ù”ŽXÓ·Ò¶lmäÔ@x šuSЩlà÷ÆqQ{ƒ‹8õ8$S§ÂìÇ»$uǵ}!á´—û*m—Áß½ºŽÙÈô®EÑõÛyÃM¡]Eãyt ƒ±Á!¿ ô‹µ€[¼‘*’Ì äàÕ6 %ùouåKo³ù1)^&¯áŽsZµär³ˆefãï®ÝÕP¶º€±—tƒ±îõ=ÈéVmîõ·+æ|ÀÜöÇ­dçb¹ŠÒ[¹ä‡j•Ýü=ÁëŒÖÄwMK™6#s¼Œ.?ÕÚÉeŸïSЩëÚ¥»³Ôu :çHžæO*áCƪ<ÿ÷úÔ©jK‰óí‘iãX¾Câ¿ hºgˆ-l®í0jIE•‚Ç<%\§%³ÏN@¯§m­¯´ë} îŒü8V!}:ñš¹àmuO‰¾ ðOˆæŽõ šÛ͸Á\Ëo*Dì˜ÛØè:×à-[]¹ø}¥\øíÍg ÌòâTùO>Øí[k¹'?ûJøQ5KK rÂ) À2Cæ ’±•-‚3‚2sÓµ|¿ðÏÄtÿ³ÜLZæPH-Ïlc÷—Å¿´Þü>þÒ¶";›qË÷€N«‘Ù³Zø×Á†mkU·–XÊÝËöˆ„Š¡™t¾;ý+hj€õ¯ßé÷ùšåBD#$61’:ˆ«>øoöƒøâ‚ o² ~Ì­­Û ‹[Øͳ¸\ñº)B°§Y[h–ÖÑÄ÷™ÆNâÝéÇå^‘¤j–w–«o?Ý+µB¶=»àýjá.Wt&®~ü9ñ§ÂK¯ ÇV¾*ÒüEáæm7ZC§ÚÖîÑÌúº«© ýa‡W¨Ùëßu{WoαŒi[pzd7Ì£õ¯Iý¾<yð£ÆQ~ÕºUŠj:¥iŸâ{#½ÞÇ"Ekt… d…ŠÌï€|¥ç5óî‘â}Ç^´×4‹ ¼™i-åÊȧ;p̧ lu×¹N\ÑLòjÆÒ=nßDø ‘†Ûâ0”˜A’Á¢’Cbº]?Aø õM'âDr‘ö˽9"Dz¡“èxâ–6^ ¹fŽú8Úæ †ÀnÀ w­ûÿ èì!ðç‡dº‘Ô³Ky$Ìr=6’>¤Ö¶2>ŒƒNø'bãûÅ3F;ý²ÕXþåÏãS›ï„p¸H< â»ûú•¯ÍøWÌ7ŽnÖI›û,ÅÂp²Jû™OzŸZï­5wÕ-¾Ñ,³BJ€©oç ¤t Ç+÷»ø?MŒ%Ÿ€5øOP&Ô­×ñ8LãéW"ø·¡Û®é||O >°Uú"åšùèêÚí›(e¹!@ÜZçùy„ñŽÂ­G­]±‹÷^IÕ€×±£”.} ÿ¢2mƒÂCwγ1þQŠ¡7Çm#4Z¼8?Ǫ\8? 'ð¯›Ä%ß̾Դä-€^I€;{ð8«VÖúOç[ëºrbBÌ|Óþð4¹RÜi¾‡´KñË\G2ÚèÚTl2¤Ë=Ü¿¡×é¾;kVR=¾†Ê{æ pÒuõã­áM6þV–{Ì;² •œ²cÛÅY³øb·ñ´qͯޯ]É£\·>ÃËË ~T¹ ]äzt?´V¥o'›gu¦Ãœ‹]ØÏ¦ç Šº¿´õõ»|Ú…¨%Œ ŸÄ–#ðÅpzìÿª^–GН2F^ºÇæêë]e·ìã¶ÄöÞñIÆ3 [(cÓ™š9)92Ü¿´Ì÷ŒDº£°í›H?Ls¶©/íäù‡V»ÙŽBEž¿ÔEû |LHÕï| ¬8Lÿ­{Ku=òTËœýi–ß³'ˆEËÁ'„-RDÁ 5å¶õ=òÞ`ëÚ¡û"­#6ßö¥²VŒ_êŒé“µ’$Îc’ÿ{šIÿhjNe½›];³·c¬cÿCê+·‹öqÕ¬fÛ&¤BÜ|“ê6y#Óa?­:_XxYZ+? <ÌÓÛ͆#árÞ'½/ݽš’ÜòCã€n@¶†mbUŒä$÷`ôÚÔ[üYðæ¢‚6žñÇh¾Û#œú?_QÒ¾ŽÓõ/éQ›H&ðT*CB臸ùTœçÛñª7~-ð%ºG)“Âs¹m¤Åc,äR@søQhö›Z;KñÀIº+Èn¤Wùû[>Cö [5Ìê^&ø]v«-íÌHŠU+Â…GјÃõr|Køqq/’ÚžnÇåQmá‹™‰ÈêNŸï{Ò\øÛÀpH‚KÈYˆçÊðñˆ¨d™çùÓ´”›7ÔøæÇÄŸ4è¶[£\$˜ .®}~ñþ\V”¾9ð-Ì~EÆ‘© F`=†OãÅ}\Þ*øe$N˧ÝßÉßÈÓ-ÐÛï°ëíPAãÏ Ù©×âÎ>x`²‰sëwAF:Ÿ&Gâo3íƒÂP0##̇¨õèOë]n®éMµt¿ ÅàxXɘ^?Zú_‰÷ÛÛè:ŠŽ ‰îàŒƒØ¨yüþ´ëŒ^-‹)‘4„®0oÏAß">ÞÔþ@xj_ëRá (ô(ëÁé²´<@ë–ð‚(N0Ñàcëƒ^ƒ7Lj pš,{@Áó®æ>ÀžÏÒ©ê«ågµ½´*§Ó¯J\®á ïìOÞ(eKLã '#ó dUè| ãk $Žסx®yþ,øªåʦ½zÊ &<¸•W×¢ÔPøïRu ?ˆïvxYyý1úQÉ Ðê¤øsãۈ̑™8ÂÙ¸?ŸøT_ð¨>#K¸"ê½Ñ^5#êk¶ñs\Oþ•âÍX/b³e³þó? dú‡‡d";ÿê×#m÷rLsì3Š\’4MøGñ*_õfgaèè½{üÌ*ë|ø”"3]\$¸,eº·P1ê7çß\a“«‰5;Ùp*YHÆ>§'ëPÛ¿ÃèØ‡¸—Ìc•fŒ»±ïRÓ+üDñE§ƒü'¥Xj×÷jI ¸Š1ÖI_$$kÔœsÐsX×¾nžÂØGÂ9ùdQ¸ãýá_Y~Ì¿´oÂI¾ ÙÛÝiþ7Õnnà5»È—Öü®É“(ƸÜoÌry5ågšÂPsJï¡ß€Â*Õ,Þ‡Öýœ~|.Ó­õOŠr'еu;’Ø©K1'.Ã?Æù'®+ʾ.~Óþ&ø¹©Éðoö{±ÓmbÒ$0ê"kHä¶Ò6 ¦;bGïnqÀ ò¡êr1^/⯉~)ý¤5»ï ü0¿›Hðe£›]SĶí¶kÃÑí´âG}ÖŸ·;}kºÒtÿøOA´ðO‚ì£Ó´‹ÁoÀP;“Õ˜žK’y¯‡òœvaYbñ’´:#|ÛC J޲+x7ÁÞøq¦O¤xTHòÝÈg¿¾¹c-ÝýÁûÒÍ!ù™Ž8`®ÂÖݦ~x­Geg%Û†q„Iõ­{›˜í“䓯G$~§J”iÇ–(ø:•eR\Ózy#ŒÁùV¶ƒá{¿¸¸¸o&ıèòDôÿz®h~†ä®¥¯®à¼ÇlÓîßá^Šoc;¹€00jLç©4´D«gk ²ÙÛ"Æ6¯@;ýIîk˜–ÉŠÄç“Ïÿ¨Õë«é@g\sžƒÖ¹ûa±¶r3éíÏó¬Ú1m³=Ùcw‰‰*‡†x’;qµ¤f\³Ÿ¼çŸâ?¦+â GÄ÷ò]K«^m¹¼º”Ë$®3—>ßNµCŽ5ˆö¬Á'·—ƒüë¢0²6¶–>ê”ø?hŠmVÝprWxcú*²j~…Ç—¬å›Î·ñŸ„âØ‚þK€NCyd‚3ë_&ø‡XŸX×nõyO2ÈÌIë‚xü†1\µŒ—бh™£‡<àqíüêHg¤2±6í={÷¡!¥c¡G¹+òóœ ‘œV½¤ØùÀ€y5‘lI)'‘÷Ny­å‘™K18Ç|V€kF¬«ËŸå[……eÇAÎ+ ÎÑÞ¿^Õ«"n>Õ, r4ÑË·¢€|Õ-²Hû‚œóóÃëO“2[$’œqÔJˆ‘°"¯+Üš "ÜÊŸ˜3ŽNzz´ošIU¤àH‡zC*G ãzûóTÈò;Cœxúõ  97-${º/g ŸlúGµuO‰µ@¸ñ‰ŒPÙ«HI¶Bp„Œn9õ® Æå®㟥yÆ-z=;M·ð¼|5Ùóf#ƒ±:Äçð¬¦í«4§ ³Ìtß ø‡ãÿÄ7áüR?ö¯ŽõU°i‘å[͹î§V³Û$Œ¾¬w¯é ñ4}1bÐü5·Ó´øc³´‰xXííÔ$jÇ@¯Êoø&¿Ã¤Õ¼câÿzœyƒ@·Ò è.îÂÜj)îDb²:ÃÖ¿Qg+"¿™|SÎ~µXZoHþgëÜ1ötFµgË_¶ßí'ì¿û%xËâþŸ"¦¹$qhZ¶~m[Waknxç—óŽŠ é_ľ•ûAIð'ÄwšDF{/Iiö õYP\C8O¼² Ë®÷ËoãkrÛ³Çî·üÿã ß‹~0øöMðgúMÇ…ì_Ä—ñ#˜Ïö¾®¯maâB–ÂâFåICÆE~Mü&ý›h®£­hhº ^ìâñ–çP,A"Æ©,ke‹{WéäSËUi¯~züº=ÄØåR»‚z/Ìî¿gßÚïàëü@Ðô/Çi©X:x´@GHÚ«ÎT”ãvî™ùNq\óþÐß±¦—£?‹<9ã M.y fÃ9›Q{k¥-ºÙàˆ6è£9e*HÀÈ$WÍ×ÿe¿ø¦ÿ@ø³zž-ñP¾Þtù"i&šiÃ2¤pC•ÚGÜ,Àppö÷ìùðçÿt_ü.×¼5¤øÀðÛ›+„Ó ƒûG\¸‘CI§=Ê.èQ"*ŽÑ|ä¡¿G>NÑ>ñ_íAáŒöv?³õ´L6É8»×µD6šu¾Â0¥™¶’ý) ôó?ŒñkŸí´ø#Ä—Þ4Ó5 ÌëWºrä ‚"‹\îPìqç6àTu$Œþ“XxUñÆ­GÁÿ¼7‰£éV¦ìé:b&A Ç´,Ü1‰ÐärÌ„Œ×ÌŸðP«Ë¯ˆþ?Ûâ="YZ|;‡™“0¹1®PD@Xäéü8é@ù{Åð“Ã~ño4j[ WD¿Ö/b…n/J‡¸ûD~fŒÐ@ëLÔuŸÁªüSøou§Ý۽͌z-™•¾´¸™Ã[µ³íâMç%Xà€ Å|ѦþÒ>|ðÏŒæÓïï4}¶Òè’E·°¼B°óØå¼À|¼p¼£>Lÿ¶_ÅÍ?ijÃâo±ê×Þ"Ö,¯nµH‘ æœ1“·ní¹ê‚k'NyÁô>øñßÅ/üsƒ@ý¡?hŸÝÇ£ØÜ\xkÄ’é[줎æÇ©] ¯–ñ¶ä«ÎNpÞ!øGû$ø»ZºÐ~ |AŽÒg"ëM‡\IcŠ`sF'uWW’wg¡¯ž/ÿn|>¼ñ,^²‚ÿûZ讳fai­¢ŽvØ®z*™Héã<äwâo¿hßÁâ[¯ ø«EšH…¹¹h!…¢x.‚ù(ó€†áÔQʧ>‡©'Žü4ú…‡¾8^¦¨ºTMöt¿—íWæEÚ¬&\©Rv¶ì`óÖ¼?â·Å‹ iþÒEˆÓÚDº†]9#Œl$„ÉóFrù$zT;øÅðÇE¹›Ã^5Ò$º·Š4H'·½Ò‰JâB2Ñ‚ýkŽñKxľÑ|]àý"W€Ý(µ ¤Kíd-2¨Úr3Zr›EJúý›ÿi߈¾ øŒ¤Z¥{ëÓ¶9 “˜Á’;CF2p2;šú§À?³¿„ôÖÕm~8üYðÿÂÛ;wÓô+­rçP¶ÇÌÖ’©ª„ØÌÜAú›û \ë:ìßáÏi¾[Çt† èCù²Kqj 2¼l Œ/ q•ÁÆ }ç¤ü Ó¦²³ñ§€üGi§¥ü÷úR[¢Ë#Å!£u6ÑÍ8ŪâÏçWÇŸðN­xbëöºñ?öÞ‘Þ¯sãI•µÃ±SP¼,ù`Aˆ‚>W öÿü‹þ ñö=ø¿'ìÿûFé·ƒysä꾕Y ´‘þ_í \•ƒ8&âÉ [Ü'ï`"A"Éú½§þÏÞ:k‹W_ÙÙÚÚ3:$6ÜÉ? Ëæ²§#Œ6E~zÿÁHàž>"øóð‚{ßXG¬x»ÁV×Z¦“«@D7ÐD<Ù´ùaþT€©!dQ´€Jžl~–*“„ÑׄÇJLþª>4| øûCø"/|2žÞæ{¸…í¼°6Øî"•rOPXtî¿#õ/‡ZÆ‘ªËá}ZúòÂöÔì‘w:pqî:×Á¿ðnÇü“Ä<7yûøÿVyüQá»y5ŸÝÊÌ^óMù~ÑdùûÍ mñƒÏ–Ø*Æ¿§‰¿ ¼#ûIøxêúf4ÏY®VE8ßácüHzz§zü¾8ªÙV)Щ·æ²ta‹¢¦·?dø]rñ…Ö$•†W¶ÀÏ©ääVSü*Õå?8[†|…DS–u­v^7°ñ/‚¯§°ñ&£.‹=¬­«qœ+ž1•ãýé\üÃS»O1SÒ½YôÿAj.ãñuÍÙÆåQy"—ÙˆçÛž*²ÂO ¨K}râÝn(%À÷ýMu©2,*ÙŽ ¢CM$mž‚ÒBrz÷ÀúW/uû%B×hµ‰! íl[È¥O¹Çêq^ûöψ˸_ÜIÁ%¦mØú*5O³ÿfœ‰~Fa3(;û’~µ^Õ¡r£æK¯ÙÄÚUÚM¤ø’;f…•ãó,É|’q‚­Œ~±/¿f_=ÓÝÜxZIÜ™ ‚DbçÓ'߯¯O¸†Ç^JeŒ`;8>Ù9®løwU»¸ûš¬Î\Ü]† ;±Å_µ¹ò£~ÌŸ!Œ›=U® Ã3Ä­ƒÓ?‡8®zO‚¿ œÆ·³ÛF ÊÌSu?•}yõÛ¤$ëw¬NIX¥`O·ßçô§§‚µ¸£1A}xP+o>FµR«"eå˃þ5·Q$Þ"Ô#V'æXŒ\翽kX|(×ä…\x­Ø|Ým˜ã×·ë_H ë–åc[¹£XAP‘É·w¹ÏJgئ ©}2àã àB99š\ò'”ñ?áuÜj¡ªIp«Ÿ³•9ôã=k«O†KO<šl×ÇÎQпô­‰m4ûåeþй“Øà¸§9¬›ÝÞò(Ùõ«‰ü°s\> žÝpOÔqKÚ1ò–Óá¼ij· ¤^„<²¤MÙ^ ¿§øÈB.F—¨LHÑãëƒ\¼Z9¶ýÆ•«ßY:ä³^ʘí†!³Ðô¥þÈÔä"#©ê¦Q“ó\Ê¿Þç=rijÇcµ_è0±I´+¤=9¤çžÆ´?áð„Ü\x^ú_1¶îV™±ŽG¸¯0HäXñ.¥«ŽJÌHëÎwIéYW—WÙuÖõQ³¯˜ÁH>᳊,ÄÒ=?þ¿Ëûû_È: 2Km'ÑŸ?LŠŠm Ãæ@±ø.ýÊ`æâÜôçùWŽÜ]?Òï¯dÚ»ŽÙíoL±ÍgË«¤_ûfùN^Nq׿ô¦œ…t{ìZn³9fo =ÌC¦mÐá{®ÎEqš—Ãí^õþÖt BØ®B,y` \ø×›ÛëZ¢)ž bõp¸€çÔgÓÖ´eÖ¼IjìÚüª$ý¥óϧ?˜]ß|+Ö'»"=P–B£;¢ ÿâ+|"ñC¹òü-u;Fwb[LÄžµ±7Š|EÐ5fŠI' l°É2)Î~ï!}€ëV|gù±jÓC†åUÉenà’pk®Òþ0|B„˜"Õ&º ~ô³~ˆËÅ5‡‘j>øÿvêñøgS}¤çé¡0ƒÕA'ßòª2ü>ø®¶ 5φ·Œ.à°ŠÌoÚ½qŽœt¯£¡ø·ñ(Åæ%óàá¾_¦Xÿ#L—âŽ5öËé.å” ÷l¸Qßå } W#\QòÞ«ðÃÆ“j³Üx;ÁzÄv^`0´Ö-HÝ“ŽFAé[Öÿi? ] ,lõ‹9R1åÆ%”*«ôÚŠÛxïÇîמ<ñ¥±/É”§œ>¯~Ü:QŽ+;}~çi#˜ÝŽÓíØ}+Ò,~%þÚ:aV»Ðo.ð£tsÚ«9íœ'ZºñÅÝÃcw.àT‘u"À©5…&µo+"K¦^¡ üÃPœ¾NYãëQ*)èÍT×sØ,¾&~×Yøl/2m63»Æ$WüÅYƒÆÿî%>#œ‚B[˜r{ç7>˜â¼ÉgtHÒæ(ÀÈioî9—8ü gÍwöÚ Éç/ö©]Ž{rÝfð‘cöÍlϰíµOêX“Rø®!Û›«@Š;“æÊ˜ü9®‘<¬êð}£þ‹í?ë#šÊ`¤ýÑŸ<?~|Ë Mq$›,]ßÌò¿ï~¤ÕKM.mdÑ£¶·c¹”O&ÆÇ}Šv’=H$T¼ ^Ì¥ˆ‘úAì÷ã[Ä’[/x’ó}¦ B¿÷ÚËŸç[ö/øötw×R (lU Ì.o!Y‚cø‚³e±Øt¯ÌUøua,ë<^ûd¬JÆÐÌé'ÉêK?¤³øGm$ϨÞ|<6lªÛn—$–n»Žìã®N9¬e–Ýîh±^GÖ~%ø%ã}>C§&·á¸o•r±Ë¬F’÷ÚH+ž½«š³øû@X¢­¤6ˆ†KÛÝÃtÒå˜Ã<ôç5ó2èž7+ ZjßK\‹kRì˜à€ª ÿ9ª×~ ðöŸh·÷zEîš9šÆx›<ífE ÓþÏó×_cé}Ká¿ÇÍ=q{a½CdŸ+zNqó®nûLø±§çé)uùŒ‹$Yã®Ý ‘ø×é~1Ka§hÞ-¾ÑäcËqÙž8/µ>Õìzg‹<{$r /âäJG+ ]1“Ž£ËdäCÅPÖ[9™fø¯§Æ. ³TÎH 6ÂqÔß1I¤üsø‰§†úÎö,ƒæ#“…ÆO7íñ#N¶q{âí„áæÍ%àú€ã=yϽcAûKë÷n-õ I¸SœæÎO3n:ä7A’}*~¢ûÛ÷íW©é—nš„O Áýí³2ç×s¾2}z{WCaûfêr1c6Á˺2 ÈçiÌWÿÃLüºfµÕü#g,ÎvbݦŒ±÷FúÖö“ñ£àEÞØàðÆ¥¤¤‡i1]@èvÿ°AÇçš—}ŠU×Sè7öµÕµ»&ˆOŒ€rÓ¼dã±Á>ƒÖYüy·†8àxay>pÈXòA89úšùê?~Ïš“-Ô‰©Ú6 *ñþÔdϾkj-_àtŒY|O.˜¬]2@Çv …ü‰¬%ƒôCXˆžÛyâ¿x†ƒ^ðæ•~Žr~Õ .»±Ðäqíü«‹Öüû:x²Xõü.ðî¥"…%{ƒ¨íRª8÷®ÂÏଗAtÿ‰Ú“IŒC|ík»=9“#µv|>½Õíâ—ñFzÈw(¶Ö!ßÿ|g‘YK5¥Ž•Z áßà‡ì"÷_Û|-Ól/$!gžÌ:;íé’­Æ:Vuïì¿ûj/5Ͷ½¡™Ñcu²Ön-£(Õ)Ú¼õ=MuÒ|&ñÔÊŽ)ç÷k*L¼÷8<þw¤xÒÛ|Öjq!€«1cç#žD¨Ë¨sAŒ´ý–?gKHeƒÃ^/ñou$SI£#¼@…$º–n8`+¸ÿ‚zþÍš·‰áÖÿá*–æÊ-.Ÿ$f8îÛq’yAÜØì8\k¶X$ºÿF²·¹Yc,b# ÜZ§…’V‡ÊÃFA+Èëp*}‡53ÇÿðO/Ù÷âWÄ^ñ*hÖw½4ʲ#Ë9!rs ã;”ƒÈÊ‘Î|ÊÏþ y¦ÃfÖÖ:ö‡ys2yN.exL¼í/pW¦@`z×¶3A )ó7%]A8÷ôª—Z…»f]"Y#Så‰ÀÏ¿ÿª…E.‚öq}„<]ÿöý¾ÝNƒá6â]ÞÏÄ/œmÛ‚%³?†kJÿ‚~~Öº?ÃïËã?É}â[ ‘hðh·PMióËqq#GÜñS…çï·ñ.©§CÄz´òHÇht“ £ØØæ·‡Äÿéàñ¹ùpÅö¸è3ž*å×Àgõhu?µØ›öÍðψ­í|Yð÷\Lb¢W³·[¸†F FðHňùJŒö¬Ïøc\ð~“†¼9á=Ú—ûeÄþÔcŽ, äá€î ‚G\WヌñŸâ”Õ-ݳ… »2§œ¬ÙŸÚâÞžcîRD-·nöUP9õ<åYû4÷‰Â@þb´_jú6™«jÓÍ(òä[å:z A0R z ŸJ»{à ]Ü(ð>‡qmÁN‹h>£©úÔû5ØÍááµçvÇÀº¦¯aikð×ÃRj×òyÊÝB‰hƒƒ$ÆI”@éV|KðgâN‹¥Áyâí,d½‘ⱋp•nXc-o0m Œàîþûã­Åû&jößÙÞ/ø_¡\$‡‘‹p<õdŸsÍyþð÷öC×u»©´OZxkÃiýÍž™¶{»œüïˆÉ2{Çb K¡ЫC±ø ªx;ŵ_°øêxtÙeC‹Yn¶þm©œqßñ®cÄi½¾ƒH±¹Žx­ñ+¼nH*yükú)Õ?gø'ι)–ç o9%¤¼/2ÎIþôÙ/¯Áêÿ°·üëÄ‚âÆïXÓU]–Z”‰Uç˜ÈÚÙ>£š•‡`úT~ØxCÄ>)/¨i‘NÖ§åÂEòŽq¸Ç¨ÍCÂëÛòKµb1mÇ©àp+ö7Å?ðLïÙïÆºïö¾•ñUû<-C§ß4GQШ ÄëÅz~ûMáÍfÎâËÇör[ÙåÙµœvдdŒ Xãó«öì5‚Ksñ:€Þ „ÙÜkzņ—öŒ˜<éw“´àæ5lõ÷GÅÖ|>.|73G«Çm.Á(‡‡R7\穯ىÿàš>¶´–?Ûx?GŠNEŵ°K·›€Iõîy5“âOø'‡fð×öVžú|®˜oDÒ4‰·¦å (8ëÎ=*%B; xEÐünðOÂËxBÿ>{HâÚhˆ ±2vœ°Üõé]Zx* ÀÚ©rp¦âÀžyïÇ¥}ßâ¿ØÇöÍ›ÃV¾Ðmt†Ó!%å’ÚãeÅÊŽ1&c<÷’+#Çÿ²ßÆKXé> ð•íàŽ@nÞ[ˆÙޤcw#=³ÍKÃöfOÏÎ lägšÂÝ—Ì2£–aŸÓŽÂ´<;®év¥óљ՗•Ä_z÷íWàÿÆï k6ºÆ¹ðÿZ¶°„4èÚ{ËægÔG¹‰GµÄøŸÂZåçˆ%†/ ëéf±¦Æ}éŸypÜbéÏ^õûêL°²èŒÉµ+-^ѧŽò;™ ù›ø.¹þîz{UýRkMVÍtYP4S€]˜W?C^sâ xŸH¿ŠÃ:Íœ>R‹PŒ[¸n~›C\-Ü^8Õ5(í$ŽЍA—[8숟Z_U‘Îðó¾Ç§ØxWê—–‘ù¶î'‹œ1#¨'œŽÕÆø“Oö öux‘'ÄaIݳ®7 ^‹'„þ*Â7ç  ­­¢«Ø‘ÔI+Ô‘Ó­r¾ ¿Õü_slLI¬q³"ÃÄÒgŒÈxùO¦hxy¢] #&ÞïZXCK,¦2Úää}AÁ©µ-aõ/&Õ#`#Î06á±ÓŽ ×MâÝ?Å7w¼I’F¸Þ›bJãõâŸá†5äÓ§ˆZÅ{–m¡•¹ÂñÅJ¥+ìJ¥-Ï,½°žþÖa`²´Ñ ÚPÖ±./üm=”:Õ²ºõ׉á¸×¢¹¶²¶ ÷6tQÐsŒ¿g.Æ‘ƒGÿÒü5ý±¾ÍðÇöŸñ.+§êÒíYŸX®?ÖFOr²«çŽ„W€ÚéÍ#¤ä•bÌ:“ØWê/ü ÂðÚÝ|?ø¢«¹oÞmF^TH‘Ir£×,"n½‡­~b¥ÌqȮϴde…yòZš'Ø|öâEŒà©úVÊ\C b+`IÜX–ö¬xeóФŽ2äùÁÞŸÇÃvˆõ¢Ã»->Õ1‚L&Wððp+ó×Q¶ŽÛP½Ò Un$T`2v«ƒé_ ‹"µÇšFÐ šøÇâ¶—ã;«°¬!¹Û%¹PÌ8`{€OLzWMº"f,Q CÂú¦n»å@Äñ•lŽ;3Å~Á[jð²¿f? |@³q§µµÃ!çj\BUÓ> M~@xZè.¶¶¬ùñIÛp?¯zýDý€5{üñg½Z`?³Œæ49&3½˜8$`å‡ žªæbqW±:9®1ÜñÉõíZ#¶ûo€t]h'H¼–ÅÇ}²á“Û‚¦º³ m"\®WÒ\üÃóïVtXÿµ¼#­xhÞ„Žþ5'Ÿ2Éö©ˆþOö™¼Gá©eÕ4Ù<¯_Ǭ%X.¥Sð6²4/hz³Ð%â¤íÓ÷r„ãÜ×OâK8ü=â­Góÿ¬³¹’5ãåU,Jóé‚+)ÅšÅèE —>h™NqÝ ö®WÇúk_xu’FÊ&öl ¿#°î3ÔVÚÈã!NÒœç'Ò²|A*\ÛOg"°uF2àdñ©Š×Rϧÿ`k›Ÿ~ÊŸþ%ÂCuáìÿظm· Æ(ƒŸÄW×?ðQÿÙ|Yýƒ4ߨÀì‘\@@$nòå…ã+8$78ëÀô¯„àš7‡Â¿µçÃ=bÝ$Óü]§\Cp]±‘µ6!írlc¹¯Òëÿ ÞøÃöñ§Âë°eºðì·PB%;X¶žÄ ÉÇ]£ž€µkFCÜüüÿ‚@xšÛ^ð©ð~¸ãè.¬8ùb"5Áêv·CÆ:WÏ&ÆëF‰´pm¼´ÿF¹ªË ×Üöø&wÀ¿žñÜš–‘àýZâÖ-U.-Y-È_&P™˜áT®ââWì–‰ÿµñÄí_Ç/xzOø ûÂ5»_ßÝX>O™»åùT3{šýfýŽ¿gï‹ÿeO|Ô|5¬èw#Ò ·K›ý>khþÓÕÌSL±ÆÌ…K+)o|sŸÛ |(ýfXb°Òt»+RÙ8SÚ®]¸Î!L„9(oÌÖÇ‹>7|WÖôùá ±´ðÄA1æ²MÄ„qÂÚÆÁWŽ@sõª^"+r•ÞÈøösÿ‚CËû7øÄšž½ã«­Rk;P)¦Áo ÚÆn•™„“•V8# )zŒ úþ ýñG—àÎ≚´:KÞi6Þl³+¾n"P0g¯PGQZ÷Þñ'‰-“Æ?u]CÄ)ÊM«¸³ÓÐŽñÁÄ=xÈjÇ7µ‘DvöŸh‰hX‚Ãq·’>ƒšæ–5t5»>Ù½øßðÃÛ§Ó¤¼Ôäþüv¥aÏ`IäÿúëÆ¼]ûDëœmm£:iq?Ë€2Àeþ¦¾}ŸÄwóÙ‹Y£û,YâÞ?•pÚÆIúÕv‰¥°ÍŽŸÓ†á®X©ÁôÇõâ¹jâÛÑBŠêiê:÷Œõ]J=KûZe„BuYŽîrÊB‚3ïœV\Z=ð”ÞÞŸµ\¹ýäŒØ$û“Éöí]5¥¾­lo¼´wé £ÐÿJՌܞQ6ÆÞêõ®Nk»¶mʺQiÖ»ÄЕsÉ,qëÍ[[6òË´AU†tÎ;úñZÍ<¯†àöúPbˆ~úÜ8Œ{šÜ´¶¸¸ŽD³˜])ááÀÇ¿ÓõÑXéCÄÿ–mòއoNIëÞ£™•dp3iw»Hd`眷ÝÿõT‹§3À-cD.Ü…‹©'¨õ½ý F¢0I@qœg^õBV³I3ö*nˆísKšÁdpïc²'—FÕb¾`'ýßñ¨…ûZy¡Eä¥Âü¿º ¬pIïòŽÕÝÉ6íÒÆœŸºSå?ŸN;Ô’¾öùTHëÝ{ÿŸz9×`²8UÑdgÃît Ø~P}xôÍt?cÆ<¦S2¬…©²†æP”ùPy8Àöàþu+[ËrÈñ[#ù›wʸëǽ'2¢‘ÏD%…Úݘòy‹Æáøúwõ«Ö{ÂÞgFà“ØxÍt6–óºyŠ"œ°?—ÿZ®Ç à äsÇ…&_)‰m¥B™Fì¡JäúûVͽ¼F¿(_›®:{Õ¨àV%¾ëÞßç­[¶´}Ø9Áà{~5!am¡Š–8m„˜'’NAþ¿Ò´ µ¹’XÕ°‘®@÷IúÔñZºÂ$ŽçØ×-â¿ižÓÆ««I,¼„r\úÝ;œQr⎮æîÏOŒO¨Oo[‡2º¨'¹ô®Æ9ðäÚ,ö¯§ÅtÊÊèKN¬§ ãhÀükåRúïÄúµÆ©ª’ÒLÁ’ r±¨á@ì0?Ƶít”‘U\¬K€1Ÿè1JFЉ[MÑf}Ÿfhßrýàvž8Èž•Ý[ÌöÑc û¼=}ª•½”v˸r¼,ŠI'ó銵+.dË#º:œú Âsèk®¤–¬šâ]©)ãœn1“Ú½I´ðÜ–áµ[ãà–Ýì{ }êã#‚Ö{o›$äpyÚ~¼~¹öX17á:±ÈAø…fQèvZÂõtf×R„&€ísÏ?C^ϧë: úE͹u\¢B1¸ž: øWΖ:…~ûu ³h­Ép…מ:çŒús^Ÿ¤|1ð}ä±^YêÓHÑá¢xŠ'Ný7 w LÚ³¼Ÿh SÛ<ŸÇõ¦oäƒíÎn†ÀÀüÍ^·ÐôëhO,ÌËÏÌG¹õ«ßÙvv°,p]¯ï áÇ#·AM±0k=LÈDF%le0øíúÖ ³ÔÀó‹ÆXª²àg'ÖŸk¤i3IæB¡Èî~•­›i «8òÕ¹láEM»ÝúGÔLbùv˜1ƒß¯5hF¡‹Éþ·È'îúN]Oˆ á¾÷Þ'ƤM&–2ûÛæ9'‘ô§c6X´¼:‰t;¸î…üL­!ûJæBfF¬›·0#× àY-á³Ò#×:KÜiò™GïÚÏ"·Bx䎧¥uÔ:e¶•¯©G˜´ýGO¼}ÇåÛosüÝ>P@'è+…ðrÞi¾$ñχô†ÃÅz¢ÀÎÛÙ£˜¤ÙÏB79ÚGjÕü {MÆŸk¬øh—d¼FA»'nxÝÇLs_¾/ñ”~ÔD×l&•­g{Y<¶~Ö$Ø' 9Î3Í~¼xZIM¤ö·3¾Ö?»f xéø×å§íYáígHø¥qŸ y»·†ò#¸l“nUŠžÄ dfµÃ“;رáÿˆ6šÖ °èº,¶QH æ[…rÔqÇÒ¾…ðÖ«¨Ét’ÁÍ}óe[Ðúõü«ó³ÃW~(kÝ—14a@ÈSü]›Ó­{W…üYâÑrÂ;”i#<;u\cŒŽ¹ôÅtI"›?H¼Kà|JðN§ðÃâ%¢Þèþ#°“NÔ!VŠd#8<|¤‚:t¯À ëŸ ¾|A¼ýš¼eð6öãÄ~•`¾Ô"ñ¾§ömN ÇyojÀˆLà†òÑŠ&Jîldþ´ø;^ŸÄñÿex”^nb‹’³ÂƒŽ‡ë_0ÿÁAþÝøgûöªÐ`“ìÚ5¼:/ŠÌ ²H¶&LÛ]’eŽ \Ç#$ÞØU$o‡jRå‘͉Z]^“㟠iEäðïì÷lCŸ›íÞ(Ôæp ®÷ÏZëŸâï°CðGÂh™ÉYµÛê}Aa_4躧â‰í®¼?â;ˆï‹‚-®¤yH Ú~m™8ô«°iž.º¸O‹m-|É °{wò˪läwéªQèqsyýuãŸ_´§ìÿà9 9æK£ôçšEñïˆn"k§ø3ðßO‰_`Œ[]I¸§-8?¥x.§a‡«¾›®ø†$”3ÎS(ÓŽ ï^—¤Oáh-"ŽçR³Ôä>c+è;°÷¡ÒD¹>ǧi¿õ{‹˜ì´ß‡~Ó™ƒ0è†olå§ÉÇLž•éZGÄoŠv1$‰á߇v¥N[AŽI¹ýùç>¢¾`×t†—z“[êZÆx£Ì1,˜ÂŸº2Üò8b Ñ4ë½>O;Ã×ãÉ|æyTà€p?^Å ö>åÓ¾>þÐAD:n£á2VyVž²Ùž‡ ûÏ×ø×ûKíIÅ §†rÙôû(£8<’D$íšùŸOðç†uXÕµ_A ÑpcŠq¸l£¾+¬Óü §Yiêzv«:ÍÏ—H8Æ—'©:QêR›=²÷ãÇ!t¥ñýY†àmÞðGm±Â«ŒuÈ5Éë?µª„Ey„¿|;­tTTÏðÆ±§×ÍkCñ›MhÁ´ÓVG'÷‚2@õR?‘¯.“á߆íaWšhši ù§|«ÜØ^}3ážšÐË}â*-£-ÚÕHQøÿAÍU ñè?¬¡MÃOˆùU}»r9éT"øñîlq“ÿֹT×g·`ƒ]°–V<$W*äœpþ¦ºÝ?Oð¤«q¦iWw¨:lnpúˆÈ?†jBZfêüwñdÊ-N8U¸ÛUþ™VoŒ>$q M^0N7r¤‘õ*OÛé -õuWÓ~ëwa¹Ä:MÉ?\Qø“]ìÿõ‹þÑ{ðÓ\¶…€ ÷v‘B=@ýäŠp}+.t.V|ó/Å-fb.Riˆ8 mPééNâ'‰.ãilïnAHÇ޽é_KCðWWíø3ìÌêZîïOBGb@¸${t«×¾ ½Ð.á·Ö!Óô³´6Ùõ V}G—#QíùYó\,ñeÄr¥¹)ò…<çž¼c5›{u¯Üïßøl…Y2äcׯ¢¯u_YIS\ÑUò¨½W*;“°`cñª3kß É?eñvœ³ÅºÏ61êBô£Ú ³>jþÏñ}ÊæhnæÁ8%˜àAþ4øü%â]Âkxeóƒ.þ™ôÁ¯q½ñÂè]d¸ñ¤Š0~{{ûzîÁª2x×à´+毊µ›Ð1À°Xºû»p})󾃷sÆî¼ñv ֒ܨÏFÑŸ|ŽkGKðGŒæ" ½0F8ÃÈB•ã9?LW«Éñ3á¦ÑöHuûüõ̰Áœ}?š?Š¿¶¾×®\‘ŒêQ ÷'4œØYGü!:†c·š@ªIzd÷õ«‰ðÂØu|ÉÏȪÇhúœrkZ_ж]}¢Ãáýã@¿{í:›çŸp˜ü3N¶ø­}l¾V™à=.<©»’k–÷ÏÊãPœƒ• ƒá·†Ô©º(äpvMŒ~&­IðïÂ|ÐÜ•_ñ4ûˆß®ä0A¶n%ìÃuõÜøÀí]U¿Ä_‰qãû_M³€!²…¡ÃmÏaÚ'!'‚4ó*ùQKrý¬NÏú•·íZWÅž‡©²€8†Ò_˜ú‚ÉŒSeñ¿Ä+² >&¹òÇÌ<™vßð´Ë½oÇ—¨>Ýâ}^uQ€ö| ú•aøTrH4èu_ üMtþOü"šœŽUœ!E OLáŠàWaÂ_ˆv+æ¿…dÝ\ZÆ qž²ñø×…Ýè‚é¼½Ròîi«Íq#’~¬Ä‘é“Xòx ÃË{»hgŒŽ|ÅVçò?­L©Ë¸H¿†õ»F"x´[&‹‡[›û\«~{zV^tkg-«ëþ·(¬HB×j%x\>ÒÖ=¾ŸmDeFÅΊزÓ&xÔŲpv®Ü:Å%N]Í,{7ö·Ãô}²ø«I(É g<Ø#ÆØùãÖ¥“\øqrÏx‹Í'Ã¥Hüd׌K,ÄÅUA±Ô_ñ­Ÿ ø2ãÆ2Ý+Èl¬maiïµ;âRÊÊÝç–iTs´±àzÖUd¡¶\)¹;#Ú|máëøwNÔo–dûT÷>LpÛÃýégwÈW§\œ}kÀ~ðÿʵ„øpo4ï‡z”äjúü§ÊÔ¼Uåø÷· œÃnHù[ ÓJÑþ(øZ=:ÂÎçKøW }¦K`Ô|]r8[»õhl7AmœÈ.|§ìkøfûN²ÒtMNÎ#·DØ…c}ÅC¸éŠÇ•}vj¥uî.ÊÄcž.¾.äÎú}•µ¾áÛh¬,,ãX-m­Ô,qFƒ…QÛßó­]7NyŸÍsò¨ÎGõ¨l4tyA³’S…r$^½}k¯6· ÞX^;uö>õöP¥E(«#äêÎs“”ˆ˜¨UŠ-ÞW’MuÚN›¦iÏöA–K±ÈR~XǯpMsÚYÓíXM¨»ùÝrG ô«—š¿‡’&)2²ƒ‘¹H9÷È…Qƒ½Î•¼Q¥Ç®KÕ°üëž¼ñTR!Hbwç<µÊÏ®X*“ñžFMr—> ‰ÁŽUBžsÎúô ò÷:ûßÍD¨ ?ÆIã±¹ë¯êP“‘n˜èUI#ó5Àjšüp)a"’xÂà’~¹¯>¼ñK£Ž»xþª¬zÕß5iU¸tPq¨$ŸzóícÇWñBù¸;Xt.@Çáƒ^YªøËz4ÈäÆÿ^¸-CÄsM݈òx!‰Èô銛(íõ?_Êq¸@:dàþþµÇ]x’îY<¹f}˜<òlr+‡¿ÕüѱXýïByÿ Ç›R9$’NsÆy«I#U#¯ŸW¸ÆészuÅb\k>a*Ï·ž›¿¥s¦üÈóÇùÖL×AæcËÛŠ–‘VGA&¡3.Õ;¹ôÀüMPk¶vÚÃ+™—Udrì¹Ú8Ó>Ø +I;P;2ÝòÍ'ÌG\sQKpЇË'<ÖWÚ ŽÑÅò‘éVt›)µ+±j¤…927e¿j´ÄѳgÞ¥p±ÛIÁcÔ(Ö³Ùé  &yHûÄ‚£éY–ö -4¯Ý„;XâoSY;8ÀÏR;S Õ¹Ô¥¸¸ó¤`>\;ûV•´Fà,ÌÙ$rŸOñ®f’K¥EÝòßR+½Ó-‘çŽ&!7|¤ƒœž óÅCÜ KxØFb‘¼¶8aëÚº[`Œ¤à7cÞ«Ç /÷S œVúi×x BÙeÎ.À#EqŒ`Œt«br¥P1ÐüêdÒo™Äl¸Ø1Ÿéõ«-¥ß†ÁÇæimܺ… >µB3näùR*J•=@=ñN[iÈ»a³?t‚ÍAwo܃*@粓ü>¤Õ$À¶úmË*µ»‡Œ“Ó¶{¡s–ˆ€ü¼:¤âò-ÂÜ0húí<Œô«6º–§oòݢȸÎÓ‚qêéT!_<‚Dy%ºÎIí_üHñ›hê~1’'»ŠÆ"ñÅÞÒÿ j£»;mU9&¾ªøŸâ+Àw7Ö9[«ì[Ĺþ&Æâ}‚rq^uû'|(‡ãíCá jðùº/†˜øÇZ Ç$QÊè^õ¢p9È„Šñ3ÜÂL-Jóv²=|« êÖŒR?h~ü “àÀ/ |¿+&¥¦Ù}«W•F<íZýÅ䟌ÎÀÀb½F;½.ÆfÔõÙ–>Æ7»»‘¸ o—Ÿø ššöææòêKÛÇß,®dsž¥ŽM~]Á]¾8Éðwö!Ö¼7£]}[ø¥{„¬¤,Q£°”µ)© lã”#Ñ_ɘJU3láEý¹~ìµ\p¸Fû#ùiøµûDê>6øÃö¢ŸI¿×á,Õnµ•ŽÁCJ–ób 8„‘—”±Š èÄœu4èxvñ#´ÓžãS:ÍÇix «>ü`– …Îxò=OIð-÷Á+Æ>-»¼Ò5ßÝÞèÆ9@²K Æ+W™Nß¿å`qƒYº}—®-´ý?á‡üGⳤ¿›©jí¨¤V–fFÂSxó#Þ2ø òôÉâ¿­ptUQ§’?$ÄÉÕ›“êÏ¿¾x{Rƒá¥Ž­­-–Ÿgm§ÝA4±²ù¶×Ò©3®,ͪĪr0I럱w…>2x¢O |3ýŸü2ž"»Ð´h$Ҭأ±ûNT]ꓵDd»eÈwÁ «žý˜þøËö¤²ðïìáà8ÄZÓ]\]ø§QuÌZdq0åÉ8Ua¶ 7?c$[¿¾|"ý—~Að—àí—Ù¬aÃ]]I†º¿œ(S=ÃŒb$*®@ñaÆôr•ìiëUþ¯”pü±?¼—Â~ih¿ðGïÚëÅzŽn~.øòÙi_a]>M]YüÛ‡\¼Óy𤅤 DP‹À$ùÇãü‹ö‰ñ…§ˆ¤Ò|eà¯ͬY-„b;9t™D IdF¸MÎI%óŽœWô'‰ÈA'vªòë67gÉO3'¨#þµùüDüÓ›˜ú•ÃtµáKö‡ÿ‚Z~Û?³Á‹ÿüað>½ªhº}ÛO£j:m~ÊÐDUÑš;TiKHÄ« QÎãù_m“­øoY´Ðçm¶¸Hº ‡B¬ÀüÑÈ!—Žø¯õÓõ}KI'û2y"~û©?\u¯ŽiØ_ö1ý¯n¿µ>>øËþе¿‰tu]7YîR.! ePÀ.PèÝM}fMâ½9>LtZó_åÿòñ|.×½Iüóe¹ðÇn~ëÒiÖ’ZE mq©ÜC"´¿f™±²C»Œ7AŒàqÞ¼£Åvº·‹üu§Ã{y.±y Fï æy7(ØÛUW< ÿZþ“ÿà¤?ðGÚÓönøqâÿ|$¿â¿Ã bò-W[Ô´ëoâ{7Fû×ÖäMnŠtö‹Ž»¡DÜÃùþð·Ã=nÃ_𿌯§`×ò«Xº°H££"¡<‚¸å…~»€ÍpØÊJµ §ÙŸ3[ RŒœd¯üñOŒý÷aUäçs©<ñôÁߨ‚ÇâF‡.£ñ²Ú?ø·Å]Üku¬3Ku¤ù˜­ôû¨S÷o`HÀaÈêEuÏJ ò’F…G´Oœ~ üvðßì=ûUøŸÃúĺ·ü+L°]­žg²Õ­ Þ²A8”íÝŠ€³ÄSœFkö£à7íWû=|DðVƒñá׈Š6¯%ÒGma4l³Á!I#®‚1ƒÎGä¶ŸðFoÚÇö”øiðïâw1iW×v7–ºÖ©i}ntÿ•Y•ƒM,D$²$jŒ:*çöGìŸÿõøwöy[Ÿ¶üK¾»·ÔÉ7ö2< 6¬ÊЍ«"¨Àe à çUn#ÁR¿4õ:a”U¨¯b俵=¾™v÷ÖWW ÀŸÝY˜B‘Ôn‘·®á^oÿ ã…ÌڵΛ œ é8{ËØR5åùväÙÁ‡&¿]¼%ÿ¾ý™´x¶ûZñ3§ÊÆYü˜Ù}ÀÉéÇ¿zúkÀß²ßì½ðÊxî|ð÷D¶ºŒ—RÛ-Äã=÷É»÷ÇäWãl2Oì¡MjÏàÿfÚGý¾niغ߈4Ý3[¶ñNŒ4‹)Z4y›Ì½³ó¶ˆÕ$-4hwlÙ&20+ûÜÓ4Oiú½¿o!‹@Šî8îÖåö0y4‘˜×8)’¤ç’+êè5;Ë$–Ò x@ùR"}¶¨ô¬]gCÑüMh¶Úä~j©Ê”$[ß®+ฃ:þДgÙÇcérü?Õ×+züvÿ…eñkS·ƒSÑ~ÞöêRîw@b¸øT©ûÅOCØWǾ ý—¾ߨ]/„m Ó¥‘Lb(”*džƒ¡Pkõ†ïà¿…æˆ%½ÌвúáºþÉ^ü°»V)¨G;·!#Ð2ÿÙšÒ³ø›àio<›½wF³y~dñIÝßå'{f¼ÿO¾ý®ô[Xôø’Öî8—`ò`¶˜dw!ÕXþu‹qñOâæ’å<t4}¥‰Ç‡áp;/”ŒG¿Zì³3=òËT´ñ-ËGaªˆÒ2|c!ýÆÞÕÔÿbê­!¹þׄd±IËc Õò~Ô¾¿Ú/é€+òo´æÓ>€K1ÉéŠîô_Š÷úŠy¶)ðÒÈÛ¶éUÏû8àÑ{ £é=>Ç^—Íž;¸e]¤1·ã9¢“O¢Ü+.̬( ÈÁ#>õ‰7‹þ%-¿ØšïͶrª˜Ó CŒæšm“+u:È>xkP‡í6Ú’Ún$0,Cê>Ÿ…Uo„^†fŠA¤` }àÜþXæ¸VñÏÄ3¹$e‘”ýó§zˇâ/ÄÛIqhñ:’NÁóõùM=H÷OL ŒÅÎ{ “ó°Rr:éùUMOá¡d ½ÕâÇòså`àCÖ¸(þ*üT¾Ñ, ¸‚¸ò矠5||^ø–² T¸„mÎï³ÅÆxî„ÕóH›¢ûü‰í¾ÎÚ„¡$ÇñíÅs×?%VOøšÌ±Y›Ëiã§#ŠÙ_‰¿.ܪj,Æ5ß½âqï÷qø[š~)g«1  q1>œƒG<û…ÑÃÅð9î šaQ–+£§ÐUy¾é;šÞúÒkÆÞ`W>ù\Šõø•ñ*9>Ïs«\o-’<¨q¸÷9LVÄ~;ø áä±ñ,H,­ K‘ïò‘ÇÒŸ4ÃCÀåøiðùfò+èÛ Þ ÛëÆ¶bømð½bmq~>`£—/¼:Wµ[üBøÈÈ‹ˆÚp‚Èô$Ç>•"ø‡ãÜBGÕbݹ1!lg¯ (æ¨-´´ø=ÉÓ Ö/DÈ夆8ØöÃg‘ôÅuvÚgƒ-G“¯¿v|ÈÞçÌaýÞ¤žk®¾ñÅ]?̲›VóÅå·ÆÑÔd+ÏcŒ×7{¬ø’âö;F=6vÅ-¤lëžOÝP3JwZJè‰m0ŽéË û¡Hç¦Aû¦¬Í=¼ÐùwÍÛ€å" ®?¥dhŸþ#i,~Ö-lSxc·ˆƒÛz¿…Z¼ø·ñ¢Úý’ÇÅSǽü{Åo³qêHò¹ë×4›¦³m#<;|ôÜÈ?wÐÏuç¿AŠ—OÖ|!D¯§Å"¦VBé’Ìz`ŸÖ¼öûãÇĈÇ?‰¤|åŸe…Yl6ÓÏáHŸ~;MÞ.ÔÊŽù6¬ÃŽ™1 W™jǯ6¥ðÚÕ|íFÊ(ïyŽí“ŒR¿‹þ é°²ßE I(Êê`vÇ5æ¶_´ïÇ ^aâ]I¨+Ï÷B?Lbº+/Ú“ãÕÂËÞǃÊÉ ùèNáCùQy3Nh¦Ÿñ#àÐuxô8®JâU»³÷NsŽG5½oñOർàÜhå¤Ü78aÑvž?JƒãgƽR¼¸øŠ–Ò>YDG°bO`GÒ›'ÄÿÚ-â ÄM>aÏ”vò±ƒÓŽ•J3‡¬i?¶"øK}ŸÃÝOû¸Áò4À$ÕIVÿk¬í[öãñ樌šŸŽuVB¼Ç4È#~Óvpˆâñ‘tŒ¹Xìå·lƒÐ—?ŸZâ&øÝûEé÷qܽä?¼Ü0 TØxýE’3º6¼MñÀÞ+ûCkÓAp·Fi!ÚÀw#(:ûq^;sáÏ„+vnl^áƒö2 M¾ùíéÐ×¥Kñ·ö‰¼µžñn­cŽ- ¶Œ«äõ†8î0+2?Ž¿aÊ]\ÙN£#Ë[8@8ô®*bæG–êþøv]ôÝÖñº€ePGcéŽõÍŸè3ß ðþ©4á ,A£nÙÝÀú×¼Áñ·â¼ÎÒZ]ÚÙI»Z@Ä‘Ð`¡ò¨5/Úã¼²ëÄC'ñéö€mô'Ê禧Ø;ž5½Ö‡|³i“yžVWtÑ£ü纕»Œš‚ÇZÕYdIílu!Éó4æv¸ÆqŽ{có¯ âýª>8ÀÏÇãs +DÏáåíM›ö«øëpq¨xÝ$’×ý•LÀR´û Û¹óíõ¼Ó8»Òt³2¹¶µ›÷P¸ú×-=ÇÅÙ$Úôñ$Ÿ)/cµtÁßÁ·Øézœ˜9n¢?Ž ÿ*ö 'Ä´n”¬º}î½}›%™ >΄Wé2þ×w1=¾Uê]>øéô5JÚ{ÃŽñ¾§¨ˆ91ÎG¸'Yÿ*-OÌø+Nñçíe§±,šÅæÁ1Ž9«g¾MvÆ/ÚÂ0C¢êsã9hm2úä˜üšúgQý£ü''_ÖtUŽG Žü“ŸÓ­s×þjN&øŠÌœÂYaÏ8ŒûäQìÓû!ím­ÏƒãOíæ<šÿu­Ãæòü7t¡³ÁË,MÔztîk¤Óüyã=Qs®|¿¸n3"iºŒ.O¸XŽ8ôW¡/Åo†& Ñ|Dñ]¹Ps¾úo/?î‚ÏãŠÀŸâ>ˆùk‹:¤%Tmþe9ôëœ{Tº+±QÄy“ ¯Ëy üKl’ð„ÙܰUï…ò·sÛpõªó'‰ßÊxþxºMÀ ¤Ìú !Qøž*­ÏŠu àŠî‰Z® CæPuI•B¶GÈO™ºsÚ¼[\ñ.‘¥ê3C«ø·ÆÊÌ ÂEªl9îÜ0Ócµe,+z"–!w=˜|<ñ®´4ÿ…~/ŽPI"M3nzüã¯b£jÝŸìãñŠø´·üU‡ù‚IfcP¤c³–Àíšù ÿâWûŠÿ|Ol»6ŸÄ¬äÏY&Ã{ç5ÏÅßHìÿ5Qæ¤>¯¨y@è?|6ÿžjVO©_YGØš§ìïñcE°{­W×ú]¶i¦]T­É*1ÜŠóÕøgâ’7iš„Œ“²+O>ªpsúׂX|Qøqo(?¯¯ÌË‚²j·N™Çt’VÉã¸â¨Üje|YbwòYçÛ 8ã’y¤²÷­Ø}eå­ü0øÓq¦Ïkáä¸ó&U„ÌîÄ®@gSž\ö5ÖÚø7≧Ûi¶ö…ílbòcÀáQG]Ò0䞤ú×ËMâÿiñ ¶ñ”VÛ eûCŽØe$?t:wÆ„Ѐö¿n%‘:ºšu`Œ`ŒŸqRòö5ˆG¬ëZŸŽ’sìq¢Œ ÇrÀsô¬KI|Ehd¾SÊ€I>¤ã UÝ#㽨YïøÖxác´>ØÈÜ ‡Lâ¬Íñ‡âÄh¾O‰4Éßæ;¥·‹ûcô¨þΘþ¶Ž'SÓæçíº‚®AùÊœþÎ;žù®oûOìh°èðN¸b,çÔŠô¸~<üN³ŸÉñ/öF¦¹1µã',¤Ž€â»(¾4øjæ>%ð®œFó&›kc=x==é< NÆŸZG€ÂWâÈ,Ùmï."n”ïe^8p+bÛÄ^#¶“íÖýÒNq#výÝNqúW¿Eñàuã±Ô¼7d錘à½U%OL† þµ™sâïÙyÝ‚h—v¯“·±K·ÛºÕ/ %º)WOsšðÏÅ?ŠwP\Å©xŠY!t’bìN2}s]ç„~*üh¿´ŸP›û6q#3*„`Þ]¸9õ®>òoÙ£WËÛê7vŠØÚ) Ý8(Üþ™¨¬­~é2¥ø— &7×ÊÀ°çó¬å…—cEZ'ªÚ|eø¯=Ì–÷*ŽˆH-a¸ŽÃ'8üjìŸ|k§îÔçšG’^w:„uÀ<\,ÇÀz„-“âë8ض1棾µBÛÂ7JË5޽o8D»·Û=ø¬Þ­Ñ^ÙùøÒ³™SVÓݼ[ZY&_<`údÍZO‰Ÿ õ dy<5cr—l¸U$ŽäÒ¸©tˆ6p°ðÀ†A)S#¬€–ÛÓãÛšáeÒ~/èónþÎfŽO1~Ew$u)$Òö ûSÔg_‚:«³j ÓÐ>Ö*°Fý÷Xcês\‹?gïØ‡^°¹¼ñ'‚–)fÈf¶/n›§Ë ?\`Öþ$A+Ý_iìB6~HNæç¿µfKâïIr˨hò¹ÝÃg=ºŒcó©tÄç¹Ïi²çìC£j‘Ágo¬is9ÚÆ+ûŽïݸ5³«~ñö²&’øRåÈeþÒ.§¸ÝÖ±î|O:Ý&§6Ÿl¡FY•²Ì¨8çð®«Iñµˆ‘.®, H%¾`î;æ¥S·A.C‘ðO_ÙÎÃþ@;Ö¬ee(’‚`3×U8#Ôæ¸ŸÁ1<â±?¯Ü ùWì1+1ï»oÊ~¸¯l½ñÞ‡%Üæˆæ'_œ¨¿Oz³aãý2)2´Œ®€ƒ·Ôõã¦jœM`ÿÓòÛCÂßð—~ʾ(ºÃG7‡<­f6Û’¢Q&=2…#Ö¿£uš7 ÁÔœn#’=}³_Ô­îmã-UðlÜÛk¶RÙJó· Ç^Êyü½ÞhÚ„u;¿ë R÷J¹šÊPÜÐ1L‘לgñ®9ÂÚ…7tY´ÔÀ€IÀâ­$M b˜$dËŠËKȕϔw?ÝpFx,z‰—x@>nê~£éYš‘f½ò|às_:üwÒ`xlõ¤ù>Ï+G.sžµí·+Sl$Æ`Oé^kñ Øë>ž ?xùBsÙ”õ­i+2d´>TµÔ›{Ò†aËnS’3‚@õ¥~Šþľ$ƒÁ´eî…ö§K rÒ9âîžmÞ[åOŒda‡?&;×ç+8ÜÓ¶ÖFÛœä’{J÷¯†Þ(›EøàO¶Ñ,¯ ï¡B˜ô8>µÓ#ôƒâO‡®<;âwA¸˜Ù\¼j¹ÚÜÊ}ø<ú×#ðÖx4¿ؤìZ Ñ%™.¿y§B»±ØF=ëßÿhý?Îñ¾â­6ùéñÍ(Ý…WˆàãÓ‚ |Ä·2ZjV×°°óíe }§={g ymæ›{¤››l¶/äÜ:dõȯlø«tº¿ˆ4ß@vǬi°4¸òÚç\VgÄ‹Ÿn5XWjŨFÿž£õäzúwö.ýŸÇíwâÉþÝk«áçÐl¤Ôqmö§žÝä 4H ®¥£e'vrÜqš‰ìTOŒî/m,ì‚ÞΩ½Ë|ŒON$Ÿ¥{¿Â/ÙOö…ý nV‡Þ¿’ x7/ØÀ'g|îy¯èçàïìû/ü†+í+ÃãÄÚʰû[o´¶OX€ ƒ¶Æ+î3ú—ØRˆ­,a@-àP=`a{gšÁÉ.¦ªö?¿g¿ø#–§ðÿâ^‰ñgÇþ;ŠÇSÓdÖÂÊpdXÎí¯)!$vç=+ÒÓÄ·>ý¶|YðÖöö!oªÖáG³*%SÈ`Bàdzúf¿ZõÏ|6ðåËÅsyªŠHX¶îÖïý|ÓñnO„>4Õ ñ_‰´¸f»µÚcyȶ– À…aÒS»8ùx#­cR½µ*4ÛgA­þÖ%–‘ðßLŽð;0PH°µ .Öàž:œ)üëÀîþ*|Iøn–ö½¦¦šŒàxQ¦†VÊ®e;ˆÆrT“Þ¸sÃ?u«ŒYØÅ¹IXàUÌC'9ó_%å\Ιà?xS^“UÓ­Ö;’‰¹Œl£‘ÆG#µpTÄ7±¼(®§¯iZÇ…|7möhÒÙ†#çüéˑɕ›’O\’jXµíV+¯í&`re K.O^¿*ôäkÓZÒÖO*9Ã9û»:cý+ZãÅ:¬Ò(³H“ s÷ŸÔÿõ«É½ÎˆÅt+O§Ýø¢Sªø–I¯ds˜Þá÷`z?*óÓU”Òì­ö=ªBœò{úýicÕ “²_1y:ätéßëR%ôHØRxÎxÆ=ùì})ÊhE¥A&X•XÈ9ç!G±ëO‚ÆÛÊ-´‚?ˆùñü«-.ZæVšá™±†U'Ð`U°Áx·MŒüã°O·­>/#ËwŒÎÄ*ügœãþu%æ‰áûöjV÷`i# ØúóùV„pXÂÆÚ4ŠÀÇlñÛÞÆ,jXÚÄË&ægVãÐõëN(-#_“sÊß;{.{š†+YÕDãnÅ#œã¯êMkÁ.$Ú&YƒpÄT¹jô(`|J¬à¡ÃzŽÕ¡ íƒ+rzuéïZ0B±¢Ë€9ëÇðÔïq +K,{0N\pN}‡j‡2£C=¼Qì`[]Ó„EhŒ6ï˜`‘è=q]5¼vêI–6p0À“×éô¯Qk¿€—?éØßn« `ŒuëŸÓµ_}Kàc‘»LÔËãs€?»‰OBÒ±æR.Õ¤Ÿ/§½"(ÓGH ŽýkÓíõ€‰rÂm~ ¼¬Ø=ú6 z?…¼CðÆîà[xsL$ç,ð/I95ÎÕÝÆx Hl.W òÁ`OÐWUcðÿÇ7Q´–öB5e^áÄ+øIÇÒ¾šR¶„†‚(Ñ}PÓ¶S†¡4Ë’ç“1“ü©à©ð‡Å×#ιšÎ@ܾanGÑk§Ò>­…×öޝ¨Ê0Íᘞ áGnrMzÄ’MÔço8ýiÆî$]®7¨o™Xn öǽ4‰r±R¸Œ(Žàºà`¹;¸éš½k$n^wF}×Ô‘üé‘[Ù†Ã.I$c#ë[ÚG:Š$œ¾óõý(²%Ì[iï¦òÛvÁm£/ozÓšþûM¤‡{³Ù@²“œuíŠlV¶‘í9n˜<žó榎bvÔ¾k†›WLªüzûÒ3cq¨Â±¼ì`.¤´M‚v·P[8<ô§%íÑg·†é÷GÏ ì}G½kØÉoªÎ>Ћ38änì>³J'Šl E,[h,»vÜP'ä¸ÿ…{® ša0uwÊWæúÔúWðÛ\>5½ñ^»¥K֙͡bšÝĈñ­¼!aìGn‡šì¼[jçÀž Óã“l’è÷ñ89hÉãê>¦¼ö?ý¢õÚkàã|H¹ð§ÃWÓ¯†ºŒ¾rEŸ $läÅÌÀäü{°›û7ìO h#n’o,áþ/aÍ|¡ûTø?ÚE£Ët,î"™&µ¸àªïÈ’6œ6wWÔZD’ý®˜Ø’1ä‘ÔôéÅrô1©éRAèžb¸=Ðdþ)Ò™M]‘þÑÅŽ¡?…îî‚Ê“ùN à7®Ö¯¥4hgÆK°@Ã*ÆXtèGúתx‚çÞ9ƒPŸdp\CóY2C'9ìzWкðïˆízÛF„í e±ë‚y®ÉKCž&þ‘&™m"Ëk Œ£åbñ¹Q€7wZ÷:ÓJÖãºÒ|KeþnÖ×¶Ò 1O «±ã*sTEq£Nð^S[Ëw{9tžP¡±è zÎ7‡ h%¢“ÈnJž˜•‡´jI£GÕ™üÛÛ~ʾ:ø1ûTx“ö^øg¥k¾8û)«X.›·—ˤ\6òH›‹¿”?ÑžRNç\ž¦½ÊÛöKý¬íµKkUø9ñ t‰U„¿`…œG—-¾X÷Ûë_XÁM~ xÃÇm¿iƒ×ºÎ‰ã¯‡LÒÍ ßÝé··šÅòÝe²’9\ÆQgÁöcÁ” µÏ‰ÿt[vŸâŒuYÔ–VñN¬ÂEoºIû^YJž„‘šö(Vœ×ºÏ2¥8Åê~„Xþ˶ž½mlš×ÀïżƒÊšÚÉITÿWævFN2Gq]µ¯ìUûYÜ8X~kÖ,H#Í[PðÝwÏÖ¾Ò¼5ñ Ť?ð‘ø†yÃ~×®jWŠU†N|ë—ïÛ8¿ Ó=äK¨}£PóJ¬°‹‰å„†i©Ô½§VbÜzwk?°Çí5âKÓ¨ø»ÀH—‚!ûv³aT_º6‰›œž„Ö¯ü×ã<±¼7f…gåü»&ñ=¤d3sƒµ›|¥Âí'Ä·.!´ÓÞ&Ú‚9\o$äàî$‘õÍ—Ái—r}·HÓ-#á#&è2v÷ô§iÿ0]v>å´ý‚
üSn<-ðnÈï×>9E,JY™tß j7R7¨]Ï·ñ5ã7+©ÜKs¡@bˆˆËØ{’=+RÏEñÛ1þϺ‘˜ä*ýÜ/\c½ŽÚ±ó®ÇlÓþÊÊ™Ô|Yñ#VS¿cÐ,­cÿɉ2¹ö¬[ï~ÈqÛù áoŠºÙÆìO¨i¶qß>ð+‰¾ÓuÛxúØFPe·Ädö¹;O­s­®ë7­¾IåÂ4a‚m#¾3ž*½„Dê4}ÿ?í5ûZÉ$’Xøßû ÝäiÚn›k´ôÉÿFr? }kçãoí5|âëTø¯-Â)D‘uµ;O´1Æ0>™¯ÏhÛÄÒÝÍt…Ú"r ±f'Ô?m¬sÍg ¬%–Y8!ßï})ûê6}‡}âoŠº¬nÚ÷µ‹¤|Òk7?¼=Éýà$~b³²cÔ]¥¹Õ¤Õ–3‚nî&œGîZFoð¯’ÓX†q%½¬ÉmÙ#žsØv©cñ‰cv[–`¹qÐö^Î!ÎÏ­mü+mi Í<–&Ì­nŒ’PÅrqÒ ðtŠËªÚX¸UpˆŸ.~ƒ­|­¯¯Ï!x­îæp0¾Z9 Íþè f»Hí|eŠÏ{asÝ€'Œ†oÁ€?N(äa©\÷/øGþióþîÞØmóáælž›Yzt¯6®s‡ŠÑÝPÀÔ}Å ¼Zú!2k’’®Ò¾Åç§ÞûÇé^Ûðãá§ÆŸ‹« ÿ |®k–Ìãê7‚ÌžZyü¸¸=Ô¶;×ïÏÿ€¿¾´q| ð‰¡\D‹ùàׇÄÓM¹‹¹$ó^¹©h«â)3â«‹aºì»™š,{B¥cÇÔó19úåµ-ÎÊyk½æ~é±çÅÉæQâ­{@ðü½ ¥½Ü¾ Ô¡·´cSÿm›Å}áÏø'ß‹/Z ÈõJD9-5ì0i‘°=–0Ò˷݆kõÊÂÒÓCµû“6q€ËxÖ%{(­\I`[9ë»&¼içuå¼I`)ÛD~LüWý€¿j½ÃPIû ø›á­¦´ë ¹>4Ò¯µ’Ï&a1H‚óÖïžÁùÅ_³ü ô½Føð›ÂŸt]ý.úÇIñ²@ÚΨ…DW2Äú}²x±˜íB) ±%F?¢ .àf °ñÜŠ«5∎1Ü u§öÖU•»‰àÙv?›oxóþ ;¥fãâ‡ìcã»s#n-£ëZ~¦²Å&1Ž™Åxò~ÚŸ ´;;‰~0|?ø‘ðþöћ̰Öü'¨Np§„öqM S؆5ýXG­^Z%¤òÄOR®ÃôÍ]_ˆ'Øb7Òºã£àýsšö)ñ²ÅŽy’ÜþB­¿à«ðM7Ö×gÓu4zŽ…{f±“ÕI–!޽ñžµö_ÃŒŸ¾-i’ë­Ö„Àc„IÕ†ô W¥øÓþ Õÿ#°»VðE—Â}~2Lšö¥§•od’Î`Açø—õòŸÄÏØãþ à¸c>ýž¼3ão0á΃ãHN¼°»Ž×#Ð)'Ú»ã`¦×-U÷£åVëó;ùþ6|1˜y:uìe‡ð•~¿îœ€+5¾'ü,þt—0“ÿ=UoÄ€E|ªxþ ¡ùÐøóö6ñ40ÇÁ:n«à'™ ðÍ|÷âߎü ¾ßÆÿ³Ä-6x†dŠ++éš×ô+¸˜¬Ü½ŒŽ¤uR‚à8#ÜWC¥~ß_°çˆ¥K<]k$§_öDîÃט‘²~™¿¶ƒÙ˜Ë/«ÑûHÞ°Ô#Íž¢³pP«ÑŽk.oêIŬñ¹í»vGà¯Ì¾>þÉÍËŽf·|`$ö3Ç1Ï<¡Dd÷Þ3X±~Ö?³íƒ×â,ö%XãÍ ÏpÃÐb©N/©?R«ØýC¹ð‡‰V@Þ\D¨ÆS#ùŠ£'†|Jª7X´ÝÀR ÏáÒ¾ð·í+áOB—>ø£ou Iw:åeÁÈô¯^±øÁñ xþÑaâ(/£ã•X¤Îz}Â3ÍU×rkt} —­[«-Ť‘¶>óŠé46¶—Q˜lŒqŠùêßl&ksö+†S‚^ØŒãІô5ºŸ´‹>U½Ñ,f^AØîŽ@úŒgô¡4C¡#Ø%˜M&aÀ-’sRÛÁæ°ÀÏ>•ÁY~ШþVs¶uc¦¯OÐ~6|>XÓÎѧ¶$ 4j²ãò9ªæ3ö:¿xnêèý¥P°CÃ?šõí3ÃMJnŠáGݯæp6>’„${îÆ+ ¶o¹´»Šqé¨Ùü‰ N›7€AóäûWSðßÀ·¾2øgàí”íc.¾×@ÝF¢S [Àò—*x Ö¸q ÛðªëÏU5©áOøÿáÄí+â÷€§ŽßYÒ-ní¡†ò/:Ý…â„bèX°_ºAã¸ÅTmÔN -÷ý„´=:ÞÒÞçâGú^¡¹­’ãOE'vd\•åH猊åõÿØÅ]Þé¶Þ;Òm§³13­Åe ;q 2K0U#®qŠãôÛWö†ÓÝuKÝ7×wVñ˜¡š]2Eò£=Qq1$’x· ý¼~/Gw=î·á¿\O~T\<6÷1Äd2n>cmÚ@Çâ´Iw2åŸcä_‹ßµ‚ŸµŸ…$¾¶ÔotAló]ZÆÑBÂæ%•T£U€lHôöók`+†<’9¯Xø›âËß‹¾;¿ø—¯ZÆÚ®·;\jF"VÞ/,*C–9eŽ3õâ¾Uñ÷Ä›-.FÐ|),w·*1-Ò¾c‡=Qp2ÍSéS-Šp}QÇüO×dñŠâÑí¾Ïd¢1Õ›–jýÿ‚pü?FøCâo—‘ÿ¦|BÕ ­ƒ:muÑt6{x1þÌ×>|ê{£)æ¿%gµñ&½ Ÿ„| ¾]Å…¾‰§7Þ‘¯uò–FHT´Òc¢!¯ékMðw…>øgHøMà–Û@ðžŸk£éñ¨V 8ÄcÇ%Kw5øÏ‹Y£„Ž/Y~Gè<„çªê5°j/”ò±‚OzšþK¿àº¿µ‹µm‡ìÙàù\Åà»KO [ìmæ½¶mB@½<Ë[+vYg“_ÕWŠ*øîá-tO é÷ZÕôÒª°YFd$ŸCŒWð1Ä_xç⟋ÿi?³¦§lnµ)ÄÄ;&¹â6.#;x&Î×lcýœsÅ|Ÿ…G´¯ëk!þ•ø:ŸðQ¿ø&Aƒ3|QÖÐÿwþMDþ¾UfÃË¿àš¶’£ø“âOo _~™@kçŸñ Óê²ûŽ×šå›ª¨þ‚Sö¿ø*±ÿÈBô‘ÓýóøzTéûd|C\ßÉÿnŸÏ¯çuÿà¦ÿðMõrñxûÄRç¦ï ]äãµDÿÁT?àœÑÉÇŠüI!^<=r×þtÿÔÎ!ÿ Y}ÌÏûS-ÿŸ¨þ¬n…Z%ÔWºlz·”Åm†#ÛqÇà*ùCáìIû~ÑŸ»‚ùšA>Vã…þ$Ï`ǪÖrÍ#ÙkßCßP!€sÏLžS©¿'Ú¼~ ÝGŽvRÜåߟZ‚?^• Æè;i¬æ—qýFGÐÚ îÀ\ó×Öœú€ ®p«ß?ãšðñEýÐÚ–Ÿiö!#=¹ÈÅpzïÅ ènSÄ7öZ~pDFän þîNN}+GQJ÷'ê2ì}u‰tȤÞ.¯Pytô®¯Kñ-´ñnF,§¿l…~W_~Ôl]ŇگÈù”ElËûon=q\×í;â;›æ¶ð¦’›e$#]Jñ(ûËQìiRâJKt+“Øý0øÇû=x3ãÞœ|?xb†[€VÞá@G†fûޤz78=FGJü0Ó<â«[v·yÄrZÍ$-ƒ‘¾(Ø™SøWÑþÒ£•%Ðõk{f^Q „VÎ2»Îsž•Áøa5mJï[·Ô/wËgxÌw&ýâoœ’x9ÜNkô~ âl>&³¡©óùÆ[RöcŽ—H×üÅŽòþ Î@Lª¶01øVÄv݈ʶò€BäIåŸÈq]¼šdÆÝn¢+s³,¿LÖLÚ5Ì’4’@‡,[ŽGµ~¥Ì|½Îv°êS2þƤeŒ–ñËO^¨sŸzå5?„_µ#‹¯è÷ „'J·Ü¤ôêùú×¥Gg=¼E>ÎÏ’Í"ö!½Gqª9 Z¼eA*=±Ú©1\ùWÄŸ²¯ÀKøÝï4w±sÈ“Kj ÿÛ'út¯+»ýŽîT,s´9ûØç5tè:Ç’!Cëógt+(ÛýܰÁ§Ê#æ»Wá ‹ö{ nØäcå"E$z€,Öšwû“ö}?Ä@±¾D玜_NÞh»,'NÓ0Û‰-T*| þ¦³uh‘Ãöëß é71´,"=zƒÊŽP>^¼Ñ$†Ÿ@Ôµ ¡¡`·lsÔã½ëY¾;±éÒê-ÂD›#™‡l—+Àï_WÏá…¶¾ó-t/Ùª´é—“¨ØNpk‹ñv¿i¦[«Áiã;k¸"Q§Çsà•›+ŽNqÅÈlåø›s#C{áËÈ\ù±÷逬k¯sãõT5ż‡ûØÛŽMw:Ä?BE=œï+³Ô¢6’Œð"ß—½w1ø“Å–ÖÝ_ézuÄ©¢9Ÿ§Ó*Ø#ÞŸ2à ŸÄòJ[YºkµB£6ÕSØzýiö³øŠòáµäí*ÌïÔžŸ•{­‡‰ïu›¶jZ~Ÿ§Iœ,Ïæ_c´}ª–©âHcq-µ½„A±±DlKÜÏéEÐÒ<Š ž[ƒöZ–áþR߇&¬Î³ÍØäÁÁRPŸ˜Ž„W¡iç‚otøou¨ìì.¼¼ËjÖû™G˜ÓƒÐçæö¥)Y ÀøÖ8oPn´· Ÿ@eéþîkNñ<²4qXIpòg+åIÆ;ð1_PGã? A©³Ük–Š#%`†ÞØEæŽáÛp'oÒ»ÍÄ>Õ¥/q*¬cþyƒ/æªÛ±ô¨U4Ø\§Âóøs^•„×Zd…B‚Íd£ÿ´G^:cü ÷}e±ºyOÍ–„ð>˜,Ö¿Cà»øOlžß,,§Ÿ³ÙËÉý?Ê£“Äm$Inµ­A™CV´wRèþU¯µ·B’?<¬~Ûïd€Ç) °ùy=y8ýjô^ñM iÇ.q¯¥}ºÚÿÂЛnµÛ›h÷%,Ä™ç€GÞ ×Ce7Àï´I!ñËÃ:q·N¾£­K­äS§sóSPð/Å BT¸kIØFwH'±Á š¥/„ü]¤Î[WÑ®¤‘¿é ÁžŒG5ú]yuð)–Y®|Su*+ ”°›=ˆÜY~¸®~ë^øk&¬ê×Ó¿»’ÑBªž­¹‡äZ¥[]…ìQùË•«óàðþ¨O!JŸ”çž9éO]į“6“ªŒžˆ¶9¯ÑÖ×þȾY×u„r¹ Ú`!qÐd¨¢Ô>â?jÒž8þÍç“Ѳ1úU{o"}‰ùÉýŸgbÂkÏj§iP6IïƒTbÖí¬.ZY¼9ylØ #Ü»}3Û5úm|y¶ñ£ó|ÅVÄFx÷cά>¡ðaX´šî¸„61àäc{}(U»¢]ó6KÓ$ZeúårÁ£RÞÀ oöE­Ü¯s§›»w—å?#¾ô¯ÒH¯~ Ýô_\VUá£Ò‘WèK9ãÞ«¿÷±ïºÕ5˜ŸUôÔqC3OÛ.Ì^Äüò‹Ã>%"8Ü6±q’{ åVWÀŸš8¤F‰Â±ûÌ‹¼zM~øeα®B¼oìÈß‘ýá»"ª¤_³Dò³&­®\1qý”±€G@¸õ©úÃì?d$ðGÄHF'ÒÑß?38 £ÓN #|<ñÜë¼i¥Q²dÚÁAnØzWÞ³Aû7Zίeuã ùwµìç]åýœÄãÌ“ÇêÄ`2Ge úñùQõ‡Ø=’î|%'Â/H|Áe#61”8ÐÆ°®¾ xÖ<4Z5ÄŽÙ,ÊêÄç¯SÉ‚¾ý½Ô~ «m°o]*•¥’Øÿèßoã†–Ð‰ãÆ .Ùâ´h7þb|ùÇJ>±ä.[ŸŸ|*ñ¼OæG£]Ä¥FsÜ£ÔóÀ=ªøTå_0[Ω‘°“ƒëŒÊ¿K­¼kð€@U‡Å· ÀÆöðgÕAçðÍoÅãσV Kýƒã¢ÄnP²·~@üzÒúÇr—|#ñµ®òÑÄt*cqÏ\àqWÇÂ?Œ¶‹É'úóúâ¿Hï~'|2 ‹x–n8ûN±oŽ{ü«Óñ¬‘ñá}Éó$ø[®^ÉÇÎÚðŽà(}1GÖÉcâ¿ Åñ—Âè]^$öê@ò¥QåcÓvtÅvkâ™õŸ*ß[ÒlåV“-"Í;ò1_FßüDð·ÙàÕ´ÿ‡-kò$KQ®^Qƒ‚ !ÛÏyÎ3U­~-øa£"ãÁEÞ,dó=ÈÌc¥_µ}ÉgÞ|0ðÞ¿‰k iË!Ádòc@¹ g wé\”¿³¥£FÿÙš>–¬€PE¸dä¼väu¯¨4ŸøJ[¶\øqÙIbg†Lß GQšVøðÆ(?¶õ¿ Å(ón$ ž9€¥íX‡u¿Ù³Æ“?KÓ4ûà]ÊË \ƒŒþ¬þü]ÒTªè¶ :…i”€GLû…~8ü:[´‹Køa£—éºæösœôû‰ÐwÑ…¢ÊÄ…ßqi#äú¼dþ”œä6¼Ëùþü@Žèoêš ŒXæ;i9Èà}ájtø={á4õò•s´~ð“èFxükô2ody:§…<>Wû‰f:à:þ¸®Ä6Ú œRÍ¡ÙHƒ¬B#NF;}{ÓºìRK¹ñ­¾»acpØhÖ¥×9óTϨëUnu½^úà-®šª¤ ¬2yh}Çëí›7ÂVÈÒ¼Ò'Uè×£~¸Ïó¨î`ø`Öáσ´Û'òÔ°¶M ·uÓß¤íØ©ñWZݽ­ý•ÔP+ýÍÉ GÓÖ«7üad_û&+û\6Ô2NwØõæ¾ß²ðwÂ]e Åö”d…SæÉÎ?»ƒŠ»ÿ ×à’ªÁQõû¢wM¹ö4´ì3à'ø¿ñ‚70E<Ó!ùB\Àœô^™©¥ø»ñ¦_ô{Hæ¸bNB¡Ú¿LŽÕúŸ‚óm!ËååwBzúUé>üw5¼AF#¹rqéÛùÔòê ¾ççXøÓñ­IK½2ÒvqÏרàøéãO=“YÑt©‚wL=¸éÊd¥}×{ðóàNò#ÒäºÀäÅy,jAÿg•'ß5ϱø;%ÄI†ËæòÉy$;üÀcõ©ä§Ø9š>F_z4jÍuám*gr7ç’5Ç|€¼ŸÆ›Ç¿…—r'Úô)ú ìƒ9éœÖ¾´½ø/ðy®^7ðä(I;Yç2ryÈŠäõÏ‚"Œý—F‚|óÃmo~søRT©7k«%±ÿÔÌýš¼pÞ5øk¢xŽè¤×fXî=d^Ç¿ãÞ¿oßÞø ö˜Ôõ‰m­…éšúÿöyý“|kðÆïW4+MV F(ÞÙï A ù•¥Ø™ŽÝõ!øðg^²ŽÞ÷Ãl ˬQœŸàdQ×õô®j˜¸ZÇT(]]Ÿ¢÷?|e}`ÓxÚnƒoœÇuuw¤ÿÀØÜ=FkȼC¬ÜêÖ‰uñÇýÁÏÙlÝJ[¶ Ó¦|ËiðÂöÉge-ÌP œÎŠsÕGÝ€«Ò|6ÓÒ/ôyoŽp‹)o®;s^\ë6ô:!IXé/¾'jú=ÇöØHrÔ±¬ó¨ï~@Ýq€@¯-½µñ§ŠµíMî5;ùHó.o{àuù¸ÀçÐzWYý…z<¦ŽÜD;¾|ŒP{ý C/‡¼A{ ¤¶ûÀùp¬Tc˜Sž=êòø®fC¦Y¼±ŒFâV §ëž?•MÑi­ü<ÒºcnK`HúëZÖÞ²ÓÞ[ǹRprØ}²qZ)ðöð©kôϦòùÏ<š³'Âÿ êPˆu>ÒP„0¹‚²ž8ÈïÓµC±VÝ#ŠÇÊW1ÈÜž@ z×ùRÛ,)}ã?1`>äâº[oøV Èð‡Ã9Ç㓜úÕù< àë¥jº]µä dCquÇbTð}ª[šˆÆPy›p÷#Ž£ÖŸÜ €‰åŒrã­vK£øgJ²Xí,a…F6¤iµè=*U´ÓžÕv¬’Aeã¯SŸÒ¤V,1óP®ñ3€?2k‰ñ¯Æ…dµ—â'‰ô o\Al·÷±Àfÿ `Ÿ æ½^DÓ/Ö²G3#pª¶º]ˆ˜ÉmÌ0ûmã%‡¦J’? 4êoñÁé~'X[éÚv>Ópòùq¢õË1àzít¯øfóI:΋¨Ù^Y]$W6ì²£˜:ƒ‘RGi£ÅmÆS#(àúm+ƒZëedêaT@ç•Ú¡@>„ZM°k֗γٺK7Æ7(#®knM_L‚Ùn't`Ä€$çéÚ®üv«Ís¡Ú²E~ùw! Œúq޽ÍnF$Š ]ÁOÐÕøšåB™$Ãf$Žã·¢`Ës¤„nÁ2"ä’?RÙjºh½6¶±I+²äÈcm«ëž+yn®MÖÛf Q@É8ç8çñ«QË0Y¤$(Ü•Kf±F\Sù²Hwóý«,ÓÐX‰^${È•\©_”p:÷©<û·¹Œ+(vˆ•ã=ª-KU‹L´—S¿$&é\~˜õ¬úŽÅŸ³,ò”8i¾™ê+ÿ]Ò4Bµnâ†0 íc¹³_=x§ãþµ2˜<<‚ÙT•[–\ɃӃÀÏjñ‰¼Eªj,÷WRæd$äs»?ÄF:Š$>ß›â?»›UŠúî97®ÖR„ŒÎ@¯7ÖáLùâóìjKŒƒm¼äð1ÅxžŸ›â³Ë 1ÔðÛ¿úÕrêæóˉtè㜙11¸bÑÔŒs»ÓµbÞ§TV‡lo¼%XÞ­Õ H׌ⷭ—áƒG“-Äão,ŠÛ}úwúW›°bìÊÃh;а<óÚ§´šò1º»Ÿ%Jçî÷àñÍYç¡Ãðѧt›‹©*@9í† sêkÕtý[Û%Õ®‰&šYIÌ‘ª0ÛÁ'ã_(Ü ·Ç3LW< §kŒcðÇa]^“âŸعµÓnn¡$®K¡Ç|ÿ*{ŸH>™(³óìal6AÈ?†z{ÓM†©È`–gNv(þuÏø/Tø©¯Ÿª¤PD1ÀˆÏ|“úšôaq¯Cpb˜?*¡ô ‰=~K-T’YÊ$Ç̼`ŸoAŠ–mX|×sA#i±É®“ûféÀ˜ ä¶H;j쀘20ÞQ /RÜõîdÝÌÍ8ÜZÞ£Ëk".ÅsÏcÏ5`ëÒ™Zìü·ÏÝ'ž?5ª—:l’ 1Ç ž¿:¯¦éQÙKsr>f•ûœƒéÎiK=^àŸž× …‹—l‘ž½3ZÚ³ÜF±Ùڌʠ|ÙžpíøÒ$6æÍb•C;1ÜIãÛñíWd¼¼†Ûb¢EeO‰ã×hÆku$Ík9höà†?ZýµÒücãY´³k©Em"íÁR¤.ûCŒú`WËß·gìõsûL|7,žoxäë:Ý/2±uiœ¯ËqBg;dTl+£W–v{3 E>e¡ò%‰õH 0ï…ˆYÑrìÌy`­ÇÝ8ç½znŸñ¢}:ÊÞÏD½6QÛŸ(@`Sž@Èäúœñ_üý£´»Ç²ð߉ê×°,©pªÑÈ“‚G pOÊÍÁ$f¾›Õô:K ‹›8…¹ŽIw?0lõþUíDóe™èÞ>ø‘â‹—°Õ5»ËhH&7±µ·F‘Oei###=Ené^Ó|E5ÏŠui™Ž;{¥‰Âäsæ¸PÌsØp+À¯¢øßÂöÚ†±"Ùÿg)¨ìçÌAÇiÀÄRêðÛ§“*H F" <×sì9ïÞ¨ƒÛtM/EÒmZ üN'ƒ˜QBñýì;f¢ñE÷…¼=Õ¸šQg™'˜IWÈV0€œœòNõóΟð«Å:½ŠjV¶KonÌv‹‡hƒcï¤á€õÇã^y€µíG¶Òô‡kŸ»‡$ÝC6á€~‡ñ £®|oá‹í2KË—Dc¹Ó„Šœ ±ÆÞ'®ž†Q¥-•Äk+ÆÌÀp™ÊzòkäFñ§„|H5 &ÂÔàŽEËfuQ *I Hà×£W&Þ°·;¨-mâi"8ó £ “ÜûÒÐvgèö£ñçÞÓ~Õâmj=BFRЛø¡šEcÐ|„dáÀéÖ¼.ÚŸáæ¥©ýžòÔ¦Æù’ƉÏ÷™ºá8µòT ð —ÉÒQЂBÌwÈ­þÁÏØ®“HðwÂ=•N•,“ÎÅxüý¬O$½ùŠÒÈ,}­|føw=ä—Ú?‡£¼0üÙiYˆ¤gü óËÿ¾)×/#´Ò•l8bêÖË+è 8ý3T­<áZEL! ñhAÔdŽyí]êÜxzÖ&¸]=Uwn¸C`ã‚zÔ:±CPlà,¼}ãTÌ¥¹å†@ÀèXsô♩ꆣö½.Ö q)"Ýbãž¼“øÖö¿ñáæŽÉm Í…„Œ¡dS¡$ïe¶ãߥc©ÜWg…iþ/Ó´%‘´›YìZA‡x¦ÜqÛŒóΦ‡Æ–…bk¨®/%lŒË.ö.;IÀÖ¾…³Ïü×K[a­|zñºå¶¼>Ñ"ßû¨óE)_~Gá[Z„?à˜6Ïé¿|S"±uinà´;‰Æ0† zú{ÒxèôE,;î|æ¾6ÓÛ÷¥O1àŒ¤sž3O—âf› öL°RI‘T…϶kèÛË¿Øw@¿·“Ãÿµ]bÙ3E­ø’v‘ÜœavHÊ9Ïta>'|Òuý›|£Ü¨7¾»¸¾süC#×¶y©úܞȵAugÉ7¾iï^e­¼Àò³È‘1ÉÇLñרÍjYürð;™Fœ£PxŸaU,ûOPÅnO°¯¤£ý®þ xfš?†þhXcY|> €ƒŒÊNO<œÔšwíÁûI Eðÿ…þ+.8F‘iÚNŸÕS%³‘ŽÅŽN(uåÔ~Ê=/Fñ¯«¤rxáî«xoŒ.•}>s׌ùó]ýÃïÚ¯Z´1xsáv·ò†&I-VÒ8é‘<‰»è¤ûâ¢o‰ŸµÄ­Z M?â‹¢Þ(Ô­}'àïø'§Ç/‰².³ûAøïWÑmxµ]Næúø¡ÿgÌ[xϹW÷¯ŸÑÃi&®uQÀJ£Ñÿ wö£ÒíZOø^ÛI–RŠ’Þ_ÚÚÄìØ™þcè2}kÒü û þÕ¾92\i–Ú6§\IŸ¶\ß1‰Bÿsdd¸î6ðkôkáGì—û0ü –+ïøZ;Ý^!ƒ¬k’6§¨q’¯1qHèGlq_FÜêW—o¾yŽOAÛÐ~ò8ž0«7jHö)äѵä|Màø'÷ÃÍSQøËâMCÅWk–k;"tûÇ®YO ú°¯³üáß|5ÓŽ—ð·Ãšw‡ à3[D­îÎAfÏ©4¯¨ZÛÄËcÓš¬u_0fÊŽp¸¯&®iV«æœ®wCN ÉÌׄÂâúv™úÍÅD?¹ëXŠ]à ¸=OµT¹+’sX}xÝa¢£{ùp£äÔM¨¡åcÏNk#È$ŒôÏsìŒ0¬N ãÞ±–0¯c£ŸP‘²Z„_ÞªùcîöÈÈ« gƒ°äé‘VF ŒË€qëÚ°x–Æ¢–Æ|z !›“ótÇ^šð]?Èf`{†â·#²iÎ[¯A銕mU×1·Ì9õéSíÆsâÚR»÷3žüà °mäcÉ Ž׿®m¤’_4sÓê*u¶Þ~;óßµÞâÐçd¶~¨F}:qR¥¹êq[¿bdÃ0ùAÇõ¶*JH2Gÿ^…Q‚Wèd¬E~r3íS:)ÆåëǬ¶Š’2Çô©’Ö!ˆ×lwÆsÖ¥×}×ÈÀXÆN1Ó4åµüO~â·þÎ!~\~4‚Ø9ÉÇ®){fú”¢a¬M wïŒT겯ϻ°¼~¤ˆÁ'ñë_JPqÓÇZ~éçžÀ ê†q‰‡Ã7÷ðÔžñ?8†6Èü |ÿâ?ø7óþ E¯XËicàïh&Pptßj%SýÄši#P;.ÒLb¿fÇÌxÅ4ÿ…uÉqÑÚ«ûÌÞƒÓ•οŒ?àÙØÄGÎðÏþ hoŽD’é·ÈHèq-ž ¼^oø6j×ÁæøûCÜifͳáH'ŠB¿t±ÉlÀçïœúWõ*`6^õEn ãÖ»hñ–aOißÕ#*™e nçRÿ‚:~ذ’Kx Æ· æ&ƒQðà÷ûµ}¾A\¾¥ÿ·ý·4ËPú‡<5xäðÚø¡pÃìi¬Ó¾qœWôÎî°,œŽµ ÅeŸš$çÕA­—㯲2y6Ö±ü²ß~Áß¶W‡¦Š=ká6ºÁ‡3i—V:” ÿŽá½ÿv+.÷öuøÙáèÖMkÀ¾'´òÆ24‹‰ ñÄJÿZþ«¢’(s$GËÿtmþU Õ¯#oô{©F{=+¶>!b¢µ‚g;áêOf#¿„üC¦7Zѵ}5rFíCK»µÔæX”Wwáè#0K¬ÚFëÅæXÈÈ9•cãÄ:źšfpx!Îÿý 9¬+¹lõ´ôë œð|ë8_?š×E?d´©Læ— Åê™üzÚ_øwPˆ›;«K’¤Œ¬ñÉÓ©ûÇùV­¦•ö„Y¬à ʺp0}"¿«Wá‡ÁÍt‘­x÷Yä³é°ƒù¨¼ãTý“dM]Ú}Kᆇ$÷Œ(ðœÀX ì§â]¼ ¥ÂÏ£?šXã×ìŽËGº…Iè“8çñ5¡Œ> éß»³Õ¯û¯ p?ï°MB×?°¿ìsvß¹ðØý­õ¨ð=1¼‚+ø'×ì…r1ý‹¬À=!Õå }7’EvCÄ|Ú‰“ázÂ[_‹_-‡ï5w“î™C׎xúóZ2ügø©0 gy ‚Íä.OâI¯ÚÛø'7ì¢Ä‹eñºç¡¿WÇâÊÙükø&ÏìÜÎLZŸˆÐwÌðý’­ø—¢?Õz§á¾¹âOø§åñô“!ÀòË„O_º˜üf:F-’ S§Nƒë_¹Gþ Ùû3Y±óuOqÁíŽ$5%¿üïöUŒ™g“ÄWÇéãŠ:VÿàŸß?á9ý¨eø‰¨FMøQ¤½ìd¡*Úæ´ÒÔ†ÎAh.]—þõ#?°×®ÌX°?Gð‡àGÂÏz&«á†–wPÚëúˆÕo¤»¸k‰f¸Hc·S¸öFªN3^šžðÞâæÍÞ9¯Â¸ÏZÝjZ÷ÄÝQR{m=<ùÛIÒ¶ÜΛ?-Ä‚;v݇<çü?ømÿÿ‚‡ü]ðŸ†4Ÿÿa-Ýäú®£y©Ý¬®nðPJ¬± XCŒã3ŠþßΕ§G‹>Îf‡ýTÂ%Þ׆# àÔþлÔ¶ îHVÜÛ°ÇÓ=½ëØÉ8Òy^aèÁ_«2ÆåTñU9æÏå[àü]â‹-cOñ/ÇŸ‹QXOhér-´ Q; Tƒ´Ë0(ö@Wëfÿ¥ý‘üdº¯ßWñŒÖŸ¼Ý«ß}š Ê:ùÂeôWÜ}Ëâj>céZò2CLFæv_{šùç_¶º×I5™¤»y'{g“Ôc ü+ÁÌ|UÆsò¹¸^ ÖÇðÝÿÏøqð{áíó®Eû;YÙéÞ×´­7Rº{³ZÅ|èéw$+3D¤„Þ5~1É~cD\’ËǦsëÍ ÷íaû ü.øóà÷Ðüm£ZêQ|Û±|ÊÄ}á •èÀƒï_Ë'í7ÿ—øƒðúâ}wàÁ:µ–XÿfÏ07+·±;cÌÇ÷\†ô'¥~¯Áþ$eÙ•(Q¯.J¶¶»?ŸO™ó™§â0òr§¬O„dï†ZÆ¿Ú[áŸÂߤ׶*ñv‰¤_Clì“5­ÝäQÜdù”ù%Îá÷q¸+ûìñüûÿˆ±Öotµðï‰ÔA;Æ»|O~B…$qºCúó_Å7ìYðGã׃¿k…>0´ð†³i&‘ã-òYͳ",]Æg&C…D\yãšÿD}GÆ+}¯^_Øçl³ÊÈÙÝ‘¸â°ñŠñ|hýF­¯{Ùÿ—õ¸dYT+Ên´vîÌíKþ èÿ‚R_É·N±ñ©Ü×çaÿîþuÂ_Á¹ÿðL˜çñÊÝ?µ÷üy ~ºÇâ‰ædù÷_Ä7JªÒr‡ÔcòÍ~I/ó–­íßÞ}Jáü2ÿ—hüÜø-ÿMÿ‚b|ñ¥¯Äj~-Ôtæó-bñEóߨ¤Àådk_–)z¨2ƒÈ¯ÓÝoÅÚµýÙ•îš=Ü~ï… ¼Æ0àÀí\´úÑ•¼´VgÏ?ÐV-Ä—;·\&GÞÁþ_ʾc5âl~=§‰«)[»= .]J‹÷cc¨‚òêáEË¿˜ORÒr¾:Õȯmí¹k”Þ…G<ô®'í­a²)$°Bç ®C]ø§á? †‹]Õl¡tä€ ¤]Þ¡ ÅytñzÜë• v=µuÌ/ú:K 0jr,’]A¥¡ù‘-ÓÍnzÏÁÇÒºá^o©‡‘÷Ä·ÓÆže–Rq¾^™=:ל럼¡»Å«ë0¹†ŽæÈ÷@Q×õ¯Šõ|Mâ9?â­Õ.µrHžbëÇ< ®*}JÒìTÄ0ŠU¶–nöúÕ:²ÝȨÐVØ÷-_öðí´'†t‹»òÇj Ûì©ö² ÀW{ñÇâ¾¥z`²6ZLs)ÙåCçH¿I°OÕx¯?¼±_$KdÁ䑌‚}{àÕ6¸ŸÉ·b€ñž ïXýa÷4T—Q¾!ñ'‹|C _ëwWr61Fr8ùàúÕ]?LðõµÌ~Tjä‘»åÚ¤8úÏZÑ×µ MNê)"ƒ˜ÇÎF2ÇsþqY××\íY »b8Ò3’ÌÄ«SÓ¾iÆu%Ô%£¨ð_ƒ5ψ^'ÿ„SÂ;ÆžmÕÓÜZB§ ïŽO¢¨å¤}›àï|<ð­ÒxwÂ~·ÖÈ\Ϩê¸yåaÁ){í¥ð/‡´¯…Þƒá_™Z¥ìky¬L§ç’v?,¿º€Žç=ëW^ЧðÕàÔ-U„H‘ÔçcçÐxÛJm5c¤Î_ãwÁÏ ê^â…4ßì‹í:ê8o-¡\3!9 c Àôa€sƒž+å_‡²Ýi>2¾Ðu(ØOv$ݸ|ÁÁÞ¹õgšý6¹žÛÅžÖté×mÁ±\Åž£nåenëÆTõ^•ùÛã ?ìísGñdG’XmƒýŽ{äŽ=ëï²&¨ãèU§£lñ1Ôùðó‹:»Çh"ŽFÁ#9Àa銒tÜ>èa“…ç#×Ò®Oc ¶«*mÂF„À‰ÿ ¦³ÌщÃcpÈõkúR:Å3ó)é&ˆÕ-ÁYÆAS銪ÂÛ™›dGn•Bãĺˆ/¾$e-À+Øý95¿ˆ­œ(¸±’2IÃ)àãëƒZ"Ô±-” P*zq‘ôçÒ¨ 6ÔN.>€|‰Þõj=GJ32Ùý ’L˜Ú>œþyo´øÊ„⃃ì“Ó&„„ÎY¼34åÁº6èç!FX/ÔœS›Ã×bçí6ìeu9©#Çcêk²—÷q–[´‘L¨Ï¸=ªHÖ@À vàƒÆ?Z«ówѯd+3Z¬“)!Œ¨üHJuѽ•Am<¼¾Œ¹^?Ÿé"o–g ¸˜à~Už×—„‘È“jžsÀ9íß4€ò»£Ì¢,F7ÈäcU táË-²#Sã¯>Õì$¦ef ÇÏZ„ÊMÂR}H,–í¬Ü¯ÔW½Íg£MŸ6%,Àžœð¬Iô­u"Þ%†7Qå àŽG`j¹s,šªä'”Þ¥d õü¨mâßçl~^pÑ6þxÈÏÖ½„ø^Ù$ãg'åÎIÏ?Z¤ÞÓavŒ¹€\(ÀR{ÓÈäÓïÑYä ë·’@îHÿõVA°×ÝWl‘’?(oçš÷fðÕ€S#î £ëŒu?dOàÛä>TåD­€dƒ×š G‘JÞ&0{·ùOÊYÁôã­lé×WB?ôˆ¥Wgš¤ç#Ó‘í^’¾ î‘çä9*Ç ʇÃ×iæÁ:ʾ[@J>â–ƒå]IÌ·äo~TÞbœôê;ŠÉ6:\¯$ aÈ“p\|Øã<àgé]ôú©ÐÁy‰ÃIùÃ}9¬›­*âÖYn  §`}@æ–„4yäÞ Ð/o—ív›y.[îöNy¬¹¾[_NɦjWÌ€ÍÜ ÀR?:õ9ô+Ù-¤£[¿4-‚íç¶}1U-|7¾œ¦A$SË~?8Í; Hò Ï„¾+Šà>“©éÞc`³=¬‘—>¤«œ{Žj4øuñ'Ýyo§]ñ’Ö×ÏüP¿¡¯[ÃúÅ”Èbº˜®>`Í“´÷õÅG x”ß<—@yk¸ò¤žØÇ?­Bægƒ¶â-:ÚS6“|c ]R%ó›€1·Ù¬˜§Óäa§H—ö ´3¥å”ÉÇ|ߘ¯ ~Ùâ½6&Š{‘+³ßåe•síÐ~›/¼C§^ý‚¾Øò” `AèxÎÓG*vprÚiQä{9­d*ª÷P8î2W }+›]3á>»xº]ûX;üÛÝY0?y0Fq^¡?nušß]Ñ^åS‘nAŽ*’ê>¸ù>ŠÆ?vGÐŒqÇz\¨Q“êyì~øaö´u ¤L~Ïy(^8á±Z±‰ðßU‰´ÿ ^j[Ûæ7 s•#úÕ¿x#ᇊí‹x‚ÆH[ dµp§®?é^[7ìíðŸË[_ jZå€eÆøu'‘—'Ѹø4ìjŸ‘èGáΩlžn›ªÙºIÑo³ÀÿxŽÂŸm࢒Ã@ÔáÉ'7­4€tÈ+Ï!øâ+\cx™u!ÂǪ[CtxdçösE‡‡>7x] /Dðî¡{É[]NX$pßôÎHÊŸ¡aøÓ(õ«øŒ®À6·!876VùEÿrFCÏRG5‰q¡>›Ë«xwW³UË~ê×Ï'orbgü+‹â_Ç]0Gg­|/º¼ƒnß7M¿´™éÒY#ãõÕè_®ü£ûGA×tÿ#,Й°éóÆ^0O®ìPD¯Ðáµ_é–à=›Ÿßà¢\FñÊüã¦>S]R_kV÷pÚ6›6ð¡q+]ƶу•ó²Xõ +è _À¦8äµ½«Æªûrx•2âÞÆýÚGš … uxÜýAéêyî‘âÝ[&—Ör¹Á;®X¨éŽsÖºÛh4wó ’.dàáÕþnØ>•B_x7X’D6˜Q´æ#þúÆy®fãàÿ‚g¸ñh>yþZG,ç=Á~1YíÖ„ „I{b+`·š¸#Гè}©"³µŽHÚîÎÛr}ãæ}ßLz×'ÁÝ8.-åÖmŽÛ î£Ó‰7p*I~ xþÉ|í#ĸV ^ânà "¢Ì,ŽÖwÒÀÍŨÜÇîBV_›¢Ãp—wŸwú¶϶'¯|× Þý¡íŒž‹|² ö“[¿Ñ¶’¹zþ-ÂÒ[]hTóm.Ó[Ü4jÌÝ‚ºç õæY¤ú‡ƒ¦¹’Úô"‘†Ê'#wÐvªM}ðþ)£‚+Ìf«8àå\­î¹âËX ºÏ…gØã“§²NÙg¨ÃñG³FÃW¶º³hÆ<¹­Š·ËÐ’3z•‚o>3`º†I%•ЦÌtàÒ§ó>Ë!S´òÆOV=‡ë…Ç^ ñ,&; Ûi%n ³mcø¶ª9´ûfeâƒ$xºô=†÷íAO¡è±]ø&1•¥êK°`"ƒ¸/`'Ô“ùTÄè÷jÉal$$2T3×9Íq£Ã:ë,o£XºÛŒãìϹÏQެ=SÃÓ]ƒaâX.á6ò@ˆí²¼žüfƒ;3µ»6ºV‹Ê ¥‡c^õ™¨\êØIrîf;PéùôÇë\÷öVÖ«mbÛ¢HÕwLv’WØò*Ôz~¨¦9m¤6èÙ “Øòhf×¼Wn¥#ÔžæHJ™r "¯¦\×/¯Ü=õÌ—Evn‹b®~R~‡ ~ºŸ:úæôíK å…%Ñ6³c»vª2­¥¦£‡ˆÜÛÜ o €­Ô~=©­ÂÇ!k¤[ZÝŸ›ÌŒGÚTœ2®GP°6š¤ w…Aó‰~FR}}zh¯M,v–³+‚Tÿw¡Æy5‹w¥xnq3—>QFfä.OOc[& ²¢âHcK‘‡,—)ž£·CSZjÖ±ÝF¾L®C… ±çÔâ·ï5[>î"[˜RiWpF<½{qéXÖšŽu@<=y0U÷¨u;º äqïNãå)_xÂ._Ë¢f|ª“ò‘œdqXÒøÓP^âEÚ›NnX{úå]•å¥Ô·ÑÛOåÛ¹P1×h$óõíO“FT²†UZIU†Bc'8Îy<÷ÍV„òyžIsñQ‚n&³wäïÈm¸ã%yž*¸ø™°“Œ ü£Ày?N•ê’ü:Ф¶E¹‰@‘rË’÷nÕ/Â["ÌÚU¼’!PœßÜÔ»Õ6Ÿâ4÷HÁd §ƒÁ ‘Û>¸¬«¿ÜyA&½*“µ‰'ß=ëÓî>E Ùu’"È!µ¶þs×­sü,¹i ÚZHNÖ.öÉõ9¯@³9&ñ}¼P/WybN ;ìrz µoñQÓ"7 Äs'É€1Ç×T>Ít¹–U?»*2}Ms3ü ÔWó!e€œß…úÆ3FgЩgñ _¸Di3FŒŒ@y­GøŠd͵”tðO^8¬É¾ kq™!°ó<Œy8“÷'ùqR¯ÀÿAòªÊ»ðråpxè'Ÿ­AË#V_\GµîV, R=8çŸÒµlü]¥H®5iv89Šà·©ãŽ?ZâãøMã[K€ÖŒè»vŸ17 Séô¥¶ð?Œì”\³ò“ófUgÉ=ŽF+ÊÿáÕã’k8mc($RðO<š¨|Ïn¿oÊ•›;—,؆}¨² ³Ü?áÔ!m¢„›#—8ÄAªvöÆÃQFº“Ï㬘Ø>Á¯>º‡tv3\.ñ¸ §®kfÓÀþ)hw®¢mÁd‚@Ç@{š•º%ŸÿÕùëâV§wá¯Û3ÿ45UÓþ ønÒ[Ì0À¼µeY\ÌêëìBñƒœúoí£àɼ}û2xŸÂ¶6æþp`¼’ImgK„e Ô«Æ>QÃt#׆ý¡¾üJøkû3ø+Pñ¤pßøFeû@‚O4°±,@û…€_LûWÕ¾ñ5½Å¾«K‡GNVLta–÷õŠjH‡{è~ |ÿ‚z~Ñß/MÆ›¦‹0£Ü8d\©Ã|ÒÀ'Ú¿YþÿÁþøjhõ‹šáÖ+eV ð™û€ ~Úé ¼e­ÚE}pLPK º¼ŸÂŒ¡·(àcõæ±u½gáÏ„àk]Zío gýU¸2G÷‚ñŸøƒi -£È~þÏ>@±|7ðÆ›§É á¥†=²8€0:ñ×õ®ÃS½Ñ­/Ú¡Àl;K1>¿.ss\‰þ'ø[VC—<æò£ù ÿLñëÍx£âÛéôén¬uô‹"Å-Îß>DÁÁ ,XöÅe*ê&‘¤Ùôίâ”Ñÿsg6Q Ü&œ ‰1þÊp«V5á÷Ç_hѾ¥¨9¿žL‚Bda/É“è?:òëɼ| ½µç‰Æ IïÞKdvÏSØpk2MÄ-˜^^Ãihs„†Ö=¥ã#'œ{šá©Šo©´(¤iÝ|iÆŽ×6WpE€© Ìf ꜞ;äo­4íxG‹bÖ.Í»|r’¾…¸Ôñšì´¯Ø[µêS"<¾2=û\]Ó´Áb8 ¬¤,A Ã=ûšçu<ÍyU:ke±Ü×÷’O!P¥§“v1Œã=ªôRÛ¼È&œã’GLþë¡þÀðìs™âµ·•Õ°ÅHï»ÿ­RCjñÉ™d¾rSN: ÎSCå9å–]º*x]§Ÿ¯¥\W¿¸„\[Ƥ@ÀëÁçõ«ìfŠY¤…Y¤”®ãœ£û£§Ö¢’þæÒñ«í;@_”€?G:)4POg'Íü,GÈEPÕ^êXŒRHcþðP>aèXt«pêz­ÙûdÛ~bÄ"(?™ëR>â}†îAÁÈ•î:psM²ŽÚhί-üï'ƒ¼íÜ;à{q[öeÚ2ÖYÃeàà öäñØ×Go¢=¬rLjz´}F}zfš—Z]»H`9ܾZŸ©çÓ·½HEnL´L±…\dòr3õæ–âÞW1¬…#ž:•?Ê·"žÊõVê][HÉöÏozr´ŸjóT3íáBŽ6zZæþÏ4€Æ¨[kÇ|V›i÷ìѱ&0Šww'·áßÞµ¥Û–„DT2‚qš¢š,×:†ùKí`HÜLŽÂ€2 SGÄÉç?‰ëQ=Œ³¸, ìÆy‡Òºámvëåß’˜$ãžã¹Å)·1É ,G@#€ÓÖ—0œzeØv‡†WÚä2O\z}(šÁ¡(\$q©ÜªIÞÍîqŠê¦ ‹Ž2NÑŠªð¤ëä2î#“ߟþµKwW(Em9‘‘1û°'×¾9ªåQwM4|‚2ÝzñšÜUʪÄwç?(ëÇCPª)u ÇtcÆ¿jCQ#EŠ‘X³ u-ŽÿõQ»?úJGå +œî>¿_¥h¬³ÇûŘ0a¸00= ³²gÙ,Œ¶1»¡=ð=¨(ŠÌmò°à€ƒíÿë«Ñ½©ÿV¬C•ÇñíïRÅ&!ÆÂ[°ýy«Ì× £Êê\ªqǵ¤>ÌÀÆ2XͰ/<¯µjÆÆBÁðJápOãô¬Ñc~½‡­[ŸŸ–éåˆNæ}ä¾OÇŒ×_¤éÈó$Q6é%$(,¤þ®_XÙKy-Ö’¬!ÆO™Æ;ãÐ÷®ÇI´uT–8“~…ƒÁÁïÏj‡$h‰ìí-ÒÑ.§Û °æ9TŽO\YÉ+î9'Œ®­t·e“ÌÛfᎠîyãKJ´ºkÇ–9Æ«´¸þ-ÞŸýn+êÍÑD<ŠÂHÇÏü 8ëÐûý;ÖÕ¬eeFaæàžÍì?Î+oAðÞy9MFBÌX•Ç @íõþuîþðw‚E°ž]>Öòà¾(wbXãL{U%a6xŸ ßœ« ¬Ó/ ‹É'ߎµé~ðg‹æÕâÕ] œHØÞ̉Ç8Q’xõ⾆··²´ˆZÙÚˆ D(,Í4²(bŸ)@ÈîsŽ)˜ÎZ–KÙ\K-ÆÑÎO8úU .¿jRÄî-‚zc×ëR_©1•W;20u= üj•”R½­ÍÌ®GîÌ{J“×œŽœPdKö‰%1$±#aÆpH­T¼’6ó*Änb89à úW?ys#iM¸•†xï@‡PXÖÎà˜Ê¨ Çü0zšê%ŸKh£-vå]ÆqŽßJµõ“™$Ë09ÉéÒ°.Ò k"[e’1È 2 ŸSIg"LÚBѺŒ`Šê<äš2±¶ÚéþqNKû{‚„¸“ {lõ¬ÛkHÚi$VY Œ0QÓÔgëWc¶œD%FÇð°=ÿ­L£r“6!¹‚íÐV<ð7_¯EÈÜ[•#ƒíYÚ}½¬ Ò©ÉŒö>ÕÑ‚Ùä…¾pAô¿ZKBJw–-¬ö׬«ö˜3ÙUÔ‚OÐWägüûÂÞ6ðkë–^8ð­ç†,|eao®xu®‘;ý:Ðý˜ÍV$&팡Õ\«dŽ+õ§\û9Ð.<ÒwIŒÜ£3ïŸÂ¡øÏ¢Xèÿ ÿeßM-ôžÔ4yî œ”Ž yvcýøËgÔ]àÙ7´¢cx~ÆîK¯3íPƒ £cæö¯cƒN¼›G†K‡Qæ‚6ý×ç¥|Õ WÎ’¢Œ²RAçŸ_§zöÏj7³AàdU*0GBTçÅq©Øìhùc↛¢jÞ)’ËÄVþe­Ä¾o$à1 uÈ9ý |Â>ü6ÆZ½®…=ìðÙÝ´PÉ#$~r…#’F:ñ_XüwÑ5}25]>g³mØNXnäWˆxÃâ²ÚjiâYí|¨`ò£ 3¼r{gžõÝN¤¬rÍ$οÁÿ m›ïÙBÞ5Ùå¤ûncd0e'ÃÖ½:Ïá×Â4o´Í¥ê‘ˆŽâ­pîÛ»ôìMyׇþ/jZ¾©n4K ¸Ö\3Ê * ô$õ&½¢Ë]ñ\×GŒ¤Ò8fvo–EÏ«sšÎsmê%ΛGð¯Â(4‹×q·o˜×ëƒè3]‘ð¯ý±k¬|=ñ¾‰cÝ6Ÿy¦}½flƒ–‘¤G\ÆÜ´Ý+Æ~(rFµpÆÉڌӰ®¢ ÕUûiKŒïT pž{Ô©I;£NXõ?oø(gì…¨þΟu_‹4¶þñŒ“5°³o#ʼ˜¤XíÉ.¿¼ÜËÉ0çµ}ið_öÿ‚ŽüOð ZŒ>])–Ö'¶¾Öu-6®ÕGx(ªP Áä+Éoÿe‚:_ˆÓQÕl†Z|Ê ÈOºÔ‚8"TÔ"]¾Å1_>ZøvθÛA²†ÃìăäÁ†GêvíîF8ªW ª&±öÉ4©/≶l‘°_”áÀ^ÿ^j^oy•©t>•оþÆÑ&ûÚûÃZ«!¥Ñ¼=u1öéRÇ‚OB ô­ø/ÿù3µÚ_^›h]/Â¥öú³Å #׿½|µw©ø’[Ùä´…¢™]Æ]ÝUEHŸZê4}[ñ®½oáí2Ê4ŽvXÁ’C²ã®ögê=yõ`ÛÿÁ)­ÞºÑ¾.kn>eÕ¡ÓÃ}V £qÿ_ƾdŸáþ¾þ8ºÑ–Á¢òa0—([ãî€3Iƒ|W­1[}5`XÇ•,ÞZ0¯@ŒGB=)ýYÿ3!Ô>®OÁ;´$…<ðÄþ$w”‰á-ñ…Ë7•Ž< '¸=•¶ê*§ÇÿÙãÂÓL¿ÿfÏè×ÿѯµ›«zhûÙ**çÚ­´{×Í3|?¶·i±z‘Ʋ˜¿|SA ŒgšètŸxt[ZãÈ6û[ )q&WHÏlžG¥ZÁÂÚ°U™õžŸÿ øÁàüÙ|5ðïÃM؆A¦ødW׿ó‡^9"«]ÿÁN¿koø~öÖÃâö‡u ëF‘ajP㕆îM|ã¦xOÔ­P–cžÊ.h´Æ¶Ô,¡ÚÛ‘ XsœžÃêhú¼{µg—i?¢²¼]cÃÞ´·’%co$rDe/Ý·cpã=Ns]ì¶>(aú·‡Ûcÿ«o´ f8íœSXƒÇÚµ„ÌÐÙM ª¨Pƒ…ïù÷¦ˆvÍåu2‚ãï}Á»ŽœÕ*ìGµ“êv6Ðx³Tºy¬tËEx&CvcÍ…Æ{w«{øîÂæÇU¼Ôôøàgh…´K"oln˶1€=Zó©> Z=íÜ`I,[J„ˆ¡IëŸè+?G×uBh|óÓµ¥!hàOôúPéD¸I³è‹/jQM*As`Ú‚£Íb úèÎ@®KQñoŒ­ì'—BÔtTœÄwKz’JÂN¸ÆBõõéXÑõøŠk Äú•ÂD²oa²+p8.ò‘„SþÑÏ ý øiûÇ¡I§ßxúÉõíkV>u–”CíÆf17"8̲ásÐt¯›Îøƒ €…å-{žR»VGÁŸ—öÑñþ¥.«­ÚxCRð”# ¬Ištå†áæ0f¸?/– ú×ë—Â_ØÑ¼GjšçŠ%’;¾a4¨aŠUn«onzèzœ€z…Å}à‚z‡.-õïÖµ‹_šÚ£û;Ol ˆŒ;¯A+ ÿt ÷k‹»Ë’Í?Î[ø&¿4Åñ†3ù)û±>ކYNš×sð7Ãÿ|,ÓŸà:;Æ`ªg©è+¡–êy `§¯©ú𲱇_˜`dûS Fœ©¯óJ\ÓwgtiÆ;cY1€ŒðGZy˜}àjÆõÀnæ¤'æ%GZÓ"’±Ya}ÞÞ½*E‰‰À;G\yÜ{Sþv`6ŸÌTûnÀЂÛæàóTË F99¾•A·†äzúS¶2¶XõëXºã,.ñ@úv¡GÌOSÞ³Œ}îAêO8©·ì ±¶Óž3Ö¢UйKÊÈw¸ 9ãœÕ€VD_/€8>¤ÖDó¡•S½IÿëTËr§1CÔwÅOÖ£Ü9Mo¸2ËÚ™½ªœšÏûNK‘Ö˜.fÚIÉ>Ý(úÒî²Üºiê:Τ%ÙŠðcïX1NTŽ‹­H·RÆ3x8¡©x¤.Cgs3Ü:az~4¦pWn}z ÅGýc8ÁΔ_”yôQÆ*>´5pÊ%9\ïÆ)1ûÃOÖ°~Úc`*{VVøÜx5/?gæk]àgòëŠ\€¸ê~•˜/÷>Ò¸8ëž*½2ó»xÆ:Ô}h9<Í’T ÌNF3ޏ¦}¤»m/àÅeùÊë¹dõõ‚ê-˜ÝÓš>²ÅÈZywFà’yÇËÖ™¸99è"¨M<êAw<š€\ÅBà^˜¥õ¦?fËòLÍÓÚ£àáTÚ~H\Ï^œTM0V*xçô©x‘û&]cçãµAæÜ}ãÎ=ª¬O!RìãëQ³íbHäJÂX†R¦[k¼É·õÍ#];cËÎ;Õ@Í$`®õä g*J¬2ý™x°Q·®yëL’Rï¿ ôÏjÎ236ïÖ…Æìç8ëõ¡âpöeÝ¥ÎqéÎi"eAçéU@oºGí̼3Qõ†5ÇšGÈÃéšA´õ9þµ £¸8Áÿ<ÕYï`·>\ÌKŽ~Qš~ßÌnóžÔЮNqÏnüVAÔïfñ/¶ÚsÖCÀü4Ùmµ[µòî®Ê.9TAü¹£ëqK@Œ/¹~æûN±R÷· S“’ý+%¼Whä®›÷GÜdcêÍýO‹§[¿™´‘Ã?Ï5yÕ6ù<õ=? ÁãeÐÕSHÁ][]¼RÇ ¼|tcþ¤ Š[[É€Èárvô>·^à,\c>ÝN+2[åd%‰#¶?:â«^Rw¹q‚)®DlAœõ#&”¢&±z/oǽ0êùh9ïYÏrÁv©g­qʬ‹p/;D#ØûÒ¬ØR9äãµdÉr\Žøê1¦M¹ôêò¤µ)GCZK²)ÈÈÎ}ajZ”–&®Pº.èäQÔÄ{Ÿuˆ™8QœÞƒÖ¼Ä¿4­RVHWw# °œ‚sŸ§zú?´j†ØÒÇ=NN+ªºÃþÒ–M^Tµ…ÌÒ0ˆ3z’y5ß“*Ñ—:v°bZåzŸö¿ çеhd†ÑˆóU¼ÒÛXã±@@úWÖþ Òõ»»iNØø^3·=ÉÍpž/øÁðkO¸a§¡Ô&ˆ¢Ù  Ÿ÷›ðÍyEßíã‹™<+k›l „îG©Q•Ïã_c_ªÁ*¬à§†Po‘|éÞšhã–G댅\òsžõOÄ9ð'…!Ö-ì‡8È ã¨Àɯ΋Ï|Gñƒ¬¾'ÖnÂËÖ;v0(Ù;ÁºqS-µŸ*¾Ia÷½É<çךñç Iû¨ÞÒ¶§ØíCà«1x.ÆçZËmÝB›þÚIÉü¯*Ô~5üTñUèF·´Ñâ;‚«»“ݘíPG ¹;}-ã9Œdü ±­x5›:-–BðÒuúãüiIE"£µ8½FÓÅÞ#»’ÓÆ½åÎÌo¥eŒÿÛ5Ú¸úçgMð–“e {kh†ÐH`›UOrGAŸ\V¤ú¼s]=äòÁzzL™¬‹ÏÉ"’[–ËpF:c½s¹Åln>Ao!T…^›>QÏóª:¶±oáï _êé±ZÂÚI”?C ^:““À’úÛîyL(ëÇ¿½miŸ´†û:xþ+]_NÒ® ñ˜³ÚÝêˆÒ&´‘ÖTEU;·+!ÆAÉïÒ·ÂÂUdã*’åWµÏøUyãßx¯O¸½ŠûV2Hw­œá’O“QŒA'8æ¾±ø™ðwÆ¿ f³_À±E¨ùlLˆ_lXÜYÈàƒÍyO‹¿à¢zö¹ºn‰¯ +q–ò´M8Ûº§÷CIócý®+'þ#oYÁãÕ¼šy*ͨÊÒȸËàF0=«ºxNXÞG?Ö')¥k#¥· ,̻˘Î=£‹Ìc3<ŽÀ(,8úqÒ¬šB.# g‘Ž1ÁÅLÏæJ0»pp¥xïÇz槆5”ôÐÌU•sˆÂ£¯`øáË[Ÿ^|GÕbótÿ ªLŠF|Ûù~Xè™Þ}0+È[t=Ĥ…RßÅžœ×Ô†ÆOü3ÐüRöô_Q\s¾Qû¥?§J¬CöTîŒÚsi!²Ëws;êwsqrKÉ)³±ä‘Žz×a'ô/Áói÷3MqžF;Š“Ð œœž˜¯%—XÝn7°,í_)~Ò´'ÂÙÛá­ÇÅïŽz¿ö?‡­d[XT!–æúò@Y µ…Nd““„PYˆšŒ‹‹ÇbU<\¤ÞÃÇ:T)óÕvGØ¿í1ã/"/iÐC¼/¸½"F–6+å/ ‘ެkÆîð=ýz¸ÑuMÁ¡ýã.T€?é[^¾Ôõïéwv0A;\Z#¼>ÏŸ¡c¨ÀÎ;ñ]<¶þ!UÖÚ ®Ü3p<ñšý§ ^3£ .ÇÁb¡j’<´xR†2·€3A•žõ—ö p\²2¨È'’>¾µí2­ÜiºòdNÀà~µiÞ¸Y×÷e¾R˜?Ÿ¥ts#˜ñøôë)n"0üÂ0\)@ 1Om"9ÌÆäÇ*}½z¥×‡-Ü–°Êxóø{â³äðÈZÚï+¿$0íç¨ö¥Ì—,ÉK&€; ItQ·Ê{¢!pX(9Ïð禯jôöÒ!†O9H£%ó…9=©¡Ã,jª+ŽÇ=qþ"Žf“¦“§06–îÌbel¾1õçjt–2 1! ’6à`‘Üñâ½ü=4}£`* €ÚÜûûÕY´K[háØ²Æ8 òð}i󯋵C Çg9ïëŸZbGjƒdŒªËóÇ'o9÷®•tè®mC!TFÆÝ£'Ô—v[¬kl‘“æ(=ñM08àÖJ×1à¡9ã9©äд—ˆ5¼¸]q··ùô®¬év+Hà]«ÆíéUÆ›n³"A¸zÏn” ;ÙÑ…º³æP¼¹}¸ªæ)«@¬q¹¸à×U6–áYm¤C‚X·uõãÖ«Ëk,oç • gw?1ïíŸJ.SW9裕%IÇnù9çð§K§¬ð2ÅÆæÈ9ηzÚO¶GºXÓç@I/ÂþµNÓÏ•ns‚̘اØu¦Æ•Žj;UóJªã“Ö±Ã,2ɯ¹NÖÈ Ÿ~Ø®é^rá£Ú@$“ÆtÇÖœÑYº´—-…lwïHWhäc[§`à€¾^ÝÀã§OΡkK°¦(eGG ØtÈÇn1]Ñô™VÞáV<«ñôÅ5tŸ³JÒDùØH$ í@œ™ç—öwAEŒEfÉ2¸5VûH‚å̾T‚êOSëÇò¯Gm!•cLb*Ù%ˆÁÏ<1Q>šðÝ  ¾9 S‘“Ï®?ȪæcÈÛáÞ¡soæ½èQ'Í·'P1þ"³%ø_~eK»)`2F Æ?ï19þuë’XKªù€6ÏN{ŠÎ¸±tv–â'B„·H8ôÿëÕxýÏ‚¼Cf›äa“;›!”¼ö5 ø[T²—ÏŸ=rê¼€=±Í{wö{¼f"J‰?½ô4>œÐ¸¶¸eÀÀëœç·jM#³&ˆÄÖGsœ‰Áþ|{ÕQá¿ÎŒ'pÎxã¶xÆkß#Ó¢šVk5…Šÿt…<{ÕDÓ¤»W³”67”ëÇ¥È|§‚Cá¿)òå&X€)†Á=W}ÚÕKxÆÕmË`õöϯ5í÷^u”H£g–™ß+qœãµPo Þ ‚VU$ž{ð;úâŽd5Æ4ï é|!ÈUv\U÷ðD3ܲ ¹ÆÖ"0vž¹É¯O‡G»´U)$¸éí\ýî†ÓI“ ¡nK!8×çò¦jqkà8üÈ¢´¿äù”ž~¸ã·µ]O½Œâ¾ í*>LŽùÈ9«O¡Þˆ€¶šFdpxØÚ´E®¼da¤¬¼’z•î½r×^ÓöíxífmÄä dRæ³ï|gwo&˜,£‰d\4оÖ#¨äñÇһϰøãEŽ8”`B²û`õôªuxîO§•@„1aÏ^Üçšò9¾éÈZ6Ô/”ªRS¡#ÓåéQŸ…6Û򾄢eÎÀqß N•ê­`“@Ép$„£( ¼¤g¿oÖ’â-,º‘'—„ÀažŸçµló¸ü+*A…&| ÊÅ)Œê9çñ¢=]ó[ÝßĬ)VÜÏ­zRXB# ëÀæ@rqôìO¥>+V;q=¬{×;w·=ºv GòøÿIO>Û[2˜T~îH†Ò=NEmiž2ñœ‡Ì¾ódÞ¹*«{ŒçЊ݋PXD‰"3p2½ûÕ9îô¥¸–{øÄ`"ü»q‚{šµqãMcoص{y&wÈ[`p3üꥧ.¯ ‹L–%Aa÷KÎßá\úæ³¼;Še‘¼ÝÛ™w?\ö¥¼´Ðåµò¢€íàî=rzàwÍCÜÇKÔ¼9¨FteK´•òLj î¸5Ò‘$W¡-4©§Â€O–ãd÷÷¯&KA=¾-gKY6ã˜÷p:qQ]\øšÁŠéײÀÎíñ¿× ãŸÒ›±ßÝéö÷Wký§á²…•’G’!—< v¬è¼áMOU]Ö—`#‘NÐÑzžœ×/‹>)é³Äñ“4JຉþAýÒyýkv_‰/y4U²˜M|®ñå¹÷S·õ ¹„ÿtƒªÜ]é–§q!6²ÂŒ¸ãån{‘Šê?á\xªÆñ.âÕu(D#$óãdåùHÀ9ï×Ö¤Ÿâ.’ÖΦEØ„’n:ŽÕKþ‹qo¥®«sHÌY"]£iç’q‹ Å×þx¦õâ×-5•··d ~Ùh“|äõᕆj„ß üSdâè\Y\²çy1<í´·å]•ŸÅ&ÒÎ=;SÖ­Ô¤„“w2‚0{àdWJ¿ü77îmõM2ò^0m®Pþòã?\S³Á¥Ñ øÃwå‚ä:¨Vò¾0OLA¬;ω³ü¬K®Ic<€à^,ªÙï·€§õÅCð·ã'Ã9­#ðψtÝ"Òú&*·– Ò Œœgîåqާ9¯§ ^Õl~Í[I'(ñ©ÿ½“TÝ‚2±óÖœþ¾»Š÷H½³¢û²\•%ÁÚ ŽIÏoMð’ù»b‚TóŽù0I#‚:{v¯a¸øsà=qDRÙÀžZá ˆÜ»Œ^Š|M㟠hpi:‰µ;;kAˆáŠr˜™ IüMCo¡|èùÄøy¤[‹=?T3¨S¹ìæäA·œ÷«š¿†µ{ñ´{èãWŒ¶³,guÅzðø»ã;Å!üy¬Û„B䋦*zG×9¬oâ޵kìûÿkÊ@•K\Ú݆ŒT{äÞ'”œ^—Äï‹)o8ÖR2¤$ pNG·CŠæ/¼_ã½N5W×uØÛ—uÓ‚®Tçð£ß Ä©‰­²/ô;äaÀŤŸ7n6©É©¯¡ñ q'‘áMYÙ/öß9îe_‡âGÄ} › 3ÅZ´&Øï«{õÅ,Ÿ¾7§IñŽº?‡ xà}û0^'7׎$ˆáMtÊÃ`fÓ®öÜ€TvžñÓLg¿ðî¯|ÒZ¸Lwᇭt’xÿã½ÂIv"|JµHd›[ÕRr %u‰ÛîwnýiÊïáå¶§¤_ü'øâ@'Áú¹AÐ=¡Øwüñ\¤ß ~.êSÿeÚxOVš@s„‡hS︎k‹Ôþ#|G††Oø‘—s¹ÔîNÜöÆñÚ¸ë¯üF»F6Þ"×¥Vå¿Ógôêaüê÷ö}ÿÖôÿWÑ|{еOü>µAtŒ}4e­×Ж8ü—&¼"ËÂ7øoákM+VÔô½Mm¡ǶÖs*Œö •Á=8Ï­} ⫟[B“ø¶ò;"ÛRÙ—&þ¥R4Ëê[W.·{¥^Á{ MwçÀw‡`¥Wþ‚ ÷á}fIÞÁ#;þßÛ'Åè#R{¡PÝË%”qŽï›h `ªoâ[MSÆÞ#k÷Š=‘[iå¼µ‘ºî™€VUö^kÑ5;üCš;ïê“_,‡ýKå”ÿ`aJÕ³ð׆,JI¨Æ÷,€R6¨ùÆUdú—is¦ê…ý²éñ‰÷›te—ä¥q^g¢Þ´bâðìBÛʰËõã“ëëZkymd<½:Ù!CÆ;cüj³^\Ï?˜òÜzð¬&îk‘-åôóås·j䆯pQÊœÚÍÈŽ5Ó¢0…\à|Äg®}»Ô(ªK˜³v-Æ=êݵ¸UòPª³Ÿ¾N:s’k&Š¾Ô¤F[€Â<F cŽ£ñ©ØÊV4¾¸]©ÊóÈ=ñŽ•˜þQ&E`Ç qÍ:KÏïÇÌß/ ö¨æçQÈÿ‰tÑÂÛƒÜuãþµ<šÜÓ•[ ñW¯Pò¬W“!KXšìÈ^Iüø«ñiÓI0;™T¥±ø÷úM;£6©>åŠG[œ>Ðø(¾¸õÍJ‡RäûTgÛ€¸çp>½xÅX°°KxO˹U¾là ÿŸZÐòveAqÁ☠mmi_,;7,Gn(óïïnØÖÉÜÀ™±Àë€õ«Kºmàîèsõ®¢ rGo‘¸PÃ1õÏs@6јâ9‘˜å€ÀúÑŽÖkc!2ŽB]¸€w« Z\!hG í;xÜÕÈc½XãµòÒ0`G_© ~ËDA‘Ô#m§*?J®ò´ªÎ¥Àû¼öúU¹ŒEbå} vþu\¹q÷BôêHÿõÐw€G9\csŒóO†àÆI•P3×ùúÔRÅrl 0<´œ ÓWÎB|üGsùÔÉnWóãÊ0àdqùÕF¶³LZs´· ’ƒßô†@’’à ª:ò–*@®í$ª|°H„žÝ+V "Ó ¹ƒíRv£éW’ÍåmÞf€X0ã>˜ô Ñ>†jÛˆÕ•÷"(ûŸ Çqê*ÆÒtª[ û­Zgµqö+)v~ò‚N¯&¥ŽÜ3¨Ñ•;p:z|Äö"•ÊQ*Û:Í&cq|mcÎÒ>µr+ 7½É-'Í“÷}2µ~4I€ª›yÆ:œv÷©wF‘ò œëííIȾQð©‡êqÁôçü*ch¦0p’~éàõõ¤ˆÃlM…Ûóž2ÝÀÅ_··hAl€ÛÓÖ¤dÏòckýä Ï­C.˜,\ëÒ]KaKy%‡}à:jóO|W°Ð£hl¡[‰#Wsa7/§\Œ×ÌúÿÄÿøÊi^îà[A»;uòÁ#žOSü¨égãw…´y^+¥½›Ü¿»BIë¸çùWÏþ"ñMlj5)ui­ŒmpÀ+ïÛÛ†ãùW ²CöÒOæç ”©üºWiidþA‘P0`TÔã¯ùŒ¤ÍãiéQ"@Ño`IÜN·&žÖ%’[™Hàäõhè¿ZËtŽÞØÁ²w3v vÿ<Ö ×:†£ XÕ<¡±†cŽsÏJÊæœ¨žÓT²$¬¤ù’°!ÎßaÖ·-¥1[¼‚N ÿëÖ-“%´{aBqÔžX“Ö¶Í H÷Á‡÷ÁcÈ=¸¤3ZßZ´Šq¼.Ä+÷O,O§ã^…¦|TŸEŒZIcF$³üÈýkÌ %Evª‡02õô¦¼hÁ˜dszéùU¦DÙõ_‡þ-h:®"ºÖF~Wr`·ŸÐ^™m%…ën€‡òº ýq_ŸOaòî`Xá¯9>•N_ZIö³G"±9SR=úg½3+]ŸtÛɲ=· ØR@\pxõô«BDqó|¶C\ƒÈëÞ¾~ðÍ÷ÆyÞ)#hg·FÚÍ.2|ût¯ #ÓÍåÀšøþô¨ÉN‡|õ %K§Ú^ÍåÜ´…¾]Àô{õGk`Œ²¢6UŽÜŽ?kv _´ïµó66ÞŒîo«[ùG®p1ÈPE™Õ´~Co•Fö 73èqê;ÕG·0Æ©7Ê>e9Üu®¤è‘Í+2î2A9 ŽrHéÍWûÒ#:’Íœ’=µfd$p@eH¢”$ŒÁ?OjžÛ)ò‹…SÑ\ZÐLiã2jâ12Œü¤äñž3VâðñžB±Èž¤çƒïÞ€³.[‹³ ‹–XäPêPuÚzUËx£Vö®?ž¥V¶ÐåŽYÙ·ŒôëZ˦¬s…Œ O§¿l棩V0µHâ“KHT¶\„;‡9éXuKÿþÍßµÛØÂ®™ãsER%ì…½Ú*ƒÔ|ʼ÷ÛÍu—†gY5fˆ©"@0}‡­|žÿ´{‹~×Z}ÓøƒÁž/¿×5[çuX#Žê+‹ˆ"26覛äUa€XçÔ­ìäe5ïÆÝÏ`°[«°l¡v†۸ç*;¥{'ƒ"Ôala4›ŒŒç'Åy¢èó}½¢™$àãfCsè:æºï é¦ÚðØéÍ ’A„$–`ËÓçšó^çs1~1Z¼Ú}ԒƲ?Ì›Š`í99ÇèkàèÞÞéöÎ@8·h™•ãí‡éÁõ¯Ò¿ðáÒµ¤7ˆx/,sõéù×Ê6Ú=åÅæµ¥Ïo1°$®Î¸QùWM9i©„¡sÆôÈuÝ" ´±~ì•È ûý+Ô|!ãRöð¦±hðËæaX¶u‡àëz;¸TKéb®²Gd’ztô¯^µÐeyQi0àîQØcÖ´¶¦N5‘ãK‹q©aäegç#p#=z}kÒ|.tÛ}+íK $Ì‘©äc úדêYyŒð«É#–`W¨ž+´Ò.ç±Óm¢™C¤qqžsê3߃š…ÇVzܲKö)by¡Æ në¬9Võ¡üÍþÒ¿ìáûZêöz ZÆ›áÏCo«iÚ™¾Ân¥ÌFÍnQ cËeùa•ƒœ‚cú’Æ}b b&Û1´§ýRAžyéšùcöÁøñ#ãÿìç¯ü2ð6©qiâ›O/XТILi-õ–[È“†gMÉ’¤£muù€#³W–vg>.•ÑùTþ(šÞTûf”f”!ŒÉóFY¸èÀsӚ$j’^¬ÒÛ”B J›”!à.qŒ`WÎÿh?øï@³“òêúmëBâAU® •>IVAÑdG ®ÌFk ×5'P¿76úö§©£Â ¤‘¼k<mcŽ } Hð^Œú;EÔ5ÝF5;[7ºv“qÆ5R} œ{Õ›9¼K% Õu=Sl„îh¡S*©r[>ÕÈø/â“áöáÔä†_/Cæá‡ç-Üשëþ#¸Ô<Ÿ ]E*Ì róĪsÔÀç9î÷b?:í4¿éZ†ý•¯éW7Zdê ŽÓáô{üš› Üôˆ<5ãËem­h–“œ™$²¸73Nò]¾Pô³’[ 4š}ÿ‰eóW,È™òÎP2B‘ÁÆzWšßøgàN³~‰§êÒè»÷K¸ždHÆÐHç§ÌIÍMiðÏá­¬²Ûë67^QVO-^Ý“Ðï9¦‹ìú?Ã:n“â‹4±ÑüY¦Ùý ô’hÚVÂä—Ü«žÇ¯xáoˆü/¦D—zƇ}ÿ K–f'î¹#@<yü+åÛ¯†úLª‘O5Š)A=¾%“oñ ßÅÇ÷®ÏKÕ<áË™,µ ë¹c ˆm‘ƒ*“ŒŒüª¡ ¤}gû>|M¹Ó-õ¯hW~(±yLQ#\£¼C2o‚Ìy “\øÃ‹çY ÉID${µlàƒц§G5ÐèŸumÃÍoðÎMr §Ã½ž£qý¼áÒ6 Œœ‘Üõ®Ç´kYÉ(×øjw9Ý%µ¾e…/ÉÀ4ЋmY¼w>Õ®$V d”Æ›Gà7Û­A©Û V·Ò$–/µ×æ ½·gë_'Â×ðž¡"¾£«ø†ÚWvÚâ•{üÜœŒñë[zv³ðÂy<ÍKÅ:„Ò“ÊÇm°ÝÉÉÛúP²5ìSI§Ã¦ÜÊË• ¥J:žzäd{Ô=3í^f§k5”yT 0pW¯ùíXpi:­®›ã³Tm« [³H ô8gñÍtšW€­o¢/sã ĉŸˆÍœ`0HbK`ÒnÊì…¹¿ká½ €}.Õ'r–$Œö-Ïçé^±ð[öwø‹ñûÄ6^lf 2iY­@ p±œ·ÝÀ_ïJØUç=5¾|Òþ-xªg4ÚšÉ/–-¢SÚ~]»ÀÞöë_½þðU¿ÀýßÃ>†ÞïÆ´!‘þemÃ1‚€pe|ÇO‡â>'Ž¥IëÕŸI–å×´¦´g ðãàgƒ¾ØZø#ÁöVz·Šš18†tí5ü¼ÏÞWì¥Îçaò¹#èŸøwNÐ>Óu ò__Þ׺ÉÍÅÓ/@OðÆ¿ÁáWÓ<Ô>Ðôÿ i­gdÏq-Ãù÷wSüÓ]NzÉ!ýz*à­dŸÞ¿Ê¿Æbªã*{JCë)Ñ…(¨Åb“Ì%GåSù§oÊÜŠÎTfàûU¸c”•À›ø}(ƒQµ–™S wTk™@àsùÕ˜í·eAÉê=3WÐ8oCϯ¿ø×C« ŒÌ`Üf¥_»‘ƒô­†³ˆmó3íô¥!•}®ÎíŽ1Ÿ^ ¨%›Ë;Û¡ì+)IؤÊ~B¨¸ÇaÍ4‚@20=êo´B™'þú¦}¯'víøéþMs]÷4Iy‡‡¸íN-"#û¤rG"—í²ÑŒRsŸÂ˜×Ê€åv“Øö¤çæW+›°9ì=iv8¯ ëUEò QÇ^OZ—íʼœ¯ÿZ¡Ôó+ ‚OÞ‘ên (”cÎß^ã³T¤ºÅÏl•_íF3×=ûVn¢îR ×Náƒèh3)`eäž3Þ²™—heÝÔôâ‚Ñãæv­a:ݘù y.yS»×š‹Îl•ÆsÜV@dåØçwhÇ˃ÆOJÍW}ÊtÍc1pJ}ì*/µ²®Ü9ÉÍP”ìݎԖ–ëOÛ±rr¿1ó©ÅÂS䚦>6ž*PC/Ìà 䊟o"œK¦L ¨ç Ï­*³œ#Þª®Þpp¸Ï4FŠ ,Á·tçŠ=¼‰²-oJKm9öþµ M3ƒ&ìƒùÒD»qž ?fò<µÜÍÇ\qG¶adC¼3:S·,¤*1?‡jU*Ìvöàà÷«FHQÀúÑí‡b%XØ‚ Ó6*ÊI'©QQÜÝiö¬È[{ŸáNIüzU´ÝI'4 À–üsÅK­æ>RñP¿:åzÕGº·ˆ—ÌÏ]§8ªÑÛ+Ò6[;§µ< ƒŸn‚³x‹(’J÷.¿LqBÆËòÆ2ž‡­.ÎŒÝè9®Ö§Ú6U‹Î\IÁǨ«8M›,3è=sëU’DŽ&YòyÏl}*¬÷cÜP À?äÒç°¹n]y”# tàdÖ\×8fnx9ö—>¬vuÉç·…fÍx:Œ8öæ£Û"ãŒÔ’ø•Û°ðÇÖ± Ë8pWƒÁ5Jy†ÃûÎzñU¼Í‹¹Iü+)Núò$‹®Y~ð<’{R`BtqYò\®ÖÉØŽžÕŸy.í–‰¸’1è+4+óO).xæé Q×"³õ›fÓm·â»¨tË$škÙVÞ0}Ò+ækÿÚëö{Ógû/%Ô|u}’¢ ÙµÌE½ ܾ]ºóþߚѩÛÝ_3hA½¡ä¼×õ‘„Ûo=‹ó4‹á fòá‘9y%8ŒÏ$àξ6Ö~>þÒ~'ybð§‡ô/ZôK­NC­^°êWʋ˅z—qšñ/x.ïDzƒñ‡Å:ߎÚ…ÇÙtôÏQö[_.7_@á¸ï\5ªQñ*|–¿ð?¦–mꬼUûB~ÏþñU®‘¬øÃNþÒ‘$‡ì–’}¢R½FV Ç*GCë^sâ?ÚkÃ7HÃÂþ¼Õ$Î{Ͷ±0#¹“Óh¯ _ ø3ÃkáûMI²Ó­£¿eD·‰P Á ëŒò@êjýÚÂ×K寱?Ãë^F"­ iN/æwƺšÚ·ÅŸŒZÒï·Ô-¼; ùZU°2èf”±'Ü(Åymæ‚.®~Ù«ÜO¨Ü̽•î'¸.H€®Î"²Ñg$ç ð}ª¤ñHÊ ¿w¶;JËÛJÚ"Ô-©ç³i6–× "È–<Ò™o¥À„i ¶HÉôãúWEvNý™$)âªT˜feR Œ÷¨ö²kP7l®£·ÂG ÎÓ´Ž‹éšÐ:ö©8Ú²ív/L¸#šÁ-b’ÞW#}àGÒ’+‹ v[†€NGÝÙÅW4º ³©óæÔŽ9]Š€ÄýßZh…A]¾On¿SH5Iø¢1/Cž8õ÷"hñÝ×óɼá{þ}½*Ý*“%ɳº@qæî}n•.&$Û¦àzîè1]¬V¶öê­n«‘Œ4ƒ8ÓúU¦·ÀÝ*à|ÀžþßQžkx`R]nÇ.º4ÛU® . ò¤mÇqøV'Žy°ÈÇåYšÌÆÀ‹W Ê.Wœn<lWÎæ•9êF’;pêË™]G¿ñ‰o¢Ó42ÚkÍFúr+kX¼’18(õë_ÅgíõûHx¯öøøµ?Œu9¥Ó<£†¶ðÆ­kdÄfWSòù×8&Få]©Øçöþ _ûNx Lýü;v!½ñBG­x¢Tl,\ M½»œñö‰WsƒÃFŒZüEð—ìññÇÇ-‡‰t»+IÔ%Š 6ëÄ35˜Ô'“åE·„#Jû›X¨ªäb¿¦ü,áH`01ÆÕï'ø/ø?‘ù§g¯YЇÂÍx'Uð~¦moAh\ƒ¤`0=?ûïþ ?ñÖo€·€õ›Ë‰!ÒµëßøGuAR%²Õ± ~U›É“#¦Êà>+ü2ñç†eŸÂŸ,íÁÕ%–×NÔìnŘ»·91d`£ÁWUoAÖ¾Gðöµ«x[^¶×ìx¼Ó¦[¨·»%£ àãr`óë_­W¤ªÑ•9uM#F«Œ“?Ôsá]Ìx5´IÏ6Ww`Žƒy#ëÇ?zº•JÇmpÈÐrWšò…šºê>m_ÑÛ\1P8i"F5êöòØÌåâ<ù~òÜý+æ²¥l<c·­V%”·7ñݳLam»±Ô˜ªû\¤Ìë”ò—%AÏN ãµfÛÅl×dµ£‘Q¤Ïðžzã¯&´žÊðÁ°+û7ŸÃŠôÎK"Xî \>ìcëõçÿ.%-æ|çåÎãÎ{þ•ÍZIf— !l,‡æ 6àý*ÈÝ%ÁÛòÇpNàqï@¬n'#ÊPÎ1Þ™%§’ŠøÂç§ôÅe$·sÚ$«’é¿ py'Ë¥N—€Enˆí¹HúõçÛ½–V(猙Êg ·<ƒéØSŤLÂÜÅÃ`…ûwª©qÎå¤È3ÙÃwç¨ï(ÆT¿R¯¥5ô‹?-¡X™c'\ÿ:«q¥[Nñ̲´Sþ„wàô­Ø¯q ­áÛ€ÏQOy£¶Çž£åÀ$uç×µs“éIå·ËÝ@]§§_Ω5½Ä1«<ºPB‘íÍvNc–àHÑnäçøj°/˜ Pü$ð÷íNì=c†ߺ·‚z~tæŽÕ­Úk€UAÛòúuüvrAh=¬Þi8àñÏ©Ç^*i­,îDòÀU:òv޼¾Ô&4Î îÎÊê T€ÝFÖõéÒ°± …×k8ØÙã¸ý‡Ö½ ô]=¡‘"cò†N0{)£Ã›€´¶¸uÚùÆXãôúsV]Î[c ù€GÐÇŒ·Ôõªùh¥ å™ åÆóÉ$s×Ü`\Å•<Ç{‚A=8õ÷¤m&þM»$LŒ ÜÍ’±ÿ@=Ž˵½À‘@Œü úf¤‚ .AåÊ]˜nÚAÝÏ¿A[7)©iÎàmªc+œãЊ‚è\Ol‡÷ $€67ŸoZ¹’4ËIá?j9dmûU±‚?ŸçVZÙ‡îÚ_›h`m vúš±›:Ú¬òGûçÉÁô3Oò¤V $ss…?ON´ÇݘI¬r ê}°ðZ24—ÈHbIaœ)ö«SY<–¹‘ !ùlƒ»ëúSžÝÙb‹ì¬vŒà6ã¦ÞIüêÛú2YL-¹UUY8«péÚMÃ*ÜnPã|q³œ`õ>*Äþdó}ªKwˆ¨RB€À‘ßéD¶‘ÍóÊ¢ýæ?)'éPÙKb—önŸ½ü¹Y—„mQñ¨&ŠÄÆ'/€xÚŸ{Ž9=:Õøíc7’›Q c°¤kKp è1#š3Æîh̉aˆ«ðÌ¿1Wå~„÷¬øl%!îVÈ9nr‡'“Šè>̪_¯Ê¼`öÍOf'‚0™ÙÜ3ýh.ç¨é“ýŠê-6‚6Ë+‚`>@ØçinµGÃzF­yáÝ6oG¦°S70ÛIæÄ$9ÈGÀÈü+ÒV#ÌûB‰£l†ÉöÛQ%ݼ–«·*fǵR¹Ñ]Pù¶ÇlJ¥¤'9aÜc±®âo²Î6Q÷d2ŒOëÅ,bVF‰ßËA€­ÓÐúJ¢)@í ¯r‘KêÛ‚è:€{Sï<-5´fk{² –.79éî1Ö½0X,’™ÙòÅ•s„Ü:du"”è^nžãQ1™r|wÉÀÇ?¥O0“<ŠãÃW22¨¹†io•Aù¶Ž8î¨.¼ªNÑÎ OÝ•nŸöq^µsáÍ f $ÕW+¹²?à &²°3}ªo2"ÀS„ÀçŠ9™§)àÂzƒ|цÃJ¹ëœŽ@ôªÍá«ØÉ ¸~­·8×Ðf¾€¹‚Êå–9Hdv2@UÏ bæ{ýRÆ;O·n†ܼAI=rYpOs36Ϙî4vÎçlr‚ä1àœû‚È®zm[óîÕØI´“"…Í}Eö%±Sç¼S¬˜teÎI<ƒíÍN÷š}¸%ˆâUÏËÈ8éK™‚w>_þvÇ2à·|gÛÚ•ü1,Šmïq ’Sßô柣2%„ ·'Ÿ~¢±#³Ak)á]€á‡©Í!ž5•Ä ¤Œ0Xr@ïÖ¤ÂùÂp7Ç&T62 zW·ÛèИšµÌQ†2qõéÍIý‡qE=®œ“#¸$)>Àw4 «ž 4›Aœï$,§’¿6ôÏÖ¬½¥õ­úMbÂIU í# q×ñ¯ZŸMФ„Íqi4hYv†ÀSÐÓñ¬ÍWÁ0ª´©p¾yf)òž  P46hþÔÇTD£— ïŸ\{ЍÚW†uXWK™b+äüì3ÎGoƺé|ºtJÐF%]¸ #3’=çôÅM†¦‡O„Ai3H¸t‡®½W0"øcÚ| öKm¦GùÄJ¹$Õt𯃤¥6Ê’((ϵQ³ßµØ¶“©º~þÑâsž7ã?® Oý™5´29PF@ùÀbý)ó £­ßÃ{©ºjzd'æ·»™™<ò8çÓ¥}s Ü¸Ý§]G£. ñÜgùV4³êZ|¦Ââ6@8ƒ³ñãoZ›™´yu·†¼I YÜhÞÐmímeB±Åi, óg8fréÖ°,µRÁóâÏ ê6N ó-‹{grÏq_CC©ß›¨SQÓUš™$Mƒ?ëV´4×o¦Ì·yŒªÒã9ïÀ?(ÏOZ.KG…Zø«Á¬÷×idÉžew×=Žqšéô¯xV¼Xí<@¯ ®KC8vèNO5è÷z¥»»¥ãÛæ v1sÐñƒøÖ΃ámYµ3MœÎ¿tB›°O=yª[sréz Ì?Ú÷A›w––âE~øŒsô¬‹ÖŠ'k˜µªóÛ“Ï¥m7ÃoYƒ›föCiÜ-®%Œz‘»…aÏð£Oµ¾ŽêGWÄ.¤ ¸KˆÎßá>bÁôäSö†ÒX¼ßi€wÄvÛ<ñW!–Ú3¾é(ÉËg<2jþ¹§Á©ÉÏ‚lÓD‘Ç—"M䔯Bê§ïz}kÍ®í>:Ú\)Ó´íTµ$ ,òZH§¸!ÕÔŸ¡€;»]:Úøý¢ÎX¡Þvç síÛ‰¡ë7ÑÅ2Ä7Ý'’ûY»t"¸Ä›Æa%¾­á ¬dÞ ž9b¸Lø8'éQOâëý+tZ•–¥nv|¬¶›”¢–-øP¤Ýiú»Hn¤Øbcå#g }pj¼ñêºPI,ÚW„Á„d¨Û×Ï¸Õø™à9žGÖ®[ìÅpòIo4X`9$Áü+£Ð|kðÏUEÓü!âï”mhbÁÎZw`\Õ&Я'ûuš42ÈÁ–Iýà p3Ú…û á6åŸzãægù=xé]º¾—wÖȳÅ.v±A W^89…qwz%²Díq2IÁÌhèÄúch»çØã}Ãl`D‡ŒN£Ö²ía¥.±b±µÉÀDÚAç¹íÇ­Aw¡Ù!€I§š Ê1lŽÄ“Æ;Uᤨl®|ЪDgqã×ü)äés¬¦®ë }·É€Ã_åŠdÖc‘ Ø²ª—Aþ¬_Zôëk!8’Ök£l̹‹tg§éšbhº™ºH§—!””îFx ÷ï@¬yqŽI%3ÃlÑ3pÄôüsëÚ©\ÅöeB˜—ÌÚáÃâ½ÔøsS»_.æÚ 9E^¸÷>õÍ ÞÃy-ÅÅÂíÚIÉ玃҄;ÿ×è,¼=;_ý¾ñ™îf'{M–vn™.Ç'ñ®ŽÎÎYÒ+‚ab¬ÛñÛ¾ÜuÉ®– òK¹›'nÇ÷ô÷« ¥#ìšRäŒ3òàôò®JǪ‘‡ourÊ-íæù³´•<­[[[´cä­+ç%Ÿ…n„A³ä]­»iõ=Î9§ÇyhX‘ýü þ<ÿœTs!ò³È;8S»89çüñW¢·XÆå;Bޤcƒ[0Ú %!‘Š)ÀO'Ö­Ínò¾•h.؊ŇfÜFNäóPä®>GqðÁòì6Æ ŒuïÆ Y·¶+±?;ò*šµ¢8óÃÃóøÔ:•Ș‹qÎÌ·Êrž•1e¤iÄÍèd\ “ÁzçùŠ-ä…¢2,¡SŸâ'ªž•%­Ð 4?)9*Çwà}EnùvsN–ö‘/”¹.€}ê†OnÑ´M-Ä¡TÈ'™þµä;ø£io¶šhuŒðÓ©á€ì£®=ûÔÞ>ø§¤hö3xzÊ>IÉBÊ…^¹=sé_%jºì×ú‚ÆÀÄ£;Ptè})s!¤I­jßnºÈ 9çžkÊÐÜ•Wª·ì;sQCö‰ŸÏ,€?K|Àté]lk¨Œ``…8㞃éXÎJæÐ‹±N UÂg²£ŸÞõ8íé[ðë̱¨˜» ¸8<ôÿëUv‡÷­%Td ‡$ß=+h&·,b`B€9S×¹Î9ÍCe¤jÝk“4‚E<(!TŒç=ÈÿZŠËi®Ü‚~cžyöªöÖ—N³®çÇË"ðª}ǽtdvÐ+*ÆÏÍ’Ae€zu%–-±$€B©!±ƒô5¯Íwç) ìÁÃr>ƒÔU™–`ˆ¤´‘¹è3ê=x©‘Û,ØýåqÁzŸz!·$v‰d–OºDy~Ý jØèº½Ìå´möpyV n³õ­ï øóž¸‘´K§ „3)}þb+ÝôŸÚ_Uñ-«Ý2®Ñä²ÂH÷ÁúðjÒ±”µ<^ÇáßÄ9ãHáÐo£bNÊ +¨°ø1ã‹òßk&Ð8ä;p{„ÎqØ÷ã7€u¹üô‹IÊ©Û7 3dœóŒWe=üwR+Úñ°È q’G\ÓœG‚|7ƒQ¤ûDó’pÊØ Œú÷®æÝ³©ºÓó©#mFržlkªÌ±¶@ÿߊêÝãkq%~l“ʓǵ-ŒÏ‚Ê †t?"8ùºçéWÚ‡˜2¬ùÀÃtý O#HާjuÈÈ9þSK »42o“†ÜO$ÜúñÅ åE` šŸ˜Ç ò¶Gc×¥n[ÛNð²ùT†#8‡úVg›ÏÚ, c)än««¶I#HŠI$½r`(¸£[.‰¸H~bÇ*yéïõ­·µ880¨=€Ùõ˜×YÇögQ7—ÔŸáúþ¦´.õ ¯”Öón™ÐrüžKÌ–ßh£¦Lk¸m8ÂÖs?”¦H˜.ß¼~¦Ÿ$Öò$/ˆÛärøTdú«*{Fîxƈ–Æ=À0‘™dÚ{¨~ÒÔRؽpgb d+Ž@î~ž•ù¥â?ݧü?KøÅ{á `´ñC6“£ø›le5¦Û3_F¥egDƒo6$RÇ*ÍȤÏq-ß‘¸“pè÷Ojø›öѽ+o,1ÇssÊ~Rã>ïò¦øoP·³×ìê‘Ë ˜)q·Ø¯…üU*é¾-Xä}¯yn °8ÈñëÞ¾»×ü?ákõY4ÈÑ.ŠoÝJûL×È?$þɻҼJð#^XJ”ôh·à¯¡bGÒ´¦fÝ”<3ã#áÙíÖQr®ÒmFbcS:~|ó_Qé>4ž÷J[눷–_¸ƒöïí†=E|ÝñöC¯X\é6ƒ¨­>|µ!¹ ÷˜Ÿ•v°†Ú’e>Zl‘—–#1ëÐqÇJèJÄó¦{}ω5Û˜aF¶ŠöHÛ‹m=Ç5.­jÑ,v÷NCÏUpN ÇN•ÂÅw5ë¯tê•vÄ>lÉïëÞºMM³±Ô“4ŒÀáË §?@qÚ”•ÑV±éÿð’îáûMÃ3†£ ÈbOøuâ¶î¼Eua{趸!‰l«ä}ÜzzÕ;-3J¼hoX¸Èp2=€½á-Q¿±<¬Y•s€O£Vi¸°iIÍüàÜ mÿüDð´o‰«Ç¦ëZ„jJÙÞjÄ­·w +ª¶¼¹=s^£xÑ/4¨ôì^òܧîšVÆH<ýÜ×éïíÅûÃMx^çÆ?f¹·ñ…µ‚Ak3G¢–…ž$…FÄì'‡Î±ϧ€~+¶„Ùø’Ûg—#E::*H§knOá ƒ‘Ô¢¾£ UNš×Sçq4ÜfûRëú‡Œ´-J­4È!¶]Êr{ôÆ{W«h!¸]&X¼CiMpT¦&ž@ÈõÂë^*Ñ5Ȭµ3æK ȱ˜€Ï̼ zŒsíPiqÉ®ZßËlÒGh¼ ÜÜàz}+¨æ=å4aâ’ÞÉ ¢¯ßŒ4y#ÿuÞ#5 7Ðô4™E´‘€F0F6‚zõÎ=:žk3Ù›}g[ˆ¤‘#'…è tlþ4kš6µiwåÅ•Ä12ˆ‚J£qb1Ï<bßöæ™a©ËwayyhT¯’¤/ §{çœÓ&Ó¼äÅtñL# ½Ù¾ò)÷<“üë'ÈÔ/¯¦„Ú L(_œáxãó¬« Þß =oÊGݺ`Ê»‰À隘Y”oü º”“j3Û˜âR¦7ó ²Æy €@<}áÚ·¼ðƒÄ:­Àoß4ƒ&&FhǨ<aš†M ÆVdYÍ}jÁΘñè†ÆAöÏDøT󮛦*ÈÊI£RÀúžyüxªZt0ý¶æ9.£°È,ÍJv’A0ì rž#ñ¥®‘%âC%•¥ ˜F¿o’(D_S‘Žy+Ysšr³Ú'ð‡‚ä€ZGo¤ië&b+4{7cÑÇǽ3Pø9ðîËJ[ä¹³`FÔO#ßÝIqÇnõãZEêx“O¶ŠÎÓRÕäY7¬:M•Åü+»ÊbFãО=+wÇÿÛ¿<#mâèú®k¨êz~™dú…¤¶h7—À̱Îßo™’BœW/¡FRŽ­#£‡s©÷?zÿeƒúgÀ/‡ãD}BÞÞ°] € 1þ¶V qÐWÑþÒ.ô³qâ0—WÕXKvTåWoú¸PöŽ pvËw¯ø]i«kÞ(Ô¼iâ¶ ¤è3ýƒNˆQ=Ò¨V©$Š6ç³é^ðg–Wg—ïþxÆcå‹©)Ë»?@†SJ%ñ3Í! “ŽpO¥2´œgž•›Wåã=kbÙ¼°8'\â°Š4cŒÅü£òzÖœe ãi¾õ›Ô Á‰Ã:ÔßjUáÎì2j“LMÖm€"å[œɧ ž\ƒ·{ŠÏ0Æ 6쎢u |FÔõâŸ1<¬¶“uV'ŸoZ˜JIÙ€@àv¬ãw¶MÙ;ºóÇÞ›.¡m†Î@ïQ*‹¸F/%Ì„f&AøsùÓŒ²1ß»ŸnGçX‰yÁÝØã¨¤kÕlîb3ÐÂU¢‹ä}•—’ 8ÏN™5T»0 oJÏmB3.ÐëÇji½Q#sŽç?^*^!> ¢ßCGÎtqÜ}TO;˜°y5žn ‰7>îyÉÍD÷jŸ3à­sʱj ¶f\å¹úSLŒüƒÇ^•Gí€n U€<ô¨þܬ›—Óë\ò­©²¦[yfçã5JŽ»@„r²Ñ”¢2/ôëšæèê9÷4 “Í;”OJl·Vö‘y·®"ŒI'ÐQíÜ,ÉẑŽV3ÁïÐÔžfØ„·l¨¹ä>kšº×®$4øB€ ‡’=qTE»\â[×’g¸üj‡U©k¶¾`ŠÂ#)'›…üæ¢ó5+³ºy@PÀíN8ôoåF¡—íZ‘È©“ËvëÞ£Ú¶_*&´‚ù+Üž=jépËéŒÖQ½HxÎsÐÍ4ß“3<ôEÑ%¨¬líŒ!‘‡Ý=ϵWyâû«À qëXÍ©"·Î:UCªAæ n•.´AS}Ž…®h‰‰ÂúñøVlúŒj>~ mιƽ‘—©çœúâ©ËtÌ 0OœT}bÆŠÎõaÇ rp8ü*”—²NU#`Â7@§…ª'™Ð—$a»Sær-SHÓi¥cœòOZŒ»±Ú¼“^uñâ·ƒ~[Y·'“íÚ›yzv“e¹ÔïŸû¶öÉ™âr 嘚òùm?i_‰[_VÔ£øM¡Êûš±j~#p}tûímN2!Iˆ<‰tÓù.i;/2m}¢õ½BúSkž-»¶Òl£šâúxí¢UIiF+曟ÛöQ3Ѿ"i:¤ªHhôs&¨Ü{Z¤ŸÎ¬i²‡ìÕ¢ë£ÆzŸ… ñWˆ#'þ'^(fÖµ%-Œ„–è¿–‡ºF=«è«kã¥Z¥•‚¥¥º íÑ`D^à Õ%„„4»$¿_Ði3ã_ö¸´¾µk„?üWâ’å–;›ÛS¢Øn_ï=Ö.÷X¶Ey'ˆ¾5þÓ>,G‚ë[Ó<jÙVµÐmZæð1wí·`–'µ}™â/›Û¶³ÓØ;gæwç'Ø×9¯iˆ ø‚Ò+¡€:ÀöÃuäb3wKñþ¾ãº+9#óí~øQÕSÄ>-ŽjIzüï©HÕDÅ•?à*+Ô ÔR8Ö(ÈŠ4àG¨€.ü«¸ñwÁY¬ÔÞx"ç{c/k1äö¹>†¼ûFeš[[¨Ì3Bå\à©__JùüELDßï$ÙèÁA/u‚u*ÖËò‚3ÏãXÓ܆]‘’CQ\ÿö†°“ça·$äqY×7© 1‘´±Æ>ŸZäPwÔÑ1ž!Ýs>–# ¨õÊ·ë[ަ*˜<ƒžEróKh¬Ã~u›eÁô)!àýkªyä`°OsJí…/c5qæ3±NTîôǸúVUåÜF"#ñœqSË Ûþòb¨½Â˜sÞ¬Ã`U„ª€’r íéÅi–äÝPY%ÝÁ‘ž•b-:æF pv¦:޹ô®¾[Hco7’Ê6€õãÖ¦G ùQŽƒ'#¨=ÍuSÂ.¤¹ý¶‘hñ°PKä8cíØÖ奤`˜cÎ×ã ?_Zµ’§b¨çœŽ¼úÓü²¢=­…$ƒ“Ó½vF„VÆndrDQ ’HÄ )ÏN?<:»wU S‚I?ÓŠq˜Æ\F@'‘ÏÖ¢7ù;”‚K|ÁGN;ûVñ§cK¹gtü;ñž #‚)ÒDrE'i ôáTä§÷C†äÛÔ­píQ á¸#ž½±«H\Źwû¨f ã ä}*«ÉnÒ‰°ÊX…n3î;UFºye-‚F ª²>% ¼4?)¡ß½tS¨kIw•µ”îÏ8íQ‹„XCˆ÷m¯=ÏÓÖ³þe°<òô&œCºoò¶íå”2>½±]‰‹‘u¦WS;°nÚGLö¨–H#&fny w{úú Î7°Dü¡9é† þu¥¡ˆïµËhn@(®—¶gðíÍ}Ø6:z³Õ.ãû‹§i1¡Í´%Üvó$ùÛñÎ+†Ö¼I xGOÔ¼mã›…±Ò4›õ=FåÈ]½¢‘˜ô*úõ­»BkÒÿj”«,H7¦>ŸZü’ÿ‚Ä|bñ/…¿g]öwøm ^x¯ã6µin‡ 5´±‡¨.nd†ÝÁ cSÂYÍsztZÒ÷~ˆ¬×°˜IOÈüŽð–­à¯þ&ø«ÿ=ý®tÿ¶x'úª ?Ã÷y_ÛÚüу¥h€€ÌÛ[ˆd¼Â° ·ðoöfñï‹>?|dÒlŽWƒUñ ꞯ –f—¥@Wʳ·ÉHÕdØ+‚Ĺ-^3ÿcø£ü0Õ|#ÿîøgz÷>ø#cöFxÉUÕ¼U~«>¯¨8Û‹;­¼[‰(©"ƒµ¹ô=Êßà_ì¥âûј.´ 4y$ßk“ª>ÑêÇ%½†kûBS§pVI$~'V£œÜå»8Ý)|+­þÉ~°ñu²Ûj_¼W®jRV[IŵÔ-Æ6Oå¡ÏÊÉ&¿"¼MÝŸ‰nío£ò.£¥Â‚d ’í¸ýý¬µàß|6øipUüá; ä…°Rþø ‰ñïëüCÒ¾>ý£4”±ø¿ªj@¨‡U·MI )û\Ž?àyükTÖÆLÿDÏÙŦ¼ø%¢^nu3iZt¡Ô‘œÛ¡ükßìuˆåãMË"úÖ¼ÏàÆ˜š?Á¯h=¦g ÙÙRÝ9#¯â+Ñ |²Œi».Ü8çóøZj4컵çw©§i}NÛ— ¨,0@Î@ø¥{Éü‡’Sƒò¾â3ïŸsüª˜Ò'»¸&2ŽŒw 7{â¬I¥jB“ (äöäÿ‡zêåg9±k{0±•òYŽwÓß5¢—Ö¯1@¸ÚÊGÓ‘Ò¹&‹Hä$±ÆSR]·1Ù,&-ê= >½³G+;í9.Ö)Tùe‡Ê:±“ëÁ«i8’I戒²`sÕPò+Ï®îåó HÙÇÉ÷Ž9Æí‘Ú­5ëÙ…NÂH°Aƒì}p(å`õ:I£pÆÞE"98'aÇ_ð­›xì,íb Î"\åºáÇjà­µ¶–ÿÊ—y.Äù¡ñ—Ϙ­× {Ï#c|Ìr ÊŸ¨©ѵìÒÀdTÝÔþ] ­[û©`Y&”]v‚Nž+ÏåÔ'K×hW~Åh•ÿÝVŠþEµÛµ¶Ì˵‹uÊ€;d’3Ï(ÀTÈ\¿áSdèÒ¢†  Çnx®_ÏžWF$¬Àà~Rú÷ô§ ,\+¤å~\éÛŠßaørÞP >VÈ9”»mÈ1y˜n7¯÷sÒ¹w݈‹!o—®r´ø®„Z–"^ìzœtã“Ö˜Öçm³ìì%NÂÊGó¨’æA´ò¯Û€Aú×7opñH#Ï,PíùIöÏzš-Sý#]šÜQ¸âÚ:Ú £ ᱸß=~µjÕ %!ÆåÇïšæÓU…¥0å±äåzô=†Mi¬ÐLÝPǯ§ëA%£h#C#ŒneRAþ•@éö³íi  ÞãAž¿J‚I3¶Ò`Ò.Üî*:žÄúf¤W¾1&ÙK¸9÷ô«H_IÉ’2ǃßqÍQ¸Ó¤¶liׇpûÊÃþUªS2µÀýØà¾ O¥hÜ%…¼jTùïÆyc­08EŠôbÞëÆˆ`úÒ^h­¸«3·kgAã·½ Ëmå$Díf6:ëT’ky x"!È?>GqúP›Üém¥ÌÎy F¤îžÀsš£öd¹“ìòÊøe D«Ðc2ä÷¯[ycaUÎþ ô®vêÏB¹Ÿ¡Ü³}ô'ŒúšóµÑOÝÃ#¨ëœd {ñV§ŠDD7’Žãµt÷Þ¤vö3$™Ê±ÃÛ=Nj1 \¬×†0Iûœ¸94ÊN±Åoçm;[#>þøSaŽÕ6G;ÈÎÏü»ðÏZÚIÔ¡•É11Ü£<1í“ØÖ³Æ€`vÆÓ0$·ÍÂÿ“@ìÆÿgÉËv C*Å,qü^ƒò¨MÓõHžââpE¹;ñœõn1’jY®Ê«ÙÜ̶Ò€’/çÒ³ÖÂÖBcy¼ÆàùI=HsÙX½¡š>k?< ¢¡m6ö6LnÚýHõ"­È—6á,ÕVDPpŒ¶OSôúÓeÕ5©®­¤òO–I^FL÷üjã+¤ÈR‹ ʾrqúŠa~S.ÎOÜL.ß}ßÌTæù¥•.E·”¹+¸’Cc¿áVîžñEMõ)I¢¸@ƒz‰b…Âu>ÇÐqN’kƒnèäGƒº$9à¥[³K;iXÁ>æUÎx⥊»á #‘é‘Üÿ…9ÌŽ~{KÍN_:HLPbG|çšlúcYéZÎ̃#gœ7æ8ôô®¢åuÛ¬5'å\.~µIlSiûT@ã§ÌWñ qøT’Ù$VMnÑBЬŒy\ç=y=2+9ô¨â”]Ì…K6¢d vúW¡˜5 -¼©6Í ’þŸZ…i^Z1oÞ1#k3ÉÍ<êûK¾¶¼‰àA¿q@Ã2·¢úÐ|;ghía$~a‘@f|¤ŸOjíãŽ5¾ü–Œ¦[q`ÈHPÄåšäµ Ök˜å¸ˆO6X•e,zúŒW­[x_JC»MžL´’X…ÏæÒ´¢ðŒÐ[†ÓåÁ];@yúŒž´ƒ™?>‘åºZ½¿ÙŽr'®8¨ä°µˆ=¼‘…—ï w=:q^é}¡j?j†ÒDŽVvWóÜjĸÐã %–«hÐe‹Ǹ=ÿ ¤µ¹ãêÀÛ´Íq*P9BÈc‘üÁÍCMAUŽ£# ; úñøö¯Y>¼ŽÐ˜™er±$w¹­CH¼ÓUfžßƒ´ÄæËôÓFD³@Ê'žü¢FÖl2ýr*šêúp»1Ã0"F¤êŒ«×Ügùֽǃž(î÷‰nq$.clnR:÷äŠøþÀ÷‰ÙÂîl*… œsƒÍW2)Aœæµ©C<ßÙò…ULá¢RXù¬¨uÇHMÂÎ"uS½¢oŽIÎ:WbÞÓnÙm®q4RŒd' uúcéL´Ðô¸l¥K(.ä0Ù·)ÓNàÑ̃‘œ½¾¶÷Ë §—(|™±†osž=k_ðLJ/ç´·Ô,me2FÆ@!Ï+Óæ5Õ·„ÞQ#ÚD É8Üàw´ý7BksöåO´y[Žæå^Üû}(æBqg™AðŸÁ.#š$Äw3[O$% àtaœz sø'ÅšT\é:í¬.¸m÷&fëÆÝÝ=kÒ¯|ªÛØBîdQ+y‹)mè£è=»UK«]fG’  Ñä îœ8ç¥È9YÈ-¯¥‚{vÕþÔÅŠ< ß/Zç~Åñ×LYm¬ì4½EYI¹x¤ ÙÛÇýõ^”öž)Š8^y"XóØ’Žù=3ZV¶z¼™8§Úà±äö¹£™+<†óÇ?ü/¬)ðΠÓpÒæ6ÆASÔñØâ®^|W´ñżþ*ðν§Ãj1íGË7 ÿõêWŽdEXŠÃ8ÇÝ« Ë!hÂ(Q’:Ê‚¹I¼q¨f,1ž§Z˜"¦2Žp?ýU1+prO^ÜR–@àFùÊä1ùÐ>TNÍn€ý£*$ïëRù°.$'ÎpX:§,ÉRç+ŽAä©"™<¹P‚€•vÎqž™®4[’ýˆrûÊ{cüjÌ 4ÅTIÐ/ÍÀïž”G,NàÓŸÀÓ,[¡“ dùïD˜›PW rNÓÆ;ñØÓ­õîY¥†'8Î ‘W …nb3Kòe°O¡ïV–òÖ?˜º³à(x¨?ÞCj%Þ»øÈ³×ò¨Är"%¸VëøÔŒ®NÄ;€R['ôè}AR^ÄUñ¦5ͱ £'Ìq‚y'ŸzK"{£ee&rBÔv¯0øËâèVí¤i³Ç.¹ãGAƒÉ'=Gzê5ïøÂÿjÕ.ÚHÂEn7Èx鞃×9â¾Rñ÷ÄQâû×á¡„’O®3Þ˜s{q¨ÜO'Û$`\Þ²ÐxÇZ·k`“FnæC»8È9SÇ®{T1Kiyt'/¼þ`üVí•ÈŠÌÿ!S—Ç™¬c©rËKXÎùcr\¨0Ïzé­m›;F3P¹?)õ'=Å%¼7Æ­±º °ôöëRÉ}¤ZÚ<¶*cí üÿxŸzÆ[›";»këþ×rñ9ºÝˆã;œã¡ÀéQY^ ‡¶ó–;WËÀ'i>ügéTÖþ Bê[÷,aUXFö-Ó éïWkx= ̃–ÎÜtÛþ5#'IDo+,` 1pXãŠÚ.»ˆÁ1’K(çæAÛ¯Zçôë™!óÌc挩»“’yþ~õ±o%ìA9))•q¹¿à?þªi ´‰í…«€ÒHI …*pWñ÷«öùž5t?»Én¥»qY¶wÍ%ŠPnXÐ7ýH§ZÞ¬ÀBçcœ³>T¯5j=Ir7ÚòÎ|$…Fãò)È!€ {ó^£á…·ž*F¸¹¿ŠÒ4¸XÚGÇ@¹ÏJòØå æm<ï ˜ÓÞ½;Áßµ=˜7¹Y2«½†@\qŠæg¹h |1Ṙ:›©¤3JŽƒµw~m¬0Et óT𑌄8ã¶8®?íÒÍk’ÌeÎtNHþUbÊâõ­Ým7mP¤äþϿ֋Îîu6·pG1·‰‡™0ÞÊ8¿†:Uˆµ1iæp6òr¹föϼú ¿µ_‹‹èÚ<:4‡×üiñÅÛD’\¢‡9ç9û¾ô¹ æn)¦Šy‚Ía… ·“êO¡©¢ñ³<›ü•@FüèSÇç\42ØI©,ÒWH󑻌õúÓuÈYÈPºnËŒóÓ¿^Ôý˜›±ßÍâ{#hI&Ù´cùóõ¢[]ÚI cRê±<‘ú â ´´Ìi«…‘ÂnܼÛתC’âkHT F'ôÿ FuöÞ'BE—*0ű†ú_JÖ‹ÄR½²,°„òÐó€N3ÓêzW ¦[I¨¹ÚŒ˜ ã'Èïë[‹ wŠPBsós‚{cßéK•»µ<ÏçB².x€ÝˆàdzÕˆµ_µD¡•ÁÀÞC`ØâªÁz÷P$–¯´²`Üž¹šú¤sµ¶R†6Ù#œ·¶(°5s¥IáP>NsòõÈ=½kç ¯µö<ÿ‚€ê^±’ïÄQÁc¬ÛXBûæž ]"ÙVí# y–ò ln&Ø-}áÍ>çPyd”ÈL!Š0`w9í_?xkáÇoŠ>5ZüÕ“IÓ5AoâD7G%•ºÝ5¼|†ó.§WuÀXÔ†<¨=ØrVÒ?qÕiZ¾£ÿ›çFñ#2åAÈíÔ×[¦ÙG¨Ü¬‹E¸%°63“Ö¾\ý›u鵇®Ýë4bêÞq# U‘N?Nyõ¨àºø¯ul³µÎ‹åÂî‚wÁ‹ʺgÐüaª[G[yŽJœæ½VëÁZ£ëvVVÜyT¤Ó¥Â¡>ƒ=?¥c'©¤NÚ×]¿µž)¡ Ö¨BGòîlö#Øž•üÙÿÁS~ Y|"ý§´hzLf‰ñ+Í@ß@íJ'A<3ó±\K£?BOô£¦|8Ó,ä7W“ÈïIJIµPz``sëŠü×ÿ‚ þÎ~1ø¢é-ãUð¶¦ÜÙjº}ª‰n¬šYRXu÷nˆU’a´°Xp¬v·$ŽhŸ‹_ <]}gÞºV«2o@§89àî­v·Ÿ¾øÃͤë¾,Ñôw¹w8¼¾‚×Ì Ô+©8™ë_X~ÅÿµÏüŸÁ^ój/ÙëQ×|cm|öï«^é£Å¾Ül+»É)t§y,25ús?üÿþ ùà%Ýû.~ÌÚ¥ý¥è]ï¤èÛ¤PÒGvë6„-Ž€×®ñS¿»M³ÊT#ÕŸ†>ñ׆¿üv´ªäú’+í=kþ ÉñfÞÎ-7áGÀ½I½WØí¨ë­*ƒ„°²#>Ű}«Ÿþ uûaøŠý4» 3Â#¸+Æ×÷‘ù˜èÅÝ3n+S'îÂÆœ”–ž+gû~׺ËKBøWâhјôûd´ïò3î+ÕGü7þ ã},ôËKhØI Wš¬8cؘÃ1õ5Zïþ iÿÔl]4ïiV­:²´ÖÚh õXVfp¥{Í|õâoÛKöÉñ¤š'‰¾,kÆ ’8;WÂò~hOך«‰Çíi-‘ûâØkþ ©ðÎÍ$øÅûYØè1—Á{s§Ä­æ<Æ‘²ß5Ù ¯ø#‡€d³Ñþkž9ø’ð¦Ö¼Ñ.¼»IöŸºÒ¢DŒÝ9¯Ã?ø>DZùVzuµº–“l‘B®6ãpy¯C´±×4˜Ÿ]~Ѫ8ãÌ9ËÇ*»IÇ=iÿgÉï;‹ë+¤OÚ™~1ÿÁ1t‹O´ZüñeíÒmò†¥t×Cp?ôÒççŠê ÿ‚…üðd“ÁðËösÑ­_÷†ÕZFOâv #à{ Ù¯ÊOøßÃþ!´m#Qû.’¡mÍÎã$³«BöéúW­\\hRé±kê0´[\mˆ3fCÀû£©çƒIåñûLq¯¡÷]÷ü;ö¢ÒtKÂþ ðN€$ó.a’êf$á"€äب=+òOþ yû@þÑ?ì<)ñâV¢o/<+§‰ÚCbK9’[kxä¼³µñ:Ò÷À^t:MÓG©êË40Ûº¢£9¬nÇ•-޽kê¿Ù·_øDÿ¶ÿí㉋:Â?„‘ÞZN\°°ûOœ.çQ™#´UFeÜœ‰Ö_ ÆP‹ÔÖ8™AÆgêW‚¾)éWžÐ5«”¿ðåý”¬¶ø`ñÞªËç –o^GJúJÖêÛR´[ý>EšyY•?ýqÜWó_ÿ&ý¼o?hßÙ»RøQâ)­dñ§€%šK›9÷7Ú­3΀Fª»œìöûS%QÊØ¯Û­Sðí½×áýNçÃ7 ß5µá2[žz,½ì |ÃÖ¿”óéÖÉ3JØ,JºNé÷OTÏÔpqŽ3´úÿLú–âêÞÀÄ%uF•¼´@ÜÞƒ¾~•$w‹¹—©þ*ùëCñì¶ÅÅ߈¢þÒ¹8ŒÝDêþ\c²/ÝÆyÊàžõéw‹|?¬.ëä,:Æß»qø6 V8Ã×^ìµLHn´;Ô¿XÀp9éÏjûchlòsžõÎ<¬¸ÎyÆ3ïP}´©Úpy®™bé˜û&vQeyÜ“Œóþ}©­¨•c=NO85É Öf ;ö¦4ä©`p?²úÛz"½‘Ô¶¡(ùŒˆîÇ8ú ªúŒq¨Vtúw¯ñ¯Ç‡¾×ÿá ¸žû^ñ óiZ ”šÍ¼n2ãÊar°$t.µñ‹á/†ü £üKñ׈­ü3§ëרÙÁ¬+Ú]µå¯úës ÂTîƒ'¸È®Èa±"å6%*piIž×&¤IÈ”vš5Ç ƒêG5ÏX˜u*ËÄZmÄéÚ„BkK±2,S!îŒÄdÁî;Óf[”•ÒHÛ+´‚àCt#üq^v"†%7x3hJ în>¨Þ`ÞÛéÞ¡mE·í9ôœ×7%̉ò¿#‘Ž„R ä<’uç­yr¯(è÷:4ö:oíi>â1õç8Å*êò¨óþ÷>Çð®PN»°O¾E8Üa¼À½{ŽÇÚ§ë-õd»hÕî‘7gÔtüGzkjr2¹#¨¹=ë™3dŸš’;…aœã…bñû”©£¦kãŒ1ù[ï}*d½Fl*˜#æ®anR5';éJnðƒü©}a÷Dtëw„Uþç“Å/Ûð1ìOë\ÊÜ»àÜqО(óÁå‰8©:ï¸r ½v^ § ˆÏû>Ç­s«; ÉÑ›žqNIÜŽ üêUo0öhéEÚ€§Œõ©ø(ŸŒ “\à¸p?Ýõ©QŽâ ç¨?ΟµDo­ÀU!ÁÀôäT¢U'’qÓÒ°|¡Žxÿ&®G2gpÇÊǽ/jÄ⑱ó•.¼×ä`>e9g>ÕB;ˆCy“¹@} Y·Šâóæ·9 ~fnß©éWÌÌÙ(“ £w´’,ÜÝ8EaœžõÏ^ø›MÓÏ“¦(º”7Ÿõ`úúŸå\ó\Üê ÒßH]øãT@+H» Pêu7zñlÅ¥!UÿžŒzŸaTDk+oŸæ~îzœÔìO9#ÛùT°·'{rGNÕ3wcåF„k.JiÒùa|`U!QÔóÇ#"Ÿ$„€qùT• Ó|ØûÙíÛ>ôö—œzÖc»nUÆOùúR“´åÏJMØ,O,áP1;³øÕF¹28bqJ…¦Œ ªåŒx>Õ”›fŠ%–»ÚrO®jœ“–ÈPzç§ÿ[ëUä#$f’+ûÇòl!y›Ÿ¸¤ô¬”''¢/H‘»•9¥@®ó¸Š2Ìîp£©9¯ñ¿íðÓÂÔÞ Ò%¸ñ‡‰ãèžU»ž2F@¸œ²ÛZ‚9Ý4«ÇA^YªËñËâLFk àÃѼ31’öd9n59\drVÙ# ð$#9íŽ vR¨ùWžÿqK]wø‰ñ›áoÂ{•Ñüqª5™t:&™¿Õeñe‘ryy6 î@¯³ñ¿í-ñÊwOZÇð§ÂÊ¥gÕnDz–½)Æ[§6–ì:³?Œg¦/ÂÏ… ½Õn<;ðëL'‡lÛÌÖná̺˜ýØÌÌL’Ê×vb@ç9"¾·¹½Òt­,[XB ²µM±Æ£ ª:`~½É®ˆW…/á«ù¿òÿ‡+ÙWÃ…¾ ø]%æ©á¸§ºÖ5?ùkZ”Íy©Ýc'pùeŒvÄ›c\áT+Ñ Ê±Q‘\Ο«O>ç¸Eä3ݳëZp ‘ÎzÊuܵ“¸½‘|܈Ë m“Çë^Wâ_Í© ´¶&8»úŸ­T¸ñŒój DØpFrß^˜®GWK]£¾º»K[RYâi˜ lƒÉÉÇ¥xøÜTšåÕFŠ½Ø‰6ùâŽ/•ƒ¦[¹çú× ™}JKBŠV>›¹â¾z~è·ßêÜÅcVrFÙu®ºŽ¿µ%1Yk±¤òK¸ cx·³p\Æia)>[²ê½t;'¹·›Vx¤"$DJùàqÓÚ¼KâV¡x´>¥h¢ ØIXf8h^›[×Ô] ôºƒ[3£Ç ¸s$Œ® ¯Eȯ1º½képr€àƒšÏ+hiyk[OÏor»e†ONØ«¶ö2]‰`{žq[²¼MFeù†0çƒÇéYÝG"¬ÖÌ-Ø'·åZP¤¥¨§&¶0µÅTŸÃK*s.»íìvÃ3JëâC³<–n€â¸ÿºcÁöñ¶ë’6sÐ¥¤ÇŸÎºY'yG(ÝÀöæ½N)- ™vFHË äcñúÓb”<¶c»ßüþ•Cs†r…l‘ž£ëP¦ÔŒ31V÷öúw§Ê‰çf”R‰W1àœ‘ââ˜7– *—`¤°Ï#=*ŸÛmâ¸2ï,êØ$psßóíURø@ìÐG#—$ž2Ú5¢ˆ93I¤hÕ (V~¼äUËÞ6OM½…Uûe궪›W$†8à~5˜ïzI,»O9PkHÃRy»›,¶¨ÇœÎõþuNK¸|µfÜXúž½ê‚¶ÄiÛ;öð{“éT¤–E £,Š8ùHúg¸ýkNR$ͦ½å!C/'¹ w¦½åô±–1‰>Vçœö¥MB¤u· @?1èÃü÷¦Mwk))c» ïÅ\R¸=ˆÞêýu: èŠ9Ûd{®cŽEiÛrñ–9ÝÛ!ûY`€àzHýiKk&ód›?tõ÷ïŒúU—híãnÁâ õÕ¬UÉæ(ÛÙÚ#4s1õÆéÍuž¶¶ƒUº™äX‘K7FbIÿ•ÎZËÄpyeÉ9 c?^k{I›Mµ´™Yã†I߀_8 ס'ëšåÆÊÔ­ÔÚ†ç^ââêU´·Ë<ìÆ2ÇŠþuüuñ«Mø±ÿøû[j©çÿeo ]¶Ž“¸[y5;V6¶hªÇýuΡ%ÁÈù)ÎB×í¯Ç/ºgÀ¯€¾4øç"´ÃšEÕäD¨÷;vB œd—a´z÷¯åkàÇ¿xËþ ñc\Фñ‹üEo©ÎrÆ]FËØ–p0 ,ndšT^rã€:Ù<Êb£[%¯Â¿_Ðù.2ƹrP^¿äsÿ·‡ÂË ÿü;ý¬ü;bu+Äp\ê¯ ¡ûDö…dÌ„Íqžrc"ºfñ5íà_ø+I±{ã߈ëId (Ó´Xÿ|8ãæ s‚1Þ¼sàoÆK?‰°—‹~ëÓ—×< qi®øy€’[G¸S<@–XŽã€>T^Y¤øë\ø}¥ã_™ãH¶¼ÑtÔ‘µ KêáÌj Êðk÷Só¾fxßÇÿZüUý©|]ã&hæ³¾Ôî³ÂÛ£6ÖÌ €«w(ÕéÍzœ¾Æ´×Á½&{´E­Ááøž%ŽÕ»L¨ê6øW%û<þÊ?þ&ë‘]X[²À«°…Ãl™ á@ÁÏBH¯×oØöPñ(ÿ‚ªøá­õͶ¦Ÿ |:šµõÄ(Le™%HSü¬¯pß;?ÆRµý †¬þª“âËuÓço}¤2Ÿ "¯GñKVð­°›âg‡µŸ ަâdûM©aßt%€_v¾ßŽÌê[–k„…UØ䎠ƒÀÍbÝZjÚYòîÞöÚ>wÇ•sž¹xíƒZ%rœ|'ñ§áÿ.ŒzˆôëéJä¬N¨ù°Ø#Û5ì]Ý…Ö7ºgæÚw©ögœó\ÿŠü ðWÆöο|7§Ê“üždÐd+Ÿàxðs^jß²G€²/þx›]ðÜðÿ©HnÚâÖ6þåHrF:àÓåFªõû¹ôPʱI·oðºóŸ¯øÖá´´P³O}ÇA±ï_8?ƒÿkO 3é~1´F9 'Ù§e=þdݸ¿ƒÒ´Æ;ÿ øËÂ"ÒUŽ$i-îÜc¸–-Ì ûõ©jÄÉ[cÖåý Á,*Ž£8Œô Ó$÷«£G¹ÖC £¨=?à´¯Œ^ ×£6:v±e)•YvʹéÃc‘Üôi¯^KE€†M¤*‡·<õ56 £­üiæÆÒý¡›kñ‘´tüª¤ºŽ³§O̯¬²½êHëš– O_´yRçd¨ß2¶A-횊ïTwû*H¤n ºgŽxêh²F}Væh © QC+àzúSž[dŸk;…Úå=}>µ^9­!º’fíä÷ãÓ·jKŒqbX"«FUÏ#w#ê @k$ðAkæD<ØòÌvàžœõÇÖŸ4v–ÑFö÷ d˜no˜íËz“úÕRÖ<9W_›.‚vú­gZ$¦g’&Ýrw ØÝìz `tpÞíRIÂJøàñÏcõ÷ô­†¼ž~ÒŠá—÷{r[¯=ë€y$Šë}Ú4ŘœîÇm¸ãð5 Îˆ×®ÍÙ98ÇAô?…&Šæg_o>m7K»ão>‹ÐŸzµi,ËiY2Èqœn>¾‚¸Øõ$žÉfšOÞ¼¤üÁGsŒVΖ$‚Ñ&2d¿ÌáGÊAéø‘Ú•­¨Ú4丽’eÜÊQ~ñŽ•©mq~¶‚XSyv=ý+¦iÞÆ!Gâ}*³Jû¿u!ˆŽΠú~5I‡*:Yo¦’f’@ P%^ƒ õÏò¦é¶Ö+¼qà—%Âò7óùÖ,šÃÀñ ¬ù·jVB«ó¼(Ç@MhMý¢¶m¢H‚5p¬pØÎO©4Š:­£Ž=Ì<²Ü ’såW ÓÚXÃZ|ç¨#œŠÉ!"²ZÈÛ£b]Ž?ϽeÛé_Ú‹Ë{ƒ”ÀsŸ“žÀI» ñÑ£VPÄ¡?ÏéV¦ó,b&qÛ8çòjž˜ò\i7;gK¤LåJíÁ鎿Jš}NåÒ?³!hϹÇÌ:¥žFG=µË3¥á §žsè=*K (omÔ—>{oÛ¿  ÇÓ½Dºý æð“ $íEßÇ^yâ­ÙËa¨éKw"¾ž†êN ¦Tb6ãM¾Dhâ Têq¹»óÅ`yW–FUžÞHÎâX‘sÀäv®š[¯´À4÷`a# ŽHçZ6ŒÂ&³|¸=µÙd6Ö~dWÑ„†A€Tñëê+6ãC·#‘£‡x,»zóØõ»kÁg‹|ðŒn •íŸ_ZškW2/Û‚’ÌHByUEÊ6¼Ðî-î–=<–†VÏ—Ÿ”ã¯4Ùl®m[tc`$Œäã5èkmf“¯Ùä(èx¸9Z„ØÚÈÍm!ó6 ïû ~è3–Œó˵%¤„0F9UaÎze¥¬Á°Û…`GÝàƒÜšõ;˜tÈãhä\» §rqÈê+›ŸÃvX_0 Ô¦Wôh%%˜…Ù`á¤ÏÏÀûƒê*¤ï¨Go¶9™6HÈúu¯W]BvÙ;"yeY[”öÏÖ¡ºðÄ hnd1¹ ¸à“ó{PZ‹<Â9õ[H 3‘ÙJ†Î0}zlw×ÿmkmF=ñ*áXðŒOqÜþ5ÔK§½Œ¢yò¾Z`´|í¸öéS]#MЕŽ\ªXŒÿ±•§jS 5št2;Ê®2qÔfÊgrg‡ìΧw<üž¾•_û2êM’[ÌY›Œ”®:ñQΓÃw#j¼ˆ£¯9>§¯ZOç[Ú€àîcÔ1ŽüzS¢²˜[ý®5#dïo›#óü)¿a–+¨@‰‡Ìy$er*œVw7S4øi6+œr¸àZ$Ô$e½‰žTK÷rGp=*Ø­Ä ;…!ÁË:àg¿¥Sž+Ë]>]@¾ÒÎc'çéYM¨ê–ìf¼Hä‡%WøY1Ó ô­¡¯”—W·3îÈPà¯cõ§¼¤y2[q 9i H81Xm¯ù¡á–]’ä.ÜuR8Ç|zÒ>µim ŽêMß)ÆHEÆ€:t¸xy:ëŒîç¶zÖ|o§ù,Ï(e ¬02:d÷Àª â½v½Ôm Œà*<¡Á'¶qùT2ø§Â3¶–õNÁ¼EÈÞçò©h–‹‘Ϧ [2²«ò€qÏ<ý}¹©#‡XšÑ’Ù£Y“&Fb“Œ ò¥>Æ[I%g‚"…GÊá2BŸ^œþµª4²H-zF\nl¯½IœŠ—š¼æ+µóäÛ»zqŒ{c½Syõ),RþK' #ì Aë8üq]•¤w³ !¸‰UÁùA •õÉ÷ô®j=_GmÚ‚Æ’0ˆ’6À†‹P{>+`—N¶Ñ“Ϙ6ò::š¥‘wß™Ã!“ë]£áûÙ#Ày&Œ.KŽþµv;[/³N#GÀ)Ô•ïcñ M­¦µ·… $ºWiäçÓ5œtÍFâYŒÑ•Þ¼™r7ÞÿwÐ[i‹±w7›³x9$7~j í*Yíä-È!²GSßÛµyŠXk1)I@¿p³=;ÔŒ—fU¿Še,|{}8ô¯DìÂ6Ð Üàp,vSHÊHY‡$aI ãÛ𦑠œÚçkÏÝÛ…3”ë´üÀžNxöªlŽnLw»$Ñ’¤¦î¿ÃøW~Ö’+Ëæ¶W–#Ï=¾=j¤Ö2ÔÉ€§¦2?犮TR›<úY´¤ÞÑÙÓ€2:ŠÓ”ÀdYHV·dvr1Žx•ªÞŠ8ÛìóDZ0¹ù³Éô«2èÑ)’&M¸cÎyÇ×ÐÔÌy¬–v·ðO#@n!2“´,,\`sÎ{ñ[ÞÒüЛÅp^Kæ(­äžþa3s]8Ón£ŽHçMžý‚ý;VΟ}öâRÕ€Ø9CÀ©ö Mœö•m«$–‘2ÂϘÒ2IŸ»¸sŸJ¢š~ w߯1såìvã¨aÓ ×ªÛéÑÃ(µ$Œïѹ¥RhLë+Ywþì)Q°óï×ë@&&Ž×Pû9Ô¥‰‘‘U·ŒŸø¥^þÅž]GÎiNÂR'œW¤Ýið,Kå¨Î@ðÅ—¿Ðu§Keekq.í¼íØ?€ÿ&‚´îyÉÒe³¼IíH‘Hbd9ùºŒYèépâÊF˜ì!‘ŽBø#©ö¯O:m´ZàÔ îYYrJœŽþŸ^+"m6A-ÌÂCx.6®ãÏå‚)ó ÚÇ• U¶“6ð$ˆÇ…~ÀtÇ<úÔw1j6©$—6ßf’Xöeˆ2Fnp=¸¯Sm(G’iK+Û׆¬ŸìÙñ~ÊìGš¥š@p9<{Sæfv?ÿÑúuª1YF7y>‡¡÷ªîöóìŽVàœ\àžçëíSØ›¤Î{c|›|ҽ̡ëãÏ`šÞ! ¯Ì­ÈŒúжƒ{â@I=1Ç4Øw¢YCŒ¯ÞÍYdy³*H0€äc©ph‰²G¶â;}*êCoªËË‘€zg>´ÕO"6sÉl§ÍœO¬¬7É0×r…Àf8wÒ€è§&Lc¡#,»ShQÔ“þEW}ñª¡$· •SïLaò¸ùr¤ §ùÁ¥r^¥Èd¸gó110by?ÃOz†)äg;ÑGb=Ö¢–XÓ÷× å¨óëíPÄÁ[i—fwŒ¸Žý=h¹HŸÎ˜¥$¹ä r=³úÓ£ž0™2*¡·­0Ç#›ÁSÜòqÔS`¶R#’Dr8ÇéPREàw« g ç9ïH°ào˜€»™¶ûŽÿ•5 nXW9㌥ÕÏYͶÛ÷0¸`603‘ŸJl_iLÛÛÈÍ"¥ã4uqÉå$_gpûºñ÷ ³ ¶C4Sa‰R·N:æ¹;µèÈcÚ+Ž£@=êä%˜$V„’ –'“žùÍLênÙâ\ya>h {ÿ]2KËFw)-Ô. >ƒ€+˜†ÞS*Ç+“ü,¤?LÕ‹;8 ˜Ip@˜’Àóxǽ«&ŠNæ”#M†ÜÁ¹§oºêÇ=};ŽÃMÐ4g"ÕÏ| ìvçÇ×5^]¸3K*‚H\¼ý;Tšæ•¢F××’*ìÆ8f>ÞÿҘβ Õ´†+‹‰QBxçîðyú÷5ó¿Ä¿Œ¶Ñ?ö~—zÒºó09þóô俾2_êím.Ú`vùhynqí_:ÜÜ]NwÎÿ{æ%¹ãÚ€7õ]r[©Uåfg ‚Aç>€Ö|RHÐ,²?ʇv;½¿ýu^/&K„šh˲ávðr#5­¦és<»GnU†9î:P4›4忳™ZÚÎ`ª ïõ®–ÖÙ^ìùÊo/•çÔCYº~ŸkjÍ5 ÆÅy3æ~•ÒZ¬‘NÈÇ p¡†IçÓÚ¦MB.ç@±Áoe²xÞ7ìÿŸ­TÍž¦7–Û\ôÿg×½fË-ÁA‚»þO1¸ü\“Ú´ḻČ £ŒáK}áíõ®vÄ7FûÅAò·gˆ­Gq3OŸt¬>è*ƒ'‘ž}½ê´òE'“òä•!F@'¦1ýkfÙ¢šy|×ä ’ÝH°õ4ùDÊš-œ¹åÈÛ½Cu'=‡oSWãšÐFÍ.ã Ppsúã4‰j–ö«çœ¸©Rr3ß·Ë8¡ºòäEk¬R}úÕj&-ܱo4k4h‘+2’ýs[¼òÕ•cQ•bÜnRsCõ«¬n Ø"´* ¹KÇžÙ?ZhÚòT¶|Gú´ù‰aéõ5L.ŒK‹„µv–XÔîŠ1ÓÔö®Ç¾ÔÕdÝl,†‰§Gehçí%³¸ò¼úŠÒºÕŬq®I',v) ‘ׯJdó Hã½{rß¼ ìs€}ýþ•|ÛÚª´±)|²¡¤;r$ŽõHVÖä6Ö±8ºº€±9ÆIÆ1“Œ÷ ^hòÄ×SÙÉT41»`{ãüjÛA§\¨{gŽâGMê‹’¯b{jɇ[Âñmˆ¸c‡z€?•3cG1ì40€UsË9éÐ}j¡Šänì,H`¬Ù sÓ×Ík-W1ÊéyİóåG§×ž•v? éÚ„!IuUP V>§žyíJÄI˜u¥¼ê[ç;Ã9$’>è'ƒÖ¯7•o³¶Ä$e‰É>ç=pkRßòX˜°çÊ,J3 ëííV®£ƒK…n®¥•nPËíŽô›#˜Çh ůÙn]¡ˆ‘.à~b¿N·ÛJgÛfBÁ·¿\žþ§òª+âK3e$“âá‹íVN€u=»ñÅ^ŽâÒe’P ‘Í·åê0G8þX©bå…þ›¢ÖÕZCŒ»¨)‡¿£o"¬b9p¨Ü U žz·}Þ•›C`|˜c0©Û»túu­kkˆ!¶Fˆ›™”u “× ÷™¢’,jÇ&Ÿ,p•£ÙµO!GVìsüë´ý‡µ_§íCãŸÈ™³ñ„-#ŸÍQ¼Éa; à ü¥.\ô5ÄÆûÛ|ˆUäbÇÍÒ½§ö+Ó-ßö¡¹iAŠsá[­’c¦Û˜v±_N‡ñ]x7iÙœµ¶?2¿d{ ]?öpðç‡nˆi©XÂ’ÓUäf¿”,¬ãåW `Œuÿ ê„•Œ^çºé^%·µ†w´µûH'q!%qÇËŒ w©l¾*x¬ÊìÖ¶¨ÌBä°ÌkÀÎO¯¶+Åü7u RE{4Œ>öÞ„±9àk´ºƒí &µ8gr–†8×5rb=4x¯ÅÒ4°O«9 ê‡×Øý¹¯Yð¯!³†ÚçÏrP¨ç,§¯Ú¾pºÒõ[mÄŒ]Dƒwê3ý;W_àí^Ñ.Èá˜G¿*y`:õç5„µf‘’±ö?†µ™õj+ÆÊÆU‡—'ÝàsÇ~¼Wu¦jqǧ}ª>7)@YçäaÎAÎ=kæ8ï|ojÒi¶W¶±Ìra<ÇPÄñŸ¥t·zŸŠ´­8Ã=ÛÁrè’$ùT©ààñ‘Ž}(ŠiÝ Nçó­ÿ@ýštOÙÓ㎑ñsÀÂßÁ¾3¸’?°*7•¥k[ZgËq‚Ñ.Ð#u ™@ùûÀž5ÕµH^鮕®a‘YUFcD#9ÎO\×ôeûN|Òÿiÿ‚:ÏÂI<÷°ÇsotB´‘ÞÂâH%pÀe¹QŠþg~j:†ƒâÝGá·‰­%ŠóJ½¸Óg·ùä–åÜqŒ®rpkéð5Ôáæx˜ºn2º>‡W¾»´>hÁrîpÒÆz ×ÓK£èúUä‹oowB‚ ˜ÔuÏ}Ý­h§ÑnîgþÒ’+‚¿$FHÁÝ‘íÓšÖy–ÆüÃgb›/ˆÅÒõ'€zþ|×[]N3WMÐîín¡]*wº»N\´¦Nœ¯ ç¥^Óí•%½Ô’Æf"G‘PÁ›û£°¤Ç¸‹J²ŸÅ‰©^¸ºò±"08P98îsZ¶÷¾‚îwY^#p®¡·˜áŒ"åB®=:ûñ^NºÎ§§Ï5…™ó¥- ÄQ[€FÑϽ*MTXà9‰å’dÞ Ê$ØqƒÏ͈÷a)öFÔÕä—sõ—ᕤøö|ñ‹~±ßÞiz>¯rÉ’DQÝ ÆOS…°s_µ¿í}ðßþ ÇñVçÀ¶š~¿â]rÿÁzÔ¨ª“_ÚkS¤Á‡-"­³@È Å~úx¶>&ðEç¯ÛʰÔôŸì‰Ž0ȳF±Çu`ŽõüŽk_>"þÕ´‰ÿÿñš\Yø“DñòxŒÛÒ´äßý  ö®±¡=å_Zø.â¨Wž-V–´ÛoÉw>“7ÉåGÙ­$Ž»þíû+|lðLVß¶g…nZÇX¼’HtKi£ŽòÙØ,¿hù­îÛÔàä ~òxþ çû"êZæ±á¯‰úW‹|áûÙ4ýZA¥M­é–w Ê×V þ5W€5áÿ~*EV¶ðnXü‡P™8‚.©ËqÏò±ûLþÜ^<ÖüUi¡þÌ·Z—¾x3}¶ƒcÍÄæbkÛöÎnnîß÷²wn'É¥ÁÔø¦¥LÃ8¦Ôeü4´j=õÞÖóõôkgŸÙ‘Ž «[¿>§÷—ðÇö²ý>5¼Vß>/øCZººQåZE©GéêFmåÚêx'WÔÏáͧÇ{öYn`nRdeaêI•˜¬¿·Ç-zHáøúWŒ-C®éV·]9åÄqÈí^ïðŸþ oð·Q‡Pðׇõ_ “óOÿw‰µ 'vKPÒ[¨Ï>^Òsœšñq¾a.Þ%Ù4ŸãuùÐ㪟òúšgú5Yx§ÄzQ[{{‰P!Ç‘&q鵺~b»k/ˆò¨Û©Û{´|sî9¯â{á—üwã6{$âî½ylˆ¬!ñ߇,uøœ€AO7N’Öuà–bOw¯¾>ÿÁx5o$qx«Ãÿ üQ÷#i4½vçÃ7³’:Çg{±†'ø|ì´+äq¾qï V5é{~vüÏb—eÕW¿™ýIZø»Ã×`nºòYº¬À¦>‡¡­Y¼] xSRÒ¯xßâW†nlµ"‘\hž(y4K­àƒ0ݬRÇ"°VB0Ààƒ^L8w=ÀURÅá$â·q\ߑбøÑå¥U]÷Ðý‚økàIü®è0Ùè÷÷V·—ËÚ’\ܦ3q0,d•G3Ù.y­ŸxkI×´MáO€Uõ”ð½µõâëZÜ@\kšÅÒò³H¤ªÍ+e›®Þ1€+òÓðïì¯iòxƒà凨¼DÂ6³ño…õéæ×­Tr©à™ÙàcËÀá¡—øÑ†k¯Ö?g¿º4ãâ‡þ=üBÇú|RÛYê>/û=ö‘-œåL¶—:}¤±<2ì\Ê….,€Ÿ¿Ê8‡-ÃRöU9¢ÞüÑküÏ–âkTökM¬Ï·|Að3à·Ž<ោ^*Óì|{àßí=SW½*ZµíÉc<ñ¨ códòÑ~laA,Ûdx³à¼º7Á†ÿ|[âmWÂ^&Ò£»Ôu-CÔœ¥œìeKIg#t«lHŠ7l3fÀ—<1â/ø)Ã}H|TÐvˆÊ™ÁuŒE¹úäçø«ÉÞiynràgó«¾'ñV½â=R_øãPW2eUÝ‚Gg"8—8Tƒ¯RI®q¦1:‘Á‚+ñî ÅЯŠo­­ëæ}fU‡©N—ÛfÂÝ>ß~? RÏœŸçY"\18àÕµ)1_9);ž“5TëO¡î+8H­ó/Sï[4û+jZX‘#‘WR}ù¨m,µ=AÖ->Þ[‚Ç- túUÝWG¾ðý±Ô µø…ñçÁq\X³G=®Ÿ¨¦¥t®½Wʵó öÅ|Iâø/çüš+ gø5o㯊w¨Û×ÃþšÌNÝs°Ž}qÛ®kƒ§~zˆý«kùÉ''×4­z¦=¤–'Ô×áf¥ÿpý²¼cknß¿cíNÖ+¥bšŸ5¤´‰ãc=¼q+zåwdqï^W«~Öð[ß™¢º×~ü'µ”0ŽM+OmNúØ»“;ÈŒøîoº+ê°¾æÕWïcêïù\ó+ñ6 ÙýÚiú¾ªq§ÚÍ1ø·òS]†ßÂv/©øÇP°Ñm㼚…äVÊ rI22×òƒão~×4ûk/ÿµÏÄ f+rdºƒBQ¢ÂÒt*¿gØJÕIçê+—Ñÿà‘Ÿ ÇÄ[àïŽüo.¬É*kzê]Ü%ÖàHsI :G9¯¦Áø7¯‰Ä}ËüßèyUx¾òî?{?|¾"ÁS¿à™? í®åñÇo \Íe¸Im£\6«q¹N ,vÈìÍž6®NkåOÁxb ¦ƒþø‹ñ"u<¦›¢ ><v°kÙcvRxùP‘ÔŠøKýœ>|9º“–ÿôïÝéÆ$if’\pw`g{’©Ü@Ækì?úwÂïØ£áµÃ^ÞÎÿãwˆM®¡©˜6ý—Áú@edµ $¿ºQ°±Ë噸EEo¥ÂxU“Ò·´NOÍÿ‘ÁS‹1Ò /Dy·‹à°ÿ¶F¹Âüý´o ÅåÉ$w¾6Ö¦še‰qy--âDR;DìHíÏß±Œ¿k¿ø(uÚ#ö¾ñÔ¯ðÕ®ŸKðç†ü+l|=§jÒÙ¿úMÔæ æh#—6â7˜G)Fܬ„ü»ø‘ÿ söÑý ü)ûü9Ô®[Zñ}Á¸ÖõÓNÓÕC\ÌûFÔX-ÇÊÚeh£?|šþ£Ž…àƒ¾Ñþü7³ð~Ÿo£é6ÊO[¨DSêÍÌz–$šù?Y^G†ú¶ ŒcR]mv’ëwvú§Ü÷¸yâ1•}¥Y6‘Í+øá?‡àð_‚4›]"ÆòYið¤¢õåTÏç\ÔZ§‰~"k6žðèó_oid•†ûÒ9=GoâÕÎx×s›Ð¹– ×'$sžãñªKxÐA,º”ÑÅ æ‰X$jƒ®òxVV§ªé:^•qâ vå-l,ÔÉ<²pª£ù“ÐÉ5ùïñOâ–·ñ~äivqµ¯‡£5µ“Þܰá^lg9ÇÊ»óT å¢Bv[³Ò<]ñîÙ|Í3á²,±#0ÓŒ¡ÏxÓ¸ôfü«ç-Cû[Åš¯Ûu©§ÕnŸ<ÈL„öTd(ÀWÂ?µïüSöwýb¹ÐõÛ•ñWŒ¡g‡4É@6îWr‹éÆä·>L4¤…¯æ÷ö‚ÿ‚¶~Ø?®¯4û þ à]7ÃàÙ LœŸ›‡;H ÁÐ63´WèÜ;áf;0Н[܃ï»ôGÏf\S‡Â·{ÏÈþΗH¹Š1o/•Ã4©ÿÇØU{ZK´XŠHŠÌœz”,8¯ó³×¼]â/jUñÜÚ…É|Û¹áð;n”¹ýk»øcñÛâïÁíQuo†#Ô´ ÑÒ@Ú}ܶÊZ6Ü7"0Æz««)îM}ô¼¡ìýÌCæôVüÏŸÿ]fç­==Oô ÒõÍgÂó;é2¢—™`l´OŸU5îZŠm|Yhg³U†æ< ôÆåõSúWòõûÁi.°{빇aû»r1ÿWM5Ø’5TuMÄŒ“\·}'ü%þGå­í5Ïd ÿ:»©™#òS8lõãŠéØÂìÞk°4¬vç$O5f7@J†ÜÜñ‚ƹg¹‘|¸a9?{Ž€zR5Ìò?—óýîqœS¸]Kܘ³ÀõOCëŸçQ=ÔUýâªíc<Çÿª¹_7gîQ¶«yéŠG¸`ËdãÜg¦)©!6Λíªò8'Ðàm#œûÕVºh£ÀÃaóq\×îÆQÈ|žÝ3éøÒ”2¨|µä“ô8­#2,Í«›¤u$õ< |S!ýá;Y1¸c¦yŸóŠÄ†âyÙmôØüãŒ2àrÞ¼ÿ*ëcðºˆrøí#&†ÞFj]d¹ÏÄñë:¢Ûè =Àä°8M£®Oq^©g£bÙþØHàö”gòÇã\ý‹è´ht`7¸ê~þ~½¥bË%ìįšÎÌÙ9bɾÕ)ɱ«#cT²ðôH­`+9 )9úÖ(,~Báv(ì{gÖ¢óâ b #ÀÜg§µTÔµ M&ÕïõØ«Ðu,}z“]°ØÎF•¹’ Ò&G9 ŒgÒ±o5Ë{Kåµi»ˆÔn<ÿ!\ÍÖ¹¬ø†GFO&ͱ†“ïàuéÒ—¼Þ~õ˜.R¼’Gùö®ªzΦî;«ÛqrZ°ùQIÜÌNHÁ=¿:Ùðö¡é66ö¶V‘ÄBŒÌ2_'¹$óžùú×/6¡v,b³óUJËÀÏCìkN]aK:‘„ÀÚ8Îçâß4ùM©¾Usò«þ …ñ²Ãáï쇣|³n|w¬}ªìXiº1YI#ïܘ×é‘_šŸ¿i+ŸÙ?ÅŸ ~øºHbðŒ¾±mZB@[ KWšYþÔÙûÊ ($~íÉãð_ðZŠz‡Å_Ûvóá~Ÿ½ ðu‡!V?'Ú' %Ë…ìY¥@Þëõ¯¿jý3^ñíkâ"ÂÔ$76Ú\hþyÛÁ(\€SÛ5ý[ÀùZÁdô)5«\ÏÕëùXü§=Å:عÊûh}®~Êž-ý>!üVøÏ§iŽŸ´ÛvÑ­¯Šmoõ卉|aŒx.Bçd[Žkî¿ø“þ Yû;~Ê6>ñŽ·¡øÇÐé“Åu«ÙÈ·—±Ý]þñ¥‰†æ]‘[~@⳿f+¿Ú#ÇðNÿŠ?°§Äý6ûZñ‡“⟠iŒO¥¤³5¬±›c›y­~b‘œì}…T~`iÿðH¯Û L³µÔ~%i¶^¶º•WUÔ­á”+ô>Lm&r: Àãµ}óçÒÞ'ÿ‚œx_Ãö©ðïöhð²ê-F6Õue0Äòü¿¾¨CHIÉĬ§=köþ™áOxÞÛâwí…ã«úÞ©­YhÃQò–;yàÓmʨ2–B…W¡\‘_˜Ÿ ÿàž?þ]G«üY»KdžDûD·r‹ ,.%ÎæN¼gu¯Ô¯Ø Ç´7€e´Ÿ„–>Õ¼3âOøƒY³¶[ƒÜ6—WÒùNw«ÆñËS ²ƒ“žƒžµ¹Y½%©ûÚ¾!Ó.e}/ÄoR8Þ¡r¬TsOöÏ5ö7„õ›Ã‰z|Åûæ)À`G|ÏÒ¾#ð×íiñÆcø›àÁ£, «ÕªèÃI!RÙÜô¯gѵ¿|iÓçMĺmÊì,$D”{m#pÇ\+…ÈÛ”öûß^[Ã/Øõy$xAÝ‚¬TPNG­SÒ.¼Csc ¶°-Á`p"ùIdêØ=3^aÁiz„¼s^Åv®¥dód(ÿï(<­}£_j¢ÄùéŽ%Êy9÷õý*.+gâŸj^°[ßôU߈ä™U•™»s‘šÄ‡â5†©,pé÷I!—DPgÜð2q^Á{©i£Ck¯ip¡•Õ®bGùs‚;¯.»à»ýr V{kc"¸g`¾Zíû¼ ÅZ‘6gž3ÒÖè%ÕêÁ4ŒUTÈq¸÷>ŸJënüc{¦j lDή#¢=Ï©öï]Eß¼w»Ñnã¸UMÁ„jß7·Ƴ®¼ªYâ[(┎…ÏÞÏSš¥$IÃxÁ^ ñLÑ¿‰ü;axì "´<õ\r>µäúoÁé4ÿ_Ãáí ÀZÞá/…ÔžàÛË£Ú½¾iõ ~ѬÂÈŽÜm_1[pèçœzUèu¯ _Jf`ÕcÊ“êQÚ†•^Áñ«ÃÖû,£Òµà7~"h~eŸÃ¯ 6ÊGY×U³:‰ˆ ìàžyÈ+ô5FÖÿã7‡®^,ðí¾¸!âK½"ì!•S£y9>Àƒ@D½Ì–6íö@FÐÎîW f´¬’Þ{R'dW—H`'Ðg©¯Ÿ"øÍðýu8àñ¡q Ý)ɇRŠH#dz6FGjô˜¬|=â8#Ô¼ˆõ8ï†kƒ®;‘³ž}ûÐäñÉçI#ýÞUÎ9|vǹ¬ÛMi¬%€˜ÈƒiAؑ܊–x  –šLRFpÛÖA·Üž¹z­RÉÞ&’20ä€1õ8É¡Å|kÖß47òòV à’z}{Кޑo¹a™¥)÷ÆÜ·ëõíX³Ù‰‘dŸa‰Ø˜Ü6rËøwþtûûÕ‹OY¢Û#ìÜr2p=‡?°¶siWM3ڳʛ•˜H àŽüf¬Y^j®XIºA»c˨ãëYQÝÍ{¦Åq ÂWb§€rAÀëÏãS¥þŸhÞ}Ô§Ëi;˜öëAišvšþœ–&É«Ë#«dõé“õÇ3O§=´k`Ò¡¶\ùIÀAëPh×V±y3ϽÀÃÒž¼d öÍkß½Ö¡y=ͼÌmÒ/–0BÇéŽ8üh²{š&TžëU»²‰-á2I&w¹mŒ2ŽØéÖÚ¬ïo%Ç“2J‘°d†céëŽõ=½·ØØ}Žö5¸`ç;Iê8töm¨F¢a &Êç™ ÉëïE†I§ê‚Ò /nÀ‚Y” ‡ãïgÕ-µå¼„Ì·03å¯%C1‡®*ÍœWM«'O¹qðÍ Tžãõ‚“è—Ñ‹{ðÏö#C•?xzvbº4ì®í$šeS–¹—q^ Õ}5·Œ²DžF•8éƒXBÆÄ¦Eq»!”€üâ X­„vöòÛDÆÇï¹³îôêJ,Âègf›”•³û£Ç=€ïïVæ)¯ ËÞ>UaÉǦ¯ Õ4M7]ñW‡}™ÈÝl ‡s¸ýjÜðœ2’{»™wÝÆÓî{þTX.z’ZŨ5#ùzð¸9qèE@lÂÃ=˨ɹ˜òóëô¯?¶ð…¦Óœ£á‡˜ÍÉãžk]ohWKu 0Ës;@c¸¸¦Å`‚6†) Ø»üÇ­I%Är`?<œ9€ÁOµKÄ%k…m›A‘œçÒƒbìLšÚcTÊ#Xc9ªw¶¬É±ã* ƒ,œœßØUíMY ß´¸sƒÏONÞµÑ µ;™CB±[)n™ùÔc©šB±Êÿg̤Aq(a´Áyã¶jû’Þ(,ÕBÄ8oâ8þ¹?¥mHòÛ¤ÖsÇ)È-æ,¡³€xëš½m£¤ª™|È,rHç 5VAdyÊù)~°^1‰2ë׿^槸Òa†G¤HN Œ0nz{W_6‰¦=ÛÍå™['qgÈç¶Î”¯¡éòÚn0!\¤˜gÒ‹1¸Í4Ö¨]‡!Tà¿eG¦OpV[hLQC™IØõ¯ {®—+—÷(üã qéPÔÝÙc,ø‡xŽø÷î†y|žµƒÍiÚrp•ôϦJÁ½ð‡®Ò)ï­ÅØo˜,͸m=ù×¶¤™¹{›æùHÇ~ç:qT§îäF¶˜A‚¼ã½+ <5>ø*Úffgo2à– Ã=CsȪëáåó¤‚ »ˆUb»™sÆ::×Ð?a¶žâ –FnUT ›‰é’:óùÖU׆äÓ'¹Ôø GUù8ROõäyÑ5;kuÒ´³-ÃÆLÙ,7ßoÖ™£,ö«ý£uѽ®]U‘ƒ?8*8û¾¦½Xé²$‚àíäY# zñUdÒõžCØ!Ø»ŽÞ˜¨2™ævzñ¹ŠKË”yÞgf\‹ŽØèqøU­>æ{ɦ g¦I8Î:WVúW’÷%£G 2¡—. 8=qáiî;í5%I Šœ(aßÖ€¹RK‰¯fWšP}ÁÓƒ×<ñŠIš4¨w$lÙÎ@äçú ¿s£ß2˜Ò9cŸnCäõçßÒ¢¸Ðµhá·:?#Τ`|ãhÁQpºVÓW 誉´©ù~Q×4Ó²iü¶ dáÊ’ßç¥P´Ðc²¹Öܺ¡1ÈÁOÕz tú”–×o: !³m@¾ž”Ë7vÅî¥[טtçèxìjxí-£–7ÜÿÞÚ@úŠÊµšêëTMBù7«†Œc^MnJdš É^'s”’$ßÔo­4õ t·²Ùn’ó3Í ó(Æâzé“TÓY–"kV2Êã¿Òœ“ií|·0]I3Ÿ”y‘ð1œ{`UxVò&ò¿ÖJùØÃï`öôÿëU¦‚Ý ,êKcCPýM;¡ŒdN_N½G?0E|ú„ú\>m½ÚÆ1#®TÔØÕk+{û[—žºFÈbʯ f÷Ö6Hë‰åíTàœ÷ÀëY×VPÙG Š^m›Ïçê4[’Ó2’Û†07{{vô«l%––<ÓŒ/¨îs@w\wùîFFJòO¥Ay¦ÂmÔ‹t+`äžÇ‘úæ´ÀšU‚#É%A#Ô–²VÚÀ.°>¿ýzåîtÂÖé- eœ; §¨ÔòqY¢ÂþWO*u,Õ É$öæ»Ñu§Al"Š<¸à($ž3“úóYÃQ²ZÇ—$x)æ.#§ÍÚ€9Ilî¤+öGÁ\‰7dŒÿPxëQZZÝÄΗ0›¶Dß¶1·‚NsÜý+­†â9f•pPÆ0ã®sÐŒU•{KYH£òÙFw‚wuÉÍR8ˆ/Á‰DpÇ滀 ¼ô÷£˜w¹M28ò7sóÉ÷>õqòàÉpaãpã ÿή˜,£@ÂD¸;‹e²:þÊ‹§U†2‰î Sžá}èæ‰<íOí%DQ#+•ãßüjÌaÆè^4lŽS’vŸâühÄÅ,²¡luažÝÊ­ÀòJâÙC~aÀÇoƤÕ!<½:Þ-›ƒµS-É=±Ó4ÇŽt»,ëû¥láHÎþœçÞ¦¹±±ž‚ê=Ä–'sŽÜ÷­µ»­ÉÆß›$ž¤t f7–ëp$’B ƒsÇoJ…b×V8c ô®Â=By®±æVùñÎ1ÓÐT’ÙÁhñH©3ç<’U{õ=é09™Úvº—*¤`í.}ê[[ì×cå\pÅ~löÇõ5Ò.œÈªFÓrä·=8Tÿ¹DhR/:bù‹ÔP\N]¬âŸåO™dR§Œüö{šóŠ:}ôöP=ƒyd“æHÙ$ ʽ¾ãÝRr„Œrx-Ïz>Ãý©4°ßª˜†ù8$uï@ÏÌSG{q#Ï>Ä Aùvœg¸?Ò²"³’H‚¡Wy]«´ûž¸¯ ¾ øFÂßW–ê/š/0„a–Ïâà {u¯'[[³)>pfäM•óÉ,V.T› Ôýzæ·´á-ÅÀòÔ¤ŒÅ‰ÛÃ:žÜVµ¦™"Û‰$>X‰Š±’Oò®•ü6ö×I+¤¤)FÜH!½*\Í£´*A5Ìë¾"ÛÆçç#üûSMªˆÍÃHÁ™ÈÁ9´Ë@/'† xÂIÚÃ?túõ²lÐÏ&ÒÙQ|Ýê$RÂN{cÖ¯J«‡»aªœ‚sòÔðÚ[]ƒ;(nôdq×Ôt­[=PÕ[Ãhçp?$g'÷r”\W98# yÙ|‚»mOR=«¡¶Óíðoš]±m±ÎGc“Ó½CÃß¼m~þd–£M Y.ºãý•'×µzö—û=hñ\¬ÚýÜšŽÞB¨ò¢ ½ñËuéš´ˆ•E±ò< ‘ p¬é³!N[ñ$9ï^…£ü:ñ¯ŒZ&´†=6ÈìS,ä‰2rƒ¯N•öŸ‡üáÍU<5e FTßæ8ÜÁ‡S“ÛÑk {(B‰.رÎ=cÐZf\ÇÎ:'À .ÖA&¡­Ív§?* €×æ½WCð¿…¼0 ­˜ŠqÒw;üÇ¢ñÐW\ú]œ &ݰ._¹PGN:óT/,­u+TÍ…(ÃøOQ޹¥}EÌP’Þk¹2ŒL áøóŸJÌÙ§Y£O«M&8øç#×5 þ›k¦€mzÉ'È 'n:.E@-î®,œLw&I, LB::d‘/Ùgeû0o›Æ_8Çlõ§ö\ɵ¹g}cetÓß”f™Î6G½‘;cîMc\_,pÄ•UL\*•qÔäñØU©í.®¤µANΙQíÚǨ$g?N”Îj]ø¾ÇN›z‰ ŒÁO¿“ÔƒšÂ»ñ%Á•o4åHÄÜuû¬>÷òk£“B[P]M"»‘ë¸;Øzš»wkaqÛÄFÒ ¨aO¨#¯ãÍ·ru}q¬ ´žqI*Xnê?ÙúVÜÌ’ÆÚlpîråŠÇrýj ? FŸ%».[+†Î‡¹'Ú´Œ6i[Æ ü¹Úë’?Ù'àÖbHÎk-;WÛmªDb Œ,Gnâ9è"’ ¶…E‚†@Ä<}jÊAvò#.è” »¶’2xãéí]m¾)¶0Cm.Ö9sެ8'Ž”ÊŽB 9݃@ÊIØNéŸz±qr^8ÙÖ^ó?_\V¸ð¾½H±ÚNÈÇ€¨XŸ¡©-|5®Œy–“ÂAãÍRÏ 8 9Kú[ÚÙ;]Hdaå‰Kå°F0 žýëÖÿcë5±ý¦ãódPgÐu PIÁ‘™átPOR»IÀíÏJð­WJ¸ÓâÄö®ÑÆ~bYr™ï‚x¬=ãw€~ ü\ðŽüe¬Yi¢ vÞÛ0ÇþtL29Þʱç#º0×ö‹C ɸ´‘áÑZj^ý­hïݨ–K/Íß”y­•­âãœpe!½ë²µžê[¥Y×d}{\w¤øågÿ·ÿÄ?èQÇqáOˆžð׉4}b£x5&…f³º’,’SjZ²¹H•ʌԓÝO ÖÒ@ÍЫ÷¹þK­QÜÞ‹¼éÚd?ú*F¹ŽMÉ.q·<œvÅz_Š$wÓbi º¼{ð¹$œsôï^9áFDhãÜ¡d,÷tÿõ±^£yyÖ‹Þ@$FªÀôïØrkÑ£å/ŠZWÚ4©¤„n‘‹»`cƒÏ¸¬ÏxÃÚÏ‚,|S *+Ç+ ù‡'©úu¯Bñ…¬SÛʰÄB"„!ºç'#Žk˼ âÈmü#ªøößÎd¬^B—98ö~µ¤^„8«žo‡tx51Ã#yÊG¡ëÈ?úõéö^ñ,vÐß:,0±RB õÇ|v®-bÕ4yéâ–Øà‡rpH'±õï[Ðx‘çu²¼¹’G<’íòªuèG_zÒ÷*:+‡ž"ñ%ŒŠ'ŽÞÚC³ XޤÙ®9|sá-VQe#$ÊW÷òsÉ#¸§½tú&­(@ÖfáÉ<*rŒ3“۞خ—SðŒ5U[Û‡Ó§ çƒÆ8äHÏJ¢vZV¥x,#[yD®\»@2S»ÝÞ»ôvŠÂ)µ9Ï’è_s62çn=3Ö¼kGÓ># YtéçR® ™#L=I¯Q“ÀzéUŽmGp Û;†=z/aòš–¶–ÌÁb˜´œ6@èö?¥=ŸðT_ÙÿþÿÄ›ÿ>Ñž95û«{Ï·C’Ñ[[ÄVåÛ€v'¢ôäWô37‚¥Óæ&êý\ ÈÞjÜc;‡Zøö÷Ò4 oösÕ´OÄn÷¤b)‘÷¼Óå‡Êÿ ÜA'‚8®Üg‰8ªZ\üCø[ãÏøMü Ói2\¾Æ‰î7mØè2ßu žõê3µÉÒí –H’D_›Íb6³ð¸øë_|$ø“â_„÷·_ntè´Ë¿5Äú¬—Y'A)åFG–˜L ò͞د¢t-g^Õ"®¶‚ßL…Ñ–W½”·O“)ëéº+Vg§¾”ãH–+»û4šÐ€S Òo8Î ÷¦Ko ¾Ÿ5¬:•ƒÈT"‡¼RÃ$c¯jÁŸ_ðÿˆ¤…`Ó X$“å‘€É;@öüë[ížÒm¥:.š²ºRY#Q–껲çŠÅÏÄë»èæÒîbÌnÛÑ”3²Løàפü6ð—¾+jòx á½¼÷wnzñÈ#„¬qà>dªç‘ëXÞñÄIn­ÞL6©k´r>Qúö©üâsÅš’A¯Ü\i×(£"&‹ì \ÔóÞ‚’;{߇~=ðùsxã[k–Z™æE½ f`-¹LÁ"¹»ï¾¯§ oD±Ðoí·¨Š[ô¶Þ« W¿Ê þuÐü-ð€Ó,×]½×ï°w“eÔàÇ„<ãçÜ„žr3šà>!xGàn•i%ì÷OqvRI?}}™I”’ÛBeÙI<–Î:ža¤gx[Zñæ·u¢ÙëºM¿™–H`t›S…UUÃOAœÕûøX:¶¿áo kGU“M—R±·r–L!òšuß½öáT޼ŒýkŒøwûPkž ²¹ð—‚¬t*Úò?-n2÷6oœ |Ì1,ÑÇá_Gü%ø­âïü@°Ó¼aâ‹»ëíòbkƒ UiÈ1“»±îMpæ2ýÄߑقIÕŠ}ÏÖ†1³ƒÆ7šMáòíµKéV%Ú™ë…p1ŸZø‡öîø©¡þÏŸuOþÍž‹Rý ~%Ùiþ±Ÿ—ò-ávcq(åA@Ù,∲cÔÍÛZZ߬dmnåóóA=ñžx¬¯þ;ñ÷Åã²÷ <_üu¯Z˜.'“ÄðZ,¬ †"šoÝçiÈÈàWóöG“§ži+BzI]ÚJ÷³¶é³ôŒÏË‚æKU·‘üÿÁ@>?xÀºE×ì_ð;Q—UÓl¯ ï¼Bï¾ãÄž#Ü|ù$qÿ,!q¶4Œöyüâ—ãŒ56†×T‘LQª¢¬q à ‚Å¥]XüO‹É»ŽÚÝ­¤òž@[h-÷;°ã 8íQëþø/âhþÓàíhiÈß$7¨è§>„ñô濦iR8(Gd~W9¹;³Ê'¹ðýó‹ë6G=]Æ?¼TQøcCÔŸvrÙ>àÈs]¥ïÂ/ˆºi´‹¸ïaÛ¸¤,wägZóízo&•ý—ªiþSÅ/š÷3¸ä`eùqWbºøª¢“a$w, Úÿ‘Çó®v÷C×tÅ"úŒg£=ª k¶ $W/·Ñ¾aù6k¥µø‰«Z-•GN6ÿ*¦îRÙ™^ñ‹<xÚ‡…u­.w]­%œ¯läÄÆTš÷ýöÏý¤4‹´©¼O>¥kÇ“©Gúè|ôv?]Ù¯"“Æz5û©Yù¬GÞe\Äc?•Jƒu4*¨bcüB_/E#­g*q—Ä‚3qØúkÁÿ¶·öÞ}çt(n ׺¹Ð/Y²I-=ŒÈNsÐ:ðkîß…ŸðXŠ?ôˆôïxïâG„’&À† bßÄ6¸8ÆWV†I@ÜVÅ~7Ká-.D2Ø^• q‘ù®Ef·ƒu%?èòEqŸùæsùçLjÊð•ÕªÓRõIþC¢ž2µ?‚V?«†ÿðpÆdkí_ø7Zimâ/ ^i“ƒ´=í•çՌx>«ôá×ü9|UfŒ~Xx€oÚeð/‹­n@eüÐßý–E$ž–ã©ü]hZýƒmž'®@Ïê+!Òâ6 r‡'®áÏë_3‹ðÿ%Ä|Tü´ÿÒlzTx‡Oiÿ_;Ÿé!áOø*¿ìAr¾wÄ­#Æþ ¹Àwdžnõ“w9k›qÔ†Àšúïá—í­û üi¸ŽËáOƯê·R±Y¶© ¥ÓtC1GqÈë_æ῎?<m—…|O«iÐÂT¤V÷ÓÇÚr–aÅqí^ÇgûlüqšÓüi.›â›6À0kzeµà uù¶#óßæ¯™ÄøE•É~éÊ?;þŸ©éÒã J~úLÿTؼ%­ßZý¿GHõ?祔©rŸ÷ÔdŠÇ¸Ó®­ɼ…áI©ýq_ægðûööÑüMΓà³á›¹2\x+\Ô¼4O`â+yZ"àc–ÏN¿D>Áoþ<øBÎ-?AøßñL;‹üAk¦xšÄc¢?™ºeõÃ)?Þ¾gàÜÕþ¯^þ«ü®zxÂ?òòqýÛ(8t^ô£ä5ü³ü0ÿ‚ý|jÔ&Öüað›Æ› ³a᥂LÀÜE×l©Î8¯»¾ÿÁj®´«4Kþ°ü"ñ7üSG’3ÿ göyñv¹‘/ˆ5=&ž‡÷Mrø=q·w°¯™ücÿ™ÿ‚’x>|7ð7¢L“$ëq®LÀôd{eRñr¥{48½j‚óšü•ßàrÏ3§ö"ßËüÏéânnØ¥¬o+´qZ³x{^²¶û}ôFÆÉ2Ý2Ä€zåÈõ¯âCâoíÙÿø—s)ñ/ÅÝ[A·å|ÊÓK†5öhâ’_ÎSí^[ðö^øûXØÍãßÛÇÞ/Öü"³½¼0\ëwwj’¡"Oõò4i#jž1ŠúüŸÂÚx—ïb“ÿ oñv<¼g:)þïïåsû0ø»ûWþÉ_m#›ãGÅÏ xb9Ø¢¤ú¤-4…z…Ž6f8=x5ùýâŸø.wü¯Ã’M§øCÆ:çŽ5ˆAiáÍîRìÇ,²¤qœöÁ5ùwàÏØ—öðUðÏÂ>þbÀƒ«oÔØvQ) ~UõÞ“â_ x*ÈiÞдÝÀ—´i°`ü¹Q‘ƒøŠýáUM/lå'ëcæ+q–!éN)§ÿ¼ø•ã+/öWý“üq®Ý60þ+¹I€GƒóüòCcåÜù…r·'ü«â Á‡EðÇÂß„6ŽÂKÁ&©|„ýÐ3#®@ëòW=©|Nñ6¯3[¼Ò4x#sÈÝ=†x®:ÿ]Õ¡TbB¶1ÏrGqÜ×Öax%ÃÛÙá£=;žUn#ÆTÞlÃñ/‚ÿà¡ÿt¹t¿ÚöÀñÕÖ¶ž±‹FuÁä+ı’ ðÆExÞ¡ÿüýõ í/>0j~+ø‡whÛÌÞ&Öæ—tØ¢’î2?^·sªê/±šB›~\g×üitËCÅͧ†ôxÍΣ{ ŠÞ$Ëf9ç…P bpé^ý¿GJTÔ}_‘æÔÇV©¬¤Ù'Ãÿ¿²_…¯b‹á¿ÂM[¥h¶˜l ÝÄ®[lQª¹ÃÉ#¨£9c_«z÷ÃÝködðÃk_.l ñ,žv¡¤é¶é›áá*ƒ΀}¦ô©ååLý ÷ïÙös?³®iñK]±ˆøßWÏ…éCG¦À˲}jåOÈ&;D볦7»Á_ÛÇþ +ðkUñ7ü+Ïëmð®5ÆÛ¹\yšÎ¨\­Õô®Ì3å±;›¦Üu¨®‡+«=®v3øÇãjqj7SÊ ‹5gÚ@=rêE[ÔµyÛàï†È™_j:¤ò…ùˆ0DÜsœuÇjü½ð·í+£üSÔmí|#Ïs·Ú ¤/(VUé¹}3_Jèÿ5K #L𞻬˜#gv‹KºŒÉžvÑ* ž'«•ûžÛ¤øšÕuྠ’{¨™Ž 0IpxÁý+ôCÁ·×ů…¿³¾­ðwž*ñ¦½¬Ù[èJ³¥ÜVKûJFî ‚ùÛc*€n×áöoíÛñ«Z³øðOàö¹5Æ·'ÙaIü‹b²ÎNÅÞÒ’¡>óÈWj fç¯ÙÏÙÏö ø)ðƒÄ"ø­ñÓÇíâ/ü+ÑdÔ„ÿ£éñ…;Rg@$»u$¼ÇØ‘…ú›þ oû'ÙÿÁEiÈ>-|BÓ~üû2ÃbÈÉ£ª Y-,ÀÆÖ^{•ÏìS‘# ò3|ÒŽ_†ž&»´b¯ýyØ ñU8+ÝŸ³?ðGoÙ Ä?³gìõ{ûK|oŒÿÂÕøÅ W·bthçÓtC™mmY%$±žqÁ–3‚¾Âñ¿ˆR¹HÃA<Ž9ïôù×¢|Wø•{wcjUØæ"ÉÐ9=1Ðzt¯™5‹ñ0ý…L²)òÑÎù%!@üOüMÅ|CW5ÆTÄMüOEÙtGî¹V]-M-Oø;á‹Oø¾_kª[IðøIdS÷e¹ÎcóÛÛµôeÆ¡.¥{&©w—šs¹¹ã$çØt‡¡x}|àë© ,9¹½eê÷R ±>¡z¥Z’T“hão\ãÒ¼ì=5êtJ£œG¸Ï Bÿt{ÖÖ­§ÚÌðßLc;YÜñµUy$žÕÅø“ÆIiišWÏxÍ´>DV,OB{ë_3|NñòCƒ´‰›?ÍvùÊŽ‘ƒÛ=O~Ô*éË–;“©—ñ;ÇzŸÆf-3GIE²—e¥°<ÜL¼ \wÇðƒ÷z×ó±ÿ.ÿ‚§†3Þ~↦’kˆZÛ^ñ5¹ögW´±nWrœ¬³àí9Tù)é?ðUÛßSø)§\þÉ¿nÌ^,¾‰bñ6­lä>—m*†û ¼ý¦d9ž@A‰UùÛrÿ9º?Ã{h-šMUdq´¢åÙqœc ‡WôW‡œtá™f¼ž±‹éæÿCó¾$â ·†ÃÊË«ýº²×¼E{>£tÒ\Í+4²ÈK33¹Éf'%™%‰$žI&®ÇðçY•7$üÀËz×Ô°xm’‚ÊÍ-0 ä{ä÷¬s§´×e^A(wjñÀõâ¿lZ+#à¥+³å-gºî„<ÍB#ÿž‹ó'æ:Všû m5 mi$n§Ì†Aê+ÈøãIøá Ͷ©£]Ã{g(þà`éžG€ ÈÊ’:ò3즎gƒ©„®¯u£ìú3Ð˱“ÂÖX=ôhøEâµÕ.ü-s÷.WÏ€7Êeá—žì¼ýG5Ò|FMÚ3\°,mdó=1·ƒ_&ü-ø½ üPð‡…?h/ 0þÎñ¶¦#ã1™€ÄpHÌrg¨¯®übV^[&[tROrFyü«ø÷„–µJVqm}Çìt+*´ãQ=ÕÏ/&_øKá'ä0éÒ“÷|dzâý¡Ïœ6`)P3‘׊ç#V—ÄòÜ¥Eœ1óÁ‰aŸzÓmѺ¦îÊñÅpMššSÝ”—Ì€íTêOQŸZ®—’1.ç*>èé’{ÖsJÂV#i$}ìrG¦úÕ!‘Y#þ.x5ÑöSí8+æ•ažâ¹mGÄ·”ÅýoÊqF9Ï\×>É=íöíBo(à¢ã·ÿ[ë*þãIÒP¥ä¦û½ rOLZÖ{‰»òO¨ÜüÂ0Ä‹ÁÇÓØTw·ÖÚu¿Úï\"œ?ÄGlÏå\Ÿöž«¨ÌSKE´ÁlƒæaÜ‘œçùSmtè`_´^Éö«7–àž1ØcÚ»¡JÄ2äþ+Ôõ-Ñéq4HT…2 ¥~;…6ßO·•ÍíëùÒ…™9ËÈÏLUm2ć¿ ÞãÓ‘Z,óÆ‘éºlÃÊ ›ˆÉ-ÉçØqZ¥ar›±=½£Çj»we°0¹*lðÂ1*°Úï'©¬XMÄ1m›’}§5 ï(YI*:߇ҟ:ŽâåìY¿¸_~õŠåC8ågÀÖ÷†õ=: E5h±°Y/®Ù‡ÞÕZVú /ãšäõo4yÔÞA-¶ð»|øYc”°C_ ÁCþ(ÍðÃöø•«Ù•†ï[·´ðí´ŠpTê’¬s€G³‰ðÅk’`¿´3:#ÖI~&9…O«á'Q½‘ü§ëŸõ?޵üSñ3Ýx¯Å1ê’ùŒ]‡Û¯–@¤ž»• +ôCþ õà _öÜøƒñâÿ‡µÁ£ßßO¦¥¥³]µ½Äæ¼JAÊDªŽB“ø×å—Â=CJ‡â׆oõ´Ûk¯o+\ð¯”P¯Ï´`Wõ…§x7Bý?ak [ÅiãïZÝë!›®®œÜIaÔFYa@:¶I濱¨AB*+¡øÍz›™õ>!ÿ‚|mø£¦xÞÇöìø¯ÜØuí3O¼Ò&„Ç(m~Óe$…FöŽFw‚D-µ‹ª¦¿7à¤:ž/ý°uO‰Z.©s¬ø[⵿4i®äy[ì²Æùò¼©–HüµùcPª0 ìwZæ¡y⇿þ!£ÝZ|LƒWVî>dƒREH`©Y#F®Õã­rv½­~šÄMrfÔõ†~+Ô¼ ¶Û‰2Z_¢É *pHÛw³jÆN8â¶1>xøyá=ZïÁÐüNñõÕî«4RÜ¥+Ü2%¶åZÈ\ä¸À 3Ó¿´[ü}ø û3xáÇ„¾·‰×à †šÛ'Š)dH¢¾]Û$ÝIê3_Îÿ‡¾ Xh_~þÍZ{4’Ç©x}/óûÆ7 wwгÛMœŒ1l þµõ¿ˆ÷—&ºŽîå¯"¹‘î£l²Ž¡r¥qbdö:(œ.™û[|<Ðôø¯¾!iú‡ƒP¶ðÅs`ñFŒGί(qœóÐŽµ¿eªþÍ?4ç_ ¶‡qq ŸbGnëŽKsœðGºY5ˆµËuco ÙÊï]Ïœ³dðk˵Oƒ?5H™ü[áÛ;+…>ZM ºHÉ2€Oá\gG³]ÏD´øyw Eÿ‡|GâTU)äÛÚKñH¤ð©½KÇNkÚ[]¸h"KûNVvò¤lz¨àÞ¾¼ýš‡µñgÃjvP$dÅiq4—08¤x!ÓùW |_Ò4ô²¾6šÀBUC´‘θ9Ú[ϦhŠ>±²ñ½ëJ-æ²–%b2ÀsÐÿi^jón¹†)d ¾=ˆŒæ¾-¹ý§4_í[ ø¿N—MžkˆƒLóŠ=Ä€îüasëÆkÙôïøsÅ÷·z‘wÔv’˜Ì¶õõ%d{Š =/-4©¶¾Øòº“ÏAéZ ãK«8d½Ô]¦…DœqŒz“^%y§&©i®Þb¶n$(ëƒßô¬{O꺆 Ö÷l²XVxc$«LAüqŒÐ§ÕQø¼[éù°È¢l†ÁÁÿ 8Ç?¥rÞ<ÒÆšgö/5»}+T³+2^Kh.Z%ÂT•,Ô+Çæñ¦¿=ýœZ–›q¦IžG‘[fxªžOÖ¶ïnté×Äw0ºÉj ƒæy{‚ž¸;Usâ{„¼uªiqCãNÞ{ÔWîÁv[²gåbžOqœ{Ñÿ¾½ld[øeKw‚§8ëÇ`Ozò M¯5˜’]VºÓn™Y #içv °!‡lÖ*ø—ÅÚ¿cÕ¼cËÜ©yhA> IÆZi{}ô>1Ó¢ûdðm¸ è ÆsÓ§CX3êPNÏØû$ŠT屌çßÞ²ü9ñ'U´U¸7{”g/|¶ôHàk¹´øa¨ÉÕí>ЀîMà{ó)íÏ›,Z¬Æ[}ùrs+iô« è¡Çå;m¿É€z8æ´—ZðmÜ-$VaqO-XðŽyý+> G¾¹Ž6îKB¹vIy„õ8úPz†‰¡ßÇ5·úu´Nw,»eRËÔaí^û>|"‡YŸUÓ<>,&Ÿæ–k¥µm‡±Ûª×ÓáýKM¶[¸åc—, /Íü?¥s^^ªu)/®ÃùMVW^¤uŠùå>ø—E˜Üx/âµbÌpa¾ }mÇ8U`;uéXWÚ·í!£^µ±Ó4/DWpbN›&¸FóAÈìô|÷ò[4k#„D`‹^rýòxþµ}lô–ó…˜0ÀØ>pG^¼VƒNÇÊ2ühÔ´c_ÎnmB÷d–"síÀ>ÕÞøSãGÂê1ÃáÍMžuùVœBù\ä2É´äzc5ï–öqʤØê1ŽK.fzƒßÜq^aã?„~ ÿ¤ëö6·2É+¡ò¥“ÆX 㟯©¤ÕÁ»Së»ÁZœzà`ôà~b³®ü]¥YÙ$WÖéʪ••Ô¸GîÄ:~5ãQ~Îòé¿Ãk^ÛU¶‘þÛhXkL+ãÔW5¨x[öªð}Èš½ŶáÑöwµ¹ yûŒîÔ€y>•<¢>ˆÓG¥>{iáÓ^âlÐ;‰,çß<ŒRcNÆtº]î¡" .”Z`·Œ“v;ž85vÍlÒ {-LnÇ 9NÙõ©’8/H!‡ÏÀdƒƒžœf ŠÎu»{ZTäÆ<*1È~‡½Iw¹j û§å¸eUŒ–8A¸¨è ô'ñ®nûQ¹KøŒN¤os&V<òG¯Óò®’5¶·3nFG1‚FÅ\ó“ëS[èÖ÷M4:ŠZÜ[Lx*åÇ_áïŸz HÍÅ0[êO¨RH…¥+¸ìq]bëz\I7ÑùûðÊêAù¿Qèk‡»ð¾še6[Od€—F,d¶1‘ž ™éXZ'„oô;æÖï5D×0«¥ÀTg9\hìú&¤÷ì-m™eP¬Îcaò·lŸJ×’É–5‘¦Ì»ˆ8ãw±ï\BC¨¬ÊðÙ[ÊUAa Ú¥~˜÷ÍdÜk³éÑ}¡tËÕrZ'’7ó@ï†ÁÓµR`zUÍåüe’"…r\üÇqÛôø5c–;cš&]Á³¹·vv¯6±ñv™”sÚ-õ¥ÄQKqlЫîÚÎqÓŽµbY1 r±H×%ƒd(>Ãük’½¼žKEŽÑÑÙ¤óî_˜r ½*Z\Åbí3F#,<Œ–÷ôöª@vRÚµò7àx?ç¥{,Ḷ”A Fd!Cg#×Þ°µyuSígd‰Ê8*z÷#½m¶Ÿ¨[GÐ\îxÎåÈ_Þ(ä†úôõª§q1k‡VŽGË0@ü?* ¤sZ‡ö•¨†Çà yÜ­ÜUÃ¥ßÃj.“cÈ¡¸å ÇnzúÕ›ívûL»š {•†‘U@#±Çõ⣷ñ6™yfûtn‡ixÁêH&HÄšÎöèIoB€³J¼`㯥d=Õ¥ŒÐéþi"E,IÉÚG9üë¤×µy´ù^ûCq"Ÿ¼Ž§GO©ªz^½iq]\*Ì$‹"ŒßPŠ°ÙØ}¡ÏÛçÆ¬²pBõ'ÿ¯RM.âÍó•pb8ÊŒ÷?ÖºTþÏCËv¡VA·gP9ëÇb;V|ÙoK€Ä…p¨¹Áü;UØhÁž]6Ko4ª¢£€î‹ÜöéÆkŸ¹±Ós+Z_6æRYYwŽ1Jí¦‡ÎO²Í( Õ‡¿® fÞøZ+©3ª²Æ6¶@${P#ƒ—@†y!,ìÌp¦06üßÐU-kI*±Åo6eBÁX@Ç?ZïM¾·K ž#2 ®xÇ5Ÿm 循|ôI~ΠʱX³c’qŽ1ÓÖ«˜KûCÄVvpÆ™ĸl*÷-ßó¬Äñ=äéök¨7K¸¼Š¯ÎG|`õ«¼o ‘f¤IÆðÃwàg¶?:¶4Û¨ï Úd²2Œ¨=‰úôÅH÷öž—Jîñ*g„Æ ú“éX6W cp^ Ñ '+«¹ùïúóÍvÏá[‰á–2† ˜ŒàífÛÉÀ=cCáçÕk2è9*>m£×ê{Ð~±§[êãk—Q(6O~8«—ilìímU+ Ýoþ½g%²¤ßh…·ª(ÀÏ ž ž;c¯z·•ôr¼öž\΄³r¿7osé@Ԣŀ+æÄTà¯b:ÕkBÕ¥òf…a¹@8ëŸéL‚-jÚUÝjbì0ãñ¦,ð,7“ÈCy› ¹ÁnN^;óÒ€5ã‚ L“»pŒŒª6^(¸Ñ ¹€Ë+a#8*[æbz’}aX©kºÔ]c`U(­ŸˆïŽßKpÒYÚJ%?»“ü‘Üñ@wvq­«Ë¦5ã Ã9ÇaƒÚ©´‹F%Ü ø¼é¤.³.àŽyÇ|~Bžlt»9TàÊþàçÞ€: Ë#‰X18Çë\$ÿ~ÙÆ¦ë^‰ >Å/¿Ó~Ó×Úµ`ø¿ðïW)ýÖ¥œ6¶ò8?îð ~TìÜvÒ[•YÚà ÝIöÕëXRBêÒ«nž@íYøÓR¹³It ë—ò1Ú¨–¡6c»oaþ4ßí* þ Õ'w;‘ýòçPh—c£¼10’:áÇû½x­H§_69¦PL`–‘8Ó®áè:מAãKªZh~ Öíf¹˜Fe¸{c.1™_åPqœ‘[NŸJ‹­+Iоé·:˜P|½€¯Ö‹”—s«†ê;xo‰E<å»ç×Ö–+‹hàO³Çf*rsè:×9ŸaŒ»xvÁ܃Êj*ÈàŸ¨“~Ð:ÝËKa/‡4(6ažo:ü«tUÚ‚2ÙêpÃ¥+”vú¼×RÚC¬Ye`‹‰DÃ`dƒÏN˜­¸e·Ô-Þ]ލ"nfò‹oP>µÇi:ÆHCx£Ã¾Cy–šîï$—G'ñ­Åð÷Ä=BT–ûÅús£H7,Z#€éÜŒÏöI› Dÿb³d•—ågŒä=r)úts}±˜ asêOôõª׆.lô™ÿ±|^ñÝ&^—KAãÀ L‘ìMxž™áŸÚ¶âåãÔ~ xhÛ¾J‹ Ëî^ñÃÇf¦åFíŸA=¼mH” /8üqPB“Âí=¼¤<²p:ûkÉ®~þÐ@"ŸâU”b4Áè(>Vç“$­ƒé‚1\Ϋá?hÑñOÆkÛDLi6J§Û ¬zþ´]Ó>–·F—N^´*cbAl3søp:Ó„Ù‡[#H¤òòsëÇ^~•ðV¯¯^YyQ§Ä¿j 1b¦(mmÐí8ÇË?rñkZåטÒxßÅAOÛ–4$ô\$`íÇ¡ ,Ϻ¾'xKÄþ"ÓÅŽ›,™È\ó$€vúšøæëÁ~ ÒîÚÓT±ž)FƒSƒÎOæ—>ðïˆïbÖµ­O]Óoö2Ùë7‘‡·.ÓøŒÖ´¿¼~‹o¬k~!»d\—Ÿ^½¯a»Ì ú×=Iãc±–÷EÓÉ·¼•mÄ€¡ŽFN{…<ÖZÜCøàÖ¬ã¶y2]̪íÎ>n>•æ×Ÿ²¯ìÿ>ë‹ý"}AXnß6¥wq•ú´Ž{Ô¶ß²ïìÙc#ZZø K»ÜT°ºƒí*Ä}¥,zV\þf–=M5_ GûKZÓbp2Ï=Ê(ÇMÛ‰Ç=±]=¥¿Ãë8a¿ñ?‹tMLªSÍÔ"ù½0G½yΟû<~Î>gŸHð?‡4³°2›=.(]½Fà8ü°kÐt†ÿ 4ILZ7…t{`ãæo±DîùmÆs×ò§t=»LÓÿfí>Í·ãŸÎC¨A´ärï'ôÍuºwÇÙáú]'Ç~²—Oúzo~ç$ñÓ°ãó€Óü¤Ì¶VzN•¦nälâN}ðµÖh—ði—6:d6¡Ky˜òc*XŒŸáÎ>†Ÿ1…÷=oRý°ÿeˆ7—Çz}Ð81›1-Îð{ƒ7áȪûl~Îvq†Óõ{»åëû½&ù¥Ð~5sßµ @–¶P3-Ã6R#Ýí¹Gv¯DÓÿh+x Ôí¯,Û9f‚MÀ½œ~•¬f™ÍÜñ¸?mO€—7q[Ekã+‰¤RQ!ð–¬ëާç[}ƒ=þ`k±Ú_À{Ñì<㻓/ ønääö˜?W³iŸtmr#‡ˆÌFM¥¢‘äFLŽø8æºË+•ŸÍ‚+³q!_0‘/ÌÃð'¨§r¹Wcäû¿Úž;)Ÿû;áÄ%S·'D‘‰õÜ@‰¯?µý¦ÿh_[ kû5øæ[DIœÒé°ýìà°–íO\/jû¾ëTÕ#Ó{‰nc€)&4•ˆ'øFøv¯?¾Ôµ]H±ƒ16Ã…g%ßÛéV¹w±ƒÜùþëǵ£¤ÛÝxgàóZÝÎs%¦«â ;Y‘=Äflt×o§êŸ´mýœrÏà I¸*¡»ñ —,äÁhx¹&½ì·v·cY]ö »”ÝîOAüÅ`M,òFVâK5™‰H¸U¦¯^õ$”õ¸?h½.Þ ¸4ï^ÞKµgµ¶Õ/#’0Ý É%¹ ¾¸P}«™Moö°i’)|/àËKï¯\Ïœt ŸdCózñŠê¬®.­î#ÀÒ‹— +§2F:–l‘ëôë]2 +¨¢‚µeÈ/Õ@ÿ\@u­i¿¶>£f¦jÞÐo·æx嵸ÔÐÝá»îf#ýšó(| ÿ†âIåøµà§1ÃqàËÄTö ¤…¾§¯ ¯®£ŽÙ&[«ÕVMû82¨ççÚxÀöéYòßhXŽAus)lùq¸ÉëØz{÷õ§òê‡ÊÙåúGƒÿiõÓßÄ¿ü?qp#ýóØøiâGõ1‰®äÚ£n­øT¿ç’;Ýg➢¨à††ÇDÓ ‹-Ç bg>¼]Å®¨<â-ü­€dmÜŒza¹'×fóÄ =¬.ˆ‘™‰\g‚ìOj†ûâï±å| ’ÁšâëâwŒ55)ƒnÒZCžì¾\Aóøâº+_†V6ÆañwŠæGBË—Àƒ·Yc>¹5èÚ^­”¢7´.ÓÇåwŽx#¹ý+Z [Q´ðå½Åò#­»´P#Œ0qÜó‚¾µ&–9á·ÃǾŽÚþ]RölÄ·s²úîùJŒwÈ­ Ÿ…? RÎåuK ¯ð¡Z_·Ý¬¤ú’®=3ØW]¦k> †Á M²2$x8n£?Ö™ee`¦}ÇíR©ÊK†RýÏN}êXì6²ý™¿g»W¹ƒÃ÷ÛlŒ÷:ëD›x<4åNsƒŽM:oÙ³önH^SàûþU2`Ç'œ~ô’^I¯KškÍVÕaÕ]PI"«e•bäñÇè:Ó Ö–;._)mìúØSí‘ëJìVG'ì·û1H¦ðx.Ò%vòÝ LèøäNáþ÷Æë³÷ìÏð¢æO‰px[MÓ†œÿjû_­cÁG•|ÂÃåV<Û¥}=-þ·>š‰c F'“‡ nÀÏZæK”®_åpzõÇÔ¢¬c}Oc·ño†­e‰ô¯:Æâ¤FˆXoêA'¨VÅÇÆ=r'ymnçd œ2ç9ïŠä<%m¢k“Üêž"ñ¦Šc;OÉ#3rJìF=ù5Û/†~[”ü@2™$ T@6g€6÷=‰¨±\Å'ãF•YuyÜ–o•‚0:‚GøW´Ü\x¹tx5k-D%´¹m±à^ ƒ‚Aç¥|É­è>›ÄsEg!6ðÌvHé·rñƒ·ðâ½wÂa4Xê7éhû˜, ÇqþéÇA¸v¥bîŒféuy…½Ö£}À€£ õ'ü+"ßáž~“5ýä— pæZʡᑠçëim¥)¾Õíâ•¡\)?¼êr3Ž:zÖ¦­s-Äa¬¡o7å ùÚ?½ÀïÆiÆJ.æucÌÁø('üãÂ^ø~ß´Â-Qô­7G›‰-îá71YÚI÷.­Â3do´J¶ª6ဤœ~øâûâ_ƒ„° 9¤±¶2_²d$EX¨VBÌÄ‘‚qèx¯ê_WÑ|;¯Ëwg­éëw£ÞYµ¥å¤Š ­åR²FÁ¸eqÆáÍ(µ>ü ý£4_†ô¯øÆvWWÖ¾'Ônµ¥¾Ñõ¨ÁŠKHm"r|¯*"‰#ƒ`&K×Ð`q^ÑYž.&‡+º=#O𜚽»ë‘ßÀ–jí(™#¸O.Gjõø> øÚçH´»‹Ýù¾c-°·(豜¿8Ã{ŠæáÒ, ³ñF’’}¯w›+*'6Aa€;uÆ+¶1ø›R½ÄZ•ÎÆmÒ;jñÈùc=s]ç:¯ x#ÇÚŒú —Ù6WÌ¿*|’£P·$㎠wÍû'ØÜÏ5§ˆ¼í\XøþâkÈêÂ?ÞGÔ÷È5_´_øM–ûl¦=§WwSßëí_mþÏ·Ú$¿þÛjHn?·l|Cáð¡ÔË}¥ÜH¡ùÆÖž~nÞj-¤xÚúñù3JD‘ãRxcø{úKÁ#ð&‘ðÓǰ™#_ øûÃÎó*fH ¸Ôb·›ðò.]\öLçŠü; Sêù½)ÿxûü]?k‚’]àâ?ìñãŸéK­2¥ÍÍŒ·uý´ î·›MÚ¶IûÁš"F Å|ïýõ™1Á#ÄAåsŽ~•úûiØøóàoüWãÀíD½•”^:×­žÞBöi •pHËE$mîgð.¿½­ýÖªÿ¯´™£*Éž‡¨aƒÈçœ×ôd]ÒhüÉœ…‡Œ5ý ƒ§\<9ý˲O\í ûŠôK?>"’3­*ÜÆÃ&… »N}ù®]'K³¼l}~aý Q“C¼…C[²H­Ó#Nàz’_|7ñ²joØä~|ËI‰çýÉ0àMRŸáÿ„î¾mUem¹òîã10õù€+ô¯%šÞálÊGÔqD7—6¬eÇ÷[¿á@ÿ 5¡Í¤ÖÓ£}ÖI29õ&¹]SÂÚþ”].à%c8%y_Ïÿ­U-|C¨Û7š¬¥Ï9*3Ÿ¨Áýk§³øâ qåË'˜„çk*¸ÿÇÁ tð>ìo^†´-õ­Nß„œãÐAüë¾Oi·_.©anàõ*†3ù‚GéR¼_u“Öå¹>[†ÐЊu}A#úãô­8ü]ªP„ÈG®þ•Ô'‚|!s5®¤ccÓÍù?¥e]|8¼ ¿Ož;‘þËOë@Z„µ>.¡†Øít'þù$SFàûÉDV·=—ü3Šçî|!â;B|ËV¹ŠÁšÊòÓ|lƒÜZí¥ðu›‘öKÀ¿õ×ÔUgðv¨±m·h§ç“ëŠäáIº†*~•eu‹øÈýñ;xù¾cúÐ÷Ð|Gjr¶Ó wQŸäMU‚ûTÒnc¹¶f¶ž&Ü’'îäSìˆýjêx§Uˆçp`?äkR/^©Ûx¡×ÓƒÌP4Ú=_ÁµÇí+ðöæ9<-ãnÙ#é^I4Xÿ®sÿ¯k¹ÿ‚…|iñ¹´ø§øoÆ‘³dwD¶q–„BN; |­aâÏI>uÛ&ž21ò"«©ö9ÁúUÆ»øbã}ŸÛ2N ÌŠ±í>êXñPéÇ{Fr¾çöÁÿ[øYû þÒÿ²kx§Oøu Åâí3Qº]X TyQä}ê˜ò‚hþê ~ÊÛ~ÉŸ-# aàý:½0ùcŸa€kø§ÿ‚#~Ø:OìÃûdi>‹RWðïÄ]*ò}‘Å|„µ¬¿63»-’ê: ÿ@!åK­9Ã[ܨš&^†7üñâMI+­Yúo b![ Ëexè|–eÿ‚PüƒÃ6žª7`~£…`^þÊoœÚ¯…íÌò¡²ÄðRNN?¥}‹wlŒqµWŒãëí_›_ðPÚ¯Æß4{oÙÇöhº‚?Œþ1µ3Gw2y±x_F”2>©:žòò–QžKfR ¡äxwŠÍ1q¡Mz¾‰w=LÇG IÎ#à/Ú[À?¾"üF¿øIðâí˜"¿©r,®Žt×MûŸæXé×¨ÛØø>/ƒ.îEýÅŒ­&B<ú•´#‘Øy»¿Jí4¿Ù+ã®®´Ë:D›twN9Çüò…²õ¯©5ÿø(OÆëµVµøƒ£h’õc£è\gÛhš+¶ýxï\f£û|þÑúÚ¼WüY¨.Øí×L^3€¦ÆÉü}kÜæKsÌÕô9]3þ ÏûNø‚E‹ì†8Ï;­ômRì@[ÊEýMz¦Ÿÿ“øû{\jÓ^Cæ šjZêA»œsõ¾|ñ_ÇoˆÞ7n2º,’ë>!ѬÙX€]¡Bò0Bƒ–#ÍzGÃß~ÇŸü¬~Ò¿laÕþ|ÓçÒ­|I¾ÝµÏˆZää4¨²’Š"º¸Q¾U"h±ÆõüÑøËã¿ÁO‡Ú¬QÝØx{@8PáÚÊ)¼µlíŒF¸®rÄW)âOø(GÂðÀð§†õû {5ÕäÔÄs4·Œò´~XòíàýÚÉ”Kó¿(ëPëÃ{š}^v²GôÙñ³öÉý€þ+x^çNý¡hÏ«øþú¥¿Ã¯ jŨ[L‚ê [‡‰bLÂÌ®’7ÌÃa<|[íqÿ$ø;§/¾|9ø‹â™¬"ES†ôý¤Úv‚ójËlÄ€99íÓ5üüê¿?kïŠ6kÃÄÚœÎÏ:G„µ Œ# ®$x‹¹R¥t^ýŽ¿àªŸ¤/¡ü-ø‹<ò(ŸI³ÓTŸvºò¤QîÍŸS\Us==gQ/V—æuÒËêÉiþ'ïî“ÿdýü7¢O¡|8ý˜üG|ã2ÀuÏYAo$„q殞óáxáÐòÿÁcü ¸°ømû6|.ðüè7Gs©ßê ýѵ ²BAä3ÛŒæ¿7|ÿ[ÿ‚¿øÖí·à# A¹„m¯x¶Þ {˜­%˜ vãp¯ ¼1ÿá~Ü^,–[‹~4ø}áÝ9”íDžó\p[®WeºÀ±5æb8¯+£ñâaÿ'ùpÉqÑSq‘âø)íÇûV|BÑ~ ëú¿‚>h×qÝÛêRxJM"ðéóGþ‘ÚMÝ̱†Ú¨] ºï;]O"¿üGöÏø‡ñ öað7ì¹¡ßØØèZn¤ú­ÓËû^¥„²G1¹V³³‰ÉŒctÈúgá÷üàë L¿~;Lör¯gᥘÛÇÊ%º’lƒÎAL{WÝ¿ ¿à‚ßðN‡)>1“Æ¢ÊXíõ­\YØ”‹ckm6;T‘3Õd ‘Á$q_;ñ/$¡ßóz'úÛó;¨ðÎ.M{–õþ™ü¼þÍŸ³ÅOÛ—Æ«û0þÊvÓ}–F7¾2ñ…ôella$Ó6:þªØ~òlsícÁ^ø}û|ð÷ìÓð_I:.hÑÛNóGç]\˃=íÀÆZyŸ.Ì~•ìZN“ðƒö{øi€þh:_‚¼+`Å Ót{d´¶óX’HŽ1™%bI,Ù9$“ɯüWâkßêÒë呤ϗ7AÐ}}kðN>ñ®p¾¯A8Ò_{ó¢?Aáþ§„~ÒZÈÆYýŸkÇsNd8$÷à“Ï\⻄cBÔüg/‰u;ˆáµÑgE”íÍ̙Ӗé^Ovë¦]Ä®ær@ôéÞ½µ­ ð§‚4ߦþôj^ÉŽTÍ÷S8Ïʸ×åTZRægÖNü¶Gµ]ø“B‡ÌÕn¯¡”W I>ÙÍyž±âýWQ½ƒˆ`zŽ1êF{ûWŸ•ˆ92܃9*2G§Ö±®¡Y%@È#jˆú€?׋mrÄÊÒ-k:§ö6&¯#5,G9/)ä{`}ï¿2¿mOÚÝ?dO×<ÓäŠçÆþ!–K/ ÚÜ™®Æ ׎½L6ªÁÛ±¢d+íÏI‰¼U›‚ÖÖÌ0šBÙH®e”“À $û޵üp~Û´¾£û\~ÑÚ×¼-3®‰k+á‰Ûm£[«6 ItÙøÉÜ ýÑ_©øSÂ?Ú8Ï­âWîéëêú#æ8«6ú®ÙSø¤|§,~ Õf»›M•õ=júæK‹›Ë§Þò\ÌÅÞFfêììI>§5è.©á"ÊÓ[ºÝw2ªO"‚í$ädªó9'åFIà õ/Ù«à7Æ?Ú_âÆû;~κ3kþ#Ôä‘åo&ÖÖÖ ï®ç?%½´@2VÏ%UC»*ŸÕ¯ |Eý‰?aO޾ý™g»‹_‹?5RßOÖþ'êÆÚ?‡n'ÌoeáëiS09ˆÝ>ã½Â–lÇõœb’²? ”œÙð¿ƒàœ_¶¯Ä]?ˆž"Ѵ߆šÒ mµÝÿf´ñC4V¸iøp2$`‚1Ç5Àkß±‡‚´Û¹,µÚwÀ wÃÅÇj·¦K î¿à°VýÉð7Ž5këÝYµÕ¡½šþw¸su‘˜÷™ ;¼²ØôŠüFS»‰¶¢ÆsƒÉ?K¼_û~Õÿ ôã.ñë½þ× Î/ ƇïÔ,«ø¯œ4¿èž&žH-CØêk’cmbÃ\t#‚:^cðËã~x‚/|;Õ.¼;©Fsö‹ jØè.Qdz)Õú¯„oÛCÃeáŽvhÓZMk‹kC, íŠýÿË@r¿q¦çWÄŸ ‹k§ñ„B8ep³Æ£ˆä=Çû-Àךò ½³_VèŸiñUµï…üSlöwÐ³Ô • r£®T9F£¬ƒ Ý`E|Á-”ú}̶wCd°1GŒ§“5Žèþ·ÿà‘þ'Ÿ]ý—µ†Wh?âœÕúÁ8±Ö¡<ô[Ÿ7oοlm5©xrÑ®2­flg!JäþUüãÁ¼Cçj¦ÑÊ©Õü%$XòÍ£ÝáH÷ħð¯ß­2bt³Ü©‘Pyh7e@9ç9ë_ʼ}CÙçß}GøŸ­äm,%5ärL"½¿¼”1æÝ0¼Œì㊸Òy‘–ÿyߎ9ªñˆ–çS‘YŠ‹›hÚ¹EžõN·’ýÄ–±–BÞFBר×çÒ–»žåǶ¹UF DFY™«özMÑ`ˆaaòÊÀœµun•wq+’À·8ãŽsÆ*-GÄIöXà²HÏUü1ÿרM²YbÛNÒt˜樬Øa±”çž:T7ž#²Xwi–Làý}«šyîo£¿ÄqX÷š•¦˜Í%áf.Ù€Iü;V±‹6ežkëŸ6w ËÀŒžß¥Q»ÕltÐd¹Cà¨UäþUËË}¯ë$-º­…ªžOÞ‘ü¾•fÛMÒíÇÛ¤µfw9-)ÞAþU×’Å›QÕu@aµì±Hïd °_aÚŸ¦Ù|ò.$>t¤’[Ô ÐóÔ©’u ànÀ#Ðc½Ri.!G8(Ï—ÜÝO¶;VÊ$ÙŒ‘`|4eŒà·^~•¹ƒÅ÷–ФÖóíÄ„ oÞ(AÔÛñ9©ôßÅ$S\ið4‘M¼ Ÿ›œ¤ç#Æ+åk"ÓO¹š2úKD™‹4,äÃÏpW§ãLÐô/YÆÌ5 'Œ±1l^ä)Æ~•ÏÊm3ëñM–‹kiþ`xIØŽùnqÇpk¤Ý_)¾ÓìᘲŒ’F#©ùxÈíšøêËÅšæŸ|ëÞdqgíO }à9#Ž„é¥ñ½ÒÃhöPŸ³¹Üì¹½ºd ôâ“B¾§Ôrjº'ˆ´×]BÔ\ÀʱO”Èê9ûÕÈ7ƒ>ØßEu¤h²hó) `ZÙw)àíR³î+Ǽ?ã5­2Ú}QîR9]õPÇ=pØïé]-§‹ÖÒí´½-âÕ‹.ùIq“×jžbh³.'·Oçê·Ùšv«st“䬎˜Œç•lcå#¹«ºö¹â; ½öÂÛÉAäHñ1IWÎI'Ó¨¯+¶×m£Cgh¶Ö;ðìÑ©<ú‘ýo$°²#ßà\4]*`³ã•äw¢Ì£Ó4ŸZÇ ‚à`¬ ÊŒ6Ö©ñªAö[5ŒÎ6à®3õàæ¼¦êõ¬-Í£[Ã}rÞp|ÿ´µFúÞ—¦Y=¼K’UÑ ‡þsÓÒ3×%„¬‰º6ËaR2>ïsÏ^´å¼y/•®_z<‹Çp:äàõõ®þæïE·}J7°¹P‘‡Ý·œžØ«¶ž%Iomín-䀩ÀEôsôÍ3²=|H¶ú›)x˜1 Ñ6Ò@ì3Jäµ­gÆIㄊKKøFXyíî —jNq›}¸÷Õ_;OÄ;'šÞDË9W݆';Nà0G×¥jøgÄ—@t«]5eWi ¢)#…< u8è)ÜVF¶ŸâÃnV}>Û~ÝáÏMн ŒžüWMÿ V®7%— œÅ¸d°8ù ÂÒ¼C¬GöŦ–¦(æX›b…-ê 7¦zœÖ|·77‘>¯~^=òºpœ ÇNÔ]ƒGªËv$Ó¤‹Êi¶#Jz„:×g¤x–öîõt©.‘­í£9/ÉcêIýkÂô¹l-XAªÞº”ÊÜ`Ÿ”œqÍtµ´´’}1U‘I;ø”“ŒgëÖ©2lÏmÓ¯-õ…—‘[\™›aÇÜÞ@úñ×­:m'CÔ'Q"l‘Ë`Âs­<—cW²{D󉌌¸!}Ažµ¥hÊn>ßîXò¸ôúö µ§Cäx¿hX<'+ZüJÐuŸ<,"žkˆÌä¼d–\÷zW†þ;|/ñ|sámnÚøÛa•Ö7ô 7'ޏé^¿$\]¡¾h¡Â˜Ì¤+ò0F5ÁøŸáÂ1][D²¿ Xîb`t$óƒ?>´ÖX"µï˜ÒE %Lcx#¨ªÜÞ-Ž[qýª“AýÒSŽzŽ£ùWΗ_²×ƒ4Ð.|­êÞ™Uà¸3F=w!Àö"¦–Ãö™ð”M›-ŒìÓ?µ0µ¹`>ðì»±Ðt>´Ï}Ó|]à(/ÍúÞH\ü¾YV!s×~uÓiú§†'œ ¹ d?Ë‘¹Xô}E|¯sñûÅo¾"øUÑ¡ŽIó‘S•9Ç=ñí^ƒàÿŠß¼ix¶þÖ­ÒòQ¹¬ç"9J¨ê3×ð4÷›cáï˜Ú0f¡Ç þ#ŸztË+{|Y[y ÎÓØçæÉè=9®~ÝË0…–?âelÇSϽh=®£<°}Œ`[-±ÆänƒœZ,€Œi¦ xã¸@JòààŸ`qÛÞ¡Óí¬í‡ØmXªFÅÕOÌ÷sÿUi«Ç}ln£1K︆#‘£²¼—e€‰œ|›TBžœQaó3"úÙ$¹{Ëù Û±* ±WÜ}qž®*½¦Ÿg¦Û<%Q",9ÃÀ˜ã5è0øed&9Pc‡’:óüª¿ µó ƒ*H7l$.qê1œý(°ù™ÈGª3ê%ìÙTB¤©ÝüDcœvô9Õu!?—oÝw ¸ñŸLzÖ›é¯me›{o#æ#Nãœwô¡âŒDexÌj¤aŒmôëRÑ¢gžø‡I³×î¡XPÜL(Æ}ˆõÅw—§O6Y\ÛnTÁÊî `>§ùÓñ2\ý™ñ´î<Ÿ§øÕ;‚àH’ȳ6ÜȽ½0iX«¢œ±iñ8kXÂÛ*ìë…‡Ö™o¤i¶ðÅu9·{’V/²ÊIäd‚ø5Ó¼–fÍím®èŽ1È烟\úöÅp:·†Z{«F²?a_?s4/óm ÷Žxç¥V€%Ö¡«[@Ñ_Xi$l’ 0ì3ÝÖ¯¯¼;}}Œm/¬g?2‰`e ·o^ t÷:@žÝ (ލËʶÙ7vbAfy.­5?°Ë¼?——ÏÌ^z÷Ï|Rb²í‘NTGzÈ_øó€ƒùV®¥Ï,ìÐÊ¥Ÿ%Xu ŠæÓOÓ5˹®54s3àÃ=…W]M’%[-ëf  •;—¨<SaØÖ½¸Ö-¤k9vâe tã‚;æŸbb–Iaf2ă„õ>ÕÌØ\[.5e¶É1ÊØbOGåɭ븴)´¹5IZKQ„™¸ ìIëžÔ ìIý©•GšGQ‚ Ÿ¯ZßMjÓ1ê›b‘x$€è6ŽþõåóØx–ÆîÚËF‘MÏš?x±ê? ·—{v±+k7Ûdbc.ÖÇáøPdÏR>!Óe•a¾‘YPïÉãõÅWþÊðÕÚ‡··(è„;º»Éàc¹8®p­æf-Æ$TF $\•ôÜ}3Ö¼Ê׾+³Õ£Uò݈)#$ðFN=è$ögÑÝmEߌ…PAÇòVÃÃÚÕ›¬ÕY˜m@B€¸ìH¹ë=sÄ=Ì×7ƈHTG‹p﹇j`ñ=ž°¾MƦ%–?šC÷˜µT@™ÿá%ÒäÝ­Úî¶“çOo\ŠM;ÅZE¼¬g.‡$ ìqúq]­¾£{ –MÑ*äq‚?à_Ö«Yêz}܉y‰€ªsÆO^Ozl «{Ÿ[Ô¹Wi¹ œ›‚0yScЬ,&i£•üæRQ˜îÏô§ -E ¹¼%(ÙÏûCÔJtz ]]Hš{œICƒþÏ#ÿ¯P‡eƧ®ä.[å8ééÐçð¥¼Ò-VåÌ 3|Çažs‘מÕR.þÎé/_Urü÷sô«w—ó“öe°An3¸õçÛœPYô—¾”GvÙ³Œc¡'Žžõ^ÓFÒ¢ÕüÙnIš=ªb'g<@ïšØ†h†Ô2àÁf®}ê­‚AoqçÍ3":“·ß•sqhšmÌX†æO= )Vɾ¿Oz¬l`yRÅç’Î8×R}ò;zWckö£ÄK2“’ Ž ü©#"HÚ¢NAU,2n½¨ΓÃh’Fm…q1û•¼=úÔØ÷rÅ*[E³`,Ø7)úÓ3[¯šcc´†Âí§l¹·w6fK ¡‹?.wëÞ€<­,ð«r<ì&üû=qÐzg½^:-ÌÒµý‚ì*›f8äõâ»Ø¡þÆÕK4¿*~V÷ïÇ·J}É/?“ŒtÜ«÷sê=}èÉdðýò;ZÏ©E,™,ÍŠ×= Š]Q‹wŒ>àFÌgvcéìkѯ´]Te¾¼…›nQÆæ `sïÞ©éz“‘\é×À° H\tì @yâ™.œ-Œ ` 8#ÐvÅ+oIV l`0@çò®ÎóIÖ-'T²·P«ÎýøÜ|zÿZq´½ûOœÖĹ)8ý1@3ÿÔõv¼ø &ö-1ñ÷™‡>£©ªëmñzY·Ôô˜íÈ ¢Zɽ}>wlÁk­³[—gibe©9ïž¼ôü©ÉkPtp&X¹l}>µñ窑Ê?…>%N0Þ)Dç…KD }2I'ü">*R"½ñ-Ù~AòaŒÿÀÁúq]!Šh­W÷“,,w.ãÉ÷8íŽÕvÝ&™T´áî ?r}ø |§?­Ë¢ê:õûï )E'ÜñŽ=ª™øYàß?35ÜÌ~ÒÈy«ƒ^£-‘ŽD·$É—þ3ƒýx­L•UQ¾é8É tëÚ¤yT?~Ç;A„’g‹]Lãñ;‡?Ö™¨|ø)+ù7Þ‚ã$9Íp Ǧ$½‚ CÍ8ys´à‚0åPHçæy›Ìþé=‡§zMØg‡Çû7þÏ^ ¬¾èÏ,€îšt’Sî’îsšít¯ƒ¿4˜ž-šUŸ˜›bƒjãºíÉv JÊ!fp«óþµ"µê–û71ÚƒäûÒ¸þ‘à‡|hѼ#¢Y²ÿÂ䎧r®Mo"YGùVvörÀ1©ô§Ê’ywŽLŒØ?9ä÷b Öcå#|§žX’;u£˜®Rëê×:z¢AueãË*ªA>˜§O¬%©9È,#!½O®G£›£C ±2’0ÕXç¤VªØhqZ,?fŠUleY7îeõ=¨æÈ Rk‰Lv/2îûÁ#¶:VÊèZé0ùÓZÆ ÈË d<õ#õ®ºQlKCeŽEŒ*8Úz}E§j:»J¶~{D[‚åA$zϘÒ1"¶Óõ«(›‡1|âɸù~½ªÉ±†"ÿÚ£\ªÃpϿ׾jKÈ'?Û¯DRw#ÿÔ1Vm¡²7"$Så:€ÊAÏÖ“eòÛieå·Ú¡óUI ¡Üç òØÖö‡JÆâOà $Ó1Ç^ÕbKÈ4è#Ž82°väg×®:{VtÍæ¢Ê.FÂX¸ü§=džä´]>vžx³Œ€¡oζ"ºžþš*d‡;í ‡Ž09>µBMNÊãO—ûbà[¾<´Y,G¯òÏë?|= [­íÂÈæãåÛ´µßu ¹»öGt¸˜2}íÉœvç¥p~!ø“áÿ )Ó÷ý¤)$¢à€Çœnêyôâ¾tñGÆ›íZî{?3ì6Ž¸Úƒ~ãÎ3^&šë]ÝÒùÒ0fF<Þƒ4žÅAj{—Œ~6kZ¶d—t0ðžJ’UÁé¸s“^¨\Ýß]«—hÁlmc’¸íýEr–zί&·½Â ‰>u$ò°Ç~{ô®©à3L—7n^o3¢özûVi7%†ÅÚ–" ãÁg®{ä×Ec ÂmeV5$³/jf™ Ã,pÕU˜sÓx'ºÈBE“b˜$?*à†ç¸¬åRÄòêhi°(²ŽæÙ%$‚F¯^-€ÓÓj}¦æR@´‚{ƒÎO¹¹š(6ÓîŸ+œ(Î1î XÒúhx‹™!ÉG·aÎ;f¹\îoza¦ÅZilË–fEùwú:z(o€Û!P™ÁÚçâj½ä2één»b“»†ÜO c¿®jYuø-eBÈ[`]»#Þ‘F²_I²É2‚©´åIŽA#Þ³´íC^²77¾\RA¸Éªä\ãÚ¥‡[[{´†ÐFÍ ßŒr;žúÝkJêáuVxRu†âM®<Ï•HÔŒg…Z`V–ñädeeGc´;.C)üê{Ko-ídm¡O…ù³ô÷5fY¾Á{L%\ùƒ©n˜ŸZ¼–7¶«oªKŽÑ•·–`rA_QLM oqäµÞ•~bêÉŽœzã°£û~) *nA$l)€ nëÅ2=TC–Ð[ùžiùü墑ŒðNk3RMËÍ,þê×à1Ï5i[P{mÖÚægl"kò¶õõ=k«Ó|Ks§YE&Ÿ+ÆÀa„S0;³Øõ÷Å` 4ÙØ›gXdˆby'Ìo¯'ò«_n³°ˆE¦¼m,¿8W?ž=*ùˆ=ÇÃ_5}BQµu<¡AM³6HÁŒ÷¯bilžÁdšC;³(+ÁéZø§Ã¶zŽ»¯G¨ÚÁµU¶2¡à ô9ëê{×ÚsÔPÛËå_ÝÄÅwa€ëƒZ)¸˜íök›.ñJ.Ù…[§ÍÛ5x–ÚXRÓÉY¶‡‘ö„žü÷=k5¼?1sö¦ýÖ®á07F@ÉÏ­o[éPµÜpH XA(Ë’çëëNèV.Zkú†Ÿm¶ vFÛ! Hž…s‚pkY»Õõ6K­P‰H‡€\)ÀŸ^õRÏF{ˆš÷P°’0 B€‘°„sÔÕÛO ÛÍnë-£¬Qñ–,:œúžFisᩳªé¯”Ú®Ÿæ[OncŽ_´.Æ%úí=ÈèÀcŽ•Tˆí¦¶+n²^L|©$¨ÃåSϸÆOà‹Í9 úÍw3;ª"0!œ’7G÷®÷Qðüææ'•dh`-'8Ç|cš›”‘SÊ{Ù¥‘!û:çj|®áG¡çõÒq ¼“Z;@€¬‘‚C2‘ÃdóÏQQX躂¼:œw³$ FNAÁà©Ïê+ª‹BÒ/­îm^;{œ)…d8IG;€ôÛÞ•ÐÌ1k¡]¹£ÀTï&W'wÈÇOJ·lÚd‹ö­/÷É÷B±ÜN>ñưn,.¥So Èd4’ þÁ_>xÖ á&µiáïø¯Y¶ŽßLŠúÁfÙ2ÆÒ ¥™.XX™C~„µÁ1)X÷`‘辕§âYøÇþuäøsÃF¿xökÙÔ“o2µ¯ˆŸÏ¼$\É -—ÎøØv"µ.#{é´ÅRŽŠÈ¹ÁR=~¢½<Õ¦â×c“ýÖ¼Ît=½¤þm²ˆÂ®÷ 'Õ{ Ûe`I+òpHÁ5” ÙNw=~ÛL’;8£¸œ“ €®OËÐÿ€¯æþ Åû,_øgã¤^=Ó|ÅðïÄ9ãÕ´ýM&óeMZبÔmÌNCÆd„#@Ä:†0ªßÓt¾%ûm²J‘K9;wa{g?‡é_ž·'싪þÖ6~ñOÃE¢ø¯Â"îÚÎ×Qrt™£º’)&3lE™|µ1ȹ• ƒ]ø ¾ÎzìrâáÍ Ä/†Ÿ´G€þ$ëS|6Ó/mü5â+k‰-"Ñn¦Fº‘!É ˆå >±\L×ÑzO‹|Cu{uá½R÷uå¼±€¾ZG˜Žgn9ÉéÅ|9ûP|Vý¤¾ü'ñ/ì{¨_éÐø‡\‹]Ôõyü<°ks^ZÈ~Ũ«FÒZ&8Á0—Ú —çŽÛöqø£ã_|13xÓJízs­•Õà…ây!–Bwàq—è{WÑFI«£ÅpksïM_B¼µðãË}o:Ï4gº¨bâÃáÅxÆã?}³Ê6wðˆdhÀQµØôü‰¯fмKá_h¶W:}ÄÖ·PœL³¦UU…N0NGƒ^gãxKÆ~ O xƒ]¸±/ Um›Ì¸'øUÕpŽ[°÷¦C:;¯/ˆl¬ü+áæ,Жo!òeVèÊõÚ9ÆÇ5ÀêUÍ¿‰ïtà uÓ<ë,Ë)aׯ$dt®³ÃÚ†µôO\+Q±¸f„«™€Ê¹ެ¼üÝzU½$xx麾¡¨ÄÉÜO ˆù²Mu,¹V‘–ÉûÙìhÊD¶ÒíaeškÛï1'¸v¸X¡ œ¨E^ÄðsÍzn‘w2Å¥‹`ÏæiDË £ôè+‰ñ6‘®Â·º/‚ÛO³½fY —k¸D£”ä½xȯ|IáŒú™}©/ˆ¤„Za™|Ý€™zªª/¦3œÔU¦¥™­rÍ3ôC^ñë‘ø¯MËEv™3ó(Àê=¹Ý^•=•×ÿgÿˆ?¬îe‚{ûyd‚X[@ÓBÈ‚% Êz‚|?¡øÏĆ‘|e;‹{ ;Ï•¯WpƤcÇ}òÝ }gðgV Á¨iú•Ÿ„õÈ †YÝ!mÛ¤SÙÏ ƒœ×î¹F)WÁÒªº¤~qŠ¥ìëJ™çËwØFýÙÇ^Õ]ätUò\?êì¶œg=MdÍÇ–S´WyÎ_P¸,¿wÓ¨üq§]\F{¨À¬ nJŒ Øç&›æÀ‘3žõH GÒôÙx¶rO×5Uô™Ôâ'CŸSþ5YäŒãËüñŠ‘V^Î? #–Êæ?•ГíȪK ‰IB=ElÇ=ä?2·¸«cY˜€'Ǻƒý`¥ÅÌyò¤`¡ÅZYÔ`pÊù>¤kBIì.2 j¾ã5ÓìÜäI×ß4«kã}jÕ‡ÎØôV*?.EkÃñáX5Â,‰Ü:?Ò¹GÒådQîÜΫ¶‘pß4{\z©È B_è*Måº1'îíÚqúÔRCàHy‘‰ lò×ùW›½¥Ä_~6_|qPqчҀ=HøSÃwuë.OYçô¨í¾_k‘éú,é<óѦylsÞ¼Ñ$Ÿ<;¡Å[[ýJ,ÃpàÄw&‚§Ô¢€=çàß‹­°Œ±´„nØœ{zÖÇ€+jñòd†ôÝ>6ϼ¢@?ÝjúÆóö†ø–‰‚ÃM·Y_fJ<}É$w¥þ(Ü>ÆžÆ×s™`È8ôÜk–§ˆyÔÓN·à¿ÈÖÕ¹ø÷ðÎ#º)æ½bsˆárãŠÀ½øÿá¦C.¦ÞLxáÊħÿB?N+å *ûV½¼Õô^ånWHxB\Ö3'œ›Îà;¯L9$z„"eÜžTx óz§ë^|ñ“¾¬Þ8jg絓†µÒ`€gιÿÇ@ɬŸ‰_î`o2æ “îˆ`À÷f?ýzó»y¡Ü£8ÉÔò{S5›ü*ZO’8õ>µ?Xovm0ìO©ß]^Éý¥¨3]LÕ31b=vƒ^!ñâ/ƒþi6ú÷‹¥šCy)ŽÖÚÖ/6i¶ýæ# ½ÉúW¡ë—2ÝÎÖvÁ‰XŠàs×¹5ù×ûTxž=[âÍî“iáËhì—oBÀy²0ô9l¥o…¢«T÷–ˆôòü¯>]’>×ø#ã_ üw“íú-µí•­¦ mnÒùU$ n‹<‡‘µøâ½ÿY¿þÔÖ®µ{€ÝÍ’ð(û¨>ƒŒW~ÍZÇÃïÙþÎKØÕooâYŽîY¥ÔÍoûæ=‹ô_zí¥½ò—ìùÄ—cß>ÞõÏTáVJšÒç-h(ÍÅt4.^’ä —*‹Œ–n¸®+ÄZÄš6“6¡h` OFþ,z(É­/e‘ʃŸ^GjóÝj 5ÝnÏÃж<·Øã†“˜ú„JŒ5^¬a«0›QNRØüÿ‚®~Ñ·ÿf~øvf_üTól³;âÐâ+öÇ)Á¸v[n͆vSò×óËð›á?ÄŠ>-ðçÁ„úDÚïŠüY}›cck̳Ý]6AÁÚˆ2ÎämŽ5g?*šõ¯Û›ö‰Ñ¿i¿Û;Å>?Òîš}Duд$PF§ï$`mšS,À礃½}õðÄö¿ðJïØþšH¾>þк-ö•ðõ¾ï ø@²­Î´AVæù‚%®@a  oðfE ¯+¥A/y«¿Wþ[‡gÙ„±X©Nútô?R>ø+à7ì·¤ê?°¯ìóªÙëͧj–ºoÅÿØ’òkþ#*IÒmœršnšË‚D’—Ý—þ7µ“àoíK#Z1€øCÄìëå2øãEÊ¿R¿àÿÅþ%|?†m²Ïo§x‚ÊÙ .Ÿ3Å1Nù1̛۩ÀÉÍ|Yÿ)ðÌ^ý¶¼w=Œ k±x—ññ´2êñÈΡ‘¤Ï|ƒøýUÏý'ÿ‚¿A ÇÁ :þÑÁŠ×ƈÀî·¶O*‘ìBb¿5bÙÍB¶v¶ÿÿ` +â%úF÷PøgU“0ŠY#kWÁ㜤ÿ=Øç“¸[Ãqs:[Ú!’W!QTd³¹'_¬¿²Ïìƒaªø®ÇÅ~%’øGáÓ£:§hY0Þanù[*‹“ŒŒøì‡ð&þþú‰~!µc½–-2óM)+æí~ÿ.áÔÀ úf©Ðývÿ‚5ëqOñÂvC$‰õ1óИDê ŸIš}ÄÚÃk· 0i‰ù_}1ë_ÌüȈ¾&øZí‡Å7 ôÉÓ[‡­MñÛËŽÒcÈŠY?qUI?AÅ4ø›Õ®ëõ?QáÉßô"ð´êöÉq.ã’IJã‚ÌÇŒ‘Ϧêîÿµã´m‘ÄbQÏ—ÆÞ;íú× jC¡iö–óVÒËÎ2FOO­Gw®évkµ·ÜÜî;€ä ÿ/Æ¿(p»> Û¼Ôg¾ýÕºùç$‚Ëéþ½cßjÖVea…„Ópv Ü÷=µbˆ¯nbH¯$ÅýÀØ8>þ•<1ÚØ¿— ƒ«ʶïS[Ó‚D6Fï¬jžVû$ÄœÒ[éö6Yln9ùyÉ>¹ÏçV'ºeuóŽñÔ23Óÿ­J%p¤íßÈ$úv®„Љƒ2±½úwÿ=ªä˜<¼êpN3è=}êUVó0áëž8ö©¼45¯kÇAøu§\ø‡QVÃÅ`Ö"Gü¶”•Š0?Ú`}ªù öx  (\§Ê¹'+žàŽi–‚ïV×—Ãþµ¹ÖufXÙ!žàg¡`>TûÒ¦¾Èðìe®êñ®¡ñ·Z6ËßÙZ,…p{‰®ð“ýØ‚êkìïx?Á_ ôŸøF>ivÚU¨<Çj»YóÝßï;å‰&¼üF>0^ÒZŸøö@ñωcY¾*êi Ùžºn–EÅÓ©þ®0Q G÷«ïü.øð‹EþÇð.—ŠIÝ4§³;¶]Ï»§ñ â7Ãÿ‚Lž,ø£â ? Û2‚釛'ýrˆeÜú`s_“ଖVñÜ鿳֌Çï­kß.1üIn9'Ð1ãÒ£ …Çã§hAØç«ˆ„Û?bõJ_J¢’´V‡•)9;¶kØ>ÀWò¯Ð/‡ö:ÿí û7éÿ<5~‹ªxQ’âK)Û 6Ÿv¬#¸–·rbÛ÷BžJ×çl3~µéŸ~ xÃá‰íükà›É,¯­ƒ*É*ã ¬¬ º0ᕆÔ!¢O¯|9ðk₼mázî{[¥Óµ½:à-¾Kˆâž2Ç=\d÷¯¸õ]Âþ?ý¦¼cé^‰á©|C«HÚ‚XÂð\>öue…­Ï|¨<ŠùCÁöúÆ™r—Q’ŸÎVä·|ž™>þ•îžÓ¬5¼¿´¹”,‰²@£ ?(úŸnÕÍbÏvÑoí4¸¶ÖZAi)YwïÏ\éOŠlÛO‡O³Šy.ÆdÎ }=‡QÖ¼ÃEšÛÌ¿–LÃþ©§!EÎ9ã?Ýé]üVkâ8n ³Š;[›•If+‡_î‘É#~µF‡ è?fµ×aÖîí¼ûÿ1¢>á  ê E¬jïyøÕŽÀ¬HB ·¿^µÄYÿÂW¤G ³\ÌÛDì_<‘Ž0?ýzët–ñ Ñ»Ö×rNêÒøùÇ÷TgŸSÐÔ1£¼»ÖõÝ2Õçº.brymäðÜð=êΞ÷%µ_°$–Ñ©’u·”6Ö=Oœõãk#ZÖ¥F:ÜJòÄåwÄÊX¨`uê1I{púí¤×Ù[Z$Ø€IùI–ᕈÁéɤRŽª_i±´ áË?´$ò᯷ýðz‘ü륛YÓlå‚ÚH ÿºVŸåNì7u úñ^}qa©BntßiÑG¸"F$‚ʸ$6N7søWgu­ˆÛOÕlDf<²)9ó3è{Ù ¯h»PE¯éÖ·þjhá–B! o&6’‹=yôÅméshz„ƒO^–@dÀ)ŽŸxŽõà â¿ éBÒ{Iÿ²® OÝ^’›öòÁ‰á±Øƒ]2xË@¿ºûDnà´mÓä #D“Ÿ½œžœzT¸‹œ÷iÖ6íÝ7¶’_-±¹»îí‘Úµ.WÃ:¦›m¨ê3ËoånA€£9þñë^Yk«hšÍåÜV޶°8ócó]P€£–žGzÕmR >Ynïãþ؆]Œ§2³)QóŒy8êzŽF¢i–gH¼X½"‰cXÔ1ãaáIäŒs]• &£4ºŒz奺·”-À2fl€77±ë\Ôñë^²Õ¼*D–ï!Š{køIó##ø”œpx z×s¨h¡n®ô=.R34QÈárПA8=*’²)#Û,4»ëQm”ù·œ»3îÎ]ÆHäŠêþèž×Öÿh?[FÖÑéš—•§ønÄ9#l:d D̹ =ÁšPË’+š– ˆ“•Iµäu¹P¥4}âÁ=¼{ã¨>ÚϨjþ¢7Â4Xoç¶‘_'›˜“Éž{×’xã@ø!û8j a¢7Šu_;t©n’IpÑn)ùóÆG ­vÿí#¬xªâÒ /ì$²F† [fP¤¨)#¸à°5_èÐǬO —|9WÛ¸çÔžNÈ⾯/ÁT¢¯)\ñ±á'¢8ëZÙ‹ÄžÒüH"–@ÆRÛPè¯9ú×}«|Y×­£Ñ¼)©y‘6»F„`’¤íÁ=0s\×ü,=_S¸EЯÙ_8|BQƒzŒ‚ÀÝSè^$ñ=ÍíÍž±,wܰrÎ m¸ð'?ášö=îXÓ¾4k–jd·ð6 /ä%|©'HA#‚ÅÔQjèm¼iâË_* MìÉ+“›ÛôR22@r@ézVÔZî•cp¶²È³ÜÈ “¶&;O'£s“ß¹ý+ÀZW‡u)olµ‹›ç`cd†E;z|ôªö€š:ë‰ÿ§e¶{KvÚ¿;N§÷@5¤×?5&)u¬é¬V¿"ÏœÕK]#ÌÛY_Þï? #‘Àç>¾µÊÏ£kqxœÉ Ñ›6BÏi.1†'€ióƒ:Yü¯^"È<_©[É"gŽ¿/'†Vü+|;ñÜwQj‘ø¶úú×?êdŽ0Ì?»”U>ÀV•åÔý¢æ7Tyv)ùÆ3Oð­û?]ÙY¼1ɲ üÜwÎOãéO˜™M–¿£¼‹5´Ï•Ú¢eóðÍ~3™[›¢ù»®Oÿ^»«oO-Üöïß·~AÊãŸZÌ>"ÑïíÞÞHáTwpzƒ×íô£˜ƒ™·Õ¤ä|Ið7À^*¸oí« !Á”@U÷â 0Aôæ½OûjÖ5Þ.ØCG”2½29ïuE_>‘Ø`{ãµ4ÀùŸÄ?³&µ}=µÿƒüsªéQX[­¼PÛZ£¤h„•ä±ÝŒà’3€3]uŸ‡hŸhCàÿ[ëÓÆÇö½ªÂò³¾!ÓÓ5ìÒ©Ó®ÿµ-nY2ʸ9 1ý}+wû^ÚI`šÝã–8˜«6Ò\ûåL¤×T|Þ¿þ0øBÐÂÄøgu"3îyt;•¼Ï,DxÞª}X+¬ðÏí#ð^¿6׺ÃxQŠ2ïg¬ÆmfVÎe\c©c§Zô µˆµ=@­«–#óüäqøã¥P×´_kÖfßS„]ÆÄJñΪãÌîrGçUn¢mt=?@ñf“iÚF·k~±bEkgYWcñü$çŸ^kuu‰¬Ù (áÔ• 4L¨Û¹ûÝÏå_ÜþÏ uÙîtÍ.-îåyu¦ƒg+ŒôfŒ€IúUM7àçÅ .χþ=ÖRJA¨¸¼€ qÄ1ÀèXiÜGÞ_Ûši„M>ÆÚ ¸È$÷iŸhÓn§û]³®ö9à `cî+á[Ÿ~Õ¾Ž4—FÓüUo»–´œA#Ô|ãúqQYþ×^´Ô¿áø‘áýkÊۇÛ-\@Ê:²ÎÃÛ'Š–ÙÏ ¶ÎùÒU@ÛÛ?(ÏAP·‡ ”žn–ƒ`´­Á sŒ×šø[Çžñ曩áûkûi"hfVäv#® ên!×-LRArщt ¤ô#?*C»,¥êJ#t&1’¥îŸcL¸Ñ/­®T.f|nÉ`UW°úûT–š÷‰4éÃy‚æÊT.Öõ;zŸz½mªØÝ™çRX¨Ü€ª¥&c°‘ÆÒ–Þ1mÃþ,ôüª«ß=”±JBNŽ<¯™É%˜ñŒsÅhǤiúºì'pü€ŽãiöÁ<|S-4 °dºµwš6!deBˆcŽjyYD7Ïkµnd‘+0lŽyúVmõÚK4“ÙDPHUUÐÜAù†;VºésÛHìáà‘ʯÀçøŽ?¥$žt™qLÂúä{Ò°ü¶^E:]Ŭ²Ì¢BãÎKwǧJŽÛB´¼éSD&†M¤‡%†;c¥\’yåòÛ·3Géßò¨nô­ò`÷»JÇrír9¢ƒ)2m_Pˆ|è¶Rü¥#;¼®€+¸4¾Ô[O€Üi6æG ã…©ô5Ÿÿ­”8’+‚G;ƒrH=±ßñéP&ìÇ#G'›V8Æ Ï`?™ ‚Ü·Wš±’äĦ)2 )á‰ëÇQY±Û^Ú;ÿfÇø` ¹‡j·¦Z^Á<³<–¶ÊC·—ì9éZóÚEò\LL,ÌPp ìO¹íë@º/ˆõ«RO^Ú˜d„ù{»6O3Zkm§Ë;Á«Ùþô8 ]~lž‡#çYóé÷ÑjGP7"K°TÆÛ~Pªçž+MìuFb׷ȰcåŸ,NNz{ÓNÀWŸM{I¼¯µL“¨!wqüòÅU³Òu›¤V‘б!É8ÇN+Få¬a½¹·¼v—bnYÜ9Õ@Ed Ü ¨J†9?/åïC`Fóji’®Ÿï$R©Š6â3»‚f ¼B—1¸1BÇåb_œ àsíV#Õ&»Óþѧ]·–„‹ýï@;â¨_Ú_}² NÞèMä‚ÒDãh×=)§ö­i«Ê-Ì/íò‹0:Ž{õî¡-ÃJ.TD™ÌQ»¿¦½YÒ'¾žìéÂ!å]à™ÆàÃø†yÆ8¢+y쮤2é \Œ¯7Ôôq£HSû<–ž0Œ1ÏãÍ]mGRŒ`G+7ÊŠËÏ=‰v{msP¿[¹!µŒ¸ã`#jö$q×óª²[4º§Ú5¹"VPᰪÿ¦hÀ}V F¶†Çís˜¬lŸýÜðp=ù¤¸»¹¶ÓÜ#@’r1´_q\6ãQ¸/.'‚!žC]Ï“q¤=Ç–×.¬3a–QÐŽsŸcøPíu9£Š[½*î9®Ê®Ã5í¤W¸3FíÊrž£Ô×áØ¬™//oí³3HSËä8ôãŸq]>Ÿw½Û»$‘Z0*êÃx G\ûûPƒÉ<6hµEš0@UÆs޼vªw÷¶ö’@nP¢Ì@$õ­MJ{9´È೸ò•›,b‘Ž™ìk<êR˜­¤ÅÃÄ7(òq×'Ö“WÅö£kt²›ÍfÚŠ¬8ž?•E‘¾ÒKËdT—$`œ(äãÜûSlõ™ïaw×,#¶i$m©n{OzÈ»ÔímÃm·uÌžZ>rvc¿¯áPÀ×´Óšä<ד•y(«É ½1íê?¡öKÆ”´÷ò@m þœÕÈF’Œ]®Dˆ©‘¸î>›yüéÐ-Ôª)Á›¯iXÿÕú`Ç9¯;U~AÛõÏÒ¤¶·Y bùP>o—èGZ­lus²:íЀ€»õ?Ê¥ˆìùC§ïAn™ÿøó×±}l¡oŽLnÆG§Až¸ì)ŒlnÈ:yGaÈÀaëšµ³¼Ép’lXÛ•ç#œTŒB–ŽÜ²¿ŒnÇ|v b4dJ‚5 ƒœ°ÆÐ;÷È¥1çÒ‹€À°ç#'ùÔ—VRYÆnîUY]p²_o¹Î9íQZªÌªðűp0>¾õ-°°ØÐy¥3Œ©ž•,04ÙŠNWûý}jhUÿ¼OÍO‚Kv›lLK±#cžàzÔÜvdK§–¶Sfv" ¥O'¡æ®§†®•åi„„Bp¤ oÏFí[PÃ,çÊØÎÄF™Ér;þ>¤sSh·×“ÂxvGóoÜØ*@Çž õæ•Í –•yÈ(­ ‘’>÷^ùãÖ¯XÙ…Ô..'Q(+ü·§ÐU–• ¶EnwBž˜óZÒKlÖÑ£ ]”¢`iës'›$À0Þ@Î ;zþ~Hb`•*1·ƒŒöô§­ü C% ¯\‚{v+>¨`ˆ[¼>|ˆw|»ÅàqÔzgÖìg‚Þr‰Ö`$ž}©¾Ù0¹Šâ=’)Wf àôÀéøÓÞ+¿?uÓ…çhù÷c±íš‚Ôið]˜ãgØÁU˜ ç±ÿ¥"†}OXv¶Œ+Ä0I^]}Hõ­›Uµ‚ÚXnÃFå‰Tïo$ôª-ª&Ÿª!SË‹ªqÎ çük›“Ä›[›‚­¤g>c¶3ÐÏýi6Ïx­ü3b²¢¬’:·,rÃÓŠøWÆ/¼ÖµSÃ1‹ïüžOá[_¼i¨k÷n4é y’m?{`íÿׯ2†åá·UGmò ƒo$cŒûVnLÙBè’Ú×MMFdºß0Œç*~S¿Ðñ†ž•râÆßÏ+§ƒ)ÀçîãÔô϶kKI´Ø ¾V”JþbùÃäãµ_²H5(‚)•qÎìl㎹ÇК—&R…ŠÐùRL«r»v>Òê2؇qõ®®Y%¸x.(„Tcv@üë>ÓD¹Yb Uv$4ŽrG¦NzVý®‰y¸Žî@³/Ì~l®öéYIèh,²_‰ÄTjS’ƒ,ÀޏK¹š]Ÿms,Ž™1FrUóÆ}ˆëTî-nÚía±/;€„`}xízF…4$š JC‚61È`1ÎqúW4›¹J+©jÎõ§¸šx/ˆÜÅL=̪§Èà{b¯gÖnâS%ľQH¼¬ D…éîMcXi—wñ™mŸËHƒ7Ì»Y°ú~u^âi-÷^=Áxå2Eò†øNÑØðx¥ÚK`¶Öï-uYaxZEYlð«·¯`ã¿jì'×­çµ°@O̹Œ £¹ÿëVPÓ¯ãkk{r©#mq´žQû{ãžk·DæŒË&• ¼#Üc#ëŽj„s—:ž¡g47daá vª2]Äçš½lÏ‹í3˜ÖB H}€ö¿TRè6æò˜iàѨûªzp?ýf´¬´÷—6Ö¥D øÎ[ÐsÒš{‰,¯-¶ÛÀXA+bGl7ËÓÓúU¤:(Óe›M¿fœ€í Œ àdàú½„-§¼“É—ÉÎÑß 3Á;@Î+\èÚeÄbq Ïò÷ª’ÏÁ©äÕ‰·÷–^Â"ߎWIø‘õªh™ñ,nd# ü+ÇP[s[’ÚÚ9£vËœUܤ¶z)à}j :x…͆“ —wv‡`@cõ?*à*¨»Û3`žâ9šÕG‘´sØ×L¾mcQKm6¼Ëò¬c''œ±zމðOÆþR\ø²âÞ΀‘#‰¥e?Ÿa@ÇÔæ½{KÓt¯ 8±ð³D THéûÇ9îzŸ â›¸Šžøoiá›vµº_´Þ¸ØØHÏ\œpHú×{—–ѬªK¡ûe©ö²\ßßCë¢ÎyýØØ »éïZÌ‘I$[º°=@§wb[(¤Wú”òZYÉò*a°(\@õ¦£élo¼¦”Æa‚|žLd´òÒO1Qe-–$äãùT·cHH#–ÖäK0êA.ŠGN1Þ¦¦ìW)GyÄ/¾×Ü€ãÿú©Æ[ØïÒÎçs[N„,Œr¨ËŽ úñšÝ´[K‹™<ë™önM‹»ÓŽÂ²X@<Ç–U–h¾@°à…=psõÉI»É%ìu†¼š"‹÷qFW ¶;î'ùüi³I.¡«™â|¤ack ôÁëŽÕÔ%¬CþÞ’–âYp™nŠ$ãÓµMo©øFý`µ%Ù<ÉæS—•±À#û¹â†À§mky©[ìp$‰>TbqÈëÇùÍUþòûj¬ò0¹pé’}1ìJÑ‚öÒö täkdÜ®UOSÓÛµ^mrêÔÏz¥‹‘B“°©ô§Ò‹›eÉÒl§'y,ÖʇG«öÇLb·4;tɦ’cäa÷n{¡­®-¬lãm:WLÀÊÅ08çwgÒ«sI‚àI%·“u)d!HqÈúI€Ë[k­j÷m¤ Î¥Øï, úU†ðí¤h!½2Gu>áû¸ÈE~€±ë~*KÝNîKmå 1ñ–Æ2G9ÅI³âimb´žic2åUc´ÝÏ57atáõÄ×qÅq~e”ç*íÀ 88>”A§Ï¡ê c,x›o÷¾éç§>œ×E}©i¿ÙŸb2É&¨ø vàF®[ÔçU´æÕfÔMÚ[`•q’HÇLÿZ.À·áåS§·Ÿs唓f1œ0R=9wëTn´}1åIžW2KÙ$C´s†É#ï ôë]Ìš¾Ž“|bµ‘9‘ÀËõ®TÉ ô©¨ÈÉ2ܶ‘“Ã*ž\ú“ת¢ÝÉ–ÇÍðMøM¡…C·`𨑜ƼžïâÇÄ넶Ѽ o`YžûV‰ì‘°Èôé]މñâ5Üñ‡ÐtŸß¯Þ‹PyŠ…=X—Œv¯£Ò>´ÓµK},kVaàá‘ÃwÜ‚ZùëÇ1ý’ín]¥ ²yŽygFP é÷½3^«añ?â$Z|¥•ŒÍ©öÉ·!ýzW!âB}^g¯Ø>‹©Ý9%ÞT–ÚGo»ûÁ‚;u®›°šDÚŽãGBµŒ\\ڻɀ‰—¨Æ>lø×œøÛV¹ŽäÞj‘/Ú÷gËfå‰É#¹Ïä+Õ~3éàŠºo‹¥]âW):# ‡ä;‡zŽù®+⌶úï‹Ä–»`F$e`$1ï‘ÔûW¡¡Ç5c§Ðž+Ã3Ƥ¤ew|dyë^‹m`mš]Òv³ ÷|׊xm¦d]:ÄZ WË\9ì3Û¦k× T¶û<´s<À»ˆpë’1õÁÈ¥"bOwc`‰ÓOå†lG½‹Ç®OjÒˆÓôo'Nd‚-àÉpÃnõÎxo®~µ‹c¤kͧɣKE`Řog=z uÍaºãe•¯?r$Ú…—#.¬{ö^¼T”z6™¬kioR$HbÉÝÔ¾F#®Aé]5ö®ob··±y˜¡ß$¸8$qÆ+ɬ|?ãï~Á¼6ÛÆ­ '#×Óü+Ñt¿x¦s;HîYH+'ÝÆÐxÉãšvÐtMwP¸ŠkM.ã.$ÆXïBßqFyw Õ{?jöéX^B&GRË2æ&ÝÁ 3ÔÖÖ“¨Ë¬Ý™$!tc€FæÆI®²Ále”ÚË+A>Ñ"ùL•þöz[2¬š³> ÿ‚šþÎz‡Æÿ‡ÄM,5Ç‹~,Ú®“k>oö…«/úM€d̪d ‰UN?…©ð³Æÿ³¿í{mðÏFñƒãm^KK)õoì[ç½²CvýŽiWnû‹tPd…ÞœÄëbîÒÚ+K‹V’êðid¶·@×.#;ÌqÊ<É1µw×µ ßµ-ÓüeÕ~-x;ÃZÏ£â[››»}&ÚÔYÎûÁô@›dóðÄŸk]8¨õ<Ìe&µ[£Ú‹ñ^ÓY»š8ᵎÚt‚©$çg$œ ๯FÐükã‰,ÂÉtŽ Œ†}ƒ£.Ë´Ÿ˜äf¾>ý›þ&ë~[Iy©Í­[,°ê+>¢H»·–Pà9¯¥4Ïí lå¶u6ú…¤»<ÇÁ·‘Æã¼“Î@àã½zGžÑë·:~”MÆ•©\Eì’F²H‹Èœ…^9cшëžk˜·ðÖ‹¬[jþ&Þ´ùîf0)Á_¹ìNsŸjOxöòÒÞÞÆh#ººòÓì…r¹ÉêAÉÊã±Ï¥mk~*‹VÔn|?m٣܋.é1& Î1מ0AA_‰~ÝÜjž*´¹Hî' ’¡BÂîTÚzçåÈÅqVöËs¥]êºäÒéD‡_“søv>Õëþ ×N¡x]-§º´IncK¹6³¡ eý#¥WÔ´ë‹8Þ¶7Wz~ø¶&üxñwˆg‰#‹Âº7ï#Ú¦K¶d(§9o)Áôæ¿€[/øE4_‰.ðlj`û%µÔzž›hR-ßf¸3²ÂŽŠÚ}À"¼:˜:8Ø×ÃUW_–ú£Õ¥ˆBpgõ ÿ+øs©ü ø©á{OµA§Øx’ÝKlËáÛ‘ö©±‘+YJ»qÔ)çÓù×ü]uâá'ˆÐMw1½Ê·Ï!cü_^õý´xGÇø÷ð‹ágƶ^%Ó,­uäݼ,m¥êIœcË‚ÝÎÒqž+ùøñðHø9ãüÔ ø;\»Ó™Ý†.-íܬl pwG±øüÜâ¼þ Ä?«ÔÁÔø©É¯•ÇFõ•U´‘ò©ýÙ^ޔǸ”ŒJ£> Ó^pm"â}sTüÏ(üœ~5öÖGŒi$°cïdŠL’™P g´åΠƒùÒ«[ñË)üè°Ýýîq׊&a†".GɃçÞžÏ:ó"tô  <¹Ðf6&¡ÌÖŒŒuÏ4«:–ã5`HÄÉ#¥R*‹óm4»#âÁ8’F÷âS¡Z…SËùõÿ×S¥íÚ¿‘î*@ÇçRˆb#p“¿¥[V»øA>ƦþÚ¸š0Ãý¥ UE”›w««\ÓÖìüª¤ý(A/ô™\ ‹dî ©ý8­²x"t¯%¶sÔ4eÇèkhgO¼¬*€rzûÐ|žð´ª]vެc*AúÏ×¥k[øE¼}Ž»nàuáøf¼ÅBœr*A îÜã…z•ÏÃDZ³y£½K— FG˜Ä€Ç’Hý¡~Áÿ,¿gÙsÃÞ<½GVHïn¤ÆÆÀñœ³cœu¯å×þ Õû>µ‡4í”ØØ8Õ.œŽX-è ßðþÍî$Q9k<ù0mŽ5'þY¯Ëšü;Æ ïÙѧ€¦õz¿Ðûî Á^r¯%¶ˆÖ[«†j±sÀùýûý*âÝJÏúƒ\÷å·šX9ô ÇçZQMæq?z¿›åK]ÒïÐÚW‚ËȆE'ËïìƺëéÐÉ´K,n¿Ã·8¯ ’@8õ;s\Ó—3LÙ+Ï‚üTö5ƽ7†¹pg•Xr°  ÿºnÇfc]…×5)”O÷2x+Ð~ã/¯i“NRÚ+˜öŒþÍ&UT`6ñ€­‹;ýbù6éÚF§)-œ„é“´ έIZÈiÓê·—q³™ØÉÔðw…XÓÍŒs‹M™£Dó_  uþ•É¥·‹÷ý‰sžH¢çÐåóúU{Ý+W¹·ò¼E49>dÌ'šeç*JŒ(=ù'+‘7Ô«W„.Qü8uЭƧ,—33¸y‡å˜ ÕªúB‰1·€'}+™72 %U Š ´ñ‚…Q‡-+1—·}Øëþ5•Næ‰#Ðà½ò,~Ì…LC…*@%‰ÉçÚ±.5¦œJ¯¶nñž×?ÈvÑ‘ÀÆ9÷«‰¶Còí øÆÓ†¹>ž•*NÂvFî‰q2µÕԣʇtóI'Ê8Fò?Jüy¶ûOÅ[i³†kêá\¯-²æMïR"¿H~5ø…|3ð;ÅŒ4—é§ÀXœ«^7–Oà¹?…|Wû"hOâŒö÷‰Ì: ”·~bœíšOôxϱù˜ñèkèò˜òЕWý[þ îå’TèΫ?X$|j»>ðu„—æÅ^weO’bIi$ûª88,FqÅd¥d‘ø¬ÝÛgÕßðMÞø{ö³ð¦Š.L6~"ûn‹t˜Iõ»ìSžŸ¿HŸ‚9QÛ ú¿üËC>-x/Æì×…m‡7Ÿe4ÑIŸ|÷¯‡e·Õ~ üJ¶»½¶ònt)ìo’2Ã-öy¹È$vÖ¿W¿à± j>ðŒôÐ’[¥æ±leSÐÞyvÅHà©_0䟴‰1¬5kÿÁ$®åÕÝsccö$íÄÖz 6ê=ʰ㹯˿‚? -æf=K–È=I¯ÊÏŒ·wŽ|g$– û6Õ¸K‡œŽØê«øúWê\9á…J¼µ*GNìùÌȩӓÔý`øÝûPéú6¡qãOŠºü—×÷$‘s}+Os/;Ü€(_Jü¯ø¿ûwxÃÅ3M§ø?°ÛWíSÓ0=Õ~êçñ5ðn¯¬ëýüš®·s-Ý̧/,Î]Ûñ$š§1|có¯Ú2ŽÁ`R|¼ÒGÆâóšÕî¶E½[YÕ¼A~ú–µs%Íħæ’V.Çñ=½ºVy\:f´­T4‡ŽEvv’ÆâI­Äª¤ey8 ü½«ê”U‘ã´ÞçžÛÜSR$ü $÷ã¥{^—áí6éAžÑ¡n€8bsøšô/ÀÖD+yp lç=yÿõÐê ågÉ9~SÁÅ[N¿— .Äò0¤äW蟃¼¦Cw$qÊ­$€Œ0‰ë€qƒ‘_Wü;ø'áývÚ+Ám<"PdB6à¿x“Ô~˜¬ž&(|¬üW¶ðwŠ®SͶӮ]xåc'¯ùükÞ¾þË¿ü}"ÝiÖbÖÛ3JIÂÿº¹9‡ûÁá/ƒþ¶‚"Î&FÉdÊá~QœH÷8ï:ô«&ÓáXÐ bPñ“Ð}Nj~°˜ùOÏ?€ÿ±‡¾ÝC®kÏ%æ®À2TýÖäõÇB+ô#ÀÞ ¶{‰4ËDÙqîÚ¼±^ç…vÖ>¸º¾G‰¼øö¾#/ŒO¨Í]Õà†Ål x¡À_´Âß¿w_R éXó6õˆ¬|3 ÙÝýŸTžæEBT¥ºä‚FJôù}Áæ½RîÛÃzÑéÚ:Þ[6§*¬…ÆèÃ…ù:p èjŽ€þ2µ³7Ñ ºÓâo.I¦)•ˆûÄ[ÐäWWg«ÞKÝÔæ¨ÕÄ‘”l|É÷˜äg:Óî§iÚã;Kw·•^ØK‚7 „r3Ù±ÜV§“u£èí£Þº%ݘhОcÊŸ½¸gõ«zÇâ4û-œE~Î7Ç7Bç© Ç5Ñ¿,N¢t­?Q6ó_[ý®h.ºmÏEaÎ3ÔsYšók×6òE¾Ú®ªàÚ ‘ry‰À>¾¢¶ÛûGHÒ%j…B ¥Î|Æ'8 oZOƒÃþ¾—E°ÌòæC6ݨÎÜm\Ÿº?ýUê ¥6¡¢Koå,vÅ|æv_”ãüã‘׎)XOý¾}OPwŠ…euÅ)€zã§\ÖÝÿ‰¬šòKÜßéJÊåp’“ÎO™QI†'³ðˆñ.½,Z‚Zæ­ØnV›9|uÏðûó]Þq¥ÜÉ.Ÿ"B‘ˆÕÚ)ÈF0qÇ>žØ¢ÈcÔ´ gV·‰ÌC÷q’¤FPz‘È à«¥ßø©æþטÂÑZÆÑ U Iab lãëZ§t fÉ®þ̱`§q“øÇn;v5KHøwãçkï k"X“!ôëä^y—¨Ç# f‹ ;íCNµŽMKBÖ¬£¼¶ŠHnW ’ˆáo˜6Ò Îx‚³[Hð5ÅÒYi×ÿÙó;¼¤„!enïÓÖ¹k j×D³¾{»uy/å[Uœ100Ç!OQŽÝySH‚ÛÅ3¶—i¨A¸Y ?3ƒ÷{J—+ÜÈÖ4;é|_jR[I ºâ"ÙWå$öÏ|sšèljüMàËòÚ41ˉPËs"l„SÆWœ9àu§ßiYêoc«B—c̆\ÁBüà`ã#¾;×}¤üø©ñéÖÓè×PhsÈwÏ4{âWŒà|û1I=a_NŒ\ª;éJnÑFd~(sáåh®Ù'½Qðþ­_µ¿ÄÏø[úí¨žÝ<;gw$=¤)JªCÏ0k¼§k€ª [ÆŸ´ïô«? Gw‡í4rÖðZiqÇoû¦ê`TËòÅ‚ç< ^a{¡¿Øî5 nâD¸Ç–,9ãßë_YÈ)RI¸êyŒÆSøO´u÷w‡Oƒ¼3 ðdžâe†+ X–+taœí Ç#¿~õÂÛjº®¥«^ÞøÃq[!¸iܼã¶Y[ž{ׄ[øžÓA»:'ˆäŠñdO,.à¥?ç™CÓ p}j{¯xgUò•c5®®±2Háó ˆ>ãç<“Ïnµô¡êŽU½Ùéö÷¯®YOÖ£í©9K| KŽ>3Áà‚:ÕûÆ››ÝKX—O·F(pö|Ä|àŒÚ¼§áï‰P 5–ómôû»+s"Ɖ8þéÎAí]o‰5=Vòsá«h£¿‚éT¥Ä/„à…õ`9ïžµ¯)„äwúV£âÀñèšÊòLbËòyÎãŽN)ºo‹üqo}qŒml4™dÞ®Fª•8&¼îÓÄpO|RG¹µžßddD¸HäÉõ úµ«)ãxmá{ñ;<$vŒ«’Üý€£”ŽcÙô=NøÛ 6 M®'†@Rá²\ÆOÜШìzŽ•Ñ·ˆ58Ä—sê&U†M“n@O}»pOµx¦ƒm¨>¥©ZJ‰1 i f‘0ÈÇןƨ\x¾ 6êO´ÜâV˜ð*ÉÙXãsŠ9C˜÷MKÇqiÒ.Ÿ§”3NÖåYÌ‹(þ à“ïUn?h˜ï<«Û™"–VÚÁ­Žàã‚}<׎K¯AötÔÏ“9Ý$­´azGAš’ÛÅQjzyŽÖëûGìY݇îÏãŽqÜw¡D9¤tÏ‹W“¥¶«;‹„ŽWG Ø¡G Œäv5sYñÖ‘¨$m ô0,¬¤rSÌ*AÚ8îs_+xWÇ?üWk9ðž¯m áÜò¬EF•ú” Ó±®ÛQðÄÑh·òj‚XâòæS)T Ý2# ûUÙÏ£mö¿1Ö¼ÓÁ¿¾ø÷ÅáÏ kÖ×Ú„æ–ËdËå¯ >uPHë€sÅf[ø’Ök©çu™$É*áwç¸ÇcŽjw½Ñ5»”kë'¹oºKcœðz`ŒÓÔhvz‰¶ží-ebÛ#y1+ìOASØ[I¦:oVÊP¼äÇO§5óv·ákpÙxSÁ>=³q \LÐÛj–qj }Éz˵ƒœÇîG~…S¹ñ Þä¼x$H®ð•òÈ=>¿–EK?‹aÒš ÝQæ)Û)`T^ûN ãEkêš¶©q?ˆô¨Ž¥«û© ¬‘‘Ôû¼çŽ)Ü“oÃóÙ33Ú¬h±†USÃcž;ŠX¡‡Äoü#ú³%Ý´r,ž]Ê CœpÜdzõÌ[X¨Ó£Ö‘LE*™“Àn€°àý è-eiiön¬ÌÞb1À*AÁÇz­ñŸ|ø7­ê¥.´T·»%›íº\¯i8$ã£#'ê§ZÇá¿|b– ×#ÿ ƒ¦è÷ö§Ãßø—Bž(E×l‰1д‡ À÷Þ‹#Tϼ£×õæ ,ÒG1mÀerT¸9­­3Ä3ξuØÌrƒ8澿^ÕùÉý¡ûWü!5}sKÆñA!o>Øî•§ÌˆrQ‡eSƒÍuý´¾ø–üøw^”øWS‚V‘lïÁFÏU3TŸ»¸c·Z‰C°î½¦–˜¾²#uÙ£8»j-%¼1&óh7“ò€d9ÛÓ#; ó=Ävž*Ó?´tûè®B…|·R¶:ñŸzßIg´½F»±I†ì³©ÃdtçÞ¢ÌÁ½ÎˆÖ2l’â&•Ð…¾÷¯=8?JË–ÓTO[«¡,X$ÆûÉ$ãõõ¬mgS™Ò+ÛãgÁŒŽFã÷s×=óUÑÓ#ÿ­E˜Ž²ëRß©-ê2,Ñ™?/ÅžÃ5CRƒÍÓ£·Õ¤¬Ì¤ynYÝn½85’òAª<:ÒÆòBB¼|íØ¤r8íëQj>³Ôm RFË‚¿*¶Ö%yéþr)µ?Øåy'šUQn˜y'wÑåFϸ€q»Ž>½(GŽþ$š˜)89ïŒÔIgö˜ä¶Õ'šÖ7#‚¥wÛ®G½vVój«s6Cd– xöïYïÛ¡{d†9u+ `X…èxþ´Ÿ¤PA­°eÄl¨á²FO@:“ïR-þ¿§Å4à<Ó(E¶í>ýÕáo hÒ;œ²É<(Ò¹}ªí“íÛ¸­Éî/ƒ¬ê¾tŸNO¹úP.«j‹zl¸2ˆpÃTs‚{z~ukζK¶&h˜3è7g?ÞÏ|þe,no"ûÑÚ2B¯ñu9ÏÖ±F¼Ô.ÔúƒBäb=ªäsÓ8&€.Yĺ]‘‚þãíO1Á‘P tÖ‹}OJ´Ìr\K åI!@O`HàûzÕ†öÞå\G—ù² 8öÏ_ ­+8õkû¶¼™"|åK0Ã.=¨©1Ô#ócýäŽ9 FãZ­g|ÖÆk_9¥€Û÷½Aþui-/.'µªùqª•b£ÃòíX–²£%Vk•`@B1·žyÇã@·zõ圥ñ¶SŒe½)l¯µDŠV£xÙŠª6k{1Jeß‘y{ºÓK<~C.0ÍßÞ¸Ûÿ^ÛÊóÞÙÌHÜ[(>÷B}ñ@å½ÍÜç tf`§rí“ÁééV­î.$eƒÍea“œqÇó®WEÕ'¾[]`Ùn°ÑX>÷åÚ»3-œÚˆ9Ö;_ÝsÏ¥CÜ d¹Þ­û²ÌFÒvìŽäzVbÇ{4ÆÔƒýîqÇ\Íjø‚s„^¬«öƒ± ®á'~½¹¬û»M)ï|û¹]çDÚT’ï‘ëèjÁŸÿÖúfÖòÑ#1Æä+ýì‘°ÇŠŸO¸û~]T>ÞŒ9;3ô¦Lí‹dkÈ>j‚áýjÛ6ò¸$2FP¨Â;ビ־=žÀÙuÊ–þyiXž>êýGOsšÒ\4€¬…Ѱ “Çoz£jtHàqwWä0ãæÏQõ¦\ê0Ç9¹ž%ŒÄaFê1ÔÒliãIÓì¯nZá &C÷TëZFQv#:m¬SÃe@Ç?ž+ r;hÉm…ÂDÆIdëžx=º~u~_Ü<‹’/œ¤b6?"žü€0)6Y£oÜ9BHªÌ2.à¾jèBÍ 8yw 6 GÓ¦kŸ¶¾¸šØÙÉ!µHÈe‘çs]%¬fHÚ³à–Ûþ=k9žW"7mÒœ1È=8'¦3Kkeed‘‰e;†C7{’=MK™ ì…$šx—ƒ„8\÷àƒÍ=<5£n‚àJ¯ç­IvÔ°¦Ù™mÁc…|cúŽ™÷«B KhŠç {@î}ÍYŽÎÞ•›!@Ž1Ð{œÓ-wyΨJrCGô ²hF› "[wÃdnLpþsIacgr²ÉyqíÚ¤¦r_­<=„S´Ò!py+žùöª ©F™ä´hÍ‚ô ØûÕ(®ƒ%I^z`gÿÕM·Ž[Û™äy€+òð õïÜúv«v¶·ÑÄëI!•ŸÓ§´Ãq^I<12¨Ã«±íèq@ÛiòwU#åšGaË€§¥…µÝÜW²e;vÀÆqŒç±«mwmû>ôe,bUƺŸ~•VÚîÕÕ`kI!`rƒîñ×i$}G®;WK¦^µ™›t{ƒ`²ããÛÿÕPÞ†©™Ñi&6ß~æ3/Íò¶AÀ^•Ô6m6$F%jì “LŸçZˆÌ¿eb!”(%†Þ ­ÉmõKÁ£ªyvïÎ2«‚0厵Ï'sDŒ‹[K½7IkX³ÌX¨Ýƒ°ãÒµ£ÑcŠ6ŠáUfØIÃ|àdg9ÍTÕuqi5ťʥÁså³±Êþ÷LƵﳬt4‚Ýä‚UÀ”qŽ3ƦÃ8ì]NæÅ¯cA$’•e/Á Þ z »g`ðNÖ:–Uœ3‰#u2œä d~5«iioj°O+§?}—#<à`vþµbk¹-å“Éš 0BÎ@»{{Óå`dAqmĘ,É'Ý‘Ødÿ½Ž¼ñW'ͬKp¤¡~WyB„÷ã‚ hiˆòĨ[Ã$¥œÍ;²Âà‘÷ñÔS´Ùìc±¸–öê6’5EH¬ø9^½¯ÒšAoj–­ ]¤2"0zdvf=ý>•zS@v€¦G~µ—ap.ËÅj‘C ¡2yª_|œØœWC$¦ËgpÏ%µ·mO÷Híê44<ì©j9<çw%§Æ ÏéZÖrË"Þ¤K ¼<‡iv“'CÉÏõ¬³lA‚ÚC‹Tz¡Ãnì~ž˜­Û]úiE’¯ú;|ÉžAÏ®ïZ¨ð÷…î¼K«$B3*¶1°¼ÿ'¨ôÅ}k¥øi4k8á‚HÒLÍpîFã—‰e'¶jÛ¹ ›6þ ¸¹“ý"T,ÊmáÎ{g¯ô®}ïmÚ_ßæÏ–ŽÎ Ó÷íïT¬ôÍN ér\Ý\ÎÄæåävçZ‚TÛs+=¤7)l©4³OÊ£/eg“Û­!öò[‚ÁäŽ5È!2Nz»“ŸQ\ȵÑÙúg}?.V4=Øž}kZëP{û¸õ ´,Àì0úõFò;5´7BͦµB†ÛŽ8Ï¿Z£sì®öÚMÄvѦ#2†>fÃ÷‡ŒúÖ#^:H“'î[-€¹bJŸ¼Iëï[’éwÚ†ŒžL k#©Ê° “žäñç]UÕž•h‘¬Ö^l’ã2Fp# ‘õªL" NÂþèOrÆ!$»v!uç¶¿áZVæÙu!z‘g,ío@GÒº KOÐmg™ïc–Uo›Í\‚z™­›m2Á´ènîBij7î¾l9÷çÚ¤NËM»Žuºš ÐÇ)vXP=qÇ5nÞæHÖ¥pÒ}™U’&RcäŒð[ðéI}#Úܘ4Ã4Šåy;x8Èçñ«>F¦[4:”ï¸;”Ê…ãF^‡hèh%ÊÄk<:ŒQ B9ež`­¿PN;ÐÒÙ%ãÿÛ|­æ’ÝÉÏ_jåíìu+Û«——P’uVdPv¨=í]µç‡#ºòu&G¹bb\²á‹NÜ»½h'žå“-õ»,ö—è}å ’ù<àŒ`ãÖºÈnì­t+=RëØ|jñíKû<Ü\<ø‡Ã:W–Ur<›ÛK»ue'©£dJüÂý‰µ¿ø£öXð…ÇŒ&#S±Óáµ½hãÊyÖê@s´8`¼gE~þÉ?ïþÿÁT< £^4všÄŸ kÞwp¥®5].Xµ 8ÎåÅ¿Û_¡n øÏá·ÃGà—Žþ-üº Ÿxó_H 8UµÕî?´­K¸ŠÚî8óþÎãÝÅ®j0“8¨;Vš=Ê[Chc¹$xÏ;w)9çê+£³Ym4ýFþÞS¼yò»Û*þîgÝ€_ˆúœÔàg¥O!GÃ<3=¶Ž4]bSs%œ1ÏhàË (ǶÓë^+g±áä K$#n?¼{q_`|j²mOJ’90e’ Äì ·$äú{WÎ_ ŒßO£Ü¤RÉ,m‡çŒ{÷ÿ8®Ø=ËQjy¼1ÝÜõ(lŒnÛ…øÿ9¯p´³š}-4&_)¡˜ݽO=z©Ïj༠¥hV¾%¸!ó¡O4®ØÆvã¨9¯xÓ"ðµÅÕö ‰åò@.pzçÒ†ÈHã,%’+¿¶Ø¢ÄfáϯJ×Óí´ûKÍ[’Tû–r=IÎÞ¹$t­íSÓ¥Ó=œ ›e^…ù†GÇÞ°®-ì,îL¶±?œT+°p#*z€=~i Òµñ-žž‰u4Ƀ D,>f'¿p})¶:ž»3ƒ{-°39±’:€ê}k&ãM´¹h5mÌÉ…e8hÛ¨ÇO³›íwFïS¿“Ëšܹûêq÷PÕ§­z:¥Ö¥m½È`Ì(rƒ9ÉÉêN:VŽŸuöMìä|¤ê‘á“’1ÈÜ:×à¸nàÒŠx†$#+'–À¦Üð Øúv­Ë„º¼‡Ï·¹ûA06¶B üÇéÚ‚Ò=M±›NÓdó'Xc”‚c¸²ž¹óžkÊ?j€_µïÀ½Oá\Ò$zœH×:£ 2>þÜŒ³eXÅ/(éXƒ^¢dÔ Òã[xÛÍD’8L;t##ùtªúD%–o°éÏ’yþϸdÇ»1p}¨¥QÂIÄ*SR‹Lþ;¾j/ð¿ãRYx¶I4ĺ¸Òõkw˜ÇWP³)IrÀpØÃ)p¿W|=7‡¼]i/ˆc1ÝÝ@Êo܆L¯ÝXãnÞ€ ä¿à¬_³:Ü|IÓ¾3ø;N±ø…%²ÔÖ$ ÇQµA²RÜrè›Xžê¾¦¼Ãàoì.~X[Ý]&Ÿ4DX]$ª±´Ò.]ÙrrBƒ}3_KN¼\S“<”šm$}Gáÿh6ºñ¸¹‚YÆŸ2%G&Π3c¦};Tþ)´“Å—_ð™h«º£ºmnbHÉýß¹õø3Ç7µ¸Oè߉%»%ËI¼žÝÂîº,#Žúý ð§ücöññ¾—%›ø#NðÌ-µZ]{PŠÕeL ¥R>MÃøƒªƳ©ÃÃâšpµe²>/ðFµâM'T›Q¿š‰%eWÛ"«€1…3Éõ¯s½ø‚Þ@ŽÑíõ+«èü×ò÷IåFxÙG#îõ¯µü%ÿ`ø½a¯=ÏÄO‰þТ¹¶d)¡é7—÷’KòŸ¿<ëTÄm¸xêo…ðF¿ÙÛ®/~*øóÆ~+€ÿ¡¤–Ú°aX 8Rã'?Å3@+ÍÅg¸ZkIjvQËêµïçüGÄ'@ñ)Ñõ„W1Ñ%šòJœž¿$®sÞ¿…/ø,gìÛcû3þÙ)ðç†áû-Î¥vV6'#{ùàç!„›ƒw9ô¯íWâeÖ©û2|°±ðÌlïàQÅŠIó=Æ•å£,dñ¸½»´Eº—5ù‰ÿ;~ÉÖÿ¼àÛ[á@ûn©Å­ô¨3¼ì-g3tÉ26~óÆ«õùîÏá[Vœž­žž?.ötc8ŸÿÁ*üe§üný‹üWðbîC4žÕä•YRÓ\ÎBŒm¾IBµ~\ÁZ~ø¾ïö­ƒâm•³Êß4ëMQR%ùýº­â)<î®Ùï%zü?â\¾ý¯[á^¥+%§t‹’í°bæÔŸpR@3ýêý^ÿ‚‹~Ìþ(ø³û8xËÅ~ ‡Èñ/ †º_êÃ[Õ.5#›é@i& »Íó¼ö!‡ ¯¨â°âÓZVÙc*HOAÐþµúó¥qæÁ÷¦IapìnìóOšÞîRæ¬=F*œw3Äs ÈÁãƒR­ÌñðãÒ®.¢OHô4×{Žìlö Íç˜1*)úqOßdë– ‡Û‘@¶‚Aû—É÷¦+…är¥K±[”*C8ç!¾‡5A£‘zÒ¢ÜÊ}>œP‘fýbËÃ:‚§ó¨öê1÷Î=5?ÛÝ2¢“ôÁ bŠy÷©¦_•$+ô¨VêÁö¤n纚¾·w(¡–LãÖ§MrG8xшþòÓ"°¶š<¥Ò©ôjUÒça´MÔš¾¾)xÈ_²ÀÀz®+j/ÅrÂ/±B¤ûgðV ¿†o%`±sÓæ®³ÃŸ5-sÄvù–[û˜mSjîbgp‡j÷Ú¤·ÐTɤ›cŒnìéCþëð†ãÁ u¿z­ºE}₉fÁKUc9=2Èìã½~ÇÅ~–ê2så·¹¯%ø{á½?ÀþÒ¼'k†Ú’5]ª‰µDcÛªƒ×»U¸tÎóŒí=0ô?Zþ<ãŒÕæ9¥Z½²ùh~Õ‘á k~§X/cˆyìÛwõfîkF˜æ…nTº[ƒ`k—K‰0c µÎFqÓÛ«ir}’|Š8÷ükâÜ_cÛLêÅÒǬ8íÎ}X7Ê­†SŽ3Ï? äŒóev)Ÿ|ž{qÚ‘ç@†" ¾¹Ç½G#οÎU¬d†lwÈ—SKatÉFòwÀ 1Œ{šå"¿–6U„d}Ý§Ž½øéR-ì«òï,Êw£*G¦K„‚è쌳¬¹‘×1œ“ëÍV¸fWBÅY{¨ä Ã…¼Ä8! äàûûúÔñÎñ‘æFÏðŸÌ{Ön w:Ûx†/29o%0ƒ“ïÎj'Õ.å‚C{,qÀ†Vi&eTQÉ$çë\£Oö˜C+ãq&6<*­©ÙÛëºׇîs ^GåoûÇï ¯#Ÿj.ìÉ•í¢6´½{Ãúç›w¢Ý‹•ˆ¢»< ÈÏbx®Š3ó‚Œmâ¸M?M¼·Õo5ÍNþ9®µ‚ݺ×d° އšÞKÀŒc^@^s‚?ÇÖ¦­ž›o³:ƒ8a¹³¸r=³S+«)`Ç ç¥sðݿȳ'^3éZ6²$Šð…Ç—ÁϽr¸\ÚèÜIOÙ÷”<=q[0ˆ"Èçñï\°v,°¡0ŸJֆꌀśøŽ:æ¥Òh.rÿ|£üTðQðF¿wu§CöÈoö›Kæ,¤0 ‚àj?‚Ÿ ü ðyo¬ü .§–ûËšîîþQ$Î" UI$('“Íu“Ç+/Œ{ÔúT¸;¹€ô8&º!Œ­N‚—ºú*’K—¡¶÷Þd‹,ÁÜ6 sŽ£¯ÅG%ÒȆF<Ll¿)ǧ½Q“ËÃ9ˆõÚx#¾E5ÉÀ§ 8±ÓÛÖ¹’'{™:•µÏˆuí/BPÒ<Ò™zàJ@íÐWñsûwøïĵü'Æiá…Ì÷Þ"Ú$~aÚE£&™nT1`¨Ò£K·nv8濯/‹¿GÂ/†~<øás'ðö£¨ÄG_= e…=ÚBí“_È/ü+ÃÖ¾%ý¯t/xÈ$ÖÞ ¶Ô¼Yz÷?:ùš=»É»æé£rÇø†zœ×ôŸ‚9_- øé-íù¿Ðü˱\Ó§EtÔ÷ø*W‰þÝ~Ò û5i—[¼-ðkÃÚ_‚´gŠf(²YƲ]Ê'y’V å¹>_µxŸŒõÿøsCýž¾[Ãw)Mojûn5;¢7Ku}?ß`1•!Ü€"ý±ü5¢j^!‡Æ:zI¿â=;Mkï8€$»»ef^vÈ~ù ô漆ï\_|1Ö®¼7p’ë¾!¹“B·1s%¶ŸjŠ.$ A™€@F>L‘Í~ö~|yŸÇ_ÀwßD?.俵Óí-ìå¿s‘wsï2UõF$=ñ‘Áô‰þ:Ü|Kýƒ­>xî)fÔ¼â+;}&ú1Á°ky[Ë’Õ«°‡ðàFO–xà…>)i_hø9­3êÖ¶âkÍV)È Ï Æ Èƒ9À\ÁÁâ¾îý”c¿‰_þø‹öxðç‡g×|e{uq¯ÛZÛ6Ø ²Òâ@×s]0ÅlÎß6v€_+@HÁ4í+¿ µá?·wü]õ}jo…³ÅÑ{{lÃu¬«n2?!…¿aŽ@~£·=<Æ×~þË«à•Ò`‚ö×NØÉæÜËeuvð 02#…Ém½ø¯"ð7ìï üTñ‰þ8Æcð/Âm"þB5+µ!þʱ µF¼¸“åcvÁÜÙZmne~È?³ÿÅÿ™ukyFÓÞ3~btîÿ2Ú¡îì£2œüˆrq¸õoÛoö—¶ÖnæýŸ~]„Ó­?w¬\Û«•¡àZDGü±Œ€ÿ σ|^ý²u«Ý¾|‰ü!à»]ÑB–ìÉ}r¤åÚyó¿÷§—忎H¯–< fͬL_ï"1ÇR?¥"®[ðŒ{gòâÛtp_Ç¾Éø³vÐ| ø5v Þ Ô¢“ª ©?*ùÇÁÚ{J'¿‘K*ê,X÷%zW¹üw¼’ÇáÂ}'Ì‹ÁW2äðBÝÝ3Ë8=‹ƒWGí/üÃMð¤w²žl¼2®¤pµ+ƒ&ÕPzý‘ø  èŸ|<Ào¤Ô˜Ž"·|À°¯ÌŸø%•œW³íljìy7Vš>•ö mÞ?à.Çñ¯Ü ?öcñgÆý[IñF›«.ƒáí)d¶Šx¡ûEÔà¨I<¥cµ ï¸g·ü›Æ¸•,â³›Ù³ö š 8H/#äÍOXÒ4ÉÔj’*´ì5伎…A,O¢ƒ^ÿðïödøÓñ"µYl‡„t—…Þ¨™º‘é®w}d+þí~‰ü"ýš¾|$]xF3êe—R½´_²Ží#ç`ÿe6¨íY?jÿÙßà1’ˆ~$K­UFWLÓÓ.ݽܬyÿhŠøä¥QòÐÏBuc ÙGáwì±ð‹áÅüZ½–Ÿý»­) 5M\‹‡Œã¹Lyp@н{?ŽÅbä½£~‡|Æê~Ø|Wÿ‚¡øC‚]7ö|ðìÞ"¹@BêZ–m¬ÃŒ„ÿZàuåÍ~M|tý§~7üg’h¾1xªi¬+¥Ùg`™þ&3óÛFc_”¿à ¿ ¼ ³i¾¹}sRRWe©%#ƒºcÇ^¡rkò‹âÇíkñsâ£Ékq|4Í9ó‹K< ÁþóŸ¾GÒ¿Wáß ¦­:å]ÞçËãø‚º„®ÏØ_‹¶WÁÏ„vÒèÖS%åâ -­¨ŒóócÏ©¯Ë/‹¶ïÅ?ˆI.™áù±´öÈÛúÖïÛè+âÝæL´‡qnNyüjQ9T¯#ò¯Ö2ÞÁ`ÒqåÝŸ/ˆÍëUëdOp÷W²½ÍÓ´²Èw39Ë1õ&—û6Y@úÔ°Ja¸dt>Ø­ÛK‚à-ÒnBÀ*€Ç·NŸ…{÷±æ6å«g.–l_l09ôÏõ­8tf¹l#sëŽqôüëÐí|<—1»Ù©Ivw(ã$3]E¯‡"UI%O1 Ñ“ÈÈëIȵÍ,4KÈ/a¸µ¤œŽNÜóÀÿ8¯sÓ¼+goêM>ÔÂ'–ÈÐzß/=ë°Ó¼kå]ì‚Yísò›8aÃ*€§'ôükéGáÏo©hº§œ°ƒ´L«¹Õp>\ãƒèkšw4‰ô^‰}FÔt¦‰fOÝ65cÁeì ëšôK¿ë+x,DS4gqC³åç’1_5Cíu´¶Òå[HõBÄÓXB…'œ×ž}Ãz3Im¶YXŒ$“,Á°r»áJ2i îzt%õ¾Ó2ùlT‹‘/¡ç×4ºtAd{'l·öìÖ]äqÁéõÅrZN•ûUM~ð[kZ¦ ›µ e›;=<½¥[ý®@ô¯eÐìüOpÍã8mäÕz‹%]xåìsGµfŠ7ì´­aÕ]„-wõŸõž gYQiRÛ‰tí5Q°ÀtÜ¥[ŸžÒ·äðgƒõY~Ù­Kyýš— pߺ¼°M¹ÝŸ\Šd¾"»¼{"æÂãMÿ[…ÉÌ'Ø0×85Jm’ÐË;]·ÖÖÄqìŽTM„`ÑOrH9ÍZ{”´¾}BÀ:Ü[«¼››*C¯Ì…{è­MCÇ]ÜQ^Eµ¢e˜a>Þÿ…[{ä´Ô,¯¢h_íÓ“år§„!º19Æ)\ÒG‡¯ ’×Ű”Zù À‘ÁSÎ388¥øwÄPÓòÆhÉdc†”§P€’ ‘ÈÏZü‡Q¿’âhm¥÷yå#$ã$à{\ Ü@°Éw2HË*E8e^ ‚@öèªLBí-®Ÿ¤1x7å×%Ç ÇÇ<ô«—º&‰¨EçÌ'­dŠbÜ`:6à œ¿¨ë^{áiZ¼k¤hN`ºfau ÜF9DànÏr>¾Õ§öo­Åк¾ˆêW?p {o]Ç=x§Ì€Ù—Æ—¶êYCj°™!\¿’Çøè3ÔUn÷Pðæ³/Š¥¹·»Ñ®-Ò1LRí<±Ê;±ï‘\½–§©¤ÍÌk ”ȇiê;‘‚+cÃþOë–wTÆÒ ‰…¼wR/úÆÛ’Ÿ7N>ëtúšÊ¶"%Í6\)ÊNÉXkÖ<‘Ú޽ϙ?˜-Œ&'’rî…¸,;ÎzW¢xá_Æï‰Ë߀´UÓ4ÄE»ÔÓd_gÉÚâ?õŒÝ·9ÇZûáGì™à]Y5õ²½ñ%­°æ ÛY§Û;ŠF@Ú©ÕC Ùõ¯¤4Ï øsE[=îMlØŸž-L¤7®ˆrÙJ®àzd_#˜ñÖ8mYìá²Û¤æx&ƒû)|:Ð5M7Äþ"OZjúx+ô™m:ì:œ€Pƒ› È®ßYº»ðŒ-u3âŽá{¡[8´û ã¼–|’ÿ¿[€<°½2¼út¯!ø‘ûvõðO€t­FE&?iµ³¿_9Yøûª²¨AŒžG'šüêý§<5û[||ñÕ犼iáàû;>ƒ¯hÑ_]Ìb}­Ü@ЂÎsƒƒ·ŽãŸŽŽëɤwÊtè+Eo~Ðÿµ†¹¬ÝÃᯀ.¿ñƯc¨CkªiqØÁ$_½BÃR¬p†ô]ç¨$€s_xÿöLø‹ã(,¼{û]üW½¹°›WµÕSÁö¶p[À’+dZÍ2™þèí•bqr@èkµÓ>2\|=ðM¾ø[Gðå¼L‰smeb©l&Œáˆþ52pAÉëÖ¸Hµ-GÄ÷·Þ,Õ¯ÖÊÎÒR'GÜñ–`î'kr8Îì+ê0)ÆìóªãžÉŸFxçö…×ôi×áE²iŸdf¶ @¤ !í Ÿ—‘_0I©k¾:™õ/jIÐL—²Hòl—sg Áôâ¨ê÷±hóü,$¸»ýôl[#ÛÏŽ<†R2íõ¬Ë»yï&¾´ŒObˆ ó”ïv?òÏŽ£Ðõ…{ô°°¦­É«ˆ”Þ¥MWLÚI»|ðð²Ì¼:²œçÛ¿¥TVžëA¸Ò4›XÒ ]‹¶å†ç…þû£…Oðž¹­}Q¿‹Ã—1(ºpââFÎíÊ8+ùdXz5Ä–Ú¦¥¥é󥌳Ä@ Œª“ØŽžµÑ©…ÐÝëÃmuäêúz]³YÔþ÷ĤŒ:ëT`8Æx縮GÒZæóûMwvÒ–T'qòñ÷3ž§¯5OÄ#ÒZæ}I›I[Ú4f¢ðFåÏÐŽôÒbg}®ßË ë0C$ÍöÑr²²#æ2®:g¡¨µ†¹©LÆ ås L²&ñãϺ~†¼²àiZd/©Þê0””móHù˜cË“@ãÐi> Ô­n!ÒMÏm}$›yfBrô9äb«•ˆö7Ç^ …âÎPÿhAƒ½@§Ç\c5·¢øÇBƒC¸¾„Æ®òGcóÊ`{ޤWÎú|ìÚ¥þ—eò¢Ädk¤áÂÁ<Ž1+Oþ5·ÑâžÃÅvBBJoùQK ¤7s“È#­¬J¼¿´»¾»³‚ác·ˆŠÇäüàÔdðuv·¨izQ³ÐŒ3»9y#$BŽpè=«ÆÞò[æŽ{[h#R®±;BŒd“éÛÞ»_ é~Žêâ%–I/H°’Æz¡*3¹zã½.V4Cá½Eïõ¥-¤L‘ùk »îéÏ®O~+¿½ð¼Þ1¶¼+iwIVo–5CÛ•Úx“V—ÃÅcñ”~ñD_¾Çm¤Oàx¯/}&úÞþÊ` ÃI@ÀýÒGþu¹ª\%î„4ŒKo|í#Ú Xä#?xõ ÎA ®Æ=4ݺ\GWAç#Ï h]âLjuy-„Ò›½a‹vŽÛ ÌŠF'=@úñ›K-sÃß £ø•u ÜÙh·—fÇO¾ÔdŽB÷-‘·j±` ”f±m|w}m?Ùoìb|®¶?œ•éÒ€>”òã¿wÔ¬'É€[÷x<ã߃ڋ›=Q­äxR&8Ý!É(à`WŒÃñ"ÊÊÊ1£;Eµ2° ¤ŽF;v ×jþ/Öçµ/èË0Õ—ÆzqÿÖ¦˜òOm ØY‹BÑå’A÷²š²u_´þδ_³¼lGin2cžW2ÀµÙköxÌ>Ò[BeI •Ä‘Çótn*àVÖ‰ð3JÒ“þ)ëz|‡-–3Ôää}â½Z+ 3I±œ»äJº1Ý—<ÏL{U{ XO2ßÉ!‘£Ä_+îÿ&ÀåóÆ:<ˆ÷ö'Ëm‘ì'lsœÊNz†²¡ý ¯<;©4ú÷…õÝ1W+ç§úbgÐù]}ò0k¶Yn4HÃÜd ù²n!W†zc¿ãJ|U£É’yÚ Ø4D3Ç>ã¾*@©à¿Úán³vö–ZÄ6—%¶MkwCåž üØûW·i:Ö‹¨É*ZÜÅqÇ‘*¹,:œ)×áMáÞ»¢Ë.JM£]n‚3Û÷n àu Óåô"×UHݼùF €N©<`zÕcâ­£Ýg2Ns½‚‚x•ùÌ5¿ÛƒáƒùóK¢üXÑ¢Â#:n«´pBí[0wjìü%ûXÅir°ü\ð^±á ܲlûM²¡þñLGCÆ=3SÈÀû¹üK£°ò’eC0ÉYx$žØ<Õ³¬Ùê2îC )\düÿ¥x¦‰¬x'ƦX|3¬Øj\•b!¸G›ûØpÙõšëâ—Sµ¸Ž ¨#Ì­µfŒ3ã‚r>ir°5¼JÞ-—N~†Âá"ÊÉ%ÔŽ®§ÛhÆ~¸­ +BxÄšåœ6òÆ€³HJ¶5‹§¥ö›’÷^xŽõ òó÷‰ÎOšÓûI¸²Ù¢:Ï>ÚÀsÐ{Rho4íFêâ)cŠHrV0Hb;g½[ŽîH RÊÇx'ÌBrr:ý*ëëºÄ:k‹s,Ó#eZPuãÒ¥kÍI¡ó.l–X1¿~Ìä‘ë×úÒœ¸6ÒêP߈e„ûþGzÔx´Ïøôº,ï<™ÆÝÜ}{Tº…„¾S]¶šœ($¤sŒú{Öú¥¨Žæ(C‰aÁ‹ÏPÇÔôÁɤÚ$‡PŽ$‡fÏ•°{þ5"ÞjñÛà¬Ìr²}ì}Hãëšë æ››m$pywSHr·3É铨ÔSËo©\I¶ÆÞtá°ÙL€{Šã^ò+(Ù,nYäpr£î~}ÏÒºÐ'Ô-¼É%3€ÇpØäãÖ£þËÑ _ÙêŠNAQŸîžßÖ­´vÒΫtâ)B’8Àÿ ÇÝ躖lïCIüûׂ¸àqþElE·Q¶v¼B¶Ì>tUÊ3v8=¥fÜÚÜ[‰%GŸ`<3€x‡õªZt"éä·†ݸ'Ì”“è(®™ei¡Þmnqï}¶ÎB¨Ýß®qW~Ñ=êÝ4‰$dò×w¯=~•%Ž™£OpaÛÑ·JT烟^xÓ´[ÇÍrb;üÂû²1ÁJ"´Ô\]Z+«¢ª¾A¸Æ}êŸÿ×únBë ØÂLž=G¥Aq,‘ÆLñ™vœ¸·VͺàòN;b˜ö‘ÝJ«'+Ï8?é_Ùì¤f4"Þ'™@V“ïIÔ€¼ŒZ·f[ì©÷!™ÆìŽÇ®=ª÷Ù­Á2:á’@ûÄvÅPâVVÃß2§ðç¥E˱ªm;4“T»?xž•$r<ŠÍþí8$ööÅ6ÍÄ›íàq!\ï^0§ž{Ú´â*ÒIkt®ŠÊvFÉF‘,µôAC¨ÚIä}µUPØ ˜ôõ”Ñ2Z€×h`3Éç?‘â¦0ï±ò(å‰É#ßÒ¡²Ò-ÅlÒe/¹¹8b×J¹¶Õ—쥶íËÝ“»o8ϽTµ¶ûYe”G)À Ü{žÆµÒãO]$ÊòÂFår2\N:Ò-2«ËhlHÉrÄ0@Ï|ž:Tl µµÿNÜc ‚Aù±ìi^ê)g–îÀN¥«u«7­hÉjÅÕá9*sŒ•ê}((‚ÖÞÒç÷$dyc,ÄžþßYšÒ8 K»…I.Ý‚ ÆçÇ|c€MN¢+…”üé€Àc>žÿ>ò#ų,%2Ìsœc§=h³jóA?™©)¶„~ïÊXˆ;‡pF™e Ë€[¶âC)ùFzúçÚ¶T«,q5Áh„%FrÜ’sÏàx«ñÙÎÀ\™ùr~v]Øœ‘ߊɲyäxÅ£Hx_iÁúg§ãZ:§ˆaÓlâœJBpû˜ ­ÉÁúÖ‹¼aáY–âé¥tË’23ƒþE|Gñâ–¡«odfLåQ:œó÷z{ÔÈïâ_Ç {‹ÙSChí¢8Bêù1Î0¾:×Í7þ)mNð}žáäó!§ãÏéTßt›d¹ÝgAÝ´ûõªÜkf“t§sd¯8þ¼~5(»"Í›ßAûí¡”ü©•$‚}GøÖœ1\Ü Šq*–Ëñ¼§Ò¨yÑH…£\Ì œãÐú}k£·ŠqöS°à©Üxç©ü:R–ÅÅj[²Ò k·µi#Ha]åБõÇ®k ³Òô™.JG0óÌ’pê{0yúÖt6Ö÷7ZEµ#e]¸ØñÖº©4Ï/{éiùW(í÷à©ÖØÚ(š{)®Qö–^7n8\}zÖó\¨Ž×RÒeHá ûÈÃ#Œ ê ã=k˜†BƬân Kåp¿N~‚¶®&°´°XaÓã»Ì[DÆF<?Zç40õ‘a{qÃq=¬Ë2Èì£*ù=“ìó\ÄÓ«È^@[|ƒŒg¸«rÜY‡·„y,ªp[¢õäsè*Œ‘–?#U]òeÝǘÁºžEZØ ’ÛT°F™gŠVÞgl®À=¹'èÇN:ûUhÖò]FHbCÉ„Tí´v†´ÖÚÞûU‘ôÄd‡nvHß6ñÔŒõ&Ÿ¥\[’fh¥Ê€Þy'ƒíè;P#JÙ﬷I5£›€óÓžç½1^å൳}V$ׯT"Ÿ5J‚Nzm§^•áÏâ›Øb’h–6ÉB·9ç'Û¥oØ]jqZÛj茶Þb|sÆNA gÜqÚßÛÄñÚ$f$ráœ8ïòóÒ’ñ&ròhöë>÷ÅÆxÇxüq^yà›. ݘÄf)N÷‹’Éî$^•ÙYjZ‡X•®M±aÎä#׿àjö1–ãæµ¿ÔdŽK¹9Ì›¥F_—ÐŽÕ'Ø5åŒØH’ÙÊÇo$ÇOj¥.¥mÊj^d¹ù‡ëJ–-KV›Èžá¢X°B€:¨Oô¦I~ÓL½¾¹–]Ë0(¥OÏœòG1Ò²$ÐtÍ=¡Õ/dòÓåó¡p[©#¹ÆsíZ «Y\Ý.-–·JªÛÌ™#Ž;súVCÞCx³õi¡}Ò Ë’ª}2GOzÛÓ®4ôœÜZ&èS× sÜqŽ¿J!†‹é­×I*bvŽøÇ|š¥y 6¨Úm¬Ë²òÁG,Çœî<ŸéXºÅÝ„P TÓ\ÌUѸ`ƒûÎ;ã°ÍkÝêz†‡|.tøö£ÈìÛ<Ï`zgi´½rÚi]ÚI…ÄfU‘“Æ1À#®1ŠE2Ù µ]zÛí ´¬GòÆò>VoQé\ü’´k ÷3)%Ùü°§Ñ¼~´¹¨éÚõÕŒSêùß3üò1RÀéŒr=k©Ñô)ïu8§¼•eE ûdàþÈëÏjó1ª½ö¯ÚcJ\;®óýáZÖº»ê×Ï$ö -á¶ñâïÍnÁo§Á}q¨ÙÜngŒo8óïÏ^•Ýâin¶¦Cr&# ‘žã>£ç¿h¸ŸÄ•ã5‚  €Ÿ-W’XœŸ_Zî4›íKzìˆ9i€rª¹*›Èäžµ¸¦DMî©#ˆTÛÈÜs’?®*xl­dŸW¼ç ­æî¾;ö¬A©Û뺖ëx^Ù2Ñü­¸ ¥¿ˆ{ñTïÝÄÒX^IÙ¤oÝ,@BŽA8Éü(vòêÞÒïd] ™f#kM¿j†þgÞ¯ÀÖWf[MN_ ò&+Ð0äŠâäuÑíÞÝô´Ü.ÉCžùä±8­ +ßìè– Ùîsshš<õ {PU¬våcÞð¢³HÛòDJ¼rx?…uZšÛ·ˆÚßU–YíbŒ²B¼>06àdw¯0´µ†à=–•8„¸Wi›Wª±ê99s]Ä×P_½íåÌ7o„ŽHIÉQü<òHÖfù»>ÕÐõÓc´ÔoŠÆ±Zûˆ/ÎÓƒÆ@è{TZ•õºÜAoæHmÙ_Ë-„ªƒŽx¬ícZ¾Ö乸»•7]»<l–Ç'­xíŒOEÓdÕ縲Ö6‰1ăj |ªÈ<äçð®ú >9V[Øæó·3(ïdã9È^?W•ø_RÑÌ—:ýÀ·vÄBûSt`zžOO@kq%𶫪]]Áx±]ùgåòåW¢ñÁÇZ’Ž_âuM>ª7[Ë$Gwý|­ðØZÙøÂûC¸o(†2ÇÎ6§ñ{uâ¾Ì½Ò.¿±ÌÚ†'¶žü¼ëŸ\Wç׎d“Dñ Õ"gòÆOE"Cõþ•¬ *§â…Öúˆ¤ÔãŒÜÜvp#}òyÜ:r½+kÃZ_ˆ¤¹VÔbòbØ@S¿®OÒ¼¿ZÐumoÃHñø·TŒÃt¨`·” d~§8ÜFp8ÈÁá›K+ ŸøJ5o=§’Á¿âbá}N{Æ*̶~Õá‰õF“X²HѰBÓýÔAX^Õç^"Õ|9co.³¯ÝAµ°ËÈ'‰GQ€Ç¦xÇ5àºO„~ Ác}ŠjŠÅFû‚ó„߯vÀÅz£x+@×#·ð_†tÁr¨ÍŽÞ5òKo9î}phûâÃ[…iX´¹2J¨È…™•1ËaWðô×Xø«ÃrD—š$w÷{؉c"+Øù¡x#ø¸ç5è‘ÚbÔe‚×ËaòÓ†fì0}}ëžÖõ;k£«jÓ\Èc”C&ùn¤0ÆGœuô´Ýÿ5ËyiM³ fRZ(Øþ¥|eÿ¿ýÿ´WRø]àˆ^<Û»¿KO YÛ3dsË‹£ìDcM|iâoø-÷íÓã»–À¿üà¨ìtÒJnüO;F£U¥61#/ÊI 9?wþaÞø­´ßÃe®ÚÛCaÐÉ&WfÀØ{”À¬}_âˆSŸûo±é÷#Ûæ˜Oðq’=Ç~õéÓȰËh&s¼uNçéÁïÚgâí=ñNîo:½®¡â'†#§½­šÙG%¤\4!˜oŒ’ÙÎJ·µ~¨ü"ð…i¿Ù¿Ç¿°Å ²ZÝYÏ&“#š%c"'Ö—eÇDlv¯åžËÅ—^ñ]ž¯à[ôÍ=Åí³ñƒ,x!ÿk•qèkú&ý˜~8xÇþ$ð‡ÇÿɺÝob·¾I'" ›yGlnÎ` ~]Äù}L§0†2Š÷%£ò>³*ÅG‡• î¶?‡ÿø3âoüçöÏÒ5OÅ%¥ïƒ5Ë}K*§dÐZO²qÛvcFààö$W÷eá«='ã?„<9ûW|Ôô»/ørwú ­7Q°½EšeúF 5•üAY\Ù*G&×òöOþ Ïÿôø!ûDøæ9Áý¿?àŸ°ÏÅÝ[Uñ×Äx‹öQñ¶¦¦hšÆËþ߇:•ÈL±¶¸°T»ÓÈÙ&híâçyðÊ??x[ð&¹s£êeÒ´kq/»NÇ&ea‚ý°üÿƒ€gøD?h]&_5Ê„¸K˜1n%¾ôDÇ*}«é~ËŸ°G홥{Á6zn¥m~T††U‚éY°NÜDZšõ_ã°RQ̰.ëUêpË ¡U_S^ÇùöC©]Û‚ î¨˜~´Ö¹´—›ˆ€ÿwŠþ­¾;ÿÁü É-ÿÂÝNîÂã’–šŒ‹ žz o’{Æ}kòãgü?ö£øGæ\=/-P3‡.6¨'ï)e' ©5ô¹e8¶£ É>ÏCÈÄd¸ª[ÃCó[YL?q.£qDšmÔk»h#Ô×[âO…¾<ðÌ–ž!Òî-ž/½¹2ÔŒø× ¬ñ|Ñœé_Q ÂkšMy;žd©Ê:IX‰‘£<ñNNŸuŽ*èÔ®€Ä¡\´3NûU“ÿ¬€ꦨ‚²ÝÎ>ñëS­ÔMþ¶0Oµ;m”¼+•ö"“û=Ý·uoÇ"E§K÷œ¡úT‡M¶ÆaOÔâ©ÏgwÌÈqõªœtÆ(ù°‘rÉó}9¦y3(ËÆNj¢—u°}ªe»»€äÐå¶“É%H\ã¯Jªùþµµk­Í yRÅ©9$˜þ5r=cIGó&²YuŠå÷¿PH¯Õø%'Á‰>'||o߯·Þ·³r×WDǦV13óЀq_œK¯èR8UÓÆÐÜWôÿ»øLþ ø/Äå[wñTÆòÖ(ÇÍ $}™ aÝ•AÓýk渻2X,®µKÙµeóÿsÖÉpþÛk¥©û!ådg‰Õ#á@S·`éVVÎîFÃ#·”Œ}8¯“õï øÔþóLñF«oD3tô‡ç^k«è?YÈ1ÕÀìì 'ýNi9ʦ¯ÉŸ®B³‚²GèH³Ô²ÈÖsÝö7øT©¢k2¡Ûjüó¹ÃdÜcô¯Ë‹í'âŒyoøL5†Çñ÷"ãõæ¹Kí/âùó¼QªNsÆoe ?ñêèŽIAÿËå÷Á±2ì~¦¬A·}¬œŒr1ÓÛŠi:¡O2hYqÎÕþgµ~%_øoÄoó\k•Âõ%ï''Ÿ«æ¹­CÁR˜ç»Ôo&Xmbi¤¸¹»•Ò(Ôe˜înö¯B‡ aê5Õ»~_ðNz˜÷M6Ö‡îÈ´•FÌÄîòƽ>­ŒU}ú]¼e¤Õ4è—¾ûø§Õù¯á‡öøÕwñ â%õï…n.aÑb&ÞÎ3<¿<)ǘྠHrÇŽ˜«çÑu)Fíš“úŠý/àå SŒ«×i¾‰Á>J·N3q„.ïãSø‘ð»M-µâÝÑ`ÆDšŒTúòüýFkÍï¿jÿÙ'EýÖ¥ñO¶Ät_í8‰_\Ù¯áIà¾dó ±÷?•@¾tG í8ì1^Œ<ËÅVOî_æsKq= îTý¾?a½Þiï~*èSy{AKf’v'ý«óqߥyÖ«ÿMý€´‹‘ñÜ÷¬ݲҮ§Ó,fþ5¾Áâ;µÊ¬¥O«`ÌÓ†‰­Žf(ŸõÒUÿ]´¼#ÉÄäþküŒ%ÆX×µ‘ýtøþ !û xzî­%ñ­,‡ ¶ºoà iÊôå—ÿð]ÙfØ•Ñ|â‹¿tÍ%¤ ûñ)#ò¯åœèW–îÕ}Œ£'éÅP’ÅSïN‡èI¯F—†9ý[ý,rÔâ¼Æ_lþ•|Aÿ÷ðœ|ð©ççýf£¬øÿr¤çñ¯2ֿ࿼¢<;ðëÖ®Øe¸¹¸Á÷â0Jþ}-â— ´Œýéò=ŒP”T,ç®MzTx!§d°±ü_êqË>ÇÉÝÕgìf³ÿ×ý·u¤]!ü;¤I!ÿYg¤ Ê=Í4£PkôCþ /ûv~×ßµÏÇ_øw㇊µáÝ+Ã÷‚²[Ûˆ®ZXÒ&C j݃¸°ôü«oµë±²}ø¯é¿þà™¬ü'ñGâløò§m7Hˆ÷Ü8üD‹ùWÇ>YƒÈqU)aàŸ-—º·m-4=,‡ЭާÔm_«gô!çîfl`m?68Z½¡cù‰Î+ ]÷È8Œv­8åŽ$ÈÆ‘ï_ÇÒIh~ÁÞæ‰2F¾S‘¹¹ê:T2K0yüŒvþ§éPÆÌY‚´ dôéú‘Yš”ës¦ùjßëO–Øàóš˜Å6‘}Ìßø+wÄðWì ¯ÙË3Å'Œµ½3Fù2 ‹Ù×+Œ+E Áû¼“Šüý€¼1ý¯ðÛã.§§H‘êú—†­ü7k5Á>Bÿk\3O¼`ƒû¸‘‚Hú×èGü«Ç÷v>øQðv-«‹Rñ Çfwr–±3ýÝø¯Ž¿`+¯ x+ö|ñOŽ|[vÖv·þ#´Ó•–31yb´3*„’CWö†˜†ÈhY|W—Þÿà#ñ~(­í1óòÐêåý’uOênø•ã´Ô&G °²x– £j”iXŒ…8QŒë^¹á_Ø7ö{±º„-ž»®\(Î'¼hËçm¸E õà÷¯›¾#ÿÁDü?á{Ù4†žœÞª0ûgˆ'ùw)<‹X>B03̼þµð‡ÄÚÛö‰ø±vtßx’æ+;¬7جØà Œl+—Ù˜Œó_t|ÑúÇã¯ÙÇà§Ãí~ׯ¿oïí5-ö+EÓÞæapƒ~Ô–LÞ$¯´ÆÌ1ȯֿø'çÇ}+ÅzýïìYñ‡NÒü mûLøFu}kM¹gÔ´…·‚VÓtøçXü¸ã¸¸g288äeA¹Ãæ·À²^ü8ŸÃÿ ¼â}ú×Äú¤Mu¨ØÁ%ÙÓd¸òã(mò<ÇÎÞAÜ9‘ö·¢øó¿áN·¯Xø{V²¸µ½°ñóý’ÎÍO:ÓP“‡h|™"ÞTî!£ÇüSÑ­<wáXÿ¬´;Ñî´’¯¢­ÑÞ®ÊÑ|¡Š” ÏÔ”‚XŸþ ]ãÿ|Sý¥uûÏèÚæ¬z„Ú¯‰-¦´¾ñF§å {Ýb4•U~ÍpÉ[ˆÆÑ`ñ¸ùéû0þÝ?fKy¼'¤Í¹àûó·PðÞ¨¾~›q?>#9òde$y‘úå•Í0>;ŽÆT”ÅrŒŒ:« õ¯gøo¢\Þ\ÜÝÄ›—tPî÷P\ÿõëöÀ¿¿a/Ú×C¼ñ÷ÂË=KG*?Óôxî»Ñ¥q…h÷e%¶'ý[ã ȯ˜>&þË~1ý–m®ücpßðxAÚs«h™ò¥ž-‘¥Ü@n¸À~ccŽT¥ò—‡a—Kø'‰d0 n§È<Ÿ˜ôA^‰ûZÂ4[ïø-” ­ÁÒç„îwúgx5ÑIá u ø[áT“RžÃHe@C3ܺ,ÈÈá˜ôí\7í%syñWöœñ$>¶/&¥âak ¾¶j¶«lÇŸN)NJ1rfôay¤L_ðKÿCá?ÙSÀÞšàBúæ©y}%ÄÃËHíårË!?ìõJý¡ñïíÿðgáN—þZ\ø¦M6?!YOÙlU—¼’¿Îù$“å‚kñ¶×ÄúÂχz4yÔbé°YïºÁÇ|ç9ñ¯ÅßÚ£áÃx›þ½n ipX[C‰'lŽ#'¿ZþhÇpå|Û1©YA»ÉŸ¦ÒÇÓÃaã++¥ŸÿlŸR[x´*FØ4ý¼l;“&G÷äjøkÅ¿üðãGŸZñ%Ü}²nf–w±ö$åØþ&¿ ~,ÁJüa¨É%—ÂK#§¦6 ËÌI.ßöchõ$ûWç/Œþ!xÏâ&¦ÚÏõ;NåŽàÓÈX/û«Â¨öP}öOáÌiÅ{{Ev[Ÿ9Žâ(ݪz³õÇâçü§Ã–žn›ðâÊMUóòÍ91@¨þ&ü¿üÆøû@|Xø¹rÍãZYmœ¶±.ÝG¦ÅûßVÜkÆQÍlǤHBHa»ž?úõú>(ÂaT`¯ß©ó5ñÕª»Í˜Û\åUs·Ž5m4éÜWžõè6^)$m*6Æ9ÞÛÌâ½KFøyçì“É <8 1ÇjôœÚ9ísçÅðƮɕ<ÏaZ°x;^1ŽqÐ.3ž?ÏZû7@ø[i*F÷ª+¯?u—°ìr{W®iŸ´ö¼G²I]”ŽTarFqÐÔ:ËØüè³ð‹%eΛ.²N8Ç®sÅvúƒµ [i†=͸e7`¨Î}°;cÚ¿Iôφq+%“³œ|²pÌOtìš'Ã]óʇ]Ó£¸[¨’DUŠ•ÈÁäôúVSªúŸø3ᆉ¨Ý[X]Þ¼bHÞY‘Œ§=ú}kéíö[ŠÌjéZ‚ÊÂ%ˆÀê&EI¿UpXÔzú)¿g_‡Ú„ug YÜ«_âH¿iÇà-kE…c¸ÒÖDŠV Óï4˙䵜\M 1ÝÚ%ÆðHóӨɯ¾¼+â±ãûßøF¼[¢M²A-ÌvbxYqÔdØÀ=GãPxïà†‡¢ë~ðýÕý…½»³½©&XäcµSòjžr¹ùƒ­|¾–êê ÝÅÚ– Ëí!Éè!»ñ^MðòàÙÞ'Û,ï¥Kiß÷A¾a‡å‘G`GJý ½ð?Š<}™àý>ëJQ °Î—@¯ÚwØ‘ÈùAû¡‰¬ÍÁ¶þñ>¥ý‹ ÝZj k,-o{µäYr¸*ü/Ì2G­iÏrls‡öð”ÐÝ4é¸Åou ˆ_o) üãïc½z·€?jý+KP扖ŠpÊ$`~ðec`OÊ[‚8úsžø¯o¥ ÿ¦wÞéz”n#W?9Î{cŸ¯Jò?ø~þÝu‹¿éñ[,¨.-&Œ®Ø|“Ä,ªŽ™ûÕp±úÉ¢üaðø›ûÄf;Këu¶f‰ÒA“±Ð`ø$Wk'Œ¼ ñCn÷q)ŠŠAÉ O8ÎTã¡ÁøÅ௎kà Úé±Þê(ŽðÈó”Q¼°8ägŽ}kÒþ ~Ж1x‡ûÆÐE [_)Îä‚9PŽá†qž ÇzN(®f~»ÚXAªÙ¼»ˆ%MêI2 €|ÛIçk ŒRx_ÁQh¶ÐÅr¦Æá™â?uR݆3žíØ^{ð×ãî«h'M³Õì¯ÒÂ,K>áŽààyaOϸñÓëúv£áÏY2é÷ sÐ%V ²rp;‚1ëWe"Õ¦§ØYÉý¤DÒB¬’|Ì/ QÓÞªßêÞÑÍ“ë“Î$¶‹÷Æ0ц•ÀþcŸjØÔÂòéQjº¨0¤Qá˜òÉ+.âO_jâî,ío4öR’DŽÙÙ• ~QLuº‘ô¦AÒjI¢‹0ÑɺöÜÎѯ—' œuíÚ›ª\A{\÷¨cøöDð§‚ui¼aûC›íGPfóþÏöi-ìÂŒ—9-€NvžÀWèÃÏë¾ÔSþËûOø:þ/:ÝÌQÇq*›XzG=@ê~hø‰û]þÍ_´9>"j©©è¿f@f·¹»óâÌpF²É»'À9¯Ï{Ÿ_¶7í¡éZ—À½5>|-‘Úyï’â+JåaÔÛ’ÂÛó¢cs»¶†ù‰GŒ—¼ìU*T•Ïѯ‰µŸÂ i—·¥§+´šu­€šþ QKB«wcçlzøëDø›ûBþ× &ñÇ·Óüh²:Ø^hkOªØdfK¡0&Ý‹ ÜË€sÚ¼ïÆö¾ð'Çý'ö˜ðü§ÄzäúΉ~dÀ’8‰<¼)Ã/%Ôü¬{Šðk»íJ}"ßJŽÑbûs¬ê‹ò%' ¸á³žsžõôY~G«ÉXœÅmê}ƾh‘xWá&–¯«$×$79îÎØêÜ“ß=+æèzÞ¡<~ mZkH­ã7±¥°W)2¿xéSXøÎê Iáûë"šI•€Ò±`:ü§ç#õ®‡V’ËTÕ4½GFón$†ClÍD“Üœ§P3ú{ŠúZhSÑEJóž¬ò˜î~" x 6:‚ês†/1†aÏñ'Ì:qX2ÜøšÃÃW ¡ù·³‚ý¨7™C0‡`x W´j:?‡£±žÂeµ4ÖòB¥*3ÆÉúãƒ\î¹§jÚ‰½yo›©n’ÕÌe…•Î⽀ǩ®Û·gŒ¯Œ¯×RNñºÓwÅ/˜•ãcyO˜g[~6ÒmÕoƒÛ>cÊJ•ݺzg?t’éz>§zôFÏsµ’ãxÚw6;zšÂÔ4 ܺÙêÚo™Ü¿uòã£ø¸ëÞ˜ŽÿL]2]2âÖîå-¡Š ˾ìîÚ9ã­rö·°j¶·úFŸleŠ"|‹–_Þ–G}¤+•Òµ‰§_Z_i“Ù]Z5²Ë$…³‚xç©äg¿©­O ê ý‘.Ÿmr.Ô1yĨ§zñž(8êžÐÙln.#ƒPÜY#bNc=WQ[Úm޵f‘ésÄ·6äJ£èÕ ˆ4‰¦·¸×ÖYá6‡ Üs•n¸=1[—6ZõÍÄ÷0Å„rÊQcvЃåÔ·\ÓNÀq·zÛj Û"òKµ°8!‡N˜튖ÌÁ jöwš{ù\O$ÚΪ#BˆyR0rǽWš½¶¢ÿdüA’ãûA‹±AÙ”?¸qÒ¸WVðÒNöw(.lífÝ#í?}O=WÔUÚéºlºoö´–qÃa ™mîÉr[¹ÏLgÈj>Ö¿ ),PÜAtã'Êâ5ÛÆN;qš×Ðõ-:HÍìkKìH³“µòœ…㎯Jí4«+PÛÝiÆf’&KˆVU$^^™À ?¼¶×¼-¦C¦Ì¢á57ŠâFPÀÄ²Žž€ƒžk«c¤Ù]‘cy%•唪#†a·<ãë\`žçÃæku¹–²Jb¸R|À~n2yúq]e•ŵ¶»o©ømD÷ æÅ}ˆY9©'*Þâ¢ö³sw©ÛÿÂCqnŽ®N!‘}rœ±ï[f©£jÚ ê¶sH·6sF[°GR zóƒžâ¸\i3 `ÊXT»wA‰À÷êCâkG¼Ó$ž($Q4{Œªœ:3ž‚³,ôc¥Øw—@ÖÖÎíTï²ÉWr:cª’O úUY5im›h×És|ìÓçAò•_˜ïÓ"¸N‹H¡ðäæÖgˆKä ݾ&èVAÁ¸àб«Ö­´«‹ ÊÊmÌÉÞô8ÇQ@­¨ø×[º½‚ÛÄ0Ú½¥«/–BaÛràŒœŽOZŽÑõ=kShašhæM‡y*ÛH<áGó¯+¶ñ%®q*êqù𤊑¤øÁ-Èç=èkÑü=â½XÕ#LŠ;)€¤’S¨Ü}hºÒõ»m:ÆM‚âÜ奘…Áù\NœŠ~³¨‹«FÖµ‹Û€—*‰ìÀ0È£ƒŒ|ê~•BÛÄž,y¯àU7yeb.Н†ä ž>•¥x´OˆVf xUYÒË <…Ï/ƒ‘¹{â€6á‹ÃSiërê—¶7Š‘¨pw1-÷”Ÿ˜0ïÞ»ð…œ‰°KbÚy› ò§#¯$äõ=Er§…ì®þÉáÛ…»µDó¢vl²¼‡å°GSƺKG–ÚÇRû,qý™nÉ,§-½§Sõàc  /ø)m5GœYÒUXNJHùùYzGn9­k2óÄÖ·j [{dó¶¼;`ŽúãÚ¸¸´ýZ[k‰5½RÚ ›\I+mBãîy¹8SÛ’k™Ñ¯ÎB]ù‹n÷ôí@»s­éqjJb“íÅ?w K.?¿ëR¿‹/ôËØííü¹mÜ#1<€¿cUl¼e¤ÅsamªØ[ÆgR€†<« ’F2O½_—QðV½u?Ø`{@²mŒ {zâ“`ohjÚ§Ù5/œ°ò°hÉéSÙë2Ëw-•šˆøòB„Ì|†ä88®KÄ~Ž}&â? ]AivªM¬ÓfXrAp@íë^I§é¿lôû5†ÓLžx$ ··‡Oîùo‚wg׊\Ì»#é;Oè7ºïö•Õµ´“éËåÇ*dÊÁÎ üë¡Ó®ô‹ý‘Íä[ÉÈh•'ξeÒã%½û6­ee§¤Ä»UšF ÉiùxúÓ‰4+;Û¸üY{¦-²–ó’7ß½òAèp3íO˜9O±­­ì!—6Q”ŽåJ•T 98Φ+šñ†o-!x´Øã#€árÝ9Uâ¾_ð§Å› ö|Eѵ+†Æë{Ò#‘ñ †ØŠöÄ5¸‰ÓÄ“i÷e[M,¤ )é–œþU\âå6`Òž=²\‰‘FL œ)ôè ýJ·þ=6é®n‰ÇuU$Ÿp?vþÔ/ïµ3@Ñ!žþm@¼Q¨]ï‘ÉzçÞµ{"ïÉ™Šýí¶î œáó>•ÁÞþÓßô§òüYc¨x{t™j´—nW'ÂñÏP êW:.‰¦,R½€·½˜°tIHXpÀô=knëEKM,\\NA þf þîIëéŒÕr^øåð;Çú’xsÆúeÝÔ'oÙÅÀ‚áÜŽ~GÛ•õ¯VÕ5¯éz¤6q¶•$ld•eÚêç   =Á¯ñ¿†^=³KøjÖi‚î7RÀ©0ÿcr`á}Ífè µ¿ Ù¶—àÞhö±r-å˜\F€z,™ @GãRØBê÷^*kxn®ž9­gFrŒ0sÑN@<õâ ÿ„ªú‹w"Z±\€£*õ-ϧ¼ ^ü_°€é¶º…¶«:v̆6r9ùOAǵdËñBûM·‘¼}¦.µ·=À&A´p qŒgƒŠ>‚>,U¸Ž7·[™·nf†@¨‡¾G9=ñW¬Ï"nFE ¨fíëM‚Ëì›î$¸Y¤q– r ™úV¬#SKVK²…°0¨xëôí_ÝÏm"|¦I”@U•‡†«ZF‘Û²YÄ#ó˜ìÃ;³ÆsÞŸo<+“tŠp ýyéõ­9m¯ã¸[[¤Ä@'-ÉÛ­Cc) Y ù¤ež K”_”çÛòçµG¨ÝIjë4IÏ>ÔXH$zž1œw­¨–غP9éíŸÓ4Ë8$vyådV<)ÉU Ïô¥q¢Î¨ë¶Éå±Dˆ“ Eq·©ÏÕ³k<í UI°¹öÍd ɽ3Ѹ" y<Æ äí ·Ó¿½#Tͫ餸'íaÓþ­#êzÕkMY€ºbIÎ9;}N?*’ YÝíÕ‰lÓèj„÷zLR%•­¥ÝÓ2“”LF>§$ƒÍ·6£Ó4ë•Þ»‡ÎKã©làÐnä{‰ä2<[¼¥ÇËÁíøÕ+K¹¥ -ÇÉ ãlLl“À=øï[Ö–7÷nþL[ÛÆ¥X¾Ôw©‘C —PûD“Æmá=¼AíÀéô¨ÛI¼¾·Ùy7ßoš@¸ÏáØŠ×µ°{m=¤‹Î!Gî߆îõ÷ªZÌvŠ×ºÔ‹J fcŒmúñRÖֶ϶YX¡7±ì¹Æ=I¯:ñÿÄ» éën³Æ%$…ˆ¿8îO@ç,8ãùÖµ»=ʼ1ˆ,´™\ꣂ*wE"ñ´¼iŒÊñÎMØcŽ98®’Î(âasv©-òªŸ™Iþ^õU4ÕšI.oc(RÙ'±$œÖ¡–ËGò§S;Ý`ñ®à¿AçQ&iI¬lçvû4 ¢S†Ü§$'ÒºÛXØ1–_´‚s)qµXãûÖf‹s~÷³x†D2\:”}ËÃÉP8ú ëô!øÑB\3«n\ ¯@n½k™ÈÚÄwéym4ö÷v¶ð¤ P)mæBøÛ€:0®%oµÑÍ­ô›7›™WÐvé]þ¡¥_¬‹{w <Û”féÎOÒ¸k;«y/g¹Uem̲•#iÇb?AV|ÉžÞêTÄŒùG uÁ¢in%¹ûEÌ ‰2†b}s‘´š·+-åÛ0’vŠ>7pûØ©ÊvÇlÑ\ÊÌ[nC)Èõ8éïV¶3.’Y|²B¸Œ—TÆÑ´r9ïøf£º™®âŠåà;[ æDü!Ðõõ«÷6·j}‚ódÎøÁU=¹$ÃcÛõ§ZÚ0–;YäF|TÈϰ#¡÷¦EÙwgsk=°c$Û]&É!=:œÖܱ2ZB"|ÕT/m¹Ï8?…Osq{eruMfÜJTìó#|r{ãÔ˜­5Í9í¿³ ·žty€Ø ßíì£Ó½™•¥_j %ÖÜ]` ÈÂù{†@8ëÇ5²÷R´i<²<–äå€@çºúæ²î>ŤXÓï.ŠK&;yÏô®’âýSÈ:c‡GM¬Àdþï·=¨,˲‡T†õÛK˜E òÊ70U=n8É­Kø®.Ì’Lòçt‘Cm“)ì0=óŠŠhþaج7ÌpGpOs[°h²YfW– 96r@=F;æ¤{ï½ÅéñßêζìÈŒ@a(íÀ¯r·³eÙ$vCìå¿}(m¬Äp2OO¹ŸÛO.•c –TfI?)ïŽÇ·=+¶76±^ì· ¢5ùËã-'läãŽþµ¡Œ¢î*Å}$¨–È”ƒ‘´u9êMeëšhs ¦ÙÃó# ‚`~u¥m¬î–U´”DÑ®LÅ (ÏÞ _lÔñ}Ÿ[—þ$óË,| È ŒŽø f‚6ÓCÒ´­jìâù¬¥Ö@Y›®HaÒ´eÑôû{¹®.buËmc¹N: zVž¨umUu F‰&—lH€8çè=‰ªZ…ÔêˆåE]£#©ÀÏ^¼Ði¥Æî¢Îx®Š€V6bŒu#=~ëoOðÓ !ÚHY^T® rN:‘ëéY0j×cRÝÌh†Ý+Þa‰äžÀsPIw©êÃûBÁ™bRV3¸|§<€;Z£ö¤ÙÛù÷0¾ýÐî®9+RhQ5[mOP‰U¢‹s¡Ã ø#z}hÅâ½oE}—WvW×I½¢½sw:ž«}%­ÐU[uŸpØ 0À瞟¥=´;Ë·–Ê’/0˜NÅßß#ׯ5RÇGÕ¬¦ZÎD“JÏ eÏ|c§áÆ*åµÖ‘q$¶:¼Ê»&?"È<”RrHÈÎy®†êÞ?]µ´m}Ü[<ª«ä0ðïí+ãø'wì¯ñ'Åš÷†‡Á߇wvÞ·Óà±™¼B5 6ºÑã’k¦s BqƒD„1fè=Ì+çÃÊ78*éZ,÷ƒshÑE5俩Щ¸àóÉ=³ùÖî•}abŸÚBEuRÌ¥ö冂ô­K(d‚ÎíÔH^BöRyúûf™#iº,©«Å•£H¾ó•Ï'Ž˜íÔוPôbX³Óü%®êwcZCqå0ËmO,`±aœ·=zì Ñ4ÏÚ,~°´šIA*쿼Èà}ÐOú×1á‡ÕÒá±³Ë ÇÆ6(<çÐtëúXÓô=*ã]ñ…•À£O2Œ9ùä–=ñY„*_,ø{_´K;Óíñº²¨aÑðqǧjø£âž—m­5Þ’%òضààð¹ëÚ¾æ°Öü;iÿg¶¼¶7ŠþpºŒü ò1Æ 7·òÆ=*x/®µusǼåvBõôôöÅ]"j|'ü4¾¶ñ7‡ß\gF)Ì îC±ºâã‘ZzÎk­&¡Àòc‚…m˸§¦zñ?†>-’-qô² LOŸlHÛƒêÜq޼ô®ÿIJXÜ1V3Èf, œ¨'‚3é]1‹9dv^}0é–Ú„øÍŸ÷ʨ^FG÷}XþUØ^ßøNÞ;˜â¶{.O˜d>a8ù~nÃ=x7ƒµOE~÷qåBWËdÚÀ£d^§eã;½6ÍN¡¦H÷¡Ä²ß6>ó{MI§ø“SÖe:2K)Xå\™\«—aÂŒžçñ¯ZðÌz|Þ¼mzV–8g>d1ÅYHÜìÍòìQÎM|å{ãy5{ËAt°‰Öu– ‹Ñ‡ñ1ÏÝë¢Òn¼Cã;ð’ê k$÷ï <&Ô:ÇnHïN)3Ýn5mHÕ“û!|ŒÎ¾R#ä»7$gƒ×ŽÕéZw‹5 ­bv²TXD‘ÊeWÈÀé÷zšù?Âzî¼b3Ø[EfÖÓ¬—¢’6À’O_Z›Ä>#Ò<=«Âñ\“Ê÷A.^,sÈ9äôùYª‘ôÿˆ-ôˈ`²¶¶¸º·$$òÄÛL!Áƒò€xãñ®zæÝ¡ÕZÓJt¶··q…ò̪½Aàœõ<ó\ç„/¯(ðÉ,aV¾³ >ÕfGY@Fz‡QÔdÈ?ƒ*7ß ´í[ÂíiqpÓ§?»‰XÞwŽq_K…Ÿ4.x8ˆrÈú?Ã&†·³\ëð<…m[È ó(”œ|ÄžG8­ví ¯öm´kkmö(‚¬±P€Ü»©$õÍy‡ãHçÙmu¦6û{³mX@l`àÄž@ÀÈë]ÊjÚ$7²ZÛ˜®4Ä­#dÃ*ž pHõàö®£œµ©ø?KÓ㉄g|9Ûƒœ@GÓ5ìÿ¾+x»ö`ñAñ^šƒWðþ°ªÚ¾ØÎŠAŽáÀÆzFõ8=YMáOÉwy,PßXÊlg À4re0É"¶?Jø×ÇŸ ~|YÑfÒu ë;…¹Ê íåÇŽ7ã8è þEuˆÞ<Ñujwˆ¹Áo;¤@·ð¶ÒªÞ¬qÏLâ³ôïÚÿãW…ï#ºðïïát86÷A.­³œ}ÙUÎÎØRõ¯Æ³ qujû|-dšØû,ÑŒ9*Äý\ý¨?àŒžñd²êdRH©céòÿ0kñÿŰOíaû1xµï„ÚŽ­¥KvgÛ…œ©;Jóò‚ }¡ð§þ IñáÜ©añƒ@]^5;~Ѣܕ±üMo9XÀôØùí_ª? ?à¨?²wÆéG†§Õì­µ9TfÇQE´³ÆÍù·çóq^MgZŸµ¤¿íå÷jz1©–âþ rËî? >ÿÁXoÿÙÚââ…”^+ÓbÌn·0˜çÇ徿H €zý&øAÿ½ý”¼ymÅ­:ïÁwï(\O Ín¼ðZX”ªŽù%E~‰xËösý˜>1Z,÷:m³K&YZËê¹ë_ÿÿàŒ‚ïFшh%óãVbH?6ëÐzü¦ø™ÿ—øïðoQÿ„“áÄ÷V¯´“6Ÿ4±Hô*ÈüúŠã´Úƒþ [û8^Ã`Þ%¹Ô­­Ž>ϪFä·`3;–b=+ÓÁå­~ó#ÍmýÙéþkò8«Vùqxo¸õ_ÿÁü}¤\Ëÿ§‰EäK#¬"I àÂ6nú×çÄ?ø&íQàJ[4oµÎÏù`ÏŽxó>Lã¶úý„ðü“Ǻ;IiñÿÀ2ÂW3iL¸$uÈ”¦ö&¾üøcÿzý„¾)$Vz®ºÞº¸Ú-J3 =rÎ¥Îq^úâ,ÀFõðÊ´{Ç_Èóç•euÝéÔä}Ÿüøžñ'ïx?U—Dñ&•qiuÃÆèI–F9ê qŒ’8ȯô:Ô~~Æ_´œ¯‡u¨$ýäsÇå3“¼¯Î:+å_ˆŸðGÙÇ2}³Gµ}5Ëe/&x˹|Îz»0¾+an£Œ¡(>§-n©kÑš’?‡hï.£ù<¿5~-N`»Y"”z²ó_Ö7ÆOø!7ÃÝWÿٟ -Æ™¨ÂC þß0œþæãÌUÎ{7â¿.¾%ÿÁ?k?F÷ZNœuQ° .’–Ïpç‡á_YƒãŒ›nJézèyu²e=à~A›Í2Oøøµ*}Q¿Æ£hô™?Õ»ÇþðÈý+é¿þÄ´¿€]×Ä>»‹gS±”qÇV~µáºÂψº[mºÒ.· ¢y„}BGã_CG0ÂÖW¥V/ѣͩ…­Ž |Ž_û*ÙÆbºCŸ^*[ÜÎû"’>{“ÅdÏeug;[^ÆÐȽVA´¨8¦*|¾ÕØŸc¬zfð«ÄZ¶±§èÖÂ6›S¹ŠÎÜ£äf`ªH8Éã 5ý‹ü-ð®àχZ/…48Ú+-"ŠÝ %¼H±ÄL’©¸ž¼óÍ8ðMŸ‚×~<øÀ_‘F°â¿ño6Š, —›>ï„pŸw¶ÄLÎЛs&@]¡‡Zμ™#ITäŒ{úúÖžÖÀG]›1çMëäã,8;O\“ë_„ª–>ïà¯4ØL‡.¹ÀîGÐW;¨é æ*å¶õÛ´çüHê+Õ^ $Dln$t#Ò±¯-Çå·ÌAcêÿ®º©Öi“*}ÙâsèŠ$Ç÷ŽW×ñôúWå?üƒãü> Òßötð|èúŽ¢«>»*ž[ ¶GyÍ/?w F¿N?ioŒÚìÛð~ûâ¾¼‰<êæÓJ³Ü3w~Ë”õÎ!™%=‚ã©¿“_ø£]ñ¯‰o¼aâ›·¾ÔužâââO½$’–>™ìp8û‡;*¯ûGu|)õ}þ_™ðüM™Æú½'«ÜæÝ‹1-Ö9Fà}ªH^$$<Ä &vî¨ÈéŸZôeñç‡Ñv 釠çÍÏð:ý¨ø|nn¦;Zgo©5ZPqœ×c­ø¶ÏYµkh4m:Ã$ö¨êã²ÎFÒ±4]oû R]Cì–÷›AýÕÒy‘œú®FqQÔfcб§ AÁ#'é^½ÆMJ×þ<ôM÷lÿèDÔOñ§Åáv[C§À?é”+ÿ²š <«äÏÞ˜©#ŠGùaBÿîŒÿ*ô#ñâ4‘¨ìÏHbPà‚°ï¼gâ½XíÔ5 œpßý MÏ…”E€I«Pè———‰ag‰&“ ÈQùž)²‡E ]Ÿñ5™+ÈNɺZH»Pð.¿¤Eæê+~˜š7ý^ðGAàOØON×öm“ÅÚÞ£~rfŽÈÇ=ŠÆ1ê5üsYA-ÅÒÇl»äêªInÀÔ“€s_ß_Á?‡Çà×ÀOü&»Š4ŸÃš …È\ô“2·–9<õ¯Êü_Æû,™POYÉ}ËúG×pu|g?d{¾žÿé 2@?—ãS-Ç–Ÿ6>ñvýz© 1Dªc,1ÉÜrɤØeŽ9AÈ$·ÿ[Ú¿”œÕѤ]e£ “ tõª—a¾Ýßq·Ó%³÷ì—¥Ú[ø®ÕÚæ½{'ÚI†O•bRUÔ\I¸“´ƒŒ9{ÿðoˆ¼%á‡ú4Ú²k-€@×u¹ZòîÞK„‡y—g\’NyÇ=q_EÁ/|ikð&_ˆãUñ­tÓÌã|övò×$òz±ÎybOS_=ÿÁN\Üx›áÃ^BͧkË~Ù´§ÙZ}LïïÐüßþ ÿúÖ|ðö\ý³< ªxÒÛTø{áëûÝRÔÇ ×†ÍÕ¿—ö/‘<Ò1Fi‘X ªŽó#û_ÿÁ8eøCà[OÚ{öiñ=·Å_‚ÚÓ–·ñŸŠãN,~H5;Só[J3´± ›Á ±püoVÔQ)àš JÇÅ_ ¼iñàߊ!øðßT—JÕ¬NhÎõ½©ùYcr°Áö ý ~˵‚j_ Ï ¼Péž2³ƒ¾†ÀîbB3=©lù“‚Ñ0/pr6±ù‡öÏÿ‚zXi¶:ŸÅoÙÊÊH#²2K«øZ&.m[£Mb¤hA$½¿-TÊákóÃàoÁÏêvó|dðLj_ÃMáÙ‘-/¢‰™ÞtNX “år’FE ý“øïðçÃ'­?j‰fò,|iw¨ÜػκH™möŒ ìäb¿þ xãÀß üXÿþ)_}³W°/5”cÌš[ÉÉw”…\loÂäý+ëïÚö¦ñ/ÄOØïHÑ3((8äsZèÓ@\ª÷f/Jô]3à km»¹IåÕq¼ƒï^‡¤ø.éæŽICerHl`äpëÍkÌŒšêy¶—໫љ"uYÈÄpûFHGë^Ç£ø4·› ¥>ï'ž^kÑ|7àë«yâ¸TÍ2ª§ð£õ^xÀõ¯]ðïƒnlïàÔ$FŸ-°Ç¿,ÆçÁ5œ¥ar³É4Ÿ ÝÚ»ùÑ;Êm*Y½ÏN¹¯zðÏÂgrêVÅqåòD’K6€?н3Ãÿ5M:ÌݼG3~P~ø=È<×°ÚxMk漚٤°†àÄÍ`<¾­Üy5“™¤c¡âz'ƒ®®ï$ÓnŒ‘£#&<üÜœq×ë>ðÕÁSt‘«¯Ãn3¸séǽzÇIkuµ–Ò!åµÃI)v%FÀTðz kÑ´]NbuÙ÷ ä¤×Ka–äÌ'¶+6ʱænued—1Dí%¥ÂËq¼|¨£®pOO­{ÒÂ>úå½ö—¦O%­â,r¤°àƇ' C‘Á®ŽÛÁ_ØW¶Þ,—ý#M–&yâGܳM<Ô)ëŠô-(xOĶÊ4xä¶xÛÍó7–ÝÄDÀÁäõÍCW6‚Ðá…4k–ÖºvŸ ¸½ Ùhòñaså¹È&ÕµX kx,í-ÃyÊ7 ?zˆF +É=ýk)H³á]CöKÖaðÕæƒ¤j–“¼¨`¶´{_(—ѧ°ò¼pO<ù÷]ý›üK-ͽ߈loláß-ЖÝI`ð‚‡iLÿw1ß5úÉn&|jñ]®¡uàk-+Ä6¶7*ÆÊç̲O.F\ºÜañ*çxNücs^ŒžÒ_4NC¯LíUÀ#­~ˆ|ý˜uØb÷TÒôƒ'ãÍ—ˆmÖ5ÍS´·ÖŒ¡‰Ig$6Ë ÿ‰q¼œ½Ï‡‰ÎÒnu‘èÐÁ}©œÿ¿Øö™Ôt÷ñWíoõ¬;ãµðæ±Ú˱³å³´Hwìã`1àã“ö¿ÀŸÙÿAÐu‰ÿ±4!¦®­k4B-fÜÛ^ cù‘¦’',àœ€ÛAæ¾Bø¯ÿ¸ø5ð2(þø»áGįx‡ìñÿÄ·ZÒ>ÎéÆ7ÂÙh®‘€Ñ»©Ç×ç ÿ‚ |Sýº|sªü.ø©évþ'°ˆÞi2J YííØc-´Lw)mÛYypA¯ ¥Ã+ÔÛô=:^ê?E?hÿÚ‹Âß³Ö¤Ý~Ò:7Œ>j‘\‘¥ê^’ÛÄšt׿P:£?¾…UÚwó׿noÛ‹öµµñ·ì¤žðΗáU·6Íâ9l®l›P‚à Ùo'›7 ½WÌeÿs;GÏ~ð/ìu:CñË^ñoÅ)¦¼¸šóZ½¹Ø/”ÖþSIöqµ”ìÂ…'Þ«·í/ñ7]Ö#³Õï/´ñfe…mÒØ’ÁÕä ?wŸ›Ž¼×«†É©%ï-Nj¸·ÐÃoÙ+öJ„E¨èúÞ—ã}.yªÙOq<‰qsfÀ»[³»ÆÑ“‚vŒpW­{Ö“ãŒ2iQéKꌹšÕÌq1;|äQ̟øv®Ã$Õ/n­õ=*o\»y“â6óÊNHÎH'?ʧ_jÏ­¶‹uÉÀœÆFCŒœŽÀr}kÖ£…ŒÇ<ñ.JÌߗƲhšà¼Ô¬dh­$Ý$`ïl']¤Ÿ˜ØÖÏÅ} TÖ†¡¥« ¢v–'xöªþsÁÇzš£¦˜ô˜†¥yÝÛe{rp@ ©#¿ç¨XÞh6z\š•¢Üóvâ0§ …cî¯|W£%dyÒßS­²ñ?äÔY×ì$ŠóÍ/‘ÈrªH`g$qšöÍ3Pðί¤·ˆ4Èà2M ¢1òÝWSê=¿Õó­äZ^¡¡Å¢[Åöâ Ò;wnçtyÎ9éÏJãîôim?Óì‚8Žòó aÇ~=;U’}U%ûØë—)cä\D]ÊG¸“k.Ýà‚ ‘מµÇßYê~Ômlo/õnP;+ã¶HeÞrGOZó{ÖuxîôKÙ­|˜¼è嵑›€Cg‚ uë×ü/®Zk±˜u˜¤laLÖ±þñH  Î(%£Ê§Ð®ìîÒÛU´Šà«´–W û·1íE_Óœr)5kÚuÜ6SÛG ±@³ÜFänPôéõ¯hñ†–¶Ö¯¬E—©lË™Ù6ï 8bB}{׆êšÖ¿ ëðj±¸‚àL¬îÕ´€¾»HÄzÖ„œ~…'‡›Tš[y¶™@±:ü»ºàzzVͤQß^³K(Œ‰ˆG†,Ëü-Ü sZ–Ö6·Ñ\ÜÚÂ$šl²».ÝÌzmôÏb:Õ ;ûO·ˆnìÅÎà€vVM¸Éãåô4 #SNð冽§œ¬wvû·9R ®O,¾Ý«…:ØGso¨j‘LÁól#ù@ù±Ç½kYÕüAÄ[Uí­ã•L‚`7*Èp£ŽN;œÔ:}Ž­®Ú¶¯©éìmbs…L`“Ž0rs×4+"Ô¤–k»xlô¸µ8í!&yXp¬ry'>•£5ç‡õM6=cQŽHà·X†ÕÜ\}89ÉéSj:%Ö3xwK»žÚÓQuI`›• ¤ÃÓ­rwžg™&Ÿ%éŠêÞM±Ê§h*qœŽý:Ь»©>¡ªG.¨^íáVÙ® ¸cœœýõÆ1ÈÅoiÖ>ÓK[é{xɆxV=ÿ{ø˜ÿ dàõ®F[߉‘-¼×riÚœ&U‰–E1l2Œë]µíüÚ6­ TK[uN‘’ÊÁÆï›9\š•–¬-máÓ//Ñ…³2C–Áä°œuïU´ý;J‚ýãÐmZ‰ðãsïl¯U瑞¾µÛ ü=£öT„;^°>cŒ¤{†0}8â°à°Ð¯®õ¬LÖ7vy·Ù4ƒl®zq’£œó@r³•‡U6ºÜÑ[uÉùáò>Uu99åªý÷Ä][¿†Îc.œÀ+´&eÇÊ1Œç?VÓ ø‘¡ßÌ·:E»E2·¨ ±8ʱäcŒç­Yÿ„Öã^¿“DÖ¼;©iòÃÊIqÉh}ÒÒ~R{ƒ@ìa³[éÚ¬2à G´)‘[&FqØv5gHÕücÅÕï‡å•mbViÑÈe]܆ÀÇ$;W_­iòê×\ê:{Ë,xìÍæ+ʸì>`¹azÏ–ëÄ:xD¹³O#ÊØ$A±fß‘‡# a¹h(½,Þ»Òÿ´ü9k-ò[ˆ¥µ‘ƒÀÑ÷1¼§>™¨úv†óH·Š/ìà¬b2y“©Æå•ÉÍjÿÂ/usâ«M_ºbîUoµYâERÄ1àƒŽÇ¥7á?Ä…þ'øÓÿ ¿öÖâð%”ÎÿgÖÚÀ¬R-«!ÜÊÑ"î"V;TŒu Ú­ þ µû.xÒÎ=CÀž?Öu¨gC5¥Õ͵Äl§¡]‘í#ÓŠüS´X­ôû¯ ë…uËmÒG%¹F ‚72’ØÎz÷® /.…¢Cgá;k [í"ÖÚg¶ËžŒ¥X¯×5ÉÂÕž´êXÞH/‰»º·üÃP¼µ-àÿ^â0ÕmÒtfíÌ;cÞ¼G_ÿ‚x‹Kºÿ„ > xvO\Û´Öš;‰ žåbÆ÷ˆ»³0PrÛSükò·FñÆ é²-е+8ˆ3²jS£€È`KfãýÅqž,øùñÊç\ðïí}­OsãO ]@bÔïEÌÑùA‰ðqˆÝÁ ÊÄ\2¡Œ¥¯=θNŒ–Çêˆÿa_ÚÃâÕ5;æmÃuËCòŽß0è{šÂ×?gÏÚK– «ÿÍ5¬ñ¤Q>–ßl€¤}>hðG'#zׯxþ Oû;?‚´‘ñÿDÔnnŒ[.î4Ø¥4sFN^KhÔȪˎT’Okì߃ÿ¶ïÂOÚOûgÀUñ„縌5­Ö©¢É6鸻Dv¸ù”áÔv® ™¶*—Ç£xà©OTÏÃߌ~–Êôéþ&ÒüÙì® ^é×%íX«"ü­0ÀÀâ¶§ñ\ ™tÎÖËì*¯nb%‘”ŒíçÖ¿X?oÏ„’xÃàî—û@®°u»6Ýlõyí‘ci-·dH¼ŒØ ñ³¿ùû3ÏðOâÚ¦“ñ3U·Óã²Cq DÂná ”¸`1Œ¸'#¥{|Æ5)*–8*á\fâ™ÎjrÅ?‘,/6—, cgÆìsŒ÷íYSjž¡^øh¯•p¾T…ŽÖ$pÁw}ÐÝ ô¯Ö/þñ·³Õ­¼E«éÒ_/Ú­ÑîÖ?:" œy±òdí ž+Äï¾Á:läŸÁ_Ä jëZ²‰¤Ý#yªP嘅T &ÑÓœûSŽeNZ¤ÉxY-Ùð¥ص]'V±3Æ,–—#ýÒ:t#¥\ð¿„lï`h|+£¿ 3ªÇ9i\–fǧq_¢Þý„>|Køg©øÓà‡Å¡©êö±·Šü%ºÃ/?,ñ¾ÙA#jùà*ü"mKSðWíUãûo‡òÙOzuìiÁ½œ«ˆ=@òßîí8oJ¸ãá$ÚL•‡}Îm®á¼itû‚·¢ªÜD»$¶”žT÷b:é\tZ„º·Úlu MÜ1îÑœ1ØÝ}ñÞ¾ýø»û|]Óf³ñÿÁÿi~4ðŽ«q¤÷¶vñ­ÅžöÁ•ü§1Ê™ù[fdqÞ«k°—Šü9¯Ÿ |;ñ?ƒ¼mâË2eê*ö72A(Ü]rÒGPJ…8<ÖÚT:±ýZ}ðôWzÍü­|=¹’i"ÛÕ”LÂh±÷Žìm*Fö«Wõm Òû@ñ/†MÔ7²ˆò~yaã*“¸’ &½gŲ?í[àË{^x¥µaš='P‰Â”' Ñ·×WšüBð÷Æ? ø^/‰>'øKã;+=22ñêºMºêsÉ+äÛ´’‚1Ë=ëXã(½¤‡õz‹VŒø‡ã5ž£§Üh¾°Ôì#[^@E°nÛ²]ݹâ½CO‹í«›ÿhLv7Ž|ÉíµA4Ð|¥~æÁžyë_5ü ý¤>|xðæµ{ðÎYnõK+‰î‘©¡³Ôm匀|ëi0ëœðHõôΛ¦}¢8¦šÎín.bŽ¡Jd}Æ#‚Ôu®ždG+¬xwĶ¥!IQ×Ê`‚ÿ„nÕÓhú¨µð­½žªn"X¡&L0 Nx^z)<š¡gj¶,²ÊŠp>äÛ¯SZ:Ýß„õµ¶]qí„p:††I0¬ çÆ9©kPjÇ ¡|LþÄñ3øWVÕæýøo³Ú_Û‚͈æN¼t Ôt¯hmzïS¶ÙÏa$ûã =ÄÊ€íë$AÖ¼;âOÃ|RðíÆ™¡Ánò®RYN 8Ž2í^,¿²¯Á_ÝÀúŒËvÈB\\4ƒÌÆ6œç¸'?ZЯ¼Wð/á÷Ä I‹|!áÙn<Ó<ŒÖÂ]̓œ œ÷$þçþÊ¿|/y¿á]7û) eØ,¦’(É<îÛ’2;v¯øUáx!uh<5ãZÅšDQÓJñ‚Ë»ß WÑ‘ÞR¯°>}sŽ;~åžøq§Áæý† ‹Ta²(‘Pnª6õ­½Á~Ñl¦³´ÓÍšÜ*ºÀdaÁìO#óªæ3Wø3¯ÉÇà¯k=ÂK½„’‹€ÊGÝP~_Cœö®çT°ñ,QZ Xmî$€¬r1‘ð>ñ?ÃÎyéX+oªhú‘iWRAJÍS1“¯«ž;VNãoÇ¥ˆuƒ ²L»#ÛÎG«Ï´sÔ]k)ý Þ•–Þæ9Þÿ);‡àúÖOˆ¼-á½CÁ·z7‹×ízd¥x3Ìþ!ÈǶ)ÉyáíFQy­iÞeÜÅØ±ÏÊ8ÁÿAW.|S¤Üé¢ sIhíœV;y¸õ¾µæZ/…´­éu_ ÄbŽA´Æ²3Ç…ï‚HÎ=문º+a¯a"Äöøù\pYO8µ"¹×XA©Mwq9½”‘(`€UNÀŠ–0d¼(³mÁÉÉØÙ1ïéYš¤Óh7ÞÙ¹v˜?˜˜vœsî3Ú¹íkÅZ…憂,Í1›æù@ c¥sÿÑûó0] H ½ÏãSÙ[ý…×TH°¼ý3žõ–4JêT]Vy¶Xxwäs[š~cj†êT}²œîr1Ðçë_Ïv)½È#{‹ÍÍ$æ$ ‘€£ñß´¶UÄÎñ“Áàãëþ­x`·/$PDeÝÁbsŸÇ½Ftí:Þ6ycÁ^AcÓ¥Ker¢Eƒ+OÁ<óÆ=*Ä76pG,ŒÂB0§k äûéR}ž3ôŒ— H‚8íV´ýÇ+u-’ɸ+C÷Aõ>ô†SþÕÓ$ŽO²©i°¿&1Ÿ¥MÉrXaÄ‘($>œÃò­mNkX£Å­ž_•ÈãžíúT:lcË#&7uÁ I\œŸZ•Éô›¼´‰y‰QB¤‡ §ò­lí ‘-æóœ¾w…*¨Ï­2âÑã·.$Œ‰wämàþb§[K‰HÏ ¨ÉÏÐu÷íI²Èâ[Ku’ë '–CdÙçŒzàÔ>§gj­æwí/ AÉÀÿõUÓkc¥ÙFÚƒ#FÅQS2dã ô÷Àª7e´72‡w`6‚¤È7:€8ˆ¿m¼!gV¶éîòÒ‘·ŽÀ/'ý«àÿüD×|[rWQ¸k••„1XðO§ô¯Jø¢›5YîåŒüîÀ•v;wÀ•àV–Öži1m”»±þÜs@Ùæ]'Ø\Ý tq…ã·>•^Ù5;’甩`‹×Ž£$`cÜÖý´l¯¢ˆjÊN\ã©ô I2Zý ¼‹Çýè`Egij%´2³”È ü¹Åh  P®ÎÜÈø>½G¢ÜÛGm%µÒ4(¥‰@:±õÏOzØ·i&uŒ&.X6Ÿáýk1¦&È3/›$€²«.0Ôö>€V¬[$‰!s <&ã’ ôÏò¦ÜAsi q@Y™¹òþó¶O$ã·¥n–Ð["N3€0‡¿~¹¤Ù²ÑBIÞh’ä)¹fm‹(¼sß§øž+¶°]N Ò\Hó½ÑVòƒ´‘÷r=*¯‡ôËKË—šäaQÏ9ê=urÁ.“â {7xüÆ;™“©ãîZÂr6ŒLùf·²Uµdurw»ÃÉu$zö®ƒUÔô¨ é©4m )ÄŒ²=qÛÖ¤þƶ¶„M YbÛäž÷óÎ{Ö¨‚-aäÓć‚ûOÌ=Abp}«2Íoí$³’ʤ†åæ¸9lt ã¡Ç¥qVVñÃu-»ªªHK8ùFHÎ{]<3Ëuyç_|»ÀÚpxÿ=ê¥ì:-æž.§h侉¤ c‘èäŽÄv4¦Ÿ5ñÿ„“ÊD‘b·.HéÈé‘ÍEcÛL«‘€ ÚžÀzð3ZQ›F…l¥E$ªƒ”œŽè=*Ã\]6¡æîV‰þBÈ8ôíÇ^*âCfZÙŸ&{Ž Ë3îaƒÐÈõ=ªmSL]Jå–Q/ ’¡Ãü*à´{Ì ƒ¸:™7™èO§Ò´ï¦ŽÙ[BA‘ý~•|¢(é—iöÐÚIu$ê£å’n~bzqœqëÒº˜EýümHâhÈ#‚1Ðþ¤W?§”ÔV;eW„³|ÊŸt¿SŸSV-"¼]FhY”Gïl§¡ÀíõéI¡£ZÊÝ£ÊG’λr ½½ë2Á屚Y÷yg'vz1}ëJ/¾À×%ÈÚç!AÆ1´Ž~¾õrÛHþÙŒ™ZF¸y?‡9Æ>ø¡"Ê‘C=åÃZE¸67…UÊœú‘íÐWÐ ü¤\¤—÷? Šˆ d"×ÿ]ðÃ;XíŒÚª±àye±ÐúqÇZ÷D¶å¬W÷Q¸Ü0£Ð f«••‘•¨ézLrGon-¥Cå(Œn/<úó×Þ¬\5µíÌW׿mÒ.àŽG_Ó­jE5ürG5š µ`$]òßn;}jݺϫ-Ä«(& ]àî#‘ŒþF™‹›fEõν…°² ‹ƒåíXÊöëß=qQÄ×VXÔ5Zä*1Ùå?'¯¹«/ouö÷™Çá~\uãÁ^•«¤éÁEÍÀ6Á ®å$c€3Ôw4qiqµåÜCÖ ¸±äaïÞ­iZF¥©h¦þÖH¯‚v‚ ð¾„×iÿ •g¦%µœ1H>g¿ÏÆFNæ³¼Eâ­o^µ·,æhR3 U!0Iù<ZãuýFkÉ-ô…±xÜmå©TƒÈlŽIæ·¤·´»Ó&Ѭ¢x·L†2­‡M€îzÀúþ¸­ ™/eÒ£/<…]3ål)i9É'ñÅd@ÎÂKwœ“)ÙÀã€O±  ]UµŽvÒõ[Hâ%ca]àp À,s[êö×râ‚ÄUTuúàŽõzgÔ·‚F E´å‡BA9þµ‰F»¤‹`^8-.K;ä,Äcœö £Ã¿ÙŽ‘=×›1LˆãEBŒÔ°ÀÏóª—V:+&•i0}’#&a ú{úãŠå ñަ¥šèí¸S¾@`'7§qYºEûÂ@±ØÉ4¡‘‹–qÁùAǽvóiIª%¶Y-­÷81†ºþ{ÓŠH,|5¦ií [FBšYIß´žrxöôí\ÕŽä“ÎÓDÓ+–ùÁVòG?)=OµcßkvÖïn ÀÙDLÙ†#©¨%ÅzÇŠ,]>Ï¢@¯n0€î`ÄŽ2«ŽFz[;FñÞf Ó.@[`Tàgœœw§=Æ™©Ü cD„§•±pç*ç§aÆ;U”¿Ö`¶“Q¹TiÄQŸ»;íèsëœÐ &”êv~½]™XUL Ñ¿„v­s7Úæ¤–M-™™”pÂ"@” ãÐVÛK©Hæ-JU™.<½Žr©ÆJí¿Ñ·¼–ÿI[kv&ÑYLÅ|¾•ävõ ¢}BÞçÄ”÷Z¬‚Í+ ƒ‘œRi>²²Ó—K½¸a5ÉR`‰·1yÜùãv¬ø|?¨^^Þé­1krKt&?“hÜAé¸çò®þÖ.ãEŽ]å¾Ûd ·vìãŸBhÏn´MEuˆ£¿˜F‘)e;wɳ¿'¿aéVtö+«™šr<â$Àó>­ÛŸNõéMcw=”0›R×-‰d †eï´g¡ã‘ž+’þÆþÚÒî¯MÏb!¼¼€îÊxLðq@]âÈï4»)ô}F¹»’Þ)Ùƒmw ¯>½®#à:é¿?àÞ*¼œ¾ñޝ<PòŠË¤k$ŒpK+g×9ï]çŠu?³øÂ=--Ý^KNU˜7 þµÎÁ3mdñ‡üoö¬ø/bbHt?øŽ+tPË7¶P_ÈÄyžfaõ½Ì­û“ô<ÜgÅæyg„›Lm-4¼o9®•'æxoç9íô®ŠMÖýÑ›Êñ)‘³óØÉÈ#®+Ͼ[]Áá-:k@ͲÍ$R:˜æPø-×pð®óNŸLVŠë®r Ï#$ôÀ¯.«Õž¤v;4°·±¼¹´2Iu<èamŽÀŽPFNkÒt? G¥xr]vo.V¹VHa¸‘˜üÝ[crÄ.pOC^)oâxÛÍÑeV—ÍÙ+²–Øá¸çêšmåÄKfºÅÄW¶9 —eÐäãëÅcÌÆÐï]i:Uœ&-®ITFo˜¨ï}q_øûNž÷K’âugËL$Ir¸9Ü ÿ¾GÖ¾¥ñ‰ªêÓË®éï´2¬_fAû÷–<SùW’xÏJþÓÑ$ºŒùi漘äfÀçzÒ‹ÖÄÏcò3Äסx£DòÔÇ-µð&b@ vú`pqÞ½ŸÄ¾Ö50øÖt†ÞÒâS»*vHÀ‰>lûW™|lÐÚÞÛÇ…¬¤.‘°Î"U£Þ‹mir «ÛJÏêl(¤ö9$×#«øKÇzÍ—“s¢Y­¡ ¦ÖÊ@YAãæg9žÔš°™áØE£ÉÚx½º»”GçÍ#EIÈCÀþÓ)î-¹~A÷T¸ê+Âü)gá8#MCX·´º’âXW!‹MÈÓÐÿׯ«t]_ÁZ ÜZ…ÐÚBÍn©7DVlŠÃ$÷©‘Qw0,´»¹á»Ôex…Ô7™Ò7qÛS÷àÖ>³¦øjùí¡¸¿š%Hó'šøB‘´ó’Ç­qz5Æ©áÑquå·‘4Û.r;G Èã'ó¯gñθÑiÒ¦Ÿ"Ë›½À11Œr©Û¯:ÖOrŽ-´­ iž^$†ÜbY˜IffFòØy#ëù–øûðFØ»ö”×/fŽÒoêV©hå¤*fûd¿=º áÞÝÁÈu Žkúlñ̺®©€¼ŠúØ9\ÇËH äyƒ¶Þ€žµùûûyü°øßû?¾£ÛEÿ /‡'}WH‰b,ÃÊÏ ·Æ[€zôæ½<G[¡Çˆ¦¥òÆçS·–ñ,<;yö¹ü¡4ke‰J£c žÄg Ù·›N»¸tpa›L‹}ãÜ6ͪÄýïRHÎI¯ž~x«áø«P¶ðOˆµ}CD±‚8ëR·K&¹•˜32¢€Ë?ʾ~9È®¶=GT¿Zxq|ž"™v€pM©ãa={<ÕîGSÈ’³±èº^» Ås­ZßK¦­Üf;ráwJÓ=x=C^Uâjæ9%×ïå”F‰“ÑB’rzVv«%Ö±vu_9®<½¾Z·T8Ú¿îŸJóŸj‡{¤f–S… ÷Ðûþ ŽoÄ:ýÍ廬Ž$ ‰ kÐúg¿×µxV»âBÂ8æE¬ŒcX£N yA'=yc]Ž·¨K öìÎ]¡by'>žÕáþ#Ö`KôYs±1ÀäÔÿ¼O|ô kML½CÄ6–]Ý’F0]JòØ#®ï\ÿ yþ­¨=Ñr±ÆDƒy\æ ¢‘Á4Ïk\ÜýªÁŠpU"=AãqîÆ¹[ËØÝV+höä ’~b{äsCK±~Ñô>‡øGû]~пgƒþOŒ5-"8N~ȳmô0È }p¿Uþ ÿÁ{þ=ø.´ïŠšž¿ ¿Ïqi!²Ÿiî…¿Í~0RÇŽ{Ô2[,s^6aÙf=[B2ó¶¿~çn6ÅÐw§QŸÛ—Á¯ø-ïì{ñ?e‡‰µ+ \„Mvܱã¤Ñ–N?Ú<ý+ïÅß³?í¥›í94¯Z”ÝörEp§ d©C“_ç*’:¡#é]7…¼mâÏꉭø?Q¹Òï# ¬ö’´GÌ„øäWÁæeÓ¼ðUeMýëô=ú<][jðR?¼ˆðMÏÙ—âmÔ“éñ¦™vÜ‘»hÜÿÞÔúô¯Î_ŠŸðC«+Åák¿µl Ãæ<rFr+ñcáü«öÚøD on[¾“¬À·j9ᇗ Ï|±úWêŸø8Râ%1|[ðL‘¾Ñ™t[Ñ–îD3íÛþèv¯©ÁüW–ûØG´KÏüÎøçf#ø°å×cå/ÿÁ+?i„7Rkž [û‹¬štŒ0{éµ°=úVNƒñ¯þ {û=D–z'‰ï®m ‘^í¸Ó—PÙÿ~5ûûð¯þ EûüEX^ø–=ä¨Öa{Eùºþñ”ÇÎ~jûcLÖfÿŽzzjš Òµëi— -œÐÜäs…$ïŠóq—ð—üïöø­$vÆzh’nD7Œªp{˜‘é^Å7ÃØ—ãC èl´ AçÃy°"[ÈÞ„4\þ"¿Ÿ¯ŠðC?éRÉ?‡ši¢ †HO#ÜÈÅ|_âOø&‡í3ð®åï|){ybQ°¯’Èè7DFOá]t²Ž¯ïeùœ©¾Ï_ò2ž+ +Ðæô?©]{þ ›û1xšVÔ4všÐŸ—h–;èN{m¹ ~¢¾4ø‰ÿøOâ¶{Ïêv–›¹g±kU;ŽH?g‘A˜í_…úgŽ?à©?”áø›Wž 2—$©r0œ}Û„,}ðsï]ÿü=Wþ à¥øëD³¾Œ(W7Vs¦G~c” Ïr¯cçô¿Ü38M‹S–xü ô¯†kä~¿|#ýŒüûê—:‰«[êRGæ-¼V›–($¸f™™ÙÞY]TF¬ÄÐaG&½Ò7Ç+«e‰À<'ÿ­ë_€1Áh¾0Ùbð7†ÐŒç‰$÷$±&±ï¿à²ÿ´´î_GÑÀP‡%(»v±ýÈ’»…a¼`W‘Çó«öú±¨öÖ·tù’2F?¿—ŸÿÁX?n˺ÇÅpèè~\i¶ñ zeÒCƼÅŸµ×í_ãÙÄž&ø®\c±Þ½²~P˜ÅV S³­^+Ñ7þB«ÆÃMŸ×v½lÚ%¹¹ñðéÑã>eÜÑÁ×qfÅ|ñ»þ û/üµ’-?^‡Æš¢ a ¿™þFÙ.XãŽq¹±ÐþbüAâÝ5ÿˆ®fÔnƒ-ÄÍrÿ÷Ô…ë\¼ö— é#o`œ_Y”øU€ÃITÄTsk¦Èñ±\[ZiÆ”møž÷ûM~Ó?iï?Œ¼s*¤ƒŸa~Íeoœˆâžz»‘ºFå±… óÍ„Z|÷QÇ©Lmá'æ‘SyUïÞ£aè½)ÿg”ƒÆHíõ¯ÔèÓ…(*tÕ¢´Hù:³œääõ=%<+ð–OÞMç;N!lzd63ïÂ’ãßoâK·E°Äûsø×›=¥Ëÿ*DÒ¦’3¹Õ~¦´æ]LùeØvµi¡Z]0Ð.仃³K”ß–MnxY~5»7eÔÒ]ß(±Ž']¾æF?…s»"ùµF ‘ùqúR¿ar¾Ç«ÈgÁ“ø‰½ŽÕöcD? L¶ZåÐäÞXbSø€Íý+Ì¢±¹. HÙíS¾ŸÉ•QNáÊûäúÇÂ3káûâG÷ïF䵪k^¿C‹¡­‹ŒbFº’RëÁÀÍskk†ýë*ãÕ€þui­¡ü²Ä s5Iý O0ùeب·mt. ;YzÞѼo¨è–Ïkmki+»—3OÉ!ÈÆ2N1ß§Z×ð·ÃxÕšßÂz=ö¬Äõ³´šå¾Ÿ»F¯ÑÿÙ«þÝûn~Ò…­Ü>›ÂZ ¤4ºÇ‰ÓàŽ ~f7ïœÈ]‹Ÿï áÅæ˜L$LMXÅ.í#¦† µYr ³Î?àž¼IûT~Õ~ÐuA·EÐî#Öõia…RÒÅÕðp&IF3ÉjþÇeou .æPŽX©éžÙ¯ý›?dß?±wÃÇøOðŽS­_ߟ•ãÆFÉÏ\vô®çÀwñŸéÌÙw7q·ìßÒ¸íÚC½•¶˜?oj¥sãKO ØêÞ,‘‡“¡iºŽ \Û@íŸÎ½L¢ŒªâéÂ+v¿3—52~GðiûDx‰üUñÏÆ>".íÚö«q¸t&kÉŸ?ŽêûöR}wZýŽ~<ø{@¹k}CH²Ò¼Cg$NRTHex®ðAn„`ý}kóräÈYe•÷´ˆ²3’YÆãøä×ÞÿðM¯èVÿ´Iømâ´WÒ~ hš§…®ÕÎÔ"úÑn9~ò1‚9Éã­uR,{#ðŠ®òlðÿ†'N°ðñ±¼%[‡o8&3S…Î;÷æ»mGBðª\Ì×%Ð&Ó„èHêAë_,kÚˆ|âýGÃó¼–·úe̶sí$2ÝÌož¼§ ×GásÆ&×,|? ìŠo®!´5Ucç¸N¸Ïzj:܉=,~ŸþÍÿ~2ÚxwMýŸüâkíÏW¿ž x¬Ò4hþÐÍ+æB¦A¹€a»ãŠâ?à¤^ Ó´ŸÖô“)¶ð'†l4ÄIY¤>Dk©]ݲ^I<ÄgrI'ß5õ_ìÓà+)¿k·µLZèrÏ*9Üf“ìÑ’ßL–=M~x|i¿½ý ?nvÞà·—âo&”›NH·­ž8èQuþèÍRÜÉn{·ü+áÍׂ¼ð"Þøs€a³“¨Éµ˜¹”þùŸ6•ö”ßøãwCžÕú÷ÿ[ñ„~#ø³á‡ºZ;x~MÌGÊ¢êãäçýØkó&ÇÀž ñ&—s«Y7—e‘$̼a~ó ÿ黿jv(ý’ÿ‚þÝ÷¿¢³øñ£PòüS`ªº.µ3’÷ð§g¹'ïL€b9²L‰ÃäÍôŸÇoƒ×¾:øUªø3àe­Ž›¨-ÅÌ „Vi¦Y‚Õ™X=M=¿>k¾!Öc{{‹‹TŽ@ÐÝBv¼F" 6„# ‚Gúµ¬þÓß´Ù:÷Sñ¿ž)Õ%ŸFÓõxO–ך|J÷æ1®òóѤäiIu@~r~Öž.ÐàK«‰¦ŸY´”n‘cI6«`€9aõêúéîb†ê–KeF$¢òÊnÏ\1é^ý ü#šîÊmsO“d²¤S™¤*ÊÒ‘Ÿ¸zsÛ­{Î…à/écOK[x£²–ú(%¸Çî(8ç¯zͱ¤x7‚þ[Ik;\ÜýŠáR6….c.¯É 3ÆN9ý+Ò¢ðYÓo'UEx%ØÈª‡æhùÀã¡ë_Eë /ôÜh:±0ˆ ybkiVxÝÔîØÃ?(ÁäÖŽ¶º…íîl÷C{{<Ky (Ç:tíYÊEržmᦫ¤ÉtècµT2@˜üÉXó´ŸáÖ½ÛMðÒA¾ Œˆ EpÌî³°ÉÆGj—Â:%Õݵ¼^2·I$ŠÔ¼flÆÀc$y¯½uø;ÃÞ,Õ®uµÍk,1$d“ÃĺDšx´ùÛËxt~F ÇçíKªØehº…Ž­r?´ìöj^mŒeVR£æ'hp¼íéT4èü?á‹-ã’_Þ…vr„ÉÀ쾸©t/‰6úFŸ?…ë@2¼Üþ½|2ý˜|7ðSL‡ÀžÒ×Bð…»{Ö²´i­všI¥bMÜÄdZ÷‹^/´ø áÇøƒãŸéú†-#a"j훉¤\¿•ðÏ#öÇCÏA_”ðÔ?µ¯ü¯àv»ì¢Zü"ø}µ¬õoø·XŒÛ܉NÙ ±C0˜Fïv¼y'ËYQÃù…‰Çcæ¹´‰è¨P£µ6¿à ¶OŽÿgíWÁž0ð¿á߈n¹q6—d ˜vÄÆW’â”TEt XcåÎkò×ÂÿðRÚ‰ü 7„<)à¹tí+R¼šÊ=~ÏQ]y²eÆ2#B~lŒŒŽ95Ÿà_ø'ßÃïÙÿã¯Å?x·Âÿõ-ú-PX>“=¾‹©$@˜Ô™%˜È©+ 6¾T4jpkO〾 |@ñ†½ãøOKð¼76cQþÎжAm ‘XD9só“·ð+ìpym*pI«¾ç•_{¥±ðïŃߵ¯ŠuÝ?ŽŸ¡×o®!"ËûRYu;¨âùˆ¤…tèNÐpLÓ|ñöãø_ªhÖþÁ¿µð©»‰?ü»¶–°–ØË#y&KR¤<낌PA9n8çŠ÷iE(Ù¨|+ÿ ñƒIÖVñ5„±‘¯s§Åk¾9  —Š‚ØR:+±¸ý˜tÍgQ—U†;k Ø.üËy­æ1ǰǑ Eã–9`ç ô­yQšwgÛZw‰¼+âÛKm3GÕcy<Åž(ïç9ù³Ór+Ôt8¬¡ñ$—SÞÛ‹Ùñ… Fq–üŒw¯Í+߆ü+¨É}°²Ú>CÈ0c•@fÚTjô¿ x—ö„ÐôÖz,~"‚áIk¨­ßÏ™_æ\À3.p0?>´r¢²üI ÞÚ]>Ÿs1†8±±âÈ9^r}G<â¸9¼M›§ÛAws2¹e0œì)!9`q‘Ç8é^oá?Ú§Z“IŠÆóÃ/öM² ÈËâh•a©xœ\ñƒ^µ¡~Ñÿ ¼]·Fñ4éÐN@D¸Œœrp_#çfš3’ÔÆÃÒë »¤_J“íù ¾ä”/ð2ôÆ?]¥¶¨_û^áü‹–RߢÚI㑚ñŸø·ö]ñÀ1¼ú…›A#yÓé.ñÆÌ~^yÚAé¹GJãõ‡ðÕ‡†®íþøë\‰­­µÞÙQ.#–2Ì0IÆ1žEW3*>¡ð.¹qáýNMVò8¾É?™ÛÍû¾ã.¤tÛÎ1Ö½?PøÃm£ÞGIÓD|°É .<ž‡9ê}ëó‡á¿Å‹8ñ奷ÊçNš%‘ä’1Ñ"|Ûà8aßñ®¦×âßÅU<)§ÝÂ&$µ™pë¸á¿rŽf¨ý‹ÄÚ&¤.õ=SR{ #bÆÁ‹&ÿ0àçi¥y×Ä?øY²òì’xÚçF¤bUÎçY9ܹã׊øÒëö“´û…Öôo°É-ÜʰÏ*ɽÓóÀAæ¦Õÿiølôë="]Úåì¥hšiIPÊOR:Ž8óZs2lϤõWš]Z©¸°†-ÒX¸e=·gƒô­QßÃZj=Ë1¦Œ,Ò¶N«=:ûñ]'ã>,Õm¬üh Óâ@Í,¢EòÆÞ~e>ŸÊ½bßÃz´~v­ŽóM’<Ç$dL¤/!TäàóÅÌV%ñW‰t}JºÒSw °´ey‘ÐýòqÆ03õ«Z‹YÝBö³4qÈø œ))ŒN+böëO×tï³hªépŠ2H»€9 Ç'·¦^h@;ÚÚÜÈÁá2 $WžÜÑÌ~&ºS_C~«wùéæ Êäuú~‚©\èŒ$"é-®æº_9€”CŽ£‡§½sðx¯Âºå–¯aáyRmGOqjñÚIõa¸pFrGâ³bÐîVöKø§¼Ñ¯–&/4®  ÆÐ#§QT©kžûg—mmsç£,‘díV||ÃÆs\riž%„ 8ثےˀØ÷Zéì/,æÓì/äŽxì®eh¼ÇNDʶ‘÷è}ªˆ×¯l’Ú! ëwt]"˜±)ÌlpyÇ>ô‰gàŸé>¢¿‘Ë‘‚ä/|da[¶úÌíÄ6è…èâ6\#Žõüj®o¡·mþáškùÕKdî~G¥T²ÐažK›'»šÖÑ”ùÏx#Ò€.øJ¸µ×Jió}®Ø“ÛÉÔ6Ü6 îN:Zì´û½j.=ìáÒ4á¼µ9'Ü“Æ+®õ(4sq¤ÛI5Ô0‚V\üý•’qÆkªÒXê7QÚè:ˆ·fŽ)Uï³û¶çROSÇ¥K`y.µ£ØMâiumÐ[ýšvb:]ø1ärÊÞ‡Œ×q©hÚ”ÖƒRšëÊAÄH îe<ã<Æ=}«FËLÖ¼Gâì[pnõÖY#ŸýܸKµ»)Á9éÚµG‰<3m«Zxšs™Uᑠآ] HÈ †äSL:ð|w·Úìúüú¼\$‹ Hçh9ãvÎ1]£©ëž"i”CŸ5ôŒd¸†%.UÆ8äò:äu¬½SÄÚ~სG%­ãÊ¡šæI—í €åN0>êñ€9®¥çÒcÔ"­2A"ˆSÌ8r¹#§J`xåÝýŵói²ß¨fùLÑPïÀgoq‘šëç·Õo/ Óu‹y[RŠ#$7Ÿ¾¸ôÆ8ö­ÍN÷K{‹{ ;N0Û^ÌÒÁ;¸9d;‹>RGQëW.|)â ûÕñ&§©Å&ä“ìñù™–4Œ ÃoNFqŒšþ²¹Œ^ê¶ñÄf”æ7;Š.Ó’¿`jþøW©I5Ž«h.ì¢B2Ìèë•ÁÚà’H<ŒjÙ¶ºÒᶆ)­æšÉ´™Ž l~…[¼´Ó4{HžÁÒâ°vNp cÈ$r1šRÔ¨ÊÄ?²oì ûë_5mã}®±©ØëzL:UãxŽ}.)çóp¾T‘3 LÖ¿s¼3ûü!ø9 ]|_øy [{ 5Ù¯´ùŽF%gUÝÔ”#'œ×âýž¡àÝJÒ9|O¤Û]$2´vËç2q˜ÆGBÇ'ƒÞº/|Vý¡|? |ys¥iVóæFÝoí7íå–œ¬1×5ó™Ž]R¤¯zxzéZçêWíaàÏŽz‡Áû}/áËêRëúaŽâ}.ÊHÚ;Ë—p¨I~tN73Ò¿õUø{oñãNðûWË‘ÖÊâ'‚\…Ì™YâPÀ3Å~Œxþ ûCø/Æ cñÃ:-ã’\^èÒ6—sA|‰7!§rãÐõ¯gñüoöxñÇ…m›ã—Â[ëk}BQÜó[[ßÁ°¥šX •y^ØÏZåÂ*¸uÉ(]ÔPŸ¼ž§Åÿ¶ŠÃ)>üQ¸¹ÖôMGMΕöÏ-¥Šx0"d‘W*Àeaó¨¼Õ ÓÂ[rE³Å ÅãçiXåÉP£à_>øI| -ŽŸ'üK£iQjˆ±ÏbÒ”¸·•V‘O3`cµ{˜u†Ç%$írôúm´ö\Hû..# P‚=1øâ³5¿xWÄ–qhÚ¶›o©ÃlÁH ÇlnÛì{©â½SXøIã'°ëÚ=Ͷ½FEšLå—B¨Ñ±ÀÆÝßpþ ¹5¶‘k¢w]ŽV1¹pFù6ça~½k¡JšÑYi-Ëš„­|wy¯ø.â ½ÚmØM$ŽêÀùžTN#ÝŽ§>µ¯ÅŽ£äx£F¿3x‹Nfš-FIäŽôŒ”·˜¸äc%H<ñÅbÙÚx¶Ü¤++ [bè¡a…8 3ÔgjèeÕ´vð÷ÚnO™ª.Ù[}®#RIãžÜU{~@ª4ôLúsÂÿ¶í3¥xQ´íoÆw—æáŒWj[\‘€Ú›‘ðF1“Ö»þ ûW|70ÙÚh>ñ&Ÿ +åC ÜYJÃwÌYÈ<ñ·¯†$¸ÓüI÷ _2"™r¦0O\“ߥt‰¦Áá­!µ;n¶Șe Ø8ã¡ÇJæ–_†dÑâ'ÜñÏÛCö±ø±ûBE§ê~9øM£ü=ÔômFW%Óâi.ÞÞE;mÐDeWÞ ±â¾?ý™¼Uð†ÎBß´Ú|KÔ#; ¾ðþ«6Ø#åËË’*‘Œƒc¦Ñ_¤–:´š~’¡4ù¸f‰ضqÈ,¬8§mØë– ¦Ác™¢d`Á>FOºFN0Aä[ºå´43U¬îϵôïØkö7ý­~ÇâßÙã&£i%ͺ\¥¦£¨†¹Bà‚“$ÁdFÎAFÜ28Åy–û ~Õ .OƒàÓ.1ÿ‚ŠCãé: ¿³½´šÍíÁ´±»kŸ³ÈÁrHŒÄ°d†n™ÍB¯Š‡ñ!qºP{3åý'Àÿ´YgÕô¿ŠŒw¤›­nôhY ^wm ?'Œ–ü+Õ¼!ÿ :ÞæÊëÅ·v:Û\Ħ‚Í-ždckÞ/ü ñ߯²IqÂB$S–hå†}ªÿ6Ñ*K¾‡QêÞÖ¬4 eñ—á·¶Qæ¥Ý¬ŒY RdܭηŽ:FGÕÙâ’Ÿˆ–Z¤°\YÅa&vüòä8 îëÐt®ÇOOÞiz–¥©Y[ZjVÉ„X®|Á#c(HÚ®GÄÿ<¤k‰áë}~ÃTm¿¾Yd+:ÆÇÝ]Žªé&ÚûSžÀµ/Óœ±Ç9ÏAØWde+¦e8r?‚ƒæ;B¨²äŒŸ5>^M¤úsXºt¾×|5¤®d’çt‰‘ƒ¸¸ïƒÒ±uxš hµ +èádbž{sþ³ *:séLƒ¶MvÛZ¹ÃWñ’T'j„Û°g㮦 GXƒU»·™’@ŽLBLïUÀ Jñ_Xê÷Þ.½Ð¾!-µÊ[Æ›1…ÜÜ+ç‚Þý9®óV3Ø è—--¬¥Dœ°*3ל Ð.T\²ñ.¥-èÐý¤3a“œtúW¦°ià‚úÎÕÁ!cg+ÜŒsÈõ®j-dišUæ«¢ðÛŒ"‘žO$¨êjÌ&Ó, Žòå…½ÄòíärŠGÇO¥ÊŠ£x«Nµ½’yâžÞæB…”‘,1áçƒÅI5¶¹%“‰f6ó.Ò²&Ú:ãß¶:U)%}KB¸½ûS ­Ã(ýÜ›¿‰±Î>•kHñoŠüYæxgV)okm2ýÔþðc†Ï ç¡-ìê³&ŽèBÆûd/¿QÏ#·ZÖð´·Z&©.‰g¦ƒ%·T¢u#qã$Šì–Úê×KŽ;Û˜™ ‰âgûI«Ú ¼xÆåL¢ïï·?¦i¶÷:¢jä”FΛX6œ®>” Ç5mñ{ÂÎË©Ø5«ÿ¥Zº¼j8(ã?…uö¾(ðÞ ëw£^¡'ª“‡#ô5¥¨Á|dÞoŽXÄ›^0ÏÓœãµyãxÂW–ñÝÛhÀ¹Öá3ǶsÇáÒXÿÒûÍPv²ÆÆOsž¦†¶µò•ÙϹ'ëP¬ÑŒ¢õ“ÜYÚ.œJìÊN?Ú¢{—²Ò•à…vvrXñ†öïRØËgt·ŽHÊ4LØr8PON½p:Ö5׈4-!žynÕpÒ™.Op«Ž¤WV¾–}*2ì,i”°áW,{•ÈÜÅ…äÚ5ŠÝ€@{àŒ1Æ7cÖ¤ ÷ñNsiÓ–S&ì»Ëº2ÜðyÿõV͵ìY¼ÒϸËÁQŸQúVΪ|=áë{­´qÉÜåÆà Ÿ~¾Â¾+ø«ñ¾ßXYt?Àöö™ÙUL€g’;{ ô¢ày/ĽXÜø†fŽôÜ,r0Úà)#'ŒŒ Õæfî¶U‹÷Dýå6ÜúÔLÍ5Žù‡›#È7íù˜Ç¥hÍ Û‰lœ¡Ü ÃqØcŠe¥Ä.®ÒÈ|°F×ÚN8äʦ{‹fÛu}!U\eWƒúæ®As5ʤ­·~^3îGõ«)¥ê…¼´UŽßpÝ»Žzã9©X¾`Óî¬bšÖP^ç)!_ïv¿ÖµÒÖÖû€ä¬»sƒƒÇ=i-­t3l°4ü£¼ ù½r=O^Õ·l#±°7Q@D¡I$-»‚{Ž*.\bgéJ,™¼ÅÙÛ¶sÃ2úsØ ê­Þæí¢I¬kÄ|¶+ó`Ÿ½“Ï#¡ïRhöšö£ ÞênZ2ë^2zdc®¡´ÝK|Úðc9Œmm¤ü«Ðè+HêŠd6:f¬gŽæ…Ž%PάÛr£žŸJµãNgk.cvùËrO§¿ëŠn‰¦­<Ëv"óI;‡œÇÕOç]z:iwpj"[TK‘@ ã×=¹¬ž¥œåÅ­üVÊ—Sí wÄ£8+ÓéÇ¥dûØåtf!{dúú}k«Õf‰5L_Ë ùQí·XÛ(£ûÌzaÖ¹­GñLïj“—Šd ˜Î§vö  Í2Y,ËÜ\N­"î ã Œa½ûÖ$¶²Ý]£]IBçœI"NÑì:s[ÚôɨýžÎ_"8£`òÈ»ËÆQŽ}i\ }6[;l²o’¹—>‰žœUDÞh¶¶·Ås;8Y#:í]˜Î}Î85dYYÝ0•%o)†Ð§o8#ž‚¯ÞDÚ“Ç-ˆ¡¶EUóŒôù€ãüi¾\ÞOØK£Æ\™ÝõàûU\ km ÷ûQ¦²vU¾ïé[—–š|ºÚ4¶ùxÔG‰î¿PqÓŠèÑɰË:4P âL‚_$}8­ ¾º+{o—3(ùñÁå±øS»1uv†ô™ã+$@¯–±Ô–}*{˜V{„¿YDA@VŒ®àþÇÁ«PÛj–~m°xlÙ·1f“ç`=…]Oí'F¹¹Š9(Ácdºc×®ëÍ (‰õ ¹³´eäÏÊÀu=ë³ðE­ôzЊyÛyfNB’ HÆ:`W<;ÈâIl…Ùa±@3Ï<üÙ3^‹à·šÍ­Ä0•†Ø²´-È8'ÔœtÍTBçÓ~ŸL]5šëi”€¡Wܼ`cßÖ´“PѬ ’ F%Y¤L…FOñcfëøÕ9§·eY^í„§™Ì cߊ¶Ð$ÖJ×Py²½]™rm§œ{Õ&ZŽòÆ ¿Ù7n‘¢óG‚NrXÛëM·×›OB°»]É<¤.Ñ•ÇzÏÓãšÆõí.d@&Á=A=·wQ±ÖQžÊ?´YÀdŸ.àxüãA‘¡©Mumuö;dÑܲbIMdž>¾•}m{g ië<1Ì®§%2¹êIèyö¨õ¯uc"%'sp§¦ì·–j¤P@ ÚªMpŠ ¼€íÔç΀,M6‘s}¹>FÒvðU1׿ žÝj;½I(ÖöÛ4u1˜âÙŽ9Ç©ïZº…´VºP†ÊÖ(­ãÚc’UU`Ùä†=MMo§j¾æÍ$†D$ʱu$ôÏò VãT»Õn#‡JŠH¶­…É“í‘Ò«Ýè¶âEÖî&3E !dËœü¤ç€qÅz—?géVK„$ “!^Ù’;аk4’ßÚ,.ä“€Wùyÿ×EÀó;_YI|Íâ™ÊŒ€AN»ˆê{ŒV¤Z}ö¯¨A›jÒÚ$!Ô‘¼2órãÒ½kþÍ'R'UÕ­ ŸŒ)šCƒ† —¯sO:«“éZ%‹À7‘ÆJàÅxü¸ éSY^˜tÒ°[D„æUý3Ðÿ*«¨x† ; Ì&¾gŽ™,ˆzzúWEiáÑp’Å«IÌÇ+pÀ¶â¼–üø§i·Öz*ªý†áRIž(öÈÙêA'9õ SR)q¦ÄgÒvÜn(òÉ’1Ô6xÆ}+-lt¸îc³´-à 'ñâ`3Ï=«ÓüA:ÞiQÇÉ%Äñ‰â…y`Ç~3ÏCšç´ønV'šæ2÷M"G€Ïtz}hM3RÓOï p®à|¤ÌzçŸzÓ‡ÃS¬­æ\G…WyQ–E<b21ž”=®± ÚêÚÜ d`"¶*f|ƒòªªðÇ<’zWW>•£ëVÂÒEž+÷$ÏJÅÙOÍÊ.8³Jè Ëm'Kò~Ï *\Ïl¡f™I „ –ÁêO¨ãœš”ZN˜—qÂå%”fP”žÞ¼v«—šd»-¼ò3*dÇ2¸ì3ÔŸjÍÒ´ÝOVŒÚ„H µbÈ÷«êBúúb‹ ;kI®×Q}2Á¡6s¯ÚâS— FLœU«ýj6û5–‘ö{hßþ[ì`rs»GëJ‹TˆÜJÑÄmÝZ9 üŒÙ0G§Z묧’-=61·(ÄÇÿ»Ô¸’yÀó뉆ŽÚmâ¸w$÷2ª·PXz÷¬Ý&ãRÔ'}BÕÒÆ'“cK·„Æ ON+Ðã±—GÔbдáþˆˆ$’å×1o=$Ÿ~Õj+=ÝÝõÚ­á@LQ"Ÿ,2õ%ze¨Åõ+I§¶¸DIL¬Ùó[ïHŒy,O¯Qé\OüSg„~þÛ>”™ŸˆßQŠM¸óã»Ð-¡VW‡àkÜdºž[绸M¶û°£å<´ç’kÏ?à˜¶o‹?jÚÏàdÓµ³ßø{ÀęHÖxo­üÀO­»I.ÇŸŽZ'æx7ìãáÅ|3¯krý‚%ÓâU Ï›±B¨Sœ³de»kÝ-ü/áû ”Ãl­æ¡¥ù—Þ¾iý–¯®ïþ øoFµ‰]4Ô¸°rw.GRÜ>m¿ðûæ{8ÿhÄ&ºI?vª01ŒŽsÜ×™[ãh%o}šfm>Í`K„HäxÐàtàr ÷¬«?Íá[ùµRø@×[\½ÈÝåF¤aSháGLæº+mgQ‡N·ÕÚv¶•Ão òÉ(ì:á}OZw‡<_«x×Q6WÖÒ^Û†æ ‚#£*Ü@Ï©¬MS#Ó`¨ƒ‘œó“\‚hšõœK¨F–Лv¶€½0NsêzUßí´BËm.K¨ÀoqÔ’j[£Ñ§ñCxræÞxÞ_í•%„E8bN ùÀÂÆ¨/T‡L+®[=@Û’.ìÄœìÇLgÒ®Ùë—÷Ú °•÷ydHÌqÁC‘õpk¼Ö´ù5‹ãr—"aih!‹Q¤$ç®z`gŠ’8[ùu+[«x V¡‡ï"YÛdä|£ßô”§N·¼´¾ŠÍ®aX×ÏPAUv€XõõÅy_ƒ¼/w©ßA{¯ÌUíTÆKD¿9ÀS뎢½·BÒ¼7– ©L°FìUb›±€Ì=;ñŠ™@󹡶Ҵ J+{ñ·ÉÊ:‘´'*Aäû~´Ë{ææ7Ö¯>×tbyc¤¼˜ÚIû ø“]Ϋá¿ÀÓìê1Þ]Ÿ-ã€I‚êGËžxÏò¬›Ò`ÓËB~Û¼ Bl›ã'jÉîUËzž·e©éq®Ÿ°Ø ˜¡„$„t·óÉö¯#Ô$Ò,µ?´x…nakk•kO³Á˜®IÏñdg>ÕÚøv×T±ºŠXÇ0™ö žCû¬ Ä‚N@ïøU½SMðÌÒ=Åì·U;b qßNN~¸­hÍ©=Uå«ö£ýŸ ø'û]]hÓÞ¾¡x²i5}á-üÅûD­™m°Û†%¹8çŠp¼½K‡Ðtû„k+yLNÁv™ãxàc$Ã5û]ûn~Ë?ðÔ_gÑ´5{oèªú¶tà•71ºÝ™H`³.WwPNFzWàgÂiž!Ò$ƒâO`mãDµÿ]æDÛ^&-²+ž¼çšú\,Ô¡¡ã×™×xšÜÞ^Çoáùw½Ì®°D‡nÀÝN8Ï|ךx‡Á:¹ûG—ö×QŸÞ¿š0Û~÷œöÍ}AañÁ×^_ ÜÇt.#Ÿý X rC#“ŒŸ­y_Š5¹u)gÕ­¦kØí‡–£ 4J8ÁœúæºNcä» éÀžÞñÒÁ)”̜ױÇoZðíb"P,Ò‘Î \|Ç®IïÞ¾¯ñ5óï‡ìÎï¨;J…<ç¥|íâx'”H-3&H9Éaß>õ,œu;o"çɉ̉’Ã=ÇN}땾e#·Zô­SAÖJ‹©,Œ`¼“×ò®^}"#¢pë$k¸Œ6?P›K’OO­AZŸÙ·<îN'®µf•+Ö©XÑE@QE=$’3˜Ø¥t¾ñ§Š¼¨.­á=BãKºQ5œ¯m&?߈«~µËÑJQRV’*2qwLýø[ÿYý¶þ40Áãµ»H†ß²ëQ¥ôgŒd± .í¥~ˆ|-ÿƒƒ~+é1ÁcñKÁö7ñ«|óé3Ég(QéžbÅø¯çNŠùüw åÄý¾-÷JÏð±éPα”~ óüÏíSáOü«öOñu›Çsë˜8‰â¿²û\X=͵.6çœ+{b¿B¾þß?±ÿÆvJð§|5¨É7Ì ÑÁ6Üu1KµÇ<}küè*r§¥71ÎütÝó:øÌo„y5]h9S~Nëñÿ3ÚÃñ†.TJGúgêøã‹eΛnñÍ…˜ÖhÈoúh¹¸Cö!øâ°Ú…–ѹ ˜™J Ž˜Ã~\Wùä|<ý¢~8|&žþø«UÐÌ(–wrG®<­Æ„‚qZ ÿ8ýŒ®IunÝ_E¸8úíù¥.+âhüUþ¿ÈŸìܵì¿~CZÁ±?c?ñ0ø“â)÷Dv€®!Ïä½Gÿƒmc},Õ¼E¯ÞàMç—ŸrAü«õÿÁIb¹Y‘üSþìšEÊŸýÿ ýˆî2øåÐ 6›r>äŒþ•ÏS‹³÷ñVkî_¡½<¯ü¨øLÿƒzÿàŸzcSƒY» «v Ÿ÷P©ýk¦·ÿ‚ÿÁ5aaö ÝÝc“»YÔ×#è&ãõÍ}µíñû3/ÇñĽ2lnGó@jáý¹cWwü,+r£±´Ÿ©ÿ€f¸*qN}'¦"&Ѻ˰ þ]¯¸ùCJÿ‚#ÿÁ34€©ø&ÛÀ2Ïs1üL²±?S^¦Á#à›zc­Ä 4iç¬"@qê"½¥ÿn_ØÁ¸‹â ·¾Í>æµ[þ£ö5-¶?ˆv­úvœŸÈ-qÏ=Ïw¯?½›G½ýšûŒ½þ Ýû xe¼ý/á7†b †éБDZ^ôý;àgìÿáfSá¿ø~Ñ”äìÓ-Õ¹í כ\~Üÿ²ÆËŽáf6ZÎöZåî¿m¿Ù5¡?ñW³÷,îä{•ÅxXÜÃ6•ÿy6ýYßJ–}•÷NÛÉ—8‹Ãvöz{®@xmÒ0 Ú+‰ñ7„­|T쉻áŠI˜Ÿ~G+Ÿ|UC'ÅìÐ¥‹¦ºŸvMû=øqYÅÌ¡{žƒòxí^Sâ?‡_¼1]êž þÎóÕ ‰"RËê üÄÞµù»ãŸÚ{ÇzŒ_|jDhC:}¡!ÎÓÇú¼dúWÉþ"ý¤þ5ÌáÔ®$bÍ/.áûn.K1½¯Oú›EýÇ-\Δ7gé—¼uàk;h¡Ñõ 5 †F¼D@$e˜g=&¿7¿o_ÚKøiû&x÷ír£Ýx¦Å¼5gí»¥Ô8˜¨¢ˆ3QÁ¯›¾#~×^ ð–˜÷zåâÛ¬„²Ê/ROAŽs_‰µïíW¬~Ò~#°·µì´ ++vl»´‡çšNÛßü+Çs_­ð'ÔúåRF6ÈsÎãQøiðcÄ>ñv½¦ÜD‘k~‚W»Ò\µ„NHI8”"á¾\åH#®*Z°7¡÷_ìÿ«Ûx7¾%ø³zá»]ßÉ#0¶P¼ƒ$öó?ZüÐý€<;7Šÿi}TÕJÚ\7úÍÎÿ™’"‹’ˆÉ6ï­}çñoÄV>ýµÔXlÔa¶ÓŒ)ß˺EÏr#RO}¢¼wþ á›y®üY㹡Œ4e¦@Hýâ|Ís7ÑpcóÒ„ŒÑgöŽøWâߎ¶ˆtÝ)dú~˜Äa^Son¯ V<$`ÉûÉ[pq–Åszσíüq{oð#àÜ«u¡[2®­¨ZÇy*-­óϧ«žsÐ×»êÚ?kšŸþX¶Ÿ¡Þ]O>¯s׊äÜ\¶ ¢îãÜøzÞÇEÐî<ð>âXmâ+®xÁÔ KÏX­7c{ÿ·ŒŒŽE\óTøqá($›áÎ…rbÒ<;Ïâýi1ä[¢ÿË»}畈ÚHsœ àðÿÆOÝütø‹ƒaoýŸ¦XÛÅYDGee&u •ó?ŽB Ë÷ õÚãn“§hzwÂï„ÖÒǦZÌÒé–2‚óß\Ž£zO%±“op£ç# læ~x8øZÊ+{aw¨Éºâ{»ŒÒHy'¯$ R”’ŠÚO„"šúæãIH–HcF{y†ô™xõîH¯fÑ<%.•-ÛÇ]_Ü:r˜_,ð($ýÞ¸ãŠô¯ø2ÊÆêhã+q=¡¿xÝvMnÁE–Ú{v¯LðÆ‘¥Gv5é®¶£–C2“¾Ñ`Só’_›”×<æRG.¾†ÆÒÓÃZ…œ¶¾|!®®«l™“'iØcp#¥z„þñ…ï†îõåVµKxy‚9&á#òøÁÕ8  ö õnÝk°ŠÃÃVº“ÝéÚD·—7½š9K-Å  ë÷wdqÚ±s¸ÎVëD¹Òa/¨_ÃåÜ-c$m†2È äÁ^:û×­x†ÊãYðÍ—†t¨ÍÖØmŽé.€äÎ uÇ9­ïk6:.…¤x&ÏMXµ]ZöynÌÀÈ•ƒ&ÐÝ@ϵnh1[ÈËg²Íq¤‘%· µö6ÇËÉáˆÅK}ŠHç|;·á­~ÇYÖ´kÈcD6÷R@Ÿº†W\FX1Éqœw®ŸHžßTÔ‰õ‹14ò<…mÑÕZác Fä—ŽMw6?ð™x—K†Ði.ŸÏýåÊHè-Î_x8,Œ8ûU/ÇàÛ­&ÞúÆ mf–Y,ì—.ˆ“>%ßÎ~Vû¸éŸJ‡.åêÛjZþ¾|OalÚ^•bÚ&Y70|±½9à`ÕÉõù“V³¹´’[‡ŽÌÁ$qÇ‚‡œ†ÈÆLö"»=/MÒ[¸Ž[¤ie7è#åD0 ÏP@ ã5Íé#Q:%è¹~öÕR’3™µ²~c÷”Ïr*op5tèôýgÊÔòâÕäž&‹.7¥½t¶±ñ눗p´&EºùQMäG/ò¬lu]6úÏI°¹VŽ%G,ª[æpKáì=jþ‡ãßÜéi§I Q «ñuäݹY¼¶äò6ŽHëIìo¢É§h²;}¦ãɼ‘Yía8Dl£±ÎE^𧉼:ÒÉaáý=¢”ãí2Ý/îåv$rqÁ5Éùמñ%džu'œÚŒ¸þd!Näu=9ÎÒ+ªðΘ¾"Ô6^NÚ,/æ\¼· ˱APã8-ü'7`UÖõß x{_“÷f#S¶ØÄ¶•Ôöà¯zå4-GÄšÌÑê> š1—q¼öÒ8],gkäJžá»ÖÖ™äêÚeåô0G®‹[©ã¸]€~îÃøˆ#Çzí¼9áŸÞߦ³¦B¶·w0:ư¸#q]ÒE’vœ`sŒÔ½‡ÈtÆž1ð&¡¦§ƒàŠåonTÍæF´ ˆÈ'ï|Ý}ET>)‹N]Ò“MÔAs0nâBpèj k”³Õ¤»Ñ–âÎ%ŽŽBü8$ï ã“]Ü+ö)¢Õ—3HQ!Žo™Ñå‡RF9ª Î;RÓž§5†â °d_õž[AÁ8n½ù4·vÞÒõ´ðäSNöV‘0®—·9'šëå𞃪ÙÙ]ê–ê·2Ï+;™ Èɹ8â±4o ëž$í¾ éWþ6º†'T¶³UVW^Iyed@£©‰8ÀñXÔÄS¦¹§+"¡'ho…&Õ¼5ª s\¶þѵò%Ž+t‘YÒM¼;ž…pqÇ5€4ßø­áð¯Á/ Þx»\¶òákxRÚ ÷˜–wÀVô8Î~Œ|&ÿ‚øoÄ köˆñRjÜÇŸØþ¼k{d}£1M(U¹,§9â¿Eõ†Ÿüà¸ï>hw  iá…X® CЛgÜ“ñ÷•ÁcžµñÙ§aè·N†²=l6S)¥)Ÿ˜_bSâ'’÷ö¸Ò¼I¦\\&N‰lUt¸ö™'¹¶Ý$îǦÉ1Ç×ôƒÂþ)ýœ>ü4‹áÇ‚íãðÖj‚8ô¸4·†20@AFÀç,X÷Ícx3öÑýŸuýOSмOñÇ—Lïm ø±©éÃGu²êwdðÜNƒæŽÜв8è¥H$u_kÿ·ÄŸÚbË^Ñ?à›ßth~™4Í[Åš“ÛÙÙØ\Ü`$rÎV@3Ç“O gŒo\ü«yð3âðø¡áoþß4Oºu´òjמfš]&Îå£e .â–Ò4lC*­ª(Û·Ÿbñ¿í!â_Àþøuc„|7ÄÐhZB%­’,LY†Hù>Š21¸b¾£-áZt×5M[û;™YÚ/CøMð³áGìùá-zø(ÿ…¼;ñ÷Å×ÄÜi¶zŒï¯&„Ìå-®n”yp8h¾â ˆ×;5ÂüJý¥ÿh=z×QÐ|>£á—»Y´}•hmZRH¡7 –ÉÀËżVt‘ÞeÜÛ‰œz69æºKëÿ éšÔ÷~ð«XIæÈ¦[„GžM‡$à¯Ê®+¯’=Œ9™³¥ü;ø|mõ˜æ¶šxuŠi"f,®¤äJ ”óšÇ´ý›¾=ôÚ³qîƒ<â[«[˜°Î6ü¤N„ù€ùOè:W‹¯´M¤{x!ØbÙy!›‡ ê{ôÅ1.ô©æŸg4ÐݳµÒïRÆXѾá#+ü3T£ØMž3¯xSáÿìý«Ûx‚ßÁBç×—{#“‰ÒÝäÏ$€Ÿ)ø'’8¯»ýª¾ø9að6·áƒ¤›U’Ü¥´ ɰœ˜äé¿6áGZûJ÷ÅÇVÒÚ÷W—:g˜,¥±æ TpöìHÅ|çãσ:‡< >§ê %¦¾áâ‡RÚÿ`E;„q?]­•êÆqNÌižé¡|ø}­hPxƒH6×pImÆÙ6ÌðlIǧ ×¯ØøÑ5íBÎãHfÓìç qk{U~@qŒ`ãùK¤þÈ‹í촄ѬVu’a5͕Ɏ Øq˜ÙÑ~ee'Œp=ëëoþÏÞ>ðU½••ްÄi(’åÚF(Ã;`‡½fœÈúÅ~ñ÷µ‰ Þ©k ¬ó${kt[‡îC·tÏ8Æ3Ö¼«Äþ²»2ÏÛêI¹`¼‘<«Y¤äí“RIúW§y^9‚òölå…×ÎVûÒʃèsÓÔמøëãÏÛt÷ðšüÚ^§I%‚5anª¹Úű¶3‘ŒýìÑf.dpº'Á¿xƒÃ0ø·]Ó“NŸ“qil_t ŒWx Ç Ò¼ÇÇÓ/õåÐ~êÖÖkg ½KIQ²Š¸¤•2 úŒãÞ±§ñÏí1c¤·‰>ø¦Û[Ón­ÃÎy9ݨàâÎkkÁ?µ?‹<+k¾>dšI-Ú6»¶´Y%¿ƒr(Iì㎢ÃM3UðGÅ.Ã]×…‚ÜM¥CöˆáJÜÆH.S¢•+Êž¾ÕÇë_­ôÝ~]3ì6wªÏqb¸••’Jm$G“‚A=+íO ~Ò º‚Ùõãy£Ûê+æC-Ä{á9뒹ا¡ãåïÅz]–¹ð§Y´¸ŽâK©$sörûpÈÜŸŒ®zu‡cä}_âF­EjËáqt.­¥IbÚÜ€x8fŒ×9Ã_k2j^%øu¢ZYézõÜ%ÚUò&Y¢@¸\‘”Ï'ƒÍ}û­xKPÖ­cñ‘©Adð"BñEN“/;v• ðpAïYsk‘Yˤ0ŒÏo×J^ÀA;<E"ÌüæŸàÄÞj*0Yy/"Éî7K*®Œ© ©\qß4ž¿ñ—€¦¸µÐo,ôùã‰7†á•›dˆ¿x¯<ã‘^½ñRóU×uíF×RbºÔ#¥¬ŠQ‹¢…r±“ǃ‘‚kÁ¥øs¡Ÿ¥Ö§t&’âÞ9ßu`ÀC)ä wbhíôÿÚGãižå—ûõ-Vs ´{×8Ï$÷ÿ÷‡_¼c­jWT–¶wqYçrn‚'ÎðsÜv<ŒñÅ|í«xsOÑã’ò '‘X\Â¥¤b€¬ Ééï^i­øïÖw_ð”i7mÌßë‚3 éó.Æ9àýh»Gê·ð_áçÄ‹{ûhÒ;˜™žÖ}:ëdŠGÌWr„ƒü,:ô¬Ý3áˆtØn—Ä guœ ?3­|kâýñ[^ésO5­Ü~ls€Û¸€#§\õ¯¡¾~Ó>1Òn¤KÍ>âè ØPn a7Nç­TY6gÓ3xWPТ’x Ý¤‹ºXu<¸ã`Üp˜=2Zð¯Š_õ¿€^UÿÅ=4\xnñ÷NÁžh-d`£À }IùºkÕ´‹~ ñùÑõÛ[­6U™f‘H Î{w°E}“á¹ãX´[‡H$UHÉ>dO!çmØOnx5WòÇÃψþ øËfºßÃMrß^’ݲ<“™ãÀÈY-ÎH¿~†»kM'Q°¸òîf“qß?›±—ËÜyB è*çÅ_دà‰¼I¦êiXêí`³Yë•ôN n˜ˆ*;ec”4lG+Ú¾pÿ…ûM|$²—\ðçÄÝOT¹´ù&þس¶³DÄdȪ|Ål}æSÁ9Æ8§'ت¼7®j~u¨j×2ÚÙ€ÏåyŒrHSïÏÛ ©ÍÜ0è‘5»¹Â+¯Ï"?;°yÏŠø[Ä?jÏ h‹}â-JÕìLîÓf3¸ždXÀÁ@8Úµ{_ÃÉâ"·ñ¢ÚÎùóR÷LVIíóó)’'åŒçð¥`>¾‹Q³’ÖèFöwðù‚;¸r«–*qÓ½Kªiö¨Z-fÒ{Ì·Ú£ù™/ÈçqëÏ.¸\ÁµÃ¾ŸsÜGuoK9È9#vÓõý*½îªÖ7>eÍÈóDj‚`Á„kFåè?(Lvg «|/°¼_µO ¬²¯Ë8`­€eÆGLñ]¿€•‡q=ÍÕ³è¡ÖqÄl p:qœ{ Ñv]ÑÆøcâ·‹<_q?†5 &ÞÞ ;åïŽbhËc¯9'ð®±¼!'öp¹×l·@fN©Lò[ç^1Œ×Ïß< á :íŸÛŸ±yéO;¿x ûNX8>Ø5»ðOÂÿôÆ:·€5}Nè}™g’ÞñÛìØ'"?CÐ atvÚ¾—5Ìö·ÿ µ;+‹e“ʸvŒ]”Ï.6“Ç"¹ÍJ/QÆÖ÷ÖpÜ¢£Ik,P…a*}ÕnÁ¹íÔW'w«|Fðµõίá‹8å·–ðM{ocåqÁÃyÏ<×)¡üxø¯à‡šM{×Y]Ï+¦ÂÊ@”S »#íÁ©z–‘òÄÿ|m´¾Ôå»·6¶Úl°Ü^ÛeH§„ ü§æ*y89Ç>ÕúÃû:ÁMÿb[/hþøõð“O¶Ô ,n/´8<ø®J€RWImüà–9Ékçíãg€¼cžÑtKÍIölº±½Œ,mÁŒ;àc= yÕÇß O£^x³ÁÞ“DÔô»¸ÿsxvÉ<2ƒæ°UÊ’aë\µ°ÞÒÆôêrŸ±¾¹ÿ‚\þÕ:ŸÀ? µ›? ê—ñ´‘ Çk üÙ3†ƒÌÄoµñǧÅà—ÚÿŸ…šŽ|yâ-*Wsoq>eÙ™Ö2 ù¾ÿÍ¿9Æ~pøOဵ‡ zš ”7q¹ÞÂe' @Ü['‘qȬ'áÝþ§Û/õ1aö6ÄÏqqp±³7”’;$c·ÍqË Z.ð‘Nªlý*ý“¾ ~ß¾‹Søû ü_·Ò-´x£º x¿JMODGbæÞHÌSÀîÇ%<Ò€r«Á¯iñäðQ­N;KŸ‹lνo ý¢ûà ±ZÍ û͹]‰F!x=Í~oø _ý£¾kéâŸÙÓâV¢š‘‰ŒÖ3$wv·P/ÞÃpð§æVVVppH?DëßðRø)…´‰u-BËÂÚ¦“|¦Õo>Ï&y”G¾Uf‰sÚ¸ªáqNw[Æq[›ºb}/\}[ã‚ðçÁ¯ÚÛê,sêÁôù,·8д›¥©Ùž2:ñ_)ø&öÛ\¼Õ|SñDúœÿeX/|ɼr*aÙ—9¼ãm}™àxQðþ›à¿IÓ'¾®îŒÞaû7÷›i|n+Ó–5½hJ½ÝÂrØúBýŒÿeÿ‰:d· |È`G=kÂÿáj|@‹Ã¶š÷†¼&ÓÙ%þÔµÞ'X†ÖvòÁäqÏ5ô¸\óßšè4Ù×ã§ÄJóÃþðγëlc6sÎÀ~ð„“›‘ÁǦk·ÛSNÍœü±ÂøŠÏXÐ5ŸífS¾&1[6ä*ú®{Õ ÍUÖ¼#©\joæBY.`HÔo·—©dÊ–\Àô®·Ä~ý£¾ø2 k⿆5‹8íd­NÁ¦µ@¼dÍð›w®ÇàwÀŠ_µ=׈>Ò¨à¥Íï”æé$hªìð·wñÅe_IFîH¨S”˜ÏxÚ;ý*ÞÒËÅ·š¼cwûl‚á# …pvŒô9WèyøûvøSm×…¾?Ú$˜P#Õô„¹¶ž2GC|‚;ï^qàoø%D[ZxƒâwˆQ5Ûue'F…d€¡èŽ'%œ\jÙÓ¿boÚ?À sÿ kâ"̶î¡•áòzí`æD¸cÒ¾kZ”ŸîäzQ„–Œ·âo„Ÿ´f¤ÒÁñ#þgˆ5 Õ'í¶šD–’¼"±”Ü g#Ö¿?õÍÅÿ §¼ð_Žt»¯ë7ãÏc¾ÂtS¹´ƒåÚW ëƯÑK?þÓ®®´/‰ÿ./--‘žKí£¼‡'ï1‰ÊÉŒs˜·:W–ëz‡Àß·1øP×&–âÖuO컜ÚK$œ²’a÷ö~†½ I£ôϘìµ;FðÞýnúÎÞUR(äš8œ»žÉÝÉèFkmjÇ\Ñon§Ôã¶›1f,Ìs¹°qÍzÅ…Z?„ÞëÀwz­­³¥í“ê~[ßm  éÒ¼þ?·„á{ËH¬u®Xb"ƒ~ÞùôôéÅ}5Wg—+'cGNñ,ZÔPMyŸ&4…æ$猑Ð×uf!šÞïlqÚFIó%eÇLäq^9¤Þèž-´’ßÃû*{i–v¶•€àuäð}…R»Õôiur`ÔVGbr¡É9ÃF銾Q]Ÿ-¦‹us—áûE†íÈáÆ1×qÏú×k­Úëxeâ·²‚7F­]€;ÊpÁÀ9Ž„×ÏË«[é)–æÆUåýŽ yo_ÃÒ¶¼MñO Bðø—ýÂáy±)xÑŸ£Ô“Ÿ^(å £¶½Õ¼<5˜¬5sö‰<¼,q±F®OLf»[ŸÚ_ÚÜMáÃ6™3±d¸0 9lämÁ®Añ€u-1mõXØÍ Q*©ÿo©ëÓ½vÚ¶•{{g5†4š|»]Ï´PÈÁïžÔY…ÐûûYyÚF£¨CuH[sîì d9ü+ŽÒ>!x{B¹ši÷[] ‘žÑ<åŽSÓÍîƒÐÕ3IÓ4ߵɬÚÇâΩ!pPŽ ð0{wnÏXð凇ÚãÃúgÚšñÄ :ž.~잢•‰eí/ÇZ¤WV™¼·•9YT* tõÁé^×!×<<'°Ö)äW ‘Æü«Æ›Fºñ,7Z«¶Êhˆ(M¾r/\c‚æ+NøgâD¿[߇yÒ¬xý—1¿ˆN>n”ö=sÁkFÕ.‘¾Õ¸‘ôÛî9ç5æ^$ð÷ˆ[Nò¼ â§_†ó1£ü t(ÙÿØÅž=:÷ö”ÚÄ7¶ 2›{ñøÖ>·ãT1j>"Ñ‹Gó…Í´x‘÷9 wçšô]"ÔYYľ&¾†îy×Ë4b0Íá_áþ•SÄñµô¿‡æµP˜m³‚”ôöÏjácñ¯…üPÆ{K˜î£ØÎÑ“‡ÞßÝQÔâºM#[…ôØuKOnÑ Éó*zÿ/Jêã°ºµ·mCQï"Ñ•@aómÎ;\N—…­®ôÝ>+ËÇV³$BB~] ð=È®Ük—ZŒpëö#äÆß'!³ÜPGjì-m|9yb5÷Ô.E´hZ%‘÷e×ø1Ö€gÿÓûp[Z*l†2$?ÅŽ?1J÷Z•¡éqnÏVÛž?èhd×áU{V… Î €|ÓŸöÿ¦+jÞÓQ‰xf9ü¸õ¯‡>€ã¯4ÿjn¬×Äʹ+qùµ]4-QåXo¤b8ȧ=?:ôö±ñË4‚'Ú§—äìjV &ɼ2(ˆOOCëI±¤cèÞÖµ‹·Ž+ÊÈB!B[ r{=søU/EÓe¼žÖòõ¤ŒQ<¼{çÕÔiË>•g4VlP°ø|œõ§´72iSJ¬ŒÑܽ€{v¹ŠHuˈÝ!½vŒFÑ€ûsYú¤O«On–ó´vñ0W‘bÞX`𠦺F°ÛÒ|Æ9 ö© vxe}Š ÆŠ0ÄŽçû¢¤e“bßb}2ÁçŽ5Pîd#k+w9ã^/ñÖà»A¤Â©xã2Äì±vÍzO‹5{A}O€ ¿;²1Ó¯ùí⿵È{†ß)LüçäñÒ€vÇ­oè×7%æ»kèãáDL…‚89ã {P Þ¢-ôOÂgˆaw‚sß$~u%ôSÚÞý·OR÷8ÆX#±=OOZÑÖ®HºIdž$¸$´Q¦°= „*žÄœûUKÍC\°µµ¶kX®dv,òD¾a1÷ÈǪ ?þ&: ŠÍ%ŽA*…m˘ÁÈð;}zÖ¥ö•³ÛAá{ĹE ‰,+÷×aéƒÇ·Zǰ֤•ãIì%·]ÄmEÚ²(仿ù£¦kÓÏs3§iÑ3¨Xã/!,O'%{ñš 6.ÒЦÞËì÷ ¾E5è3ÏøŠÀ»_6‹¨K0š.˜¶Ý±}Aï[úŒžK‹ætR’\Á8ÁïíȪŽÖ°D!Ò’àÄr%±ß½ªðÐPZ]ûB²jŒ^ä€á;Hãžp=2^¾¹ƒû2EÓYídž@·#´•2:6+^Úo˦¡¡ÊL¸ÈWǺ®ì uÝUµ›h¥y.¿Ð.¡À®=;óŽôZäù‚ÚÊÂM‘E!Êðêx¯mðV‰2ÞZÝ\³m‘•o˜…n§ÔÒ¼Ê+]±Ç©ˆÌð26×¶ÏÝ#†`F+è_Ÿ²i‘ÜE¹›bÙH+·©Ï ªˆžÇ°X 2öÆëJóÚ)áSä®Ò@¸ÆÍTÒ-ôÉìd¾¼”2À›pŽr Øg9­ A½Iç®ášI#S$Œ0þp¸ü¨›á¥–¢²@`–3˜[¹êXTe#2ÚÆóR¹šHf_•A%XÐ g'é[6q]òêVýñ ‰³…Ï¡ÏAÖ®Ý-…®¢4í6xnæ1ü€Éò¨îHœX1ø²ñæ7ñÅ"A"Á˜Ó$ýâÝI %±ÔtC[LŒß_Ü”'ɈÚr‹É?…hYÂ.â“O·‚8ÝádbÉå™z’?k¬wÑÜÁ¥»êDò»Ù;F|´ÆÒY~n{Õ˜í¯ä½’îÂ&8ræI3,›Ç9 ò1@‡HŠäÙë‘Mw© $*T3{{:’Ç\Õ|L÷LÖrÙhíºh•w\ ðªÃ§¡8ªë:ž§—2yåÕƒ–êóÂýjŦ§§jqÝ,UŽ6•w&é:=•hÅu§^X„¶ 1LË[.NçszúšKøHÙa\(·ˆîà¾ïïÉ þu«£Úý¡ „G$ðåpª–'Àëý*ðþÜÔ5E»’ÒxlÙ#¤!N1Õ½ŸZIX_x¿Jk8à ¸‡ä¸Šóõ ? Ót+H5­at‹=fêu—æñÈ@U=wŒœpEsÚŽƒªXêqb‘G¾àårüœŒŽõ£§êwz^³ —aA>aéÐn#ž)€1}F[ß²XJ,¬$7.ù ×#oQÏZÓ—W¶Ó­m-^Îq-Ãò¤y\ñýMCm«C«Ok§²¤­w)-Y‹y­Úèó]ߘ£ºBã8éÆž(ilt‹G‡R¸¶‚9šT‘åÀé…SƻޫiZÆ‹yo%¤ ‹2’HÜdê99JÚ¾Ó¢{k¹­ôÈàyGîØ ,I¹8ÏQŽ•ÁÅáûÝ4hlo) ci`GL‘ÔŽß­&ÀîlRóì‚ö+Ñn!C81[oÝÈ-Æ{dw©4ëßiqÜêÚ¦¯<îˆé~ùÀ¹ú× «Ý^G§BдQGpê’ ìîÚÇž=”c=éšt–ðKy«iVñ\É|ÆÙK¹;3Œ.è 8Àæ ÍuwÖô³g¢^4±YIöŒÝCµ²F6† ¤ô¬¨í9f›ìÇpê•É#¸=²kÏYÖt˜dŽwûW—2Ã)ã`+Ðä÷ŒWe¨ßÞºIym¦M};crçlJî2#“M , MN}2Æužf¹/¶ ä€8oö½ù«°jWU³Ðn%ó’#/6Ã"9Hãæ=sÏåTæÓü?ªx[Oñˆ¤xÒÉ&f_õR¹Û÷AÉäc"¥iÚT¢Å¦ÔGšó$h›í(b‡jÀÛ°:ÌÑ‘¨í6êK‡f§å\½+‰¹Ö¤*RÛ˲X–8Ú ‘’OnGâb‘éÂ=:ãÌ:Œä¹l¤àçÛŠâbíÆ@à÷¯Ò Å+’sÔç¯^EvÓzÕc©·.—6—y@ò7—Œñ‘‚q\¦°š|òGfÊË´çý¹gfJ2|¸##i9ïÐê¼~56kfŒmhdËìTr¡£±‡"71Ku(œ…nFå^¹í‘^™à_ Þx{JµÖÅâHŃÈ]$r0¹ROZ$4ÎÖ}'ÁÚnƒ%þºÐ U“h(¡ÝTŸ” tÏbqVS^ŽÊôˆä†9m­ƒ7È0ç¦7IÇáXšG‡õ-Ö];xæšã3à-_¡íŸNzWO?‚n¯.#X/#HVÝÞ4ç-˨^2ŸJÉî_-ÎGX¶»Ô´õ³´‰‘¥“Í‘ƒL ŽNÜà W)¡è:¬„Z…ÜØµy„SM7ÍócÜtëŠõDð“j!b¿’×ì@îº~í†Jàðxçšì4ÍêÑ}‘£Ym÷±PpÁÕO'ŽäûûÔ·m‰±ó÷ˆ­u¸µ˜`±àH]Y£oºªqœ9#¶~¿JúR_ ø~ùÅÄ÷!YU¤Š8ÃËÔ{údu®#ÅðÒÙ t‡’&Ü3FA8çœ|£š`|éqÛHbr Ç"¡ž•è7G!»y%”îÆ=‡sX÷¶±Dñ7Ë&s€p28þ]ªÀå°GZLظ·‹vÛvÎNxªÏnìU\nÝÓBŠêôÿK¨é×zx ŠÌ&C¶×»mG|wé\õŸ†R¨~\ðh­!†M»ÀȡЎv‘õ è©#@ï´ñS•Hºó@(©6dçµ9¶ÿD‡CŠw˜{€}È¥;±ÇJŽ•µ¹\ÎÖ$‘ü+ÿ|ŠrÏ*0e8#¡c鎕 ôì MlÎ¢ÃÆÞ1ÒÙ›MÕo-ËýãĉŸ®Ö®¢ÛãgÆ 4Úx«Xˆ(À ¨\.§W—ÑPéS{ÅÚK¹íÖ?´ŸÇí9Ym|g­Øû×ÒÉŒzofǾ1žõÖ[þØÿ´½¨Ä0¿é·æ1°Áú¡¯™h¬'ÃËYS‹ù"㈨¾ÓûÏ®-?nŸÚžÉDpø¶}Š0 €€=¿wŸÖº[_ø(wíQmG× ™‡ñÉeb=3´WÄTV/*Á½èÇÿFŸ\«üÏï>ô¶ÿ‚Ž~Òðž÷O”ñ’ö)Î>„V’ÁJ?h’Á¦]"_ö^È‘ùy•ùóEa,‡.“»¡¹±õÖÓyú,¿ðRBÜéÚ;ã°¶*? ?õ­x¿à§ÿV#ú6‹'¿G~iQY>ËøxýÅÿiâçã?NGüÿâ™@’øJ'>ŒáNOø*İŇôÜ}N?ôüÄ¢¡ð¶R÷ïĥšâ—ü¼gé“ÁN¾)îf‡GÓáÏEþ¤U;¿ø)ÆYÁXc‚Œ|ˆ>àä~X¯Íš).ÊV«‡ý­‹þv}ùsÿ øÙrw=äÃ=|©A¿ï•&¸½göÕø©­’÷²I&z‰.dlýzf¾7¢·e±Ú„H–e‰–óg½ê´Wï&ó¿pûJd?›kžÔ¾8|KÔã0>¤Ñ!JĪœ}@Ïë^KEwC/ÂÃगÈÂXª²ÞL½{©ßê3µÍôÏ4¯Ë<Œ]Ôœš ih®¤’ØÅɽÙ*t§ÔJÁzÓ¼Åÿ?þªd—mî&+*îFXwǨ÷¯Ò…ÿ´÷„üIoáØ~;}©µ/#MñU™?hÅ }›PA̱€HYÎ\üÄþjÕë JûKŸí2ØðØèÃÐŽ„PÀýäø¥¡ë¾èvß-ì|Q¢Û]½Ô¯o2é—°ºÞéWwò0!Mö©q:z‚‡åaõ¤‘<§ìˆüOáÏØÁ࿊óø‚DPËàߨŽbsºòãp@'ç`<1Å|•ñßöœ¾¿´‹Â±'þÏôi¶Ÿg66ù—NûD«ýߺ¤p8Ý_Mñ3ÆÞ%X¼'ádKµ›(,ô˜Ì>f¼Wçoö»{WÕ¿dïë6¶Úþ «o³°™2*¯Þf`)ÉÀszRrަGÃO†zý½Ô¾4ñb½Î­{ ÅÁ%_‚ªsÁ=û```W×¾Юt Üë ÚYY‘ ÚI‡9îF$ô¯o¶ø០C†³©ÛRÊÚ`XðÊØ=ÈÇËŽæ½^ÒÕtÁ.—­ßG#G=›€.‘”õn¹é\õ$h‘ÃC¤yÖ–úæ‡m¨^Ú¯Û7(2ÆÒrYzeH¯UšÿZ·Ó¤ðîµ¥Á Åä-ÍÝ–R)L€l,:+óó×ñ«Z}ÝïÄvë]66“¼%”†H£0à™MŠÇ‘œÇ8«º5¥ÅÎk¬1·þÍÕîOä¢Þx[p3$d°Ï8â°jã.ë þÌðôšµœF艢ž=‚A΀8vÎÐUã–ãZÑ5—º‘£’Ò=>b¶òÌÌeþëT“Áä †€é šÜ!»¸‹Î¾Í¸žÕr›¤PCœ„`Œ~µtOö60Em ]ÅpPvI.dÇ Ç"¸K©ï.ïÛZ±[[T±[§»€8Ê0^Ê«ƒóÓ5ìß> ~Ðÿ´æ±qgð#B½×-­-ã3j·(-tØ}Ò׃3ƒü9:Ö«Bœ[›±¥:rnÉÌ·žI´ú…œïöËf]›+ŒòÀç§‘ZZRx§âF°áôW¾#¹Ek%ͽ¬‚ÚÍ$%G›4¨‘©Sç'g¥~€ü3ýŽþ|3ÔmßBðï„t«Ÿ Ù¨W´Óq°·<¬|’§<áýu7Ú“¡ê7ß!¼Òü= â}’K›õòâd òýÖL–=Î í_—_´ßíñúOŠ>ý‘~ |.‡Åÿ!ðχ´¨_áê_ KB½¾æi¦¼U-s3[y]c(rÑçÞñ‡Æ?­|7ðûÀþðƒ¤¬öÚm‡…![ m!ºûé¹nÁe?.z_E¯Ç¬ZjZ}Ô/ê^nÉ_|±ù~óÍŽà+sRñΣ}|×gÙî&•c¹XˆòçM¸€ô= ë_uC-Œ"—n‡[)2¼ “s}¦˜ïçhÇï$G.QOÌïÈã¶Ò°lõ-Fmô©ÞÖäªçN%@q‡ô•Õͨ^^Þà|›˜Õ¢ ·l’Æë†ßÎ[a©¤‹G»ÓcÑôÉ£7tã¹F#“ÎÆÆG¥z‘‰Ìçs AŠM#]kÍ^5‚h]šA¸aS‚‡«`óÏjÖñ“¨ÙÙA¬\M±Î@HQðrA"Aì@àzŠÍñ]…ƃ5·ˆµ;6•î[d¬çtl±pW=¸éëG…ÞÎ÷Å#ÃÚP[„º¸ò`v~2ËŸ,“ÀÎÓÓŠnݵÇÛ5QÑÖ9у•ˆþ6hvZ|:Dúõ´r"5’ì,ß0aÓ=zÖ¾¨jþ!ñ4¨Ä²­³ðÌW ß–GãšÉÕm5} šm#Q†Þxm±–\…'‡=IM Pð·aâáu}¥Z\Û-Œ¿kˆÝ)Œß1 ¤cëšõ[½ I× ©Öí"æ$-˜HPÀÐdôõô®A­QÓ"Ô4mQn@M³ØÇû¢øûÀ7þ¡]4^(Ö­¬l|2cù"µv·•Àv· ÿÄsóÓ>”rÎø'à·…þ^_x§CÔ/¤Œ®é­ZrÑÂwË5…ž==«„ñ7‚¾ üAÖn¦ð/ˆ$Š÷IŒÜ*Iqåî‹~Ü’O9=E¨Zø¼x-®õÑ`±’G—#¸W'nsŸ_Æ´üam©i–¶0k~“X’ì² =Z˜ÊH FÏ4rà^#ðŸí3Çkâ›ÍA˜É‰–WuFC)c “íô5å·üW¥j“kzmÝÜ_o¿TÙ^HÒË G9WÁv¯¯~ø÷AÒ5„¹×†²5!eßåàƒËÉÆÖË6!àçí!á}ÏSøw¤¼ùºÚ¢,ÌR·;âr2½Á#ŒW¹|+øªøŽþKŠ0^èÑ_Lª/aÆì¸Bq‡æÁ⾺Ò9@ÀãåÏ\ùÛö‰oÏ i÷-Ù͸kI¶³ÁgØÔ´\Ž?Vø{ð?Ã>#ŸÂ:àñ2Y\yIw;ýŒvï £j)=I `óXš¯ìÛà MWUÑtmRk+í64¹’AšAörÙ’?Õ‚pr½ëŽñÞ½ãýKš¯†t­nòóCºÓ÷5•ÆÛˆËÌÇ*HÎáœÄ9æÿµ/ˆ¾ñ¯Š5½Z+Í=ôù´«„ð}Žð.íÛŽõRê¿.xê)r—Ì{w‡?g‰ÿî¢ñ/„¼I}o»æ6ÐK›ií)רaº×¼øCãÖ¨ÞYjh5m1¼»›[ƒûÂÀ ʪÅYAž+çí7âÅø& Ãí9tšB®s!‡þ¹› v?¯%ñoÅÿ‰Z‘|ae&¨Ò9‰¥{T_8¶2ÕçŒóÖ¤mŸ¡w:ç‚~$ÛA6½§i·sº»m»ù¤„p§!=°kÍüKû>ü*ßmÓ&¸Ñ¯&‘¦Ü›•W+ò‚™È¯ƒ_&Øèú£k÷¶·°höÕ ¿Ó®K4ðÏ'0ʼçkcr1QK«|ZÒµiÃú“­„Å*¡öáßûÅÐdÐKgÕZwÂí]ÎïU×fK‹xQˆŒ(Tí–ääòÞõ—âM*ÿIÓ4¨ìµ‹{Ë[iÙ]î`Ìsn$ÜtƒëÅ|·c¯ kÙuýoWŽMBÝ<ÒòÝXÃ6 Ex é8¯iÑ^8ªˆ «è–úŠ5x§SÔ.šÞ(¢Xæe“tŒ¹ÎüoÊö#=+“ƒWñ†…m!ѵ{¶Ó∊&y ºœîàãƒÐãŠï¼W 7Š|U-ψ.–ÒâÕWÊxãâëË81“Ÿö'šÒÒþËã?4Þ{]{R¦[håuMŒ–èwzñœÕ —Qžý³“2ιóþÒÑ¡/ÇÌNcŒœâº/ˆöRkzËÈãU'i\Æ¤àƒŒuÇå^q§‰!ѧÓ!ûBÚ@Ê¿*¡'ëŸÿ]¹Øó-SãÆ¿‡÷í¢ëV•¨Û“±Lw.\âl¯ÊÙÍ ûEØHñhž9Òmí£¼û­¥â“œ–P=Èçâ#øsãå–{ߨÞé«*>ÇŠL ðAççôWÊÞ7›Æö2\i)Ò®¼K§Á¶ö&•OÙ¦L‚ñHW¡`0ËÜ*$Ÿ@Nîçéo†¾9þÏSêãBð—‰­ãÕ]|–´šì öÉ•"5'±#w¯VÖ¼ ¦XêÖ–ºÞ¥ª4äÌßjpŠÍŽ¡I=û_XþËðQ?ØoöÜð¨øñ{ீì5 Ñ&Ÿáòm –híÂàÛ‹ÈcÑ6ÊàŽy÷'ŽÿdÙÆÓÛßø—ö|–KËXói<÷p¼°Ž .ÉŽåvä­|Þ+;öp©RŽN<ÈüŽÔ|gý‰á½Bßᥟ‰çÔmü·„Nc• ð67?2ÿtð}k”ýœ¬dxëXÓ¿hï‡>#»ñøŠ\išÄW2(ç’#°¤R3´×ëÇÃØNþê üGðÅ׃u(°‹e=‘ÛÁ]÷poŠeÇ+¼žxâ½oá–ð3Ç~&] Ã~:ðŸŽ&Ñâòb7ųi‘ÇœQ”8Ak̯ÄP©­NØ`yw>,Ó>xSÂöÿ õñE´&i¤¹wŒíÂXsŒùwˆ6ÛÓæW?Þ&»}'âŸü3á·†â{¦ðßl$Ëš×U´¾‘âÉË11ÛàcÛ¸öÆy?G|kðçí›aâ;}ÀžðÆ©£Åþ£}$Ðc¦ÃˆŽƒ¹#Ú¿?Û'ö…ý£lñ*F3Š÷=#öÖ/¾ÙÚ|"…"ÒYntëÛM6ä3°—,6« LI ã~|<ðOmå½¹ºÓe˜ê(·’ˆ¹TØÙŒg8ï€O<×ègÁ¿ÝÜøÝ´û‹X⸖a)òÀx倎2Éo¥}ü0ÐQ²>vudäî~€~ÄŸµgÁ/Ùnâú_ø‹Kզͬ—&æK‹mAN ,ö¥ˆY!ñ¨ zAúãíð›ã]¼RÞx§^ðn¿§\Iua©ÛÅ-“Ãæ€¥ “†Œ€2sŒñ_œPÍgsaªë/–¦‡Î‚YãâD` þï½+Ìï­b½Ó†«|¿fk_ÝRÅw‘ÜHs\òË)J\Ì%¥d¦¼gÿ9ý¼¾ê‰à‹)íþ'èSù5G‚9B[^=èï ^¹, túücö×u­?þ‡>(ø}ã;pÆ×YÒt³ngl‘€Y%'>[¦ßQ‘_ßÛÚè×0Á¦\ùPÜâ@¹ÏÓ=>µ¾šåÆ‹¿×”fC¶TÊ–' ‘صëS<¦„•˜ã‹’w>÷·ÿ‚¢ü2Ð5{KK¸uoß®UvX¾+œá|ÆÎÂ{üª=ñ\çÄïÛºÛã†&˜Þ{këpóZß& ðOΣæðN3Í|"úΣoâÉN›mggk Ó¦æV>­ÇÞ=¦Mñ_ ¤«¬YÇg̈ªÀæU<ñØÊ¦..ê:—#¼´Òäz¤`\D¨@†ï2œäã‚1@™s«k>ðež³c4W2ËlIPmdÝÎW’1ÛÞ¹½GÇ—sè©qa͸r.ÌE»6z€sÉæ¼÷HžÆÜ,:š™­ívD\¹PQÎp­Ý€êOÒ•õ_7Oº·Šá„irÆLþì:÷êM&€ö¯ üEÒìî-5.’ùɘçà‰;¯á×êö!^›í1²I»f|(ç88èsÓõ¯oHo j:Ô€@¢ù%·‰›Ê㪒¾¸ìk°ð·ŠÂ>#]wFÔ¡´´Õ£ —tÑË)À<0ÈÁ÷¹@÷-sPÖ5yÅ÷ƒÔZiX®”HISÑqéõ¬+ëßxnËûkX½ÝkƒÄFõéÐŽ@ë\m—Ä­OSñLÞ¸ŠÖ9'Ž@×6ùUÞƒÁ÷™yã­>úÞßBÖÕ®e·p;w)+}Þ;ŸZ9@éî5/ŸÅiæ¤z”‡rµ´^[ƒœõëŒT^Ò<7u¤_\Ét»¦‘ñ°ÿdqúTwþ8ð΋wc§]iÞM® »íîY”2²ðèÃëœò+·ðt>³µ»¸]NØèò—KŒC½Ð7|ƒ’LcŠ6ô{¯Ãm üR¼Æ<²Hß¼aî:ð>”¿ðéˆo)er_–hչ䑎9¯7²Óô¿‡×muáïÝD·¬íoo· ƒÓ;²@>ýëÒ­5ÝT·¹Ôð 2ò7qžF+QþÙsuå9mÀü¥†ÜãùšÇ‚ÛU×/㊠#i7|û”ƒcÓ•tµäbëˆ-œ*‚rÇ·$zø{£è °ÛÙª‰íØHT†bÃÙî 2¶8šdò÷tÝžôüåEËiÒÌ·H¯Ê2 ™Çã“S=Á¸UÔa>w7I’ŽÇþu ´Tynbo´ÎObsŽ˜^”ÝÞl Ú”Fä·'Ø©!³ó.U6ûK´¿Çžœvý)7¡VDŽò©ʤŒe²È«i¹µ¬e}æ*6äζ?r&Q2²ˆþë’O©íšHãÓ~ÚʲI6é>] …Çsß T•ìíH™-fÄÐÈ~e0Ýk¦Ò4ý ¦Šê)-@‰Ã†žŸKeá‹{üÉm8[—Wp[hî¼c’:U‰¬dÒld†IDê®Ïûõ ¸ÅŸfm–K˜¥ˆÉ…Š6Éùyc¯¼³Ê!’}y€\`ûzûÖ¸¿C} œ ®DfH¶‚ z÷ü€õwO’ qv˜ˆÌÛ `ÃŒQ)(ê_µÐ†º°ZêP§Û$`Ëûª›{ã×Û4j:6™áåžÈNÎÚ ¶ ç¯!cL’òÞÒæI¥¹Â–ù ðØöëKÖWÐb¦‘OÌn3ÛÞ¹fõ4HŸG¿KxVÁw(vùŠB‘Žz÷©f—Z¸µžK‹”·ÁÄ(‰ÕA<ätã×5-•”‘B³·Ìc Í—Ž3O»mKÀ:ž›om%¼Ñµ°vüå ã¯óéR·™iq[Kew!º ÁýÚŒô'qõ8â¹]*?ÛÉ×$ Óò±û° Æy¨®ÚÜxQÔïlàŽÐYÚ[¤4—ŒG@#¾fo@¢²¡ºÖ'¾×6Í*ŵ<¶`¡XŸÃsV"ýí®›?•欲z®1ü#?CQMgki©A+ÉúÅÌ´ ó‚88Ç^”÷Ô¯4ëÕŠ+•…Û–Ž=À ôËõ>õ‘örF™Ý%ÃnÿVK)A$gîÐ6ª×~$…¢ÓtËHc`¡¶Œ V^\œûâ²4äÖtæ}.Bæ g‘.ùÿ1ù€úb¶ô-?ÄšÕŸÚ4{c${IÞ£åܼàóØwÖžµv÷2Åm¡ò˜7›ò‚rj´@hêÖÚ…ÍÜbK©/ÀØ’€B÷qÈäVž£e®ÝÚÛÆ×¾|xQåÊ¡luÉÉ#}kSÖnî&´µÒ”l–0ìïhÀ^ÀœñZ»õv >tHU” cø²x}:Ñp°¶šm„W%¾Ò}Ÿ™#e.üp£ÛÒ[iš+B%71Äk ´Ÿ7Ó§Ó­rÎÑ[[K&P#\/ËŽÇÜûÕ.Ól vÎ-X&#p¤g©=Sv§Ölt]Fç_Ða¼’yÄó€Ë•#¾{V=†>³l³]F¬ÓT»a—9ûÊ?< énÿ¶&Ò×QЮ€bÿÙúU!Ùß„|5nmXM}öB*Hēײ§\úb½«Eð]Ö·k¢Ex×PcòÀVòשmØ#œw¬ÏÛjZU½´VÆ a”'Ÿ7“¹‰èu8ÅwÞ&ÔõM:ïκ¶•îgÛ;« ½0êxëW^Ás§Ï.³-ìMm:¨•rS¦@8?…?}•¥ä¦Ê¶Iû½²7Ëö¤e¿0Fo—-Êù>r°¡'8Ï_Ö³uÍOÂQꋤXjÊ–åÈÄ'è*Œ™ÝÅq¦Žï ‹H&Øa4<Žç늩:ÚÛ_Fú|bæ+~0PªñÁ=x®nâ-:ÛOÔµ"Ôec‡ ¹ûã§ãUtý^°ž Y¯<Ý&míb|É üã€Qߊ ;Ët²Yc¿ÉmêaŽHÁ+¹B5Zòñô«gÙg’@ÌsÃヸž¸í޵‘` ³ÜÏ":ºX¶_wžØ=Me¨ðÁ«C=íó+4¬˜zÀ0x©<úv‘z²=ÔŒ·3&È#FÚ~§>ƒÓ½Gcmý™©ÚþöÚHí—ˇÎ$qŸ˜§sYZ¬#‰#’K›™egh¶yŠ[ÂwàuÅt×¥ÜoÛ HìлÉ)þ,g’{ý)\ ˽?Äpë76º}Ú¼AC¬B«™98ÛØZÁ‰ïõ ¦Ð\¥ÛÄYglüËä–íÇA^„òh6öj-tû±#* §”~äž¹Q÷°sQé¶ÖÙ·Wî¢ ÊvMDZ#5``hfág—OûDˆvÇœwîÜñŽÕÒjš^·¶1o‘ä}Ò–' LpN:`Õ­7OÞ òeS1Þî±’6ÎTãôïVõMRîÐÝÝØ^Êæ%òÀЏà1Ç9  Ρub¨›ÌžUÚÁÔdÇñ°ç¼l_jÒo°ÆÊÖï“år‡ É9÷î+ÕàÑ,ïaÚ× ïˆä{£‰ŽUõÍQ›J·ðÌ¡ü?dKM¶6u>[F$@>ݪ.Àä´Í@}’æ+ûÛ«mFà|ìöà„‰¹ÂäðÇð©õOßÙi‘jw pª9û3¸3³7L¨än9õ8®”hZÌ–W7 `ó©ŽGyA1äÉç'Ô§oámFöÅuH`]NrZCs'ÞG9Æ2˜éž8¥p0(%[m^ú(ì˪…ƒwïJàç°#® kÞj°êB6ž­3VQ‚J½!9#p}kiZMÆ»á«ß"#3G‰ü­î}@€g¶j‚Þ}³\ÔB[n°¶DŒyh#à|Ëî[ùS³9tKmsK¶_ E>¦—?úÅÚ¹ àõÇsÖ»ÍõkÝ6òKh£±±‰ÕD’óF0~qÜ÷5¥Ýø3AÕmõ]¯,°ê “å*Aù@=C˜èjÖ›k&ˆ¦û]¸-®Ýü›||€““¼:ð2sT€ªÞ;Ò-õ´»²Ï(g—2ùgøNÁçùTºµ›ßjói^"¹K¤ ŒàFŽ8C´¸çk_A†-÷)mþhBÀ#Œb8³óùŽŽ:ž:TöžÒ4ÀºŠ$Ñ›ëÕ]ÑŸÝm·Êr9=iÉÇ û¶mn,´­$bÝf Ý[?1ÏQг­xoHÕ´›˜lóØw³Ž«ÝÇ?Zí.l êPZÏo,ïq·syj2X°ôàzš«5•¯ömÕ„úyI'ˆc2ò~gb:g°ï@{%þ£i¥iÒønu‘4þ¤G)‘’Ä9ÝëéLÿ‚yOªè_ðQÿŠöZÌñ4Þ-ðƒ|[)HºEíÒÊ@…&;¨îqùnÛêìmÆ”×!’vDØÞƒŽryãð¯2ý˜<í þ Ëà[« †Xµ¿ x¢Öå6Ȭöw¼®ŒTƒÓ"½\¦_½±ÅŽþà´Õæ‘ÿø±¤ø’Õá·ñšvµ§‘ó%Ä kE›Óo”Èæ½JÑaŠ(µ´Ñ™ÞÙASµÈ £×Žk¾ÿ‚šé#Bý¸<;,’—ÞiÀÈO²]4`ŸcæŽøöÈy‹§iV—Zãý!Œ!Q™#û£''?•FaUf¸Y^š.Ãasx./¬ßʸæ>W9zö5«a£\E CUHN»dÈ¡¤ 9sÎsíÅp­<ðYÝÝݸÓÌÛ#hó½Ô (úcžëFÞþ=Q.%·¹–æ~埔RÝ@Ï¡ô¯9=#EÓìôÉí|Oàtšê#.ù 'ryƒlýÞ èW÷º.³©ÙjÚ”Æ$ya/6ù.A Oa»ã­y†¼[ã›-XÁ¦Î|«0ÒGq±TJ |ÛóÁéÀ5¿¬ê0kË÷J-`bÒÊ1w<– Ï…=(ñæ….›¨ÞC3¤ñ+mVŒ‚Œ™àŒc>Õùóâ;Ôðÿ‰ÅÜê|¦iF…%²Ò¿Nþ À,o,­í<Ó§ÜÀ$¥³wçtè+ó»âŽ-µÑs*ÇåÕ”Œ œàúdÖ°‘…]Ïpðv¥ý³¡dÚ´7&þå$c–h‰;îO¥Y±¿M"Y—]±H¡f ÒB~h÷ ÔƒÓó?Âív}C§¦OÙšiK&KƒÎ>˜é]ž#ñvµ4ðéiº×žGùwŸ¼ îÙè h›2=æëH³wÓîÓl;Ú “~fõ8#Ûšç«§Eȵ‰#vFÿXà e¹ÁçÓxoC¼·Öç½’ÎmÚàþè±2}ÝØÀû zþåžMÂ:Ló¥¼S¢Ê¢ESŸ,œe”œóÜ×£Yø³Yÿ„†àøjkxAv”üÙR>èëÏQEÁÒš5œ…Ö­q½£Y$óI8SʪƒÛ¶}ª¶§ªèÖ[dÔ%š2P:,)€XŸ»žÞõçMâ kUÕínµKÀ~Ù²Y\)B¼ ¤O¥vÚmõ¾©-…ôßoä&;…`S ÁFâ3ê*¬NËCÖ¼5cá׺×ôÆ»šö";ØwbŒ…û§Ö¶¯<3u?†!¶Ñ.£·¶Ýä›±+9ŽÃ±5^â?>‰am«j ¦Êá¤T0Ì£’w#§Mr0k‹%ì²ër5´ªí=¤ŠØˆ&~PsÔàþ2/”ê.t3c¥Ãm¬I‡³("°“÷Ï¿q\n£¦ÛÙhí.„$™®YeUP2yä)ôã‘Ò¯]øŸÃV ÇªI5ê¼,Xdã$ž§'·^/Y™~Ö¶yvÅ•÷ÿ¬W­J“Z •;£ùïÿ‚¥|‡à'Æ‹ÚÁ‰·†> (´Õ¶ÛVsŒ…ó—…'xÛŸ˜WÀž :&⨵Þ̨ðN¢Îåsk÷ăó2ÀÏ$ñ_ÕGíðsôÁíSàߊ,ZþË\ß2¢ÞP3 £p"@¤cëù8:Lþ°¿øñcM’Û\ðeÌÚmÔr†_<ÆÅVAž«*Œ€89à×Ó`1<ñ³zž*+º5á“ÃÚ̶Þ$ÐYÓIµ…¥xRݙʠÚÎÄd–$Ž+~ûE:µ…ΡoZ-œ(#`8¿È?­gÄ&•¥ëþ ñ ¥ª^ŰiÚJKxɆpàá³÷¹ÓÒ=n ,µ+ JmAK"Ím ˆê­ÑYççŒW{g÷Áêv«mª[Ín÷rH›Q˜ôèIëù×™x‹áø~îUÕQ É’¡ºœçŒŠô¿ ê7¾ñךìH&Ùå#H$9û¾Ob?†½£Q}gR·C©éË>š‹#É"° oáØy,{uªóÇÄúdÚ&­$~Šââ5E+3Àê\ã°Æí¸Wruwo³ê04rÍ ©]§¾qëëÞ¾¡ñF¯ã#}«›iÇö{e Ÿ*%äŽÇó®rÃÃÞ%Õ5+K><̼}??nÔåúWÂuñ€¹½Ó$·ŠÎÊ5šñ›*6ä¨àdå˜a@êk—¸ýµKÕXMÒDd;£¡@›þîæ$ç½s_xxwEÒ,4;¿Üëì®Ñ\ x<Á+Àw,€|¹>ø®ñ¼!§ë¸YlçP¹Qq vӈᶅOBy2w{uîçœ_¼CáíBÒ÷O²µ†æá£HÜŽ *½Û½eÝ|*’êkµÕ¢[kÇÜ뀪 œ3×ҾͼM_ÃÖ6׺·‡ÖïJ•ÚÞ; À©œõ)Ž[g|Õsðæåõ8ôT–ÚÖi]ï.. ˜ŠÂ }íÙÇ<€:ÑÌÀ2|?µ¾†öW¹†fcÌœâ8ÉùíîtZçÀß|;œiž7DÒÖå7·pæY¢$m‘$…n1“žzWÝÚ‡€~è¶QZø·P¿ë1´Q5Œ(|ÁÎv–g$ž+ʼQ¢k‡‰¬|Q¦j3ëeˆòíæÔ§Þ‡ #hUã` —&Á‡JºÄIyk¦Ãureà°SÊv<ÿ?Ê35äžn™†æâ8”€„òìN1žž WÙÿ~>‹âÍÑIlð‹{{éZ9J«Ç0'bä}óŒ|¾¹í_4jZŠn¯—YŸNgÓÒëm¿˜Hˆ2ä„,q–'’F)©0<‹V³Ñ‹İO„.Rl ­ß8íéYbÉ·¤q7˜ÍŒŽ˜üëÔæð•½ømGQ»‚Æ[—vxTSÑHõÀé\ÝÖm$¯s¤K,ÈË—_,‡uȪ”€ã.`kiÚê§Uc€2Ý«ªÖ|1®i’ªß[4FEó@lgktéX±éÓ³bDàãUtaçžÇ‘ô§¸È®‘47Ø2Âv‡íùûÖtÖÒ¤ÞDjIPbÁ+FҨʩÁüi¶Â#¯µi[XO4âÞ G<ô5vöÍb¸0Üa»^œ ç¶5&Æ­+‹e‚O$¶[ñïÛëP{uö  eH¤«n (uéL1óŒs@épÕ>¼íëN IÅVÁi*Ì‘H8"ž¶Çìþqëèzâ€)ÑVŒ.3•<Zs[Ê –B1ÏJ§E[XY€ pjU·‘†@=qøÐ½3øÓÌD?úõ­›r\Äêc`ÀÜzSÿ³ÜRw6qœw”¹€ÄH¦íãp9®¾=ÖPÒÀÌ!_Ý‚ØÝæ{~•«/…- †ÒMùi•ƒFÇ ¬§ø‡¸éK˜= óíaÛ·­=!,8ËgW­éÞ¼¹v}%Zwb« j2X{ç½Y“À0ý¢}Dù^K*4epÛlQÌŽù#h?‰öµE"1ˆ¯Ì=ù¯¤4„·:³ÇadÂdÏÊÀgœ9·=ënÏà_ÚoÄ$Ò :4A;·-žÜg¥ó>S٠ꇟJrÃ;°P“Ðb¾É“öpM>Àêwš„+oå“ò±,­œmèn½ ²î©w­ÈtëÆ±‚Òå-¯/ bTµŽX÷¤¥s­ÀÏlòÑÌ‚Ìüú1Låº0aÁ`Š·—ª\©6Ö²¾ÕÜÛQˆu<•ú­á¯Ù¾ÇGTÿ„žÉ¯IßfÑ‘*’UX:>2{×Ö~ø#áxÆÛ⃤Éö[%hI˜˜}¹Èþ0ÙëòIÍ$gâÞ‹û6|hÖ.­-ÿ±&¶KÜyrÏ…B=IèsÒ¾ÞøÿæÑotf×þ!øÚÚÅá–H_NŽóË(H”1]­žÞ+î¿è~»ÖãÓü—íÅÄ—0ÊwÄ–ðäî]Üà’0ø~•ìšr|5±Vµ¯G°]B†æ;V&D—UÙy2kÖ“Ø¥Åþ~Ê>ðm•ÒxGNröÖñÏys¢˜¢gA‘¹,¤åǧAšõÍBÒã—pZhw2êP,¥ÐþéæˆðTp ºóš³ðÊÎÀøKS_ N²Íw9kƒ+”$¥£VŒc’zŸÂ»ø~ZÊÐêlm®uhᘛåÛÊ÷ʰRr?‹­G3Œ] Âòëšõü²ÁöølÞxî%;d2îP£8ÜNc Ãø®çOMgQP×WÛÞB°ù«Qü¹AÎsÔŠ–Ûº/ü!÷Æ~–ÒÛ_û™©Ëqe>Ÿ©ÝÁÀ'©‘¼µ°sßÒ¹WÔtؼ#.…zdX5í®"8Y3CÏXÈóÎx¤+e¤kšt³éõç›öÀÊkV GÉY Æyôí]=­÷…ôÝb[ý~Ö[;­R)<øL‰3B6Ë*mçî㎾•¦ê^ñ»© ä°jTr\[F£6ï©VR¿SÓŠ­y}woem>§#ÞÜÆÑ¬VРn~¡;½êh´M῎|/…¦³Mc=Á¶’ÎhLs…3;Ná×ÛŽµ7…>ø/áç‚5øOM/NÕf‘žYfi̼V@ÇpUÆ08ïYšRêw^5Ž läÝ7—(“¡ò—!ƒ2g8­Ø¬´¨#š®ÚXî'Ž-)íÁ.Ïï©èPð£m.¥¦ÞÿakP™î-âò£¼ä–9À@¸ç¼çšÃM+FðŽ‡­/"½‹L/OS‘&Ã!2#6qŽã­wn£áÍ#Ä#ñY^ÜjSÎñ[Û·ßpr¤²·+·Ó®+*ÿH´×ôwÓmíå†%šO³¸Ncán=ps‘ëS$ŠLõ]âÝ΃ª%«Gi'Ù™§d-ÂCµã«‚8Ç :yKwÔ°mt»hó³ÜüÊò)áãP];‘_¤¿?à™¿þøjÛǵ6¥ã}B4]9#±³3>0ëó®6¼IWŒ+õKQŸSð—†­¼1ðƒÃ±ZéÖi°µ‚%¶´‰Œ-vžž™bzæ¾34ã:t“Ž]÷=l6M)kSD~dü+ÿ‚QøÖDºý£äÓ>'”Ž#H¹kËm -‹óG%º¹KÀ’¦U硽7âu®±û2j¢þ/†°¿ÂèçŽÑâø®ß階ö’ýš(šx&)råã,?D,ü;ã‰7]_k66á×w—±ùdc’ãÓž¢±l_|ñ‡Í„ž7ñ§ì®#xµm+ZÕ®e‡™-á¸UÚ&)WÞ¿6þ-Û[|ð­â¿ø%GüY±¡ÝZÚêEÔ´zúù‚Ái wªÞLϼºö_•Ön-Bé÷_×õærÖÆB7W:OÚWöGøÙì¾.ÁS|Eüý”?øG^¸ÑuTùeec –Ës ¨3|N 9%êºßì¹ð—ÅÚe·ì[ðýþ-ªÊ­ªÛÜùÚÓ]€²<÷;ÙÙö|¯–;¸9ȯ“ xm[ÃlµšM31fQ#m%éô⽈ÓQVHòœ¤ÝÛ;=/Q´Óô{­ÛíדZ;Þ9­ÀV\å¾¹Îj´½µëÙëš,RÏw9Ï ó~RUz1Ú»-JëHm-ü36 £sþ•nì^F#$tÒ¸ 7þ?îõ†ÆõÛÃ$ñÊät=*¬#ªñ~7‡|Af5)b‘cQR–Úåßú{w®{Sñ.‹áû=‘,–ú¥¤ ©r^=Ù3Õ–¹›¯[øËQ·¸ñLmsåL[D»º’C“Ð…<û õ¿ðˆa¿Ñîna´ŠÎ·7D2FmŒ†$cÞ©ÅÏã­gU{ Ö’k[¹Õç‘Æóå•Îà£ÉàûS¥¸ðΣ¥\Ú][¼R[ïX‚!Îâp í#šÄšËRñ.‹q©d–8ÒHdŒ€celÈ€v%yµ4ÑyöMu¯ µ ÄeËuÚã§9UßK èzß…×SðüÒÏt3ö¨eÈ-å`å½IÇíY·Þ^é‘ÞDýç 呌gÛúæ±´OjVz|QÄŠÎêÏ8b‹B@P}A^kzׯ:^©©\ßÚ2G-Ø 4sª´NtƒQcK¡öºö›£Ã^[¿œÁÕoùg#©äÏËÚ­G©é£S³ºµ‰\¨>aeó"Øp9ü}y­ËÙü%ª£ÔmCj>g”|òDL˜<§jå5Iô€®Úô«'—åYû žd¶vÚߊü=.‘§Áâm-´æßr#–Æ"æH•ƒßÀ>õVõ¾Ý¬Xêš³ÇgygåX=Â’yÏy® P61ϦEá­Aõ[°ó ‚àñ"²žWÓŸJÔ›ÆZŽ¡¥iþºž[[+Õmþ_*b~e= ;yïøR°®Èµxîí|¥ÖÌ *\qùÀQ€ÃhäùUÝ_SÓ®®åÕ-®§[{§.%Ž6oLuã§µeiÚfžÚ‡ö¬Ð-ðLÇ<öÌ^er9Ü3×è8ªªÚ –·óζ«çGjžfà’cæ\ƒÊ¶Zìí´ í ¯"µ·¸s-‡¸ÝsâÎrsƒ_=|Oø ¡xi¥ñŸ‚ô¤³O3Ìf´_žB,»÷­{PðeÄ6r_hŠnc;Aò÷~ï=7×ÜÕí/R×5ÌÓGyX_¸Øä¯4˜&|]¤øîæÖm6Þ V{F°*òDüî 9Ík/Åí6OÃkâ{h5H­Cyb”9b¨+§hÆF }i®è^×lñªèö÷0»|ÂHPî#Ѻ‚j௾üÑõNÂßCˆåľs‚7¹G¾0sPhA°? 8ø‘iú¡ôñiðI=¶~Y\`# 9©w5V±ôÿƒ~xCC†MbÊxõV¹bɇÎÎ2Æ5Èž„Wkªøj/h‡F–Ý –äˆåó#ó–:Äü¤ön£µyÃÿ |JÒ5ag3é·OwC³"+C"MÄž¬1‘ÇJô™üs¤øSÅw~ÕîíæÕl•so” 78Qõ¤£á|ñ¿‰üWm¨É¡A¬ÏÿbIdîkeÌ•Î;ûW¾x{Fý¡¼x_þðô¶²»IýÅÒÌ…Ú±…e ¹úß5õÄž$ñZtZ°Û%™\¬«ŒÏR¤}}kœ€jk±®©sö½.ç„xÓ•$Œ©AóíÎ}¨‘óŽ¥w㟠êÿ¼geÎK†9’&èHuei õ¯*ø±ðãáï‰4›kK_ŸÜ^ÌÐeÛB²ÚÉ"|Á£\«ÄÁs†G Î ~‡é1é©u}a-ý­Ì3NѵšÂUÑ6ü¤—8ÁÏzòïˆÿÿg={MšoˆÚe¾Ÿ‰Rd* ɽ¾ï—$Yë´ähT~røáÄJko i:µ§Œ,àfK[oßyN¸dfA–åT ÏéV?þ/xwQ·73ÏnÚc÷™ *ìÎ$í€Fx¯{ñì‘à+»Ø¼kðò-{G¼‚’lfã|!’@ØÏ 3Þ»¿ ü6ÔµO ?†SÓ5¡¥éwWIq¦è°ºW{‹i6Í“ýåÇcøPC1çµ¶ðýšønÖçÊ‚ /’‡cA>„ûÖ¾™iy²{‘u,¢GE…¦`¥\Œ•R8ÆzYÓøš×Áp^hþ'Ó\ŸQH71–%vÛ‚ØéžAö¦ëÌ|#8ð•—MªÊfË*Σ9 œã·ÐšwdÂ÷>`ý¢>|`ø”ç[ðÕýÍÝ…¡´’Ñ 2 D¬7˜˜|¤‘‚ô<×Á:UÇÿc‰gA×íõ [Á7rÄ.$òåF¶’UùÊ),±în27™~l†ýq—⯅´HþÄu½ÿsŽVwê2ñŠã_ã·ì›ñNí¼7ˆÒKû•H¤¶Ô÷[I/”ç #ía‚Wx#R5¹ãý©> |^ÕÓÀZ„ Í-ÂÛÚBçd‰PFpAœþ˜k¬ƒöak𺗄ü9’8î!Ó®•¦VdMË2¨p® Ûè3ŠË¹ý–ü"‹5Þ‘ –VrD ±Î"˜”îJÊ­Óº‚@í^mL‡-£cxæuVìá¼Iÿýþéój?³ºxÚÖ,µ–æîÒßÍ<”I&Öœtö¯’ÿi?ø(?i_ ÞhÞ‚5Ónž˜4»øæufá¶Ý¡CãÉ0"¾Ñÿ… eùþi¬õ .ŠÆé"ˆ[¤ŽÒÑ;PœeT‚}käOгGÇjš—Œ•t­jå£X$KÒìÀr±aS¨%Ãc±­°ùF„¹é­I©ŽEfyÃX>/ÉáwŽÐ¯Ù-öÉI+ È·ŽU€c=†1š÷_Gñ*â+ÍkÀZâÙ¿h³¹ FgÛ·Ü1÷†1\vðËâ¥âÃtÛë hQÚxß"J»ˆ#©“Ú¾àýþx£Ã^)¸}NûÊ·’3"­ØGó™zgvïƒZõÆßS´ðûxƒR°³Ô4›Xå–çlwV·ñca#çÁä¡î w·†´™ïµ(¢†ø;LmàÇÙ„cï¨ç®xÇZéîÛ@Ž÷>LÖÓ¨]ÝJDIão9;ºŠâNŠËÔwÑÙéV6>ºÒ¦ºxüË´G5¤bL®y}¯Ü@Ïjå< Ÿ­ønXtm5ilïfià[|£IÇCÔMpZ†ƒ6§§ÛÅ£}¥&Īî¥X˜ç·¯Zm´zÍŒQ7™,ñ¾Èg™7ð~ú°=é].­©i:6­ua¤æ<ù¡ÂîL7NGEf6˜$–úq‰d•„ ÉåäÉÔ ôö  n4ƒ%ÌÂóÂÆXPÄŸºÁ‡Š¥â/ø£^Òí,fÕåÒdY É5ÖUm †ûØk¨ºƒTÑoÓ@¿ˆÉŽDaI2(¿¸ê¥eê¾µ}fä_ÜvÅ¿å­ÈÈÎ=ꢘ$_´¿€þË„uK=NÊÞ6EûdDe^„ã Ü·Z­£|~¹ñNúOÅGkª#í†ëOŠKp½þL,ƒ×'Šö­ÃÚn­l"¶Ön^4™a˜$.x ž23×Ö¯ë^ »ÑN™ª‹†ŠÚ ‰7OAξިØêß µO ØYi·‰oz”êw ·#-Ï^‚»« /è>3‚[ûˆšÒPä Év/^zqšñ|=Ñu›õµò·îìSäuñŒ`äušì´½÷FÓâM:Iïn ™e}Òy}ê~Q׊R7£Ô´/5=I´ †»š7‰$ä• áNAô©| bÇÄ¢Ú}3T²I#[emÈX÷ ƒzãŠeÔz…ÝÍÒøž2÷ŠU^& CÝ0½EszW‚τﭵ£*ùÏò¢ÈÛ|â¼ç'ŒŒr3žh ³«ŽËÄ7Â(m– H‘”yŒ`~b©½®£m«YXÍÞ‰QSÊ+È#¿=úÕkDÐõ©m®ZúîÆICáí$Þêw(ëŽqéÞ®x‹IŸÂ:«ê¶ææXÉ÷I>edn²míÇ^Ù .ÍíZóI>ž-7}ÏÚZh!r®JŽ»sžýñ\aÒn4ÛÝÅÍØ½·’di£dÎÀÇ®ø9³¢øöûOºÐoíZQ“*Kó6AIÆ1V5.ÓÃzTº¼RÝËã‡nSwÞœ$uÙéã^dÔ5K;1 éÍw¾v8Œ|„çå|“ÓÛXMŸÿÕýû>›æ3Ù]ÄK*–fM¤>êGµWÔ„Ü >"V0Ae^7Îrz­QÔõ6(¼›pE S–$;’GCU?µ¬§º_2Íe–@PÛ°'n1À÷¯ƒ>‰#Qo%¸x ƒ„¯ÌßCéô¬Û¨¦Ò·ÂnGŽD[°Ó=ëY Žö)¦Û²T¤í8=0@;UHÌ)j’œä–üx £?O{>®”äFÊí´£$÷ÿ*ùËâMÕ·‹¼ëk“,ûز[LÈp:¡R@<€z×Ò~!×tý/Ã1AeÆdVV ïÜg©5ñF½¨-¾¯4~VØþó8 ñõ hñÝNÊÚ#äE• ò«í9#±=y¬xtôµµóNö áÝùY ÿ=«¤½¾š+•:†å2 œ½õ=ë:Ž5.BÞpl*ç#wLT7r’°·H`v‚(ÔÁl}:ûS ·Š–â`²1ùvòFzgÒ­¬–¦Ê¹b£êO©“Ä’H7%Èb»þ4…mKL›n|²YÃñÈ•~ÏR‹L±¼³)ËíÊ‚Á÷G¿¶)šf•sh ¬R]HÛ‹rª£Ôžž¸¥±Ó.l¼É5h>ÑsvN[¶Aã>Æ“v-"Þ™q;Í5ý²ÐE…m¬Á±ïÓ&®éQ­Äê:ŒªQò4¢t%‹zv¬ø£Ö¯õa˜6R"H\nàjíü?£]ÚÙ¤w*óÈ„å|¡Œ·§¯·¢ØÒ0dÖó IR¹G(\…Cràœc­(u£XïÖ IÚRÇk.r3ÿ׫±h²%¢_<“ùÛÎÈ–?•qÎW<ªÞ&»Ôo<;Öª’ƪÏóNÙv ãæ`sÓÚ°œ’7`¹ðíŸÛ¤ºK9Ø.ÐñM¾îxçӊǹռ5k¨ùþs4EÐ[½Jg¯L ån"·•¡º¹·Žx.ß1w®qÔ:ìßLuŠêÚ’¦¡/&qêŽýë#E±CHѾ!ØÎ¾!ÔP¤Í0XmD”ÓŒ“Ó“ž•躧Œ4ë›mCR¾he?8wa!‰O ägð¯2»¶mZ9ã¾k(¦„’ÜÛÉ(–fë´»@À­¸³´‘cÕdA ÀI'Ìê{nÉÏ­,JÚS]ý¼Î×7"fvŽCú“!€j ¿í WŠA"\-ÁÚû¤âÀúW9q{¨ZÛµ¼6î±\Ê襗j Ãå H䯴<5ms¤‰v²LÒá\ð1ÙXp2}ê¢L†J#Ö>×¥Ù\ýŸÊŒ3åw'¨ä{Ž•zÔ|$,c¼2ÙDWËŽhbÉ X/e$t<ã늵:m¹šÆáí­®,Ó8C!’Fê¨FFGLž*į«§³Þ\½©Ê¦f$¨tãðh’ljnSOòíÖÈE¥¢KRI•Ôï1ËŒçŠÅkÙ[K‹OÓmü¥•9cPñ®?¼§ǯj°š-Ê Ô&‘u Ô ¢íŽÍ£Ž™ãŽœÔiu«ë¨Ò!·!p$¶²]„¯ûÜî>àÐØM¼B;qöhíá·XÔ9¹—ýê¬k1¦Ûܸ֒k ÄùiàoRN=sŠ›^Ò¼?y§Eok%¿ÛåF ¨Yœ¤mèl÷5JQ–õ4{8'W“h̉ی¶áü#Óµ"ÖÄéªØ¤ÿf6!§ V%fû…?ô!èk¢m?U¹–4†-— 73ßé´àWEeá*ÂöÎo‹è@R»zà·5ßø3Â6ºŒóµÌ7<»ä8R©,8éƒÅRBjǧü7k‚·—Ú3ÞÜÆ,$ÉÆ6žß{;Oië¨k¶¿ag$ÅÚÌ‘Ž `3ŒžÜ×]£è·qZ¶¥?˜Öðmeg ÊÅx 0ô&·- és_<›K9ªìäü£ÏªŒÜ™ˆÒj’*é£Iþ°ÛÅ †rHàa¶å[žÔü3§Ç?Œôû ýC3Ú4“«Ó€7(ÉbÝÏA\ý͇‰ti’ü[F «í"«“ú·ãVØ]Þi2k¶úƒÜÝ\åfGg¸0ú.ó¹ôÓ&ãE‹Úiïsâ6S9mŒ—T'bzŸn+¢*ñ³¥/ƒí®¡ÔV+û¤ŠÀS’]‡$tÜNMEu¢Gâ¨Ò+xe2¨ÜÈŠB3€8þ¹¬›ñ¤ÛK¹Ó'pŤVÚX€zõ«™¥}áWJû6qk%Åñ‹i2ܲóÒ@@ÆíõÍ =&×JðÞŒõ£+{4×%#.?çš1Ø™êHÇW¯G¡ø{Ã)son·WüÓ\¶JNÆ%ª”ž"ðÓ麎—3ÙÈK#eâ%‡@:·×ÉhQ¼ðÖe§Cyf“\î̳ "žp#Q€ÃÞ·¬ma²¶ÓtÝ—r]\y%·ÛåÃìrIÉæŸlš&‰¢ØéZÒD·"Ço%€çÔr=òkkWk­cPŠÓWûPÃÄ·°O²Æ¸ä”üÄž§ñÅD˜’¹Eõ [Iynn"¾*ŒØ–Иƒ)ûÎ#eÜIï‚=+”¸ÕµÆ³®md}: Wh¼O(²¹ë·%‰Íu·k­YÙËg6À#²2çî÷!›Ó¿­K®iÞ'“‘Éöav„Ä…—8¹ OÀæ™>·¯kZõñÕ£Ž+(F˸£å8ã‚UÓtY´õžþ-bâêÝF÷€ (Hô'$ÜuªƒDÖ¼M¢ÇwªGip¯(Xã*௒:¡k&½àý.’Ê]CÉvŽBWƒsÇU=NK$ŽÊ;]IäX^ bÄ!Ü$r3Tã×î"ºµÑ!³ËâkÀg×4‹‹ß™Z íÓ’Î3’>ð#°çYÛÍgá«+BÕïmm/•c»;À2„'ä3…çÔ{Òu,;¼€‹%P6È ™cqSŒýk£ø~çNÒ'Ó5;,²4‰#I;"¦âIbª8ãœs\ÿ‡Çì¤LŸ;&UITüIúƒW¼ªxNãO‘<+ Å¤ÜDí%õÕÌŠn'vè¨ä–HU,¸?^ø¯’⸟Ã?ðPïÙßÇzsïâÙ4+ N¶×Úeàã³n—ËÈô稯 [Å:6“k©j½¶°Sf(Ü~È<’2kåŒÚŽ—Æß…ž4×.™¥é~0ÐõîdË=´isÁ2pûö3üñšô2×jÈæÅ«ÒgןðU‹;»?ÚÏáÞ² ¼ðÖ«m¼Œ¦Rö&Æ^ñ_<]éw[é} Ìò*–)òªëÀì+ìoø,m£éþ"øIãTímWC~˜ŒMh÷{Éäc6ûG»WÅÖN°iPëev´…2«œïÆíÄœuèklÊ?¼3À¿Ý£}m´ømV-¢6þ`R#mùcÉcŸº=}k¨–bCpÿe@Ðý«É³F–e+‘´ã#æëÓŠñŸë6‰¦}’IJ‹™ŠÏîËòz};ñ^ŸáŸL¸ž-/Sò¡D1ȹ ò7©#9=+ÌqÔïLôÝK-GJ´¾™DrÍ)Ak· 'LÆáWtmÅúLlúÔ© ¸î|É ¼œmÀÆHéè+šÒu›VÕ­íomVÞÞÞÝ–ÚDrPLy ϯLŽ•ëMqsâé‘›Qå Õ'–7åÀÎÓ‚pIõì3PQâú¬í5¢øjÚãÏ6ÎnežQ‰ß*‘ #AÅ|yñ“F²¸¹x»&Èãwb:{רwšzéqjšjI°½Ë0žO¾Tƒ€`1ÇjùçÆ.µ?…¦mfA †8î7lÜH2îÞõ¥7sÄùÃ×?‡µXå˜l27“ŸZôé-õ›}âóBµ„,Æ7Œ,€|Èw9ÎpAë\]Τ]øÂ/"-ÉBJ“Ì:cÓ5ërøoÀÃÁÊöòGg†æ8e åJXåðr3Ï­tœî™&±$­¬j '¹Ùå}®8ÃñŽõ‹s¯I`šõê»Á$®Pc8 ‘´·^jŒÞ Ö.¤ƒP²¾H$!Õ£æLäñÐÓ¶kE£Ó4åµÓdLo‘Ž]¢”\ úu M\ó«M2gÄòé6E-ÁW.ÛJÆJ ¶~½jKH–ËYµŸZ/µœÛcË„ 8ç ñ=±Ó4)\øƒÂ6"x~Æé.|»Ö½ûCÆD›Üñ´ž«ŽlV¥ôþÓü=&£e©<>%‡3 .­ YÌøÆ;AéXš­õÞ­yܬ·˜]¡ííò¨Hry¸®f '‰¬$»ðôË äi+_HFCù|¸žHû¤qÚ‚–‡«øgZµ½¹’ËW¼7ì·^M+íÞ8‡%Ž0KïÍtÚo‰æñM¯ïàCarŽ»òˆIäè21^!a¦éº°Š÷T·Å»Ç˜Šv¼¼aC‹Û#šõŸÞ^è­[±~ì¼b0>h£àƒîONø¨žÆÊF¾»¡yÖ¶ú7ˆ,â¸Ic.ÜÅðB©ãp9â¸ùu_麂fîHÄRˆ•UyÁ,mŽ‘Éü«¬ðþ¥cmã/ÆšW“äO,¢8.’Nƒ!ÕTüܨÆìçÓ¦9mRÂ÷ÆŸ®5ω\kþ!–ÞH¡7‡ìV›l§pŠÒØI $ŽÆY[ºª«‘wÐít=wáýä·š…à»dÄ*̸àõ8'ŒãÒ¿"ÿà¨^ºð†tÚ;ÁÒü@²ÌÚf¸5&}š ”>S#.B—˜ i ±þI­:›¡Û[ .EˆN`(¤}³ƒÍ`øÿᆉñGá^¿à?ÛGIÖ –Îè(¦0Hà0'!†9s]8PI${N‡àKOxO©êVÆ)r‰ 1ÜÍÆfò@à…ȺgŽ+ꓺ¹óõ#gc„Ð>éÚ^ªš×ˆ®’x\p‘€_pþöFqî:W¦ßiÚ:·˜cµhÀŽØe†z|Àõ=éSQðþ©â(¢Ñ¼ÙÓq,²¯Î¨¼@èê{šß²Añn“$2­ˆy2JndÇ!ý1WÌŒÎ7Y¿·±°2É»Û*î‡zà÷1·ëȬ› /Qñ>»…£C ÕĦ4Œƒ´ €¤ñŒ€rO¥tzo‡¢óeÒüu”šw6“ " 2À§?6yšÈÒná!ÒÒh–'ÚÆ,;vùÈü³ÞŸ2 ûþÑnI¶°†ÚKH K%£î[‡nŽIyô=ê_x¡µ.ËÂzyM?NÓôØìÕP.é ®f’r¼³ÊÙ$çZõmNÇû_k¬ë;—Ž!^AŒÏF p*ä7¿eek¢Cæ@dò˜O-ä#tŠp ‚>Uë“Í.`<ÓÅÖº}­é•d´K7···“æ6ÆL9bT™zö®ZÑuÏëëyá‹d3ÜÀ–O¤ò7ðÎzúŸÆ¾ðž‚- ²’=BÂñ£2¡ .dÈhãŒr1»' Œó\ž›ðÇNâ½0C Ĉd—,]±‘·¡#’})'`>b½ðOˆâžâ+ïS4h±ò¢—¶ ueŠ«máM7ÃúÆšþ#–iôûÛ´‚Hm‚ˆwL#Ÿoá^ߨ%ѺmÂàBXžßí³¾Â»¸5(QÎzšågðÞ£áhí®Îµk3ió7ÙÞT$¤„€KÛŽ~µ`yî·}¥~k¿x~I´A)û+kN­pcBvƒŒª¾@xúñ\ͨñf­¥jžñXƒû6ö8b6á\¿˜ƒï3· »v=«Ú¼Y¤èv~ŸBÒ&›SÕ¦ÔRòûPsþ‹ˆöìŒwgb:ñ·8Å3Kð~¨jwþóînÑ.L ±¯Ù²Û6°ÿWÎçœÐÉ—ß 4 ñvVË%ôw2 ‚6T[c‚ ä¶î¹8ÇJòé¯àŒ‹þÉ ¬cBxÜê;æ¿\4 QÒcÖáZ}‹X·¶x×S·Ô8¢þ?2ÙC,¬øùw¡¯–üSà#ã›Û{È´£w"Fémn‘ì–qœî'Fy¤˜ö££Ésöë‡óeŸ%·´mÁúö«R[êQé#Mkh—a‘™ÉùÈ|g ó5õF‡ðRk­NçH¾ÒÒIq*«ÄS„\±$Œ £¾++Ä_­Vòi/X RLq”9†¡éÏáL™[RŽk9$¹²óe"4)&@uS…zzŽkœÖSG{¸ßD´’@Cy¹XÉÇ;yý+ê _…–÷ š‚ð(,-Ó¡9éœÖF¿ð]´³ l°ãjà™÷rzzqT˜.J.e-z…vÚŠ¤cŽìqך’j6ÝËH •(OÊ3ü«Ü%ð™nÅ'oߟ—Êqß¶0â{ ‹þ«ÈîYˆf†LePîè0zÏÖŸ2ÈáðÄÍ#"”ÿ¬“F:RsK¨xO #³o=§—ËVûŠ@ïÏݽÿCøK}ªOigqx¶¾y+™ÎÕ ¹ã g·jÖ“á‹$³éw¤Z²°ŽVE-°/!»õõ©òÍÇ‚5Vœ›6K˜UŠyŸuY€çòª7~Õ4–hï“ 1‚¬v}1_ak¾Õmá·²ðº¥á]¯æ²…NA\ä“Çá^]§xO]{Óo|Èbø@ª$^Ùíôô þÀ—Îd\öêjH|'s8&,²©Ã2=†kèUð“^åá°šv_ö8ã’zJšmKgŽÖÞ ¬Ñ£  )‹7Q‘óg¦Fhçì+˜&1^ÄÉØ1§jC¥²8ÄB(ù_Ç××Òvžá–êùrP.ã Î6žp\ŽÕ¸þûK8‚eA»€" šMòjhÚÝÁ†%2ew}Ò2jM7Âzþ¯ttûHYæó½2HÍ}a?Ãé¬.#–;„2°àƧÇjŠÏÁúžrºŒL„žc•daØ~ésòä^Õò8'à¨mÙऌqWíü7{+î‰>ÿÌväñô#­}%¦øGÕ§•¡¿ŠÙŒ­ˆçnìÙÆx5«gà[«@¤Ã/’ÌDп™Ç@qëÈ÷ô£˜›Û@y. ± Ñ®ÑÜý}ÎkRÂ9R»å7d)_A‘Ï|×ÑðÛÝÞ#XºÎî˜Àê'å·öo2ÖfÚ®§%™:õúóÍ{Ɖàè¦Ó¢ÕeyPiZ7‡Ê$íäÇ͸v§èºÃ4•µÔXy‰?™™ $á²ì€ýÐsÓµK•†•ÏŸ,|k«H‘MÜsÂñ*(‘½Ž;¨É¯QµømáÉu­Mà}ŠÙÄ‘G ÞfȸfížÙé_Mi?õxõ[v¸³…u DVýÓHŸtìŽý+»ƒà¿€4专ZºšöÚ"Ý-Žh ••Ž~mÙ‡—>ƒå>Tðw‚lþDR¤8 ãvÅÈ;‡AŸcšöØüwˆÚ®£m Ô^pÚ¬¡ÓäTËÄóÚ¾¤”x3NÓÖêç@D¾i[èeóJȤ|§ (ùxÉ£›ÃZ>µ­]^ø°Ì#žÛpK\¨Ø9\r@»ã“Y¶ÊÛøkKO èît¹o‘¯Úa‰$’8€Œû–û cz›ð[ÂÙGPеiìæ½IÍŵÌ]£+åÝç-МT~ŒÞ#ÄwvÉ´h»-w¨F –m¤‘½Ç‚¥ÉÊ]éúΫm}«hö¢ÖÆY3ekq&L®3æ‘Ç$m=*¶ ž'„Y_kWò2ÚÍ-e¶ßµ>ÒL`§þ\‚H95ïZŸ„¥¹³þͺ„S}à‘¾T¶dìXdAå»˓š†©Å«ÛÙXëy®‘C{+$ QQØ oñƒŽ:â“wʼSX³ºÑmÚÂîËN¶½¸’ȵo9dR›€!°FÓÖ´cºðìúnŸ>£M¡Î±ImvÒM‘+ºîI7ÜPMw×ZÞ«¨Ù›Ý?ÂèÞCgi¨ii[•KÛV/9I;Âü›ïr8®«Å×–Öz„÷V–âúê2"þ/Þ8È\~9©É|¨‰¼yö˜´Ö—ánà pQJäàÀÇÍë^«>¹es­ézÖµ¤Z©³¶™¡·V/ ò·¦O>¸­O Ú¦¥âmº‚X\ëZ”’:[Z–‹dP)c¼®B¾ÞpˆMEâß[ø¦ËOÑ´JÖÏS‚GŽ;·_.3÷e”naÛ•À<óß4[Ã^ Ñ5e}zÆÚÃNÒg¿g¸G•Tïº ÃßÞº¯ ü9Šæ OÃÚ-Õί®Ú)}3I‚5’ëy9>TìWo—ÏÊ9 Uë] jú}¯Â)o-îÕ!û ”BÖ¥Æ#2»p×¹NGZälü®èº±µMSí#J1Û5ÌLN;:«‚0l¯Ê€9íŸK³ˆÞâÕD“Aö»yŒrÚ;g|R“÷CónõËèQÂ4ñׇ¾Ð÷;„Îçfl#Þ¤zö¿ø0xŸÆ—^]R-Ä0Ík<Æu+I}ÝŸÅ$ŠI^Üs]Ä|ð‡¢°øAâ=SP³Óô›{ŸSʼ“Åþ\a8\c£ƒJàrÞ&¹ðœvW~‡ime©Ù»C· =¤ìÀ¨Ã¨èOÖ¹y5»/"=XêvVÌ ºåT¼zŠ€i7‰­¦­h¡æ–Ú(åÆ7Äî~r£¾x<»ÈtX`Ô4¿j3ÞS Ä1Ê¢Ùîw±ÈúS*uÐu߇±G{:j‘Φ Ñ‹T~Y¸á·Ÿâª7VVöV6šUÓ”²ÔDò0lÊË!{z]LÞ‡GÑ!žÞPš}âIou&ðóK0B¤v‚2qX·z^›}5Í®ž¿aµ¹³r¤©–6cÅÜ­GGÿ ýµ»¼Íö—~ú’ÈÑþùäJ>ñÁ<úu®ª]C^Õô½ÞêÝollVu…-TE#,Ü–m½vó‚zóT<3â]>ÃUŸÅÚ€¶µž;wy§—ˆ¨÷d€±äg “ô¯ < ðKã?Œ5 x«ážw>—â¥kiüE¯Bú^ çs$…À.‘œ“÷xw+ƒÉ5ÇŠÅÒ£êJÇE 2›Ñ t[}>Å4X-~Ñl϶âÖb ȯÀ G~Fç5í¿gŒ5AÃË=´ å=ÔŠE¥²Œ£0à¿2¨'w WìÀŸø'ßÁˆ~8ëgǺˆù…­°º,M’WljÍ5à ã2I·Œ„ú5‡¼)¥ØÁ§Zißd¶ƒ+{„qÆã¸QÇãêkóüç©Ó^Ï«=Ü6M{:‡åOƒÿà˜î4ÝŽ­&¹{À¼h _"6hÈ+•;š6ã#=ÏjýEOxjKM: 6Î:8Éìh¶Œ!åŒù@p;zzÖgŒ­ü_bÐj¾û-¤×RªÍoª±[yAZ»£Ž9‚=+ó“öý¿¼Að+ÆÐü3µð<~!ñ•ìE´±j&þÒâ@>a ûl²(<¢ ^Ìë_ ñ¼Á¹Nz3Ü…*T•¢Ðí_ÇþøS`ºŸÄ äÑtèåò[U½R-Wå/ûÉ×!`-½€P ó_?à ?°÷„u³øsâ6•â­lÆóC¥xzþ=Jöàà²ì¶„»»|‰3¿EVÇá‰tŸx&=òkºŸÄ \Û­¤@¬²XÝÈ™Ä{0]œžT øóö›øÝñ‡âÅ®³áû¿ÚíÀ¹"Í.|3Ðt™ì»…l‹Ë,bC Ìò¯8qšòïŽþ(þÓ^7¹ñÇÆ­j}wXHQ8Ì6‘?ï<¨“¢)=¦G®j¾‘ðß_¶ÒîuËxáÔ4èa„ed +y„m:Œ±Çëï°“k¨Mqnö3±ù?(Aö¬±¡ßxŠé¬´­'û:êÕåi8˜«ð7Ž$qœþô¤¯vy“©s/WÔ ëq,-iÉk¶^²•$ä¯a×§ãF·š&•®šXÞâˆp°€H^>ðŒW[9å“Gñå…î¥ö[¿ìá¯m}'ÌK•Áx×9õ¨4Auàø­u M´è¤Ç2œÜ#NO±ô­ŒÈô »Ûk›+­”3«Û—ÀN.±Ï'€zÕ ›“£i÷pX]eã(>Ëœùû2œôDz­eÕô}môÈ¡}§E'™°„b›·©Á<°ësF»³Õl/µ-2%t–æzÅÉ. õÉÍtþñœñÙM³FlÛÎJÌ›×)‚zc·zu¯‹¬õ=V k nê6ò.%<4›29‘žyª6Þ ¾Ö´„ŽÔ´ÆçËm±Œd#•xžõ‘âÜèmeÀ‚ÔDjNÑ#íÁ{u=’Xh‘êºzÏmzbDÚX 0Èê1Ò²mžÊËT³ûe¿ÚbHá†çu“«yàƒAMþÎñî©ywzŠÇ§ÛXÙå†CØ‘€WÉ­©í|U®ë(ѪÌÐE-uA;†¿$çÔé^ ×tŸ‡þ!¿¿¼°F³kY­%¶8"At݆À dñôªþ%ñ•­iÐÚøO[hÄòù¡ÙZI\. Ê‚=CW®hWš4—W~6‘dC»—êîÛUp?¼£¦: ¿©£™4xñck`Q°ƒ)žOmÇï@—¤ió.™,šÞ«y{U†ÛMÊ0%#süC'Šó ÃÉ«ÌF—Ù$·}“³œ硹äÿ…dicźL­w¡ÚÏ$ëäÜÇz•ä`Žq´žjþ§â mìåÔ¯|¸Ð…p"BHù²y'óë@ÖMý…os­ë>|ºUÅé´18ÝäO‚ËcØŠÌÓlõgÊè%¼Ì»Â¡"NWhê1Ó§éW4›ÝF º‹ œè+#I#r!’hTíù³ÁœuªƒU¶±žóR“næ%Î$*ë’ON =G4ÔA G¨Ü‰, CqxXË‚ B±9“éšQ¦É¨ÛÃá?Û´’éÎð¢Æv´Å;?{8Ï$ãô®jëÃWבéÚ™•­Å·”åƒñÙOÐ_í„Ò|Am=ÍÔ²]ÛÛ•Rã÷‘°'?/û@ò}:šLI³Ö“OÓæ¾Ñm#µXŸpAò¬QËG<öíS\éÖ—^.Ók+;»áå»DWÒÈT–!sƒ¹q“í^Wc­Ùj@¥Ä²G üêάzŽ ƒ§n}>ÿWˆYéw{%gIRÞá{)8-AõÇ¥@ã¡øÎtñ­¦\4Z4W‘X8E`@CàÎ9ê*ÕœsY[ÅmÐ6Æe˰ɔË(ëŒÔšàâƒÄø\A}5„_=Wd·a³Ï#€k¾³Õ´Ý3L[ëé`&=’,! ‹ü¥:ðy u hg†|%¦^éí7Š.¥ŠñòÊÌO ØíïžÂ³õ†ñØëè—ØÆì¥\…d'p9'Èï]6›öË‹G½»ˆ[ÇÊÑZD†AÆXö+וOIµ³Ðdþ×,UŽe·LIÆã·qïéI¢Î"Óà÷„4»ËOxy_ÍA"Z‚Uá–9~ñ(Fž‡€sï^iªøÁZ^ë60iO³çÔîØ3óytí]-´Öž!Ó¦Óü&-"0Ð*)U2¹àãÔŽ8ª6º-Ö…qªjZ”O%ËÛ2\Ȩ!È*O`zŠ\¥ÆvGÙ|yø¤ø¢æÒæöæÔ&4ÏnÍ~gÌ ÇAýìt¯M“á÷Áˆi~$’;MFÛV†Q ͤ¤K·¦æÉŒŽ1³~)ø3EømÖ¯áØšÕâ‰ÚæËl3³)æFàdg‚Nk”ðÂïx_V²Òôû¹Ý|ç*âBŒ™$ªè;‘Ö¡£Nd} áßٟžh"ðT÷–:s-Õ´×Fkq!í ㌠õ¯[ðµ—†Yc¹—xéÎ)î¿>ÇñF_µèÇN¿ŸÃ©ŸÙîkÍ’$Q&@µ³Ž¸Í~px·áœÚw…üSâ^ÅôvÞxã‰(¢W„ ÀùÆÒAÀŽõú¡Ü[xcÌømã@öQjÒ,¶³óVGDI† ¡¯Í[¶?ð‰ÞxⵓKm«\«C}#í’ b%Ã.p:œmÈÁÐ䉃õx´M;Ãiy¦ë·¸¾¸ócI"FÜñ„$ üéÓÖ½cÀÚ—ˆü@f¿ÕüO©=†ZO)Ú0¬€Œ1aê¯r‹ö>OI.‡áéú´ï‚ŧAs$’7*ü›ÔŒ£ Œñšñ/ü#øÏ£j¦×ì—ú¥ÍÔ(òùM­³,jBíbA¯Pœç¯` ô¯~Ð)¥jÿ¾Å.¡ Eså¬ï|Ù€–fv`Äò»q^›á¯Ú)4ÈR/‰}GSU&8æeÜHÿZShÁØ ŽqÚ¾gÒ|E?ÃÉN¾Ó¯|8º¢Æe—to#+çtŠp¬Aã'lW­h~7Ó!“LÕtH-5]^+‡W™œ £W]®¬„ô`éÎ4Ì}Wà¯Þ }b;ÍcAÕÊÝ˺Úå ›d 6„ œîÆHätçÍß~É¿üM«Câ |L¸û.Õ”[]—·Vprñï„€©È_™K2{šøkÃZ´ž¾·øs¥Ý_i"sý­oÃÁ$.’1 ·à1‘´ô¯ºü>> ülñŒš‡…,|Y¡ê—IåÔGSV³‘b 0Ùd ‚7ìŽô GÉZŸ‹h„ºe燾!x›RÖ¡Õ º•Söc³$Hêq÷wdïP3^­à‰ﯠÖͨø_ÆšV™âoêú­–©i%å®—})™ ~_Èw1÷ÁëÅ~É]ê¶÷ôß³é÷½#@GúÅÏ:ñ¯-ø¡ð§á½“âÏ^JæÌ­½‹Û"kPŒw.åçÜçm2¹ °ý5k?oÐuûÝ>X –{ˆmæm­´‚²d2ˆŠŒƒŒäpkè=#IðŸ„, ðü:­î§vð¬“]ÜH$h×?ÃØ©ç=~µgÀš%Öƒªjimy$ÒËæÁÓ¶æû Ø > ãueëþ*Ó5“4zÖ”©®[¤Ö¢[L¤sFj’Œ>ñ‚(fý®Œ××j.&&Ich2Qüƒ×;NÍiê¿ð˜jZ´VúoihÎ!’úêM‹…7˜™_½ü,3\wÂ0ø[ÃÓh÷ºhµ¾vû4p”3$ùëp|·=rx«·¶–¾+Ó¯­¼qmm©iW1˜M½ä­ …ðÉ·¯±ÐIÒ\i>=ÿG¶¹Ô…I5Æ@èêN#¦+>GÑmg‡BÖen.i®ìÃ’AÈÅgÜÙø¯Gøyc}á t¹­/ã´µƒí>t nVvùŽWÖ¤ñ.³á›…Ž÷⯂§d´/|ûp‚áב»¦ ⪠Eâ/ Û\øTG~bYíYä‡$¹Yà°7~"¼îçJñˆ<{40I§IöŒ}­ÛÌ•æ0¹Jõ/ éÚ:Æ¡¬i÷ò4øKRx…«`yaKr£­ušö¦Ç®]ØÂ¯’œÉGÌFá˜ú?•P8êÞ(Ö¬-u gUÃÞ$m; 1·š>]…‡²{÷­kÞ*—íøßM˜•âš4YQ”¤‰!•†989®äiú&“¥\Ic*ê¦ä3$ÌBÃq|ÁìsÚ°5uÙ<+dö(,#’d¢“w“Áè@#(½@¨ˆ±Ö4¹qœ*‘*²ed| Ý$;G^ß•jYë:gÃë-uY$hõ6KUw@‘«·@N1È8ò+;#|!ñeÆžl|­Ùê[ͽ®¯¤ÆÐ*ÿ¬VLíÉ8æ°<7àÿˆ)ŠçAø‡ªÿÂA¦G-¼Ö`Ê'ƒ†Ã€7… ç4õýÞ…,vZ=„k-Š(ØÃ2àgå#\ú׉¸¾.÷¯ žlDmY#!¶/þËZ—¿`ð¥œ3hÂêîîG~åxÇš g'¾:⺘>4êK5†ƒ¯xsR±²vaóÀ']‡¶Í·>¸#½qÞ6¾×£Ó¬Š.^ȈÑ<1æ6g8rödæªKãYÖþæÆâÙÛlKÈŠ%”¶0Χ®aSêZ¯‡4˜®a´Õ Å»ÎЮäùø‡ÊyMµ‰£øïRÒ%Ú–÷äFd7iJË´ä‡ï³Òª ?Âß<¥j1ÚHðÛÚ²°–êRÂ%ànÏQÚ½'Y¹‹L±ÕlZícû\ ±ˆ¿{Êÿ7NœŽ„­|ß.£¥|E¿¸‚òH.­f¤ò][£|rA* †ãÞ¤Ò¼%à}UÔo|- ý“µ°ŒE3–µxßdãÔc@zÍç‰ü?&‰m¡Ÿô™š4Iʸ)^99Èü9¬òÛÛ>™¤ ¸,äó$Œ¤¤È[ý¬ó‚;Wžèð½–³qâÿÛ‹ÍEñ(s#+HêAaƒòŒý1WuxÎÂòá.4ýE#æ lÜ8‰O'äôã®iHMu¯†õÍr7ƒA`Ë 4*ûJãýŸNüàVæ“àïZÝD“]ã8Œn$´hSÈ'Ö¼OJñ†`ñøÞ föÎòY‘$˜ÅÕ¾ÝÓSÔï®­¼g*ô­FÞâÂICÇ3Jv'㯳ô¨%£Åšn»à¯ýº]Y.<Ça5¼‹GRA8ª·Ñõˆm¦ÕgÝ%Þä·¶¸fk~œºp0Ià‘À®Â]+Z¿Ðn4ÏƲéÆ^XÈ ìèSã‘Ðõ¤ðæ»áëm ÚÞÿ¦]¤l¶C©CµÃá<ü£¦E<ÞøgBò¬µ-0Ù7ýxs±Ÿî6:œ{t«?Ž®¬ídðŒðÁqmò„2妋1Ï Á GÒ·ÕFãQÖ¢y—ľóÀo&/•‚râ=¨ÿÖý ûk‰|ˆ`†âDûÙL*†î>½²kBþÖÅ`%ãBÊݳË#‘Áäç©ïP]ùZÞ©.Ÿpûf‹|¯”©ÿõuÍ[‹SÒlXÚ/Û$O”¼§O\ýE|ô„n¶Í bF\®óœv°íYÖï¹á§Ž<~]¿JÛ°ÕoíƒZç;²s¹@ôÍr^$ø“§x& %¶O>ýò\¨,@=É˽¹/Œ5‘¡èsÃ}$a™N72qÐ WÂ&m+T›Îó|‡BÇ÷¿ Éè}NÕ­ãˆ$ñÔÉw•¸µ7@ùa‹<õô÷¤ö¹$ò™®^Ú6óíÉ ûŽ™Ï}ݽH­¨,­æUò<ÀÑäneÚ¿GÖ¡‚îóJÇ— Ú Á#wc¸­2+ t›Y¯þÈ‘}ïÈ[B? ²¬±h¶72Lq;L1»vUWéÛ?¥tš~™w®h»ím-Ù!b­$ó÷Ø^§°ªïáû[㺳’è™ØV?ìçGw© yÞÔ^2•ùZ%PJƒü…+…˜Ñag¦ ~ÔÒ$…2â,˜õ9ì+PÏ$vñËt]Ãà¤J¿Þâ¹(üHouHɱvKæG¹YW¡ìk§2%ýßÚ"¾‚âxWå‚(бïÔ’áQ)ËAânžêÚ2‘:ƒÛÌ÷íüë¹Ñî|e.¨-£ó#ˆ¦Áæ0B}OÍŽG¨ªWz„÷–"úž³J¥¢•þRËÈÎ:T—Þ'ñ-©…Ì6ëÞj©r±nÿJÂOSt™«{§ø“B™ ¸¾’keµ"a"®îs»½RW¾Õì¤ÐRî)NR'C,üñx S[ÜÅ£[Ioi#\ÊòX4?.ÓÕÈUFŽ8u9NŒ‚Ñp&›* ôÁ=qYfuVÖ×µ“FH.pÐÙ8?¯ÂÁ.µlÓX^À¶ṗt–ê77±ÉÎ\ ضŸWÓmc´ñ%Õ¼ìîX5²¿– õ_›Øïžk¢Ó4­'MEK˜$pŒÁ  ³Ø…Ïó 59M.ÕŸO1h¶ò±Â;È›Ø÷±žc½^‚= ¦S©‹in8hü蘘³ßpÈÅzΕã­SÃ6sE¦ÙÛ*K»|³!,££åÎzf¼àhzÕÁ“ßA €+»1ÈÜ@À”hßk7š„°æí!o¹Ê$LA> cJи‡Äž+ðçÚtùn"X•‰FŒ)¢„ÆMA¡iušFÉe˜å‚0 ê=ð{+–}[Y“Ä®!¸#0~èVxŒ`~uq ›á¿ øBãBžoI«É¬Â6Cj±Ç¨r8gln`bG®+‘W¸ñgDžÈ«Fw»îRã‚K7¦¹.¤/ïãš=Ì®…œää˜ÁïПÊå4Ë9£Öõ]vâ)wyБžHòq·Œ/<LVFý¦¥ª¥êè×Û?x……íõãÀ浯¬ï Eöâ!òØÉò,eyùxÉÍAgªÙɬI•÷vö çFßœnÇÊ=>•· ëZþ¢öÁktÒeŠÚ$ûTÒ3w ŠI>Ý…dAe6“¤ÛKuyµeÁ\çy Žvûû Ñ𶑬iо³¦]¼ºt²-®dL|ßìgéZMá]KU¿EºHäÝ!ßm%¾&F_¼Å}à†¾ð¶–—w÷éò–dœœ.qÇNO”Ðö<¿Ã>’áß\ºÒÚè‚VyXËŽ§nz{×ÐÞÒŒzTz‡Œe„Ú´»U$\€’zŠ‘ÆŠþ™p‘¼,snŠ€y 7J½§hÔåž³ˆíŸ³£±Â±Ž? Õ™ÊFþ­£K¥Ë5ŒQÜÇ æX¥Cò<,xù{c\ìv·z<Ú­¶ ¶ð¬‹¼NÂ9Ô.U{óÓ½¨øÂöÛQmI’¹À‰aub_Œàjà5%‡ÄšÐÑ.åµäÊY"ÊÅ»‚7cŽ=h3;{ »x/OÑ,eÔ"wógÆ<ÁØoq·ナßÓ…µÔ×é·)i‘îò›¢¾y^$/qŸj§§Þë°ÃMå!ˆÛÅ c°õ®‹Ä^[&[¿‡Ú|×ma(kÝB@!ŠÇ?x¯Rzš@PÓì|e3[Øi1Ék­¸É1P G©!ˆc»±¨.´[3LMGRµ‚Òi¦h’>cCùå‡àì@è:UYb->òíæÕ]%”ܶø]Svñ½½>•‘§uª ê“]K{¼ÌøÃë“÷W±Ïzèî¥Õï&awo$–‘(ÖQŒœóϵT¸¼ñN§#i6±>ŸnÅQÂù×:zìÇ]Ýi‰ìdZøI‡V»þɸžat›]ŒŠÈ{œ±\ŒÖ~­àèÅfÒ5I-``š¤r7¸Ï¿ÐË2[]$¼ŠÚÙò­‰ƒŒŸÇðÅ?þˆØ'†î7]é¥6Ä”–UlÏOëI«“rèv^šæ;ËÛ¹ 4ùÄ`³p<.=À®¢ÿL›NKý>Ò@ªÑîy!Ã&OUÇ'שKâ+ý2×H¿³´»µ0²ˆ”ò¡¹`;SZÍᯠøwÂW¬RÊÅnVÙfœ6÷w8ôrN(åB$Ôü;}l Ÿ@[Æ‚c]Ó&¯Ü(^¼G¥Wºñ6©©BWV-Ø X㈘R4é·h'RkG±¶Ôd‚ÎÖòXU’Dò¦‘vÇÆâ¡ Ã*–£áÏS[y<Èb+12m8ØÏ~1šv¡kÍgSX¬ôõÚ£¢ˆ£9FaݶÇÛ<JuÔóE=ßÙnˆ•Ї €º…ûß1Àúf¹C¤ý¦Mñ\` ‡p!qî}óÅdKâ;?²M i³— áåaûÜó|Œç¯"€:-÷LMFµ†6–#æ¯-¸Æ[¶yÏ¥oÝé“êæFòî[iM Í7XÏÊ£€?Zäoü;µ…ºè÷‰<»±,“í‘Ü …ÊýÑž¹Ífxƒ_ñzørÊãÆÖ u`YXÜ4~öѸ€Hx¥dkk©k÷wK¥Ì—R 6ÊóÉå.ÎØÇÊBã8#šïf×<^t«CH€Åh©›æDfÆ0¤^ýëËtKm Uu?Û·öM¤O4ñ[™®þï Ÿº äT0ê!¶m<Ï)Š)cYK)fû+8eÆÁøQdúo®<3ÓžAgj¤Mr bíœ a¹%Aç[Z4:¼›N³•-`Ô$Yœ›÷ÿ€Ò¼Æ·ÒM[³5ãKp§Ì·*SŸãÈ=9#ßøÿÂ~.cdÖ÷ÑÙÇ¥D¬‘É YXH27psÉ>ôÀîµ@½»ÐõK‡º‚b<ƒÐ±×p÷㵩gáÔÑeŽÆÉ’Úhž-£GeîY䓚óÖµXôkR÷ɺi›ÈH”aÂ7ÞcŽ„Êñ­>¡:­“ùv6ñ˜ã¶ L¥S¼õÏZõÛ=CUÔü'…4ËkY¥–A!’hÖLÅO˜à{~µÂ_mî¥Õ¼UªÃ5Žv—Œ´1ÆëÊ£ØþX‘e¨.©htÉ‘­æTáùhÊ9 1Ê´´MSN¾Û éòÿh=Ê:I†éÈ#Þ€5ßWðTÍ ï‡n®$I•\=²³@ÒÀ<~µ•¬êÞ#Ñcû5Ä‚[‹UB‹ÆÇi2>~¹çö­i|wusm¬Å©‹[k9÷ÛÛ®Vê Ê?*ÒÔ¬õíSìê ê2[ Û‘ÔóÎFx¤Øe=ÕÅöwi­ x.ÇÌR¸ ô@'?Ö¾;ý°µû7øAs¨êã’ÊãI¸ ‡ä…a¾‚Y$,y,#BÀLWÜè±^ko§ërFM•¶Ö;¶“7PÍÎsÀõð·üFßû;öSø«_M$/i¢Ovë& S!U^ìØ~•Û—JÕSfŸá³õÓþ ;msoû5i4ûo´>‰â½&î9ÉÀ‹íŠmH=þq0SØf¿?4Ë»FŠòÆú6–`R#ˆðÅŽ[' éÎkô+þ ª@à™÷ºÊ†™SðeÖ Ápš•Œœúd/#¿JüÑÑuOÞ:Hû„ˆ²JQ†ÙU”qŸQšïÌ—¿s›.w¦Mxt]*êI4åó¡hr ‡Ç'ò®º-6yíU’IåÃy“‘q7/?Š µMLÍ;ù±ÅynÍÉùr;šÙ·ño‡wÅgmqu ËÀ ¿ltü»ò^ç¢vvžN¡¬YY ’Hn^6O–9U'€L×{áÅ_ ×u'ìsÍ.Øíd`Žè£æì 9ëÚ¹ =;JûLm´öé{8 £)!ã¨÷ÛßC¶¤ú\°ÿ¢éˆ0rU¤•¹ žµsÍ–rž87zŽ—m«D¦Öíµ%\ä?PO¦:sœ×…ë÷ÇQŠçòê3FÁBè£rŒöÇsé_Kø‘>Ö–ösHy˜?tè:gúׄx’c¨ê?aX¬d²Ë&ƒÁ8-Çlõ­i±=Œ5²l¼MÌÄ®/,)°Ç}EzûÚYë/¯Ã#Z˜æ•%$F q¸÷;zó?Š6;VæÊÒAö˜&Ê7ú¶`xôâº?†þ!žúÂ="úçr6ãå¨Ê1-Яñî+¨å{œ5޳oá›xt¸®&ºk dte ;ð xí>˜Ú&ye§Øk]yßkWl»g ÓæôÉñÖ·u¬ØøŠóT³,DΖ®ûså¬x$1ÀÏLtšç¼E5ðK™õ8|´†6ÂZ/Ít‹Êá› nàqÈIæ§=}èÄu-3V]NË@„´ÆêòÐ1Œ„‰ÎwHÆ+Õô«¶Ñb’òæ6¹uݰ åÈܶî‡bœ‘íï]…t++YµË´•岑àXøòö°ùȼÕ[퉭.õ‹¤Vkuò¼¸”íãŒ`ðsøÔÈi³ÖgЇuEµÐoî®a1F˜ìFLeUT m€sV‡ƒu/ØÇ »…–RÒ/&–` ä@¯9Ò< «Ø£›‹ß*9vMmâÄ$™%'€ãì±¶ðç‰ã³¾ˆ;*ÆÙÉP.w²÷ÚqŒÉ£Xí©J ´«Ÿ/‰í¯D6"ÈÛª4K#‰@#*ìzôàW-r[è_ðèó^K Ÿq¼UVYyˆú‚I9í]¾©g>—¢\ØÚ@_^oóß»…Jç¿·ÍÚM¨éºC Á°%²ÛŽ>SÀ=ûqY «è~gþÞÿðNø?jÿx ÷MÔ#Óµ}/X²Óõ;ØóD½p+ŸË€bv $ò2ãÅ?~ÎZ?í}â߆®õ3ðçÂÞ þÎþ×»º’öú{{T¿xå“–oLJW&5l¬\óšÂð~¶óxŽ]9®¢Òõ-)Œ«¦øü·ÞsŒóÚ™>½5÷†­tù•Çö{‘ ÛVy7ÚËŸîö d>?ñŠ-ŒöÞTÍ›çfŒ4ðùHVþlGͱãì;AsùiÉÉê+åï½Å÷‹¢±ÐTÙ†¸XŒìåbÀ]Ãs ’×Es¯j£µó›‹«ãq8sˆ«áGÞ!›ÇJôý]ÃÈ×t{B–ð¾e¸Û—ÙK¨oQ\ÒëžšþïP–î(#28uÊ‘ÙG@k¥ëž2†kREH"ÆmÓ䩘08?(9'Ö¶ì´­ÃóXêÚ-ž³ö[¨íî\°p‘æcø[ÛƒIÁxŸÃÚW…õ:ÏT°mIdݨnƒÍ,26˜`õ¹¦øbó]QÑ|iiw¦!<ŒãÊ^Çï;Ã¥~˜ø?þ ®ÅðÞïÂþ$ðv‹ªø‚ôL‘ë–Ú9AB¯V.Ê¿pPG\WÊ6^)‹Hð¬ºG‰{Û‹°ò­¼KI ÝÆX·ÔHQŒÓMØ‘æðþ¢"ÓDž{[¥–öÊî „Ëç=˜)Èè@5èí¿‡uÿO¯¥œ:Ežè|¶ûY˜,ó/™“ËHÅ·c‘U¼a'…u™¬bñ²ÚXï—2ÂÆIä8ÁŽäúÔ?u¯…žø› _b\jÞÓîVâçM¾}þ~r¤0êȇÛÝ”S¸:\ØxsZ¹×ô«Û{+ja²%µ¬HÁ€}ÁëšËñ‘âÏx¦;Óìîµ}V MµÕ‹,2ßDiVç”ãëšô-OÚ&ƒouodèšÊÊÖ÷7ñ$V©&A Š0Húf®ÞxÇá4 &±á&JO´A5Ä‚+ÇxÈÚ mRG8õ¤•xöׯt©›Å:­½¬Öjí-œs9ypAEþ#Üð+æ{Ÿ†I>%ýŽÔ†b0‚Ç““¹³Æ ÈÏRkíí[⇌õuXuUÔ6I$ Ä’†;Œ>c`üª0Ä)| â/é^8¼}rÊÛIðýÕæV Î]ñ¨-~®Ü ïíN쀢Ñ-t© ¼”¬ŒSÍqȱäò­ŽÏzì|#á›­6Æ+éü›•–ïqÐ:ÈùYb:W×Þ ¸ðmÿˆf>³ƒì©‰–1æ7œ2°‡ð¯`1^iáÛ+‹OJðÄÖ¤¾‘!ÚkËù xP½riókÁiðݵÍÅ\"ÒÊX/v«¬O,o&%‘†Gqô5«â¥ýžô­ Zºÿ„:É5kÔ¸—O³Œ¢E<ñäòXt¯7ñ§Ã=KNÕ—CñCµ¦­i$Ò-¹e,añqã=k…´~&‡Wº¿]BÞÉTÜ3à°bHÚÊ9ùõÇj–ØÄ?õ}^Ñï¼9¡ÚÉíªÞ„2ùZ°Ë©8ù@Q\мá-gÚ?ˆ´qaiâ™#„év³ó¦v«!n€ŽH`8âºM[Zÿ„§[X.%ÜUgòäm¥ÆÒã¡à~5~Ms–v¶¾Ñt˜,Þ;ù ½Ì€µË²2Ó‚ÙÀè)¦Ð=jÿ ôki~Çui›ÕÞ$Š9ylá€>¼Ux¾ *î%ƒO.é‰;®ü:òJžÙäu¯«¼o6±y%•¾²Ð5—•Ðyl¬òJO˜àFã¶}j÷ÄßÙ|`¸´‡LÐt÷M¥®Ž¤ùÎ1–‘˜£ï×âŸ;ä{j>µ]3Jà]Åþ’Àüž1ìj³7‡µ¿jZv“ª]4VH"´ŠY!]Ÿ»Ë1ŽC ï^ù§Ùø.Iõ>ìêѳÆfY?ÑÕãè9Æryã8©&›.½r,/ðÐ,‹vÌZ‚¯6›xžK.K2 Èù¾îN8Åvþ!øc%Žk®Z´QÛê…£Š4\xÎO¦AãÖ½ïTðÞ…ÿ ]ɱ/s´¨ð†R^hÜ`FÃÔœ×_eðÞçU»:mµÇ‰÷Ìn¸TŠØ™Ô7(è;Ôó0>Óüa©ÜIk£É*ù-œ¸þûSÛž?:”x0jÿè¶òKlBÑ ³)ã'ÓÜû3Âzf¥âk 5 øoÏžÒ&ߨñ“û²uà0ûÜ`wÅbÿÂ?<êzÌAVK‹†¹(–ÏÑHR3¤B(»ãËÿ€Ö!–)æKéƒãl0|ð[Ó¯&»[o‚7†´Õ‘o–9&F’XPà"ƒ·›©Èãœñ_JXøfÃQÔgšþ7GÔmÇ—,qí\Æ>ûÀç‚$Ô6>¼Ò£:æ²òÜ\TÛ ]ѳ‚C dÊ:vühæ`y”µØuH4+m.hïd)Ï„žIÛ²Žàõ-Ð ÖÚxDð߈dӵɧ¼{%›ÎŽˆ>aòýà¤à×»xêïÄ>0ñ›ã[)‰»[áŠê‘à‰zñœå²AÇ5™á´ 8ÏÉ6«x’Å3•%m!sÕ1óósB`pú-†™<útþ´»w‘͸™cI‡,LÞ8{}i5›«½:_ ê×wPA+«Ë4`ÈŽãsaìç_MøÇÞ ðÆ™'¼MàˆÛP7ذñ%•é‚â $S½e·#Ëxöð„19'ŒÓtYõH´É-Ý®&Ù5ãm¥¡Ý„p?ŽLcj‰Jå¥cÊuísÅw¶Öþ+Ô#{‹‹5Xâ$"®qÈÆ¡èjþ‹¨xNOIâ;ÛØ.Ÿ ¬aÑÙT¶ÉAñu#¥túo„´omð¼ZªéÑEö¹${¸Ù¡U…·Caʹ铑\½µšÇ¯i¶ÓKûˈ–fP>HdÉ@˜<ŽrjFuž ñߌ,Õ^[ 9¢{BðÆÑÿiÆ"ã AÈ9¯OÔåÐt?h·¾KMKÄòsöEa5³È[yÕþꜜ„ô®oÃ6~+Ž/lµ;[9fà³°HáÊÐ÷‡·Ô9Õîô©t‹›ù“ȃθ·…ž÷w ÊŒÒws·$µ²»Ö¦ g{öÉ,lS5°à¬¬@ùˆçoB*àzcØøsÄúF¿¢§Š$·ÓtèT—x ­Ÿ™Ž7# g©'¥x€þÉÐD·Ú“^Gq°^U*œ‹ÈÅvÖÚ΋'ÅK½7\³—LÒ¯]m¤¾hˆTŒŽHÈÎ"¾ˆðÏìçáÿ‰7ŠgÚî'“yŒÆz¨·Ú±µ}CMo†>ƒQÀÕµ53ÛLÊ8ØK[®=Mt¡ žÚ _MPFÈØ4m$C,ëÈnO† j^6vòÉö©´Ÿ²ìžB« XƒAЃÈ¡¬ÍfÇD¶huÏÍÈ^6XdpÓÌ£!¦usÑWÛšn•%·ƒtýHêšœZu…Ê‹«{kØÚI$h2}Á sÝOÌq_RþÉß±í%ñU$ð‰aáO MÞn³âÔ’ÓsOþ­­¬AJwpZ0ă1Ÿ;™QæêÊÆô°ò¨í|Å­ø§ÃïáÛ'žî‰Š\Ít1[˜ YÎvsž+ؾþÏŸÿh^ àþ‰tšLQ;ë¶—~ŒÊX’Ý3x9XNþ¡€ä~ßüÿ‚hüøS§|Cñm…—ÄÏXÊ&‡QÔà §ÛN¹í,‰xâ“ÛsHÏ$×è³ê~6–.þŶ#~åVe]‘öÂ~ð™¯F ÓÃ+¾ç¹‡ÉÔ¦ô?1~ÿÁ+þèzO‹ü}¯ËñÅžÔa¼Îz4Aµ„_Ù‹•dWbƒ$¡Žíß*ãô·ÄWwö²ùšÄ°Íc9)+»a2~âçŒö æõ¯ø?Vv½Ö£ºÑîåýì×Ö×bßæ\(-*°]à``ƒÓ¡¯ÎïÚïâgþxU°ý”|[©|CñZ]'Û<0Ðÿl]OfÊw·–¶,±F m`ÞfÑ*Ì|E\V30Ÿ½6{Ô(S¤½Ô~Žx–? è“k¾þÌŽ(£óöÌZ—ûñž„Šüæñoü@øy¨XéÚÇïë7úµãi–I¡C6®4\¸w*/Vf\ŠÂÑf_~מӿhÚâηàß…±L߃õ›$Ðà‘lÉLµÄ¢+” üœ’ߓ֩|Fÿ‚¯è^³—áìáçžX'’Ú/êÈfKÉäb·b¯*±Y%hÕ€Ü7“èåù§4œyŒqxøRÑjÏhñ~ƒñk㧃bø•ûCøûNø àë2·Rªj^^·$ÿ«–2±¬2È00ÆF• “ÇÇ—ß·Çì™û0›ý/þ ÅðÎÂVý"·½ñίh#º½Ø6¬„>.¦ï‡œªž¡Hëù§ñ²óâ/Ž|k}㿎Wš‡‰|Kmy-n¢xÀ-B0±Æ"*ª(ÈIæ¹eŽÆÈ, .ĸV!J€ßNzc8=³_}•ðÔi{Óû›Äf3›vf?Åï_¾>xÞmoâRº>LbæfDŽÉ[hãˆ¹ìª cœàV=œ¤3M¨Û5³é‘GûŒ¤ŒHØêÄ{ Wcq¢ëU–9Xu+4ólÊ}åTÉËᇭq׈lüC¬Yèz†¡¾}‚Öä$€ºîÊ9ǧa_UJ„)«A\ªÊ[žŽÚ†‹â-?UÕ¦ÙmçÊ-–áf{uýÛ«ç<šê¼ÿV¡e.¼n×˵¶Ý,{Ú9Fé…Ák {ç5溞¦ÿbØÚøBra·’k§ŠSæJpV0ÀŽ˜ç­zÌÇT—VµÓ/æ·ÎŠÛ™T4Û>bÛTvŒšÞä\̵ñ‡ˆÿ³-½Â<ßc»L§˜2HŸ-ÁÏÅȬ«Ïøª?†‰â­*ÒÒÎÂö&Hí£”É,*@fRÁ8ëW†¯¾$x¶Uð¥í»‹ÏŠ)Ÿj\F1æF 1\ír+2ÿÅ:Ó.m~êf;_¶k0iÖM4~TQΠ,nd<(Ü“À¢â0¦×˜qsýäÐïRí÷¶8 ÀäîéÞ½À¿5ÿx1"¹Šm#M˜læUÄŒ™Ä rT‚OBj º'ˆt=Uññ 3Yi2'ÛàÚc¸ÙùO³Üg-ÏLâ‹°3îœj·M«jVÉ"Úou Œw3•?{ÔàðEmÃk.£¤K£Z‘®"µxˆÞüe²1Œ{Õ¿ |=—ÆÚMæ«£Hc¶³‘£•æo+s7 ?Åœ`sÉ“à­cZ¶½ñ“p–“ÛXÍ&¥a9϶e%UIù²ÆÞ}è» ¥¢YÞh~#´ÿ„’ÁÖGF—z‰ÑÔt$GÊSŽjø‹@k{ÚiPý•íZ8ÛhÆ£ ÌÊNî3^ká »¯ YÜëš»6r³K䌨¦$–#'*[€zôÍ^°¹¾‹ÀMmâ2¥¶ë(eP¬."¸”˶TmZöÚçźöª¢|µ±Š¡‘² ©lñƒŠÏgÖ5¤–ÓÃÖ)¼EO™ldAÉA‚õëW¼!á߇özÊ]x–ÎkÍ55¼1“ Q’Y•þñ¹éÞ€=Å߯­i®ß—ri6 å½¼^^ëw3®x  äsÍqrØi7ú0ñ~ƒ%½ä ¦Iåƒá°;Ž7c¥gjާ-íÖ‹bË-¿IšÝ÷¬Ðz’9ÈÆëÍ\ð}Ý÷‚dAšÊÝ¥ÞòÄŽ<ÂÌ6 téÊõ•îÈ ¾ñ§é]Êx¦ .{+Qh¥½Ô#Cça‚«7eÇ‚:dW+ð÷áäÈÉa/iåÈR^0ðó•î¦}jÿ´Í{Bñ®µs5Ô+ˆ4•{{wMëùÚÌG¨é•ê~”œBìçím|C¦kCVÕ¥–RÎÒÊ- qmÛ–ÆrGJî¼=âÚÝ­Ž“#yPG,SÝ™*Hu_§9¯2ð©ñÃÿÚh^%Ô>Ð×´R˂ш݉ ±éŒàŸÂ’Ê=CWÕõ+;K1,0,>r·—B@eÞ'océK|Ìët }(k—vÖzíÝÅŠ³™c!a~åätöíŠÜº›Iðô÷º“"µ¤ÙŽH0Æd ó0$ÿ=:v®?Eñ^›à{;¨¯´ùÅÍú‡7¶ØeU ¬ Û·Ä*Øð埆ü'ªÞi÷×k¶ÿ8 !†à’¸Ç.ìJÍÄ9Ù³¥éói±ÜévïqoiΗ?” ó þ±OPxé]v“h‰§ÙÙkSØÛÏnà[¤­ŒÝWvÂ2éøÖÔÞ)ñ>¥§¶‘áu†Hf¸œÅæòM¼˜È+Ó¨l}AªZ擦é`×õqöΛvP³+I#ÂO3’Ì P¢Æ¦Î›Ã:ÌšäúMÅÃêWL‹-¼W ‡Šh2ؘaÎߘ¨æŸ¨é>!¿ñ$w©-œ×6lª·1/. Q‚~Sž3Ø×™øGâ'ƒ¼c®Øé¶zÒéºV’Ãʹ–Ûa’W1¡<¬›ƒN9J_ë;ðî.»á½15ý"XÌË:ͺy 1ÛÏ(}XMDÒ该7Ç ;Q‹Uñ:v©¦ÚB|ÃÂÌ#Â’¤ø' ¹ ßï~ðæ‰á}¼câ¹¾¡3ÞcV…nA{˜Â¥ˆe Íp>ñf««xqî¼q§Üiê±$©ÛT:Èvü»räu5±£\i–úœ¶*ÓàÕfŽÕ­âIl¨ðò’ÄHï¿JM΃_ø/ðC´}?Ä—Wzþ¡"³†Iš;DRqå•'q= 8ÀäWƒëþÚjÁî¼,ŠèŠ`G äÞ)»zŒ‘^oâû/øßâd¶wÚ–ÆÍï$dÂådVL¬ÜûZõÿ€Z/þé¶þÔ|uªx–iÓDÒZÅ"¨—9*Òn98ˆ<_ƱŸÆMÃI§|:Ôìü=$· ¨éW #îÇÄŠÛó„™qûÆyÇ5SáÏÀŸÚ*ïâ†âOu¥êwQí6ÒB%”¤Frme¾b `’zð+î;{‹ý7N½¼ð­ÝêÚÚGæ$,DÈ’à‚ÊHäc‚á^"Ö¾6|LºðüšM³ømí¤’xõ.U–䪜ldsê1@ÙÑ?Äø?C¼ÒµïW— Ñ<ÏåÜA;4^cvÀ ƒÏ5ïú¿ˆþC«ÙøÓM¼™5 tÈížÚCæCå°–E .ì÷íÚ¼«À~'šÆ-âMÝ®¢$SÈ\ 7).[‚}rhÔ¼,õ›‰.¯´hÀ’I¡l:…ÈÔŒñÓÒ¹2vØm¿Ž´-RÎæo êi5ßË[íx™Bœ’w‚;ÖYñ$º“m.©¨3½Ì¡À ¢Lp6ÓñÖ²-þ$hú£Ò’Þå,åo&Ç— 9è;gé]³aá›Ë´MBâIî—ÎÈÙ/"°þ˜ãŠ®R.rºŠÁ©i×1JX¤a8˜v*œà短ZÖõËw²³ÒüEhæÖÍ—Ëh‰2zP9ãñ¬ÖÑ…—ˆ ‹ÁBk&»™ŸÎT‘—\ŒqÚ–ãN·’ÎÞcQ6²§šmå#p,Ÿ}2:ãør>”X²/i#k·xž.ʹòä—;O »@kŠø‹jþÒ­üAá{èµAc)3ÚG&dµŒŒô^={óøP=/Añ;]h¶©o&qm|žX,®9ã‚ |çighâÒ]ÿgBÙ„‚?1¸\tÈèA®º?Šþ¸¸¶ñ‹Z *k«víúƒå“l¹ª¾ñÿ…îfH༵Õ`9d–/”‡ÆX¼{ƒß½5äñî…ª-α–öÑÄ@iUz«Ÿ0'²±ñ§ˆbÔ¥|/Ù÷ùEÀ]±¶[$c!º ×ã kA´½ÞÑ®¬t¤r±Û4í<¨$å°ìrA?t oCÖõOxš×U×m¾Ç `‰âuWGYx`çòzUÔ–='Y–ÂÂxNº ·)æ1;²Ã+€+zMâ:Þ{d˜ÜÂ0Œá·Åêsœ‘Ó=«È.¼-ñ¿Ãž!‘|9x¶ñµË2¼±¬¥£í•# ýуïEÀô»O Oi—¦Â‰§¬æækeÂ}î0rW½bx«Á‘^j3ÿcK6÷â ¥QÓø•¸ãÌxŸMø¯u­NŸ ü‹‹© ÎûLæ«YSï`0(C5§§/í·‘Ûxž+U…â”ù»€Ÿp\àvž{Nìb?†—ðÛj6ºTŸcš&,m&Lþï¡Áé[šG‚¼We©‰RÓ‘÷mb$oQŒzï¼?/†õ ÒïÆR˨ZÈD³‘-åF§=ë'Oñ7ˆ4ˆµ8| qö’Þ´ÿa™‹Š1’zŒ÷è¸Ms£ëÚG._´Û9dM‚S¸6á´cçVÍy¯ÃíÃVÚV»¯j÷2iš„ÓE¨<0F§;¿u» }@Æ }tšÄZŸԭ­ÞÖS+@œüÙÉ`N2Ažjóˆl<=¯xÐx“[Óä±3D¶þk‘²Žì¼ úúÒF»ÑŸNÅœ7òo?šÑÜ9ár1ìEt2Xh: ¦çMw¸šæ'Y$I‡îÂôìJÕÕ|¤éðý‹ÃWe­®eÞ‹»t*GŒw?¥sÍ¡iÉkV¾eäÃ7úçŒòFO"Yh:…ÇĽ6i£ÞdÒ䙟!‚’2¡yäþ\WC«éWšU„¯ \GÀY«6y'=2{ö5s¤Å¡µ´ÞÙŒ²Æ†ò2D»sÃ:BHlŽ+SÄú­ôFï×Xº¼¹\®1±Õ¹Nr3žÃšÈÛð̯ªÚE¤^XI4:r‡º™\906ƒ…äõã7ˆµ­-ÎÿI³šæÎÁŠ"åä_rprqÞ³<à½j7»Ynahd€[mòÁ,H?0ëè+‚µ¶“ÃQÚÙ5ÝÃ}šM¢wl¼RN£íͳ »×|-áíTCw¥™Rî33DÏŒ© '@üþu‚uk mV-2ÐÄ¡<¸-nT¸òóÀ$ œÿ:¯­j1¶¡iâ "¹•ne]ÙŒœã¡>Õé]Õÿ‹4Å鯧Ê`h›wF»~eåppIêyõ G•§Œ<[k¯ÙIšm!1ÜÀÊC¬÷1žÜç&»¦³ú2Yø‚ÒN4Þ³ÜÄ)6@8’=j½ß…4¿5®±ussggìí<+ŽTüÀ‘œž>Rr})ɨh ñ5LJü=}=Ì7ìX¦.ŽÜÛ»çõ ,yU¾ƒàWðÍÖŸu¨ÏàsåL#gPåIÛÈlHïU4}TÒ¦McO×nom‘ðñ3ù‘”ÁÝü®1‘^õ¤­¾• ºÂV?63®â$g¸¦ëžÔÏñ—ˆ¥†;—ÔStˆp6§LôëžÃ­|;ârëU–ygÞ663àÉî1Å{OÅ_™£k(e`áŽÐ€€q¡=sÚ¾bñÖ©}e©HÒ*‘•@úu4¹Šug¸Š;3¸¯ÝŽ0¹$÷¼_g4qn‚Up=;S,t}ÍŽ×YÖàžêHÌΖc|pÓ{6>fè1Wôï XÍ4ÓÙÙ¬¡?+wÝŽ„Ö2‘¬c}YxSO³¾û[jÖ2yê²Ïo¸±AÔ³úÖ†—â=+Ä71ØøRdk}á"*~@=ýZ«¬EáDÓ¿áÖo$7Ï,pÇ–ç ‘‚+’ðþ­¤^iÙiúmÚIŒÂ1ÖTSÁv\zŽzV|Æœ§¶_kÀ§N¼Ò‘n`|$±”sž{÷Vè+Çss÷ ÌbQ½wr=«)ã¶:Røžêg0Ç´}‘_ç,ü ý\àç;€Æ3ÁÉ&2ÿü$>“M“Gñ&¶ÑÝ<¨È‘C¼–ôÈÿ8­[7ì“Ϩ>¶]n0VØZ`çÌÉÁõÇ5Å¢ø£ÁðxéÖÖÜY¦ÄŠ%UéÁ IÎãØŽMq÷^!þÆÓ"š{’=Ê ß n§¿øb˜.¯'†¡³†=kS¼€¿ÍäD¥¢ÝŸ¼?ÚïšIÔMáÈ4ÛG¸û1Ë n3¾Ns–=Ç|VÝ­Æ—s-¾¯E²(YY”6wãGN½«™þÕµ7ÓtÛù¥ºó\ˆ¢ÜR §#~x @è³iÖ÷êf|0p¡dœûv§¼v£Pš[׎ u9áÛ“‘ŠÓ·ÿ„n õº½‡Î‘¿Õ–;·>3ÓÐzÕ»‹ÙmÙ庆+el˜a\Jå±Ôg€>¤UÄ Z垉y¿ã˜c!Š$iŽ»ð*‚ÇÃÚ]Ÿ‡Úù¢2¥Ÿvv¶AV=>µ£j>(»Òo´ÏˆÕ.#Ù•@®£¶[ŸÇ³à[ûG±¸ð¾¥ŸËbHŸ—«m#ôö¦M,¶šª>›ðæÆd·ÕVê@…]ˆ8`yl†àb¼WÄ>(—S±µ»†x­¦Dk ¬D°é¼·¹ô«VZ_ˆµ º´šå‹eä 3·©¯ô«[#è;D™&k«¨bhàDý}:ö®ÃOðfƒ¨i«¬ø—Z‹O„¾UÕymª€“Zá4FFðæšöZQ70‡È–‘·Œ`/¶:é´ýKO¨[4"F°G»¨PrGãLȤÚPHæÒ<;|ñÁ¿a™y67†“æ\þxô«¾†óÁ—zìË2¯ÈvCã9Ú=9ªÚ6 ÜêqèšÍýðµ¿9›B@çnþ½=:Šžy|¥øŽ/ ø\\‹(ãc4÷R͸ÏOJïåÐ$Ô´8­4BÙ ƒ"¾ÖÉêNÜž}Îk#Â>tV7~×¢’ÖÙ°Ë4å¥iêS?1ãŒú×=á½7ìÖ—pN¯ ¹$DVM­´…}\õ®–òk„v¾Ö~êD%·A¹†8 [”÷ïM00’6Óµkè¼/––êÍÌo`N9ÓÐtªâêÎÂ9tX§„²Þ` Ûù$»sSZxÛ^ñŒ°xÔHÑ/”Í ;¤ó´nÀë]ÛõÉn†—¬?˜]q¾ ¼ŸéŠ®d=o¥Áe•­ã•"*Èû÷'@0{ʺ뺮§:i±Ý[Gt‘~ñ®?v„x%y'éXW7É·@𮈠’6/%IJyŽìGVÇ×µs¶Þ“±Ôà†öëvö¸Y ’粌€¢˜žÆ÷‚M³Ì¾*¸Žþâ_Ýy1DJ°à¤àí2MY»§]™¢¡Ê‡ÆR$aÊ èõ‹q§›ë¥–iÌ1¯±^GM¬zzô {­f×K0iH·/–%–EUpz°ùØuëÍž£ Çp!¹°¸”G©Û±È;üÛ}ÅZ²ðÕäÚ øs^ÕPi`™|‰§Ã´§0W¿ZWÖ­-titE{{vŸfëÉOïSBªäÿë¯5¼‡GÕoP•Ö[4;¤œ9“v΄“ÎIèJö 7WðíØ²°x§¼¶¶(òÒ'hÛ€3ŠìbðΛ¤Aý—qlûe¤žVÜ”ŒóÜóÒ¼»@¼3_¥Ý´Ià ØJÍóq‘ÇnqWµoÜëúã½üæx!—ahãà`ŒÈb€#ÖâÓ5fžÛNž(®Fà¸}Þ\cƒò޵Å*ÓL²VzZwRR;[<÷[×-5Ä‘õKKµÓÝ30‚5`ÞPOvõ5FïÅâÍ5+øf¿žXÖf.͘@ÁÁÆÐ sÇ^ÔÐCa¬E& Ó +H£ù|Ö}¼à”|gÚ·ð OS@~“O©ë1kÚeÙ:b²¢ÛŒG³Œy?)È«ñXÝx¦â;o̶r‡r-wsò»3˜`téZú®›¥H¶bãKæhr7r€XöÍnxGHÄ×wA|£ag ó.9ÀÎ8lv'µp—3è2Ù\ióÇ4 24M‰'”d4™è«í[Zާs¥è¿è"+g…ZEä…±À9f?ÄEske¦[æâ+¹<å}‘Á$~dŽPÄ(Èo½WE¿ÑôÛÙ|Aö¾ÔЬžM÷™?‡r—ÛÖ€)¦«ðÃÄ^mOUÓ¥µ{ȾȓE!\áy2ž˜>Ú©àÝ3ÃZFåéqy …iAÜe@ÙÉÀÀ\ô5Ôk¿üoâx£}&ÂÎÖH&PÍ4h!ËuÂðr À⺻KF·ñ“[”ãÉòš5Qû¶#<ªñ×±é@.‘5ͼw·s@×knòö“ó1 véϵz I?Ÿow¨É,ÞJ™(ÉDÝ÷W#'߯z­¤Ë¨ÞC,v²B–²8T‚!µ˜…<Èǧ<+JÚÊÍ® 2Ý´·VväOµp…ÀÏÞ?†*d”êZމâx—û2Ê'+3&‰ÁãÙž¤Á÷¯¿n§³OÙcâ>¡q’oì+Ø„lqûÆÀ]ÝŽ;ö÷´qÁ¨ÛK6m MyòJˆÈfo˜àFNIëšùOöÉø}â-_öqñFt©æÞÙ\d]å³ ó´à“ŒWV ûö9±? >ºÿ‚ªé³øþ câ?µÊ.²4Ÿ jr¬M´+X_ØM3þÊ+}+ócÂ2[ÝxfÒöØ¡2æGÔOP}ëõ{þ .?á*ÿ‚:üi×tk¥»µ½ø9©\Ú¼aY 9dÊmêÓƒ×5ùIð~»Ÿézœ70Ei5´ n9ò¤@û½b~_j÷3Hû±‘É–¿‰Þƒiiq-ëLâ!o¹ ‘ß=¸ªútkm¨¥•Ä^L+&³Ì@$t:óTï„ö6ͨýœN³Ü‘ï*»W»¤3ךѽ·µ³šêW”Ï2ùmç1ùFþOË×Û¾|õÖǬZÇÜçD‚|=¤]¨rê¹Æö9z.+kJø¯¨ê¬úÄ?iµ…CÆâ ÄÉêÎ3Œñ»>µÊø~Kø­®îmRÕŸu¬Ò©+$û¾ï0™ãÖŸàhõ¤ñ"cþÁ°¶¸‚y؃%ċݫ9À¬d®3Ôïlü3‰þ³mru ˆ÷ýÜ~«‚{{W•xÂÒÄ\Ár‘½ÊˆÀ%-¤£žÕëš”·i¦VÙ>£›%nAe”žAãÒ¼ûÄ1é?o:V¥m¤Ÿ43oÊ£’3ýÜŽ(WLÐùâ Ow§£•$r’ ¶yÀÏ#Óbº»– [Moz¤ÒyG¯s×"®$žgðßVñćÚO‰|c 7†/¡Y‡Ù'$hÖeWfLãxÃÎ3ŠßK«‹--ƒíŽîd–_,a¼¥9À'Õ½ºWcyχmm‹[-ÝÄÒ4s3u1íûøóv¬{Í YÔnlà± ‰‡%Ø,ŒÃžAõP3ÐfñÓ4»gB°Šk[¸üŒ¨Vh!aƒŒòY›’Äיܸé<=jlotùíVÞ=ž`A¼œ€»zç$ð+v_x»û ãL¶¿‡IŽC-œq5¨-Tg9ã-߸Ý7Â.×4Ë];KÕßQ¶´’)®c¼UóÚAÊ4§«'÷qŽôÊΠø»ÄÚ=Ü·šTŒ×D&òÔȪã.8êÝ1Z:¤~0Ón-쯅ž“t-÷ÏàJ£ò¾nÂ>nûyÕÏ ipèâkÿiÑ\kO$²†ºÈ[qŸ’M½Èà(Ùû§1‰&Úª®9×½aiwö‘A)ÔeI¾Ò\Ƽ£F€|¬½Ž}ó‘Íwƒ¯Ñ.ïl’ksF$ᣚVþÙû‹ƒ¸Ö&£wÓ5=WRŽÚúîa˜ãm©n£åU^@ ïY$RV8Ø/mµÛ-*ÛPO*çì÷-bFDä°W|uÝ»…\r+àø(WÀÛ/ÚKöx(ðL‡<%ºžXo%‚êþÀ·úTLU”Ï]21ŒWéÖ“ ØkwÞ2_6 lôùa‚`…L/& ´y䓎¢™á­'Ãw²ÝÜÜÜyÉsdbžv$+ªçyt<œäêz×F«„Ñ–"Ÿ4OâÛövÖfÒ5Þ¶{ù|©ç´²š –òƱ Џ"½ŽÊßP†wñ=ååä²B‚YÆn2æ‹0Œ:d×SûPþËž"ø5ûI^|.ð«I¤E¯‘µÔ$·›iy6÷ žpnƒ­v~+±øuð×F°ðÄ:ýµÒ^È×QÛ4ÆSf±+à)ÜÃ#¯¬…E$š>n­7îxýÅ­ÝæŽo%·k+O·ÞLÛLŒÛ†ê@ëþ5è~%†6ð%­¦©oçÙ.šébˆ7˜LƒÌØáO\dqXz3è7ÃÆp[Á?öU¬öòZEš0^TÚûB°Ü¤qÍt_ ´«Ñ¯iê÷BÔ^Hø‚âBè-Q[áNÜÞ´3±gÅZf“®x3Oñ® §Ê³†[X–Þ]‘ïGù¶ÿ~E\pxÀ÷®Rð͉/-o­îþÏ=ÆZÛwÊ’2&âyäté^ƒ¨øáï‰ôÝ'À:i¸ÔäÓ¯ZáílÓbÜ^ê@À© “j”Lƒ¸úd×–x“áõÿÿê^ÔF¢š¿‡c6767ê7Ú;"¹VD;ABX}ä`W9týjîúÕõèYíå#ʸeû·E¿ý\ç½%ÿ‡Ff|7¢G—ÃÕ‰ÉéÆ: ³¥ü>moÃßxUë^]oá饞H>vžXı|ÃÌPxE꼦:WIay…5 Òí£’Õ‘íØ$ÛãÁÓžA óS¹‰tëwM’;¶Ó n뺲¯m9ÝšKZÓIøk©.¯¦Zé:¾¡%¶¯£ë³Ç—¹‰e tžC"©û;±Þ¹-*d[kŸí[e€ê³¼Ó*9ÜC‚ÆFÇ?3ôÖ®xëÁ·šN›àý?ÇžÕ|?%„SÃwsªj;­.Öä«Û >ØJâP•‚!fÇÞê<ÚÊ]Ä÷ö¶VŠ× ]£Ý7™¶9\avùsÕˆÎAéÒ½[áƒucñ×OЭ´m3Ä:Ö§y&—ak4¦+cy,m’Ò°ãb†cŽãÊk3i’ÿÂ>Åͽù»K—`×7 g#R8dòkè¯èQxâPÐ"ñªj¦ËK·Õ4ÝKGÛIr»‰S‚LêÄ«ñŒÐ–øjÚ‡^-Ô¼ «E³ªi3ßiÿÙήmpÿº-çq˜¢#?í1Z—V¯¨[Ü Ÿ-­£»3Ùirô‡ŸÊýì±Ànã»8$â¯ZøS\ñNµ©Û­Å½…†©®_k—‡Ì¹¼»¡Ï7« •ÀééÅq¿õøþøÚÃÅšYIE¼sÇoer2ÓGñ-Üú׈šê[M.H‰ŸÌÝq$`   “Áþ.Ý«½³ø¡aáO§Ž<3fb¸³Ìúdw1b­Ÿ+¾tãÏR rrpzâ¼nÓÚŽ-ýÜÑ5¼fX7Äù©Ê†J“žü×¾_üIñ¿ÄoøGàN©«Åg£øMŠÃL·KeœÜ WžYH.ò`  °PI8'RÔüQâoÜC¤ë ¤éòËxl-maœìU2Ñ›æbåˆÏ^‚²Ošl:w‰,Öm;W®4ù˜n†_³LU¦‰°w¦ð<sSèž²ðn£®øgÆ»¦º¹ÑeT ø›âf‘.—ðÚÖûÅÖÚI{½©ûE’3so0«|À•‚3^k£¬Ö:几r|…‘Q"eò‚Äç¿|à×¢izÏö‡ŠtßéŸhÒ|Ck¾6Ôl.ä´–D‘H*|²¦H³ÎÆ$ÆqŽÿIÔ¼3•«]ê&A>èÌE r<{÷ã©Ç$sïR–Áà»Û-^ Iö…¼ÑÊÍm+mÚà§ŽOLñZ~<µ±¹Ðt‡¬×+ríåÒ˜·~ð°ìHš÷ßEàvÓôm7Ã/pñÁrÒÜ]LJÍ ù+Ô)8Ò®ÏâXE/„´û”Ñô ¹Ñ¾Ð±,¿i’$-&×Ú[…ã qÍx¦‰¦èzf©¦èRÙùÑ]ÛÍ<öòJbyat]¨Y ¼tõ¬Ïhžš{Kí}­µëˆ­-•·ˆ$ K«±ùˆÏõ¯M½Òt?$°ÄòòÚÜM¶«óȲ¶Ä΂F+œÖ>üPÓ¡‡W×|;{¤iö †æâ &exÉýä‹’W?x‘ŠM<’ãx“Oš[i™ ÂBËDùÜŒG“Éa‚*ëøu¼ â{½#DñSN·“Mȉ7%¡c°0 …u^7©ç“Vtéµ­ X¶ñŒ1ÝÛ^âÎKÉ0Ê­Q¤Qëž1]Áð¾7ˆ‡Šôyˆ³ÃÀw÷ùÆ<ç9 %Õ¾jšZyZVŸ¨o6ì±F±€r0:ó×'$zVõ‡Ã}SC²ÖŒÚÂÉdmTévÄ š[„ÃMÜäõP:ö©®í4Ÿk–þ9ñŒwZe˜º …³}ŠTX H”ŒüÇ$åy½ú×Oøs H5xe³Ž×ì×v×6ù2 ¤1œœ£pÅs~&]GŖמ!Õµxõ+ÍzS({…Q6Ä8(ª c`š æG‘x³M“DÒ'ñ»­Ãh:”­7°•‘ÃÂ7,r¨éHÝßô²ÖüOâ{; O¹Õµc K P€¸b[ãwsPj6ÎÞº°Ñç_ø—C%ɵÉXå™xŽ„œàwÎk/[µºÑíΛk¨M[¤^}³:0Ð7)ÎFO~Ô õûNÒ/¬¼«ØYµïÛeŽiuTš)'8M¤áPŒžqXWé¨é^/ñƒÚxËÚìHž0&üå6“×µ\ñçˆþ2]iz^—£Äú°T‡Ï²·Üë Kµ—9w”€IíYÖ¾×¾É5¶­¤²]_Û¥¤ù£wš’dQи8Ç`(9ï|;à¯x—LÑôا‰"’Þ7bƒµtÐê¶^,Ñot-JÚööþòÆÙ,n à5µ¾"òÝT͹#ËÈ;”òsšéíÒivöö×0Iu²„ºš\/Ýlƒ œ=k_á÷„âøu¯Xø’áΩ¡]iÒÃrfLÇ w g-–ÉCƒŒdõ7ü k¡øª '·ַi®Mgö[4œÝK+Ý?i¯‹Öšçöpø}á{©àj^&ÒYbc÷nå¹v#ë¼…^Î2+õ«àÏüÓöø_¦é¶ú'‡å×|c¢Ü¦¥‹¼B þ"ŠíÿH†wùmÔ‘þª%W×Û?Žá‰F§yµ'Ì^ætU™Ôž *ç×ùWç¹Ç+ras “]Þ®Çç§ìëûü,ý•ükaâín>.jÚÜ[ø¯\†Íu-›„Q[GD°Lñ€ÒD¢]Ûw–Pý–ûHÖ$ûn¥¸Øùo3¯§ݱڷ’Òê(„ª­#p$9íJåµÛx~öMNÓnõ½Z25…€ŒÎ@èxÈ‹“ÀÜzœô¯‚Ææ˜œc¼ÝÏz†• -2Eiô»fµe"3Œ ÁéxükçŠÿ´ìÞ Ðæ±ð…|IâF“Gei§i3Ú\ÜLj×A<¤ ~ñb0+£Óì¿j‹öÃ\ñbÙü#ð¥¬Í-Ò_º_êo x9yC-µ±nzp;ö¯˜~.ÁR~ ü†~ÎÝ|V׌¶êîbšU¡wLÍœgœF¤vÞ Ö¹nQ^¼ï6ˆ¯‹§OâgâÏÙ£ãÏí+à O~Õúï‡ü áí"ØêR-´?êgt"O¶K;ª‰2ÖbIl_,]ÿÁJÿgÿÙWBo…ÿ°õ„~3Ôâ ¿ñ†§m$\L‹…1ñ¸\r¤„Ÿ•ŽHü×øùûAþП´µýŸŠ¾6ø¼Íi òÒ4øÌZm—+»ŒÁyódó%É;YF¼bÿá…Þ™ÐeFÓ/ô»›†-¨j¸É$¢/=Èè1œWêO S¦•J«Sç±y´§x-»¯ñãÿÅïÚb?â‡Å­q¼Mt·[Íœò¬pX¸Ü vöÑ…TÁ݆r pÚ“áÝcÄGNd¿¶‰s4¢ÍzˆŒ¹0» îLr0i4‡Z-õŸŽ5%C²ç¶?èòym‡u#1ïŒØ«šŸ‡­£Xu9¯åM6]B5š˜–H™Ž•àzúWÙPÃÓ¤­xµ+JOVwøÓÁú'u‰—Z5î§£I‚Ù/ÆÃ o—ÌpA9#ž™È Ô-´K³ö˜gg ´8Ýó3³Î3X_‡t›¿0Õc+s8™f€íÛ¨'rô$žÙýh-ü â kWžµ´3‰-¯`º%f2eã—ŒmÈàŒõÇJf¦þ"ÖÐí»G¸s€„¼>†‚8𾻩Epº\šts4ò”¼Y:ypƒ‚?†¹­wÁúœšEðÓ”Ce4›ìã,wG(`eêzcunèÐÍ®ê+•²Y-ç¾…­äeygy_›¨°í]e¥¿ˆ<+'€5øÖ)'h¯"¾BVl±%oá(Ã{ÐEÅïˆ`Ò.4«t– ÇŽ[™QBHò!ì=pë×Èt8ÖÖM@!ýÄk$…9ݶùezäƒ×Ö¶îíï´ëmrxZçʇ70È™ó~Rêá#ŸvÄ?ƒ·^Ñ,‘.â×>ÊöÚÓJ6ÛºDR.9,:~4ÝÏ6Ò4Øâñf·¡À%Óù7l¿~hØ :>x‘€TA5—c>µãÏ\jš¥­Å¤«jÓ΄ySB-Ûvr3ß5럡Ôü`º;hŠÌvÄÇ,V9Z!–mÃ;€'Þ¹o YËâ_ ÞxƒO”ÞB“F$’œÅ)äïÉ_N´Ü­c៾³§j—šmœ×¬ßdóUE>`dfc²k¸ÓüCñź"ÚXXZèš®y-•Õ‘apÎÉ&P€y FAä×1â KÄ?ðŠÞi×–Éw§ÚL–‰5»¡³½NÓó3q]‚®¼= iƒQÜ×ZÞŸ2^[41ç1ãçF#©Æp𠳤¸Ð|?¨øâø‹Cy4è.¤[¸äƒæ,l¬ €“Žj—„tßÙ^›¥Ì¦ßd2j “l~P ’8=|æ»£Ýø•õɶ ÆBcËYÁ}¯Ÿ¼LvÅ OÂ?„µX­ã¹”G<3C”•ƒ)cÔ žžâ€:o|":ÍÇ“4"Ù‚]?•)^á†UÅIk©øÂ-aˆI5+[g™'E"6†@B«(éƒÓÔRé^7ñΜÖð#ØÛGaæ#ÚÏ(_9›©ÉÉÇ>õ'ˆc[£—hväç–Á=«Í|{âÛÍCÇš•¨Ü¥µú‚»¶ÉeÎÚ>^3U|[u¢jöiâ JH/la—kæda”uÀQÍEg-”zÄzngDPË !•Üa~÷@åMX ºÊLf ¨ÈdY™°Ñž.wÀí××kã wÀºý®£Ùõ}&â7‰f·2°ù„l¤ð@à:W¢jZ­šÃÖÞ+mCN¹ŽXYG˜£ƒ”P©ù°q\†·-ˆa]ÄÁkÅÄ·äD2& sÉ*HÆ;SÐ Õt+—ºð…½œˆ÷˜y"†3 äéëYÞmEº1é1>¡¢yÒ¡[„_:Ø8ùÕƒå¼çÐG§[CªÞÍlðMx#Œ¥À|ù¡•|Õr Át"½[Ñm4Í#Rð•LJEôZ‰kˆfF%¤9|¤uÍCH(Ò|âïìÙuKkØ5¶[æß¬"6oº®ùÁÝÀÊŽ;Šåµ/]ê·ÑÚx³L¼°ñ ´âv¸VB¨£iq»¦=ê?x‹Äzî¹§hž†k˜ÌjeÓ¤”Fd‰yl1O½OV3xâÊÖ‹¿麅šÛjñc—æ"ÞVÃâDȾ÷ t©åÓ‘|)¥_Aâ$ò½Þµ—JSÉ·xUwoBüãùû%Ô4«ÙL&ôÜ9ÉÛÊ‘ô¥ñ'Š÷k~›C‚)á"_´[1yn##¯ÍÕ}±‘Šìllü)wú­ôæÎÞ÷¶nÈ>‡ß°íZöÉ7fÛOœ¬SE˜gF8‘ ž£ðï\ÁIà謵FšÝu) $S°íwç¦È5•-¥Ýõž§‡m¿y§Ã[ÎIE½€Iç@¶—§hþ Ó,#Óåq9s8ŽáÚBBœ€ò§z¶5a¬__Aâ.öÝ®›dLŸêT0bN¸¯Ÿâ¥”º›Cya"\F›efÁ$ ”î¿{v®>‰àý*ÿQŽàÛ_– ÉÛCò©'zdô š>(~ËÖŸxgá_ŸÆzGˆ.õ]1d1@ÒJ²*Èß1Œ!9êyÀÅ{ÇŠ.þ,Zkzo‰|s¤ßXÜY ¸…“dåñœ)ä`Ò¼RÛö[øj¾†ÇÃW·§(¤‘ã”g•)Œê?JÄÐ/~6ü0’?†ºÍLjzGk|„Ç呈öÍ €s÷ù>´0=ÖmzaÓµ[d–â+§0Ën\,–“¶w:㪩ìzŠæü7ã}kWÔ¦ðNµ4“êI.È¥(¢ÜÁýîNTú× ­~Ð~ðŸ‹IñÆ‹%´¦Ù$öŠ –XÉDÑŸ»»>„ÔWŸ´÷ìɪk–VDWšL—Æ;5»žØ 'Ub wïYëÅ5]?ÄË<–êcI¶ L(*I9Nj ï4=n;o Ý3Èñ<²HáÈê~SŸoLö®[Æzç…ô¿¶»w|nìmn6}¥oÇñ0Ldã®Gj¿eâ xßIÒuÉì~Ñ`Ñ#ZêD…¸Y)æ)†lr}8  š6‹qáÍìú}ÿÚí$qö´Ê°êOlz`ƒZš†·jðÝ[éQˆ¦Š\ª… ’ã«.#y®’ûû"ê{û©VÞîI6†ݲ` Ç#Þ³ŸÂ3xMg“Kcn̾xÛ$c 89¦˜rxoÅåí®¥3‹ƒ½Vdò÷vFp£¾1]Ž‹¢Ccm!½º5¬R-¹™7…Vè#Œž#5ç65ÏêVž#ð~£5ÄVìÍr`Ëfª0áÇuÏLv¯jÕ~%ÞüaÒt§¶Õ­n-ž3-´[8>\Ø'v{8«Á5»;$Ö´ýrãJ‘-Œ»¯6ÊXd@ ï5Òø¯_Òlƒ¶‹à=VæêÞòå~Õ ®EÛÃz#¸<÷¤…µF¹ûÈïô.Öµ·á•Àà®ÿá'¶kzÓEÔõ[k+T´6’]ÎJûù^~R>î~ïµršEÇŒ<=%×…/ì‹CtÒlŒ?ÊÃ?+îᇵhO¢ëׯmö@âhãHP ìHIàýj퇎u}ÅQÚ]èætÝÂHÄM,¤·„ƒ9 {b²îÔìæµ—÷L„G0#’²wÈéŒu ï|^º¶–¾#´:{GO9ЕÁ|øã&µ4?]ø¯Q’ûY¿²‹O„l-âØÌs…ÞÃ;ôýio4ytˆì®µo3T¼L¥ðÞ¢ç8 ‘Ç^zâ¸ÍGU²ð¬†m"2Öís$> öRGa@3ÿÐý-ÿ„‚ÖñwXD¾\ƒ#iÆ1ßyoŒ~#i:ÍmbVâò1’W»ºdö¯ñ_Ä]t¨³´…ÎLqdžÄŸ›'°ÍxN½¨ê–åe»QR±ÜBá½”äæ¾ ì}!èž#ñ$ד¾¹y—7•n?JñkzìŸé–†pH@äí Ý€§Û_K¨jÙ‹žÀH¤$NËódwØS×ÃuoæIÃÜ=·(3’$G ª™ÒVÞÍìŸí0Ï4eç‘‘³/ ï1'5Òêþ!ñÍ·†ƒÌÉ$k"/’±¤EQ‰9$ “Û“Qèš<óíñ4ڕLjØ3Å·ŽÖ%üTÇ=ë—ñ—Šìu˜Ä·R4—ìÍ¥¶LP…ÉûÝÎN9ÐsIªØ]¥¶©¥@ÉåîLœI3휎+¶]-Y¯Øše;c–g|¼¡ô®´‰õ»Huoì§—Q˜ªÆÍ#3’¤]¼ñŒò~µé?þ|yðCYx¿â·…&ðÝ®­ˆížîkf*(rQJò! É8éÁâ­!9$Òg-§ipÙÞ u˜`¹ýÁT%Éhœç1ê*)&ˆôîÄ×V§Ï- !8#Cuky<0oü9 ÷1ùWU¸îüÄzŠäõÏÚM%„–Áà…Uãp<µÊôÿhƒëš›ê2í”ÓºÍ~VÙ‰e I(ìTòŒý 3Yð5‡ˆ|ƒrÅl2²‡=ÈÛéêx«6âH·­ÂÃ ä—ØÄª'\±8Ûõçšm½ÐÔðÚê ÊêQ¦[J‚?™ª²Ñ|+{‘qvm|»t)¶éßb©þî[©>€WOáçñCZ™#¸³³ÓREÃ9Ú<Æë¹¹8õÊi^¹–Ìj·÷ïukÀò£3‡ìr~_ÄW«é—I¡h¶×ú}£ià´·‘ž]Äà|£$äôb­lDIð¥¥ÅÓZÿb_­Ä«µ•â ð‚¨ÿ<Ö´÷ÚÝïŒn-µÚkA–‘``±‡Ù?—z϶¼µ¿ÓmÃe¬‡dËädÇsÜè5o‡ö~+c§i*—¼,³GÀ êÇŸALÈÖµÖÑ´+µµŠÅÎ`ç|›äLƒ‚xàâ°WRÓn|2‘[à ԷRç+²¸EÀ%³Žj¸ÓnŽšúf°ÁË#bÁf=I©÷³Ó`V{˜å†"©½›ÊÉrzphVñî.mGÓ-‡—ª¥‹äƒ×vî¼zêgÒ<-l¯¬øŠþén]IX×s€î¿Ö¼¯Q}WR¿‹EµˆCi+S ³mÇ©Àô¯U/m¦ÛGØÜj[Q²ð!P®p>`z…šãc‡]ŸMx´ÉɉœX+I!¼W:ÚÙʱíó`Ç$ÏãV⿲¸º“D¼‚IeÚ«$ÀˆbPØÆqÜWYo{£ÛjO¤[ª$1¶Õ’% ǾÝÍÆß^ô¬jZe¼þðÜ£ìñmwòÐn™‡\3`qø×f5‹¥k»ÈáÌlË–MŒ=øã5½¨Xx‚ÂÅõmAv£Aó£Ú:{n…pözTº8‚ܼƒ&F@n1ÎB“ÇNµ žÅ v+ÝfÖâÊÒ8¢€ª®ç˜18¢¡Ñ´KÛB¶ž&¼[YTï?za^Üçn[µYµÑ¦Ô®…€ÚÔÓ:åQO¿®j¤+â=+X:¶¹ckqqfàã¦vûunEáÝSÓ†y¥B?Ö¡2Ý€^œU˜ü=£{o$ª¶–¶³}•T/˜GMÄð}qÖ¬Ú]ßëË5½ÔÓ]º´*ìÏAÇlsQ\K¨Ã#ÜMzöÒ#\! ©qÈè >½¨)¼oþ·#\»ÀY–1*qåŘçJܵѼ!©Ü%¤z¬M1ˆÌÂBcÚGÌsò>™¹è|A6}©x‡D•£¹[ÖŠ5:l­YÔ­4¯ZÜëúý—ö¨,gV°ÍÐc8ã@lõ?IªZYZ;î¸Aö?"•ÏÝDÁ~:’}é¾$ñ%ö«âk¹ÒgÖ,  ª< QÇð"/\âcšæ­µÛYm×EHÛ¢†UØ#åëŽ6RßêºÌƒMÓlWíï‡euÙ !!‹(9g½O¨ê¦áÆ·© U%b†9Ul`n°ðåÍÝä÷ ® vCÇçC)n7==ÍqœWº¦™q¢[¬+%£gËÂz€9Ít:L_ú6¡x Pº²Ì’1Üëßn6€h³Ò¼9ig,D6¯=ÇšÆW*±¬’–úëߊXì_Ô®lîdû5Ôn<¶2 ªÿuÆ9^ØÒXÃãK+ûØ$š;«K#‘R"J´ç*¥€%OEæ¶|.t›}I¢¸y¯c7’™6¸pr¤FFs€jê×÷š¡DÑ¡Y¥@«<ÒHã‘3€2Bž2Iä ÓOê·z¼_ØÙ¤W¡!ßnUX&9ÀÉ»gµr7—ÑI´;YÐÛJ|R‰7»÷'·¸Í_ò|EâM2ãQ·´ˆÛØ ûªVFaÎ@Nsë].­§è·Þ!Âqߨ½¼¸ 1Å SÌŽÜ. $·áL°Ô%Ól£WÖ¬ô«k6i#08&ë’²1Ø~áéîy  OiâO ǬÝH¶íª!ŽTò¤i#Só`€v‚FsXºcXèšM¼3ê7³"‰R>$ÞÙÜY¹ü× iVžñÆ›q©ÉÝ\Ú#$7ú¿(áüÜŸ|Ö$¶> Ðì'¸Ó-–ÊkØcU2¹nP·³ÛŸzž`7´-û9´ñvyìåŒ?8rü)>çÐWiq¥C­ê<çQA'ïeˆã…ãꃜ’N3Ž+©Ñï4·Ô­îaÚ²Y«:ÈQøÀÈà°œ¹ÝJ?iZ²ëZkÇs¹!¤v=÷Ãçõ¤ÝÀ]R=B$Ôµ;tÁ7Æùu‡ãêOR:ןx«WÔ¼gá»èf‘ÚÒyуµ) øésx~×OÕ$Û›§¸e`ñŸÝg©ú`õ¯=×n!¿¿þÈ€¨§iUl©S÷‚ ŒqÆkZÓF5–‡ÕðOM_UøãÿçðÏ„®töÒÚ3Xð\Bän†xôÓ=Œw#˜¤7©=ëùúýƒ®µûÙûÁ6Zùó›E°]»àÄòiiöWhר@ñ6ÐyÇZýÚÿ‚HxÚë\ø ⟅Ò!ø{ãGK·‘O/i¨2ÞF1ü!ÁŒûs_ˆÿ²ÿ‚„õ_ˆ>ÚË‹ñ Åö–ÑÈ­ˆ­WX»{Üxœô¯£Ì]ðð‘çåÏßš=âÝ–[ÕÔÍОr‡ÌBG–¸OL󜎕¬·+k©5Ž¥m,£÷eFoPsÔœŽ´kV[h÷ik²5ŠUY%q<*úŽx¢ïU¶3[êq´3²Æ#nWŽ0§¨Ïµó¨öVÇc¢jñ‹ˆ-l4ù'6an%h×{BÃ8û¤óžý+½¹²ñ&•mm«iFÑê¶ÇŒbY°ÚrG¯c\–…%¯†,ΛqÍs9_5„›7a²C7®O+Ú»‹Ë]gÄV ¤ål ¬²Ç42A_ãSŒç ›>Ô/®ãºÐµ;*i6£Bà<±q¬ÇœÎ+‚¯[EÔ,>Í£€b•Í´O ï^¥g§ki¯XÏ,ú´h¾c³my–%±ÆyÆ}+†sáÛ/G©»Ý¬SLcß [9 »Ž)u+¡àÞ+ðÍôRCçL }FXõÔb¾Rñ3éVÚóÍ"æ>Ýäcn=P3šúó[ò5v’kYÜý˜.×?ÄOP?ÄWËß"Š\ÝG˜<í‚W‚£‚xÖº¡±Ë2}#BÔ-th|­XÝÚ_ϲ¨ÁR~^N: ×uâ_ ]iq­ãj“˜°×¯Ëæ’x\“À:W¥èÚþ¡¡[§•µ®ü@ÌI%“©$2:W¡ÛËlö7g‰ä º±œ»–89è½+DÌÌ]2Î÷TŠ(n&-ÈÎцÃ,Jp£>Œy<ó\î©amªÛxr;—ž8ÒY÷íEç A?{nx®¾ÛL×%ƒNº·¸GY#«ÎÄ©8#®9§ËªèÆÜÛµ¬{’HÖU ÉÆIÇÞÏ9µ`cÛ}£NÐõi¯Ø_ÚYÃ<÷xf–nŠÀv$`~çúõìÞ×mõo_E5Ýå¬fPª\‰ Ç–yÎAÇ# ­[­nÏ\µÕ<¨Äð VF.Âò«~íó’qø­`xZoCg|cd±5ÒEJª£‡– :䑌/^´íw±ÞéíuáN·:œIŸwq‰dY€Üù#¢øÒiÖü.­­Ü-ÉU&Ù]V-€žIÙíÚ£Ôt+Ý^¹ÖlíNž.d:ƒ[Á¹†æç¨×iéŠîôèõMÃfëZ³2DŽÈ¦\4²´ƒ•{dñ’x¨{š(«þ— Üø±d¾¶·Eû7rȘâ/»»'-ógúÖՌϓâÔ¤ŽmGí.×2§Év§s–oº8'­bOm©Ícu â[=@ÂÄʱªÇ) I Œnúò+KSЧðF¯mcg¶Kˆ¢O"I$ΠÉçÚ‘iXèü7¾½àëÿhw ¾³«Ç¾I—dKöFÓŒõ&½K¹ðæ£¯ÜÆòÍ,2"¬[“j»nÃêWŒc×½y7ö5͵NJ5$h`]ÅÙFQ ¼?Úç==+³ðœ–Z¶‘.»¦Ë*BÒµ±¦Ö`¸Üp~§ô¨Î§S²Ö‘[J}T¦Ÿ¤OˆX Ûvœ˜úÕ/ øCCmfç[Ô ŠÿbŠÿU†ë Áà&~_~kOÆ~™máyu[+P#Ê÷ KÊY°2ëïúW™Yéé‹7ˆäÔ1¥´qÝYÜ.I€ÈŽ\ã’1Ž+#CÔn4­2ûJ¸³Òˆ¬’@ÁÙƒˆÛæã<»óª7Z…-mít=Ý¢‘Ýî.'l`¾ Ž€œ½+޽ÔdÒ£º{U¹a™\gbœ9ž;×c5ö‘¨‰ä½ŒAtè£Ê\¬‹Ë7®ZRv𾇿Ÿüöoñ'ÄÙÂãâÏÂY­dñg‚ä•b†å[˹ÓîYàƒæ¢“,}·¯ÁïÙ¾ßáO‡ìüWñÊèq˨ë/`/, »·òá‚Êdà©”Àär#"¿­ëûË‹õ¶¼Óíåôˤ•cQòO¹Ha"žÎ1Ú¿¿Œ¿|5û0~Ø^1øc¬xjËÄÞñ†œþ1Ñô»ÏÜÇq3–컘ý–NíÁ9⾇-®ÜyYããiXòŸü:ð½Î§â xÓ[ŸQµ»‘¯üUÖmcðdž^æK³³ÃYÅrÞl‘¤Ë*†ùU÷<׸kžðòKaãRµ»mbEÔíïtÖËamÀh†r²Ê_•ã€GQ^¶Ç’ÏN¸Ñ|7©x¯Eøáû6ûF™¬?ÚVe‹yŽFŒ2çÀõ®?Ä)qâ›'×GòJ—\ÌÀ¬ÄóN“A¼øáJþq¦Þéš­¾áfq$ÖÂ;ÊÉ•Ä{úœŒ÷£™ˆ£eà8>­¨[êvñi~Ñã¼i¯º»HÞ;xcN daXò ÀëXž ðo޼[â+þKÕðâkDÑ_^J&†ÚÜFÒ00òüÆ\1÷…liúÅ׎¡á#Qóí/{Ü¿ÖE÷"Y °äç9¦x¯CñN“þ“4sO/qi#!*VP–F0>êô=é§p&ñ¥ÿ‚ï¢7ŸÙ^TÞ$±ŠéÍ«ì–ÞFQQ“,3ŒW&>ib];Bûvu+¹KRåw`´Œ¹9Èܼ½³Qh^7ð>µ`ÇIŽK‹Í)ÄP›iØ9FÁ±ÏØápA=«Ó<;ðK⮿ã8¼7ª[ÅuIgŠD˜GcºrAœò# ò„êO^ÔÀæìäm$óŒsÖ‚¹Q©m¬\ü)ñÕç‰ln­â¶Öšd³»Œo·Em¬UÎ2»Øc»Šôí/Þ+ø¬/.ŸR‚Úä@ìgûKÇÑ Ûå(]Û‰8#¼^=zþÃÀÍg¨éñßiïå%¬Š¤„lí”äãæê;ô§Üé*C§A©ÝÙ[Å«¬Ï4g¼a¢,Ì¥ùCŠ5¤ÐôûX.”2Ø®ˆ"’銪õáÓkã óÅz5ŒÚŒ–ÿkÓeAcq$lm6…s‰’¥±œž ú×›Þ¦»âëK©ì%€LÎææÝÛç3¤›cóûÁ³»Õ¨ü'â Ÿ ý Ú[<ð\´óæ°š4;Âäd ½Npq@‡gã}CFÕ.g•`û^ª|¹f•÷‡1)Ãäwõ5åÞ$º}&Îöù/­Æëµ¡€2Æï)Û³i'Ý*†7×>|DÓ<]áÍ>ÖêÒÖi –+¼H× s •o¸Û~`s€kÏ4û¡Ö,4I-†µs:[ItøŽ(w;1î1œc°úPûÿêÞ Ð4ÛÞ‹ÇÔbyÚT<[hF#?x •#>•…á6óÆÐ_xçÂÚͼVlZæÚâCæ*J3D¸ùc¨îEz>‘à}CõMRMPÔ7˜Á ï‘ÌJA¶ŒûÖÖgxÑøÕ4f»ÐÞ{h¬ˆØÍ(KwÁ ¼§Eé@:œ>¸¾³›GYÖræ9_pór0á€ù”žO¥uQÉiªZj^Ö/ÞæßPŽeiÓjm8^3‚ÀŒfºßø»áïˆ57ÒfÒîô]2I7”µ€4þT#jƈ2Pœ ÇÖ¾eø“ðÛöñÇŠôߊ~Õ¬ît©Lpêút˜ŒØù„mÀ—W„üØ!ƒƒAkcê'ð~µáÿºÞëPiZ¦™0¸¿’ aRU$Yp#¹®^ÛRø_¢Ivž&Ôµ7 ¨ÂB³<ÏpJ–Éá~^äóÍhEomãx´ýoK·ƒM)—(ò %_–6ÞƒŒ“Ï­tž+ñ_޾ éVž¼³Òì­¢‚eÓ¼¨Ç˜b±æ9ëµ›1ÇjMŒàü©ýžyu»G{x5)d‚W-‡0*ü®{ò@ksÃ~е»Ï?Æú…ÌrÙ<÷NývåppzàdTÞÐu[Ù4tñ]¼MRF/ÖÐa¦I”‚¡ø ‚á^—à|!¤ÞÞxÚè&—aq#áX™ZÀ<¯,†ûʼnù‡åK™Éø*ïÃ^(Önï­-¢³³¹ûTV,ÅöbÆÌ1c ädâ¸è´È5}"=&ÎfŽîi ›d©#Ÿ-‘‰?6é^ñSá§‚o4×¶Ñ ±ÙÛ]Àc†Ö@¯#>YœPžH=8ëmâbÎÅ<9yá¸,!³µ„C ¾×–X÷13ïoº\›ÐTN¢Jò/’û3àµM?Å^€É§Íö1jV3æ,°§†x ²tö©µ» ?Iòôjº·»DƒÌò˪“Ð껡àöýKñïÄ}/À_bÕüS­ÙºÒô»F½¸‘¥MÙ‘âhIÊù“ȱîÏÍ_s| ÿ‚tê^;ø£þÚ—ÚÇ‚Ý|¹-¼3`‚Üj‘`¨?Úð4¨Éœƒ ,—nY¶WƒÏ°Øh¹Nhꡃ©QÙ#ò6Ã\ðÿ‡õOøWº“­ø«Zƒ2E¦xzÊ}Vñ|òw1†Ý¢A´±wÚ™g5úðOö×üQá­Æ_´¯Žcøy¦j¶ä.‘sºÛS›{•¼Ý‰2í<ò­AûÑð‡á¿Â€Þ—ô+? i±±R,a îÞ@7»·vrIö®ßÄZ††º1]r&k;¶,ÿhíäŽ`CõÿhkóÌ׎jU¼0ªÞg½…Ê#:‡Ï³×ìÏð7ö}Ð¥ÿ†|Ó,VúXж²ÒEyzù?1iPeAêBas^ضôëv¹ñ4–ÎTŒKž[2ôË—ïŸ~kþ Üé âûkM"ÌX?™ö›8¾Îêª:/‘°ƒžÄ{b¤×µo ÆÖþ±Ñ$ÖŽ¤ªñÅ;#ð¼ÝÄã¯ãÍ|>3[.i»¶{t©Æš´U‘ÜOØ·mI'‚;p¤ ¦š!Þ;°@ë_|Aý²¼áXü3øA¥ÏñGÇÚƒ, ¦i¢A ® GçÌü` áa<}[Çÿ²çì«o§Mñ[ã΢x~ÖÆ?žþI„+ñ°°`ŽtÀÝØWäÇ_ß±7†þ!ÙüFýŠü-«YxÃIxãÿ„ ÝÝYéJÐn ÎV sÁÛ¹£HÙN2@ëå%LK²‡ÌÃ…5w©úïào„Ÿ´ïŠà×|gûgkÚO…¼. TM#GœÁmo'{Mxì¬ùxü½€~ñ/íyû"~Êþ Ô¬d/Íâ­UÙõ¹çºM;Íp0TÈÌf ‚߀ æ¿1þ,~Öÿ~7ê^~Ñž/ºÖgÕ£†Åž›*Ûl¹ŒFàÌ9ÝVl-üYý³¿i=Un>0x†ÿTÒd‘å]Æ%¶Ó#t!É)ÛÌg`{šò™t_ i:„wI$±ƒªîÛ2m¿˜Ý°8¹­ù|oñ~Âö_¬ÑG óù“ØÆTESžkìð˜T *µyLú]¶ø=â?†š^­àÝ_TµÖþÙ0º³ž1äZ„GÙ'žW2n2: Ö0ñ$¯†~À>Éæh–ðÁ×4.o;Üt8aÁçƒÏ"¼¿QÓ+_øŸá—Åû¿®¾ÎöÇ uhbŽ'È„Ž…TIAÆO‚M=/H¼ñG‚¬¾Úö°C-ÓËq l’!&v4‹Ùr¸=ªKoÛjšo‡|s¡ê*òëk÷v¶¯²á¨m§$ª¾Jàc"¸ÍwVñ¥®‘.ºÉa§[FÑZ\…Y„™ùØ€½Ç¸¦j·Þ!Ö<[oáO]­«Ø«ÚÛÍoˆáãà¶9ÁSœŽ ·Hð· ¸ºË*™¬Úà"W›óÇA‚æº9tkcCºð}¤‹Â@mIvGÎ òòzŒ}Üòkn‹z†îôŸ iòÂöºtI5ùL’¤ÛB31ès€WÜÕKï x?Äׯã 4—3H—6ŽÀŽ]Ão˜@RFÓÓÐ!ðûÀO­[øsÇRêú}Ê5ÕΣl¡¦Í¹Ûç•'‘ÈÅsþ"ðœÏuq&¦÷©k6dYƒ³ŸT uÉäöÅz/Š-ƒJ¾ÔÓSˆ<Öz}ú Ý ‚Fbt'Ë1Œœ0çµE6Ÿñ<|F‚ÛÆVójvfÞ%¸KE«E°¨šUäÏ|W¤.•yàÙ§Öu%ÓÅĉks$ƒt‘ÈŽŒâRß0=Eu±ê^%Ònµ/ k^$›ûNû1ù ˜åN@/@£ Íbë>¾Òá›â$·ÿÙr]Üy%$,°Ê@Ü$Σr``óšõÿø/T_jÖöWpè,–£¬SÚÈ&Œ:ïÁÉu8¦3\rx®ÇXðñÒõ™å–}&á$¼±q‰•7~ô†#¤îµŽi­h¾'°Ð¦8/MŃDÀn}»¢Þˆ¿#ŸJÂm K¼ñôÞ.ðuÁ–=8fÔ c¹ µºGŒE"òJÀÇœçä ÖOÃ/ÙxGRÐõ¯ É,ö°É}þw}Š{S´œ`íYySŽB­§kz…–¹k«Ã¥j·VQE©¬ ‹‹¸*w ž2H5ÜxSļº†­ªøÿO‰tH¬š^÷ÚÉ(U&|…hœŽA9Ð#ãévRÃâß…—sjÖ7WÏö$O.æÔK–p‡îÉ<£zðz×#§ø6oüM¹šÍå†K¹f7ºz‚Ió#2,ª\8—±'Ök¿ôm NŸþÔN»½{O²»±¹ÒÌ‘yÆ!´|Öæ@<¶Á85ŒíüGg¯Å⛉Z½J?±Þ’‰p€‰ÇmÊ ®½¤rZŒú÷…<(|MðëP„j‘êP«i7»VÒæÞá‚>ÊÀœƒè#$W¥i¿ °/,lõXmo·£G#4-Uˆ³÷Ó’°ÍGÿ¿‡u½Y²Ö-,ïî5S½´7y¾ÒN̈Ê{‚2yô®&ÃáV‘á¿A§üC¿‘î®àŽ™e·“ ed$ãæÉ gá;Œ~Ö4ýKŦÖEÑüÅŠ9\nº•påFà9LgvwÚ÷‚üfþ9Ñ>#^ ½2ÛR¶š;ý2UÝg,2bHæCüHé ¶HÈ«S|)ñ=©¿‡MšMWH‚çdr…3 T6ݾoP>£ zWµë¾2ßá­[ÂBÉî!Ц–â9¼ûx­Ë¼WøùHÀ OcÄü mâMEÖu_«Ù«Ã%•¢8q"³¹åP’ {Ðø{àÖÑ`¶ñe´âïJCqq ËÆØ„äãhÉ>äSô;vMKM𦧠»M»´R:îu…QŒy'– Ð1ë^—àÙl¯4íJò›Íû"$§ÆáæÆýyÏ8â‚GL›TmGZð߉.­ãµO.h]@y6±9Æ2¯BøqðÛৈû+Å:¤Z,ŠgK{‹WÙòÄBÍAýq^sâ_.©âxZ×ZµD’ÇqlT‰À1†9àzC^mâ½wÂ~‚þ÷Å—« |ÑÂ|¢c—-€Üt ô ksÑõ‹/i÷j—l.nï /=’niTíw‹‘Œô<U¼o%–®Á«|3º{yºÚ\[ÇjTNèIf¸·“Ž1\YøãíOÄ).œmç´Ó¼›¹1e¶ ž~b@?w—^ x¦‰öó­Æ{ßÅ ŽvcÎH8î ²½l4E¾Ñ¼[§T]:\I J`b¡HG@ã<8ìkÒg?ÙøÓã¶…ñúÂ+oø¦ÖâÈjìwÚÝÛû#3¹3•2»²p®'Kñ®…ñëP’ÞI¦˜}™IÃ2Äé‚Gæjòx{BÑuKuðÜ2ÜZØ£,’©!Ñ¥'q#=ô튭ñ#ÁZÖ›¨Ï.© Œr­ë‰¼‰Å廨©=8Þ ÷5bëã.ŸðÿFX5]âí®mgDTK9'¶ÑÎÒ>QÒ´&ñŠôÉî®&ÓmõEÀQ±!îcÀ Ñ‘ü{9±ª·w^ð½ÍÄ_ ¥ÁÉsާ†{‡3FËül†Ö_¼<Ð+êž?‹JÀú£v|7¯G Ð[2@æé åc˜sò†òÉçž+o]Ðþ |^ðßü$z¯Š¥Ñ5ÿ™"–Õó ìä¸èxÓ¹nÌ=kŽð§Û<_–PBm^}R×çòàó6’ƒ­wú&³àÙ<2–—¢Í/å·n.“?peƒÈ‘¹NzÐu¤]>òçU×`–V·0Åp<¦kV\y‘çåesÎTõ®DÖµ Nïû è  ·¸W ¶\r¬ÀóóñŽÕ«¨øÆûÄZ Ï‚¤Ô›PÒ,c2Yé§Ém(,°±ù”ÇË*’}©úG‹t/l¼›ËºK…ˆ3gYŠ>wR0ŒÃž™þtÄÛê·×¾1ÕtôÿEºˆ%Ú]Û¨ò¥d+cŒŽ7w§Ëq§[7Œo„:ŠÜù±ÞZ£l‚ ¾ç§ÑßXiÒºi¶ó5×›|'+¶%9dtôçÚ¼ö{ˆ—[h9/ì¸j:?29oj½âOO¢h0ëñÜ=ͽÌKÕbdS×w>^=ñøWIáÍ9-//|7ump-òFWѱ”ØÞúœ©ÖkÖv“ø^ÎO E5±ò÷´…œáq²Hx*F@<ð9èkS’ökÈ¿°at‹Þ],j¬¬cŒ¯äðJô‡ÿ uo$ÐhÁ©Ãi•‘‚„¸/tYä|¹ ÓžMdÁ§jžѬõz«zðÄ$È–ÒU_)ÖX‰!âvã%µ›N‹VS –›e\ŠNU‘»¹¯Öl¼_¡ê²X n5íÍ„¹ÙGòýܘ㞹éA-œfâK¹ç›CžÒ[+È/"`¾g Çs ŒÈí_E%Ž­¦^ZϤëéÖÈ„mF_5C¯ÝüÙ“ù׋¶±¯Ýèú}õïïíîæwNNBa¾R߆E{?¸¾k^.]/âtÐÚXÚYI(ÉÉBB¹Æò:‘ùP:= î|9 zŽ•s'ˆ–ö9¢–fŽâ%ï‚çæÇC=+ŒÔÝ3m* `ãqùEo6ÉÖ÷ÃR[¥ÍûJ³ÙÜ;ù,£Fy qÁæ®x·ÁºKxjßP°¶“FÂYc¹a&r1ò‘Àº{VMì:/„µM3__#]°hÜ$ƒ.öŽ9ç뎕‹wã_íë ë['²Tóbh²Vv^±1þw  MNçXÕôý>ͶXoÝj?uˆS—\ó¸vö«Z‡ƒ­ï#Óï{uùèª9VÉç·"€=/HÑ,&ÑVÖÆî;IìT[£±ÜYSå{°œ“[6>·©Ë}^îÒþÛÈ󌑦äíÈÝÐàç8ÍxPÝÝxPÞk7FÖ÷í(¡ƒygÌ#þYâ¿¥v ²ø›¦X,_ÚöwZd²2n(|â»)Î:Ÿn wmÕ§†t­BÞÚâf¹µE—ìòv3ü¨Êz=ÏP+cLÐíµcMà ÔìŸÌ@ÙÈ#q‡¥Q¸>×õX4í(›»ÔF3ùùRŒ?å’ƒÃ1 ‘ÛµnM¥é> ·’÷T²œ^ƒ•†RH–o–6e3ß¶h[χvš-ÌÚ‹mïÞÒ#Nœì\I—i#¿çúN¿ŒÍí§öƒØ]0°ðQäˆåCcYxÏ©«úÁ°ÔûcQׯ-µˆ­Úݬ˜9a´qóc¡Æ*¯‡<¾Ð-~#kÛ5’¸ƒýg˜£•1øŒuªþ=´¿Ð&¶ñÆ•kosdГ¨Ù© -¯LKÉŽ Þ¹+Ý'MÕbiô]vâm<éa#oµ†0PO§­uz¾y.•.ª—¶óÚH‚fIªaªG ƒÉCž+…¾ðFŸcáaã]&Éc¸¶!¢FXäf ’1ò‘@ ¿Ñ’æÞÖdŸlRy;ù—i'æ9æ¯i~-º´ñ”—¾4W²ŽyZÝÒê4Æ¡I»“Æzw®òÏâ-¯…ííGˆâDºUYb(¦P!uúdÜŸJÍo‰±Ì©e£¥½õÄ’yÆåã"UŠF#h=6ÓƒŠrÍÆŸéK•%Ôí¨4ò,w¯ûÆÂà/}„ôÅEâJËW¿×0Æ÷–Ã2ä‰Olõ#§Nµç—¯õýJ-bÍ–)mY¥–tÎâųëÖ’O†ÿ­- ñgGöš“ȇrÇœõôÁ<{šä{MŸ†HÓVòUÅò«Èñ[’ñLæQþÏ=+‘Ô|;à_Ík¦x«G²šîi­^8ÌR®ìÊS>§šÇðž½ñËÀž"þݵÕ#µºš!–"YŠpÊÀÈêy®ÛÃ"ðøñ-Ô?RdvgÍÕßFë‹ÎÞz­FLðÝGözø_á9õH¼&§¥ý®X§k››·˜E$§.à#ž þ"µm?f…>ð¾¡âÝB 5I¤“ìβDYDH ¨UA–É=óŒ×Òþ9ð·‚õŸ Ãâ_ê“‹ÙeŽàCv>üòY¸ÆÒq]7ƒtÈtk_íI!³Ó¼@dh$.…ˆ·D¼zW;¤øoâ-ß…"xÅmµdmÄjÌ$µÔaÁ2 ü¤uà‚§­Ÿ1Þ~ÙÖž/ŸI»“É‹o"UB©ŒcgÞë×½}Yá/ŽZŧ‰ô9uIf}\k‹EPï"d`ô%Þ+G^ÙXØÝi:”Ú¥Ò ºŒZ,q† #)* õ Ò¨j:‡6Zx”h÷:«§/—rbq%³düÒ¨'‚G$/†˜ø¦ÛŸ§ ø»CÔ..¬.žâÒæ11Fo”d¯Ìq‘VµO…¾ñžÛ]Š+Öxe™…¨YchÎåÉ*W±õë_Fê:í…Ì:Ɔ.u{@ ŒÃ2°ä0'´Ž•ÊGâMÄ7ÿÙzˆ)-ôq$Áðå@9ŒƒÏ JÌ‹>2Mñ_Áö:w„¼3ãÝ$øbq4…5{ßœŸõaàœ‘œõ¯Rð¿ìõã-_ÀÞ›_ÕG‡.b²Ca}e)–ÑŒ •.ª7¡»WÑÆ•iu§[[]h‘jöÖÒ(N‚E„;'ƒ†ÅuZÕåü—Vͪ[éV±‚­Q–†2˜Ú«r½è aµ½Öüfñërj»oáŒ,0I ¢ERNûÇñ®ÛÄ—º‚í¡´ÕuKK?µC™á’U‘ú–#å=AÉúW™„ø‡¢hºœ‘¨û;³$±L ˆäl©\zŠòÏ‹ÿì¼C¤Ç?…`k»ë‰Qä‚õ¸«ü »ÛÖ€:ùü'áé¦]ðÂ-Áh̨‰(h\{lÉunõ‡¬|@ñ5”vkão¶•gqGÄÙÚ•”aˆ3Í|ç'ÀCªèÞ$Ðõ+´´·/,ìßÈq&rBœãgµ}©£øCÅc–:׉ä“G³¹g[ ¨VmÁº~ó;€^•k`<òÃâñbñ‡¯¾£ FÑ2­ÈnzóGJ߇D†öØéе­Íûm±ðê¸ásßµ6-RKm]¼9ös2â$¹X˜ìÉÈÎ?˜‹â µ}?Åéc¬jµ” 7¬@¡'8.¤ô?0-kÞ×í,â¾yV„Éå­ò’ËÛð¯%ŸÃ^'¹Õî,¬$cg3ÀÓ¡sD™бàü¾ÕÕJ5-7ív0j× oæ\Äïæè­žvŒö­­"ó]д™µgožàgMâDà†ûËŒãµ_²šÓAñĶâñ¦ÒLn°ƒ&ùFAÎSõª>šËR´ÕtVæ9åƒ3À³­ó«Ž8ï\ö”ÏâÝ^mGGµK›»8ÔÁ"ýÇØf gÓºOx]­¯ìu(ôùï‘!wºžÜ «èÇp(Ÿ„üC¥[Ü̾(±þØŽhÁ‡ì“g‡ñÿµ“ÔÃŽ×âßë1Z RàÚâÅ$`ª˜9R¿zßѯ~Ï8¶±Š GæE¸î>¾ýi—-Ô§´º×’ÊKÇ‹åVN7aço¶>´Àø2_Ú‡ÁZ’ëž _±‚k+€˜=™FH'µzÊÜxfëT“Uº³€½Ëî- ÈÇ“†¿¯,»ân©l.µ½6éQp²yl€B9ÈôÍzä ±´[ £)–tÞ³01¢nyö÷S×€š×‰ì¯îl¼?z>Ç+16òEæ.IáA=s›µÁskºB –‘–á°qŽCp=~•êÖ^câ}BÖüŇimdÜëêJÎ=;Ó´Ý&Oø›Â“Ûü#ׯ<+á¨ärt牚âuçr³’G¸½`Ùº4 ÔüKhÂàZ«£¹x±,`¨êàž¼Ö¾¹jÖ&ÞÏTÖ¯^Рkx-¶ˆùêùÇå’i’hÿ5+9l$µm7JŽ-°ÛÛ&r}ÔåñëëÞµ4½(\•u…µ­•‚©dbIÜòÌ3Ÿ@0(›´½šîO¶xªK­d;˜ ¶Ô.L餄!Tú(S֠щ†;]FîúxÕ– *;o(*ŸR:‘ÛžkÐü]ãKBÓå¹¼K4>b«x#‰^3ÈýMr6iÚ¼2xºÊq’…w´’ROÌIö è¨Ñ5›}Iâû8†øfenÛ³Æ~•©­iVúØ‹^ÕµŽ À¶•IÜ3’ãût¨á›Ái·ZÃu&ÒŽ¤Êäc©Üp÷ªž)³Ómþ×eg%ýüì¢ÙÚ=ê288?)r*âÒÖ<ïÃ~Ö0a‘ØùpËØáôϵOqa4Ó¾­«Nöñ¦10lð ßéW­|[âFö ¯LcŽ (2pO@ Gð®ŸÄ<ð?†]Ž›áK/K«Ûù-w¬ÚcìgÏh™ÂÈsÌÀÅRŸ¶ð†ëªÏu4VÁK–v3òªœ“Tí,¾×¨™äšòîr>ctÒJÞ¼4ŒÛWâ¶5 {Å:‘¶¼Ò¬æ5HåÉmˆ£ Ïʹym|~–‰6¡‹XÙîÃâÉ<…‡W{z»’ÑC[‹GÛæ«©¾›pèDarA'€:u«³x›U®.qqA lîÊ|°qÓ¹îj¾•kkãzÛL‘¦wiiŠÛÐtçÜô5ÑjÚO„4ýrÆúþÞ[­69|¹JÊ!flýÔ#¡$PQyo&µ}%ý…É,Y@·ˆ 6#¿µ¿…¦¶ ²kQíýìq¿–ê„c“Ôy®xhþN¥=ß„Òå>ÔÄ$ "‚ŠÇ©sŒ±é]Æ“á_µïÛ4Ë·Çi £‘”íÏüÓ"€74 .ãQ¹˜iWK¦ZBÅä ÅAωêÞ½+Á¾›H`÷ -Œ2†&èŸIÜRSÓå:-¯‰4bqâ%û,¯+ÿ£y¿»ËËòޏõ¯]Ñì¼Ptkh¯íÕm/Kgl´‹œa9¼p Z؉µ ޽ƅðòA¨êW²ì‘çT*rpC?È2yã½tZÔZLJõMGÃÒj–WZ”¡‘2#¡;ÁçƒÁéÅs>#ðñ·¼IoäÛËõ‘åoŒñœž{ÑþβIõPš”‘aÀXÄa7ž€’¡¦du[.,ü%k­jI=Š´qHñò O~¾µVfPÖÖí#¥º1}À”„ƒ×V‹Pñ­ëèÖÖæàL¢Xa §“¸ñÜô«ë7:}¹ÑµÿH‘Š˜­ðX"õèqíR÷Xk¾&¿1Iey41R<ŒDÃ×o?Ò¬ëúŸ‡d¹Ó¯ õã—!®]ÛËUaܤäµ;ˆô+!¨d"Jë-1çžÀj­m«\¢Ü>¢ò²K("&ݳ+ÏF<ç¾j€Û+ªGä,L¯fcmêà êïÎHp3Zö±ëWšX¹¹±Ž{Yx¶lÔg»sëY¶ºÎ$çTÖl×ËòËKoùgœž€t«ž1µ¾•iöÈE¥ÇïÚÖ5 F‘Ž݇¦hìZ’[ÇzòÃ3Æ ClŒwÜȧ’å@ìOZµ­ÜiòÚiúxÓ®cÔî˜Ö *B®÷ì¾++Áþ ñƒèW:ð¥£7²™nã|µû¢#Õ³éÁ=k*hõ+-ZmsZÕá±’5ކ@ÒÉ;Êq°áv¯­5¸™ÚI{w$:=ÌÍ3¯Î"¶!CÙÏ·¯sX÷òx.µ«¨g°d;Q2$=8P=>µ©i©i^7FÀ ÙdfC6vƒ#Ž=sÅmO­ëšWÙ4 µV’áCÌÞ„çüв <5&‹§éyªH–÷wDï7n8‚3×Ö¹ÝÅö1ÜÚêRÆ#ºÈ |†^˜“…>µÌk-àù$¹’Ô4~NRBG4Ž19zs]F‘ è6–M®¡y}x¸€An¤¡^ŽÝÀ'Ò€'ø{¯4Ó]x^Ö!mc:¯›*F0b^§s“À?‰ª÷¾¼Ôã†ÛD¿tµF1ËäÈ]„™áHn:u=«ª¸}=µ’HÒKÄ€Àé#ÁqœwõÀëLûÞ ðÜs_ZÛÊ’K²(¡'rnêÜã©Çjó;é:V¡ö¶Ö÷\«4Óe»ñ×ö}+ Ô|à[[ýWÄë&ò+á«X¿š’JŸxœä“ÎMnižðΩ}`¶Z|Vº¤³•Vš_1W»e_åÎ9â½ÄÚªö×>Ò$LbkuUÙßyUîzðs@6úšº]Ö­ YÜËgd ÜXF0 7}â}€­쵟hã=L:–¢Ñ£Ã¥³mŒI&0yË«øÕï ˜¼Q¯Ùø>ÓR¸„H¤µ¤OÄï»;ÞF†@Æ8ë]¶»kxoÞ|Äb¾dMæ+ø;0NxÏOå@z…î«¡Ýé÷ž3ÓM…íãî•UÑÂ`g1©bØ0Ø­í Ã6zŸŒ%–ö+‰µK«i)ƒ$qCÆ\¹|óŽ€qœâ¸ï JËB¸³Õìê~^Éï^=¬§=þ\óÖº? hž"“uïŽdk‹Ëß´8Pëü$„Œ¶hmÂ6‘è ¤Áq.«¹Ù¯Tî†9ämó8Çt®ƒÀðx;K·Ÿ@ñÛô•PâÖP$·YeQX)'ޤš5MNïMðmÆ¿5ó [_žxÓ¡#!A=3‘Ø×-¥YÜøŸÁw>*ðüW³ÚºÄ’Þ;ïŠÑŸæŸî‚qÈŠÙ“ÓëQ«ø9íàhÀP²¦dW'9ä.$ÖÖ‹àˆô]ïÃú•´š™¸g„iòy/ŒÇ´ü£9ê c_ø+Z}_OÕ4é!xn£ßö·¸Á(ˆÜ£’§žƒšé­ìµ–ß•ÂìDCþ±”ÿ…cß°Í Â“iáWHº–ÓJ€²‘<™ˆ>lmð{ ç#Ó/ÓP“Wð}ý­ÕÍ ’)!—#8P‰À œœœæ­&s©uâ颌٫‰¬ã%œºƒÁÀîy¦ØišW†Z;-5ãýÙÕm[stܼÐzêú;Êí빤ýÜò¾ò›ù*îã¹ô¨ìüðçDÔcÕ,cy'yZ]¬ï v?{årzgZwü$·ój¦÷TµíjޱÃhŸ3 r£?|ã“Qø’óÂqèpx“Ãz³ié#,sG!%Ù»6ŸZM\}|qck©j^–{6ýéT+ ÷•G÷‡ñv©ŸT²Ô­“_×e·§„ðĽv…QÎN:w¯ðÏŠü©x¼‹†¹‰õÙ …@T$nîß{w‰|?i£6‰ªÙÙÀÖÚ8YeºÍÎr gÞªš³¹H¶‹ÿðH BYücûDøv5³ì¾%ÑïÉ.~ØežÆÃGŒAå°92™Ao? x—ÂÚ‡…j¯}¾£-¬O¤[L§í«¨[Áx5)‰w]íÉ#¦kìø%¢ézíÕñkK‚)ûRÐ,no%R~Ï2Ãs"ÂqÐJœ·}¬¢±?jÿ YÉûvø¯WÔg’Ö+'J¿Ž-Þ_ÚåX^Ôu]%'7§2\´¥Š2ðvÚxÈþœWCáýg[-Ρe½²É&X ŽF‘·©}}k·¸Ó/õ—“ĚǗ#ºïg‡ Àùs‘’@ö¬iµM,x2ËP»Ñ Þ©$Îgd]™1“·ž€u¯ž=ÓšÓt¯\ÍŒ·Öó-ÂK4^d›Š„9ÜÌÊHõ­¿7ˆõÑÊòâ7íqq‘׿<•Ç\u­gÅ·q¬qÙ[[èÖĈŠYþðHØ ’ÄnäõµámýõKo‚M8uyHXòxâ³Ð5Ýri´»xt‹Ž3qO£°­;(õ=>Áµ[«w¼k–0¼AÊy#6ô9é\–¹e¤¿Ùµ[XæŽÖÖ%Žr[$uùGRÙ#ÒšqúÃýAðÝÖ©¦_Ïus Ÿ)’ÝHÁ€)=u=ëv==>Ù¢h±^G-´÷it!‘s’IÎ?(8ïT/u}KþÑgaÞdž 1ƒÏ|ö©ü=©øf;ë˜õ Õ'ˆˆ¥‰s¶g^ s÷G§j²O«´wšÓAºñ\HMõåô†4•ö•…Üq×'‘Wí§¾Õµ»Fšë:ä€B'¡?ìõõ¯ðåÕ’iI•óݲ(G‘Žè•º£ÐuìšbºT6Zôk˜Æf¸‰ …L;1}jæñØ—^Óÿ´Ú}Fö¯’F›µbà*ð‡ËÇ_­cÝÜj_n ¢î¼û4QÅ”$¶ÎX•õ=ë"Î/6©,uävÖ–NÛ,áó¹Ý{ñÏÖ¶¯ÃÈ—Úf©Ä+嬱Éó»9ËüÇØóǵ'¨Ë°jº¯Œt»Ï‡¬Ñ•»ÉuÎVD “‘Î<+¾¹ñ%þ›“Ziûl'ËC!ÁòÏe» #­UðÆ¿iá› OIðÂO5þ V+(m£ÜÁ/+¹ä œm®Næ÷Ä0Cte¿†r'‡÷ ˆ›Çhú}j,¼5e š¶Ÿâ›ó'¹2cØèÍ' ,F6ã9©5O >«›­êÄÚ-äˆ2ÜÛ½°º\à)8ÀÍ[:m£Çi¨\´WzŒŒïåÅž3òñ“Î8Çn•ÔëoD7wwQ”œ•Ïv%%‹øvŒœtäu·zÆæ„×Z†¦j—vš.œÏacjpQ:ÅæŒfõ$|¸Âxxéך#jz¤ªu#s¼1ÞJ¹’züƒ?•tºgu‚ÚTÚÔV ªÆnRb„ˆÁàÇÓéøTwš§‰t?][[7öµÉ¶û;J±ˆöɆîùëŠ.€ëuè.བß@‰šÆÂR­<è!çRê:žzò+ñÏþ iðAøÓðšÝÁ´ñ…â›SÓïᕠʲaà2) ±Ø°¬@'5ú£uü%dúUœS_ß[$M,RÎ ºž’NNÒsõâ¾RøïàÛKLµø; x=5›ÜC¦G­Ë&â·@ÓŒŒŠÖ»0ùfqã)Þ'à/ì1â_ˆ^5øíá'ºv•©ë̾Ù©â#õÚy€eV(»› nÎ;¾ø†ãNÑWþ1±m4x \Ô<:%‹6·³¹’3NNÙ L»Uþ\ƒ’9¯(ñÁ?Š:޼N¾´¼ðÅŸ‡¼Mw.‰p×1¡…¶Ñ>õx™Xä”`v¯^ðÞ·¬üfø‡§Ý|D³µñ~¥a GºÑbV0Ï;îóä·2´8æ,ÛÎàÍ}<'Ì®|ýEfmøÃLY¦ƒÀÞÛâ ^ _3íc •ç˜xào;Æ:N+»ðÿƒïuî4HyWÐZÍuäFÁ¼¸í•" +¡+–+ó0'wœUxgá×ü9‹þÍ-´ZTW‹3“>Ÿ{ ­çÅ3Žw³mH1ùËm'\iv³Ðtësÿ }ž¨þ<³˜çaµ–.é·ÝäðIª32ü› Ì]k2[CsªØ_CqÄ|ËKÄŒ¿ ¸oo'9¯4/Šš÷ˆ<+¡¬ëwq]jfžÊÞ6û3,,gÌ;˜;†`Û~U¯¶µhŸ´ˆuýDzD/k>¬ì#T1^XÚù- ÷HæÞ<Ìä² Æ9¯‰¡³Ñ¼9 øUÖ^ê{ 0Ûi—OË ãZ‚ÎQs¹WÀs¸£&ª }uûD|\ñŸí-ñ/ÃÕ¼3mðÍ„òXÛXÛÇjN ­)Þ1½S˳ŽOBMcøo^ñúü¸ÔômJBÉ¡˜mP/Ÿx²·o 1€YT¯ñóËã‡~*›Å¾+šïP0^Cš±·Þxw¯ÎHù îYxÁªZïˆV/\h¾×o.m­í?µ®u›X…©¸¹`Sì¶ÂA€3¼`œmÍP~ ñˆ|©Â5,‰¥Û_ZÛ $2o–ày’³3|‡f@ t'œWSð›Zðÿ‰¼A©Z|PÔ"³¹–ÀÊo£•^BTíË*‚¨¡@“ÞŸ‚¼Yãè¾:²ð¤º¦œ×_Ú:t~!¸X­ŒÒÊÌÄ—ÝÁv ÁWîäñYÞ(Ò~H,­þkéú&«׺ÌWð†Ô-®`vˆåàyLËû1€[“@ u RÒ¦—P‰ªE<ÎÖòŒ¬Ör|°:“òÅ2Œ Ï|7ñÖeiñ¢M&+ûTi#’ÒY¶A(Ôô_¦¡a†á´¿¸’pf’XnI±q†þèæ¢»øQt“h,÷±h~×!žîúâ—r4EQäœcƒÁ&€<º/éöún½áËT¶ƒYžybûA'eªà‰ÞLà“&HnÙ¯Fµðoˆ5¿ üCø·¤[Åuià­OOÓ/n$O݉ï£F-“ÁFÞ€Ýô5ç6º6°ö:5þºÏeª\Ýiºs£l³tö8ó`ãpÜëŠßñúø«Ã]6kãwˆä3ê°ÚNRÖìÀ†F…~VX˜¾Ã=¨íáïØÚ¿ˆt-%¥°Ò¤ŽÖöh}¬7$e¤8‰?uA<×sá¯[hþÐÖãOMNÐá¹ñ«e+m2 >aˆ–0à ëŠÁ°_h¹øEi«\A¦êÇOñÄ- ‘ ¹‰™ÎxÈ/…] Ž+#Àþ×þ-k·ZjÞYé×—÷j‹ãì/ J7¬cîó“€x-Å·ÐiŸVÞè¿ ´ÍZìí; MRÎ+ñ h’@n(è2~l;W¬ø#Sð]ìÚ QÛK§¢<}šO5î°¹bXƒ’G'9ãÈø;âå™¶Ô< ñ&¸Ó¢ÝæÚbšjøhw`g¥]øsãû½7ÂZ—‚üË-å¼ú®•,±+L›‰V9rœ}j2®Ž®-ndÑôÜÛézLVÇ|‘Äg©ÁÆNO©~xçþ/_øWVhõ(nšk"’®üFw¤ÁÎ b6œô&£Ó>ÃâMcFñ•«·ˆáþÉ/ ¿“Êfd#t–ùTr1“PøûBðÿ€o"MÓ嵆hEÄû$ó.­c· ¨ó2wpIíŒR Óðõ·…¥[ë?Ì`¿Š5„ r©ö«˜IA9ü*’øKÅÔ.ã½Ô¦¹ÒZÌEºia,ÁþñÆsÇ"­]AðÃÅR鑸ÛMÁÓk?ÚÚåüÌ>Ö°8P|„ãѱÔ×Wuà jž8ÑuŸø“PšÚ=/É"(BÅæÆùf_Œ3¶ìu  ×n­¤ðÖ›1·{’xžo;¨ÆrH<çX³hÂ÷úËL%¹Ð7Àavù͇Ÿ VŒ7\·\tÁ®ó^ð“·‹5 2îæñô½>i.¦½ºˆ€ÛGÌTµ”± “Ú¤øzó[–æÑa—O¶Ö¥ŽIícq ÎöûT8üJp¿®;3Áÿücâx‹ÁžÑÞ]_Ãö÷3D# ¢X@T $ô®‚ÿÀÞ*ðþs¡ü6¼¸½Ò†¡ ÄIýÜÓ Ó Vù3í^»âm#JðÆâ;¯êzœŒzy–h¼32rèÿÅÛŠóý+Lñö5Õþƒs%…¦–þ\‡quI:(2I=úšwÐÅ»ð7ôMJÛÄ£Q´Mp\6öXB`¶ !Acø×3ÿ®©à»Ýeüqå•Èüìaˆ$€vŒŒwÍ{,5Ÿø¾èÅ©Z]Û…ˆj·ª#XmÌLñÈ»s’Xc=‰ç¥WÔ>øŸÆ-!ñ®uYu+$-,iå …v*È:[=hÌï |9ðŸˆ<=£'Š.mü;K=¾™«gÍ <™aæ1ˆÙA O~+Ã6¾ Õ'Õtl¸¼·a!¾WÝn¸0¢ŒŽ*zžõêÞ7ð†ôÝJ×MÕ´¥Ž-BÒÎ+…¶$„ØI2õÁsÔ“Û¥eø/ávŽ›á}bM*ØwõÎgž<(F§,¬ P§¯=k³QWl¤›Ñ¼\÷>6±ðÖ‹ð£PVׄ‹©jsMŽÖÞ8†è•\q(sò0㯯Dñ5Å¿Äë+O èº Šêu©¹Ô$3ˆ¢˜$ÀÈã#j¢¯]Ì ãäWµxCáçÇOšŸö_À®­-•±°w‚TÓ´ˆ‚HFë‹¶Êäª$Œ?… ~…|*ÿ‚\ø;O¿}ö¹š×Åò×AÑd’-FxŠåÈIîÕŸ‘€‰º•ä ùìψð¸H^R»;ðÙ}J¯cñ6? ø§Ç¶Ú¿„þx\ñn£hEÄ‘é5Ç’‘°ãÏMЦOXÙ÷°ä.~²~ÍðKøñbñ§í;ñIÖ ¸Ø|=á—k‹äíï¯eqpÁ22E È –¿oü §Yx+Nø¦ÙèzDJ-2µ‰p¬aFJ9Éâ¼+âÿÀ?55oŒžÓ¼E¯é$\YLñ˜o‘„Š#ºc®ò«¸nÚq‚1_f\o:íºzŽQü[žà?|:ø?áÃàƒú.Ÿá½>ê!Ó"F¾÷™´#œõrH'Þºy!“ìËsPHUÀl³"kÉÿ·|MðCÀ·ˆü7®kº}”ŽÓâ[íFf$ª4[ƒÜyCäÊn‘€èMKðö†ø3ûDxU¼EðoR{“ËÚ]iÚŒ§j¶× ÉŽkK€“FÄ…e¦kã%*õ½çvVáOdXø‚ß´m6MOᧆt' ï¤Óî^#&Ù9!ó%wŒqƒë^s üZý­îu»_ xãàý†„“³˜o†¾šµ²…ÿV¯PÅ!‘Áä‘°õê_¾ xoàÏ„?hÃà1e7š\Žr †̳ÈÀcj)&¿*þ*ÁW¾)ü@–çJýü:þÒƒ­»x›Ä­q¨JŒ>ô6`ˆí—h$I;3‚0cý¬«#Äâ„iõÝÿV0ÄciÒ}ÙúñCá¾ð—ÂW~+|T m2æâ[hìœÜÈç+±o¡œ±RvªF§=¿ºñÄëÝ[Äž*Uó¥Õ®fyÛìÍ!RŠ ²€v¨U…vñ¹.ßPð*G,+›hð­å·åݸà6y^ö¯Ñ²Î ÃÒ\ÕÝÙábsyÉÙ= _x¯Âßï”?€~ö1ø×x—ÆÐx ͵]qm´K+kˆˆ¹‘‰`ò6ï˜6T…öäžM}V )(-2¾!ÔZž^|*Þ.ñþÕ'x^õ¢ŠØŠ'‘ÇU8;Hã‘éZºïÃý/ᆛ©§‰m¢¼¿[´†+[0gÄYùpáw¸ÁŸ¥z¥¾¹«øÊ5ñ¼)íɹk{[õÎÉP ádnò§…É9ÍyçƒõmzÏÃzý¿‰-b“p†W¹V*ckywªã¹=7vï]§ÎÏÅ>ÔõMÜè ™pÓ,e "ŽÝB‚èuã­Gâ­ZëÂÖð‡jVÙ‚Úê§µ·>t‹€]å$ ®[Œãå­hh:¶‡®½Äz”ï%¼Q]]ʲ9ùwF õõ®/Ä~"ñ%ì–*A"é7>jÜ)xÖä`Ä»€%¶.@Ç>ÔS¹ð¥¼~$ð§ö]¥½ÎŸoaʾFý±„äNÕ×iZ%Å×… Ñ´ë[;ˆtÛv¶½’V_-®Ù·2¹`¸#±=+Ï|ñ8øÉçðÿ‹ô<[Ee Ü %hËÍ·÷ŠÁ‚ü¤à¨9Á¨´hµ··ð…ákyy$®<±ö†Cå&î可xjÌ물W¯x{Âè-§±·‡Py¥–Ò4\,©ò2înFáŒàuö®CÄÞ6ð§ÄÙ-ü3u§Kc©ZÚ#YßÌË%¼³Ûà´LØ'$màž@©tšµ K[’+[5n.VÊXÌ"+ˆŸ`6Nsò׬øŠúÆ=SÁ¯ýýµgm ßöŸÙ`wFl¯fDx=‡4™óÿ‰5ßøÞÛQÒ|s¤yRÙß[„(d™0X¨dá @®žmF_ˆ‰§ßi˜u¦k©ñæC)ãhô¦éÚÞ³áÿ\øžÍÚÚâÞîê9tÉ×dþKÈßg¹vI[.Aéî9­½kÁ~ð–“áïA|×úŽ·§ ù!TÃG •ðp ;ŒŽÜP4Œ<7 ØK³˜tH‡Ù5gxÀ&D#j˶H#œóéµÝo]ñ¡ÿ~—´‡lñHq N6ÄP÷f<çžÕêÑKàÍKOÖ)ŸY±1¬7rˆ@Hùl½r¤Œ× §ëú Þ¢º­a¦_i2峎ÙY-d™½[w~†¶<7ã‰>ê°xwá÷‘}a¨¸ŠKrÆo@1À?+(%¢÷|]®ÞÁ©h:–¦âîG–Y~kI´S·ª–ÆI§xwÃÒ4{¸üYâ+Ï·5›Ëg$ÖÍ+­Èù£FÛÆÒË·wU'5ÒøRñ†·ý¦ÐÈÍg$L,’0Å'O›‚3Þ·Š8öâ½[Âz¥¹º½ºÔí¼ý;G‡ÊRStqË·åÂH>´—Å)oð“Sk½j+mgUÒndû1"IDä&ô!•þößQ].—«h/ð†¹¡éh$"òb°¹ùehÕ¿zBú¦rpkª›^ð– ÛøV× yÜwMu4Ù­äãUlU\‘ôÅzž•ð×Å4;/ØyR^™fµº½V1"F»„…Žìð x÷„o<#e¦ÜZÝÉ3Åcª¼ †Ë _;aÇ@ÙÈ$ö®Î÷Qð§ÄŠ~ ³Ö?±õ;¨¼˜m¦·ó"0EdD”,Ì3½£'9Éô®Cñc/‚±Öä·±k Ȉ#I¢`%f#,|À#®1\W…Gõ¯ø¯YINŸÊ@ºuÌà9[ÿ-’WQ÷“vã3Þ‚¢q­ÞëÓ›XX®WPŽiìiåK¶7QåÌŒFÒ P@ô«þ𮿩üM¸Ð.4Ø/´Í5ŒR=ìÂ#º` ˜o•¸Â¯\ñȯi> øi¡~ÎxïZñ~›uâM:Â#/†Œ{od¶½Ûºh¤É¡rr›IÀõa^ã]PðÇWÂ~>Òÿ´tû™ì/b»·—̉<Èä…ÎôeRF1Æ9zÄ^(ñçý"×Fñ^úX´-ewnX&mäÆÓï(ùIàôê9¯3ƒÃºV§ ߊtmfT´ÔÒæÎtˆ•`a9`ݘ)aÆ3ß‘]_ˆíu=âþ³â_‰^Ôµ¿ÜÛͨÜj6“ùŒÏjãzuߌÁ5çÇÀϯx¶ËÁKÔÑ|Mê±¥Ò•´ë°I%TŒJ#â=èÜ>Eb÷sjÞ2²¬o#ÛÅpgh0*nÊ 9\0$Æ+Ÿ²ðŸˆ&ñÕÞ¿àÈcѯÞÖòâ(å‰Ã»¨ÃÂ[!¶aw€~oÒ¹­Áú…nõ|)Õ®µ­6iDörÝçtpÀt”?M­Æßº{ è~ ßj^Úþ ¶¶T·¹X®,DjöÞzÅTÉÓ¿j hßø—¦Ý^_ê>;ðþ›„bWì·ÎÄIpw,Ò¦îŠÃPŸ— ëPÃBÞ »—ŽDK+È9š¿ ¤2)éÏj›âN¯¤ëQCªkZ¥•žw²†6UŒÀ`â7z,„ÍŒt ¤“\×|{sâ Y]kZÎ’æú+ü»–¨·6®¾ u¦øçÂ:Íôº?†Ô[ÙÝÚ3w [»À•(ÀmÁÃ)Íx–¡ãO…©­j^ðŽ»&›­hpÅeÛÉ$1›)D­…ǘ±}í¤óé^³ãk;ÝNËVøu­xÆÿÅv—š|Qh÷’¸I#VÞÃÆv‘€Ê3Èô ²‡+ å—ˆ4h µÓ怋¨ü¯–IFA;Ðýà@Ís:Å|ñOü%_®­ã/¼~_—¤ŠÈë!™9ïQøwÀM¡Gjþ’ú7H¼»ˆ§˜­»´d‘¡ˆ7AéTþ$kJú]페ö˯ßÞæIB4d_ºÊ¬x÷ÏZ—I³ñBø)ôjšgˆô[“ŠúÚédžÛÄEÈàã‘Î;× q{ã wÃo%®¥kq¤‡D@;·ç ï¸ÆOZ©§~Ìÿ tX–çGÒåÓ|E©6Éõ .cn‘;6ì:ƒ±ö‚väg=:Òjß>(øoJy4ý1$Ù"j6qÈ ·9W@$\ã¾Fs@©¥]ÚÎ.íç–â¨dŠXâ$íW9˜þTïk1k5¸ðÍÝ‘-t³ÚC9ÈftÌd¶r­•+ŸZàZ×âU•Ž©qã(€²´Ž;ß?NÃ8…›çtŒa¾EaÉÏjÐð7Œ~\øFKÝCÄ ö³qö•;$US'·*\¸„šlê¼=¨h^ñ¡ð×Ä 1uoxn.Þã!ÐGT éÃy€ñÏÖ» Áã«¿_èZ½†‡«^êJºTÈc{‹hÕQdX”a·!ÙRqƒ^i7<†à¹Ôµf¶ÕeEY÷ÂÓCq»PØ!eç~oø‡[ð]úG¤G?…š{¯î®îf$yEy2Fy NqÍš~0ðçÄh|A‰¬5§‰4ý1D.–lu¡f¼^vÈ “À#å>ÕÐx«GÓ­|©ê¿m§}:Òæ0 —-+ëÿà¿òtÀº`UÔl-ÑãWòÃy“`ýÀwƒ³¸Ç5‹àŸµÛAá+᪉íG›lwG;.sm<¸ÚÞ•ÑøÒ8¢ñ$:ÆzÖw^'€\ˆœ>ÓǸuYsÒ€7ußx†ûâfŽnÐZܳÀp²Æ«Ÿ¼P0ÏÞéUdû?ÂyàÓ¤‚ÖÞÛG2A-#Ä3Õ#çŽ@rpyéŠã´ÉîT¹ñ ìmcd[K†a²)d&ðÉÇíÅvóøŠêê×'³RÛCOp¡¤òâRÙR¹ÀþÊ÷Фø…gâIi§éÒÇ1ÓYŒ~m´ÙQ[ü¤qŠø[PÔ4Í?ÄšŽ‹c-Â]"4-++$nr?‹’Àë“_@x¿Æú}î—'ˆ­!X>ËYw™¼Â0Ê1€[µ|ßáÛ]gÄz¦¥ñÏK“QÓôXþÐöóH¢êå7aÖ2>ñ„€?:ÒÚÏKÔ¼=¢øÚÒ×ÁV"KIf ’±7þn§$޵ìß´x~Áµ©^®¾šMªGåñùƒ ×$¡Æ0Ý+ðv½áó«_ÙxÍÑÛ[xZÞòhvÏc³î’ŒtÜGø×®êŸoŠéþ-Óõ«)YÖîöÞM¢GˆvÚ¿#¢ã9óÈÍf8–äÔµÿ‡·qøÛÀMc}¢]¹ic–•“vUÚ6R9$ô5äÚ„VqÿlIâ Úi.–Ýf»Œ™Œ¹(C¡ ¯a6vžŽëJ³(ÆúD†(÷bFZeö3^i¯xsIÔm¡²ñ(¿·ñr\΂úk"dX­¿t²™2T…&Ó¯-Ù4+±yò¶¥tÒîØÌ:íîñÈ몼ÓÞxÂC4šÎöžH.¢;ÒWŸËqüBŸÆ‚^»«xÃ\œͦK«DRûß” ÎìöÏç[:Ç‹f¿×/mï4É4»È•DÖa¶´-ó(õÇ&ªx§I³ŸS#–Éi¨´„ŠÀnÞHù³Èõ®]€ž2Ö/®eIfxëa2¼›8å˜)O¨(ö]ÀwZÄ¢ßPžHà’Ô¼°8>\±å”¿CÔ ít‘ãß ßø‡ÇÐÏ/Ší£Ž(&2nµº³ä ¯$g½y‡~Ó¦Ek¤i÷Sj"eG&ˆpH'Ôg‚µî‰¦K§G%ÚÝêžb²†fòâW”p£Þ™÷‘ý…ee«_Ä—÷wnû@ Ècãä#¹¬=IìP½²Ð¦¸‡J›o,²†!º:â¶|SáOøVý´/ijÍn#gC2±$‰‘ÉõÁÇjš­Qfðe®©ý¡¦¥»ÓïbO($Š?xŽ£.÷ kÃ~4ð…Ç€¼Û;䉴Ð_k®å”ÊØQÇ;€Ídiú†¿á¨-ôkS–ÿÃaœÉü±Àòƒ·…ÏS]×ÂÏ øâ:Ÿƒüš]ÍúÛ È`2›y®Ê ΩØÉÝAƒ\¶—ñ!tý¦µ£<6d0º;›ÌBU¨äp0Aï@º§…¼em:ëÖ†ê]21áÄí0ùrXÆÞÜ•‡µËM>;j«”׌ØÕ÷Fâ\’ ù¢ÏNAúf¶æø¬ZhP\øÞNÅeTUy$CªHÌØ<)>µÏøkKñ6²ºšëz.œº‹$“é·—R|öÑîÞUÊç;sà {ޱñ…LJ¼c'ƒ¬u)•n!St€—O'rŒ³œÕè~ñ×…5«ÿ x&ôÜÚÜÂêómUã ã<óÞ°<=yàí‘ëºõ£ßjWy·”AÌDðISÆFGÍžp*ËøgKòîu 朡f¶¶\IåÉ×€sêzXÐõ½OJÔ#K”Ub‹vóOÌ=Ï^NkY¼C³¨Ã§2¼ò;•—jùSƒÂ©7¿zò[ é>!ø‚ú$^ÚérÚ¢g뗸׼@÷sØëèu¬ãG‚ê2 yã¡?…,ø‰u]cáø·ðì1½èwÞY|4Gæ(0Ç3Ðô5ÔøZÂ×þ; Õš;‹‹Cæ[Jò9èOoÀšÍ¸x¼9 \Cã­etØá!-D±S!$mÏéœÖï‹<9áËý7]ˆÜÜ\¼>NèäÄ| {äð}(Í[;]7HðØøsáÇ/4çÀÒ±ÆÙ g§¨ô«"𾑧Mgâ}NòÊæýD1³•}þ?sTd𯊒!ã+HÕ¦|ý¡AÄÆ,ü¤/§©ƒ¥ëwZ7‰/n´UÓI-oS|¢6œdþ_ÊžŸ¥x¯^†Ö{ÛØPØÉäÁ~F§#­tÚeÍ·‡³ùtù$[w‡~2Û€_l‚{â€<þ?|AҴ˯x‡Ã±Ûh–7K§Ü^E„q,¬‚:àädô®âÏÂ:5î£"Ë3ÙM™c“ÌR¡€ÉÝŽNxë[º¯ŠôK7Vø{â2I¬5«Ÿµo‘ÎÃå€B¯màóŠàïtÄð^¿m øH:­Æ×€€ ã;\ž½0s@.†¯m SiÁ§–G2D¥÷+Îx8ïVµKÿ¼¶vú$‚7™eY”eéêA¯ ´=vÖçI›þ8¬¦FÙRÆžŠËÛ<Šó}[þû•’÷IJygØHØ0£ƒþééõ ›ü¦Â/ ¶’jÒ¼¢r R Ûƒ’Nz÷õ®ßúÄúἿ¶¶ŽæÂÒSé´Fѱ9Âþ4íÂv×>%“WþÍþÏ¢c½œ5±œúsŽ:V¾¹k>Yí¶B²²# üãç#®sß½ÏÿÒò ´ùÚ@‚òe†?žER»x= Çõ®©‰¥XÉMÙ@ÌÁ› 3ëŽxªóxq_LdÕ󫟼ªpÌ8ÛœŽÆ›¤é¾#Ôgò,t¥{HˆlcåÆ3ÏQ_ŸPO/ˆt›©V[5¥‰|ŽTÛZ’{tMÊ · I ƒÓ¨uüMMâOiÖïˆm!¼+µßaóØmïüªæƒ¤ÅsQx„­Ã:î*ï°‚:AÆ¥r ÆÛX¶žÚò-#eófQÑsʸ${u¯F¶ñg‡¡´Ïí4{X£'ËŽ#e{;þb³!Ðã»ìH¿d‰¡Ä~G$žäÖèMCF´XCÉs”òUw2îèT`’kžs6„Lß xwÄ>/Õ•ôWŽÎÞ$IrÅVþè>ÝéaÚ—ò>(ha%f–V(¥TàžHà×b|;©ê–Lš¤¿gHˆ[}önxeÏc½lXI¢•Ò®­˜ è϶PyÎ1‚=Ï)ˆßÍðÃP¿ó¯lΧlU£t½]ÑGeà¾HîzšÙÔßJ³9KSJÓÑ¢±Ö¡²ûdîϹ˜ÆI¶?­z4I©xoTŸK¶º…¤0nÊ€pž£=óÐö®"ïIÖä‡÷–ñ¼LÌòËîXA9dÃß­*ÃL“QmBÊv½,ÆËåãp >™õ<Òlƒ/TŒÞi‘i³:«Þ’̘”óÐ7%Aî+uô%Khm­u«häŠAm3ykÔ4 õõ=è±Ó¼=<ÀË!±ò£µ´UDÊI;*’;é]%µ‡ŽÿáW°K‰ n‘w”«á¿‡ûÓµm7Âïmm¥\G4—š‰ ,çhr«ÉƒÂ‚x˜k„wvÐÚiîúöDVñ" ÀàþÑ—x‡NÒ¤[û [l3$ŸeO9·ÏšH?7cí]7‚LÿÛ÷šÆƒ#lÓlÈ‚c®zHã5¡ kk£\ÞÜÜùk}'ïY˜,AÉåR2FãŽFsŠL 4ß[êÚ]¸šHá1Ä$lF0Iäpz/¿zÕ–ÊÏDxux›>ZIKpÁGaéí\oŠuk߈ZTÞ#…u-!N>¡ ˆZ, Û˜6ã¬K­s^³Ó ·ycž0«¦¬«ŒdŽ„c¦;SR‹š„zOö†«áé¦[9–Ñw±#…‡~ç½uW:õ¼~´ÖôÛK¸Z º¹3³ù'3ŽO|v®m<;§xJÚXßÉ,k²PþôcqÚ8enÇ9µ,¼ÝâÚ$Ës+ÈãnŶ„?ÞÀÏR?‹JD3WH°³Öm%Ô5"ðÁ4%ÖHÉ2FTõ#ÝÓ¶++@Ñ%{Û„²ÕÍÃÜ®æ{»=ÏßáÉ~~ƒ‡¨ø×]Ó"¾:Ik‹·Í²¨b rNFCâ/µhè—þ(ÖŸGµ›÷q¼Pfc ÀÉ\±ç¨Uüj€õÿøôÿ¦½âq}9†LÃF‘»«íØ`äqùÕ uË=ÞËà #–öý¢!⸠"ê¶×=zTþ×´ë=>ãE[ËBÎ8U¦–âI"šY{‚f1è½qÍvº¶›â1YVM2ú9"h¢ÒJO(Dï6y ŸR3@ú׊ü3>‡—¡é*ºÔVæö9 YT"… 3Ëœv3^â=Ã>ÕtWÂZK£YÂÉuz.X¥ÅÊ¡." H‘Žöâ¼§QÐ4O ¿á#Ô¯b–à4« .¾tŠÝ°)Ïàk£ðׇ>!é¶>oŠ4x,<=s*Ou8{ÖWämƒnã‚p3@'K]CÄPø¾ÊöêêÒÎ%º{hÍÔ‘œœ…í’HϧZ­â_Ùø“KM'ÃúMÈi®/‰Ã?_”ps§­lipãø–ÒÇÃr7MhÜ.frª>gb (lã¶1ïY3ÙÛÞØ‹KÑyw³€Œ%ËAbGʼ~=è°Ö¼3ám;ÃúF§k‘µÚºÉqm$ºryË‚AãÎq[skú‡‹î®tÇ·I´èRÎY„ï¼aB¡?6qÀþ•ç‡õê)âdš'OÓ·šF`Ï31êÎ~N:`g57‹$ÐïähVñæ*« Q[‚L&цlðÐe ø{O±Ö†»¬´‘@„„± òIû ƒÀ>Ý:U¯&ÚçTÕ4AkeæGûØ`<{¸P­œôëï^;¦iþÒF£k]›â#X#`óˆ3žo\v¯[—Lñ †‹§­´r\´Ø›ÍphÙùŸ¹>ܜЄ|dš;Zx'Äíü#©¶G·6W ¬³Ëþ±öòvgŒô«ßÚ¾›VY<3Ž4R²:ýùås…UôéÛÖ¹ëí;V“Tò&µÓâ3¸ ‚rNy9¼Q¥N³ë··º?–°²«B¤¯&Ñ‚08õã¥SÑõkoSšélnlu%BÇ—òö½ŽqÖ¸;‹¬%¼ÑmÖHlÕo57aEp<Ñ-¼ÈO®0Ä}x¯¡©ï`•ž˜¯Sæí-;KÒîíü[{qIEh‚àHNöòqȯ<µ/4Ûí7Oº7—¨&v_!#@~ð_¼Iì=keõ %¸‚M]eÀ "ù‡¾ºçùWœÝÚêúEÝΠ<øã–L¹e!J¹À<õÍ|éî-¥¤Vª×vË<ím$eíQ”.Ö?}˜äƒì:×m¨ø+⾞Öþßì¶6@2É#¡–Mü…lîÇ@G"¹#Y¹Óc“Jš¾êq,ŽOÈ«1–Ç$Ò­é¾(¹Ónu?Éd÷—±¸6ÑNJÊCç2àç€9ÏPk&ÀßOŠz®‹,½µ¹×¼Y{so¤‘¦öDó7„1± ùØ‘€y¯CM/Ãw𤸝NmEf6Ç<¿kÉ,rIguû õÏzë¼Cià©£ÔüS­ßAÚ"¼jM†6yˆb#A’Å›£uã4¹„Ñæ>,¶ÐRk6F‚ (&âU8˜ŒçŽ8ϧ5ó®»:sGq*2Å*²í –È{úŠú‹T×üGsá„Ñ ÓàšÎå™#CóÆèwSž„÷?…|ñâ›{]BÞo"=ÌÒ*ýç;û…¿ZÒ;Š[-ÝÝ_hž*³½†"."QºHþáà‚{që^ß§kOg-Ʊc ÄnQ¨éÁÉ9Ï_JòoÊ#ÔVöáöCåáüµÁʶ5éz^¦e°M…Å´Ö’d““"®8úñ]3‰gN˜Í{oÑ/%’| ±#‚qÔc¯5¯m®^Mq§ö%ŽÎw)n0W1ØñžhÔ4Ø­àÓôëY!’Þ Y®€!¶±!¾ï#Ø ó ½F×Wf¹y¤XaÚ €²¨Î×ÔÕ¡½‹ºŽ¿áGS[´á%¹™ŠM'ÌKtàzqÍbEá«{û+‹Û$GK{ÀáJcp²är¿á]N…§A®[Ësöu‰¡vXJ¾Wæêêv¼{”ÛX`Òl&Ó匼¸f°#×<Žâ¬Àn‘¬húÅÅå´Ô™Œªÿžyã©é]¿†nñ:iÒÆJù€D±ü¾c¿zàVGÃ/MðÓÆÑx£@Óìõ«¶ môû˜™”ŽÒ ÉƒÎ;ר|Gø»âŽ^#McÄ6öYB!òí×Ê@È#…cõÇj‡¹¼v*ëšo‡môJY á»)"çzh†ì–õ-ÛÒ¹]6Íõ nœ¬l÷xMì¿0ÆÎ€SdîžÒ^Ç,pʬI¤år{O¥tZ†¥%ç…ì´}RØ›ˆ&F[˜ßj8s¿×¯áÖ„’½âÐ5+·Ñcx.'EoCmr©‚ê¿Ý,~^:WY·ðÅ5Hô«9Z]E\G%¹€W 1ùº‘ëžsUn¼â:Siâ3 Ãc0%î~HÙãmÊêTþ¢º j-ч¶»¦ZÜÜjê±Zã!Üò­ƒÁ9Çè+BÏÂ1Ö­åñ_ŠwI*Nd½eÈ`OÝlcæch< î|YjúLðë¢á-¦¸µ[ç·-‰åP:e@ôÅsW^(‚çMÑt(*\@®žYF‘î.n$F¹ùqзáHR_i—>#ÒµÚdÚ´ÅÌ6$Äæáç¡Á5±kámfê]X0ê#F¯Êã;\’Tá;qÙïíZž¸×5‹ÔÒõ¯ï´G+¬™Žá¡Aûß¿ÐÉç ƒBÖ¼%aæ³g§ZÙ©¹xw|Ä“'±êÃ{ S½µE´¤¬ÏÉoÛ_ö»ñ÷Ãÿø‹ö|×m.ošÂIâÓͨ’ ®ùÓÇ«¥ÇMÀ’OÖ¿/iÚþøoÅ73¾¹n’$Éd#ØÇr2 `¯ðç'’ýÃÍck¡ø¾Î[-BkÔ+Ú(Vt\ 9S¸÷½-?ðPoÙÖ_Ù›ö’×­l-!±ðŠ¯Î­d€mo&V{¸ÊPNyv}.[]N<¬ð1Ø~Wty×Â)~Ïÿ #TÔôÛ÷û5ÝÔ°Î@µv–Ýã;\–iÊxëÞð§î¾ø“Æ~ñäÎ|‘Á.£›­WY’ýý©$ |»P…“s ùCÛ¾Toëúî½§øbûGÓtíuâ:¦¬ja¡¹J²‰žHÝ¥¢˜nUÉ “Î~ÇýŸ>ü`Ôüq©ü9×Ö)o-ì!¾ÔÛJ·7sCÌfeÀ+*Ø&æ`ç׃éžYè7ÚO‹tŸKiâßi¶~5¹Ò`ímgYf™%„à±BѦè¾vRI …äׂØi¾ ƒáv‰à}Ë5þ‰~÷wú–¦UVÀψ’Ãs#*þèöcž¤gÎì-,<#¤Ïñ;Ã>-‡W‚Ö ä’â3åGrú„dİ}Îål‚p¤`óM>Ð|%ðªÛÁÐê §ÂA-ÝÕݬR‡¤Úz«G$­!ó«|ªÀžqœÕ$Eá¿És |:ø•¯Åwkq¨Í¨ ²ƒ7V’cŽ<ï”퉲zã®2j‰³ð·öŒÚGˆówi﵌NËö—‚]¯$“ã8$(ÀÉÉàU?øËÄÿ¯¼%¬êÚ«Úê­°éP–4,ŠY²,Ì¡\7±^}¯ZßÅk§\©€40[—ÔËÙÌLåC³t*1Ç4ÀõO Y|Òtt|MÕ5dÐÚmõ#=ýõ·ü²ŽÚß‘…b\¹ÿZ\áEPø'›iàr_²ÜørÎmFòKËTŒ‹?(‹wQ–rea‚¸ `údùì~ðN­¬[躄š‚ÃàÕšÎxn.œ*¬Ñ¬¨„Œ'¬Oºv? ÿhÝáÇÆ֮|{¯ßxfËS´†{:ãQluYCy×7G!Xð…PÍåµ¶œPº_Üxªûà%àý"Öòâ`5«}Já@/¾%eÜL!Çî£û©ŽäÞ+Ð|w¥a¯|eñÝYé×/qy§iê°_͈ð¾[¹6‚£,?Zí¾$ø§ÅŸ´x´¿…-ká¯i²ÚʆØùþN6±Cœªî„•û¿(oƼ—âW‚õØôÝkâF¥¸´–æIíõO7ýd–¡R`ñœ—Ù!ÂàzqI²’7aÕô)|=£¾¡ åŠ,%ûLe¡ÓÉötœtûG–xÈ0›Eq×/†_-³É£˜“®»ñï„ ð¤Q|@ÔtýKâF…$6 ¥éöߺ†ÇNc ¼t5Ìï ¸Üp6 ·»Æž ²½ñV½gãHn¼S¨hV–5è#ßxa‰Øùl‘à"3ù…°Àë^_ðßáßÄ |8¼Ó¬üo%ÕΉp4Ûq¥é±Iq{}!.cinIß””,å¹t¯¥5o|AÐ|5áâD-à»ëm*[-JKx•šYŠ€7…-˜Ç9SÎöÆ)-ÀÒXµ_”Öbƒq“XZ'í â+«¯ÏñK@·ñ±E­[½ÙoÞD/®¼³Œma·n (ÈÅzÁ?…Wþ)ñ›Äúºh>ÔRþϸ’6™ç[ApyyXŽI  ÏøgÅRh¿ÚR‹›{?ù´7köy$eA3ÌÏõUÍpZ6­¬[›VN‚öÞØ.¡ º’ó5ÃäDÍÜ„lÁ=p+²ñ ßuo…ÞÑüa,o¯4÷×0LåçßoÄ~fx\6ާ¯JòÍn-GEµg·•¶LÏmÛÈaaÛ¦) =«Æ:_ƒ¼Qÿõ…õ»ý^ãW‚S{=ý°²6W¬á^6cnI»+#@Ò´‡šÆ¡c6E†‰q5¶«5¬‚fÄh Ä'qf`r¹®'Ãä–¶‘¥¼WöW6æßlììd‘›q>ZóžƒMzN¡'…á ¿ŠÃßٶóJ·ÿc(Bí™fÈK6qì(`v^3ñ榾 KYÜhòBÞíæy…ØpJÆcP (R HkÓo>%øÄþ»Õ<[âv±½›dpX½ †ŸËTü¢r œZò_¾¯´º¤3kz}Γ$ÄÞZGp@2ì•ÀÃ?/jæ,¼9¤Þ[›½o]¶³ž_>Ú×yÄOäR:76O=ªÒ#ÖüKâÍG\­|„¥½¬×èPH1HxHû‘Ôó_BÅâ nßNÒ4½:d{{«›kÙlÇîžhæ FxýÇgÉÆsÍx¿Âm2óÇ_að¿ˆ4Ý>YáÔ./ÚY%dq ‘* +œç>•ôÎ’~Ñð#Oð¬é×úŽŸ:Ü_Ãû¸íç°ˆ`dFx`O œ€ô›¯éþø¥ëŸu+=NÆÌ¼·VÓÌV&¼RÜ™`Ý»;@Î x¯Ä]:?ˆv3j: RÚëQ»YC=È•M¸p¸$¯>­^‹.Ÿ¦xcE‹Âß­­í­ï‘o´è\‡»+jÀ>áÉ AKsƒMø… ê6 ]Àš:ýc·ex ¢hD‹»žIÉϸ¬ù /…ƒÅ³â½WGÓáþΞxm£ešõa€)…‘¶†ÞrA9ù€Å{—Ä_ |%ðfŸÿÖ‡â}þ=q¥’óMÑ-|©Õ¶†_´+–f” üÀö$•â÷ž Ó5=ϤÉi,O "ú(Ê5ÕµÂ>ÖFnIW?2ã^ kAã/Œú7Ö¶Éwm§O%Ä× M4PT˜Ù°±árp{Ösª£«ØÒœy´<ÞãÀZ牦·ñ¼×:vBhï¥à iJŒ•áUúc­všÎ«§é?t+ÝO”^ÝO=½þÂY^.P¹ï@Ç_jõŸ†_³ÇÇ?‰ž Ô´Ÿ ü=[+ŸŠW7>6ñd1Ü!‡K³xÔyž]²®Kuy‹’ç?;™ñFåvwÐËçQì~#üøEñûö‰Ôï¼û4ønÚõ=sWÅž§ÿž× %g}¹) 1;3bãöoáüáï‡>Üx/ö‘ñ þ<›PºŠþKm*/ìM2Îê"Ù£Ýzv8 · ¡º(÷ƇðëÃzV¹yâŸß\éwz‚Eùµ`mïV¶6’7¨à:…l ^šöÚ‚Ú³ wŒ³ Çè8ú׿YÏWÄ^´‹=ü6WY½Yç¾ðf£àýLð·…u ™´Í9ÀX¯|·&3€rè©“Ž2–8ë]Ì7ou6”öB䑽QïþÕj.ϧ„ÓSí"fHÕö:‚>ò“ß5Í^Þk~Ñmnt=2ãY»F ÙefMßxdvñU«N«¼ÝÙëB >F·gâ»9eY…¥ªàHñÄL:ú.C8öÏ\b³toêzlKg§°Ô.$˜°½xŒâ<FrF;gŒs]Š|[¤|5ð¼¿þ9x‚ÛÁú4LZ[­Ní-âýØÐÊ»‰<_”ÿ?ଚƒ[ÜYþÊ~ŸÈv…Å>&‘6M2ÅæZiíµóÉ!®Jtû„W±–ðî/ðFÈâÄã©CMÏÕ?k~ è÷?~%xªè>b½ÍƳrÖYY€T‰$ùšG$XòÌÄ WáOíÿøKûQøO_ø ðSá½Î‘ª¬mqâÍb²jEa$wz|*¢â ¨e@ðÏ+Ç,NDR@5ð׉îµßo¾"þÐz­÷ŠuI'Y“θ ªÒ%U ¿;y ¶Gq]ïŠãÒ¼!áÆz„IiÐÍÅ„‹‚­pªc‘s±3Û5úŽGÁôð®3¨ï-ϘΧ»}խωµK¿‹¾8ñ ö­­éF¨x‚YµÄ›T$’³lÜGΊŸ]ÜÒGãO®Šlô«VÖuhî,lAŽ'‹qRɸ†BÃæèEW´ÓôÍo_W¹×VÈ\É­ðÎ2® gåÀÞ‡¯dñ%ƒaÕ|A¤Û=¼Ú|Rîu(Vv„­Ó<6Ò¾â pÒ1²<¹M½ÎoGð_Ä‹{VÕ¼(IZ› ¯2nXO4da‡M§9Ç5%Íî©á-RÿâOÖ3gåÛ¾¡ ÄqÚà ¢p@'åfÆ=ê­¯…ôøL®tág¢É¤ãlä>8ÓÌ  ê ûÇ$kJ]0M§hþñ42ÛY^´þ_º€‚\¼Œ+D¬e#±½Ôuÿˆ“Å:Tcì7‘›ë{»³—öã $qqÀ;pê+ޏÒ|=â}Røkñ9¬ïbñM‰ýÝÜ9ˆZ9mBÎÇœçÅu6þ7²¿¶Ò­5Bv½¾vÓN®Wη6ÎÅUhW=k…ñRø»[ñve¥k:& ÖP}‚Ü,l-/-Õ·m•NYe\„sL–U𞂟 õ[/‚ K§„TØ4ú›g½R|]²T ±¦üN…nü)®¿ˆ-?³ÑnͭضpÈUÈÜ©°Ã–ükOÅzU¶«ð’ RçÃmexÚäÛΤ…í`Œ™¡W$cvH!†xÈ«–)ø_áÏ iM‹Z½Æ†ÑÄb¼Ýo-ÁØ›£l˜ÿæƒ3Âþð.©«Iu.©5¦¨#³e`-¯­¼  €ÅÁÎï@G©â3ªx"ÇPÒ®l¤° žpº· 1G á”à©çƒÛ/Žã·Ôõ-µm>ÇM‡a‚æ=0«#6ä<ñÁ)ëÖ²n<[qáí];T’ÚÒån­e[ˆ7,ba´à™_ÎqAI¿Ž¤ð…—‡´ísÄ÷º–¹w§%ü 2*®|¶eã'>•èZ·‚õ‹kMKÅÖÒKåí½Å£¦â"GJ«ãî… «Ëð=¿Œtkk3ÄmÚöžÄ%ʨ]E€#“iá d^ã¥u¾мG Mãû¸ ºšÎî8¤ É$Y•âôBxì¤b‚.Ö|E¡x¯[ÕaSíöÒ]ŽÑaîô;pÀÿšô Ë{¨[ø[Tš-?K·ŠÞ É @e”º²“Ðî?+šãü5Õµôwz~ŽtåšQ$ö|;Gk. íü@œþ5¡ð£àæ§àÛÃþ%»MVÌòÌœHc»™Ú5*Nà±c ŸB(®ø£qáÙ~2ÚiÚ½„š–žúœdeýÙK{ĉ ñÃy œ.pSð®Mð´žñ†¡àËyH¼7s©N%UÉ™!Œa2yG`ºr;WEy¦økV¾±µñ\²ØZØÃý¡%Å«W†Õ*ä6ðF\VŸ„mü˜þ"Š[Ë”Öá–m6YPÀ’#®ÏºÇ!Ôåˆ=zŠâ´_ ¿‰5m;[¶²{}Fé`•o“÷°Íz@’8e~—·Jô¿ˆ:\Ú¥åÊÝÃkouuiž bà—F2w)þé]^“á¯øW^0‹MÒ®b³¹ÔÒ+¤Ó¬›~R †'ɦüAamyâ;+=Ní,ì™.s”;;>qŽ£šâ>%KàÛ RÃv©«Z%ùµû:@#„Ë1ç‚üнâYxÊÓG›Ã7’Íwa<+¨isª‡’;¼I Ëõ€c ã½q¿KÄ*T¶Ìnxøxêj @Ô"ð¬_³äÚä‘xrÂi'ÒrIýšÍÿ.§~q¡ÀÚŽ¸dž®¼Gã?ø‡_Ôµ•‚ÆþY4ý>ââd $9ˆJêH3` gƒê+Ú%ø½¬|O¹Óü ¬Z®¥sk£O;ǰË–¤ÈÃårw5ë~8ñ‡„míõ¥ÓtË›zÆÚ{{}:Øf9£ 73ƒ€‰¿ ¯N´›°ø·DñÕÖ§àŸÞZ\Zê8ñÁm/[Ó4ÝgF·¿Ñõˆ’0ów2›6và`gê)÷Þ.øu¥é–¿ð…Mub𣋠•IcûJ¿¼ HUÿh}êÃñU׈¼-âkïê×6w1Û žkB{{„ù‘”ýÒŸJõ;ÄúJè:´¶\C"e¤Ì7RI‘c|oÁÈ +¾¼¼I®x/ÂR>­Ïk 3FÐÜœòY[™*qÁ\ôx£\Ѿ2éúgŠüz>Õc(³—yÉ2 ù+€®wŒõë\æ«ãýgÀ^0Ðüye—ö¾·¼am0ó–H¶ždŠ2ë´óžâ‚9ÝÃߨ~ –àë!ïî&»šÞf˷ٮآG“Ô# zgÒ¥Õô'SŽ«¦Ãkåö™¸š(ÿt$…vá€?6ã‚+Þ¢—Uÿ„cF¾Ð<9§¯j1ÚêZg“*>H˜ù’±–^@dn⹋#ÅÚ6—/4—mwqâ+)$D‘oÊ7÷‡jx}§†õ¯ü::/ŽõWQÑWûZâ+}°Ü%œLjÆGͱ¹lrW¥jÙ¸¼µ»T»7cPa:ÛFû"™à V7BpÀ‘Ÿºké ßüø‡e£ø§AŽÁk[x¤QаÚçÌaÌ€d©šò?ŠÞÒ­üqÄøí¡µÓÖH˜%¨Q¸Ç¸H]~èf=@ƒ@Ôü)ãoëz¬·Z¤~&Ò&±´þËÔ@uûU´Ñ‰¼$—G”psÊç½qùð÷ŒníÔuß Øë7±kS}—N¸¿e†õ­ßW<3hùÃ`0æ~è×òøÆ?ÚÙ=­È- vwQ/Ñ78)»9ÛÓÉ>ˆ´Ò¾"=¡²ÒüS£øª)„Au?hXÀ$FW«-…rrít¾“Y‚×OÖu»ÛkXm$Œ=£‘Á®¾UlôTV;AéZå ðö£ãxï´«:Ú OSš25f"Kw2*<»N >÷¨üë“ø•o{eâ«O-µÔ–7O¤¾£ ~M…Ø€î'iÇ8cÔ*’&GãÏø'XñΫ¢Ù*]éúU£YÜÃsE{ÈœýÒqÐã¸äW—Iâm"ÏL:u­ŸØ¯à»ŠâÝ-g/ 09ùÕ¶ô=9­½ZÞßÅ:¾«áVì]Ø\Êm¼`*]2 ¶pxÇPA®SÁÞð7…ü;›ãø$‡X›É´u° n’à3dº9áåä|¦´èIîz¾›¢Y_xF½·Ônæ‰%&ÿ,Á¾PFÓŽ3ß®+Ô4·}BŒè1Æ&ÐUþŸ!Ù‰Cy–À%±Øöæ³t øgDÔuk ín)îf€Áo$¡pcØH#œç#“Åuž!мsð'GÓ~ k6Ún¥¤j6–Ò½œól+e€…# :·Ö±*&ÿ†b]+FñN¢—O¤_h Æ7†‰rÁI8¶ERÕüá}i¾!iìòÃyV“—rbƒÍÁ‰¶Œå‰êOzÊK½?OM-ou½E.æ3Ù[Ù·™ko€…ÆxÜ3·±UtøkÄiðëBg·Šò&{¿2?Þ¤–­Øû¤ÁzýœzF‡¡ØxÆÓTM^úÚx®%š.F& ¡1Ó#©&¼àY]ê¶Ð隬ÑjV¾uË%ÜlÞ`~X©È8ÅsZ,ž¾ÑgøSyâÍ-<¤ú…Üðù–€Ð¢7!úcé^éΧ†ÂöïorÚd_h±»A•Ó’$,˜àJó¯ø§Á/q¥kºv™yáë_°ˆ¯-!—ΦŒãÏn¥UØ‘´ú×9ªèZ'Ä ,êv'YY€íÈ6÷–ãæS¤ŠGÌcX:-õ®±6­¥èw2éW0»M%Ê ¸·mì †E<ì<íÇJô[ŸÞI£ÉâojPÜÜYËçÇk§ÊZFƒÊxlvÅbkšSI•‡äCqch¶Ì ö¶^6÷`Í[ºðwŒ¼I{}u¥_›< é±\¨Y# ˜äd){b½ÓOÖt+›‹(“í6Üùæ0Þ[H2‡pgÒ¼ò÷QÕ[_´]6ömHeIåB¬Ò8þñùvÂâ‚/Gã™t³wáM'I[Ásgö}A˜‘æ+ èÑžÛeHé[z\ðxwá6§u¡™o¯¥¿†i!oÞ·Ù™H)·ÕOV#½rv&¿ ÚÛ_ÝZMs8œ¨%‚³s€ÃÀWƒÚ½"ôæÕ®µ öß[h‘$žT¶M·&CØ€Gêx ¨Ÿ?ë>?‹Äö±Á.›u¦CjÊöÓ´<îP~Bþޙ®ŸÇž>'ÒöllŠÌË/ÍU›±Æ×=kfÂOk° Z#%ÅŒñÈ÷²Ç˜¦6ãhã$ðq^©¥k¾ð6¼úÍŒ/i¡¶–ÝãaåÌÜÈ>‡µ>/Ѽd·žßÆ©o§Íbˆ¶×¨Ù,A~Uääžµ‡ü$úÎs®X–Yb™:·˜#?Âã=}yâŸxÄžÕ|[áûë!¨[:M³BªÏ ƒïqÁ Ü£+ÜW€ø/À:®ƒ|l%־ǩÌÂW²P^Òá.N>£ž‡Ùk¶ëu§ÝÌ÷v–²ˆg’{Waæ†ÂÈv·xßR¶KM¶Ááù™ÕFÌÎXuæs¾1ðt×z}¯ö5­ÛÙÝÅs²/—4{ä|¼9ç&X0!»ú€hgž]êVövO¢ÙÛ]=„ÐÆì¡Ÿ“­¤`ž*Ô7¶E¯ˆ5К}Ìví<@ÎÕ‘—ø[Y:“¯ß\Íðòö}2æâÆv¸†õfÀ™n3&Õãœç§Jëm¼wák­nßUÔí’öX 6÷+å ¡$gr©(rTŒ q#Çöͧ-¤ñÛ}‚ìï{e]“á² ƒë[Zeô~%’(mš[ktºXåf™” ˜eFppqÈÍv÷0hž*×d³ð4Eš\24bë²¢¸3œ•¨î~ê~2ÖïÇ„ï-¬çÔíåk‹I%òÄ—ã÷må†×­yÿÄKÏÝøŸP³7æiMÌpÁ*`³í@.GÞþùïÏ5©iáɯ¼0šˆR]JwH~ĕެ|xÉÂCœg"¹ßèúŒ:~ñfÖH®žÙÜÏmyøBQ”©ãp<žyíÍ{ÏÄTþÔ½µñäv±iÎÅ:ò¥ÐŠE;xàþ´8¿ˆšŸÄ•ÏÁ«;Í#BƒFQ¨YêMæn½„°u†F.BIÁ?.;gäž ²ñ&“ –ãJ¹î¦Bʧï²0F;v¯b—ÄWˆäÕ5m/TI¥·€Ü][)÷è9dpzýi!ñŵ¢‡Ròíä³eQå–Ê–ÊÉŒg˜³¥Î“´Ü¾RÉóŒ0¾ç¡­/k²xxíSL†o³º¬!ª¿u°¬FsØ×ŸÍqâMsOÒÆœ°JòÆ °ÈLãîõǯµÕøêòóÁpMà-ZÎãHÕ4ž60™D‚UÜ®§$8íô4z“㨴ÏH|lÒýŠXáÇçŽ9V pqòöè)¾-žÙ5i®ü9¢Ç¨j>S2\KNInxã’{W™øËÅšOŒô7Z…¥±½Ž×ì×!ã/¸ÈÝœnr+Ø|+â;ß üAÑþ*hI=Ìzl)åŽõ‰¦pYwzs‘@Ó<÷À—°[붺¤VcË‘šö9$U°@äÿ ìEuñÕÖ¥7‡|V‚ {‰|ûqÙ·i Âüݯozáõ­CÁzçÄxóWû3Yj ò­Š ›"¯qžr;Õ¯‡73øNmgGeµÑõØCGaq“Ë‘yÝŸÀ~4˜íî<s¤ø‚MsS¾/kg$‘Ý1ùàòmàžkΞúÎûÅ×:^‹q$ñÝŸ5•ÆÅa#p',3ÊŒÖΟ·ƒìæßS¸—í>jËhÜ1iW¦?LW•ÜišMÝÕ®…i-é`É,M¶eí×841ë—·>-Ñ/Q1íŠU7ʼm#¹üëÃ×>ðîž×x¯ßøÉĦ1‘·=[ÔÖ¿ÃoL•õ-®m™Yffqæ®ã÷•ºp;kFçÃ~Ôü=ã’S ídFÀÈ9=z ñŠ˜ânßÃþ3Ó[ÄZœ7?<†3†%8ìzŸÂ¯x:óÃSèK4ªÉlîê’‘ÉèyêjM7Ä]êgðŒ±,—1(d1ˆã`HÀ9àœw{Rð®³y5´ÑÄ…üñˆ|Ñ·ðôè;PQè·¶>ñ‹xl!eø„2¦wïÓ<‘\mž‚úe°»ñ{¦÷‡Ì·R¥VVì tW-uáËyü!§\éÒ´:­¼m&é2I(BõãûßZ–_kqÚø™n¯UËI‰Âð=¿:Ù¾¸ŽçSKý^Sda\æJ aÓ·5KHû^»oÿ¤ÑjNÍtYNÜ:œðÇý¬WM?Ä鼫\ø>I¢ÓîNÂbºC*wt'ŒÖV»ñ Àiäë>²žÏRòJ\E ‡¶l¶NÒ8þDÐ>êïÃ:L:]Ä#Qó]¥KÄðHx,ŸÞõÅs~"·Öü©ýƒÅvѽ®¦<¨žU1[‘Á9 Žs[~µ¦ëš†›i­ì’ÞIKÇ#¨q8ävÅKãyßÇ6—xþêyï£UŽÌ`;…^ƒ#œûúP¬'—ÂÚµ¥Í»ù–Rv2!\dOCýk¤“Z³ÖC“Óè*®¯Ù鎡=´6÷KXâ‘6ˆ÷ws‚*‡†ü}ðÞÚV¶ñ°Þ²!1Jò}ÒM½:õÍKö»Û‰1hÖÓyª"Þç³Ç¯Zèuø–û-5¢²d³O.5‚&QV$œäúàW3­êún³(æíÆÙÐÈ>qÓh\~º/è-¤Ï¤kºZBʲÉ3@dó\œyc'Ž@Í4€§{sco¨Fº­¥…Ôp­4‘¯šOVòÜöÆ*–·âË;TŽÒêK]2Ü)Ìv±Ëu3g P}ëİØ\iná–ëhÍÏ–ÆxeÏÝ ~9¬ËsâÍ Q"•%’ä‚]M߯IôZ» =3OƒRÕ=RìÜB%&5¸¸$c?t•É=Ç5Æ^x_B½¼¿mBñ//X僣í8êX¶d ` mž«¯i–±ÜEs×äÛ‡bB©T9éÞ¤ŸBñåýÕß›w曇-‘É Ô{ ʛƷ±Ëu¥@°ÊIdyZE',gåt­ {¯ÈÖškIw'îšÑ¥šàwˆá„85›§xÄ—Óý†ÆæÏC†NæöaŒz‘€«Ô¹¯R×<+¥è‹ýx¢ž9G%¹Ü’÷²J·•©{áÍ ÎâMLA ß(B«ó’¯ÉëŒ{žHí]Í¿t[ /ìZ¼)u²þç?7)È+¸#9¦3caý»5ÅÆ•i.³©3³D ùPlN™r@+Ôðjƃâ‰ö0,Þ!þÎѤvhÒ(. ÁE¶f‚sr6ã-“òg§ÌÌiH“Íô½kRÒìg¶­2+žfSnÞøîOå^›ªéz•ž›´íäÛÛùf\È ©#€Håõâ“KÕ4––|Û­Û®C37*3ýã…R+£×¬ôS¦Ã¥¦§V2È¡‘Ä™Ë/‰â  Ðõ«M÷ÅšÜ.ð¼ˆ! À$““´œñÇ'½sfòD¼fh¤–Yþg—`XJÿw×ò5é—%³Ï&‰iu%äS$j¹“÷¸ ¸}ü*‚3šå!x´ë“Â× mÄcpTëÔîêj¢˜]lÎ|Gö­v–3+Å4p¢HÈÀgnOŽ@ªšVa¦^ù:SGiÁʪH÷kÑwH{~µÖ]k¨Ko¡=¤AoH–PX±êJ¯ éÓkQµñF‚ÂóGŽÏí¨J#²:pw$‚j€óÛýgFðÿ‡o!Óæ¸—Pò3¶EeãïcÎ;VV…£Õâñöž/æÚg¹œH ‘¾\v\‚rvŒö¯D×Vo¥4€›Æº¤þ9xôëKIÄzsr$qÀÂqŒàý³õiqÂòß›“4X .ÑÇÊ‚2xÀïWu?ø«G¸ŸTð»K%ñˆ¿šéÁ Æ]W íW-ô¸l4µ+˜£¸’þ`e»˜gçãvääóƒ´PՀǭí.šãÄpJ2 Ž6Þ¶ñ9àüÄãðÒxv9müqe.®ñM Fd0Ä …;rÜ`tÏqU ®“ákë›=­Á¼ù,à@v¢Ÿ1€ù½±M†ÿÁ²Ê×ZUÌx•Œ¾blQpOSB!–‡ˆ´½ZÚoêö–úd¦ìÄ-Bæà2ôÉ=IéT ¯‰%¼ðU¼ªƒyÎ÷ˆ?_-IùXúŽ‚­\èMõ§›á™Ùç‘,èÆFÑÔ'¯µnhÞñˆ´«k+„Ké|Ð×wIˆ$pÀàB€Ž1À'š±t4mjÖù¤×nZkØò‹8^O%û±ÇAÞ½ÃÖºÞšÚ–“+‰W0̶áPàzžÜtî+/Æz>¿£]GhÐéöŠÈ$’ášIÎ8\€ØÎ=;ÕÝwZ´Ð´»]*{{ ¡O—–óÁù;zñ@Œ¾ðe«·‰Zõ$HcVóŽ]¡}å‚Y¾žôÿüGŵԧ¾žo5—-Ú”bªÜ=Á¯ Ó|)¥ø'XÓE¸]G\žÝÚI®wáVN~\á¹Ç+Ô4í ëV¢ÇÄ×Ñ5¹c#Gi™.ŒŠ2>fÊíÉÆ:Š.€­âÿiQxB->þamc²êâçpódÛÉè7tè óÒ¶!Ñü{¯iñÁ'†~ÇhÁnb3ƒ;ÇÀ2†2¤|¤ç±Áª—>ðÜ^†ßį+È&Š8Þ5K¡‚[øŽÒzn$bŸ¯köú–ŽãÃV—³Þ«ùr!™rx¹ 7 ôÍ+ :ÝGOðVº4ë¹F¤~`·†1GÇ佋VNµâ›¦Ô!Ñ|mPLŒ§={bº/ÜÙø—ÆañœöÚ›`›Ñ~gÞÀ‘¹¸ÎJ“ÆrsëzO‡&‚á{y.dyDK÷PQ]Ç=HÀÀû&©áytÍ8-¤Šy¤Yˆg á™ßÍ|Øè1Tõýqî!ÓüG§Á·Š7ÊÏ8BÈ9 ÷†1Œw­k"K 9|O­Ë¡¨ÌÍ·h ´·@Šzç#ž*-SÀ‘ëþ³Ñ|Og ë 3ÈefKt~B¿N@ ñmëë6ÚwˆïöòçY,QªgÌD žÊHÀõ½§TðŒˆö&[Û‹K‘ ‘ q"Æ®üˆøç#œý*§ƒôoê~7»ÖãK¹4ûTw´¢(±¡F/œõï]åŒúþ©¬<7séq‰ŠÈ7]³˜ãq\úТxP}KL×5:yäͽ°•€Ín™_Q“ÍjÝx§ÁãZ>•î¬5 ;- …œêä|îé]Œ¯4¨ü[z¥Ô–7Iå˜w9,×…àŸjó¨õ+ÇZúi‚\_Ýy­Ú®r‹q“‚¬ppOðt=ê¢k7Ž´C5ר¬üOu«Z¬vvóÈÒ4-’»œ/nyRGµy¼¾}>ôé¾#gK¨v…dܻͱqÆF~èäq]_öN¡¢V×Áö–ó¤q––ôáy,P¼q¢õ9Ícø~ms]Õ–ãÅWÖö^t¯t!¶„»C#®ØâÞç=[Ðñš¤Ð2h6w>ÿ‚µ~ÏWitŽî÷V´¸‰þhÇüKnSý²@Ü{ ý(ÿ‚±è]h5{‹Óc>#¹³óBîp.-‰° `¨¯Í?Û^x þ 9û0x£\,-î¼iujòG†Ž(ît‹ØS,pFùÊ0Xv¯Õ¯ø*†^ºÔ-·[ÛxÒ5¸VŠ[+€N{ÜÓ5ô´šx&xÓÓ~kϯiZV•syo`n¡U0ãs’;ñŸa[–Æ×\Ó¨Ú$vgË1KÕɳ–åˆíØVƒ<+b-$ÕìõäŠ+„„G93ª« ÜuŒréõ«w7f`÷q@–pIJ,òmÚ­´à0N€·îkædõÐ÷Ç¢|.²¹Õ5KÍù­¿Ó‘`ƒWÔ9·°TbÎÂ1ürIÀï^k¤hú]߈ÛÄz‚F×¶°ÌˆË!w”9*¯Œí¾½©–ú›U¶Õu¦û.è– }:²Jìw4g%W<Œ× xkLð¨5œQÜ¥ìmmsw2³$ÑfMÛ¿„ ßwŒÖoašÞÕ|5«µäZ”¨ÇžS¡¦º§''95oJÿ„êÛNÒïl>Ƕ·Â&’wEG,à• ;àV=öÇ…®bÑÙÛQ[’CÃ[§Ìr¸ÀR õ=*m"ò)´ß"æñÄ“Äð*Lh$9 ¤ðAY0«¶«¥hpø„I‹ÛùœKÉ´|¨Wœm=}ëÀüA êN¥q=¸H¯™,cåëƒê{×Óþ'—I‹{èe[kkd’R¡TûŠ?¸ëÀúׄj°ëwº:^­«Á§^Ä÷QLìÚDl22õQéëZÁê&|ñN'N—QvßY$ áNN8ïÖ¼ÇÀ¾1“C²¾‚Ù<ËéW}¼¤TD 8?í>SÐW¼x¾í"e(Èvºã Ü'#¦{W€ü>ÔíST½Ñ/áIxVXØ|¬­&2¹é·8õ5×'¡èzg­nõY`Ò£cmt„¼EŽäeþ#ŽÍžƒZçoeµ—RÚÎÜÝ–+%Äl¸WÏLdvàf½On©wum¤ÙY@Ð¥¹ûFÈÀyCrO0qz÷‚®¡¼·×>ÕæÜ߯Ë*» u;V 9mÙïTfäs5®¹e¥ÉÙf‚dGd'Ží~yï[Þ“V¶¶mfêO;ˈµ¦pä*ðß/¸ãœÖöµáýZÂ9ç½ÔD[ÚDn2Å÷ˆÕóôÎ=jä‹á…ðýŒö(æâÞ)c¹P O.C•!º×Ö©2KúUÖ±¥ÛÄ,å,·jˆ±¯Ë"cæ*qÈS鑚ߟJ‹S»ž-@oÝ IÚ^Ç®ƼžÜ¨„?‡åiã‘Ñ•ÛÈìNkÐm®„šv¦ºÓ2˜(Ú|ÀÄ €œûOshµc¸Ñ¼#.µg­Û3=Á—v0‘õ =øàdÓ5{Oèú¯öF1Oå”™a’0FxÊŠŸÁ> ±ÒoVú&I­¦†[EÏ>_š˜/ƒÈ!¸èµKxm­´Û†y†›" o'ï¼Û9îñ–'¨¹G¤Ø ?ÄÞ×N£$×7/*H#¹×ä²Å³É9Î0¯;µ´Õô*Oy¶ó$P¦Sæ gåXÔ/^8Zî´}Z]kKÔ/ÙDk–Un“†$¤cÇ~+Ìtÿhz}»µëK“r erTûÌ}ãŠiÛj¾,мg ÿhø^²M%áX£Ù€“ýæ rkÐ|%i¢øªHí5»³’ Qæu—¿-óÀž3Šãô šg„ó\ª¼p1ûÏê[бdÄiÚy>vM´…æi$mÀç¨ÇJåf‚ZkzõÌM|_UþÉmÍ1ÊùQ¯È¥Ø`ÉÚ9àWE øŠFø|úuΤÉâ{¢ø‰HE‰¦;£ù‡,Û9Æ+Nñ ð𵯇uic‡D‡S’ìiñ|Ò•6`±ëÇCž+2öÏOðö€tÛ»V´ÕRâˆQð÷9ß÷j'Yn€s¸ŸLU‹]+ÅZ¿‡ìãñµ›É£~˾dÿF¶…’]ù¢×Ðv³¸ÛÙ4«Èí<–¦üiÄÍ¥*ÏUn\ýíÝOJøçöïý’µ¿Ú#áæ•x¾¶½ñ‡…e›Åzb”ßAåšäovŒ—TΨëê½/ìšœkg<ÍbÚ¹fй¤(±Wž€ŸösÍtZ®-ýïØcÕ.¬õx“dmm.¹r£»s³8¿…uá*ºsM˜¨s£øñ¼WñwâÖµñ3]×dñÖ·óZ¹€ÆL±f1À"P PùdŒŽ Ãd“_GþÍþ!jšgˆ|AaâMVÕa,µ›ËKö´tiä"X£~àª,üäÈ⾌ý¾?fë‚´ìþ#ðTCÃGÅ÷sÙâ"Öéî£ßynádYÐÈ2ÂC€yÇÁŸ -| ãßÙøoÇ:âÂ0Ójv¸·»H#áäŠ`ÃkcœƒŠúêsSŠ’>~påvgÓ¦øg®éÞ(ø[á›ÝgáÂ_ÛA E¬L/—²(æ–kh‰ldžhԨ®¯Qô‡ƒ¼áÔº¼ÐeK9~$I¬Û +XT´’Þm8_™^+ï3`† ]É"ÆÒ0ÞʳÎìeû]ü6Ò>;Ã{ð Áš/Âÿék~š©Æ5;Íor˜ÞWžgÞ÷›ySæ–$¾A_vñ‹~0|ø¬x à­¿Š~x>òiüQt“¬:†“tþQRo.I”Åûñ¦ð‰ RŠ6Õ™r³7Áß~hŸ ü!©øŸ[ŸÅ-á4¿ðÞ¼š8žíIxäµ¼;Yí`ù–í‡Ëæá•e¯¡ÿgσ¾øã]sÁÖ‚ûJ¹ð§‡ô»L—±´#Æ0Öö! R³Û:yÍsp‡1ðIùx|@Ñìþ|!´ø££xÓGµ]fæïNxæ·Ö–t"7[EÜ®¦ ì Ã¥büøÃâ?„^,ñ¥ðªo±ø×\†OíyR=éuu}fŽ9Ÿ#Îv„8A#’N³>nðŸÁoŽ^3ñ‚ôoŒ><Ò´Mâæµo.·%•˜hRú[Ghau%H¿Ñâ…r¬¤’Ù'ëOx?f¯ˆž1øá jßXVƃ¬^i±µµ¾¯/²ù1+I'•šË$xÞO¸ÅGáO‡ñÁ«®k‘iÚJü>ÒtíVØÜÜ4S[^I!Š„€ÛîcØ6®2z 3Gø¥ðoJÕ4/†tM;Tñlz…Ó ™ç6óÜß±{€Bãæ™³ÁçƒS'Øiã¬hÚÏÆÿ´ü]ð\vþü/Ò'Ò©áícö_ðݧÄ}ö GÅ~%»Õoõm6Tµ’Ì2ÛEÝ,Ê¿>ß›=«¸ðÏÆŸüm²™~<üCŸOðG…f’²Û솬ô•´ä<ϸ³JsµÁÚ&¡_©G¿| Õç×¼u¬üHñï†-uùut¾²ƒN–äF¢kGÇ:oáFdŒ±#Ò¸í?[·ñGŠ’ñ5}>îOÜZ‹Ûi¦f’îõ˜Æ©?eÀÚ£-Šá<ñsDÒ¾øÃ²›ÆÚŒ¶DI«Å§Ê!´×)¬Æ6.àHÿXØŽ=á÷Š<oâï |Iøàj|2µëÝ[«ZjWš[»Í?œH2‰;·ŒF)‰ìaxWNðŸÅýS[Ò¼MâkM:Ñu+«{ÿì ]×pyY´Œá‰;ÀRÝç"³ü#ð«â|B]~÷\Óµ$ÞYb¡áÈs1ÆÄ@Ã;›Ó¹ï x?â—ƒü+âOÜÍm>ã­WWuŠ!k}x5‹‡¹`€òT"<.ZºM[ÿ´5ׇ-ü3àkxü ’°½:†©,l ƒj©…€ ‚ÉÏ­–âσžðU׉¼_qâë7ŸkÓ,#·’íegöãå ¬AF<ó]ÆŒz©a<^.5}Jþ§Íçí†Ö0„`HIÜåÜovQÎj³üø³£Ûéúù­µpT=ö´ó)ˆî#x†-»@ÏÊäO­j|_ø9³âOü%^µÄ7Qj±i·Ú¤`ÛÀÓÊ 0@@qm$“’=(>/…«ûPüIч‚ü•奅½–§"/—htÝ&Hd·vÃ,mH–âæßI´|ÛY»mtfSáÛ»ÐÅsŸ â›]ÐoM§ÃÈôoH‘ùsêz‰ºÔç¸GÜ#‚݇––AØÈXrzg4\ ¼à_ øžÒÐKs¦[O`nînu˜PÁjÑýàÙ'Þ,Àø *ñÖ¸=^mgV½ÐEíøº‚ÞÖæòÂ=r¼e›”tn7àn ѲsŠõOi6„,‡RI®'´’­íPÊ-Í´ÀQ;Ãnb@+Çzó½óÁZ¯Šìo<[ý¥oá«‹É ¸¸¸‘á?u#XÈuØÝFsŠ´ÀãüCsàMOÂÓk6×:Ÿ—m¨[]Üù’+M*]f1„)YFçãæþñ^‡¡x®ßÆr OÉ¥_H÷¾Õ#tŽòÐ+®Á VÚ@o3¡éŠé5èºÇ‡õȼ q ³±Õíl6&ilQ‹<ê„ä¶xÛž½MRñŒßâ×l.~&©iÒEº«G‰ïË•‘LP®b?º¬sÜÕ 8MYÒ4RÇÄn·7hyñÚéûVxU™ˆ—À)Ú„‘Œf½&çâOŽüeñgQñuÜs´ºÝ»ÁµÆÇt‰B„‰Ù á,ÜÙ>•ká­XKí?Ä·öör}‘®,¥b$ŠYm[sÅ ;pvJõÍNð_Œ¼`Ÿ´Ó|58é¾xÄPÁÉY’2e|»)zPÀàmt/ÂÃÑõ ùZ~›¨ÛM£j3»©,ø‰â¯ÙÞ®–ÑZ@–î— r²ìà…X.$’k Ñou¥øŸ§Í§L± &æêà:3o¸.#ŽÌ¡v@ä–<)EÇ·‚xWã/ˆt xM®<3ö3áx[ìˆ ×2œË)Rñq~I'×·éþ,³ù×z¶©¤É¯Ã$­¡éÚlÚž¹uNJØÚE-Ê  ]BÆÅb;›n3\5j¨«¶T)¹;$u­­îüX¾ ¸Ñ$Oxµ‹Éo}°ýšŒ(08mŠ„‚Ä‚w^q{àoz¬VÚX‹Qû^¯"éÐGk%ºF»FÅù‹˜A ~QÖ»¿é>?¼×-nµ‡_5¿ÈâÂòâ_ê×–vwŸ$²Z6£¯;Ó’ HP O¿³ìA¢\èßg-|O©xRW‚=f{É.hÌr4æV*Ó(%I ”aØŒWËfÜUGÕž–-œÞ«CñGà—ükãÄß>‡©øRóá–‘lŸhºñ¯n7jPXÞQ°’FùË:¨Ùó.zÖ¿„ðN?ÙKá‰õ‘ªx×W±äµ›Ä5žö ‰œ`BÞV6¤“ % Ã9ÉÏÝ ©FòÏ>¡-Ô³œán¹UõÁ`žÿ­V‚Aad/µa\ …ôçœæ¿1ÌøË‰mAÙþ,…5æiÜjºò‚³‹ƒ „©ìü©ÂñÄlá‹¡p0{Tër.îÄ0 Q÷“¿åRK¬ÜG‘áëC{(fFU`£wnOëÞ¾:¥Z•%Í&zŠ +cŸÕ4û-"85Mn6Ha(7”ÇhÜ^µÇjž#Ô|YªÂ5ðÒDµŽÑ÷Ëxñ€í8(¼‚ÙÏwö®ÃTð߆|7¦ÜüNø©C¥ZZÆÏ}yr!ÓጠòÒ /µ~Yütÿ‚¼ü?ð5µ×„¿d Â]so’jCì$S$)þ¾ì‚r«ˆ’B?ÖA¯k-áü^2ÎÐä«§OÍŸ©šöƒáŸèW^:ø›¯+F±ˆ›Ë»”´¶Xד¾FÆ&¿-~6Á[R!D7+¦Ôn4Ë›Dðˆõ'–ÇWÓ£›!I ʈÏ@È0G½z^â =|9ma ÚnõIåB hîb(@+Ó²;Š¡ðóÂzfesuâ?ì­GMŽ »!ˆÏqö(°…ÕO"@£SÛšûšXjtâ£dy´¤õ<^×῎ŸLò¾*xªMY|/{çÛj"Ýc{»WLF&HÇŸ›€W¶O­®iºÄÚi¶Z“[kK=álI¦´E„é» §ÝÈí]/Ãê°ë~.³¼Ô¢Õì`³±™/dI@¦E̪zÈ#*ÍŽŠì~xÀ:DÚ/ŽõC¨ÝëV’iú>¹"ªÝÛÛÅ À·ôó2#*ß{ŽÕ²CNçÊÚ/…ŸÃ—†‘áýU5›ŒZYÜÉÅ 0ÜØ eˆ9]Ç?ojwÿ5ž¿ðÃO ¦Ëví'‘®DŠ,b–BH‚6玕õNµð{Ãá†.Ÿ{±Á’½‘`ÀÒ2ûx*GAØŽµó¿Ã½CÃ^ñ‹øGQ²MKKÐÓ¤°œ‰÷mØV>…dg¯çT<¬x^ã⿉<]àYK§èš”–²ZMq†?kµbêQÈó#À,8 kÑ5_Ç®èéá[­:]Zê;µ·xîÆQ£…OÏ‘Œ²‚ öÍ?XÕôý+Äú5«×ûdO=äCäã;þyI´†ÀÆ?»âÍKÕõûMCKAe}<‘+C#å†Ü›±Pä÷Íx€çø{â?kºwÄ;-VU×Ú¦?”ždоQìðI`85ê~Óÿ²<›àè`Ä‘} KqªMÌ“,¹b¤6Üz Õ=;ã7„´Í#Ä—k Ìšw“-«Z£fÞ'p@pCùçúT~/ž/jÖ~9Òm´Ëå°ÔdÜH8Y_n9ù›9ÁéÔqA t˜¼I`ÚkW:ŒEïµ!³“ kqpƒË‘‘òvŒ9ã¡¥×t xçLÖ쎈×>+…Dqy±¬¾lÖdnPã¤néœV—„¼)ªè -õÍròvÕìuyÔÙDÖÖÑ]>QãÚrh?)㨯¡|kÔ`ð$Ö/ˆô—U[8\9˜Ç"ËQ.Úñ·SÜWxÑ¥šÖÛÃ^ ×·q}¡¯ó%D'†‹r•,@8==+ ñÿÁ Xkúׄõ¨Íæ©â´‚ê×T´ÝÛ˜”´{T’¼À3€G&ºï…‘øÆÏYѵÝZÞêkͬg³1V‚d-<Ôð(LÏ±ð„º ‚ÿT×µ?èWvÆÖ ¯8¤ŽkCŸ"q(Ü3ò¸·|œWa¨ø/Õ֋ãSöûg¼]QÛdåeM’,d‘œe»ô85ÛxxôÏŠrM®¥2…X!½fŠØK)d’Yà'Xêú°m>k[û2ÞTrYÉ‘‘¿nŒ9ÕWø—¦ß&w¦O¨ZOlÝK ¹·å—OEbÒØö  ÑÇ(|9âk(¬_í&ÞYSò ±»‡÷‡#òÜøŸÅZ‚¾xoHŠ Ãú+6ÝBc,q^Ü´m!c¹Š²¾9þ,8®ëNd•Ï…5]µÍòxd¹¾‰Ñ>É3Ú]™ùSi] óï^-ã/Þñ/Ľn?%Õ׆m$Šqotåo œà)u 3ÆÐ¹ëoñgÆbøõâÿh7¶šŒ0YéÒÚâöY &ÐFÈÁ9ë\±§x÷@ñ7†®¾Ç$7—sLu HÕÛk·ùÕYÆ7F eÚAÉàW/¯ø[¿…øs¢Hð†×¾Ìú•ñF:dI<)‘€%ÇSÔwÉ«þ ñÏŽo'ÒtÝAwyàw´¾Ó·¢ùsM…JzþòrÝ0sÚ€=·ÃZç…íôÙ>ø‚ýaÒ­ ¹‹Q¼•?ÒCÏ3»ã [O®im§µÅÄöº¬Óãi¥ŽKɾÓ÷>ùØFÔ +Žøg"ê>ð§ˆotû»v+ÝB) 9eú£/¨^FEtšÜ~'ÓîL¶W>±¸ÓRf…–D²»mѤmÿ-6¿¦H+ À¾Õ4o…–¶š¥ÖŸÿ9X\%¦ÖœMps:“ЕUlô',ð·‡|-áý ñc±wÕn‘eÛ=Õ³‘ü¾HòœðAÁÏJñB/M«îô8ìµ¹áû,ßhSÎó†çý\‰€Àã¥}§|@ñ—ÃÿÜZêž9KHÛE¼ùϼ´8pÃ#ªž ÷¬½*]P×î4/Änlï.ãƒ"SJÒ7úÆ`xmßÅ‘Šò˜ü7âý›Eñ¡=ܰ[«Ã4ÑsÈs´‘€ÁN0y$õÏè¾ðå—€|OãIÖ8¼]ks.²,ÐÊöU1ÄÅÎw©ÊñÜŽ”àÞ(øAà¡ãƽ𮚼óÊmÝZ6HË´[.d'÷Êó©äó_QøCÅó|ðŽ­u¬xjK(£‚[!kpËt¸¼ ¡s•Ù¼r1òóÐ× ãoƒM§x­ô-k\ ¡Ï´ÚT÷RpÞhu‰”0ù¨ã¹çŠô;ÆïtËxëÄ0^ ËM M··iÔ›|£¼Äð¿¼?(Ê« òh7Æ-ñ7án€4]-ž]{»MM!‘ƒGu3ÄQœ€ %ÛÆGË‘Î3[QkV¼1áíGÃÑGqkcö‹{ØÕn×p2 Æò,“ŽAïY¾1ñO…4MCOÖ­’îæm%6w‘ŸÝÞ òðêzqÓí.îL¤[C€Ñ¿¦ êË@ß <;àÙáñ.‘ã‹(¯íµë'Ògžq²d•Ïš’ÆA™ISŒßJ«©øNëLð$~ø™hWZ†Ÿrú S>~ÒÛУ°f»Ÿø+â +¬ø’ÛH‡ìw÷²jhVO4@-—($n7‡`#kÍ´m\ø­«Cñ;ÇU¶ŽÖÏk$_4“Æf`ÂÛ·<“ô$Puâ‹D¾ÁºuÎ’4ëè4fyÌsGyæF¬%I,I §ÀÅz…ôÍ7NÔ´ûmwÄð$·k6ŸÄŠ<ù!•Kº<#&)9Î0y®[þ° _X¤­s7ÛZW0ÎH~Ldà)Ù²sƒÒ½CSøÍðóI’ÛâW‚<©üC¤ÙiWmdÛÄÑióŠd •qN÷C—S“ÛMk¨xVÀÁkár;{Iµi4ÝF.EÁµc² ×T僒1È­=ƾ:ñ‡î丂;ëûËK«Õ˜ìšÜ¹ùH)† <ЇÇ~ø7¥YøÃÇ_54MϵŸGÐǘï4n¡1!é€Iû‚3ŠÊµÔµ¿xzçÂÚOƒ§Ó5X£¬Ëˆo%•H”³(ùZ,dã ­t:ñ'…ü/ám?ÂÖ?nñ{¼w÷7vËlöZ„D2+0!B¹ÎF9­^Ϥ®•"x_S³ÒõHžÊIUGîÐIÛyá³ßïb¼Ÿö“³¸Õ>"ê¾%ñV«(¾‹ÃºM²Å¥c¼[èûІp[ PÌ6çëZ½§ÃÏOaà½S[¾¶ÓãÑ ¶°ºyŸdr "&2)8 ü¬Ç®hÏím¼6¾)·ðï…oìÛKñų_ßÝ a `@8)ãÜõSÄ~´Ö,íôíz$²Õ-®d[d²„\ØfÖBžoœ9ElŽô®ß^TƒQÀþ­çƒlmôÏxrñ¼ŠDwöwQ’“ Ø.¬¨å~lä`×ÓüCàïü2“Vðlº…­ K½>u Ê#¿Ž/8ÄÒr²’Bç8ކ€<·âo‚µxN]kÅ){¼V;«Û"|¶‘Ø;NÒ¤`c§=éš¾‡µi§k>Õ‹Ïqwö}M®}ÌSí …‹HŠËðÞ¡¥§…ty>!.¥‡uíM4Jæ2^ßM_/̆âás»Ë'åfLíÜð¬G£xVðW‹õM[á®cçÛ5½…‹z¶ŽJH¬Rƒ*Ãï´8ßø3Å>ŽtÖf€!&cœE.ä§’oJ±áVûáˆ.¼-ñFY±Öí&šÎê9ж…ظù ž +s’+²€O©ø“P_–Ö—@‘…Øà¶ÕÁB  S‘ê3X8Ô|aâ; {XÔôIµ‡Ñ¦ŠêÞ 2ÐÈÉc•Е'æaÔuéAç5Ÿ|$ðgŒìî5¨5Ø-§Ùí§²*Cw2å1åä*î9?^k»øecâ_ørûÀ>9€\Ü¥¡šø&Ýæy#æ$äíÝǯpr+˜øoka¤Ú_j,úî‹~¶—QÉ3/ú<…I)Žpp@#§Ýü;Ó|;ÄËýNÕ¥³Ñt*åY ² }·rLœP]‘¡à=F½ðt¿5¨’ÇR’ÉçZ¾<™p’uÆÿ¼0N3ŽÕæžøKâ‹ø’ñƒÛhodÓ¯£ %ÚFêX)ä²€ß69Û^ƒðêçÃ^‡€¼qy7†µø.ïnD2¨‘eµZ #qË$X䜫Û³£kZa†[[,:Íq$giŽFè„n„×ÔZ>££êÚ…íî¯á—Ô/l®„Ò^)Ûz³2hYXm’"õ#sŒæ­ø¼-ðóáÚß\Måý·•ö„ì`Ø}ƒ;£eHÅdx/_¹³ñœW^(ÓÚ+4Q#A.w¨GØq½pzúSoBR/ÿÂ5ð÷XÖ´»Ý6;›+™u5·[™”ˆ¾u%£d8*G§®+ÓµÄox–ãÓé[m56‘-ˆc$ÆÃoÍžO!”ãqœÂñMï|u¯Ùi ¬m¯5Ëùo"´xÉ’æyŒUK‘’2q‘ŒŠÇð”zæ™$‘ëé·7Ìm]Ø$W(ŤYóWŒàŠ‚Ìˇ^¹·¹Óôssá«Ú'6¶ïæC$w°H€±?#|Ù8ëTü1â]" .âÑ­ncºkˆ^u“å>þðå$Œ÷àÖÖ¥â¿]ø†ÏÄšŽMBÚk5¿%^'!œqÏ8â·<ÿ †ãâg†#×¢ƒTÑŒš Š‘ÝÊ’{y€ç £%Iè~¹­Oñ?YÒt{߃wº]ÿ‡ä»Cl·nÆH²yMØÎÖåq”õª?|;¡ÿÂymñH¶{8ä\[ÙB3ð¡Ë¡'Àx5·aãû(-õÀˆúf©l[PŽÁáóÅrØhò£‚•aÀèhŸºYöÒÓß®ÇO/¦ø{K×%‰Vh>T`|Û†%ÁîÄséšO øOÁ÷Þ!‹\@ÿX Ç~Ͳõ;•GPsÏ\Uk/†—¢óÃ:f¿;Ç ì DñÑ«G¾ìßPºÿÂ3¯Üx¦-#Ã>$+iªÚÁ ©8ž0[zr­ÃTÚU•¿…®5øþâ-J) SÙ-Ë+¨ ‘ŸâÇòÖ­»©ëVžøÜÁ£Ì°¾¯ô’[u$ràWç;³ÔWðûÃ[Ô‘,µæ·û$ûn¤Q#Âês°=AÆ;óA™»âo„^²Ki|-âÝKÃÏnždD\­Ýƒ;Ä"¶æŒg +‰{ÏxWÅñx?ÅëÔ­ J/´÷C{ƒ  1sóBª2xZ—Åv±èvæóKº‘mµç!V2@t\`árHÏoZè_Â:ˆµyü¬^OqsgxÎ“à†–*ï=0 sÛŠ ‰.¥â~ÚÖÛÃ÷>e–k9’uÇÌ­÷ïMØÈÈÎ+£°Ó<)âJñíì1¶Ébº9Ø%BC#ž€?I<;×OgáÛÍÁ’x[N‘o´ëä’ÒW¹mÏ„mëƒ÷‰êr9®~/ xÆb;o"ÛÇu`ØYæV…¥‚A”#œïî1øŠršTš¼~ ñ®§§?Ú”I=´±L«uncbìÎÿÕœŠæ,>.E¦xWL:V ë„h.™ [“$ñÊ\]o|Ô>ðý§ˆüI.…¨³iºÞ=ÄW6ñ¹uMÌ01dŒñƒÔt®Ã^ñ/‚|Xöê ··•’/! HH*Çž§¦hØ4›+]rÏR¹‚tÒu5×Q\J‹åÀ³†*<åHëŒûVeÔZö»á[Ÿè^N®k{›h$ÛÆ† §¡ –ÀûÙë[.¤š7Ú®­?´¾Û!h|²<©Pà¢8ÜPð8ãõªšõ¥õ†¼óø“O4–Ì—¡.‰ŒWR+ƒŒ{žkmþßê#¾ûBÚGͳ4fHáA…‘Oü´ Æ5éúÄi¾%°Õ4ùØê;ŠÛEó«¥Â)%¹I`N3Çz䯓¬ëw7(]wMÖeŽòÍÚ#rXó‘呞ƴ´Ÿ„ö~*½ÃþÕ“B¿ûG˜–Ä̾H%•XŸ¼ G 5ÿêþ ‹\Ö®nºfa)£'%Y Á_ ;q^Ëðÿļ{-÷‹k"Ú2®.Cy’Ç6xp½‰ê}+Ìe½Ö¼sk4—@ê³\ YŒDÐI¸ã§9ë½¹ñŸöÇ„´™ô½q¤ÕnWʸgUÄ mÚÄp 7àðëá×Äk_ê‚Ó®'–Iü?wK eH ™'jœ0îzó=7Oð®­jº§Œìä¸7é§•l”Frß{¯ïé^Á¡hÖ—¿ÒÞíѺ—æÝ˜ÃcÌV?0Éá‡AJþ¹ºñ ¯‰Ì1GgtªÓA ^$-ÏÊÌ3œ áï¢Ó®¼Kc`úPÔmmÀšx %ÁèÁ—’:=û×OâWMÙ;øSÃ2E=ºùígy.Ù¡ 1•u9uûTÿðëÚ.›áý?Å{wöÍ/YÒ&ò‰øí®âb sFÇ8ã·JI$ñw„µk½U­m§‚ÿO2Ýý¡Žé9#j±ãq{'Âj÷ö^‚ÆÓÅmשö–Žæv‘yã1mlz‚yÍ[mW]´y¯uK«Áᛨ¶Ù¤ñä[°#œŸ“ 9äÜðÕÄú¿ÃññoGhõÛo ]ÅÆ…qy ”ÀæE';¢±ü'µ­>¡áÛÍWQñV¡ëY^ÀŸÙ¢;…Ú?ºå\î`ñÍynƒámâU×t{HåŽöf³;³iäôñÎzשè–zÕ¼w ®!´·ŽÆW¸º‚X²FÞw‘ÀúÕÄÞ m4ê^*Ò¯t¨o­ž £tº@–4#ÜtÇåÞñ=¦¯­_êún¢º£Ú'Ù¦–(›{(鏯Gr(È|16©fSN‚)L¨ð»£œíÛÈÇcÛ5ôÇÄqñÄÖ–”±O§ê±¢f300¦×ŒÌd7§•ç:OÂh®í¯¼]¡Ka=­¼ -ÍÄÅgòzɱHÃýÜjÓðç†tO‡š5Ö£¢é‘Ϧ^Ÿ·Z½†çVCׯ+·<äñEÀOˆ)¤]Z‹-Jö+:9,,@‘yìàuÆîý+™&³£iyö„º²V²9? #G¦G@s^©à}kì^ÁâxL:f³F©r7Ʋ*ü¡‡'¦F+ɬ§Òô­z)4H¤’¹W³…¡ƒ‘Œ{šö}*o YȺ¥ï‡t×¼† W}ªif‰á{{›íJÚâyaI£‰¶¼°Ê V·]¹õçšòí_\û ­¾m •Á”¼,ø\ÃО‚­ü:ðg„éÖº£¬©mlZ&!ÒFdc‘ûkoOµ{‹&kK[˜qw–£\{סhÞðý©‡L³>~0ò±?{©$qì+óiLúøCS±Ò®ô·&qÔD–Ž0LA°}HÇóâ³ź®”òÝø¾};MO”\ž0{Ÿò+©Õµ?iòÂo,ój¬UîLLcLÿ´ß.¥nh?õ ^ ¿ ˜ìoA%Ò¬d*63…•YqïŽ=k HÝFÅO ø—ÁO²æH5†R ¨±eÝÇœr9íš‚æÞóQ¸“PÓä½`IJBWw`Ú®»þ?ø–y/›Uºšwùr‹ô@çé\Ω¡ø°¬GU™möK½|§]ÍŸ¼ vöíš–Ê*hÞÖâŠKM-¯fµ½,·>L¬ÌRÙþU¥kkâ? Å.‡£éÒéÖÓ ó’|‚œäW©ný뢷M5µ¦Ÿ_ÔoíôøTˆíì&X^i8ÀfÆQ[¹#µnj>8Ðot™4ûÈ$‚[p#K—f™Tg;^FË£©9Ï­HšœÓ…°ÒdK'WÉ 70àúg×Ö¢Ôî®-HÓ|I~oz­¼qª×–÷бs¤|9×.šx5Y®.X&цR¿Ýkª_ ®ÛBó¾™Ð àQ,©êÁ9ù½r(Í?³oôù,ô_ ؘ®/Ý„×|¤"òÀöQïø étéü3=ì>ŽI"&GŽuÝýÇjådw„(Î2Ç€éÞ­Ùø[7·ÖRÒ/”‡föñߦôç5£¬øÎ+ #NƒV·³–A,ÆâM„÷ x'¯Jãî5­NO.ØjwV±ÊÒ˾,FÌw`3“À95kas…ï†[Fµû]§Ù¯-¡cÇ”0胓ƒÔ÷¯KøQã_ x_ÅW^%ñ„ä×µXP›X¤¿C Íÿ-¤;ü£…US^s§jÞ]Ô÷šª“¨ZhÏ-Hè~•¹¼»¶šîÓåyËrꪯv"˜s=¾µ£ø×Ä÷:µóý–éÄ—R\${¢$çÇlv¬)õ(¼û{ ¤y$Ã<¬|¥ÚOryÏ^õÌhž¸Óõ ‹-CV³ÊŠò«. ž0yǾuZ¯cÓ|Þ!il­"y1mjìM̫а#Ï Š˜â5?hné«xªëûuĦ(ôÿ» ç —9ää×N×Þhd}œpÜ«X£T Ë08àg¥s>)ÐN‘ Pè:lcîbÌÊr÷cÐåõ®«Áz—†4Û—]VžòUdKm>7’BqÔ7¼H‚Šú¶°þdÒg)#.ÈÔ®ÇòûÝ~•ÙN4? [Úèö>"mvh£U1 1FǪÀ³úÕWÐn¥±–ööÈÚù ¬F[²Ì09à`>œÔ7§¸ŠåÑš&Õ–õ¶6ÜrWçè*âGe¡xÎóJ“\Ó4õ¶°L‰nä‘ÏSœû”ÿxv{.ô½ÜZÁnCœ#ÜNìyc¹É¤žªöZ@’Úyµ[Ü|œº£g©Ÿâ#®+»¸¼ð}û.‘á;;«iov£ElòH×ÁP|ÿt)µ©Að÷HðKkzÝÅÞ©â+…o²ÛC$09á LyÊñ˜ä‘Ž•¢ëÖúEüZ6ƒd·nÒÝKÏŸ˜‘Ó`±üëgÆ>×<9 N½ap"+"ê b‘yÝ÷î‘ÉÁ©-|9cuáë-D^››†}×2A .T`•ˆ`¤p[½tøöÃU‘4»ôÿ2<I<¯,óµxÑ@¯BÐto ]éSë¶W–æÑJ.Ü<Þcàç-ÔÐW dÚ Î«k3E5¬0’|°¤Ž½Hɼä×h×À¬–šdòC¤I3ÈÆ6È<( qëÍ2܊ѼAey=íÅœ–6öáZ4wfë•É<ýk«Õ:±Æ=i¶INiÓÄww:‚ôÓug’>TIÓ~O5¿âØ<=£ÙÄôROiò·”áb@G;@êÇ‘ÁëYöÚ,º–žÚ'ƒ¡–Ú=ªóÁ3‹çÐÏ}ÀVÇŠ<öZÙ›móÚ2ü¥Pê2AbyÃJ@hèÞ¿Öæ¼ñ ‰Y­cH¾Y¤HÊÜBžYϦԼyy‘iÓiÑù,e–þð´P•þév8㢅Sž´h'‘vyÒ2¶YÝ÷¬A‡Ê p:qøW3áMwQןZ޾à±fû:[ÈÎÃŒýÖO9ïVí|G~úxÖô·Ûj$M#¤f}íé»=}vWÞ'Ö 8ŸZã.ôKOº–xbIŒp‘=̘„¬į݀=F{Vï†í¢{‹Hü/©ùQ\DZHäý쌤pA?wã¥5¸´{M/Yðuî›k ö—óŸ.{™”’¶êzÄ@ÀféíUü‰oµ8[B¶[=" ë#L7Ï+¨À̧æ8Ï­Eyã-FÎÜÛx~ñÍ…’µ¾@ȹšFåFX¨ã•uÙ¾,ÒncûEúÄ$½Š§–’” p?Ú«”š÷_øsnÆ~!¹¼¾¿“|VöJFõ€¹8;Fvû×?á+½v-{TÒìô›‹9f_0Í:ù{<ϺTä‡n0ç]7‰ìîÞâ ¾ÎS¢,¿}#+Ô–è½xÅužñ,Ð\C/ˆcŽ)£‰…3G׎ ùP=§ÃËcT“W×ÒidP ²‚<Ùv¯ÌÁOힸ¯(Ô#šæ95}e‹Î0[2‰Î61¸ÆÑÜ×¦Úø‚7œê·r§Øävýë0t‹8ÜzAíô­íkÃúmÌ#PÓaEt¢X Üñ·ÂÿH$ô4åwÑè°iIs'îfxÔùhìIõ#{f¹#]Õ-µ}zþK”‘(*o( ï'n>¹Î:W¶k~Ñô›Iô‹{vš0ÆP¾d»ŸU'€}{ò‹Ý3PÕ®&¶ñ$÷ZÌÒ[”Y$h‘.w#€>lgðP ùö¬¼Ò´=Gà§Å û¦ŸOÒ¾*x_Í™Siû5ÅìP‡È?u eÏ÷A<â¿sà§ÐKìåýµ…K×ôË…cÊÆ^BÛÛçç±Ïã_‚?ðQ8‡„¿c ¯K ·³ðÖ£i4›b·±Ì›ôR»SØóšþ€¿à¡;¼Yûø¿ÄZNLqhÖšº#{9ÆIï´wêkép+› $y…jÑ?)¾[M¥iv÷ž#>ͬà­ÌHŠÀ`²5ù³îGJ}·ÃŸ\ëZ§ö wVVp=Ä×÷+å¤FwoŽ3’9ÉUMQ×u8¬Ýt•·hc-nÁòã&UùN ã$ãð®ƒÇþ²GºÔ¼{¨¼f†;›ktvU’b@D9ûÁ›ªŽ ×ÎÊ.ìöo±äú®þOZìvøCM×^ÚïJ´¾±Ø Gæ$¸`Fô®3ÅÃÄ1øzâýXbBŒû.Fp¸'øëê*é¼7vÒkW^¶Ž;YµÂGœeZ6R@éŒ kÚ&©st7û>m @…˜aäàr§:ŽkÎ/nÈ¥ÅôPÈ÷1¨)q„g¯Ÿ^q]W‚ô]!‡ö…ÔëíB@ÍËmÆpN'“F‡x–·7ñ*Ù|>±PžRÛ:ãqžïhÈ=7`dð3OÒôk+ï2xÝ…”:sKo%àlh‡ªËÔnùt ‘Zú†¹¬ VòÛOX­§¹›0¬Ê6éßçÁÀÁ5—}¦êº|óZ=â­è·dNû‘÷ÁŽ=éÜžj¯§kö×—·Sß²@аyÈì ?9UR¸Ûô­;‹‹[ ´­:%M>k¥±à)y8ä’H :zõ ¦oøW\°îüG§YC§kV»íá…¦,eŽ`=ºœä‘É=ëÂþø£Æ“Æ:ýÏ•,v>$¹Ñ´äb±(6J"‘‰ç*òdƒÉ=ëÚ~Û}¡Á6ˆQ´×1\›Å8Û*à眥UÑ®þxŽòm/óÛÜj¶7>\¾P+yƒz¹)ònäõ?Z¸;3®|%ÿýµ¯Ú#àï‰|md/µ=µ+—Q¶ßdŸíà©ZþE5kI©_YÅ3&£yl‘–ùùV\oTf3($ ãƒ_߯ˆGöÞ…ÿ þ¦/æH–8Ö(¶Å‹snä“ÔóÇÖ¿•ÿÛÃà<ß ¿kñ|WÛž7µŒé·vì­oYȺˆ/_›ÌÜ;Ó?A‚Ä]ržV.µ>ý«l?eù¾2èžý|=âøoB°×·ZÞ³6¥©j7ß)i‚î1Ú*dƉù²K`…¯Ð¯…ZüÄ¿°w‹ÿfë|4ø€ð.©©O8º¾Ò£ÒÙd™(åQ”n% ڜיþÿ°îµ¯þÓ~=—ÇvQêÿ~®µMÚO±_´¬$Hc* “»FÃäPU\þð€W?½zþ“û-üøEâ[¯…ú6³áÏêºü&>'“M´}"3n&{xâÎä¸uìHÇÞ½8ͶyòÁo‚Ÿþ%é>:²Ôuÿ x—ÆÖ©2é¶w£Gk{«y£{v¹¹–HÄvÑGæ‘ H8ÍÁú¿öm×µ_þ-ÐõïŠ2ZxKÂ÷ÒÞϨêðÊ—šdq«¬Y$XØËæª"´F9*7T¿|k |{ø>º“Ç¡ê0FÒ_j3Òo;z–hØFÓYBѲ3oC&p85Ï~ÑgÂÚGâWÄíCSÖt¯ i^(:cIªÁ¨Ï4º›èQ#ý¶âÉ@‚ ¾PîKmPKd-]È=ÇÃ_|9®èú–‡ãè5 ZÚO²Ù]=™šâÚìHD·®ã UŒ¶FàM|·áÝ[­ÄÞø`·xûUÀš·Šur^îÛ^k¦-lçܾG(ò·©Â’¥lø;ß < á{½[¾Öõñ‚Ò{o]xjî;›Ò–Ús&¢¸;‘6v>KGŒ†¯W¼ý¡á øEmá[½7J:¦‰qa¯é±»ù*×zY òÔ‡š‘€¿1P†3HÃÞ Ñ%VýŒ¾(Euwã?†p^ÛÛx^+¶®u™å2HóJP¤6å-÷Ðîô¯£þ.xÓÇ? < û5|=Ò´ß‹h—מ0»ÐÍ£Ykú=´^SÁo²Ñâ‘àÝp—бSåz”^øÕ?‹ÃÅGÃñêzI‹½ºyÒEîò r™“çmιÇ<NÛÅZ¿Äß üñEÌ–z4êqKvì±]ZiJÆÐy!C¤ò’¿¸È$dx?Qü+ø¥¤Z|!Ö¥ñ¶‘e⟠ø‹JÖõ5Óµ¸ A.£ª\ó:Y…²Dû‚ê„c4 «Ÿ1¶¹ñoÅ ¼cð³N³™/>Ͼ©n¯jÞIÒ¥„¨Ùº4æ–Š:Æã»õøƒo'‡´GÚiÂfSl±¬1ym¸–,prÛy|ö¯dðL~ðoˆ|3 ø›Zñ>¯ã+ ¤Õ.ôè#}h&SȈæEˆF@#!²¹%Žsà¶Þñ>µñƒAÑ|9Ö¯ i¬Ö'Y¦Óõ›+뺅ӹ%a•ËòWh+»‚Fp ”ñ߈?µ‹ñRóÁÞ1ð/ƒuŸ‡ºŒ:ÜšnµãIâXë¶—p¼qÚËi°ÊåHdÛ´Ú9'>‰ãOŒ3èémy§Aö­"Õ,-„èR6¶´W<¦$ìiC®ÄÜWØ~4ðœøCCøswž‡5Y/IJja`$·1ÇP6bÞcãæ9öå¼;á‡퇉|-®øîÅŠ/ìí,g÷RŸ°¡WÝÏÎ’ØNF3ž˜¥t§™ë~Ьt ®‹±'½iá3\[fP–å²ùÎÝ»€+Γƞ)ÒuøH8ñ^§§ü%ðφîu¿ÞyWö6èÆâgáö¨ÄQçï;€ +:˜ªp\ÒvEFŒÛ²G6þ?¿¼‹NÓîl…üq]ØÞ\,¬XBbÚIœnÓœW%§ø÷ÄÚ–§¢øRò{ÍFþÂÚE°ÒôûIµ ˆ-jK{(ä¸1®3…ÚŒFâ2+÷'ÁðE¯x?ñüfýº¾ Ãào/àÑbóµ)¤¼*‚î]`MÄ#yQ—,rz×îÏìÏð/ögýŸþ oØ×DÒ´]'V¶†îRÁ7Ýê;¾Q=ÍÔ¹šyTe‰ã¾K7ã>>æ§«ƒË¥WV´?¿bØÇß´ÿƒ%ñÇŠ5MKÀ»»Œ[Ü\Û4šÅІ<8†ÞívÖÈfi “_ÐßÀŸÙ÷àìãî­ðÛLa®_¢.£¯jÝjýchšîOœª‚vÄ›#@pª{F£ÞˆTê<ÅWp‘‰$»}ïΨjV3ÌË2F›£X+ó׸ÿ8¯ÈóN,ÅâÛ\Í#èè`(ÓZ#vßÄZ•úýªâi|§å|Òp?:ñ¿øUZ\þ$¹ñ.¤YË9ÏüKÛ™= »,Œ=Y:î5<‹¨KæHV…NTƒZ]zûÄÖ®£™WÌä” Ïæ+çZÕW¼îvBšŠ²Ø×Šî+;UXàgˆœ‚Å}sŽGÎ[k¶åÌÚOö=ÎÔ C3Aû¹töÇ¡8ª>?ñÿÃÿ‚þ_|jÕ­|5`Cä¹fqÎÈc<舤ž•ù%ñ¿þ qñŸÄökáŸØû§N”7'ˆµ‚“Ï´ óÍöub–瑱d/'91Œ÷ržÅb¤Ÿ.‡%|m:k»?W|]ã? üðµ÷‰~1x‡Kðn‹ •[ÝJeä=HŒ72žÀ(bÇ_”ßà®~Ð¥»ð?ì“ >½ r™¼Câ(^ß”{f˜oÇÍ)…9à·¿*üá½_öŸñÌ—¼E¯ø‚Þ;yΣqpgšäâ42qaC‰cBs•5Ãx«D_M¨xsC=Œñ«­ê¨.)å[yw õÀà×èùga©>zÚ³ÄÅf•'¢z¼uâOÚ{ÆZí?âëÏÝZH†[µT³‚L1_³Û ±Ç’6²§˜Hù˜Œ†C¸{2;$[¸<¢–s|»Óp08Ïq×Ö¹k VëöÞ!ð}¬76š…­ÌsÎíö‰VKœxUaœW'âïÚ¯ìô_h:UÕÀ7j¶ÐÇoåÞ’9yw€†#‚¡‡¾ç ƒ¥J)B6G“*òz³Üþ0ÞèðŦw \[jå[©_.Ñ-Ç2y¨Ã–x <â½ÞÃC𯊬õ ÁªC éMµ»T¹ýÿü{«ÞFÖUç½|±ªøóÄž<ðÖ£m iµ8ôû‰VKˆ®uŽŸ•T©‘ÏŸiã¥ðÞ•m¯hn57µ·šÑlç‡sÚF¤|‹’éyãšêå9Û¹ôW‡l ŸEØ›_ÿhB÷F;iwæFÜ*Ž •ŽäQâmoC°ð¤·²ÙAgq§]Àeœ'”ÜåD-’I.HëÞ¼ú-~×Âw³x[E-Åš¦—;#‚ÊHÛç.¸„Œ¡HëÉíY~ñ?‰þ"Cb¾"µ²ÕuM5ílîVÞA³P»´l‘£Á(%@7-È£”;]ODø›c¢‹´¸}*Ue†æêÜ«ù ÙÝ ä;1Âäd™ÏoÁº§‰4Á¡xƒQÓž8-‚Jn3˜g¸Ê‡ ‡aŽÜõã¢ñþ§sã‰72xžöÞßDµ×âŽ=%‹ùv3F·¼xo/kòðT×ÞxëÂVϯØh‚K¦ÞëKæmŒì´ÀÉ Èætu$ŽJM¦}£5œïiÿ aµñ—¦Û,;’VÚ@Q‚÷«àá†;Ö?‡µ?Å$vž²ÒáþÕœ\ØÀ>Ѷ÷bT"2:ñŠ¥¨üaÿ„{âO‡ü=»ßëK!Þ–6‹å YíÎÙävùP+yÁëŠÅÑ4_ŠúŸŠ|WâÕÓ¡´»GŽâÆÞ2±Ã+È„0·çNÀä¶2ÄÒâæ£âk-_ñû[Í®jW–ÐK öQlçx‰¥]Ç‹Ÿõyé+XÒ<[&Ÿ{e¡ß[™íˆŸí`4^T íÁ ©,AdkzëÁvZ·ÃÏ‹,o5;?‰šeµ¸Ó­XÛ´:ÊB×0v‚5 §ç‘P\Úkë–7^ ×/¦Òl¯%“UÒ§›ÎŠK=ù„LÍ“°L2pW¡8$P4Î÷OOxwâf—ª]Y?Ã÷ö°i–fUJÌ‹3Cp1ÿ-pqž‡Š_è^д}NïEоÇqâYÄpi±¶¹MËö§Ac½[ñŠ,ÞúëÀ:½¤º•Lj&‹Q··½ØË`–àyM($äŽÄW›øÚ)Gˆ5?Ãiw6«eh—tÑM$²ì‚ÍPX'“Æóþ‹yýâÖÕü-ªGÉY¶šÈZ'tâL¹8ß“•Ͻoxƒ]ñ’‹ ZmíÛ[Is~¶óK˜‚.ÞrÄ3´à3`d ªªÞTz‘Òc²•W1y|È ™ˆØÎr&‚¹ö®â߆:4Ý&F»Â c¥ÿ uºq{䥕Z ‹ËÜ€òvîèk‡ñuµÛ‹íG]x-`·žÛI¼³UŒ²C)|’¿x? ósƒT¼5áÛ]éZ'Æ£[x­¾ÍÉÃu/ú¹Qcít ޏçƒ^A,þð¿ög†!ˆêjæKYcW$-Í‹˜eù%d±€1ë@Ö‡¬|Nøµ¡ø¯Æšo‹¼En·V>·±[7ö¸P7ÛÜË&ð¼‚ȧµmë ô¡¨Y]xïS[]&ÚæÛE–T@÷3ZND‘ÈçæW1Àܾ2Àä×Ïúö¥/…´«;O Zë}¥æ½±µb2„Ë´ëžAPAäã5×ø WNµµÕ2_‹o¬ !’H$²m¦ËiÎC•Ú@Æ añÏ„ôÛ/ èúu‡ŒÒy]ŽNb$nÚ¸¯>ñ8ñ¶²Æ~ ñ.¤h:e§öMÞ‡©Bw]­É*òC.á‚Ä«ÇTŒ Šö¯ˆwš4Éogâ.mBÆ@׿ÖÞØµšH†Þ¦Ý˜a‚+ļ{}ðsFÓ.µ+(µ›ý+I¶(aʤöÖÅ”¡oùkûÄŽ@«ðÿÁ×Ð/~þÑ:d”© ¾¤’;*ÜÂUã!s»x"E`Ø#¦+Î|C ø‹ÀÑŸj–7úˆ¯Rê„îhØ|Š ?lîÈà‘^©áé:.ƒü «\j’øƒÃ–íxÚ”«s­ú8ŸÊ۸˾-ÝT:W‡|hÑt½?ÅX|$eΩ§A}wüA~Éu168 PGp½Ít´³ÒôGBÝkm¤I6¥6›~že³A*‚Ï~|ñÇ=GZ¯àÿø/çÆ^ŠÞßÄ"‚[&ÎÖC‚W%vÜ«smOʽy[Ãñ2{x¼Oâ8ìmYÌÚL· æx# (‘x9hØ•Sƒõ¯¡íáñÝ­¶›ñ’6ŸN’ÒXg–ßýâÙß:É•lnRyÜ u~&ø§x£SÐ<=ðÇú~‹­é+Üb_2iAˆ±+.7â3·fs¹s\Αâ?ÝÙüOñÀ½Ò./ïZ+[¨£ß ¥Ò—’9#^0è„£Œg­eiw^&´ð¯†,ßK»ðæŒ÷¶“®ë†Š2Ñm%·gËëÔðÙǯàßxÇÄ~±ñ—‰D÷—P\G% º•Ò?6í¶F¥Îà ®HïÏ9 ÒËâŒß¾(ÚxjßSû6¯¨´ïê°Fa·x~lD\ü¤2’»[øÀÇ8­aâïi÷¾ ›Ç‚ÏY]¡¶Õ<ÖÜ$QÌc ÚwAR¤·©®câï}‹_Ä÷Öúm—‡„qËk˱'ˆy‘p|år¬»H`pp Rð'‰þjwºç…þ)Ák.›â 7Ì–íä"å56Ë ç;‹äó’ ï@Ûi“¬E⊷jðø`*èúTûü¨æ¿( ºôÛCŒôÈ ×‡¿Ãü,°½°û\Z¶¡(³»›S•šAÐÈ]Õco”¤Êz{žy¯Kм1¨ÚxAøwâku¤ÿdkhØÍ™b;Ø!ÎVR3Ž£¥w¶Ú•Ç?Iᢺ·»ðÖŒ³¬Ÿ)[˜ífù£QÃndÈSÈzP—[Ûxkâæ¿­|AÔWLñ~–lÅæ2‰`º²¨†òÞA†5<ŒnR¬5›â½ösñîâ/ Ü›KÛ=&X ’Îb<ÍBÒèDòsýõ`cÃ+Ž+²¾ðô>3ñý¯‰txÞkËmâf;eÝnXd#•VÚàœW›xÄ ¼ªh^?²4ËŸþ÷VŠd_°¼¸WDÈËwèGMÀÉбnº¤‘C½¬€5ÃD·DÎ…ùàýhÅüYmã»±§~в´šò4è·¢·Ø.%Ü€´$‡Aä•ïÖ¤ð¤z¿ˆü-m¬èZ„Kébâ;Њø1—ktB¯ºí/Å^8ñ½¢üF»¹Ñ®¡ÓDÂ÷K¹|ÐSæ ÑÇ;S®~ü>À²Ùhš%Ä–:ž¤Ó½Äs¹¶{‚wlFèƒÐáhÑ>"izO…ü ¢kR[É%–•œmåF©tïtrÈÒÜC =«iâÍoÂúŸ†#¶¸‘´ôhçû<ˆciÐîÇ ÚyÇûWÚü?i×6¾º»E°`š„Lí/”„4P²¶àë}×ê£Úº-ynÿRøqãëkKízØhóéÍww‘‰"+¼nŒ7G.£€sÓ¥x—Šü i¿cñG‚ÍïÚVïʵ¸RŸfHå$²KÃ(ÊžÍŠš ¿ ¯…¬¼!㋉`µžXåioŠ3øìO!«£ð†±ðíômJk§»:u½Äs[D²Š¯´g–ì=84Æh¾5ŸáêXüPð–%ݾ ±Ûêq¥Á–lF¼nCóay¨SÚ½ßÂÚÀûÿ‡?¤ÑîŸAð·‰-áÕJ…EÒehîv`´%•‚9æ¼çÇž„kÚž‚¨Sì^\]Bv«ù܀ιù— úÔMà'L³Õ|-¨ÁÌ?fŽé üÛ¸k[ŽÁ‡%Œ61@›±ÇøÏáWÄŸéš\ÚŽö­úÝ$²½ÒçYmåULäÇCpPƒÀàלhú6›áëë9üM¡Ã¢Ã<BP#nY¿„眜ʺ M@Ó¯%ðßöÛ\ÙZ<¨‘Aq h.edd<yŒ€3ÍoX|9ñŠÒëAñ¾¸šO†ÚçÏŽþïË’XÁUÙŒ“íÍæ8?øëÄ~×lü­Û¥¾»%Ó†¯nÆ7¸¶EùHñµœgh˜þº+Nñ‡„µèüqà JëA[ˆ¡ŠöÍåóÛÇþ¼«r6°çoRs[Ÿ,ψõ&·{ˆ¥ðÓYÚ Kæe’ÿN¹ °6ùáRï',­ÈÇ57ü]v¾»øg®ÛÙÚ®½k2Þ>H´¼ØK•v¶§j&‚¼1#ÄÚm¸Æª Þ|nR ­·§gMëüC‚:sIãøö=>_j—>"Ôfin5¨t™BB.ß.a‘†7æ¶ü[¬KðçK±ø5áÝYu*ÚÃûUæ·ùKö,ÉžJ¢’wþÂü>Óîô OÁÚ²éÇà §ÞjRÝÝÈCZêv·ê&9b ‚¦h(å5_Ûjþ ŽÃ[°†Ö{{ÅûiäÙæ¤a£L?Êž™ÚHÍ0xŸÆ¾ýž¼]gàå½ñ­…¥”¦Ú3°Å’¤®ç9k>µf Ù_Þ_¾’±Ïi¦Gs6µ¦ßsð¸—d&0z0œãŽ3ŠÖð„¬dð'‰üðòÛþÏíU¶‚&ÞCåÎrQÉÇï†ÁàóA<Å> ðÿÁË›Í"Y4ÝwHÔšÜ}–܈fx¦‚6u p®’ga=Åyþý£á;…ÔŒPø¯AŒ 4}F{³Þ[»&æ‚Téº/˜»£âlwÖž"Ѿ|XðÔºi¸žö½:C?ÛV4 º~q‚r{õåº×ƒ5»¿jW÷)¦É3Ïe'ï!•É#•{á9Nø8ëÔcïÿx"-KðïÄ_‰Ö‘[ÇâÞØ_X8”CpSî¸A‘æ…Á$u¯¸Ñþëéiá?ÝË`—Ï*ÇnŠ7ŒC(c‘ÔqÎ=+CÀº®¥¤x¥.tõó®íg îVßÊŽ=‘–ås€sÖ³|Wû?è~"ð¶°o¼AÖ¯¤]‹ßZnP¶YÉ0´xËf\3Æñ.Ã´Ž¢×ì©ûE~̳õOêžÖ|Å„vÞ·Ho³ê°Êï(¸–FÂ$ˆ!G”m#«~˜ñ·í½¨þÖö ¦ü+Ó<¡†žKÍ?Qò~Ù$–䤋+Û–•ÁÊmÏË×ò¦¡yâÿŒ:ž…¤tXü#§¤NÒÚ‘7•oÈ%Î2ìà“Óšñÿ øsâ¶—}w¯-Ô^,ð^©ÝÚËkk»kĈÀrWÀ\œÔ8]ÜêöÞï)ÕÙü6·‚mk[ð´¿ÙÞf•nì<çKrFᓸc°9ãÞµ¼Qoi¨k¶š—‚R)¬Î; ì‹ìÿwæê§sGƒþ(þÏÞ©è7%– ZKy-¿³âFŽtºˆîdmÀ‘ÊîÀ=ý+Õþ1þÐ6ý§/¼7§êvÚ‹q¤±ûµ¿özÏò‰pCºn`¼0 2Of•&‡ñ7ÃjñEÅëXÞDR{#š/)· …ù˜àð2CÖ®húŽƒâ#¯^]jϬy=Ÿœ¤«‰[t»xËr¤þä¶š{èþ%·´ø£§\Ás5óÝX ¸Ý –Ç»rxÇæ½’ßÄVºÄåðÆœ—6†&·˜¼BP’9(ùàAc­Ag¥-Í–­ãOé‰dM©°…Ôˆ|µË`7!z‘úW)ãÍC_ðÇŠE—ŽžVÚE}üéÈóÆÑóŒ Ö–hÞø¡ ͼ‚æ])¢YÈ$yȤ¡mÇ‚@nr9®òïþ ø‰k«OàÄ´¾¿³…f¹‚âaóB$!– ð^5ä) v ,Ñt›3¥¶£cª&¯ekÏ11´AóËdœ1#û×uðzÊËL‡‰tM1lu[ÌÛÜ>íÎð¾®XàmeÔf¸ÏxgOMv ÃV÷vöš‚ p¿hx°Y¶õIÁF×µvzÇ‹5È´‹Ï ùog¨$™™<–X`^©ÁVÝБҀ$þÝñÚjÖ6f¶2Ë$×­e7hb8WP9^úWw¦ÝøcǶ:¡Òm¥°Ô­¦â¸„² ¬Ðíó•N *HÜ£úבÛxóÚYÒõ_ LæöG}¥\íPÃ÷»”ã$ð? ö=gƱñœsg©ÍlÐZJдjÇpÀ=qœa³í@øÇ~$Ñ-u€ö¡mÒÖÚÒöè”h¦òZP]sÁe^™ë^=ø§AY,4›/\[”Ø&ýÜm)uu ŒGJúoVø¹á»½FÓWÒ¯Ã[Ó£t‰ïÔI ÁŽÙc~>PHàñ\7í âO Íâ½ N·ž£å›Ã¸–/ÆØCc/æF9 ÌðÝIþ!kÞÓu›{Ë ¦ãºX<0M ü¯êŒƒ]ìQé·mÿ ^›æßjßjÿHˆÌC"(Ìl:(aÓ¦áU­¢MÊ[Q³š[3;}£Ë$¦ÈÏÊopsœò©u=oÃÚŒõrK†Ÿr»®<Ý´¥hê½\´¡ðo‹¾ 6·§ßÇk ¾›fünS!À#==OÆ^ÒtÍM|Vé%Þ½©±ÓÌd‹Ž$ˆäa•°ûÜzW?¡Ùøo@¸Ki/î#¸-Í y‘Å&Ü\¯RÙ$f¸ŸizOŽl¥Ð¼JÓÅ.š|ëYÖB9 ù$‰û!êzdWQ£üGÕþ#øUþÄ"—X½D’k©XÆ^k\p¬8e`>¹ žc ñÅÖ‘$«¥x[}1ÐG=¼¶6ò40îSe#“ŽG¨ª÷þ ñφln(ñ7‰µ«­Xòç²6Ë˃¶6°mÈWî±<ñøÖ®ƒ—­ü7ŽãRómÍäOiNŸ:H¤íÊthÛ§\ŽÕÃé«{ðšûRø{¯YtÝvä³ó"­ÂŸ•ãqžPðËЃŠG£h¾ðw>!>¹á¡s§A¡#’O29„¸ß©Îጠ$ãŠé4¿|,ÕcW±Ôe†K‡’Ü_”«g*#lÛ¦¯:EΙá7XÕâ_µ‹°·ª3‰CüœxÇ{VÍçŒ%›VƒLžOµéŒéĸ`„t*Tdœã9ëHGeªjZG…ä·¸{‹ÛÛË'’( •BÆ€ïóŒ×fÃHñvâ›U1‹¿2ÚX­Q·–a÷Ûhè' ㊻'…íþÀúv§poä×çóòF(ð¤@0 yã?xqÏ៊Ëá6½¹×n¤žôhD{¢#8fÜLàÑ`5ÞÓV°¿—Gñ¬؇YîW÷™Ž\°QÀ#÷iú¯ˆ|Yyáý'Qð¦¥:½‹´bÙ"ÊMmÁ”m^3íéÒ½#ÀÞ/ðõíÉñ°LÜ W…p«2”Ї½fxKÅ6žð寛a*Z5Œ’Íp%½x÷í ‹ôûÞ€qBˆâ oÄŠ5_éo X6Ù¬ï ƒ »Â2’J“Œò8éZúpñ‘cjt+ȯìbýåÐ1”'¤OŒ’:ûÔ>mO_½½Ð&ºûe¤‘·’Ê „NX Û““Ò¹-?Ãÿ<<6:L^Ðo Igsâ©ýå¼€ž »‘ÏcUÊCñ Høkgã uVÔ´}BÑ!¸ÝÉ1?8Ï¡ƒŒWîaâ->ÝQ\)¹w B»ót9ïê }áÝsš–™ kQx¢ÎÂüÛ-¡!“i Û@W…;¸÷¯ øÕâ]OðÏàÿ[Ç© I$[Ý6#¶ä1Ü„${޵k`8OƒÚö©¦y‹©²[o1RY2×”ó€F= o[|Rðοy÷éæê1+Z…RJ™X+×Þ»ß ÚxÄÿtèŒ.´û†K+Ý.õÎ ìR=®wáHàƒ×¼?âÿ‚þþØx{Ã÷º~«êZÍmµ˜¹Q(^§i|¤ŒZ`{öƒáHo/´Í&b¶®‹†áA ócœ‘Æ}kéŠß 5(nôÝ3ÁÖâ+qŒRÞC)€; ‘ÈÏ­~{è¾-Õ¼£]ÚxÁîam,`¶Íæ„ÎìÁ~£êw^'øZOéwº•¶µrH­Ø”‘—¹FÞ›Åz3ßÝèXèšé¾Ól Eí® „•Ií¸ `¯$µävZwƒ|EñE½¹ŒÞM¸[³³Ç(eùw  +nÃìkÑ<-ñ[âˆô ûzÂëXÒ.ð Ÿô”«ã†\ãœ~5WV¹ñŽ—¡]ëCAµs Ä."Œ™f Ð|«’»sž¤ö¤ö4ðÛx·T½Xíš+ydGl2í1¸qÛ½sº6±.ü'§^Ï7—›âŸ$“Øsê1‘Y7Ÿ|[lÚn©áê‰íÍ‘^ A<œ`à{â¶õ5Óµê^&‡Os¬’Á:ÆLd‘÷N:Ž~µDçô½MfÆñbDUº̛ÀQÜç§My·Œ57U–çÄÞX³ÎQ¶¾‰m»—ŸÓŽ+Ý4ýkBmP‡VŒ@—"E nJ20û¥sÎyäâ¼?áY¼>¶öú|³O,®<ÈnÄrG"ž§8ŽEøj_¶—-ú¥±/<èÇÌÀËgGÒµ5¯ ¯Œ­®ÊY‹va2Í¿Œ£ž@1Ú·ü7à{ÿ뺂Á ß6«³‰­&Ɉ`1)Ê·àÒ²>øOVðv¥5Ư{6 ’Ì]“Él„á•”ðÊèÀÁ ÿÕîòóOai¡@²+ "·Þ$g m©â–úYm×RT´‘0<Æ“rÇ€sŒc¸'ñªPø‚;YѼ9°&1$ß*óÁÜ1×Þ»,þx~ÌÇãI¼C}ÃýžÙ™!VnÅð ÷ÀÅ~[ÌϵHØÕ¢·ŸQƒT‚Œæ3Ç#ƒŽüPi~&½¶µØÇ§:^œk`å”vbsòû «§ ¬š§†ìb³…T—&WÚ£Õ™û¾µÊøkÇz_o¥±ð}ÄW¶M¶kœl†G\ü©ÓpÏ™go&¶~"A/Ã{ÒÊòÞ&˜ (†Ÿnˆ;4Ã.Iöȯ𞉣xPK¡Y´Kù·¤ò\,ŒÇ´’Oà1^¼Öw-¹7×ÂÏK6ë…®˜ÿž` »w9¬[ø Ë­¤ÛùåÓ|¬Ä =1ïH ·Wº>Ò·í<àe†ÓÆxùyëÒ®%çÃû¨ßMV¹‘et„VÎSœšÃÑ<'á½$Iý¹u¯‹I-dÓØØÙ;Í‘ö) ݰ2ÇŽ*k{/Q²³:Ž­s¥3‚ï$€,`u;Ï×€jÀÓ³xõ sû7Æs/ ¹6•ô g¦Ezž¯¯xQEÇÂý^çD¾Š'Š=BÅT¼LF À ;sצk†ðEç€ô[áýjúÊÎòJ;±Õ°ràýzÖÂiß ||4û½S\×|3d·DÝB±C%¹$¹Üäv©  ¼0¾ð¥£_x§^Ôüc©­–çÄ̲¯›ÉËuÎ8ôìwž2ÖlÓ/ð㿾2*üšÏˆ¼IãIµ{ˆ­Ò‰ ‚ݾGT‹$³ÀÏqÍcF|câU|_âðÒ]´A-ã<¤Ž>3±AÚ£»7&¶´mOO¶ÖÅŒ‘Ûksǽ·ËgÓï;ž´ìÅtnhö:ò_I¨\ڽ㿇Ê`ˆ#ÇÝù»~µJ÷ÆOz“éÂ'‚è.ß%åwôÎî¤þô¦Ý-ÿ‹üQ&±«^Ï$ЪDz3å+"ôPƒåÚ=Iú×I¤éžŽê+_LšáNâÈò*î¾­ƒŒHLã4 KÈ.cƒR6òCjáÌN7(u*¿ÄÞ…³ê)“^Ì׋§ÚM+´¯2$[Q±’Ì@ÎNN=+oR‡Äš¬¾*Žx- B¨Ï2¬Œ}Ð ¹íÖ·|9ñu¼Meq¡húÔ¯ b&“ìbÚØ8ëÈçp9z¥"^ÆMÄvMà;¹Do5ü¦ÖÝ´*·a޹= «âˆcÒ4;;k{)/o% …÷íHŒYG#§Ö¼ºåe»¿!¾U¼›$þäË“ÍgÝxæ­bEÖÃÞL¹T„H§ÉNÜÂŽsëWÌAÜjú©©Cs „¶ÖÑÆ ‰ÝDœrÄ*Ÿ”qÀ4ž'ñ\oog6£˜.jÛC!ÞyÂaS’}rq^[q¬x—Ä,¶úhq—$–2ãžOI®³EøqâMO÷r[A4·žî(‘yÔöª±mèkǨx“^ûLú|³êz­ÂcådàdqÀ®ÏAøa,QXÏãØîï¾u(WË·‰†x•Ø’Ù>جOyáSu§Þ‡’õü«‰l€i<¿â í}+/âÏ…ôÝ&[OÝ\jv’L“< óHË18!ÜðA?Â4ɹؽߌôìêi¥HÓ]\ùí], œ,q’ cÇ'Œ+ÏÆtž"¸±Ñm¯o%iâó&šEi’ P0 ƒ§­Go«kÚo‰N£ ØDÂd"_1Ë€rY¸ïÏjî<qâkaõë;$‡pÇf—zã$ôR¸>Ô½ã«Il|i‘c ³B±§˜Öèdiºä (°9êµö“©Ï¨¯Û%û g/l­–™ 0/Ëß=+.?x¿P±›Áš]´ð­¼†{™üá<’·Í·ð…±Îs‘WlVÐü5sq ºM2ð2±À‘·8ÛµàñÔšÀ¹ñ"ÝYÊW{(d|D ŒæSŸ¼6ôã,y­»½[]Ô’ËÁó§ó˜4ÒJß<¤ŸâaÉéXx\ðõ–œgŒ£Ì$~`ÛõÇÌ8õ5à¸ÓôÛ«o¶Hwº¤…eu HéØ(¸ðΫá[ GÌÕ,›P¿¶i£˜bÊpH;¿–y¬zÒÇÄ;èú™o9wó– Û„s¹<¾Ï\fµ±¢øÞ ØxNßH³C›‰c•ä¼±×Í`¸õÎ?J‚ ´=æßKð•Œvûäš9$yçb¼x œ¥K@svþ¶€éúDžLÈC[4j#»±çŒ™ÇZõ=[ÃÚß„/tÝþXH½‘¶y'.@ \d|þ¼j§k6Pø†ÆîËP²‰ŒÓ‰>ðe< "œ¨®2mCXÖ/ ×¼a¬Ïyw™+X6³6ã’w(WÒ•˜6¯§x—Q¸ÓLIŒ ™.$o»È8$œ>ã­As©Çg©ÞZêV«d›Sþ=’îêF蛺P2p1ƒI§%âÛCe£XüÏ™3oD$ýæ~sŽjÀðìÍ¯Ç­é· Ä ÖÉߎN3ŒõäôíÖ„µ¦•}Å)°ðìÒ±˜È%Y<°›úÜüÜu¯zèm,§ˆ]j>±škäly·±¸õ\·ßÁ÷ëWÃÚä"M+@Ô%ŽæYî´Éž\ôíÀ_JãµøÊ=}nlõ‹»Û¤‘ Cç}áÞ5@6€êž±ñ£{©C®LþX±Vb?;<¸Ýޤ ÐP!§[êú5ôž'ñ„¤ÛÏ*¬QGw©Ë)à^¿ã^¹k㸴8í¬cÍôú¤’qÑè0ˆì1óg'šÎ²Öcð¿…S@ÕG›q«Ü+‹rw•[>XcÎæïÛKÇúÖ”·i6:\fKb²¢+m‰Ÿ©ÀQ¹†8'¥&ÀïÐñN›y½Ä%”•RMÜg#œcjã$ðn­¤êéâ{ƒ4HÂ(”b93ÆIþ" ñ.»«5­Î£˨\Îa•6‘ *xHsÀ@1ƒÍz‹¼UjºÍ¶—#YIjO›À Â!霌š-Ÿ™?ðS=ÄÞ,ÿ‚üeÐõKv){áË÷€Ä>1!`Ö#¡<ôȯÓŽ?ï~!Áu‹7¶Öún“ª|,¶ÔØÈí#:Î6ÇÊ*9ä ž•ðÏÇÛé¾+üñÅ•ýߟxÞÔD ¯VïFHòñºU‹?¾|MÒ~#hvÚßÃõ Ý=íbµMC%­¦‘ $a€+° o p|½ñ7ö¯ð¥ïŽæø/a x—[ñä§ÊÓ,–Ò_³¯–v›‰.Iòã…Ic‚zH·Å=Çß<áXD:~—¥ÊštwdýŒ€Þs!Ã(b~c’qžkéÚÃÆ_±ÿìµð/Äÿu,Z…í“XXÛÙ¸¹Ônf¹ÊÅ A>v‘˜€ˆ9ÉàW“*k™¤z~ÑY6hxfæ;{.íä“P¼ÑíÖè°Ãn—ÝÀë’1Ö»W¼•­¡—ZÂ[@˜†‘ÆWß½x7ÛØ/ ´Ò>'köz¼Ö±ÑÑöLÓ$a‰Êýà¼ñýáƒ^¿®é÷^Ôí#Òt»ûëýJXšÖCN¬$_õŒ@!zsÎ+†´ØÒ2¹µªÇŸ DˆWÌlܸËI´qÇLW«[è·ž»Y|E¶Íï ¼~lÛ@†ÁfOñ7ný+€×c¿Ðô™µÍ>Çísé£ý!™ö™&›hQ¶z`g¿â?ø…¬¢½H„K$\Œeávgq'€3šçµ÷-1š¯ƒ¯£ÓuO^ÙE6›$×6ÐÛ\L„ȹ &éN ×w¢j¶Þ Äñ4ö0ªIv’ëôû¤rkÕuÿë—i·âÉ¢‹J¹ŽY">WL”Èû²6½xö«ð»W·ðæ¾ë¥™¸Žv·–äÍrY•~^Fq¸çФµÊ|E}e°¾˜7E4§Ëœ0ó=Aô¯œ¾;xn&Ò®üGá{cöy„ à˜ƒáÃsƒŽ¦¾¥¼ðþ•ªMö{4û4Q$g±~­¸öö¼§â.ˆ—)&‘­@¬‹ŽH›&9 ŒôÏë]tš1­±æ eF“.ÅP¤tÝë^7ðÒcà«¡a©JRÆÙÝ቎åVÏÝ9<‚:W±i×*ñêS\ÚÍ›vŒåNÏCɆ+¢ÈÅ&®çQ6)i8[²Ÿ½ ÷bÆìêzW®ÿoøšãG¶Ð¼16ö÷ µÂ¨IÆÿ$lÀe¹äÀæ¼ÒÈè–zŸØêâw÷˜€ wŸ»Ïaëé^›Â[\[h¶0¹šS3È£î‚s‚}¹¦sa²-Fï«\¬¸•Ó;¥'ÔõÇÓµPt’;[ýPO?šìßz0ÔgŠèô˜u ëŒîH9s9Æõc×ñf¸Öº¦‰nïeqo¢¡Ú;¨ùÉé‘ÔõëN´[iüMqá]^ú+ ôÕé,r É ÇÑpkŸ—ÄÚ½·‡´oCªIäêz…ÄÚÛ©8ŽÆJŽy+íï]O‰c›Ä:¦›­Ý[[ÛC+biAR y²1ÎO9÷ëPÀ’Hµ?ÂÒø§ÄVžm­® Š’ÌÍ· §nY™»V÷…a’ãMÔSXˆ[˜­…é3*Æ¡cp¦‰U²29Çyä]AÒ´MZê×PK!<öчùä{‰3¼©=NöÎs:Χg}}©jòX…:˜Wš9ð«(Úê½J¨#*SÚ\ã¾1h–^7½Ò>x[Q»´¸ñ<‘[ß^@Íç[hö„Éy,gIfP!À6W‘^í¤x;@Ð|9ü!)—¦\3Ço”±–eù#2eŒú{Ö-•¥Í¶¯a?‰ô˜íŒ¶æx›ÌÛ$о@ÁàŒõ©®YÓ|?7‡ôÍ#š’¤…€Š&fòT†Ágf$àgö;±cÇ^•›ãßx+Æ^ž3Ñ-õµºYí&.‚ìÇÎÉ `ãœõµè`ª(ËSŸÇÌþ1>|@øU¡|[ÓþxÞ_ëÞ Ó×.e}ZÆÛà—Šu Cákëy}¯EJmo®…íÄŽnò$­S)ba†VýØüÉø¯-·ì§ûKøÃO›Â{¤Úë××¾&5óJÈ¥Á´;È"@  *à ¨|M¥O¥^xšãÅ7×ú†·o.—á=8´Z;jW${éáÙ!221BªÛA`ÄWÑÒ’kCÁ©™ôìµáøÓÄÐè »Ñ×Á:ÝÖ¡¡ÛÍ,ÓË­ÜP-,ñ*±PÑìcÊÌ©¾'kŸ ~ü_øCÎîKKÔ{Û¨b ŽÇáËË2:¸Î W³~Í?¿jþϾ&Ô>x;JÖ-ü9âÝO[’ãY†ÄBnLsOŒÀ¶’8Ü<,xÏɸä0ð?ÂÍsÆúƒ|:Ó^ËÃÚ¾¥}$ ¥ó °¶–’A#s>X a„\­•º˜œ¼ñ×üRÞ#ƒYÒâšîÎ8!´Gä›’’ªZUQ±ÇSÀÏjö¿ü.»ø[ãÍ;ŠǃôÏ̶ºüt2]͵áßè·’Âúì[°eÙ9y<Õ‘—nà“òt¨¼3áÚ»@µðµ¯ŒõCÃ:¶½Œ§D×chïỸb±-Êʧ`Å™²Áx )ÃøŸà‰?eMV?Ú²ËG°ñ¬^Ó£šúK+ij·½ÊùI§8¤I•Þ7ÉEヌšîÍ×…¼cmªxÞóX¸ñ0Ñmm®"eÓÂÞÁ?’°J&ÜÉk ò,— ¢Œà1À;~øÀ¿ |Qðþ/ßjÖš¿ˆžÃÄÓC©Ä|Ɠ̇íVPñ„S´}îs^Câ}gÂðŸ‡ü;áûa¨GáïK*k;Ã]_êºÌy»1 ]Q FŽ0(£ñÃÿÛþÑü1ð«é!¸ÒÖkÏ·i³Óo5¸ãŽ‹¸á•ÜF°’Ê£,û€ä×yñ Xðæ›ñ£Ç6Ÿ ¼ÒŸ ÜézdRÜÌ÷W:Íü˜/s+È fI-É ª{+á·Â»; 7Ç¿/.O‡4yµÛ;Û DeÔÒ[öÛÜNçy ‹‚yîE|Õá?øÏNø7¯kþ'¾¾OˆQ´ÚÆ‹,äÛB¢)±mÇœ¼†5u|Ùê(î-;ÂZ~7šÿRÖÄ—ÿÛ¥ÜÜH‰²Þxâg–y*ŸA^«¬\|Sø'à/ |Dñ¿ƒ‡‡S@»ð¢Ít.%¹´½I..g»K,l"Ï“œóÁâ¿*<âOø›Äš×Æ ^þ[í_V'W³”ùÿiÎñ&‚B¨U™ŸzúËÀ^7øãŸ kðx…Ò÷º%éÔtë VÿË›Us šAcÉc$&ufÂò&€>žÒ¿iÏi“x~óÀ‘Å®6±-Û­­¼CEkÛ&y›å‰‡ñˆmþxÚûÄž?ðÞ—¤ø_Ã×’ÜÚÛêȲý¿X¸Ýæ,H¬òÌWŒ÷¯—4/ŠŸìü9{ãO…¾¾³Ó¼&¥-bÔnD“^_ß·œ÷w{E»;Ÿ-OÏ#ò+Õ|goyªh>7ý«n帲ðü×–ºn“hž_˜ºYÿ|ˆHn6 ( zÔIÝÚüH_iv.Ñ´U×#†g Ä­s,vËåyª™ÃE»'C^sñ«áÇÂ/ˆ>µñßáKKJî6Ííá{k”Bžk’AÚÓÀÈ3Î+³»ðF¬Ç?Ä߆4OM·ŽeŽC[Û4›®£$ yR|T:‡õOˆ~=½Ö<5tÞ1Ðâ´»Ô/,¯,ZÚêØÜD"O³¹ $U(9׿ÈÀÎN"#³<ÛâçÅ];âÿ†æðn•<1ørHÛʽ+  Ã"¹FÝ€2Ç=ëjÛÇš‡5¯Ýø£ÂÏ®­«ÿmMq ´SDÒ•؃œ"ɹ«Þ½ Æ~ømáK…´Öü5£s‡¤¿¹’Fp&¿;D æ/±¸ÊXñÁ9æ¹ÏÙÂ?‹Ÿ´¾¡Ÿû=| Ö<_´mcs}4ÑA¡éÒE”tKë‚"”‚pâÐÈÑ0Ôkž¾*âå)ZÆô¨9ìŽwMøcñJëÄ?ð‡ø¾F=.þ|i¯{ä]I …¦ ¿ 6 «Ðd\ï€4ïxCÖþü2ÑÓHÓc°°ŠÝö£%Ö%¹&D–IˆppT`Šý|ø3ð+Àÿ~Û|$ð4ÖºLgEi¼×-pååyÌCîÄ–n WÃæ¼oBƒä¦îz¸|ªOâ?< ÿ™ à?Rÿ‚üS²ð…ôG{w¦hô—wóEjw fvÐÿe‹Ì剉Ã×êo€ÿl?ÙOà·Â[+¿ÙãÁ·ú<š¼im])£¹i8XÌñÊEÑ r ðÀkî«?x?G‰cÒô+8’'Y#ò NñÑ·mÎáêNj=nÿâIi/´+ËpÃæHå‹s’;y„ñùWÃã8º¥}%¢=j8Aj®~|xGáïíñ“ÇZž»ã»oÁþ¿ØË¹|5-BêdÎðWÃþÏÞ·ðÏ…¾Õ~×óC}0–Ulp#AÀNÔP£Ò»ÿ ¾£öK™µÇ³¸d_´[UE$œ×âÿíÿlø‡©\/‡ÿcÝ Mk˜meñ.¾«-ÕÄ“È" eb…!ŽKÌÙ<³œ×ãw‚'ðÄ߉oñO⟉õ?x®ØÜÉ5Þ«Ú…å£.K;†fE 6Dª¹è+{Aø¯ð¦ÿáޑ⟠ø‹Lr·MhÞfŸyö¤126ŽUÚ™Œ¨(árpzŠû¬¯‚(a’sÕžN#5r{žÛ®é?üEâCª|\ñο㠃ëúµïˆ‹ùÖZTÇJ$pG'"6îÁù‰Æøµâ]gáv±cªøNðÈšŒ’éÐ,jñÝÙùI¹A@ wy-ÔcŽ•ã¯Žþ"ý uýGñ÷£]>î}Éö¶D†grüçŸ` øKáǃõ»ó㿈Êú·…­!Š[gYßÊ!¢òAOõ,r*‚pqŽÿYO vPV<éb\ŽwBøg­ÛßÞxÃãP´´¶‡7‘æÌ«áø*Ë„`¹ê+¨ñ¯€õu¸Ò¯ôÙ´¶¶H´²³ÚĈÆHÝNw¿7V¯"ü#oÙjO5ä0Ç+_Ául€C$Ž0 á¹ÆZÓµñG€Æ§núfždÔá–h®ŸÈó£v¸8F2À6r~QÒºÕ##ZÀ\x“ÂW^ ·‚ÆäEwªji1òV4UY1Œ—“#äïÎ*¦ã5×¾øSH¹‰äÕ®õ²ZÆa,~Á´–ŠuÇÊXåàò`x§â‡Œ¼¥µž·c¡4ö—‹un’,’Y[qHΙÆÞ¤õö®á÷įØhƒL_Aiý•Ã-Íò¨ž©˜Ùz?+dûÖÑ‚2r8Ë[ᦤëúÞk¨»¬‚_q‚(áV;–58È-ÃÛ‘^ïoà(;k«w6v6ZTö¢—Ø%l|™ë‘€µ|Ëãïjú弞ñ Ç=œŒ›U-2ÉË`ð0O$Îh‡ÆŸlo/áþÕg¶“O·³@‘…il”ª(~ð@õ8ªäDÝžç¥h\h—~5ñˆ$Ó´¥iÒ*U™$w„>àqåîÉÇ\×cá+‡šVŸ¤|Dñíì÷ójòYZ]Û®‘òËJ=ùkâ›ÿøª÷ÃSxnæÒè¶±vŠæFÝ.7ò«2ôϨ¼k§ø§Ãri:íü±2\ËçÂñæ5Œª¼mØuSÎ(åAv~“ü,ð'¾øûMñ6‹®Zj:Œ—¶º~¨›‡“h—öò´H %÷å€Çr3Þ©|øóðCá‡u¯ø_Hk«KÆHn,î­-²B$•f9Vy‰'9 q_Ýkß î¾(Ú[Ä· ¥jL­za|}Šc+ ?ºW•î¿J½ x‚t·¾¸±Ô]ôý,Á2n\;[´ì‰“Óp ž¤qG" ³ï­/ãV½¨j:Œ7ég-íŤÓË«ZÂa[Ï!Ç‘¹åuòž8Æy¯*½ý¤4‰þ8ÓcÐôkíÒtž[˜µ)ÖX pø­,«‘ò1_3i^ÕãÐÄ~Ôî.m¬HÒ[¹™¥nvƒ\pFÍw𿉮Üé&ÒÂÁ´Fæ·µ¿‰s$±>©´€/ÞÈ5›Š4»>ÚÕ|cðËÇžÑ< â_ÜxPÖîÁ_uµ½œª"‘–Ln˜ ®NAéŒÖ‘ð‡áWÂYÞè7wwøvÎ]5l®¥ó%¾†i/šÜ “Û;Ey÷…þø[Wž3u Ÿ…“O¸ŠÖÎó;]¤(Ì„ 0Ï2Moø›Ãš§„õÛý.òÚæòÊ9a{…’M’2Ìß)C“ò·8l䊆‚ìúËÁ)?ˆïï&ñ…öŸ‰>ŒÂwnK[Ž# ÓœÔsX¿RgxÁ2pI'ëúÍ’h÷^4ðž•mg¨éZT_ÛAŽ9ÞÞ­r#\l,Ö'±ÉÍx=§Ä /â‡.|Wá‹Kˆ¥¾°Y4˜!CæE5¾Dʰ°Üÿ6ÕRNA4 ôû/üAðn‹ðËÀ^.¾Óu›ßê2êº~¬Ž`ŒÃw†f•Žâ©0˜0RzW9ñ¼é? þ0ø—ÂdÐü_¯k2Åy¦­‹k;¹£ÜþXLmË`.Kç9&¸ H4/]xòæÕu ˜ÄO Ÿ']«$* êF8Ç5›á]'H´¼»žû@ÚEèÛ§[HŠˆ$oõ‚pXÁˆÆâÇ4í“,i¥xvþ-:Yn ûe£¸û*Os [°|²£dž½k¥ð®½ðãÄ;Z¹¸ðÄŠ“>§i柴C|dOƒµÑ—æR Ž•Ç¼Eyá}Â^¿²šo iöú|¶2FÊd(ß0Ãd£’ Œ×£ÛÙkúEý×ÄuÖmgƒH,ÇOÕ§ÅÔ‚%bDQûÀ¹à·R:ÐIà]VË^ø®¡áÛ™[UÔc¶Ôb´¼É3_\Ÿ&ê%éòƒ¤ËÏ$W‘ê^ð¶öÿ²Ùŵͼ7),E¤Ž{¸ ——¸ëÒ¼CÅ3é>¸+ãøµ[[ýw¥OhD qö©7VUÉÌdä!#ÜW¯Yø²+Õ½ðÅíì÷:ˆmî —Pu Æá:3ôÚå”1ÍzâëøÁµ #RŠ ]0·P]eÞåFÙ¢`qº1ÁpHéš»áψºw¯kÓN·¶3Gi=Ý•ãžÑôg’"v2àêw#?2ÚMi§øçÃz]Ìï`·/:\Þ<^\‘̰³¬m‘´†®îTñœú«Æúņ½¡ü=ð'ˆìn-®|Eáé¬/omPù+¨_D\Ä ßfŒœwôâ€1'Õï|9«êþ?ÑmÐåVE»“. {’YŠýÐÙʱà1â¡N1ø#Q“WÒìµ›M.æÞùàœ‚²YKC ” Iƒ•u¯=ýžþ(ÜéúÛizÿŠ-¯ô¸¯æ°¹µº yÎãftPA Ù8õ®¿YÑæøeâ›ï YÝVÎm6;[[)¾iMÌLYÑm‘@(Ò€%ðïÂvøgã+oü{³>"ð÷Š×LµËÞ™M¬ë$걂w.ÂWçëòמC¤Þx ïWðމÅÝœw¨#ˆæWæ!GÝóÑí*z‚9¯ø›ñ#Å^6ñ­ÿÅ­.ÚÒKËøVÐJë*Ee|mö¢þ"\p7 WÛÑÚXxã@Ó‡ÃÝnêûÆVú$sÝÞË%£}v‰• dí Ÿš€:ð¯5Û&ßÄËšl×ÛI€‹™AŽh#´ü€Q³Œcf€8K cÅèz7ˆ´/M–}ZDÕg Šîqj ä®ã”Ï|S|GñßOðŽŸ«iþXõ«Aw¡Ëµhà–!½™ÃH¥AW\G^k;Vðg…<§xÅ:ߊWÒ­aŽÊm [\^IåbUçÊ“çsÇÈ v«ZÖ§¡Úø¢}XÕìõuxîM¯Íæ:¨È,áž8 ß]Õ| áVñ•³›cPÔ Deo9n¬$…YIO˜Ô0I¬ëÞñ€¶Öôá:BqíœÒfæàÇ-°êAò·pkËå¶Ô¼;¯Ù¬º]ûêðÛÃ7†®¡>tQÀ ¯‘uãÙ±Œ£­ãOIËñ'ÃèÚÃÇñÆ¿ã;ë©uws‰=»/í/o*èôÚÄí; ½mø›áû^hR\ÜéšÄþv‰©hîž\*…G˜›—9C€Fs× Ð4Ž›YðFðÓÆWž–úâÖ[»hb±ÖLBa„Œ…€ŒHÝØŒó\+kz§ö:x·VÑWí6¡&—¨Ã€$zšÌâ?áG@YxÁš£áßÙøÅZÏ…tÝegÐ^ÞÞêÖê`óÝÚÌ_D¥²YÓ1“Ðp+½ñoÄ?øŸÀ­©A¡¥®²÷`ÞMc¨Õg]­çJ‡æ6¯' ã8 «žsáÔ´ñ4W¯â{Vµ¶’ÄMYÛÛ˜ä,ƒ’@Çæ¸ rïÄß |=ªëy–%Ü67³åö‡üŽ>ð=ùé\ÆŸ¯øFû[Ô_ÀQ_ɤklê4YÜù1o±‹$˜Š¸$ãñÅ}áÙ›Uñ­¦­x†ãI[ë¡eo§Ý2¥¢¬ÇäTl•Rä œ±žoð¾ÃãoŒµÍ+Ã0kvþ&’y~Ú’Iò¬÷ƒµãs‚¸éÉÇë^â™c‚M?ĹŽy¡q«Ío1Ï¥ `r3•«_ ~x»À:.·žkKí9M¥«Û.HpÅq'eTgŒç5çÍ©jº´úÂxV°ký([\^Ø—";˜d“÷ê€gl£øàò(}7â–§¦øçR¹o ÛjùÓÙÜ\/Ê<©b1Û’9íŸÂ¶ô½?D³ø‹¤kZQÕ¯‡l¥µ‡UÔûb¼`h³óÆ™È<úW¤øKÅñhÚ,¾ ð†‘«\ø¢C:ÛO#B³rAˆò¤›O~§Šf¿¨Úø­¬áñ.Ÿ6‡j‘„2KyX,˜~L‘ƒÇb(Zêž)×|_¬6™6­Óuµò./@{T¶ @ÀlÄsœt¯œ¼ã#G×4¯jú&­àíib•oá–¶‚YvÎü»ÃuËc­zEׇ™¼?=Ö>ÐæçNÔR;ÈÄ¥"‘Xgc²sƒÁjõ KÁ¶×ž ºñÞ«â)µÛ d6ºmÐX䈢&ŠDËÀ/ßSóHÍœ]ÏÂÿ ßZ꺟‰–^}m¡[»˜¶ÇsÑ)ò¤ó‘’¬ÝHõÂê7üUáh?áÔ,dÕô ÑK .n¢Ç "çx|p[r9®ÆÆÒØË>±ðâ)-æ5ò-ç%cvyxèFQÍy­—…¼|MaãM'ÃQÍa Ï&¨tø_é­*û—;ÝU²NÜägÚ€.^Ø[k÷ö—3y¶¶GÊy9“dkþ²&'¯ËÈöª–ŸÃºBhúf·m¨£Ý\›Fó>Ê„e„÷”7ðîÈV&¶î¼tÚ¯®@?´4„¿XîZÖ,HŽyNb °GS‚Øàà÷¯xóG¹ñÿ< í.æˆ#Š)…ÄN£ÌÞ­À'ons@k}á{ï¦k°Éwâ+ŠêQ†zKsÏ÷ˆü+®šßÀþÑôçñuüzRê¦>Ôîq)É ê¼÷‡^ý+sV´ðO‹|/>µà-A­5©¥[¸¬¦Yíåcˆd»åãŒb³¾+iºÅý‡ô› =?ÃÖ–ó/2LŸnŒ1–LºJ|£¢“õ¢ÁÌ™‹ðoÄøKIñP²ML%ÚÛ}¼s~ÊÞdo Ÿºî‡ñæ½gÄz¿Áïéº'Ž“P—HñA¹qj±¡•#É¢‘X×-“¿Ó넃QðGЇ†ô‰–kmo²óPš,Bßg #nÙÀôàןcO´ÑíuÖ²µ¾°u™t„2FÙ2+wùy8úP"÷?/u;Ï ^ióè°_Ë‹{€3“£àù_.±É?0¬ÍVãÆÞ¯¤\x:X™l#x.B\)‘³Ÿ$:臉|'…¥Û&¤.u‰uR‚°GÞ­9Á+Çü ½?O¼ð;|B¸ñMðÐ4ëlÈo=Ñ£¡VLddôÙÍx—šŒ`»‡ÃÖž%™î¦iì_—Ëc’ñ±å³´d5ÖÝøsZñεá«íKš;}V?:Ù²öáHF‰åoR0yükÔþ'é>ñž}¬ü;Óâ³Ö.o–õE›l‚æ AœœÓ@@¶k„°×u™ü;øFv·Ô¡t£…ŠIgù§Pq¼÷Ôr;Šdk[x Å_ ñÇ‚.5ßkú’Üj¼-$²Í°Ípã`’0Y1·ž£°®:_ø—UñS^è2ý®uýÜiu1‘ä;žNy g¥øE&ðæ¯Çukâ5áy­'³ŒÛ4ʦG9ë€yÍ[è~ óæøk«\Å©O|’]‰§SòÌäFo]dz޽ÀÚm΃áÉ|=âÛ˜î`ŠÞCo^Ff<$˜ÃlíÞµîü áËh¿á2”ðjžL‘Ù>ÒïÁ½q@3/EÔüñ áΉãav,u½ÝôGL›X.¡r²ÛJ‡×4RUІSDß 4]!R÷GןLH#û]¬°Ä¸,ëÈÏ^ ŠÆø¡àO Y|FMWLµ™'½iy2|Ì y'¦^e­ë±ê÷·Pi¶÷e¬B—6cùzíÏ##ªŒU%Ü̹}ㆷñ ¾Ÿ©jvÚij*Â.þÌmÜ'«Ž '9ãšíν©iÖ’kpܼ(TŠHpÒ2†pG͸wåš~Žž<Ð ÖKySZ]÷œ)uÜF:`œšµ¯C­iú\÷^PgÓ.Ì·Q:ºÊ;(<ŸÖ“@{ƒþ$é°cL3½íõí´-ì2—·¿Ëà6[€ñq¼ZØñ݃‹ëoë6ð鱯‰$‘6 ™æ ­ÏÊI¯&ÓbøWâÚÉ¡\A¤]êr¹Ô­ ÐKË‘–çÎê9­ÎÉôa«ý¦MN;µ$’!iV%Êì’NÌQfac¦êº…”žðü²ÒÂY†[i†8ã•#jÈŸÀzÕ½’Ùøú}R]fÖVK„°œÛïU8ŽHÈù~ïÞëžÒl…†®]ÚèÍí9æÓà²uš`y o*z sŒƒï^óâÛ߇z'ÂÑmðÇW»º7÷è‰ä^;Iº¶èÆkt>ôì3çO \ë—–æÝ´»ˆžÚÜ2Ü[³=»<_tŒôÁ®ÂÖïÀºþ“?ö•ÄâŠD¹ÓæPÉÀü’Àp6£u~xn•Ñ›gGðUîgc$Ör­t“îÈq«×æôõ¯Ñu}xj±M:[[YÏm,7r7úéÎ";º‚®:S±25uãhÞm; Ú& ÓÊJÃ3 m<ò qÀ5¡ I/‡|1ö½HŸUaµ#V0î=‰äb³õoøŠXômzP× åò~uWCüxëŸÄæ¸Þéú—ƒãÑ#×RÖ÷D”4°Î‘àN#hÎrT‚G"•‘'ÐúOÄ8ôo K©ë(ï©YH£ìríx牆>Byê}Eyߊ|C£x…×Äšm€²Ñ‘Ä i … – A»œÓí¼_¢ë¾0FÔ-|Ý2 tXd# X bqÐô#Š¿/‡[R¾mz8ÞãIŸýS´È?å›'<ä÷â˜ë? ô[4KÍ6ä¬sApÒnIb~…J’'ƒVµ»KußYÓ¯¢¼Ô¢EûM´ ¨7Ž °0@îsSè~¼ÐôØn¼- öv9Œ%óoy8 ¼¨¯5NK‹/\x‡Z³Œ3Kqle¾D0¶7äq×Ö€/[xŽO‡·ŒH¶¶¨˜ýØ ÉæŒu‹ì+¼ø=â sQÒntI4ñ c‘Ä,J”ä†V9ô¼).uWo‰l`æºÏ‡Þ0ø©ý«Ä6âK]"±K¦È˘Y×!¶žvã~ÔÐW?ü9ã=:ÎßWÆ{ |¾YÁÁÜ8É8ÆqÒ»OxëÆ6&¶×4ÝVÿN°•áÑí›AëŸqXšN±àoGÿ޹¡Øiš»M/•¨Ù]ÊÄ6@olÌÔü?k­x[Rñf½­ Òm.#±ˆÚÚf#•çrœžÝ꬀ô¿xâ…ÿˆõߎVÒ,w>Ž16©¥•´’X% ,°ß¸ðÙ 9­aàü`Ð|5¨x9´½ZÎyo®oå+-œ×'ɘdHSÕ“!N21\×ÅOøH­‡Œæk}BÆÞÞ€¨¼µ*dl¤ðyŠãfø»ãï ùݺýšÞÎ6K{‹h|ȉs—R2AÛè~¢³²yô­;ų¶¯i«ÏáOéÆE–ÖTÍ”ªO ޱœŒ†äÛ½wÿ> |BðOˆ,ü/ñKN¶x5-C63$¬ñÇÃ+b>`Ç 3Ò»#«Zk¿ eñOŽ,-üEq.!µºÓB¦SœH¿yXt å}1^}¬Ã{ec¢ÙiÓÇ,~tI*0bv‘òääƒLwø±yà_Ãw߯ ‚Ëí2¦Ed9`¸;Óý aé>-Ó´Ý9lãÕeÔ®¬öÇc©_)2ÅGq»‚t`~¼óQÿÂáÏ|>“ĺ%ôMâ;I.ôW>\Ƀ²T– A¹Îk”ƒÃ>*Òíîmtæ’;¤Äñºoð˜w‡é:F¹­¤Ÿˆ|7¹ðÖšÖÚ~¥yq½,®\[悬Äduë^i«Gâ{„xóLím61¿³XP@cøP¥üAð熊aºð]ü÷7¨ØÚJS~ö@à‘œŽy­?†íàrúeõ­Ô7ÊDÕVV"~ê: ryÞ¼ÿUÐí5Ûj¾ ¼–ÇM…yq1 4Ëáa··Ð׫x94u±»Õ¼YÍŒ¤V÷€!¹ÈOÌ>ly˜ÖçÌzß„õ¯xŽîÔëE¨;o‚PcwPCw¶LkÑlGŠÿ°lü/á›¦ß ¾q¸C"¶èGéNÒ4³s`÷Ú,m–X¯F%YŸ.F8üEz?† ø—ã…3üO½Jºcg#HY&†ö×Á~6:VX§¸·K¨§CBÀ€1€27V¼6¾¾kµ¼7)$ˆRÒäpÜgðÆ9«_¼?6³áË]D]ηÖÐF¥ ÿ2œ¨ù³¸v$‹ÂV¾×uçyÍ­E‰-f!¢VQ‘,jØ8ÏOC@ÏQñ¦§¤j:î–žkÏk^r…ÌÃÍ„¯2Ben9ê)$‡Ä^³Õõ_´Ó]Z ‘Q˜þõ{±ç©ïÞ¼gÆÚ ÁãÓ$–·qL ŠYX€PŸœo†8I5ÜøZïÅRø2ìܨ{Ò7Å4oæ“nßwêzŒŸÆ€gÿÖî5Ojú®¥’C§Ú#H&n&CÐ/=¸©´ïÝi÷ß,pÍ4d G»æ'ŒôàuÅs­aá"æ+ «óÑÆ!ógw##<{ Ù]V×MŠ)´Ë_´Krå#H,G\çî€+òÞSíìwž#‹Äz²½Þ«¨É©ù¼¨âۀ݂Ðuç5ä:6§/Ûç¸ñTËqcŽ hÄWêqž¤×`n¼Sp…!¿†Ý³ûÈÙ€z}zq]n™áëÚtòjºÅ¼M¯&ölŒ¨AÁÎzäc½&3Œ²¹¶MJÃït'rQàÝ#ãgoóÍtÖö×$Ÿ¡¸µ…ÏËiÄÌÜmÜØ8'Ðv¨ü®Íài%h`2d §Êì P§Awd#®b]J3)Ê;e˜ñ€:ûÒ¬³Ô/ôƒ%Õ¾•¦iFéÙ×RV»|wõÆq“’8¯6ð­÷ˆ¼_¬¶‰¡ê–Ú„Ó4îѬ"¨Ç :œö«öúuÅíÁk´”yl 8…ëÁ9ïM»ðW†V)µä–‘¾C” \œûvÍox£Gÿ„I"þѾµ›Ïä1*½À q“ë\÷†uíK ËÛYí¢7HÄÄ3qœñÎ?ã.¼3â‹«¸4ÿÞÛ%»–¢De¸7©$*p=+ªð̳-áß Ûµ—ÙŽù.Ràsx'4)ë~±XæV•>Bûâ;áÓžüV†´ýsí–ú¥Ö½,ºkÈù‚hˆ‘ˆþï·Ö½ÒÔ^Þ´ºmí®¯—™Èqæ!Î8Px­ƒÌ´{˜"mð>K>_Rx÷æª&g3«ê÷pk6Ö÷s$@ïþàQÙéÈëšÔ³]Z¸–æÙݰgg,K}GLv¬"öî®,µ›æº–ñÔÅPzªŽsŽ9Î+Ógð%Ç‚.m¡0ƒÊ4Íɸ¦áÕ‚ôç°ª”]7Dð‡‹S]6Ö:ȸ€Ç’Vòân¸$u$õ5ÇëÏ«ø†ä`ÙÛ@]C$.%ÏLôõÓ%–¡©Å%¶¨c[U,°,)–F¦î„Ðבiþ}Pe28eVA%Ç%:sŽÔZÂÎ!c.£mö=Ñ/íkq¸°ìŒuô©´ëMOUµ="ÞC5¤a¦‘°Ç¯zÔÐuÝ;Ã_`¹Óí廸ˆ*™P0ˆ~UàÜšÂßµŸˆºÓi>·¼ºÔ¯ffE"ÃoûµÝ—i  P9äâ€;‡ž‚ìßêZœÒ¼ì›Œ‚uÉ œiqÚ¹xÏÀ:n§v4k»y]·ùßaí©AŒ8“Ür—ÿ|1¥Ï&ã+•€I9†âigûQ•º´a6z—ºk­'àO†­¢Ð|ë“@|·¿kƒm·_’0  ÐÐÏ’ÚêKË»»‘,÷W TÆøQØ®Üàçû cšƒ\ðþ4ãì)ö™£HûŽbAÏ~3ÀâºÇñ$ú.„!²Ð,Þi&uŽòVo<#J€0£ÍEi¯G:ë[Ú@àâ+E2<¤v?þ®´GNÖã°°C¥Ù%²ßF¨Ò+ù’¼cmÇ…Éè .»eœ`ŽÓʼó\Èef\ð2Êk›ñ&›«ÞµÎ“lŸÙèB$H~ò‚2SZ>’â;ˆ<-mi.­uýðÆåfc÷Žôܦµâm635¾ŸhB D‘ä UéÓ½kxf ûfžöêÍ8Ue’8¤$ s’Óî{š©©xsHŒ¿†õ;k‹6žD24¼äõ*8ÛŽ˜5·¢x7K ¶6׆Æh·‰IfÿtÚ€=ÿß<,Þ¸ðâé‹ÌÝË2»àð7ž¤Ý+§´Ñÿµ.ou:ÙVÉÑV8äàïrëï^{á='O¿ÖÊëz¤ZM…­³‡¹¸C!P£77p;šëtï h·-i­éº¬º¦œ%\JàªÈª0åAì¿…Z؉‡`¾°Ž-[[¹k…JIÁ¢M©ƒ´•9 ÞØ®–ßTðç‹^mSöÖ6lQíì”ùkŒŽOÌx®oHÒf׺¶IôË{åKHÎ\ù­#”q·o©¹Ñ~lo!ŸÃo-Œ-™\DÛBŽzÿ{ÞŸ(s~6kûf¹©µµµ´¨–ÐJw´²¿8»®çY°Ðu_[A%Œ·Wv8re•’Ú4>bù,nµÓøGÄóý²+élôÍA Bë4°•Yz2“œ7©5‰§Çeª%Ä©«8’Vɶ|¡';Fxúb“‡a©¦›oáë-§_Ü[ÛÆ<µŠ' \ã,Îy#áXÚ—†l¦ÕdÛpÊx¼í™yÀŽMzî‰á-H]‹Kë#tÓí•cŠd*6œ|ÍýîçœTsëRèSßéñXÇ3BìLi"ä¿uÞx9 X›9½ÂÓOµŽïA¸´e2‹†AîØùUGlÔ×PøW†çÂ:}¥¾¤êËö‰m]¥“‘Œ—¯lñSiWMO k:$:2‡µ¶•É"äo òJÂ6›m¬]ézk=²–Ä’*[£ó€H™ºgzf¬F_‡þÞA%¾—¥ÛÜ[DŽBùçjA$@úšmËj1‹ë (…™¢Þ& FPãpd—9çÞ¶õ j¶ÚÄ÷&ÖZ)7ˆä”1H€&â>côæ«ø¦ ONÒío4­.Hl )Ž×;L¥ ä •$ççÉôÿÚ­¦-íÔ÷ˆÆðBû7\mã×­y®µâ/°ºØYI-Ü1ÍåI!fÈ‹× œŸ¦+ªÐnnæ’k;ÃÑÜ9fÄ£ YÆrç8?ʺóQЖÓÃ6QéÒ^\¬òIç ©êÇÞúž(Éô[‹Øµ¤¸²–mÙžÖV1£ñÃ1ÏçÍ{WgZXõ Û¨~Ó:ò<ýæïåî“ÈÀäVgÙí4;YU²Šååö6ÿžvÉÊ*ŽHõÆ®ÏPÕ5eÒì#“Âú…º£2ÎGa$ƒæÏ? AOiš“µýÆ»z&oô&0Ñ'B{Œz½CÆÇRðRj>6±\ÜÅH%28w}ó•GL\'ˆüwáûÁ£x2ÚÞ;JD -$’邯2Á-‚ÀW½>7Öõ«í"ÖÆúÆÆÎ0å&O“{¸ ü«“ÎIàP?wwªÉ¤M­FëöÙ6¯Ï¹S®Pr@Ç~õÙx{Ã×SÙÖ¼pÈGÚîŸ9UÇË(ß}˜žIé\Î¥¤øzÿCŸÄ“Ïz'Ô£["©0Â"ûÆ6ëÏ8#ŒÖ‡…­î¯åþÌ7W6–‘Ùss™];ªŽ¼qÆ(±×¥›ÀZ¥¦†LOt°™Lw@ |¾Š ¨Ú¤ŽqéPiZÆ¥¨A>°º|M¸a· 2nÏËGÔûú×36¥©jÚ«Â7 Æ£xCM-Õʙ昨ÁÂŒ¤×k­ºÔl'ñ*É=Ö›ºÞÖBìLAØ:n'zÖ‡´?&º×Z¾¢’ÌѬò [v ¥Çʹ< £­t~ ÑëBÕnd·D¾–If·’îP« 0§WÇE]´ÕuiÚR7—;\NùìdêßEõ¥ÑüQ£øKN:ÕÍäWZ´÷¨ù;§mƒ¨À Ôu¬Vâ_ ÙÀ>"\ÛZÚÀIVŽ"Á àº÷#V¯‡<)mñÉ.¬¬¦ºŽÎ3'ïWÊ-ß?¼˜œaÖ€3|7âôþÒ¸¹Öí"–Úô•âäHÜ÷88ÈôÍyî¿§hš•åçŠôÙ.,Ä/ªB’æåŽ#Hùã=søÖ_‰üG>x?HÒ¿µï®fÀÈUc@NõPc±æ½ÃúV¶#[d‡TºŒ˜öÚ5#â-ß¶áj<1çjZ%Þ§®é¢×ZÓJo˜cÞ2dïâü…u~'°Ñ­[PÒm’{»—HÁÈ>^Ñód· qŽôÍ'ÃW¶öZw‰5{Y-ôɈ/0`X”ÎvÐóR[ø–Ù"Õ¢·µô[R`[^à“™ìOsúÔÈ ýgâ]¾…o«yæH%ÌË oeëòx=O³áo -î™ý£t¬·H<ÿòÍIÀ$w-ŸóŠó/êÞ#Ðmb’Õm¼‘á,Ö.W¢ž¤cÔõ®ïÁú—ˆ-´>ëQ–Ê)®ò5'0¸9 IëÆ2=zSèK]O.Õ|g§øÅ:KŸ2)¬u 3«Â;¡Ë“鎞•õçìCeã-oþÁðÿÿ /h)㯆ºg‡¼Saâ½FÔ][]¤$$™÷JªýB/ñkìŸþÖ>#³ð§ü 𩵷j•L±uº€1÷=;Ÿ^hðö1Âmô¸šåå…âVa‡)Îq¨Éì+ócÇ_þøö’´Ð~Y ^o ØÉ?ˆuÅ ,XÊDvÖ¯I¥,ÙÓî…G3W:bÏÖoxãÀ?¼Y¾¿ ÄZ»ý¡ Üeµ@߀¿u¸æ»)|G¢êöº-ür_ê÷ë+HÛvÀ«·°Žõò/„µÏxĶú,RÄäâI—È‘£AÊíÆîGn¸¯¤>ü0ñ÷Œ.¬.WÃCB‚ôË#Oq!HöÇ>Só)s¹Á<šç”54R7Eω|CáMCÁVVw+‰-cs’“žƒoQ¸}Òz5è´†Ùâk™ kYf‹t1™#’_/NÑÂç£Û®+ÍÛPáMί6ú}ν1–ìÉo8‘RHøœd*ŽËÔµcéߣñ·„Ûûqg¨ÄûJH?x¹9,¹ÀrIÁ+:Rä+™o¬ÛÏ.“pU0àðc_½NkÎ׬íîtáse¨:É4‰ó$aÙ¾Eêz qÅ^×ôW¹±Žþxž{†u7UÜ錞{×øvÛG´×®YÔåWÓî–8í D$‰yÆNs’•B>Ó#¶m<[êí%ÔÖë‡B#±þÙÉÏ·jôë¸¬íæ‚é‹y{B*(,»¿…ygóÅxêi/ch-õ™šââÏ.ñ¹ù¡,r£#v1œg®ÖÛÆ^$’ÎGÑ3°ÊpÊ›ú°ûÀsÔb¥£xìwº&•¥ÅlÅÒXÕ¥v ÆÃ ãÓ’sƒž€WUoá«FÂîM<[½ÄG²ƒîï=ݤïÄÒÇ©x¶ÓÖæÆ;vYÓ/Äܲóu?/©ëI—̈|?y©øN’ÊÍ£ŠÙCnB7ÚÀäHO}¿3qƒ]Æ¡¥·‹ü;.‡6»kg©ÛÜ&Ñvpѹa»©É!qÇ­qÚ]ݽˆÒ<;ª]é&t’;s J|Écçr MÝÈçÞ›«x15‡l4ô†M ÖÒÛSÔ–‰¦cò’OBqŒŽO~Õ”&jiºEµ¨ºÐdƒU›K4¯¹‹ª1ÀËdŽÔ\Úhšv‡8ñ&—0ÔnŽÇ{²Þm¡Q¥:g8þuÏøÚëQ{ûoiJ—6úb³,0Ëå«4g#i_SÉÏ¥I¡¼ú–>¥â:yuy.úêcp] ¶‘†ùN†<àTŒõ}+OÖüuw[½Æ¡-“²ˆcgLy Êq¯L“žÂ¯hZ…ô»xµ»ë¨!Ów‘o¿ê‹0Ë4¥¸QŽŠ>µÄ^øÄé~0–ûÁ7WpèIÉ*Y¶Ôš @FYÎÞƒ<×[£xZ-[EÖ±5ÚéÚ«£i¿k@âˆmrT’7ŽÇNÜæ£¨ñ¿„®,5H®¤³Aj³Bé“òȧ¡=¶‘Œæ¼—âO5ÝÁpøƒû¬vÖ±îàb¦S¸*†À8QüG¥v/âX¼]áhíVîâïUÐÚ8€*vç ÇnõßèÚ¿‹ü%¦ëº/‡®"ºƒWÓ~Ëz’*ܤJÙ;1Ê£àð:qmJæubå±üÙ~Þž-øñûÞ>ñ$^ñ=Åð°°¾—N¸šÞ(ÿãâfyQB;¦ÜD›þóxjøGö½øSâÏÙŸ]Ò Ò¼oñö_ñÔú=ÌÂ+Ecxö¼–,ï(‘ßlaÝœ4,¸úq^šŽ‡'gcî¯Eâø_Ážð·ˆ-tý8ÊnõI$f³ŸK…%ÛäȮ۠òJȤ|Ãȯsñ_Æ??|y£ü3.2ðޝ~ºíÖ¨ ê׬“,)mùBFˆ£æ\õàß³GÇ„ ¼yâ»_>²ñ>™gᛋkØe„Ë5£Þ nmÞ}Â\F¬€¶ï\ë_‹°ë>!Ñüeã¯5dž4ÔÓµ5k«”°‘¦ FÚÄõÒIQ¿s eù€9§Ê.dzæ‹mo5ž©ðÅ>¾ÑïÍýÍ–‰­` {4®GÚJÞþ^ŒŽA ×½Ùü ñ›uâÂ7–×ú^áägÖ´¨þÎ5IbY#‚ÐÈIU;Hã¯7x·âgÁ…  Ö¾è^!±ÓÆÍ#È1…ä8ÜrA9ÉüFøáâÿˆ^ ÔþÜκ=œ lÞ¶Ó0¤ðª¨F•ˆ'F À Õu/j÷wÞ6¶‘t¹--vÇ凄JXÈr]r@b8fÅqWŸ´޵k‹D¹Öî¥B£Ÿ.H‚ïv,róÈýÎFÑŽ(åì3øoãåÐ~6¤é¶ e WŠ®Öè5íµÛªíŽHFíŸ12ÁÜÉÏÂ/x»]Õµ-jÖ BïÁ:-ï‡4[hmËyÒKpñ‘ÉÚÆEÆpT’pxòŸ¿¶'įŒv:Šþ&]èþ#S0\Îú~˜4û‹ ‹6UH|ÐîdÞ Á†F>lVÇ´­GÇž5Ó|+kw†ôh§{‘s 4pÇ'™ö–pÀ\Ôg9=)4­xsBðW‡ôËO ØÝi–í4ÑÚG¡©/¨] ZâíÉlºyû#ãh ÁÅ}“w­ü4Ž{«ÿiñë“ÛiBcEL]¶D‘[¸?.æÁÈù²+àÏü[¿ý™´Ox?Nñ5¬—v—âH"XÃ[Ä¡¡…$™£_1Џ UŸp“¤ÿeø&¿íkûj*k^Ñ.< á‰É¸¼ñŠl¦³K–wË-•‹´7Sí‘Äq†RãÁŒÆQÃSu+JÈÚ…NI#ÜÿfÏ|øÅ¤j¾0øcâÃm0÷ñ ‹˜ïcPÊ|Õ‘¢.¼ SŽ0Ù$×Qð×à?íûe|BÕ4ƒ/c§\ø&óì÷¾(½Ô$³°1Iæ(Kxàä¼\¨`бD‚²R+õ‹à/ü'ö øQ«XxËâÖŸ{ñŸÆv;BjÞ4X¦´¶hÈe6ú|i¤{HY£y1Õ~[éðíôúÆ“£Gez–ÑY‰0m`ÿVŠ©„ع;@ ü×6ãztÜ–óoè}+M^ZŒ_¿à‡¾ ´†ÏRýµ¼NŸ®­iCÓ!¸Ót9L¬IûP–âi¯mŽËî§Œ~Õé¾²ðÖƒkàŸ Cg hÖ‘Zéöбà ÀDEU}”V^¡yi:DëqöIgr;‘ŸlqŸ ®9¼;ã!-õ[y7°ýÜñÀ'±ê°¯Ï±ùö/ù§-?ÛÃàáMhz”Ú]¡ž;é!;Ì™ˆ¥›'9,\VÁO¶;Kp @x,=kÌôí/OðÓ7ˆ|Ix×jƒvЬ¡ž¦pêàÖ/ ~϶ ~)kº„„4ZU¨ónä$p»Ý?ï~F¿¿jÏø*—Lj7rxàèøo¥]HöÏsh¹Öð'‘\&x\FŠAþ!_O“ð¦+Ö–G_'Ê~äþÐßµ?ìÑû$h‹¬|{ñU®‡&Ã$zTCíZµæ2vÇméXäë_Ï/í;ÿ¼øõñ Sÿ³žŸÿ ÓÃÚ”Ÿf·½o.ã]œâÉ ¨<"»¶¦¿3¼Tnt/ø¸Oƒ4¿O®G_ÚZ¼Ïw{ìk†¹g‘žBä }îp"¼?N°ñeçÃ8<­ÛÚK›ª.§kz¿%óÚ–ÿH‹ Ã(þyÇjýW)à¬6–UÚ>{šNjɉl-¼}©·Ž|g}=ö£¬]]Aw-ÔÆæöy¢SûÇ–L»óÈ[×z§Å„_üáÝàŸ™¬ßèèöºÎ‘­àͲýw´Û”|æ\‡7<Šùoâ_Ãχ:GˆÓÄ? µ6–ÒÒ{é­ÚO0É5ÛHíÐ`-°däŽpZgÅ{_k¾9‹ÆZŽé#Õím%ƒdK >Hlyxá€,9É9Ò¾ÆSVв<·7'v}ããï=·„ôÚ×I[o ´6úŽ¿¤Þ(ÔP¸W“Èš1$Að®Ìr§øqœyߎ~#|FÕ/u_ Yê£UŸÂVÒ\ÞÚÛ[F`BЫHÑ`ÔŒšøGBÔ¼Aa¯}«ÅLwze¼³ß-¢Må[,¬Á‚yʱ\äÕõo|qð¯á¿õH¾Üßê~0»³’ nˆ&UŽãÌE'f1Èç=kBoÐóø‹Å·ðéÚ£kš5¼6—2Æ6‰ °TiL@`-PT÷¯Gð…ïüC©ø—HšÊì}·OŽêÒûT‘sæ ®Dßu€ùFÐY~4Ð< n5HtýLXHë YDìW&m¾|J8O¿Ûjú¥×ˆeMgÃ^/YíƒÆßg6~Q··†é¼ùß~A^ï  È⸽cá‹[Æ·w’$—ð#œ\C•°åÖnsœãòÍ~›‹ƒS^j®¡tc¶„ØÁ ÈReŒ,í"%VÈ<žœ×šüMÖü'ð»OøsñV´»Õ¼=§ëÅ}.’ÞJ›ô1$Ž)f6î2‡¾)ó‡³}ÏŒt¯‡~8¿ð­ìVºƒ%Ä76·Ko ˆñ[•~@ã!Èê2*µ·…´+mfuÖæ}FÚÒòá®mb –P¡¶Ž@¯¨é_\|`øk„¾]øÛÃWvWM⻡4+ap«óR) uà‘ƒ\ÏÇo‡k3é7ž´‡ÂÚ»Dº² bÂîËH‚1•ß»ï€7qƒR¤O)çzýÅ>,‡ÂºdQÅcáÔUB×yФ9%»å¸õâ²|ðR;!¨ø'Ä“ÜéwúvµskxòÆeµò;Myƒœù¥‡ðœöÁ¯Uñ‡üOðíÇú]½¥åê473›,…{[lK#ÉS¯ÃtÍ{OÆøƒÊ³ñF¡h4KÏéVmÛ]éóK²0n3––$Èe%p'§Ì +ê~$Ö<ã»='âw‡­<76¿â4ßIa*O§ +«fGºó[jÈU.È60'Ò-øyü!ûÑÿSQžglÅ$lIÎ[ç=Ekh7šÅ/ °×õ…Óuåe–Õ¤Ùq"[²(œ‘²TV,ªxaÎkÂoõh|k¢7ÅÚqÑѵí'LkbD‘F’º¾ôe£![vyý«gÅÞñ_ƒ/aøÅ®´ cªë¢!k™Ö6µ”Åþß8Pé‘·­&®Õ<ðÏ€ôφú§ƒ|[â;ËZZ¤v³Ù†’;èvâ%”cU9$'5¹»³xôr·WÒÚË5í¼ë²Õí£H™ä£Ýs_4éß"èú”0éoaâ-vŠÖɼÛkx޲1í>ƒÓ“SÊ4mÅÿø"î ¤·°þØÃ]\Gr&ŽêÜർ­’T˜àZæþ*x§â¹ñGDø›sª¥ßˆ4M=£·û$KqD“Ôkí†ÇQÖ½†Ÿ³Åþ“§[ê2¦Ÿö—›-©ýÔñ &x|±‚r@9#ÚŸñM3Å~›Å‘Hú-ìw—6¾À*Ç`’>>IB™ è¸ëÅ"¹‘æ÷’Ç㻽Kâ>œ·Ó^DÒ›‡ìöºœ‘‘2•ÚdŒ†N0å=jÖ©ðÓÀ^.°Õ¥ø‡iwâ"½ÓFöµ¹¶»Ó¶+¼.¥vïV˜gîó‘]÷ˆ~!øCKøiwbuk+øµ&š7¾q¹Q¶dDV^¡ÀÆ9Á‡®ø¿HÖOðÛèW7÷ ¦@¯¼¦4Ž9ã|‘€ApÊUÏ¡ g|OðÿÁ»Û«ß‰ Öµ5–-4²ê^g™w/Éò©Þvc¶2¼WW¦xZMá…̺§o?ÄÍ7GM Û#ÝZ³€²Ê¼”šÕ€‘8rFáT~x_Ðî´­)´¿íAoy3@סK {ǦÕ݃ÏsYß |1âïx—J×[··½dº£.¼(Êüѱ#cŽõô§Šíôï ÚL|;om9†6C"Iå¤- ù‹È<† ©õ©|]ð«Ç:¿ˆô¯‰vzx°ðæ¿5½“^ùŸèâúHŒ¯Ž N£j›Ísð¯Åþ0ðæ®¶Ú%´z¶«meuyo-ÔÖí!÷î8€>ðûÃ8É®çá›hZŸ€¬üIñÆÖÚfŸ ÞËqe¥Ý°îîmöò+1ÿV˜øPqœÖ,Ÿüqà+»[MÔdÓ5ëÉÕtýnÝg&“K«€@‚é¹A•€*z×ðÇáw†¯5? |1ñæ¹c¨i¾ˆÚËm¶ýBÖEÀ+!`D…°w àŽ(+Ã{£øîÇÃ?´û_ßF“6¡¦2ŠI•[ì¤`,Ëó†8ù†3Ӧߌ¼Qu¡iŸ"ºƒÈ½»[‚¥7Ü>¡fàE¶qó.q¸éÎkãe·‰5ß‹—Z—ŠZ^hºüwZbC”bÊweˆu8ààŽk¾ðç†þ.x¿ÃT*Ñí5‹Ë‹; ª·â&9¯-¦C“lv¨`Ó BOºxgUv—bx‡IžÊâÕ#k™å{£÷Ç–8tÚvÉŒàû×®|(ñ³´ñÖ•6$:®žö­"eÛ ‚C#†~œžEaY|;ñ÷À=Ã>'ñˆ¾#¿Ó­›N"+’DS+ªgz‘Ž8¬mGáÅmAñ‡ƒo4[m/BÕîáÔ¾×!BÁ¢!„ˆPåC±¸½tÿ|q¤ø“ÁÚŸŽ´«û›kí[I¸YV(Œbëûà³Z±Ám¡gNÌWÔ ôOÅzOˆkš5Äžm¯ŠÚ0-n¬¶íjû€ ’Çxʆ8 :Ð2ßuh$´ñgƒuëm{ÃÑ\È—>X&õ.qû¶ ’)=ÆÒÁÎ+kÂéâ_ÝßXÚÚÚX.©–íy[§Y£ ûæb.TŒ'­tß>'xIì|¢è³é– 赋øOŸi4èLèR#óF̼vÁ1Í:çÆÖg‹O‡<%{gm«Iiý±klxûn•rKEqµ¿ŠùeÈ>Ì(§ø©ÿ ¯þÙüHÖ,-5 mOJ¿»`!r—kû–y?1ž§½|Í€nõ}gÖm’ßVÖò¥Æ 2¯‘Èã9SÞ»/j?|{gg®iR,?ÙQÅ}=¢s ¯# ÑAEÁ8 àž sºÆ¯á»‰´oŽûN…§XÉ%Ž·¤È|×[Ç»òƒ(ýÑ`<·Éz'Žt_h m¾ ë·:ŒžÖ®L-åW1Ï+‰« ²‰H\ž„ûš©gá¿‹~ ÒÞ_‚·’ê—V2Û=ô!@˜˜„ŒÇÐqéïS~Í^5ÖoüYâ_Þi#S×|Kö*ÆÚf>U¬I•™óCÔc#5ï:îâ/ˆ¿ì¾)ÝèÃFye6òɵ©œ’Œ»ùä+/Ë»©A®x¤6µ§ ‘it¶Ìg[ »†ù. c;XüǦyª~ðuÄ~?×üYâáoŸg,Ò%Ä’+¹o,‘ ù³œœŽ•éž>3ñÆË¦ð»ÛM¶[‰íæÌÑnΕf'î8®¦ïC‡PÖï> üFDÑu³pñZ¬b™YU% HøÝÆž:ÐÎÚ­¾‹â¥´Òn$¸±šºv•ÜeXÐç®OÒ½WC—g‚,´X¨ï­—û7Q´ ‘±s¾ Pœ©ºíÀɺ¿[xæþÃKðÖ›w§Ý<3Y(h',¡]>`zqÓÒ¹é/ü#á«-OK½±i|A©\%»4NV( )òÉŒm™_œuÇ=¨+ž ñ¿hÂßRº¹I¦›LÕ£Daqn,Ûj2mù•Õ,{Œ+ RÐõ?]\Üx«Å÷1ÅrE´zÚ U%FP%s†)fcéšõm?Å:¯‡•ð¯ÁÔõˆ~±ñT׈ᱷԴ{ˆÜ¥¾¡mdÛ®Ñ3¿Ë¸ØÊª€Ø'Ô*Ó|6¿<+£ëv:Ã.¤lͲïò—Ù't|psž ex7Ç:†äÓ5[ÿµj2˜î^å#)%£¨(]H*Üí$v5ô>—âKOˆÞñm§Œ®'»hiSZ‘l¼€Ÿ*uØ,x du –Š~Áï¡ßx[ÄšbE=ÁhôÇ’ ²5ÓIÂ3©ËPzןøí¼uàox5Ë™¼5“ I¡_“,qÜÅþ°Fy1²Œ28êzW%câ½JM.;ï\®¤ÐœFóm‚ê)”€Êvà18ï^¯ãËÍ{Pñv›ã=BHîæ¶´“O¸{…%ošéƒD]ùÐ.ÕÏ|ƒÚ$eü>ø³©kŸõŸÝYÞÇ î³[kP¶ëc:ü—W ~o*UÃ,€|²88®oM¶øw7ˆ­„–-§j‘ƒ‘·«}äb n óÎ9Òêú7ˆæb#™"‘”nL 28½>•>.ñ_À&ëB]¥‹Vó-ícÔaØ…gÆLRr7FNQ€8éSx+CÑp$ù¸ùJŸ¦T×+áýkÂ_4«Æð;´wºMAlå‡eÛÛ·&3À’~eÉt­ÉuφÞ/ÑõOøGMþÊñCÉpšÕ¬p¼rBç¼d ÊÜ?© R{ ©õf…û~Ñÿ|/g{ð÷A½Ðìæ5 *öñ‘K¬¹‘L;rÆ3Û*¸î~!èZ¶»§ü_?¶âHln?z¾T¦0Wd)VRç¯<×ì/€?à qê>ðÙ³ÒuÖ´­*ÎÒ++!Ù®'‰K$ÄŒ&m¬^˜¯Èßü7×4ßøªÜC-·‰õ«`\ ÊðÉ#ùλO*C±ÀèÕ•)¶ÝË©n‡”øjËXŸK»ÒµÛTMìî÷‘ <Ô¶pxe`w$g‘Šõt-3Aðè±ðö‘±¢xr#}› —¹W}Û¼¢Ü”pÄ€; ðŸ[ëšbË­ø/[º»¹“)©ÚÉj®›\7)ÆF1ÈèkÊ> øÄú¨ê~<ž_íËME4ý7ÈýÙ’ù£ ƒ³%[<â¶!3×ô}GÂÚe§‰åŽ?KÔ¤’'ysZL¿1‰±ÎF>\ã¥AeâÏ|M¼ƒÀúý¾“u$« Iwˆí¤’ˆ‹.1‰}ø»oˆ_|"<5¨Ef–7¾Ÿ 4Ú}Ô@%ëqÃŒH;f°tŸ†þño„,õÕ¶ú¤Š~Ñi3qöK³”^ÃF}Gn´ åDÞ+ðv‡yâm,i‰oá‹•]¼Þnô•ÊÌ‚Hîq^‘âõ aMRæÝm$),f, ¸Ãø1Ô׉øwáwˆ<«}«I¹ûR[HââcÊ ïÜFó¯|ðœw>8Òï|‰¬VVNö—n»Œr¹FFY¢qòœr½¨Ä.üAð¯âŸwðËÄw‚Ææm·–ét DÍm(’)QÈBºò3šïlï4K¸5=kJ6VQCm -&,ƒå¶ríÇZñ SÀú·‡®._Æ^Ÿ-Þ Âð üÂÑÌw+Áž¤wÎ é^ðÓD½µøw¯[kVrÀ–Ë$Œ4soå#eä‚ ãozÛÑôï üGµ±Ñ<ã´Oi÷Τk14jVm¾UͤÛXIrŠsƒž~⟃¾-|+ø‰q¤|G…´ïA/úlcZ̳mh§…—ƒ#8ê<ŒU;Âw·:fáÏ „¹s+·k©ÉâLp@ê§‘ŒWÔ:'…u]oÁ¶ÞÖ¦¹ÕdÒäž{³ní£ÙnL¤–€ÿ ä…àŒ`S[-.|k/‹>-ëvPÇt?³¬®õ3 m2ŠáãRâ/3fáʶzW§Úø‚ÞÛT†öòÙ¬ØÜ¼rå\Fïáœu«^.øñÂÚž— Éá­'ÄÚ^­möØs€¢Hx`F0³Fy$Ç«ºu÷‰|%w¨|^¾ÒÞå.m¥/mñ[Ï:íPË×a\ŒôÉ«"G…xSY–{(4×)¦ÛO$×Q;‰*?Õ“Ô³þX®—^¹·Õínµû;o-5k2‘ÆÄ·™~R¸{9Ïê+°¾ý_øUuã»)#µ·žâÞÒ=1%ÿNFrä=NìgÓš¡*ÙIö±¥KcwàéšÝ²­rW—üÃàÐIçö›ð²Üxb}d‘y}]ElšÚh¾XËz«uÜ:ô¯D𖽬6­;èv‰>klf·š?”¤Ÿu¶‚0C½Nk¦ðÃÿ|Y×®µo‡2YÜë6Vr˪ÆaÇ ÎrÄž=ûÖœºž¥à=>Ïú‡‰Óc1]i·±”ž9e¸,_;‘²xàPŸøºãÄ—:‡nuá¥fšÐÆw<î*Km8R§§º½gUÖt‹i|.&‘nOk»q:”ó6ƒœH½1Ü7Zò}*ÈM{'‡%››fHmå;$(ì)ýÓ×¾+×u‹_Á¨Éi¤Ú\×ì–¬Óé Ât`ü­žæ‚“8mWÒcð{FÍ5ÕΫd™WöùŸx:Žùô®f)"Ó5IfðÌí XðËl¸V ¿0*x²=*¬rAà >ËW³ºrÚZ<¬ðÆ@ˆ9í(ý+×N©hc'å‚bG–ÃûèÜŽ+²Ð&±ÐõK/Æk*Ñyð·Þ|óÎÐ:ǧҼ·ÂÚ‚i¾ ‹XºQ«é ep®|ÑÉâ\ô5WÇö¾;Ôîî¥ÓaÅÝ´Ê-'~±îýâmÇ}:ñM"dz}æ§a«ÜG ëš]¾‰ÂÓ®í#`wÈÛ‰uÇ÷ רëñ¬z…Ä–‚+MY–÷ˆ!G~q»–ê1ß5Í‹_‰­¯é^Ôõ]FÍãš8Ýnï¼Ù\Tãלv¯Iñ>»g®ê:Ž«©éI£ÆÐ¬qÄœ~óÌÇÊ}A$sœPÑ$š7†¯.ü-oªZZÛÞIIÞf¡ ƒ´÷Ç ©thæ›T!{ùJµµ£È@2ÆyŒÎëž•Î\xÆÚçÃZ>‘e<d.ô®g¶˜È¾lbI àð¥²6ÿz´î´K}]м™å¿¿„†“Ì‘žÜ¡l`’AÀä÷é^Á¤Üê^ð†¡­x.dÕ´_Ë-“ékûÛA:ÿ¬Þ ’.03ŽœPá[Ÿ_ßIá?Šk?„Úø‘g­Ûskg3|Ѥñ/XX¬GÝûÙÅhxÓX¿×ÛOÐ5#e¨ØÛ‹Y/!!Za%d\ ŽU#¡¤ƒÃÖþ,– øÙ¢Ó5 >q$²Àe‰†v?'p¦:W«iz¯ÚCᛟ´,Rd(f3ß§ò XÒt¯Ùü2›ÅÞ:ðüSøªÚÿìpj:\¬[ÊÛò4‰÷U”Ÿ›#殯ľ)øK®|оhÚEŒ^&¼¼ºš]Acòæ‘$Ì¡e^>î6Šðkzݯ‹¯4®R MOs…‘²Ÿ”dðéî+Ò´Y¬tÏ _ø§E¸¶:ôM-³Ìø‘YrP€¤uÛš«è?üD¿Ö|£Yj;É/†íÂÖùb+4}ÿ–Ýrk[Æ~.ø‡ñ®Ò SÀž·Ñ´Vm{`bgPF’"ÐO'ƒ“^yáí{Æšœöw‘”Ô7h®"*„)Èlà‚Oפßx{âÖ‰«Xkš Òÿj#Nm td¶îŒ‡8#ŒT_Ñüoð“Gе©æÓµ¤»>SÿcfGIqò¬Ñã Ê9ëóY7õßøä\H\ÙB·(³Äq"§V+Ôä“È=j…üy«ø7Ä’]Ãnm~Ønm$U®`¾£“Ò½ßMñ7Œm¯ÇŽš ãhÝld™pðÅ';LdƒÓ¨Êõ¿ˆ^Ö¼?/Œµ%—°É$ÐÅ$˜gel¹†A‚Gu^ºoüZñ'ˆ4 nlÅ嬪Š#iâ2prey¡Á¯Sø/ðƒFø«¨Cð«Uº³Óõ;IH!š-ââ0¤²¨êr8 Nq]7ÂýCøiãïG­\éútçÓ-ÓÎ0Ç@b#qbrÑÓŠ| |ÒÚÿŠ|g©]ë>4Ô’Þæ±þú%6Ò¤€“‡=G úW®é^/ð•ðÕü;kouÕÖ<Ñåy°­Àî˜?"Ÿî㎼צx Á¿~'¦¥¬ëÑ]Ot1Ü:ȤÆeŒ7Aœ{W!¬iþ2ðç‹ÓÅ¥ŽŸ£Göw›æòÛaûíÆNF0ݨhi‰ñ ƾð_Ãß ‹øRëR¾&­Â“ÊÝÎAüÇ'Ú¸_jþÕ/ ·‡did1yÞ^—jJ¸ÏOðõ5GP°Ñµ >;‹¹ã‹QŽF¹¶¹f)bR Ÿû¤õ¯EñVð«Åž¶’+-|e;¹m^Á6Cr¬0¾w;I#ÝMKØ|¬õÅÒcÓ´Ýt(d¹Ž{›@2ù“x3ëëÅy.µ¦øÇÂö³G¤éÚ…–Ÿ¨\¤^f™'J£¤°¹vÈ8+Å>3ø­«xÊ[MOMŽó@O³Â©ûK˜r3dqÓIàŒz–“§Ïâ·˜ÚxÆ tk¨­ñ7Ù9É/•ããÅ@r³·ðôú¾¤óøcÆ:’ÜéRy‘°©$F1À ÀÉ'¡x®3Áþ¿Ð¾$Iccw¥ÜMo ž.q#nqÎ85CÄþ1Ò¾-x^ïÄþ[ìírã7³Ç•yav«óÇ:¯Ê0Èaòžµƒ/ümávû,úÓZDc&\¼¡e!Ü9öpP3º'P ÿ×ß“â^‹s¢ÇðÓÂ÷:D× ûåK+Vkôýéç9ïÇn+Bâk{mµ+•KˠѺÇ&À¾Û¹É>ØÅi6ká«[›©ôᥙ”Jí yJàŒu<޵‰>›á?›MCO¼7±ÆY¦Œ/tÏ^z“_—ŸpjGá»ÝìÖžK[(Ôï¹Sos»¯\äâ¯=Æ·&±ýk¢\¹O¹{åí…CãØô¬ox>3¢GL–í>Ry˜ðá·ž «%ÃÊ]]Táü¸ìI&¨Ð—@ŸSÖoUº\iIÌòyë ±>ñWínåõ Ü£Hå¤E;ÃÊz¬q¨Â¢ö¼×Lþ°ӦƒOai#þñÚèÒz/¨§XhšVeny³¨¯ M–É倇 ½Œä ô'¿Z¢VÅä¶Õ¦ÚÙêA`Þ¥Õdg¹UÀɨ®µïh²Ís©Ãæí—kÍ !\ôéÛ³§Õl&½M¸’Sy,a£!4=î2~‚½Á^“¤·Û|/(ÉæMu|ÆvfT#7@½ kÖðÁ'‡U,mäÜï4ŠÜŒºÌrO|Tšʵ¤öþ(…žÌrÔ¹ù¹q·'žù­Ÿß[隟rN9#¸â™á»Çøn÷’ø§g}ÃmÜ*ÄÜ’ª2r}OÖ€0¤ÒôË(ßSÒIh²bge%sß§SìkN‘4ùàÒ¡%®\•°LŽä‘…ïŽ+£—U¶ñ °iz‰D±Œ™\;Ãçnþ- ÅÈÎ*Ýõ‚Ø$WJIwg4l$û PYGL¹è8úÐ ¬é—׺’ëzúCv–ù— &âO©, ‰®î/u CN¼Ôôû`|Ä‹r¨väÎ=8â½y4½+G‚ú 5¿|Ÿµ`œmà(-ót=ëgÁ÷~$k9-'²io­œ»Äòù[EùUW7¹éÒ€9­:ÃĶkª¿ö¸àtúÖÞ‹£ki÷ľy¿X?Ñ4ûG,ZBß1•‡#–ÇJì4²ð–«ªxrÇX’S0û<³Ù¸(ðvÉ·æÏ­oÛø~ú(¢Ñô#Hì]žæc¹¨ÆÅÉçÏ'8¯´Öt_Y.“¦‡†êæApòE_Þ“Œy†? »«øÛC¼ ^OqrÊdšWq˜Ñ …Tãh üÌÆ€:­Å×.³öMyÞAmâ8Á2`üÝ}IÀ­ýc\Ô­nŸÄ1Ãg¾âEqóʳ³ëQÐWÏwü+m–:n±kw! åíŸä.Ã%D‹ÉUàÜ×¥ø‹G‡TŽ[Sa)åa¥;¾÷ÈFI,h.È÷ÿøÃVñ6™—u*;é¯ç¬Vðå%vᤔ6NUÏ=k\xÇ:dž®/î>Úšk0âmó]GÞd ü ñÞ¼‰5Í*{£?ƒá¹£uˆµÎäyGÞe'9€=*yåø˜/ ©Úm½d,ùR±ãb§ G¦j[ #«ð6…4Vwžøs$öúgÏ5ì÷ry¥+–ÚHÆà8ÀàgšîN‹âßxgJ—ÂÚgÙaÔËšàûÙ›9g*ª;ž®WÅ_ð´¿á²óµtût0YZÙ@ÏÜy‘É I'‘ÓŽµÆxsÁ2]Ø%–·¨jºÝ²ÿ§Ï$òGm»ï´P(Ædà¶ éTˆgo¨jº3ÁäCªÃ§A¥È,îLò´žt‹ób5 ëÓ­kˆu÷ƒ­ntk™®ô‹k¥†g‚,Á²ÎIÀ ¯ê KdŽÂ? @Eð‰lónµ‰û®@ÝŸ¼ÍÏ븛Ã:Í••ÒØk6—6Ù!¡¤œÇÊ2xjdSEµñÅÝý¬v7[YZGäÌìew%ˆSÏ@õï\ö!µ™a´Ò‰´´]îïq¸½IgŽ™$qÖ¶µOë¿õ›_ ëúµŸQX¥»Pw¤6ܳ3=~Sò޽+¢×umMu½ð’k÷¬’¢ÆéRpVˆõàn¥pfEŽ¿w{¥ ôM?1²~ô\.U‰<•U$àרÁ(x"ÃLœ-ÃÇo¯Ndò¤à«¶ÁqŽœWÓßðToçá·üsŸµ K=Ì_ü5„‘È@ŠÚãA’R¨§ýìweñ“’ŒF0sÌÛøWI¼[Éï—Ë2ÆÙ“® ôàñÇcU˜Ç–£w ïMCñâ_íñáÄ ŸÂK¦Gâáow©hW¤¨³JC8eYõÎA_—œâû:þÊV~·Æ‡ޝª“êsɸËvÀ´ÍƒåªªáP¸ƒMz’âÓL6 <²Æ>XãfÉ“<…îN:×Ð^ðÔº‡¡Ð£aDÒ¹žFË‹ŽAãé^\¤v+šš?‚î|7â9¼I>·pJ…µ°e0@z1@>w8ä“…ÉÏ8«Öw~ñ¿uiã?]eM!º»’Yžh¡|¨<…*»óÐcç­eiš}ψn$“÷³\Yë¸.Ù^bÚž¤â¹«ÿìý5ÿ°´¿ ­ô–®“^ß™ˆO´Iƒ‚~lÏAבÅgsH«£Ùu¯ èœ5?—ŠÒ6š²Ä¿Âà‘ŒœžF9«bÓàïŠô}gT³†çÌŹŠEÈeàp8ê}¾µÇê¾PÑm¼]©ê‘jê< oEO”ÆÝÏ$öé^ïáh¿„üÿ øm$¶FÅ,b³3HT3K+à㲨'´³æ'øgâí.îâ8ud»¶•šm¹bº“ž9ϦkÎôI=^ó@–7¶UšS&>q!'8=OO¾­ñg‰4°6>HÒâ%³ÀêÈ!”œ`uSÆ:f¾Lø±á»ZMn?6ÌaZP¼4‘¨sÝ€5Q+nxÆ-28ômj憆¸Ï`1»ëÆ+Ì|w%ï†t»Ý>P (ò²pX‘œ…=Ií_Jj—Vž$Òmœ+ùínÉ42¨ÇOÔ“_xbÊïMñ«ðöæw‰ì¥Cå‘UFTGÓs‘[Åh×>•ÔìTóm¡–YVIgHäòòãŸÈwÇzùËúf¥.œòxr5I Û¼£Ëý÷–í»pv@àb½ª{m+OŽÜZ–ˆÆ›žel†8É=3Ôú׌xŸP¼Oox’âp7ùpAm˜€È¹#åì00jÉlöH4vââñð]]@Ã8eè¤ó½ý}+Ñ´Öã³½Ót«xU§r’É9Ê*¸ÆW¦[ýkå=yšÎ4dÒÈì±ÙƒŒ–b3‚G9ö¯ ´k.Øô¸. †ñ"òY˜bxc× ÿ]FnÇ«¿‘b£TÒåIâ†0ŒÎ7aÉ p:7áÅzNÚŒºÅ£ËrÜiI¨Ìƒ8N"Œ~¸¯Ÿ¯çÔ­n­tëV· ÑÄŠ„Ž Fwg£ü]«ÞZ{Mr=>;@šQAjÚƒ>áæ?Ìy<í2zž•”ŠçgG¥iz•®˜—¾0ÐÍôºÉ’äÁ¸ìa),QTtÀÇ^+sXð(ÑÕÊi²YI¨ BÿP·ó'ÍÌ6;Æ` ½ÑÛýœãúÓüC'ˆuf°Õ´½Qlc–YîL(Ûb6ÌÝÉ=O•- êt7ðbš÷´Iã³Óí<\Ï3I-Ýä¿Æ™f èp=«§ÕµûøGLÒõkxµ4n·Àkc9îüs8í^aâ? þM¤é×Ïp—ò&ÃÈ…'*eÚ9a#Šõ x†çÄzž» óéº-Œ¶¤–p &Â~ƒq$ç¸5m5mÃúެžÓûKĂ٭VÚ#4Â!V)AØJsŽkÌtËY>Z˜ôî¢ÿKkL¤n˶A—ßÁíùW³hþ0—IñD~;ðD³=½­°3I‡aƒÇ n½/ÄÍ Ä~Y5Í W¼Šß]‰ÖêÞP›]fçp'’FHö¦€ä4ûm&hµ];íB="Êú#f'fY'A.Pö\±#5øgÿ1ýœ~$j¾»ñ¯Ã­;û^óÂ&êK–Y&îÖô§’]ÉÞ'Ο”ôÀæ¿p¼:ž“G¾ÑüOgqv·O¶óÛ¸.b]ª®XçÇLñÅ|‘ûcÍãß|,’óÞ¹ñj+š²^pvÜH‘¯úÉcŒ–DçõÙ†“ç9+C›cùøÿ™¨|u¼øû(èvzŸ†4ètèçÖo¤xUuc—q Fà3˜8 ŽX–'¹_‚ÿ³ä?¼Iwgy{dmlo…î«¢Ç5ÇÚõˆRwŠ+xi£ +µŽ{dS|e?À­SâçÄ?~Ξ"Ö$ÐlbƒRÑUf7’¨7q˜æPذ|ÀwmèµöÂÙâÓ¿ <#ûTü×¼7ðãÅ+ ¶ײÉ,ú¥¶§dšÿÉeU ü«+JC#tçêéÎêÇÏÖ‡,™­áŸhøƒ_ðy¤j¿t)#®›q-‡ Óá+ö{òcŠX¤\ $› Žß&í­'‡öoøÿáïk^ Ð-gÔ—F¶Ó<÷¼¹¤ÓïD )…Pœ³G^Muß >?ÁBüuãÛøSW:¯Ä]WÃÖzô÷Em§M¤i²Ë%”÷6r’Ü8&?Ÿtî>ûø—{ûüÑüñkL’%ñ=ÍõÍÆ¢!»y%Ô§³Ž,n•™™¢ó~O˜î5©‘ùÅ}ðkâ·ÂûÄñÖ­¡ÜÅk¹±¿Û*Þ¸”âu_&=À«îùˆ'iÀë¿ð¢h’xO^ñΡªØØxŽúS{göu’æÚ×kÊ¢ ÊĨQ¿Ÿjý"¶ñÖ…ã›¥ØØÞYO>¥sxlÀ}¶øÛ¹@|‹xqºO˜– sŠù_âÏ„| §kÓøWŸT»Ôt–ÓåšKWß3B»Û{†rüŽ£·ZÎS±J-«œç‡þ~Ì·ž(ÕôÍG_×,|4ñìµ›+µ_nÜÈå yqƒ— NFkÇ,þ%Ý +OѼFðÉqdÂæ AaÚàE¸ÙpT I%‡AÅ{§…m~Á¨ÛøCáæŸ«ê:…õ÷Úl|?£ÚM¨ß;ºyeÄQ+VbM+$`œ³(æ¿Hgßø ÏíCñz(|WñçZ±ø?¤^É%ÃX˜bÖüM$S|ÀK–6Ž­œ"‹Üœñ\Xœ× ‡‹u¦‘½*'ð£ðN›Z>5¶ñ„ìRÆöîæ&KxÔgº‘±¹b“‹g`l·½}ÇðKöý«?i/Óþ«95h¡ºÔn4ùµsÌà–òb1!XÃn%åQ€F1_×_ìÉÿ»ý…?d^ÓÆ? üe¨xÆÕ ¯Š¼DßÚzñbÅ£’mËÌNEQ€WÛ^'Ô|U¤iWòø*+MKS–ŠÞy>ÉÓõI€G¶Ÿzø Ûáá„_3ÚÂå7’ç?6ÿe_ø%Ÿì=ûx’µ¥ç> ´&%ñ'‰‚^Ï òìv‘"ÚYÆÝÄ1)`âHÍ~Œx¢-GÄúlré×R¤±‘,l}1€x®wᎭñÄš ZŸÅ]ÇÃÚÜŒË=µ•âê1§¡YBFyŠŠïô½>}.w[›µ0¸"4dÃŒõäŸÈWæ9ŽsŠÅɺÓlöéaáKáE}6ùbY¹WXT)å-´rßµqºÆŸyª {+[Ÿ"Fý¡‘„ öÉôÎFOº]>×O×£¹œã=Çîç©üs]™ªk7×réšœ- rÍS=1^?±svFî|ŒùkǺžñDÐnK…½O9啷०æ çšöŸ è$¿¹‹Y¼au‹r+¶AnØ#§µæÿ´‡íû3~ÏÖ1Ú|}ñŽ—áèî }škÊ’^Lqû± º†}å¸^=+ñsâ‡ü÷CÐô»ÿ|3Ñ|Em{©ÛÝA¥^Ëp±Çi4 å«\À…ŽÉÃoˆÄ€¿62 û£…±ºoݱ†#0§]½OÜ?‹ß~ üÐßÄÿu¸lb^ H³-ÔíÙ …rìxë·ø9ñÛþ %ñ{ã®|û1i’x7K˜9[ë’>ßñg„á·Ð®ŒZ•ÈŸg”€Ir9&î­ƒ¼ç>•úNGÁ8|*R¬“‘óX¬ÑÔøv=s[ñ†¥ã=Jî©kšµÍ¼“ÝÜ]¸V†RáFÝ¥™Ì£'víÜg¥y•ŸŠ¯äÿ„ÓÃ0=ªxŠÖÒeÐõ;‡ ÚSºŒÞl vÿO~¹©ÿgOü-ÒCü8ñçÃ]ÆwšœÒÃáÿßI=—ˆ4Ë÷‘­ù^qû³µ\¹°vž#Ã~øk©x¦ÂÓö7º•¾œ-ÿ¶î-$öìêReMAi%‡]£<_gK ZAXó§QËsÅu[M}l¡ÕmuHoôE@önè‚F|…ÀÉgn¼úäñŠê¼)áý7Å'ñŸˆ,n,ôýZo£Í;2ywÁ Â3*LžyÛß“]÷†þh±~Ðv:×ôµðŒ—RÃcwbŽG†I»/ÚD{bÁÃHÄÛ£ñÀñø[\Ôî¯DŒ^ÚhqB#Ó-à”¶éÔðåælÃ|¸äWAÎy/Œ´¯‹~ ð¶âÿé¶ö) ]‘”náhþRáG; èøÆ+/E½ñ„mâO‰ }¤CnEÇ,Ð(SåzmPårzß½–ÒÛÅÖöƒñ ã\w7ÚvŽŸØóy¨¯+Y_BU*î PípXä‘Ò›á­"çÃWš•ÇÃMX\ÙǦ[D©0’æîÙç2ùxÁ~θÓ­x熼3q¦H¢8ާe{—3fFB¸‰s…žGozú“@øsûCø£P<Öú xˆ}šK…ANMíËÖ=ËÛ«æ¼BÁ%Ñõfð¶³vÚ]„Oo$Ò$@Oå#"1þö:c"¾Íø;¯Ý]êךoÅ_[iºtÂg³ŠËí&º•¼¬åÌ ~ó2E&†·ÁÞ𵎔úOŠ|;ki®\³7Ö—íì'w ³äT¯¡ö¯ ôi~×u¿‰ZĪZjš$Z]” •µ‚âй”9;gb§ŽAx‡†¼gðÓIñe¶»ð×JžAt·Cí—RŸ2b&1„Ha¤eÉÈ=ó^€%ñ>µá{‡^$ÖWFÑ,®.õMJæ‘õ–Ù2Ð9 ¨JäY³À5”‹/]|:±Ñµ»/ë6i­h:zîÒ¦¶œ%Ä2Ì2RHŽ>Ts„9¯>Ö4ï‡^ð>“eâ3woâë­Yd¾°ˆ}¢#µ™MÈ)*¦»£ÆiîsWõ/xVõl¼7àm>ñïõ5‹ìŒÑ’&Úûâv\ä £qÎz[>øyñÃâ‡ˆáµøU¥H5Ki亿¼r¶Ñ©@[k<Œqæ*’€}áÅHÑí´×€´ø‹OðoÁk=(Btˆ5v®X¼òÆãËc!ܼ`íQÂä;UË]1[EÕ|YâÕ/5ýV[MB;+‹‡3Z[y@<*á¹,ðnh ïüYàCIñ†‘pošén‘ Aö‡HSý!b 9 8<÷úU;ãôºÇÅh5 ÏLtËók©K§Ýb JòÏIÝ w$!Ul8*<Š )Sk¢M¤Íc¤£C#—ÖèNL@a$*¸ëŒŽh:}+ÆÖ·iáω…u­´ùn`Õ´4â&%¶'%"_¹ÎzãŠù¢Óâ…üGãм$Óê–W24Z„…—ÊŠuæ=¬)aó0ãšúóö}ÿ…i·‚Ó\¹žÖÍn®ÛìgÌ·BŠaù¡ •,ŒA"¼;ÃñüðÂøw÷LÏ­Eá§¼[ɤ2A5í¹!#˜),L ÄrN*S3¤jvZ]®¥§èñµ¤ó]†G`cÉ ¸†uÂÿ…a[kÞ%ñO‰4Ëïìö²Ö­ÒÎÚÞâRñ*ÙndÉ.12œ½G «<ºñ³'…õÍÎ kD}Y?µ+£)‰|ÄG1\ã¼×}â«K/X·ŠÏRº›JŠÅÁÔ˜-ØUd# öUcÁnGâ¨ñ÷‚ôhº×þÆ÷6÷+Úüû“$öI3:¸c‚.BžÇ¥wÿ¼a©xÓáþ¡nÑ6Ÿ¥O5£ÛÇæChº„ûÓÍnv«ò¨5Ã|=ðf³âÿˆš<+<ÞGäíÞmÙn",²nþìê´“ÜT^Õ5½ßÅ7¾!²ûo‡õŸ OœRCØÇk/•+²wòÀ]Š9ÎhCð¿áݰOøcSÔ|JloÛJÖ ›ýU¬¬¢–î,Xg*zÖ]ÆŸâO xo@²ðö¢×^±ÔdÂBáÖòþóYÕ[$ìr\^åá?Œ¯§|G“KšÕ"Oi0êpÌ‘yQ=ò‡l¤à/\/ªšð;Ÿ‡~(ðÒŸiè¹Ò®µ5¸ž8!ó`³yG—+EƒómæSŒ‘@'í ÜY|8·ø«áMAlüCoky¤–·Œµ»ß]mœ<è¹Ú@«äm'9"¸m'MñßÁëHo­#¶›ÃÞ,œ ´½:=÷:mà…]3tHV…¶Ÿ—h*Ç=½›GðµÃ_Žöÿ5 gDÖµ;{]ZòÙf¿ˆÅo('€¡Ú5’<ü¿6OJê5M'á/„õû†7·Ú¥æ¨ú=ÄÞ ÓÔV×PÑèî-€ó"w~ïæÀÇ=EcþÏ:¾©â¹õŸx‘ÙÚBºrØÝ R³ÊûÖN@*G#œŽ3Xž?º½Ö®ô_ é]oBvš† ¡@‚Ì­ a‘SçW?7?/Z„u¿|fM'Xøcª\j:¬Ñ]Î×ÖîM¾'V(yl·®9©®¾=ø7Äz犼Wâ‹3ûM‹[\«ylÉpÂgNèÒ}Tõ⥠=GMÐ4/øWHð¾•m¶–ÐÜÞi;¶;9 Ì3œ³nÈÈÝŠ¥©ên“y}ã-ã¼ÕôIc·K’v%ì·Ĥxéµ˵zâ“P´ñm·ÄŸì›4‹iàK f_:ÞC(`Zí_Cê0+µñ¯‚ìü/àmCÅ«©iwl–ñÇbç„0f°I8éõ‘Üó߆ø›¯üQÒoõ#jš™²þÕ˜\Ý-²<6èE ä#å£c½yÞ™hŸ¾7€¼wo-Ÿ†µ»Û“w¯ÛH ¶ æm4O ÛÕ‘;m'­zσî?áoE>ñJþÏÔ´o¬xvëO+pfþ »k >A¸R™ ’H¨ÎƱàyl|S¯CvobñÚj0Z¢ìE=ÕâdÆ#]­Ú€lù§áOÂýOÃ>2K¿éÏ%…ªJ,nã_)&q‰C¶I`¬øá‡ Vµ×ÿ|FÕüðƒRÖΛ!¿“[ñ–€o>+yä à†rGn=kÜßÁiâ)ôëh/â“S¹¸¶„Ë.&…V6%û  Of¾z·øk«ø£Ãšg…¼.ÝSkYä©p¡åi;4Sm!»¡ã¥:oë>5ð>‹câQm.»£øjGÑüAs¨]yÐÙMp6ÚF‹¨1Øq_NZxJçÄÞ Óü+ðÚæÂ'E7w£P\3IwržÚ5?òÁsópI⼓]ð4¼Iaàï†Ú¤ëž!µ7ºõ­×Ϧê<ÄéÆÃc‘øW¿5Yÿ p––¾…ŒÒ¬p°Šícù™á”r @v^*þ•û(áü[ñ¥ÅÜš½½žŽ•µŠ;wÜé*Œò1ŽNä…5ÏxDøMã ZþK6´·ÑaŒizuœ ý¯U«‰ ãiVÎÖàV¾±ðçÄ/ćÖ-ã…a¹[=rÛKŸ3C6•© `Ñ©àMÇà d`õÍu¾¿ðŽ—¬]'ÃK»]Z4·»Im®-Õ§ŠþbªÝ²p£9ÅÄùÛÅÚφ¼1¤k:f–×°%úØ^Ee'”ÏlCn 2€pI5ËYiŸðšx-1ð×u„·²Ö/‚[[ÚÀ$òýŠ&óþR;7åHägšúC[ñ««|4ðÅω‘>™yãYN•­KC¬éêl3å‰Q$|¤@æ°u[‚z¥æ«ãýoXЭ/å“Hx#‡ÏH–Û~& !eG=I8ÛŽ˜¯œ´7Ե߈z¿ö×>™áùnu+϶ï{"c?$(¤yh  ªã¯Üx“ÄÞÑF¯ãCiwoâk+=’ÊÎÑGöµË9àí‘r7{qÍßSÒ< ãiÿ¼ âËÛjÖÞ8¶Ž)âŽ"“Xk‘|‹Îv1‘HÝŽ{gÇ|wñ›Ã?þ#øf(íôí?Lº’è›ÇbóEx<Á¼çcnC±ÔŒô=MW‚ëÆ_ ìí5›=.8ôß Ý›òÑ…VèÀЬrFÝcpùà ©R qúT^)ÒüRß|S«Ée§ê––pÜ,rý¦)„å`Äð¶ ‡'r0qšÙ›màM †šF—áÛ‹»]ZÚòå’}ì’iItÁä†' ½Im¬¤d8¥¸ø]ã -7^“QÒfñ5€ÓÕ®§rë4gÎUl`³$Ž:íÈÅv~<×.~jZºÿhØ3]}ŠóM°H’EÓ¥rÂíìñŸ˜“‘Î1^»m¬ÅsñJ_Š: ¥Ï…¼%ż¶3È\Þ½Ô '’6 IAã¦1A<Ìò‹o‰6½ðÿ_–8,ô¯ëOcu •¢8ŠXãTÅÄ$Œ"îF\òG¥p¾#øyá âÃâÍdé1ë:4ÉáéY¿}¥\£®`F'¤˜7+Ó<7âü7öÅëX —Jµ‰ îžæA,äG Q´ykÁ= gkëkñÆž‡ámÕ§ˆ¼I&©-¬–å*eß$Å2ÛµJ?1ÁëAgðVóQðOÄ}_áÅg»Ömµ hµ,ÂÂÒÆÇoÚxûì^A”'$tk×nukßMq:êún—®$–k‡Ú,䉯jän†›ñGðw…tÏXZÙø¢}Fk9¥ÊÞ¬›TáVL¤˜^¸®?SÔ>'ü@ñ†—ð»ÅúÓx7ZÒa¼ÔgÖ#–o-¬YcqÉc´7`ÀôîÒkþ$ð¾-ѼUð—Éá BBãì÷¶roK»iá`ÑO6 þ z“…­O|eÖ'ø?q¥ÚÝÿdÊ&Ž+ý$ÆÄL±Œ…„‘ R$QÕÇ8ÍyÇÁÿ‡BEðÇ„|au%Åÿ‹n µÎ–ûÒÝeVeòPãæb-ÔñŠõ¿h«ëW«áF[ÝN ­­!–Hö¨ŠØ›q+ÓELr:@‡ñ~mSJƒ@ðN—as5æŠÑÇt5­Á¸…ƒÜDX.<Ç$\“Æ@®GOñ¯‚>!ÍsyãÍëLšÐìu m2mH³Ã†;†XÜw¾4>2¼†?ü5†ÒKIìÿµ5°_7nµ¦<,Ç(.T‚Àt÷¯‘o>%øj÷â!‡â.‡}wköm9î5 Gú%Õ΢Z&ŠBõšP« !†O`캶»âO x‚?³K혉¡d“[hŠ–&&k” `‘’+Hiôkž ´†[Ée¸ ³óeWÌS’1ÎvŒg¸5ëšï| ãÝJ_†þðtZ…–FLwW/ÝÉ—~2DˆCtàðq^§ê7ø4:Ÿ5Oí+á/‘}¢ßbX¦µ¶&T ]&‹i/ôë@ àôÓt]jÇã_Åû{Í6ÂÆ+‹;È4t{©¤òż•¾ÃÀ àñN´ðãëUÌZ…¢Áÿ]>;ËSÂéʦUP‘¹r¤7b+­ñ¿ŠæøŽ-þ#ø&ÐC1E­Y[?ú=ÄÒK…—çù„oÊËÛ¿JOŒ¿üc§øe#]X\7‡ítû;¨ÓN@¯qÆ£Á ÅÎÞ¿x^«ãÿø³Å^'oˆ–ºâiW÷ ¦Ûéñ!aŠ5Þ”a‘‰Ë0g¾(È4ýRÕôEüimcwiö†y¬ÕvÜÛÝ*à0n 9®x¯Eøywà­SÃÚ4/hm,ã¹[BÖý÷Eä!!Ù˜žFTõV¯5ð‚>#x†Ã\ñ€oS¼†òHníHóÀ»”dðU×{Öˆµ›oˆ¾ÑßXÒ>˲Åa©ÉZ9b™ÊKÁ‰±ó.~ñÈë@¬} öß„þ“L›Ã–K¶š}Šgk7˜ËmëœGRý™~>hÚf…®ønêßLkËFšÚÒåi.‰q!ܯGRJ’JƒÁ®á> Ã>³àÛÕ‹GÓ¯e]6Æú(7˜e,¨¨A"?3ÏsÏíß4-^/ˆÞ&øWá;mE|_ðÆúm$ÉöëkØ.Wt’Zc,Uˆa´m'8©leŸ ë_²³ñdZ·†ï4¨{;—Sæ‹‚¤ˆåÙò”WS¼GjùM×!øÄÚ&­­Ç™÷WqÏ$«¸¹‘÷y’nÞ^:sÒ½“ãVŸãm[Dzx·â/ööq£Ý›í>ÐÚyšjFÊd’„!–@ï˜'µv¾ø‰ð¿KÉâ‹['ˆ¶ý>êVûªÜ å˜p?úôsòÕ—ƒ›ÁŸjðÄOÞꚤ«wiyf­-£†Ê(óTc @Êœcr 7©ÞŒ¤aôë^sãYÀZ5¿Š$•´ízÉdÔ|øÈ{;äÇ—,n£&\g'‚=ëíÜè×'´ðÆ/ Ý(ÐÞ)«hûâdŒl ¬Ç$Œƒ±¸#šñŸMeñ;D+cym}mh“Å4å%x‰F1>C †^úUcÍ5o‚–z¶kâ-gLþÔ[ÇC§jÜÊ®“zœ‡UÈ!°E]ŠH4ê?ð€Gs¢_Bbi,5µÞ~è2 +°þÈð—†<-wᛋɣӵ•ŠýÖÙ2ì+'ÊF•9lrÙ«>ø…ðß^HðF³wïØ÷+jw1”Gú¥’@9B^” v“«k:Æ£©xz ˜›æÞ Ólù‚ð™¯\g‚*;]3é7–ÐÝϦê’ÍmŽ ù‘»A;_¯Êpz»ñ'Á‡J‹â?Ã{M½šÓÍû]¬¹•c#F pAâ¼·ÅZ¾›c‰u ’«Â˜ÃK:r«ƒÙ€!ºzЦê7ö¯áÝOÃZnÓj±¨yn\nŽx#·ÜÁçV'„ì>!|:øƒ/„>(^C­øjûOŠöÕT´s³ºHÒAòíå_=xÀâ¹üQð¿…ü]eáûä—Ê×qwÈÃÚ³ÊxËöÉ»â=CÂvWÌÚ4b(å^Ö0 41§ Ÿ¼ÜäxU¦¥üOÐâÔ5ý~ñ“Ú‹Ií ÁåÛ’mÈÈùNÒW;¹©±Xñ[íjP‡ÀzEÔk=œ1lÙ¶‘¼lÎK…íŒã¥zWü,K=WL/}·Ön¸{™F/åÙà1àõí_³gÁŸÙãã/5Ÿ|{:Ÿ‰4™"’ÐA3Z8·8k)dY ‡\¡€aƒ_Zükÿ‚Sëúî–|Ið Æ ªGw Åy£xªÛììç#Eq\›ÔóÐâ¹ç‰ŒeË ö}QùÝðßÁ~·ŸX²¼±ŽÕ|Û+‰­®V³ÂÁÃÄ22'\ÒüCm«x—åƬ“FaµšéÁy2ç'Üö«ZçìýâËÏŸ€Ÿîìü;ã_ \›Œj›Ñïb#l2Å*Þ~ï'Bx<äTü#ðmå²´ÖâîÙŒl¯pVVlçhåØŒzw­cQI]—s‡øðGã?„.cø·§è‚÷Ã6ÂÁ¯u8.Ke,ò*<Í!ŒhX3•Ί«ñ_Ä3ðwÅo|9ñoö~«%•ä2­Å”ÂêÞùg„8–ÙÀçá—•³õ®Êo|7ðÂ\ku=fÑÖ 6ñLím3†ÄX)ã{ÕK_ xËàŒÓBðÿ…4ÍVÎH£\gýUîç†V#ƒŽýjÐìp:‡u½wÃ÷ÒìnPÒ£3‹i´ÆÚl³«’ê;ú Sé:>‡¬é6—·Z}µæ¡h…D°?&3ò‚«ì>ðõ­'‰%ø‰kñŸÁ—×Zn³§È!žÖÍÌo ¿ü³™OúÈðJž €++\Ð?áZꚥ¾›¥Myw èØ)F[k–!›ŒDNH<ÍRÏx®êóAðφ¬-âµ’ ÒèI³jÊ\…bAà68À5¡uâ/èzv©8™<¹#Ëcœc'‚}ªÛØxßGuµ,ÌÀ©RÅî=€ôíU­´mKéÚ>§¦²Fò¼@xÇMܵv&E«ËéeÖD—QºR±…Diþ¬L‘¬99ýÞü×kð§ÆWwº}×€Ö+Yà½}ò5×ÊáP¼'ƒŒŠã4›-/L·sãy5’ÂöÍVc ·F*NâOLóÇ5¡é>]cìz½ü¦k»×_5xV$ŒvKj$ÃñW„¥´ñ$–š}ãZµ£­ÁlîPW-…dû¹ë×½zÖ‰áZøÃ^7Òü_óëÜícSö« "Ü sÈrˆVN9ÚFFNPÕ„t]WOºÓ'žœyRDœ’/=óÅghSx^kKß°"ÍônB´yhe+€WÓÛ5* wR / G·ktÚ„N£ù<Æ;¨íëÞ«Ùø«]ðwƒÁzÓîôÉm,¯¤¸†Úo´ËIߎÙëŠÓ½ðgŠluM°N-g±‘LLŒ›œqÎÇ䯥; >ÔÔü5ðƒÅþƒSðn­,Úòƒó\(x,ï 90Ì1’©á»v¯tSã½\O·’2$‹j'F»Ï!I?tt®WFÓ¯ü0úž¥m}ý›}ªZÌŠÒÜÈXgOñg…=Wµt? ¼®ÞhWÚ·ˆà·´ë-âW.4Es£¸ÇQøÑd]SÁš•ž¯wiq4—OJÑËå\é³DIIc”zÇô4ï\êù¿SF»½¸?¸»[gH®­ÙIË0'œŒš§à=KL0ÞxJ–9&Ô-…Í¥ä‡ÍÉ·–sü'¦}k½ð-ûø‹G»Ö|q Û^Ån`–(v£ÆÊ0ß>ÕŸ0”z…üc¥Im¤èém{%¢ËâdDÛNb1óçž&ñŽWÂ÷vÒ_Ú˧_'Ù儺¾ó•ËÊHVw´ÍKÂö¶K¦ÝÃ+ÙÂ%Ýnw%Äo‚ €Ã½r><žk/ wQ±·¾´×™L2î1€ê9ݺÃÐõ"—1q:}B/ÏáýB×n“K’Ö†(å@Îèç;@{r3W4­¼¢4’Ý á\ätÀÁ9 Žžâ¸Ï øMñYhWv`O`EÕœÂr$R‹¸"ç –W85ÔXh_> jWðË[Mom\­¥Ãlfòùe'®9àTŒà5 ;BÐu;ÍCS¶¸7ò‘#$Oo ÆB3“ÜŠá·áÿkQ̉³Ç{–2UÕÇ!†Fx#ï~uëúµþ‚º|~#ø‘£A>•3¥¥ÄÖòn¹µ™¸V~mc<û×âéÖþ 7>¼¶º×ô«’–7qIËÛ9dp’x<а¯tox\x†â}Rí‘§©´ÈTr&ÀÚH^ ÇjŽK? øûÄwÑKq¦5òFmÞÜž[™Þ£”ž•æC\ñ/‚´·§H÷/ ”ܺ £¶Ù_áÆzg5×Å®ëÿ?â¢ð>‘o®N€\§ÎFÄêY£ì¤1¯Q@q¬x‡Æ^ ñŽ™¥êÚ»êþ–uš8® UžÆ^…•@%òÁ² çŠõOØøàëÅ.ªºÞœ+ÓfHce6 e†pW¡Å]Ó¾5éþ-·µÅ>þÌ›T˜ù8Q, ΈÃ/ÆAÅrÚ¦tNx ôÏÿÐö][Tñ•õ¿üN¤ŽæfÂáS†ÛÏ9=»Slt Á¥Hºíâ[¼¨CA;Î9w¯Ó¥|ï&¹e`c¶ñ.=´²È¢;2÷r½"p¹÷5èio¬jOkäG&˜ÖϺ(U„³°ì<*ö=M~^}ÁÛY^[øng¶µ1 ¥*L®p¼Ç¿Óëþ 𿈠µÖ5‹aÔUä]· [p§ºkÂ7¿ lÆ}MkÍI³qz­³*G÷P0h=ñ‚h%±Òé¶Z”T¸½Ž´˜Â¸ !?t¹¶}0 pÖVÚ©ÚnÞ{‹EýÒà¬cóã°5bÛ]7mÅ”¶¹,UX>ù#Œî3ƒŽ™?…]±mjm*²#Û[\0Šké•T¾?…SõÆ)2HumG^GƒL½žÂÊæfËË€Y¢éÂÁ'ÔV<Í«[Gö¯ ØÏ"Û#`?Þr3Óœjë5OøzúhõYÅi-» ÜH|ù§#«rì3YÁõý Q{Fi$ºº$,3.p§îŠs“ØqSÊÀå|1«x¢ÿGh|K`t™<Üy+(–Gþé r=5êÓ¤‡Su±‰ï£e̱UdŸÞ7E€×9ª]hZ ?U¼¶±¹PßhXÔ‰ á<çÇsRiþ#ñÚu¯„ô+™&KˆöÃmÇÌØÃÎIçµR@Tñ5ὺšmKP´W“hŽ”¦ÑŸ@1޽ɭ;˜l#¹‚Ã@òe™ðK«œã×µÏxIÕôù§Óíç½Aó,-¼@Ê@ dèXu#Û‹Oµ±ŠC»S½„qŒ ’ÙÀÿ"˜ XUÍÄÞ)BÍÉ+†FrGœð0:t¯m‘yÚQ r ÌÁF¯QÅmM{£iZ7›Íö¸.ûÈ”ýУ¦+'M¾Ô|T­eª!•$\î`¢¿vÐ2=sÚ­l7…ì59Ω¾k`ÀïFÚ¥{ ާß<Ô÷®©m,š'„š[ >mÈ»N÷=7mäñØñŠ‘ï,uI©áÝÃïáh³ÍkyI¼Â8c`F $d3ŒvcÇ¥aø—ÃúF£~u› ž8çf¨*Ž"_¸ —åªwû–ÎÆµ;/³[ÄBC1"}ò’NCAwGnúV‡£yWVÚp[y2)d Hà€OCX~!ŸÃ×z)±ŠÑ"»ºhåó•”"6TqÀçŽù¨˜ÿd@mþ×%ØòІ?¼ Øð¬ûÉ´+—ËŽær_1ο> Î þúÐF=ªËuo-«#4Q)žB „)ûÒ?P{®§Ãk¤jº-ÔÚ…ë½´²2e8ßžªAô­ëOëzmÎ… ZXCrªY"\<€s€IÁ÷Å7IK ‰ÕuhBÙYóÊQHˆ‘’Tþô˜]ÕN“â/²ü›÷РÜÇöE— §Òºfðþ“¨iº¶¥¨E=ô›w1Þ§=Àç'ªçŠä Ÿû|]iþ¹ž]=eÞûÿuÓ—*}†O½nx[Ã7‚iµ›hÑMÒ‚ÛÁ,zâ”Gr¾±áQÑmtHõ‹±ªJ¾TœÆ¨O?2.2Gjú‹öwømâ ,o5ojvÖÚ$1GöyŒï •yR[,X·¯'Ñ4 ZV‡XS „ZU•ä2ØÎý+Þ¼ðê÷Z±{½OP6š} !-6‰Y𻘪3’@­VÄHö?TƒDÔ.¼A%Ô?Øv p±D¤8éÏ*¹ûÝÚ±G‰µ/OdF£r,îK\ÃUÎ@ Pd í[^Òthâ2½½ì6䆳¾Vï)~NÚÁÅw-÷‹eƒFð^ƒ§ø~Ç Þ{G íûŜ䜼j“±‹G4Þ+>±w%©¹EmkeÀßÎÜ÷ïŠf§h• ÷ð$v’ùØ[EåêÍÇ/êÌNk[Â÷þ_ŽÛOÑïàˆ0’l–ßuا“ü=+›ñ‡ŠtK Níì4»‹û›‰ü»;iÜ Yþz2úõõÅ>t.Vm\øsOñ]¬šÄÒÚ¥’(qJ2«žœS×'5ÓÞ0Ó¼<ËçÞŮ݅ ­ª‹x Aù¤f¿n58!k9ÞËG´a"EŽR~]ÙÎFAëšòMCZð…¶¯a6­~o&˜ùÒoBÉ0';zéØV®«ãßéÈÚ…íòÛêš—Ë   MÞ_ʪûö Ú/^<×Zލní¯æÀKø¡ó:ƒ|¨HëJksøËNMI‹#ÝÜ#n½ºf'Iñ÷±…¥y.¡ñ;Áúš‹A¬[J°¦Å6à»F3ÓŸSE§<)ge.ŸeªÇiæÇ°ˆâgyTôŒsÓ<õÏIá;»[Åó繊4Á*ÂFiaQO®y'­pË©¨‹KÆvm–B¿dÓUCš>T·$(“‘Ö¼¿HñîµKkàûìÅDr6Ï6Oû9  æ´|Fu½3TM;\¼Ž).0x ™q’ »ŸJënêKhn&¼òå]‰(¨:·©8ü£‘€–ú>½¤èú“Ø™-àcmÍ0ù˜äó–æ¾äÿ‚yêRj>ñÕÍä^EÂxÒX†>V°|£¢Ùjž3¾Ñ´k….%fUšáŠ*8õÓô¯¡a;Ý:ÃÁ_¬¡ŽM@Øx¦u˜ÛüÆsö82"#„~µíäßÅ<¼ËøLðÏø,_À[â/À? |qðåÄi/Áídë÷ð4 4™¡x.öíó"W(9.¢ò¿<ô«/|@ðŽ›¢ëéie¢¡`0s*ÇÀUwÄ|ÍÏÇZûkþ ïw—ì#á» ;Oì{MsÅTX!#Ì´c$²E(b7o(¡²z_ü3ñï¯<;¥éº\/kf— Yœm%ƒá;¸üÇ5®n­Q–½§|9Ðô ¸ììY.|‰<·hÉ`\qÉ=zrEuÞ"¿¾[˜¼'5˜\N 1|Ì府öUïÚ³5H/´ˆMç„RhÍôfd3|¾Fá£Ø¹ƒ§|C×á7I©Á•bŸ2oã yë‘Óã6z¶="ËWñ„-®|) ß.œ%ØÚ€B‹p<ÉÉ$tÚx¦]^jš vW¶zs»Ü˜˜ÜI€œç’§Ôgé\寓ö8!ÓuWR•"_´®ÝªU Øxüéþ³Ô#Šw’8SL¼iT0#0ç ó`ç "¤g¥Øi¶3è·:Ž‹†h¶“÷y|w<Nâ1Ðu®óÁ_|aðúÖëÃÞÖ%O>mûU—5ÔŠoà/ñšñ_ x§AÖé`‘-®•ÖàH6GÀnž¹ãŽ¢Ÿ¤[i—VP_ErŤ|ëhWtŒ#s°ç±ltÇJ œìšõÖ‰¨xnëVð߃JkÈ!ÅÏš«3’ZI@?;unü×Ï¿ô‹Hlt¨m¡½šd[—¹%£yè‰Â†'’GjúCÁväQËgâ4@¯ûåHÈ6Ûù–ÇWžüF†ÖãJ6É;¼¤7•$Ò0X»nÇòúÐŒøƒÄ7ᤊúä#‰¤R¯»Øü}p{×ξ,Ó‡Åh|Akm÷SưŸ0+züé#zã€+ê}F‹Q±m>²ÀABÓc-êSޝœ|[£7„™ŒŽnV …Ÿr’[+‚€ätÈÈÖÑ%«¾m¡êökxZÜÞ¸»™Îg9,Ýp1ÓäŸõ[ýCÅ>¥q9Ô®Öß´ûŽ|¤pÙú××Þ4Ò5-wDÆÚ՛ǨkXXƒ´÷ãà+ç_]èY†9IŽââ ¡ÜðIéÏ 1Z­ˆ‘Æß5”ÚÉ£"Å &C=$œàþÊ€=«ºŽYȾÐ4ˆF±mcq,O“ÍšCþ-£Ž8Ípž×|Gt¯ xRÁUí¢‘Ë>Òà7rOROaÚº húæ—t–ñ8†í®)v…* '=ÒªÀ™ï¶óø†9ìµÝFÎ4y·*6U›bûvÎ;÷­ãâ¿Ûi÷/<± ŠÞdÜÉó|Äãµs·ž ‚;K+¨5cm'’/ ¯–]Ýer¸Œ“ß©¯AÒô­OÕõ;D”5¶—,v¯4 4ó]Ê2c Ъ“ŒãŠÊQe=—ÄBEߣ[È%AóÀÐu˜¯E´Õ4]Nú(c…ââeI é´ Š£ ÆL× õkíN VÄKui!¶hü¿ÝCsæíŠö¯ØE½·ˆnöèÒ«°?2~˜Œð‰Î88æ§•¾¥a¨h:Úø„#ZܸòáîeT8iHÄÙãŒVŠ4´¿½Ã>këÉáiººÔe)2üÆ<‚ 'з©®ý¼e¬›½RÝùt×­ÛǺIÃੌp‘øWeá8õMSÄwpøõà€\ió;–eù%#G ÀqN•&©œ—»¦xF÷Hðé6vÚ„‹6fŒ»~RQ!G@{щ§éòØèÞ!’"Ê{w’è6Ù³ÉÇ~ w­_ kºµ¶š4H‰ò%þ[î/Ð{תø«ÁúUüOuâ9-¬â%"–ØIîTóÛÜêÞÃrHò[Oƒ^ ø§j|G-µÔ6Öí$¶$Îf+÷”uW=GAÒ¶´H,µbëv³q±§|y^LMéÛdÀcÂ~ÐtëfYu´¼˜È<ˆ,¦ÁyÃ0?w®8<k˜Õ~&| ø@¿Ø_üs¤ÙÝ#y§N¸‘]ÑUúíRN÷ÏÌ'ž1UÈØ¹ÑÕê··Zµ%…·…dÐaÓÚÂ{­6+•o²±>uÌÄ›¸G?…|áñþ 3û2x&÷Uþ˹¸Ö#l èöm+]y‹¸$`à¤à1ãŽz×ßÁB|Añ ]özø'«]H¶Ë2ɨݭµ¬â_½,Û<éC1Œg=09¥ììÑM3í½OXñ4:eÞ‰a"h‹; 8Úc9©8ÏŸ^sÒ½?PAyâ; Ÿ¬úżVë+ÞÞÈ(r í‡yÃ…äãžN3_w?ࢾ(ø{®áÆ™á]_RÔ-Α›¥½‚{bª.g˜3’’Fw²Œ,CŠñ|(ÿ‚¦üI{;_øÃKð½ºÌZ;K«eFi æII¶žBã@@£œœñ‡=Ù3©m×{½ ßDÓšúÆÅ­lá§–êO•ÁæW¸çï=8®cÄW¾ð7ÂýKâ_ˆµ: C¶7Íy¨D‘K(Æ!I,ï!8@$ã½~øÏö<ý½µ¦m"øcW±¼AͶ¯¯ÞÛ$íüEÒ+B¥u§jùãOìkûJ|%ñe¯þ,ÛøZÃT»ŽÝŒ÷ZšÇlU\Lªv&K.b ž5Ñ šÜçsmj}SûxþÏß°GÄ®>.økÀΕã_N™¯é·“Øiv÷¯’5–ÓÍXÚIHrœs_\ú7ÅO‚Éð÷SÕ'½ñ^•<ŸÙÚB6[Ýĉgm¡ˆ‘F6yæ½ïÅ <[ñÀWðÿŠçñ©Ð@¿H­%òW|€©À |Ê20+ä µ׆à·Ôµß}±à}rÆh¢¸XŒG8ÎæUð ôÍz”kZ×g™Z œ®©àÝCú$±x‘îN£<_eÖ^Îf̆û;í9’ p6ýÒݸ®Ãá7À­?Eý¡ü)á¿hxnÊ{6eºØy,LÐddáÁPæ?a^…qáoh:Ì“ø–'Šá4ˆ5=#ŒÉ êBù’WbÌ¥FGÎ{×ÊŸ´oÅ?‰_µÄ {˜®¢µ½žâ9mÒ%hóuB9Hû®Ê0¤÷5ßí[8ùçè‡Á¸> xúêÝ<'á ïøŸíwt"ÊC£$!ä!²F^AKÝuDKí ÷Ë6[Oöˆá–HeÃ.[z¼¯&¾Šó\mÿ³GÝ}Oo†§5ïŸVüøðKösðáðoìñáûmN•wÝß É{xľ{™3,¤€:žW¡O¯éë> y§x2]v§Üf™&›¬økH³˜Ë¸[AJ„}ðüÕ½sPÿ„mÒ{8Ra! UÏ## ~5øþ+ˆ®ß¶gÑR¡Jš´Ÿ£ u™F¥¨‚çtJÃ@ê3ּĴ§Àe•’2O Ђq^ßiâ‹ÝFÔ8ÒeqÁ“sœ:}k_ÌÓQvWÌ•€\l=:ðG~)P¡)¦’/™-ÑæÇ©'‰æ—Áy¦ñ·Iu•ò™8Æ1ÓžH5èÖSjóC½X…Ò®TRÀ7} öížµçþ=øËðÿà¿€%ø‘ñÛ\ƒÃöpÉ,‘Ã+ƒs2ÆHXãˆ|ò³gî¨9¯çÃö´ÿ‚âøÖú¼1û%hcDÓes ž Ô15ó ÊŸ³[¶#Œú3±`:¨ïíe|)‰ÅÊñ‹±ÇˆÌ)ÓVlýñøëûAüýœtK¿ü^Ö!‚þËOº¾ŠÊ"­y:[®JE÷›pQŸ^+ùûýªà¶_Jí NÉï’zç5ãš‡ŽŽ¥á£žqp…wÅÊ"?ÝLçsÎ=kôü§‚pÔ•mdx¬ÒSVƒ±ì×¾,×´vK›ý^}Iõô«™ç®.¥~ƒí?ïýKcŸjɾ»½¾Ñu?ˆ¶wói:’²\2%”÷må@×ð±ÌùŽ"G/Æ+ËåŸFÕµTm ÜÌ M,…î&¯ÌÒ·ñA=+Ø<ñľ'øªþÏ µW𗈵õ½Õd²U’Cud¥,RY;aŠåwc¡|†8ȯ´§FÒŒU&srÖLè¾x×âo…._Oøs©[i‹smqe _!–;ƒw†ùÆFw6 ±<ÇÓü!î­}}âkI Ð&ÒbAš/îÚc+¸†i°ùxÏ¥^øàïéð†¿ðçQ¾Ô5{Y´ÏhLï{¬iÑå絺q–G U¢ª…R»~ló¿ þ|[ø…áxpK}k$w7¾h!mƒÀ|´.à]œˆ~õhf‘•}rÚþ¹“ªE>™,ëð2óÑrò3‚éÁ ó^›ðÃÀúõÿ‚í§¦&£&£qw6®nÊñÛÄ¥­ÐwY›¯Lç5ìþð…-šÏ_ñ«x¬Ãh-eÒïÙa»¶»f‘¶|ª7|£=½EkxcÃ~)–ÛÆŸu‹£ËáøìÚàG"ÇqöK©Z1'‘Ϙ‹€¥ºœf•ÀùÓMø1¬GªÝxcÇ^[íJþËþ$Ö–’-B. }­óŸ›<ׯÙx[áV©ðœ^Üé á/xnKa­ö‰%kÕô’<áÐ’T&>R3\Þ¡mñáoŽ>x’åüHlÞÙ˜– •†ö\¸QÔµÈø\ñ–©¥ÙøI!½¸½[‹œ^$ŠHn e‘‹tu r?*`hi¢÷C¹Ó4–eÕü?âMF)î ¾øã>i £å;¡ç‚IzŠóy|÷‰5Ï XÅ&“§Cây<3fò1ÒúY±÷oœ£#+–èú×Ö¿|¡xSÆcYñ†µøNÿɶ{yn„Ws:üZÐm¾|Lñµ…¼Þ"´ñFäžî[\Ø –ùc0œ—`°ÎúsÇ“ü\ñ§ˆ´? éš,Ú®‰®\i÷óëúoˆ4I¾Ù>£-ʈ0è€F€Jv*îÜ@#¥|•¬x² 1ï4‚giÛ::Ü­À}ñ¶×rœŽMC‹.çôñÁºÿÃ{ }'Ä£JÖ5Û? XÝé¦ÊRâuž/ÝÜ$8 ó.T¦O žkÍ|#âÓ øÂÞãáÞ‹7‹,,­â[æMÏjÂ|£$`†- å~^yæ¾'ø%ûAüð}‡…|io¨^ê¾!ð¾™{ÚêòHÆ Í˜IÒV-„X·$i÷W8WÔ³Ž·gà›ßKçéþ)Ó×R»šór yU E:ír0ÃRâÆÒoüpñ/ˆ&ŸÇ8°µÓl<8É=­¼R47ÓA.Pó­×…àŠâeÔü'®x“EÒ|#ÇÄëšr%Xmþd{HXüÛ€¥XüÄ÷Å|ãñÃ~-³ƒÁžø‰¨YxWU–îK;+½.c#êv·L¾hqˆø½’¼€kô*ËMð‡_NñV¯¬Z&µ§YÉe¡%ò,0ÛÉTŠå€ÈpüOJ†Ë>fðwõÏŠº\ÏÄûX¼­k“j-•ØVq`¨›iÆ:nPÄdW’hoð‹â‡ïü9ðË^A¬ü+‘¬†¬¹ˆ®3ˆeÈÁµó*m΢ñ0ñ“k‡ˆþ94Âòöîê_´ÚHdYPŽ"§„aàæ»MÄúí•ÏÂÝ/EƒIñf‰¨Bu¸%¶«,~ífp<|à~n™s :‘û:Ç¥hþñü$á¿ëÒ[é2Ú[,qµ¥Ì›óæ1fW.Û@À³ë_Í­üB𦩧êþ·›Kñ•ÕÅå¶§«&ÛkËK;­H>B—_¹R{W©üsñÖ§ñ7íº^—Ú†Ÿ­½ºiÑÚ@ò´7ºiòžåeˆn*&°0[ ‚+›ð浩x0x+â÷Žõâ]À­“Ýé­ì’(g)4>^1†gf*ààŽ§O+=0øëPø«ñ\Ö¾à ýœæG»²•ľšòG ¹HFîe/’Üù»ŠÞ²ñwÅÍS_Ö®£†ÊÿAžÙôÛ™.JE r”¸@Ç’$+’9À>•¯e èþ'¿ñWÇ?hú†|O«êŒö–j²5‡%‡þ<¢b3¶]ŠÌ·¦Ñ\׆¼1à‹ hÏx×eÕ…¿šÖŠZîf#Ë)8'§æ­l&"ø›þ[x;[ø-k6‘ ÞÛÛËáòÛgƒìö±˜„qc .ÖFLô‡jé~|(¾»ðmÖ…ã I Öw7÷WR¨‰„’y ŽËŽs׌W ,š^›ðÛFð§‰oå´Ótzy­µ{|ÍgÄѺIhêüĬ¬ ¯ÝÜ3\_ŒüQðçÆ/§xKH±}7û6ú]ûJí•í®n<ÅmÊOß68÷¸¦:ñŽ…¥üGÔlþ ”»]/SÒ¡³·Öå]±Gu&éRVÁä '®Hk‚øàÿÇð§PðŽ‘¯}šÊÊÞ{ÝQþòôi«¹|˜Ï>i'ž€w5Ñ]ø[âOŠ|Qáäñ5¼Û]]-½ÜvÒ* ¬ǡÞÊ7c`t¯!±ÓôÝ+ÄÒjß|I×Q5m“ ˜ Ìcgp0ÀPÕr°=c_¾øqáýN°‹O¸×´?è©#hžnòoPù²)”RN7AdW¶.¹à­KŶ>$ðüw3è:®©%·…žùü×Ò¬£…Víe’ÇË›ÌFqµ€¯…¯¼3ñ+Â¥…þ¤[¹[”ÕVM=çæXÙ¤ 2…âÝ#€{W°^xSðmƽ¯|%´Ç‚üa í½ç‡çÊú³ÒNXy 1$½ŒúÔ m¼m§ø?Ã’|0ŽÖ×Ú–4s·”g²9, 0ñ¨Ú"»o‡Þð‹<s®üCÔ4Í>Yü1aqr³Ì±Ü>™s1HÄóBH.{)®à߇ì|:ÚV‡â?Ajš‘ÞúXÚâ[uòvªªNã®GNMwÿ ¬¾ü7¼×ì>)iGÄž*ð¬ÒYizÆ–æ}6ëG»6Å%»eVPIPÃ$uŠM¡x7R´{ï/W–ÃVÒ ‚·Á-v’°AÑóá•9ï^“¬êž4ø…>5Ñ { ãqö{«´XA™Ó í«7ŒsÉOâç„ìl5O|3·¹þßÖ Òn`kpÒM¦ZǹžÞGÙ$*Iê¸oøƒXÔ>xóÃ÷à_jzkDò^Ff:mä2`Iuõ2C!Pà ®Êxë‘< {ž‘âÿ‡ÿþ[øw@ób¿êM*ñc.Ñ3ç9§q¸Xïãå×|ë›–hà¸k[üþñ€%ÂKÝ[,F<â>™'ø{¯ø/ãÜxšÎêxïôhž7ƒPfÝ ÑÅ#3ó–ã'šôåmwÄ^8›Ä>0µ·’k'Bm·k‰ö*È@ )R9 2´Okí¬xv{3*\ :êúé§ÿžvÊ—=×l¹×'‘^ao®¡—RÒ‘ä³Ö¬mîncñK¶E•YÏ™ùH89=9¯E´ŸBÐ|O£^kºƒê-ᕞÂÓË–F¼WýÑR~p‘ÈScq€P1É|ýŸüiâo‹1| µÕͼW–&ÛNšþ#ncO;zÛ>îWpݳ’¿&ÑŒŠä¢ø}ªxçÂ:^±u«¿†¯/ }vÇH¶µÕnl`ÓîJϦ¦©o)BHu/ 3L6»n ±Ðß|Yñf™ñ_Kg/ÃûÏYÇ4šE”èöW‚DhŒ7â>Œùm¨\ó’@>lñO޼[á¯éVÞ'¹:‡ô‹„Ñ4mT°,åðÙc™Jó‘´‘[ž ñŠ'ñÆ»ã+#N²– VêäZ¼û’îÙ¢ØzFOÌyn:sZ~7øi®ižЗG¯C8µ»l‘+Éæ;ò3´“¸c¹À®'_Ò¦ð×ÁˆãÇ:]埊ôå·}ê8m'…Ïï›q!]ã ¹8àðMÄöÿøÃám¤:BøÎ=kP_ ¤š8Óí- Òü±ÅóÁ þ%û  Õñ–ƒðþíþ&ÓþǦÜêSØÍ“­ã/û÷€zWœüñ÷Ä= T¶ð½–«i:ëºfÉBY¨[L£Ë1» ££`èÖ»_‹? u뻯Yx‡T¶·šöó3N’y±»S»yF›öé@Î:úÛ\´´´ðN» }»WÝÃ%”¹%°|¹:Ä|Àö½ TÒ|ðóâ6¿ªÝ\Ï òîî^@ïxAä`ÛY¿­{4þýŸ¬~Åâ_%Æ©$ÓÏus%“¼W+ € sS¹A#r62£Àˆ\õ"‚duú7Ãÿ„ö¥¦x‡Àž%¾ð­Ä–·zõå§Êlu=B­« ””î`ªCƒ‘^ቾ!³¹ø«á/°Ëâ‰b³…a—Ìd[ÉÎH?|p}s¾‹áF“u î§y©Ü麕¼ž@·lZ~¥&ùžå,¹ÚGLW®ü?ðïëØí|á‹ë.þîÝu]'T¸´rGrìV3¥0êê¤töæ‚M x‡Â±Câ?øÇÂ)c©éºtöé—Qµ_ÝË:8óYVEì@÷­/Ù«âÇŠþ,™¯nôHáñ_†f¹»‹QóVóÁs ¶¸H÷’DBPÇ×®kÔ¾37‰o4ÍZÂI&ñ…)´Öô«hÄêÄ÷‘À×¹÷ó߈^)¿ñ†“¡ø+ÅQXØhóꨥlÀc’îÙ4ÅIbªÙ#œÐTH<âÏŽžýŸ¦µÕÁÕõ).æ]6þ-²ÜÛZ0Y\0 Z!æ&A$W[௉ µÓd³ø¡G…|Abl­¦š RÒæáZKiJòd\€8Áê1\ŸŠ~#ü`Ð~:/Â_Ée­Oá³<WöL¬¥¡_-£eù‹ˆÈ =d8=A¦)ØOsŸð^¿ðçâ€|1¤Ã©KáïÇdêðêbe±¸¸†åâ†9ˆ>`ØB1ù%B» c`ø]¡h_¼IàCH“GÓâ¾M?Q€:Ÿ±ÄW"P:2ù™o*JúN¾¾"šûÇé£Ûx¢ãá_…­ô½x­OÙïï-·M M³åvØì¨YOÍŒ‘^q¡é¿aðf¯m¤éŸÙëŠÜ]]F‚+ëeyäF»³¾I qHG©üW¼ø_?"øI­ßCv"´¹‘5;¤+%¥å¨("G_ãpà8"¼ƒBñ/þ%øzÏÇai¬ørê>¤OÓ e–5l:ÉÉ>¬:W9àÝ{XþØŸ[ø‘¥¾½}1ÙæxÌ-} ;Zmã©çæ$sÀ®jÂ×âÂZ]xö[3¨Z/ÚìLZbÿ¦&çUd%0\D™Ûžp9‘én¾0}¶ÓV¹²l·fRB¬¤m9-ÆWšñ}JËÁÚÕ§öo|U©øsÄRZãÔåL¡T¡–ÞO‘¢ÉÆSæSЃZzÄŸ¶ð¦™ªjYÜÙŸ³]jŠ“ZÞ%ÁuóÙ)c‡ Ãàç h{§Ã‡ÿ< 'Š4½H›{§?Ûq?<ÓBÛ¾Ñ*Η1È¡[=F3‘Œmø\xCUðî“©üD·‡Ä·:­ÄòE©ÜAkrÌbÌ`.<ƒ•ä’NkÄuoêŸ >&ß|yøw§5—Ôîm¯¤Y³c<¯[›[«`vþþ ²FÃïéÞ%øƒàÛíOÒü=¡JÑÇíÑeP’X„# v Ñ꺦³ðsàö•/‹¼+ifu}0ÛH‡g’ÖhT 59;ÑØö?)"¾søãx4ÿØø.æÚt–÷eÔ÷Âî.J´‘<¯#œçœq[¾9‚ïÇ—:6µ&¹v>/²·ºKøU8ÌLÞbüËÏpÆ»/xMÐôH<ðÃÃöž$Ölìä†ÒYecq4Ȧð–<íÀÆF:Šr_¯t? ø£Ä–WZ†§£jÖsišuÄù:mý¦§9 »üÊ. †$px ÖŽüWû?ø²ë_ð>‰®\Ü :mKKÕmζòüpâXÜ sÀ#µzÏÄË¿_ ,¿g+ §„%šúÆâTœx®,ÏI0Ub‘[©õë^wáÍ_á_ÅU»ñŸ‡ÛRÓ#’K™§Ó}p/¢9[ão˜V\o\rÏZõ?†ßÓàxÃcË”>^w(À¬þ!MáOêÿ³÷ÄŸ j‰§è1C/‡¥Òí¤k›yaßöˆf+%´¹Y!d'knqTáý¡¾#꺞¨xBÊçOÓà’ØË¨ÞÚ°Ž[­À鲌këñûVü9Ñ<¦¯Ã}CWž3_h2ÃæKe"0Ar6î*sƒŠéô|sý§¾ø£âWÂß_ëVóÞÉwÛI‰á·r$Xag@åW%†OP™4ÿ j? <#©øÇÀfóJ·¿¼ÔuFT¼´Rû‚FÇ•\¸8®ŠÊßž'ø>©ar-ïÞ(a»°·wÚ¦I<È'Ïf$á‰×ÚÇ/gY|Ú‡†¥¸Aµ.Ÿ¿If!½Âà?œ•;†xÁç5âO‹Þ*ðö‰ øNëLµ³Ô¼:ëmfâ2’\Ù¨.ÇïbÏ8 Jà…õŸƒ­ü)Ê^k’jš¥´p®U”,°®ñ,€qXZ/Ÿˆš‡„õ¯Û‹Æm­h·ún²ÖÉy¡ÞÉe¬éz°HÕ.Y#!d²˜‘‡Ü™u*N=ËEøß(ñã\i~]4^X46—jûÝ$#kA \…x›$`òÅy•í£6©ð‚+ýZãÃ÷Ø[Ë›k2ê¾C,’3dä¸+‡^Au¤ÕÀúj/ø(_ücEÔ„<ãOxÝ~βÅ.¿£'Ÿ à,jЮðF gÔW|iñ'ÄŒ:¶•«ë^ð‡ ü¹Ñõ- ÃמÖ¢µ¸¿ÍÓýµ<ÉScD¬Œqå܇ å ax~ßÇ?|Ao­i—v/m¦´×4iþ‘h.ÇÏ‘’É’ps•àW=ª|:øƒâÍ ãàþ±izχï¡fxi-m]×6»IÃ[°;ÀÜB01ÅKá_üÓ>ùš±7s隣]ZÞ錶³Í ‰½®AS°!ºÕ¥p:_‡’iöZ»xA.¡³¿¶Šä6ªª˜BÈë!﻽reGRËg¹dVÀÀ¾^}r¾»mÃ3ø¢ÇFŠûY³Ó–[]?zG,êäeb‘þ\žJ«pÇŒŠÊÓÞÿKX ˆîÓ˜Ù©·Q‚ÀöQÊÀëtß„þ'ð-µ§‰¾jG‹¼ â[a{g}æ¼¶.â=¯ÚeÈ;Aë„ñG‡¼q-ޝã?[ÙH,o£·& 7Ï a•o$€Ts·~rO`Vðê_‡ µ]/ÂÊñêVënl㉙eiÌ ¸ñÞ‡­mx{Pi¢Óîþ$h“ Ô™še›(ë'WÆpHnÝ@¤W¢x7ºo‡<%á‰úmÅÝÕ€»¿{Q¾km¹$R91QÛTüI¬i>6·ƒVÐn&:ŒÓYň¦–Î6/ Îá21Ñ×ø}+­Ñ5}Gîu˜Òm_H»V´€§3ÃnÌO–Á¿¸O©ÝÖ¹ßx3û0ê“i—>vtë6ž#p—VŽ  ñŒ¹;ÔÆ(ÑËüDðņ|'?‚ñ‡Â WX·Ô|Ke,vz•©³[ÛI”µ–¢½%Rr60áÈãµ|âÿÚÓÃWI'…×P>Ÿý'S²`—ä4¬‹ƒœÁcÃÚæƒà]3LV1ë×§pnb)¶W˹uç%¦Í!g~xÏLK-[â}Ó,v±Ü.œ 7Û``èscýÖèzTx§öšøkâm/Ã_³¿Æx*6´Sjb f|¬·«/\ã†SÇZóØ<[ã Vàir5Õ¾˜†Hôós šÙá œýìtànÅu¾[ŠQj¾ñ%Ô3<ÖËb×¾[if9R=Ðõ¨8ÍY¡ÜÛø«ûPþÙ_tm?á·íÅáoü[´¶"M+ÅZ,·ñ.–èvù‘¢,Ñ>Òq$fDIUŠŒîù¯Äž'ñ‡ÀÈãÕ4o Ëãëw §ßjDɨèWƒ Ía·[‚X»¨Êm¾RHö!eâkÍ;N𦵡^ gG2i÷ip7[·Ç,n¤‰0ƒp ä~+wAÕ´ i~ñv¥{oa­A-•¶©jš†cövdaóœêFsÀáÆ)l»žAâŸx VÑük½*êÖ@Ú¥™$%Üøòg+Ñãæž9¯¡måOˆ~ ðî›á]0&§¡Ú½”’Ú¿7v÷?4òyŽA†+ƒ×5âZ€¾6AðM¼Wã´M7Å ÓZ¤ CÙë6Ëå.>SVÆõÝÇП]ð\ Ó¼;e©éè©}i£ VÞ+U’6’,¬€u\N*„dhï¦ø;Ä–çã÷yš J9Q¶Ãä÷nõãŸÜxâêÖÓN¹´’Þ&+,ŠQ.w9ÈlðvöúÕÝâý£yaâ},Ç ®÷3FWia‚»ºŽFr>”s" úÆ¡ãOŒš¬Ú”¶Öz¤¶Ú6ÙÕ#Î8ÚTœþ5ÇøGKƒÃ~:þÒñ¤—›ƒ±®"V*SnÆ «œŸz4û»k;) ðU¼¶:ªÜæ9Uò<¦%¶`ö#¹àݺñn±§¶©j—G#Mƒ(¸ÁY0:7_J†Q¨[h à[[¿£M¨Yj?gydˆ¢$'>ZŽqÈ8Ïé_ ­þkZG‰4ŠÚÄ-ñ´§>̪]DÙÙÀ;ÃŒ z×5£A¨è:\^]ÿixkÄ6þb ²*+7%†7nCC]?Ã?èþ%½Ôz‚;Ò€eŽÆm"=Fg´’ÕdeY#Ù±É'ëŒÉí]õ†‡qâ}n=zþè›—ÊÂ)"PÙÃýÜúב|Iñ "²†Y ²ªËÙ…“£ŒÇ¥o|4ý¥?±´é<âK« -_Jd´Y…u‡FÆsžõkèþñ=¶©y Á¯¤1£“ R&ÕÀ]­Ðc½wvþѵ)—Ú…³L±‚ßg–P Va‚ëÎëŠæ5&ßVÒ¤÷À$¯5‡¼§¶—­ßxÎHB^08š@c“•‰ô5Èé¿£¬Cg£_-ÖŸ1xᵈ´‘ªŒ®Â¤òöíYþðöãkYn¼#86€^&”<;Õ½LžüU}Cúׅ®`²ðíäÖÖ×X¤²rÄ|€‡Ž0ztÇZè¼yàoŠ:Gƒäð>›¬^;[ç¹,{dÆ#õÈÏz_bëM°Ö'€¬b3ms4M´.S§ çŒw5oWý¡‡ÔzÕ{èíf¿{ýëÈT1MÁ, üßýjÙ¯j­á›)ŸÃV¶6ÑHÁç !’aÓs³óŸq\]†©a®_JÑÞ5椯åÅåÆÌ‘1þûŸ(…tZæ…$…&‚;™Õ@TEùaËsßµPÒõ -è(þLòáf)ò¨R:íÞgK:‚é–^ Y®on ²Ê ÆT•zŸ¡&»3Sø£ù×ÒH÷—ø¿ŸqfãžXà}+&îÒÂka‡m¼ÍªI#oÀäþ•sfÓSºhþÑ‘ÓG"ü¼c8æ€$Óõ öâ÷UÓ#YðÒ 7p|zúÖµ¤¥dmÜùÊ+-¹ í‘È$œŒþ•“}y©µúi¶ZÃjŒFØBªƒÎTu>µµª]Ùj7ÿjðí„ÖøE§“jù¬G 66ýyªH– XXø&Û]¼ÕìŸW¼.N•cºV…kƒÆæþîÐ~¸®OY²Ð¼5áË;XÆ—÷ òÜK#³:³uó´ž~µËÍ©êvZ¬v7_ê v–ââ<vewgœVÆ­¨ø_\¹Mmná…A)÷vIÈÇ÷l |¨—@>!¸ÓÌsiùÑáUƒ‹ž Áå}Õ^;ký5¼¹®<É\1wn0ÞÀ~u‘$Ú½µÜ3OÊ»"Ü/ì;ð=ýµ}"ÞêD™™IÛê«Í0:=3SÕ,¼-=œhn®ˆhâe]©c·rk°N‘+ï6)RS…\68ï»ïÏ5¿®éכĂáäÛ²{N” aˆáTž¹æª\Ë¢]iˆÙ›9ÔM™~½Ž}(%3KPŽî/²9ExŽ$#yóCÜ.Gj஼W§‰¤Óí¢U• [†vîI?ÈS.õ·Òä*nóò¡Éúg׿¥kYøŸP‚Õu BÖÝfR" ÑcsÔãÚ‚‚ËQÔôÈŸPÕtµ½Â†‰¶"¸XŽ ô V¾Ÿáû]jüÂo¬Ï…’ËN² `Ž{åÎñt5ͨ³‰Úà¦cK!û‡= óø]N¯k÷úƧi-ãƒj;ì 6ó;B(‡4‡â‹[xì$ðþ•Ñ$²eÒ3×oáÅuº6™©kRZØi‘µ'EýÃ0nè2îMcøbm4j­Šn@„’DÄÄpAíŸZètù¯´'º½ÑäTWBªåq#FN@ÎxϯZÉÖ4Yøœxs^H>Û°nUtX­×©Ë) $Ö„óYKlº6š!dy6™ö±W?NìI汬¼'o©–ŸXÝ>y$Bʆ< îfÉÌš´¯kö¶ò[–Ú%Ý Pr@?ÇÇ8ÇLŸÂ€6®m­lo­Åc‰pÈ òŽzqAÖ´âñºéVcS»š+x¤O-„ß{¶:ó\„^}üÖ6é÷+,ÅÄ€m€ã‘òŸ˜¹ês]î•{ i¸ñœÁR2yPÍj.Ž NÈwfË‹“I+WJñiÖ­®d°ƒwÚ‘BŒ;…=9®ÿÃÚ/Œo’cÙ­™_ç„ÉóŒô y"¾s׎­ã+¨|Y«ßMœGjÚÄI W˜&1“Ï9®ÓDñˆtt]3Jšg¿8YÆ dúsÓ×íUÌ)¡¾Òoo.šxb·‰"€"I3æÙÿÙyúäU«m^ãIž[dñ¶WÍ #ÞGñmè 5áž¾ñâXùzȸ†ÀF Q±Q†ÎÕ#qÏv¯[²Ò.ï#Kkð÷K2ü»Hÿk¾;Cd4O¥x§BÕRãì¶¢Kóçd?0«„ŒuíRÝêš%ñ³ÕíËGŽ7‰Gß“€8 É$óŠ­§iÜsÚZøf)f.­ÐDƒk ’ÎNBžù5j]G“B[ê±42><È¢‘Á=ÕN#½þç5†£äà E6Òˆ×=ËdóôíWõŸƒvvvPݽ‹Æ–Ðí¶û|¡÷Ë!ÎJd³:qŠÛ’æóÁ~¶ŽI_í«;µÄ·²ù‡  001øšÁAñÞ‹¬Z_øÇQܗ͸D¡KøÚäŒ{n'¬$e[YŸ [Ãá¿ A ë¶E·d“Ë=‹8!“žÔÍkAºÑ4SG½6êšqóï’GtTi9Ûô¡mà{Ÿkw’Á¬­ûO9Ë"æHó÷c`3ÇÖº›]:÷F…®/,£·Fmïe óC2œ–wþ7'úÅž.tk{Í'MÕô½-n^â#ÇA•Kr§¦zU[êÑo£Ò'–äMN¾\'<–pÇús^åqãM>Éû_{½%o$X-mí­7<’œòX€v¨åˆáE`xWX½ƒÆ·w7÷·Ù6Ðlµ¸r[tNÜŽvçžÂ€0ÏÃ^.õŠ;«¦Üÿ.á QÓÛÿ¯[úÇÃËðtiÒ\ê:̘!„ã–f8TYºƒ[þ9ñ>“ª4BãÄ[•£ p¤ïVçïtÉíëPi6V:é6ž] Ñ•ÆòT03ÏQßõ  McNÔ,4JyâÂ?0µ´ $®ÿÝ\¸^ìxô®Ëñ±¬‰-tæ–@¡n.'!ü¨‰ÌŒÙÁ®öð*3K*ÈÌÌ0F€p£·#š‚ÊÛú…†³©&«+² v3U'8·ÌIÍ^X´mCO0¿ž×ûs }28à+]ÑdÏå®Y‚ÒtÈçÒ±5«Âú•µëë7ÚË]Ba¹6Ęã€çldžrOVã»à½kÅ#O’K•š&æÚ΃s*ð«$œòÇ­rú_ŽuïxÂi|.–²G,¶Ñ-ò“ÏÞ%Ž\¿Þ=GjÛO ë:¤¶ž+ºH,m­|éî0¿h ~ê¨þ1ÍuÞ Šoæ&ÀLŠKÛÝH»ŠžF6téÒµ'Óÿ²Å|@RúÕ¥ÏÙ¤\+JùÁ#9`ƒ¢ð(JÅs2¼º÷ˆîd»_Å2ËÓÈT™îÆ7Jã¯NœWñ â?Œ¡ÒÒ_±‹Ö¹» Ë‚a'’î̓´€v⺉ì>&^ø….m/¬´½.Ú=ÌçRFÞ8èíL³Ðî#{]7Àh.­ÂÍ$îEHGEw ü¹çÔמh×Í%…Þ¬x¦ÌˆsŒ‘Øã®tºN–5m=5»ÉÊÛ4-o?º ?uS“É5á¤esBò=K–^"Ômྺ¼¸1¤K.(8ãê3Z¥áûËÍqì§¿·2-„•ÁRHÁ?xŸ^•Ÿwov¥áêâ#—¸;{OÛøªx~Ö×¾’ÞäÜC\Ýžb‡Ì8)Š 5¼3âò{­Jk-¡ÌPÐð:(8Ǩ®ƒÂñGcv|Q§Ý k½­‘ `1+óg€¼óÇn+˜K#KÓÏÄ>Nté’0å’âF\Œƒü#8é]“cÍ«Y¹Y£Q ªnYQ›çÁ=EzÕž›}âIVÜjêº]´xº»´8Oü A9Áç¡õ¬i1‹¹-"¹IÄ{1*`…oqÏNŸZ—Âk«i×WwÞ™ ´ýÃ\ÄÊ72GÆ©Áæº/M˜×Ëf2ª •ŒwÓ¯ »x¿Q°´×R(mä‚8_h%÷n=NzgÀâ¹é6ÚÄ“µäcËž œ’Þùõý+Ó>&øbþãYœéFÈHògynN?º}åú.¥k¨i‹k b‘—²2C«}zV±1nç‡ønûTÐ|Kyi-ä×6P¢ÝçfsŒÇ¦3ÜVÆ«¯>‘§Â\- žæXY\:†LHG#°•×üUð'ü)ï…÷Wÿt;›ylŠ˜µ; VD·—<BÄ 9`Fßzá>üZñG‡":·Ã ê·Öò¨¿K¨íÚ(Èf µXõÞ Ÿ”`œÖÉ+}O¬£ø€“*j)X-£–H•<⪠ÞFq´qÆ»*}'â–?ö|~]ü—*ÇR6He(s&ÁÏËŒ‘޵ò}‡Ä/Ž^Óu4߆rëw·×S\G$·iŒIýà­–ùL}qɬK_ß·æ“þðóá¶™£tÙ|f´`Œ‡,Ò1ùzíMÜõíY¸¢ù©<'q}£è·óÆ#þ‚%`]îäÉ åzÈúà×°øsRð¾ð÷þKÍNÞÚî[‡ž;)˜bij pF}«å;Á_µ©á› ŸüTƒKÔ%¸“Oðþ‘k¼~fqËr'™ÊŽ  ®[•p<‹Qý¿á+…¦ø…ã½f[8‡1Üyr@C7ÝÄhŒKœäxÏáQ¤ŸQ9Øýµñm„ž Ôuï´ìû% 4öÏ[tî´¿0eÏQ‘œ_x³öÚý–>ø&ïÁZe÷vu&šidY.X°>Ãbä€3\7‰?`¿‚> ø‡/įý²i5‰'n¥T–+tQÐ8H§¿澚Ñ>xá]œRhþ´ºXcò ²EHã);r8_º£ø{htâ·´løÇÆßðUI ZoÁ¿ ¯ˆfžaf.&·“k€€¤aY³¸ŽO®Êåÿmí[WMT×í4¨­¢û5­´ðýªaµB±ÜH9 _^kêÝ&-.}KþýOÂvZ-ýÕ¶¶Š¤yä#7R;ÖÆ™ñ Æó_½ƒ_Ódºµ’1 ¼°ŽmYIcŽ™ÞzƒšŸuÝÏ›SöSñÏŠ¼?nÿõǺ7-´¦­•@Ì&8±–<;n'Íz7ÿàš°7‰4­oZøá¯ø“E×`Ïc—vÞs¬ˆÙfP¦9HÁ…Q‚Çæ¯\Ò-GŠ I¼Fï¥YØÞË$¹.ÓDG.ÜàG|ó]†—&½+ÚÜé÷& .ÞE†At.Ì<µ)ó.ãÈ#kúÝÕ×CÔüsá?­ÚG©%½›]LË#C¸iÃnÁ N8¯ñ?ìSû1üzº“ÁÞ/ñDú_Ž¡]öÍ«ní%>rLl™p²,€¼!æÇâõW0$#º‹åy£ñV¡®E"½œ7*³MÄränVÉ$mïÉí]߉t«oƒ¾ Ð<{¢kZÔµ A%…„óÚ,–EY¿Ò%ˆ‚©¼†@ÙRØã5ö÷Á/|Jýÿk bßÁ6ÚgˆôÖÑn´k‹{‹%k‹­ ÷•{(†öˆ4‘#³a°‹††_ 4¥ð/í5¬Y|<ÒVòmCÆWòé:Eþ`/ÄÆž{/"(ò¬Xä*àÄ è­N ÓWºòI3úlÿ‚R~ßšüKö^Ó¼OâkY´¿øM`Ò¼Oc2lI/!@ ; o·˜ò¤ò­•l08ý&Õ¢Ó®O™2yr#ŒÊX¨Ç5üÿþÁ¶çì¿ð~Ó]ý”?c?…~)ñ·ƒ><Ëâépi¶ß_Än&ËqÝÃ=ÒJ¤ì,Ü¡©kÿà¸Mï…tY¾ikáßøHMÌë%C¨Üi·¸O(éö”Ü7æiäQ…7É ùÖ;„çWåMYv=˜ãã^çî¶»eáë}k>%ÔV È·›%ÑÛ(À@ 9û II!¾¿Ô4Ï/|²ÊÔ…ÚÇ%˜gnÜgò×ã×ÇOŽß´÷Š´ßŠ?ñ]ö¸4Í óP´Ób‰¡ì!}ØX•–/>fRèÏ·6+äŸü/øÏñÇYŸ_ðÿŒSÂ7ûšãOKÍÚ”’\Þ-¼…2~Sœ»Î }†UÃtpñJq»<ìNe)+Ešž>øñ»ãŸŒ%ñwÅ-_Tñ/ˆ¤Ü%i‰ŠQnV8ð¾TMÀÚ»CjùÇâ,“éqéZ>¶Öp½„RLVÍHØò¹\ØÏ=zWµjþ—ânŸ¬k~1Ö¦Ò|ruKM1žXæ° 1d?ÙÎ0ÁËË€NF+«ðOì×à›««;«bmGP%Ýá…¥"+›”q¶3pB£§å#œ‘šúÊt¡ËdxNRr¼›|ðã\ø€°j?5k#¬iK£I§Þ1Š'/Ú#•¦9X€Ue qÈ#Ô¼4ß /.>#xê8&M"WÓo-¢Ú©åÜþíæˆ¯ÞhÌ\ü¤ú/Ã66Ô5Y~'Ã=½µ¶ÕÒ;¯9·ZRiYÑKX1 1*0z×m¤iÚ‡€áÞ¿¦Ç¢xãÃÞ9¹oê&!6ž4q+}‚Ý!˯ˆÏ–q½F{Ö·Åî´ü5Öô¿ j:ž“þ‰± æv³ZCæ-¼³ciuU 0O­}JºF‘ñÆËÇ~"M*?øÛÆZe¬±ÚùÃOÒõ #o"ÐËR qEÀö»WែáΣðþóWŸÃšb•m,¡ýúLîÖVl¼×R¹tr~ú>HÍyI§\ëú×Ã_Ùè]j²kº£jžÓYÿ³ÚîÎi^àFЂ(¤ÆN8UÝŠ¹iáè¼3­?…¼u Ö“â Bë-¹Z¼º€*o@²6K €kåÏ‹ï}ðgÅšf‹â};Y‹ÄÞ‡ìÚvµöiì¾Ñnqº[WuQ*¨!+¸dc<Ô&Ø|gð*Ïârø§özð´žðÿØ­æñ RßÍw-åãƒæîó‹,k# òÉ œñ^}ãï? ï´ý7Æ¿ ,äOé´»ÉË‘£§²"2DÜù‘3Ë$Öˆ~ xäø÷Á–þ’–5Šx]KÛÞ%²n2\íÈLïÜY°£>¸®Å_õ­S_ñ³à? Þ]hÚ.£-´÷Z|OyÃB’¤(ZBFÿ3x”œ më“üwðæ·â‰u߇ÞšF‘¡ýŠM¿,ÒÊÞh¸ßó̧Øó^â/Ú&ëâ–ªÚ_‚4ȼ/k­ýžÖ [(`Š%%‹•ùÍn¾Õ½qðëâ-§Š5KmL¹¹Óôûc¨j‘mš/-M b 6ô®KÅ4À’ø§Â:Âê:-ø`ÛN~Ëþ¸¿`ºÁU+00uk oƒ^»´˜É}gâÑ£DŒ±šü±Œ½Ãð#Ç95é|9ñÁs¿¼A¢ÜÝ‘~ººi÷B[È$´|_-ÄQÑ·–͵›î·Þ"¼£UÔiÞ°OøKV´±Ð>ø§áMWAÓ¼!ã¡#iz¾»k,qš;{b=•«<ÂhÜ8<–#!Q>#Ò|PÖº_‡üK©%ž¯û2ØIn’WýÚ¬Œ¸Ü¡°z}kôÁ_~-|0×ô¿ü~ðô‹áy?²ä—L…Ì‘“tP³Æ_°:­xoÃÿÙû]øçñËFøy®,z‡…RÖ¼M®[¼v‘iu$ªd•YЪ ”ƒ<c…ɯ_ñ¾~Ïß|Sð]k^M PŽÑufio, ôR‹pl¦| ó®ÓÈè9ÍCÜ£éÖñ÷ìÿûbx7ÅéñQû6¸ïgam¸›K.ÞiÂb݆<¥Ž1† ç ·½kÏñ§â§„þ"x»àÆ ÓÅ7 Ôb–o騒—°x¢’#0o”7鼯V×Íß 5ßÛ[ižñ¦“æxÿ[S·Õ¤7WOasm>pÈ$¼ØÜ2lý¦ø×Ã:—ÇMsCøU¥I§Xø–­_[Kiµ,tø­„mm3œ‰­Œ’7ñÒ±š±iÜñ?'…>kZÿÂýVñ –›«Ë«j¶¢áç´ "© –(YWn8#$W¡øãÅéñOâ6¡ñ§KÔo¼-¤jBHšÖ4 ½Œ X‡”ŽYYØ)= ÖOÁ‘câí,Gð³M¹‡ì×[ê±:´Vé#|é̇'”a\Ž‚½ÇÇ_u†>Ó¼#ð‚)/®ck6ZðIep?zë‚£÷o„=9¨ñž¹ûNh¾µ·øð§R¸ð×…,’×Eµ·†%u{›‰5E q$ÍŽxbHÇQ]7‡¬¾ègVÐ|O¥^Ù[ÛÞ'Q„ÇöËÛ…y9r1 7\‚E})ð¯â?ˆ|G¥xžãGðÆ—mªë oŽ˜Î¿c6šx?h»— µ»Nÿ(æ²´UñTºÏÙ~,ÚiÞ2²¼Dš8®X- •pçæ>jbFzБhZÖ‹ðò í ãS}'JžËìÖpܨ =µÄ$yÛ°HÁÈ#¢]摤é:_еI<)wçxcÄZ5Ž¡æO*âÖ1¸î»%Žùâ¸ë?è>3ð_õKÓÄzη¥®œ±^F˜u‰'µb˜SóÅ/ƒï|Uðw¾ ñ'Ú-¾Ïs¨XKlñ†ySìÎ7Z°?òÊLÁ~µkbdzv‡ãm>Ûáf‡ðjÓí®µˆ®µÓ{z¶óÝ^ÝÐ䌪€Äã’½kƼj<câ»o øWNs£øŽÕÒïNeR±jPe…˜}ߺË^cá¿ >ëzÒxÃËuâO ë—ú­ÔŒ :…–°]E²Fr6Dd §TcŒ×¦|TðÅÏö¯‡ôñŒ~*Ó+»-çÑ/â\ÛH]NK8s™á”/Ë‚j–䉩ßxBßO±±ÒõC2Âg•[{bÉÔ4ùŒï\sé^ñsàgmôÝb{ãý›g 5ïm#egPÁ“{uHø#žx5½ðŸáŒ¼Q®¯ÁŸˆw²ê÷vw7ÚF§¨‘oÎÀ<±Lãå&ì¦ß¿Ôr QÕ4oøkÃú„ß?ðãeê÷b[¨gFx^ óж6†á8¥r:wˆ4mâOˆouÛé—R°ûF¡º#²üÁ“àŽTg ß"·¼»©hzæ¯â V{‹ÿ[ßãé·,_Û’LDya·hXÀÏB§§¿ëþð6£¥ê~%ñŒú†ã-zòÚÞÃHƒ/l<ª&Gqœ‡çÉ+–êj[é?¼Qã[Ïé¾#Ö´Ÿ7ˆ-N¿dg‘fº}Â^xÆÐŽ åcÜsœõàìž(ðŒ7:÷„5Ý:OkÚå͈½XŠºÛÚ°ˆ]ÏÈ®W9Á"¼²-^-3[}C_¶U»·ºm&kÖ—uÔvöäÆ˜<îÚ8àŒ ÷/Þø?¨|IÑ!ñN«Àøçû8ê^´ñV…ÿŸ Xʺ„³ík»Ÿ²  °`¢–2& Ï^ükØ\ZÍm"[ÞNò{W{1l¾dwš<®²TŽ2€r2ç^Äÿx²Ó[ø[5Ðÿ‰:|‰sªŸ´4÷v’°•DÎÄãÉØ„Ǹï “âTöÛLÖ-lå»»PòÓ-¦¡©‹ËQÒQx£‘ŸZ†€òkááù~ÙèÚ,—ú¶¦ºœ7n¬¦ÚH2… 8ÊíWe£ë^8ñ]ï†|s®Ï6Ÿãxµ…µŽñ *ƒm™èðPî@Ü2œH¡† ô/ë^Ð~0è?ïô{»¹µMFH4¿ìÀM™»xÃÛ’vî ËÁPß…y³ã=Áß´kOÁ½/ïþæÒâ#[Í$…3ÿM‡Þþ,ç¾iÖø@¹ñ£_[øŽAo£êÐÜé—š›FFŠî2áfŒŒpáƒc$“\.³á½ûÅÞñ…¦KÉ'ÔôÛ&œ³¼!QZ ìÍ‹“ÓÔÔ—:‡ÅO/м]w}åβ>Ã-¡R¶l2°[Oc R,~|`W_¨x÷àõ·ÂûCÁ:•æ¬ÛxfåG»…’{ÍvÞmš‚.I”ò­G☾!iÞ;ÒN¸±¿öIa©%÷rP¼gÞ¤)SíŠÆÑõ¿ˆz†—ià?ŒðOyá-A¯’ÚõdWU¸ˆüªñ‚HóÐa‰ž˜5‰~+ÿ½Ôõ/„¾*³êK”¼ÓÔÜef_3l²1Pf‰$2à0s]†´iµÿ‡_þ+i uâ¥ø‰%´PizJ$-^´ÎzÂ]Ð&ŽO#š ‰â²øÄzF©û?i¯u«é¶"î0þB!”˜T`¤‚z `ײëú·‚üü':m†©o6ºÿð]hz¬bhlõÁ.-­åß¹Ç'±Óx?ÃIu{¿·¹‘öîæÒIThgÚÛà AC¸7®=kíâ¿…Þ×lt_øzãÄx^Hì/’L-•ôSòç$a™X–ä“°:Ð.cçãñCÅ:/‚<7£Þh:ƒ+Årî§›q¡S8*»²AœW}ð©|i«üM?²ß„-t»ïøÆøÃ¥\ß¹ŽL˜HCb6DbcÁcКöOþÉÿ%ñfŸâ?„ïciͺt‰¨(¸h¤óç’h´NrY”O'#Òµ~/ƒu¿í#´mBÊÊÞX­4§Ûö™By$c»·Ìfnz |¨4ø9¥Zêúž¥möy&—7ÎÔƒä³6öÞ\¨÷™ðûÃ^Ѿ:é~ýÒëÄð™ô©ÓVÃoa¬ÚÈòF—Ê|¨Ø–Œ¶”)ägÛ.µ1ðïEºñŠ5˜`ñN—k]:ÞÅrŒ‹-ð…÷ü#6=ÍåÅÔÖå£Üì¬6¨á˜’éšî¾Ùè–†g4Ú¤ž*ðôzŽ-l-ñ&¯i)2þñq—1.ryܧ5ä?¾ë¿´¶ð£á}ز»‘u«ã‚<©cSçC Bsˆ€òü¿^Ø©h º½†­ã8"ã]’Y?‡ãÔ"[8Ï ÌªÒ1ÿå“0Yuè8§xÃÁZ?‰u ë—WÖÐøzÉ,­ã²¶rL±Ù¾ÿÞ rÌN['#Õí>2¿¸½øÝi„ï,5›èÑhöîYc raUb¹îøÂîþ/”óŠñßÝh^ПQÕÜêV¶ñê'O¿‡È¹´Ô4æ\Å&ÜÛ~áê}éIü&Óü7ñOIø‹ã_ iϨGÙË7ú+\¹ Á<ì(÷c#ó¯-*ñ?¼V¾Ô<'>ŸQ5…ì‘!ò ²¸ò¥‰È A?tƒƒœxª>Õ¾)ë¿ ïׇtýSþÕÒõ ’#tသÞò49hÆÈÏ$g¦kéwö‡Ðþ#ø âÇž¸ÆŽÂû\¶¼ü«@º@ÎÅ‘,‡HàЈÉáI>.x²í[Mi¡Ó´é`kqˆžÉ’"x$0<{ñÅq>×ãÄÿu«ÙìɦÇy«ØÜ* ÍÎvFÍÑù…ÌC°í@~7Ð.n´8¾&x[S‹Rm¥inEó±|U@nî'®G5–l’F+‰£`>_âJó¿ßK§]êv¾Š]6óNµ³2t̾|,I™@ûÅ€9Àæ¸?èß´&¹âË _T±:¦…®,¥íž‚g£iGXüÄ ¤á×1_^źWì&Ó®j˜%Hd*®#8 N~R¿)ê{PcãÏüNñ'‰wø_ŸTºX#´Šêç ψ1Ř1 qœn9俏_†Þñ-î¯5…ò?öµòÅ%³`†—bŽ3†2}:Ô÷ò7ÁÏYjÞ¼¶×%i¤³·©–é2sÈáŸ|Õ÷%¼øaÅ k+a¬™Œ&ÞêÞÞІÄ{sçÀì~f$h'™Ï€®õ„Pê ¾›¯]yZÁš=ex˜GduäïZï7ðß/tÓogâV‚òU•.lì|çîËÚAà’1Y:·‹õ¿‡Þ9Ôlô¥¶¸ÓõKÍZò ˜1}ºåæ‰×‡ŒÆßáÆ+”ñÅÛÝ铸»J·]{Ãú•×Ûm$ ú}ôÓ&æßœª±Ã4lr­œ9>Gà×þ"é'×.4i4¥’âXÆž³ù‚™ö›"’[i¯ãTWx;ÅÞ-Ñ|Kwà½KBþϽÑlfÔ4o<#tWÆÑ›;˜²|Ø&toÁƒÓ5ó§†|ñ%®n„`I&Ôb[›ÿ±·­oó 2: ðAõ™ðºï⛦êú­‹É¯kÒÎÚÉerÞeœܤç.ÑÙÎßZô´¾ñw‚§—Wð¾£oi«5›ZÏ!K¦Â𬂨Y6‘ƒÇ%`6êº ÕuëÓuIï§3[4©ž1ƯQŽqš\¨ ¶^2¼ñØÐ| ].ý©—5ñˆ¤1¼êÌ œÉ´Ý3ÔŠâ~#ü¹Õ¾k>5ñ6±ý£¯hWÑþòÊv‚ÿû*U»¶Hóà'pFk¸ â=?Áš•–Ÿ¾ÓžYolc¼«—†C‰QH‘‚'<ךévšÎ…uiªø×Äãź¹“æÒxöks1Üe¢8S‘ÎGLÑÊMõ8~ÓSOiÚOuVÑ5Õ mb/izUìB$6ëݤNªØƒ!þ=úšò_ë§À$—IÒd½ñ8ѧ“ýí„®¶èrŠ ]Ò(P;–Ç­bÞ\i~+º“ÅÞÖçÕ4«‹¶œXj0˜î,FòÎÔûI* íÇSÉé|ñ áUŸ¬Ô–×PHÌFB6Úò\±YXpЃ†R95‹ç\ø‡âv¢Þim‡|O¦]­Ô×,±jºŽžÊÑFOñ$ÑoÛ  ›[’+è ëžñ‡ÃBòKHu¹o. ‰¬ï¤Ü„‚â9~ò‘ž2ÜüB´ñÖ‹á->K]ùâðÍå×ïl¤‚Wýý»`Ÿ•×;Tà©ÈÆ+×~)x_ÄúO`øiâ? jž²¸eÔlf·xîN½‰7wïíåÆàW,¹ ã‘à[ýb O@øñá}_J¸“HŽy§ÐJ¬¹%'F‡$¤ªI(Gñuâ€3¼!u©xÞûÀÚö‘›xV{²`r’Dó“÷â?xžÏI¼K jš Ÿ‰­ï³|G¨êгÄé _ ²C u¬ýv®§wñÄ’{æd[ˆT©mÿ6ÒT€qž•7„ŸÃ~ñ“kh×:~±ØÉ(;}E?Õ2ç•i3SŽ*Ò«ðý¿€>>xsM’æùañö–d[»ö>SË Ê€’t óêsGÅ/ éßNƒªÞÎ×É©Eö[ù@~ÒFõó \¸ä÷äׂx/á?‰å¾HóÂÀ±¦“Ú¾ž¿×>üEÓtË_Éw46°ÿg\ß#•bãˆÙãä3<œf˜µ;Ox¿à†«ñ#RÓ¤šøÍnð¡ET/nBº¾@)”åX{WâoøkÀŸÙ¾.ÒZâ3«–¸³…dÚÇ"ò3çrN@>ø«º›á8¥·øi7Œ|ËxüÈ^Úþ7…%‚LeUúuù¡®?â¥ÕíŒðƒxJÂÖ÷M± ,r£°º“jä‚cÇ Hë@üA¾ø—©i®|9l¿fVær$ò䟞…G÷Eaø†ãÄ^ ð¤~7ð•¼óÏåÁ5“Iå³ãu”ÇÓºX4ïŠ%ðe¤Zz¥”·6[ĈWç\áC–ãzã`W¥ø5¾øgÆ_Ûº­„‘]ÓBÄ׌1ó"ã;±Ô 5cÁÆ„±èòxKÅÚEç‡5ˆ¤tjq´í—?1Ï÷¹ÇJéü=.›¨øÂ_éw2½Ä°¡‘倈.mÝ2U[¸<ÖïÅ áÆOîiQ—¾[qms Ì[â;8Yàœÿ 0#5Ÿá/‡0hΖþ×fð»ÛÄÆ.gE|à"ÂÝ#b9 Ž(¢xKZøo©ÞËm3dj0Íõ‹8EPÜ :ØpsÖ³¼=­øÃÞ"v‡©_éÚ³ÛaÓŽ¸¯BðÆ—á_øÖk›¤²Ô‘®dHߨ#np=ÍnøÂ^=Òï,%µÕôþB– J1ü_7piß Ïø[ñoâ}¾µs«ëú!šá6D²ÆUíîÁ<®ÉW cÛ¶+÷{Wý¬à—?§ðvðßRÕuÄÒ¶Ei²Ú_ÉqçÜ6Ò¥d3œb¿æÖüw¨JºVµ½¼¦HäPEùãoΞ‡¸ëI­k8ðu¤wZîˆ^["w][åíå…¿ˆíúóÈ"‰FàyÇÄ=Dx¶M{UškFOšk ¥ ²¿C´p8$pIÍ{ÂÁqð߯ í-®t[R’_Mi|‰áS‰QFpŒ¼²·PN¹Hõ½ Äæ×Sñ/—¤.ð°M 2Fñ9Àà1ôª¶ý•?ŽãÐXZ2…a”ðAz~"œ¶»ð׆¬5Ï ëšÔ𔫍è»Lk‡ daü$>zV‰¾øbæÆÏÄ·ö+o¨É*Í,ÑmÝq‘ÈprO¨æ½?Jð¿€¼Muàïn –(ç†öÑŽãue’3‚ äsÍUñ džoå½Òôkõ™àSnž_XOJp2ÄþC­@V¾]Ciék§J8ƒ¸ó#* `¹àn?ZÅø„-<©hvþ×'¹´‘V_3hóË`£ãÀ§ò­].ÇUøk¯-·ušeL {Èñ ä@Î}+»Õ¼8~/Ú®§aok¦§–ªåOÊLy ŒzÒl¸œÞ«a ëÚ=Ž °K¨Äù«´±˜œ°àšOxßÀ?µit¿ŠBïG ˆ€`[ýâ7$ðö®¯Ápøù‹x{Fò®“OƒÍY!L˜$AÉÂóÏ¥s2ðýÕÌ’ø__ŽôžEoµðÛ Cœ€jtŸ´/ÙÛÂÚ«ø—ÁóÝiæìèŠæÚmÌ2Z3ÆxêHÆkÛ¯|¥ëš¦ù£ŠåŒ«¹‹Ô<5à ™5‹]L¦—vèVÖünd›ø°Þ¹ô Jݦ¼IðËÄšµäš&$+e–‚(&`$“w;w¾Ü“^wñ:×^ø[eg%Öp–­¿}¨P|ÁËžsïÎ+ª¹Õ/µ‹kmgO–(à½c5¤Ö¬$Ê c!¾˜5æ~/ñŸÄKÛßÜéú¿—cmw!•„A× eTŽq“žsœv gsðßÇšˆ|-oum*i÷W ckYAHÉþ"Ýxçšùâ´Ô–o±»ª|­–Ú8 vçÖ¿/>ÐǶ»Ôµ+åÓu¶¯ÎÌÀRqW!ioµTŠá-¤bເî=FzÕŸ´K©ëÒD–ÞÌ)R0 ºƒ8è:ˆizfqÂ#\n“ÌHNÄ(9õ  ŽgHå–ÂÊs*oIü¨£ø@ÆOãȪú~™?‰w Ó¼¯µXiúý®£#…}’­é¸Œ7¿8®vâÃV›MŠÇUÔ!žÙyX `å±î98ôÍw5ºÑm¼9e¦éšU´kmÖV¾JîõrI7¶¯)ðN·/‡g¹Öty.î/$*k¤@#1ŽjÐÅgb—ÂËVYÒ Ÿ$dí‰qÝ’Jî|9Vâõx泉˜,rÈá"9,6äŒ{â½Äpø[ÅZ ¾)ÉÈP^æï ½‡#A÷³êEx¦£§9ÓÒ=@ I˰ ‚xÝýÑJïüm†uÛ›«‹[ëk¹ ‘ ‚ÎÎØã98ü«ËûÄÖ.4ñö[ku“Ë,ÒçÑ8Åv1ø9o­•Y¸ø-½žÞÙ¬Ûïê–uѳ†n"'-$œÆ¾¸Ry÷ W0mæ‘´¶]:g’`þayWf2Ù c·ÔçßYx‚ÿHÐäÓb¶S}q€oOϱ@ä¨>¹À®CNÐæ²´’êùþÛÃw‘ mÁøàz÷­Ý+Ä·¶1Asœfáã)å´dªƒÜ.qÓ×4s·ZH½ÝoÊ%Ú.i_8SÕ²£®;R ¯ïnL~ŠmJ8Y.±Ÿu_¼IíëÖ¹„¶/zòG,í–ŒÙä  cðÚi²x²ÚÎd‚òhbw"J… ÃÓôϵ»*]éÚ¦‰;%òÆ.n~gÉç…ëÀÏ¢¦›Â:åýÆßNÑÈû[hùðÍ’XŽúõ¨­tÛ]:?°iñ¬@šER£h÷õ8íÐP4t:çŽÒç^Ž×X´|Ñ€±[E¹¤ÈÆKs¸×1a}âe¹›Qµ²¸–ÔªïÀu#±>£µw3k­›iÚ$i-ÔñÆG £¹n½*OíýróJ‚ m?Ïû1d.çrŸÜ@ASž¤õ £ŠOi–—:†«¸{™Å ;™ƒuGzš½7Øî´5·73Ø„Ãí,UzÁ¬»Û“h$.SsåßhçqtÀÕ³áøµ(ïáŸÅÀiÊɺ2Ø’rãŽG@3’=¨BßÄ–3íŠËT¼mц˜¼c{Û'¦=;Ô“j÷²ii ÙÁ3Gs6ÿ0œ³·d@2N $ñ]o‹t i‘Ç$6Âw(ìvqùÜãÞ±6^!²i¡Ô§6ۡس8&4g+ÔpÏÈ#Šäoþ!éVsà_¤vR¬!FÈͪŒ ¨Ú6ägœ ÈÖtÛ6‹íÚµã]Ü&! h›ï…c’@iã/xea°’Þo&XÄ)BcœáaCíøÓ@D–-«é±ØYÇçO yu€’.\ó×ùUë-rkí6?øEµh,ĸÛ#…gÚrXŒ~½ sž$¿ñ¶­s(³1évQK¶EÆÂU»¹ÜHíž vRü8ÑJ»¸µÒP#H¾Ñ$ç(®y(žµ¸V]˜ŸL–==kÜõ­kĚ΅§èß|(Ú5¡TóîæÉ’F#æ*‡.NrpI ™ö·g¤øJåí’7òŸ&æ2|ÌçÏ Ç5s§ü@ÖOì@Q4牔þðÇ€\帹ϸßü;£ê0êúÝõö¡ªÃ•Ž($X£B£|£¯®}k¡Ò¯¼[¤Ë.F 7y°¹VA‘Ææ8Üy­˜ÚߌíàÿNׇÛ.®E"–0LðN»Ôטx—LðUΕ‹ýµ<3$ŒÞBÌZy ‘»äè<õÇÖ½OÄÞ Ôµk&¿ÒSͽ‰öâÛÈ]—w :ûÖæ¡?€¼§[¤öqAs¨¤qYZÊs<óœ1ç;’p(–ÐþhÞº–îyå»±X'Ú9Ÿ¾Ì½ù{Ö·…|ñiòkšÒYbB¿¿@ÊŸÅ×Àt=kÄöëÃg¬º<·ÆG–5c"E³ïdsŽæ±’;-W…-ôû‰ M‘¬3ÊdGå‰É 8û¹>”ÖXØÏq寄ašõî_ %ÜJ £7Ì@ô#ò+³ø•¦ø;ÂÞ¶ðnqm©x—Re‰­¬à+ÌŒ=°çë\}·Šþ&øªxáð°—K³Td‰àQ 2(à`KýÕÀZO Úë~¾¼¶ÔôµÕ'‚ÛÌv¸cH²tÃŒîbÝ9õÍ[Ðô¸¼ §M©øºCqw+ tªÆÞêÁGVõçÓ5™â/|!øoüA§\ˣݤ×0ZÜI¶[Ɉ'*¤ç€ÁÈ­Í/Åw‘iÛø‘m./¯î„P[ÙÑ+Ànäg©àRÜ\¿„\Žý ; ¹±«Ûé3ü±³Ón~Ñ-¥ÁšàõùËç<žIÅwŸ³E¦oûYø3RÓãD7Zf¶»Pa€)N;.Wñ$W”è‘øzÒk[Ií×ìÓÀ²ìˉÇ>™ç§zïÿf_ø{Wý²|gáè„1 ;Ä ó™R߸íÆqþß–ÊÕãcü6}¥ÿ ÒtOöEø€šËKV6Ö÷èñ w[‹9ã’2 ú°ûþu>h'…v‘Y™.nï/o®có[>J³7G¯5ý^|uÑt½á'Œ4ÍJ¹†çF¾FŠA¹X¬FG±q_È'ü—ZÓ|iû4xÓÅ×·7:O‡,¯®ï•ö™ÞxWË9cÉ8ïšõs¨¦“82ÙhÑö¯‚_OFžÞ h|é)ã å,™Î =O9Ç5éÚÖ—6„nc{è%[1D®5 @§¡n{wÍxÝïŽ<ºv³ohuhg`²(~Ê‘Åýä²1ËÊÍÇ\t®×ÃÚ¾§-Í®…«Y"È ³í…b c¦Üð9¯œ=hîUÐô‹ÝWKŸÃÖÊ©&¯yû½ÜˆÑÌìIã>•èž'±¹·²Óü·Š?šg@0qÂóÛ'ô¦èÚUÕ¥óÉsØËåªïäùƒœç§sUâ°¿7›y–dHFpz/L㎜P]Ù^ÏQðôZu®tÍ*@ïóº†ÞsÓïëÖµïoìd¾´–úY­Úæ&ag ²0ÛQ1·=}k– q¢ÞE“<¬Œ²1˜p0~lžÙéÍtZu߇Üòçû½³Þ˜;ŸAK£i7Ú6£ay©Ë9¾2-è¹A2!c’Odœµ¡á\½Ö--|AçÛi ,1;²áMºð0€tï\ÏÃÏ\Ia}¢Ø,WrE(¾ŽÎI™pYÛyÀàc95èz§Å-3]¼…¢“û-c‡ìÆÔ8”eÌN3Œzõ§v#…¶Òìo5YŒI<ÐÃs² Ld3ÆänŸÝÇóPj>‹KÔ®îFi>ošg\à„?ž~õ[ñgÚM•´ž#1Å{,¨äBC/œtàn+Ïtï 'Ä7ºªÂ.U’kv†L… 7=@ü³Íwo ÜÏÅÕ“:+¬ÂÇšy¡™Ê£9"‹Ê_Þ=ÅìÖ7òn·Ò•–Ö<€íÜ…É$Ñ´/_Gk4. l$]Ó3dùjNqŒt³^£áý~ CÂZç…üAöEŒÂK• ¡¦¹ÔQ—j*Ç´rG<8mAð~¿©Ç£ëŠéVÒFÍ¢Ià…ÞT(ëóð1Ó™ãøÛ\…¯|WrÞOîí­d·#Š0[n9b9Ï­gÝÛÚ@e—ÃQÜOr>Tž h¸Ë¨-Õ8ÇZ·á}CIÖô½Öu,Hñ:ÞP¢Þ4BBî£w8 R<Ú-G÷ Å¿0]7Ÿ"ÞD‡‚¿tàçžÃ3^“ðÓBÒµïhºOŽ¢IÛKžKù–[hGÌBpsŽäƒ\Πþ¹Ö³++Á`êñ a²W‰8m8Æ;Šõ_Máÿøóí^,‘¿°5›E²½k@Qà³;‹ì$ÄŽ@ü*ã!&¬U·ñ/‡õß'ÀƒeÆ ä[ÅÝ/îÓ!Fx“Ððy|ã;_ß¶n¶š•ÍÓ[,×äÆÎ退YC3+Ñ~Yêÿ´ÝFîÂØi×ÚT÷?aš#›™l\°ŒJägÍdÁ`1ކº;kQÔJñOŠŽó073&ZFQ°6Á$ÿzÚ-_S&ÏæSà×Å?Œë_þ6øÏSÃ<$ðv³ggoçK¦iÚ® –Ј^@6ÄÒB¬ŽT±‰¼Åà×ÐzÇí¿ã?ø—@ýŸÿeí"Û\Ô|moŠö–j–vö·Wì¢K‰dØì%ˆÆ ¢³'Š÷¯ø)/ÁÚgÄ:‡ˆÁŸxsÃþ ø¥¦Ø·ˆ­om1©¼ÞVX.ÁÂÇ&xBéƒÆÞÕ¯-|C¬ZC®èÚVƒÇ%¾¯xs´²¬prÀ#‚1^Õâ-;À÷Úþá¨öÓCÐí´Û‘92[ÞM¥#Å4̽#†n ©Ü>nkÌ,<áÿˆ¾$|7ñìKðÝô»Ycº…¼¹Èx’ ƒQò¶r6ñÅ4¬dÙKÅ~ø¬x¯Ä^+ñ_Žou½6ïOU¹Õž—w“Æ!¼³º˜åÜÂÉ’ŠrAÚ¢¼»NÔõíÅ(øe¼VO|¶63_Föö–z<Äÿh…Š“$¤±Cósê+Út/ü*ñ‡†5ÿ xûPÔÁz–§â>ÃH¹E¼’öÇcHŽ`y“mŒŠóÏ|ZñgÅM>ÓÄ©ãmWQñ¯äióhËmmk§F/líägòËn!‘p­·98Ï5iÏS›KÐ4¯:½¿‡|W¥D¢D‹Kòç´¼µ’%g¿Þó½YN•ògÄ}sâ•ï„û^¡®øŠþËPŠu‰cS§ L¡™UYŒ¥¶ã$)Ú3Šì#~Óž'·ð…ôï£Ú>‘¦x^ÂÒ6°Ùê<´ºfÀ!âLTqÕY‡J–Äx\wW—¼ñ³Ã üG¬ê¿{%ý䳸í÷åáÛþ=×òË&>ц¯Ð|cø—û@|øYñŸã¿À¿xDñm•ÊxSÓÝ=-®ÛÎ]ÈfRèöñ(LFèTÔ‚|‚ÇÁ¿5k xNËÄiGFMN+ÆŠuÝAâ–Œ£,÷%0² »ç8¯>ý’®’i_ 4»]5®tÙûJÕ'-c wlÉ$RK÷ÄÄáÉËÍqQ'{E°°$¿O(›Cñ'…´/ |J¿¶ŽÿÃzæåY#@žUüJíu¦É’LwÎ¥$^€¡îi| Öl쉟Fñ|x×Á,ߨ’i‘•#•<Ë‹ù¤méºÞS¶ÚC ù ÇÆÖu¯ü4Õ|8¶Ó_ÝjïZó'¸ ¾Ÿ2IöxxO9òÆFß’h¦ðO‡þ øÛÄ×>ø“ã™ôèüewga©\ ÐÛ— dû’v Þ ίhøÙûb|h·øGâï,ôKÍRk¯ ÜØÚB"ŠdwÛݪG»tï‰ ·žÕÂxá)øeg¦ê=ƒH×-¯l`»Ô4¸Y¾×dntqJrTH¤«}Þ„w®wáýðXÕþ$éQß}º[Åðå‘È]ÑÌ«<“¾p©àxñ@îÏ1¸ÓüM¨ÜZé×z\·v×Öö×DûÚV·(X1?ÂE~¤~Í?¿i;i>"x¢Ó<}{s :®© kstÐCÄvø€eH¶î*FW89&¾Õ¾%|<Ô§u§øVÍ¡[‹Œ"ÜO}ÙöI3UX¹|`ð+ãÏkÍá–ð—‹ín¯Fœ†þÂk c{™«1òÉ$Fb©=ú×!û=~Ö^ѾÞøwÇ:V¢³ø€iZ}¼K1¸h¼‚òIä ¥dUºÁÀñ?H¼ø÷☼[à]Jðý½ÌW7ÚÌ™Inâqœ $(årÌ7)ƒƒFªHú{Bø‡£hºN·áO µ¶Ÿ-ô;ô·¸ÚVuL’Z¸È+¼¶Tç®zéK¦Â¤êVú~»w¦Úë\Ÿ.ÞiUÑg±ÙIåHè+柇6?üq©^iZ¿†tˆn´+S´‘¯.†è2P‹ˆc@û÷£#WuÂÿü1³Ölljl×LÐ,¡Ö¬ßRyZ8â!žØÙ†ÈòÈ,ÀŽi“v/€<-wðLðÇŒ¼¤Ýßk:ž§ayfÐù ÈÖ×n¿tUã-’­vðœÚÜüAÐô‹>é­//íaBÑå"¸¿€Ip|”$¬„€Içšî|Qã/~Ð~Ñ5B]fåµ&þÚKx¥Xâ¶i"·P) 9/Ž8w®~î+hî.t? kZµ¨Ñ"y-,ƒ(4˜DËÍ+Q»§¦(â7÷k~,Ô|2Ú,Ú6¢X|Ô}Í-¥¼‡Ê‘ØD„.Î2jY|¡kúŸ‚­õ è.õˆ4ÈáÿV´Þ0X&UgžOzô?ü-Òt+[%þ«ew§eqmæE+Ê÷d¼îÍÝãv03\棯øË^ð•¥x/Kk¸´9æÔt«âJL×—d Ö3—*¤˜øê2jî€×ñŸÃŸüÕ¯ü¯ëZf³>–ΗÚ|ŠYa»(ã¿8ØÃäôÅx^£gmâ­gXøyw ú‹ø§D—ûCK-ݺ¬é9ÇÝÚÛTô ɯ`Õ~ü@ñK'ˆ¼Scubò;Îuw\Eq!m»$É,¬]q†íÜÖ/Âû¸&]ÇÚVØÏ…´ùõ››¹ÜmŠk•d)ž7©yèNiåüãÿø[Añ—Öõ{!ggpû!‚;û¹Ô@À¼ü¡h¸Íw~_ xꦹ6œ÷zÒ^]\kR—8m¶`ŒpÂNN3œgŠå~ ^xƒ^±º×HÒGß+@@X×°Yø[Ç^ñðmΗw¦x³L°û-ìÞbùÑF›@ß'xo•Ësбâ{Æñ.µ©iR•¾Õ,d>[…É•…”dý㻟B½)04þ/|Ó>ø66Ôü]§ëB=z;W6.%’îùO˜ÛÎy ÊU1Ðàáþ èß.~$ø_YøG`n|Y}q¨Þ6›xwB²F¢PÄm2) zãV^¢èñøëCÓný‡u„ÕçŠh6œeV­¶R„Ž1Žõî>øãÍ –÷ÃßÙ©â]aÄ7Zܤ£e²~î3´0©åªÕ¼u¤ÍgáM+G¿]?^³Ô-¼)¢kÜÀYº2JÀqÈ#žÔ¾5·øŸðcㆯáÿˆÓ%¾¬¶>[¥äA·Ã<=Ô”1ă ÙùH#§‰ûBZiú·ƒíXYÏ<ú{^Üj¶ G<:­¼˜2ŽrJã=†z×¼x¯â«ûox#Úߌå²hoµ…ÊL«=½¤P˜®Ã~t€ÆxÜ£¸ÍfâÀó†Úwˆ|[âmWá7ÂûÛ/]xê{m=î|$} f_´È‡ÞNX®°à׋xîãâÂÛÝRoŠim®êºV¿%Ýí­ªlû-Å”Bæ¶nIŠáq!õÎ × i^.ðÃË ­[ÅÒjÞ0\Ó´ýc”·[-ÞSJBäY99éß>‡/ƒõƒñòù¾"ØÉ­ën“¨¹³´ù|éô°¬²)èÎ0AR#á~éØx·Â7†mgÄ— ­$±6­æFF6±`HoU<Öf‡ðÆM6ÛûWàf°š›qû£ìÉkG(0H-•}8+ÊGÅÿë:^·âktÛ—¹¼2:ý™qX„np§),H…kAð{â¯ÆïÛø·LÔí¢>ÄRZÛÉûÙH÷\8vÊC‘ž(Úî`øwðêÊãÆ—:©“N÷ú]Á`g¸Ô&›2É1ï´îfNkÉuŸŠZxºoxyžÇL×´¦k{›”>OÚ™ƒB?Ê¿2 õ%Æ£àmÃÞ½}6÷ÅÚ>±£Ë|òYÄU­î-%òîda”evc¨9æº'R+Ðí<ã½29´ß£yHÊìD ÐC‘ÊȤo²0ôìz¯‹¼s¥êº?Ço‡WW § Y,h ÆfD~Tªx$®FÖî’+ɼe⦭ªøXê¬tÝWÂÐÄ·)ûBN“âU•@åŒg ÜíY?¬|{¬k—Ÿ ü|šO‘ªi7·ºdÌÃäíif‰‡C2¨,:3䦽Kサoü)Õ ±—æó¡|ÂòrÂXÅåŒô‘e\ žüV€|¥ðþ_ÅâÛ}æ)ÿ´¿²f¸“Hšïç@èð9û̇„ï´šõ‹xZßáv¡áßÙ¨V‚]Mïìß.meË»@Ï–@é«Ô׃Jñ5ÁZÊÚ…ö‘e§F0e¾KÈ@~[¨;Œ`|øáÆIȯ›|WàW¿ø­¡øOLškí¤µ–ÚÛ-%ÿšXÍjÊ~ò¡€ã½QñgÃ?„°µ×‚¾3j:•¦³‡Ôéú¢ü°Ùß#´¨®ƒ;‘·©#%Hl•»ák Ä_ 5ƒ¿­tÝ+ÅÒEkoe­´y‘¬È,Ñ£/!d‚ÙúÔš§‰¼9¯éñëPiÄ7 Ó‹ë;‰u·„m¹†<äcäƒÇ¯&Ó‹¼0µÆPcÞP31s\G‚?imàö¹'‚¼gg ÷‚EŸIÕ £Ù÷_”ǵp>ñ—„~)hcáÇŒ--/u;(ÞKa ¡]kÄðž ‘F©êE}àÿ éÚG†4ÚCᇌ%Ðt¿ Ísaic©È³ê6m.LkÇ™mÚ"ʛÜ/ÌAàršqÉáâ}ö‹{᫹nçÔ-õ}8-î"™$L‡‰O0rSf¼[šoÃO|/ƒÂ/âØ´ý^Ç/:†æ…o%·Ñð@Î*/ˆþ/øÛð÷MÓ¼áLÛÀÓÇ Ñ‡óì&ic ­@Ú[$òò@©‘kc¦ño…>x«áÿ‚¾%iÓËowý²NÔ§”Äm/¤¹DŽ Õqº7•@VÇu"ºŒ>øÍ'ÂßͪilÚũëZbªIqsev6C{ ÈÇr®²…Ãn\úgŒ‚-#ZøŸ¦hºNŸq¥Nõhm5vÅ,Ú/ï²§$:ÁvÇzígo|Gø÷ñg@ð$·M¥Ãâ¸ZòùD¤µÅª’òÛBÌ8-ÈÉ }*F{-·ÃŸXxñoîu |iàýYt»«[F‹ËÔ`k?ÞoŽ0$h˜+̠瞊üx¾ý ¼5ã[Ÿ|*'Zi ½Ó.î,ãX ¾†ýŒ±Îñä²!—8Þ9¯W·‹þ üJ¿Òÿfˆ¶ÖºËkºlÞby$)qc)#jÆ;p7ž:×’ŸŒ7øŽòÃâ?ïkZoïïÆš5&’Øsæª:à¬Ñª‘´8a׊s>"ñ³âÕ¸ñφelçÔ¤Ð5+{Õ+-ѳ.@O>aˆœ¶yÎ zÿÃX´ÝSã/„~&xsN’ïDðv˜ø‡Nš0Ö׺]é‘ w,3°}Ò­ƒ´¯#®ü"ø/ªë–ÇQÖd¶ž(µKéæŒºƒ4ÂGŠYcB–ù™O>¼W³ø"ß³ŸŠµ¯\j¾xÔ¬ö”–-©{.M£tȤŸ—ûÝ;Šùëã/|⤓Ǟ»63é~¹··Ñî—sYˆ$1d1âhÌ[T2ž@ Fs_/ݦ³6­¤üEÔ­>Ϧ®£h¬t)ê’2~U!2vçšúgGø7añ[Æ:WÅV¶¹Ì™.ôŸºmàEm–Ó/>T‡gXC^³u£|^Öl-<5â?iSxRÂáàÖ<¹#f‰[9FÙÜÊ0ÃzÐ øKX¼ð?ŠÞïJ’í¬/ ÕZ]6耲IvwmŽN£Ë|ÏGÒ¼£Dñ6±áéW~/T¸ÒuÍ¡ûB·ØÇÚÁ2C4O-]†7 ¶k쇳xÅüI,m;T×î¿°µ}6U` ’ •äû“DvËž¤ê+‰øãàOŠøYÅ}S°ÑìMÏÚJï·Ôï’².rRTe(ÊNcæÿjñe¥º\h6Ú\Ú-ól–ÈââÆF$FªJ€ÑL¹û¬9Šúá´ÿ ïcø·©k+¡ø/Å>Ÿ¨¼­Òé÷‚¿h•‚5pN>ëpkÁï|%Ä?†ž)Ðþ!Ú,ž™¨YÉ1²”ÂE¼ˆ³ÛÎÐÌʇ$5Ðøïái³ø/LÔ.ìwÈ–Úµþa}“¨c+# ¬¸Àfôæ„m>»§x^ïNÕ4åמÕ"¾¢’¶ðDû™²Œ†SƒÁ¬ÿˆ¾4ñýݱԴýÚæmcSKÔ[(&X¾d=̃€@Ý^sâ}+Ä:—ˆ ÓÞÖ ³ƒ(®p­"õh· WG§xº? ]éþðÄË‘ßN«däÇ .<µÞr0ÁÎHÏJ±ÝΩð÷Æ]ÅŸŒ¯föºêÛjöÖò¯ž<¹Ü¬±S$L :ÿ èk’ðÏü ñâçÂmvy¼5¨Ÿ´,ÒMóFÑ2à¤dœ¬í{Åše†©k§\Þjw¶×°Í«io( ²Äû·ÄÙòœ2œíR žk±°ðÿ¾(|Hm[G{Y´vžIã,žTZ¨ÆÎ\’ Ç®( ³KN’÷C»Ñ4Ïi¤KáZ]<¬dG“³á+ÜúœÖ·‹ô­Áž%ñ5Ÿˆt`­®YÁ=±'ÌKy‰*ëÌl§= zóÉj¿<_„¿´^yk>?g |ée6ŽÑM‘ª®Ü€rFé^mâý+âÈú7„|]â’š'ˆÅ¥Î™-Ã5ÚÅôvƒ{Á°r·Ý(Ϩ¾|Y𶳬ÙÙx’Ú Ø²\HñÛEyv« 7 … ‚Cã õ<7m'Àß ë¿ ¼&š}Ö«sý™ ­³ùˆÑF•ãpG;AÃ÷ëƒøÝâï ßiòø+ĺ^á¯êFþ-VÌn‚6²Ê¼EÿºÀ‚¨{ò1Œ×‘ü‚ûN}XéQ[Ú]]X­ý¦§vŸh:.¡m8\°È- Šûgˆ0 §9f‚®_Ót )Èäb€º=VÛÇvÑ´Ï xßCy4Ãit–Úò0¤h]>Îøò§æCÏé_5økÂ>ø›«øUñ¡>“£[ê6¶ßÚ*»$ŠÊè„Mñ’QÆ ðBŽNk¬×~/ø£Y†Óö{¿ñ§öŽ=œsipë0#[Û][ʾ^$U Ù|¹ m¥NÓÁ¯¥ü &sà|6ñGƒ­t¨,õµÔlÌN&¼°‰ãI'òˆÿYrß(È FÑá>$þÅø'âKï…0j~"ðß…µø•ø…àA*$ˆÒ…P›˜¢²€ŒœÕþðOÄFå¾ k+¬]ëQ¥ÔÿkEŠa{"4#Êù2Uàž½ë£°ðþ­ðS]ðŸ‰.|g¿k-…þ“¨$RAom"yÑM –‹ãØK 7+T<+ðçá5/øI<«j~×.ïm#¶­û²È7ZÏ=rÀ#®FqÍ<îãD?tM3Á¿×TÒüu ¤’ÞAæÛÞ[+e ¸q–ËlÇ/·R2+Òüvu µ7©±-ŸüL~ÅåIì\aÌl„6à8ã <׺YxËÇÿ¥´Ö5-uuí_B¹¹‡RÓï Hå„*˜²²*$däcÎkækÿˆòÞxØxgFš J_Ì´ŽÕ]Ú>Q˜|»O Ôçn¾7èþá1:„_µ ›{[A©Û Ѥ^Ã8Îc í óÎsœ×”xJø™ð߯šÇ‚înÚ þÒ`H&`†Èò%á‡b³Œ‘Ò½_ÅÓ_iž× ñÏ„[MÕmüë¤I¼ü‘µãIcälŒ‚4ÿ‡>+øÁðkÀ½Þ—⫯éVºÓt­^¹óQÆB­É̯ärKßÐxÄ76¾%»ð-‡Œ +ߨ8EÄçæˆž„•#8W›iV^"Ón¦ð.ö‹ôÓ2ö!ËÆŽWž¤)ýsPŠL¥ð¿ÂÚæ³y–v©§Ü_™naÓo7$`¶_˃°‹ØW·ë¾ ñ=¯†þéøJI/-åÊ;¨*X×pÓ<¼ãŠî>x»áÜ:F§,—–3˜^"MÌ3FwlAŒ6î€ö¯Ò_~Á>,Ö-tßˆÞ ñÍ„zÕ•µò;ÆÒÁwi*|’ð'ÀŸþÎzÝ5³®k‚͵!O¶á$·@’Ç·ø—nÜž£óEçÂ-GZ¾“S±éì¶ÿmr  ­Þ.»@ûã,{ŠÜÐü3àOø÷J¿ðþ­‰gnžeûXÁ½&‘ÓýdlJHz7Å5-Çr]oÆš_ˆ¾ø[Â^!¼†æëÃ2yV÷–P…šKi—8%x* ŽNྕÕÒÃÇ¿°ðô³G{'•7ÏS( ã-º £t¯œôÀþx4-FòÊX./¢¿…§eyŸxpGÊ@5èrÛ|4¹øŒÖk©ÜøkE¹ŠPó©23ÉÁGA¸íRGÍÕV•­ mSñÁW÷Vs¾Ô -òº°¼ VAÔÄz†SŽk›ñÂ? øã^ŽãáiCí:Õ¼wîÖæÛ-qn¥Ë–EÀ¯>ðKh:_‡5.‹sªxc¸žÇUÝþŽmáÈPã–;—èy®¿öjð‹ì¯§Õ<_®¿€üQ§eln€KÝ>kiãbuó"•²i»G@(Úüàx~-,k׉®iר—šeýµÁâŠ5,6†9*W¢ƒ•b¹á+»É,<_¦i±<7º—Øîî,âtyß÷rÔûr½&¶<+á;¯†ãI­åÜZEp÷[ À.elæÐ*ƒ„m¹ÚpkWàÕÌ÷zV­¨\ê‚ÒÞúúÞÝîH;í.ƒ.gEÆøÔà’0=¨Æ^4ñ÷Ç[øgâ~7†|g¦ÜÏm.›"iº¤v§î+ ì}£ ±Ú݉«¾ø§®x3Ã#S½Ñ¬uÍUVóàe ' àŽá]Èl x¯¤¾*üuñ?„µ­;áçˆÎžu[;¨µ·éò Ä·hpL2•REë¨ñ^ñ#Àþ×þ#\üXðE´¿Ù—·1\jš,D@.â<]O>Xd9Þp'<Ö€|ó{«kšWÅš×Ç#Hðõíákg×áöJɼE,ŠCÄŽNÔ‘›†=*i¼C7†f‹KñŽim=ã—¼Ó¥ó2)dtÇe8êrE}á¿ |>𞯪øóEÔmîôn¤¾›«[~áüßž5Êß2‘½Ažq_7Ùl^\èg•ä¥ ‹ªIb}„^‘í)Ö€=\»:ïËM"Êæ+©!:˜<®s½ÂúžsÏ­q–¾!¸ÿ„gFѯ´°÷×Cì·RZ\‚† IpÂ@: àô5ß +Ĺ×uÏißfÑíåxõQe0¸´Ž&LïPyWäÛµz¶‡ðÓá-×ÃÛmJäÃyªhÏy¥êÞZ‹¶_ÊÊùsJ´Ô_âuÆ‘>¥5ÿ†”¹exÌRÆ0èÅ8È9 ƒ÷«Wž->—Nñ‚aam µ–éÀß!¸fQŽÀdûWu£ßx¦â;Í#DÓd¸[ˆÚÞÀbß43Iò¤t÷{ÖO‰|'s¢éwžÖo’ÅìuˆDÅ#lb] å]F=@¢âhʵ¸µƒÅ¿k—É·†ðGü¨÷0ǘ£$­_øŸ¨é×ãûîDPŽ$Š;´/ç €$ç2g¹"¼Ö? èÞðõ¯‚õ]F={H -½®³o uÜ>dŠá>f˜)$ƒŽÜV~­áýßÁs]x†ÊxüÄgŠàφ…Á‘@?7¿`G¦h$ê£ñøzi4-zÜ5ü$[$ào;_¸=~aØŠíþü1µ×bƒÄ^3_ßÃq%»Ù•Co±óœ©ÀAèǯlW׿ ¿j/€þƒÃšg>Zx—UKçê–­o K ~!.woÿlƒ“ÆG5Oã¿í»ðüø¾? |;ø`|-”ÝÍ"Ç ]Ë™Ü#e!ÃIÍqεKÙDÚ =Ï–~)ø*Óáüºl:„WöðJ³­Ä>ièŒüÏÓÒ¹}+ÆÚ…|K–—=­äò¿˜m‰ò]”åBŸ¼Õ†}­oˆ߆¾7®»à+&oj‚¥ÚÝ—1]Z¬_0"6-ÀÀàw®+ÇÿF£áÄRÛ•µ–Çåã0Êg„ívŽEãrcæ]Ùº¡~Us9¯yØú³þ«[Í ÿXŠ[hÖì­ÉµQ‰!Æ7p@$wÆ=ëç¤ñk©øjÞöMCNM±<°Æ¡0ÃÊà  ðHüj‡—2é÷6ôëí{UÓ&XþÑnLaí˜I´Œ‘‚¯½vðøwXÕóÄ0¼±° ^hŒ ù¾ÃÁËÍ]ÙÇ„¾ø_ÄúV¯g415„,’ÃrX‚–Îä=TáÁâ¹N𞀺~«Û@Ë¢xoDB@àd/›‘«Ñ‰íÎzW[¢xKTð$ÑkZt’&;‘mrÎ ªªëœ18Ü3Œ3[M{s¢i0èZ–¡ašŸ[Ë }¨ÿ0LðÁ}ÛÛ¥Ce­/øû£Ááˆ+¯|1±[j¶Ñ½µ«ÅæD› ƒÕ•‰ÈôNÏÄZŸƒ¦·ÕÏ@ªßýjÔ¸·´m·zµìQ¤GËŽ7cIžx§>¤ŠuìZ¡*‹Èüûbù¦?;íã<öéX°M¦6¯mÒê6R±£ŒìÇe¿×ë@¹†Üê÷wì€I$踘 °u;½0ÜÍå}±$S³aêÄtÏ=k£Ó.æ_´]Ü"éÈ$Ú€+G.8ÔV£êÆií’§³åŽC1Ïn84 ³ÂÒâkXô;Xƒ\~`ÄËp~{ Š.4ØÍ®›¤Úy6Ö±…iZA#ÊêrÌq×$öÅvC«Î#R·1-³~'òk“TÐâÕc¸‘äÚË’B½r;â…µ¨¼3­Gx°#É"¾Òc,9ëÀôéš«‰µ_ìëûøoTÉÇÉUŒ<Œ§°àvýiϨZÁ­ZXܾÿ•B+†Œö xÁüê½ÜWv²»D¥|ÒYSae?‡jJ¸ñž¸`ŽÝBK: Ët¡X’AúÖôšÒ*Å©ÛG´TÔn–âÊçR† ’re†>B>b©â}¨iú®“`-™Ø¢¡ŸQæÞø¨îu›K­/ì1FÍ“·8'¡õt§A}¢­˜…-KÊ£ËRäíÇr¿Ö€4®onõg-¼s[íoô‰ Ž8p:m{ükjÏT°‡Jû]›Ø`„þêÝzã«tÁãŸÂªùzÕÞ†–Öc‰èÐŽדÓrr+oA»Ð¼=p“jWK&¦Hhãþäù†IûÄö$c4 ;©ðní£Õ¤ñö}•ÓÉE[¥7@€€ÛAýÚ– |ç'Œõú'4z~µqmªx¢‹«9Üý07 ÛFæ=ŠŽ|×Ê~×.üWvE¤­-í—dQ(Y%d%¸FIÏS_CxsÄ­vË«Kh–Pؼ lªÒ`xŒî ü]9 ‰ê\¿ðM¾§¯¤Ö–¡þëEnF  Ÿ3oð¯¦y®?_µ½Ó—ì±Ãe;_HÊà|·'” xàwü«ÕnåÕ’Ú(®§¶°žè šBü•Æ9÷8ÍaÙ=ÏÙ%½Ó‘fÙ½Ïn8sžW8'ëŸÂƒ$Î~Y|AåiñÚé_Øš}¬\G// C×%ƒz÷5‡â?Iâ9ëXaaˆÕ %|‚à2yn=\VÅÖ™âûkH¥ðÕÓÁª]ßÍ9Yn$Œ)ÛÇÝŠ,ôÇ~õÆhðhV›jÞ"¹ΣjWwϽ"—ÔÝ…4Z:o øOT¿Ö`Ñ­g0ÙÍÕÌ­²<îÎîwÌ ÓñŸˆü/m®Éká^å,ãÞ’O»Èà 9Éã8¨õÉôøoÉñö‹ËÊKºyP…=ÞC†bÜ€ƒ“LÔSÁZž§‡¡xu¼2v32³´×2yƒ|¨»GÈ£¥nO1áM®°Ñ gºÜCe6ë!^~KÈÌþ‡¦\Õ>!I¨ÜÛEãÙ…Ë)šKXwòäc^0õ¨g»ñ柧[7…4;Ãhù]0…ã…Æ<ùÚFÈ ýÐ¥±ÐWcaðÞïQ»ŠæêP÷E´•ÀÿeØdŽ>î? ÙÎÍ'Û­†©$Ie ŒGåî1È Ü–#ïÞ¢ñºv©•ŸˆmÍÝ”,—0Ü^iGT p>µ¸!¸Õlî´ô»Kk-%ß-°3yÕT÷lñõ®úÃM³Ð|!öõ1\ê“ȱÃç͹¢_ï¶8‘ÀõÅ*hzöµâMjûÃþ³"4üŠÁJ¦1‡'…'·zñ/êÿ /þ.éš×º²;ß°i--Â.Kd8bx(ÆqÍ7QÖ•ð…¦¹{iw{u×ˤ+1Ù”9Øq‚å@H€;;Ë­Ão.£gæ\ƒÆ«p0‡rù¹9àv•‹àØ#…n!ÎÙ?<`ôíÖ—‰âMâ8c¾‚i(¢r}xíXZ߈|QªÝnxešÁíây•L…#‹ƒË}ÕP3“@5›MRóÇpø›ÅúŒš½ö†ÞM¬JJE’°ÞJŒ)-´9ÇS^»wm}¦ÜÝx~e{™uJ×h®qÉÇ ð+Éno5OÚé¾Öìî’Ð]¥û€¥Û˜!I r9-ÏoZ×Ñ´øçžOx’òêÏq&7D² f@ø,샎@q@‡¡ü%´Õ59.tñ©IcmÑf’(£(~ir¤nêóë\#xb]WÄ0Ûø†inôù!iáCG÷|ÇnUSÛ’{×Yý±á#_žþHåšÒîŠãP¸fe-!9Øp;`TzÇŒ4MKÂ=º¦§vþ[ð%1¨\”ù° ±áAùWäÑr’==õVÎÎÒÖÂho- ŒªA e7s$ÄÀ=1Ðq^aá-'⧇ín•à9HŠÚÉW;¾PÒ¸àtËcðªPªXùßš&±g|·zþ¡¡ý¡ D#¢º}âBò@è¾§­}•£¦§ëéâ+«– !!Çó˜ èI<×”è˜ôÑáM6"R@Z9˜,`Ó“Ô(è?Ù“Ãvú£iú®©¯ùãOÝÎê"ûåÈïÎZŒe+‡Ä_éÞ#ð}õž…9Wµ™‰Û»Ë<ªã“Î2E|ç«øë÷ƒÙø’Ò8à‚ynU‹Éæ•^ŒàuÈëë^Ô¾ Ñ­oçñe–¥ ècÛ0ˆýÒ͹‹cŒ·JëEðF«¯'ˆl´ÛV«9— W¡oRÝ{ÕrÿuÜéqÜ˦Ýj×¶öÒKf%ùÖC/*»•Œ£vXzèîô¦ñŠEÿ jÖ]FÝw3Ú€má™”|«&Ð F$gžõ¡¬jvž6~ðôcŽv2L!âI€ 3:däÚ·®¼O?‹¥Q¨§—m MfRˆÁÝǶ:ãô  &ê NÏXÖ4àe3X´l<ˆ£AµBF½H7fº_ƒ7úvûMü/IàŠ9®®u+Gï^ÎyXgû™LžXjåì$þÕººŽÎâ;[-+­²&$ló´g8ã5…¢x£LðïíðÃÄúå´¶–&» œ3|Ú¬RÚD€öIby;}«³íZ&UÕéÈýªñÌÓÝj7^Š"êz=ó)ÏÌÒ( ´gõüþÅ7#Ó~øoá¬6Vúv‡¥Ø¼:ŒóI-¯mX¢[3çsØÄª©kûÔ,á“Ä–úœË‰-ƒC?yg*õü’|»h?hü—NTÓ4ÏêW`;”Ëò¬›É•µ{Ù´oMF[£g×~0ñ/Ã_ ÞÉ}¢xrRÊKh£Axá–NŠ$àsß®2+Ít?[ë q.‘{%ÊÆ¾A˜&Üã‚é×°ÉŠö iukz‡‰5}1#²†èðP¾H+(êAïÚ¸Ÿ|$ñÿÄ?†¾[ÇvÖÁîY‘I@ )nÈùÆÑ“ß#­|Éì§cs\×·ðþ¼Ñ«WÎò×÷‰žÄ‚y*2ÕÚ§ÄÛØlßOÓm`H.!X’EînÀW ¨øFçZ³­/"I`""йyYA ‡=׸¦ #Y·Š+‰¤Š+HBÇ‚pN©>çÒó]¼Bë0_\+3I¶FíëÏzíítÛ} Ö™eû‘le#ælcæ?ýzä-n-V#s X',yyñÐc§#žk°Ñ߯ÐG%ýÆm–6o²Œ*œu,G9ª¹J*Øè×·&êêòG·f=™ „|¸#¸÷¯]Ñt}_IðÕµõâÞ\I!˜4‡çÊ‘ÁNv¨¡Îzדé~-ƒÅú´60ïO±†ÝY‡V$÷ǽ{4N!±—P¤3ÈŸ½='ÔŽ)5c9œü;âmæñ\%ÞØ€$d寸“‘Èõ¯€&ððžK«:èÖ¯ÕzÜdg¶9WÆ~¶¿Õæ`„L»eSÂŽ2ØõþU¯uo6Ÿ©Þ£HP4ImŸõJž:ŒcŸJ¯â{K›½]‘0{duÎ=½+@›=ãÁf‰?€VóU¹imï÷Yì‹ýydç ŽÞ¢¼ ÇxóÁºí‡Ž5´Û/Ëgö{Ù‡Ët·Q·Rs¸ÁÏ9­¯‚~#†Ì^øae0+G%ÄXä!ÛÎÞ§'Žœæ¼×ãÏÂO‡ž2Ö¾x¤<ÚÔÞ šæþo^—ÞÞ`|×q¶‚ÈF\3T“=öÏ\ø{âK+3s¬^ÜZ­ª¼WVAZ!)$Ç õ8úrkXøKMÖïµ eã›ZÔ6˜Y8M©µW œuúb¾jµðn¿§x¦h—‹áëMZì^_évÑ/Ø¥‰ÀDÆ# GðöçÚ½+A‰¥¶·Öm4ÉžâÖéÈfVHäTÃo$ãÓ¯ÒŽR¹ŽŸÆ]ÞÚéRê0­¡Ó?w¾䉒sŽç¡Ï½jx£JÖåÖttÕ–àkyâ°XHÀ€ç ,zŠ4m3ᇎõ)®ùª~hî¡^KÉ)HçÊBíŽX¾s•ì+kšg‡ô^X]ÞÞi–j%û,*Šò3Â2¾i!°¿JçuØt«½,YøÊÿíl’kcn„¡œ’;9©ŽÚmÄž´ŸQÒb³}*ÈGeñ¯ž¢Kƒ‘ÁÃßÞç§¥qšN£§éº¼:¥•â™à“,Ò.ÔDQÀÚs’ONبüQã¨u>ád@Ò<°"‚S¾dŒ #F8à3^ÑiãëVµ†ýô«MMlÍÍÝ” º[xÓX‘˜`ížMŸ5K©øšúóÄwò@×’­âE…`:s]ÃM^óR×¼=¥k°Zh_y€åË;’rÒä€8Ò½Â}CHÕ¼Gáû"µì,p©Ëžìý—W%¡è–òøŠtË[]N;x³.wuÈÊãQC*'¨i Ôuè!Õ5³ åï›zÑ^Gó³B§÷.É1…ŠËðΫàØuh5ÏE6¡ Å4WrH®A)ÀSêÍÆGµf/‹tÚYÉðÎýWW¾IÞu2E Œ‘· ‹è+¨Ð¹¤éQxrDmJV"m‘–Œy|€Žä §ñ_Ä~‰{6¡‰d†éCYÃ)>jÀÃ[û¤ðy÷¯Ó5M_ÄZÑ‹V·–ÚÖÖmd€²"7Bİ ·â©"ÞãY:” à-->ÎeÍÅåûÜM'–ÆN:ÉàvÅji¶~:Ô­4µ7Hô}>ÐÅlÓ)iÞ2ÙPíŒ ʶµ¹Ñµ;Xm O.Kvk›Œ—Wˆ`îÏlñÇá[‘kº‚i‘Þé1²Œ<’™#c„SŸ(g» ªæ?ÚÀ¶ÿ¼;¤éš½¼:œn¥rëir3o4Wí’6 e¢e#ßÖ¿8m¿ø'6»ñçâ§Œþß&‡áÿÊ5 ë;ݧi÷Ö¶ñ¥´ ãl/å•Ü1‡'vsšýøÓ|g}àÈu¿²Zh÷7Ÿ¾²¶ÚCåN3ü`àý+ÊáðÇÂxÂ/ivŽ¡s|¾]ëZË,,³²ï$*‘€=«ÐÃbytfS§usù}øcûP|]‹Æ#ý•oÃ>¹£sÅáÿ[Dšeα§ËÙÚI6þò T²ªG ŠøÇ㇈|IàŸÚEþ!ë:†>Zx‹^¶ñž‡áf7zf–Ö°Ão)‰v¦Ó#£ÊÀ ImK¿¶üçá‡íðÕ4ï ÜÝèÞ"±YmôæÕ${è¡™KaIp$L±ÎmäŠþU¼QðâŸÀ?Œ=—íOðóT›Ã~xSÅk ‹5™†ç|6· :¸sLU— !ùw`W±B´d´<ê‘hýøwñFñFš¾7Ó­ï5ñŽ%žŽ²Ûù!í­Ý¤{„Üpá–ù±¹¸­?Å®þ Yé^2RÒ/<c ü%›‹éæ—|77 ʯ 1q´)<_‹6^.¼ðwÄÃî¡q§èz¤v:æCºlÒn{uG?,x9c‚Hž@¯ÖŸ€WÅHu-1üã94-'TÒu«]2ó춃W»ºF°¿¸vtÚÇ ¹xòìpQˆÅZf'mâÍá‡ü"¦âÅ5ÖÕWÍÔn¬Gm&£{Œh_ ÂC‡eQ†­iü;¶Ñ5Ÿƒ6³æ“k`|(³ÝxÂÒîå|­^ÿ[±»Ží£‰‹oŽÒ,ãCÙp8Óøuªþɾ ø;®Øüdñ%ĺ垤¶rx.Hf±Ô/¢T­å„Ÿ#y}<×O•˜ŠðKÏxÒçÅZE¯ƒÛM🊼Cyq¥Xèwê c£Ï Œ%ilƒ+qûÃѺSÚ~+êößð¶´?h:ÅŽ‹ øcWƒVñ]œ¬ÑæÝ‘¼”`ê_3;˜ã­yïÃߟéßtO ÞX^=Í•ñ±¹µ˜n‚Âö<Q€²BT“9óÍyoÅÍóUøGañâ'†Ã_}+gûKn‡T¸Óä)&U¸Ý°À’}1^ÇãŠÞðÝÝ—Ã hq^GáTjvÓiÖ±_iÍiæÅ¡4i±R+·É‘~lÐL‡~9ðÂËý/ÅZªÁáõ’嵉e‰æŽ=5ZóÉD Ó]ùC>áŒsÏ¿|BýŸþ+|ñ¯ƒ|aá­7Dû‹|/«E¦xzëPûMï‡ôåhÚÌêe›P !*™+æHG˜Ü߇>9ø¿â?Âýöt×,´ >-kG¾¹Ó£ºƒu͵Õþã%Ä …Øâãšðo…ÿ ÒÖ_ «¬¾)‡Áú ŸTÔ$x »žÀ¾ì}ž'ÙGÌÇž@Iô¯‚>ü1¹²‹Å<?ˆõO Ù›Ïû,W;ðMÒMq VPCe”Èà.~c_4kZÍíœ#Åÿf’ tñs=–­o $·r¹·f8Ü¥ˆ9 æºüKü#àÍEðÂëo Òõ›Inž9ínÚ9c‰bŒÜ€v6Tç¦0§ Ãþ*é~ø ÷öÖ™4þ!Ñ`Hììt”ŠBÂì JeÌËà¶â£­wú^§áïüÒ´X¯õkðê72}’æß÷Sž¬G“°DÌIÉã’x¨~-ø?Pi¼qñ*Oê–Zö‡áõm"Þ@ ŠöòYÊGUcöw`ví| dŒ éü#ãK_ÙÇ[Óï| q§x“K²ÓuOZkS'˜ºŒÓDöðÇ”8R‘ýÕS÷HÏ<ÖÁÔ¶ñÿŒôŸø¿Ã7š—‘ki©Øh6H÷W~S¼÷ÎöŠ~cé‚H=(‡øÛbÞñîƒãÿ‡z%µž¨øuôývÞòUmõæÆ•›ˆ–6$ Ny™¥Ém⯆¾ð®¡à­NçTðÔZd ‚3‹è\mŠiw‘³¹*0MrzÃOø³MÕ5;)¯|s=öŸ£O4« ½‘ä2|Üð!L°l0ëÚçÅýö|ño…¡ø=ï‹ü!ql×°éwòL’Á©Ùȉ DÅ7\µÁ#8 F1Q‹MòÓâOŒ´¹­ï5/ ZZ\¥”×/bÒ€Á'yýâ•s·ïÝ:W¤øÇàïìå‡5 ß I©_k+-Í@là‚q‡' Ô4aAùÁ$Š›Ä>4ñGµM2Ïâ¥íΡ¡ëºÅâ»Z?ö•ùı\²°b!ádŒq]D~‡À¾Õü;ãËVòu«fÓc22É3é¶;ÚØÂù%Ÿ{€þ½ê€“áƈü=ð?Q}^= NÒ¾&ø’ iõ“ºÕô[]&C%¸Xœm¤o—$–fä@¾(è¿ uo‰ºç‰¼âKïé×Maq§^ßÚGj.dXÊ´¥8×|ŠÑ¶À;òFk¥ð§ìíàÙíQÔW ãßßêhö^‹Qµ×õo êÚö¹ªêSÉi;ÀÂâR§|‰37–¬ä AÀ/ðï@ð?Šp@îÊ€€@(Ó4=7Á—úͧÄDÔ$kåžÎQ¦ZHÜÎÈT3)8nF7v#­}ÿàOZßÄ/OðŸâEŽ›¥Øê6jVé#G{æG(';¶¸*™ÚÏ5ò–™®üøWðÚÆ=2úßTø…¨ÏÐivà¬6P]²‡p>Y<¼yãu¯$Ôd,™çV·Âåñ'ˆ><Úk~I¼7q£j[¬n§ì÷ŸÐJ‘é×¹ñn‹à†ºÖŸá}kQÔ.®î`†-6‚HâX¦>ìg|Jq“Žz×Xþ ñ¿‡ì´/x[Y:õ½î¨ö¶·ê#:n«/Ë á×ÊÚyA9¦·¨×ô¯Ú‹[Ö5M?â=Öœ’ø7M‡T¶‘ÿ£N—áÂÍŸŸË%@ùŽ@ñŸˆ¼ ¦'…¼Gð¶æõ'ºÔ, ŸGƒN£¶“¸. Cǀ縯rÐlþéž2Õ´«9¯´¯†‹Nׯîîæ›O¼™s$¢Ð3”L¯ðã¡Áë^àOøöËOºð.¥¬Xéóév²A¦Þ_(I/Vá̉*£h;sV•·öäZ§uƒo6§ M‘y£íƲ„¸ˆ/÷$\d f¹ÍKá?~Xjzµï…’óÃÞÔ/5ë FH¤qo§\®Z” A26ॺ\%ߌ|/}5®âí·~0ò-µK 4—º’çí2ÿª¨qÇA_qøö„ºñì¥ãŸÙsUÔ<›¨f{S¥+‚g·•IJfFÆcrHl´§òÖ«ãˆÞ øiðƒàTzD³kZ.•«®»s6:®±tSìÛ?ˆG M!ü Êëü/ð¯ã/Ájüaâ†Ðl4ÛY‡áß>þhíÁP°N»õ` -òƒí@Ú…å÷†µ{Á>+Ô?á$–ëHÓ?"òùRâ5fùeK!<ãŠù7ÄúçÄ¼Þ д‹^ÞéöÍkuy)*yŒTFHù÷:ðÜW¼üRñ]µÈµø§¢Üãˠ[÷L˜¤Ž¥¼¬É)‘ŒŽpr0)¾(øsñ3Dm'Æ?$Ö5?‰þÖ~3xwX>ñ7…¬’<lMeü‹´óx!cdGeÁô#ÕoüÕô½/Mø“ðÖÒ×@‡Pxd†6Ÿ†”µºÊ²rOº1üGà>ñ§u­6 Kà½CZ}Rê}FÐÙ2Ëk¸ÝE*3)ƆäóÅCWëŸxÛá·‰ô/ ü.ý¯ì¡ðæ·™r“ê–Xk;×yyIX˜Ø* Ècé^ið—â÷ĊƒT¾i4Óe–7 ~Kȇ%^'¸ùAÇQXÖn>0ÿaÝ齚käßkg*¤NUcù—*W?+cÉ5ô×ÁíC^´Òì4¯hòéÞ&Ñ/uã ’RßËÞÁÎF3väc‘K”•|_ñ“ö…øU…àï†R[x›Jð»Ig¬[‹?[Ê`@(Ë»bIW<‡^ø;âMRŽ]k_ÂF{K«‹B“K6›6óåŸâeuPÁpJqÎ1MñUÅ—Æ}KÄ+Š uk‚Vò-®‚yTHŽrH*ÊSæìEzχüwñ>i« Ã,:ïö¥œ_f¿]ñGw.]挜1ŽT`zàƒ»µpž>ð—ˆþÙ k°‹¹µ}fáu…°?hH."%vùyÛ"ÊxÁ<Õßxÿ´Ãïh¿5ÿÙøºßKY58|3ihçQ³Ó,AD•ggf’H·Ä¯Ì€®Ó^w®ÙüMø­â‹Ú7º“èzÇ…õصòFé®~Øë$8Rtc¬êT|•#­PðÿÄÏMµÜOmcoyuâ­ûâ­†;sò²;õ®¿Pðì:½ük}&–cŒ¬¯òˆ²„‘ÁÈùõò×ćzÕ—¼gñsÂæyÿµ¼DjrèSKöÝS‡µºHÄ-ÃËØA'0EEã/Ie¤évÚ_Ú`Õ-5–úöÎF’ØÂïµbIRÏGñcÇ6>,>"Ó¥ºÓÖàÁ©ZÉ–iya*²’θ#ŽÕ-™ôÖ©àÁãoøS^-ø{o¥w¨ÙÎg¶k­·dVd$N¥‹y’)f$PG¡¬[mMäð?¿møi¶¿ £U‹Ãl›/'MvKÁÒ2âc¼Øæ¸O€º‡ˆ|6«¨xWNŠmÄö²i–··q²jº<²ùL~vŒ†)~VçÖ¾kÕ5ˆÞ ð4šƒ¼q8×$Ôv^is[%ŪZ¢K¤Mm ×wj—}åÅÊ  a˜Ù¥Œ6\ò0ØÍ¤òž×ñ ?k³ÃŠº<ŸÚV:'ˆ–Æ]Ráqgoo‰ín\eÞ'¶c"ÈKÚ{€+3âÏÃõÛ˯x²ïO“šì½Ó¶¼º ø‚ì')4YuI ª§æ8ÍxNŸã“àýRÓì¤.œ—7&Ô‚KŠ“>gvg‘žqY>ñ§ƒ~ _êZôÖZÔj“F­ì.Ûd ü³E"œ0ä/qK•”}ñðçáÏ|]ñÄ:¯ã¸µ?x#WR—NŠÞ;HšÛýj]Bnå{®AÁÁÍs¿> xWVøz¶Ÿ/Eýåþ§qª-ýª‚Ögù™AæW%”sƒ:šÏøñ#áϼMku¤I¨è?´m"m.k­PFRéJÐ[9Q‡‰ÁýÛc8äç…à¿èö:¦‘á=CE–ûS·±Ôô¹ô«¿,Ko,î· ªÇ(Æ3À>‡Š,Àú+Wð‰>ø/Â%ø[%¿ˆîµ¸nàÔ®u ·Ù$AqbDßf hÌ»‚¾ÓÔšÀðů‰ðø·Uð~½áEMbþ;[·³·Ô-nrREÉÄn½W9¯"øÿe}¥xGRÒôV»Ôe´žÚçN’_š’U öÓ(åc8mì¿/ õª¾5¹ø“߆¾?hïq¥ÝXhÑ[Çy ¬¶¡­³·…$}¤vÌ1tcz4Œ3¤í냌TŸµÇìåð—Áž#ŸÆxòÏ[Я K 2hdTTǺÚFV’);d^üW£|%Ôü?w{ãÛoоÒuM'N°ó|%2‚Ï%IJ:@Àæ&‰ñË}ìr|ûã³$Óe}gÃvVŸ¼&"] kæÒµaf¾hÓ&–,˜^h‹™GÈëœ6 –˜¹Ož¾øàðׄ¤ŸW’õ!·×#L’[™3ÎWr¤X€ç½V÷DøõñGQñg…þßèº6‡¤J¶7òêñ™"s(;!IWk£ Y0@Èâ¾Æýœ¾(øoTÒõ¿€³'…¯|5ªx‡Ì°×<4cYÍòÜDEݽ½Ä„3˜•sVSÀÀÁÅ|©ñÀú¿À­ÅSü<ñˆÔ4 vêÀ½œ‘m¾3Û™$\«Ã duçVIÅ|7½×|/ámjþÞËNº}KM’ÉšíLî“iOó²XÆÜÿÅ}5ðWá·Æ_ßXø›Æ^…tÚM©h¦9•’{f+%‰Û>Võ,…Cªdã kÊ ð­ÄzO¡MNü†´ócýÔñLÃí;€ÆZ&xvö›wá?i_´o…ž<ñþ ðçöƒk ¾ICAk­˜ŒDŽNؼÑ#a¸¬Œ§ ° Þ£àÉlô]O¹‚¯ ¹Ó®Øî¤ÿG»DPs"˜·`†lf¼‡Çš%çŽþ kN—š†«*¢ÍûÀ``YB“÷<·ÉSŽíöXx3Cñ5¶øãã[¨õh®šN¥£ÛÜ%³Ù°‹,¨¨’"vƒœýáŠñÛÏ⯠j!ø¤Í£^É,…ã¶VEwÞ˱ÑÉÉqÃóŽy "ðö´·Ž“áOZJtKYlî®Йc¸IH;r[>Õ躉|'aâ¯xÀ–‰¦é7úŒV,©‚–“m!˜sÃA^ÄW5ãûøRÛQµøui©¶§sja׬®axñlÊv] Æ>fÂ’¿.­léz'ìùñ/à0ø‡ðóÆãPñ#Ü®“©ø~è,úmã@Òy³'Þx¨PØä8`Ø )ñoÅOü$ý¡.þø»Á÷:ê,ö÷1°ó#²¸|©a_î§$¯±ã¥eè³Úë6³cá-:=lêZüˆÔíWÁk£zH‡ýdesÈÆZúƒà]õä–“êÌomtï³£kÎ’Ëcå#m1%£ mgŠàÕtÿi–BÒê1åÞ[”3 dŒ8ÃÐ{W’jZ¯¨7Vñ"­†žöq[Mxÿ}Kgj‚G\ãÆ€=ëÃ?ü? üðFã «M*ÚÎ[í!n.Aw6·×­”²àcåŽ8Œs\÷%½ø=ã=a,maÖu=+Rµ’=*à†Pµbdnc‘9xÜt;{dU)¾ÁsàM:öÂêÓÄ–N»o¨Þé"Aö¸ôk…Ek˜ }åŠo™†2«’§#×þ ü$ð]ŸŠ¯õ[N].û —,ñ‘ıüÑ“  ð»²h-lsZÙÔ|3ñÃSñž‘)ðýäÛKÈ2E.æ}£²’¹dÈ ç“‘È¯¯¯¾þΞ,“TøŸ5ׇ5={O}[AƒN¬OjÁ^21äJ·~•óO„~ZǤ‹__\¦¡wºmB9˜Ð³“‚€ä÷^¸é^iñ¯â?‡>iVî-ï5KéLWÞÖ-âgí0 ?ºA >¤›á-ì?§ñ‹u¨um7R–é£6܈ “mÂéçDã*+æ?Š_ < ¨è1ø“ÀSÚj^½}@ØßÄJ}šàlùdCÌN[æ\pÜŽ+Òõï]ø/Å›\C4ZŶ£;+.e’BtR@Á¨Îk’ñß ø[ÀÚ¾±á1=…¯ˆ/žit,†°–9$ߘÏðrW(%³Êü[á nmto•Åý„p¤wgË{ÙS‰)F3‘ÈçƒÖ¾ˆƒÁ÷‚[Û»[(-µNëkIäÛròÄ£,|¤2ž×®U:gÄŸx_ìË7ÃW ©ÙC© ÑM%Ôg*Çø•±’:׉|[Ò~-]üLÕ|_âx^ÎÞiþÕoƒïXdÂqýÅ*±çk2Ë6¾ðŸÄ ˆôQ$Ú7‰´C%å›C'Êß.eˆ«pßÞÁê:W¬ü+ýª|eðE®¼ðßSMwÀ–Ïöˆ|=®åⱞ\¼ß`»Ü@ŒÄ‘n‹?uBñ^>¨c¾Ó<=âš;½NÞYmõ¹ó'o #8çë]ÝÇÀ/H1øƒMŽë^Q¸¸CöIòC8lŽrçJ™EIY•5±Þ|Yýº4ÏøÛNðv±y'ô¸dhÕîŠËoe}¥xÆÞàŽøÚ8w^8ø—áσüg«è:çÃÿеðž»x ÝÚhĘ&“,‘8Ã?8Àq‚1œWã=gNñ_ÄÍOá׈,áxšÚàL%S>žÅçto³?ˆÅz„µÿŠ7Ä Gð“ì¾#ð±¡H“Û4Œóh×H1ÂÀïx$2¦ˆ©(AGaNM÷†t]wAðÖ»¦é–2ÜêÚ.”ÂÝŠ mÆÅfåŽWp9ãàW›Áâo„Z‚ujµ¼6‘Ç;Ú…>j‰Ž7DÀüÃ''§5¹ð·ãýçonõ[Gìs>§m—rŸ3ý?,\»[!³ƒŒkØþ$x3àž;±ñÍ †óCÔµF{„8"^¶â„v,§rÙ*kX‘Õ¼A¦é´¾ñXjþ¿žÞÚÝn" mð‹ å¡cïT5o >¿ã¿ éwKi}dëkm€B¥Ô¶vê~aß95Æ]x3Ä)»ñüïlÚîë4È2«ñòŒ Ç-É­›o‡Ú×…š=FÛRf¹xÙl®@ Ä‹Ö넎™ëÒ‹jQêÞñIm®E†u»XZ a wÜ©S(?@sïÍa|6ø…má/Ùé£Á[¼IjT£ÊÌŸ$È î8ã®s^iáˆuψšUç…¯-®Q³Iî$ŽùpÑDOÝÇ#ø}yèk?Á¾Ò.ì¥Óu«A©jÚCyÚ|¥·¢ÿG8edaǦxâŽP=ËXñwöSø×Ä^ Ö¢û]ô)%–—:m,¹Q"†a•yàãŠñë‹Z׆´‰åñ”£Ú¹ŠÚá2’Ï °m † Ùºõ¯_Ó´ûßø OI¥œ3¬vÍz>b­cÔÜ物_x7ĶÐm :z³4V“ñ[·ñ…=0zŒõÍ ox‡Âš_Ãßé¾ ¹…tën/³y‘þú˜å\€ÉTœ¡=~µnîëâNàf—÷qx‹LŠO&Y£uÛ[…ܽwŽŽ£æÇ5…g£xÚÍÇrÔ>\š}ݬ¯å2*ù£vÕd|xü«Žÿ…{âÝ6çQðwƒîþʺû¥Mÿº¸òâ#`HßëП¥PŸ¨_Ç©ü-¹ðе·ÕbBà³ÛHÜÌdñÇLóž•Èéº&›kñ3J¶–xfh£{¨$€lÇ–0RU8æühšÅ¡mÞ4ŠòÒh·\I!Û uÈe#‚0Ãõ›ák[«/gÄz½¾“},6ó¶ýÉ UÜX·UŽG~¾´ê6úŸõ}2òuoü-t—,·—:\…-§Âùñr „úc5±cmÿv‹žÎcO½º•Òx‰RÑ7 „8*ýý |äž&ñ]æ³™mái­4¹®çû¿žm•êv³¾6øsÒ»y¾ 7ˆ<+,šîÊ V6R>\‹’0Þ¹èIéÒ€>²ð—Ä[xúÃÆ~‹©C-˺”Hèʩ’§ `ýÓ^âNçÇšÖ³ñS…¶xŸQ’iR@x¥SÛŒcžÕÞx'âÍÔ:‰eÔÅý•„"áãº\, +‚;¾1^ãŸi¿|E¿a¦2 J`v ³Ì~ô™ùH`~ðäRhMØéusÃG\DÓt8ô¡©éÌ–×ñÛ\IVF`'¡d$×­Uø¡&§7„!°ñG4ð@RYÈa…ÜÙ “éÆ{Vî¯wã‡ß¢ø#ñ9­¼K¡è×2ÞhëòÙ¼ÈK˜æ$gl£d`8â²ãÓ¼5•­¥þý4ø†Ú™„y€à$àtÁè5¡að ÁÑkzEýôk5êxwM3\# yôÏQé]?n¼ã{m&ÿ[—eæÂÖWöŸ¿}¡s(ÛËn‘ÆkÈ<'ñ×Ä~0ÊŸ"<’Y8 :ËýèÜ7€~¼ÕO‰Ÿô_ëºOÄ*Ú ê°u-4æ8¦Ÿx wlåV#+*7p}s.û•ׯü1ð·ÅºmLJõ‹³¬È£ì÷0†åSÄŠÀ)GǬv‘Áì?n4ý6ÚëàGÆC ú%×™6•ª¼À¥¥ÄŠI²<¦|ž¹\œgš«âŸƒÿ ´ÿ„v?´_iöúŦ•öOH¸>MÂI8 ÏO¿×!qÀÉ'µKðŸã=çìíâ•Ó,¼;m­ø“O.ÞmÜb{k»G!£äù˜þ ÓƒÒ³m”x´©ãï„W°ø^rÄ塼¼8òz«ž§ ŽqÈÇ7ôíXÑuñ¯€/¬¥¸%º·²’"u,§¨n„ 0<Š—Ç¿´/¯üV¾×¬|4ÞðÞ¶‘™´˜H±ÎIóËoP…Ú c»? èß %ø‘¨ø_O¾.Oóâ…×Ê»VàÆÊçœðªLV"øCâ½{Ë­¯Ž4;]wÂþ ßZA!w}À…13l(éýàGÖ¹ÍÂ?ʼnšo†fîþÚÅÄ{5ÅÈ88I1–`˜ ¬s’+ÅšÏÛmôåðFŽš„“³Åt‰º)ÒxÏÌHQþ5_á½Î Úf£¯èZeÍ•ö–Æëɶæ,Õ8óðzph¯«h>ø[âÈ|{â­*K-FËäš?)•£,:ínJž­ÇN‚¾’ñÀ?ÝY]øÒÒdû¤°\H–7+%¶H>;žLW%:ŸíU­ˆúäpZÝGGíJVo2À‹Ó'–ëÀë]dž´« ü5Õ¼pÒß½¼2Cv0¡:²ï¦1ƒÜãŽhÕ~ ßj)£øFá-­§Ú×].B” +¦:»=+ˆÔ> ǧkvv^ Id¿´½ˆaÌ­òå}9È트ÃÄ â¶ñ¦‡·zlL£T²ÞHò%8^I#éÉÈô­km#Tð¿Œ?á<øo¬[Iñ ‘ÉkûnHÌ„ †N~ecƒžŸJÐñÁßøgHÕ|M>›ròÇ7–ÛTeÝ3–\ó‚9Æ+ðô#Óµ ¼3$ÒÛêq£ßnÎÃ%`lÇ9$Œ•õ–¹ãOŽѯü)ª¾!´·0Ýi32òC|“Æy-•Æ8"¼‡Á_üt<uÍ_MM'UIRÛR¢ÝënŽHÉÀ sÈ€<óJ¿ñçÁïÉ?…æm1µ¸Åü (xd˜ßnr²)È?tâµµ¿Ú_â&¹ö|CÓ_PþÇßnLª$º¶¸PÌ@.ƒªÀõ5—ñ‡Pm OTÒ§kÙîѤ¾€v…ùRî±SÛÒ¼f÷âºø„j¾[­iòкK–Z AÛƒó9 :óŽ”ÞMâ+›Íf+oÚ³ÀŠAY ³OÝ=¸®C¾ÒÇÄ+‡Þ0²û<vw¬í¾6p1±–RØ<É©lüOáÏxÞ+ NlažÕ|ÛeƒËt’1ÿ<Ø óÜTþ#ð¾•ã{ƒâ¯ ¤š~»`¡Þ5$¤xÊ–åI<ã—f’+ÚÈ~è$eQ³éœùÓôØxÓÆžƒ¨_ˆõËô>âI2“!)´ã â¶à´×õëxµy­½Ó[íF¤Øå†R¢©j÷—7m&š‘´öñ·™$jØÜ@c¡ükžÓ¼aöÝbãD¸Ò¾Ï2Fæ&•Ë…AŽ?Jê´k˜ŸÃÚ„×3d#®QAø(8éë_—ŸhQ‹âŠ|cg‚,Þ=<Ÿb¶`rr ™›§GÆZŠè]¾—klg‚F ¼ òõ8êN}«¡ƒIIm„Ú.¹…r` +¤k,yû¥‰üñɪ:®«¢x>Ò)´Ýt]ÞfûL©µþO“»Ð~t/ƒ­sá¹`× –+›¦c¸< #©Áý kÁ.ü>ÕÇŸ§Ú)wt_8u¾µÈøzÚÎÖi|G¨_O¨5ʆ7Ê£<ðׯO¦¼âH”ÜÝJCaCb<ôã¾{œS[×ÛÛÚA¦M©è‘C ¬Êß šBx sÈÅs ‹¢ÜÏ.¢ÒÞ\lV\±£7lp1Ö´¯¬õ«©Þ)”Á$ í8ÇP=ýMqöÖÛ›V–3s/ü»ƒ¹ð;ãÓߥX³u½¿ÝóIråa€†gn0ƒ©ïÎ*[½[HÒõÑ5ötß>ó‘=<ëô{­f×P_ì{½;JûNì<ì‹.Ü`”A–Ï¡ö¯?Õu Þgêwoö0O”Œ7K+±åØžH'¦h ¾•¤ÛY$šœVK«®I˜å¶°ùBúg8&™yâ4º i ¬+ "8/vþ}뙾½ÕäV¹º1° Áfb>èXøÎ>«=´ Jë_‚îú1eK8£òÌŽÝ7ó€Ã¿nh%£¨Š ^xä¹ò ;‹yÏ’ˆWœŽ}½ê]liZŽÛMœkW3dþê2‘ÇéÁã'¹?Z‹Qº¶»×—LÓ^žûôˆ‘„à¨ÏV'¿J†ËSðÛî–å®Á @Ù'•,zç=Ò‚K2Y“k›§¨»¾8Ý |?x‚º×Iáÿ RyÅqKÏ܃Ê`Ï8°õ¬ «ùîí:l í †1óxÛÁÉ'¹5­«A¬øy,×_ ÈU2D탂8Üã=è³Ò¼^šŒòiV8‚"È ‘K½žœW i—ÀÅ çŒí〉·–²Ió`ƒ’3Ï¥r^'ñ%Í‚®ˆmÁÌvÜá~½±Øw«ÖºVŽv]Ias)•™$ î3×=@'°í@}£hÚÎëŸjRÎ7€(ŒçåÏ;séÞºé5VKKv;W‰eù²(H€#hl“šèµíKTy-nmÅ•®Ÿ’e™€ŽŠ‘žIã w<×{­ßêS¶3™\F¥ð[h=tÉêN8 hô(þk>3Õ~ͧÛߥ¬PÌc@ãÍØ“Áê®x¯­ü%3MâËø^÷í·qÛ¬‘¬„ð ×nxëV–„Èéõyôý* W¾¶ŽÓP¸]s$àˆã\dIRݱZÖÖš¶©eöÂêm<2;eÄi3½æ¹;ˆ#²‘À©m<¥ëúÝ­Çm¬ïµ6”ÉejÌ]m‚ y®‘ c…8'5ÓëÙèz“ß-Õµ‹À‚x”™{-Ê2{þI#6C6¨Ð[Ϭêße°¼š#$P—Ü!Àg½”u=êÏÁ-àö«>—â­ZêëZD2Ê,€±°‰›•Ì®7Ë'# 2§Ò¸mœQâÛMSÀúm´þ{> säÉ< «N-åªKnëÀb·¬5Ï ßhöÚž¿oq§™ÄkûWin¤<¯NÙlŒP&‘áÛ–s-Ͷ³\yvo0-%ÑbpÛSî†ê¸žõÓXèpéºÆ£7‰µ=2Þ %W!Û1–ÎÕ.Øù‚äö®kÄÞ*Ó,uämbG„3H |©n£j1 ò£ÈßtvÆMCcàcŶú£^Ùh—ŽÛâ·¹ß; Æí«À繓@î©¢øWÅ7òÊñÊñq ‘T¬ŽÄv™A÷ÔxC±Ñµ«È-´…µ´µÞuÃÉ$Œ~ê&rXâ'Šêt=þIsæZjÞ%¿Ye¾‚¶´ˆ¶NÔw$ÉŽåGoZè­¼Wàý?A{«Í7ìE÷Ú]¬§sÙ' /‰@ðUí­Î©s<ÒÞ¦$Žá|é0ü»ùqÓÖ¸Û¥hþ!´Ô.ôë‹­6YEÅ­œä_¸_€$dò+oÅ_¬4 x~?·\Çn'hÃ9‘‹|ÆIä#;Tt\äŠó{[H’êë¨ëù!+ ¢FÞBHÜ*ªÿdŽ7qÖ€=†ûÇk6»j0\[yŠV×J€-Ú5à3Ÿõ’ÜrrMW›Ã6Þ!Óncø‹ª\hÖr yö*«º7ü³ù°pÇ‚G9¯(ð7Â|:—?Ö/î„V©á_†þ ð€×Å’½áe»Ô2¬í)våöœàô¬{­&O=¶¡§^[M§ÈÂBÈ æ<"Ž9#Øb¡µrÖÆ­¯ˆc¶½Lò^5â¹R#Ú3’XŒðHàfº¯ø.×^½·Õµù²`gò•NÓŽ»ØàvÎjä—ÃÒÝhRÉjž\³]G(¬ìõùGçšõ¿ ]ßG©®˜’­Ý„sJ UÀç-×ñ©zì3‚oê>)Žámíl"&Þ4Œª¹#àõˆÛÙ©‘øâsêwú½å½ÏïívDn¥‡y3’Ç<œð*/x#_Ö4ýW[PÈóe@ ¢‘ ÄY<ª9¥M¤ø—Æ–Z%³x3ÃÓ\[ Èk‡2ùÓ‘ÉF''µ.VE{Ë}{ž!Òü§X<º7†-£‚æm13\9çkÈ”}ò zv·cã›?ˆ6›áÔ›O°(aK·P¡ÈÏ›/\¨ÉÀä‚+Õ¼-¯ø«_ñmÅÀ®Ì þdGtK1m§s}Ñ·¦+:ëáǯüYªx·PÖc[÷K+;Œ¢.ÀÇ$ƒÀõæªÄ\‹Mð„º]´š/Šõ@¶·Äžk¬PÜ" À3mÎ~SÓÏJá¼)âOišeþœòHÖº\+mo;™b8Þ@îÝGÕɤèôÄñ'‰®ã´¶¶¸[[ÆÇz*®ÞXŽ7pHqí]…~xCK¹Ö÷ûFÚ‰0M¤–<…ø[õÅRdJæT“|9ñç‡[^]C˜¥^UVU.ØùIq‚r:GN•‰ð»ÃGUÖ-üu§].¯alÏb°\dlšeû觨UÎqÞº¯Óx&î+? øÒ ømôÈͽ•‹¡‘»+ºòX÷¨~ø÷ûCH:›i-§Ziöà x¾EgÚ~Uô;@ÞO*‰ÂãÃcÂv%„Fè"˜òÍ9Q“¸ž{Šç"Ôõk6Ö—vÉ.›+†òá%™™yà=ý©°¯|yw=ì©ý•e*4»vá²z¶{‚H=sÅoéž·ÑôÍAÔ..o¯¬ñ(·W$3îùAQÐ.xÉ  Åñ‰|kŒ÷«õÍˤD€RÞÆW€@üÉ®›TÓõ½7ÆÉ¤Ý7Û® C2à,ÕÈ\àc¡ô«º…ôÛ¯>¯­¸³0’’4#k¬˜Ã.NWv:ñÅvz^­áû¥šûBÒ'´XÜÄ.¤“Í–@§’z)ô´\V8‰&Ö °3é(©2Ëæ4²‚Û×¾GŸZàü[w¥¿Œ<q­³®‘iâóQŽÔfg)>cÛßbÏå3 Þ½¾ßW²¸ð»G‹‹Ÿ³”råN.}óõ¯“ÿh¹Ã õŸ‰Q,—…5i0á$UÓ¦Žv ÿyveG8­ð­*±dT‹phþŒ/dIu¨c’DÆ}sþ&¿ ´¶_¶†¥#ÃxÞ$ñ,1Ä¿˜#à© A_Øåͽ孶»fÙYcŽpGBúæ¿”Ú+Á/ðûþ eñê7bî9µ3¯X¬i°Ç&£mn¢6ná ÈÜrYëê3%zG€v¨Ñôˆ.µÏÌÚLR,1Ù¢£D ýé‹Ï=·ãY~¶ñ¿‡ïb:ÍÖ’É!dHœÂxÚy\6 õäuæ´õívO†Ú奎‹¥Õ´k,g$9=²yÿ ádñ%†¢-´‡-6¡5Ùº¹œ’#D•#¦=; ù[Éë>±Ôü5§BÊDÂÔl êFÐzœ÷ÇÎZK¨Üi`\±{©Ýؤ‡hR¼QééVõ½v[;h4»iQgUYF;B¬„…äúžsøV:Íýª&›t<Öƒ<ýìooïuQÎxžµÈxóῆ‡…¯õË9ÖFÓní­NvµÀ˜çæô8ü(‰™ùÛâO M£ë6Ë·Ì.²D’ê Œp3ÐVV¯öHΑ&;Ñ$;Gnÿ­z¯ŒµX¬—ûì¦`÷–cÆ×c÷@èç¾+‹Å–³$€HåA;w xÇQÆhŸˆô+KìÝÃwX°ñ1–âòD\í‚Øáÿi!yÇS^sw« JÖ;ø§[m÷ØKo°«)„ÿhŒ}ÞMvƒo´OÁáK™f2KžÉÞ‘³/ȊÀÎy ué^¦¾ “á^¿à¿xj&¿ñ»»½×Ì>ÎÔà—¿ÌsÔ÷í@ ÿŽtßè×–Ú#Ýjº”ò]8œ«ÇÊ ªÇåXàœ`šàîw:áÖu‹UÒÖÌ~ñ¢\dDpnŽwc­u>&xÃÄZαirŸ{ª­ÅĶĤ q. D€’J cÓœžµ™ã´/É=Ö¸òiÖò9_"2îFÝ· 'ëIì׊|MáøH‡Äm*õ® òÓÎEíæ§w¡lr+NÕ¢Úèþ<ѯ,<7 ;‰áŒù˜›ýYÃ`7¾:g5cDÕ´ ZmDÊÌ×^\J`p¨Šb ðùǽlêú‡ü'×WÚ–¹­´—0³}ŽVÙöy#†và`t6`nYþÍÞ%ñoŠZãáVä˩ʋ¥‡¹T·òÂ~ót¬Nd |ÊÀé\wto|6ñÄÉ|ë«jsÊ#¹h-|ëSå”à´`‚Fs¸Œô­­VÃÚƻá]CÀzmýŽ‹c.ø,ëÏ%Æ«ƒ,Ø …È8À à÷¯CÓßû_âÞŸ¦ë–ž ¶µ‚â·†1QÝÜýܸû̜ÎiX6ëTmnî+Å-öy&BñO ùI½—;Ÿx€sŒŒÕÝ"ÊÇÂö—ÚmäÎâKu…fq‘ål¾Àfº|A¹Õ<{ðëÅZ4Mw£K$—8B»Š<€ŒmhÀüqž¢Ÿ{ iÚµ²xUuuºº´O±4±|°ˆQCWÆ™ÌsÁâ“ ˜‡Âú:DÞÑŒV·Ú¼°ÎFÂL§ =Nx­-{Þ>½ðç‡ôë6íN¬åKxùL·®æÂ‚3Åf]øfÎÏà œV× wre®§bÁ÷H0cƒ‚£ïzž8¯¨¯tsû>úãÁ¶þ|zRÿhÈICkÃ'æ<š›1Üá¼)ã_‰Ô,ôX¡ŽþûPŽG¾c†Y ?†8Ðv=Oµjø7SñgŒ~*Yi:µ®žÚÁ;¤R°+ÌJ³l',Ùã€y¯!ñ¿Æ­KáŠèšN"Ë=µ•ÏÚ|¤óeŠ[£îyäüÜ]U¯€F¥¨Ç¨¾µw6¯+ÚO;‚r ¸ 8UÀþ(>*Šæ+û¸Œ÷qóâOÝüäñëÀæ¼ÃÞ/¾ð½Äâ êIšìÝÀ›,›UF2T)ç­s¾*Ô¯ol£½ÑuFÓá“e‘µ†FÜ)bîAîXòséŠúI4‹}CY™®u0·R™Ç“‚crÙ?1ÇJøËã—ìñðïöˆø\ÿ¾2ÚJtm\Âg–Úw³žSg'š›gˆ«ƒ½GÚz`‚kÝáÖÚDƒ[ðõÌ^½Ì‹t²¨c…Ê…#†ÜO'< §£Ùê—ºD¾$ñ¢eÓÍéÕ€Ûæc9eFqÀ­éTäwLR‚’³?þ)ÿÁ&þ |4²¿ø“ð[Ãxª7LyWBñ&±=ر¸L»ê0¼»³$H8Œä“_ŸÏÅo xãÂß ~ xŽÚëXøƒá3¨ß-½ß—··”Ê+ò™ ¶åÊT1_ÚÆ¡ X6úòæ.,ÊÊ—Q¶Ls[J…]qœ’ùÚ@=kùÅý½ÿà>ø¡Ïñûö0Ó¤ÓäÓØ\]h îP[mÃ5¦w4o’|¡ò0Ès^Ö§î·©çb(8ìŽüOðßÇÿ ×½¥¿x«Tøñ‹Âž$›JÔükðÚÆêâ}>á’]bæ ’Idû*³¢F­>Æ™êp+èmoãí gû07ij¨hZG€¼G¨ëv_†­íÂ_éún•~±G Ôå¾{™!mÛÀý–qùJßâw‡ãÔ4¯Þ»\jµž§¨Â<É (LÒùG•;‚'Ót5éçJð÷„¡ø‘ñ'R¶ð¿…t‹FæßO¼´Mq=áDj3—š1Ëõ:R&GEñâ·€|3ñ_Zñï€ô û«YRØÜ\Û:ÞÅ‘(Â`Í ÁeÀÚ p+Ï|qñWøãï…šG…¯­uyUŸNßnÀHÔÜ[‡ÆÒӮĜ)4Ÿ~/xOÅ~-›Æ^2Ò5-3Ãzæ Ö÷öÐK‹„;` 00ãwæ·î|1ªXü\Ñ"¸ñ÷Ö×úEçöž£j­´í³Ë´uŸ9¶09lž˜wž¼ýž~h7ºòÅ.§¬xÓFµ‚æÑï‘ ÆÀß0NGúUÊîF?9ëÀ®Ÿl~/x3Jø]âmY>C¨Zµ¯‰ñçƒþ]Ëû\øGâ“ài6·WúVsk–Š?(Ù¬Wœy“Àrc¸ù¸Í| aðž.xÃü,4äòŸ\Ôµ‰dó¬¬ôXÁGtñgÈ‘‚Ç» [œ¦§×ôßü8Ô¼UðãL÷Z•Ù¼K‹_Ùâí,çqçÛZ²€Y¼¨™Èeˆã°6/5í ûÅÛ|ñ­oàß*j\ÞÂ`º¶ÔU.ã†7P‹l”©.®G ±û<|WÕ¥ðˆæðì°ÝëÞÔ,´M9ÈŸE‚yÔK})sÃ7)Æ NFj—À¯ø/â®›Á_‹Þ–çMð¿„üE©]Gm;ÚO6·{5·•yq2d·¶‡xH‡ßݸŒ¨®—LÒõï|_µñŽ‘¡ÙÙK¢ßÚA5•ºŸ³¾— “$ipIJâE-Öœ@ò/k×¾ø—âoxïìºÅ§‡/µK½F N@‘ê7÷­$)Ê3#ñ»… Ó5ê6Z¯Ãjw>,ý ´ÍCÂ>¾œ]xmU•šÓl6°à –eódÆ>RÆ8ËÕnþ øËâgü&šuŒZv© ¿žc1±pãË\qÑ·?y€ª?oußü@ñ…ï‡ü?=ö‰ªë«‡­¶ùqBº‹Gihî8TûSùDœ`òp¼ÕÕøwáØÔ>Xü:ÿ„‚ÎO éúâx“IÑ®Ð$Ðê2´¢Q,Çæ’ÝTŸ/#îpv×:§‚ü)w£Yê·Px‡[Ö›íÚ~ž~ѧˆ-fY Ú°P^Y" :uô¯ þ0üøWáÏ ]|@Õ¯)Ó'²VK™Õô«aáô_í`ñ+åÜârv Œô:ÿ³þ…àj^+ð¯Ä&‹Äú}¥–¯¥Mò¥Ó¢Ä%IðíܸÈ*yâ€<¶iõO^èV¾=°Ô¼C¦h×ZŽ£ç­ÉVŽöå¶ßáBà`7×ÇQüNñ¯Œþ'iÞ!ø”÷7Ú§‰µ+=ëNYYâÆ©*ÛCj\aºŒ·’O9Ï¿k´”? üq¡ð®åõ‹VkË´SËϘÙÀVfÅ%3´õ­‰¼wðkÂߊ>éz—ˆµ/ÙÇ©x…ÚÒ[]*}aãE:ÊÊËÀëµPÙPÝFhþ‰ð±müQñáÕ¤²hÞ0ðfŸ¦]ϾP¶^bÜË [O(ù¬hy8—¢ø–M3â퟊¼q¥—Ú'‡LYwZ’è<´æd$°çž+˜ÒôMsÅßõoêVïÔôïí[“+9·žüÝ c[—U$ç‰î+Ì´/üZÕ|Uñ+á§‚m´‹-k[M6ö;“¨eسÁù‚ %óóÚ€=ïÃß²þ¿ñFÚëKÒ¼Q¥Ç®Ÿrúä¬UVG˜1ÞBGÏÓšä<5á±ðÃá¾ñ3öhñlVÚý‚êz¶.#k!£ýêù‚ G# £©Íwö>)ðÇÃø÷á‡ôY5˜®WÌMAæa¶2Dqó1lžãŠàh…Ÿ þÃáOø},ÒÿÄwÏdE¸%Äj|Ø¥ùOET1º÷ˆ =ð‡‹< âïˆ6:'ÄÂÂê wZš=ÞD2º‰®Ä}Tî\p=+°ø‹¨ëÞ¿ñÖŸð?Pi~øcÄñh6Z•›ì¼Æ⹚<ËBçœ ¼W' ¿Š­¾5xâ'†¼=oâ}[KA¨ÚÜ\"¬V m…’W‰Š‚X){##9¯®~.þÕ¾ø‹$Ú®—ðò×GÐo´ù-ì-,$Œ]jwÖMÝÅÒªrÓÓâ„ÈÞ#ð‹³.¼S¥[ÙZÕm‰–|-Ì“³~ò\Ae9äçÚºéGŒþ%jwº}λÕl ’ÖÞ[§9¤¶`–bHÊg8§ÍgÄ6ßþ&jž$ñ¿„µm7Kñ$0 êÜ£%Š|‘¬ó"6QˆË€NO5ƒãïƒ:'…¼G¬|4ñW›gâŸ_ÄbŽÕÄ‘ÝÀe’gv6r~ï~:Õò¢“?J~|³ñG†ô߇^ X­¼QªÊö÷žlÞe¼‰;<–{˜d4ÊŒ€2 \ñ^Ñâßêžø¡kZ~£g&¿{sw§¥¤cíZŽ™q;w+ ¡ò¾˜ç#8¯Ï¯Ù®ÏÂ,ñV«£^øÒÍ¥jYÐõ[§VïäOõa”–W]¹Óƒôí–µáŸi¶?uŸO®ëþ7ÖomoÔ"ǧÞA)I¤– –;‚¹ŽE‚à .MrTM2^ÿ…ãÛ†ºN¥«j–óëRéÓ<–÷|ÜM#H^â8úbRpvç¨À¯6–ÛWÕ´A©hOq¡jë ¾£oóSN¿·Pð̤`™@‘F z¶—㯠iz¤>$M;\E©,_`$˸ñ¯Ã,k·š^± ÙÛ0š)ôÙ U’ÖEÏú¶‘{‡+Íã×5OjPxM²]*üi^X‰T¤Ñ6™uòDÄá‘dÇÐ{ÕÝç0ƒÂÉs¡üH½Ð¬Ç‹|=¨‘¡jѰ°(—"rŽ­±”äÏ¥xV½ðSW¼²Õþ!|=½¸>% <ÐÙ\© p#ÔIáî¹*zõ¯¦.þ\øßÃ/ñnöÖ)áŠíÞËPÙ¦³‘\ÄñðÌmÈžµÉx¿Çš¾ã? øËÖ“5Ž—m¨>¢“FJAuªÏò—aŒãåläÕ <ßâÆëÚ·ˆµ§Í¤i:µæÃlI-f»\ …¥ÐqƒY>øéðëâsC…-f¹Ä±Zf‹ÚöÒ\#ùhÀ§Zäü_q‘à|'ñJÓõ‹(šî]BÞ ¥v-Ê@Êoä6xíší4ŸˆŸü#y¥j4·Ôô˽>;­-í`Xá¹yÎǹŽP?Õ©êry÷  ¿þ0|8ýžZÃÆÿ¤[=;â û\Nïhlï"¥ÌE€Â³)óã’¬Ý}ð¾ûHþÆOÙŸ_ºkŸI=ØÒ•˜Âú³$¶°—;UÜ¢‚ ¶iÇJùîæ=^MvãEñf¥µá;«Û·ŠêXÄö¶~Kà$©‚ÆAÈä€H5µñâ?¾üEÓ¾|FОmgI|èZå”{÷PµpÖ²[;’³l|’Ã= m_j_ ôŸ„ž#ø}áí'ñdf7w7OºÓi-$Ë,q2"ÈûØÜqÍ{·>)ü,Õÿh;Çÿtè¼C}:m¥ÅìN¶7Rݧ•Ÿ0®U<Í à¤7`×;ãٳúެ<¢]Am|4»]ÊÌYå–üÉðÛÆ‡2˜™]¥ÇÌ»€#.+'Ä^ÐþA?€áñ]¬žð\–÷º^«c*Éo4 óyÄy)p’1hHc„Ïf€0g‚÷Ãÿ/¾|Sðž›®xgWû>Ÿª[áE·ÙgÙqm÷·K‚Æøä3^S{àit­RëÅz¤:ärI,Å™p«…q?1QÆÒ~ö?ÇR|Lø'Ú¼?{kª.¢²I$7r6A%6\ù¼†‘P1þ0¿ž<ßüEàÍZÛIñvŽñC-Å•ÍÊÌá¡Þ¯ó¨Çc*®rs‚8æ€:v_Š_-ìu½>ô]7G{»›+û…‹ìkm)D-رÆPTÿ|sâ­oÇÚo4«é´½ ÒÝâËÜà¸9ùq°ǽrš¿ˆ4_é¯{7†¥Ö!ÖîÖæúm<ùRAŒÅa*~ñÇêž—Âñø»J¶ñçƒüU ‡Ú#<¥Å¨-ó·<•9 í)ø{màíÆ]Ÿˆ< ñWžÆ]X8‘­õ HŒ’ ›‚ªè xÈÆrExwíáߊӟß´+ß·ˆì.VËÂ(Ï0»’ó!xcrd0ãä9äW—ø?áׯOx;ÿ õhÕ½É2˸LX!u•äPF7FÌç9ÚºÈ>=é~µð…߃µ;ØüQà½bÑ´ÍUØ â¶±- ¤ÊmÉ\w¯S“E€ê?h;­.?‰qx®ËC¿]'ĶV+käDP[A) Gµ±²HYHp«ÅüãmÓÅž ñ=ÕÔÚæ¯ª]aˆysÛ¢ªK2 € ETcžyïZbøûàßÚÄ+ñ¿Š§Õ<1äÃ6%Œ'•ü+)‰xÛ™"EÆá&ÖRMßøwW×-¼=ñ·IÓ¬-­ôW¿ÒáÆTOiª”² P̪JœeW ú–`yÓü<‡ÁúEæ½ðËÄÑê‘i±FYÔŒµîÒÄ m °8Ç\œö®×áÆ‹àðv¶;kø›¸˜<–öhâ?´:’RAµöŒóOð‡‚¾øOà‡Œ5+¯I§jöéo`úÄl×wQEõXÎ "TÞUÔ0ÝÅsžøàßü3°ø§ðÇ“h8Ò¼/?ö]ä=“½äCÌ"‘’) ®áµ‘ÎÜçYbÃLÕì¼_‰uoR}'Y"â­Äp¼ÑÈÖàä}Ü•ùOâ¹hŸ^øà½àÕŠ\ÚǪµÖ{ò粸ü‡_1rÎ+Ò> |KÒ~4x†okÐÚøÐfæ áØ‰¨ey0 <Ñ•òX׸x#FÒmDø Ú-RK™Ögb¥I>ZT`ßÅŒcš¥n s³Ã<-aðúY’Ëâ_„ofÔemN°Iöt‘½¬ˆ¬ 0Û¾&¦ÓQËâ;M#Uÿ„"ÇÎðÖ—¨‹3ƲEf÷@OŒ±x‹Ä¸µñe¼÷VÇ6Ð!\z˜òŸ™;P;³?ö øñGSø9¢x ᥦ –­¨Âºåü [Žò!³ÏC ùÕcgp0kËt/Šzµ÷ˆß~Ï^7´²øµÕ¾›©ÿf\ÇÃ*²²íã…ˆRíç#ž+ó£á×Äøã_øT'J}2{(ñdº“Çv$Qn« #ãc/B}Mã[¿\ÉmÆ.]o^ž{k­'Q,"¸†äœ´ ,²;†¤ÐÏ]ø³­Øøãâ-×…ü¢[i×> mbâ(¶ý¯F½&4fP2ÂÝ×>`åUˆÀæ»øgKñc|TÕà3[ë3Ûiš¤m’ÒëM˜e”üÙ œ£: ×’êŸo|#âkx<]tá!²²šê+kX¿Òa²º^vŸ»2¡\Iœã8­ øËGñw†ü;ñ¯À~,Ô4ȵ;7Ä^‹c$, S$S Âceèœlc׌gfWã»? ê7‹@øw5ý¶«¦j7óͧÊL1=­˜P¦äqäW%â x3ãö±o³|úF©âÉÌZf½ª¡Š+}N!¶Ý%’1ˆžB6¬‡*X2qZ:—< ã¿ øŸÄ'Ô CTŠaygªi“yaVhß“`§ÌŒü¤g#5èÞÛâo…ZÏ€â×ôûÏD‘ÝÆbQå²Á"½½Ú.qå€Ä†È‹0ãO|Oá]W_ø—û@é) ÛiÖÖÞÔˆžº–‡Lº¥œñí1NÙQ€‡ó¿ø/ãŸÄhµï‹ºu·Ž®f·_¬v†9Õ¸AºçtaC+¾A·'=ú×´|Cøóð»Ãßô¯ |KÒVãH†ïû/Ä–ðòµÄö~[^FXœª3©oöxç俼u éŸõÏê¿eÒað‡ƒo4È£¶rxºi°µSÎ$ ê1Î*ÌÏ:øWûAkþø¡âßj^Óõ-V70xZk© ½:+ØU„;€*Ç&M¸+ÀUÉ$šËð?Â/x›á‘¤xÚî×T_Ø®¥$Rni,ïnLL¹ÆÂè ’»ð; ìéòAá¯i7~¼ÕÞ+U½B\+]©Ur‚²É„en„‚E}ñÀ¾ý õíGâu;kû½],ì¼IeÉh𣷓Ï–ÿ4&TP­ %C'Çé?³®±oc­øoƺ&¹àèZÿHÓ.¤'Q¸L´Q«‚K£²‚¤ŒçS@|É/‚üEá x«Cð}´^!Ò.­LZ¼å]Ú\Ú†!“'8 Wx"¥ñ?Ç­âÃ_ßxþÖ Áâ›o²Á=¹’Â9ìÛn&*3AÉ< ä×¥éŸañF´~Oíï…š•×¼E­Á®Z}’U´Õô–Û$I8áâ`ÍæF¤üÑ’G§¥z¼ú‰/~&‡©ëSø’òà,P[*¬iuSæM 7áÔ|Àã֫äh_ 5½ïM¶X|­]Æ®"\Y´ŒWd¿Â¹mc×<‘^5/Å}DŸÃ?¼fÆÃÄ }f] SZ»‡Éoùç nÅNE­Gð¯5Ûi´‹ýKQk¸%;£´—2ÅP¤Œß0uíœæ¾‡°ý¦&ø‹áé>ø‡F´¾°y͵œÓFaÔ¬ozAuo0àœõ^ ~è:…¶¯ygâÉfc5̶·HN7|‚¡sü«éù´ëZE¯À¶ºF«4“[Ü+âA L€BŽà’ äžôŽÃÆÑø«ã¯<3esdú­ÍœmwÆ ßi1þ_ŽIUx úšáüñ^ÛÄ¿õKgyu¦Ú@ú…¼ÖÑyŽmålÜGóÊx«Þ°ÔôÝ3Sëcyû¤’vù'‰[hX“†’“Q|Jø£àÿüVþÉð•âèæýíÓʵ¸¹B>V$†PÜœmlv8 ƒ¡KO‡ÿþ}†öæâÛH·Ù.ƒªGH-þld9É듃ô­];Ä>8Ñ<[ž#¾}nÂâ²{‹x¾EØ™GFGÆž*í·¾'|6ÕáøeªIk}¦„7 Ú|aÇ‘tÛ›`=J6r„gó®ÃI×>$ê^;>9ð\Öš¯‡.ô÷Ó5[=»Um“"LéýÒxJò¹'Õ|)áŸéšé¢žæo³^XO µ”!Ë#1Táç­3\Ò¼gáo]|Wø}‡FŒ›”ÓZBR;˜Û #y^>•æÖkâÿM'mJéþ&’ &k).¿•†ýÙÇß@Æq_GYx?M±ð…¦kø¯µ»sæÌìÆ’E;ÄNÜoOîõ>”08†ÿo¯>%ÞxËÆ±[A»,€©L¬n€Xá[>†½ÛâwÄ_‚zÖ©|+ñÖ%¯ˆ´øÖ[}V+p"»ÓïäŠp>ñFà9SÐàšð=Gྛ¤|,°ñ¿†à»  Ã}%ÑómRçÌb xF”÷ˆí_þ |kø)ð‹Ã|m¢SRUÒ£KÈ÷:¼ ɉ^#ÙéH&4¶øsðSBÒ-·Ã¥ÉŽÐ+–…dFûÊ@£æÚø³Àúׇ</Œ<%yi¨Ø^ï%Žg æùWž{ƒÓŠóÝâï‚'M*ãY”ÛYG½U E’!Ÿ¦xcƒÁü+ÍcÔô=.î÷]ðÍê\húÍÄpˆO²ÆÏÕz¤tþéö iøk¤ê^0“Iðý­Õ„ê7]£‚óî-±àF:aZz¤þÑáûÆ—ݸiíïÓÌOW?)9—¨ôâ²oþ xÃ6ÚÍåæ¾× á‰RâÓKšSIˆÜ€ûÈs€Tú÷¬jG…>$ü+MGÃ÷®[ZÓ/dkµ·–6+$kÝãþñ ù”qíA-"ë>Öü(Úl· vŒf6$IÃû²9e#¿àk¡ñ$ÒiZ5Ÿ…cºšöÒKoõ¶Ún¾dNdCœWoá¯éÞ0·Ñõm]ØÛÝÈÂö3…k)ؕێ~N7£Úø“ÇþÔ¬ô¯ ɪ­µÍ¥Ù³†Fæš>v«¹ùq»£Ð$ŽïÆ7Þ ñç…|)ªx?ÃÒ¾±á[E‡PH‰Þ!%Án>fŒýÓŒ€HïTü[áïx³û:h¤„GYHÙs˜¤VÁúï\þ¬ux’Á<5©½Ž·pÛË·÷ÉS ÈÈÁ=k[Å> Õ¿áOøòÖ8¯-îÕlÜJ¡[ ¥ÊãåAžçµ&ÍO .µ›ô뇞*ðíœqGÕ¤Uû\!³åöûƒî–º×!}ðËǚ׉­|Kà}RØê¶Z¶›(1Ja^ ±žÃpÊ=kÈSâþ™¢é²_ÛÊ.hLH–Ò,Ë$ªxQœ½ðyÏ|J×>-jžÓåŽM9¬.%æFÝžèäöœïÇÒ¢Ãä{Ÿlè_>øÛš7Ä­=,|Oáô{{-J(°%Um’Áv gr¹ÎsÁלi^±Õu¸|C¦IÔÚ4Á<×Q»ìä}×e眯¦+ÄüQ6?õ©üwmâIµÍfsûÚ„O†ve)ó)Îr®××^"Ò.¬ümã[Y¼%u¨[5¨¼·c-Üë÷LŠs‚@ ‚3Ïiø_Eøeâ_ø’÷ÅV·­y5®ÛWÓ.6÷öä¼s o•„‹òJ„À äµÝwÆþñRkò¤Bö¡DÖç;Dƒœ¦2vž£V‰eüm¶Ñþ)xÆŸð‚x“Fžéç³G(2‰C7cåÈ#ñ_Yü=øû{â?ØéÞ/Ó4ÝkXÑD—±ftüÍ€w¹õ4Ê|>m;IðÔ×^ _>ÚúC<ñ[°V?€Ã/B jj7~‹Æúî»àÍN}SE¹·÷€byyR¯GhÈÆ}+ÀµÍsÁw7³G¡O5þû¶kh`FßV8=zì¾ønþ?j6SÎWO¾µi^U;~Ï 3/b?ZÖ¸ñå…÷‡æÑ“=»M2ÜÙ\–Ëç;Y[*SŽN I¨X_ø{Hÿ„#MŠ KG´ r­bYVÕæ%˜ÉžO$†·ü/¥ë1X/†u­BÚïO–Úè¨%Kž²@'å9«2xoXÒìïôÉx ã>ƒâß Zøvâòß]‹MbX§Ì2Éò…MëÐÏ­^Óõí}B6M+·ËÙ]Ï'#Ä gÖ¼ÃÆ?²Ý®±©ÍâÛe ]Ɔi´Ø£7:q˜cqI‡(ÉÛØ×KáI¤ÜZx.ö¿²Še6¾j‰-ä.>x÷ç§ñd¶÷ uªÇâµ\»Æ9™\àe;†;ñV ÿÕõÝ} LÔnt׸ƒRh÷ysÛ@D[—²äýMWµñ¸q¨–ÐC#b(á.GRDZÆ>¦µ<_­x{U½aáý.÷KÓ9Ù˜&`o”¨àŸEÍp·W]¬ÇLÓå’ED9&1ŒîbAŠü¼û@‘g¸Ô`²Ó`RÊxG\¶ðz98õé[!ðn¦d[ÝrÞ©€}H*: µÓüC#C7‡¡Š9&@·Í!R±u!v÷>œWR¾Û¼Ô<É®[fIEúóÉ÷$Щ]Íöxtí<³‘´÷àGÍ^•>£©éšlÒ\Y½Õö®G—$®ps誸E@uYQ™î°±Dim^8Ç\ŸÃ¯¥_’ în-4Í>㺽eK`‰YØð€X’xÆ3MnŸ_ZøÊÒX|GªÞRà‚ÑïÌ›ñ¸uÍtl¶¿nOìðòÍ9æà.AÐ&y#×5îßð‚éÿ 4$Ö~2hÐ u™ž4i|é•IùvÄ>™çq\—Û¼ªE¡~ÓA楴@&vý3ÐõíVϺ֛á{9ËkŒ“\K(ívô8ôö¯`¹Ót3PŠÿS·0Æ@N~c“Àä“·¶kì´­|ëÚ-¨¼¿.@icfŠ,õÊŽ¼vÍ_ñÖªZKK«Ù Í1 +ah(áWùÐͯkã]{åºû;ÅÄYȸ÷ÏáT/u¡¢Î’ZÞÍý£<€y’íÕˆê.*·öLMä\Ïuç»ãg\"¯bX÷Ï¥R’ÚÎFŠ{=×>åi¸Ú¹=ëõ ƒM†ÓN½ûn ¢êé ìsÏ×8äúýjáðýÅìºÊÑ+ùÑÅ ªs׎ýñSM¯hZ¦—k s]íU #ryÅU׈[I#0ËnIÁ@YÇ}¸ïíAsCR¶š B=_AgŠ ãsmÇÍØÓßÖ¨j0-•ÙÔ#ØÏ&XÏ<€»1ë’NI=‡j³¢xW@:màh!Yc,OA¸zÿJò»ý U½ñ*Û궦ÞÜ4d%û è Îq@$uÚ‡‹,.fþüµ¹¼[òª²F¡sw`-’jÄz¦’°Å¡O2ÝFûDÏ)9Ç.ãƒÁÀÇzõsTðõŸ…­í|9mg§G¥í+ƒóÊ穚F;±œøW›É¦Ï¯Þ¯¯çO³ééˆãÚ<¤$u üÄ÷ |¤šv¡a¥Fóx‰þÓ+£¬K?ÎGk‘ê3ÔÕ´Õ¤M.x¼3¦Äú”ª¨.&ð‡û£ZÀ‹V¾¸hîM¡™%,ˆÛrû½@ô«ŒuÛ&g‚8À$íd^àc nüèÑ— Òntµ–{û›[‹²¸ÜNÏ/º?ƈ¬<#=‡Ÿw„ú‹!w )l¨8ÉÀ#pþ鿍[ZÛN=6k%ˆ…ÞÌÙó9öÏâMu^‚ÍVMOm€+åomd噳ëïAIœDZBÜ_[ê–ÓýœZ‘,P*ìýêò¹^~\õÈ®ùçñ¡vˆe’ûQ• {ŒäF`íýÇkú¦“£ù—V×){%ÁÚdpvœmUêG½aø/Å%µ‰µI ¼kvO$¿ÞQŒøü‡A@ËpxcÄI 0Ä­.C±äÛÆ?ˆ·@üjÄ‹o–+OßÜ-œ2‡•Ù°²¨í¸àõí“Åvºw…üGyá›ÿÛ,ôxågó®[{±¢ÅÁ ö<×)«[Í}o±âkÉŒn|¸£Û墯‰cMz^—/öþž± ƒL\\K e™žv=ÎOô¯®¼.… ´šÃk ]f…šæîiUæ—ž²ÊØÆ>诓üã I´[¿éÖÎúS”Ef  ÆyÆæã5õW…ü@5+*æÊøbS¬V‹¼ mì_Ê­lKGMð·Âþ;¿Óç÷BÊþÞée¿{t2ܸ`|¸ÄŸ2¢ à‚k«½ÑtkmD[]ê²ê—Êî[–e“vGÌ3€;.{Wi«kZ®àí?ÀVv‡J[…wšßN]‘Èò`nšAûÉ$aŽ:*;ÃRøtEáýI~VÔŽÊÀni^OöUÅÉž{zÞ³¸²·šÞ[뫉Iòã܇÷ºŒþMoi°YÇy>§®Ì¦Úê¶JKªFzg'ûf¯ÞËá?êz®·©Í$—_ÀÑ íå.sDZY–6·ú¬©s=Œ¹²,£l#†,;z¹ª$ì´ døãS_jz”Zo‡ádöûvþíWåU 3’@dâ¹ÿøJãTj63µ•ŒS+[C6I‡a’Pc°ÍêOâÍfÑm´»8ešYK,p.ϺrNþ¾I­ý5uY¶¾^(®H]MÄ{Ÿ³9á˜v8 _»²Mm­îµ-N B{bd+3…Œ2ô pqÛ&³5­_ÅÚuÚÛÞßZËkbKç³RÏ ª„eU›§B@çÞ¾vפ¹ñ/ˆR¸†å’ó%»hJùÎ8 œ`(öü³]?‡|Oñf<éšõ­’YDÁíaµŒ üs9À` ¤wæ€.x¤x§Ä~!·¶Ó4GÔØ{x† „œüÓ>p»FîwtiÚ¶‰©Xè7×ú¥ý„¿¾º¸MêòQWyÇÝ¥ziâý{^ŠMzÒêFxÆåU oe3¹›XöôñsRðŸ‡#ðç‡å‚ëO³}ÒiÖcååòMÄ  ³ PŸiZV¯’m_PÑá Q!òŒp\œ`t g­tZw„Äž7WÕ ‚u±…V ˆ¢ý–ëÏëZ?¼qâÍfÒ;‹‹›}&IcO+L°PŽ@'$ê}k—ÒôÝJ3æø·Rº¹ûRå<¯õ‘ämÇ]½ÉÎMv߉¼5e¨ÚéZÖ§±¾”,V6ñ.õ —<7"`c'ŒV¥¼—z߈üø4˜lm4ó$@®˜b€1Ô\å…ü)àÏ êŸ´Xn!Ö]L6ó] ólo”³IèíT5KßÏáË-kÄS¬1êòìÆw°òÆX€Gs@‡à­Cû*]gÆZf©u „¼_mŠ¡ÇÞXÙÜ@ÈÈêkÈüŠå¼aw©Øx£N]FÖ"í*ãçeÉPLg¯ëSêÚ‡‹gð6gj#´Õ/çŠÚÆÇ™*Hù•ØŠHàb´ õ/ iw„ÿ³¼ Ü¿—<_¾,Fénçe|¹<ùVœ°ëfÿûGWpÚœ›c·*ù¯êyè£ïÔ×®K{ã%Ò¯®$e·”[Û#Ú1´d°à ÕË(.ïæ¼¶¢[Å3@!a¼ˆ—©føYqàJڠãÏó®ürìƒïHðt‰5–’ kh$´·†6 Y6±=áŠÔŽ÷Äsi¨jÓ?Ùç”Å$…¶´Š;Pu÷=Å@7ŠtÍ͉¨é\†ktÈy“|rŒ³‘ùõâªêòêµÑ-l’ëRw/0µŒ()ü`g±ÇéV/[ÄpÜÜ=ö¶m4”`ÆÚ>pK÷88Àbxº÷[G‹IðnµŒJŸh»sy¾JŽ99ÛÏ;G'­hˆgŸj×&Ñ/lÌ¢=Ã|œ”rÌ Î=qTwÿÚ3|O%î­e¢øwEèjH'žfbDAWsÃþyƒÀíÅy•ku¬]M‰µlZÛHÖââ53cŽ€pF3Þ®]›ë–¼Ú(w¬·*7׃ܞ•Oý"Ã@[ß!ãÑîo –æT\³Ëð©éów¾T÷ãMðBx‰¡ÔZy%¶¶•‡ÿ–Û>ê}8âµt+½Têr^jö„ÄChÈ› ŸZ!Ö§Ôµ–þÓ_ì¸íʘÚgØ¥›î®;6:ù¨µOÏ-ïÙ$>HÂÄqÆIëî=¨&GA§øÒòÒ /ÍøXGp1œŸÈzVä?¯<[¦Üéºý¤lÇkB±ü©òp¥ÉçŽø¯=ð÷„.õx¤¸½-`20؃{ÉŽ¬às×µzD:/ƒmn­æPyfÕ 9 8%‰àdôPIé®àÐ,a+r//§+¸ñ¼ã 8ç Å}=«kz²Ão>£kä¬Ð Òpá¶ñžy•ñï…¤Òmï$ñ#E1”xñ±ô?oZú[BÖçžÑ Õ|½ïl~²× @çŠâµÛ IJ´zœæ1§ÆòGv<23ƒŸz£7ƒ´K´’KËõ+q°ˆQ \d¤üU‹ü3em{¯cv-¢bæáäBÇs„ŽH'¹¯+±´Õ.µˆL½i|ð[ÎûåØO^xsÅ$€ùkâG‚|W¡\^GªÖRÜÉ?N:zõ¯)Ð^Ko(º‚'‰Ý$c§=‡ôïÆmWX±°Õ-õ ˜,NÕ._Œúð5ñ΃«ZYj±ØƒQ˜ÉÎsÆsïúÖ°`?â=±žÚh› äF)9ê¹öÿ ã~x7Çÿt«hÚgÚS@Ô—x ˜å9¸û¹ÔçÒ½oÆVškivú˜´Yí`ïbqÓ€qÅsüUÿGŠŠ´y¦šÍæXg¶·!]˜.ä''Aìq×9­ÖÀu^$³ð]¿‹ãøi«Y¶‘i¦Ë,w¿cŒ!û\?9c“–}¸PÍžyÍ{¿>.xGR²ÓX<Çc¬1åeÓIŒ<„c?/'$W—êþðUÖ†>(ø†åõ-GV¾™oí£ož œä—Aœ¨R'œô¯6> °ÓÚ]cEw–ª]XÚÝ–Û! œrr{ëD€ú¢/ø^eƒV‚ÁWÄVNÐ…³?êÓoÉ#v§$äd6é^7âmÅú§‡íu+¹Ö]+N{©Ôo9L %›æÇ¯<Ô#½µY§Ô4&ŽÖk¹È)Wxåü ô¤ÕuïZèÑh©Ó‰¼µWå`ÌÄžX“J€2í_ÀšM‰Ð5›ÛÙPX1Ó¡µ-±îúìf>I<óRêðx Ä_ Ï‹ìgžÛSÓ_É6²'úéJä0Ç „‚ $÷ïÅ3OE¼ÔLz„köFÎ$”mxý‚öÉï]=Þ¿EÔ"ŽÎF¼»¬`ƒQò•Î2:Œzæ€8->[Ý26¼0Cs ÅËou ¿Zïµ cÃÒx~îÚóÃÑÁ²Á-Ì|ŸiÆpKÌzÒ¼–æúÑ/·Ù‰-ÙgÌ?)9¡ãÛÞ½cÞ&Òm´™lïaþÑÕn®àùÏÈØtÇBs@¿ƒÄ:]î«i¨Gë6éåÈþh+ H¡œF1œ°ùX.½{/‹…?.i'*0xîEylWîºÝÚ6¶šƒß4s)2l°»H<ã9ô5èú¤¦×R‚MvEÔnbŸiF>t*WpÈÇËÁ­ fÛGþ,“Ìiá¸i+vÝö…“™}É|‘“Ü×Qã]\¼ñ?‡õ G…tQm •µŒ¡™„21ie“üÅŽÐy®2]ØxŽïRЮ¶2À‘Çç.Abr@äŸQÒ½ê_Xiõ Çjªöñ%ÕÇï&Œœec_î)äÔÔ5`,éxßW¿¹ƒâ§W›Ãð4Z]Â,çnÕé’6ävÇ Õ|9{c˜u7]kìqBñBÆ4E¾Ju!ù?1¨4¿Zø{ÁZ†—­­ÝÄ6j׌ä}˜ùŠŽCÀq^ià Zø¯Ä7:®§IÌŒûï®ä)p[œõÀ¤‡ŠtŸíE|W¤oªÇK<²(–¹e<õ'Ö½Ïמð×/$ñÅ–ƒ<óøsOò.5ktŠky”3DÊÀ|¸ôóÏ¡°8ýSL èø:çR/*TàÆ0AÆN6žž¦½—ÀZ\K¨Á.· !l®#ó—ýc.âÊ9?;‚0;Wžx½¼A§i£ÁVBÞêH¶O˜Ðy‚6lðçû¤ò3Ò»+ë¡ck¦júÌËlÒL-Â3²Ár‡<“ÒÓµK+mNÎïâ}¬Ú=½åÌââ8ýByepIá¢i§@¾¿šîSeý Í gwmBÀq½Æ ô­»™ôv×o¯/u{‹…ñG1/á·‘œ dã­Z¸ðÖ¿âiJÓOý¡»l1ňY`êû€ÀÀî~”ì4Q𿆴ÙüOaõò®—} ×C–ˆ„ú«6úôo{•i‚^g³i oháÁ(\ƒ¸|¥}ë´ºð®4ˆth´V±¶Ž T÷НÞsÒ·4¯Gàû˜†°‰46××-ÜÈ$ÝÏ99ý))¸»¡µÌ¬ÏÅÿÛgþ 7¬üL¹ƒãŸìhl4‰ŠŸd¼K¹E–£§NêfŽb‘Hcq†d‘e#“µŽ ÿh¯„_µÿì7âfÖ¾2øVËþ½dH§Ñu!¨ià¶wFÄ,r«\¶ø”|Á²FHþä¥ÕôM3Z¶Ò'˜­œð¼¡²,­ëì+ľ.x;¾=ÐgµŠÁ/æ¸ûTa‘¹#”pAÏ`}sÒ½\>>VJG \2ÝÇßÂÛçá÷† Ô5ßCáËÂe²X¢‰$j ‚ø†Æ*rxë^Ùá_ÚOÿ„ÛEøËu‘g Xéþ³·þиºI‹G;¾CmÜ̬¹ÀÂÞ¼ö°ý‚¾*ü¸ñF¯à—]{ÁI¯Æº|sŽî µ-3F‘© 27—¸à¨èÁ«ß±ô­3ãÌŸ~,x”ø'Y—n§jÖª.]n|ÅÙ”þõzÑ’jéœN n}ñ/ö ø aâ(¼Mñ#TÕü=§Þë6Vúc@ø¢}÷Ñû± !' ,vŠäþ)/‚5oˆvþ*øu­ x†Ò;½RkÒaA©¸>\±s¸&Ö@HÇzâ~)|$ð‡Ä_‡?ôê¶Þ(×<¬Yj§}Ç=Å™Ô 7S«(ÚáãS¼/Ê y§‡|-ñ ão‡®láyáð­ ¶Šy-òQäfÙäÄÃå£tLÊ[ŸBüeøcñGÃ^>ð½¿Å/Ù_xËíúUž‡§Úí¸iáÌjeqåðëÁ*3\ßÄïüF×~1êßc·†Öo i÷¼6Pâ»{iQ¾Xã ómÔm<šömIø{¢x›Ä~"ñ·‡ïçðø×´>Þ}¥/R(c"5Rq"ƒ9óLƒ‚‹ž†½·á´WþŸÁÞ+øeोÆñxjëLÔuË[¿2 Mîn|÷iCõ …9ÈÜWŠ <át¾"øC\Ð~xé4-Ä7Z'†<[k¯£]ßkVW·@\½¼ŽØŽ+(朜+c“¬«ï…_4¿ˆ>1ø}ðûUÕ5]ZÖÃRp¢ÞkÁ|Qç·@qäà¨bI ãƒ[þð÷íñGÆšgÀƒÓèÐ\xjI5»mEàT½·HœLâBä®Nƒ’¹çŠà| §x‹á?Å™>9þÑɬëÿN3Ív:'Ž<]§øSÔþjVºÑt·°Ö¥Ò–ïSŽ7¬rƇ!ÖEÊ’XñÔŠã|EðÛÇ>xzßEñ„úÙøƒ©ý£Å~’1’Z4mö‰¶H¨¸ bDVݻƒCÅZ~xÅZxÕtÿx‹Å\Z$ZlLöÆÊòä\k¬™VAµJÈÜH äœùÿt=wã¯ÃŸ|W¸ñV™ˆõ-:È´:¬ÿg¼™¢F[O³yJdj¾Y\œsCøÓ_ø“ªx;â×í¬65ž°þ°Ò£¶DžÓäiæ=‹™è8_˜–Ø8f¹{oiþ‡ªx§Ã:$ú¶…£ë—~¾Ô®aû:YÂê²D°Dà;˜â<°ɤ·ë KøðìÂðÆ{âM'JÐ-õM2æù¢[˜žE2êV¶‰„쬬z }7áî— µž¯á; “P´’@–£MiöÛÇ+@|®IûÙò†·¨7‡¯¼4|5«¯…â¹[£ù­ÃÈIyq_Bx*ãâ/ÅËß|PÖå{{_ÝÛ›«{FÜ5EŽá~ÎÃï¦òS¶sÍOñ³àn¯ðsö¨½ðwÄ-.ÃÆÖɻ匂GzÂ×¼9ðÛÀ? ìu_Lú5þäÚØè7)ûù„ŒYåu9,20­ŽÔìž ñW‹€ß¼7‡4Þ+Ô­lí Ôàã¶iˆÛï$³ ÅŽGc^]ñ+Â÷^’?ݲMwªYIn-4ó&fÿZÄœ€ 6ÑÁ=븱ø‹ð“Z½áÏøäÕWU[9®l´u!¢ŽI7\'8f>SÏñÿˆ×ÚÃ85ï é·7rÛi:µÜÿiÔó-b’0D ÄuCÁï¸ó@þñ¿Œ4m6ò/,²éºI†Å¾Ôªn-"gV¸TÁNF0k¢Õ'ê:~‹5ºéþÖn¤‚h,§c]Üá· £$ z—ƒ¾+ꚉÑ~$™£ÔfÕVêÂUSÒ’DÚB†‰¹ž95â·„ü7‡¯ô»Å¿¾Õõ€Ù¬Oæ[Ä‹´åâ_9”‡^»222+WãŽ>!|lø‡yñžÇÖ>Òb¼µ°‹NÒ¦ýÆ›2G°HŠÇ´HS{í{#žM)E4RgÕ¿ þ Ékâ.çPþ#‡Z]Q~Èï1ÛÍm mWÃT¶pØ#¥{¯Á‡úÇßÙòÇâ/öeöƒ­K«ê—×éäÇ6•~õHì2Ïp{WçV‰ð»ãö«á£ñÛ©e}¡øB+}wU’æçɼ´´–wò¦†2„\G)F ®ÑÐ1⾌ÕüñgÅ¿ üCŒ|,¿Ht½atæ°û ÊKmn®†SœÉó1äÕíOâ&«õìßÙj×Zj«ÃvJù“¼X ªªãŽ}:W•Þül7~ ðÂ{;;X4ÇÓí¯/Y¸d!óœaÐd‘óu­Oø(ø;âœ^'Ö…åχ÷­þ™s¥J·2$w¬ˆKHWqnG(ÎGÇzãø‚A|¶7j³ÎîBJE•ÅÁÀ!À•Î7 sZ^-ð¯Ä?ü+Ò|G¤øÊ×_µ¼”ÿhi(]XÉ”s.yhÈ'hÀÁïXÐüBy4oÁsåA¤jÚ”7 7†âUˆdÈ cå'Oµü+§xÃvm©__]jÚ?Œf‘Ñ’2º•´K²C/OÊHç€;UÄø…tÿ |TøoZ‚æÓQ o¨ß K¤šÇËo§+¸œsÕxï\ׄ>]üCðÕ‡Ä/‹ZÛxsÂÚTF)®b3K%…Ãoò<µÛÔyª_ oí>ݯöVÚCqshnX2ʬÐüÏaØžõôŒ¼HŸ4 ÄÞ4– 'LðLJe²‡O· éqæ¦$ŸÌݹ€ë@c«iúF‘àOGnº^©©êsiÒØÉo1KÅ2.zK‘œò VŸ­.ü/ð÷Oøµk&¯©išã¶¹ ^ÊÑÿu„8J§ ¹ÏÊ3Ÿ^ªÖÇÿ4Ëİû<Ú.«kj·?òÔ=ª<±äýÕÉõû¼s^[¯ø^ïN¿ñWÅ}< SL¾µfÒþÓ ’G½³”Áw0óTáÐrqÎ*¹H{^Ž=7Åöºá¼#ü#Ñ@76§ktf2Ã;3sµÑ€àrGNbúo€uËHõѯ¥j­«ê>Ï$±M †wÿ>ìd S|K®êšö¢øƒHµŽmNhno$Œü·©lUTöƒœž:+Ö¾ [ÙhÞ>Ôõ­z¨é÷zdÐ6—Oİ À!Fü‚yÈ&“E&s7ñ–âß„Úį†š4¶'P°Õ–îD ½£Än]År`sïQüOðþ­èžñ„õ‹»û cJºI®'PÒZ4nÒ:ŒyooGãÍQðG/¼Sã½+TÓ&‹KðµÔâÙ´˜š(àE}è÷wðqÎÜ‘Æ+GÀ~?ð‡‡¼3Âˉ†•â&ŸÄr[±„”1N"†2„ä: þ|ÒÈ/Äïˆzßlu®çà/ìóðêûY_Ú[Äòhº.“ÖÐÿan2$É‚›×iÂÄä>0w”ŒVðCöOøŸÇöv£¤}°¥¤k E'–»$Uc‡E Å—' }E{§ø×ZÖþÙßøVÚµjmczñ¹ÉþÐV "Iù0Àb½OÁÿu-BíuŸj ¥ØxßĶ× f3äY\0Šdu ŸÝÉûÑ€0®'Áž¿Òµ½FñòWÃ÷ÖpÞIŽØÆê²©7n£ šôt³økªÝgÃzT6zmÍÔÚuÔSNK-Ü*e3ÀùÈY‡ßÃ#­èÝöøÿðËÁ³Øü,¿_éðë7v|³>{K‹WtÝnîYBË|ðx=«àľ'ñ»â¿ Å~÷ê²Æ—® ‘쌒™äÀÇÓŠô¯ø†ƒ¾:Ó/tX¤›Nѵˆn.íPŸ³\ùèLxõßÀ=¶àñ^µð/Æ¿ ¾ü6·ñ5Å»Á¥xš[­űٲ«Åiu;=½ë¯_6ÚB;ä¡n´Xg”ü&ø‘?ƒõý"÷ÃÒÞ6¢×ÑÚ[\y…„ÓF¼ÈÀõI@ØÛ¸>Õ¼Þ?_ |ñïƒlà²7:¬(Ï5Ä@\["!ÝmÕXK´@Ù®÷ÄE½¿ˆtO5¼ „¬ãµŸT„†[åÔ/ ‰y^¢8˜pzb¸/!ÿ„×S°·–ÞóKÔµr’ßí_0&šë³©Î7ªî ÷ ÓáŒ4ω>ƒRñî£4V×Ú/Š|8MÂo{k­GÈòŽCyf&òÉé¸àŒœý«ñ áïìË«A©ÙCsa.¯£^G«Ãm=¦Í¡³Æ6yƒÓ'?7òUî›/Æ-+^³ø+$ÐÝxw_Ó¼«i[¤×bV”!l(J›¾V¯O×?j¯‰PøC_ðö§:®¯Êo!ÓîF!6Ðy, Úà…œ´|ŒA®U·Ö5[ŸøLîÃV¾"³m>}fés²;E3A4%ÆÝ²6ÏBÒ¼‚Ãá—ï|-á놓Þx®ãÀI.§Þ]B<«Xõ³Maò€Ku&sµ.­ü_§éÞ øÃ¯ÝÇqá‹NËJMºË¨FX˪ãnÔ$g®AÏQ^Õ |kðÿ‡|Yâ?„þ>Ñ®¼/¯A=½ùÔ4ßšÆU±”Ê:tPèÀo998„Ï>‹sjb¹·‘v”ûS™XŸœ©û¸æ“ìø7K»ŸÅ_noe¸“W›íº’,VñÜ  œäÜòErÚ?‰µøGuï‡ÐÍ©Üéz^©‰¡–ÒàÅçi7‰þ“l¿6àÑÜfU=rƒâ¿üm~Ÿ‡ÖêK-7S††W[iáÚ•ð@\çô8Jµã[Ûïì[¿Š¾n¼ð»išˆöw“•VÈã÷2|¤…RH9¤ ÿõ}ÞÎëI:<6î-nÞâ:,IEBç Àuë^©/ŒøÐé⟠ê…©•ôéú}ÅŒù"×Î%ÀLrU”°ÀãŸjô];áw‡o´¿x£IÖàÓtýjÚêQ­â±¿ Ã1BɆBØÃpGZóKš½”¿ôïZ\kún´g¸Ñ#‹Ë˜[)Ü·QîÉt~21òœŠ®P4´«çåHPùŽ1Õsìx_Œ_SÀúý—Œ¯mê6·˜gøÊYvz`sŠî<+ã¯ɤ­Ç‹l-’}OKºÓ[G±„ËbÖ¯ïV0ÆGÍó j;™?tcÄ/Ò>økÂÑêú´fïLÔlÙ!±½³¶>avf?)Úq³Œ“ÓƒT¼¥|:ø㫸<[¦ÚëDjRÚêÚ ÆÒB“d€œ6ø982Ž+Í>x[ÅZ¼m½çˆe¶ðî‹-Ä Cñü§$–±O½žz×UñƒöfðoÆZ|gÓ5{{jÐ<ÖÓÜ]oRxÆ^(Èûû—ï+üØ5b8ÝÅ|;ñ.·§Xéïâojе¤°«,WJVVJŽFCyxܧ ƒŸøkðŸÇ#×uo‡Wþ:Ö<=¯Û¥ý´rëòÉqkspGŸcçI3d]ó¸¶Ï"¾¾Óõo‚ö·Þð÷Šü!-þ£a®DÚ®¯iuöT»´¹m«ªOÌÐ9Sœ€Qq׊ðωzuÇÄ/¾.мP\[꺭ÜWöçæ_'NùmÙU¸o”#Ôî'èúXÑø'£~Ôú½ñ+á¶µzÞ5Öüeo«èÏl±O4ö8Ýt¶ëÄ’´KÕ9Ž™Í{gÃþÎß|Sÿ¿ÃZ=6?*RÏQC¶«y¢ r“åÉî>÷MÝ“…!sþ±¢M¥ÏSŠö(|ñ Á¾ðö«àßêš&©ks.¥ 34wº}ý¤ÑàIÁØë•x_k÷Q\ÿÄo‡ßtyàø­û:xŠ/ø7TÓít¸¦­µ=ögÁ‚î5Ùµ—yRT/˜‡$q“èÞÔ¼U¤|#ñ ¯†µk¨að“È4E‚èË͸p— Œ¶Ý„—‹©>îEf½à‡ÿ¾ø¦oéòø_]µÖ$M;MI;É¢I<§‚Ÿ-ÞÃqÒ¾Æðˆ~¯ìØþ?ø%ޝ†4Û-^µ˜yw»ŒèÇx[láù0Ž>rµñŸÁoŒO}á¯ê÷>ø‘} *Å  T»µÜÏ È·Yá YK¨&½'à{h>Õï¬.o£Õ5^&†îÞDIlµF¶Ú¥YO Û m<Œnâ€V2Æ7'8m5kÆ?øÝ7‰|=ñ3ânœÚΙáÉcûñ¢;,EÕföýçÚm’­CðHÒ/¾&x×öxð熓I“Ožký.Õ¾kX`ŒxbÀQGÌ ŒqÚ½ïàŽŸ§ø–÷ÄZD—tH—írYÇÚ Òež… …HÉ4¹€å¼û$èºÆÿiΙako}u«Çöu+mβ<|…(ÅK•ÇÝ'Ö»ï…ú§¾xËXð·&—©ø³]¹‰mî.v›IóÜÛð’ÆÃ–SÔs\_Š®SÎOBKwW"Õ´Ïx@ñ7ᵊ„¶žË\·¶Ä+xnäD±:gý\ËÇ&¼îçâWÄ?]øfÔçƒVÒï!o6é<©íвC!m¼ÉÐn䯨5ö_‡ü c¯jrë:ŸmqwpÖšf·&OÕäó6›[ÿ,W,0Öï»¶e\ ßC|Bømà3 ÚÉ}a™¨éŠmRy"1[Ü@0Y ùNWpÅÅ|Ùñãös±‡Âš®¥ªÙ¦£oâuš+‹I$ó"a!‚«//#Ø×GáŸ|\_ xcáµð›ÅZnŒÒZ[ÇzÛï¢Ó®a\’\HwÜ ÆÀÌwŒ ³š–Z8Gá¶“»uào^Ae-Ð o 3†‘±’ºƒÐkè…š7€.~"jøï¢^iv±Ä–Ö&veÙw Á9î¿¶G׊épZ|9ñOˆ­õ;KGIµ°MF;[g w`ûö;# –Œñ¹Nv‘ž3^µ£|Pð‡í ðîàË}s¡¤Á5îŸÊ>Ó1l¡‚X`g ÏjéïüsðkÁ:•冩kw«é æ #R»QöÁ§](ÄS4aK2UŽ ePØ5¥¦ü&ðç4_ÄúMë_ˆté_MóeÞËë¸*Ƀ†0ó@›#Ót«ŸøVZ…ýåÍÜ:Η¨—LPļM²F‚E 3Æò¹<1È5Ùk¿¼«ü"ðߎ¼;Í´r][ÜK#n¸’df?M„8\c¯?Òµø'ážñ?EKå½’%¶×­šW– 5>qrRF"6lóåíóÀ¯«£ŸÂÿ¼/¡Ÿ‡ÒÇa©k(ó œª¤üç’æ?2hóŠüã=#H¶¾´¼ûf“$pþì¸ó"¹ˆ1SÀ>›E{”ßüu¯xI|7ãk•Ó|5qöm^ÓVHssa=¡Ä¤?£aÄ‘°àgÂjß5-kÄ:jM>§áí.çξŽÙ·Oc2¾IŒôx_ª±qZ¾0ø~¿dû-ž¼5]ëK‡S‚G5ºÇpYdG\ðÀ˜wÏ4Àx“Ã?õk©>'ü<ñž«¤é¥ÒëQÒÌ.ÙC.ä4B@yR>Vï[MñfÿÃ^‡ÆtÛÍKD¹š!­éV8ÿŒOŸ›jà}¹®nOjv'Wñ/…ô¸âÑ5«+CqöUP.mS îÛû$‚­MÆš¿…­n´ ßÝ660Åg0YmxÜY‚AŽÝè!îq_¼ma¦~Óšw†¼e{æøYÓþÛàËÙåiôë…2•) ÊÄ\(e ¦pqÈÍs.«ãk{¿i—W7ÖsÖÖ ¹v\ñF™ 03ÀÉš¥ã;†ºå¼Z~¹§5¤z êÞÙF)H„IäÀ‰ÉYØ@“_Rê? 5oü%×þ"ø*ãûJãQGyô·ùÊáŠÊjqœwl•7ó®¥àŸƒš'ƽCö7:†‡âä]9Ÿd€íùe1¾>B™å ƒÅW“â·‰üñVëÂvéã _ÞÄÒ[ÆÄyÈrÉcˆîcns•½n=%¿l…òø³I´³‡Å\­¢_]·ÙotíZ±ËmzŸy¡»@ r~l61Xž*ø_âƒ> ½Ñ´ŒÛøÎTK»kˆT*Ë5¯úȘv±ƒß¦yÅ4Á³¡‡__ˆ ügð§Jý‡âË_"ïK1º¶»…ËÇqî^A÷[ö™ý¸?j?ÚŸà—…>üCÒ4í-6à]ɬZþýÍÕ”FÕã#ä|ÑÉ`z׊C©ø§Rº·ñ&Ÿ¯Þèú¦½ \Io+,W7H±±UVÉÛÚº .ß^øcâ¯Ü4ÚœÄ×7°Ì¤G¶RCÜòsœûóTO29¯¿ >,|FñD¶šµþŸ ۘ糕# Ê(ÈŒ'l‘óÐ}kšÆëÄŸ|mà­rìícL¹hõ[r1+Àó‘ˆUÁsœ×_a¨x×ᧈlüKtL Äò[Û\ÄG™ä,TПcŠõÝ&ëNñ7Ä<\ªºÎ¤m~Ä×ÐF­s&—*²´stY¹9S@ù‘òΡð÷U‹I×ü¬ù—ðưI¥êð¶ùa|®Œîˆã•=Nk{Æç_ømk7#¹»ÒîtÛh¥·[4?k‰B‡™ ȸfÚ2B’2yúÁòè¿®4…ÿ4Ÿ&ÒÚÒXìµKqµ¥·0[ˆó¸H1Âs_DÛ~Ö÷ß>XþΟtZÑô#¶Ó¯n-7ê6l‹ˆ²wmÚñâ7 eÉïSÌ.cò«Â?|g©ê_gÔu¯íÛdR’Cp‚ÞT“#©@¸\sœ`šêüXñ-¯‡4 ëÃ/>Èîe‚Å›ûÛ>îc»o þÆ×?|C/м=«êG¢JgÔ-#"Xä…~ó&sÎ?:úëÇ·š—ü"Óø‹Z[?øºêØX¶©§·›e¨ÄGÈe„cdÃ9 Áï_;|ñˆÿgoêsxëÅS­æ­bWû/Us³Í”ÍlHÛ"‘Ž2pO"¤¥'kCø3Ãß <}âKáî•aö«-WNK»LS©9-$2.6:HF}¸År‡Wø¡á ÍàOG‰´¸‘ ŽÚãkˆž?âRrºç$æ´>øÃÁ> ø½aã/Z^&žîmd»Éjb¹¼UEû 6Ö >‚ºÜhš/ÄÏê^Ôšm5®<ë)nÿÕLÜÂä†è§–Ð#Æl®¼;‡4wâfƒöƒÒClïÆîpcÈluºâ½3ÁÖV6z…Üú\v2Û‡UâQÂ@ýVEû®?Z—Æ)ø‘â+R–ÿKÓ’( _jŒD-ᘞw›÷ràcƒ^yiá­OA#Åßî º³ÃIa¨ óK9Ï—€âE9ôæ€5üIâhZŒžÖm¾Åw™ý„Eqå^2'­uþñW‚£ðUî§á«W¸–5íQù›dû>H2(nŒ§’2Å|]ø‘áM}´ûßøa,¬çS Ü!2¼r0Á€è2 s\öðÂËFÒuh÷{g}m²–TÙщçœôÉ åmáGÆzîáÿ^ÿfèºÄ…ÀAl)lpv>õö³¯Ûø‹áßiÖZߨ.gUŽV•áŒð7÷¸õÆ*·…üaâÏ é_ õ$ŽMJêÓͰ›v9IØTéÈç#¶kÃâñ½§u+]FݤêI)¶Õ­­Â;3k€Lyþ,dt Ižµmâ=/ÃÚMä‘ÉΛ4lgkpC¡ÆÒȽÊç$¶>Ãyâë#QÓ¦K­.ñ¬‘È ‘ÈÃ|ËÎ3¸åkð®·ð«ÅúF¡áMz{]'Q²¼Y —û<ªõevù±#p¬8¬ü,>µñ½½®¥ðôiöö·l²Î—R"€áÜ 'ž˜¯ñ‚n£›Vºñ`Ô-NZUÝ Ìx)pGã^‹á/éÃMY<ažæ=(j¶ÉÛäƒó¸…(H?(9 J@V»Ô¼[ x›TÓuEL†ŽDpNw©é×Þ²n¼GáÙô-CÀÞ3º6íð™Þ),ñ9ž{èa¶ÓÓ2#m’I£c·ãV's¡¹›Ä¾*Š鸚e‘Uc·R (à·l‘Ô“Åsúdž/ô]VM:þÑ~ÑY]Ý„§o}Ä£×â·â†ëOÔ]í§u.¸ÌbyÆî§=ûV•½žµ¡a-ôzL7¦¿º1™¤dSƒhØÈî|1sàˆ­<)¥Í©j¬|ûçH-áBvâB@äó…í\Uƒ_X¼V‘ZZ…vÜ¿¼o+?íÀþ´éµíì³øbÞáÄòRf\Lˆxs»ð«É¤KeWu•¾]Ìv3×ÐP+°ÕcŸKÔ#¸`‘Ö=žrÉQžqŽçš¯oñÚ=žœÚ,€±‘\—PÇ;ÔœŒçµwñOà¡söoß%’ EŠÂÚ#$ÒÈF3žƒ>§Ö³uoéPiÃP‚îÐÝ-¢± 1ùwŸº_–káíKYV½µ–$´·šf6{¶ÌäŒw©5[-/XšÑÖY@eÞ`fgN[¿§AÞ¯Úø{ZÖ¬$k[tI¶aç\\6Á;¯dy‡ä+Gö‰jãUÕ¿Ñíqû˜m÷4“ÛTp;öÅDÏÖ|;«¾’5mDY$a³ÎÁÕˆèMH‘› kUß¹`dHã8@ õ÷«:VµâÛÉN’“Gigón•°Þaêª:lWGc«_ø2íF8ÍÁ.ã8ùÊ®7{dñAG0úûË8··‹ì,„†2A=À8õ©mõI­ ±T–gEÄç„UûÌ}0zÓÖu(µÆš×æ{’àÍ4Qðy.ﻃßÒ²—VX´étxÙ-þ@ðHUÉçÓëA-–Zö¥ ·öŽžAšX69‰$rzm-úãµii•ó+j¼qÀóR¹”d|ÇŽ2{ à ø›Æš ö¹¦Á®‘gÌ—×’÷2 "Œg?ÝqU­¢·2Ëm«N¨èâ=‘£E @öÏ9'ÜÐ8žSªkº…¯O *ÀÁš£?Eô žµè1êÚî“ßjLúØp4BGÍÆÐ:p:¶A®ÂçÆìlítÍ:dx¢+$ÿg;|Ârrq‚=úÔz/ˆ<-ªÛ}ƒOÕ¥’ 4i<›h¼••‡bÏÏ>½(ï[G%µ¹X£µœÆ›FÁ œIÍ7Ã^ _Oçé¥ïîˆTŒÊ ¬jyAÈ9OAñ,:åËI¬Ç¬;b‚/ðzdžNÞ?•q—ÚöEÜv>Ôn2[Ïe·,#Œväu8éœPÓ^øMâj¯áŸ]Ë iø}Fîr7ˆ|Å#QË1ã_^h÷ÿ|4…´(ÙÆElRe0‚åGÜg§’kã=*Òç[´Yõ-y Š$RÑLãp®ºOnsšúŸámþ“ Wz«Ïmö[+Q‘ÈØÎœlF'ænàu<Ö‘z¤:Ö£w»ð©V£>VÑ#?išÒ.¤sÀnI#­rSx[Vð´S[}´_ § &–fšBäd’~ósŽp=+«Ä>µ!†ÿ(<åO–O8b¸ÜG±¬©5xjÑj6a&–ÿ ,y§M¨ƒ•à?ZÐç4§Ò´¤‚¹‘~Õn>Yåõ$¯ROA]N›}wpŸg¼Ò/ïtûhÕ D„yÎznÏðó“OÔum:Î(4-GNKBì`…C²:¹þðÏÝ+šñ‰>#Ûh·“i×S[Fao—j—àŽÙqê(Önì|5¡øE5fXôKëâÊI ’H­”ãsè9èy¯Ö~'èšv“§é:V¨ºÐóÔGÂÍ,̼|¨:{õ5‹á)¬4»{½A t dE›,Ò·÷°Ä–cêkÐ<sá­KÄW_Ûúãi—·²ÊöV‚I Fy@m;Y‡qÍeë—е=F=>ÞyŠ@¯0wGç~~Uüó]‘oâjóûNûO†[X“s0‹hǨÏNÍ\oˆ¾ ²Ðî<¢Gw4I'•iašÜò†aÜú}kž¹ðO‹lµwÔíµô:•ºÆ&žV!S8)Jxèzžô|øƒâv–ך¯‡$òï5 `†6F’4Þn¼ g88àVƒæø‹àûvðþ«¦Ew*I=ôøT ýð‹Ãî2ÞòÿÃW µ¨Ü0Y6G L1Á#–ç°®ÃãÄßÛ÷öÎËu‚Ô)ß¹tRrHÿkPš÷FÓ/¯-5Yšeº»¸ wÉá$ÿ ì8â¹Okþ)ñµòE}ºž®¸·€y“ÍÉò6ŽœÝMvz ¦·o5߈|DdX⤉§û‚^ÜdöQ©Ù,bÞ_ê7zdº‹hŒ³ž@i9fö€±Øjšf™ªYÙø‡W¹öö±u>cRV(¿V'½y§Œî5]ZÕdÐÞ{[ Íò‡®Õ<ý+¢ÒtM>ÂIç³…ç¶¼”¸’q™c€‘ÂîÈ]ýOsëPëšþe2=À2œí©'rƒÀã°ïëIì4Ž[J¶‰X]xŽS¨ÝùJ)ùtÏ¡=°9¨ü'wâ _Mq§\ÿbXÎæ¼Ñï:¼óÛÞº ­OO°µŽ{˜°0Áa"“È{ž•O[ñ^¹©êºL–ÀÊY¢÷P7Ÿ_jÀ³³ÑtÔÒnÅ®]–iZIo¦_&ÝA‡¦9'¦k?SÕ´ŸXË=Þ°“Xi6þbÚ©.I$…r£Œž6Œýkœ‡[¼³ñ½Ÿ‹Lš’9žH•ü¢ˆœàuÈÆp1Ö­ø‹ÄÞñ-™Õôû8ü5¦\€öšu°ÙWå¤p:±’zõiÛ,ø:-Gû?ûMx¢»Ôcó|–ÃÍcøÊ¯Ý'¶{tÐø—JÑü]f|;uªÿfZY/ŸtmT$`ìVPy‘ÏEÎïj󩯼àô¶ðïîWY×𺦤û–$‰¸‘QÛ˜ôt<ñ[Þø}§EjltV:6i3M{z˜•¼¥ä¨#¬²œ*N3Z$+…ð÷Äúªðö4våQ®/HÞ‡v$ò9Ú*ý¾•§xSûGãHnÔiV3Ïû˜áxql§/#ÈÇô¯0ÿ…©™âtÓ4„Ôõ]rA,±ÀãrÛ û¸D3z‘´c“^®÷¯‹îtø¼B©¤ê2ý&¸užh£A“·hÚ Ÿ½Á>ô›ÍI-õ—Û¿xrÒóÃÚm¢Ê÷ÚΧ´D‰»îÃL’Hç€ zæ»fÖ<=o¦iz®˜ßFHaiåº!&½vݳ¨ ~âýã[šæ·ðãEð“j†±ˆÚÒd¸k 8˜‰™AP²nʨäžqÖ¼þóNð¿ô»_íV^Bø[£og¦Üc×À4®Àõ©\k7ÿT¸ý÷´=7Œû p*]øsV±´¸Ñ`–Hm¥óžN¾kï/eàSô i÷z{k&‡ÈÓˆ>U¦})>d˜çg÷Gzå4OØÁã[½BÞÔ$+µ³·EÛ3ÁÀõÆN{R¤·º‡VñŒÚÚ•-ü•*“ñeÿžqž½²j†½f¾ Ò¥×.ï“S¾Õ‰˜À±çon|µâøsOÓæñ¯ÄK{xí-h’VgS'@¡Tn98ÂŽ[é\Gßéwð½ÍëÜ=äÁ[2C± ‰ŽDj2V5yçÖ­mJþ¯§øÛG“Ú®Ÿqnd–%¶²¶,L¨L zŽˆ8k¾º×<1¡^/†o4ám%´ˆ¶öF¦$¸p$<  óÔq޵Îj¾6ñ•ªÚGáå†ÚIçeó jõb{Ð•æºÆ·u¨xºï@˜µôVɽîþìO<Ä+v0_± ¥"¬wú“i¾·“RÐîßͶž8þÅ·{]JX¨ ¼œ1è1ï]õ„µHmnï5Û£¢^jj¦Kt;Ú§ ‡üÞ£;úW“øõõ=;Ån¯á‚ÂâÊɵ‚"ñCŽ <‡æ,Aüp1^•ÿ FO ê:–þÕÖu8‡’òª·ÙªÊ9Ëò Èô©„Ô¼+âÍ:c§ l\ <ªÎ$L|Û¥“ t_º¡­ ;TÒ|v“ø'JÕ †_,Þ3Û°crPª˜ÕHáñ7P+KÇÿdð4_^,R° ¥ÉÜe-€rIažAë]Æ¡oöKuá‹k -ZÕ%¸Üȱ™UFB9á•1òóÀïZRv’b’ºhúçþ é‹OØ/áи¿s§¶»åÉotºôsFÎP´VÿgŸå= ùzu=±Í}¥yóPù1A8â.|Kà_ˆ¼2uO ø:æÛU{˜ÍµÅሼ夨88l|¹À8Î+Ðô;t“R‚®æ×.­ Oy†6Ñ1áJÆ6®áÙˆ¯5ð¿âÏøÃW³øgasá/ [‘k7è†i[hg’F‡åV“ £$šíίáXtwÐü szþkÍBSå‹€N8Æ>ñü+夠=F³²{õÖ£Ô¿ðÚY M\jbæ;ÄxžÞ!ocå,I#q8§<מxr ­w]’ÌÀÿg¶Viævv©š :ùaÞ™í-¼àŸSÔ ~«}®ýšÍ%‹Q¼Ù¸/„ÜGÌ}y57dž}®C«j6±ÝjlóA‘$2·\ç½~l|Jñšxb{‹‹‹5·….¤·Œð õ q_¬>=Ñ~#ÞßÝxÎé–ËN·U©eù½1ÿÖë_Ÿÿ¼¢|Dð¬ž!Ôò¶¶×ë‹È8 Ôæµ¦ÝÀâþø–ÛÅš=Ο{4nûUþSü cÿ×\'…"Ô|3ã¨t Åfž2€ Î_–zàtÍyoŒçoƒ`‡I… Ò®"‚Xßø68ÏL†éßê;Öï4¯iž"·ŒµÕªð±JÈAäƒÔ8­uêwOâ¿ Þ>‡&õ2#J&P&‘˜®ÝÈÇN@¥zž–ªn~ê·Íüw¹¸B©oH»9$Œšà&øœž*ñ…¦x²9“E…!ŠîáFÆx‡2aºü£ƒƒ^ñâ­oá§ñ˜Cû?Ù´[m.#0b|·˜’>RNâ@ÆæïYɰ:oƒÞ ¸µÕ<;yej5IH1ÝÝÅ¢RG–ƒø<“ÔÔwšWŠü-k§jú½Å¦¡pɆË)'%‰#ç q\¦¥á;eðÝôV’Ç`èßh¸ Éۜ߾+@³º7V¶Ú;ÈñÀd‘dgÊŸhçžÕ`R×<)6•ª7ˆš9drìî§1¼g¸Ír:·…ã×$MFës"•ÙHþ0úuíú.¿ªÄ·ÍrâÖTi#7ÌpzõúñŠòFàji:S—!K„…pL¥Oø=1Æ(R„Ò4‹–ñ–24®Ñî¸Øyܱr9íÛž•ìš§€Íž£`‘+\¼± ëo–¦i×r Ç÷: }k“ñW€¹¤·€µ+_Ë:ê7V’ÙD©Rá„„ú±?†+3Xð4šd-ªø—ZŠXížh‘¡ùæ™Ð`ª}µv¿³ß€ÏÆoÛh>$µ½¸Ò´«V»Ôd„ˆÒ983Iü8ÜÐoá/…Zεgwã-Q-ìþÍ$Ðy­ò>#È%³òŽù"°à´Ðô?ý§âEý¬R¨x¢?;JJ»=2H9ô¯r²ðÄ—Ì|)iª[]Û,„«Íó‹{dícÜmׄÝèZ h.<`ñG¦NÍ&é2Ï"çêO”tXÂVßuÝ/Wðþ¾²‹£¨Œ]Úూ#¸ƒ†F=GJ؇TŸVÒ.µ;»“CûE¼W ¡Àl(Ær99ÈÇZv‰¥xcÄŸÚ¶¿ ­õwP7m(±"ñ¼)Àrsýî•ê2h0hÚޥ௠iRÉ ZEk¶Vôê7¹ìzúæ¡Åê^ðï‰õ]b÷CÒ¥Óí,¦ ¯šX4êB‹ÑPö÷æ·üàïG}£¸ž7,.ån9qØ^c®ŸXÁy£G,‘Zg{¤ù…÷d¢ÏnI®§Ãþ<ømâw¶ðç-o­µ-.;ƒ·M¾cºî9PIäw§G±]»¥¶….qÀÏoJçuø{à éñyåÝš-¾àe’8ää¾âÎÇŸzö-+x [ƒUÑekB/5 #r‡*cS€%¸ÆIÍi_Ïoi&•§™î. ½¥º`‘® 9f;†x)YóåÝþ‹öï_ëMæ(O Ó½ex«BÕ|Uâ«ÿø ê+"]Þß© c”à—R0 q‘Ö‹ù)ðƒã§íQñ*$Ò<)àM_PÕ's2ßZI¦¤xvÝr±†èHÇÖ¿_¿fÚ¿Áÿ #Ð~6µ¬zÖ±¨·öu¥œ‚ðXØà.Y‚ª´Œù| !WŒ÷ªto…/4û«íkXñe×™˜Ú9üÒ Ž -µ œàyç+éï…^&ñ‰µCÃwš_ÙçÒ˜Ù‹¶,s´[ÃqX)ÔŠÐ ‹ÏêzSjµ[>ÞA“ȸU•×ø ÎAùÇÖ¥Ò<3qu¢[‹y<è¯v-è<±9ôÆ9=…r¾;ðïà·¹ñ:µ¥Ý¾‹m'™b¨Êγ¬ryW?ÃϸH.uëƒúsµù°ÔÞBó¨qæ4a²±©¶ã?‘¬¤\M]_Á–Æ÷ó-~Ç,òqîŽ?Žù—\Õ-í¼g²ÈB#i]&ÜѨCŒrÝúàVW-äÕ¼kᆺn¼–—Úœ$30ÌQÆ™v,8ÛœŒ×Y¡µ¯Š§Õ SC»C…-ÖS…’òq‘•ü£Iµ°Þ§«þÌ¿ür«©_Ú\ òÙ,çŽÒr±+?½Ürwœä“žE_·÷ì‰ñàÇ«ø—À 'ƒüo©™ükbûîµO]´¿cŠ÷jç1w!pHÇZþâãÓckí/ÄZ…á’É!|— ”‚ÌAÓÚ¾vøÎ–~!𧋴ײ·Ôl5[Y-à†ê!$7W)ù]3ayõè`±RŒ¹[Ðæ­F-^Çðï¢üUÐÜ F½Œ«à‘¼*)=S9¤öØöo‡ž ñ+kgí9ðÛDðþ¥âê¶Ú%¿„ÚëÍÔÞöÝÌMsx6Ǻ¨ˆÈ<Æå°¼1¨éÿµöˆÕ¼%â 6khµÖŸq¨ÚOöÖ׳)‘‰Ä+b0ÊNXmv¼Ç?¾(þÌšìÿ>'XXh:‚ØØ¶‚ëíú…åõäØKÛ‡j<Œ¤ÆˆÍ´Œç€+WZüaðÍ>ø3Ä"Óâ‚x™`9[.ášZEÏï-¥G,±Ï& ¿5M/ħk^¾’ßLÒlc[‘µ¿²º)fÊ,Y¥f8#Tdb¾†Ö¿h}[PÖ?áiËci‚üh­£Ék(†ÖÈkˆâ@Vg¹cµØr7©/>}iñ£ö'øwðÇÅ> ñoÂû‰þ/ñN§§¯‡¯n%{{¼Ž(ìW,¤9A9ûF(Ò¶ñÈÍyŒ<;ãï€^ ÒþüaðÜ‘ÞF·Š_5À’KH‘Î鳨8 `Ž ‹Ãžø©ñ â^Ÿð59µM_â-宋¢‹‡Y#”BÞ`+ÀɆÝ$rÈPkè-_Á5O‚]x»Ã§Á^)Ñ|Mcýƒ¯-´¢ÎûA·¼òîå¼ Ufk¡,;²¸X +^ð£ø·OøiðÞøe&¡®Ø˜üTnLú¤––í/Ú`f-Ã÷k*¹|sƒÈë?üâ?üLøOªk·#±Õì-ôϾ¤¾L¶IavÓ¬¢-ªCJÃ~Â:ï‹æ@\Õ¾&x“]¹ðÖi¤h7Ú¤rÚCxaÁbŠðP$ýáXÀÃ> =kÂ>,|,ý¢¾üZÔ´ÿZ´ãÇš4ö—)otËw$Ún¦ÿº âTìeÙÔ@é[Ö‡‹|§øsIÛ6Ÿogzö·?Åm9t’ën9bT0^êÇ#µw§â§„1ø•{ñ?Ä÷6öÖÚ/Ü3,vð«±Ü§˜Ã|Ý÷z×è¿ÇˆºGÇ¿é^ð–tcágˆ¥²ðü2Hðß[YÝÀ×wÓÞOª¬R{“šù×Ã?±¶‡¥^èÿ~¯a¼’rɉÃnn x×ü='ƒÆƒmðÛÌÔïõ«p4¤Ž J5³ùûqœ4Š¡7Ðñ]¯Œ>$Ùü_ñž«ñ?×ká_MinúÝ”Nc™oä9Ð/ÞFšE Øç9ë@½œ¾5Õ¯¼5ûLëº!Ô´KF¹¾‚@‹$Bê6”*Þ‡a{àWñçã+ßCã{ÈþÏ£Eæ_FcdFs'Þó#^„®NÞ08­_ x×C¹øK©é67÷WW‘ݱ[ÆJÙéhùyv+¥€xÓÿg$Ò;Ó~øçûu4·ñ“¢êwÎþ¸x¸¸‰žÝÈS¶8äç@÷ÆøÒÓC4¸íímBÏýŸlL‰möó·œùyßî‚AVaáïý›\ÔeÔ-ä„ý–Î 4!myŸ2/ °oUÛÛ­sÞ"Ônuy´Ÿè³Ïu¢ê:µ÷•m!6½¹@²Iƒœ|¸ž;WUâOyqø“º>¢o4§¶ nJÒ[ÅáKIUù[ØWâ/ xsOøàßé7ñ®¥{,Ö²éq¾ß.æ,nãÁÜœ>™>ôšÐŸÂ·~_XizÄK6£>—4Ìå‚´ríT ó¸3ù׸j/Œô¿ i)Ðî“LþÏÕ>Å!ZÜ=çʲnÉ.ÝŒÆ2kÁü%ทዠÓ../ÝM=úƒ"¤¬ÄFd^Ÿ!‰{Õˆ#ø^Ð|p· ëkeâ±ÀD‚DÝåK!@!ÏERÄÙ¬šÙúLÞ8ñ®¡ñÓá,4ëI/|=zªemYã óA ù‡M± ðo†zUæ¡ Çg7Ž ‹VÔ-þËmäûvBcœºœ¹B†9¾Ëñ=¿ˆ¼1®[üð¦ªéw?i²V/&úKŸ¼ò–êÉæîàã"¾Sð÷ØÛÄå¼ã ý?Å–7i&Ÿmo’1À3‚¹l¯ðíj 8 ¯øt}FÖçJ_²\êŠ4Ù[å"a w600 5Óø~êï@‡Køwà-¶™¨j²kpG©3ʺd(ñ‚8eÎÖ?xó^ñÃFºñDþ·Ó¯´‹k“ÚÎ/Ô¡h¾Î›x'—.íóŽ 6Exp:‹|G¥\ønÒödµÒ¤k¨U»°—åtlðB¨þ*wŽ ŠZ‡âK}Ã:dqKy©Íi¨C)ù#€€è l q_Dx³Ç tOÙwÀÞ—Â6,5[ýO[¾bˆo ™¦{{u%Ï•FÀ•\lã95ò¯‹¼ âï[Z|I×õø^î}R +P j"Ûk; r£^<òàzô3âÇÅ}sÇOÃ=¬ú;\[³ÄK×µ†DÁöbAý(¸ž› ü uªÿiø£ÃÖòÙZMxâÙX–òʪB`ã+¹ù#ñéRøÿBÕ|77‡4o‰M9ðµ´°é¾YRZ=B}× Oñ¬R vZåt¿|IÓü%…–É Äצ·Y-$€¨¼ûsCø“ÅÙÖ>;YZHîn¬äÞ7=º̃>Î >Æ®Én.5Ï i6úÿ…î ×`½ym5Kq"™¥Ä…$-žŒ26‘Ï•ê¾9Ÿã”¾3Ò<7ñMŸM·Ò­£¹Ñ¾Ð»b™‚»»‚w¯”vžkçMGáçÃ+â5Æ™áæ]ZGuÎìb:…À®ì`s¸õÚxƒÆ?´ŸÜXÝê÷Zœ>I!…%_8[y€(Ž6ä”'#®b€¹ïŸþ+x»Àß|U¯øGKÓ-5{¯íM;K·Ôø²µµ½U1‚L{w&q÷±Å|ÑãO…~$ø%¦Xü1Õî´íFÛX†Û[Òí s#Ø7Ýlœr§{{WWsã)µÝ;IÖµ›v]Nƒ]A³aa '8c‘ž:Öˆ|aýŸ®XøÊÛNiÖÍ4ýJA`vÞÉÔ£o›Hô .urøGCÿ…Ouâ5 ¡×.®¥³ÒmU‡“?öYËë÷]‚Ž˜ìZìñð‡ÄŠ~*ÙÜ>›á˨­å‘ÏÍ:ªË**Œ†ãžNHâV=ð?‡>%§Øt™îô=>?³[DU¿yx¤åŽ7v8<×)ãjš²]øÀsµÌomý¹q)…ZÞ1¶_,žã|&"€»=[Å> Ò|3à[ÿ fÖ‚À°á-c<ª¾‹Áô§7‚üQiâü3ñ‰²‡XšÒDr«}Üe˜ 0uÏ£ s]o>Þx[Áž6ñMƦÞxkH²Ó¥Ód@·í.¨FócÇ’Í»'¼Wᦃ=¦§ø—Y7f«§MÙ’FiÚKYY\±$PÛb€»<óÄ_5/ ksø£GfµƒPŽÖÌX°âÝYRGÇRŒ[¾<äV}¼>9Õ¤ð÷¼HnÎÞYc´µ ’cØȘÉc¬1ŒŽ+ì{oŠ7? üau¤Aàý7QŸQÓßM¸µ»EbòM2•88%yäšËðžƒ}w¦Çà‰–=&xã·.$òR °£Ž§IÔ¹Å+Ù |Oøyª|>Ô¾ø¦ñ¦²½+Œ«!Óžñ‹ åí¶)>Ryõé]¿üiðŸÃ´/X(gñ}µß…ücÏo¦O%¨XµË}è”'TféÔ×Í~›À þ(i3ß<Þµy§êºv¢©)0Üm“tes•Üû¼ô⺭zûFð?Ã÷ñ÷þÏ«\k·“hšž”PÝO2Ü,øþE-O-ÆqŠ,‚ìõï_|-?ìføwy¢x¤‡Òï­Ù_ÝZ¿l ²¤¸6‘À^Oi¥øûâN‘¤i“9›[²·¼]BÞ|¶Œ G*òvÙ Žk²oˆ>Ñüc¨|2Ö.­ÚE¾4=^ĶSʤ<´ õÎô¯[ñOÅ-kÂ~0ðæ³á©"Ò|\¶×LÚ”*†Bæá‚Ì̼¨GPŽê9äÔ²‘ặÆO‹¿þGá?hy²‹Q³†îöÈ´`Ú\Ê¡äW€â"È:|ØÏ5éþ!xáo‹õü ‚?|8×4…´Õn” žÞY¥#ÊyƒdF+½XnÚ}@'œÔ~6éWšæ½¥`x‡Â6—0>£tɸ.”9E”„8犋á÷„> ?†÷Mg.¦òß·Ÿ!Yf-3.æó2®xÉÎc½+ ç¾'xzïá–Ÿá üi𾑥G☠øf"úÊîÈ‚^WÇ&ta¸ù@9'ŠóM[Æÿ´€5]x7NŠÍôH¤–îH‚¼ÓZˆ¿$Vçô®ËÄ6t¯|>Ô~:èÿð‘Þé×÷Kf— æý¢Ü¨ˆ‰wÎK{qšöŸêöÖßðŽ|Eð‡šYâ¾¼ÐoKI¾Æô\ºËk ~Le4N àôªH;Ð>4ZßÜ|>—Ä‘Åsa¨i3Yj7² pàá8@=C{V|/ÃH-øsãhàðŒ4¹cÔl,uXâ[Ž7Æ…Ì0:d7ZùËóGÅ{ÃZÅχ|gq¨i—3½Æ£g-¼/$Æî·rÁ à›¨<Õå7^Ô|øXxƒÃ×òÝháãY–õ&Œ,±n¨Ä™uS\ž¯ceâ¿ÿjøWM îËR)£ÜDäÈn®2Û°0 #<`ŽÔÿˆñ„‘xsųé‰sã}K»›#À’Œ ouMêËÔq@ºŠ5é<{{ã_|–«5Ì–°.Ò›sJ‘¸Ê ã[:¦£ðÇâ/ÅüGð f¿¶¾“]±¹’Þ=VQÄY%@–2w޹QŽõ¥ð·á‡Æ? hžøéð´C®xwâ}¼í ï,Ö²32\Y\#q Ñ1û‚xàÕ߆ß_ÙecŸ-›²ÊOË´õ¢à|ã 2ë¾Ô­u€aº‚ñ`ºÓØ”3ÆdË0óà‚½0:q]Ãx]dºÕõ8ì临™XÙ¨[¤–8ñýKOSø×Ô6ý˜¼M¢ê^»ñ‡Ù¢]zêâÚÂ]ù‚[;"kiNwy‘€Jž7*úWð¯ZðÏ‚õÿxGÏ"k yÁ·oš{‰Á؈]ÊHû¿Âz'âç‡ôO ë^ ¾¿Š/hóÃgypv+,Ö­Y’H>[žËÜóZ 4‚> øýàO‰¿ Ñ-Vyn¬5­ ù™ìþÐÙh.mXÿ«=H\m9'­tžð7Ã|7Óþkk¨éZž‘y%ö›p¹•-äaûÕ•[æòÛ?2go €1\žÒ<;¥xƒEñE…Æ«zm®mô=NÍ µ¾©hË"´±ÿtFH'’Ñ`¹ì·Å jž8û}Ž«iðêÌXÜéw&æÝ×PÔ!¹i4ƒB‹Æåpà ×çŸíûx§â7Œ&Ô>éV\:†5[†Ž"ÐZjas5ƒòÇ>ºñ†Éæ5ö‡Šu»¯ø:ûO±ðõ¶±àË]SN½Õ;.-­Ú š ܈À`ìq^ƒðïHÓ&øYguáÏ_i°ÃÖòXN—ˆá%‘;IêpÀÒ²)IŸ8ø/ö%øÕ¦ü"‹T‹Ä¦][ÅÒKk+D©öü¸¼ÈÖeÎõ*àìä8Î@ê+/ºÆ_x7ÃÚ†¢Åáëý"Å"Öì „ŸQ…ÉûBÇ ùDèGš3†Îx9Ïè_Ç s[ð‡<5%åàÕí>Ù‡/焬{u+°›páHUß&bܹ;™s×MñÿÁûüc´ø¥e¬K¡ëš—4ÃN¦ §]±6äÏÎÛGLàíãšÉ1òŸhŸ´ì±ûVøBfÐ4› |F±Ø¬'욆›­Â6¼q2£¢˜¼1Á9éðëÆ~øwà[ƒ†µ:so¬^gýCKòM$ŽX|é)RÀœ dq_!xûö/ø_ñoö„ñW‰ÓijxBîñlL2Íh×lòmj¾Ln‡ånÜ>U# +šñ—ìŸûEþÍ|AðëÃ~(¶Õ|ˈd¼:°û\6í"mÿIŽø% 1"6@+‘T.SôBçN¾øoñãRð±×­´hLÂKÛÇßi{‘¯Îã‰"qÆJ’:f›á¹þ%Å∴ GA6*là™UÖ\1û¬>o,˜uÛø×æ¯Š,¿hßøYµˆ¾]JæÒl“é û­løh£B~t‰1°½kéÏÙóâíÞ½¡øGãÀÿQ ó!ÓìîJ«$Híui2œ*ÀçîöÀ¤ÐrŸcÛhO«kñIñWÔt 3XÙÉ©AûöÑnâaåïVé 1Üç)ŽÙ#§Ô|Að†oi'Ú×N‘×P›G•ZÚ[¸³ÜÆÈʬ—@ #É'žx¬oˆ:Ÿƒ1x.ßIÓ~h'ÄZVh±ÚëV¾Z­Ü2).%Bß$¶í¹ 0Ç5X²ßôè,/4Ÿ†:ºKÙÉáÔÈ·xíY|å,¤Ø?/`rxª>,xõtÛ&”-¬$¹’4´!–ÃQ‚6Ãùk*˜Ý¤ª¿t÷®døfÆ}+Ã’|RÓ—LÔÞøóSýšt8ü6až[-JÎÙ¼§Šà§Ÿ†PÅQ\dQ†¨é_ x‹Áß&Š÷¿,/­.uÈ—PO»”·¾Q†Ž"pRü¿JúOâß´Ý …þ=µŸRñŒØRê<‹ÕôÈÜ›d½\‚·0gjJ¤n`GBE| âo Ýxëá-í׊|+­éz—…®>Ã>¥uCÒ+÷ .àÆpѱÈÊÒ©;“Êvžñ?…>ººmæ¨^(ò'¾‚0|‡cûÌ.{ã5©âmÅŸ5[_ þÚšPº»ÔUåÓ/ôÅk…ÞNq´+yö¦܇áÏŽ,5ïøoYm&ãF%°šÞÖkåVuß±K †ŠéTƒ“÷±ëWc?ü!ø–Ò|=ý¢4SO,ú…„7QFÖò[Ç1i"2DÁdhÒ@Fäm ‡AàïZüM×,>ˆíÐi ­¦Ý›–(· æ#…–Ùœ2©åƒc ¯ ¿â_Šž »ø/uÄšÿÂýV´_Å‘qq§\<‘îiz¸L‡9<•¦”Š;Ï xà/„"ðGÄ-3C–mR6TÔô­UY¢òô«We #€Ù cû ödºñ¯Ä¯ÙøZx4Èü`­¤]å5ŠÈ^)Ö3ë‚62å€Wšø¿ãGÇi|/£x7ÀÚt> Ô<+ª}†kŸ•ì’áa Ž@”üªIôâ¸èü=©ü>ñ]瀬ÂXx•e-is3Û]éWˆÞk¤2®b”dpqÁ©¸½ñ/áDž߇ÃÏkPkžðË‹kíGL‰—er¤ ¢‰Žä’"TºogB¤©b®1¾!ø/âÿ‹­<)ãO²\ɧF4ý_Z€ÆZööÌbÞõÐrD±„W,9?Jóßj±j~=Ó~üñ>­ö‹­>ymãÔ^IY•ƒ[:±Ë°ûÈç%”ó×ðF±¬øŽÇT½Ò4§Õ,m…®§*³\O* ó>VS÷àõ=éóÒ|Qøa¨ü?S"[ wýôj£©9\ç(ÊzÁÍZð¾"øáΫêƒ[е˜ž'š(üÈRÆäê6¡ÈqÃ)çÚ½“Cñg‰-<=/ˆ¾è6—·ò…ˆÁ!Y£*«‡ŠDãï/\óÞºŸxcâ¼.ºWÂØ­ì.4›V»¿Ñ.dßnÐHw¹#ŸSB1Jì™/ˆáð®fë==5-ZÐÛjK©ÝI‹Yê3µºàíÏËåH:æ½7áß¾%j^ñxÐüRÔl5e·Ò¼)ikqkkjé¦Íæ³Aækk°¼+È={Šñ;ÿiÿðŽx3|\<Ö÷2=Ä2,yS´·Ì”¸àZ·à[[[Ï ßßé4™´·@±–S‚Ä2õ%“ŽüP˜i_ôÛ­gB“ÃsÍàï [N®Áev­!Ù,’ ‡w^:ž•í^0×uÉãÿJLq9Ï Å>è^&ð.™ÑiñÉÍòÞ Ôꇱe9Á­/|bøéðCá·†¼]ðãL1Û‡›Ì¼ÊËosÂ6ã*ÊÜÁûºïé¬4›ôðŽ®tOíÓZÚĬ0œ,¥+*ƒó2`Áí^mã}_ã7Á¿?Âk6Z×ÛÙ©e"G¶êÞf#!ö‚$‰ó»pÛóprsSbŽ÷Åÿ´~:Ó´üEð®ã;)ot}SN”*‹Ûq¸Ã! 2HÜ–\œòä“ã|@ðw„¾ë‘ÝZ}¦+¹®Ά@ÀÜ[L®Xy‰€AÉÜ+#[´¼·´[­:mCBÔœ_Z T´s·|cp:|ÙZ³áÿ„6–þ,—Mð.½²ïd·šÓ0w–!ºxƒc1¸Ûæ+sUXOcÝ>0|<øÍð_áî·ñ“Çš´Ú†u«õ+«+…žh!¾"8æt@ ÆÌB³t^sÍs|UÔ®mnÇ€µhî/<¡1±º@ââÉÿÖŒòYcÉÕ¥¨~Ð^Ö|¯ø;âÖ™¨xwÅë&‘«Yº¼¶Z•“Ç“‘†XH„åHÃ!äò/‚u?xvëÂÚŒßD¾ÒäeÓ5a)Iâb„ìÝÈ1¸Fxç+õ úßPøðÇIÑôß…Þ2Š;­#Z¹Y´ûèyŽ8d3ô$GU<â«xCS_A«|<ÐôgMGÃ’™­µ;FMÍ”À+1_ï*‚Œdó^OãÏxãÁ?ðœø~8/Zíg”XGû³«aŠŽ‹»ºñÍrÒø»ÅzgÃ=]ð|†ÎçÄ3I¡jzuÔdácRñºIÜ:asÐ3{Sê?øŠçâËøÅÚýÀÕ¬4• sumÇ4öÒ¶ÞqĆ2ᱜs\¥Æ­,Òêz¦µ<:V¯¦J§ODRâÏvWk†#°ò ÅpÖqj¿´Ómý©y¤ÚøÎÚIµ8&·ûm¤wkÀ–ÄMƒÊŒn¦xåüAà LiZ.±¦x¶ÎêÖÎ÷O¿}¤3Kƒ”XUâ8#ƒÞ•¥¤~Ð<;e­[iÊ­ ø‚Íìu[P¶ó ÎÙH䬀Ÿ¼¸Èâ½ ÀšwÁí7ú›§­Æ›ªIµÂ9¢sºkvqÁ^¬¼pN+ŸïÚ\zljô‹}B×VÙs Öœ7Ĺáá‘€Â78Ëzñ_x|¤é±ü<¹1éW3•¶†ùÄÍk9b]EÀÆFGZ,5¹é_,g}k^áÅ÷Š¡‹S³™âÓc¼R-Õ\nÂ\|Ù8'8¯£<5£éº¿¥xßO·´ðÛÛÚ$wv±F½Ý¤«¶häÚ‘sÉÇCÍ|]ã_ Ëñ1—PÕ ¶ûj,z„WX A‡£tõ¯Ò_á¾™áÍ-JK§k+&i4á»ìé&@òãÇ\pyÇ9¥!ÈÏø‹âÇ®¶™¤B/,t©üØnb;öù¶äœcnOQXZ¿Œüñ¾Æã&‡yu¤é¡®a['Xî-÷ôòá—ߎÕËøúïá÷‰ü]c¦ü4Óß@ñ,Jæq0û>«jcóî´ñÿ«.f\îÝÁ¾ñ‡4»½Yt»qö­<ù…„ïæ,jÃ,ÊÝvLñŠ› lzÿ„þxo@Ö¥ðg†ü]>»föÉ}¥=Âbx][-Âd‘‚§⻽+Àw^'Ô®îR®PK"¸ÙHƒ(çk0Ð×Ç–^&ðV£ñOÃþ,³[&ßMš+Sqv› eÛå"`v²Üõì¾4ñÄüCxgVk=*idŠI?ÖZ³“‘Ïð–œsÅ;oøþÓ\Ñ.áðdÏiyfšÖñL‹!FÁ<1^äs\n‡}­j|ª¢Èû&Í´nñ§;“¨=Áâ½£@мâϳ½ÅýŒÅ$08ÌR¿RQA8oS×¥xñ¹á:ãÅ~ YïÔíÔì™ Í8Þ2qØŠôÝWMñjÉk¥%¬bÚXÃÁºm”«, þ#ŒúŽõ§.ü{ƒQM>ÎèIû™PKLhR?¼ýãõkòóí 0ø;QIoîÛTÕ—8bc‹?tîõÏÖê~8ð/†õ•²µ²¸¼Õb ù„awŽAÜz‘ìªvúf£¥ÛÉ}iuoÁޒݧ”§ßê+ _ÚèZËø†YàšgO.(Ê™”®Tu ™ndø£s¨GªAüœ;²Xä–~À{ îƒÍ£\ù:|QK~ØÜâ5èDc¨>ç5çþ´ñkºÞ®÷z”ò0hGAÏTQÔãð·sÔŒ$•Xuî88Urh¿‰|-}­@¦F±Ì­¸©a€éîjµç…¯t]&ÚçS2’Xù›ˆ6ó½sèkn<7qÛ[ÅåJ¨^RàŸ,ãø¿Â¸^ÚÛ^šêÿR»›í7ÄÒPy>Š„PCa{àK‹Éä$³‰ÉS*î’vÎ7tÈéV5ÿZhZh—Àш*­vY™ˆä ÿ¥y…†«>™µÆæ(e³L˜28`3ÉǯA]î¡<×r§}Ô‰´oaì1“ß5aÿdñíÜ6ºŸ‰ž)ÎVdÚ©ÿy€È¹8®þÓOÕ%›ûF=è"@¦ãpS´ äô¦¹Ð|_¤ø~Ö oNl×¾Txýô¼g,¼ã#‘ž}k‹‰4eÓSL´Òç·q+‹««™&ð0¨~Qï·ŒP š¢f±¾7:¥Ô×­dŽÖÑñƒÐHz¨ç,O'µUÓqª™õ"D[²&‘·&8ãqü€ëX62M§j«¢Eb–qPÒ\ÌÛL¹êpH®‹\ñ‡b»þÁh–ÖÞ?ÞƒÊNÞ t@}ù ®deêÆ™?ú.‚·¢äy’0  ôÚ8ú×]§_Zj:}§‡Æ˜²Ü[æl¯Î²žŒÒ1펊x5†Þ$‡I×ÁÚk,P—3jãí,œ¤*àyîkÇt ýêÓø—ÆŸu/k¢]Íe—icj!# €ÄŒdvÇnh}âk½k^ÐdÒ!ÔÆš Q í XG€2znÇËë\&£¨ê—:2Ýk'ˉ¦Bÿ­ÙÁ`3’OCWìçðµ÷“«j÷íi q$['$¥ú,`zž¤Õ@nµ+Ÿ3W´&îßjDä1*œüÊ~ëùš¨‰­¾¡6£i“ªi†ñ›…sÊÇå8×µwZƇ†äŠÙ-L\åX#}ÐG8ƒÛúRÙ^Á¥Üÿo‹é®5,-SGÏBqØu›­^êþ ݨjW¦K—}±ÇîŸwm¹À ô¦ÊȰ˜ÚUû,?sw,ûORqÎO8š±©ÇodÑm.㱆5åUV“?ÜŒÅ@Ç\¿.¹á]+L´²Ó̲^C $•ZB­ŽÊpõ&¶|-†õyáºñ4——·d“Ëb%NqŸB)r°; ;Æu׈þߦhz•úmÝëŸ2I‡D ¼’±ãj‚z׳ü1º>#Ðd\ài±î÷AÝ‹XäeTãïžNy-¿†|e!Ô,ôEºÔ^.ÖÞi–§8Bc\œg“óu&¾êøGáÛm/M‚oÇmwåC椠hâ•ÉmÁ +Ç g'½Rlhš_ƒµ";K ï·HüÆC°;„äsú×k¤iLd7–Síº¶B †^¢1БÜþUG:f©¨^å {„Ù3¨ùÄ}Âà ¹õ«xkN¿ð¶šn|Ÿk EžFù‚{#§çZÍ’hºtðëwÞ%Žx ±Óó%ÅÔ²a‘óXôíëÏÎxªïZñuõ®¥e}!;]„·L÷8ã­r‘hÏñêãûzI¼–q½U<­ØíŒp=ÆMz†ŸàÛÍ{Xkmپæ"ª)“b„^9f98í‘ÏZÌŽOû_Ä—-ÎŒ#»tB²<ñ•Aȱ Ûõ«>5ñæ§á­þï ®Ÿ(¼‰$›P·Îcf± OÉ9ÇZõ[¿ Ùµ‰³†É%Ry “9àíþ#ƒß#½|·á¿ë~%»Ô£¼º’ÇLk‡H¢TQ²1Á,[Fxã­¶Ÿàí/Uð-¶±y©ºˆþbç*ÓÍÐ"ó~™ï]´{‹%†óÇúšé¶«,PAºy“ƒ+}â3ÇãÉ«›ñëø+ÀÙèšn°uÛ˜#"Û©ò-óŽPI`z·z»á)mu)!ñ°\<¶Ø|Ü)Qç·íéÀázûÐÑ^3ð†áÉä»·†æêÁœ¯.p;ú$yè: ^KâÿxŸ_³:„mWOeA"Eò¥Û¡8çoAWÖã_ wâ-fsyqAl£‘Ácï1r3€:w®?Å>!Öõè´ŸìK†{-Ì‘¡ÉÆÄõÏR{})=†·ÁVZ W@Iöë‹Ë`Yî'•äÞzm Ä®Ò*?ÍãV±¶Õô‡6±\̨e’Ýd˜Ð.AÂŒuÇ4ºbxF„]Ý4Vv\±FËJÇ >¹ÏÓ¥uÞ ñÝ–’°4ï¶3öi0çÌ»‚çNyà ‚Ëv¿Úú½–â[©¤(ÿM Çr£å @â¹Û-'S¶××O{¤¾y²Ïej¹¹`Þ8ËAêØéÒ®½µåÅŠ]Þx¶+½_K·{­v;y<Èm|¾|¶‘0¬ùùB Æx®+Á¾2ñ×~jbj·D´ò\Éh¶±¾«v$9fyp^4 @<RˆœŽÒˆ:¥¿Š‚<1¦Go Ú ÷ õã“Üõ¿â?øKá»Çñ â_ŠtÛKæ]Ñé6¦K›‰’‡ú޾kÁŽ>+x'Lµñ/†< s­&§*n%H7)á¦mÙ*˜.~cÉÆ0OI¬iºÿÄ_²ëž7ÑmmnŒb8¢Ž2 rN瘜ÛÚ” L¤úí¼E§ø²3©x$ÝD Š·ÚÔ%!DO¼îå‹QÛšõ+Ÿè:þ™g øwGKÉ 4Óê.¾Z+/˸ ÎG”WÏRjZ†ã};IÚy•ãžÙPí`F$œ/>€8ï^פkÔÞ!%´ï³ééåÇ1F^qWm |¹þ{ÓŒlw,ë¾Ò|yv¿m—ì:%²#Ý¥º~úP! ~â’v_˜ãfµ Ÿ…ôÏ ÚxWÀA †¥ód –W”îÀRq½ò£¥:óÂ:µïÄ mõØåÓôèσNó#¢È%™ÌrÜWKá èÞÕ¯5Ý-á[Ù'ûEÏÚä>R|¸häàœÓhGŽßË{á«;Fð턒Ŭ\³jw{Ùbˆ‚±4ª7!8Æp>µë:ßµ_›]mKÆZ첉µkû³éšdqªU•†#‰V%R]¾ñÍsšÈþÞÛAzn­ÕLjÑ/—PsÀ÷' çÔ×E5ýÉÐ4ßèÖ(Ï;•]ë¹A=›yíÍ.VDîô*iÿ¼Q§C¦“wföVøFÑ$H9”qÀcÔuäW#ÿ 8ðOƒ5mf{Quªj÷é[™ ‹…§.­öð÷‹¼'¦ê>Žý/ARÙbû¸ÉÀïŠlç°ÚÈÝüe!µÔRK©¤”ªÄ„ @ œ–nI=±UeÔ"Ñá¶Ð´@BIq$LÁy"'uÏÌèOzæ¬ï¼' ZÁ¨Å,¶:ž¤Íu,*¾t…[„lcåÊýÕ?1í]lj¼CáŸE-Ö—u6¯©Il¾lÓ&ÒÅÈýÊ0;õ¹Yeûßiº¯…åRB<ð-íeÇ•¼+‘÷¾œç¥'ƒ}¥úw?7¼¬^Ícgy-Òý‚æÅYžÛýd²mxÀÁ¥li«ŠuAi§©´´µEòRA–ã’_·>«†øž~éºûC¾ÛOY<ˆÞv î·È£šíÛI¹ñ»$~½Št‚äFì#+ã¾={Žk槦‡°˜¶¾2û[Oö‚¬`%Ö(Ô  8Èèã#9â¨ë¼ú}–¡lÏi …œ+!›·Ô ír_Ù„/5Õª¨µž$QóÈTí×ÔöcOÒõ=JÜ‘k O±bI'€@ÏoqP3»×5=}>ßÂö6±ÚYFæëål»œ| ú×=I®ŽÑN«‰¹ÌŒ³JNAè>˜í\fšº-õ¬É} °‰Þ$ˆ`—å#ÔJï¼â 7…¦ÐVã]ŠFó£ÚAB¼/=0}»Ð Cú.›ªÊ÷7ó¤^|*G °dŸZô¿‡(»Ô¬äÖ/-wÆU£‡Îo–i8ú‚kžÓuMÄs,ú´"àÙÈ#Ê|Ç8çÓÅv×íá˜l¬´øó 2©ò S’§q8 õäŸÂ¦D½M-B-B®·¢Õ•·Ã•É` u x¸¨<úd¶ñëÖk+­ÇË÷È1ã ^¼ûö®.îçSy¦³†#u4¬0UÁ8Çð¨¯Hðí…ô‘ÛYÏo<|*Bp¡ºã=©5!ÊO¨xO\ø›ªÏáä[¥“,—QdªErÍé÷A#ñWʦ¨k:yŽ-QÏ c¶$#žœœnQ×i¯·üE¢üIA“Å—ítëÖ¸Mî|²ÞJ`) rPŒàt?xÄ/ ]\ü&K‹h¬ÒþBXÙrÅ@îÇ>õµ7¨ZÈümø»â¸|uð¢j·$k–M–Ò¸¼yÉèX8úÖÖ­i¾4дínÞ`>QË9ù–uG^{W“|^ðì¶gK‡ûÏa }Ž}RÝS;¼Ãs)?"ÈÙÆžØÆq^ðÇÃmq$×÷ÒJ-îfMÌù%¤œ¨Ç>æ½Çv–úÅì$Iç)‘TÄ òp8ýNiÑÄuk-'VÖ¼‹O²™¼ý–gR F¿Sǽaèõ;•Òmu¿H£QMÖÈÁT’ \’ÿ(®~ûÄ–š#Íà«Y${L£O-âl€£œn>¼ö]Òï^ð…þ›g¦Fó¤/±&`Ï}äe'œî?7aÒ€-h>$Ó¡Önmu]%í œ{Ý p¬"½Êq´uÃf¼Û@øÁã7ů¯i·Å µ¸x­íî#Ì‘‘´4™RH9>µŸkut|)­x¶Fm[DW¶¼ŠPïІÂô;0z ôï‡ÖO‡¼bëâÏ1õ6†+€bŒŠKŒƒƒÐ!±Ç?J†â½Nî_ϪÄë%ô°¼²4q²# 6ÕÏjà>øR-CXÔ5ïË+á¢Ù?y2¿(ž™äqϽzì÷þ.ðU„ÚGŒ¯Ò}JêY®F7ÙŒc3¹±»¾3ŽÕÄèžÑ!±ÕXȘ®×ZÒ¼ áGÁÿ|9¯¬óÞ¼GRŠöà=Á[p¥Š+ª$î_lñ@©¥iµ‘âË8í´í:ÒÒíe’EŒˆqž»ºç žkå©m>2xÖÚýt9—Rt ÝÎŒ*±Â–-É@ü«ºðÿí#ªk^7Ö¼G—Û ÕŒ›´kÆim£³‹8äê¸ù‰ã$ó]Æâ‡V¾$Ô¼gá{ðö›y¤fÝ’öƒ…h¢²A*°â€8o†þðÏŽ|{€<_a}§iñEn¯r²ê I³SåÆpH'|<±Ô´=ƺçÚm´ÛK­>+y\Ff•‚6HÕ~W01zõ½;âÖ½®øZþÝZGm|³Zµœ¯å2b%„ðTôÁƒ@î9#‘ïW"ðn—=ÿöuÕüÈбºBp¢$cÉQÎyçÿÕŠä´ÏO¢üOK¨íVHÞÍ"ŸfXfÎcÏÎ=+²½Ô!´¼:•œ†&¸R$߆Uä¹îr~™­ šw"NëCð?þ ®j_³?íweû èÇâýsš¿‹|y ’ =»lµ†ém‰U–ðǺRjrzø)ðäÍ>D²;ÛGGwûÒÑ@‹vOÌà’Äÿ ýãÿ‚Ý|+øâOü4ý¢üöš7‡´mOÞ&Õl˜¤‹%ýÌ j· &Ê›ßåLw5ø%áŠÿÿgŠp|LðŒZ|×ÿÙ2éϧhn¬ç·ºÀ8Bë‰2£Ë~vŒkê0¯ššižMei4}Kÿý’~IñGÄ~0øc¨ÞØèvÚ1¿†[†Þ²ÛÜn î͵q’Jø‡Å~ý¥¿b‘àé~ø|KeâíãS‹N³•åN•`OÁ>ðß6ÂØ]ðßím*x·Á“ÞjóéV°YYßkV–vÉt²]0-&p†MîpH_»îkÏ |,Ñü5dÚý—ˆ¼_ã1I¨^ZÉx­|Ñ$Yù˜Fn8×§|3ý˜~éß¾+x»ÄzŸˆô/ÙÍ7Å ËfÝ"Lt©VMâgÎòvŽkètÑ~üFøŸ} þÆšn¾ÿ "·šäOâþ\÷6,ÿ»¶’`]¹‰äÁ³PKGËZç‚þ ø£ÃÞ%ñ¯RÿRðG„;×l¯ùÞº<nœrYJ*tEˆàVoÅü#ø¥áoø'×ZÕ÷Œ´Ë=CYµ]53aq5”û `‚>ΜHæ0#<‚ øwÿ /Â~¾ñýÆ»¬_|Cºñ$)ૉÊ{(š7¼{‚WJ‘±`8Ld׬|Ö¾|DÔÖËFÐÛMŽÖçWñlíÄ 5•¤lî¦1Îùb‚p†59, þkðNðbÇàx[I·»Ô¾#ϪÍwu#½šé8±ŠÓiÚ²Ã0 |Û‰ÈÊézmßÄOÙïUøiáÓwg'‡¼w¨ÏrÆ—­zÆHŸg?¾ÚÆcÕ£€9|E´ñ÷Ž-~*xN->[4¸kÓåí²_= ~tÃîƒ8Á:Õý@øi¥é–W€¼Mt‹âKç·Ô™@VséªÆÎRH ›† ¨¯´5Ÿ~ÎÃàU·€[L³ÓEÕÕ˜¿‚Ò39¿µXÝ£›+–سmàãsÞ¾.? 4=#Bm3Á+ý§©éÖmå#;Kt=«?Å3Ù·‹-4ˆ#ÕH×mDä³Oe2 >AÖ*9)\çí5¨¾$xrÛÅ:άÙù‘2-#E²¶òœÌ¤ Ý04¼a«kÓøWÃ~=øC§R×¶¡’ÝÏÚ#š]®“úíÀꦼ¶-GQñv¶o“O‹T×uyc¼¸žXÚÜÄѰó*€¤p“Ç­vZÅ÷Šõ;}bãáÕ‹höLd€˜aXÁ%¥”÷¹ \í׈IÓþjfÒTòa™1%»È* r³sÏ@®±¨éšDéz΄–ºž¡«Ék|–Çj4HÊ;²¶ Øò°žéÊmš)db]GÝ öÇ¥¶ñ¿VâÉ®,šâÅ,š+xal°¹'9süYêFzЯøÇÆzåÏ‚¬5é¦=>HmÑ5 dùV£ûâI9ÎG çžµâ~:·¼Ó­|k«éš¤W6ZÜ:vŸy,G4i!`WŒn==k>ÜÙèƒCÓ5í,•Æ û¨÷6âJÿ¼y=I®ƒD³ðìu—‰SíûD2bd-WžÇZµ°›,ê¾6Ä~ Ò,¼0¶©>“ºÆââT Ü@ˆÀ `}ÔÏBMszo‚#¶½¿'–Þ[‹Ë‹0Û²`§Úap?ÔÎùIÏ¥u·þ$ø³ño^Ò'øç«ÜEªèú½®…4R:M ´v”$sæ+`œrTsY–Ϩþ?§ÄŸüg×bÕtè%·Ðo-ô /t‘Vc3gíŠ@ dò¸¯“¼IªGá«GGðnªÃ¤ê;6Fº²¹Üãi?x©Cœqž+µojÙézµâ «±®LÐÜË#´ÍFÊ‚X’YH'¤ö®_Äÿ |a¤jdZÅz–²Ìæá[v†+ƒœİè1@6z÷޼CàÝij:«Cµ$Z•(¥Á—*b{ÞµÆéòêzÌ3hžX®¦Ëw#LÞI[U}»£|a™IgÔxÁÚ7Ä;ßN½‚Æþ[:áÌ|IrÃ2)ÉGñ(ý+#Àö¯‡µ­wÃBæ-Au-=g† À«7 Põ™?)èFp{4®A¬xãÖú¬Zd7_ÞA·ã143¢®%BÀd+ž=3Þ½_SÔäÕ¯®/.o–ÚÓZ’‘Øgýzíýç^ã>ýë”Õ^ë⯅|->¯j¶WÞ•´+›‰$ ’Bòý©÷f‰POPÙõªºG€¼1ãÏ kãK¡_è/ ŽÒ0ÜÆ¤´[X䲕?.:7zx/Æ×þ5_h­¶ƒc«Cq§µ¶Ù%šævTò¹?2£l’1_F|4ø/­ø÷Yø‘ðÖ?àjÚ\R=„¡ÙߺL$’Í÷e„¤ä¦ÎTžâ¹Ç¿³²Ð4Q²K§H-¸}²Ïp•rOõé^Ãâ ø+aðÝuÉSPñ“âË[âC‘@±›g•0ÁŽÍæ94#åøSÇÄþ"ð÷‹'“MÕlfh®Z8É€`F3 ÁðOW?iðÛâ†ô§ñíÒê>±/ÚaXÉ&o´œ:»X.qžkèx'Çv£Hø…k5Õññ$¦Òcq!f‘AQóõ.¥8F?LÕ=#Ázv©â]â6™q"i³Ûê‘$EØGö«<ü£ÒÁ}qI±´|óa'ü#Ÿ´Ë{ùä}]8ÂL ¸ÁrÊÛJ“×fÉìk¥øù x_Â%±ðôr~Ö áÔ&‘DdZêB)& ÀrR2sŽ ºx~)þøGâ¶tñÝj)ykydc%!’7hQýNñ“ŽÍÇÜ|Öü]ªêºæ©cᨵ[Z× ’­?h»·¹r²Äc`!lrÌêÞ1ñ·Â­?áæŸ©K¨hšn·5üWldˆ^\D`ÄüßuÙ '¡ÇZí´½sÀ¿Ížµ{©7T{­6eR@¦ÚPîNrªx,w`޵ó×Ä…ßü'áëÏéNV{ cªiðHÄCtP´sœ è\àà°b+Î~j^“TÒü!â{KÍ.m6æêØ]F…®,µi¤ó¢K…9S–2™ »OÝÉ¥žøŸw«Ë®éž$µYõ]ný¯ÅÔ{eX`½¥‚>Á ‘áB~v·ƒô_išo‰¾øsPÐe†ýåšßY— “d(,ÙRŹSÈõ躥†‹áÍcQðoƒ.Dú棨¤÷v…‚ìžÅVê'ù±˜§Œ4ez¶8é^ýáïh_û‰.Ÿ6ºY»Ö¬$ “ìwyʲ°'[/¯Žtwln–*òGaŽ*åœÞ¡«ø?ÁiÞ›Ni— -©Dˆ,S6ež,zg޵Nå,<#e¢Ëàý-¤­­´éaW,çìóoKgåmØÎxÍzψ¼9yqã›…VZtqÛéWÉ"Þ^J¾\’\) ~ètù³œ+¤øm¥\Ùêö_ ¾#5—ö•åÇØ§ŽanIH¤·-Àenøö« ø¯Ãz‡Œ4K«ûøÛRð¦ªgþËÕˆòÛ\)ÉÐ$FpÊ;WðÇþøcñ^øŽ«øbØj·÷:Fÿ4Âé00È›ø“ÊùŠ3ó s\ů…ãᇈø ÄØä)?0<ŒŽ•—âÍ7¶š_„|Má»ÅÕ4ïXÝÚI|‘y¬¦ƒËßñÃd·8Æ­wßþ#[xÃÆW^1ñµ±—F×î$?håf{kžWd@§ž21V^oøB +Äÿ /äšÖë첋y‹É5´%”Çp27~„އ=î·©xÇCðͲ@ךe¹XYú5ä ™Œ ò}}«Ž²´Ð4¹e¼@Å. Œƒœ¹õÎ* i>6ñâx#BðuŠjž"ñ%œÖ¿d°ùѲµË}òa,Á˜òM ­<âyôÏêv‹=÷…c»Ý眳ÝÍ€¢š2äŒëËþ¿†¾"Úë ¼S§¹¿Õb7¯«€cšÖúÈo*ÍÈup@ cÉ­Yÿhk;Øí´¿ˆº~Ý6ÞÕ, Äq,b!´2‚÷RCv¯ ~k^¶·ñ7ìçãx#h¼i§YjÚv¿¢xã–Dhb ¤åYY@fÅfËÞñŒ|I¡E¬xÞm¾°Ô¡¼Ô­/fÚtû«‘öF¿‹ÌÉPU,>^¤õ¯BѼðÓÃÞ'’‰ Åþ•[øŠÓkGr¸b¿h·3÷íÊÊŽ>RÇç5ç:ÃGâ]gáÇѲëz]þ‡1vM°ؤŒ+(cß5Ø|nøª|4Öü1¥x[^‚ï\Ôü8³Ã2ìõ-%‰7qÒBË€‡'¶h@t5µðo†u}wþ׿á#†ÓJ“RT¸ M*‡ÙJ…2q\m·ÃÚ Å~%Óum7O¸º³ñ.‘%ô‘G*HÖR¼`Ùœà¨ÈpzWû5ø&ËÅõ¯ø>Õ–ïTÓ^ÕòIW ógÐm¥tñ~‘ðûâ•ÿüq¥áÙ´ {« k…“[aäÊ®¿$±à†N£k`à‚+@04Ï ørþëAñ]ÝÙ†ÒèlNÁŠÜ[Ý àÍ ƒ€Ã<Ö½ž‡ày?gŸiž £AqÚÊD‘™¯LáÇ;b\–è¬ø"OišßÅ R–èé·1Z Såœ}¹¶òA›Ó9æ½÷àïÁ¯i~Òþ-j>+‹AÐVÊDԾݬo,¯Ê1¹ü˜ûÌØÍxå÷ÃÿŠþÑm]¼y‰ÇUð4Þ…nm¯4Ë¢Š€ßiµN(sÌR G•ðûDý þx¢?ø ßüCð6¥f°øŠÁÅ4k?&U.ÁABÁÓ¶28ëRÀŸ\ý<á-cÄ–4ûkêšeäZ׆üAØž`§i…ÎÙª·Ì ’ ÷ÅwÞ1ñŸÆ¯~Íž8OŠ‚}CÆÑý’ýtõÿD—ìÒ#Û\È„·—>á»zsŸAÅy—ÆÄý«&ø㟌š/Œ&Õ‡€´´ÔfÐâË^]h¢aç (ݾ(¾t;Ê㸯œþ~Ø|-|ºúêvZy\Ïò4"A•ü,¤d@iĤ~*|?¶ø‹ãŸ x—NñbZxGÄÖÛÒ¢òÛí „5¨-”Iw …b í®—àu—†>:ø«Tðý¿Ä‰ìõ8´÷·µ´ñ?úƽ´É·ò&'d2mÝÈÜìž¼µ—Æ}¿jÑxÛâg€´ÝVÒì'ûF¥§|æ{¨YÞi!Tfûì\¤`A㇋öÖXÔ>$| Ô¿²K9ÍÃjêñÞ¼¨æÙ‘¾RzI-½\gÈ?XxŸáoŽ4?‹úQµkM^V·¸Óå!h†&‚ 0Ü7…nCä”àxŸ¾8h>ø…¬| 𦉮•âƒRÔ¤†Ý<¸îå_$L®Å¢IP|ªÊ’yjR̾ë¿4/¾¹tUc½‰-üûPä‰4>`>dÆGµ{|MñÅŸì>#øªÀO~ˆâi­~kyÝå£8ûÛ[ëÏA^^¶^·Ö¼YᇺÁ:·‡tÛkÙà•FÉíå` nþ!»i¯CŸ[ÔuÛ½Ã~†÷Hñ%Ë-ŽVÙ¼€üÊpT†>¼óPˆ'Áñøb×ã™os-öƒuRÃyZŽ‘yuûв’ÈÇ*Gr+'Ç:Î…aãÛ_øfþçQº×.ÿ´#C«Es"€IO¸ñJ ȆZHì|Gq¡ÉâmJhíõÛI¥Ó亂­ÂBvIÊp§nJñŠï5Šÿ |Uk¨üW×ô8àÔ7„þx‹âž½¯^]ICL¼6r7›=œŽsŸ›zç2G¥y·Ã¯‰~ð,ún»ñWA»ºðn°²"j²ím2þwn¡`G–XŒ>8`Ý;W³Ã—àßøG^M{Ãþ+¹a4R`i€K§MÀ` ŒÓh™Áï‡6¼¯ø#áUí–­`:âjNèšµ£Ä€E8. ]Œ:)Ïc^y‡<)gá#âO€¯Íæ£q©"êšmëï*!WvÈÈÚW>+Ûô‚ÞðŸÆÈþ&x;Yµ:®¢&²ŸO´”A,2L…fPªrŠTŒkÆ~0~Óº7…tÿ |9ý¡<wik$·VzöšGË#/ÊÌA êÃp · ´R<{Å^#—ᇋ¥Ñ¼Uoºts¡µ€H®¿f¹9ùXpñî?tŒŠô½GÃ>9Ô5k}sÁiïkm,ÒÛO¸¹Ðu-A…øŒŽÒT¾ô$H™a¸²®}x®D‡ÂúwˆüA¯èž½¤xšÜKs¥_Ü–‰æ±”‘~t}£ t=y ‡¹æ·þ1¶ñ·Ä_û_P}2Õ‘®çI€t†KdQÃä˜ü€ECâ­ü)ø/€~,¢ØxsƉe©è^"·ýõ“ ²¢e•2©!'SÌG–Hjë´MKNøQûHÇñ;á›Ë¢ØÜÛ]ÃcªFҥݬˆ©=´ŒÿëQr@$žB±é^•/ìwáAãO|ðÞ¬úO…¼S­?ˆ|¤4¾uŽ.¤‹,¶BËÛ¡œ¹Û(®ÑÚÄñøG|c¡êz”2GÃVÒ\]Âí¸Ko»hšN$Fžr:k_Á÷ÞÔtÇ‹_ŠF½¼³:qfߙ˴ž@¤u<Ö„4Šÿ ÅåÏÄ;vÔtë Îö0o³†ï÷.ØÆöTÀ=‡a6áO ]øO]ó-ï´Áݬfâ 1\£0Œ§ ½Aé@žç°ÅãûMD6Ö!Òµ[Ø®¦³»Œ›H]ð%‰ÑÇú™ù9^gUk]#¶º¶Ÿâ[KVµ½†âˆ%iá·nve‰‹iøz÷¬x"ÒßíŸÄQi¨\LÊ,ï'|[Ï °tÉ&xº}_AÐîüI¡Xê7 ¬o5›å–2Å‘¶`nGµ|õñ£à§À?Š:%¦¯^hwúR{”·ˆºÉÅœíu`¥IÈ ƒQi?Ä .‹â[@Ð[ïf¶q¿Ëž1óª’@*FN;ŽqÅzN§«x£]²³“MK-KKÕ£fÝö,.P2g?!ÀÉNÇx?áµÏƒþéÚM«§h ­Éˆââ1$™óLyÝŽÍƒÔ f½sàÜ? n®æÖ·Òíµf´¿ÎR97ÇÉ(~GP$àGÖ¾ øÉgà¯Ë/‚ôÝ&[½=îå•5h¥@͹ 9ÜzÕIu½7ľ ¼øqn¬ž4ð–¤5+aÈS´›p‘Ÿ ²a·mʆ*O̵Øx?W¼ñF·cà­>'»Ö£‰fÒŠÌas1«xsÅVÑÚD¬Z†…¬}Ùf•JË ;X3È$W€éÚeÞ…àÛ»×°¸¿ðæ‹<ÿè +ÌÖ2Œ€­Œ;}pÉ=B[;¯ˆãà‰ƒC×’ÂÒí¯4å’ÞûO”–^0¥˜ªÝ ç®kKÅÿ³¦€h&B0’ÆOÊã†ô5ÒYøÂÓÅžáBÇöQ§•¹ÓΡÚËžxûËÛHéœP#Ãtø¨ü<ø“ Çý–}“PŠá‘N¬1À~æ:©éƒ_XüøñàÉ¡|?·Imæ]‹ö›l´q§Ê«'gN€K ×Çß#×>%j0ø£Ä¾·ƒYÓm†›¨GnäÛ\¼Yýáþá`z~U‘à+Áà«;éì4Ë-'*A´ˆªy;F3Œã‘Û½&€ûsZÑað/Š_Æ^ÿ‰£+¥µ²É ¶O™uCœ« 5æ¾5ñψøuãÍ?UÓü{ylöP[®ë›9¢þb`Oƒ#ràãpê9®“ÀÞñ%ù³×¹õ¯¥ôý_Ä7þ¼¹ð½¦£”ÓEs¤ß¬U#•ld¨þuÉøFÇöxñG‹Ø¬÷ZD·‰ô¹‹¢2thd^¦0Ob6ã­L°|`½ðý÷´˜õy,"³O˜¶x3ÇÏxgÆZ$÷×~ m1´{{H‚lQ¾6wà¸éÔs^ƒ¨üvø—¤L|"ºMµí…¬2Z£[Ìë;EÊ9 †ë:öÉ®WÂWž ðÕˆŸâD“C»°°Ø"-÷A'§<îìzP©ø‹Eð'‰4%¿Ý%ÑÔ"G)Ë$±d<ÕóíÃéoL‘žÝë´ð‹xVçáî£q¥ßË<:S,į‰3J0` ãÝ|ñÕŽµpšîŠÐ_jÐG{2áœ8ù£”q¹N;ò"€gÿÐô?ÚÉâ»M#T{û©!pÐØD䯤tܧ€£®;V´÷šu⯟¶¥ï%îx…x;€Û›¨Ýø©$#ÃZ‚Û Œ d™;Ù3~`z×'áÉ|M¦jwöþ%—ûNÑ\‹D"7\62ÄLâ¿/>Íìuš—ö.¨’x‹âˆ®õÛ°»•§ ƒuW W™_x«À¾¾…žio¤™r²2ìEÏ@ƒšë'¸Õ.ÍÇÛ Wµ˜íeƒØŽqÖ±µ« ,Ðh*ÛKù[hà¤vý( ˆ÷Ú͵ʹ÷2‹÷ÖÛØƒwO¨Ívze§‚–8oµK¢"V+yX’à>ßZÖЬìõ ,4xÔ¬¤2±;²Ù¸Ç^=j=j? i$ Ÿô›õw6Lp{*É#«Mn4­v ;UPÐtä³±ehä™HóuÄ|çš5­Tx‚Ëeµœ³ÇÊË4͹²zlQÁë\­Ž£¡ÏpÒËi,óÈFBQ}³ëR«ê7vwÙ&Ó·’­·bç®GqíÖ¬®fp­¡kW÷¡¢¸dœÉƒ´q‚{cÐW¤ønI|)wý£5øžôÆË 1yÉn¨?Åîk7DÓ,'¸Š7 ý¶ÿ0êr{žæµ<]n<4¿ÚZÌ#Ìû"ä®3ÈÀ¸æ€æc´ÿ GqV­©É#\ÉæÉs PÌzœŽØ®/W¶¸ÔüY†´u†þ2Á¥ÃæzFêU@íÖ·áµMGE—V{óO™°Èß{<ÁÆ{µÉØi÷öQ6£$,^8P$tòj™'öNŒßa´ÕÈÕŽ‘$D\Zü–‰¸é8ä7ñæ»kÖÚ}ÜCÃöŸÙÐLãË+.ë‡*>fwÇ?ÝàV.“¡^K¤¤×lÝL“Æ@üÆ9¬¡á붸:Œïži˜2Cy8@xÈ*¿ZµB­ßˆ5Z}U¦¹ÔgÀ]Opí…ô$‘øŒ×´ü.øƒ//í¼In5c’‹+i-üèD…¸rÇåCƒŽMQºÐ¯tÿ A’KS àâIˆçÔï\f±vöVñÀZkxÊïuˆ•wÇBäBŽÞ¾´‘‹ã¯ÚÚjòhš•Õ5 g o$6¥±¿9ÔãQ\øTiz©vÁ+ó;óØ(¦¤_i:EÔPÛŽ' –q¼»/¹ôèHÇQL·»Ôu«éµ»­RUšfŒCòá"E8(«Ó¿'½deiz”úŒ?Øú»4‚âA ŒªÌÄ·aƒ“‘í^‹¨j¿¾h—ú¶»áûÝzæÒ×v™ Ù ÞÝg ·å‰ æS†!3Á<)~Õ¡j>Y//d*M¿a¹ÛŽ1ëZZG‹üEojúfUù;œð±9ÜÃ%‡¯cÞœþâËÝ2ÞøÂ×Kðî³°·Ù´¥"ÚÔ¸û3eˆü¼æ­jCWñ ­é’ådûïpÌžaëÁ<¿MMáK½CJÕn/a}JU1¾©|¡„jN`Œü©‘ÀÀ' µ­i¯>™/‹Uû`T­Ã¬1ªy÷Èõæ¦sÚü&·‘áˆ&»“…*‰¹J©éócó¯F–ÎþÎ;Ë?EökÓ‡’%ÿ\7vÈ>ž;ÕO ø–m y~ÓfרêÌQr2GEÎ §Xøº=kù´û%¾–éU’iœl<Œ°Ëg½Rb'ÓâšÆÚKK¸ˆ2mò–FV>_r õ<àδ|\÷vm ´òRET*¼OÿXŠâ¯e¾1Ú›Ët1·‹vì“÷@<¨Íz~­áèt‹kð–ínT€¹y»“üª€èü%cxŠš¤°ïA(õ$ö “Ò¾Ûð±{¥Y‹íäŽââ4%Â7RÝvƒïÍ|_á_j×76¾Òa’ÒÎó™@GöÇñ÷àb¾ÄðLJ¤Ö4¥h‚•ßþæ@ ±?P[©¡KDz·ü$vš [˜íÖl e˜ŒHèå]/ü[q7„Þ(bYYf*o_®1\—„þÅ{{#"!O,1Ž_½·?{ý=*ÝßÃ-#M¾§{&b—*–„©^20Ücò«0hôMWÄš…‰ymã†Em‡çÁ>«ØúgÏÇâ]~-*3I™ ·ÓuE'1Ëž¼ò{ àn¡±ÑHÐt X~ÓzÇìð@Íq)ážY%Ÿ=Ífkºï‰¼#w‡á€E$ —‘ßs©nNà}(!ÇSÙ|@ºÝކ ª‘ ¤h½¸i¶îE!I矧=«»‡Åú‡­#ÐtwŠ+˜Ô¸›I<ª¹Î{î W‘ÙÉâ+­eµÏµI©ZÂQ o IÚÄ· à€E{tð^ë1ßxKá¥åýœ–öË=Ö«¨Üù“M3’\Æ8HTgËPHãPYÉ.§kàïǾq`ÎÂÚ2¬ÎÃŒ2}=5«ˆ¼E­›¸ìmÄŠ~d·€‹¸œd~M^ÑüñcQì½wUNÐ-c3Ü2n{‰ß¡Íó3¹ùrÄc Ðç¾ðOö.¯®,ÞH³3‚<#&ãåîçhH–xv›ðÿÍ›MÐ?·ø‹âÛ Æ-Åžn/.™|¨ãÉ"¨åˆsÇ¥{¦“­èÚuÌ×77:Œ“ÉåY¥Ò‚ó•ä¸U.yË`‘ÜjþûÅÃͪþÖ¬¶qÐÓ\]¯ÃÍm¹½[AV“÷ÒÉýáèH÷8ÍnZØÝA5ε¬³Ï©cÍäÈûW¢ŽÁ¾œcÚ¦·¨ø‚ÓMð„òéÚ\˜Ô&q€âQ'Ùù‰Ï÷ ïþ"Êò ~ãRÅ­¬¢'$ƒß ÀvÏNÕ•¤ê^)ðrG¨éÍ ² ‰Üa\¶Fôú~5™ã ‹h-ÚÒÖsÈ$hmÇÊÓžpÌ9f^˜ùyÁ;U}æX÷ŸüV–âÎìBZóVÕÏ“s|Ð!»ûªZ=ßöY¸Õeoï-£ F?êöŒÆIãq†k¶Ñ¼-¨ÛZØi~Ÿ÷Ó––<Èc,0X“Žiþ,Ò¼{(HŠ,í8Bª±ÆÒô‘ÉÇsÚ‚V懮n´›õÕô A³­L ´I¤óŒÓ0íµrK`ø“Ç~ Óü;w¦I¬¼–Z}ɶ¶¸{p·IKÈ{È;Xõ®ƒAð%þˆoŸ” yaY†ösµˆqÀÆsX^"´ð5‡†ƒaÑSPÖµ'X.Y—|Qy§pŒÃFOAô ³–ð„ÞƒLº×¼¯squ‹‹‹ÛÙÀVwêùîxÀöª~*Õ.õÏCá).â“÷éu7 ËDÛ†óÜÃÓ]økÀº?…n¼1âk­®´ñ£d ̧hT£õÄé¾[øF¡¸’(.•–IÓ÷rýÕnÙéžÃ4ìÿ„C{â­OW·OèR‰/Te¤žV‚ð8þ=©ö)¦Ëj5mÖ'ÌÞÄ£†#²…ÅsZEŒÞ‚Û­”q™c’[hPÌ>èv#ÁÁ=k/ÇVrøª8|Oco#ݼªG˜Œ„ P9ÚGb€:[©áðüš…¬Io­_ÜæÙbYsn#Ì|Œ…Eg¾+ί<ÖV·¾0¹¿ŠÚÈ!+iˆí„™Â°'øp&¶5HõÉäo iЯõYPKeaz“Ç5Êx‚++Eº¶ñ,_lÔ 3=¢’Û\D¾˜Ï'¹íB}ðÏÄ¿ ¼ZštúÅðÓ­§‰ÜɰÊóÎYT1‘÷›¦1^¡ñ†_ÙFûÀW^‚5˸†ÍNh÷ˆ]ˆa°>r@¢×Á“j:ï„ôæ‡S¼i§¡u T`;§û+Üšõ+U´ðеÉ-ï5»k˜/¡gÏÙàH o¼Þþ·ƒÔçšÛSÛÿà™÷zîƒñ›âÃ?<×/§i:•¸¸}Ä(3Û2ã‚0#RsÈÈä×ÑŸ·–“¥jÿ°ŸÄí3w“¾Œ×*ãæ1Ém"ÊŒ>Œä?²•펡û\ë>-±e‘µÏ%Á(rŽË|KÁû 8Ò¾¶ý§ü)¥øãöZø“á=a™!¼ðÖ¤­äœ0)2•>ÅE}NsP×±ââ}ÚúÌïì©â=+Ä?Px‡Î“N´º½ÿF·s±ˆvÌkžBçn¸é^½w¬hÚ—zÌ0‡Ö/’8Ñ{ÇTvξSýŠõgÖ¾Çq'îåV{ˆÔlØíó²çÝŽqé_aE¢éþ"ÓàÕ.2V9€UæVÉÊ©ë€|ö#I´zñ,ßèWlVËÃ÷)!…|éòw v#zzt9'ðÅjZéqê¶ŸwÝ3rl(®éÈ=«T³°§–êxí­¡Ú2ÞQaž’Ôû‹ÚG®É?‘æ(йó'ƒì1ùÖlbMäø4K(¼˜eÞbl`©9éžøæº ØivW7GF=®áÛþz’3ÜyúÓtË-oY¬´ˆc»Xhº‘ˆ¸d‘ëÚ¹}B[=<Üx3Kƒ|;Š\ή\I; ¹<ã±ÅO0›:¿][ëpÇmå¿–eó'ã ''w¦k½Ò´›[_’é’M‘À­¹Â“‘øzÖ©øRûP]>ÖÃK²d{K\Ï0_‘›¦z}:w­MN´Õ|C%Ž—x=³Ž€Æ‘}Ky©\x*/ _® “ùJíiòÆz³qI®? ñ¿j·šGƒôùíµ H"GKRdIJ>T/nGÓŠ÷Ó®øwCП\ñ…ùû%œ[¤FÉTòÀucÎô¯µ›»}B GKÑ´Û‰¢½¹gVš3òG)ÈÚ9È>¹Ö”Þ¡%¡ùQûSø ÷Iñ%åìr±¶ŽböÏ·%Þ^ƒÝW‚¾0ñf¢u ÿ?Y€­ëD<åV'€>ø¶+õ[ö”¹ß‚.5¡0&–Whˆ#ŸVû¿\Wç"E©á;]mÊy<±H˜;3òä÷Ç'ÅzPØógñ¿û7x»Kk»½UæBö6ªNáƒ)rWn:³`sÐ+ïÝÅ#û%޹*NlÙ.-ÑxËJ ÁáF:ç_^Ö¢ðŽeûl>d2¸ÚpÈ›ºRÁúâYWM–Ó_Òœ½…ë[ªÄ–7É=Lzʪ: î©ô߈z'‡µC¦ÏoJûx”¨Ê»bôà¹ë]†¾'ü8×üVŸgÓ×N·h®d”\ã`Æß»ð¯Ÿ< àÄ3Ëã§–Ö3" —O‘nÀm9^ã¦Üz×§ü0’ãTŽ{Yô›v‡OŽ9|ç]›§.Až>îž+RÏdÔ¬4htøMtÓ·×y·†"»V2ýqèy5çvÞÓåÓ§¾Ô­‚ÍM‚žy» ǯ˞[Ö¼ÎÇÅWQC'‡.”,¾]¼Èq¼èìsÏ$‘Àö5Üé¾5ÖáŠú=Zu¸{X;¬@ì9ù³ÍgÊÀÇ¿²‹QÖa¶¾1ȺmÁýÜHÑwn>ßGàýE¼tÓ\êqCp»?Ú%}ÌÊŒTˆúu=JèaÓæ×|+sâ­R7û9¸…UÚ^6ù‘Î}^+‡´ñOÁëKíCáf’¿ðÈj‚6,ÑJÊ%òÙÉ;¤”È?‡péW=†ëÄ·‡5¦h’ÛÁ´QdeÇVçç¥r–ü°ÞBu‹¡5Áˆ¬@ÿ¬˜n¸ïÆk#PÑîôÿ$þ+Ø$–Þ;Œ.€H¤ªŒ~Y¯(Ñ5x|I⫨–P Dª÷¶¶W·ùéT¿øWXÕü3 ø¯Uc\ËšYr9îk²±›JðïˆÅ7wW2ÞÞÝ3iÐY£e›…F÷'ò¯ ºšï_{ùœO#&øãÚyÈîÆ®i×0ñ5õµŒ7Y£f!Ž`ŽYä/ê@À @Ii_ 4û UüCâ5h.QL÷Zs?˜Ñ\HwfnŽzc4ýGÇö¾#Ò5o‰º%¬fî2-æ™O˜ B€Ã nÄþ5æš\v*3G¨!RꊮZW]Ù_1‰ä€ $ôé\©x–[ù|áÈcšÚéåšhò2ÁºN¼£=)X”Üøã\šçU¸·ynâ´Y­âS»ÍI8› N?ž v> Õ<3q⟇¸¼¸Ó®RËN·m­àP"šGìˆÀž„ŸJäõk>,k¯h¾œ±èñ´™i&Ž0w8é€rO½ª^Øj:-Ö«efbÓ¼;lf¹–Y@y<¡óŒg À<‘I =Àøš+Y\K%Þ§å%ÑRY›æb2I@Zç§ñ>·'Š!·Ò­3êæâúdÝjÕ@xÈÀÅuZÿŽt„þзÓRËL½•´Š7»#cŽ@ýMIy§}šïIñûÆ‹y;Ú]IBy?ÂzÚ¤ÔnüI$W}~aóZÇ +óuÆà~Pï8®ëÄ^Ð|zšEÿÂM)µö·Ð–Nyãò`³¿VËl-´wÔ€zVßߨ_kmáΦ–óºGtŠ6?Ä‹‘®ÇZ¥Ï‡àñ7‡—WH‚Ýa¶´³_ß\ÊH“q<.Çò  V¿¾¯†ôëinótš‰@“G4^`(G tú×m¬Yø‡Sñ=¦„ú»xvÊÖ9fUÚ®À|Ø$sŒà +Îþk¶rXhÚF™cóJ—/{là‹È'kg¶Gðžk³ðï†-ï|[­ßZµÆŸ}l\² ¦0H(ôã“xGá–—ãgñ¾µá$×4ûíÐM$³»¸šF2®qµ@Æ8íZ×ÁïÃñ/Hñ=ökoá4‘~,ʰ·rÛJàncòäŒñU¤‡Á>œx[Á·ÒêÐË$SÜ‹ IJ½G Œ.Ò=«Ø¼%ñCDÖu=Jñ¯ 0^Í—ÈF’¦ÄòÏ?0OóÅyÞ‡}eàMKUÔüÜÛëE-!O0Ë$m#ïwà džøé^Eâ&ÿdñÃ[\ïhà–p­\áG@rã^ëðûÂòü8Óõh­mwh—ßÙÖM,¥‹ý¡~Ye€8êÇóÿ‹VñCk𦝤·Ó`[k±f 5¤ŸtÌpw€@ã“K”|PÔôËýbÓWÒ¬‡âëkY.AÝo„Uþ"9ïšúwÅþ²ðF£©ø~ ©n¬ì^ÞKi`!Ùã˜c :eO\t¯1 áy>èÏ„!¸[˜<Èõ©®Sä²wÀ†bvüØÀ O\“š³â/ˆú\þøwJk Ù-\_È>b\u)ž€ãð©ê6…«éþ —Rñ?Žç)íŠØÇŒÛ99jŸM¼Ô|Vm¤»¹ŠÚÖòÿâõä2NqŠòGÕÏŠlm¤¼µ{½:ÚÝS<±QœîóK¤ÝÝ]ê1êÍ[íʈ”HB©Û‘ßùÖ‰ãŸðS¯é¿?à^ ð嵄wÖž)Õü? kï+<Ö7W^eÄ‘AÜd“ÑïŠþ]‰4ýYOx³Eg°¹W_06é IX˜˜Ôã"¿ºß|ðOÄ/ÿ¶ø‰¦¥æ‘â-H.c*…¹P¦V-÷]wåHçÒ¿„ÏøwÄþ%ë_²WÄÙ÷Å? u;ÿI};çYÚmkIN…ÞÞHÜŸï1ô¯+wƒ<ÜWÄz·€VïZë:´Ñè:už¡o©›dq—Œ¼êÙûÛq¸rNz“ûUü<ñÇ€¦2G{¡øÃXH žâIÙžÚÕw$n9ÚdPÛxQœu¯Ÿ4 Â3ÜÜZxÖIƘÓCÃZ‚~Ôà2€£ªä•íšö-ZïÂrxŠ Ï… `Ô£ñ.Ÿùz’ln-.VfPÍ +œ°Üÿ"½&Ž8ý߯í3Á¿´û¿ŽÚÖ¯ÿdž¯¯µk_XÇYÅkpK^Ý:… ¾I¦có9ààN~8ý™çÒüAâ/øê+ã oÅ7wV†ýü¦Ð죞Y"hÕºÉ%»*”ãhSŽõô?ÅOj ñ÷‹­üg§=½¼Ö¯ Ø›?¼yW,ª9 ¤+· õí]WìµñÃö4øuð)¼'ñ#át—¿¼9}6¡g«Ar° ÛYK$1Ü0|8í#­IGÇï ÛþÍ7³Åçt›ýkIñ+ë2xÛ5³­ÃLl´a’0Ÿ¸ë’ƒÖ»Æ:&›à-'Gø«f“$¶ž!ÓmÚ,;H<»|¦ K‚ØÁÏ#¥bøŠË_øƒ«k^ m?Jñ±â]ìÐ_Æ<˜´‹™>ÐOñ/åÇSŽ:šÉÕ®>,j¤Í©]éöÑ-­¶›%Äùû»wŒ‚ùÉÏr8ïIìO3=Úßᦻñcáç‚âð“>·wá´ÜëV¶Ñ‰Sö—“4%Á%wDì§Ë!Iã¦"Ä߉´GŸ é¾ÖeŸIÒ5c \i¥<”D'säȉw÷Ÿ ~;øÇáÅ׊ÛÂÚÓZßø±-ômQ!€¶ý*|ÆtÁ;ÀfTpI s^§â]'á­€´ýOá­ä:õ…¦ ˆªÒäÍ »ŠªÆ~âĵø´’g‡ø_]ŸÆ¾4¹ð†åô“eu·XEhíÖX;E#Œ2#m  ú‡à7‚¼?ð×âεñÿXÒ´Ëûs§yÚ#¾Wv‡ÏQ½YR!²`¼•ó,þ4ñ'‰~xËá…ô¡‰î"Õl.!o.[[Èø’d^¥XBPNx¯¦~*þÓPO xÆßC°žÏÅâ=?QÓãD–ŸÙñéöâã.À y¨ñ…1ëÀæ¤FÇ/€£á>ºžø;¯Ë«øwÚ¥ìÞÔˆ§¸Ó¯#óc·ºˆ‰à”4hqÌ`0’+è˜|Cð‡áÿ€uƒŸ³Æ±?ƒ&ñn³¦]jÞ"ŽeÔ¼¸¯R(®-çÜ娋_4… !÷¯’üIã8mæðÿÄï‰;ç’îÛÄ’ÚYϱn- eqa'ƒå¾AÊ®³Æÿðø÷Å_<ðî˺gŠoO‰t½:'Y"µƒU9(Š0ĪÅ[n“Ç4Ïx×__…º•Üß³}Úx~Éu 1s©.û»k;Äf7Ã>â7EaÛ5ä¿~)ü_ð>·oã-NÒþß3\ ÛržEÅ»…W…Âõ`s•<’x¯pøwñköJo„þ-ñÅyuÏxÃRÕõ;í2Ò50Ÿ³4ë:ª§“pÊÒL$8RIàžü9ã‹¿ˆºWÄmKO‚!á‹ów¥éIOoo€|Ñåÿ`K@#9àVˆ¡bÿ[|ný¡>hz^…³Iýµ®k:‰F¶›N‡ÌB«só—h=«™ø±ð¯Áß 5‹ý'Áö s«Þk'Hò_z;ExL¼’?z…I^Ù¬ño|sq㈸};PÑàŠm?M¶_*«UCç@¸à/çÚ9­gÁZÂiü]ñÞ&¹ñ^£y§xWNÒ$¾ŽÕM¤ñÝ\’¢%bfYáHÉ£Àö?j߉Ú^±ð«Qk­Q­ã#¼Ô ŒYÚ—hSb2‘&ÍÄã°æ¼ã^ðž•ᯇÿ|Gæ]&«$z¤ÖŽÍçIjø]7Œêpx  oŒ^1·_‰ÚŒ¡µ‡E‡P‹d:s8‰ö·J£ï»8ÝÓ‚MTø­ñK៎õý[ã/†t«m>þdµ¾–;a¾4[¬vÌÛ0Ìè[ s“^Qðêãá¥ÇÄiSÇ#¡'¢†o™º×Ûø§Ã_´-K[q&¡vÍuh»IÉ@衸$ e^s“Pi¾ °ø‰ðßSÔ´C ¨‰q-Ó¥ k)úçŽ:}*Ö±­ñàÄ‚s;UÕb·Ônm£›RƒM¹aöqtå!GØT0#@þ.˜æ¶`ñ ¶ðòøçJ–nÄÝ%½Ê0i&ypT0ªc€Cšé ø_¦Ÿ†ãFÔn÷Þ[}®ö[ç7M++ªÁï$ì;TOʸÇŸè>/³Òük­x¯WO²ÍªÇ˜e0I¼Å܃â­jo¶ÚÚêÉowmËF$$Îs÷OjØÐñ¬Äxõ>Ûk5½—M’ê0üN 9 ÀùI$wíÅHžæ=ýÝŽ±âwÉÓEöŸ¦Ý°àÁså²³2¯PÛ“8ã>Ù¬û¯Ù[ëF; ‚Ïp$aqÊZº>0yã·½k^øÓÚ]µ–‘e£6¡«[=»jW’K˜aûárcŸ”}s^ ®øÓR’æ=sB °ÜG$å ƒå™ãÎ1Ž$â´„Dzk«I¬ê ¥åýµ…Ú¾cÍ‚#9NQ€ç’k³ñ…ÛÀ^Ôô™Æ³Ÿ¬1fLºOlTJYƒµåö5ãv:T…å—‰¥/üîèç®ãæR:ó\.«â Z-l’êeŒÓ[Gó}¡Ä)œôÜ6ñêqTâ€ïüWâ/0]øgM˜¼ZW”m]Žàö²s¹¹Êœ­{GôÆñÍî©á}\Úi‚ÎþþÚYâØ³^ið︳;È(ò.L||ĵäÖŸJÔîü=â{;‹/Ù~æóCº‡Ë¸¶»‹÷±G.½Hðq^âžÕçð÷ÄB`ÒPÓì[)Hå–ï0M´‘ŒFAߌàuíY´4Î÷Ãw>èº\Þ8Õ¬-e¥ŽøåØÞ/› ñ²çx#å`°¬_øËោgºñ§¢j–Ñ+ü°cìÖ±M÷ßx!U7dŽügŠ×¹ñ‡§øV<'ª\*èšPŠíóO¥Ç ¥b€7 ÆÀz‚@âµ¾&§ñÏörÖ¾3¤>#7÷:bÚYŒ›‹ ÙD~VsÊ€÷Á5Ï6ð/޵»´}ŬøTj:¦2‘ {yŠÜÅæcD|mèJ’q^ÁðO\ išůiòÅ©kFÕmØŸ³[¦¥1„[’ü¹.;]~ÉðîâÏÇŸ •„º³Øéëgjs-µå­Ê mäa7ÚåÊOZðÄñoƒÏƒþ!øEšKAukv-¤¶È@ÑÜâ=¤ô`]1ÐäÓõ…÷ìûðoÀŸ4·}RXõWQ†?/ýJM4HÝâoSMØç=ó_hš.«ð÷P}GÅÆ×WðõÍÌZÉ‘AhÅôÁnUH8Xc,[k>÷=+ѵÿˆÞ2ø‰âÛ_yðê©¿¶µ[ÕQ3•]ª ºï$pOå^;‡ü{ªëž$6>dZ^Mö,b›{¢#ƒÀÏ"n9ß·'­g|oð‚¯í kà,˪iöö‚âk‰°ý‡‰âŒ7,LJìêAÍz'Å/k> ŽûW‹JýÞ½,6¶¶’©ßýŸl² nýæOAÅHÞ<Ð<¢Çá[ÿƒºæí[WÑ帎ÆãçÝÛÊ¢¶Ü«©à`•CâÅï…|3ñÆ6Úu…çö?Øm¡ÒõI1Å4䀑Ðb3Ž:kËþ"hV¾ ·ñ^´ñÚËá{é­ôëÛ7å<åɃèdT-í]}ÄM{Æ ´4»/KññɪZ͵垡n>iân{iT®×U>¤â•‹[øÁ¬ÚxÎ#Jñ–ÑøÞÊÓE¶×´Å¹P—QÚ«¬OB¡ÜXýïºpx®3WðHµÇöO‡î5[Ý:%ŠKkW;żѓ>Ðä+ÎÒ©<Ž@¯Hø“á‚2¶ðMõþ³‡n´=ÿM}xm wâ‘%ûÚœ8y"X›‚¸m§¨?6ëÖ‚ÙCÃú§–î³9)q+¾ÒrvȘŸåLg¡iþ5“ßÓâÁÓîuý+̳°˜je¼×‘p œáHü§·jéK+œHÉ!ÂÉÛ‚Lc¥sZ¿Ãß øƒQ_‰?ï>ÀÞh®­’Ñ-ÚþöþüLC*w­8|S¡kÞ1Žã\Ó–··Žhî•Føäe`§<y¡3‘Ô¾"xëáѺð¶€óO¡šuËDè‡ ©»°é[ßþ,Þø#Åcâ€Ò!Õí¿³u :Ú× 2ÇÀh¾féƒÓ¾kÔt›™€tsÆz[ÜèPÞMö¥Û´¸Ty@ç‘Ó<^=i¬ÁàßkRÏr–ÐØ@ík,˵nmnSªÆñ´änøÞÿà¯Å«;?ëÂ=[DÕ/â¹{˱äKmÜåqó>GÝ Šò| ×¼/®é^4ý›µ u GNðÜ2i¤‚9nV ð±ÈùgÚOÊØÝÈÍ|c£yÄÛ RîkU±ò!»µù4‡2&õ ÉcÇ_ -ð߈t/xºßâ_‚´WµñL6¶öPi¢O"&—î ~î×!Cœg( çšôßxgÁ ~"Þ]øûE–]?ÅCRÑõë`%¿ð߈Xy70œ·ïm¥PUÓ“Œ²ç·•|*ø‰áÛ¯k þ(XJž&°Q{¡êË—4wòeíea‚D‘’'P+¶ðÆ•¯üjðî­âë D͡ʆ t™¾{kË<O9É'#¨9Î+09©üâßøYÓg/Ǩëºf›6ú-åÉDË.JÆ\mda–UnXð1[Ö^‡öø–aÐ⸲Ô.¡‰VÊ餵´;n#ŽB6³ÂÃz€=ëµðGÃOxfF×`„ƒv6·¹ÍqÚÅŽÚŸˆ´ë­6з….5ÝRö$‘íV“M ‘æ VPÎIõ °¾| ø‘¤ÜÚjë–:¿ƒ®¢¼Ðµ:îÙ^î[` gL,R…Ù!ê§$µà^8ð“ð»Æ¨m´;¨àR{DÔ­Þ9-..!þô[‹+4|:6Çjôxê/„!¶ð¬z†©á«Oñ¦°Íqow<¨{euVBÀ¯16Œö¬ßþhŸ¼!ñ§Ãúmö­kyxž(Ò§µ’;kËa Û23®ÈÜ3óc@ ¥#Ðõûý~ÃÅþ † ‹ÿ jðM£ÝióMä$±²˜Åͤ¸Ý‘8qÕzž>ªø$š_€uÿ‡¶óø¢-f_ÚÝØÉc¨!HïÞÎÛÌBòZ;È‚°I:²®¨à>$|_ý‹|Gû5é·ßn'ðG‹WÉMkÂ÷êÑ+\/,;²¤N„KæÄÆ&Ú3†ÝToü%iâÝúáÑ®´/Äp +m¿i1Þ@Š·Pœ—(qò‚OQI¢‰¯¾xŸKšÇö—¿ñD¶š~«¯¾‡¬i×*öbVgKyöÊñ\ñœIí“VuO˥ϯøf=<æ“ZÍ`ÈL¾j:ä0ìBƾyø»ð‹áüÑt„¾&—Y𡿸´—tŠ“XÝ>÷h×åiˆqɳÀ©âÛßhÿ ®µ;_±é6O®Xj!if³Ô-ÄJy_-K*}©rí >&xõ éö7:Iñ_‡åðÍþ¥p–ÚN±£—Z9I覹_øá÷ÀønòËJÑõ {½2[OèK{-VÒdÙ—4ä—º=Ø­æQò3çdˆÜíaÇŠöÿÙÿãŠu øgþ1Óí´Ë[KŰԟSE™$N~Ï:J2áK¹/JùŸÆ?¶ÿfÝëÿµo€/u?†Þ".lµ-Lòè^f@†u‰ûe*UåÃayuýž¿hoÙ›âOÃMCÂ:‰m$HåxlâÕÓì³@·„2:îÚÆ&n¡¾ëñÓŠ9@ÜÕ´¿‚7>3Õ¾æî÷B³Ô^yŠ–½)#ešE8̰¹,®z¬‹Y~6Ñ|§h²Ý[h76–^¿†Úy¬åóVïI¹Çúdh3“mθ'` s^·7†ï¾ëW‡Â:¶•«Ëhƒ˜˜3y)É*2w¸äŒrkÊü ?мOàÍk]ºÔ,îmlËB²î­<Üùb@~Vˆã GÒ .×`±ð¹»øc/Ú#71j¶V7VæM?V±c¹$…ÆWc¯%Aà¼W°ÛèzÏÁ'Ñ/üEá¨bÑg¾ɦÌ̲٥àcÆp7G´R¼¨Wsá8-õƒ 7ˆ®-çŠÆå[F¹.«&™tüI‚¹çË$ä€+•ý¦5/Ú;Ä>MubŠêêÁ’(%M‰""äpqƒò”õÁ§¹-j3ÆÖþÖ>%Ûx«áœPëïo<0‹°vMj …ä(yå¿ !å—ƒYíã]üT>:ëšv¨ë5Å·ÛlÏöUõáÄùpÎOEf;»WËcÃ?üãÍ3Ä÷vv×òÚZý¡.­b³ÜEr6´S¢í $g œœðÕîz®µñª]=ü7á-;R·–e2At…Áp£ÊÁl˜]”×-VQÏøoàuá­LϧIp Íigu…¿³œ¿Ÿž78ÇJ©ðkQø‰áÞ|-Ô§QðjI-Þ— éßäÛ#›¨Ê’1’3¥ze¶“©ügð>¯ñàêPG§Y¬ó[E†ãQK\6a¿gñù˜Å^ÓgŸ…ÿ*ÜCv·W–ò!K2æYwlû/ï#Úü0lŒ/n«GÆ׈7xŸ^žmI5¥‰î@ˆZÎï”ue#V>ñæ“Â~/ð׿ i6Þ2ÓíµÆÈö7s\¡ßlQAt ‚X?($ci Ö¹àý[âoÂX<9à¸Fªønù.t½j(ˆ’h)2JÊ?x¹9qõ§Ûèzýߟ øGâŒhšç‡î¡76Gä–íWßWŒ‚‡€:3Á?²—ƒ´MJOø·Um*þÎÕ´¹êÜ›•&9¬gu-·Ì71Œ fxcÀþ%ñ_‹Å­æŸk¯>£dmf±ÔÊRŽ%Ì2£•˜€`w){zwÀëžð÷ÄO‡Þ·´ñf}dºÍ½Â«È·¶l µÅ³ðVE@ÑÊ9GÂð§ïz_‡üg§<Ú^­à¯ìýzÂ{©¦Ó€—h¶.nè§*pIP0G4ÁËðÃXø‡·¢k‡ÃË­ÙG]3~öíÀÜ!-*<’â¾ MSź\fâÉkß K,7Övµ®võhò1×iÆkô‡Äßõ–çÄ×66Ñ/‡æ”]ÆÎY•;‹…îAñÁmÇ—^3ñwÅ/ GŒ<=cêÁò‰Ä…’”ÅPò å”ÇŠ˜OPøŠ@mq#Á*±Q$j;Nñœ‘šù?YñG†|yñ è|4ñÞ•zóÍqodóKo‚ ³*˜›ŒtgŽ1@œOühŠ/‡W:Äm.×QЭ­¡žhØêz^Ç E.zªôWÏî+Ù¼+ðÛÄ_|Qeñwáz?µHbG»ûâ¢àbUbUÊaò |àK?ÝE¨h>&´“íL-ø‘ÿ~ö·,b󟾀œÜŒu¯xð¶øo§xÚÑ®$·;mn‰!Á?(倯A#4 ˜äþ(èâø£¨ø+ãÏ…nïbquo©ÄKs m Ìaƒ*©Áä{æ²õíJãáÔ7$»K¥ðÄÖëÝ[;?–Ž0åÔgrœƒƒÈÁ¯¦~/|a×þ1|ouðí,õ-;E‡O¾Òu̪þPl8Æ ²«ðFNF1çzGÃOÚƒOÑo>!øGH·ÖìtÔû=Ö‹æ#£GÝsûÀÀÁÁäc4ðg×5m7B·ÒgñWz,Éö{˜WŒ¼®‚ ƒŽx­{›KÿÚAà‹‹ uf1@â9<Ô?ia½K‘4‘DÛ¥…"!™ca×pÎGCÒ¯x§ÂOö¨´_ë°jún§Ô­7€Hr Ž¡²v8  Ðõ›Ï/ü&ói-“¨»ÙOO2•Ô¾0x8ÛÐtâ§ñ_ü9ñ&oXk:¢Y¥Äk5šNòiòZIå5¬…‘GB69À­{Ãω+_‡÷ÀV8m¥ÕÌw‘<À”†tlù‘œpX ­Î0kæÝKâ>â­~ãÁº~ûwí ÷ºlñk•Šhؾ'r°È> ‚)4×+×¼[áK{k/˴˜á…fòr8üy¯'°ñuˆmí¼Eãà°#]5•½ø$Ýø)*’p7ð¹8¯¦5ë^0ðï„üwá—ÐxB3is̺Ã2†iÏ!XÇá_?iž;ðg†üQ­øÇÚ4:¯„î។ú)›ávÒo·s­ÜhÝd¹H,d›’ƒ‘gïnõé>3WÁúŸØÿ³54PÝ+I½$^&¡ÈÛŒTßP<û[ð%¾‡}ðëR€øc^Ô- i$„bî,}õÁÚÝ9+Ëÿg›-Sá=÷‰<)®B—î›LörewÀIWºœ~ÚøëÄú‡íÚ¼7~mõ“g¨ØÈJŸ 1a,Y`AÀ=k_]ðU·Ž>]ø£S×ÝÙ[²Zjhéó"“˜¤eÆvžãT ÿÑö|Vо%ëös|5øhÞ ÐôÈš8¿µ/¾Ù©\#3]2ErAÂ#¸ç# §eã —†WòáUƒ—hÁlÛ9ïí\7Šu§³¸]2È´‰ Á$áœzßZët? G¬xaίu¢ž"µ·8r{î>˜ïšü¼ûC¤[ë Ð ·®?wÑIþõæwZχ¬îæ„À²HÀ"Hã''œ‚r?Kªè÷úÃÃáhí´5+$B#åæ@Þ åϳU«¨¼8mà·Òm–öñO/æmÙÁ=ºñÓÑn95xna‚ÒA<Œ-oÐ+€ŒÇ¥b\[Ë{túlrA*D0|·.=÷œ·¨ÍmxkÃwZ²ÏkâÍ^(p6°&é9ê B0;úVv£áÛ/ Ký›¡´‘ÅÉ^§-ìz 6NÓì,ûUœÁ)*€eä#²ŽÃÜÖ~ãkÛ$µŒÆ±ÆãˆRIë¹½‡4šO†&×nCk2xÏPdÚOlÕÍwÃú°#Ó,O“l¤K 2qØߎsV§ü$v]»j’<³ÈÝ^p7?©®³NºŸP¼{ÕŒLJ†…e`ÛvŽàç8­[ÏøzßXµ²Ó4ëcåEŽ ËÉ@¼±ö¯2ŸTÕÆRé7'ÍgBÒI…\ã8 €1Š`XñN¯ãkRîÔTJxË9ú× em}©½rŸgc™”€å» ýqZøšãD¹6•t$dŒ‰XäÆXò¨§—côÅiCa§ü>ó"½¹m_Tº}óÜ:áV0NÝž€ú}j­´pZØDf»Ü9(ùÏ™ÏÝn#úÕWS¼²ŸV¼‘dµPD¾cfLžƒ`è;W'¬ë¾1Ö~Ì<:‘Ã/*^H·´c¬Jx$÷ô ËUÒü&k—ù¥sŽÇ·AÚ¨ßG¿¿‚ÓF?Ny8²ZIOʦI$àøW‚½÷‰4È…­Ì«m ¬f I‚ xÝø×®ü6¸¹ðäwž7¸º†ÞèDLz”ËæHXL(ØQ!?Å‚À:Õ¦dâ–ÆL?è$“úW ®¥Þ¡uus­­Ê™ {†—öƒÐmäWާ4÷"¸»»‡MžážFì츼sÎ㜷§µ_ñ¯iöh´™o _¶¿ïdg;¶.2X“žGjb*ê^>Ò£ðïƒl`Òíï\I+y“\IÙ@çó#º¿ë–Zt«}­G6³«†Ý$ELV‘¿,HçñŸjìí4ž1²¿ðΚ‰Ù£é@R±(ûØ“'žÀcŠÜñÃOëñßøˆ¤¶öq Æ åƒÏ'¹íŠãµ¿‹_lÂG£iÚM08v-P9àìà’A<8ô¯*¸¿Ôì®×[ñEl$‘RHĻݑ~öæäàôÀÅtšõö¥†½zúM¬3¼g21òå=¸<ô®ÆçKÐäðämBÃO†A+Ü]eîŒXýÕZä|câ=GÕ-µ/Y›s Iûöب£ŒàôŒry5Îxâ–ƒá­2mf[¹Œr 4.;cþY†êŽzµÖ_\·•ggq¦‰žèîÙ)F’8Oïå”–ôÏzÕx;Äž3ñ…‡‹5KÈìl´'Co 4{äá ÇÊ0J¤u_.»o-—“t ‘g™f h ñ*ë ŒÞ1´ƒMÔÖq£Ûåãò·E $öŒ·Ê7zŽM[Ô/?á:¾»ÐÚ[Ü$ÑÍ),n&º—€¨ª£ïuÅrÞ8¿ñv»©Ë è÷÷^&—LæXãEKX¦N$ …*½XóM+j7vwãÀßÙ–‘éVÒIåg•Ø‘–n=Ívž,iVºú5¬“\ù04ÊU,c å‡a^Kàc_ðýÿ‰ ’êþ,4ÿh“1>`¨ `ÿ1]î™ãïühÖô›[m&kà¸Kxç@2H~o/#i98úSÌÏxGÆ>#ñdךÕÖí>ÎGBÍ— s´ÏÝ=yë]GˆõM?Å××úV–‘¦£ÛJ¦æåŠÄdpÏ“÷‰ä(àÍz‰ü1¦µï…|@Ïo ˶I vAÎcÞ~„öô¨-5çÃVú>»¤Ç ºüÜ´&ÎFâ~þ1Œ7~Ôç—ü3ðöƒ®èz^àÍ?m¹!Úáò†á—¨9ažßwê:[\éZœozo.'ŸÉœªQAÿWî~˜ô©|%ãHþ&øÂ)ôk)Nµb–×L bUL©H£|£žw_Š´?„÷wwÚÖ›q¨aá°ÒcO=Ãç&I¶~Lçwy½uŸPc%å›HrC´“·î–mAö¬[¯Ëy¦\iö:<š‡Š.g–RëŸ&4NŽ‘ÑÝã\—ÃMC\Õ5Û­7KÒT×ç’/°ÙÂTG¾bw¹aÎF7³1àf¥",}âOøbÊÞÛÃɦK(-*¯ðüíÔàn|ûV¿ñ¾ÃÄ1Ãk«ÛÜB¬<Ø]§(ޏOóí^jÞ ñ²º…Þ·8•#o#x|¡)ÕS¡v'þ:šì´[K§Áqàià‚[vmÒ¸þ.2Iƒ?Ò¤ O x›ÅŽáãZ}–êò<ƈÅËê<ÏQŸáQ‘Üæ»à)<(¬&·&§s¨‘ÅÚe¹_¸¥²~U'·$u®Ž;ýÃWphÚ9ûF©{ûEÒ.éåœ<˜÷zqÇZ›ÃÚ©ÿ ØVW‹u3G h_÷Ç…Èç®H=}èš´ðG‡®µÁkãëèîõy1tñb"RH'œ(Áœæ»kŸèº7ˆô¸¬/ÞÚÕä$·–< ó1è1Pjÿ ¡ÆëuyK¬N±5ŤÍòÂeˆ™ù\Ž¥AÎãëZ:߇5j‘AªJºU¤ËGÝ SË1cÀÏaÉ9⣩iœïˆ5{û’·ÞˆAlg’'¸‘9—y¸þ/Eâ°¬¼iâ›3«ÛL%0[’ÙcÆ÷Wã Ç…Ïr:W¡ø‚óRÖïâÔÚ%[FA¼p'ú¤ 7ã§ÞÏJåõ]sþ}vEÑÌ7­‚A•"v/‘Ôã½]ÇsÅ´kÍ{KÕï-vÚûA»]ðê0Ok"FÙ®ç_µ ZÛö¼øeeªÃäÜMs« DIM¥Ë"€¹Èf8Ü'=«ö~ÒÙlZ` €È6äqè+ë0NއŽÒ±ü^þÊž¹±¶Õ>Ü]C~¿ºÒšå•™®%±slÍòœÏsÛšû>æÄ^2¼:B¹xU"*Ί6ü¥ºå¹'5áð”¿i_‰Þ ÓgQ—еé!ŠœOrgÎi˜à“Ö¾‰×"´º6ú´ÆÞý LB…P`íØ½ñÔשø~ÏFþ˃íVñÅ­Wï n¾§Ô×:Í}6¥- ß*àÍ#I÷Aãñëí[öZ~‘¡éWWZíÄpÛ˜JÇ mÄEÁàç9¤ö!„ѵ—E°à‰N=Žr·Ó­biÞ·ÔÅæ±,ÆRUŠ’`‘õÎ8ö¨¼!s¬M§ÿlèÊ-¡UŠI%ùІäò{ãúW¦hÚn©¢¨ñµ$3ØÍ3¨FLÊ黃Ç\Ô$>··þÒ'wh‘—Œ‘ÜŽŸC[Zž³my.Ÿaaxmʬ¿l¹x÷8^¤õ÷è*k}SD—Yº}2y"ÓÉBŸtc£ ªþ7Ô®tíÑôûq Ï#ù…FdhIɹ*Œ)¢®iØxFñ‘6Ÿ*¤—…rx7 Á ½ àדÛxÓÄ^ðýäWPÇ,w2â2Sžã¦;_BxYþÔðæ¡¥ÞxnAªê´C.VÒÌ•8aÔ3a‰ö5ÌxãIе§Ôt=:0ÍÄI#iÁcôhÉ4~yøïáö•â_ ^éï#ϧÊï½Êî!Ý眓׃_“Þ2²ºðf¹wo§$µ´JÅÔ, ÃÆ2#'û•âmSû%eÓ¢T{eÈX@Ø‚1'¯¦+ኼ9­kRÝøq£?ÚÖì×¥È m¤nÞêô®ÈKC’QGæÄdÒîí5Ø«æÆFT•'“‚‡Ú°”u:´=SÂ~*> Ô†¥¦Ã¨I4A¤Šñv€]HúÅÕµM>K„Ö„r)ºÚŒ¨6ÂÏ»vu!zçÒ¸MORñ¶»y/…5ˆ¶Ëqq»¶‹*áyuyõ­rÃP‹ÅöyÍÌ2<19;"ÙüÄç<{Ôò²K¾4ø¡eð×ÃúŸŒõÆií|?iq¬› Ü;@¤®G£¾¿<~ Úx£Àš‚í|C3KâÏëbïQn½ZS5ÞàÄçø¶'EPàWÐô-KÇ^×<5dÞç\Š5WTçý‡Guw ¸èEyÁÏücñž¹á_üW²²ÐÞáæo0¼—W!;}ˆÑ…rÌw¡qÕ¸,Ϊ#µ#í%ðˆõ]ZêI¤xí^_9æVBváàñŽ8ÔMà_øWG3iW'ˆ+(¼ÉYØò<ãÛµkèÖg‰ü©xóÄz³ÄÞ·Xtí1SkÜ»ÇÍb~TÈ gmx)ñ‹u ôñX­CÏb8ÔŠçh žOúÑfr·©¯ÿ ¥§^]]jÅí {pˆTœ²+å·ÌÇŸ¥wÞ ø¡ikgáõG ~ÎÌ]»•±•Éäz×#¯_E§X'‰õØFª!v’hÁÛµ”áwÉë\µÿ¼A⦳a b['óÆÈùòÈ ?ýj)3Ý_\Ô%ŸSð= ˆÂó¤`ãk¢Hz‚9ëÁíÆÞ øO,Ú›ggwq}òË~Ç2C-Ìqœpýæê1ŒW%à]Ë«OâYY–d2•n õ'¶ ê*µû¤·öº¼‘ÀÐË6\e-¸“ØéBÑ¥«xŸû~æÂÆöIcšK¦xŒû°™9Œ~µÜx&)|eáMwÂÞ{ìÝ&´ê%öº¬Œ@qÎò¿wÚ¸-Ã>&ñÅÅÇ‹õ]"[›X\»y*|¥ „Œ¨Çä}8®ßHýžôÍ7À~(ñÄ·ñé²xnè[¬ÆëÍAq˜Â“«œÈ š¦‚è×ðÅÆƒáCl—§R¾ß(vïëê2:t¯@Ó¯|!?ŠeÒõáq¨év¬“]G¥­EÑ S 1QùWƒxCBñTZ՝е¥‹OÓ¯|Ȥ!•Xƒ´sÝzTo¥xᦧ¥ëA’Òî\,£ii$;–@ ãÐ~U6at}ÅuãëýÅ §iÖ?Ù“xŒA¡;cH„˜ œ|«““ŠùÛâu¦‹7õ/ø÷Êitùš;7A˜Úâ?•¥ÏSÏsÁÅq^*ñ?Š-muKIžþëH˜m6“¸+nÜàœÐWÐßà Ǿ?¶ZÔâ»Ö/´•žîMß¹³(@u`§b~_QE‚ç“|:Ñl¾èßð‘xŽå[Ⱥ!Q>ã©9rÞ ôëž®ž9ñqÕ4x§´†DÊ΋¶=‹†q…íÏ­rÚ¯†|+¤è«o¯ÝÈtûyL3YBwM4€ QÎsž+Ì4¿ÙÅ£¤or·ý AjC!iciÈÂãƒÔý*Z¸ÏA²ð7ÄŸxŠi,b¶Šöþ0&Y\C yxdœ’TuÏ¥zMŸ‡| »Úx|Îu(Av6À}ÝÞ¬>†¼!54ÒµWTñ%èÒÞhÚW!ÚFT@êGZÐøc®Ãa©KÎÓé÷&[¹Ë"¬g’ݸç>ô’Õ¥øTÚçõ¯øcW67:[¤É§“ÖPÛÃ"µÈÀÆiº'Å›¯øM4oIföÇS[¥Öìa_žãÈRB1—Ǩ'¯áö¾-²ñT“O¤¬öcÛìÒ b>T1îìdœúV·ˆ¯u9|u§jÚd¬oïšÝä·ÆÖ "¨û¹ûÄóÍP®Š÷Äyå²Óüãï"Ñ/ocÔ¤[|$ à•,ÿÇ„8 €ע隮¥Åaâ/Kí…ì³,poÄ‘lly’~\tk;Ç~Ò5­VÊÛUµ’#knË5¸cþaÊzãqÎ;Šö_…|¤i³K©i«x/®Ä‰+°&`LÅ$¿’Ó¸XHÚªpí"w©<7«Ö¥ŒMYž}J6Ôõ_Ú÷A×¼}ûDÝ]3oÃrÍíub7£,¶©ò¹%€2{×æí¶˜°x| ñÃiáOøj(î, M†McM½wxnr2<·T‘rÄ0+œ•ú™ð¿ösø…âÏžñ?Â﯄庆æ!}ûCµ¶Z•L—$©ä¯ë^©þÏšµçÄ@>'x’ë=½È¹Õn£A%½½¼f8`S¢²)vp§<±æº•E{³>S¢ýƒ>ü-ý±þ'øoà« Z_Û]ê>%ß¹n/¤¶eòl¢”à ”3me%p„c'5ï_>øOIñÏÅ Wà'†õíWÀž›I´¼º×~Ϧêjá^8›«¸`›î<å¸"¾ð7‚~-|¶°±²ñ©xau‘qg$(Ö—¯$ƒpó¦ÉfTê1ƒÅ}¹ð÷öæøŸð³à…÷<¨i:Ö‹¨x–ïWºÓ|Œ^<èVX$wÎKòœZÓ™5¡›GÇ^|Kð_ÅOá«$}&i¥’ö Q SÌ¥Y›åò—~ œqÅs¿ -<-£ø#WñˆÿÂH5[´°ˆùi7lYFÅãi—†a‚~+ì?‹_n>(øûWÑ ’ÞÃÂrépØH–³Ì’íØÊüà–VÚrÃü ·¼øOðñ ³¶þÐÉum§›•óîZ;ex¾Ô¹èF ø…ö[;­bk;ä‚6á-ä³Ý5Â0;RÙmv ÇœWžøÓÇðŒêv_Øǯ\kÖk’2·ÙâŽÜŒž ì œq‚OS^ð/_ñ‡íð çà‹úD¾ÿ„vî{«v–v´™ó4É!^rÙ$žGJóKºŸ†õ‰mÔÿ 4¨ü?§iˆ AíÄÙ29\n1ÆO}Ø<Švçkoh>ñ ¿„|m«ÙÊ—òÏ«XêZxiHdQ-³©ÎÐW ç8ö¯ ¼ ãwÂ3ÂÒøQa§}«]Ë`<¨|ØÎšpe P»(¸b°ü¤j¾ñîŸñÁ¶kww®ßÁœ-mçCnoAN¬07Ú9úWºXx3â§Ã߆úá]ËJÓ5í`éòêLËö©ç‘L¥š>vDŒ®7dÊFÑÖ¬ðÇŠ|cá/_ÝêVïf¢ê=Bk9âòbdá%_]ª¤ÕËÜ|1²Ñ|M¨ßYÎí¦Eö­B{›Y7Å# óšvž_æW°ã5ïž#_ÁàK›ˆo§øPòå’xb\[ $ÄŠ[?0€î\x¯1ñO¯Ø|tÒ¾x–[+_ /þ m?Mœ¬ fã îãh2󒼂(¸¾2ñn§su¦x{àìQéÚUæšÖ…ãœÁ+Íu²I^‡+^•åÿ4?ü1º×¾ézv…âm/WÓÖQp¨ÆkK™F£~¡¢ ¸·£Wª[ÃðïTÐ/|Cg%Õååº7›r‘7ÙàvýÔQdªÎ¥HÜÙoJàï~üxñ.¯oðãIÓHº7[&öÀy=í&ìpŒ2¯Ž¹ âWÃS៯>øžÎ)!„µ¬³ÆBÇ$W1)VWÛ‡ÆÌôCÇÚß…´ßx›Wð…¬"ÏW¶´²]9§ýäQX]Õ¹ÝÈ,{Šèt‡ÿüSàûïëVrXÛ]‹h5ï¼4¨_ bC¦èÂ䔕»¨Ç¤øãᮤ¿‡l4X|iäÉyl|Éî¿´pï-Ë2œ²½xÏ4çv¾-Ðbø{­SK“Vñв¶×ÇËym£Ý+½½öãrއµqÃ\ñ¿„¬/< {j‚Î ¨/nlD"Ebø¤e\¢žÝ€N} á½7á~‹áoÍ=­“Ú]K€ZgxP°ûÌGQÒ¾…}2ïÁ^,ðï‚RêÓSÔ¼~º…åìÖª¤˜!JA&sŸ€NÇÓäß x®ù>ëî–ú—›q¹_ ‡Î!Q"c’«œ‘Œȯ³ð®¯¬øbûÄz¶šæÒ×Ú\G&H½Îðq‚AÈížkÝõ xOÄ^'°:õÖŸ¤éº¼ƒV½‘ˆ¸Ž/)°ƒ߹€}9¯ðݪi¿ÛDñ‹\*C4óZB2›Zi nqüGo#ÜœU\—áYñ]„Í`t+†·¼žævqäíQž ô8êx­û„úv¯&©ãZ¥Æ‹áO²j·ÒFÂ)­¢–ä!*  wp:j¿ðçÀ^/½¼ðþ±£x‚1g¯,f£n¹2ÂÓ1v&"2X’Fäò+¡ð÷ˆ­4Iu««Mˆ#“Mx`m«;[\`+âS´=M0=gÃ>ðmωt^>ŸS°Ò55î°¶(E×ödÈқȆ3ˆ²»‰ÀÇq^ùñ‹ÄZ&—ñÅ? þø©ü]ðÃÃ:Lrxbíä$“F¨Òm8L 0.3ïQÂ]ãßëºçÄ [[Úkž9Ò%ÒH|VöŽwe•ˆ<þòŸàûëÝ"Å´íf-Ä%ï’+]ÞLRZís"ŽÖ#œµ6×t_‰3|C¼´ËM?FÂÖÍqª@zj6^|y8QÊÍѲJž+Ѽg㈿ üsuû8ü–MkâèüC«¥€ÒZC«aæ†V@U5$:ƒ¥sÁÅ|¿ðbo†Öž"†oˆZMÕÞƒ ²A« FØ'T~RUöàšõ¸*¹ÓìYà ”#8’”,ËÔ»X óŸ|[ø—ee㇞òõŸkò]èÒZ]‚ÊöÒ¿Ë5»¾T¬»¥s²]i(Ó£¹´±ŠËZ:t©§Z•Ñí@UXÛ®$Ö²lt|xãLÐ¥-eup–Ï+ÄvG+Å"È&U<pCg •¨løgáOG¥|Bñ.„‘8šxtÔ»lHKíØ¥3ÁŒÎ8`x¯¦¿àÜßÿj›Ï‡Þ$ÑÛVñ[M9£U’1,Í&¬gˆ¬pF;t¯*¹žO|gÔ¾'ëk>&«4ÉžH½6e!íÀ6 !>øë[ÿ®¾|=ø¡cñ[ö[ñ5ݾ•$…•ñ”¶¡¥\ÃG†Vq’RFe*Àü¤qR¢~ÔÿüañCâ»ñ¦M>tïÜ&§¦Â±y‰q¤Ú¦œáXÝmÓ÷™Ã¸è+æ_§<ðºÛâ4“êê[µÍ­” ¸\O*ÑIžÌxÉ9çŠéï¼_s¨iVúmɺÒe³¸Ô® }“RÔf"íð òdä aÉ5KľÖuE§hMçi÷¶ d-Ðââˆ$2ep1¹Ny,e|BøsâφÞ3×¾iìF²Ñ|Ai¨Û†ˆÚ<òä .pYUŒ×cãO‰~9¶ø§«I:tº…ÝÓÜÝÚæ3ȬÈ?ÖÊ©ê"µ¾(ë2þÐ#ðoÄ r=3VÑô™ôÝj!V[ûgŽH˜ †”€H ¶:Šîu íKáçÃýEñT¶7·/“ûGFÔ7í•"‚ݲ”`©ÚË·vr@ ž­$ñv· wE„[ëékhöº•û»ù˜eB?‹t{U»ní\Ÿˆï5/¦‡ám!–êG»Ò≃5¥å³,w¿ºCã#Ò½Sá™Õ&øsc—pöÆòsÉ÷B.¤vÊîäqƒ^[âk¯ß\h:„16¾Gî¼Í—1“ç™1ÿ=°¢EüO­xƒD×SRw³Õ´íJ û Âÿ2ÌåÝßÝš,ñÐÕ믈—ìîoc^k´¾ÿµ ª~„F+™ø››e¬iV–¬Êã˺Ý&ê)—‚=UOQëN±µíjÇS¾#Ô<*¶Ða´Af²Âó× 3ï@ß/áñX_ƒ^/’ Ne’P–ó¨Ò΃þÉú‘_hÅañÀ¿|âÓCø 5î‹i¦ëHJÔæ´ˆMƒÈ°“EóG2d£`Ø*~.ð™ðûZÖl<= ŵƷËi𴤫Ç~£åŠFÈØí’«œFG8¯~Ö~.üañOĉ>%üT–=fOÙ½®›h" ½õ²©óQºF^9çæ<㊖ŠLùÿYÒ5ÿŒ:Ä¿®ô£á©|kâmMeÒ¥!¡‹X³ÐÆ׉¤þðÀà׳kzf¨øãÃOŒ´Ÿ´´Ö”·aÿx_ì׈NѤŒ>€vÀØè^.ø§àË=BÓU=„Úåüó¼Äª¥ÕÊ´’ì%IœäòÞjþæ™ ßÝK$³_¦‹|aÔ ™í\$‚2OGÆXjl³ñ/âGŒ<_ûÓu#HŽ+´Ñ´KËËÛKÀq*ÙÂ7Ob?|ÿ(+Á8÷¯ ¹ÐäÑ4Í_²Õ¤™ º7ö“Ú"¨xÞ=ÈéÔ¤˜Ø  =£KÐ>jÚÇöŸÖ›ztí;›Uw1&Mž\iüj„†ö¯Wøgâè<2Ú÷Ã]r+³]iÑEy«Ì¥‰ü¦–Ûa=nÚËÁ*kãÍw]Ô|ãý3ÅšI•ï,B]yï&BÚ¹]á³ÎÉ#$é‘ê+Ó|cÿ ÛNøÝ?Ä a’ßLÖí®àÕ#;š §»‰Êá—‘ ޱ¹ ÂóÅo]j?¼/ðæ×á‹f‚Òk«H%šÛËÜÜ/,»—¦F89< é|/â(|Q6»áË¡i%Î¥Æ>Éu Y‚’:Ô0dQšÒЯ5/Œ>»:ݤRiöË-ýÚ>a2²yJ{ãÌ@zò+çí=-n4Ëè“-ûéïm¬ÃtY%ŠQ”•ãvûñH í#¨Ð\Kï„õ+ØÆ¯ocqiqåxå¡g\²¯}€žÝ+éo®³ð;ãøû]ðÜ—zv© GÔ,¥V·¼‚XÁ‘AÎÖmªX¦Aœ^á¿ëºr7€!iÚWƒü"ŸÙ·^Ò®¬¯%¼BÑËö’FÀŒ #‘~SŸ˜7Ò¾nø¥áß ø?UÒÓáE¬3 GC³ºÕ´Èd2,é¹nš6êB¸SÎ é_ ø³©ü-×þ!iÒíüQi~²E¶åEÌ7Ù.±_YœpëŸzñ›MÃ^Ñ.¼KâKIõ-+Ä7[ÜBŠ­s¦^4{—,ï-Ѷ=HÉ (ñ®£ã­{ G•t«Ë[Öxf”ˆ¥óþd` ž¹Å\ø‘gáÏkžѵ^ÞuŸLÞÞZüÁ^á÷*)#¬L¤tèEw?t=Uý¥uÏí–çKþÎ…cò/"ÝD‡'¦ÜŽ{n¯<ø¬x&ä[ø7Çvv×¶úý¬:-”—yPâyÖKõẄÁ‡!—Œf€.Øü3ð{øÊ÷ÅÓø¢ GÂÿf¸¶¼ µÝ.%Ue Ÿ¾€àõü{·Â_ü@øµ¨nx^{¯i‘&¥§_j. šâDû;„}Ÿ¼eP£Iæ=ëäˆþ׿f?êz­{i®Ï£ë^Ôôýþtq_éŒ$]`îUžÝÖHe1V2÷ÿxÓÁ~&ø›á¿†Ú¼þÒí K‹eÓ¥.mo502« !¶HG =ÅxWį xÿâO‰£ñæ™âHçS¾v½¶D0Ë É] ?®Ó½p99®£áOïéñè2Ã>¥™-gȵÕÃ)H®îÙq_"jÚÏô?éÞºÛ‰4¡d{§òî-¯·~e'>£ð®·Æü9ð׃¼;ãï…7Óë½ôÐyRÊ»R@ÝC†+׸¯Gøeð¦Ú÷ã•¥µ•’j~mæöçMk†·•oíŽÑ×̤§ÔØb|øãá2ëÄŸ³ÇÅ„húÝŒž ÓkHdXÙ¼¦R~hd à(ÝÏC_NxöŸønþ[ å“Ä+´‚ëPšÒ mÐÍmn2²ãÊV16»+ÔWÀ3üHÑ~êWÂ/èâka=÷‡õÕ‰f–Êöß 5å»pH*à©#!‰3ÎüFk}"óBøàýRÆ-sU°šÖçQ±-ç‚ø†Q$,6«·Ì¼ôä S³é?xoàWíº¶þ+øyr°ê·L«ik©@Ö—Ü1q˜ù$EÉ+’ŠsÚ©xâ…ÿgèü?áŸË5æµ¶m§±’{uóp8ßò‡Þ^œ×év­ð¿à-ׇ>j°Áu¤\$€´‰æaÜ[Ê:ùƒpJâføßðçTð:/‹tÄ×õÛ]z-V nÎM·Á¢Oâ[Ÿ”q“Šj,¾|a«ü>µý‹üI¦|&‰TÙxÊÚ}IçDó k»‚æ_“,bI íoáVäñ¿‡ºÆ¹à[;â.¦¶’/®å½[Yð~פIÿÑÀpw2©iQ?‹^Ñ¡xãáçíðêâãFxôŸÛ-Äs[[â(uël,ŠFp¦tnJn;±Ö¼¯ÁžøwûGøóþ{CN¸ðo…ˉlüÓü-”r”ex¤8e'x9¤´½Àßñ.£ð÷Qø•â„ømöO®B±µ .x’ö×â4Â…vÇ7–Ïœã$Ÿ«W[ðßÄO \øÇz;èsxOY¤‚D¶š8·@s‘òŽF8#ãß¿~øoÆÚU‡üE ”\–¶·M„¸·hnÿwèÑËòŠrs^¡©Ú£Á|W¨øi4ýKKð޳m§ÜJ¡îu(d…ÕÏ’»O›бXËB°œCVØ»÷ìïñs˺«m§‹{‰í?áÖc%´ûØ7Ú!318drÉ$D àdq×xÊóÃ_¾7ϤZü+³Ö´k ˜áêÎúÕÐ`‡1¶LŠä*ô?^CÁŸþ~ήüñ_†­áÍR6šçHºƒoÙæ»už;›Xßd›.¯åîÚà#æâþøé‰¯ÝXþÌú“JÚuH­îÏœé¶O™7ä™CAÎwòã­C=ÇáM¯Âí{âu¿íû?YÈ—ÑCq§ø«Ãò®^¶ˆÂÓ+)ù…_˜„®FÖ,|@ø/âýSÂú¦­â-|O­¡Õ#û"´z™fÇš˜ Ë<9çô"»ï‰ÞøÇðãü. &ŸágÄHnn,­œ^h^ ´ùL²ž —pÞJ‡ŽOŸžwPÔhÙ<¤x{Å—ú¡}áGžÿL»K3æê}© Ü[;!̯>e¸}{lokAñ'Æ^âGÃo¶Ò]xFçJþϹ°’™Éa’7à ©äGzù£öžý‚þê <}ᇾ…|Iqhu_êÖ?è·R¤R67JHVMêbÉw‘Í}ÁãøF~%|pƒÂßo,XßIá=JK}COÕBÈþÔáKˆDr<ØŒîRq·8ôÅs¸Õ¾ xÒòÇÁúÕÁ‹NgŠëMÔ Me%¤£nö;HÀ1”ÆÖ™ôÃþðÿÃê^4ñÞ‡%–•o¤%έá[ˆÒ{t½³”Å$[ƒnM›%„õùnžA©~Ï~!ø£³™ªXê:N‘4wZbÏÂ\ir9*žqÉGYT#’Gå_þ%ø§ã”lã•íõØ[k‰$IoyFUx?už1¶E6-¤Ö°ÊöÒOæÖ;²«ó(1’ gžk*†¾Ô|-7ˆ<#â{;{«mkGçmŽW`X.áÌm· ¤ðzWI~ß5ÿéŸ ~$ ‡ÄVÏo ü‘‘çbNøáÁGAÅp¾0ý—4ßx.mKÃLu¬–z¦›$¦g6ĺ€ƒµ€¾ðê€õ|7ð†«§Ëá_\\hº§ŠtÀ—3i-ä“>ãÐç#Üu¯ðx—àGŒüNÓ_KÕ ÑlntO†T¸‹•ˆHªt òJG'Šã¼¡x—â§Ãh^&°]#Åþ»š-e%],b]®('FÌn2¤‘@ž(øƒcâ¿ì‡O¸¶ÖÂ4Øä,¡I÷N1¸tÇ ó^Gá/‹?²ç¼U'ÂßßÇaãÛ6+G”¬sØßD¡öÈ€‡åJ’Äcž:×WðÛUñu…ê|Nø™£Ëö +s¬Ú(ÿIŠØ¢æ09ùåzœ¢©§Ã¯‚>9ŽÞ Ómo/´û©`¸½Ø!¸x]vÃ$ƒƒ()†«3@Óà-7âW€Þ/Œú5Ñ–;»ˆÚK/1œ rVMŠùUBG$óÞ»+YÕ¼;ñ_iš"ß^\äF¤F‰pûr‚0pTŒÃ5àž$ð/Œ|7e%ÃYê¶EEýö˜ìß¾HÛ'Ëc’ޏ6+œÖu_ˆ6Zš<«m«$qKÊ2Œq&áÎA@µÿŠþÖïm'øµk}¡ëž½½Òô»ÆE{é3%µÆÎ/1C© á€Á"½ÇãGˆìô_hÿÙú”Ootf·0pñðr»Ib’Ý_ë?m|)â£xÊÔ4KÁ$÷¨,Ó(.A“¹ê}:ׯj³£Á}¦øßš|–š.¿ŸÉ³Œ²ÛJØòGœ0“?}W$q úÃ]èr|]… ñ>‰æ_YnûC©o“ÍDzm$1çnOô¯†4/‰^ð‹.~êêV·Kj–°]îV?ëã\ç##pÕÄ_x{Á÷ZSBöÚv¤m¥[yÒMé|©Ÿ7É|ðèGÌk—Ñ~ø£âö¥¨iq\Giy6:µ‡î…Ô‘Ÿ”K“„p­Œg  ^øbúLj×SðÍü×ï²R-î)u!–Fèdï¸ýìóÍi|<øéâ+?„÷µ2ÿÂ=£ëQÍy§ÝA›&í ›þy> uÁê{q^û([xCáü‘øsã]…ä7Á(¸¿—)lî]K ‚Ê3Ã#"¥Ót¿ |3ø«xN=@êÚf®%˜Ú9 ñÜïßP:ã‘@þ Ó~øæëR±økâ;}3ű7ÚåÓ&ྈŒù‹9ÎæŒðO"¼â?…?økX™u(çMUÓT[êvB0¶ÇaçÆ õçŽõË|ñgÃAwygñ¢ÇUÕgÑ‹¦ëiá5++;€£j'V‹z‰ ¶áÛ·¼WûH|XÓ Ñ|!ñ^Æ-FÛE¿Xäy`+ylá|¦h¤É$œËŒ0è?ð—ø{âg…u/ Cy4Þ—p“ZIt<‰Z7yc,¬9ŸzMÝx[ÅËà¥ñ_„,鷺ØöÊDP‚öªªÌFæCÒ¼ÿ[´ø9ñVC­ßÉ¡ê×ÑÆöísAr㌹ˆàœÒ´|¡j>!°H-^.àŸÈIæ|Æ¡¾R³2”ÔÕßYEoá+½⦔#¼ÒwGûÅ\•Þ=Ì£û¤f”J‰“ªi>'øq¬Ûø¶ØÅ¬Âmp£1JC‚Øl“ŒÓšÃñ¯…¾h¾(´øÓ¨ê¢xKÅGL¼»½›E²Õ%\Åoª®KY ÏËÀ?39e¿‚~7jšE߇þßÜ][,m1Òï¡áD¼HñIÉûÀ×ëNÐþ x=5_øwÂz³Èu; í¬ñcš’ñÎ ç¨g^Cš(ú«àgƒ¾|ñ-LJétÒGËEÁh»qÐã±ãÓ|¨x»ÄZ¯wðRµ½½²I<¹_‰ìãÎõš,’r r=EH!ðýô‹—k%µôÐÙéÝ,äÈšÙ°ÁˆÂŒvȯxñŒõxR ø“Æ:E­ÃE7JñL®Boçw|7Ý޵âÞøQªøËDoŠ>)¦jöVÙÔ,QÀ†åeÏú²0À0gÓžkÆþ'Ù|zÑšÎçF†yôëØ“9êÜôo¹Ã Ï玽(Ý<3ã ãK_„w‡¼E£aîô=H$ÎN6–PB±k)äwïZŸõOº Ïî•$³SfÎ~hÜ4R7G$ä‰Éïï‘âoÚÁà·×šFÔR/2Ò{¨Ã³:´Œ3 QÈRNÞÕè¡ø—Ç:¢èðA-¼’ªÝÊŠ¬èÓ"“æ,d–ýh@pŸ tÿé1ÅñC½ˆw;DÊdg‡^1Æ8 z’I4 o[ðO†ËXXj<¦ÑåÄh|³ˆóÕ{ŒqKsðVÛN°»ñÖ›ª½´a3q !öÜ [ ÏSއ5á±ë¿ •Åxÿî§ý~Iÿ¡Wªj¿ñ᣺(äAÌqzÍ÷‰|+¤M©Ã¦ç»ÿT‘+‰[#²FB|T¾VO Oâ ‰ W,ÃÌÜCÝù€=+Ôþ$«Ó?ëÕ«È,?äA¼ÿ®çùÑÈ„;LñpŸÄ ¤ÙÙ½Û*Ò!#ï{Vì·V>µº¼ÔôØ­]†D“9iü#®ìþ•Ä|4ÿ‘à~ʺ¿ŽñêßCUÊ€åRo:÷ûn6(g ´*áˆþófµ5Xt‹‰-îå!$ ÂQÞc†=Ïy¬›Oøôµÿ®&¡›þ?!ÿ®tr wàñp÷·Vײ„v€{sÆ}«&êöÿÄ,ôKw³‹pyP™Ž„·;@ì:š“_ÿ&ÿvºÿÇôßõÍhå@GmáOÅdL¥­Ù@'aÉï€sùõ«÷:>§Y -Bf¿BUØ6_-ôÏ$zšõÿË_¬uå·ßëëýir˜È¶† -„–‘Çii&eWRL’2ñ†ì¤þÔ’Öþ)´Åb VX¡e,$y8-Žä~5ŸüŠößîKÿ¡Ñaÿ!;þºæ(åcbÏSñZÃ*\Z,Bß-$÷*Éå/^`qßÚ®XxCZÔt‹hŒÓ.òKó\œª p€ä“Àü+°ñÏü€5ïúã/ò®ßáOü‘×9¿öj9C˜á¼qàV»6ÿõˆ¼5¡[FÓÌÖöï,÷ãÅä=jǃu=YÒ—Äz]ŒÖ’_ÎEšê5’²ŠáI_0  ‘ž:WÏŸ?æ ÿ_?ã^Ùáùôoúî?£”9޳ė‘Ûj­¥\]²Äw,³;gù‰QÀ‚²õŸ t_xbÓQƒKXU-⻜Ks+¨ù¥p¿*#žˆ3´½«âü‡çÿwúUÛ_õö_õæßÖšƒ˜¡àyl¯±«iÉ*›~éfãæÉè;zÕ™T\xŽ{MÆÚîo˜´’°(ôcß±ÅGgÿ ;oªèFŸàù\ÿ»7ó¢ÁÌr¾$ð„W÷VÑø®v¼x˜:Aù0 <áñó0îy®÷Ã.·®lt;X› ?2Íê<€>µ•âoù Ëÿ\—úÖÿŸù[ý[ùUØ9·¾ øEµA©xƒ˜ ß#¿÷Wýœwì~:“â?|mmàÙ,v6Ѭ›f›jÈÎyg~äî•Åü0ÿ^—ÿ]Gò5ôw€¿ä©?ý{§õ£”W>sñ³¬Áâ„Ðôim./÷2áýìhAþxf_QV®$¿ŽúKZyoš2‘¢È6ÅЏ%ucߌWáÏù(–Ÿõ÷süÍz6µþ±¿ëñÿ4‰f6¿§øªîy/uI~ÕH‘Á ëæyiÆv@·=kF÷Hðå¥Ñ´[»†“$Ý+†÷P0e=‡Zíµßùoþä?È×ÿ¼¿Ò™&w‰´èײëúµ×Ú¢¸b‚ÜùqÀ|ª«ÆyÆx95/.5«\ê~5{Ô½Õ¯%%™È0?y°’áàf»ïˆßò ¶ÿ€:ËñÏü‹^ÿpÿZá4é­ôÍBYfº0$è¨DÏrNO\zâ¬ë·&¡âü¢‘}{å5ÎæUŒ‘ÀR2G¹ãµÁøÃ¨ü?ô1SøþK÷û¿û-±Úé> Ñü/­ÝéZÌÒë-i‚Ñ,:3wÜÎØoEÏëÃþ*Ö¬¤WñO…¬,à´Sö+´Ëup…y2ÌJãoeȵcÃÿò<ÛOêjçŠä/¨ÿ×´´ YâA¤ø‡Ì×4­2çûBûq{«‰ˆ[t~6à .:–#&½§À^ ²Ó|1¦]xRGš(dß%ªò>,íÀ˽‡A^]mÿ ßûb¿È×Ð?¿äŸî7þ„)ó0èr¾8ñ=®£«$é¤Ë‹gt‘Mæ}€ÁÍv§Ç×:6í¤h5BÛQåÛ«u‘O ¹'#Þ¹ÿÇÖ±þñÿÐEp¾#ÿœöÿÙ >bZ ×ÿ„ïRy“FL³Í¨Ü$)5ÀÇ8ÈþÀü+ã/ƒ¿ñ÷sÿ\/ô_¬þ#ÿ“8úì?ô:ו©+ùöœÇKg{MJU¶0þæÞa¸¬a>gÚ:‘Å]ð¼ |=¨ø.iô2}«R˜ù·w’(ÎÈÇÞØ:“ò®{f¹X¿äšjö_ý+‘ðçü”o ÿ¹uÿ Òå4¾‡±é~Ñt:þ ?ÍHî¡1´p¯ïæÈ9MݲO<ãëÊï^ðç‡ῃâ •üJ·wV’º)#|ê’Œ°!FÝÀäf½jÏþBQ¾ßú yõçü}Ù×9™£”\ÌòGÀ:¡×3à]2M;Gšãb.QDqÇŒ»—<’rÜ‚yé^âm LÒ`UÓo¤¶C$K™¸ŽÜxárÇ'=«Öaÿ‘hÿ¼õãß¿ä'ýp‡ùÒpD9;š‡‡îµxf‡Á6—’X%³[ÚÜÊB‰d|”qܱÁjØ·øa§èzJëÞ&Ôì´½6tÓÒÜÈAwrì¶ö9ÏQ^·ðãþIÎÿ]"þF¼—ö€ÿ’acÿ_÷?Ê“‚1ÌxZ;85Ó-˜¸Kù>ì…\© ªª@,Ç8žµÙøÏRøØ5KýÂsf^éâ5¾²º‰eÛ)ä8#2ÇéYžÿ‘ßHÿ®öúuí7ßò_þ1×öÿ¢ã§ìй OøËá“jV“^Þj †Y·Æ#.Pnë†bY%R²ñ?ÿØaôžê¿lþ?åúå_M•/Üž?øˆþY?h_j7ßðRŠþ Ñl.of¼ÕôjŠG šuœÒ¹cŒÇ¼³rOÌxô¦j–oýmàkkXÆþ}Ãä9!†G#Œ·JúÆ?ò—‰?ö¸ÿÓey¿ü·_õÎý¼ìÆ Ui–þí>øR-?[6·1>y×qù"4ù¯9àf»oxŽöMb B æÆ$d…œa™Ïr»øT‘ÈÑýzÉü…eø“þE»O©¯?•;ø›L›NÐÈK¨Û€,}âÇbkfûLÒ­to²ÜG12;üÍÁ×=+žøYÒïýç­¿Èóþº/õ¥ÊMŽÏVñÝŠé)káÍ8ßÙi²íÚ³ÍüNã®ÅíÇ&­ZüKñWŒl¤ƒÄ-ôð#‡.qþÑéÏls^oàùOÿ]%þb¶<=÷nÿë ÿÐ….D4N:•Õž›‹ ./®¢ ³†Ø£'ëžþ•êš_….üU³KÓí>Ûsm ™Ë¸Ž%Sœdž  Wk?ñý¡ÿ×sÿ šúcà·ü†5Oú÷_æhä@|ããYü.Ó5e€KurùBÖçå’nŠ3Ôàä-qÞÓc:jø‚fº²»¹F3¯˜Tn',=ºV/Æßø÷ŸþÂèÓ]íçü€¿ïïþ„iòŠÈðˆ–‰¯h;¨„"× ËÜ'!ýãÖ¿4ü]®jºt&šÏÑ«4€ðO׸¯Ô?ÿÈ_úäÿÖ¿-|ÿZ‡ýqž¶ƒ²9ç¹æ^>×,üKme'ˆmd’;F ¸#Ýs·“õ¯Òì,mõ§ÕÚí"4jx “ÁNÿJõÿȼå^XÖEþâZëZ«‘ö?ÿx›ÄÿáÑmoå²Ñ"'¹±²'\7@¬çžœW°xûž'µ‚ ? <3ËÉÛ%»ƒ Ç' ¹Á8 Ðó“^%ð;þ>oì?ô1]Æ•ÿ úÅÿ¡ŠÊKSDwž!ðߌ´Ÿêy²±¹Ž3l,ØáýçÌ3Óžµ«âýÿízg…­ãž;‹[U KŸ25¸ùüÆrW¹¯\Õ?äg³ÿ¯y¿ô¨ü}ÿ%ÞëþÀ¶úNµ#<7PÑõht¸‚õ§%Tp9tV펧é^»áÿ‡Ö<1Z”’Ë« 3å[¤ìU§<œã€O9õ®!äý}7þ‚kÐ~ÈÝaõú PK$ÔÙ´-1tìù·êñEi±¹Ý—ù›vl•gÇZ¦‡¡ê~ðd{äm.#{!ù’ÆXß/®Ktï]—ÿäaðßýtOý¼cQÿ‘ûÆßõëþµ ðOÃkÿ[_¶ºïþý\íW`Õ 3Á=ÏZ¯â?x‡ÂzýÌvÖñ-ˆŒ…(SÆ‘Æ=Gç_@xGþA—ýu‡úW€üRÿQuþüúSå¸Ìíc⯇¾xBß\øÛö85%6¶ÐÄ^â@˜cµGÔ`È®wBø©wãß Ùëšf‰ufV)A‚à/˜ÏÉ#(é¸v=óŸü¯þE߇ÿõÂý•ôÂOùKÿ^ÿÒ’ŠÛt¯ˆž*ðnÿ€îî¬.-,íÒ雕™£É+°‚»C^Çá¯ü=ñ/‡µMST¿ºÔü_È»šSöx¢œæI¶ó»¸läcóÕÇü¯ÿëþÌkßþÿÈÇãûYÿèf¯• ᵯ|@Swuôk§E u"0ÀQO*à‘×®+R–ÏZ¸ÿ„·Åú¤Ó¥­ÊCpŒÒ‹4]¡²Ù ã½ãPÿ.¥ÿ\ùWÊ:·üŠ>"ÿ¯Cÿ¡9P°5CàeƒÅ¾=Óbð½•Ö“2è66Óùׂ®Ü\ËŒ€°1“Å2 ÿ³­uy­áû4Ú¬}µ±’!P8CÜÎGç^‡ûpÈáýŠ_û=µpzÇü{\ÿØ2?çIÅ,Ìðõ­ö·r|_%Õžmh*yÒ:å‡Ý¸ê8Îî+ÊcŽ×ÄWúÿ‹µ_Úé3Á_gk‘½ç’A†Xú`d|ÍÉ®®÷þEúæÿÒ¾Zñü€âÿ®‹üêyg£_O¡xŸPº¸Ô-ΡuqC!Haà c8ÈúV¯Àë­ü¯jš„e§™fÒ,•s„iHn3¹°~^ ®'áïü~·ûÿû)­Oßò#Ãÿc*ÿèUN }á__x ê ñÄe%½’ìJÿ*övéÁâ»ÏjžðÖ¹ÄiœÇýžP &´+»hÜG-œgwâGü•gþ½lÿô`¯*ý¢?äF¹ÿ¯ÉôRÒäD¤ki/Ö<]ã 6çP{­S-”´ª²¡1 ÉÀ$ãíš®Úx«Æ>×cE›íÆIuX‘|¥Cçi>§'>•óÏÀÿù.>ÿ°Îü…}yâù(Þ:ÿ°¹ÿÐk>DQÁ[üDø§ðÃO>ðèúv¶Õï QæÃ «p@BC1-ÉÏÝÅyî¡wàM â éV¶úÖ‘ob–ÐO ®†e° ’xÝÀâ¸ÿ‹ò%iÿöóÿ¡šã|3ÿ ;þ¹Eÿ¡9ú¿‹|á¿E­húE¦“ª¤ñæÏ™÷Pg'î–;×¦é¾ ›[‘Þ8žâÎO2æAÑÇÞà`ô¾>ø§ÿ%—Qÿ®Ñÿè±_X|-ÿ‘vçþ½›ùŠn6@wkãÿx“W»Ð°ð«¥ê?³ûGüa‘5ðί}túd„ªÃ§HæÎÉ]¹ûÄ2óÆ ð¯.¬Ÿ´Ÿ|Ö×HÑnm º¶Ôµ8KDšÅÃ6ëUÆ2>tyë]6³ÿ(ÔÒ?ì9{üš¸Ãÿ&Ùáûôý.Up=WEøÏª~ÎÕ¼§êqĺUœ–šÝ„jÍ*ÉyT¦/$~QÓ—ðJÛÇŸ5]oÃZ%ß‹0/u)'—gš$e¤,0ÌGCŒŽÕà?äJøÑÿcF™ÿ§S_fÿÁ5¿äê¦ú¤&ðÄwšmëÞÚíû,7êÞdVГóåyÀøé_)Mñ[^¸Ò$ÔõíFkÒ&˜Ú@s²ÚfrKŒœ®K2¯kô;Å?òiºÇýzó’¿#ï?ä/ý|ýG*é/ø;â߯]JâãA·–ß_K¶ÓãŽÙp‘ÜǸ‡@2ãjî8Ít¾3ñ`Ôu ¾“j°Ës£¥ìû7¤w‘¡XœÁzv5öì]ÿ'7sÿa¸ÿôŠJønãýF‡ÿa;Ïýõ2@mx“âÕ¶§âßx_I¸Ô|+âì¹.lÐížKý<®ÉÆp´øÔž/ðÿå½ÊZØ6éÖwÁa9gS€Ä‡kÉ|ÿ#eÏý}/ó5åz'ü”é?ì/qÿ š@{ïŠì>%AàÍá.‹r¶ÞðͪI0̉^B®ÎG%‰ÀROõ®\Ñü}ã[Í7QÖ@¸·•ÂëûZ,Ãh…–yy(ê2qŸzúÅßò!x‹þÁÚgþŒ’£øOÿ"¨ÿ¯+ßý´ó^¡ðÓÆ#àÝׯÉü',6—3Ä4ýR2¾\L²yb7Œ6Y²­’PŒ÷­Ÿ Ë¡6½iâínÂX⸸û,·–rî>Ó4D&Å¡ˆ,x'Šûç\ÿ”WØÿØV?ý.ž¾Ó?äSÒÿìbƒÿAí#á‹5 èž;Õ|i£i6šuü‘jh´—+^èúr:d÷¯%ð—„®¹øâ Ãâ-á—UÓΘṴ́ià]\¦ŸJóɃ‚DdåÐrXß³§xªÁ¼2]Ùꦩ¹±ä¥ÍÌdMã¸Sžäñ_T~ÀŸòQ“WŽâ]3PµÕ5(äÃ2X^•†)6’r£Ë9l` Šûûã·ü€¾7צ‰ÿ -|MûUÿÉ×øÏþÄéeͨÒaÔ<}â;;kLûUÔÞ$Ô5K­.<¢^E>ði÷ÁÀÅzGŽtß…_¼y§üPðÍì¾ðæ§iytš ¢§_Æ¢6DÆC#H ®}qV>ÉÀxcðÿÐ%¯ÿ™KGúÞÿèóYÙÇø‡Â>!Ó¼_âWÄÖâËQÔÒÈ?u*†`Êí±íZÿ<5o¦~Í~ñU­Ò›xnæÒ €M0wrV] 6z öÏÚSþC_öÊ/ç^?ñSþM'Á_ö4Íÿ¤­E‚<ß|}§x~Þö /GÓôm!/µ+¤R’Hþs²H'Íp#8MÙ#¾¾µøE |Cøc«Ùü¾³¿Ιm¦5¤Ó ÞI›vlà‰!|¯Ìäÿ_ÿ\“ù­Dô…lÏÅŸí lIw¦ëâý]ü­±¥Äö¢87!î²&êk²ý—þ6\|+øáo½­åãÛÇg¨ZÞÆîŸvÉc<:Î7àŒWËßããGÿ°ÇþÎk2ù<ïi_ΡŽúvk^0øgû(øúîàiM“âËoàkeù!–&Ärá9P½Ê}1^}¤øïUð¿Ä=R÷âeœ:”^(Ñv^G¦Mµ¥BðÍ6 ȨJœuJæ?o_øððý‹5ã/ùü?ÿ`$ÿÑtâ3èoÙcÃ'Ÿ­¾8xKýKñ”ÒÇáº)­>Ró@qŒ‘“]׉>ümмY¨||ð…®»àï\Ë*^éR¶n~Co`@$Wañ®ÏU¸ÔünÖðÜj÷—l×"á<¨È•±”T¶áמsȯ¥þJ6·ÿbUçþŠ’¿4´øô“ý蘩aÌ~¨ø/Åšç‚áÓb× ±›Ãþ!³¹µÓî “ bpòZ\D9L¦Z3Üg>ÿKþË~"x‹â¶»à_øŸKðÖ±k§‹Í1ïbߪÐI……ƒ•FuGS‚w'‘ñ^¯ÿ$ÁŸö1_é+×Qû-ÉÜxSþ¸Ïÿ ÇI«‚gègíKûMxö…øÃ |pø‹àm;N½ð"/‡u(ìn<Ó=Ô’|&åÊ¢²Ê–áΘÊÇ%kÆ®uOÙöëÁzŸÁ!¬ØÉ~¿ÿ°·þ‰Z\ˆ•Q£áíáÿůٷD½ðGí¦Ýëþ O*ûÃþ)Ò@yt²ìw[ÝDH&ÙÁã%ÄoÆ@*µéÿ?àŸÿ´~‡¨C³f—Þ´‚ËW1Éäƒu`&Vàn`0è ¨‘py&¾¬ý³?äÐõÏû[ÿèÕ¯äßö2û¿?ì ôªj³6Šºlþ…-?e_ ùÚ/Œ´Çw¢éN"³º¸ˆMi/óyNÌûóÐî\`ô­{á‚õkºö²—Vz¬ƒX²H…Ýb…|ÔÞ>ö8;Iëœs]ßÃßù3-þ¶ßú ×qãßøóñ·ý…öÚ¬GçÕÿ‡­>KcñWÀ7XðŠ’+ö6ê2U˜ ëg#†=xû’ÇZýœþ*éžð¯„üWsáïÛZ¼3³cË3(;š'¼&A¸`ü»ˆà×Ä6òazwýz·þ¯1øCÿ%ú?ûÛÿèK@Jx·á_íCⱪéöhzͯ†ï`°×¬üÂ’4W 6]Â@; uÆGPÙ¥{¾µ¤þ̾&ø+­GªkÇCñ¾•y%ð¦«*+ý¦2}£¯ÌÐȧzn$c àŒ ¾ÈßñŸþ¼4Oý Wã¿Çïù‰­ñ—íakàý3ÄS[ˆîïcm휱—"$†F ïœWŸ.“›âYuë»h¬cƒOMJÊKSµçµ•NU6à§§þUã¿ä_Òÿì!qüš½ïÅŸò ðßýŠ1S[Îø§ÁÒÏâ=óG¿·óš5% ¤7p±Üv3c!Õ‡ÊÜtÅtß<+tOˆ¾ñêÚN«h@‰£âÞhÁO&U©'¡zÕ;Ïøöðgý‚_ÿB5¹áïù W¿ö“ÿB¤%á¯øľ ›Â7ÓÍÆ™©Zƒ}¥ÜͽòýÉín@6=Z›áÝ–¯àùô§øcâ+»¿éþeŒÚ]ìÎÏe×ív*Ȥ|„sŒWQaѿܶþuÅ|*ûÞ%ÿ°×ôjд×< ßï~ø’Ùí5Ôž}:ûx)o6I*T|Ë‘Án˜'šìüâOÁ«¶±á»‘¥\5·•slOîKF–8!Ûç–éƒ_5øßþ=®¿ì#/þƒTü'ÿ!Iÿë§þÉ@ 2|Nðï‹<¯¨YØøŽIžÚkyØAzL9À#'o^˜÷Í|íâK…+ã+K>{«-.;lÄâq'zíÃ2xë\×ÃøöÖë«è5çÞ&ÿ[ÿ×5þf© =gâ?…|àËÑ{¢ëæòP±K,Ö¬fWÊæ'rªÀü§¾+„Ôþ)xWÃ>³ø‡¡‰ícÑg ¬Åj™¸X™±ö0K*’IœŠçõßùêõçiýkÆuŸù#ÿì ßÌÓå@~” ›ßiÚOŠt«Û?M8[ÈõH6––#|¼2c dzæ¹ø†ÿÅ>#|aá«} úÞr©wiDŸoÜ2ØãƒŽµ—ûÉðýж¿ú®÷ãü…-¿ë¬ΡÇP4¼Añ^ÿÃ~+‹Å¾±[9õ~Ë}k «[ÜÌ m-ÆŸ^3^#ãïˆ>/ÖõÉZ]/³O =õ¦H‘æUsó: èµ~–_öùVw‰ÿäsºÿ°hÿц’C[š~6øSãðçKø¡á…¶Ó-ôIRÏT’Û÷F˜í·y|͹Úz÷®áÇíOVø¯ÄOùvÞ%µi{ìÛÞÙÌ2°ûãqܸ³ŒœWÛÿü™ïÄ¿úá¥èá^û>ÉJ¸ÿ®Çù ešþ!³ÑcÔ,4¿iv—ÚVÛl³ ˜d)9Îÿ ãŠØøsà_„z.‹¬øóáþŸq¡_Y½Ì3YI´ýžg\þì Œ8‘ÍqŸ¿ä?õõkÿ šôO ÿÈÇŸõýþйEsŽ×¾hÿ |/¢ü`øYâ9¯mõ|Û_évã|/ ^•“q9<:g¥kxÅõMSNøkñƒIÓõ]~æNŶ©a+Ýí ld_ºcliþ(ÿ’¢ÿ×êìµÛøkþF+/úÿÿBr ¹ã¿¾<Þ|5»¿øQã[Ö|!_&kˆss  &R:º}9Íz?ÀÏÚ;Zñÿ† êz¡¶hc gq)WóÐ,Š›¯BkäßÛ»ýV³ÿ]ÿAjæ?fù%ú/ÐèT¤´ÏÐQá]#ÄRø£âE¥Äš&¬ÂSK´"(å*Æ#hâǺÈùã¿ì»ðÍ“XÔ~,Þ>ðÁŸíÝùm«X–ÁhŸ s‚€“ÜÉúWâçüˆÖ?õ÷ÿ²µt³ü“-þºÿJ˜¡6ÿÙROCm-AMDMIGraphX-46524e8/examples/vision/python_unet/requirements.txt000066400000000000000000000025041510465702400255260ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### numpy matplotlibROCm-AMDMIGraphX-46524e8/examples/vision/python_unet/unet_inference.ipynb000066400000000000000000000147011510465702400263000ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n" ] }, { "cell_type": "markdown", "id": "cd7a3990", "metadata": {}, "source": [ "## Import MIGraphX Python Library" ] }, { "cell_type": "code", "execution_count": null, "id": "3930d7b8", "metadata": {}, "outputs": [], "source": [ "import migraphx\n", "from PIL import Image\n", "import numpy as np\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "id": "b350c333", "metadata": {}, "source": [ "## Fetch U-NET ONNX Model" ] }, { "cell_type": "code", "execution_count": null, "id": "02a7b7de", "metadata": {}, "outputs": [], "source": [ "!wget -nc https://www.dropbox.com/s/3ntkhyk30x05uuv/unet_13_256.onnx" ] }, { "cell_type": "markdown", "id": "a6cfe6e9", "metadata": {}, "source": [ "## Load ONNX Model" ] }, { "cell_type": "code", "execution_count": null, "id": "e05a13dc", "metadata": {}, "outputs": [], "source": [ "model = migraphx.parse_onnx(\"unet_13_256.onnx\")" ] }, { "cell_type": "code", "execution_count": null, "id": "52c67023", "metadata": {}, "outputs": [], "source": [ "model.compile(migraphx.get_target(\"gpu\"))" ] }, { "cell_type": "markdown", "id": "80edb6f1", "metadata": {}, "source": [ "## Print model parameters" ] }, { "cell_type": "code", "execution_count": null, "id": "fd5c3269", "metadata": {}, "outputs": [], "source": [ "print(model.get_parameter_names())\n", "print(model.get_parameter_shapes())" ] }, { "cell_type": "code", "execution_count": null, "id": "47f956c7", "metadata": {}, "outputs": [], "source": [ "def preprocess(pil_img, newW, newH):\n", " w, h = pil_img.size\n", " assert newW > 0 and newH > 0, 'Scale is too small'\n", " pil_img = pil_img.resize((newW, newH))\n", "\n", " img_nd = np.array(pil_img)\n", "\n", " if len(img_nd.shape) == 2:\n", " img_nd = np.expand_dims(img_nd, axis=2)\n", "\n", " # HWC to CHW\n", " img_print = pil_img\n", " img_trans = img_nd.transpose((2, 0, 1))\n", " if img_trans.max() > 1:\n", " img_trans = img_trans / 255\n", " \n", " img_trans = np.expand_dims(img_trans, 0)\n", "\n", " return img_trans, img_print\n", "\n", "def plot_img_and_mask(img, mask):\n", " classes = mask.shape[0] if len(mask.shape) > 3 else 1\n", " print(classes)\n", " fig, ax = plt.subplots(1, classes + 1)\n", " ax[0].set_title('Input image')\n", " ax[0].imshow(img)\n", " if classes > 1:\n", " for i in range(classes):\n", " ax[i+1].set_title(f'Output mask (class {i+1})')\n", " ax[i+1].imshow(mask[:, :, i])\n", " else:\n", " ax[1].set_title(f'Output mask')\n", " ax[1].imshow(mask[0,0])\n", " plt.xticks([]), plt.yticks([])\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "389ddc4d", "metadata": {}, "outputs": [], "source": [ "img = Image.open(\"./car1.jpeg\")\n", "img, imPrint = preprocess(img, 256, 256)\n", "input_im = np.zeros((1,3,256,256),dtype='float32') \n", "np.lib.stride_tricks.as_strided(input_im, shape=img.shape, strides=input_im.strides)[:] = img #getting correct stride\n", "print(input_im.strides)\n", "print(input_im.shape)\n", "imPrint.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "9de6f2a7", "metadata": {}, "outputs": [], "source": [ "mask = model.run({'inputs':input_im}) # Your first inference would take longer than the following ones.\n", "output_mask = np.array(mask[0])\n", "print(output_mask.shape)" ] }, { "cell_type": "code", "execution_count": null, "id": "acbd68e3", "metadata": {}, "outputs": [], "source": [ "def sigmoid(x):\n", " return 1 / (1 + np.exp(-x))" ] }, { "cell_type": "code", "execution_count": null, "id": "58e3062c", "metadata": {}, "outputs": [], "source": [ "probs = sigmoid(output_mask)\n", "full_mask = probs > 0.996\n", "plot_img_and_mask(imPrint, full_mask)" ] }, { "cell_type": "markdown", "id": "6126df0b", "metadata": {}, "source": [ "NOTE: The model weights utilized here are trained by using car images with plain backgrounds. The imperfect result on a \"real-world\" image as shown above is expected. To get a better result fine-tuning the model on a dataset of real-world examples is recommended. " ] } ], "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" } }, "nbformat": 4, "nbformat_minor": 5 } ROCm-AMDMIGraphX-46524e8/examples/vision/python_yolov4/000077500000000000000000000000001510465702400225225ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/examples/vision/python_yolov4/README.md000066400000000000000000000006161510465702400240040ustar00rootroot00000000000000# YoloV4 Object Detection The notebook [yolov4_inference.ipynb](./yolov4_inference.ipynb) is intended to be an example of how to use MIGraphX to perform object detection. The model used within is a pre-trained yolov4 from the ONNX model zoo. ## Run the Notebook To run the example notebook, simply issue the following command from this directory: ``` $ jupyter notebook yolov4_inference.ipynb ``` ROCm-AMDMIGraphX-46524e8/examples/vision/python_yolov4/image_processing.py000066400000000000000000000230051510465702400264120ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### # All pre- and post-processing methods used below are borrowed from the ONNX MOdel Zoo # https://github.com/onnx/models/tree/master/vision/object_detection_segmentation/yolov4 import numpy as np import cv2 from scipy import special import colorsys import random # this function is from tensorflow-yolov4-tflite/core/utils.py def image_preprocess(image, target_size, gt_boxes=None): ih, iw = target_size h, w, _ = image.shape scale = min(iw / w, ih / h) nw, nh = int(scale * w), int(scale * h) image_resized = cv2.resize(image, (nw, nh)) image_padded = np.full(shape=[ih, iw, 3], fill_value=128.0) dw, dh = (iw - nw) // 2, (ih - nh) // 2 image_padded[dh:nh + dh, dw:nw + dw, :] = image_resized image_padded = image_padded / 255. if gt_boxes is None: return image_padded else: gt_boxes[:, [0, 2]] = gt_boxes[:, [0, 2]] * scale + dw gt_boxes[:, [1, 3]] = gt_boxes[:, [1, 3]] * scale + dh return image_padded, gt_boxes def get_anchors(anchors_path, tiny=False): '''loads the anchors from a file''' with open(anchors_path) as f: anchors = f.readline() anchors = np.array(anchors.split(','), dtype=np.float32) return anchors.reshape(3, 3, 2) def postprocess_bbbox(pred_bbox, ANCHORS, STRIDES, XYSCALE=[1, 1, 1]): '''define anchor boxes''' for i, pred in enumerate(pred_bbox): conv_shape = pred.shape output_size = conv_shape[1] conv_raw_dxdy = pred[:, :, :, :, 0:2] conv_raw_dwdh = pred[:, :, :, :, 2:4] xy_grid = np.meshgrid(np.arange(output_size), np.arange(output_size)) xy_grid = np.expand_dims(np.stack(xy_grid, axis=-1), axis=2) xy_grid = np.tile(np.expand_dims(xy_grid, axis=0), [1, 1, 1, 3, 1]) xy_grid = xy_grid.astype(np.float) pred_xy = ((special.expit(conv_raw_dxdy) * XYSCALE[i]) - 0.5 * (XYSCALE[i] - 1) + xy_grid) * STRIDES[i] pred_wh = (np.exp(conv_raw_dwdh) * ANCHORS[i]) pred[:, :, :, :, 0:4] = np.concatenate([pred_xy, pred_wh], axis=-1) pred_bbox = [np.reshape(x, (-1, np.shape(x)[-1])) for x in pred_bbox] pred_bbox = np.concatenate(pred_bbox, axis=0) return pred_bbox def postprocess_boxes(pred_bbox, org_img_shape, input_size, score_threshold): '''remove boundary boxs with a low detection probability''' valid_scale = [0, np.inf] pred_bbox = np.array(pred_bbox) pred_xywh = pred_bbox[:, 0:4] pred_conf = pred_bbox[:, 4] pred_prob = pred_bbox[:, 5:] # (1) (x, y, w, h) --> (xmin, ymin, xmax, ymax) pred_coor = np.concatenate([ pred_xywh[:, :2] - pred_xywh[:, 2:] * 0.5, pred_xywh[:, :2] + pred_xywh[:, 2:] * 0.5 ], axis=-1) # (2) (xmin, ymin, xmax, ymax) -> (xmin_org, ymin_org, xmax_org, ymax_org) org_h, org_w = org_img_shape resize_ratio = min(input_size / org_w, input_size / org_h) dw = (input_size - resize_ratio * org_w) / 2 dh = (input_size - resize_ratio * org_h) / 2 pred_coor[:, 0::2] = 1.0 * (pred_coor[:, 0::2] - dw) / resize_ratio pred_coor[:, 1::2] = 1.0 * (pred_coor[:, 1::2] - dh) / resize_ratio # (3) clip some boxes that are out of range pred_coor = np.concatenate([ np.maximum(pred_coor[:, :2], [0, 0]), np.minimum(pred_coor[:, 2:], [org_w - 1, org_h - 1]) ], axis=-1) invalid_mask = np.logical_or((pred_coor[:, 0] > pred_coor[:, 2]), (pred_coor[:, 1] > pred_coor[:, 3])) pred_coor[invalid_mask] = 0 # (4) discard some invalid boxes bboxes_scale = np.sqrt( np.multiply.reduce(pred_coor[:, 2:4] - pred_coor[:, 0:2], axis=-1)) scale_mask = np.logical_and((valid_scale[0] < bboxes_scale), (bboxes_scale < valid_scale[1])) # (5) discard some boxes with low scores classes = np.argmax(pred_prob, axis=-1) scores = pred_conf * pred_prob[np.arange(len(pred_coor)), classes] score_mask = scores > score_threshold mask = np.logical_and(scale_mask, score_mask) coors, scores, classes = pred_coor[mask], scores[mask], classes[mask] return np.concatenate( [coors, scores[:, np.newaxis], classes[:, np.newaxis]], axis=-1) def bboxes_iou(boxes1, boxes2): '''calculate the Intersection Over Union value''' boxes1 = np.array(boxes1) boxes2 = np.array(boxes2) boxes1_area = (boxes1[..., 2] - boxes1[..., 0]) * (boxes1[..., 3] - boxes1[..., 1]) boxes2_area = (boxes2[..., 2] - boxes2[..., 0]) * (boxes2[..., 3] - boxes2[..., 1]) left_up = np.maximum(boxes1[..., :2], boxes2[..., :2]) right_down = np.minimum(boxes1[..., 2:], boxes2[..., 2:]) inter_section = np.maximum(right_down - left_up, 0.0) inter_area = inter_section[..., 0] * inter_section[..., 1] union_area = boxes1_area + boxes2_area - inter_area ious = np.maximum(1.0 * inter_area / union_area, np.finfo(np.float32).eps) return ious def nms(bboxes, iou_threshold, sigma=0.3, method='nms'): """ :param bboxes: (xmin, ymin, xmax, ymax, score, class) Note: soft-nms, https://arxiv.org/pdf/1704.04503.pdf https://github.com/bharatsingh430/soft-nms """ classes_in_img = list(set(bboxes[:, 5])) best_bboxes = [] for cls in classes_in_img: cls_mask = (bboxes[:, 5] == cls) cls_bboxes = bboxes[cls_mask] while len(cls_bboxes) > 0: max_ind = np.argmax(cls_bboxes[:, 4]) best_bbox = cls_bboxes[max_ind] best_bboxes.append(best_bbox) cls_bboxes = np.concatenate( [cls_bboxes[:max_ind], cls_bboxes[max_ind + 1:]]) iou = bboxes_iou(best_bbox[np.newaxis, :4], cls_bboxes[:, :4]) weight = np.ones((len(iou), ), dtype=np.float32) assert method in ['nms', 'soft-nms'] if method == 'nms': iou_mask = iou > iou_threshold weight[iou_mask] = 0.0 if method == 'soft-nms': weight = np.exp(-(1.0 * iou**2 / sigma)) cls_bboxes[:, 4] = cls_bboxes[:, 4] * weight score_mask = cls_bboxes[:, 4] > 0. cls_bboxes = cls_bboxes[score_mask] return best_bboxes def read_class_names(class_file_name): '''loads class name from a file''' names = {} with open(class_file_name, 'r') as data: for ID, name in enumerate(data): names[ID] = name.strip('\n') return names def draw_bbox(image, bboxes, classes=read_class_names("./utilities/coco.names"), show_label=True): """ bboxes: [x_min, y_min, x_max, y_max, probability, cls_id] format coordinates. """ num_classes = len(classes) image_h, image_w, _ = image.shape hsv_tuples = [(1.0 * x / num_classes, 1., 1.) for x in range(num_classes)] colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples)) colors = list( map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), colors)) random.seed(0) random.shuffle(colors) random.seed(None) for i, bbox in enumerate(bboxes): coor = np.array(bbox[:4], dtype=np.int32) fontScale = 0.5 score = bbox[4] class_ind = int(bbox[5]) bbox_color = colors[class_ind] bbox_thick = int(0.6 * (image_h + image_w) / 600) c1, c2 = (coor[0], coor[1]), (coor[2], coor[3]) cv2.rectangle(image, c1, c2, bbox_color, bbox_thick) if show_label: bbox_mess = '%s: %.2f' % (classes[class_ind], score) t_size = cv2.getTextSize(bbox_mess, 0, fontScale, thickness=bbox_thick // 2)[0] cv2.rectangle(image, c1, (c1[0] + t_size[0], c1[1] - t_size[1] - 3), bbox_color, -1) cv2.putText(image, bbox_mess, (c1[0], c1[1] - 2), cv2.FONT_HERSHEY_SIMPLEX, fontScale, (0, 0, 0), bbox_thick // 2, lineType=cv2.LINE_AA) return image ROCm-AMDMIGraphX-46524e8/examples/vision/python_yolov4/yolov4_inference.ipynb000066400000000000000000000165351510465702400270510ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MIT License (MIT)\n", "#\n", "# Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved.\n", "#\n", "# Permission is hereby granted, free of charge, to any person obtaining a copy\n", "# of this software and associated documentation files (the 'Software'), to deal\n", "# in the Software without restriction, including without limitation the rights\n", "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n", "# copies of the Software, and to permit persons to whom the Software is\n", "# furnished to do so, subject to the following conditions:\n", "#\n", "# The above copyright notice and this permission notice shall be included in\n", "# all copies or substantial portions of the Software.\n", "#\n", "# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n", "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n", "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n", "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n", "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n", "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n", "# THE SOFTWARE.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Object Detection with YoloV4\n", "This notebook is intended to be an example of how to use MIGraphX to perform object detection. The model used below is a pre-trained yolov4 from the ONNX model zoo. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Download dependencies" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os.path\n", "\n", "if not os.path.exists(\"./utilities/coco.names\"):\n", " !wget https://github.com/onnx/models/raw/main/validated/vision/object_detection_segmentation/yolov4/dependencies/coco.names -P ./utilities/\n", "if not os.path.exists(\"./utilities/yolov4_anchors.txt\"):\n", " !wget https://github.com/onnx/models/raw/main/validated/vision/object_detection_segmentation/yolov4/dependencies/yolov4_anchors.txt -P ./utilities/\n", "if not os.path.exists(\"./utilities/input.jpg\"):\n", " # The image used is from the COCO dataset (https://cocodataset.org/#explore)\n", " # Other images can be tested by replacing the link below\n", " image_link = \"https://farm3.staticflickr.com/2009/2306189268_88cc86b30f_z.jpg\"\n", " !wget -O ./utilities/input.jpg $image_link\n", "if not os.path.exists(\"./utilities/yolov4.onnx\"):\n", " !wget https://github.com/onnx/models/raw/main/validated/vision/object_detection_segmentation/yolov4/model/yolov4.onnx -P ./utilities/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Serialize model using MIGraphX Driver\n", "Please refer to the [MIGraphX Driver example](../../migraphx/migraphx_driver) if you would like more information about this tool." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if not os.path.exists(\"yolov4_fp16.mxr\"):\n", " !/opt/rocm/bin/migraphx-driver compile ./utilities/yolov4.onnx --gpu --enable-offload-copy --fp16 --binary -o yolov4_fp16.mxr\n", "if not os.path.exists(\"yolov4.mxr\"):\n", " !/opt/rocm/bin/migraphx-driver compile ./utilities/yolov4.onnx --gpu --enable-offload-copy --binary -o yolov4.mxr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Import libraries \n", "Please refer to [this section](https://github.com/ROCmSoftwarePlatform/AMDMIGraphX#using-migraphx-python-module) of the main README if the migraphx module is not found. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import migraphx\n", "import cv2\n", "import time\n", "import numpy as np\n", "import image_processing as ip\n", "from PIL import Image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Read and pre-process image data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "input_size = 416\n", "\n", "original_image = cv2.imread(\"./utilities/input.jpg\")\n", "original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)\n", "original_image_size = original_image.shape[:2]\n", "\n", "image_data = ip.image_preprocess(np.copy(original_image), [input_size, input_size])\n", "image_data = image_data[np.newaxis, ...].astype(np.float32)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Load and run model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Load serialized model (either single- or half-precision)\n", "model = migraphx.load(\"yolov4.mxr\", format=\"msgpack\")\n", "#model = migraphx.load(\"yolov4_fp16.mxr\", format=\"msgpack\")\n", "\n", "# Get the name of the input parameter and convert image data to an MIGraphX argument\n", "input_name = next(iter(model.get_parameter_shapes()))\n", "input_argument = migraphx.argument(image_data)\n", "\n", "# Evaluate the model and convert the outputs for post-processing\n", "outputs = model.run({input_name: input_argument})\n", "detections = [np.ndarray(shape=out.get_shape().lens(), buffer=np.array(out.tolist()), dtype=float) for out in outputs]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Post-process the model outputs and display image with detection bounding boxes" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ANCHORS = \"./utilities/yolov4_anchors.txt\"\n", "STRIDES = [8, 16, 32]\n", "XYSCALE = [1.2, 1.1, 1.05]\n", "\n", "ANCHORS = ip.get_anchors(ANCHORS)\n", "STRIDES = np.array(STRIDES)\n", "\n", "pred_bbox = ip.postprocess_bbbox(detections, ANCHORS, STRIDES, XYSCALE)\n", "bboxes = ip.postprocess_boxes(pred_bbox, original_image_size, input_size, 0.25)\n", "bboxes = ip.nms(bboxes, 0.213, method='nms')\n", "image = ip.draw_bbox(original_image, bboxes)\n", "\n", "image = Image.fromarray(image)\n", "image.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "name": "python3", "display_name": "Python 3.8.3 64-bit ('base': conda)" }, "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.8.3" }, "metadata": { "interpreter": { "hash": "d7283edef085bb46d38a3069bce96b3de1793019cb5bd7b1e86bf9785b67f304" } }, "interpreter": { "hash": "d7283edef085bb46d38a3069bce96b3de1793019cb5bd7b1e86bf9785b67f304" } }, "nbformat": 4, "nbformat_minor": 2 } ROCm-AMDMIGraphX-46524e8/hip-clang.docker000066400000000000000000000037021510465702400175750ustar00rootroot00000000000000FROM ubuntu:22.04 ARG PREFIX=/usr/local # Support multiarch RUN dpkg --add-architecture i386 # Add rocm repository RUN sh -c 'echo deb [arch=amd64 trusted=yes] http://repo.radeon.com/rocm/apt/7.1/ jammy main > /etc/apt/sources.list.d/rocm.list' # From docs.amd.com for installing rocm. Needed to install properly RUN sh -c "echo 'Package: *\nPin: release o=repo.radeon.com\nPin-priority: 600' > /etc/apt/preferences.d/rocm-pin-600" # Install dependencies RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \ apt-utils \ build-essential \ cmake \ curl \ gdb \ git \ lcov \ pkg-config \ python3 \ python3-dev \ python3-pip \ python3-full \ software-properties-common \ wget \ rocm-device-libs \ hip-dev \ libnuma-dev \ miopen-hip \ rocblas \ zlib1g-dev && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Workaround broken rocm packages RUN ln -s /opt/rocm-* /opt/rocm RUN echo "/opt/rocm/lib" > /etc/ld.so.conf.d/rocm.conf RUN echo "/opt/rocm/llvm/lib" > /etc/ld.so.conf.d/rocm-llvm.conf RUN ldconfig # Workaround broken miopen cmake files RUN sed -i 's,;/usr/lib/x86_64-linux-gnu/librt.so,,g' /opt/rocm/lib/cmake/miopen/miopen-targets.cmake # Workaround for distributions running cmake < 3.25 RUN sed -i -e 's/^block/if(COMMAND block)\nblock/g' -e 's/^endblock/endblock\(\)\nendif/g' /opt/rocm/lib/cmake/hipblaslt/hipblaslt-config.cmake ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 # Install yapf RUN pip3 install yapf==0.28.0 # Install doc requirements ADD docs/sphinx/requirements.txt /doc-requirements.txt RUN pip3 install -r /doc-requirements.txt # Install dependencies ADD dev-requirements.txt /dev-requirements.txt ADD requirements.txt /requirements.txt ADD rbuild.ini /rbuild.ini COPY ./tools/install_prereqs.sh / COPY ./tools/requirements-py.txt / RUN /install_prereqs.sh /usr/local / && rm /install_prereqs.sh && rm /requirements-py.txt ROCm-AMDMIGraphX-46524e8/rbuild.ini000066400000000000000000000016531510465702400165270ustar00rootroot00000000000000[main] cxx = ${rocm_path}/llvm/bin/clang++ cc = ${rocm_path}/llvm/bin/clang deps = ROCm/rocm-recipes -f requirements.txt [gh] ignore = danmar/cppcheck ROCm/rocMLIR deps = -f dev-requirements.txt oneapi-src/oneDNN@v1.7 define = CMAKE_C_COMPILER_LAUNCHER=${deps_dir}/bin/ccache CMAKE_CXX_COMPILER_LAUNCHER=${deps_dir}/bin/ccache MIGRAPHX_ENABLE_CPU=On BUILD_DEV=On [develop] cxx = ${rocm_path}/llvm/bin/clang++ cc = ${rocm_path}/llvm/bin/clang deps = -f dev-requirements.txt oneapi-src/oneDNN@v1.7 define = CMAKE_C_COMPILER_LAUNCHER=${deps_dir}/bin/ccache CMAKE_CXX_COMPILER_LAUNCHER=${deps_dir}/bin/ccache MIGRAPHX_ENABLE_CPU=On BUILD_DEV=On [cibuild] cxx = ${rocm_path}/llvm/bin/clang++ cc = ${rocm_path}/llvm/bin/clang deps = -f dev-requirements.txt define = CMAKE_C_COMPILER_LAUNCHER=${deps_dir}/bin/ccache CMAKE_CXX_COMPILER_LAUNCHER=${deps_dir}/bin/ccache ROCm-AMDMIGraphX-46524e8/requirements.txt000066400000000000000000000040511510465702400200240ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### abseil/abseil-cpp@20250512.0 -DABSL_ENABLE_INSTALL=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON google/protobuf@v30.0 -DCMAKE_POSITION_INDEPENDENT_CODE=On -Dprotobuf_BUILD_TESTS=Off -DCMAKE_POLICY_VERSION_MINIMUM=3.5 nlohmann/json@v3.8.0 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ROCm/half@rocm-5.6.0 pybind/pybind11@3e9dfa2866941655c56877882565e7577de6fc7b --build msgpack/msgpack-c@cpp-3.3.0 -DMSGPACK_BUILD_TESTS=Off -DMSGPACK_BUILD_EXAMPLES=Off -DCMAKE_POLICY_VERSION_MINIMUM=3.5 sqlite3@3.50.4 -DCMAKE_POSITION_INDEPENDENT_CODE=On ROCm/composable_kernel@b7775add2d28251674d81e220cd4a857b90b997a -DCK_BUILD_JIT_LIB=On -DCMAKE_POSITION_INDEPENDENT_CODE=On ROCm/rocMLIR@3d7e854e66a40ad31909e2a93b166e27ea14fc32 -DBUILD_FAT_LIBROCKCOMPILER=On -DLLVM_INCLUDE_TESTS=Off ROCm-AMDMIGraphX-46524e8/src/000077500000000000000000000000001510465702400153275ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/CMakeLists.txt000066400000000000000000000252101510465702400200670ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### include(ExportHeader) include(Embed) include(ROCMInstallTargets) include(ROCMPackageConfigHelpers) include(RegisterOp) include(CheckCXXLinkerFlag) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_FLAGS "-Werror=deprecated-declarations") check_cxx_source_compiles(" #include int main() { std::stable_sort((int *)0, (int*)0); } " COMPILER_IGNORES_DEPRECATED_DECL_IN_SYSTEM_HEADERS) execute_process(COMMAND gcc-11 --print-file-name "" OUTPUT_VARIABLE LIBSTDCXX11_PATH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT COMPILER_IGNORES_DEPRECATED_DECL_IN_SYSTEM_HEADERS AND EXISTS "${LIBSTDCXX11_PATH}") add_compile_options("--gcc-install-dir=${LIBSTDCXX11_PATH}") endif() add_library(migraphx adjust_allocation.cpp analyze_streams.cpp apply_alpha_beta.cpp argument.cpp autocast_fp8.cpp auto_contiguous.cpp base64.cpp common.cpp common_dims.cpp compile_src.cpp convert_to_json.cpp cpp_generator.cpp dead_code_elimination.cpp dom_info.cpp dynamic_loader.cpp eliminate_allocation.cpp eliminate_common_subexpression.cpp eliminate_concat.cpp eliminate_contiguous.cpp eliminate_convert.cpp eliminate_data_type.cpp eliminate_identity.cpp eliminate_pad.cpp env.cpp file_buffer.cpp fileutils.cpp fp_to_double.cpp fp8_ocp_to_fnuz.cpp fuse_attention.cpp fuse_concat.cpp fuse_pointwise.cpp fuse_pointwise_reduce.cpp fuse_reduce.cpp generate.cpp graphviz.cpp inline_module.cpp insert_pad.cpp instruction.cpp json.cpp layout_convolution.cpp lexing.cpp load_save.cpp make_op.cpp memory_coloring.cpp module.cpp msgpack.cpp netron_output.cpp normalize_attributes.cpp normalize_ops.cpp op_enums.cpp operation.cpp optimize_module.cpp pad_calc.cpp param_utils.cpp pass.cpp pass_manager.cpp permutation.cpp preallocate_param.cpp process.cpp program.cpp propagate_constant.cpp propagate_precision.cpp promote_literals.cpp quantization.cpp quantize_int4.cpp quantize_8bits.cpp reduce_dims.cpp register_op.cpp register_target.cpp replace_allocate.cpp rewrite_reduce.cpp rewrite_dot.cpp simplify_qdq.cpp split_reduce.cpp sqlite.cpp rewrite_gelu.cpp rewrite_low_precision.cpp rewrite_pooling.cpp rewrite_quantization.cpp rewrite_rnn.cpp rewrite_topk.cpp schedule.cpp serialize.cpp shape.cpp shape_transform_descriptor.cpp simplify_algebra.cpp simplify_dyn_ops.cpp simplify_reshapes.cpp split_single_dyn_dim.cpp target.cpp tmp_dir.cpp truncate_float.cpp value.cpp verify_args.cpp ) file(GLOB BUILDER_SRCS CONFIGURE_DEPENDS op/builder/*.cpp) target_sources(migraphx PRIVATE ${BUILDER_SRCS}) if(WIN32) # Due to compilation crashing, we need to use type-erased matchers on Windows. target_compile_definitions(migraphx PUBLIC MIGRAPHX_USE_TYPE_ERASED_MATCHERS=1) target_compile_options(migraphx PUBLIC "-mno-ms-bitfields") # Due to BinSkim errors EnableControlFlowGuard target_compile_options(migraphx PUBLIC "SHELL:-Xclang -cfguard") endif() configure_file(version.h.in include/migraphx/version.h) add_library(migraphx_version INTERFACE) rocm_install_targets( TARGETS migraphx_version INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include ) rocm_set_soversion(migraphx ${MIGRAPHX_SO_VERSION}) function(register_migraphx_ops) foreach(OP ${ARGN}) register_op(migraphx HEADER migraphx/op/${OP}.hpp OPERATORS op::${OP}) endforeach() endfunction() register_migraphx_ops( abs acosh acos add allocate argmax argmin asinh asin as_shape atanh atan bit_cast bitwise_and broadcast broadcast_for_dot broadcast_with_dims capture ceil clip concat contiguous convert convolution convolution_backwards cosh cos dequantizelinear dimensions_of div dot elu equal erf exp fill fixed_pad flatten floor fmod gather gathernd get_tuple_elem greater group_query_attention group gru identity if_op im2col isinf isnan layout leaky_relu less load log log2 logical_and logical_or logical_xor logsoftmax loop lrn lstm max min mod mul multibroadcast multinomial nearbyint neg nonmaxsuppression nonzero onehot outline pack_fp4 pack_int4 pad pointwise pooling pow prefix_scan_sum prelu quant_convolution quant_dot quantizelinear random_uniform random_seed recip reduce_all reduce_any reduce_max reduce_mean reduce_min reduce_prod reduce_sum relu reshape reshape_lazy resize reverse rnn rnn_last_cell_output rnn_last_hs_output rnn_var_sl_last_output roialign rsqrt run_on_target scalar scan_slice scatter_none scatter_add scatter_mul scatter_min scatter_max scatternd_add scatternd_mul scatternd_none scatternd_max scatternd_min select_module sigmoid sign sinh sin slice softmax sqdiff sqrt squeeze step sub tanh tan topk transpose unary_not undefined unique unknown unpack_fp4 unpack_int4 unsqueeze where ) register_op(migraphx HEADER migraphx/op/rnn_variable_seq_lens.hpp OPERATORS op::rnn_var_sl_shift_output op::rnn_var_sl_shift_sequence) register_op(migraphx HEADER migraphx/builtin.hpp OPERATORS builtin::literal builtin::param builtin::returns) rocm_clang_tidy_check(migraphx) migraphx_generate_export_header(migraphx) rocm_install_targets( PRIVATE TARGETS migraphx INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/op/builder/include ) target_link_libraries(migraphx PUBLIC migraphx_version) if(NOT WIN32) check_cxx_linker_flag(-lstdc++fs HAS_LIB_STD_FILESYSTEM) if(HAS_LIB_STD_FILESYSTEM) target_link_libraries(migraphx PRIVATE -lstdc++fs) endif() target_link_libraries(migraphx PRIVATE -ldl) endif() target_include_directories(migraphx SYSTEM PUBLIC $) target_link_libraries(migraphx PUBLIC Threads::Threads) if(MIGRAPHX_HAS_EXECUTORS AND ParallelSTL_USES_TBB) list(APPEND MIGRAPHX_CONFIG_DEPENDS PACKAGE TBB) endif() if(MIGRAPHX_HAS_EXECUTORS) message(STATUS "Parallel STL enabled") target_compile_definitions(migraphx PUBLIC MIGRAPHX_HAS_EXECUTORS=1) target_link_libraries(migraphx PUBLIC ${ParallelSTL_LIBRARIES}) else() message(STATUS "Parallel STL disabled") target_compile_definitions(migraphx PUBLIC MIGRAPHX_HAS_EXECUTORS=0) endif() find_package(nlohmann_json 3.8.0 REQUIRED) target_link_libraries(migraphx PRIVATE nlohmann_json::nlohmann_json) find_package(SQLite3 REQUIRED) target_link_libraries(migraphx PRIVATE SQLite::SQLite3) # See: https://github.com/msgpack/msgpack-c/wiki/Q%26A#how-to-support-both-msgpack-c-c-version-5x-and-6x- # Prefer 6.x (msgpack-cxx) find_package(msgpack-cxx) if(msgpack-cxx_FOUND) message(STATUS "Found msgpack-cxx (>=6.x)") else() find_package(msgpackc-cxx REQUIRED NAMES msgpackc-cxx msgpack) message(STATUS "Found msgpackc-cxx (<=5.x)") add_library(msgpack-cxx ALIAS msgpackc-cxx) endif() target_link_libraries(migraphx PRIVATE msgpack-cxx) # Make this available to the tests target_link_libraries(migraphx INTERFACE $) add_library(migraphx_all_targets INTERFACE) add_subdirectory(api) add_subdirectory(driver) add_subdirectory(onnx) add_subdirectory(tf) if(MIGRAPHX_ENABLE_PYTHON) add_subdirectory(py) endif() add_subdirectory(targets/ref) target_link_libraries(migraphx_all_targets INTERFACE migraphx_ref) if(MIGRAPHX_ENABLE_CPU) add_subdirectory(targets/cpu) target_link_libraries(migraphx_all_targets INTERFACE migraphx_cpu) target_compile_definitions(migraphx_all_targets INTERFACE -DHAVE_CPU) endif() if(MIGRAPHX_ENABLE_GPU) if(MIGRAPHX_USE_MIOPEN) list(APPEND MIGRAPHX_CONFIG_DEPENDS PACKAGE MIOpen) endif() if(MIGRAPHX_USE_ROCBLAS) list(APPEND MIGRAPHX_CONFIG_DEPENDS PACKAGE rocblas) endif() if(MIGRAPHX_USE_HIPBLASLT) list(APPEND MIGRAPHX_CONFIG_DEPENDS PACKAGE hipblaslt) endif() add_subdirectory(targets/gpu) target_link_libraries(migraphx_all_targets INTERFACE migraphx_gpu) target_compile_definitions(migraphx_all_targets INTERFACE -DHAVE_GPU) endif() if(MIGRAPHX_ENABLE_FPGA) add_subdirectory(targets/fpga) target_link_libraries(migraphx_all_targets INTERFACE migraphx_fpga) target_compile_definitions(migraphx_all_targets INTERFACE -DHAVE_FPGA) endif() if(MIGRAPHX_USE_MIOPEN) target_compile_definitions(migraphx_all_targets INTERFACE MIGRAPHX_USE_MIOPEN=1) else() target_compile_definitions(migraphx_all_targets INTERFACE MIGRAPHX_USE_MIOPEN=0) endif() if(HAVE_HALF_EXPR) target_compile_definitions(migraphx PUBLIC -DHAS_HALF_V1) endif() if(BUILD_DEV) target_compile_definitions(migraphx PUBLIC -DBUILD_DEV) endif() target_compile_definitions(migraphx PUBLIC MIGRAPHX_CXX_COMPILER="${CMAKE_CXX_COMPILER}") rocm_export_targets( TARGETS migraphx::migraphx_c NAMESPACE migraphx:: DEPENDS Threads ${MIGRAPHX_CONFIG_DEPENDS} ) ROCm-AMDMIGraphX-46524e8/src/adjust_allocation.cpp000066400000000000000000000054271510465702400215420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void adjust_allocation::apply(module& m) const { for(auto ins : iterator_for(m)) { // skip instruction with no input if(ins->inputs().empty()) continue; // Skip target-independent operators if(ins->get_operator().is_context_free()) continue; auto alias_ins = instruction::get_output_alias(ins, true); if(alias_ins->name() != model.name() and alias_ins->name() != "@param") continue; // shape allocated is different from actual shape // of the instruction, reallocate and replace the previous one if(alias_ins->get_shape() == ins->get_shape()) continue; auto alloc_ins = m.insert_instruction(ins, model.allocate(ins->get_shape())); m.replace_instruction(alias_ins, alloc_ins); // If the memory is an output parameter then copy the memory to the parameter if(alias_ins->name() == "@param") { auto copy = m.insert_instruction(std::next(ins), make_op(model.copy()), ins, alias_ins); auto tail = range(std::next(copy), m.end()); for(auto i : iterator_for(tail)) { if(contains(i->inputs(), ins)) instruction::replace_argument(i, ins, copy); } } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/analyze_streams.cpp000066400000000000000000000103361510465702400212370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static bool happens_before(const std::vector& e1, const std::vector& e2) { return std::equal(e1.begin(), e1.end(), e2.begin(), e2.end(), std::less_equal<>{}) and not std::equal(e1.begin(), e1.end(), e2.begin(), e2.end(), std::greater_equal<>{}); } std::vector analyze_streams(const module& m, const stream_model& strmm) { using vector_clock = std::vector; std::vector races; auto nstream = strmm.get_nstream(); std::vector vclock(nstream, vector_clock(nstream)); std::unordered_map timestamp; std::unordered_map events; for(auto ins : iterator_for(m)) { if(not strmm.has_stream(ins)) continue; std::size_t s = strmm.get_stream(ins); assert(s < nstream); assert(vclock.size() == nstream); assert(vclock[s].size() == nstream); if(strmm.is_record(ins)) { vclock[s][s]++; auto event = strmm.get_event_id(ins); events[event] = vclock[s]; } else if(strmm.is_wait(ins)) { auto event = strmm.get_event_id(ins); if(not contains(events, event)) MIGRAPHX_THROW("Event is waited on before being recorded: " + std::to_string(event)); auto payload = events.at(event); assert(vclock[s].size() == payload.size()); std::transform(vclock[s].begin(), vclock[s].end(), payload.begin(), vclock[s].begin(), [&](auto x, auto y) { return std::max(x, y); }); vclock[s][s]++; } else { vclock[s][s]++; } timestamp[ins] = vclock[s]; } for(auto ins : iterator_for(m)) { if(not strmm.has_stream(ins)) continue; if(ins->inputs().empty()) continue; std::size_t s = strmm.get_stream(ins); // Find inputs from different streams std::vector inputs; fix([&](auto self, auto start) { for(auto input : start->inputs()) { if(not strmm.has_stream(input)) self(input); else if(strmm.get_stream(input) != s) inputs.push_back(input); } })(ins); auto it = std::find_if(inputs.begin(), inputs.end(), [&](auto input) { return not happens_before(timestamp.at(input), timestamp.at(ins)); }); if(it != inputs.end()) { races.push_back({ins, *it}); } } return races; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/api/000077500000000000000000000000001510465702400161005ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/api/CMakeLists.txt000066400000000000000000000037511510465702400206460ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### add_library(migraphx_c api.cpp ) set_target_properties(migraphx_c PROPERTIES EXPORT_NAME c) migraphx_generate_export_header(migraphx_c DIRECTORY migraphx/api) # migraphx_c is stable API interface library. SO version of this should be # bumped when binary compatibility is broken. rocm_set_soversion(migraphx_c 3.0) if(BUILD_TESTING) target_compile_definitions(migraphx_c PRIVATE MIGRAPHX_BUILD_TESTING) endif() rocm_clang_tidy_check(migraphx_c) target_link_libraries(migraphx_c PRIVATE migraphx migraphx_tf migraphx_onnx) target_link_libraries(migraphx_c PUBLIC migraphx_version) rocm_install_targets( TARGETS migraphx_c INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include ) ROCm-AMDMIGraphX-46524e8/src/api/api.cpp000066400000000000000000002662251510465702400173720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { #ifdef MIGRAPHX_BUILD_TESTING static thread_local bool disable_exception_catch = false; // NOLINT extern "C" MIGRAPHX_C_EXPORT void migraphx_test_private_disable_exception_catch(bool b) { disable_exception_catch = b; } #endif template migraphx_status try_(F f, bool output = true, source_location llc = source_location::current()) // NOLINT { #ifdef MIGRAPHX_BUILD_TESTING if(disable_exception_catch) { f(); } else { #endif try { f(); } catch(const migraphx::exception& ex) { if(output) std::cerr << llc.function_name() << ": Error: " << ex.what() << std::endl; if(ex.error > 0) return migraphx_status(ex.error); else return migraphx_status_unknown_error; } catch(const std::exception& ex) { if(output) std::cerr << llc.function_name() << ": Error: " << ex.what() << std::endl; return migraphx_status_unknown_error; } catch(...) { return migraphx_status_unknown_error; } #ifdef MIGRAPHX_BUILD_TESTING } #endif return migraphx_status_success; } static shape::type_t to_shape_type(migraphx_shape_datatype_t t) { switch(t) { case migraphx_shape_tuple_type: return shape::tuple_type; case migraphx_shape_fp4x2_type: return shape::fp4x2_type; #define MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT(x, y) \ case migraphx_shape_##x: return shape::x; MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT) #undef MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT } MIGRAPHX_THROW(migraphx_status_bad_param, "Unknown type"); } static migraphx_shape_datatype_t to_shape_type(shape::type_t t) { switch(t) { case shape::tuple_type: return migraphx_shape_tuple_type; case shape::fp4x2_type: return migraphx_shape_fp4x2_type; #define MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT(x, y) \ case shape::x: return migraphx_shape_##x; MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT) #undef MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT } MIGRAPHX_THROW(migraphx_status_bad_param, "Unknown type"); } template static auto to_obj_vector(const T* x, std::size_t n) { std::vectorobject)> result; std::transform(x, x + n, std::back_inserter(result), [&](auto&& y) { return y->object; }); return result; } template static auto to_objptr_vector(const U* x, std::size_t n) { std::vector result; std::transform( x, x + n, std::back_inserter(result), [&](auto&& y) { return std::addressof(y->object); }); return result; } static target get_target(const std::string& name) { return make_target(name); } static void set_offload_copy(compile_options& options, bool value) { options.offload_copy = value; } static void set_fast_math(compile_options& options, bool value) { options.fast_math = value; } static void set_exhaustive_tune_flag(compile_options& options, bool value) { options.exhaustive_tune = value; } static void set_file_format(file_options& options, const char* format) { options.format = format; } static void set_default_dim_value(onnx_options& options, size_t value) { options.default_dim_value = value; } static void set_default_dyn_dim_value(onnx_options& options, const shape::dynamic_dimension& dd) { options.default_dyn_dim_value = dd; } static void set_default_loop_iterations(onnx_options& options, int64_t value) { options.max_loop_iterations = value; } static void set_external_data_path(onnx_options& options, const char* external_data_path) { options.external_data_path = std::string(external_data_path); } static void set_limit_loop_iterations(onnx_options& options, int64_t value) { options.limit_max_iterations = value; } static void set_nhwc(tf_options& options, bool is_nhwc) { options.is_nhwc = is_nhwc; } static void set_default_dim_value(tf_options& options, size_t value) { options.batch_size = value; } static void set_input_parameter_shape(onnx_options& options, const char* name, std::vector dims) { options.map_input_dims[std::string(name)] = std::move(dims); } static void set_dyn_input_parameter_shape(onnx_options& options, const char* name, std::vector dyn_dims) { options.map_dyn_input_dims[std::string(name)] = std::move(dyn_dims); } static void set_input_parameter_shape(tf_options& options, const char* name, std::vector dims) { options.map_input_dims[std::string(name)] = std::move(dims); } static void set_output_names(tf_options& options, std::vector names) { options.output_node_names = std::vector(names.begin(), names.end()); } static std::vector run_async(program& p, const parameter_map& params, void* s, std::string_view name) { execution_environment exec_env{any_ptr(s, name), true}; return p.eval(params, exec_env); } template static std::vector get_names(const std::unordered_map& m) { std::vector result; std::transform( m.begin(), m.end(), std::back_inserter(result), [](auto&& p) { return p.first.c_str(); }); return result; } template static std::set make_set(const T* x, std::size_t n) { return {x, x + n}; } static void quantize_fp16_with_op_names(program& prog, std::vector& names) { if(names.empty()) { names = {"all"}; } migraphx::quantize_fp16(prog, names); } static void quantize_bf16_with_op_names(program& prog, std::vector& names) { if(names.empty()) { names = {"all"}; } migraphx::quantize_bf16(prog, names); } struct quantize_int8_options { std::vector calibration = {}; std::unordered_set op_names = {}; }; static void add_op_name(quantize_int8_options& options, const char* name) { options.op_names.insert(name); } static void add_calibration_data(quantize_int8_options& options, parameter_map& data) { options.calibration.push_back(data); } static void quantize_int8_wrap(program& prog, const target& t, quantize_int8_options& options) { if(options.op_names.empty()) { options.op_names = {"dot", "convolution"}; } migraphx::quantize_int8(prog, t, options.calibration, options.op_names); } struct quantize_fp8_options { std::vector calibration = {}; }; static void add_calibration_data(quantize_fp8_options& options, parameter_map& data) { options.calibration.push_back(data); } static void quantize_fp8_wrap(program& prog, const target& t, quantize_fp8_options& options) { migraphx::quantize_fp8(prog, t, options.calibration); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" #endif static operation create_op(const char* name, const char* attributes, va_list vlist) { std::string sattributes = attributes == nullptr ? "" : attributes; std::vector buffer(sattributes.size() * 2); std::vsnprintf(buffer.data(), buffer.size(), sattributes.c_str(), vlist); value v = value::object{}; if(attributes != nullptr) { v = from_json_string(convert_to_json(std::string(buffer.data()))); } auto op = make_op(name, v); return op; } #ifdef __clang__ #pragma clang diagnostic pop #endif template static bool equal(const T& x, const T& y) { return x == y; } static std::vector run(program& p, const parameter_map& params) { return p.eval(params); } static std::vector get_output_shapes(program& p) { return p.get_output_shapes(); } static void print_program(const program& p) { std::cout << p << std::endl; } static void print_module(const module& m) { std::cout << m << std::endl; } static migraphx::instruction_ref add_allocation(module& m, const migraphx::shape& s) { return m.add_instruction(migraphx::make_op("allocate", {{"shape", migraphx::to_value(s)}}), {}); } struct experimental_custom_op { std::string name; experimental_custom_op() = default; experimental_custom_op(std::string pname) : name(std::move(pname)) {} }; template struct custom_operation { template static auto reflect(Self&, F) { return pack(); } value attributes() const { return {{"custom_op", true}, {"target", op.runs_on_offload_target() ? "gpu" : "cpu"}}; } CustomOp op; std::string name() const { return op.xobject.name; } shape compute_shape(std::vector inputs) const { return op.compute_shape(std::move(inputs)); } // TODO: Compute method with module_args argument compute(migraphx::context ctx, migraphx::shape output_shape, std::vector inputs) const { return op.compute(std::move(ctx), std::move(output_shape), std::move(inputs)); } std::ptrdiff_t output_alias(std::vector inputs) const { auto alias_vec = op.output_alias(std::move(inputs)); // TODO: For now, only support one output alias if(alias_vec.empty()) { return -1; } if(alias_vec.size() > 1) { MIGRAPHX_THROW("Currently, CustomOps in MIGraphX only supports one output_alias"); } return alias_vec.front(); } bool runs_on_offload_target() const { return op.runs_on_offload_target(); } }; template static void register_custom_op(const CustomOp& op) { register_op(custom_operation{op}); } static migraphx::context get_context(const program& p) { return p.get_context(); } } // namespace migraphx template > static Target* object_cast(U* x) { return reinterpret_cast(x); } template > static const Target* object_cast(const U* x) { return reinterpret_cast(x); } template > static Target* allocate(Ts&&... xs) { if constexpr(std::is_aggregate{}) return new Target{std::forward(xs)...}; // NOLINT else return new Target(std::forward(xs)...); // NOLINT } template static void destroy(T* x) { delete x; // NOLINT } // TODO: Move to interface preamble template struct manage_generic_ptr { manage_generic_ptr() = default; manage_generic_ptr(std::nullptr_t) {} manage_generic_ptr(void* pdata, const char* obj_tname, C pcopier, D pdeleter) : data(nullptr), obj_typename(obj_tname), copier(pcopier), deleter(pdeleter) { copier(&data, pdata); } manage_generic_ptr(const manage_generic_ptr& rhs) : data(nullptr), obj_typename(rhs.obj_typename), copier(rhs.copier), deleter(rhs.deleter) { if(copier) copier(&data, rhs.data); } manage_generic_ptr(manage_generic_ptr&& other) noexcept : data(other.data), obj_typename(other.obj_typename), copier(other.copier), deleter(other.deleter) { other.data = nullptr; other.obj_typename = ""; other.copier = nullptr; other.deleter = nullptr; } manage_generic_ptr& operator=(manage_generic_ptr rhs) { std::swap(data, rhs.data); std::swap(obj_typename, rhs.obj_typename); std::swap(copier, rhs.copier); std::swap(deleter, rhs.deleter); return *this; } ~manage_generic_ptr() { if(data != nullptr) deleter(data); } void* data = nullptr; const char* obj_typename = ""; C copier = nullptr; D deleter = nullptr; }; extern "C" struct migraphx_optimals; struct migraphx_optimals { template migraphx_optimals(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::set object; }; extern "C" struct migraphx_dynamic_dimension; struct migraphx_dynamic_dimension { template migraphx_dynamic_dimension(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::shape::dynamic_dimension object; }; extern "C" struct migraphx_dynamic_dimensions; struct migraphx_dynamic_dimensions { template migraphx_dynamic_dimensions(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::vector object; }; extern "C" struct migraphx_shape; struct migraphx_shape { template migraphx_shape(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::shape object; }; extern "C" struct migraphx_argument; struct migraphx_argument { template migraphx_argument(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::argument object; }; extern "C" struct migraphx_target; struct migraphx_target { template migraphx_target(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::target object; }; extern "C" struct migraphx_program_parameter_shapes; struct migraphx_program_parameter_shapes { template migraphx_program_parameter_shapes(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::unordered_map object; }; extern "C" struct migraphx_program_parameters; struct migraphx_program_parameters { template migraphx_program_parameters(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::unordered_map object; }; extern "C" struct migraphx_arguments; struct migraphx_arguments { template migraphx_arguments(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::vector object; }; extern "C" struct migraphx_shapes; struct migraphx_shapes { template migraphx_shapes(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::vector object; }; extern "C" struct migraphx_instruction; struct migraphx_instruction { template migraphx_instruction(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::instruction_ref object; }; extern "C" struct migraphx_instructions; struct migraphx_instructions { template migraphx_instructions(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::vector object; }; extern "C" struct migraphx_modules; struct migraphx_modules { template migraphx_modules(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::vector object; }; extern "C" struct migraphx_module; struct migraphx_module { template migraphx_module(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::module object; }; extern "C" struct migraphx_program; struct migraphx_program { template migraphx_program(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::program object; }; extern "C" struct migraphx_operation; struct migraphx_operation { template migraphx_operation(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::operation object; }; extern "C" struct migraphx_onnx_options; struct migraphx_onnx_options { template migraphx_onnx_options(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::onnx_options object; }; extern "C" struct migraphx_file_options; struct migraphx_file_options { template migraphx_file_options(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::file_options object; }; extern "C" struct migraphx_compile_options; struct migraphx_compile_options { template migraphx_compile_options(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::compile_options object; }; extern "C" struct migraphx_tf_options; struct migraphx_tf_options { template migraphx_tf_options(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::tf_options object; }; extern "C" struct migraphx_quantize_op_names; struct migraphx_quantize_op_names { template migraphx_quantize_op_names(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } std::vector object; }; extern "C" struct migraphx_quantize_int8_options; struct migraphx_quantize_int8_options { template migraphx_quantize_int8_options(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::quantize_int8_options object; }; extern "C" struct migraphx_quantize_fp8_options; struct migraphx_quantize_fp8_options { template migraphx_quantize_fp8_options(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::quantize_fp8_options object; }; extern "C" struct migraphx_context; struct migraphx_context { template migraphx_context(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) { } migraphx::context object; }; extern "C" struct migraphx_experimental_custom_op; struct migraphx_experimental_custom_op { template migraphx_experimental_custom_op(void* p, migraphx_experimental_custom_op_copy c, migraphx_experimental_custom_op_delete d, const char* obj_typename, Ts&&... xs) : object_ptr(p, obj_typename, c, d), xobject(std::forward(xs)...) { } manage_generic_ptr object_ptr = nullptr; migraphx::experimental_custom_op xobject; migraphx_experimental_custom_op_compute compute_f = nullptr; migraphx::argument compute(migraphx::context ctx, migraphx::shape output, std::vector inputs) const { if(compute_f == nullptr) throw std::runtime_error("compute function is missing."); std::remove_pointer_t out; std::array exception_msg; exception_msg.front() = '\0'; auto api_error_result = compute_f(&out, object_ptr.data, exception_msg.data(), exception_msg.size(), object_cast(&(ctx)), object_cast(&(output)), object_cast(&(inputs))); if(api_error_result != migraphx_status_success) { const std::string exception_str(exception_msg.data()); throw std::runtime_error("Error in compute of: " + std::string(object_ptr.obj_typename) + ": " + exception_str); } return (&out)->object; } migraphx_experimental_custom_op_compute_shape compute_shape_f = nullptr; migraphx::shape compute_shape(std::vector inputs) const { if(compute_shape_f == nullptr) throw std::runtime_error("compute_shape function is missing."); std::remove_pointer_t out; std::array exception_msg; exception_msg.front() = '\0'; auto api_error_result = compute_shape_f(&out, object_ptr.data, exception_msg.data(), exception_msg.size(), object_cast(&(inputs))); if(api_error_result != migraphx_status_success) { const std::string exception_str(exception_msg.data()); throw std::runtime_error("Error in compute_shape of: " + std::string(object_ptr.obj_typename) + ": " + exception_str); } return (&out)->object; } migraphx_experimental_custom_op_output_alias output_alias_f = nullptr; std::vector output_alias(std::vector inputs) const { if(output_alias_f == nullptr) throw std::runtime_error("output_alias function is missing."); std::array out; std::remove_pointer_t out_size = 1024; std::array exception_msg; exception_msg.front() = '\0'; auto api_error_result = output_alias_f(out.data(), &out_size, object_ptr.data, exception_msg.data(), exception_msg.size(), object_cast(&(inputs))); if(api_error_result != migraphx_status_success) { const std::string exception_str(exception_msg.data()); throw std::runtime_error("Error in output_alias of: " + std::string(object_ptr.obj_typename) + ": " + exception_str); } return {out.begin(), out.begin() + out_size}; // cppcheck-suppress returnDanglingLifetime; } migraphx_experimental_custom_op_runs_on_offload_target runs_on_offload_target_f = nullptr; bool runs_on_offload_target() const { if(runs_on_offload_target_f == nullptr) throw std::runtime_error("runs_on_offload_target function is missing."); std::remove_pointer_t out; std::array exception_msg; exception_msg.front() = '\0'; auto api_error_result = runs_on_offload_target_f( &out, object_ptr.data, exception_msg.data(), exception_msg.size()); if(api_error_result != migraphx_status_success) { const std::string exception_str(exception_msg.data()); throw std::runtime_error("Error in runs_on_offload_target of: " + std::string(object_ptr.obj_typename) + ": " + exception_str); } return out; } }; extern "C" migraphx_status migraphx_optimals_destroy(migraphx_optimals_t optimals) { auto api_error_result = migraphx::try_([&] { destroy((optimals)); }); return api_error_result; } extern "C" migraphx_status migraphx_optimals_assign_to(migraphx_optimals_t output, const_migraphx_optimals_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_optimals_create(migraphx_optimals_t* optimals, const size_t* ptr, size_t size) { auto api_error_result = migraphx::try_([&] { *optimals = object_cast( allocate>(migraphx::make_set((ptr), (size)))); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimension_destroy(migraphx_dynamic_dimension_t dynamic_dimension) { auto api_error_result = migraphx::try_([&] { destroy((dynamic_dimension)); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimension_assign_to(migraphx_dynamic_dimension_t output, const_migraphx_dynamic_dimension_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimension_create_min_max( migraphx_dynamic_dimension_t* dynamic_dimension, size_t min, size_t max) { auto api_error_result = migraphx::try_([&] { *dynamic_dimension = object_cast( allocate((min), (max))); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimension_create_min_max_optimals(migraphx_dynamic_dimension_t* dynamic_dimension, size_t min, size_t max, migraphx_optimals_t optimals) { auto api_error_result = migraphx::try_([&] { if(optimals == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter optimals: Null pointer"); *dynamic_dimension = object_cast( allocate((min), (max), (optimals->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimension_is_fixed(bool* out, const_migraphx_dynamic_dimension_t dynamic_dimension) { auto api_error_result = migraphx::try_([&] { if(dynamic_dimension == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dynamic_dimension: Null pointer"); *out = (dynamic_dimension->object).is_fixed(); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimension_equal(bool* out, const_migraphx_dynamic_dimension_t dynamic_dimension, const_migraphx_dynamic_dimension_t x) { auto api_error_result = migraphx::try_([&] { if(dynamic_dimension == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dynamic_dimension: Null pointer"); if(x == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter x: Null pointer"); *out = migraphx::equal((dynamic_dimension->object), (x->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimensions_destroy(migraphx_dynamic_dimensions_t dynamic_dimensions) { auto api_error_result = migraphx::try_([&] { destroy((dynamic_dimensions)); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimensions_assign_to(migraphx_dynamic_dimensions_t output, const_migraphx_dynamic_dimensions_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimensions_create(migraphx_dynamic_dimensions_t* dynamic_dimensions, const const_migraphx_dynamic_dimension_t* ptr, size_t size) { auto api_error_result = migraphx::try_([&] { *dynamic_dimensions = object_cast( allocate>( migraphx::to_obj_vector((ptr), (size)))); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimensions_size(size_t* out, migraphx_dynamic_dimensions_t dynamic_dimensions) { auto api_error_result = migraphx::try_([&] { if(dynamic_dimensions == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dynamic_dimensions: Null pointer"); *out = (dynamic_dimensions->object).size(); }); return api_error_result; } extern "C" migraphx_status migraphx_dynamic_dimensions_get(const_migraphx_dynamic_dimension_t* out, migraphx_dynamic_dimensions_t dynamic_dimensions, size_t idx) { auto api_error_result = migraphx::try_([&] { if(dynamic_dimensions == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dynamic_dimensions: Null pointer"); *out = object_cast( &((dynamic_dimensions->object).at((idx)))); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_destroy(migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { destroy((shape)); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_assign_to(migraphx_shape_t output, const_migraphx_shape_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_shape_create(migraphx_shape_t* shape, migraphx_shape_datatype_t type, size_t* lengths, size_t lengths_size) { auto api_error_result = migraphx::try_([&] { if(lengths == nullptr and lengths_size != 0) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter lengths: Null pointer"); *shape = object_cast( allocate((migraphx::to_shape_type(type)), (std::vector(lengths, lengths + lengths_size)))); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_create_with_strides(migraphx_shape_t* shape, migraphx_shape_datatype_t type, size_t* lengths, size_t lengths_size, size_t* strides, size_t strides_size) { auto api_error_result = migraphx::try_([&] { if(lengths == nullptr and lengths_size != 0) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter lengths: Null pointer"); if(strides == nullptr and strides_size != 0) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter strides: Null pointer"); *shape = object_cast( allocate((migraphx::to_shape_type(type)), (std::vector(lengths, lengths + lengths_size)), (std::vector(strides, strides + strides_size)))); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_create_scalar(migraphx_shape_t* shape, migraphx_shape_datatype_t type) { auto api_error_result = migraphx::try_([&] { *shape = object_cast( allocate((migraphx::to_shape_type(type)))); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_create_dynamic(migraphx_shape_t* shape, migraphx_shape_datatype_t type, migraphx_dynamic_dimensions_t dims) { auto api_error_result = migraphx::try_([&] { if(dims == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dims: Null pointer"); *shape = object_cast( allocate((migraphx::to_shape_type(type)), (dims->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_lengths(const size_t** out, size_t* out_size, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(out == nullptr or out_size == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter out: Null pointer"); if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); auto&& api_result = (shape->object).lens(); *out = api_result.data(); *out_size = api_result.size(); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_strides(const size_t** out, size_t* out_size, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(out == nullptr or out_size == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter out: Null pointer"); if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); auto&& api_result = (shape->object).strides(); *out = api_result.data(); *out_size = api_result.size(); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_dyn_dims(migraphx_dynamic_dimensions_t* out, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = allocate((shape->object).dyn_dims()); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_type(migraphx_shape_datatype_t* out, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(out == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter out: Null pointer"); if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = migraphx::to_shape_type((shape->object).type()); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_elements(size_t* out, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = (shape->object).elements(); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_bytes(size_t* out, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = (shape->object).bytes(); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_ndim(size_t* out, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = (shape->object).ndim(); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_equal(bool* out, const_migraphx_shape_t shape, const_migraphx_shape_t x) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); if(x == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter x: Null pointer"); *out = migraphx::equal((shape->object), (x->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_standard(bool* out, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = (shape->object).standard(); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_dynamic(bool* out, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = (shape->object).dynamic(); }); return api_error_result; } extern "C" migraphx_status migraphx_shape_index(size_t* out, const_migraphx_shape_t shape, size_t i) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = (shape->object).index((i)); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_destroy(migraphx_argument_t argument) { auto api_error_result = migraphx::try_([&] { destroy((argument)); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_assign_to(migraphx_argument_t output, const_migraphx_argument_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_argument_create(migraphx_argument_t* argument, const_migraphx_shape_t shape, void* buffer) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *argument = object_cast( allocate((shape->object), (buffer))); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_create_empty(migraphx_argument_t* argument, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *argument = object_cast(allocate((shape->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_shape(const_migraphx_shape_t* out, const_migraphx_argument_t argument) { auto api_error_result = migraphx::try_([&] { if(argument == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter argument: Null pointer"); *out = object_cast(&((argument->object).get_shape())); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_buffer(char** out, const_migraphx_argument_t argument) { auto api_error_result = migraphx::try_([&] { if(argument == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter argument: Null pointer"); *out = (argument->object).data(); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_equal(bool* out, const_migraphx_argument_t argument, const_migraphx_argument_t x) { auto api_error_result = migraphx::try_([&] { if(argument == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter argument: Null pointer"); if(x == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter x: Null pointer"); *out = migraphx::equal((argument->object), (x->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_save(const_migraphx_argument_t a, const char* filename) { auto api_error_result = migraphx::try_([&] { if(a == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter a: Null pointer"); migraphx::save_argument((a->object), (filename)); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_load(migraphx_argument_t* out, const char* filename) { auto api_error_result = migraphx::try_( [&] { *out = allocate(migraphx::load_argument((filename))); }); return api_error_result; } extern "C" migraphx_status migraphx_argument_generate(migraphx_argument_t* out, const_migraphx_shape_t s, size_t seed) { auto api_error_result = migraphx::try_([&] { if(s == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter s: Null pointer"); *out = allocate(migraphx::generate_argument((s->object), (seed))); }); return api_error_result; } extern "C" migraphx_status migraphx_target_destroy(migraphx_target_t target) { auto api_error_result = migraphx::try_([&] { destroy((target)); }); return api_error_result; } extern "C" migraphx_status migraphx_target_assign_to(migraphx_target_t output, const_migraphx_target_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_target_create(migraphx_target_t* target, const char* name) { auto api_error_result = migraphx::try_([&] { *target = object_cast( allocate(migraphx::get_target((name)))); }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameter_shapes_destroy( migraphx_program_parameter_shapes_t program_parameter_shapes) { auto api_error_result = migraphx::try_([&] { destroy((program_parameter_shapes)); }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameter_shapes_assign_to(migraphx_program_parameter_shapes_t output, const_migraphx_program_parameter_shapes_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameter_shapes_size(size_t* out, migraphx_program_parameter_shapes_t program_parameter_shapes) { auto api_error_result = migraphx::try_([&] { if(program_parameter_shapes == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program_parameter_shapes: Null pointer"); *out = (program_parameter_shapes->object).size(); }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameter_shapes_get(const_migraphx_shape_t* out, migraphx_program_parameter_shapes_t program_parameter_shapes, const char* name) { auto api_error_result = migraphx::try_([&] { if(program_parameter_shapes == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program_parameter_shapes: Null pointer"); *out = object_cast(&((program_parameter_shapes->object).at((name)))); }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameter_shapes_names( const char** out, migraphx_program_parameter_shapes_t program_parameter_shapes) { auto api_error_result = migraphx::try_([&] { if(out == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter out: Null pointer"); if(program_parameter_shapes == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program_parameter_shapes: Null pointer"); auto&& api_result = migraphx::get_names((program_parameter_shapes->object)); std::copy(api_result.begin(), api_result.end(), out); }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameters_destroy(migraphx_program_parameters_t program_parameters) { auto api_error_result = migraphx::try_([&] { destroy((program_parameters)); }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameters_assign_to(migraphx_program_parameters_t output, const_migraphx_program_parameters_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameters_create(migraphx_program_parameters_t* program_parameters) { auto api_error_result = migraphx::try_([&] { *program_parameters = object_cast( allocate>()); }); return api_error_result; } extern "C" migraphx_status migraphx_program_parameters_add(migraphx_program_parameters_t program_parameters, const char* name, const_migraphx_argument_t argument) { auto api_error_result = migraphx::try_([&] { if(program_parameters == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program_parameters: Null pointer"); if(argument == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter argument: Null pointer"); (program_parameters->object)[(name)] = (argument->object); }); return api_error_result; } extern "C" migraphx_status migraphx_arguments_destroy(migraphx_arguments_t arguments) { auto api_error_result = migraphx::try_([&] { destroy((arguments)); }); return api_error_result; } extern "C" migraphx_status migraphx_arguments_assign_to(migraphx_arguments_t output, const_migraphx_arguments_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_arguments_size(size_t* out, migraphx_arguments_t arguments) { auto api_error_result = migraphx::try_([&] { if(arguments == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter arguments: Null pointer"); *out = (arguments->object).size(); }); return api_error_result; } extern "C" migraphx_status migraphx_arguments_get(const_migraphx_argument_t* out, migraphx_arguments_t arguments, size_t idx) { auto api_error_result = migraphx::try_([&] { if(arguments == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter arguments: Null pointer"); *out = object_cast(&((arguments->object).at((idx)))); }); return api_error_result; } extern "C" migraphx_status migraphx_shapes_destroy(migraphx_shapes_t shapes) { auto api_error_result = migraphx::try_([&] { destroy((shapes)); }); return api_error_result; } extern "C" migraphx_status migraphx_shapes_assign_to(migraphx_shapes_t output, const_migraphx_shapes_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_shapes_size(size_t* out, migraphx_shapes_t shapes) { auto api_error_result = migraphx::try_([&] { if(shapes == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shapes: Null pointer"); *out = (shapes->object).size(); }); return api_error_result; } extern "C" migraphx_status migraphx_shapes_get(const_migraphx_shape_t* out, migraphx_shapes_t shapes, size_t idx) { auto api_error_result = migraphx::try_([&] { if(shapes == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shapes: Null pointer"); *out = object_cast(&((shapes->object).at((idx)))); }); return api_error_result; } extern "C" migraphx_status migraphx_instruction_destroy(migraphx_instruction_t instruction) { auto api_error_result = migraphx::try_([&] { destroy((instruction)); }); return api_error_result; } extern "C" migraphx_status migraphx_instruction_assign_to(migraphx_instruction_t output, const_migraphx_instruction_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_instructions_destroy(migraphx_instructions_t instructions) { auto api_error_result = migraphx::try_([&] { destroy((instructions)); }); return api_error_result; } extern "C" migraphx_status migraphx_instructions_assign_to(migraphx_instructions_t output, const_migraphx_instructions_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_instructions_create(migraphx_instructions_t* instructions, const const_migraphx_instruction_t* ptr, size_t size) { auto api_error_result = migraphx::try_([&] { *instructions = object_cast(allocate>( migraphx::to_obj_vector((ptr), (size)))); }); return api_error_result; } extern "C" migraphx_status migraphx_modules_destroy(migraphx_modules_t modules) { auto api_error_result = migraphx::try_([&] { destroy((modules)); }); return api_error_result; } extern "C" migraphx_status migraphx_modules_assign_to(migraphx_modules_t output, const_migraphx_modules_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_modules_create(migraphx_modules_t* modules, migraphx_module_t* ptr, size_t size) { auto api_error_result = migraphx::try_([&] { *modules = object_cast(allocate>( migraphx::to_objptr_vector((ptr), (size)))); }); return api_error_result; } extern "C" migraphx_status migraphx_module_create(migraphx_module_t* module, char* name) { auto api_error_result = migraphx::try_([&] { if(name == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter name: Null pointer"); *module = object_cast(allocate((std::string(name)))); }); return api_error_result; } extern "C" migraphx_status migraphx_module_print(const_migraphx_module_t module) { auto api_error_result = migraphx::try_([&] { if(module == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter module: Null pointer"); migraphx::print_module((module->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_module_add_instruction(migraphx_instruction_t* out, migraphx_module_t module, migraphx_operation_t op, migraphx_instructions_t args) { auto api_error_result = migraphx::try_([&] { if(module == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter module: Null pointer"); if(op == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter op: Null pointer"); if(args == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter args: Null pointer"); *out = allocate( (module->object).add_instruction((op->object), (args->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_module_add_instruction_with_mod_args(migraphx_instruction_t* out, migraphx_module_t module, migraphx_operation_t op, migraphx_instructions_t args, migraphx_modules_t module_refs) { auto api_error_result = migraphx::try_([&] { if(module == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter module: Null pointer"); if(op == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter op: Null pointer"); if(args == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter args: Null pointer"); if(module_refs == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter module_refs: Null pointer"); *out = allocate( (module->object).add_instruction((op->object), (args->object), (module_refs->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_module_add_literal(migraphx_instruction_t* out, migraphx_module_t module, const_migraphx_shape_t shape, const char* buffer) { auto api_error_result = migraphx::try_([&] { if(module == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter module: Null pointer"); if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = allocate( (module->object).add_literal((shape->object), (buffer))); }); return api_error_result; } extern "C" migraphx_status migraphx_module_add_parameter(migraphx_instruction_t* out, migraphx_module_t module, const char* name, const_migraphx_shape_t shape) { auto api_error_result = migraphx::try_([&] { if(module == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter module: Null pointer"); if(shape == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter shape: Null pointer"); *out = allocate( (module->object).add_parameter((name), (shape->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_module_add_return(migraphx_instruction_t* out, migraphx_module_t module, migraphx_instructions_t args) { auto api_error_result = migraphx::try_([&] { if(module == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter module: Null pointer"); if(args == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter args: Null pointer"); *out = allocate((module->object).add_return((args->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_module_add_allocation(migraphx_instruction_t* out, migraphx_module_t module, const_migraphx_shape_t s) { auto api_error_result = migraphx::try_([&] { if(module == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter module: Null pointer"); if(s == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter s: Null pointer"); *out = allocate( migraphx::add_allocation((module->object), (s->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_program_destroy(migraphx_program_t program) { auto api_error_result = migraphx::try_([&] { destroy((program)); }); return api_error_result; } extern "C" migraphx_status migraphx_program_assign_to(migraphx_program_t output, const_migraphx_program_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_program_create(migraphx_program_t* program) { auto api_error_result = migraphx::try_( [&] { *program = object_cast(allocate()); }); return api_error_result; } extern "C" migraphx_status migraphx_program_get_main_module(migraphx_module_t* out, migraphx_program_t program) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); *out = object_cast((program->object).get_main_module()); }); return api_error_result; } extern "C" migraphx_status migraphx_program_create_module(migraphx_module_t* out, migraphx_program_t program, const char* name) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); *out = object_cast((program->object).create_module((name))); }); return api_error_result; } extern "C" migraphx_status migraphx_program_compile(migraphx_program_t program, migraphx_target_t target, migraphx_compile_options_t options) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); if(target == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter target: Null pointer"); if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); (program->object).compile((target->object), (options->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_program_get_parameter_shapes(migraphx_program_parameter_shapes_t* out, migraphx_program_t program) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); *out = allocate((program->object).get_parameter_shapes()); }); return api_error_result; } extern "C" migraphx_status migraphx_program_get_output_shapes(migraphx_shapes_t* out, migraphx_program_t program) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); *out = allocate(migraphx::get_output_shapes((program->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_program_print(const_migraphx_program_t program) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); migraphx::print_program((program->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_program_sort(migraphx_program_t program) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); (program->object).sort(); }); return api_error_result; } extern "C" migraphx_status migraphx_program_run(migraphx_arguments_t* out, migraphx_program_t program, migraphx_program_parameters_t params) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); if(params == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter params: Null pointer"); *out = allocate(migraphx::run((program->object), (params->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_program_run_async(migraphx_arguments_t* out, migraphx_program_t program, migraphx_program_parameters_t params, void* s, const char* name) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); if(params == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter params: Null pointer"); *out = allocate( migraphx::run_async((program->object), (params->object), (s), (name))); }); return api_error_result; } extern "C" migraphx_status migraphx_program_equal(bool* out, const_migraphx_program_t program, const_migraphx_program_t x) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); if(x == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter x: Null pointer"); *out = migraphx::equal((program->object), (x->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_program_experimental_get_context(migraphx_context_t* out, const_migraphx_program_t program) { auto api_error_result = migraphx::try_([&] { if(program == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter program: Null pointer"); *out = allocate(migraphx::get_context((program->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_operation_destroy(migraphx_operation_t operation) { auto api_error_result = migraphx::try_([&] { destroy((operation)); }); return api_error_result; } extern "C" migraphx_status migraphx_operation_assign_to(migraphx_operation_t output, const_migraphx_operation_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_operation_create(migraphx_operation_t* operation, const char* name, const char* attributes, ...) { va_list vlist; va_start(vlist, attributes); auto api_error_result = migraphx::try_([&] { *operation = object_cast( allocate(migraphx::create_op((name), (attributes), (vlist)))); }); va_end(vlist); return api_error_result; } extern "C" migraphx_status migraphx_operation_name(char* out, size_t out_size, migraphx_operation_t operation) { auto api_error_result = migraphx::try_([&] { if(out == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter out: Null pointer"); if(operation == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter operation: Null pointer"); auto&& api_result = (operation->object).name(); auto* it = std::copy_n(api_result.begin(), std::min(api_result.size(), out_size - 1), out); *it = '\0'; }); return api_error_result; } extern "C" migraphx_status migraphx_load(migraphx_program_t* out, const char* name, migraphx_file_options_t options) { auto api_error_result = migraphx::try_([&] { if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); *out = allocate(migraphx::load((name), (options->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_save(migraphx_program_t p, const char* name, migraphx_file_options_t options) { auto api_error_result = migraphx::try_([&] { if(p == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter p: Null pointer"); if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); migraphx::save((p->object), (name), (options->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_destroy(migraphx_onnx_options_t onnx_options) { auto api_error_result = migraphx::try_([&] { destroy((onnx_options)); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_assign_to(migraphx_onnx_options_t output, const_migraphx_onnx_options_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_create(migraphx_onnx_options_t* onnx_options) { auto api_error_result = migraphx::try_([&] { *onnx_options = object_cast(allocate()); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_set_input_parameter_shape( migraphx_onnx_options_t onnx_options, const char* name, size_t* dims, size_t dims_size) { auto api_error_result = migraphx::try_([&] { if(onnx_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter onnx_options: Null pointer"); if(dims == nullptr and dims_size != 0) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dims: Null pointer"); migraphx::set_input_parameter_shape( (onnx_options->object), (name), (std::vector(dims, dims + dims_size))); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_set_dyn_input_parameter_shape( migraphx_onnx_options_t onnx_options, const char* name, migraphx_dynamic_dimensions_t dims) { auto api_error_result = migraphx::try_([&] { if(onnx_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter onnx_options: Null pointer"); if(dims == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dims: Null pointer"); migraphx::set_dyn_input_parameter_shape((onnx_options->object), (name), (dims->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_set_default_dim_value(migraphx_onnx_options_t onnx_options, size_t value) { auto api_error_result = migraphx::try_([&] { if(onnx_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter onnx_options: Null pointer"); migraphx::set_default_dim_value((onnx_options->object), (value)); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_set_default_dyn_dim_value(migraphx_onnx_options_t onnx_options, const_migraphx_dynamic_dimension_t dd) { auto api_error_result = migraphx::try_([&] { if(onnx_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter onnx_options: Null pointer"); if(dd == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dd: Null pointer"); migraphx::set_default_dyn_dim_value((onnx_options->object), (dd->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_set_default_loop_iterations(migraphx_onnx_options_t onnx_options, int64_t value) { auto api_error_result = migraphx::try_([&] { if(onnx_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter onnx_options: Null pointer"); migraphx::set_default_loop_iterations((onnx_options->object), (value)); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_set_limit_loop_iterations(migraphx_onnx_options_t onnx_options, int64_t value) { auto api_error_result = migraphx::try_([&] { if(onnx_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter onnx_options: Null pointer"); migraphx::set_limit_loop_iterations((onnx_options->object), (value)); }); return api_error_result; } extern "C" migraphx_status migraphx_onnx_options_set_external_data_path(migraphx_onnx_options_t onnx_options, const char* external_data_path) { auto api_error_result = migraphx::try_([&] { if(onnx_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter onnx_options: Null pointer"); migraphx::set_external_data_path((onnx_options->object), (external_data_path)); }); return api_error_result; } extern "C" migraphx_status migraphx_file_options_destroy(migraphx_file_options_t file_options) { auto api_error_result = migraphx::try_([&] { destroy((file_options)); }); return api_error_result; } extern "C" migraphx_status migraphx_file_options_assign_to(migraphx_file_options_t output, const_migraphx_file_options_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_file_options_create(migraphx_file_options_t* file_options) { auto api_error_result = migraphx::try_([&] { *file_options = object_cast(allocate()); }); return api_error_result; } extern "C" migraphx_status migraphx_file_options_set_file_format(migraphx_file_options_t file_options, const char* format) { auto api_error_result = migraphx::try_([&] { if(file_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter file_options: Null pointer"); migraphx::set_file_format((file_options->object), (format)); }); return api_error_result; } extern "C" migraphx_status migraphx_compile_options_destroy(migraphx_compile_options_t compile_options) { auto api_error_result = migraphx::try_([&] { destroy((compile_options)); }); return api_error_result; } extern "C" migraphx_status migraphx_compile_options_assign_to(migraphx_compile_options_t output, const_migraphx_compile_options_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_compile_options_create(migraphx_compile_options_t* compile_options) { auto api_error_result = migraphx::try_([&] { *compile_options = object_cast(allocate()); }); return api_error_result; } extern "C" migraphx_status migraphx_compile_options_set_offload_copy(migraphx_compile_options_t compile_options, bool value) { auto api_error_result = migraphx::try_([&] { if(compile_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter compile_options: Null pointer"); migraphx::set_offload_copy((compile_options->object), (value)); }); return api_error_result; } extern "C" migraphx_status migraphx_compile_options_set_fast_math(migraphx_compile_options_t compile_options, bool value) { auto api_error_result = migraphx::try_([&] { if(compile_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter compile_options: Null pointer"); migraphx::set_fast_math((compile_options->object), (value)); }); return api_error_result; } extern "C" migraphx_status migraphx_compile_options_set_exhaustive_tune_flag(migraphx_compile_options_t compile_options, bool value) { auto api_error_result = migraphx::try_([&] { if(compile_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter compile_options: Null pointer"); migraphx::set_exhaustive_tune_flag((compile_options->object), (value)); }); return api_error_result; } extern "C" migraphx_status migraphx_parse_onnx(migraphx_program_t* out, const char* name, migraphx_onnx_options_t options) { auto api_error_result = migraphx::try_([&] { if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); *out = allocate(migraphx::parse_onnx((name), (options->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_parse_onnx_buffer(migraphx_program_t* out, const void* data, size_t size, migraphx_onnx_options_t options) { auto api_error_result = migraphx::try_([&] { if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); *out = allocate( migraphx::parse_onnx_buffer((data), (size), (options->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_tf_options_destroy(migraphx_tf_options_t tf_options) { auto api_error_result = migraphx::try_([&] { destroy((tf_options)); }); return api_error_result; } extern "C" migraphx_status migraphx_tf_options_assign_to(migraphx_tf_options_t output, const_migraphx_tf_options_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_tf_options_create(migraphx_tf_options_t* tf_options) { auto api_error_result = migraphx::try_([&] { *tf_options = object_cast(allocate()); }); return api_error_result; } extern "C" migraphx_status migraphx_tf_options_set_nhwc(migraphx_tf_options_t tf_options, bool is_nhwc) { auto api_error_result = migraphx::try_([&] { if(tf_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter tf_options: Null pointer"); migraphx::set_nhwc((tf_options->object), (is_nhwc)); }); return api_error_result; } extern "C" migraphx_status migraphx_tf_options_set_input_parameter_shape( migraphx_tf_options_t tf_options, const char* name, size_t* dims, size_t dims_size) { auto api_error_result = migraphx::try_([&] { if(tf_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter tf_options: Null pointer"); if(dims == nullptr and dims_size != 0) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter dims: Null pointer"); migraphx::set_input_parameter_shape( (tf_options->object), (name), (std::vector(dims, dims + dims_size))); }); return api_error_result; } extern "C" migraphx_status migraphx_tf_options_set_default_dim_value(migraphx_tf_options_t tf_options, size_t value) { auto api_error_result = migraphx::try_([&] { if(tf_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter tf_options: Null pointer"); migraphx::set_default_dim_value((tf_options->object), (value)); }); return api_error_result; } extern "C" migraphx_status migraphx_tf_options_set_output_names(migraphx_tf_options_t tf_options, const char** names, size_t names_size) { auto api_error_result = migraphx::try_([&] { if(tf_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter tf_options: Null pointer"); if(names == nullptr and names_size != 0) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter names: Null pointer"); migraphx::set_output_names((tf_options->object), (std::vector(names, names + names_size))); }); return api_error_result; } extern "C" migraphx_status migraphx_parse_tf(migraphx_program_t* out, const char* name, migraphx_tf_options_t options) { auto api_error_result = migraphx::try_([&] { if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); *out = allocate(migraphx::parse_tf((name), (options->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_parse_tf_buffer(migraphx_program_t* out, const void* data, size_t size, migraphx_tf_options_t options) { auto api_error_result = migraphx::try_([&] { if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); *out = allocate( migraphx::parse_tf_buffer((data), (size), (options->object))); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_op_names_destroy(migraphx_quantize_op_names_t quantize_op_names) { auto api_error_result = migraphx::try_([&] { destroy((quantize_op_names)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_op_names_assign_to(migraphx_quantize_op_names_t output, const_migraphx_quantize_op_names_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_op_names_create(migraphx_quantize_op_names_t* quantize_op_names) { auto api_error_result = migraphx::try_([&] { *quantize_op_names = object_cast(allocate>()); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_op_names_add(migraphx_quantize_op_names_t quantize_op_names, const char* name) { auto api_error_result = migraphx::try_([&] { if(quantize_op_names == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter quantize_op_names: Null pointer"); (quantize_op_names->object).push_back((name)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_fp16_with_op_names(migraphx_program_t prog, migraphx_quantize_op_names_t name) { auto api_error_result = migraphx::try_([&] { if(prog == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter prog: Null pointer"); if(name == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter name: Null pointer"); migraphx::quantize_fp16_with_op_names((prog->object), (name->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_fp16(migraphx_program_t prog) { auto api_error_result = migraphx::try_([&] { if(prog == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter prog: Null pointer"); migraphx::quantize_fp16((prog->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_bf16_with_op_names(migraphx_program_t prog, migraphx_quantize_op_names_t name) { auto api_error_result = migraphx::try_([&] { if(prog == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter prog: Null pointer"); if(name == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter name: Null pointer"); migraphx::quantize_bf16_with_op_names((prog->object), (name->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_bf16(migraphx_program_t prog) { auto api_error_result = migraphx::try_([&] { if(prog == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter prog: Null pointer"); migraphx::quantize_bf16((prog->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_int8_options_destroy(migraphx_quantize_int8_options_t quantize_int8_options) { auto api_error_result = migraphx::try_([&] { destroy((quantize_int8_options)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_int8_options_assign_to(migraphx_quantize_int8_options_t output, const_migraphx_quantize_int8_options_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_int8_options_create(migraphx_quantize_int8_options_t* quantize_int8_options) { auto api_error_result = migraphx::try_([&] { *quantize_int8_options = object_cast( allocate()); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_int8_options_add_op_name(migraphx_quantize_int8_options_t quantize_int8_options, const char* name) { auto api_error_result = migraphx::try_([&] { if(quantize_int8_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter quantize_int8_options: Null pointer"); migraphx::add_op_name((quantize_int8_options->object), (name)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_int8_options_add_calibration_data( migraphx_quantize_int8_options_t quantize_int8_options, migraphx_program_parameters_t data) { auto api_error_result = migraphx::try_([&] { if(quantize_int8_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter quantize_int8_options: Null pointer"); if(data == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter data: Null pointer"); migraphx::add_calibration_data((quantize_int8_options->object), (data->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_int8(migraphx_program_t prog, migraphx_target_t target, migraphx_quantize_int8_options_t options) { auto api_error_result = migraphx::try_([&] { if(prog == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter prog: Null pointer"); if(target == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter target: Null pointer"); if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); migraphx::quantize_int8_wrap((prog->object), (target->object), (options->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_fp8_options_destroy(migraphx_quantize_fp8_options_t quantize_fp8_options) { auto api_error_result = migraphx::try_([&] { destroy((quantize_fp8_options)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_fp8_options_assign_to(migraphx_quantize_fp8_options_t output, const_migraphx_quantize_fp8_options_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_fp8_options_create(migraphx_quantize_fp8_options_t* quantize_fp8_options) { auto api_error_result = migraphx::try_([&] { *quantize_fp8_options = object_cast( allocate()); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_fp8_options_add_calibration_data( migraphx_quantize_fp8_options_t quantize_fp8_options, migraphx_program_parameters_t data) { auto api_error_result = migraphx::try_([&] { if(quantize_fp8_options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter quantize_fp8_options: Null pointer"); if(data == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter data: Null pointer"); migraphx::add_calibration_data((quantize_fp8_options->object), (data->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_quantize_fp8(migraphx_program_t prog, migraphx_target_t target, migraphx_quantize_fp8_options_t options) { auto api_error_result = migraphx::try_([&] { if(prog == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter prog: Null pointer"); if(target == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter target: Null pointer"); if(options == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter options: Null pointer"); migraphx::quantize_fp8_wrap((prog->object), (target->object), (options->object)); }); return api_error_result; } extern "C" migraphx_status migraphx_context_finish(const_migraphx_context_t context) { auto api_error_result = migraphx::try_([&] { if(context == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter context: Null pointer"); (context->object).finish(); }); return api_error_result; } extern "C" migraphx_status migraphx_context_get_queue(void** out, migraphx_context_t context) { auto api_error_result = migraphx::try_([&] { if(context == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter context: Null pointer"); *out = (context->object).get_queue().unsafe_get(); }); return api_error_result; } extern "C" migraphx_status migraphx_experimental_custom_op_destroy(migraphx_experimental_custom_op_t experimental_custom_op) { auto api_error_result = migraphx::try_([&] { destroy((experimental_custom_op)); }); return api_error_result; } extern "C" migraphx_status migraphx_experimental_custom_op_assign_to(migraphx_experimental_custom_op_t output, const_migraphx_experimental_custom_op_t input) { auto api_error_result = migraphx::try_([&] { *output = *input; }); return api_error_result; } extern "C" migraphx_status migraphx_experimental_custom_op_create(migraphx_experimental_custom_op_t* experimental_custom_op, void* obj, migraphx_experimental_custom_op_copy c, migraphx_experimental_custom_op_delete d, const char* obj_typename, const char* name) { auto api_error_result = migraphx::try_([&] { *experimental_custom_op = allocate((obj), (c), (d), (obj_typename), (name)); }); return api_error_result; } extern "C" migraphx_status migraphx_experimental_custom_op_set_compute(migraphx_experimental_custom_op_t obj, migraphx_experimental_custom_op_compute input) { auto api_error_result = migraphx::try_([&] { (obj)->compute_f = (input); }); return api_error_result; } extern "C" migraphx_status migraphx_experimental_custom_op_set_compute_shape( migraphx_experimental_custom_op_t obj, migraphx_experimental_custom_op_compute_shape input) { auto api_error_result = migraphx::try_([&] { (obj)->compute_shape_f = (input); }); return api_error_result; } extern "C" migraphx_status migraphx_experimental_custom_op_set_output_alias(migraphx_experimental_custom_op_t obj, migraphx_experimental_custom_op_output_alias input) { auto api_error_result = migraphx::try_([&] { (obj)->output_alias_f = (input); }); return api_error_result; } extern "C" migraphx_status migraphx_experimental_custom_op_set_runs_on_offload_target( migraphx_experimental_custom_op_t obj, migraphx_experimental_custom_op_runs_on_offload_target input) { auto api_error_result = migraphx::try_([&] { (obj)->runs_on_offload_target_f = (input); }); return api_error_result; } extern "C" migraphx_status migraphx_experimental_custom_op_register(migraphx_experimental_custom_op_t experimental_custom_op) { auto api_error_result = migraphx::try_([&] { if(experimental_custom_op == nullptr) MIGRAPHX_THROW(migraphx_status_bad_param, "Bad parameter experimental_custom_op: Null pointer"); migraphx::register_custom_op((*experimental_custom_op)); }); return api_error_result; } ROCm-AMDMIGraphX-46524e8/src/api/include/000077500000000000000000000000001510465702400175235ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/api/include/migraphx/000077500000000000000000000000001510465702400213425ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/api/include/migraphx/migraphx.h000066400000000000000000001070271510465702400233410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_C_API_MIGRAPHX_H #define MIGRAPHX_GUARD_C_API_MIGRAPHX_H #include #include #include #include // Add new types here // clang-format off #define MIGRAPHX_SHAPE_VISIT_TYPES(m) \ m(bool_type, bool) \ m(half_type, half) \ m(float_type, float) \ m(double_type, double) \ m(uint8_type, uint8_t) \ m(int8_type, int8_t) \ m(uint16_type, uint16_t) \ m(int16_type, int16_t) \ m(int32_type, int32_t) \ m(int64_type, int64_t) \ m(uint32_type, uint32_t) \ m(uint64_type, uint64_t) \ m(fp8e4m3fnuz_type, migraphx::fp8::fp8e4m3fnuz) \ m(fp8e4m3fn_type, migraphx::fp8::fp8e4m3fn) \ m(fp8e5m2_type, migraphx::fp8::fp8e5m2) \ m(bf16_type, bf16) \ m(fp8e5m2fnuz_type, migraphx::fp8::fp8e5m2fnuz) // clang-format on #ifdef __cplusplus extern "C" { #endif // return code, more to be added later typedef enum { migraphx_status_success = 0, migraphx_status_bad_param = 1, migraphx_status_unknown_target = 3, migraphx_status_unknown_error = 4, } migraphx_status; #define MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES(x, t) migraphx_shape_##x, /// An enum to represent the different data type inputs typedef enum { migraphx_shape_tuple_type, migraphx_shape_fp4x2_type, MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES) } migraphx_shape_datatype_t; #undef MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES typedef struct migraphx_optimals* migraphx_optimals_t; typedef const struct migraphx_optimals* const_migraphx_optimals_t; typedef struct migraphx_dynamic_dimension* migraphx_dynamic_dimension_t; typedef const struct migraphx_dynamic_dimension* const_migraphx_dynamic_dimension_t; typedef struct migraphx_dynamic_dimensions* migraphx_dynamic_dimensions_t; typedef const struct migraphx_dynamic_dimensions* const_migraphx_dynamic_dimensions_t; typedef struct migraphx_shape* migraphx_shape_t; typedef const struct migraphx_shape* const_migraphx_shape_t; typedef struct migraphx_argument* migraphx_argument_t; typedef const struct migraphx_argument* const_migraphx_argument_t; typedef struct migraphx_target* migraphx_target_t; typedef const struct migraphx_target* const_migraphx_target_t; typedef struct migraphx_program_parameter_shapes* migraphx_program_parameter_shapes_t; typedef const struct migraphx_program_parameter_shapes* const_migraphx_program_parameter_shapes_t; typedef struct migraphx_program_parameters* migraphx_program_parameters_t; typedef const struct migraphx_program_parameters* const_migraphx_program_parameters_t; typedef struct migraphx_arguments* migraphx_arguments_t; typedef const struct migraphx_arguments* const_migraphx_arguments_t; typedef struct migraphx_shapes* migraphx_shapes_t; typedef const struct migraphx_shapes* const_migraphx_shapes_t; typedef struct migraphx_instruction* migraphx_instruction_t; typedef const struct migraphx_instruction* const_migraphx_instruction_t; typedef struct migraphx_instructions* migraphx_instructions_t; typedef const struct migraphx_instructions* const_migraphx_instructions_t; typedef struct migraphx_modules* migraphx_modules_t; typedef const struct migraphx_modules* const_migraphx_modules_t; typedef struct migraphx_module* migraphx_module_t; typedef const struct migraphx_module* const_migraphx_module_t; typedef struct migraphx_program* migraphx_program_t; typedef const struct migraphx_program* const_migraphx_program_t; typedef struct migraphx_operation* migraphx_operation_t; typedef const struct migraphx_operation* const_migraphx_operation_t; typedef struct migraphx_onnx_options* migraphx_onnx_options_t; typedef const struct migraphx_onnx_options* const_migraphx_onnx_options_t; typedef struct migraphx_file_options* migraphx_file_options_t; typedef const struct migraphx_file_options* const_migraphx_file_options_t; typedef struct migraphx_compile_options* migraphx_compile_options_t; typedef const struct migraphx_compile_options* const_migraphx_compile_options_t; typedef struct migraphx_tf_options* migraphx_tf_options_t; typedef const struct migraphx_tf_options* const_migraphx_tf_options_t; typedef struct migraphx_quantize_op_names* migraphx_quantize_op_names_t; typedef const struct migraphx_quantize_op_names* const_migraphx_quantize_op_names_t; typedef struct migraphx_quantize_int8_options* migraphx_quantize_int8_options_t; typedef const struct migraphx_quantize_int8_options* const_migraphx_quantize_int8_options_t; typedef struct migraphx_quantize_fp8_options* migraphx_quantize_fp8_options_t; typedef const struct migraphx_quantize_fp8_options* const_migraphx_quantize_fp8_options_t; typedef struct migraphx_context* migraphx_context_t; typedef const struct migraphx_context* const_migraphx_context_t; typedef struct migraphx_experimental_custom_op* migraphx_experimental_custom_op_t; typedef const struct migraphx_experimental_custom_op* const_migraphx_experimental_custom_op_t; typedef migraphx_status (*migraphx_experimental_custom_op_compute)(migraphx_argument_t out, void* obj, char* exception_msg, size_t exception_msg_size, migraphx_context_t ctx, migraphx_shape_t output, migraphx_arguments_t inputs); typedef migraphx_status (*migraphx_experimental_custom_op_compute_shape)(migraphx_shape_t out, void* obj, char* exception_msg, size_t exception_msg_size, migraphx_shapes_t inputs); typedef migraphx_status (*migraphx_experimental_custom_op_output_alias)(size_t* out, size_t* out_size, void* obj, char* exception_msg, size_t exception_msg_size, migraphx_shapes_t inputs); typedef migraphx_status (*migraphx_experimental_custom_op_runs_on_offload_target)( bool* out, void* obj, char* exception_msg, size_t exception_msg_size); typedef migraphx_status (*migraphx_experimental_custom_op_copy)(void** out, void* input); typedef migraphx_status (*migraphx_experimental_custom_op_delete)(void* input); MIGRAPHX_C_EXPORT migraphx_status migraphx_optimals_destroy(migraphx_optimals_t optimals); MIGRAPHX_C_EXPORT migraphx_status migraphx_optimals_assign_to(migraphx_optimals_t output, const_migraphx_optimals_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_optimals_create(migraphx_optimals_t* optimals, const size_t* ptr, size_t size); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimension_destroy(migraphx_dynamic_dimension_t dynamic_dimension); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimension_assign_to( migraphx_dynamic_dimension_t output, const_migraphx_dynamic_dimension_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimension_create_min_max( migraphx_dynamic_dimension_t* dynamic_dimension, size_t min, size_t max); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimension_create_min_max_optimals(migraphx_dynamic_dimension_t* dynamic_dimension, size_t min, size_t max, migraphx_optimals_t optimals); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimension_is_fixed( bool* out, const_migraphx_dynamic_dimension_t dynamic_dimension); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimension_equal(bool* out, const_migraphx_dynamic_dimension_t dynamic_dimension, const_migraphx_dynamic_dimension_t x); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimensions_destroy(migraphx_dynamic_dimensions_t dynamic_dimensions); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimensions_assign_to( migraphx_dynamic_dimensions_t output, const_migraphx_dynamic_dimensions_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimensions_create(migraphx_dynamic_dimensions_t* dynamic_dimensions, const const_migraphx_dynamic_dimension_t* ptr, size_t size); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimensions_size(size_t* out, migraphx_dynamic_dimensions_t dynamic_dimensions); MIGRAPHX_C_EXPORT migraphx_status migraphx_dynamic_dimensions_get(const_migraphx_dynamic_dimension_t* out, migraphx_dynamic_dimensions_t dynamic_dimensions, size_t idx); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_destroy(migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_assign_to(migraphx_shape_t output, const_migraphx_shape_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_create(migraphx_shape_t* shape, migraphx_shape_datatype_t type, size_t* lengths, size_t lengths_size); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_create_with_strides(migraphx_shape_t* shape, migraphx_shape_datatype_t type, size_t* lengths, size_t lengths_size, size_t* strides, size_t strides_size); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_create_scalar(migraphx_shape_t* shape, migraphx_shape_datatype_t type); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_create_dynamic(migraphx_shape_t* shape, migraphx_shape_datatype_t type, migraphx_dynamic_dimensions_t dims); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_lengths(const size_t** out, size_t* out_size, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_strides(const size_t** out, size_t* out_size, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_dyn_dims(migraphx_dynamic_dimensions_t* out, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_type(migraphx_shape_datatype_t* out, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_elements(size_t* out, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_bytes(size_t* out, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_ndim(size_t* out, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_equal(bool* out, const_migraphx_shape_t shape, const_migraphx_shape_t x); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_standard(bool* out, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_dynamic(bool* out, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_shape_index(size_t* out, const_migraphx_shape_t shape, size_t i); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_destroy(migraphx_argument_t argument); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_assign_to(migraphx_argument_t output, const_migraphx_argument_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_create(migraphx_argument_t* argument, const_migraphx_shape_t shape, void* buffer); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_create_empty(migraphx_argument_t* argument, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_shape(const_migraphx_shape_t* out, const_migraphx_argument_t argument); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_buffer(char** out, const_migraphx_argument_t argument); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_equal(bool* out, const_migraphx_argument_t argument, const_migraphx_argument_t x); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_save(const_migraphx_argument_t a, const char* filename); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_load(migraphx_argument_t* out, const char* filename); MIGRAPHX_C_EXPORT migraphx_status migraphx_argument_generate(migraphx_argument_t* out, const_migraphx_shape_t s, size_t seed); MIGRAPHX_C_EXPORT migraphx_status migraphx_target_destroy(migraphx_target_t target); MIGRAPHX_C_EXPORT migraphx_status migraphx_target_assign_to(migraphx_target_t output, const_migraphx_target_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_target_create(migraphx_target_t* target, const char* name); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameter_shapes_destroy( migraphx_program_parameter_shapes_t program_parameter_shapes); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameter_shapes_assign_to( migraphx_program_parameter_shapes_t output, const_migraphx_program_parameter_shapes_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameter_shapes_size( size_t* out, migraphx_program_parameter_shapes_t program_parameter_shapes); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameter_shapes_get(const_migraphx_shape_t* out, migraphx_program_parameter_shapes_t program_parameter_shapes, const char* name); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameter_shapes_names( const char** out, migraphx_program_parameter_shapes_t program_parameter_shapes); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameters_destroy(migraphx_program_parameters_t program_parameters); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameters_assign_to( migraphx_program_parameters_t output, const_migraphx_program_parameters_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameters_create(migraphx_program_parameters_t* program_parameters); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_parameters_add(migraphx_program_parameters_t program_parameters, const char* name, const_migraphx_argument_t argument); MIGRAPHX_C_EXPORT migraphx_status migraphx_arguments_destroy(migraphx_arguments_t arguments); MIGRAPHX_C_EXPORT migraphx_status migraphx_arguments_assign_to(migraphx_arguments_t output, const_migraphx_arguments_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_arguments_size(size_t* out, migraphx_arguments_t arguments); MIGRAPHX_C_EXPORT migraphx_status migraphx_arguments_get(const_migraphx_argument_t* out, migraphx_arguments_t arguments, size_t idx); MIGRAPHX_C_EXPORT migraphx_status migraphx_shapes_destroy(migraphx_shapes_t shapes); MIGRAPHX_C_EXPORT migraphx_status migraphx_shapes_assign_to(migraphx_shapes_t output, const_migraphx_shapes_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_shapes_size(size_t* out, migraphx_shapes_t shapes); MIGRAPHX_C_EXPORT migraphx_status migraphx_shapes_get(const_migraphx_shape_t* out, migraphx_shapes_t shapes, size_t idx); MIGRAPHX_C_EXPORT migraphx_status migraphx_instruction_destroy(migraphx_instruction_t instruction); MIGRAPHX_C_EXPORT migraphx_status migraphx_instruction_assign_to(migraphx_instruction_t output, const_migraphx_instruction_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_instructions_destroy(migraphx_instructions_t instructions); MIGRAPHX_C_EXPORT migraphx_status migraphx_instructions_assign_to( migraphx_instructions_t output, const_migraphx_instructions_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_instructions_create( migraphx_instructions_t* instructions, const const_migraphx_instruction_t* ptr, size_t size); MIGRAPHX_C_EXPORT migraphx_status migraphx_modules_destroy(migraphx_modules_t modules); MIGRAPHX_C_EXPORT migraphx_status migraphx_modules_assign_to(migraphx_modules_t output, const_migraphx_modules_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_modules_create(migraphx_modules_t* modules, migraphx_module_t* ptr, size_t size); MIGRAPHX_C_EXPORT migraphx_status migraphx_module_create(migraphx_module_t* module, char* name); MIGRAPHX_C_EXPORT migraphx_status migraphx_module_print(const_migraphx_module_t module); MIGRAPHX_C_EXPORT migraphx_status migraphx_module_add_instruction(migraphx_instruction_t* out, migraphx_module_t module, migraphx_operation_t op, migraphx_instructions_t args); MIGRAPHX_C_EXPORT migraphx_status migraphx_module_add_instruction_with_mod_args(migraphx_instruction_t* out, migraphx_module_t module, migraphx_operation_t op, migraphx_instructions_t args, migraphx_modules_t module_refs); MIGRAPHX_C_EXPORT migraphx_status migraphx_module_add_literal(migraphx_instruction_t* out, migraphx_module_t module, const_migraphx_shape_t shape, const char* buffer); MIGRAPHX_C_EXPORT migraphx_status migraphx_module_add_parameter(migraphx_instruction_t* out, migraphx_module_t module, const char* name, const_migraphx_shape_t shape); MIGRAPHX_C_EXPORT migraphx_status migraphx_module_add_return(migraphx_instruction_t* out, migraphx_module_t module, migraphx_instructions_t args); MIGRAPHX_C_EXPORT migraphx_status migraphx_module_add_allocation(migraphx_instruction_t* out, migraphx_module_t module, const_migraphx_shape_t s); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_destroy(migraphx_program_t program); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_assign_to(migraphx_program_t output, const_migraphx_program_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_create(migraphx_program_t* program); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_get_main_module(migraphx_module_t* out, migraphx_program_t program); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_create_module(migraphx_module_t* out, migraphx_program_t program, const char* name); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_compile(migraphx_program_t program, migraphx_target_t target, migraphx_compile_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_get_parameter_shapes( migraphx_program_parameter_shapes_t* out, migraphx_program_t program); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_get_output_shapes(migraphx_shapes_t* out, migraphx_program_t program); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_print(const_migraphx_program_t program); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_sort(migraphx_program_t program); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_run(migraphx_arguments_t* out, migraphx_program_t program, migraphx_program_parameters_t params); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_run_async(migraphx_arguments_t* out, migraphx_program_t program, migraphx_program_parameters_t params, void* s, const char* name); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_equal(bool* out, const_migraphx_program_t program, const_migraphx_program_t x); MIGRAPHX_C_EXPORT migraphx_status migraphx_program_experimental_get_context( migraphx_context_t* out, const_migraphx_program_t program); MIGRAPHX_C_EXPORT migraphx_status migraphx_operation_destroy(migraphx_operation_t operation); MIGRAPHX_C_EXPORT migraphx_status migraphx_operation_assign_to(migraphx_operation_t output, const_migraphx_operation_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_operation_create(migraphx_operation_t* operation, const char* name, const char* attributes, ...); MIGRAPHX_C_EXPORT migraphx_status migraphx_operation_name(char* out, size_t out_size, migraphx_operation_t operation); MIGRAPHX_C_EXPORT migraphx_status migraphx_load(migraphx_program_t* out, const char* name, migraphx_file_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_save(migraphx_program_t p, const char* name, migraphx_file_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_destroy(migraphx_onnx_options_t onnx_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_assign_to( migraphx_onnx_options_t output, const_migraphx_onnx_options_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_create(migraphx_onnx_options_t* onnx_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_set_input_parameter_shape( migraphx_onnx_options_t onnx_options, const char* name, size_t* dims, size_t dims_size); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_set_dyn_input_parameter_shape( migraphx_onnx_options_t onnx_options, const char* name, migraphx_dynamic_dimensions_t dims); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_set_default_dim_value(migraphx_onnx_options_t onnx_options, size_t value); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_set_default_dyn_dim_value( migraphx_onnx_options_t onnx_options, const_migraphx_dynamic_dimension_t dd); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_set_default_loop_iterations( migraphx_onnx_options_t onnx_options, int64_t value); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_set_limit_loop_iterations( migraphx_onnx_options_t onnx_options, int64_t value); MIGRAPHX_C_EXPORT migraphx_status migraphx_onnx_options_set_external_data_path( migraphx_onnx_options_t onnx_options, const char* external_data_path); MIGRAPHX_C_EXPORT migraphx_status migraphx_file_options_destroy(migraphx_file_options_t file_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_file_options_assign_to( migraphx_file_options_t output, const_migraphx_file_options_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_file_options_create(migraphx_file_options_t* file_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_file_options_set_file_format(migraphx_file_options_t file_options, const char* format); MIGRAPHX_C_EXPORT migraphx_status migraphx_compile_options_destroy(migraphx_compile_options_t compile_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_compile_options_assign_to( migraphx_compile_options_t output, const_migraphx_compile_options_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_compile_options_create(migraphx_compile_options_t* compile_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_compile_options_set_offload_copy(migraphx_compile_options_t compile_options, bool value); MIGRAPHX_C_EXPORT migraphx_status migraphx_compile_options_set_fast_math(migraphx_compile_options_t compile_options, bool value); MIGRAPHX_C_EXPORT migraphx_status migraphx_compile_options_set_exhaustive_tune_flag( migraphx_compile_options_t compile_options, bool value); MIGRAPHX_C_EXPORT migraphx_status migraphx_parse_onnx(migraphx_program_t* out, const char* name, migraphx_onnx_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_parse_onnx_buffer(migraphx_program_t* out, const void* data, size_t size, migraphx_onnx_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_tf_options_destroy(migraphx_tf_options_t tf_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_tf_options_assign_to(migraphx_tf_options_t output, const_migraphx_tf_options_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_tf_options_create(migraphx_tf_options_t* tf_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_tf_options_set_nhwc(migraphx_tf_options_t tf_options, bool is_nhwc); MIGRAPHX_C_EXPORT migraphx_status migraphx_tf_options_set_input_parameter_shape( migraphx_tf_options_t tf_options, const char* name, size_t* dims, size_t dims_size); MIGRAPHX_C_EXPORT migraphx_status migraphx_tf_options_set_default_dim_value(migraphx_tf_options_t tf_options, size_t value); MIGRAPHX_C_EXPORT migraphx_status migraphx_tf_options_set_output_names( migraphx_tf_options_t tf_options, const char** names, size_t names_size); MIGRAPHX_C_EXPORT migraphx_status migraphx_parse_tf(migraphx_program_t* out, const char* name, migraphx_tf_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_parse_tf_buffer(migraphx_program_t* out, const void* data, size_t size, migraphx_tf_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_op_names_destroy(migraphx_quantize_op_names_t quantize_op_names); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_op_names_assign_to( migraphx_quantize_op_names_t output, const_migraphx_quantize_op_names_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_op_names_create(migraphx_quantize_op_names_t* quantize_op_names); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_op_names_add(migraphx_quantize_op_names_t quantize_op_names, const char* name); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_fp16_with_op_names(migraphx_program_t prog, migraphx_quantize_op_names_t name); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_fp16(migraphx_program_t prog); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_bf16_with_op_names(migraphx_program_t prog, migraphx_quantize_op_names_t name); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_bf16(migraphx_program_t prog); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_int8_options_destroy(migraphx_quantize_int8_options_t quantize_int8_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_int8_options_assign_to( migraphx_quantize_int8_options_t output, const_migraphx_quantize_int8_options_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_int8_options_create(migraphx_quantize_int8_options_t* quantize_int8_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_int8_options_add_op_name( migraphx_quantize_int8_options_t quantize_int8_options, const char* name); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_int8_options_add_calibration_data( migraphx_quantize_int8_options_t quantize_int8_options, migraphx_program_parameters_t data); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_int8(migraphx_program_t prog, migraphx_target_t target, migraphx_quantize_int8_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_fp8_options_destroy(migraphx_quantize_fp8_options_t quantize_fp8_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_fp8_options_assign_to( migraphx_quantize_fp8_options_t output, const_migraphx_quantize_fp8_options_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_fp8_options_create(migraphx_quantize_fp8_options_t* quantize_fp8_options); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_fp8_options_add_calibration_data( migraphx_quantize_fp8_options_t quantize_fp8_options, migraphx_program_parameters_t data); MIGRAPHX_C_EXPORT migraphx_status migraphx_quantize_fp8(migraphx_program_t prog, migraphx_target_t target, migraphx_quantize_fp8_options_t options); MIGRAPHX_C_EXPORT migraphx_status migraphx_context_finish(const_migraphx_context_t context); MIGRAPHX_C_EXPORT migraphx_status migraphx_context_get_queue(void** out, migraphx_context_t context); MIGRAPHX_C_EXPORT migraphx_status migraphx_experimental_custom_op_destroy(migraphx_experimental_custom_op_t experimental_custom_op); MIGRAPHX_C_EXPORT migraphx_status migraphx_experimental_custom_op_assign_to( migraphx_experimental_custom_op_t output, const_migraphx_experimental_custom_op_t input); MIGRAPHX_C_EXPORT migraphx_status migraphx_experimental_custom_op_create(migraphx_experimental_custom_op_t* experimental_custom_op, void* obj, migraphx_experimental_custom_op_copy c, migraphx_experimental_custom_op_delete d, const char* obj_typename, const char* name); MIGRAPHX_C_EXPORT migraphx_status migraphx_experimental_custom_op_set_compute( migraphx_experimental_custom_op_t obj, migraphx_experimental_custom_op_compute input); MIGRAPHX_C_EXPORT migraphx_status migraphx_experimental_custom_op_set_compute_shape( migraphx_experimental_custom_op_t obj, migraphx_experimental_custom_op_compute_shape input); MIGRAPHX_C_EXPORT migraphx_status migraphx_experimental_custom_op_set_output_alias( migraphx_experimental_custom_op_t obj, migraphx_experimental_custom_op_output_alias input); MIGRAPHX_C_EXPORT migraphx_status migraphx_experimental_custom_op_set_runs_on_offload_target( migraphx_experimental_custom_op_t obj, migraphx_experimental_custom_op_runs_on_offload_target input); MIGRAPHX_C_EXPORT migraphx_status migraphx_experimental_custom_op_register(migraphx_experimental_custom_op_t experimental_custom_op); #ifdef __cplusplus } #endif #endif ROCm-AMDMIGraphX-46524e8/src/api/include/migraphx/migraphx.hpp000066400000000000000000001416631510465702400237050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_API_RTGLIB_MIGRAPHX_HPP #define MIGRAPHX_GUARD_API_RTGLIB_MIGRAPHX_HPP #include "migraphx.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { #ifndef DOXYGEN inline namespace api { // NOLINT #endif #ifdef __has_cpp_attribute #if __has_cpp_attribute(deprecated) #define MIGRAPHX_DEPRECATED(...) [[deprecated(__VA_ARGS__)]] #endif #endif #ifndef MIGRAPHX_DEPRECATED #define MIGRAPHX_DEPRECATED(...) #endif template struct rank : rank { }; template <> struct rank<0> { }; template std::string compute_type_name() { std::string name; #if defined(_MSC_VER) && !defined(__clang__) const char struct_name[] = "struct "; const char class_name[] = "class "; const char function_name[] = "compute_type_name<"; const char parameter_name[] = ">(void)"; const char cdecl_name[] = "__cdecl"; name = __FUNCSIG__; auto begin = name.find(function_name) + sizeof(function_name) - 1; auto length = name.find(parameter_name) - begin; name = name.substr(begin, length); if(name.find(class_name) == 0) name = name.substr(sizeof(class_name) - 1); else if(name.find(struct_name) == 0) name = name.substr(sizeof(struct_name) - 1); begin = name.find(cdecl_name); if(begin != std::string::npos) name.erase(begin, sizeof(cdecl_name) - 1); #else const char parameter_name[] = "PrivateMigraphTypeNameProbe ="; // NOLINT name = __PRETTY_FUNCTION__; auto begin = name.find(parameter_name) + sizeof(parameter_name); #if(defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 4 && __GNUC_MINOR__ < 7) auto length = name.find_last_of(",") - begin; #else auto length = name.find_first_of("];", begin) - begin; #endif name = name.substr(begin, length); #endif return name; } template const std::string& get_type_name() { static const std::string name = compute_type_name(); return name; } template const std::string& get_type_name(const T&) { return get_type_name(); } template T* make(F f, Ts&&... xs) { T* result = nullptr; auto e = f(&result, std::forward(xs)...); if(e != migraphx_status_success) throw std::runtime_error("Failed to call function"); return result; } template void call(F f, Ts&&... xs) { auto e = f(std::forward(xs)...); if(e != migraphx_status_success) throw std::runtime_error("Failed to call function"); } template struct iota_iterator { Iterator index; F f; using difference_type = std::ptrdiff_t; using reference = decltype(f(std::declval())); using value_type = typename std::remove_reference::type; using pointer = typename std::add_pointer::type; using iterator_category = std::input_iterator_tag; iota_iterator& operator+=(int n) { index += n; return *this; } iota_iterator& operator-=(int n) { index += n; return *this; } iota_iterator& operator++() { index++; return *this; } iota_iterator& operator--() { index--; return *this; } iota_iterator operator++(int) // NOLINT { iota_iterator it = *this; index++; return it; } iota_iterator operator--(int) // NOLINT { iota_iterator it = *this; index--; return it; } // TODO: operator-> reference operator*() const { return f(index); } friend iota_iterator operator+(iota_iterator x, iota_iterator y) { return iota_iterator(x.index + y.index, x.f); } friend iota_iterator operator-(iota_iterator x, iota_iterator y) { return iota_iterator(x.index - y.index, x.f); } friend bool operator==(iota_iterator x, iota_iterator y) { return x.index == y.index; } friend bool operator!=(iota_iterator x, iota_iterator y) { return x.index != y.index; } }; template struct array_base { const Derived& derived() const { return static_cast(*this); } template using value_type_t = decltype(std::declval()[0]); struct iterator_read { const Derived* self; template value_type_t operator()(size_t pidx) const { return (*self)[pidx]; } }; template using iterator_t = iota_iterator; bool empty() const { return derived().size() == 0; } template value_type_t front() const { return derived()[0]; } template value_type_t back() const { return derived()[derived().size() - 1]; } template iterator_t begin() const { return {0, {&derived()}}; } template iterator_t end() const { return {derived().size(), {&derived()}}; } }; #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-template-friend" #endif template struct holder { // Friend injection friend auto migraphx_adl_handle_lookup(holder); // Function left unimplemented since its only used in non-evaluated // context T get() const; }; template struct handle_lookup { friend auto migraphx_adl_handle_lookup(holder) { return holder{}; } }; #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif template using as_handle = decltype(migraphx_adl_handle_lookup(holder>>{}) .get()); struct own { }; struct borrow { }; template struct share { share(std::shared_ptr p) : ptr(std::move(p)) {} template std::shared_ptr alias(U* p) const { return std::shared_ptr{ptr, p}; } private: std::shared_ptr ptr; }; template struct handle_base : handle_lookup> { using handle_type = T; handle_base() : m_handle(nullptr) {} template void make_handle(F f, Ts&&... xs) { using type = typename std::remove_cv::type; set_handle(make(f, std::forward(xs)...), own{}); } const std::shared_ptr& get_handle() const { return m_handle; } T* get_handle_ptr() const { assert(m_handle != nullptr); return get_handle().get(); } template void set_handle(U* ptr, own) { m_handle = std::shared_ptr{ptr, Deleter}; } template void set_handle(U* ptr, borrow) { m_handle = std::shared_ptr{ptr, [](U*) {}}; } template void set_handle(U* ptr, share b) { m_handle = std::shared_ptr{ptr, [b](U*) {}}; // NOLINT(performance-unnecessary-value-param) } share share_handle() const { return {m_handle}; } template void assign_to_handle(U* x) { Assigner(x, this->get_handle_ptr()); } protected: std::shared_ptr m_handle; }; // NOLINTNEXTLINE #define MIGRAPHX_HANDLE_CONSTRUCTOR(name) \ template {}>::type> \ name(HandleType* p, Lifetime lifetime) \ { \ this->set_handle(p, std::move(lifetime)); \ } template struct out_params { }; template struct interface_base : Base { interface_base() : Base() {} protected: template static migraphx_status try_(F f, char* ex_msg = nullptr, size_t ex_msg_size = 0) // NOLINT { try { f(); return migraphx_status_success; } catch(const std::exception& ex) { if(ex_msg) { std::strncpy(ex_msg, ex.what(), ex_msg_size); ex_msg[ex_msg_size - 1] = '\0'; } return migraphx_status_unknown_error; } catch(...) { return migraphx_status_unknown_error; } } template void make_interface(F f, T& obj, Ts&&... xs) { auto copy = [](void** out, void* input) { return try_([&] { T** y = reinterpret_cast(out); T* x = reinterpret_cast(input); assert(x != nullptr and y != nullptr and *y == nullptr); // cppcheck-suppress useSmartPointer *y = new T(*x); // NOLINT }); }; auto del = [](void* input) { return try_([&] { T* x = reinterpret_cast(input); delete x; // NOLINT }); }; this->make_handle(f, &obj, copy, del, std::forward(xs)...); } template void set_fp(Setter setter, F pf, out_params<2>) { static F f = pf; (void)f; // avoid warning on gcc call(setter, this->get_handle_ptr(), [](auto out1, auto out2, void* obj, char* ex_msg, size_t ex_msg_size, auto... xs) -> migraphx_status { return try_([&] { call_cast_arg(rank<2>{}, f, out1, out2, obj, xs...); }, ex_msg, ex_msg_size); }); } template void set_fp(Setter setter, F pf, out_params<1>) { static F f = pf; (void)f; // avoid warning on gcc call(setter, this->get_handle_ptr(), [](auto out, void* obj, char* ex_msg, size_t ex_msg_size, auto... xs) -> migraphx_status { return try_( [&] { call_cast_arg(rank<1>{}, f, out, obj, xs...); }, ex_msg, ex_msg_size); }); } template void set_fp(Setter setter, F pf, out_params<0>) { static F f = pf; (void)f; // avoid warning on gcc call(setter, this->get_handle_ptr(), [](void* obj, char* ex_msg, size_t ex_msg_size, auto... xs) -> migraphx_status { return try_( [&] { call_cast_arg(rank<0>{}, f, obj, xs...); }, ex_msg, ex_msg_size); }); } template void set_auto_fp(Setter setter, F f, Out nums) { return set_fp( setter, [=](T& obj, auto out1, auto out2, auto... xs) { auto_invoke(f, out1, out2, obj, auto_convert_param(rank<2>{}, xs)...); }, nums); } struct no_out_arg { }; template {}>> static void call_cast_arg(rank<0>, F f, X* obj, Xs... xs) { f(reinterpret_cast(obj), no_out_arg{}, no_out_arg{}, xs...); } template {}>> static void call_cast_arg(rank<1>, F f, R result, X* obj, Xs... xs) { f(*static_cast(obj), result, no_out_arg{}, xs...); } template {}>> static void call_cast_arg(rank<2>, F f, R1 result1, R2 result2, X* obj, Xs... xs) { f(*static_cast(obj), result1, result2, xs...); } template void auto_invoke(F f, T1* out1, T2* out2, Ts&&... xs) { auto_assign(rank<2>{}, out1, out2, f(std::forward(xs)...)); } template void auto_invoke(F f, T* out, no_out_arg, Ts&&... xs) { auto_assign(rank<1>{}, out, f(std::forward(xs)...)); } template void auto_invoke(F f, no_out_arg, no_out_arg, Ts&&... xs) { f(std::forward(xs)...); } template {} or std::is_enum{}>> T auto_convert_param(rank<0>, T x) { return x; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" template auto auto_convert_param(rank<1>, T x) -> decltype(as_handle{x}) { return as_handle{x}; } #pragma GCC diagnostic pop template auto auto_convert_param(rank<2>, T x) -> decltype(as_handle{x, borrow{}}) { return as_handle{x, borrow{}}; } template void auto_assign(rank<0>, T* out, U x) { *out = x; } template auto auto_assign(rank<1>, T* out, U x) -> decltype(x.assign_to_handle(out)) { x.assign_to_handle(out); } template {}>> auto auto_assign(rank<2>, T1* out_ptr, T2* out_size, U x) { *out_size = std::min(*out_size, x.size()); std::copy_n(x.begin(), *out_size, out_ptr); } }; // NOLINTNEXTLINE #define MIGRAPHX_INTERFACE_LIFT(n_out, T, prefix, name) \ this->set_auto_fp( \ &migraphx_##prefix##_set_##name, \ [](T& x, auto&&... xs) { return x.name(static_cast(xs)...); }, \ out_params{}) template using require_interface = std::enable_if_t{} and not std::is_same{} and std::is_copy_constructible{} and std::is_final{}>; #ifdef DOXYGEN #define MIGRAPHX_DETAIL_HANDLE_BASE(name, const_) handle_base<> #else #define MIGRAPHX_DETAIL_HANDLE_BASE(name, const_) \ handle_base #endif // NOLINTNEXTLINE #define MIGRAPHX_HANDLE_BASE(name) MIGRAPHX_DETAIL_HANDLE_BASE(name, ) // NOLINTNEXTLINE #define MIGRAPHX_CONST_HANDLE_BASE(name) MIGRAPHX_DETAIL_HANDLE_BASE(name, const) /** * Container to hold optimal dynamic dimension values. */ struct optimals : MIGRAPHX_HANDLE_BASE(optimals) { MIGRAPHX_HANDLE_CONSTRUCTOR(optimals) optimals(std::initializer_list init_list) { this->make_handle(&migraphx_optimals_create, init_list.begin(), init_list.size()); } }; /** * @brief Dynamic dimension object. * @details minimum, maximum, and optimal dimensions */ struct dynamic_dimension : MIGRAPHX_CONST_HANDLE_BASE(dynamic_dimension) { MIGRAPHX_HANDLE_CONSTRUCTOR(dynamic_dimension) dynamic_dimension(size_t min, size_t max) { this->make_handle(&migraphx_dynamic_dimension_create_min_max, min, max); } dynamic_dimension(size_t min, size_t max, const optimals& opts) { this->make_handle( &migraphx_dynamic_dimension_create_min_max_optimals, min, max, opts.get_handle_ptr()); } bool is_fixed() const { bool result = false; call(&migraphx_dynamic_dimension_is_fixed, &result, this->get_handle_ptr()); return result; } friend bool operator==(const dynamic_dimension& x, const dynamic_dimension& y) { bool pout; call(&migraphx_dynamic_dimension_equal, &pout, x.get_handle_ptr(), y.get_handle_ptr()); return pout; } friend bool operator!=(const dynamic_dimension& x, const dynamic_dimension& y) { return not(x == y); } }; /** * Container to hold dynamic_dimension objects. */ struct dynamic_dimensions : MIGRAPHX_HANDLE_BASE(dynamic_dimensions) { MIGRAPHX_HANDLE_CONSTRUCTOR(dynamic_dimensions) template dynamic_dimensions(const Ts&... xs) { std::array a{xs.get_handle_ptr()...}; this->make_handle(&migraphx_dynamic_dimensions_create, a.data(), a.size()); } size_t size() const { size_t pout; call(&migraphx_dynamic_dimensions_size, &pout, this->get_handle_ptr()); return pout; } dynamic_dimension operator[](size_t pidx) const { const_migraphx_dynamic_dimension_t pout; call(&migraphx_dynamic_dimensions_get, &pout, this->get_handle_ptr(), pidx); return {pout, this->share_handle()}; } }; /** * @brief Describe shape of tensor * @details A shape consists of a data type, lengths of multi-dimension tensor, and strides */ struct shape : MIGRAPHX_CONST_HANDLE_BASE(shape) { shape() {} MIGRAPHX_DEPRECATED("Contructor without lifetime annotation is deprecated.") shape(const migraphx_shape* p) { this->set_handle(p, borrow{}); } MIGRAPHX_HANDLE_CONSTRUCTOR(shape) /// Construct a scalar shape shape(migraphx_shape_datatype_t type) { this->make_handle(&migraphx_shape_create_scalar, type); } /// Construct a shape with its type and lengths. The strides are /// automatically computed assumming a packed layout. shape(migraphx_shape_datatype_t type, std::vector plengths) { this->make_handle(&migraphx_shape_create, type, plengths.data(), plengths.size()); } // Force all calls of the format `shape( type_t, { size_t compatibles } )` to map to // shape(type_t, std::vector l) shape(migraphx_shape_datatype_t t, std::initializer_list d) : shape::shape(t, std::vector{d.begin(), d.end()}) { } shape(migraphx_shape_datatype_t type, std::vector plengths, std::vector pstrides) { this->make_handle(&migraphx_shape_create_with_strides, type, plengths.data(), plengths.size(), pstrides.data(), pstrides.size()); } shape(migraphx_shape_datatype_t type, const dynamic_dimensions& dyn_dims) { this->make_handle(&migraphx_shape_create_dynamic, type, dyn_dims.get_handle_ptr()); } std::vector lengths() const { const size_t* pout; size_t pout_size; call(&migraphx_shape_lengths, &pout, &pout_size, this->get_handle_ptr()); return {pout, pout + pout_size}; } std::vector strides() const { const size_t* pout; size_t pout_size; call(&migraphx_shape_strides, &pout, &pout_size, this->get_handle_ptr()); return {pout, pout + pout_size}; } /// Get the dynamic dimensions of the shape dynamic_dimensions dyn_dims() const { migraphx_dynamic_dimensions_t pout; call(&migraphx_shape_dyn_dims, &pout, this->get_handle_ptr()); return {pout, own{}}; } migraphx_shape_datatype_t type() const { migraphx_shape_datatype_t pout; call(&migraphx_shape_type, &pout, this->get_handle_ptr()); return pout; } size_t elements() const { size_t pout; call(&migraphx_shape_elements, &pout, this->get_handle_ptr()); return pout; } size_t bytes() const { size_t pout; call(&migraphx_shape_bytes, &pout, this->get_handle_ptr()); return pout; } bool standard() const { bool result = false; call(&migraphx_shape_standard, &result, this->get_handle_ptr()); return result; } /// Is the shape dynamic bool dynamic() const { bool result = false; call(&migraphx_shape_dynamic, &result, this->get_handle_ptr()); return result; } // map element index to space index size_t index(size_t i) const { size_t result; call(&migraphx_shape_index, &result, this->get_handle_ptr(), i); return result; } friend bool operator==(const shape& px, const shape& py) { bool pout; call(&migraphx_shape_equal, &pout, px.get_handle_ptr(), py.get_handle_ptr()); return pout; } friend bool operator!=(const shape& px, const shape& py) { return not(px == py); } }; /** * @brief Arguments to be passed to an migraphx arguments * * An `argument` represents a raw buffer of data with a shape. * */ struct argument : MIGRAPHX_CONST_HANDLE_BASE(argument) { argument() {} MIGRAPHX_HANDLE_CONSTRUCTOR(argument) MIGRAPHX_DEPRECATED("Contructor without lifetime annotation is deprecated.") argument(const migraphx_argument* p) { this->set_handle(p, borrow{}); } argument(shape pshape) { this->make_handle(&migraphx_argument_create_empty, pshape.get_handle_ptr()); } argument(shape pshape, void* pbuffer) { this->make_handle(&migraphx_argument_create, pshape.get_handle_ptr(), pbuffer); } shape get_shape() const { const_migraphx_shape_t pout; call(&migraphx_argument_shape, &pout, this->get_handle_ptr()); return {pout, this->share_handle()}; } char* data() const { char* pout; call(&migraphx_argument_buffer, &pout, this->get_handle_ptr()); return pout; } template std::vector as_vector() const { auto ss = this->get_shape(); auto num_elements = ss.elements(); std::vector res(num_elements); T* buffer_ptr = reinterpret_cast(this->data()); for(size_t i = 0; i < num_elements; i++) { res[i] = buffer_ptr[ss.index(i)]; } return res; } /// Save an argument to a file static void save_argument(const argument& a, const std::string& filename) { call(&migraphx_argument_save, a.get_handle_ptr(), filename.c_str()); } /// Load an argument from a file static argument load_argument(const std::string& filename) { return {make(&migraphx_argument_load, filename.c_str()), own{}}; } /// Generate an argument using random data static argument generate(shape ps, size_t pseed = 0) { return {make(&migraphx_argument_generate, ps.get_handle_ptr(), pseed), own{}}; } friend bool operator==(const argument& px, const argument& py) { bool pout; call(&migraphx_argument_equal, &pout, px.get_handle_ptr(), py.get_handle_ptr()); return pout; } friend bool operator!=(const argument& px, const argument& py) { return not(px == py); } }; /// A target for compilation struct target : MIGRAPHX_HANDLE_BASE(target) { target() {} MIGRAPHX_HANDLE_CONSTRUCTOR(target) /// Construct a target from its name target(const char* name) { this->make_handle(&migraphx_target_create, name); } }; struct program_parameter_shapes : MIGRAPHX_HANDLE_BASE(program_parameter_shapes) { program_parameter_shapes() {} MIGRAPHX_HANDLE_CONSTRUCTOR(program_parameter_shapes) size_t size() const { size_t pout; call(&migraphx_program_parameter_shapes_size, &pout, this->get_handle_ptr()); return pout; } shape operator[](const char* pname) const { const_migraphx_shape_t pout; call(&migraphx_program_parameter_shapes_get, &pout, this->get_handle_ptr(), pname); return {pout, this->share_handle()}; } std::vector names() const { std::vector result(this->size()); if(not result.empty()) { call(&migraphx_program_parameter_shapes_names, result.data(), this->get_handle_ptr()); } return result; } }; /// A class to construct the inputs parameters for a program struct program_parameters : MIGRAPHX_HANDLE_BASE(program_parameters) { MIGRAPHX_HANDLE_CONSTRUCTOR(program_parameters) MIGRAPHX_DEPRECATED("Contructor without lifetime annotation is deprecated.") program_parameters(migraphx_program_parameters* p) { this->set_handle(p, borrow{}); } program_parameters() { this->make_handle(&migraphx_program_parameters_create); } /// Construct the parameters from initializer_list program_parameters(std::initializer_list> l) { this->make_handle(&migraphx_program_parameters_create); for(auto&& p : l) this->add(p.first.c_str(), p.second); } /// Add a new parameter void add(const char* pname, const argument& pargument) const { call(&migraphx_program_parameters_add, this->get_handle_ptr(), pname, pargument.get_handle_ptr()); } }; struct arguments : MIGRAPHX_HANDLE_BASE(arguments), array_base { MIGRAPHX_HANDLE_CONSTRUCTOR(arguments) size_t size() const { size_t pout; call(&migraphx_arguments_size, &pout, this->get_handle_ptr()); return pout; } argument operator[](size_t pidx) const { const_migraphx_argument_t pout; call(&migraphx_arguments_get, &pout, this->get_handle_ptr(), pidx); return {pout, this->share_handle()}; } }; struct shapes : MIGRAPHX_HANDLE_BASE(shapes), array_base { MIGRAPHX_HANDLE_CONSTRUCTOR(shapes) size_t size() const { size_t pout; call(&migraphx_shapes_size, &pout, this->get_handle_ptr()); return pout; } shape operator[](size_t pidx) const { const_migraphx_shape_t pout; call(&migraphx_shapes_get, &pout, this->get_handle_ptr(), pidx); return {pout, this->share_handle()}; } }; struct operation : MIGRAPHX_HANDLE_BASE(operation) { MIGRAPHX_HANDLE_CONSTRUCTOR(operation) template operation(const char* name, const char* attributes = nullptr, Ts... xs) { this->make_handle(&migraphx_operation_create, name, attributes, xs...); } std::string name() { std::array out_name; call(&migraphx_operation_name, out_name.data(), 1024, this->get_handle_ptr()); return {out_name.data()}; } }; struct instruction : MIGRAPHX_CONST_HANDLE_BASE(instruction) { MIGRAPHX_HANDLE_CONSTRUCTOR(instruction) }; struct instructions : MIGRAPHX_HANDLE_BASE(instructions) { MIGRAPHX_HANDLE_CONSTRUCTOR(instructions) template instructions(const Ts&... xs) { std::array a{xs.get_handle_ptr()...}; this->make_handle(&migraphx_instructions_create, a.data(), a.size()); } }; struct module; struct modules : MIGRAPHX_HANDLE_BASE(modules) { MIGRAPHX_HANDLE_CONSTRUCTOR(modules) template modules(const Ts&... xs) { std::array a = {xs.get_handle_ptr()...}; this->make_handle(&migraphx_modules_create, a.data(), a.size()); } }; struct module { MIGRAPHX_DEPRECATED("Constructor without lifetime annotation is deprecated.") module(migraphx_module* m) :mm(std::shared_ptr(), m) {} module(migraphx_module* m, borrow) :mm(std::shared_ptr(), m) {} template module(migraphx_module* m, const share& b) : mm(b.alias(m)) { } void print() const { call(&migraphx_module_print, mm.get()); } instruction add_instruction(const migraphx::operation& op, const migraphx::instructions& args) { migraphx_instruction_t op_ins; call(&migraphx_module_add_instruction, &op_ins, mm.get(), op.get_handle_ptr(), args.get_handle_ptr()); return instruction(op_ins, own{}); } instruction add_instruction(const migraphx::operation& op, const migraphx::instructions& args, const migraphx::modules& module_args) { migraphx_instruction_t op_ins; call(&migraphx_module_add_instruction_with_mod_args, &op_ins, mm.get(), op.get_handle_ptr(), args.get_handle_ptr(), module_args.get_handle_ptr()); return instruction(op_ins, own{}); } template instruction add_literal(const migraphx::shape& s, T* buffer) { migraphx_instruction_t literal_ins; const auto* buffer_ptr = reinterpret_cast(buffer); call(&migraphx_module_add_literal, &literal_ins, mm.get(), s.get_handle_ptr(), buffer_ptr); return instruction(literal_ins, own{}); } instruction add_parameter(const std::string& name, shape s) { migraphx_instruction_t param_ins; call( &migraphx_module_add_parameter, ¶m_ins, mm.get(), name.c_str(), s.get_handle_ptr()); return instruction(param_ins, own{}); } instruction add_return(const migraphx::instructions& args) { migraphx_instruction_t ret_ins; call(&migraphx_module_add_return, &ret_ins, mm.get(), args.get_handle_ptr()); return instruction(ret_ins, own{}); } instruction add_allocation(const migraphx::shape& s) { migraphx_instruction_t ret_ins; call(&migraphx_module_add_allocation, &ret_ins, mm.get(), s.get_handle_ptr()); return instruction(ret_ins, own{}); } migraphx_module_t get_handle_ptr() const { return mm.get(); } private: std::shared_ptr mm; }; struct context : handle_lookup { context(migraphx_context* p, borrow) : ctx(std::shared_ptr(), p) {} template context(migraphx_context* p, const share& b) : ctx(b.alias(p)) { } void finish() const { call(&migraphx_context_finish, ctx.get()); } template T get_queue() { void* out; call(&migraphx_context_get_queue, &out, ctx.get()); // TODO: check type here return reinterpret_cast(out); } private: std::shared_ptr ctx; }; struct compile_options : MIGRAPHX_HANDLE_BASE(compile_options) { compile_options() { this->make_handle(&migraphx_compile_options_create); } MIGRAPHX_HANDLE_CONSTRUCTOR(compile_options) /// For targets with offloaded memory(such as the gpu), this will insert /// instructions during compilation to copy the input parameters to the /// offloaded memory and to copy the final result from the offloaded /// memory back to main memory. void set_offload_copy(bool value = true) { call(&migraphx_compile_options_set_offload_copy, this->get_handle_ptr(), value); } /// Optimize math functions to use faster approximate versions. There may /// be slight accuracy degredation when enabled. void set_fast_math(bool value = true) { call(&migraphx_compile_options_set_fast_math, this->get_handle_ptr(), value); } /// Set or un-set exhaustive search to find fastest kernel void set_exhaustive_tune_flag(bool value = true) { call(&migraphx_compile_options_set_exhaustive_tune_flag, this->get_handle_ptr(), value); } }; /// A program represents the all computation graphs to be compiled and executed struct program : MIGRAPHX_HANDLE_BASE(program) { program() { this->make_handle(&migraphx_program_create); } MIGRAPHX_HANDLE_CONSTRUCTOR(program) /// Compile the program for a specific target to be ran on void compile(const target& ptarget, const compile_options& poptions) const { call(&migraphx_program_compile, this->get_handle_ptr(), ptarget.get_handle_ptr(), poptions.get_handle_ptr()); } /// Compile the program for a specific target to be ran on void compile(const target& ptarget) const { call(&migraphx_program_compile, this->get_handle_ptr(), ptarget.get_handle_ptr(), migraphx::compile_options{}.get_handle_ptr()); } /// Return the shapes for the input parameters program_parameter_shapes get_parameter_shapes() const { migraphx_program_parameter_shapes_t pout; call(&migraphx_program_get_parameter_shapes, &pout, this->get_handle_ptr()); return program_parameter_shapes(pout, own{}); } /// Get the shapes of all the outputs returned by this program shapes get_output_shapes() const { migraphx_shapes_t pout; call(&migraphx_program_get_output_shapes, &pout, this->get_handle_ptr()); return shapes(pout, own{}); } /// Run the program using the inputs passed in arguments eval(const program_parameters& pparams) const { migraphx_arguments_t pout; call(&migraphx_program_run, &pout, this->get_handle_ptr(), pparams.get_handle_ptr()); return arguments(pout, own{}); } template /// Overloaded to allow for execution_environment input arguments run_async(const program_parameters& pparams, Stream* s) const { migraphx_arguments_t pout; call(&migraphx_program_run_async, &pout, this->get_handle_ptr(), pparams.get_handle_ptr(), s, get_type_name().c_str()); return arguments(pout, own{}); } void print() const { call(&migraphx_program_print, this->get_handle_ptr()); } program sort() { call(&migraphx_program_sort, this->get_handle_ptr()); return *this; } friend bool operator==(const program& px, const program& py) { bool pout; call(&migraphx_program_equal, &pout, px.get_handle_ptr(), py.get_handle_ptr()); return pout; } module get_main_module() { migraphx_module_t p_modu; call(&migraphx_program_get_main_module, &p_modu, this->get_handle_ptr()); return module{p_modu, this->share_handle()}; } context experimental_get_context() { migraphx_context_t ctx; call(&migraphx_program_experimental_get_context, &ctx, this->get_handle_ptr()); return context{ctx, this->share_handle()}; } module create_module(const std::string& name) { migraphx_module_t p_modu; call(&migraphx_program_create_module, &p_modu, this->get_handle_ptr(), name.data()); return module{p_modu, this->share_handle()}; } friend bool operator!=(const program& px, const program& py) { return not(px == py); } }; // options for migraphx file format options struct file_options : MIGRAPHX_HANDLE_BASE(file_options) { MIGRAPHX_HANDLE_CONSTRUCTOR(file_options) file_options() { this->make_handle(&migraphx_file_options_create); } // set file format void set_file_format(const char* format) { call(&migraphx_file_options_set_file_format, this->get_handle_ptr(), format); } }; /// Load a saved migraphx program from a file inline program load(const char* filename, const file_options& options) { return program(make(&migraphx_load, filename, options.get_handle_ptr()), own{}); } /// Load a saved migraphx program from a file inline program load(const char* filename) { return program( make(&migraphx_load, filename, migraphx::file_options{}.get_handle_ptr()), own{}); } /// Save a program to a file inline void save(const program& p, const char* filename, const file_options& options) { call(&migraphx_save, p.get_handle_ptr(), filename, options.get_handle_ptr()); } /// Save a program to a file inline void save(const program& p, const char* filename) { call(&migraphx_save, p.get_handle_ptr(), filename, migraphx::file_options{}.get_handle_ptr()); } /// Options for parsing onnx options struct onnx_options : MIGRAPHX_HANDLE_BASE(onnx_options) { onnx_options() { this->make_handle(&migraphx_onnx_options_create); } MIGRAPHX_HANDLE_CONSTRUCTOR(onnx_options) /// Make onnx parser treat an inputs with a certain dimensions void set_input_parameter_shape(const std::string& name, std::vector dim) { call(&migraphx_onnx_options_set_input_parameter_shape, this->get_handle_ptr(), name.c_str(), dim.data(), dim.size()); } void set_dyn_input_parameter_shape(const std::string& name, const dynamic_dimensions& dyn_dims) { call(&migraphx_onnx_options_set_dyn_input_parameter_shape, this->get_handle_ptr(), name.c_str(), dyn_dims.get_handle_ptr()); } /// When there is a dimension parameter, then use this default value void set_default_dim_value(unsigned int value) { call(&migraphx_onnx_options_set_default_dim_value, this->get_handle_ptr(), value); } void set_default_dyn_dim_value(const dynamic_dimension& dd) { call(&migraphx_onnx_options_set_default_dyn_dim_value, this->get_handle_ptr(), dd.get_handle_ptr()); } /// Set default max iteration number for the loop operator void set_default_loop_iterations(int64_t value) { call(&migraphx_onnx_options_set_default_loop_iterations, this->get_handle_ptr(), value); } /// Set max iteration limit for the loop operator void set_limit_loop_iterations(int64_t value) { call(&migraphx_onnx_options_set_limit_loop_iterations, this->get_handle_ptr(), value); } /// Set absolute path for external data files void set_external_data_path(const std::string& external_data_path) { call(&migraphx_onnx_options_set_external_data_path, this->get_handle_ptr(), external_data_path.c_str()); } }; /// Parse an onnx file into a migraphx program inline program parse_onnx(const char* filename, const migraphx::onnx_options& options) { return program(make(&migraphx_parse_onnx, filename, options.get_handle_ptr()), own{}); } /// Parse an onnx file into a migraphx program inline program parse_onnx(const char* filename) { migraphx::onnx_options options; return program(make(&migraphx_parse_onnx, filename, options.get_handle_ptr()), own{}); } /// Parse a buffer of memory as an onnx file inline program parse_onnx_buffer(const void* data, size_t size, const migraphx::onnx_options& options) { return program( make(&migraphx_parse_onnx_buffer, data, size, options.get_handle_ptr()), own{}); } /// Parse a buffer of memory as an onnx file inline program parse_onnx_buffer(const void* data, size_t size) { migraphx::onnx_options options; return program( make(&migraphx_parse_onnx_buffer, data, size, options.get_handle_ptr()), own{}); } /// Parse a buffer of memory as an onnx file inline program parse_onnx_buffer(const std::string& buffer, const migraphx::onnx_options& options) { return program( make( &migraphx_parse_onnx_buffer, buffer.data(), buffer.size(), options.get_handle_ptr()), own{}); } /// Parse a buffer of memory as an onnx file inline program parse_onnx_buffer(const std::string& buffer) { migraphx::onnx_options options; return program( make( &migraphx_parse_onnx_buffer, buffer.data(), buffer.size(), options.get_handle_ptr()), own{}); } /// Options for parsing tf options struct tf_options : MIGRAPHX_HANDLE_BASE(tf_options) { tf_options() { this->make_handle(&migraphx_tf_options_create); } MIGRAPHX_HANDLE_CONSTRUCTOR(tf_options) /// Make tf parser treat an inputs with a certain dimensions void set_input_parameter_shape(const std::string& name, std::vector dim) { call(&migraphx_tf_options_set_input_parameter_shape, this->get_handle_ptr(), name.c_str(), dim.data(), dim.size()); } /// Change data layout to NHWC (default is NCHW) void set_nhwc(bool is_nhwc = true) { call(&migraphx_tf_options_set_nhwc, this->get_handle_ptr(), is_nhwc); } /// When there is a dimension parameter, then use this default value void set_default_dim_value(unsigned int value) { call(&migraphx_tf_options_set_default_dim_value, this->get_handle_ptr(), value); } /// Set output node names to return specific outputs from graph void set_output_names(std::vector names) { call(&migraphx_tf_options_set_output_names, this->get_handle_ptr(), names.data(), names.size()); } }; /// Parse a tf file into a migraphx program inline program parse_tf(const char* filename, const migraphx::tf_options& options) { return program(make(&migraphx_parse_tf, filename, options.get_handle_ptr()), own{}); } /// Parse a tf file into a migraphx program inline program parse_tf(const char* filename) { migraphx::tf_options options; return program(make(&migraphx_parse_tf, filename, options.get_handle_ptr()), own{}); } /// Parse a buffer of memory as an tf file inline program parse_tf_buffer(const void* data, size_t size, const migraphx::tf_options& options) { return program( make(&migraphx_parse_tf_buffer, data, size, options.get_handle_ptr()), own{}); } /// Parse a buffer of memory as an tf file inline program parse_tf_buffer(const void* data, size_t size) { migraphx::tf_options options; return program( make(&migraphx_parse_tf_buffer, data, size, options.get_handle_ptr()), own{}); } /// Parse a buffer of memory as an tf file inline program parse_tf_buffer(const std::string& buffer, const migraphx::tf_options& options) { return program( make( &migraphx_parse_tf_buffer, buffer.data(), buffer.size(), options.get_handle_ptr()), own{}); } /// Parse a buffer of memory as an tf file inline program parse_tf_buffer(const std::string& buffer) { migraphx::tf_options options; return program( make( &migraphx_parse_tf_buffer, buffer.data(), buffer.size(), options.get_handle_ptr()), own{}); } struct quantize_op_names : MIGRAPHX_HANDLE_BASE(quantize_op_names) { quantize_op_names() { this->make_handle(&migraphx_quantize_op_names_create); } MIGRAPHX_HANDLE_CONSTRUCTOR(quantize_op_names) void add(const std::string& name) { call(&migraphx_quantize_op_names_add, this->get_handle_ptr(), name.c_str()); } }; /// Quantize program to use fp16 inline void quantize_fp16(const program& prog, const quantize_op_names& names) { call(&migraphx_quantize_fp16_with_op_names, prog.get_handle_ptr(), names.get_handle_ptr()); } /// Quantize program to use fp16 inline void quantize_fp16(const program& prog) { call(&migraphx_quantize_fp16, prog.get_handle_ptr()); } /// Quantize program to use fp16 inline void quantize_bf16(const program& prog, const quantize_op_names& names) { call(&migraphx_quantize_bf16_with_op_names, prog.get_handle_ptr(), names.get_handle_ptr()); } /// Quantize program to use fp16 inline void quantize_bf16(const program& prog) { call(&migraphx_quantize_bf16, prog.get_handle_ptr()); } /// Options to be passed when quantizing for int8 struct quantize_int8_options : MIGRAPHX_HANDLE_BASE(quantize_int8_options) { quantize_int8_options() { this->make_handle(&migraphx_quantize_int8_options_create); } MIGRAPHX_HANDLE_CONSTRUCTOR(quantize_int8_options) /// Add an operator that should be quantized void add_op_name(const std::string& name) { call(&migraphx_quantize_int8_options_add_op_name, this->get_handle_ptr(), name.c_str()); } /// Add calibrartion data to be used for quantizing void add_calibration_data(const program_parameters& pp) { call(&migraphx_quantize_int8_options_add_calibration_data, this->get_handle_ptr(), pp.get_handle_ptr()); } }; /// Quantize program to use int8 inline void quantize_int8(const program& prog, const target& ptarget, const quantize_int8_options& options) { call(&migraphx_quantize_int8, prog.get_handle_ptr(), ptarget.get_handle_ptr(), options.get_handle_ptr()); } /// Options to be passed when quantizing for int8 struct quantize_fp8_options : MIGRAPHX_HANDLE_BASE(quantize_fp8_options) { quantize_fp8_options() { this->make_handle(&migraphx_quantize_fp8_options_create); } MIGRAPHX_HANDLE_CONSTRUCTOR(quantize_fp8_options) /// Add calibrartion data to be used for quantizing void add_calibration_data(const program_parameters& pp) { call(&migraphx_quantize_fp8_options_add_calibration_data, this->get_handle_ptr(), pp.get_handle_ptr()); } }; /// Quantize program to use fp8 inline void quantize_fp8(const program& prog, const target& ptarget, const quantize_fp8_options& options) { call(&migraphx_quantize_fp8, prog.get_handle_ptr(), ptarget.get_handle_ptr(), options.get_handle_ptr()); } struct experimental_custom_op_base { experimental_custom_op_base() = default; experimental_custom_op_base(const experimental_custom_op_base&) = default; experimental_custom_op_base& operator=(const experimental_custom_op_base&) = default; virtual ~experimental_custom_op_base() = default; virtual std::string name() const = 0; virtual argument compute(context ctx, shape output, arguments inputs) const = 0; virtual shape compute_shape(shapes inputs) const = 0; virtual std::vector output_alias(shapes) const { return {}; } // TODO: Return target string instead of bool virtual bool runs_on_offload_target() const = 0; }; struct experimental_custom_op : interface_base { template experimental_custom_op(T& obj) { this->make_interface(&migraphx_experimental_custom_op_create, obj, get_type_name(obj).c_str(), obj.name().c_str()); MIGRAPHX_INTERFACE_LIFT(1, T, experimental_custom_op, compute_shape); MIGRAPHX_INTERFACE_LIFT(1, T, experimental_custom_op, compute); MIGRAPHX_INTERFACE_LIFT(2, T, experimental_custom_op, output_alias); MIGRAPHX_INTERFACE_LIFT(1, T, experimental_custom_op, runs_on_offload_target); } void register_op() { call(&migraphx_experimental_custom_op_register, this->get_handle_ptr()); } }; template > void register_experimental_custom_op(T& obj) { experimental_custom_op op{obj}; op.register_op(); } #ifndef DOXYGEN } // namespace api #endif } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/api/migraphx.py000066400000000000000000000454171510465702400203040ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import api def bad_param_error(msg): return 'MIGRAPHX_THROW(migraphx_status_bad_param, "{}")'.format(msg) api.error_type = 'migraphx_status' api.success_type = 'migraphx_status_success' api.try_wrap = 'migraphx::try_' api.bad_param_error = bad_param_error @api.cwrap('migraphx::shape::type_t') def shape_type_wrap(p): if p.returns: p.add_param('migraphx_shape_datatype_t *') p.bad_param('${name} == nullptr', 'Null pointer') p.write = ['*${name} = migraphx::to_shape_type(${result})'] else: p.add_param('migraphx_shape_datatype_t') p.read = 'migraphx::to_shape_type(${name})' def auto_handle(*args, **kwargs): def with_handle(f): return api.handle('migraphx_' + f.__name__, 'migraphx::' + f.__name__, *args, **kwargs)(f) return with_handle @api.handle('migraphx_optimals', 'std::set') def optimals(h): h.constructor('create', api.params(ptr='const size_t*', size='size_t'), fname='migraphx::make_set') @api.handle('migraphx_dynamic_dimension', 'migraphx::shape::dynamic_dimension') def dynamic_dimension(h): h.constructor('create_min_max', api.params(min='size_t', max='size_t')) h.constructor( 'create_min_max_optimals', api.params(min='size_t', max='size_t', optimals='std::set')) h.method('is_fixed', returns='bool', const=True) h.method('equal', api.params(x='const migraphx::shape::dynamic_dimension&'), invoke='migraphx::equal($@)', returns='bool', const=True) @api.handle('migraphx_dynamic_dimensions', 'std::vector') def dynamic_dimensions(h): h.constructor( 'create', api.params(ptr='const const_migraphx_dynamic_dimension_t*', size='size_t'), fname='migraphx::to_obj_vector') h.method('size', returns='size_t') h.method('get', api.params(idx='size_t'), fname='at', cpp_name='operator[]', returns='const migraphx::shape::dynamic_dimension&') @auto_handle() def shape(h): h.constructor( 'create', api.params(type='migraphx::shape::type_t', lengths='std::vector')) h.constructor( 'create_with_strides', api.params(type='migraphx::shape::type_t', lengths='std::vector', strides='std::vector')) h.constructor('create_scalar', api.params(type='migraphx::shape::type_t')) h.constructor( 'create_dynamic', api.params(type='migraphx::shape::type_t', dims='std::vector')) h.method('lengths', fname='lens', returns='const std::vector&', const=True) h.method('strides', returns='const std::vector&', const=True) h.method('dyn_dims', returns='std::vector', const=True) h.method('type', returns='migraphx::shape::type_t', const=True) h.method('elements', returns='size_t', const=True) h.method('bytes', returns='size_t', const=True) h.method('ndim', returns='size_t', const=True) h.method('equal', api.params(x='const migraphx::shape&'), invoke='migraphx::equal($@)', returns='bool', const=True) h.method('standard', returns='bool', const=True) h.method('dynamic', returns='bool', const=True) h.method('index', api.params(i='size_t'), returns='size_t', const=True) @auto_handle() def argument(h): h.constructor('create', api.params(shape='const migraphx::shape&', buffer='void*')) h.constructor('create_empty', api.params(shape='const migraphx::shape&')) h.method('shape', fname='get_shape', cpp_name='get_shape', returns='const migraphx::shape&', const=True) h.method('buffer', fname='data', cpp_name='data', returns='char*', const=True) h.method('equal', api.params(x='const migraphx::argument&'), invoke='migraphx::equal($@)', returns='bool', const=True) api.add_function('migraphx_argument_save', api.params(a='const migraphx::argument&', filename='const char*'), fname='migraphx::save_argument' ) api.add_function('migraphx_argument_load', api.params(filename='const char*'), fname='migraphx::load_argument', returns='migraphx::argument' ) api.add_function('migraphx_argument_generate', api.params(s='const migraphx::shape&', seed='size_t'), fname='migraphx::generate_argument', returns='migraphx::argument') @auto_handle() def target(h): h.constructor('create', api.params(name='const char*'), fname='migraphx::get_target') @api.handle('migraphx_program_parameter_shapes', 'std::unordered_map') def program_parameter_shapes(h): h.method('size', returns='size_t') h.method('get', api.params(name='const char*'), fname='at', cpp_name='operator[]', returns='const migraphx::shape&') h.method('names', invoke='migraphx::get_names(${program_parameter_shapes})', returns='std::vector') @api.handle('migraphx_program_parameters', 'std::unordered_map') def program_parameters(h): h.constructor('create') h.method('add', api.params(name='const char*', argument='const migraphx::argument&'), invoke='${program_parameters}[${name}] = ${argument}') @api.handle('migraphx_arguments', 'std::vector') def arguments(h): h.method('size', returns='size_t') h.method('get', api.params(idx='size_t'), fname='at', cpp_name='operator[]', returns='const migraphx::argument&') @api.handle('migraphx_shapes', 'std::vector') def shapes(h): h.method('size', returns='size_t') h.method('get', api.params(idx='size_t'), fname='at', cpp_name='operator[]', returns='const migraphx::shape&') @api.handle('migraphx_instruction', 'migraphx::instruction_ref') def instruction(h): pass @api.handle('migraphx_instructions', 'std::vector') def instructions(h): h.constructor( 'create', api.params(ptr='const const_migraphx_instruction_t*', size='size_t'), fname='migraphx::to_obj_vector') @api.handle('migraphx_modules', 'std::vector') def modules(h): h.constructor('create', api.params(ptr='migraphx_module_t*', size='size_t'), fname='migraphx::to_objptr_vector') @auto_handle(ref=True) def module(h): h.constructor('create', api.params(name='std::string')) h.method('print', invoke='migraphx::print_module($@)', const=True) h.method('add_instruction', api.params(op='migraphx::operation', args='std::vector'), returns='migraphx::instruction_ref') h.method('add_instruction_with_mod_args', api.params(op='migraphx::operation', args='std::vector', module_refs='std::vector'), fname='add_instruction', returns='migraphx::instruction_ref') h.method('add_literal', api.params(shape='const migraphx::shape&', buffer='const char*'), returns='migraphx::instruction_ref') h.method('add_parameter', api.params(name='const char*', shape='const migraphx::shape&'), returns='migraphx::instruction_ref') h.method('add_return', api.params(args='std::vector'), returns='migraphx::instruction_ref') h.method('add_allocation', api.params(s='const migraphx::shape&'), invoke='migraphx::add_allocation($@)', returns='migraphx::instruction_ref') @auto_handle() def program(h): h.constructor('create') h.method('get_main_module', returns='migraphx::module*') h.method('create_module', api.params(name='const char*'), returns='migraphx::module*') h.method( 'compile', api.params(target='migraphx::target', options='migraphx::compile_options')) h.method('get_parameter_shapes', returns='std::unordered_map') h.method('get_output_shapes', invoke='migraphx::get_output_shapes($@)', returns='std::vector') h.method('print', invoke='migraphx::print_program($@)', const=True) h.method('sort') h.method('run', api.params( params='std::unordered_map'), invoke='migraphx::run($@)', returns='std::vector') h.method('run_async', api.params( params='std::unordered_map', s='void*', name='const char *'), invoke='migraphx::run_async($@)', returns='std::vector') h.method('equal', api.params(x='const migraphx::program&'), invoke='migraphx::equal($@)', returns='bool', const=True) h.method('experimental_get_context', invoke='migraphx::get_context($@)', const=True, returns='migraphx::context') @auto_handle() def operation(h): h.constructor('create', api.params(name='const char*', attributes='const char*', vlist='...'), fname='migraphx::create_op') h.method('name', returns='std::string') api.add_function('migraphx_load', api.params(name='const char*', options='migraphx::file_options'), fname='migraphx::load', returns='migraphx::program') api.add_function('migraphx_save', api.params(p='migraphx::program&', name='const char*', options='migraphx::file_options'), fname='migraphx::save') @auto_handle() def onnx_options(h): h.constructor('create') h.method( 'set_input_parameter_shape', api.params(name='const char*', dims='std::vector'), invoke='migraphx::set_input_parameter_shape($@)', ) h.method( 'set_dyn_input_parameter_shape', api.params(name='const char*', dims='std::vector'), invoke='migraphx::set_dyn_input_parameter_shape($@)', ) h.method( 'set_default_dim_value', api.params(value='size_t'), invoke='migraphx::set_default_dim_value($@)', ) h.method( 'set_default_dyn_dim_value', api.params(dd='const migraphx::shape::dynamic_dimension&'), invoke='migraphx::set_default_dyn_dim_value($@)', ) h.method( 'set_default_loop_iterations', api.params(value='int64_t'), invoke='migraphx::set_default_loop_iterations($@)', ) h.method( 'set_limit_loop_iterations', api.params(value='int64_t'), invoke='migraphx::set_limit_loop_iterations($@)', ) h.method( 'set_external_data_path', api.params(external_data_path='const char*'), invoke='migraphx::set_external_data_path($@)', ) @auto_handle() def file_options(h): h.constructor('create') h.method('set_file_format', api.params(format='const char*'), invoke='migraphx::set_file_format($@)') @auto_handle() def compile_options(h): h.constructor('create') h.method('set_offload_copy', api.params(value='bool'), invoke='migraphx::set_offload_copy($@)') h.method('set_fast_math', api.params(value='bool'), invoke='migraphx::set_fast_math($@)') h.method('set_exhaustive_tune_flag', api.params(value='bool'), invoke='migraphx::set_exhaustive_tune_flag($@)') api.add_function('migraphx_parse_onnx', api.params(name='const char*', options='migraphx::onnx_options'), fname='migraphx::parse_onnx', returns='migraphx::program') api.add_function('migraphx_parse_onnx_buffer', api.params(data='const void*', size='size_t', options='migraphx::onnx_options'), fname='migraphx::parse_onnx_buffer', returns='migraphx::program') @auto_handle() def tf_options(h): h.constructor('create') h.method( 'set_nhwc', api.params(is_nhwc='bool'), invoke='migraphx::set_nhwc($@)', ) h.method( 'set_input_parameter_shape', api.params(name='const char*', dims='std::vector'), invoke='migraphx::set_input_parameter_shape($@)', ) h.method( 'set_default_dim_value', api.params(value='size_t'), invoke='migraphx::set_default_dim_value($@)', ) h.method( 'set_output_names', api.params(names='std::vector'), invoke='migraphx::set_output_names($@)', ) api.add_function('migraphx_parse_tf', api.params(name='const char*', options='migraphx::tf_options'), fname='migraphx::parse_tf', returns='migraphx::program') api.add_function('migraphx_parse_tf_buffer', api.params(data='const void*', size='size_t', options='migraphx::tf_options'), fname='migraphx::parse_tf_buffer', returns='migraphx::program') @api.handle('migraphx_quantize_op_names', 'std::vector') def quantize_op_names(h): h.constructor('create') h.method('add', api.params(name='const char*'), fname='push_back') api.add_function('migraphx_quantize_fp16_with_op_names', api.params(prog='migraphx::program&', name='std::vector&'), fname='migraphx::quantize_fp16_with_op_names') api.add_function('migraphx_quantize_fp16', api.params(prog='migraphx::program&'), fname='migraphx::quantize_fp16') api.add_function('migraphx_quantize_bf16_with_op_names', api.params(prog='migraphx::program&', name='std::vector&'), fname='migraphx::quantize_bf16_with_op_names') api.add_function('migraphx_quantize_bf16', api.params(prog='migraphx::program&'), fname='migraphx::quantize_bf16') @auto_handle() def quantize_int8_options(h): h.constructor('create') h.method( 'add_op_name', api.params(name='const char*'), invoke='migraphx::add_op_name($@)', ) h.method( 'add_calibration_data', api.params(data='std::unordered_map'), invoke='migraphx::add_calibration_data($@)', ) api.add_function('migraphx_quantize_int8', api.params(prog='migraphx::program&', target='migraphx::target', options='migraphx::quantize_int8_options'), fname='migraphx::quantize_int8_wrap') @auto_handle() def quantize_fp8_options(h): h.constructor('create') h.method( 'add_calibration_data', api.params(data='std::unordered_map'), invoke='migraphx::add_calibration_data($@)', ) api.add_function('migraphx_quantize_fp8', api.params(prog='migraphx::program&', target='migraphx::target', options='migraphx::quantize_fp8_options'), fname='migraphx::quantize_fp8_wrap') @auto_handle(ref=True) def context(h): h.method('finish', const=True) h.method('get_queue', returns='void*', fname='get_queue().unsafe_get') @api.interface('migraphx_experimental_custom_op', 'migraphx::experimental_custom_op') def experimental_custom_op(h): h.constructor('create', api.params(obj_typename='const char*', name='const char*')) h.virtual('compute', api.params(ctx='migraphx::context', output='migraphx::shape', inputs='std::vector'), returns='migraphx::argument') h.virtual('compute_shape', api.params(inputs='std::vector'), returns='migraphx::shape') h.virtual('output_alias', api.params(inputs='std::vector'), returns='std::vector') h.virtual('runs_on_offload_target', returns='bool') h.method('register', invoke='migraphx::register_custom_op($@)') ROCm-AMDMIGraphX-46524e8/src/apply_alpha_beta.cpp000066400000000000000000000065111510465702400213230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { instruction_ref insert_apply_alpha_beta(module& m, instruction_ref pos, const std::vector& args, const operation& op, const literal& alpha, const literal& beta) { auto a = args[0]; auto b = args[1]; auto input_type = a->get_shape().type(); if(not float_equal(alpha.at(0), 1.0)) { auto alpha_literal = m.add_literal(alpha); a = insert_common_op(m, pos, migraphx::make_op("mul"), {alpha_literal, a}); if(a->get_shape().type() != input_type) { a = m.insert_instruction(pos, make_op("convert", {{"target_type", input_type}}), a); } } auto op_res = m.insert_instruction(pos, op, a, b); if(args.size() == 3) { if(not float_equal(beta.at(0), 0.0) and args[2]->get_shape().elements() > 0) { auto out_lens = op_res->get_shape().lens(); auto c = args[2]; auto c_lens = c->get_shape().lens(); input_type = c->get_shape().type(); if(out_lens != c_lens) { c = m.insert_instruction( pos, migraphx::make_op("multibroadcast", {{"out_lens", out_lens}}), args[2]); } auto beta_literal = m.add_literal(beta); auto beta_c = insert_common_op(m, pos, migraphx::make_op("mul"), {c, beta_literal}); if(beta_c->get_shape().type() != input_type) { beta_c = m.insert_instruction( pos, migraphx::make_op("convert", {{"target_type", input_type}}), beta_c); } return m.insert_instruction(pos, migraphx::make_op("add"), op_res, beta_c); } } return op_res; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/argument.cpp000066400000000000000000000153511510465702400176620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { argument::argument(const shape& s) : m_shape(s) { auto buffer = make_shared_array(s.bytes()); assign_buffer({[=]() mutable { return buffer.get(); }}); } argument::argument(shape s, std::nullptr_t) : m_shape(std::move(s)), m_data({[] { return nullptr; }}) { } argument::argument(const shape& s, const argument::data_t& d) : m_shape(s), m_data(d) {} void argument::assign_buffer(std::function d) { const shape& s = m_shape; if(s.type() != shape::tuple_type) { m_data = {std::move(d)}; return; } // Collect all shapes std::unordered_map shapes; { std::size_t i = 0; fix([&](auto self, auto ss) { if(ss.sub_shapes().empty()) { shapes[i] = ss; i++; } else { for(auto&& child : ss.sub_shapes()) self(child); } })(s); } // Sort by type size std::vector order(shapes.size()); std::iota(order.begin(), order.end(), 0); std::sort(order.begin(), order.end(), by(std::greater<>{}, [&](auto i) { return shapes[i].type_size(); })); // Compute offsets std::unordered_map offsets; std::size_t offset = 0; for(auto i : order) { offsets[i] = offset; offset += shapes[i].bytes(); } assert(offset == s.bytes()); std::size_t i = 0; m_data = fix([&](auto self, auto ss) { data_t result; if(ss.sub_shapes().empty()) { auto n = offsets[i]; result = {[d, n]() mutable { return d() + n; }}; i++; return result; } std::vector subs; std::transform(ss.sub_shapes().begin(), ss.sub_shapes().end(), std::back_inserter(subs), [&](auto child) { return self(child); }); result.sub = subs; return result; })(s); } std::vector flatten(const std::vector& args) { std::vector result; for(const auto& arg : args) { if(arg.get_shape().type() == shape::tuple_type) { auto subs = flatten(arg.get_sub_objects()); result.insert(result.end(), subs.begin(), subs.end()); } else { result.push_back(arg); } } return result; } std::vector to_shapes(const std::vector& args) { std::vector shapes; std::transform(args.begin(), args.end(), std::back_inserter(shapes), [](auto&& arg) { return arg.get_shape(); }); return shapes; } argument::argument(const std::vector& args) : m_shape(to_shapes(args)), m_data(data_t::from_args(args)) { } char* argument::data() const { assert(m_shape.type() != shape::tuple_type); assert(not this->empty()); return m_data.get(); } bool argument::empty() const { return not m_data.get and m_data.sub.empty(); } const shape& argument::get_shape() const { return this->m_shape; } argument argument::reshape(const shape& s) const { assert(s.element_space() <= this->get_shape().element_space()); return {s, this->m_data}; } argument argument::convert(shape::type_t t) const { argument result{this->get_shape().with_type(t)}; this->visit([&](auto x) { result.fill(x.begin(), x.end()); }); return result; } argument::data_t argument::data_t::share() const { data_t result; if(this->get) { auto self = std::make_shared(*this); result.get = [self]() mutable { return self->get(); }; } std::transform(sub.begin(), sub.end(), std::back_inserter(result.sub), [](const auto& d) { return d.share(); }); return result; } argument::data_t argument::data_t::from_args(const std::vector& args) { data_t result; std::transform(args.begin(), args.end(), std::back_inserter(result.sub), [](auto&& arg) { return arg.m_data; }); return result; } argument argument::copy() const { argument result{this->get_shape()}; auto* src = this->data(); std::copy(src, src + this->get_shape().bytes(), result.data()); return result; } argument argument::share() const { return {m_shape, m_data.share()}; } std::vector argument::get_sub_objects() const { std::vector result; assert(m_shape.sub_shapes().size() == m_data.sub.size()); std::transform(m_shape.sub_shapes().begin(), m_shape.sub_shapes().end(), m_data.sub.begin(), std::back_inserter(result), [](auto&& s, auto&& d) { return argument{s, d}; }); return result; } argument argument::element(std::size_t i) const { assert(this->get_shape().sub_shapes().empty()); auto idx = this->get_shape().index(i); auto offset = this->get_shape().type_size() * idx; return argument{shape{this->get_shape().type()}, this->data() + offset}; } void save_argument(const argument& a, const std::string& filename) { write_buffer(filename, to_msgpack(to_value(a))); } argument load_argument(const std::string& filename) { return from_value(from_msgpack(read_buffer(filename))); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/auto_contiguous.cpp000066400000000000000000000056721510465702400212740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void auto_contiguous::apply(module& m) const { std::string key = "require_std_shape"; for(auto ins : reverse_iterator_for(m)) { auto&& attr = ins->get_operator().attributes(); if((attr.get(key, false))) { auto args = ins->inputs(); auto new_args = args; std::transform(args.begin(), args.end(), new_args.begin(), [&](auto in) { if(in->name() == "contiguous") { return in; } return m.insert_instruction(ins, make_op("contiguous"), in); }); if(new_args != args) { m.replace_instruction(ins, ins->get_operator(), new_args); } } } auto last = std::prev(m.end()); for(auto ins : iterator_for(m)) { if(contains({"layout", "@return"}, ins->name())) continue; // for last instruction that is NOT a return if(ins->outputs().empty() and ins != last) continue; shape s = ins->get_shape(); // If s is not standard layout or has out of sequence strides, insert "contiguous" op // to make a standard shape if(not s.dynamic() and (not s.standard() or s.normalize_standard() != s) and s.elements() > 1) { auto c = m.insert_instruction(std::next(ins), make_op("contiguous"), ins); m.replace_instruction(ins, c); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/autocast_fp8.cpp000066400000000000000000000065661510465702400204500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void autocast_fp8_pass::apply(module& m) const { std::vector remove_parameters; for(auto ins : iterator_for(m)) { const auto& ins_name = ins->name(); if(ins_name == "@param" and contains(fp8_types{}.get(), ins->get_shape().type())) { shape::type_t fp8_type = ins->get_shape().type(); migraphx::shape new_shape = ins->get_shape().with_type(target_type); std::string param_name = ins->get_operator().to_value()["parameter"].to(); m.rename_parameter(ins, param_name + "_old"); auto new_param = m.add_parameter(param_name, new_shape); auto new_ins = m.insert_instruction( ins, migraphx::make_op("convert", {{"target_type", migraphx::to_value(fp8_type)}}), new_param); m.replace_instruction(ins, new_ins); remove_parameters.push_back(ins); } if(ins_name == "@return") { std::vector inputs = ins->inputs(); std::vector new_inputs; std::transform( inputs.begin(), inputs.end(), std::back_inserter(new_inputs), [&](auto i) { if(contains(fp8_types{}.get(), i->get_shape().type())) { return m.insert_instruction( ins, migraphx::make_op("convert", {{"target_type", migraphx::to_value(target_type)}}), i); } else return i; }); m.replace_return({new_inputs}); } } // Remove unused parameters with fp8 type for(const auto& i : remove_parameters) m.remove_instruction(i); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/base64.cpp000066400000000000000000000064631510465702400171300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { using byte = unsigned char; std::array constexpr b64_chars{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; /// base64 encoder snippet altered from https://stackoverflow.com/a/37109258 std::string encode(const std::vector& buf) { std::size_t len = buf.size(); std::vector res_vec((len + 2) / 3 * 4, '='); std::size_t j = 0; std::size_t remaining = len % 3; const size_t last = len - remaining; for(size_t i = 0; i < last; i += 3) { std::size_t n = static_cast(buf.at(i)) << 16u | static_cast(buf.at(i + 1)) << 8u | static_cast(buf.at(i + 2)); res_vec.at(j++) = b64_chars.at(n >> 18u); res_vec.at(j++) = b64_chars.at(n >> 12u & 0x3Fu); res_vec.at(j++) = b64_chars.at(n >> 6u & 0x3Fu); res_vec.at(j++) = b64_chars.at(n & 0x3Fu); } // Set padding if(remaining != 0) { std::size_t n = --remaining == 0 ? static_cast(buf.at(last)) : static_cast(buf.at(last)) << 8u | static_cast(buf.at(last + 1)); res_vec.at(j++) = b64_chars.at(remaining == 0 ? n >> 2u : n >> 10u & 0x3Fu); res_vec.at(j++) = b64_chars.at(remaining == 0 ? n << 4u & 0x3Fu : n >> 4u & 0x03Fu); res_vec.at(j++) = remaining == 0 ? '=' : b64_chars.at(n << 2u & 0x3Fu); } return {res_vec.begin(), res_vec.end()}; } } // namespace std::string base64_encode(const std::string& str) { return encode(std::vector(str.begin(), str.end())); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/common.cpp000066400000000000000000000231121510465702400173220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { std::vector compute_broadcasted_lens(std::vector s0, std::vector s1) { if(s0 == s1) return s0; if(s0.size() > s1.size()) s0.swap(s1); std::vector out_lens(s1); auto offset = s1.size() - s0.size(); std::transform( s0.begin(), s0.end(), s1.begin() + offset, out_lens.begin() + offset, [&](auto a, auto b) { if(a != b and a != 1 and b != 1) { MIGRAPHX_THROW("COMPUTE_BROADCASTLEN: shape {" + migraphx::to_string_range(s0) + "} and {" + migraphx::to_string_range(s1) + "} mismatch!"); } return std::max(a, b); }); return out_lens; } std::vector compute_broadcasted_dyn_dims(std::vector dds0, std::vector dds1) { if(dds0.size() > dds1.size()) { std::swap(dds0, dds1); } auto offset = dds1.size() - dds0.size(); std::vector out_dims(dds1); std::transform(dds0.cbegin(), dds0.cend(), dds1.cbegin() + offset, out_dims.begin() + offset, [&](auto a, auto b) { if(a == b or b == 1) { return a; } else if(a == 1) { return b; } else { auto intersect = a.intersection(b); if(intersect.has_value()) { return intersect.value(); } MIGRAPHX_THROW("COMPUTE_BROADCASTED_DYN_DIMS: dynamic shapes {" + migraphx::to_string_range(dds0) + "} and {" + migraphx::to_string_range(dds1) + "} mismatch!"); } }); return out_dims; } std::vector compute_broadcasted_dyn_dims(shape s0, shape s1) { // change both shapes to dynamic_dimension representation s0 = s0.to_dynamic(); s1 = s1.to_dynamic(); return compute_broadcasted_dyn_dims(s0.dyn_dims(), s1.dyn_dims()); } std::vector compute_common_dyn_dims(const std::vector& shapes) { auto ret_shape = shapes.at(0); std::for_each(shapes.cbegin() + 1, shapes.cend(), [&](auto s) { ret_shape = shape{ret_shape.type(), compute_broadcasted_dyn_dims(ret_shape, s)}; }); return ret_shape.dyn_dims(); } std::vector compute_common_lens(const std::vector& shapes) { assert(not shapes.empty()); assert( std::none_of(shapes.cbegin(), shapes.cend(), [](auto shape) { return shape.dynamic(); })); return transform_accumulate(shapes.begin() + 1, shapes.end(), shapes.front().lens(), &compute_broadcasted_lens, [](auto s) { return s.lens(); }); } static shape::type_t compute_common_type(shape::type_t t1, shape::type_t t2) { if(t1 == t2) return t1; shape::type_t result; shape::visit(t1, [&](auto x) { shape::visit(t2, [&](auto y) { // Workaround broken warning on gcc 5 (void)x; (void)y; using type = std::common_type_t; result = shape::get_type{}; }); }); return result; } static shape::type_t compute_common_types(const std::vector& shapes) { assert(not shapes.empty()); return transform_accumulate( shapes.begin() + 1, shapes.end(), shapes.front().type(), &compute_common_type, [&](auto s) { return s.type(); }); } shape common_shape(const std::vector& shapes) { if(shapes.empty()) return {}; return {compute_common_types(shapes), compute_common_lens(shapes)}; } std::vector insert_common_args(module& m, instruction_ref ins, std::vector inputs, common_options options) { if(std::any_of( inputs.cbegin(), inputs.cend(), [](auto input) { return input->get_shape().dynamic(); })) { auto input_shapes = to_shapes(inputs); if(options.common_lens) { auto c_dyn_dims = compute_common_dyn_dims(input_shapes); auto s0 = inputs[0]->get_shape(); // always add both multibroadcast instructions for dynamic shapes inputs[0] = m.insert_instruction( ins, make_op("multibroadcast", {{"out_dyn_dims", to_value(c_dyn_dims)}}), inputs); std::transform(inputs.begin() + 1, inputs.end(), inputs.begin() + 1, [&](auto input) { // uses previous input to avoid recalculating the common shape from the // full set of input shapes at runtime auto s = input->get_shape(); return m.insert_instruction( ins, make_op("multibroadcast", {{"out_dyn_dims", to_value(c_dyn_dims)}}), input, inputs[0]); }); } if(options.common_type) { auto c_type = compute_common_types(input_shapes); std::transform(inputs.begin(), inputs.end(), inputs.begin(), [&](auto input) { if(input->get_shape().type() != c_type) { input = m.insert_instruction( ins, make_op("convert", {{"target_type", c_type}}), input); } return input; }); } } else { auto common = common_shape(to_shapes(inputs)); std::transform(inputs.begin(), inputs.end(), inputs.begin(), [&](auto input) { if(options.common_lens and input->get_shape().lens() != common.lens()) { input = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", common.lens()}}), input); } if(options.common_type and input->get_shape().type() != common.type()) { input = m.insert_instruction( ins, make_op("convert", {{"target_type", common.type()}}), input); } return input; }); } return inputs; } std::vector add_common_args(module& m, std::vector inputs, common_options options) { return insert_common_args(m, m.end(), std::move(inputs), options); } instruction_ref insert_common_op(module& m, instruction_ref ins, const operation& op, std::vector inputs, common_options options) { return m.insert_instruction(ins, op, insert_common_args(m, ins, std::move(inputs), options)); } instruction_ref add_common_op(module& m, const operation& op, std::vector inputs, common_options options) { return insert_common_op(m, m.end(), op, std::move(inputs), options); } shape make_bcast_shape(const shape& input_shape, const std::vector& bcast_lens) { assert(not input_shape.dynamic()); auto offset = bcast_lens.size() - input_shape.ndim(); std::vector bcast_strides(bcast_lens.size(), 0); for(std::ptrdiff_t i : reverse(range(input_shape.ndim()))) { if(bcast_lens.at(i + offset) == input_shape.lens()[i]) { bcast_strides.at(i + offset) = input_shape.strides()[i]; } } return shape{input_shape.type(), bcast_lens, bcast_strides}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/common_dims.cpp000066400000000000000000000145161510465702400203460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template static auto compute_end_dim(Iterator start, Iterator last, std::size_t dim) { std::size_t x = 1; auto it = std::find_if(start, last, [&](auto i) { x *= i; return x > dim; }); if(x < dim) return start; return it; } struct common_dim_state { common_dim_state(const std::vector& pdims, std::vector>& paxes_map) : dims(&pdims), axes_map(&paxes_map), it(dims->begin()) { } const std::vector* dims = nullptr; std::vector>* axes_map = nullptr; std::vector::const_iterator it{}; std::size_t rem = 1; std::size_t get() const { return *it / rem; } bool is_end() const { return it == dims->end(); } void next(std::size_t i = 1) { it += i; } auto dims_for(std::size_t d) const { auto dim_end = compute_end_dim(it, dims->end(), d); return range(it, dim_end); } void add_axes(std::size_t naxes, std::size_t start) MIGRAPHX_TIDY_CONST { auto axes = compute_axes(naxes, start); axes_map->push_back(std::move(axes)); } void add_multi_axes(std::size_t naxes, std::size_t start) MIGRAPHX_TIDY_CONST { auto axes = compute_axes(naxes, start); std::transform(axes.begin(), axes.end(), std::back_inserter(*axes_map), [&](auto axis) -> std::vector { return {axis}; }); } std::vector compute_axes(std::size_t naxes, std::size_t start) const { if(rem != 1) { assert(start > 0); naxes++; start--; } std::vector axes(naxes); std::iota(axes.begin(), axes.end(), start); return axes; } }; static bool compute_common_dim(std::vector& cd_dims, common_dim_state& state1, common_dim_state& state2) { assert(state1.get() < state2.get()); auto d2 = state2.get(); auto dims = state1.dims_for(d2); auto n = elements(dims); auto naxes = distance(dims); if(naxes == 0) return false; // If not divisible then we can't compute a common dim if((d2 % n) != 0) return false; auto rem = d2 / n; state1.add_multi_axes(naxes, cd_dims.size()); state2.add_axes(rem == 1 ? naxes : naxes + 1, cd_dims.size()); state1.rem = rem; state2.rem = 1; cd_dims.insert(cd_dims.end(), dims.begin(), dims.end()); if(state1.rem != 1) cd_dims.push_back(state1.rem); state1.next(distance(dims)); state2.next(); return true; } common_dims common_dims::compute(const std::vector& dims1, const std::vector& dims2) { assert(elements(dims1) > 0); assert(elements(dims1) == elements(dims2)); common_dims cd; common_dim_state state1{dims1, cd.axes_map1}; common_dim_state state2{dims2, cd.axes_map2}; while(not state1.is_end() and not state2.is_end()) { auto d1 = state1.get(); auto d2 = state2.get(); if(d1 == d2) { state1.add_axes(1, cd.dims.size()); state2.add_axes(1, cd.dims.size()); state1.rem = 1; state2.rem = 1; cd.dims.push_back(d1); state1.next(); state2.next(); } else if(d1 < d2) { if(not compute_common_dim(cd.dims, state1, state2)) return {}; } else // if(d1 > d2) { if(not compute_common_dim(cd.dims, state2, state1)) return {}; } } assert(elements(dims1) == elements(cd.dims)); return cd; } const std::vector>* common_dims::get_axes_map(std::size_t n) const { if(axes_map1.size() == n) return &axes_map1; if(axes_map2.size() == n) return &axes_map2; return nullptr; } std::vector common_dims::get_dimensions_for(const std::vector& idims) const { if(dims.size() == idims.size()) return idims; if(elements(dims) == elements(idims)) return dims; // Bail for now since its ambiguous which axes map can be used // TODO: Check for similiarity if(axes_map1.size() == axes_map2.size()) return {}; const auto* axes_map = get_axes_map(idims.size()); if(axes_map == nullptr) return {}; auto xdims = dims; for(auto i : range(axes_map->size())) { auto dim = idims[i]; const auto& axes = (*axes_map)[i]; if(axes.size() == 1) { xdims[axes.front()] = dim; } else if(dim == 1) { for(auto axis : axes) xdims[axis] = 1; } } if(elements(xdims) == elements(idims)) return xdims; return {}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/compile_src.cpp000066400000000000000000000052271510465702400203400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { std::vector src_compiler::compile(const std::vector& srcs) const { assert(not srcs.empty()); tmp_dir td{"compile"}; std::vector params{flags}; params.emplace_back("-I."); auto out = output; for(const auto& src : srcs) { fs::path full_path = td.path / src.path; fs::path parent_path = full_path.parent_path(); fs::create_directories(parent_path); write_buffer(full_path, src.content.data(), src.content.size()); if(src.path.extension().string() == ".cpp") { params.emplace_back(src.path.filename().string()); if(out.empty()) out = src.path.stem().string() + out_ext; } } params.emplace_back("-o " + out); std::vector args; if(not launcher.empty()) args.push_back(compiler.string()); args.insert(args.end(), params.begin(), params.end()); td.execute(launcher.empty() ? compiler : launcher, args); auto out_path = td.path / out; if(not fs::exists(out_path)) MIGRAPHX_THROW("Output file missing: " + out); return read_buffer(out_path); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/convert_to_json.cpp000066400000000000000000000061421510465702400212510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static std::vector json_tokenize(const std::string& s) { std::vector lexers; // Quote lexers.push_back([](const char* start, const char* end) { if(*start != '\"') return start; ++start; while((start != end) and (*start != '\"')) { if(*start == '\\') start++; start++; } return ++start; }); // Line comments lexers.push_back([](const char* start, const char* end) { if(*start == '#') start++; else if((start + 1) < end and start[0] == '/' and start[1] == '/') start += 2; else return start; return std::find_if(start, end, [&](char c) { return c == '\n'; }); }); // Whitespace lexers.push_back(lex_while(&isspace)); // Punctation lexers.push_back(lex_if(&ispunct)); // Identifier/number lexers.push_back(lex_while([](char c) { return (isalnum(c) != 0 or contains({'_', '.', '+'}, c)); })); return tokenize(s.data(), s.data() + s.length(), lexers); } std::string convert_to_json(const std::string& str) { auto tokens = json_tokenize(str); std::stringstream ss; for(auto& token : tokens) { std::string s(token); if(starts_with(s, "#") or starts_with(s, "//")) continue; if(std::isalpha(s.front()) != 0 and not contains({"null", "nan", "true", "false", "inf"}, s)) { ss << "\"" << s << "\""; } else { ss << s; } } return ss.str(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/cpp_generator.cpp000066400000000000000000000237451510465702400206760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { cpp_generator::function& cpp_generator::function::set_body(const module& m, const cpp_generator::generate_module_callback& g) { const std::string prefix = "zz"; std::unordered_map names; std::stringstream ss; auto return_ins = std::prev(m.end()); for(auto ins : iterator_for(m)) { ss << "// " << ins->get_operator() << " -> " << ins->get_shape() << "\n"; if(ins->name() == "@param") { names[ins] = to_c_id( migraphx::any_cast(ins->get_operator()).parameter); } else if(ins->name() == "@return") { names[ins] = prefix + "return"; ss << "auto " << names[ins] << " = " << g(ins, names) << ";\n"; return_ins = ins; } else { std::string n = prefix + std::to_string(names.size()); names[ins] = n; ss << "auto " << n << " = " << g(ins, names) << ";\n"; } } ss << "return " << names.at(return_ins) << ";\n"; body = ss.str(); return *this; } cpp_generator::function& cpp_generator::function::set_types(const module& m) { return cpp_generator::function::set_types(m, [](auto s) { return shape::cpp_type(s.type()); }); } cpp_generator::function& cpp_generator::function::set_types(const module& m, const std::function& parse) { this->params.clear(); auto pmap = m.get_parameter_shapes(); std::map input_map(pmap.begin(), pmap.end()); std::transform( input_map.begin(), input_map.end(), std::back_inserter(this->params), [&](auto&& p) { return param{p.first, parse(p.second)}; }); auto output_shapes = m.get_output_shapes(); assert(not output_shapes.empty()); this->return_type = parse(output_shapes.front()); return *this; } cpp_generator::function& cpp_generator::function::set_generic_types(const module& m) { this->params.clear(); auto pmap = m.get_parameter_shapes(); std::map input_map(pmap.begin(), pmap.end()); std::transform( input_map.begin(), input_map.end(), std::back_inserter(this->params), [&](auto&& p) { return param{p.first, "T" + to_c_id(p.first)}; }); std::transform(input_map.begin(), input_map.end(), std::back_inserter(this->tparams), [&](auto&& p) { return "class T" + to_c_id(p.first); }); this->return_type = "auto"; return *this; } cpp_generator::function& cpp_generator::function::unused_param(const std::string& pname) { body.insert(0, "(void)" + pname + ";\n"); return *this; } cpp_generator::function& cpp_generator::function::add_generic_param(const std::string& pname) { params.push_back({pname, "T" + pname}); tparams.push_back("class T" + pname); return *this; } struct cpp_generator_impl { std::stringstream fs{}; std::size_t function_count = 0; std::function fmap = nullptr; std::function fresult = nullptr; std::unordered_map point_op_map = {}; bool always_return_tuple = false; }; cpp_generator::cpp_generator() : impl(std::make_unique()) {} cpp_generator::cpp_generator(cpp_generator&&) noexcept = default; cpp_generator& cpp_generator::operator=(cpp_generator rhs) { std::swap(impl, rhs.impl); return *this; } cpp_generator::~cpp_generator() noexcept = default; void cpp_generator::fmap(const std::function& f) { impl->fmap = f; } void cpp_generator::fresult(const std::function& f) { impl->fresult = f; } void cpp_generator::always_return_tuple(bool b) { impl->always_return_tuple = b; } void cpp_generator::add_point_op(const std::string& op_name, const std::string& code) { impl->point_op_map[op_name] = code; } std::string cpp_generator::generate_point_op(const operation& op, const std::vector& args) { auto v = op.to_value(); std::string code; if(contains(impl->point_op_map, op.name())) { code = impl->point_op_map.at(op.name()); } else { auto attributes = op.attributes(); if(not attributes.contains("point_op")) MIGRAPHX_THROW("op is missing point_op attribute: " + op.name()); code = attributes["point_op"].to(); } return interpolate_string(code, [&](auto start, auto last) -> std::string { auto key = trim({start, last}); if(key.empty()) MIGRAPHX_THROW("Empty parameter"); std::string fselector = "function:"; if(starts_with(key, fselector)) { auto fname = key.substr(fselector.size()); if(impl->fmap == nullptr) return fname; else return impl->fmap(fname); } else if(with_char(::isdigit)(key[0])) { auto i = std::stoul(key); if(i >= args.size()) MIGRAPHX_THROW("Invalid argument index: " + key); return args.at(i); } else if(v.contains(key)) { return v[key].template to(); } else { return key; } }); } std::string cpp_generator::str() const { return impl->fs.str(); } cpp_generator::function cpp_generator::generate_module(const module& m, const generate_module_callback& g) { function f; f.set_name(to_c_id(m.name())) .set_types(m) .set_body(m, [&](instruction_ref ins, const auto& names) -> std::string { if(ins->name() == "@literal") { std::string string_literal; ins->get_literal().visit([&](auto v) { assert(v.size() == 1); auto x = v.front(); if(std::isinf(static_cast(x))) { string_literal = "__builtin_huge_val()"; if(x < 0) string_literal = "-__builtin_huge_val()"; } else if(std::isnan(static_cast(x))) string_literal = "__builtin_nan(\"0\")"; else string_literal = ins->get_literal().to_string(); }); return shape::cpp_type(ins->get_shape().type()) + "(" + string_literal + ")"; } if(ins->name() == "@return") { // TODO: Customize the make_tuple call if(impl->always_return_tuple or ins->inputs().size() != 1) return "make_tuple(" + join_strings(to_args(ins->inputs(), names), ", ") + ")"; return names.at(ins->inputs().front()); } auto s = g(ins, names); if(impl->fresult) return impl->fresult(ins->get_shape()) + '(' + s + ')'; else return s; }); return f; } std::vector cpp_generator::to_args(const std::vector& inputs, const std::unordered_map& names) { std::vector args; std::transform(inputs.begin(), inputs.end(), std::back_inserter(args), [&](auto i) { return names.at(i); }); return args; } cpp_generator::function cpp_generator::generate_module(const module& m) { return this->generate_module(m, [&](auto ins, const auto& names) { return this->generate_point_op(ins->get_operator(), to_args(ins->inputs(), names)); }); } std::string cpp_generator::create_function(const cpp_generator::function& f) { impl->function_count++; if(not f.tparams.empty()) impl->fs << "template<" << join_strings(f.tparams, ", ") << ">\n"; std::string name = f.name.empty() ? "f" + std::to_string(impl->function_count) : f.name; impl->fs << join_strings(f.attributes, " ") << " " << f.return_type << " " << name; char delim = '('; if(f.params.empty()) impl->fs << delim; for(auto&& p : f.params) { impl->fs << delim << p.type << " " << to_c_id(p.name); delim = ','; } impl->fs << ") {\n" << f.body << "\n}\n"; return name; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/dead_code_elimination.cpp000066400000000000000000000067161510465702400223240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void dead_code_elimination::apply(program& p) const { p.remove_unused_modules(); } void dead_code_elimination::apply(module& m) const { auto last = std::prev(m.end()); for(auto ins : iterator_for(m)) { // Skip the first instruction, since we always process the previous // instruction if(ins == m.begin()) continue; const auto i = std::prev(ins); // Skip the last instruction if(i == last) break; // Skip instruction with empty shape as output unless its [dynamic, builtin, undefined, // identity, allocate, or tuple_type] if((not i->get_shape().dynamic() and (i->get_shape().elements() == 0 and i->get_shape().type() != migraphx::shape::tuple_type)) and not(i->name().front() == '@') and not contains({"identity", "allocate"}, i->name()) and not i->is_undefined()) continue; assert(std::distance(m.begin(), i) <= std::distance(m.begin(), last)); std::unordered_set visited; fix([&](auto self, auto leaf) { if(not m.has_instruction(leaf)) return; if(leaf->outputs().empty()) { // Dont visit inputs twice if(not visited.insert(leaf).second) return; std::unordered_set args(leaf->inputs().begin(), leaf->inputs().end()); leaf->clear_arguments(); assert(std::distance(m.begin(), leaf) < std::distance(m.begin(), last)); assert(leaf != ins); if(leaf->name() != "@param") m.move_instruction(leaf, m.end()); for(auto arg : args) self(arg); } })(i); } m.remove_instructions(std::next(last), m.end()); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/dom_info.cpp000066400000000000000000000070101510465702400176230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { bool dominator_info::strictly_dominate(instruction_ref ins1, instruction_ref ins2) const { if(ins1 == ins2) return false; auto iter = ins2idom.find(ins2); while(iter != ins2idom.end()) { if(ins1 == iter->second) return true; assert(iter != ins2idom.find(iter->second)); iter = ins2idom.find(iter->second); } return false; } struct module_visitor { const module* mm; const module& get_nodes() const { return *mm; } const std::vector& get_children(instruction_ref ins) { return ins->inputs(); } }; template static dominator_info compute_dominator_generic(Visitor v) { dominator_info info; std::unordered_map> instr2_doms; for(instruction_ref ins : iterator_for(v.get_nodes())) { const std::vector& children = v.get_children(ins); if(children.size() == 1) { info.ins2idom[ins] = children.front(); instr2_doms[ins] = instr2_doms[children.front()]; } else if(children.size() > 1) { auto&& doms = instr2_doms[ins]; doms = instr2_doms[children.front()]; std::for_each(children.begin() + 1, children.end(), [&](instruction_ref child) { auto&& child_doms = instr2_doms[child]; erase_if(doms, [&](auto x) { return not contains(child_doms, x); }); }); auto iter = std::find_if(doms.begin(), doms.end(), [&](auto dom1) { return std::none_of(doms.begin(), doms.end(), [&](auto dom2) { if(dom1 == dom2) return false; return info.strictly_dominate(dom1, dom2); }); }); if(iter != doms.end()) info.ins2idom[ins] = *iter; } instr2_doms[ins].insert(ins); } return info; } dominator_info compute_dominator(const module& m) { return compute_dominator_generic(module_visitor{&m}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/000077500000000000000000000000001510465702400166225ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/driver/CMakeLists.txt000066400000000000000000000046171510465702400213720ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### add_executable(driver main.cpp verify.cpp passes.cpp mlir.cpp models.cpp perf.cpp trim.cpp marker_roctx.cpp ) set_target_properties(driver PROPERTIES OUTPUT_NAME migraphx-driver) if(NOT WIN32) # Copy driver for backwards compatibility (Linux only) add_custom_command( TARGET driver POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/driver BYPRODUCTS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/driver ) set_directory_properties(PROPERTIES ADDITIONAL_CLEAN_FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/driver) endif() rocm_clang_tidy_check(driver) file(STRINGS "${CMAKE_SOURCE_DIR}/test/onnx/.onnxrt-commit" String_output) target_compile_definitions(driver PUBLIC MIGRAPHX_ORT_SHA1="${String_output}") target_link_libraries(driver migraphx_all_targets migraphx_onnx migraphx_tf) if(MIGRAPHX_ENABLE_PYTHON) target_link_libraries(driver migraphx_py) target_compile_definitions(driver PRIVATE MIGRAPHX_ENABLE_PYTHON) endif() rocm_install_targets( TARGETS driver ) ROCm-AMDMIGraphX-46524e8/src/driver/argument_parser.hpp000066400000000000000000000566751510465702400225540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ARGUMENT_PARSER_HPP #define MIGRAPHX_GUARD_RTGLIB_ARGUMENT_PARSER_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #endif namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { #ifdef MIGRAPHX_USE_CLANG_TIDY #define MIGRAPHX_DRIVER_STATIC #else #define MIGRAPHX_DRIVER_STATIC static #endif template using bare = std::remove_cv_t>; namespace detail { template auto is_container(int, T&& x) -> decltype(x.insert(x.end(), *x.begin()), std::true_type{}); template std::false_type is_container(float, T&&); } // namespace detail template struct is_container : decltype(detail::is_container(0, std::declval())){}; template using is_multi_value = std::integral_constant{} and not std::is_convertible{})>; enum class color { reset = 0, bold = 1, underlined = 4, fg_red = 31, fg_green = 32, fg_yellow = 33, fg_blue = 34, fg_default = 39, bg_red = 41, bg_green = 42, bg_yellow = 43, bg_blue = 44, bg_default = 49 }; inline std::ostream& operator<<(std::ostream& os, const color& c) { #ifndef _WIN32 static const bool use_color = isatty(STDOUT_FILENO) != 0; if(use_color) return os << "\033[" << static_cast(c) << "m"; #else (void)c; #endif return os; } inline std::string colorize(color c, const std::string& s) { std::stringstream ss; ss << c << s << color::reset; return ss.str(); } template struct type_name { static const std::string& apply() { return migraphx::get_type_name(); } }; template <> struct type_name { static const std::string& apply() { static const std::string name = "std::string"; return name; } }; template struct type_name> { static const std::string& apply() { static const std::string name = "std::vector<" + type_name::apply() + ">"; return name; } }; template struct value_parser { template {} and not is_multi_value{})> static T apply(const std::string& x) { // handle whitespace in string if constexpr(std::is_same{}) { return x; } else { T result; std::stringstream ss; ss.str(x); ss >> result; if(ss.fail()) throw std::runtime_error("Failed to parse '" + x + "' as " + type_name::apply()); return result; } } template {} and not is_multi_value{})> static T apply(const std::string& x) { std::ptrdiff_t i; std::stringstream ss; ss.str(x); ss >> i; if(ss.fail()) throw std::runtime_error("Failed to parse '" + x + "' as " + type_name::apply()); return static_cast(i); } template {} and not std::is_enum{})> static T apply(const std::string& x) { T result; using value_type = typename T::value_type; result.insert(result.end(), value_parser::apply(x)); return result; } }; // version for std::optional object template struct value_parser> { static T apply(const std::string& x) { return value_parser::apply(x); } }; struct argument_parser { struct argument { using action_function = std::function&)>; using validate_function = std::function&)>; std::vector flags; action_function action{}; std::string type = ""; std::string help = ""; std::string metavar = ""; std::string default_value = ""; std::string group = ""; unsigned nargs = 1; bool required = false; std::vector validations{}; std::string usage(const std::string& flag) const { std::stringstream ss; if(flag.empty()) { ss << metavar; } else { ss << flag; if(not type.empty()) ss << " [" << type << "]"; } return ss.str(); } std::string usage() const { if(flags.empty()) return usage(""); return usage(flags.front()); } }; template {})> std::string as_string_value(const T& x) { return to_string_range(x); } template auto as_string_value(rank<1>, const T& x) -> decltype(to_string(x)) { return to_string(x); } template std::string as_string_value(rank<0>, const T&) { throw std::runtime_error("Can't convert to string"); } template {})> std::string as_string_value(const T& x) { return as_string_value(rank<1>{}, x); } template void operator()(T& x, const std::vector& flags, const Fs&... fs) { arguments.push_back({flags, [&](auto&&, const std::vector& params) { if(params.empty()) throw std::runtime_error("Flag with no value."); if(not is_multi_value{} and params.size() > 1) throw std::runtime_error("Too many arguments passed."); x = value_parser::apply(params.back()); return false; }}); argument& arg = arguments.back(); arg.type = type_name::apply(); migraphx::each_args([&](const auto& f) { f(x, arg); }, fs...); if(not arg.default_value.empty() and arg.nargs > 0) arg.default_value = as_string_value(x); } template void operator()(std::nullptr_t x, std::vector flags, const Fs&... fs) { arguments.push_back({std::move(flags)}); argument& arg = arguments.back(); arg.type = ""; arg.nargs = 0; migraphx::each_args([&](const auto& f) { f(x, arg); }, fs...); } MIGRAPHX_DRIVER_STATIC auto nargs(unsigned n = 1) { return [=](auto&&, auto& arg) { arg.nargs = n; }; } MIGRAPHX_DRIVER_STATIC auto required() { return [=](auto&&, auto& arg) { arg.required = true; }; } template MIGRAPHX_DRIVER_STATIC auto write_action(F f) { return [=](auto& x, auto& arg) { arg.action = [&, f](auto& self, const std::vector& params) { f(self, x, params); return false; }; }; } template MIGRAPHX_DRIVER_STATIC auto do_action(const F& f) { return [=](auto&, auto& arg) { arg.nargs = 0; arg.action = [&, f](auto& self, const std::vector&) { f(self); return true; }; }; } MIGRAPHX_DRIVER_STATIC auto append() { return write_action([](auto&, auto& x, auto& params) { using type = typename bare::value_type; std::transform(params.begin(), params.end(), std::inserter(x, x.end()), [](const std::string& y) { return value_parser::apply(y); }); }); } template MIGRAPHX_DRIVER_STATIC auto validate(const F& f) { return [=](const auto& x, auto& arg) { arg.validations.push_back( [&, f](auto& self, const std::vector& params) { f(self, x, params); }); }; } MIGRAPHX_DRIVER_STATIC auto file_exist() { return validate([](auto&, auto&, const auto& params) { if(params.empty()) throw std::runtime_error("No argument passed."); if(not fs::exists(params.back())) throw std::runtime_error("Path does not exist: " + params.back()); }); } MIGRAPHX_DRIVER_STATIC auto matches(const std::unordered_set& names) { return validate([=](auto&, auto&, const auto& params) { auto invalid_param = std::find_if( params.begin(), params.end(), [&](const auto& p) { return names.count(p) == 0; }); if(invalid_param != params.end()) throw std::runtime_error("Invalid argument: " + *invalid_param + ". Valid arguments are {" + to_string_range(names) + "}"); }); } template argument* find_argument(F f) { auto it = std::find_if(arguments.begin(), arguments.end(), f); if(it == arguments.end()) return nullptr; return std::addressof(*it); } template bool has_argument(F f) { return find_argument(f) != nullptr; } template std::vector find_arguments(F f) { std::vector result; for(auto& arg : arguments) { if(not f(arg)) continue; result.push_back(&arg); } return result; } std::vector get_group_arguments(const std::string& group) { return find_arguments([&](const auto& arg) { return arg.group == group; }); } std::vector get_required_arguments() { return find_arguments([&](const auto& arg) { return arg.required; }); } template std::vector get_argument_usages(SequenceContainer args) { std::vector usage_flags; std::unordered_set found_groups; // Remove arguments that belong to a group auto it = std::remove_if(args.begin(), args.end(), [&](const argument* arg) { if(arg->group.empty()) return false; found_groups.insert(arg->group); return true; }); args.erase(it, args.end()); transform(found_groups, std::back_inserter(usage_flags), [&](auto&& group) { std::vector either_flags; transform(get_group_arguments(group), std::back_inserter(either_flags), [](auto* arg) { return arg->usage(); }); return "(" + join_strings(either_flags, "|") + ")"; }); transform(args, std::back_inserter(usage_flags), [&](auto* arg) { return arg->usage(); }); return usage_flags; } auto show_help(const std::string& msg = "") { return do_action([=](auto& self) { argument* input_argument = self.find_argument([](const auto& arg) { return arg.flags.empty(); }); auto required_usages = get_argument_usages(get_required_arguments()); if(required_usages.empty() and input_argument) required_usages.push_back(input_argument->metavar); required_usages.insert(required_usages.begin(), ""); print_usage(required_usages); std::cout << std::endl; if(self.find_argument([](const auto& arg) { return arg.nargs == 0; })) { std::cout << color::fg_yellow << "FLAGS:" << color::reset << std::endl; std::cout << std::endl; for(auto&& arg : self.arguments) { if(arg.nargs != 0) continue; const int col_align = 35; std::string prefix = " "; int len = 0; std::cout << color::fg_green; for(const std::string& a : arg.flags) { len += prefix.length() + a.length(); std::cout << prefix; std::cout << a; prefix = ", "; } std::cout << color::reset; int spaces = col_align - len; if(spaces < 0) { std::cout << std::endl; } else { for(int i = 0; i < spaces; i++) std::cout << " "; } std::cout << arg.help << std::endl; } std::cout << std::endl; } if(self.find_argument([](const auto& arg) { return arg.nargs != 0; })) { std::cout << color::fg_yellow << "OPTIONS:" << color::reset << std::endl; for(auto&& arg : self.arguments) { if(arg.nargs == 0) continue; std::cout << std::endl; std::string prefix = " "; std::cout << color::fg_green; if(arg.flags.empty()) { std::cout << prefix; std::cout << arg.metavar; } for(const std::string& a : arg.flags) { std::cout << prefix; std::cout << a; prefix = ", "; } std::cout << color::reset; if(not arg.type.empty()) { std::cout << " [" << color::fg_blue << arg.type << color::reset << "]"; if(not arg.default_value.empty()) std::cout << " (Default: " << arg.default_value << ")"; } std::cout << std::endl; std::cout << " " << arg.help << std::endl; } std::cout << std::endl; } if(not msg.empty()) std::cout << msg << std::endl; }); } MIGRAPHX_DRIVER_STATIC auto help(const std::string& help) { return [=](auto&, auto& arg) { arg.help = help; }; } MIGRAPHX_DRIVER_STATIC auto metavar(const std::string& metavar) { return [=](auto&, auto& arg) { arg.metavar = metavar; }; } MIGRAPHX_DRIVER_STATIC auto type(const std::string& type) { return [=](auto&, auto& arg) { arg.type = type; }; } MIGRAPHX_DRIVER_STATIC auto group(const std::string& group) { return [=](auto&, auto& arg) { arg.group = group; }; } template MIGRAPHX_DRIVER_STATIC auto set_value(T value) { return [=](auto& x, auto& arg) { arg.nargs = 0; arg.type = ""; arg.action = [&, value](auto&, const std::vector&) { x = value; return false; }; }; } template void set_exe_name_to(T& x) { actions.push_back([&](const auto& self) { x = self.exe_name; }); } void print_try_help() { if(has_argument([](const auto& a) { return contains(a.flags, "--help"); })) { std::cout << std::endl; std::cout << "For more information try '" << color::fg_green << "--help" << color::reset << "'" << std::endl; } } void print_usage(const std::vector& flags) const { std::cout << color::fg_yellow << "USAGE:" << color::reset << std::endl; std::cout << " " << exe_name << " "; std::cout << join_strings(flags, " ") << std::endl; } auto spellcheck(const std::vector& inputs) { struct result_t { const argument* arg = nullptr; std::string correct = ""; std::string incorrect = ""; std::ptrdiff_t distance = std::numeric_limits::max(); }; result_t result; for(const auto& input : inputs) { if(input.empty()) continue; if(input[0] != '-') continue; for(const auto& arg : arguments) { for(const auto& flag : arg.flags) { if(flag.empty()) continue; if(flag[0] != '-') continue; std::ptrdiff_t d = levenshtein_distance(flag, input); if(d < result.distance) result = result_t{&arg, flag, input, d}; } } } return result; } bool run_action(const argument& arg, const std::string& flag, const std::vector& inputs) { std::string msg = ""; try { for(const auto& v : arg.validations) v(*this, inputs); return arg.action(*this, inputs); } catch(const std::exception& e) { msg = e.what(); } catch(...) { msg = "unknown exception"; } std::cout << color::fg_red << color::bold << "error: " << color::reset; auto sc = spellcheck(inputs); if(sc.distance < 5) { std::cout << "Found argument '" << color::fg_yellow << sc.incorrect << color::reset << "'" << " which wasn't expected, or isn't valid in this context" << std::endl; std::cout << " " << "Did you mean " << color::fg_green << sc.correct << color::reset << "?" << std::endl; std::cout << std::endl; print_usage({sc.arg->usage(sc.correct)}); } else { const auto& flag_name = flag.empty() ? arg.metavar : flag; std::cout << "Invalid input to '" << color::fg_yellow; std::cout << arg.usage(flag_name); std::cout << color::reset << "'" << std::endl; std::cout << " " << msg << std::endl; std::cout << std::endl; print_usage({arg.usage()}); } std::cout << std::endl; print_try_help(); return true; } bool parse(const std::vector& args) { std::unordered_map keywords; for(auto&& arg : arguments) { for(auto&& flag : arg.flags) keywords[flag] = arg.nargs + 1; } auto arg_map = generic_parse(args, [&](const std::string& x) { return keywords[x]; }); std::list missing_arguments; std::unordered_set groups_used; for(auto&& arg : arguments) { bool used = false; auto flags = arg.flags; if(flags.empty()) flags = {""}; for(auto&& flag : flags) { if(arg_map.count(flag) > 0) { if(run_action(arg, flag, arg_map[flag])) return true; used = true; } } if(used and not arg.group.empty()) groups_used.insert(arg.group); if(arg.required and not used) missing_arguments.push_back(&arg); } // Remove arguments from a group that is being used missing_arguments.remove_if( [&](const argument* arg) { return groups_used.count(arg->group); }); if(not missing_arguments.empty()) { std::cout << color::fg_red << color::bold << "error: " << color::reset; std::cout << "The following required arguments were not provided:" << std::endl; std::cout << " " << color::fg_red << join_strings(get_argument_usages(std::move(missing_arguments)), " ") << color::reset << std::endl; std::cout << std::endl; auto required_usages = get_argument_usages(get_required_arguments()); print_usage(required_usages); print_try_help(); return true; } for(auto&& action : actions) action(*this); return false; } void set_exe_name(const std::string& s) { exe_name = s; } const std::string& get_exe_name() const { return exe_name; } using string_map = std::unordered_map>; template static string_map generic_parse(const std::vector& as, IsKeyword is_keyword) { string_map result; std::string flag; bool clear = false; for(auto&& x : as) { auto k = is_keyword(x); if(k > 0) { flag = x; result[flag]; // Ensure the flag exists if(k == 1) flag = ""; else if(k == 2) clear = true; else clear = false; } else { result[flag].push_back(x); if(clear) flag = ""; clear = false; } } return result; } private: std::list arguments; std::string exe_name = ""; std::vector> actions; }; } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/driver/command.hpp000066400000000000000000000067531510465702400207640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_COMMAND_HPP #define MIGRAPHX_GUARD_RTGLIB_COMMAND_HPP #include "argument_parser.hpp" #include #include #include #include #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { inline auto& get_commands() { // NOLINTNEXTLINE static std::unordered_map< std::string, std::function args)>> m; return m; } template std::string compute_command_name() { static const std::string& tname = get_type_name(); auto name = tname.substr(tname.rfind("::") + 2); if(ends_with(name, "_command")) name = name.substr(0, name.size() - 8); if(ends_with(name, "_cmd")) name = name.substr(0, name.size() - 4); return name; } template const std::string& command_name() { static const std::string& name = compute_command_name(); return name; } template void run_command(const std::string& exe_name, const std::vector& args, bool add_help = false) { T x; argument_parser ap; ap.set_exe_name(exe_name + " " + command_name()); if(add_help) ap(nullptr, {"-h", "--help"}, ap.help("Show help"), ap.show_help()); x.parse(ap); if(ap.parse(args)) return; x.run(); } template int auto_register_command() { auto& m = get_commands(); m[command_name()] = [](const std::string& exe_name, const std::vector& args) { run_command(exe_name, args, true); }; return 0; } template struct command { static const int static_register; // This typedef ensures that the static member will be instantiated if // the class itself is instantiated using static_register_type = std::integral_constant; }; #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wglobal-constructors" #endif template const int command::static_register = auto_register_command(); // NOLINT } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/driver/main.cpp000066400000000000000000001045101510465702400202530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify.hpp" #include "verify_options.hpp" #include "argument_parser.hpp" #include "command.hpp" #include "mlir.hpp" #include "precision.hpp" #include "passes.hpp" #include "perf.hpp" #include "trim.hpp" #include "models.hpp" #include "marker_roctx.hpp" #include #include #ifdef MIGRAPHX_ENABLE_PYTHON #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { using dims_map = std::unordered_map>; std::vector get_unrecognized_migraphx_envs(const char* envp[], const std::map& used_env) { std::vector unused_migx_env; for(; *envp != nullptr; ++envp) { std::string e(*envp); if(not migraphx::starts_with(e, "MIGRAPHX")) continue; size_t pos = e.find('='); if(pos == std::string::npos) continue; if(used_env.find(e.substr(0, pos)) == used_env.end()) unused_migx_env.push_back(e); } return unused_migx_env; } std::string get_formatted_timestamp(std::chrono::time_point time) { auto now_in_time_t = std::chrono::system_clock::to_time_t(time); auto* now_as_tm_date = std::localtime(&now_in_time_t); std::stringstream ss; ss << std::put_time(now_as_tm_date, "%Y-%m-%d %H:%M:%S"); return ss.str(); } } // namespace namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { inline static std::string get_version() { return "MIGraphX Version: " + std::to_string(MIGRAPHX_VERSION_MAJOR) + "." + std::to_string(MIGRAPHX_VERSION_MINOR) + "." + std::to_string(MIGRAPHX_VERSION_PATCH) + "." MIGRAPHX_VERSION_TWEAK; } struct loader { std::string file; std::string file_type; unsigned batch = 1; bool is_nhwc = true; bool is_test = false; unsigned trim = 0; unsigned trim_size = 0; bool optimize = false; bool mlir = false; bool skip_unknown_operators = false; bool brief = false; bool verbose = false; std::string output_type; std::string output; std::string default_dyn_dim; std::vector param_dims; std::vector dim_params; std::vector dyn_param_dims; std::vector output_names; std::vector passes; void parse(argument_parser& ap) { ap(file, {}, ap.metavar(""), ap.file_exist(), ap.required(), ap.group("input")); ap(is_test, {"--test"}, ap.help("Run a single GEMM to test MIGraphX"), ap.set_value(true), ap.group("input")); ap(file_type, {"--onnx"}, ap.help("Load as onnx"), ap.set_value("onnx")); ap(file_type, {"--tf"}, ap.help("Load as tensorflow"), ap.set_value("tf")); ap(file_type, {"--migraphx"}, ap.help("Load as MIGraphX"), ap.set_value("migraphx")); ap(file_type, {"--migraphx-json"}, ap.help("Load as MIGraphX JSON"), ap.set_value("json")); ap(batch, {"--batch"}, ap.help("For a static model, sets default_dim_value size (commonly batch size). For a " "dynamic batch model, sets the batch " "size at runtime.")); ap(is_nhwc, {"--nhwc"}, ap.help("Treat tensorflow format as nhwc"), ap.set_value(true)); ap(skip_unknown_operators, {"--skip-unknown-operators"}, ap.help("Skip unknown operators when parsing and continue to parse."), ap.set_value(true)); ap(is_nhwc, {"--nchw"}, ap.help("Treat tensorflow format as nchw"), ap.set_value(false)); ap(trim, {"--trim", "-t"}, ap.help("Trim instructions from the end")); ap(trim_size, {"--trim-size", "-s"}, ap.help("Number of instructions in the trim model")); ap(param_dims, {"--input-dim"}, ap.help("Dim of a parameter (format: \"@name d1 d2 dn\")"), ap.append(), ap.nargs(2)); ap(dim_params, {"--dim-param"}, ap.help("Symbolic parameter dimension name (fixed / dynamic) - " "(fixed format): \"@dim_param_name\" \"x\" / " "(dynamic format): \"@dim_param_name\" \"{min:x, max:y, optimals:[o1,o2]}\""), ap.append(), ap.nargs(2)); ap(dyn_param_dims, {"--dyn-input-dim"}, ap.help("Dynamic dimensions of a parameter (format: \"@name_1\" \"[{min:x, max:y, " "optimals:[o1,o2,...]}, dim2,dim3, ...]\", \"@name_2\", ... You can supply a " "single integer value for a dimension to specify it as fixed."), ap.append(), ap.nargs(2)); ap(default_dyn_dim, {"--default-dyn-dim"}, ap.help("Default dynamic dimension (format: \"{min:x, max:y, optimals:[o1,o2]}\").")); ap(output_names, {"--output-names"}, ap.help("Names of node output (format: \"name_1 name_2 name_n\")"), ap.append(), ap.nargs(2)); ap(optimize, {"--optimize", "-O"}, ap.help("Optimize when reading"), ap.set_value(true)); ap(mlir, {"--mlir"}, ap.help("Offload everything to mlir"), ap.set_value(true)); ap(passes, {"--apply-pass", "-p"}, ap.help("Passes to apply to model"), ap.append()); ap(output_type, {"--graphviz", "-g"}, ap.help("Print out a graphviz representation."), ap.set_value("graphviz")); ap(brief, {"--brief"}, ap.help("Make the output brief."), ap.set_value(true)); ap(output_type, {"--cpp"}, ap.help("Print out the program as C++ program."), ap.set_value("cpp")); ap(output_type, {"--python", "--py"}, ap.help("Print out the program as python program."), ap.set_value("py")); ap(output_type, {"--json"}, ap.help("Print out program as json."), ap.set_value("json")); ap(output_type, {"--text"}, ap.help("Print out program in text format."), ap.set_value("text")); ap(output_type, {"--binary"}, ap.help("Print out program in binary format."), ap.set_value("binary")); ap(output_type, {"--netron"}, ap.help("Print out program as Netron readable json."), ap.set_value("netron")); ap(output, {"--output", "-o"}, ap.help("Output to file.")); } static auto parse_param_dims(const std::vector& param_dims_info) { dims_map map_input_dims; std::string name = ""; for(auto&& x : param_dims_info) { if(x[0] == '@') { name = x.substr(1); } else { map_input_dims[name].push_back(value_parser::apply(x)); } } return map_input_dims; } static auto parse_dyn_dims_json(const std::string& dd_json) { // expecting a json string like "[{min:1,max:64,optimals:[1,2,4,8]},3,224,224]" auto v = from_json_string(convert_to_json(dd_json)); std::vector dyn_dims; std::transform(v.begin(), v.end(), std::back_inserter(dyn_dims), [&](const auto& x) { if(x.is_object()) return from_value(x); auto d = x.template to(); return migraphx::shape::dynamic_dimension{d, d}; }); return dyn_dims; } static auto parse_dyn_dims_map(const std::vector& param_dyn_dims) { // expecting vector of strings formatted like // {"@param_name_0", "dd_json_0", "@param_name_1", "dd_json_1", ...} std::unordered_map> map_dyn_input_dims; std::string name = ""; for(auto&& x : param_dyn_dims) { if(x[0] == '@') { name = x.substr(1); } else { map_dyn_input_dims[name] = parse_dyn_dims_json(x); } } return map_dyn_input_dims; } static auto parse_dim_params(const std::vector& dim_params_info) { std::unordered_map map_dim_params; std::string name = ""; for(auto&& x : dim_params_info) { if(x[0] == '@') { name = x.substr(1); } else { if(std::all_of(x.begin(), x.end(), [](char ch) { return std::isdigit(static_cast(ch)); })) map_dim_params[name] = {std::stoul(x), std::stoul(x)}; else { auto dyn_dim = parse_dyn_dims_json(x); if(dyn_dim.size() != 1) MIGRAPHX_THROW("dim_param must only specifiy one dimension"); map_dim_params[name] = dyn_dim.front(); } } } return map_dim_params; } static auto parse_output_names(const std::vector& output_names_info) { std::vector output_node_names; std::transform(output_names_info.begin(), output_names_info.end(), std::back_inserter(output_node_names), [&](const auto& x) { return value_parser::apply(x); }); return output_node_names; } tf_options get_tf_options() const { auto map_input_dims = parse_param_dims(param_dims); auto output_node_names = parse_output_names(output_names); tf_options options; options.is_nhwc = is_nhwc; options.batch_size = batch; options.map_input_dims = map_input_dims; options.output_node_names = output_node_names; return options; } onnx_options get_onnx_options() const { auto map_input_dims = parse_param_dims(param_dims); auto map_dyn_input_dims = parse_dyn_dims_map(dyn_param_dims); auto map_dim_params = parse_dim_params(dim_params); onnx_options options; if(default_dyn_dim.empty()) { options.default_dim_value = batch; } else { auto v = from_json_string(convert_to_json(default_dyn_dim)); options.default_dyn_dim_value = from_value(v); } options.skip_unknown_operators = skip_unknown_operators; options.print_program_on_error = true; options.map_input_dims = map_input_dims; options.map_dyn_input_dims = map_dyn_input_dims; options.dim_params = map_dim_params; return options; } static std::string get_file_type(const std::string& file) { if(ends_with(file, ".onnx")) return "onnx"; else if(ends_with(file, ".pb")) return "tf"; else if(ends_with(file, ".json")) return "json"; else if(ends_with(file, ".py")) return "py"; else return "migraphx"; } program load() { program p; if(is_test) { p = test_gemm(); } else { if(file_type.empty()) { file_type = get_file_type(file); } std::cout << "Reading: " << file << std::endl; if(file_type == "onnx") { p = parse_onnx(file, get_onnx_options()); } else if(file_type == "tf") { p = parse_tf(file, get_tf_options()); } else if(file_type == "json") { file_options options; options.format = "json"; p = migraphx::load(file, options); } #ifdef MIGRAPHX_ENABLE_PYTHON else if(file_type == "py") { p = migraphx::load_py(file); } #endif else if(file_type == "migraphx") { p = migraphx::load(file); } } if(trim > 0) { trim_module(*p.get_main_module(), trim, trim_size); } // Remove unused variable when exporting to cpp if(output_type == "cpp") migraphx::run_passes(*p.get_main_module(), {migraphx::dead_code_elimination{}}); if(optimize) { migraphx::run_passes(*p.get_main_module(), { migraphx::eliminate_identity{}, migraphx::dead_code_elimination{}, migraphx::simplify_algebra{}, migraphx::dead_code_elimination{}, migraphx::simplify_reshapes{}, migraphx::dead_code_elimination{}, migraphx::propagate_constant{}, migraphx::dead_code_elimination{}, migraphx::eliminate_pad{}, migraphx::dead_code_elimination{}, }); } if(not passes.empty()) migraphx::run_passes(p, get_passes(passes)); if(mlir) offload_to_mlir(p); return p; } static void write(std::ostream& os, const std::vector& buffer) { os.write(buffer.data(), buffer.size()); } void save(const program& p) const { auto* os = &std::cout; std::ofstream fs; if(not output.empty()) { fs.open(output, std::ios::binary); os = &fs; } std::string type = output_type; if(type.empty()) { if(output.empty()) type = "text"; else type = "binary"; } if(type == "py") p.print_py(*os); else if(type == "cpp") p.print_cpp(*os); else if(type == "graphviz") p.print_graph(*os, brief); else if(type == "text") *os << p << std::endl; else if(type == "json") *os << to_json_string(p.to_value()) << std::endl; else if(type == "binary") write(*os, save_buffer(p)); else if(type == "netron") *os << make_netron_output(p) << std::endl; } }; struct program_params { std::vector fill0{}; std::vector fill1{}; std::vector load_args_info; void parse(argument_parser& ap) { ap(fill0, {"--fill0"}, ap.help("Fill parameter with 0s"), ap.append(), ap.nargs(2)); ap(fill1, {"--fill1"}, ap.help("Fill parameter with 1s"), ap.append(), ap.nargs(2)); ap(load_args_info, {"--load-arg"}, ap.help("Load arguments for the model (format: \"@name filename\")"), ap.append(), ap.nargs(2)); } static auto parse_load_args(const std::vector& load_args_info, const target& t, bool offload) { parameter_map map_load_args; std::string name = ""; for(auto&& x : load_args_info) { if(x[0] == '@') { name = x.substr(1); } else { argument arg = migraphx::load_argument(x); if(not offload) arg = t.copy_to(arg); map_load_args[name] = arg; } } return map_load_args; } auto generate(const program& p, const target& t, bool offload, unsigned batch, dims_map map_input_dims = {}) { parameter_map m; auto param_shapes = p.get_parameter_shapes(); std::unordered_map static_param_shapes; for(auto&& param : param_shapes) { if(contains(map_input_dims, param.first)) static_param_shapes[param.first] = {param.second.type(), map_input_dims[param.first]}; else static_param_shapes[param.first] = param.second.to_static(batch); } for(auto&& s : fill0) m[s] = fill_argument(static_param_shapes.at(s), 0); for(auto&& s : fill1) m[s] = fill_argument(static_param_shapes.at(s), 1); fill_param_map(m, static_param_shapes, t, offload); auto load_arg_map = program_params::parse_load_args(load_args_info, t, offload); for(auto&& arg : load_arg_map) { m[arg.first] = arg.second; } return m; } }; struct compiler_target { #ifdef HAVE_GPU std::string target_name = "gpu"; #elif defined(HAVE_CPU) std::string target_name = "cpu"; #elif defined(HAVE_FPGA) std::string target_name = "fpga"; #else std::string target_name = "ref"; #endif void parse(argument_parser& ap) { ap(target_name, {"--gpu"}, ap.help("Compile on the gpu"), ap.set_value("gpu")); ap(target_name, {"--cpu"}, ap.help("Compile on the cpu"), ap.set_value("cpu")); ap(target_name, {"--ref"}, ap.help("Compile on the reference implementation"), ap.set_value("ref")); } target get_target() const { return make_target(target_name); } }; struct compiler { loader l; program_params parameters; compiler_target ct; compile_options co; bool to_fp16 = false; bool to_bf16 = false; bool to_fp8 = false; bool to_int8 = false; bool to_int4 = false; std::vector fill0; std::vector fill1; void parse(argument_parser& ap) { l.parse(ap); parameters.parse(ap); ct.parse(ap); ap(co.offload_copy, {"--enable-offload-copy"}, ap.help("Enable implicit offload copying"), ap.set_value(true)); ap(co.fast_math, {"--disable-fast-math"}, ap.help("Disable fast math optimization"), ap.set_value(false)); ap(co.exhaustive_tune, {"--exhaustive-tune"}, ap.help("Exhastively search for best tuning parameters for kernels"), ap.set_value(true)); ap(to_fp16, {"--fp16"}, ap.help("Quantize for fp16"), ap.set_value(true)); ap(to_bf16, {"--bf16"}, ap.help("Quantize for bf16"), ap.set_value(true)); ap(to_int8, {"--int8"}, ap.help("Quantize for int8"), ap.set_value(true)); ap(to_fp8, {"--fp8"}, ap.help("Quantize for fp8"), ap.set_value(true)); ap(to_int4, {"--int4-weights"}, ap.help("Quantize weights for int4"), ap.set_value(true)); } auto params(const program& p) { return parameters.generate( p, ct.get_target(), co.offload_copy, l.batch, loader::parse_param_dims(l.param_dims)); } auto host_params(const program& p) { return parameters.generate(p, ct.get_target(), true, l.batch); } program compile() { auto p = l.load(); // Dont compile if its already been compiled if(p.is_compiled()) { if(ct.target_name == "gpu") { if(is_offload_copy_set(p) and not co.offload_copy) { std::cerr << "[WARNING]: MIGraphX program was likely compiled with offload_copy " "set, Try " "passing " "`--enable-offload-copy` if program run fails.\n"; } else if(not is_offload_copy_set(p) and co.offload_copy) { std::cerr << "[WARNING]: MIGraphX program was likely compiled without " "offload_copy set, Try " "removing " "`--enable-offload-copy` if program run " "fails.\n"; } } std::cout << "The program is already compiled, skipping compilation ..." << std::endl; if(to_fp16 or to_bf16 or to_int8 or to_fp8 or to_int4) { std::cerr << "[WARNING]: Quantization options are ignored as the program is already " "compiled." << std::endl; } return p; } auto t = ct.get_target(); if(to_fp16) { std::cout << "Quantizing to fp16 ... " << std::endl; quantize_fp16(p); } if(to_bf16) { std::cout << "Quantizing to bf16 ... " << std::endl; quantize_bf16(p); } if(to_int8) { std::cout << "Quantizing to int8 ... " << std::endl; quantize_int8(p, t, {host_params(p)}); } if(to_fp8) { std::cout << "Quantizing to fp8 ... " << std::endl; quantize_fp8(p, t, {host_params(p)}); } if(to_int4) { std::cout << "Quantizing weights to int4 ... " << std::endl; quantize_int4_weights(p); } std::cout << "Compiling ... " << std::endl; p.compile(t, co); l.save(p); return p; } }; struct read : command { loader l; void parse(argument_parser& ap) { l.parse(ap); } void run() { auto p = l.load(); l.save(p); } }; struct params : command { loader l; void parse(argument_parser& ap) { l.parse(ap); } void run() { auto p = l.load(); for(auto&& param : p.get_parameter_shapes()) std::cout << param.first << ": " << param.second << std::endl; } }; struct verify : command { compiler c; std::optional rms_tol; std::optional atol; std::optional rtol; bool per_instruction = false; bool reduce = false; bool bisect = false; verify_options vo; void parse(argument_parser& ap) { c.parse(ap); ap(rms_tol, {"--rms-tol"}, ap.help("Tolerance for the RMS error")); ap(atol, {"--atol"}, ap.help("Tolerance for the elementwise absolute difference")); ap(rtol, {"--rtol"}, ap.help("Tolerance for the elementwise relative difference")); ap(per_instruction, {"-i", "--per-instruction"}, ap.help("Verify each instruction"), ap.set_value(true)); ap(reduce, {"-r", "--reduce"}, ap.help("Reduce program and verify"), ap.set_value(true)); ap(bisect, {"-b", "--bisect"}, ap.help("Bisect program and verify"), ap.set_value(true)); ap(vo.ref_use_double, {"--ref-use-double"}, ap.help("Convert floating point values to double on ref"), ap.set_value(true)); ap(vo.compiled_model, {"--compiled-model", "-c"}, ap.help("Compiled model to use")); } void run() { auto p = c.l.load(); c.l.save(p); std::cout << p << std::endl; auto t = c.ct.get_target(); auto m = c.parameters.generate(p, t, true, c.l.batch, loader::parse_param_dims(c.l.param_dims)); if(c.to_fp16) { vo.quantize = precision::fp16; } if(c.to_bf16) { vo.quantize = precision::bf16; } if(c.to_int8) { vo.quantize = precision::int8; } auto tols = get_tolerances(p, vo, rms_tol, atol, rtol); std::cout << "rms_tol: " << tols.rms_tol << std::endl; std::cout << "atol: " << tols.atol << std::endl; std::cout << "rtol: " << tols.rtol << std::endl; if(per_instruction) { verify_instructions(p, t, c.co, vo, tols); } else if(reduce) { verify_reduced_program(p, t, c.co, vo, m, tols); } else if(bisect) { verify_bisected_program(p, t, c.co, vo, m, tols); } else { verify_program(c.l.file, p, t, c.co, vo, m, tols); } } }; struct compile : command { compiler c; void parse(argument_parser& ap) { c.parse(ap); } void run() { c.compile(); } }; struct run_cmd : command { compiler c; void parse(argument_parser& ap) { c.parse(ap); } void run() { auto p = c.compile(); std::cout << "Allocating params ... " << std::endl; auto m = c.params(p); p.eval(m); std::cout << p << std::endl; } }; struct time_cmd : command { compiler c; unsigned n = 100; void parse(argument_parser& ap) { ap(n, {"--iterations", "-n"}, ap.help("Number of iterations to run.")); c.parse(ap); } void run() { auto p = c.compile(); std::cout << "Allocating params ... " << std::endl; auto m = c.params(p); std::cout << "Running ... " << std::endl; double t = time_run(p, m, n); std::cout << "Total time: " << t << "ms" << std::endl; } }; struct perf : command { compiler c; unsigned n = 100; bool detailed = false; void parse(argument_parser& ap) { c.parse(ap); ap(n, {"--iterations", "-n"}, ap.help("Number of iterations to run for perf report")); ap(detailed, {"--detailed", "-d"}, ap.help("Show a more detailed summary report"), ap.set_value(true)); } void run() { auto p = c.compile(); std::cout << "Allocating params ... " << std::endl; auto m = c.params(p); std::cout << "Running performance report ... " << std::endl; p.perf_report(std::cout, n, m, c.l.batch, detailed); } }; struct roctx : command { compiler c; void parse(argument_parser& ap) { c.parse(ap); } void run() { auto p = c.compile(); std::cout << "Allocating params ... " << std::endl; auto m = c.params(p); std::cout << "rocTX:\tLoading rocTX library..." << std::endl; auto rtx = create_marker_roctx(); p.mark(m, std::move(rtx)); } }; struct op : command { bool show_ops = false; std::string op_name{}; void parse(argument_parser& ap) { ap(op_name, {}, ap.metavar("")); ap(show_ops, {"--list", "-l"}, ap.help("List all the operators of MIGraphX"), ap.set_value(true)); } void run() const { if(show_ops) { for(const auto& name : get_operators()) std::cout << name << std::endl; } else { auto op = load_op(op_name); std::cout << op_name << ": " << std::endl; std::cout << to_pretty_json_string(op.to_value()) << std::endl; } } }; struct onnx : command { bool show_ops = false; void parse(argument_parser& ap) { ap(show_ops, {"--list", "-l"}, ap.help("List all onnx operators supported by MIGraphX"), ap.set_value(true)); } void run() const { if(show_ops) { for(const auto& name : get_onnx_operators()) std::cout << name << std::endl; } } }; struct tf : command { bool show_ops = false; void parse(argument_parser& ap) { ap(show_ops, {"--list", "-l"}, ap.help("List all tf operators supported by MIGraphX"), ap.set_value(true)); } void run() const { if(show_ops) { for(const auto& name : get_tf_operators()) std::cout << name << std::endl; } } }; struct main_command { static std::string get_command_help(const std::string& title = colorize(color::fg_yellow, "COMMANDS:")) { std::string result = title + "\n"; std::vector commands(get_commands().size()); std::transform(get_commands().begin(), get_commands().end(), commands.begin(), [](const auto& p) { return colorize(color::fg_green, p.first); }); std::sort(commands.begin(), commands.end()); return std::accumulate(commands.begin(), commands.end(), result, [](const auto& r, auto&& s) { return r + " " + s + "\n"; }); } void parse(argument_parser& ap) { std::string version_str = get_version(); ap(wrong_commands, {}, ap.metavar(""), ap.append()); ap(nullptr, {"-h", "--help"}, ap.help("Show help"), ap.show_help(get_command_help())); ap(nullptr, {"-v", "--version"}, ap.help("Show MIGraphX version"), ap.show_help(version_str)); ap(nullptr, {"--ort-sha"}, ap.help("Show MIGraphX onnx runtime SHA")); // Trim command off of exe name ap.set_exe_name(ap.get_exe_name().substr(0, ap.get_exe_name().size() - 5)); ap.set_exe_name_to(exe_name); } std::vector wrong_commands{}; std::string exe_name = ""; void run() { std::cout << color::fg_red << color::bold << "error: " << color::reset; auto it = std::find_if(wrong_commands.begin(), wrong_commands.end(), [](const auto& c) { return get_commands().count(c) > 0; }); if(it == wrong_commands.end()) { std::cout << "'" << color::fg_yellow << wrong_commands.front() << color::reset << "' is not a valid command." << std::endl; std::cout << get_command_help("Available commands:"); } else { std::cout << "command '" << color::fg_yellow << *it << color::reset << "' must be first argument" << std::endl; std::cout << std::endl; std::cout << color::fg_yellow << "USAGE:" << color::reset << std::endl; std::cout << " " << exe_name << " " << *it << " " << std::endl; } std::cout << std::endl; } }; } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx using namespace migraphx::driver; // NOLINT int main(int argc, const char* argv[], const char* envp[]) { std::vector args(argv + 1, argv + argc); // no argument, print the help infomration by default if(args.empty()) { args.push_back("-h"); } auto&& m = get_commands(); auto cmd = args.front(); if(cmd == "--ort-sha") { std::cout << MIGRAPHX_ORT_SHA1 << std::endl; return 0; } if(cmd == "-v" or cmd == "--version") { std::cout << get_version() << std::endl; return 0; } if(m.count(cmd) > 0) { std::string driver_invocation = std::string(argv[0]) + " " + migraphx::to_string_range(args, " "); std::cout << "Running [ " << get_version() << " ]: " << driver_invocation << std::endl; // Print start timestamp auto start_time = std::chrono::system_clock::now(); std::cout << "[" << get_formatted_timestamp(start_time) << "]" << std::endl; m.at(cmd)(argv[0], {args.begin() + 1, args.end()}); // run driver command found in commands map // Dump all the MIGraphX (consumed) Environment Variables: const auto mgx_env_map = migraphx::get_all_envs(); for(auto&& [k, v] : mgx_env_map) std::cout << k << "=" << v << "\\ \n"; // backslash(s) to facilitate cut-n-paste auto unused_envs = get_unrecognized_migraphx_envs(envp, mgx_env_map); for(auto&& e : unused_envs) std::cout << "Unused environment variable: " << e << "\n"; // Print end timestamp auto end_time = std::chrono::system_clock::now(); std::cout << "[" << get_formatted_timestamp(end_time) << "]" << std::endl; // Print total duration auto duration = std::chrono::duration_cast>(end_time - start_time); std::cout << "[ " << get_version() << " ] Complete(" << duration.count() << "s): " << driver_invocation << std::endl; } else { run_command(argv[0], args); } return 0; } ROCm-AMDMIGraphX-46524e8/src/driver/marker_roctx.cpp000066400000000000000000000054361510465702400220360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "marker_roctx.hpp" #include #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { class marker_roctx { std::function sym_roctx_mark; std::function sym_roctx_range_start; std::function sym_roctx_range_stop; std::function sym_roctx_range_push; std::function sym_roctx_range_pop; uint64_t range_id = 0; public: marker_roctx() { dynamic_loader lib = migraphx::dynamic_loader{"libroctx64.so"}; sym_roctx_mark = lib.get_function("roctxMarkA"); sym_roctx_range_start = lib.get_function("roctxRangeStartA"); sym_roctx_range_stop = lib.get_function("roctxRangeStop"); sym_roctx_range_push = lib.get_function("roctxRangePushA"); sym_roctx_range_pop = lib.get_function("roctxRangePop"); sym_roctx_mark("rocTX marker created."); } void mark_start(instruction_ref ins_ref) { std::string text = "Marker start: " + ins_ref->name(); sym_roctx_range_push(text.c_str()); } void mark_stop(instruction_ref) { sym_roctx_range_pop(); } void mark_start(const program&) { range_id = sym_roctx_range_start("0"); } void mark_stop(const program&) { sym_roctx_range_stop(range_id); } }; marker create_marker_roctx() { return marker_roctx(); } } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/marker_roctx.hpp000066400000000000000000000027511510465702400220400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MARKER_ROCTX_HPP #define MIGRAPHX_GUARD_RTGLIB_MARKER_ROCTX_HPP #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { marker create_marker_roctx(); } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/driver/mlir.cpp000066400000000000000000000034431510465702400202750ustar00rootroot00000000000000#include "mlir.hpp" #include #include #include #include #include #include #include #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { void offload_to_mlir(program& p) { auto* mm = p.get_main_module(); auto* mlirm = p.create_module("mlir"); mlirm->set_bypass(); std::vector inputs; copy_if(iterator_for(*mm), std::back_inserter(inputs), [&](instruction_ref ins) { if(ins->name() == "@param") return true; if(ins->name() == "@literal") return ins->get_shape().elements() != 1; return false; }); std::unordered_map map_ins; std::size_t n = 0; for(auto ins : inputs) { map_ins[ins] = mlirm->add_parameter(param_name(n++), ins->get_shape().as_standard()); } auto mlir_last = mlirm->add_instructions(mm, &map_ins); mlirm->add_return(mlir_last); auto last = std::prev(mm->end()); auto mlir_op = mm->insert_instruction(last, make_op("gpu::mlir_op"), inputs, {mlirm}); if(mlir_last.size() > 1) { std::vector outputs; transform(range(mlir_last.size()), std::back_inserter(outputs), [&](auto i) { return mm->insert_instruction(last, make_op("get_tuple_elem", {{"index", i}}), mlir_op); }); mm->replace_return(outputs); } else { mm->replace_return({mlir_op}); } run_passes(*mm, {dead_code_elimination{}}); } } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/mlir.hpp000066400000000000000000000005721510465702400203020ustar00rootroot00000000000000#ifndef MIGRAPHX_GUARD_DRIVER_MLIR_HPP #define MIGRAPHX_GUARD_DRIVER_MLIR_HPP #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { void offload_to_mlir(program& p); } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif // MIGRAPHX_GUARD_DRIVER_MLIR_HPP ROCm-AMDMIGraphX-46524e8/src/driver/models.cpp000066400000000000000000000033741510465702400206200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "models.hpp" #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { migraphx::program test_gemm() { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{migraphx::shape::float_type, {4, 5}}); auto b = mm->add_parameter("b", migraphx::shape{migraphx::shape::float_type, {5, 3}}); mm->add_instruction(migraphx::make_op("dot"), a, b); return p; } } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/models.hpp000066400000000000000000000026051510465702400206210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { migraphx::program test_gemm(); } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/passes.cpp000066400000000000000000000077321510465702400206350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "passes.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { static std::unordered_map create_passes_lookup() { std::unordered_map result; // clang-format off std::initializer_list passes = { auto_contiguous{}, dead_code_elimination{}, eliminate_allocation{}, eliminate_common_subexpression{}, eliminate_concat{}, eliminate_contiguous{}, eliminate_data_type{}, eliminate_identity{}, eliminate_pad{}, fuse_pointwise{}, fuse_reduce{}, inline_module{}, insert_pad{}, normalize_ops{}, optimize_module{}, promote_literals{}, propagate_constant{}, rewrite_dot{}, rewrite_gelu{}, rewrite_pooling{}, rewrite_quantization{}, rewrite_rnn{}, simplify_algebra{}, simplify_dyn_ops{}, simplify_qdq{}, simplify_reshapes{}, }; // clang-format on for(const auto& pass : passes) result[pass.name()] = pass; result["eliminate_dead_code"] = dead_code_elimination{}; return result; } std::vector get_passes(const std::vector& names) { std::vector result; static const std::unordered_map lookup = create_passes_lookup(); std::transform( names.begin(), names.end(), std::back_inserter(result), [](const std::string& name) { if(not contains(lookup, name)) MIGRAPHX_THROW("Unknown pass: " + name); return lookup.at(name); }); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/passes.hpp000066400000000000000000000030241510465702400206300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_DRIVER_PASSES_HPP #define MIGRAPHX_GUARD_DRIVER_PASSES_HPP #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { std::vector get_passes(const std::vector& names); } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/driver/perf.cpp000066400000000000000000000112531510465702400202640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "perf.hpp" #include #include #include #include #include #include #ifdef HAVE_GPU #include #endif namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { using milliseconds = std::chrono::duration; template static auto get_hash(const T& x) { return std::hash{}(x); } parameter_map fill_param_map(parameter_map& m, const std::unordered_map& param_shapes, const target& t, bool offload) { for(auto&& x : param_shapes) { argument& arg = m[x.first]; if(arg.empty()) { assert(not x.second.dynamic()); arg = generate_argument(x.second, get_hash(x.first), random_mode::random); } if(not offload) arg = t.copy_to(arg); } return m; } parameter_map create_param_map(const program& p, const target& t, bool offload) { parameter_map m; for(auto&& x : p.get_parameter_shapes()) { auto arg = generate_argument(x.second, get_hash(x.first), random_mode::random); if(offload) m[x.first] = arg; else m[x.first] = t.copy_to(arg); } return m; } parameter_map create_param_map(const program& p, bool gpu) { parameter_map m; for(auto&& x : p.get_parameter_shapes()) { #ifdef HAVE_GPU if(gpu) m[x.first] = gpu::to_gpu(generate_argument(x.second, get_hash(x.first), random_mode::random)); else #else (void)gpu; #endif m[x.first] = generate_argument(x.second, get_hash(x.first), random_mode::random); } return m; } target get_target(bool gpu) { if(gpu) return make_target("gpu"); else return make_target("cpu"); } bool is_offload_copy_set(const program& p) { assert(p.is_compiled()); const module* mm = p.get_main_module(); std::vector param_names = mm->get_parameter_names(); std::unordered_set param_ins; std::transform(param_names.begin(), param_names.end(), std::inserter(param_ins, param_ins.begin()), [&](const auto& i) { return mm->get_parameter(i); }); for(const auto& i : *mm) { if(i.name() == "hip::copy_to_gpu") { auto copy_arg = instruction::get_output_alias(i.inputs().front(), true); param_ins.erase(copy_arg); } else if(i.name() == "@return") { auto return_args = i.inputs(); for(const auto& j : return_args) { auto alias_ins = instruction::get_output_alias(j, true); if((alias_ins->name() == "@param" and param_ins.erase(alias_ins) == 0) or (alias_ins->name() != "hip::copy_from_gpu")) return false; } } } return param_ins.empty(); } double time_run(const program& p, const parameter_map& m, int n) { // Run once without timing p.eval(m); p.finish(); double total = time([&] { for(auto i : range(n)) { (void)i; p.eval(m); } p.finish(); }); return total / n; } } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/perf.hpp000066400000000000000000000046321510465702400202740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_PERF_HPP #define MIGRAPHX_GUARD_RTGLIB_PERF_HPP #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { parameter_map fill_param_map(parameter_map& m, const std::unordered_map& param_shapes, const target& t, bool offload = false); parameter_map create_param_map(const program& p, const target& t, bool offload = false); parameter_map fill_param_map(parameter_map& m, const program& p, bool gpu); parameter_map create_param_map(const program& p, bool gpu = true); target get_target(bool gpu); /** * @brief Checks if MIGraphX program compiled for "GPU" has offload_copy set of not. This is intended to print a HINT for the users and would not always correctly classify compiled program as with or without offload_copy in all cases. * @param p Compiled MIGraphX program for GPU backend * @return true if program is classified as compiled with "offload_copy" set */ bool is_offload_copy_set(const program& p); double time_run(const program& p, const parameter_map& m, int n = 100); } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/driver/precision.hpp000066400000000000000000000027461510465702400213370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_PRECISION_HPP #define MIGRAPHX_GUARD_RTGLIB_PRECISION_HPP namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { enum class precision { fp32, fp16, bf16, int8 }; } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/driver/trim.cpp000066400000000000000000000072751510465702400203140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include "trim.hpp" #include #include #include #include #include #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { static instruction_ref capture_arg(std::unordered_set& s, instruction_ref ins) { auto alias = instruction::get_output_alias(ins, true); if(alias != ins) { s.insert(ins); return capture_arg(s, alias); } if(contains({"reshape", "contiguous"}, ins->name())) { s.insert(ins); return capture_arg(s, ins->inputs().front()); } return ins; } static instruction_ref add_placeholder(module& m, instruction_ref ins) { if(ins->inputs().empty()) return ins; if(ins->can_eval()) { auto e = ins->eval(); return m.add_literal(literal{e.get_shape(), e.data()}); } return m.add_parameter("x" + std::to_string(m.get_parameters().size()), ins->get_shape()); } void trim_module(module& m, std::size_t loc, std::size_t n) { if(loc > m.size()) MIGRAPHX_THROW("Trim out of range."); auto last = std::prev(m.end(), loc); auto start = std::prev(last, n); m.remove_instructions(last, m.end()); if(n == 0) return; if(n > m.size()) MIGRAPHX_THROW("Trim size out of range."); std::unordered_map map_ins; std::unordered_set instruction_set; auto instructions = range(start, m.end()); for(instruction_ref ins : iterator_for(instructions)) { instruction_set.insert(ins); for(auto input : ins->inputs()) { if(contains(instruction_set, input)) continue; auto arg = capture_arg(instruction_set, input); auto placeholder = add_placeholder(m, arg); assert(placeholder->get_shape() == arg->get_shape()); if(placeholder == arg) continue; instruction_set.insert(placeholder); map_ins[arg] = placeholder; } } for(auto [old_ins, new_ins] : map_ins) m.replace_instruction(old_ins, new_ins); run_passes(m, {dead_code_elimination{}}); for(auto pins : m.get_parameters()) { if(not pins->outputs().empty()) continue; m.remove_instruction(pins); } } } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/trim.hpp000066400000000000000000000030451510465702400203100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DRIVER_TRIM_HPP #define MIGRAPHX_GUARD_RTGLIB_DRIVER_TRIM_HPP #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { void trim_module(module& m, std::size_t loc, std::size_t n); } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/driver/verify.cpp000066400000000000000000000270271510465702400206420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify.hpp" #include "perf.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { /** * Gives tolerances based on user input (`rms_tol`, `atol`, `rtol` parameters) and defaults. * Sets to fp16 tolerances if `quantize` input is fp16 or any fp16 instruction in found in the * model. */ verify::tolerance get_tolerances(const program& p, const verify_options& vo, std::optional rms_tol, std::optional atol, std::optional rtol) { bool has_16bit = any_of(p.get_modules(), [](auto&& m) { return any_of(*m, [](auto&& ins) { return (ins.get_shape().type() == shape::half_type or ins.get_shape().type() == shape::bf16_type); }); }); migraphx::verify::tolerance result{}; if(has_16bit or vo.quantize == precision::fp16 or vo.quantize == precision::bf16) { result.rms_tol = 8e-2; result.atol = 4e-2; result.rtol = 4e-2; } if(rms_tol) { result.rms_tol = *rms_tol; } if(atol) { result.atol = *atol; } if(rtol) { result.rtol = *rtol; } return result; } static std::vector run_ref(program p, const compile_options& options, const verify_options& vo, const parameter_map& inputs) { if(vo.ref_use_double) { run_passes(p, {fp_to_double{}}); } p.compile(migraphx::make_target("ref"), options); auto out = p.eval(inputs); std::cout << p << std::endl; return out; } static std::vector run_target(program p, const target& t, const compile_options& options, const verify_options& vo, const parameter_map& inputs) { if(vo.compiled_model.empty()) { if(vo.quantize == precision::fp16) { quantize_fp16(p); } if(vo.quantize == precision::bf16) { quantize_bf16(p); } p.compile(t, options); } else { p = load(vo.compiled_model); } parameter_map m; for(auto&& x : p.get_parameter_shapes()) { auto arg = inputs.count(x.first) == 0 ? generate_argument(x.second) : inputs.at(x.first); m[x.first] = options.offload_copy ? arg : t.copy_to(arg); } auto gpu_out = p.eval(m); std::vector output(gpu_out.size()); std::cout << p << std::endl; std::transform(gpu_out.begin(), gpu_out.end(), output.begin(), [&](auto& argu) { return options.offload_copy ? argu : t.copy_from(argu); }); return output; } bool verify_program(const std::string& name, const program& p, const target& t, compile_options options, const verify_options& vo, const parameter_map& inputs, verify::tolerance tols) { auto ref_outs = run_ref(p, options, vo, inputs); auto target_outs = run_target(p, t, options, vo, inputs); std::size_t output_num = ref_outs.size(); bool passed = true; for(std::size_t i = 0; i < output_num; ++i) { if(ref_outs[i].get_shape().type() != target_outs[i].get_shape().type() or ref_outs[i].get_shape().lens() != target_outs[i].get_shape().lens()) { std::cout << "FAILED: " << name << std::endl; std::cout << "Shape mismatch {" << ref_outs[i].get_shape() << "} != {" << target_outs[i].get_shape() << "}" << std::endl; } else { passed &= verify_args(name, target_outs[i], verify::expected{ref_outs[i]}, tols); } } if(passed) std::cout << "MIGraphX verification passed successfully." << std::endl; return passed; } void verify_instructions(const program& prog, const target& t, compile_options options, const verify_options& vo, verify::tolerance tols) { const auto* mm_prog = prog.get_main_module(); for(auto&& ins : (*mm_prog)) { if(ins.name().front() == '@') continue; if(ins.name() == "broadcast") continue; if(ins.name() == "transpose") continue; if(ins.name() == "reshape") continue; if(ins.name() == "undefined") continue; program p; auto* mm_p = p.get_main_module(); std::vector inputs; for(auto&& arg : ins.inputs()) { if(arg->name() == "@literal") inputs.push_back(mm_p->add_literal(arg->get_literal())); else inputs.push_back( mm_p->add_parameter(std::to_string(inputs.size()), arg->get_shape())); } mm_p->add_instruction(ins.get_operator(), inputs); try { std::cout << "Verify: " << ins.name() << std::endl; std::cout << p << std::endl; verify_program(ins.name(), p, t, options, vo, create_param_map(p, false), tols); } catch(...) { std::cout << "Instruction " << ins.name() << " threw an exception." << std::endl; throw; } } } static bool verify_reduced(program p, int n, const target& t, compile_options options, const verify_options& vo, const parameter_map& inputs, verify::tolerance tols) { auto* mm = p.get_main_module(); auto last = std::prev(mm->end(), n); mm->remove_instructions(last, mm->end()); std::cout << "Verify: " << n << std::endl; std::cout << p << std::endl; try { return verify_program(std::to_string(n), p, t, options, vo, inputs, tols); } catch(const std::exception& e) { std::cout << "FAILED: " << n << std::endl; std::cout << "Exception: " << e.what() << std::endl; return false; } } void verify_reduced_program(const program& p, const target& t, compile_options options, const verify_options& vo, const parameter_map& inputs, verify::tolerance tols) { const auto* mm = p.get_main_module(); auto n = std::distance(mm->begin(), mm->end()); std::cout << "Verify steps: " << n << std::endl; for(std::size_t i = 1; i < n; i++) { auto last = std::prev(mm->end(), i + 1); if(contains({"@literal", "@param"}, last->name())) { std::cout << "Skip: " << i << std::endl; continue; } verify_reduced(p, i, t, options, vo, inputs, tols); } } static std::unordered_map accumulate_weights(instruction_ref last) { std::unordered_map weights; fix([&](auto self, auto ins) -> std::size_t { if(not contains(weights, ins)) { if(ins->can_eval()) return 0; std::size_t weight = 1; weights[ins] = std::accumulate( ins->inputs().begin(), ins->inputs().end(), weight, [&](std::size_t w, instruction_ref i) -> std::size_t { return w + self(i); }); } return weights[ins]; })(last); return weights; } static optional get_parent(const std::unordered_map& weights, instruction_ref ins) { if(ins->inputs().empty()) return nullopt; auto next = std::max_element(ins->inputs().begin(), ins->inputs().end(), by(std::less<>{}, [&](instruction_ref input) -> std::size_t { if(not contains(weights, input)) return 0; return weights.at(input); })); return *next; } static std::vector find_trim_instructions(const module& m) { std::vector result; auto last = std::prev(m.end()); auto weights = accumulate_weights(last); auto next = get_parent(weights, last); std::size_t i = 0; while(auto parent = get_parent(weights, *next)) { i += std::distance(*parent, *next); result.push_back(i + 1); next = parent; } return result; } void verify_bisected_program(const program& p, const target& t, compile_options options, const verify_options& vo, const parameter_map& inputs, verify::tolerance tols) { const auto* mm = p.get_main_module(); std::vector trims = find_trim_instructions(*mm); std::int64_t right = trims.size(); std::int64_t left = 0; std::int64_t failed = -1; while(left <= right) { std::int64_t mid = left + (right - left) / 2; assert(mid < trims.size() and mid >= 0); std::int64_t trim = trims.rbegin()[mid]; bool passed = verify_reduced(p, trim, t, options, vo, inputs, tols); if(passed) { left = mid + 1; } else { failed = trim; right = mid - 1; } } if(failed > 0) { std::cout << "Failure starts at: " << failed << std::endl; } } } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/driver/verify.hpp000066400000000000000000000063551510465702400206500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DRIVER_VERIFY_HPP #define MIGRAPHX_GUARD_RTGLIB_DRIVER_VERIFY_HPP #include "verify_options.hpp" #include #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { verify::tolerance get_tolerances(const program& p, const verify_options& vo, std::optional rms_tol, std::optional atol, std::optional rtol); bool verify_program(const std::string& name, const program& p, const target& t, compile_options options = compile_options{}, const verify_options& vo = verify_options{}, const parameter_map& inputs = {}, verify::tolerance tols = verify::tolerance{}); void verify_instructions(const program& prog, const target& t, compile_options options = compile_options{}, const verify_options& vo = verify_options{}, verify::tolerance tols = verify::tolerance{}); void verify_reduced_program(const program& p, const target& t, compile_options options = compile_options{}, const verify_options& vo = verify_options{}, const parameter_map& inputs = {}, verify::tolerance tols = verify::tolerance{}); void verify_bisected_program(const program& p, const target& t, compile_options options = compile_options{}, const verify_options& vo = verify_options{}, const parameter_map& inputs = {}, verify::tolerance tols = verify::tolerance{}); } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/driver/verify_options.hpp000066400000000000000000000033501510465702400224130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DRIVER_VERIFY_OPTIONS_HPP #define MIGRAPHX_GUARD_RTGLIB_DRIVER_VERIFY_OPTIONS_HPP #include "precision.hpp" #include namespace migraphx { namespace driver { inline namespace MIGRAPHX_INLINE_NS { struct verify_options { /// Quantization precision precision quantize = precision::fp32; /** * Converts floating point values to double on the ref target. */ bool ref_use_double = false; std::string compiled_model = ""; }; } // namespace MIGRAPHX_INLINE_NS } // namespace driver } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/dynamic_loader.cpp000066400000000000000000000137461510465702400210200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #ifdef _WIN32 // cppcheck-suppress definePrefix #define WIN32_LEAN_AND_MEAN #include #else #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifndef _WIN32 static void check_load_error(bool flush = false) { char* error_msg = dlerror(); if(not flush and error_msg != nullptr) MIGRAPHX_THROW("Dynamic loading or symbol lookup failed with " + std::string(error_msg)); } struct dynamic_loader_impl { dynamic_loader_impl() = default; #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wignored-attributes" #endif dynamic_loader_impl(const fs::path& p, std::shared_ptr t = nullptr) : handle(dlopen(p.string().c_str(), RTLD_GLOBAL | RTLD_NOW), manage_deleter{}), temp(std::move(t)) { check_load_error(); } #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif static std::shared_ptr from_buffer(const char* image, std::size_t size) { auto t = std::make_shared("dloader"); auto f = t->path / "libtmp.so"; write_buffer(f, image, size); return std::make_shared(f, t); } std::shared_ptr handle = nullptr; std::shared_ptr temp = nullptr; }; fs::path dynamic_loader::path(void* address) { fs::path p; Dl_info info; // Find the location of .so if(dladdr(address, &info) != 0) p = info.dli_fname; return p; } #else struct dynamic_loader_impl { dynamic_loader_impl() = default; dynamic_loader_impl(const fs::path& p, tmp_dir t = {}) : handle{LoadLibrary(p.string().c_str())}, temp{std::move(t)} { if(handle == nullptr) { MIGRAPHX_THROW("Error loading DLL: " + p.string() + " (" + std::to_string(GetLastError()) + ")"); } } dynamic_loader_impl(const dynamic_loader_impl&) = delete; dynamic_loader_impl& operator=(const dynamic_loader_impl&) = delete; dynamic_loader_impl(dynamic_loader_impl&&) = default; ~dynamic_loader_impl() { if(handle != nullptr) { FreeLibrary(handle); } } static std::shared_ptr from_buffer(const char* image, std::size_t size) { auto t = tmp_dir{"migx-dynload"}; auto f = t.path / "tmp.dll"; write_buffer(f, image, size); return std::make_shared(f, std::move(t)); } HMODULE handle = nullptr; tmp_dir temp; }; fs::path dynamic_loader::path(void* address) { HMODULE module = nullptr; if(GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast(address), &module) == 0) { auto err = GetLastError(); MIGRAPHX_THROW("Unable to obtain module handle, error = " + std::to_string(err)); } TCHAR buffer[MAX_PATH]; if(GetModuleFileName(module, buffer, sizeof(buffer)) == 0) { auto err = GetLastError(); MIGRAPHX_THROW("Unable to read module file path, error = " + std::to_string(err)); } if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) { MIGRAPHX_THROW("Buffer too small (" + std::to_string(MAX_PATH) + ") to hold the path"); } return {buffer}; } #endif optional dynamic_loader::try_load(const fs::path& p) { try { return dynamic_loader{p}; } catch(const std::exception&) { return nullopt; } } dynamic_loader::dynamic_loader(const fs::path& p) : impl(std::make_shared(p)) { } dynamic_loader::dynamic_loader(const char* image, std::size_t size) : impl(dynamic_loader_impl::from_buffer(image, size)) { } dynamic_loader::dynamic_loader(const std::vector& buffer) : impl(dynamic_loader_impl::from_buffer(buffer.data(), buffer.size())) { } std::shared_ptr dynamic_loader::get_symbol(const std::string& name) const { #ifndef _WIN32 // flush any previous error messages check_load_error(true); void* symbol = dlsym(impl->handle.get(), name.c_str()); if(symbol == nullptr) check_load_error(); return {impl, symbol}; #else FARPROC addr = GetProcAddress(impl->handle, name.c_str()); if(addr == nullptr) MIGRAPHX_THROW("Symbol not found: " + name + " (" + std::to_string(GetLastError()) + ")"); return {impl, reinterpret_cast(addr)}; #endif } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/eliminate_allocation.cpp000066400000000000000000000046421510465702400222150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void eliminate_allocation::apply(module& m) const { assert(alignment > 0); std::size_t n = 0; std::vector> allocs; for(auto ins : iterator_for(m)) { if(ins->name() != allocation_op) continue; allocs.emplace_back(ins, n); std::size_t size = ins->get_shape().bytes(); std::size_t padding = (alignment - (size % alignment)) % alignment; n += size + padding; } if(n > 0) { auto mem = m.add_parameter("memory", shape{shape::int8_type, {n}}); for(auto&& pp : allocs) { auto ins = pp.first; auto s = ins->get_shape(); auto offset = pp.second; m.replace_instruction( ins, make_op("load", {{"shape", to_value(s)}, {"offset", offset}}), mem); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/eliminate_common_subexpression.cpp000066400000000000000000000055631510465702400243540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template static void cse_range(module& m, Range&& r) { std::unordered_multimap instructions; std::unordered_set processed_ins; for(auto ins : r) { // Skip dead instructions if(ins->outputs().empty()) continue; // Find instruction with the same name auto found_instructions = range(instructions.equal_range(ins->name())); for(const auto& pp : found_instructions) { auto eq = pp.second; if(contains(processed_ins, eq)) continue; if(*eq != *ins) continue; m.replace_instruction(ins, eq); processed_ins.emplace(ins); std::vector outputs; std::copy_if(eq->outputs().begin(), eq->outputs().end(), std::back_inserter(outputs), [&](auto x) { return m.has_instruction(x); }); std::sort(outputs.begin(), outputs.end(), [&](auto x, auto y) { return std::distance(eq, x) < std::distance(eq, y); }); cse_range(m, outputs); } instructions.emplace(ins->name(), ins); } } void eliminate_common_subexpression::apply(module& m) const { cse_range(m, iterator_for(m)); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/eliminate_concat.cpp000066400000000000000000000116121510465702400213320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void eliminate_concat::apply(module& m) const { for(auto ins : iterator_for(m)) { auto concat_op = concat_opt.get_concat(ins->get_operator()); // Look for the concat operator if(not concat_op.has_value()) continue; // If any inputs are builtin or context free then abort // If any inputs are used more than once, then abort since there could // be errors due to aliasing if(std::any_of(ins->inputs().begin(), ins->inputs().end(), [](auto arg) { return arg->name().front() == '@' or (arg->get_operator().is_context_free() and not contains({"concat", "identity"}, arg->name())) or arg->outputs().size() > 1; })) continue; // We can only do this optimization when concat axis is either the leftmost // axis OR the sizes to the left of this axis are all equal to 1 // Since we've already checked that the non-axis dimensions are identical // we only need to check the first input auto lens = ins->inputs().front()->get_shape().lens(); std::size_t axis_index = tune_axis(lens.size(), concat_op->axis, concat_op->name()); if(axis_index == 0 or std::all_of(lens.begin(), lens.begin() + axis_index, [](auto x) { return x == 1; })) { // Last input should be an allocation auto last = ins->inputs().back(); if(last->name() != concat_opt.allocate()) continue; // Where are the allocations for the tensors to be concatenated? std::vector allocations; std::transform( ins->inputs().begin(), std::prev(ins->inputs().end()), std::back_inserter(allocations), [&](instruction_ref x) { return instruction::get_output_alias(x, true); }); if(std::any_of(allocations.begin(), allocations.end(), [&](auto x) { return x->name() != concat_opt.allocate(); })) continue; // Need to sort the allocations, so that we know where to // insert the "super"-allocation auto sorted_allocations = allocations; std::sort(sorted_allocations.begin(), sorted_allocations.end(), [&](instruction_ref x, instruction_ref y) { return std::distance(m.begin(), x) < std::distance(m.begin(), y); }); // Move "super" allocation to the front auto first = sorted_allocations.front(); auto super = m.move_instruction(last, first); // Replace each allocation with a load std::size_t offset = 0; for(auto alloc : allocations) { op::load op{alloc->get_shape(), offset}; m.replace_instruction(alloc, op, {super}); offset += alloc->get_shape().bytes(); } std::vector args = {super}; std::copy(ins->inputs().begin(), ins->inputs().end() - 1, std::back_inserter(args)); m.replace_instruction(ins, migraphx::make_op("identity"), args); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/eliminate_contiguous.cpp000066400000000000000000000150741510465702400222700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_ELIMINATE_CONTIGUOUS) static bool try_compute_shape(instruction_ref ins, const std::vector& inputs, const std::vector& mods) { try { shape new_shape = ins->get_operator().compute_shape(inputs, mods); // Cannot tell if a dynamic shape will need to be made contiguous if(new_shape.dynamic()) { return false; } // If the output shape is a standard shape, no need to try its output if(new_shape.standard()) { return true; } // if no changes for the shape, the contiguous can also be removed if(new_shape == ins->get_shape()) { return true; } auto outputs = ins->outputs(); // If the current instruction has no output, it means it is the last // instruction and generates a non-standard output shape, and the last // output shape is different from the case with the contiguous operator if(outputs.empty()) { return false; } for(auto output : outputs) { auto args = output->inputs(); std::vector input_shapes(args.size()); std::transform(args.begin(), args.end(), input_shapes.begin(), [&](auto& arg) { return (arg == ins) ? new_shape : arg->get_shape(); }); if(not try_compute_shape(output, input_shapes, output->module_inputs())) { return false; } } } catch(const std::exception& e) { if(enabled(MIGRAPHX_TRACE_ELIMINATE_CONTIGUOUS{})) { std::cout << "Exception: " << e.what() << std::endl; } return false; } catch(...) { if(enabled(MIGRAPHX_TRACE_ELIMINATE_CONTIGUOUS{})) { std::cout << "Unknown exception" << std::endl; } return false; } return true; } static bool try_compute_shape(instruction_ref ins, const std::vector& args, const std::vector& mods) { auto inputs = to_shapes(args); return try_compute_shape(ins, inputs, mods); } template static void remove_contiguous(const std::string& op_name, module& m, F f) { auto last = std::prev(m.end()); std::vector const_instructions; for(auto ins : iterator_for(m)) { // return instruction should have inputs with standard shape if(ins->name() == "@return") continue; if(ins != last and ins->outputs().empty()) continue; if(not f(ins)) continue; auto args = ins->inputs(); auto mod_args = ins->module_inputs(); for(auto arg : ins->inputs()) { if(arg->name() != op_name) continue; if(enabled(MIGRAPHX_TRACE_ELIMINATE_CONTIGUOUS{})) { std::cout << "eliminate_contiguous: "; m.debug_print(ins); } auto prev = arg->inputs().front(); // create copy of args each time as they are modified inside the loop auto new_args = ins->inputs(); replace(new_args, arg, prev); if(try_compute_shape(ins, new_args, mod_args)) { instruction::replace_argument(ins, arg, prev); } else if(prev->can_eval()) { const_instructions.push_back(arg); } } } // Perform static contiguous evaluations in parallel std::vector literals(const_instructions.size()); par_for(const_instructions.size(), 1, [&](const auto i) { auto c = op::contiguous{}; auto prev = const_instructions[i]->inputs().front(); // compute the output contiguous shape from the previous instruction shape shape computed_shape = c.compute_shape({prev->get_shape()}); const std::vector& prev_eval = {prev->eval()}; // prev_eval should not be used in make_compute_output_shape() as computed_shape is static auto co_shape = make_compute_output_shape(pack(c, computed_shape, prev_eval)); literals[i] = c.compute(co_shape, prev_eval); }); // Replace static contiguous operations with a literal for(size_t i = 0; i < const_instructions.size(); i++) { auto l = m.add_literal(literals[i].get_shape(), literals[i].data()); m.replace_instruction(const_instructions[i], l); } } void eliminate_contiguous::apply(module& m) const { // Skip contiguous from splits first remove_contiguous(op_name, m, [](auto ins) { if(ins->name() != "slice") return true; return (ins->inputs().front()->outputs().size() == 1); }); remove_contiguous(op_name, m, [](auto) { return true; }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/eliminate_convert.cpp000066400000000000000000000056171510465702400215530ustar00rootroot00000000000000/* * The MIT License (MIT) ,* * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * Matches with some sequence of sequential convert instructions. * If input to the sequence of converts has the same shape as the last convert, * replace last convert with the input. * If input to the sequence is not the same shape as the last convert, * replace last convert with convert from the input to the last shape. */ struct find_nested_convert { auto matcher() const { return match::name("convert")(match::arg(0)(match::name("convert"))); } void apply(module& m, const match::matcher_result& mr) const { auto matched_ins = mr.result; auto prev_convert = matched_ins->inputs().front(); auto input = prev_convert->inputs().front(); while(input->name() == "convert") { input = input->inputs().front(); } if(matched_ins->get_shape() == input->get_shape()) { m.replace_instruction(matched_ins, input); } else { m.replace_instruction(matched_ins, matched_ins->get_operator(), input); } } }; struct find_nop_converts { auto matcher() const { return match::name("convert")(match::same_shape(match::arg(0))); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; m.replace_instruction(ins, ins->inputs().front()); } }; void eliminate_convert::apply(module& m) const { match::find_matches(m, find_nested_convert{}, find_nop_converts{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/eliminate_data_type.cpp000066400000000000000000000124211510465702400220340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static void insert_convert_to_supported_type(module& m, instruction_ref ins, migraphx::shape::type_t target_type, std::set unsupported_types) { migraphx::shape::type_t orig_type = ins->get_shape().type(); std::vector inputs = ins->inputs(); std::transform(inputs.begin(), inputs.end(), inputs.begin(), [&](const auto& i) { if(contains(unsupported_types, i->get_shape().type())) { return m.insert_instruction( ins, migraphx::make_op("convert", {{"target_type", migraphx::to_value(target_type)}}), i); } else { return i; } }); // if no change if(inputs == ins->inputs()) return; auto op = ins->get_operator(); auto attributes = op.attributes(); if(attributes.contains("general_data_type")) { op = make_op(attributes["general_data_type"].to(), op.to_value()); } auto new_ins = m.insert_instruction(ins, op, inputs); if(orig_type == shape::tuple_type) { auto orig_outs = ins->outputs(); if(not std::all_of(orig_outs.begin(), orig_outs.end(), [&](const auto out_ins) { return out_ins->name() == "get_tuple_elem"; })) MIGRAPHX_THROW( "eliminate_data_type: Instruction with tuple output doesn't have all its " "usages as get_tuple_elem instruction"); std::transform( orig_outs.begin(), orig_outs.end(), orig_outs.begin(), [&](const auto out_ins) { auto gte_ins = m.insert_instruction(ins, out_ins->get_operator(), new_ins); auto orig_out_type = out_ins->get_shape().type(); if(contains(unsupported_types, orig_out_type)) { auto gte_convert = m.insert_instruction( ins, make_op("convert", {{"target_type", orig_out_type}}), gte_ins); return m.replace_instruction(out_ins, gte_convert); } else { return m.replace_instruction(out_ins, gte_ins); } }); } else { auto convert_back_ins = m.insert_instruction( ins, migraphx::make_op("convert", {{"target_type", migraphx::to_value(orig_type)}}), new_ins); m.replace_instruction(ins, convert_back_ins); } } void eliminate_data_type::apply(module& m) const { static const std::vector skip_op_names = {"convert", "get_tuple_elem", "if", "loop", "roialign", "nonmaxsuppression", "scatternd_add", "scatternd_mul", "scatternd_none", "select_module"}; if(unsupported_types.empty()) return; for(auto ins : iterator_for(m)) { if(ins->name()[0] == '@') continue; if(contains(skip_op_names, ins->name()) and not contains(unsupported_ops, ins->name())) continue; if(contains(unsupported_ops, "all") or contains(unsupported_ops, ins->name())) insert_convert_to_supported_type(m, ins, target_type, unsupported_types); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/eliminate_identity.cpp000066400000000000000000000050621510465702400217160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void eliminate_identity::apply(module& m) const { auto last = std::prev(m.end()); for(auto ins : iterator_for(m)) { // Skip the first instruction, since we always process the previous // instruction if(ins == m.begin()) continue; const auto i = std::prev(ins); if(i->name() == "identity") { m.replace_instruction(i, i->inputs().front()); m.move_instruction(i, m.end()); } if(ins == last) { if(ins->name() == "identity") { const instruction_ref& identity_input = ins->inputs().front(); if(identity_input->outputs().size() == 1) { m.move_instruction(identity_input, last); // since this is the last instruction, removing it only // requires changing "last" and calling remove below last = std::prev(last); } } break; } } m.remove_instructions(std::next(last), m.end()); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/eliminate_pad.cpp000066400000000000000000000101461510465702400206300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static void update_op(const instruction_ref& input, const instruction_ref& ins, module& m) { auto pad_op = any_cast(input->get_operator()); auto kdims = input->get_shape().lens().size() - 2; auto kdims_it = pad_op.pads.begin() + 2; std::vector pads_l(kdims_it, kdims_it + kdims); std::vector pads_r(kdims_it + kdims + 2, pad_op.pads.end()); auto op = ins->get_operator(); std::vector padding(kdims * 2, 0); std::transform( pads_l.begin(), pads_l.end(), padding.begin(), padding.begin(), std::plus()); std::transform(pads_r.begin(), pads_r.end(), padding.begin() + kdims, padding.begin() + kdims, std::plus()); op.from_value({{"padding", padding}}); std::vector new_inputs{ins->inputs()}; new_inputs.front() = input->inputs().front(); m.replace_instruction(ins, op, new_inputs); } static void update_pooling(const instruction_ref& input, const instruction_ref& ins, module& m) { auto op = any_cast(ins->get_operator()); if(op.mode == op::pooling_mode::average) { return; } auto pad_op = any_cast(input->get_operator()); auto kdims = input->get_shape().lens().size() - 2; auto kdims_it = pad_op.pads.begin() + 2; std::vector pads_l(kdims_it, kdims_it + kdims); std::vector pads_r(kdims_it + kdims + 2, pad_op.pads.end()); std::transform( pads_l.begin(), pads_l.end(), op.padding.begin(), op.padding.begin(), std::plus()); std::transform(pads_r.begin(), pads_r.end(), op.padding.begin() + kdims, op.padding.begin() + kdims, std::plus()); std::vector new_inputs{ins->inputs()}; new_inputs.front() = input->inputs().front(); m.replace_instruction(ins, op, new_inputs); } void eliminate_pad::apply(module& m) const { for(auto ins : iterator_for(m)) { const std::string& op_name = ins->name(); if(op_name != "convolution" and op_name != "im2col" and op_name != "pooling") continue; auto input = ins->inputs().front(); if(input->name() != "pad") continue; if(op_name == "convolution" or op_name == "im2col") update_op(input, ins, m); else if(op_name == "pooling") update_pooling(input, ins, m); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/env.cpp000066400000000000000000000051071510465702400166260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static auto access_envs() { static std::map obj; static std::mutex m; return [&](auto f) { std::lock_guard lock(m); return f(obj); }; } bool enabled(const char* name) { auto e = env(name); if(e.empty() or not contains({"1", "enable", "enabled", "yes", "true"}, e.front())) return false; access_envs()([&](auto& m) { m[name] = e.front(); }); return true; } bool disabled(const char* name) { return not enabled(name); } std::size_t value_of(const char* name, std::size_t fallback) { auto e = env(name); if(e.empty()) return fallback; access_envs()([&](auto& m) { m[name] = e.front(); }); return std::stoul(e.front()); } std::string string_value_of(const char* name, std::string fallback) { auto e = env(name); if(e.empty()) return fallback; auto rv = e.front(); access_envs()([&](auto& m) { m[name] = rv; }); return rv; } std::vector env(const char* name) { auto* p = std::getenv(name); if(p == nullptr) return {}; return {{p}}; } std::map get_all_envs() { return access_envs()([&](const auto& m) { return m; }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/file_buffer.cpp000066400000000000000000000061161510465702400203070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template static T generic_read_file(const fs::path& filename, size_t offset = 0, size_t nbytes = 0) { std::ifstream is(filename, std::ios::binary | std::ios::ate); if(not is.is_open()) MIGRAPHX_THROW("Failure opening file: " + filename); if(nbytes == 0) { // if there is a non-zero offset and nbytes is not set, // calculate size of remaining bytes to read nbytes = is.tellg(); if(offset > nbytes) MIGRAPHX_THROW("offset is larger than file size"); nbytes -= offset; } if(nbytes < 1) MIGRAPHX_THROW("Invalid size for: " + filename); is.seekg(offset, std::ios::beg); T buffer(nbytes, 0); if(not is.read(&buffer[0], nbytes)) MIGRAPHX_THROW("Error reading file: " + filename); return buffer; } std::vector read_buffer(const fs::path& filename, size_t offset, size_t nbytes) { return generic_read_file>(filename, offset, nbytes); } std::string read_string(const fs::path& filename) { return generic_read_file(filename); } void write_string(const fs::path& filename, const std::string& buffer) { write_buffer(filename, buffer.data(), buffer.size()); } void write_buffer(const fs::path& filename, const char* buffer, std::size_t size) { std::ofstream os(filename, std::ios::out | std::ios::binary); if(os.fail()) MIGRAPHX_THROW("Failure opening file: " + filename); os.write(buffer, size); if(os.bad()) MIGRAPHX_THROW("Error writing file: " + filename); } void write_buffer(const fs::path& filename, const std::vector& buffer) { write_buffer(filename, buffer.data(), buffer.size()); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/fileutils.cpp000066400000000000000000000050411510465702400200330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef _WIN32 constexpr std::string_view executable_postfix{".exe"}; constexpr std::string_view library_prefix{""}; constexpr std::string_view library_postfix{".dll"}; constexpr std::string_view static_library_postfix{".lib"}; constexpr std::string_view object_file_postfix{".obj"}; #else constexpr std::string_view executable_postfix{""}; constexpr std::string_view library_prefix{"lib"}; constexpr std::string_view library_postfix{".so"}; constexpr std::string_view static_library_postfix{".a"}; constexpr std::string_view object_file_postfix{".o"}; #endif fs::path make_executable_filename(std::string_view name) { return std::string{name}.append(executable_postfix); } fs::path make_shared_object_filename(std::string_view name) { return std::string{library_prefix}.append(name).append(library_postfix); } fs::path make_object_file_filename(std::string_view name) { return std::string{name}.append(object_file_postfix); } fs::path make_static_library_filename(std::string_view name) { return std::string{library_prefix}.append(name).append(static_library_postfix); } fs::path append_extension(const fs::path& path, std::string_view ext) { return fs::path{path}.replace_extension(path.extension().string().append(ext)); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/fp8_ocp_to_fnuz.cpp000066400000000000000000000177651510465702400211550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { using fp8::fp8e4m3fnuz; std::unordered_set get_quantizable_op_names() { static std::unordered_set s = {"convolution", "dot"}; return s; } struct match_fp8ocp_convert_to_fp8fnuz { auto matcher() const { auto dq1 = match::arg(0)( skip_post_dq_ops(match::dequantizelinear_op("scale1", "zp1").bind("dq1"))); auto dq2 = match::arg(1)( skip_post_dq_ops(match::dequantizelinear_op("scale2", "zp2").bind("dq2"))); return match::name(get_quantizable_op_names())(dq1, dq2); } static auto bit_cast_and_handle_specials(module& m, const instruction_ref dq, const instruction_ref x, const instruction_ref bits_0x80_lit, const instruction_ref bits_0x7f_lit, const instruction_ref bits_0xff_lit, const instruction_ref bits_0x00_lit) { auto x_lens = x->get_shape().lens(); auto cast_input = m.insert_instruction( dq, make_op("bit_cast", {{"target_type", shape::fp8e4m3fnuz_type}}), x); auto mb_bits_0x80_lit = m.insert_instruction( dq, make_op("multibroadcast", {{"out_lens", x_lens}}), bits_0x80_lit); auto mb_bits_0x7f_lit = m.insert_instruction( dq, make_op("multibroadcast", {{"out_lens", x_lens}}), bits_0x7f_lit); auto mb_bits_0xff_lit = m.insert_instruction( dq, make_op("multibroadcast", {{"out_lens", x_lens}}), bits_0xff_lit); auto mb_zero_lit = m.insert_instruction( dq, make_op("multibroadcast", {{"out_lens", x_lens}}), bits_0x00_lit); // negative zero in fp8e4m3fn to zero in fp8e4m3fnuz // a == 0x80 ? 0x0 : a auto is_neg_zero = m.insert_instruction(dq, make_op("equal"), cast_input, mb_bits_0x80_lit); auto ret = m.insert_instruction(dq, make_op("where"), is_neg_zero, mb_zero_lit, cast_input); // positive and negative NaN in fp8e4m3fn to NaN in fp8e4m3fnuz // (a == 0x7f or a == 0xff) ? 0x80 : a auto eq_0x7f = m.insert_instruction(dq, make_op("equal"), ret, mb_bits_0x7f_lit); auto eq_0xff = m.insert_instruction(dq, make_op("equal"), ret, mb_bits_0xff_lit); auto cond = m.insert_instruction(dq, make_op("logical_or"), eq_0x7f, eq_0xff); ret = m.insert_instruction(dq, make_op("where"), cond, mb_bits_0x80_lit, ret); return ret; } // Add the same broadcast instructions after adjusted scales or // adjusted zero points from after the originals. Similar to // propagate_quantized_ins in simplify_qdq. static auto propagate_broadcasts(module& m, const instruction_ref adj, const instruction_ref ori, const instruction_ref start, const instruction_ref insert_pt) { auto prev_ins = start; std::vector ins_between; // matcher skips continguous, multi/broadcasts and transposes, collect all those // instructions while(prev_ins != ori) { ins_between.push_back(prev_ins); prev_ins = prev_ins->inputs().front(); } auto ret = adj; for(auto ins : reverse_iterator_for(ins_between)) { ret = m.insert_instruction(insert_pt, (*ins)->get_operator(), {ret}); } return ret; } static auto cast_to_fnuz(module& m, const instruction_ref dq, const instruction_ref input, const instruction_ref dq_scale, const instruction_ref dq_zp) { auto x = input; std::vector bits_0x80 = {fp8e4m3fnuz(0x80, fp8e4m3fnuz::from_bits())}; auto bits_0x80_lit = m.add_literal(shape{shape::fp8e4m3fnuz_type, {1}, {0}}, bits_0x80); std::vector bits_0x7f = {fp8e4m3fnuz(0x7f, fp8e4m3fnuz::from_bits())}; auto bits_0x7f_lit = m.add_literal(shape{shape::fp8e4m3fnuz_type, {1}, {0}}, bits_0x7f); std::vector bits_0xff = {fp8e4m3fnuz(0xff, fp8e4m3fnuz::from_bits())}; auto bits_0xff_lit = m.add_literal(shape{shape::fp8e4m3fnuz_type, {1}, {0}}, bits_0xff); std::vector bits_0x00 = {fp8e4m3fnuz(0x00, fp8e4m3fnuz::from_bits())}; auto bits_0x00_lit = m.add_literal(shape{shape::fp8e4m3fnuz_type, {1}, {0}}, bits_0x00); x = bit_cast_and_handle_specials( m, dq, x, bits_0x80_lit, bits_0x7f_lit, bits_0xff_lit, bits_0x00_lit); auto adj_dq_zp = bit_cast_and_handle_specials( m, dq, dq_zp, bits_0x80_lit, bits_0x7f_lit, bits_0xff_lit, bits_0x00_lit); // adj_scale = 2 * scale auto two_lit = m.add_literal(literal{shape{dq_scale->get_shape().type()}, {2}}); two_lit = m.insert_instruction( dq, make_op("multibroadcast", {{"out_lens", dq_scale->get_shape().lens()}}), two_lit); auto adj_dq_scale = m.insert_instruction(dq, make_op("mul"), dq_scale, two_lit); adj_dq_scale = propagate_broadcasts(m, adj_dq_scale, dq_scale, dq->inputs().at(1), dq); adj_dq_zp = propagate_broadcasts(m, adj_dq_zp, dq_zp, dq->inputs().at(2), dq); m.replace_instruction(dq, make_op("dequantizelinear"), x, adj_dq_scale, adj_dq_zp); } auto apply(module& m, const match::matcher_result& r) const { auto dq1 = r.instructions["dq1"]; auto dq2 = r.instructions["dq2"]; auto scale1 = r.instructions["scale1"]; auto scale2 = r.instructions["scale2"]; auto zp1 = r.instructions["zp1"]; auto zp2 = r.instructions["zp2"]; std::set supported_types = {migraphx::shape::fp8e4m3fn_type}; if(not contains(supported_types, dq1->inputs().front()->get_shape().type()) or not contains(supported_types, dq2->inputs().front()->get_shape().type())) return; cast_to_fnuz(m, dq1, dq1->inputs().front(), scale1, zp1); cast_to_fnuz(m, dq2, dq2->inputs().front(), scale2, zp2); } }; } // namespace void fp8_ocp_to_fnuz::apply(module_pass_manager& mpm) const { module_ref mm = &mpm.get_module(); match::find_matches(*mm, match_fp8ocp_convert_to_fp8fnuz{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/fp_to_double.cpp000066400000000000000000000032631510465702400205000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void fp_to_double::apply(module_pass_manager& mpm) const { mpm.run_pass(eliminate_data_type{convert_fp_types, shape::type_t::double_type}); mpm.run_pass(eliminate_convert{}); mpm.run_pass(migraphx::dead_code_elimination{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/fuse_attention.cpp000066400000000000000000000200771510465702400210700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { // TODO: Write this in matcher.hpp as a general matcher for iterating through inputs inline auto pointwise_inputs() { return [](auto start, auto f) { std::unordered_set visited; fix([&](auto self, auto ins) { if(ins->can_eval()) return; if(not visited.insert(ins).second) return; if(not ins->get_operator().attributes().contains("pointwise") and ins->get_operator().name() != "reshape") { f(ins); return; } for(auto input : ins->inputs()) self(input); })(start); }; } struct find_attention { std::size_t* counter; auto matcher() const { auto gemm1 = match::any_of[pointwise_inputs()](match::name("dot").bind("dot1")); auto softmax = match::skip(match::name("convert"))(match::softmax_input(gemm1)); return match::name("dot")(match::arg(0)(softmax)); } std::string get_count() const { return std::to_string((*counter)++); } std::unordered_map invert_map_ins(const std::unordered_map& map_ins) const { std::unordered_map inverse_map; for(auto const& [key, value] : map_ins) { assert(not contains(inverse_map, value)); inverse_map[value] = key; } return inverse_map; } std::vector get_attn_instructions(module& m, instruction_ref gemm1, instruction_ref gemm2) const { auto attn_inss = find_instructions_between(gemm1, gemm2, &m); std::vector sorted_inss(attn_inss.begin(), attn_inss.end()); std::sort( sorted_inss.begin(), sorted_inss.end(), [&](instruction_ref x, instruction_ref y) { return std::distance(m.begin(), x) < std::distance(m.begin(), y); }); return sorted_inss; } static bool has_lse_out(std::vector& group_outs) { return (group_outs.size() == 3 and std::all_of(group_outs.begin(), group_outs.end(), [](auto o) { return contains({"dot", "reduce_max", "reduce_sum"}, o->name()); })); } std::vector get_lse_instructions(std::vector& group_outs) const { std::vector lse_inss; auto rsum = *std::find_if( group_outs.begin(), group_outs.end(), [](auto o) { return o->name() == "reduce_sum"; }); auto rsum_outs = rsum->outputs(); auto log = std::find_if( rsum_outs.begin(), rsum_outs.end(), [](auto o) { return o->name() == "log"; }); if(log == rsum_outs.end()) return lse_inss; auto log_outs = (*log)->outputs(); if(log_outs.size() != 1 or log_outs.front()->name() != "add") return lse_inss; auto add = log_outs.front(); lse_inss.insert(lse_inss.end(), {*log, add}); return lse_inss; } std::vector find_outputs(std::vector inss) const { std::vector outputs; std::copy_if(inss.begin(), inss.end(), std::back_inserter(outputs), [&](auto i) { return not std::all_of(i->outputs().begin(), i->outputs().end(), [&](auto o) { return contains(inss, o); }); }); return outputs; } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto gemm2 = r.result; auto gemm1 = r.instructions["dot1"]; auto softmax_input = r.instructions["x"]; // Capture all instructions part of the attention op auto attn_inss = get_attn_instructions(mpm.get_module(), gemm1, gemm2); // Add captured instructions to new submodule module m_attn; std::unordered_map map_mm_to_mattn; m_attn.fuse(attn_inss, &map_mm_to_mattn); // Define outputs based on instructions that are used elsewhere in the graph auto required_outputs = find_outputs(attn_inss); assert(not required_outputs.empty()); // LSE case requires output from reduce_max and reduce_sum instructions if(has_lse_out(required_outputs)) { auto lse_inss = get_lse_instructions(required_outputs); m_attn.fuse(lse_inss, &map_mm_to_mattn); // Recompute required outputs after adding lse instructions attn_inss.insert(attn_inss.end(), lse_inss.begin(), lse_inss.end()); required_outputs = find_outputs(attn_inss); // Final outputs should be the second gemm and add instruction // (shift lse to adjust for subtracting max during stable softmax computation) if(required_outputs.size() != 2) return; } else if(required_outputs.size() > 1) { return; } // Find corresponding output instructions in m_attn std::vector m_attn_outputs; std::transform(required_outputs.begin(), required_outputs.end(), std::back_inserter(m_attn_outputs), [&](auto i) { return map_mm_to_mattn.at(i); }); m_attn.add_return(m_attn_outputs); // Define inputs to m_attn auto map_mattn_to_mm = invert_map_ins(map_mm_to_mattn); auto new_inputs = m_attn.get_inputs(map_mattn_to_mm); module_ref mpm_attn = mpm.create_module("attn" + get_count(), std::move(m_attn)); mpm_attn->set_bypass(); auto group_ins = mpm.get_module().insert_instruction( softmax_input, make_op("group", {{"tag", "attention"}}), new_inputs, {mpm_attn}); if(m_attn_outputs.size() == 1) { mpm.get_module().replace_instruction(required_outputs.front(), group_ins); } else { for(std::size_t i = 0; i < required_outputs.size(); ++i) { mpm.get_module().replace_instruction( required_outputs[i], make_op("get_tuple_elem", {{"index", i}}), group_ins); } } } }; } // namespace void fuse_attention::apply(module_pass_manager& mpm) const { std::size_t counter = 0; match::find_matches(mpm, find_attention{.counter = &counter}); mpm.get_module().sort(); mpm.run_pass(dead_code_elimination{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/fuse_concat.cpp000066400000000000000000000254521510465702400203340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct fused_concat { int64_t axis = 0; std::string name() const { return "fused_concat"; } template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } shape compute_shape(std::vector inputs, const std::vector& mods) const { check_shapes{inputs, *this}.same_ndims(); // original concat can have multiple inputs. Let's say it has `n` input args. // Each of those `n` input args are converted into pointwise modules that take atleast 1 // input parameter. Fused concat will have `n+1` module arguments. `n+1`th module is the // post pointwise module which can take 0 or more input arguments. if((inputs.size() + 1) < mods.size()) MIGRAPHX_THROW("FUSED_CONCAT: Missing fused modules inputs parameters"); auto input_iter = inputs.begin(); std::vector concat_inputs; for(module_ref mod : range(mods.begin(), mods.end() - 1)) { concat_inputs.push_back(*input_iter); input_iter += mod->get_parameter_names().size(); } module_ref post_mod = mods.back(); // post_mod has one input argument that is result of concat and will get generated from // pre-mods internally. Therefore deduct 1 from post_mod params while asserting. assert(input_iter + (post_mod->get_parameter_names().size() - 1) == inputs.end()); auto type = std::prev(post_mod->end())->get_shape().type(); const auto& first_shape_lens = concat_inputs.front().lens(); auto mismatch_it = std::find_if_not(concat_inputs.begin() + 1, concat_inputs.end(), [&](auto s) { const auto& lens = s.lens(); return std::equal(lens.begin(), lens.begin() + axis, first_shape_lens.begin(), first_shape_lens.begin() + axis) and std::equal(lens.begin() + axis + 1, lens.end(), first_shape_lens.begin() + axis + 1, first_shape_lens.end()); }); if(mismatch_it != concat_inputs.end()) MIGRAPHX_THROW("FUSED_CONCAT: all input dimensions should match along non-axis of " + std::to_string(axis) + ": {" + to_string_range(first_shape_lens) + "} != {" + to_string_range(mismatch_it->lens()) + "}"); std::size_t new_dim_axis = transform_accumulate( concat_inputs.begin(), concat_inputs.end(), 0, std::plus<>{}, [&](const auto& input) { return input.lens()[axis]; }); auto new_lens = concat_inputs.front().lens(); new_lens[axis] = new_dim_axis; return shape::from_permutation(type, new_lens, find_permutation(inputs)); } }; MIGRAPHX_REGISTER_OP(fused_concat); namespace { bool is_fusable_pointwise(instruction_ref ins) { return ins->name() == "pointwise" and ins->outputs().size() == 1 and ins->get_shape().type() != shape::tuple_type; } template auto fusable_pointwise(Ts... xs) { return match::name("pointwise")(match::not_tuple(), xs...); } template struct concat_counter { static_assert(N < Max, "Factor N must be less than Max"); std::shared_ptr counter = std::make_shared(0); unsigned int get_noop_counter() const { if(counter == nullptr) MIGRAPHX_THROW("Invalid counter"); return N + Max * (*counter)++; } }; struct find_concat_pointwise : concat_counter<0> { auto matcher() const { auto pointwise_used_once = fusable_pointwise(match::used_once()); return match::name("concat")(match::used_once(), match::any_of[match::inputs()](pointwise_used_once)); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto concat_ins = r.result; std::vector inputs; size_t num_noops = 0; for(auto input : concat_ins->inputs()) { if(input->name() == "pointwise" and input->outputs().size() == 1) { inputs.insert(inputs.end(), input->inputs().begin(), input->inputs().end()); } else { num_noops++; inputs.push_back(input); } } if(num_noops > std::max(size_t{1}, concat_ins->inputs().size() / 4)) { return; } std::vector module_inputs; std::transform(concat_ins->inputs().begin(), concat_ins->inputs().end(), std::back_inserter(module_inputs), [&](instruction_ref input) { if(is_fusable_pointwise(input)) { auto* pm = input->module_inputs().front(); return mpm.create_module("concat:" + pm->name(), *pm); } auto* pm = mpm.create_module("concat:noop" + std::to_string(get_noop_counter())); auto x = pm->add_parameter("x0", shape{input->get_shape().type()}); pm->add_return({x}); return pm; }); auto* post_pm = mpm.create_module("noop:concat" + std::to_string(get_noop_counter())); auto x = post_pm->add_parameter("!x0", shape{concat_ins->get_shape().type()}); post_pm->add_return({x}); module_inputs.push_back(post_pm); mpm.get_module().replace_instruction( concat_ins, make_op("fused_concat", concat_ins->normalized_operator().to_value()), inputs, module_inputs); } }; struct find_pointwise_concat_pointwise : concat_counter<1> { auto matcher() const { auto pointwise = fusable_pointwise(match::used_once()); auto concat = match::name("concat")(match::used_once(), match::any_of[match::inputs()](pointwise)); return fusable_pointwise(match::any_of[match::inputs()](concat.bind("concat"))); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto concat_ins = r.instructions["concat"]; auto concat_arg = std::find(ins->inputs().begin(), ins->inputs().end(), concat_ins) - ins->inputs().begin(); std::vector inputs; for(auto input : concat_ins->inputs()) { if(input->name() == "pointwise" and input->outputs().size() == 1) inputs.insert(inputs.end(), input->inputs().begin(), input->inputs().end()); else inputs.push_back(input); } std::copy_if(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(inputs), [&](auto input) { return input != concat_ins; }); std::vector module_inputs; std::transform(concat_ins->inputs().begin(), concat_ins->inputs().end(), std::back_inserter(module_inputs), [&](instruction_ref input) { if(is_fusable_pointwise(input)) { auto* pm = input->module_inputs().front(); return mpm.create_module("concat:" + pm->name(), *pm); } auto* pm = mpm.create_module("concat:noop" + std::to_string(get_noop_counter())); auto x = pm->add_parameter("x0", shape{input->get_shape().type()}); pm->add_return({x}); return pm; }); auto* post_pm = ins->module_inputs().front(); auto* rm = mpm.create_module(post_pm->name() + ":concat", *post_pm); std::vector names = rm->get_parameter_names(); std::sort(names.begin(), names.end()); auto concat_param_name = names[concat_arg]; auto concat_param = rm->get_parameter(concat_param_name); auto param = rm->add_parameter("!" + concat_param_name, concat_param->get_shape()); rm->replace_instruction(concat_param, param); rm->remove_instruction(concat_param); module_inputs.push_back(rm); mpm.get_module().replace_instruction( ins, make_op("fused_concat", concat_ins->normalized_operator().to_value()), inputs, module_inputs); } }; } // namespace void fuse_concat::apply(module_pass_manager& mpm) const { match::find_matches(mpm, find_pointwise_concat_pointwise{}); mpm.run_pass(migraphx::dead_code_elimination{}); match::find_matches(mpm, find_concat_pointwise{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/fuse_pointwise.cpp000066400000000000000000000336301510465702400211030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_POINTWISE_FUSION) namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static literal get_scalar(instruction_ref ins) { if(contains({"contiguous", "broadcast", "multibroadcast"}, ins->name())) return get_scalar(ins->inputs().front()); const auto& s = ins->get_shape(); if(s.elements() != 1 and not(s.scalar())) return {}; if(not ins->can_eval()) return {}; auto e = ins->eval(); literal r{}; // needed for bool as visit_at invokes as() which promotes bool to int8 // Without this we'll break type checks for logical ops that are fused. if(e.get_shape().type() == shape::bool_type) { r = literal{e.at()}; } else { e.visit_at([&](auto x) { r = literal{x}; }); } return r; } static shape to_scalar(const shape& s) { return shape{s.type()}; } static bool is_dead(instruction_ref ins) { if(ins->name() == "@return") return false; if(ins->outputs().empty()) return true; if(ins->name() != "pointwise") return false; return ends_with(ins->module_inputs().front()->name(), "-deleted"); } // We dont want to consider the `extra` instruction as dead as it might be an implicit return static bool is_used_once(instruction_ref ins, instruction_ref* extra = nullptr) { return std::count_if(ins->outputs().begin(), ins->outputs().end(), [&](auto output) { if(extra and *extra == output) return true; return not is_dead(output); }) == 1; } static void create_pointwise_modules(module_pass_manager& mpm) { std::size_t n = 0; for(auto ins : iterator_for(mpm.get_module())) { if(not ins->get_operator().attributes().get("pointwise", false)) continue; if(ins->get_operator().name() == "layout") continue; auto* pm = mpm.create_module(mpm.get_module().name() + ":pointwise" + std::to_string(n++)); pm->set_bypass(); std::unordered_map param_map; std::vector pointwise_inputs; std::size_t i = 0; for(auto input : ins->inputs()) { if(contains(param_map, input)) continue; auto scalar = get_scalar(input); if(scalar.empty()) { pointwise_inputs.push_back(input); param_map[input] = pm->add_parameter(param_name(i), shape{input->get_shape().type()}); i++; } else { param_map[input] = pm->add_literal(scalar); } } // Don't create pointwise module if no inputs are detected if(pointwise_inputs.empty()) continue; std::vector inputs; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(inputs), [&](auto input) { return param_map[input]; }); auto r = pm->add_instruction(ins->get_operator(), inputs); pm->add_return({r}); mpm.get_module().replace_instruction(ins, make_op("pointwise"), pointwise_inputs, {pm}); } } static module::with_inputs append_pointwise_module(instruction_ref ins, instruction_ref output) { std::unordered_set original_inputs{ins->inputs().begin(), ins->inputs().end()}; original_inputs.insert(output->inputs().begin(), output->inputs().end()); module pm = *ins->module_inputs().at(0); module_ref xm = output->module_inputs().at(0); const bool dependent = contains(output->inputs(), ins); assert(not dependent or pm.get_returns().size() == 1); std::unordered_map map_ins = pm.get_ins_param_map(ins->inputs()); if(dependent) map_ins[ins] = pm.get_returns().front(); auto returns = pm.fuse(*xm, output->inputs(), &map_ins, nullptr, &to_scalar); if(not is_used_once(ins, &output) or not dependent) { auto ireturns = pm.get_returns(); returns.insert(returns.end(), ireturns.begin(), ireturns.end()); } pm.replace_return(returns); auto inputs = find_inputs(map_ins, original_inputs, &pm); return {std::move(pm), inputs}; } static void move_output_instructions_after(module& m, instruction_ref src, instruction_ref dst) { auto d = std::distance(src, dst); std::vector> instructions; fix([&](auto self, instruction_ref ins) { for(auto output : ins->outputs()) { if(any_of(instructions, [&](const auto& p) { return p.second == output; })) continue; auto i = std::distance(src, output); if(i >= d) continue; instructions.emplace_back(i, output); self(output); } })(src); std::sort(instructions.begin(), instructions.end(), by(std::less<>{}, [](auto&& p) { return p.first; })); auto loc = std::next(dst); for(auto [i, ins] : instructions) m.move_instruction(ins, loc); } static void replace_with_tuple(module& m, instruction_ref ins, instruction_ref rep, bool first) { if(rep->get_shape().type() != shape::tuple_type) { assert(ins->get_shape().type() != shape::tuple_type); m.replace_instruction(ins, rep); return; } if(ins->get_shape().type() != shape::tuple_type) { auto i = first ? 0 : rep->get_shape().sub_shapes().size() - 1; auto elem = m.insert_instruction(std::next(rep), make_op("get_tuple_elem", {{"index", i}}), rep); m.replace_instruction(ins, elem); return; } // TODO: We need to add a new operator to repack a tuple to support this scenario if(std::any_of(ins->outputs().begin(), ins->outputs().end(), [](instruction_ref output) { return output->name() != "get_tuple_elem"; })) MIGRAPHX_THROW("Unsupported tuple replacement"); std::size_t start = first ? 0 : rep->get_shape().sub_shapes().size() - ins->get_shape().sub_shapes().size(); auto outputs = ins->outputs(); for(auto output : outputs) { auto v = output->get_operator().to_value(); auto i = v.at("index").to(); assert((i + start) < rep->get_shape().sub_shapes().size()); m.replace_instruction(output, make_op("get_tuple_elem", {{"index", i + start}}), rep); } } static instruction_ref merge_instruction(module_pass_manager& mpm, instruction_ref input, instruction_ref output) { auto fused = append_pointwise_module(input, output); auto name = fused.mod.name(); mpm.rename_module(name, name + ":" + output->module_inputs().front()->name() + "-deleted"); auto* new_pm = mpm.create_module(name, std::move(fused.mod)); auto fins = mpm.get_module().insert_instruction(output, input->get_operator(), fused.inputs, {new_pm}); if(fins->get_shape().tuple_size() != output->get_shape().tuple_size()) { move_output_instructions_after(mpm.get_module(), input, fins); replace_with_tuple(mpm.get_module(), input, fins, false); } replace_with_tuple(mpm.get_module(), output, fins, true); return fins; } static auto find_input_pointwise(const module& m, instruction_ref ins, bool multi_out) { auto it = std::find_if(ins->inputs().begin(), ins->inputs().end(), [&](auto i) { return i->name() == "pointwise" and i->outputs().size() == 1 and m.has_instruction(i); }); if(it == ins->inputs().end() and multi_out) { it = std::find_if(ins->inputs().begin(), ins->inputs().end(), [&](auto i) { if(not m.has_instruction(i)) return false; auto base_distance = std::distance(i, ins); return i->name() == "pointwise" and std::none_of(i->outputs().begin(), i->outputs().end(), [&](auto output) { if(not m.has_instruction(output)) return false; if(output == ins) return false; if(std::distance(i, output) > base_distance) return false; return reaches(output, ins, &m); }); }); } return it; } static std::vector find_output_pointwise(const module& m, instruction_ref ins, bool multi_out) { std::vector result; if(not multi_out) return result; std::vector outputs; std::copy_if(ins->outputs().begin(), ins->outputs().end(), std::back_inserter(outputs), [&](auto output) { return output->name() == "pointwise" and m.has_instruction(output) and not is_dead(output); }); if(outputs.size() < 2) return result; std::sort(outputs.begin(), outputs.end(), by(std::less<>{}, [&](auto x) { return std::distance(ins, x); })); std::copy_if(outputs.begin(), outputs.end(), std::back_inserter(result), [&](auto output) { return std::none_of( result.begin(), result.end(), [&](auto other) { return reaches(other, output, &m); }); }); return result; } static bool find_pointwise_modules(module_pass_manager& mpm, bool multi_out) { bool changed = false; auto last = std::prev(mpm.get_module().end()); for(auto ins : iterator_for(mpm.get_module())) { if(ins != last and is_dead(ins)) continue; auto pw_outs = find_output_pointwise(mpm.get_module(), ins, multi_out); if(pw_outs.size() > 1) { (void)std::accumulate( pw_outs.begin() + 1, pw_outs.end(), pw_outs.front(), [&](auto input, auto output) { return merge_instruction(mpm, input, output); }); changed = true; } else if(ins->name() == "pointwise") { auto it = find_input_pointwise(mpm.get_module(), ins, multi_out); if(it == ins->inputs().end()) continue; auto input = *it; if(is_dead(input)) continue; merge_instruction(mpm, input, ins); changed = true; } } return changed; } namespace { struct pointwise_reshape : rewrite_reshapes_base { static std::string name() { return "pointwise"; } }; struct pointwise_broadcast_pointwise { auto matcher() const { auto broadcast_pointwise = match::name("multibroadcast")( match::used_once(), match::args(match::name("pointwise")(match::used_once()).bind("x"))) .bind("broadcast"); return match::name("pointwise")(match::any_of[match::inputs()](broadcast_pointwise)); } void apply(module& m, const match::matcher_result& r) const { auto broadcast_ins = r.instructions["broadcast"]; auto x_ins = r.instructions["x"]; auto broadcast = broadcast_ins->get_operator(); auto x_inputs = x_ins->inputs(); std::transform(x_inputs.begin(), x_inputs.end(), x_inputs.begin(), [&](auto input) { return m.insert_instruction(broadcast_ins, broadcast, input); }); m.replace_instruction( broadcast_ins, x_ins->get_operator(), x_inputs, x_ins->module_inputs()); } }; } // namespace static void rewrite_broadcasts(module_pass_manager& mpm) { match::find_matches(mpm.get_module(), pointwise_broadcast_pointwise{}); mpm.run_pass(dead_code_elimination{}); } void fuse_pointwise::apply(module_pass_manager& mpm) const { mpm.run_pass(eliminate_identity{}); create_pointwise_modules(mpm); mpm.run_pass(dead_code_elimination{}); if(enabled(MIGRAPHX_DISABLE_POINTWISE_FUSION{})) { return; } for(int i = 0; i < 8; i++) { if(enable_rewrite_reshapes) mpm.run_pass(rewrite_reshapes{}); if(enable_rewrite_broadcasts) rewrite_broadcasts(mpm); if(not find_pointwise_modules(mpm, enable_multi_output)) break; mpm.run_pass(dead_code_elimination{}); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/fuse_pointwise_reduce.cpp000066400000000000000000000050041510465702400224240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_SPLIT_REDUCE_SIZE); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_MULTI_OUTPUT_FUSION); static std::size_t get_split_size(std::size_t default_split) { std::string value = string_value_of(MIGRAPHX_SPLIT_REDUCE_SIZE{}); if(value.empty()) return default_split; return std::stoul(value); } void fuse_pointwise_reduce::apply(module_pass_manager& mpm) const { mpm.run_pass(fuse_pointwise{.enable_rewrite_reshapes = false}); mpm.run_pass(optimize_module{}); mpm.run_pass(fuse_reduce{.enable_rewrite_reshapes = false}); mpm.run_pass(fuse_pointwise{.enable_rewrite_reshapes = true}); mpm.run_pass(fuse_reduce{.enable_rewrite_reshapes = true}); mpm.run_pass(split_reduce{.split_size = get_split_size(split_size)}); mpm.run_pass(fuse_pointwise{.enable_rewrite_broadcasts = true}); if(not enabled(MIGRAPHX_DISABLE_MULTI_OUTPUT_FUSION{})) { mpm.run_pass(fuse_pointwise{.enable_multi_output = true}); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/fuse_reduce.cpp000066400000000000000000000370771510465702400203420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_REDUCE_FUSION) struct fused_reduce { std::vector axes{}; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes")); } shape compute_shape(const std::vector& inputs, std::vector mods) const { if(mods.size() != 1) MIGRAPHX_THROW("should have one submodule."); const auto* sm = mods.front(); if(sm->get_output_shapes().size() != 1) MIGRAPHX_THROW("Only one output supported"); if(not sm->bypass()) MIGRAPHX_THROW("fused_reduce: bypass flag is not set"); auto names = sm->get_parameter_names(); check_shapes{inputs, *this}.has(names.size()).same_ndims(); std::sort(names.begin(), names.end()); auto shapes = sm->get_parameter_shapes(); // Check dimension matches for each input if(not equal(names, inputs, [&](const auto& name, const auto& input) { return shapes.at(name).lens() == input.lens(); })) MIGRAPHX_THROW("Input dimension does not match the submodule."); return shape::from_permutation(sm->get_output_shapes().front().type(), sm->get_output_shapes().front().lens(), find_permutation(inputs)); } std::string name() const { return "fused_reduce"; } }; MIGRAPHX_REGISTER_OP(fused_reduce); /* * Predicate matcher checks that input and output shapes have the same rank. This is assumed * for broadcast instructions for these fusions. */ MIGRAPHX_PRED_MATCHER(input_output_ndim_match, instruction_ref ins) { auto input_shape = ins->inputs().front()->get_shape(); auto output_shape = ins->get_shape(); return input_shape.ndim() == output_shape.ndim(); } static auto insert_module_in_submodule(module_ref sm, instruction_ref ins, std::unordered_map* map_ins = nullptr, module::inserter insert = nullptr) { assert(ins->module_inputs().size() == 1); return sm->fuse(*ins->module_inputs().front(), ins->inputs(), map_ins, std::move(insert)); } static void create_reduce_modules(module_pass_manager& mpm) { std::size_t n = 0; for(auto ins : iterator_for(mpm.get_module())) { if(not ins->get_operator().attributes().get("reduce", false)) continue; if(ins->inputs().size() != 1) continue; auto* rm = mpm.create_module(mpm.get_module().name() + ":" + ins->name() + std::to_string(n++)); rm->set_bypass(); rm->add_return(rm->fuse({ins})); auto v = ins->get_operator().to_value(); mpm.get_module().replace_instruction( ins, make_op("fused_reduce", {{"axes", v["axes"]}}), ins->inputs(), {rm}); } } namespace { instruction_ref get_broadcast_output(instruction_ref broadcast) { if(broadcast->outputs().size() != 1) return broadcast; auto output = broadcast->outputs().front(); if(output->name() == "contiguous") return get_broadcast_output(output); return output; } MIGRAPHX_PRED_MATCHER(used_once_except_broadcast, instruction_ref ins) { if(ins->outputs().size() == 1) return true; if(ins->outputs().size() == 2) { auto is_broadcast = [](instruction_ref output) { return contains(output->name(), "broadcast"); }; auto broadcast = std::find_if(ins->outputs().begin(), ins->outputs().end(), is_broadcast); if(broadcast == ins->outputs().end()) return false; auto non_broadcast = std::find_if_not(ins->outputs().begin(), ins->outputs().end(), is_broadcast); if(non_broadcast == ins->outputs().end()) return false; auto output = get_broadcast_output(*broadcast); return output == *non_broadcast; } return false; } } // namespace template static auto match_broadcast(Ms... ms) { return match::skip(match::name("contiguous"))( match::name("multibroadcast")( match::arg(0)(ms...), match::used_once(), input_output_ndim_match()) .bind("broadcast")) .bind("final_broadcast"); } template static auto any_input(Ms... ms) { return match::any_of[match::inputs()](match::any(ms...).bind("input")); } static bool is_valid_broadcast(const instruction_ref b, const std::vector& reduce_axes) { std::vector broadcast_axes; auto bstrides = b->get_shape().strides(); for(size_t i = 0; i < bstrides.size(); ++i) { if(bstrides.at(i) == 0) broadcast_axes.push_back(i); } return broadcast_axes == reduce_axes; } template static auto match_broadcast_axes(M m) { return match::make_basic_fun_matcher( [=](match::matcher_context& ctx, instruction_ref ins) -> optional { optional result = m.match(ctx, ins); if(contains(ctx.instructions, "broadcast")) { instruction_ref reduce; if(ins->get_operator().name() == "fused_reduce") { reduce = ins; } else { assert(contains(ctx.instructions, "reduce")); reduce = ctx.instructions["reduce"]; } auto axes = reduce->get_operator().to_value().at("axes").to_vector(); auto broadcast = ctx.instructions["broadcast"]; if(not is_valid_broadcast(broadcast, axes)) return nullopt; } return result; }); } static auto match_broadcastable_input(const std::string& op, const std::string& name) { auto match_op = match::name(op)(used_once_except_broadcast()).bind(name); auto match_op_input = any_input(match_op, match::used_once()); auto broadcast_match_op_input = any_input(match_broadcast(match_op), match::used_once()); return match::any_of(match_op_input, match_broadcast_axes(broadcast_match_op_input)); } static void finalize_reduce_module(module_ref m) { eliminate_common_subexpression{}.apply(*m); dead_code_elimination{}.apply(*m); } namespace { struct find_pointwise_reduce { auto matcher() const { // fused_reduce instruction with pointwise inputs. return match::name("fused_reduce")(match_broadcastable_input("pointwise", "pointwise")); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto reduce = r.result; auto input = r.instructions["pointwise"]; const auto* pm = input->module_inputs().front(); const auto* old_rm = reduce->module_inputs().front(); auto* rm = mpm.create_module(pm->name() + ":" + old_rm->name()); rm->set_bypass(); std::unordered_map map_ins; // Insert pointwise auto rins = rm->fuse({input}, &map_ins).front(); map_ins[input] = rins; if(contains(r.instructions, "broadcast")) { auto broadcast = r.instructions["broadcast"]; auto fbroadcast = r.instructions["final_broadcast"]; map_ins[broadcast] = rm->fuse({broadcast}, &map_ins).front(); if(fbroadcast != broadcast) map_ins[fbroadcast] = map_ins[broadcast]; } // Insert fused_reduce rm->add_return(insert_module_in_submodule(rm, reduce, &map_ins)); finalize_reduce_module(rm); auto new_inputs = find_inputs(map_ins, &mpm.get_module(), rm); mpm.get_module().replace_instruction(reduce, reduce->get_operator(), new_inputs, {rm}); } }; struct find_reduce_pointwise { auto matcher() const { return match::name("pointwise")(match_broadcastable_input("fused_reduce", "reduce")); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto pw = r.result; auto reduce = r.instructions["reduce"]; auto input = r.instructions["input"]; const auto* pm = pw->module_inputs().front(); const auto* old_rm = reduce->module_inputs().front(); auto* rm = mpm.create_module(old_rm->name() + ":" + pm->name()); rm->set_bypass(); std::unordered_map map_ins; // Copy module instructions insert_module_in_submodule(rm, reduce, &map_ins); if(contains(r.instructions, "broadcast")) { auto broadcast = r.instructions["broadcast"]; map_ins[broadcast->inputs().front()] = rm->get_returns().front(); auto bout = rm->fuse({broadcast}, &map_ins); map_ins[input] = bout.front(); } else { map_ins[input] = rm->get_returns().front(); } auto out = rm->fuse({pw}, &map_ins); rm->replace_return(out); finalize_reduce_module(rm); auto new_inputs = find_inputs(map_ins, &mpm.get_module(), rm); mpm.get_module().replace_instruction(pw, reduce->get_operator(), new_inputs, {rm}); } }; struct find_reduce_reduce { auto matcher() const { return match::name("fused_reduce")(match_broadcastable_input("fused_reduce", "reduce")); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto reduce1 = r.result; auto reduce2 = r.instructions["reduce"]; auto input = r.instructions["input"]; if(reduce1->get_operator() != reduce2->get_operator()) return; const auto* rm1 = reduce1->module_inputs().front(); const auto* rm2 = reduce2->module_inputs().front(); auto* rm = mpm.create_module(rm1->name() + ":" + rm2->name()); rm->set_bypass(); std::unordered_map map_ins; // Copy reduce1 instructions insert_module_in_submodule(rm, reduce2, &map_ins); if(contains(r.instructions, "broadcast")) { auto broadcast = r.instructions["broadcast"]; map_ins[broadcast->inputs().front()] = rm->get_returns().front(); auto bout = rm->fuse({broadcast}, &map_ins); map_ins[input] = bout.front(); } else { map_ins[input] = rm->get_returns().front(); } auto out = insert_module_in_submodule(rm, reduce1, &map_ins); rm->replace_return(out); finalize_reduce_module(rm); auto new_inputs = find_inputs(map_ins, &mpm.get_module(), rm); mpm.get_module().replace_instruction(reduce1, reduce1->get_operator(), new_inputs, {rm}); } }; struct reduce_reshape : rewrite_reshapes_base { static std::string name() { return "fused_reduce"; } template static auto transform_op(Transform t) { return [=](module& m, instruction_ref ins, const operation& op, const std::vector& inputs, const std::vector& mod_args) { auto new_op = t(op); return m.insert_instruction(ins, new_op, inputs, mod_args); }; } template static instruction_ref insert(module_pass_manager& mpm, instruction_ref ins, const std::vector& inputs, const AxesMap& am) { auto op = any_cast(ins->get_operator()); std::vector axes; for(auto axis : op.axes) { auto new_axes = am.at(axis); axes.insert(axes.end(), new_axes.begin(), new_axes.end()); } std::sort(axes.begin(), axes.end()); auto dims = base_dims(inputs); auto* oldm = ins->module_inputs().front(); auto* sm = mpm.create_module(oldm->name() + "_reshape"); sm->set_bypass(); auto outs = sm->fuse(*oldm, inputs, nullptr, transform_op([&](const operation& sop) { if(contains(sop.name(), "reduce")) return make_op(sop.name(), {{"axes", axes}}); if(sop.name() == "multibroadcast") return make_op("multibroadcast", {{"out_lens", dims}}); assert(sop.name() == "pointwise"); return sop; })); sm->add_return(outs); return mpm.get_module().insert_instruction(ins, fused_reduce{axes}, inputs, {sm}); } static std::vector base_dims(const std::vector& inputs) { auto input = std::max_element(inputs.begin(), inputs.end(), by(std::less<>{}, [](auto i) { return i->get_shape().elements(); })); return (*input)->get_shape().lens(); } static std::vector base_dims(instruction_ref ins) { return base_dims(ins->inputs()); } }; } // namespace void fuse_reduce::apply(module_pass_manager& mpm) const { if(enabled(MIGRAPHX_DISABLE_REDUCE_FUSION{})) return; create_reduce_modules(mpm); mpm.run_pass(dead_code_elimination{}); for(int i = 0; i < 4; i++) { if(enable_rewrite_reshapes) mpm.run_pass(rewrite_reshapes{}); match::find_matches( mpm, find_reduce_pointwise{}, find_pointwise_reduce{}, find_reduce_reduce{}); mpm.run_pass(dead_code_elimination{}); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/generate.cpp000066400000000000000000000074501510465702400176330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { argument fill_argument(shape s, double value) { argument result; if(s.type() == shape::tuple_type) { std::vector sub_args; const auto& sub_ss = s.sub_shapes(); std::transform(sub_ss.begin(), sub_ss.end(), std::back_inserter(sub_args), [&](auto ss) { return fill_argument(ss, value); }); result = argument(sub_args); } else { s.visit_type([&](auto as) { using type = typename decltype(as)::type; auto v = fill_tensor_data(s, value); result = {s, v}; }); } return result; } argument generate_argument(shape s, unsigned long seed, random_mode m) { argument result; if(s.type() == shape::tuple_type) { const auto& sub_ss = s.sub_shapes(); std::vector sub_args; std::transform(sub_ss.begin(), sub_ss.end(), std::back_inserter(sub_args), [&](auto ss) { return generate_argument(ss, seed, m); }); result = argument(sub_args); } // special processing for non-computable type else if(not s.computable()) { // NOTE: these values can be wrong (ex. not valid fp4x2) auto v = generate_tensor_data(s, seed, m); result = {s, v}; } else { s.visit_type([&](auto as) { // we use char type to store bool type internally, so bool_type // needs special processing to generate data if(s.type() == shape::bool_type) { auto v = generate_tensor_data(s, seed, m); result = {s, v}; } else { using type = typename decltype(as)::type; auto v = generate_tensor_data(s, seed, m); result = {s, v}; } }); } return result; } literal generate_literal(shape s, unsigned long seed) { literal result; if(not s.computable()) { auto v = generate_tensor_data(s, seed); result = {s, reinterpret_cast(v.get())}; } else { s.visit_type([&](auto as) { using type = typename decltype(as)::type; auto v = generate_tensor_data(s, seed); result = {s, reinterpret_cast(v.get())}; }); } return result; } // TODO: Move to literal.cpp literal abs(literal l) { return transform(std::move(l), [](auto x) { return std::fabs(x); }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/graphviz.cpp000066400000000000000000000152571510465702400176770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace graphviz { static std::string html_bold(const std::string& content) { return "" + content + ""; } static std::string html_table_start(const html_table_style& style) { return R"(<)"; } static std::string html_table_end() { return "
>"; } static std::string html_cell(const std::string& content, const std::string& align = "center") { return "" + content + ""; } static bool is_hex_color(std::string color) { return not color.empty() and color[0] == '#'; } std::string enclose_name(const std::string& name) { return '"' + replace_string(name, "\"", "\\\"") + '"'; } std::string format_shape_name(const migraphx::shape& s, const std::string& linebreak) { if(s.sub_shapes().empty()) { if(s.dynamic()) { return "dynamic" + linebreak + s.type_string() + linebreak + "{" + to_string_range(s.dyn_dims()) + "}"; } return s.type_string() + linebreak + "{" + to_string_range(s.lens()) + "}, {" + to_string_range(s.strides()) + "}"; } return "[" + to_string_range(s.sub_shapes()) + "]"; } std::string build_html_label(const graphviz_node_content& content) { std::ostringstream ss; ss << html_table_start(content.html_style); ss << html_cell(html_bold(content.title)); std::for_each(content.body_lines.begin(), content.body_lines.end(), [&ss](const std::string& line) { ss << html_cell(line); }); ss << html_table_end(); return ss.str(); } std::string build_node_style(const graphviz_node_style& node_style) { std::ostringstream ss; ss << "style=\"" << node_style.style << "\" "; if(is_hex_color(node_style.fillcolor)) ss << "fillcolor=\"" << node_style.fillcolor << "\" "; else ss << "fillcolor=" << node_style.fillcolor << " "; if(is_hex_color(node_style.fontcolor)) ss << "fontcolor=\"" << node_style.fontcolor << "\" "; else ss << "fontcolor=" << node_style.fontcolor << " "; if(node_style.bordercolor.empty() or is_hex_color(node_style.bordercolor)) ss << "color=\"" << node_style.bordercolor << "\" "; else ss << "color=" << node_style.bordercolor << " "; ss << "shape=" << node_style.shape << " "; ss << "fontname=" << node_style.fontname; return ss.str(); } std::string get_graph_color(const instruction_ref& ins) { const auto& op = ins->get_operator(); const auto& attr = op.attributes(); bool context_free = is_context_free(op); bool alias = op.output_alias(to_shapes(ins->inputs())) >= 0; if(ins->can_eval()) { return "#ADD8E6"; // lightblue } else if(attr.contains("pointwise")) { return "#9ACD32"; // yellowgreen } else if(starts_with(op.name(), "reduce")) { return "#90EE90"; // light green } else if(context_free and alias) { return "#98FB98"; // palegreen } else if(context_free and not alias) { return "#FFA500"; // orange } else if(not context_free and alias) { return "#EFBF04"; // gold } else if(attr.contains("fillcolor")) { return attr.at("fillcolor").to(); } else { return "#D3D3D3"; // lightgray } } graphviz_node_content get_node_content(const instruction_ref& ins) { const auto& op = ins->get_operator(); const std::string name = ins->name(); graphviz_node_content content; if(name == "@param") // for params, get typing information { content.title = name; content.body_lines.push_back(graphviz::format_shape_name(ins->get_shape(), "
")); content.html_style = {0, 0, 0, 0}; content.node_style = { "#F0E68C" /* khaki */, "#000000" /* black */, "filled", "rectangle", "Helvectica"}; } else if(name == "@literal") // for literals, just put @literal for name { content.title = name; content.html_style = {0, 0, 0, 0}; content.node_style.style = "filled"; content.node_style.shape = "rectangle"; } else if(name == "gpu::code_object") // use code_object_op::symbol_name for title { content.title = op.to_value()["symbol_name"].to(); content.body_lines.push_back(to_string(op)); content.node_style.fillcolor = "#E9D66B"; // arylideyellow } else { // default case content.title = name; if(std::string op_to_string = to_string(op); name != op_to_string) // stops title == body, don't like doing compare content.body_lines.push_back(op_to_string); const auto& attr = op.attributes(); if(attr.contains("style")) content.node_style.style = attr.at("style").to(); if(attr.contains("fontcolor")) content.node_style.fontcolor = attr.at("fontcolor").to(); } if(content.node_style.fillcolor.empty()) content.node_style.fillcolor = get_graph_color(ins); return content; } } // namespace graphviz } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/include/000077500000000000000000000000001510465702400167525ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/include/migraphx/000077500000000000000000000000001510465702400205715ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/include/migraphx/adjust_allocation.hpp000066400000000000000000000032151510465702400250020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ADJUST_ALLOCATION_HPP #define MIGRAPHX_GUARD_RTGLIB_ADJUST_ALLOCATION_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct MIGRAPHX_EXPORT adjust_allocation { allocation_model model; std::string name() const { return "adjust_allocation"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/algorithm.hpp000066400000000000000000000146201510465702400232730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ALGORITHM_HPP #define MIGRAPHX_GUARD_RTGLIB_ALGORITHM_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template void transform_if(Iterator start, Iterator last, Output out, Predicate pred, F f) { while(start != last) { if(pred(*start)) { *out = f(*start); ++out; } ++start; } } /// Similiar to std::accumulate but a projection can be applied to the elements first template T transform_accumulate(Iterator first, Iterator last, T init, BinaryOp binop, UnaryOp unaryop) { return std::inner_product( first, last, first, std::move(init), binop, [&](auto&& x, auto&&) { return unaryop(x); }); } /// Similiar to std::partial_sum but a projection can be applied to the elements first template OutputIterator transform_partial_sum( Iterator first, Iterator last, OutputIterator d_first, BinaryOperation binop, UnaryOp unaryop) { if(first == last) return d_first; auto acc = unaryop(*first); *d_first = acc; while(++first != last) { acc = binop(std::move(acc), unaryop(*first)); *++d_first = acc; } return ++d_first; } template void group_by(Iterator start, Iterator last, Output out, Predicate pred) { while(start != last) { auto it = std::partition(start, last, [&](auto&& x) { return pred(x, *start); }); out(start, it); start = it; } } template void group_unique(Iterator start, Iterator last, Output out, Predicate pred) { while(start != last) { auto it = std::find_if(start, last, [&](auto&& x) { return not pred(*start, x); }); out(start, it); start = it; } } template void group_find(Iterator start, Iterator last, Predicate pred, Output out) { start = std::find_if(start, last, pred); while(start != last) { auto it = std::find_if_not(start, last, pred); out(start, it); start = std::find_if(it, last, pred); } } /// Similiar to std::remove_if but instead pass adjacent pairs to the predicate template Iterator adjacent_remove_if(Iterator first, Iterator last, Predicate p) { first = std::adjacent_find(first, last, p); if(first == last) return first; auto i = first; while(std::next(++i) != last) { if(not p(*i, *std::next(i))) { *first = std::move(*i); ++first; } } *first = std::move(*i); ++first; return first; } /// Similiar to std::for_each but instead pass adjacent pairs to the function template Iterator adjacent_for_each(Iterator first, Iterator last, F f) { if(first == last) return last; Iterator next = first; ++next; for(; next != last; ++next, ++first) f(*first, *next); return last; } /// Like std::for_each but can pass in another range like std::transform template F for_each(Iterator1 first1, Iterator1 last1, Iterator2 first2, F f) { for(; first1 != last1; ++first1, ++first2) f(*first1, *first2); return f; } template std::ptrdiff_t levenshtein_distance(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) { if(first1 == last1) return std::distance(first2, last2); if(first2 == last2) return std::distance(first1, last1); if(*first1 == *first2) return levenshtein_distance(std::next(first1), last1, std::next(first2), last2); auto x1 = levenshtein_distance(std::next(first1), last1, std::next(first2), last2); auto x2 = levenshtein_distance(first1, last1, std::next(first2), last2); auto x3 = levenshtein_distance(std::next(first1), last1, first2, last2); return std::ptrdiff_t{1} + std::min({x1, x2, x3}); } inline size_t levenshtein_distance(const std::string& s1, const std::string& s2) { const size_t l1 = s1.length(); const size_t l2 = s2.length(); if(l1 < l2) levenshtein_distance(s2, s1); std::vector d(l2 + 1); std::iota(d.begin(), d.end(), 0); for(size_t i = 1; i <= l1; i++) { size_t prev_cost = d[0]; d[0] = i; for(size_t j = 1; j <= l2; j++) { if(s1[i - 1] == s2[j - 1]) { d[j] = prev_cost; } else { size_t cost_insert_or_delete = std::min(d[j - 1], d[j]); size_t cost_substitute = prev_cost; prev_cost = d[j]; d[j] = std::min(cost_substitute, cost_insert_or_delete) + 1; } } } return d[l2]; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/allocation_model.hpp000066400000000000000000000303141510465702400246100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_ALLOCATION_MODEL_HPP #define MIGRAPHX_GUARD_ALLOCATION_MODEL_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// An interface for target-dependent allocation struct allocation_model { /// A name of the target-dependent allocate operator std::string name() const; /// A name of the target-dependent copy operator std::string copy() const; /// Create an allocation operator for the given shape operation allocate(const shape& s) const; /// Create a preallocated operator for the given shape operation preallocate(const shape& s, const std::string& id) const; /// Check if outputs are to be inserted bool needs_out_params() const; }; #else #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT allocation_model { // std::string name() const; // std::string copy() const; // operation allocate(const shape& s) const; // operation preallocate(const shape& s, std::string id) const; // bool needs_out_params() const; }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct allocation_model { private: template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(std::declval().name(), std::declval().copy(), std::declval().allocate(std::declval()), std::declval().preallocate(std::declval(), std::declval()), std::declval().needs_out_params(), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors allocation_model() = default; template < typename PrivateDetailTypeErasedT, typename = private_te_constraints, typename = typename std::enable_if< not std::is_same, allocation_model>{}>::type> allocation_model(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template < typename PrivateDetailTypeErasedT, typename = private_te_constraints, typename = typename std::enable_if< not std::is_same, allocation_model>{}>::type> allocation_model& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { allocation_model rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } std::string name() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().name(); } std::string copy() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().copy(); } operation allocate(const shape& s) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().allocate(s); } operation preallocate(const shape& s, std::string id) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().preallocate(s, std::move(id)); } bool needs_out_params() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().needs_out_params(); } friend bool is_shared(const allocation_model& private_detail_x, const allocation_model& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual std::string name() const = 0; virtual std::string copy() const = 0; virtual operation allocate(const shape& s) const = 0; virtual operation preallocate(const shape& s, std::string id) const = 0; virtual bool needs_out_params() const = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } std::string name() const override { return private_detail_te_value.name(); } std::string copy() const override { return private_detail_te_value.copy(); } operation allocate(const shape& s) const override { return private_detail_te_value.allocate(s); } operation preallocate(const shape& s, std::string id) const override { return private_detail_te_value.preallocate(s, std::move(id)); } bool needs_out_params() const override { return private_detail_te_value.needs_out_params(); } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const allocation_model* x) { return x->any_cast(); } template inline ValueType* any_cast(allocation_model* x) { return x->any_cast(); } template inline ValueType& any_cast(allocation_model& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const allocation_model& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/analyze_streams.hpp000066400000000000000000000033641510465702400245110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ANALYZE_STREAMS_HPP #define MIGRAPHX_GUARD_RTGLIB_ANALYZE_STREAMS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct stream_race { instruction_ref ins; // The instruction that should before instruction_ref before; }; MIGRAPHX_EXPORT std::vector analyze_streams(const module& m, const stream_model& strmm); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/any_ptr.hpp000066400000000000000000000055661510465702400227720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_ANY_PTR_HPP #define MIGRAPHX_GUARD_MIGRAPHX_ANY_PTR_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct any_ptr { any_ptr() = default; template any_ptr(T* p) : ptr(p), ti(typeid(T*)), name(get_name()) { } any_ptr(void* p, std::string_view pname) : ptr(p), name(pname) {} void* get(std::string_view n) const { if(name != n) MIGRAPHX_THROW("any_ptr: type mismatch: " + std::string{name} + " != " + std::string{n}); return ptr; } template T get() const { static_assert(std::is_pointer{}, "Must be a pointer"); assert(not ti or ptr != nullptr); if(ti and std::type_index{typeid(T)} != *ti) MIGRAPHX_THROW("any_ptr: type mismatch: " + std::string{name} + " != " + get_name()); else if(name != get_name()) MIGRAPHX_THROW("any_ptr: type mismatch: " + std::string{name} + " != " + get_name()); return reinterpret_cast(ptr); } void* unsafe_get() const { return ptr; } private: void* ptr = nullptr; optional ti = nullopt; std::string_view name = ""; template static const std::string& get_name() { return get_type_name>>(); } }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_ANY_PTR_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/apply_alpha_beta.hpp000066400000000000000000000055441510465702400245770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_APPLY_ALPHA_BETA_HPP #define MIGRAPHX_GUARD_MIGRAPHX_APPLY_ALPHA_BETA_HPP #include "migraphx/make_op.hpp" #include "migraphx/normalize_attributes.hpp" #include "migraphx/operation.hpp" #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT instruction_ref insert_apply_alpha_beta(module& m, instruction_ref pos, const std::vector& args, const operation& op, const literal& alpha, const literal& beta); template instruction_ref insert_apply_alpha_beta(module& m, instruction_ref pos, const std::vector& args, const operation& op, T alpha = 1, T beta = 0) { return insert_apply_alpha_beta(m, pos, args, op, literal{T{alpha}}, literal{T{beta}}); } template instruction_ref add_apply_alpha_beta(module& m, const std::vector& args, const operation& op, T alpha = 1, T beta = 0) { return insert_apply_alpha_beta(m, m.end(), args, op, alpha, beta); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_APPLY_ALPHA_BETA_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/argument.hpp000066400000000000000000000107231510465702400231270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_ARGUMENT_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_ARGUMENT_HPP #include #include #include #include #include #include // clang-format off namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * @brief Arguments passed to instructions * * An `argument` can represent a raw buffer of data that either be referenced from another element * or it can be owned by the argument. * */ struct MIGRAPHX_EXPORT argument : raw_data { argument() = default; explicit argument(const shape& s); template ()())>{})> argument(shape s, F d) : m_shape(std::move(s)) { assign_buffer([f = std::move(d)]() mutable { return reinterpret_cast(f()); }); } template argument(shape s, T* d) : m_shape(std::move(s)) { assign_buffer([d] { return reinterpret_cast(d); }); } template argument(shape s, const std::shared_ptr& d) : m_shape(std::move(s)) { assign_buffer([d] { return reinterpret_cast(d.get()); }); } argument(shape s, std::nullptr_t); argument(const std::vector& args); /// Provides a raw pointer to the data char* data() const; /// Whether data is available bool empty() const; const shape& get_shape() const; argument reshape(const shape& s) const; argument copy() const; /// Make copy of the argument that is always sharing the data argument share() const; std::vector get_sub_objects() const; /// Return the ith element argument element(std::size_t i) const; // Keeps the same data ordering as the given container template void fill(Iterator start, Iterator end) { assert(std::distance(start, end) <= m_shape.elements()); this->visit([&](auto output) { std::copy(start, end, output.begin()); }); } argument convert(shape::type_t t) const; private: void assign_buffer(std::function d); struct data_t { std::function get = nullptr; std::vector sub = {}; data_t share() const; static data_t from_args(const std::vector& args); }; argument(const shape& s, const data_t& d); shape m_shape; data_t m_data{}; }; MIGRAPHX_EXPORT std::vector flatten(const std::vector& args); MIGRAPHX_EXPORT std::vector to_shapes(const std::vector& args); MIGRAPHX_EXPORT void migraphx_to_value(value& v, const argument& a); MIGRAPHX_EXPORT void migraphx_from_value(const value& v, argument& a); MIGRAPHX_EXPORT void save_argument(const argument& a, const std::string& filename); MIGRAPHX_EXPORT argument load_argument(const std::string& filename); // Visit-like function but just converts argument to double template auto get_all(Ts&&... xs) { return [&](auto v) { [&](auto&&... xs) { v(xs.template get()...); }(xs.convert(shape::get_type{})...); }; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx // clang-format on #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/array.hpp000066400000000000000000000055741510465702400224330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ARRAY_HPP #define MIGRAPHX_GUARD_RTGLIB_ARRAY_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace detail { template struct array_type { using type = R; }; template struct array_type : std::common_type { }; template using array_type_t = typename array_type::type; template constexpr std::array, N> to_array_impl(T (&a)[N], seq) { return {{a[I]...}}; } } // namespace detail template 0))> constexpr std::array, sizeof...(Ts)> make_array(Ts&&... xs) { return {static_cast>(std::forward(xs))...}; } constexpr std::array make_array() { return {}; } template constexpr auto to_array(T (&a)[N]) { return detail::to_array_impl(a, detail::gens{}); } namespace detail { template constexpr auto rearray_impl(Array a, seq) { return make_array(a[I + Offset]...); } } // namespace detail template constexpr auto pop_front(std::array a) { return detail::rearray_impl(a, detail::gens{}); } template constexpr auto pop_back(std::array a) { return detail::rearray_impl<1>(a, detail::gens{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/as_number.hpp000066400000000000000000000032431510465702400232570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_AS_NUMBER_HPP #define MIGRAPHX_GUARD_RTGLIB_AS_NUMBER_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template T as_number(const T& x) { return x; } inline int32_t as_number(int8_t x) { return static_cast(x); } inline uint32_t as_number(uint8_t x) { return static_cast(x); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_RTGLIB_AS_NUMBER_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/assert.hpp000066400000000000000000000037621510465702400226130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_ASSERT_HPP #define MIGRAPHX_GUARD_MIGRAPHX_ASSERT_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template auto abort_on_throw(F f) -> decltype(f()) { try { return f(); } catch(const std::exception& e) { std::cerr << e.what() << std::endl; std::abort(); } catch(...) { std::cerr << "Unknown exception" << std::endl; std::abort(); } } #ifdef NDEBUG #define MIGRAPHX_ASSERT_NO_THROW(...) __VA_ARGS__ #else #define MIGRAPHX_ASSERT_NO_THROW(...) \ migraphx::abort_on_throw([&]() -> decltype(__VA_ARGS__) { return __VA_ARGS__; }) #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_ASSERT_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/assignment_options.hpp000066400000000000000000000030721510465702400252270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ASSIGNMENT_OPTIONS_HPP #define MIGRAPHX_GUARD_RTGLIB_ASSIGNMENT_OPTIONS_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct assignment_options { support_metric metric = support_metric::latency; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_RTGLIB_ASSIGNMENT_OPTIONS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/auto_any_cast.hpp000066400000000000000000000035231510465702400241360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_AUTO_ANY_CAST_HPP #define MIGRAPHX_GUARD_RTGLIB_AUTO_ANY_CAST_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // Forward declare any_cast template const T& any_cast(const T&); namespace detail { template void any_cast() { } template struct auto_any_caster { T& x; // NOLINT template operator U&() { return any_cast(x); } operator T&() { return x; } }; } // namespace detail template detail::auto_any_caster auto_any_cast(T& x) { return {x}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/auto_contiguous.hpp000066400000000000000000000031701510465702400245320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_AUTO_CONTIGOUS_HPP #define MIGRAPHX_GUARD_RTGLIB_AUTO_CONTIGOUS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct MIGRAPHX_EXPORT auto_contiguous { std::string name() const { return "auto_contiguous"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/auto_register.hpp000066400000000000000000000052721510465702400241640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_AUTO_REGISTER_HPP #define MIGRAPHX_GUARD_RTGLIB_AUTO_REGISTER_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template int auto_register_action() { Action::template apply(); return 0; } template struct auto_register { const static int static_register; // This typedef ensures that the static member will be instantiated if // the class itself is instantiated using static_register_type = std::integral_constant; }; #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wglobal-constructors" #endif template const int auto_register::static_register = auto_register_action(); // NOLINT #ifdef __clang__ #pragma clang diagnostic pop #endif #define MIGRAPHX_AUTO_REGISTER_NAME_DETAIL(x) migraphx_auto_register_##x #define MIGRAPHX_AUTO_REGISTER_NAME(x) MIGRAPHX_AUTO_REGISTER_NAME_DETAIL(x) // NOLINTNEXTLINE #define MIGRAPHX_AUTO_REGISTER(...) \ [[maybe_unused]] static void MIGRAPHX_AUTO_REGISTER_NAME(__LINE__)( \ migraphx::auto_register<__VA_ARGS__> x [[maybe_unused]] = \ migraphx::auto_register<__VA_ARGS__>{}) \ { \ } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/autocast_fp8.hpp000066400000000000000000000034671510465702400237140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_AUTOCAST_FP8_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_AUTOCAST_FP8_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct program; struct module; /** This pass will convert model with fp8 input parameter to model with fp32 input parameter and internally add casts to fp8 for those converted params.*/ struct MIGRAPHX_EXPORT autocast_fp8_pass { shape::type_t target_type = migraphx::shape::float_type; std::string name() const { return "autocast_fp8_pass"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/base64.hpp000066400000000000000000000030071510465702400223660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_BASE64_HPP #define MIGRAPHX_GUARD_RTGLIB_BASE64_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /// encode string to base64 std::string MIGRAPHX_EXPORT base64_encode(const std::string& str); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/bf16.hpp000066400000000000000000000027451510465702400220500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_BF16_HPP #define MIGRAPHX_GUARD_RTGLIB_BF16_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { using bf16 = migraphx::generic_float<7, 8>; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/bit.hpp000066400000000000000000000042331510465702400220620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_BIT_HPP #define MIGRAPHX_GUARD_MIGRAPHX_BIT_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template constexpr unsigned int all_ones() noexcept { return (1u << N) - 1u; } template constexpr int countl_zero(T value) { unsigned int r = 0; for(; value != 0u; value >>= 1u) r++; return 8 * sizeof(value) - r; } constexpr std::uint64_t bit_ceil(std::uint64_t x) noexcept { if(x <= 1) return 1; --x; x |= x >> 1u; x |= x >> 2u; x |= x >> 4u; x |= x >> 8u; x |= x >> 16u; x |= x >> 32u; return x + 1; } constexpr std::uint32_t bit_ceil(std::uint32_t x) noexcept { if(x <= 1) return 1; --x; x |= x >> 1u; x |= x >> 2u; x |= x >> 4u; x |= x >> 8u; x |= x >> 16u; return x + 1; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_BIT_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/bit_cast.hpp000066400000000000000000000043301510465702400230720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_BITCAST_HPP #define MIGRAPHX_GUARD_RTGLIB_BITCAST_HPP #include #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstrict-aliasing" #pragma GCC diagnostic ignored "-Wduplicated-branches" #endif #include #include // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define MIGRAPHX_CONST_FOLD(x) (__builtin_constant_p(x) ? (x) : (x)) namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template {} and std::is_trivially_copyable{})> constexpr To bit_cast(From fr) noexcept { // NOLINTNEXTLINE(bugprone-sizeof-expression) static_assert(sizeof(To) == sizeof(From)); return __builtin_bit_cast(To, fr); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif #endif // MIGRAPHX_GUARD_RTGLIB_BITCAST_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/bit_signal.hpp000066400000000000000000000046471510465702400234300ustar00rootroot00000000000000#ifndef MIGRAPHX_GUARD_MIGRAPHX_BIT_SIGNAL_HPP #define MIGRAPHX_GUARD_MIGRAPHX_BIT_SIGNAL_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /// Observer pattern for keeping track of if something has changed or been /// updated. Can have up to `N` different subscribers. Use by creating a /// `bit_signal` and adding subscribers with `bit_signal.subscribe()`. Use /// `bit_signal.notify()` to set that subscribers should be notified. Get the /// status of the subscription by checking the `slot` returned by /// `bit_signal.subscribe()`. template struct bit_signal { std::bitset slots; std::bitset allocated; struct slot { bit_signal* handler = nullptr; std::size_t i = N; slot() = default; slot(bit_signal* h, std::size_t x) : handler(h), i(x) {} slot(slot&& rhs) noexcept : handler(rhs.handler), i(rhs.i) { rhs.handler = nullptr; rhs.i = N; } slot(const slot& rhs) : handler(rhs.handler), i(rhs.handler->allocate()) {} slot& operator=(slot rhs) { std::swap(handler, rhs.handler); std::swap(i, rhs.i); return *this; } ~slot() noexcept { if(valid()) handler->deallocate(i); } bool valid() const { return i < N and handler != nullptr; } bool triggered() const { assert(valid()); return handler->triggered(i); } operator bool() const { return triggered(); } }; slot subscribe() { return {this, allocate()}; } std::size_t allocate() { auto i = *find_if(range(N), [&](auto x) { return not allocated[x]; }); if(i == N) MIGRAPHX_THROW("Too many signals allocated"); slots[i] = false; allocated[i] = true; return i; } void deallocate(std::size_t i) { allocated[i] = false; } void notify() { slots.set(); } bool triggered(std::size_t i) const { return slots[i]; } void clear() { slots.reset(); allocated.reset(); } std::size_t nslots() const { return allocated.count(); } }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_BIT_SIGNAL_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/builtin.hpp000066400000000000000000000064251510465702400227570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_BUILTIN_HPP #define MIGRAPHX_GUARD_BUILTIN_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace builtin { struct literal { std::string name() const { return "@literal"; } shape compute_shape(const std::vector&) const { MIGRAPHX_THROW("builtin"); } argument compute(context&, const shape&, const std::vector&) const { MIGRAPHX_THROW("builtin"); } }; struct outline { shape s; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape")); } std::string name() const { return "@outline"; } shape compute_shape(const std::vector&) const { return s; } argument compute(context&, const shape&, const std::vector&) const { MIGRAPHX_THROW("builtin"); } }; struct param { std::string parameter; uint32_t order = 0; template static auto reflect(Self& self, F f) { return pack(f(self.parameter, "parameter")); } std::string name() const { return "@param"; } shape compute_shape(const std::vector&) const { MIGRAPHX_THROW("builtin"); } argument compute(context&, const shape&, const std::vector&) const { MIGRAPHX_THROW("builtin"); } friend std::ostream& operator<<(std::ostream& os, const param& op) { os << op.name() << ":" << op.parameter; return os; } }; struct returns { std::string name() const { return "@return"; } shape compute_shape(const std::vector& arg) const { if(arg.empty()) return {}; else if(arg.size() == 1) return arg[0]; else return shape(arg); } argument compute(context&, const shape&, const std::vector&) const { MIGRAPHX_THROW("builtin"); } }; } // namespace builtin } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/byte.hpp000066400000000000000000000072761510465702400222610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_BYTE_HPP #define MIGRAPHX_GUARD_BYTE_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * Implementation of std::byte for MIGraphX. * Created to have a custom stream operator so that it prints as an unsigned int. * This type is essentially a limited unsigned_char to prevent things like trying to add two bytes. */ enum class byte : unsigned char { }; template {} and std::is_unsigned{})> constexpr byte operator<<(byte b, IntType shift) noexcept { return static_cast(static_cast(b) << shift); }; template {} and std::is_unsigned{})> constexpr byte operator>>(byte b, IntType shift) noexcept { return static_cast(static_cast(b) >> shift); }; template {} and std::is_unsigned{})> constexpr byte& operator>>=(byte& b, IntType shift) noexcept { b = b >> shift; return b; }; template {} and std::is_unsigned{})> constexpr byte& operator<<=(byte& b, IntType shift) noexcept { b = b << shift; return b; }; constexpr byte operator|(byte l, byte r) noexcept { return static_cast(static_cast(l) | static_cast(r)); } constexpr byte& operator|=(byte& l, byte r) noexcept { l = l | r; return l; } constexpr byte operator&(byte l, byte r) noexcept { return static_cast(static_cast(l) & static_cast(r)); } constexpr byte& operator&=(byte& l, byte r) noexcept { l = l & r; return l; } constexpr byte operator^(byte l, byte r) noexcept { return static_cast(static_cast(l) ^ static_cast(r)); } constexpr byte& operator^=(byte& l, byte r) noexcept { l = l ^ r; return l; } constexpr byte operator~(byte b) noexcept { return static_cast(~static_cast(b)); } template {} and std::is_unsigned{})> constexpr IntType to_integer(byte b) noexcept { return static_cast(b); } template Stream& operator<<(Stream& os, const byte& b) { os << static_cast(b); return os; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/check_context.hpp000066400000000000000000000064221510465702400241270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CHECK_CONTEXT_HPP #define MIGRAPHX_GUARD_RTGLIB_CHECK_CONTEXT_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct check_context { struct op : auto_register_op { static std::string compute_op_name() { const auto& op_type_name = get_type_name(); const auto& split_name = split_string(op_type_name, ':'); std::vector name_without_version = {"check_context"}; // op_type_name would contain internal namespace name with version_x_y_z // remove version and construct op_name such as check_context::migraphx::gpu::context std::copy_if( split_name.begin(), split_name.end(), std::back_inserter(name_without_version), [&](const auto& i) { return not i.empty() and not contains(i, "version"); }); return join_strings(name_without_version, "::"); } std::string name() const { static auto op_name = compute_op_name(); return op_name; } shape compute_shape(const std::vector&) const { return {}; } argument compute(context& ctx, const shape&, const std::vector&) const { this->check(ctx); return {}; } void finalize(context& ctx, const shape&, const std::vector&) const { this->check(ctx); } void check(context& ctx) const { T* x = any_cast(&ctx); if(x == nullptr) MIGRAPHX_THROW(std::string("Unexpected context type: ") + ctx.type_id().name()); } }; std::string name() const { return "check_context"; } void apply(module& m) const { m.insert_instruction(m.begin(), op{}); } }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/check_shapes.hpp000066400000000000000000000320311510465702400237210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CHECK_SHAPES_HPP #define MIGRAPHX_GUARD_RTGLIB_CHECK_SHAPES_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // Check that deduced type is incrementable, dereferencable, and comparable template struct is_iterator { }; template struct is_iterator()), decltype(*std::declval()), decltype(std::declval() == std::declval())>> : std::true_type { }; template struct check_shapes { static_assert(is_iterator{}, "CHECK_SHAPES: Deduced type must be an iterator"); Iterator begin; Iterator end; std::string name; bool dynamic_allowed; check_shapes(Iterator b, Iterator e, const std::string& n, const bool d = false) : begin(b), end(e), name(n), dynamic_allowed(d) { check_dynamic(); } template check_shapes(Iterator b, Iterator e, const Op& op, const bool d = false) : begin(b), end(e), name(op.name()), dynamic_allowed(d) { check_dynamic(); } template {})> check_shapes(const std::vector& s, const Op& op, const bool d = false) : begin(s.begin()), end(s.end()), name(op.name()), dynamic_allowed(d) { check_dynamic(); } check_shapes(const std::vector& s, const std::string& n, const bool d = false) : begin(s.begin()), end(s.end()), name(n), dynamic_allowed(d) { check_dynamic(); } void check_dynamic() const { if(not dynamic_allowed and this->any_of([&](const shape& s) { return s.dynamic(); })) { MIGRAPHX_THROW(prefix() + "Dynamic shapes not supported"); } } std::string prefix() const { if(name.empty()) return ""; else return name + ": "; } std::size_t size() const { if(begin == end) return 0; return end - begin; } /*! * Require the number of shape objects to equal to one of the * given sizes. * \param ns template parameter pack of sizes to check against */ template const check_shapes& has(Ts... ns) const { if(migraphx::none_of({ns...}, [&](auto i) { return this->size() == i; })) MIGRAPHX_THROW(prefix() + "Wrong number of arguments: expected " + to_string_range({ns...}) + " but given " + std::to_string(size())); return *this; } /*! * Require the number of shape objects to equal at least a given amount. Use this * method for ops that can take any number (variadic) of inputs. * \param n min. number of shapes */ const check_shapes& has_at_least(std::size_t n) const { if(this->size() < n) MIGRAPHX_THROW(prefix() + "Wrong number of arguments: expected at least " + to_string(n) + " but given " + std::to_string(size())); return *this; } /*! * Require all shapes to have the same number of elements. * \param n number of */ const check_shapes& nelements(std::size_t n) const { if(not this->all_of([&](const shape& s) { return s.elements() == n; })) MIGRAPHX_THROW(prefix() + "Shapes must have only " + std::to_string(n) + " elements"); return *this; } /*! * Check that the first shape has exactly n dimensions. * Do nothing if the container is empty. * \param n number of dimensions */ const check_shapes& only_dims(std::size_t n) const { if(begin != end) { if(begin->ndim() != n) MIGRAPHX_THROW(prefix() + "Only " + std::to_string(n) + "d supported"); } return *this; } /*! * Check that the first shape has a maximum of n dimensions. * Do nothing if the container is empty. * \param n number of dimensions */ const check_shapes& max_ndims(std::size_t n) const { if(begin != end) { if(begin->ndim() > n) MIGRAPHX_THROW(prefix() + "Shape must have at most " + std::to_string(n) + " dimensions"); } return *this; } /*! * Check that the first shape has a minimum of n dimensions. * Do nothing if the container is empty. * \param n number of dimensions */ const check_shapes& min_ndims(std::size_t n) const { if(begin != end) { if(begin->ndim() < n) MIGRAPHX_THROW(prefix() + "Shape must have at least " + std::to_string(n) + " dimensions"); } return *this; } /*! * Check all shapes have the same shape. */ const check_shapes& same_shape() const { if(not this->same([](const shape& s) { return s; })) MIGRAPHX_THROW(prefix() + "Shapes do not match"); return *this; } /*! * Check all shapes have the same type. */ const check_shapes& same_type() const { if(not this->same([](const shape& s) { return s.type(); })) MIGRAPHX_THROW(prefix() + "Types do not match"); return *this; } /*! * Check all shapes have the same lens. */ const check_shapes& same_dims() const { if(not this->same([](const shape& s) { return s.max_lens(); })) MIGRAPHX_THROW(prefix() + "Dimensions do not match"); if(this->any_of([&](const shape& s) { return s.dynamic(); })) if(not this->same([](const shape& s) { return s.min_lens(); })) MIGRAPHX_THROW(prefix() + "Min dynamic dimensions do not match"); return *this; } /*! * Check all shapes have the same number of dimensions. */ const check_shapes& same_ndims() const { if(not this->same([](const shape& s) { return s.ndim(); })) MIGRAPHX_THROW(prefix() + "Number of dimensions do not match"); return *this; } /*! * Check all shapes have the same layout. */ const check_shapes& same_layout() const { if(not this->same([](const shape& s) { return find_permutation(s); })) MIGRAPHX_THROW(prefix() + "Layouts do not match"); return *this; } /*! * Check all shapes are standard. */ const check_shapes& standard() const { if(not this->all_of([](const shape& s) { return s.standard(); })) MIGRAPHX_THROW(prefix() + "Shapes are not in standard layout"); return *this; } /*! * Check all shapes are scalar. */ const check_shapes& scalar() const { if(not this->all_of([](const shape& s) { return s.scalar(); })) MIGRAPHX_THROW(prefix() + "Shapes are not a scalar"); return *this; } /*! * Check all shapes are standard or scalar. */ const check_shapes& standard_or_scalar() const { if(not this->all_of([](const shape& s) { return s.standard() or s.scalar(); })) MIGRAPHX_THROW(prefix() + "Shapes are not a scalar or in standard layout"); return *this; } /*! * Check all shapes are packed. */ const check_shapes& packed() const { if(not this->all_of([](const shape& s) { return s.packed(); })) MIGRAPHX_THROW(prefix() + "Shapes are not packed"); return *this; } /*! * Check all shapes are packed with certain layouts */ const check_shapes& packed_layouts(const std::initializer_list>& layouts) const { if(not this->all_of([&](const shape& s) { return s.packed() and contains(layouts, find_permutation(s)); })) MIGRAPHX_THROW(prefix() + "Shapes are not packed with correct layout"); return *this; } /*! * Check all shapes are packed or broadcasted. */ const check_shapes& packed_or_broadcasted() const { if(not this->all_of([](const shape& s) { return s.packed() or s.broadcasted(); })) MIGRAPHX_THROW(prefix() + "Shapes are not packed nor broadcasted"); return *this; } /*! * Check all shapes are tuples. */ const check_shapes& tuple_type() const { if(not this->all_of([](const shape& s) { return s.type() == shape::tuple_type; })) MIGRAPHX_THROW(prefix() + "Shapes are not tuple!"); return *this; } /*! * Check all shapes are not transposed. */ const check_shapes& not_transposed() const { if(not this->all_of([](const shape& s) { return not s.transposed(); })) MIGRAPHX_THROW(prefix() + "Shapes are transposed"); return *this; } /*! * Check all shapes are not broadcasted. */ const check_shapes& not_broadcasted() const { if(not this->all_of([](const shape& s) { return s.standard() or not s.broadcasted(); })) MIGRAPHX_THROW(prefix() + "Shapes are broadcasted"); return *this; } /*! * Check all shapes have the same n elements. * \param n number of elements */ const check_shapes& elements(std::size_t n) const { if(not this->all_of([&](const shape& s) { return s.elements() == n; })) MIGRAPHX_THROW(prefix() + "Wrong number of elements"); return *this; } /*! * Check the batches of all the shapes do not have transposed strides. */ const check_shapes& batch_not_transposed() const { if(not this->all_of( [&](const shape& s) { return batch_not_transposed_strides(s.strides()); })) MIGRAPHX_THROW(prefix() + "Batch size is transposed"); return *this; } template bool same(F f) const { if(begin == end) return true; auto&& key = f(*begin); return this->all_of([&](const shape& s) { return f(s) == key; }); } template bool all_of(Predicate p) const { if(begin == end) return true; return std::all_of(begin, end, p); } template bool any_of(Predicate p) const { if(begin == end) return false; return std::any_of(begin, end, p); } Iterator get(long i) const { if(i >= size()) MIGRAPHX_THROW(prefix() + "Accessing shape out of bounds"); if(i < 0) return end - i; return begin + i; } check_shapes slice(long start) const { return {get(start), end, name}; } check_shapes slice(long start, long last) const { return {get(start), get(last), name}; } private: static bool batch_not_transposed_strides(const std::vector& strides) { if(strides.size() <= 2) return true; auto dim_0 = strides.size() - 2; auto matrix_size = std::max(strides[dim_0], strides[dim_0 + 1]); std::vector batch(strides.begin(), strides.begin() + dim_0); if(std::all_of(batch.begin(), batch.end(), [&](auto i) { return (i < matrix_size); })) { return false; } if(std::adjacent_find(batch.begin(), batch.end(), [&](auto i, auto j) { return (i < j or i < matrix_size or j < matrix_size); }) != batch.end()) { return false; } return true; } }; // Deduction guide for std::vector constructor template check_shapes(const std::vector&, const Op&, bool d = false) -> check_shapes::const_iterator>; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/clamp.hpp000066400000000000000000000035701510465702400224030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CLAMP_HPP #define MIGRAPHX_GUARD_RTGLIB_CLAMP_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template U pad_clamp(T x) { if(float_equal(x, std::numeric_limits::lowest())) return std::numeric_limits::lowest(); if(float_equal(x, std::numeric_limits::max())) return std::numeric_limits::max(); return (x < std::numeric_limits::lowest()) ? std::numeric_limits::lowest() : (std::numeric_limits::max() < x) ? std::numeric_limits::max() : U(x); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/cloneable.hpp000066400000000000000000000045161510465702400232340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CLONEABLE_HPP #define MIGRAPHX_GUARD_RTGLIB_CLONEABLE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct cloneable { friend Base; virtual std::shared_ptr clone() = 0; template struct derive : Base { friend Derived; std::shared_ptr clone() override { return std::make_shared(static_cast(*this)); } template derive(Args&&... args) : Base(std::forward(args)...) { } }; struct share : Base, std::enable_shared_from_this { std::shared_ptr clone() override { return this->shared_from_this(); } template share(Args&&... args) : Base(std::forward(args)...) { } }; cloneable() = default; cloneable(const cloneable&) = default; cloneable& operator=(const cloneable&) = default; virtual ~cloneable() {} }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/common.hpp000066400000000000000000000142721510465702400226000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_COMMON_HPP #define MIGRAPHX_GUARD_MIGRAPHX_COMMON_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct operation; struct common_options { bool common_type = true; bool common_lens = true; }; /** * Broadcasting works by comparing the shapes element-wise starting with * the trailing (right-most) dimensions and working leftwards. This is equivalent * to what is done in NumPy. * example 1: * s0 = (3,2,4,5) and s1 = (2,1,1) * In this case we need to broadcast (:,1,1) portion of * s1 plus broadcast the 1st dimension of s0 * giving output_lens = (3,2,4,5) * * example 2: * s0 = (3,2,1,5) and s1 = (2,7,5) * In this case we need to broadcast the (:,:,1:,:) axis * of s0 plus the 1st dimension of s1 giving * output_lens = (3,2,7,5) * * example 3: * s0 = (4, 1, 1) and s1 = (3, 4) * output_lens = (4, 3, 4) */ MIGRAPHX_EXPORT std::vector compute_broadcasted_lens(std::vector s0, std::vector s1); /** * Broadcasting for two vectors of dynamic_dimensions. * Compares `dynamic_dimension` objects from the trailing (right-most) dimension and working * leftwards. * * Rules for broadcasting dynamic_dimension: * If the same `dynamic_dimension`, return either. * If one of the `dynamic_dimension`s is 1, return the other one. * If the `dynamic_dimension`s have an intersection, return the intersection. * Explanation: * For the shape to be broadcastable at runtime (when the dimensions are constant) the dimensions * must be the same. The only way for the dimensions to be the same is if the output dimension is * the intersection of the ranges. * In practice, we will mostly see this case for handling unknown dynamic_dimensions like {0, * max_int}. Else, throw an error. * * There is a contrived edge case for ranges that include 1 but are not a fixed {1, 1}. * That case is not supported. */ MIGRAPHX_EXPORT std::vector compute_broadcasted_dyn_dims(std::vector dds0, std::vector dds1); MIGRAPHX_EXPORT std::vector compute_broadcasted_dyn_dims(shape s0, shape s1); MIGRAPHX_EXPORT shape common_shape(const std::vector& shapes); /** * @brief Compute the common (broadcasted) dimensions of a list of fixed shapes */ MIGRAPHX_EXPORT std::vector compute_common_lens(const std::vector& shapes); /** * @ brief Compute the common (broadcasted) dynamic dimensions of a list of dynamic shapes */ MIGRAPHX_EXPORT std::vector compute_common_dyn_dims(const std::vector& shapes); /** * @brief Creates and adds instructions to convert input arguments to common shapes and types * by adding multi-broadcast and type convert operations. This is a utility function for creating * operations where the shape and type of inputs need to match. It supports both dynamic and * static-shaped arguments. * * @param m containing module for instruction * @param ins insertion location in instruction list * @param inputs instructions to use as argument list; also, the shapes * attached to each instruction_ref are considered for broadcasting * @return std::vector a modified argument list */ MIGRAPHX_EXPORT std::vector insert_common_args(module& m, instruction_ref ins, std::vector inputs, common_options options = {}); MIGRAPHX_EXPORT std::vector add_common_args(module& m, std::vector inputs, common_options options = {}); MIGRAPHX_EXPORT instruction_ref insert_common_op(module& m, instruction_ref ins, const operation& op, std::vector inputs, common_options options = {}); /** * @brief Wrapper for insert_common_args() which inserts operation at the end of the module. */ MIGRAPHX_EXPORT instruction_ref add_common_op(module& m, const operation& op, std::vector inputs, common_options options = {}); /** * Calculates the broadcasted shape with the given input_shape and broadcasted dimensions. * * @param input_shape static shape to broadcast * @param bcast_lens dimensions to broadcast to * @return broadcasted shape with calculated strides */ MIGRAPHX_EXPORT shape make_bcast_shape(const shape& input_shape, const std::vector& bcast_lens); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_COMMON_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/common_dims.hpp000066400000000000000000000052041510465702400236070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_COMMON_DIMS_HPP #define MIGRAPHX_GUARD_MIGRAPHX_COMMON_DIMS_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /// This will compute a higher dimensional space that will preserve the axes /// for both sets of dimensions. Two axes_maps are provided for each of the /// dims that will map the axis to the axes that are used by the result of /// common_dims. struct MIGRAPHX_EXPORT common_dims { static common_dims compute(const std::vector& dims1, const std::vector& dims2); /// Map the dimensions into the common higher dimensional space. The /// dimension doesnt need to have the same number of elements as the /// common dimension. std::vector get_dimensions_for(const std::vector& idims) const; /// Get the corresponding axes map based on the rank of tensor const std::vector>* get_axes_map(std::size_t n) const; std::vector dims; std::vector> axes_map1; std::vector> axes_map2; }; template auto elements(const Range& r) { return std::accumulate(r.begin(), r.end(), std::size_t{1}, std::multiplies<>{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_COMMON_DIMS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/compile_options.hpp000066400000000000000000000033731510465702400245130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_COMPILE_OPTIONS_HPP #define MIGRAPHX_GUARD_RTGLIB_COMPILE_OPTIONS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct compile_options { /** * Have MIGX allocate memory for parameters and add instructions * to copy parameters and output to/from an offload device like a GPU. */ bool offload_copy = false; bool fast_math = true; bool exhaustive_tune = false; tracer trace{}; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/compile_src.hpp000066400000000000000000000046641510465702400236130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_COMPILE_SRC_HPP #define MIGRAPHX_GUARD_MIGRAPHX_COMPILE_SRC_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct src_file { fs::path path; std::string_view content; src_file() = default; src_file(fs::path file_path, std::string_view file_content) : path{std::move(file_path)}, content{file_content} { } explicit src_file(const std::pair& pair) : path{pair.first}, content{pair.second} { } }; struct MIGRAPHX_EXPORT src_compiler { #ifdef _WIN32 fs::path compiler = MIGRAPHX_CXX_COMPILER; #else fs::path compiler = "c++"; #endif std::vector flags = {}; fs::path output = {}; fs::path launcher = {}; std::string out_ext = ".o"; std::function process = nullptr; std::vector compile(const std::vector& srcs) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_COMPILE_SRC_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/concat_opt.hpp000066400000000000000000000251241510465702400234370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_CONCAT_OPT_HPP #define MIGRAPHX_GUARD_CONCAT_OPT_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// An interface for target-dependent optimization for the concat instruction struct concat_optimization { /// A name of the target-dependent allocate operator std::string allocate() const; /// Return the target-independent concat operator optional get_concat(const operation& op) const; }; #else #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT concat_optimization { // std::string allocate() const; // optional get_concat(const operation& op) const; }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct concat_optimization { private: template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(std::declval().allocate(), std::declval().get_concat( std::declval()), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors concat_optimization() = default; template , typename = typename std::enable_if< not std::is_same, concat_optimization>{}>::type> concat_optimization(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template , typename = typename std::enable_if< not std::is_same, concat_optimization>{}>::type> concat_optimization& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { concat_optimization rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } std::string allocate() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().allocate(); } optional get_concat(const operation& op) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().get_concat(op); } friend bool is_shared(const concat_optimization& private_detail_x, const concat_optimization& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual std::string allocate() const = 0; virtual optional get_concat(const operation& op) const = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } std::string allocate() const override { return private_detail_te_value.allocate(); } optional get_concat(const operation& op) const override { return private_detail_te_value.get_concat(op); } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const concat_optimization* x) { return x->any_cast(); } template inline ValueType* any_cast(concat_optimization* x) { return x->any_cast(); } template inline ValueType& any_cast(concat_optimization& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const concat_optimization& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/config.hpp000066400000000000000000000044031510465702400225500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_CONFIG_HPP #define MIGRAPHX_GUARD_CONFIG_HPP #include #include #if !defined(MIGRAPHX_USE_CLANG_TIDY) && !defined(DOXYGEN) #ifdef BUILD_DEV #define MIGRAPHX_INLINE_NS version_1 #else #include #define MIGRAPHX_VERSION_PRIMITIVE_CONCAT(x, y) x##_##y #define MIGRAPHX_VERSION_CONCAT(x, y) MIGRAPHX_VERSION_PRIMITIVE_CONCAT(x, y) #define MIGRAPHX_VERSION \ MIGRAPHX_VERSION_CONCAT( \ MIGRAPHX_VERSION_CONCAT(MIGRAPHX_VERSION_MAJOR, MIGRAPHX_VERSION_MINOR), \ MIGRAPHX_VERSION_PATCH) #define MIGRAPHX_INLINE_NS MIGRAPHX_VERSION_CONCAT(version, MIGRAPHX_VERSION) #endif // build_dev #endif // clang_tidy #ifdef DOXYGEN #define MIGRAPHX_INLINE_NS internal #endif // doxygen #ifdef MIGRAPHX_USE_CLANG_TIDY #define MIGRAPHX_TIDY_CONST const #else #define MIGRAPHX_TIDY_CONST #endif // tidy_const #ifdef NDEBUG #define MIGRAPHX_DEBUG_USED [[maybe_unused]] #else #define MIGRAPHX_DEBUG_USED #endif #endif // clang_tidy ROCm-AMDMIGraphX-46524e8/src/include/migraphx/context.hpp000066400000000000000000000360451510465702400227760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_CONTEXT_HPP #define MIGRAPHX_GUARD_CONTEXT_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// A context is used to store internal data for a `target`. A context is /// constructed by a target during compilation and passed to the operations /// during `eval`. struct context { /// Wait for any tasks in the context to complete void finish() const; }; #else template value to_value_context(const T&) { return value{}; } template void from_value_context(T&, const value&) { } template any_ptr get_queue_context(T&) { return {}; } template void wait_for_context(T&, any_ptr) { } template void finish_on_context(T&, any_ptr) { } #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT context { // (optional) value to_value() const; // (optional) void from_value(const value& v); // (optional) any_ptr get_queue(); // (optional) void wait_for(any_ptr queue); // (optional) void finish_on(any_ptr queue); // void finish() const; }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct context { private: template static auto private_detail_te_default_to_value(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.to_value()) { return private_detail_te_self.to_value(); } template static value private_detail_te_default_to_value(float, T&& private_detail_te_self) { return to_value_context(private_detail_te_self); } template static auto private_detail_te_default_from_value(char, T&& private_detail_te_self, const value& v) -> decltype(private_detail_te_self.from_value(v)) { private_detail_te_self.from_value(v); } template static void private_detail_te_default_from_value(float, T&& private_detail_te_self, const value& v) { from_value_context(private_detail_te_self, v); } template static auto private_detail_te_default_get_queue(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.get_queue()) { return private_detail_te_self.get_queue(); } template static any_ptr private_detail_te_default_get_queue(float, T&& private_detail_te_self) { return get_queue_context(private_detail_te_self); } template static auto private_detail_te_default_wait_for(char, T&& private_detail_te_self, any_ptr queue) -> decltype(private_detail_te_self.wait_for(queue)) { private_detail_te_self.wait_for(queue); } template static void private_detail_te_default_wait_for(float, T&& private_detail_te_self, any_ptr queue) { wait_for_context(private_detail_te_self, queue); } template static auto private_detail_te_default_finish_on(char, T&& private_detail_te_self, any_ptr queue) -> decltype(private_detail_te_self.finish_on(queue)) { private_detail_te_self.finish_on(queue); } template static void private_detail_te_default_finish_on(float, T&& private_detail_te_self, any_ptr queue) { finish_on_context(private_detail_te_self, queue); } template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(private_detail_te_default_to_value(char(0), std::declval()), private_detail_te_default_from_value(char(0), std::declval(), std::declval()), private_detail_te_default_get_queue(char(0), std::declval()), private_detail_te_default_wait_for( char(0), std::declval(), std::declval()), private_detail_te_default_finish_on( char(0), std::declval(), std::declval()), std::declval().finish(), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors context() = default; template , typename = typename std::enable_if< not std::is_same, context>{}>::type> context(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template , typename = typename std::enable_if< not std::is_same, context>{}>::type> context& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { context rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } value to_value() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().to_value(); } void from_value(const value& v) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().from_value(v); } any_ptr get_queue() { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().get_queue(); } void wait_for(any_ptr queue) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().wait_for(queue); } void finish_on(any_ptr queue) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().finish_on(queue); } void finish() const { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().finish(); } friend bool is_shared(const context& private_detail_x, const context& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual value to_value() const = 0; virtual void from_value(const value& v) = 0; virtual any_ptr get_queue() = 0; virtual void wait_for(any_ptr queue) = 0; virtual void finish_on(any_ptr queue) = 0; virtual void finish() const = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } value to_value() const override { return private_detail_te_default_to_value(char(0), private_detail_te_value); } void from_value(const value& v) override { private_detail_te_default_from_value(char(0), private_detail_te_value, v); } any_ptr get_queue() override { return private_detail_te_default_get_queue(char(0), private_detail_te_value); } void wait_for(any_ptr queue) override { private_detail_te_default_wait_for(char(0), private_detail_te_value, queue); } void finish_on(any_ptr queue) override { private_detail_te_default_finish_on(char(0), private_detail_te_value, queue); } void finish() const override { private_detail_te_value.finish(); } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const context* x) { return x->any_cast(); } template inline ValueType* any_cast(context* x) { return x->any_cast(); } template inline ValueType& any_cast(context& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const context& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif inline void migraphx_to_value(value& v, const context& ctx) { v = ctx.to_value(); } inline void migraphx_from_value(const value& v, context& ctx) { ctx.from_value(v); } #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/convert_to_json.hpp000066400000000000000000000030071510465702400245150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_CONVERT_TO_JSON_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_CONVERT_TO_JSON_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT std::string convert_to_json(const std::string& str); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/convolution.hpp000066400000000000000000000101221510465702400236550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CONVOLUTION_HPP #define MIGRAPHX_GUARD_RTGLIB_CONVOLUTION_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template void convolution( Output output, T input, T weights, Padding padding, Stride stride, Dilation dilation, int group) { auto output_shape = output.get_shape(); auto in_lens = input.get_shape().lens(); auto wei_lens = weights.get_shape().lens(); auto wei_n = wei_lens[0]; auto wei_c = wei_lens[1]; std::vector win_size(wei_lens.begin() + 1, wei_lens.end()); par_for(output_shape.elements(), [&](auto i) { auto idx_o = output_shape.multi(i); auto w = idx_o[1]; auto n_dim = idx_o.size(); std::vector win_start; for(std::size_t dim = 2; dim < n_dim; ++dim) { auto d_2 = dim - 2; win_start.push_back(std::ptrdiff_t(idx_o[dim] * stride[d_2]) - std::ptrdiff_t(padding[d_2])); } const auto group_id = w / (wei_n / group); shape win_shape{output_shape.type(), win_size}; double acc = 0.0; shape_for_each(win_shape, [&](const auto& idx_win) { auto k = idx_win[0]; const auto in_ch = group_id * wei_c + k; std::vector idx(idx_o.begin(), idx_o.end()); idx[1] = in_ch; std::vector idx_dil(idx_win.size() - 1); std::transform(idx_win.cbegin() + 1, idx_win.cend(), dilation.cbegin(), idx_dil.begin(), [](std::ptrdiff_t ii, std::ptrdiff_t d) { return d * ii; }); std::transform(idx_dil.begin(), idx_dil.end(), win_start.begin(), idx.begin() + 2, [](std::ptrdiff_t ii, std::ptrdiff_t jj) { return ii + jj; }); std::vector idx_wei(idx_o.size()); idx_wei[0] = w; std::copy(idx_win.begin(), idx_win.end(), idx_wei.begin() + 1); if(std::all_of(idx.begin() + 2, idx.end(), [&](auto ii) { return ii >= 0; }) and std::equal(idx.begin(), idx.end(), in_lens.begin(), in_lens.end(), std::less{})) { acc += input(idx.begin(), idx.end()) * weights(idx_wei.begin(), idx_wei.end()); } }); output[i] = acc; }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/copy_assignable_function.hpp000066400000000000000000000046071510465702400263600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_COPY_ASSIGNABLE_FUNCTION_HPP #define MIGRAPHX_GUARD_MIGRAPHX_COPY_ASSIGNABLE_FUNCTION_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct copy_assignable_function_wrapper { optional f; copy_assignable_function_wrapper(F pf) : f(std::move(pf)) {} copy_assignable_function_wrapper(const copy_assignable_function_wrapper& other) = default; copy_assignable_function_wrapper(copy_assignable_function_wrapper&& other) noexcept = default; copy_assignable_function_wrapper& operator=(copy_assignable_function_wrapper other) { f.reset(); if(other.f.has_value()) f.emplace(std::move(*other.f)); return *this; } template auto operator()(Ts&&... xs) const -> decltype((*f)(std::forward(xs)...)) { return (*f)(std::forward(xs)...); } }; template using copy_assignable_function = std::conditional_t{}, F, copy_assignable_function_wrapper>; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_COPY_ASSIGNABLE_FUNCTION_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/cpp_generator.hpp000066400000000000000000000077741510465702400241510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_CPP_GENERATOR_HPP #define MIGRAPHX_GUARD_MIGRAPHX_CPP_GENERATOR_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct operation; struct module; struct shape; struct cpp_generator_impl; struct MIGRAPHX_EXPORT cpp_generator { using generate_module_callback = std::function&)>; struct param { std::string name; std::string type; }; struct MIGRAPHX_EXPORT function { std::vector params = {}; std::string body = ""; std::string return_type = "void"; std::string name = ""; std::vector attributes = {}; std::vector tparams = {}; function& set_body(const module& m, const generate_module_callback& g); function& set_body(const std::string& s) { body = s; return *this; } function& set_name(const std::string& s) { name = s; return *this; } function& set_attributes(std::vector attrs) { attributes = std::move(attrs); return *this; } function& set_types(const module& m); function& set_types(const module& m, const std::function& parse); function& set_generic_types(const module& m); function& add_generic_param(const std::string& pname); function& unused_param(const std::string& pname); }; cpp_generator(); // move constructor cpp_generator(cpp_generator&&) noexcept; // copy assignment operator cpp_generator& operator=(cpp_generator rhs); ~cpp_generator() noexcept; void fmap(const std::function& f); void fresult(const std::function& f); void always_return_tuple(bool b = true); void add_point_op(const std::string& op_name, const std::string& code); std::string generate_point_op(const operation& op, const std::vector& args); std::string str() const; function generate_module(const module& m, const generate_module_callback& g); function generate_module(const module& m); std::string create_function(const function& f); static std::vector to_args(const std::vector& inputs, const std::unordered_map& names); private: std::unique_ptr impl; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_CPP_GENERATOR_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/dead_code_elimination.hpp000066400000000000000000000034011510465702400255570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEAD_CODE_ELIMINATION_HPP #define MIGRAPHX_GUARD_RTGLIB_DEAD_CODE_ELIMINATION_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct program; /** * Remove instructions where the output is not used. */ struct MIGRAPHX_EXPORT dead_code_elimination { std::string name() const { return "dead_code_elimination"; } void apply(module& m) const; void apply(program& p) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/dfor.hpp000066400000000000000000000032721510465702400222400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_DFOR_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_DFOR_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // Multidimensional for loop inline auto dfor() { return [](auto f) { f(); }; } template auto dfor(T x, Ts... xs) { return [=](auto f) { for(T i = 0; i < x; i++) { dfor(xs...)([&](Ts... is) { f(i, is...); }); } }; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/dom_info.hpp000066400000000000000000000034651510465702400231040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DOM_INFO_HPP #define MIGRAPHX_GUARD_RTGLIB_DOM_INFO_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct MIGRAPHX_EXPORT dominator_info { bool strictly_dominate(instruction_ref ins1, instruction_ref ins2) const; std::unordered_map ins2idom; }; MIGRAPHX_EXPORT dominator_info compute_dominator(const module& m); // MIGRAPHX_EXPORT dominator_info compute_dominator_naive(const module& m); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/dyn_output.hpp000066400000000000000000000047701510465702400235240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_DYN_OUTPUT_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_DYN_OUTPUT_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct dyn_output { // original shape from the instruction shape ins_shape; // shape computed at eval time using input arguments shape computed_shape; }; /** * Handle dynamic and static shape at evaluation time. * If converted to shape type, returns original ins_shape. * If converted to dyn_output type, will compute an output shape using the input arguments. */ template struct compute_output_shape { F ins_inputs; operator dyn_output() const { return ins_inputs([](const auto& x, shape ins_shape, const std::vector& inputs) { if(ins_shape.dynamic()) return dyn_output{ins_shape, compute_shape(x, to_shapes(inputs))}; return dyn_output{ins_shape, ins_shape}; }); } operator shape() const { return ins_inputs( [](const auto&, shape ins_shape, const std::vector&) { return ins_shape; }); } }; template compute_output_shape make_compute_output_shape(F f) { return {std::move(f)}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/dynamic_loader.hpp000066400000000000000000000050121510465702400242520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_DYNAMIC_LOADER_HPP #define MIGRAPHX_GUARD_MIGRAPHX_DYNAMIC_LOADER_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct dynamic_loader_impl; struct MIGRAPHX_EXPORT dynamic_loader { template static fs::path path(T* address) { return path(reinterpret_cast(address)); } static fs::path path(void* address); static optional try_load(const fs::path& p); dynamic_loader() = default; dynamic_loader(const fs::path& p); dynamic_loader(const char* image, std::size_t size); dynamic_loader(const std::vector& buffer); std::shared_ptr get_symbol(const std::string& name) const; template std::function get_function(const std::string& name) const { auto s = get_symbol(name); return [=](auto&&... xs) -> decltype(auto) { auto f = reinterpret_cast>(s.get()); return f(std::forward(xs)...); }; } private: std::shared_ptr impl; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_DYNAMIC_LOADER_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/eliminate_allocation.hpp000066400000000000000000000035111510465702400254560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ELIMINATE_ALLOCATION_HPP #define MIGRAPHX_GUARD_RTGLIB_ELIMINATE_ALLOCATION_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove memory allocations. This will create a parameter which is the max of all memory used in * the program. */ struct MIGRAPHX_EXPORT eliminate_allocation { std::string allocation_op{}; std::size_t alignment = 32; std::string name() const { return "eliminate_allocation"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/eliminate_common_subexpression.hpp000066400000000000000000000033441510465702400276160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_COMMON_SUBEXPRESSION_ELIMINATION_HPP #define MIGRAPHX_GUARD_RTGLIB_COMMON_SUBEXPRESSION_ELIMINATION_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove identical instructions. */ struct MIGRAPHX_EXPORT eliminate_common_subexpression { std::string name() const { return "eliminate_common_subexpression"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/eliminate_concat.hpp000066400000000000000000000034501510465702400246020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ELIMINATE_CONCAT_HPP #define MIGRAPHX_GUARD_RTGLIB_ELIMINATE_CONCAT_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove concat operators by having each operator can write to different chunk of memory. */ struct MIGRAPHX_EXPORT eliminate_concat { concat_optimization concat_opt; std::string name() const { return "eliminate_concat"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/eliminate_contiguous.hpp000066400000000000000000000034121510465702400255300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ELIMINATE_CONTIGUOUS_HPP #define MIGRAPHX_GUARD_RTGLIB_ELIMINATE_CONTIGUOUS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove contiguous instructions by checking if the operator can use non-standard shapes. */ struct MIGRAPHX_EXPORT eliminate_contiguous { std::string op_name; std::string name() const { return "eliminate_contiguous"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/eliminate_convert.hpp000066400000000000000000000032701510465702400250130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ELIMINATE_CONVERTS_HPP #define MIGRAPHX_GUARD_RTGLIB_ELIMINATE_CONVERTS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove nested converts and nop converts. */ struct MIGRAPHX_EXPORT eliminate_convert { std::string name() const { return "eliminate_convert"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/eliminate_data_type.hpp000066400000000000000000000036171510465702400253120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ELIMINATE_DATA_TYPE_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ELIMINATE_DATA_TYPE_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove data types. This will instert convert operators so the data type * is not used by any operator. */ struct MIGRAPHX_EXPORT eliminate_data_type { std::set unsupported_types; shape::type_t target_type; std::set unsupported_ops = {"all"}; std::string name() const { return "eliminate_data_type"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/eliminate_identity.hpp000066400000000000000000000035241510465702400251660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ELIMINATE_IDENTITY_HPP #define MIGRAPHX_GUARD_RTGLIB_ELIMINATE_IDENTITY_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove identity instructions. Currently when used as the last pass, it will * preserve the semantics of previous program state, therefore dead code elimination * should not be used afterwards. */ struct MIGRAPHX_EXPORT eliminate_identity { std::string name() const { return "eliminate_identity"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/eliminate_pad.hpp000066400000000000000000000034041510465702400240760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ELIMINATE_PAD_HPP #define MIGRAPHX_GUARD_RTGLIB_ELIMINATE_PAD_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove pads if they can be written as an * attribute to another op (im2col, convolution, pooling) */ struct MIGRAPHX_EXPORT eliminate_pad { std::string name() const { return "eliminate_pad"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/env.hpp000066400000000000000000000052471510465702400221020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ENV_HPP #define MIGRAPHX_GUARD_RTGLIB_ENV_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // Declare a cached environment variable #define MIGRAPHX_DECLARE_ENV_VAR(x) \ struct x /* NOLINT */ \ { \ static const char* value() { return #x; } \ }; MIGRAPHX_EXPORT bool enabled(const char* name); MIGRAPHX_EXPORT bool disabled(const char* name); MIGRAPHX_EXPORT std::vector env(const char* name); MIGRAPHX_EXPORT std::size_t value_of(const char* name, std::size_t fallback = 0); MIGRAPHX_EXPORT std::string string_value_of(const char* name, std::string fallback = ""); MIGRAPHX_EXPORT std::map get_all_envs(); template bool enabled(T) { static const bool result = enabled(T::value()); return result; } template bool disabled(T) { static const bool result = disabled(T::value()); return result; } template std::size_t value_of(T, std::size_t fallback = 0) { static const std::size_t result = value_of(T::value(), fallback); return result; } template std::string string_value_of(T, std::string fallback = "") { static const std::string result = string_value_of(T::value(), std::move(fallback)); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/erase.hpp000066400000000000000000000042251510465702400224040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_ERASE_HPP #define MIGRAPHX_GUARD_ERASE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * @brief Erase all elements from a container * * @param r The container to erase elements from * @param value The value to be erased * @return Returns iterator to erased element */ template auto erase(R&& r, const T& value) { return r.erase(std::remove(r.begin(), r.end(), value), r.end()); } /** * @brief Erase all elements from a container * * @param r The container to erase elements from * @param pred Predicate function that selects which elements should be erased. */ template void erase_if(R&& r, P&& pred) { auto first = r.begin(); auto last = r.end(); while(first != last) { if(pred(*first)) first = r.erase(first); else first++; } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/errors.hpp000066400000000000000000000053721510465702400226250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_ERRORS_HPP #define MIGRAPHX_GUARD_ERRORS_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /// Represents exceptions that can be thrown by migraphxlib struct exception : std::runtime_error { unsigned int error; exception(unsigned int e = 0, const std::string& msg = "") : std::runtime_error(msg), error(e) { } }; /** * @brief Create an exception object * * @param context A message that says where the exception occurred * @param message Custom message for the error * @return Exceptions */ inline exception make_exception(const std::string& context, const std::string& message = "") { return {0, context + ": " + message}; } inline exception make_exception(const std::string& context, unsigned int e, const std::string& message = "") { return {e, context + ": " + message}; } /** * @brief Create a message of a file location * * @param file The filename * @param line The line number * * @return A string that represents the file location */ inline std::string make_source_context(const std::string& file, int line, const std::string& fname) { return file + ":" + std::to_string(line) + ": " + fname; } // NOLINTNEXTLINE #define MIGRAPHX_MAKE_SOURCE_CTX() migraphx::make_source_context(__FILE__, __LINE__, __func__) /** * @brief Throw an exception with context information */ #define MIGRAPHX_THROW(...) throw migraphx::make_exception(MIGRAPHX_MAKE_SOURCE_CTX(), __VA_ARGS__) } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/execution_environment.hpp000066400000000000000000000030731510465702400257340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_EXECUTION_ENV_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_EXECUTION_ENV_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct execution_environment { any_ptr queue = any_ptr{}; bool async = false; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif /* MIGRAPHX_GUARD_MIGRAPHLIB_EXECUTION_ENV_HPP */ ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fallthrough.hpp000066400000000000000000000030011510465702400236130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_FALLTHROUGH_HPP #define MIGRAPHX_GUARD_FALLTHROUGH_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef __clang__ #define MIGRAPHX_FALLTHROUGH [[clang::fallthrough]] #else #define MIGRAPHX_FALLTHROUGH #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/file_buffer.hpp000066400000000000000000000036631510465702400235620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FILE_BUFFER_HPP #define MIGRAPHX_GUARD_RTGLIB_FILE_BUFFER_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT std::vector read_buffer(const fs::path& filename, size_t offset = 0, size_t nbytes = 0); MIGRAPHX_EXPORT std::string read_string(const fs::path& filename); MIGRAPHX_EXPORT void write_string(const fs::path& filename, const std::string& buffer); MIGRAPHX_EXPORT void write_buffer(const fs::path& filename, const char* buffer, std::size_t size); MIGRAPHX_EXPORT void write_buffer(const fs::path& filename, const std::vector& buffer); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/filesystem.hpp000066400000000000000000000047751510465702400235030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FILESYSTEM_HPP #define MIGRAPHX_GUARD_RTGLIB_FILESYSTEM_HPP #include #if defined(CPPCHECK) #define MIGRAPHX_HAS_FILESYSTEM 1 #define MIGRAPHX_HAS_FILESYSTEM_TS 1 #elif defined(_WIN32) #if _MSC_VER >= 1920 #define MIGRAPHX_HAS_FILESYSTEM 1 #define MIGRAPHX_HAS_FILESYSTEM_TS 0 #elif _MSC_VER >= 1900 #define MIGRAPHX_HAS_FILESYSTEM 0 #define MIGRAPHX_HAS_FILESYSTEM_TS 1 #else #define MIGRAPHX_HAS_FILESYSTEM 0 #define MIGRAPHX_HAS_FILESYSTEM_TS 0 #endif #elif defined(__has_include) #if __has_include() && __cplusplus >= 201703L #define MIGRAPHX_HAS_FILESYSTEM 1 #else #define MIGRAPHX_HAS_FILESYSTEM 0 #endif #if __has_include() && __cplusplus >= 201103L #define MIGRAPHX_HAS_FILESYSTEM_TS 1 #else #define MIGRAPHX_HAS_FILESYSTEM_TS 0 #endif #else #define MIGRAPHX_HAS_FILESYSTEM 0 #define MIGRAPHX_HAS_FILESYSTEM_TS 0 #endif #if MIGRAPHX_HAS_FILESYSTEM #include #elif MIGRAPHX_HAS_FILESYSTEM_TS #include #else #error "No filesystem include available" #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #if MIGRAPHX_HAS_FILESYSTEM namespace fs = ::std::filesystem; #elif MIGRAPHX_HAS_FILESYSTEM_TS namespace fs = ::std::experimental::filesystem; #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fileutils.hpp000066400000000000000000000040761510465702400233110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_FILEUTILS_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_FILEUTILS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT fs::path make_executable_filename(std::string_view name); MIGRAPHX_EXPORT fs::path make_shared_object_filename(std::string_view name); MIGRAPHX_EXPORT fs::path make_object_file_filename(std::string_view name); MIGRAPHX_EXPORT fs::path make_static_library_filename(std::string_view name); MIGRAPHX_EXPORT fs::path append_extension(const fs::path& path, std::string_view ext); inline std::string operator+(std::string l, const fs::path& r) { return std::move(l) + r.string(); } inline std::string operator+(const fs::path& l, std::string r) { return l.string() + std::move(r); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHLIB_FILEUTILS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/float8.hpp000066400000000000000000000367621510465702400225150ustar00rootroot00000000000000/* ************************************************************************ * Copyright (C) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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 cop- * ies 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 IM- * PLIED, 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 CONNE- * CTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * ************************************************************************ */ #ifndef MIGRAPHX_GUARD_RTGLIB_FLOAT8_HPP #define MIGRAPHX_GUARD_RTGLIB_FLOAT8_HPP // We are clipping/saturation in down conversion by default. Unclipped version is not tested and // shouldn't be used without having enough tests. // logic is based on clipping table from here : https://onnx.ai/onnx/technical/float8.html#cast // NOLINTNEXTLINE #define MIGRAPHX_F8_DOWNCAST_CLIPPING 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fp8 { enum class rounding_mode { standard, // standard rounding is doing RNE -- round to nearest even stochastic }; enum class f8_type { bf8 = 0, // s1e5m2 fp8 = 1 // s1e4m3 }; template class numeric_limits; template struct float8 { uint8_t data = 0x00; // default constructor constexpr float8() = default; // default copy constructor constexpr float8(const float8& y) = default; struct from_bits_t { }; static constexpr from_bits_t from_bits() { return from_bits_t(); } explicit constexpr float8(uint8_t bits, from_bits_t) : data(bits) {} explicit constexpr float8( float v, migraphx::fp8::rounding_mode rm = migraphx::fp8::rounding_mode::standard, uint32_t rng = 0) { if constexpr(T == migraphx::fp8::f8_type::fp8) { #ifdef MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<3, 4, float, FNUZ /*negative_zero_nan*/, true /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #else // MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<3, 4, float, FNUZ /*negative_zero_nan*/, false /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #endif // MIGRAPHX_F8_DOWNCAST_CLIPPING } else { #ifdef MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<2, 5, float, FNUZ /*negative_zero_nan*/, true /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #else // MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<2, 5, float, FNUZ /*negative_zero_nan*/, false /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #endif // rocblas_F8_downcast_clipping} } } constexpr operator float() const { if constexpr(T == migraphx::fp8::f8_type::fp8) { return migraphx::fp8::impl::cast_from_f8<3, 4, float, FNUZ /*negative_zero_nan*/>(data); } // else return migraphx::fp8::impl::cast_from_f8<2, 5, float, FNUZ /*negative_zero_nan*/>(data); } explicit constexpr operator bool() const { return not is_zero(); } constexpr bool is_zero() const { if constexpr(FNUZ) { return data == 0x00; } else { return (data == 0x00) or (data == 0x80); } } constexpr bool is_nan() const { if constexpr(FNUZ) { return data == 0x80; } else { if(T == migraphx::fp8::f8_type::bf8) { return (data == 0x7D) or (data == 0x7E) or (data == 0x7F) or (data == 0xFD) or (data == 0xFE) or (data == 0xFF); } else { return (data == 0x7F) or (data == 0xFF); } } } constexpr bool is_inf() const { if constexpr(FNUZ) { return data == 0x80; } else { if(T == migraphx::fp8::f8_type::bf8) { return (data == 0x7C) or (data == 0xFC); } else { // no infinities in e4m3fn, represent them as NaNs return (data == 0x7F) or (data == 0xFF); } } } // NOLINTNEXTLINE #define MIGRAPHX_FP8_UNARY_OP(unary_op, binary_op) \ constexpr float8& operator unary_op(const float8& rhs) \ { \ const auto tmp = static_cast(*this) binary_op static_cast(rhs); \ *this = static_cast(tmp); \ return *this; \ } \ constexpr float8& operator unary_op(const float& rhs) \ { \ const auto tmp = static_cast(*this) binary_op static_cast(rhs); \ *this = static_cast(tmp); \ return *this; \ } MIGRAPHX_FP8_UNARY_OP(*=, *) MIGRAPHX_FP8_UNARY_OP(-=, -) MIGRAPHX_FP8_UNARY_OP(+=, +) MIGRAPHX_FP8_UNARY_OP(/=, /) constexpr float8& operator=(const float8& rhs) = default; constexpr float8& operator=(float8&& rhs) noexcept = default; constexpr float8& operator=(float rhs) { *this = static_cast(rhs); return *this; } constexpr bool operator==(const float8& rhs) const { if(rhs.is_nan() or rhs.is_inf() or this->is_nan() or this->is_inf()) return false; else if((rhs.is_zero() and this->is_zero()) or (this->data == rhs.data)) return true; return false; } constexpr bool operator<(const float8& rhs) const { const auto we = static_cast(*this); const auto them = static_cast(rhs); return we < them; } constexpr bool operator>(const float8& rhs) const { const auto we = static_cast(*this); const auto them = static_cast(rhs); return we > them; } }; // https://onnx.ai/onnx/technical/float8.html using fp8e4m3fn = float8; using fp8e5m2 = float8; using fp8e4m3fnuz = float8; using fp8e5m2fnuz = float8; /* // NOLINTNEXTLINE #define MIGRAPHX_FP8_BINARY_OP(binary_op, T, U) \ inline constexpr U operator binary_op(const T& lhs, const T& rhs) \ { \ return U(static_cast(lhs) binary_op static_cast(rhs)); \ } // TODO: these should return floats for binary ops // NOLINTNEXTLINE #define MIGRAPHX_FP8_BINARY_OP_GEN_FOR(T) \ MIGRAPHX_FP8_BINARY_OP(*, T, T) \ MIGRAPHX_FP8_BINARY_OP(-, T, T) \ MIGRAPHX_FP8_BINARY_OP(/, T, T) \ MIGRAPHX_FP8_BINARY_OP(+, T, T) \ MIGRAPHX_FP8_BINARY_OP(==, T, bool) \ MIGRAPHX_FP8_BINARY_OP(>=, T, bool) \ MIGRAPHX_FP8_BINARY_OP(<=, T, bool) \ MIGRAPHX_FP8_BINARY_OP(>, T, bool) \ MIGRAPHX_FP8_BINARY_OP(<, T, bool) \ MIGRAPHX_FP8_BINARY_OP(!=, T, bool) MIGRAPHX_FP8_BINARY_OP_GEN_FOR(fp8e5m2) MIGRAPHX_FP8_BINARY_OP_GEN_FOR(fp8e4m3fn) MIGRAPHX_FP8_BINARY_OP_GEN_FOR(fp8e5m2fnuz) MIGRAPHX_FP8_BINARY_OP_GEN_FOR(fp8e4m3fnuz) */ // Special operator overloading inline std::ostream& operator<<(std::ostream& os, const fp8e4m3fnuz& rhs) { return os << static_cast(rhs); } inline fp8e4m3fnuz fabs(fp8e4m3fnuz v) { v.data = v.data & 0x7F; // NOLINT return v; } // Special operator overloading inline std::ostream& operator<<(std::ostream& os, const fp8e4m3fn& rhs) { return os << static_cast(rhs); } inline fp8e4m3fn fabs(fp8e4m3fn v) { v.data = v.data & 0x7F; // NOLINT return v; } // Special operator overloading inline std::ostream& operator<<(std::ostream& os, const fp8e5m2fnuz& rhs) { return os << static_cast(rhs); } inline fp8e5m2fnuz fabs(fp8e5m2fnuz v) { v.data = v.data & 0x7F; // NOLINT return v; } // Special operator overloading inline std::ostream& operator<<(std::ostream& os, const fp8e5m2& rhs) { return os << static_cast(rhs); } inline fp8e5m2 fabs(fp8e5m2 v) { v.data = v.data & 0x7F; // NOLINT return v; } template <> class numeric_limits { public: static constexpr bool has_infinity = false; static constexpr fp8e4m3fnuz epsilon() { return fp8e4m3fnuz(0x28, fp8e4m3fnuz::from_bits()); } // NOLINTNEXTLINE static constexpr fp8e4m3fnuz quiet_NaN() { return fp8e4m3fnuz(0x80, fp8e4m3fnuz::from_bits()); } static constexpr fp8e4m3fnuz max() { return fp8e4m3fnuz(0x7F, fp8e4m3fnuz::from_bits()); } // this is min value that is not DeNorm. DeNorm min is 0x01 static constexpr fp8e4m3fnuz min() { return fp8e4m3fnuz(0x08, fp8e4m3fnuz::from_bits()); } static constexpr fp8e4m3fnuz lowest() { return fp8e4m3fnuz(0xFF, fp8e4m3fnuz::from_bits()); } }; template <> class numeric_limits { public: static constexpr bool has_infinity = false; static constexpr fp8e4m3fn epsilon() { return fp8e4m3fn(0x20, fp8e4m3fn::from_bits()); } // NOLINTNEXTLINE static constexpr fp8e4m3fn quiet_NaN() { return fp8e4m3fn(0x7F, fp8e4m3fn::from_bits()); } static constexpr fp8e4m3fn max() { return fp8e4m3fn(0x7E, fp8e4m3fn::from_bits()); } // this is min value that is not DeNorm. DeNorm min is 0x01 static constexpr fp8e4m3fn min() { return fp8e4m3fn(0x08, fp8e4m3fn::from_bits()); } static constexpr fp8e4m3fn lowest() { return fp8e4m3fn(0xFE, fp8e4m3fn::from_bits()); } }; template <> class numeric_limits { public: static constexpr bool has_infinity = false; static constexpr fp8e5m2fnuz epsilon() { return fp8e5m2fnuz(0x34, fp8e5m2fnuz::from_bits()); } static constexpr fp8e5m2fnuz quiet_NaN() // NOLINT { return fp8e5m2fnuz(0x80, fp8e5m2fnuz::from_bits()); } static constexpr fp8e5m2fnuz max() { return fp8e5m2fnuz(0x7F, fp8e5m2fnuz::from_bits()); } // this is min value that is not DeNorm. DeNorm min is 0x01. I am not sure if we want to make // this distinction. For the floating points we would end up using lowest most of the times. static constexpr fp8e5m2fnuz min() { return fp8e5m2fnuz(0x4, fp8e5m2fnuz::from_bits()); } static constexpr fp8e5m2fnuz lowest() { return fp8e5m2fnuz(0xFF, fp8e5m2fnuz::from_bits()); } }; template <> class numeric_limits { public: static constexpr bool has_infinity = true; static constexpr fp8e5m2 epsilon() { return fp8e5m2(0x34, fp8e5m2::from_bits()); } // 7D, 7E, 7F are positive NaNs and FD, FE, FF are negative NaNs static constexpr fp8e5m2 quiet_NaN() { return fp8e5m2(0xFF, fp8e5m2::from_bits()); } // NOLINT static constexpr fp8e5m2 max() { return fp8e5m2(0x7B, fp8e5m2::from_bits()); } // this is min value that is not DeNorm. DeNorm min is 0x01. I am not sure if we want to make // this distinction. For the floating points we would end up using lowest most of the times. static constexpr fp8e5m2 min() { return fp8e5m2(0x4, fp8e5m2::from_bits()); } static constexpr fp8e5m2 lowest() { return fp8e5m2(0xFB, fp8e5m2::from_bits()); } // 7C and FC both are infinity static constexpr fp8e5m2 infinity() { return fp8e5m2(0x7C, fp8e5m2::from_bits()); } }; } // namespace fp8 } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx // ================================================================================================= // define numeric limits for the new data type // NOLINTBEGIN(cert-dcl58-cpp) namespace std { template inline bool isfinite(migraphx::fp8::float8 x) { return not x.is_inf() and not x.is_nan(); } template inline bool isnan(migraphx::fp8::float8 x) { return x.is_nan(); } template class numeric_limits> : public migraphx::fp8::numeric_limits> { }; template struct common_type, U> : std::common_type { }; template struct common_type> : std::common_type { }; template struct common_type, migraphx::fp8::float8> { using type = migraphx::fp8::float8; }; template struct common_type, migraphx::fp8::float8> { using type = float; }; template struct common_type, migraphx::fp8::float8> { using type = float; }; template struct common_type, migraphx::generic_float> { using type = float; }; template struct common_type, migraphx::fp8::float8> : std::common_type { }; template struct common_type, migraphx::generic_float> : std::common_type { }; } // namespace std // NOLINTEND(cert-dcl58-cpp) // ================================================================================================= #endif // MIGRAPHX_GUARD_RTGLIB_FLOAT8_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/float8_impl.hpp000066400000000000000000000316021510465702400235220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FLOAT8_IMPL_HPP #define MIGRAPHX_GUARD_RTGLIB_FLOAT8_IMPL_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fp8 { namespace impl { // NOLINTBEGIN template constexpr uint8_t cast_to_f8(T f_x, bool stoch = false, uint32_t rng = 0) { constexpr bool is_float = std::is_same::value; // half is not supported for now constexpr bool is_half = false; static_assert(Wm + We == 7, "Wm+We==7"); static_assert(is_float or is_half, "Only float can be cast to f8"); const uint32_t mfmt = (sizeof(T) == 4) ? 23 : 10; typename std::conditional::type x = 0; if constexpr(sizeof(T) == 4) x = migraphx::bit_cast(f_x); else x = migraphx::bit_cast(f_x); uint32_t head = 0; uint32_t mantissa = 0; int exponent = 0; uint32_t bias = 0; uint32_t sign = 0; if constexpr(sizeof(T) == 4) { head = x & 0xFF800000; mantissa = x & 0x7FFFFF; exponent = (head >> 23) & 0xFF; sign = head >> 31; bias = 127; } else { head = x & 0xFC00; mantissa = x & 0x3FF; exponent = (head >> 10) & 0x1F; sign = head >> 15; bias = 15; } uint32_t signed_inf = (sign << 7) + (((1 << We) - 1) << Wm); uint32_t signed_all_ones = (sign << 7) + ((((1 << We) - 1) << Wm) + ((1 << Wm) - 1)); // Calcualte maximum singed value FLT_MAX, FLT_MIN uint32_t signed_max = signed_all_ones; if(not NegativeZeroNan) signed_max = (Wm == 2) ? (signed_max - 4) : (signed_max - 1); // Deal with inf and NaNs if(NegativeZeroNan) // For the FNUZ cases, it is simple just return NaNs { if((sizeof(T) == 4 and ((x & 0x7F800000) == 0x7F800000)) or (sizeof(T) == 2 and ((x & 0x7C00) == 0x7C00))) return 0x80; } else { // calculate most common NaN mantissa for FP8, which is all Ones in binary uint32_t nan_mantissa = 1; for(auto i = 1; i < Wm; ++i) { nan_mantissa |= (nan_mantissa << 1); } if((sizeof(T) == 4 and ((x & 0x7F800000) == 0x7F800000)) or (sizeof(T) == 2 and ((x & 0x7C00) == 0x7C00))) { // infinity if(mantissa == 0) { if(sign == 0) return (Wm == 2) ? 0x7B : 0x7E; else return (Wm == 2) ? 0xFB : 0xFE; } else // NaNs return signed_inf + nan_mantissa; } } // handle positive zero if(x == 0) return 0; // handle negative zero else if((sizeof(T) == 4 and x == 0x80000000) or (sizeof(T) == 2 and x == 0x8000)) { return NegativeZeroNan ? 0 : 0x80; // For FNUZ types neg zero is just positive zero } /* First need to check if it is normal or denorm as there is a difference of implict 1 Then need to adjust the exponent to align with the F8 exponent, in the meanwhile, shift The mantissa. Then for stochastic rounding, add rng to mantissa and truncate. And for RNE, no need to add rng. Then probably need to check whether there is carry and adjust exponent and mantissa again*/ // For IEEE bias mode, the bias is 2^(k-1) -1 where k is the width of exponent bits const int f8_bias = (1 << (We - 1u)) - 1 + (NegativeZeroNan ? 1 : 0); const int f8_denormal_act_exponent = 1 - f8_bias; // actual exponent of f8 denormal /* act_exponent is the actual exponent of fp32/fp16 (after subtracting bias) f8_exponent is the converted f8 exponent with bias encoding exponent_diff is the diff between fp32/fp16 exponent and f8 exponent, the difference needs to be adjusted and mantissa shifted*/ int act_exponent = 0; int f8_exponent = 0; int exponent_diff = 0; if(exponent == 0 and mantissa != 0) { // fp32/fp16 is in denormal. /* fp32 denormal is below 2^-127 so it is usually not a concern here, we mostly concern fp16 here. In this case, f8 is usually in denormal. But there could be exceptions. fp16 denormal has exponent bias 15 while bf8 with FNUZ has exponent bias 16. It means that there are some numbers in fp16 denormal but they are bf8 (FNUZ) normals - smallest bf8 (FNUZ) normal is 2^-15. fp16 numbers where exponent==0 (actual exponent -14) and highest bit of mantissa is 1 are bf8 (FNUZ) normal. In this case, the fp16 mantissa should be shift left by 1 */ act_exponent = 1 - bias; exponent_diff = f8_denormal_act_exponent - act_exponent; // actual exponent is exponent-bias+1 as it is denormal } else { // fp32/fp16 is normal with implicit 1 act_exponent = exponent - bias; if(act_exponent <= f8_denormal_act_exponent) { /* This is the case where fp32/fp16 is normal but it is in f8 denormal range. For example fp8 FNUZ mode, denormal exponent is -7, but if the fp32/fp16 actual exponent is -7, it is actually larger due to the implict 1, Therefore it needs to be adjust to -6 and mantissa shift right by 1. So for fp32/fp16, exponent -8 is the cut point to convert to fp8 FNUZ */ exponent_diff = f8_denormal_act_exponent - act_exponent; } else { // both fp32/fp16 and f8 are in normal range exponent_diff = 0; // exponent_diff=0 does not mean there is no difference for this case, // act_exponent could be larger. Just that it does not need shift mantissa } mantissa += (1u << mfmt); // Add the implicit 1 into mantissa } // need to know whether the number is right in the middle of two adjacent fp8 numbers. use max // value of 31 to avoid undefined behaviour bool midpoint = (mantissa & ((1u << std::min(31u, mfmt - Wm + exponent_diff)) - 1)) == (1u << std::min(31u, mfmt - Wm + exponent_diff - 1)); /* This part is a bit tricky. The judgment of whether it is a tie needs to be done before we shift right as shift right could rip off some residual part and make something not midpoint look like midpoint. For example, the fp16 number 0x1002 (0 00100 0000000010), it is larger than midpoint, but after shift right by 4 bits, it would look like midpoint. */ if(exponent_diff > 0) mantissa >>= std::min(31u, uint32_t(exponent_diff)); else if(exponent_diff == -1) mantissa <<= -exponent_diff; bool implicit_one = mantissa & (1 << mfmt); // if there is no implict 1, it means the f8 is denormal and need to adjust to denorm exponent f8_exponent = (act_exponent + exponent_diff) /*actual f8 exponent*/ + f8_bias - (implicit_one ? 0 : 1); // Now we have the exponent and mantissa adjusted uint32_t drop_mask = (1u << (mfmt - Wm)) - 1; bool odd = mantissa & (1u << (mfmt - Wm)); // if the least significant bit that is not truncated is 1 /* This part is doing rounding by adding mantissa part that is going to get dropped. e.g. if the dropped part for less than 0.5 than it would round down. if the dropped part is more than 0.5 then it would round up by rolling carry to LSB of retained mantissa. For the mid point when bit pattern is like this for Odd: `xy1:10000000` for Odd and `xy0:10000000` for the Even. where `:` is delimiter for dropped v/s retained part. For the odd case : this will add xy1:10000000 + 000:10000000 which would roll over carry to LSB of retained part making it RNE. For the even case : this will add xy0:10000000 + 000:01111111 which would round down and keep number Even */ mantissa += (stoch ? rng : (midpoint ? (odd ? mantissa : mantissa - 1) : mantissa)) & drop_mask; // Now we deal with overflow if(f8_exponent == 0 and ((1 << mfmt) & mantissa)) { f8_exponent = 1; // denormal overflow to become normal, promote exponent } else if((1 << (mfmt + 1)) & mantissa) { mantissa >>= 1; f8_exponent++; } mantissa >>= (mfmt - Wm); // above range: quantize to maximum possible float of the same sign // for e5m2 case, max_exp is 14, since exp = 15 is reserved for Infs and Nans const int max_exp = (1 << We) - ((NegativeZeroNan or Wm == 3) ? 1 : 2); if(f8_exponent > max_exp) { if(Clip) return signed_max; else { // https://onnx.ai/onnx/technical/float8.html#cast if(NegativeZeroNan) return 0x80; else return (Wm == 2) ? signed_inf : signed_all_ones; } } if(f8_exponent == 0 and mantissa == 0) return NegativeZeroNan ? 0 : (sign << 7); mantissa &= (1 << Wm) - 1; return (sign << 7) | (f8_exponent << Wm) | mantissa; } // NOLINTEND template constexpr T cast_from_f8(uint8_t x) { // half is not supported for now constexpr bool is_half = false; constexpr bool is_float = std::is_same::value; static_assert(is_float or is_half, "Only float are supported"); constexpr int weo = is_half ? 5 : 8; constexpr int wmo = is_half ? 10 : (is_float ? 23 : 7); // NOLINTNEXTLINE T f_inf, f_neg_inf, f_nan, f_neg0; if constexpr(is_float) { const uint32_t if_inf = 0x7F800000; const uint32_t if_neg_inf = 0xFF800000; const uint32_t if_nan = 0x7F800001; const uint32_t if_neg0 = 0x80000000; f_inf = migraphx::bit_cast(if_inf); f_neg_inf = migraphx::bit_cast(if_neg_inf); f_nan = migraphx::bit_cast(if_nan); f_neg0 = migraphx::bit_cast(if_neg0); } if(x == 0) return 0; uint32_t sign = x >> 7; // NOLINT uint32_t mantissa = x & ((1 << Wm) - 1); // NOLINT int exponent = (x & 0x7F) >> Wm; // NOLINT if(NegativeZeroNan) { if(x == 0x80) return f_nan; } else { if(x == 0x80) return f_neg0; if(exponent == ((1 << We) - 1) and Wm == 2) // NOLINT return (mantissa == 0) ? (sign ? f_neg_inf : f_inf) : f_nan; else if(Wm == 3 and (x == 0x7F or x == 0xFF)) return f_nan; } typename std::conditional::type retval; const int exp_low_cutoff = (1 << (weo - 1)) - (1 << (We - 1)) + 1 - (NegativeZeroNan ? 1 : 0); // NOLINT // subnormal input if(exponent == 0) { // guaranteed mantissa!=0 since cases 0x0 and 0x80 are handled above int sh = 1 + __builtin_clz(mantissa) - (32 - Wm); mantissa <<= sh; // NOLINT exponent += 1 - sh; mantissa &= ((1 << Wm) - 1); // NOLINT } exponent += exp_low_cutoff - 1; mantissa <<= wmo - Wm; // NOLINT // subnormal output (occurs when T=half, We=5, negative_zero_nan=true) if(exponent <= 0) { mantissa |= 1 << wmo; // NOLINT mantissa >>= 1 - exponent; // NOLINT exponent = 0; } if(sizeof(T) == 2) retval = (sign << 15) | (exponent << 10) | mantissa; // NOLINT else retval = (sign << 31) | (exponent << 23) | mantissa; // NOLINT return migraphx::bit_cast(retval); } } // namespace impl } // namespace fp8 } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_RTGLIB_FLOAT8_IMPL ROCm-AMDMIGraphX-46524e8/src/include/migraphx/float_equal.hpp000066400000000000000000000044311510465702400236000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_FLOAT_EQUAL_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_FLOAT_EQUAL_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template using common_type = typename std::common_type::type; struct float_equal_fn { template {})> static bool apply(T x, T y) { return std::isfinite(x) and std::isfinite(y) and std::nextafter(x, std::numeric_limits::lowest()) <= y and std::nextafter(x, std::numeric_limits::max()) >= y; } template {})> static bool apply(T x, T y) { return x == y; } template bool operator()(T x, U y) const { return float_equal_fn::apply>(x, y); } }; static constexpr float_equal_fn float_equal{}; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fp4_casts.hpp000066400000000000000000000102621510465702400231710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FLOAT4_CASTS_HPP #define MIGRAPHX_GUARD_RTGLIB_FLOAT4_CASTS_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fp4_detail { static constexpr std::array fp4_to_float_lut = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f, -0.0f, -0.5f, -1.0f, -1.5f, -2.0f, -3.0f, -4.0f, -6.0f}; // pair is {fp4_tie_value, round_to_zero}, index is the positive fp4 value // if round_to_zero round tie towards zero, else round tie away from zero static constexpr std::array, 7> float_to_fp4_lut = { {{0.25f, 1}, {0.75f, 0}, {1.25f, 1}, {1.75f, 0}, {2.5f, 1}, {3.5f, 0}, {5.0f, 1}}}; } // namespace fp4_detail // converts 4 LSB to float constexpr float fp4_to_float(uint8_t x) { return fp4_detail::fp4_to_float_lut[x % fp4_detail::fp4_to_float_lut.size()]; } // converts 4 LSB to fp8e4m3fn_type constexpr auto fp4_to_fp8(uint8_t x) { return migraphx::fp8::fp8e4m3fn( fp4_detail::fp4_to_float_lut[x % fp4_detail::fp4_to_float_lut.size()]); } // rounding mode = roundToNearestRoundTiesToEven // Reference quantization code from Microsoft: // https://github.com/microsoft/microxcaling/blob/main/mx/elemwise_ops.py#L82 // Not constexpr because of std::signbit and std::upper_bound not constexpr in C++17 template inline uint8_t cast_to_fp4(T x) { float f_x(x); using fp4_detail::float_to_fp4_lut; using fp4_detail::fp4_to_float_lut; if(std::isnan(f_x)) { return 0; } bool sign = std::signbit(f_x); uint8_t sign_add = sign ? fp4_to_float_lut.size() / 2 : 0u; float abs_f = std::abs(f_x); // index value is the positive fp4 value uint8_t i = std::upper_bound(float_to_fp4_lut.begin(), float_to_fp4_lut.end(), std::make_pair(abs_f, uint8_t{0})) - float_to_fp4_lut.begin(); return i + sign_add; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fp8_ocp_to_fnuz.hpp000066400000000000000000000040741510465702400244110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FP8_OCP_TO_FNUZ_HPP #define MIGRAPHX_GUARD_RTGLIB_FP8_OCP_TO_FNUZ_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * Convert fp8e4m3fn to fp8e4m3fnuz for hardware that only supports fp8e4m3fnuz data types * intrinsically. Conversion uses the same bit representation and adjusts scaling factors at the * dequantization. Using the same bit representation from fp8e4m3fn to fp8e4m3fnuz halves the * floating point representation. This pass should run before simplify_qdq so that the scales and * zero points calculated by simplify_qdq have the correct adjusted scaling factors */ struct MIGRAPHX_EXPORT fp8_ocp_to_fnuz { std::string name() const { return "fp8_ocp_to_fnuz"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fp8_types.hpp000066400000000000000000000034371510465702400232320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_FP8_TYPES_HPP #define MIGRAPHX_GUARD_MIGRAPHX_FP8_TYPES_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct fp8_types { const std::set types = {shape::fp8e4m3fnuz_type, shape::fp8e5m2fnuz_type, shape::fp8e4m3fn_type, shape::fp8e5m2_type}; std::set get() const { return types; } }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_FP8_TYPES_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fp_to_double.hpp000066400000000000000000000035731510465702400237530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FP_TO_DOUBLE_HPP #define MIGRAPHX_GUARD_RTGLIB_FP_TO_DOUBLE_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Convert floating point values to double precision. */ struct MIGRAPHX_EXPORT fp_to_double { std::set convert_fp_types = {shape::type_t::half_type, shape::type_t::float_type}; std::string name() const { return "fp_to_double"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/functional.hpp000066400000000000000000000135051510465702400234500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FUNCTIONAL_HPP #define MIGRAPHX_GUARD_RTGLIB_FUNCTIONAL_HPP #include #include #include // Lifts an expression into a function object so it can be passed to a higher-order function // NOLINTNEXTLINE #define MIGRAPHX_LIFT(...) \ [](auto&&... private_lifts_xs) MIGRAPHX_RETURNS( \ (__VA_ARGS__)(static_cast(private_lifts_xs)...)) namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct swallow { template constexpr swallow(Ts&&...) { } }; template auto tuple_size(const T&) { return typename std::tuple_size::type{}; } namespace detail { template struct fix_f { F f; template R operator()(Ts&&... xs) const { return f(*this, std::forward(xs)...); } }; template struct seq { using type = seq; }; template struct merge_seq; template struct merge_seq, seq> : seq { }; template struct gens : merge_seq::type, typename gens::type> { }; template <> struct gens<0> : seq<> { }; template <> struct gens<1> : seq<0> { }; template constexpr void repeat_c_impl(F f, seq) { swallow{(f(std::integral_constant{}), 0)...}; } template constexpr auto sequence_c_impl(F&& f, seq) { return f(std::integral_constant{}...); } } // namespace detail template constexpr void repeat_c(F f) { detail::repeat_c_impl(f, detail::gens{}); } template constexpr auto sequence_c(F&& f) { return detail::sequence_c_impl(f, detail::gens{}); } template constexpr auto sequence(IntegerConstant ic, F&& f) { return sequence_c(f); } template constexpr void each_args(F f, Ts&&... xs) { swallow{(f(std::forward(xs)), 0)...}; } template constexpr void each_args(F) { } template auto unpack(F f, T&& x) { return sequence(tuple_size(x), [&](auto... is) { f(std::get(static_cast(x))...); }); } /// Implements a fix-point combinator template detail::fix_f fix(F f) { return {f}; } template auto fix(F f) { return fix(f); } template auto fold_impl(F&&, T&& x) { return std::forward(x); } template auto fold_impl(F&& f, T&& x, U&& y, Ts&&... xs) { return fold_impl(f, f(std::forward(x), std::forward(y)), std::forward(xs)...); } template auto fold(F f) { return [=](auto&&... xs) { return fold_impl(f, std::forward(xs)...); }; } template auto pack(Ts... xs) { return [=](auto f) { return f(xs...); }; } inline auto pack_join() { return pack(); } template auto pack_join(Ps... ps) { return fold([](auto p1, auto p2) { return p1([=](auto... xs) { return p2([=](auto... ys) { return pack(xs..., ys...); }); }); })(ps...); } template auto by(F f, Proj proj) { return [=](auto&&... xs) { return f(proj(std::forward(xs))...); }; } template auto index_of(T& x) { return [&](auto&& y) { return x[y]; }; } template decltype(auto) front_args(T&& x, Ts&&...) { return static_cast(x); } template decltype(auto) back_args(Ts&&... xs) { return std::get(std::tuple(static_cast(xs)...)); } template auto pop_front_args(T&&, Ts&&... xs) { return [&](auto f) { f(static_cast(xs)...); }; } template auto pop_back_args(Ts&&... xs) { return [&](auto f) { using tuple_type = std::tuple; auto t = tuple_type(static_cast(xs)...); return sequence_c( [&](auto... is) { return f(std::get(static_cast(t))...); }); }; } template struct always_f { T x; template constexpr T operator()(Ts&&...) const { return x; } }; template auto always(T x) { return always_f{x}; } struct id { template constexpr T operator()(T&& x) const { return static_cast(x); } }; template void nop(Ts&&...) { } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fuse_attention.hpp000066400000000000000000000032361510465702400243350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_FUSE_ATTENTION_HPP #define MIGRAPHX_GUARD_MIGRAPHX_FUSE_ATTENTION_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; struct MIGRAPHX_EXPORT fuse_attention { std::string name() const { return "fuse_attention"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_FUSE_ATTENTION_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fuse_concat.hpp000066400000000000000000000032141510465702400235730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_FUSE_CONCAT_HPP #define MIGRAPHX_GUARD_MIGRAPHX_FUSE_CONCAT_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; struct MIGRAPHX_EXPORT fuse_concat { std::string name() const { return "fuse_concat"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_FUSE_CONCAT_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fuse_pointwise.hpp000066400000000000000000000034351510465702400243520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_FUSE_POINTWISE_HPP #define MIGRAPHX_GUARD_MIGRAPHX_FUSE_POINTWISE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; struct MIGRAPHX_EXPORT fuse_pointwise { std::string name() const { return "fuse_pointwise"; } void apply(module_pass_manager& mpm) const; bool enable_rewrite_reshapes = true; bool enable_rewrite_broadcasts = false; bool enable_multi_output = false; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_FUSE_POINTWISE_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fuse_pointwise_reduce.hpp000066400000000000000000000033451510465702400257010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_FUSE_POINTWISE_REDUCE_HPP #define MIGRAPHX_GUARD_MIGRAPHX_FUSE_POINTWISE_REDUCE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; struct MIGRAPHX_EXPORT fuse_pointwise_reduce { std::size_t split_size = 32768; std::string name() const { return "fuse_pointwise_reduce"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_FUSE_POINTWISE_REDUCE_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/fuse_reduce.hpp000066400000000000000000000032711510465702400235760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_FUSE_REDUCE_HPP #define MIGRAPHX_GUARD_MIGRAPHX_FUSE_REDUCE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; struct MIGRAPHX_EXPORT fuse_reduce { std::string name() const { return "fuse_reduce"; } void apply(module_pass_manager& mpm) const; bool enable_rewrite_reshapes = true; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_FUSE_POINTWISE_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/gemm.hpp000066400000000000000000000050551510465702400222340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GEMM_HPP #define MIGRAPHX_GUARD_RTGLIB_GEMM_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template void gemm(tensor_view cmat, tensor_view amat, tensor_view bmat, F alpha, F beta) { std::size_t n_dims = cmat.get_shape().lens().size(); std::size_t dim_0 = n_dims - 2; std::size_t dim_1 = n_dims - 1; auto k = amat.get_shape().lens()[dim_1]; assert(amat.get_shape().lens()[dim_1] == bmat.get_shape().lens()[dim_0]); assert(cmat.get_shape().lens()[dim_0] == amat.get_shape().lens()[dim_0]); assert(cmat.get_shape().lens()[dim_1] == bmat.get_shape().lens()[dim_1]); auto cs = cmat.get_shape(); par_for(cs.elements(), [&](auto i) { auto c_idx = cs.multi(i); auto a_idx = c_idx; auto b_idx = c_idx; double s = 0.0; dfor(k)([&](auto kk) { a_idx[dim_1] = b_idx[dim_0] = kk; s += static_cast(amat(a_idx.begin(), a_idx.end())) * static_cast(bmat(b_idx.begin(), b_idx.end())); }); cmat(c_idx.begin(), c_idx.end()) = alpha * s + cmat(c_idx.begin(), c_idx.end()) * beta; }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/generate.hpp000066400000000000000000000114431510465702400230770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_GENERATE_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_GENERATE_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { enum class random_mode { legacy, random }; template {})> constexpr T normalize(unsigned long z, random_mode m) { auto max = (m == random_mode::legacy) ? 32 : 1ULL << (sizeof(T) * 8 - 1); const double range = max / 2.0; double result = -1.0 + (z % max) / range; // Expected output: between -1.0 and 1.0 return T(result); } template {} and not is_floating_point{})> constexpr T normalize(unsigned long z, random_mode m) { const long long max = (m == random_mode::legacy) ? 1ULL << (sizeof(T) * 5) : 1ULL << (sizeof(T) * 6 - 1); const auto half_max = max / 2; auto result = half_max - (z % max); // Expected output: between -half_max and half_max return T(result); } template {} and std::is_integral{} and not std::is_same{})> constexpr T normalize(unsigned long z, random_mode m) { const auto max = (m == random_mode::legacy) ? 1ULL << (sizeof(T) * 5) : 1ULL << (sizeof(T) * 8 - 1); // Expected output: between 0 and max - 1 return z % max; } template {})> constexpr bool normalize(unsigned long z, random_mode) { // Expected output: 0 or 1b return static_cast(z % 2); } template struct xorshf96_generator { unsigned long x = 123456789; unsigned long y = 362436069; unsigned long z; random_mode mode; xorshf96_generator(unsigned long seed, random_mode m) : z(521288629ULL ^ seed), mode(m) {} constexpr T operator()() noexcept { x ^= x << 16U; x ^= x >> 5U; x ^= x << 1U; unsigned long t = x; x = y; y = z; z = t ^ x ^ y; return normalize(z, mode); } }; template struct xorshift_generator { unsigned long x; random_mode mode; xorshift_generator(unsigned long seed, random_mode m) : x(521288629ULL ^ seed), mode(m) {} constexpr T operator()() noexcept { x ^= x >> 12U; x ^= x << 25U; x ^= x >> 27U; return normalize(x * 0x2545F4914F6CDD1D, mode); } }; template auto generate_tensor_data(const migraphx::shape& s, unsigned long seed, random_mode m = random_mode::legacy) { auto result = make_shared_array(s.element_space()); std::generate(result.get(), result.get() + s.element_space(), xorshf96_generator{seed, m}); return result; } template auto fill_tensor_data(const migraphx::shape& s, double value = 0) { auto result = make_shared_array(s.element_space()); std::generate(result.get(), result.get() + s.element_space(), [=] { return value; }); return result; } MIGRAPHX_EXPORT argument fill_argument(shape s, double value = 0); MIGRAPHX_EXPORT argument generate_argument(shape s, unsigned long seed = 0, random_mode m = random_mode::legacy); MIGRAPHX_EXPORT literal generate_literal(shape s, unsigned long seed = 0); MIGRAPHX_EXPORT literal abs(literal l); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/generic_float.hpp000066400000000000000000000325271510465702400241140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_GENERIC_FLOAT_HPP #define MIGRAPHX_GUARD_MIGRAPHX_GENERIC_FLOAT_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { constexpr std::size_t integer_divide_ceil(std::size_t x, std::size_t y) { return (x + y - std::size_t{1}) / y; } template struct unsigned_type { }; template <> struct unsigned_type<1> { using type = std::uint8_t; }; template <> struct unsigned_type<2> { using type = std::uint16_t; }; template <> struct unsigned_type<4> { using type = std::uint32_t; }; template <> struct unsigned_type<8> { using type = std::uint64_t; }; struct float32_parts { unsigned int mantissa : 23; unsigned int exponent : 8; unsigned int sign : 1; static constexpr unsigned int exponent_width() { return 8; } static constexpr unsigned int mantissa_width() { return 23; } static constexpr unsigned int max_exponent() { return all_ones<8>(); } static constexpr int exponent_bias() { return all_ones<7>(); } constexpr float to_float() const noexcept { return migraphx::bit_cast(*this); } }; constexpr float32_parts get_parts(float f) { return migraphx::bit_cast(f); } template struct __attribute__((packed, may_alias)) generic_float { using type = typename unsigned_type::type; type mantissa : MantissaSize; type exponent : ExponentSize; type sign : 1; static constexpr int exponent_bias() { return all_ones(); } explicit constexpr generic_float(float f = 0.0) noexcept { from_float(get_parts(f)); } constexpr generic_float& operator=(float f) noexcept { from_float(get_parts(f)); return *this; } constexpr generic_float operator-() const noexcept { generic_float result = *this; result.sign = not this->sign; return result; } constexpr generic_float operator+() const noexcept { return *this; } constexpr float to_float() const noexcept { float32_parts f{}; f.sign = sign; if(exponent == 0 and ExponentSize != float32_parts::exponent_width()) // subnormal fps { if(mantissa == 0) { f.exponent = 0; f.mantissa = 0; } else { type shift = 0; f.mantissa = mantissa; if(MantissaSize < float32_parts::mantissa_width()) { shift = MantissaSize - ((sizeof(type) * 8) - countl_zero(mantissa)); f.mantissa <<= (shift + 1u); } f.exponent = float32_parts::exponent_bias() - exponent_bias() - shift; f.mantissa = f.mantissa << (float32_parts::mantissa_width() - MantissaSize); } } else if(exponent == all_ones()) { f.mantissa = mantissa << (float32_parts::mantissa_width() - MantissaSize); f.exponent = float32_parts::max_exponent(); } else { f.mantissa = mantissa << (float32_parts::mantissa_width() - MantissaSize); constexpr const int diff = float32_parts::exponent_bias() - exponent_bias(); f.exponent = int(exponent) + diff; } return f.to_float(); } constexpr void from_float(float32_parts f) noexcept { sign = f.sign; if(f.exponent == 0) { exponent = 0; mantissa = f.mantissa >> (float32_parts::mantissa_width() - MantissaSize); } else if(f.exponent == float32_parts::max_exponent()) { exponent = all_ones(); mantissa = f.mantissa >> (float32_parts::mantissa_width() - MantissaSize); } else { constexpr const int diff = float32_parts::exponent_bias() - exponent_bias(); auto e = int(f.exponent) - diff; if(e >= static_cast(all_ones())) { exponent = all_ones(); mantissa = 0; } else if(e < 1) { exponent = 0; auto shift = diff - int(f.exponent); auto shift_amount = shift + (float32_parts::mantissa_width() - MantissaSize) + 1; if(shift_amount < (sizeof(unsigned int) * 8)) { mantissa = (f.mantissa | (1u << float32_parts::mantissa_width())) >> (shift + (float32_parts::mantissa_width() - MantissaSize) + 1); } else { mantissa = 0; } } else { exponent = int(f.exponent) - diff; mantissa = f.mantissa >> (float32_parts::mantissa_width() - MantissaSize); } } exponent = std::min(exponent, all_ones()); } constexpr bool is_normal() const noexcept { return exponent != all_ones() and exponent != 0; } constexpr bool is_inf() const noexcept { return exponent == all_ones() and mantissa == 0; } constexpr bool is_nan() const noexcept { return exponent == all_ones() and mantissa != 0; } constexpr bool is_finite() const noexcept { return exponent != all_ones(); } constexpr operator float() const noexcept { return this->to_float(); } static constexpr generic_float infinity() { generic_float x{}; x.exponent = all_ones(); return x; } static constexpr generic_float snan() { generic_float x{}; x.exponent = all_ones(); x.mantissa = 1u << (MantissaSize - 2u); return x; } static constexpr generic_float qnan() { generic_float x{}; x.exponent = all_ones(); x.mantissa = 1u << (MantissaSize - 1u); return x; } static constexpr generic_float min() { generic_float x{}; x.exponent = 1; x.mantissa = 0; return x; } static constexpr generic_float denorm_min() { generic_float x{}; x.exponent = 0; x.mantissa = 1; x.sign = 0; return x; } static constexpr generic_float lowest() { generic_float x{}; x.exponent = all_ones() - 1; x.mantissa = all_ones(); x.sign = 1; return x; } static constexpr generic_float max() { generic_float x{}; x.exponent = all_ones() - 1; x.mantissa = all_ones(); x.sign = 0; return x; } static constexpr generic_float epsilon() { generic_float x{1.0}; x.mantissa++; return generic_float{x.to_float() - 1.0f}; } // NOLINTNEXTLINE #define MIGRAPHX_GENERIC_FLOAT_ASSIGN_OP(op) \ constexpr generic_float& operator op(const generic_float & rhs) \ { \ float self = *this; \ float frhs = rhs; \ self op frhs; \ *this = generic_float(self); \ return *this; \ } MIGRAPHX_GENERIC_FLOAT_ASSIGN_OP(*=) MIGRAPHX_GENERIC_FLOAT_ASSIGN_OP(-=) MIGRAPHX_GENERIC_FLOAT_ASSIGN_OP(+=) MIGRAPHX_GENERIC_FLOAT_ASSIGN_OP(/=) // NOLINTNEXTLINE #define MIGRAPHX_GENERIC_FLOAT_BINARY_OP(op) \ friend constexpr generic_float operator op(const generic_float& x, const generic_float& y) \ { \ return generic_float(float(x) op float(y)); \ } MIGRAPHX_GENERIC_FLOAT_BINARY_OP(*) MIGRAPHX_GENERIC_FLOAT_BINARY_OP(-) MIGRAPHX_GENERIC_FLOAT_BINARY_OP(+) MIGRAPHX_GENERIC_FLOAT_BINARY_OP(/) // NOLINTNEXTLINE #define MIGRAPHX_GENERIC_FLOAT_COMPARE_OP(op) \ friend constexpr bool operator op(const generic_float& x, const generic_float& y) \ { \ return float(x) op float(y); \ } MIGRAPHX_GENERIC_FLOAT_COMPARE_OP(<) MIGRAPHX_GENERIC_FLOAT_COMPARE_OP(<=) MIGRAPHX_GENERIC_FLOAT_COMPARE_OP(>) MIGRAPHX_GENERIC_FLOAT_COMPARE_OP(>=) friend constexpr bool operator==(const generic_float& x, const generic_float& y) { if(not x.is_finite() or not y.is_finite()) return false; if((x.mantissa == 0 and x.exponent == 0) and (y.mantissa == 0 and y.exponent == 0)) { return true; } return std::tie(x.mantissa, x.exponent, x.sign) == std::tie(y.mantissa, y.exponent, y.sign); } friend constexpr bool operator!=(const generic_float& x, const generic_float& y) { return not(x == y); } constexpr generic_float& operator++() noexcept { *this += generic_float(1.0f); return *this; } const generic_float operator++(int) noexcept // NOLINT(readability-const-return-type) { generic_float temp = *this; *this += generic_float(1.0f); return temp; } }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx // NOLINTBEGIN(cert-dcl58-cpp) namespace std { template class numeric_limits> { public: static constexpr bool has_infinity = true; static constexpr migraphx::generic_float epsilon() { return migraphx::generic_float::epsilon(); } static constexpr migraphx::generic_float quiet_NaN() { return migraphx::generic_float::qnan(); } static constexpr migraphx::generic_float signaling_NaN() { return migraphx::generic_float::snan(); } static constexpr migraphx::generic_float max() { return migraphx::generic_float::max(); } static constexpr migraphx::generic_float min() { return migraphx::generic_float::min(); } static constexpr migraphx::generic_float lowest() { return migraphx::generic_float::lowest(); } static constexpr migraphx::generic_float infinity() { return migraphx::generic_float::infinity(); } static constexpr migraphx::generic_float denorm_min() { return migraphx::generic_float::denorm_min(); } }; template struct common_type, T> : std::common_type { }; template struct common_type> : std::common_type { }; template struct common_type, migraphx::generic_float> { using type = migraphx::generic_float; }; template struct common_type, migraphx::generic_float> { using type = float; }; } // namespace std // NOLINTEND(cert-dcl58-cpp) #endif // MIGRAPHX_GUARD_MIGRAPHX_GENERIC_FLOAT_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/graphviz.hpp000066400000000000000000000107321510465702400231370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_UTILS_GRAPHVIZ_HPP #define MIGRAPHX_GUARD_UTILS_GRAPHVIZ_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace graphviz { /* Operation color map: binary: #000000 , black (w/white font) unary: #CD5C5C , indianred convolution: #4682B4 , steelblue load: #1E90FF, dodger blue broadcast: #9ACD32, yellowgreen pointwise: #9ACD32, yellowgreen slice: #FFA500, orange gpu::code_object_op: #E9D66B, arylideyellow pooling: #3CB371, mediumseagreen reduce: #8470FF, lightslateblue To add new colors for operations, add attributes to the value yourOp::attributes() const {...} member of your selected operation. See the graphviz_node_style struct for naming. For example: value rocm_op::attributes() const {{"fillcolor", "#EF0707"}} will set the fillcolor for the rocm_op node to be hex #ef0707 */ /** * Struct for html-style table parameters created with * ...
*/ struct html_table_style { int border = 0; int cellborder = 0; int cellpadding = 4; int cellspacing = 0; }; /** * Struct for tracking graphviz node style using default * values for nodes with no attributes */ struct graphviz_node_style { std::string fillcolor = ""; // defaults to white std::string fontcolor = "#000000"; // black std::string style = "rounded,filled"; std::string shape = "none"; std::string fontname = "Helvetica"; std::string bordercolor = ""; // defaults to none when shape is none }; /** * Struct for storing all content and style settings * for a graphviz node. Stores title, lines of the body * text, perf_data (to be implemented), and above style * structs */ struct graphviz_node_content { std::string title; std::vector body_lines; std::optional> perf_data; html_table_style html_style{}; graphviz_node_style node_style{}; }; /** * Escape all quotes in string */ std::string enclose_name(const std::string& name); /** * Formats migraphx::shape for printing so we dont have to use the * migraphx::shape::operator<< which places content on one line */ std::string format_shape_name(const migraphx::shape& s, const std::string& linebreak = "\\n"); /** * Builds html-style table for content, given a graphviz_node_content struct */ std::string build_html_label(const graphviz_node_content& content); /** * Builds the node style string given a graphviz_node_style struct */ std::string build_node_style(const graphviz_node_style& node_style); /** * Given an instruction_ref ins we build a graphviz_node_content object to store * all of our necessary data. In this function we do formatting for specific * instructions: param, literal, and gpu::code_object */ graphviz_node_content get_node_content(const instruction_ref& ins); /** * Given an instruction_ref ins, determine the coloring based on alias and * context-free qualities */ std::string get_graph_color(const instruction_ref& ins); } // namespace graphviz } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/half.hpp000066400000000000000000000032171510465702400222170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_HALF_HPP #define MIGRAPHX_GUARD_RTGLIB_HALF_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { using half = migraphx::generic_float<10, 5>; namespace detail { template struct deduce { using type = T; }; } // namespace detail template using deduce = typename detail::deduce::type; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/hash.hpp000066400000000000000000000033161510465702400222300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_HASH_HPP #define MIGRAPHX_GUARD_MIGRAPHX_HASH_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template auto hash_value(const T& v) -> decltype(std::hash{}(v)) { return std::hash{}(v); } template void hash_combine(std::size_t& seed, const T& v) { seed ^= hash_value(v) + 0x9e3779b9 + (seed << 6u) + (seed >> 2u); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_HASH_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/inline_module.hpp000066400000000000000000000031621510465702400241270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_INLINE_MODULE_HPP #define MIGRAPHX_GUARD_RTGLIB_INLINE_MODULE_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct MIGRAPHX_EXPORT inline_module { std::string name() const { return "inline_module"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/insert_pad.hpp000066400000000000000000000034621510465702400234370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_INSERT_PAD_HPP #define MIGRAPHX_GUARD_RTGLIB_INSERT_PAD_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * insert pads if attribute of padding is asymmetrical */ struct MIGRAPHX_EXPORT insert_pad { std::unordered_set ops = {"convolution", "pooling", "im2col"}; std::string name() const { return "insert_pad"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/instruction.hpp000066400000000000000000000152701510465702400236700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_INSTRUCTION_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_INSTRUCTION_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT shape compute_shape(const operation& op, const std::vector& args); MIGRAPHX_EXPORT shape compute_shape(const operation& op, const std::vector& args, const std::vector& mods); MIGRAPHX_EXPORT std::vector to_shapes(const std::vector& args); MIGRAPHX_EXPORT std::vector try_compute_shape(const operation& op, const std::vector& inputs); MIGRAPHX_EXPORT bool reaches(instruction_ref start, instruction_ref end); MIGRAPHX_EXPORT bool reaches(instruction_ref start, instruction_ref end, const_module_ref m); MIGRAPHX_EXPORT bool is_interdependent(const std::vector& instructions, const_module_ref m, instruction_ref root); MIGRAPHX_EXPORT std::unordered_set find_instructions_between(instruction_ref start, instruction_ref end, const_module_ref m); struct MIGRAPHX_EXPORT instruction { instruction() {} instruction(operation o, shape r, std::vector args); instruction(operation o, shape r, std::vector args, std::vector modules); instruction(literal l); void replace(operation o); void recompute_shape(); void clear_arguments(); MIGRAPHX_EXPORT friend bool operator==(const instruction& i, instruction_ref ref); bool valid(instruction_ref start, bool check_order = false) const; bool valid() const; const shape& get_shape() const; const literal& get_literal() const; const operation& get_operator() const; std::string name() const; const std::vector& inputs() const; const std::vector& module_inputs() const; /// Where this instruction is used as an input to another instruction const std::vector& outputs() const; MIGRAPHX_EXPORT friend bool operator==(const instruction& x, const instruction& y); MIGRAPHX_EXPORT friend bool operator!=(const instruction& x, const instruction& y); MIGRAPHX_EXPORT friend bool operator==(instruction_ref ref, const instruction& i); MIGRAPHX_EXPORT friend bool operator!=(const instruction& i, instruction_ref ref); MIGRAPHX_EXPORT friend bool operator!=(instruction_ref ref, const instruction& i); void add_output(instruction_ref ins); template void remove_output(const T& ins) { migraphx::erase(output, ins); } static void replace_refs(instruction_ref ins, const std::unordered_map& map_insts, const std::unordered_map& map_mods); static void backreference(instruction_ref ref); static void replace_argument(instruction_ref ins, instruction_ref old, instruction_ref new_ins); static void replace_mod_argument(instruction_ref ins, module_ref old, module_ref new_mod); static void replace(instruction_ref ins, operation o, const shape& r, std::vector args); static void replace(instruction_ref ins, operation o, const shape& r, std::vector args, std::vector module_args); bool can_eval() const; bool is_undefined() const; argument eval(bool check_eval = true) const; void finalize(context& ctx); static instruction_ref get_output_alias(instruction_ref ins, bool shallow = false); void set_normalized(bool value = true); bool is_normalized() const; bool need_normalization() const; operation normalized_operator() const; std::size_t get_target_id() const; void set_target_id(std::size_t tid); void debug_print() const; static void print(std::ostream& os, instruction_ref ins, const std::unordered_map& names); private: // internal void replace(operation o, const shape& r, std::vector args); // internal void replace(operation o, const shape& r, std::vector args, std::vector mdl_args); // internal void replace(std::vector args); // internal void replace(std::vector args, std::vector mdl_args); // internal void replace_argument(instruction_ref old, instruction_ref new_ins); // internal void replace_mod_argument(module_ref old, module_ref new_mod); void replace(const shape& r); operation op; shape result{}; std::vector output; std::vector arguments; std::vector module_args; literal lit; bool normalized = false; std::size_t target_id = 0; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/instruction_ref.hpp000066400000000000000000000063031510465702400245210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_INSTRUCTION_REF_HPP #define MIGRAPHX_GUARD_INSTRUCTION_REF_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct instruction; MIGRAPHX_EXPORT migraphx::instruction* as_address(const std::list::iterator& ins) noexcept; MIGRAPHX_EXPORT const migraphx::instruction* as_address(const std::list::const_iterator& ins) noexcept; #if defined(CPPCHECK) using instruction_ref = std::list::iterator; #else struct instruction_ref : std::list::iterator { using instruction_iter = std::list::iterator; using instruction_const_iter = std::list::const_iterator; instruction_ref() = default; instruction_ref(const instruction_iter& other) : instruction_iter(other) {} template {} or std::is_same{})> friend auto operator==(const T& x, const U& y) -> decltype(bool(as_address(x) == as_address(y))) { return as_address(x) == as_address(y); } template {} or std::is_same{})> friend auto operator!=(const T& x, const U& y) -> decltype(bool(as_address(x) != as_address(y))) { return as_address(x) != as_address(y); } }; #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx namespace std { template <> struct hash { using argument_type = migraphx::instruction_ref; using result_type = std::size_t; result_type operator()(const migraphx::instruction_ref& x) const noexcept { return std::hash{}(migraphx::as_address(x)); } }; } // namespace std #ifdef _MSC_VER #include #endif #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/instruction_traversal.hpp000066400000000000000000000035241510465702400257520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_INSTRUCTION_TRAVERSAL_HPP #define MIGRAPHX_GUARD_MIGRAPHX_INSTRUCTION_TRAVERSAL_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { inline auto get_output_path(instruction_ref ins) { return unfold(ins, [](instruction_ref out) -> std::optional { if(out->outputs().size() != 1) return std::nullopt; return out->outputs().front(); }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_INSTRUCTION_TRAVERSAL_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/iota_iterator.hpp000066400000000000000000000063501510465702400241530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_IOTA_ITERATOR_HPP #define MIGRAPHX_GUARD_RTGLIB_IOTA_ITERATOR_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct basic_iota_iterator : iterator_operators>, iterator_types()(std::declval())), std::random_access_iterator_tag> { Iterator index; F f; using reference = decltype(std::declval()(std::declval())); // cppcheck-suppress uninitMemberVar constexpr basic_iota_iterator() = default; template constexpr basic_iota_iterator(Iterator i, Ts&&... xs) : index(i), f{std::forward(xs)...} { } constexpr basic_iota_iterator::reference operator*() const { return f(index); } template static constexpr auto increment(U& x) -> decltype(++x.index) { return ++x.index; } template static constexpr auto decrement(U& x) -> decltype(--x.index) { return --x.index; } template static constexpr auto advance(U& x, I n) -> decltype(x.index += n) { return x.index += n; } template static constexpr auto distance(const U& x, const V& y) -> decltype(y.index - x.index) { return y.index - x.index; } template static constexpr auto equal(const U& x, const V& y) -> decltype(x.index == y.index) { return x.index == y.index; } template friend Stream& operator<<(Stream& s, const basic_iota_iterator& x) { return s << x.index; } }; template basic_iota_iterator make_basic_iota_iterator(T x, F f) { return {x, f}; } using iota_iterator = basic_iota_iterator; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/iterator.hpp000066400000000000000000000167471510465702400231520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_ITERATOR_HPP #define MIGRAPHX_GUARD_MIGRAPHX_ITERATOR_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template auto is_end(rank<2>, Iterator it, EndIterator) -> decltype(not it._M_dereferenceable()) { return not it._M_dereferenceable(); } template auto is_end(rank<1>, Iterator it, EndIterator last) { return it == last; } template bool is_end(Iterator it, EndIterator last) { return is_end(rank<2>{}, it, last); } template auto* iterator_address(rank<0>, Iterator it) { return std::addressof(*it); } template auto iterator_address(rank<1>, Iterator it) -> decltype(it._M_dereferenceable() ? std::addressof(*it) : nullptr) { return it._M_dereferenceable() ? std::addressof(*it) : nullptr; } template auto iterator_address(rank<1>, Iterator it) -> decltype(std::addressof(it._Unwrapped()._Ptr->_Myval), std::addressof(*it)) { return it._Unwrapped()._Ptr ? std::addressof(it._Unwrapped()._Ptr->_Myval) : nullptr; } template auto* iterator_address(Iterator it) { return iterator_address(rank<1>{}, it); } template struct arrow_proxy { T x; constexpr auto operator->() const { return std::addressof(x); } }; template arrow_proxy(T&&) -> arrow_proxy; template struct iterator_operators { // Core operators // Increment template , MIGRAPHX_REQUIRES(std::is_same{})> friend constexpr auto operator++(U&& x) -> decltype(Self::increment(x), static_cast(x)) { Self::increment(x); return static_cast(x); } // Decrement template , MIGRAPHX_REQUIRES(std::is_same{})> friend constexpr auto operator--(U&& x) -> decltype(Self::decrement(x), static_cast(x)) { Self::decrement(x); return static_cast(x); } // Advance template {})> friend constexpr auto operator+=(U& x, I n) -> decltype(U::advance(x, n), std::declval()) { U::advance(x, n); return x; } // Distance template friend constexpr auto operator-(const U& x, const T& y) -> decltype(Self::distance(y, x)) { return Self::distance(y, x); } // Equal template friend constexpr auto operator==(const T& x, const U& y) -> decltype(Self::equal(x, y)) { return Self::equal(x, y); } template friend constexpr auto operator<(const T& x, const U& y) -> decltype(static_cast((x - y) < 0)) { return static_cast((x - y) < 0); } template friend constexpr auto operator<=(const T& x, const U& y) -> decltype(not static_cast(x > y)) { return not static_cast(x > y); } template friend constexpr auto operator>=(const T& x, const U& y) -> decltype(not static_cast(x < y)) { return not static_cast(x < y); } template friend constexpr auto operator>(const T& x, const U& y) -> decltype(y < x) { return y < x; } template friend constexpr auto operator!=(const T& x, const U& y) -> decltype(not static_cast(x == y)) { return not static_cast(x == y); } template friend constexpr auto operator+(T lhs, const U& rhs) -> decltype(T(lhs += rhs)) { return lhs += rhs; } template {})> friend constexpr auto operator+(const U& lhs, T rhs) -> decltype(T(rhs += lhs)) { return rhs += lhs; } template friend constexpr auto operator-(T lhs, const U& rhs) -> decltype(T(lhs += -rhs)) { return lhs += -rhs; } template friend constexpr auto operator-=(T& lhs, const U& rhs) -> decltype(lhs += -rhs) { return lhs += -rhs; } template friend constexpr auto operator++(U& x, int) -> decltype(T(++std::declval())) { T nrv(x); ++x; return nrv; } template friend constexpr auto operator--(U& x, int) -> decltype(T(--std::declval())) { T nrv(x); --x; return nrv; } template constexpr auto operator[](I n) const -> decltype(*(static_cast(*this) + n)) { auto it = static_cast(*this) + n; // cppcheck-suppress migraphx-ConditionalAssert if constexpr(std::is_reference{}) { // Ensure that result is not an internal reference assert((bit_cast(&*it) < bit_cast(&it) or bit_cast(&*it) > bit_cast(&it) + sizeof(U)) and "Random access iterator cannot return internal reference"); } return *it; } template constexpr auto operator->() const -> decltype(arrow_proxy{*static_cast(*this)}) { return arrow_proxy{*static_cast(*this)}; } }; template struct iterator_types { using reference = T; using iterator_category = Category; using difference_type = std::ptrdiff_t; using value_type = std::decay_t; using pointer = std::add_pointer_t>; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_ITERATOR_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/iterator_for.hpp000066400000000000000000000065231510465702400240070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ITERATOR_FOR_HPP #define MIGRAPHX_GUARD_RTGLIB_ITERATOR_FOR_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct iterator_for_select { template static T deref(T x) { return x; } template static auto begin(T* x) { return x->begin(); } template static auto end(T* x) { return x->end(); } }; struct iterator_for_select_reverse { template static auto deref(T x) { return std::prev(x.base()); } template static auto begin(T* x) { return std::make_reverse_iterator(x->end()); } template static auto end(T* x) { return std::make_reverse_iterator(x->begin()); } }; template struct iterator_for_range { T* base; using base_iterator = std::remove_reference_t; struct iterator { using difference_type = std::ptrdiff_t; using reference = decltype(std::declval()); using value_type = std::remove_reference_t; using pointer = std::add_pointer_t; using iterator_category = std::input_iterator_tag; base_iterator i; auto operator*() const { return Selector::deref(i); } base_iterator operator++() { return ++i; } bool operator==(const iterator& rhs) const { return i == rhs.i; } bool operator!=(const iterator& rhs) const { return i != rhs.i; } }; iterator begin() const { assert(base != nullptr); return {Selector::begin(base)}; } iterator end() const { assert(base != nullptr); return {Selector::end(base)}; } }; template iterator_for_range iterator_for(T& x) { return {&x}; } template iterator_for_range reverse_iterator_for(T& x) { return {&x}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/json.hpp000066400000000000000000000033501510465702400222540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_JSON_HPP #define MIGRAPHX_GUARD_RTGLIB_JSON_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT std::string to_pretty_json_string(const value& val, std::size_t indent = 4); MIGRAPHX_EXPORT std::string to_json_string(const value& val); MIGRAPHX_EXPORT value from_json_string(const std::string& str); MIGRAPHX_EXPORT value from_json_string(const char* str, std::size_t size); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/layout_convolution.hpp000066400000000000000000000034401510465702400252570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_LAYOUT_CONVOLUTION_HPP #define MIGRAPHX_GUARD_MIGRAPHX_LAYOUT_CONVOLUTION_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; /** * Transform convolutions layout */ struct MIGRAPHX_EXPORT layout_convolution { bool channels_last = false; std::string name() const { return "layout_convolution"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_LAYOUT_CONVOLUTION_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/lexing.hpp000066400000000000000000000042151510465702400225720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_LEXING_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_LEXING_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { using lexer = std::function; template inline auto lex_while(P p) { return [=](const char* start, const char* end) { return std::find_if(start, end, [&](char c) { return not p(c); }); }; } template inline auto lex_if(P p) { return [=](const char* start, const char*) { if(p(*start)) return start + 1; return start; }; } MIGRAPHX_EXPORT std::function lex_equal(const std::string& s); MIGRAPHX_EXPORT std::vector tokenize(const char* start, const char* end, const std::vector& lexers); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/lifetime.hpp000066400000000000000000000030001510465702400230710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_LIFETIME_HPP #define MIGRAPHX_GUARD_MIGRAPHX_LIFETIME_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { enum class lifetime { local, global, borrow }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_LIFETIME_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/literal.hpp000066400000000000000000000122771510465702400227470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_LITERAL_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_LITERAL_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * @brief Represents a raw literal * @details This stores the literal has a raw buffer that is owned by this class */ struct literal : raw_data { literal() {} /*! * Empty literal with a specific shape type */ explicit literal(shape::type_t shape_type) : m_shape(shape_type, {}) {} template , shape::type_t ShapeType = shape::get_type{}> literal(U x) : buffer(make_shared_array(sizeof(T))), m_shape(ShapeType) { static_assert(std::is_trivially_copyable{}, "Literals can only be trivial types"); *(reinterpret_cast(buffer.get())) = x; } template literal(const shape& s, const std::vector& x) : buffer(make_shared_array(s.bytes())), m_shape(s) { static_assert(std::is_trivially_copyable{}, "Literals can only be trivial types"); fill(x.begin(), x.end()); } template literal(const shape& s, const std::initializer_list& x) : buffer(make_shared_array(s.bytes())), m_shape(s) { static_assert(std::is_trivially_copyable{}, "Literals can only be trivial types"); fill(x.begin(), x.end()); } template literal(const shape& s, Iterator start, Iterator end) : buffer(make_shared_array(s.bytes())), m_shape(s) { fill(start, end); } // Directly copies buffer of x template literal(const shape& s, T* x) : buffer(make_shared_array(s.bytes())), m_shape(s) { std::copy(x, x + s.bytes(), buffer.get()); } /// Whether data is available bool empty() const { return this->buffer == nullptr; } /// Provides a raw pointer to the data const char* data() const { return this->buffer.get(); } const shape& get_shape() const { return this->m_shape; } std::vector get_sub_objects() const { return {}; } /// Convert the data to an argument argument get_argument() const { auto b = make_shared_array(buffer.get(), buffer.get() + m_shape.bytes()); return {m_shape, [b]() { return b.get(); }}; } private: std::shared_ptr buffer; shape m_shape; // Keeps the same data ordering as the given container template void fill(Iterator start, Iterator end) { assert(std::distance(start, end) == m_shape.elements()); m_shape.visit_type([&](auto as) { auto output = make_view(m_shape, as.from(buffer.get())); std::copy(start, end, output.begin()); }); } }; template literal transform(literal l, F f) { literal result; l.visit([&](auto x) { using type = std::remove_cv_t; std::vector output(x.size(), type(0)); std::transform(x.begin(), x.end(), output.begin(), f); result = literal{l.get_shape(), output}; }); return result; } template literal transform(literal l1, literal l2, F f) { assert(l1.get_shape() == l2.get_shape()); literal result; visit_all(l1, l2)([&](auto x, auto y) { using type = std::remove_cv_t; std::vector output(x.size(), type(0)); std::transform(x.begin(), x.end(), y.begin(), output.begin(), f); result = literal{l1.get_shape(), output}; }); return result; } MIGRAPHX_EXPORT void migraphx_to_value(value& v, const literal& l); MIGRAPHX_EXPORT void migraphx_from_value(const value& v, literal& l); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/liveness.hpp000066400000000000000000000054771510465702400231470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_LIVENESS_HPP #define MIGRAPHX_GUARD_MIGRAPHX_LIVENESS_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // This will do liveness analysis on the module, and it will call the // function `f` with the instruction and the set of the other instructions // that are live template void liveness(const module& m, F f) { auto implicit_deps = m.calc_implicit_deps(); std::unordered_set live_set; auto rp = reverse(m); for(auto rins : iterator_for(rp)) // NOLINT { // The base iterator is one ahead, so we need to use the previous iterator auto ins = std::prev(rins.base()); // Add live variables auto add_live_variables = [&](const auto& inputs) { for(auto input : inputs) { auto i = instruction::get_output_alias(input); // Skip if variable comes from parent if(not m.has_instruction(i)) continue; live_set.insert(i); } }; add_live_variables(ins->inputs()); add_live_variables(implicit_deps[ins]); // Remove last usage auto it = live_set.find(ins); if(it != live_set.end()) { live_set.erase(it); f(ins, live_set); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_LIVENESS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/load_save.hpp000066400000000000000000000043641510465702400232460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_LOAD_SAVE_HPP #define MIGRAPHX_GUARD_RTGLIB_LOAD_SAVE_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct file_options { std::string format = "msgpack"; }; MIGRAPHX_EXPORT program load(const std::string& filename, const file_options& options = file_options{}); MIGRAPHX_EXPORT program load_buffer(const std::vector& buffer, const file_options& options = file_options{}); MIGRAPHX_EXPORT program load_buffer(const char* buffer, std::size_t size, const file_options& options = file_options{}); MIGRAPHX_EXPORT void save(const program& p, const std::string& filename, const file_options& options = file_options{}); MIGRAPHX_EXPORT std::vector save_buffer(const program& p, const file_options& options = file_options{}); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/make_op.hpp000066400000000000000000000044741510465702400227260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MAKE_OP_HPP #define MIGRAPHX_GUARD_RTGLIB_MAKE_OP_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT operation make_op(const std::string& name); MIGRAPHX_EXPORT operation make_op(const std::string& name, const std::initializer_list>& v); MIGRAPHX_EXPORT operation make_op_from_value(const std::string& name, const value& v); // A template overload is added for migraphx::value so the initializer_list // cannot be passed in directly. This is to enforce at compile-time that all // initializer_list are key-value pairs, whereas migraphx::value allows other // types of initializer_list such as for arrays. template operation make_op(const std::string& name, const Value& v) { return make_op_from_value(name, v); } MIGRAPHX_EXPORT operation make_json_op(const std::string& name, const std::string& s); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/make_shared_array.hpp000066400000000000000000000036051510465702400247470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_MAKE_SHARED_ARRAY_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_MAKE_SHARED_ARRAY_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template std::shared_ptr make_shared_array(size_t size) { // cppcheck-suppress migraphx-UseSmartPointer return std::shared_ptr(new T[size](), std::default_delete()); // NOLINT } template std::shared_ptr make_shared_array(Iterator start, Iterator last) { auto result = make_shared_array(std::distance(start, last)); std::copy(start, last, result.get()); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/make_signed.hpp000066400000000000000000000031601510465702400235500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MAKE_SIGNED_HPP #define MIGRAPHX_GUARD_RTGLIB_MAKE_SIGNED_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template typename std::conditional_t{}, std::make_signed, std::enable_if>:: type make_signed(T x) { return x; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/manage_ptr.hpp000066400000000000000000000045021510465702400234200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_MANAGE_PTR_HPP #define MIGRAPHX_GUARD_MIGRAPHX_MANAGE_PTR_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template // NOLINT struct manage_deleter { template void operator()(T* x) const { if(x != nullptr) { (void)f(x); } } }; struct null_deleter { template void operator()(T*) const { } }; template // NOLINT using manage_ptr = std::unique_ptr>; template struct element_type { using type = typename T::element_type; }; template using remove_ptr = typename std:: conditional_t{}, std::remove_pointer, element_type>::type; template using shared = std::shared_ptr>; template shared share(T p) { return shared{std::move(p)}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #define MIGRAPHX_MANAGE_PTR(T, F) \ migraphx::manage_ptr, decltype(&F), &F> // NOLINT #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/marker.hpp000066400000000000000000000257651510465702400226020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MARKER_HPP #define MIGRAPHX_GUARD_MARKER_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// Marker is an interface to general marking functions, such as rocTX markers. #else #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT marker { // void mark_start(instruction_ref ins_ref); // void mark_start(const program& prog); // void mark_stop(instruction_ref ins); // void mark_stop(const program& prog); }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct marker { private: template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(std::declval().mark_start( std::declval()), std::declval().mark_start( std::declval()), std::declval().mark_stop( std::declval()), std::declval().mark_stop(std::declval()), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors marker() = default; template , typename = typename std::enable_if< not std::is_same, marker>{}>::type> marker(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template , typename = typename std::enable_if< not std::is_same, marker>{}>::type> marker& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { marker rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } void mark_start(instruction_ref ins_ref) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().mark_start(ins_ref); } void mark_start(const program& prog) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().mark_start(prog); } void mark_stop(instruction_ref ins) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().mark_stop(ins); } void mark_stop(const program& prog) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().mark_stop(prog); } friend bool is_shared(const marker& private_detail_x, const marker& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual void mark_start(instruction_ref ins_ref) = 0; virtual void mark_start(const program& prog) = 0; virtual void mark_stop(instruction_ref ins) = 0; virtual void mark_stop(const program& prog) = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } void mark_start(instruction_ref ins_ref) override { private_detail_te_value.mark_start(ins_ref); } void mark_start(const program& prog) override { private_detail_te_value.mark_start(prog); } void mark_stop(instruction_ref ins) override { private_detail_te_value.mark_stop(ins); } void mark_stop(const program& prog) override { private_detail_te_value.mark_stop(prog); } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const marker* x) { return x->any_cast(); } template inline ValueType* any_cast(marker* x) { return x->any_cast(); } template inline ValueType& any_cast(marker& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const marker& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/match/000077500000000000000000000000001510465702400216655ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/include/migraphx/match/dq_helpers.hpp000066400000000000000000000043751510465702400245350ustar00rootroot00000000000000 /* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MATCH_DQ_HELPERS_HPP #define MIGRAPHX_GUARD_MATCH_DQ_HELPERS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace match { /** * Find dequantizelinear (DQ) instruction with constant scale and zero point input * while skipping broadcast instructions between DQ and scale/zero point. Used * in simplify_qdq and fp8_ocp_to_fnuz. */ inline auto dequantizelinear_op(const std::string& scale, const std::string& zp) { return match::name("dequantizelinear")( match::arg(1)(match::skip_broadcasts(match::is_constant().bind(scale))), match::arg(2)(match::skip_broadcasts(match::is_constant().bind(zp)))); } /** * Skip certain operators after DQ instruction. * Used in simplify_qdq and fp8_ocp_to_fnuz. */ template auto skip_post_dq_ops(Ms... ms) { return match::skip(match::name( "broadcast", "multibroadcast", "contiguous", "transpose", "reshape", "convert"))(ms...); } } // namespace match } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/match/gelu_erf.hpp000066400000000000000000000046131510465702400241720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MATCH_GELU_ERF_HPP #define MIGRAPHX_GUARD_MATCH_GELU_ERF_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace match { namespace detail { template struct gelu_erf_matcher { F f; auto erf_fn() const { auto mul_1_sqrt_2 = f("mul")( either_arg(0, 1)(none_of(has_value(M_SQRT1_2)).bind("x"), has_value(M_SQRT1_2))); auto div_sqrt_2 = f("div")(args(none_of(has_value(M_SQRT2)).bind("x"), has_value(M_SQRT2))); return f("erf")(used_once(), arg(0)(used_once(), any_of(mul_1_sqrt_2, div_sqrt_2))); } auto add_erf() const { return f("add")(used_once(), either_arg(0, 1)(erf_fn(), has_value(1.0))); } auto one_half() const { return has_value(0.5); } auto matcher() const { return unordered_tree(f("mul"), one_half(), add_erf(), any()); } }; } // namespace detail template auto gelu_erf(F f) { return detail::gelu_erf_matcher{f}.matcher(); } inline auto gelu_erf() { return gelu_erf([](auto x) { return name(x); }); } } // namespace match } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MATCH_GELU_ERF_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/match/gelu_tanh.hpp000066400000000000000000000072711510465702400243530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MATCH_GELU_TANH_HPP #define MIGRAPHX_GUARD_MATCH_GELU_TANH_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace match { namespace detail { template struct gelu_tanh_matcher { F f; /// x ^ 3 auto pow_fn() const { return f("pow")(used_once(), arg(1)(has_value(3.0))); } auto tanh_fn() const { /// Gelu tanh approximation /// tanh( sqrt(2/M_PI) * (x + 0.044715 * x ^ 3 ) auto mul_const_pow1 = f("mul")(either_arg(0, 1)(has_value(0.044715), pow_fn())); auto add_any_mul = f("add")(any_arg(0, 1)(mul_const_pow1)); auto mul_sqrt2rpi_add = f("mul")(either_arg(0, 1)(has_value(sqrt(M_2_PI)), add_any_mul)); /// FastGelu tanh approximation /// tanh( 0.797885 * x + 0.035677 * x ^ 3 ) auto mul_const_pow2 = f("mul")(either_arg(0, 1)(has_value(0.035677), pow_fn())); auto mul_const_x = f("mul")(any_arg(0, 1)(has_value(0.797885))); auto add_mul_x_mul_pow = f("add")(either_arg(0, 1)(mul_const_pow2, mul_const_x)); return f("tanh")(used_once(), arg(0)(any_of(add_mul_x_mul_pow, mul_sqrt2rpi_add))); } /// x * (0.5? + 0.5 * tanh( sqrt(2/M_PI) * (x? + 0.044715 * x? ^ 3) ) ) or /// x * (0.5? + 0.5 * tanh( 0.797885 * x? + 0.035677 * x? ^ 3 ) ) /// ? question mark means it doesn't explicitly match that item (anything will work) auto matcher_v0() const { auto mul_half_tanh = f("mul")(either_arg(0, 1)(has_value(0.5), tanh_fn())); auto add_any_mul = f("add")(any_arg(0, 1)(mul_half_tanh)); return f("mul")(either_arg(0, 1)(any().bind("x"), add_any_mul)); } /// x * 0.5 * (1.0 + tanh( sqrt(2/M_PI) * (x + 0.044715 * x ^ 3) ) ) or /// x * 0.5 * (1.0 + tanh( 0.797885 * x + 0.035677 * x ^ 3 ) ) ) auto matcher_v1() const { auto add_one_tanh = f("add")(used_once(), either_arg(0, 1)(has_value(1.0), tanh_fn())); auto mul_half_x = f("mul")(used_once(), either_arg(0, 1)(has_value(0.5), any().bind("x"))); return f("mul")(either_arg(0, 1)(mul_half_x, add_one_tanh)); } }; } // namespace detail template auto gelu_tanh(F f) { auto gtm = detail::gelu_tanh_matcher{f}; return any_of(gtm.matcher_v0(), gtm.matcher_v1()); } inline auto gelu_tanh() { return gelu_tanh([](auto x) { return name(x); }); } } // namespace match } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MATCH_GELU_TANH_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/match/layernorm.hpp000066400000000000000000000064621510465702400244160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_MATCH_LAYERNORM_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_MATCH_LAYERNORM_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace match { namespace detail { template struct layernorm_matcher { F f; auto last_axis() const { return make_basic_pred_matcher([](instruction_ref ins) { auto v = ins->get_operator().to_value(); if(not v.contains("axes")) return false; auto axes = v["axes"].to_vector(); if(axes.size() != 1) return false; return axes.front() == ins->inputs().front()->get_shape().lens().size() - 1; }); } auto reduce_mean() const { return f("reduce_mean")(last_axis()); } auto x_minus_mean() const { return f("sub")(arg(0)(any().bind("x")), arg(1)(skip_broadcasts(reduce_mean()))); } auto variance() const { return reduce_mean()(arg(0)(any_of( f("pow")(arg(0)(x_minus_mean()), arg(1)(has_value(2.0f))), f("mul")(arg(0)(x_minus_mean()), arg(1)(x_minus_mean())), f("sqdiff")(either_arg(0, 1)(any().bind("x"), skip_broadcasts(reduce_mean())))))); } auto sqrt_add_eps(const std::string& name) const { auto add_eps = f("add")(either_arg(0, 1)(variance(), is_constant().bind("eps"))); return skip_broadcasts(f(name)(arg(0)(any_of(add_eps, variance())))); } auto layernorm_onnx() const { auto div_sqrt = f("div")(arg(0)(x_minus_mean()), arg(1)(sqrt_add_eps("sqrt"))); auto mul_rsqrt = f("mul")(either_arg(0, 1)(x_minus_mean(), sqrt_add_eps("rsqrt"))); return any(any_of(div_sqrt, mul_rsqrt)); } auto matcher() const { return layernorm_onnx(); } }; } // namespace detail template auto layernorm(F f) { return detail::layernorm_matcher{f}.matcher(); } inline auto layernorm() { return layernorm([](auto x) { return name(std::move(x)); }); } } // namespace match } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/match/softmax.hpp000066400000000000000000000046331510465702400240650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MATCH_SOFTMAX_HPP #define MIGRAPHX_GUARD_MATCH_SOFTMAX_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace match { namespace detail { template struct softmax_matcher { F f; M input_matcher; auto exp_x_minus_max() const { auto x_minus_max = f("sub")(arg(0)(input_matcher.bind("x")), arg(1)(skip_broadcasts(f("reduce_max")))); return f("exp")(arg(0)(x_minus_max)); } auto softmax_base_ops() const { auto sum_exp_x_minus_max = f("reduce_sum")(arg(0)(exp_x_minus_max())); return f("div")(arg(0)(exp_x_minus_max()), arg(1)(skip_broadcasts(sum_exp_x_minus_max))); } auto matcher() const { return softmax_base_ops(); } }; } // namespace detail template auto softmax(F f, M input_matcher) { return detail::softmax_matcher{f, input_matcher}.matcher(); } inline auto softmax() { return softmax([](auto x) { return name(x); }, any()); } template inline auto softmax_input(M m) { return softmax([](auto x) { return name(x); }, m); } } // namespace match } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/matcher.hpp000066400000000000000000000776051510465702400227440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MATCHER_HPP #define MIGRAPHX_GUARD_RTGLIB_MATCHER_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MIGRAPHX_USE_TYPE_ERASED_MATCHERS #define MIGRAPHX_USE_TYPE_ERASED_MATCHERS 0 #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace match { struct matcher_context { matcher_context(module& m) : mod(&m) {} std::unordered_map instructions; template bool matched(M m, instruction_ref ins) { return has_value(m.match(*this, ins)); } template bool matched(M m, optional ins) { if(ins) return has_value(m.match(*this, *ins)); return false; } template auto lazy_match(M m, I ins) { return [=] { return this->matched(m, ins); }; } bool has_instruction(instruction_ref ins) const { if(mod == nullptr) return true; return mod->has_instruction(ins); } bool has_instruction(optional ins) const { if(ins) return this->has_instruction(*ins); return false; } bool is_last(instruction_ref ins) const { assert(mod->begin() != mod->end()); assert(this->has_instruction(ins)); return ins == std::prev(mod->end()); } void debug_print(instruction_ref ins) const { mod->debug_print(ins); } private: module* mod = nullptr; }; /// Convert a predicate function into a matcher template struct predicate_matcher { P p; optional match(const matcher_context&, instruction_ref ins) const { if(p(ins)) return optional{ins}; return nullopt; } }; /// Convert a predicate function into a matcher template predicate_matcher

make_predicate_matcher(P p) { return {std::move(p)}; } /// Convert a function into a matcher template struct function_matcher { F f; auto match(matcher_context& ctx, instruction_ref ins) const { return f(ctx, ins); } }; /// Convert a function into a matcher template function_matcher make_function_matcher(F f) { return {std::move(f)}; } /// Converts a matcher to bind the instruction to name template auto bind_match(M m, std::string name) { return make_function_matcher( [=, m_name = std::move(name)](matcher_context& ctx, instruction_ref ins) -> optional { auto result = m.match(ctx, ins); if(result) { if(not ctx.has_instruction(ins)) return nullopt; ctx.instructions[m_name] = ins; } return result; }); } /// Convert a matcher to a bindable matcher template struct bindable_matcher { M m; auto bind(std::string name) const { return bind_match(m, std::move(name)); } auto match(matcher_context& ctx, instruction_ref ins) const { return m.match(ctx, ins); } }; /// Create a bindable matcher template bindable_matcher make_bindable_matcher(M m) { return {m}; } /// Create a bindable matcher from a function template bindable_matcher> make_bf_matcher(F f) { return {{f}}; } /// Create a bindable matcher from a predicate function template bindable_matcher> make_bp_matcher(F f) { return {{f}}; } using bool_list = std::initializer_list; struct id_matcher { auto match(matcher_context&, instruction_ref ins) const { return optional{ins}; } }; // Forward declare class and constructors template struct basic_matcher; struct any_matcher; template struct type_erased_matcher { #if MIGRAPHX_USE_TYPE_ERASED_MATCHERS using type = any_matcher; #else using type = basic_matcher; #endif }; template typename type_erased_matcher::type make_basic_matcher(M m); template auto make_basic_fun_matcher(F f); template auto make_basic_pred_matcher(P p); /// The basic matcher provides the all_of composability of the matcher template struct basic_matcher { M m; template auto operator()(Ts... ms) const { // Copy m because we cant capture `this` by value auto mm = m; return make_basic_fun_matcher([=](matcher_context& ctx, instruction_ref ins) -> optional { auto result = mm.match(ctx, ins); if(result) { bool matches = fold([&](auto x, auto y) { return x and ctx.matched(y, result); })(true, ms...); if(matches) return result; } return nullopt; }); } auto bind(std::string name) const { return bind_match(m, std::move(name)); } auto match(matcher_context& ctx, instruction_ref ins) const { return m.match(ctx, ins); } }; /// Create a typed-erased matcher using any_matcher_base = basic_matcher< function_matcher(matcher_context&, instruction_ref)>>>; struct any_matcher : any_matcher_base { template any_matcher(M mm) : any_matcher_base({[=](auto& ctx, auto ins) { return mm.match(ctx, ins); }}) { } }; /// Create a basic matcher from a matcher template typename type_erased_matcher::type make_basic_matcher(M m) { return {m}; } /// Create a basic matcher from a function template auto make_basic_fun_matcher(F f) { return make_basic_matcher(make_function_matcher(std::move(f))); } /// Create a basic matcher from a predicate function template auto make_basic_pred_matcher(P p) { return make_basic_matcher(make_predicate_matcher(std::move(p))); } /// This macro takes care of the boilerplate for defining a matcher #define MIGRAPHX_BASIC_MATCHER(name, ...) \ struct name##_m \ { \ optional match(__VA_ARGS__) const; \ }; \ const constexpr auto name = migraphx::match::basic_matcher{{}}; \ inline optional name##_m::match(__VA_ARGS__) const /// This macro takes care of the boilerplate for defining a predicate matcher #define MIGRAPHX_PRED_MATCHER(name, ...) \ struct name##_m \ { \ bool operator()(__VA_ARGS__) const; \ }; \ const constexpr auto name = \ migraphx::match::basic_matcher>{{}}; \ inline bool name##_m::operator()(__VA_ARGS__) const struct matcher_result { struct instruction_container { instruction_container() = default; instruction_container(std::unordered_map x) : ins_map(std::move(x)) { } instruction_ref operator[](const std::string& name) const { auto it = ins_map.find(name); if(it == ins_map.end()) MIGRAPHX_THROW("Accessing name that wasn't bound in matcher: " + name); return it->second; } auto find(const std::string& name) const { return ins_map.find(name); } auto begin() const { return ins_map.cbegin(); } auto end() const { return ins_map.cend(); } bool has_instructions_in(const module& mod) const { return std::all_of(ins_map.begin(), ins_map.end(), [&](auto&& p) { return mod.has_instruction(p.second); }); } void debug_print() const { for(const auto& it : ins_map) { std::cout << it.first << ": \n"; it.second->debug_print(); } } private: std::unordered_map ins_map; }; void debug_print() const { std::cout << "matcher_container: \n instructions:"; instructions.debug_print(); std::cout << " result: \n"; result->debug_print(); } instruction_container instructions; instruction_ref result; }; /// Match a single instruction template matcher_result match_instruction(module& mod, instruction_ref ins, M&& m) { assert(ins != mod.end()); assert(mod.has_instruction(ins)); matcher_context ctx{mod}; matcher_result result; if(m.match(ctx, ins)) { result.result = ins; result.instructions = ctx.instructions; assert(result.instructions.has_instructions_in(mod)); } else { result.result = mod.end(); } return result; } template bool instruction_matches(module& mod, instruction_ref ins, M&& m) { return match_instruction(mod, ins, std::forward(m)).result != mod.end(); } /// Find first instance of a matching instruction in a module template match::matcher_result find_match(module& modl, M&& m) { match::matcher_result result; for(auto ins : iterator_for(modl)) { result = match::match_instruction(modl, ins, m); if(result.result != modl.end()) return result; } return result; } MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_MATCHES) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_MATCHES_FOR) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_VALIDATE_MATCHES) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TIME_MATCHERS) /// Find matches for an instruction in the module for per section of matchers template void find_matches_for(source_location location, Mod& mod, instruction_ref ins, Ms&&... ms) { const int trace = value_of(MIGRAPHX_TRACE_MATCHES{}); const bool validate = enabled(MIGRAPHX_VALIDATE_MATCHES{}); const auto trace_filter = string_value_of(MIGRAPHX_TRACE_MATCHES_FOR{}); const bool time_matchers = enabled(MIGRAPHX_TIME_MATCHERS{}); bool match = false; each_args( [&](auto&& m) { const auto& matcher_name = get_type_name(m); const bool trace_for = not trace_filter.empty() and (contains(std::string{location.file_name()}, trace_filter) or contains(std::string{location.function_name()}, trace_filter) or contains(matcher_name, trace_filter)); if(match) return; // print running matcher even if it doesn't match anything if(trace > 1 and trace_for) std::cout << "Running matcher: " << matcher_name << std::endl; matcher_result r; if(time_matchers or trace_for) { timer match_timer{}; r = match_instruction(get_module(mod), ins, m.matcher()); const auto match_time = match_timer.record>(); std::cout << "Matcher time for " << matcher_name << ": " << match_time << "us" << std::endl; } else { r = match_instruction(get_module(mod), ins, m.matcher()); } // did not match any instruction if(r.result == get_module(mod).end()) return; if(trace > 0 or trace_for) { std::cout << "Matched by: " << matcher_name << std::endl; get_module(mod).debug_print(ins); } // If its already invalid dont validate it again bool invalidated = validate and get_module(mod).validate() != get_module(mod).end(); auto apply_time = time>([&] { m.apply(mod, r); }); if(time_matchers or trace_for) { std::cout << "Apply time for " << matcher_name << ": " << apply_time << "us" << std::endl; } if(validate and not invalidated) { auto invalid = get_module(mod).validate(); if(invalid != get_module(mod).end()) { std::cout << "Invalid program from match: " << matcher_name << std::endl; std::cout << "Invalid instructions: " << std::endl; get_module(mod).debug_print(invalid->inputs()); get_module(mod).debug_print(invalid); } } match = true; }, ms...); } /// Find matches in a module template struct find_matches { find_matches(Mod& mod, Ms&&... ms, source_location location = source_location::current()) { for(auto ins : iterator_for(get_module(mod))) { find_matches_for(location, mod, ins, ms...); } } }; template find_matches(Mod& mod, Ms&&... ms) -> find_matches; template struct find_generic_match { M m; F f; M matcher() const { return m; } void apply(module& mod, const matcher_result& mr) const { f(mod, mr); } }; template find_generic_match make_match_finder(M m, F f) { return {m, std::move(f)}; } template struct find_skip { M m; M matcher() const { return m; } void apply(module&, const matcher_result&) const {} }; template find_skip make_find_skip(M m) { return {m}; } struct lazy_and { template auto operator()(F f, G g) const // NOLINT(performance-unnecessary-value-param) { return [=] { return f() and g(); }; } }; struct lazy_or { template auto operator()(F f, G g) const // NOLINT(performance-unnecessary-value-param) { return [=] { return f() or g(); }; } }; template struct match_fold_f { template static bool fold_matchers(matcher_context& ctx, instruction_ref ins, Ms... ms) { Op op; auto matched = [&](auto m) { return [=, &ctx] { return ctx.matched(m, ins); }; }; return fold(op)(always(Start), matched(ms)...)(); } template static bool fold_matchers_pack(matcher_context& ctx, instruction_ref ins, const Pack& p) { return p([&](auto... ms) { return match_fold_f::fold_matchers(ctx, ins, ms...); }); } template auto operator()(Ts... ms) const { return make_basic_fun_matcher( [=](matcher_context& ctx, instruction_ref ins) -> optional { bool matches = match_fold_f::fold_matchers(ctx, ins, ms...); if(matches == Matches) return {ins}; return nullopt; }); } template auto operator[](Selector select) const { return [=](auto... ms) { // Workaround ICE on gcc by packing matchers into an object auto mpack = pack(ms...); return make_basic_fun_matcher( [=](matcher_context& ctx, instruction_ref start) -> optional { Op op; bool matches = Start; select(start, [&](auto ins) { auto fm = [&] { return match_fold_f::fold_matchers_pack(ctx, ins, mpack); }; matches = op(always(matches), fm)(); }); if(matches == Matches) return {start}; return nullopt; }); }; } }; const constexpr auto all_of = match_fold_f{}; const constexpr auto any_of = match_fold_f{}; const constexpr auto none_of = match_fold_f{}; template auto skip_matches(Ms... ms) { return make_find_skip(any_of(ms...)); } inline auto inputs() { return [](auto ins, auto f) { for(auto&& x : ins->inputs()) f(x); }; } inline auto outputs() { return [](auto ins, auto f) { for(auto&& x : ins->outputs()) f(x); }; } inline auto trace(const std::string& s) { return [=](auto m) { return make_basic_fun_matcher([=](matcher_context& ctx, instruction_ref ins) { std::cout << s << ": "; ctx.debug_print(ins); optional result = m.match(ctx, ins); if(result.has_value()) std::cout << "Found\n"; else std::cout << "Not Found\n"; return result; }); }; } inline auto trace_found(const std::string& s) { return [=](auto m) { return make_basic_fun_matcher([=](matcher_context& ctx, instruction_ref ins) { optional result = m.match(ctx, ins); if(result.has_value()) { std::cout << "Found: " << s << ": "; ctx.debug_print(ins); } return result; }); }; } inline auto trace_not_found(const std::string& s) { return [=](auto m) { return make_basic_fun_matcher([=](matcher_context& ctx, instruction_ref ins) { optional result = m.match(ctx, ins); if(not result.has_value()) { std::cout << "Not Found: " << s << ": "; ctx.debug_print(ins); } return result; }); }; } MIGRAPHX_PRED_MATCHER(any, instruction_ref) { return true; } MIGRAPHX_PRED_MATCHER(none, instruction_ref) { return false; } MIGRAPHX_PRED_MATCHER(standard_shape, instruction_ref ins) { return ins->get_shape().standard(); } MIGRAPHX_PRED_MATCHER(not_standard_shape, instruction_ref ins) { return not ins->get_shape().standard(); } MIGRAPHX_PRED_MATCHER(dynamic_shape, instruction_ref ins) { return ins->get_shape().dynamic(); } MIGRAPHX_PRED_MATCHER(static_shape, instruction_ref ins) { return not ins->get_shape().dynamic(); } MIGRAPHX_PRED_MATCHER(broadcast_shape, instruction_ref ins) { return ins->get_shape().broadcasted(); } MIGRAPHX_PRED_MATCHER(scalar_shape, instruction_ref ins) { return ins->get_shape().scalar(); } MIGRAPHX_PRED_MATCHER(transpose_shape, instruction_ref ins) { return ins->get_shape().transposed(); } inline auto ndim(std::size_t n) { return make_basic_pred_matcher( [=](instruction_ref ins) { return ins->get_shape().ndim() == n; }); } MIGRAPHX_PRED_MATCHER(not_tuple, instruction_ref ins) { return ins->get_shape().type() != shape::tuple_type; } MIGRAPHX_PRED_MATCHER(same_input_shapes, instruction_ref ins) { if(ins->inputs().empty()) return false; auto s = ins->inputs().front()->get_shape(); return std::all_of( ins->inputs().begin(), ins->inputs().end(), [&](auto x) { return x->get_shape() == s; }); } MIGRAPHX_PRED_MATCHER(same_inputs, instruction_ref ins) { if(ins->inputs().empty()) return false; auto input = ins->inputs().front(); return std::all_of( ins->inputs().begin(), ins->inputs().end(), [&](auto x) { return x == input; }); } MIGRAPHX_PRED_MATCHER(has_same_value, instruction_ref ins) { if(ins->name() != "@literal") return false; bool all_same = false; ins->get_literal().visit([&](auto s) { all_same = std::all_of(s.begin() + 1, s.end(), [&](const auto& scale) { return float_equal(scale, s.front()); }); }); return all_same; } MIGRAPHX_BASIC_MATCHER(output, const matcher_context&, instruction_ref ins) { if(ins->outputs().size() == 1) return {ins->outputs().front()}; return nullopt; } MIGRAPHX_BASIC_MATCHER(used_once, const matcher_context& ctx, instruction_ref ins) { if(ins->outputs().size() == 1) return {ins}; if(ins->outputs().empty() and ctx.is_last(ins)) return {ins}; return nullopt; } MIGRAPHX_PRED_MATCHER(is_constant, instruction_ref ins) { return ins->can_eval(); } MIGRAPHX_BASIC_MATCHER(is_unused, const matcher_context& ctx, instruction_ref ins) { if(ins->outputs().empty() and not ctx.is_last(ins)) return {ins}; return nullopt; } MIGRAPHX_PRED_MATCHER(broadcast, instruction_ref ins) { return contains({"broadcast", "multibroadcast"}, ins->name()); } /** * Makes a matcher that recursively traverses over single inputs to an instruction that * match the given matchers. The matcher will then be at the instruction before the `ms` * matched instructions. */ template auto skip(Ms... ms) { static_assert(((not std::is_convertible{}) and ...), "Use a matcher not a string for skip."); auto m = any_of(ms...); return make_basic_fun_matcher([=](matcher_context& ctx, instruction_ref start) { return fix>( [&](auto self, auto ins) -> optional { if(ins->inputs().size() == 1 and ctx.matched(m, ins)) { auto next = ins->inputs().front(); return self(next); } return ins; })(start); }); } /** * Makes a matcher that recursively traverses over single outputs to an instruction that * match the given matchers. The matcher will then return at the instruction after the `ms` * matched instructions. If any instruction matched has more than one output the matcher * returns nullopt. */ template auto skip_output(Ms... ms) { auto m = any_of(ms...); return make_basic_fun_matcher([=](matcher_context& ctx, instruction_ref start) { return fix>( [&](auto self, auto ins) -> optional { if(ins->outputs().size() == 1) { auto next = ins->outputs().front(); if(ctx.matched(m, next)) { auto skipped_next = self(next); if(skipped_next) return skipped_next; } return next; } return nullopt; })(start); }); } inline auto var(std::string s) { return make_basic_fun_matcher( [=, m_s = std::move(s)](const matcher_context& ctx, instruction_ref) -> optional { auto it = ctx.instructions.find(m_s); if(it == ctx.instructions.end()) return nullopt; return it->second; }); } inline auto name(std::string s) { return make_basic_pred_matcher( [=, m_s = std::move(s)](instruction_ref ins) { return ins->name() == m_s; }); } inline auto name_contains(const std::string& name) { return make_basic_pred_matcher( [=](instruction_ref ins) { return contains(ins->get_operator().name(), name); }); } inline auto name(std::unordered_set names) { return make_basic_pred_matcher([=, m_names = std::move(names)](instruction_ref ins) { return m_names.count(ins->name()) > 0; }); } template inline auto name(std::string s, Ts... xs) // NOLINT { return name(std::unordered_set{std::move(s), std::move(xs)...}); } inline auto nargs(std::size_t n) { return make_basic_pred_matcher([=](instruction_ref ins) { return ins->inputs().size() == n; }); } inline auto arg(std::size_t i) { return make_basic_fun_matcher( [=](const matcher_context&, instruction_ref ins) -> optional { if(i < ins->inputs().size()) return ins->inputs()[i]; return nullopt; }); } // Workaround for bugs in clang template struct args_impl_ints { }; template auto args_impl(args_impl_ints, Ms... ms) { return match::all_of(nargs(sizeof...(Ns)), arg(Ns)(ms)...); } template auto args(Ms... ms) { return sequence_c([=](auto... is) { // It needs to be written as `decltype(is)::value` for gcc 5 return args_impl(args_impl_ints{}, ms...); }); } inline auto either_arg(std::size_t i, std::size_t j) { return [=](auto m1, auto m2) { return match::any_of(match::all_of(arg(i)(m1), arg(j)(m2)), match::all_of(arg(j)(m1), arg(i)(m2))); }; } inline auto any_arg(std::size_t i, std::size_t j) { return [=](auto m) { return match::any_of(arg(i)(m), arg(j)(m)); }; } template std::size_t tree_leafs_impl(matcher_context& ctx, std::array& leafs, M m, instruction_ref ins) { std::size_t idx = 0; fix([&](auto self, auto i) { if(idx == leafs.size()) return; if(ctx.matched(m, i) and i->inputs().size() >= 2) { self(i->inputs()[0]); self(i->inputs()[1]); return; } leafs[idx] = i; idx++; })(ins); return idx; } template auto tree(M main_op, Ms... ms) { return make_basic_fun_matcher( [=](matcher_context& ctx, instruction_ref ins) -> optional { // Flatten leaf nodes std::array leafs; std::size_t idx = tree_leafs_impl(ctx, leafs, main_op, ins); if(idx != leafs.size()) return nullopt; // Use explicit captures to workaround ICE on gcc // Capture by value to workaround compile error on gcc 9 bool found = sequence_c([ms..., &ctx, &leafs](auto... is) { return fold(lazy_and{})(ctx.lazy_match(ms, leafs[is])...)(); }); if(not found) return nullopt; return ins; }); } template auto unordered_tree(M main_op, Ms... ms) { return make_basic_fun_matcher( [=](matcher_context& ctx, instruction_ref ins) -> optional { // Flatten leaf nodes std::array leafs; std::size_t idx = tree_leafs_impl(ctx, leafs, main_op, ins); if(idx != leafs.size()) return nullopt; // Use explicit captures to workaround ICE on gcc bool found = sequence_c([ms..., &ctx, &leafs](auto... is) { return by(fold(lazy_and{}), [is..., &ctx, &leafs](auto m) { return fold(lazy_or{})(ctx.lazy_match(m, leafs[is])...); })(ms...)(); }); if(not found) return nullopt; return ins; }); } template auto same_shape(M m) { return make_basic_fun_matcher( [=](matcher_context& ctx, instruction_ref ins) -> optional { auto i = m.match(ctx, ins); if(i and (*i)->get_shape() == ins->get_shape()) return ins; return nullopt; }); } template auto same_shape(Ms... ms) { return all_of(same_shape(ms)...); } template auto skip_broadcasts(Ms... ms) { return skip(name("broadcast", "multibroadcast", "contiguous"))(ms...); } template auto skip_broadcasts_converts(Ms... ms) { return skip(name("broadcast", "multibroadcast", "contiguous", "convert"))(ms...); } template inline auto literal_value_checker(F f) { return skip_broadcasts_converts(make_basic_pred_matcher([=](instruction_ref ins) { if(ins->name() != "@literal") return false; auto l = ins->get_literal(); if(l.empty()) return false; return f(l); })); } /** * Uses integer multiples of the corresponding floating point epsilon and * compares with abs(y - x) < eps * (atol_mult + rtol_mult * abs(x)). * atol_mult controls the absolute tolerance. * rtol_mult controls the relative tolerance. * Uses no tolerance for integral types. */ template inline auto has_value(T x, std::size_t atol_mult = 10, std::size_t rtol_mult = 10) { return literal_value_checker([=](migraphx::literal l) { bool b = false; l.visit([&](auto v) { // cast to the literal's data type before comparing using type = typename decltype(v)::value_type; auto tolerance = atol_mult + rtol_mult * std::fabs(x); if(migraphx::float_equal(tolerance, 0) or std::is_integral{}) { if(std::all_of(v.begin(), v.end(), [&](auto val) { return migraphx::float_equal(val, static_cast(x)); })) b = true; } else { auto eps = std::numeric_limits::epsilon(); if(std::all_of(v.begin(), v.end(), [&](auto val) { return std::fabs(val - static_cast(x)) < (eps * tolerance); })) b = true; } }); return b; }); } inline auto has_attribute(const std::string& name) { return make_basic_pred_matcher( [=](instruction_ref ins) { return ins->get_operator().attributes().contains(name); }); } template inline auto has_op_value(const std::string& name, const T& value) { return make_basic_pred_matcher([=](instruction_ref ins) { auto op_val = ins->get_operator().to_value(); return op_val.contains(name) and op_val[name].to>() == value; }); } template auto pointwise(Ms... ms) { return match::has_attribute("pointwise")(ms...); } MIGRAPHX_PRED_MATCHER(reduce, instruction_ref ins) { return starts_with(ins->name(), "reduce_"); } } // namespace match } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/memory_coloring.hpp000066400000000000000000000034501510465702400245100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MEMORY_COLORING_HPP #define MIGRAPHX_GUARD_RTGLIB_MEMORY_COLORING_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Remove multiple memory allocations using graph coloring to find memory allocations that can be * reused. */ struct MIGRAPHX_EXPORT memory_coloring { std::string allocation_op{}; bool verify = false; std::string name() const { return "memory_coloring"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/module.hpp000066400000000000000000000401231510465702400225670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_MODULE_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_MODULE_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT const operation& get_operation(instruction_ref ins); struct module_impl; using parameter_map = std::unordered_map; using ins_dep_map = std::unordered_map>; struct module_with_inputs; /** * @brief Stores the instruction stream */ struct MIGRAPHX_EXPORT module { using inserter = std::function& inputs, const std::vector& mod_args)>; module(const std::string& name = ""); // move constructor module(module&&) noexcept; // copy constructor module(const module&); // copy assignment operator module& operator=(module); ~module() noexcept; std::string name() const; bool bypass() const; void set_bypass(bool b = true); template {}...)> instruction_ref add_instruction(operation op, Ts... args) { return add_instruction(op, {args...}); } instruction_ref add_instruction(const operation& op, std::vector args); instruction_ref add_instruction(const operation& op, std::vector args, std::vector module_args); template {}...)> instruction_ref insert_instruction(instruction_ref ins, operation op, Ts... args) { return insert_instruction(ins, op, {args...}); } instruction_ref insert_instruction(instruction_ref ins, const operation& op, std::vector args); instruction_ref insert_instruction(instruction_ref ins, const operation& op, std::vector args, std::vector module_args); template {}...)> instruction_ref replace_instruction(instruction_ref ins, operation op, Ts... args) { return replace_instruction(ins, op, {args...}); } instruction_ref replace_instruction(instruction_ref ins, const operation& op, std::vector args) MIGRAPHX_TIDY_CONST; instruction_ref replace_instruction(instruction_ref ins, const operation& op, std::vector args, std::vector module_args) MIGRAPHX_TIDY_CONST; instruction_ref replace_instruction(instruction_ref ins, instruction_ref rep); instruction_ref remove_instruction(instruction_ref ins); instruction_ref remove_instructions(instruction_ref first, instruction_ref last); instruction_ref move_instruction(instruction_ref src, instruction_ref dst); instruction_ref move_instructions(instruction_ref src, instruction_ref dst); std::vector add_instructions(const std::vector& instructions, std::unordered_map* map_ins = nullptr, inserter insert = nullptr); std::vector add_instructions(const_module_ref m, std::unordered_map* map_ins = nullptr, inserter insert = nullptr); std::vector add_instructions(instruction_ref start, instruction_ref last, std::unordered_map* map_ins = nullptr, inserter insert = nullptr); std::vector insert_instructions(instruction_ref ins, const std::vector& instructions, std::unordered_map* map_ins = nullptr, inserter insert = nullptr); std::vector insert_instructions(instruction_ref ins, const_module_ref m, std::unordered_map* map_ins = nullptr, inserter insert = nullptr); std::vector insert_instructions(instruction_ref ins, instruction_ref start, instruction_ref last, std::unordered_map* map_ins = nullptr, inserter insert = nullptr); template instruction_ref add_literal(Ts&&... xs) { return add_literal(literal{std::forward(xs)...}); } instruction_ref add_literal(literal l); instruction_ref add_outline(const shape& s); instruction_ref add_parameter(std::string name, shape s); instruction_ref add_return(std::vector args); instruction_ref replace_return(std::vector args); instruction_ref insert_literal(instruction_ref ins, literal l); instruction_ref insert_parameter(instruction_ref ins, std::string name, shape s); std::vector get_parameter_names() const; shape get_parameter_shape(std::string name) const; instruction_ref get_parameter(std::string name) const; std::vector get_parameters() const; void rename_parameter(instruction_ref ins, const std::string& name); std::unordered_map get_parameter_shapes() const; bool has_instruction(instruction_ref ins) const; std::vector get_returns() const; std::size_t size() const; instruction_ref begin() const; instruction_ref end() const; instruction_ref insert_end() const; struct compute_shapes_options { std::string name = "compute_shapes"; bool strict_type = false; bool strict_lens = false; std::vector scalar_const_out_lens = {}; }; /// Compute a new ouput shape by replacing each parameter with input /// shapes passed in. std::vector compute_shapes(const std::vector& inputs, compute_shapes_options options) const; std::vector compute_shapes(const std::vector& inputs) const; std::vector get_output_shapes() const; instruction_ref validate() const; instruction_ref find_dangling_reference() const; void finalize(std::vector& contexts); /// Create a mapping from the input instruction to the corresponding /// parameter instruction. Use the `reverse` flag to reverse the lookup /// to be from parameter instruction to input instread. std::unordered_map get_ins_param_map(const std::vector& inputs, bool reverse = false) const; /// Given a mapping from submodule instructions to parent module instructions /// construct a vector of inputs with parent module instructions in the /// correct order std::vector get_inputs(const std::unordered_map& map_ins) const; using with_inputs = module_with_inputs; /// This will split the module into two parts at the instruction splits. /// Each split instruction becomes an input parameter in the second /// module. As such the inputs instructions to the second module will use /// the split instructions as input placeholders that can be replaced /// later. std::array split(const std::vector& args, const std::vector& splits) const; /// This will split the module in 3 parts using different split /// instruction for each additional module. std::array split(const std::vector& args, const std::vector& splits1, const std::vector& splits2) const; // Insert params to module based on given input instructions and add // mappings from inputs to corresponding params in instructions map void add_params(const std::vector& inputs, std::unordered_map* map_ins = nullptr, const std::function& shape_transform = nullptr); /** * Fuse the instruction into the module by inserting the instructions and * parameters for any missing inputs. * `map_ins` is mapping from previous instructions to new instructions. */ std::vector fuse(const std::vector& inss, std::unordered_map* map_ins = nullptr, inserter insert = nullptr, const std::function& shape_transform = nullptr); /** * Fuse another module into this module by inserting the instructions and * parameters from the module * map_ins is mapping from previous instructions to new instructions * Returns output instructions to the module. */ std::vector fuse(const module& m, const std::vector& inputs, std::unordered_map* map_ins = nullptr, inserter insert = nullptr, const std::function& shape_transform = nullptr); /* Insert instructions from module `m` to this module at position `ins` */ std::vector insert_inline(instruction_ref ins, const module& m, const std::vector& inputs, std::unordered_map* map_ins = nullptr, inserter insert = nullptr); void debug_print() const; void debug_print(instruction_ref ins) const; void debug_print(instruction_ref ins, std::unordered_map& names) const; void debug_print(const std::vector& inss) const; std::unordered_map print( const std::function&)>& print_func, std::unordered_map names) const; void print(const std::function&)>& print_func) const; void print_graph(std::ostream& os, bool brief = false) const; void print_py(std::ostream& os) const; std::unordered_map print_py(std::ostream& os, const std::string& mname, std::unordered_map names) const; void print_cpp(std::ostream& os) const; std::unordered_map print_cpp(std::ostream& os, const std::string& mname, std::unordered_map names) const; void annotate(std::ostream& os, std::function a) const; std::vector get_sub_modules(bool shallow = false) const; /* sorts the module in topological order aka reverse-post order (RPO) DFS order it takes last instruction or @return as the root and walks back the graph and moves inputs of the each instruction such that it appears before the instruction itself. */ module& sort(); module& shuffle(std::vector permutation); /* Any instruction "X" can have module arguments and those modules inside them can use any other * instruction "Y" from predecessor modules of the instruction "X". Such instruction "Y" inside * module args are not listed as input instructions to "X". But those instructions "Y" must be * evaluted before the instruction "X" can. Therefore such "Y" instructions are considered * implicit dependency to "X". */ ins_dep_map calc_implicit_deps() const; void repeat_while_changes(std::size_t n, const std::function& f); void localized_sort(instruction_ref start_ins, instruction_ref end_ins); MIGRAPHX_EXPORT friend std::ostream& operator<<(std::ostream& os, const module& m); MIGRAPHX_EXPORT friend bool operator==(const module& x, const module& y); friend bool operator!=(const module& x, const module& y) { return not(x == y); } friend struct program; private: void set_name(const std::string& name); void assign(const module& m); void calc_implicit_deps(const module& smod, const module& pmod, instruction_ref ins, ins_dep_map& deps) const; std::unique_ptr impl; }; struct MIGRAPHX_EXPORT module_with_inputs { module mod; std::vector inputs; /// Replace the instruction in the inputs with rep void replace(instruction_ref ins, instruction_ref rep); /// Replace the input instructions using the map_ins to lookup the replacement void replace(const std::unordered_map& map_ins); /// Replace the input instructions of the keys with the instructions /// passed as values. Both vectors should be in the same order. void replace(const std::vector& keys, const std::vector& values); }; inline module& get_module(module& m) { return m; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/module_ref.hpp000066400000000000000000000030171510465702400234240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MODULE_REF_HPP #define MIGRAPHX_GUARD_MODULE_REF_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; using module_ref = module*; using const_module_ref = const module*; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/msgpack.hpp000066400000000000000000000034401510465702400227300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MSGPACK_HPP #define MIGRAPHX_GUARD_RTGLIB_MSGPACK_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT void to_msgpack(const value& v, std::function writer); MIGRAPHX_EXPORT std::vector to_msgpack(const value& v); MIGRAPHX_EXPORT value from_msgpack(const std::vector& buffer); MIGRAPHX_EXPORT value from_msgpack(const char* buffer, std::size_t size); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/netron_output.hpp000066400000000000000000000030331510465702400242260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_NETRON_OUTPUT_HPP #define MIGRAPHX_GUARD_RTGLIB_NETRON_OUTPUT_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT std::string make_netron_output(const program& prog); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/normalize_attributes.hpp000066400000000000000000000061701510465702400255540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_NORMALIZE_ATTRIBUTES_HPP #define MIGRAPHX_GUARD_RTGLIB_NORMALIZE_ATTRIBUTES_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct operation; template struct select_dependent_type { using type = T; }; template using dependent_type = typename select_dependent_type::type; /** * Used to normalize variable input axes at model runtime. * Example: the axes inputs of the slice operator. * * \param axes the axes to normalize * \param input_shape shape of the input tensor * \param attr_val the normalize_axes attributes from the operator * \param prefix error message prefix */ MIGRAPHX_EXPORT std::vector normalize_axes(const std::vector& axes, const shape& input_shape, const value& attr_val, const std::string& prefix = ""); /** * Used to normalize variable input axes at model runtime. * Example: the starts and ends inputs of the slice operator. * * \param indices the indices to normalize * \param axes which axes the indices apply over * \param input_shape shape of the input tensor * \param attr_val the normalize_axes attributes from the operator * \param prefix error message prefix */ MIGRAPHX_EXPORT std::vector normalize_indices(const std::vector& indices, const std::vector& axes, const shape& input_shape, const value& attr_val, const std::string& prefix = ""); MIGRAPHX_EXPORT bool normalize_attributes(operation& op, const shape& input_shape); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/normalize_ops.hpp000066400000000000000000000033161510465702400241660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_NORMALIZE_OPS_HPP #define MIGRAPHX_GUARD_RTGLIB_NORMALIZE_OPS_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Process negative axis attributes of ops */ struct MIGRAPHX_EXPORT normalize_ops { std::string name() const { return "normalize_ops"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/onnx.hpp000066400000000000000000000073501510465702400222710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_ONNX_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_ONNX_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /// struct to pass in onnx options to parser struct onnx_options { /// Old way to set default fixed dimension size std::size_t default_dim_value = 0; /// Default dynamic dimension size (if both default_dim_value and default_dyn_dim_value set /// parser throws) shape::dynamic_dimension default_dyn_dim_value = {1, 1}; /// Explicitly specify the dims of an input std::unordered_map> map_input_dims = {}; /// Explicitly specify a symbolic named parameter dimension std::unordered_map dim_params = {}; /// Explicitly specify dynamic dims of an input (if both map_input_dims and map_dyn_input_dims /// set parser throws) std::unordered_map> map_dyn_input_dims = {}; /// Continue parsing onnx file if an unknown operator is found bool skip_unknown_operators = false; /// Print program if an error occurs bool print_program_on_error = false; /// Max iter num for the loop operator if trip count is not set int64_t max_loop_iterations = 10; /// Max iter limit for the loop operator. /// Since loop will become a tensor of max iter size a huge number can cause overflow during /// shape computations. int64_t limit_max_iterations = std::numeric_limits::max(); /// Use dynamic output for operators when available bool use_dyn_output = false; /// Path to use for the external data if it is stored at different location compared to onnx /// file std::string external_data_path = ""; }; /// Create a program from an onnx file MIGRAPHX_ONNX_EXPORT program parse_onnx(const std::string& name, const onnx_options& = onnx_options{}); /// Create a program from an onnx buffer MIGRAPHX_ONNX_EXPORT program parse_onnx_buffer(const std::string& buffer, const onnx_options& options); /// Create a program from an onnx buffer MIGRAPHX_ONNX_EXPORT program parse_onnx_buffer(const void* data, std::size_t size, const onnx_options& options); MIGRAPHX_ONNX_EXPORT std::vector get_onnx_operators(); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/000077500000000000000000000000001510465702400212075ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/abs.hpp000066400000000000000000000032151510465702400224660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ABS_HPP #define MIGRAPHX_GUARD_OPERATORS_ABS_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct abs : unary { auto apply() const { return [](auto x) { return std::abs(make_signed(x)); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/acos.hpp000066400000000000000000000031411510465702400226440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ACOS_HPP #define MIGRAPHX_GUARD_OPERATORS_ACOS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct acos : unary { auto apply() const { return [](auto x) { return std::acos(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/acosh.hpp000066400000000000000000000030661510465702400230220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ACOSH_HPP #define MIGRAPHX_GUARD_OPERATORS_ACOSH_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct acosh : unary { auto apply() const { return [](auto x) { return std::acosh(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/add.hpp000066400000000000000000000034201510465702400224470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ADD_HPP #define MIGRAPHX_GUARD_OPERATORS_ADD_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct add : binary { value attributes() const { auto a = base_attributes(); a["commutative"] = true; return a; } std::string point_function() const { return "+"; } auto apply() const { return [](auto x, auto y) { return x + y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/allocate.hpp000066400000000000000000000105071510465702400235070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ALLOCATE_HPP #define MIGRAPHX_GUARD_OPERATORS_ALLOCATE_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Static allocate: * No inputs: `allocate()` * `this.s` attribute set to the static output shape of the buffer. * `this.s` attribute can be set to a dynamic output shape; however this will allocate the maximum * buffer size for that case * * Dynamic allocate: * One input: `allocate(output_dims)` * `output_dims` are the output buffer dimensions and has a static shape. * Either `this.s` or `this.buf_type` (but not both) must be set to calculate the dynamic output * shape at compute time. If `this.buf_type` is set, the compute_shape() of allocate at compile time * will have dynamic_dimensions from {0, max_int} with rank = output_dims.ndim(). If `this.s` is set * then the compute_shape() will output `this.s`; `this.s` should be a dynamic shape. */ struct allocate { optional s; // for dynamic allocate to set the buffer type optional buf_type; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape"), f(self.buf_type, "buf_type")); } std::string name() const { return "allocate"; } shape compute_shape(const std::vector& inputs) const { if(s.has_value()) { if(buf_type.has_value()) { MIGRAPHX_THROW("ALLOCATE: shape and buf_type attributes both set"); } if(inputs.size() == 1) { migraphx::check_shapes{inputs, *this, false}.only_dims(1); } else { migraphx::check_shapes{inputs, *this, false}.has(0); } return s.value(); } else { if(not buf_type.has_value()) { MIGRAPHX_THROW("ALLOCATE: shape and buf_type attributes both not set"); } migraphx::check_shapes{inputs, *this, false}.has(1).only_dims(1); const auto& out_dims = inputs.at(0); std::size_t max_val = std::numeric_limits::max(); std::vector dyn_dims(out_dims.lens().at(0), shape::dynamic_dimension{0, max_val}); return {buf_type.value(), dyn_dims}; } } argument compute(const shape& output_shape, const std::vector& args) const { if(args.empty()) { return argument{output_shape}; } else { std::vector output_dims(output_shape.ndim()); args.at(0).visit([&](auto a) { output_dims.assign(a.begin(), a.end()); }); if(s) { return argument{shape{s->type(), output_dims}}; } return argument{shape{buf_type.value(), output_dims}}; } } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/argmax.hpp000066400000000000000000000076331510465702400232100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ARGMAX_HPP #define MIGRAPHX_GUARD_OPERATORS_ARGMAX_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct argmax { int64_t axis = 0; bool select_last_index = false; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis"), f(self.select_last_index, "select_last_index")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "argmax"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& s0 = inputs[0]; if(s0.dynamic()) { auto dyn_dims = s0.dyn_dims(); dyn_dims[axis] = {1, 1}; return {shape::int64_type, dyn_dims}; } else { auto lens = s0.lens(); lens[axis] = 1; return {shape::int64_type, lens}; } } template int64_t calc_argmax(T& input, std::vector& indices, size_t item_num) const { auto max_val = input(indices.begin(), indices.end()); int64_t max_index = 0; for(std::size_t i = 1; i < item_num; ++i) { indices[axis] = i; auto cur_val = input(indices.begin(), indices.end()); if(max_val < cur_val) { max_val = cur_val; max_index = i; } else if(select_last_index and float_equal(max_val, cur_val)) { max_index = i; } } return max_index; } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; auto batch_item_num = args.front().get_shape().lens()[axis]; result.visit([&](auto output) { args[0].visit([&](auto input) { par_for(dyn_out.computed_shape.elements(), [&](auto i) { auto data_idx = dyn_out.computed_shape.multi(i); output[i] = this->calc_argmax(input, data_idx, batch_item_num); }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/argmin.hpp000066400000000000000000000071311510465702400231770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ARGMIN_HPP #define MIGRAPHX_GUARD_OPERATORS_ARGMIN_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct argmin { int64_t axis = 0; bool select_last_index = false; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis"), f(self.select_last_index, "select_last_index")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "argmin"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1); auto lens = inputs[0].lens(); lens[axis] = 1; return {shape::int64_type, lens}; } template int64_t calc_argmin(T& input, std::vector& indices, size_t item_num) const { auto min_val = input(indices.begin(), indices.end()); int64_t min_index = 0; for(std::size_t i = 1; i < item_num; ++i) { indices[axis] = i; auto cur_val = input(indices.begin(), indices.end()); if(min_val > cur_val) { min_val = cur_val; min_index = i; } else if(select_last_index and float_equal(min_val, cur_val)) { min_index = i; } } return min_index; } argument compute(const shape& output_shape, std::vector args) const { argument result{output_shape}; std::size_t batch_item_num = args.front().get_shape().lens()[axis]; result.visit([&](auto output) { args[0].visit([&](auto input) { par_for(output_shape.elements(), [&](auto i) { auto data_idx = output_shape.multi(i); output[i] = this->calc_argmin(input, data_idx, batch_item_num); }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/as_shape.hpp000066400000000000000000000042461510465702400235110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_AS_SHAPE_HPP #define MIGRAPHX_GUARD_OPERATORS_AS_SHAPE_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct as_shape { shape s; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape")); } std::string name() const { return "as_shape"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(1).standard(); assert(inputs.front().elements() >= s.elements()); return s; } argument compute(shape output_shape, std::vector args) const { return args.front().reshape(output_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/asin.hpp000066400000000000000000000031411510465702400226510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ASIN_HPP #define MIGRAPHX_GUARD_OPERATORS_ASIN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct asin : unary { auto apply() const { return [](auto x) { return std::asin(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/asinh.hpp000066400000000000000000000030661510465702400230270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ASINH_HPP #define MIGRAPHX_GUARD_OPERATORS_ASINH_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct asinh : unary { auto apply() const { return [](auto x) { return std::asinh(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/atan.hpp000066400000000000000000000031411510465702400226420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ATAN_HPP #define MIGRAPHX_GUARD_OPERATORS_ATAN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct atan : unary { auto apply() const { return [](auto x) { return std::atan(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/atanh.hpp000066400000000000000000000030661510465702400230200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ATANH_HPP #define MIGRAPHX_GUARD_OPERATORS_ATANH_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct atanh : unary { auto apply() const { return [](auto x) { return std::atanh(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/binary.hpp000066400000000000000000000075361510465702400232170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_BINARY_HPP #define MIGRAPHX_GUARD_OPERATORS_BINARY_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { template struct binary : op_name { std::string point_function() const { return this->name(); } std::string point_op() const { const auto& self = static_cast(*this); auto pf = self.point_function(); if(pf.empty()) return {}; if(with_char(::ispunct)(pf.front())) { return "${0} " + pf + " ${1}"; } else { return "${function:" + pf + "}(${0}, ${1})"; } } value base_attributes() const { const auto& self = static_cast(*this); return {{"pointwise", true}, {"point_op", self.point_op()}, {"fillcolor", "#008080" /* teal */}}; } value attributes() const { return base_attributes(); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, static_cast(*this), true} .has(2) .same_type() .same_dims(); auto s0 = inputs.at(0); auto s1 = inputs.at(1); if(s0.dynamic() or s1.dynamic()) { if(s0 == s1) return s0; MIGRAPHX_THROW("BINARY: " + point_function() + ": fixed-dyn shape for inputs"); } else if(s0 == s1 and s0.packed()) { return s0; } else if(s0.packed() != s1.packed()) { return s0.packed() ? s0 : s1; } else if(s0.broadcasted() != s1.broadcasted()) { return s0.broadcasted() ? s1.with_lens(s0.lens()) : s0.with_lens(s0.lens()); } else { return shape::from_permutation(s0.type(), s0.lens(), find_permutation({s0, s1})); } } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; visit_all(result, args[0], args[1])([&](auto output, auto input1, auto input2) { par_transform(input1.begin(), input1.end(), input2.begin(), output.begin(), static_cast(*this).apply()); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/bit_cast.hpp000066400000000000000000000070701510465702400235140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_BIT_CAST_HPP #define MIGRAPHX_GUARD_OPERATORS_BIT_CAST_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Obtain a value of type `target_type` by reinterpreting * the object represnetaion of the input. Originally used * for casting from fp8e4m3fn to fp8e4m3fnuz. */ struct bit_cast : unary { shape::type_t target_type; template static auto reflect(Self& self, F f) { return pack(f(self.target_type, "target_type")); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& input = inputs.at(0); std::size_t target_type_size; shape::visit(target_type, [&](auto as) { target_type_size = as.size(); }); if(input.type_size() != target_type_size) { MIGRAPHX_THROW("BIT_CAST: target_type has different type_size from input's"); } if(input.dynamic()) { return {target_type, input.dyn_dims()}; } else { return {target_type, input.lens(), input.strides()}; } } std::string point_op() const { return "${function:bit_cast}<" + shape::cpp_type(target_type) + ">(${0})"; } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; result.visit([&](auto output) { using otype = typename decltype(output)::value_type; args[0].visit([&](auto input) { using itype = typename decltype(input)::value_type; if constexpr(sizeof(otype) == sizeof(itype)) { par_transform(input.begin(), input.end(), output.begin(), [&](auto x) { return migraphx::bit_cast(x); }); } else { // not possible to hit this unless somehow the types change after compute_shape // is called MIGRAPHX_THROW("BIT_CAST: type size mismatch"); } }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/bitwise_and.hpp000066400000000000000000000050541510465702400242140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_BITWISE_AND_HPP #define MIGRAPHX_GUARD_OPERATORS_BITWISE_AND_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct bitwise_and : binary { value attributes() const { auto a = base_attributes(); a["commutative"] = true; return a; } std::string point_function() const { return "&"; } shape compute_shape(const std::vector& inputs) const { auto result = binary::compute_shape(inputs); if(not shape::is_integral(result.type())) { MIGRAPHX_THROW("BITWISE_AND: only supports integral types"); } return result; } auto apply() const { return [](auto x, auto y) { if constexpr(std::is_integral{} and std::is_integral{}) { // NOLINTNEXTLINE(hicpp-signed-bitwise) return x & y; } else { MIGRAPHX_THROW("BITWISE_AND: only supports integral types"); // will never return the next line; needed for return template typing return x; } }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/broadcast.hpp000066400000000000000000000146261510465702400236730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_BROADCAST_HPP #define MIGRAPHX_GUARD_OPERATORS_BROADCAST_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * 1 input version: * Broadcasts a tensor from the original shape to the broadcast_lens by setting the stride of * broadcasted dimensions to zero. `axis` attribute for a 1D input shape is the output dimension * that stays the same. * ex: broadcasting shape [1024] -> [4, 1024, 3] has axis = 1. * * For higher rank input shapes, axis is an offset parameter for the broadcasting. * Such that this operator would work in the opposite direction of NumPy broadcasting * (left-most to rightwards element-wise comparison) * ex: broadcasting shape [2, 2] -> [2, 2, 3] with axis = 0 * * 2 input version: * Broadcast the first input 1D shape into the second input shape based on the axis parameter. * Handles broadcasting a 1D static shape into a higher rank dynamic shape. * broadcast_lens is not used */ struct broadcast { uint64_t axis = 0; std::vector broadcast_lens = {}; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis"), f(self.broadcast_lens, "out_lens")); } std::string name() const { return "broadcast"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2); auto s0 = inputs.at(0); auto t = s0.type(); if(inputs.size() == 1) { // the ONNX broadcast op is deprecated now, so not handling the negative // value of axis anymore if(s0.dynamic()) MIGRAPHX_THROW( "BROADCAST: Single dynamic input shape not supported. Use two inputs."); if(axis >= broadcast_lens.size()) { MIGRAPHX_THROW("BROADCAST : axis " + migraphx::to_string(axis) + " is out of range"); } if(broadcast_lens.size() - axis < s0.lens().size()) { MIGRAPHX_THROW("BROADCAST: (broadcast ndims - axis) is less than s0 ndims"); } if(not std::equal(s0.lens().begin(), s0.lens().end(), broadcast_lens.begin() + axis)) { MIGRAPHX_THROW("BROADCAST: when broadcasting, succeeding sizes must match"); } std::vector bcast_strides(broadcast_lens.size(), 0); std::copy(s0.strides().begin(), s0.strides().end(), bcast_strides.begin() + axis); shape output{t, broadcast_lens, std::move(bcast_strides)}; if(output.elements() < s0.elements()) { // don't think this can occur? MIGRAPHX_THROW("BROADCAST: output size must be greater than or equal to s0 size"); } return output; } else { // two inputs auto s1 = inputs.at(1); if(s0.dynamic()) { MIGRAPHX_THROW("BROADCAST_2in: s0 is a dynamic shape, does not handle broadcasting " "a dynamic shape"); } if(s0.ndim() != 1) { MIGRAPHX_THROW("BROADCAST_2in: s0 has ndim " + migraphx::to_string(s0.ndim()) + ", only handle ndim = 1"); } if(axis >= s1.ndim()) { MIGRAPHX_THROW("BROADCAST_2in: axis " + migraphx::to_string(axis) + " is out of range"); } if(s1.dynamic()) { s0 = s0.to_dynamic(); if(s0.dyn_dims()[0] != s1.dyn_dims()[axis]) { MIGRAPHX_THROW("BROADCAST_2in: s0 length doesn't match with dynamic s1 axis " "dimension length (" + migraphx::to_string(s0.dyn_dims()[0]) + " != " + migraphx::to_string(s1.dyn_dims()[axis]) + ")"); } return s1; } if(s0.lens()[0] != s1.lens()[axis]) { MIGRAPHX_THROW("BROADCAST_2in: s0 length doesn't match with static s1 axis " "dimension length (" + migraphx::to_string(s0.lens()[0]) + " != " + migraphx::to_string(s1.lens()[axis]) + ")"); } std::vector bcast_strides(s1.ndim(), 0); std::copy(s0.strides().begin(), s0.strides().end(), bcast_strides.begin() + axis); shape output{t, s1.lens(), std::move(bcast_strides)}; return output; } } argument compute(const dyn_output& dyn_out, std::vector args) const { return args[0].reshape(dyn_out.computed_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } value attributes() const { return {{"fillcolor", "#9ACD32" /* yellowgreen */}}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/broadcast_for_dot.hpp000066400000000000000000000075661510465702400254140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_BROADCAST_FOR_DOT_HPP #define MIGRAPHX_GUARD_OPERATORS_BROADCAST_FOR_DOT_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Broadcast dimensions between two tensors for the `dot` operator. * Essentially broadcasts between two shapes for dimensions other than the last two. * This operator is only needed if one of the shapes are dynamic. * Example: * a = shape[{1, 4}, 3, 248, 248] * b = shape[248, 365] * broadcast_for_dot(a, b) => shape[{1, 4}, 3, 248, 248] (no change) * broadcast_for_dot(b, a) => shape[{1, 4}, 3, 248, 365] */ struct broadcast_for_dot { std::string name() const { return "broadcast_for_dot"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2); check_shapes{{inputs.at(0)}, *this, true}.min_ndims(2); check_shapes{{inputs.at(1)}, *this, true}.min_ndims(2); auto s0 = inputs.at(0); auto s1 = inputs.at(1); if(s0.dynamic() or s1.dynamic()) { s0 = s0.to_dynamic(); s1 = s1.to_dynamic(); auto dds0_it = s0.dyn_dims().end() - 2; auto dds1_it = s1.dyn_dims().end() - 2; std::vector sliced_dds0{s0.dyn_dims().begin(), dds0_it}; std::vector sliced_dds1{s1.dyn_dims().begin(), dds1_it}; auto output_dyn_dims = compute_broadcasted_dyn_dims(sliced_dds0, sliced_dds1); output_dyn_dims.insert(output_dyn_dims.end(), dds0_it, s0.dyn_dims().end()); return {s0.type(), output_dyn_dims}; } else { auto l0_it = s0.lens().begin() + s0.ndim() - 2; std::vector l0_broadcasted_lens(s0.lens().begin(), l0_it); auto l1_it = s1.lens().begin() + s1.ndim() - 2; std::vector l1_broadcasted_lens(s1.lens().begin(), l1_it); auto output_lens = compute_broadcasted_lens(l0_broadcasted_lens, l1_broadcasted_lens); output_lens.insert(output_lens.end(), l0_it, s0.lens().end()); return make_bcast_shape(s0, output_lens); } } argument compute(const dyn_output& dyn_out, std::vector args) const { return args[0].reshape(dyn_out.computed_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } value attributes() const { return {{"fillcolor", "#9ACD32" /* yellowgreen */}}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/broadcast_with_dims.hpp000066400000000000000000000072121510465702400257330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_BROADCAST_WITH_DIMS_HPP #define MIGRAPHX_GUARD_OPERATORS_BROADCAST_WITH_DIMS_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Broadcast the input tensor to the shape defined by the values of the second input. * Used as `broadcast_with_dims(input_tensor, dims)`, where dims is a vector of integer dimensions. * `input_tensor` must be broadcastable with `dims`, otherwise this operator with throw at compute. * This operator can be replaced with `multibroadcast(input_tensor)` if the `dims` vector is * constant. * * Example: * input_tensor shape: lens = {2, 3}, strides = {3, 1} * dims = [4, 1, 3] * output shape: lens = {4, 2, 3}, strides = {0, 3, 1} */ struct broadcast_with_dims { std::string name() const { return "broadcast_with_dims"; } shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this, true}.has(2); // check that second input has a static shape (void)migraphx::check_shapes{inputs.begin() + 1, inputs.end(), *this, false}; // output tensor rank is greater of input_tensor rank or length of dims vector const auto& input_tensor_shape = inputs.at(0); const auto& dims_shape = inputs.at(1); size_t out_ndim = std::max(input_tensor_shape.ndim(), dims_shape.lens().at(0)); std::size_t max_int = std::numeric_limits::max(); std::vector dyn_dims(out_ndim, shape::dynamic_dimension{0, max_int}); return {input_tensor_shape.type(), dyn_dims}; } argument compute(const shape& output_shape, const std::vector& args) const { auto s0 = args.at(0).get_shape(); const auto& in_lens = s0.lens(); std::vector dims_input(output_shape.ndim()); args.at(1).visit([&](auto a) { dims_input.assign(a.begin(), a.end()); }); auto out_lens = compute_broadcasted_lens(in_lens, dims_input); auto out_shape = make_bcast_shape(s0, out_lens); return args[0].reshape(out_shape); } value attributes() const { return {{"fillcolor", "#9ACD32" /* yellowgreen*/}}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/capture.hpp000066400000000000000000000047501510465702400233710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_CAPTURE_HPP #define MIGRAPHX_GUARD_OPERATORS_CAPTURE_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct capture { std::size_t ins_index; std::function)> f{}; template static auto reflect(Self& self, F f) { return pack(f(self.ins_index, "ins_index")); } std::string name() const { return "capture"; } shape compute_shape(std::vector inputs) const { return inputs.front(); } // the context argument is added to prevent the op from be eliminated by // constant propagation argument compute(context&, const shape&, const std::vector& args) const { if(f) { f(ins_index, args); } else { MIGRAPHX_THROW("CAPTURE: callback function is not callable!"); } return args.front(); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/ceil.hpp000066400000000000000000000031021510465702400226300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_CEIL_HPP #define MIGRAPHX_GUARD_OPERATORS_CEIL_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct ceil : unary { auto apply() const { return [](auto x) { return std::ceil(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/clip.hpp000066400000000000000000000047011510465702400226510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_CLIP_HPP #define MIGRAPHX_GUARD_OPERATORS_CLIP_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct clip { std::string name() const { return "clip"; } value attributes() const { return {{"pointwise", true}, {"point_op", "${function:min}(${function:max}(${1}, ${0}), ${2})"}}; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(3).same_type().same_dims(); return inputs.front(); } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; visit_all(result, args[0], args[1], args[2])([&](auto output, auto x, auto min, auto max) { par_for(dyn_out.computed_shape.elements(), [&](auto i) { output[i] = std::min(std::max(min[i], x[i]), max[i]); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/common.hpp000066400000000000000000000045031510465702400232120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_COMMON_HPP #define MIGRAPHX_GUARD_OPERATORS_COMMON_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { // Specifies where to add the "extra" cell of padding if the // calculated padding is an odd number. // Padding mode is default_ for fixed shape padding. // same_lower and same_upper specify dynamic padding. // The odd cell goes at the beginning of the dimension // (same_lower) or end (same_upper). enum padding_mode_t { default_, // NOLINT same_lower, same_upper }; // The pooling modes must correspond 1-1 to the operators defined for struct parse_pooling. // Used in pooling and roialign operators. enum class pooling_mode { average, max, lpnorm }; // indicate rnn computation direction enum class rnn_direction { forward, reverse, bidirectional, }; MIGRAPHX_EXPORT std::ostream& operator<<(std::ostream& os, pooling_mode v); MIGRAPHX_EXPORT std::ostream& operator<<(std::ostream& os, rnn_direction v); } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/concat.hpp000066400000000000000000000144541510465702400231770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_CONCAT_HPP #define MIGRAPHX_GUARD_OPERATORS_CONCAT_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct concat { int64_t axis = 0; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "concat"; } std::vector compute_offsets(const shape& output_shape, const std::vector& args) const { auto n_dims = args[0].get_shape().lens().size(); std::vector offsets; std::vector offset(n_dims, 0); offset[axis] = 0; for(const auto& arg : args) { offsets.push_back(output_shape.index(offset)); offset[axis] += arg.get_shape().lens()[axis]; } return offsets; } shape normalize_compute_shape(std::vector inputs) const { // inputs can contain 1 or more shapes (variadic). compute_shape_op ensures there must // be at least 1. check_shapes{inputs, *this, true}.same_ndims().same_type(); if(std::none_of(inputs.begin(), inputs.end(), [&](const shape& s) { return s.dynamic(); })) { // Static input shapes const auto& first_shape_lens = inputs.front().lens(); const auto& type = inputs.front().type(); for(std::size_t ll = 0; ll < first_shape_lens.size(); ll++) { if(ll != axis) { if(not std::all_of(inputs.begin(), inputs.end(), [&](auto s) { return s.lens()[ll] == first_shape_lens[ll]; })) { MIGRAPHX_THROW("CONCAT: all input dimensions should match along axis " + std::to_string(ll)); } } } std::size_t new_dim_axis = 0; for(const auto& input : inputs) { const auto& lens = input.lens(); new_dim_axis += lens[axis]; } std::vector new_lens = first_shape_lens; new_lens[axis] = new_dim_axis; return shape::from_permutation(type, new_lens, find_permutation(inputs)); } else if(std::all_of( inputs.begin(), inputs.end(), [&](const shape& s) { return s.dynamic(); })) { // Dynamic input shapes for(std::size_t index = 0; index < inputs[0].ndim(); index++) { if(index != axis) { if(not std::all_of(inputs.begin(), inputs.end(), [&](const shape& s) { return s.dyn_dims()[index] == inputs[0].dyn_dims()[index]; })) MIGRAPHX_THROW("CONCAT: all input dimensions should match in axis " + std::to_string(index)); } } std::size_t new_min = 0; std::size_t new_max = 0; for(const auto& input : inputs) { auto ddim = input.dyn_dims()[axis]; new_min += ddim.min; new_max += ddim.max; } auto new_dims = inputs[0].dyn_dims(); new_dims[axis] = migraphx::shape::dynamic_dimension{new_min, new_max}; return {inputs[0].type(), new_dims}; } else { MIGRAPHX_THROW("CONCAT: Cannot mix static and dynamic input shapes."); } } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; std::vector coffsets = compute_offsets(dyn_out.computed_shape, args); for(std::size_t l = 0; l < args.size(); l++) { auto argl = args[l]; visit_all(result, argl)([&](auto output, auto input) { auto slice_shape = shape{dyn_out.computed_shape.type(), input.get_shape().lens(), dyn_out.computed_shape.strides()}; auto slice = make_view(slice_shape, output.data() + coffsets[l]); std::copy(input.begin(), input.end(), slice.begin()); }); } return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/contiguous.hpp000066400000000000000000000060531510465702400241230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_CONTIGUOUS_HPP #define MIGRAPHX_GUARD_OPERATORS_CONTIGUOUS_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /// The contiguous operator takes a non-standard input tensor and returns /// the same tensor but in standard form. For example, if input tensor A which has lens = (4,5) /// is first transposed, i.e. lens = (5,4), this tensor's data layout remained the same /// during the transpose operation; only it's shape lengths and strides were changed. /// This leaves the tensor in a non-standard form. The contiguous operator copies the /// underlying data such that resulting tensor is returned to a standard form. struct contiguous { std::string name() const { return "contiguous"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); auto s0 = inputs.front(); if(s0.dynamic()) { return s0; } else { const auto& lens = s0.lens(); auto t = s0.type(); return {t, lens}; } } argument compute(const dyn_output& dyn_out, std::vector args) const { assert(dyn_out.computed_shape.standard()); argument result{dyn_out.computed_shape}; visit_all(result, args[0])([&](auto output, auto input) { shape_for_each(output.get_shape(), [&](const auto& idx) { output(idx.begin(), idx.end()) = input(idx.begin(), idx.end()); }); }); return result; } auto apply() const { return [](auto x) { return x; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/convert.hpp000066400000000000000000000066061510465702400234100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_CONVERT_HPP #define MIGRAPHX_GUARD_OPERATORS_CONVERT_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct convert : unary { shape::type_t target_type = shape::half_type; template static auto reflect(Self& self, F f) { return pack(f(self.target_type, "target_type")); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& input = inputs.at(0); if(input.dynamic()) { return {target_type, input.dyn_dims()}; } else { return {target_type, input.lens(), input.strides()}; } } std::string point_op() const { return "${function:convert}<" + shape::cpp_type(target_type) + ">(${0})"; } auto apply() const { auto type = target_type; return [type](auto x) { auto y = x; shape::visit(type, [&](auto as) { // clamping value between target_type's max and min doesn't work for NaNs, if(std::isnan(static_cast(x))) { y = as.nan(); } else if(shape::is_integral(type) and std::is_floating_point_v) { // for the floating point to integer conversion, clamp first and then convert to // avoid undefined behaviour y = as(std::min(std::max(static_cast(x), static_cast(as.min())), static_cast(as.max()))); } else { // clamp overflowing/underflowing values to min()/max() instead of +/-infinity // during downcasting y = std::min(std::max(as(x), as.min()), as.max()); } }); return y; }; } convert(shape::type_t t) : target_type{t} {} convert() {} }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/convolution.hpp000066400000000000000000000221301510465702400242750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_CONVOLUTION_HPP #define MIGRAPHX_GUARD_OPERATORS_CONVOLUTION_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Convolution operator. Does not support optimal dimensions for spatial dimensions. Returns empty * optimals. */ struct convolution { std::vector padding = {0, 0}; std::vector stride = {1, 1}; std::vector dilation = {1, 1}; int group = 1; padding_mode_t padding_mode = default_; template static auto reflect(Self& self, F f) { return pack(f(self.padding, "padding"), f(self.stride, "stride"), f(self.dilation, "dilation"), f(self.group, "group"), f(self.padding_mode, "padding_mode")); } std::string name() const { return "convolution"; } void check_attribute_size() const { if((padding.size() != stride.size() and (padding.size() / 2) != stride.size()) or stride.size() != dilation.size()) { MIGRAPHX_THROW("CONVOLUTION: inconsistent attribute sizes"); } } value attributes() const { return {{"normalize_padding", "padding"}, {"fillcolor", "#4682B4" /* steelblue */}}; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2).same_type().same_ndims().min_ndims(3); check_attribute_size(); // num of dims of input and attribute should match const auto input_ndim = inputs[0].ndim(); const auto padding_size = padding.size(); if(input_ndim != padding_size / 2 + 2 and input_ndim != padding_size + 2) { MIGRAPHX_THROW("CONVOLUTION: input and attribute size mismatch!"); } const shape& x_shape = inputs.at(0); const shape& w_shape = inputs.at(1); const size_t num_spatial_dims = input_ndim - 2; if(num_spatial_dims != this->kdims()) { MIGRAPHX_THROW("CONVOLUTION: input k-dims does not match attribute size"); } if(not x_shape.dynamic() and not w_shape.dynamic() and x_shape.lens().at(1) != (w_shape.lens().at(1) * group)) MIGRAPHX_THROW("CONVOLUTION: mismatched channel numbers"); if(x_shape.dynamic() or w_shape.dynamic()) { return dynamic_compute_shape(x_shape, w_shape); } else { return static_compute_shape(x_shape, w_shape); } } std::vector calc_conv_lens(std::vector x_lens, std::vector w_lens) const { const size_t num_spatial_dims = x_lens.size() - 2; std::vector ret = {}; // calculate the output shape of the convolution: ((W - K + 2P) / S) + 1 for(size_t i = 0; i < num_spatial_dims; i++) { if(x_lens[i] == 0 or w_lens[i] == 0) { // for handling when a dimension = 0 (opt of dynamic_dimension) ret.push_back(0); } else { auto padding_factor = 2 * padding[i]; if(padding.size() == 2 * num_spatial_dims) { // when padding is {x0_begin, x1_begin, ... x0_end , x1_end, ...} padding_factor = padding[i] + padding[i + num_spatial_dims]; } ret.push_back(std::size_t(std::max( 1, (x_lens[i + 2] - (1 + dilation[i] * (w_lens[i + 2] - 1)) + padding_factor) / stride[i] + 1))); } } return ret; } shape dynamic_compute_shape(shape x_shape, shape w_shape) const { std::vector output_dyn_dims = {}; output_dyn_dims.push_back(x_shape.to_dynamic().dyn_dims().at(0)); output_dyn_dims.push_back(w_shape.to_dynamic().dyn_dims().at(0)); const size_t num_spatial_dims = x_shape.ndim() - 2; if(padding_mode != default_) { for(std::size_t i = 0; i < num_spatial_dims; ++i) { auto ceil_div = [](std::size_t x, std::size_t y) { return (x + y - 1) / y; }; auto s = stride[i]; if(x_shape.dynamic()) { auto x = x_shape.dyn_dims()[i + 2]; std::set optimals{}; std::transform(x.optimals.begin(), x.optimals.end(), std::inserter(optimals, optimals.begin()), [&](auto o) { return ceil_div(o, s); }); output_dyn_dims.push_back( shape::dynamic_dimension{ceil_div(x.min, s), ceil_div(x.max, s), optimals}); } else { auto od = ceil_div(x_shape.lens()[i + 2], s); output_dyn_dims.push_back(shape::dynamic_dimension{od, od}); } } } else { // Does not compute for optimals auto min_spatial_dims = calc_conv_lens(x_shape.min_lens(), w_shape.max_lens()); auto max_spatial_dims = calc_conv_lens(x_shape.max_lens(), w_shape.min_lens()); for(size_t i = 0; i < num_spatial_dims; ++i) { output_dyn_dims.push_back( shape::dynamic_dimension{min_spatial_dims[i], max_spatial_dims[i], {}}); } } return shape{x_shape.type(), output_dyn_dims}; } shape static_compute_shape(shape x_shape, shape w_shape) const { std::vector output_lens{x_shape.lens()[0], w_shape.lens()[0]}; auto spatial_lens = calc_conv_lens(x_shape.lens(), w_shape.lens()); std::for_each(spatial_lens.begin(), spatial_lens.end(), [&output_lens](auto x) { output_lens.push_back(x); }); return x_shape.with_lens(output_lens); } size_t kdims() const { check_attribute_size(); return stride.size(); } argument compute(shape output_shape, std::vector args) const { std::vector new_padding; if(padding_mode != op::padding_mode_t::default_) { // auto-Calculate the padding sizes with calc_dyn_auto_pad auto input_lens = args[0].get_shape().lens(); auto weights_lens = args[1].get_shape().lens(); new_padding = padding_mode == op::same_upper ? calc_dyn_auto_pad(input_lens, weights_lens, stride, dilation, true) : calc_dyn_auto_pad(input_lens, weights_lens, stride, dilation, false); output_shape = compute_padded_shape( args[0].get_shape(), args[1].get_shape(), new_padding, stride, dilation); } else { // Use the padding that was given new_padding = padding; if(output_shape.dynamic()) { output_shape = normalize_compute_shape({args.at(0).get_shape(), args.at(1).get_shape()}); } } argument result{output_shape}; visit_all(result, args[0], args[1])([&](auto output, auto input, auto weights) { migraphx::convolution(output, input, weights, new_padding, stride, dilation, group); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/convolution_backwards.hpp000066400000000000000000000212161510465702400263220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_CONVOLUTION_BACKWARDS_HPP #define MIGRAPHX_GUARD_OPERATORS_CONVOLUTION_BACKWARDS_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct convolution_backwards { std::vector padding = {0, 0}; std::vector stride = {1, 1}; std::vector dilation = {1, 1}; padding_mode_t padding_mode = default_; int group = 1; template static auto reflect(Self& self, F f) { return pack(f(self.padding, "padding"), f(self.stride, "stride"), f(self.dilation, "dilation"), f(self.padding_mode, "padding_mode"), f(self.group, "group")); } std::string name() const { return "convolution_backwards"; } void check_attribute_size() const { if(padding.size() != stride.size() or stride.size() != dilation.size()) { MIGRAPHX_THROW("CONVOLUTION_BACKWARDS: inconsistent attribute sizes"); } } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2).same_type().same_ndims().min_ndims(3); const shape& x_shape = inputs.at(0); const shape& w_shape = inputs.at(1); if(x_shape.ndim() - 2 != this->kdims()) { MIGRAPHX_THROW("CONVOLUTION_BACKWARDS: input k-dims does not match attribute size"); } if(not x_shape.dynamic() and not w_shape.dynamic() and x_shape.lens().at(1) != (w_shape.lens().at(0))) { MIGRAPHX_THROW("CONVOLUTION_BACKWARDS: mismatched channel numbers"); } if(x_shape.dynamic() or w_shape.dynamic()) { return dynamic_compute_shape(x_shape, w_shape); } else { return static_compute_shape(x_shape, w_shape); } } std::vector calc_spatial_lens(std::vector x_lens, std::vector w_lens) const { std::vector spatial_lens(x_lens.size() - 2); // stride * (input - 1) + output_padding + ((kernel - 1) * dilation + 1) - padding_L - // padding_R. This assumes padding_L = padding_R and output_padding handled in parser. for(size_t i = 0; i < spatial_lens.size(); i++) { spatial_lens.at(i) = (std::size_t(std::max( 1, stride[i] * (x_lens[i + 2] - 1) + ((w_lens[i + 2] - 1) * dilation[i] + 1) - 2 * padding[i]))); } return spatial_lens; } shape dynamic_compute_shape(shape x_shape, shape w_shape) const { std::vector output_dyn_dims = {}; output_dyn_dims.push_back(x_shape.to_dynamic().dyn_dims().at(0)); output_dyn_dims.push_back(w_shape.to_dynamic().dyn_dims().at(1) * group); const std::size_t num_spatial_dims = x_shape.ndim() - 2; // Does not compute for optimals auto min_spatial_dims = calc_spatial_lens(x_shape.min_lens(), w_shape.min_lens()); auto max_spatial_dims = calc_spatial_lens(x_shape.max_lens(), w_shape.max_lens()); for(size_t i = 0; i < num_spatial_dims; ++i) { output_dyn_dims.push_back( shape::dynamic_dimension{min_spatial_dims[i], max_spatial_dims[i], {}}); } return shape{x_shape.type(), output_dyn_dims}; } shape static_compute_shape(shape x_shape, shape w_shape) const { std::vector output_lens{x_shape.lens()[0], w_shape.lens()[1] * group}; auto spatial_lens = calc_spatial_lens(x_shape.lens(), w_shape.lens()); std::for_each(spatial_lens.begin(), spatial_lens.end(), [&output_lens](auto x) { output_lens.push_back(x); }); return x_shape.with_lens(output_lens); } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; auto num_spatial_dims = this->kdims(); visit_all(result, args[0], args[1])([&](auto output, auto input, auto weights) { using type = typename decltype(output)::value_type; std::fill(output.begin(), output.end(), type{0}); auto in_lens = input.get_shape().lens(); auto in_n = in_lens[0]; auto in_c = in_lens[1]; auto wei = weights.get_shape().lens(); auto wei_n = wei[0]; auto wei_c = wei[1]; auto out_lens = dyn_out.computed_shape.lens(); std::vector win_size{in_c}; std::copy(in_lens.begin() + 2, in_lens.end(), std::back_inserter(win_size)); std::copy(wei.begin() + 2, wei.end(), std::back_inserter(win_size)); shape win_shape{dyn_out.computed_shape.type(), win_size}; par_dfor(in_n, wei_c)([&](int o, int k) { shape_for_each(win_shape, [&](const auto& idx_win) { const int w = idx_win[0]; auto input_dims_start = idx_win.begin() + 1; auto wei_dims_start = idx_win.begin() + num_spatial_dims + 1; std::vector win_start; win_start.reserve(num_spatial_dims); for(std::size_t n = 0; n < num_spatial_dims; ++n) { win_start.push_back(std::ptrdiff_t(*(input_dims_start + n) * stride[n]) - std::ptrdiff_t(padding[n])); } const int group_id = w / (wei_n / group); const int in_ch = group_id * wei_c + k; std::vector idx_out{o, in_ch}; for(size_t n = 0; n < num_spatial_dims; n++) { idx_out.push_back(win_start[n] + *(wei_dims_start + n) * dilation[n]); } std::vector idx_wei{w, k}; std::copy(wei_dims_start, idx_win.end(), std::back_inserter(idx_wei)); std::vector idx_in{o, w}; std::copy(input_dims_start, wei_dims_start, std::back_inserter(idx_in)); if(std::all_of( idx_out.begin() + 2, idx_out.end(), [&](auto ii) { return ii >= 0; }) and std::equal(idx_out.begin() + 2, idx_out.end(), out_lens.begin() + 2, out_lens.end(), std::less{})) { output(idx_out.begin(), idx_out.end()) += input(idx_in.begin(), idx_in.end()) * weights(idx_wei.begin(), idx_wei.end()); } }); }); }); return result; } size_t kdims() const { check_attribute_size(); return stride.size(); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/cos.hpp000066400000000000000000000031341510465702400225050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_COS_HPP #define MIGRAPHX_GUARD_OPERATORS_COS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct cos : unary { auto apply() const { return [](auto x) { return std::cos(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/cosh.hpp000066400000000000000000000031411510465702400226530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_COSH_HPP #define MIGRAPHX_GUARD_OPERATORS_COSH_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct cosh : unary { auto apply() const { return [](auto x) { return std::cosh(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/dequantizelinear.hpp000066400000000000000000000062751510465702400252760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_DEQUANTIZE_LINEAR_HPP #define MIGRAPHX_GUARD_OPERATORS_DEQUANTIZE_LINEAR_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct dequantizelinear { value attributes() const { // Note: point_op attribute is not used in this op. Instead, in // gpu compilation pipeline, rewrite_quantization will be invoked // from generate_pointwise() to rewrite this op. return {{"pointwise", true}}; } std::string name() const { return "dequantizelinear"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.same_dims().has(2, 3); if(inputs.size() == 3 and inputs[0].type() != inputs[2].type()) { MIGRAPHX_THROW("DEQUANTIZELINEAR: Zero point and input should be the same type."); } return inputs[0].with_lens(inputs[1].type(), inputs[0].lens()); } argument compute(const shape& output_shape, std::vector args) const { auto x = args.at(0); auto x_scale = args.at(1); std::vector zeros(output_shape.bytes(), 0); argument x_zero_point{{x.get_shape().type(), output_shape.lens()}, zeros.data()}; if(args.size() == 3) { x_zero_point = args.at(2); } argument result{output_shape}; visit_all(x, x_zero_point)([&](auto input, auto zero_pts) { visit_all(result, x_scale)([&](auto output, auto scales) { par_for(output_shape.elements(), [&](auto i) { output[i] = (static_cast(input[i]) - static_cast(zero_pts[i])) * scales[i]; }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/dimensions_of.hpp000066400000000000000000000055311510465702400245600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_DIMENSIONS_OF_HPP #define MIGRAPHX_GUARD_OPERATORS_DIMENSIONS_OF_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Returns the dimensions of the input argument from starting axis to ending axis. * Atleast `end` must be set to use this operator (set `end` to ndim for default ONNX behavior of * `Shape` operator) This should only be used for dynamic shapes as this can be simplified to a * literal for static shapes. */ struct dimensions_of { std::size_t start = 0; std::size_t end = 0; template static auto reflect(Self& self, F f) { return pack(f(self.start, "start"), f(self.end, "end")); } std::string name() const { return "dimensions_of"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this, true}.has(1); if(start >= end) { MIGRAPHX_THROW("DIMENSIONS_OF: start >= end. start = " + std::to_string(start) + ", end = " + std::to_string(end)); } return shape{shape::int64_type, {end - start}}; } argument compute(const shape& output_shape, std::vector args) const { argument result{output_shape}; auto input_lens = args[0].get_shape().lens(); result.visit([&](auto output) { std::copy(input_lens.cbegin() + start, input_lens.cbegin() + end, output.begin()); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/div.hpp000066400000000000000000000032061510465702400225030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_DIV_HPP #define MIGRAPHX_GUARD_OPERATORS_DIV_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct div : binary

{ std::string point_function() const { return "/"; } auto apply() const { return [](auto x, auto y) { return x / y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/dot.hpp000066400000000000000000000132211510465702400225050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_DOT_HPP #define MIGRAPHX_GUARD_OPERATORS_DOT_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Matrix multiplication of two tensors. */ struct dot { std::string name() const { return "dot"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.same_type().same_ndims().has(2); const shape& a = inputs.at(0); const shape& b = inputs.at(1); auto t = a.type(); if(not std::all_of(inputs.begin(), inputs.end(), [](auto s) { return s.ndim() >= 2; })) { MIGRAPHX_THROW("DOT: dot only accepts operands with 2 or more dimensions "); } if(a.dynamic() or b.dynamic()) { auto s0 = a.to_dynamic(); auto s1 = b.to_dynamic(); std::vector out_dyn_dims; // Check outer dynamic dimensions are compatible. // Must allow for intersection because of how simplify_dyn_ops // simplifies each broadcast_for_dot individually. bool same_outers = std::equal(s0.dyn_dims().begin(), s0.dyn_dims().end() - 2, s1.dyn_dims().begin(), s1.dyn_dims().end() - 2, [&](const auto& x, const auto& y) { auto intersect = x.intersection(y); if(intersect.has_value()) { out_dyn_dims.push_back(intersect.value()); return true; } return false; }); if(not same_outers) { MIGRAPHX_THROW("DOT: dynamic outer dimensions of A and B are not compatible: {" + to_string_range(s0.dyn_dims()) + "} x {" + to_string_range(s1.dyn_dims()) + "}"); } std::size_t dim_i = s0.ndim() - 2; std::size_t dim_j = s0.ndim() - 1; auto x = s0.dyn_dims()[dim_j]; auto y = s1.dyn_dims()[dim_i]; // check inner dimensions are compatible if(not x.intersection(y).has_value()) { MIGRAPHX_THROW("DOT: dynamic inner dimensions are not compatible: {" + to_string_range(s0.dyn_dims()) + "} x {" + to_string_range(s1.dyn_dims()) + "}"); } out_dyn_dims.push_back(s0.dyn_dims()[dim_i]); out_dyn_dims.push_back(s1.dyn_dims()[dim_j]); return {t, out_dyn_dims}; } else { // only handle the case that all the dimensions except the last two are the same if(not std::equal( a.lens().rbegin() + 2, a.lens().rend(), b.lens().rbegin() + 2, b.lens().rend())) { MIGRAPHX_THROW("DOT: static outer dimensions of A and B mismatch: {" + to_string_range(a.lens()) + "} x {" + to_string_range(b.lens()) + "}"); } std::size_t dim_0 = a.ndim() - 2; std::size_t dim_1 = a.ndim() - 1; if(a.lens()[dim_1] != b.lens()[dim_0]) { MIGRAPHX_THROW("DOT: static inner dimensions do not match: {" + to_string_range(a.lens()) + "} x {" + to_string_range(b.lens()) + "}"); } auto out_lens = a.lens(); out_lens[dim_1] = b.lens()[dim_1]; return {t, out_lens}; } } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result = argument{dyn_out.computed_shape}; visit_all(result, args[0], args[1])( [&](auto cmat, auto amat, auto bmat) { gemm(cmat, amat, bmat, 1.0f, 0.0f); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/elu.hpp000066400000000000000000000036311510465702400225100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ELU_HPP #define MIGRAPHX_GUARD_OPERATORS_ELU_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct elu : unary { float alpha = 1; std::string point_op() const { return "${function:where}(${0} > 0, ${0}, ${alpha} * (${function:exp}(${0}) - 1))"; } template static auto reflect(Self& self, F f) { return pack(f(self.alpha, "alpha")); } auto apply() const { return [&](auto x) { return x > 0 ? x : alpha * std::expm1(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/equal.hpp000066400000000000000000000034451510465702400230350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_EQUAL_HPP #define MIGRAPHX_GUARD_OPERATORS_EQUAL_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct equal : binary { value attributes() const { auto a = base_attributes(); a["commutative"] = true; return a; } std::string point_function() const { return "=="; } auto apply() const { return [](auto x, auto y) { return float_equal(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/erf.hpp000066400000000000000000000031131510465702400224720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ERF_HPP #define MIGRAPHX_GUARD_OPERATORS_ERF_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct erf : unary { auto apply() const { return [](auto x) { return std::erf(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/exp.hpp000066400000000000000000000031341510465702400225150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_EXP_HPP #define MIGRAPHX_GUARD_OPERATORS_EXP_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct exp : unary { auto apply() const { return [](auto x) { return std::exp(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/fill.hpp000066400000000000000000000047731510465702400226610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_FILL_HPP #define MIGRAPHX_GUARD_OPERATORS_FILL_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * fill(default_value, output_buffer) * Fill an output buffer with the given default_value. * Note that if the default_value is a literal and the output_buffer * has a static shape this operator can be replaced with a literal. */ struct fill { std::string name() const { return "fill"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2).same_type(); if(inputs.at(0).dynamic() or inputs.at(0).elements() != 1) { MIGRAPHX_THROW("FILL: default_value is dynamic or more than one element"); } return inputs.back(); } argument compute(const dyn_output& dyn_out, std::vector args) const { visit_all(args[0], args[1])([&](auto value, auto output) { par_for(dyn_out.computed_shape.elements(), [&](auto i) { output[i] = value.front(); }); }); return args[1]; } std::ptrdiff_t output_alias(const std::vector&) const { return 1; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/fixed_pad.hpp000066400000000000000000000055311510465702400236470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_FIXED_PAD_HPP #define MIGRAPHX_GUARD_OPERATORS_FIXED_PAD_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Pads an input with dynamic shape to its maximum dimensions. * No-op for a static shape input. * The main use for this op versus the standard pad op is that it can * accept a dynamic input shape and convert it to a padded static shape. */ struct fixed_pad { std::string name() const { return "fixed_pad"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& s0 = inputs.front(); if(s0.dynamic()) { return {s0.type(), s0.max_lens()}; } return s0; } argument compute(const shape& output_shape, std::vector args) const { const auto& input_arg = args.front(); auto input_shape = input_arg.get_shape(); if(input_shape == output_shape) return input_arg; argument out{output_shape}; visit_all(out, input_arg)([&](auto output, auto input) { par_for(input_shape.elements(), [&](auto i) { auto idx = input_shape.multi(i); output[idx] = input[idx]; }); }); return out; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/flatten.hpp000066400000000000000000000075451510465702400233700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_FLATTEN_HPP #define MIGRAPHX_GUARD_OPERATORS_FLATTEN_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct flatten { int64_t axis = 1; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min, normalize_attribute::include_max}; return {{"normalize_axes", normalize}}; } std::string name() const { return "flatten"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& s = inputs[0]; if(s.dynamic()) { // Doesn't handle optimals auto min_lens = s.min_lens(); auto max_lens = s.max_lens(); // If any of the opt values is 0, output opt will be 0 shape::dynamic_dimension x = { std::accumulate( min_lens.begin(), min_lens.begin() + axis, std::size_t{1}, std::multiplies<>{}), std::accumulate( max_lens.begin(), max_lens.begin() + axis, std::size_t{1}, std::multiplies<>{}), {}}; shape::dynamic_dimension y = { std::accumulate( min_lens.begin() + axis, min_lens.end(), std::size_t{1}, std::multiplies<>{}), std::accumulate( max_lens.begin() + axis, max_lens.end(), std::size_t{1}, std::multiplies<>{}), {}}; return {s.type(), {x, y}}; } else { auto&& lens = s.lens(); auto x = std::accumulate( lens.begin(), lens.begin() + axis, std::size_t{1}, std::multiplies<>{}); auto y = std::accumulate( lens.begin() + axis, lens.end(), std::size_t{1}, std::multiplies<>{}); return {s.type(), {x, y}}; } } argument compute(const dyn_output& dyn_out, std::vector args) const { assert(dyn_out.computed_shape.standard()); argument result{dyn_out.computed_shape}; visit_all(result, args[0])([&](auto output, auto input) { std::copy(input.begin(), input.end(), output.begin()); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/floor.hpp000066400000000000000000000031071510465702400230420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_FLOOR_HPP #define MIGRAPHX_GUARD_OPERATORS_FLOOR_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct floor : unary { auto apply() const { return [](auto x) { return std::floor(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/fmod.hpp000066400000000000000000000034121510465702400226450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_FMOD_HPP #define MIGRAPHX_GUARD_OPERATORS_FMOD_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct fmod : binary { std::string name() const { return "fmod"; } value attributes() const { auto a = base_attributes(); a["commutative"] = false; return a; } auto apply() const { return [](auto x, auto y) { return std::fmod(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/gather.hpp000066400000000000000000000122241510465702400231730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_GATHER_HPP #define MIGRAPHX_GUARD_OPERATORS_GATHER_HPP #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct gather { int64_t axis = 0; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "gather"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2); shape data = inputs[0]; shape indices = inputs[1]; auto type = data.type(); // If index_dims is dynamic, convert the data to dynamic too. if(indices.dynamic()) { data = data.to_dynamic(); } if(data.dynamic()) { auto dims = data.dyn_dims(); dims.erase(dims.begin() + axis); if(not indices.scalar()) { auto index_dims = indices.to_dynamic().dyn_dims(); dims.insert(dims.begin() + axis, index_dims.begin(), index_dims.end()); } return {type, dims}; } else { // Both data and indices are static. indices may be scalar auto lens = data.lens(); lens.erase(lens.begin() + axis); if(not indices.scalar()) { auto ind_lens = indices.lens(); lens.insert(lens.begin() + axis, ind_lens.begin(), ind_lens.end()); } // for scalar output if(lens.empty()) { return {type}; } return {type, lens}; } } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; // negative axis means counting dimensions from back auto lens = args[0].get_shape().lens(); std::size_t axis_dim_size = lens[axis]; // max dimension in axis visit_all(result, args[0])([&](auto output, auto data) { args[1].visit([&](auto indices) { if(dyn_out.computed_shape.scalar()) { auto in_index = indices.front(); in_index = (in_index < 0) ? in_index + axis_dim_size : in_index; output[0] = data[in_index]; } else { auto out_lens = data.get_shape().lens(); out_lens[axis] = indices.get_shape().elements(); migraphx::shape out_comp_shape{data.get_shape().type(), out_lens}; shape_for_each(out_comp_shape, [&](const auto& out_idx_v, size_t out_idx) { auto data_idx = out_idx_v; auto in_index = indices[data_idx[axis]]; in_index = (in_index < 0) ? in_index + axis_dim_size : in_index; // don't go out of bounds: https://github.com/ROCm/AMDMIGraphX/issues/2838 assert(in_index >= 0 and in_index < axis_dim_size); data_idx[axis] = in_index; output[out_idx] = data(data_idx.begin(), data_idx.end()); }); } }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/gathernd.hpp000066400000000000000000000230011510465702400235100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_GATHERND_HPP #define MIGRAPHX_GUARD_OPERATORS_GATHERND_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct gathernd { int batch_dims = 0; template static auto reflect(Self& self, F f) { return pack(f(self.batch_dims, "batch_dims")); } std::string name() const { return "gathernd"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2); auto i_shape = inputs.back(); auto data_shape = inputs.front(); auto r = data_shape.ndim(); auto q = i_shape.ndim(); size_t k; if(i_shape.dynamic()) { // the rank of the output is a function of k, so it must be fixed. if(not i_shape.dyn_dims().back().is_fixed()) { MIGRAPHX_THROW( "GATHERND: last dimension of indices tensor must be fixed (min=max)"); } k = i_shape.dyn_dims().back().min; } else k = i_shape.lens().back(); // Begin input validation checks. int output_ndim = int(q) + r - k - batch_dims - 1; if(k > r - batch_dims) { MIGRAPHX_THROW("GATHERND: Indices of length " + std::to_string(k) + " cannot be used to access data of rank " + std::to_string(r - batch_dims)); } if(batch_dims >= q or batch_dims >= r) { MIGRAPHX_THROW("GATHERND: rank of an input cannot be less than batch_dims=" + std::to_string(batch_dims)); } if(output_ndim < 0) { MIGRAPHX_THROW("GATHERND: Indices too large for static data input: k=" + std::to_string(k)); } if(migraphx::none_of(inputs, [](auto v) { return v.dynamic(); })) { auto indices_lens_iter = i_shape.lens().begin(); // A rank 0 output is a scalar if(output_ndim == 0) return shape{data_shape.type(), {1}}; // Part of the output shape comes from indices tensor, part from data tensor std::vector output_lens(output_ndim); std::copy(indices_lens_iter, indices_lens_iter + (q - 1), output_lens.begin()); // fill the rest of output shape from data tensor if(k + batch_dims < r) { auto data_lens = data_shape.lens(); std::copy(data_lens.begin() + batch_dims + k, data_lens.end(), output_lens.begin() + q - 1); } shape output_shape{data_shape.type(), output_lens}; return output_shape; } else { // If one or both inputs are dynamic shapes, the output is dynamic. // Make both inputs dynamic to simplify computations. data_shape = data_shape.to_dynamic(); i_shape = i_shape.to_dynamic(); // A rank 0 output is a scalar if(output_ndim == 0) return shape(data_shape.type(), {shape::dynamic_dimension({1, 1})}); // Part of the output shape comes from indices tensor, part from data tensor std::vector output_dims(output_ndim); std::copy(i_shape.dyn_dims().begin(), i_shape.dyn_dims().begin() + q - 1, output_dims.begin()); // fill the rest of output shape from data tensor if(k + batch_dims < r) { auto data_dims = data_shape.dyn_dims(); std::copy(data_dims.begin() + batch_dims + k, data_dims.begin() + r, output_dims.begin() + q - 1); } shape output_shape(data_shape.type(), output_dims); return output_shape; } } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; visit_all(result, args[0])([&](auto output, auto data) { get_all(args[1])([&](auto indices) { auto indices_shape = indices.get_shape(); auto indices_shape_lens = indices_shape.lens(); auto data_shape = data.get_shape(); auto data_shape_lens = data_shape.lens(); auto k = indices_shape.lens().back(); const auto num_slice_dims = k; std::size_t num_slices = std::accumulate(indices_shape_lens.begin(), indices_shape_lens.end() - 1, 1, std::multiplies()); std::size_t slice_size = std::accumulate(data_shape_lens.begin() + k + batch_dims, data_shape_lens.end(), 1, std::multiplies()); std::size_t num_batches = std::accumulate(data_shape_lens.begin(), data_shape_lens.begin() + batch_dims, 1, std::multiplies()); std::size_t data_batch_stride = std::accumulate(data_shape_lens.begin() + batch_dims, data_shape_lens.end(), 1, std::multiplies()); auto num_slices_per_batch = num_slices / num_batches; std::vector sizes_from_slice_dims(num_slice_dims); { auto running_product = slice_size; for(std::size_t i = 0; i < num_slice_dims; ++i) { sizes_from_slice_dims[num_slice_dims - 1 - i] = running_product; running_product *= data_shape_lens[batch_dims + num_slice_dims - 1 - i]; } } std::vector input_slice_offsets(num_slices); par_for(num_slices, [&](const auto i) { std::size_t batch_idx = i / num_slices_per_batch; auto slice_indices = indices.begin() + (i * num_slice_dims); std::size_t relative_slice_offset = 0; for(size_t dim_idx = 0; dim_idx < num_slice_dims; ++dim_idx) { int64_t index = *(slice_indices + dim_idx); const std::size_t input_dim_idx = batch_dims + dim_idx; const auto input_dim = data_shape_lens[input_dim_idx]; if(index < -static_cast(input_dim) or index >= static_cast(input_dim)) MIGRAPHX_THROW("GatherND: index " + std::to_string(index) + " is out of bounds for dim of len " + std::to_string(input_dim)); if(index < 0) index += input_dim; relative_slice_offset += index * sizes_from_slice_dims[dim_idx]; } input_slice_offsets[i] = (batch_idx * data_batch_stride) + relative_slice_offset; }); par_for(num_slices * slice_size, [&](const auto i) { auto slice_offset = input_slice_offsets[i / slice_size]; output[i] = data[slice_offset + i % slice_size]; }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/get_tuple_elem.hpp000066400000000000000000000051011510465702400247070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_GET_TUPLE_ELEM_HPP #define MIGRAPHX_GUARD_OPERATORS_GET_TUPLE_ELEM_HPP #include "migraphx/errors.hpp" #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct get_tuple_elem { std::size_t index = 0; template static auto reflect(Self& self, F f) { return pack(f(self.index, "index")); } std::string name() const { return "get_tuple_elem"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1).tuple_type(); const auto& sub_shapes = inputs.at(0).sub_shapes(); if(index >= sub_shapes.size()) { MIGRAPHX_THROW("GET_TUPLE_ELEM: index " + std::to_string(index) + " is out of range " + std::to_string(sub_shapes.size())); } return sub_shapes.at(index); } argument compute(const shape&, std::vector args) const { assert(args.size() == 1); auto vec_args = args.at(0).get_sub_objects(); assert(index < vec_args.size()); return vec_args.at(index); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/greater.hpp000066400000000000000000000033351510465702400233550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_GREATER_HPP #define MIGRAPHX_GUARD_OPERATORS_GREATER_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct greater : binary { std::string point_function() const { return ">"; } auto apply() const { return [](auto x, auto y) { return x > y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/group.hpp000066400000000000000000000043341510465702400230600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_GROUP_HPP #define MIGRAPHX_GUARD_OPERATORS_GROUP_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct group { std::string tag = ""; std::string name() const { return "group"; } template static auto reflect(Self& self, F f) { return pack(f(self.tag, "tag")); } shape compute_shape(const std::vector& inputs, const std::vector& mods) const { if(mods.size() != 1) MIGRAPHX_THROW("should have one submodule."); module_ref mod = mods[0]; check_shapes{inputs, *this}.has_at_least(1); auto result = mod->compute_shapes(inputs, {.name = name(), .strict_type = true, .strict_lens = true}); if(result.size() == 1) return result.front(); return shape{result}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/group_query_attention.hpp000066400000000000000000000675641510465702400264100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_OPERATORS_GROUP_QUERY_ATTENTION_HPP #define MIGRAPHX_GUARD_OPERATORS_GROUP_QUERY_ATTENTION_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct gqa_parameters { std::size_t batch_size = 0; // Batch size used by input std::size_t sequence_length = 0; // Sequence length used by input std::size_t hidden_size = 0; // Hidden size used by input std::size_t head_size = 0; // Head size std::size_t rotary_embedding_dim = 0; // Rotary embedding dimension. std::size_t num_heads = 0; // num_heads = hidden_size / head_size std::size_t max_sequence_length = 0; // Sequence length used by cos/sin cache std::size_t head_stride = 0; // Head stride std::size_t seq_stride = 0; // Sequence stride std::size_t batch_stride = 0; // Batch stride bool position_ids_use_batch = false; // Format of position ids - false is (1), true is // (batch_size, sequence_length) std::size_t seqlen_present_kv_cache = 0; // Sequence length of present kv-cache // (4096 when using shared buffer) bool past_present_share_buffer = false; // Whether to use same buffer for KV-cache // inputs and outputs }; struct group_query_attention { bool do_rotary = false; std::size_t kv_num_heads = 0; int local_window_size = -1; std::size_t num_heads = 1; bool rotary_interleaved = false; float scale = 1.0; template static auto reflect(Self& self, F f) { return pack(f(self.do_rotary, "do_rotary"), f(self.kv_num_heads, "kv_num_heads"), f(self.local_window_size, "local_window_size"), f(self.num_heads, "num_heads"), f(self.rotary_interleaved, "rotary_interleaved"), f(self.scale, "scale")); } std::string name() const { return "group_query_attention"; } shape compute_shape(std::vector inputs) const { auto query_lens = inputs.front().lens(); std::size_t q_hidden_size = (query_lens[2] * num_heads) / (num_heads + 2 * kv_num_heads); std::vector output_lens{query_lens.at(0), query_lens.at(1), q_hidden_size}; shape output_shape{inputs.front().type(), output_lens}; return shape({output_shape, inputs[3], inputs[4]}); } template void run_rotary_embedding(T input, T cos_cache, T sin_cache, T output, bool interleaved, const std::size_t* pos_ids, gqa_parameters parameters) const { const std::size_t batch_size = parameters.batch_size; const std::size_t sequence_length = parameters.sequence_length; const std::size_t n_heads = parameters.num_heads; const std::size_t head_size = parameters.head_size; const std::size_t head_stride = parameters.head_stride; const std::size_t seq_stride = parameters.seq_stride; const std::size_t batch_stride = parameters.batch_stride; const std::size_t position_ids_use_batch = parameters.position_ids_use_batch; const std::size_t rotary_emb_dim = parameters.rotary_embedding_dim; const std::size_t half_rotary_emb_dim = rotary_emb_dim / 2; const std::size_t loop_len = batch_size * sequence_length * n_heads; par_for(loop_len, [&](const auto idx) { const std::size_t b = (idx / n_heads) / sequence_length; const std::size_t s = (idx / n_heads) % sequence_length; const std::size_t n = idx % n_heads; const std::size_t block_offset = b * batch_stride + s * seq_stride + n * head_stride; auto input_data = input + block_offset; auto output_data = output + block_offset; // Cache is (M, H/2) or (M, rotary_embedding_dim/2) const std::size_t position_id = position_ids_use_batch ? pos_ids[b * sequence_length + s] : pos_ids[0] + s; const std::size_t cache_offset = position_id * half_rotary_emb_dim; auto cos_data = cos_cache + cache_offset; auto sin_data = sin_cache + cache_offset; std::size_t cache_idx = 0; float sign = 0.0; std::size_t j = 0; for(std::size_t i = 0; i < rotary_emb_dim; i++) { if(interleaved) { cache_idx = (i / 2) % half_rotary_emb_dim; sign = (i % 2 == 0) ? -1.0 : 1.0; j = (i % 2 == 0) ? i + 1 : i - 1; // i - sign } else { cache_idx = i % half_rotary_emb_dim; sign = (i < half_rotary_emb_dim) ? -1.0 : 1.0; j = (i + half_rotary_emb_dim) % rotary_emb_dim; } output_data[i] = input_data[i] * cos_data[cache_idx] + sign * input_data[j] * sin_data[cache_idx]; } std::copy( input_data + rotary_emb_dim, input_data + head_size, output_data + rotary_emb_dim); }); } template void pack_v_into_rotary_qkv(gqa_parameters parameters, const T input, T output) const { const std::size_t loop_len = parameters.batch_size * parameters.sequence_length * kv_num_heads; par_for(loop_len, [&](const auto idx) { const std::size_t b = (idx / kv_num_heads) / parameters.sequence_length; const std::size_t s = (idx / kv_num_heads) % parameters.sequence_length; const std::size_t n = idx % kv_num_heads; const std::size_t block_offset = b * parameters.batch_stride + s * parameters.seq_stride + n * parameters.head_stride; const T input_data = input + block_offset; T output_data = output + block_offset; for(std::size_t i = 0; i < parameters.head_size; i++) { output_data[i] = input_data[i]; } }); } template void copy_data(T destination, const T source, std::size_t n) const { par_for(n, [&](auto i) { destination[i] = source[i]; }); } template T concat_state_chunk(const T past, const T chunk, T present, std::size_t present_buff_chunk_length, std::size_t past_buff_chunk_length, std::size_t past_chunk_length, std::size_t new_chunk_length, bool is_prompt, bool past_present_share_buffer, std::ptrdiff_t i) const { T start = present + i * present_buff_chunk_length; T p = start; if(not is_prompt) { if(not past_present_share_buffer) { const T src_past = past + i * past_buff_chunk_length; copy_data(p, src_past, past_chunk_length); } p += past_chunk_length; } copy_data(p, chunk, new_chunk_length); return start; } template void softmax_inplace(T score, std::size_t n, std::size_t d) const { par_for(n, [&](const auto j) { auto x = score + j * d; auto y = x; // e^x is represented as infinity if x is large enough, like 100.f. // Infinity divided by Infinity is a NAN. Thus, softmax gets a NAN if // one or more item are large enough. a math transform as below is // leveraged to get a stable softmax: e^xi/(e^x1 + ...e^xn) = e^(xi - // max) / (e^(x1 - max) + ... + e^(xn - max)) float max = -std::numeric_limits::infinity(); for(std::size_t i = 0; i < d; i++) { if(max < x[i]) max = x[i]; } for(std::size_t i = 0; i < d; i++) { y[i] = expf(x[i] - max); } double sum = 0.0; for(std::size_t i = 0; i < d; i++) { sum += x[i]; } for(std::size_t i = 0; i < d; i++) { y[i] = x[i] / static_cast(sum); } }); } // Helper function to compute the attention probs. It does 2 things: // attention_probs(B, N, S, T) = 1/sqrt(H) x Q(B, N, S, H) x K'(B, N, T, H -> B, N, H, T) // attention_probs(B, N, S, T) = Softmax(attention_probs) template void calculate_attention_probs(T attention_probs, // output buffer with size BxNxSxT T query, // Q data. Its size is BxNxSxH T key, // k data. Its size is BxNxLxH U seqlens_k, // past sequence lengths tensor T past_key, // past key only T present_key, // present key only shape::type_t dtype, gqa_parameters params) const { const std::size_t batch_size = params.batch_size; const std::size_t sequence_length = params.sequence_length; const std::size_t head_size = params.head_size; const std::size_t past_buffer_sequence_length = params.seqlen_present_kv_cache; const std::size_t present_buffer_sequence_length = past_buffer_sequence_length; const bool past_present_share_buffer = params.past_present_share_buffer; const bool is_prompt = sequence_length != 1; const std::size_t packed_batch_stride = (num_heads + 2 * kv_num_heads) * sequence_length * head_size; const std::size_t kv_num_heads_factor = num_heads / kv_num_heads; const std::size_t q_input_chunk_length = sequence_length * head_size; // S x H const std::size_t kv_input_chunk_length = sequence_length * head_size; // L x H const std::size_t past_buff_chunk_length = past_buffer_sequence_length * head_size; // L x H const std::size_t present_buff_chunk_length = present_buffer_sequence_length * head_size; // T x H const std::size_t loop_len = batch_size * num_heads; const float alpha = scale == 0.0f ? 1.0f / std::sqrt(static_cast(head_size)) : scale; par_for(loop_len, [&](const auto i) { const std::size_t batch_index = i / num_heads; const std::size_t head_index = i % num_heads; const std::size_t past_seqlen = sequence_length == 1 ? seqlens_k[batch_index] : past_buffer_sequence_length; const std::size_t past_chunk_length = past_seqlen * head_size; const std::size_t total_seqlen = seqlens_k[batch_index] + 1; const std::size_t output_offset = i * sequence_length * present_buffer_sequence_length; auto output = attention_probs + output_offset; auto k = key + packed_batch_stride * batch_index + kv_input_chunk_length * (head_index / kv_num_heads_factor); k = concat_state_chunk(past_key, k, present_key, present_buff_chunk_length, past_buff_chunk_length, past_chunk_length, kv_input_chunk_length, is_prompt, past_present_share_buffer, i / kv_num_heads_factor); // Calculate Q*K' + AttentionMask // original transposed each iteration // A: Q (B x N x) S x H (B x N x) S x H S x H // B: K' (B x N x) T x H (B x N x) H x T H x T // C: attention_probs (B x N x) S x T (B x N x) S x T S x T auto q = query + packed_batch_stride * batch_index + q_input_chunk_length * head_index; auto output_shape = shape{dtype, {sequence_length, total_seqlen}, {present_buffer_sequence_length, 1}}; auto q_shape = shape{dtype, {sequence_length, head_size}, {head_size, 1}}; auto k_shape = shape{dtype, {head_size, total_seqlen}, {1, head_size}}; auto cmat = make_view(output_shape, &(*output)); auto amat = make_view(q_shape, &(*q)); auto bmat = make_view(k_shape, &(*k)); gemm(cmat, amat, bmat, alpha, 0.0f); T output_softmax = output; for(std::size_t seq = 0; seq < sequence_length; seq++) { std::size_t seq_causal_length = sequence_length == 1 ? total_seqlen : seq + 1; if(local_window_size > 0 and seq_causal_length > local_window_size + 1) { for(std::size_t total_seq_id = 0; total_seq_id < seq_causal_length - local_window_size - 1; total_seq_id++) { output_softmax[total_seq_id] = 0.f; } softmax_inplace(output_softmax + seq_causal_length - local_window_size - 1, 1, local_window_size + 1); } else { softmax_inplace(output_softmax, 1, seq_causal_length); } // set causal [seq_causal_length, total_seqlen) to 0.f for(std::size_t total_seq_id = seq_causal_length; total_seq_id < total_seqlen; total_seq_id++) { output_softmax[total_seq_id] = 0.f; } output_softmax += present_buffer_sequence_length; } }); } template void calculate_attention_score(T output, // buffer for the result with size BxSxNxH const W attention_probs, // Attention probs with size BxNxSxT const T val, // V value with size BxN_kvxSxH const U seqlens_k, // past sequence lengths tensor const T past_value, // past value only T present_value, // present value only shape::type_t dtype, gqa_parameters params) const // whether Q, K, V are packed { const std::size_t batch_size = params.batch_size; const std::size_t sequence_length = params.sequence_length; const std::size_t head_size = params.head_size; const std::size_t hidden_size = params.hidden_size; const std::size_t past_buffer_sequence_length = params.seqlen_present_kv_cache; const std::size_t present_buffer_sequence_length = past_buffer_sequence_length; const bool past_present_share_buffer = params.past_present_share_buffer; const bool is_prompt = sequence_length != 1; const std::size_t packed_batch_stride = (num_heads + 2 * kv_num_heads) * sequence_length * head_size; const std::size_t kv_num_heads_factor = num_heads / kv_num_heads; const std::size_t kv_input_chunk_length = sequence_length * head_size; // L x H const std::size_t past_buff_chunk_length = past_buffer_sequence_length * head_size; // L x H const std::size_t present_buff_chunk_length = present_buffer_sequence_length * head_size; // T x H auto loop_len = batch_size * num_heads; par_for(loop_len, [&](const auto i) { const std::size_t batch_index = i / num_heads; const std::size_t head_index = i % num_heads; const std::size_t past_seqlen = sequence_length == 1 ? seqlens_k[batch_index] : past_buffer_sequence_length; const std::size_t past_chunk_length = past_seqlen * head_size; const std::size_t total_seqlen = seqlens_k[batch_index] + 1; auto v = val + packed_batch_stride * batch_index + kv_input_chunk_length * (head_index / kv_num_heads_factor); v = concat_state_chunk(past_value, v, present_value, present_buff_chunk_length, past_buff_chunk_length, past_chunk_length, kv_input_chunk_length, is_prompt, past_present_share_buffer, i / kv_num_heads_factor); T output_current = output + (batch_index * sequence_length * num_heads + head_index) * head_size; ptrdiff_t attention_probs_offset = sequence_length * present_buffer_sequence_length * i; auto output_shape = shape{dtype, {sequence_length, head_size}, {hidden_size, 1}}; auto probs_shape = shape{dtype, {sequence_length, total_seqlen}, {present_buffer_sequence_length, 1}}; auto v_shape = shape{dtype, {total_seqlen, head_size}, {head_size, 1}}; auto cmat = make_view(output_shape, &(*output_current)); auto amat = make_view(probs_shape, &(*(attention_probs + attention_probs_offset))); auto bmat = make_view(v_shape, &(*v)); gemm(cmat, amat, bmat, 1.0f, 0.0f); }); } template void apply_attention(T qkv, T past_key, T past_value, T output, T present_key, T present_value, U seqlens_k, T attention_probs, gqa_parameters parameters, shape::type_t dtype) const { const T k = qkv + num_heads * parameters.sequence_length * parameters.head_size; calculate_attention_probs( attention_probs, qkv, k, seqlens_k, past_key, present_key, dtype, parameters); const T v = qkv + (num_heads + kv_num_heads) * parameters.sequence_length * parameters.head_size; calculate_attention_score( output, attention_probs, v, seqlens_k, past_value, present_value, dtype, parameters); } argument compute(const shape& output_shape, std::vector args) const { auto q_shape = args[0].get_shape(); const auto& q_lens = q_shape.lens(); const std::size_t batch_size = q_lens[0]; const std::size_t sequence_length = q_lens[1]; auto past_key_shape = args[3].get_shape(); const auto& past_key_lens = past_key_shape.lens(); auto past_sequence_length = past_key_lens[2]; std::size_t q_hidden_size = q_lens[2]; std::size_t head_size = q_hidden_size / (num_heads + 2 * kv_num_heads); q_hidden_size = head_size * num_heads; std::size_t rotary_dim = args[7].get_shape().lens()[1] * 2; auto output_shape_0 = output_shape.sub_shapes().front(); argument result{output_shape_0}; argument qkv_rotary{ shape{output_shape_0.type(), {batch_size, num_heads + 2 * kv_num_heads, sequence_length, head_size}}}; shape kv_shape{output_shape_0.type(), {batch_size, kv_num_heads, past_sequence_length, head_size}}; argument present_k_out{kv_shape}; argument present_v_out{kv_shape}; argument attention_probs{shape{ output_shape_0.type(), {batch_size, num_heads, sequence_length, past_sequence_length}}}; args[0] = args[0].reshape( shape{output_shape_0.type(), {batch_size, sequence_length, num_heads + 2 * kv_num_heads, head_size}}); argument qkv{qkv_rotary.get_shape()}; visit_all(qkv, args[0])([&](auto a, auto b) { auto in_shape = args[0].get_shape(); auto out_shape = qkv.get_shape(); shape_for_each(in_shape, [&](const auto& idx) { std::vector out_idx{idx[0], idx[2], idx[1], idx[3]}; a(out_idx.begin(), out_idx.end()) = b(idx.begin(), idx.end()); }); }); visit_all(result, qkv, args[3], args[4], args[7], args[8], qkv_rotary, present_k_out, present_v_out, attention_probs)([&](auto output, auto query, auto past_key, auto past_value, auto cos_cache, auto sin_cache, auto rotary_qkv, auto present_k, auto present_v, auto attn_probs) { get_all(args[5])([&](auto seqlens_k) { par_for(kv_shape.elements(), [&](auto i) { present_k[i] = past_key[i]; present_v[i] = past_value[i]; }); auto seq_stride = head_size; auto head_stride = sequence_length * seq_stride; auto batch_stride = num_heads + 2 * kv_num_heads; auto position_ids_use_batch = sequence_length == 1; std::vector pos_ids(sequence_length == 1 ? batch_size : 1); if(sequence_length == 1) { std::copy(seqlens_k.begin(), seqlens_k.begin() + batch_size, pos_ids.begin()); } else { pos_ids[0] = 0; } auto q_input = query.begin(); auto k_input = q_input + num_heads * sequence_length * head_size; auto q_rotary = rotary_qkv.begin(); auto k_rotary = q_rotary + num_heads * sequence_length * head_size; gqa_parameters gqa_params = {}; gqa_params.batch_size = batch_size; gqa_params.sequence_length = sequence_length; gqa_params.hidden_size = q_hidden_size; gqa_params.head_size = head_size; gqa_params.rotary_embedding_dim = rotary_dim; gqa_params.num_heads = num_heads; gqa_params.max_sequence_length = sequence_length; gqa_params.seq_stride = head_size; gqa_params.head_stride = head_stride; gqa_params.batch_stride = batch_stride; gqa_params.position_ids_use_batch = position_ids_use_batch; gqa_params.seqlen_present_kv_cache = past_sequence_length; gqa_params.past_present_share_buffer = false; if(do_rotary) { run_rotary_embedding(q_input, cos_cache.begin(), sin_cache.begin(), q_rotary, rotary_interleaved, pos_ids.data(), gqa_params); } std::size_t kv_hidden_size = head_size * kv_num_heads; gqa_params.num_heads = kv_num_heads; gqa_params.hidden_size = kv_hidden_size; if(do_rotary) { run_rotary_embedding(k_input, cos_cache.begin(), sin_cache.begin(), k_rotary, rotary_interleaved, pos_ids.data(), gqa_params); } auto v_input = k_input + kv_num_heads * sequence_length * head_size; auto v_rotary = k_rotary + kv_num_heads * sequence_length * head_size; gqa_params.num_heads = num_heads; if(do_rotary) { pack_v_into_rotary_qkv(gqa_params, v_input, v_rotary); } else { rotary_qkv = query; } apply_attention(rotary_qkv.begin(), past_key.begin(), past_value.begin(), output.begin(), present_k.begin(), present_v.begin(), seqlens_k.begin(), attn_probs.begin(), gqa_params, output_shape_0.type()); }); }); return {{result, present_k_out, present_v_out}}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/gru.hpp000066400000000000000000000063161510465702400225230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_GRU_HPP #define MIGRAPHX_GUARD_OPERATORS_GRU_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct gru { std::size_t hidden_size = 1; std::vector actv_funcs{sigmoid{}, tanh{}}; rnn_direction direction = rnn_direction::forward; float clip = 0.0f; int linear_before_reset = 0; template static auto reflect(Self& self, F f) { return pack(f(self.hidden_size, "hidden_size"), f(self.actv_funcs, "actv_func"), f(self.direction, "direction"), f(self.clip, "clip"), f(self.linear_before_reset, "linear_before_reset")); } std::string name() const { return "gru"; } shape compute_shape(std::vector inputs) const { auto in_dims = inputs[0].lens(); auto hidden_dims = inputs[2].lens(); if(hidden_size != hidden_dims[2]) { MIGRAPHX_THROW("GRU: hidden size mismatch in attribute and input"); } std::size_t num_directions = 1; if(direction == rnn_direction::bidirectional) { num_directions = 2; } if(num_directions != hidden_dims[0]) { MIGRAPHX_THROW("GRU: num_direction does not match the direction attribute"); } std::vector out_dims(in_dims); out_dims.insert(out_dims.begin() + 1, num_directions); out_dims.back() = hidden_size; return {inputs[0].type(), out_dims}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/identity.hpp000066400000000000000000000035671510465702400235640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_IDENTITY_HPP #define MIGRAPHX_GUARD_OPERATORS_IDENTITY_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct identity { std::string name() const { return "identity"; } shape compute_shape(std::vector inputs) const { return inputs.at(0); } argument compute(shape, std::vector args) const { return args[0]; } value attributes() const { return {{"pointwise", true}, {"point_op", "${0}"}}; } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/if_op.hpp000066400000000000000000000065571510465702400230310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_IF_OP_HPP #define MIGRAPHX_GUARD_OPERATORS_IF_OP_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct if_op { std::string name() const { return "if"; } shape compute_shape(const std::vector& inputs, std::vector mods) const { check_shapes{inputs, *this}.standard(); if(mods.size() != 2) { MIGRAPHX_THROW("IF: operator should have two submodules."); } auto out_shapes0 = mods[0]->get_output_shapes(); auto out_shapes1 = mods[1]->get_output_shapes(); if(not std::equal( out_shapes1.begin(), out_shapes1.end(), out_shapes0.begin(), out_shapes0.end())) { MIGRAPHX_THROW("IF: output shapes of submodules must be the same."); } return shape(out_shapes0); } argument compute(const shape&, const std::vector& args, const std::vector& mods, const std::function( module_ref&, const std::unordered_map&)>& run) const { auto cond = args.front().at(); module_ref mod = cond ? mods[0] : mods[1]; std::unordered_map params; std::set pnames; for(const_module_ref smod : mods) { auto names = smod->get_parameter_names(); pnames.insert(names.begin(), names.end()); } assert(pnames.size() < args.size()); std::transform(pnames.begin(), pnames.end(), args.begin() + 1, std::inserter(params, params.end()), [](auto&& name, auto&& arg) { return std::make_pair(name, arg); }); auto results = run(mod, params); return argument{results}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/im2col.hpp000066400000000000000000000066601510465702400231150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_IM2COL_HPP #define MIGRAPHX_GUARD_OPERATORS_IM2COL_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct im2col { std::vector padding{0, 0}; std::vector stride{1, 1}; std::vector dilation{1, 1}; padding_mode_t padding_mode = default_; template static auto reflect(Self& self, F f) { return pack(f(self.padding, "padding"), f(self.stride, "stride"), f(self.dilation, "dilation"), f(self.padding_mode, "padding_mode")); } std::string name() const { return "im2col"; } value attributes() const { return {{"normalize_padding", "padding"}}; } shape normalize_compute_shape(std::vector inputs) const { const auto& input = inputs[0]; const auto& weights = inputs[1]; auto batch_size = input.lens()[0]; auto input_channels = weights.lens()[1]; auto kernel_height = weights.lens()[2]; auto kernel_width = weights.lens()[3]; check_shapes{inputs, *this}.has(2); if(batch_size != 1) MIGRAPHX_THROW("im2col only support batch_size 1"); auto padding_h = 2 * padding[0]; auto padding_w = 2 * padding[1]; if(padding.size() == 2 * stride.size()) { padding_h = padding[0] + padding[2]; padding_w = padding[1] + padding[3]; } auto output_height = std::size_t(std::max( 1, (input.lens()[2] - (1 + dilation[0] * (kernel_height - 1)) + padding_h) / stride[0] + 1)); auto output_width = std::size_t(std::max( 1, (input.lens()[3] - (1 + dilation[1] * (kernel_width - 1)) + padding_w) / stride[1] + 1)); auto channels_col = kernel_height * kernel_width * input_channels; return {input.type(), {output_height * output_width, channels_col}}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/isinf.hpp000066400000000000000000000034761510465702400230420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ISINF_HPP #define MIGRAPHX_GUARD_OPERATORS_ISINF_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct isinf : unary { auto apply() const { return [&](auto x) { return std::isinf(static_cast(x)); }; } std::string name() const { return "isinf"; } shape compute_shape(std::vector inputs) const { return unary::compute_shape(std::move(inputs)).with_type(shape::bool_type); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/isnan.hpp000066400000000000000000000034751510465702400230410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ISNAN_HPP #define MIGRAPHX_GUARD_OPERATORS_ISNAN_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct isnan : unary { auto apply() const { return [](auto x) { return std::isnan(static_cast(x)); }; } std::string name() const { return "isnan"; } shape compute_shape(std::vector inputs) const { return unary::compute_shape(std::move(inputs)).with_type(shape::bool_type); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/layout.hpp000066400000000000000000000053531510465702400232430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OP_LAYOUT_HPP #define MIGRAPHX_GUARD_OP_LAYOUT_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Rearrange the memory layout of the input instruction based on the permutation attribute. * This operator changes the order of elements in memory, *not* the order in the tensor. * Therefore, regardless of how the memory layout is changed, the order of elements returned by a * tensor_view will be unchanged. * `permutation`: List with how to rearrange the data buffer of the input instruction. This * permutation is the transpose from the order in the tensor to the order in memory. */ struct layout : unary { std::vector permutation; template static auto reflect(Self& self, F f) { return pack(f(self.permutation, "permutation")); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1).only_dims(permutation.size()); auto lens = inputs.at(0).lens(); auto t = inputs.at(0).type(); return shape::from_permutation(t, lens, permutation); } auto apply() const { return [](auto x) { return x; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_OP_LAYOUT_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/leaky_relu.hpp000066400000000000000000000037201510465702400240560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LEAKY_RELU_HPP #define MIGRAPHX_GUARD_OPERATORS_LEAKY_RELU_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct leaky_relu : unary { float alpha = 0.01; template static auto reflect(Self& self, F f) { return pack(f(self.alpha, "alpha")); } std::string point_op() const { return "${function:where}(${0} > 0, ${0}, ${alpha} * ${0})"; } std::string name() const { return "leaky_relu"; } auto apply() const { return [&](auto x) { return x > 0 ? x : x * alpha; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/less.hpp000066400000000000000000000032331510465702400226670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LESS_HPP #define MIGRAPHX_GUARD_OPERATORS_LESS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct less : binary { std::string point_function() const { return "<"; } auto apply() const { return [](auto x, auto y) { return x < y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/load.hpp000066400000000000000000000051441510465702400226430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LOAD_HPP #define MIGRAPHX_GUARD_OPERATORS_LOAD_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct load { shape s; std::size_t offset = 0; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape"), f(self.offset, "offset")); } std::string name() const { return "load"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(1); return s; } argument compute(const shape&, const std::vector& args) const { if((offset + s.bytes()) > args[0].get_shape().bytes()) MIGRAPHX_THROW("Load access is out of bounds"); return argument{s, args[0].data() + offset}; } lifetime get_lifetime() const { return lifetime::borrow; } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } friend std::ostream& operator<<(std::ostream& os, const load& op) { os << op.name() << "["; os << "offset=" << op.offset << ","; os << "end=" << (op.offset + op.s.bytes()) << "]"; return os; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/log.hpp000066400000000000000000000031341510465702400225020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LOG_HPP #define MIGRAPHX_GUARD_OPERATORS_LOG_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct log : unary { auto apply() const { return [](auto x) { return std::log(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/log2.hpp000066400000000000000000000031421510465702400225630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LOG2_HPP #define MIGRAPHX_GUARD_OPERATORS_LOG2_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct log2 : unary { auto apply() const { return [](auto x) { return std::log2(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/logical_and.hpp000066400000000000000000000033401510465702400241540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LOGICAL_AND_HPP #define MIGRAPHX_GUARD_OPERATORS_LOGICAL_AND_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct logical_and : binary { std::string point_function() const { return "&&"; } auto apply() const { return [](auto x, auto y) { return static_cast(x) and static_cast(y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/logical_or.hpp000066400000000000000000000033331510465702400240340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LOGICAL_OR_HPP #define MIGRAPHX_GUARD_OPERATORS_LOGICAL_OR_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct logical_or : binary { std::string point_function() const { return "||"; } auto apply() const { return [](auto x, auto y) { return static_cast(x) or static_cast(y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/logical_xor.hpp000066400000000000000000000033371510465702400242300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LOGICAL_XOR_HPP #define MIGRAPHX_GUARD_OPERATORS_LOGICAL_XOR_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct logical_xor : binary { std::string point_function() const { return "^"; } auto apply() const { return [](auto x, auto y) { return static_cast(x) xor static_cast(y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/logsoftmax.hpp000066400000000000000000000045471510465702400241150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LOGSOFTMAX_HPP #define MIGRAPHX_GUARD_OPERATORS_LOGSOFTMAX_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct logsoftmax { int64_t axis = 1; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "logsoftmax"; } shape normalize_compute_shape(std::vector inputs) const { if(inputs.at(0).packed()) { return inputs.at(0); } else { auto lens = inputs.at(0).lens(); return {inputs.at(0).type(), lens}; } } auto output() const { return [=](auto x, auto y) { return std::log(x / y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/loop.hpp000066400000000000000000000143671510465702400227040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LOOP_HPP #define MIGRAPHX_GUARD_OPERATORS_LOOP_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct loop { int64_t max_iterations = 10; std::vector scan_output_directions = {}; template static auto reflect(Self& self, F f) { return pack(f(self.max_iterations, "max_iterations"), f(self.scan_output_directions, "scan_output_directions")); } std::string name() const { return "loop"; } shape compute_shape(const std::vector& inputs, std::vector mods) const { check_shapes{inputs, *this}.standard(); if(mods.size() != 1) { MIGRAPHX_THROW("LOOP: operator should have one submodule."); } const_module_ref mod = mods.front(); auto mod_out_shapes = mod->get_output_shapes(); auto dep_param_num = inputs.size() - 2; // first item of the mod output shapes is condition used in loop, // which is not needed to compute output shape mod_out_shapes.erase(mod_out_shapes.begin()); std::vector ins_out_shapes(mod_out_shapes.begin(), mod_out_shapes.begin() + dep_param_num); mod_out_shapes.erase(mod_out_shapes.begin(), mod_out_shapes.begin() + dep_param_num); for(const auto& out_s : mod_out_shapes) { auto lens = out_s.lens(); lens.insert(lens.begin(), max_iterations); ins_out_shapes.push_back({out_s.type(), lens}); } return shape(ins_out_shapes); } struct ref_loop { int64_t max_iterations = 0; template void copy(context&, const argument& src, T& dst) const { dst = *src.cast(); } template void copy(context&, T src, const argument& dst) const { *dst.cast() = src; } void append(const std::vector& iter_state, const std::vector& concatenated_outputs, const std::vector& scan_output_dirs, int64_t curr_iter, int64_t num_iters) const { assert(iter_state.size() == concatenated_outputs.size()); for(auto i : range(iter_state.size())) { const auto& iter_stat = iter_state.at(i); const auto& scan_out = concatenated_outputs.at(i); auto dir = scan_output_dirs.empty() ? 0 : scan_output_dirs[i]; auto idx = (1 - dir) * curr_iter + dir * (num_iters - 1 - curr_iter); auto* in_data = iter_stat.data(); auto* out_data = scan_out.data(); std::size_t out_size = iter_stat.get_shape().bytes(); assert((idx + 1) * out_size <= scan_out.get_shape().bytes()); std::copy(in_data, in_data + out_size, out_data + idx * out_size); } } void set_zero(context&, const std::vector& concatenated_outputs, int iter) const { if(iter >= max_iterations) return; for(const auto& out : concatenated_outputs) { auto s = out.get_shape(); auto size = s.bytes() / max_iterations; std::fill(out.data() + iter * size, out.data() + max_iterations * size, 0); } } std::unordered_map get_output_params(const module&) const { return {}; } }; argument compute(context& ctx, const shape& out_shape, const std::vector& args, const std::vector& mods, const std::function( module_ref&, const std::unordered_map&)>& run) const { // wrap up the arguments vector, so ref and gpu impl are the same auto cpy_args = args; bool in_cond = args.at(1).at(); bool cond = in_cond; int64_t iter = 0; // insert iter and cond used in the loop auto s_cond = args.at(1).get_shape(); auto s_iter = args.at(0).get_shape(); cpy_args.push_back({s_iter, &iter}); cpy_args.push_back({s_cond, &cond}); cpy_args.insert(cpy_args.end(), args.begin() + 2, args.end()); // add cond and mod outputs to the argument list cpy_args.push_back(argument(s_cond)); cpy_args.push_back(argument(out_shape)); // run loop return run_loop(ref_loop{max_iterations}, scan_output_directions, ctx, cpy_args, mods, run); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/lrn.hpp000066400000000000000000000043221510465702400225140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LRN_HPP #define MIGRAPHX_GUARD_OPERATORS_LRN_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct lrn { float alpha = 0.0001; float beta = 0.75; float bias = 1.0; int size = 1; std::string name() const { return "lrn"; } template static auto reflect(Self& self, F f) { return pack(f(self.alpha, "alpha"), f(self.beta, "beta"), f(self.bias, "bias"), f(self.size, "size")); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1); return inputs.front(); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/lstm.hpp000066400000000000000000000063161510465702400227050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_LSTM_HPP #define MIGRAPHX_GUARD_OPERATORS_LSTM_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct lstm { std::size_t hidden_size = 1; std::vector actv_funcs{sigmoid{}, tanh{}, tanh{}}; rnn_direction direction = rnn_direction::forward; float clip = 0.0f; int input_forget = 0; template static auto reflect(Self& self, F f) { return pack(f(self.hidden_size, "hidden_size"), f(self.actv_funcs, "actv_func"), f(self.direction, "direction"), f(self.clip, "clip"), f(self.input_forget, "input_forget")); } std::string name() const { return "lstm"; } shape compute_shape(std::vector inputs) const { auto in_dims = inputs[0].lens(); auto hidden_dims = inputs[2].lens(); if(hidden_size != hidden_dims[2]) { MIGRAPHX_THROW("LSTM: hidden size mismatch in attribute and input"); } std::size_t num_directions = 1; if(direction == rnn_direction::bidirectional) { num_directions = 2; } if(num_directions != hidden_dims[0]) { MIGRAPHX_THROW("LSTM: num_direction does not match the direction attribute"); } std::vector out_dims(in_dims); out_dims.insert(out_dims.begin() + 1, num_directions); out_dims.back() = hidden_size; return {inputs[0].type(), out_dims}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/max.hpp000066400000000000000000000033631510465702400225120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_MAX_HPP #define MIGRAPHX_GUARD_OPERATORS_MAX_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct max : binary { value attributes() const { auto a = base_attributes(); a["commutative"] = true; return a; } auto apply() const { return [](auto x, auto y) { return std::max(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/min.hpp000066400000000000000000000033631510465702400225100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_MIN_HPP #define MIGRAPHX_GUARD_OPERATORS_MIN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct min : binary { value attributes() const { auto a = base_attributes(); a["commutative"] = true; return a; } auto apply() const { return [](auto x, auto y) { return std::min(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/mod.hpp000066400000000000000000000034361510465702400225050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_MOD_HPP #define MIGRAPHX_GUARD_OPERATORS_MOD_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct mod : binary { std::string name() const { return "mod"; } value attributes() const { auto a = base_attributes(); a["commutative"] = false; return a; } auto apply() const { return [](auto x, auto y) { return std::fmod((std::remainder(x, y)) + y, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/mul.hpp000066400000000000000000000034411510465702400225170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_MUL_HPP #define MIGRAPHX_GUARD_OPERATORS_MUL_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct mul : binary { value attributes() const { auto a = base_attributes(); a["commutative"] = true; return a; } std::string point_function() const { return "*"; } auto apply() const { return [](auto x, auto y) { return x * y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/multibroadcast.hpp000066400000000000000000000105341510465702400247400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_MULTIBROADCAST_HPP #define MIGRAPHX_GUARD_OPERATORS_MULTIBROADCAST_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Broadcast multiple dimensions between two tensors. * Two versions of this operator: 1 input and 2+ inputs. * One input version uses output_lens attribute and broadcasts to it. * 2+ inputs version broadcasts first input to the common shape at evaluation time. */ struct multibroadcast { std::vector output_lens = {}; // optional attribute std::vector output_dyn_dims = {}; template static auto reflect(Self& self, F f) { return pack(f(self.output_lens, "out_lens"), f(self.output_dyn_dims, "out_dyn_dims")); } std::string name() const { return "multibroadcast"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has_at_least(1); auto t = inputs.at(0).type(); const auto& s0 = inputs.at(0); if(s0.ndim() < 1) { MIGRAPHX_THROW("MULTIBROADCAST: input dimensions should be > 0"); } if(inputs.size() == 1) { if(s0.dynamic()) MIGRAPHX_THROW( "MULTIBROADCAST: Single dynamic input shape not supported. Use two inputs."); if(s0.ndim() > output_lens.size()) { MIGRAPHX_THROW("MULTIBROADCAST: input dimensions should <= output size"); } auto offset = output_lens.size() - s0.ndim(); for(std::ptrdiff_t i = s0.ndim() - 1; i >= 0; i--) { if(output_lens[i + offset] != s0.lens()[i] and s0.lens()[i] != 1) { MIGRAPHX_THROW("MULTIBROADCAST: input shape {" + to_string_range(s0.lens()) + "} cannot be broadcasted to {" + to_string_range(output_lens) + "}!"); } } return make_bcast_shape(s0, output_lens); } else { // 2+ inputs if(std::any_of( inputs.cbegin(), inputs.cend(), [](auto input) { return input.dynamic(); })) { if(not output_dyn_dims.empty()) { return {t, output_dyn_dims}; } return {t, compute_common_dyn_dims(inputs)}; } else { // output_lens will not be set for 2+ input version auto bcast_lens = compute_common_lens(inputs); return make_bcast_shape(s0, bcast_lens); } } } argument compute(const dyn_output& dyn_out, std::vector args) const { return args[0].reshape(dyn_out.computed_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/multinomial.hpp000066400000000000000000000145021510465702400242540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ /** * * Multinomial or categorical distribution. Performs a sampling of random input * and returns a count of * each category, or bucket. This does not require the standard multinomial * distribution but instead takes a probability distribution, i.e. cumulative * distribution function (CDF) as its first input. * * Inputs: args[0] - a tensor of probabilities for each category. Values are * cumulative density function * totals as provided by operation prefix_scan_sum. Values are * cumulative probabilities (i.e. start with any set of numbers > 0 * and then apply prefix_scan_sum). Values do not need to be * normalized to sum to 1; this is done in runtime computation. * * This input has Rank 2. Dimension 0 is batch #, so that there can be * a different CDF for each iteration in the batch. The size of dimension * 1 is the number of categories. * * args[1] - a tensor of random numbers. The last dimension is the sample * size, i.e. the number of * random samples in each iteration of the batch. Nominally * has two dimensions where the first dimension is batch size, but * any reshaping such that the total * number of elements is (batch_size * sample_size) is legal. * * Values as created by a std::mt19937 like this: * * size_t sample_size = 100000; * float seed = 0.0f; * std::mt19937 gen(seed); * std::uniform_real_distribution<> dis(0.0, 1.0); * std::vector rand_samples(sample_size); * std::generate(rand_samples.begin(), rand_samples.end(), [&]() { return * dis(gen); }); * * Output: A 2D vector of category each input. Dimensions are (Input 1[first], Input 2[last]). * */ #ifndef MIGRAPHX_GUARD_OPERATORS_MULTINOMIAL_HPP #define MIGRAPHX_GUARD_OPERATORS_MULTINOMIAL_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct multinomial { shape::type_t dtype = shape::type_t::int32_type; template static auto reflect(Self& self, F f) { return pack(f(self.dtype, "dtype")); } std::string name() const { return "multinomial"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2).only_dims(2); if(inputs.back().ndim() < 1) MIGRAPHX_THROW("Multinomial: Second input shape (sample) has no dimensions"); if(dtype == shape::bool_type) MIGRAPHX_THROW("Multinomial: boolean output type invalid."); // Output takes one dimension from each of the two input shapes. If they are both fixed, // return a static shape if((not inputs.front().dynamic()) or (inputs.front().dyn_dims().front().is_fixed())) { if((not inputs.back().dynamic()) or (inputs.back().dyn_dims().back().is_fixed())) { size_t batch = {inputs.front().max_lens().front()}; size_t sample_size{inputs.back().max_lens().back()}; return {dtype, {batch, sample_size}}; } } return {dtype, {inputs.front().to_dynamic().dyn_dims().front(), inputs.back().to_dynamic().dyn_dims().back()}}; } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; size_t batch_size = dyn_out.computed_shape.lens().front(); size_t class_size = args[0].get_shape().lens().back(); size_t sample_size = dyn_out.computed_shape.lens().back(); get_all(args[0], args[1])([&](auto cdf, auto dist) { result.visit([&](auto output) { par_for(batch_size * sample_size, [&](auto i) { auto idx = args[1].get_shape().multi(i); auto cdf_begin = cdf.begin() + (idx[0] * class_size); auto cdf_end = cdf_begin + class_size; // std::upper_bound returns an iterator to the bucket the value belongs in, // when normalized by the probability distribution dist auto sample_iter = std::upper_bound(cdf_begin, cdf_end, dist[i] * *(std::prev(cdf_end))); // convert iterator to an integer index output[i] = std::distance(cdf_begin, sample_iter); }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/name.hpp000066400000000000000000000034041510465702400226410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_NAME_HPP #define MIGRAPHX_GUARD_RTGLIB_NAME_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /// Create name from class template struct op_name { std::string name() const { static const std::string& name = get_type_name(); assert((name.rfind("::") + 2) < name.size()); return name.substr(name.rfind("::") + 2); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/nearbyint.hpp000066400000000000000000000034531510465702400237200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_NEARBYINT_HPP #define MIGRAPHX_GUARD_OPERATORS_NEARBYINT_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct nearbyint : unary { auto apply() const { return [](auto x) { auto rounding_mode = fegetround(); fesetround(FE_TONEAREST); auto result = std::nearbyint(x); fesetround(rounding_mode); return result; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/neg.hpp000066400000000000000000000032121510465702400224670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_NEG_HPP #define MIGRAPHX_GUARD_OPERATORS_NEG_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct neg : unary { std::string point_function() const { return "-"; } auto apply() const { return [](auto x) { return -x; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/nonmaxsuppression.hpp000066400000000000000000000403451510465702400255410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_NONMAXSUPPRESSION_HPP #define MIGRAPHX_GUARD_OPERATORS_NONMAXSUPPRESSION_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* https://github.com/onnx/onnx/blob/main/docs/Operators.md#NonMaxSuppression Filter out boxes that have high intersection-over-union (IOU) overlap with previously selected boxes. Bounding boxes with score less than score_threshold are removed. Bounding box format is indicated by attribute center_point_box. Note that this algorithm is agnostic to where the origin is in the coordinate system and more generally is invariant to orthogonal transformations and translations of the coordinate system; thus translating or reflections of the coordinate system result in the same boxes being selected by the algorithm. The selected_indices output is a set of integers indexing into the input collection of bounding boxes representing the selected boxes. The bounding box coordinates corresponding to the selected indices can then be obtained using the Gather or GatherND operation. Version This version of the operator has been available since version 11 of the default ONNX operator set. Other versions of this operator: 10 Attributes center_point_box : int (default is 0) Integer indicate the format of the box data. The default is 0. 0 - the box data is supplied as [y1, x1, y2, x2] where (y1, x1) and (y2, x2) are the coordinates of any diagonal pair of box corners and the coordinates can be provided as normalized (i.e., lying in the interval [0, 1]) or absolute. Mostly used for TF models. 1 - the box data is supplied as [x_center, y_center, width, height]. Mostly used for Pytorch models. Inputs (2 - 5) --------------------------------------------------------------------------------------------------------------------- boxes : tensor(float) An input tensor with shape [num_batches, spatial_dimension, 4]. The single box data format is indicated by center_point_box. scores : tensor(float) An input tensor with shape [num_batches, num_classes, spatial_dimension] max_output_boxes_per_class (optional) : tensor(int64) Integer representing the maximum number of boxes to be selected per batch per class. It is a scalar. Default to 0, which means no output. iou_threshold (optional) : tensor(float) Float representing the threshold for deciding whether boxes overlap too much with respect to IOU. It is scalar. Value range [0, 1]. Default to 0. score_threshold (optional) : tensor(float) Float representing the threshold for deciding when to remove boxes based on score. It is a scalar. ---------------------------------------------------------------------------------------------------------------------- Outputs selected_indices : tensor(int64) selected indices from the boxes tensor. [num_selected_indices, 3], the selected index format is [batch_index, class_index, box_index]. ---------------------------------------------------------------------------------------------------------------------- */ namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct nonmaxsuppression { bool center_point_box = false; bool use_dyn_output = false; template static auto reflect(Self& self, F f) { return pack(f(self.center_point_box, "center_point_box"), f(self.use_dyn_output, "use_dyn_output")); } std::string name() const { return "nonmaxsuppression"; } shape compute_shape(std::vector inputs) const { // requires at least 2 inputs check_shapes{{inputs.at(0), inputs.at(1)}, *this, true}.only_dims(3).same_ndims(); auto boxes_max_lens = inputs.at(0).max_lens(); // num batches * num boxes const auto max_num_boxes = boxes_max_lens.at(0) * boxes_max_lens.at(1); auto fixed_shape_error_check = [&]() { auto lens = inputs.front().lens(); if(lens[1] != inputs.at(1).lens()[2]) { MIGRAPHX_THROW( "NonMaxSuppression: spatial dimension mismatch between boxes and scores input"); } if(lens[0] != inputs.at(1).lens()[0]) { MIGRAPHX_THROW( "NonMaxSuppression: number of batches mismatch between boxes and scores input"); } }; if(use_dyn_output) { if(inputs.at(0).dynamic()) { // both boxes and scores should be dynamic // check dynamic dimensions are consistent const auto boxes_dims = inputs.at(0).dyn_dims(); const auto scores_dims = inputs.at(1).dyn_dims(); if(boxes_dims.at(1) != scores_dims.at(2)) { MIGRAPHX_THROW("NonMaxSuppression: dynamic spatial dimension mismatch between " "boxes and scores input"); } if(boxes_dims.at(0) != scores_dims.at(0)) { MIGRAPHX_THROW("NonMaxSuppression: dynamic number of batches mismatch between " "boxes and scores input"); } } else if(inputs.at(1).dynamic()) { // scores has dynamic shape, boxes fixed shape // check that it is only a dynamic number of classes const auto scores_dims = inputs.at(1).dyn_dims(); const auto boxes_lens = inputs.at(0).lens(); if(not scores_dims.at(0).is_fixed() or scores_dims.at(0).max != boxes_lens.at(0)) { MIGRAPHX_THROW("NonMaxSuppression: scores dynamic num_classes; num_batches not " "fixed or mismatched"); } if(not scores_dims.at(2).is_fixed() or scores_dims.at(2).max != boxes_lens.at(1)) { MIGRAPHX_THROW("NonMaxSuppression: scores dynamic num_classes; " "spatial_dimension not fixed or mismatches"); } } else { fixed_shape_error_check(); } std::vector out_lens = {}; out_lens.push_back({0, max_num_boxes}); out_lens.push_back({3, 3}); return {shape::int64_type, out_lens}; } else { if(inputs.at(0).dynamic() or inputs.at(1).dynamic()) { MIGRAPHX_THROW( "NonMaxSuppression: dynamic input shape with use_dyn_output set to false"); } fixed_shape_error_check(); std::vector out_lens = {max_num_boxes, 3}; return {shape::int64_type, out_lens}; } } struct box { std::array x; std::array y; void sort() { if(x[0] > x[1]) { std::swap(x[0], x[1]); } if(y[0] > y[1]) { std::swap(y[0], y[1]); } } std::array& operator[](std::size_t i) { return i == 0 ? x : y; } double area() const { assert(x[0] <= x[1]); assert(y[0] <= y[1]); return (x[1] - x[0]) * (y[1] - y[0]); } }; template box batch_box(T boxes, std::size_t box_idx) const { box result{}; auto start = boxes + 4 * box_idx; if(center_point_box) { double half_width = start[2] / 2.0; double half_height = start[3] / 2.0; double x_center = start[0]; double y_center = start[1]; result.x = {x_center - half_width, x_center + half_width}; result.y = {y_center - half_height, y_center + half_height}; } else { result.x = {static_cast(start[1]), static_cast(start[3])}; result.y = {static_cast(start[0]), static_cast(start[2])}; } result.sort(); return result; } bool suppress_by_iou(box b1, box b2, double iou_threshold) const { const double area1 = b1.area(); const double area2 = b2.area(); if(area1 <= .0f or area2 <= .0f) { return false; } box intersection{}; for(auto i : range(2)) { intersection[i][0] = std::max(b1[i][0], b2[i][0]); intersection[i][1] = std::min(b1[i][1], b2[i][1]); if(intersection[i][0] > intersection[i][1]) { return false; } } const double intersection_area = intersection.area(); const double union_area = area1 + area2 - intersection_area; if(union_area <= .0f) { return false; } const double intersection_over_union = intersection_area / union_area; return intersection_over_union > iou_threshold; } // filter boxes below score_threshold template std::vector> filter_boxes_by_score(T scores_start, std::size_t num_boxes, double score_threshold) const { std::vector> boxes_heap; int64_t box_idx = 0; if(score_threshold > 0.0) { transform_if( scores_start, scores_start + num_boxes, std::back_inserter(boxes_heap), [&](auto sc) { box_idx++; return sc >= score_threshold; }, [&](auto sc) { return std::make_pair(sc, box_idx - 1); }); } else { // score is irrelevant, just push into boxes_heap and make a score-index pair std::transform(scores_start, scores_start + num_boxes, std::back_inserter(boxes_heap), [&](auto sc) { box_idx++; return std::make_pair(sc, box_idx - 1); }); } // Sort by the higher score; or if equal then the early (i.e. lower) index of the box // The tie below compares in effect t2.second > t1.second par_sort(boxes_heap.begin(), boxes_heap.end(), [](auto const& t1, auto const& t2) { return std::tie(t1.first, t2.second) > std::tie(t2.first, t1.second); }); return boxes_heap; } template std::size_t compute_nms(Output output, const Boxes& boxes, const Scores& scores, std::size_t max_output_boxes_per_class, double iou_threshold, double score_threshold) const { std::fill(output.begin(), output.end(), 0); const auto& lens = scores.get_shape().lens(); const auto num_batches = lens[0]; const auto num_classes = lens[1]; const auto num_boxes = lens[2]; // boxes of a class with NMS applied [score, index] std::vector selected_indices; // iterate over batches and classes shape comp_s{shape::double_type, {num_batches, num_classes}}; shape_for_each(comp_s, [&](const auto& idx) { auto batch_idx = idx[0]; auto class_idx = idx[1]; // index offset for this class auto scores_start = scores.begin() + (batch_idx * num_classes + class_idx) * num_boxes; // iterator to first value of this batch auto batch_boxes_start = boxes.begin() + batch_idx * num_boxes * 4; auto boxes_heap = filter_boxes_by_score(scores_start, num_boxes, score_threshold); int64_t selected_boxes_inside_class = 0; while(not boxes_heap.empty() and selected_boxes_inside_class < max_output_boxes_per_class) { // select next top scorer box and remove any boxes from boxes_heap that exceeds IOU // threshold with the selected box const auto next_top_score = boxes_heap.front(); auto next_box = batch_box(batch_boxes_start, next_top_score.second); auto next_box_idx = next_top_score.second; selected_boxes_inside_class++; selected_indices.push_back(batch_idx); selected_indices.push_back(class_idx); selected_indices.push_back(next_box_idx); std::vector> remainder_boxes(boxes_heap.size()); auto it = par_copy_if( boxes_heap.begin() + 1, boxes_heap.end(), remainder_boxes.begin(), [&](auto iou_candidate_box) { auto iou_box = batch_box(batch_boxes_start, iou_candidate_box.second); return not this->suppress_by_iou(iou_box, next_box, iou_threshold); }); remainder_boxes.resize(it - remainder_boxes.begin()); boxes_heap = remainder_boxes; } }); std::copy(selected_indices.begin(), selected_indices.end(), output.begin()); return selected_indices.size() / 3; } argument compute(const shape& output_shape, std::vector args) const { // make buffer of maximum size shape max_output_shape = {output_shape.type(), output_shape.max_lens()}; argument result{max_output_shape}; std::size_t max_output_boxes_per_class = (args.size() > 2) ? (args.at(2).at()) : 0; if(max_output_boxes_per_class == 0) { return result; } double iou_threshold = (args.size() > 3) ? (args.at(3).at()) : 0.0f; double score_threshold = (args.size() > 4) ? (args.at(4).at()) : 0.0f; std::size_t num_selected = 0; result.visit([&](auto output) { get_all(args[0], args[1])([&](auto boxes, auto scores) { num_selected = compute_nms(output, boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold); }); }); if(use_dyn_output) { return result.reshape({output_shape.type(), {num_selected, 3}}); } else { return result; } } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/nonzero.hpp000066400000000000000000000056731510465702400234250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_NONZERO_HPP #define MIGRAPHX_GUARD_OPERATORS_NONZERO_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct nonzero { std::string name() const { return "nonzero"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1).standard(); auto elem_num = inputs[0].elements(); auto dim_num = inputs[0].lens().size(); std::vector out_lens = {dim_num, elem_num}; return {shape::int64_type, out_lens}; } argument compute(const shape& output_shape, std::vector args) const { std::vector> vec_idx; auto s = args.front().get_shape(); args.front().visit([&](auto v) { shape_for_each(s, [&](const auto& idx_v, size_t idx) { if(not float_equal(v[idx], 0)) { vec_idx.push_back(idx_v); } }); }); argument result{output_shape}; result.visit([&](auto output) { std::fill(output.begin(), output.end(), 0); par_for(vec_idx.size(), [&](auto i) { for(std::size_t j = 0; j < vec_idx.front().size(); ++j) { output[output_shape.index({j, i})] = vec_idx[i][j]; } }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/normalize_attribute.hpp000066400000000000000000000054451510465702400260130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_OP_NORMALIZE_ATTRIBUTE_HPP #define MIGRAPHX_GUARD_OPERATORS_OP_NORMALIZE_ATTRIBUTE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * `normalize_attribute` settings: * Note that default options are not included as enums. * 1. `use_input` (default) vs. `use_output`: * Affects the rank of the attribute. * `use_input -> lens.size()`, `use_output -> lens.size() + vec.size()`. * 2. use_rank (default) vs use_len: * `use_rank` sets the max value/index of the attribute as the rank of lens. * `use_lens` sets the max value/index as the corresponding value in lens at the axes index. * Uses the dynamic_dimension.max value for dynamic shapes. Returns the original vector * (no normalization) if any of dynamic_dimension[axes] are not fixed. * 3. `clip_min` vs. `not_clip_min` (default): * Clip values less than the minimum to the minimum or not. * 4. `include_min` vs. `exclude_min` (default): * Include or exclude the minimum value/index for range checking and clipping. * 5. `clip_max` vs. `not_clip_max` (default): * Clip values greater than the maximum or not. * 6. `include_max` vs. `exclude_max` (default): * Include or exclude the maximum value/index for range checking and clipping. * 7. `normalize_padding`: * To normalize the padding to `2*(pad ndim)` dimensions. */ enum class normalize_attribute { use_output, use_len, clip_max, clip_min, include_max, include_min, normalize_padding }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/onehot.hpp000066400000000000000000000155401510465702400232210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ONEHOT_HPP #define MIGRAPHX_GUARD_OPERATORS_ONEHOT_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Produces a one-hot tensor. * Called with `axis` attribute that defaults to the last output axis * Constant depth: `onehot(indices, values), depth attribute must be set; * Variable depth: `onehot(indices, depth, values)`; * `indicies` as a N rank tensor of indices where value is `on_value` * `depth` scalar with the number of classes for the one-hot dimension * `values` `[off_value, on_value]` * `axis` which axis to add the one-hot dimension to * For axis = 0 and rank(indices) = 2: * output is A[indicies[j, k], j, k] = on_value; A[i, j, k] = off_value otherwise * Can be simplified to other operators when `indices` has a static shape and * `depth` is constant at compile-time. */ struct onehot { // cannot use normalize_attribute here since rank(output_shape) = rank(indices) + 1 int64_t axis = -1; // optional depth attribute for static output shape std::optional depth; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis"), f(self.depth, "depth")); } std::string name() const { return "onehot"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this, true}.has(2, 3); const auto& indices_shape = inputs[0]; auto normalized_axis = (this->axis < 0) ? this->axis + indices_shape.ndim() + 1 : this->axis; if(normalized_axis > indices_shape.ndim()) { MIGRAPHX_THROW("ONEHOT: axis is out of range"); } if(depth.has_value()) { if(depth.value() < 0) { MIGRAPHX_THROW("ONEHOT: negative depth attribute value"); } check_shapes{inputs, *this, true}.has(2); // `values` should have static shape (void)check_shapes{inputs.begin() + 1, inputs.end(), *this, false}; const auto& values_shape = inputs[1]; if(not indices_shape.dynamic()) { // static output shape auto output_lens = indices_shape.lens(); output_lens.insert(output_lens.begin() + normalized_axis, depth.value()); return {values_shape.type(), output_lens}; } // dynamic output shape auto output_dds = indices_shape.to_dynamic().dyn_dims(); std::size_t depth_val = depth.value(); output_dds.insert(output_dds.begin() + normalized_axis, shape::dynamic_dimension{depth_val, depth_val}); return {values_shape.type(), output_dds}; } else { // dynamic output shape check_shapes{inputs, *this, true}.has(3); // `depth` and `values` should have static shape (void)check_shapes{inputs.begin() + 1, inputs.end(), *this, false}; const auto& values_shape = inputs[2]; auto output_dds = indices_shape.to_dynamic().dyn_dims(); std::size_t max_val = std::numeric_limits::max(); output_dds.insert(output_dds.begin() + normalized_axis, shape::dynamic_dimension{0, max_val}); return {values_shape.type(), output_dds}; } } argument compute(const shape&, std::vector args) const { auto indices_shape = args[0].get_shape(); int64_t depth_val; auto values_iter = args.begin(); if(this->depth.has_value()) { assert(args.size() == 2); depth_val = depth.value(); values_iter += 1; } else { assert(args.size() == 3); args[1].visit([&](auto d) { depth_val = d(0); }); values_iter += 2; } if(depth_val < 0) { MIGRAPHX_THROW("ONEHOT: negative depth value"); } auto output_lens = indices_shape.lens(); auto normalized_axis = (axis < 0) ? axis + indices_shape.ndim() + 1 : axis; output_lens.insert(output_lens.begin() + normalized_axis, depth_val); shape output_shape{values_iter->get_shape().type(), output_lens}; argument result{output_shape}; visit_all(result, *values_iter)([&](auto output, auto values) { auto off_value = values(0); auto on_value = values(1); // fill result with off_value par_for(output_shape.elements(), [&](auto i) { output[i] = off_value; }); args[0].visit([&](auto indices) { auto ind_s = indices.get_shape(); shape_for_each(ind_s, [&](const auto& idx) { auto index = indices(idx.begin(), idx.end()); // normalize negative indices index = (index < 0) ? index + depth_val : index; // no on_value if index is out of range if(index >= 0 and index < depth_val) { std::vector out_idx = idx; out_idx.insert(out_idx.begin() + normalized_axis, index); output(out_idx.begin(), out_idx.end()) = on_value; } }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/outline.hpp000066400000000000000000000041511510465702400234000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_OUTLINE_HPP #define MIGRAPHX_GUARD_OPERATORS_OUTLINE_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct outline { shape s; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape")); } std::string name() const { return "outline"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(0); return s; } argument compute(const shape&, const std::vector&) const { return {s, nullptr}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/pack_fp4.hpp000066400000000000000000000071351510465702400234150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_PACK_FP4_HPP #define MIGRAPHX_GUARD_OPERATORS_PACK_FP4_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct pack_fp4 { int64_t axis = -1; std::string name() const { return "pack_fp4"; } value attributes() const { value normalize = value::object{}; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } migraphx::shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.same_dims().has(1); const auto& in_shape = inputs.front(); auto new_lens = in_shape.lens(); if(new_lens[axis] % 2 != 0) { MIGRAPHX_THROW("PACK_FP4: Can not pack axis that has odd lengths"); } new_lens[axis] /= 2; return {migraphx::shape::fp4x2_type, new_lens}; } argument compute(const shape& output_shape, const std::vector& args) const { const auto& input = args.front(); auto in_shape = input.get_shape(); argument result{output_shape}; auto out = result.get(); input.visit([&](auto inp) { par_for(output_shape.elements(), [&](auto i) { using inp_type = typename decltype(inp)::value_type; auto data_idx = output_shape.multi(i); auto in_data_multi_idx = data_idx; in_data_multi_idx[axis] *= 2; inp_type inp_val0 = inp[in_data_multi_idx]; in_data_multi_idx[axis] += 1; inp_type inp_val1 = inp[in_data_multi_idx]; uint8_t out_val0 = cast_to_fp4(inp_val0); uint8_t out_val1 = cast_to_fp4(inp_val1); // NOTE: integral promotion occurs when bitshifting for uint8_t out[i] = static_cast(out_val1 << 4u) | static_cast(out_val0 & 0xFu); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/pack_int4.hpp000066400000000000000000000107361510465702400236030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_PACK_INT4_HPP #define MIGRAPHX_GUARD_OPERATORS_PACK_INT4_HPP #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct pack_int4 { int64_t axis = -1; std::string name() const { return "pack_int4"; } value attributes() const { value normalize = value::object{}; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } migraphx::shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.same_dims().has(1); const auto& in_shape = inputs.front(); if(in_shape.type() != migraphx::shape::int8_type and in_shape.type() != migraphx::shape::uint8_type) { MIGRAPHX_THROW("PACK_INT4: Only Int8 or Uint8 is supported for packing"); } auto new_lens = in_shape.lens(); if(new_lens[axis] % 2 != 0) { MIGRAPHX_THROW("PACK_INT4: Can not pack axis that has odd lengths"); } new_lens[axis] /= 2; return {in_shape.type(), new_lens}; } argument compute(const shape& output_shape, std::vector args) const { auto input = args.front(); auto in_shape = input.get_shape(); argument result{output_shape}; visit_all(result, input)([&](auto out, auto inp) { par_for(output_shape.elements(), [&](auto i) { using type = typename decltype(inp)::value_type; type min_4bit; // clip min value type max_4bit; // clip max value if constexpr(std::is_signed{}) { min_4bit = -8; max_4bit = 7; } else { min_4bit = 0; max_4bit = 15; } auto data_idx = output_shape.multi(i); auto in_data_multi_idx = data_idx; in_data_multi_idx[axis] *= 2; type val1 = inp[in_data_multi_idx]; in_data_multi_idx[axis] += 1; type val2 = inp[in_data_multi_idx]; // clip: val1 = std::min(std::max(val1, min_4bit), max_4bit); val2 = std::min(std::max(val2, min_4bit), max_4bit); // pack: // the bit operations are forced into uint8_t mode, // and this would avoid compiler warnings as well. uint8_t val_ui8_1 = static_cast(val1); uint8_t val_ui8_2 = static_cast(val2); out[i] = (val_ui8_2 << 4) | (val_ui8_1 & 0xf); // NOLINT(hicpp-signed-bitwise) }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/pad.hpp000066400000000000000000000063041510465702400224670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_PAD_HPP #define MIGRAPHX_GUARD_OPERATORS_PAD_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct pad { std::vector pads; float value = 0.0f; enum pad_op_mode_t { constant_pad, reflect_pad, edge_pad }; pad_op_mode_t mode = constant_pad; template static auto reflect(Self& self, F f) { return pack(f(self.mode, "mode"), f(self.pads, "pads"), f(self.value, "value")); } std::string name() const { return "pad"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& s0 = inputs.front(); if(s0.dynamic()) { auto out_dyn_dims = s0.dyn_dims(); for(std::size_t i = 0; i < s0.ndim(); ++i) { out_dyn_dims[i] += pads[i] + pads[i + s0.ndim()]; } return {s0.type(), out_dyn_dims}; } else { auto&& idims = s0.lens(); std::vector rdims(idims.begin(), idims.end()); std::size_t num_dims = rdims.size(); for(std::size_t i = 0; i < num_dims; i++) { rdims[i] += pads[i] + pads[i + num_dims]; } return s0.with_lens(rdims); } } std::size_t pad_ndims() const { assert(pads.size() % 2 == 0); return pads.size() / 2; } bool symmetric() const { std::size_t num_dims = pads.size() / 2; return std::equal( pads.begin(), pads.begin() + num_dims, pads.begin() + num_dims, pads.end()); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/pointwise.hpp000066400000000000000000000077071510465702400237540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OP_POINTWISE_HPP #define MIGRAPHX_GUARD_OP_POINTWISE_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct pointwise { std::string name() const { return "pointwise"; } shape compute_shape(const std::vector& inputs, std::vector mods) const { if(mods.size() != 1) { MIGRAPHX_THROW("should have one submodule."); } if(inputs.empty()) MIGRAPHX_THROW("pointwise should have at least one input"); auto* pm = mods.front(); auto pnames = pm->get_parameter_names(); check_shapes{inputs, *this}.has(pnames.size()).same_dims(); auto result = pm->compute_shapes( inputs, {.name = name(), .strict_type = true, .scalar_const_out_lens = inputs.front().lens()}); if(result.size() == 1) return result.front(); return shape{result}; } argument compute(const shape& output_shape, const std::vector& args, const std::vector& mods, const std::function( module_ref&, const std::unordered_map&)>& run) const { argument output{output_shape}; auto* pm = mods.front(); auto pnames = pm->get_parameter_names(); std::sort(pnames.begin(), pnames.end()); par_for(args[0].get_shape().elements(), [&](auto i) { std::unordered_map params; std::transform( pnames.begin(), pnames.end(), args.begin(), std::inserter(params, params.end()), [&](auto&& name, auto&& arg) { return std::make_pair(name, arg.element(i)); }); auto results = run(pm, params); assert(results.size() == output.get_sub_objects().size() or (results.size() == 1 and output.get_sub_objects().empty())); std::vector outputs; if(results.size() == 1) outputs = {output.share()}; else outputs = output.share().get_sub_objects(); for(auto j : range(results.size())) visit_all(outputs[j], results[j])([&](auto out, auto x) { out[i] = x.front(); }); }); return output; } value attributes() const { return {"fillcolor", "#9ACD32" /* yellow green */}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_OP_POINTWISE_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/pooling.hpp000066400000000000000000000460061510465702400233750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_POOLING_HPP #define MIGRAPHX_GUARD_OPERATORS_POOLING_HPP #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { // The Pooling operator mostly follows the specifications for the Onnx pooling op. // It assumes an NCHW layout, extended to support any number of spatial dimensions // from 1 on up; dimensions are // struct pooling { // Class members mode, ceil_mode, padding_mode have similar names but refer to separate // concepts. pooling_mode mode = {pooling_mode::average}; // If the input has rank other than 4 then padding, stride, lengths must all be specified // since the defaults have 2-dimensions. Exception: padding not required if // padding_mode != default_ // Padding along each spatial input dimension // Can be ndim or 2*ndim values where ndim is size of lengths // ndim values means pad the same before and after each dimension // 2*ndim values contains n pre and then n post padding values std::vector padding = {0, 0}; // Size of stride to take from one placement of the pooling kernel to the next. // This is distinct from the strides used by the shape class. Must be the same // ndim as lengths. std::vector stride = {1, 1}; // Spatial dimensions of the pooling kernel or window, // 2 smaller than the input tensor rank (NCHW layout) std::vector lengths = {1, 1}; // Spacing between the elements of the pooling kernel. Must be the same ndim as lengths. std::vector dilations = {1, 1}; // ceiling mode is a flag affecting output size // or equivalently, placements of the pooling kernel. // When true, round the size upwards. When false, round down so that all // kernel placements fit but some input values may be dropped. bool ceil_mode = false; int lp_order = 2; // Mode for auto padding. default_ indicates no auto padding. padding_mode_t padding_mode = padding_mode_t::default_; // Global pooling with dynamic shape input bool dyn_global = false; // Whether padding elements are included in the average count bool count_include_pad = false; template static auto reflect(Self& self, F f) { return pack(f(self.mode, "mode"), f(self.padding, "padding"), f(self.padding_mode, "padding_mode"), f(self.stride, "stride"), f(self.lengths, "lengths"), f(self.dilations, "dilations"), f(self.ceil_mode, "ceil_mode"), f(self.count_include_pad, "count_include_pad"), f(self.lp_order, "lp_order"), f(self.dyn_global, "dyn_global")); } std::string name() const { return "pooling"; } void check_attribute_size() const { if(dyn_global) return; if((padding_mode != default_ and padding.size() != stride.size() and (padding.size()) != stride.size() * 2) or stride.size() != lengths.size() or dilations.size() != lengths.size()) { MIGRAPHX_THROW("POOLING: inconsistent attribute sizes"); } const auto is_zero = [](auto el) { return el == 0; }; if(std::any_of(lengths.begin(), lengths.end(), is_zero) or std::any_of(stride.begin(), stride.end(), is_zero) or std::any_of(dilations.begin(), dilations.end(), is_zero)) { MIGRAPHX_THROW("POOLING: size 0 pooling kernel or stride or dilations"); } } size_t kdims() const { check_attribute_size(); return stride.size(); } value attributes() const { return {{"normalize_padding", "padding"}, {"fillcolor", "#3CB371" /* medium sea green */}}; } std::size_t dilate_dim(std::size_t dim, std::size_t dilation) const { return 1 + dilation * (dim - 1); } std::vector calc_spatial_dim_out(const std::vector& input_lens, std::size_t kdims) const { std::vector output_lens{}; for(size_t i = 0; i < kdims; ++i) { std::size_t padding_factor = 2 * padding[i]; if(padding.size() == 2 * kdims) padding_factor = padding[i] + padding[i + kdims]; std::size_t dilated_length = dilate_dim(lengths[i], dilations[i]); std::size_t dim_size; if(input_lens[i + 2] + padding_factor < dilated_length) { if(padding_mode == default_) MIGRAPHX_THROW("POOLING: not enough padding for the given kernel size"); // lengths can be legitimately larger only if we're doing auto padding // with a dynamic shape, in which case given padding is ignored. Set a dummy value. dim_size = 2; } else { dim_size = input_lens[i + 2] + padding_factor - dilated_length; } std::size_t len = (ceil_mode) ? dim_size / stride[i] + static_cast((dim_size % stride[i] != 0)) // ceil uint divide : dim_size / stride[i]; // floor divide output_lens.push_back(len + 1); } return output_lens; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1).min_ndims(3); check_attribute_size(); const shape& input = inputs.at(0); auto stride_size = stride.size(); size_t kdims = input.ndim() - 2; if(input.ndim() != stride_size + 2) { MIGRAPHX_THROW("POOLING: input and attribute size mismatch!"); } if(input.dynamic()) { auto input_dyn_dims = input.dyn_dims(); std::vector output_dyn_dims(input_dyn_dims.begin(), input_dyn_dims.begin() + 2); if(dyn_global) { for(size_t i = 0; i < kdims; ++i) { output_dyn_dims.push_back(shape::dynamic_dimension{1, 1}); } return {input.type(), output_dyn_dims}; } else if(padding_mode != default_) { const size_t num_spatial_dims = inputs[0].ndim() - 2; const shape& x_shape = inputs[0]; // same as convolution::dynamic_compute_shape() for(std::size_t i = 0; i < num_spatial_dims; ++i) { auto ceil_div = [](std::size_t x, std::size_t y) { return (x + y - 1) / y; }; auto s = stride[i]; auto x = x_shape.dyn_dims()[i + 2]; std::set optimals{}; std::transform(x.optimals.begin(), x.optimals.end(), std::inserter(optimals, optimals.begin()), [&](auto o) { return ceil_div(o, s); }); output_dyn_dims.push_back( shape::dynamic_dimension{ceil_div(x.min, s), ceil_div(x.max, s), optimals}); } return {input.type(), output_dyn_dims}; } else { // does not compute optimals auto min_spatial_dims = calc_spatial_dim_out(input.min_lens(), kdims); auto max_spatial_dims = calc_spatial_dim_out(input.max_lens(), kdims); for(size_t i = 0; i < kdims; ++i) { output_dyn_dims.push_back( shape::dynamic_dimension{min_spatial_dims[i], max_spatial_dims[i], {}}); } return {input.type(), output_dyn_dims}; } } else { auto input_lens = input.lens(); std::vector output_lens(input_lens.begin(), input_lens.begin() + 2); // Used for when normalize_compute_shape() is called again at model eval time // for an originally dynamic shape. Kernel shape is not used with dyn_global. if(dyn_global) { for(size_t i = 0; i < kdims; ++i) { output_lens.push_back(1); } return {input.type(), output_lens}; } else { auto output_spatial_lens = calc_spatial_dim_out(input_lens, kdims); output_lens.insert( output_lens.end(), output_spatial_lens.begin(), output_spatial_lens.end()); return inputs[0].with_lens(output_lens); } } } struct lpnorm_pool { int p = 0; lpnorm_pool() = delete; explicit lpnorm_pool(int x) : p{x} {}; template double init() const { return 0.0; } double operator()(double x, double y) const { return x + std::pow(std::abs(y), p); } double final(double x, std::size_t) const { return (p == 0) ? 1 : std::pow(x, 1. / p); } }; struct avg_pool { template double init() const { return 0.0; } double operator()(double x, double y) const { return x + y; } double final(double x, std::size_t y) const { return (y == 0) ? 0.0 : (x / y); } }; struct max_pool { template T init() const { return std::numeric_limits::lowest(); } double operator()(double x, double y) const { return std::max(x, y); } double final(double x, std::size_t) const { return (x); } }; template void calc_pooling(const shape& output_shape, Out& output, const In& input, const std::vector& kernel_dims, const std::vector& padding_vals, Op op) const { auto in_s = input.get_shape(); auto in_lens = in_s.lens(); // For each element of output; i.e., for each placement of pooling kernel... par_for(output_shape.elements(), [&](auto i) { auto idx_o = output_shape.multi(i); auto n_dim = idx_o.size(); // starting offset of the pooling window std::vector win_start; std::vector win_size; // For each spatial dimension, find starting and ending index of pooling kernel for(std::size_t dim = 2; dim < n_dim; ++dim) { auto d_2 = dim - 2; int start = static_cast(idx_o[dim] * stride[d_2]) - static_cast(padding_vals[d_2]); int end; std::size_t dilated_kernel_dim = dilate_dim(kernel_dims[d_2], dilations[d_2]); // NOLINT if(count_include_pad and mode == pooling_mode::average) { // Even when using padding, if in ceil_mode a window // could extend beyond the end of both input and // padding. Clip out-of-bounds indexes but not padding. // Check if this kernel extends beyond the padding at end of dimension end = std::min(start + dilated_kernel_dim, in_lens[dim] + static_cast(padding_vals[d_2])); } else { // count_include_pad is false, or for max pooling, clip off padding. end = std::min(start + dilated_kernel_dim, in_lens[dim]); } win_start.push_back(start); if(end < start) { // This error can be caused by misc. bad input combinations MIGRAPHX_THROW("POOLING: invalid attributes"); } win_size.push_back(end - start); } shape win_shape{output_shape.type(), win_size}; auto pool_size = win_shape.elements(); double output_val = op.template init(); // for each element in the window... shape_for_each(win_shape, [&](const auto& idx_w) { // Skip elements that belong to the dilated area for(size_t axis = 0; axis < idx_w.size(); ++axis) { if(idx_w[axis] % dilations[axis]) { pool_size -= 1; return; } } // the coordinates of this element auto idx = idx_o; // Add the kernel location idx_w and the offset win_start, for each dimension. // Negative results are cast to very large unsigned integers. std::transform(idx_w.begin(), idx_w.end(), win_start.begin(), idx.begin() + 2, [](auto ii, auto jj) { return ii + jj; }); // Check if any of coordinates are out of input tensor's range if(std::equal(idx.begin() + 2, idx.end(), in_lens.begin() + 2, in_lens.end(), std::less<>{})) { output_val = op(output_val, input[idx]); } else { // this is a padding element. Padding locations // don't contribute to average or max pooling total but can play in // lpnorm pooling. if(mode == pooling_mode::lpnorm) { output_val = op(output_val, op.template init()); } if(mode == pooling_mode::average and not count_include_pad) { // Ignore padding pool_size -= 1; } } }); output[i] = Type(op.final(output_val, pool_size)); }); } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result; auto input_lens = args[0].get_shape().lens(); std::vector kernel_dims; shape output_shape; // If we have to auto-calculate padding, it will be passed to calc_pooling() as an argument // instead of the member variable padding. std::vector temp_padding(padding); if(dyn_global) { // for dynamic GlobalPooling, there's no padding kernel_dims.insert(kernel_dims.end(), input_lens.begin() + 2, input_lens.end()); output_shape = dyn_out.computed_shape; result = argument{dyn_out.computed_shape}; } else if((padding_mode != op::padding_mode_t::default_)) { // if padding_mode is set, input was a dynamic size. Calculate padded size now. // kernel_lens is the same as kernel_dims, but prepended with the 2 non- // spatial dimensions. For size computations, it's used like the weights // tensor for convolutions. std::vector kernel_lens; kernel_lens.insert(kernel_lens.end(), input_lens.begin(), input_lens.begin() + 2); kernel_lens.insert(kernel_lens.end(), lengths.begin(), lengths.end()); kernel_dims = this->lengths; auto type = args[0].get_shape().type(); // dilation not currently supported for pooling, so default to all 1's temp_padding = calc_dyn_auto_pad( input_lens, kernel_lens, stride, {1, 1}, bool(padding_mode == op::same_upper)); output_shape = compute_padded_pool_shape( args[0].get_shape(), shape(type, kernel_dims), temp_padding, stride, {1, 1}); result = argument(output_shape); } else // fixed/static input { kernel_dims = this->lengths; output_shape = dyn_out.computed_shape; result = argument{dyn_out.computed_shape}; } // Perform the computation and populate result visit_all(result, args[0])([&](auto output, auto input) { using type = typename decltype(output)::value_type; switch(mode) { case migraphx::op::pooling_mode::average: calc_pooling( output_shape, output, input, kernel_dims, temp_padding, avg_pool{}); break; case migraphx::op::pooling_mode::max: calc_pooling( output_shape, output, input, kernel_dims, temp_padding, max_pool{}); break; case migraphx::op::pooling_mode::lpnorm: calc_pooling( output_shape, output, input, kernel_dims, temp_padding, lpnorm_pool{lp_order}); break; } }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/pow.hpp000066400000000000000000000031301510465702400225220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_POW_HPP #define MIGRAPHX_GUARD_OPERATORS_POW_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct pow : binary { auto apply() const { return [](auto x, auto y) { return std::pow(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/prefix_scan_op.hpp000066400000000000000000000126701510465702400247250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ /** * Parent struct for prefix scan ops. A prefix scan is a mathematical entity useful * in parallelizing various computations. Given a list of numbers, a prefix scan * op returns an equal size list of running totals of the values. Other operations * besides addition can be supported by child ops. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCAN_OP_HPP #define MIGRAPHX_GUARD_OPERATORS_SCAN_OP_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Parent struct for prefix scan operations. A prefix scan is equivalent to the C++ * std::exclusive_scan or std::inclusive_scan. Given a list of numbers, a prefix scan * sum op returns an equal size list of running totals of the values. Other operations * besides addition can be supported by their own child ops. */ template struct prefix_scan_op : op_name { int64_t axis; bool exclusive = false; bool reverse = false; template static auto reflect(Self& self, F f) { return pack( f(self.axis, "axis"), f(self.exclusive, "exclusive"), f(self.reverse, "reverse")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); auto s = inputs.front(); if(s.dynamic()) { return s; } else if(s.broadcasted()) { return {s.type(), s.lens()}; } else { return s.with_lens(s.lens()); } } argument compute(const dyn_output& dyn_out, std::vector args) const { shape output_shape(dyn_out.computed_shape); argument result{output_shape}; auto s = args[0].get_shape(); if(s == output_shape) { result = args[0].copy(); } else { visit_all(result, args[0])([&](auto output, auto input) { par_for(output_shape.elements(), [&](auto i) { output[output_shape.index(i)] = input[s.index(i)]; }); }); s = output_shape; } auto slice = shape{s.type(), {s.lens()[axis]}, {s.strides()[axis]}}; auto lens = s.lens(); lens[axis] = 1; auto batch = shape{s.type(), lens, s.strides()}; auto& self = static_cast(*this); result.visit([&](auto output) { using type = decltype(output); par_for(batch.elements(), [&](auto i) { auto* start = output.data() + batch.index(i); type x{slice, start}; if(reverse) { if(exclusive) { std::copy(++x.begin(), x.end(), x.begin()); x.back() = 0; } std::partial_sum(std::make_reverse_iterator(x.end()), std::make_reverse_iterator(x.begin()), std::make_reverse_iterator(x.end()), self.op()); } else { if(exclusive) { std::copy_backward(x.begin(), --x.end(), x.end()); x.front() = 0; } std::partial_sum(x.begin(), x.end(), x.begin(), self.op()); } }); }); return result; } auto init() const {} prefix_scan_op() : axis(0) {} prefix_scan_op(int64_t ax) : axis(ax) {} prefix_scan_op(int64_t ax, bool excl) : axis(ax), exclusive(excl) {} prefix_scan_op(int64_t ax, bool excl, bool rev) : axis(ax), exclusive(excl), reverse(rev) {} }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/prefix_scan_sum.hpp000066400000000000000000000037541510465702400251160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCAN_INCLUSIVE_SUM_HPP #define MIGRAPHX_GUARD_OPERATORS_SCAN_INCLUSIVE_SUM_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct prefix_scan_sum : prefix_scan_op { prefix_scan_sum() {} prefix_scan_sum(int64_t ax) : prefix_scan_op(ax) {} prefix_scan_sum(int64_t ax, bool excl) : prefix_scan_op(ax, excl) {} prefix_scan_sum(int64_t ax, bool excl, bool rev) : prefix_scan_op(ax, excl, rev) {} auto op() const { return [](auto x, auto y) { return x + y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/prelu.hpp000066400000000000000000000032431510465702400230510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_PRELU_HPP #define MIGRAPHX_GUARD_OPERATORS_PRELU_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct prelu : binary { std::string point_op() const { return "(${0} < 0) ? (${0} * ${1}) : ${0}"; } auto apply() const { return [](auto x, auto slope) { return ((x < 0) ? (x * slope) : x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/quant_convolution.hpp000066400000000000000000000132351510465702400255130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_QUANT_CONVOLUTION_HPP #define MIGRAPHX_GUARD_OPERATORS_QUANT_CONVOLUTION_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * 2 input version: * Standard quantized convolution operation * inputs = {A_mat, W_mat} * * 4 input version: * Quantized convolution with two sets of scales for A and W matricies. * inputs = {A_mat, W_mat, scale_A, scale_W} */ struct quant_convolution { std::vector padding = {0, 0}; std::vector stride = {1, 1}; std::vector dilation = {1, 1}; padding_mode_t padding_mode = default_; int group = 1; template static auto reflect(Self& self, F f) { return pack(f(self.padding, "padding"), f(self.stride, "stride"), f(self.dilation, "dilation"), f(self.padding_mode, "padding_mode"), f(self.group, "group")); } value attributes() const { return {{"general_data_type", "convolution"}, {"normalize_padding", "padding"}}; } std::string name() const { return "quant_convolution"; } void check_attribute_size() const { if((padding.size() != stride.size() and (padding.size() / 2) != stride.size()) or stride.size() != dilation.size()) { MIGRAPHX_THROW("QUANT_CONVOLUTION: inconsistent attribute sizes"); } } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(2, 4); check_shapes{{inputs.at(0), inputs.at(1)}, *this}.same_type().same_ndims().min_ndims(3); if(inputs.size() == 4) { check_shapes{{inputs.at(2), inputs.at(3)}, *this}.same_type(); } check_attribute_size(); const shape& input = inputs.at(0); const shape& weights = inputs.at(1); auto t = input.type(); size_t loc_kdims = input.lens().size() - 2; if(loc_kdims != this->kdims()) { MIGRAPHX_THROW("QUANT_CONVOLUTION: input k-dims does not match attribute size"); } // limit input types to int8, fp8 types, float, or fp4x2 std::set supported_types = fp8_types{}.get(); supported_types.insert(shape::int8_type); supported_types.insert(shape::float_type); supported_types.insert(shape::fp4x2_type); if(not contains(supported_types, t)) { MIGRAPHX_THROW("QUANT_CONVOLUTION: only supports int8_t, uint8_t, fp4x2, and fp8"); } std::vector output_lens{input.lens()[0], weights.lens()[0]}; auto padding_size = padding.size(); for(size_t i = 0; i < loc_kdims; i++) { auto padding_factor = 2 * padding[i]; if(padding_size == 2 * loc_kdims) padding_factor = padding[i] + padding[i + loc_kdims]; output_lens.push_back(std::size_t(std::max( 1, (input.lens()[i + 2] - (1 + dilation[i] * (weights.lens()[i + 2] - 1)) + padding_factor) / stride[i] + 1))); } if(t == shape::int8_type) { return inputs[0].with_lens(shape::int32_type, output_lens); } return inputs[0].with_lens(shape::float_type, output_lens); } size_t kdims() const { check_attribute_size(); return stride.size(); } argument compute(shape output_shape, std::vector args) const { // TODO: implement ref version of 4 input quant_convolution if(args.size() != 2) { MIGRAPHX_THROW("QUANT_CONVOLUTION: ref 4 input quantized convolution not implemented"); } argument result{output_shape}; result.visit([&](auto output) { get_all(args[0], args[1])([&](auto input, auto weights) { migraphx::convolution(output, input, weights, padding, stride, dilation, group); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/quant_dot.hpp000066400000000000000000000100101510465702400237060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_QUANT_DOT_HPP #define MIGRAPHX_GUARD_OPERATORS_QUANT_DOT_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * 2 input version: * Standard quantized GEMM operation * inputs = {A_mat, B_mat} * * 4 input version: * Quantized GEMM with two sets of scales for A and B matricies. * inputs = {A_mat, B_mat, scale_A, scale_B} */ struct quant_dot { value attributes() const { return {{"general_data_type", "dot"}}; } std::string name() const { return "quant_dot"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(2, 4); check_shapes{{inputs.at(0), inputs.at(1)}, *this}.same_type(); if(inputs.size() == 4) { check_shapes{{inputs.at(2), inputs.at(3)}, *this}.same_type(); } const shape& a = inputs.at(0); const shape& b = inputs.at(1); auto t = a.type(); std::set supported_types = fp8_types{}.get(); supported_types.insert(shape::int8_type); supported_types.insert(shape::uint8_type); // for how mxfp4 is handled with pack/unpack supported_types.insert(shape::float_type); if(not contains(supported_types, t)) { MIGRAPHX_THROW("QUANT_DOT: only supports int8_t, uint8_t, float, and fp8"); } if(not std::all_of( inputs.begin(), inputs.end(), [](auto s) { return s.lens().size() >= 2; })) { MIGRAPHX_THROW("QUANT_DOT: dot only accepts >= 2D operands"); } // only handle the case that the batch size of a and b are the same if(not std::equal( a.lens().rbegin() + 2, a.lens().rend(), b.lens().rbegin() + 2, b.lens().rend())) { MIGRAPHX_THROW("QUANT_DOT: batch size of A and B mismatch: {" + to_string_range(a.lens()) + "} x {" + to_string_range(b.lens()) + "}"); } std::size_t dim_0 = a.lens().size() - 2; std::size_t dim_1 = a.lens().size() - 1; if(a.lens()[dim_1] != b.lens()[dim_0]) { MIGRAPHX_THROW("QUANT_DOT: inner dimensions do not match: {" + to_string_range(a.lens()) + "} x {" + to_string_range(b.lens()) + "}"); } auto out_lens = a.lens(); out_lens[dim_1] = b.lens()[dim_1]; if(inputs.size() == 4 or contains(fp8_types{}.get(), t)) { return {shape::float_type, out_lens}; } // else int8 gemm return {shape::int32_type, out_lens}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/quantizelinear.hpp000066400000000000000000000105541510465702400247600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_QUANTIZE_LINEAR_HPP #define MIGRAPHX_GUARD_OPERATORS_QUANTIZE_LINEAR_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct quantizelinear { std::string name() const { return "quantizelinear"; } std::optional out_type; value attributes() const { // Note: point_op attribute is not used in this op. Instead, in // gpu compilation pipeline, rewrite_quantization will be invoked // from generate_pointwise() to rewrite this op. return {{"pointwise", true}}; } template static auto reflect(Self& self, F f) { return pack(f(self.out_type, "out_type")); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.same_dims().has(2, 3); if(inputs[0].type() != inputs[1].type()) { MIGRAPHX_THROW("QUANTIZELINEAR: Scales and input must be the same type"); } if(inputs.size() == 3) { return inputs[0].with_lens(inputs[2].type(), inputs[0].lens()); } return inputs[0].with_lens(out_type.value_or(shape::uint8_type), inputs[0].lens()); } argument compute(const shape& output_shape, std::vector args) const { auto x = args.at(0); auto y_scale = args.at(1); std::vector zeros(output_shape.bytes(), 0); argument y_zero_point{output_shape, zeros.data()}; if(args.size() == 3) { y_zero_point = args.at(2); } argument result{output_shape}; auto rounding_mode = fegetround(); fesetround(FE_TONEAREST); visit_all(result, y_zero_point)([&](auto output, auto zero_pts) { visit_all(x, y_scale)([&](auto input, auto scales) { using quant_type = typename decltype(output)::value_type; auto min_value = std::numeric_limits::lowest(); auto max_value = std::numeric_limits::max(); par_for(output_shape.elements(), [&](auto i) { double quantized; if constexpr(std::is_integral{}) { quantized = static_cast(std::nearbyint(input[i] / scales[i])) + static_cast(zero_pts[i]); } else { quantized = static_cast(input[i]) / static_cast(scales[i]) + static_cast(zero_pts[i]); } output[i] = std::max(static_cast(min_value), std::min(static_cast(max_value), quantized)); }); }); }); fesetround(rounding_mode); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/random_seed.hpp000066400000000000000000000047001510465702400242010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RANDOM_SEED_HPP #define MIGRAPHX_GUARD_OPERATORS_RANDOM_SEED_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Generates a random seed for the use of random number generators. Generating the seed * at runtime guarantees there will be a different random sequence on every execution. * This operation has no inputs or attributes, and outputs an unsigned integer tensor with * a single value. */ struct random_seed { shape::type_t dtype = shape::type_t::uint64_type; template static auto reflect(Self& self, F f) { return pack(f(self.dtype, "dtype")); } std::string name() const { return "random_seed"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(0); return shape{dtype}; } argument compute(const shape& output_shape, const std::vector&) const { argument result(output_shape); result.visit([&](auto output) { output.front() = std::random_device{}(); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/random_uniform.hpp000066400000000000000000000107321510465702400247420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ /** * Random Uniform distribution operator. Given a shape, populate it with random * values. Calls to random_uniform using the same randomization seed as a * literal input will * always generate the same pseudo-random sequence. * * Inputs: (1) randomization seed (any type is allowed) * (2) output buffer argument to be populated. * * Attributes: none * * Output: Returns the buffer from input #2. * */ #ifndef MIGRAPHX_GUARD_OPERATORS_RANDOM_UNIFORM_HPP #define MIGRAPHX_GUARD_OPERATORS_RANDOM_UNIFORM_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * random_uniform populates the passed shape with random numbers, in a uniform * distribution. Range for floating-point data types is (0, 1); * for integer types it is [0, ] */ struct random_uniform { // The random_uniform operation needs the random number generator seed // to be passed as a runtime input. std::string name() const { return "random_uniform"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2); return inputs.at(1); } argument compute(const dyn_output& dyn_out, std::vector args) const { // Output goes into the passed buffer, not the shape output. argument result{dyn_out.computed_shape}; uint64_t local_seed = args[0].at(0); std::mt19937 gen(local_seed); result.visit([&](auto output) { using type = typename decltype(output)::value_type; if constexpr(std::is_integral{}) { #ifdef _MSC_VER // According to the C++ specification, the effect is undefined if the result type // for the generator is not one of short, int, long, long long, unsigned short, // unsigned int, unsigned long, or unsigned long long. See // https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution. if constexpr(sizeof(type) == 1) { std::uniform_int_distribution dis{std::numeric_limits::min(), std::numeric_limits::max()}; std::generate(output.begin(), output.end(), [&] { return dis(gen); }); } else #endif { // default range for all integer types is // (0, std::uniform_int_distribution::max()). // Todo: enable different ranges std::uniform_int_distribution dis; std::generate(output.begin(), output.end(), [&] { return dis(gen); }); } } else { // default real distribution type is double with range (0, 1); std::uniform_real_distribution<> dis; std::generate(output.begin(), output.end(), [&] { return dis(gen); }); } }); return result; } std::ptrdiff_t output_alias(const std::vector&) const { return 1; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/recip.hpp000066400000000000000000000031461510465702400230260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RECIP_HPP #define MIGRAPHX_GUARD_OPERATORS_RECIP_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct recip : unary { std::string point_op() const { return "1 / ${0}"; } auto apply() const { return [](auto x) { return 1 / x; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reduce_all.hpp000066400000000000000000000035261510465702400240250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_REDUCE_ALL_HPP #define MIGRAPHX_GUARD_OPERATORS_REDUCE_ALL_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reduce_all : reduce_op { reduce_all() {} reduce_all(std::vector ax) : reduce_op(std::move(ax)) {} auto op() const { return [=](auto x, auto y) { if(static_cast(x) and static_cast(y)) return decltype(x){1}; return decltype(x){0}; }; } auto init() const { return one(); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reduce_any.hpp000066400000000000000000000034541510465702400240440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_REDUCE_ANY_HPP #define MIGRAPHX_GUARD_OPERATORS_REDUCE_ANY_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reduce_any : reduce_op { reduce_any() {} reduce_any(std::vector ax) : reduce_op(std::move(ax)) {} auto op() const { return [=](auto x, auto y) { if(static_cast(x) or static_cast(y)) return decltype(x){1}; return decltype(x){0}; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reduce_max.hpp000066400000000000000000000033371510465702400240420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_REDUCE_MAX_HPP #define MIGRAPHX_GUARD_OPERATORS_REDUCE_MAX_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reduce_max : reduce_op { reduce_max() {} reduce_max(std::vector ax) : reduce_op(std::move(ax)) {} auto op() const { return [=](auto x, auto y) { return x > y ? x : y; }; } auto init() const { return lowest(); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reduce_mean.hpp000066400000000000000000000034741510465702400241770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_REDUCE_MEAN_HPP #define MIGRAPHX_GUARD_OPERATORS_REDUCE_MEAN_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reduce_mean : reduce_op { reduce_mean() {} reduce_mean(std::vector ax) : reduce_op(std::move(ax)) {} auto op() const { return [](auto x, auto y) { return x + y; }; } auto output(const shape& s) const { return [&](auto val) { return val / static_cast(s.elements()); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reduce_min.hpp000066400000000000000000000033401510465702400240320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_REDUCE_MIN_HPP #define MIGRAPHX_GUARD_OPERATORS_REDUCE_MIN_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reduce_min : reduce_op { reduce_min() {} reduce_min(std::vector ax) : reduce_op(std::move(ax)) {} auto op() const { return [=](auto x, auto y) { return x < y ? x : y; }; } auto init() const { return highest(); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reduce_op.hpp000066400000000000000000000204751510465702400236750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_OP_HPP #define MIGRAPHX_GUARD_OPERATORS_OP_HPP #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct lowest { template operator T() const { return std::numeric_limits::lowest(); } }; struct highest { template operator T() const { return std::numeric_limits::max(); } }; struct zero { template operator T() const { return T{0}; } }; struct one { template operator T() const { return T{1}; } }; template struct reduce_op : op_name { std::vector axes{}; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes")); } value attributes() const { value normalize; normalize["axes"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}, {"reduce", true}, {"fillcolor", "#8470FF" /* lightslateblue */}}; } shape collapse_reduced_axes(const shape& original_shape, const std::vector& reduce_axes) const { auto lens = original_shape.lens(); for(const auto a : reduce_axes) { lens[a] = 1; } return original_shape.with_lens(lens); } // Compute the output shape for cases when the input tensor has a dynamic shape. // // If the axes are passed as a variable input(indicated by an empty axes attribute), we cannot // determine which axes must be collapsed until we see the actual input values, so we must treat // each axis as potentially collapsable and set its minimum dimension to 1. shape compute_dynamic_shape(const std::vector& inputs) const { const auto& data_shape = inputs[0]; auto dims = data_shape.dyn_dims(); if(axes.empty()) { for(auto& dim : dims) { dim = {1, dim.max}; } } else { for(auto a : axes) { dims[a] = {1, 1}; } } return {data_shape.type(), dims}; } // Compute the output shape for cases when the input tensor has a static shape. // Depending on how axes is passed to the operator the output shape can be either dynamic or // static. // // If the axes are passed as a variable input(indicated by an empty axes attribute), we cannot // determine which axes must be collapsed until we see the actual input values, so we must treat // each axis as potentially collapsable, producing a dynamic output shape. shape compute_static_shape(const std::vector& inputs) const { const auto& data_shape = inputs[0]; if(axes.empty()) { std::vector dims(data_shape.ndim()); auto lens = data_shape.lens(); std::transform(lens.begin(), lens.end(), dims.begin(), [](auto len) { return shape::dynamic_dimension{1, len}; }); return {data_shape.type(), std::move(dims)}; } else { return collapse_reduced_axes(data_shape, axes); } } /** * @brief returns a shape in which the axis or axes named * for reduction by this op are set, to size 1. * * @param inputs list of input shapes * @return shape */ shape normalize_compute_shape(std::vector inputs) const { auto expected_arg_count = axes.empty() ? 2 : 1; check_shapes{inputs, *this, true}.has(expected_arg_count); if(inputs[0].dynamic()) { return compute_dynamic_shape(inputs); } else { return compute_static_shape(inputs); } } template void tune_dims(const std::vector& tuned_axes, const std::vector& in_lens, std::vector& out_lens) const { for(const auto& axis : tuned_axes) { out_lens[axis] = in_lens[axis]; } } template void reduce(const tensor_view& input, const shape& batch_shape, const std::vector& tuned_axes, const std::vector& out_idx, tensor_view& output) const { using accumulator = accumulator_type; auto& self = static_cast(*this); auto data_idx = out_idx; accumulator val = self.init(); shape_for_each(batch_shape, [&](const auto& b_idx) { this->tune_dims(tuned_axes, b_idx, data_idx); accumulator x = input(data_idx.begin(), data_idx.end()); val = self.op()(accumulator{self.input()(x)}, val); }); output(out_idx.begin(), out_idx.end()) = static_cast(*this).output(batch_shape)(val); } argument reduce(const shape& computed_shape, const std::vector& reduce_axes, argument& data_arg) const { std::vector batch_lens(computed_shape.ndim(), 1); auto arg_lens = data_arg.get_shape().lens(); tune_dims(reduce_axes, arg_lens, batch_lens); shape batch_shape{computed_shape.type(), batch_lens}; argument result{computed_shape}; visit_all(result, data_arg)([&](auto output, auto input) { par_for(computed_shape.elements(), [&](auto i) { auto out_idx = computed_shape.multi(i); this->reduce(input, batch_shape, reduce_axes, out_idx, output); }); }); return result; } argument compute(const dyn_output& dyn_out, std::vector args) const { auto&& data_arg = args[0]; // cppcheck-suppress knownConditionTrueFalse if(not axes.empty()) return reduce(dyn_out.computed_shape, axes, data_arg); if(args[1].get_shape().elements() == 0) return args[0]; std::vector reduce_axes; args[1].visit([&](auto&& s) { reduce_axes.assign(s.begin(), s.end()); }); const auto result_shape = collapse_reduced_axes(data_arg.get_shape(), reduce_axes); return reduce(result_shape, reduce_axes, data_arg); } auto init() const { return zero(); } auto input() const { return [](auto val) { return val; }; } auto output(const shape&) const { return [](auto val) { return val; }; } reduce_op() {} reduce_op(std::vector ax) : axes(std::move(ax)) {} }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reduce_prod.hpp000066400000000000000000000033321510465702400242140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_REDUCE_PROD_HPP #define MIGRAPHX_GUARD_OPERATORS_REDUCE_PROD_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reduce_prod : reduce_op { reduce_prod() {} reduce_prod(std::vector ax) : reduce_op(std::move(ax)) {} auto op() const { return [=](auto x, auto y) { return x * y; }; } auto init() const { return one(); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reduce_sum.hpp000066400000000000000000000032531510465702400240560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_REDUCE_SUM_HPP #define MIGRAPHX_GUARD_OPERATORS_REDUCE_SUM_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reduce_sum : reduce_op { reduce_sum() {} reduce_sum(std::vector ax) : reduce_op(std::move(ax)) {} auto op() const { return [=](auto x, auto y) { return x + y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/relu.hpp000066400000000000000000000033101510465702400226640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RELU_HPP #define MIGRAPHX_GUARD_OPERATORS_RELU_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct relu : unary { std::string point_op() const { return "${function:max}(decltype(${0}){0}, ${0})"; } auto apply() const { return [](auto x) { return std::max(decltype(x){0}, x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reshape.hpp000066400000000000000000000204261510465702400233530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RESHAPE_HPP #define MIGRAPHX_GUARD_OPERATORS_RESHAPE_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * 1 input version: * reshape(input_data) * this.dims = output_dims * Makes a copy of input_data to the output shape. * * 2 input version: * reshape(input_data, output_buffer) * this.dims = unset * Copies input_data to output_buffer; output_buffer already has the output shape. * This version will not fail gracefully if the input shape and output_buffer shape are * incompatible. There's a throw that will catch when the number of elements do not match at * runtime. This version should only be used for dynamic reshapes (output dimensions only known at * runtime). If output_buffer has a static shape during compile/parse, you can use the 1 input * version. */ struct reshape { std::vector dims; template static auto reflect(Self& self, F f) { return pack(f(self.dims, "dims")); } std::string name() const { return "reshape"; } // Assumes that the shape from the `dims` attribute will be valid at run-time. // Makes no checks for the validity of the `dims` attribute for the given input shape. shape dyn_1arg_compute_shape(shape s0) const { auto input_dyn_dims = s0.dyn_dims(); const auto neg_dim_num = std::distance(this->dims.begin(), std::find(this->dims.begin(), this->dims.end(), -1)); const bool has_negative_dim_attr = neg_dim_num < dims.size(); // construct output dynamic shape from dims attribute std::vector output_dyn_dims(dims.size()); // NOTE: input_dyn_dims.size() may not equal dims.size() for(std::size_t i = 0; i < dims.size(); ++i) { auto d = dims.at(i); if(d == 0) { output_dyn_dims.at(i) = input_dyn_dims.at(i); } else if(d == -1) { output_dyn_dims.at(i) = {1, 1}; } else { std::size_t u_dim = d; output_dyn_dims.at(i) = {u_dim, u_dim}; } } if(has_negative_dim_attr) { // comparing the -1 dimension against the other dimensions // accumulate the minimum and maximum elements in the dimensions before the -1 dimension std::size_t min_cur_elements = 1; std::size_t max_cur_elements = 1; for(const auto& dd : output_dyn_dims) { min_cur_elements = mul_sat(min_cur_elements, dd.min); max_cur_elements = mul_sat(max_cur_elements, dd.max); } // accumulate the elements in the input dimensions std::size_t min_input_elements = 1; std::size_t max_input_elements = 1; for(const auto& dd : input_dyn_dims) { min_input_elements = mul_sat(min_input_elements, dd.min); max_input_elements = mul_sat(max_input_elements, dd.max); } // maximum dimensions should never accumulate to zero assert(max_cur_elements != 0); std::size_t max_int = std::numeric_limits::max(); // handle 0 dimension value (keep unknown lower bound) std::size_t min_dim = (min_cur_elements == 0) ? 0 : min_input_elements / min_cur_elements; // handle maximum dimension value (keep unknown upper bound) std::size_t max_dim = (max_cur_elements == max_int) ? max_int : max_input_elements / max_cur_elements; shape::dynamic_dimension x_dd = {min_dim, max_dim}; output_dyn_dims.at(neg_dim_num) = x_dd; } return {s0.type(), output_dyn_dims}; } shape static_compute_shape(std::vector inputs, std::size_t n_neg_dims) const { check_shapes{inputs, *this}.has(1); auto&& idims = inputs.front().lens(); std::vector rdims(dims.begin(), dims.end()); for(std::size_t i = 0; i < dims.size(); i++) { if(dims[i] == 0) rdims[i] = idims[i]; // convert -1 to 1 for rdims since rdims uses size_t (-1 is max_int for size_t) if(dims[i] == -1) rdims[i] = 1; } if(n_neg_dims > 0) { size_t missing_dim = inputs.front().elements() / std::accumulate(rdims.begin(), rdims.end(), 1, std::multiplies()); for(std::size_t i = 0; i < rdims.size(); i++) { if(dims[i] == -1) rdims[i] = missing_dim; } } auto s = shape{inputs.front().type(), rdims}; if(s.elements() != inputs.front().elements()) MIGRAPHX_THROW("Reshape: Wrong number of elements for reshape: reshape has " + std::to_string(s.elements()) + " elements whereas the input has " + std::to_string(inputs.front().elements())); return s; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2); auto n_neg_dims = std::count(dims.begin(), dims.end(), -1); if(n_neg_dims > 1) MIGRAPHX_THROW("Reshape: Dimensions for reshape can only have one -1 dim"); const auto& s0 = inputs.front(); if(inputs.size() == 1) { if(s0.dynamic()) { return dyn_1arg_compute_shape(s0); } else { return static_compute_shape(inputs, n_neg_dims); } } else { return inputs.back(); } } argument compute(const dyn_output& dyn_out, std::vector args) const { assert(dyn_out.computed_shape.standard()); if(args.size() == 1) { argument result{dyn_out.computed_shape}; visit_all(result, args[0])([&](auto output, auto input) { std::copy(input.begin(), input.end(), output.begin()); }); return result; } else { // 2 arg if(args[0].get_shape().elements() != args[1].get_shape().elements()) { MIGRAPHX_THROW("Reshape: Number of elements must match at runtime. Input: " + std::to_string(args[0].get_shape().elements()) + " Output buffer: " + std::to_string(args[1].get_shape().elements())); } visit_all(args[1], args[0])([&](auto output, auto input) { std::copy(input.begin(), input.end(), output.begin()); }); return args[1]; } } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reshape_lazy.hpp000066400000000000000000000267611510465702400244220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RESHAPE_LAZY_HPP #define MIGRAPHX_GUARD_OPERATORS_RESHAPE_LAZY_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reshape_lazy { std::vector dims; template static auto reflect(Self& self, F f) { return pack(f(self.dims, "dims")); } value attributes() const { return {{"require_std_shape", true}}; } std::string name() const { return "reshape_lazy"; } shape dyn_compute_shape(shape s0) const { const auto& dyn_dims = s0.dyn_dims(); auto num_not_fixed = std::count_if( dyn_dims.cbegin(), dyn_dims.cend(), [](const auto& dd) { return not dd.is_fixed(); }); if(num_not_fixed != 1) { MIGRAPHX_THROW("reshape_lazy: Only supports one non-fixed dynamic_dimension"); } // track number of fixed elements in input and output std::size_t num_dims_ele = 1; std::size_t num_dd_ele = 1; for(std::size_t i = 0; i < dyn_dims.size(); ++i) { if(dyn_dims[i].is_fixed()) { num_dims_ele *= dims[i]; num_dd_ele *= dyn_dims[i].min; } else { if(dims[i] != 0 and dims[i] != -1) { MIGRAPHX_THROW( "reshape_lazy: Non-fixed dynamic_dimension doesn't match with 0 or -1 " "output dimension"); } } } if(num_dims_ele != num_dd_ele) { MIGRAPHX_THROW("reshape_lazy: Number of fixed elements must match. Input: " + std::to_string(num_dd_ele) + " Output: " + std::to_string(num_dims_ele)); } // construct output dynamic shape from dims attribute std::vector output_dyn_dims(dims.size()); std::transform(dims.cbegin(), dims.cend(), dyn_dims.cbegin(), output_dyn_dims.begin(), [](std::size_t dim, auto dyn_dim) { if(not dyn_dim.is_fixed()) return dyn_dim; return shape::dynamic_dimension{dim, dim}; }); return {s0.type(), output_dyn_dims}; } template static auto compute_end_dim(Iterator start, Iterator last, std::size_t dim) { std::size_t x = 1; auto it = std::find_if(start, last, [&](auto i) { x *= i; return x >= dim; }); if(x != dim) return start; return it; } template static OptionalPair try_merge_pairs(OptionalPair p2, OptionalPair p1) { if(not p1.has_value()) return nullopt; if(not p2.has_value()) return nullopt; auto dim1 = p1->first; auto dim2 = p2->first; auto stride1 = p1->second; auto stride2 = p2->second; auto elements = dim1 * dim2; // Transposed if(stride2 > stride1) return nullopt; // Broadcasted check to avoid division by zero if(stride2 == 0) { if(stride1 == 0) return {{elements, 0}}; return nullopt; } if(stride1 % stride2 != 0) return nullopt; auto space = (stride1 * dim1 + stride2 * dim2 - stride1) / stride2; // Nonpacked if(space != elements) return nullopt; return {{elements, stride2}}; } template static optional merge_strides(DimIterator dim_start, DimIterator dim_last, StrideIterator stride_start, StrideIterator stride_last) { if(dim_start == dim_last) return nullopt; (void)stride_start; // Is only used in the assert assert(std::distance(dim_start, dim_last) == std::distance(stride_start, stride_last)); auto make_pair_optional = [&](auto dim, auto stride) { return std::make_optional(std::make_pair(dim, stride)); }; auto dim_stride_pair = std::inner_product(std::make_reverse_iterator(dim_last - 1), std::make_reverse_iterator(dim_start), std::make_reverse_iterator(stride_last - 1), make_pair_optional(*std::prev(dim_last), *std::prev(stride_last)), MIGRAPHX_LIFT(try_merge_pairs), make_pair_optional); if(not dim_stride_pair.has_value()) return nullopt; return dim_stride_pair->second; } template static auto can_strides_merge(DimIterator dim_start, DimIterator dim_last, StrideIterator stride_start, StrideIterator stride_last) { return merge_strides(dim_start, dim_last, stride_start, stride_last).has_value(); } // This will attempt to alias the dimensions of the input shape to the lens of // `rdims`. If this can't be done without changing memory layout then it // will return nullopt static optional reshape_lazy_dims(const shape& input, const std::vector& rdims) { if(input.standard()) return shape{input.type(), rdims}; const auto& idims = input.lens(); const auto& istrides = input.strides(); std::vector rstrides; std::size_t i = 0; std::size_t r = 0; while(i < idims.size() and r < rdims.size()) { auto idim = idims[i]; auto rdim = rdims[r]; if(rdim == idim) { rstrides.push_back(istrides[i]); } // squeeze else if(rdim > idim) { auto start = idims.begin() + i; auto it = compute_end_dim(start, idims.end(), rdim); if(it == start) return nullopt; auto n = it - start; assert((i + n) <= istrides.size()); if(not can_strides_merge( start, it + 1, istrides.begin() + i, istrides.begin() + i + n + 1)) return nullopt; i += n; rstrides.push_back(istrides[i]); } // unsqueeze else // if(rdim < idim) { auto start = rdims.begin() + i; auto it = compute_end_dim(start, rdims.end(), idim); if(it == start) return nullopt; auto n = it - start; assert((r + n) <= rdims.size()); auto stride = istrides[i] * idim; std::for_each(start, it + 1, [&](auto dim) { stride /= dim; rstrides.push_back(stride); }); r += n; } i++; r++; } // Handle trailing 1s if(rstrides.size() < rdims.size() and not rstrides.empty()) { auto stride = rstrides.back(); for(auto d : range(rdims.begin() + rstrides.size(), rdims.end())) { if(d != 1) return nullopt; rstrides.push_back(stride); } } if(rdims.size() != rstrides.size()) return nullopt; return shape{input.type(), rdims, rstrides}; } shape static_compute_shape(std::vector inputs, std::size_t n_neg_dims) const { check_shapes{inputs, *this}.has(1); auto&& idims = inputs.front().lens(); std::vector rdims(dims.begin(), dims.end()); for(std::size_t i = 0; i < dims.size(); i++) { if(dims[i] == 0) rdims[i] = idims[i]; // since rdims using size_t type, -1 is the max value // is size_t that cause later compuation incorrect if(dims[i] == -1) rdims[i] = 1; } if(n_neg_dims > 0) { size_t missing_dim = inputs.front().elements() / std::accumulate(rdims.begin(), rdims.end(), 1, std::multiplies()); for(std::size_t i = 0; i < rdims.size(); i++) { if(dims[i] == -1) rdims[i] = missing_dim; } } auto s = reshape_lazy_dims(inputs.front(), rdims); if(not s.has_value()) MIGRAPHX_THROW("reshape_lazy on axis that is not packed."); if(s->elements() != inputs.front().elements()) MIGRAPHX_THROW( "reshape_lazy: Wrong number of elements for reshape_lazy: reshape_lazy has " + std::to_string(s->elements()) + " elements whereas the input has " + std::to_string(inputs.front().elements())); assert(s->bytes() == inputs.front().bytes()); return *s; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); auto n_neg_dims = std::count(dims.begin(), dims.end(), -1); if(n_neg_dims > 1) MIGRAPHX_THROW("reshape_lazy: Dimensions for reshape_lazy can only have one -1 dim"); const auto& s0 = inputs[0]; if(s0.dynamic()) { return dyn_compute_shape(s0); } else { return static_compute_shape(inputs, n_neg_dims); } } argument compute(const dyn_output& dyn_out, std::vector args) const { return args[0].reshape(dyn_out.computed_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/resize.hpp000066400000000000000000000322041510465702400232220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RESIZE_HPP #define MIGRAPHX_GUARD_OPERATORS_RESIZE_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * The Resize operation mirrors the Onnx Resize operation with some differences. * Currently, only Nearest mode is supported. "Axes" and "ROI" attributes not recognized. * * Accepts either one or two runtime inputs. * Input 0 - data to be resized * Input 1 - sizes or scales. If data type is uint64, Input 1 is interpreted as output sizes; * otherwise as scaling factors. * * If the second input is not used, either a "sizes" or "scales" attribute must be * provided. */ struct resize { // Selects one of several integer rounding rules for use with Nearest mode. // // Returns a lambda that returns size_t. static auto& get_nearest_op(const std::string& near_mode) { using nearest_op = std::function; static std::unordered_map const nearest_ops = { {"round_prefer_floor", [=](std::size_t d_in, double val) { val = std::max(0.0, std::min(d_in - 1.0, val)); return static_cast(std::ceil((val - 0.5))); }}, {"round_prefer_ceil", [=](std::size_t d_in, double val) { val = std::max(0.0, std::min(d_in - 1.0, val)); return static_cast(std::round((val))); }}, {"floor", [=](std::size_t d_in, double val) { val = std::max(0.0, std::min(d_in - 1.0, val)); return static_cast(std::floor((val))); }}, {"ceil", [=](std::size_t d_in, double val) { val = std::max(0.0, std::min(d_in - 1.0, val)); return static_cast(std::ceil((val))); }}}; if(not contains(nearest_ops, near_mode)) { MIGRAPHX_THROW("RESIZE: nearest_mode " + near_mode + " not supported!"); } return nearest_ops.at(near_mode); } // Selects one of several rules for converting a coordinate by a scaling factor. // These rules differ in how they account for subpixel distances and end // values. They apply to all modes. // // Returns a lambda that returns double. static auto& get_original_idx_op(const std::string& s_mode) { using original_idx_op = std::function; static std::unordered_map const idx_ops = { {"half_pixel", [=](std::size_t, std::size_t, std::size_t idx, double scale) { return (idx + 0.5) / scale - 0.5; }}, {"pytorch_half_pixel", [=](std::size_t, std::size_t l_out, std::size_t idx, double scale) { return l_out > 1 ? (idx + 0.5) / scale - 0.5 : 0.0; }}, {"align_corners", [=](std::size_t l_in, std::size_t l_out, std::size_t idx, double) { return (l_out == 1) ? 0.0 : (1.0 * idx * (l_in - 1.0) / (l_out - 1.0)); }}, {"asymmetric", [=](std::size_t, std::size_t, std::size_t idx, double scale) { return idx / scale; }}, {"tf_half_pixel_for_nn", [=](std::size_t, std::size_t, std::size_t idx, double scale) { return (idx + 0.5) / scale; }}}; if(not contains(idx_ops, s_mode)) { MIGRAPHX_THROW("RESIZE: coordinate_transformation_mode " + s_mode + " not supported!"); } return idx_ops.at(s_mode); } std::vector scales; std::vector sizes; // what integer rounding rule to use with Nearest mode. std::string nearest_mode{"floor"}; // Resizing modes. 1: nearest 2: bilinear/linear 3: cubic // Only "nearest" currently supported. std::string mode{"nearest"}; // What floating-point conversion rule to use (any resizing mode) std::string coordinate_transformation_mode; std::string name() const { return "resize"; } template static auto reflect(Self& self, F f) { return pack(f(self.scales, "scales"), f(self.sizes, "sizes"), f(self.nearest_mode, "nearest_mode"), f(self.mode, "mode"), f(self.coordinate_transformation_mode, "coordinate_transformation_mode")); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2); if(mode != "nearest") MIGRAPHX_THROW("RESIZE: Only Nearest mode is supported"); // Inputs are X, sizes or scale, ROI and axes not supported. if(inputs.size() == 1) { if(inputs.front().dynamic()) { // Not supported at this point--needs different validity checking MIGRAPHX_THROW("RESIZE: Single dynamic input not supported"); } const auto& input_s = inputs.front(); // No size/scale input. Size/scale must be an attribute and so output will be static. if((sizes.empty()) == (scales.empty())) MIGRAPHX_THROW( "RESIZE: One and only one of sizes or scales attributes must be given"); if(not sizes.empty()) { if(sizes.size() != input_s.ndim()) MIGRAPHX_THROW("RESIZE: sizes attribute's size must match rank of input X"); return shape{input_s.type(), sizes}; } else { if(scales.size() != input_s.ndim()) MIGRAPHX_THROW("RESIZE: scales attribute's size must match rank of input X"); std::vector lens; std::transform(scales.begin(), scales.end(), input_s.lens().begin(), std::back_inserter(lens), [](auto scale_i, size_t in_len) { return static_cast(scale_i * in_len); }); return shape{input_s.type(), lens}; } } else { // 2 inputs: 2nd sets sizes/scales for output. if(inputs.back().ndim() != 1) MIGRAPHX_THROW("RESIZE: size/scale input must have rank 1"); if(inputs.back().dynamic()) MIGRAPHX_THROW("RESIZE: size/scale input must have static shape"); if(inputs.front().ndim() != inputs.back().lens()[0]) MIGRAPHX_THROW("RESIZE: size/scale input's size must match rank of input X"); // Note: a shape with {0, max_val} ranges can't really be allocated. For the reference // target, this doesn't matter because the real output shape is determined in the // compute() method. For any other target, there must be a compiler pass that replaces // this operation with a fixed-size output at runtime. std::size_t max_val = std::numeric_limits::max(); std::vector dyn_dims(inputs.back().lens().at(0), shape::dynamic_dimension{0, max_val}); return {inputs.front().type(), dyn_dims}; } } argument compute(const migraphx::shape&, std::vector args) const { auto in_lens = args[0].get_shape().lens(); std::vector out_lens(in_lens.size()); // Scales are either given, or calculated from output shape std::vector vec_scale(in_lens.size(), 1.0f); if(args.size() == 1) { // single input argument; sizes or scales is constant. // In practice, the input is never a dynamic shape. if(not sizes.empty()) { out_lens = sizes; // compute scales std::transform(out_lens.begin(), out_lens.end(), in_lens.begin(), vec_scale.begin(), [](size_t out_len, size_t in_len) { return (in_len == 0 ? 1.f : static_cast(out_len) / in_len); }); } else { vec_scale = this->scales; // compute output sizes std::transform(in_lens.begin(), in_lens.end(), scales.begin(), out_lens.begin(), [](size_t in_len, auto scale_i) { return static_cast(scale_i * in_len); }); } } else { // 2 inputs; 2nd input is either sizes or scales. // First input may be dynamic. args[1].visit([&](auto input) { using type = typename decltype(input)::value_type; if constexpr(std::is_integral{}) { // Copy the output size from args[1]. std::copy(input.begin(), input.end(), out_lens.begin()); // Deduce the scales for each axis std::transform( input.begin(), input.end(), in_lens.begin(), vec_scale.begin(), [](auto sz, size_t in_len) { return static_cast(sz) / in_len; }); } else { // read the scale from args[1] // std::copy(input.begin(), input.end(), vec_scale.begin()); // compute the output dimensions from the given scales. This computation // always rounds down, unlike the internal computation in Nearest mode // which has several options as given in nearest_mode. std::transform(input.begin(), input.end(), in_lens.begin(), out_lens.begin(), [](auto scale_i, size_t in_len) { return static_cast(scale_i * in_len); }); } }); } shape output_shape = {args[0].get_shape().type(), out_lens}; argument result{output_shape}; auto nearest_op = get_nearest_op(nearest_mode); auto idx_op = get_original_idx_op(coordinate_transformation_mode); // Populate each element in output by selecting "nearest" item in input. visit_all(result, args[0])([&](auto output, auto data) { migraphx::shape out_comp_shape{data.get_shape().type(), out_lens}; shape_for_each(out_comp_shape, [&](const auto& out_idx_v, size_t out_idx) { std::vector in_idx(out_idx_v.size()); for(auto ii = 0; ii < out_idx_v.size(); ++ii) { auto idx_val = idx_op(in_lens[ii], out_lens[ii], out_idx_v[ii], vec_scale[ii]); in_idx[ii] = nearest_op(in_lens[ii], idx_val); } output[out_idx] = data(in_idx.begin(), in_idx.end()); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/reverse.hpp000066400000000000000000000055561510465702400234060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_REVERSE_HPP #define MIGRAPHX_GUARD_OPERATORS_REVERSE_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct reverse { std::vector axes; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes")); } std::string name() const { return "reverse"; } value attributes() const { value normalize; normalize["axes"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1); return inputs[0].with_lens(inputs[0].lens()); } argument compute(const shape& s, std::vector args) const { argument result{s}; auto lens = s.lens(); visit_all(result, args.front())([&](auto output, auto input) { shape_for_each(s, [&](const auto& out_idx_v, size_t out_idx) { auto in_idx = out_idx_v; for(const auto& axis : axes) { in_idx[axis] = lens[axis] - 1 - out_idx_v[axis]; } output[out_idx] = input[s.index(in_idx)]; }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/rnn.hpp000066400000000000000000000060701510465702400225200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RNN_HPP #define MIGRAPHX_GUARD_OPERATORS_RNN_HPP #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct rnn { std::size_t hidden_size = 1; std::vector actv_funcs{tanh{}, tanh{}}; rnn_direction direction = rnn_direction::forward; float clip = 0.0f; template static auto reflect(Self& self, F f) { return pack(f(self.hidden_size, "hidden_size"), f(self.actv_funcs, "actv_func"), f(self.direction, "direction"), f(self.clip, "clip")); } std::string name() const { return "rnn"; } shape compute_shape(std::vector inputs) const { auto in_dims = inputs[0].lens(); auto hidden_dims = inputs[2].lens(); if(hidden_size != hidden_dims[2]) { MIGRAPHX_THROW("RNN: hidden size mismatch in attribute and input"); } std::size_t num_directions = 1; if(direction == rnn_direction::bidirectional) { num_directions = 2; } if(num_directions != hidden_dims[0]) { MIGRAPHX_THROW("RNN: num_direction mismatch in attribute and input"); } std::vector out_dims(in_dims); out_dims.insert(out_dims.begin() + 1, num_directions); out_dims.back() = hidden_size; return {inputs[0].type(), out_dims}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/rnn_last_cell_output.hpp000066400000000000000000000036371510465702400261700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RNN_LAST_CELL_OUTPUT_HPP #define MIGRAPHX_GUARD_OPERATORS_RNN_LAST_CELL_OUTPUT_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct rnn_last_cell_output { std::string name() const { return "rnn_last_cell_output"; } shape compute_shape(std::vector inputs) const { auto dims = inputs[0].lens(); // remove the first dimension, remaing are output shape dims.erase(dims.begin()); return {inputs[0].type(), dims}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/rnn_last_hs_output.hpp000066400000000000000000000036271510465702400256620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RNN_LAST_HS_OUTPUT_HPP #define MIGRAPHX_GUARD_OPERATORS_RNN_LAST_HS_OUTPUT_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct rnn_last_hs_output { std::string name() const { return "rnn_last_hs_output"; } shape compute_shape(std::vector inputs) const { auto dims = inputs[0].lens(); // remove the first dimension, remaing are output shape dims.erase(dims.begin()); return {inputs[0].type(), dims}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/rnn_var_sl_last_output.hpp000066400000000000000000000042371510465702400265340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RNN_VAR_SL_LAST_OUTPUT_HPP #define MIGRAPHX_GUARD_OPERATORS_RNN_VAR_SL_LAST_OUTPUT_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct rnn_var_sl_last_output { rnn_direction direction = rnn_direction::forward; template static auto reflect(Self& self, F f) { return pack(f(self.direction, "direction")); } std::string name() const { return "rnn_var_sl_last_output"; } shape compute_shape(std::vector inputs) const { auto dims = inputs[0].lens(); // remove the first dimension, remaing are output shape dims.erase(dims.begin()); return {inputs[0].type(), dims}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/rnn_variable_seq_lens.hpp000066400000000000000000000113071510465702400262550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RNN_VARIABLE_SEQ_LENS_HPP #define MIGRAPHX_GUARD_OPERATORS_RNN_VARIABLE_SEQ_LENS_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct rnn_var_sl_shift_output { std::string output_name = "hidden_states"; rnn_direction direction = rnn_direction::forward; template static auto reflect(Self& self, F f) { return pack(f(self.output_name, "output_name"), f(self.direction, "direction")); } std::string name() const { return "rnn_var_sl_shift_output"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(2); return inputs[0]; } argument compute(const shape& output_shape, std::vector args) const { argument result{output_shape}; int64_t max_len = output_shape.lens()[0]; visit_all(result, args[0])([&](auto output, auto input) { using value_type = typename decltype(output)::value_type; get_all(args[1])([&](auto seq_lens) { par_for(output_shape.elements(), [&](auto i) { auto idx = output_shape.multi(i); auto batch_id = idx[2]; auto d = idx[1]; auto t = idx[0]; auto sl = seq_lens[batch_id]; value_type val = value_type{0}; if(t < sl) { auto in_idx = idx; int offset = (direction == rnn_direction::reverse or d == 1) ? 1 : 0; in_idx[0] += offset * (max_len - sl); val = input(in_idx.begin(), in_idx.end()); } output(idx.begin(), idx.end()) = val; }); }); }); return result; } }; struct rnn_var_sl_shift_sequence { std::string name() const { return "rnn_var_sl_shift_sequence"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(2); return inputs[0]; } argument compute(const shape& output_shape, std::vector args) const { argument result{output_shape}; int64_t max_len = output_shape.lens()[0]; visit_all(result, args[0])([&](auto output, auto input) { using value_type = typename decltype(output)::value_type; args[1].visit([&](auto seq_lens) { par_for(output_shape.elements(), [&](auto i) { auto idx = output_shape.multi(i); auto b = idx[1]; auto t = idx[0]; auto sl = seq_lens[b]; value_type val = value_type{0}; if(t >= max_len - sl) { auto in_idx = idx; in_idx[0] -= (max_len - sl); val = input(in_idx.begin(), in_idx.end()); } output(idx.begin(), idx.end()) = val; }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/roialign.hpp000066400000000000000000000271361510465702400235350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_ROIALIGN_HPP #define MIGRAPHX_GUARD_OPERATORS_ROIALIGN_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct roialign { std::string coord_trans_mode = "half_pixel"; pooling_mode mode = {pooling_mode::average}; int64_t output_height = 1; int64_t output_width = 1; int64_t sampling_ratio = 0; float spatial_scale = 1.0f; template static auto reflect(Self& self, F f) { return pack(f(self.coord_trans_mode, "coordinate_transformation_mode"), f(self.mode, "mode"), f(self.output_height, "output_height"), f(self.output_width, "output_width"), f(self.sampling_ratio, "sampling_ratio"), f(self.spatial_scale, "spatial_scale")); } std::string name() const { return "roialign"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(3); auto x_lens = inputs.at(0).lens(); auto roi_lens = inputs.at(1).lens(); auto bi_lens = inputs.at(2).lens(); auto type = inputs.at(0).type(); // check input correct if(bi_lens.size() != 1) { MIGRAPHX_THROW("ROIALIGN: batch indices should be 1 dimension!"); } if(roi_lens.size() != 2 or roi_lens.at(1) != 4) { MIGRAPHX_THROW( "ROIALIGN: rois should be 2 dimensions, and the second dim should be 4!"); } if(roi_lens.front() != bi_lens.front()) { MIGRAPHX_THROW("ROIALIGN: rois and batch indices inputs should have the same number!"); } std::vector out_lens = x_lens; out_lens[0] = roi_lens[0]; out_lens[2] = output_height; out_lens[3] = output_width; return {type, out_lens}; } struct pos_weight { // neighbor indices for the bilinear interpolation std::array pos = {0, 0, 0, 0}; // neighbor weights for the bilinear interpolation std::array w = {0.0f, 0.0f, 0.0f, 0.0f}; }; auto calc_pos_weight(const std::array& dims, const shape& comp_s, const std::array& roi_start, const std::array& bin_size, const std::array& bin_grid_size) const { std::vector results(bin_grid_size[0] * bin_grid_size[1] * output_height * output_width); shape_for_each(comp_s, [&](const auto& idx_v, size_t index) { std::array p = {idx_v[0], idx_v[1]}; std::array i = {idx_v[2], idx_v[3]}; std::array xy{}; std::array low{}; std::array high{}; for(auto ii : range(p.size())) { xy[ii] = roi_start[ii] + p[ii] * bin_size[ii] + (i[ii] + .5f) * bin_size[ii] / bin_grid_size[ii]; xy[ii] = (coord_trans_mode == "half_pixel") ? (xy[ii] - 0.5f) : xy[ii]; if(xy[ii] < -1.0 or xy[ii] > dims[ii]) { results[index] = pos_weight{}; return; } xy[ii] = std::max(xy[ii], 0.0f); low[ii] = xy[ii]; high[ii] = low[ii] + 1; if(low[ii] >= dims[ii] - 1) { xy[ii] = high[ii] = low[ii] = dims[ii] - 1; } } results[index].pos = {low[0] * dims[1] + low[1], low[0] * dims[1] + high[1], high[0] * dims[1] + low[1], high[0] * dims[1] + high[1]}; float ly = xy[0] - low[0]; float lx = xy[1] - low[1]; float hy = 1.0f - ly; float hx = 1.0f - lx; // save weights and indeces results[index].w = {hy * hx, hy * lx, ly * hx, ly * lx}; }); return results; } struct max_pool { double init() { return std::numeric_limits::lowest(); } double operator()(double x, double y) { return std::max(x, y); } double final(double x, std::size_t) { return (x); } }; struct avg_pool { double init() { return 0.0; } double operator()(double x, double y) { return x + y; } double final(double x, std::size_t y) { return (y == 0) ? 0.0 : (x / y); } }; template std::tuple calc_pooling(const T& data, const std::array& bin_grid_size, const std::vector& pos_weights, int64_t index, Op op) const { double output_val = op.init(); const int64_t count = bin_grid_size[0] * bin_grid_size[1]; dfor(bin_grid_size[0], bin_grid_size[1])([&](auto, auto) { const auto& pc = pos_weights[index]; std::array wv; std::transform( pc.w.begin(), pc.w.end(), pc.pos.begin(), wv.begin(), [&](auto w, auto pos) { return *(data + pos) * w; }); output_val = std::accumulate(wv.begin(), wv.end(), output_val, op); index += 1; }); output_val = op.final(output_val, count); return {output_val, index}; } argument compute(const shape& output_shape, std::vector args) const { argument result{output_shape}; const auto& out_lens = output_shape.lens(); int64_t n_rois = out_lens[0]; std::size_t channels = out_lens[1]; // output dims of height and width, in all 2-dim arrays, the first dim // is for height and second dim is for width std::array out_dims = {out_lens[2], out_lens[3]}; const auto& x_lens = args.at(0).get_shape().lens(); // input dims of height and width std::array in_dims = {x_lens[2], x_lens[3]}; auto roi_s = args.at(1).get_shape(); visit_all(result, args.at(0), args.at(1))([&](auto output, auto x, auto roi) { const auto* batch_indices = args.at(2).cast(); par_for(n_rois, [&](auto n) { const auto bottom_data = x.begin(); const auto roi_batch_ind = batch_indices[n]; // Do not using rounding; this implementation detail is critical std::array roi_starts = { static_cast(roi[roi_s.index({n, 1})] * spatial_scale), static_cast(roi[roi_s.index({n, 0})] * spatial_scale)}; std::array roi_ends = { static_cast(roi[roi_s.index({n, 3})] * spatial_scale), static_cast(roi[roi_s.index({n, 2})] * spatial_scale)}; // Force malformed ROIs to be 1x1 std::array roi_size{}; std::array bin_size{}; std::array bin_grid_size{}; for(auto ii : range(roi_size.size())) { roi_size[ii] = roi_ends[ii] - roi_starts[ii]; roi_size[ii] = std::max(roi_size[ii], 1.0f); bin_size[ii] = roi_size[ii] / out_dims[ii]; bin_grid_size[ii] = (sampling_ratio > 0) ? sampling_ratio : std::ceil(roi_size[ii] / out_dims[ii]); } // we want to precalculate indices and weights shared by all channels, // this is the key point of optimization std::vector comp_lens = { out_dims[0], out_dims[1], bin_grid_size[0], bin_grid_size[1]}; shape comp_s{shape::float_type, comp_lens}; auto pre_calc = this->calc_pos_weight(in_dims, comp_s, roi_starts, bin_size, bin_grid_size); std::vector comp_lens1 = {channels, out_dims[0], out_dims[1]}; shape comp_s1{migraphx::shape::float_type, comp_lens1}; std::vector vec_index(channels, 0); shape_for_each(comp_s1, [&](const auto& idx) { auto c = idx[0]; auto ph = idx[1]; auto pw = idx[2]; const auto offset_bottom_data = bottom_data + static_cast((roi_batch_ind * channels + c) * in_dims[0] * in_dims[1]); double output_val; std::tie(output_val, vec_index[c]) = (mode == migraphx::op::pooling_mode::average) ? this->calc_pooling(offset_bottom_data, bin_grid_size, pre_calc, vec_index[c], avg_pool{}) : this->calc_pooling(offset_bottom_data, bin_grid_size, pre_calc, vec_index[c], max_pool{}); output(n, c, ph, pw) = output_val; }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/rsqrt.hpp000066400000000000000000000031121510465702400230700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_RSQRT_HPP #define MIGRAPHX_GUARD_OPERATORS_RSQRT_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct rsqrt : unary { auto apply() const { return [](auto x) { return 1 / std::sqrt(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/run_on_target.hpp000066400000000000000000000071001510465702400245640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_RUN_ON_TARGET_HPP #define MIGRAPHX_GUARD_RTGLIB_RUN_ON_TARGET_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct run_on_target { std::size_t target_id = 0; std::string name() const { return "run_on_target"; } template static auto reflect(Self& self, F f) { return pack(f(self.target_id, "target_id")); } migraphx::shape compute_shape(const std::vector& inputs, std::vector mods) const { if(mods.size() != 1) { MIGRAPHX_THROW("RUN_ON_TARGET: must have exactly 1 module argument"); } auto* mod_input = mods.front(); if(inputs.size() != mod_input->get_parameter_shapes().size()) { MIGRAPHX_THROW("RUN_ON_TARGET: Mismatched number of input parameters"); } auto mod_out_shapes = mod_input->get_output_shapes(); return shape(mod_out_shapes); } migraphx::argument compute(const migraphx::shape&, const std::vector& args, const std::vector& mods, const std::function( migraphx::module_ref&, const std::unordered_map&)>& run) const { std::unordered_map params; std::set pnames; const auto* smod = mods.front(); assert(mods.size() == 1); auto names = smod->get_parameter_names(); pnames.insert(names.begin(), names.end()); assert(pnames.size() == args.size()); std::transform(pnames.begin(), pnames.end(), args.begin(), std::inserter(params, params.end()), [](auto&& name, auto&& arg) { return std::make_pair(name, arg); }); auto* mod = mods.front(); auto results = run(mod, params); return migraphx::argument{results}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scalar.hpp000066400000000000000000000044241510465702400231710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCALAR_HPP #define MIGRAPHX_GUARD_OPERATORS_SCALAR_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scalar { std::vector scalar_bcast_lens; template static auto reflect(Self& self, F f) { return pack(f(self.scalar_bcast_lens, "scalar_bcst_dims")); } std::string name() const { return "scalar"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1).only_dims(1).nelements(1); auto t = inputs.at(0).type(); std::vector strides(scalar_bcast_lens.size(), 0); return {t, scalar_bcast_lens, strides}; } argument compute(shape output_shape, std::vector args) const { return args[0].reshape(output_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scan_slice.hpp000066400000000000000000000063151510465702400240300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCAN_SLICE_HPP #define MIGRAPHX_GUARD_OPERATORS_SCAN_SLICE_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scan_slice : op_name { int64_t axis = 0; int64_t direction = 0; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis"), f(self.direction, "direction")); } value attributes() const { value normalize_axes = value::object{}; normalize_axes["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize_axes}}; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(2); const auto& input_shape = inputs[0]; auto new_lens = input_shape.lens(); new_lens[axis] = 1; return shape{input_shape.type(), new_lens, input_shape.strides()}; } argument compute(shape output_shape, std::vector args) const { const auto& input = args[0]; auto input_sh = input.get_shape(); int64_t idx; args[1].visit([&](auto i) { idx = i.front(); }); const auto max_idx = input_sh.lens()[axis] - 1; if(idx > max_idx or idx < 0) MIGRAPHX_THROW("ScanSlice: index {" + std::to_string(idx) + "} out of range [0, " + std::to_string(max_idx) + "]"); idx = (1 - direction) * idx + direction * (max_idx - idx); auto offset = idx * input_sh.strides().at(axis) * input_sh.type_size(); return {output_shape, [=] { return input.data() + offset; }}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatter_add.hpp000066400000000000000000000031671510465702400242040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_ADD_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_ADD_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatter_add : public scatter_op { auto reduction() const { return [](auto& x, const auto& y) { x += y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatter_max.hpp000066400000000000000000000032031510465702400242300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_MAX_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_MAX_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatter_max : public scatter_op { auto reduction() const { return [](auto& x, const auto& y) { x = std::max(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatter_min.hpp000066400000000000000000000032031510465702400242260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_MIN_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_MIN_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatter_min : public scatter_op { auto reduction() const { return [](auto& x, const auto& y) { x = std::min(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatter_mul.hpp000066400000000000000000000031671510465702400242510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_MUL_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_MUL_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatter_mul : public scatter_op { auto reduction() const { return [](auto& x, const auto& y) { x *= y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatter_none.hpp000066400000000000000000000031721510465702400244070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_NONE_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_NONE_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatter_none : public scatter_op { auto reduction() const { return [](auto& x, const auto& y) { x = y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatter_op.hpp000066400000000000000000000120661510465702400240700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_OP_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTER_ELEMENTS_OP_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { // The scatter operator fetches a subset of data given by an index array and then performs a // reduction operation (add, multiply, or just set the data) on each element returned. We implement // it as a separate derived struct for each of the three reduction methods. The related operator // scatterND is a generalization that works on a set of 3 tensors of different ranks. The // complementary operations are gather/gatherND. template struct scatter_op : op_name { int64_t axis = 0; // skip scattering indicies that are out of bounds bool skip_out_of_bounds = false; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis"), f(self.skip_out_of_bounds, "skip_out_of_bounds")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(3); // If non-packed, this converts to a packed output while preserving permutation of tensor return inputs.front().with_lens(inputs.front().lens()); } // iterate through items in shape template void scatter_reduce_iterate(TensorView0 indices, TensorView1 output, TensorView1 update) const { auto ind_s = indices.get_shape(); auto output_shape = output.get_shape(); auto axis_dim_size = output_shape.lens()[axis]; shape_for_each(ind_s, [&](const auto& idx) { auto out_idx = idx; // tensor_view::() invokes indexing logic of // std::size_t shape::index(std::size_t i) const auto index = indices(idx.begin(), idx.end()); // this addition doesn't necessarily make index positive if index was out of bounds index = (index < 0) ? index + axis_dim_size : index; assert(skip_out_of_bounds or index >= 0); if(skip_out_of_bounds and index < 0) { return; } out_idx[axis] = index; // skip index out of bounds if attribute on, else assert assert(skip_out_of_bounds or output_shape.multi_within_bounds(out_idx)); if(skip_out_of_bounds) { if(not output_shape.multi_within_bounds(out_idx)) { return; } } // look up the appropriate locations in output, using idx and out_idx. // call reduction() method of derived struct to copy and reduce that element derived().reduction()(output(out_idx.begin(), out_idx.end()), update(idx.begin(), idx.end())); return; }); } argument compute(const shape& output_shape, std::vector args) const { argument result{output_shape}; visit_all(result, args[0], args[2])([&](auto output, auto data, auto update) { std::copy(data.begin(), data.end(), output.begin()); args[1].visit([&](auto indices) { scatter_reduce_iterate(indices, output, update); }); }); return result; } const Derived& derived() const { return static_cast(*this); } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatternd_add.hpp000066400000000000000000000032021510465702400245140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTERND_ADD_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTERND_ADD_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatternd_add : scatternd_op { scatternd_add() {} auto reduction() const { return [](auto& x, const auto& y) { x += y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatternd_max.hpp000066400000000000000000000032161510465702400245560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTERND_MAX_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTERND_MAX_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatternd_max : scatternd_op { scatternd_max() {} auto reduction() const { return [](auto& x, const auto& y) { x = std::max(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatternd_min.hpp000066400000000000000000000032161510465702400245540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTERND_MIN_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTERND_MIN_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatternd_min : scatternd_op { scatternd_min() {} auto reduction() const { return [](auto& x, const auto& y) { x = std::min(x, y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatternd_mul.hpp000066400000000000000000000032021510465702400245610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTERND_MUL_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTERND_MUL_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatternd_mul : scatternd_op { scatternd_mul() {} auto reduction() const { return [](auto& x, const auto& y) { x *= y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatternd_none.hpp000066400000000000000000000032061510465702400247270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTERND_NONE_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTERND_NONE_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct scatternd_none : scatternd_op { scatternd_none() {} auto reduction() const { return [](auto& x, const auto& y) { x = y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/scatternd_op.hpp000066400000000000000000000151701510465702400244110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SCATTERND_OP_HPP #define MIGRAPHX_GUARD_OPERATORS_SCATTERND_OP_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * @brief * N-dimensional Scatter operations. This struct is parent class to ops which differ in what formula * is used to reduce (combine old and new values of) the scattered value. It was originally based * on Onnx ScatterND operation (see * https://github.com/onnx/onnx/blob/main/docs/Operators.md#ScatterND) and is also similar to Numpy * numpy.add.at(). * * @tparam Derived a template parameter in the CRTP inheritance idiom, represents one of the child * operations. */ template struct scatternd_op : op_name { /** Validate input shapes and return the correct output shape. For Scatter ops, the output * is the same shape as the data tensor (first input), but cast to a standard shape. * */ shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(3); auto data_shape = inputs.front(); const auto& index_shape = inputs.at(1); const auto& upd_shape = inputs.back(); auto r = data_shape.ndim(); auto q = index_shape.ndim(); size_t k; if(index_shape.dynamic()) { // the rank of the output is a function of k, so k must be fixed. if(not index_shape.dyn_dims().back().is_fixed()) { MIGRAPHX_THROW( "GATHERND: last dimension of indices tensor must be fixed (min=max)"); } k = index_shape.dyn_dims().back().min; } else k = index_shape.lens().back(); // Checks on the sizes of input tensors if(q + r != upd_shape.ndim() + k + 1) MIGRAPHX_THROW("ScatterND: ranks of inputs don't match. " + std::to_string(q) + " + " + std::to_string(r) + " - " + std::to_string(k) + " - 1 != " + std::to_string(upd_shape.ndim())); if(k > r) MIGRAPHX_THROW("ScatterND: index of size " + std::to_string(k) + " is too large for tensor of rank " + std::to_string(r)); // Convert all static shape dimensions to dynamic so they can be compared. // It's possible for some of the 3 inputs to be dynamic shapes and some static, // but any dynamic dimension that's compared to a static dimension must be fixed. auto ind_dims = index_shape.to_dynamic().dyn_dims(); auto upd_dims = upd_shape.to_dynamic().dyn_dims(); auto data_dims = data_shape.to_dynamic().dyn_dims(); // Check that corresponding portions of tensor shapes match. // Brackets around q - 1 are placed for safeguarding against the breaking iterator out of // vector range. if(not(std::equal(ind_dims.begin(), ind_dims.begin() + (q - 1), upd_dims.begin()) and std::equal(data_dims.begin() + k, data_dims.end(), upd_dims.begin() + (q - 1)))) MIGRAPHX_THROW("ScatterND: incorrect update shape. Update dimensions must match " "indices and data."); if(data_shape.dynamic()) return data_shape; else if(data_shape.broadcasted()) { return {data_shape.type(), data_shape.lens()}; } else { return data_shape.with_lens(data_shape.lens()); } } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; auto& self = static_cast(*this); visit_all(result, args[0], args[2])([&](auto output, auto data, auto updates) { std::copy(data.begin(), data.end(), output.begin()); args[1].visit([&](auto indices) { auto updates_shape = updates.get_shape(); auto updates_std = shape{updates_shape.type(), updates_shape.lens()}; auto indices_shape = indices.get_shape(); auto k = indices_shape.lens().back(); auto q = indices_shape.ndim(); auto r = dyn_out.computed_shape.ndim(); for(auto i = 0u; i < updates_shape.elements(); ++i) { auto updates_idx = updates_std.multi(i); std::vector indices_idx(q, 0); std::copy( updates_idx.begin(), updates_idx.begin() + (q - 1), indices_idx.begin()); auto index_start = indices.begin() + indices_shape.index(indices_idx.begin(), indices_idx.end()); auto index_end = index_start + k; std::vector out_idx(r, 0); std::copy(index_start, index_end, out_idx.begin()); std::copy( updates_idx.begin() + (q - 1), updates_idx.end(), out_idx.begin() + k); self.reduction()(output[dyn_out.computed_shape.index(out_idx)], updates[i]); } }); }); return result; } auto init() const {} scatternd_op() {} }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/select_module.hpp000066400000000000000000000142201510465702400245430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SELECT_MODULE_HPP #define MIGRAPHX_GUARD_OPERATORS_SELECT_MODULE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct select_module { shape output_dyn_shapes; template static auto reflect(Self& self, F f) { return pack(f(self.output_dyn_shapes, "output_dyn_shapes")); } std::string name() const { return "select_module"; } shape compute_shape(const std::vector& inputs, const std::vector&) const { check_shapes{inputs, *this, true}.has_at_least(1); return shape{output_dyn_shapes}; } std::vector get_input_parameter_names(module_ref mod) const { auto param_names = mod->get_parameter_names(); std::vector ret; std::copy_if(param_names.cbegin(), param_names.cend(), std::back_inserter(ret), [](const auto& pn) { return not contains(pn, "#output_"); }); std::sort(ret.begin(), ret.end()); return ret; } std::vector get_output_parameter_names(module_ref mod) const { auto param_names = mod->get_parameter_names(); std::vector ret; std::copy_if(param_names.cbegin(), param_names.cend(), std::back_inserter(ret), [](const auto& pn) { return contains(pn, "#output_"); }); // needs to be sorted to ensure output parameter ordering std::sort(ret.begin(), ret.end()); return ret; } argument compute(const shape&, const std::vector& args, const std::vector& submodule_list, const std::function( module_ref&, const std::unordered_map&)>& run) const { // Find submodule with input parameter shapes exactly the same as the input instruction // arguments. Assuming instruction arguments are in the same order as the instruction // parameters. auto module_iter = std::find_if(submodule_list.cbegin(), submodule_list.cend(), [&](module_ref mr) { auto in_param_names = get_input_parameter_names(mr); auto param_shapes = mr->get_parameter_shapes(); assert(in_param_names.size() <= args.size()); return std::equal(in_param_names.cbegin(), in_param_names.cend(), args.cbegin(), [&](const auto& p_name, const auto& a) { return a.get_shape() == param_shapes[p_name]; }); }); if(module_iter == submodule_list.end()) { MIGRAPHX_THROW("SELECT_MODULE: no compatible submodules found for given input shapes"); } auto* module_to_run = *module_iter; std::unordered_map p_map; // add input parameters to parameter_map auto in_param_names = get_input_parameter_names(module_to_run); assert(in_param_names.size() <= args.size()); std::transform(in_param_names.begin(), in_param_names.end(), args.begin(), std::inserter(p_map, p_map.end()), [&](auto&& name, auto&& a) { return std::make_pair(name, a); }); // One tuple output parameter in main module to multiple output parameters in submodule auto out_param_names = get_output_parameter_names(module_to_run); auto param_shapes = module_to_run->get_parameter_shapes(); auto output_sub_objects = args.back().get_sub_objects(); assert(out_param_names.size() == output_sub_objects.size()); std::transform(out_param_names.begin(), out_param_names.end(), output_sub_objects.begin(), std::inserter(p_map, p_map.end()), [&](auto&& name, auto&& a) { auto ps = param_shapes.at(name); if(a.get_shape() != ps) { assert(ps.bytes() <= a.get_shape().bytes()); return std::make_pair(name, a.reshape(ps)); } else { return std::make_pair(name, a); } }); auto results = run(module_to_run, p_map); return argument{results}; } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/sigmoid.hpp000066400000000000000000000036471510465702400233650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SIGMOID_HPP #define MIGRAPHX_GUARD_OPERATORS_SIGMOID_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct sigmoid : unary { std::string point_op() const { return "1.f / (1.f + ${function:exp}(-${0}))"; } auto apply() const { return [](auto x) { return 1.f / (1.f + std::exp(-x)); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/sign.hpp000066400000000000000000000033131510465702400226600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SIGN_HPP #define MIGRAPHX_GUARD_OPERATORS_SIGN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct sign : unary { std::string point_op() const { return "(${0} > 0 ? 1 : ((${0} < 0) ? -1 : 0))"; } auto apply() const { return [](auto x) { return (x > 0 ? 1 : ((x < 0) ? -1 : 0)); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/sin.hpp000066400000000000000000000031341510465702400225120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SIN_HPP #define MIGRAPHX_GUARD_OPERATORS_SIN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct sin : unary { auto apply() const { return [](auto x) { return std::sin(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/sinh.hpp000066400000000000000000000031411510465702400226600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SINH_HPP #define MIGRAPHX_GUARD_OPERATORS_SINH_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct sinh : unary { auto apply() const { return [](auto x) { return std::sinh(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/slice.hpp000066400000000000000000000515641510465702400230320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SLICE_HPP #define MIGRAPHX_GUARD_OPERATORS_SLICE_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Slice operator that accepts variable axes, starts and ends. * All of `starts`, `ends`, and `axes` must be supplied by either * their attribute or an input (but not both). * * Valid calls: * slice(input); axes, starts, ends set * slice(input, starts); axes, ends set * slice(input, ends); starts, axes set * slice(input, axes); starts, ends set * slice(input, starts, ends); axes set * slice(input, starts, axes); ends set * slice(input, ends, axes); starts set * slice(input, start, ends, axes); none set * * Attributes: * axes: constant axes to slice over (optional) * starts: constant slice starting indices (optional) * ends: constant slice ending indices (optional) * * Parameters: * data: the input tensor to slice (dynamic or static shape) * input_starts: starting indices of slice (optional, static shape) * input_ends: ending indices of slice (optional, static shape) * input_axes: axes to slice over (optional, static shape) */ struct slice { std::vector axes{}; std::vector starts{}; std::vector ends{}; /** * Named arrays for the set attribute possibilities. */ static constexpr std::array all_set = {true, true, true}; static constexpr std::array ends_axes = {false, true, true}; static constexpr std::array starts_axes = {true, false, true}; static constexpr std::array starts_ends = {true, true, false}; static constexpr std::array axes_only = {false, false, true}; static constexpr std::array ends_only = {false, true, false}; static constexpr std::array starts_only = {true, false, false}; static constexpr std::array none_set = {false, false, false}; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes"), f(self.starts, "starts"), f(self.ends, "ends")); } /** * Ensure that attribute axes is within limits. * Will attempt to normalize starts and ends; but will use the dynamic_dimension.max * values for dynamic shapes. This makes it so you have to renormalize for * non-fixed dynamic_dimensions. */ value attributes() const { value normalize_axes = value::object{}; normalize_axes["axes"] = value::array{normalize_attribute::include_min}; normalize_axes["starts"] = value::array{normalize_attribute::clip_max, normalize_attribute::clip_min, normalize_attribute::include_max, normalize_attribute::use_len, normalize_attribute::include_min}; normalize_axes["ends"] = value::array{normalize_attribute::clip_max, normalize_attribute::clip_min, normalize_attribute::include_max, normalize_attribute::use_len, normalize_attribute::include_min}; return {{"normalize_axes", normalize_axes}, {"fillcolor", "#FFA500" /* orange */}}; } std::string name() const { return "slice"; } /** * Computes the slice output shape dimensions for given starts, ends,and axes. * Templated to also handle tensor views. * Possibly different type between [in_starts, in_ends] and [in_axes] if in_axes is this * object's axes attribute. Assumes in_starts and in_ends are normalized; in_axes are valid. */ template std::vector lens_calc(const std::vector& lengths, A in_starts, A in_ends, B in_axes) const { auto new_lens = lengths; for(std::size_t i = 0; i < in_axes.size(); ++i) { auto axis = in_axes[i]; new_lens[axis] = in_ends[i] - in_starts[i]; } return new_lens; } /// Get the attributes that are non-empty std::array get_set_attributes() const { std::array, 3> attrs = {this->starts, this->ends, this->axes}; std::array bool_vec; std::transform(attrs.cbegin(), attrs.cend(), bool_vec.begin(), [](const auto& a) { return not a.empty(); }); return bool_vec; } /// Helper function for normalize_compute_shape() shape compute_two_or_more(std::vector inputs) const { auto input_shape = inputs[0]; auto set_attributes = get_set_attributes(); // check that inputs [1, end) are all 1D, have the same // dimension, and are static check_shapes{inputs.begin() + 1, inputs.end(), std::string("SLICE: inputs (starts, ends, and input_axes)"), false} .only_dims(1) .same_dims(); auto dds = input_shape.to_dynamic().dyn_dims(); if(inputs.size() == 2) { if(set_attributes == ends_axes) { // attr ends and axes set; inputs are (data, input_starts) if(inputs[1].lens().at(0) != axes.size()) { MIGRAPHX_THROW("SLICE: 2 input and attributes mismatch"); } std::for_each(axes.cbegin(), axes.cend(), [&](const auto& axis) { dds.at(axis) = {0, dds.at(axis).max}; }); } else if(set_attributes == starts_axes) { // attr starts and axes set; inputs are (data, input_ends) if(inputs[1].lens().at(0) != axes.size()) { MIGRAPHX_THROW("SLICE: 2 input and attributes mismatch"); } std::for_each(axes.cbegin(), axes.cend(), [&](const auto& axis) { dds.at(axis) = {0, dds.at(axis).max}; }); } else if(set_attributes == starts_ends) { // attr starts and ends set; inputs are (data, input_axes) if(inputs[1].lens().at(0) != starts.size()) { MIGRAPHX_THROW("SLICE: 2 input and attributes mismatch"); } std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { return shape::dynamic_dimension{0, dd.max}; }); } else { MIGRAPHX_THROW("SLICE: Invalid 2 input and attributes configuration"); } } else if(inputs.size() == 3) { if(set_attributes == axes_only) { // attr axes set; inputs are (data, input_starts, input_ends) if(inputs[1].lens().at(0) != axes.size()) { MIGRAPHX_THROW("SLICE: 3 input and attributes mismatch"); } std::for_each(axes.cbegin(), axes.cend(), [&](const auto& axis) { dds.at(axis) = {0, dds.at(axis).max}; }); } else if(set_attributes == ends_only) { // attr ends set; inputs are (data, input_starts, input_axes) if(inputs[1].lens().at(0) != ends.size()) { MIGRAPHX_THROW("SLICE: 3 input and attributes mismatch"); } std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { return shape::dynamic_dimension{0, dd.max}; }); } else if(set_attributes == starts_only) { // attr starts set; inputs are (data, input_ends, input_axes) if(inputs[1].lens().at(0) != starts.size()) { MIGRAPHX_THROW("SLICE: 3 input and attributes mismatch"); } std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { return shape::dynamic_dimension{0, dd.max}; }); } else { MIGRAPHX_THROW("Invalid 3 input and attributes configuration"); } } else { // all 4 inputs (data, inputs_starts, input_ends, input_axes) std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { return shape::dynamic_dimension{0, dd.max}; }); } return shape{input_shape.type(), dds}; } // uses the normalize_axes flag to normalize axes, starts, and ends shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2, 3, 4); if(inputs.size() == 1) { auto input_shape = inputs[0]; auto set_attributes = get_set_attributes(); if(set_attributes != all_set) { MIGRAPHX_THROW("SLICE 1_arg: Invalid 1 input and attributes configuration"); } // NOTE: make sure to update how normalization works here if this type of slicing is // changed to be allowed if(input_shape.dynamic() and std::any_of(axes.begin(), axes.end(), [&](auto axis) { return not input_shape.dyn_dims()[axis].is_fixed(); })) { MIGRAPHX_THROW( "SLICE 1_arg: slicing is not allowed on non-fixed dynamic input axis "); } if(input_shape.dynamic()) { return shape{ input_shape.type(), lens_calc(input_shape.min_lens(), this->starts, this->ends, this->axes), lens_calc(input_shape.max_lens(), this->starts, this->ends, this->axes), {}}; } else { return shape{input_shape.type(), lens_calc(input_shape.lens(), this->starts, this->ends, this->axes), input_shape.strides()}; } } else { return compute_two_or_more(inputs); } } /** * Calculates the starting offset for the sliced tensor. * Used in compute when only data input and all other information are in the attributes. * * \param s static input shape */ auto compute_offset(const shape& s) const { const std::vector& lens = s.lens(); const std::vector& strides = s.strides(); auto offset = 0; if(not axes.empty()) { for(std::size_t i = 0; i < axes.size(); i++) { auto axis = axes[i]; offset += starts[i] * strides[axis]; } } else { for(std::size_t axis = 0; axis < lens.size(); axis++) { offset += starts[axis] * strides[axis]; } } return offset * s.type_size(); } /** * Calculates the starting offset for the sliced tensor (for aliasing). * Used for 2-4 inputs to `slice. * * \param s static input shape * \param input_starts starting indices of slice * \param ax_vec axes to slice on */ template auto compute_offset(const shape& s, const T& input_starts, const T& ax_vec) const { auto ret = 0; for(std::size_t i = 0; i < ax_vec.size(); ++i) { auto axis = ax_vec[i]; ret += input_starts[i] * s.strides().at(axis); } return ret * s.type_size(); } /** * If given, normalize the inputs. Otherwise get from operator attributes. * Return the values in a map. * * Parameters * input_shape: static shape of the input * input_starts: optional * input_ends: optional * input_ends: optional */ std::unordered_map> normalize_starts_ends_axes(shape input_shape, const optional>& input_starts, const optional>& input_ends, const optional>& input_axes) const { auto axes_attrs = this->attributes().at("normalize_axes"); std::vector norm_starts; std::vector norm_ends; std::vector norm_axes; if(input_axes) { norm_axes = normalize_axes(input_axes.value(), input_shape, axes_attrs.at("axes"), "Slice variable input_axes"); } else { norm_axes = this->axes; } if(input_starts) { norm_starts = normalize_indices(input_starts.value(), norm_axes, input_shape, axes_attrs.at("starts"), "Slice variable input_starts"); } else { norm_starts = this->starts; } if(input_ends) { norm_ends = normalize_indices(input_ends.value(), norm_axes, input_shape, axes_attrs.at("ends"), "Slice variable input ends"); } else { norm_ends = this->ends; } return {{"norm_starts", norm_starts}, {"norm_ends", norm_ends}, {"norm_axes", norm_axes}}; } argument compute(const dyn_output& dyn_out, std::vector args) const { auto input = args[0]; auto input_shape = input.get_shape(); if(args.size() == 1) { std::size_t offset = compute_offset(input_shape); return {dyn_out.computed_shape, [=] { return input.data() + offset; }}; } else { // Note that we re-normalize both the attributes and inputs because of the non-fixed // dynamic input shape case. It's possible to only re-normalize if slicing over // non-fixed dynamic_dimensions. auto set_attributes = get_set_attributes(); std::unordered_map> norm_inputs; if(set_attributes == ends_axes) { // attr ends and axes set; inputs are (data, input_starts) args[1].visit([&](auto input_starts) { norm_inputs = normalize_starts_ends_axes(input_shape, input_starts.template to_vector(), this->ends, this->axes); }); } else if(set_attributes == starts_axes) { // attr starts and axes set; inputs are (data, input_ends) args[1].visit([&](auto input_ends) { norm_inputs = normalize_starts_ends_axes(input_shape, this->starts, input_ends.template to_vector(), this->axes); }); } else if(set_attributes == starts_ends) { // attr starts and ends set; inputs are (data, input_axes) args[1].visit([&](auto input_axes) { norm_inputs = normalize_starts_ends_axes(input_shape, this->starts, this->ends, input_axes.template to_vector()); }); } else if(set_attributes == axes_only) { // attr axes set; inputs are (data, input_starts, input_ends) visit_all(args[1], args[2])([&](auto input_starts, auto input_ends) { norm_inputs = normalize_starts_ends_axes(input_shape, input_starts.template to_vector(), input_ends.template to_vector(), this->axes); }); } else if(set_attributes == ends_only) { // attr ends set; inputs are (data, input_starts, input_axes) visit_all(args[1], args[2])([&](auto input_starts, auto input_axes) { norm_inputs = normalize_starts_ends_axes(input_shape, input_starts.template to_vector(), this->ends, input_axes.template to_vector()); }); } else if(set_attributes == starts_only) { // attr starts set; inputs are (data, input_ends, input_axes) visit_all(args[1], args[2])([&](auto input_ends, auto input_axes) { norm_inputs = normalize_starts_ends_axes(input_shape, this->starts, input_ends.template to_vector(), input_axes.template to_vector()); }); } else { // no attr set, all inputs visit_all(args[1], args[2], args[3])( [&](auto input_starts, auto input_ends, auto input_axes) { norm_inputs = normalize_starts_ends_axes(input_shape, input_starts.template to_vector(), input_ends.template to_vector(), input_axes.template to_vector()); }); } auto offset = compute_offset( input_shape, norm_inputs.at("norm_starts"), norm_inputs.at("norm_axes")); shape calc_shape = shape{input_shape.type(), lens_calc(input_shape.lens(), norm_inputs.at("norm_starts"), norm_inputs.at("norm_ends"), norm_inputs.at("norm_axes")), input_shape.strides()}; return {calc_shape, [=] { return input.data() + offset; }}; } } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/softmax.hpp000066400000000000000000000045521510465702400234070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SOFTMAX_HPP #define MIGRAPHX_GUARD_OPERATORS_SOFTMAX_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct softmax { int64_t axis = 1; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "softmax"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); auto s0 = inputs[0]; if(s0.dynamic() or s0.packed()) { return s0; } else { return {s0.type(), s0.lens()}; } } auto output() const { return [=](auto x, auto y) { return x / y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/sqdiff.hpp000066400000000000000000000032251510465702400231760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SQDIFF_HPP #define MIGRAPHX_GUARD_OPERATORS_SQDIFF_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct sqdiff : binary { std::string point_op() const { return "(${0} - ${1}) * (${0} - ${1})"; } auto apply() const { return [](auto x, auto y) { return (x - y) * (x - y); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/sqrt.hpp000066400000000000000000000031201510465702400227050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SQRT_HPP #define MIGRAPHX_GUARD_OPERATORS_SQRT_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct sqrt : unary { auto apply() const { return [](auto x) { return std::sqrt(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/squeeze.hpp000066400000000000000000000124031510465702400234010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SQUEEZE_HPP #define MIGRAPHX_GUARD_OPERATORS_SQUEEZE_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct squeeze { std::vector axes; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes")); } value attributes() const { value normalize; normalize["axes"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "squeeze"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); auto input_shape = inputs[0]; if(input_shape.dynamic()) { // Allow for any dynamic_dimension that intersects with {1, 1}. // Assuming that the shape at run-time will be compatible. if(std::any_of(axes.begin(), axes.end(), [&](auto axis) { return not input_shape.dyn_dims() .at(axis) .intersection(shape::dynamic_dimension{1, 1}) .has_value(); ; })) { MIGRAPHX_THROW( "SQUEEZE: dynamic axis dimension should have an intersection with {1, 1}"); } std::vector dyn_dims = {}; if(axes.empty()) { std::copy_if(input_shape.dyn_dims().cbegin(), input_shape.dyn_dims().cend(), std::back_inserter(dyn_dims), [&](const auto& dd) { return dd != 1; }); } else { for(auto i : range(input_shape.ndim())) { if(std::find(axes.begin(), axes.end(), i) == axes.end()) { dyn_dims.push_back(input_shape.dyn_dims()[i]); } } } return {input_shape.type(), dyn_dims}; } else { auto type = input_shape.type(); auto old_lens = input_shape.lens(); const auto& old_strides = input_shape.strides(); if(std::any_of( axes.begin(), axes.end(), [&](auto axis) { return old_lens[axis] != 1; })) { MIGRAPHX_THROW("SQUEEZE: static axis dimension should be equal to 1"); } std::vector new_lens; std::vector new_strides; if(axes.empty()) { for(auto i : range(old_lens.size())) { if(old_lens[i] != 1) { new_lens.push_back(old_lens[i]); new_strides.push_back(old_strides[i]); } } } else { for(auto i : range(old_lens.size())) { if(std::find(axes.begin(), axes.end(), i) == axes.end()) { new_lens.push_back(old_lens[i]); new_strides.push_back(old_strides[i]); } } } if(new_lens.empty()) { return shape{type}; } else { return shape{type, new_lens, new_strides}; } } } argument compute(const dyn_output& dyn_out, std::vector args) const { return args[0].reshape(dyn_out.computed_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/step.hpp000066400000000000000000000064021510465702400226750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_STEP_HPP #define MIGRAPHX_GUARD_OPERATORS_STEP_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct step { std::vector axes; std::vector steps; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes"), f(self.steps, "steps")); } value attributes() const { value normalize; normalize["axes"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "step"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1); const auto& input = inputs.at(0); auto in_lens = input.lens(); auto t = input.type(); if(axes.size() != steps.size()) { MIGRAPHX_THROW("STEP: attribute axes {" + to_string_range(axes) + "} has different dimensions from step {" + to_string_range(steps) + "}."); } if(std::any_of(axes.begin(), axes.end(), [&](auto axis) { return axis >= in_lens.size(); })) { MIGRAPHX_THROW("STEP: axis value is out of range!"); } auto lens = in_lens; auto strides = input.strides(); for(auto i : range(axes.size())) { auto axis = axes[i]; auto step = steps[i]; lens[axis] = (in_lens[axis] + step - 1) / step; strides[axis] *= step; } return {t, lens, strides}; } argument compute(shape output_shape, std::vector args) const { return args[0].reshape(output_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/sub.hpp000066400000000000000000000032271510465702400225150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_SUB_HPP #define MIGRAPHX_GUARD_OPERATORS_SUB_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct sub : binary { std::string point_function() const { return "-"; } auto apply() const { return [](auto x, auto y) { return x - y; }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/tan.hpp000066400000000000000000000031341510465702400225030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_TAN_HPP #define MIGRAPHX_GUARD_OPERATORS_TAN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct tan : unary { auto apply() const { return [](auto x) { return std::tan(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/tanh.hpp000066400000000000000000000031411510465702400226510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_TANH_HPP #define MIGRAPHX_GUARD_OPERATORS_TANH_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct tanh : unary { auto apply() const { return [](auto x) { return std::tanh(x); }; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/topk.hpp000066400000000000000000000126631510465702400227050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_GATHER_HPP #define MIGRAPHX_GUARD_OPERATORS_GATHER_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct topk { int64_t k = 1; int64_t axis = 0; bool largest = true; template static auto reflect(Self& self, F f) { return pack(f(self.k, "k"), f(self.axis, "axis"), f(self.largest, "largest")); } value attributes() const { value normalize; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "topk"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1, 2); auto lens = inputs.at(0).lens(); auto type = inputs.at(0).type(); lens[axis] = k; shape s_val{type, lens}; shape s_ind{shape::int64_type, lens}; return shape({s_val, s_ind}); } template static auto compare_pair(Compare compare) { return [=](auto p1, auto p2) { auto [x, i] = p1; auto [y, j] = p2; if(not float_equal(x, y)) return compare(x, y); return i < j; }; } argument compute(const shape& output_shape, std::vector args) const { const auto& vec_ss = output_shape.sub_shapes(); argument res_val{vec_ss.front()}; argument res_ind{vec_ss.back()}; auto in_val = args.front(); auto relements = in_val.get_shape().lens()[axis]; auto make_indices = [&](const auto& m_idx) { return [&](int64_t i) { if(args.size() < 2) return i; auto j = m_idx; j[axis] = i; return args[1].at(j); }; }; auto outer_lens = in_val.get_shape().lens(); outer_lens[axis] = 1; shape outer_shape{in_val.get_shape().type(), outer_lens}; visit_all(res_val, args.front())([&](auto output, auto input) { res_ind.visit([&](auto out_ind) { using type = typename decltype(input)::value_type; std::vector> data(relements); par_for(outer_shape.elements(), [&](auto i) { auto outer_idx = outer_shape.multi(i); auto x = input.slice_at({axis}, outer_idx); auto y = output.slice_at({axis}, outer_idx); auto y_ind = out_ind.slice_at({axis}, outer_idx); auto get_index = make_indices(outer_idx); transform(range(relements), data.begin(), [&](auto j) { return std::make_pair(x[j], get_index(j)); }); if(this->largest) std::partial_sort(data.begin(), data.begin() + k, data.end(), compare_pair(std::greater<>{})); else std::partial_sort(data.begin(), data.begin() + k, data.end(), compare_pair(std::less<>{})); std::transform(data.begin(), data.begin() + this->k, y.begin(), [](const auto& p) { return p.first; }); std::transform(data.begin(), data.begin() + this->k, y_ind.begin(), [](const auto& p) { return p.second; }); }); }); }); return {{res_val, res_ind}}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/transpose.hpp000066400000000000000000000067441510465702400237510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_TRANSPOSE_HPP #define MIGRAPHX_GUARD_OPERATORS_TRANSPOSE_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct transpose { std::vector dims; template static auto reflect(Self& self, F f) { return pack(f(self.dims, "permutation")); } std::string name() const { return "transpose"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); auto input = inputs.at(0); if(dims.size() != input.ndim()) { MIGRAPHX_THROW("TRANSPOSE: Permutation has wrong number of axes"); } std::vector axes(dims.size()); std::iota(axes.begin(), axes.end(), 0); if(not std::is_permutation(axes.begin(), axes.end(), dims.begin())) { MIGRAPHX_THROW("TRANSPOSE: Invalid permutation"); } if(input.dynamic()) { std::vector output_dyn_dims(input.ndim()); std::transform(dims.cbegin(), dims.cend(), output_dyn_dims.begin(), [&](auto dim) { return input.dyn_dims()[dim]; }); return {input.type(), output_dyn_dims}; } else { const auto& input_lens = input.lens(); const auto& input_strides = input.strides(); std::vector output_lens(input.ndim()); std::vector output_strides(input.ndim()); for(std::size_t i = 0; i < input.ndim(); i++) { output_lens[i] = input_lens[dims[i]]; output_strides[i] = input_strides[dims[i]]; } return {input.type(), output_lens, output_strides}; } } argument compute(const dyn_output& dyn_out, std::vector args) const { return args[0].reshape(dyn_out.computed_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/unary.hpp000066400000000000000000000065641510465702400230710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_UNARY_HPP #define MIGRAPHX_GUARD_OPERATORS_UNARY_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { template struct unary : op_name { std::string point_function() const { return this->name(); } std::string point_op() const { const auto& self = static_cast(*this); auto pf = self.point_function(); if(pf.empty()) return {}; if(with_char(::ispunct)(pf.front())) { return pf + "${0}"; } else { return "${function:" + pf + "}(${0})"; } } value base_attributes() const { const auto& self = static_cast(*this); return {{"pointwise", true}, {"point_op", self.point_op()}, {"fillcolor", "#CD5C5C" /* indianred */}}; } value attributes() const { return base_attributes(); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, static_cast(*this), true}.has(1); auto s = inputs.at(0); if(s.dynamic() or s.scalar()) { return s; } else if(s.broadcasted()) { return {s.type(), s.lens()}; } else { return s.with_lens(s.lens()); } } argument compute(const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; result.visit([&](auto output) { args[0].visit([&](auto input) { par_transform(input.begin(), input.end(), output.begin(), static_cast(*this).apply()); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/unary_not.hpp000066400000000000000000000033251510465702400237410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_UNARY_NOT_HPP #define MIGRAPHX_GUARD_OPERATORS_UNARY_NOT_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct unary_not : unary { std::string point_function() const { return "!"; } auto apply() const { return [](auto x) { return not x; }; } std::string name() const { return "not"; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/undefined.hpp000066400000000000000000000034731510465702400236700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_UNDEFINED_HPP #define MIGRAPHX_GUARD_RTGLIB_UNDEFINED_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct undefined { std::string name() const { return "undefined"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(0); return {}; } argument compute(const shape&, const std::vector&) const { return {{}, nullptr}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/unique.hpp000066400000000000000000000305001510465702400232240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_UNIQUE_HPP #define MIGRAPHX_GUARD_OPERATORS_UNIQUE_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { // https://onnx.ai/onnx/operators/onnx__Unique.html // The Onnx spec refers to numpy specification, used as a reference: // https://numpy.org/doc/stable/reference/generated/numpy.unique.html // Input : Given an array of elements : X. // Output(s) : // 1. Find the unique elements (Y) of input (X). // // There are three outputs in addition to the unique elements in Y: // 2. the indices of the input array that give the unique values // 3. the indices of the unique array that reconstruct the input array // 4. the number of times each unique value comes up in the input array // Optional Attribute: 'Sorted' = 1 for sorted; = 0 for unsorted. // Onnx specification makes 'sorted' a default, while Numpy always sorts. // // Optional Attribute: 'Axis' is 'None' (default) or a valid int < rank(X). // Negative values are allowed. // // Numpy has the following important note on Axis: // ------------------------------------------------------------------ // When an axis is specified the subarrays indexed by the axis are // sorted. This is done by making the specified axis the first // dimension of the array (move the axis to the first dimension to // keep the order of the other axes) and then flattening the subarrays // in C order. The flattened subarrays are then viewed as a structured // type with each element given a label, with the effect that we end // up with a 1-D array of structured types that can be treated in the // same way as any other 1-D array. The result is that the flattened // subarrays are sorted in lexicographic order starting with the first // element. // ------------------------------------------------------------------ struct unique { template auto make_idx_less_fn(const T& data, size_t chunk_sz) const { return [&data, chunk_sz](auto idx1, auto idx2) { return std::lexicographical_compare(data.begin() + idx1, data.begin() + idx1 + chunk_sz, data.begin() + idx2, data.begin() + idx2 + chunk_sz); }; } // CASE SORTED: // // To process into a sorted unique series of elements/chunks: // Chunk size == 1 means a simple element; >1 means a flat representation. // Steps: first go through the input elements/chunks for uniqueness. // At the end of this processing, per the sorted sequence of unique elements: // update/create data structures: y, y_indices, x_rev_indices, y_count // // INPUT x: [2, 1, 1, 3, 4, 3], attr_sorted = 1; // OUTPUT(s): indices.. // y_indices: [1, 0, 3, 4] --- first incidence, in terms of index in sequence x // x_rev_indices: [1, 0, 0, 2, 3, 2] --- x seen in terms of indices of unique sequence y // y_count: [2, 1, 2, 1] -- count at each y_index. sum = len(x) // NOTE: y [1, 2, 3, 4] --- the unique output is constructed from x[y_indices[...]] template auto sorted_uniq_indices(const T& input_data, size_t chunk_sz) const { struct y_info { size_t y_idx; size_t x_idx; size_t ct = 0; }; auto idx_less_fn = make_idx_less_fn(input_data, chunk_sz); std::map uniq_val_map(idx_less_fn); std::tuple, std::vector, std::vector> rv; auto& [y_indices, x_rev_indices, y_count] = rv; // go through all the elements and find the unique elements.. size_t count_x = input_data.size(); for(size_t f_idx = 0, x_idx = 0; f_idx < count_x; f_idx += chunk_sz, x_idx++) { y_info entry = {.y_idx = uniq_val_map.size(), .x_idx = x_idx}; auto [itr, added_new] = uniq_val_map.insert({f_idx, entry}); itr->second.ct++; x_rev_indices.push_back(itr->second.y_idx); } std::vector y2x_indices(uniq_val_map.size()); y_indices.resize(uniq_val_map.size()); y_count.resize(uniq_val_map.size()); size_t idx = 0; // the unique elements are now sorted: // post-processing for all the return indices. for(const auto& v : uniq_val_map) { y2x_indices[v.second.y_idx] = idx; y_indices[idx] = v.second.x_idx; y_count[idx] = v.second.ct; idx++; } // update x_rev_indices as per the sorted order of y_indices for(auto& i : x_rev_indices) i = y2x_indices[i]; return rv; } // CASE UNSORTED: // // To process into an un-sorted unique series of elements/chunks: // For chunk size = 1 is a simple element, else use a flat representation of a tensor obj // Go through the input elements/chunks one by one with inline processing of indices.. // INPUT x: [2, 1, 1, 3, 4, 3], attr_sorted = 0; // OUTPUT(s): indices.. // y_indices: [0, 1, 3, 4] --- first incidence, in terms of index in sequence x // x_rev_indices: [0, 1, 1, 2, 3, 2] --- x seen in terms of indices of unique sequence y // y_count: [1, 2, 2, 1] -- count at each y_index. sum = len(x) // NOTE: y [2, 1, 3, 4] --- the unique output is constructed from x[y_indices[...]] // Output data structures: y_indices, x_rev_indices, y_count are processed inline. template auto unsorted_uniq_indices(const T& input_data, size_t chunk_sz) const { auto idx_less_fn = make_idx_less_fn(input_data, chunk_sz); std::map uniq_val_map(idx_less_fn); // rv is used for NVRO below.. std::tuple, std::vector, std::vector> rv; auto& [y_indices, x_rev_indices, y_count] = rv; // go through all the elements and add the unique elements into the map.. // inline processing for outputs: y_indices, x_rev_indices, y_count size_t count_x = input_data.size(); for(size_t f_idx = 0; f_idx < count_x; f_idx += chunk_sz) { auto [itr, added_new] = uniq_val_map.insert({f_idx, y_indices.size()}); if(added_new) { y_count.push_back(0); y_indices.push_back(x_rev_indices.size()); } y_count[itr->second]++; x_rev_indices.push_back(itr->second); } return rv; } // Axis. Default: none. Range: [-rank, rank-1] std::optional axis; // Sorted, Default: 1= sorted. 0 = unsorted. bool sorted = true; template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis"), f(self.sorted, "sorted")); } std::string name() const { return "unique"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1); auto& sh_x = inputs[0]; auto lens_x = sh_x.lens(); size_t dim_x = sh_x.ndim(); size_t max_uniq_ct = sh_x.elements(); std::vector d_out; if(axis) { int64_t t_axis = migraphx::tune_axis(dim_x, *axis, name()); if(t_axis != 0) MIGRAPHX_THROW("Unique: Only supports axis = 0 or None"); d_out = sh_x.to_dynamic().dyn_dims(); // only axis = 0 is supported: max_uniq_ct = lens_x[0]; // min = 1 unique element; max = full dimension along axis 0 d_out[0] = {1, max_uniq_ct}; } else { d_out.push_back({1, max_uniq_ct}); } shape sh_y = {sh_x.type(), d_out}; // The three outputted Indices are just 1-D: shape sh_idx{shape::int64_type, {d_out[0]}}; return shape({sh_y, sh_idx, sh_idx, sh_idx}); } argument compute(const dyn_output& dyn_out, std::vector args) const { auto sh_x = args.front().get_shape(); auto lens_x = sh_x.lens(); shape output_shape = dyn_out.computed_shape; const auto& vec_ss = output_shape.sub_shapes(); auto ct_x = sh_x.elements(); shape sh_y = {vec_ss[0].type(), {ct_x}}; shape sh_idx = {vec_ss[1].type(), {ct_x}}; shape sh_x_idx = {vec_ss[1].type(), {ct_x}}; argument res_y{sh_y}; argument res_y_idx{sh_idx}; argument res_x_rev_idx{sh_idx}; argument res_y_ct_idx{sh_idx}; std::vector out_y_idx; std::vector out_x_rev_idx; std::vector out_y_ct; // If axis is not none, for >1D tensors, we have to consider // then, the uniqueness of chunks of sub-tensors: a subsequence of built-ins.. // For a built-in type, chunk_sz is of course = 1 size_t chunk_sz = 1; if(axis) chunk_sz = ct_x / lens_x[0]; // axis = 0 is supported. visit_all(args.front(), res_y)([&](auto x, auto y_flat) { using o_type = typename decltype(x)::value_type; std::vector x_in(x.begin(), x.end()); std::tie(out_y_idx, out_x_rev_idx, out_y_ct) = sorted ? sorted_uniq_indices(x_in, chunk_sz) : unsorted_uniq_indices(x_in, chunk_sz); const auto uniq_ct = out_y_idx.size(); // construct y from x[indices] in flattened form // later we reshape y to the final shape.. auto y_dst = y_flat.begin(); for(size_t idx = 0; idx < uniq_ct; idx++) y_dst = copy_n(x_in.begin() + out_y_idx[idx] * chunk_sz, chunk_sz, y_dst); std::vector lens_y; // if axis is specified: // the output shape keeps the n-1 dimensions of x if(axis) { lens_y = lens_x; lens_y[0] = uniq_ct; } else { lens_y = {uniq_ct}; } sh_y = {sh_y.type(), lens_y}; sh_idx = {sh_idx.type(), {uniq_ct}}; }); visit_all(res_y_idx, res_x_rev_idx, res_y_ct_idx)( [&](auto y_indices, auto x_rev_indices, auto y_count) { std::copy(out_y_idx.begin(), out_y_idx.end(), y_indices.begin()); std::copy(out_x_rev_idx.begin(), out_x_rev_idx.end(), x_rev_indices.begin()); std::copy(out_y_ct.begin(), out_y_ct.end(), y_count.begin()); sh_x_idx = {sh_idx.type(), {out_x_rev_idx.size()}}; }); return {{res_y.reshape(sh_y), res_y_idx.reshape(sh_idx), res_x_rev_idx.reshape(sh_x_idx), res_y_ct_idx.reshape(sh_idx)}}; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/unknown.hpp000066400000000000000000000037421510465702400234250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_UNKNOWN_HPP #define MIGRAPHX_GUARD_RTGLIB_UNKNOWN_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct unknown { std::string op; template static auto reflect(Self& self, F f) { return pack(f(self.op, "op")); } std::string name() const { return "unknown:" + op; } shape compute_shape(std::vector input) const { if(input.empty()) return {}; else return input.front(); } friend std::ostream& operator<<(std::ostream& os, const unknown& x) { os << x.name(); return os; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/unpack_fp4.hpp000066400000000000000000000067641510465702400237670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_UNPACK_FP4_HPP #define MIGRAPHX_GUARD_OPERATORS_UNPACK_FP4_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct unpack_fp4 { int64_t axis = -1; std::string name() const { return "unpack_fp4"; } value attributes() const { value normalize = value::object{}; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } migraphx::shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.same_dims().has(1); const auto& in_shape = inputs.front(); if(in_shape.type() != migraphx::shape::fp4x2_type) { MIGRAPHX_THROW("UNPACK_FP4: Only fp4x2_type is supported for unpacking"); } auto new_lens = in_shape.lens(); new_lens[axis] *= 2; return {migraphx::shape::fp8e4m3fn_type, new_lens}; } argument compute(const shape& output_shape, const std::vector& args) const { const auto& input = args.front(); auto in_shape = input.get_shape(); migraphx::shape fp8_shape = shape{migraphx::shape::fp8e4m3fn_type, output_shape.lens()}; argument fp8_arg{fp8_shape}; auto inp = input.get(); fp8_arg.visit([&](auto out) { par_for(in_shape.elements(), [&](auto i) { auto data_idx = in_shape.multi(i); data_idx[axis] *= 2; // unpacking 2 unsigned parts // unpacking 4 least significant bits first uint8_t fp4_val = inp[i]; out[data_idx] = fp4_to_fp8(fp4_val); data_idx[axis] += 1; fp4_val = fp4_val >> 4u; out[data_idx] = fp4_to_fp8(fp4_val); }); }); return fp8_arg; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/unpack_int4.hpp000066400000000000000000000110051510465702400241340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_UNPACK_INT4_HPP #define MIGRAPHX_GUARD_OPERATORS_UNPACK_INT4_HPP #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct unpack_int4 { int64_t axis = -1; std::string name() const { return "unpack_int4"; } value attributes() const { value normalize = value::object{}; normalize["axis"] = value::array{normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } template static auto reflect(Self& self, F f) { return pack(f(self.axis, "axis")); } migraphx::shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.same_dims().has(1); const auto& in_shape = inputs.front(); if(in_shape.type() != migraphx::shape::int8_type and in_shape.type() != migraphx::shape::uint8_type) { MIGRAPHX_THROW("UNPACK_INT4: Only Int8 or Uint8 is supported for unpacking"); } auto new_lens = in_shape.lens(); new_lens[axis] *= 2; return {in_shape.type(), new_lens}; } argument compute(const shape& output_shape, std::vector args) const { auto input = args.front(); auto in_shape = input.get_shape(); argument result{output_shape}; visit_all(result, input)([&](auto out, auto inp) { par_for(in_shape.elements(), [&](auto i) { using type = typename decltype(out)::value_type; auto data_idx = in_shape.multi(i); data_idx[axis] *= 2; if constexpr(std::is_signed{}) { // signed input: [Most significant nibble | Least significant nibble] int8_t val1 = inp[i]; int8_t val2 = val1; // Step1: move the LSN to MSN: // However avoid doing a left shift of signed quantity // due to its possible run time error. uint8_t u_tmp = static_cast(val1); u_tmp <<= 4; // NOLINT(hicpp-signed-bitwise) val1 = static_cast(u_tmp); // Step2: the sign bit is copied in a right signed-shift: val1 >>= 4; // NOLINT(hicpp-signed-bitwise) out[data_idx] = val1; data_idx[axis] += 1; val2 >>= 4; // NOLINT(hicpp-signed-bitwise) out[data_idx] = val2; } else { // unpacking of 2 unsigned nibbles: uint8_t val = inp[i]; out[data_idx] = val & 0xf; // NOLINT(hicpp-signed-bitwise) data_idx[axis] += 1; out[data_idx] = val >> 4; // NOLINT(hicpp-signed-bitwise) } }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/unsqueeze.hpp000066400000000000000000000136631510465702400237550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_UNSQUEEZE_HPP #define MIGRAPHX_GUARD_OPERATORS_UNSQUEEZE_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** * Adds dimensions to a tensor based on the axes attribute. * `axes` are based on the number of output shape dimensions and should not contain duplicates. * `steps` are for modifying dimensions added to the middle of the original shape. * Each step must be a factor of the original dimension. * ex: unsqueeze(shape = [3, 4, 10], axes = [2, 4, 5], steps = [2]) -> shape = [3, 4, 2, 5, 1, 1] * Dynamic shape version does not handle `steps`. */ struct unsqueeze { std::vector axes; std::vector steps; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes"), f(self.steps, "steps")); } value attributes() const { value normalize; normalize["axes"] = value::array{normalize_attribute::include_min, normalize_attribute::use_output}; return {{"normalize_axes", normalize}}; } std::string name() const { return "unsqueeze"; } shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& input_shape = inputs[0]; if(input_shape.dynamic()) { if(not steps.empty()) { MIGRAPHX_THROW("UNSQUEEZE_dyn: nonempty steps attribute"); } std::vector dyn_dims = {}; auto new_ndim = input_shape.ndim() + axes.size(); std::size_t k = 0; for(auto i : range(new_ndim)) { if(std::find(axes.begin(), axes.end(), i) != axes.end()) { dyn_dims.push_back({1, 1}); } else { dyn_dims.push_back(input_shape.dyn_dims().at(k++)); } } return {input_shape.type(), dyn_dims}; } else { auto type = input_shape.type(); auto old_lens = input_shape.lens(); const auto& old_strides = input_shape.strides(); auto is_scalar = input_shape.scalar(); if(is_scalar and old_lens.size() == 1 and old_lens.front() == 1) return shape{type, old_lens}; if(steps.size() > axes.size()) MIGRAPHX_THROW("UNSQUEEZE: Steps provided with no axis"); std::size_t new_size = old_lens.size() + axes.size(); std::vector new_lens(new_size); std::vector new_strides(new_size); std::size_t p = 0; for(auto i : range(new_size)) { auto axis_idx = std::find(axes.begin(), axes.end(), i) - axes.begin(); if(axis_idx < axes.size()) { std::int64_t step = 1; if(axis_idx < steps.size()) step = steps[axis_idx]; if(step == 0) MIGRAPHX_THROW("UNSQUEEZE: step must be non-zero"); if(is_scalar and step != 1) MIGRAPHX_THROW("UNSQUEEZE: step must be 1 when input is scalar"); new_lens[i] = step; if(p < old_strides.size()) { if((old_lens[p] % step) != 0) MIGRAPHX_THROW("UNSQUEEZE: Axis dimension is not divisible by step"); old_lens[p] /= step; new_strides[i] = is_scalar ? 1 : old_strides[p] * old_lens[p]; } else { if(step != 1) MIGRAPHX_THROW("UNSQUEEZE: Step must be 1 for extra axes"); new_strides[i] = 1; } } else { new_lens[i] = old_lens[p]; new_strides[i] = old_strides[p++]; } } return shape{type, new_lens, new_strides}; } } argument compute(const dyn_output& dyn_out, std::vector args) const { return args[0].reshape(dyn_out.computed_shape); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/op/where.hpp000066400000000000000000000067651510465702400230500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_WHERE_HPP #define MIGRAPHX_GUARD_OPERATORS_WHERE_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { struct where { std::string name() const { return "where"; } value attributes() const { return {{"pointwise", true}, {"point_op", "${0} ? ${1} : ${2}"}}; } shape compute_shape(std::vector inputs) const { check_shapes shape_checker{inputs, *this, true}; shape_checker.has(3); if(auto s = inputs[0]; not s.dynamic() and s.elements() == 1) check_shapes{std::next(inputs.begin()), inputs.end(), *this, true}.same_dims(); else shape_checker.same_dims(); auto s1 = inputs.at(1); auto s2 = inputs.at(2); if(s1.dynamic() or s2.dynamic()) { if(s1 == s2) return s1; MIGRAPHX_THROW("WHERE: dynamic input shapes must be the same"); } // Compare two static shapes, returning a standard shape if(s1 == s2 and s1.packed()) { return s1; } else if(s1.packed() != s2.packed()) { return s1.packed() ? s1 : s2; } else if(s1.broadcasted() != s2.broadcasted()) { return s1.broadcasted() ? s2.with_lens(s1.lens()) : s1.with_lens(s1.lens()); } else { return {s1.type(), s1.lens()}; } } argument compute(shape output_shape, std::vector args) const { if(auto s = args[0].get_shape(); not s.dynamic() and s.elements() == 1) return args[args[0].at() ? 1 : 2].copy(); if(output_shape.dynamic()) output_shape = compute_shape(to_shapes(args)); argument result{output_shape}; visit_all(result, args[1], args[2])([&](auto output, const auto x, const auto y) { args[0].visit([&](const auto condition) { par_for(output_shape.elements(), [&](auto i) { output[i] = condition[i] ? x[i] : y[i]; }); }); }); return result; } }; } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/operation.hpp000066400000000000000000001570101510465702400233060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_OPERAND_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_OPERAND_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct context; #ifdef DOXYGEN /// The operation interface represents an action an instruction will perform. All /// operation classes must be CopyConstructible. struct operation { /// A unique name identifying the operation std::string name() const; /// An optional method that can be used to finalize the operator before running void finalize(context& ctx); /// This is used to compute the resulting shape from an operation. If an /// operation cannot be run with input shapes, then it should throw an /// exception. shape compute_shape(const std::vector& input) const; /** * @brief This performs the operation's computation. * * This method can be optional when the operation is only used as a placeholder to be lowered * later on. * * @param ctx This is the context created by the `target` during compilation. Implementations * can use the target's `context` class rather than the `context` interface class. * @param output Equivalent to running `compute_shape` with each `shape` of the `argument`. * For a fixed shape, the returned argument will have the same shape as `output`. * For a dynamic shape, the returned `argument` will be a fixed shape within the bounds * set in the dynamic shape `output`. * @param input This is the `argument` result from the previous instruction's computation. * @return Return an `argument` of the result computation. The `shape` of `argument` should be * the same the `output` shape. */ argument compute(context& ctx, const shape& output, const std::vector& input) const; /// An optional method to return which argument the output will alias. If /// there is no aliased output then -1 can be returned. std::ptrdiff_t output_alias(const std::vector& input) const; /// An optional stream operator to print the operation. When this is not /// implemented, it will just print the operation's name. friend std::ostream& operator<<(std::ostream& os, const operation& op); }; /// Returns true if operation does not require a context to run compute bool is_context_free(const operation& x); /// Returns true if operation needs normalization before running compute bool need_normalization(const operation& x); /// Returns true if the operation has a finalize method bool has_finalize(const operation& x); #else namespace detail { namespace operation_operators { template auto operator<<(std::ostream& os, const T& x) -> decltype(os << x.name()) { os << x.name(); char delim = '['; reflect_each(x, [&](auto&& y, auto name) { os << delim; os << name << "="; stream_write_value(os, y); delim = ','; }); if(delim == ',') os << "]"; return os; } template auto operator==(const T& x, const U& y) -> decltype(x.name() == y.name()) { static_assert(is_reflectable{} or sizeof(T) <= 1, "Missing equality operator or reflect method."); if(x.name() != y.name()) return false; const auto& yy = any_cast(y); return reflect_tie(x) == reflect_tie(yy); } } // namespace operation_operators template auto compute_shape_op(rank<3>, const T& x, const std::vector& inputs) -> decltype(x.compute_shape(inputs)) { return x.compute_shape(inputs); } template auto compute_shape_op(rank<2>, const T& x, const std::vector& inputs) -> decltype(x.normalize_compute_shape(inputs)) { if(inputs.empty()) MIGRAPHX_THROW("At least one input is required for " + x.name()); dependent_type y = x; normalize_attributes(y, inputs[0]); return any_cast(y).normalize_compute_shape(inputs); } template auto compute_shape_op(rank<1>, const T& x, const std::vector& inputs) -> decltype(x.compute_shape(inputs, {})) { return x.compute_shape(inputs, {}); } template shape compute_shape_op(rank<0>, const T& x, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Shape not computable: " + name); } template shape compute_shape_op(const T& x, const std::vector& inputs) { return compute_shape_op(rank<3>{}, x, inputs); } template auto mod_compute_shape_op(rank<1>, const T& x, const std::vector& inputs, const std::vector& mod_args) -> decltype(x.compute_shape(inputs, mod_args)) { return x.compute_shape(inputs, mod_args); } template shape mod_compute_shape_op(rank<0>, const T& x, const std::vector& inputs, const std::vector& mod_args) { if(mod_args.empty()) return compute_shape_op(x, inputs); std::string name = x.name(); MIGRAPHX_THROW("Shape not computable: " + name); } template shape mod_compute_shape_op(const T& x, const std::vector& inputs, const std::vector& mod_args) { return mod_compute_shape_op(rank<1>{}, x, inputs, mod_args); } template auto compute_op(rank<1>, const T& x, context& ctx, const shape& output_shape, const std::vector& input) -> decltype(x.compute(auto_any_cast(ctx), make_compute_output_shape(pack(x, output_shape, input)), input)) { return x.compute( auto_any_cast(ctx), make_compute_output_shape(pack(x, output_shape, input)), input); } template argument compute_op(rank<0>, const T& x, context&, const shape&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument compute_op(const T& x, context& ctx, const shape& output_shape, const std::vector& input) { return compute_op(rank<1>{}, x, ctx, output_shape, input); } template auto compute_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input)) { return x.compute(make_compute_output_shape(pack(x, output_shape, input)), input); } template argument compute_op(rank<0>, const T& x, const shape&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument compute_op(const T& x, const shape& output_shape, const std::vector& input) { return compute_op(rank<1>{}, x, output_shape, input); } template auto compute_op(rank<1>, const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { return x.compute( make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template argument compute_op(rank<0>, const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, F) // NOLINT { if(module_args.empty()) return compute_op(x, output, inputs); std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument compute_op(const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) { return compute_op(rank<1>{}, x, output, inputs, module_args, std::move(f)); } template auto compute_op(rank<4>, const T& x, context& ctx, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { return x.compute(auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template auto compute_op(rank<3>, const T& x, context&, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute( make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { return x.compute( make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template auto compute_op(rank<2>, const T& x, context&, const shape& output, const std::vector& inputs, const std::vector&, F) // NOLINT -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs)) { return x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs); } template auto compute_op(rank<1>, const T& x, context& ctx, const shape& output, const std::vector& inputs, const std::vector&, F) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs)) { return x.compute( auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs); } template argument compute_op(rank<0>, const T& x, context&, const shape&, const std::vector&, const std::vector&, F) // NOLINT { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument compute_op(const T& x, context& ctx, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) { return compute_op(rank<4>{}, x, ctx, output, inputs, module_args, std::move(f)); } template auto is_context_free_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input), std::true_type{}); template auto is_context_free_op(rank<0>, const T&, const shape&, const std::vector&) -> std::false_type; template auto is_context_free_op(const T& x) -> decltype(is_context_free_op( rank<1>{}, x, std::declval(), std::declval>())) { return {}; } template auto need_normalization_op(rank<1>, const T& x, const std::vector& inputs) -> decltype(x.normalize_compute_shape(inputs), std::true_type{}); template auto need_normalization_op(rank<0>, const T&, const std::vector&) -> std::false_type; template auto need_normalization_op(const T& x) -> decltype(need_normalization_op(rank<1>{}, x, std::declval>())) { return {}; } template std::ptrdiff_t output_alias_op(const T&, const std::vector&) { return -1; } template auto finalize_op( rank<1>, T& x, context& ctx, const shape& output_shape, const std::vector& input) -> decltype(x.finalize(auto_any_cast(ctx), output_shape, input), void()) { x.finalize(auto_any_cast(ctx), output_shape, input); } template void finalize_op(rank<0>, T&, context&, const shape&, const std::vector&) { } template void finalize_op(T& x, context& ctx, const shape& output_shape, const std::vector& input) { finalize_op(rank<1>{}, x, ctx, output_shape, input); } template auto has_finalize_op( rank<1>, T& x, context& ctx, const shape& output_shape, const std::vector& input) -> decltype(x.finalize(auto_any_cast(ctx), output_shape, input), std::true_type{}); template auto has_finalize_op(rank<0>, T&, context&, const shape&, const std::vector&) -> std::false_type; template auto has_finalize_op(const T&) -> decltype(has_finalize_op(rank<1>{}, std::declval(), std::declval(), std::declval(), std::declval>())) { return {}; } template auto compile_op( rank<1>, T& x, context& ctx, const shape& output_shape, const std::vector& input) -> decltype(x.compile(auto_any_cast(ctx), output_shape, input)) { return x.compile(auto_any_cast(ctx), output_shape, input); } template value compile_op(rank<0>, T&, context&, const shape&, const std::vector&) { return value::object{}; } template value compile_op(const T& x, context& ctx, const shape& output_shape, const std::vector& input) { return compile_op(rank<1>{}, x, ctx, output_shape, input); } template value attributes_op(const T&) { return value::object{}; } template value to_value_op(const T& x) { return migraphx::to_value(x); } template void from_value_op(T& x, const value& v) { if(not(v.is_object() or (v.empty() and v.is_array()))) MIGRAPHX_THROW("Value is not an object"); return migraphx::from_value(v, x); } template lifetime get_lifetime_op(const T&) { return lifetime::local; } } // namespace detail #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT operation { // std::string name() const; // (optional) bool is_context_free() const; // (optional) bool need_normalization() const; // (optional) bool has_finalize() const; // (optional) lifetime get_lifetime() const; // (optional) std::ptrdiff_t output_alias(const std::vector& input) const; // (optional) value compile(context& ctx, const shape& output, const std::vector& input); // (optional) void finalize(context& ctx, const shape& output, const std::vector& input); // (optional) shape compute_shape(const std::vector& input) const; // (optional) shape compute_shape(const std::vector& inputs, const std::vector& mod_args) const; // (optional) argument compute(context& ctx, const shape& output, const std::vector& input) const; // (optional) argument compute(const shape& output, const std::vector& input) const; // (optional) argument compute(const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const; // (optional) argument compute(context& ctx, const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const; // (optional) value to_value() const; // (optional) void from_value(const value& v); // (optional) value attributes() const; // friend std::ostream& operator<<(std::ostream& os, const operation& op); // friend bool operator==(const operation& x, const operation& y); }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct operation { private: template static auto private_detail_te_default_is_context_free(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.is_context_free()) { return private_detail_te_self.is_context_free(); } template static bool private_detail_te_default_is_context_free(float, T&& private_detail_te_self) { return detail::is_context_free_op(private_detail_te_self); } template static auto private_detail_te_default_need_normalization(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.need_normalization()) { return private_detail_te_self.need_normalization(); } template static bool private_detail_te_default_need_normalization(float, T&& private_detail_te_self) { return detail::need_normalization_op(private_detail_te_self); } template static auto private_detail_te_default_has_finalize(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.has_finalize()) { return private_detail_te_self.has_finalize(); } template static bool private_detail_te_default_has_finalize(float, T&& private_detail_te_self) { return detail::has_finalize_op(private_detail_te_self); } template static auto private_detail_te_default_get_lifetime(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.get_lifetime()) { return private_detail_te_self.get_lifetime(); } template static lifetime private_detail_te_default_get_lifetime(float, T&& private_detail_te_self) { return detail::get_lifetime_op(private_detail_te_self); } template static auto private_detail_te_default_output_alias(char, T&& private_detail_te_self, const std::vector& input) -> decltype(private_detail_te_self.output_alias(input)) { return private_detail_te_self.output_alias(input); } template static std::ptrdiff_t private_detail_te_default_output_alias(float, T&& private_detail_te_self, const std::vector& input) { return detail::output_alias_op(private_detail_te_self, input); } template static auto private_detail_te_default_compile(char, T&& private_detail_te_self, context& ctx, const shape& output, const std::vector& input) -> decltype(private_detail_te_self.compile(ctx, output, input)) { return private_detail_te_self.compile(ctx, output, input); } template static value private_detail_te_default_compile(float, T&& private_detail_te_self, context& ctx, const shape& output, const std::vector& input) { return detail::compile_op(private_detail_te_self, ctx, output, input); } template static auto private_detail_te_default_finalize(char, T&& private_detail_te_self, context& ctx, const shape& output, const std::vector& input) -> decltype(private_detail_te_self.finalize(ctx, output, input)) { private_detail_te_self.finalize(ctx, output, input); } template static void private_detail_te_default_finalize(float, T&& private_detail_te_self, context& ctx, const shape& output, const std::vector& input) { detail::finalize_op(private_detail_te_self, ctx, output, input); } template static auto private_detail_te_default_compute_shape(char, T&& private_detail_te_self, const std::vector& input) -> decltype(private_detail_te_self.compute_shape(input)) { return private_detail_te_self.compute_shape(input); } template static shape private_detail_te_default_compute_shape(float, T&& private_detail_te_self, const std::vector& input) { return detail::compute_shape_op(private_detail_te_self, input); } template static auto private_detail_te_default_compute_shape(char, T&& private_detail_te_self, const std::vector& inputs, const std::vector& mod_args) -> decltype(private_detail_te_self.compute_shape(inputs, mod_args)) { return private_detail_te_self.compute_shape(inputs, mod_args); } template static shape private_detail_te_default_compute_shape(float, T&& private_detail_te_self, const std::vector& inputs, const std::vector& mod_args) { return detail::mod_compute_shape_op(private_detail_te_self, inputs, mod_args); } template static auto private_detail_te_default_compute(char, T&& private_detail_te_self, context& ctx, const shape& output, const std::vector& input) -> decltype(private_detail_te_self.compute(ctx, output, input)) { return private_detail_te_self.compute(ctx, output, input); } template static argument private_detail_te_default_compute(float, T&& private_detail_te_self, context& ctx, const shape& output, const std::vector& input) { return detail::compute_op(private_detail_te_self, ctx, output, input); } template static auto private_detail_te_default_compute(char, T&& private_detail_te_self, const shape& output, const std::vector& input) -> decltype(private_detail_te_self.compute(output, input)) { return private_detail_te_self.compute(output, input); } template static argument private_detail_te_default_compute(float, T&& private_detail_te_self, const shape& output, const std::vector& input) { return detail::compute_op(private_detail_te_self, output, input); } template static auto private_detail_te_default_compute( char, T&& private_detail_te_self, const shape& output, const std::vector& input, const std::vector& module_args, std::function(module_ref&, const std::unordered_map&)> run) -> decltype(private_detail_te_self.compute(output, input, module_args, std::move(run))) { return private_detail_te_self.compute(output, input, module_args, std::move(run)); } template static argument private_detail_te_default_compute( float, T&& private_detail_te_self, const shape& output, const std::vector& input, const std::vector& module_args, std::function(module_ref&, const std::unordered_map&)> run) { return detail::compute_op( private_detail_te_self, output, input, module_args, std::move(run)); } template static auto private_detail_te_default_compute( char, T&& private_detail_te_self, context& ctx, const shape& output, const std::vector& input, const std::vector& module_args, std::function(module_ref&, const std::unordered_map&)> run) -> decltype(private_detail_te_self.compute(ctx, output, input, module_args, std::move(run))) { return private_detail_te_self.compute(ctx, output, input, module_args, std::move(run)); } template static argument private_detail_te_default_compute( float, T&& private_detail_te_self, context& ctx, const shape& output, const std::vector& input, const std::vector& module_args, std::function(module_ref&, const std::unordered_map&)> run) { return detail::compute_op( private_detail_te_self, ctx, output, input, module_args, std::move(run)); } template static auto private_detail_te_default_to_value(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.to_value()) { return private_detail_te_self.to_value(); } template static value private_detail_te_default_to_value(float, T&& private_detail_te_self) { return detail::to_value_op(private_detail_te_self); } template static auto private_detail_te_default_from_value(char, T&& private_detail_te_self, const value& v) -> decltype(private_detail_te_self.from_value(v)) { private_detail_te_self.from_value(v); } template static void private_detail_te_default_from_value(float, T&& private_detail_te_self, const value& v) { detail::from_value_op(private_detail_te_self, v); } template static auto private_detail_te_default_attributes(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.attributes()) { return private_detail_te_self.attributes(); } template static value private_detail_te_default_attributes(float, T&& private_detail_te_self) { return detail::attributes_op(private_detail_te_self); } template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(std::declval().name(), private_detail_te_default_is_context_free( char(0), std::declval()), private_detail_te_default_need_normalization( char(0), std::declval()), private_detail_te_default_has_finalize(char(0), std::declval()), private_detail_te_default_get_lifetime(char(0), std::declval()), private_detail_te_default_output_alias(char(0), std::declval(), std::declval&>()), private_detail_te_default_compile(char(0), std::declval(), std::declval(), std::declval(), std::declval&>()), private_detail_te_default_finalize(char(0), std::declval(), std::declval(), std::declval(), std::declval&>()), private_detail_te_default_compute_shape(char(0), std::declval(), std::declval&>()), private_detail_te_default_compute_shape( char(0), std::declval(), std::declval&>(), std::declval&>()), private_detail_te_default_compute(char(0), std::declval(), std::declval(), std::declval(), std::declval&>()), private_detail_te_default_compute(char(0), std::declval(), std::declval(), std::declval&>()), private_detail_te_default_compute( char(0), std::declval(), std::declval(), std::declval&>(), std::declval&>(), std::declval( module_ref&, const std::unordered_map&)>>()), private_detail_te_default_compute( char(0), std::declval(), std::declval(), std::declval(), std::declval&>(), std::declval&>(), std::declval( module_ref&, const std::unordered_map&)>>()), private_detail_te_default_to_value(char(0), std::declval()), private_detail_te_default_from_value(char(0), std::declval(), std::declval()), private_detail_te_default_attributes(char(0), std::declval()), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors operation() = default; template , typename = typename std::enable_if< not std::is_same, operation>{}>::type> operation(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template , typename = typename std::enable_if< not std::is_same, operation>{}>::type> operation& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { operation rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } std::string name() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().name(); } bool is_context_free() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().is_context_free(); } bool need_normalization() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().need_normalization(); } bool has_finalize() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().has_finalize(); } lifetime get_lifetime() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().get_lifetime(); } std::ptrdiff_t output_alias(const std::vector& input) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().output_alias(input); } value compile(context& ctx, const shape& output, const std::vector& input) { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compile(ctx, output, input); } void finalize(context& ctx, const shape& output, const std::vector& input) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().finalize(ctx, output, input); } shape compute_shape(const std::vector& input) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compute_shape(input); } shape compute_shape(const std::vector& inputs, const std::vector& mod_args) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compute_shape(inputs, mod_args); } argument compute(context& ctx, const shape& output, const std::vector& input) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compute(ctx, output, input); } argument compute(const shape& output, const std::vector& input) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compute(output, input); } argument compute(const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compute( output, input, module_args, std::move(run)); } argument compute(context& ctx, const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compute( ctx, output, input, module_args, std::move(run)); } value to_value() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().to_value(); } void from_value(const value& v) { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().from_value(v); } value attributes() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().attributes(); } friend std::ostream& operator<<(std::ostream& os, const operation& op) { assert(op.private_detail_te_handle_mem_var); return op.private_detail_te_get_handle().operator_shift_left(os); } friend bool operator==(const operation& x, const operation& y) { assert(x.private_detail_te_handle_mem_var); return x.private_detail_te_get_handle().operator==(y); } friend bool is_shared(const operation& private_detail_x, const operation& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual std::string name() const = 0; virtual bool is_context_free() const = 0; virtual bool need_normalization() const = 0; virtual bool has_finalize() const = 0; virtual lifetime get_lifetime() const = 0; virtual std::ptrdiff_t output_alias(const std::vector& input) const = 0; virtual value compile(context& ctx, const shape& output, const std::vector& input) = 0; virtual void finalize(context& ctx, const shape& output, const std::vector& input) = 0; virtual shape compute_shape(const std::vector& input) const = 0; virtual shape compute_shape(const std::vector& inputs, const std::vector& mod_args) const = 0; virtual argument compute(context& ctx, const shape& output, const std::vector& input) const = 0; virtual argument compute(const shape& output, const std::vector& input) const = 0; virtual argument compute(const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const = 0; virtual argument compute(context& ctx, const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const = 0; virtual value to_value() const = 0; virtual void from_value(const value& v) = 0; virtual value attributes() const = 0; virtual std::ostream& operator_shift_left(std::ostream& os) const = 0; virtual bool operator==(const operation& y) const = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } std::string name() const override { return private_detail_te_value.name(); } bool is_context_free() const override { return private_detail_te_default_is_context_free(char(0), private_detail_te_value); } bool need_normalization() const override { return private_detail_te_default_need_normalization(char(0), private_detail_te_value); } bool has_finalize() const override { return private_detail_te_default_has_finalize(char(0), private_detail_te_value); } lifetime get_lifetime() const override { return private_detail_te_default_get_lifetime(char(0), private_detail_te_value); } std::ptrdiff_t output_alias(const std::vector& input) const override { return private_detail_te_default_output_alias(char(0), private_detail_te_value, input); } value compile(context& ctx, const shape& output, const std::vector& input) override { return private_detail_te_default_compile( char(0), private_detail_te_value, ctx, output, input); } void finalize(context& ctx, const shape& output, const std::vector& input) override { private_detail_te_default_finalize( char(0), private_detail_te_value, ctx, output, input); } shape compute_shape(const std::vector& input) const override { return private_detail_te_default_compute_shape(char(0), private_detail_te_value, input); } shape compute_shape(const std::vector& inputs, const std::vector& mod_args) const override { return private_detail_te_default_compute_shape( char(0), private_detail_te_value, inputs, mod_args); } argument compute(context& ctx, const shape& output, const std::vector& input) const override { return private_detail_te_default_compute( char(0), private_detail_te_value, ctx, output, input); } argument compute(const shape& output, const std::vector& input) const override { return private_detail_te_default_compute( char(0), private_detail_te_value, output, input); } argument compute( const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const override { return private_detail_te_default_compute( char(0), private_detail_te_value, output, input, module_args, std::move(run)); } argument compute( context& ctx, const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const override { return private_detail_te_default_compute( char(0), private_detail_te_value, ctx, output, input, module_args, std::move(run)); } value to_value() const override { return private_detail_te_default_to_value(char(0), private_detail_te_value); } void from_value(const value& v) override { private_detail_te_default_from_value(char(0), private_detail_te_value, v); } value attributes() const override { return private_detail_te_default_attributes(char(0), private_detail_te_value); } std::ostream& operator_shift_left(std::ostream& os) const override { using migraphx::detail::operation_operators::operator<<; return os << private_detail_te_value; } bool operator==(const operation& y) const override { using migraphx::detail::operation_operators::operator==; return private_detail_te_value == y; } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const operation* x) { return x->any_cast(); } template inline ValueType* any_cast(operation* x) { return x->any_cast(); } template inline ValueType& any_cast(operation& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const operation& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif inline bool operator!=(const operation& x, const operation& y) { return not(x == y); } inline value compile(operation& op, context& ctx, const shape& output_shape, const std::vector& input) { return op.compile(ctx, output_shape, input); } template inline value compile(operation& op, Context& ctx, const shape& output_shape, const std::vector& input) { dependent_type ctx2 = std::ref(ctx); return compile(op, ctx2, output_shape, input); } template inline auto compile(T& op, Context& ctx, const shape& output_shape, const std::vector& input) -> decltype(op.compile(ctx, ctx, output_shape, input)) { return op.compile(ctx, ctx, output_shape, input); } inline shape compute_shape(const operation& op, const std::vector& inputs) { return op.compute_shape(inputs); } template inline auto compute_shape(const T& op, const std::vector& inputs) -> decltype(op.compute_shape(inputs)) { return op.compute_shape(inputs); } template inline auto compute_shape(const T& op, const std::vector& inputs) -> decltype(op.normalize_compute_shape(inputs)) { return detail::compute_shape_op(op, inputs); } inline shape compute_shape(const operation& op, const std::vector& inputs, const std::vector& mod_args) { return op.compute_shape(inputs, mod_args); } template inline auto compute_shape(const T& op, const std::vector& inputs, const std::vector& mod_args) -> decltype(op.compute_shape(inputs, mod_args)) { return op.compute_shape(inputs, mod_args); } template inline auto compute_shape(const T& op, const std::vector& inputs, const std::vector& mod_args) -> decltype(op.normalize_compute_shape(inputs, mod_args)) { return detail::compute_shape_op(op, inputs, mod_args); } inline bool is_context_free(const operation& op) { return op.is_context_free(); } template bool is_context_free(const T& x) { return detail::is_context_free_op(x); } inline bool need_normalization(const operation& op) { return op.need_normalization(); } template bool need_normalization(const T& x) { return detail::need_normalization_op(x); } inline bool has_finalize(const operation& op) { return op.has_finalize(); } template bool has_finalize(const T& x) { return detail::has_finalize_op(x); } MIGRAPHX_EXPORT void migraphx_to_value(value& v, const operation& op); MIGRAPHX_EXPORT void migraphx_from_value(const value& v, operation& op); #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/operators.hpp000066400000000000000000000131561510465702400233260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_HPP #define MIGRAPHX_GUARD_OPERATORS_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/optimize_module.hpp000066400000000000000000000034361510465702400245150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_OPTIMIZE_MODULE_HPP #define MIGRAPHX_GUARD_RTGLIB_OPTIMIZE_MODULE_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; /** * Runs several passes in a loop */ struct MIGRAPHX_EXPORT optimize_module { std::unordered_set propagate_constant_skip_ops = {}; std::string name() const { return "optimize_module"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/optional.hpp000066400000000000000000000055011510465702400231300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_OPTIONAL_HPP #define MIGRAPHX_GUARD_MIGRAPHX_OPTIONAL_HPP #include #if defined(CPPCHECK) #define MIGRAPHX_HAS_OPTIONAL 1 #define MIGRAPHX_HAS_OPTIONAL_TS 1 #elif defined(_WIN32) #if _MSC_VER >= 1920 #define MIGRAPHX_HAS_OPTIONAL 1 #define MIGRAPHX_HAS_OPTIONAL_TS 0 #elif _MSC_VER >= 1900 #define MIGRAPHX_HAS_OPTIONAL 0 #define MIGRAPHX_HAS_OPTIONAL_TS 1 #else #define MIGRAPHX_HAS_OPTIONAL 0 #define MIGRAPHX_HAS_OPTIONAL_TS 0 #endif #elif defined(__has_include) #if __has_include() && __cplusplus >= 201703L #define MIGRAPHX_HAS_OPTIONAL 1 #else #define MIGRAPHX_HAS_OPTIONAL 0 #endif #if __has_include() && __cplusplus >= 201103L #define MIGRAPHX_HAS_OPTIONAL_TS 1 #else #define MIGRAPHX_HAS_OPTIONAL_TS 0 #endif #else #define MIGRAPHX_HAS_OPTIONAL 0 #define MIGRAPHX_HAS_OPTIONAL_TS 0 #endif #if MIGRAPHX_HAS_OPTIONAL #include #elif MIGRAPHX_HAS_OPTIONAL_TS #include #else #error "No optional include available" #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #if MIGRAPHX_HAS_OPTIONAL template using optional = std::optional; using nullopt_t = std::nullopt_t; constexpr auto nullopt = std::nullopt; #elif MIGRAPHX_HAS_OPTIONAL_TS template using optional = std::experimental::optional; using nullopt_t = std::experimental::nullopt_t; constexpr auto nullopt = std::experimental::nullopt; #endif template bool has_value(const optional& x) { return x != nullopt; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_OPTIONAL_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/output_iterator.hpp000066400000000000000000000053041510465702400245550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_OUTPUT_ITERATOR_HPP #define MIGRAPHX_GUARD_MIGRAPHX_OUTPUT_ITERATOR_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct function_output_iterator { copy_assignable_function f; using self = function_output_iterator; using difference_type = void; using reference = void; using value_type = void; using pointer = void; using iterator_category = std::output_iterator_tag; struct output_proxy { template output_proxy& operator=(const T& value) { assert(f); (*f)(value); return *this; } copy_assignable_function* f; }; output_proxy operator*() { return output_proxy{&f}; } self& operator++() { return *this; } self& operator++(int) { return *this; } // NOLINT }; template function_output_iterator make_function_output_iterator(F f) { return {std::move(f)}; } template auto join_back_inserter(Container& c) { return make_function_output_iterator( [&](const auto& r) { c.insert(c.end(), r.begin(), r.end()); }); } template auto push_inserter(Container& c) { return make_function_output_iterator([&](const auto& x) { c.push(x); }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_OUTPUT_ITERATOR_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/pad_calc.hpp000066400000000000000000000072211510465702400230320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_PAD_CALC_HPP #define MIGRAPHX_GUARD_OPERATORS_PAD_CALC_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT void calc_auto_padding(std::string auto_pad, const std::vector& strides, const std::vector& k_lens, const std::vector& dilation, const std::vector& in_lens, std::vector& paddings); MIGRAPHX_EXPORT void calculate_padding(int64_t idx, std::vector& pads, int64_t input_dim, int64_t stride, int64_t dilation, int64_t weight_dim, bool is_same_upper = true); /*! * Calculate the padding for auto_padding. Used for dynamic shapes * where the padding calculation must be done at evaluation time. * \return padding in the form of {x0_begin, x1_begin, ... x0_end , x1_end, ...} */ MIGRAPHX_EXPORT std::vector calc_dyn_auto_pad(const std::vector& input_lens, const std::vector& wei_lens, const std::vector& strides, const std::vector& dilations, bool use_upper); // Used for dynamic auto padding of convolution operators since padding needs to be computed at // evaulation time. MIGRAPHX_EXPORT shape compute_padded_shape(const shape& input, const shape& weights, const std::vector& padding, const std::vector& stride, const std::vector& dilation); // Used for dynamic auto padding of pooling operators where padding needs to be computed at // evaulation time. MIGRAPHX_EXPORT shape compute_padded_pool_shape(const shape& input, const shape& kernel, const std::vector& padding, const std::vector& stride, const std::vector& dilation); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/par.hpp000066400000000000000000000100431510465702400220620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_PAR_HPP #define MIGRAPHX_GUARD_MIGRAPHX_PAR_HPP #include #if MIGRAPHX_HAS_EXECUTORS #include // Warn if parallel stl is not parallel #ifdef _PSTL_PAR_BACKEND_SERIAL #warning "Using serial backend for parallel stl" #endif #else // MIGRAPHX_HAS_EXECUTORS #include #endif #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace detail { struct exception_list { std::vector exceptions; std::mutex m; void add_exception() { std::lock_guard guard(m); exceptions.push_back(std::current_exception()); } template auto collect(F f) { return [f, this](auto&&... xs) { try { f(std::forward(xs)...); } catch(...) { this->add_exception(); } }; } void throw_if_exception() const { if(not exceptions.empty()) std::rethrow_exception(exceptions.front()); } }; } // namespace detail template OutputIt par_transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op) { #if MIGRAPHX_HAS_EXECUTORS return std::transform(std::execution::par, first1, last1, d_first, std::move(unary_op)); #else return std::transform(first1, last1, d_first, std::move(unary_op)); #endif } template OutputIt par_transform( InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOperation binary_op) { #if MIGRAPHX_HAS_EXECUTORS return std::transform( std::execution::par, first1, last1, first2, d_first, std::move(binary_op)); #else return std::transform(first1, last1, first2, d_first, std::move(binary_op)); #endif } template void par_for_each(InputIt first, InputIt last, UnaryFunction f) { #if MIGRAPHX_HAS_EXECUTORS // Propagate the exception detail::exception_list ex; std::for_each(std::execution::par, first, last, ex.collect(std::move(f))); ex.throw_if_exception(); #else simple_par_for(last - first, [&](auto i) { f(first[i]); }); #endif } template auto par_copy_if(Ts&&... xs) { #if MIGRAPHX_HAS_EXECUTORS return std::copy_if(std::execution::par, std::forward(xs)...); #else return std::copy_if(std::forward(xs)...); #endif } template auto par_sort(Ts&&... xs) { #if MIGRAPHX_HAS_EXECUTORS return std::sort(std::execution::par, std::forward(xs)...); #else return std::sort(std::forward(xs)...); #endif } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_PAR_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/par_dfor.hpp000066400000000000000000000054001510465702400230750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_PAR_DFOR_HPP #define MIGRAPHX_GUARD_RTGLIB_PAR_DFOR_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template auto par_dfor(Ts... xs) { return [=](auto f) { using array_type = std::array; array_type lens = {{static_cast(xs)...}}; auto n = std::accumulate(lens.begin(), lens.end(), 1, std::multiplies{}); const std::size_t min_grain = 8; if(n > 2 * min_grain) { array_type strides; strides.fill(1); std::partial_sum(lens.rbegin(), lens.rend() - 1, strides.rbegin() + 1, std::multiplies()); auto size = std::accumulate(lens.begin(), lens.end(), 1, std::multiplies()); par_for(size, min_grain, [&](std::size_t i) { array_type indices; std::transform(strides.begin(), strides.end(), lens.begin(), indices.begin(), [&](size_t stride, size_t len) { return (i / stride) % len; }); migraphx::unpack(f, indices); }); } else { dfor(xs...)(f); } }; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/par_for.hpp000066400000000000000000000033621510465702400227360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_PAR_FOR_HPP #define MIGRAPHX_GUARD_RTGLIB_PAR_FOR_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template void par_for(std::size_t n, F f) { using iterator = basic_iota_iterator; par_for_each(iterator{0}, iterator{n}, f); } template void par_for(std::size_t n, std::size_t min_grain, F f) { simple_par_for(n, min_grain, f); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/param_utils.hpp000066400000000000000000000044121510465702400236230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_PARAM_UTILS_HPP #define MIGRAPHX_GUARD_MIGRAPHX_PARAM_UTILS_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT std::string param_name(std::size_t i, const std::string& prefix = "x"); void sort_params(std::vector& params); // Find the inputs for a module by finding instructions that are mapped to the // parameters in the module MIGRAPHX_EXPORT std::vector find_inputs(const std::unordered_map& map_ins, const_module_ref parent, const_module_ref sub); MIGRAPHX_EXPORT std::vector find_inputs(const std::unordered_map& map_ins, const std::unordered_set& parent_instructions, const_module_ref sub); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_PARAM_UTILS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/pass.hpp000066400000000000000000000306311510465702400222530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_PASS_HPP #define MIGRAPHX_GUARD_PASS_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct program; struct module; struct module_pass_manager; #ifdef DOXYGEN /// An interface for applying a transformation to the instructions in a /// `program` struct pass { /// A unique name used to identify the pass std::string name() const; /// Run the pass on the module void apply(module_pass_manager& mpm) const; void apply(module& m) const; /// Run the pass on the program void apply(program& p) const; }; #else MIGRAPHX_EXPORT module& get_module(module_pass_manager& mpm); namespace detail { template auto module_pass_manager_apply(rank<1>, const T& x, module_pass_manager& mpm) -> decltype(x.apply(get_module(mpm))) { return x.apply(get_module(mpm)); } template void module_pass_manager_apply(rank<0>, const T&, module_pass_manager&) { } template void module_pass_manager_apply(const T& x, module_pass_manager& mpm) { module_pass_manager_apply(rank<1>{}, x, mpm); } } // namespace detail #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT pass { // std::string name() const; // (optional) void apply(module_pass_manager& mpm) const; // (optional) void apply(program& p) const; }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct pass { private: template static auto private_detail_te_default_apply(char, T&& private_detail_te_self, module_pass_manager& mpm) -> decltype(private_detail_te_self.apply(mpm)) { private_detail_te_self.apply(mpm); } template static void private_detail_te_default_apply(float, T&& private_detail_te_self, module_pass_manager& mpm) { migraphx::detail::module_pass_manager_apply(private_detail_te_self, mpm); } template static auto private_detail_te_default_apply(char, T&& private_detail_te_self, program& p) -> decltype(private_detail_te_self.apply(p)) { private_detail_te_self.apply(p); } template static void private_detail_te_default_apply(float, T&& private_detail_te_self, program& p) { migraphx::nop(private_detail_te_self, p); } template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(std::declval().name(), private_detail_te_default_apply(char(0), std::declval(), std::declval()), private_detail_te_default_apply( char(0), std::declval(), std::declval()), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors pass() = default; template , typename = typename std::enable_if< not std::is_same, pass>{}>::type> pass(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template , typename = typename std::enable_if< not std::is_same, pass>{}>::type> pass& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { pass rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } std::string name() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().name(); } void apply(module_pass_manager& mpm) const { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().apply(mpm); } void apply(program& p) const { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().apply(p); } friend bool is_shared(const pass& private_detail_x, const pass& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual std::string name() const = 0; virtual void apply(module_pass_manager& mpm) const = 0; virtual void apply(program& p) const = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } std::string name() const override { return private_detail_te_value.name(); } void apply(module_pass_manager& mpm) const override { private_detail_te_default_apply(char(0), private_detail_te_value, mpm); } void apply(program& p) const override { private_detail_te_default_apply(char(0), private_detail_te_value, p); } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const pass* x) { return x->any_cast(); } template inline ValueType* any_cast(pass* x) { return x->any_cast(); } template inline ValueType& any_cast(pass& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const pass& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif /// Used in the targets to enable/disable compiler passes MIGRAPHX_EXPORT pass enable_pass(bool enabled, pass p); #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/pass_manager.hpp000066400000000000000000000052521510465702400237460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_PASS_MANAGER_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_PASS_MANAGER_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager { module_pass_manager() = default; module_pass_manager(const module_pass_manager&) = delete; virtual module& get_module() = 0; virtual module* create_module(const std::string& name) = 0; virtual module* create_module(const std::string& name, module m) = 0; virtual void rename_module(const std::string& old_name, const std::string& new_name) = 0; virtual module* get_common_parent() = 0; virtual module* get_root_module() = 0; virtual void run_pass(const pass& p) = 0; protected: virtual ~module_pass_manager() {} }; MIGRAPHX_EXPORT void run_passes(program& prog, module_ref root_mod, const std::vector& passes, tracer trace = tracer{}); MIGRAPHX_EXPORT void run_passes(module& mod, const std::vector& passes, tracer trace = tracer{}); MIGRAPHX_EXPORT void run_passes(program& prog, const std::vector& passes, tracer trace = tracer{}); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/permutation.hpp000066400000000000000000000055441510465702400236610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_PERMUTATION_HPP #define MIGRAPHX_GUARD_RTGLIB_PERMUTATION_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template inline Vector reorder_dims(const Vector& dims, const std::vector& permutation) { Vector result(dims.size()); assert(dims.size() == permutation.size()); for(std::size_t i = 0; i < dims.size(); i++) { result[i] = dims[permutation[i]]; } return result; } MIGRAPHX_EXPORT shape reorder_shape(const shape& s, const std::vector& permutation); template inline std::vector sort_permutation(const Vector& data, Op op) { std::vector result(data.size()); std::iota(result.begin(), result.end(), 0); std::stable_sort( result.begin(), result.end(), [&](auto x, auto y) { return op(data[x], data[y]); }); return result; } /*! * Returns the inverse permutation that could be applied to undo the inputted permutation */ MIGRAPHX_EXPORT std::vector invert_permutation(const std::vector& permutation); /*! * Finds the permutation that would make the shape not transposed (refering to shape.transposed()) */ MIGRAPHX_EXPORT std::vector find_permutation(const shape& s); MIGRAPHX_EXPORT std::vector find_permutation(const std::vector& shapes); /// Normalize the shapes so the order of dimensions will be in the order it is /// in memory as much as possible. MIGRAPHX_EXPORT std::vector normalize_permutation(const std::vector& shapes); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/pmr.hpp000066400000000000000000000031151510465702400221000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_PMR_HPP #define MIGRAPHX_GUARD_MIGRAPHX_PMR_HPP #include #if defined(__has_include) && __has_include() #include #endif #if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L #define MIGRAPHX_HAS_PMR 1 #endif #ifndef MIGRAPHX_HAS_PMR #define MIGRAPHX_HAS_PMR 0 #endif #endif // MIGRAPHX_GUARD_MIGRAPHX_PMR_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/pmr/000077500000000000000000000000001510465702400213675ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/include/migraphx/pmr/unordered_map.hpp000066400000000000000000000037201510465702400247260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_PMR_UNORDERED_MAP_HPP #define MIGRAPHX_GUARD_PMR_UNORDERED_MAP_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace pmr { #if MIGRAPHX_HAS_PMR template , class KeyEqual = std::equal_to> using unordered_map = std::pmr::unordered_map; #else template , class KeyEqual = std::equal_to> struct unordered_map : std::unordered_map { using std::unordered_map::unordered_map; }; #endif } // namespace pmr } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_PMR_UNORDERED_MAP_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/pmr/unordered_set.hpp000066400000000000000000000036651510465702400247540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_PMR_UNORDERED_SET_HPP #define MIGRAPHX_GUARD_PMR_UNORDERED_SET_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace pmr { #if MIGRAPHX_HAS_PMR template , class KeyEqual = std::equal_to> using unordered_set = std::pmr::unordered_set; #else template , class KeyEqual = std::equal_to> struct unordered_set : std::unordered_set { using std::unordered_set::unordered_set; }; #endif } // namespace pmr } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_PMR_UNORDERED_SET_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/pmr/vector.hpp000066400000000000000000000032611510465702400234040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_PMR_VECTOR_HPP #define MIGRAPHX_GUARD_PMR_VECTOR_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace pmr { #if MIGRAPHX_HAS_PMR template using vector = std::pmr::vector; #else template struct vector : std::vector { using std::vector::vector; }; #endif } // namespace pmr } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_PMR_VECTOR_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/preallocate_param.hpp000066400000000000000000000033301510465702400247540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_PREALLOCATE_PARAM_HPP #define MIGRAPHX_GUARD_MIGRAPHX_PREALLOCATE_PARAM_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct MIGRAPHX_EXPORT preallocate_param { std::string param; allocation_model model; std::string name() const { return "preallocate_param"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_PREALLOCATE_PARAM_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/process.hpp000066400000000000000000000044341510465702400227650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_PROCESS_HPP #define MIGRAPHX_GUARD_MIGRAPHX_PROCESS_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct process_impl; struct MIGRAPHX_EXPORT process { using writer = std::function; explicit process(const std::string& cmd, const std::vector& args = {}); explicit process(const fs::path& cmd, const std::vector& args = {}) : process{cmd.string(), args} { } // move constructor process(process&&) noexcept; // copy assignment operator process& operator=(process rhs); ~process() noexcept; process& cwd(const fs::path& p); process& env(const std::vector& envs); void exec(); void write(std::function pipe_in); void read(const writer& output) const; private: std::unique_ptr impl; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_PROCESS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/program.hpp000066400000000000000000000132541510465702400227560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_PROGRAM_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_PROGRAM_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_COMPILE) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_EVAL) struct program_impl; struct marker; /** * @brief Stores the instruction stream */ struct MIGRAPHX_EXPORT program { program(); explicit program(module m); // move constructor program(program&&) noexcept; // copy constructor program(const program&); // copy assignment operator program& operator=(program); ~program() noexcept; std::vector get_parameter_names() const; shape get_parameter_shape(std::string name) const; instruction_ref get_parameter(std::string name) const; std::unordered_map get_parameter_shapes() const; std::size_t total_instructions() const; std::vector eval(const parameter_map& params, execution_environment exec_env = execution_environment{}) const; std::vector eval_with_context(std::vector& ctx, const parameter_map& params) const; void finish() const; std::size_t size() const; std::vector get_output_shapes() const; context& get_context() const; instruction_ref validate() const; target_assignments get_target_assignments(const std::vector& targets, assignment_options options = assignment_options{}); void compile(const target& t, compile_options options = compile_options{}); void compile(const std::vector& targets, std::vector compile_opts = {}); bool is_compiled() const; void finalize(); void perf_report(std::ostream& os, std::size_t n, parameter_map params, std::size_t batch = 1, bool detailed = false) const; void mark(const parameter_map& params, marker m); value to_value() const; void from_value(const value& v); void debug_print() const; void debug_print(instruction_ref ins) const; void print(std::unordered_map& names, const std::function)>& print_func) const; void print(const std::function)>& print_func) const; void print_graph(std::ostream& os, bool brief = false) const; void print_py(std::ostream& os) const; void print_cpp(std::ostream& os) const; void dry_run(const parameter_map& params) const; void annotate(std::ostream& os, const std::function& a) const; program& sort(); MIGRAPHX_EXPORT friend std::ostream& operator<<(std::ostream& os, const program& p); MIGRAPHX_EXPORT friend bool operator==(const program& x, const program& y); friend bool operator!=(const program& x, const program& y) { return not(x == y); } // module related api module* create_module(const std::string& name); module* create_module(const std::string& name, module m); module* get_module(const std::string& name); const module* get_module(const std::string& name) const; module* get_main_module(); const module* get_main_module() const; std::vector get_modules() const; std::vector get_modules(); std::unordered_multimap get_module_tree(); void remove_module(const std::string& name); void rename_module(const std::string& old_name, const std::string& new_name); void remove_unused_modules(); private: void assign(const program& p); std::unique_ptr impl; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/promote_literals.hpp000066400000000000000000000034051510465702400246700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_PROMOTE_LITERALS_HPP #define MIGRAPHX_GUARD_RTGLIB_PROMOTE_LITERALS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * Replace literals in submodules with literals in the root module. * Intended to allow for reuse of the literals between submodules. */ struct MIGRAPHX_EXPORT promote_literals { std::string name() const { return "promote_literals"; } void apply(module_pass_manager&) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/propagate_constant.hpp000066400000000000000000000034051510465702400251770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_PROPAGATE_CONSTANT_HPP #define MIGRAPHX_GUARD_RTGLIB_PROPAGATE_CONSTANT_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Replace instructions which take all literals with a literal of the computation. */ struct MIGRAPHX_EXPORT propagate_constant { std::unordered_set skip_ops = {}; std::string name() const { return "propagate_constant"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/propagate_precision.hpp000066400000000000000000000034061510465702400253420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_PROPAGATE_PRECISION_HPP #define MIGRAPHX_GUARD_MIGRAPHX_PROPAGATE_PRECISION_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; /// This pass will propagate higher precision through more adjacent operators. struct MIGRAPHX_EXPORT propagate_precision { std::string name() const { return "propagate_precision"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_PROPAGATE_PRECISION_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/quantization.hpp000066400000000000000000000046211510465702400240330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_QUANTIZATION_HPP #define MIGRAPHX_GUARD_RTGLIB_QUANTIZATION_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct program; MIGRAPHX_EXPORT void quantize_fp16(program& prog, const std::vector& ins_names = {"all"}); MIGRAPHX_EXPORT void quantize_int8(program& prog, const target& t, const std::vector& calibration, const std::unordered_set& ins_names = { "dot", "convolution"}); MIGRAPHX_EXPORT void quantize_fp8(program& prog, const target& t, const std::vector& calibration); MIGRAPHX_EXPORT void quantize_int4_weights(program& prog); MIGRAPHX_EXPORT void quantize_bf16(program& prog, const std::vector& ins_names = {"all"}); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/quantize_8bits.hpp000066400000000000000000000043421510465702400242560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_QUANTIZE_8BITS_HPP #define MIGRAPHX_GUARD_RTGLIB_QUANTIZE_8BITS_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct program; struct module; /** * capture inputs of operators to be quantized to int8 or fp8 */ struct MIGRAPHX_EXPORT capture_arguments_pass { std::unordered_set ins_names = {"dot", "convolution"}; std::function)> f{}; std::size_t* param_index = nullptr; std::string name() const { return "capture_arguments"; } void apply(module& m) const; }; /** * quantize a program to int8 or fp8 */ struct MIGRAPHX_EXPORT quantize_8bits_pass { shape::type_t precision = shape::int8_type; std::vector> quant_params; std::string name() const { return "quantize_8bits"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/quantize_int4.hpp000066400000000000000000000032771510465702400241110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_QUANTIZE_INT4_HPP #define MIGRAPHX_GUARD_RTGLIB_QUANTIZE_INT4_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct program; struct module; /** * quantize a program to int4 */ struct MIGRAPHX_EXPORT quantize_int4_pass { std::vector ins_names; std::string name() const { return "quantize_int4"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/ranges.hpp000066400000000000000000000160721510465702400225670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_RANGES_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_RANGES_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace detail { template auto generic_find_impl(rank<2>, String&& s, const T& x) -> decltype(s.npos, s.begin() + s.find(x)) { auto index = s.find(x); if(index == s.npos) return s.end(); else return s.begin() + index; } template auto generic_find_impl(rank<1>, C&& c, const T& x) -> decltype(c.find(x)) { return c.find(x); } template auto generic_find_impl(rank<0>, C&& c, const T& x) { return std::find(c.begin(), c.end(), x); } template auto generic_find_at_impl(rank<1>, C&& c, const T& x) -> decltype(c.find(x)) { return c.find(x); } template auto generic_find_at_impl(rank<0>, C&& c, const T& x) { auto n = std::distance(c.begin(), c.end()); if(x >= n) return c.end(); return std::next(c.begin(), x); } template decltype(auto) generic_at_impl(rank<1>, const C&, T&& it) { return it->second; } template decltype(auto) generic_at_impl(rank<0>, const C&, T&& it) { return *it; } struct empty { }; } // namespace detail template auto generic_find(C&& c, const T& x) { return detail::generic_find_impl(rank<2>{}, c, x); } template decltype(auto) at(C&& c, const T& x, const std::string& msg = "") { auto it = detail::generic_find_at_impl(rank<2>{}, c, x); if(it == c.end()) { if(msg.empty()) MIGRAPHX_THROW("At operator out of range for " + get_type_name(c)); else MIGRAPHX_THROW(msg); } return detail::generic_at_impl(rank<2>{}, c, it); } template bool contains(const C& c, const T& x) { return generic_find(c, x) != c.end(); } template bool contains(const std::initializer_list& c, const T& x) { return generic_find(c, x) != c.end(); } template bool contains(const std::initializer_list& c, const U& x) { return generic_find(c, x) != c.end(); } template bool all_of(const C& c, const Predicate& p) { return std::all_of(c.begin(), c.end(), p); } template bool all_of(const std::initializer_list& c, const Predicate& p) { return std::all_of(c.begin(), c.end(), p); } template bool all_of(detail::empty, const Predicate&) { return true; } template bool any_of(const C& c, const Predicate& p) { return std::any_of(c.begin(), c.end(), p); } template bool any_of(const std::initializer_list& c, const Predicate& p) { return std::any_of(c.begin(), c.end(), p); } template bool any_of(detail::empty, const Predicate&) { return false; } template bool none_of(const C& c, const Predicate& p) { return std::none_of(c.begin(), c.end(), p); } template bool none_of(const std::initializer_list& c, const Predicate& p) { return std::none_of(c.begin(), c.end(), p); } template bool none_of(detail::empty, const Predicate&) { return true; } template auto find_if(const C& c, const Predicate& p) { return std::find_if(c.begin(), c.end(), p); } template void copy(Range&& r, Iterator it) { std::copy(r.begin(), r.end(), it); } template void copy_if(Range&& r, Iterator it, Predicate pred) { std::copy_if(r.begin(), r.end(), it, pred); } template void transform(Range&& r, Iterator it, F f) { std::transform(r.begin(), r.end(), it, f); } template void transform(Range1&& r1, Range2&& r2, Iterator it, F f) { std::transform(r1.begin(), r1.end(), r2.begin(), it, f); } template auto reverse(Range&& r) { return range(std::make_reverse_iterator(r.end()), std::make_reverse_iterator(r.begin())); } template void replace(Range&& r, const T& old, const T& new_x) { std::replace(r.begin(), r.end(), old, new_x); } template bool equal(R1&& r1, R2&& r2, Predicate... pred) { return std::equal(r1.begin(), r1.end(), r2.begin(), r2.end(), pred...); } template auto distance(Range&& r) { return std::distance(r.begin(), r.end()); } template using range_value = std::decay_t().begin())>; template std::vector> find_all(Range&& r, Predicate p) { std::vector> result; std::copy_if(r.begin(), r.end(), std::back_inserter(result), p); return result; } template struct iterator_range { Iterator start; Iterator last; Iterator begin() const { return start; } Iterator end() const { return last; } }; template {})> iterator_range range(Iterator start, Iterator last) { return {start, last}; } inline iterator_range range(std::ptrdiff_t start, std::ptrdiff_t last) { return {{start}, {last}}; } inline iterator_range range(std::ptrdiff_t last) { return range(0, last); } template iterator_range range(std::pair p) { return {p.first, p.second}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rank.hpp000066400000000000000000000027431510465702400222430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_RANK_HPP #define MIGRAPHX_GUARD_RTGLIB_RANK_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct rank : rank { }; template <> struct rank<0> { }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/raw_data.hpp000066400000000000000000000276061510465702400230770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RAW_DATA_HPP #define MIGRAPHX_GUARD_RAW_DATA_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct raw_data_base { }; /** * @brief Provides a base class for common operations with raw buffer * * For classes that handle a raw buffer of data, this will provide common operations such as equals, * printing, and visitors. To use this class the derived class needs to provide a `data()` method to * retrieve a raw pointer to the data, and `get_shape` method that provides the shape of the data. * */ template struct raw_data : raw_data_base { template friend Stream& operator<<(Stream& os, const Derived& d) { if(not d.empty()) d.fallback_visit([&](auto x) { os << x; }, [&](auto&& xs) { for(auto&& x : xs) { os << "{ "; os << x; os << " }, "; } }); return os; } /** * @brief Visits a single data element at a certain index. * * @param v A function which will be called with the type of data * @param n The index to read from */ template void visit_at(Visitor v, Index n = 0) const { auto&& derived = static_cast(*this); if(derived.empty()) MIGRAPHX_THROW("Visiting empty data!"); auto&& s = derived.get_shape(); s.visit_type([&](auto as) { v(*(as.from(derived.data()) + s.index(n))); }); } template void visit(Visitor v, TupleVisitor tv) const { auto&& derived = static_cast(*this); if(derived.empty()) MIGRAPHX_THROW("Visiting empty data!"); auto&& s = derived.get_shape(); s.visit_type([&](auto as) { v(make_view(s, as.from(derived.data()))); }, [&] { tv(derived.get_sub_objects()); }); } /** * @brief Visits the data * * This will call the visitor function with a `tensor_view` based on the shape of the data. * * @param v A function to be called with `tensor_view` */ template void visit(Visitor v) const { visit(v, [&](const auto&) { MIGRAPHX_THROW("Invalid tuple type"); }); } /** * Visit the data using the normal visit function for computable types. * For non-computable types, use a tensor_view with shape = {type = uint8_type, lens = * {num bytes}}; */ template void fallback_visit(Visitor v, TupleVisitor tv) const { auto&& derived = static_cast(*this); if(derived.empty()) MIGRAPHX_THROW("Visiting empty data!"); auto&& s = derived.get_shape(); if(s.computable()) { visit(v, tv); } else { auto* buffer = static_cast(*this).data(); shape view_shape = {shape::uint8_type, {s.bytes()}}; using byte_type = std::conditional_t>{}, const byte*, byte*>; v(make_view(view_shape, reinterpret_cast(buffer))); } } template void fallback_visit(Visitor v) const { fallback_visit(v, [&](const auto&) { MIGRAPHX_THROW("Invalid tuple type"); }); } /// Returns true if the raw data is only one element bool single() const { auto&& s = static_cast(*this).get_shape(); return s.elements() == 1; } /** * @brief Retrieves a single element of data * * @param n The index to retrieve the data from * @tparam T The type of data to be retrieved * @return The element as `T` */ template T at(Index n = 0) const { T result; this->visit_at([&](auto x) { result = x; }, std::move(n)); return result; } struct auto_cast { const Derived* self; template operator T() { assert(self->single()); return self->template at(); } template using is_data_ptr = bool_c<(std::is_void{} or std::is_same>{} or std::is_same>{})>; template using get_data_type = std::conditional_t{}, float, T>; template bool matches() const { return is_data_ptr{} or self->get_shape().type() == migraphx::shape::get_type>{}; } template operator T*() { using type = std::remove_cv_t; assert(matches()); return reinterpret_cast(self->data()); } }; /// Implicit conversion of raw data pointer auto_cast implicit() const { return {static_cast(this)}; } /// Get a tensor_view to the data. /// For get() returns a 1D tensor_view. template tensor_view get() const { auto&& s = static_cast(*this).get_shape(); auto&& buffer = static_cast(*this).data(); if constexpr(std::is_same, migraphx::byte>{}) { shape view_shape = {shape::uint8_type, {s.bytes()}}; return make_view(view_shape, reinterpret_cast(buffer)); } else { if(s.computable() and s.type() != migraphx::shape::get_type{}) MIGRAPHX_THROW("Incorrect data type for raw data"); return make_view(s, reinterpret_cast(buffer)); } } template T* cast() const { auto&& buffer = static_cast(*this).data(); assert(static_cast(*this).get_shape().type() == migraphx::shape::get_type{}); return reinterpret_cast(buffer); } std::string to_string() const { std::stringstream ss; ss.precision(std::numeric_limits::max_digits10); ss << static_cast(*this); return ss.str(); } template std::vector to_vector() const { std::vector result(static_cast(*this).get_shape().elements()); this->visit([&](auto x) { result.assign(x.begin(), x.end()); }); return result; } }; namespace detail { template void visit_all_flatten(const shape& s, V1&& v1, V2 v2, Ts&&... xs) { s.visit_type([&](auto as) { v1(make_view(xs.get_shape(), as.from(xs.data()))...); }, [&] { v2(xs.get_sub_objects()...); }); } template auto visit_all_pack(const shape& s, V1&& v1, V2 v2) { return [=](auto&&... xs) { // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70100 visit_all_flatten(s, v1, std::move(v2), xs...); }; } template auto visit_all_pack(const shape& s, V1&& v1) { return visit_all_pack(s, v1, [](auto&&...) { MIGRAPHX_THROW("Invalid tuple type"); }); } } // namespace detail /** * @brief Visits every object together * @details This will visit every object, but assumes each object is the same type. This can reduce * the deeply nested visit calls. Returns a function that takes the visitor callback. * Calling syntax is `visit_all(xs...)([](auto... ys) {})` where `xs...` and `ys...` are the * same number of parameters. * * @param x A raw data object * @param xs Many raw data objects. * @return A function to be called with the visitor */ template auto visit_all(T&& x, Ts&&... xs) { auto&& s = x.get_shape(); std::initializer_list types = {xs.get_shape().type()...}; if(not std::all_of(types.begin(), types.end(), [&](shape::type_t t) { return t == s.type(); })) MIGRAPHX_THROW("Types must be the same"); return [&](auto... vs) { detail::visit_all_pack(s, vs...)(x, xs...); }; } /** * @brief Visits every object together * @details This will visit every object, but assumes each object is the same type. This can reduce * the deeply nested visit calls. Returns a function that takes the visitor callback. * * @param x A vector of raw data objects. Types must all be the same. * @return A function to be called with the visitor */ template auto visit_all(const std::vector& x) { auto&& s = x.front().get_shape(); if(not std::all_of( x.begin(), x.end(), [&](const T& y) { return y.get_shape().type() == s.type(); })) MIGRAPHX_THROW("Types must be the same"); return [&](auto v) { s.visit_type([&](auto as) { using type = typename decltype(as)::type; std::vector> result; std::transform(x.begin(), x.end(), std::back_inserter(result), [&](const auto& y) { return make_view(y.get_shape(), as.from(y.data())); }); v(result); }); }; } template {} and std::is_base_of{})> bool operator==(const T& x, const U& y) { auto&& xshape = x.get_shape(); auto&& yshape = y.get_shape(); bool result = x.empty() and y.empty(); if(not result and xshape == yshape) { if(xshape.computable()) { visit_all(x, y)([&](auto xview, auto yview) { result = xview == yview; }, [&](auto&& xs, auto&& ys) { result = std::equal(xs.begin(), xs.end(), ys.begin(), ys.end()); }); } else { result = x.template get() == y.template get(); } } return result; } template {} and std::is_base_of{})> bool operator!=(const T& x, const U& y) { return not(x == y); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/reduce_dims.hpp000066400000000000000000000031531510465702400235670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REDUCE_DIMS_HPP #define MIGRAPHX_GUARD_RTGLIB_REDUCE_DIMS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /// Collapse adjacent shape dimensions that are the same between shapes. MIGRAPHX_EXPORT std::vector reduce_dims(const std::vector& shapes); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/reflect.hpp000066400000000000000000000104501510465702400227260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REFLECT_HPP #define MIGRAPHX_GUARD_RTGLIB_REFLECT_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace detail { struct reflect_placeholder { template int operator()(Ts&&...) const { return 0; } }; template auto reflect_impl(rank<1>, T& x, Selector f) -> decltype(T::reflect(x, f)) { return T::reflect(x, std::move(f)); } template auto reflect_impl(rank<0>, T&, Selector) { return pack(); } template auto reflectable_impl(rank<1>, const T& x) -> decltype(T::reflect(x, reflect_placeholder{}), std::true_type{}); template auto reflectable_impl(rank<0>, const T&) -> decltype(std::false_type{}); template struct remove_rvalue_reference { using type = T; }; template struct remove_rvalue_reference { using type = T; }; template struct wrapper { using type = typename remove_rvalue_reference::type; type data; // NOLINT type get() const { return data; } }; template wrapper wrap(std::remove_reference_t& x) { return wrapper{std::forward(x)}; } template using auto_tuple_t = std::tuple::type...>; template auto_tuple_t auto_tuple(Ts&&... xs) { return auto_tuple_t{std::forward(xs)...}; } } // namespace detail template using is_reflectable = decltype(detail::reflectable_impl(rank<1>{}, std::declval())); template auto reflect(T& x, Selector f) { return detail::reflect_impl(rank<1>{}, x, std::move(f)); } template auto reflect_tie(T& x) { return reflect(x, [](auto&& y, auto&&...) { // cppcheck-suppress UnnecessaryElseStatement if constexpr(is_reflectable{}) { auto t = reflect_tie(y); return detail::wrap(t); } else { return detail::wrap(y); } })([](auto&&... xs) { return detail::auto_tuple(xs.get()...); }); } template void reflect_each(T& x, F f) { return reflect(x, [](auto&& y, auto... ys) { return pack(detail::wrap(y), ys...); })([&](auto&&... xs) { each_args([&](auto p) { p([&](auto&& y, auto... ys) { f(y.get(), ys...); }); }, xs...); }); } template struct reflect_equality { friend bool operator==(const T& x, const T& y) { return reflect_tie(x) == reflect_tie(y); } friend bool operator!=(const T& x, const T& y) { return not(x == y); } }; template struct reflect_stream { template friend Stream& operator<<(Stream& os, const T& x) { char d = '{'; reflect_each(x, [&](const auto& y, const auto& name) { os << d << name << "=" << y; d = ','; }); os << "}"; return os; } }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/register_op.hpp000066400000000000000000000051521510465702400236270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REGISTER_OP_HPP #define MIGRAPHX_GUARD_RTGLIB_REGISTER_OP_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // unregister all ops for specified target, useful when unloading dynamically plugged-in target lib MIGRAPHX_EXPORT void unregister_op(const std::string& op_name); namespace detail { struct op_handler { operation op; std::string name; op_handler(const operation& op_r) : op(op_r), name(op.name()){}; ~op_handler() { unregister_op(name); } }; } // namespace detail MIGRAPHX_EXPORT void register_op_init(); MIGRAPHX_EXPORT void register_op(const operation& op); MIGRAPHX_EXPORT operation load_op(const std::string& name); MIGRAPHX_EXPORT bool has_op(const std::string& name); MIGRAPHX_EXPORT std::vector get_operators(); template void register_op() { register_op_init(); // instantiate static op_map; static auto op_h = detail::op_handler(T{}); register_op(op_h.op); } struct register_op_action { template static void apply() { register_op(); } }; template using auto_register_op = auto_register; #define MIGRAPHX_REGISTER_OP(...) MIGRAPHX_AUTO_REGISTER(register_op_action, __VA_ARGS__) } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/register_target.hpp000066400000000000000000000050241510465702400244750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REGISTER_TARGET_HPP #define MIGRAPHX_GUARD_RTGLIB_REGISTER_TARGET_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT void register_target_init(); MIGRAPHX_EXPORT void register_target(const target& t); MIGRAPHX_EXPORT void unregister_target(const std::string& name); MIGRAPHX_EXPORT target make_target(const std::string& name); MIGRAPHX_EXPORT std::vector get_targets(); namespace detail { struct target_handler { target t; std::string target_name; explicit target_handler(target t_r) : t(std::move(t_r)), target_name(t.name()) {} ~target_handler() { unregister_target(target_name); } }; } // namespace detail template void register_target() { register_target_init(); static auto t_h = detail::target_handler(T{}); register_target(t_h.t); } struct register_target_action { template static void apply() { register_target(); } }; template using auto_register_target = auto_register; #define MIGRAPHX_REGISTER_TARGET(...) MIGRAPHX_AUTO_REGISTER(register_target_action, __VA_ARGS__) } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/replace_allocate.hpp000066400000000000000000000034361510465702400245670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REPLACE_ALLOCATE_HPP #define MIGRAPHX_GUARD_RTGLIB_REPLACE_ALLOCATE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; /** * Replace `allocate` instructions with target allocations or output parameters. */ struct MIGRAPHX_EXPORT replace_allocate { allocation_model model; bool offload_copy = false; std::string name() const { return "replace_allocate"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/requires.hpp000066400000000000000000000045501510465702400231450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_REQUIRES_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_REQUIRES_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct and_ : std::is_same, and_<(Bs or true)...>> // NOLINT { }; template using bool_c = std::integral_constant; #define MIGRAPHX_REQUIRES_PRIMITIVE_CAT(x, y) x##y #define MIGRAPHX_REQUIRES_CAT(x, y) MIGRAPHX_REQUIRES_PRIMITIVE_CAT(x, y) #define MIGRAPHX_REQUIRES_VAR() MIGRAPHX_REQUIRES_CAT(PrivateRequires, __LINE__) #ifdef CPPCHECK #define MIGRAPHX_REQUIRES(...) class = void #define MIGRAPHX_CLASS_REQUIRES(...) void #else #define MIGRAPHX_REQUIRES(...) \ long MIGRAPHX_REQUIRES_VAR() = __LINE__, \ typename std::enable_if<(MIGRAPHX_REQUIRES_VAR() == __LINE__ and \ (migraphx::and_<__VA_ARGS__>{})), \ int>::type = 0 #define MIGRAPHX_CLASS_REQUIRES(...) typename std::enable_if<(migraphx::and_<__VA_ARGS__>{})>::type #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/returns.hpp000066400000000000000000000031661510465702400230120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_RETURNS_HPP #define MIGRAPHX_GUARD_MIGRAPHX_RETURNS_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // Similar to decltype(auto) except it will propagate any substitution failures // NOLINTNEXTLINE #define MIGRAPHX_RETURNS(...) \ ->decltype(__VA_ARGS__) { return __VA_ARGS__; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_RETURNS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_dot.hpp000066400000000000000000000031631510465702400236340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_REWRITE_DOT_HPP #define MIGRAPHX_GUARD_MIGRAPHX_REWRITE_DOT_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct MIGRAPHX_EXPORT rewrite_dot { std::string name() const { return "rewrite_dot"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_REWRITE_DOT_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_gelu.hpp000066400000000000000000000033051510465702400240000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REWRITE_GELU_HPP #define MIGRAPHX_GUARD_RTGLIB_REWRITE_GELU_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Rewrite GELU blocks as different approximations. */ struct MIGRAPHX_EXPORT rewrite_gelu { bool fast_math = true; std::string name() const { return "rewrite_gelu"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_low_precision.hpp000066400000000000000000000033561510465702400257260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REWRITE_LOW_PRECISION_HPP #define MIGRAPHX_GUARD_RTGLIB_REWRITE_LOW_PRECISION_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Rewrite operators in low precision types to avoid going out of precision bounds. */ struct MIGRAPHX_EXPORT rewrite_low_precision { std::string name() const { return "rewrite_low_precision"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_pooling.hpp000066400000000000000000000033021510465702400245100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REWRITE_POOLING_HPP #define MIGRAPHX_GUARD_RTGLIB_REWRITE_POOLING_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Rewrite pooling to reduce_mean */ struct MIGRAPHX_EXPORT rewrite_pooling { bool rewrite_lrn = false; std::string name() const { return "rewrite_pooling"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_quantization.hpp000066400000000000000000000032421510465702400255720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REWRITE_QUANTIZATION_HPP #define MIGRAPHX_GUARD_RTGLIB_REWRITE_QUANTIZATION_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Rewrite quantization ops to equivalent operators */ struct MIGRAPHX_EXPORT rewrite_quantization { std::string name() const { return "rewrite_quantization"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_reduce.hpp000066400000000000000000000031441510465702400243140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_SRC_INCLUDE_MIGRAPHX_REWRITE_REDUCE #define MIGRAPHX_GUARD_SRC_INCLUDE_MIGRAPHX_REWRITE_REDUCE #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct MIGRAPHX_EXPORT rewrite_reduce { std::string name() const { return "rewrite_reduce"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_reshapes.hpp000066400000000000000000000221461510465702400246620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_REWRITE_RESHAPES_HPP #define MIGRAPHX_GUARD_MIGRAPHX_REWRITE_RESHAPES_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct rewrite_reshapes_base { template static instruction_ref insert(module_pass_manager& mpm, instruction_ref ins, const std::vector& inputs, const AxesMap&) { return mpm.get_module().insert_instruction( ins, ins->get_operator(), inputs, ins->module_inputs()); } template static bool supports(instruction_ref, std::vector&, const AxesMap&) { return true; } static std::vector base_dims(instruction_ref ins) { return ins->get_shape().lens(); } }; template struct rewrite_reshapes { std::string name() const { return "rewrite_reshapes"; } struct find_op_reshape_op { std::string op1; std::string op2; auto matcher() const { auto reshapes = match::name("reshape", "squeeze", "unsqueeze", "flatten", "transpose", "contiguous", "multibroadcast", "broadcast")(match::used_once()); auto pointwise = match::name(op1)(match::used_once()); auto reshapes_pointwise = reshapes(match::arg(0)(match::skip(reshapes())(pointwise.bind("x")))); return match::name(op2)( match::any_of[match::inputs()](reshapes_pointwise.bind("input"))); } template static instruction_ref find_input_if(instruction_ref start, instruction_ref last, F f) { while(start != last) { if(f(start)) return start; if(start->inputs().size() != 1) return last; start = start->inputs().front(); } return last; } template static bool any_input_of(instruction_ref start, instruction_ref last, F f) { return find_input_if(start, last, f) != last; } static bool match_input(instruction_ref ins, instruction_ref x_ins) { if(ins->inputs().empty()) return false; auto input = ins->inputs().front(); if(input->name() == "contiguous") return match_input(input, x_ins); return x_ins == input; } static std::optional is_broadcasted(instruction_ref start, instruction_ref last) { auto broadcast_ins = find_input_if(start, last, [&](auto i) { return i->name() == "multibroadcast"; }); bool result = broadcast_ins != last; if(result and not match_input(broadcast_ins, last)) return nullopt; return result; } static bool is_broadcast(instruction_ref ins) { return ins->name() == "multibroadcast"; } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto x_ins = r.instructions["x"]; auto input_ins = r.instructions["input"]; // If its just a broadcast then skip if(not any_input_of(input_ins, x_ins, [](instruction_ref x) { return not contains({"multibroadcast", "broadcast", "contiguous"}, x->name()); })) return; auto dims1 = T::base_dims(ins); auto dims2 = T::base_dims(x_ins); if(elements(dims1) != elements(dims2)) return; std::vector ops; auto next_ins = input_ins; while(next_ins != x_ins) { ops.push_back(next_ins->get_operator()); next_ins = next_ins->inputs().front(); } assert(next_ins == x_ins); std::reverse(ops.begin(), ops.end()); auto desc = shape_transform_descriptor::create(x_ins->get_shape().lens(), ops).rebase(dims2); if(desc.empty()) return; auto cdims = desc.common_dims(); auto reshape_input = [&](const auto& ins_to_insert, const auto& gdesc) { return [&](auto input) { auto gops = gdesc.generate(input->get_shape().lens()); auto start = input; for(const auto& op : gops) { start = mpm.get_module().insert_instruction(ins_to_insert, op, start); } return start; }; }; auto x_inputs = x_ins->inputs(); std::transform(x_inputs.begin(), x_inputs.end(), x_inputs.begin(), reshape_input(x_ins, desc.to_common_from_src())); auto new_x_ins = insert(mpm, x_ins, x_inputs, desc.common_axes_map_from_src()); if(new_x_ins->get_shape().lens() != cdims) { new_x_ins = mpm.get_module().insert_instruction( x_ins, make_op("multibroadcast", {{"out_lens", cdims}}), new_x_ins); } auto inputs = ins->inputs(); std::transform(inputs.begin(), inputs.end(), inputs.begin(), [&](auto input) { if(input == input_ins) return new_x_ins; return reshape_input(ins, desc.to_common_from_dst())(input); }); auto pw = insert(mpm, ins, inputs, desc.common_axes_map_from_dst()); auto rins = reshape_input(ins, desc.to_dst_from_common())(pw); mpm.get_module().replace_instruction(ins, rins); } static bool same_dims(instruction_ref ins) { return all_of(ins->inputs(), [&](auto input) { return input->get_shape().lens() == ins->get_shape().lens(); }); } template static instruction_ref insert(module_pass_manager& mpm, instruction_ref ins, const std::vector& inputs, const AxesMap& am) { if(ins->name() == "pointwise") return mpm.get_module().insert_instruction( ins, ins->get_operator(), inputs, ins->module_inputs()); return T::insert(mpm, ins, inputs, am); } }; void apply(module_pass_manager& mpm) const { if(T::name() == "pointwise") { match::find_matches(mpm, find_op_reshape_op{"pointwise", T::name()}); } else { match::find_matches(mpm, find_op_reshape_op{"pointwise", T::name()}, find_op_reshape_op{T::name(), "pointwise"}, find_op_reshape_op{T::name(), T::name()}); } mpm.run_pass(simplify_reshapes{1}); mpm.run_pass(eliminate_common_subexpression{}); mpm.run_pass(dead_code_elimination{}); } }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_REWRITE_RESHAPES_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_rnn.hpp000066400000000000000000000111471510465702400236440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REWRITE_RNN_HPP #define MIGRAPHX_GUARD_RTGLIB_REWRITE_RNN_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Rewrite rnn to gemm and add. */ struct MIGRAPHX_EXPORT rewrite_rnn { std::string name() const { return "rewrite_rnn"; } void apply(module& m) const; private: // for vanilla rnn operators void apply_vanilla_rnn(module& m, instruction_ref ins) const; std::vector vanilla_rnn_cell(bool is_forward, module& m, instruction_ref ins, std::vector inputs, const operation& actv_func) const; std::vector vanilla_rnn_actv_funcs(instruction_ref ins) const; // for gru operators void apply_gru(module& m, instruction_ref ins) const; std::vector gru_cell(bool is_forward, module& m, instruction_ref ins, std::vector inputs, int linear_before_reset, const operation& actv_func1, const operation& actv_func2) const; std::vector gru_actv_funcs(instruction_ref ins) const; // for lstm operators void apply_lstm(module& m, instruction_ref ins) const; std::vector lstm_cell(bool is_forward, module& m, instruction_ref ins, std::vector inputs, const operation& actv_func1, const operation& actv_func2, const operation& actv_func3) const; std::vector lstm_actv_funcs(instruction_ref ins) const; bool is_variable_seq_lens(const module& m, instruction_ref seq_lens) const; instruction_ref replace_last_hs_output(module& m, instruction_ref ins, instruction_ref seq_lens, instruction_ref last_hs_output, op::rnn_direction dirct) const; void replace_last_cell_output(module& m, instruction_ref ins, instruction_ref seq_lens, instruction_ref cell_outputs, instruction_ref last_cell_output, op::rnn_direction dirct) const; std::size_t get_seq_len(const module& m, instruction_ref input, instruction_ref seq_lens) const; instruction_ref pad_hidden_states(module& m, instruction_ref seq, instruction_ref seq_lens, instruction_ref hs) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/rewrite_topk.hpp000066400000000000000000000033421510465702400240220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_REWRITE_TOPK_HPP #define MIGRAPHX_GUARD_MIGRAPHX_REWRITE_TOPK_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /// Rewrite topk operators ideally to better performing operators struct MIGRAPHX_EXPORT rewrite_topk { std::size_t split_threshold = 8192; std::string name() const { return "rewrite_topk"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_REWRITE_TOPK_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/run_loop.hpp000066400000000000000000000131511510465702400231400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_RUN_LOOP_HPP #define MIGRAPHX_GUARD_RTGLIB_RUN_LOOP_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template argument run_loop(const LoopModel& model, const std::vector& scan_output_directions, T& ctx, std::vector args, const std::vector& mods, const std::function( module_ref&, const std::unordered_map&)>& run) { std::vector> results; // process argu lists auto iter_num = args.at(0).at(); auto cond = args.at(1).at(); auto input_num = (args.size() - 2) / 2; auto dep_num = input_num - 2; module_ref mod = mods.at(0); auto param_name_shapes = mod->get_parameter_shapes(); auto param_names = mod->get_parameter_names(); std::vector dep0(args.begin() + input_num + 1, args.begin() + 2 * input_num); std::vector dep1(args.begin() + 2 * input_num, args.begin() + 2 * input_num + 1); auto ins_outputs = args.back().get_sub_objects(); dep1.insert(dep1.end(), ins_outputs.begin(), ins_outputs.begin() + dep_num); std::array, 2> loop_carry_deps = {dep0, dep1}; // loop iter argument std::vector in_args = {args.at(input_num), dep1.at(0)}; in_args.insert(in_args.end(), args.begin() + 2, args.begin() + input_num); std::vector out_args = dep0; out_args.insert(out_args.end(), ins_outputs.begin() + dep_num, ins_outputs.end()); std::vector scan_outputs(ins_outputs.begin() + dep_num, ins_outputs.end()); auto out_param_indices = model.get_output_params(*mod); int64_t iter = 0; for(iter = 0; iter < iter_num and cond; ++iter) { // copy iter num and cond to device memory model.copy(ctx, iter, in_args.at(0)); model.copy(ctx, cond, in_args.at(1)); // wrap up the inputs and outputs std::unordered_map params; int input_index = 0; for(const auto& name : param_names) { auto ps = mod->get_parameter_shape(name); if(ps == shape{}) { continue; } // it is an input parameter if(not contains(out_param_indices, name)) { params[name] = in_args.at(input_index++); } else { auto output_index = out_param_indices[name]; if(output_index > dep_num) { int64_t dir = scan_output_directions.empty() ? 0 : scan_output_directions[output_index - dep_num - 1]; auto idx = (1 - dir) * iter + dir * (iter_num - 1 - iter); const auto& arg = out_args.at(output_index); assert((idx + 1) * ps.bytes() <= arg.get_shape().bytes()); params[name] = argument(ps, arg.data() + idx * ps.bytes()); } else { params[name] = out_args.at(output_index); } } } auto mod_args = run(mod, params); // copy back cond to be used next iteration model.copy(ctx, mod_args.at(0), cond); // mod outputs are used as next loop input std::copy(mod_args.begin(), mod_args.begin() + dep_num + 1, in_args.begin() + 1); const auto& dep_out = loop_carry_deps[(iter + 1) % 2]; std::copy(dep_out.begin(), dep_out.end(), out_args.begin()); std::vector mod_scan_outs(mod_args.begin() + 1 + dep_num, mod_args.end()); model.append(mod_scan_outs, scan_outputs, scan_output_directions, iter, iter_num); } out_args.erase(out_args.begin()); std::copy(in_args.begin() + 2, in_args.end(), out_args.begin()); model.set_zero(ctx, scan_outputs, iter); return {out_args}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/sat_ops.hpp000066400000000000000000000032431510465702400227540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SAT_OPS_HPP #define MIGRAPHX_GUARD_RTGLIB_SAT_OPS_HPP #include #include template constexpr T mul_sat(T a, T b) noexcept { T c; if(not __builtin_mul_overflow(a, b, &c)) { return c; } if constexpr(std::is_unsigned{}) { return std::numeric_limits::max(); } else if((a < 0) != (b < 0)) { return std::numeric_limits::min(); } return std::numeric_limits::max(); } #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/schedule.hpp000066400000000000000000000033631510465702400231030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SCHEDULE_HPP #define MIGRAPHX_GUARD_RTGLIB_SCHEDULE_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Schedule instructions for concurrent execution */ struct MIGRAPHX_EXPORT schedule { schedule_model model{}; bool enable = true; std::string name() const { return "schedule"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/schedule_model.hpp000066400000000000000000000322171510465702400242630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_SCHEDULE_MODEL_HPP #define MIGRAPHX_GUARD_SCHEDULE_MODEL_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct operation; #ifdef DOXYGEN /// An interface for target-dependent model for the scheduler struct schedule_model { /// Get the number of concurrent instruction allowed std::size_t concurrency() const; /// Schedule a concurrent instruction void sched(module& m, instruction_ref ins, std::size_t n) const; // Insert necessary waits before an instruction void wait(module& m, instruction_ref ins, std::size_t wait_id) const; // Insert necessary records after an instruction void record(module& m, instruction_ref ins, std::size_t wait_id) const; /// Compute weights for an operation std::size_t weight(const operation& op) const; }; #else #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT schedule_model { // std::size_t concurrency() const; // void sched(module& m, instruction_ref ins, std::size_t n) const; // void wait(module& m, instruction_ref ins, std::size_t wait_id) const; // void record(module& m, instruction_ref ins, std::size_t wait_id) const; // std::size_t weight(const operation& op) const; }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct schedule_model { private: template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(std::declval().concurrency(), std::declval().sched(std::declval(), std::declval(), std::declval()), std::declval().wait(std::declval(), std::declval(), std::declval()), std::declval().record(std::declval(), std::declval(), std::declval()), std::declval().weight(std::declval()), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors schedule_model() = default; template < typename PrivateDetailTypeErasedT, typename = private_te_constraints, typename = typename std::enable_if< not std::is_same, schedule_model>{}>::type> schedule_model(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template < typename PrivateDetailTypeErasedT, typename = private_te_constraints, typename = typename std::enable_if< not std::is_same, schedule_model>{}>::type> schedule_model& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { schedule_model rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } std::size_t concurrency() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().concurrency(); } void sched(module& m, instruction_ref ins, std::size_t n) const { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().sched(m, ins, n); } void wait(module& m, instruction_ref ins, std::size_t wait_id) const { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().wait(m, ins, wait_id); } void record(module& m, instruction_ref ins, std::size_t wait_id) const { assert((*this).private_detail_te_handle_mem_var); (*this).private_detail_te_get_handle().record(m, ins, wait_id); } std::size_t weight(const operation& op) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().weight(op); } friend bool is_shared(const schedule_model& private_detail_x, const schedule_model& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual std::size_t concurrency() const = 0; virtual void sched(module& m, instruction_ref ins, std::size_t n) const = 0; virtual void wait(module& m, instruction_ref ins, std::size_t wait_id) const = 0; virtual void record(module& m, instruction_ref ins, std::size_t wait_id) const = 0; virtual std::size_t weight(const operation& op) const = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } std::size_t concurrency() const override { return private_detail_te_value.concurrency(); } void sched(module& m, instruction_ref ins, std::size_t n) const override { private_detail_te_value.sched(m, ins, n); } void wait(module& m, instruction_ref ins, std::size_t wait_id) const override { private_detail_te_value.wait(m, ins, wait_id); } void record(module& m, instruction_ref ins, std::size_t wait_id) const override { private_detail_te_value.record(m, ins, wait_id); } std::size_t weight(const operation& op) const override { return private_detail_te_value.weight(op); } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const schedule_model* x) { return x->any_cast(); } template inline ValueType* any_cast(schedule_model* x) { return x->any_cast(); } template inline ValueType& any_cast(schedule_model& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const schedule_model& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/serialize.hpp000066400000000000000000000165021510465702400232750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SERIALIZE_HPP #define MIGRAPHX_GUARD_RTGLIB_SERIALIZE_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // Avoid implicit conversion with ADL lookup template void migraphx_to_value(value&, const T&) = delete; template value to_value(const T& x); template void from_value(const value& v, T& x); template T from_value(const value& v) { T x{}; from_value(v, x); return x; } namespace detail { template {})> value to_value_impl(rank<0>, const T&) { return value::object{}; } template auto to_value_impl(rank<1>, const T& x) -> decltype(std::tuple_size{}, value{}) { value result = value::array{}; repeat_c{}>([&](auto i) { result.push_back(to_value(std::get(x))); }); return result; } template auto to_value_impl(rank<2>, const T& x) -> decltype(x.begin(), x.end(), value{}) { value result = value::array{}; for(auto&& y : x) { result.insert(to_value(y)); } return result; } template {})> value to_value_impl(rank<3>, const T& x) { value result = value::object{}; reflect_each(x, [&](auto&& y, const std::string& name) { result.emplace(name, to_value(y)); }); return result; } template auto to_value_impl(rank<4>, const optional& x) { value result{}; if(x.has_value()) return to_value(*x); return result; } template {})> value to_value_impl(rank<5>, const T& x) { return std::int64_t{x}; } template {})> value to_value_impl(rank<6>, const T& x) { return std::uint64_t{x}; } template {})> value to_value_impl(rank<7>, const T& x) { return double{x}; } template {})> value to_value_impl(rank<8>, const T& x) { return x; } inline value to_value_impl(rank<9>, const std::string& x) { return x; } template auto to_value_impl(rank<10>, const T& x) -> decltype(migraphx_to_value(x)) { return migraphx_to_value(x); } template auto to_value_impl(rank<11>, const T& x) -> decltype(x.to_value()) { return x.to_value(); } template auto to_value_impl(rank<12>, const T& x) -> decltype(migraphx_to_value(std::declval(), x), value{}) { value v; migraphx_to_value(v, x); return v; } template {})> value to_value_impl(rank<13>, const T& x) { return x; } template {})> void from_value_impl(rank<0>, const value& v, T& x) { if(not v.is_object()) MIGRAPHX_THROW("Expected an object"); if(not v.get_object().empty()) MIGRAPHX_THROW("Expected an empty object"); x = T{}; } template auto from_value_impl(rank<1>, const value& v, T& x) -> decltype(std::tuple_size{}, void()) { repeat_c{}>( [&](auto i) { std::get(x) = from_value>(v[i]); }); } template auto from_value_impl(rank<2>, const value& v, T& x) -> decltype(x.insert(x.end(), *x.begin()), void()) { x.clear(); for(auto&& e : v) x.insert(x.end(), from_value(e)); } template {})> auto from_value_impl(rank<3>, const value& v, T& x) -> decltype(x.insert(x.end(), *x.begin()), void()) { x.clear(); if(v.is_binary()) { for(auto&& e : v.get_binary()) x.insert(x.end(), e); } else { for(auto&& e : v) x.insert(x.end(), from_value(e)); } } template auto from_value_impl(rank<4>, const value& v, T& x) -> decltype(x.insert(*x.begin()), std::declval(), void()) { if(v.is_object()) { x.clear(); for(auto&& e : v) x.emplace(from_value(e.get_key()), from_value(e)); } else if(v.is_array()) { x.clear(); for(auto&& e : v) { if(e.size() != 2) MIGRAPHX_THROW("Expected a pair"); x.emplace(from_value(e[0]), from_value(e[1])); } } else { MIGRAPHX_THROW("Expected object or array"); } } template {})> void from_value_impl(rank<5>, const value& v, T& x) { reflect_each(x, [&](auto& y, const std::string& name) { using type = std::decay_t; if(v.contains(name)) y = from_value(v.at(name).without_key()); }); } template void from_value_impl(rank<6>, const value& v, optional& x) { if(not v.is_null()) x = from_value(v); } template {} or std::is_enum{})> void from_value_impl(rank<7>, const value& v, T& x) { x = v.to(); } inline void from_value_impl(rank<8>, const value& v, std::string& x) { x = v.to(); } template auto from_value_impl(rank<9>, const value& v, T& x) -> decltype(x.from_value(v), void()) { x.from_value(v); } template auto from_value_impl(rank<10>, const value& v, T& x) -> decltype(migraphx_from_value(v, x), void()) { migraphx_from_value(v, x); } template {})> void from_value_impl(rank<11>, const value& v, T& x) { x = v; } } // namespace detail template value to_value(const T& x) { return detail::to_value_impl(rank<13>{}, x); } template void from_value(const value& v, T& x) { detail::from_value_impl(rank<11>{}, v, x); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/shape.hpp000066400000000000000000000410631510465702400224060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_SHAPE_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_SHAPE_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct value; struct shape_impl; struct MIGRAPHX_EXPORT shape { // Add new types here // clang-format off #define MIGRAPHX_SHAPE_VISIT_TYPES(m) \ m(bool_type, bool) \ m(half_type, half) \ m(float_type, float) \ m(double_type, double) \ m(uint8_type, uint8_t) \ m(int8_type, int8_t) \ m(uint16_type, uint16_t) \ m(int16_type, int16_t) \ m(int32_type, int32_t) \ m(int64_type, int64_t) \ m(uint32_type, uint32_t) \ m(uint64_type, uint64_t) \ m(fp8e4m3fnuz_type, migraphx::fp8::fp8e4m3fnuz) \ m(fp8e4m3fn_type, migraphx::fp8::fp8e4m3fn) \ m(fp8e5m2_type, migraphx::fp8::fp8e5m2) \ m(bf16_type, bf16) \ m(fp8e5m2fnuz_type, migraphx::fp8::fp8e5m2fnuz) // clang-format on #define MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES(x, t) x, enum type_t { MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES) tuple_type, fp4x2_type // packed fp4 contained in uint8 }; #undef MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES template struct get_type; #define MIGRAPHX_SHAPE_GENERATE_GET_TYPE(x, t) \ template \ struct get_type : std::integral_constant \ { \ }; MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_GET_TYPE) #undef MIGRAPHX_SHAPE_GENERATE_GET_TYPE template struct get_type : get_type { }; struct MIGRAPHX_EXPORT dynamic_dimension { std::size_t min = 0; std::size_t max = 0; std::set optimals{}; template static auto reflect(Self& self, F f) { return pack(f(self.min, "min"), f(self.max, "max"), f(self.optimals, "optimals")); } bool is_fixed() const; bool has_optimal() const; /** * Return a dynamic_dimension with the intersection of two dynamic_dimension ranges if * possible. */ std::optional intersection(const dynamic_dimension& other) const { auto left = std::max(this->min, other.min); auto right = std::min(this->max, other.max); if(left <= right) { return dynamic_dimension{left, right}; } return nullopt; } MIGRAPHX_EXPORT friend bool operator==(const dynamic_dimension& x, const dynamic_dimension& y); MIGRAPHX_EXPORT friend bool operator!=(const dynamic_dimension& x, const dynamic_dimension& y); MIGRAPHX_EXPORT friend std::ostream& operator<<(std::ostream& os, const dynamic_dimension& x); // compare to fixed std::size_t dimension MIGRAPHX_EXPORT friend bool operator==(const dynamic_dimension& x, const std::size_t& y); MIGRAPHX_EXPORT friend bool operator==(const std::size_t& x, const dynamic_dimension& y); MIGRAPHX_EXPORT friend bool operator!=(const dynamic_dimension& x, const std::size_t& y); MIGRAPHX_EXPORT friend bool operator!=(const std::size_t& x, const dynamic_dimension& y); // add, subtract, multiply fixed std::size_t dimension dynamic_dimension& operator+=(const std::size_t& x); dynamic_dimension& operator-=(const std::size_t& x); dynamic_dimension& operator*=(const std::size_t& x); MIGRAPHX_EXPORT friend dynamic_dimension operator+(const dynamic_dimension& x, const std::size_t& y); MIGRAPHX_EXPORT friend dynamic_dimension operator+(const std::size_t& x, const dynamic_dimension& y); MIGRAPHX_EXPORT friend dynamic_dimension operator-(const dynamic_dimension& x, const std::size_t& y); MIGRAPHX_EXPORT friend dynamic_dimension operator*(const dynamic_dimension& x, const std::size_t& y); MIGRAPHX_EXPORT friend dynamic_dimension operator*(const std::size_t& x, const dynamic_dimension& y); }; static std::string to_sizes_string(const std::vector& shapes); static const std::vector& types(); static std::string name(type_t t); static std::string cpp_type(type_t t); static bool is_integral(type_t t); static bool is_compatible(const shape& actual, const shape& expected); static bool is_unsigned(type_t t); static bool is_computable(type_t t); shape(); shape(type_t t); shape(type_t t, std::vector l); shape(type_t t, std::vector l, std::vector s); // Force all calls of the format `shape( type_t, { size_t compatibles } )` to map to // shape(type_t, std::vector l) shape(type_t t, std::initializer_list d); shape(type_t t, std::vector dims); // Construct a dynamic shape from vectors of mins, maxes, and optimals. // optimals_list is a vector of optimals that corresponds to each min and max. shape(type_t t, std::vector mins, std::vector maxes, std::vector> optimals_list); template shape(type_t t, const Range& l) : shape(t, std::vector(l.begin(), l.end())) { } template shape(type_t t, const Range1& l, const Range2& s) : shape(t, std::vector(l.begin(), l.end()), std::vector(s.begin(), s.end())) { } explicit shape(const std::vector& subs); /** * Creates an output shape with dimensions `l` and strides computed to fulfill the given * permutation. * * `t` = shape type * `l` = output dimensions * `perm` = order dimensions from slowest dimension to fastest dimension * * Example: * `t` = float_type, `l` = [2, 3, 4], `perm` = [1, 2, 0] * axis=1 to slowest dimension, axis=2 to second slowest, axis=0 to fastest * returns shape{type = float, lens = [2, 3, 4], strides = [1, 8 ,2]} */ static shape from_permutation(type_t t, const std::vector& l, const std::vector& perm); type_t type() const; const std::vector& lens() const; const std::vector& strides() const; /*! * The number of dimensions in the shape, either static or dynamic. * Same as the number of indices required to get a data value. */ std::size_t ndim() const; /*! * Return the number of elements in the tensor. */ std::size_t elements() const; /*! * Return the number of total bytes used for storage of the tensor data; includes subshapes. * For dynamic shape, returns the maximum number of bytes presuming a packed shape. */ std::size_t bytes() const; /*! * Return the size of the type of the main shape. * Returns 0 if there are subshapes. */ std::size_t type_size() const; const std::vector& dyn_dims() const; /*! * Minimum lengths for dynamic shape. * lens() for static shape. */ std::vector min_lens() const; /*! * Maximum lengths for dynamic shape. * lens() for static shape. */ std::vector max_lens() const; /*! * Optimum lengths for dynamic shape. * Empty for static shape. */ std::vector> opt_lens() const; /// Map multiple indices to space index std::size_t index(std::initializer_list l) const; /// Map multiple indices to space index std::size_t index(const std::vector& l) const; /// Map multiple indices from a range of iterator to a space index template std::size_t index(Iterator start, Iterator last) const { if(this->dynamic()) { MIGRAPHX_THROW("SHAPE: index() called on dynamic shape"); } assert(std::distance(start, last) <= this->lens().size()); assert(this->lens().size() == this->strides().size()); return std::inner_product(start, last, this->strides().begin(), std::size_t{0}); // NOLINT } /// Map element index to space index std::size_t index(std::size_t i) const; /// Map element index to multi-dimensional index std::vector multi(std::size_t idx) const; /// Map element index to multi-dimensional index and put them them into location provided by /// pointers void multi_copy(std::size_t idx, std::size_t* start, const std::size_t* end) const; /// Check if a multi-dimensional index is within bounds for the shape. bool multi_within_bounds(std::vector multi) const; /// Convert multi-dimensional index into a single element index template std::size_t single(Iterator start, Iterator last) const { if(start == last) return 0; assert(std::distance(start, last) == this->lens().size()); return *std::prev(last) + inner_product( this->lens().begin() + 1, this->lens().end(), start, std::size_t{0}, [](const auto& a, const auto& b) { return (a + b[0]) * b[1]; }, [](auto len, auto i) -> std::array { return {i, len}; }); } /// Convert multi-dimensional index into a single element index std::size_t single(const std::vector& idx) const; /// Returns true if the shape is packed (number of elements and buffer size the same) with /// no padding bool packed() const; /// Returns true if the shape has been transposed. That is the strides are not in descending /// order bool transposed() const; /// Returns true if the shape is broadcasting a dimension. That is, one of the strides are zero bool broadcasted() const; /// Returns true if the shape is in its standard format. That is, the shape is both packed and /// not transposed. bool standard() const; /// Returns true if all strides are equal to 0 (scalar tensor) bool scalar() const; /// Return true if the shape is dynamic bool dynamic() const; /// Return true if this shape or any of the sub_shapes are dynamic bool any_of_dynamic() const; /// If type is computable (can do math ops like add or divide) and has a visitor function bool computable() const; shape normalize_standard() const; shape as_standard() const; shape with_lens(type_t t, const std::vector& l) const; shape with_lens(const std::vector& l) const; shape with_type(type_t t) const; // convert the shape to an equivalent dynamic shape with empty optimals shape to_dynamic() const; // convert the shape to a static one setting any non-fixed dynamic_dimensions to x shape to_static(std::size_t x) const; MIGRAPHX_EXPORT friend bool operator==(const shape& x, const shape& y); MIGRAPHX_EXPORT friend bool operator!=(const shape& x, const shape& y); MIGRAPHX_EXPORT friend std::ostream& operator<<(std::ostream& os, const shape& x); template struct as { using type = std::conditional_t{}, int8_t, T>; type max() const { return std::numeric_limits::max(); } type min() const { return std::numeric_limits::lowest(); } type nan() const { return std::numeric_limits::quiet_NaN(); } template type operator()(U u) const { return type(u); } template type* operator()(U* u) const { return static_cast(u); } template const type* operator()(const U* u) const { return static_cast(u); } type operator()() const { return {}; } std::size_t size(std::size_t n = 1) const { return sizeof(type) * n; } bool is_integral() const { return std::is_integral{}; } bool is_signed() const { return std::is_signed{}; } bool is_unsigned() const { return std::is_unsigned{}; } template type* from(U* buffer, std::size_t n = 0) const { return reinterpret_cast(buffer) + n; } template const type* from(const U* buffer, std::size_t n = 0) const { return reinterpret_cast(buffer) + n; } type_t type_enum() const { return get_type{}; } }; template static void visit(type_t t, Visitor v, TupleVisitor tv) { switch(t) { case tuple_type: { tv(); return; } case fp4x2_type: { MIGRAPHX_THROW("fp4x2_type cannot be visited."); } #define MIGRAPHX_SHAPE_GENERATE_VISITOR_CASE(x, t) \ case x: v(as()); return; MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_VISITOR_CASE) #undef MIGRAPHX_SHAPE_GENERATE_VISITOR_CASE } MIGRAPHX_THROW("Unknown type"); } template static void visit(type_t t, Visitor v) { return visit(t, v, [] { MIGRAPHX_THROW("Tuple cannot be visited."); }); } template void visit_type(Visitors... vs) const { visit(this->type(), vs...); } template static void visit_types(Visitor v) { #define MIGRAPHX_SHAPE_GENERATE_VISITOR_ALL(x, t) v(as()); MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_VISITOR_ALL) v(as()); #undef MIGRAPHX_SHAPE_GENERATE_VISITOR_ALL } std::string type_string() const; static type_t parse_type(const std::string& s); const std::vector& sub_shapes() const; std::size_t tuple_size() const; /*! * Returns the number of elements in the data buffer. * For a dynamic shape, returns the maximum number of elements of the data buffer and assumes it * is packed. * Will clip to the maximum of size_t if overflows for dynamic shapes. */ std::size_t element_space() const; private: shape(std::shared_ptr pimpl); std::shared_ptr impl; }; /// Flatten subshapes to a single vector of non-tuple type of shapes MIGRAPHX_EXPORT std::vector flatten(const std::vector& shapes); MIGRAPHX_EXPORT void migraphx_to_value(value& v, const shape& s); MIGRAPHX_EXPORT void migraphx_from_value(const value& v, shape& s); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/shape_for_each.hpp000066400000000000000000000045621510465702400242370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_SHAPE_FOR_EACH_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_SHAPE_FOR_EACH_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * Iterates the given function over the indices from the shape in order. */ template void shape_for_each(const migraphx::shape& s, F f) { std::vector indices(s.lens().size()); const auto& index_const_ref = indices; shape ss{s.type(), s.lens()}; size_t max = ss.elements(); for(std::size_t i = 0; i < max; i++) { std::transform(ss.strides().begin(), ss.strides().end(), ss.lens().begin(), indices.begin(), [&](std::size_t stride, std::size_t len) { assert(len > 0 and stride > 0); return (i / stride) % len; }); if constexpr(std::is_invocable{}) f(index_const_ref, i); else f(index_const_ref); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/shape_transform_descriptor.hpp000066400000000000000000000164451510465702400267450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_SHAPE_TRANSFORM_DESCRIPTOR_HPP #define MIGRAPHX_GUARD_MIGRAPHX_SHAPE_TRANSFORM_DESCRIPTOR_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct operation; // The shape_transform_descriptor class is data structure to simplify shape // transformations like reshape, transpose, broadcast, etc. This is made up // of a collection of dimensions which are a collection of subdimensions. // // Each subdimension has an axis and a `len`. The `len` is the length of the // subdimension. The axis represents the axis the dimension originated. It is // represented as a vector, the first element represents the axis in the // original dimension and the additional elements are used when such // dimension is split. The axis is empty when its a broadcasted dimension, // and a hidden axis can be set if the dimension is associated with a `1` // dimension in the original shape. // // This will first record shape transformations with the `apply` method. This // will manipulate this data structure to represent how the transformation // changes the dimensions. // // For example, if we start with an initial dimensions as `[x, y, z]` then // each dimension will have one subdimension that corresponds to each // original dimension: `[[x:0]], [[y:1]], [[z:2]]`. // // When a transpose is applied we would just permutate the dimensions. // // When a reshape that would merge dimensions together then the subdimensions // are copied to the same subdimension. So if we reshape the dimensions as ` // [x*y, z]` then it would become `[[x:0], [y:1]], [[z:2]]`. If the reshape // splits the dimension then the subdimension is copied to each dimension and // the axis is updated to maintain the order. So a reshape of `[2, x/2, y, // z]` would become: `[[2:0,0]], [[x/2:0,1]], [[y:1]], [[z:2]]`. // // After recording the operators, `simplify` method is used to simplify the // data structure such as merging adjacent dimension, etc. The `generate` // method is called to generate the operators need to do this // transformation. struct MIGRAPHX_EXPORT shape_transform_descriptor { shape_transform_descriptor() = default; explicit shape_transform_descriptor(const std::vector& dims); static shape_transform_descriptor create(const std::vector& dims, const std::vector& ops); shape_transform_descriptor rebase(const std::vector& dims, bool broadcast = false) const; bool apply(const std::vector& ops); bool apply_reshape(const std::vector& rdims); bool apply_reshape_impl(const std::vector& rdims); bool apply_transpose(const std::vector& permutation); bool apply_broadcast(const std::vector& out_lens, optional axis = nullopt); void simplify(); std::size_t elements() const; std::vector generate(const std::vector& input_dims = {}) const; std::set find_broadcasted_axes() const; bool has_broadcast() const; void flatten_broadcast(); std::vector common_dims(const std::vector& input_dims = {}) const; std::size_t common_rank() const; shape_transform_descriptor to_common_from_src() const; shape_transform_descriptor to_common_from_dst() const; shape_transform_descriptor to_dst_from_common() const; shape_transform_descriptor to_src_from_common() const; std::vector> common_axes_map_from_src() const; std::vector> common_axes_map_from_dst() const; bool empty() const; std::vector lens() const; struct MIGRAPHX_EXPORT dimension { void simplify(); std::size_t len() const; struct MIGRAPHX_EXPORT sub { std::size_t len; std::vector axis = {}; // The hidden axis is used for broadcasted dimensions. The // original axis has a length of 1, but this subdimension has a // length greater then 1, so it cant be directly associated with // the axis. However, it still needs to accounted for. After we // generate the broadcast we will set the axis to the hidden // axis, and then length to 1. std::vector hidden_axis = {}; const std::vector& origin_axis() const; bool has_hidden_axis() const; void add_split_axis(std::size_t i); void expose(); void hide(); MIGRAPHX_EXPORT friend bool operator==(const sub& x, const sub& y); MIGRAPHX_EXPORT friend bool operator!=(const sub& x, const sub& y); MIGRAPHX_EXPORT friend std::ostream& operator<<(std::ostream& os, const sub& x); }; MIGRAPHX_EXPORT friend bool operator==(const dimension& x, const dimension& y); MIGRAPHX_EXPORT friend bool operator!=(const dimension& x, const dimension& y); MIGRAPHX_EXPORT friend std::ostream& operator<<(std::ostream& os, const dimension& x); std::vector subdimensions; }; MIGRAPHX_EXPORT friend bool operator==(const shape_transform_descriptor& x, const shape_transform_descriptor& y); MIGRAPHX_EXPORT friend bool operator!=(const shape_transform_descriptor& x, const shape_transform_descriptor& y); MIGRAPHX_EXPORT friend std::ostream& operator<<(std::ostream& os, const shape_transform_descriptor& x); std::vector dimensions; // Rank of the original dimensions std::size_t rank = 0; }; MIGRAPHX_EXPORT std::vector optimize_shape_transforms(const std::vector& dims, const std::vector& ops); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_SHAPE_TRANSFORM_DESCRIPTOR_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/simple_par_for.hpp000066400000000000000000000070151510465702400243060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SIMPLE_PAR_FOR_HPP #define MIGRAPHX_GUARD_RTGLIB_SIMPLE_PAR_FOR_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct joinable_thread : std::thread { template joinable_thread(Xs&&... xs) : std::thread(std::forward(xs)...) // NOLINT { } joinable_thread& operator=(joinable_thread&& other) = default; joinable_thread(joinable_thread&& other) = default; ~joinable_thread() { if(this->joinable()) this->join(); } }; template auto thread_invoke(std::size_t i, std::size_t tid, F f) -> decltype(f(i, tid)) { f(i, tid); } template auto thread_invoke(std::size_t i, std::size_t, F f) -> decltype(f(i)) { f(i); } template void simple_par_for_impl(std::size_t n, std::size_t threadsize, F f) { if(threadsize <= 1) { for(std::size_t i = 0; i < n; i++) thread_invoke(i, 0, f); } else { std::vector threads(threadsize); // Using const here causes gcc 5 to ICE #if(!defined(__GNUC__) || __GNUC__ != 5) const #endif std::size_t grainsize = std::ceil(static_cast(n) / threads.size()); std::size_t work = 0; std::size_t tid = 0; std::generate(threads.begin(), threads.end(), [=, &work, &tid] { auto result = joinable_thread([=] { std::size_t start = work; std::size_t last = std::min(n, work + grainsize); for(std::size_t i = start; i < last; i++) { thread_invoke(i, tid, f); } }); work += grainsize; ++tid; return result; }); assert(work >= n); } } template void simple_par_for(std::size_t n, std::size_t min_grain, F f) { const auto threadsize = std::min(std::thread::hardware_concurrency(), n / std::max(1, min_grain)); simple_par_for_impl(n, threadsize, f); } template void simple_par_for(std::size_t n, F f) { const int min_grain = 8; simple_par_for(n, min_grain, f); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/simplify_algebra.hpp000066400000000000000000000032421510465702400246140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_ALGEBRA_HPP #define MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_ALGEBRA_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Simplify many algebraic instructions to more efficient versions. */ struct MIGRAPHX_EXPORT simplify_algebra { std::string name() const { return "simplify_algebra"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/simplify_dyn_ops.hpp000066400000000000000000000033711510465702400246750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_DYN_OPS_HPP #define MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_DYN_OPS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Convert dynamic ops to their static version if possible. * Should be run after the split_single_dyn_dims pass. */ struct MIGRAPHX_EXPORT simplify_dyn_ops { std::string name() const { return "simplify_dyn_ops"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/simplify_qdq.hpp000066400000000000000000000033111510465702400240010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_QDQ_HPP #define MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_QDQ_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Inserts quantized operators in place of dq->quantizable_op->q * then removes remaining fake quantization (q->dq pairs) */ struct MIGRAPHX_EXPORT simplify_qdq { std::string name() const { return "simplify_qdq"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/simplify_reshapes.hpp000066400000000000000000000033601510465702400250320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_RESHAPES_HPP #define MIGRAPHX_GUARD_RTGLIB_SIMPLIFY_RESHAPES_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; /** * Eliminate redundant reshapes. */ struct MIGRAPHX_EXPORT simplify_reshapes { size_t depth = 4; bool enable_op_shape_transform_op = false; std::string name() const { return "simplify_reshapes"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/source_location.hpp000066400000000000000000000053621510465702400245000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_SOURCE_LOCATION_HPP #define MIGRAPHX_GUARD_MIGRAPHX_SOURCE_LOCATION_HPP #include #include #if defined(CPPCHECK) #define MIGRAPHX_HAS_SOURCE_LOCATION 1 #define MIGRAPHX_HAS_SOURCE_LOCATION_TS 1 #elif defined(__has_include) #if __has_include() && __cplusplus >= 202003L #define MIGRAPHX_HAS_SOURCE_LOCATION 1 #else #define MIGRAPHX_HAS_SOURCE_LOCATION 0 #endif #if __has_include() && __cplusplus >= 201103L #define MIGRAPHX_HAS_SOURCE_LOCATION_TS 1 #else #define MIGRAPHX_HAS_SOURCE_LOCATION_TS 0 #endif #else #define MIGRAPHX_HAS_SOURCE_LOCATION 0 #define MIGRAPHX_HAS_SOURCE_LOCATION_TS 0 #endif #if MIGRAPHX_HAS_SOURCE_LOCATION #include #elif MIGRAPHX_HAS_SOURCE_LOCATION_TS #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #if MIGRAPHX_HAS_SOURCE_LOCATION using source_location = std::source_location; #elif MIGRAPHX_HAS_SOURCE_LOCATION_TS using source_location = std::experimental::source_location; #else struct source_location { static constexpr source_location current() noexcept { return source_location{}; } constexpr std::uint_least32_t line() const noexcept { return 0; } constexpr std::uint_least32_t column() const noexcept { return 0; } constexpr const char* file_name() const noexcept { return ""; } constexpr const char* function_name() const noexcept { return ""; } }; #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_SOURCE_LOCATION_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/split_reduce.hpp000066400000000000000000000041311510465702400237630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_SPLIT_REDUCE_HPP #define MIGRAPHX_GUARD_MIGRAPHX_SPLIT_REDUCE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; /// For large reductions that are larger than the split_size, this pass will /// split the fused_reduce operators so that the reduction will happen across /// multiple compute units gaining better occupancy for targets with many /// compute units. Since the reduction is split across compute units, any /// elementwise operators will be split into separate operators as well due to /// needing global synchronization. struct MIGRAPHX_EXPORT split_reduce { std::size_t split_size = 8192; std::string name() const { return "split_reduce"; } void apply(module_pass_manager& mpm) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_SPLIT_REDUCE_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/split_single_dyn_dim.hpp000066400000000000000000000034371510465702400255100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_SPLIT_SINGLE_DYN_DIM_HPP #define MIGRAPHX_GUARD_RTGLIB_SPLIT_SINGLE_DYN_DIM_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * Split dynamic dimension over submodules if exactly one dimension in the parameter list is * dynamic. */ struct MIGRAPHX_EXPORT split_single_dyn_dim { std::string name() const { return "split_single_dyn_dim"; } void apply(module_pass_manager&) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/sqlite.hpp000066400000000000000000000035301510465702400226040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_SQLITE_HPP #define MIGRAPHX_GUARD_MIGRAPHX_SQLITE_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct sqlite_impl; struct MIGRAPHX_EXPORT sqlite { sqlite() = default; static sqlite read(const fs::path& p); static sqlite write(const fs::path& p); std::vector> execute(const std::string& s); private: std::shared_ptr impl; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_SQLITE_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/stream_model.hpp000066400000000000000000000317361510465702400237670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_STREAM_MODEL_HPP #define MIGRAPHX_GUARD_STREAM_MODEL_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// An interface for target-dependent model for the scheduler struct stream_model { /// Get the number of streams used in the program std::size_t get_nstream() const; /// Get stream for instruction std::size_t get_stream(instruction_ref ins) const; /// Get unique event id for instruction std::size_t get_event_id(instruction_ref ins) const; /// Returns true if instruction has a stream assignment bool has_stream(instruction_ref ins) const; /// Returns true if the instruction records the event bool is_record(instruction_ref ins) const; /// Returns true if the instruction wait on the event bool is_wait(instruction_ref ins) const; }; #else #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT stream_model { // std::size_t get_nstream() const; // std::size_t get_stream(instruction_ref ins) const; // std::size_t get_event_id(instruction_ref ins) const; // bool has_stream(instruction_ref ins) const; // bool is_record(instruction_ref ins) const; // bool is_wait(instruction_ref ins) const; }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct stream_model { private: template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(std::declval().get_nstream(), std::declval().get_stream( std::declval()), std::declval().get_event_id( std::declval()), std::declval().has_stream( std::declval()), std::declval().is_record( std::declval()), std::declval().is_wait(std::declval()), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors stream_model() = default; template < typename PrivateDetailTypeErasedT, typename = private_te_constraints, typename = typename std::enable_if< not std::is_same, stream_model>{}>::type> stream_model(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template < typename PrivateDetailTypeErasedT, typename = private_te_constraints, typename = typename std::enable_if< not std::is_same, stream_model>{}>::type> stream_model& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { stream_model rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } std::size_t get_nstream() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().get_nstream(); } std::size_t get_stream(instruction_ref ins) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().get_stream(ins); } std::size_t get_event_id(instruction_ref ins) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().get_event_id(ins); } bool has_stream(instruction_ref ins) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().has_stream(ins); } bool is_record(instruction_ref ins) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().is_record(ins); } bool is_wait(instruction_ref ins) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().is_wait(ins); } friend bool is_shared(const stream_model& private_detail_x, const stream_model& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual std::size_t get_nstream() const = 0; virtual std::size_t get_stream(instruction_ref ins) const = 0; virtual std::size_t get_event_id(instruction_ref ins) const = 0; virtual bool has_stream(instruction_ref ins) const = 0; virtual bool is_record(instruction_ref ins) const = 0; virtual bool is_wait(instruction_ref ins) const = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } std::size_t get_nstream() const override { return private_detail_te_value.get_nstream(); } std::size_t get_stream(instruction_ref ins) const override { return private_detail_te_value.get_stream(ins); } std::size_t get_event_id(instruction_ref ins) const override { return private_detail_te_value.get_event_id(ins); } bool has_stream(instruction_ref ins) const override { return private_detail_te_value.has_stream(ins); } bool is_record(instruction_ref ins) const override { return private_detail_te_value.is_record(ins); } bool is_wait(instruction_ref ins) const override { return private_detail_te_value.is_wait(ins); } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const stream_model* x) { return x->any_cast(); } template inline ValueType* any_cast(stream_model* x) { return x->any_cast(); } template inline ValueType& any_cast(stream_model& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const stream_model& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/streamutils.hpp000066400000000000000000000065561510465702400236720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_STREAMUTILS_HPP #define MIGRAPHX_GUARD_STREAMUTILS_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct stream_range_container { const T* r; stream_range_container(const T& x) : r(&x) {} friend std::ostream& operator<<(std::ostream& os, const stream_range_container& sr) { assert(sr.r != nullptr); if(not sr.r->empty()) { os << sr.r->front(); std::for_each( std::next(sr.r->begin()), sr.r->end(), [&](auto&& x) { os << ", " << x; }); } return os; } }; template inline stream_range_container stream_range(const Range& r) { return {r}; } namespace detail { template auto stream_write_value_impl(rank<1>, std::ostream& os, const T& x) -> decltype(os << x, void()) { os << x; } template auto stream_write_value_impl(rank<1>, std::ostream& os, const optional& x) { if(x.has_value()) { os << *x; } else { os << "nullopt"; } } template void stream_write_value_impl(rank<1>, std::ostream& os, const std::vector& r) { os << "{"; os << stream_range(r); os << "}"; } template auto stream_write_value_impl(rank<0>, std::ostream& os, const Range& r) -> decltype(r.begin(), r.end(), void()) { os << "{"; os << stream_range(r); os << "}"; } template {})> void stream_write_value_impl(rank<0>, std::ostream& os, const T& x) { char delim = '{'; reflect_each(x, [&](auto&& y, auto name) { os << delim; os << name << "="; stream_write_value_impl(rank<2>{}, os, y); delim = ','; }); if(delim == ',') os << "}"; } } // namespace detail template void stream_write_value(std::ostream& os, const T& x) { detail::stream_write_value_impl(rank<1>{}, os, x); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/stringutils.hpp000066400000000000000000000155551510465702400237040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_STRINGUTILS_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_STRINGUTILS_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #define MIGRAPHX_STRINGIZE_1(...) #__VA_ARGS__ #define MIGRAPHX_STRINGIZE(...) MIGRAPHX_STRINGIZE_1(__VA_ARGS__) template auto with_char(F f) { return [=](unsigned char c) -> bool { return f(c); }; } inline void replace_string_inplace(std::string& subject, const std::string& search, const std::string& replace) { size_t pos = 0; while((pos = subject.find(search, pos)) != std::string::npos) { subject.replace(pos, search.length(), replace); pos += replace.length(); } } inline std::string replace_string(std::string subject, const std::string& search, const std::string& replace) { replace_string_inplace(subject, search, replace); return subject; } inline bool ends_with(const std::string& value, const std::string& suffix) { if(suffix.size() > value.size()) return false; else return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin()); } template inline std::string join_strings(Strings strings, const std::string& delim) { auto it = strings.begin(); if(it == strings.end()) return ""; auto nit = std::next(it); return std::accumulate(nit, strings.end(), *it, [&](std::string x, std::string y) { return std::move(x) + delim + std::move(y); }); } inline std::vector split_string(const std::string& s, char delim) { std::vector elems; std::stringstream ss(s + delim); std::string item; while(std::getline(ss, item, delim)) { elems.push_back(item); } return elems; } template std::string trim(const std::string& s, F f) { auto start = std::find_if_not(s.begin(), s.end(), f); auto last = std::find_if_not(s.rbegin(), std::string::const_reverse_iterator(start), f).base(); return {start, last}; } inline std::string trim(const std::string& s) { return trim(s, [](unsigned char c) { return std::isspace(c); }); } template inline std::string transform_string(std::string s, F f) { std::transform(s.begin(), s.end(), s.begin(), f); return s; } inline std::string to_upper(std::string s) { return transform_string(std::move(s), ::toupper); } inline std::string to_lower(std::string s) { return transform_string(std::move(s), ::tolower); } inline bool starts_with(const std::string& value, const std::string& prefix) { if(prefix.size() > value.size()) return false; else return std::equal(prefix.begin(), prefix.end(), value.begin()); } inline std::string remove_prefix(std::string s, const std::string& prefix) { if(starts_with(s, prefix)) return s.substr(prefix.length()); else return s; } template inline std::string interpolate_string(const std::string& input, F f, std::string start = "${", std::string end = "}") { std::string result = ""; result.reserve(input.size()); auto it = input.begin(); while(it != input.end()) { auto next_start = std::search(it, input.end(), start.begin(), start.end()); auto next_end = std::search(next_start, input.end(), end.begin(), end.end()); result.append(it, next_start); if(next_start == input.end()) break; if(next_end == input.end()) { throw std::runtime_error("Unbalanced brackets"); } auto r = f(next_start + start.size(), next_end); result.append(r.begin(), r.end()); it = next_end + end.size(); } return result; } inline std::string interpolate_string(const std::string& input, const std::unordered_map& vars, std::string start = "${", std::string end = "}") { return interpolate_string( input, [&](auto start_it, auto last_it) { auto key = trim({start_it, last_it}); auto it = vars.find(key); if(it == vars.end()) throw std::runtime_error("Unknown key: " + key); return it->second; }, std::move(start), std::move(end)); } inline std::string to_c_id(const std::string& name, char rep = '_') { std::string id = transform_string(name, [&](auto c) { if(with_char(::isalnum)(c) or c == '_') return c; return rep; }); while(id.find("__") != std::string::npos) replace_string_inplace(id, "__", "_"); return id; } inline std::string quote_string(const std::string& str) { return "\"" + str + "\""; } template inline std::string to_string_range(Iterator start, Iterator last, const char* delim = ", ") { std::stringstream ss; if(start != last) { ss << as_number(*start); std::for_each(std::next(start), last, [&](auto&& x) { ss << delim << as_number(x); }); } return ss.str(); } template inline std::string to_string_range(const Range& r, const char* delim = ", ") { return to_string_range(r.begin(), r.end(), delim); } template inline std::string to_string_range(const std::initializer_list& r, const char* delim = ", ") { return to_string_range(r.begin(), r.end(), delim); } template inline auto to_string(const T& x) -> decltype((std::declval() << x), std::string{}) { std::stringstream ss; ss << x; return ss.str(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/support_metric.hpp000066400000000000000000000027621510465702400243700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_SUPPORT_METRIC_HPP #define MIGRAPHX_GUARD_MIGRAPHX_SUPPORT_METRIC_HPP namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { enum class support_metric { latency, throughput }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_SUPPORT_METRIC_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/supported_segments.hpp000066400000000000000000000032501510465702400252340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_SUPPORTED_SEGMENTS_HPP #define MIGRAPHX_GUARD_MIGRAPHX_SUPPORTED_SEGMENTS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct supported_segment { std::unordered_set instructions; float metric; }; using supported_segments = std::vector; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_SUPPORTED_SEGMENTS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/target.hpp000066400000000000000000000453711510465702400226020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_TARGET_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_TARGET_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct value; #ifdef DOXYGEN /// An interface for a compilation target struct target { /// A unique name used to identify the target std::string name() const; /** * @brief The transformation pass to be run during compilation. * * @param ctx This is the target-dependent context that is created by `get_context` * @param options Compiling options passed in by the user * @return The passes to be ran */ std::vector get_passes(context& ctx, const compile_options& options) const; /** * @brief Construct a context for the target. * @return The context to be used during compilation and execution. */ context get_context() const; /** * @brief Get the ranges of instructions that are supported on a target * @param module Module to check for supported instructions * @param metric Used to define how the quality of the support should be measured * @return the supported segments of the graph */ supported_segments target_is_supported(T&, const_module_ref mod, support_metric metric) const; /** * @brief copy an argument to the current target. * * @param arg Input argument to be copied to the target * @return Argument in the target. */ argument copy_to(const argument& arg) const; /** * @brief copy an argument from the current target. * * @param arg Input argument to be copied from the target * @return Argument in the host. */ argument copy_from(const argument& arg) const; /** * @brief Allocate an argument based on the input shape * * @param s Shape of the argument to be allocated in the target * @return Allocated argument in the target. */ argument allocate(const shape& s) const; }; #else template argument target_allocate(T& x, const shape&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument copy_to_target(T&, const argument& arg) { return arg; } template argument copy_from_target(T&, const argument& arg) { return arg; } template supported_segments target_find_supported(T&, const_module_ref, support_metric) { return {}; } #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct MIGRAPHX_EXPORT target { // std::string name() const; // std::vector get_passes(context& ctx, const compile_options& options) const; // context get_context() const; // (optional) supported_segments find_supported(const_module_ref mod, support_metric m) const; // (optional) argument copy_to(const argument& input) const; // (optional) argument copy_from(const argument& input) const; // (optional) argument allocate(const shape& s) const; }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct target { private: template static auto private_detail_te_default_find_supported(char, T&& private_detail_te_self, const_module_ref mod, support_metric m) -> decltype(private_detail_te_self.find_supported(mod, m)) { return private_detail_te_self.find_supported(mod, m); } template static supported_segments private_detail_te_default_find_supported(float, T&& private_detail_te_self, const_module_ref mod, support_metric m) { return target_find_supported(private_detail_te_self, mod, m); } template static auto private_detail_te_default_copy_to(char, T&& private_detail_te_self, const argument& input) -> decltype(private_detail_te_self.copy_to(input)) { return private_detail_te_self.copy_to(input); } template static argument private_detail_te_default_copy_to(float, T&& private_detail_te_self, const argument& input) { return copy_to_target(private_detail_te_self, input); } template static auto private_detail_te_default_copy_from(char, T&& private_detail_te_self, const argument& input) -> decltype(private_detail_te_self.copy_from(input)) { return private_detail_te_self.copy_from(input); } template static argument private_detail_te_default_copy_from(float, T&& private_detail_te_self, const argument& input) { return copy_from_target(private_detail_te_self, input); } template static auto private_detail_te_default_allocate(char, T&& private_detail_te_self, const shape& s) -> decltype(private_detail_te_self.allocate(s)) { return private_detail_te_self.allocate(s); } template static argument private_detail_te_default_allocate(float, T&& private_detail_te_self, const shape& s) { return target_allocate(private_detail_te_self, s); } template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv< typename std::remove_reference::type>::type; template using private_te_constraints_impl = decltype(std::declval().name(), std::declval().get_passes( std::declval(), std::declval()), std::declval().get_context(), private_detail_te_default_find_supported(char(0), std::declval(), std::declval(), std::declval()), private_detail_te_default_copy_to(char(0), std::declval(), std::declval()), private_detail_te_default_copy_from(char(0), std::declval(), std::declval()), private_detail_te_default_allocate(char(0), std::declval(), std::declval()), void()); template using private_te_constraints = private_te_constraints_impl< typename private_te_unwrap_reference>::type>; public: // Constructors target() = default; template , typename = typename std::enable_if< not std::is_same, target>{}>::type> target(PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var( std::make_shared< private_detail_te_handle_type>>( std::forward(value))) { } // Assignment template , typename = typename std::enable_if< not std::is_same, target>{}>::type> target& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { target rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT* any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } template const typename std::remove_cv::type* any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type>&>( private_detail_te_get_handle()) .private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } std::string name() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().name(); } std::vector get_passes(context& ctx, const compile_options& options) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().get_passes(ctx, options); } context get_context() const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().get_context(); } supported_segments find_supported(const_module_ref mod, support_metric m) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().find_supported(mod, m); } argument copy_to(const argument& input) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().copy_to(input); } argument copy_from(const argument& input) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().copy_from(input); } argument allocate(const shape& s) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().allocate(s); } friend bool is_shared(const target& private_detail_x, const target& private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type() {} virtual std::shared_ptr clone() const = 0; virtual const std::type_info& type() const = 0; virtual std::string name() const = 0; virtual std::vector get_passes(context& ctx, const compile_options& options) const = 0; virtual context get_context() const = 0; virtual supported_segments find_supported(const_module_ref mod, support_metric m) const = 0; virtual argument copy_to(const argument& input) const = 0; virtual argument copy_from(const argument& input) const = 0; virtual argument allocate(const shape& s) const = 0; }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value>::type* = nullptr) : private_detail_te_value(value) { } template private_detail_te_handle_type( PrivateDetailTypeErasedT value, typename std::enable_if::value, int>::type* = nullptr) noexcept : private_detail_te_value(std::move(value)) { } std::shared_ptr clone() const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } std::string name() const override { return private_detail_te_value.name(); } std::vector get_passes(context& ctx, const compile_options& options) const override { return private_detail_te_value.get_passes(ctx, options); } context get_context() const override { return private_detail_te_value.get_context(); } supported_segments find_supported(const_module_ref mod, support_metric m) const override { return private_detail_te_default_find_supported( char(0), private_detail_te_value, mod, m); } argument copy_to(const argument& input) const override { return private_detail_te_default_copy_to(char(0), private_detail_te_value, input); } argument copy_from(const argument& input) const override { return private_detail_te_default_copy_from(char(0), private_detail_te_value, input); } argument allocate(const shape& s) const override { return private_detail_te_default_allocate(char(0), private_detail_te_value, s); } PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type(std::reference_wrapper ref) : private_detail_te_handle_type(ref.get()) { } }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type& private_detail_te_get_handle() const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type& private_detail_te_get_handle() { assert(private_detail_te_handle_mem_var != nullptr); if(private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType* any_cast(const target* x) { return x->any_cast(); } template inline ValueType* any_cast(target* x) { return x->any_cast(); } template inline ValueType& any_cast(target& x) { auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType& any_cast(const target& x) { const auto* y = x.any_cast::type>(); if(y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif #endif void migraphx_to_value(value& v, const target& t); void migraphx_from_value(const value& v, target& t); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/target_assignments.hpp000066400000000000000000000043241510465702400252060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_ASSIGNMENT_HPP #define MIGRAPHX_GUARD_MIGRAPHX_ASSIGNMENT_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct target_assignments { using iterator = std::unordered_map::const_iterator; using value_type = std::pair; auto size() const { return assignments.size(); } auto& at(instruction_ref ins) const { return assignments.at(ins); } auto insert(iterator it, const std::pair& assignment) { return assignments.insert(it, assignment); } auto find(instruction_ref ins) const { return assignments.find(ins); } auto begin() const { return assignments.begin(); } auto end() const { return assignments.end(); } private: std::unordered_map assignments; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_ASSIGNMENT_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/tensor_view.hpp000066400000000000000000000157361510465702400236620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TENSOR_VIEW_HPP #define MIGRAPHX_GUARD_TENSOR_VIEW_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct tensor_view_iterator_read { T* view; auto& operator()(std::size_t n) const { assert(view != nullptr); return (*view)[n]; } }; template struct tensor_view { using value_type = T; using iterator = basic_iota_iterator>, std::size_t>; using const_iterator = basic_iota_iterator>, std::size_t>; tensor_view() : m_data(nullptr) {} tensor_view(shape s, T* d) : m_data(d), m_shape(std::move(s)) {} const shape& get_shape() const { return this->m_shape; } bool empty() const { return m_data == nullptr or m_shape.lens().empty(); } std::size_t size() const { return m_shape.elements(); } T* data() { return this->m_data; } const T* data() const { return this->m_data; } template {}...)> const T& operator()(Ts... xs) const { assert(std::vector{static_cast(xs)...} < m_shape.lens()); assert(m_shape.index({static_cast(xs)...}) < m_shape.bytes() / sizeof(T)); return m_data[m_shape.index({static_cast(xs)...})]; } template {}...)> T& operator()(Ts... xs) { assert(std::vector{static_cast(xs)...} < m_shape.lens()); assert(m_shape.index({static_cast(xs)...}) < m_shape.bytes() / sizeof(T)); return m_data[m_shape.index({static_cast(xs)...})]; } template {})> const T& operator()(Iterator start, Iterator last) const { assert(std::distance(start, last) > 0); assert(std::all_of(start, last, [](auto x) { return x >= 0; })); return m_data[m_shape.index(start, last)]; } template {})> T& operator()(Iterator start, Iterator last) { assert(std::distance(start, last) > 0); assert(std::all_of(start, last, [](auto x) { return x >= 0; })); return m_data[m_shape.index(start, last)]; } T& operator[](std::size_t i) { assert(not this->empty() and i < this->size()); return m_data[m_shape.index(i)]; } const T& operator[](std::size_t i) const { assert(not this->empty() and i < this->size()); return m_data[m_shape.index(i)]; } template auto operator[](const Range& r) -> decltype((*this)(r.begin(), r.end())) { return (*this)(r.begin(), r.end()); } template auto operator[](const Range& r) const -> decltype((*this)(r.begin(), r.end())) { return (*this)(r.begin(), r.end()); } T& front() { assert(not this->empty()); return m_data[0]; } const T& front() const { assert(not this->empty()); return m_data[0]; } T& back() { assert(not this->empty()); return m_data[m_shape.index(this->size() - 1)]; } const T& back() const { assert(not this->empty()); return m_data[m_shape.index(this->size() - 1)]; } iterator begin() { return {0, this}; } template iterator begin_at(const Range& r) { return {this->m_shape.single(r.begin(), r.end()), this}; } iterator end() { return {this->size(), this}; } const_iterator begin() const { return {0, this}; } template const_iterator begin_at(const Range& r) const { return {this->m_shape.single(r.begin(), r.end()), this}; } const_iterator end() const { return {this->size(), this}; } template std::vector to_vector() const { return std::vector(this->begin(), this->end()); } template tensor_view slice_at(std::initializer_list axes, Range&& r) { assert(std::distance(r.begin(), r.end()) == this->get_shape().ndim()); std::vector new_lens(this->get_shape().ndim(), 1); for(auto axis : axes) new_lens[axis] = this->get_shape().lens()[axis]; shape s{this->get_shape().type(), new_lens, this->get_shape().strides()}; return {s, this->data() + this->get_shape().index(r.begin(), r.end())}; } friend std::ostream& operator<<(std::ostream& os, const tensor_view& x) { if(not x.empty()) { os << as_number(x.front()); for(std::size_t i = 1; i < x.m_shape.elements(); i++) { os << ", " << as_number(x.m_data[x.m_shape.index(i)]); } } return os; } private: T* m_data; shape m_shape; }; template bool operator==(const tensor_view& x, const tensor_view& y) { if(x.get_shape() == y.get_shape()) { for(std::size_t i = 0; i < x.get_shape().elements(); i++) { if(not float_equal(x[i], y[i])) return false; } return true; } return false; } template bool operator!=(const tensor_view& x, const tensor_view& y) { return not(x == y); } template tensor_view make_view(const shape& s, T* data) { return {s, data}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/tf.hpp000066400000000000000000000047731510465702400217260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_TF_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_TF_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /// struct to pass in tf options to parser struct tf_options { bool is_nhwc = false; unsigned int batch_size = 1; /// Explicitly specify the dims of an input std::unordered_map> map_input_dims = {}; std::vector output_node_names = {}; }; /// Create a program from a tf pb file (default is nhwc format) MIGRAPHX_TF_EXPORT program parse_tf(const std::string& name, const tf_options& options = tf_options{}); /// Create a program from an tf buffer MIGRAPHX_TF_EXPORT program parse_tf_buffer(const std::string& buffer, const tf_options& options = tf_options{}); /// Create a program from tf buffer MIGRAPHX_TF_EXPORT program parse_tf_buffer(const void* data, std::size_t size, const tf_options& options = tf_options{}); MIGRAPHX_TF_EXPORT std::vector get_tf_operators(); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/time.hpp000066400000000000000000000035161510465702400222450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_TIME_HPP #define MIGRAPHX_GUARD_RTGLIB_TIME_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct timer { std::chrono::time_point start = std::chrono::steady_clock::now(); template auto record() const { auto finish = std::chrono::steady_clock::now(); return std::chrono::duration_cast(finish - start).count(); } }; template auto time(F f) { timer t{}; f(); return t.record(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/tmp_dir.hpp000066400000000000000000000037271510465702400227510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_TMP_DIR_HPP #define MIGRAPHX_GUARD_RTGLIB_TMP_DIR_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct MIGRAPHX_EXPORT tmp_dir { fs::path path; tmp_dir(std::string_view prefix = ""); tmp_dir(tmp_dir&&) = default; void execute(std::string_view cmd, const std::vector& args = {}) const; void execute(const fs::path& cmd, const std::vector& args = {}) const { execute(std::string_view{cmd.string()}, args); } tmp_dir(tmp_dir const&) = delete; tmp_dir& operator=(tmp_dir const&) = delete; ~tmp_dir(); }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/tracer.hpp000066400000000000000000000034741510465702400225720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_TRACER_HPP #define MIGRAPHX_GUARD_RTGLIB_TRACER_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct tracer { tracer() {} tracer(std::ostream& s) : os(&s) {} bool enabled() const { return os != nullptr; } template void operator()(const Ts&... xs) const { if(os != nullptr) { swallow{*os << xs...}; *os << std::endl; } } private: std::ostream* os = nullptr; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/transform_view.hpp000066400000000000000000000116071510465702400243540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_TRANSFORM_VIEW_HPP #define MIGRAPHX_GUARD_MIGRAPHX_TRANSFORM_VIEW_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace views { template struct transform_view : totally_ordered> { constexpr transform_view(Range& prng, F pf) : rng(&prng), f(std::move(pf)) {} template struct iterator : iterator_operators> { using reference = decltype(std::declval()( std::declval::reference>())); using value_type = std::decay_t; using iterator_category = typename std::iterator_traits::iterator_category; using difference_type = typename std::iterator_traits::difference_type; using pointer = std::add_pointer_t>; constexpr iterator() = default; constexpr iterator(const transform_view* pparent, BaseIterator it) : parent(pparent), current(it) { } constexpr reference operator*() const { return parent->f(*current); } template static auto increment(U& x) -> decltype(++x.current) { return ++x.current; } template static auto decrement(U& x) -> decltype(--x.current) { return --x.current; } template static auto advance(U& x, I n) -> decltype(x.current += n) { return x.current += n; } template static auto distance(const U& x, const V& y) -> decltype(x.parent == y.parent, y.current - x.current) { assert(x.parent == y.parent); return y.current - x.current; } template static auto equal(const U& x, const V& y) -> decltype(x.parent == y.parent and x.current == y.current) { return x.parent == y.parent and x.current == y.current; } private: const transform_view* parent = nullptr; BaseIterator current{}; }; template static constexpr iterator make_iterator(const transform_view* v, BaseIterator it) { return {v, it}; } constexpr auto begin() const { return make_iterator(this, std::begin(base())); } constexpr auto end() const { return make_iterator(this, std::end(base())); } constexpr auto begin() { return make_iterator(this, std::begin(base())); } constexpr auto end() { return make_iterator(this, std::end(base())); } constexpr Range& base() { return *rng; } constexpr const Range& base() const { return *rng; } template constexpr bool operator==(const transform_view& b) const { return std::equal(this->begin(), this->end(), b.begin(), b.end()); } template constexpr bool operator<(const transform_view& b) const { return std::lexicographical_compare(this->begin(), this->end(), b.begin(), b.end()); } private: Range* rng = nullptr; F f; }; template auto transform(Range& rng, F f) { return transform_view(rng, std::move(f)); } } // namespace views } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_TRANSFORM_VIEW_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/truncate_float.hpp000066400000000000000000000034451510465702400243220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_TRUNCATE_FLOAT_HPP #define MIGRAPHX_GUARD_RTGLIB_TRUNCATE_FLOAT_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct program; struct module; /** * quantize a program to fp */ struct MIGRAPHX_EXPORT truncate_float_pass { std::vector ins_names = {"all"}; shape::type_t float_type = shape::float_type; std::string name() const { return "truncate_float"; } void apply(module& m) const; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/tune_axis.hpp000066400000000000000000000032731510465702400233060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_OPERATORS_TUNE_AXIS_HPP #define MIGRAPHX_GUARD_OPERATORS_TUNE_AXIS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { inline int tune_axis(int n_dim, int axis, const std::string& name = "OPERATOR") { if(axis < 0) axis += n_dim; if(axis < 0 or axis >= n_dim) MIGRAPHX_THROW(to_upper(name) + ": axis is out of range."); return axis; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/type_name.hpp000066400000000000000000000046261510465702400232730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_TYPE_NAME_HPP #define MIGRAPHX_GUARD_RTGLIB_TYPE_NAME_HPP #include #include namespace private_migraphx_detail { // Type is computed in a private namespace to avoid the compiler removing // namespaces if the type resides in the migraphx namespace template std::string compute_type_name() { const char parameter_name[] = "PrivateMigraphTypeNameProbe ="; // NOLINT std::string name = __PRETTY_FUNCTION__; auto begin = name.find(parameter_name) + sizeof(parameter_name); #if(defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 4 && __GNUC_MINOR__ < 7) auto length = name.find_last_of(",") - begin; #else auto length = name.find_first_of("];", begin) - begin; #endif return name.substr(begin, length); } } // namespace private_migraphx_detail namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template const std::string& get_type_name() { static const std::string name = private_migraphx_detail::compute_type_name(); return name; } template const std::string& get_type_name(const T&) { return migraphx::get_type_name(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/type_traits.hpp000066400000000000000000000066711510465702400236630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_TYPE_TRAITS_HPP #define MIGRAPHX_GUARD_RTGLIB_TYPE_TRAITS_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #define MIGRAPHX_DETAIL_DEFINE_TRAIT(trait) \ template \ struct trait : std::trait \ { \ }; #define MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(trait, T) \ template <> \ struct trait : std::true_type \ { \ }; MIGRAPHX_DETAIL_DEFINE_TRAIT(is_floating_point); MIGRAPHX_DETAIL_DEFINE_TRAIT(is_arithmetic); MIGRAPHX_DETAIL_DEFINE_TRAIT(is_signed); MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_floating_point, half) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_signed, half) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_arithmetic, half) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_floating_point, bf16) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_signed, bf16) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_arithmetic, bf16) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_floating_point, migraphx::fp8::fp8e4m3fnuz) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_signed, migraphx::fp8::fp8e4m3fnuz) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_arithmetic, migraphx::fp8::fp8e4m3fnuz) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_floating_point, migraphx::fp8::fp8e5m2fnuz) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_signed, migraphx::fp8::fp8e5m2fnuz) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_arithmetic, migraphx::fp8::fp8e5m2fnuz) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_floating_point, migraphx::fp8::fp8e4m3fn) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_signed, migraphx::fp8::fp8e4m3fn) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_arithmetic, migraphx::fp8::fp8e4m3fn) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_floating_point, migraphx::fp8::fp8e5m2) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_signed, migraphx::fp8::fp8e5m2) MIGRAPHX_DETAIL_EXTEND_TRAIT_FOR(is_arithmetic, migraphx::fp8::fp8e5m2) template using accumulator_type = std::conditional_t{}, double, std::conditional_t{}, std::int64_t, std::uint64_t>>; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/unfold.hpp000066400000000000000000000122101510465702400225650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_UNFOLD_HPP #define MIGRAPHX_GUARD_MIGRAPHX_UNFOLD_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct unfold_range : iterator_operators> { unfold_range(std::optional pz, F pf, G pg) : z(std::move(pz)), f(std::move(pf)), g(std::move(pg)) { } struct iterator : iterator_operators { using reference = decltype(std::declval()(std::declval())); using value_type = std::decay_t; using difference_type = std::ptrdiff_t; using iterator_category = std::forward_iterator_tag; using pointer = std::add_pointer_t>; iterator() = default; iterator(const unfold_range* pparent, std::optional pstate) : parent(pparent), state(std::move(pstate)) { } reference operator*() const { return parent->f(*state); } template static void increment(U& x) { x.state = x.parent->g(*x.state); } template static auto equal(const U& x, const V& y) { return x.parent == y.parent and x.state == y.state; } const unfold_range* parent = nullptr; std::optional state = std::nullopt; }; iterator begin() const { return iterator{this, z}; } iterator end() const { return iterator{this, std::nullopt}; } private: std::optional z; F f; G g; }; /** * @brief Returns a range which generates a sequence by repeatedly applying a step function and * projecting each state. * * The `unfold` function creates a range starting from an initial state `z`. At each step, the range * yields `f(state)` as the sequence element, and advances the state by applying `g(state)`. If * `g(state)` returns `std::nullopt`, the sequence ends. * * - The range's value type is the result of `f(State)`. * - The sequence begins with the initial state `z`. * - At each iteration, the current element is `f(state)`, and the next state is computed by * `g(state)`. * - When `g(state)` returns `std::nullopt`, the range ends (the end iterator is reached). * * This function is analogous to Haskell's `unfoldr`, but instead of returning a pair `(a, b)` or * `Nothing`, the generator function `g` here is only responsible for producing the next state: on * each step, `f(state)` produces the value, and `g(state)` produces the next state or * `std::nullopt` to stop. * * Example: * auto rng = unfold(1, [](int x) { return x; }, [](int x) -> std::optional { * if (x < 5) return x+1; * return std::nullopt; * }); * // rng yields 1, 2, 3, 4, 5 * * @param z The initial state. * @param f Function to apply to the state to generate the value for each element. * @param g Function to step to the next state. Returns std::optional; if std::nullopt is * returned, iteration stops. * @return An input range whose iterator yields values of type `decltype(f(z))`. */ template auto unfold(State z, F f, G g) { return unfold_range(std::move(z), std::move(f), std::move(g)); } template auto unfold(std::nullopt_t z, F f, G g) { return unfold_range(std::move(z), std::move(f), std::move(g)); } template auto unfold(State z, G g) { return unfold(std::move(z), [](const auto& x) -> const auto& { return x; }, std::move(g)); } template auto unfold(std::nullopt_t z, G g) { return unfold(z, [](const auto& x) -> const auto& { return x; }, std::move(g)); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_UNFOLD_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/utility_operators.hpp000066400000000000000000000115731510465702400251120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_UTILITY_OPERATORS_HPP #define MIGRAPHX_GUARD_MIGRAPHX_UTILITY_OPERATORS_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template struct equality_comparable { struct private_ops { template {})> static constexpr auto equal1(const U& x, const X& y) MIGRAPHX_RETURNS(x == y); template {})> static constexpr auto equal2(const T& x, const U& y) MIGRAPHX_RETURNS(x.operator==(y)); }; friend constexpr auto operator!=(const X& x, const X& y) { return not(private_ops::equal1(x, y)); } template friend constexpr auto operator==(const U& x, const T& y) MIGRAPHX_RETURNS(private_ops::equal2(y, x)); template friend constexpr auto operator!=(const U& x, const T& y) MIGRAPHX_RETURNS(not(private_ops::equal2(y, x))); }; template struct less_than_comparable { struct private_ops { template {})> static constexpr auto less1(const U& x, const X& y) MIGRAPHX_RETURNS(x < y); template {})> static constexpr auto less2(const T& x, const U& y) MIGRAPHX_RETURNS(x.operator<(y)); template {})> static constexpr auto greater2(const T& x, const U& y) MIGRAPHX_RETURNS(x.operator>(y)); }; friend constexpr bool operator>(const X& x, const X& y) { return private_ops::less1(y, x); } friend constexpr bool operator<=(const X& x, const X& y) { return not(private_ops::less1(y, x)); } friend constexpr bool operator>=(const X& x, const X& y) { return not(private_ops::less1(x, y)); } template friend constexpr auto operator<=(const T& x, const U& y) MIGRAPHX_RETURNS(not(private_ops::greater2(x, y))); template friend constexpr auto operator>=(const T& x, const U& y) MIGRAPHX_RETURNS(not(private_ops::less2(x, y))); template friend constexpr auto operator<(const U& x, const T& y) MIGRAPHX_RETURNS(private_ops::greater2(y, x)); template friend constexpr auto operator>(const U& x, const T& y) MIGRAPHX_RETURNS(private_ops::less2(y, x)); template friend constexpr auto operator<=(const U& x, const T& y) MIGRAPHX_RETURNS(not(private_ops::less2(y, x))); template friend constexpr auto operator>=(const U& x, const T& y) MIGRAPHX_RETURNS(not(private_ops::greater2(y, x))); }; template struct equivalence { using private_ops = typename less_than_comparable::private_ops; friend constexpr auto operator==(const X& x, const X& y) { return not private_ops::less1(x, y) and not private_ops::less1(y, x); } template friend constexpr auto operator==(const U& x, const T& y) MIGRAPHX_RETURNS(not private_ops::less2(y, x) and not private_ops::greater2(y, x)); template friend constexpr auto operator==(const T& x, const U& y) MIGRAPHX_RETURNS(not private_ops::less2(x, y) and not private_ops::greater2(x, y)); }; template struct totally_ordered : equality_comparable, less_than_comparable { }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_UTILITY_OPERATORS_HPP ROCm-AMDMIGraphX-46524e8/src/include/migraphx/value.hpp000066400000000000000000000400331510465702400224160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_VALUE_HPP #define MIGRAPHX_GUARD_RTGLIB_VALUE_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct value_base_impl; template struct value_converter { template static auto apply(const std::string& x) -> decltype((std::declval() >> std::declval()), To{}) { To result; std::stringstream ss; ss.str(x); ss >> result; if(ss.fail()) throw std::runtime_error("Failed to parse: " + x); return result; } template {})> static To apply(const From& x) { return To(x); } }; template struct value_converter{})> { template static auto apply(const From& x) -> decltype(static_cast(value_converter>::apply(x))) { return static_cast(value_converter>::apply(x)); } }; template <> struct value_converter { static const std::string& apply(const std::string& x) { return x; } template static auto apply(const From& x) -> decltype(std::declval() << x, std::string()) { std::stringstream ss; ss << x; if(ss.fail()) throw std::runtime_error("Failed to parse"); return ss.str(); } }; template struct value_converter> { template static auto apply(const std::pair& x) -> decltype(std::pair(x.first, value_converter::apply(x.second))) { return std::pair(x.first, value_converter::apply(x.second)); } }; template To try_convert_value(const From& x); namespace detail { template To try_convert_value_impl(rank<1>, const std::pair& x) { return try_convert_value(x.second); } template auto try_convert_value_impl(rank<2>, const From& x) -> decltype(value_converter::apply(x)) { return value_converter::apply(x); } template {})> To try_convert_value_impl(rank<3>, std::nullptr_t) { MIGRAPHX_THROW("Incompatible values: null -> " + get_type_name()); } template To try_convert_value_impl(rank<0>, const From& x) { MIGRAPHX_THROW("Incompatible values: " + get_type_name(x) + " -> " + get_type_name()); } } // namespace detail template To try_convert_value(const From& x) { return detail::try_convert_value_impl(rank<3>{}, x); } struct MIGRAPHX_EXPORT value { // clang-format off #define MIGRAPHX_VISIT_VALUE_TYPES(m) \ m(int64, std::int64_t) \ m(uint64, std::uint64_t) \ m(float, double) \ m(string, std::string) \ m(bool, bool) \ m(binary, value::binary) // clang-format on enum type_t { #define MIGRAPHX_VALUE_GENERATE_ENUM_TYPE(vt, cpp_type) vt##_type, MIGRAPHX_VISIT_VALUE_TYPES(MIGRAPHX_VALUE_GENERATE_ENUM_TYPE) object_type, array_type, null_type #undef MIGRAPHX_VALUE_GENERATE_ENUM_TYPE }; using iterator = value*; using const_iterator = const value*; using value_type = value; using key_type = std::string; using mapped_type = value; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; using array = std::vector; using object = std::unordered_map; struct binary : std::vector { using base = std::vector; binary() {} template ().begin()) == 1)> explicit binary(const Container& c) : base(c.begin(), c.end()) { } template binary(T* data, std::size_t s) : base(data, data + s) { } explicit binary(std::size_t s) : base(s) {} friend std::ostream& operator<<(std::ostream& os, const binary& obj) { os << "{binary_object: " << obj.size() << "}"; return os; } }; value() = default; value(const value& rhs); value& operator=(value rhs); value(const std::string& pkey, const value& rhs); value(const std::initializer_list& i); value(const std::vector& v, bool array_on_empty = true); value(const std::unordered_map& m); value(const std::string& pkey, const std::vector& v, bool array_on_empty = true); value(const std::string& pkey, const std::unordered_map& m); value(const std::string& pkey, std::nullptr_t); value(std::nullptr_t); value(const char* i); value(const std::string& pkey, const char* i); #define MIGRAPHX_VALUE_GENERATE_DECL_METHODS(vt, cpp_type) \ value(cpp_type i); \ value(const std::string& pkey, cpp_type i); \ value& operator=(cpp_type rhs); \ bool is_##vt() const; \ const cpp_type& get_##vt() const; \ const cpp_type* if_##vt() const; MIGRAPHX_VISIT_VALUE_TYPES(MIGRAPHX_VALUE_GENERATE_DECL_METHODS) template using literal_to_string = std::conditional_t<(std::is_convertible{} and std::is_convertible{}), std::string, T>; template using pick_numeric = std::conditional_t< std::is_floating_point{}, double, std::conditional_t{}, std::int64_t, std::conditional_t{}, std::uint64_t, T>>>; template using pick = pick_numeric{}, std::underlying_type, std::enable_if>::type>; template using is_pickable = bool_c<((std::is_arithmetic{} or std::is_enum{}) and not std::is_pointer{})>; template using range_value = std::decay_t().end(), *std::declval().begin())>; template using is_generic_range = bool_c<(std::is_convertible, value>{} and not std::is_convertible{} and not std::is_convertible{})>; template {})> value(const T& r) : value(from_values(r)) { } template {})> value(const std::string& pkey, const T& r) : value(pkey, from_values(r)) { } template {})> value(T i) : value(static_cast>(i)) { } template {})> value(const std::string& pkey, T i) : value(pkey, static_cast>(i)) { } template value(const std::pair& p) : value(p.first, p.second) { } template {})> value& operator=(T rhs) { return *this = static_cast>(rhs); // NOLINT } template {})> value& operator=(const T& rhs) { return *this = from_values(std::move(rhs)); // NOLINT } value& operator=(const char* c); value& operator=(std::nullptr_t); value& operator=(const std::initializer_list& i); bool is_array() const; const std::vector& get_array() const; const std::vector* if_array() const; bool is_object() const; const std::vector& get_object() const; const std::vector* if_object() const; bool is_null() const; const std::string& get_key() const; value* find(const std::string& pkey); const value* find(const std::string& pkey) const; bool contains(const std::string& pkey) const; std::size_t size() const; bool empty() const; const value* data() const; value* data(); value* begin(); const value* begin() const; value* end(); const value* end() const; value& front(); const value& front() const; value& back(); const value& back() const; value& at(std::size_t i); const value& at(std::size_t i) const; value& at(const std::string& pkey); const value& at(const std::string& pkey) const; value& operator[](std::size_t i); const value& operator[](std::size_t i) const; value& operator[](const std::string& pkey); void clear(); void resize(std::size_t n); void resize(std::size_t n, const value& v); std::pair insert(const value& v); value* insert(const value* pos, const value& v); template std::pair emplace(Ts&&... xs) { return insert(value(std::forward(xs)...)); } template value* emplace(const value* pos, Ts&&... xs) { return insert(pos, value(std::forward(xs)...)); } void push_back(const value& v) { insert(end(), v); } void push_front(const value& v) { insert(begin(), v); } value with_key(const std::string& pkey) const; value without_key() const; template void visit(Visitor v) const { switch(this->get_type()) { case null_type: { std::nullptr_t null{}; if(this->key.empty()) v(null); else v(std::make_pair(this->get_key(), std::ref(null))); return; } #define MIGRAPHX_VALUE_GENERATE_CASE(vt, cpp_type) \ case vt##_type: { \ if(this->key.empty()) \ v(this->get_##vt()); \ else \ v(std::make_pair(this->get_key(), std::ref(this->get_##vt()))); \ return; \ } MIGRAPHX_VISIT_VALUE_TYPES(MIGRAPHX_VALUE_GENERATE_CASE) MIGRAPHX_VALUE_GENERATE_CASE(array, ) MIGRAPHX_VALUE_GENERATE_CASE(object, ) } MIGRAPHX_THROW("Unknown type"); } // Visit value without key template void visit_value(Visitor v) const { switch(this->get_type()) { case null_type: { std::nullptr_t null{}; v(null); return; } #define MIGRAPHX_VALUE_GENERATE_CASE_VALUE(vt, cpp_type) \ case vt##_type: { \ v(this->get_##vt()); \ return; \ } MIGRAPHX_VISIT_VALUE_TYPES(MIGRAPHX_VALUE_GENERATE_CASE_VALUE) MIGRAPHX_VALUE_GENERATE_CASE_VALUE(array, ) MIGRAPHX_VALUE_GENERATE_CASE_VALUE(object, ) } MIGRAPHX_THROW("Unknown type"); } template To to() const { To result; this->visit([&](const auto& y) { result = try_convert_value(y); }); return result; } template literal_to_string value_or(const To& default_value) const { if(this->is_null()) return default_value; return to>(); } template std::vector to_vector() const { std::vector result; const auto& values = is_object() ? get_object() : get_array(); result.reserve(values.size()); std::transform(values.begin(), values.end(), std::back_inserter(result), [&](const auto& v) { return v.template to(); }); return result; } template literal_to_string get(const std::string& pkey, const To& default_value) const { const auto* v = find(pkey); if(v == this->end()) return default_value; return v->to>(); } template std::vector get(const std::string& pkey, const std::vector& default_value) const { const auto* v = find(pkey); if(v == this->end()) return default_value; return v->to_vector(); } template std::vector> get(const std::string& pkey, const std::initializer_list& default_value) const { return get(pkey, std::vector>{default_value.begin(), default_value.end()}); } MIGRAPHX_EXPORT friend bool operator==(const value& x, const value& y); MIGRAPHX_EXPORT friend bool operator!=(const value& x, const value& y); MIGRAPHX_EXPORT friend bool operator<(const value& x, const value& y); MIGRAPHX_EXPORT friend bool operator<=(const value& x, const value& y); MIGRAPHX_EXPORT friend bool operator>(const value& x, const value& y); MIGRAPHX_EXPORT friend bool operator>=(const value& x, const value& y); MIGRAPHX_EXPORT friend std::ostream& operator<<(std::ostream& os, const value& d); std::size_t hash() const; void debug_print(bool show_type = false) const; type_t get_type() const; const std::shared_ptr& get_impl() const; private: template std::vector from_values(const T& r) { std::vector v; std::transform( r.begin(), r.end(), std::back_inserter(v), [&](auto&& e) { return value(e); }); return v; } std::shared_ptr x; std::string key; }; } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx namespace std { template <> struct hash { using argument_type = migraphx::value; using result_type = std::size_t; result_type operator()(const migraphx::value& x) const { return x.hash(); } }; } // namespace std #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/verify.hpp000066400000000000000000000173771510465702400226250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_VERIFY_HPP #define MIGRAPHX_GUARD_VERIFY_HPP #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_VERIFY_ENABLE_ALLCLOSE) namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace verify { // Compute the value of a range template using range_value = std::decay_t().begin())>; struct sum_fn { template auto operator()(T x, U y) const { return x + y; } }; static constexpr sum_fn sum{}; struct max_fn { template static T id(T x) { return x; } template auto operator()(T x, U y) const { return x > y ? x : y; } }; static constexpr max_fn max{}; namespace abs_diff_detail { using std::fabs; struct fn { template auto operator()(T x, U y) const { return fabs(x - y); } }; } // namespace abs_diff_detail static constexpr abs_diff_detail::fn abs_diff{}; struct not_finite_fn { template bool operator()(T x) const { return not std::isfinite(static_cast(x)); } }; static constexpr not_finite_fn not_finite{}; struct compare_mag_fn { template bool operator()(T x, U y) const { return std::fabs(x) < std::fabs(y); } }; static constexpr compare_mag_fn compare_mag{}; struct square_diff_fn { template double operator()(T x, U y) const { return (x - y) * (x - y); } }; static constexpr square_diff_fn square_diff{}; template bool range_empty(R1&& r1) { return r1.begin() == r1.end(); } template auto range_distance(R1&& r1) { return std::distance(r1.begin(), r1.end()); } template bool range_zero(R1&& r1) { return std::all_of(r1.begin(), r1.end(), [](auto x) { return float_equal(x, 0); }); } template T range_product(R1&& r1, R2&& r2, T state, Reducer r, Product p) { return std::inner_product(r1.begin(), r1.end(), r2.begin(), state, r, p); } template std::size_t mismatch_idx(R1&& r1, R2&& r2, Compare compare) { auto p = std::mismatch(r1.begin(), r1.end(), r2.begin(), compare); return std::distance(r1.begin(), p.first); } template long find_idx(R1&& r1, Predicate p) { auto it = std::find_if(r1.begin(), r1.end(), p); if(it == r1.end()) return -1; else return std::distance(r1.begin(), it); } template double max_diff(R1&& r1, R2&& r2) { return range_product(r1, r2, 0.0, max, abs_diff); } template std::size_t mismatch_diff(R1&& r1, R2&& r2, T diff) { return mismatch_idx(r1, r2, [&](auto x, auto y) { auto d = abs_diff(x, y); return float_equal(d, diff); }); } template double rms_range(const R1& r1, const R2& r2) { std::size_t n = range_distance(r1); if(n == range_distance(r2)) { double square_difference = range_product(r1, r2, 0.0, sum_fn{}, square_diff); double mag1 = *std::max_element(r1.begin(), r1.end(), compare_mag); double mag2 = *std::max_element(r2.begin(), r2.end(), compare_mag); double mag = std::max({std::fabs(mag1), std::fabs(mag2), std::numeric_limits::min()}); return std::sqrt(square_difference) / (std::sqrt(n) * mag); } else return std::numeric_limits>::max(); } template double get_rms_tol(const R&, std::size_t tolerance = 80) { return std::numeric_limits>::epsilon() * tolerance; } /* C++ doesn't support named arguments, this is just wrapper that helps distinguish between actual results v/s expected results arguments. */ template struct expected { expected() = default; explicit expected(const T& input) : x(&input) {} const T& data() const { assert(x != nullptr); return *x; } private: const T* x = nullptr; }; // deduction guide for templated expected class template expected(const T&) -> expected; struct tolerance { double rms_tol = 0.001; double atol = 0.001; double rtol = 0.001; }; /* MIGraphX implementation of numpy's np.allclose() which checks if elementwise absolute diff is within tolerance using this formula: abs(a - b) < atol + rtol(abs(b)) */ template bool allclose(const R1& r1, const R2& r2, tolerance tols) { std::size_t n = range_distance(r1); if(n == range_distance(r2)) { auto idx = mismatch_idx(r1, r2, [&](auto x, auto y) { return abs_diff(double(x), double(y)) < tols.atol + tols.rtol * std::abs(double(y)); }); return idx >= range_distance(r1); } return false; } template bool verify_rms_range(const R1& r1, const R2& r2, std::size_t tolerance = 80, double* out_rms_error = nullptr) { double threshold = get_rms_tol(r1, tolerance); auto error = rms_range(r1, r2); if(out_rms_error != nullptr) *out_rms_error = error; return error <= threshold; } template bool verify_range_with_tolerance(const R1& r1, const expected& r2, tolerance tols = tolerance{}, double* out_rms_error = nullptr) { auto rms_error = rms_range(r1, r2.data()); // disable ewise_verify by default for now, it requires lot of tests to be fixed bool ewise_verify = true; if(enabled(MIGRAPHX_VERIFY_ENABLE_ALLCLOSE{})) { ewise_verify = allclose(r1, r2.data(), tols); } if(out_rms_error != nullptr) *out_rms_error = rms_error; return rms_error <= tols.rms_tol and ewise_verify; } // expected argument should be passed as second, but if it is passed as the first by mistake then // flip the order template bool verify_range_with_tolerance(const expected& r1, const R2& r2, tolerance tols = tolerance{}, double* out_rms_error = nullptr) { return verify_rms_range(r2, r1, tols, out_rms_error); } } // namespace verify } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/include/migraphx/verify_args.hpp000066400000000000000000000040261510465702400236240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_VERIFY_ARGS_HPP #define MIGRAPHX_GUARD_RTGLIB_VERIFY_ARGS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_EXPORT bool verify_args(const std::string& name, const argument& target_arg, const verify::expected& ref_arg, verify::tolerance); MIGRAPHX_EXPORT bool verify_args_with_tolerance(const std::string& name, const argument& target_arg, const verify::expected& ref_arg, std::size_t tolerance = 80); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/inline_module.cpp000066400000000000000000000046531510465702400206660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static void inline_submodule(module& m, instruction_ref ins, bool cond) { const auto& mod_inputs = ins->module_inputs(); module_ref smod = cond ? mod_inputs.at(0) : mod_inputs.at(1); auto mod_outputs = m.insert_instructions(ins, smod); auto ins_outputs = ins->outputs(); assert(mod_outputs.size() >= ins_outputs.size()); for(const auto& out : ins_outputs) { auto val = out->get_operator().to_value(); assert(val.contains("index")); auto index = val.at("index").to(); m.replace_instruction(out, mod_outputs.at(index)); } } void inline_module::apply(module& m) const { for(auto ins : iterator_for(m)) { if(ins->name() != "if") continue; auto arg_cond = ins->inputs().front()->eval(); if(not arg_cond.empty()) { bool cond = arg_cond.at(); inline_submodule(m, ins, cond); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/insert_pad.cpp000066400000000000000000000107531510465702400201710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static void update_op(const instruction_ref& input, const instruction_ref& ins, module& m) { auto op = ins->get_operator(); auto val = op.to_value(); auto op_padding = val.at("padding").to_vector(); // skip if shape is dynamic if(input->get_shape().dynamic()) { return; } auto kdims = input->get_shape().lens().size() - 2; if(std::equal(op_padding.begin(), op_padding.begin() + kdims, op_padding.begin() + kdims, op_padding.end())) return; std::vector padding(input->get_shape().lens().size() * 2, 0); std::vector pads_l(op_padding.begin(), op_padding.begin() + kdims); std::vector pads_r(op_padding.begin() + kdims, op_padding.end()); op_padding = std::vector(kdims * 2, 0); op.from_value({{"padding", op_padding}}); std::copy(pads_l.begin(), pads_l.end(), padding.begin() + 2); std::copy(pads_r.begin(), pads_r.end(), padding.begin() + kdims + 2 + 2); auto pad_op = m.insert_instruction(ins, op::pad{padding}, input); auto new_inputs = ins->inputs(); new_inputs.front() = pad_op; m.replace_instruction(ins, op, new_inputs); } static void update_pooling(const instruction_ref& input, const instruction_ref& ins, module& m) { auto op = any_cast(ins->get_operator()); if(op.mode == op::pooling_mode::average) { return; } auto kdims = input->get_shape().ndim() - 2; if(std::equal(op.padding.begin(), op.padding.begin() + kdims, op.padding.begin() + kdims, op.padding.end())) return; std::vector padding(input->get_shape().ndim() * 2, 0); std::vector pads_l(op.padding.begin(), op.padding.begin() + kdims); std::vector pads_r(op.padding.begin() + kdims, op.padding.end()); op.padding = std::vector(kdims * 2, 0); std::copy(pads_l.begin(), pads_l.end(), padding.begin() + 2); std::copy(pads_r.begin(), pads_r.end(), padding.begin() + kdims + 2 + 2); float pad_val = 0.0f; // for the lpnorm if(op.mode == op::pooling_mode::max) { // maxpool uses lowest value for padding pad_val = std::numeric_limits::lowest(); } auto pad_op = m.insert_instruction(ins, op::pad{padding, pad_val}, input); auto new_inputs = ins->inputs(); new_inputs.front() = pad_op; m.replace_instruction(ins, op, new_inputs); } void insert_pad::apply(module& m) const { for(auto ins : iterator_for(m)) { const std::string& op_name = ins->name(); if(not contains(ops, op_name)) continue; auto input = ins->inputs().front(); if(op_name == "convolution" or op_name == "im2col") update_op(input, ins, m); else if(op_name == "pooling") update_pooling(input, ins, m); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/instruction.cpp000066400000000000000000000511761510465702400204260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template static auto equal_to(const T& x) { return [&](const T& y) { return std::equal_to{}(x, y); }; } instruction::instruction(operation o, shape r, std::vector args) : op(std::move(o)), result(std::move(r)), arguments(std::move(args)) { } instruction::instruction(operation o, shape r, std::vector args, std::vector modules) : op(std::move(o)), result(std::move(r)), arguments(std::move(args)), module_args(std::move(modules)) { } instruction::instruction(literal l) : op(builtin::literal{}), result(l.get_shape()), lit(std::move(l)) { } struct replace_shape_order { instruction_ref start; std::size_t location(instruction_ref x) const { return std::distance(start, x); } bool operator()(instruction_ref x, instruction_ref y) const { return location(x) > location(y); } }; void instruction::replace(const shape& r) { if(r != result) { result = r; if(output.empty()) { return; } auto start = std::find_if(output.front()->inputs().begin(), output.front()->inputs().end(), [&](instruction_ref x) { return this == as_address(x); }); assert(as_address(*start) == this); std::priority_queue, replace_shape_order> q( output.begin(), output.end(), replace_shape_order{*start}); while(not q.empty()) { instruction_ref ins = q.top(); q.pop(); assert(ins->name() == "@return" or ins->name().front() != '@'); shape new_r = compute_shape(ins->op, ins->arguments, ins->module_args); if(new_r != ins->result) { ins->result = new_r; std::copy(ins->output.begin(), ins->output.end(), migraphx::push_inserter(q)); } } } } void instruction::replace(operation o) { normalized = false; op = std::move(o); recompute_shape(); } void instruction::recompute_shape() { replace(compute_shape(op, arguments, module_args)); } void instruction::clear_arguments() { for(auto&& arg : arguments) { arg->remove_output(*this); } arguments.clear(); module_args.clear(); } bool operator==(const instruction& i, instruction_ref ref) { return std::addressof(i) == std::addressof(*ref); } bool instruction::valid(instruction_ref start, bool check_order) const { return valid() and std::all_of(arguments.begin(), arguments.end(), [&](instruction_ref i) { auto self = std::find(i->outputs().begin(), i->outputs().end(), *this); bool ret = self != i->outputs().end(); if(check_order) { // check arguments for this instruction before this instruction ret = ret and (std::distance(start, i) < std::distance(start, *self)); } return ret; }); } bool instruction::valid() const { shape computed; if(op.name() == "@literal") { computed = lit.get_shape(); } else if(op.name() == "@param") { computed = result; } else { try { computed = compute_shape(op, arguments, module_args); } catch(migraphx::exception&) { return false; } } return (result == computed) and std::all_of(output.begin(), output.end(), [&](instruction_ref i) { return std::find(i->inputs().begin(), i->inputs().end(), *this) != i->inputs().end(); }); } const shape& instruction::get_shape() const { return result; } const literal& instruction::get_literal() const { assert(op.name() == "@literal"); return lit; } const operation& instruction::get_operator() const { return op; } std::string instruction::name() const { return op.name(); } const std::vector& instruction::inputs() const { return arguments; } const std::vector& instruction::module_inputs() const { return module_args; } const std::vector& instruction::outputs() const { return output; } bool operator==(const instruction& x, const instruction& y) { if(not std::equal(x.arguments.begin(), x.arguments.end(), y.arguments.begin(), y.arguments.end(), std::equal_to{})) return false; if(std::tie(x.result, x.op, x.module_args) != std::tie(y.result, y.op, y.module_args)) return false; if(x.name() == "@literal") return x.lit == y.lit; return true; } bool operator!=(const instruction& x, const instruction& y) { return not(x == y); } bool operator==(instruction_ref ref, const instruction& i) { return i == ref; } bool operator!=(const instruction& i, instruction_ref ref) { return not(i == ref); } bool operator!=(instruction_ref ref, const instruction& i) { return not(i == ref); } void instruction::add_output(instruction_ref ins) { if(std::find_if(output.begin(), output.end(), equal_to(ins)) == output.end()) output.push_back(ins); } void instruction::backreference(instruction_ref ref) { for(auto&& arg : ref->inputs()) arg->add_output(ref); } void instruction::replace_argument(instruction_ref ins, instruction_ref old, instruction_ref new_ins) { ins->replace_argument(old, new_ins); backreference(ins); ins->recompute_shape(); } void instruction::replace_mod_argument(instruction_ref ins, module_ref old, module_ref new_mod) { ins->replace_mod_argument(old, new_mod); backreference(ins); ins->recompute_shape(); } void instruction::replace(instruction_ref ins, operation o, const shape& r, std::vector args) { ins->replace(std::move(o), r, std::move(args)); backreference(ins); } void instruction::replace(instruction_ref ins, operation o, const shape& r, std::vector args, std::vector module_args) { ins->replace(std::move(o), r, std::move(args), std::move(module_args)); backreference(ins); } void instruction::replace(operation o, const shape& r, std::vector args) { normalized = false; op = std::move(o); replace(r); replace(std::move(args)); } void instruction::replace(operation o, const shape& r, std::vector args, std::vector mdl_args) { op = std::move(o); replace(r); replace(std::move(args), std::move(mdl_args)); } void instruction::replace_refs( instruction_ref ins, const std::unordered_map& map_insts, const std::unordered_map& map_mods) { const auto& args = ins->inputs(); for(const auto& arg : args) { if(contains(map_insts, arg)) { instruction::replace_argument(ins, arg, map_insts.at(arg)); } } const auto& module_args = ins->module_inputs(); if(module_args.empty()) return; for(const auto& mod : module_args) { if(contains(map_mods, mod)) { instruction::replace_mod_argument(ins, mod, map_mods.at(mod)); } } } void instruction::replace(std::vector args) { clear_arguments(); arguments = std::move(args); } void instruction::replace(std::vector args, std::vector mdl_args) { clear_arguments(); arguments = std::move(args); module_args = std::move(mdl_args); } void instruction::replace_argument(instruction_ref old, instruction_ref new_ins) { assert(std::any_of(arguments.begin(), arguments.end(), equal_to(old))); std::replace_if(arguments.begin(), arguments.end(), equal_to(old), new_ins); old->remove_output(*this); } void instruction::replace_mod_argument(module_ref old, module_ref new_mod) { assert(std::any_of(module_args.begin(), module_args.end(), [&](auto i) { return i == old; })); std::replace(module_args.begin(), module_args.end(), old, new_mod); } bool instruction::is_undefined() const { if(op.name() == "undefined" or (op.name() == "@literal" and this->get_literal().empty())) { return true; } else if(this->inputs().empty()) { return false; } else { return std::all_of(this->inputs().begin(), this->inputs().end(), [](auto arg) { return arg->is_undefined(); }); } } bool instruction::can_eval() const { if(op.name() == "@literal") { return true; } else if(is_context_free(op)) { return std::all_of( this->inputs().begin(), this->inputs().end(), [](auto arg) { return arg->can_eval(); }); } else { return false; } } argument instruction::eval(bool check_eval) const { if(op.name() == "@literal") { return this->get_literal().get_argument(); } if(is_context_free(op)) { if(check_eval and not this->can_eval()) return {}; std::vector args; std::transform(this->inputs().begin(), this->inputs().end(), std::back_inserter(args), [](auto arg) { return arg->eval(false); }); return normalized_operator().compute(result, args); } return {}; } void instruction::finalize(context& ctx) { if(has_finalize(this->op)) this->op.finalize(ctx, this->get_shape(), to_shapes(this->inputs())); } void instruction::print(std::ostream& os, instruction_ref ins, const std::unordered_map& names) { os << names.at(ins) << " = "; os << ins->get_operator(); if(ins->name() == "@literal") { if(ins->get_literal().get_shape().elements() > 10) os << "{ ... }"; else os << "{" << ins->get_literal() << "}"; } if(not ins->inputs().empty()) { char delim = '('; for(auto&& arg : ins->inputs()) { std::string arg_name = contains(names, arg) ? names.at(arg) : "?"; os << delim << arg_name; delim = ','; } os << ")"; } // print module inputs if(not ins->module_inputs().empty()) { std::string delim = ", ["; for(const const_module_ref& mod_arg : ins->module_inputs()) { os << delim << mod_arg->name(); delim = ", "; } os << "]"; } // skip return instruction shape if(ins->name() != "@return") os << " -> " << ins->get_shape(); // print tid if(ins->target_id != 0) os << ", target_id=" << ins->target_id; } static void debug_name(std::ostream& os, const instruction& ins) { if(ins.name() == "@literal") { os << "@literal"; if(ins.get_literal().get_shape().elements() > 10) os << "{ ... }"; else os << "{" << ins.get_literal() << "}"; } else { os << ins.get_operator(); } } void instruction::debug_print() const { debug_name(std::cout, *this); std::string delim = "("; for(auto arg : this->inputs()) { std::cout << delim; debug_name(std::cout, *arg); delim = ", "; } if(not this->inputs().empty()) std::cout << ")"; std::cout << " -> " << this->get_shape() << std::endl; } instruction_ref instruction::get_output_alias(instruction_ref ins, bool shallow) { auto i = ins->get_operator().output_alias(to_shapes(ins->inputs())); if(i < 0) return ins; if(shallow) return ins->inputs().at(i); return get_output_alias(ins->inputs().at(i)); } void instruction::set_normalized(bool value) { normalized = value; } bool instruction::is_normalized() const { return normalized; } bool instruction::need_normalization() const { return this->get_operator().need_normalization() and not normalized; } operation instruction::normalized_operator() const { operation o = this->get_operator(); if(this->need_normalization()) { auto s = this->inputs().front()->get_shape(); if(not normalize_attributes(o, s)) return this->get_operator(); } return o; } std::size_t instruction::get_target_id() const { return target_id; } void instruction::set_target_id(std::size_t tid) { this->target_id = tid; } std::vector to_shapes(const std::vector& args) { std::vector shapes(args.size()); std::transform( args.begin(), args.end(), shapes.begin(), [](instruction_ref i) { return i->get_shape(); }); return shapes; } shape compute_shape(const operation& op, const std::vector& args) { return op.compute_shape(to_shapes(args)); } shape compute_shape(const operation& op, const std::vector& args, const std::vector& mods) { if(mods.empty()) { return op.compute_shape(to_shapes(args)); } else { return op.compute_shape(to_shapes(args), mods); } } std::vector try_compute_shape(const operation& op, const std::vector& inputs) { shape new_shape; try { new_shape = op.compute_shape(inputs); } catch(...) { return {}; } return {new_shape}; } migraphx::instruction* as_address(const std::list::iterator& ins) noexcept { return iterator_address(ins); } const migraphx::instruction* as_address(const std::list::const_iterator& ins) noexcept { return iterator_address(ins); } template static auto track_visits(instruction_ref start, instruction_ref end, F f) { const std::size_t small = 16; std::size_t n = std::distance(start, end); if(n < small) { std::bitset visited; auto stop = [&](auto ins) { auto i = std::distance(ins, end); if(i > n) return true; if(visited.test(i)) return true; visited.set(i); return false; }; return f(stop); } else { std::unordered_set visited; visited.reserve(n); auto stop = [&](auto ins) { if(not visited.insert(ins).second) return true; if(std::distance(ins, end) > n) return true; return false; }; return f(stop); } } // DFS through inputs of `end` to find `start`. // `start` must be positioned before `end`. bool reaches(instruction_ref start, instruction_ref end) { if(start == end) return true; std::unordered_set visited; return fix([&](auto self, auto ins) -> bool { if(ins == start) return true; // hit a previously visited instruction if(not visited.insert(ins).second) return false; return std::any_of(ins->inputs().begin(), ins->inputs().end(), self); })(end); } // `reaches` version that checks if instructions are in the module `m` // Additional condition that stops if DFS instruction's distance to `end` // is greater than the distance between `start` and `end`. template static bool reaches(instruction_ref start, instruction_ref end, const_module_ref m, P predicate) { if(start == end) return true; if(not m->has_instruction(start) or not m->has_instruction(end)) return false; assert(std::distance(m->begin(), start) < std::distance(m->begin(), end)); return track_visits(start, end, [&](auto stop) { return fix([&](auto self, auto ins) -> bool { if(not m->has_instruction(ins)) return false; if(ins == start or predicate(ins)) return true; if(stop(ins)) return false; return std::any_of(ins->inputs().begin(), ins->inputs().end(), self); })(end); }); } bool reaches(instruction_ref start, instruction_ref end, const_module_ref m) { return reaches(start, end, m, [](auto) { return false; }); } bool is_interdependent(const std::vector& instructions, const_module_ref m, instruction_ref root) { if(instructions.size() < 2) return true; const std::size_t small_size = 8; if(instructions.size() <= small_size) { std::array loc; std::transform(instructions.begin(), instructions.end(), loc.begin(), [&](instruction_ref ins) { return std::distance(root, ins); }); auto start = instructions[std::distance( loc.begin(), std::min_element(loc.begin(), loc.begin() + instructions.size()))]; return all_of(instructions, [&](instruction_ref ins) { if(ins == start) return true; return reaches(start, ins, m, [&](instruction_ref i) { return i != ins and contains(instructions, i); }); }); } std::unordered_map loc; loc.reserve(instructions.size()); std::transform( instructions.begin(), instructions.end(), std::inserter(loc, loc.end()), [&](instruction_ref ins) { return std::make_pair(ins, std::distance(root, ins)); }); auto min_it = std::min_element( loc.begin(), loc.end(), [](const auto& x, const auto& y) { return x.second < y.second; }); auto start = min_it->first; return all_of(instructions, [&](instruction_ref ins) { if(ins == start) return true; return reaches( start, ins, m, [&](instruction_ref i) { return i != ins and contains(loc, i); }); }); } // Return set of all instructions that are connected to both start and end nodes (inclusive) std::unordered_set find_instructions_between(instruction_ref start, instruction_ref end, const_module_ref m) { assert(reaches(start, end, m)); std::unordered_set result; std::unordered_set inss; fix([&](auto self, auto ins) { if(not m->has_instruction(ins)) return; if(ins->inputs().empty()) return; if(not inss.insert(ins).second) return; if(ins == start) return; for(auto input : ins->inputs()) self(input); })(end); fix([&](auto self, auto ins) { if(ins == end) return; if(ins != start and not contains(inss, ins)) return; if(not result.insert(ins).second) return; for(auto output : ins->outputs()) self(output); })(start); result.insert(end); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/json.cpp000066400000000000000000000113621510465702400170070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { using json = nlohmann::json; static void value_to_json(const value& val, json& j); static migraphx::value value_from_json(const json& j); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx namespace nlohmann { template <> struct adl_serializer { static void to_json(json& j, const migraphx::value& val) { migraphx::value_to_json(val, j); } static void from_json(const json& j, migraphx::value& val) { val = migraphx::value_from_json(j); } }; } // namespace nlohmann namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { using json = nlohmann::json; template static void value_to_json(const T& x, json& j) { j = x; } static void value_to_json(const value::binary& x, json& j) { j = json::object(); j["bytes"] = std::vector(x.begin(), x.end()); } static void value_to_json(const std::vector& x, json& j) { for(const auto& v : x) { if(v.get_key().empty()) { j.push_back(v); } else { j[v.get_key()] = v.without_key(); } } } static void value_to_json(std::nullptr_t, json& j) { j = {}; } void value_to_json(const value& val, json& j) { if(val.is_array()) { j = json::array(); } if(val.is_object()) { j = json::object(); } val.visit([&](const auto& v) { value_to_json(v, j); }); } migraphx::value value_from_json(const json& j) { migraphx::value val; json::value_t type = j.type(); switch(type) { case json::value_t::null: val = nullptr; break; case json::value_t::boolean: val = j.get(); break; case json::value_t::number_float: val = j.get(); break; case json::value_t::number_integer: val = j.get(); break; case json::value_t::number_unsigned: val = j.get(); break; case json::value_t::string: val = j.get(); break; case json::value_t::array: val = migraphx::value::array{}; std::transform(j.begin(), j.end(), std::back_inserter(val), [&](const json& jj) { return jj.get(); }); break; case json::value_t::object: if(j.contains("bytes") and j.size() == 1) { val = migraphx::value::binary{j["bytes"].get>()}; } else { val = migraphx::value::object{}; for(const auto& item : j.items()) { const auto& key = item.key(); const json& jv = item.value(); val[key] = jv.get(); } } break; case json::value_t::binary: MIGRAPHX_THROW("Convert JSON to Value: binary type not supported!"); case json::value_t::discarded: MIGRAPHX_THROW("Convert JSON to Value: discarded type not supported!"); } return val; } std::string to_json_string(const value& val) { json j = val; return j.dump(); } std::string to_pretty_json_string(const value& val, std::size_t indent) { json j = val; return j.dump(indent); } migraphx::value from_json_string(const char* str, std::size_t size) { json j = json::parse(str, str + size); return j.get(); } migraphx::value from_json_string(const std::string& str) { json j = json::parse(str); return j.get(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/layout_convolution.cpp000066400000000000000000000120641510465702400220120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { std::vector get_permutation(instruction_ref ins, const layout_convolution& lc) { std::vector perm(ins->get_shape().ndim()); if(lc.channels_last) { std::iota(perm.begin() + 1, perm.end() - 1, 2); perm.back() = 1; } else { std::iota(perm.begin(), perm.end(), 0); } return perm; } std::vector get_default_permutation(instruction_ref ins) { std::vector perm(ins->get_shape().ndim()); std::iota(perm.begin(), perm.end(), 0); return perm; } bool skip_layout(const shape& s) { return s.ndim() == 1 or s.dynamic() or s.type() == shape::tuple_type; } void preserve_output_layout(module& m) { auto last = std::prev(m.end()); if(last->name() == "@return") { std::vector outputs; std::transform(last->inputs().begin(), last->inputs().end(), std::back_inserter(outputs), [&](instruction_ref ins) { if(skip_layout(ins->get_shape())) return ins; auto permutation = find_permutation(ins->get_shape()); return m.insert_instruction( last, make_op("layout", {{"permutation", permutation}}), ins); }); m.replace_return(outputs); } else if(not skip_layout(last->get_shape())) { auto permutation = find_permutation(last->get_shape()); m.add_instruction(make_op("layout", {{"permutation", permutation}}), last); } } void transform_convolutions(module& m, const layout_convolution& lc) { for(auto ins : iterator_for(m)) { if(not contains({"convolution", "quant_convolution"}, ins->name())) continue; if(ins->get_shape().dynamic()) continue; if(ins->get_shape().lens().size() != 4) continue; auto v = ins->get_operator().to_value(); bool is_group_conv = v.at("group").to() > 1; auto args = ins->inputs(); auto perm = is_group_conv ? get_default_permutation(ins) : get_permutation(ins, lc); std::transform(args.begin(), args.end(), args.begin(), [&](const auto& i) { return m.insert_instruction(ins, make_op("layout", {{"permutation", perm}}), i); }); auto conv = m.insert_instruction(ins, ins->get_operator(), args); auto c = m.insert_instruction(ins, make_op("contiguous"), conv); m.replace_instruction(ins, c); } } void remove_layout(module& m) { for(auto ins : iterator_for(m)) { if(ins->name() != "layout") continue; auto perm = ins->get_operator().to_value()["permutation"].to_vector(); auto iperm = find_permutation(ins->inputs().front()->get_shape()); if(perm != iperm) continue; m.replace_instruction(ins, ins->inputs().front()); } } } // namespace void layout_convolution::apply(module_pass_manager& mpm) const { preserve_output_layout(mpm.get_module()); transform_convolutions(mpm.get_module(), *this); mpm.run_pass(dead_code_elimination{}); mpm.run_pass(eliminate_contiguous{"contiguous"}); mpm.run_pass(dead_code_elimination{}); remove_layout(mpm.get_module()); mpm.run_pass(dead_code_elimination{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/lexing.cpp000066400000000000000000000044241510465702400173250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { std::function lex_equal(const std::string& s) { return [=](const char* start, const char* end) { auto n = end - start; if(n < s.size()) return start; if(std::equal(start, start + s.size(), s.data())) return start + s.size(); return start; }; } std::vector tokenize(const char* start, const char* end, const std::vector& lexers) { std::vector result; while(start != end) { bool error = true; for(const auto& l : lexers) { const auto* next = l(start, end); if(next != start) { result.emplace_back(start, next - start); start = next; error = false; break; } } if(error) { MIGRAPHX_THROW("TOKENIZE: no token found!"); } } return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/load_save.cpp000066400000000000000000000067161510465702400200020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { program load(const std::string& filename, const file_options& options) { return load_buffer(read_buffer(filename), options); } program load_buffer(const std::vector& buffer, const file_options& options) { return load_buffer(buffer.data(), buffer.size(), options); } program load_buffer(const char* buffer, std::size_t size, const file_options& options) { program p; if(options.format == "msgpack") { p.from_value(from_msgpack(buffer, size)); } else if(options.format == "json") { p.from_value(from_json_string(buffer, size)); } else { MIGRAPHX_THROW("Unknown format: " + options.format); } return p; } void save(const program& p, const std::string& filename, const file_options& options) { write_buffer(filename, save_buffer(p, options)); } // MIOpen doesn't support serializing fusion plans with Find-2.0 APIs static void print_miopen_warning(const program& p) { auto mods = p.get_modules(); if(std::any_of(mods.begin(), mods.end(), [](const auto* m) { return std::any_of(m->begin(), m->end(), [](const instruction& i) { return i.name() == "gpu::miopen_fusion"; }); })) { std::cerr << "[WARNING]: Program has miopen_fusion instructions for which tuned solutions " "are not stored inside serialized MIGraphX program. Consider serializing with " "MIGRAPHX_DISABLE_MIOPEN_FUSION=1 flag set." << std::endl; ; } } std::vector save_buffer(const program& p, const file_options& options) { value v = p.to_value(); print_miopen_warning(p); std::vector buffer; if(options.format == "msgpack") { buffer = to_msgpack(v); } else if(options.format == "json") { std::string s = to_json_string(v); buffer = std::vector(s.begin(), s.end()); } else { MIGRAPHX_THROW("Unknown format: " + options.format); } return buffer; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/make_op.cpp000066400000000000000000000050711510465702400174510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { operation make_op(const std::string& name) { return load_op(name); } template static operation make_op_generic(const std::string& name, F for_each) { auto op = load_op(name); // Merge values value w = op.to_value(); for_each([&](const auto& key, const auto& x) { if(not w.contains(key)) // NOLINTNEXTLINE(performance-inefficient-string-concatenation) MIGRAPHX_THROW("No key '" + key + "' in " + name); w.at(key) = x; }); op.from_value(w); return op; } operation make_op(const std::string& name, const std::initializer_list>& v) { return make_op_generic(name, [&](auto f) { for(auto&& [key, x] : v) f(key, x); }); } operation make_op_from_value(const std::string& name, const value& v) { if(not(v.is_object() or (v.empty() and v.is_array()))) MIGRAPHX_THROW("Value is not an object for make_op: " + name); return make_op_generic(name, [&](auto f) { for(auto&& x : v) f(x.get_key(), x.without_key()); }); } operation make_json_op(const std::string& name, const std::string& s) { return make_op(name, from_json_string(convert_to_json(s))); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/memory_coloring.cpp000066400000000000000000000322251510465702400212430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DEBUG_MEMORY_COLORING); using instruction_set = std::unordered_set; using instruction_set_map = std::unordered_map; // This will build the conflict table or interference graph. This is // essentially a map from one instruction to a set of instruction that are // used together. Each instruction will be the allocation instruction. static instruction_set_map build_conflict_table(const module& m, std::string allocation_op) { instruction_set_map conflict_table; liveness(m, [&](auto ins, const auto& live_set) { // Skip variables that aren't allocations if(ins->name() != allocation_op) return; // Skip zero allocations if(ins->get_shape().bytes() == 0) return; conflict_table[ins]; for(auto i : live_set) { if(i == ins) continue; // Skip variables that aren't allocations if(i->name() != allocation_op) continue; // Skip zero allocations if(i->get_shape().bytes() == 0) continue; conflict_table[i].insert(ins); conflict_table[ins].insert(i); } }); assert(std::all_of(conflict_table.begin(), conflict_table.end(), [](auto&& pp) { return pp.second.count(pp.first) == 0; })); return conflict_table; } // Check if intervals overlap static bool is_overlap(std::pair x, std::pair y) { return std::max(x.first, y.first) < std::min(x.second, y.second); } struct allocation_segment { using segment = std::pair; std::unordered_map ins2segment; const segment* add_segment(instruction_ref ins, segment s) { return &(ins2segment[ins] = s); } const segment* get_segment(instruction_ref ins) const { auto it = ins2segment.find(ins); if(it == ins2segment.end()) return nullptr; return &it->second; } // Remove segment for an instruction void remove(instruction_ref ins) { auto it = ins2segment.find(ins); if(it != ins2segment.end()) { ins2segment.erase(it); } } std::size_t max() { std::size_t n = 0; for(auto&& pp : ins2segment) { auto seg = pp.second; n = std::max(n, seg.second); } return n; } template static bool overlaps(Iterator first, Iterator last, const segment& s) { return std::any_of(first, last, [&](auto&& t) { return is_overlap(s, t); }); } static bool overlaps(const std::set& segments, const segment& s) { return overlaps(segments.begin(), segments.end(), s); } static auto find_gap(const std::set& segments, std::size_t n) { std::size_t max_end = 0; return std::adjacent_find(segments.begin(), segments.end(), [&](segment x, segment y) { if(x.second < max_end) return false; max_end = x.second; if(is_overlap(x, y)) return false; assert(y.first >= x.second); auto k = y.first - x.second; return (k >= n); }); } static std::size_t max_type_size(const shape& s) { return std::accumulate( s.sub_shapes().begin(), s.sub_shapes().end(), s.type_size(), [](auto size, const auto& sub) { return std::max(size, max_type_size(sub)); }); } static std::size_t compute_alignment(instruction_ref ins) { auto alignment = max_type_size(ins->get_shape()); // A rough estimate for the total number of elements auto n = ins->get_shape().bytes() / alignment; // Check for vectorized alignment if(n > 4) { auto d = n % 4; if(d == 0) alignment *= 4; if(d == 2) alignment *= 2; } return alignment; } static segment next_segment(std::set& segments, instruction_ref ins, std::size_t alignment) { assert(ins->get_shape().bytes() > 0); // Compute alignment std::size_t n = 1 + (ins->get_shape().bytes() - 1) / alignment; assert(n > 0); std::size_t start = 0; // Insert at end if it cant fit at the begining if(segments.empty() or segments.begin()->first <= n) { auto it = find_gap(segments, n); if(it == segments.end()) it = std::max_element(segments.begin(), segments.end(), [&](segment x, segment y) { return x.second < y.second; }); if(it != segments.end()) start = it->second; } auto s = segment{start, start + n}; assert(not overlaps(segments, s)); segments.insert(s); return s; } static std::unordered_map create_allocation_index(const module& m, const instruction_set_map& conflict_table) { std::unordered_map result; int i = 0; for(auto ins : iterator_for(m)) { if(not contains(conflict_table, ins)) continue; result[ins] = i++; } return result; } // Build the allocation_color class from the conflict_table static allocation_segment build(const module& m, const instruction_set_map& conflict_table, std::size_t alignment) { allocation_segment as{}; std::vector conflict_queue; // Add all allocations to the conflict_queue std::transform(conflict_table.begin(), conflict_table.end(), std::back_inserter(conflict_queue), [](auto&& pp) { return pp.first; }); auto alloc_index = create_allocation_index(m, conflict_table); // Sort the conflict queue so we process the allocation with the most // number of adjacent allocations first std::sort(conflict_queue.begin(), conflict_queue.end(), by(std::greater<>{}, [&](auto x) { return std::make_tuple( conflict_table.at(x).size(), x->get_shape().bytes(), alloc_index.at(x)); })); // Process the conflict_queue, we refer to the current allocation as // the parent and the adjacent allocations as children for(auto parent : conflict_queue) { // Sort children by size std::vector children(conflict_table.at(parent).begin(), conflict_table.at(parent).end()); std::sort(children.begin(), children.end(), by(std::less<>{}, [&](auto x) { return std::make_tuple(x->get_shape().bytes(), alloc_index.at(x)); })); assert(not contains(children, parent)); // This set is to track the segments already processed std::set segments; // Add all segments for the children to the segments already processed transform_if( children.begin(), children.end(), std::inserter(segments, segments.begin()), [&](auto child) { return as.get_segment(child); }, [&](auto child) { return *as.get_segment(child); }); assert(as.get_segment(parent) == nullptr); as.add_segment(parent, next_segment(segments, parent, alignment)); } // Reduce the number of segments for(std::size_t n = 0; n < 3; n++) { for(auto parent : conflict_queue) { auto children = conflict_table.at(parent); // This set is to track the segments already processed std::set segments; // Add all segments for the children to the segments already processed transform_if( children.begin(), children.end(), std::inserter(segments, segments.begin()), [&](auto child) { return as.get_segment(child); }, [&](auto child) { return *as.get_segment(child); }); // Get the segment for the parent const auto* parent_segment = as.get_segment(parent); assert(parent_segment != nullptr); auto s = next_segment(segments, parent, alignment); if(s != *parent_segment and s.second <= as.max()) { as.add_segment(parent, s); } } } return as; } }; static std::size_t find_max_alignment(const module& m, const std::string& allocation_op) { std::size_t alignment = 1; for(auto ins : iterator_for(m)) { if(ins->name() != allocation_op) continue; alignment = std::max(allocation_segment::compute_alignment(ins), alignment); } return alignment; } void memory_coloring::apply(module& m) const { const std::size_t alignment = find_max_alignment(m, allocation_op); auto conflict_table = build_conflict_table(m, allocation_op); auto as = allocation_segment::build(m, conflict_table, alignment); // All allocations should have a segment assert(std::all_of(conflict_table.begin(), conflict_table.end(), [&](auto&& pp) { return as.get_segment(pp.first); })); // Adjacent allocations should not have overlapping segments assert(std::none_of(conflict_table.begin(), conflict_table.end(), [&](auto&& pp) { auto* x = as.get_segment(pp.first); return std::any_of(pp.second.begin(), pp.second.end(), [&](auto ins) { auto* y = as.get_segment(ins); assert(x and y); return is_overlap(*x, *y); }); })); // Print out segments if(enabled(MIGRAPHX_DEBUG_MEMORY_COLORING{})) { for(auto&& pp : conflict_table) { std::cout << "------- conflict -------" << std::endl; auto s1 = as.ins2segment.at(pp.first); std::cout << s1.first << ", " << s1.second << ": "; m.debug_print(pp.first); for(auto ins : pp.second) { auto s2 = as.ins2segment.at(ins); std::cout << s2.first << ", " << s2.second << ": "; m.debug_print(ins); } } } // Total memory std::size_t n = as.max() * alignment; // Replace allocations auto mem = m.add_parameter("scratch", shape{shape::int8_type, {n}}); for(auto&& [ins, seg] : as.ins2segment) { assert(ins->name() == allocation_op); auto s = ins->get_shape(); std::size_t offset = seg.first * alignment; assert(offset < n); m.replace_instruction( ins, make_op("load", {{"shape", to_value(s)}, {"offset", offset}}), mem); } // Replace zero allocation for(auto ins : iterator_for(m)) { if(ins->name() != allocation_op) continue; assert(ins->get_shape().bytes() == 0); m.replace_instruction( ins, make_op("load", {{"shape", to_value(ins->get_shape())}, {"offset", 0}}), mem); } // Remove scratch parameter if its not used if(mem->outputs().empty()) { m.remove_instruction(mem); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/module.cpp000066400000000000000000001565231510465702400173340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_FINALIZE) struct module_impl { // A list is used to keep references to an instruction stable std::list instructions; std::unordered_set instruction_set; std::string name; uint32_t nparams = 0; bool bypass = false; // used for skipping compiler passes bit_signal<64> changed{}; bool contains(instruction_ref ins) const { if(ins == instructions.end()) return false; auto r = instruction_set.count(std::addressof(*ins)) > 0; assert(r == std::any_of(instructions.begin(), instructions.end(), [&](auto&& x) { return std::addressof(x) == std::addressof(*ins); })); return r; } template instruction_ref emplace(instruction_ref pos, Ts&&... xs) { changed.notify(); // cppcheck-suppress redundantInitialization auto r = instructions.emplace(pos, std::forward(xs)...); instruction_set.insert(std::addressof(*r)); return r; } instruction_ref insert(instruction_ref pos, const instruction& ins) { changed.notify(); return emplace(pos, ins); } void clear() { changed.notify(); instructions.clear(); instruction_set.clear(); nparams = 0; } void push_front(const instruction& ins) { insert(instructions.begin(), ins); } void push_back(const instruction& ins) { insert(instructions.end(), ins); } template void emplace_front(Ts&&... xs) { emplace(instructions.begin(), std::forward(xs)...); } template void emplace_back(Ts&&... xs) { emplace(instructions.end(), std::forward(xs)...); } instruction_ref erase(instruction_ref pos) { changed.notify(); instruction_set.erase(std::addressof(*pos)); return instructions.erase(pos); } instruction_ref erase(instruction_ref start, instruction_ref last) { changed.notify(); std::for_each(start, last, [&](auto& ins) { instruction_set.erase(std::addressof(ins)); }); return instructions.erase(start, last); } }; const operation& get_operation(instruction_ref ins) { return ins->get_operator(); } module::module(const std::string& name) : impl(std::make_unique()) { impl->name = name; } module::module(module&&) noexcept = default; module::~module() noexcept = default; // copy constructor module::module(const module& m) { assign(m); } // copy assignment operator module& module::operator=(module m) { std::swap(m.impl, this->impl); return *this; } std::string module::name() const { return impl->name; } void module::set_name(const std::string& name) { impl->name = name; } bool module::bypass() const { return impl->bypass; } void module::set_bypass(bool b) { impl->bypass = b; } void module::assign(const module& m) { // copy the impl if(not impl) impl = std::make_unique(); *impl = *m.impl; // clear instructions if(not impl->instructions.empty()) { impl->clear(); } std::unordered_map ins_map; for(auto ins : iterator_for(m)) { instruction_ref copy_ins{}; if(ins->name() == "@literal") { auto l = ins->get_literal(); copy_ins = impl->insert(impl->instructions.end(), instruction{l}); } else if(ins->name() == "@param") { auto&& name = any_cast(ins->get_operator()).parameter; auto order = any_cast(ins->get_operator()).order; auto s = ins->get_shape(); copy_ins = impl->insert(impl->instructions.end(), {builtin::param{name, order}, std::move(s), {}}); impl->nparams++; } else if(ins->name() == "@outline") { auto s = ins->get_shape(); copy_ins = impl->insert(impl->instructions.end(), {builtin::outline{s}, s, {}}); } else { // if there are sub_module inputs, need to make a copy of the submodule auto module_args = ins->module_inputs(); // retrieve its mapped input auto inputs = ins->inputs(); std::vector copy_inputs(inputs.size()); std::transform(inputs.begin(), inputs.end(), copy_inputs.begin(), [&](auto i) { return contains(ins_map, i) ? ins_map[i] : i; }); if(ins->name() == "@return") { copy_ins = add_return(copy_inputs); } else { copy_ins = add_instruction(ins->get_operator(), copy_inputs, module_args); } } ins_map[ins] = copy_ins; } } template static std::vector insert_generic_instructions_impl(module& m, instruction_ref ins, Range&& instructions, std::unordered_map& map_ins, Inserter insert) // NOLINT(performance-unnecessary-value-param) { assert(m.has_instruction(ins) or is_end(ins, m.end())); std::vector mod_outputs; instruction_ref last; for(instruction_ref sins : instructions) { last = sins; if(contains(map_ins, sins)) continue; instruction_ref copy_ins; if(sins->name() == "@literal") { auto l = sins->get_literal(); copy_ins = m.add_literal(l); } else if(sins->name() == "@param") { auto&& name = any_cast(sins->get_operator()).parameter; auto s = sins->get_shape(); copy_ins = m.add_parameter(name, s); } else if(sins->name() == "@outline") { auto s = sins->get_shape(); copy_ins = m.add_outline(s); } else { auto mod_args = sins->module_inputs(); auto inputs = sins->inputs(); std::vector copy_inputs(inputs.size()); std::transform(inputs.begin(), inputs.end(), copy_inputs.begin(), [&](auto i) { return contains(map_ins, i) ? map_ins[i] : i; }); if(sins->name() == "@return") { mod_outputs = copy_inputs; break; } copy_ins = insert(m, ins, sins->get_operator(), copy_inputs, mod_args); } map_ins[sins] = copy_ins; } if(mod_outputs.empty() and instructions.begin() != instructions.end()) mod_outputs = {map_ins.at(last)}; return mod_outputs; } template static std::vector insert_generic_instructions(module& m, instruction_ref ins, Range&& instructions, std::unordered_map& map_ins, module::inserter insert) { if(insert == nullptr) return insert_generic_instructions_impl( m, ins, static_cast(instructions), map_ins, [](module& mm, auto&&... xs) { return mm.insert_instruction(std::forward(xs)...); }); return insert_generic_instructions_impl( m, ins, static_cast(instructions), map_ins, std::move(insert)); } instruction_ref module::add_instruction(const operation& op, std::vector args) { return insert_instruction(this->insert_end(), op, std::move(args)); } instruction_ref module::insert_instruction(instruction_ref ins, const operation& op, std::vector args) { assert(has_instruction(ins) or is_end(ins, this->end())); assert(not starts_with(op.name(), "@")); shape r = compute_shape(op, args); auto result = impl->insert(ins, {op, r, std::move(args)}); instruction::backreference(result); assert(result->valid(begin())); return result; } instruction_ref module::add_instruction(const operation& op, std::vector args, std::vector module_args) { return insert_instruction(this->insert_end(), op, std::move(args), std::move(module_args)); } instruction_ref module::insert_instruction(instruction_ref ins, const operation& op, std::vector args, std::vector module_args) { assert(has_instruction(ins) or is_end(ins, this->end())); assert(not starts_with(op.name(), "@")); auto out_shape = compute_shape(op, args, module_args); auto result = impl->insert(ins, {op, out_shape, std::move(args), std::move(module_args)}); instruction::backreference(result); assert(result->valid(begin())); return result; } instruction_ref module::replace_instruction(instruction_ref ins, const operation& op, std::vector args) MIGRAPHX_TIDY_CONST { impl->changed.notify(); assert(has_instruction(ins)); assert(not starts_with(op.name(), "@")); shape r = compute_shape(op, args); instruction::replace(ins, op, r, std::move(args)); assert(ins->valid(begin())); return ins; } instruction_ref module::replace_instruction(instruction_ref ins, const operation& op, std::vector args, std::vector module_args) MIGRAPHX_TIDY_CONST { impl->changed.notify(); assert(has_instruction(ins)); assert(not starts_with(op.name(), "@")); auto out_shape = compute_shape(op, args, module_args); instruction::replace(ins, op, out_shape, std::move(args), std::move(module_args)); assert(ins->valid(begin())); return ins; } instruction_ref module::replace_instruction(instruction_ref ins, instruction_ref rep) { impl->changed.notify(); assert(has_instruction(ins)); assert(ins != rep); if(ins == std::prev(this->end())) { // "rep" instruction could be used earlier in the program and moving it at the end // may cause invalid program, therefore make an identity operation in this case. return replace_instruction(ins, make_op("identity"), rep); } // TODO: Should it be an error if the output is empty? if(ins->outputs().empty()) { return rep; } // Make a copy of outputs which can be changed when calling replace_argument auto outputs = ins->outputs(); for(auto out : outputs) { // TODO: Check for possible cycles if(out != rep) { instruction::replace_argument(out, ins, rep); } assert(out->valid(begin())); } // Replacement should not be dead code unless its the last instruction assert(not rep->outputs().empty() or rep == std::prev(end())); // Output of the original instruction should only be the replacement or empty assert(ins->outputs().empty() or std::all_of(ins->outputs().begin(), ins->outputs().end(), [&](auto i) { return i == rep; })); assert(ins->valid(begin())); assert(rep->valid(begin())); return rep; } instruction_ref module::remove_instruction(instruction_ref ins) { assert(has_instruction(ins)); assert(ins->outputs().empty()); ins->clear_arguments(); return impl->erase(ins); } instruction_ref module::remove_instructions(instruction_ref first, instruction_ref last) { if(first == last) return first; // TODO: Check every element assert(has_instruction(first)); std::for_each(first, last, [&](instruction& ins) { ins.clear_arguments(); }); assert(std::all_of(first, last, [&](const instruction& ins) { return ins.outputs().empty(); })); return impl->erase(first, last); } instruction_ref module::move_instruction(instruction_ref src, instruction_ref dst) { impl->changed.notify(); assert(has_instruction(src)); assert(has_instruction(dst) or is_end(dst, this->end())); impl->instructions.splice(dst, impl->instructions, src); return src; } instruction_ref module::move_instructions(instruction_ref src, instruction_ref dst) { for(auto ins : src->inputs()) { if(not contains(this->impl->instructions, ins)) continue; this->move_instructions(ins, dst); } this->move_instruction(src, dst); return src; } std::vector module::add_instructions(const std::vector& instructions, std::unordered_map* map_ins, module::inserter insert) { return this->insert_instructions(this->insert_end(), instructions, map_ins, std::move(insert)); } std::vector module::add_instructions(const_module_ref m, std::unordered_map* map_ins, module::inserter insert) { return this->insert_instructions(this->insert_end(), m, map_ins, std::move(insert)); } std::vector module::add_instructions(instruction_ref start, instruction_ref last, std::unordered_map* map_ins, module::inserter insert) { return this->insert_instructions(this->insert_end(), start, last, map_ins, std::move(insert)); } std::vector module::insert_instructions(instruction_ref ins, const std::vector& instructions, std::unordered_map* map_ins, module::inserter insert) { std::unordered_map default_map_ins; return insert_generic_instructions(*this, ins, instructions, map_ins == nullptr ? default_map_ins : *map_ins, std::move(insert)); } std::vector module::insert_instructions(instruction_ref ins, const_module_ref m, std::unordered_map* map_ins, module::inserter insert) { std::unordered_map default_map_ins; return insert_generic_instructions(*this, ins, iterator_for(*m), map_ins == nullptr ? default_map_ins : *map_ins, std::move(insert)); } std::vector module::insert_instructions(instruction_ref ins, instruction_ref start, instruction_ref last, std::unordered_map* map_ins, module::inserter insert) { auto r = range(start, last); std::unordered_map default_map_ins; return insert_generic_instructions(*this, ins, iterator_for(r), map_ins == nullptr ? default_map_ins : *map_ins, std::move(insert)); } instruction_ref module::add_literal(literal l) { return insert_literal(begin(), std::move(l)); } instruction_ref module::add_outline(const shape& s) { impl->push_front({builtin::outline{s}, s, {}}); return impl->instructions.begin(); } instruction_ref module::add_parameter(std::string name, shape s) { return insert_parameter(begin(), std::move(name), std::move(s)); } instruction_ref module::add_return(std::vector args) { shape instr_shape = compute_shape(builtin::returns{}, args); impl->push_back({builtin::returns{}, instr_shape, std::move(args)}); auto result = std::prev(impl->instructions.end()); instruction::backreference(result); assert(result->valid(begin())); return result; } instruction_ref module::insert_literal(instruction_ref ins, literal l) { impl->emplace(ins, std::move(l)); return std::prev(ins); } instruction_ref module::insert_parameter(instruction_ref ins, std::string name, shape s) { assert(get_parameter_shape(name) == shape{}); impl->insert(ins, {builtin::param{std::move(name), impl->nparams}, std::move(s), {}}); impl->nparams++; return std::prev(ins); } instruction_ref module::replace_return(std::vector args) { impl->changed.notify(); auto last = std::prev(this->end()); // If there is no return then add a return if(last->name() != "@return") { assert(std::none_of(impl->instructions.begin(), impl->instructions.end(), [](const instruction& ins) { return ins.name() == "@return"; })); return this->add_return(args); } shape r = compute_shape(last->get_operator(), args); instruction::replace(last, last->get_operator(), r, std::move(args)); assert(last->valid(begin())); return last; } shape module::get_parameter_shape(std::string name) const { auto ins = std::find_if( impl->instructions.begin(), impl->instructions.end(), [&](const instruction& x) { if(x.name() == "@param") { return any_cast(x.get_operator()).parameter == name; } else { return false; } }); if(ins != this->end()) return ins->get_shape(); else return {}; } std::vector module::get_parameter_names() const { std::vector result; std::vector params; for(auto&& ins : impl->instructions) { if(ins.name() == "@param") { auto&& param = any_cast(ins.get_operator()); params.push_back(param); } } std::stable_sort( params.begin(), params.end(), by(std::less<>{}, [](auto&& p) { return p.order; })); std::transform(params.begin(), params.end(), std::back_inserter(result), [&](auto&& p) { return p.parameter; }); return result; } instruction_ref module::get_parameter(std::string name) const { auto ins = std::find_if( impl->instructions.begin(), impl->instructions.end(), [&](const instruction& x) { if(x.name() == "@param") { return any_cast(x.get_operator()).parameter == name; } else { return false; } }); if(ins != this->end()) return ins; else return this->end(); } std::vector module::get_parameters() const { std::vector result; auto refs = iterator_for(*this); std::copy_if(refs.begin(), refs.end(), std::back_inserter(result), [&](instruction_ref ins) { return ins->name() == "@param"; }); return result; } void module::rename_parameter(instruction_ref ins, const std::string& name) { impl->changed.notify(); assert(ins->name() == "@param"); auto op = any_cast(ins->get_operator()); op.parameter = name; auto outputs = ins->outputs(); *ins = instruction{op, ins->get_shape(), {}}; for(auto output : outputs) ins->add_output(output); } std::unordered_map module::get_parameter_shapes() const { std::unordered_map result; for(auto&& ins : impl->instructions) { if(ins.name() == "@param") { auto&& name = any_cast(ins.get_operator()).parameter; result[name] = ins.get_shape(); } } return result; } bool module::has_instruction(instruction_ref ins) const { return impl->contains(ins); } std::size_t module::size() const { return impl->instructions.size(); } instruction_ref module::begin() const { return impl->instructions.begin(); } instruction_ref module::end() const { return impl->instructions.end(); } instruction_ref module::insert_end() const { auto e = impl->instructions.end(); if(impl->instructions.empty()) return e; auto last_ins = std::prev(e); if(last_ins->name() == "@return") return last_ins; return e; } std::vector module::get_output_shapes() const { if(impl->instructions.empty()) return {}; auto last_ins = impl->instructions.back(); if(last_ins.name() == "@return") { const auto& output_ins = last_ins.inputs(); std::vector output_shapes; std::transform(output_ins.begin(), output_ins.end(), std::back_inserter(output_shapes), [](auto& ins) { return ins->get_shape(); }); return output_shapes; } // The else branch is to provide backward compatibility else { return {last_ins.get_shape()}; } } std::vector module::compute_shapes(const std::vector& inputs, compute_shapes_options options) const { auto params = this->get_parameter_names(); std::sort(params.begin(), params.end()); std::unordered_map ins_shapes; std::unordered_map adjusted_param_shapes; std::transform(inputs.begin(), inputs.end(), params.begin(), std::inserter(adjusted_param_shapes, adjusted_param_shapes.end()), [](auto ps, const auto& name) { return std::make_pair(name, ps); }); for(auto ins : iterator_for(*this)) { if(ins->name() == "@param") { ins_shapes[ins] = adjusted_param_shapes[any_cast(ins->get_operator()).parameter]; if(options.strict_type and ins->get_shape().type() != ins_shapes[ins].type()) { MIGRAPHX_THROW(options.name + ": Mismatched type: expected " + ins->get_shape().type_string() + " but passed " + ins_shapes[ins].type_string()); } if(options.strict_lens and ins->get_shape().lens() != ins_shapes[ins].lens()) { MIGRAPHX_THROW(options.name + ": Mismatched lens: expected {" + to_string_range(ins->get_shape().lens()) + "} but passed {" + to_string_range(ins_shapes[ins].lens()) + "}"); } } else if(ins->name() == "@literal") { if(not options.scalar_const_out_lens.empty() and ins->get_shape().scalar()) { std::vector strides(options.scalar_const_out_lens.size()); ins_shapes[ins] = shape{ins->get_shape().type(), options.scalar_const_out_lens, strides}; } else { ins_shapes[ins] = ins->get_shape(); } } else { std::vector input_shapes; input_shapes.resize(ins->inputs().size()); std::transform(ins->inputs().begin(), ins->inputs().end(), input_shapes.begin(), [&](auto in) { return ins_shapes.at(in); }); if(ins->name() == "@return") return input_shapes; ins_shapes[ins] = ins->get_operator().compute_shape(input_shapes, ins->module_inputs()); } } MIGRAPHX_THROW("No return found in the submodule"); } std::vector module::compute_shapes(const std::vector& inputs) const { return compute_shapes(inputs, {}); } std::vector module::get_returns() const { auto last = std::prev(this->end()); if(last->name() == "@return") return last->inputs(); return {last}; } instruction_ref module::validate() const { return std::find_if( impl->instructions.begin(), impl->instructions.end(), [&](const instruction& i) { auto inputs = i.inputs(); bool check_order = std::all_of( inputs.begin(), inputs.end(), [&](auto in) { return has_instruction(in); }); return not i.valid(impl->instructions.begin(), check_order); }); } static bool is_borrowed(instruction_ref ins) { auto alias = instruction::get_output_alias(ins, true); if(alias == ins) return false; lifetime l = alias->get_operator().get_lifetime(); if(l == lifetime::borrow) return true; return is_borrowed(alias); } static bool is_global(instruction_ref ins) { const auto& op = instruction::get_output_alias(ins)->get_operator(); return op.name() == "@param" or op.get_lifetime() == lifetime::global; } static bool is_dangling(instruction_ref ins) { return not is_global(ins) and is_borrowed(ins); } instruction_ref module::find_dangling_reference() const { auto last = std::prev(end()); if(last->name() == "@return") { auto dangling = std::find_if( last->inputs().begin(), last->inputs().end(), [](auto x) { return is_dangling(x); }); if(dangling != last->inputs().end()) return *dangling; } else if(is_dangling(last)) { return last; } return end(); } void module::finalize(std::vector& contexts) { assert(not contexts.empty()); const bool trace = enabled(MIGRAPHX_TRACE_FINALIZE{}); for(auto ins : iterator_for(*this)) { if(trace) { std::cout << "Finalize: "; this->debug_print(ins); } ins->finalize(contexts[ins->get_target_id()]); for(const auto& smod : ins->module_inputs()) { smod->finalize(contexts); } } // Warn when an instruction is not normalized auto ins = std::find_if(begin(), end(), [](auto& i) { return i.need_normalization(); }); if(ins != end()) std::cerr << "WARNING: Instruction needs normalization, performance may be affected." << std::endl; } std::unordered_map module::get_ins_param_map(const std::vector& inputs, bool reverse) const { std::unordered_map result; auto params = this->get_parameters(); assert(params.size() == inputs.size()); sort_params(params); if(reverse) { std::transform( params.begin(), params.end(), inputs.begin(), std::inserter(result, result.end()), [&](instruction_ref param, auto input) { return std::make_pair(param, input); }); } else { std::transform( params.begin(), params.end(), inputs.begin(), std::inserter(result, result.end()), [&](instruction_ref param, auto input) { return std::make_pair(input, param); }); } return result; } std::vector module::get_inputs(const std::unordered_map& map_ins) const { std::vector inputs; auto params = this->get_parameters(); sort_params(params); std::transform(params.begin(), params.end(), std::back_inserter(inputs), [&](instruction_ref param) { return map_ins.at(param); }); return inputs; } static std::vector select_params(const std::vector& instructions, const std::unordered_map& param_map) { std::vector result; std::vector params; std::copy_if(instructions.begin(), instructions.end(), std::back_inserter(params), [&](instruction_ref ins) { return contains(param_map, ins); }); sort_params(params); std::transform(params.begin(), params.end(), std::back_inserter(result), [&](instruction_ref ins) { return param_map.at(ins); }); return result; } static std::array generic_split(const module& m, const std::vector& args, const std::vector& splits, std::unordered_map* map_ins = nullptr) { std::unordered_map param_map = m.get_ins_param_map(args, true); std::unordered_set selected_instructions; fix([&](auto self, const std::vector& inputs) { for(auto input : inputs) { if(contains(selected_instructions, input)) continue; selected_instructions.insert(input); self(input->inputs()); } })(splits); std::vector instructions1; // TODO: copy_if for(auto ins : iterator_for(m)) { if(not contains(selected_instructions, ins)) continue; instructions1.push_back(ins); } std::vector inputs1 = select_params(instructions1, param_map); module m1; std::unordered_map map_ins1; m1.add_instructions(instructions1, &map_ins1); std::vector outputs; std::transform(splits.begin(), splits.end(), std::back_inserter(outputs), [&](instruction_ref ins) { return map_ins1.at(ins); }); m1.add_return(outputs); std::vector instructions2; for(auto ins : iterator_for(m)) { if(contains(selected_instructions, ins)) continue; // Input params can be used in both modules std::vector input_params; std::copy_if(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(input_params), [&](instruction_ref input) { if(input->name() != "@param") return false; return not contains(instructions2, input); }); instructions2.insert(instructions2.end(), input_params.begin(), input_params.end()); instructions2.push_back(ins); } std::vector inputs2 = splits; module m2; std::size_t n = 0; std::unordered_map map_ins2; for(auto ins : splits) map_ins2[ins] = m2.add_parameter(param_name(n++), ins->get_shape().as_standard()); for(auto ins : iterator_for(m)) { if(ins->name() != "@param") continue; if(not contains(instructions2, ins)) continue; inputs2.push_back(param_map.at(ins)); map_ins2[ins] = m2.add_parameter(param_name(n++), ins->get_shape().as_standard()); } auto r = m2.add_instructions(instructions2, &map_ins2); m2.add_return(r); if(map_ins != nullptr) *map_ins = map_ins2; return {{{std::move(m1), std::move(inputs1)}, {std::move(m2), std::move(inputs2)}}}; } std::array module::split(const std::vector& args, const std::vector& splits) const { return generic_split(*this, args, splits); } std::array module::split(const std::vector& args, const std::vector& splits1, const std::vector& splits2) const { std::unordered_map map_ins; auto mods1 = generic_split(*this, args, splits1, &map_ins); assert(all_of(mods1[0].inputs, [&](auto ins) { return contains(args, ins); })); assert(all_of(mods1[1].inputs, [&](auto ins) { return contains(args, ins) or contains(splits1, ins); })); std::vector new_splits2; std::transform(splits2.begin(), splits2.end(), std::back_inserter(new_splits2), [&](auto ins) { return map_ins.at(ins); }); auto mods2 = mods1[1].mod.split(mods1[1].inputs, new_splits2); // Replace new splits with old splits mods2[1].replace(new_splits2, splits2); assert(all_of(mods2[0].inputs, [&](auto ins) { return contains(args, ins) or contains(splits1, ins); })); assert(all_of(mods2[1].inputs, [&](auto ins) { return contains(args, ins) or contains(splits1, ins) or contains(splits2, ins); })); return {{std::move(mods1[0]), std::move(mods2[0]), std::move(mods2[1])}}; } // Insert parameters into the module based on the input instructions and then // update the map_ins to map the input to the parameter. static void insert_params(module& m, const std::vector& inputs, std::unordered_map& map_ins, const std::function& shape_transform = nullptr) { auto n = m.get_parameter_shapes().size(); for(auto input : inputs) { if(contains(map_ins, input)) continue; auto s = shape_transform ? shape_transform(input->get_shape()) : input->get_shape().as_standard(); map_ins[input] = m.add_parameter(param_name(n++), s); } } void module::add_params(const std::vector& inputs, std::unordered_map* map_ins, const std::function& shape_transform) { std::unordered_map default_map_ins; if(map_ins == nullptr) map_ins = &default_map_ins; insert_params(*this, inputs, *map_ins, shape_transform); } std::vector module::fuse(const std::vector& inss, std::unordered_map* map_ins, module::inserter insert, const std::function& shape_transform) { std::unordered_map default_map_ins; if(map_ins == nullptr) map_ins = &default_map_ins; std::vector inputs; for(auto ins : inss) { for(auto input : ins->inputs()) { if(contains(inss, input)) continue; if(contains(inputs, input)) continue; inputs.push_back(input); } } insert_params(*this, inputs, *map_ins, shape_transform); return this->add_instructions(inss, map_ins, std::move(insert)); } std::vector module::fuse(const module& m, const std::vector& inputs, std::unordered_map* map_ins, module::inserter insert, const std::function& shape_transform) { std::unordered_map default_map_ins; if(map_ins == nullptr) map_ins = &default_map_ins; insert_params(*this, inputs, *map_ins, shape_transform); auto param_map = m.get_ins_param_map(inputs, true); for(auto&& [param, input] : param_map) { (*map_ins)[param] = map_ins->at(input); } return this->add_instructions(&m, map_ins, std::move(insert)); } std::vector module::insert_inline(instruction_ref ins, const module& m, const std::vector& inputs, std::unordered_map* map_ins, module::inserter insert) { std::unordered_map default_map_ins; if(map_ins == nullptr) map_ins = &default_map_ins; auto param_map = m.get_ins_param_map(inputs, true); map_ins->insert(param_map.begin(), param_map.end()); return this->insert_instructions(ins, &m, map_ins, std::move(insert)); } void module_with_inputs::replace(instruction_ref ins, instruction_ref rep) { auto it = std::find(inputs.begin(), inputs.end(), ins); if(it == inputs.end()) return; assert((*it)->get_shape().lens() == rep->get_shape().lens()); *it = rep; } void module_with_inputs::replace( const std::unordered_map& map_ins) { for(auto& ins : inputs) { if(not contains(map_ins, ins)) continue; assert(ins->get_shape().lens() == map_ins.at(ins)->get_shape().lens()); ins = map_ins.at(ins); } } void module_with_inputs::replace(const std::vector& keys, const std::vector& values) { for(auto& ins : inputs) { auto it = std::find(keys.begin(), keys.end(), ins); if(it == keys.end()) continue; assert(ins->get_shape().lens() == values[it - keys.begin()]->get_shape().lens()); ins = values[it - keys.begin()]; } } void module::debug_print() const { std::cout << *this << std::endl; } void module::debug_print(instruction_ref ins, std::unordered_map& names) const { if(is_end(ins, this->end())) { std::cout << "End instruction" << std::endl; return; } if(not has_instruction(ins)) { std::cout << "Instruction not part of module" << std::endl; return; } names = this->print( [&](auto x, const auto& ins_names) { if(x == ins) { instruction::print(std::cout, x, ins_names); std::cout << std::endl; } }, names); } void module::debug_print(instruction_ref ins) const { std::unordered_map names; this->debug_print(ins, names); } void module::debug_print(const std::vector& inss) const { for(auto ins : inss) this->debug_print(ins); std::cout << std::endl; } std::unordered_map module::print( const std::function&)>& print_func, std::unordered_map names) const { const bool is_root = names.empty(); int count = 0; for(auto ins : iterator_for(*this)) { std::string var_name; if(not this->name().empty() and not is_root) var_name = this->name() + ":"; if(ins->name() == "@param") { var_name.append(any_cast(ins->get_operator()).parameter); } else { var_name.append("@" + std::to_string(count)); } // count every instruction so index matches loc in the printout program count++; names.emplace(ins, var_name); print_func(ins, names); } return names; } void module::print(const std::function< void(instruction_ref, const std::unordered_map&)>& print_func) const { this->print(print_func, {}); } static std::string enclose_name(const std::string& name) { return '"' + replace_string(name, "\"", "\\\"") + '"'; } void module::print_graph(std::ostream& os, bool brief) const { os << "digraph {" << std::endl; os << "\trankdir=LR;" << std::endl; this->print([&](auto ins, auto ins_names) { std::string label; if(brief) label = ins->name(); else label = to_string(ins->get_operator()); os << "\t" << enclose_name(ins_names.at(ins)) << "[label=" << enclose_name(label) << "]"; os << ";" << std::endl; if(not ins->inputs().empty()) { for(auto&& arg : ins->inputs()) { os << "\t" << enclose_name(ins_names.at(arg)) << " -> " << enclose_name(ins_names.at(ins)); if(not brief) os << "[label=" << enclose_name(to_string(ins->get_shape())) << "]"; os << ";" << std::endl; } } }); os << "}" << std::endl; } static std::string cpp_var_name(const std::string& name) { std::string prefix = "x_"; if(not contains(name, "@")) prefix = "p_"; return to_c_id(prefix + replace_string(name, ":", "_module_")); } static void print_py_op(std::ostream& os, const operation& op) { auto v = op.to_value(); os << "migraphx.op(" << enclose_name(op.name()); auto default_values = make_op(op.name()).to_value(); for(auto&& x : v) { auto name = x.get_key(); if(default_values[name] == x) continue; os << ", " << name << "=" << to_json_string(x.without_key()); } os << ")"; } static void print_make_op(std::ostream& os, const operation& op) { auto v = op.to_value(); if(not v.empty()) { os << "migraphx::make_json_op(" << enclose_name(op.name()); os << ", " << enclose_name(to_json_string(v)); } else { os << "migraphx::make_op(" << enclose_name(op.name()); } os << ")"; } static void print_py_shape(std::ostream& os, const migraphx::shape& s) { os << "migraphx.shape(type=" << to_json_string(s.type_string()) << ", lens=[" << to_string_range(s.lens()) << "]"; if(not s.standard()) os << ", strides=[" << to_string_range(s.strides()) << "]"; os << ")"; } static void print_cpp_shape(std::ostream& os, const migraphx::shape& s) { os << "migraphx::shape{migraphx::shape::" << s.type_string(); os << ", {" << to_string_range(s.lens()) << "}"; if(not s.standard()) os << ", {" << to_string_range(s.strides()) << "}"; os << "}"; } std::unordered_map module::print_py(std::ostream& os, const std::string& mname, std::unordered_map names) const { // cppcheck-suppress variableScope unsigned long seed = names.size(); auto last = std::prev(this->end()); names = this->print( [&](auto ins, auto ins_names) { std::vector input_vars; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(input_vars), [&](auto input) { return cpp_var_name(ins_names.at(input)); }); if(ins != last) os << cpp_var_name(ins_names.at(ins)) << " = "; if(ins->name() == "@literal") { os << mname << ".add_literal("; if(ins->get_shape().elements() < 10) { os << "migraphx.create_argument("; print_py_shape(os, ins->get_shape()); os << ", [" << ins->get_literal() << "])"; } else { const bool use_abs = false; // Disable abs for now // ins->get_literal().visit([&](auto v) { // use_abs = std::none_of(v.begin(), v.end(), [](auto x) { return x < 0; }); // }); if(use_abs) os << "migraphx.abs_literal("; os << "migraphx.generate_argument("; print_py_shape(os, ins->get_shape()); os << ", " << seed << ")"; if(use_abs) os << ")"; seed++; } os << ")" << std::endl; } else if(ins->name() == "@param") { std::string name = any_cast(ins->get_operator()).parameter; os << mname << ".add_parameter(" << enclose_name(name) << ", "; print_py_shape(os, ins->get_shape()); os << ")" << std::endl; } else if(ins->name() == "@return") { os << mname << ".add_return([" << join_strings(input_vars, ", ") << "])" << std::endl; } else { assert(ins->name().front() != '@'); os << mname << ".add_instruction("; print_py_op(os, ins->get_operator()); os << ", [" << join_strings(input_vars, ", ") << "]"; os << ") # "; print_py_shape(os, ins->get_shape()); os << std::endl; } }, names); return names; } std::unordered_map module::print_cpp(std::ostream& os, const std::string& mname, std::unordered_map names) const { // cppcheck-suppress variableScope unsigned long seed = names.size(); auto last = std::prev(this->end()); names = this->print( [&](auto ins, auto ins_names) { std::vector input_vars; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(input_vars), [&](auto input) { return cpp_var_name(ins_names.at(input)); }); if(ins != last) os << "auto " << cpp_var_name(ins_names.at(ins)) << " = "; if(ins->name() == "@literal") { os << mname << "->add_literal("; bool use_abs = false; ins->get_literal().visit([&](auto v) { use_abs = std::none_of(v.begin(), v.end(), [](auto x) { return x < 0; }); }); if(use_abs) os << "migraphx::abs("; os << "migraphx::generate_literal("; print_cpp_shape(os, ins->get_shape()); os << ", " << seed << ")"; if(use_abs) os << ")"; os << ");" << std::endl; seed++; } else if(ins->name() == "@param") { std::string name = any_cast(ins->get_operator()).parameter; os << mname << "->add_parameter(" << enclose_name(name) << ","; print_cpp_shape(os, ins->get_shape()); os << ");" << std::endl; } else if(ins->name() == "@return") { os << mname << "->add_return({"; os << join_strings(input_vars, ", "); os << "});" << std::endl; } else { assert(ins->name().front() != '@'); os << mname << "->add_instruction("; print_make_op(os, ins->get_operator()); os << ", " << join_strings(input_vars, ", "); os << ");" << std::endl; } }, names); return names; } void module::print_py(std::ostream& os) const { this->print_py(os, this->name(), {}); } void module::print_cpp(std::ostream& os) const { this->print_cpp(os, this->name(), {}); } void module::annotate(std::ostream& os, std::function a) const { this->print([&](auto ins, const auto& ins_names) { instruction::print(os, ins, ins_names); a(ins); os << std::endl; }); } std::vector module::get_sub_modules(bool shallow) const { std::vector vec_modules; for(auto ins : iterator_for(*this)) { const auto& mod_args = ins->module_inputs(); vec_modules.insert(vec_modules.end(), mod_args.begin(), mod_args.end()); if(not shallow) { for(const auto& smod : mod_args) { auto sub_mods = smod->get_sub_modules(); vec_modules.insert(vec_modules.end(), sub_mods.begin(), sub_mods.end()); } } } return vec_modules; } module& module::sort() { if(this->begin() == this->end()) return *this; std::unordered_set visited; auto implicit_deps = calc_implicit_deps(); std::vector lasts; copy_if(iterator_for(*this), std::back_inserter(lasts), [&](auto last) { return last->outputs().empty(); }); for(auto last : lasts) { fix([&](auto self, auto ins) { if(visited.insert(ins).second == false) return; auto ins_inputs = ins->inputs(); if(implicit_deps.find(ins) != implicit_deps.end()) { auto ins_implict_inputs = implicit_deps.at(ins); ins_inputs.insert( ins_inputs.end(), ins_implict_inputs.begin(), ins_implict_inputs.end()); } for(auto child : ins_inputs) { if(not contains(this->impl->instructions, child)) continue; self(child); } this->move_instruction(ins, this->end()); })(last); } assert(this->validate() == this->end()); return *this; } module& module::shuffle(std::vector permutation) { if(permutation.empty()) return *this; const std::size_t n = this->impl->instructions.size(); assert(permutation.size() == n and "permutation size must match list size"); auto it_dest = this->impl->instructions.begin(); for(std::size_t i = 0; i < n; ++i) { // find j >= i such that permutation[j] == i auto itp = std::find(permutation.begin() + i, permutation.end(), i); assert(itp != permutation.end()); std::size_t j = std::distance(permutation.begin(), itp); std::size_t dist = j - i; auto it_src = it_dest; std::advance(it_src, dist); if(dist > 0) this->impl->instructions.splice(it_dest, this->impl->instructions, it_src); // Rotate permutation[i..j] right by 1 so that permutation[i] == i std::rotate(permutation.begin() + i, permutation.begin() + j, permutation.begin() + j + 1); // if nothing moved (dist==0), advance it_dest manually; // otherwise, splice has already pushed the old it_dest forward if(dist == 0) ++it_dest; } return *this; } void module::calc_implicit_deps(const module& smod, const module& pmod, instruction_ref ins, ins_dep_map& deps) const { const auto& ins_inputs = ins->inputs(); for(auto ii : iterator_for(smod)) { const auto& ii_inputs = ii->inputs(); for(auto iii : ii_inputs) { if(pmod.has_instruction(iii)) { if(not contains(ins_inputs, iii)) deps[ins].push_back(iii); } } const auto& mod_args = ii->module_inputs(); for(const auto* ssmod : mod_args) { calc_implicit_deps(*ssmod, pmod, ins, deps); } } } ins_dep_map module::calc_implicit_deps() const { ins_dep_map mod_implicit_deps; for(auto ins : iterator_for(*this)) { const auto& mod_args = ins->module_inputs(); if(mod_args.empty()) { continue; } for(const auto* mod : mod_args) { calc_implicit_deps(*mod, *this, ins, mod_implicit_deps); } } return mod_implicit_deps; } void module::repeat_while_changes(std::size_t n, const std::function& f) { if(n == 0) return; if(n == 1) { f(); return; } auto has_changed = impl->changed.subscribe(); for(auto i : range(n)) { f(); if(not has_changed) break; (void)i; } } // For topologically sorting a region in a module, canonically, such that the // dependent chain between the two input instructions is last void module::localized_sort(instruction_ref start_ins, instruction_ref end_ins) { // get the chain of instructions between start_ins and end_ins, inclusive auto fusion_ins = find_instructions_between(start_ins, end_ins, this); // move all instructions between start_ins & end_ins that are not in the fusion chain // to the start_ins. In order, moving to the same destination, this will naturally preserve // the preexisting topological order of the module for(auto it = std::next(start_ins); it != end_ins;) { if(fusion_ins.count(it) == 0) { auto next = std::next(it); // move_instruction updates the iterator this->move_instruction(it, start_ins); it = next; } else { ++it; } } } bool operator==(const module& x, const module& y) { return to_string(x) == to_string(y); } std::ostream& operator<<(std::ostream& os, const module& m) { m.print([&](auto ins, const auto& ins_names) { instruction::print(os, ins, ins_names); os << std::endl; }); return os; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/msgpack.cpp000066400000000000000000000177351510465702400174750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { // Leave an extra byte for error checking constexpr std::size_t msgpack_size_limit = std::numeric_limits::max() - 1; template static std::size_t msgpack_chunk_size(const Range& r) { return 1 + (r.size() - 1) / msgpack_size_limit; } template static void msgpack_chunk_for_each(Iterator start, Iterator last, F f) { while(std::distance(start, last) > msgpack_size_limit) { auto next = std::next(start, msgpack_size_limit); f(start, next); start = next; } f(start, last); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx namespace msgpack { MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) { namespace adaptor { template <> struct convert { const msgpack::object& operator()(const msgpack::object& o, migraphx::value& v) const { switch(o.type) { case msgpack::type::NIL: { v = nullptr; break; } case msgpack::type::BOOLEAN: { v = o.as(); break; } case msgpack::type::POSITIVE_INTEGER: { v = o.as(); break; } case msgpack::type::NEGATIVE_INTEGER: { v = o.as(); break; } case msgpack::type::FLOAT32: case msgpack::type::FLOAT64: { v = o.as(); break; } case msgpack::type::STR: { v = o.as(); break; } case msgpack::type::BIN: { // For backwards compatibility v = migraphx::value::binary{o.via.bin.ptr, o.via.bin.size}; break; } case msgpack::type::ARRAY: { if(o.via.array.size != 0 and o.via.array.ptr->type == msgpack::type::BIN) { auto bin = migraphx::value::binary{}; std::for_each( o.via.array.ptr, o.via.array.ptr + o.via.array.size, [&](const msgpack::object& so) { bin.insert(bin.end(), so.via.bin.ptr, so.via.bin.ptr + so.via.bin.size); }); v = bin; } else { migraphx::value r = migraphx::value::array{}; std::for_each( o.via.array.ptr, o.via.array.ptr + o.via.array.size, [&](const msgpack::object& so) { r.push_back(so.as()); }); v = r; } break; } case msgpack::type::MAP: { migraphx::value r = migraphx::value::object{}; std::for_each(o.via.map.ptr, o.via.map.ptr + o.via.map.size, [&](const msgpack::object_kv& p) { r[p.key.as()] = p.val.as(); }); v = r; break; } case msgpack::type::EXT: { MIGRAPHX_THROW("msgpack EXT type not supported."); } } return o; } }; template <> struct pack { template packer& operator()(msgpack::packer& o, const migraphx::value::binary& x) const { const auto* data = reinterpret_cast(x.data()); auto size = x.size(); o.pack_array(migraphx::msgpack_chunk_size(x)); migraphx::msgpack_chunk_for_each( data, data + size, [&](const char* start, const char* last) { o.pack_bin(last - start); o.pack_bin_body(start, last - start); }); return o; } }; template <> struct pack { template void write(msgpack::packer& o, const std::nullptr_t&) const { o.pack_nil(); } template void write(msgpack::packer& o, const T& x) const { o.pack(x); } template void write(msgpack::packer& o, const std::vector& v) const { if(v.empty()) { o.pack_array(0); return; } if(v.size() > migraphx::msgpack_size_limit) MIGRAPHX_THROW("Size is too large for msgpack"); if(not v.front().get_key().empty()) { o.pack_map(v.size()); for(auto&& x : v) { o.pack(x.get_key()); o.pack(x.without_key()); } } else { o.pack_array(v.size()); for(auto&& x : v) { o.pack(x); } } } template packer& operator()(msgpack::packer& o, const migraphx::value& v) const { v.visit_value([&](auto&& x) { this->write(o, x); }); return o; } }; } // namespace adaptor } // MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) } // namespace msgpack namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct vector_stream { std::vector buffer{}; vector_stream& write(const char* b, std::size_t n) { buffer.insert(buffer.end(), b, b + n); return *this; } }; struct writer_stream { std::function writer; writer_stream& write(const char* b, std::size_t n) { writer(b, n); return *this; } }; void to_msgpack(const value& v, std::function writer) { writer_stream ws{std::move(writer)}; msgpack::pack(ws, v); } std::vector to_msgpack(const value& v) { vector_stream vs; msgpack::pack(vs, v); return vs.buffer; } value from_msgpack(const char* buffer, std::size_t size) { msgpack::object_handle oh = msgpack::unpack(buffer, size); return oh.get().as(); } value from_msgpack(const std::vector& buffer) { return from_msgpack(buffer.data(), buffer.size()); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/netron_output.cpp000066400000000000000000000223601510465702400207630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { // from https://onnx.ai/onnx/intro/concepts.html int get_onnx_type(shape::type_t s_type) { switch(s_type) { case shape::float_type: return 1; case shape::uint8_type: return 2; case shape::int8_type: return 3; case shape::uint16_type: return 4; case shape::int16_type: return 5; case shape::int32_type: return 6; case shape::int64_type: return 7; case shape::bool_type: return 9; case shape::half_type: return 10; case shape::double_type: return 11; case shape::uint32_type: return 12; case shape::uint64_type: return 13; case shape::bf16_type: return 16; case shape::fp8e4m3fn_type: return 17; case shape::fp8e4m3fnuz_type: return 18; case shape::fp8e5m2_type: return 19; case shape::fp8e5m2fnuz_type: return 20; case shape::tuple_type: return 0; case shape::fp4x2_type: return 21; // TODO update this when the type is added } MIGRAPHX_THROW("MIGraphX type " + std::to_string(s_type) + " not supported"); } auto make_attribute(const migraphx::value& val) { value attribute = value(std::unordered_map()); attribute["name"] = val.get_key(); auto val_string = val.to(); std::string sub_str = val.get_key() + ":"; auto find_key = val_string.find(sub_str); if(find_key != std::string::npos) { val_string = val_string.substr(find_key + sub_str.length() + 1); } // TODO: doesn't work for some reason with Netron now // attribute["s"] = base64_encode(val_string); // attribute["type"] = "STRING"; attribute["docString"] = val_string; return attribute; } /// Returns a value with the JSON structure needed for a node auto make_onnx_json_node(instruction_ref ins, std::unordered_map ins_uids) { value node; // TODO add support for module inputs value input_arr = value({}); for(instruction_ref input_ins : ins->inputs()) { auto name = input_ins->name(); if(name == "@literal" or name == "@param") { input_arr.push_back(ins_uids.at(input_ins)); } // TODO make a better process for handling nodes to ignore else if(name.find("hip::hip_allocate_memory") != std::string::npos) { continue; } else { input_arr.push_back(ins_uids.at(input_ins) + "->" + ins_uids.at(ins)); } } value output_arr = value({}); for(instruction_ref output_ins : ins->outputs()) { if(output_ins->name() == "@return") { output_arr.push_back(ins_uids.at(output_ins)); } else { output_arr.push_back(ins_uids.at(ins) + "->" + ins_uids.at(output_ins)); } } node["input"] = input_arr; node["output"] = output_arr; node["name"] = ins_uids.at(ins); node["opType"] = ins->name(); value op_attribute_arr = value({}); auto op_value = ins->get_operator().to_value(); std::for_each(op_value.begin(), op_value.end(), [&](const auto& v) { const std::string& attr_key = v.get_key(); if(v.is_binary() or attr_key == "code_object") { return; } else if(attr_key == "symbol_name" or attr_key == "name") { node["opType"] = migraphx::from_value(v); } else { op_attribute_arr.push_back(make_attribute(v)); } }); node["attribute"] = op_attribute_arr; return node; } // ONNX graph constant data called "initializer" auto make_onnx_json_literal(instruction_ref ins, std::unordered_map ins_uids) { value lit; lit["dims"] = ins->get_shape().lens(); lit["dataType"] = get_onnx_type(ins->get_shape().type()); lit["name"] = ins_uids.at(ins); // ignoring literal data, setting to "NULL" in base64 lit["rawData"] = "TlVMTA=="; return lit; } // TODO handle dynamic shapes // TODO handle subshapes auto make_onnx_json_shape(const shape& s) { value ret; value dim = value({}); for(std::size_t len : s.lens()) { // cppcheck-suppress useStlAlgorithm dim.push_back({{"dimValue", len}}); } ret["dim"] = dim; return ret; } // ONNX graph edges called "valueType" auto make_onnx_json_edge(instruction_ref ins, instruction_ref out_ins, std::unordered_map ins_uids) { value ret; shape ins_shape = ins->get_shape(); ret["name"] = ins_uids.at(ins) + "->" + ins_uids.at(out_ins); value type = {{"tensorType", {{"elemType", get_onnx_type(ins_shape.type())}, {"shape", make_onnx_json_shape(ins_shape)}}}}; ret["type"] = type; return ret; } auto make_onnx_json_in_out(instruction_ref ins, std::unordered_map ins_uids) { value ret; shape ins_shape = ins->get_shape(); ret["name"] = ins_uids.at(ins); value type = {{"tensorType", {{"elemType", get_onnx_type(ins_shape.type())}, {"shape", make_onnx_json_shape(ins_shape)}}}}; ret["type"] = type; return ret; } std::unordered_map make_ins_uids(const module& mod) { std::unordered_map ret; int count = 0; for(auto ins : iterator_for(mod)) { std::string var_name; var_name = mod.name() + ":"; var_name.append(ins->name() + ":"); if(ins->name() == "@param") { var_name.append(any_cast(ins->get_operator()).parameter + ":"); } var_name.append("@" + std::to_string(count)); count++; ret.emplace(ins, var_name); } return ret; } value make_graph(const module* mod) { value graph = {{"node", value({})}, {"initializer", value({})}, {"input", value({})}, {"output", value({})}, {"valueInfo", value({})}}; auto ins_uids = make_ins_uids(*mod); for(auto ins = mod->begin(); ins != mod->end(); ++ins) { const auto& name = ins->name(); if(name == "@literal") { graph["initializer"].push_back(make_onnx_json_literal(ins, ins_uids)); } else if(name == "@param") { graph["input"].push_back(make_onnx_json_in_out(ins, ins_uids)); } else if(name == "@return") { graph["output"].push_back(make_onnx_json_in_out(ins, ins_uids)); } else if(name.find("hip::hip_allocate_memory") != std::string::npos) { continue; } else { graph["node"].push_back(make_onnx_json_node(ins, ins_uids)); const auto& outputs = ins->outputs(); for(auto out_ins : outputs) { if(out_ins->name() != "@return") { graph["valueInfo"].push_back(make_onnx_json_edge(ins, out_ins, ins_uids)); } } } } return graph; } } // namespace std::string make_netron_output(const program& prog) { value output; auto prog_value = prog.to_value(); // ONNX IR version 6 // TODO: investigate sure how this affects things output["irVersion"] = 6; output["producerName"] = "AMDMIGraphX"; output["producerVersion"] = prog_value.at("migraphx_version").to(); for(auto& mod : prog.get_modules()) { auto graph = make_graph(mod); output["graph"] = graph; } return to_pretty_json_string(output, 4); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/normalize_attributes.cpp000066400000000000000000000235211510465702400223040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * Parameters: * vec: the vector attribute to normalize * axes: the operator's axes attribute if it exists, empty otherwise * val: the normalize_axes key and options. Ex: normalize["axes"] = * value::array{normalize_attribute::include_min}; * input_shape: input shape passed when calling * normalize_attributes(op&, input_shape) * * See normalize_attribute.hpp for explaining the options. */ template static auto tune_attribute(const std::vector& vec, const std::vector& axes, const value& val, const shape& input_shape, Message m) { std::vector result(vec); if(result.empty()) { return result; }; int64_t n_rank = input_shape.ndim(); std::vector vec_attrs = val.to_vector(); if(contains(vec_attrs, op::normalize_attribute::use_output)) { n_rank = n_rank + vec.size(); } std::vector max_vals(vec.size(), n_rank); if(contains(vec_attrs, op::normalize_attribute::use_len)) { if(input_shape.dynamic()) { // return the unchanged `vec` if the dynamic_dimensions at `axes` are not fixed if(std::any_of(axes.begin(), axes.end(), [&](auto ax) { return not input_shape.dyn_dims().at(ax).is_fixed(); })) { return vec; } std::transform(axes.begin(), axes.end(), max_vals.begin(), [&](auto i) { return input_shape.dyn_dims().at(i).max; }); } else { std::transform(axes.begin(), axes.end(), max_vals.begin(), [&](auto i) { return input_shape.lens().at(i); }); } } if(contains(vec_attrs, op::normalize_attribute::clip_max)) { if(contains(vec_attrs, op::normalize_attribute::include_max)) { std::transform(result.begin(), result.end(), max_vals.begin(), result.begin(), [](auto v, auto mv) { return v > mv ? mv : v; }); } else { std::transform(result.begin(), result.end(), max_vals.begin(), result.begin(), [](auto v, auto mv) { return v >= mv ? mv - 1 : v; }); } } else { if(contains(vec_attrs, op::normalize_attribute::include_max)) { if(not std::equal(result.begin(), result.end(), max_vals.begin(), std::less_equal<>{})) { MIGRAPHX_THROW(m() + "value out of range!"); } } else { if(not std::equal(result.begin(), result.end(), max_vals.begin(), std::less<>{})) { MIGRAPHX_THROW(m() + "value out of range!"); } } } std::vector min_vals = max_vals; std::transform(min_vals.begin(), min_vals.end(), min_vals.begin(), [](auto v) { return -v; }); if(contains(vec_attrs, op::normalize_attribute::clip_min)) { if(contains(vec_attrs, op::normalize_attribute::include_min)) { std::transform(result.begin(), result.end(), min_vals.begin(), result.begin(), [](auto v, auto mv) { return v < mv ? mv : v; }); } else { std::transform(result.begin(), result.end(), min_vals.begin(), result.begin(), [](auto v, auto mv) { return v < mv + 1 ? mv + 1 : v; }); } } else { if(contains(vec_attrs, op::normalize_attribute::include_min)) { if(not std::equal( min_vals.begin(), min_vals.end(), result.begin(), std::less_equal<>{})) { MIGRAPHX_THROW(m() + "attribute out of range!"); } } else { if(not std::equal(result.begin(), result.end(), min_vals.begin(), std::less<>{})) { MIGRAPHX_THROW(m() + "attribute out of range!"); } } } std::transform( result.begin(), result.end(), max_vals.begin(), result.begin(), [](auto v, auto mv) { return v < 0 ? v + mv : v; }); return result; } static auto tune_pad_attribute(const value& val) { std::vector vec_attrs = val.to_vector(); std::vector result(vec_attrs.begin(), vec_attrs.end()); std::copy(vec_attrs.begin(), vec_attrs.end(), std::back_inserter(result)); return result; } /** * Assumptions: * Dimensions to pad start from the third dimension (index 2). * Called by compute_shape_op() with the shape of the first input. */ bool normalize_attributes(operation& op, const shape& input_shape) { bool tuned = false; auto attrs = op.attributes(); auto val = op.to_value(); if(attrs.contains("normalize_padding")) { bool use_auto_padding = (val.contains("padding_mode") and (val.at("padding_mode").to() != migraphx::op::padding_mode_t::default_)); if(not use_auto_padding) { auto padding = val.at(attrs.at("normalize_padding").to()); auto padding_size = padding.size(); auto padding_start = 2; if(padding_size == 2 * (input_shape.ndim() - padding_start)) tuned = true; else if(padding_size != (input_shape.ndim() - padding_start)) { MIGRAPHX_THROW("normalize_attributes: inconsistent padding vector size "); } else { auto result = tune_pad_attribute(padding); val["padding"] = result; op.from_value(val); tuned = true; } } } if(not attrs.contains("normalize_axes")) { return tuned; } auto attr_v = attrs.at("normalize_axes").without_key(); for(const auto& rv : attr_v) { const auto& key = rv.get_key(); if(val.contains(key)) { auto message = [&] { return op.name() + ": " + key + ": "; }; auto vv = val.at(key).without_key(); if(vv.is_array()) { std::vector axes; if(val.contains("axes")) { axes = val.at("axes").without_key().to_vector(); } auto vec = vv.to_vector(); auto result = tune_attribute(vec, axes, rv.without_key(), input_shape, message); val[key] = result; op.from_value(val); val = op.to_value(); tuned = true; } else { auto num = vv.to(); auto result = tune_attribute({num}, {num}, rv.without_key(), input_shape, message); val[key] = result.front(); op.from_value(val); val = op.to_value(); tuned = true; } } else { MIGRAPHX_THROW("NORMALIZE_ATTR : op " + op.name() + " attribute \"" + key + "\" not exist!"); } } return tuned; } std::vector normalize_axes(const std::vector& axes, const shape& input_shape, const value& attr_val, const std::string& prefix) { return tune_attribute(axes, {}, attr_val, input_shape, [&] { return prefix; }); } std::vector normalize_indices(const std::vector& indices, const std::vector& axes, const shape& input_shape, const value& attr_val, const std::string& prefix) { return tune_attribute(indices, axes, attr_val, input_shape, [&] { return prefix; }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/normalize_ops.cpp000066400000000000000000000041141510465702400207140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void normalize_ops::apply(module& m) const { for(auto ins : iterator_for(m)) { auto inputs = ins->inputs(); if(inputs.empty()) continue; auto s = inputs[0]->get_shape(); migraphx::operation tuned_op = ins->get_operator(); if(normalize_attributes(tuned_op, s)) { m.replace_instruction(ins, tuned_op, inputs); ins->set_normalized(); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/000077500000000000000000000000001510465702400163115ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/onnx/CMakeLists.txt000066400000000000000000000054711510465702400210600ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### find_package(protobuf QUIET CONFIG) if(protobuf_FOUND) add_library(onnx-proto STATIC) protobuf_generate(TARGET onnx-proto PROTOS onnx.proto) target_include_directories(onnx-proto SYSTEM PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${PROTOBUF_INCLUDE_DIR}) target_link_libraries(onnx-proto PRIVATE ${PROTOBUF_LIBRARY}) target_link_libraries(onnx-proto PRIVATE protobuf::libprotobuf) else() find_package(Protobuf REQUIRED) protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS onnx.proto) add_library(onnx-proto STATIC ${PROTO_SRCS}) target_include_directories(onnx-proto SYSTEM PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${PROTOBUF_INCLUDE_DIR}) target_link_libraries(onnx-proto PRIVATE ${PROTOBUF_LIBRARY}) endif() if(MSVC) target_compile_options(onnx-proto PRIVATE /w) else() target_compile_options(onnx-proto PRIVATE -w) endif() set_target_properties(onnx-proto PROPERTIES POSITION_INDEPENDENT_CODE On) file(GLOB ONNX_SRCS CONFIGURE_DEPENDS *.cpp) add_library(migraphx_onnx ${ONNX_SRCS}) target_include_directories(migraphx_onnx PRIVATE include) set_target_properties(migraphx_onnx PROPERTIES EXPORT_NAME onnx) migraphx_generate_export_header(migraphx_onnx) rocm_set_soversion(migraphx_onnx ${MIGRAPHX_SO_VERSION}) rocm_clang_tidy_check(migraphx_onnx) target_link_libraries(migraphx_onnx PRIVATE onnx-proto) if(NOT WIN32) target_link_libraries(migraphx_onnx PRIVATE "-Wl,--exclude-libs,ALL") endif() target_link_libraries(migraphx_onnx PUBLIC migraphx) rocm_install_targets( PRIVATE TARGETS migraphx_onnx ) ROCm-AMDMIGraphX-46524e8/src/onnx/broadcast_qdq.cpp000066400000000000000000000064411510465702400216310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // This method is to prep for quantizelinear or dequantizelinear operation for // either the broadcasting of weight-scale or zero-points of qlinearadd operator // outputs: operator op (inputs x, broadcasted: scale (float) & zero_pt (8-bit)) instruction_ref bcast_qdq_instr(const std::string& op_name, instruction_ref x_in, instruction_ref arg_fscale, instruction_ref arg_z_pt, const onnx_parser::node_info& info) { auto in_lens = x_in->get_shape().lens(); // prep 1: broadcast scale. it can come as a scalar or a 1-D tensor. instruction_ref bcast_scale; if(arg_fscale->get_shape().elements() > 1) bcast_scale = info.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", in_lens}}), arg_fscale); else bcast_scale = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", in_lens}}), arg_fscale); // prep 2: broadcast zero point. it can come as a scalar or a 1-D tensor. instruction_ref bcast_zero_pt; if(arg_z_pt->get_shape().elements() > 1) bcast_zero_pt = info.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", in_lens}}), arg_z_pt); else bcast_zero_pt = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", in_lens}}), arg_z_pt); // op_name is either quantizelinear or dequantizelinear: return info.add_instruction(migraphx::make_op(op_name), x_in, bcast_scale, bcast_zero_pt); } // Multibroadcast a scaler.. instruction_ref bcast_scalar_instr(const migraphx::shape& shape_out, instruction_ref arg_in, const onnx_parser::node_info& info) { return info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", shape_out.lens()}}), arg_in); } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/checks.cpp000066400000000000000000000034311510465702400202560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { void check_arg_empty(const argument& arg, const std::string& msg) { if(arg.empty()) { MIGRAPHX_THROW(msg); } } void check_attr_sizes(size_t kdims, size_t attr_size, const std::string& error_msg) { if(kdims != attr_size) { MIGRAPHX_THROW(error_msg + " k-dims: " + std::to_string(kdims) + " attribute size: " + std::to_string(attr_size)); } } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/conv.cpp000066400000000000000000000051271510465702400177670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { void recalc_conv_attributes(value& v, size_t kdims) { if(v["padding"].size() != kdims and v["padding"].size() != kdims * 2) { v["padding"].resize(kdims); std::fill_n(v["padding"].begin(), kdims, 0); } if(v["stride"].size() != kdims) { v["stride"].resize(kdims); std::fill_n(v["stride"].begin(), kdims, 1); } if(v["dilation"].size() != kdims) { v["dilation"].resize(kdims); std::fill_n(v["dilation"].begin(), kdims, 1); } } static instruction_ref apply_nhwc_perm(const onnx_parser::node_info& info, instruction_ref ins, bool invert) { std::vector perm(ins->get_shape().ndim()); std::iota(begin(perm) + 1, end(perm) - 1, 2); perm.back() = 1; return info.add_instruction( make_op("transpose", {{"permutation", invert ? invert_permutation(perm) : perm}}), ins); } instruction_ref from_nhwc(const onnx_parser::node_info& info, instruction_ref ins) { return apply_nhwc_perm(info, ins, true); } instruction_ref to_nhwc(const onnx_parser::node_info& info, instruction_ref ins) { return apply_nhwc_perm(info, ins, false); } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/include/000077500000000000000000000000001510465702400177345ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/000077500000000000000000000000001510465702400215535ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/000077500000000000000000000000001510465702400225355ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/broadcast_qdq.hpp000066400000000000000000000045011510465702400260550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_BROADCAST_QDQ_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_BROADCAST_QDQ_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // This method is to prep for quantizelinear or dequantizelinear operation for // either the broadcasting of weight-scale or zero-points of qlinearadd operator // outputs: operator op (inputs x, broadcasted: scale (float) & zero_pt (8-bit)) instruction_ref bcast_qdq_instr(const std::string& op_name, instruction_ref x_in, instruction_ref arg_fscale, instruction_ref arg_z_pt, const onnx_parser::node_info& info); // Multibroadcast a scaler.. instruction_ref bcast_scalar_instr(const migraphx::shape& shape_out, instruction_ref arg_in, const onnx_parser::node_info& info); } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/checks.hpp000066400000000000000000000032321510465702400245060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_CHECKS_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_CHECKS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { void check_arg_empty(const argument& arg, const std::string& msg); void check_attr_sizes(size_t kdims, size_t attr_size, const std::string& error_msg); } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/conv.hpp000066400000000000000000000034261510465702400242200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_CONV_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_CONV_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { void recalc_conv_attributes(value& v, size_t kdims); instruction_ref from_nhwc(const onnx_parser::node_info& info, instruction_ref ins); instruction_ref to_nhwc(const onnx_parser::node_info& info, instruction_ref ins); } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/map_activation_functions.hpp000066400000000000000000000032161510465702400303360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_MAP_ACTIVATION_FUNCTIONS_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_MAP_ACTIVATION_FUNCTIONS_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { const std::unordered_map& map_activation_functions(); } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/onnx_parser.hpp000066400000000000000000000127441510465702400256140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_PARSER_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_PARSER_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { namespace onnx = onnx_for_migraphx; struct onnx_parser { std::string filename; fs::path path; std::string external_data_path; using attribute_map = std::unordered_map; struct node_info { attribute_map attributes{}; std::size_t num_outputs = 1; std::string name = ""; module* mod = nullptr; instruction_ref make_contiguous(instruction_ref ins) const; instruction_ref add_bias(const std::vector& args, instruction_ref curr_ins, uint64_t axis) const; instruction_ref add_broadcastable_binary_op(const std::string& op_name, instruction_ref arg0, instruction_ref arg1) const; instruction_ref add_common_op(const std::string& op_name, std::vector inputs) const; template instruction_ref add_common_op(const std::string& op_name, Ts... xs) const { return add_common_op(op_name, {xs...}); } instruction_ref add_instruction(const operation& op, const std::vector& args) const; instruction_ref add_instruction(const operation& op, const std::vector& args, const std::vector& mods) const; template instruction_ref add_instruction(const operation& op, Ts... xs) const { return add_instruction(op, {xs...}); } instruction_ref add_literal(literal l) const; template instruction_ref add_literal(Ts&&... xs) const { return add_literal(literal{std::forward(xs)...}); } }; using node_map = std::unordered_map; using op_func = std::function( onnx_parser&, const node_info&, std::vector)>; node_map nodes; std::unordered_set parent_input_nodes; std::unordered_map instructions; program prog = program(); shape::dynamic_dimension default_dyn_dim_value = {1, 1}; std::unordered_map> map_input_dims; std::unordered_map dim_params; std::unordered_map> map_dyn_input_dims; bool use_dyn_output = false; bool skip_unknown_operators = false; int64_t max_loop_iterations = 10; int64_t limit_max_iterations = std::numeric_limits::max(); int64_t opset_version = 13; std::unordered_map ops; onnx_parser(); operation load(const std::string& name, const node_info& info) const; void parse_undefined(module* mod, const std::string& name); static int64_t get_opset_version(const onnx::ModelProto& model); void parse_from(std::istream& is, std::string name = ""); void parse_from(const void* data, std::size_t size); std::vector parse_graph(module* mod, const onnx::GraphProto& graph, bool inlining = false); literal parse_value(const onnx::AttributeProto& attr) const; literal parse_tensor(const onnx::TensorProto& t) const; shape parse_type(const onnx::TypeProto& t) const; shape parse_type(const onnx::TypeProto& t, const std::vector& input_dims) const; }; shape::type_t get_type(int dtype); bool is_type_float(shape::type_t dtype); } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/op_parser.hpp000066400000000000000000000050021510465702400252350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_REGISTER_OP_PARSER_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_REGISTER_OP_PARSER_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct op_desc { std::string onnx_name = ""; std::string op_name = ""; }; void register_op_parser(const std::string& name, onnx_parser::op_func f); onnx_parser::op_func get_op_parser(const std::string& name); std::vector get_op_parsers(); inline std::vector implicit_multi_op(std::vector inss) { return inss; } inline std::vector implicit_multi_op(instruction_ref ins) { return {ins}; } template void register_op_parser() { T parser; for(auto&& opd : parser.operators()) register_op_parser(opd.onnx_name, [opd, parser](auto&&... xs) { return implicit_multi_op(parser.parse(opd, xs...)); }); } struct register_op_parser_action { template static void apply() { register_op_parser(); } }; template using op_parser = auto_register; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/padding.hpp000066400000000000000000000055511510465702400246620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_PADDING_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_PADDING_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { bool is_asym_padding(const std::vector& padding); void cal_auto_padding_size(std::string auto_pad, const std::vector& strides, const std::vector& k_lens, const std::vector& dilation, const std::vector& in_lens, std::vector& paddings); void cal_auto_padding_size(onnx_parser::node_info info, value& v, const std::vector& k_lens, const std::vector& dilation, const std::vector& in_lens, std::vector& paddings); void check_padding_mode(const onnx_parser::node_info& info, const std::string& onnx_name); void tune_padding_size(const value& v, std::vector& padding, int count_include_pad, std::vector& s_start); void check_asym_padding(const onnx_parser::node_info& info, instruction_ref& ins, const std::vector& padding, value& v, int count_include_pad = 0, float pad_val = 0); } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/pooling.hpp000066400000000000000000000035621510465702400247230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_POOLING_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_POOLING_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { value handle_pooling_values(const op_desc& opd, onnx_parser::node_info info, const shape& in_shape, value values); instruction_ref add_pooling_op(const op_desc& opd, onnx_parser::node_info info, instruction_ref l0); } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/include/migraphx/onnx/quantize_dequantize_linear.hpp000066400000000000000000000036341510465702400306770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_QUANTIZE_DEQUANTIZE_LINEAR_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_ONNX_QUANTIZE_DEQUANTIZE_LINEAR_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { std::vector transform_quantize_dequantize_linear_inputs(const onnx_parser::node_info& info, const std::string& onnx_name, int block_size, int axis, std::vector args); } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/onnx/map_activation_functions.cpp000066400000000000000000000033671510465702400241140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { const std::unordered_map& map_activation_functions() { static const std::unordered_map m = { {"tanh", make_op("tanh")}, {"relu", make_op("relu")}, {"sigmoid", make_op("sigmoid")}, {"leakyrelu", make_op("leaky_relu")}, {"elu", make_op("elu")}}; return m; } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/onnx.cpp000066400000000000000000000075761510465702400200160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template static program parse_onnx_from(const onnx_options& options, Ts&&... xs) { onnx::onnx_parser parser; parser.external_data_path = options.external_data_path; parser.map_input_dims = options.map_input_dims; parser.dim_params = options.dim_params; parser.map_dyn_input_dims = options.map_dyn_input_dims; auto dim_val = options.default_dim_value; if(dim_val != 0) { if(options.default_dyn_dim_value != shape::dynamic_dimension{1, 1}) { MIGRAPHX_THROW("PARSE_ONNX_FROM: both default_dim_value and default_dyn_dim_value" "set to non-default value"); } else { parser.default_dyn_dim_value = {dim_val, dim_val}; } } else { parser.default_dyn_dim_value = options.default_dyn_dim_value; } if(not options.map_input_dims.empty() and not options.map_dyn_input_dims.empty()) { MIGRAPHX_THROW("PARSE_ONNX_FROM: both map_input_dims and map_dyn_input_dims non-empty, only" "one should be used"); } parser.skip_unknown_operators = options.skip_unknown_operators; parser.max_loop_iterations = options.max_loop_iterations; parser.limit_max_iterations = options.limit_max_iterations; parser.use_dyn_output = options.use_dyn_output; if(options.print_program_on_error) { // Log the program when it can't be parsed try { parser.parse_from(std::forward(xs)...); } catch(...) { std::cerr << parser.prog << std::endl; throw; } } else { parser.parse_from(std::forward(xs)...); } return std::move(parser.prog); } program parse_onnx(const std::string& name, const onnx_options& options) { std::fstream input(name.c_str(), std::ios::in | std::ios::binary); return parse_onnx_from(options, input, name); } program parse_onnx_buffer(const std::string& buffer, const onnx_options& options) { return parse_onnx_from(options, buffer.data(), buffer.size()); } program parse_onnx_buffer(const void* data, std::size_t size, const onnx_options& options) { return parse_onnx_from(options, data, size); } std::vector get_onnx_operators() { return onnx::get_op_parsers(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/onnx.proto000066400000000000000000001106121510465702400203610ustar00rootroot00000000000000// // WARNING: This file is automatically generated! Please edit onnx.in.proto. // // SPDX-License-Identifier: Apache-2.0 syntax = "proto2"; package onnx_for_migraphx; // Overview // // ONNX is an open specification that is comprised of the following components: // // 1) A definition of an extensible computation graph model. // 2) Definitions of standard data types. // 3) Definitions of built-in operators. // // This document describes the syntax of models and their computation graphs, // as well as the standard data types. Together, they are referred to as the ONNX // Intermediate Representation, or 'IR' for short. // // The normative semantic specification of the ONNX IR is found in docs/IR.md. // Definitions of the built-in neural network operators may be found in docs/Operators.md. // Notes // // Protobuf compatibility // // To simplify framework compatibility, ONNX is defined using the subset of protobuf // that is compatible with both protobuf v2 and v3. This means that we do not use any // protobuf features that are only available in one of the two versions. // // Here are the most notable contortions we have to carry out to work around // these limitations: // // - No 'map' (added protobuf 3.0). We instead represent mappings as lists // of key-value pairs, where order does not matter and duplicates // are not allowed. // Versioning // // ONNX versioning is specified in docs/IR.md and elaborated on in docs/Versioning.md // // To be compatible with both proto2 and proto3, we will use a version number // that is not defined by the default value but an explicit enum number. enum Version { // proto3 requires the first enum value to be zero. // We add this just to appease the compiler. _START_VERSION = 0; // The version field is always serialized and we will use it to store the // version that the graph is generated from. This helps us set up version // control. // For the IR, we are using simple numbers starting with 0x00000001, // which was the version we published on Oct 10, 2017. IR_VERSION_2017_10_10 = 0x0000000000000001; // IR_VERSION 2 published on Oct 30, 2017 // - Added type discriminator to AttributeProto to support proto3 users IR_VERSION_2017_10_30 = 0x0000000000000002; // IR VERSION 3 published on Nov 3, 2017 // - For operator versioning: // - Added new message OperatorSetIdProto // - Added opset_import in ModelProto // - For vendor extensions, added domain in NodeProto IR_VERSION_2017_11_3 = 0x0000000000000003; // IR VERSION 4 published on Jan 22, 2019 // - Relax constraint that initializers should be a subset of graph inputs // - Add type BFLOAT16 IR_VERSION_2019_1_22 = 0x0000000000000004; // IR VERSION 5 published on March 18, 2019 // - Add message TensorAnnotation. // - Add quantization annotation in GraphProto to map tensor with its scale and zero point quantization parameters. IR_VERSION_2019_3_18 = 0x0000000000000005; // IR VERSION 6 published on Sep 19, 2019 // - Add support for sparse tensor constants stored in model. // - Add message SparseTensorProto // - Add sparse initializers IR_VERSION_2019_9_19 = 0x0000000000000006; // IR VERSION 7 published on May 8, 2020 // - Add support to allow function body graph to rely on multiple external opreator sets. // - Add a list to promote inference graph's initializers to global and // mutable variables. Global variables are visible in all graphs of the // stored models. // - Add message TrainingInfoProto to store initialization // method and training algorithm. The execution of TrainingInfoProto // can modify the values of mutable variables. // - Implicitly add inference graph into each TrainingInfoProto's algorithm. IR_VERSION_2020_5_8 = 0x0000000000000007; // IR VERSION 8 published on July 30, 2021 // Introduce TypeProto.SparseTensor // Introduce TypeProto.Optional // Added a list of FunctionProtos local to the model // Deprecated since_version and operator status from FunctionProto IR_VERSION_2021_7_30 = 0x0000000000000008; // IR VERSION 9 published on May 5, 2023 // Added AttributeProto to FunctionProto so that default attribute values can be set. // Added FLOAT8E4M3FN, FLOAT8E4M3FNUZ, FLOAT8E5M2, FLOAT8E5M2FNUZ. IR_VERSION_2023_5_5 = 0x0000000000000009; // IR VERSION 10 published on TBD // Added UINT4, INT4. IR_VERSION = 0x000000000000000A; } // Attributes // // A named attribute containing either singular float, integer, string, graph, // and tensor values, or repeated float, integer, string, graph, and tensor values. // An AttributeProto MUST contain the name field, and *only one* of the // following content fields, effectively enforcing a C/C++ union equivalent. message AttributeProto { reserved 12, 16 to 19; reserved "v"; // Note: this enum is structurally identical to the OpSchema::AttrType // enum defined in schema.h. If you rev one, you likely need to rev the other. enum AttributeType { UNDEFINED = 0; FLOAT = 1; INT = 2; STRING = 3; TENSOR = 4; GRAPH = 5; SPARSE_TENSOR = 11; TYPE_PROTO = 13; FLOATS = 6; INTS = 7; STRINGS = 8; TENSORS = 9; GRAPHS = 10; SPARSE_TENSORS = 12; TYPE_PROTOS = 14; } // The name field MUST be present for this version of the IR. optional string name = 1; // namespace Attribute // if ref_attr_name is not empty, ref_attr_name is the attribute name in parent function. // In this case, this AttributeProto does not contain data, and it's a reference of attribute // in parent scope. // NOTE: This should ONLY be used in function (sub-graph). It's invalid to be used in main graph. optional string ref_attr_name = 21; // A human-readable documentation for this attribute. Markdown is allowed. optional string doc_string = 13; // The type field MUST be present for this version of the IR. // For 0.0.1 versions of the IR, this field was not defined, and // implementations needed to use has_field heuristics to determine // which value field was in use. For IR_VERSION 0.0.2 or later, this // field MUST be set and match the f|i|s|t|... field in use. This // change was made to accommodate proto3 implementations. optional AttributeType type = 20; // discriminator that indicates which field below is in use // Exactly ONE of the following fields must be present for this version of the IR optional float f = 2; // float optional int64 i = 3; // int optional bytes s = 4; // UTF-8 string optional TensorProto t = 5; // tensor value optional GraphProto g = 6; // graph optional SparseTensorProto sparse_tensor = 22; // sparse tensor value // Do not use field below, it's deprecated. // optional ValueProto v = 12; // value - subsumes everything but graph optional TypeProto tp = 14; // type proto repeated float floats = 7; // list of floats repeated int64 ints = 8; // list of ints repeated bytes strings = 9; // list of UTF-8 strings repeated TensorProto tensors = 10; // list of tensors repeated GraphProto graphs = 11; // list of graph repeated SparseTensorProto sparse_tensors = 23; // list of sparse tensors repeated TypeProto type_protos = 15;// list of type protos } // Defines information on value, including the name, the type, and // the shape of the value. message ValueInfoProto { // This field MUST be present in this version of the IR. optional string name = 1; // namespace Value // This field MUST be present in this version of the IR for // inputs and outputs of the top-level graph. optional TypeProto type = 2; // A human-readable documentation for this value. Markdown is allowed. optional string doc_string = 3; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 4; } // Nodes // // Computation graphs are made up of a DAG of nodes, which represent what is // commonly called a "layer" or "pipeline stage" in machine learning frameworks. // // For example, it can be a node of type "Conv" that takes in an image, a filter // tensor and a bias tensor, and produces the convolved output. message NodeProto { repeated string input = 1; // namespace Value repeated string output = 2; // namespace Value // An optional identifier for this node in a graph. // This field MAY be absent in this version of the IR. optional string name = 3; // namespace Node // The symbolic identifier of the Operator to execute. optional string op_type = 4; // namespace Operator // The domain of the OperatorSet that specifies the operator named by op_type. optional string domain = 7; // namespace Domain // Overload identifier, used only to map this to a model-local function. optional string overload = 8; // Additional named attributes. repeated AttributeProto attribute = 5; // A human-readable documentation for this node. Markdown is allowed. optional string doc_string = 6; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 9; } // Training information // TrainingInfoProto stores information for training a model. // In particular, this defines two functionalities: an initialization-step // and a training-algorithm-step. Initialization resets the model // back to its original state as if no training has been performed. // Training algorithm improves the model based on input data. // // The semantics of the initialization-step is that the initializers // in ModelProto.graph and in TrainingInfoProto.algorithm are first // initialized as specified by the initializers in the graph, and then // updated by the "initialization_binding" in every instance in // ModelProto.training_info. // // The field "algorithm" defines a computation graph which represents a // training algorithm's step. After the execution of a // TrainingInfoProto.algorithm, the initializers specified by "update_binding" // may be immediately updated. If the targeted training algorithm contains // consecutive update steps (such as block coordinate descent methods), // the user needs to create a TrainingInfoProto for each step. message TrainingInfoProto { // This field describes a graph to compute the initial tensors // upon starting the training process. Initialization graph has no input // and can have multiple outputs. Usually, trainable tensors in neural // networks are randomly initialized. To achieve that, for each tensor, // the user can put a random number operator such as RandomNormal or // RandomUniform in TrainingInfoProto.initialization.node and assign its // random output to the specific tensor using "initialization_binding". // This graph can also set the initializers in "algorithm" in the same // TrainingInfoProto; a use case is resetting the number of training // iteration to zero. // // By default, this field is an empty graph and its evaluation does not // produce any output. Thus, no initializer would be changed by default. optional GraphProto initialization = 1; // This field represents a training algorithm step. Given required inputs, // it computes outputs to update initializers in its own or inference graph's // initializer lists. In general, this field contains loss node, gradient node, // optimizer node, increment of iteration count. // // An execution of the training algorithm step is performed by executing the // graph obtained by combining the inference graph (namely "ModelProto.graph") // and the "algorithm" graph. That is, the actual // input/initializer/output/node/value_info/sparse_initializer list of // the training graph is the concatenation of // "ModelProto.graph.input/initializer/output/node/value_info/sparse_initializer" // and "algorithm.input/initializer/output/node/value_info/sparse_initializer" // in that order. This combined graph must satisfy the normal ONNX conditions. // Now, let's provide a visualization of graph combination for clarity. // Let the inference graph (i.e., "ModelProto.graph") be // tensor_a, tensor_b -> MatMul -> tensor_c -> Sigmoid -> tensor_d // and the "algorithm" graph be // tensor_d -> Add -> tensor_e // The combination process results // tensor_a, tensor_b -> MatMul -> tensor_c -> Sigmoid -> tensor_d -> Add -> tensor_e // // Notice that an input of a node in the "algorithm" graph may reference the // output of a node in the inference graph (but not the other way round). Also, inference // node cannot reference inputs of "algorithm". With these restrictions, inference graph // can always be run independently without training information. // // By default, this field is an empty graph and its evaluation does not // produce any output. Evaluating the default training step never // update any initializers. optional GraphProto algorithm = 2; // This field specifies the bindings from the outputs of "initialization" to // some initializers in "ModelProto.graph.initializer" and // the "algorithm.initializer" in the same TrainingInfoProto. // See "update_binding" below for details. // // By default, this field is empty and no initializer would be changed // by the execution of "initialization". repeated StringStringEntryProto initialization_binding = 3; // Gradient-based training is usually an iterative procedure. In one gradient // descent iteration, we apply // // x = x - r * g // // where "x" is the optimized tensor, "r" stands for learning rate, and "g" is // gradient of "x" with respect to a chosen loss. To avoid adding assignments // into the training graph, we split the update equation into // // y = x - r * g // x = y // // The user needs to save "y = x - r * g" into TrainingInfoProto.algorithm. To // tell that "y" should be assigned to "x", the field "update_binding" may // contain a key-value pair of strings, "x" (key of StringStringEntryProto) // and "y" (value of StringStringEntryProto). // For a neural network with multiple trainable (mutable) tensors, there can // be multiple key-value pairs in "update_binding". // // The initializers appears as keys in "update_binding" are considered // mutable variables. This implies some behaviors // as described below. // // 1. We have only unique keys in all "update_binding"s so that two // variables may not have the same name. This ensures that one // variable is assigned up to once. // 2. The keys must appear in names of "ModelProto.graph.initializer" or // "TrainingInfoProto.algorithm.initializer". // 3. The values must be output names of "algorithm" or "ModelProto.graph.output". // 4. Mutable variables are initialized to the value specified by the // corresponding initializer, and then potentially updated by // "initializer_binding"s and "update_binding"s in "TrainingInfoProto"s. // // This field usually contains names of trainable tensors // (in ModelProto.graph), optimizer states such as momentums in advanced // stochastic gradient methods (in TrainingInfoProto.graph), // and number of training iterations (in TrainingInfoProto.graph). // // By default, this field is empty and no initializer would be changed // by the execution of "algorithm". repeated StringStringEntryProto update_binding = 4; } // Models // // ModelProto is a top-level file/container format for bundling a ML model and // associating its computation graph with metadata. // // The semantics of the model are described by the associated GraphProto's. message ModelProto { // The version of the IR this model targets. See Version enum above. // This field MUST be present. optional int64 ir_version = 1; // The OperatorSets this model relies on. // All ModelProtos MUST have at least one entry that // specifies which version of the ONNX OperatorSet is // being imported. // // All nodes in the ModelProto's graph will bind against the operator // with the same-domain/same-op_type operator with the HIGHEST version // in the referenced operator sets. repeated OperatorSetIdProto opset_import = 8; // The name of the framework or tool used to generate this model. // This field SHOULD be present to indicate which implementation/tool/framework // emitted the model. optional string producer_name = 2; // The version of the framework or tool used to generate this model. // This field SHOULD be present to indicate which implementation/tool/framework // emitted the model. optional string producer_version = 3; // Domain name of the model. // We use reverse domain names as name space indicators. For example: // `com.facebook.fair` or `com.microsoft.cognitiveservices` // // Together with `model_version` and GraphProto.name, this forms the unique identity of // the graph. optional string domain = 4; // The version of the graph encoded. See Version enum below. optional int64 model_version = 5; // A human-readable documentation for this model. Markdown is allowed. optional string doc_string = 6; // The parameterized graph that is evaluated to execute the model. optional GraphProto graph = 7; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 14; // Training-specific information. Sequentially executing all stored // `TrainingInfoProto.algorithm`s and assigning their outputs following // the corresponding `TrainingInfoProto.update_binding`s is one training // iteration. Similarly, to initialize the model // (as if training hasn't happened), the user should sequentially execute // all stored `TrainingInfoProto.initialization`s and assigns their outputs // using `TrainingInfoProto.initialization_binding`s. // // If this field is empty, the training behavior of the model is undefined. repeated TrainingInfoProto training_info = 20; // A list of function protos local to the model. // // The (domain, name, overload) tuple must be unique across the function protos in this list. // In case of any conflicts the behavior (whether the model local functions are given higher priority, // or standard operator sets are given higher priotity or this is treated as error) is defined by // the runtimes. // // The operator sets imported by FunctionProto should be compatible with the ones // imported by ModelProto and other model local FunctionProtos. // Example, if same operator set say 'A' is imported by a FunctionProto and ModelProto // or by 2 FunctionProtos then versions for the operator set may be different but, // the operator schema returned for op_type, domain, version combination // for both the versions should be same for every node in the function body. // // One FunctionProto can reference other FunctionProto in the model, however, recursive reference // is not allowed. repeated FunctionProto functions = 25; }; // StringStringEntryProto follows the pattern for cross-proto-version maps. // See https://developers.google.com/protocol-buffers/docs/proto3#maps message StringStringEntryProto { optional string key = 1; optional string value = 2; }; message TensorAnnotation { optional string tensor_name = 1; // pairs to annotate tensor specified by above. // The keys used in the mapping below must be pre-defined in ONNX spec. // For example, for 8-bit linear quantization case, 'SCALE_TENSOR', 'ZERO_POINT_TENSOR' will be pre-defined as // quantization parameter keys. repeated StringStringEntryProto quant_parameter_tensor_names = 2; } // Graphs // // A graph defines the computational logic of a model and is comprised of a parameterized // list of nodes that form a directed acyclic graph based on their inputs and outputs. // This is the equivalent of the "network" or "graph" in many deep learning // frameworks. message GraphProto { // The nodes in the graph, sorted topologically. repeated NodeProto node = 1; // The name of the graph. optional string name = 2; // namespace Graph // A list of named tensor values, used to specify constant inputs of the graph. // Each initializer (both TensorProto as well SparseTensorProto) MUST have a name. // The name MUST be unique across both initializer and sparse_initializer, // but the name MAY also appear in the input list. repeated TensorProto initializer = 5; // Initializers (see above) stored in sparse format. repeated SparseTensorProto sparse_initializer = 15; // A human-readable documentation for this graph. Markdown is allowed. optional string doc_string = 10; // The inputs and outputs of the graph. repeated ValueInfoProto input = 11; repeated ValueInfoProto output = 12; // Information for the values in the graph. The ValueInfoProto.name's // must be distinct. It is optional for a value to appear in value_info list. repeated ValueInfoProto value_info = 13; // This field carries information to indicate the mapping among a tensor and its // quantization parameter tensors. For example: // For tensor 'a', it may have {'SCALE_TENSOR', 'a_scale'} and {'ZERO_POINT_TENSOR', 'a_zero_point'} annotated, // which means, tensor 'a_scale' and tensor 'a_zero_point' are scale and zero point of tensor 'a' in the model. repeated TensorAnnotation quantization_annotation = 14; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 16; reserved 3, 4, 6 to 9; reserved "ir_version", "producer_version", "producer_tag", "domain"; } // Tensors // // A serialized tensor value. message TensorProto { enum DataType { UNDEFINED = 0; // Basic types. FLOAT = 1; // float UINT8 = 2; // uint8_t INT8 = 3; // int8_t UINT16 = 4; // uint16_t INT16 = 5; // int16_t INT32 = 6; // int32_t INT64 = 7; // int64_t STRING = 8; // string BOOL = 9; // bool // IEEE754 half-precision floating-point format (16 bits wide). // This format has 1 sign bit, 5 exponent bits, and 10 mantissa bits. FLOAT16 = 10; DOUBLE = 11; UINT32 = 12; UINT64 = 13; COMPLEX64 = 14; // complex with float32 real and imaginary components COMPLEX128 = 15; // complex with float64 real and imaginary components // Non-IEEE floating-point format based on IEEE754 single-precision // floating-point number truncated to 16 bits. // This format has 1 sign bit, 8 exponent bits, and 7 mantissa bits. BFLOAT16 = 16; // Non-IEEE floating-point format based on papers // FP8 Formats for Deep Learning, https://arxiv.org/abs/2209.05433, // 8-bit Numerical Formats For Deep Neural Networks, https://arxiv.org/pdf/2206.02915.pdf. // Operators supported FP8 are Cast, CastLike, QuantizeLinear, DequantizeLinear. // The computation usually happens inside a block quantize / dequantize // fused by the runtime. FLOAT8E4M3FN = 17; // float 8, mostly used for coefficients, supports nan, not inf FLOAT8E4M3FNUZ = 18; // float 8, mostly used for coefficients, supports nan, not inf, no negative zero FLOAT8E5M2 = 19; // follows IEEE 754, supports nan, inf, mostly used for gradients FLOAT8E5M2FNUZ = 20; // follows IEEE 754, supports nan, not inf, mostly used for gradients, no negative zero // 4-bit data-types UINT4 = 21; // Unsigned integer in range [0, 15] INT4 = 22; // Signed integer in range [-8, 7], using two's-complement representation // Future extensions go here. } // The shape of the tensor. repeated int64 dims = 1; // The data type of the tensor. // This field MUST have a valid TensorProto.DataType value optional int32 data_type = 2; // For very large tensors, we may want to store them in chunks, in which // case the following fields will specify the segment that is stored in // the current TensorProto. message Segment { optional int64 begin = 1; optional int64 end = 2; } optional Segment segment = 3; // Tensor content must be organized in row-major order. // // Depending on the data_type field, exactly one of the fields below with // name ending in _data is used to store the elements of the tensor. // For float and complex64 values // Complex64 tensors are encoded as a single array of floats, // with the real components appearing in odd numbered positions, // and the corresponding imaginary component appearing in the // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i] // is encoded as [1.0, 2.0 ,3.0 ,4.0] // When this field is present, the data_type field MUST be FLOAT or COMPLEX64. repeated float float_data = 4 [packed = true]; // For int32, uint8, int8, uint16, int16, uint4, int4, bool, float8 and float16 values // float16 and float8 values must be bit-wise converted to an uint16_t prior // to writing to the buffer. // uint4 and int4 values must be packed to 4bitx2 prior to writing to the buffer, the first element is stored in // the 4 LSB and the second element is stored in the 4 MSB. // When this field is present, the data_type field MUST be // INT32, INT16, INT8, INT4, UINT16, UINT8, UINT4, BOOL, FLOAT16, BFLOAT16, FLOAT8E4M3FN, FLOAT8E4M3FNUZ, FLOAT8E5M2, FLOAT8E5M2FNUZ repeated int32 int32_data = 5 [packed = true]; // For strings. // Each element of string_data is a UTF-8 encoded Unicode // string. No trailing null, no leading BOM. The protobuf "string" // scalar type is not used to match ML community conventions. // When this field is present, the data_type field MUST be STRING repeated bytes string_data = 6; // For int64. // When this field is present, the data_type field MUST be INT64 repeated int64 int64_data = 7 [packed = true]; // Optionally, a name for the tensor. optional string name = 8; // namespace Value // A human-readable documentation for this tensor. Markdown is allowed. optional string doc_string = 12; // Serializations can either use one of the fields above, or use this // raw bytes field. The only exception is the string case, where one is // required to store the content in the repeated bytes string_data field. // // When this raw_data field is used to store tensor value, elements MUST // be stored in as fixed-width, little-endian order. // Floating-point data types MUST be stored in IEEE 754 format. // Complex64 elements must be written as two consecutive FLOAT values, real component first. // Complex128 elements must be written as two consecutive DOUBLE values, real component first. // Boolean type MUST be written one byte per tensor element (00000001 for true, 00000000 for false). // uint4 and int4 values must be packed to 4bitx2, the first element is stored in the 4 LSB and the second element is stored in the 4 MSB. // // Note: the advantage of specific field rather than the raw_data field is // that in some cases (e.g. int data), protobuf does a better packing via // variable length storage, and may lead to smaller binary footprint. // When this field is present, the data_type field MUST NOT be STRING or UNDEFINED optional bytes raw_data = 9; // Data can be stored inside the protobuf file using type-specific fields or raw_data. // Alternatively, raw bytes data can be stored in an external file, using the external_data field. // external_data stores key-value pairs describing data location. Recognized keys are: // - "location" (required) - POSIX filesystem path relative to the directory where the ONNX // protobuf model was stored // - "offset" (optional) - position of byte at which stored data begins. Integer stored as string. // Offset values SHOULD be multiples 4096 (page size) to enable mmap support. // - "length" (optional) - number of bytes containing data. Integer stored as string. // - "checksum" (optional) - SHA1 digest of file specified in under 'location' key. repeated StringStringEntryProto external_data = 13; // Location of the data for this tensor. MUST be one of: // - DEFAULT - data stored inside the protobuf message. Data is stored in raw_data (if set) otherwise in type-specified field. // - EXTERNAL - data stored in an external location as described by external_data field. enum DataLocation { DEFAULT = 0; EXTERNAL = 1; } // If value not set, data is stored in raw_data (if set) otherwise in type-specified field. optional DataLocation data_location = 14; // For double // Complex128 tensors are encoded as a single array of doubles, // with the real components appearing in odd numbered positions, // and the corresponding imaginary component appearing in the // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i] // is encoded as [1.0, 2.0 ,3.0 ,4.0] // When this field is present, the data_type field MUST be DOUBLE or COMPLEX128 repeated double double_data = 10 [packed = true]; // For uint64 and uint32 values // When this field is present, the data_type field MUST be // UINT32 or UINT64 repeated uint64 uint64_data = 11 [packed = true]; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 16; } // A serialized sparse-tensor value message SparseTensorProto { // The sequence of non-default values are encoded as a tensor of shape [NNZ]. // The default-value is zero for numeric tensors, and empty-string for string tensors. // values must have a non-empty name present which serves as a name for SparseTensorProto // when used in sparse_initializer list. optional TensorProto values = 1; // The indices of the non-default values, which may be stored in one of two formats. // (a) Indices can be a tensor of shape [NNZ, rank] with the [i,j]-th value // corresponding to the j-th index of the i-th value (in the values tensor). // (b) Indices can be a tensor of shape [NNZ], in which case the i-th value // must be the linearized-index of the i-th value (in the values tensor). // The linearized-index can be converted into an index tuple (k_1,...,k_rank) // using the shape provided below. // The indices must appear in ascending order without duplication. // In the first format, the ordering is lexicographic-ordering: // e.g., index-value [1,4] must appear before [2,1] optional TensorProto indices = 2; // The shape of the underlying dense-tensor: [dim_1, dim_2, ... dim_rank] repeated int64 dims = 3; } // Defines a tensor shape. A dimension can be either an integer value // or a symbolic variable. A symbolic variable represents an unknown // dimension. message TensorShapeProto { message Dimension { oneof value { int64 dim_value = 1; string dim_param = 2; // namespace Shape }; // Standard denotation can optionally be used to denote tensor // dimensions with standard semantic descriptions to ensure // that operations are applied to the correct axis of a tensor. // Refer to https://github.com/onnx/onnx/blob/main/docs/DimensionDenotation.md#denotation-definition // for pre-defined dimension denotations. optional string denotation = 3; }; repeated Dimension dim = 1; } // Types // // The standard ONNX data types. message TypeProto { message Tensor { // This field MUST NOT have the value of UNDEFINED // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. optional int32 elem_type = 1; optional TensorShapeProto shape = 2; } // repeated T message Sequence { // The type and optional shape of each element of the sequence. // This field MUST be present for this version of the IR. optional TypeProto elem_type = 1; }; // map message Map { // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. // This field MUST refer to an integral type ([U]INT{8|16|32|64}) or STRING optional int32 key_type = 1; // This field MUST be present for this version of the IR. optional TypeProto value_type = 2; }; // wrapper for Tensor, Sequence, or Map message Optional { // The type and optional shape of the element wrapped. // This field MUST be present for this version of the IR. // Possible values correspond to OptionalProto.DataType enum optional TypeProto elem_type = 1; }; message SparseTensor { // This field MUST NOT have the value of UNDEFINED // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. optional int32 elem_type = 1; optional TensorShapeProto shape = 2; } oneof value { // The type of a tensor. Tensor tensor_type = 1; // NOTE: DNN-only implementations of ONNX MAY elect to not support non-tensor values // as input and output to graphs and nodes. These types are needed to naturally // support classical ML operators. DNN operators SHOULD restrict their input // and output types to tensors. // The type of a sequence. Sequence sequence_type = 4; // The type of a map. Map map_type = 5; // The type of an optional. Optional optional_type = 9; // Type of the sparse tensor SparseTensor sparse_tensor_type = 8; } // An optional denotation can be used to denote the whole // type with a standard semantic description as to what is // stored inside. Refer to https://github.com/onnx/onnx/blob/main/docs/TypeDenotation.md#type-denotation-definition // for pre-defined type denotations. optional string denotation = 6; } // Operator Sets // // OperatorSets are uniquely identified by a (domain, opset_version) pair. message OperatorSetIdProto { // The domain of the operator set being identified. // The empty string ("") or absence of this field implies the operator // set that is defined as part of the ONNX specification. // This field MUST be present in this version of the IR when referring to any other operator set. optional string domain = 1; // The version of the operator set being identified. // This field MUST be present in this version of the IR. optional int64 version = 2; } // Operator/function status. enum OperatorStatus { EXPERIMENTAL = 0; STABLE = 1; } message FunctionProto { // The name of the function, similar to op_type in NodeProto. // This is part of the unique-id (domain, name, overload) of FunctionProtos in a model. optional string name = 1; // Deprecated since IR Version 8 // optional int64 since_version = 2; reserved 2; reserved "since_version"; // Deprecated since IR Version 8 // optional OperatorStatus status = 3; reserved 3; reserved "status"; // The inputs and outputs of the function. repeated string input = 4; repeated string output = 5; // The attribute parameters of the function. // It is for function parameters without default values. repeated string attribute = 6; // The attribute protos of the function. // It is for function attributes with default values. // A function attribute shall be represented either as // a string attribute or an AttributeProto, not both. repeated AttributeProto attribute_proto = 11; // The nodes in the function. repeated NodeProto node = 7; // A human-readable documentation for this function. Markdown is allowed. optional string doc_string = 8; // The OperatorSets this function body (graph) relies on. // // All nodes in the function body (graph) will bind against the operator // with the same-domain/same-op_type operator with the HIGHEST version // in the referenced operator sets. This means at most one version can be relied // for one domain. // // The operator sets imported by FunctionProto should be compatible with the ones // imported by ModelProto. Example, if same operator set say 'A' is imported by FunctionProto // and ModelProto then versions for the operator set may be different but, // the operator schema returned for op_type, domain, version combination // for both the versions should be same. repeated OperatorSetIdProto opset_import = 9; // The domain which this function belongs to. // This is part of the unique-id (domain, name, overload) of FunctionProtos in a model. optional string domain = 10; // The overload identifier of the function. // This is part of the unique-id (domain, name, overload) of FunctionProtos in a model. optional string overload = 13; // Information for the values in the function. The ValueInfoProto.name's // must be distinct and refer to names in the function (including inputs, // outputs, and intermediate values). It is optional for a value to appear // in value_info list. repeated ValueInfoProto value_info = 12; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 14; } // For using protobuf-lite option optimize_for = LITE_RUNTIME; ROCm-AMDMIGraphX-46524e8/src/onnx/onnx_parser.cpp000066400000000000000000000733621510465702400213660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_ONNX_PARSER) static shape shape_from_dyn_dims(shape::type_t shape_type, const std::vector& dyn_dims) { if(std::all_of(dyn_dims.begin(), dyn_dims.end(), [](const auto& dd) { return dd.is_fixed(); })) { std::vector dims; std::transform(dyn_dims.cbegin(), dyn_dims.cend(), std::back_inserter(dims), [](const auto& d) { return d.max; }); return {shape_type, dims}; } return {shape_type, dyn_dims}; } static onnx_parser::attribute_map get_attributes(const onnx::NodeProto& node) { std::unordered_map result; for(auto&& attr : node.attribute()) { result[attr.name()] = attr; } return result; } static literal create_literal(shape::type_t shape_type, const std::vector& dims, const char* data) { // empty input auto elem_num = std::accumulate(dims.begin(), dims.end(), std::size_t(1), std::multiplies()); if(elem_num == 0) { return literal{shape_type}; } // in case of scalar constants in onnx file, use dims=1 to fill initializer data if(dims.empty()) return literal{{shape_type}, data}; return literal{{shape_type, dims}, data}; } template {})> static literal create_literal(shape::type_t shape_type, const std::vector& dims, T data) { // empty input auto elem_num = std::accumulate(dims.begin(), dims.end(), std::size_t(1), std::multiplies()); if(elem_num == 0) { return literal{shape_type}; } // scalar input if(dims.empty()) return literal{{shape_type}, data.begin(), data.end()}; return literal{{shape_type, dims}, data.begin(), data.end()}; } template static literal from_repeated(shape::type_t t, const T& r) { std::size_t size = r.size(); return literal{{t, {size}}, r.begin(), r.end()}; } instruction_ref onnx_parser::node_info::make_contiguous(instruction_ref ins) const { auto attr = ins->get_operator().to_value(); std::string key = "require_std_shape"; if((attr.get(key, false)) or (not ins->get_shape().standard())) { return add_instruction(make_op("contiguous"), ins); } return ins; } instruction_ref onnx_parser::node_info::add_bias(const std::vector& args, instruction_ref curr_ins, uint64_t axis) const { if(args.size() == 3) { instruction_ref bias_bcast; // if curr_ins has a dynamic output shape use 2 input broadcast if(curr_ins->get_shape().dynamic()) { bias_bcast = mod->add_instruction(make_op("broadcast", {{"axis", axis}}), args[2], curr_ins); } else { bias_bcast = mod->add_instruction( make_op("broadcast", {{"axis", axis}, {"out_lens", curr_ins->get_shape().lens()}}), args[2]); } return mod->add_instruction(make_op("add"), curr_ins, bias_bcast); } return curr_ins; } instruction_ref onnx_parser::node_info::add_broadcastable_binary_op(const std::string& op_name, instruction_ref arg0, instruction_ref arg1) const { return this->add_common_op(op_name, arg0, arg1); } /** * @brief A wrapper for insert_common_args(), which constructs an argument list * and inserts multibroadcast and convert ops to match inputs to a common shape and type * as required. The requested operation is placed after the added multibroadcast and convert ops, * if any, so that their results are transparent to the programmer. * * Use add_common_op() to match input sizes when inputs may be * either static or dynamic. * * @param op_name string; Name of operation (op) to add; valid names are the same as * for make_op() * * @param inputs vector of instruction_ref. List of instructions for the new * operator. Multibroadcast and convert operations, if needed, are deduced from these too. * * @return instruction_ref Returns an instruction_ref which is the result of the requested * operation. * */ instruction_ref onnx_parser::node_info::add_common_op(const std::string& op_name, std::vector inputs) const { return migraphx::add_common_op(*mod, make_op(op_name), std::move(inputs)); } instruction_ref onnx_parser::node_info::add_instruction(const operation& op, const std::vector& args) const { return mod->add_instruction(op, args); } instruction_ref onnx_parser::node_info::add_instruction(const operation& op, const std::vector& args, const std::vector& mods) const { return mod->add_instruction(op, args, mods); } instruction_ref onnx_parser::node_info::add_literal(literal l) const { return mod->add_literal(std::move(l)); } onnx_parser::onnx_parser() { // Add all registered op parsers for(auto&& name : get_op_parsers()) ops.emplace(name, get_op_parser(name)); } operation onnx_parser::load(const std::string& name, const node_info& info) const { auto op = make_op(name); auto v = op.to_value(); for(auto&& x : v) { if(info.attributes.count(x.get_key()) == 0) continue; literal s = parse_value(info.attributes.at(x.get_key())); if(x.is_array()) { std::vector values; s.visit([&](auto y) { std::transform(y.begin(), y.end(), std::back_inserter(values), [](auto z) { return value(z); }); }); x = values; } else { s.visit([&](auto y) { x = y.front(); }); } } op.from_value(v); return op; } void onnx_parser::parse_undefined(module* mod, const std::string& name) { if(not contains(instructions, name)) { auto ins = mod->add_instruction(make_op("undefined")); instructions[name] = ins; } } void onnx_parser::parse_from(std::istream& is, std::string name) { auto* mm = prog.get_main_module(); this->filename = std::move(name); auto parent_path = fs::path(this->filename).parent_path(); if(not parent_path.empty()) this->path = parent_path.string(); onnx::ModelProto model; if(model.ParseFromIstream(&is)) { auto version = get_opset_version(model); opset_version = (version == -1) ? opset_version : version; if(model.has_graph()) { (void)this->parse_graph(mm, model.graph()); } } else { MIGRAPHX_THROW("PARSE_FROM: Failed reading onnx file: " + this->filename); } } void onnx_parser::parse_from(const void* data, std::size_t size) { auto* mm = prog.get_main_module(); onnx::ModelProto model; if(model.ParseFromArray(data, size)) { auto version = get_opset_version(model); opset_version = (version == -1) ? opset_version : version; if(model.has_graph()) { (void)this->parse_graph(mm, model.graph()); } } else { MIGRAPHX_THROW("Failed reading onnx file."); } } int64_t onnx_parser::get_opset_version(const onnx::ModelProto& model) { const auto& opset_import = model.opset_import(); int64_t version = -1; for(const auto& opset : opset_import) { if(opset.has_version()) { version = std::max(version, opset.version()); } } return version; } static void print_added_instructions(module* mod, const std::vector& args, const std::vector& result) { // Print instructions added by the parser not in args std::vector added_instructions; fix([&](auto self, const auto& r) { for(auto ins : r) { if(contains(args, ins)) continue; if(contains(added_instructions, ins)) continue; self(ins->inputs()); added_instructions.push_back(ins); } })(result); mod->debug_print(added_instructions); } static bool is_type_packed_int4(const onnx::TensorProto& t) { return t.data_type() == onnx::TensorProto::INT4 or t.data_type() == onnx::TensorProto::UINT4; } static std::unordered_map parse_intializer(const onnx_parser& parser, module* mod, const onnx::GraphProto& graph) { std::unordered_map mod_insts; for(auto&& f : graph.initializer()) { if(enabled(MIGRAPHX_TRACE_ONNX_PARSER{})) std::cout << "initializer: " << f.name() << std::endl; // backup instructions in parent mod auto pt = parser.parse_tensor(f); auto lit = mod->add_literal(pt); if(is_type_packed_int4(f)) lit = mod->add_instruction(migraphx::make_op("unpack_int4"), lit); mod_insts[f.name()] = lit; if(enabled(MIGRAPHX_TRACE_ONNX_PARSER{})) mod->debug_print(mod_insts[f.name()]); } return mod_insts; } static std::unordered_map parse_inputs(const onnx_parser& parser, module* mod, const onnx::GraphProto& graph, std::unordered_map mod_insts) { for(auto&& input : graph.input()) { const std::string& name = input.name(); // input not in initializer_data, so it is a real input if(not contains(mod_insts, name)) { if(contains(parser.instructions, name)) { MIGRAPHX_THROW("module \"" + mod->name() + "\" has parameter name \"" + name + "\" existing in parent graph!"); } shape s; if(parser.map_input_dims.count(name) > 0) { std::vector dims = parser.map_input_dims.at(name); s = parser.parse_type(input.type(), dims); } else if(parser.map_dyn_input_dims.count(name) > 0) { shape::type_t shape_type = get_type(input.type().tensor_type().elem_type()); s = shape_from_dyn_dims(shape_type, parser.map_dyn_input_dims.at(name)); } else { s = parser.parse_type(input.type()); } mod_insts[name] = mod->add_parameter(name, s); } } return mod_insts; } struct node_maps { std::unordered_map> input_to_node_map; std::unordered_map> node_to_output_map; }; static node_maps create_node_maps(const onnx::GraphProto& graph) { node_maps maps{}; for(size_t node_index = 0; node_index < graph.node_size(); node_index++) { const onnx::NodeProto& node = graph.node(node_index); // To track actual node outputs, a future node will mention the current node's output // in its list of inputs. For example: // node A: outputs ["a_out"] // node B: inputs ["a_out"] // Here the output of node A is node B because "a_out" is the output of node A and // input of node B. // There can be multiple outputs, so if each new node sees an input already // in the map, then append to it. // In the prior example, if we add the following: // node C: inputs ["a_out"] // then we will have something like {"a_out": [node B, node C]} in our map. for(auto&& input : node.input()) { if(input.empty()) continue; maps.input_to_node_map[input].push_back(node_index); } std::vector node_outputs; std::transform(node.output().begin(), node.output().end(), std::back_inserter(node_outputs), [](const auto& output) { return output; }); // Use this second map to keep track of all references of the output names to be used // as inputs to future nodes. maps.node_to_output_map.emplace(node_index, std::move(node_outputs)); } return maps; } static void traverse(std::vector& sorted_nodes, std::unordered_set& visited_nodes, const std::unordered_map>& input_to_node_map, const std::unordered_map>& node_to_output_map, size_t curr_node) { if(contains(visited_nodes, curr_node)) return; visited_nodes.insert(curr_node); for(const auto& out_node_name : node_to_output_map.at(curr_node)) { // check if node output is used in graph if(not out_node_name.empty() and contains(input_to_node_map, out_node_name)) { for(const auto& in_node_name : input_to_node_map.at(out_node_name)) traverse(sorted_nodes, visited_nodes, input_to_node_map, node_to_output_map, in_node_name); } } sorted_nodes.insert(sorted_nodes.begin(), curr_node); } static std::vector toposort(const onnx::GraphProto& graph) { node_maps maps = create_node_maps(graph); std::vector sorted_nodes; std::unordered_set visited_nodes; for(size_t node_index = 0; node_index < graph.node_size(); node_index++) { traverse(sorted_nodes, visited_nodes, maps.input_to_node_map, maps.node_to_output_map, node_index); } return sorted_nodes; } static bool check_sorted(const onnx::GraphProto& graph, std::unordered_set& parent_input_nodes) { std::unordered_set visited_nodes; // first visit all graph inputs and initializers, // keep track globally since subgraph nodes may depend on parent inputs for(auto&& input : graph.input()) { const std::string& input_name = input.name(); // ONNX specification does not specify how to deal with the // scenario that a nested subgraph contains a parameter with the // same name in its parent graph. // In the current implementation, MIGraphX throws an exception for that. if(contains(parent_input_nodes, input_name)) MIGRAPHX_THROW("subgraph \"" + graph.name() + "\" has parameter name \"" + input_name + "\" existing in parent graph!"); parent_input_nodes.insert(input_name); } for(auto&& initializer : graph.initializer()) { parent_input_nodes.insert(initializer.name()); } // propagate parent graph input nodes into visited nodes visited_nodes.insert(parent_input_nodes.begin(), parent_input_nodes.end()); for(auto&& node : graph.node()) { for(auto&& input : node.input()) { // operators like resize can use empty strings to skip unused inputs if(not input.empty()) if(not contains(visited_nodes, input)) return false; } // node outputs struct is used to keep track of currently visited nodes visited_nodes.insert(node.output().begin(), node.output().end()); } return true; } std::vector onnx_parser::parse_graph(module* mod, const onnx::GraphProto& graph, bool inlining) { std::vector node_indices(graph.node_size()); if(check_sorted(graph, parent_input_nodes)) { std::iota(node_indices.begin(), node_indices.end(), 0); } else { std::cerr << "Warning: onnx model is not topologically sorted. Attempting to sort..." << std::endl; node_indices = toposort(graph); } std::unordered_map mod_insts = parse_intializer(*this, mod, graph); mod_insts = parse_inputs(*this, mod, graph, mod_insts); std::copy(mod_insts.begin(), mod_insts.end(), std::inserter(instructions, instructions.end())); for(auto& node_index : node_indices) { const onnx::NodeProto& node = graph.node(node_index); if(enabled(MIGRAPHX_TRACE_ONNX_PARSER{})) std::cout << "operator: " << node.op_type() << '\t' << node.name() << std::endl; std::vector args; for(auto&& input : node.input()) { if(input.empty()) { this->parse_undefined(mod, input); } if(instructions.count(input) == 0) { MIGRAPHX_THROW("PARSE_GRAPH: invalid onnx file. Input \"" + input + "\" is unavailable due to unordered nodes!"); } args.push_back(instructions.at(input)); } std::vector result; std::size_t output_num = node.output().size(); if(ops.count(node.op_type()) == 0) { if(skip_unknown_operators) result.push_back(mod->add_instruction(op::unknown{node.op_type()}, args)); else MIGRAPHX_THROW("Unknown operator: " + node.op_type()); } else { std::string node_name = node.op_type() + "_" + std::to_string(mod->size()); result = ops[node.op_type()]( *this, {get_attributes(node), output_num, node_name, mod}, args); } output_num = std::min(output_num, result.size()); std::transform(node.output().begin(), node.output().begin() + output_num, result.begin(), std::inserter(instructions, instructions.end()), [](auto&& x, auto&& y) { return std::make_pair(x, y); }); if(enabled(MIGRAPHX_TRACE_ONNX_PARSER{})) { print_added_instructions(mod, args, result); } } // Find instructions corresponding to the output auto prog_output = graph.output(); std::vector all_output_names; std::vector prog_output_names; std::transform(prog_output.begin(), prog_output.end(), std::back_inserter(all_output_names), [](auto& node) { return node.name(); }); std::copy_if( all_output_names.begin(), all_output_names.end(), std::back_inserter(prog_output_names), [&](const auto& name) { return not(name.empty() or instructions.count(name) == 0); }); std::vector output_ins; std::transform(prog_output_names.begin(), prog_output_names.end(), std::back_inserter(output_ins), [&](const auto& name) { return instructions[name]; }); if(not inlining) { // add the return instuction mod->add_return(output_ins); // Remove instructions added in module (this is turned off for subgraph inlining) erase_if(instructions, [&](auto&& p) { return mod->has_instruction(p.second); }); } return output_ins; } literal onnx_parser::parse_value(const onnx::AttributeProto& attr) const { switch(attr.type()) { case onnx::AttributeProto::FLOAT: return literal{attr.f()}; case onnx::AttributeProto::INT: return literal{attr.i()}; case onnx::AttributeProto::TENSOR: return parse_tensor(attr.t()); case onnx::AttributeProto::FLOATS: return from_repeated(shape::float_type, attr.floats()); case onnx::AttributeProto::INTS: return from_repeated(shape::int64_type, attr.ints()); case onnx::AttributeProto::UNDEFINED: case onnx::AttributeProto::GRAPH: case onnx::AttributeProto::STRING: case onnx::AttributeProto::STRINGS: case onnx::AttributeProto::TENSORS: case onnx::AttributeProto::SPARSE_TENSOR: case onnx::AttributeProto::SPARSE_TENSORS: case onnx::AttributeProto::TYPE_PROTOS: case onnx::AttributeProto::TYPE_PROTO: case onnx::AttributeProto::GRAPHS: return {}; } MIGRAPHX_THROW("PARSE_VALUE: Invalid attribute type " + std::to_string(attr.type())); } static shape parse_tensor_shape(const onnx::TensorProto& t) { std::vector dims(t.dims().begin(), t.dims().end()); if(is_type_packed_int4(t)) { auto dim_n = dims.back(); if(dim_n > 0 and (dim_n % 2 == 0)) dims.back() = dim_n / 2; // int4-packed dimension converted to int8-sized units else MIGRAPHX_THROW("Int4: currently supports only even-sized packed tensors"); } return shape{get_type(t.data_type()), dims}; } literal onnx_parser::parse_tensor(const onnx::TensorProto& t) const { auto tensor_shape = parse_tensor_shape(t); const auto& dims = tensor_shape.lens(); auto type = tensor_shape.type(); const auto& external_data = t.external_data(); if(not external_data.empty()) { const std::string& data_file = external_data.at(0).value(); size_t num_data_fields = external_data.size(); size_t offset = 0; size_t nbytes = tensor_shape.bytes(); if(num_data_fields > 1) // if offset field is present { offset = std::stoull(t.external_data().at(1).value()); } if(num_data_fields > 2) // if nbytes field is present { nbytes = std::stoull(t.external_data().at(2).value()); } std::vector raw_buffer; if(not external_data_path.empty()) { raw_buffer = read_buffer(fs::path{external_data_path} / data_file, offset, nbytes); } else { raw_buffer = read_buffer(path / data_file, offset, nbytes); } std::string s(raw_buffer.begin(), raw_buffer.end()); return create_literal(type, dims, s.data()); } if(t.has_raw_data()) { const std::string& s = t.raw_data(); return create_literal(type, dims, s.data()); } switch(t.data_type()) { case onnx::TensorProto::BOOL: return create_literal(shape::bool_type, dims, t.int32_data()); // INT4 or UINT4 operate as 8-bit buffers: case onnx::TensorProto::INT4: return create_literal(shape::int8_type, dims, t.int32_data()); case onnx::TensorProto::UINT4: return create_literal(shape::uint8_type, dims, t.int32_data()); case onnx::TensorProto::INT8: return create_literal(shape::int8_type, dims, t.int32_data()); case onnx::TensorProto::UINT8: return create_literal(shape::uint8_type, dims, t.int32_data()); case onnx::TensorProto::INT16: return create_literal(shape::int16_type, dims, t.int32_data()); case onnx::TensorProto::UINT16: return create_literal(shape::uint16_type, dims, t.int32_data()); case onnx::TensorProto::INT32: return create_literal(shape::int32_type, dims, t.int32_data()); case onnx::TensorProto::UINT32: return create_literal(shape::uint32_type, dims, t.uint64_data()); case onnx::TensorProto::INT64: return create_literal(shape::int64_type, dims, t.int64_data()); case onnx::TensorProto::UINT64: return create_literal(shape::uint64_type, dims, t.uint64_data()); case onnx::TensorProto::FLOAT16: { std::vector data_uint16(t.int32_data().begin(), t.int32_data().end()); std::vector data_half; std::transform(data_uint16.begin(), data_uint16.end(), std::back_inserter(data_half), [](uint16_t raw_val) { return *reinterpret_cast(&raw_val); }); return create_literal(shape::half_type, dims, data_half); } case onnx::TensorProto::DOUBLE: return create_literal(shape::double_type, dims, t.double_data()); case onnx::TensorProto::FLOAT: return create_literal(shape::float_type, dims, t.float_data()); case onnx::TensorProto::FLOAT8E4M3FNUZ: { std::vector data_int32(t.int32_data().begin(), t.int32_data().end()); std::vector data_fp8; std::transform(data_int32.begin(), data_int32.end(), std::back_inserter(data_fp8), [](float raw_val) { return migraphx::fp8::fp8e4m3fnuz{raw_val}; }); return create_literal(shape::fp8e4m3fnuz_type, dims, data_fp8); } case onnx::TensorProto::FLOAT8E5M2FNUZ: case onnx::TensorProto::FLOAT8E5M2: case onnx::TensorProto::FLOAT8E4M3FN: case onnx::TensorProto::UNDEFINED: case onnx::TensorProto::STRING: case onnx::TensorProto::COMPLEX64: case onnx::TensorProto::COMPLEX128: throw std::runtime_error(""); } MIGRAPHX_THROW("PARSE_TENSOR: Invalid tensor type"); } shape onnx_parser::parse_type(const onnx::TypeProto& t) const { shape::type_t shape_type = get_type(t.tensor_type().elem_type()); std::vector dynamic_dims; auto&& tensor_dims = t.tensor_type().shape().dim(); std::transform(tensor_dims.begin(), tensor_dims.end(), std::back_inserter(dynamic_dims), [&](auto&& d) -> shape::dynamic_dimension { if(d.has_dim_param()) { const auto& dim_param = d.dim_param(); if(contains(dim_params, dim_param)) { return dim_params.at(dim_param); } } if(d.has_dim_value()) { if(static_cast(d.dim_value()) <= 0) { return default_dyn_dim_value; } std::size_t tmp = d.dim_value(); return {tmp, tmp}; } else { return default_dyn_dim_value; } }); if(dynamic_dims.empty()) { return {shape_type}; } return shape_from_dyn_dims(shape_type, dynamic_dims); } shape onnx_parser::parse_type(const onnx::TypeProto& t, const std::vector& input_dims) const { shape::type_t shape_type = get_type(t.tensor_type().elem_type()); if(input_dims.empty()) return {shape_type}; return {shape_type, input_dims}; } shape::type_t get_type(int dtype) { switch(dtype) { case 1: return shape::float_type; case 2: return shape::uint8_type; case 3: return shape::int8_type; case 4: return shape::uint16_type; case 5: return shape::int16_type; case 6: return shape::int32_type; case 7: return shape::int64_type; case 9: return shape::bool_type; case 10: return shape::half_type; case 11: return shape::double_type; case 12: return shape::uint32_type; case 13: return shape::uint64_type; case 18: return shape::fp8e4m3fnuz_type; case 21: return shape::uint8_type; case 22: return shape::int8_type; case 23: return shape::fp4x2_type; case 14: case 15: case 16: return shape::bf16_type; case 17: case 19: case 20: default: { MIGRAPHX_THROW("Prototensor data type " + std::to_string(dtype) + " not supported"); } } } bool is_type_float(shape::type_t dtype) { bool r = false; if(dtype == shape::float_type or dtype == shape::double_type or dtype == shape::half_type or dtype == shape::bf16_type) { r = true; } return r; } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/op_parser.cpp000066400000000000000000000041211510465702400210050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static std::unordered_map& op_parser_map() { static std::unordered_map m; // NOLINT return m; } void register_op_parser(const std::string& name, onnx_parser::op_func f) { op_parser_map()[name] = std::move(f); } onnx_parser::op_func get_op_parser(const std::string& name) { return op_parser_map().at(name); } std::vector get_op_parsers() { std::vector result; std::transform(op_parser_map().begin(), op_parser_map().end(), std::back_inserter(result), [&](auto&& p) { return p.first; }); std::sort(result.begin(), result.end()); return result; } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/padding.cpp000066400000000000000000000131111510465702400204200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { void cal_auto_padding_size(onnx_parser::node_info info, value& v, const std::vector& k_lens, const std::vector& dilation, const std::vector& in_lens, std::vector& paddings) { if(not contains(info.attributes, "auto_pad")) { return; } calc_auto_padding(info.attributes.at("auto_pad").s(), v["stride"].to_vector(), k_lens, dilation, in_lens, paddings); } bool is_asym_padding(const std::vector& padding) { assert(padding.size() % 2 == 0); size_t pad_ndims = padding.size() / 2; for(size_t i = 0; i < pad_ndims; i++) { if(padding[i] != padding[i + pad_ndims]) { return true; } } return false; } void check_padding_mode(const onnx_parser::node_info& info, const std::string& onnx_name) { // ensure pads availabe only when auto_pad is "NOT_SET" if(contains(info.attributes, "pads") and contains(info.attributes, "auto_pad")) { auto s = info.attributes.at("auto_pad").s(); if(to_upper(s) != "NOTSET") { MIGRAPHX_THROW("PARSE_" + to_upper(onnx_name) + ": auto_pad and padding cannot be specified simultaneously"); } } } static void tune_padding_to_symmetric(int64_t& left, int64_t& right, const int stride, int64_t& s_start) { s_start = 0; if(left > right) { right = left; } else if(left < right) { auto diff = right - left; s_start = (diff + stride - 1) / stride; left = left + s_start * stride; right = left; } } void tune_padding_size(const value& v, std::vector& padding, int count_include_pad, std::vector& s_start) { // maxpooling or count_include_pad is 1, no change is required. if(v.at("mode").to() == op::pooling_mode::max or count_include_pad == 1) { return; } // if padding is symmetric, return directly if(not is_asym_padding(padding)) { return; } // asymmetric padding, make it symmetric std::size_t n_dims = padding.size() / 2; s_start.resize(n_dims); for(std::size_t i = 0; i < n_dims; ++i) { tune_padding_to_symmetric( padding[i], padding[i + n_dims], v.at("stride")[i].to(), s_start[i]); } } void check_asym_padding(const onnx_parser::node_info& info, instruction_ref& ins, const std::vector& padding, value& v, int count_include_pad, float pad_val) { size_t pad_ndims = padding.size() / 2; auto left_pad_it = padding.begin(); auto right_pad_it = left_pad_it + pad_ndims; if(count_include_pad == 1) { std::vector asym_pads{0, 0, 0, 0}; // don't pad N and C // add left pads asym_pads.insert(asym_pads.begin() + 2, left_pad_it, right_pad_it); // add right pads asym_pads.insert(asym_pads.begin() + pad_ndims + 4, right_pad_it, padding.end()); ins = info.add_instruction(make_op("pad", {{"pads", asym_pads}, {"value", pad_val}}), ins); std::vector new_padding(padding.size()); // subtract asym padding originally found from parsing the operator std::transform(padding.begin(), left_pad_it, asym_pads.begin() + 2, new_padding.begin(), std::minus()); std::transform(right_pad_it, padding.end(), asym_pads.begin() + pad_ndims + 4, new_padding.begin() + pad_ndims, std::minus()); v["padding"] = new_padding; } } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_arg_op.cpp000066400000000000000000000056301510465702400214620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_arg_op : op_parser { std::vector operators() const { return {{"ArgMax", "argmax"}, {"ArgMin", "argmin"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, const std::vector& args) const { int64_t axis = 0; if(contains(info.attributes, "axis")) { axis = static_cast(parser.parse_value(info.attributes.at("axis")).at()); } int keep_dims = 1; if(contains(info.attributes, "keepdims")) { keep_dims = parser.parse_value(info.attributes.at("keepdims")).at(); } bool select_last_index = false; if(contains(info.attributes, "select_last_index")) { select_last_index = static_cast( parser.parse_value(info.attributes.at("select_last_index")).at()); } if(keep_dims == 0) { auto ins = info.add_instruction( make_op(opd.op_name, {{"axis", axis}, {"select_last_index", select_last_index}}), args); return info.add_instruction(make_op("squeeze", {{"axes", {axis}}}), ins); } else { return info.add_instruction( make_op(opd.op_name, {{"axis", axis}, {"select_last_index", select_last_index}}), args); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_aten.cpp000066400000000000000000000064111510465702400211400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { enum class reduce_mode_t { sum = 0, mean = 1, max = 2 }; struct parse_aten : op_parser { std::vector operators() const { return {{"ATen"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, onnx_parser::node_info info, std::vector args) const { if(contains(info.attributes, "operator")) { auto op_name = info.attributes.at("operator").s(); if(op_name.find("embedding_bag") != std::string::npos) { return parse_embedding_bag(info, std::move(args)); } } MIGRAPHX_THROW("PARSE_ATEN: unsupported custom operator"); } instruction_ref parse_embedding_bag(onnx_parser::node_info info, std::vector args) const { if(args[2]->get_shape().elements() != 1) MIGRAPHX_THROW("PARSE_EMBEDDING_BAG: MIGraphX only supports offsets of size 1"); reduce_mode_t reduce_mode = reduce_mode_t::sum; if(contains(info.attributes, "mode")) { reduce_mode = static_cast(info.attributes.at("mode").i()); } auto l0 = info.add_instruction(make_op("gather"), args[0], args[1]); switch(reduce_mode) { case reduce_mode_t::sum: l0 = info.add_instruction(make_op("reduce_sum", {{"axes", {0}}}), l0); break; case reduce_mode_t::mean: l0 = info.add_instruction(make_op("reduce_mean", {{"axes", {0}}}), l0); break; case reduce_mode_t::max: l0 = info.add_instruction(make_op("reduce_max", {{"axes", {0}}}), l0); break; } return l0; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_attention.cpp000066400000000000000000000773611510465702400222320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_attention : op_parser { std::vector operators() const { return {{"Attention"}}; } enum class mask_pad { // Used to check mask_index when input vector size == 2 no_pad, // Not Set - If input isn't set indicates this is an error with op raw, // Indicates input mask is raw mask where 0 masks the value and 1 does not. right_padding, // second dimension is (batch_size) left_padding, // second dimension }; enum class atten_mode { // Important to determine how to calculate total_sequence_length not_set, // Not Set - If Past/Present used this indicates an error with op self_attention, // Implies K,V lengths equal and equal to sequence_length cross_attention, // Relevant as K/V may have different lengths in this case }; // For items explicitly parsed from attributes struct attention_attr { bool do_rotary = false; // Rotary encode input prior to projection with weight matrices bool past_present_share_buffer = false; // Related to past input shares buffer with present output bool unidirectional = false; // Mask is lower triangular ie) only pay attention to prev words std::size_t num_heads = 1; // Required by inputs std::size_t rotary_embedding_dim = 0; // Gets set to head_size when not set std::vector qkv_hidden_sizes{ 0, 0, 0}; // Sets hidden sizes if not set defiend by input float scale = 0.0; // Default 1/sqrt(query_size) (query_size also known as head_size) float mask_filter_val = -10000.0f; // Default value used for masking our input encoding }; struct attention_args { // Parsed in attributes // Used to infer other traits wtih input arguments attention_attr attr; // Shape is (batch, sequence_length, hidden size) instruction_ref input; // Shape is (hidden size, sum of QKV hidden sizes) instruction_ref weights; // Optional inputs std::optional projection_bias; std::optional mask_index; std::optional past_input; std::optional attention_bias; std::optional past_sequence_length; size_t max_sequence_length = 0; void set_max_sequence_length(size_t val) { max_sequence_length = val; } // Optional input checks leveraging std::optional bool has_proj_bias() const { return projection_bias.has_value(); } bool has_mask() const { return mask_index.has_value(); } bool has_past_input() const { // Need to be used in tandem return past_input.has_value() and past_sequence_length.has_value(); } bool has_attn_bias() const { return attention_bias.has_value(); } // Useful infered parameters from inputs std::size_t batch_size() const { return input->get_shape().lens().at(0); } std::size_t sequence_length() const { return input->get_shape().lens().at(1); } std::size_t past_seq_length() const { return size_t(0); } std::size_t total_sequence_length() const { return sequence_length() + past_seq_length(); } std::size_t hidden_size() const { return input->get_shape().lens().at(2); } std::size_t num_heads() const { return attr.num_heads; } std::size_t query_size() const { return hidden_size() / num_heads(); } std::size_t sum_of_qkv_hidden() const { return std::accumulate( attr.qkv_hidden_sizes.begin(), attr.qkv_hidden_sizes.end(), size_t(0)); } std::size_t qk_hidden_size() const { return attr.qkv_hidden_sizes.at(0); } std::size_t v_hidden_size() const { return attr.qkv_hidden_sizes.at(2); } bool is_kv_same() const { return v_hidden_size() == qk_hidden_size(); } bool scale_is_set() const { return attr.scale != 0.0f; } float get_scale_value() const { if(scale_is_set()) return attr.scale; else return query_size(); } float get_mask_filter_val() const { return attr.mask_filter_val; } mask_pad padding_mode() const { if(has_mask()) { auto mask_shape = mask_index.value()->get_shape(); if(mask_shape.ndim() == 1) { if(mask_shape.lens().at(0) == batch_size()) return mask_pad::right_padding; else if(mask_shape.lens().at(0) == (batch_size() * 2)) return mask_pad::left_padding; } else if(mask_shape.ndim() > 1) { return mask_pad::raw; } } return mask_pad::no_pad; } // Input Vector void set_input(const instruction_ref& input_arg) { if(input_arg->get_shape().ndim() != 3) { MIGRAPHX_THROW("Attention: Input must have shape defiend as (batch, " "sequence_length, hidden_size"); } input = input_arg; } // Input Weights // qkv values must be greater than zero to be "set" bool qkv_size_not_set(std::vector& qkv_vec) { return std::any_of(qkv_vec.begin(), qkv_vec.end(), [](auto i) { return i <= 0; }); } void qkv_sizes_sum_arg_valid(const std::vector& qkv_vec, const instruction_ref input_arg, const size_t dim, const std::string& name) { if(std::accumulate(qkv_vec.begin(), qkv_vec.end(), size_t(0)) != input_arg->get_shape().lens().at(dim)) { MIGRAPHX_THROW("Attention: q k v hidden sizes sum must match " + name + " tensor " + std::to_string(dim) + " dimension"); } } bool weights_not_equal(const shape& weight_shape) { return (weight_shape.lens().at(1) % 3 != 0); } void set_weights(const instruction_ref& input_arg) { auto weight_tensor = input_arg; auto weight_shape = weight_tensor->get_shape(); auto input_shape = input->get_shape(); if(weight_shape.lens().at(0) != input_shape.lens().at(2)) { MIGRAPHX_THROW( "Attention: Input hidden size must be the same for input and weight tensors"); } if(weight_shape.type() != input_shape.type()) { MIGRAPHX_THROW("Attention: Input and weight datatype must be the same"); } if(weights_not_equal(weight_shape)) { if(qkv_size_not_set(attr.qkv_hidden_sizes)) MIGRAPHX_THROW( "Attention: QKV size attribute must be set with non even weights"); if(past_input.has_value()) MIGRAPHX_THROW("Attention: QKV size must be equally sized when using " "past/present buffers"); } else { // QKV is identical when second weight dim is divisible by 3 and qkv not set if(qkv_size_not_set(attr.qkv_hidden_sizes)) { std::vector default_qkv_sizes(3, (weight_shape.lens().at(1) / 3)); attr.qkv_hidden_sizes = default_qkv_sizes; } } // Ensure qkv_hidden sizes set are valid wrt input weights qkv_sizes_sum_arg_valid(attr.qkv_hidden_sizes, weight_tensor, 1, "weights"); weights = weight_tensor; } // Helpers for optional parametrs // simple call to check if the arg index exists std::optional check_and_return_arg(const std::vector& args, const size_t index) { if(args.size() > index) { return args.at(index); } return nullopt; } void set_projection_bias(const std::vector& args) { if(auto bias = check_and_return_arg(args, 2)) { auto bias_shape = (*bias)->get_shape(); const auto& bias_lens = bias_shape.lens(); // ensure qkv dimension sum matches that of the bias vec qkv_sizes_sum_arg_valid(attr.qkv_hidden_sizes, *bias, 0, "bias"); if(args.at(0)->get_shape().type() != bias_shape.type()) { MIGRAPHX_THROW("Attention: input bias must be the same type as input vector"); } if(bias_lens.size() != 1) { MIGRAPHX_THROW( "Attention: Bias requires tensor of (hidden_size + hidden_size + " "v_hidden_size) "); } projection_bias = bias; } } // Input Mask_index // Helper void check_mask_index_shapes(const std::vector& mask_index_lens) { // Mask index is handled differently based on size of the input. // // raw attention mask has shape (batch, total sequence_length) // or (batch, seq_length, total_sequence_length) with 0/1 // values // where: total_sequence_length = sequence_length + // past_sequence_length // // Right side padded has shape (batch) - value is sequence_length excluding padding // Left side Padding has shape (2 * batch) with inclusive start and exclusive end // positions if(mask_index_lens.size() == 1) { // check left or right padding case MIGRAPHX_THROW("Attention: Left/Right Padding not currently supported"); } else if(mask_index_lens.size() == 2) { // This case assumes potentially past is set which is captured in // total_sequence_length if(mask_index_lens.at(0) != batch_size() or mask_index_lens.at(1) != total_sequence_length()) { MIGRAPHX_THROW("Attention: Invalid Mask_Index shape\n \ Use (batch, total_sequence_length) for shapes of size 2"); } } else if(mask_index_lens.size() == 3) { // Similar to case 2 but with sequence length in dim 1 if(mask_index_lens.at(0) != batch_size() or mask_index_lens.at(1) != sequence_length() or mask_index_lens.at(2) != total_sequence_length()) { MIGRAPHX_THROW("Attention: Invalid Mask_Index shape\n \ Use (batch, sequence_length, total_sequence_length) for shapes of size 3"); } MIGRAPHX_THROW("Attention: Mask_index 3D masking not supported"); } else if(mask_index_lens.size() == 4) { // Oddball case and can be used to infer max_sequence_length_parameter if(mask_index_lens.at(0) != batch_size() or mask_index_lens.at(1) != 1 or mask_index_lens.at(2) != mask_index_lens.at(3)) { MIGRAPHX_THROW("Attention: Invalid Mask_Index shape\n \ Use (batch, 1, max_sequence_length, max_sequence_length) for shapes of size 4"); } set_max_sequence_length(mask_index_lens.at(2)); MIGRAPHX_THROW("Attention: Mask_index 4D Megatron masking not supported"); } else { MIGRAPHX_THROW( "Attention: Mask_index Require shape of size either 1, 2, 3, 4 dimensions"); } } void set_mask_index(const std::vector& args) { if(auto mask = check_and_return_arg(args, 3)) { auto mask_index_shape = (*mask)->get_shape(); const auto& mask_index_lens = mask_index_shape.lens(); if(mask_index_shape.type() != migraphx::shape::int32_type) { MIGRAPHX_THROW("Attention: Mask_Index type must be int32 type"); } check_mask_index_shapes(mask_index_lens); mask_index = mask; } } // Unsupported Currently void handle_past(const std::vector& args) { if(auto past = check_and_return_arg(args, 4)) { MIGRAPHX_THROW("Attention: Past Not supported"); } } void handle_attention_bias(const std::vector& args) { if(auto atten_bias = check_and_return_arg(args, 5)) { MIGRAPHX_THROW("Attention: attention_bias Not supported"); } } void handle_past_sequence_length(const std::vector& args) { if(auto past_seq_length = check_and_return_arg(args, 6)) { MIGRAPHX_THROW("PARSE_ATTENTION: past_sequence_length not supported"); } } }; static void handle_qkv_hidden_size_attr(const onnx_parser& parser, const onnx_parser::node_info& info, attention_attr& attr_out) { auto input_val = parser.parse_value(info.attributes.at("qkv_hidden_sizes")); std::vector qkv_values; if(input_val.get_shape().type() != shape::int64_type) { MIGRAPHX_THROW("PARSE_ATTENTION: qkv_hidden_sizes must be int64 type"); } qkv_values = input_val.get_argument().to_vector(); if(qkv_values.size() != 3) { MIGRAPHX_THROW("PARSE_ATTENTION: qkv_hidden_sizes must have exactly 3 values"); } if(qkv_values[0] != qkv_values[1]) { MIGRAPHX_THROW("Attention: q and k hidden sizes must be identitical!"); } std::vector qkv_vec{static_cast(qkv_values[0]), static_cast(qkv_values[1]), static_cast(qkv_values[2])}; if(std::any_of(qkv_vec.begin(), qkv_vec.end(), [](auto i) { return (i == 0) or (i < 0); })) { MIGRAPHX_THROW("PARSE_ATTENTION: qkv_hidden_sizes must be nonzero and valid"); } attr_out.qkv_hidden_sizes = qkv_vec; } static attention_attr handle_attributes(const onnx_parser& parser, const onnx_parser::node_info& info) { attention_attr attr_out; if(contains(info.attributes, "do_rotary")) { // TODO: Add rotary embedding support attr_out.do_rotary = (1 == parser.parse_value(info.attributes.at("do_rotary")).at()); if(attr_out.do_rotary) MIGRAPHX_THROW("PARSE_ATTENTION: Rotary Embedding in Attention OP not supported"); } if(contains(info.attributes, "mask_filter_value")) { attr_out.mask_filter_val = parser.parse_value(info.attributes.at("mask_filter_value")).at(); } if(contains(info.attributes, "num_heads")) { attr_out.num_heads = parser.parse_value(info.attributes.at("num_heads")).at(); } else { MIGRAPHX_THROW("PARSE_ATTENTION: num_heads attribute required"); } if(contains(info.attributes, "past_present_share_buffer")) { attr_out.past_present_share_buffer = (1 == parser.parse_value(info.attributes.at("past_present_share_buffer")).at()); } if(contains(info.attributes, "qkv_hidden_sizes")) { handle_qkv_hidden_size_attr(parser, info, attr_out); } if(contains(info.attributes, "rotary_embedding_dim")) { // TODO: Add rotary embedding support -- parsed but not used right now auto rotary_embedding_dim = parser.parse_value(info.attributes.at("rotary_embedding_dim")).at(); if(rotary_embedding_dim != 32 and rotary_embedding_dim != 64 and rotary_embedding_dim != 128) { MIGRAPHX_THROW( "PARSE_ATTENTION: rotary_embedding_dim must be either 32, 64, or 128"); } if(not attr_out.do_rotary) { MIGRAPHX_THROW( "PARSE_ATTENTION: rotary_embedding_dim must be used with do_rotary attribute"); } attr_out.rotary_embedding_dim = rotary_embedding_dim; } if(contains(info.attributes, "scale")) { attr_out.scale = parser.parse_value(info.attributes.at("scale")).at(); } if(contains(info.attributes, "unidirectional")) { attr_out.unidirectional = (1 == parser.parse_value(info.attributes.at("unidirectional")).at()); if(attr_out.unidirectional) MIGRAPHX_THROW("PARSE_ATTENTION: unidirectional attr not supported"); } return attr_out; } static attention_args handle_inputs(const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) { if(args.size() < 2 or args.size() > 7) { MIGRAPHX_THROW("Attention: Wrong number of inputs provided"); } attention_args attention_block; // Required inputs attention_block.attr = handle_attributes(parser, info); attention_block.set_input(args.at(0)); attention_block.set_weights(args.at(1)); // Optional inputs attention_block.set_projection_bias(args); attention_block.set_mask_index(args); // Currently not supported attention_block.handle_past(args); attention_block.handle_attention_bias(args); attention_block.handle_past_sequence_length(args); return attention_block; } static std::vector qkv_split_per_head(const onnx_parser::node_info& info, const std::vector& qkv_mats, const size_t num_heads) { auto q_lens = qkv_mats.at(0)->get_shape().lens(); auto k_lens = qkv_mats.at(1)->get_shape().lens(); auto v_lens = qkv_mats.at(2)->get_shape().lens(); // Split embedding into querry size and num heads from embedding dimension // Permute so we now result in (batch, sequence_length, querry_size, num_heads) prior to // calculations auto split_q = info.add_instruction( make_op("reshape", {{"dims", {q_lens.at(0), q_lens.at(1), num_heads, q_lens.at(2) / num_heads}}}), qkv_mats.at(0)); auto split_k = info.add_instruction( make_op("reshape", {{"dims", {k_lens.at(0), k_lens.at(1), num_heads, k_lens.at(2) / num_heads}}}), qkv_mats.at(1)); auto split_v = info.add_instruction( make_op("reshape", {{"dims", {v_lens.at(0), v_lens.at(1), num_heads, v_lens.at(2) / num_heads}}}), qkv_mats.at(2)); // Permute so we now result in (batch, num heads, sequence_length, querry_size) prior to // calculations split_q = info.add_instruction(make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), split_q); split_k = info.add_instruction(make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), split_k); split_v = info.add_instruction(make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), split_v); return {split_q, split_k, split_v}; } static instruction_ref scale_dot_attention_head(const onnx_parser::node_info& info, const std::vector& qkv, const instruction_ref& scale_factor, const std::optional& mask, const std::optional& bias) { auto q = qkv.at(0); auto k = qkv.at(1); auto v = qkv.at(2); auto k_trans = info.add_instruction(make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), k); auto qk_out = info.add_instruction(make_op("dot"), q, k_trans); // Apply bias to QK result auto qk_biased = qk_out; if(bias.has_value()) { auto bc_bias = info.add_instruction( make_op("multibroadcast", {{"out_lens", qk_out->get_shape().lens()}}), bias.value()); qk_biased = info.add_common_op("add", qk_out, bc_bias); } // Mask must be done after all bias and calculations done auto qk_masked = qk_biased; if(mask.has_value()) { qk_masked = info.add_common_op("add", qk_masked, mask.value()); } // Apply scale only after all the masking and biasing has occurred auto qk_scaled = info.add_common_op("mul", qk_masked, scale_factor); auto softmax_out = info.add_instruction(make_op("softmax", {{"axis", 3}}), qk_scaled); // Final result to compare with respect to values matrix auto output = info.add_instruction(make_op("dot"), softmax_out, v); // Transpose result from (batch, num heads, sequence_length, query_size) to (batch, // sequence_length, num_heads, query_size) output = info.add_instruction(make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), output); // Collapse back to (batch, sequence_length, query_size) auto lens = output->get_shape().lens(); output = info.add_instruction( make_op("reshape", {{"dims", {lens.at(0), lens.at(1), lens.at(2) * lens.at(3)}}}), output); return output; } // Get Q, K, V matricies from stacked weight matrix static std::vector input_linear_to_qkv(const onnx_parser::node_info& info, const instruction_ref& input, const instruction_ref& stacked_weights, const std::vector& qkv_sizes, const std::optional& input_bias) { // Input encodes the batch, sequence_length and input_hidden_size (also known as embedding // size) auto input_lens = input->get_shape().lens(); auto stacked_weights_unsq = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), stacked_weights); auto w_lens = stacked_weights_unsq->get_shape().lens(); w_lens.at(0) = input_lens.at(0); auto stacked_weights_unsq_bcast = info.add_instruction( make_op("multibroadcast", {{"out_lens", w_lens}}), stacked_weights_unsq); auto stacked_result = info.add_instruction(make_op("dot"), input, stacked_weights_unsq_bcast); if(input_bias.has_value()) { stacked_result = info.add_common_op("add", stacked_result, input_bias.value()); } // Input stacked weights are (input_hidden_size, hidden_size + hidden_size + v_hidden_size) // so slice out parts for each matrix Since we known the input_hidden size is one dimension // wee need to slice out the weight tensors accordingly before we perform matmul auto q = info.add_instruction( make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {qkv_sizes.at(0)}}}), stacked_result); auto k = info.add_instruction(make_op("slice", {{"axes", {2}}, {"starts", {qkv_sizes.at(0)}}, {"ends", {qkv_sizes.at(1) + qkv_sizes.at(0)}}}), stacked_result); auto v = info.add_instruction( make_op("slice", {{"axes", {2}}, {"starts", {qkv_sizes.at(0) + qkv_sizes.at(1)}}, {"ends", {qkv_sizes.at(0) + qkv_sizes.at(1) + qkv_sizes.at(2)}}}), stacked_result); std::vector qkv_mats{q, k, v}; return qkv_mats; } // Slice, mul, convert and concat until we get a mask matrix useful prior to the where static instruction_ref generate_raw_mask_per_batch(const onnx_parser::node_info& info, const attention_args& attention) { auto batch_size = attention.batch_size(); auto total_seq_len = attention.total_sequence_length(); auto num_heads = attention.num_heads(); // Other two cases require us to generate masks from sequence or total sequence length pads. auto pass_value_lit = info.add_literal( migraphx::literal{migraphx::shape{attention.input->get_shape().type(), {1}, {1}}, {0}}); auto mask_value_lit = info.add_literal( migraphx::literal{migraphx::shape{attention.input->get_shape().type(), {1}, {1}}, {attention.get_mask_filter_val()}}); // For dim = 2 or dim =3 generate the apporiate mask across batches // We need to handle the batch case since raw masking involes shape [batch, seq_len] or // [batch, seq_len, total_seq_len], auto bc_pass = info.add_instruction( make_op("multibroadcast", {{"out_lens", {batch_size, num_heads, total_seq_len, total_seq_len}}}), pass_value_lit); auto bc_mask = info.add_instruction( make_op("multibroadcast", {{"out_lens", {batch_size, num_heads, total_seq_len, total_seq_len}}}), mask_value_lit); auto raw_mask = attention.mask_index.value(); // For raw masks we just need to mask out key value padding thus the 3d mask isn't needed // here. raw_mask = info.add_instruction( make_op("reshape", {{"dims", {batch_size, 1, 1, total_seq_len}}}), raw_mask); raw_mask = info.add_instruction( make_op("multibroadcast", {{"out_lens", {batch_size, num_heads, total_seq_len, total_seq_len}}}), raw_mask); raw_mask = info.add_instruction( make_op("reshape", {{"dims", {batch_size, num_heads, total_seq_len, total_seq_len}}}), raw_mask); // Reuse "0" broadcasted converted to int32 to check if input mask is greater than 0 for // where condition auto in_pass = info.add_instruction( make_op("convert", {{"target_type", (attention.mask_index).value()->get_shape().type()}}), bc_pass); auto in_bool = info.add_instruction(make_op("equal"), raw_mask, in_pass); in_bool = info.add_instruction( make_op("convert", {{"target_type", migraphx::shape::bool_type}}), in_bool); return info.add_instruction(make_op("where"), in_bool, bc_mask, bc_pass); } static std::optional create_input_mask(const onnx_parser::node_info& info, const attention_args& attention) { // Shape Scale dot attention prior to mask will be in (batch, num_heads, query_size, // query_size) thus mask needs to handle batch and query_size We should return mask of // batch, 1, query_size, query_size so that this per-batch masked can be broadcasted across // each attention head if(attention.padding_mode() == mask_pad::raw) { // Raw Mask - 0 means mask, 1 means pass through. Apply mask_filter_val to mask indicies // and zero otherwise // Need to generate from 2 dims or 3 dim cases return generate_raw_mask_per_batch(info, attention); } return nullopt; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { attention_args attention = handle_inputs(parser, info, args); // Apply linear stage to QKV mats from weight matrix - If past input just return q mat later // split will be extracted from past vector auto qkv_mats = input_linear_to_qkv(info, attention.input, attention.weights, attention.attr.qkv_hidden_sizes, attention.projection_bias); // Set attention mask and bias when detected on input std::optional attn_mask; if(attention.has_mask()) attn_mask = create_input_mask(info, attention); // Used to scale all key values before any masking or other inputs auto scale_factor = info.add_literal(migraphx::literal{ migraphx::shape{qkv_mats.at(0)->get_shape().type()}, {attention.get_scale_value()}}); if(not attention.scale_is_set()) { scale_factor = info.add_instruction(make_op("sqrt"), scale_factor); scale_factor = info.add_instruction(make_op("recip"), scale_factor); } // split QKV into proper batched attention head shape before we perform scale_dot_attention // (saves us a concat) auto split_qkv = qkv_split_per_head(info, qkv_mats, attention.num_heads()); return scale_dot_attention_head( info, split_qkv, scale_factor, attn_mask, attention.attention_bias); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_batchnorm.cpp000066400000000000000000000041711510465702400221670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_batchnorm : op_parser { std::vector operators() const { return {{"BatchNormalization"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { value options = {}; if(contains(info.attributes, "epsilon")) { const float epsilon = parser.parse_value(info.attributes.at("epsilon")).at(); options.insert({"epsilon", epsilon}); } return op::builder::add("batchnorm", *info.mod, args, options).at(0); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_biasadd.cpp000066400000000000000000000036511510465702400216030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_biasadd : op_parser { std::vector operators() const { return {{"BiasAdd"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, const std::vector& args) const { auto x_plus_bias = info.add_common_op("add", args[0], args[1]); return info.add_common_op("add", x_plus_bias, args[2]); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_binary_op.cpp000066400000000000000000000065411510465702400221770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_binary_op : op_parser { std::vector operators() const { return {{"Add", "add"}, {"Div", "div"}, {"And", "logical_and"}, {"Or", "logical_or"}, {"Xor", "logical_xor"}, {"BitwiseAnd", "bitwise_and"}, {"Mul", "mul"}, {"PRelu", "prelu"}, {"Sub", "sub"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { if(args.size() != 2) MIGRAPHX_THROW("binary operators should have 2 operands"); if(contains(info.attributes, "broadcast") and contains(info.attributes, "axis")) { uint64_t broadcasted = parser.parse_value(info.attributes.at("broadcast")).at(); if(broadcasted != 0) { if(std::any_of( args.cbegin(), args.cend(), [](auto a) { return a->get_shape().dynamic(); })) { MIGRAPHX_THROW( "Binary op broadcast attribute not supported for dynamic input shapes"); } uint64_t axis = parser.parse_value(info.attributes.at("axis")).at(); auto l = info.add_instruction( make_op("broadcast", {{"axis", axis}, {"out_lens", args[0]->get_shape().lens()}}), args[1]); return info.add_instruction(make_op(opd.op_name), args[0], l); } return info.add_instruction(make_op(opd.op_name), args); } else { return info.add_broadcastable_binary_op(opd.op_name, args[0], args[1]); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_cast.cpp000066400000000000000000000041651510465702400211470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_cast : op_parser { std::vector operators() const { return {{"Cast"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, const std::vector& args) const { if(not contains(info.attributes, "to")) { MIGRAPHX_THROW("PARSE_CAST: missing to type attribute!"); } int to_type = parser.parse_value(info.attributes.at("to")).at(); shape::type_t type = get_type(to_type); return info.add_instruction(make_op("convert", {{"target_type", type}}), args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_castlike.cpp000066400000000000000000000041651510465702400220140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_castlike : op_parser { std::vector operators() const { return {{"CastLike"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, const std::vector& args) const { if(not(args.size() == 2)) { MIGRAPHX_THROW("PARSE_CASTLIKE: CastLike must have exactly 2 inputs!"); } shape::type_t target_type = args[1]->get_shape().type(); return info.add_instruction(make_op("convert", {{"target_type", target_type}}), args[0]); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_celu.cpp000066400000000000000000000040471510465702400211440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_celu : op_parser { std::vector operators() const { return {{"Celu"}}; } instruction_ref parse(const op_desc&, const onnx_parser&, const onnx_parser::node_info& info, const std::vector& args) const { value options = {}; if(contains(info.attributes, "alpha")) { const float alpha = info.attributes.at("alpha").f(); options.insert({"alpha", alpha}); } return op::builder::add("celu", *info.mod, args, options).at(0); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_clip.cpp000066400000000000000000000044341510465702400211430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_clip : op_parser { std::vector operators() const { return {{"Clip"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { if(args.size() == 1 and contains(info.attributes, "min") and contains(info.attributes, "max")) { float min_val = parser.parse_value(info.attributes.at("min")).at(); float max_val = parser.parse_value(info.attributes.at("max")).at(); args.push_back(info.add_literal(min_val)); args.push_back(info.add_literal(max_val)); } return op::builder::add("clip", *info.mod, args, {}).at(0); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_compare_op.cpp000066400000000000000000000042211510465702400223320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_compare_op : op_parser { std::vector operators() const { return {{"Equal", "equal"}, {"Greater", "greater"}, {"Less", "less"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { auto l = info.add_broadcastable_binary_op(opd.op_name, args[0], args[1]); if(l->get_shape().type() != shape::bool_type) { l = info.add_instruction(make_op("convert", {{"target_type", shape::bool_type}}), l); } return l; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_constant.cpp000066400000000000000000000063321510465702400220440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_constant : op_parser { std::vector operators() const { return {{"Constant"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, const std::vector& /*args*/) const { static const std::vector attributes = { "value", "value_float", "value_floats", "value_int", "value_ints"}; std::vector present_attributes; std::copy_if(attributes.begin(), attributes.end(), std::back_inserter(present_attributes), [&](const std::string& a) { return contains(info.attributes, a); }); if(present_attributes.empty()) { MIGRAPHX_THROW("Constant node does not contain any supported attribute"); } if(present_attributes.size() > 1) { MIGRAPHX_THROW("Constant contains multiple attributes: " + join_strings(std::move(present_attributes), ", ")); } // cppcheck-suppress accessMoved auto&& attr = info.attributes[present_attributes[0]]; literal v = parser.parse_value(attr); // return empty literal if(v.get_shape().elements() == 0) { return info.add_literal(literal{v.get_shape().type()}); } // if dim_size is 0, it is a scalar if(attr.has_t() and attr.t().dims_size() == 0) { migraphx::shape scalar_shape{v.get_shape().type()}; return info.add_literal(migraphx::literal{scalar_shape, v.data()}); } return info.add_literal(v); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_constant_fill.cpp000066400000000000000000000105401510465702400230460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // Use a literal instruction to replace the constantFill operator. In RNN, input shape // and value are fixed, so no need to do the actual computation for the constantFill // operator struct parse_constant_fill : op_parser { std::vector operators() const { return {{"ConstantFill"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { int input_as_shape = 0; int dtype = 1; float value = 0.0f; if(contains(info.attributes, "dtype")) { dtype = parser.parse_value(info.attributes.at("dtype")).at(); } shape::type_t type = get_type(dtype); if(contains(info.attributes, "input_as_shape")) { input_as_shape = parser.parse_value(info.attributes.at("input_as_shape")).at(); } if(contains(info.attributes, "value")) { value = parser.parse_value(info.attributes.at("value")).at(); } if(contains(info.attributes, "extra_shape")) { MIGRAPHX_THROW("ConstantFill: cannot handle extra shape attribute"); } if(input_as_shape == 1) { if(args.size() != 1) { MIGRAPHX_THROW("ConstantFill: need an input argument as output shape"); } if(contains(info.attributes, "shape")) { MIGRAPHX_THROW("ConstantFill: cannot set the shape argument and pass in an input " "at the same time"); } migraphx::argument in = args[0]->eval(); check_arg_empty(in, "ConstantFill: dynamic shape is not supported"); std::vector dims; in.visit([&](auto input) { dims.assign(input.begin(), input.end()); }); migraphx::shape s(type, dims); std::vector values(s.elements(), value); return info.add_literal(migraphx::literal(s, values)); } else if(input_as_shape == 0) { if(not contains(info.attributes, "shape")) { MIGRAPHX_THROW("ConstantFill: attribute output shape is needed"); } literal ls = parser.parse_value(info.attributes.at("shape")); std::vector dims; ls.visit([&](auto s) { dims.assign(s.begin(), s.end()); }); migraphx::shape s{type, dims}; std::vector values(s.elements(), value); return info.add_literal(migraphx::literal(s, values)); } else { MIGRAPHX_THROW("ConstantFill: wrong value of attribute input_as_shape"); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_constant_of_shape.cpp000066400000000000000000000100531510465702400237030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_constant_of_shape : op_parser { std::vector operators() const { return {{"ConstantOfShape"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { literal l_val{}; if(contains(info.attributes, "value")) { l_val = parser.parse_value(info.attributes.at("value")); if(l_val.get_shape().elements() != 1) { MIGRAPHX_THROW("ConstantOfShape: attribute value can contain only 1 elements!"); } // convert to a scalar literal l_val = literal(shape{l_val.get_shape().type(), {1}, {0}}, l_val.data()); } else { l_val = literal({shape::float_type, {1}, {0}}, {0.0f}); } if(args.empty()) { MIGRAPHX_THROW("ConstantOfShape : must have 1 input!"); } else { migraphx::shape s; // input is empty, output is a scalar auto type = l_val.get_shape().type(); migraphx::argument input = args[0]->eval(); if(not input.empty()) { // empty input tensor, output is a scalar if(args[0]->get_shape().elements() == 0) { s = migraphx::shape{type, {1}, {0}}; } else { std::vector dims; input.visit([&](auto ia) { dims.assign(ia.begin(), ia.end()); }); s = migraphx::shape{type, dims}; } literal l_out{}; l_val.visit([&](auto val) { using val_type = std::remove_cv_t; // l_val contains only one element std::vector out_vec(s.elements(), val.front()); l_out = literal(s, out_vec); }); return info.add_literal(l_out); } // has variable input (dynamic shape buffer) else { auto dv_lit = info.add_literal(l_val); auto alloc_ins = info.add_instruction(make_op("allocate", {{"buf_type", type}}), args[0]); return info.add_instruction(make_op("fill"), dv_lit, alloc_ins); } } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_conv_transpose.cpp000066400000000000000000000200471510465702400232550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { template static std::vector to_int64_vector(const std::vector& input_vector) { std::vector output_vector(input_vector.begin(), input_vector.end()); return output_vector; } struct parse_conv_transpose : op_parser { std::vector operators() const { return {{"ConvTranspose"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { operation op = make_op("convolution_backwards"); value values = op.to_value(); auto l0 = args[0]; std::vector padding; bool asym_padding = false; assert(l0->get_shape().ndim() > 2); auto kdims = l0->get_shape().ndim() - 2; // ensure pads available only when auto_pad is "NOT_SET" check_padding_mode(info, opd.onnx_name); if(contains(info.attributes, "pads")) { copy(info.attributes["pads"].ints(), std::back_inserter(padding)); asym_padding = is_asym_padding(padding); size_t pad_ndims = padding.size() / 2; if(not asym_padding) { check_attr_sizes(kdims, pad_ndims, "PARSE_CONV_TRANSPOSE: inconsistent paddings"); values["padding"].clear(); std::transform(padding.begin(), padding.begin() + pad_ndims, std::back_inserter(values["padding"]), [](auto pad_val) { return pad_val; }); } else if(l0->get_shape().dynamic()) { MIGRAPHX_THROW("PARSE_CONV_TRANSPOSE: asymmetric padding (padding_L != padding_R) " "not supported with dynamic shapes"); } else { // set padding to 0s, asym_padding handled by parser with slice // TODO changing parser and op to do asym padding in op values["padding"] = std::vector(pad_ndims, 0); } } if(contains(info.attributes, "strides")) { values["stride"].clear(); copy(info.attributes["strides"].ints(), std::back_inserter(values["stride"])); check_attr_sizes( kdims, values["stride"].size(), "PARSE_CONV_TRANSPOSE: inconsistent strides"); } if(contains(info.attributes, "dilations")) { values["dilation"].clear(); copy(info.attributes["dilations"].ints(), std::back_inserter(values["dilation"])); check_attr_sizes( kdims, values["dilation"].size(), "PARSE_CONV_TRANSPOSE: inconsistent dilations"); } // TODO: auto padding needs to be implemented for this parser and operator if(contains(info.attributes, "auto_pad") and to_upper(info.attributes.at("auto_pad").s()) != "NOTSET") { MIGRAPHX_THROW("PARSE_CONV_TRANSPOSE: auto padding not supported"); } if(contains(info.attributes, "group")) { values["group"] = parser.parse_value(info.attributes.at("group")).at(); } recalc_conv_attributes(values, kdims); op.from_value(values); auto l1 = info.add_instruction(op, l0, args[1]); if(asym_padding) { std::vector dims = to_int64_vector(l1->get_shape().lens()); std::vector curr_shape(dims.begin() + 2, dims.end()); std::vector axes(kdims); std::iota(axes.begin(), axes.end(), 2); // ignore first 2 dims auto pad_kdim_start = padding.begin() + kdims; std::vector starts(padding.begin(), pad_kdim_start); std::vector ends{}; std::transform(curr_shape.begin(), curr_shape.end(), pad_kdim_start, std::back_inserter(ends), [](auto curr_dim, auto pad_dim) { return curr_dim - pad_dim; }); l1 = info.add_instruction( make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}), l1); } // TODO, should check output_padding < (strides or dilations) if(contains(info.attributes, "output_padding") and not contains(info.attributes, "output_shape")) { size_t non_kdims = l1->get_shape().ndim() * 2 - kdims; std::vector output_padding(non_kdims, 0); copy(info.attributes["output_padding"].ints(), std::back_inserter(output_padding)); check_attr_sizes(kdims, output_padding.size() - non_kdims, "PARSE_CONV_TRANSPOSE: inconsistent output padding"); l1 = info.add_instruction(make_op("pad", {{"pads", output_padding}}), l1); } // TODO, doing unnecessary calcuations with this. Could instead // calculate the padding to conv_transpose that would give the output_shape. if(contains(info.attributes, "output_shape")) { if(l1->get_shape().dynamic()) { MIGRAPHX_THROW("PARSE_CONV_TRANSPOSE: output_shape attribute and dynamic shapes " "not supported"); } std::vector dims = to_int64_vector(l1->get_shape().lens()); std::vector curr_shape(dims.begin() + 2, dims.end()); std::vector output_shape; copy(info.attributes["output_shape"].ints(), std::back_inserter(output_shape)); check_attr_sizes( kdims, output_shape.size(), "PARSE_CONV_TRANSPOSE: inconsistent output shape"); if(curr_shape != output_shape) { std::vector target_padding(dims.size() * 2 - kdims, 0); std::transform(output_shape.begin(), output_shape.end(), curr_shape.begin(), std::back_inserter(target_padding), [](auto out_dim, auto curr_dim) { return out_dim - curr_dim; }); l1 = info.add_instruction(make_op("pad", {{"pads", target_padding}}), l1); } } return info.add_bias(args, l1, 1); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_convolution.cpp000066400000000000000000000076071510465702400226000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_convolution : op_parser { std::vector operators() const { return {{"Conv", "convolution"}, {"ConvInteger", "quant_convolution"}, {"NhwcConv", "convolution"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { if(opd.onnx_name == "NhwcConv") { auto x = args[0]; auto weights = args[1]; args[0] = from_nhwc(info, x); args[1] = from_nhwc(info, weights); } // ensure pads available only when auto_pad is "NOT_SET" check_padding_mode(info, opd.onnx_name); value options = {}; if(contains(info.attributes, "strides")) { const auto& attr = info.attributes["strides"].ints(); std::vector strides{attr.begin(), attr.end()}; options.insert({"strides", strides}); } if(contains(info.attributes, "dilations")) { const auto& attr = info.attributes["dilations"].ints(); std::vector dilations{attr.begin(), attr.end()}; options.insert({"dilations", dilations}); } if(contains(info.attributes, "pads")) { const auto& attr = info.attributes["pads"].ints(); std::vector paddings{attr.begin(), attr.end()}; options.insert({"paddings", paddings}); } if(contains(info.attributes, "group")) { const auto group = parser.parse_value(info.attributes.at("group")).at(); options.insert({"group", group}); } if(contains(info.attributes, "auto_pad")) { const auto auto_pad = to_upper(info.attributes["auto_pad"].s()); options.insert({"auto_pad", auto_pad}); } auto ret = op::builder::add(opd.op_name, *info.mod, args, options).at(0); if(opd.onnx_name == "NhwcConv") { ret = to_nhwc(info, ret); } return ret; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_depthtospace.cpp000066400000000000000000000071711510465702400227000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_depthtospace : op_parser { std::vector operators() const { return {{"DepthToSpace"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { auto s = args[0]->get_shape(); // mode attribute of DepthToSpace auto mode = std::string("DCR"); if(contains(info.attributes, "mode")) { mode = info.attributes.at("mode").s(); // DCR or CRD? } // blocksize attribute of DepthToSpace int blocksize = 0; if(contains(info.attributes, "blocksize")) { blocksize = info.attributes.at("blocksize").i(); } if(blocksize < 1) { MIGRAPHX_THROW("DepthToSpace: blocksize is less than 1"); } // calculate dimensions auto lens1 = s.lens(); auto lens2 = s.lens(); unsigned long divisor = std::pow(blocksize, 2); if((lens2[1] % divisor) == 0) lens2[1] = lens2[1] / divisor; else MIGRAPHX_THROW("DepthToSpace: div by blocksize quotient not int "); lens1.push_back(lens1[2]); lens1.push_back(lens1[3]); lens2[2] = lens2[2] * blocksize; lens2[3] = lens2[3] * blocksize; lens1[2] = blocksize; std::vector perm; if(mode == "DCR") { lens1[3] = lens1[1] / divisor; lens1[1] = blocksize; perm = {0, 3, 4, 1, 5, 2}; } else if(mode == "CRD") { lens1[1] = lens1[1] / divisor; lens1[3] = blocksize; perm = {0, 1, 4, 2, 5, 3}; } else MIGRAPHX_THROW("DepthToSpace: mode attribute cannot be read."); auto temp1 = info.add_instruction(make_op("reshape", {{"dims", lens1}}), args[0]); auto temp2 = info.add_instruction(make_op("transpose", {{"permutation", perm}}), temp1); return info.add_instruction(make_op("reshape", {{"dims", lens2}}), temp2); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_dequantizelinear.cpp000066400000000000000000000064661510465702400235670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_dequantizelinear : op_parser { std::vector operators() const { return {{"DequantizeLinear"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { if(args.size() < 2 or args.size() > 3) { MIGRAPHX_THROW("DequantizeLinear: must have either 2 or 3 inputs, " + std::to_string(args.size()) + " inputs provided"); } if(args.size() == 3) { if(args[0]->get_shape().type() != args[2]->get_shape().type()) MIGRAPHX_THROW("DequantizeLinear: x and y_zero_point must be of same type"); if(args[1]->get_shape().lens() != args[2]->get_shape().lens()) { MIGRAPHX_THROW("DequantizeLinear: y_scale and y_zero_point shape mismatch. " "Provided y_scale " "shape: " + to_string_range(args[1]->get_shape().lens()) + ", provided y_zero_point shape: " + to_string_range(args[2]->get_shape().lens())); } } int axis = 1; if(contains(info.attributes, "axis")) axis = info.attributes.at("axis").i(); int block_size = 0; if(contains(info.attributes, "block_size")) block_size = info.attributes.at("block_size").i(); args = transform_quantize_dequantize_linear_inputs( info, opd.onnx_name, block_size, axis, args); return info.add_instruction(make_op("dequantizelinear"), args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_dropout.cpp000066400000000000000000000042371510465702400217110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_dropout : op_parser { std::vector operators() const { return {{"Dropout"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { auto out = info.add_instruction(make_op("identity"), args[0]); auto s = args[0]->get_shape(); std::vector vec(s.elements(), 1); shape mask_s{shape::bool_type, s.lens()}; auto mask = info.add_literal(literal(mask_s, vec)); return {out, mask}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_dynamicquantizelinear.cpp000066400000000000000000000146201510465702400246120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /* ********************************************************************************* * Reference: see DynamicQuantizeLinear in * * https://github.com/onnx/onnx/blob/main/docs/Operators.md * ********************************************************************************* DynamicQuantizeLinear A Function to fuse calculation for Scale, Zero Point and FP32->8Bit conversion of FP32 Input data. Outputs Scale, ZeroPoint and Quantized Input for a given FP32 Input. Scale is calculated as: y_scale = (maximum(0, max(x)) - minimum(0, min(x))) / (qmax - qmin) * where qmax and qmin are max and min values for quantization range i.e. [0, 255] in case of uint8 * data range is adjusted to include 0. Zero point is calculated as: intermediate_zero_point = qmin - min(x)/y_scale y_zero_point = cast(round(saturate(itermediate_zero_point))) * where qmax and qmin are max and min values for quantization range .i.e [0, 255] in case of uint8 * for saturation, it saturates to [0, 255] if it's uint8, or [-127, 127] if it's int8. Right now only uint8 is supported. * rounding to nearest ties to even. Data quantization formula is: y = saturate (round (x / y_scale) + y_zero_point) * for saturation, it saturates to [0, 255] if it's uint8, or [-127, 127] if it's int8.Right now only uint8 is supported. * rounding to nearest ties to even. Version This version of the operator has been available since version 11 of the default ONNX operator set. Inputs x : T1 Input tensor Outputs y : T2 Quantized output tensor y_scale : tensor(float) Output scale. It's a scalar, which means a per-tensor/layer quantization. y_zero_point : T2 Output zero point. It's a scalar, which means a per-tensor/layer quantization. Type Constraints T1 : tensor(float) Constrain 'x' to float tensor. T2 : tensor(uint8) Constrain 'y_zero_point' and 'y' to 8-bit unsigned integer tensor. */ struct parse_dynamicquantizelinear : op_parser { std::vector operators() const { return {{"DynamicQuantizeLinear"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, const std::vector& args) const { auto x = args[0]; auto x_shape = x->get_shape(); auto x_type = x_shape.type(); if(x_shape.dynamic()) MIGRAPHX_THROW("DYNAMICQUANTIZELINEAR: dynamic shapes are not supported"); auto lit_0 = info.add_literal(migraphx::literal{migraphx::shape{x_type}, {0}}); // 1. Computing y_scale // Note: currently, DynamicQuantizeLinear only has uint8 quantization: const auto type_max = std::numeric_limits::max(); const auto type_min = std::numeric_limits::min(); std::vector axes(x_shape.lens().size()); std::iota(axes.begin(), axes.end(), 0); // maximum(0, max(x)) auto reduce_max_x = info.add_instruction(migraphx::make_op("reduce_max", {{"axes", axes}}), x); auto max_x = info.add_common_op("max", lit_0, reduce_max_x); // minimum(0, min(x)) auto reduce_min_x = info.add_instruction(migraphx::make_op("reduce_min", {{"axes", axes}}), x); auto min_x = info.add_common_op("min", lit_0, reduce_min_x); auto q_range = info.add_literal(migraphx::literal{ migraphx::shape{x_type, max_x->get_shape().lens()}, {type_max - type_min}}); auto q_min = info.add_literal( migraphx::literal{migraphx::shape{x_type, max_x->get_shape().lens()}, {type_min}}); auto q_max = info.add_literal( migraphx::literal{migraphx::shape{x_type, max_x->get_shape().lens()}, {type_max}}); // y_scale = (maximum(0, max(x)) - minimum(0, min(x))) / (qmax - qmin) auto sub0 = info.add_common_op("sub", max_x, min_x); auto y_scale = info.add_common_op("div", sub0, q_range); // 2. Computing y_zero_point // intermediate_zero_point = qmin - min(x) / y_scale auto div1 = info.add_common_op("div", min_x, y_scale); auto interm_zp = info.add_common_op("sub", q_min, div1); // y_zero_point = cast(round(saturate(itermediate_zero_point))) auto saturate = info.add_instruction(migraphx::make_op("clip"), interm_zp, q_min, q_max); auto round = info.add_instruction(migraphx::make_op("nearbyint"), saturate); auto y_zero_point = info.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::uint8_type}}), round); // 3. quantize x with y_scale and y_zero_point auto quant = bcast_qdq_instr("quantizelinear", x, y_scale, y_zero_point, info); return {quant, y_scale, y_zero_point}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_dynamicscale.cpp000066400000000000000000000143621510465702400226510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /** * Operator from Brevitas to calculate dynamic quantization scales. */ struct parse_dynamicscale : op_parser { std::vector operators() const { return {{"DynamicScale"}}; }; instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, onnx_parser::node_info info, const std::vector& args) const { const instruction_ref input = args.front(); instruction_ref tmp_in = input; const auto input_lens = input->get_shape().lens(); if(args.size() != 1) { MIGRAPHX_THROW("DynamicScale: must have only 1 input"); } int block_axis = info.attributes.at("group_dim").i(); block_axis = tune_axis(input->get_shape().ndim(), block_axis, "DynamicScale"); int block_size = info.attributes.at("group_size").i(); if(block_size != 32) { MIGRAPHX_THROW("DynamicScale: only group_size of 32 is supported"); } migraphx::shape::type_t output_type = get_type(info.attributes.at("output_dtype").i()); // TODO expand this to handle other MX types if(output_type != migraphx::shape::fp4x2_type) { MIGRAPHX_THROW("DynamicScale: only support MXFP4 type"); } std::string scale_selection_method = info.attributes.at("scale_selection_method").s(); if(scale_selection_method != "floor") { MIGRAPHX_THROW("DynamicScale: only support floor scale selection"); } std::string zero_point_selection_method = "None"; if(contains(info.attributes, "zero_point_selection_method")) zero_point_selection_method = info.attributes.at("zero_point_selection_method").s(); if(zero_point_selection_method != "None") { MIGRAPHX_THROW("DynamicScale: zero_point not supported"); } // make reduction axes for calculating block scales // tmp_lens != input_lens if runt block is padded auto tmp_lens = input_lens; auto block_dim = tmp_lens.at(block_axis); std::size_t block_padding = std::ceil(double(block_dim) / double(block_size)) * block_size - block_dim; // handle runt block by padding if(block_padding != 0) { std::vector pads_vec(2 * tmp_lens.size(), 0); pads_vec.at(block_axis + tmp_lens.size()) = block_padding; tmp_in = info.add_instruction(make_op("pad", {{"pads", pads_vec}}), tmp_in); tmp_lens = tmp_in->get_shape().lens(); } // reshape block dimension to {num_blocks, block_size} std::size_t num_blocks = tmp_lens.at(block_axis) / std::size_t(block_size); std::vector reduct_dims = tmp_lens; reduct_dims.at(block_axis) = block_size; reduct_dims.insert(reduct_dims.begin() + block_axis, num_blocks); instruction_ref reshape_ins = info.add_instruction(make_op("reshape", {{"dims", reduct_dims}}), tmp_in); // dynamic quantization for MX types: // V_k = fp32 vector input of block size k // B_k = pow(2, floor(log2(reduce_max(abs(V_k))))) # largest power of 2 less than V // X_k = block scale k = B_k / (largest power of 2 in fp4e2m1) = B_k / 4 auto abs_ins = info.add_instruction(make_op("abs"), reshape_ins); auto reduce_max_ins = info.add_instruction(make_op("reduce_max", {{"axes", {block_axis + 1}}}), abs_ins); auto log2_ins = info.add_instruction(make_op("log2"), reduce_max_ins); auto floor_ins = info.add_instruction(make_op("floor"), log2_ins); auto lit_2_ins = info.add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {2.f}}); auto broadcast_lit_2_ins = info.add_instruction( make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_2_ins); auto pow_ins = info.add_instruction(make_op("pow"), broadcast_lit_2_ins, floor_ins); auto lit_4_ins = info.add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {4.f}}); auto broadcast_lit_4_ins = info.add_instruction( make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_4_ins); auto block_scales_ins = info.add_instruction(make_op("div"), pow_ins, broadcast_lit_4_ins); // squeeze reduction axis for use in block quantized quantizelinear block_scales_ins = info.add_instruction(make_op("squeeze", {{"axes", {block_axis + 1}}}), block_scales_ins); return block_scales_ins; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_einsum.cpp000066400000000000000000000040521510465702400215100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_einsum : op_parser { std::vector operators() const { return {{"Einsum"}}; } instruction_ref parse(const op_desc&, const onnx_parser&, const onnx_parser::node_info& info, const std::vector& args) const { if(not contains(info.attributes, "equation")) MIGRAPHX_THROW("Equation attribute is required"); std::string equation = info.attributes.at("equation").s(); return op::builder::add("einsum", *info.mod, args, {{"equation", equation}}).at(0); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_expand.cpp000066400000000000000000000053751510465702400215000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_expand : op_parser { std::vector operators() const { return {{"Expand"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { migraphx::argument arg_s = args[1]->eval(); if(arg_s.empty()) { // variable dims input return info.add_instruction(make_op("broadcast_with_dims"), args[0], args[1]); } else { const shape& shape_0 = args[0]->get_shape(); if(shape_0.dynamic()) { MIGRAPHX_THROW( "PARSE_EXPAND: dynamic input tensor with fixed dims input not supported"); } const auto& in_lens = shape_0.lens(); std::vector dims; arg_s.visit([&](auto input) { dims.assign(input.begin(), input.end()); }); auto out_lens = compute_broadcasted_lens(in_lens, dims); return info.add_instruction(make_op("multibroadcast", {{"out_lens", out_lens}}), args[0]); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_eyelike.cpp000066400000000000000000000066571510465702400216540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_eyelike : op_parser { std::vector operators() const { return {{"EyeLike"}}; } instruction_ref parse(const op_desc&, const onnx_parser&, const onnx_parser::node_info& info, std::vector args) const { auto input_shape = args[0]->get_shape(); const auto& input_lens = input_shape.lens(); if(input_lens.size() != 2) { MIGRAPHX_THROW("EYELIKE: tensor input not of rank 2"); } std::ptrdiff_t num_rows = input_lens.front(); std::ptrdiff_t num_cols = input_lens.back(); shape::type_t output_type = args[0]->get_shape().type(); if(contains(info.attributes, "dtype")) { output_type = get_type(info.attributes.at("dtype").i()); } std::ptrdiff_t k = 0; if(contains(info.attributes, "k")) { k = info.attributes.at("k").i(); } if(k >= 0) { if(k >= num_cols) { std::ostringstream oss; oss << "EYELIKE: positive k out of bounds, k = " << k << " num_cols = " << num_cols; MIGRAPHX_THROW(oss.str()); } } else { if(std::abs(k) >= num_rows) { std::ostringstream oss; oss << "EYELIKE: negative k out of bounds, k = " << k << " num_rows = " << num_cols; MIGRAPHX_THROW(oss.str()); } } std::vector eyelike_mat(num_rows * num_cols, 0); for(std::ptrdiff_t i = 0; i < num_rows; ++i) { auto idx = i + k; if(idx < num_cols and idx >= 0) eyelike_mat[(num_cols + 1) * i + k] = char{1}; } return info.add_literal( migraphx::literal{migraphx::shape{output_type, input_lens}, eyelike_mat}); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_gather_elements.cpp000066400000000000000000000103461510465702400233610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_gather_elements : op_parser { std::vector operators() const { return {{"GatherElements"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { int axis = 0; if(contains(info.attributes, "axis")) { axis = parser.parse_value(info.attributes.at("axis")).at(); } // standardize input data and index auto arg_data = info.make_contiguous(args[0]); auto arg_ind = info.make_contiguous(args[1]); auto data_s = arg_data->get_shape(); auto ind_s = arg_ind->get_shape(); if(data_s.lens().size() != ind_s.lens().size()) { MIGRAPHX_THROW("PARSE_GATHER_ELEMENTS: input data and index must have the same rank!"); } int n_rank = data_s.lens().size(); int tuned_axis = tune_axis(n_rank, axis, opd.onnx_name); auto axis_stride = data_s.strides()[tuned_axis]; int64_t data_elem_num = data_s.elements(); // reshape the input data as one dimension and used as input data // to the gather operator arg_data = info.add_instruction(make_op("reshape", {{"dims", {data_elem_num}}}), arg_data); std::size_t elem_num = ind_s.elements(); std::vector ind_index(elem_num); std::iota(ind_index.begin(), ind_index.end(), 0); // convert index in input indices to that in input data std::vector data_indices(elem_num); std::transform(ind_index.begin(), ind_index.end(), data_indices.begin(), [&](auto i) { return data_s.index(ind_s.multi(i)); }); std::vector vec_axis_ind(elem_num); std::transform(ind_index.begin(), ind_index.end(), vec_axis_ind.begin(), [&](auto i) { return ind_s.multi(i)[tuned_axis]; }); auto l_shape_idx = info.add_literal(literal(ind_s, data_indices.begin(), data_indices.end())); auto l_dim_idx = info.add_literal(literal(ind_s, vec_axis_ind.begin(), vec_axis_ind.end())); auto l_stride = info.add_literal(literal{{ind_s.type(), {1}}, {axis_stride}}); l_stride = info.add_instruction(make_op("multibroadcast", {{"out_lens", ind_s.lens()}}), l_stride); auto dim_diff = info.add_instruction(make_op("sub"), arg_ind, l_dim_idx); auto delta = info.add_instruction(make_op("mul"), dim_diff, l_stride); auto ind = info.add_instruction(make_op("add"), l_shape_idx, delta); auto op = make_op("gather", {{"axis", 0}}); return info.add_instruction(op, arg_data, ind); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_gelu.cpp000066400000000000000000000073641510465702400211550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_gelu : op_parser { std::vector operators() const { return {{"BiasGelu"}, {"BiasSplitGelu"}, {"FastGelu"}, {"QuickGelu"}, {"Gelu"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { std::string approximate = "none"; auto x = args[0]; auto x_type = x->get_shape().type(); auto fast = false; if(not is_type_float(x_type)) { MIGRAPHX_THROW("PARSE_GELU: input tensor is not a floating type"); } if(contains(info.attributes, "approximate")) { approximate = info.attributes.at("approximate").s(); } if(opd.onnx_name == "QuickGelu") { auto alpha = info.attributes.at("alpha").f(); return op::builder::add("gelu_quick", *info.mod, {x}, {{"alpha", alpha}}).at(0); } if(opd.onnx_name == "FastGelu") { if(x_type == migraphx::shape::double_type) { MIGRAPHX_THROW("PARSE_GELU: FastGelu can't accept input with double precision"); } // FastGelu uses tanh approximation approximate = "tanh"; fast = true; } if(args.size() > 1 and args.at(1)->name() != "undefined") { auto y = args[1]; auto y_type = y->get_shape().type(); if(y_type != x_type) { MIGRAPHX_THROW("PARSE_GELU: mismatching input tensor types"); } x = info.add_common_op("add", x, y); } if(opd.onnx_name == "BiasSplitGelu") { // add should've been inserted from previous conditional statement assert(args.size() == 2); return op::builder::add("gelu_split", *info.mod, {x}, {}).at(0); } if(approximate == "tanh") { return op::builder::add("gelu_tanh", *info.mod, {x}, {{"fast", fast}}).at(0); } else { return op::builder::add("gelu_erf", *info.mod, {x}, {}).at(0); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_gemm.cpp000066400000000000000000000052741510465702400211440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_gemm : op_parser { std::vector operators() const { return {{"Gemm"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, const std::vector& args) const { value options = {}; if(contains(info.attributes, "alpha")) { const float alpha = parser.parse_value(info.attributes.at("alpha")).at(); options.insert({"alpha", alpha}); } if(contains(info.attributes, "beta")) { const float beta = parser.parse_value(info.attributes.at("beta")).at(); options.insert({"beta", beta}); } if(contains(info.attributes, "transA")) { const bool trans_a = parser.parse_value(info.attributes.at("transA")).at(); options.insert({"transA", trans_a}); } if(contains(info.attributes, "transB")) { const bool trans_b = parser.parse_value(info.attributes.at("transB")).at(); options.insert({"transB", trans_b}); } return op::builder::add("gemm", *info.mod, args, options).at(0); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_generic_op.cpp000066400000000000000000000067221510465702400223300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_generic_op : op_parser { std::vector operators() const { // clang-format off return {{"Abs", "abs"}, {"Acos", "acos"}, {"Acosh", "acosh"}, {"Asin", "asin"}, {"Asinh", "asinh"}, {"Atan", "atan"}, {"Atanh", "atanh"}, {"Ceil", "ceil"}, {"Concat", "concat"}, {"Cos", "cos"}, {"Cosh", "cosh"}, {"Elu", "elu"}, {"Erf", "erf"}, {"Exp", "exp"}, {"Flatten", "flatten"}, {"Floor", "floor"}, {"Gather", "gather"}, {"GatherND", "gathernd"}, {"Identity", "identity"}, {"IsNaN", "isnan"}, {"LeakyRelu", "leaky_relu"}, {"Log", "log"}, {"LRN", "lrn"}, {"Neg", "neg"}, {"Reciprocal", "recip"}, {"Relu", "relu"}, {"Round", "nearbyint"}, {"Sigmoid", "sigmoid"}, {"Sign", "sign"}, {"Sin", "sin"}, {"Sinh", "sinh"}, {"Sqrt", "sqrt"}, {"Tan", "tan"}, {"Tanh", "tanh"}, {"Not", "not"}}; // clang-format on } bool needs_contiguous(const std::string& op_name) const { return contains({"flatten", "gather", "scatter"}, op_name); } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { auto op = parser.load(opd.op_name, info); if(needs_contiguous(opd.op_name)) { std::transform(args.begin(), args.end(), args.begin(), [&](auto arg) { return info.make_contiguous(arg); }); } return info.add_instruction(op, args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_greaterorequal.cpp000066400000000000000000000043141510465702400232330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_greaterorequal : op_parser { std::vector operators() const { return {{"GreaterOrEqual"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { auto in_res = info.add_broadcastable_binary_op("less", args[0], args[1]); if(in_res->get_shape().type() != shape::bool_type) { in_res = info.add_instruction(make_op("convert", {{"target_type", shape::bool_type}}), in_res); } return info.add_instruction(make_op("not"), in_res); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_gridsample.cpp000066400000000000000000001033051510465702400223400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct grid_sampler { std::string m_padding; bool m_align_corners; instruction_ref m_input; instruction_ref m_grid; size_t m_batch{1}; size_t m_channel{1}; size_t m_in_height{1}; size_t m_in_width{1}; size_t m_out_height{1}; size_t m_out_width{1}; migraphx::shape m_nc_shape; instruction_ref m_one_l; instruction_ref m_two_l; instruction_ref m_zero_l; instruction_ref m_minus_half_l; instruction_ref m_width_l; instruction_ref m_width_max_l; instruction_ref m_height_l; instruction_ref m_height_max_l; instruction_ref m_unnorm_x; instruction_ref m_unnorm_y; grid_sampler(const instruction_ref& input, const instruction_ref& grid, bool align, std::string&& padding, const onnx_parser::node_info& info) : m_padding(std::move(padding)), m_align_corners(align), m_input(input), m_grid(grid) { auto i_lens = input->get_shape().lens(); m_batch = i_lens.at(0); m_channel = i_lens.at(1); m_in_height = i_lens.at(2); m_in_width = i_lens.at(3); auto g_lens = grid->get_shape().lens(); m_out_height = g_lens.at(1); m_out_width = g_lens.at(2); auto type = m_grid->get_shape().type(); m_nc_shape = migraphx::shape{type, {1, 2}}; m_zero_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {0.0f}}); m_one_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {1.0f}}); m_two_l = info.add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type}, {2}}); m_minus_half_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {-0.5f}}); m_width_max_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {m_in_width - 1}}); m_width_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {m_in_width}}); m_height_max_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {m_in_height - 1}}); m_height_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {m_in_height}}); auto x_coords = info.add_instruction( make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {1}}}), m_grid); auto y_coords = info.add_instruction( make_op("slice", {{"axes", {3}}, {"starts", {1}}, {"ends", {2}}}), m_grid); x_coords = info.add_instruction(make_op("squeeze", {{"axes", {3}}}), x_coords); y_coords = info.add_instruction(make_op("squeeze", {{"axes", {3}}}), y_coords); m_unnorm_x = unnormalize(info, x_coords, m_in_width); m_unnorm_y = unnormalize(info, y_coords, m_in_height); if(m_padding == "reflection") { auto corner_start = m_align_corners ? m_zero_l : m_minus_half_l; m_unnorm_x = reflect_coordinates( info, m_unnorm_x, m_align_corners ? m_width_max_l : m_width_l, corner_start); m_unnorm_y = reflect_coordinates( info, m_unnorm_y, m_align_corners ? m_height_max_l : m_height_l, corner_start); m_unnorm_x = info.add_common_op("clip", m_unnorm_x, m_zero_l, m_width_max_l); m_unnorm_y = info.add_common_op("clip", m_unnorm_y, m_zero_l, m_height_max_l); } if(m_padding == "border") { m_unnorm_x = info.add_common_op("clip", m_unnorm_x, m_zero_l, m_width_max_l); m_unnorm_y = info.add_common_op("clip", m_unnorm_y, m_zero_l, m_height_max_l); } } instruction_ref reflect_coordinates(const onnx_parser::node_info& info, instruction_ref coords, instruction_ref size, instruction_ref corner_start) const { auto index_align_corner = info.add_common_op("sub", corner_start, coords); index_align_corner = info.add_common_op("abs", index_align_corner); auto size_times = info.add_common_op("floor", index_align_corner); size_times = info.add_common_op("div", size_times, size); size_times = info.add_common_op("floor", size_times); auto cond = info.add_common_op("mod", size_times, m_two_l); cond = info.add_common_op("equal", cond, m_zero_l); auto extra = info.add_common_op("mul", size_times, size); extra = info.add_common_op("sub", index_align_corner, extra); auto cond_true = info.add_common_op("add", extra, corner_start); auto cond_false = info.add_common_op("sub", size, extra); cond_false = info.add_common_op("add", cond_false, corner_start); return info.add_common_op("where", cond, cond_true, cond_false); } instruction_ref unnormalize(const onnx_parser::node_info& info, const instruction_ref& coords_t, float size) const { auto unnorm = info.add_common_op("add", coords_t, m_one_l); if(m_align_corners) { // unnorm_x = (x + 1) * (size - 1) / 2 auto mul_const = info.add_literal( migraphx::literal{migraphx::shape{coords_t->get_shape().type()}, {(size - 1) / 2}}); unnorm = info.add_common_op("mul", unnorm, mul_const); } else { // unnorm_x = -0.5 + (x + 1) * size / 2 auto mul_const = info.add_literal( migraphx::literal{migraphx::shape{coords_t->get_shape().type()}, {size / 2}}); unnorm = info.add_common_op("mul", unnorm, mul_const); unnorm = info.add_common_op("add", unnorm, m_minus_half_l); } return unnorm; } static instruction_ref concat_on_first_dim(const onnx_parser::node_info& info, std::vector instructions) { return std::accumulate(std::next(instructions.begin()), instructions.end(), instructions.front(), [&info](auto& ret, auto& ins) { return info.add_instruction( make_op("concat", {{"axis", 0}}), ret, ins); }); } static instruction_ref concat_on_dim(const onnx_parser::node_info& info, std::array instructions, int64_t dim) { return std::accumulate(std::next(instructions.begin()), instructions.end(), instructions.front(), [&info, &dim](auto& ret, auto& ins) { return info.add_instruction( make_op("concat", {{"axis", dim}}), ret, ins); }); } bool has_border_padding() const { return m_padding == "border"; } }; struct nearest_sampler : grid_sampler { instruction_ref m_round_x; instruction_ref m_round_y; nearest_sampler(const instruction_ref& input, const instruction_ref& grid, bool align, std::string&& padding, const onnx_parser::node_info& info) : grid_sampler(input, grid, align, std::move(padding), info), m_round_x(info.add_common_op("nearbyint", m_unnorm_x)), m_round_y(info.add_common_op("nearbyint", m_unnorm_y)) { } instruction_ref sample(const onnx_parser::node_info& info) { std::vector hw_indices; std::vector nc_values; const static auto nhw_shape = migraphx::shape{migraphx::shape::int64_type, {1, 3}}; bool validate = not has_border_padding(); dfor(m_batch, m_out_height, m_out_width)([&](auto n, auto h, auto w) { auto nhw = info.add_literal(migraphx::literal{nhw_shape, {n, h, w}}); for(size_t c = 0; c < m_channel; c++) { hw_indices.push_back(nhw); nc_values.push_back(info.add_literal(migraphx::literal{m_nc_shape, {n, c}})); } }); auto hw_indices_t = concat_on_first_dim(info, hw_indices); auto h_samples = info.add_instruction(make_op("gathernd"), m_round_y, hw_indices_t); auto w_samples = info.add_instruction(make_op("gathernd"), m_round_x, hw_indices_t); instruction_ref validation; if(validate) { auto h_clip = info.add_common_op("clip", h_samples, m_zero_l, m_height_max_l); auto w_clip = info.add_common_op("clip", w_samples, m_zero_l, m_width_max_l); auto h_valid = info.add_common_op("equal", h_samples, h_clip); auto w_valid = info.add_common_op("equal", w_samples, w_clip); validation = info.add_common_op("logical_and", h_valid, w_valid); h_samples = h_clip; w_samples = w_clip; } auto nc = concat_on_first_dim(info, nc_values); h_samples = info.add_instruction( make_op("reshape", {{"dims", {h_samples->get_shape().elements(), 1}}}), h_samples); w_samples = info.add_instruction( make_op("reshape", {{"dims", {w_samples->get_shape().elements(), 1}}}), w_samples); auto indices_t = info.add_instruction(make_op("concat", {{"axis", 1}}), h_samples, w_samples); indices_t = info.add_instruction(make_op("concat", {{"axis", 1}}), nc, indices_t); auto samples = info.add_instruction(make_op("gathernd"), m_input, indices_t); if(validate) { samples = info.add_common_op("where", validation, samples, m_zero_l); } samples = info.add_instruction( make_op("reshape", {{"dims", {m_batch, m_out_height, m_out_width, m_channel}}}), samples); samples = info.add_instruction(make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), samples); samples = info.add_instruction( make_op("convert", {{"target_type", m_input->get_shape().type()}}), samples); return samples; } }; struct linear_sampler : grid_sampler { instruction_ref m_floor_x; instruction_ref m_floor_y; instruction_ref m_ceil_x; instruction_ref m_ceil_y; std::array m_corner_weights; linear_sampler(const instruction_ref& input, const instruction_ref& grid, bool align, std::string&& padding, const onnx_parser::node_info& info) : grid_sampler(input, grid, align, std::move(padding), info), m_floor_x(info.add_common_op("floor", m_unnorm_x)), m_floor_y(info.add_common_op("floor", m_unnorm_y)), m_ceil_x(info.add_common_op("add", m_floor_x, m_one_l)), m_ceil_y(info.add_common_op("add", m_floor_y, m_one_l)) { auto fract_x = info.add_common_op("sub", m_unnorm_x, m_floor_x); auto fract_y = info.add_common_op("sub", m_unnorm_y, m_floor_y); auto one_minus_fract_x = info.add_common_op("sub", m_one_l, fract_x); auto one_minus_fract_y = info.add_common_op("sub", m_one_l, fract_y); m_corner_weights[0] = info.add_common_op("mul", one_minus_fract_y, one_minus_fract_x); m_corner_weights[1] = info.add_common_op("mul", one_minus_fract_y, fract_x); m_corner_weights[2] = info.add_common_op("mul", fract_y, one_minus_fract_x); m_corner_weights[3] = info.add_common_op("mul", fract_y, fract_x); } instruction_ref sample(const onnx_parser::node_info& info) { std::vector xy_indices_data; std::vector weight_indices_data; std::vector nc_values_data; dfor(m_batch, m_out_height, m_out_width, m_channel)([&](auto n, auto h, auto w, auto c) { xy_indices_data.push_back(n); xy_indices_data.push_back(h); xy_indices_data.push_back(w); weight_indices_data.push_back(n); weight_indices_data.push_back(h); weight_indices_data.push_back(w); nc_values_data.push_back(n); nc_values_data.push_back(c); }); size_t num_indices = m_batch * m_out_height * m_out_width * m_channel; auto xy_indices_t = info.add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {num_indices, 3}}, xy_indices_data}); auto weight_index_t = info.add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {num_indices, 3}}, weight_indices_data}); auto nc = info.add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {num_indices, 2}}, nc_values_data}); auto y0_samples = info.add_instruction(make_op("gathernd"), m_floor_y, xy_indices_t); auto x0_samples = info.add_instruction(make_op("gathernd"), m_floor_x, xy_indices_t); auto y1_samples = info.add_instruction(make_op("gathernd"), m_ceil_y, xy_indices_t); auto x1_samples = info.add_instruction(make_op("gathernd"), m_ceil_x, xy_indices_t); auto validate_samples = [&](auto& samples, auto& max) { auto clip = info.add_common_op("clip", samples, m_zero_l, max); auto validation = info.add_common_op("equal", samples, clip); samples = clip; return validation; }; auto y0_validation = validate_samples(y0_samples, m_height_max_l); auto x0_validation = validate_samples(x0_samples, m_width_max_l); auto y1_validation = validate_samples(y1_samples, m_height_max_l); auto x1_validation = validate_samples(x1_samples, m_width_max_l); y0_samples = info.add_instruction( make_op("reshape", {{"dims", {y0_samples->get_shape().elements(), 1}}}), y0_samples); x0_samples = info.add_instruction( make_op("reshape", {{"dims", {x0_samples->get_shape().elements(), 1}}}), x0_samples); y1_samples = info.add_instruction( make_op("reshape", {{"dims", {y1_samples->get_shape().elements(), 1}}}), y1_samples); x1_samples = info.add_instruction( make_op("reshape", {{"dims", {x1_samples->get_shape().elements(), 1}}}), x1_samples); auto make_corner_indices = [&](auto& x, auto& y) { auto hw = info.add_instruction(make_op("concat", {{"axis", 1}}), y, x); return info.add_instruction(make_op("concat", {{"axis", 1}}), nc, hw); }; std::array corner_indices{make_corner_indices(x0_samples, y0_samples), make_corner_indices(x1_samples, y0_samples), make_corner_indices(x0_samples, y1_samples), make_corner_indices(x1_samples, y1_samples)}; std::array corner_validations{ info.add_common_op("logical_and", x0_validation, y0_validation), info.add_common_op("logical_and", x1_validation, y0_validation), info.add_common_op("logical_and", x0_validation, y1_validation), info.add_common_op("logical_and", x1_validation, y1_validation)}; std::array corner_samples; std::transform(corner_indices.begin(), corner_indices.end(), corner_validations.begin(), corner_samples.begin(), [&](const auto& indices, const auto& validations) { auto samples = info.add_instruction(make_op("gathernd"), m_input, indices); return info.add_common_op("where", validations, samples, m_zero_l); }); std::transform(corner_samples.begin(), corner_samples.end(), m_corner_weights.begin(), corner_samples.begin(), [&](const auto& samples, const auto& weights) { auto weights_t = info.add_instruction(make_op("gathernd"), weights, weight_index_t); return info.add_instruction(make_op("mul"), samples, weights_t); }); auto samples = std::accumulate( std::next(corner_samples.begin()), corner_samples.end(), corner_samples.front(), [&](auto acc, auto s) { return info.add_instruction(make_op("add"), acc, s); }); samples = info.add_instruction( make_op("reshape", {{"dims", {m_batch, m_out_height, m_out_width, m_channel}}}), samples); samples = info.add_instruction(make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), samples); samples = info.add_instruction( make_op("convert", {{"target_type", m_input->get_shape().type()}}), samples); return samples; } }; struct bicubic_sampler : grid_sampler { instruction_ref m_a_l; instruction_ref m_aplus2_l; instruction_ref m_aplus3_l; instruction_ref m_4a_l; instruction_ref m_5a_l; instruction_ref m_8a_l; std::array m_x_weights; std::array m_y_weights; std::array m_x_corners; std::array m_y_corners; bicubic_sampler(const instruction_ref& input, const instruction_ref& grid, bool align, std::string&& padding, const onnx_parser::node_info& info) : grid_sampler(input, grid, align, std::move(padding), info) { auto type = m_grid->get_shape().type(); m_a_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {-0.75}}); m_aplus2_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {1.25}}); m_aplus3_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {2.25}}); m_4a_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {-3.0}}); m_5a_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {-3.75}}); m_8a_l = info.add_literal(migraphx::literal{migraphx::shape{type}, {-6.0}}); auto floor_x = info.add_common_op("floor", m_unnorm_x); auto floor_y = info.add_common_op("floor", m_unnorm_y); auto fract_x = info.add_common_op("sub", m_unnorm_x, floor_x); auto fract_y = info.add_common_op("sub", m_unnorm_y, floor_y); m_x_weights[0] = cubic_weight_2(info, info.add_common_op("add", fract_x, m_one_l)); m_x_weights[1] = cubic_weight_1(info, fract_x); m_x_weights[2] = cubic_weight_1(info, info.add_common_op("sub", m_one_l, fract_x)); m_x_weights[3] = cubic_weight_2(info, info.add_common_op("sub", m_two_l, fract_x)); m_y_weights[0] = cubic_weight_2(info, info.add_common_op("add", fract_y, m_one_l)); m_y_weights[1] = cubic_weight_1(info, fract_y); m_y_weights[2] = cubic_weight_1(info, info.add_common_op("sub", m_one_l, fract_y)); m_y_weights[3] = cubic_weight_2(info, info.add_common_op("sub", m_two_l, fract_y)); m_x_corners[0] = info.add_common_op("sub", floor_x, m_one_l); m_x_corners[1] = floor_x; m_x_corners[2] = info.add_common_op("add", floor_x, m_one_l); m_x_corners[3] = info.add_common_op("add", floor_x, m_two_l); m_y_corners[0] = info.add_common_op("sub", floor_y, m_one_l); m_y_corners[1] = floor_y; m_y_corners[2] = info.add_common_op("add", floor_y, m_one_l); m_y_corners[3] = info.add_common_op("add", floor_y, m_two_l); if(m_padding == "reflection") { auto corner_start = m_align_corners ? m_zero_l : m_minus_half_l; std::transform( m_x_corners.begin(), m_x_corners.end(), m_x_corners.begin(), [&](const auto& corner) { auto tmp = reflect_coordinates( info, corner, m_align_corners ? m_width_max_l : m_width_l, corner_start); return info.add_common_op("clip", tmp, m_zero_l, m_width_max_l); }); std::transform( m_y_corners.begin(), m_y_corners.end(), m_y_corners.begin(), [&](const auto& corner) { auto tmp = reflect_coordinates( info, corner, m_align_corners ? m_height_max_l : m_height_l, corner_start); return info.add_common_op("clip", tmp, m_zero_l, m_height_max_l); }); } if(m_padding == "border") { std::transform( m_x_corners.begin(), m_x_corners.end(), m_x_corners.begin(), [&](auto& corner) { return info.add_common_op("clip", corner, m_zero_l, m_width_max_l); }); std::transform( m_y_corners.begin(), m_y_corners.end(), m_y_corners.begin(), [&](auto& corner) { return info.add_common_op("clip", corner, m_zero_l, m_height_max_l); }); } } instruction_ref cubic_weight_1(const onnx_parser::node_info& info, const instruction_ref& ins) const { //((A + 2) * fraction - (A + 3)) * fraction * fraction + 1 auto mul_1 = info.add_common_op("mul", m_aplus2_l, ins); auto sub = info.add_common_op("sub", mul_1, m_aplus3_l); auto mul_2 = info.add_common_op("mul", sub, ins); auto mul_3 = info.add_common_op("mul", mul_2, ins); return info.add_common_op("add", mul_3, m_one_l); } instruction_ref cubic_weight_2(const onnx_parser::node_info& info, const instruction_ref& ins) const { // ((A * fraction - 5 * A) * fraction + 8 * A) * fraction - (4 * A) auto mul_1 = info.add_common_op("mul", m_a_l, ins); auto sub_1 = info.add_common_op("sub", mul_1, m_5a_l); auto mul_2 = info.add_common_op("mul", sub_1, ins); auto add = info.add_common_op("add", mul_2, m_8a_l); auto mul_3 = info.add_common_op("mul", add, ins); return info.add_common_op("sub", mul_3, m_4a_l); } static instruction_ref compute_weights(const onnx_parser::node_info& info, const std::vector& weight_indices, const std::array& weights, const std::vector& out_lens, size_t gather_dim) { auto weight_indices_t = concat_on_first_dim(info, weight_indices); weight_indices_t = info.add_instruction( make_op( "reshape", {{"dims", {weight_indices_t->get_shape().elements() / gather_dim, gather_dim}}}), weight_indices_t); std::array corner_weights; std::transform(weights.cbegin(), weights.cend(), corner_weights.begin(), [&](auto& corner) { auto corner_weight = info.add_instruction(make_op("gathernd"), corner, weight_indices_t); return info.add_instruction( make_op("reshape", {{"dims", {corner_weight->get_shape().elements(), 1}}}), corner_weight); }); auto weights_t = std::accumulate(std::next(corner_weights.begin()), corner_weights.end(), corner_weights.front(), [&info](auto& acc, auto& ins) { return info.add_instruction( make_op("concat", {{"axis", 1}}), acc, ins); }); return info.add_instruction(make_op("reshape", {{"dims", out_lens}}), weights_t); } instruction_ref sample(const onnx_parser::node_info& info) { std::vector x_weight_indices; std::vector y_weight_indices; std::vector inner_x_indices; std::vector nc_values; std::vector inner_indices; const static auto nhw_shape = migraphx::shape{migraphx::shape::int64_type, {3}}; dfor(m_batch, m_out_height, m_out_width)([&](auto n, auto h, auto w) { auto nhw = info.add_literal(migraphx::literal{nhw_shape, {n, h, w}}); x_weight_indices.insert(x_weight_indices.end(), {nhw, nhw, nhw, nhw}); y_weight_indices.push_back(nhw); dfor(m_channel, m_y_corners.size())([&](auto c, auto) { inner_indices.push_back(nhw); auto nc = info.add_literal(migraphx::literal{m_nc_shape, {n, c}}); nc_values.insert(nc_values.end(), {nc, nc, nc, nc}); }); }); auto inner_indices_t = concat_on_first_dim(info, inner_indices); inner_indices_t = info.add_instruction( make_op("reshape", {{"dims", {inner_indices_t->get_shape().elements() / nhw_shape.elements(), nhw_shape.elements()}}}), inner_indices_t); std::array inner_y_samples; std::transform( m_y_corners.begin(), m_y_corners.end(), inner_y_samples.begin(), [&](auto corner) { auto sample = info.add_instruction(make_op("gathernd"), corner, inner_indices_t); return info.add_instruction( make_op("reshape", {{"dims", {sample->get_shape().elements(), 1}}}), sample); }); auto inner_y_t = concat_on_dim(info, inner_y_samples, 1); auto elements = inner_y_t->get_shape().elements(); inner_y_t = info.add_instruction(make_op("reshape", {{"dims", {elements / 16, 4, 4}}}), inner_y_t); inner_y_t = info.add_instruction(make_op("transpose", {{"permutation", {0, 2, 1}}}), inner_y_t); inner_y_t = info.add_instruction(make_op("reshape", {{"dims", {elements}}}), inner_y_t); std::array inner_x_samples; std::transform( m_x_corners.begin(), m_x_corners.end(), inner_x_samples.begin(), [&](auto corner) { auto sample = info.add_instruction(make_op("gathernd"), corner, inner_indices_t); return info.add_instruction( make_op("reshape", {{"dims", {sample->get_shape().elements(), 1}}}), sample); }); auto inner_x_t = concat_on_dim(info, inner_x_samples, 1); inner_x_t = info.add_instruction( make_op("reshape", {{"dims", {inner_x_t->get_shape().elements()}}}), inner_x_t); auto validate_index = [&](auto& index, auto& max) { auto clip = info.add_common_op("clip", index, m_zero_l, max); auto validation = info.add_common_op("equal", index, clip); index = clip; return validation; }; auto y_validation = validate_index(inner_y_t, m_height_max_l); auto x_validation = validate_index(inner_x_t, m_width_max_l); inner_y_t = info.add_instruction( make_op("reshape", {{"dims", {inner_y_t->get_shape().elements(), 1}}}), inner_y_t); inner_x_t = info.add_instruction( make_op("reshape", {{"dims", {inner_x_t->get_shape().elements(), 1}}}), inner_x_t); auto nc_t = concat_on_first_dim(info, nc_values); auto indices_t = info.add_instruction(make_op("concat", {{"axis", 1}}), inner_y_t, inner_x_t); indices_t = info.add_instruction(make_op("concat", {{"axis", 1}}), nc_t, indices_t); auto samples = info.add_instruction(make_op("gathernd"), m_input, indices_t); auto validation_t = info.add_common_op("logical_and", y_validation, x_validation); samples = info.add_common_op("where", validation_t, samples, m_zero_l); auto x_weights_t = compute_weights( info, x_weight_indices, m_x_weights, samples->get_shape().lens(), nhw_shape.elements()); auto weighted_samples = info.add_common_op("mul", samples, x_weights_t); weighted_samples = info.add_instruction( make_op("reshape", {{"dims", {weighted_samples->get_shape().elements() / 4, 4}}}), weighted_samples); auto coefficients = info.add_instruction(make_op("reduce_sum", {{"axes", {1}}}), weighted_samples); coefficients = info.add_instruction(make_op("squeeze", {{"axes", {1}}}), coefficients); auto y_weights_t = compute_weights(info, y_weight_indices, m_y_weights, coefficients->get_shape().lens(), nhw_shape.elements()); auto weighted_coefficients = info.add_common_op("mul", coefficients, y_weights_t); weighted_coefficients = info.add_instruction( make_op("reshape", {{"dims", {weighted_coefficients->get_shape().elements() / 4, 4}}}), weighted_coefficients); auto res = info.add_instruction(make_op("reduce_sum", {{"axes", {1}}}), weighted_coefficients); auto expected_shape = migraphx::shape{migraphx::shape::int64_type, {m_batch, m_out_height, m_out_width, m_channel}}; res = info.add_instruction( make_op("reshape", {{"dims", {m_batch, m_out_height, m_out_width, m_channel}}}), res); res = info.add_instruction(make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), res); res = info.add_instruction( make_op("convert", {{"target_type", m_input->get_shape().type()}}), res); return res; } }; struct parse_gridsample : op_parser { std::vector operators() const { return {{"GridSample"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { bool align_corners = false; // Note: default mode can be linear or bilinear depending on the onnx version std::string mode = "linear"; std::string padding_mode = "zeros"; if(contains(info.attributes, "align_corners")) { align_corners = parser.parse_value(info.attributes.at("align_corners")).at(); } if(contains(info.attributes, "mode")) { mode = info.attributes.at("mode").s(); } if(contains(info.attributes, "padding_mode")) { padding_mode = info.attributes.at("padding_mode").s(); } const auto& grid = args.at(1); const auto& grid_shape = grid->get_shape(); if(not is_type_float(grid_shape.type())) { MIGRAPHX_THROW("PARSE_GRID_SAMPLE: grid input must have floating type"); } const auto& x = args.at(0); const auto& x_dims = x->get_shape().lens().size(); if(grid_shape.lens().size() != x_dims) { MIGRAPHX_THROW( "PARSE_GRID_SAMPLE: x and grid inputs must have same number of dimensions"); } if(x_dims != 4) { MIGRAPHX_THROW("PARSE_GRID_SAMPLE: only 4-D inputs are supported"); } return contains(mode, "nearest") ? nearest_sampler(x, grid, align_corners, std::move(padding_mode), info) .sample(info) : (contains(mode, "linear") ? linear_sampler(x, grid, align_corners, std::move(padding_mode), info) .sample(info) : bicubic_sampler(x, grid, align_corners, std::move(padding_mode), info) .sample(info)); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_group_query_attention.cpp000066400000000000000000000116501510465702400246600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_group_query_attention : op_parser { std::vector operators() const { return {{"GroupQueryAttention"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { bool do_rotary = false; std::size_t kv_num_heads = 0; int local_window_size = -1; std::size_t num_heads = 1; bool rotary_interleaved = false; float scale = 0.0; if(contains(info.attributes, "do_rotary")) { do_rotary = parser.parse_value(info.attributes.at("do_rotary")).at(); } if(contains(info.attributes, "kv_num_heads")) { kv_num_heads = parser.parse_value(info.attributes.at("kv_num_heads")).at(); } if(contains(info.attributes, "local_window_size")) { local_window_size = parser.parse_value(info.attributes.at("local_window_size")).at(); } if(contains(info.attributes, "num_heads")) { num_heads = parser.parse_value(info.attributes.at("num_heads")).at(); } if(contains(info.attributes, "rotary_interleaved")) { rotary_interleaved = parser.parse_value(info.attributes.at("rotary_interleaved")).at(); } if(contains(info.attributes, "scale")) { scale = parser.parse_value(info.attributes.at("scale")).at(); } if(contains(info.attributes, "softcap")) { if(not float_equal(parser.parse_value(info.attributes.at("softcap")).at(), 0.0)) { MIGRAPHX_THROW("GroupQueryAttention: non-zero softcap is not yet supported."); } } if(args.size() < 7 or args.size() > 9) { MIGRAPHX_THROW("GroupQueryAttention: Wrong number of inputs provided"); } auto new_args = args; if(args.at(1)->get_shape().lens().size() > 1) { new_args[0] = info.add_instruction( make_op("concat", {{"axis", 2}}), args.at(0), args.at(1), args.at(2)); } auto gqa = info.add_instruction(make_op("group_query_attention", {{"do_rotary", do_rotary}, {"kv_num_heads", kv_num_heads}, {"local_window_size", local_window_size}, {"num_heads", num_heads}, {"rotary_interleaved", rotary_interleaved}, {"scale", scale}}), new_args); auto gqa_output = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), gqa); auto gqa_present_key = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), gqa); auto gqa_present_value = info.add_instruction(make_op("get_tuple_elem", {{"index", 2}}), gqa); return {gqa_output, gqa_present_key, gqa_present_value}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_groupnorm.cpp000066400000000000000000000203531510465702400222420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static instruction_ref apply_channels_last_perm(const onnx_parser::node_info& info, instruction_ref ins, bool invert) { std::vector perm(ins->get_shape().ndim()); std::iota(perm.begin() + 1, perm.end() - 1, 2); perm.back() = 1; return info.add_instruction( make_op("transpose", {{"permutation", invert ? invert_permutation(perm) : perm}}), ins); } static instruction_ref convert_tensor_type(const onnx_parser::node_info& info, instruction_ref tensor, shape::type_t target_type) { return tensor->get_shape().type() != target_type ? info.add_instruction(migraphx::make_op("convert", {{"target_type", target_type}}), tensor) : tensor; } static void validate_tensor_shape(const instruction_ref& tensor, size_t channels, const std::string& name) { if(tensor->get_shape().ndim() != 1 or tensor->get_shape().lens().at(0) != channels) { MIGRAPHX_THROW("PARSE_GROUPNORM: " + name + " tensor shape should be equal to the number of channels"); } } struct parse_groupnorm : op_parser { std::vector operators() const { return {{"GroupNormalization", "GroupNorm"}, {"GroupNorm", "Contrib_GroupNorm"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { bool is_contrib = (opd.op_name == ("Contrib_GroupNorm")); float epsilon = 1e-5f; if(contains(info.attributes, "epsilon")) { epsilon = parser.parse_value(info.attributes.at("epsilon")).at(); } size_t num_groups; if(contains(info.attributes, "num_groups") or contains(info.attributes, "groups")) { if(is_contrib) { num_groups = std::abs(parser.parse_value(info.attributes.at("groups")).at()); } else { num_groups = std::abs(parser.parse_value(info.attributes.at("num_groups")).at()); } } else { MIGRAPHX_THROW("PARSE_GROUPNORM: num_groups must be available"); } bool is_channels_last = false; if(is_contrib) { // default state for GroupNorm Contrib op is_channels_last = true; if(contains(info.attributes, "channels_last")) { is_channels_last = (1 == parser.parse_value(info.attributes.at("channels_last")).at()); } } bool silu_activation = false; if(contains(info.attributes, "activation") and is_contrib) { silu_activation = (1 == parser.parse_value(info.attributes.at("activation")).at()); } else if(is_contrib) { MIGRAPHX_THROW("PARSE_GROUPNORM: activation must be available"); } if(args.size() != 3) { MIGRAPHX_THROW("PARSE_GROUPNORM: invalid input count"); } // Adjust chanels from channels_last-> NCHW if last channel is set for contrib op auto x = args.at(0); if(is_channels_last and is_contrib) { x = apply_channels_last_perm(info, x, true); } auto x_shape = x->get_shape(); auto x_dtype = x_shape.type(); auto x_dims = x_shape.lens(); if(x_shape.ndim() <= 2) { MIGRAPHX_THROW("PARSE_GROUPNORM: invalid input shape"); } auto c = x_shape.lens().at(1); if(c % num_groups != 0) { MIGRAPHX_THROW( "PARSE_GROUPNORM: num_groups should be a divisor of the number of channels"); } // Pre-process scale and bias auto scale = convert_tensor_type( info, args.at(1), x_shape.type()); // gamma in the GroupNorm contrib case auto bias = convert_tensor_type( info, args.at(2), x_shape.type()); // beta in the GroupNorm contrib case validate_tensor_shape(scale, c, "scale"); validate_tensor_shape(bias, c, "bias"); // Original shape: N x C x D1 x ... x Dn // New shape: N x num_groups x C // num_groups x D1 x ... x Dn std::vector dims = {x_dims.at(0), num_groups, c / num_groups}; std::copy(x_dims.begin() + 2, x_dims.end(), std::back_inserter(dims)); auto x_reshaped = info.add_instruction(make_op("reshape", {{"dims", dims}}), x); // Axes for D1 x ... x Dn std::vector axes(dims.size() - 2); std::iota(axes.begin(), axes.end(), 2); // y = (x - mean) * rsqrt(variance + epsilon) * scale + bias // mean = reduce_mean({D1, D2, ... Dk}, x) // variance = reduce_mean({D1, D2, ... Dk}, (x - mean)^2) auto mean = info.add_instruction(make_op("reduce_mean", {{"axes", axes}}), x_reshaped); auto x_sub_mean = info.add_common_op("sub", x_reshaped, mean); auto x_sqdiff_mean = info.add_common_op("sqdiff", x_reshaped, mean); auto variance = info.add_instruction(make_op("reduce_mean", {{"axes", axes}}), x_sqdiff_mean); epsilon = (x_dtype == migraphx::shape::half_type and std::abs(epsilon) < 1e-7) ? 1e-7 : epsilon; auto eps = info.add_literal(migraphx::literal{migraphx::shape{x_dtype}, {epsilon}}); auto var_eps = info.add_common_op("add", variance, eps); auto rsqrt = info.add_instruction(make_op("rsqrt"), var_eps); auto result = info.add_common_op("mul", x_sub_mean, rsqrt); auto result_reshaped = info.add_instruction(make_op("reshape", {{"dims", x_dims}}), result); auto scale_bcast = info.add_instruction(make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), scale); auto bias_bcast = info.add_instruction(make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), bias); auto scaled = info.add_instruction(make_op("mul"), result_reshaped, scale_bcast); auto output = info.add_instruction(make_op("add"), scaled, bias_bcast); // Convert to NCHW -> channels_last for contrib GroupNorm if(is_channels_last and is_contrib) { output = apply_channels_last_perm(info, output, false); } if(silu_activation) { // SiLU activation is just out = x * sigmoid(x) auto sigmoid = info.add_instruction(make_op("sigmoid"), output); output = info.add_instruction(make_op("mul"), output, sigmoid); } return output; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_gru.cpp000066400000000000000000000162351510465702400210130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static void gru_transpose_inputs(onnx_parser::node_info& info, std::vector& args) { std::vector perm{1, 0, 2}; args[0] = info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[0]); if(not args[5]->is_undefined()) { args[5] = info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[5]); } } static void gru_transpose_outputs(onnx_parser::node_info& info, instruction_ref& hidden_states, instruction_ref& last_output) { std::vector perm_hs{2, 0, 1, 3}; hidden_states = info.add_instruction(make_op("transpose", {{"permutation", perm_hs}}), hidden_states); std::vector perm_last{1, 0, 2}; last_output = info.add_instruction(make_op("transpose", {{"permutation", perm_last}}), last_output); } struct parse_gru : op_parser { std::vector operators() const { return {{"GRU"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { migraphx::shape input_shape = args[0]->get_shape(); std::size_t hidden_size = args[2]->get_shape().lens()[2]; if(contains(info.attributes, "hidden_size")) { std::size_t hidden_size_att = parser.parse_value(info.attributes.at("hidden_size")).at(); if(hidden_size != hidden_size_att) { MIGRAPHX_THROW("GRU: hidden size mismatch in input and attribute"); } } // Handling of direction to be added later std::string direction{"forward"}; if(contains(info.attributes, "direction")) { direction = info.attributes.at("direction").s(); } op::rnn_direction dirct = op::rnn_direction::forward; if(direction == "bidirectional") { dirct = op::rnn_direction::bidirectional; } else if(direction == "reverse") { dirct = op::rnn_direction::reverse; } // set default activation functions std::vector vec_names = {"sigmoid", "tanh"}; if(dirct == op::rnn_direction::bidirectional) { // repeat the activation functions vec_names.push_back(vec_names.at(0)); vec_names.push_back(vec_names.at(1)); } if(contains(info.attributes, "activations")) { auto names = info.attributes.at("activations").strings(); vec_names.clear(); vec_names.resize(names.size()); std::transform(names.begin(), names.end(), vec_names.begin(), [](auto name) { return to_lower(std::move(name)); }); } auto num_actv_functions = dirct == op::rnn_direction::bidirectional ? 4 : 2; if(vec_names.size() != static_cast(num_actv_functions)) { MIGRAPHX_THROW("GRU: Invalid activation functions number, should be: " + to_string(num_actv_functions)); } auto name_it = std::find_if(vec_names.begin(), vec_names.end(), [&](auto& name) { return (map_activation_functions().count(name) == 0); }); if(name_it != vec_names.end()) { MIGRAPHX_THROW("GRU: activation function " + std::string(*name_it) + " not supported"); } std::vector vec_actv_funcs(vec_names.size()); std::transform(vec_names.begin(), vec_names.end(), vec_actv_funcs.begin(), [&](const auto& name) { return map_activation_functions().at(name); }); float clip = 0.0; if(contains(info.attributes, "clip")) { clip = parser.parse_value(info.attributes.at("clip")).at(); } int layout = 0; if(contains(info.attributes, "layout")) { layout = parser.parse_value(info.attributes.at("layout")).at(); } int linear_before_reset = 0; if(contains(info.attributes, "linear_before_reset")) { linear_before_reset = parser.parse_value(info.attributes.at("linear_before_reset")).at(); } // append undefined opeator to make 6 arguments if(args.size() < 6) { auto ins = info.add_instruction(make_op("undefined")); args.insert(args.end(), 6 - args.size(), ins); } if(layout != 0) { gru_transpose_inputs(info, args); } // first output for concatenation of hidden states auto hidden_states = info.add_instruction(make_op("gru", {{"hidden_size", hidden_size}, {"actv_func", to_value(vec_actv_funcs)}, {"direction", dirct}, {"clip", clip}, {"linear_before_reset", linear_before_reset}}), args); // second output for last gru output auto last_output = info.add_instruction(make_op("rnn_last_hs_output"), hidden_states); if(layout != 0) { gru_transpose_outputs(info, hidden_states, last_output); } return {hidden_states, last_output}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_hardmax.cpp000066400000000000000000000071721510465702400216420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_hardmax : op_parser { std::vector operators() const { return {{"Hardmax", "hardmax"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { auto input = args[0]; auto input_lens = input->get_shape().lens(); // default axis value is -1 for opset 13 int64_t axis = -1; // axis value is 1 for previous opset versions if(parser.opset_version < 13) { axis = 1; } if(contains(info.attributes, "axis")) { axis = parser.parse_value(info.attributes.at("axis")).at(); } if(parser.opset_version < 13) { // input is coerced into a 2D matrix of size NxD axis = axis < 0 ? axis + input_lens.size() : axis; size_t n = 1; for(int i = 0; i < axis; i++) { n *= input_lens[i]; } size_t d = input->get_shape().elements() / n; input = info.add_instruction(make_op("reshape", {{"dims", {n, d}}}), input); axis = 1; } auto input_type = input->get_shape().type(); auto indices = info.add_instruction(make_op("argmax", {{"axis", axis}}), input); auto data = info.add_instruction( make_op("multibroadcast", {{"out_lens", input->get_shape().lens()}}), info.add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = info.add_instruction( make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), info.add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto output = info.add_instruction(make_op("scatter_none", {{"axis", axis}}), data, indices, updates); if(parser.opset_version < 13) { output = info.add_instruction(make_op("reshape", {{"dims", input_lens}}), output); } return output; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_hardsigmoid.cpp000066400000000000000000000071531510465702400225070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_hardsigmoid : op_parser { std::vector operators() const { return {{"HardSigmoid"}, {"HardSwish"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { float alpha = 0.2; float beta = 0.5; if(opd.onnx_name == "HardSwish") { alpha = 1.0 / 6.0; } else { if(contains(info.attributes, "alpha")) alpha = info.attributes.at("alpha").f(); if(contains(info.attributes, "beta")) beta = info.attributes.at("beta").f(); } auto input_lens = args[0]->get_shape().lens(); auto input_type = args[0]->get_shape().type(); auto mb_alpha = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), info.add_literal(migraphx::literal{migraphx::shape{input_type}, {alpha}})); auto mb_beta = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), info.add_literal(migraphx::literal{migraphx::shape{input_type}, {beta}})); auto mb_zero = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), info.add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto mb_one = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), info.add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto mul = info.add_instruction(migraphx::make_op("mul"), mb_alpha, args[0]); auto add = info.add_instruction(migraphx::make_op("add"), mb_beta, mul); auto hardsigmoid = info.add_instruction(migraphx::make_op("clip"), add, mb_zero, mb_one); if(opd.onnx_name == "HardSwish") return info.add_instruction(migraphx::make_op("mul"), args[0], hardsigmoid); return hardsigmoid; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_if.cpp000066400000000000000000000103511510465702400206050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_if : op_parser { std::vector operators() const { return {{"If"}}; } std::vector parse(const op_desc& /*opd*/, onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { const auto& then_graph = info.attributes.at("then_branch").g(); const auto& else_graph = info.attributes.at("else_branch").g(); if(args.front()->get_shape().elements() != 1) { MIGRAPHX_THROW("PARSE_IF: " + info.name + " condition input can have only one element!"); } // Fold instruction if condition is constant thus can be evaled // prior to inference if(args.front()->can_eval()) { auto cond_arg = args.front()->eval(); auto* mod = info.mod; // then branch if(cond_arg.at()) { return parser.parse_graph(mod, then_graph, true); } // else branch else { return parser.parse_graph(mod, else_graph, true); } } std::string then_name = info.name + "_if"; module_ref then_mdl = parser.prog.create_module(then_name); std::string else_name = info.name + "_else"; module_ref else_mdl = parser.prog.create_module(else_name); // parse the then sub_graph (void)parser.parse_graph(then_mdl, then_graph); // parse_the else sub_graph (void)parser.parse_graph(else_mdl, else_graph); auto then_out_shapes = then_mdl->get_output_shapes(); auto else_out_shapes = else_mdl->get_output_shapes(); if(not std::equal(then_out_shapes.begin(), then_out_shapes.end(), else_out_shapes.begin(), else_out_shapes.end())) { MIGRAPHX_THROW("PARSE_IF: " + info.name + " then and else sub_grahps must have same output shapes!"); } auto if_ret = info.add_instruction(make_op("if"), args, {then_mdl, else_mdl}); auto out_s = if_ret->get_shape(); assert(out_s.type() == shape::tuple_type); const auto& vec_shapes = out_s.sub_shapes(); std::vector out_inss; for(std::size_t i = 0; i < vec_shapes.size(); ++i) { auto ret = info.add_instruction(make_op("get_tuple_elem", {{"index", i}}), if_ret); out_inss.push_back(ret); } return out_inss; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_imagescalar.cpp000066400000000000000000000060551510465702400224650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_imagescalar : op_parser { std::vector operators() const { return {{"ImageScaler"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { float scale = 1.0; std::vector bias{}; if(contains(info.attributes, "scale")) { scale = parser.parse_value(info.attributes.at("scale")).at(); } if(contains(info.attributes, "bias")) { auto&& bias_floats = info.attributes["bias"].floats(); bias = std::vector(bias_floats.begin(), bias_floats.end()); } auto input_shape = args.front()->get_shape(); auto const& input_lens = input_shape.lens(); auto input_type = input_shape.type(); auto scale_val = info.add_literal(literal{shape{input_type}, {scale}}); auto bias_vals = info.add_literal(literal{shape{input_type, {bias.size()}}, bias}); auto scale_tensor = info.add_instruction( migraphx::make_op("scalar", {{"scalar_bcst_dims", input_lens}}), scale_val); auto img_scaled = info.add_instruction(migraphx::make_op("mul"), args.front(), scale_tensor); auto bias_bcast = info.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", input_lens}}), bias_vals); return info.add_instruction(migraphx::make_op("add"), img_scaled, bias_bcast); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_instancenorm.cpp000066400000000000000000000153171510465702400227160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_FP16_INSTANCENORM_CONVERT); namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_instancenorm : op_parser { std::set valid_types = { shape::float_type, shape::half_type, shape::double_type, shape::bf16_type}; std::vector operators() const { return {{"InstanceNormalization"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector oargs) const { // y = scale * ( x - mean ) / sqrt ( variance + epsilon ) + bias // mean = reduce_mean({D1, D2, ... Dk}, x) // variance = reduce_mean({D1, D2, ... Dk}, (x - mean)^2) // Convert fp16 to fp32 to workaround for FP16 accuracy issues with reduce_mean/variance. bool convert_fp16 = true; if(enabled(MIGRAPHX_DISABLE_FP16_INSTANCENORM_CONVERT{})) { convert_fp16 = false; } float epsilon = 1e-5f; if(contains(info.attributes, "epsilon")) { epsilon = parser.parse_value(info.attributes.at("epsilon")).at(); } auto dtype = oargs[0]->get_shape().type(); auto literal_dtype = dtype; std::vector args; // cppcheck-suppress knownConditionTrueFalse if(dtype == shape::half_type and convert_fp16) { std::transform(oargs.begin(), oargs.end(), std::back_inserter(args), [&](const auto i) { return info.add_instruction( make_op("convert", {{"target_type", shape::float_type}}), i); }); literal_dtype = shape::float_type; } else { args = oargs; } auto x = args[0]; auto scale = args[1]; auto bias = args[2]; if(not contains(valid_types, dtype)) MIGRAPHX_THROW(opd.onnx_name + ": invalid output type: " + std::to_string(dtype) + ". Valid types are 1 (float), 10 (half), and 11 (double)."); auto ndims = x->get_shape().ndim(); assert(ndims >= 2); auto kdims = ndims - 2; std::vector axes(kdims); std::iota(axes.begin(), axes.end(), 2); auto mean = info.add_instruction(make_op("reduce_mean", {{"axes", axes}}), x); // Use add_common_op() to insert multibroadcast/convert instructions where needed when // inputs may be either static or dynamic. auto l1 = info.add_common_op("sub", x, mean); // for the fp16, if not converting to fp32 then divide `x` and `mean` by `sqrt(n)` and take // reduce_sum to calculate variance i.e. // var = reduce_sum((x/s_n - mean/s_n)^2) where s_n = sqrt(n) std::string reduce_op_name = (dtype == shape::half_type and not convert_fp16) ? "reduce_sum" : "reduce_mean"; if(dtype == shape::half_type and not convert_fp16) { if(x->get_shape().dynamic()) { MIGRAPHX_THROW("PARSE_INSTANCENORM: half type not supported with dynamic shape " "unless convert_fp16 is TRUE"); } auto dims = x->get_shape().lens(); double n = std::accumulate(dims.begin() + 2, dims.end(), 1, [&](const auto& i, const auto& j) { return i * j; }); n = 1.0 / std::sqrt(n); auto n_literal = info.add_literal(literal{dtype, {n}}); x = info.add_common_op("mul", {x, n_literal}); } auto l0 = info.add_common_op("sqdiff", x, mean); auto variance = info.add_instruction(make_op(reduce_op_name, {{"axes", axes}}), l0); auto epsilon_literal = info.add_literal(literal{shape{literal_dtype}, {epsilon}}); auto l2 = info.add_common_op("add", variance, epsilon_literal); auto l3 = info.add_instruction(make_op("rsqrt"), l2); auto l4 = info.add_common_op("mul", l1, l3); // add_common_op() doesn't apply the plain broadcast op, so we add that op explicitly for // both scale and bias. instruction_ref scale_bcast; instruction_ref bias_bcast; if(x->get_shape().dynamic()) { scale_bcast = info.add_instruction(make_op("broadcast", {{"axis", 1}}), scale, x); bias_bcast = info.add_instruction(make_op("broadcast", {{"axis", 1}}), bias, x); } else { auto dims = x->get_shape().lens(); scale_bcast = info.add_instruction( make_op("broadcast", {{"axis", 1}, {"out_lens", dims}}), scale); bias_bcast = info.add_instruction(make_op("broadcast", {{"axis", 1}, {"out_lens", dims}}), bias); } auto l5 = info.add_instruction(make_op("mul"), l4, scale_bcast); auto ret = info.add_instruction(make_op("add"), l5, bias_bcast); if(dtype == shape::half_type and convert_fp16) { return info.add_instruction(make_op("convert", {{"target_type", shape::half_type}}), ret); } return ret; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_isinf.cpp000066400000000000000000000066211510465702400213240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_isinf : op_parser { std::vector operators() const { return {{"IsInf", "isinf"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, const std::vector& args) const { bool detect_negative = true; bool detect_positive = true; if(contains(info.attributes, "detect_negative")) { detect_negative = static_cast( parser.parse_value(info.attributes.at("detect_negative")).at()); } if(contains(info.attributes, "detect_positive")) { detect_positive = static_cast( parser.parse_value(info.attributes.at("detect_positive")).at()); } auto x_shape = args[0]->get_shape(); if(not detect_negative and not detect_positive) { return info.add_instruction( make_op("multibroadcast", {{"out_lens", x_shape.lens()}}), info.add_literal(migraphx::literal{migraphx::shape{shape::bool_type}, {false}})); } auto is_inf = info.add_instruction(make_op("isinf"), args[0]); if(detect_negative and detect_positive) { return is_inf; } auto zero_l = info.add_literal(migraphx::literal{migraphx::shape{x_shape.type()}, {0}}); auto mb_zero = info.add_instruction(make_op("multibroadcast", {{"out_lens", x_shape.lens()}}), zero_l); auto cond = info.add_broadcastable_binary_op( detect_negative ? "less" : "greater", args[0], mb_zero); if(cond->get_shape().type() != shape::bool_type) { cond = info.add_instruction(make_op("convert", {{"target_type", shape::bool_type}}), cond); } return info.add_instruction(make_op("logical_and"), is_inf, cond); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_layernorm.cpp000066400000000000000000000216671510465702400222330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_layernorm : op_parser { std::vector operators() const { return {{"LayerNormalization"}}; } static int64_t handle_axis(const onnx_parser& parser, const onnx_parser::node_info& info) { int64_t axis = -1; if(contains(info.attributes, "axis")) { axis = parser.parse_value(info.attributes.at("axis")).at(); } return axis; } static float handle_epsilon(const onnx_parser& parser, const onnx_parser::node_info& info) { float epsilon = 1e-5f; if(contains(info.attributes, "epsilon")) { epsilon = parser.parse_value(info.attributes.at("epsilon")).at(); } return epsilon; } static bool handle_stash_type(const onnx_parser& parser, const onnx_parser::node_info& info) { bool stash_type = true; if(contains(info.attributes, "stash_type")) { stash_type = (1 == parser.parse_value(info.attributes.at("stash_type")).at()); } return stash_type; } static void is_type_valid(const migraphx::shape::type_t& dtype, const std::string& var_name) { std::set valid_types = { migraphx::shape::float_type, migraphx::shape::bf16_type, migraphx::shape::half_type}; if(not(contains(valid_types, dtype))) { MIGRAPHX_THROW("PARSE_LAYERNORM: Invalid type for " + var_name); } } static void check_x_input(const instruction_ref& x, const int64_t& axis) { auto x_shape = x->get_shape(); auto x_dtype = x_shape.type(); int64_t x_rank = x_shape.ndim(); is_type_valid(x_dtype, "input"); if(x_rank < 2) { MIGRAPHX_THROW("PARSE_LAYERNORM: invalid ndims=" + std::to_string(x_rank) + ", must be at least 2"); } // If rank(X) is r, axis' allowed range is [-r, r) if(axis < -x_rank or axis >= x_rank) { MIGRAPHX_THROW("PARSE_LAYERNORM: invalid axis"); } } static std::tuple stage_one_calculation(const onnx_parser::node_info& info, const instruction_ref& input, const float& epsilon, const int64_t& axis, const int64_t& kdims, bool stash_type) { // y = (x - mean) * rsqrt(variance + epsilon) * scale + bias // mean = reduce_mean({D1, D2, ... Dk}, x) // variance = reduce_mean({D1, D2, ... Dk}, (x - mean)^2) std::vector axes(kdims); std::iota(axes.begin(), axes.end(), axis); auto x_shape = input->get_shape(); auto x_dtype = x_shape.type(); auto x = input; if(stash_type and x_dtype != migraphx::shape::float_type) { x = info.add_instruction( make_op("convert", {{"target_type", migraphx::shape::float_type}}), input); } auto mean = info.add_instruction(make_op("reduce_mean", {{"axes", axes}}), x); auto x_sub_mean = info.add_common_op("sub", x, mean); auto x_sqdiff_mean = info.add_common_op("sqdiff", x, mean); auto variance = info.add_instruction(make_op("reduce_mean", {{"axes", axes}}), x_sqdiff_mean); auto epsilon_val = (x_dtype == migraphx::shape::half_type and std::abs(epsilon) < 1e-7) ? 1e-7 : epsilon; auto eps = info.add_literal(migraphx::literal{migraphx::shape{x_dtype}, {epsilon_val}}); auto var_eps = info.add_common_op("add", variance, eps); auto rsqrt = info.add_instruction(make_op("rsqrt"), var_eps); auto result = info.add_common_op("mul", x_sub_mean, rsqrt); if(stash_type and x_dtype != migraphx::shape::float_type) { result = info.add_instruction(make_op("convert", {{"target_type", x_dtype}}), result); } return {result, mean, rsqrt}; } static instruction_ref stage_two_calculation(const onnx_parser::node_info& info, const instruction_ref& x, const instruction_ref& scale, const instruction_ref& bias, const instruction_ref& result, const int64_t& kdims, bool skip_bias) { auto x_shape = x->get_shape(); auto x_rank = x_shape.ndim(); auto skipped_axes = x_rank - kdims; instruction_ref scale_bcast = scale; instruction_ref bias_bcast = bias; if(skipped_axes > 0) { auto x_dims = x_shape.lens(); if(scale->get_shape().ndim() == 1) { scale_bcast = info.add_instruction( make_op("broadcast", {{"axis", skipped_axes}, {"out_lens", x_dims}}), scale); } if(not skip_bias) { if(bias->get_shape().ndim() == 1) { bias_bcast = info.add_instruction( make_op("broadcast", {{"axis", skipped_axes}, {"out_lens", x_dims}}), bias); } } } auto scaled = info.add_common_op("mul", result, scale_bcast); return skip_bias ? scaled : info.add_common_op("add", scaled, bias_bcast); } std::tuple handle_inputs(std::vector& args, const int64_t& axis) const { if(args.size() < 2 or args.size() > 3) { MIGRAPHX_THROW("PARSE_LAYERNORM: invalid input count"); } auto x = args.at(0); check_x_input(x, axis); auto scale = args.at(1); is_type_valid(scale->get_shape().type(), "scale"); bool skip_bias = args.size() == 2; instruction_ref bias; if(not skip_bias) { bias = args.at(2); is_type_valid(bias->get_shape().type(), "bias"); } return {x, scale, bias, skip_bias}; } std::tuple handle_attributes(const onnx_parser& parser, const onnx_parser::node_info& info) const { auto axis = handle_axis(parser, info); auto epsilon = handle_epsilon(parser, info); auto stash_type = handle_stash_type(parser, info); return {axis, epsilon, stash_type}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { auto [axis, epsilon, stash_type] = handle_attributes(parser, info); auto [x, scale, bias, skip_bias] = handle_inputs(args, axis); auto x_rank = x->get_shape().ndim(); // axis can be negative axis = axis < 0 ? axis + x_rank : axis; auto kdims = x_rank - axis; auto [result, mean, rsqrt] = stage_one_calculation(info, x, epsilon, axis, kdims, stash_type); auto y = stage_two_calculation(info, x, scale, bias, result, kdims, skip_bias); return {y, mean, rsqrt}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_lessorequal.cpp000066400000000000000000000043061510465702400225510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_lessorequal : op_parser { std::vector operators() const { return {{"LessOrEqual"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { auto in_res = info.add_broadcastable_binary_op("greater", args[0], args[1]); if(in_res->get_shape().type() != shape::bool_type) { in_res = info.add_instruction(make_op("convert", {{"target_type", shape::bool_type}}), in_res); } return info.add_instruction(make_op("not"), in_res); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_loop.cpp000066400000000000000000000077551510465702400211760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_loop : op_parser { std::vector operators() const { return {{"Loop"}}; } std::vector parse(const op_desc& /*opd*/, onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { // default value of the max_iter_num int64_t max_iterations = parser.max_loop_iterations; // iteration input is empty if(args.at(0)->name() == "undefined") { shape iter_s{shape::int64_type}; args[0] = info.add_literal(literal(iter_s, {max_iterations})); } else { auto arg_iters = args.at(0)->eval(); if(not arg_iters.empty()) { max_iterations = arg_iters.at(); } } // cap max_iter because loop uses static shapes with max_iter size and huge numbers // here can cause overflow if(max_iterations > parser.limit_max_iterations) { std::cerr << "WARNING: PARSE_LOOP max_iterations exceeds the maximum loop " "iterations limit, it will be changed from " << max_iterations << " to " << parser.limit_max_iterations << ".\n"; max_iterations = parser.limit_max_iterations; } // condition input is empty if(args.at(1)->name() == "undefined") { shape cond_s{shape::bool_type}; args[1] = info.add_literal(literal(cond_s, {true})); } // retrieve the subgraph const auto& sub_graph = info.attributes.at("body").g(); std::string mod_name = info.name + "_loop"; module_ref sub_mod = parser.prog.create_module(mod_name); // parse the sub_graph (void)parser.parse_graph(sub_mod, sub_graph); auto ret = info.add_instruction( make_op("loop", {{"max_iterations", max_iterations}}), args, {sub_mod}); auto out_s = ret->get_shape(); assert(out_s.type() == shape::tuple_type); const auto& vec_shapes = out_s.sub_shapes(); std::vector out_inss; for(std::size_t i = 0; i < vec_shapes.size(); ++i) { auto r = info.add_instruction(make_op("get_tuple_elem", {{"index", i}}), ret); out_inss.push_back(r); } return out_inss; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_lpnormalization.cpp000066400000000000000000000107341510465702400234360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // Parser for LpNormalization ONNX operator. /*! Normalizes a tensor by the L1 or L2 norms along a given axis. Norms that evaluate to 0 are changed to 1 to prevent division by zero. */ struct parse_lpnormalization : op_parser { std::vector operators() const { return {{"LpNormalization"}}; } instruction_ref parse(const op_desc&, const onnx_parser&, const onnx_parser::node_info& info, std::vector args) const { int p = 2; if(contains(info.attributes, "p")) { p = info.attributes.at("p").i(); } if(p != 1 and p != 2) { MIGRAPHX_THROW("LPNORMALIZATION: only L1 and L2 norm supported"); } auto input = args.front(); auto input_shape = input->get_shape(); const auto& input_lens = input_shape.lens(); auto input_type = input_shape.type(); std::ptrdiff_t num_axes = input_lens.size(); std::ptrdiff_t axis = -1; if(contains(info.attributes, "axis")) { axis = info.attributes.at("axis").i(); if(axis < -num_axes or axis >= num_axes) { // handled in normalize_attributes but throwing here might be clearer MIGRAPHX_THROW("LPNORMALIZATION: selected axis out of bounds"); } } migraphx::instruction_ref p_val; if(p == 1) { p_val = info.add_instruction(migraphx::make_op("abs"), input); } else { p_val = info.add_instruction(migraphx::make_op("mul"), input, input); } // need to check for zeros from lp norm to prevent division by zero // change them to 1 for the element-wise division auto norms = info.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {axis}}}), p_val); if(p == 2) { norms = info.add_instruction(migraphx::make_op("sqrt"), norms); } // broadcast back to initial shape, negative axis option doesn't work with unidirectional norms = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), norms); auto zero_mb = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), info.add_literal(migraphx::literal{migraphx::shape{input_type}, {0.}})); auto one_mb = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), info.add_literal(migraphx::literal{migraphx::shape{input_type}, {1.}})); auto is_zero = info.add_instruction(migraphx::make_op("equal"), norms, zero_mb); auto norms_zeros_to_one = info.add_instruction(migraphx::make_op("where"), is_zero, one_mb, norms); return info.add_instruction(migraphx::make_op("div"), input, norms_zeros_to_one); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_lstm.cpp000066400000000000000000000202341510465702400211670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static void lstm_actv_functions(op::rnn_direction dirct, std::vector& actv_func_names) { // need 6 activation functions for bidirectional directions if(dirct == op::rnn_direction::bidirectional) { actv_func_names.push_back(actv_func_names.at(0)); actv_func_names.push_back(actv_func_names.at(1)); actv_func_names.push_back(actv_func_names.at(2)); } } static void lstm_transpose_inputs(onnx_parser::node_info& info, std::vector& args) { std::vector perm{1, 0, 2}; args[0] = info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[0]); if(args.size() >= 6 and not args[5]->is_undefined()) { args[5] = info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[5]); } if(args.size() >= 7 and not args[6]->is_undefined()) { args[6] = info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[6]); } } static void lstm_transpose_outputs(onnx_parser::node_info& info, instruction_ref& hidden_states, instruction_ref& last_output, instruction_ref& last_cell_output) { std::vector perm_hs{2, 0, 1, 3}; hidden_states = info.add_instruction(make_op("transpose", {{"permutation", perm_hs}}), hidden_states); std::vector perm_last{1, 0, 2}; last_output = info.add_instruction(make_op("transpose", {{"permutation", perm_last}}), last_output); last_cell_output = info.add_instruction(make_op("transpose", {{"permutation", perm_last}}), last_cell_output); } struct parse_lstm : op_parser { std::vector operators() const { return {{"LSTM"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { migraphx::shape input_shape = args[0]->get_shape(); std::size_t hidden_size = args[2]->get_shape().lens()[2]; if(contains(info.attributes, "hidden_size")) { std::size_t hidden_size_att = parser.parse_value(info.attributes.at("hidden_size")).at(); if(hidden_size != hidden_size_att) { MIGRAPHX_THROW("LSTM: hidden size mismatch in input and attribute"); } } // Handling of direction to be added later std::string direction{"forward"}; if(contains(info.attributes, "direction")) { direction = info.attributes.at("direction").s(); } op::rnn_direction dirct = op::rnn_direction::forward; if(direction == "bidirectional") { dirct = op::rnn_direction::bidirectional; } else if(direction == "reverse") { dirct = op::rnn_direction::reverse; } else if(direction == "forward") { dirct = op::rnn_direction::forward; } else { MIGRAPHX_THROW("LSTM: incorrect direction attribute"); } // set default activation functions std::vector vec_names = {"sigmoid", "tanh", "tanh"}; lstm_actv_functions(dirct, vec_names); if(contains(info.attributes, "activations")) { auto names = info.attributes.at("activations").strings(); vec_names.clear(); vec_names.resize(names.size()); std::transform(names.begin(), names.end(), vec_names.begin(), [](auto name) { return to_lower(std::move(name)); }); } auto num_actv_functions = dirct == op::rnn_direction::bidirectional ? 6 : 3; if(vec_names.size() != static_cast(num_actv_functions)) { MIGRAPHX_THROW("LSTM: Invalid activation functions number, should be: " + to_string(num_actv_functions)); } auto name_it = std::find_if(vec_names.begin(), vec_names.end(), [&](auto& name) { return (map_activation_functions().count(name) == 0); }); if(name_it != vec_names.end()) { MIGRAPHX_THROW("LSTM: activation function " + std::string(*name_it) + " not supported"); } std::vector vec_actv_funcs(vec_names.size()); std::transform(vec_names.begin(), vec_names.end(), vec_actv_funcs.begin(), [&](const auto& name) { return map_activation_functions().at(name); }); float clip = 0.0; if(contains(info.attributes, "clip")) { clip = parser.parse_value(info.attributes.at("clip")).at(); } int input_forget = 0; if(contains(info.attributes, "input_forget")) { input_forget = parser.parse_value(info.attributes.at("input_forget")).at(); } int layout = 0; if(contains(info.attributes, "layout")) { layout = parser.parse_value(info.attributes.at("layout")).at(); } // append undefined opeator to make 6 arguments if(args.size() < 8) { auto ins = info.add_instruction(make_op("undefined")); args.insert(args.end(), 8 - args.size(), ins); } if(layout != 0) { lstm_transpose_inputs(info, args); } // first output for concatenation of hidden states auto hidden_states = info.add_instruction(make_op("lstm", {{"hidden_size", hidden_size}, {"actv_func", to_value(vec_actv_funcs)}, {"direction", dirct}, {"clip", clip}, {"input_forget", input_forget}}), args); auto last_output = info.add_instruction(make_op("rnn_last_hs_output"), hidden_states); // third output for last cell output auto last_cell_output = info.add_instruction(make_op("rnn_last_cell_output"), hidden_states); if(layout != 0) { lstm_transpose_outputs(info, hidden_states, last_output, last_cell_output); } return {hidden_states, last_output, last_cell_output}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_matmul.cpp000066400000000000000000000464751510465702400215260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_matmul : op_parser { std::vector operators() const { return {{"MatMul", "dot"}, {"MatMulInteger", "quant_dot"}, {"MatMulIntegerToFloat", "quant_dot_scaled"}}; } static void broadcast_dimensions(const onnx_parser::node_info& info, const std::vector& s0_lens, const std::vector& s1_lens, const instruction_ref& a0, const instruction_ref& a1, instruction_ref& ba0, instruction_ref& ba1) { // try broadcasting if dimensions other than last two do not match if(not std::equal( s0_lens.rbegin() + 2, s0_lens.rend(), s1_lens.rbegin() + 2, s1_lens.rend())) { auto l0_it = s0_lens.begin() + s0_lens.size() - 2; std::vector l0_broadcasted_lens(s0_lens.begin(), l0_it); auto l1_it = s1_lens.begin() + s1_lens.size() - 2; std::vector l1_broadcasted_lens(s1_lens.begin(), l1_it); auto output_lens = compute_broadcasted_lens(l0_broadcasted_lens, l1_broadcasted_lens); l0_broadcasted_lens = output_lens; l0_broadcasted_lens.insert(l0_broadcasted_lens.end(), l0_it, s0_lens.end()); l1_broadcasted_lens = output_lens; l1_broadcasted_lens.insert(l1_broadcasted_lens.end(), l1_it, s1_lens.end()); if(s0_lens != l0_broadcasted_lens) { ba0 = info.add_instruction( make_op("multibroadcast", {{"out_lens", l0_broadcasted_lens}}), a0); } if(s1_lens != l1_broadcasted_lens) { ba1 = info.add_instruction( make_op("multibroadcast", {{"out_lens", l1_broadcasted_lens}}), a1); } } } // Convert to half prior to a shift to ensure we preserve accuracy here then // convert back to int8 static instruction_ref add_int8_shift(const onnx_parser::node_info& info, const instruction_ref& offset_op, instruction_ref& unshifted_input) { auto unshifted_input_half = info.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), unshifted_input); auto input_shifted_half = info.add_common_op("add", unshifted_input_half, offset_op); return info.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); } static bool is_symmetric_zero_point(instruction_ref zp) { if(not zp->can_eval()) return false; float check_value = 0; if(zp->get_shape().type() == migraphx::shape::uint8_type) check_value = 128; bool all_zeros = false; zp->eval().visit([&](auto z) { all_zeros = std::all_of( z.begin(), z.end(), [&](auto val) { return float_equal(val, check_value); }); }); return all_zeros; } static instruction_ref set_scale_arg(const onnx_parser::node_info& info, const std::vector& args, const instruction_ref& mat_input, const int index) { instruction_ref scale_arg = args[index]; std::set supported_dq_types = {migraphx::shape::float_type, migraphx::shape::half_type}; auto scale_shape = scale_arg->get_shape(); if(not(contains(supported_dq_types, scale_shape.type()))) { MIGRAPHX_THROW("PARSE_QUANT_DOT_SCALED: Scales must be float or half_type"); } if(scale_shape.lens().at(0) != *(mat_input->get_shape().lens().rbegin()) and not scale_shape.scalar()) { MIGRAPHX_THROW("PARSE_QUANT_DOT_SCALED: Scale must have same dim as matrix column"); } if(scale_shape.lens().size() > 1 and not scale_shape.scalar()) { MIGRAPHX_THROW("PARSE_QUANT_DOT_SCALED: Scales shape must be scalar or 1-D tensor"); } if(scale_shape.scalar()) { scale_arg = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), scale_arg); scale_shape = scale_arg->get_shape(); } scale_arg = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), scale_arg); return scale_arg; } static instruction_ref set_scale_bias(const std::vector& args, const int index, const migraphx::shape& scale_arg_shape, const instruction_ref& compare_arg, bool& has_valid_scale_bias) { has_valid_scale_bias = false; if(args.size() > index) { instruction_ref scale_bias_arg = args[index]; std::set supported_dq_types = {migraphx::shape::float_type, migraphx::shape::half_type}; if(not(contains(supported_dq_types, scale_bias_arg->get_shape().type()))) { MIGRAPHX_THROW("PARSE_QUANT_DOT_SCALED: Bias must be float or half_type"); } if(scale_bias_arg->get_shape().type() != scale_arg_shape.type()) { MIGRAPHX_THROW("PARSE_QUANT_DOT_SCALED: Bias must be the same type as scales"); } if(scale_bias_arg->get_shape().lens().at(0) != *(compare_arg->get_shape().lens().rbegin())) { MIGRAPHX_THROW("PARSE_QUANT_DOT_SCALED: Bias have same dim as matrix B column"); } has_valid_scale_bias = true; return scale_bias_arg; } return compare_arg; } static instruction_ref set_bias_arg(const std::string& name, const std::vector& args, const int index, const instruction_ref& input, bool& has_valid_bias) { has_valid_bias = false; if(args.size() > index) { instruction_ref bias_arg = args[index]; if(bias_arg->get_shape().type() != input->get_shape().type()) { MIGRAPHX_THROW(name + ": zero point must be the same type as data"); } // Don't return zero point if it will cause symmetric zero point. No need to bias if(is_symmetric_zero_point(bias_arg)) return input; has_valid_bias = true; return bias_arg; } return input; } static void shift_input_and_bias(const onnx_parser::node_info& info, const instruction_ref& offset_op, const bool has_bias, instruction_ref& input, instruction_ref& input_bias) { input = add_int8_shift(info, offset_op, input); if(has_bias) { input_bias = add_int8_shift(info, offset_op, input_bias); } else { input_bias = input; } } static void handle_scaled_transposes(const onnx_parser::node_info& info, instruction_ref& scale, instruction_ref& zp, bool no_zp) { if(no_zp) { scale = info.add_instruction(make_op("transpose", {{"permutation", {0, 1}}}), scale); } else { scale = info.add_instruction(make_op("transpose", {{"permutation", {0, 1}}}), scale); zp = info.add_instruction(make_op("transpose", {{"permutation", {1, 0}}}), zp); } } static instruction_ref handle_dequantized(const onnx_parser::node_info& info, const instruction_ref& a0, const instruction_ref& scale_a0, const instruction_ref& zp_a0, bool no_zp) { instruction_ref dequantized_op; if(no_zp) { auto bc_scale_a0 = info.add_instruction( make_op("multibroadcast", {{"out_lens", a0->get_shape().lens()}}), scale_a0); dequantized_op = info.add_instruction(make_op("dequantizelinear"), a0, bc_scale_a0); } else { auto bc_scale_a0 = info.add_instruction( make_op("multibroadcast", {{"out_lens", a0->get_shape().lens()}}), scale_a0); auto bc_zp_a0 = info.add_instruction( make_op("multibroadcast", {{"out_lens", a0->get_shape().lens()}}), zp_a0); dequantized_op = info.add_instruction(make_op("dequantizelinear"), a0, bc_scale_a0, bc_zp_a0); } return dequantized_op; } static instruction_ref handle_scaled_output(const onnx_parser::node_info& info, const instruction_ref& a0, const instruction_ref& a1, const instruction_ref& scale_a0, const instruction_ref& scale_a1, const instruction_ref& zp_a0, const instruction_ref& zp_a1, const instruction_ref& scaled_bias, const bool has_scale_bias) { instruction_ref unsq_zp_a0; instruction_ref unsq_zp_a1; bool a0_has_no_zp = (a0 == zp_a0); bool a1_has_no_zp = (a1 == zp_a1); if(not a0_has_no_zp) { unsq_zp_a0 = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), zp_a0); if(zp_a0->get_shape().scalar()) { unsq_zp_a0 = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), unsq_zp_a0); } } if(not a1_has_no_zp) { unsq_zp_a1 = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), zp_a1); if(zp_a1->get_shape().scalar()) { unsq_zp_a1 = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), unsq_zp_a1); } } auto dq_a0 = handle_dequantized(info, a0, scale_a0, unsq_zp_a0, a0_has_no_zp); auto dq_a1 = handle_dequantized(info, a1, scale_a1, unsq_zp_a1, a1_has_no_zp); auto res = info.add_instruction(make_op("dot"), dq_a0, dq_a1); // Handle case of the bias after scaling if(has_scale_bias) res = info.add_common_op("sub", res, scaled_bias); return res; } static void handle_uint8_input(const onnx_parser::node_info& info, const bool has_bias, const instruction_ref& offset_op, instruction_ref& arg, instruction_ref& bias_arg) { auto arg_type = arg->get_shape().type(); // always convert uint8 to int8 to avoid rollover if(arg_type == migraphx::shape::uint8_type) { shift_input_and_bias(info, offset_op, has_bias, arg, bias_arg); } // subtract bias from result after conversion if(has_bias) { bias_arg = info.add_common_op("sub", arg, bias_arg); } } instruction_ref parse(const op_desc& opd, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { std::string op_name{opd.op_name}; auto a0 = args[0]; auto a1 = args[1]; auto s0 = a0->get_shape(); auto s1 = a1->get_shape(); instruction_ref dot_res; bool is_a_prepended = false; bool is_b_appended = false; if(s0.ndim() == 1) { is_a_prepended = true; a0 = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), args[0]); } if(s1.ndim() == 1) { is_b_appended = true; a1 = info.add_instruction(make_op("unsqueeze", {{"axes", {1}}}), args[1]); } auto is_quant_dot = opd.op_name == "quant_dot"; auto is_quant_dot_scaled = opd.op_name == "quant_dot_scaled"; auto is_dot = opd.op_name == "dot"; if(s0.dynamic() or s1.dynamic()) { if(is_quant_dot or is_quant_dot_scaled) { MIGRAPHX_THROW(op_name + ": dynamic inputs not supported"); } auto s0_dds = a0->get_shape().to_dynamic().dyn_dims(); auto s1_dds = a1->get_shape().to_dynamic().dyn_dims(); if(not std::equal( s0_dds.rbegin() + 2, s0_dds.rend(), s1_dds.rbegin() + 2, s1_dds.rend())) { auto broadcasted_a0 = info.add_instruction(make_op("broadcast_for_dot"), a0, a1); auto broadcasted_a1 = info.add_instruction(make_op("broadcast_for_dot"), a1, a0); dot_res = info.add_instruction(make_op(opd.op_name), broadcasted_a0, broadcasted_a1); } else { dot_res = info.add_instruction(make_op(opd.op_name), a0, a1); } } else { auto s0_lens = a0->get_shape().lens(); auto s1_lens = a1->get_shape().lens(); if(is_dot and args.size() > 2) { MIGRAPHX_THROW(op_name + ": Bias Args not supported"); } bool has_ba0 = false; bool has_ba1 = false; bool has_scale_bias = false; int a0_zp_index = 2; int a1_zp_index = 3; instruction_ref scale_a0; instruction_ref scale_a1; // Handles case with for when scales are present in operator if(is_quant_dot_scaled) { a0_zp_index = 4; a1_zp_index = 5; scale_a0 = set_scale_arg(info, args, a0, 2); scale_a1 = set_scale_arg(info, args, a1, 3); if(scale_a0->get_shape().type() != scale_a1->get_shape().type()) { MIGRAPHX_THROW(op_name + ": Scales must be the same type"); } } instruction_ref ba0 = set_bias_arg(op_name, args, a0_zp_index, a0, has_ba0); instruction_ref ba1 = set_bias_arg(op_name, args, a1_zp_index, a1, has_ba1); // handle optional bias arg to the result instruction_ref scaled_bias; if(is_quant_dot_scaled) { auto scaled_index = 6; scaled_bias = set_scale_bias(args, scaled_index, scale_a1->get_shape(), a1, has_scale_bias); } // Only INT8 or UINT8 type currently supported std::set supported_types = {migraphx::shape::uint8_type, migraphx::shape::int8_type}; const auto a0_type = a0->get_shape().type(); const auto a1_type = a1->get_shape().type(); if((not is_dot) and (not contains(supported_types, a0_type) or not contains(supported_types, a1_type))) { MIGRAPHX_THROW(op_name + ": Unsupported type"); } if((is_quant_dot and ((a0_type == migraphx::shape::uint8_type) or (a1_type == migraphx::shape::uint8_type)))) { auto offset_op = info.add_literal( migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {-128}}); handle_uint8_input(info, has_ba0, offset_op, a0, ba0); handle_uint8_input(info, has_ba1, offset_op, a1, ba1); } broadcast_dimensions(info, s0_lens, s1_lens, a0, a1, ba0, ba1); // Apply the scale to dequantize input to then perform a simple dot // after the zero points are applied otherwise get a int32 output from the quantized // equivalent. Ensure these are broadcasted accordingly before we perform a dot if(is_quant_dot_scaled) { dot_res = handle_scaled_output( info, a0, a1, scale_a0, scale_a1, ba0, ba1, scaled_bias, has_scale_bias); } else { dot_res = info.add_instruction(make_op(opd.op_name), ba0, ba1); } } // squeeze the appended or prepended dimensions int64_t num_axis = dot_res->get_shape().ndim(); if(is_a_prepended) { dot_res = info.add_instruction(make_op("squeeze", {{"axes", {num_axis - 2}}}), dot_res); --num_axis; } if(is_b_appended) { dot_res = info.add_instruction(make_op("squeeze", {{"axes", {num_axis - 1}}}), dot_res); } return dot_res; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_matmulnbits.cpp000066400000000000000000000177521510465702400225620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/errors.hpp" #include "migraphx/instruction_ref.hpp" #include "migraphx/onnx/onnx_parser.hpp" #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_matmulnbits : op_parser { std::vector operators() const { return {{"MatMulNBits"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, const std::vector& args) const { const size_t n = parse_attribute(parser, info, "N"); const size_t k = parse_attribute(parser, info, "K"); const size_t bits = parse_attribute(parser, info, "bits"); const size_t block_size = parse_attribute(parser, info, "block_size"); if(bits != 4) MIGRAPHX_THROW("MatMulNBits: bits only supported for value of 4, actual value " + std::to_string(bits)); if(block_size < 16 or (block_size & (block_size - 1)) != 0) MIGRAPHX_THROW("MatMulNBits: block_size must be a power of 2 and >=16, actual value " + std::to_string(block_size)); const size_t n_blocks_per_col = (k + block_size - 1) / block_size; const size_t blob_size = std::ceil(block_size * bits / 8.0f); std::vector expected_b_lens{n, n_blocks_per_col, blob_size}; if(args[1]->get_shape().lens() != expected_b_lens) MIGRAPHX_THROW("MatMulNBits: Input B does not match expected dims: " + to_string_range(expected_b_lens) + ". Actual dims: " + to_string_range(args[1]->get_shape().lens())); const size_t expected_scales_lens = n * n_blocks_per_col; if(args[2]->get_shape().elements() != expected_scales_lens) MIGRAPHX_THROW("MatMulNBits: Input scales does not match expected dims: " + to_string(expected_scales_lens) + ". Actual dims: " + to_string_range(args[2]->get_shape().lens())); if(args.size() > 3) { std::vector expected_zp_lens{ static_cast(n * std::ceil(n_blocks_per_col * bits / 8.0f))}; if(args[3]->get_shape().lens() != expected_zp_lens) MIGRAPHX_THROW("MatMulNBits: Input zero_points does not match expected dims: " + to_string_range(expected_zp_lens) + ". Actual dims: " + to_string_range(args[3]->get_shape().lens())); } auto b = dequantize_b(info, n, k, block_size, args); b = info.add_instruction(make_op("transpose", {{"permutation", {1, 0}}}), b); return matmul(info, args[0], b); } private: int parse_attribute(const onnx_parser& parser, onnx_parser::node_info& info, const std::string& attribute_name) const { if(not contains(info.attributes, attribute_name)) MIGRAPHX_THROW("MatMulNBits: Attribute " + attribute_name + " required, but is missing"); return parser.parse_value(info.attributes[attribute_name]).at(); } instruction_ref dequantize_b(onnx_parser::node_info& info, int n, int k, int block_size, const std::vector& args) const { auto b = unpack(info, n, k, args[1]); auto n_blocks_per_col = (k + block_size - 1) / block_size; auto scales = info.add_instruction(make_op("reshape", {{"dims", {n, -1}}}), args[2]); scales = prepare_blockwise_dq_arg(info, n, k, block_size, scales); instruction_ref zp; if(args.size() == 4) { zp = unpack(info, n, n_blocks_per_col, args[3]); zp = prepare_blockwise_dq_arg(info, n, k, block_size, zp); } else { zp = info.add_literal(literal{shape{shape::uint8_type, {1}}, {8}}); zp = info.add_instruction( make_op("multibroadcast", {{"out_lens", b->get_shape().lens()}}), zp); } return info.add_instruction(make_op("dequantizelinear"), {b, scales, zp}); } instruction_ref unpack(onnx_parser::node_info& info, int n, int dim1, instruction_ref x) const { x = info.add_instruction(make_op("reshape", {{"dims", {n, -1}}}), x); x = info.add_instruction(make_op("unpack_int4"), x); if(x->get_shape().lens()[1] > dim1) { x = info.add_instruction( make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {dim1}}}), x); } return x; } instruction_ref prepare_blockwise_dq_arg( onnx_parser::node_info& info, int n, int k, int block_size, instruction_ref x) const { x = info.add_instruction(make_op("unsqueeze", {{"axes", {2}}}), x); auto bc_lens = x->get_shape().lens(); bc_lens[2] = block_size; x = info.add_instruction(make_op("multibroadcast", {{"out_lens", bc_lens}}), x); x = info.add_instruction(make_op("reshape", {{"dims", {n, -1}}}), x); // Detect runt block if(x->get_shape().lens()[1] > k) { x = info.add_instruction( make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {k}}}), x); } return x; } instruction_ref matmul(onnx_parser::node_info& info, instruction_ref a, instruction_ref b) const { const auto a_rank = a->get_shape().ndim(); // B is always rank 2: // If A is rank 1, unsqueeze A to make it rank 2 to prepare for dot // If A is rank 2, just a regular dot // If A is rank > 2, broadcast B to match outer dims of A to prepare for dot if(a_rank == 1) { a = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), a); } else if(a_rank > 2) { auto b_lens = b->get_shape().lens(); auto b_bc_lens = a->get_shape().lens(); std::copy(b_lens.begin(), b_lens.end(), b_bc_lens.end() - 2); b = info.add_instruction(make_op("multibroadcast", {{"out_lens", b_bc_lens}}), b); } auto dot = info.add_instruction(make_op("dot"), a, b); if(a_rank == 1) dot = info.add_instruction( make_op("squeeze", {{"axes", {dot->get_shape().ndim() - 2}}}), dot); return dot; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_mean.cpp000066400000000000000000000066141510465702400211360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_mean : op_parser { std::set float_types = {shape::float_type, shape::half_type, shape::double_type}; std::vector operators() const { return {{"Mean"}}; } /// Calculates the element-wise mean of n>=1 input tensors instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { auto num_data = args.size(); if(num_data == 1) return args[0]; auto divisor = info.add_literal( migraphx::literal{migraphx::shape{args[0]->get_shape().type()}, {num_data}}); if(contains(float_types, args[0]->get_shape().type())) { return std::accumulate(args.begin() + 1, args.end(), info.add_broadcastable_binary_op("div", args[0], divisor), [&](auto mean, auto data_i) { // Pre-divide each tensor element-wise by n to reduce risk of // overflow during summation auto div = info.add_broadcastable_binary_op("div", data_i, divisor); return info.add_broadcastable_binary_op("add", mean, div); }); } else { // Compute sum before division for integral types auto sum = std::accumulate( args.begin() + 1, args.end(), args[0], [&](auto accum, auto data_i) { return info.add_broadcastable_binary_op("add", accum, data_i); }); return info.add_broadcastable_binary_op("div", sum, divisor); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_mean_variance_normalization.cpp000066400000000000000000000050611510465702400257470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_mean_variance_normalization : op_parser { std::vector operators() const { return {{"MeanVarianceNormalization"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, onnx_parser::node_info info, std::vector args) const { auto&& data = args.front(); auto data_rank = data->get_shape().ndim(); value options = {}; if(contains(info.attributes, "axes")) { const auto& axes_attr = info.attributes["axes"].ints(); std::vector axes{axes_attr.begin(), axes_attr.end()}; options.insert({"axes", axes}); } else if(data_rank != 4) { MIGRAPHX_THROW( "Input tensor needs to be rank 4 when axes is not specified. Instead it is rank " + std::to_string(data_rank)); } return op::builder::add("mean_variance_normalization", *info.mod, args, options).at(0); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_mod.cpp000066400000000000000000000045531510465702400207750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_mod : op_parser { std::vector operators() const { return {{"Mod"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { std::string mod = "mod"; if(is_type_float(args[0]->get_shape().type()) or is_type_float(args[1]->get_shape().type())) { if(not contains(info.attributes, "fmod")) { MIGRAPHX_THROW("Mod operator with float args and fmod=0 invalid"); } } if(contains(info.attributes, "fmod")) { if(parser.parse_value(info.attributes.at("fmod")).at() == 1) { mod = "fmod"; } } return info.add_common_op(mod, args[0], args[1]); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_multi_head_attention.cpp000066400000000000000000000315211510465702400244110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { enum class qkv_fomat_t { q_k_v = 0, q_k_v_cross = 1, kv_packed = 2, qkv_packed = 3 }; struct multi_head_attention_parameters { int64_t batch_size; int64_t q_sequence_length; int64_t kv_sequence_length; int64_t hidden_size; int64_t hidden_size_v; int64_t head_size; int64_t head_size_v; qkv_fomat_t qkv_fomat; }; struct parse_multi_head_attention : op_parser { std::vector operators() const { return {{"MultiHeadAttention"}}; } void unpack_qkv(const onnx_parser::node_info& info, instruction_ref& query, instruction_ref& key, instruction_ref& value) const { // (batch_size, q_sequence_length, num_heads, 3, head_size) -> // (3, batch_size, q_sequence_length, num_heads, head_size) auto qkv_packed = info.add_instruction(make_op("transpose", {{"permutation", {3, 0, 1, 2, 4}}}), query); query = info.add_instruction( make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), qkv_packed); query = info.add_instruction(make_op("squeeze", {{"axes", {0}}}), query); key = info.add_instruction( make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), qkv_packed); key = info.add_instruction(make_op("squeeze", {{"axes", {0}}}), key); value = info.add_instruction( make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {3}}}), qkv_packed); value = info.add_instruction(make_op("squeeze", {{"axes", {0}}}), value); } void unpack_kv(const onnx_parser::node_info& info, instruction_ref& key, instruction_ref& value) const { // (batch_size, kv_sequence_length, num_heads, 2, head_size) -> // (2, batch_size, kv_sequence_length, num_heads, head_size) auto kv_packed = info.add_instruction(make_op("transpose", {{"permutation", {3, 0, 1, 2, 4}}}), key); key = info.add_instruction( make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), kv_packed); key = info.add_instruction(make_op("squeeze", {{"axes", {0}}}), key); value = info.add_instruction( make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), kv_packed); value = info.add_instruction(make_op("squeeze", {{"axes", {0}}}), value); } void check_inputs(const std::vector& args, const int64_t num_heads, multi_head_attention_parameters& params) const { if(args.empty() or args.size() > 3) MIGRAPHX_THROW("MultiHeadAttention: Wrong number of inputs. Only 'query', 'key' and " "'value' inputs are supported."); auto query_dim = args[0]->get_shape().ndim(); auto query_lens = args[0]->get_shape().lens(); params.batch_size = query_lens[0]; params.q_sequence_length = query_lens[1]; if(query_dim != 3 and query_dim != 5) MIGRAPHX_THROW("MultiHeadAttention: Input 'query' rank needs to be 3 or 5, current: " + std::to_string(query_dim)); if(query_dim == 5) { if(query_lens[2] != num_heads or query_lens[3] != 3) MIGRAPHX_THROW("MultiHeadAttention: Input 'query' shape needs to be (batch_size, " "q_sequence_length, num_heads, 3, head_size) for packed input."); params.kv_sequence_length = query_lens[1]; params.head_size = query_lens[4]; params.head_size_v = query_lens[4]; params.hidden_size = num_heads * query_lens[4]; params.hidden_size_v = num_heads * query_lens[4]; params.qkv_fomat = qkv_fomat_t::qkv_packed; } else // query_dim == 3 { if(args.size() < 2) MIGRAPHX_THROW("MultiHeadAttention: Wrong number of inputs, 'key' is missing."); params.hidden_size = query_lens[2]; params.head_size = query_lens[2] / num_heads; auto key_dim = args[1]->get_shape().ndim(); auto key_lens = args[1]->get_shape().lens(); if(key_dim < 3 or key_dim > 5) MIGRAPHX_THROW( "MultiHeadAttention: Input 'key' rank needs to be 3, 4 or 5, current: " + std::to_string(key_dim)); if(key_dim == 5) { if(key_lens[0] != params.batch_size or key_lens[2] != num_heads or key_lens[3] != 2 or key_lens[4] != params.head_size) MIGRAPHX_THROW("MultiHeadAttention: Input 'key' shape needs to be (batch_size, " "kv_sequence_length, num_heads, 2, head_size)"); params.kv_sequence_length = key_lens[1]; params.hidden_size_v = params.hidden_size; params.head_size_v = key_lens[4]; params.qkv_fomat = qkv_fomat_t::kv_packed; } else { if(args.size() < 3) MIGRAPHX_THROW( "MultiHeadAttention: Wrong number of inputs, 'value' is missing."); auto value_dim = args[2]->get_shape().ndim(); auto value_lens = args[2]->get_shape().lens(); if(key_dim != value_dim) MIGRAPHX_THROW( "MultiHeadAttention: Input 'key' and 'value' rank needs to be equal."); if(key_dim == 3) { if(key_lens[0] != params.batch_size or key_lens[2] != params.hidden_size) MIGRAPHX_THROW("MultiHeadAttention: Input 'key' shape needs to be " "(batch_size, kv_sequence_length, hidden_size)"); if(value_lens[0] != params.batch_size or value_lens[1] != key_lens[1]) MIGRAPHX_THROW("MultiHeadAttention: Input 'value' shape needs to be " "(batch_size, kv_sequence_length, hidden_size_v)"); params.kv_sequence_length = key_lens[1]; params.hidden_size_v = value_lens[2]; params.head_size_v = value_lens[2] / num_heads; params.qkv_fomat = qkv_fomat_t::q_k_v; } else // key_dim == 4 { if(key_lens[0] != params.batch_size or key_lens[1] != num_heads or key_lens[3] != params.head_size) MIGRAPHX_THROW("MultiHeadAttention: Input 'key' shape needs to be " "(batch_size, num_heads, kv_sequence_length, head_size)"); if(value_lens[0] != params.batch_size or value_lens[1] != num_heads or value_lens[2] != key_lens[2]) MIGRAPHX_THROW("MultiHeadAttention: Input 'value' shape needs to be " "(batch_size, num_heads, kv_sequence_length, head_size_v)"); params.kv_sequence_length = key_lens[2]; params.hidden_size_v = value_lens[3] * num_heads; params.head_size_v = value_lens[3]; params.qkv_fomat = qkv_fomat_t::q_k_v_cross; } } } } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { if(not contains(info.attributes, "num_heads")) MIGRAPHX_THROW("MultiHeadAttention: num_heads attribute is required"); int64_t num_heads = parser.parse_value(info.attributes.at("num_heads")).at(); multi_head_attention_parameters params; check_inputs(args, num_heads, params); auto query = args[0]; instruction_ref key; instruction_ref value; if(params.qkv_fomat == qkv_fomat_t::qkv_packed) { // Packed QKV: (batch_size, q_sequence_length, num_heads, 3, head_size) unpack_qkv(info, query, key, value); } else { // Query: (batch_size, q_sequence_length, hidden_size) std::vector q_dims{ params.batch_size, params.q_sequence_length, num_heads, params.head_size}; query = info.add_instruction(make_op("reshape", {{"dims", q_dims}}), query); key = args[1]; if(params.qkv_fomat == qkv_fomat_t::kv_packed) { // Packed KV: (batch_size, kv_sequence_length, num_heads, 2, head_size) unpack_kv(info, key, value); } else { value = args[2]; if(params.qkv_fomat == qkv_fomat_t::q_k_v) { // Key: (batch_size, kv_sequence_length, hidden_size) // Value: (batch_size, kv_sequence_length, hidden_size_v) std::vector k_dims{ params.batch_size, params.kv_sequence_length, num_heads, params.head_size}; std::vector v_dims{params.batch_size, params.kv_sequence_length, num_heads, params.head_size_v}; key = info.add_instruction(make_op("reshape", {{"dims", k_dims}}), key); value = info.add_instruction(make_op("reshape", {{"dims", v_dims}}), value); } } } // Target shape: (batch_size, num_heads, sequence_length, head_size) std::vector perm{0, 2, 1, 3}; query = info.add_instruction(make_op("transpose", {{"permutation", perm}}), query); if(params.qkv_fomat != qkv_fomat_t::q_k_v_cross) { key = info.add_instruction(make_op("transpose", {{"permutation", perm}}), key); value = info.add_instruction(make_op("transpose", {{"permutation", perm}}), value); } float scale = 1 / std::sqrt(params.head_size); if(contains(info.attributes, "scale")) scale = parser.parse_value(info.attributes.at("scale")).at(); auto scale_literal = info.add_literal( migraphx::literal{migraphx::shape{query->get_shape().type()}, {scale}}); auto key_transposed = info.add_instruction(make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), key); auto result = info.add_instruction(make_op("dot"), query, key_transposed); result = info.add_common_op("mul", result, scale_literal); result = info.add_instruction(make_op("softmax", {{"axis", -1}}), result); result = info.add_instruction(make_op("dot"), result, value); result = info.add_instruction(make_op("transpose", {{"permutation", perm}}), result); result = info.add_instruction( make_op( "reshape", {{"dims", {params.batch_size, params.q_sequence_length, params.hidden_size_v}}}), result); return result; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_multinomial.cpp000066400000000000000000000145701510465702400225500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_multinomial : op_parser { std::vector operators() const { return {{"Multinomial"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { if(args.empty()) MIGRAPHX_THROW("PARSE_MULTINOMIAL: no arguments given"); int dtype = 6; if(contains(info.attributes, "dtype")) dtype = info.attributes.at("dtype").i(); shape::type_t output_type = get_type(dtype); size_t sample_size = 1; if(contains(info.attributes, "sample_size")) sample_size = info.attributes.at("sample_size").i(); else MIGRAPHX_THROW("PARSE_MULTINOMIAL: sample_size not given"); // Use logarithmic math to scale probabilities while avoiding division by very // small numbers. Scaling by the maximum makes very tiny ranges more // tractable; any constant factor gives equivalent distr. since the Multinomial op. // normalizes at runtime. // Subtract the per-batch maximum log-probability, making the per-batch max 0 auto maxes = info.add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), args[0]); auto cdf = info.add_common_op("sub", args[0], maxes); // Take the element-wise exponent to get probabilities in the range (0, 1] cdf = info.add_instruction(migraphx::make_op("exp"), cdf); // Compute the cumulative distribution function cdf = info.add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), cdf); instruction_ref seed_input; if(contains(info.attributes, "seed")) { float seed = info.attributes.at("seed").f(); migraphx::shape s{migraphx::shape::float_type, {1}}; std::vector data = {seed}; seed_input = info.add_literal(migraphx::literal(s, data)); } else { seed_input = info.add_instruction(migraphx::make_op("random_seed")); } instruction_ref randoms; shape s0 = args[0]->get_shape(); if(s0.dynamic()) { // Dynamic batch_size will be taken from args[0]. The input argument to this should // have a second dimension of sample_size. std::vector dyn_dim_set; dyn_dim_set.emplace_back(s0.dyn_dims().front()); dyn_dim_set.emplace_back(shape::dynamic_dimension{sample_size, sample_size}); // read the input dimensions auto dim_of = info.add_instruction(migraphx::make_op("dimensions_of", {{"end", 2}}), args[0]); // The next two operations insert the value sample_size into the second array position // make an argument of (1, 0) shape s(shape::int64_type, {2}); std::vector data1{1, 0}; auto l1 = info.add_literal(s, data1); auto batch_arg = info.add_instruction(migraphx::make_op("mul"), dim_of, l1); std::vector data2(2, 0); // make an argument of (0, sample_size) data2[1] = sample_size; auto l2 = info.add_literal(s, data2); auto alloc_shape = info.add_instruction(migraphx::make_op("add"), batch_arg, l2); // alloc_shape should contain the input-based shape dimensions as its values at runtime, // and its own shape is {2} // compile_shape is the shape used when compiling the Allocate op, and may be dynamic migraphx::shape compile_shape = migraphx::shape(s0.type(), {s0.dyn_dims().front(), {sample_size, sample_size}}); // Allocate on-device storage for the random values auto alloc = info.add_instruction( migraphx::make_op("allocate", {{"shape", to_value(compile_shape)}}), alloc_shape); randoms = info.add_instruction(migraphx::make_op("random_uniform"), seed_input, alloc); } else { // use literal. The array populated by random_uniform may have any shape, as long its // number of elements is batch_size * sample_size . size_t batch_size = s0.lens().front(); auto rand_dummy = info.add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {batch_size, sample_size}}, std::vector(batch_size * sample_size)}); randoms = info.add_instruction(migraphx::make_op("random_uniform"), seed_input, rand_dummy); } return info.add_instruction( migraphx::make_op("multinomial", {{"dtype", output_type}}), cdf, randoms); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_mxfixneuron.cpp000066400000000000000000000176331510465702400226030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_mxfixneuron : op_parser { std::vector operators() const { return {{"MXFixNeuron"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, onnx_parser::node_info info, std::vector args) const { const instruction_ref input = args.front(); instruction_ref tmp_in = input; const auto input_lens = input->get_shape().lens(); if(args.size() != 1) { MIGRAPHX_THROW("MXFixNeuron: must have only 1 input"); } int block_axis = info.attributes.at("axis").i(); block_axis = tune_axis(input->get_shape().ndim(), block_axis, "MXFixNeuron"); int block_size = info.attributes.at("block_size").i(); if(block_size != 32) { MIGRAPHX_THROW("MXFixNeuron: only block_size of 32 is supported"); } std::string element_dtype = info.attributes.at("element_dtype").s(); if(element_dtype != "fp4_e2m1") { MIGRAPHX_THROW("MXFixNeuron: only support MXFP4 type"); } int rounding_mode = info.attributes.at("rounding_mode").i(); if(rounding_mode != 2) { MIGRAPHX_THROW("MXFixNeuron: only round ties to even is supported"); } // make reduction axes for calculating block scales // tmp_lens != input_lens if runt block is padded auto tmp_lens = input_lens; auto block_dim = tmp_lens.at(block_axis); std::size_t block_padding = std::ceil(double(block_dim) / double(block_size)) * block_size - block_dim; // handle runt block by padding if(block_padding != 0) { std::vector pads_vec(2 * tmp_lens.size(), 0); pads_vec.at(block_axis + tmp_lens.size()) = block_padding; tmp_in = info.add_instruction(make_op("pad", {{"pads", pads_vec}}), tmp_in); tmp_lens = tmp_in->get_shape().lens(); } // reshape block dimension to {num_blocks, block_size} std::size_t num_blocks = tmp_lens.at(block_axis) / std::size_t(block_size); std::vector reduct_dims = tmp_lens; reduct_dims.at(block_axis) = block_size; reduct_dims.insert(reduct_dims.begin() + block_axis, num_blocks); instruction_ref reshape_ins = info.add_instruction(make_op("reshape", {{"dims", reduct_dims}}), tmp_in); // dynamic quantization for MX types: // V_k = fp32 vector input of block size k // B_k = pow(2, floor(log2(reduce_max(abs(V_k))))) # largest power of 2 less than V // X_k = block scale k = B_k / (largest power of 2 in fp4e2m1) = B_k / 4 auto abs_ins = info.add_instruction(make_op("abs"), reshape_ins); auto reduce_max_ins = info.add_instruction(make_op("reduce_max", {{"axes", {block_axis + 1}}}), abs_ins); auto log2_ins = info.add_instruction(make_op("log2"), reduce_max_ins); auto floor_ins = info.add_instruction(make_op("floor"), log2_ins); auto lit_2_ins = info.add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {2.f}}); auto broadcast_lit_2_ins = info.add_instruction( make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_2_ins); auto pow_ins = info.add_instruction(make_op("pow"), broadcast_lit_2_ins, floor_ins); auto lit_4_ins = info.add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {4.f}}); auto broadcast_lit_4_ins = info.add_instruction( make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_4_ins); auto block_scales_ins = info.add_instruction(make_op("div"), pow_ins, broadcast_lit_4_ins); // broadcast scales for use in quantizelinear block_scales_ins = info.add_instruction( make_op("multibroadcast", {{"out_lens", reduct_dims}}), block_scales_ins); block_scales_ins = info.add_instruction(make_op("reshape", {{"dims", tmp_lens}}), block_scales_ins); // if padded runt block do slicing if(tmp_lens != input_lens) { std::size_t slice_size = input_lens.at(block_axis); block_scales_ins = info.add_instruction( make_op("slice", {{"axes", {block_axis}}, {"starts", {0}}, {"ends", {slice_size}}}), block_scales_ins); } auto q_ins = info.add_instruction( make_op("quantizelinear", {{"out_type", migraphx::shape::float_type}}), input, block_scales_ins); // output is float_type // packing axis set to fastest dimension auto quantized_shape = q_ins->get_shape(); const auto& qs_strides = quantized_shape.strides(); if(qs_strides.empty()) { MIGRAPHX_THROW("MXFixNeuron: quantized_shape has no strides"); } int fast_axis = std::min_element(qs_strides.cbegin(), qs_strides.cend()) - qs_strides.cbegin(); bool odd_fast_axis = (quantized_shape.lens().at(fast_axis) % 2 == 1); if(odd_fast_axis) { // pad fastest dimension by 1 if it is odd std::vector padding(2 * quantized_shape.ndim(), 0); padding.at(fast_axis * 2 + 1) = 1; q_ins = info.add_instruction(make_op("pad", {{"pads", padding}}), q_ins); } auto pack_ins = info.add_instruction(make_op("pack_fp4", {{"axis", fast_axis}}), q_ins); // output is fp4x2_type auto unpack_ins = info.add_instruction(make_op("unpack_fp4", {{"axis", fast_axis}}), pack_ins); // output is fp8e4m3fn_type if(odd_fast_axis) { // slice off padded values unpack_ins = info.add_instruction(make_op("slice", {{"axes", {fast_axis}}, {"starts", {0}}, {"ends", {quantized_shape.lens().at(fast_axis)}}}), unpack_ins); } return info.add_instruction(make_op("dequantizelinear"), unpack_ins, block_scales_ins); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_nonmaxsuppression.cpp000066400000000000000000000037611510465702400240310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_nonmaxsuppression : op_parser { std::vector operators() const { return {{"NonMaxSuppression", "nonmaxsuppression"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { auto op = parser.load(opd.op_name, info); op.from_value({{"use_dyn_output", parser.use_dyn_output}}); return info.add_instruction(op, args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_nonzero.cpp000066400000000000000000000063451510465702400217110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { template static std::vector nonzero_indices(const std::vector& data) { std::vector indices; for(std::size_t i = 0; i < data.size(); ++i) { if(not float_equal(data[i], 0)) indices.push_back(i); } return indices; } struct parse_nonzero : op_parser { std::vector operators() const { return {{"NonZero"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { migraphx::argument data_arg = args.back()->eval(); if(data_arg.empty()) { return info.add_instruction(make_op("nonzero"), args); } else { std::vector indices; data_arg.visit([&](auto val) { using val_type = std::remove_cv_t; std::vector vec_data; vec_data.assign(val.begin(), val.end()); indices = nonzero_indices(vec_data); }); shape in_s = args[0]->get_shape(); shape out_s{shape::int64_type, {in_s.lens().size(), indices.size()}}; std::vector out_data(out_s.elements()); for(std::size_t i = 0; i < indices.size(); ++i) { auto idx = in_s.multi(indices[i]); for(std::size_t j = 0; j < in_s.lens().size(); ++j) { out_data[out_s.index({j, i})] = idx[j]; } } return info.add_literal(literal(out_s, out_data)); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_onehot.cpp000066400000000000000000000045731510465702400215140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_onehot : op_parser { std::vector operators() const { return {{"OneHot"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, onnx_parser::node_info info, std::vector args) const { int64_t axis = -1; if(contains(info.attributes, "axis")) { axis = info.attributes.at("axis").i(); } migraphx::argument depth_arg = args[1]->eval(); if(depth_arg.empty()) { return info.add_instruction(make_op("onehot", {{"axis", axis}}), args); } else { size_t depth_val = depth_arg.at(); return info.add_instruction(make_op("onehot", {{"axis", axis}, {"depth", depth_val}}), {args[0], args[2]}); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_pad.cpp000066400000000000000000000300061510465702400207520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static void calc_reflect_indices(std::vector& indices, const int64_t num_dims) { int k = 0; bool reversed = false; // in reflect padding, if the num_pads > num_dims, // compute the extra pad indices periodically, ex. ( 1, 2, 3, 2, 1, 0) for(int& idx : indices) { if(k == num_dims - 1) reversed = true; if(k == 0) reversed = false; if(reversed) k--; else k++; idx = k; } } static instruction_ref reflect_pad(const onnx_parser::node_info& info, const std::vector& pads, instruction_ref input) { size_t num_dims = input->get_shape().ndim(); assert(num_dims * 2 == pads.size()); std::vector ldims(pads.begin(), pads.begin() + num_dims); std::vector rdims(pads.begin() + num_dims, pads.end()); std::vector axes(num_dims); std::iota(axes.begin(), axes.end(), int64_t{0}); // iterate over dimensions, starting from lowest dimension for(int64_t i = num_dims - 1; i >= 0; i--) { auto axis = i; auto lcount = ldims.at(i); auto rcount = rdims.at(i); if(lcount == 0 and rcount == 0) // no padding for current dim continue; // calculate starts and ends for each iteration since shape may change std::vector dims = input->get_shape().lens(); std::vector starts(axes.size(), 0); std::vector ends(dims.begin(), dims.end()); std::vector slices; auto starts_it = starts.begin() + i; auto ends_it = ends.begin() + i; auto dims_it = dims.begin() + i; std::vector l_indices(lcount); std::vector r_indices(rcount); // compute slice indices in a periodic fashion calc_reflect_indices(l_indices, *dims_it); calc_reflect_indices(r_indices, *dims_it); for(int idx : l_indices) { *starts_it = idx; *ends_it = *starts_it + 1; slices.push_back(info.add_instruction( make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}), input)); } // when padding on the left side, the outermost pad should be at the beginning std::reverse(slices.begin(), slices.end()); slices.push_back(input); for(int idx : r_indices) { *starts_it = *dims_it - idx - 1; *ends_it = *starts_it + 1; slices.push_back(info.add_instruction( make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}), input)); } input = info.add_instruction(make_op("concat", {{"axis", axis}}), slices); } return input; } static instruction_ref edge_pad(const onnx_parser::node_info& info, const std::vector& pads, instruction_ref input) { size_t num_dims = input->get_shape().ndim(); assert(num_dims * 2 == pads.size()); std::vector ldims(pads.begin(), pads.begin() + num_dims); std::vector rdims(pads.begin() + num_dims, pads.end()); std::vector axes(num_dims); std::iota(axes.begin(), axes.end(), int64_t{0}); // iterate over dimensions, starting from lowest dimension for(int64_t i = num_dims - 1; i >= 0; i--) { auto axis = i; auto lcount = ldims.at(i); auto rcount = rdims.at(i); if(lcount == 0 and rcount == 0) // no padding for current dim continue; // calculate starts and ends for each iteration since shape may change std::vector dims = input->get_shape().lens(); std::vector starts(axes.size(), 0); std::vector ends(dims.begin(), dims.end()); std::vector slices; slices.reserve(lcount + rcount + 1); // left side starts[i] = 0; ends[i] = 1; auto ins = info.add_instruction( make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}), input); for(int64_t j = 0; j < lcount; j++) { slices.push_back(ins); } // original input slices.push_back(input); // right side starts[i] = dims[i] - 1; ends[i] = dims[i]; ins = info.add_instruction( make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}), input); for(size_t j = 0; j < rcount; j++) { slices.push_back(ins); } // concat all together input = info.add_instruction(make_op("concat", {{"axis", axis}}), slices); } return input; } struct parse_pad : op_parser { std::vector operators() const { return {{"Pad"}}; } std::string parse_mode(const onnx_parser::node_info& info, const std::vector& args) const { if(contains(info.attributes, "mode")) { auto mode = info.attributes.at("mode").s(); if(mode == "reflect" or mode == "edge") { if(args.front()->get_shape().dynamic()) { MIGRAPHX_THROW("PARSE_PAD: " + mode + " padding with dynamic shape not supported"); } } else if(mode != "constant" and mode != "edge") { MIGRAPHX_THROW("PARSE_PAD: MIGraphX currently only supports constant, reflect, and " "edge padding"); } return mode; } else { // default mode return "constant"; } } std::vector parse_pads(const onnx_parser::node_info& info, const std::vector& args) const { std::vector pads{}; if(args.size() >= 2) { auto pad_arg = args.at(1)->eval(); check_arg_empty(pad_arg, "PARSE_PAD: `pads` input must be constant"); pad_arg.visit([&](auto v) { pads.assign(v.begin(), v.end()); }); } else if(contains(info.attributes, "pads")) { auto&& pad_vals = info.attributes.at("pads").ints(); pads = std::vector(pad_vals.begin(), pad_vals.end()); } else { MIGRAPHX_THROW("PARSE_PAD: `pads` must be available"); } return pads; } float parse_constant_value(const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { float value = 0.0f; if(args.size() >= 3 and args.at(2)->get_shape().elements() >= 1) { auto val_ins = args.at(2); if(not val_ins->can_eval()) { MIGRAPHX_THROW("PARSE_PAD: input `value` must be constant"); } auto val_arg = val_ins->eval(); if(val_arg.get_shape().elements() != 1) { MIGRAPHX_THROW("PARSE_PAD: `value` should contain only one element"); } value = val_arg.at(); } else if(contains(info.attributes, "value")) { value = parser.parse_value(info.attributes.at("value")).at(); } return value; } std::vector parse_axes(const std::vector& args, bool is_constant_mode) const { std::vector axes{}; // axes is 3rd or 4th, depending on constant mode auto pos = is_constant_mode ? 4 : 3; if(args.size() >= pos) { auto axes_arg = args.at(pos - 1)->eval(); check_arg_empty(axes_arg, "PARSE_PAD: variable `axes` input not supported"); axes_arg.visit([&](auto v) { axes.assign(v.begin(), v.end()); }); } return axes; } std::vector calculate_pads_with_axes(const std::vector& pads, const std::vector& axes, size_t input_rank) const { size_t num_axes = axes.size(); if(num_axes * 2 != pads.size()) { MIGRAPHX_THROW("PARSE_PAD: number of elements of pads should be equal to 2 * " "number of elements of axes"); } std::vector new_pads(input_rank * 2); for(size_t idx{0}; idx < num_axes; ++idx) { // axis can be negative int64_t axis = axes[idx] < 0 ? input_rank + axes[idx] : axes[idx]; // pad format is x1_begin, x2_begin, ... , x3_end, x4_end new_pads[axis] = pads[idx]; new_pads[axis + input_rank] = pads[idx + num_axes]; } return new_pads; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { std::vector pads = parse_pads(info, args); // check if padding is actually being done (at least one value is nonzero) if(std::all_of(pads.begin(), pads.end(), [](const int& i) { return i == 0; })) { return info.add_instruction(make_op("identity"), args.front()); } std::string mode = parse_mode(info, args); bool is_constant_mode = mode == "constant"; float value = is_constant_mode ? parse_constant_value(parser, info, args) : 0.0f; std::vector axes = parse_axes(args, is_constant_mode); size_t input_rank = args.front()->get_shape().ndim(); if(not axes.empty()) { pads = calculate_pads_with_axes(pads, axes, input_rank); } if(pads.size() != input_rank * 2) { MIGRAPHX_THROW("PARSE_PAD: number of elements of pads should be equal to 2 * " "input rank"); } if(mode == "reflect") { return reflect_pad(info, pads, args.front()); } if(mode == "edge") { return edge_pad(info, pads, args.front()); } return info.add_instruction(migraphx::make_op("pad", {{"pads", pads}, {"value", value}}), args.front()); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_pooling.cpp000066400000000000000000000041141510465702400216560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_pooling : op_parser { std::vector operators() const { return { {"AveragePool", "average"}, {"GlobalAveragePool", "average"}, {"GlobalMaxPool", "max"}, {"MaxPool", "max"}, {"LpPool", "lpnorm"}, {"GlobalLpPool", "lpnorm"}, }; } instruction_ref parse(const op_desc& opd, const onnx_parser& /*parser*/, onnx_parser::node_info info, std::vector args) const { return add_pooling_op(opd, std::move(info), args[0]); }; }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_pow.cpp000066400000000000000000000074331510465702400210230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static auto compute_type(shape::type_t t1, shape::type_t t2) { const static std::unordered_map op_order = {{shape::int8_type, 1}, {shape::uint8_type, 2}, {shape::int16_type, 3}, {shape::uint16_type, 4}, {shape::int32_type, 5}, {shape::uint32_type, 6}, {shape::int64_type, 7}, {shape::uint64_type, 8}, {shape::half_type, 9}, {shape::float_type, 10}, {shape::double_type, 11}}; int it1 = t1; int it2 = t2; if(not contains(op_order, it1) or not contains(op_order, it2)) { MIGRAPHX_THROW("PARSE_POW: Input data type not supported!"); } return ((op_order.at(it1) >= op_order.at(it2)) ? t1 : t2); } struct parse_pow : op_parser { std::vector operators() const { return {{"Pow"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { auto type_base = args[0]->get_shape().type(); auto type_exponent = args[1]->get_shape().type(); auto type_compute = compute_type(type_base, type_exponent); if(type_compute != type_base) { args[0] = info.add_instruction(make_op("convert", {{"target_type", type_compute}}), args[0]); } if(type_compute != type_exponent) { args[1] = info.add_instruction(make_op("convert", {{"target_type", type_compute}}), args[1]); } auto ret = info.add_broadcastable_binary_op("pow", args[0], args[1]); if(type_compute != type_base) { ret = info.add_instruction(make_op("convert", {{"target_type", type_base}}), ret); } return ret; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_prefix_scan.cpp000066400000000000000000000057731510465702400225240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static instruction_ref parse_prefix_scan_oper(const std::string& op_name, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) { migraphx::argument in = args[1]->eval(); check_arg_empty(in, "PARSE_PREFIX_SCAN: axis - dynamic shape not supported"); std::vector axis_in; in.visit([&](auto input) { axis_in.assign(input.begin(), input.end()); }); int64_t axis = axis_in[0]; bool exclusive = false; bool reverse = false; if(contains(info.attributes, "exclusive")) { exclusive = parser.parse_value(info.attributes.at("exclusive")).at(); } if(contains(info.attributes, "reverse")) { reverse = parser.parse_value(info.attributes.at("reverse")).at(); } return info.add_instruction( make_op(op_name, {{"axis", axis}, {"exclusive", exclusive}, {"reverse", reverse}}), args[0]); } struct parse_prefix_scan_op : op_parser { std::vector operators() const { return {{"CumSum", "prefix_scan_sum"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { return parse_prefix_scan_oper(opd.op_name, parser, std::move(info), std::move(args)); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_qlinearbinary.cpp000066400000000000000000000140001510465702400230420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /* ********************************************************************************* * Reference: see QLinearAdd, QLinearMul in * * https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md * ********************************************************************************* com.microsoft.QLinearAdd Performs element-wise binary addition on 8 bit data types (with Numpy-style broadcasting support). C = (A_scale * (A - A_zero_point) + B_scale * (B - B_zero_point))/C_scale + C_zero_point Version This version of the operator has been available since version 1 of the 'com.microsoft' operator set. com.microsoft.QLinearMul Performs element-wise binary multiplication on 8 bit data types (with Numpy-style broadcasting support). C = ((A - A_zero_point) * (B - B_zero_point)) * (A_scale * B_scale)/C_scale + C_zero_point Version This version of the operator has been available since version 1 of the 'com.microsoft' operator set. General definition of binary QLinear* ops: Inputs (7 - 8) A : T First operand. A_scale : tensor(float) Input A's scale. It's a scalar, which means a per-tensor/layer quantization. A_zero_point (optional) : T Input A zero point. Default value is 0 if it's not specified. It's a scalar, which means a per-tensor/layer quantization. B : T Second operand. B_scale : tensor(float) Input B's scale. It's a scalar, which means a per-tensor/layer quantization. B_zero_point (optional) : T Input B zero point. Default value is 0 if it's not specified. It's a scalar, which means a per-tensor/layer quantization. C_scale : tensor(float) Output scale. It's a scalar, which means a per-tensor/layer quantization. C_zero_point (optional) : T Output zero point. Default value is 0 if it's not specified. It's a scalar, which means a per-tensor/layer quantization. Outputs C : T Result, has same element type as two inputs Type Constraints T : tensor(uint8), tensor(int8) Constrain input and output types to 8 bit signed and unsigned tensors. */ struct parse_qlinearbinary : op_parser { std::vector operators() const { return {{"QLinearAdd", "add"}, {"QLinearMul", "mul"}}; } // basic type checking for binary QLinear Operator void check_inputs(const std::vector& args, const std::string& onnx_name) const { if(args.size() < 7) MIGRAPHX_THROW(onnx_name + ": missing inputs"); const auto& in_a = args[0]; const auto& in_b = args[3]; auto sh_a = in_a->get_shape(); auto sh_b = in_b->get_shape(); auto type_a = sh_a.type(); auto type_b = sh_b.type(); if(type_a != migraphx::shape::int8_type and type_a != migraphx::shape::uint8_type) MIGRAPHX_THROW(onnx_name + ": unsupported input type"); if(type_b != migraphx::shape::int8_type and type_b != migraphx::shape::uint8_type) MIGRAPHX_THROW(onnx_name + ": unsupported input type"); if(type_a != type_b) MIGRAPHX_THROW(onnx_name + ": mismatched input types"); } instruction_ref parse(const op_desc& opd, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, const std::vector& args) const { check_inputs(args, opd.onnx_name); // A const auto& in_a = args[0]; const auto& in_scale_a = args[1]; const auto& in_zero_pt_a = args[2]; auto dquant_a = bcast_qdq_instr("dequantizelinear", in_a, in_scale_a, in_zero_pt_a, info); // B const auto& in_b = args[3]; const auto& in_scale_b = args[4]; const auto& in_zero_pt_b = args[5]; auto dquant_b = bcast_qdq_instr("dequantizelinear", in_b, in_scale_b, in_zero_pt_b, info); // C = op(A, B) auto out_c = info.add_common_op(opd.op_name, dquant_a, dquant_b); const auto& in_scale_c = args[6]; // zero_pt for C is supplied as the last optional argument.. if(args.size() == 8) return (bcast_qdq_instr("quantizelinear", out_c, in_scale_c, args[7], info)); // if no zero_pt: just broadcast the scale.. auto bcast_scale_c = bcast_scalar_instr(out_c->get_shape(), in_scale_c, info); return (info.add_instruction(migraphx::make_op("quantizelinear"), out_c, bcast_scale_c)); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_qlinearconcat.cpp000066400000000000000000000103431510465702400230330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_qlinearconcat : op_parser { std::vector operators() const { return {{"QLinearConcat"}}; } // basic type checking for QLinearConcat Operator void check_inputs(const std::vector& args) const { auto args_size = args.size(); // at least 5 input tensors: // 1. is Y_scale: tensor(float) // 2. is Y_zero_pont: tensor(uint8)/tensor(int8) // remaining is a sequence of : // 3. Tensor: tensor(uint8)/tensor(int8) // 4. Scale: tensor(float), // 5. ZeroPoint: tensor(uint8)/tensor(int8) tensors // Size can be 5, 8, 11 ... if((args_size < 5) or ((args_size - 2) % 3 != 0)) MIGRAPHX_THROW("QLINEARCONCAT: missing inputs"); auto y_zp = args[1]; auto y_zp_type = y_zp->get_shape().type(); if(y_zp_type != migraphx::shape::int8_type and y_zp_type != migraphx::shape::uint8_type) MIGRAPHX_THROW("QLINEARCONCAT: unsupported output type"); auto t0_type = args[2]->get_shape().type(); if(t0_type != migraphx::shape::int8_type and t0_type != migraphx::shape::uint8_type) MIGRAPHX_THROW("QLINEARCONCAT: unsupported input type"); for(auto idx = 2; idx < args.size(); idx += 3) { if((args[idx]->get_shape().type() != t0_type) or (args[idx + 2]->get_shape().type() != t0_type)) { MIGRAPHX_THROW("QLINEARCONCAT: mismatching input types"); } } } instruction_ref parse(const op_desc& /* opd */, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { check_inputs(args); if(not contains(info.attributes, "axis")) MIGRAPHX_THROW("QLINEARCONCAT: missing axis attribute"); auto axis = parser.parse_value(info.attributes.at("axis")).template at(); std::vector tmp; for(auto idx = 2; idx < args.size(); idx += 3) { auto data_tensor = args[idx]; auto scale = args[idx + 1]; auto zero_pt = args[idx + 2]; tmp.push_back(bcast_qdq_instr("dequantizelinear", data_tensor, scale, zero_pt, info)); } auto y = info.add_instruction(migraphx::make_op("concat", {{"axis", axis}}), tmp); auto y_scale = args[0]; auto y_zero_pt = args[1]; return bcast_qdq_instr("quantizelinear", y, y_scale, y_zero_pt, info); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_qlinearconv.cpp000066400000000000000000000215301510465702400225310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /* ********************************************************************************* * Reference: see QLinearConv in * * https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md * ********************************************************************************* com.microsoft.QLinearConv Version This version of the operator has been available since version 1 of the 'com.microsoft' operator set. ATTRIBUTES: auto_pad : string channels_last : int dilations : list of ints group : int kernel_shape : list of ints pads : list of ints strides : list of ints INPUTS (8 - 9): x : T1 x_scale : tensor(float) x_zero_point : T1 w : T2 w_scale : tensor(float) w_zero_point : T2 y_scale : tensor(float) y_zero_point : T3 B (optional) : T4 OUTPUTS: y : T3 Type Constraints: T1 : tensor(int8), tensor(uint8) T2 : tensor(int8), tensor(uint8) T3 : tensor(int8), tensor(uint8) T4 : tensor(int32) More details also at: https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__QLinearConv.html */ struct parse_qlinearconv : op_parser { std::vector operators() const { return {{"QLinearConv"}}; } // basic type checking for QLinearConv Operator void check_inputs(const std::vector& inp_arg) const { if(inp_arg.size() < 8) MIGRAPHX_THROW("QLINEARCONV: missing inputs"); const instruction_ref& in_x = inp_arg[0]; const instruction_ref& in_scale_x = inp_arg[1]; const instruction_ref& in_w = inp_arg[3]; const instruction_ref& in_scale_w = inp_arg[4]; const instruction_ref& in_scale_y = inp_arg[6]; auto sh_x = in_x->get_shape(); auto sh_w = in_w->get_shape(); auto type_x = sh_x.type(); auto type_w = sh_w.type(); assert(in_x->get_shape().ndim() > 2); if(type_x != shape::int8_type and type_x != shape::uint8_type) MIGRAPHX_THROW("QLINEARCONV: unsupported input type"); if(type_w != shape::int8_type and type_w != shape::uint8_type) MIGRAPHX_THROW("QLINEARCONV: unsupported weight type"); if(in_scale_x->get_shape().type() != shape::float_type) MIGRAPHX_THROW("QLINEARCONV x scale type should be float"); if(in_scale_w->get_shape().type() != shape::float_type) MIGRAPHX_THROW("QLINEARCONV: wt scale type should be float"); if(in_scale_y->get_shape().type() != shape::float_type) MIGRAPHX_THROW("QLINEARCONV: y scale type should be float"); if(inp_arg.size() > 8 and inp_arg[8]->get_shape().type() != shape::int32_type) MIGRAPHX_THROW("QLINEARCONV y bias should be int32"); } // process all attributes of QLinearConv Operator.. value process_attributes(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { value values; const auto& in_x = args[0]; const auto& wt = args[3]; size_t kdims = in_x->get_shape().ndim() - 2; check_padding_mode(info, opd.onnx_name); values["stride"] = std::vector(kdims, 1); values["dilation"] = std::vector(kdims, 1); values["padding"] = std::vector(kdims, 0); values["group"] = 1; if(contains(info.attributes, "group")) values["group"] = parser.parse_value(info.attributes.at("group")).template at(); if(contains(info.attributes, "strides")) { std::vector st; copy(info.attributes.at("strides").ints(), std::back_inserter(st)); check_attr_sizes(kdims, st.size(), "QLINEARCONV: inconsistent strides"); values["stride"] = st; } if(contains(info.attributes, "dilations")) { std::vector dil; copy(info.attributes.at("dilations").ints(), std::back_inserter(dil)); check_attr_sizes(kdims, dil.size(), "QLINEARCONV: inconsistent dilations"); values["dilation"] = dil; } if(contains(info.attributes, "pads")) { std::vector pads; copy(info.attributes.at("pads").ints(), std::back_inserter(pads)); check_attr_sizes(kdims, pads.size() / 2, "QLINEARCONV: inconsistent padding"); values["padding"] = pads; } else if(contains(info.attributes, "auto_pad")) { auto in_lens = in_x->get_shape().lens(); auto wt_lens = wt->get_shape().lens(); std::vector k_lens(wt_lens.begin() + 2, wt_lens.end()); std::vector pads = values["padding"].to_vector(); cal_auto_padding_size( info, values, k_lens, values["dilation"].to_vector(), in_lens, pads); values["padding"] = pads; } recalc_conv_attributes(values, kdims); return values; } instruction_ref add_bias_to_conv(const instruction_ref bias_arg, const instruction_ref conv_instr, const onnx_parser::node_info& info) const { auto conv_sh = conv_instr->get_shape(); auto conv_lens = conv_sh.lens(); auto conv_type = conv_sh.type(); auto broadcast_bias = info.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv_lens}}), bias_arg); auto f_bias = info.add_instruction(make_op("convert", {{"target_type", conv_type}}), broadcast_bias); return info.add_instruction(migraphx::make_op("add"), conv_instr, f_bias); }; instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { check_inputs(args); auto values = process_attributes(opd, parser, info, args); // input: quantized x, scale, zero_pt const instruction_ref& in_x = args[0]; const instruction_ref& in_scale_x = args[1]; const instruction_ref& in_zero_pt_x = args[2]; // input: quantized weights, scale, zero_pt const instruction_ref& in_w = args[3]; const instruction_ref& in_scale_w = args[4]; const instruction_ref& in_zero_pt_w = args[5]; // for the dequantized output y: scale & zero_pt const instruction_ref& in_scale_y = args[6]; const instruction_ref& in_zero_pt_y = args[7]; auto dquant_x = bcast_qdq_instr("dequantizelinear", in_x, in_scale_x, in_zero_pt_x, info); auto dquant_w = bcast_qdq_instr("dequantizelinear", in_w, in_scale_w, in_zero_pt_w, info); auto conv_op = migraphx::make_op("convolution", values); auto conv_x_w = info.add_instruction(conv_op, dquant_x, dquant_w); // Biases, if any.. : is an optional argument. if(args.size() > 8) conv_x_w = add_bias_to_conv(args[8], conv_x_w, info); return bcast_qdq_instr("quantizelinear", conv_x_w, in_scale_y, in_zero_pt_y, info); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_qlinearmatmul.cpp000066400000000000000000000175241510465702400230730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /* ********************************************************************************* * Reference: see QLinearMatMul in * * https://onnx.ai/onnx/operators/onnx__QLinearMatMul.html * ********************************************************************************* Matrix product that behaves like numpy.matmul: https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.matmul.html. It consumes two quantized input tensors, their scales and zero points, scale and zero point of output, and computes the quantized output. The quantization formula is y = saturate((x / y_scale) + y_zero_point). For (x / y_scale), it is rounding to nearest ties to even. Refer to https://en.wikipedia.org/wiki/Rounding for details. Scale and zero point must have same shape. They must be either scalar (per tensor) or N-D tensor (per row for ‘a’ and per column for ‘b’). Scalar refers to per tensor quantization whereas N-D refers to per row or per column quantization. If the input is 2D of shape [M, K] then zero point and scale tensor may be an M element vector [v_1, v_2, …, v_M] for per row quantization and K element vector of shape [v_1, v_2, …, v_K] for per column quantization. If the input is N-D tensor with shape [D1, D2, M, K] then zero point and scale tensor may have shape [D1, D2, M, 1] for per row quantization and shape [D1, D2, 1, K] for per column quantization. Production must never overflow, and accumulation may overflow if and only if in 32 bits. Inputs a (heterogeneous) - T1: N-dimensional quantized matrix a a_scale (heterogeneous) - tensor(float): scale of quantized input a a_zero_point (heterogeneous) - T1: zero point of quantized input a b (heterogeneous) - T2: N-dimensional quantized matrix b b_scale (heterogeneous) - tensor(float): scale of quantized input b b_zero_point (heterogeneous) - T2: zero point of quantized input b y_scale (heterogeneous) - tensor(float): scale of quantized output y y_zero_point (heterogeneous) - T3: zero point of quantized output y Outputs y (heterogeneous) - T3: Quantized matrix multiply results from a * b Type Constraints T1 in ( tensor(int8), tensor(uint8) ): Constrain input a and its zero point data type to 8-bit integer tensor. T2 in ( tensor(int8), tensor(uint8) ): Constrain input b and its zero point data type to 8-bit integer tensor. T3 in ( tensor(int8), tensor(uint8) ): Constrain output y and its zero point data type to 8-bit integer tensor. */ struct parse_qlinearmatmul : op_parser { std::vector operators() const { return {{"QLinearMatMul"}}; } // basic type checking for QLinearMatMul Operator void check_inputs(const std::vector& args) const { if(args.size() < 8) MIGRAPHX_THROW("QLINEARMATMUL: missing inputs"); const auto& in_a = args[0]; const auto& in_b = args[3]; auto sh_a = in_a->get_shape(); auto sh_b = in_b->get_shape(); auto type_a = sh_a.type(); auto type_b = sh_b.type(); if(type_a != migraphx::shape::int8_type and type_a != migraphx::shape::uint8_type) MIGRAPHX_THROW("QLINEARMATMUL: unsupported input type"); if(type_b != migraphx::shape::int8_type and type_b != migraphx::shape::uint8_type) MIGRAPHX_THROW("QLINEARMATMUL: unsupported input type"); auto lens_a = sh_a.lens(); auto lens_b = sh_b.lens(); size_t dim_a = lens_a.size(); size_t dim_b = lens_b.size(); if(dim_a == 0 or dim_b == 0) MIGRAPHX_THROW("QLINEARMATMUL: empty input"); // broadcast supported if either is 1-D -- the other can be a 2-D tensor. // if it is 1-D, just prepend/append that lens and check further constraints.. if(dim_a == 1) { lens_a.insert(lens_a.begin(), 1); dim_a++; } if(dim_b == 1) { lens_b.push_back(1); dim_b++; } // 2-D or higher-order mat mul if(dim_a != dim_b or *lens_a.rbegin() != *(lens_b.rbegin() + 1) or not std::equal(lens_a.rbegin() + 2, lens_a.rend(), lens_b.rbegin() + 2, lens_b.rend())) MIGRAPHX_THROW("QLINEARMATMUL: mismatched input dimensions"); if(migraphx::any_of({args[1], args[2], args[4], args[5]}, [](auto arg) { return not arg->get_shape().scalar(); })) MIGRAPHX_THROW("QLINEARMATMUL: unsupported row/column quantization"); } instruction_ref parse(const op_desc& /* opd */, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, const std::vector& args) const { check_inputs(args); // A const auto& in_a = args[0]; const auto& in_scale_a = args[1]; const auto& in_zero_pt_a = args[2]; auto dquant_a = bcast_qdq_instr("dequantizelinear", in_a, in_scale_a, in_zero_pt_a, info); // B const auto& in_b = args[3]; const auto& in_scale_b = args[4]; const auto& in_zero_pt_b = args[5]; auto dquant_b = bcast_qdq_instr("dequantizelinear", in_b, in_scale_b, in_zero_pt_b, info); bool is_a_prepended = false; bool is_b_appended = false; // un-squeeze either tensor if 1-D. if(in_a->get_shape().ndim() == 1) { is_a_prepended = true; dquant_a = info.add_instruction(make_op("unsqueeze", {{"axes", {0}}}), dquant_a); } if(in_b->get_shape().ndim() == 1) { is_b_appended = true; dquant_b = info.add_instruction(make_op("unsqueeze", {{"axes", {1}}}), dquant_b); } // Y = A * B auto out_y = info.add_instruction(migraphx::make_op("dot"), dquant_a, dquant_b); // squeeze just once if necessary.. not twice. if(is_a_prepended) out_y = info.add_instruction(make_op("squeeze", {{"axes", {0}}}), out_y); else if(is_b_appended) out_y = info.add_instruction(make_op("squeeze", {{"axes", {1}}}), out_y); const auto& scale_y = args[6]; const auto& zero_pt_y = args[7]; return bcast_qdq_instr("quantizelinear", out_y, scale_y, zero_pt_y, info); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_qlinearpooling.cpp000066400000000000000000000106721510465702400232400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /* ********************************************************************************* * Reference: see QLinearAveragePool and QLinearGlobalAveragePool in * * github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md * ********************************************************************************* */ struct parse_qlinearpooling : op_parser { std::vector operators() const { return {{"QLinearGlobalAveragePool", "average"}, {"QLinearAveragePool", "average"}}; } void check_inputs(const op_desc& opd, const std::vector& args) const { const auto& in_x = args[0]; const auto onnx_name = opd.onnx_name; if(in_x->get_shape().ndim() <= 2) MIGRAPHX_THROW(onnx_name + ": input dimensions too small"); auto type_x = in_x->get_shape().type(); if(type_x != migraphx::shape::int8_type and type_x != migraphx::shape::uint8_type) MIGRAPHX_THROW(onnx_name + ": unsupported input type"); const auto& zero_pt_x = args[2]; if(type_x != zero_pt_x->get_shape().type()) MIGRAPHX_THROW(onnx_name + ": mismatched type: input zero point"); if(args.size() == 5) { const auto& zero_pt_y = args[4]; if(type_x != zero_pt_y->get_shape().type()) MIGRAPHX_THROW(onnx_name + ": mismatched type: output zero point"); } } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { if(contains(info.attributes, "channel_last")) { int channels_last = parser.parse_value(info.attributes.at("channels_last")).template at(); if(channels_last != 0) MIGRAPHX_THROW(opd.onnx_name + ": channels_last (N x D1..Dn x C) is not supported"); } check_inputs(opd, args); // Input: X const auto& in_x = args[0]; const auto& scale_x = args[1]; const auto& zero_pt_x = args[2]; auto dquant_x = bcast_qdq_instr("dequantizelinear", in_x, scale_x, zero_pt_x, info); // Output Y = pooling_op(X) auto out_y = add_pooling_op(opd, info, dquant_x); const auto& in_scale_y = args[3]; // zero_pt for Y is supplied as the last optional argument.. if(args.size() == 5) return (bcast_qdq_instr("quantizelinear", out_y, in_scale_y, args[4], info)); // if no zero_pt: just broadcast the scale.. auto bcast_scale_y = bcast_scalar_instr(out_y->get_shape(), in_scale_y, info); return (info.add_instruction(migraphx::make_op("quantizelinear"), out_y, bcast_scale_y)); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_qlinearunary.cpp000066400000000000000000000130151510465702400227210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /* ********************************************************************************* * Reference: see QLinearSigmoid, QLinearLeakyRelu in * * https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md * ********************************************************************************* com.microsoft.QLinearSigmoid QLinearSigmoid takes quantized input data (Tensor), and quantize parameter for output, and produces one output data (Tensor) where the function f(x) = quantize(Sigmoid(dequantize(x))), is applied to the data tensor elementwise. Where the function Sigmoid(x) = 1 / (1 + exp(-x)) Version This version of the operator has been available since version 1 of the 'com.microsoft' operator set. ***************************************************************************************************** com.microsoft.QLinearLeakyRelu QLinearLeakyRelu takes quantized input data (Tensor), an argument alpha, and quantize parameter for output, and produces one output data (Tensor) where the function f(x) = quantize(alpha * dequantize(x)) for dequantize(x) < 0, f(x) = quantize(dequantize(x)) for dequantize(x) >= 0, is applied to the data tensor elementwise. Version This version of the operator has been available since version 1 of the 'com.microsoft' operator set. Attributes alpha : float Coefficient of leakage. ****************************************************************************************************** Generic input layout of QLinear unary operators: Inputs (4 - 5) X : T Input tensor X_scale : tensor(float) Input X's scale. It's a scalar, which means a per-tensor/layer quantization. X_zero_point (optional) : T Input X's zero point. Default value is 0 if it's not specified. It's a scalar, which means a per-tensor/layer quantization. Y_scale : tensor(float) Output Y's scale. It's a scalar, which means a per-tensor/layer quantization. Y_zero_point (optional) : T Output Y's zero point. Default value is 0 if it's not specified. It's a scalar, which means a per-tensor/layer quantization. Outputs Y : T Output tensor Type Constraints T : tensor(uint8), tensor(int8) Constrain input and output types to 8 bit tensors. */ struct parse_qlinearunary : op_parser { std::vector operators() const { return {{"QLinearSigmoid", "sigmoid"}, {"QLinearLeakyRelu", "leaky_relu"}}; } void check_inputs(const op_desc& opd, const std::vector& args) const { if(args.size() < 4) MIGRAPHX_THROW(opd.op_name + ": missing inputs"); const auto& in_x = args[0]; auto sh_x = in_x->get_shape(); auto type_x = sh_x.type(); if(type_x != migraphx::shape::int8_type and type_x != migraphx::shape::uint8_type) MIGRAPHX_THROW(opd.op_name + ": unsupported input type"); } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { check_inputs(opd, args); // X const auto& in_x = args[0]; const auto& in_scale_x = args[1]; const auto& in_zero_pt_x = args[2]; auto dquant_x = bcast_qdq_instr("dequantizelinear", in_x, in_scale_x, in_zero_pt_x, info); // Y = (op(dequantizelinear(x)) auto op = parser.load(opd.op_name, info); auto y = info.add_instruction(op, dquant_x); const auto& in_scale_y = args[3]; // zero_pt for Y is supplied as the last optional argument.. if(args.size() == 5) return (bcast_qdq_instr("quantizelinear", y, in_scale_y, args[4], info)); // if no zero_pt: just broadcast the scale.. auto bcast_scale_sigm = bcast_scalar_instr(y->get_shape(), in_scale_y, info); return (info.add_instruction(migraphx::make_op("quantizelinear"), y, bcast_scale_sigm)); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_quantizelinear.cpp000066400000000000000000000150301510465702400232410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_quantizelinear : op_parser { std::vector operators() const { return {{"QuantizeLinear"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { if(args.size() < 2 or args.size() > 3) { MIGRAPHX_THROW("QuantizeLinear: must have either 2 or 3 inputs, " + std::to_string(args.size()) + " input(s) provided"); } // Starting with version 19 ONNX introduced the constraint that x and y_scale types must be // the same if(parser.opset_version >= 19 and args[0]->get_shape().type() != args[1]->get_shape().type()) { MIGRAPHX_THROW("QuantizeLinear: x and y_scale must be of same type"); } if(args.size() == 3 and args[1]->get_shape().lens() != args[2]->get_shape().lens()) { MIGRAPHX_THROW( "QuantizeLinear: y_scale and y_zero_point shapes must be equal. Provided y_scale " "shape: " + to_string_range(args[1]->get_shape().lens()) + ", provided y_zero_point shape: " + to_string_range(args[2]->get_shape().lens())); } int axis = 1; if(contains(info.attributes, "axis")) axis = info.attributes.at("axis").i(); int block_size = 0; if(contains(info.attributes, "block_size")) block_size = info.attributes.at("block_size").i(); std::optional output_type; if(contains(info.attributes, "output_dtype")) { output_type = get_type(info.attributes.at("output_dtype").i()); } if(output_type.has_value() and args.size() == 3 and *output_type != args[2]->get_shape().type()) { MIGRAPHX_THROW( "QuantizeLinear: output_type and y_zero_point type must match. output_type: " + to_string(*output_type) + +", y_zero_point type: " + to_string(args[2]->get_shape().type())); } args = transform_quantize_dequantize_linear_inputs( info, opd.onnx_name, block_size, axis, args); if(output_type == migraphx::shape::fp4x2_type) { // Parsing in pack_fp4 and unpack_fp4 for the FP4 case auto q_ins = info.add_instruction( make_op("quantizelinear", {{"out_type", migraphx::shape::float_type}}), args); // packing axis set to fastest dimension auto quantized_shape = q_ins->get_shape(); const auto& qs_strides = quantized_shape.strides(); if(qs_strides.empty()) { MIGRAPHX_THROW("QuantizeLinear: MX type quantized_shape has no strides"); } int fast_axis = std::min_element(qs_strides.cbegin(), qs_strides.cend()) - qs_strides.cbegin(); bool odd_fast_axis = (quantized_shape.lens().at(fast_axis) % 2 == 1); if(odd_fast_axis) { // pad fastest dimension by 1 if it is odd std::vector padding(2 * quantized_shape.ndim(), 0); padding.at(fast_axis * 2 + 1) = 1; q_ins = info.add_instruction(make_op("pad", {{"pads", padding}}), q_ins); } auto pack_ins = info.add_instruction(make_op("pack_fp4", {{"axis", fast_axis}}), q_ins); // output is fp4x2_type auto unpack_ins = info.add_instruction(make_op("unpack_fp4", {{"axis", fast_axis}}), pack_ins); // output is fp8e4m3fn_type if(odd_fast_axis) { // slice off padded values unpack_ins = info.add_instruction( make_op("slice", {{"axes", {fast_axis}}, {"starts", {0}}, {"ends", {quantized_shape.lens().at(fast_axis)}}}), unpack_ins); } return unpack_ins; } if(parser.opset_version < 19) { auto common_type = common_shape({args[0]->get_shape(), args[1]->get_shape()}).type(); std::transform(args.begin(), args.begin() + 2, args.begin(), [&](auto ins) { if(ins->get_shape().type() != common_type) ins = info.add_instruction(make_op("convert", {{"target_type", common_type}}), ins); return ins; }); } if(output_type.has_value()) return info.add_instruction(make_op("quantizelinear", {{"out_type", *output_type}}), args); else return info.add_instruction(make_op("quantizelinear"), args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_randomnormal_ops.cpp000066400000000000000000000106301510465702400235610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_randomnormal_ops : op_parser { std::set valid_types = {shape::float_type, shape::half_type, shape::double_type}; std::vector operators() const { return {{"RandomNormal"}, {"RandomNormalLike"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { int dtype = 1; bool use_dtype = false; if(contains(info.attributes, "dtype")) { dtype = info.attributes.at("dtype").i(); use_dtype = true; } shape::type_t out_type = get_type(dtype); if(not contains(valid_types, out_type)) MIGRAPHX_THROW(opd.onnx_name + ": invalid output type: " + std::to_string(dtype) + ". Valid types are 1 (float), 10 (half), and 11 (double)."); float mean = 0.0; if(contains(info.attributes, "mean")) mean = info.attributes.at("mean").f(); float scale = 1.0; if(contains(info.attributes, "scale")) scale = info.attributes.at("scale").f(); shape out_shape; if(contains(info.attributes, "shape")) { // RandomNormal: // output type and shape must come from attributes std::vector out_lens; literal ls = parser.parse_value(info.attributes.at("shape")); ls.visit([&](auto s) { out_lens.assign(s.begin(), s.end()); }); out_shape = shape{out_type, out_lens}; } else if(args.size() == 1) { // RandomNormalLike: // output type and shape are the same as the input's by default // dtype is used instead when attribute is set if(not contains(valid_types, args[0]->get_shape().type())) MIGRAPHX_THROW(opd.onnx_name + ": invalid output type: " + std::to_string(args[0]->get_shape().type()) + ". Valid types are float, half, and double."); out_shape = use_dtype ? shape{out_type, args[0]->get_shape().lens()} : args[0]->get_shape(); } else { MIGRAPHX_THROW(opd.onnx_name + ": cannot deduce shape without shape attribute or argument."); } std::mt19937 gen(std::chrono::high_resolution_clock::now().time_since_epoch().count()); if(contains(info.attributes, "seed")) gen.seed(info.attributes.at("seed").f()); std::normal_distribution<> d(mean, scale); std::vector rand_vals(out_shape.elements()); std::generate(rand_vals.begin(), rand_vals.end(), [&]() { return d(gen); }); return info.add_literal(literal{out_shape, rand_vals}); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_randomuniform_ops.cpp000066400000000000000000000106301510465702400237500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_randomuniform_ops : op_parser { std::set valid_types = {shape::float_type, shape::half_type, shape::double_type}; std::vector operators() const { return {{"RandomUniform"}, {"RandomUniformLike"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { int dtype = 1; bool use_dtype = false; if(contains(info.attributes, "dtype")) { dtype = info.attributes.at("dtype").i(); use_dtype = true; } shape::type_t out_type = get_type(dtype); if(not contains(valid_types, out_type)) MIGRAPHX_THROW(opd.onnx_name + ": invalid output type: " + std::to_string(dtype) + ". Valid types are 1 (float), 10 (half), and 11 (double)."); float high = 1.0; if(contains(info.attributes, "high")) high = info.attributes.at("high").f(); float low = 0.0; if(contains(info.attributes, "low")) low = info.attributes.at("low").f(); shape out_shape; if(contains(info.attributes, "shape")) { // RandomUniform: // output type and shape must come from attributes std::vector out_lens; literal ls = parser.parse_value(info.attributes.at("shape")); ls.visit([&](auto s) { out_lens.assign(s.begin(), s.end()); }); out_shape = shape{out_type, out_lens}; } else if(args.size() == 1) { // RandomUniformLike: // output type and shape are the same as the input by default // dtype is used instead when attribute is set if(not contains(valid_types, args[0]->get_shape().type())) MIGRAPHX_THROW(opd.onnx_name + ": invalid output type: " + std::to_string(args[0]->get_shape().type()) + ". Valid types are float, half, and double."); out_shape = use_dtype ? shape{out_type, args[0]->get_shape().lens()} : args[0]->get_shape(); } else { MIGRAPHX_THROW(opd.onnx_name + ": cannot deduce shape without shape attribute or argument."); } std::mt19937 gen(std::chrono::high_resolution_clock::now().time_since_epoch().count()); if(contains(info.attributes, "seed")) gen.seed(info.attributes.at("seed").f()); std::uniform_real_distribution<> d(low, high); std::vector rand_vals(out_shape.elements()); std::generate(rand_vals.begin(), rand_vals.end(), [&]() { return d(gen); }); return info.add_literal(literal{out_shape, rand_vals}); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_range.cpp000066400000000000000000000063441510465702400213120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_range : op_parser { std::vector operators() const { return {{"Range"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, onnx_parser::node_info info, std::vector args) const { auto start_arg = args[0]->eval(); check_arg_empty(start_arg, "PARSE_RANGE: start arg dynamic shape is not supported"); auto limit_arg = args[1]->eval(); check_arg_empty(limit_arg, "PARSE_RANGE: limit arg dynamic shape is not supported"); auto delta_arg = args[2]->eval(); check_arg_empty(delta_arg, "PARSE_RANGE: delta arg dynamic shape is not supported"); assert(args[0]->get_shape().elements() == 1 and args[1]->get_shape().elements() == 1 and args[2]->get_shape().elements() == 1); instruction_ref l0; visit_all(start_arg, limit_arg, delta_arg)([&](auto start, auto limit, auto delta) { auto start_val = start.front(); auto limit_val = limit.front(); auto delta_val = delta.front(); size_t num_elements = ceil(static_cast(limit_val - start_val) / static_cast(delta_val)); assert(num_elements > 0); using type = decltype(start_val); std::vector range_vals(num_elements); std::generate(range_vals.begin(), range_vals.end(), [&]() { auto result = start_val; start_val += delta_val; return result; }); l0 = info.add_literal({shape{args[0]->get_shape().type(), {num_elements}}, range_vals}); }); return l0; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_reduce_op.cpp000066400000000000000000000220071510465702400221550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { template struct reduce_parser : op_parser { instruction_ref parse_reduce_oper(const std::string& op_name, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { auto constant_axes = parse_constant_axes(args, info); int noop_with_empty_axes = parse_attribute("noop_with_empty_axes", parser, info).value_or(0); int keep_dims = parse_attribute("keepdims", parser, info).value_or(1); std::vector all_axes(args.front()->get_shape().ndim()); std::iota(all_axes.begin(), all_axes.end(), 0); // Handle axes attribute, constant input axes, and missing both attribute and input cases if(constant_axes.has_value()) { if(noop_with_empty_axes != 0 and constant_axes->empty()) return args[0]; if(noop_with_empty_axes == 0 and constant_axes->empty()) constant_axes = all_axes; auto reduce = info.add_instruction(make_op(op_name, {{"axes", *constant_axes}}), args[0]); if(keep_dims == 0) return info.add_instruction(make_op("squeeze", {{"axes", *constant_axes}}), reduce); return reduce; } // Handle variable input axes if(keep_dims == 0) MIGRAPHX_THROW("Keepdims not supported with runtime provided axes"); // Empty axes attribute indicates to the operator to look for axes in the inputs // If the input axes are empty, the default behavior of reduce_op is to be an // identity operator auto reduce_op = make_op(op_name, {{"axes", {}}}); if(noop_with_empty_axes != 0) return info.add_instruction(reduce_op, args); if(args[1]->get_shape().dynamic()) { auto reduce_input_axes = info.add_instruction(reduce_op, args); auto all_axes_lit = info.add_literal( literal{shape{shape::type_t::int64_type, {all_axes.size()}}, all_axes}); auto reduce_all_axes = info.add_instruction(reduce_op, args[0], all_axes_lit); auto zero = info.add_literal(literal{shape{shape::type_t::int64_type}, {0u}}); auto axes_size = info.add_instruction(make_op("dimensions_of", {{"end", 1}}), args[1]); auto is_axes_empty = info.add_instruction(make_op("equal"), axes_size, zero); return info.add_instruction( make_op("where"), is_axes_empty, reduce_all_axes, reduce_input_axes); } else if(args[1]->get_shape().elements() == 0) { auto all_axes_lit = info.add_literal( literal{shape{shape::type_t::int64_type, {all_axes.size()}}, all_axes}); return info.add_instruction(reduce_op, args[0], all_axes_lit); } else { return info.add_instruction(reduce_op, args); } } private: template std::optional parse_attribute(const std::string& attribute_name, const onnx_parser& parser, onnx_parser::node_info& info) const { if(not contains(info.attributes, attribute_name)) return std::nullopt; return parser.parse_value(info.attributes[attribute_name]).at(); } std::optional> parse_constant_axes(std::vector& args, onnx_parser::node_info& info) const { std::vector axes; if(args.size() == 2) { if(not args[1]->can_eval()) return std::nullopt; args[1]->eval().visit([&](auto s) { axes.assign(s.begin(), s.end()); }); } else if(contains(info.attributes, "axes")) { auto&& attr_axes = info.attributes["axes"].ints(); axes.assign(attr_axes.begin(), attr_axes.end()); } return axes; } }; struct parse_reduce_op : reduce_parser { std::vector operators() const { return {{"ReduceMax", "reduce_max"}, {"ReduceMean", "reduce_mean"}, {"ReduceMin", "reduce_min"}, {"ReduceProd", "reduce_prod"}, {"ReduceSum", "reduce_sum"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { return parse_reduce_oper(opd.op_name, parser, std::move(info), std::move(args)); } }; struct parse_reduce_l1 : reduce_parser { std::vector operators() const { return {{"ReduceL1"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { args[0] = info.add_instruction(make_op("abs"), args[0]); return parse_reduce_oper("reduce_sum", parser, std::move(info), std::move(args)); } }; struct parse_reduce_l2 : reduce_parser { std::vector operators() const { return {{"ReduceL2"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { args[0] = info.add_instruction(make_op("mul"), args[0], args[0]); auto sum_ins = parse_reduce_oper("reduce_sum", parser, info, std::move(args)); return info.add_instruction(make_op("sqrt"), sum_ins); } }; struct parse_reduce_log_sum : reduce_parser { std::vector operators() const { return {{"ReduceLogSum"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { auto sum_ins = parse_reduce_oper("reduce_sum", parser, info, std::move(args)); return info.add_instruction(make_op("log"), sum_ins); } }; struct parse_reduce_log_sum_exp : reduce_parser { std::vector operators() const { return {{"ReduceLogSumExp"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { args[0] = info.add_instruction(make_op("exp"), args[0]); auto sum_ins = parse_reduce_oper("reduce_sum", parser, info, std::move(args)); return info.add_instruction(make_op("log"), sum_ins); } }; struct parse_reduce_sum_square : reduce_parser { std::vector operators() const { return {{"ReduceSumSquare"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { args[0] = info.add_instruction(make_op("mul"), args[0], args[0]); return parse_reduce_oper("reduce_sum", parser, std::move(info), std::move(args)); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_reshape.cpp000066400000000000000000000054241510465702400216430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_reshape : op_parser { std::vector operators() const { return {{"Reshape"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { std::vector dims; if(args.size() == 1) { literal s = parser.parse_value(info.attributes.at("shape")); s.visit([&](auto v) { copy(v, std::back_inserter(dims)); }); return info.add_instruction(make_op("reshape", {{"dims", dims}}), args[0]); } else { // 2 inputs auto s = args[1]->eval(); if(s.empty()) { // arg[1] not eval-able auto alloc_ins = info.add_instruction( make_op("allocate", {{"buf_type", args[0]->get_shape().type()}}), args[1]); return info.add_instruction(make_op("reshape"), args[0], alloc_ins); } else { s.visit([&](auto v) { copy(v, std::back_inserter(dims)); }); return info.add_instruction(make_op("reshape", {{"dims", dims}}), args[0]); } } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_resize.cpp000066400000000000000000000614751510465702400215250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /* * Algorithm of calc_neighbor_points(): * Input: vvv_ind, a collection of neighbors per resized dimension as: * layer-1: (# resized dimensions, vector) * layer-2: (A vector of 2 of: hi/low) * layer-3: Neighor index of every pixel in that output dimension (vector) * in_s, the original input tensor shape (vector) * out_s, the output tensor shape (vector) * resized_m, lens indices that have to resized (map) * * Output: per resized pixel, its neighboring hi/lo indexes (vector): all permutations. * This api stitches all the neighbors (for every dimension) for a resized pixel, * to yield its neighbor index w.r.t to the input shape, in_s. */ static std::vector calc_neighbor_points(const std::vector>>& vvv_ind, const shape& in_s, const shape& out_s, const std::map& resized_m) { std::size_t ndims = out_s.ndim(); const auto& strides = out_s.strides(); std::size_t elements_ct = vvv_ind[0][0].size(); // This function computes for each element, all permutations of its neighbor indices into an // Perm block in one go. (Instead of computing each permutation in isolation per element) size_t permutations = 1u << resized_m.size(); std::vector> perm_blk(permutations, std::vector(strides)); // final outputted vector: permutations of neighbors. std::vector out_idx_vec(permutations * elements_ct); for(size_t e_idx = 0; e_idx < elements_ct; ++e_idx) { size_t t_idx = e_idx; for(size_t l_idx = 0; l_idx != ndims; ++l_idx) { auto entry = resized_m.find(l_idx); if(entry != resized_m.end()) { size_t hi_cmp_bit = 1u << entry->second; auto lo = vvv_ind[entry->second][0][e_idx]; auto hi = vvv_ind[entry->second][1][e_idx]; for(size_t i = 0; i < permutations; i++) perm_blk[i][l_idx] = ((i & hi_cmp_bit) != 0) ? hi : lo; } else { size_t idx = t_idx / strides[l_idx]; // no permutations in an unmodified lens index, so idx is copied over: for(size_t i = 0; i < permutations; i++) perm_blk[i][l_idx] = idx; } t_idx %= strides[l_idx]; } // write out the permuted indices, calculated off the perm_blk: for(size_t i = 0; i < permutations; i++) out_idx_vec[e_idx + elements_ct * i] = in_s.index(perm_blk[i]); } return out_idx_vec; } struct parse_resize : op_parser { std::vector operators() const { return {{"Resize", "resize"}, {"Upsample", "upsample"}}; } struct resize_attr { std::vector axes; // resize - 18 int antialias = 0; // resize - 18 int exclude_outside = 0; // resize - 11 float cubic_coeff_a = -0.75f; // resize - 11 float extrapolation_value = 0.0f; // resize - 11 std::string coord_t_mode = "half_pixel"; // resize - 11 std::string nearest_mode = "round_prefer_floor"; std::string keep_aspect = "stretch"; // resize - 18 // Overlaps with upsample operator std::string mode = "nearest"; // Upsample related std::vector scales = {}; // Upsample 7 }; struct resize_args { // Since inception opset(10) instruction_ref x; // For Upscale this may be an attr std::optional scales; // resize/upsample-10 // Added in resize - 11 // resize 13 makes roi optional std::optional roi; std::optional sizes; resize_attr r_attr; shape in_s; std::vector in_lens; std::vector out_lens; std::vector vec_scale; instruction_ref scales_sizes_arg; int opset_version = -1; // if scale an attr must be greater or equal to 1 bool is_scale_attr() const { return not r_attr.scales.empty(); } bool is_axes_used() const { return not r_attr.axes.empty(); } bool is_constant_scale_input() const { return not vec_scale.empty(); } std::string get_nearest_mode() const { return r_attr.nearest_mode; } std::string get_coord_trans_mode() const { return r_attr.coord_t_mode; } std::string get_mode() const { return r_attr.mode; } void set_scales_sizes_arg(instruction_ref ref) { scales_sizes_arg = ref; } instruction_ref get_scales_sizes_arg() const { return scales_sizes_arg; } void check_scales_and_inputs() const { if(in_lens.size() != vec_scale.size()) { MIGRAPHX_THROW("PARSE_RESIZE: ranks of input and scale are different!"); } } bool is_output_not_set() const { return all_of(out_lens.cbegin(), out_lens.cend(), [](auto o) { return o == 0; }); } void compute_output_sizes() { std::transform( in_lens.begin(), in_lens.end(), vec_scale.begin(), out_lens.begin(), [&](auto idx, auto scale) { return static_cast(idx * scale); }); } void compute_scales() { vec_scale.resize(in_lens.size()); std::transform(in_lens.begin(), in_lens.end(), out_lens.begin(), vec_scale.begin(), [](auto iss, auto oss) { return 1.0 * oss / iss; }); } void assign_scale_or_size(const std::vector& args) { set_scales_sizes_arg(args[0]); if(not is_constant_scale_input()) { // Depending on the args, it *must* populate the `vec_scale`, and might populate // `out_lens`. Skip first input and `roi` input (if present) size_t args_offset = args.size() > 2 ? 2 : 1; std::vector inputs{args.begin() + args_offset, args.end()}; for(const auto& arg : inputs) { if(is_arg_invalid(arg)) continue; scales_sizes_arg = arg; auto arg_out = arg->eval(); auto type = arg->get_shape().type(); if(is_arg_skipped(arg_out)) break; if(type == shape::int64_type) { // When input is using sizes assign_output_sizes(arg_out); check_output_size(); compute_scales(); break; } else if(type == shape::float_type) { // When input is using scales if(is_scale_rank_valid(arg)) { assign_scales(arg_out); } break; } else { MIGRAPHX_THROW("PARSE_RESIZE: invalid shape type "); } } if(vec_scale.empty() and out_lens.empty()) MIGRAPHX_THROW("PARSE_RESIZE: no shapes for scales/size input provided"); } if(is_constant_scale_input()) { check_scales_and_inputs(); if(is_output_not_set()) { compute_output_sizes(); } } } void set_coord_trans_mode(const onnx_parser::attribute_map& attr) { if(contains(attr, "coordinate_transformation_mode")) { auto coord_trans_mode = attr.at("coordinate_transformation_mode").s(); // does not support transformation mode "tf_crop_and_resize" if(coord_trans_mode == "tf_crop_and_resize") { MIGRAPHX_THROW("PARSE_RESIZE: \"tf_crop_and_resize\" mode is not supported!"); } r_attr.coord_t_mode = coord_trans_mode; } } void set_cubic_coeff(const onnx_parser::attribute_map& attr) { if(contains(attr, "cubic_coeff_a")) { auto coeff = attr.at("cubic_coeff_a").f(); r_attr.cubic_coeff_a = coeff; } } void set_mode(const onnx_parser::attribute_map& attr) { if(contains(attr, "mode")) { // TODO: Add support for cubic mode auto mode = attr.at("mode").s(); if(mode != "nearest" and mode != "linear") { MIGRAPHX_THROW("PARSE_RESIZE: only nearest and linear modes are supported!"); } r_attr.mode = mode; } } void set_nearest_mode(const onnx_parser::attribute_map& attr) { if(contains(attr, "nearest_mode")) { r_attr.nearest_mode = attr.at("nearest_mode").s(); } } void set_exclude_outside(const onnx_parser::attribute_map& attr) { // TODO: Add support for exclude outside = 1 if(contains(attr, "exclude_outside") and attr.at("exclude_outside").i() == 1) { MIGRAPHX_THROW("PARSE_RESIZE exclude_outside 1 is not supported!"); } } void set_extrapolation_val(const onnx_parser::attribute_map& attr) { if(contains(attr, "extrapolation_value")) { r_attr.extrapolation_value = attr.at("extrapolation_value").f(); } } void set_axes(const onnx_parser::attribute_map& attr) { // TODO: support implementation of 'axes' attribute. // For now, it's used to check the length of 'sizes' input (if present) if(contains(attr, "axes")) { auto&& axes_vals = attr.at("axes").ints(); r_attr.axes = std::vector(axes_vals.begin(), axes_vals.end()); } } void set_aspect_ratio_policy(const onnx_parser::attribute_map& attr, const std::vector& args) const { // TODO: Add support for this instead of keeping it as a check if(contains(attr, "keep_aspect_ratio_policy")) { shape last_arg_shape = args.back()->get_shape(); size_t last_arg_elements = last_arg_shape.elements(); // Check if the last arg is 'sizes' input. // This attribute is only relevant if 'sizes' input is used. // The shape constraints for 'sizes' are below: if(last_arg_shape.type() == shape::int64_type and (last_arg_elements == args.front()->get_shape().ndim() or (is_axes_used() and last_arg_elements == r_attr.axes.size()))) { MIGRAPHX_THROW("PARSE_RESIZE: keep_aspect_ratio_policy is not supported!"); } } } // "scales" is an attribute of the deprecated Upsample op. ver7 only void set_scales(const onnx_parser::attribute_map& attr) { if(contains(attr, "scales")) { copy(attr.at("scales").floats(), std::back_inserter(r_attr.scales)); vec_scale = r_attr.scales; compute_output_sizes(); } } bool is_arg_skipped(const argument& arg) const { return arg.empty(); } bool is_arg_invalid(const instruction_ref arg) const { if(arg->name() == "undefined") return true; // skip any empty input (some of the Onnx args. are optional) auto lens = arg->get_shape().lens(); return lens.empty(); } void check_output_size() const { if(out_lens.size() != in_lens.size()) { MIGRAPHX_THROW( "PARSE_RESIZE: specified output size's rank does not match input size"); } } void assign_output_sizes(const argument& arg_out) { arg_out.visit([&](const auto& ol) { out_lens.assign(ol.begin(), ol.end()); }); } void assign_scales(const argument& arg_out) { arg_out.visit([&](const auto& v) { vec_scale.assign(v.begin(), v.end()); }); } bool is_scale_rank_valid(const instruction_ref arg) const { return arg->get_shape().lens().at(0) == in_lens.size(); } }; // Helper to add a "reshape" and "gather" instruction. These can implement // Nearest mode resizing if all sizes are known at compile time. static instruction_ref make_gather_instruction(const onnx_parser::node_info& info, resize_args& resize, instruction_ref args_0) { auto in_s = resize.in_s; auto in_lens = resize.in_lens; auto out_lens = resize.out_lens; auto vec_scale = resize.vec_scale; shape out_s{in_s.type(), out_lens}; std::size_t out_elements = out_s.elements(); std::string nearest_mode = resize.get_nearest_mode(); std::vector ind(out_elements); // map out_idx to in_idx auto nearest_op = op::resize::get_nearest_op(nearest_mode); std::string coord_trans_mode = resize.get_coord_trans_mode(); auto idx_op = op::resize::get_original_idx_op(coord_trans_mode); shape_for_each(out_s, [&](const auto& out_idx_v, size_t out_idx) { std::vector in_idx(out_idx_v.size()); for(auto ii = 0; ii < in_lens.size(); ++ii) { auto idx_val = idx_op(in_lens[ii], out_lens[ii], out_idx_v[ii], vec_scale[ii]); in_idx[ii] = nearest_op(in_lens[ii], idx_val); } ind[out_idx] = static_cast(in_s.index(in_idx)); }); // reshape input to one-dimension std::vector rsp_lens = {static_cast(in_s.elements())}; auto rsp = info.add_instruction(make_op("reshape", {{"dims", rsp_lens}}), args_0); // ins_ind should be a multi dimensional index that will restore original rank shape ind_s{shape::int32_type, out_lens}; auto ins_ind = info.add_literal(literal(ind_s, ind)); return info.add_instruction(make_op("gather", {{"axis", 0}}), rsp, ins_ind); } static instruction_ref handle_nearest_neighbor(const onnx_parser::node_info& info, resize_args& resize, instruction_ref args_0) { if(args_0->get_shape().dynamic() or not resize.is_constant_scale_input()) { // Resize's compute_shape() will read scales_sizes_arg as "scales" or "sizes" // depending on its data type return info.add_instruction( make_op("resize", {{"nearest_mode", resize.get_nearest_mode()}, {"coordinate_transformation_mode", resize.get_coord_trans_mode()}}), args_0, resize.get_scales_sizes_arg()); } else { // If there are no dynamic shapes and size/scale attributes are literals, then // all the indexes can be calculated now at compile time and // the Resize can be accomplished with Gather operation. Preferred for // better performance. return make_gather_instruction(info, resize, args_0); } } static instruction_ref handle_linear_mode(const op_desc& opd, const onnx_parser::node_info& info, resize_args& resize, instruction_ref& args_0) { auto in_s = resize.in_s; auto in_lens = resize.in_lens; auto out_lens = resize.out_lens; auto vec_scale = resize.vec_scale; // out_lens and other variables can't be populated if non-constant (runtime) size // inputs. if(not resize.is_constant_scale_input()) MIGRAPHX_THROW("PARSE_" + opd.onnx_name + ": linear mode not supported for non-constant inputs"); if(in_lens == out_lens) return args_0; // if input and output shapes are the same, return the input shape out_s{in_s.type(), out_lens}; // reshape input to one-dimension std::vector rsp_lens = {static_cast(in_s.elements())}; auto rsp = info.add_instruction(make_op("reshape", {{"dims", rsp_lens}}), args_0); auto nearest_floor = op::resize::get_nearest_op("floor"); auto nearest_ceil = op::resize::get_nearest_op("ceil"); std::vector resized_axes; // vector of dimensions to be resized std::size_t out_elements = 1; // total number of elements to be resized size_t resized_ct = 0; std::map resized_m; // modified indices --> vvv_ind index below for(std::size_t axis = 0; axis != out_lens.size(); ++axis) { out_elements *= out_lens[axis]; if(in_lens[axis] == out_lens[axis]) continue; resized_axes.push_back(axis); resized_m[axis] = resized_ct++; } // Neighbor indices. For an axis. Two sets of max/min per element: std::vector> vv_ind(2, std::vector(out_elements)); // Neighbor indices. For all resized axes: std::vector>> vvv_ind(resized_ct, vv_ind); // Delta list. For each resized axes - per element. std::vector> delta(resized_ct, std::vector(out_elements)); auto idx_op = op::resize::get_original_idx_op(resize.get_coord_trans_mode()); shape_for_each(out_s, [&](const auto& out_idx_v, std::size_t out_idx) { for(size_t ii = 0; ii != resized_ct; ++ii) { auto idx = resized_axes[ii]; auto idx_val = idx_op(in_lens[idx], out_lens[idx], out_idx_v[idx], vec_scale[idx]); vvv_ind[ii][0][out_idx] = nearest_floor(in_lens[idx], idx_val); vvv_ind[ii][1][out_idx] = nearest_ceil(in_lens[idx], idx_val); delta[ii][out_idx] = idx_val - vvv_ind[ii][0][out_idx]; } }); auto ind = calc_neighbor_points(vvv_ind, in_s, out_s, resized_m); auto dim_lens = out_lens; // indices matrix size grows 2x per resized-axis: dim_lens[0] *= (1u << resized_ct); shape ind_s{shape::int32_type, dim_lens}; auto ins_ind = info.add_literal(literal(ind_s, ind)); auto data = info.add_instruction(make_op("gather", {{"axis", 0}}), rsp, ins_ind); for(auto idx = resized_ct; idx != 0u; --idx) { dim_lens[0] /= 2; // halved for 2 slices of data (hi & low below) shape dim_s{in_s.type(), dim_lens}; const auto& dim_delta = delta[idx - 1]; std::vector delta_data; for(std::size_t j = 0; j < dim_lens[0] / out_lens[0]; ++j) delta_data.insert(delta_data.begin(), dim_delta.begin(), dim_delta.end()); auto ins_delta = info.add_literal(dim_s, delta_data); // slice the data int64_t slc_stride = dim_lens[0]; auto low = info.add_instruction( make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {slc_stride}}}), data); auto hi = info.add_instruction( make_op("slice", {{"axes", {0}}, {"starts", {slc_stride}}, {"ends", {2 * slc_stride}}}), data); auto diff = info.add_instruction(make_op("sub"), hi, low); auto ddf = info.add_instruction(make_op("mul"), diff, ins_delta); data = info.add_instruction(make_op("add"), ddf, low); } return data; } static void set_resize_attributes(const onnx_parser::node_info& info, const std::vector& args, resize_args& resize) { resize.set_coord_trans_mode(info.attributes); resize.set_cubic_coeff(info.attributes); resize.set_axes(info.attributes); resize.set_exclude_outside(info.attributes); resize.set_extrapolation_val(info.attributes); resize.set_aspect_ratio_policy(info.attributes, args); resize.set_nearest_mode(info.attributes); resize.set_mode(info.attributes); } static void set_resize_args(const std::vector& args, resize_args& resize) { resize.x = args.at(0); resize.assign_scale_or_size(args); } static void set_upsample_attributes(const onnx_parser::node_info& info, resize_args& resize) { resize.set_mode(info.attributes); resize.set_scales(info.attributes); } static void set_upsample_args(const std::vector& args, resize_args& resize) { resize.x = args.at(0); // scale is input it must be a required input if(not resize.is_scale_attr()) resize.assign_scale_or_size(args); } // Split of what we handle since this parser is used for both resize/upscale operators static resize_args handle_inputs(const op_desc& opd, const onnx_parser::node_info& info, const std::vector& args) { resize_args resize; // input data shape info resize.in_s = args[0]->get_shape().to_static(1); resize.in_lens = resize.in_s.lens(); // output shape is explicitly specified resize.out_lens = std::vector(resize.in_lens.size()); if(opd.op_name == "upsample") { set_upsample_attributes(info, resize); set_upsample_args(args, resize); } else { set_resize_attributes(info, args, resize); set_resize_args(args, resize); } return resize; } instruction_ref parse(const op_desc& opd, const onnx_parser&, const onnx_parser::node_info& info, std::vector args) const { auto resize = handle_inputs(opd, info, args); if(resize.get_mode() == "nearest") { return handle_nearest_neighbor(info, resize, args[0]); } // linear mode else { return handle_linear_mode(opd, info, resize, args[0]); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_reversesequence.cpp000066400000000000000000000132011510465702400234100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // Parser for ReverseSequence ONNX operator. /*! Reverses the data along the time axis for the batches along the batch axis. The sequence lengths can be given to reverse up to the given length for each batch, keeping the rest of the sequence in the original order. Variable sequence_lens is not supported in this version of MIGraphX. You can pass the sequence_lens either as a constant node or an attribute. The batch axis and time axis must be [0, 1] and not the same. */ struct parse_reversesequence : op_parser { std::vector operators() const { return {{"ReverseSequence"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { int batch_axis = 1; if(contains(info.attributes, "batch_axis")) { batch_axis = info.attributes.at("batch_axis").i(); } if(batch_axis != 0 and batch_axis != 1) { MIGRAPHX_THROW("REVERSESEQUENCE: batch axis not 0 or 1"); } int time_axis = 0; if(contains(info.attributes, "time_axis")) { time_axis = info.attributes.at("time_axis").i(); } if(time_axis != 0 and time_axis != 1) { MIGRAPHX_THROW("REVERSESEQUENCE: time axis not 0 or 1"); } if(time_axis == batch_axis) { MIGRAPHX_THROW("REVERSESEQUENCE: time axis and batch axis are the same"); } auto input = args[0]; auto input_lens = input->get_shape().lens(); if(input_lens.size() < 2) { MIGRAPHX_THROW("REVERSESEQUENCE: input tensor must have rank >= 2"); } std::vector sequence_lens; if(args.size() == 2) { migraphx::argument seq_lens_arg = args.back()->eval(); check_arg_empty(seq_lens_arg, "REVERSESEQUENCE: cannot handle variable sequence_lens"); seq_lens_arg.visit([&](auto s) { sequence_lens.assign(s.begin(), s.end()); }); } else if(contains(info.attributes, "sequence_lens")) { literal s = parser.parse_value(info.attributes.at("sequence_lens")); s.visit([&](auto v) { sequence_lens.assign(v.begin(), v.end()); }); } auto batch_size = input_lens[batch_axis]; auto time_size = input_lens[time_axis]; // this condition may still work if sequence_len's shape was incorrect if(sequence_lens.size() != batch_size) { MIGRAPHX_THROW("REVERSESEQUENCE: sequence_lens has incorrect shape"); } instruction_ref ret; auto add_slice = [&info, &input, batch_axis, time_axis](int b, int t_start, int t_end) { return info.add_instruction(make_op("slice", {{"axes", {batch_axis, time_axis}}, {"starts", {b, t_start}}, {"ends", {b + 1, t_end}}}), input); }; for(int b = 0; b < batch_size; ++b) { instruction_ref s0; if(sequence_lens[b] > 1) { s0 = add_slice(b, 0, sequence_lens[b]); s0 = info.add_instruction(make_op("reverse", {{"axes", {time_axis}}}), s0); // if reversed less than whole batch, concat rest of batch if(sequence_lens[b] < time_size) { auto s1 = add_slice(b, sequence_lens[b], time_size); s0 = info.add_instruction(make_op("concat", {{"axis", time_axis}}), s0, s1); } } else { // cases where nothing changes s0 = add_slice(b, 0, time_size); } if(b == 0) { ret = s0; } else { ret = info.add_instruction(make_op("concat", {{"axis", batch_axis}}), ret, s0); } } return ret; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_rnn.cpp000066400000000000000000000157511510465702400210150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static void rnn_transpose_inputs(onnx_parser::node_info& info, std::vector& args) { std::vector perm{1, 0, 2}; args[0] = info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[0]); if(not args[5]->is_undefined()) { args[5] = info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[5]); } } static void rnn_transpose_outputs(onnx_parser::node_info& info, instruction_ref& hidden_states, instruction_ref& last_output) { std::vector perm_hs{2, 0, 1, 3}; hidden_states = info.add_instruction(make_op("transpose", {{"permutation", perm_hs}}), hidden_states); std::vector perm_last{1, 0, 2}; last_output = info.add_instruction(make_op("transpose", {{"permutation", perm_last}}), last_output); } struct parse_rnn : op_parser { std::vector operators() const { return {{"RNN"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { migraphx::shape input_shape = args[0]->get_shape(); std::size_t hidden_size = args[1]->get_shape().lens()[1]; if(contains(info.attributes, "hidden_size")) { std::size_t hidden_size_att = parser.parse_value(info.attributes.at("hidden_size")).at(); if(hidden_size != hidden_size_att) { MIGRAPHX_THROW("RNN: hidden size mismatch in input and attribute"); } } // Handling of direction to be added later std::string direction{"forward"}; if(contains(info.attributes, "direction")) { direction = info.attributes.at("direction").s(); } op::rnn_direction dirct = op::rnn_direction::forward; if(direction == "bidirectional") { dirct = op::rnn_direction::bidirectional; } else if(direction == "reverse") { dirct = op::rnn_direction::reverse; } std::vector vec_names = {"tanh", "tanh"}; if(contains(info.attributes, "activations")) { auto names = info.attributes.at("activations").strings(); vec_names.clear(); vec_names.resize(names.size()); std::transform(names.begin(), names.end(), vec_names.begin(), [](auto name) { return to_lower(std::move(name)); }); } if(vec_names.size() == 2 and dirct != op::rnn_direction::bidirectional) { // default activations are {"tanh", "tanh"} // in this case, take the first default activation. vec_names.resize(1); } auto num_actv_functions = dirct == op::rnn_direction::bidirectional ? 2 : 1; if(vec_names.size() != static_cast(num_actv_functions)) { MIGRAPHX_THROW("RNN: Invalid activation functions number, should be: " + to_string(num_actv_functions)); } auto name_it = std::find_if(vec_names.begin(), vec_names.end(), [&](auto& name) { return (map_activation_functions().count(name) == 0); }); if(name_it != vec_names.end()) { MIGRAPHX_THROW("RNN: activation function " + std::string(*name_it) + " not supported"); } std::vector vec_actv_funcs(vec_names.size()); std::transform(vec_names.begin(), vec_names.end(), vec_actv_funcs.begin(), [&](const auto& fn) { return map_activation_functions().at(fn); }); // To be added later float clip = 0.0; if(contains(info.attributes, "clip")) { clip = parser.parse_value(info.attributes.at("clip")).at(); } int layout = 0; if(contains(info.attributes, "layout")) { layout = parser.parse_value(info.attributes.at("layout")).at(); } // if the number of arguments is less than 6, append // undefined operator to have 6 arguments if(args.size() < 6) { auto ins = info.add_instruction(make_op("undefined")); args.insert(args.end(), (6 - args.size()), ins); } if(layout != 0) { rnn_transpose_inputs(info, args); } // first output for the concatenation of hidden states auto hidden_states = info.add_instruction(make_op("rnn", {{"hidden_size", hidden_size}, {"actv_func", to_value(vec_actv_funcs)}, {"direction", dirct}, {"clip", clip}}), args); // second output for the last hidden state auto last_output = info.add_instruction(make_op("rnn_last_hs_output"), hidden_states); if(layout != 0) { rnn_transpose_outputs(info, hidden_states, last_output); } return {hidden_states, last_output}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_roialign.cpp000066400000000000000000000077511510465702400220250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_roialign : op_parser { std::vector operators() const { return {{"RoiAlign"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, const std::vector& args) const { std::string coord_trans_mode = parser.opset_version >= 16 ? "half_pixel" : "output_half_pixel"; if(const auto* a = "coordinate_transformation_mode"; contains(info.attributes, a)) { coord_trans_mode = info.attributes.at(a).s(); } if(not contains({"half_pixel", "output_half_pixel"}, coord_trans_mode)) { MIGRAPHX_THROW("coordinate_transformation_mode \"" + coord_trans_mode + "\": invalid value!"); } migraphx::op::pooling_mode rmode(migraphx::op::pooling_mode::average); if(contains(info.attributes, "mode")) { // read mode; default is "avg" if(info.attributes.at("mode").s() == "max") { rmode = migraphx::op::pooling_mode::max; } } int64_t output_height = 1; if(contains(info.attributes, "output_height")) { output_height = info.attributes.at("output_height").i(); } int64_t output_width = 1; if(contains(info.attributes, "output_width")) { output_width = info.attributes.at("output_width").i(); } int64_t sampling_ratio = 0; if(contains(info.attributes, "sampling_ratio")) { sampling_ratio = info.attributes.at("sampling_ratio").i(); } float spatial_scale = 1.0f; if(contains(info.attributes, "spatial_scale")) { spatial_scale = info.attributes.at("spatial_scale").f(); } return info.add_instruction(make_op("roialign", {{"coordinate_transformation_mode", coord_trans_mode}, {"mode", rmode}, {"output_height", output_height}, {"output_width", output_width}, {"sampling_ratio", sampling_ratio}, {"spatial_scale", spatial_scale}}), args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_rotary_embedding.cpp000066400000000000000000000377111510465702400235360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct rotary_parameters { // Extracted from Inputs std::size_t batch_size = 0; // Batch used by input std::size_t seq_len = 0; // Sequence length used by input std::size_t head_size = 0; // Head size used for offset in each block std::size_t max_seq_len = 0; // Sequence length used by sin/cos caches std::size_t hidden_size = 0; // Hidden size used by the input // input shape: // true => [batch_size, num_heads, seq_len, head_size] // false => [batch_size, seq_len, hidden_size=num_heads*head_size] bool is_bnsh = false; // Extracted from both std::size_t num_heads = 0; // num_heads = hidden_size / head_size or input bool head_diff = false; // head_size > rotary_embedding_dim // Extracted from Attributes std::size_t rotary_embedding_dim = 0; bool interleaved = false; bool is_packed_batching = false; float scale = 1.0; }; struct parse_rotary_embedding : op_parser { std::vector operators() const { return {{"RotaryEmbedding"}}; } static void parse_attributes(const onnx_parser& parser, const onnx_parser::node_info& info, rotary_parameters& param) { if(contains(info.attributes, "interleaved")) { param.interleaved = parser.parse_value(info.attributes.at("interleaved")).at(); } if(contains(info.attributes, "is_packed_batching")) { // Ragged batching is a form of dynamic batching where inputs of varying lengths // are padded and batched together. E.g. inputs of [1, 3], [1, 4], [1, 5] // would each be padded to [1, 5] and batched together as [3, 5] param.is_packed_batching = parser.parse_value(info.attributes.at("is_packed_batching")).at(); if(param.is_packed_batching) { MIGRAPHX_THROW( "RotaryEmbedding: is_packed_batching aka ragged batching is not supported."); } } if(contains(info.attributes, "num_heads")) { param.num_heads = parser.parse_value(info.attributes.at("num_heads")).at(); } if(contains(info.attributes, "rotary_embedding_dim")) { param.rotary_embedding_dim = parser.parse_value(info.attributes.at("rotary_embedding_dim")).at(); } if(contains(info.attributes, "scale")) { // ContribOp spec does not specify how to apply the scale and ORT neither // handles nor tests this attribute param.scale = parser.parse_value(info.attributes.at("scale")).at(); if(not float_equal(param.scale, 1.0f)) { MIGRAPHX_THROW("RotaryEmbedding: scale is not supported."); } } if((param.num_heads != 0) xor (param.rotary_embedding_dim != 0)) { MIGRAPHX_THROW("RotaryEmbedding: num_heads and rotary_embedding dims must be used " "together and non-zero"); } } static void parse_input(const instruction_ref& input, rotary_parameters& param) { auto input_lens = input->get_shape().lens(); auto input_dims = input_lens.size(); if(input_dims < 3 or input_dims > 4) { MIGRAPHX_THROW( "RotaryEmbedding:Input must be 3D (Batch , Sequence Length, Hidden size) or \ 4D (Batch, Num Heads, Sequence Length, Head Size))"); } param.batch_size = input_lens.at(0); if(input_dims == 3) { param.seq_len = input_lens.at(1); param.hidden_size = input_lens.at(2); } else { param.num_heads = input_lens.at(1); param.seq_len = input_lens.at(2); param.head_size = input_lens.at(3); param.hidden_size = param.num_heads * param.head_size; param.is_bnsh = true; } } // Ensure position ID shapes comply with input dimensions static void parse_position_ids(const instruction_ref& position_ids, const rotary_parameters& param) { auto position_len = position_ids->get_shape().lens(); auto position_dim = position_ids->get_shape().lens().size(); if(position_dim > 2 or position_ids->get_shape().scalar()) { MIGRAPHX_THROW("RotaryEmbedding: Position_ids must be either 1D tensor of shape (1) or " "2d (Batch, Sequence Length)"); } if(position_dim == 1 and position_len.at(0) != 1) { MIGRAPHX_THROW("RotaryEmbedding: Position_id must have shape of 1 for 1D tensor"); } if((position_dim == 2) and ((position_len.at(0) != param.batch_size) or (position_len.at(1) != param.seq_len))) { MIGRAPHX_THROW("RotaryEmbedding: Position_id 2D dims must match input batch size and " "sequence length"); } } static void parse_cos_cache(const instruction_ref& cos_cache, rotary_parameters& param) { auto cos_cache_len = cos_cache->get_shape().lens(); param.max_seq_len = cos_cache_len.at(0); if(param.num_heads == 0) { param.head_size = cos_cache_len.at(1) * 2; param.num_heads = param.hidden_size / param.head_size; } else { param.head_size = param.hidden_size / param.num_heads; } if(param.rotary_embedding_dim == 0) { param.rotary_embedding_dim = param.head_size; } else if(param.head_size > param.rotary_embedding_dim) { param.head_diff = true; } else if(param.head_size < param.rotary_embedding_dim) { MIGRAPHX_THROW("RotaryEmbedding: rotary_embedding_dim must be <= head_size"); } compare_sin_cos_cache_dims(cos_cache_len.at(1), param); } static void parse_sin_cache(const instruction_ref& sin_cache, const rotary_parameters& param) { auto sin_cache_len = sin_cache->get_shape().lens(); if(param.max_seq_len != sin_cache_len.at(0)) { MIGRAPHX_THROW( "RotaryEmbedding: max_sequence_length must be the same between sin & cos caches!"); } compare_sin_cos_cache_dims(sin_cache_len.at(1), param); } static void compare_sin_cos_cache_dims(const size_t dim, const rotary_parameters& param) { if(param.rotary_embedding_dim != 0 and param.rotary_embedding_dim / 2 != dim) { MIGRAPHX_THROW( "RotaryEmbedding: rotary_embedding must be the same between sin & cos caches!"); } } static void parse_input_args(const std::vector& args, rotary_parameters& param) { // Order matters as we're basing params related to the first input parse_input(args.at(0), param); parse_position_ids(args.at(1), param); parse_cos_cache(args.at(2), param); parse_sin_cache(args.at(3), param); } static instruction_ref apply_rotary_embedding(const instruction_ref& in, const instruction_ref& cos, const instruction_ref& sin, const onnx_parser::node_info& info, const rotary_parameters& params) { if(params.interleaved) { std::vector signs(params.rotary_embedding_dim); for(auto i = 0; i < params.rotary_embedding_dim; ++i) { signs[i] = (i % 2 == 0) ? -1.0 : 1.0; } auto signs_lit = info.add_literal( literal{shape{in->get_shape().type(), {params.rotary_embedding_dim}}, signs}); signs_lit = info.add_instruction( make_op("multibroadcast", {{"out_lens", in->get_shape().lens()}}), signs_lit); auto mul_cos = info.add_broadcastable_binary_op("mul", in, cos); auto mul_sin = info.add_broadcastable_binary_op("mul", signs_lit, sin); auto rs_in = info.add_instruction( make_op("reshape", {{"dims", {in->get_shape().elements() / 2, 2}}}), in); auto evens = info.add_instruction( make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), rs_in); auto odds = info.add_instruction( make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), rs_in); auto in2 = info.add_instruction(make_op("concat", {{"axis", -1}}), odds, evens); in2 = info.add_instruction(make_op("reshape", {{"dims", in->get_shape().lens()}}), in2); mul_sin = info.add_broadcastable_binary_op("mul", mul_sin, in2); return info.add_broadcastable_binary_op("add", mul_sin, mul_cos); } auto pos = info.add_instruction( make_op("slice", {{"axes", {-1}}, {"starts", {0}}, {"ends", {params.rotary_embedding_dim / 2}}}), in); auto neg = info.add_instruction(make_op("slice", {{"axes", {-1}}, {"starts", {params.rotary_embedding_dim / 2}}, {"ends", {params.rotary_embedding_dim}}}), in); neg = info.add_instruction(make_op("neg"), neg); auto concat = info.add_instruction(make_op("concat", {{"axis", -1}}), neg, pos); auto mul_sin = info.add_broadcastable_binary_op("mul", concat, sin); auto mul_cos = info.add_broadcastable_binary_op("mul", in, cos); return info.add_broadcastable_binary_op("add", mul_sin, mul_cos); } static instruction_ref get_cache_slice(const instruction_ref& cache, const instruction_ref& pos_ids, const bool interleaved, const onnx_parser::node_info& info, const rotary_parameters& params) { instruction_ref rsps; if(pos_ids->get_shape().lens().size() == 1) { rsps = info.add_instruction( make_op("multibroadcast", {{"out_lens", {params.batch_size, params.seq_len, 1}}}), pos_ids); std::vector pos_vec(params.seq_len); std::iota(pos_vec.begin(), pos_vec.end(), 0); auto pos_lit = info.add_literal( literal{shape{pos_ids->get_shape().type(), {1, params.seq_len, 1}}, pos_vec}); rsps = info.add_broadcastable_binary_op("add", rsps, pos_lit); } else { rsps = info.add_instruction( make_op("reshape", {{"dims", {params.batch_size, params.seq_len, 1}}}), pos_ids); } auto gather = info.add_instruction(make_op("gathernd", {{"batch_dims", 0}}), cache, rsps); if(interleaved) { gather = info.add_instruction( make_op("reshape", {{"dims", {gather->get_shape().elements(), 1}}}), gather); } auto output = info.add_instruction(make_op("concat", {{"axis", -1}}), gather, gather); std::vector out_lens = { params.batch_size, params.seq_len, 1, params.rotary_embedding_dim}; output = info.add_instruction(make_op("reshape", {{"dims", out_lens}}), output); out_lens = { params.batch_size, params.seq_len, params.num_heads, params.rotary_embedding_dim}; output = info.add_instruction(make_op("multibroadcast", {{"out_lens", out_lens}}), output); out_lens = { params.batch_size, params.num_heads, params.seq_len, params.rotary_embedding_dim}; return info.add_instruction(make_op("reshape", {{"dims", out_lens}}), output); } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { if(args.size() != 4) { MIGRAPHX_THROW("RotaryEmbedding: Wrong number of inputs provided require 4"); } // Sanity check input dimension and shapes while extracting params rotary_parameters params{}; parse_attributes(parser, info, params); parse_input_args(args, params); // Setup based on parsed params gathered from input attributes/inputs auto input = args.at(0); auto position_ids = args.at(1); auto cos_cache = args.at(2); auto sin_cache = args.at(3); if(not params.is_bnsh) { input = info.add_instruction( make_op( "reshape", {{"dims", {params.batch_size, params.num_heads, params.seq_len, params.head_size}}}), input); } instruction_ref tail; if(params.head_diff) { tail = info.add_instruction(make_op("slice", {{"axes", {-1}}, {"starts", {params.rotary_embedding_dim}}, {"ends", {params.head_size}}}), input); input = info.add_instruction( make_op("slice", {{"axes", {-1}}, {"starts", {0}}, {"ends", {params.rotary_embedding_dim}}}), input); } auto cos = get_cache_slice(cos_cache, position_ids, params.interleaved, info, params); auto sin = get_cache_slice(sin_cache, position_ids, params.interleaved, info, params); auto output = apply_rotary_embedding(input, cos, sin, info, params); if(params.head_diff) { output = info.add_instruction(make_op("concat", {{"axis", -1}}), output, tail); } if(not params.is_bnsh) { output = info.add_instruction( make_op("reshape", {{"dims", {params.batch_size, params.seq_len, params.hidden_size}}}), output); } return {output}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_scan.cpp000066400000000000000000000356261510465702400211470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_scan : op_parser { std::vector operators() const { return {{"Scan"}}; } std::vector parse(const op_desc& /*opd*/, onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { if(parser.opset_version == 8) MIGRAPHX_THROW("Scan: Opset 8 version not supported"); check_for_required_attributes(info, {"body", "num_scan_inputs"}); const auto& body_graph = info.attributes["body"].g(); auto* body = parser.prog.create_module(info.name + "_scan"); parser.parse_graph(body, body_graph); // Scan has: // N + M inputs (N state variables, M scan inputs) // N + K outputs (N state variables, K scan outputs) // Same input and output counts apply for body auto body_outs = body->get_returns(); const auto m = info.attributes["num_scan_inputs"].i(); const auto n = args.size() - m; const auto k = body_outs.size() - n; std::vector body_params; transform(body->get_parameter_names(), std::back_inserter(body_params), [&](const auto& name) { return body->get_parameter(name); }); if(auto num_body_params = body_params.size(); num_body_params != n + m) MIGRAPHX_THROW("Scan: Number of inputs to body {" + std::to_string(num_body_params) + "} does not match number of inputs to Scan {" + std::to_string(n + m) + "}"); const auto scan_input_axes = parse_axes(info, "scan_input_axes", m, args.begin() + n, 0); const auto scan_input_directions = parse_dirs(info, "scan_input_directions", m); const auto scan_output_axes = parse_axes(info, "scan_output_axes", k, body_outs.begin() + n, 1); const auto scan_output_directions = parse_dirs(info, "scan_output_directions", k); // Check that scan axes lens are the same across all scan inputs size_t num_iters = args[n]->get_shape().lens()[scan_input_axes[0]]; for(auto i = 1; i < m; ++i) if(args[n + i]->get_shape().lens()[scan_input_axes[i]] != num_iters) MIGRAPHX_THROW( "Scan: Lengths of scan_input_axes do not match across all scan inputs.\n" "Scan input shapes: " + to_string_range( to_shapes(std::vector(args.begin() + n, args.end()))) + "\nScan input axes: " + to_string_range(scan_input_axes)); if(num_iters > parser.max_loop_iterations) MIGRAPHX_THROW("Scan: Number of required iterations {" + std::to_string(num_iters) + "} would exceed the maximum iteration limit {" + std::to_string(parser.max_loop_iterations) + "}"); // Check that state variable shapes match between the Scan node and its body attribute for(auto i = 0; i < n; ++i) if(args[i]->get_shape() != body_params[i]->get_shape()) MIGRAPHX_THROW("Scan: State input " + std::to_string(i) + " shape {" + to_string(args[i]->get_shape()) + "} does not match corresponding body input shape {" + to_string(body_params[i]->get_shape()) + "}"); // Check that the shapes of scan inputs sliced across scan input axes match the shapes of // the body attribute scan inputs for(auto i = 0; i < m; ++i) { auto node_shape = args[i + n]->get_shape(); auto node_lens = node_shape.lens(); node_lens.erase(node_lens.begin() + scan_input_axes[i]); auto slice_sh = shape(node_shape.type(), std::move(node_lens)); if(body_params[i + n]->get_shape() != slice_sh) MIGRAPHX_THROW("Slice: Sliced scan input " + std::to_string(i) + " shape {" + to_string(slice_sh) + "} does not match corresponding body input shape {" + to_string(body_params[i + n]->get_shape()) + "}"); } modify_body(body, args, n, m, scan_input_axes, scan_input_directions); auto max_iter_lit = info.add_literal(literal{shape{shape::int64_type}, {num_iters}}); auto cond_lit = info.add_literal(literal{shape{shape::bool_type}, {true}}); std::vector loop_args{max_iter_lit, cond_lit}; loop_args.insert(loop_args.end(), args.begin(), args.begin() + n); auto loop = info.add_instruction(make_op("loop", {{"max_iterations", num_iters}, {"scan_output_directions", scan_output_directions}}), loop_args, {body}); std::vector ret; ret.reserve(n + k); for(auto i = 0; i < n; ++i) ret.push_back(info.add_instruction(make_op("get_tuple_elem", {{"index", i}}), loop)); for(auto i = 0; i < k; ++i) { auto o = info.add_instruction(make_op("get_tuple_elem", {{"index", i + n}}), loop); // Loop concatenates scan axes along axis 0 which is inserted/unsqueezed, e.g. a body // scan output(from a single iteration) of shape {2, 2} is first expanded to {1, 2, 2}, // and then concatenated with body scan outputs from previous iterations. For n // iterations of the loop, this will end up producing a scan output of shape {n, 2, 2}. // // The scan_output_axes attribute of Scan can define an axis other than zero as the // concatenation axis. Using the previous scenario, for a body scan output of // shape {2,2}, with the scan output axis being 1, it is unsqueezed to {2, 1, 2}. The // final concatenation is then of shape {2, n, 2}. // // Since Loop only concatenates along the unsqueezed axis 0, a transpose is necessary to // place axis 0 in the appropriate scan_output_axis position auto perm = make_perm_for_scan_out(o->get_shape().ndim(), scan_output_axes[i]); ret.push_back(info.add_instruction(make_op("transpose", {{"permutation", perm}}), o)); } return ret; } void check_for_required_attributes(onnx_parser::node_info& info, const std::vector& attribute_names) const { auto it = std::find_if( attribute_names.cbegin(), attribute_names.cend(), [&](const std::string& name) { return not contains(info.attributes, name); }); if(it != attribute_names.cend()) MIGRAPHX_THROW("Scan: " + *it + " attribute required"); } std::vector parse_vector_attribute(onnx_parser::node_info& info, const std::string& attr_name, size_t expected_size) const { if(not contains(info.attributes, attr_name)) return {}; std::vector res; auto&& attr = info.attributes[attr_name].ints(); if(attr.size() != expected_size) MIGRAPHX_THROW("Scan: " + attr_name + " size is " + to_string(attr.size()) + ", should be " + to_string(expected_size)); res.assign(attr.begin(), attr.end()); return res; } std::vector parse_dirs(onnx_parser::node_info& info, const std::string& name, size_t expected_size) const { auto dirs = parse_vector_attribute(info, name, expected_size); if(dirs.empty()) return std::vector(expected_size, 0); // NOLINT if(any_of(dirs, [](auto i) { return i != 0 and i != 1; })) MIGRAPHX_THROW("Scan: " + name + " may contain only 1s and 0s, actual values: " + to_string_range(dirs)); return dirs; } int64_t normalize_axis(int64_t axis, int64_t rank, const std::string& attr_name) const { if(axis < -rank or axis >= rank) MIGRAPHX_THROW("Scan: " + attr_name + " axis value {" + to_string(axis) + "} out of range [" + to_string(-rank) + ", " + to_string(rank) + ")"); return axis < 0 ? rank + axis : axis; } std::vector parse_axes(onnx_parser::node_info& info, const std::string& name, long expected_size, std::vector::iterator ins_begin, size_t rank_offset) const { auto axes = parse_vector_attribute(info, name, expected_size); if(axes.empty()) return std::vector(expected_size, 0); // NOLINT std::transform(axes.begin(), axes.end(), ins_begin, axes.begin(), [&](int64_t axis, instruction_ref arg) { return normalize_axis(axis, arg->get_shape().ndim() + rank_offset, name); }); return axes; } // Alter the Scan body to match a body that Loop would expect. // // Loop body inputs: iteration_num, condition, loop_state_variables // Scan body inputs: loop_state_variables, scan_input_slices // iteration_num and condition parameters are prepended to the Scan body parameter list, while // scan_input_slices are removed from parameters. // Instead, scan_inputs are used directly in Scan body(as values from enclosing scope), and // together with iteration_num passed to the scan_slice operator which produces slices that are // used instead of the scan_inputs_slices. // // Loop body outputs: condition, loop_state_variables, scan_output_slices // Scan body outputs: loop_state_variables, scan_output_slices // The inserted Scan body condition parameter is prepended to the Scan body returns void modify_body(module_ref mod, const std::vector& args, int64_t n, int64_t m, const std::vector& scan_input_axes, const std::vector& scan_input_directions) const { std::vector params; params.reserve(n + m); transform(mod->get_parameter_names(), std::back_inserter(params), [&](const std::string& name) { return mod->get_parameter(name); }); // iteration_num, condition, and duplicate loop_state_variables are appended to parameters. // References to the original loop_state_variables in other instructions are then replaced // with references to the duplicate ones, after which the originals are removed. // // References to the scan_input_slices are replaced with references to inserted // scan_slice->squeeze instructions, after which the scan_input_slices parameters are // removed. auto iter_param = mod->add_parameter("iter", shape{shape::int64_type}); auto cond_param = mod->add_parameter("cond", shape{shape::bool_type}); std::vector new_params; new_params.reserve(n); for(auto i = 0; i < n; ++i) new_params.push_back( mod->add_parameter("state_var" + std::to_string(i), params[i]->get_shape())); for(auto i = 0; i < params.size(); ++i) { if(i < n) { mod->replace_instruction(params[i], new_params[i]); } else { auto scan_axis = scan_input_axes[i - n]; auto scan_dir = scan_input_directions[i - n]; auto new_ins = mod->insert_instruction( params[i], make_op("scan_slice", {{"axis", scan_axis}, {"direction", scan_dir}}), args[i], iter_param); new_ins = mod->insert_instruction( params[i], make_op("squeeze", {{"axes", {scan_axis}}}), new_ins); mod->replace_instruction(params[i], new_ins); } mod->remove_instruction(params[i]); } auto returns = mod->get_returns(); returns.insert(returns.begin(), cond_param); mod->replace_return(returns); } // Creates permutation so that axis 0 will be permuted to position axis, while maintaining the // relative ordering of all the other axes. // e.g. for rank = 4, axis = 2, the created perm is: [1, 2, 0, 3] std::vector make_perm_for_scan_out(int64_t rank, int64_t axis) const { std::vector perm(rank); std::iota(perm.begin(), perm.end(), 0); std::copy(perm.begin() + 1, perm.begin() + 1 + axis, perm.begin()); perm[axis] = 0; return perm; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_scatter.cpp000066400000000000000000000053711510465702400216620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_scatter : op_parser { std::vector operators() const { return {{"ScatterElements"}, {"Scatter"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, const std::vector& args) const { operation op; std::string reduction = "none"; int axis = 0; if(contains(info.attributes, "axis")) axis = info.attributes.at("axis").i(); if(contains(info.attributes, "reduction")) { reduction = info.attributes.at("reduction").s(); // check for a valid reduction attribute. We have an operator for each one. if(not contains({"none", "add", "mul", "min", "max"}, reduction)) MIGRAPHX_THROW("PARSE_SCATTER: unsupported reduction mode " + reduction); // merge scatter with reduction attribute to specify which scatter operation. Future // reduction op names should follow this pattern and should also be added to the check // above. } op = migraphx::make_op("scatter_" + reduction, {{"axis", axis}}); return info.add_instruction(op, args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_scatternd.cpp000066400000000000000000000044201510465702400221760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_scatternd : op_parser { std::vector operators() const { return {{"ScatterND"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector& args) const { std::string reduction = "none"; if(contains(info.attributes, "reduction")) { reduction = info.attributes.at("reduction").s(); if(not contains({"none", "add", "mul", "min", "max"}, reduction)) { MIGRAPHX_THROW("PARSE_SCATTERND: unsupported reduction mode " + reduction); } } return info.add_instruction(migraphx::make_op("scatternd_" + reduction), args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_selu.cpp000066400000000000000000000064471510465702400211720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_selu : op_parser { std::vector operators() const { return {{"Selu"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, onnx_parser::node_info info, std::vector args) const { auto type = args[0]->get_shape().type(); auto lens = args[0]->get_shape().lens(); float alpha = 1.67326f; if(contains(info.attributes, "alpha")) { alpha = info.attributes.at("alpha").f(); } float gamma = 1.0507f; if(contains(info.attributes, "gamma")) { gamma = info.attributes.at("gamma").f(); } auto l_alpha = info.add_literal({{type, {1}}, {alpha}}); auto l_gamma = info.add_literal({{type, {1}}, {gamma / 2.0f}}); if(lens != std::vector{1}) { l_alpha = info.add_instruction(make_op("multibroadcast", {{"out_lens", lens}}), l_alpha); l_gamma = info.add_instruction(make_op("multibroadcast", {{"out_lens", lens}}), l_gamma); } auto sign_x = info.add_instruction(make_op("sign"), args[0]); auto exp_x = info.add_instruction(make_op("exp"), args[0]); auto alpha_ex = info.add_instruction(make_op("mul"), l_alpha, exp_x); auto aex_alpha = info.add_instruction(make_op("sub"), alpha_ex, l_alpha); auto ins1 = info.add_instruction(make_op("add"), aex_alpha, args[0]); auto ins2 = info.add_instruction(make_op("sub"), aex_alpha, args[0]); auto sign2 = info.add_instruction(make_op("mul"), sign_x, ins2); auto ins_sub = info.add_instruction(make_op("sub"), ins1, sign2); return info.add_instruction(make_op("mul"), ins_sub, l_gamma); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_shape.cpp000066400000000000000000000076071510465702400213210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { /** * If static shape input, creates a literal in migraphx. * If dynamic shape input, creates a dimensions_of operator in migraphx (runtime evaluation of * shape). */ struct parse_shape : op_parser { std::vector operators() const { return {{"Shape"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { if(args.size() != 1) MIGRAPHX_THROW("Shape: operator should have 1 operand"); auto input_shape = args[0]->get_shape(); int input_ndim = input_shape.ndim(); std::size_t start = 0; std::size_t end = input_ndim; // Normalizing the start and end is handled here because of how the static shape version // works. Clamping to [-r, r], where r is ndim of input and then making positive. auto normalize_ind = [&](int64_t ind) { ind = std::max(ind, -1 * input_ndim); ind = std::min(ind, input_ndim); return (ind >= 0) ? ind : input_ndim + ind; }; if(contains(info.attributes, "end")) { end = normalize_ind(info.attributes.at("end").i()); } if(contains(info.attributes, "start")) { start = normalize_ind(info.attributes.at("start").i()); } if(end <= start) { MIGRAPHX_THROW("PARSE_SHAPE: ending axis <= starting axis, end: " + std::to_string(end) + " start: " + std::to_string(start)); } if(input_shape.dynamic()) { return info.add_instruction(make_op("dimensions_of", {{"start", start}, {"end", end}}), args[0]); } else { std::size_t output_ndim = end - start; std::vector vec_shape(output_ndim); migraphx::shape s(migraphx::shape::int64_type, {output_ndim}); std::vector input_lens = input_shape.lens(); std::transform(input_lens.begin() + start, input_lens.begin() + end, vec_shape.begin(), [](auto i) { return int64_t(i); }); return info.add_literal(migraphx::literal{s, vec_shape}); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_shrink.cpp000066400000000000000000000063421510465702400215120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_shrink : op_parser { std::vector operators() const { return {{"Shrink"}}; } instruction_ref parse(const op_desc&, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { float bias = 0.0; if(contains(info.attributes, "bias")) { bias = parser.parse_value(info.attributes.at("bias")).at(); } float lambd = 0.5; if(contains(info.attributes, "lambd")) { lambd = parser.parse_value(info.attributes.at("lambd")).at(); } auto x = args[0]; auto x_shape = x->get_shape(); auto x_type = x_shape.type(); auto lit_bias = info.add_literal(bias); auto lit_neg_lambd = info.add_literal(-lambd); auto lit_lambd = info.add_literal(lambd); auto x_plus_bias = info.add_common_op("add", x, lit_bias); auto x_min_bias = info.add_common_op("sub", x, lit_bias); auto cond1 = info.add_common_op("less", x, lit_neg_lambd); auto cond2_a = info.add_common_op("not", cond1); auto cond2_b = info.add_common_op("greater", x, lit_lambd); auto cond2 = info.add_common_op("logical_and", cond2_a, cond2_b); auto first = info.add_common_op("mul", cond1, x_plus_bias); auto second = info.add_common_op("mul", cond2, x_min_bias); auto ret = info.add_common_op("add", first, second); if(ret->get_shape().type() != x_type) { ret = info.add_instruction(make_op("convert", {{"target_type", x_type}}), ret); } return ret; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_simplified_layer_normalization.cpp000066400000000000000000000104411510465702400264760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // ONNXRunTime implementation for reference: // https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/providers/cpu/nn/layer_norm_impl.cc struct parse_simplified_layer_normalization : op_parser { std::vector operators() const { return {{"SimplifiedLayerNormalization"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { int64_t axis = -1; if(contains(info.attributes, "axis")) { axis = parser.parse_value(info.attributes.at("axis")).at(); } float epsilon = 1e-5f; if(contains(info.attributes, "epsilon")) { epsilon = parser.parse_value(info.attributes.at("epsilon")).at(); } if(contains(info.attributes, "stash_type")) { std::cerr << "WARNING: SIMPLIFIED_LAYER_NORMALIZATION attribute stash_type is only " "used for training.\n"; } if(args.size() != 2) { MIGRAPHX_THROW( "PARSE_SIMPLIFIED_LAYER_NORMALIZATION: invalid input count - expected 2 got " + std::to_string(args.size())); } auto x = args.at(0); auto scale = args.at(1); auto x_shape = x->get_shape(); auto x_dtype = x_shape.type(); int64_t x_rank = x_shape.ndim(); axis = axis < 0 ? axis + x_rank : axis; if(x_rank < 2) { MIGRAPHX_THROW("PARSE_SIMPLIFIED_LAYER_NORMALIZATION: invalid ndims=" + std::to_string(x_rank) + ", must be at least 2"); } // Convert to float before reduce_mean // Fp16 reduce_mean on GPU causes loss of accuracy auto float_x = info.add_instruction( make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); auto x_sq = info.add_common_op("mul", float_x, float_x); auto rms = info.add_instruction(make_op("reduce_mean", {{"axes", {axis}}}), x_sq); rms = info.add_instruction(make_op("convert", {{"target_type", x_dtype}}), rms); auto mean = rms; epsilon = (x_dtype == migraphx::shape::half_type and std::abs(epsilon) < 1e-7) ? 1e-7 : epsilon; auto eps = info.add_literal(migraphx::literal{migraphx::shape{x_dtype}, {epsilon}}); rms = info.add_common_op("add", rms, eps); auto rrms = info.add_instruction(make_op("rsqrt"), rms); auto result = info.add_common_op("mul", x, rrms); result = info.add_common_op("mul", result, scale); return {result, mean, rrms}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_size.cpp000066400000000000000000000037751510465702400211750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_size : op_parser { std::vector operators() const { return {{"Size"}}; } instruction_ref parse(const op_desc&, const onnx_parser&, const onnx_parser::node_info& info, std::vector args) const { return info.add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type}, {args[0]->get_shape().elements()}}); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_skip_layer_normalization.cpp000066400000000000000000000154421510465702400253250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // com.microsoft.SkipLayerNormalization // Skip and Layer Normalization Fusion // Version // This version of the operator has been available since version 1 of the 'com.microsoft' operator // set. // Type Constraints // T : tensor(float), tensor(float16) // Constrain input and output types to float or half tensors. // U : tensor(float) // Constrain mean and inv_std_var to float tensors. struct parse_skip_layer_normalization : op_parser { std::vector operators() const { return {{"SkipLayerNormalization"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { // Attributes // epsilon : float // The epsilon value to use to avoid division by zero. float epsilon = 1e-5f; if(contains(info.attributes, "epsilon")) { epsilon = parser.parse_value(info.attributes.at("epsilon")).at(); } // Inputs (3 - 5) // input : T // 3D input tensor with shape (batch_size, sequence_length, hidden_size) Or 2D input tensor // with shape (token_count, hidden_size) // skip : T // 3D input tensor with shape (batch_size, sequence_length, hidden_size) // Or 2D input tensor with shape (token_count, hidden_size) // gamma : T // 1D input tensor with shape (hidden_size) // beta (optional) : T // 1D skip tensor with shape (hidden_size) // bias (optional) : T // 1D bias tensor with shape (hidden_size) - not used by ORT if(args.size() < 3 or args.size() > 5) { MIGRAPHX_THROW("PARSE_SKIPLAYERNORMALIZATION: invalid input count"); } auto x = args.at(0); auto skip = args.at(1); auto gamma = args.at(2); auto x_shape = x->get_shape(); auto x_dtype = x_shape.type(); int64_t x_rank = x_shape.ndim(); int64_t skip_rank = skip->get_shape().ndim(); int64_t gamma_rank = gamma->get_shape().ndim(); // axis = hidden_size dim int64_t axis = x_rank - 1; if(x_rank != 3 or (x_rank != skip_rank and skip_rank != 2) or gamma_rank != 1) { MIGRAPHX_THROW("PARSE_SKIPLAYERNORMALIZATION: invalid input shape"); } // Beta always applied at the end result as an affine offset instruction_ref beta; if(args.size() >= 4) { beta = args.at(3); const auto& beta_shape = beta->get_shape(); const auto& beta_len = beta_shape.lens(); if(beta_shape.type() != x_dtype or beta_len.size() != 1) { MIGRAPHX_THROW("PARSE_SKIPLAYERNORMALIZATION: Invalid Beta shape"); } } // Bias is always applied to the input along with any skip input instruction_ref bias; if(args.size() == 5) { bias = args.at(4); auto bias_shape = bias->get_shape(); const auto& bias_len = bias_shape.lens(); if(bias_shape.type() != x_dtype or bias_len.size() != 1) { MIGRAPHX_THROW("PARSE_SKIPLAYERNORMALIZATION: Invalid Bias shape"); } } x = info.add_common_op("add", x, skip); if(args.size() == 5) { x = info.add_common_op("add", x, bias); } // Get the mean of input and squared of the expectation (for variance calc later) // Var = E( (x - E[x])) ^2) auto mean = info.add_instruction(make_op("reduce_mean", {{"axes", {axis}}}), x); auto pr_var = info.add_common_op("sqdiff", {x, mean}); auto var = info.add_instruction(make_op("reduce_mean", {{"axes", {axis}}}), pr_var); epsilon = (x_dtype == migraphx::shape::half_type and std::abs(epsilon) < 1e-7) ? 1e-7 : epsilon; auto eps = info.add_literal(migraphx::literal{shape{x_dtype}, {epsilon}}); auto var_ep = info.add_common_op("add", var, eps); // reciprical sqrt here on resulting variance + epsilon offset to avoid div by zero auto r_var = info.add_instruction(make_op("rsqrt"), var_ep); // Output is (x - E[x]) * gamma / (sqrt(var(x) - epsilon)) + beta auto result = info.add_common_op("sub", x, mean); result = info.add_common_op("mul", result, r_var); result = info.add_common_op("mul", result, gamma); if(args.size() >= 4) { result = info.add_common_op("add", result, beta); } // Outputs (1 - 4) // output : T // 3D output tensor with shape (batch_size, sequence_length, hidden_size) // mean (optional) : U Saved mean used during training // to speed up gradient computation // inv_std_var (optional) : U Saved inverse standard // variance used during training to speed up gradient computation. // input_skip_bias_sum (optional) : T Sum of the input and skip inputs (and bias if it // exists)with shape (batch_size, sequence_length, hidden_size) or (token_count, // hidden_size). return {result, mean, r_var, x}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_skip_simplified_layer_normalization.cpp000066400000000000000000000135631510465702400275340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // com.microsoft.SkipSimplifiedLayerNormalization // Skip and Root Mean Square Layer Normalization // Version // This version of the operator has been available since version 1 of the 'com.microsoft' operator // set. // Type Constraints // T : tensor(float), tensor(float16) // Constrain input and output types to float or half tensors. // U : tensor(float) // Constrain mean and inv_std_var to float tensors. struct parse_skip_simplified_layer_normalization : op_parser { std::vector operators() const { return {{"SkipSimplifiedLayerNormalization"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { // Attributes // epsilon : float // The epsilon value to use to avoid division by zero. float epsilon = 1e-5f; if(contains(info.attributes, "epsilon")) { epsilon = parser.parse_value(info.attributes.at("epsilon")).at(); } // Inputs (3 - 4) // input : T // 3D input tensor with shape (batch_size, sequence_length, hidden_size) Or 2D input tensor // with shape (token_count, hidden_size) // skip : T // 3D input tensor with shape (batch_size, sequence_length, hidden_size) // Or 2D input tensor with shape (token_count, hidden_size) // gamma : T // 1D input tensor with shape (hidden_size) // bias (optional) : T // 1D bias tensor with shape (hidden_size) - not used by ORT if(args.size() < 3 or args.size() > 4) { MIGRAPHX_THROW("PARSE_SKIPSIMPLIFIEDLAYERNORMALIZATION: invalid input count"); } auto x = args.at(0); auto skip = args.at(1); auto gamma = args.at(2); instruction_ref bias; if(args.size() == 4) { bias = args.at(3); } auto x_shape = x->get_shape(); auto x_dtype = x_shape.type(); int64_t x_rank = x_shape.ndim(); int64_t skip_rank = skip->get_shape().ndim(); int64_t gamma_rank = gamma->get_shape().ndim(); // axis = hidden_size dim int64_t axis = x_rank - 1; if(x_rank < 2 or x_rank > 3 or x_rank != skip_rank or gamma_rank != 1) { MIGRAPHX_THROW("PARSE_SKIPSIMPLIFIEDLAYERNORMALIZATION: invalid input shape"); } x = info.add_common_op("add", x, skip); // Convert to float before reduce_mean // Fp16 reduce_mean on GPU causes loss of accuracy auto float_x = info.add_instruction( make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); auto x_sq = info.add_common_op("mul", float_x, float_x); auto rms = info.add_instruction(make_op("reduce_mean", {{"axes", {axis}}}), x_sq); rms = info.add_instruction(make_op("convert", {{"target_type", x_dtype}}), rms); auto mean = rms; epsilon = (x_dtype == migraphx::shape::half_type and std::abs(epsilon) < 1e-7) ? 1e-7 : epsilon; auto eps = info.add_literal(migraphx::literal{migraphx::shape{x_dtype}, {epsilon}}); rms = info.add_common_op("add", rms, eps); auto rrms = info.add_instruction(make_op("rsqrt"), rms); auto result = info.add_common_op("mul", x, rrms); result = info.add_common_op("mul", result, gamma); if(args.size() == 4) { result = info.add_common_op("add", result, bias); x = info.add_common_op("add", x, bias); } // Outputs (1 - 4) // output : T // 3D output tensor with shape (batch_size, sequence_length, hidden_size)Or 2D output tensor // with shape (token_count, hidden_size) // mean (optional) : U Saved mean used during training // to speed up gradient computation // inv_std_var (optional) : U Saved inverse standard // variance used during training to speed up gradient computation. // input_skip_bias_sum (optional) : T Sum of the input and skip inputs (and bias if it // exists)with shape (batch_size, sequence_length, hidden_size) or (token_count, // hidden_size). return {result, mean, rrms, x}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_slice.cpp000066400000000000000000000145601510465702400213140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_slice : op_parser { std::vector operators() const { return {{"Slice"}}; } struct slice_desc { op::slice op; std::vector op_args; std::vector steps; std::vector raxes; void always_insert(instruction_ref arg) { op_args.insert(op_args.begin(), arg); } /** * Either insert argument into `this->op_args` or return the constant value of the argument */ std::vector insert(instruction_ref arg) { std::vector result; migraphx::argument arg_value = arg->eval(); if(arg_value.empty()) { op_args.insert(op_args.begin(), arg); } else { arg_value.visit([&](auto s) { result.assign(s.begin(), s.end()); }); } return result; } }; instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { auto sd = construct_slice_desc(parser, info, args); auto ins = info.add_instruction(sd.op, sd.op_args); if(not sd.raxes.empty()) { ins = info.add_instruction(make_op("reverse", {{"axes", sd.raxes}}), ins); } // If any steps are other than default 1, add a "steps" op if(std::any_of(sd.steps.begin(), sd.steps.end(), [](auto s) { return std::abs(s) != 1; })) { std::vector nsteps; std::transform(sd.steps.begin(), sd.steps.end(), std::back_inserter(nsteps), [](auto s) { return std::abs(s); }); return ins = info.add_instruction( make_op("step", {{"axes", sd.op.axes}, {"steps", nsteps}}), ins); } else return ins; } slice_desc construct_slice_desc(const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { slice_desc sd; // slice can have up to 5 inputs, we first check the 5th one // to decide whether MIGRAPHX can handle this slice. if(args.size() == 5) { migraphx::argument step_arg = args.back()->eval(); check_arg_empty(step_arg, "PARSE_SLICE: cannot handle variable steps for slice"); step_arg.visit([&](auto s) { sd.steps.assign(s.begin(), s.end()); }); } if(args.size() >= 4) { sd.op.axes = sd.insert(args.at(3)); } else if(contains(info.attributes, "axes")) { literal s = parser.parse_value(info.attributes.at("axes")); s.visit([&](auto v) { copy(v, std::back_inserter(sd.op.axes)); }); } if(args.size() >= 3) { sd.op.ends = sd.insert(args.at(2)); } else if(contains(info.attributes, "ends")) { literal s = parser.parse_value(info.attributes.at("ends")); s.visit([&](auto v) { copy(v, std::back_inserter(sd.op.ends)); }); } if(args.size() >= 2) { sd.op.starts = sd.insert(args.at(1)); } else if(contains(info.attributes, "starts")) { literal s = parser.parse_value(info.attributes.at("starts")); s.visit([&](auto v) { copy(v, std::back_inserter(sd.op.starts)); }); } // data input argument sd.always_insert(args.at(0)); // If axes arg is not given, the default is all of them. if(sd.op.axes.empty() and sd.op_args.size() <= 3) { std::vector axes(args[0]->get_shape().ndim()); std::iota(axes.begin(), axes.end(), int64_t{0}); sd.op.axes = axes; } if(std::any_of(sd.steps.begin(), sd.steps.end(), [](auto s) { return s != 1; })) { if(sd.op.starts.empty() or sd.op.ends.empty()) MIGRAPHX_THROW( "PARSE_SLICE: steps and variable starts and/or ends is not supported"); if(sd.op.axes.empty()) MIGRAPHX_THROW("PARSE_SLICE: steps and variable axes is not supported"); } // If any axes have negative step, prepare to add a "reverse" op for(auto i : range(sd.steps.size())) { if(sd.steps[i] >= 0) continue; sd.op.starts[i] += 1; if(sd.op.starts[i] == 0) sd.op.starts[i] = INT_MAX; sd.op.ends[i] += 1; sd.raxes.push_back(sd.op.axes[i]); std::swap(sd.op.starts[i], sd.op.ends[i]); } return sd; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_softmax.cpp000066400000000000000000000045221510465702400216730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_softmax : op_parser { std::vector operators() const { return {{"Softmax", "softmax"}, {"LogSoftmax", "logsoftmax"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { // default axis value is -1 for opset 13 int64_t axis = -1; // axis value is 1 for previous opset versions if(parser.opset_version < 13) { axis = 1; } if(contains(info.attributes, "axis")) { axis = parser.parse_value(info.attributes.at("axis")).at(); } return info.add_instruction(make_op(opd.op_name, {{"axis", axis}}), args); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_softmaxcrossentropyloss.cpp000066400000000000000000000504531510465702400252730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ /* ----------------------- SoftmaxCrossEntropyLoss ----------------------- Loss function that measures the softmax cross entropy between 'scores' and 'labels'. This operator first computes a loss tensor whose shape is identical to the labels input. If the input is 2-D with shape (N, C), the loss tensor may be a N-element vector L = (l_1, l_2, ..., l_N). If the input is N-D tensor with shape (N, C, D1, D2, ..., Dk), the loss tensor L may have (N, D1, D2, ..., Dk) as its shape and L[i,][j_1][j_2]...[j_k] denotes a scalar element in L. After L is available, this operator can optionally do a reduction operator. shape(scores): (N, C) where C is the number of classes, or (N, C, D1, D2,..., Dk), with K >= 1 in case of K-dimensional loss. shape(labels): (N) where each value is 0 <= labels[i] <= C-1, or (N, D1, D2,..., Dk), with K >= 1 in case of K-dimensional loss. The loss for one sample, l_i, can calculated as follows: l[i][d1][d2]...[dk] = -y[i][c][d1][d2]..[dk], where i is the index of classes. or l[i][d1][d2]...[dk] = -y[i][c][d1][d2]..[dk] * weights[c], if 'weights' is provided. loss is zero for the case when label-value equals ignore_index. l[i][d1][d2]...[dk] = 0, when labels[n][d1][d2]...[dk] = ignore_index where: p = Softmax(scores) y = Log(p) c = labels[i][d1][d2]...[dk] Finally, L is optionally reduced: If reduction = 'none', the output is L with shape (N, D1, D2, ..., Dk). If reduction = 'sum', the output is scalar: Sum(L). If reduction = 'mean', the output is scalar: ReduceMean(L), or if weight is provided: ReduceSum(L) / ReduceSum(W), where tensor W is of shape (N, D1, D2, ..., Dk) and W[n][d1][d2]...[dk] = weights[labels[i][d1][d2]...[dk]]. Attributes +++++++++++ ignore_index : int Specifies a target value that is ignored and does not contribute to the input gradient. It's an optional value. reduction : string (default is mean) Type of reduction to apply to loss: none, sum, mean(default). - 'none': no reduction will be applied - 'sum': the output will be summed. - 'mean': the sum of the output will be divided by the number of elements in the output. Inputs (2 - 3) ++++++++++++++ scores (differentiable) : T The predicted outputs with shape [batch_size, class_size], or [batch_size, class_size, D1, D2 , ..., Dk], where K is the number of dimensions. labels (non-differentiable) : Tind The ground truth output tensor, with shape [batch_size], or [batch_size, D1, D2, ..., Dk], where K is the number of dimensions. Labels element value shall be in range of [0, C). If ignore_index is specified, it may have a value outside [0, C) and the label values should either be in the range [0, C) or have the value ignore_index. weights (optional, non-differentiable) : T A manual rescaling weight given to each class. If given, it has to be a 1D Tensor assigning weight to each of the classes. Otherwise, it is treated as if having all ones. Outputs (1 - 2) ================== output (differentiable) : T Weighted loss float Tensor. If reduction is 'none', this has the shape of [batch_size], or [batch_size, D1, D2, ..., Dk] in case of K-dimensional loss. Otherwise, it is a scalar. log_prob (optional, differentiable) : T Log probability tensor. If the output of softmax is prob, its value is log(prob). Type Constraints =================== T : tensor(float16), tensor(float), tensor(double), tensor(bfloat16) (Currently not supported in MIGX) Constrain input and output types to float tensors. Tind : tensor(int32), tensor(int64) Constrain target to integer types */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_softmaxcrossentropyloss : op_parser { std::vector operators() const { return {{"SoftmaxCrossEntropyLoss", "softmaxcrossentropyloss"}, {"NegativeLogLikelihoodLoss", "negativelikelihoodloss"}}; } // Handle ignore index if it's within range of allowable classes // return false if ignore index out of bounds and never add literal to graph // return true if ignore index is in bound and pass back literal bool normalize_input_index(const onnx_parser& parser, const onnx_parser::node_info& info, instruction_ref& ignore_index, int64_t& ignore_index_val) const { bool has_ignore_index = contains(info.attributes, "ignore_index"); if(has_ignore_index) { ignore_index_val = parser.parse_value(info.attributes.at("ignore_index")).at(); ignore_index = info.add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int64_type, {1}, {0}), {ignore_index_val})); return true; } return false; } std::string get_reduction_param(const onnx_parser::node_info& info, const std::string& name) const { std::string reduction = "mean"; if(contains(info.attributes, "reduction")) { reduction = info.attributes.at("reduction").s(); if(not contains({"mean", "sum", "none"}, reduction)) { MIGRAPHX_THROW(name + ":Invalid reduction mode: " + reduction + "\n Valid options are [none, mean, sum]"); } } return reduction; } instruction_ref get_scores(const instruction_ref& arg, const std::string& name) const { auto scores = arg; auto scores_shape = scores->get_shape(); if(scores_shape.ndim() < 2) { MIGRAPHX_THROW(name + "Scores must be two or more dimensions [batch, class_size, D1...Dk]"); } if(migraphx::shape::is_integral(scores_shape.type())) { MIGRAPHX_THROW(name + ": Score must be either half, float, or double type"); } return scores; } instruction_ref get_labels(const instruction_ref& arg, const std::string& name, const shape& scores_shape) const { auto labels = arg; auto label_shape = labels->get_shape(); if(label_shape.type() != migraphx::shape::int32_type and label_shape.type() != migraphx::shape::int64_type) { MIGRAPHX_THROW(name + ": Labels must either be int32 or int64 types"); } if(scores_shape.lens()[0] != label_shape.lens()[0]) { MIGRAPHX_THROW(name + ": Score and Labels must identical batch size inputs"); } if((scores_shape.ndim() - 1) != label_shape.ndim()) { MIGRAPHX_THROW(name + ": Score and Labels must contain identical K-Dimensions"); } // Check that K-Dimensions are equal between scores and labels if(label_shape.ndim() > 1) { auto score_len = scores_shape.lens(); auto label_len = label_shape.lens(); if(not std::equal(score_len.begin() + 2, score_len.end(), label_len.begin() + 1)) { MIGRAPHX_THROW(name + ": K-Dimensions must be equal values between " "score and labels"); } } return labels; } instruction_ref get_weights(const onnx_parser::node_info& info, const std::string& name, const std::vector& args, const shape& scores_shape, size_t class_size) const { // Default weights will always be 1 auto weights = info.add_literal( migraphx::literal(migraphx::shape(scores_shape.type(), {1}, {0}), {1})); weights = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights); bool has_weights = (args.size() > 2); // Get optional input weights (Used for mean reduction) if(has_weights) { weights = args.at(2); auto weights_shape = weights->get_shape(); if(weights_shape.lens()[0] != scores_shape.lens()[1]) { MIGRAPHX_THROW(name + ": Invalid weight vector shape. Weight must " "contain weight for each class"); } if(migraphx::shape::is_integral(weights_shape.type())) { MIGRAPHX_THROW(name + ": weight must be either half, float, or double type"); } if(weights_shape.type() != scores_shape.type()) { MIGRAPHX_THROW(name + ": Weight and Scores inputs must be the same type"); } } // Always make weights negative saves pointwise after indexing weights = info.add_instruction(migraphx::make_op("neg"), weights); return weights; } instruction_ref handle_index_selection(const onnx_parser::node_info& info, const instruction_ref labels) const { // Pick out the coordinates from the inputs to gerneate the proper indicies to gather // what will be operated on later. // Use label indices to select weights auto labels_unsq = info.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto label_shape = labels->get_shape(); auto labels_rank = labels_unsq->get_shape().ndim(); std::vector coordinate_index_literals; auto lengths = labels_unsq->get_shape().lens(); for(size_t axis = 0; axis < (labels_rank - 1); axis++) { auto len_val = lengths.at(axis); // Trying to replicate torch arrange() here. std::vector vect_of_lit(len_val); std::iota(vect_of_lit.begin(), vect_of_lit.end(), 0); auto batch_dim_indicies = info.add_literal(migraphx::shape(label_shape.type(), {len_val}), vect_of_lit); // This is supposed to do unsq_dims = [:a] + [a + 1:] std::vector unsq_dims(labels_rank); std::iota(unsq_dims.begin(), unsq_dims.end(), 0); auto it = unsq_dims.begin(); it += axis; unsq_dims.erase(it); auto batch_dim_index_unsq = info.add_instruction( migraphx::make_op("unsqueeze", {{"axes", unsq_dims}}), batch_dim_indicies); auto batch_dim_indicies_bc = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels_unsq->get_shape().lens()}}), batch_dim_index_unsq); coordinate_index_literals.push_back(batch_dim_indicies_bc); } coordinate_index_literals.push_back(labels_unsq); return info.add_instruction(migraphx::make_op("concat", {{"axis", -1}}), coordinate_index_literals); } instruction_ref handle_reduction(const onnx_parser::node_info& info, const instruction_ref loss_tensor, const instruction_ref weights, const std::string& reduction, bool has_weights) const { instruction_ref final_loss_tensor = loss_tensor; // Used for reductions std::vector loss_dims(loss_tensor->get_shape().ndim()); std::iota(loss_dims.begin(), loss_dims.end(), 0); // Add reduction step after we're generated crossentropyloss tensor and rearragned weight // scaling tensor if(reduction == "mean" and has_weights) { std::vector weight_dims(weights->get_shape().ndim()); std::iota(weight_dims.begin(), weight_dims.end(), 0); final_loss_tensor = info.add_instruction( migraphx::make_op("reduce_sum", {{"axes", loss_dims}}), final_loss_tensor); auto reduced_weights = info.add_instruction( migraphx::make_op("reduce_sum", {{"axes", weight_dims}}), weights); reduced_weights = info.add_instruction(migraphx::make_op("neg"), reduced_weights); final_loss_tensor = info.add_instruction(migraphx::make_op("div"), final_loss_tensor, reduced_weights); } else if(reduction == "mean" and not has_weights) { final_loss_tensor = info.add_instruction( migraphx::make_op("reduce_mean", {{"axes", loss_dims}}), final_loss_tensor); } else if(reduction == "sum") { final_loss_tensor = info.add_instruction( migraphx::make_op("reduce_sum", {{"axes", loss_dims}}), final_loss_tensor); } return final_loss_tensor; } instruction_ref handle_ignored_labels(const onnx_parser::node_info& info, const instruction_ref labels, const instruction_ref ignore_index, const instruction_ref loss_tensor) const { auto labels_shape = labels->get_shape(); auto ignore_idx_bc = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels_shape.lens()}}), ignore_index); auto conv_labels = info.add_instruction( migraphx::make_op("convert", {{"target_type", ignore_index->get_shape().type()}}), labels); std::vector zero_val_vect(labels_shape.elements(), 0); auto zero_vector = info.add_literal(migraphx::literal(loss_tensor->get_shape(), zero_val_vect)); auto equals_mask = info.add_instruction(migraphx::make_op("equal"), conv_labels, ignore_idx_bc); // If the any label is equal to ignore index, zero out the final tensor value return info.add_instruction( migraphx::make_op("where"), equals_mask, zero_vector, loss_tensor); } std::vector parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, const std::vector& args) const { // Get the op name to be used for parsing std::string op_name{opd.op_name}; auto is_softmaxcrossentropy = (opd.op_name == "softmaxcrossentropyloss"); // Get and handle attributes auto reduction = get_reduction_param(info, op_name); // Get and validate Inputs auto scores = get_scores(args.at(0), op_name); auto scores_shape = scores->get_shape(); auto labels = get_labels(args.at(1), op_name, scores_shape); // Meta parameters based on input scores shape size_t ndims = scores_shape.ndim(); size_t class_size = scores_shape.lens().at(1); bool is_k_dim = (ndims >= 3); // Ensure first k-th dimension is greater then one if ndims == 3 if(ndims == 3) { auto last_dim = scores_shape.lens().at(2); if(last_dim < 2) is_k_dim = false; } // Ignore_index is optional attribute, assign this as a scalar literal input to the op instruction_ref ignore_index; int64_t ignore_index_val = -1; auto has_ignore_index = normalize_input_index(parser, info, ignore_index, ignore_index_val); bool has_weights = (args.size() > 2); instruction_ref weights = get_weights(info, op_name, args, scores_shape, class_size); // Adjust weights based on ignore index if its in bounds of [0, class_size) if that's set to // reduce output after mul to zero. Saves us from doing a where() here and just scale at the // end if(has_ignore_index and (ignore_index_val < class_size and ignore_index_val >= 0)) { auto weights_shape = weights->get_shape(); std::vector zero_val_vect(weights_shape.elements(), 0); auto zero_val = info.add_literal(migraphx::literal(weights_shape, zero_val_vect)); weights = info.add_instruction( migraphx::make_op("scatter_none", {{"axis", 0}}), weights, ignore_index, zero_val); } if(is_softmaxcrossentropy) { // Need to perform softmax on all the data before we select final axes scores = info.add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), scores); } // Index selection before loss calculation completed auto gathernd_indicies = handle_index_selection(info, labels); std::vector perm(class_size, 0); if(is_k_dim) { std::iota(perm.begin() + 1, perm.end(), 2); perm.at(class_size - 1) = 1; scores = info.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), scores); } scores = info.add_instruction(migraphx::make_op("gathernd"), scores, gathernd_indicies); std::vector axis_list(ndims - 1, 0); std::iota((axis_list.begin() + 1), axis_list.end(), 2); weights = info.add_instruction(migraphx::make_op("unsqueeze", {{"axes", axis_list}}), weights); weights = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores_shape.lens()}}), weights); if(is_k_dim) weights = info.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), weights); weights = info.add_instruction(migraphx::make_op("gathernd"), weights, gathernd_indicies); // Do pointwise operators on the final set of indicies and scores we care about rather than // before so that we're not doing a bunch of pointwise on items that aren't part of the loss // calulation. auto log_sm_scores = scores; if(is_softmaxcrossentropy) { log_sm_scores = info.add_instruction(migraphx::make_op("log"), scores); } // Always multiply out the weights. auto weighted_result = info.add_instruction(migraphx::make_op("mul"), log_sm_scores, weights); auto loss_tensor = handle_reduction(info, weighted_result, weights, reduction, has_weights); // Handle the case where label == ignore_index regardless if label or ignore index are // outside of the range of [0, Class_size) if(has_ignore_index and ((ignore_index_val < 0) or (ignore_index_val >= class_size))) { loss_tensor = handle_ignored_labels(info, labels, ignore_index, loss_tensor); } if(is_softmaxcrossentropy) return {loss_tensor, log_sm_scores}; else return {loss_tensor}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_softplus.cpp000066400000000000000000000045161510465702400220740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_softplus : op_parser { std::vector operators() const { return {{"Softplus"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { // Apply pointwise formula: y = ln(exp(x) + 1) auto mb_ones = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", args[0]->get_shape().lens()}}), info.add_literal(migraphx::literal{migraphx::shape{args[0]->get_shape().type()}, {1}})); auto exp = info.add_instruction(migraphx::make_op("exp"), args[0]); auto add = info.add_instruction(migraphx::make_op("add"), exp, mb_ones); return info.add_instruction(migraphx::make_op("log"), add); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_softsign.cpp000066400000000000000000000045261510465702400220520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_softsign : op_parser { std::vector operators() const { return {{"Softsign"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { // Apply pointwise formula: y = x / (1 + |x|) auto mb_ones = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", args[0]->get_shape().lens()}}), info.add_literal(migraphx::literal{migraphx::shape{args[0]->get_shape().type()}, {1}})); auto abs = info.add_instruction(migraphx::make_op("abs"), args[0]); auto add = info.add_instruction(migraphx::make_op("add"), abs, mb_ones); return info.add_instruction(migraphx::make_op("div"), args[0], add); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_spacetodepth.cpp000066400000000000000000000067271510465702400227060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_spacetodepth : op_parser { std::vector operators() const { return {{"SpaceToDepth"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { auto s = args[0]->get_shape(); // blocksize attribute of SpaceToDepth int blocksize = 1; // if blockSize of 1 then, this is a no-op if(contains(info.attributes, "blocksize")) { blocksize = info.attributes.at("blocksize").i(); } if(blocksize < 1) { // blockSize less than 1 would rather result in DepthToSpace instead of SpaceToDepth MIGRAPHX_THROW("SpaceToDepth: blocksize is less than 1"); } // calculate dimensions auto res_lens = s.lens(); // {N, C, H, W} if(((res_lens[2] % blocksize) == 0) and ((res_lens[3] % blocksize) == 0)) { // Co = C * (blocksize ^ 2) res_lens[1] = res_lens[1] * blocksize * blocksize; // Ho = (H / blocksize) res_lens[2] = res_lens[2] / blocksize; // Wo = (W / blocksize) res_lens[3] = res_lens[3] / blocksize; } // res_shape = (N, Co, Ho, Wo) else MIGRAPHX_THROW("SpaceToDepth: div by blocksize quotient not int "); auto trans_lens = s.lens(); // {N, C, H, W} trans_lens[2] = res_lens[2]; trans_lens[3] = blocksize; trans_lens.push_back(res_lens[3]); trans_lens.push_back(blocksize); // {N, C, Ho, blocksize, Wo, blocksize} std::vector perm = {0, 3, 5, 1, 2, 4}; auto temp1 = info.add_instruction(make_op("reshape", {{"dims", trans_lens}}), args[0]); auto temp2 = info.add_instruction(make_op("transpose", {{"permutation", perm}}), temp1); return info.add_instruction(make_op("reshape", {{"dims", res_lens}}), temp2); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_split.cpp000066400000000000000000000176041510465702400213520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { static auto parse_dyn_split(const onnx_parser::node_info& info, const std::vector& args, int64_t tuned_axis) { if(contains(info.attributes, "split")) { MIGRAPHX_THROW("PARSE_SPLIT: dynamic input and non-fixed split axis and `split` " "attribute not supported"); } if(args.size() == 2) { MIGRAPHX_THROW("PARSE_SPLIT: dynamic input and non-fixed split axis and `split` " "input not supported"); } std::size_t num_outputs = info.num_outputs; std::vector ret_ins(num_outputs); // Doing shape calculations for the splits in the graph auto split_dim = info.add_instruction( make_op("dimensions_of", {{"start", tuned_axis}, {"end", tuned_axis + 1}}), args[0]); shape int64_scalar_shape{shape::int64_type, {1}, {0}}; auto num_outputs_lit = info.add_literal(literal{int64_scalar_shape, {num_outputs}}); auto num_outputs_minus_1_lit = info.add_literal(literal{int64_scalar_shape, {num_outputs - 1}}); // (A + (B - 1)) / B == ceil(A / B) auto chunk_size = info.add_instruction( make_op("div"), info.add_instruction(make_op("add"), split_dim, num_outputs_minus_1_lit), num_outputs_lit); for(int n = 0; n < num_outputs - 1; ++n) { // slice(input, starts = {n * chunk_size}, ends = {(n+1) * chunk_size}); axes = // {tuned_axis} ret_ins.at(n) = info.add_instruction( make_op("slice", {{"axes", {tuned_axis}}}), args[0], info.add_instruction( make_op("mul"), chunk_size, info.add_literal(literal{int64_scalar_shape, {n}})), info.add_instruction(make_op("mul"), chunk_size, info.add_literal(literal{int64_scalar_shape, {n + 1}}))); } // last slice: slice(input, starts = {n * chunk_size}); ends = max_int, axes = // {tuned_axis} ret_ins.at(num_outputs - 1) = info.add_instruction( make_op("slice", {{"axes", {tuned_axis}}, {"ends", {std::numeric_limits::max()}}}), args[0], info.add_instruction(make_op("mul"), chunk_size, info.add_literal(literal{int64_scalar_shape, {num_outputs - 1}}))); return ret_ins; } static auto parse_static_split(const onnx_parser::node_info& info, const onnx_parser& parser, const std::vector& args, int64_t tuned_axis) { const auto& input_shape = args[0]->get_shape(); // either static shape or fixed dynamic_dimension for split axis auto tuned_axis_len = input_shape.to_static(0).lens().at(tuned_axis); std::vector vec_splits; if(contains(info.attributes, "split")) { literal s = parser.parse_value(info.attributes.at("split")); s.visit([&](auto v) { vec_splits.assign(v.begin(), v.end()); }); } else if(args.size() == 2) { auto s = args[1]->eval(); check_arg_empty(s, "PARSE_SPLIT: non-constant `split` input is not supported"); s.visit([&](auto v) { vec_splits.assign(v.begin(), v.end()); }); } // no split attribute, input is equally divided else { std::size_t num_outputs = info.num_outputs; // the num_outputs attribute seems to be redundant since we already have // node_info::num_outputs, but we can still perform an error check if(contains(info.attributes, "num_outputs")) { num_outputs = parser.parse_value(info.attributes.at("num_outputs")).at(); if(num_outputs != info.num_outputs) { MIGRAPHX_THROW("PARSE_SPLIT: num_outputs attribute " + std::to_string(num_outputs) + " doesn't match actual number of outputs " + std::to_string(info.num_outputs) + "!"); } } if(tuned_axis_len % num_outputs == 0) { std::size_t chunk_size = tuned_axis_len / num_outputs; vec_splits.resize(num_outputs, chunk_size); } else { std::size_t chunk_size = tuned_axis_len / num_outputs + 1; std::size_t last_chunk_size = tuned_axis_len - chunk_size * (num_outputs - 1); vec_splits.resize(num_outputs - 1, chunk_size); vec_splits.push_back(last_chunk_size); } } if(std::accumulate(vec_splits.begin(), vec_splits.end(), int64_t(0)) != static_cast(tuned_axis_len)) { MIGRAPHX_THROW( "PARSE_SPLIT: sum of split attribute unequal to dim size of axis! tuned axis:" + std::to_string(tuned_axis_len) + " Output " + to_string_range(vec_splits) + " Rank " + std::to_string(input_shape.ndim())); } std::vector ret_ins; int64_t start = 0; for(auto sl : vec_splits) { ret_ins.push_back(info.add_instruction( make_op("slice", {{"axes", {tuned_axis}}, {"starts", {start}}, {"ends", {start + sl}}}), args[0])); start += sl; } return ret_ins; } struct parse_split : op_parser { std::vector operators() const { return {{"Split"}}; } std::vector parse(const op_desc& opd, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { int64_t axis = 0; if(contains(info.attributes, "axis")) { axis = parser.parse_value(info.attributes.at("axis")).at(); } const auto& input_shape = args[0]->get_shape(); // axis over which the split occurs (split_axis) int64_t tuned_axis = tune_axis(input_shape.ndim(), axis, opd.onnx_name); auto split_axis_is_fixed = [&]() { return input_shape.dyn_dims().at(tuned_axis).is_fixed(); }; if(input_shape.dynamic() and not split_axis_is_fixed()) { return parse_dyn_split(info, args, tuned_axis); } else { return parse_static_split(info, parser, args, tuned_axis); } } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_squeeze.cpp000066400000000000000000000051221510465702400216700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_squeeze : op_parser { std::vector operators() const { return {{"Squeeze", "squeeze"}, {"Unsqueeze", "unsqueeze"}}; } operation assign_axes(operation& op, const std::vector& axes) const { auto v = op.to_value(); v["axes"] = axes; op.from_value(v); return op; } instruction_ref parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { auto op = parser.load(opd.op_name, info); if(args.size() == 2) { auto arg_axes = args.at(1)->eval(); check_arg_empty(arg_axes, "PARSE_" + opd.op_name + ": cannot handle variable axes!"); std::vector axes; arg_axes.visit([&](auto s) { axes.assign(s.begin(), s.end()); }); op = assign_axes(op, axes); } auto arg = info.make_contiguous(args.front()); return info.add_instruction(op, arg); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_thresholdedrelu.cpp000066400000000000000000000053451510465702400234130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_thresholdedrelu : op_parser { std::vector operators() const { return {{"ThresholdedRelu"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { float alpha = 1.0; if(contains(info.attributes, "alpha")) alpha = parser.parse_value(info.attributes.at("alpha")).at(); auto x_shape = args[0]->get_shape(); auto lit_zero = info.add_literal(migraphx::literal{migraphx::shape{x_shape.type()}, {0}}); auto lit_alpha = info.add_literal(migraphx::literal{migraphx::shape{x_shape.type()}, {alpha}}); auto mb_zero = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x_shape.lens()}}), lit_zero); auto mb_alpha = info.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x_shape.lens()}}), lit_alpha); auto condition = info.add_instruction(migraphx::make_op("greater"), args[0], mb_alpha); return info.add_instruction(migraphx::make_op("where"), condition, args[0], mb_zero); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_tile.cpp000066400000000000000000000046121510465702400211470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_tile : op_parser { std::vector operators() const { return {{"Tile"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { migraphx::argument arg_s = args[1]->eval(); check_arg_empty(arg_s, "PARSE_TILE: dynamic shape is not supported"); std::vector repeats; arg_s.visit([&](auto input) { repeats.assign(input.begin(), input.end()); }); auto l0 = args[0]; for(int i = 0; i < repeats.size(); i++) { auto l1 = l0; for(int j = 1; j < repeats[i]; j++) { l0 = info.add_instruction(make_op("concat", {{"axis", i}}), l0, l1); } } return l0; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_topk.cpp000066400000000000000000000056431510465702400211740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_topk : op_parser { std::vector operators() const { return {{"TopK"}}; } std::vector parse(const op_desc& /*opd*/, const onnx_parser& parser, onnx_parser::node_info info, std::vector args) const { int64_t k = 0; if(args.size() == 2) { auto arg_k = args.at(1)->eval(); check_arg_empty(arg_k, "PARSE_TopK: k input must be constant"); k = arg_k.at(); } else if(contains(info.attributes, "k")) { k = info.attributes.at("k").i(); } bool largest = true; if(contains(info.attributes, "largest")) { largest = static_cast(info.attributes.at("largest").i()); } int64_t axis = -1; if(contains(info.attributes, "axis")) { axis = parser.parse_value(info.attributes.at("axis")).at(); } auto topk_ret = info.add_instruction( make_op("topk", {{"k", k}, {"axis", axis}, {"largest", largest}}), args.at(0)); auto ret_val = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), topk_ret); auto ret_ind = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), topk_ret); return {ret_val, ret_ind}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_transpose.cpp000066400000000000000000000050421510465702400222260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_transpose : op_parser { std::vector operators() const { return {{"Transpose"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, onnx_parser::node_info info, std::vector args) const { std::vector perm{}; if(contains(info.attributes, "perm")) { auto&& perm_vals = info.attributes["perm"].ints(); perm = std::vector(perm_vals.begin(), perm_vals.end()); } // if perm is empty, use the default value auto n_dim = args.front()->get_shape().ndim(); if(perm.empty()) { perm.resize(n_dim); std::iota(perm.rbegin(), perm.rend(), 0); } if(perm.size() != n_dim) { MIGRAPHX_THROW("PARSE_TRANSPOSE: perm and input have diffferent number of dims!"); } return info.add_instruction(make_op("transpose", {{"permutation", perm}}), args.front()); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_trilu.cpp000066400000000000000000000063251510465702400213540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_trilu : op_parser { std::vector operators() const { return {{"Trilu"}}; } instruction_ref parse(const op_desc&, const onnx_parser&, const onnx_parser::node_info& info, std::vector args) const { auto input_shape = args[0]->get_shape(); assert(input_shape.ndim() >= 2); auto input_lens = input_shape.lens(); size_t num_rows = *(input_lens.rbegin() + 1); size_t num_cols = input_lens.back(); int k = 0; bool upper = true; if(args.size() > 1) { auto arg_k = args[1]->eval(); check_arg_empty(arg_k, "PARSE_TRILU: dynamic k not supported"); k = arg_k.at(); } if(contains(info.attributes, "upper")) { upper = static_cast(info.attributes.at("upper").i()); } shape::type_t output_type = args[0]->get_shape().type(); // when creating the mask, if upper == 1, // the inner triangle will have values set to 0 std::vector mask_mat(num_rows * num_cols, upper); // if upper == 0, kth diagonal must also be masked if(not upper) k++; for(size_t i = 0; i < num_rows; i++) { for(int j = 0; j < std::min(k, static_cast(num_cols)); j++) { mask_mat[i * num_cols + j] = not upper; } k++; } auto mask = info.add_literal( migraphx::literal{migraphx::shape{output_type, {num_rows, num_cols}}, mask_mat}); return info.add_broadcastable_binary_op("mul", mask, args[0]); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_unique.cpp000066400000000000000000000074731510465702400215300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { // generate unique output stream y, given input stream x; // // case unsorted: // input x: [2, 1, 1, 3, 4, 3], attr_sorted = 0; // output(s): // y: [2, 1, 3, 4] --- the unique output // y_indices: [0, 1, 3, 4] --- first incidence, in terms of indices of x // x_rev_indices: [0, 1, 1, 2, 3, 2] --- x seen in terms of indices of y // y_count: [1, 2, 2, 1] -- count at each y_index. sum = len(x) // // case sorted: // input x: [2, 1, 1, 3, 4, 3], attr_sorted = 1; // output(s): // y: [1, 2, 3, 4] --- the unique output // y_indices: [1, 0, 3, 4] --- first incidence, in terms of indices of x // x_rev_indices: [1, 0, 0, 2, 3, 2] --- x seen in terms of indices of y // y_count: [2, 1, 2, 1] -- count at each y_index. sum = len(x) struct parse_unique : op_parser { std::vector operators() const { return {{"Unique"}}; } std::vector parse(const op_desc& opd, const onnx_parser& parser, const onnx_parser::node_info& info, std::vector args) const { int64_t sorted = 1; // default = sorted. if(contains(info.attributes, "sorted")) sorted = parser.parse_value(info.attributes.at("sorted")).at(); std::optional axis; if(contains(info.attributes, "axis")) { auto n_dim = args[0]->get_shape().ndim(); axis = parser.parse_value(info.attributes.at("axis")).at(); axis = tune_axis(n_dim, *axis, opd.onnx_name); } migraphx::argument data_arg = args.back()->eval(); auto opr = axis ? migraphx::make_op("unique", {{"axis", *axis}, {"sorted", sorted}}) : migraphx::make_op("unique", {{"sorted", sorted}}); auto u_opr = info.add_instruction(opr, args.at(0)); auto i_y = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), u_opr); auto i_y_idx = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), u_opr); auto i_x_idx = info.add_instruction(make_op("get_tuple_elem", {{"index", 2}}), u_opr); auto i_count = info.add_instruction(make_op("get_tuple_elem", {{"index", 3}}), u_opr); return {i_y, i_y_idx, i_x_idx, i_count}; } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_variadic_op.cpp000066400000000000000000000042341510465702400224720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_variadic_op : op_parser { std::vector operators() const { return {{"Sum", "add"}, {"Max", "max"}, {"Min", "min"}}; } instruction_ref parse(const op_desc& opd, const onnx_parser&, onnx_parser::node_info info, std::vector args) const { return std::accumulate(std::next(args.begin()), args.end(), args.front(), [&](instruction_ref a, instruction_ref b) { return info.add_broadcastable_binary_op(opd.op_name, a, b); }); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/parse_where.cpp000066400000000000000000000071531510465702400213270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { struct parse_where : op_parser { std::vector operators() const { return {{"Where"}}; } instruction_ref parse(const op_desc& /*opd*/, const onnx_parser& /*parser*/, const onnx_parser::node_info& info, std::vector args) const { // TODO: broadcasting for dynamic shapes is only implemented // for binary ops at time of writing, not ternary ops. // When it becomes available, add multibroadcasting steps in the dynamic shape case. // For now for dynamic shapes, just insert the Where op. All shapes must be the // same for it to succeed. if(std::all_of(args.begin(), args.end(), [](auto v) { return v->get_shape().dynamic(); })) { return info.add_instruction(make_op("where"), args[0], args[1], args[2]); } else if(std::none_of( args.begin(), args.end(), [](auto v) { return v->get_shape().dynamic(); })) { // If shapes are static and any are broadcasted, insert multibroadcast ops auto lens = compute_broadcasted_lens(args[0]->get_shape().lens(), args[1]->get_shape().lens()); lens = compute_broadcasted_lens(lens, args[2]->get_shape().lens()); if(args[0]->get_shape().lens() != lens) { args[0] = info.add_instruction(make_op("multibroadcast", {{"out_lens", lens}}), args[0]); } if(args[1]->get_shape().lens() != lens) { args[1] = info.add_instruction(make_op("multibroadcast", {{"out_lens", lens}}), args[1]); } if(args[2]->get_shape().lens() != lens) { args[2] = info.add_instruction(make_op("multibroadcast", {{"out_lens", lens}}), args[2]); } return info.add_instruction(make_op("where"), args[0], args[1], args[2]); } else MIGRAPHX_THROW("PARSE_WHERE: doesn't support mixed static and dynamic shape inputs"); } }; } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/pooling.cpp000066400000000000000000000221761510465702400204740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { value handle_pooling_values(const op_desc& opd, onnx_parser::node_info info, const shape& in_shape, value values) { auto kdims = in_shape.ndim() - 2; if(starts_with(opd.onnx_name, "Global") or starts_with(opd.onnx_name, "QLinearGlobal")) { // if spatial dimensions are dynamic use dyn_global flag if(in_shape.dynamic() and std::any_of(in_shape.dyn_dims().cbegin() + 2, in_shape.dyn_dims().cend(), [](const auto& dd) { return not dd.is_fixed(); })) { values["dyn_global"] = true; values["lengths"] = std::vector(); } else { // works with static and fixed dynamic shape auto m_lens = in_shape.max_lens(); values["lengths"] = std::vector(m_lens.begin() + 2, m_lens.end()); } } if(contains(info.attributes, "ceil_mode")) { values["ceil_mode"] = static_cast(info.attributes.at("ceil_mode").i()); } if(contains(info.attributes, "strides")) { values["stride"].clear(); copy(info.attributes["strides"].ints(), std::back_inserter(values["stride"])); check_attr_sizes(kdims, values["stride"].size(), "PARSE_POOLING: inconsistent strides"); } if(contains(info.attributes, "kernel_shape")) { values["lengths"].clear(); copy(info.attributes["kernel_shape"].ints(), std::back_inserter(values["lengths"])); check_attr_sizes(kdims, values["lengths"].size(), "PARSE_POOLING: inconsistent lengths"); } if(contains(info.attributes, "dilations")) { values["dilations"].clear(); copy(info.attributes["dilations"].ints(), std::back_inserter(values["dilations"])); check_attr_sizes( kdims, values["dilations"].size(), "PARSE_POOLING: inconsistent dilations"); } // lp_order attribute if(contains(info.attributes, "p")) { values["lp_order"] = info.attributes.at("p").i(); } // ensure pads available only when auto_pad is "NOT_SET" check_padding_mode(info, opd.onnx_name); return values; } instruction_ref add_pooling_op(const op_desc& opd, onnx_parser::node_info info, instruction_ref l0) { std::string mode = opd.op_name; const std::unordered_map mode_map = { {"max", op::pooling_mode::max}, {"average", op::pooling_mode::average}, {"lpnorm", op::pooling_mode::lpnorm}}; if(not contains(mode_map, mode)) { MIGRAPHX_THROW( "PARSE_POOLING: onnx pooling mode must be [\"max\", \"average\", \"lpnorm\"]"); } operation op = make_op("pooling", {{"mode", mode_map.at(mode)}}); value values = op.to_value(); auto in_shape = l0->get_shape(); assert(in_shape.ndim() > 2); auto kdims = in_shape.ndim() - 2; values = handle_pooling_values(opd, info, in_shape, values); // count include padding, if count include pad is 1, we always use // explicit pad int count_include_pad = 0; if(contains(info.attributes, "count_include_pad")) { if(in_shape.dynamic()) { MIGRAPHX_THROW("PARSE_POOLING: count_include_pad attribute is not supported for " "dynamic input shape"); } count_include_pad = info.attributes.at("count_include_pad").i(); } std::vector paddings; float pad_val = ((mode == "max") ? std::numeric_limits::lowest() : 0.0f); if(contains(info.attributes, "pads")) { values["padding"].clear(); copy(info.attributes["pads"].ints(), std::back_inserter(paddings)); check_attr_sizes( kdims, paddings.size() / 2, "PARSE_POOLING: inconsistent explicit paddings"); } if(paddings.size() != 2 * kdims) { paddings.resize(kdims * 2); std::fill_n(paddings.begin(), 2 * kdims, 0); } if(values["padding"].size() != kdims) { values["padding"].resize(kdims); std::fill_n(values["padding"].begin(), kdims, 0); } if(values["stride"].size() != kdims) { values["stride"].resize(kdims); std::fill_n(values["stride"].begin(), kdims, 1); } if(values["dilations"].size() != kdims) { values["dilations"].resize(kdims); std::fill_n(values["dilations"].begin(), kdims, 1); } // used to calculate the supposed output shape std::vector orig_padding = paddings; // TODO: add parsing for dilations if(contains(info.attributes, "auto_pad") and to_upper(info.attributes["auto_pad"].s()) != "NOTSET") { auto auto_pad = to_upper(info.attributes["auto_pad"].s()); // don't use the given padding sizes, if any // values["padding"].clear(); if(in_shape.dynamic()) { // set padding_mode to trigger auto padding at runtime bool is_same_upper = (auto_pad.find("SAME_UPPER") != std::string::npos); values["padding_mode"] = is_same_upper ? to_value(op::padding_mode_t::same_upper) : to_value(op::padding_mode_t::same_lower); } else { // Calculate auto padding // dilations (argument 4) not supported; default to all 1's cal_auto_padding_size(info, values, values["lengths"].to_vector(), values["dilations"].to_vector(), in_shape.lens(), paddings); values["padding"] = paddings; // default padding_mode indicates that padding sizes are not calculated dynamically values["padding_mode"] = migraphx::op::padding_mode_t::default_; } } std::vector slice_start; std::vector slice_end; tune_padding_size(values, paddings, count_include_pad, slice_start); if(not slice_start.empty()) { if(in_shape.dynamic()) { MIGRAPHX_THROW( "PARSE_POOLING: asymmetric padding not supported for dynamic input shape"); } // calculate expected output shape orig_padding.insert(orig_padding.begin() + kdims, 2, 0); orig_padding.insert(orig_padding.begin(), 2, 0); op::pad pad{orig_padding, 0.0f}; shape padded_shape = pad.compute_shape({l0->get_shape()}); // make an op just to get its output shape auto out_lens = make_op("pooling", values).compute_shape({padded_shape}).lens(); // compute slice_end information slice_end.resize(slice_start.size()); std::transform(out_lens.begin() + 2, out_lens.end(), slice_start.begin(), slice_end.begin(), [](auto i, auto j) { return i + j; }); } values["padding"] = std::vector(paddings.begin(), paddings.end()); check_asym_padding(info, l0, paddings, values, count_include_pad, pad_val); op.from_value(values); auto l1 = info.add_instruction(op, l0); if(not slice_start.empty()) { std::vector axes(kdims); std::iota(axes.begin(), axes.end(), 2); l1 = info.add_instruction( make_op("slice", {{"axes", axes}, {"starts", slice_start}, {"ends", slice_end}}), l1); } return l1; } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/onnx/quantize_dequantize_linear.cpp000066400000000000000000000133741510465702400244500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { std::vector transform_quantize_dequantize_linear_inputs(const onnx_parser::node_info& info, const std::string& onnx_name, int block_size, int axis, std::vector args) { const auto x = args.at(0); const auto x_lens = x->get_shape().lens(); const auto x_rank = x_lens.size(); instruction_ref y_scale = args.at(1); const auto y_scale_lens = y_scale->get_shape().lens(); const auto y_scale_rank = y_scale_lens.size(); // Per-tensor (per-layer) granularity if(y_scale->get_shape().elements() == 1) { std::transform(args.begin() + 1, args.end(), args.begin() + 1, [&](auto ins) { return info.add_instruction(make_op("multibroadcast", {{"out_lens", x_lens}}), ins); }); } // Per-axis granularity else if(y_scale_rank == 1) { axis = tune_axis(x_rank, axis, onnx_name); if(x_lens[axis] != y_scale_lens[0]) { MIGRAPHX_THROW(onnx_name + ": For per axis granularity the length of y_scale (actual: " + to_string(y_scale_lens[0]) + ") must be equal to size of x on axis " + to_string(axis) + "(actual: " + to_string(x_lens[axis]) + ")"); } std::transform(args.begin() + 1, args.end(), args.begin() + 1, [&](auto ins) { return info.add_instruction( make_op("broadcast", {{"axis", axis}, {"out_lens", x_lens}}), ins); }); } // Blocked granularity else { axis = tune_axis(x_rank, axis, onnx_name); if(x_rank != y_scale_rank) { MIGRAPHX_THROW(onnx_name + ": x(rank: " + to_string(x_rank) + ") and y_scale(rank: " + to_string(y_scale_rank) + ") must be of same rank for block granularity"); } for(auto i = 0u; i < x_lens.size(); ++i) { if(x_lens[i] != y_scale_lens[i] and i != axis) { MIGRAPHX_THROW(onnx_name + ": x(shape: " + to_string_range(x_lens) + ") and y_scale(shape: " + to_string_range(y_scale_lens) + ") shapes may only differ along provided axis(" + to_string(axis) + ")"); } } // Given x shape (D0, ..., Di, ..., Dn), y_scale shape (S0, ... Si, ...Sn) and // axis=i, the accepted range is [ceil(Di/Si), ceil(Di/(Si-1))-1] float di = x_lens[axis]; float si = y_scale_lens[axis]; int block_size_min = std::ceil(di / si); int block_size_max = std::ceil(di / (si - 1)) - 1; // default block_size if not given is calculated (to support quark generated models): if(block_size == 0) block_size = block_size_min; if(block_size < block_size_min or block_size > block_size_max) MIGRAPHX_THROW(onnx_name + ": Block size(actual: " + to_string(block_size) + ") must be within range [" + to_string(block_size_min) + ", " + to_string(block_size_max) + "]"); std::transform(args.begin() + 1, args.end(), args.begin() + 1, [&](auto ins) { if(block_size == 1) return ins; ins = info.add_instruction(make_op("unsqueeze", {{"axes", {axis + 1}}}), ins); auto bc_lens = ins->get_shape().lens(); bc_lens[axis + 1] = block_size; ins = info.add_instruction(make_op("multibroadcast", {{"out_lens", bc_lens}}), ins); auto reshape_lens = x_lens; reshape_lens[axis] = ins->get_shape().lens()[axis] * block_size; ins = info.add_instruction(make_op("reshape", {{"dims", reshape_lens}}), ins); // Detect runt block if(x_lens[axis] < reshape_lens[axis]) { ins = info.add_instruction( make_op("slice", {{"axes", {axis}}, {"starts", {0}}, {"ends", {x_lens[axis]}}}), ins); } return ins; }); } return args; } } // namespace onnx } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/000077500000000000000000000000001510465702400157455ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/op/builder/000077500000000000000000000000001510465702400173735ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/op/builder/batchnorm.cpp000066400000000000000000000110271510465702400220550ustar00rootroot00000000000000/* The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { struct batchnorm : op_builder { float epsilon = 1e-5f; template static auto reflect(Self& self, F f) { return pack(f(self.epsilon, "epsilon")); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { auto x_lens = args[0]->get_shape().max_lens(); auto x_type = args[0]->get_shape().type(); if(std::any_of(args.cbegin() + 1, args.cend(), [](auto a) { return a->get_shape().lens().size() != 1; })) { MIGRAPHX_THROW("batchnorm op_builder: argument scale, bias, mean, or var rank != 1"); } auto x_rank = x_lens.size(); if(x_rank == 1 or x_rank == 2) { auto eps = m.add_literal(migraphx::literal{migraphx::shape{x_type}, {epsilon}}); auto x_sub_mean = insert_common_op(m, ins, "sub", args[0], args[3]); auto var_eps = insert_common_op(m, ins, "add", args[4], eps); auto rsqrt = m.insert_instruction(ins, make_op("rsqrt"), var_eps); auto mul0 = insert_common_op(m, ins, "mul", args[1], rsqrt); auto r0 = insert_common_op(m, ins, "mul", x_sub_mean, mul0); return {insert_common_op(m, ins, "add", r0, args[2])}; } else if(x_rank > 2) { // unsqueeze tensors of shape (C) to broadcast correctly std::vector unsqueeze_axes(x_lens.size() - 2); std::iota(unsqueeze_axes.begin(), unsqueeze_axes.end(), 1); auto eps = m.add_literal(migraphx::literal{migraphx::shape{x_type}, {epsilon}}); auto scale_unsqueeze = m.insert_instruction( ins, migraphx::make_op("unsqueeze", {{"axes", unsqueeze_axes}}), args[1]); auto bias_unsqueeze = m.insert_instruction( ins, migraphx::make_op("unsqueeze", {{"axes", unsqueeze_axes}}), args[2]); auto mean_unsqueeze = m.insert_instruction( ins, migraphx::make_op("unsqueeze", {{"axes", unsqueeze_axes}}), args[3]); auto var_unsqueeze = m.insert_instruction( ins, migraphx::make_op("unsqueeze", {{"axes", unsqueeze_axes}}), args[4]); auto x_sub_mean = insert_common_op(m, ins, "sub", args[0], mean_unsqueeze); auto var_eps = insert_common_op(m, ins, "add", var_unsqueeze, eps); auto rsqrt = m.insert_instruction(ins, make_op("rsqrt"), var_eps); auto mul0 = insert_common_op(m, ins, "mul", scale_unsqueeze, rsqrt); auto r0 = insert_common_op(m, ins, "mul", x_sub_mean, mul0); return {insert_common_op(m, ins, "add", r0, bias_unsqueeze)}; } else { // rank == 0 MIGRAPHX_THROW("batchnorm op_builder: rank " + std::to_string(x_lens.size()) + " input tensor, unhandled data format"); } } }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/builder/celu.cpp000066400000000000000000000065711510465702400210400ustar00rootroot00000000000000/* The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { struct celu : op_builder { float alpha = 1.0f; template static auto reflect(Self& self, F f) { return pack(f(self.alpha, "alpha")); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { if(float_equal(alpha, 0.0f)) { MIGRAPHX_THROW("celu op_builder: alpha is zero (division by zero)"); } auto input_lens = args[0]->get_shape().lens(); auto input_type = args[0]->get_shape().type(); if(input_type != migraphx::shape::float_type) { MIGRAPHX_THROW("celu op_builder: input tensor not float type"); } auto zero_lit = m.add_literal({input_type, {0.}}); zero_lit = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), zero_lit); auto one_lit = m.add_literal({input_type, {1.}}); one_lit = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), one_lit); auto alpha_lit = m.add_literal({input_type, {alpha}}); alpha_lit = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), alpha_lit); auto linear_part = insert_common_op(m, ins, "max", zero_lit, args[0]); auto divi = insert_common_op(m, ins, "div", args[0], alpha_lit); auto expo = insert_common_op(m, ins, "exp", divi); auto sub = insert_common_op(m, ins, "sub", expo, one_lit); auto mul = insert_common_op(m, ins, "mul", alpha_lit, sub); auto exp_part = insert_common_op(m, ins, "min", zero_lit, mul); return {insert_common_op(m, ins, "add", linear_part, exp_part)}; } }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/builder/clip.cpp000066400000000000000000000045261510465702400210350ustar00rootroot00000000000000/* The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { struct clip : op_builder { template static auto reflect(Self&, F) { return pack(); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { bool max_used = args.size() == 3 and not args[2]->is_undefined(); bool min_used = args.size() >= 2 and not args[1]->is_undefined(); if(min_used and max_used) return {insert_common_op(m, ins, make_op("clip"), args)}; if(max_used) return {insert_common_op(m, ins, "min", args[0], args[2])}; if(min_used) return {insert_common_op(m, ins, "max", args[0], args[1])}; return {m.insert_instruction(ins, make_op("identity"), args[0])}; } }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/builder/convolution.cpp000066400000000000000000000344641510465702400224710ustar00rootroot00000000000000/* The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { template struct convolution_base : op_builder { std::string auto_pad = "NOTSET"; std::vector paddings; std::vector strides; std::vector dilations; int group = 1; padding_mode_t padding_mode = padding_mode_t::default_; template static auto reflect(Self& self, F f) { return pack(f(self.auto_pad, "auto_pad"), f(self.paddings, "paddings"), f(self.strides, "strides"), f(self.dilations, "dilations"), f(self.group, "group"), f(self.padding_mode, "padding_mode")); } void validate_or_init_attributes(size_t kdims, const instruction_ref x, const instruction_ref w) { if(strides.empty()) { strides.resize(kdims); std::fill_n(strides.begin(), kdims, 1); } else if(strides.size() != kdims) { MIGRAPHX_THROW("Inconsistent strides size, is: " + std::to_string(strides.size()) + ", should be: " + std::to_string(kdims)); } if(dilations.empty()) { dilations.resize(kdims); std::fill_n(dilations.begin(), kdims, 1); } else if(dilations.size() != kdims) { MIGRAPHX_THROW("Inconsistent dilations size, is: " + std::to_string(dilations.size()) + ", should be: " + std::to_string(kdims)); } if(paddings.empty()) { paddings.resize(kdims); std::fill_n(paddings.begin(), kdims, 0); } else if(paddings.size() != kdims and paddings.size() != 2 * kdims) { MIGRAPHX_THROW("Inconsistent paddings size, is: " + std::to_string(paddings.size()) + ", should be: " + std::to_string(kdims) + " or: " + std::to_string(2 * kdims)); } if(contains(auto_pad, "SAME")) { if(is_dynamic(x->get_shape()) or is_dynamic(w->get_shape())) { // must calculate "same" padding with input shape data padding_mode = contains(auto_pad, "SAME_UPPER") ? op::padding_mode_t::same_upper : op::padding_mode_t::same_lower; } else { // kernel shape will be fixed, so max_lens() == min_len() for kernel lengths auto weight_lens = w->get_shape().max_lens(); std::vector k_lens(weight_lens.begin() + 2, weight_lens.end()); calc_auto_padding( auto_pad, strides, k_lens, dilations, x->get_shape().max_lens(), paddings); } } } bool is_dynamic(const shape& s) const { return s.dynamic() and std::any_of(s.dyn_dims().begin() + 2, s.dyn_dims().end(), [](const auto& dyn_dim) { return not dyn_dim.is_fixed(); }); } operation make_conv_op(const std::string& name) const { return make_op(name, {{"stride", strides}, {"dilation", dilations}, {"padding", paddings}, {"group", group}, {"padding_mode", padding_mode}}); } }; struct convolution : convolution_base { static std::string name() { return "convolution"; } std::vector insert(module& m, instruction_ref ins, const std::vector& args) { auto x = args[0]; auto weights = args[1]; auto in_lens = x->get_shape().max_lens(); assert(in_lens.size() > 2); auto kdims = in_lens.size() - 2; validate_or_init_attributes(kdims, x, weights); auto conv = m.insert_instruction(ins, make_conv_op("convolution"), x, weights); return {add_bias(m, ins, args, conv, 1)}; } // TODO Move this out into util file instruction_ref add_bias(module& m, instruction_ref ins, const std::vector& args, instruction_ref curr_ins, uint64_t axis) const { if(args.size() == 3) { instruction_ref bias_bcast; // if curr_ins has a dynamic output shape use 2 input broadcast if(curr_ins->get_shape().dynamic()) { bias_bcast = m.insert_instruction( ins, make_op("broadcast", {{"axis", axis}}), args[2], curr_ins); } else { bias_bcast = m.insert_instruction( ins, make_op("broadcast", {{"axis", axis}, {"out_lens", curr_ins->get_shape().lens()}}), args[2]); } return m.insert_instruction(ins, make_op("add"), curr_ins, bias_bcast); } return curr_ins; } }; struct quant_convolution : convolution_base { static std::string name() { return "quant_convolution"; } std::vector insert(module& m, instruction_ref ins, const std::vector& args) { auto x = args[0]; auto weights = args[1]; auto in_lens = x->get_shape().max_lens(); assert(in_lens.size() > 2); auto kdims = in_lens.size() - 2; validate_or_init_attributes(kdims, x, weights); auto op = make_conv_op("quant_convolution"); auto x_zp = get_zero_point(m, x, 2, args); auto w_zp = get_zero_point(m, weights, 3, args); handle_quant_inputs(m, ins, x, weights, x_zp, w_zp); auto conv = m.insert_instruction(ins, op, x, weights); return {handle_quant_bias(m, ins, op, conv, x, weights, x_zp, w_zp)}; } // Convert to half prior to a shift to ensure we preserve accuracy here then // convert back to int8 instruction_ref add_int8_shift(module& m, instruction_ref ins, const instruction_ref& offset_op, instruction_ref& unshifted_input) const { auto unshifted_input_half = m.insert_instruction( ins, migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), unshifted_input); auto input_shifted_half = insert_common_op(m, ins, "add", unshifted_input_half, offset_op); return m.insert_instruction( ins, migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); } void shift_input_and_bias(module& m, instruction_ref ins, const instruction_ref& offset_op, const bool has_bias, instruction_ref& input, instruction_ref& input_bias) const { input = add_int8_shift(m, ins, offset_op, input); if(has_bias) { input_bias = add_int8_shift(m, ins, offset_op, input_bias); } } float get_symmetric_value(const instruction_ref& input) const { float symmetric_value = 0; // adjust symmetric zero point value for uint8 types if(input->get_shape().type() == migraphx::shape::uint8_type) { symmetric_value = 128; } return symmetric_value; } instruction_ref gen_symmetric_literal(module& m, const instruction_ref& input) const { float symmetric_value = get_symmetric_value(input); return m.add_literal({{input->get_shape().type(), {1}, {0}}, {symmetric_value}}); } instruction_ref get_zero_point(module& m, const instruction_ref& input, int index, const std::vector& args) const { instruction_ref ret = input; if(args.size() > index) { // Check for type mismatch on parse if(input->get_shape().type() != args[index]->get_shape().type()) MIGRAPHX_THROW("PARSE:Conv Data and Data Zero Point must have same type"); ret = args[index]; if(is_symmetric_zero_point(ret)) { ret = gen_symmetric_literal(m, ret); } } else { ret = gen_symmetric_literal(m, ret); } return ret; } bool is_symmetric_zero_point(instruction_ref zp) const { if(not zp->can_eval()) return false; float symmetric_value = get_symmetric_value(zp); bool all_zeros = false; zp->eval().visit([&](auto z) { all_zeros = std::all_of( z.begin(), z.end(), [&](auto val) { return float_equal(val, symmetric_value); }); }); return all_zeros; } auto qparam_broadcast_op(instruction_ref qparam, std::vector lens, std::size_t axis) const { if(qparam->get_shape().elements() == 1) { return migraphx::make_op("multibroadcast", {{"out_lens", lens}}); } return migraphx::make_op("broadcast", {{"out_lens", lens}, {"axis", axis}}); } instruction_ref handle_quant_bias(module& m, instruction_ref ins, const operation& op, const instruction_ref& input, const instruction_ref& x, const instruction_ref& weights, const instruction_ref& x_zp, const instruction_ref& w_zp) const { // to handle the bias, apply the following transformation: // conv(x-x_zp,w-w_zp) = conv(x,w) - conv(x_zp,w) - conv(x,w_zp) + conv(x_zp,w_zp) instruction_ref ret = input; // multibroadcast (or broadcast) zero points according to spec // x_zp should be a scalar or literal with one element // w_zp can be either a single element or a 1d tensor with size out_channels migraphx::operation x_zp_bc = migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}); migraphx::operation w_zp_bc = qparam_broadcast_op(w_zp, weights->get_shape().lens(), 0); if(not is_symmetric_zero_point(x_zp)) { auto x_zp_mb = m.add_instruction(x_zp_bc, x_zp); auto out_zp_1 = m.add_instruction(op, x_zp_mb, weights); ret = insert_common_op(m, ins, "sub", ret, out_zp_1); } if(not is_symmetric_zero_point(w_zp)) { auto w_zp_mb = m.add_instruction(w_zp_bc, w_zp); auto out_zp_2 = m.add_instruction(op, x, w_zp_mb); ret = insert_common_op(m, ins, "sub", ret, out_zp_2); } if(not(is_symmetric_zero_point(x_zp)) and not(is_symmetric_zero_point(w_zp))) { auto x_zp_mb = m.add_instruction(x_zp_bc, x_zp); auto w_zp_mb = m.add_instruction(w_zp_bc, w_zp); auto out_zp_3 = m.add_instruction(op, x_zp_mb, w_zp_mb); ret = insert_common_op(m, ins, "add", ret, out_zp_3); } return ret; } void handle_quant_inputs(module& m, instruction_ref ins, instruction_ref& input, instruction_ref& weights, instruction_ref& input_zp, instruction_ref& weight_zp) const { auto input_type = input->get_shape().type(); auto weight_type = weights->get_shape().type(); // Handle uint8 bias and input shifts instruction_ref offset_op; if(((input_type == migraphx::shape::uint8_type) or (weight_type == migraphx::shape::uint8_type))) { offset_op = m.add_literal({{migraphx::shape::half_type}, {-128}}); } if(input_type == migraphx::shape::uint8_type) { shift_input_and_bias( m, ins, offset_op, (not is_symmetric_zero_point(input_zp)), input, input_zp); } if(weight_type == migraphx::shape::uint8_type) { shift_input_and_bias( m, ins, offset_op, (not is_symmetric_zero_point(weight_zp)), weights, weight_zp); } } }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/builder/einsum.cpp000066400000000000000000000725501510465702400214100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { struct einsum : op_builder { std::string equation = ""; template static auto reflect(Self& self, F f) { return pack(f(self.equation, "equation")); } using int_mat = std::vector>; struct equation_info { bool explicit_form = false; std::vector input_terms; std::string output_term; std::map label_count; std::vector>> duplicates; size_t ellipsis_ndim = 0; }; std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { const equation_info eq_info = analyze_equation(args); auto terms = eq_info.input_terms; terms.push_back(eq_info.output_term); const auto map_mat = make_mapping_matrix(terms, eq_info.label_count, eq_info.ellipsis_ndim); // Holds the mapping matrix representations of the two terms being processed // cur_pair[0] acts as the accumulator for previously processed inputs // cur_pair[1] holds the representation for the current input // As operations are added to the einsum graph, cur_pair gets manipulated int_mat cur_pair = make_matrix(2, map_mat[0].size(), -1); instruction_ref cur_op; std::optional last_op; // Perform a left fold on the inputs for(auto arg_idx = 0; arg_idx < args.size(); ++arg_idx) { cur_op = args[arg_idx]; cur_pair[1] = map_mat[arg_idx]; cur_op = preprocess_input( m, ins, cur_op, eq_info.duplicates[arg_idx], map_mat, arg_idx, cur_pair); if(last_op) cur_op = process_pair(m, ins, *last_op, cur_op, map_mat, arg_idx, cur_pair); last_op = cur_op; cur_pair[0] = cur_pair[1]; } return {finalize_output(m, ins, cur_op, map_mat, cur_pair)}; } // Equation Parsing equation_info analyze_equation(const std::vector& args) const { equation_info eq_info = parse_equation(); eq_info.ellipsis_ndim = validate_input_terms(eq_info.input_terms, args); if(not eq_info.output_term.empty()) validate_output_term(eq_info.output_term, eq_info.label_count, eq_info.ellipsis_ndim); else if(not eq_info.explicit_form) eq_info.output_term = generate_output_term(eq_info.label_count, eq_info.ellipsis_ndim); eq_info.duplicates = find_duplicates(eq_info.input_terms); return eq_info; } // Equation: Input Output // Input: Term | Term ',' Input // Output: '->' TermOpt | epsilon // TermOpt: Term | epsilon // Term: Labels | LabelsOpt '...' LabelsOpt // LabelsOpt: Labels | epsilon // Labels: [a-zA-Z]+ equation_info parse_equation() const { equation_info ret; std::vector lexers; lexers.push_back(lex_while(&isspace)); lexers.push_back(lex_while(&isalpha)); lexers.push_back(lex_equal("->")); lexers.push_back(lex_equal("...")); lexers.push_back(lex_equal(",")); auto tokens = tokenize(equation.data(), equation.data() + equation.length(), lexers); std::string term; bool has_ellipsis = false; for(const auto& token : tokens) { if(std::isspace(token.front()) != 0) continue; if(std::isalpha(token.front()) != 0) { term += token; if(not ret.explicit_form) { for(auto c : token) ++ret.label_count[c]; } } else if(token == "->") { if(ret.explicit_form) MIGRAPHX_THROW("einsum op_builder: Einsum equation has multiple '->' symbols"); if(term.empty()) MIGRAPHX_THROW("einsum op_builder: No term specified before '->' symbol"); ret.explicit_form = true; has_ellipsis = false; ret.input_terms.push_back(term); term.clear(); } else if(token == "...") { if(has_ellipsis) MIGRAPHX_THROW("einsum op_builder: Ellipsis can only appear once per einsum " "equation term"); has_ellipsis = true; term += "*"; } else if(token == ",") { if(ret.explicit_form) MIGRAPHX_THROW( "einsum op_builder: Einsum equation can't have a ',' symbol in the output"); if(term.empty()) MIGRAPHX_THROW("einsum op_builder: No term specified before ',' symbol"); has_ellipsis = false; ret.input_terms.push_back(term); term.clear(); } } if(ret.explicit_form) ret.output_term = term; else if(not term.empty()) ret.input_terms.push_back(term); else MIGRAPHX_THROW("einsum op_builder: Last input term is missing"); return ret; } size_t validate_input_terms(const std::vector& input_terms, const std::vector& args) const { if(input_terms.size() != args.size()) MIGRAPHX_THROW("Number of terms in the input equation - " + std::to_string(input_terms.size()) + " does not match the number of inputs " + std::to_string(args.size())); auto global_ellipsis_dims = 0u; for(auto i = 0u; i < args.size(); ++i) { const auto& term = input_terms[i]; const auto dims = args[i]->get_shape().lens(); const auto rank = dims.size(); auto current_dim = 0u; for(const auto l : term) { if(l == '*') { const auto ellipsis_dims = rank - term.size() + 1; if(global_ellipsis_dims > 0 and ellipsis_dims != global_ellipsis_dims) MIGRAPHX_THROW( "einsum op_builder: Every occurrence of ellipsis in the equation must " "represent the same number of dimensions"); global_ellipsis_dims = ellipsis_dims; current_dim += ellipsis_dims; } else ++current_dim; } if(current_dim != rank) MIGRAPHX_THROW("einsum op_builder: Number of labels in " + std::to_string(i + 1) + ". input_term (" + term + ") does not match the rank (" + std::to_string(rank) + ") of corresponding input"); } return global_ellipsis_dims; } void validate_output_term(std::string_view output_term, const std::map& label_count, size_t ellipsis_ndim) const { std::string_view::iterator it = std::find_if(output_term.begin(), output_term.end(), [&](auto l) { return not contains(label_count, l) and l != '*'; }); if(it != output_term.end()) MIGRAPHX_THROW("einsum op_builder: Output term contains label " + std::to_string(*it) + ", which is not present in any of the input terms"); if(ellipsis_ndim != 0 and not contains(output_term, "*")) MIGRAPHX_THROW("einsum op_builder: Output term does not contain ellipsis (...) even " "though an input term does"); } // Creates output term when the equation is in implicit mode. // The created output term must contain the alphabetically sorted sequence of labels appearing // exactly once in the equation. // If ellipsis are present in the left hand side of the equation, the ellipsis dimensions are // set to the beginning of the output term. std::string generate_output_term(const std::map& label_count, size_t ellipsis_ndim) const { std::string output_term = ellipsis_ndim == 0 ? "" : "*"; output_term = transform_accumulate( label_count.begin(), label_count.end(), output_term, std::plus<>(), [](const auto& p) { if(p.second == 1) return std::string{p.first}; else return std::string{}; }); return output_term; } // Creates a matrix representation of the equation. // // Rows correspond to equation terms, in order of appearance. // // Columns represent the unique labels contained in the equation, ordered alphabetically. If // ellipses are present in the equation, they are represented by the final N columns(N being the // number of dimensions covered by and ellipsis). // Labels not present in a given term are signified by -1. // Labels present in a given term are signified by the input axis they represent. // // e.g. For equation "...ik,kj...->ij...", assuming ... cover two dimensions, the resulting // matrix is: // +-------+----+----+----+---+---+ // | | i | j | k | * | * | // +-------+----+----+----+---+---+ // | ...ik | 2 | -1 | 3 | 0 | 1 | // | kj... | -1 | 1 | 0 | 2 | 3 | // | ij... | 0 | 1 | -1 | 2 | 3 | // +-------+----+----+----+---+---+ int_mat make_mapping_matrix(const std::vector& terms, const std::map& label_count, size_t ellipsis_ndim) const { std::map label_to_column; auto it = label_count.begin(); for(auto i = 0; i < label_count.size(); ++i) label_to_column[(it++)->first] = i; int_mat map_mat = make_matrix(terms.size(), label_count.size() + ellipsis_ndim, -1); for(auto i = 0; i < terms.size(); ++i) { const auto& term = terms[i]; int col_id = 0; for(const auto l : term) { if(l == '*') { std::iota(map_mat[i].end() - ellipsis_ndim, map_mat[i].end(), col_id); col_id += ellipsis_ndim; } else map_mat[i][label_to_column[l]] = col_id++; } } return map_mat; } // Finds the duplicated labels in each of the terms and stores the axes on which they occur. // // e.g. For equation "iikjj,jkj", the result is a vector containing the two following maps: // result[0]: {'i': [0, 1], 'j': [3, 4]} // result[1]: {'j': [0, 2]} std::vector>> find_duplicates(const std::vector& terms) const { std::vector>> duplicates; for(const auto& term : terms) { std::map> duplicate_axes; for(auto i = 0; i < term.size(); ++i) duplicate_axes[term[i]].push_back(i); erase_if(duplicate_axes, [](const auto& p) { return p.second.size() < 2; }); duplicates.push_back(duplicate_axes); } return duplicates; } // Graph Building instruction_ref preprocess_input(module& m, instruction_ref ins, instruction_ref op, const std::map>& duplicates, const int_mat& map_mat, size_t input_idx, int_mat& cur_pair) const { if(not duplicates.empty()) { std::vector> diag; diag.reserve(duplicates.size()); std::transform(duplicates.begin(), duplicates.end(), std::back_inserter(diag), [](const auto& d) { return d.second; }); op = gather_diagonal(m, ins, cur_pair, op, diag); } // Unsqueeze the input shape in the dimensions marked as -1 in the mapping_matrix // Transpose the input shape so the labels are in alphabetical order op = transpose_unsqueeze(m, ins, cur_pair, op); std::vector red; // Check if a given label appears in any of the subsequent mapping matrix terms(this // includes the output). If does not, it is reduced and marked as -1 in cur_pair. for(int d = 0; d < map_mat[0].size(); ++d) { bool all_neg_one = all_of(extract_column(map_mat, d, input_idx + 1, map_mat.size()), [](auto i) { return i == -1; }); if(all_neg_one and cur_pair[1][d] != -1 and cur_pair[0][d] == -1) red.push_back(d); } return apply_reduce_sum_op(m, ins, op, red, cur_pair[1]); } instruction_ref gather_diagonal(module& m, instruction_ref ins, int_mat& cur_pair, instruction_ref op, const int_mat& diag) const { if(diag.size() != 1) MIGRAPHX_THROW("einsum op_builder: Parsing of equations with more than one duplicated " "labels per input term is not " "implemented"); const auto& op_lens = op->get_shape().lens(); int first_axis = diag[0][0]; const std::vector& axes = diag[0]; if(not all_of(axes, [&](int a) { return op_lens[first_axis] == op_lens[a]; })) MIGRAPHX_THROW("einsum op_builder: All duplicate labels have to be the same dimension"); std::vector batch_axes = set_difference(arange(0, op_lens.size()), axes); if(not all_of(batch_axes, [&](int ba) { return ba < axes.front(); })) MIGRAPHX_THROW("einsum op_builder: Parsing of equations with duplicated labels and " "batch axes that are not " "the outer-most axes, is not implemented"); size_t batch_size = calc_dim(batch_axes, op_lens); std::vector indices; for(size_t batch = 0; batch < batch_size; ++batch) { for(size_t i = 0; i < op_lens[first_axis]; ++i) { std::vector index(axes.size(), i); indices.insert(indices.end(), index.begin(), index.end()); } } std::vector indices_lens{op_lens[first_axis], axes.size()}; if(batch_size > 1) indices_lens.insert(indices_lens.begin(), batch_size); auto indices_arg = m.insert_literal( ins, migraphx::literal{migraphx::shape{migraphx::shape::int64_type, indices_lens}, indices}); op = m.insert_instruction(ins, migraphx::make_op("gathernd", {{"batch_dims", batch_axes.size()}}), op, indices_arg); // compute output row std::replace_if( cur_pair[1].begin(), cur_pair[1].end(), [&](auto r) { return contains(axes, r); }, first_axis); for(auto t : range(axes.begin() + 1, axes.end())) { std::transform(cur_pair[1].begin(), cur_pair[1].end(), cur_pair[1].begin(), [t](auto r) { return r > t ? r - 1 : r; }); } return op; } instruction_ref process_pair(module& m, instruction_ref ins, instruction_ref op1, instruction_ref op2, const int_mat& map_mat, size_t input_idx, int_mat& cur_pair) const { // Label is present in current two terms and somewhere in subsequent terms std::vector batch_axes; // Label is present in only left term std::vector left_only; // Label is present in only right term std::vector right_only; // Label is present in current two terms, but not in the subsequent terms std::vector sum_axes; auto not_neg_one = [](auto i) { return i != -1; }; // Categorize axes according to label distribution in equation for(int d = 0; d < map_mat[0].size(); ++d) { // The label is present in both terms of cur_pair if(all_of(extract_column(cur_pair, d, 0, cur_pair.size()), not_neg_one)) { // The label is present in at least one of the subsequent terms if(any_of(extract_column(map_mat, d, input_idx + 1, map_mat.size()), not_neg_one)) batch_axes.push_back(d); else sum_axes.push_back(d); } // The label is missing in one or both of the cur_pair else { if(cur_pair[0][d] >= 0) left_only.push_back(d); else if(cur_pair[1][d] >= 0) right_only.push_back(d); else batch_axes.push_back(d); } } // Permute the inputs so batch_axes are outermost axes and sum_axes are innermost axes auto&& perm = concat_vectors(batch_axes, left_only, right_only, sum_axes); std::vector perm64(perm.begin(), perm.end()); op1 = apply_transpose_op(m, ins, op1, perm64, cur_pair[0]); op2 = apply_transpose_op(m, ins, op2, perm64, cur_pair[1]); auto new_batch_axes = arange(0, batch_axes.size()); auto new_sum_axes = arange(perm.size() - sum_axes.size(), perm.size()); auto common_labels = set_union(new_batch_axes, new_sum_axes); std::tie(op1, op2) = apply_broadcast_op(m, ins, op1, op2, common_labels); auto op = batch_dot(m, ins, cur_pair, op1, op2, new_batch_axes, new_sum_axes); return apply_transpose_op(m, ins, op, invert_permutation(perm64), cur_pair[1]); } instruction_ref batch_dot(module& m, instruction_ref ins, int_mat& cur_pair, instruction_ref op1, instruction_ref op2, const std::vector& batch_axes, const std::vector& sum_axes) const { auto op1_lens = op1->get_shape().lens(); auto op2_lens = op2->get_shape().lens(); std::vector dims1{static_cast(calc_dim(batch_axes, op1_lens)), -1, static_cast(calc_dim(sum_axes, op1_lens))}; std::vector dims2{static_cast(calc_dim(batch_axes, op2_lens)), -1, static_cast(calc_dim(sum_axes, op2_lens))}; op1 = m.insert_instruction(ins, make_op("reshape", {{"dims", dims1}}), op1); op2 = m.insert_instruction(ins, make_op("reshape", {{"dims", dims2}}), op2); op2 = m.insert_instruction(ins, make_op("transpose", {{"permutation", {0, 2, 1}}}), op2); instruction_ref op = m.insert_instruction(ins, make_op("dot"), op1, op2); std::vector new_lens(op1_lens.size(), 1); std::transform(op1_lens.begin(), op1_lens.begin() + (new_lens.size() - sum_axes.size()), op2_lens.begin(), new_lens.begin(), [](auto len1, auto len2) { return std::max(len1, len2); }); op = m.insert_instruction(ins, make_op("reshape", {{"dims", new_lens}}), op); // compute output row std::transform(cur_pair[0].begin(), cur_pair[0].end(), cur_pair[1].begin(), cur_pair[1].begin(), [](int lhs, int rhs) { return std::max(lhs, rhs); }); for(int a : sum_axes) cur_pair[1][a] = -1; return op; } instruction_ref finalize_output(module& m, instruction_ref ins, instruction_ref op, const int_mat& map_mat, int_mat& cur_pair) const { if(any_of(map_mat.back(), [](auto i) { return i >= 0; })) { cur_pair[1] = map_mat.back(); std::vector red; for(int d = 0; d < map_mat[0].size(); ++d) { if(cur_pair[0][d] > 0 and cur_pair[1][d] == -1) red.push_back(d); } op = apply_reduce_sum_op(m, ins, op, red, cur_pair[1]); } return squeeze_transpose(m, ins, cur_pair, op, map_mat.back()); } // Permutes the labels so they are in alphabetical order and expands the input dimensions to // match the number of unique labels in the entire equation. instruction_ref transpose_unsqueeze(module& m, instruction_ref ins, int_mat& cur_pair, instruction_ref op) const { std::vector perm; std::vector unsq_axes; for(auto i = 0; i < cur_pair[1].size(); ++i) { if(cur_pair[1][i] == -1) // unsqueeze the dimensions corresponding to the missing labels unsq_axes.push_back(i); else // permute the rest perm.push_back(cur_pair[1][i]); } std::vector perm64(perm.begin(), perm.end()); op = apply_transpose_op(m, ins, op, perm64, perm); // compute output row for(auto axis : unsq_axes) { perm.insert(perm.begin() + axis, -1); } cur_pair[1] = perm; return m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsq_axes}}), op); } // Reverts the effects of transpose_unsqueeze (adjusts the output so it fits the equation) instruction_ref squeeze_transpose(module& m, instruction_ref ins, int_mat& cur_pair, instruction_ref op, std::vector row_output) const { std::vector sq_axes; std::vector perm; for(auto i = 0; i < row_output.size(); ++i) { if(row_output[i] == -1) // squeeze the dimensions corresponding to the missing labels sq_axes.push_back(i); else // permute the rest perm.push_back(row_output[i]); } if(not sq_axes.empty()) op = m.insert_instruction(ins, make_op("squeeze", {{"axes", sq_axes}}), op); if(not perm.empty()) { std::vector perm64(perm.begin(), perm.end()); op = apply_transpose_op(m, ins, op, invert_permutation(perm64), perm); // compute output row for(auto axis : sq_axes) { perm.insert(perm.begin() + axis, -1); } cur_pair[1] = perm; } return op; } instruction_ref apply_transpose_op(module& m, instruction_ref ins, instruction_ref op, const std::vector& perm, std::vector& row) const { op = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), op); // compute output row row = reorder_dims(row, perm); return op; } std::pair apply_broadcast_op(module& m, instruction_ref ins, instruction_ref opl, instruction_ref opr, const std::vector& common_labels) const { std::pair ret; auto llens = opl->get_shape().lens(); auto rlens = opr->get_shape().lens(); bool lbc = false; bool rbc = false; for(auto l : common_labels) { if(llens[l] == 1 and rlens[l] == 1) continue; if(llens[l] == 1) { lbc = true; llens[l] = rlens[l]; } if(rlens[l] == 1) { rbc = true; rlens[l] = llens[l]; } } if(lbc) opl = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", llens}}), opl); if(rbc) opr = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", rlens}}), opr); ret.first = opl; ret.second = opr; return ret; } instruction_ref apply_reduce_sum_op(module& m, instruction_ref ins, instruction_ref op, const std::vector& axes, std::vector& row) const { if(axes.empty()) return op; for(int a : axes) row[a] = -1; return m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), op); } // Utility int_mat make_matrix(int cur_pair, int cols, int fill_value) const { return {static_cast(cur_pair), std::vector(cols, fill_value)}; } std::vector extract_column(int_mat map_mat, int col_idx, int row_begin, int row_end) const { std::vector ret; ret.reserve(row_end - row_begin); std::transform(map_mat.begin() + row_begin, map_mat.begin() + row_end, std::back_inserter(ret), [col_idx](const auto& x) { return x[col_idx]; }); return ret; } std::vector set_union(const std::vector& lhs, const std::vector& rhs) const { std::vector ret; std::set_union(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(ret)); return ret; } std::vector set_difference(const std::vector& lhs, const std::vector& rhs) const { std::vector ret; std::set_difference( lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(ret)); return ret; } // Equivalent to numpy.arange without the step parameter std::vector arange(int start_value, int end_value) const { std::vector ret(end_value - start_value); std::iota(ret.begin(), ret.end(), start_value); return ret; } template Vec concat_vectors(Vec vec, Vecs&&... vecs) const { size_t reserve_size = vec.size(); each_args([&](auto&& v) { reserve_size += v.size(); }, vecs...); vec.reserve(reserve_size); each_args([&](auto&& v) { vec.insert(vec.end(), v.begin(), v.end()); }, vecs...); return vec; } size_t calc_dim(const std::vector& axes, const std::vector& lens) const { return std::accumulate( axes.begin(), axes.end(), 1, [&](auto acc, auto axis) { return acc * lens[axis]; }); }; }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/builder/gelu.cpp000066400000000000000000000141551510465702400210410ustar00rootroot00000000000000/* The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { struct gelu_quick : op_builder { float alpha = 1.0f; template static auto reflect(Self& self, F f) { return pack(f(self.alpha, "alpha")); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { auto x = args[0]; auto x_type = x->get_shape().type(); auto alpha_lit = m.add_literal(migraphx::literal{migraphx::shape{x_type}, {alpha}}); auto mul_alpha = insert_common_op(m, ins, make_op("mul"), {alpha_lit, x}); auto sigmoid = m.insert_instruction(ins, migraphx::make_op("sigmoid"), mul_alpha); return {insert_common_op(m, ins, make_op("mul"), {x, sigmoid})}; } }; struct gelu_erf : op_builder { template static auto reflect(Self&, F) { return pack(); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { auto x = args[0]; auto x_type = x->get_shape().type(); auto half = m.add_literal({x_type, {0.5f}}); auto one = m.add_literal({x_type, {1.0f}}); auto sqrt2 = m.add_literal({x_type, {static_cast(M_SQRT2)}}); auto mul_half = insert_common_op(m, ins, "mul", x, half); auto div = insert_common_op(m, ins, "div", x, sqrt2); auto erf = m.insert_instruction(ins, migraphx::make_op("erf"), div); auto add_one = insert_common_op(m, ins, "add", erf, one); return {insert_common_op(m, ins, "mul", mul_half, add_one)}; } }; struct gelu_tanh : op_builder { bool fast = false; template static auto reflect(Self& self, F f) { return pack(f(self.fast, "fast")); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { auto x = args[0]; auto x_type = x->get_shape().type(); auto fit_const_val = fast ? 0.035677 : 0.044715; auto fit_const = m.add_literal({x_type, {fit_const_val}}); auto sqrt_2_rpi_val = fast ? 0.797885 : sqrt(M_2_PI); auto sqrt_2_rpi = m.add_literal({x_type, {sqrt_2_rpi_val}}); auto one = m.add_literal({x_type, {1.0f}}); auto half = m.add_literal({x_type, {0.5f}}); auto three = m.add_literal({x_type, {3.0f}}); // [0.044715|0.035677] * x^3 auto pow0 = insert_common_op(m, ins, "pow", x, three); auto mul0 = insert_common_op(m, ins, "mul", pow0, fit_const); instruction_ref tanh_in; if(fast) { // approx = 0.797885 * x + 0.035677 * x^3 auto mul1 = insert_common_op(m, ins, "mul", sqrt_2_rpi, x); tanh_in = insert_common_op(m, ins, "add", mul0, mul1); } else { // approx = sqrt(2/pi) * (x + 0.044715 * x^3 auto add0 = insert_common_op(m, ins, "add", mul0, x); tanh_in = insert_common_op(m, ins, "mul", add0, sqrt_2_rpi); } // 0.5 * x * (1 + Tanh(approx)) auto tanh0 = insert_common_op(m, ins, "tanh", tanh_in); auto add1 = insert_common_op(m, ins, "add", tanh0, one); auto mul2 = insert_common_op(m, ins, "mul", x, half); return {insert_common_op(m, ins, "mul", add1, mul2)}; } }; struct gelu_split : op_builder { template static auto reflect(Self&, F) { return pack(); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { auto x = args[0]; size_t last_dim_size = x->get_shape().lens().back(); if(last_dim_size < 2 or last_dim_size % 2 != 0) MIGRAPHX_THROW( "gelu_split op_builder: BiasSplitGelu must have even last dimension which is >= 2"); auto split_left = m.add_instruction( migraphx::make_op("slice", {{"axes", {-1}}, {"starts", {0}}, {"ends", {last_dim_size / 2}}}), x); auto split_right = m.add_instruction( migraphx::make_op( "slice", {{"axes", {-1}}, {"starts", {last_dim_size / 2}}, {"ends", {last_dim_size}}}), x); auto gelu_erf = op::builder::add("gelu_erf", m, {split_right}, {}).at(0); return {insert_common_op(m, ins, "mul", split_left, gelu_erf)}; } }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/builder/gemm.cpp000066400000000000000000000116051510465702400210270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { struct gemm : op_builder { float alpha = 1.0f; float beta = 1.0f; bool trans_a = false; bool trans_b = false; template static auto reflect(Self& self, F f) { return pack(f(self.alpha, "alpha"), f(self.beta, "beta"), f(self.trans_a, "transA"), f(self.trans_b, "transB")); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { auto a_arg = args[0]; auto b_arg = args[1]; if(a_arg->get_shape().ndim() != 2 or b_arg->get_shape().ndim() != 2) { MIGRAPHX_THROW("gemm op_builder: A and B should be rank 2, A is rank " + std::to_string(a_arg->get_shape().ndim()) + ", B is rank " + std::to_string(b_arg->get_shape().ndim())); } std::vector perm = {1, 0}; auto dot_type = a_arg->get_shape().type(); if(alpha != 1.0f) { auto alpha_literal = m.add_literal(alpha); a_arg = insert_common_op(m, ins, "mul", alpha_literal, a_arg); if(a_arg->get_shape().type() != dot_type) { a_arg = m.insert_instruction( ins, make_op("convert", {{"target_type", dot_type}}), a_arg); } } a_arg = (trans_a) ? m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), a_arg) : a_arg; b_arg = (trans_b) ? m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), args[1]) : args[1]; auto dot_ins = m.insert_instruction(ins, make_op("dot"), a_arg, b_arg); if(args.size() == 3) { if(not float_equal(beta, 0.0f)) { auto c_arg = args[2]; if(dot_ins->get_shape().dynamic()) { c_arg = m.insert_instruction(ins, make_op("multibroadcast"), args[2], dot_ins); } else { auto out_lens = a_arg->get_shape().lens(); out_lens.back() = b_arg->get_shape().lens().back(); auto c_lens = c_arg->get_shape().lens(); if(not std::equal( out_lens.begin(), out_lens.end(), c_lens.begin(), c_lens.end())) { c_arg = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", out_lens}}), args[2]); } } if(not float_equal(beta, 1.0f)) { auto beta_literal = m.add_literal(beta); c_arg = insert_common_op(m, ins, "mul", c_arg, beta_literal); if(c_arg->get_shape().type() != dot_type) { c_arg = m.insert_instruction( ins, make_op("convert", {{"target_type", dot_type}}), c_arg); } } return {m.insert_instruction(ins, make_op("add"), dot_ins, c_arg)}; } } return {dot_ins}; } }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/builder/include/000077500000000000000000000000001510465702400210165ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/op/builder/include/migraphx/000077500000000000000000000000001510465702400226355ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/op/builder/include/migraphx/op/000077500000000000000000000000001510465702400232535ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/op/builder/include/migraphx/op/builder/000077500000000000000000000000001510465702400247015ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/op/builder/include/migraphx/op/builder/insert.hpp000066400000000000000000000065041510465702400267230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_OP_BUILDER_INSERT_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_OP_BUILDER_INSERT_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { MIGRAPHX_EXPORT std::vector insert(const std::string& name, module& m, instruction_ref ins, const std::vector& args, const value& options); MIGRAPHX_EXPORT std::vector insert(const std::string& name, module& m, instruction_ref ins, const std::vector& args, const std::vector& module_args, const value& options); MIGRAPHX_EXPORT std::vector add(const std::string& name, module& m, const std::vector& args, const value& options); MIGRAPHX_EXPORT std::vector add(const std::string& name, module& m, const std::vector& args, const std::vector& module_args, const value& options); template instruction_ref insert_common_op(module& m, instruction_ref ins, const std::string& op_name, Ins... args) { return insert_common_op(m, ins, make_op(op_name), {args...}); } } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/op/builder/include/migraphx/op/builder/op_builder.hpp000066400000000000000000000073731510465702400275500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_OP_BUILDER_REGISTER_BUILDER_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_OP_BUILDER_REGISTER_BUILDER_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { using builder_func = std::function(module& m, instruction_ref ins, const std::vector& args, const std::vector& module_args, const value& options)>; MIGRAPHX_EXPORT void register_builder(const std::string& name, builder_func f); template auto invoke_builder(module& m, instruction_ref ins, const std::vector& args, const std::vector& module_args, const value& options) -> decltype(T{}.insert(m, ins, args, module_args)) { auto x = from_value(options); return x.insert(m, ins, args, module_args); } template auto invoke_builder(module& m, instruction_ref ins, const std::vector& args, const std::vector& module_args, const value& options) -> decltype(T{}.insert(m, ins, args)) { if(not module_args.empty()) MIGRAPHX_THROW("Module args should be empty"); auto x = from_value(options); return x.insert(m, ins, args); } template void register_builder() { builder_func f = [](module& m, instruction_ref ins, const std::vector& args, const std::vector& module_args, const value& options) { return invoke_builder(m, ins, args, module_args, options); }; register_builder(T::name(), std::move(f)); } struct register_builder_action { template static void apply() { register_builder(); } }; template struct op_builder : auto_register { static std::string name() { static const std::string& name = get_type_name(); return name.substr(name.rfind("::") + 2); } }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/op/builder/mean_variance_normalization.cpp000066400000000000000000000057551510465702400256510ustar00rootroot00000000000000/* The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { struct mean_variance_normalization : op_builder { std::vector axes{0, 2, 3}; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes")); } std::vector insert(module& m, instruction_ref ins, const std::vector& args) const { auto x = args.front(); if(axes.size() != x->get_shape().ndim() - 1) MIGRAPHX_THROW("mvn op_builder: Length of axes attribute needs to be equal to input " "tensor rank - 1"); auto x_mean = m.insert_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), x); auto x_mean_squared = insert_common_op(m, ins, "mul", x_mean, x_mean); auto x_squared = insert_common_op(m, ins, "mul", x, x); auto x_squared_mean = m.insert_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), x_squared); auto mean_sub = insert_common_op(m, ins, "sub", x_squared_mean, x_mean_squared); auto std = insert_common_op(m, ins, "sqrt", mean_sub); auto dividend = insert_common_op(m, ins, "sub", x, x_mean); auto epsilon = m.add_literal( {x->get_shape().type(), {x->get_shape().type() == shape::half_type ? 1e-7 : 1e-9}}); auto divisor = insert_common_op(m, ins, "add", std, epsilon); return {insert_common_op(m, ins, "div", dividend, divisor)}; } }; } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op/builder/op_builder.cpp000066400000000000000000000065741510465702400222370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { static std::unordered_map& builder_map() { static std::unordered_map m; // NOLINT return m; } void register_builder(const std::string& name, builder_func f) { builder_map()[name] = std::move(f); } std::vector insert(const std::string& name, module& m, instruction_ref ins, const std::vector& args, const value& options) { return at(builder_map(), name, "Builder not found: " + name)(m, ins, args, {}, options); } std::vector insert(const std::string& name, module& m, instruction_ref ins, const std::vector& args, const std::vector& module_args, const value& options) { return at(builder_map(), name, "Builder not found: " + name)( m, ins, args, module_args, options); } std::vector add(const std::string& name, module& m, const std::vector& args, const value& options) { return at(builder_map(), name, "Builder not found: " + name)(m, m.end(), args, {}, options); } std::vector add(const std::string& name, module& m, const std::vector& args, const std::vector& module_args, const value& options) { return at(builder_map(), name, "Builder not found: " + name)( m, m.end(), args, module_args, options); } } // namespace builder } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/op_enums.cpp000066400000000000000000000045111510465702400176610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ // // Supporting functions for enum values used in operator parameters. // These values are declared as "enum class" and should include << streaming operators // to be able to write their values in human-readable format so users can // save and edit model files. // #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { std::ostream& operator<<(std::ostream& os, pooling_mode v) { // the strings for the enum are the same as the values used for onnx parsing // but this enum is not onnx-specific: strings must be converted when parsing tf static const std::vector pooling_mode_str = {"average", "max", "lpnorm"}; os << pooling_mode_str[static_cast::type>(v)]; return os; } std::ostream& operator<<(std::ostream& os, rnn_direction v) { static const std::vector rnn_direction_str = { "forward", "reverse", "bidirectional"}; os << rnn_direction_str[static_cast::type>(v)]; return os; } } // namespace op } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/operation.cpp000066400000000000000000000031321510465702400200320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void migraphx_to_value(value& v, const operation& op) { v["name"] = op.name(); v["operator"] = op.to_value(); } void migraphx_from_value(const value& v, operation& op) { op = make_op(v.at("name").to(), v.at("operator")); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/optimize_module.cpp000066400000000000000000000044611510465702400212450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void optimize_module::apply(module_pass_manager& mpm) const { mpm.get_module().repeat_while_changes(2, [&] { // loop to further optimize after initial transformations mpm.get_module().repeat_while_changes(4, [&] { mpm.run_pass(simplify_reshapes{}); mpm.run_pass(eliminate_convert{}); mpm.run_pass(dead_code_elimination{}); mpm.run_pass(simplify_algebra{}); }); mpm.run_pass(eliminate_common_subexpression{}); mpm.run_pass(dead_code_elimination{}); mpm.run_pass(propagate_constant{propagate_constant_skip_ops}); mpm.run_pass(dead_code_elimination{}); }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/pad_calc.cpp000066400000000000000000000157411510465702400175710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void calc_auto_padding(std::string auto_pad, const std::vector& strides, const std::vector& k_lens, const std::vector& dilation, const std::vector& in_lens, std::vector& paddings) { size_t kdims = in_lens.size() - 2; assert(k_lens.size() == kdims and dilation.size() == kdims); auto_pad = to_upper(auto_pad); if(contains(auto_pad, "SAME")) { bool is_same_upper = contains(auto_pad, "SAME_UPPER"); paddings.resize(2 * kdims); for(size_t i = 0; i < paddings.size() / 2; i++) { calculate_padding( i, paddings, in_lens[i + 2], strides[i], dilation[i], k_lens[i], is_same_upper); } } } void calculate_padding(int64_t idx, std::vector& pads, int64_t input_dim, int64_t stride, int64_t dilation, int64_t weight_dim, bool is_same_upper) { int64_t output_dim = (input_dim + stride - 1) / stride; // round up result int64_t new_weight_dim = weight_dim + (weight_dim - 1) * (dilation - 1); int64_t pad = std::max(static_cast(0), (output_dim - 1) * stride + new_weight_dim - input_dim); auto pad_ndims = pads.size() / 2; if(is_same_upper) { pads[idx] = pad / 2; pads[idx + pad_ndims] = pad - pad / 2; } else { pads[idx + pad_ndims] = pad / 2; pads[idx] = pad - pad / 2; } } /** * Given the input array dimensions; kernel (wei_lens); strides; and dilations, * calculate the padding value in each dimension. * */ std::vector calc_dyn_auto_pad(const std::vector& input_lens, const std::vector& wei_lens, const std::vector& strides, const std::vector& dilations, bool use_upper) { std::vector padding; assert(input_lens.size() >= 3); assert(input_lens.size() == wei_lens.size()); std::size_t num_spatial_dims = input_lens.size() - 2; padding.resize(2 * num_spatial_dims); for(std::size_t i = 0; i < num_spatial_dims; i++) { std::ptrdiff_t input_dim = input_lens[i + 2]; std::ptrdiff_t stride = strides[i]; std::ptrdiff_t weight_dim = wei_lens[i + 2]; std::ptrdiff_t dilation = dilations[i]; std::ptrdiff_t output_dim = (input_dim + stride - 1) / stride; // round up result std::ptrdiff_t new_weight_dim = weight_dim + (weight_dim - 1) * (dilation - 1); std::size_t pad = std::max(static_cast(0), (output_dim - 1) * stride + new_weight_dim - input_dim); auto pad_ndims = padding.size() / 2; if(use_upper) { padding[i] = pad / 2; padding[i + pad_ndims] = pad - pad / 2; } else { padding[i + pad_ndims] = pad / 2; padding[i] = pad - pad / 2; } } return padding; } /** * Calculate the correct output shape for a convolution with * a given input size and other parameters. * */ shape compute_padded_shape(const shape& input, const shape& weights, const std::vector& padding, const std::vector& stride, const std::vector& dilation) { const size_t num_spatial_dims = input.lens().size() - 2; std::vector output_lens{input.lens()[0], weights.lens()[0]}; // calculate the output shape of the convolution: ((W - K + 2P) / S) + 1 for(size_t i = 0; i < num_spatial_dims; ++i) { auto padding_factor = padding[i] + padding[i + num_spatial_dims]; output_lens.push_back(std::size_t(std::max( 1, (input.lens()[i + 2] - (1 + dilation[i] * (weights.lens()[i + 2] - 1)) + padding_factor) / stride[i] + 1))); } return input.with_lens(output_lens); } /** * Calculate the correct output shape for a pooling with * a given input size and other parameters. This uses * the same formula for pooling that compute_padded_shape() uses * for convolutions, but takes slightly different inputs. * */ shape compute_padded_pool_shape(const shape& input, const shape& kernel, const std::vector& padding, const std::vector& stride, const std::vector& dilation) { const size_t num_spatial_dims = input.lens().size() - 2; std::vector output_lens{input.lens()[0], input.lens()[1]}; // calculate the output shape of the pooling: ((W - K + 2P) / S) + 1 for(size_t i = 0; i < num_spatial_dims; ++i) { auto padding_factor = padding[i] + padding[i + num_spatial_dims]; output_lens.push_back(std::size_t(std::max( 1, (input.lens()[i + 2] - (1 + dilation[i] * (kernel.lens()[i] - 1)) + padding_factor) / stride[i] + 1))); } return input.with_lens(output_lens); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/param_utils.cpp000066400000000000000000000073621510465702400203630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { std::string param_name(std::size_t i, const std::string& prefix) { if(i < 10) return prefix + std::to_string(i); const std::size_t max_digits = 5; if(i >= std::pow(10, max_digits)) MIGRAPHX_THROW("Too many parameters."); std::size_t n = log10(i) + 1; return prefix + ":" + std::string(max_digits - n, '0') + std::to_string(i); } void sort_params(std::vector& params) { std::sort(params.begin(), params.end(), by(std::less<>{}, [](instruction_ref ins) { const auto& param = any_cast(ins->get_operator()); return param.parameter; })); } template static std::vector find_inputs_impl(const std::unordered_map& map_ins, const_module_ref sub, F parent_has) { std::vector result; std::map names; for(auto&& [input, param] : map_ins) { if(sub != nullptr and not sub->has_instruction(param)) continue; if(param->name() != "@param") continue; if(not parent_has(input)) continue; auto v = param->get_operator().to_value(); auto name = v.at("parameter").template to(); names[name] = input; } std::transform(names.begin(), names.end(), std::back_inserter(result), [](const auto& p) { return p.second; }); assert(not sub or result.size() == sub->get_parameter_shapes().size()); return result; } std::vector find_inputs(const std::unordered_map& map_ins, const_module_ref parent, const_module_ref sub) { return find_inputs_impl(map_ins, sub, [&](instruction_ref ins) { if(parent == nullptr) return false; return parent->has_instruction(ins); }); } std::vector find_inputs(const std::unordered_map& map_ins, const std::unordered_set& parent_instructions, const_module_ref sub) { return find_inputs_impl( map_ins, sub, [&](instruction_ref ins) { return contains(parent_instructions, ins); }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/pass.cpp000066400000000000000000000030511510465702400170000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /// Dummy pass for default return struct id_pass { std::string name() const { return "id"; } void apply(const module&) const {} }; pass enable_pass(bool enabled, pass p) { if(enabled) return p; return id_pass{}; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/pass_manager.cpp000066400000000000000000000147171510465702400205050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_PASSES); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TIME_PASSES); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_PASSES); static bool is_pass_disabled(const std::string& name) { static const auto passes = split_string(string_value_of(MIGRAPHX_DISABLE_PASSES{}, ""), ','); return contains(passes, name); } static void validate_pass(module& mod, const pass& p, tracer trace) { (void)mod; (void)p; (void)trace; #ifndef NDEBUG trace("Validate ..."); auto invalid = mod.validate(); if(invalid != mod.end()) { auto index = std::distance(mod.begin(), invalid); MIGRAPHX_THROW(p.name() + " pass produces invalid program at instruction " + std::to_string(index) + ": " + invalid->name()); } trace(); #endif } static void run_pass(program& prog, const pass& p, tracer trace) { trace("Pass: ", p.name()); p.apply(prog); trace(prog); } struct module_pm : module_pass_manager { module* mod = nullptr; module* root_mod = nullptr; tracer* t = nullptr; module* common_parent = nullptr; program* prog = nullptr; module_pm(module* pmod = nullptr, tracer* pt = nullptr) : mod(pmod), t(pt) {} module_pm(module* pmod = nullptr, module* rmod = nullptr, tracer* pt = nullptr) : mod(pmod), root_mod(rmod), t(pt) { } template void trace(Ts&&... xs) const { assert(t); (*t)(xs...); } virtual module& get_module() override { assert(mod); return *mod; } virtual module* create_module(const std::string& name) override { assert(prog); return prog->create_module(name); } virtual module* create_module(const std::string& name, module m) override { assert(prog); return prog->create_module(name, std::move(m)); } virtual void rename_module(const std::string& old_name, const std::string& new_name) override { assert(prog); assert(mod); assert( any_of(mod->get_sub_modules(), [&](module_ref sm) { return sm->name() == old_name; })); prog->rename_module(old_name, new_name); } virtual module* get_common_parent() override { return common_parent; } virtual module* get_root_module() override { if(root_mod != nullptr) return root_mod; assert(prog); return prog->get_main_module(); } virtual void run_pass(const pass& p) override { if(is_pass_disabled(p.name())) return; trace("Pass: ", p.name()); assert(mod); assert(mod->validate() == mod->end()); if(enabled(MIGRAPHX_TIME_PASSES{})) { using milliseconds = std::chrono::duration; auto ms = time([&] { p.apply(*this); }); std::cout << p.name() << ": " << ms << "ms\n"; } else { p.apply(*this); } trace(*mod); validate_pass(*mod, p, *t); } }; module& get_module(module_pass_manager& mpm) { return mpm.get_module(); } void run_passes(program& prog, module_ref root_mod, const std::vector& passes, tracer trace) { if(enabled(MIGRAPHX_TRACE_PASSES{})) trace = tracer{std::cout}; std::unordered_set visited; for(const auto& p : passes) { auto tree = prog.get_module_tree(); std::vector sub_mods = root_mod->get_sub_modules(); sub_mods.insert(sub_mods.begin(), root_mod); visited.clear(); for(const auto& mod : reverse(sub_mods)) { if(mod->bypass()) continue; if(not visited.insert(mod).second) continue; module_pm mpm{mod, root_mod, &trace}; mpm.prog = &prog; auto parents = range(tree.equal_range(mod)); auto nparents = distance(parents); if(nparents == 0) mpm.common_parent = nullptr; else if(nparents == 1) mpm.common_parent = parents.begin()->second; else // Just set common parent to main module when there is muliple parents for now // TODO: Compute the common parent mpm.common_parent = prog.get_main_module(); mpm.run_pass(p); } run_pass(prog, p, trace); } } void run_passes(module& mod, const std::vector& passes, tracer trace) { if(enabled(MIGRAPHX_TRACE_PASSES{})) trace = tracer{std::cout}; for(const auto& p : passes) { module_pm{&mod, &mod, &trace}.run_pass(p); } } void run_passes(program& prog, const std::vector& passes, tracer trace) { run_passes(prog, prog.get_main_module(), passes, trace); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/permutation.cpp000066400000000000000000000060771510465702400204140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { shape reorder_shape(const shape& s, const std::vector& permutation) { return {s.type(), reorder_dims(s.lens(), permutation), reorder_dims(s.strides(), permutation)}; } std::vector invert_permutation(const std::vector& permutation) { return sort_permutation(permutation, std::less<>{}); } std::vector find_permutation(const shape& s) { std::vector result(s.lens().size()); std::iota(result.begin(), result.end(), 0); std::stable_sort(result.begin(), result.end(), by(std::greater<>{}, [&](auto x) { return std::make_tuple(s.strides()[x], s.lens()[x]); })); return result; } std::vector find_permutation(const std::vector& shapes) { if(shapes.empty()) return {}; std::map, std::size_t> count; for(auto&& s : shapes) { if(s.broadcasted()) continue; count[find_permutation(s)]++; } if(count.empty()) { std::vector r(shapes.front().lens().size()); std::iota(r.begin(), r.end(), 0); return r; } auto it = std::max_element( count.begin(), count.end(), by(std::less<>{}, [](auto&& p) { return p.second; })); assert(it != count.end()); return it->first; } /// Normalize shapes by reordering them by their permutation std::vector normalize_permutation(const std::vector& shapes) { auto result = shapes; auto perm = find_permutation(shapes); std::transform(result.begin(), result.end(), result.begin(), [&](auto s) { return reorder_shape(s, perm); }); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/preallocate_param.cpp000066400000000000000000000040451510465702400215110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void preallocate_param::apply(module& m) const { auto last = std::prev(m.end()); for(auto ins : iterator_for(m)) { if(ins->name() != "@param") continue; if(param != any_cast(ins->get_operator()).parameter) continue; std::string id = m.name() + ":" + param; auto r = m.insert_instruction(ins, model.preallocate(ins->get_shape(), id)); m.replace_instruction(ins, r); m.move_instruction(ins, m.end()); } m.remove_instructions(std::next(last), m.end()); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/process.cpp000066400000000000000000000332001510465702400175070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 // cppcheck-suppress definePrefix #define WIN32_LEAN_AND_MEAN #include #include #include #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_CMD_EXECUTE) #ifndef _WIN32 static std::function redirect_to(std::ostream& os) { return [&](const char* x) { os << x; }; } template static int exec(const std::string& cmd, const char* type, F f) { int ec = 0; if(enabled(MIGRAPHX_TRACE_CMD_EXECUTE{})) std::cout << cmd << std::endl; auto closer = [&](FILE* stream) { auto status = pclose(stream); ec = WIFEXITED(status) ? WEXITSTATUS(status) : 0; // NOLINT }; { // TODO: Use execve instead of popen std::unique_ptr pipe(popen(cmd.c_str(), type), closer); // NOLINT if(not pipe) MIGRAPHX_THROW("popen() failed: " + cmd); f(pipe.get()); } return ec; } static int exec(const std::string& cmd, const std::function& std_out) { return exec(cmd, "r", [&](FILE* f) { std::array buffer; while(fgets(buffer.data(), buffer.size(), f) != nullptr) std_out(buffer.data()); }); } static int exec(const std::string& cmd, std::function std_in) { return exec(cmd, "w", [&](FILE* f) { std_in([&](const char* buffer, std::size_t n) { std::fwrite(buffer, 1, n, f); }); }); } #else constexpr std::size_t MIGRAPHX_PROCESS_BUFSIZE = 4096; enum class direction { input, output }; template class pipe { public: explicit pipe() { SECURITY_ATTRIBUTES attrs; attrs.nLength = sizeof(SECURITY_ATTRIBUTES); attrs.bInheritHandle = TRUE; attrs.lpSecurityDescriptor = nullptr; if(CreatePipe(&m_read, &m_write, &attrs, 0) == FALSE) throw GetLastError(); if(dir == direction::output) { // Do not inherit the read handle for the output pipe if(SetHandleInformation(m_read, HANDLE_FLAG_INHERIT, 0) == 0) throw GetLastError(); } else { // Do not inherit the write handle for the input pipe if(SetHandleInformation(m_write, HANDLE_FLAG_INHERIT, 0) == 0) throw GetLastError(); } } pipe(const pipe&) = delete; pipe& operator=(const pipe&) = delete; pipe(pipe&&) = default; ~pipe() { if(m_write != nullptr) { CloseHandle(m_write); } if(m_read != nullptr) { CloseHandle(m_read); } } bool close_write_handle() { auto result = true; if(m_write != nullptr) { result = CloseHandle(m_write) == TRUE; m_write = nullptr; } return result; } bool close_read_handle() { auto result = true; if(m_read != nullptr) { result = CloseHandle(m_read) == TRUE; m_read = nullptr; } return result; } std::pair read(LPVOID buffer, DWORD length) const { DWORD bytes_read; if(ReadFile(m_read, buffer, length, &bytes_read, nullptr) == FALSE and GetLastError() == ERROR_MORE_DATA) { return {true, bytes_read}; } return {false, bytes_read}; } HANDLE get_read_handle() const { return m_read; } bool write(LPCVOID buffer, DWORD length) const { DWORD bytes_written; return WriteFile(m_write, buffer, length, &bytes_written, nullptr) == TRUE; } HANDLE get_write_handle() const { return m_write; } private: HANDLE m_write = nullptr, m_read = nullptr; }; // clang-format off template int exec(const std::string& cmd, const std::string& cwd, const std::string& args, const std::string& envs, F f) // clang-format on { if(enabled(MIGRAPHX_TRACE_CMD_EXECUTE{})) { std::cout << "[cwd=" << cwd << "]; cmd='" << cmd << "\'; args='" << args << "'; envs='" << envs << "'\n"; } // See CreateProcess() WIN32 documentation for details. constexpr std::size_t CMDLINE_LENGTH = 32767; // Build lpCommandLine parameter. std::string cmdline = quote_string(cmd); if(not args.empty()) cmdline += " " + args; // clang-format off if(cmdline.size() > CMDLINE_LENGTH) MIGRAPHX_THROW("Command line too long, required maximum " + std::to_string(CMDLINE_LENGTH) + " characters."); // clang-format on if(cmdline.size() < CMDLINE_LENGTH) cmdline.resize(CMDLINE_LENGTH, '\0'); // Build lpEnvironment parameter. std::vector environment{}; if(not envs.empty()) { std::istringstream iss{envs}; std::string str; while(iss >> str) { environment.insert(environment.end(), str.begin(), str.end()); environment.push_back('\0'); } environment.push_back('\0'); } try { STARTUPINFO info; PROCESS_INFORMATION process_info; pipe input{}; pipe output{}; ZeroMemory(&info, sizeof(STARTUPINFO)); info.cb = sizeof(STARTUPINFO); info.hStdError = output.get_write_handle(); info.hStdOutput = output.get_write_handle(); info.hStdInput = input.get_read_handle(); info.dwFlags |= STARTF_USESTDHANDLES; info.wShowWindow = SW_HIDE; ZeroMemory(&process_info, sizeof(process_info)); if(CreateProcess(cmd.c_str(), cmdline.data(), nullptr, nullptr, TRUE, CREATE_NO_WINDOW, environment.empty() ? nullptr : environment.data(), cwd.empty() ? nullptr : static_cast(cwd.c_str()), &info, &process_info) == FALSE) { MIGRAPHX_THROW("Error creating process (" + std::to_string(GetLastError()) + ")"); } CloseHandle(process_info.hThread); if(not output.close_write_handle()) MIGRAPHX_THROW("Error closing STDOUT handle for writing (" + std::to_string(GetLastError()) + ")"); if(not input.close_read_handle()) MIGRAPHX_THROW("Error closing STDIN handle for reading (" + std::to_string(GetLastError()) + ")"); f(input, output); if(not input.close_write_handle()) MIGRAPHX_THROW("Error closing STDIN handle for writing (" + std::to_string(GetLastError()) + ")"); WaitForSingleObject(process_info.hProcess, INFINITE); DWORD status{}; GetExitCodeProcess(process_info.hProcess, &status); CloseHandle(process_info.hProcess); return static_cast(status); } // cppcheck-suppress catchExceptionByValue catch(DWORD error) { MIGRAPHX_THROW("Error spawning process (" + std::to_string(error) + ")"); } } // clang-format off int exec(const std::string& cmd, const std::string& cwd, const std::string& args, const std::string& envs, HANDLE std_out) { TCHAR buffer[MIGRAPHX_PROCESS_BUFSIZE]; return (std_out == nullptr or std_out == INVALID_HANDLE_VALUE) ? GetLastError() : exec(cmd, cwd, args, envs, [&](const pipe&, const pipe& out) { for(;;) { auto [more_data, bytes_read] = out.read(buffer, MIGRAPHX_PROCESS_BUFSIZE); if(bytes_read == 0) break; if(WriteFile(std_out, buffer, bytes_read, nullptr, nullptr) == FALSE) break; if(not more_data) break; } }); } int exec(const std::string& cmd, const std::string& cwd, const std::string& args, const std::string& envs, std::function std_in) { return exec(cmd, cwd, args, envs, [&](const pipe& input, const pipe&) { std_in([&](const char* buffer, std::size_t n) { input.write(buffer, n); }); }); } // clang-format on #endif struct process_impl { std::string args{}; std::string envs{}; std::string command{}; fs::path cwd{}; std::string get_command() const { std::string result; if(not cwd.empty()) result += "cd " + cwd.string() + "; "; if(not envs.empty()) result += envs + " "; result += command; if(not args.empty()) result += " " + args; return result; } template void check_exec(Ts&&... xs) const { int ec = migraphx::exec(std::forward(xs)...); if(ec != 0) MIGRAPHX_THROW("Command " + get_command() + " exited with status " + std::to_string(ec)); } }; process::process(const std::string& cmd, const std::vector& args) : impl(std::make_unique()) { impl->command = cmd; if(not args.empty()) impl->args = join_strings(args, " "); } process::process(process&&) noexcept = default; process& process::operator=(process rhs) { std::swap(impl, rhs.impl); return *this; } process::~process() noexcept = default; process& process::cwd(const fs::path& p) { impl->cwd = p; return *this; } process& process::env(const std::vector& envs) { if(not envs.empty()) { impl->envs = join_strings(envs, " "); } return *this; } void process::read(const writer& output) const { #ifdef _WIN32 // clang-format off constexpr std::string_view filename = "stdout"; auto tmp = tmp_dir{}; HANDLE handle = CreateFile((tmp.path / filename).string().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); impl->check_exec(impl->command, impl->cwd.string(), impl->args, impl->envs, handle == nullptr or handle == INVALID_HANDLE_VALUE ? GetStdHandle(STD_OUTPUT_HANDLE) : handle); CloseHandle(handle); handle = CreateFile((tmp.path / filename).string().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if(handle == nullptr or handle == INVALID_HANDLE_VALUE) MIGRAPHX_THROW("Unable to open file: " + (tmp.path / filename)); auto size = GetFileSize(handle, nullptr); std::string result(size, '\0'); if(ReadFile(handle, result.data(), size, nullptr, nullptr) == FALSE) MIGRAPHX_THROW("Failed reading file: " + (tmp.path / filename)); CloseHandle(handle); // clang-format on #else std::stringstream ss; impl->check_exec(impl->get_command(), redirect_to(ss)); auto result = ss.str(); #endif output(result.data(), result.size()); } void process::exec() { #ifndef _WIN32 impl->check_exec(impl->get_command(), redirect_to(std::cout)); #else // clang-format off impl->check_exec(impl->command, impl->cwd.string(), impl->args, impl->envs, GetStdHandle(STD_OUTPUT_HANDLE)); // clang-format on #endif } void process::write(std::function pipe_in) { #ifndef _WIN32 impl->check_exec(impl->get_command(), std::move(pipe_in)); #else // clang-format off impl->check_exec(impl->command, impl->cwd.string(), impl->args, impl->envs, std::move(pipe_in)); // clang-format on #endif } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/program.cpp000066400000000000000000001362771510465702400175220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { using milliseconds = std::chrono::duration; struct mark_instruction_target { std::size_t target_id = 0; std::string name() const { return "mark_instruction_target"; } void apply(module& m) const { for(auto& ins : m) ins.set_target_id(target_id); } }; struct program_impl { // A map is used to keep references to modules of the program std::unordered_map modules; std::vector contexts; std::vector targets; }; program::program() : impl(std::make_unique()) { this->create_module("main"); } program::program(module m) : impl(std::make_unique()) { this->create_module("main", std::move(m)); } program::program(program&&) noexcept = default; program::~program() noexcept = default; // copy constructor program::program(const program& p) { assign(p); } // copy assignment operator program& program::operator=(program p) { std::swap(p.impl, this->impl); return *this; } void program::assign(const program& p) { if(not impl) { impl = std::make_unique(); } *impl = *p.impl; // build a map from old ins to new ins // Build a map from old module to new module std::unordered_map mod_map; std::transform( impl->modules.begin(), impl->modules.end(), std::inserter(mod_map, mod_map.begin()), [&](auto&& xp) { return std::make_pair(&p.impl->modules.at(xp.first), &xp.second); }); std::unordered_map ins_map; for(auto&& pp : mod_map) { auto old_ins = iterator_for(*pp.first); auto new_ins = iterator_for(*pp.second); std::transform(old_ins.begin(), old_ins.end(), new_ins.begin(), std::inserter(ins_map, ins_map.begin()), [](auto x, auto y) { return std::make_pair(x, y); }); } // Update all references from all modules for(auto&& mp : impl->modules) { for(auto ins : iterator_for(mp.second)) instruction::replace_refs(ins, ins_map, mod_map); } } shape program::get_parameter_shape(std::string name) const { const auto* mm = this->get_main_module(); return mm->get_parameter_shape(std::move(name)); } std::vector program::get_parameter_names() const { const auto* mm = this->get_main_module(); return mm->get_parameter_names(); } instruction_ref program::get_parameter(std::string name) const { const auto* mm = this->get_main_module(); return mm->get_parameter(std::move(name)); } std::unordered_map program::get_parameter_shapes() const { const auto* mm = this->get_main_module(); return mm->get_parameter_shapes(); } std::size_t program::size() const { return impl->modules.size(); } std::vector program::get_output_shapes() const { const auto* mm = this->get_main_module(); return mm->get_output_shapes(); } context& program::get_context() const { assert(impl->contexts.size() == 1); return impl->contexts.front(); } instruction_ref program::validate() const { const auto* mm = this->get_main_module(); return mm->validate(); } target_assignments program::get_target_assignments(const std::vector& targets, assignment_options options) { const auto m = options.metric; target_assignments p; const auto* mod = get_main_module(); std::vector> target_subgraphs; target_subgraphs.reserve(targets.size()); std::transform(targets.begin(), targets.end(), std::back_inserter(target_subgraphs), [&](const auto& t) { return std::make_pair(t, t.find_supported(mod, m)); }); for(const auto ins : iterator_for(*mod)) { if(contains(p, ins)) { continue; } for(const auto& [target, subgraph] : target_subgraphs) { // can't pass a structured binding into lambda in C++17 so create a variable for it const auto& t = target; for(const auto& segment : subgraph) { const auto& instructions = segment.instructions; if(not contains(instructions, ins)) { continue; } std::transform(instructions.begin(), instructions.end(), std::inserter(p, p.end()), [&](auto instr) { return std::make_pair(instr, t.name()); }); } } } return p; } bool program::is_compiled() const { return not this->impl->contexts.empty(); } void program::compile(const std::vector& targets, std::vector compile_opts) { // Gather all the target roots std::unordered_multimap roots; auto mods = this->get_modules(); for(const auto* mod : mods) { for(const auto& ins : *mod) { if(ins.name() != "run_on_target") continue; auto v = ins.get_operator().to_value(); module_ref root = ins.module_inputs().front(); std::size_t root_target_id = v.at("target_id").to(); assert(root_target_id < targets.size()); roots.insert({root_target_id, root}); } } auto trace = tracer{}; // TODO: Add tracer based on compile options if(enabled(MIGRAPHX_TRACE_COMPILE{})) trace = tracer{std::cout}; trace(*this); trace(); // It is assumed that all instructions outside of any root module would run on "ref" target // Ref target may or may not be passed as one of the target for the "compile()". // If it is not passed, Create one and add context of it into the map. auto target_idx = [&](const std::string& t_name) { return static_cast( std::find_if( targets.begin(), targets.end(), [&](const auto& t) { return t.name() == t_name; }) - targets.begin()); }; std::size_t ref_target_id = target_idx("ref"); if(ref_target_id == targets.size()) { this->impl->contexts.resize(targets.size() + 1); this->impl->contexts[ref_target_id] = migraphx::make_target("ref").get_context(); // users could pass lessers compile_ops than targets, in that case use default compile_opts compile_opts.resize(targets.size() + 1, migraphx::compile_options{}); } else { this->impl->contexts.resize(targets.size()); compile_opts.resize(targets.size(), migraphx::compile_options{}); } // mark all the instruction as ref target first, later change target_id based on root-target run_passes(*this, {mark_instruction_target{ref_target_id}}); // Run passes on each root target for(const auto i : range(targets.size())) { const auto& root_target = targets.at(i); auto root_target_id = i; auto root_modules_range = roots.equal_range(root_target_id); this->impl->contexts[root_target_id] = root_target.get_context(); for(const auto& [id, current_mod] : range(root_modules_range)) { auto passes = root_target.get_passes(this->impl->contexts[root_target_id], compile_opts[root_target_id]); passes.push_back(mark_instruction_target{static_cast(root_target_id)}); run_passes(*this, current_mod, passes, trace); auto invalid = current_mod->validate(); if(invalid != current_mod->end()) { MIGRAPHX_THROW("Invalid module " + current_mod->name() + " from compilation at instruction " + std::to_string(std::distance(current_mod->begin(), invalid))); } auto dangling = current_mod->find_dangling_reference(); if(dangling != current_mod->end()) { auto index = std::distance(current_mod->begin(), dangling); MIGRAPHX_THROW("Dangling reference in module " + current_mod->name() + " from instruction " + std::to_string(index)); } } } this->finalize(); } void program::compile(const target& t, compile_options options) { // todo: combine with multi-target compile method assert(not this->is_compiled()); this->impl->targets = {t}; this->impl->contexts = {t.get_context()}; if(enabled(MIGRAPHX_TRACE_COMPILE{})) options.trace = tracer{std::cout}; options.trace(*this); options.trace(); auto&& passes = t.get_passes(this->impl->contexts.front(), options); run_passes(*this, passes, options.trace); auto mods = this->get_modules(); // Validate and finalize for(const auto& mod : reverse(mods)) { auto invalid = mod->validate(); if(invalid != mod->end()) { MIGRAPHX_THROW("Invalid module " + mod->name() + " from compilation at instruction " + std::to_string(std::distance(mod->begin(), invalid))); } auto dangling = mod->find_dangling_reference(); if(dangling != mod->end()) { auto index = std::distance(mod->begin(), dangling); MIGRAPHX_THROW("Dangling reference in module " + mod->name() + " from instruction " + std::to_string(index)); } mod->finalize(this->impl->contexts); } } void program::finalize() { auto* mm = this->get_main_module(); mm->finalize(this->impl->contexts); } template static std::string classify(T x) { switch(std::fpclassify(static_cast(x))) { case FP_INFINITE: return "inf"; case FP_NAN: return "nan"; case FP_NORMAL: return "normal"; case FP_SUBNORMAL: return "subnormal"; case FP_ZERO: return "zero"; default: return "unknown"; } } static void print_statistics(std::ostream& os, const argument& a) { a.visit( [&](auto t) { os << "Min value: " << *std::min_element(t.begin(), t.end()) << ", "; os << "Max value: " << *std::max_element(t.begin(), t.end()) << ", "; double num_elements = t.size(); auto mean = std::accumulate(t.begin(), t.end(), 0.0) / num_elements; auto stddev = std::sqrt( std::accumulate(t.begin(), t.end(), 0.0, [&](auto r, auto v) { return r + std::pow((v - mean), 2.0); }) / num_elements); os << "Mean: " << mean << ", "; os << "StdDev: " << stddev << "\n"; }, [&](const auto& xs) { for(const auto& x : xs) { print_statistics(os, x); } }); } static std::unordered_set classify_argument(const argument& a) { std::unordered_set result; a.visit( [&](auto t) { for(const auto& x : t) result.insert(classify(x)); }, [&](const auto& xs) { for(const auto& x : xs) { auto r = classify_argument(x); result.insert(r.begin(), r.end()); } }); return result; } static void preview_argument(std::ostream& os, const argument& a) { a.visit( [&](auto t) { if(t.size() <= 10) { os << t; } else { os << to_string_range(t.begin(), t.begin() + 5); os << ", ..., "; os << to_string_range(t.end() - 5, t.end()); } }, [&](const auto& xs) { for(const auto& x : xs) { os << '{'; preview_argument(os, x); os << '}'; } }); } // This function currently used only in an Assertion. // "Almost identical" shapes. To support an MLIR feature, there is a limited // case where shapes may both be standard but have non-identical strides. #ifndef NDEBUG static bool is_compatible_shape(const shape& actual, const shape& expected) { // Check subshapes if(expected.type() == shape::tuple_type) return equal(actual.sub_shapes().begin(), actual.sub_shapes().end(), expected.sub_shapes().begin(), &is_compatible_shape); // Only the expected can be dynamic if(expected.dynamic()) return true; if(actual == expected) return true; if(actual.type() != expected.type()) return false; // If both shapes are standard and lens match, they are considered compatible // even if strides are different. if(actual.standard() and expected.standard()) return actual.lens() == expected.lens(); return false; } #endif template static std::vector generic_eval(const module* mod, std::vector& ctx, const std::unordered_map& params, pmr::unordered_map& results, F trace) { assert(mod->validate() == mod->end()); std::vector values; values.reserve(16); for(auto ins : iterator_for(*mod)) { assert(mod->name() != "main" or results.find(ins) == results.end()); #ifndef NDEBUG results.emplace(ins, argument{}); #endif const auto& name = ins->name(); if(name == "@literal") { results.insert_or_assign(ins, trace(ins, [&] { return ins->get_literal().get_argument(); })); } else if(name == "@param") { results.insert_or_assign( ins, trace(ins, [&] { auto param_name = any_cast(ins->get_operator()).parameter; if(not contains(params, param_name)) MIGRAPHX_THROW("Parameter not found: " + param_name); auto param = params.at(param_name); // TODO: may want to check correct number of dimensions and/or was within bounds if(not ins->get_shape().any_of_dynamic() and param.get_shape() != ins->get_shape()) { MIGRAPHX_THROW("Incorrect shape {" + to_string(param.get_shape()) + "} for parameter: " + param_name + " should be: " + to_string(ins->get_shape())); } return param; })); } else if(name == "@outline") { results.insert_or_assign( ins, trace(ins, [&] { return argument{ins->get_shape(), nullptr}; })); } else if(name == "@return") { std::vector prog_outputs; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(prog_outputs), [&](instruction_ref i) { assert(results.find(i) != results.end()); return results[i]; }); return prog_outputs; } else { values.resize(ins->inputs().size()); std::transform( ins->inputs().begin(), ins->inputs().end(), values.begin(), [&](instruction_ref i) { assert(results.find(i) != results.end()); return results[i]; }); const auto& mod_args = ins->module_inputs(); auto module_eval = [&](module_ref smod, const std::unordered_map& inputs) { return generic_eval(smod, ctx, inputs, results, trace); }; results.insert_or_assign( ins, trace(ins, [&] { auto op = ins->normalized_operator(); if(op.is_context_free()) return op.compute(ins->get_shape(), values, mod_args, module_eval); if(ins->get_target_id() >= ctx.size()) MIGRAPHX_THROW("No context available for " + op.name()); return op.compute( ctx[ins->get_target_id()], ins->get_shape(), values, mod_args, module_eval); })); } assert(results.find(ins) != results.end()); assert(is_compatible_shape(results.at(ins).get_shape(), ins->get_shape())); } return {results.at(std::prev(mod->end()))}; } template static std::vector generic_eval(const program& p, std::vector& ctx, const std::unordered_map& params, F trace) { const module* mm = p.get_main_module(); #if MIGRAPHX_HAS_PMR std::size_t n = p.total_instructions(); std::vector buffer(n * (sizeof(instruction_ref) + sizeof(argument)) * 4); std::pmr::monotonic_buffer_resource bres( buffer.data(), buffer.size(), std::pmr::null_memory_resource()); pmr::unordered_map results(&bres); results.reserve(n); #else pmr::unordered_map results; #endif return generic_eval(mm, ctx, params, results, trace); } std::size_t program::total_instructions() const { return transform_accumulate(impl->modules.begin(), impl->modules.end(), std::size_t{0}, std::plus<>{}, [](const auto& p) { return p.second.size(); }); } std::vector program::eval_with_context(std::vector& ctx, const parameter_map& params) const { return generic_eval(*this, ctx, params, [](auto&&, auto f) { return f(); }); } std::vector program::eval(const parameter_map& params, execution_environment exec_env) const { auto& contexts = this->impl->contexts; auto trace_level = value_of(MIGRAPHX_TRACE_EVAL{}); std::vector ret; if(exec_env.async) { assert(contexts.size() == 1); contexts.front().wait_for(exec_env.queue); } if(trace_level > 0) { std::unordered_map ins_out; // get instruction names this->print([&](auto x, const auto& ins_names) { std::stringstream ss; instruction::print(ss, x, ins_names); ins_out[x] = ss.str(); }); ret = generic_eval(*this, contexts, params, [&](instruction_ref ins, auto f) { const auto& ctx = contexts[ins->get_target_id()]; ctx.finish(); std::cout << "Run instruction: " << ins_out.at(ins) << std::endl; timer t{}; auto result = f(); double t1 = t.record(); ctx.finish(); double t2 = t.record(); std::cout << "Time: " << t1 << "ms, " << t2 << "ms" << std::endl; if(trace_level > 1 and ins->name().front() != '@' and ins->name() != "load" and not result.empty()) { migraphx::argument buffer; try { const target& tgt = this->impl->targets.at(ins->get_target_id()); buffer = tgt.copy_from(result); } catch(const migraphx::exception&) { // instruction was run on host then no need to copy buffer from target buffer = result; } catch(...) { MIGRAPHX_THROW("MIGraphX program execution with MIGRAPHX_TRACE_EVAL failed.\n"); } if(trace_level == 2) { std::cout << "Output has " << to_string_range(classify_argument(buffer)) << std::endl; std::cout << "Output: "; preview_argument(std::cout, buffer); std::cout << std::endl; print_statistics(std::cout, buffer); } else { std::cout << "Output: " << buffer << std::endl; } } return result; }); } else { ret = generic_eval(*this, contexts, params, [&](auto&&, auto f) { return f(); }); } if(exec_env.async) { assert(contexts.size() == 1); contexts.front().finish_on(exec_env.queue); } return ret; } void program::finish() const { for(const auto& ctx : this->impl->contexts) ctx.finish(); } static std::string get_migraphx_version() { std::stringstream ss; ss << std::to_string(MIGRAPHX_VERSION_MAJOR) << "." << std::to_string(MIGRAPHX_VERSION_MINOR) << "." << std::to_string(MIGRAPHX_VERSION_PATCH); return ss.str(); } /* program file version is for the data structure or format of the MXR file. Version should be bumped if any changes occur to the format of the MXR file. */ const int program_file_version = 7; value program::to_value() const { value result; result["version"] = program_file_version; result["migraphx_version"] = get_migraphx_version(); result["targets"] = migraphx::to_value(this->impl->targets); result["contexts"] = migraphx::to_value(this->impl->contexts); value module_vals = value::object{}; std::unordered_map names; for(auto& mod : this->get_modules()) { value mod_val; value nodes; mod_val["name"] = mod->name(); names = mod->print( [&](auto ins, auto ins_names) { value node; node["output"] = ins_names.at(ins); node["name"] = ins->name(); node["shape"] = migraphx::to_value(ins->get_shape()); node["normalized"] = ins->is_normalized(); if(ins->name() == "@literal") node["literal"] = migraphx::to_value(ins->get_literal()); node["operator"] = ins->get_operator().to_value(); std::vector inputs; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(inputs), [&](auto i) { assert(contains(ins_names, i)); return ins_names.at(i); }); node["inputs"] = inputs; auto module_args = ins->module_inputs(); if(not module_args.empty()) { std::vector module_inputs; std::transform(module_args.begin(), module_args.end(), std::back_inserter(module_inputs), [&](auto mod_ref) { return mod_ref->name(); }); node["module_inputs"] = module_inputs; } nodes.push_back(node); }, names); mod_val["nodes"] = nodes; module_vals[mod->name()] = mod_val; } result["modules"] = module_vals; return result; } static void mod_from_val(module_ref mod, const value& v, std::unordered_map& instructions, const std::unordered_map& map_mods) { const auto& module_val = v.at(mod->name()); for(const value& node : module_val.at("nodes")) { instruction_ref output; auto name = node.at("name").to(); auto fields = node.at("operator"); auto normalized = node.at("normalized").to(); if(name == "@param") { output = mod->insert_parameter(mod->end(), fields["parameter"].to(), migraphx::from_value(node.at("shape"))); } else if(name == "@literal") { output = mod->insert_literal(mod->end(), migraphx::from_value(node.at("literal"))); } else { auto op = make_op(name, fields); std::vector inputs; std::transform(node.at("inputs").begin(), node.at("inputs").end(), std::back_inserter(inputs), [&](const value& i) { auto i_name = i.to(); assert(contains(instructions, i_name)); return instructions.at(i_name); }); std::vector module_inputs; if(node.contains("module_inputs")) { std::transform(node.at("module_inputs").begin(), node.at("module_inputs").end(), std::back_inserter(module_inputs), [&](const value& i) { return map_mods.at(i.to()); }); for(const auto& smod : module_inputs) { mod_from_val(smod, v, instructions, map_mods); } } if(name == "@return") { output = mod->add_return(inputs); } else if(module_inputs.empty()) { output = mod->insert_instruction(mod->end(), op, inputs); } else { output = mod->insert_instruction(mod->end(), op, inputs, module_inputs); } } output->set_normalized(normalized); instructions[node.at("output").to()] = output; } } void program::from_value(const value& v) { auto version = v.at("version").to(); if(version != program_file_version) { MIGRAPHX_THROW( "Error: Program version mismatch. MXR file was created using program file version: " + std::to_string(version) + ", while installed MIGraphX is using program file version: " + std::to_string(program_file_version) + ", Try regenerating MXR file using installed MIGraphX and running again."); } auto migx_version = v.at("migraphx_version").to(); if(migx_version != get_migraphx_version()) { std::cerr << "[WARNING]: MXR File was created using MIGraphX version: " << migx_version << ", while installed MIGraphX is at version: " << get_migraphx_version() << ", operators implementation could be mismatched.\n"; } migraphx::from_value(v.at("targets"), this->impl->targets); for(auto i : range(this->impl->targets.size())) { this->impl->contexts.push_back(this->impl->targets[i].get_context()); this->impl->contexts.back().from_value(v.at("contexts")[i]); } auto module_vals = v.at("modules"); for(const auto& vv : module_vals) { const auto& name = vv.get_key(); if(name == "main") continue; impl->modules.emplace(name, name); } std::unordered_map map_mods; std::transform(impl->modules.begin(), impl->modules.end(), std::inserter(map_mods, map_mods.end()), [&](auto&& pp) { return std::make_pair(pp.first, &pp.second); }); std::unordered_map map_insts; auto* mm = get_main_module(); mod_from_val(mm, module_vals, map_insts, map_mods); // Finalize a compiled model if(not this->impl->contexts.empty()) this->finalize(); } static double common_average(const std::vector& v) { std::size_t n = v.size() / 4; double total = std::accumulate(v.begin() + n, v.end() - n, 0.0); return total / std::distance(v.begin() + n, v.end() - n); } static double mean(const std::vector& v) { double total = std::accumulate(v.begin(), v.end(), 0.0); return total / v.size(); } static double median(const std::vector& v) { size_t mid = v.size() / 2; if(v.size() % 2 == 0) { return (v[mid - 1] + v[mid]) / 2.0; } else { return v[mid]; } } static double percentile(const std::vector& v, double percentile) { size_t index = (percentile * (v.size() - 1)); return v[index]; } static std::string perf_group(instruction_ref ins, bool detailed) { std::string result; auto attr = ins->get_operator().attributes(); if(attr.contains("group")) result = attr.at("group").to(); else result = ins->get_operator().name(); if(detailed) { result += "<" + ins->get_shape().type_string(); result += "(" + shape::to_sizes_string(to_shapes(ins->inputs())) + ")>"; } return result; } void program::mark(const parameter_map& params, marker m) { auto& ctx = this->impl->contexts; // Run once by itself eval(params); this->finish(); m.mark_start(*this); generic_eval(*this, ctx, params, [&](auto ins, auto f) { argument result; m.mark_start(ins); result = f(); m.mark_stop(ins); return result; }); m.mark_stop(*this); } void program::perf_report( std::ostream& os, std::size_t n, parameter_map params, std::size_t batch, bool detailed) const { auto& ctx = this->impl->contexts; // Run once by itself eval(params); this->finish(); // Run and time entire program std::vector total_vec; total_vec.reserve(n); for(std::size_t i = 0; i < n; i++) { total_vec.push_back(time([&] { eval(params); this->finish(); })); } std::sort(total_vec.begin(), total_vec.end()); std::unordered_map> ins_vec; // Fill the map generic_eval(*this, ctx, params, [&](auto ins, auto) { ins_vec[ins].reserve(n); return argument{ins->get_shape(), nullptr}; }); // Run and time each instruction for(std::size_t i = 0; i < n; i++) { generic_eval(*this, ctx, params, [&](auto ins, auto f) { argument result; ins_vec[ins].push_back(time([&] { result = f(); this->impl->contexts[ins->get_target_id()].finish(); })); return result; }); } for(auto&& p : ins_vec) std::sort(p.second.begin(), p.second.end()); // Run and time implicit overhead std::vector overhead_vec; overhead_vec.reserve(n); for(std::size_t i = 0; i < n; i++) { overhead_vec.push_back(time([&] { dry_run(params); })); } double total_time = common_average(total_vec); double min_time = total_vec.front(); double max_time = total_vec.back(); double mean_time = mean(total_vec); double median_time = median(total_vec); double percentile_90_time = percentile(total_vec, 0.90); double percentile_95_time = percentile(total_vec, 0.95); double percentile_99_time = percentile(total_vec, 0.99); double rate = 1000.0 / total_time; double overhead_time = common_average(overhead_vec); double overhead_percent = overhead_time * 100.0 / total_time; double total_instruction_time = 0.0; std::unordered_map op_times; std::unordered_map op_n; for(auto&& p : ins_vec) { double avg = common_average(p.second); op_times[perf_group(p.first, detailed)] += avg; total_instruction_time += avg; op_n[perf_group(p.first, detailed)]++; } double calculate_overhead_time = total_time - total_instruction_time; double calculate_overhead_percent = calculate_overhead_time * 100.0 / total_time; std::unordered_map names; this->print(names, [&](auto ins, const auto& ins_names) { instruction::print(std::cout, ins, ins_names); // skip return instruction if(ins->name() == "@return") return; double avg = common_average(ins_vec[ins]); double percent = std::ceil(100.0 * avg / total_instruction_time); os << ": " << avg << "ms, " << percent << "%"; os << std::endl; }); os << std::endl; os << "Summary:" << std::endl; std::vector> op_times_sorted; std::transform( op_times.begin(), op_times.end(), std::back_inserter(op_times_sorted), [&](const auto& p) { auto&& name = p.first; return std::make_tuple(p.second, op_n.at(name), name); }); std::sort(op_times_sorted.begin(), op_times_sorted.end(), std::greater<>{}); for(auto&& [avg, nn, name] : op_times_sorted) { double percent = std::ceil(100.0 * avg / total_instruction_time); double per_ins = avg / nn; os << name << ": " << avg << "ms / " << nn << " = " << per_ins << "ms, " << percent << "%" << std::endl; } os << std::endl; os << "Batch size: " << batch << std::endl; os << "Rate: " << rate * batch << " inferences/sec" << std::endl; os << "Total time: " << total_time << "ms "; os << "(Min: " << min_time << "ms, "; os << "Max: " << max_time << "ms, "; os << "Mean: " << mean_time << "ms, "; os << "Median: " << median_time << "ms)" << std::endl; os << "Percentiles (90%, 95%, 99%): ("; os << percentile_90_time << "ms, " << percentile_95_time << "ms, " << percentile_99_time << "ms)" << std::endl; os << "Total instructions time: " << total_instruction_time << "ms" << std::endl; os << "Overhead time: " << overhead_time << "ms" << ", " << calculate_overhead_time << "ms" << std::endl; os << "Overhead: " << std::round(overhead_percent) << "%" << ", " << std::round(calculate_overhead_percent) << "%" << std::endl; } void program::debug_print() const { std::cout << *this << std::endl; } void program::debug_print(instruction_ref ins) const { std::unordered_map names; if(std::any_of(this->impl->modules.begin(), this->impl->modules.end(), [&](const auto& pp) { return is_end(pp.second.end(), ins); })) { std::cout << "End instruction" << std::endl; return; } else if(std::none_of(this->impl->modules.begin(), this->impl->modules.end(), [&](const auto& pp) { return pp.second.has_instruction(ins); })) { std::cout << "Instruction not part of program" << std::endl; return; } this->print(names, [&](auto x, const auto& ins_names) { if(x == ins) { instruction::print(std::cout, x, ins_names); std::cout << std::endl; } }); } void program::print( std::unordered_map& names, const std::function)>& print_func) const { for(const auto& pp : this->impl->modules) { names = pp.second.print(print_func, names); } } void program::print( const std::function)>& print_func) const { std::unordered_map names; this->print(names, print_func); } void program::print_graph(std::ostream& os, bool brief) const { const auto* mm = this->get_main_module(); os << "digraph {\n\tperipheries=0;\n"; mm->print([&](auto ins, auto ins_names) { const auto& ins_name = graphviz::enclose_name(ins_names.at(ins)); os << "\t" << ins_name << "["; if(brief) { os << "label=" << graphviz::enclose_name(ins->name()) << "]"; os << ";" << std::endl; } else { graphviz::graphviz_node_content content = graphviz::get_node_content(ins); os << "label=" << graphviz::build_html_label(content) << " "; os << graphviz::build_node_style(content.node_style); os << "];\n"; } if(not ins->inputs().empty()) { for(auto&& arg : ins->inputs()) { os << "\t" << graphviz::enclose_name(ins_names.at(arg)) << " -> " << graphviz::enclose_name(ins_names.at(ins)); if(not brief) os << "[label=" << graphviz::enclose_name(graphviz::format_shape_name(ins->get_shape())) << "]"; os << ";\n"; } } }); os << "}" << std::endl; } void program::print_py(std::ostream& os) const { auto vec_modules = this->get_modules(); std::unordered_map names; os << "p = migraphx.program()\n"; for(auto& mod : vec_modules) { std::string var_name = "m"; if(mod->name() != "main") var_name += mod->name(); os << var_name << " = "; if(mod->name() == "main") os << "p.get_main_module()"; else os << "p.create_module(\"" << mod->name() << "\");"; os << std::endl; names = mod->print_py(os, var_name, names); os << std::endl; } } void program::print_cpp(std::ostream& os) const { auto vec_modules = this->get_modules(); std::unordered_map names; os << "migraphx::program p;\n"; for(auto& mod : vec_modules) { std::string var_name = "m" + mod->name(); os << "migraphx::module_ref " << var_name << " = "; if(mod->name() == "main") os << "p.get_main_module();"; else os << "p.create_module(\"" << mod->name() << "\");"; os << std::endl; names = mod->print_cpp(os, var_name, names); os << std::endl; } } void program::dry_run(const parameter_map& params) const { auto& ctx = this->impl->contexts; generic_eval(*this, ctx, params, [](auto ins, auto&&...) { return argument{ins->get_shape(), nullptr}; }); } void program::annotate(std::ostream& os, const std::function& a) const { for(auto& pp : this->impl->modules) { std::cout << pp.first << ":" << std::endl; pp.second.annotate(os, a); } } const module* program::get_module(const std::string& name) const { return &impl->modules.at(name); } module* program::create_module(const std::string& name) { assert(not contains(impl->modules, name)); auto r = impl->modules.emplace(name, name); return &(r.first->second); } module* program::create_module(const std::string& name, module m) { assert(not contains(impl->modules, name)); m.set_name(name); auto r = impl->modules.emplace(name, std::move(m)); return &(r.first->second); } module* program::get_module(const std::string& name) { return &impl->modules.at(name); } module* program::get_main_module() { return get_module("main"); } const module* program::get_main_module() const { return get_module("main"); } template static std::vector generic_get_modules(T* mm) { std::vector vec_modules; vec_modules.push_back(mm); auto sub_modules = mm->get_sub_modules(); vec_modules.insert(vec_modules.end(), sub_modules.begin(), sub_modules.end()); return vec_modules; } template static void generic_get_unused_modules(Map& m, const std::vector& mods, OutputIterator out) { std::unordered_set used; std::transform(mods.begin(), mods.end(), std::inserter(used, used.end()), [](auto&& mod) { return mod->name(); }); transform_if( m.begin(), m.end(), out, [&](auto&& pp) { return not contains(used, pp.first); }, [](auto&& pp) { return &pp.second; }); } std::vector program::get_modules() const { auto result = generic_get_modules(this->get_main_module()); generic_get_unused_modules(impl->modules, result, std::back_inserter(result)); return result; } std::vector program::get_modules() { auto result = generic_get_modules(this->get_main_module()); generic_get_unused_modules(impl->modules, result, std::back_inserter(result)); return result; } template static void generic_insert_module_tree(Module* pm, Map& m) { for(auto* sm : pm->get_sub_modules(true)) { m.insert(std::make_pair(sm, pm)); generic_insert_module_tree(sm, m); } } std::unordered_multimap program::get_module_tree() { std::unordered_multimap result; generic_insert_module_tree(this->get_main_module(), result); return result; } template MIGRAPHX_DEBUG_USED static bool is_unused_module(Map& m, const std::vector& mods, const std::string& name) { bool is_unused = false; generic_get_unused_modules(m, mods, make_function_output_iterator([&](auto* mod) { if(mod->name() == name) is_unused = true; })); return is_unused; } template MIGRAPHX_DEBUG_USED static bool references_instruction(Map& m, const instruction& ins, const std::string& name) { return std::any_of(m.begin(), m.end(), [&](auto&& p) { if(p.first == name) return false; return std::any_of(p.second.begin(), p.second.end(), [&](auto&& i) { return std::any_of(i.inputs().begin(), i.inputs().end(), [&](auto&& j) { return std::addressof(*j) == std::addressof(ins); }); }); }); } void program::remove_module(const std::string& name) { // cppcheck-suppress assertWithSideEffect assert(is_unused_module(impl->modules, generic_get_modules(this->get_main_module()), name) and "Module used in program"); assert(std::none_of( impl->modules.at(name).begin(), impl->modules.at(name).end(), [&](auto&& ins) { return references_instruction(impl->modules, ins, name); }) and "Instruction referenced in another module"); // if an instruction has an input out side of the current module, need to remove // the instruction from its input's outputs auto& mod = impl->modules.at(name); for(auto ins : iterator_for(mod)) { auto inputs = ins->inputs(); for(auto in : inputs) { if(not mod.has_instruction(in)) { in->remove_output(ins); } } } impl->modules.erase(name); } void program::rename_module(const std::string& old_name, const std::string& new_name) { assert(old_name != new_name); assert(contains(impl->modules, old_name)); assert(not contains(impl->modules, new_name)); auto node = impl->modules.extract(old_name); node.key() = new_name; node.mapped().set_name(new_name); impl->modules.insert(std::move(node)); } void program::remove_unused_modules() { std::vector unused; generic_get_unused_modules( impl->modules, generic_get_modules(this->get_main_module()), std::back_inserter(unused)); for(const auto* m : unused) this->remove_module(m->name()); } program& program::sort() { std::queue mqueue; mqueue.push(get_main_module()); while(not mqueue.empty()) { module_ref current_mod = mqueue.front(); current_mod->sort(); mqueue.pop(); auto child_mods = current_mod->get_sub_modules(true); for(auto& sub_mod : child_mods) { mqueue.push(sub_mod); } } return *this; } bool operator==(const program& x, const program& y) { return to_string(x) == to_string(y); } std::ostream& operator<<(std::ostream& os, const program& p) { auto vec_modules = p.get_modules(); std::unordered_map names; for(auto& mod : vec_modules) { os << "module: \"" << mod->name() << "\"" << std::endl; names = mod->print( [&](auto ins, const auto& ins_names) { instruction::print(os, ins, ins_names); os << std::endl; }, names); os << std::endl; } return os; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/promote_literals.cpp000066400000000000000000000037731510465702400214310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void promote_literals::apply(module_pass_manager& mpm) const { module& m = mpm.get_module(); module_ref root_module = mpm.get_root_module(); if(m == *root_module) return; for(auto ins : iterator_for(m)) { if(ins->name() == "@literal") { auto new_lit = root_module->add_literal(ins->get_literal()); auto ins_outputs = ins->outputs(); for(auto out_ins : ins_outputs) { migraphx::instruction::replace_argument(out_ins, ins, new_lit); } } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/propagate_constant.cpp000066400000000000000000000120021510465702400217210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_PROPAGATE_CONSTANT) static bool skip_propagate(instruction_ref ins) { if(contains({"contiguous", "dequantizelinear", "reshape"}, ins->name())) return skip_propagate(ins->inputs().front()); if(contains({"unpack_int4", "unpack_fp4"}, ins->name())) return true; auto&& s = ins->get_shape(); if(s.broadcasted() and s.element_space() < s.elements()) return true; auto alias = instruction::get_output_alias(ins, true); if(alias != ins) return skip_propagate(alias); if(ins->is_undefined()) return true; return false; } static bool is_const_ins(instruction_ref ins, const std::unordered_set& skip_ops) { return ins->can_eval() and not skip_propagate(ins) and skip_ops.find(ins->name()) == skip_ops.end(); } static argument as_packed(const argument& c) { if(c.get_shape().packed()) return c; auto s = c.get_shape().with_lens(c.get_shape().lens()); argument result; c.visit([&](auto x) { result = literal{s, x.begin(), x.end()}.get_argument(); }); return result; } void propagate_constant::apply(module& m) const { std::unordered_set const_instrs; auto last = std::prev(m.end()); // Find instructions that can be evaluated to a literal for(auto i : iterator_for(m)) { const bool is_const = is_const_ins(i, skip_ops); if(is_const and i != last) continue; if(i == last and is_const) { const_instrs.insert(i); } else { std::copy_if(i->inputs().begin(), i->inputs().end(), std::inserter(const_instrs, const_instrs.begin()), [&](const instruction_ref ins) { return is_const_ins(ins, skip_ops) and ins->name() != "@literal"; }); } } // Compute literals in parallel std::vector const_instrs_vec{const_instrs.begin(), const_instrs.end()}; std::vector literals(const_instrs_vec.size()); std::size_t grainsize = 1; #if !MIGRAPHX_HAS_EXECUTORS std::size_t n = std::max(2048 / std::thread::hardware_concurrency(), 1); grainsize = const_instrs_vec.size() / n; #endif simple_par_for(const_instrs_vec.size(), grainsize, [&](const auto i) { literals[i] = as_packed(const_instrs_vec[i]->eval()); }); // Replace instructions in m for(size_t i = 0; i < const_instrs_vec.size(); i++) { if(not literals[i].empty()) { if(enabled(MIGRAPHX_TRACE_PROPAGATE_CONSTANT{})) { std::cout << "Constant replace: " << std::endl; std::vector inss; fix([&](auto self, auto ins) { if(contains(inss, ins)) return; for(auto input : ins->inputs()) self(input); inss.push_back(ins); })(const_instrs_vec[i]); m.debug_print(inss); } assert(literals[i].get_shape().lens() == const_instrs_vec[i]->get_shape().lens()); assert(literals[i].get_shape().bytes() <= const_instrs_vec[i]->get_shape().bytes()); auto l = m.add_literal(literals[i].get_shape(), literals[i].data()); m.replace_instruction(const_instrs_vec[i], l); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/propagate_precision.cpp000066400000000000000000000176031510465702400220770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" #endif // Class wrappper so we can compare precision using comparison operators struct precision { shape::type_t type; friend bool operator==(const precision& xp, const precision& yp) { return xp.type == yp.type; } friend bool operator<(const precision& xp, const precision& yp) { bool is_less = false; shape::visit(xp.type, [&](auto x) { shape::visit(yp.type, [&](auto y) { if(x.is_integral() != y.is_integral()) return; if(x.is_integral()) { if(x.is_unsigned() != y.is_unsigned() and x.size() == y.size()) is_less = y.is_unsigned(); else is_less = x.size() < y.size(); } else { is_less = x.size() < y.size(); } }); }); return is_less; } friend bool operator!=(const precision& xp, const precision& yp) { return not(xp == yp); } friend bool operator>(const precision& xp, const precision& yp) { return yp < xp; } // This is not totally ordered friend bool operator<=(const precision& xp, const precision& yp) { return (xp < yp) or (xp == yp); } friend bool operator>=(const precision& xp, const precision& yp) { return (xp > yp) or (xp == yp); } }; #ifdef __clang__ #pragma clang diagnostic pop #endif } // namespace static bool is_pointwise_or_reduce(instruction_ref ins) { return contains(ins->name(), "reduce") or ins->get_operator().attributes().get("pointwise", false); } // Check if its not a scalar constant static bool is_non_scalar_const(instruction_ref ins) { return not(ins->get_shape().scalar() and ins->can_eval()); } // Get the next input instruction otherwise return a nullopt static std::optional get_next_input(instruction_ref ins) { if(ins->inputs().size() == 1) return ins->inputs().front(); if(ins->inputs().size() > 1) { std::unordered_set non_scalars; std::copy_if(ins->inputs().begin(), ins->inputs().end(), std::inserter(non_scalars, non_scalars.end()), &is_non_scalar_const); if(non_scalars.size() == 1) return *non_scalars.begin(); } return nullopt; } // Find all adjacent instructions that could be upgraded with higher precision // by traversing the inputs from a convert static std::unordered_set find_adjacent_inputs(instruction_ref start) { std::unordered_set result; // Promote inputs fix([&](auto self, instruction_ref ins) { if(not is_pointwise_or_reduce(ins)) return; if(contains(result, ins)) return; auto next = get_next_input(ins); if(not next.has_value()) return; result.insert(ins); self(*next); })(start->inputs().front()); return result; } // Find all adjacent instructions that could be upgraded with higher precision // by traversing the outputs from a convert static std::unordered_set find_adjacent_outputs(instruction_ref start) { std::unordered_set result; // Promote outputs fix([&](auto self, instruction_ref ins) { for(auto output : ins->outputs()) { if(not is_pointwise_or_reduce(output)) continue; if(contains(result, output)) continue; auto next = get_next_input(output); if(not next.has_value()) continue; if(*next != ins) continue; result.insert(output); self(output); } })(start); return result; } // Insert the instructions to upgrade into the map. If the map already has the // instruction then choose the highest precision template static void insert_instructions_to_upgrade(Map& m, const Instructions& instructions, shape::type_t t) { for(auto ins : instructions) { auto it = m.find(ins); if(it == m.end()) { m[ins] = t; } else { it->second = std::max(precision{t}, precision{it->second}).type; } } } // Find adjacent instructions from a convert to upgrade to use a higher // precision static std::unordered_map find_instruction_to_upgrade(module& m) { std::unordered_map result; for(auto ins : iterator_for(m)) { if(ins->name() != "convert") continue; auto output = precision{ins->get_shape().type()}; auto input = precision{ins->inputs().front()->get_shape().type()}; if(output.type == shape::type_t::bool_type) continue; if(input < output) { insert_instructions_to_upgrade(result, find_adjacent_inputs(ins), output.type); } else if(input > output) { insert_instructions_to_upgrade(result, find_adjacent_outputs(ins), input.type); } } return result; } void propagate_precision::apply(module_pass_manager& mpm) const { auto upgrade = find_instruction_to_upgrade(mpm.get_module()); for(const auto& p : upgrade) { auto ins = p.first; auto t = p.second; auto convert1 = mpm.get_module().insert_instruction( std::next(ins), make_op("convert", {{"target_type", ins->get_shape().type()}}), ins); mpm.get_module().replace_instruction(ins, convert1); std::vector inputs; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(inputs), [&](auto input) { return mpm.get_module().insert_instruction( ins, make_op("convert", {{"target_type", t}}), input); }); mpm.get_module().replace_instruction(ins, ins->get_operator(), inputs); } mpm.run_pass(eliminate_convert{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/py/000077500000000000000000000000001510465702400157575ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/py/CMakeLists.txt000066400000000000000000000047041510465702400205240ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### add_library(migraphx_py py_loader.cpp) migraphx_generate_export_header(migraphx_py) target_include_directories(migraphx_py PRIVATE include) target_link_libraries(migraphx_py PUBLIC migraphx) rocm_install_targets(TARGETS migraphx_py INCLUDE include) include(PythonModules) foreach(PYTHON_VERSION ${PYTHON_VERSIONS}) py_add_module(migraphx_pybind_${PYTHON_VERSION} migraphx_py.cpp PYTHON_VERSION ${PYTHON_VERSION} PYTHON_MODULE migraphx) target_link_libraries(migraphx_pybind_${PYTHON_VERSION} PRIVATE migraphx migraphx_tf migraphx_onnx migraphx_all_targets) rocm_install_targets(TARGETS migraphx_pybind_${PYTHON_VERSION}) add_dependencies(migraphx_py migraphx_pybind_${PYTHON_VERSION}) add_library(migraphx_py_${PYTHON_VERSION} py.cpp) target_include_directories(migraphx_py_${PYTHON_VERSION} PRIVATE include) target_link_libraries(migraphx_py_${PYTHON_VERSION} PUBLIC migraphx) target_link_libraries(migraphx_py_${PYTHON_VERSION} PRIVATE pybind11::pybind11 python${PYTHON_VERSION}::runtime) rocm_install_targets(TARGETS migraphx_py_${PYTHON_VERSION}) add_dependencies(migraphx_py migraphx_py_${PYTHON_VERSION}) endforeach() ROCm-AMDMIGraphX-46524e8/src/py/backend/000077500000000000000000000000001510465702400173465ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/py/backend/__init__.py000066400000000000000000000024641510465702400214650ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### ROCm-AMDMIGraphX-46524e8/src/py/backend/backend.py000066400000000000000000000137551510465702400213220ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### # ------------------------------------------------------------------------- # Copyright (c) Advanced Micro Devices. All rights reserved. # Licensed under the MIT License. # -------------------------------------------------------------------------- """ Implements ONNX's backend API. """ import sys if sys.version_info < (3, 0): sys.exit() from onnx import ModelProto from onnx.checker import check_model from onnx.backend.base import Backend import migraphx from onnx_migraphx.backend_rep import MIGraphXBackendRep def get_device(): return ("CPU", "GPU") class MIGraphXBackend(Backend): _device = "GPU" _input_names = [] _prog_string = "" @classmethod def set_device(cls, device): cls._device = device """ Implements `ONNX's backend API `_ with *ONNX Runtime*. The backend is mostly used when you need to switch between multiple runtimes with the same API. `Importing models from ONNX to Caffe2 `_ shows how to use *caffe2* as a backend for a converted model. Note: This is not the official Python API. """ # noqa: E501 @classmethod def get_program(cls): return cls._prog_string @classmethod def is_compatible(cls, model, device=None, **kwargs): """ Return whether the model is compatible with the backend. :param model: unused :param device: None to use the default device or a string (ex: `'CPU'`) :return: boolean """ device = cls._device return cls.supports_device(device) @classmethod def supports_device(cls, device): """ Check whether the backend is compiled with particular device support. In particular it's used in the testing suite. """ return device in get_device() @classmethod def prepare(cls, model, device=None, **kwargs): """ Load the model and creates a :class:`migraphx.program` ready to be used as a backend. :param model: ModelProto (returned by `onnx.load`), string for a filename or bytes for a serialized model :param device: requested device for the computation, None means the default one which depends on the compilation settings :param kwargs: see :class:`onnxruntime.SessionOptions` :return: :class:`migraphx.program` """ if isinstance(model, MIGraphXBackendRep): return model elif isinstance(model, migraphx.program): return MIGraphXBackendRep(model, cls._input_names) elif isinstance(model, (str, bytes)): if device is not None and not cls.supports_device(device): raise RuntimeError( "Incompatible device expected '{0}', got '{1}'".format( device, get_device())) inf = migraphx.parse_onnx_buffer(model) cls._prog_string = str("\nPython =\n{}\nProgram =\n{}".format( inf.to_py(), inf)) device = cls._device cls._input_names = inf.get_parameter_names() inf.compile(migraphx.get_target(device.lower())) cls._prog_string = cls._prog_string + str( "\nCompiled program =\n{}".format(inf)) return cls.prepare(inf, device, **kwargs) else: # type: ModelProto check_model(model) bin = model.SerializeToString() return cls.prepare(bin, device, **kwargs) @classmethod def run_model(cls, model, inputs, device=None, **kwargs): """ Compute the prediction. :param model: :class:`migraphx.program` returned by function *prepare* :param inputs: inputs :param device: requested device for the computation, None means the default one which depends on the compilation settings :param kwargs: see :class:`migraphx.program` :return: predictions """ rep = cls.prepare(model, device, **kwargs) return rep.run(inputs, **kwargs) @classmethod def run_node(cls, node, inputs, device=None, outputs_info=None, **kwargs): ''' This method is not implemented as it is much more efficient to run a whole model than every node independently. ''' raise NotImplementedError( "It is much more efficient to run a whole model than every node independently." ) is_compatible = MIGraphXBackend.is_compatible prepare = MIGraphXBackend.prepare run = MIGraphXBackend.run_model supports_device = MIGraphXBackend.supports_device ROCm-AMDMIGraphX-46524e8/src/py/backend/backend_rep.py000066400000000000000000000054371510465702400221660ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### """ Implements ONNX's backend API. """ import sys if sys.version_info < (3, 0): sys.exit() import migraphx from onnx.backend.base import BackendRep import numpy as np from typing import Any, Tuple class MIGraphXBackendRep(BackendRep): """ Computes the prediction for a pipeline converted into an :class:`onnxruntime.InferenceSession` node. """ def __init__(self, prog, input_names): """ :param session: :class:`migraphx.program` """ self._program = prog self._input_names = input_names def run(self, inputs, **kwargs): # type: (Any, **Any) -> Tuple[Any, ...] """ Computes the prediction. See :meth:`migraphx.program.run`. """ if isinstance(inputs, list): inps = {} for i, name in enumerate(self._input_names): inps[name] = migraphx.argument(inputs[i]) mgx_outputs = self._program.run(inps) outs = [] for out in mgx_outputs: outs.append(np.array(out)) return outs else: inp = self._program.get_parameter_shapes().keys() if len(inp) != 1: raise RuntimeError("Model expect {0} inputs".format(len(inp))) inps = {inp[0]: migraphx.argument(inputs)} mgx_outputs = self._program.run(inps) outs = [] for out in mgx_outputs: outs.append(np.array(out)) return self._program.run(inps) ROCm-AMDMIGraphX-46524e8/src/py/include/000077500000000000000000000000001510465702400174025ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/py/include/migraphx/000077500000000000000000000000001510465702400212215ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/py/include/migraphx/py.hpp000066400000000000000000000030641510465702400223650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHX_PY_HPP #define MIGRAPHX_GUARD_MIGRAPHX_PY_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_PY_EXPORT program load_py(const std::string& filename); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_PY_HPP ROCm-AMDMIGraphX-46524e8/src/py/migraphx_py.cpp000066400000000000000000000715411510465702400210220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GPU #include #endif using half = migraphx::half; namespace py = pybind11; #ifdef __clang__ #define MIGRAPHX_PUSH_UNUSED_WARNING \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wused-but-marked-unused\"") #define MIGRAPHX_POP_WARNING _Pragma("clang diagnostic pop") #else #define MIGRAPHX_PUSH_UNUSED_WARNING #define MIGRAPHX_POP_WARNING #endif #define MIGRAPHX_PYBIND11_MODULE(...) \ MIGRAPHX_PUSH_UNUSED_WARNING \ PYBIND11_MODULE(__VA_ARGS__) \ MIGRAPHX_POP_WARNING #define MIGRAPHX_PYTHON_GENERATE_SHAPE_ENUM(x, t) .value(#x, migraphx::shape::type_t::x) namespace migraphx { migraphx::value to_value(py::kwargs kwargs); migraphx::value to_value(py::list lst); template void visit_py(T x, F f) { if(py::isinstance(x)) { f(to_value(x.template cast())); } else if(py::isinstance(x)) { f(to_value(x.template cast())); } else if(py::isinstance(x)) { f(x.template cast()); } else if(py::isinstance(x) or py::hasattr(x, "__index__")) { f(x.template cast()); } else if(py::isinstance(x)) { f(x.template cast()); } else if(py::isinstance(x)) { f(x.template cast()); } else if(py::isinstance(x)) { f(migraphx::to_value(x.template cast())); } else { MIGRAPHX_THROW("VISIT_PY: Unsupported data type!"); } } migraphx::value to_value(py::list lst) { migraphx::value v = migraphx::value::array{}; for(auto val : lst) { visit_py(val, [&](auto py_val) { v.push_back(py_val); }); } return v; } migraphx::value to_value(py::kwargs kwargs) { migraphx::value v = migraphx::value::object{}; for(auto arg : kwargs) { auto&& key = py::str(arg.first); auto&& val = arg.second; visit_py(val, [&](auto py_val) { v[key] = py_val; }); } return v; } } // namespace migraphx namespace pybind11 { namespace detail { template <> struct type_caster : list_caster { }; template <> struct npy_format_descriptor { static std::string format() { // following: https://docs.python.org/3/library/struct.html#format-characters return "e"; } static constexpr auto name() { return _("half"); } }; template <> struct npy_format_descriptor { static std::string format() { // TODO: no standard format in numpy for fp8 return "z"; } static constexpr auto name() { return _("fp8e4m3fnuz"); } }; template <> struct npy_format_descriptor { static std::string format() { // TODO: no standard format in numpy for fp8 return "z"; } static constexpr auto name() { return _("fp8e5m2fnuz"); } }; template <> struct npy_format_descriptor { static std::string format() { // TODO: no standard format in numpy for fp8 return "z"; } static constexpr auto name() { return _("fp8e4m3fn"); } }; template <> struct npy_format_descriptor { static std::string format() { // TODO: no standard format in numpy for fp8 return "z"; } static constexpr auto name() { return _("fp8e5m2"); } }; template <> struct npy_format_descriptor { static std::string format() { // TODO: no standard format in numpy for bf16 return "z"; } static constexpr auto name() { return _("bf16"); } }; } // namespace detail } // namespace pybind11 template void visit_type(const migraphx::shape& s, F f) { s.visit_type(f); } template void visit(const migraphx::raw_data& x, F f) { x.visit(f); } template void visit_types(F f) { migraphx::shape::visit_types(f); } template py::buffer_info to_buffer_info(T& x) { migraphx::shape s = x.get_shape(); assert(s.type() != migraphx::shape::tuple_type); if(s.dynamic()) MIGRAPHX_THROW("MIGRAPHX PYTHON: dynamic shape argument passed to to_buffer_info"); auto strides = s.strides(); std::transform( strides.begin(), strides.end(), strides.begin(), [&](auto i) { return i * s.type_size(); }); py::buffer_info b; visit_type(s, [&](auto as) { // migraphx use int8_t data to store bool type, we need to // explicitly specify the data type as bool for python if(s.type() == migraphx::shape::bool_type) { b = py::buffer_info(x.data(), as.size(), py::format_descriptor::format(), s.ndim(), s.lens(), strides); } else { b = py::buffer_info(x.data(), as.size(), py::format_descriptor::format(), s.ndim(), s.lens(), strides); } }); return b; } py::object to_py_object(const migraphx::value& val) { py::object result; val.visit_value([&](const auto& x) { if constexpr(std::is_same, std::vector>{}) { if(val.is_object()) { py::dict py_dict; for(const auto& item : x) { py_dict[py::str(item.get_key())] = to_py_object(item.without_key()); } result = py_dict; } else { py::list py_list; for(const auto& item : x) { py_list.append(to_py_object(item)); } result = py_list; } } else { result = py::cast(x); } }); return result; } migraphx::shape to_shape(const py::buffer_info& info) { migraphx::shape::type_t t; std::size_t n = 0; // Unsupported pybuffer types lead to undefined behaviour when comparing with migraphx type enum if(info.format == "z") { MIGRAPHX_THROW( "MIGRAPHX PYTHON: Unsupported data type. For fp8 and bf16 literals try using " "migraphx.generate_argument with migraphx.add_literal"); } visit_types([&](auto as) { if(info.format == py::format_descriptor::format() or (info.format == "l" and py::format_descriptor::format() == "q") or (info.format == "L" and py::format_descriptor::format() == "Q")) { t = as.type_enum(); n = sizeof(as()); } else if(info.format == "?" and py::format_descriptor::format() == "b") { t = migraphx::shape::bool_type; n = sizeof(bool); } }); if(n == 0) { MIGRAPHX_THROW("MIGRAPHX PYTHON: Unsupported data type " + info.format); } auto strides = info.strides; std::transform(strides.begin(), strides.end(), strides.begin(), [&](auto i) -> std::size_t { return n > 0 ? i / n : 0; }); // scalar support if(info.shape.empty()) { return migraphx::shape{t}; } else { return migraphx::shape{t, info.shape, strides}; } } MIGRAPHX_PYBIND11_MODULE(migraphx, m) { py::class_ shape_cls(m, "shape"); shape_cls .def(py::init([](py::kwargs kwargs) { auto v = migraphx::to_value(kwargs); auto t = migraphx::shape::parse_type(v.get("type", "float")); if(v.contains("dyn_dims")) { auto dyn_dims = migraphx::from_value>( v.at("dyn_dims")); return migraphx::shape(t, dyn_dims); } auto lens = v.get("lens", {1}); if(v.contains("strides")) return migraphx::shape(t, lens, v.at("strides").to_vector()); else if(v.contains("permutation")) return migraphx::shape::from_permutation( t, lens, v.at("permutation").to_vector()); else return migraphx::shape(t, lens); })) .def("type", &migraphx::shape::type) .def("lens", &migraphx::shape::lens) .def("strides", &migraphx::shape::strides) .def("ndim", &migraphx::shape::ndim) .def("elements", &migraphx::shape::elements) .def("bytes", &migraphx::shape::bytes) .def("type_string", &migraphx::shape::type_string) .def("type_size", &migraphx::shape::type_size) .def("dyn_dims", &migraphx::shape::dyn_dims) .def("packed", &migraphx::shape::packed) .def("transposed", &migraphx::shape::transposed) .def("broadcasted", &migraphx::shape::broadcasted) .def("standard", &migraphx::shape::standard) .def("scalar", &migraphx::shape::scalar) .def("dynamic", &migraphx::shape::dynamic) .def("__eq__", std::equal_to{}) .def("__ne__", std::not_equal_to{}) .def("__repr__", [](const migraphx::shape& s) { return migraphx::to_string(s); }); py::enum_(shape_cls, "type_t") MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_PYTHON_GENERATE_SHAPE_ENUM); py::class_(shape_cls, "dynamic_dimension") .def(py::init<>()) .def(py::init()) .def(py::init>()) .def_readwrite("min", &migraphx::shape::dynamic_dimension::min) .def_readwrite("max", &migraphx::shape::dynamic_dimension::max) .def_readwrite("optimals", &migraphx::shape::dynamic_dimension::optimals) .def("is_fixed", &migraphx::shape::dynamic_dimension::is_fixed); py::class_(m, "argument", py::buffer_protocol()) .def_buffer([](migraphx::argument& x) -> py::buffer_info { return to_buffer_info(x); }) .def(py::init([](py::buffer b) { py::buffer_info info = b.request(); return migraphx::argument(to_shape(info), info.ptr); })) .def("get_shape", &migraphx::argument::get_shape) .def("data_ptr", [](migraphx::argument& x) { return reinterpret_cast(x.data()); }) .def("tolist", [](migraphx::argument& x) { py::list l{x.get_shape().elements()}; visit(x, [&](auto data) { l = py::cast(data.to_vector()); }); return l; }) .def_static( "save", [](const migraphx::argument& a, const std::string& filename) { migraphx::save_argument(a, filename); }, "Save argument to a file encoded in msgpack format", py::arg("arg"), py::arg("filename")) .def_static( "load", [](const std::string& filename) { return migraphx::load_argument(filename); }, "Load argument from a file encoded in msgpack format", py::arg("filename")) .def("__eq__", std::equal_to{}) .def("__ne__", std::not_equal_to{}) .def("__repr__", [](const migraphx::argument& x) { return migraphx::to_string(x); }); py::class_(m, "target"); py::class_(m, "instruction_ref") .def("shape", [](migraphx::instruction_ref i) { return i->get_shape(); }) .def("op", [](migraphx::instruction_ref i) { return i->get_operator(); }) .def("inputs", [](migraphx::instruction_ref i) { return i->inputs(); }) .def("outputs", [](migraphx::instruction_ref i) { return i->outputs(); }) .def("name", [](migraphx::instruction_ref i) { return i->name(); }) .def("get_literal", [](migraphx::instruction_ref i) { return i->get_literal().get_argument(); }) .def(py::hash(py::self)) .def(py::self == py::self) .def(py::self != py::self); py::class_>(m, "module") .def("print", [](const migraphx::module& mm) { std::cout << mm << std::endl; }) .def( "add_instruction", [](migraphx::module& mm, const migraphx::operation& op, std::vector& args, std::vector& mod_args) { return mm.add_instruction(op, args, mod_args); }, py::arg("op"), py::arg("args"), py::arg("mod_args") = std::vector{}) .def( "add_literal", [](migraphx::module& mm, migraphx::argument a) { return mm.add_literal(a.get_shape(), a.data()); }, py::arg("data")) .def( "add_literal", [](migraphx::module& mm, py::buffer data) { py::buffer_info info = data.request(); auto literal_shape = to_shape(info); return mm.add_literal(literal_shape, reinterpret_cast(info.ptr)); }, py::arg("data")) .def( "add_parameter", [](migraphx::module& mm, const std::string& name, const migraphx::shape shape) { return mm.add_parameter(name, shape); }, py::arg("name"), py::arg("shape")) .def( "add_return", [](migraphx::module& mm, std::vector& args) { return mm.add_return(args); }, py::arg("args")) .def( "replace_return", [](migraphx::module& mm, std::vector& args) { return mm.replace_return(args); }, py::arg("args")) .def("__repr__", [](const migraphx::module& mm) { return migraphx::to_string(mm); }) .def( "__iter__", [](const migraphx::module& mm) { auto r = migraphx::iterator_for(mm); return py::make_iterator(r.begin(), r.end()); }, py::keep_alive<0, 1>()); py::class_(m, "program") .def(py::init([]() { return migraphx::program(); })) .def("get_parameter_names", &migraphx::program::get_parameter_names) .def("get_parameter_shapes", &migraphx::program::get_parameter_shapes) .def("get_output_shapes", &migraphx::program::get_output_shapes) .def("is_compiled", &migraphx::program::is_compiled) .def( "compile", [](migraphx::program& p, const migraphx::target& t, bool offload_copy, bool fast_math, bool exhaustive_tune) { migraphx::compile_options options; options.offload_copy = offload_copy; options.fast_math = fast_math; options.exhaustive_tune = exhaustive_tune; p.compile(t, options); }, py::arg("t"), py::arg("offload_copy") = true, py::arg("fast_math") = true, py::arg("exhaustive_tune") = false) .def("get_main_module", [](const migraphx::program& p) { return p.get_main_module(); }) .def( "create_module", [](migraphx::program& p, const std::string& name) { return p.create_module(name); }, py::arg("name")) .def("run", [](migraphx::program& p, py::dict params) { migraphx::parameter_map pm; for(auto x : params) { std::string key = x.first.cast(); py::buffer b = x.second.cast(); py::buffer_info info = b.request(); pm[key] = migraphx::argument(to_shape(info), info.ptr); } return p.eval(pm); }) .def("run_async", [](migraphx::program& p, py::dict params, std::uintptr_t stream, std::string stream_name) { migraphx::parameter_map pm; for(auto x : params) { std::string key = x.first.cast(); py::buffer b = x.second.cast(); py::buffer_info info = b.request(); pm[key] = migraphx::argument(to_shape(info), info.ptr); } migraphx::execution_environment exec_env{ migraphx::any_ptr(reinterpret_cast(stream), stream_name), true}; return p.eval(pm, exec_env); }) .def("to_py", [](const migraphx::program& p) { std::stringstream ss; p.print_py(ss); return ss.str(); }) .def("sort", &migraphx::program::sort) .def("print", [](const migraphx::program& p) { std::cout << p << std::endl; }) .def("__eq__", std::equal_to{}) .def("__ne__", std::not_equal_to{}) .def("__repr__", [](const migraphx::program& p) { return migraphx::to_string(p); }); py::class_ op(m, "op"); op.def(py::init([](const std::string& name, py::kwargs kwargs) { migraphx::value v = migraphx::value::object{}; if(kwargs) { v = migraphx::to_value(kwargs); } return migraphx::make_op(name, v); })) .def("name", &migraphx::operation::name) .def("values", [](const migraphx::operation& operation) -> py::object { return to_py_object(operation.to_value()); }); py::enum_(op, "pooling_mode") .value("average", migraphx::op::pooling_mode::average) .value("max", migraphx::op::pooling_mode::max) .value("lpnorm", migraphx::op::pooling_mode::lpnorm); py::enum_(op, "rnn_direction") .value("forward", migraphx::op::rnn_direction::forward) .value("reverse", migraphx::op::rnn_direction::reverse) .value("bidirectional", migraphx::op::rnn_direction::bidirectional); m.def( "argument_from_pointer", [](const migraphx::shape shape, const int64_t address) { return migraphx::argument(shape, reinterpret_cast(address)); }, py::arg("shape"), py::arg("address")); m.def( "parse_tf", [](const std::string& filename, bool is_nhwc, unsigned int batch_size, std::unordered_map> map_input_dims, std::vector output_names) { return migraphx::parse_tf( filename, migraphx::tf_options{is_nhwc, batch_size, map_input_dims, output_names}); }, "Parse tf protobuf (default format is nhwc)", py::arg("filename"), py::arg("is_nhwc") = true, py::arg("batch_size") = 1, py::arg("map_input_dims") = std::unordered_map>(), py::arg("output_names") = std::vector()); m.def( "parse_onnx", [](const std::string& filename, unsigned int default_dim_value, migraphx::shape::dynamic_dimension default_dyn_dim_value, std::unordered_map> map_input_dims, std::unordered_map> map_dyn_input_dims, bool skip_unknown_operators, bool print_program_on_error, int64_t max_loop_iterations, int64_t limit_max_iterations) { migraphx::onnx_options options; options.default_dim_value = default_dim_value; options.default_dyn_dim_value = default_dyn_dim_value; options.map_input_dims = map_input_dims; options.map_dyn_input_dims = map_dyn_input_dims; options.skip_unknown_operators = skip_unknown_operators; options.print_program_on_error = print_program_on_error; options.max_loop_iterations = max_loop_iterations; options.limit_max_iterations = limit_max_iterations; return migraphx::parse_onnx(filename, options); }, "Parse onnx file", py::arg("filename"), py::arg("default_dim_value") = 0, py::arg("default_dyn_dim_value") = migraphx::shape::dynamic_dimension{1, 1}, py::arg("map_input_dims") = std::unordered_map>(), py::arg("map_dyn_input_dims") = std::unordered_map>(), py::arg("skip_unknown_operators") = false, py::arg("print_program_on_error") = false, py::arg("max_loop_iterations") = 10, py::arg("limit_max_iterations") = std::numeric_limits::max()); m.def( "parse_onnx_buffer", [](const std::string& onnx_buffer, unsigned int default_dim_value, migraphx::shape::dynamic_dimension default_dyn_dim_value, std::unordered_map> map_input_dims, std::unordered_map> map_dyn_input_dims, bool skip_unknown_operators, bool print_program_on_error, const std::string& external_data_path) { migraphx::onnx_options options; options.default_dim_value = default_dim_value; options.default_dyn_dim_value = default_dyn_dim_value; options.map_input_dims = map_input_dims; options.map_dyn_input_dims = map_dyn_input_dims; options.skip_unknown_operators = skip_unknown_operators; options.print_program_on_error = print_program_on_error; options.external_data_path = external_data_path; return migraphx::parse_onnx_buffer(onnx_buffer, options); }, "Parse onnx file", py::arg("filename"), py::arg("default_dim_value") = 0, py::arg("default_dyn_dim_value") = migraphx::shape::dynamic_dimension{1, 1}, py::arg("map_input_dims") = std::unordered_map>(), py::arg("map_dyn_input_dims") = std::unordered_map>(), py::arg("skip_unknown_operators") = false, py::arg("print_program_on_error") = false, py::arg("external_data_path") = ""); m.def( "load", [](const std::string& name, const std::string& format) { migraphx::file_options options; options.format = format; return migraphx::load(name, options); }, "Load MIGraphX program", py::arg("filename"), py::arg("format") = "msgpack"); m.def( "save", [](const migraphx::program& p, const std::string& name, const std::string& format) { migraphx::file_options options; options.format = format; return migraphx::save(p, name, options); }, "Save MIGraphX program", py::arg("p"), py::arg("filename"), py::arg("format") = "msgpack"); m.def( "save_buffer", [](const migraphx::program& p) { auto buffer = migraphx::save_buffer(p); return py::bytes(buffer.data(), buffer.size()); }, "Serialize MIGraphX program", py::arg("p")); m.def( "load_buffer", [](const py::bytes& b) { std::string_view byte_str{b}; std::vector char_arr(byte_str.begin(), byte_str.end()); return migraphx::load_buffer(char_arr); }, "Deserialize MIGraphX program", py::arg("b")); m.def("get_target", &migraphx::make_target); m.def("create_argument", [](const migraphx::shape& s, const std::vector& values) { if(values.size() != s.elements()) MIGRAPHX_THROW("Values and shape elements do not match"); migraphx::argument a{s}; a.fill(values.begin(), values.end()); return a; }); m.def( "generate_argument", [](const migraphx::shape& s, unsigned long seed) { return migraphx::generate_argument(s, seed); }, py::arg("s"), py::arg("seed") = 0); m.def("fill_argument", &migraphx::fill_argument, py::arg("s"), py::arg("value")); m.def("quantize_fp16", &migraphx::quantize_fp16, py::arg("prog"), py::arg("ins_names") = std::vector{"all"}); m.def("quantize_int8", &migraphx::quantize_int8, py::arg("prog"), py::arg("t"), py::arg("calibration") = std::vector{}, py::arg("ins_names") = std::unordered_set{"dot", "convolution"}); m.def("quantize_fp8", &migraphx::quantize_fp8, py::arg("prog"), py::arg("t"), py::arg("calibration") = std::vector{}); m.def( "autocast_fp8", [](migraphx::program& prog) { migraphx::run_passes(*prog.get_main_module(), {migraphx::autocast_fp8_pass{}}); }, "Auto-convert FP8 parameters and return values to Float for MIGraphX Program", py::arg("prog")); m.def("quantize_bf16", &migraphx::quantize_bf16, py::arg("prog"), py::arg("ins_names") = std::vector{"all"}); #ifdef HAVE_GPU m.def("allocate_gpu", &migraphx::gpu::allocate_gpu, py::arg("s"), py::arg("host") = false); m.def("to_gpu", &migraphx::gpu::to_gpu, py::arg("arg"), py::arg("host") = false); m.def("from_gpu", &migraphx::gpu::from_gpu); m.def("gpu_sync", [] { migraphx::gpu::gpu_sync(); }); #endif #ifdef VERSION_INFO m.attr("__version__") = VERSION_INFO; #else auto version_string = std::to_string(MIGRAPHX_VERSION_MAJOR) + "." + std::to_string(MIGRAPHX_VERSION_MINOR) + "." + std::to_string(MIGRAPHX_VERSION_PATCH) + ".dev"; std::string tweak(MIGRAPHX_VERSION_TWEAK); if(not tweak.empty()) version_string += "+" + tweak; m.attr("__version__") = version_string; #endif } ROCm-AMDMIGraphX-46524e8/src/py/py.cpp000066400000000000000000000055731510465702400171250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #ifdef _WIN32 #define MIGRAPHX_PY_VERSION_EXPORT __declspec(dllexport) #else #define MIGRAPHX_PY_VERSION_EXPORT #endif namespace py = pybind11; namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreturn-type-c-linkage" #endif // extern "C" is used to disable name mangling, but the function will still be called from C++ MIGRAPHX_PY_VERSION_EXPORT extern "C" program migraphx_load_py(const std::string& filename); #ifdef __clang__ #pragma clang diagnostic pop #endif const std::string& python_path() { static const auto path = dynamic_loader::path(&migraphx_load_py).parent_path().string(); return path; } static py::dict run_file(const std::string& file) { py::object scope = py::module_::import("__main__").attr("__dict__"); std::string buffer; buffer.append("import sys\n"); buffer.append("sys.path.insert(0, '" + python_path() + "')\n"); buffer.append("import migraphx\n"); buffer.append(read_string(file)); py::exec(buffer, scope); return scope.cast(); } extern "C" program migraphx_load_py(const std::string& filename) { py::scoped_interpreter guard{}; py::dict vars = run_file(filename); auto it = std::find_if(vars.begin(), vars.end(), [](const auto& p) { return py::isinstance(p.second); }); if(it == vars.end()) MIGRAPHX_THROW("No program variable found"); return it->second.cast(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/py/py_loader.cpp000066400000000000000000000047611510465702400204510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static std::vector find_available_python_versions() { std::vector result; auto path = dynamic_loader::path(&load_py).parent_path(); for(const auto& entry : fs::directory_iterator{path}) { auto p = entry.path(); if(not fs::is_regular_file(p)) continue; if(not contains(p.stem().string(), "migraphx_py_")) continue; result.push_back(p); } std::sort(result.begin(), result.end(), std::greater<>{}); return result; } static dynamic_loader load_py_lib() { auto libs = find_available_python_versions(); for(const auto& lib : libs) { auto result = dynamic_loader::try_load(lib); if(result.has_value()) return *result; } MIGRAPHX_THROW("Cant find a viable version of python"); } static dynamic_loader py_lib() { static dynamic_loader lib = load_py_lib(); return lib; } MIGRAPHX_PY_EXPORT program load_py(const std::string& filename) { static auto f = py_lib().get_function("migraphx_load_py"); return f(filename); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/quantization.cpp000066400000000000000000000205221510465702400205620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_8BITS_QUANTIZATION_PARAMS) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_QUANTIZATION) static tracer quant_tracer() { if(enabled(MIGRAPHX_TRACE_QUANTIZATION{})) return tracer{std::cout}; return tracer{}; }; // This function is to convert any instructions specified in the input // from double or float to float16 by inserting a convert operator. // For the conversion, there could be cases of overflowing or underflowing, but it // is uncommon. Run optimize_module() before converting to fp16 to const eval and fold in FP32 to // avoid loss of precision. void quantize_fp16(program& prog, const std::vector& ins_names) { run_passes(prog, {normalize_ops{}, optimize_module{{"quantizelinear", "dequantizelinear"}}, truncate_float_pass{ins_names, shape::half_type}, optimize_module{{"quantizelinear", "dequantizelinear"}}}, quant_tracer()); } void quantize_bf16(program& prog, const std::vector& ins_names) { run_passes(prog, {normalize_ops{}, optimize_module{{"quantizelinear", "dequantizelinear"}}, truncate_float_pass{ins_names, shape::bf16_type}, optimize_module{{"quantizelinear", "dequantizelinear"}}}, quant_tracer()); } static void quantize_8bits(program& prog, const target& t, shape::type_t precision, const std::vector& calibration, const std::unordered_set& ins_names) { // Run optimize_module() before converting to int8/fp8 to const eval and fold in FP32 to // avoid loss of precision. run_passes(prog, {rewrite_rnn{}, normalize_ops{}, optimize_module{}}, quant_tracer()); std::shared_ptr>> quant_8bit_params = std::make_shared>>(); std::shared_ptr> max_abs_vals = std::make_shared>(); std::map type_ranges = {{shape::type_t::int8_type, 127.0}, {shape::type_t::fp8e4m3fnuz_type, 240.0}, {shape::type_t::fp8e4m3fn_type, 448.0}}; float quantized_range = type_ranges.at(precision); auto calc_quant_params = [&](std::size_t ins_index, std::vector args) { std::pair param_pair{64.0f, 0.0f}; // scale and shift is need for only int8 type, and we do not // consider shift, so set shift to 0 std::vector vec_val; argument arg = t.copy_from(args.front()); arg.visit([&](auto output) { vec_val.assign(output.begin(), output.end()); }); auto max_val = *std::max_element(vec_val.begin(), vec_val.end()); auto min_val = *std::min_element(vec_val.begin(), vec_val.end()); auto max_abs = std::max(std::fabs(max_val), std::fabs(min_val)); max_abs_vals->at(ins_index) = std::max(max_abs_vals->at(ins_index), max_abs); // if all values are 0, no need to do scaling if(float_equal(max_abs_vals->at(ins_index), 0.0f)) { param_pair.first = 1.0f; } else { param_pair.first = quantized_range / max_abs_vals->at(ins_index); } quant_8bit_params->at(ins_index) = param_pair; }; // pass to add capture argument op std::size_t param_num = 0; run_passes( prog, {capture_arguments_pass{ins_names, calc_quant_params, ¶m_num}}, quant_tracer()); quant_8bit_params->resize(param_num, std::pair(64.0f, 0.0f)); max_abs_vals->resize(param_num, 0.0f); // use the calibration data to compute the quantization scale auto capture_prog = prog; capture_prog.compile(t); // use all calibration data to run the program to calculate the // quantization scale and shift for(auto&& arg : calibration) { parameter_map m; for(auto&& x : capture_prog.get_parameter_shapes()) { if(arg.count(x.first) > 0) { assert(x.second == arg.at(x.first).get_shape()); m[x.first] = t.copy_to(arg.at(x.first)); } else { m[x.first] = t.allocate(x.second); } } capture_prog.eval(m); } // print the quantization parameters in only the main module if(enabled(MIGRAPHX_8BITS_QUANTIZATION_PARAMS{})) { for(std::size_t i = 0; i < quant_8bit_params->size(); ++i) { auto param = quant_8bit_params->at(i); std::cout << "ins_index = " << i << ", scale = " << param.first << ", shift = " << param.second << std::endl; } std::cout << std::endl; } run_passes(prog, {quantize_8bits_pass{precision, *quant_8bit_params}, dead_code_elimination{}}, quant_tracer()); } void quantize_int8(program& prog, const target& t, const std::vector& calibration, const std::unordered_set& ins_names) { std::unordered_set op_names = {"convolution", "dot"}; if(op_names != ins_names) { MIGRAPHX_THROW("QUANTIZE_INT8: only support DOT and CONVOLUTION operation"); } quantize_8bits(prog, t, shape::int8_type, calibration, ins_names); } void quantize_int4_weights(program& prog) { run_passes(prog, {normalize_ops{}, optimize_module{}, quantize_int4_pass{}}, quant_tracer()); } void quantize_fp8(program& prog, const target& t, const std::vector& calibration) { std::unordered_set supported_ins_names; auto* mm = prog.get_main_module(); for(auto ins : iterator_for(*mm)) { if(ins->name() == "convert") { continue; } if(not starts_with(ins->name(), "@")) { supported_ins_names.insert(ins->name()); } } quantize_8bits(prog, t, shape::fp8e4m3fn_type, calibration, supported_ins_names); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/quantize_8bits.cpp000066400000000000000000000103741510465702400210110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static std::vector& get_quantizable_type() { static std::vector quantable_types = { shape::float_type, shape::double_type, shape::half_type}; return quantable_types; } void quantize_8bits_pass::apply(module& m) const // NOLINT { const auto& quantizable_types = get_quantizable_type(); for(auto ins : iterator_for(m)) { if(ins->name() != "capture") continue; auto op_val = ins->get_operator().to_value(); assert(op_val.contains("ins_index")); auto param_index = op_val.at("ins_index").to(); auto param = quant_params[param_index]; auto input = ins->inputs().front(); auto s = input->get_shape(); if(contains(quantizable_types, s.type()) and s.type() != precision) { auto zero_point = m.add_literal(migraphx::literal{migraphx::shape{precision}, {param.second}}); auto scale = m.add_literal(literal({s.type()}, {1.0f / param.first})); const auto& lens = s.lens(); scale = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", lens}}), scale); zero_point = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", lens}}), zero_point); auto q_in = m.insert_instruction(ins, make_op("quantizelinear"), input, scale, zero_point); auto dq_in = m.insert_instruction(ins, make_op("dequantizelinear"), q_in, scale, zero_point); m.replace_instruction(ins, dq_in); } } } void capture_arguments_pass::apply(module& m) const // NOLINT { assert(param_index != nullptr); const auto& quantizable_types = get_quantizable_type(); for(auto ins : iterator_for(m)) { if((not contains(ins_names, ins->name())) or (ins->name() == "convert")) { continue; } auto inputs = ins->inputs(); std::vector new_args; for(auto input : inputs) { if(contains(quantizable_types, input->get_shape().type())) { auto new_in = m.insert_instruction(ins, op::capture{(*param_index)++, f}, input); new_args.push_back(new_in); } else { new_args.push_back(input); } } m.replace_instruction(ins, ins->get_operator(), new_args); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/quantize_int4.cpp000066400000000000000000000107031510465702400206320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static void int4_quantize_module(module& m) { std::vector int4_instrs{"dot", "convolution"}; for(auto ins : iterator_for(m)) { if(not(contains(int4_instrs, ins->name()))) continue; if(ins->inputs().empty()) continue; auto s = ins->get_shape(); auto mod_inputs = ins->module_inputs(); // Convert each of the inputs that are fp32 or fp16 to int4 auto inputs = ins->inputs(); std::transform(inputs.begin(), inputs.end(), inputs.begin(), [&](auto inp) { auto sh = inp->get_shape(); if(sh.broadcasted()) return inp; auto input_type = sh.type(); if(input_type != shape::float_type and input_type != shape::half_type) return inp; auto lens = sh.lens(); if(lens[lens.size() - 1] % 2) return inp; // even sized dimensions to pack if(not inp->can_eval()) return inp; std::vector val; inp->eval().visit([&](auto in_data) { val.assign(in_data.begin(), in_data.end()); }); auto [min, max] = std::minmax_element(val.begin(), val.end()); *min = *min > 0 ? 0 : *min; *max = *max < 0 ? 0 : *max; float fscale4 = (*max - *min) / 15; // INT4 range is [0-15] int zp4 = float_equal(fscale4, 0) ? 0 : std::round(-*min / fscale4); auto scale = m.add_literal(literal({s.type()}, {fscale4})); scale = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", lens}}), scale); auto zp = m.add_literal(literal{{shape::uint8_type}, {zp4}}); zp = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", lens}}), zp); auto q_in = m.insert_instruction(ins, make_op("quantizelinear"), inp, scale, zp); auto pk = m.insert_instruction(ins, make_op("pack_int4", {{"axis", -1}}), q_in); auto unpk = m.insert_instruction(ins, make_op("unpack_int4", {{"axis", -1}}), pk); auto dq_scale = m.add_literal(literal({s.type()}, {fscale4})); dq_scale = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", lens}}), dq_scale); auto dq_zp = m.add_literal(literal{{shape::uint8_type}, {zp4}}); dq_zp = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", lens}}), dq_zp); return m.insert_instruction(ins, make_op("dequantizelinear"), unpk, dq_scale, dq_zp); }); auto converted_ins = m.insert_instruction(ins, ins->get_operator(), inputs, mod_inputs); m.replace_instruction(ins, converted_ins); } } void quantize_int4_pass::apply(module& m) const { int4_quantize_module(m); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/reduce_dims.cpp000066400000000000000000000113041510465702400203150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static bool reduce_dim(std::vector& shapes, std::size_t n) { std::vector new_lens; for(const auto& s : shapes) { assert(n < s.lens().size()); if((n + 1) >= s.lens().size()) return false; auto astride = s.strides()[n]; auto alen = s.lens()[n]; auto bstride = s.strides()[n + 1]; auto blen = s.lens()[n + 1]; if(astride == bstride * blen or alen == 1) new_lens.push_back(alen * blen); } if(new_lens.size() != shapes.size()) return false; std::size_t i = 0; for(auto& s : shapes) { auto lens = s.lens(); auto strides = s.strides(); lens.erase(lens.begin() + n); strides.erase(strides.begin() + n); lens[n] = new_lens[i]; s = shape{s.type(), lens, strides}; i++; } return true; } static void reduce_dim1(std::vector& shapes) { if(std::any_of(shapes.begin(), shapes.end(), [&](const auto& s) { return s.lens().size() < 2 or s.lens().back() != 1; })) return; for(auto& s : shapes) { auto lens = s.lens(); auto strides = s.strides(); lens.pop_back(); strides.pop_back(); s = shape{s.type(), lens, strides}; } } static std::size_t reduce_dim_all(std::vector& shapes, std::size_t n) { while(reduce_dim(shapes, n) and n < shapes.size()) { (void)n; } return n + 1; } static void reduce_dim_all(std::vector& shapes) { std::size_t n = 0; while(n < shapes.front().lens().size() - 1) n = reduce_dim_all(shapes, n); reduce_dim1(shapes); } static std::vector base_lens(const std::vector& shapes) { return std::accumulate( shapes.begin() + 1, shapes.end(), shapes.front().lens(), [](auto&& lens, auto&& s) { std::vector result; const auto* x = &s.lens(); const auto* y = &lens; if(x->size() > y->size()) std::swap(x, y); std::transform( x->begin(), x->end(), y->begin(), std::back_inserter(result), [&](auto a, auto b) { return std::max(a, b); }); return result; }); } static shape mask_shape(const shape& s, const std::vector& lens) { assert(s.lens().size() == lens.size()); std::vector rstrides(lens.size()); std::size_t stride = 1; for(std::size_t i = lens.size() - 1; i < lens.size(); i--) { if(lens[i] == s.lens()[i]) { rstrides[i] = stride; stride *= lens[i]; } else if(lens[i] != 1 and s.lens()[i] != 1) { return shape{}; } } return shape{s.type(), lens, rstrides}; } std::vector reduce_dims(const std::vector& shapes) { if(shapes.empty()) return {}; auto result = shapes; auto base = base_lens(shapes); for(auto&& s : shapes) { if(s.lens().size() != base.size()) return shapes; if(s.lens() == base) continue; auto mshape = mask_shape(s, base); if(mshape.lens().size() != base.size()) return shapes; result.push_back(mshape); } reduce_dim_all(result); result.erase(result.begin() + shapes.size(), result.end()); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/register_op.cpp000066400000000000000000000042771510465702400203670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static std::unordered_map& op_map() { static std::unordered_map m; // NOLINT return m; } void register_op_init() { (void)op_map(); } void register_op(const operation& op) { op_map()[op.name()] = op; } void unregister_op(const std::string& op_name) { assert(op_map().count(op_name)); op_map().erase(op_name); } operation load_op(const std::string& name) { return at(op_map(), name, "Operator not found: " + name); } bool has_op(const std::string& name) { return op_map().count(name) == 1; } std::vector get_operators() { std::vector result; std::transform(op_map().begin(), op_map().end(), std::back_inserter(result), [&](auto&& p) { return p.first; }); std::sort(result.begin(), result.end()); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/register_target.cpp000066400000000000000000000070021510465702400212240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static void store_target_lib(const dynamic_loader& lib) { static std::vector target_loader; target_loader.emplace_back(lib); } static std::unordered_map& target_map() { static std::unordered_map m; // NOLINT return m; } void register_target_init() { (void)target_map(); } void unregister_target(const std::string& name) { assert(target_map().count(name)); target_map().erase(name); } void register_target(const target& t) { target_map()[t.name()] = t; } target make_target(const std::string& name) { if(not contains(target_map(), name)) { std::string so_major_version = "." + std::to_string(MIGRAPHX_SO_MAJOR_VERSION); auto target_name = make_shared_object_filename("migraphx_" + name); // Try to load library with so_major_version appended to the name. // If library with so_major_version name is not found, // try loading the library without the so_major_version name appended. // For example, if "libmigraphx_ref.so.2010000" is not found, // try loading "libmigraphx_ref.so". try { // Default to loading shared libraries with // so_major_version appended. store_target_lib(dynamic_loader(target_name + so_major_version)); } catch(...) { // Load the library without the so_major_version in the name. store_target_lib(dynamic_loader(target_name)); } } const auto it = target_map().find(name); if(it == target_map().end()) { MIGRAPHX_THROW("Requested target '" + name + "' is not loaded or not supported"); } return it->second; } std::vector get_targets() { std::vector result; std::transform(target_map().begin(), target_map().end(), std::back_inserter(result), [&](auto&& p) { return p.first; }); std::sort(result.begin(), result.end()); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/replace_allocate.cpp000066400000000000000000000124621510465702400213170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { std::unordered_map create_output_names(const module& mod) { std::unordered_map mod_output_names; auto returns = mod.get_returns(); std::vector outputs_alias(returns.size()); std::transform(returns.begin(), returns.end(), outputs_alias.begin(), [](const auto& i) { return instruction::get_output_alias(i); }); std::size_t index = 0; if(outputs_alias.size() == 1 and mod.name().empty()) { mod_output_names[outputs_alias.front()] = "output"; } // Preserve main module output buffer naming across migraphx versions else if(mod.name() == "main") { for(auto ins : outputs_alias) { mod_output_names[ins] = mod.name() + ":#output_" + std::to_string(index++); } } else { for(auto ins : outputs_alias) { mod_output_names[ins] = param_name(index++, mod.name() + ":#output_"); } } return mod_output_names; } void insert_copy(module& m, const allocation_model& model) { auto returns = m.get_returns(); std::unordered_set returns_set(returns.begin(), returns.end()); for(auto ins : returns_set) { if(ins->get_shape().any_of_dynamic()) continue; auto alias = instruction::get_output_alias(ins); if(alias->get_shape() == ins->get_shape()) continue; auto insert_ins = std::next(ins); auto alloc = m.insert_instruction( insert_ins, make_op("allocate", migraphx::value{{"shape", to_value(ins->get_shape())}})); auto copy = m.insert_instruction(insert_ins, make_op(model.copy()), ins, alloc); m.replace_instruction(ins, copy); } } void insert_submod_allocations(instruction_ref ins, module& mod, const allocation_model& model) { std::vector inputs = ins->inputs(); std::vector mod_args = ins->module_inputs(); std::map name_shapes; for(const auto& smod : mod_args) { auto ps = smod->get_parameter_shapes(); name_shapes.insert(ps.begin(), ps.end()); } for(const auto& pn : name_shapes) { const auto& s = pn.second; instruction_ref output{}; output = mod.insert_instruction(ins, model.allocate(s)); inputs.push_back(output); } mod.replace_instruction(ins, ins->get_operator(), inputs, mod_args); } } // namespace void replace_allocate::apply(module_pass_manager& mpm) const { module& m = mpm.get_module(); bool is_root = *mpm.get_root_module() == m; bool root_offload_copy = is_root ? this->offload_copy : false; // Adjust allocations before replacing for(auto ins : iterator_for(m)) { // check if allocations from submodules need to be inserted // for now, only the "if" operator is affected if(ins->name() != "if") continue; insert_submod_allocations(ins, m, model); } if(not root_offload_copy and model.needs_out_params()) insert_copy(m, model); auto mod_output_names = create_output_names(m); for(auto ins : iterator_for(m)) { if(ins->name() != "allocate") continue; auto s = ins->get_shape(); if(not root_offload_copy and model.needs_out_params() and contains(mod_output_names, ins)) { auto out_param = m.add_parameter(mod_output_names[ins], s); m.replace_instruction(ins, out_param); } else { m.replace_instruction(ins, make_op(model.name(), migraphx::value{{"shape", to_value(s)}})); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/rewrite_dot.cpp000066400000000000000000000113561510465702400203700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { MIGRAPHX_PRED_MATCHER(conv_1x1, instruction_ref ins) { if(ins->name() != "convolution") return false; auto v = ins->get_operator().to_value(); if(v.at("group").to() != 1) return false; if(not all_of(v.at("stride"), [](const value& x) { return x.to() == 1; })) return false; if(not all_of(v.at("padding"), [](const value& x) { return x.to() == 0; })) return false; if(not all_of(v.at("dilation"), [](const value& x) { return x.to() == 1; })) return false; auto w = ins->inputs().at(1)->get_shape(); return std::all_of(w.lens().begin() + 2, w.lens().end(), [](std::size_t i) { return i == 1; }); } struct find_1x1_convolution { auto matcher() const { return conv_1x1(match::arg(1)(match::is_constant())); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto input = ins->inputs().front(); auto weights = ins->inputs().back(); std::vector sq_axes(ins->get_shape().ndim() - 2); std::iota(sq_axes.begin(), sq_axes.end(), 2); auto sq_weights = m.insert_instruction(ins, make_op("squeeze", {{"axes", sq_axes}}), weights); if(ins->get_shape().transposed()) { std::vector aperm(ins->get_shape().ndim()); std::iota(aperm.begin(), aperm.end(), 0); std::rotate(aperm.begin() + 1, aperm.begin() + 2, aperm.end()); auto a_mat = m.insert_instruction(ins, make_op("transpose", {{"permutation", aperm}}), input); auto transpose = m.insert_instruction( ins, make_op("transpose", {{"permutation", {1, 0}}}), sq_weights); auto b_lens = a_mat->get_shape().lens(); copy(transpose->get_shape().lens(), b_lens.end() - 2); auto b_mat = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", b_lens}}), transpose); auto dot = m.insert_instruction(ins, make_op("dot"), a_mat, b_mat); m.replace_instruction( ins, make_op("transpose", {{"permutation", invert_permutation(aperm)}}), dot); } else { auto batch_dim = ins->get_shape().lens().front(); auto m_dim = std::accumulate(input->get_shape().lens().begin() + 2, input->get_shape().lens().end(), 1, std::multiplies<>{}); auto n_dim = weights->get_shape().lens()[0]; auto k_dim = weights->get_shape().lens()[1]; auto a_mat = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", {batch_dim, n_dim, k_dim}}}), sq_weights); auto b_mat = m.insert_instruction( ins, make_op("reshape", {{"dims", {batch_dim, k_dim, m_dim}}}), input); auto dot = m.insert_instruction(ins, make_op("dot"), a_mat, b_mat); m.replace_instruction( ins, make_op("reshape", {{"dims", ins->get_shape().lens()}}), dot); } } }; } // namespace void rewrite_dot::apply(module& m) const { match::find_matches(m, find_1x1_convolution{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/rewrite_gelu.cpp000066400000000000000000000102331510465702400205270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * The replacement approximation is equivalent to: * GELU(x) ~= 0.5 * x * ( 1 + tanh( sqrt(2/M_PI) * (x + 0.044715 * x^3))) * You can rearrange to the form used in this by recognizing that * 1 + tanh(x) = (2) / (1 + exp(-2 * x)). * The fitting constant 0.044715 is from * A. Choudhury, ‘A simple approximation to the area under standard normal curve’, Mathematics and * Statistics, vol. 2, no. 3, pp. 147–149, 2014. */ static void replace_with_tanh_exp_gelu(module& m, const match::matcher_result& r) { auto ins = r.result; auto x = r.instructions["x"]; double const0 = -2. * sqrt(M_2_PI); double const1 = 0.044715 * const0; auto lit0 = m.add_literal(literal{shape{x->get_shape().type()}, {const0}}); auto lit1 = m.add_literal(literal{shape{x->get_shape().type()}, {const1}}); auto one = m.add_literal(literal{shape{x->get_shape().type()}, {1.0}}); auto xb = insert_common_op(m, ins, make_op("mul"), {x, lit1}); auto a = m.insert_instruction(ins, make_op("mul"), x, xb); auto b = insert_common_op(m, ins, make_op("add"), {a, lit0}); auto u = m.insert_instruction(ins, make_op("mul"), x, b); auto emu = m.insert_instruction(ins, make_op("exp"), u); auto c = insert_common_op(m, ins, make_op("add"), {one, emu}); auto y = m.insert_instruction(ins, make_op("div"), x, c); m.replace_instruction(ins, y); } /** * Finds erfGELU blocks using the Gaussian distribution and replaces them with the tanh_exp * approximation if the data type is fp16. TODO consider also for fp8 datatype. */ struct find_gelu_erf { auto matcher() const { return match::any_of(match::gelu_erf(), match::gelu_tanh()); } void apply(module& m, const match::matcher_result& r) const { auto x = r.instructions["x"]; auto input_type = x->get_shape().type(); std::set convert_types = {migraphx::shape::half_type}; if(not contains(convert_types, input_type)) return; replace_with_tanh_exp_gelu(m, r); } }; /** * Find tanhGELU blocks and replace them with a rearranged version that is less likely to overflow * and is more performant. */ struct find_tanh_fast_gelu { auto matcher() const { return match::gelu_tanh(); } void apply(module& m, const match::matcher_result& r) const { replace_with_tanh_exp_gelu(m, r); } }; void rewrite_gelu::apply(module& m) const { if(fast_math) { match::find_matches(m, find_gelu_erf{}); } else { match::find_matches(m, find_tanh_fast_gelu{}); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/rewrite_low_precision.cpp000066400000000000000000000050131510465702400224470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct find_pow2_div { auto pow2() const { auto pow2 = match::name("pow")(match::arg(0)(match::any().bind("x")), match::arg(1)(match::has_value(2.0f))); auto x_square = match::name("mul")(match::same_inputs(), match::arg(0)(match::any().bind("x"))); return match::any_of(pow2, x_square); } auto matcher() const { return match::name("div")(match::arg(0)(pow2()), match::arg(1)(match::is_constant().bind("n"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto n = r.instructions["n"]; auto x = r.instructions["x"]; if(x->get_shape().type() != migraphx::shape::half_type) return; auto x_div_n = m.insert_instruction(ins, make_op("div"), {x, n}); auto mul = m.insert_instruction(ins, make_op("mul"), {x_div_n, x}); m.replace_instruction(ins, mul); } }; void rewrite_low_precision::apply(module& m) const { match::find_matches(m, find_pow2_div{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/rewrite_pooling.cpp000066400000000000000000000235021510465702400212450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static void replace_with_reduce(module& m, instruction_ref ins) { auto&& s = ins->inputs().front()->get_shape(); auto&& op = any_cast(ins->get_operator()); auto lens = s.lens(); std::vector axes(lens.size() - 2); std::iota(axes.begin(), axes.end(), 2); // average pooling if(op.mode == op::pooling_mode::average) { m.replace_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), ins->inputs()); } // max pooling else { m.replace_instruction(ins, make_op("reduce_max", {{"axes", axes}}), ins->inputs()); } } static void lower_lrn_to_pooling(module& m, instruction_ref ins) { auto v = ins->get_operator().to_value(); float alpha = v.at("alpha").to(); float beta = v.at("beta").to(); float k = v.at("bias").to(); int size = v.at("size").to(); auto x = ins->inputs().at(0); const auto& xshape = x->get_shape(); const auto& lens = xshape.lens(); if(lens.size() != 4 or size <= 0 or size > lens[1]) { return; } auto x2 = m.insert_instruction(ins, make_op("mul"), x, x); std::vector perm = {0, 2, 3, 1}; auto transpose1 = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), x2); auto transposed_shape = transpose1->get_shape(); const auto& transposed_lens = transposed_shape.lens(); int64_t channel_dim = lens[1]; std::vector calculated_pads(2); calculate_padding(0, calculated_pads, channel_dim, 1, 1, size, true); auto avg = m.insert_instruction( ins, make_op("pooling", {{"mode", op::pooling_mode::average}, {"lengths", std::vector{1, size}}, {"stride", std::vector{1, 1}}, {"padding", std::vector{0, calculated_pads[0], 0, calculated_pads[1]}}, {"dilations", std::vector{1, 1}}, {"count_include_pad", true}}), transpose1); auto avg_shape = avg->get_shape(); const auto& avg_lens = avg_shape.lens(); if(avg_lens.size() != 4 or avg_lens[3] != transposed_lens[3]) { return; } std::vector inv_perm = {0, 3, 1, 2}; auto transpose2 = m.insert_instruction(ins, make_op("transpose", {{"permutation", inv_perm}}), avg); auto final_shape = transpose2->get_shape(); const auto& final_lens = final_shape.lens(); if(final_lens != lens) return; auto k_lit = m.add_literal(k); auto a_lit = m.add_literal(alpha); auto b_lit = m.add_literal(beta); auto k_mb = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", lens}}), k_lit); auto a_mb = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", lens}}), a_lit); auto b_mb = m.insert_instruction(ins, make_op("multibroadcast", {{"out_lens", lens}}), b_lit); auto alpha_avg = m.insert_instruction(ins, make_op("mul"), a_mb, transpose2); auto den = m.insert_instruction(ins, make_op("add"), k_mb, alpha_avg); auto denpow = m.insert_instruction(ins, make_op("pow"), den, b_mb); auto y = m.insert_instruction(ins, make_op("div"), x, denpow); m.replace_instruction(ins, y); } static void replace_dilations_with_gather_pooling(module& m, instruction_ref ins) { // TODO remove this when MIOpen supports dilated pooling auto&& s = ins->inputs().front()->get_shape(); auto&& op = any_cast(ins->get_operator()); // Ignore N, C axes std::vector dims = {s.lens().cbegin() + 2, s.lens().cend()}; bool default_padding = std::all_of(op.padding.cbegin(), op.padding.cend(), [](auto i) { return i == 0; }); if(not default_padding) { for(size_t idx{0}; idx < op.padding.size(); ++idx) { // We need to pad both ends dims[idx] += op.padding.at(idx) * 2; } } std::vector kernels = op.lengths; std::vector strides = op.stride; std::vector dilations = op.dilations; std::vector> axis_indices; axis_indices.resize(dims.size()); for(auto idx{0}; idx < dims.size(); ++idx) { // Only consider if iw fits into the window for(size_t stride{0}; stride < dims.at(idx) - dilations.at(idx) * (kernels.at(idx) - 1); stride += strides.at(idx)) { for(size_t step{0}; step < kernels.at(idx); ++step) { axis_indices.at(idx).push_back(stride + dilations.at(idx) * step); } } } auto elements = ins->inputs().front(); if(not default_padding) { // Pad supports asym, we need to provide both ends std::vector padding(2 * s.lens().size(), 0); // Format will be e.g {N, C, P1, P2, N, C, P1, P2} for(size_t idx{0}; idx < op.padding.size(); ++idx) { // Ignore N, C axes padding.at(2 + idx) = op.padding.at(idx); padding.at(2 + idx + s.lens().size()) = op.padding.at(idx); } // Default value needed for Max pooling elements = m.insert_instruction( ins, make_op("pad", {{"pads", padding}, {"value", std::numeric_limits::lowest()}}), elements); } for(auto idx{0}; idx < axis_indices.size(); ++idx) { migraphx::shape s_indices{migraphx::shape::int32_type, {axis_indices.at(idx).size()}}; auto indices = m.add_literal(migraphx::literal{s_indices, axis_indices.at(idx)}); elements = m.insert_instruction( ins, make_op("gather", {{"axis", idx + 2 /*ignore N,C*/}}), elements, indices); } // Ignore padding std::vector new_padding(kernels.size(), 0); // The kernel window elements are places next to each other. E.g. {x1, y1, x2, y2, ...} // We need to skip them to not overlap std::vector new_strides(kernels); // Ignore dilations std::vector new_dilations(kernels.size(), 1); m.replace_instruction(ins, make_op("pooling", {{"mode", op.mode}, {"padding", new_padding}, {"stride", new_strides}, {"lengths", kernels}, {"dilations", new_dilations}}), elements); } void rewrite_pooling::apply(module& m) const { for(auto ins : iterator_for(m)) { if(ins->inputs().empty()) continue; if(rewrite_lrn and ins->name() == "lrn") { lower_lrn_to_pooling(m, ins); continue; } if(ins->name() != "pooling") continue; auto&& s = ins->inputs().front()->get_shape(); auto&& op = any_cast(ins->get_operator()); bool same_kernel_as_shape = std::equal( s.lens().cbegin() + 2, s.lens().cend(), op.lengths.cbegin(), op.lengths.cend()); bool default_strides = std::all_of(op.stride.cbegin(), op.stride.cend(), [](auto i) { return i == 1; }); bool default_padding = std::all_of(op.padding.cbegin(), op.padding.cend(), [](auto i) { return i == 0; }); bool default_dilations = std::all_of(op.dilations.cbegin(), op.dilations.cend(), [](auto i) { return i == 1; }); if(same_kernel_as_shape and default_strides and default_padding and default_dilations) { replace_with_reduce(m, ins); } else if(not default_dilations) { // Dilated AvgPool with padding is not supported if(not default_padding and op.mode == op::pooling_mode::average) { continue; } auto size = std::accumulate(s.lens().cbegin(), s.lens().cend(), 1, std::multiplies()); // Can't handle too much size because of literal size if(size > 100000) { continue; } replace_dilations_with_gather_pooling(m, ins); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/rewrite_quantization.cpp000066400000000000000000000110171510465702400223220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_CK_WORKAROUNDS); static void apply_quantizelinear(module& m, instruction_ref ins) { assert(ins->name() == "quantizelinear"); auto x = ins->inputs()[0]; auto y_scale = ins->inputs()[1]; if(x->get_shape().type() != y_scale->get_shape().type()) { x = m.insert_instruction( ins, make_op("convert", {{"target_type", y_scale->get_shape().type()}}), x); } auto div = m.insert_instruction(ins, make_op("div"), x, y_scale); instruction_ref add_zero_point = div; if(shape::is_integral(ins->get_shape().type())) { add_zero_point = m.insert_instruction(ins, make_op("nearbyint"), div); } if(ins->inputs().size() == 3) { auto zero_point = m.insert_instruction(ins, make_op("convert", {{"target_type", y_scale->get_shape().type()}}), ins->inputs()[2]); add_zero_point = m.insert_instruction(ins, make_op("add"), add_zero_point, zero_point); } double max_quant = 0; double min_quant = 0; ins->get_shape().visit_type([&](auto qt) { max_quant = qt.max(); min_quant = qt.min(); }); auto s = add_zero_point->get_shape(); instruction_ref min_arg; instruction_ref max_arg; if(enabled(MIGRAPHX_ENABLE_CK_WORKAROUNDS{})) { std::vector min_data(s.elements(), min_quant); std::vector max_data(s.elements(), max_quant); min_arg = m.add_literal(literal(s, min_data)); max_arg = m.add_literal(literal(s, max_data)); } else { min_arg = m.add_literal(literal{shape{s.type()}, {min_quant}}); max_arg = m.add_literal(literal{shape{s.type()}, {max_quant}}); } auto saturate = insert_common_op(m, ins, make_op("clip"), {add_zero_point, min_arg, max_arg}); m.replace_instruction( ins, make_op("convert", {{"target_type", ins->get_shape().type()}}), saturate); } static void apply_dequantizelinear(module& m, instruction_ref ins) { assert(ins->name() == "dequantizelinear"); auto x_scale = ins->inputs()[1]; auto x = m.insert_instruction( ins, make_op("convert", {{"target_type", x_scale->get_shape().type()}}), ins->inputs()[0]); if(ins->inputs().size() == 3) { auto x_zero_point = m.insert_instruction(ins, make_op("convert", {{"target_type", x_scale->get_shape().type()}}), ins->inputs()[2]); x = m.insert_instruction(ins, make_op("sub"), x, x_zero_point); } m.replace_instruction(ins, make_op("mul"), x, x_scale); } void rewrite_quantization::apply(module& m) const { for(auto ins : iterator_for(m)) { if(ins->name() == "quantizelinear") { apply_quantizelinear(m, ins); } else if(ins->name() == "dequantizelinear") { apply_dequantizelinear(m, ins); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/rewrite_reduce.cpp000066400000000000000000000202321510465702400210420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_FP32_SOFTMAX); namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { struct find_softmax { auto matcher() const { return match::name("softmax"); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto op = ins->get_operator().to_value(); auto axis = op["axis"].to(); auto input = ins->inputs().front(); auto max = m.insert_instruction(ins, make_op("reduce_max", {{"axes", {axis}}}), input); auto maxb = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", input->get_shape().lens()}}), max); auto sub = m.insert_instruction(ins, make_op("sub"), input, maxb); auto exp = m.insert_instruction(ins, make_op("exp"), sub); auto sum = m.insert_instruction(ins, make_op("reduce_sum", {{"axes", {axis}}}), exp); auto sumb = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", input->get_shape().lens()}}), sum); m.replace_instruction(ins, make_op("div"), exp, sumb); } }; struct find_softmax_base_ops { bool full_precision; auto matcher() const { return match::softmax(); } void apply(module& m, const match::matcher_result& r) const { auto div = r.result; auto inp = r.instructions["x"]; auto inp_type = inp->get_shape().type(); auto requires_upcast = not contains({shape::float_type, shape::double_type}, inp_type); if(not requires_upcast) return; auto softmax_inss = find_instructions_between(inp, div, &m); for(const auto& ins : softmax_inss) { if(ins == inp) continue; // Upcast inputs std::vector ins_inputs_up; std::transform( ins->inputs().begin(), ins->inputs().end(), std::back_inserter(ins_inputs_up), [&](auto i) { return m.insert_instruction( ins, make_op("convert", {{"target_type", shape::float_type}}), i); }); // Duplicate instruction to perform op in higher precision auto ins_up = m.insert_instruction(ins, ins->get_operator(), ins_inputs_up); // replace original ins with downcast to preserve graph validity m.replace_instruction( ins, make_op("convert", {{"target_type", ins->get_shape().type()}}), ins_up); } } }; struct find_reduce_mean_variance { auto matcher() const { auto reduce_mean = match::name("reduce_mean"); auto skip_broadcasts_mean = match::skip_broadcasts(reduce_mean.bind("mean")); auto x_minus_mean = match::name("sub")(match::arg(0)(match::any().bind("x")), match::arg(1)(skip_broadcasts_mean)); auto pow_x_minus_mean = match::name("pow")(match::arg(0)(x_minus_mean), match::arg(1)(match::has_value(2.0f))); auto mul_x_minus_mean = match::name("mul")(match::arg(0)(x_minus_mean), match::arg(1)(x_minus_mean)); auto sqdiff = match::name("sqdiff")( match::either_arg(0, 1)(match::any().bind("x"), skip_broadcasts_mean)); return reduce_mean( match::arg(0)(match::any_of(pow_x_minus_mean, mul_x_minus_mean, sqdiff))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto x_ins = r.instructions["x"]; auto mean = r.instructions["mean"]; if(ins->get_operator() != mean->get_operator()) return; if(mean->inputs().front() != x_ins) return; auto x2 = m.insert_instruction(ins, make_op("mul"), x_ins, x_ins); auto mean_x2 = m.insert_instruction(ins, mean->get_operator(), x2); auto mean_x_2 = m.insert_instruction(ins, make_op("mul"), mean, mean); m.replace_instruction(ins, make_op("sub"), mean_x2, mean_x_2); } }; struct find_reduce_mean { auto matcher() const { return match::name("reduce_mean"); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto op = ins->get_operator().to_value(); auto axes = op["axes"].to_vector(); auto input = ins->inputs().front(); bool is_integral = false; double max_n = 0; std::size_t size = 0; input->get_shape().visit_type([&](auto t) { is_integral = t.is_integral(); max_n = t.max(); size = t.size(); }); auto n = input->get_shape().elements() / ins->get_shape().elements(); // Convert accumulator to float if <= 8bit type or if < 3 bytes and n >= max_n /4 if(size == 1 or (n >= max_n / 4 and size < 3)) { shape::type_t t = is_integral ? shape::int32_type : shape::float_type; input = m.insert_instruction(ins, make_op("convert", {{"target_type", t}}), input); } auto n_literal = m.add_literal(literal{{input->get_shape().type(), {1}}, {n}}); if(is_integral) { auto reduce_sum = m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), input); auto div = insert_common_op(m, ins, make_op("div"), {reduce_sum, n_literal}); m.replace_instruction( ins, make_op("convert", {{"target_type", ins->get_shape().type()}}), div); } else { auto new_input = insert_common_op(m, ins, make_op("div"), {input, n_literal}); auto reduce_sum = m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), new_input); m.replace_instruction( ins, make_op("convert", {{"target_type", ins->get_shape().type()}}), reduce_sum); } } }; } // namespace void rewrite_reduce::apply(module& m) const { match::find_matches(m, find_softmax{}, find_reduce_mean_variance{}); if(not enabled(MIGRAPHX_DISABLE_FP32_SOFTMAX{})) { match::find_matches(m, find_softmax_base_ops{}); migraphx::run_passes(m, {migraphx::eliminate_convert{}, migraphx::dead_code_elimination{}, migraphx::eliminate_common_subexpression{}}); } match::find_matches(m, find_reduce_mean{}); migraphx::run_passes(m, {simplify_reshapes{}}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/rewrite_rnn.cpp000066400000000000000000001571061510465702400204030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void rewrite_rnn::apply(module& m) const { for(auto ins : iterator_for(m)) { if(ins->name() == "rnn") { apply_vanilla_rnn(m, ins); } else if(ins->name() == "gru") { apply_gru(m, ins); } else if(ins->name() == "lstm") { apply_lstm(m, ins); } } } // NOLINTNEXTLINE(readability-function-cognitive-complexity) void rewrite_rnn::apply_vanilla_rnn(module& m, instruction_ref ins) const { assert(ins->name() == "rnn"); // could be 3 to 6 inputs, but the parse_rnn function will // append undefined operators to make 6 arguments when parsing // an onnx file. Another case is user can have num of arguments // when writing their module. auto args = ins->inputs(); shape seq_shape = args[0]->get_shape(); std::size_t hidden_size = args[1]->get_shape().lens()[1]; std::size_t batch_size = seq_shape.lens()[1]; shape::type_t type = seq_shape.type(); migraphx::shape ih_shape{type, {1, batch_size, hidden_size}}; std::vector data(ih_shape.elements(), 0); auto actv_funcs = vanilla_rnn_actv_funcs(ins); auto rnn_op = any_cast(ins->get_operator()); op::rnn_direction dirct = rnn_op.direction; // process sequence length instruction_ref seq_lens = m.end(); if((args.size() >= 5) and not args[4]->is_undefined()) { seq_lens = args[4]; } bool variable_seq_len = is_variable_seq_lens(m, seq_lens); instruction_ref last_output{}; if(dirct == op::rnn_direction::bidirectional) { // input weight matrix auto w_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[1]); auto w_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[1]); // hidden state weight matrix auto r_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[2]); auto r_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[2]); // process bias instruction_ref bias_forward = m.end(); instruction_ref bias_reverse = m.end(); if(args.size() >= 4 and not args[3]->is_undefined()) { bias_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[3]); bias_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[3]); } // process intial hidden state, it could be the 6th argument // or the 5th one (if the sequence len argument is ignored) instruction_ref ih_forward{}; instruction_ref ih_reverse{}; if(args.size() == 6 and not args[5]->is_undefined()) { ih_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[5]); ih_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[5]); } else { ih_forward = m.add_literal(migraphx::literal{ih_shape, data}); ih_reverse = m.add_literal(migraphx::literal{ih_shape, data}); } auto ret_forward = vanilla_rnn_cell(true, m, ins, {args[0], w_forward, r_forward, bias_forward, seq_lens, ih_forward}, actv_funcs.at(0)); if(variable_seq_len) { args[0] = m.insert_instruction(ins, make_op("rnn_var_sl_shift_sequence"), args[0], seq_lens); } auto ret_reverse = vanilla_rnn_cell(false, m, ins, {args[0], w_reverse, r_reverse, bias_reverse, seq_lens, ih_reverse}, actv_funcs.at(1)); auto concat_output = m.insert_instruction( ins, make_op("concat", {{"axis", 1}}), ret_forward[1], ret_reverse[1]); last_output = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), concat_output); // The following logic is to ensure the last instruction rewritten from // rnn operator is a concat instruction // sequence len is 1 if(ret_forward[0] == m.end()) { m.replace_instruction( ins, make_op("concat", {{"axis", 1}}), ret_forward[1], ret_reverse[1]); } else { ret_forward[0] = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), ret_forward[0], ret_forward[1]); ret_reverse[0] = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), ret_reverse[1], ret_reverse[0]); m.replace_instruction( ins, make_op("concat", {{"axis", 1}}), {ret_forward[0], ret_reverse[0]}); } } else { bool is_forward = (dirct == op::rnn_direction::forward); // input weight matrix auto w = args[1]; // hidden state weight matrix auto r = args[2]; // process bias and initial hidden state instruction_ref bias = m.end(); if(args.size() >= 4 and not args[3]->is_undefined()) { bias = args[3]; } // process intial hidden state instruction_ref ih; if(args.size() == 6 and not args[5]->is_undefined()) { ih = args[5]; } else { ih = m.add_literal(migraphx::literal{ih_shape, data}); } if(not is_forward and variable_seq_len) { args[0] = m.insert_instruction(ins, make_op("rnn_var_sl_shift_sequence"), args[0], seq_lens); } auto ret = vanilla_rnn_cell( is_forward, m, ins, {args[0], w, r, bias, seq_lens, ih}, actv_funcs.at(0)); last_output = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), ret[1]); // following logic is to ensure the last instruction is a // concat instruction // sequence len is 1 if(ret[0] == m.end()) { m.replace_instruction(ins, make_op("concat", {{"axis", 0}}), ret[1]); } else { auto concat_arg0 = is_forward ? ret[0] : ret[1]; auto concat_arg1 = is_forward ? ret[1] : ret[0]; m.replace_instruction(ins, make_op("concat", {{"axis", 0}}), concat_arg0, concat_arg1); } } // in case of all sequences are of the same lengths and shorter than the // max sequence length, need to pad 0's at the end for output hidden states ins = pad_hidden_states(m, args[0], seq_lens, ins); replace_last_hs_output(m, ins, seq_lens, last_output, dirct); } std::vector rewrite_rnn::vanilla_rnn_cell(bool is_forward, module& m, instruction_ref ins, std::vector inputs, const operation& actv_func) const { assert(inputs.size() == 6); auto seq = inputs.at(0); auto w = inputs.at(1); auto r = inputs.at(2); auto bias = inputs.at(3); auto seq_lens = inputs.at(4); auto ih = inputs.at(5); // squeeze and transpose w std::vector perm{1, 0}; auto sw = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), w); auto tran_sw = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), sw); // squeeze and transpose r auto sr = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), r); auto tran_sr = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), sr); // initial hidden state auto sih = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), ih); auto sih_lens = sih->get_shape().lens(); // bias instruction_ref bb{}; if(bias != m.end()) { long hs = r->get_shape().lens()[2]; auto sbias = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), bias); auto wb = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {hs}}}), sbias); auto rb = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {hs}}, {"ends", {2 * hs}}}), sbias); auto wrb = m.insert_instruction(ins, make_op("add"), wb, rb); bb = m.insert_instruction( ins, make_op("broadcast", {{"axis", 1}, {"out_lens", sih_lens}}), wrb); } instruction_ref hidden_out = m.end(); instruction_ref last_out{}; last_out = m.insert_instruction(ins, make_op("unsqueeze", {{"axes", {0, 1}}}), sih); long seq_len = get_seq_len(m, seq, seq_lens); for(long i = 0; i < seq_len; i++) { long seq_index = is_forward ? i : (seq_len - 1 - i); auto xt = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {seq_index}}, {"ends", {seq_index + 1}}}), seq); auto cont_xt = m.insert_instruction(ins, make_op("contiguous"), xt); xt = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), cont_xt); auto xt_wi = m.insert_instruction(ins, make_op("dot"), xt, tran_sw); auto ht_ri = m.insert_instruction(ins, make_op("dot"), sih, tran_sr); if(bias != m.end()) { xt_wi = m.insert_instruction(ins, make_op("add"), xt_wi, bb); } auto xt_ht = m.insert_instruction(ins, make_op("add"), xt_wi, ht_ri); // apply activation function auto ht = m.insert_instruction(ins, actv_func, xt_ht); sih = ht; // add the dimensions of sequence length (axis 0 for sequence length, // axis 1 for num_directions last_out = m.insert_instruction(ins, make_op("unsqueeze", {{"axes", {0, 1}}}), ht); // concatenation for the last last_out is performed in the apply() // function to ensure the last instruction is concat, then we have // output inserted if(i < seq_len - 1) { if(is_forward) { hidden_out = (seq_index == 0) ? last_out : m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), hidden_out, last_out); } else { hidden_out = (seq_index == seq_len - 1) ? last_out : m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), last_out, hidden_out); } } } return {hidden_out, last_out}; } std::vector rewrite_rnn::vanilla_rnn_actv_funcs(instruction_ref ins) const { auto rnn_op = any_cast(ins->get_operator()); // could be 3 to 6 inputs, but the parse_gru function will // append undefined operators to make 6 arguments when parsing // an onnx file. Another case is user can have any num of arguments // when writing their program. if(rnn_op.direction == op::rnn_direction::bidirectional) { if(rnn_op.actv_funcs.empty()) { // default is tanh return {make_op("tanh"), make_op("tanh")}; } else if(rnn_op.actv_funcs.size() == 1) { return {rnn_op.actv_funcs.at(0), rnn_op.actv_funcs.at(0)}; } else { return rnn_op.actv_funcs; } } else { if(rnn_op.actv_funcs.empty()) { // default is tanh return {make_op("tanh")}; } else { return rnn_op.actv_funcs; } } } // NOLINTNEXTLINE(readability-function-cognitive-complexity) void rewrite_rnn::apply_gru(module& m, instruction_ref ins) const { assert(ins->name() == "gru"); const auto actv_funcs = gru_actv_funcs(ins); // could be 3 to 6 inputs, but the parse_gru function will // append undefined operators to make 6 arguments when parsing // an onnx file. Another case is user can have num of arguments // when writing their program. auto args = ins->inputs(); shape seq_shape = args[0]->get_shape(); std::size_t hidden_size = args[2]->get_shape().lens()[2]; std::size_t batch_size = seq_shape.lens()[1]; shape::type_t type = seq_shape.type(); migraphx::shape ih_shape{type, {1, batch_size, hidden_size}}; std::vector data(ih_shape.elements(), 0.0); auto gru_op = any_cast(ins->get_operator()); op::rnn_direction dirct = gru_op.direction; // process sequence length instruction_ref seq_lens = m.end(); if((args.size() >= 5) and not args[4]->is_undefined()) { seq_lens = args[4]; } bool variable_seq_len = is_variable_seq_lens(m, seq_lens); instruction_ref last_output{}; if(dirct == op::rnn_direction::bidirectional) { // w weight matrix auto w_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[1]); auto w_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[1]); // r weight matrix auto r_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[2]); auto r_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[2]); // bias instruction_ref bias_forward = m.end(); instruction_ref bias_reverse = m.end(); if(args.size() >= 4 and not args[3]->is_undefined()) { bias_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[3]); bias_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[3]); } // intial hidden state instruction_ref ih_forward{}; instruction_ref ih_reverse{}; if(args.size() == 6 and not args[5]->is_undefined()) { ih_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[5]); ih_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[5]); } else { ih_forward = m.add_literal(migraphx::literal{ih_shape, data}); ih_reverse = m.add_literal(migraphx::literal{ih_shape, data}); } auto ret_forward = gru_cell(true, m, ins, {args[0], w_forward, r_forward, bias_forward, seq_lens, ih_forward}, gru_op.linear_before_reset, actv_funcs.at(0), actv_funcs.at(1)); if(variable_seq_len) { args[0] = m.insert_instruction(ins, make_op("rnn_var_sl_shift_sequence"), args[0], seq_lens); } auto ret_reverse = gru_cell(false, m, ins, {args[0], w_reverse, r_reverse, bias_reverse, seq_lens, ih_reverse}, gru_op.linear_before_reset, actv_funcs.at(2), actv_funcs.at(3)); auto concat_output = m.insert_instruction( ins, make_op("concat", {{"axis", 1}}), ret_forward[1], ret_reverse[1]); last_output = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), concat_output); // The following logic is to ensure the last instruction rewritten // from gru operator is a concat if(ret_forward[0] == m.end()) { m.replace_instruction( ins, make_op("concat", {{"axis", 1}}), ret_forward[1], ret_reverse[1]); } else { ret_forward[0] = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), ret_forward[0], ret_forward[1]); ret_reverse[0] = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), ret_reverse[1], ret_reverse[0]); m.replace_instruction( ins, make_op("concat", {{"axis", 1}}), {ret_forward[0], ret_reverse[0]}); } } else { bool is_forward = (dirct == op::rnn_direction::forward); // weight matrix auto w = args[1]; auto r = args[2]; // bias instruction_ref bias = m.end(); if(args.size() >= 4 and not args[3]->is_undefined()) { bias = args[3]; } // intial hidden state instruction_ref ih{}; if(args.size() == 6 and not args[5]->is_undefined()) { ih = args[5]; } else { ih = m.add_literal(migraphx::literal{ih_shape, data}); } if(not is_forward and variable_seq_len) { args[0] = m.insert_instruction(ins, make_op("rnn_var_sl_shift_sequence"), args[0], seq_lens); } auto ret = gru_cell(is_forward, m, ins, {args[0], w, r, bias, seq_lens, ih}, gru_op.linear_before_reset, actv_funcs.at(0), actv_funcs.at(1)); last_output = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), ret[1]); if(ret[0] == m.end()) { m.replace_instruction(ins, make_op("concat", {{"axis", 0}}), ret[1]); } else { auto concat_arg0 = is_forward ? ret[0] : ret[1]; auto concat_arg1 = is_forward ? ret[1] : ret[0]; m.replace_instruction(ins, make_op("concat", {{"axis", 0}}), concat_arg0, concat_arg1); } } // in case of all sequences are of the same lengths and shorter than the // max sequence length, need to pad 0's at the end for output hidden states ins = pad_hidden_states(m, args[0], seq_lens, ins); replace_last_hs_output(m, ins, seq_lens, last_output, dirct); } // NOLINTNEXTLINE(readability-function-cognitive-complexity) std::vector rewrite_rnn::gru_cell(bool is_forward, module& m, instruction_ref ins, std::vector inputs, int linear_before_reset, const operation& actv_func1, const operation& actv_func2) const { assert(inputs.size() == 6); auto seq = inputs.at(0); auto w = inputs.at(1); auto r = inputs.at(2); auto bias = inputs.at(3); auto seq_lens = inputs.at(4); auto ih = inputs.at(5); instruction_ref hidden_states = m.end(); instruction_ref last_output{}; migraphx::shape seq_shape = seq->get_shape(); migraphx::shape r_shape = r->get_shape(); long hs = r_shape.lens()[2]; migraphx::shape ss(seq_shape.type(), {seq_shape.lens()[1], r_shape.lens()[2]}); std::vector data(ss.elements(), 1.0f); auto l1 = m.add_literal(migraphx::literal{ss, data}); // w matrix squeeze to 2-dim and do a transpose std::vector perm{1, 0}; auto sw = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), w); auto tw = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), sw); // r slide to two part, zr and h auto sr = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), r); auto rzr = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {2 * hs}}}), sr); auto trzr = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), rzr); auto rh = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {2 * hs}}, {"ends", {3 * hs}}}), sr); auto trh = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), rh); // initial states auto sih = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), ih); size_t bs = ih->get_shape().lens()[1]; // bias instruction_ref bwb{}; instruction_ref brb_zr{}; instruction_ref brb_h{}; if(bias != m.end()) { auto sbias = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), bias); auto wb = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {3 * hs}}}), sbias); bwb = m.insert_instruction( ins, make_op("broadcast", {{"axis", 1}, {"out_lens", {bs, static_cast(3 * hs)}}}), wb); auto rb_zr = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {3 * hs}}, {"ends", {5 * hs}}}), sbias); auto rb_h = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {5 * hs}}, {"ends", {6 * hs}}}), sbias); brb_zr = m.insert_instruction( ins, make_op("broadcast", {{"axis", 1}, {"out_lens", {bs, static_cast(2 * hs)}}}), rb_zr); brb_h = m.insert_instruction( ins, make_op("broadcast", {{"axis", 1}, {"out_lens", {bs, static_cast(hs)}}}), rb_h); } long seq_len = get_seq_len(m, seq, seq_lens); for(long i = 0; i < seq_len; i++) { long seq_index = is_forward ? i : (seq_len - 1 - i); auto xt = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {seq_index}}, {"ends", {seq_index + 1}}}), seq); auto cont_xt = m.insert_instruction(ins, make_op("contiguous"), xt); xt = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), cont_xt); auto xt_w = m.insert_instruction(ins, make_op("dot"), xt, tw); auto ih1_rzr = m.insert_instruction(ins, make_op("dot"), sih, trzr); if(bias != m.end()) { xt_w = m.insert_instruction(ins, make_op("add"), xt_w, bwb); ih1_rzr = m.insert_instruction(ins, make_op("add"), ih1_rzr, brb_zr); } auto xw_z = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {hs}}}), xt_w); auto xw_r = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {hs}}, {"ends", {2 * hs}}}), xt_w); auto xw_h = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {2 * hs}}, {"ends", {3 * hs}}}), xt_w); auto hr_z = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {hs}}}), ih1_rzr); auto hr_r = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {hs}}, {"ends", {2 * hs}}}), ih1_rzr); auto xw_hr_z = m.insert_instruction(ins, make_op("add"), xw_z, hr_z); auto zt = m.insert_instruction(ins, actv_func1, xw_hr_z); auto xw_hr_r = m.insert_instruction(ins, make_op("add"), xw_r, hr_r); auto rt = m.insert_instruction(ins, actv_func1, xw_hr_r); instruction_ref hr_h{}; if(linear_before_reset == 0) { // equation g(Xt*(Wh^T) + (rt (.) Ht-1)*(Rh^T) + Rbh + Wbh) auto rt_ht1 = m.insert_instruction(ins, make_op("mul"), rt, sih); hr_h = m.insert_instruction(ins, make_op("dot"), rt_ht1, trh); if(bias != m.end()) { hr_h = m.insert_instruction(ins, make_op("add"), hr_h, brb_h); } } else { // equation ht = g(Xt*(Wh^T) + (rt (.) (Ht-1*(Rh^T) + Rbh)) + Wbh) auto ht1_rh = m.insert_instruction(ins, make_op("dot"), sih, trh); if(bias != m.end()) { ht1_rh = m.insert_instruction(ins, make_op("add"), ht1_rh, brb_h); } hr_h = m.insert_instruction(ins, make_op("mul"), rt, ht1_rh); } auto xw_hr_h = m.insert_instruction(ins, make_op("add"), xw_h, hr_h); auto ht = m.insert_instruction(ins, actv_func2, xw_hr_h); // equation Ht = (1 - zt) (.) ht + zt (.) Ht-1 auto one_minus_zt = m.insert_instruction(ins, make_op("sub"), l1, zt); auto one_minus_zt_ht = m.insert_instruction(ins, make_op("mul"), one_minus_zt, ht); auto zt_ht1 = m.insert_instruction(ins, make_op("mul"), zt, sih); sih = m.insert_instruction(ins, make_op("add"), one_minus_zt_ht, zt_ht1); last_output = m.insert_instruction(ins, make_op("unsqueeze", {{"axes", {0, 1}}}), sih); if(i < seq_len - 1) { if(is_forward) { hidden_states = (seq_index == 0) ? last_output : m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), hidden_states, last_output); } else { hidden_states = (seq_index == seq_len - 1) ? last_output : m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), last_output, hidden_states); } } } return {hidden_states, last_output}; } std::vector rewrite_rnn::gru_actv_funcs(instruction_ref ins) const { auto gru_op = any_cast(ins->get_operator()); // before rewrite the gru operator, need to ensure // we have 4 actv funcs, even though a user does not // specifiy any actv func. If less than 4, use the // algorithm in parse_gru to make 4 actv functions if(gru_op.direction == op::rnn_direction::bidirectional) { if(gru_op.actv_funcs.empty()) return {make_op("sigmoid"), make_op("tanh"), make_op("sigmoid"), make_op("tanh")}; else if(gru_op.actv_funcs.size() == 1) return {gru_op.actv_funcs.at(0), gru_op.actv_funcs.at(0), gru_op.actv_funcs.at(0), gru_op.actv_funcs.at(0)}; else if(gru_op.actv_funcs.size() == 2) return {gru_op.actv_funcs.at(0), gru_op.actv_funcs.at(1), gru_op.actv_funcs.at(0), gru_op.actv_funcs.at(1)}; else if(gru_op.actv_funcs.size() == 3) return {gru_op.actv_funcs.at(0), gru_op.actv_funcs.at(1), gru_op.actv_funcs.at(2), gru_op.actv_funcs.at(0)}; else return gru_op.actv_funcs; } else { if(gru_op.actv_funcs.empty()) return {make_op("sigmoid"), make_op("tanh")}; else if(gru_op.actv_funcs.size() == 1) return {gru_op.actv_funcs.at(0), gru_op.actv_funcs.at(0)}; else return gru_op.actv_funcs; } } // for lstm operators // NOLINTNEXTLINE(readability-function-cognitive-complexity) void rewrite_rnn::apply_lstm(module& m, instruction_ref ins) const { assert(ins->name() == "lstm"); auto args = ins->inputs(); shape seq_shape = args[0]->get_shape(); std::size_t hidden_size = args[2]->get_shape().lens()[2]; std::size_t batch_size = seq_shape.lens()[1]; shape::type_t type = seq_shape.type(); migraphx::shape ihc_shape{type, {1, batch_size, hidden_size}}; std::vector ihc_data(ihc_shape.elements(), 0.0); migraphx::shape pph_shape{type, {1, 3 * hidden_size}}; auto actv_funcs = lstm_actv_funcs(ins); auto lstm_op = any_cast(ins->get_operator()); op::rnn_direction dirct = lstm_op.direction; // process sequence length instruction_ref seq_lens = m.end(); if((args.size() >= 5) and not args[4]->is_undefined()) { seq_lens = args[4]; } bool variable_seq_len = is_variable_seq_lens(m, seq_lens); instruction_ref last_hs_output{}; instruction_ref last_cell_output{}; instruction_ref hidden_state{}; instruction_ref cell_outputs{}; if(dirct == op::rnn_direction::bidirectional) { // input weight matrix // input weight matrix auto w_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[1]); auto w_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[1]); // hidden state weight matrix auto r_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[2]); auto r_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[2]); // process bias instruction_ref bias_forward = m.end(); instruction_ref bias_reverse = m.end(); if(args.size() >= 4 and not args[3]->is_undefined()) { bias_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[3]); bias_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[3]); } // process intial hidden state, it is the 6th argument instruction_ref ih_forward{}; instruction_ref ih_reverse{}; if(args.size() >= 6 and not args[5]->is_undefined()) { ih_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[5]); ih_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[5]); } else { ih_forward = m.add_literal(migraphx::literal{ihc_shape, ihc_data}); ih_reverse = m.add_literal(migraphx::literal{ihc_shape, ihc_data}); } // process initial cell value instruction_ref ic_forward{}; instruction_ref ic_reverse{}; if(args.size() >= 7 and not args[6]->is_undefined()) { ic_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[6]); ic_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[6]); } else { ic_forward = m.add_literal(migraphx::literal{ihc_shape, ihc_data}); ic_reverse = m.add_literal(migraphx::literal{ihc_shape, ihc_data}); } // process weight of the peephole instruction_ref pph_forward = m.end(); instruction_ref pph_reverse = m.end(); if(args.size() == 8 and not args[7]->is_undefined()) { pph_forward = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), args[7]); pph_reverse = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), args[7]); } auto ret_forward = lstm_cell(true, m, ins, {args[0], w_forward, r_forward, bias_forward, seq_lens, ih_forward, ic_forward, pph_forward}, actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(2)); if(variable_seq_len) { args[0] = m.insert_instruction(ins, make_op("rnn_var_sl_shift_sequence"), args[0], seq_lens); } auto ret_reverse = lstm_cell(false, m, ins, {args[0], w_reverse, r_reverse, bias_reverse, seq_lens, ih_reverse, ic_reverse, pph_reverse}, actv_funcs.at(3), actv_funcs.at(4), actv_funcs.at(5)); auto concat_hs_output = m.insert_instruction( ins, make_op("concat", {{"axis", 1}}), ret_forward[1], ret_reverse[1]); auto concat_cell_output = m.insert_instruction( ins, make_op("concat", {{"axis", 1}}), ret_forward[3], ret_reverse[3]); last_hs_output = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), concat_hs_output); last_cell_output = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), concat_cell_output); // the following logic is to ensure the last instruction is a concat if(ret_forward[0] == m.end()) { cell_outputs = concat_cell_output; } else { ret_forward[1] = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), ret_forward[0], ret_forward[1]); ret_reverse[1] = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), ret_reverse[1], ret_reverse[0]); ret_forward[3] = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), ret_forward[2], ret_forward[3]); ret_reverse[3] = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), ret_reverse[3], ret_reverse[2]); cell_outputs = m.insert_instruction( ins, make_op("concat", {{"axis", 1}}), ret_forward[3], ret_reverse[3]); } hidden_state = m.replace_instruction( ins, make_op("concat", {{"axis", 1}}), {ret_forward[1], ret_reverse[1]}); } else { bool is_forward = (dirct == op::rnn_direction::forward); // weight matrices auto w = args[1]; auto r = args[2]; // bias instruction_ref bias = m.end(); if(args.size() >= 4 and not args[3]->is_undefined()) { bias = args[3]; } // initial hidden state instruction_ref ih{}; if(args.size() >= 6 and not args[5]->is_undefined()) { ih = args[5]; } else { ih = m.add_literal(migraphx::literal{ihc_shape, ihc_data}); } // initial cell value instruction_ref ic{}; if(args.size() >= 7 and not args[6]->is_undefined()) { ic = args[6]; } else { ic = m.add_literal(migraphx::literal{ihc_shape, ihc_data}); } // process weight of the peephole instruction_ref pph = m.end(); if(args.size() == 8 and not args[7]->is_undefined()) { pph = args[7]; } if(not is_forward and variable_seq_len) { args[0] = m.insert_instruction(ins, make_op("rnn_var_sl_shift_sequence"), args[0], seq_lens); } auto ret = lstm_cell(is_forward, m, ins, {args[0], w, r, bias, seq_lens, ih, ic, pph}, actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(2)); last_hs_output = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), ret[1]); last_cell_output = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), ret[3]); if(ret[0] == m.end()) { cell_outputs = ret[3]; hidden_state = m.replace_instruction(ins, make_op("concat", {{"axis", 0}}), ret[1]); } else { auto concat_cell_arg0 = is_forward ? ret[2] : ret[3]; auto concat_cell_arg1 = is_forward ? ret[3] : ret[2]; cell_outputs = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), concat_cell_arg0, concat_cell_arg1); auto concat_arg0 = is_forward ? ret[0] : ret[1]; auto concat_arg1 = is_forward ? ret[1] : ret[0]; hidden_state = m.replace_instruction( ins, make_op("concat", {{"axis", 0}}), concat_arg0, concat_arg1); } } // in case of all sequences are of the same lengths and shorter than the // max sequence length, need to pad 0's at the end for output hidden states hidden_state = pad_hidden_states(m, args[0], seq_lens, hidden_state); // replace last hidden states with corresponding instructions ins = replace_last_hs_output(m, hidden_state, seq_lens, last_hs_output, dirct); // replace last cell outputs with corresponding instructions replace_last_cell_output(m, ins, seq_lens, cell_outputs, last_cell_output, dirct); } // NOLINTNEXTLINE(readability-function-cognitive-complexity) std::vector rewrite_rnn::lstm_cell(bool is_forward, module& m, instruction_ref ins, std::vector inputs, const operation& actv_func1, const operation& actv_func2, const operation& actv_func3) const { // must have 7 args in the input vector assert(inputs.size() == 8); auto seq = inputs.at(0); auto w = inputs.at(1); auto r = inputs.at(2); auto bias = inputs.at(3); auto seq_lens = inputs.at(4); auto ih = inputs.at(5); auto ic = inputs.at(6); auto pph = inputs.at(7); instruction_ref hidden_states = m.end(); instruction_ref cell_outputs = m.end(); instruction_ref last_hs_output{}; instruction_ref last_cell_output{}; migraphx::shape r_shape = r->get_shape(); long hs = r_shape.lens()[2]; auto bs = ih->get_shape().lens()[1]; std::vector perm{1, 0}; // w matrix, squeeze and transpose auto sw = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), w); auto tsw = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), sw); // r matrix, squeeze and transpose auto sr = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), r); auto tsr = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), sr); // initial hidden state auto sih = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), ih); // initial cell state auto sic = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), ic); auto ic_lens = sic->get_shape().lens(); // bias instruction_ref wrb{}; if(bias != m.end()) { auto sbias = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), bias); auto ub_wb = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {4 * hs}}}), sbias); auto ub_rb = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {4 * hs}}, {"ends", {8 * hs}}}), sbias); auto ub_wrb = m.insert_instruction(ins, make_op("add"), ub_wb, ub_rb); wrb = m.insert_instruction( ins, make_op("broadcast", {{"axis", 1}, {"out_lens", {bs, 4 * static_cast(hs)}}}), ub_wrb); } // peep hole instruction_ref pphi_brcst{}; instruction_ref ppho_brcst{}; instruction_ref pphf_brcst{}; if(pph != m.end()) { auto spph = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), pph); auto pphi = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {hs}}}), spph); pphi_brcst = m.insert_instruction( ins, make_op("broadcast", {{"axis", 1}, {"out_lens", ic_lens}}), pphi); auto ppho = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {hs}}, {"ends", {2 * hs}}}), spph); ppho_brcst = m.insert_instruction( ins, make_op("broadcast", {{"axis", 1}, {"out_lens", ic_lens}}), ppho); auto pphf = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {2 * hs}}, {"ends", {3 * hs}}}), spph); pphf_brcst = m.insert_instruction( ins, make_op("broadcast", {{"axis", 1}, {"out_lens", ic_lens}}), pphf); } long seq_len = get_seq_len(m, seq, seq_lens); for(long i = 0; i < seq_len; ++i) { long seq_index = is_forward ? i : (seq_len - 1 - i); auto xt = m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {seq_index}}, {"ends", {seq_index + 1}}}), seq); auto cont_xt = m.insert_instruction(ins, make_op("contiguous"), xt); xt = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), cont_xt); auto xt_tsw = m.insert_instruction(ins, make_op("dot"), xt, tsw); auto sih_tsr = m.insert_instruction(ins, make_op("dot"), sih, tsr); auto xt_sih = m.insert_instruction(ins, make_op("add"), xt_tsw, sih_tsr); if(bias != m.end()) { xt_sih = m.insert_instruction(ins, make_op("add"), xt_sih, wrb); } auto it_before_actv = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {hs}}}), xt_sih); auto ot_before_actv = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {hs}}, {"ends", {2 * hs}}}), xt_sih); auto ft_before_actv = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {2 * hs}}, {"ends", {3 * hs}}}), xt_sih); auto ct_before_actv = m.insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {3 * hs}}, {"ends", {4 * hs}}}), xt_sih); if(pph != m.end()) { auto pphi_ct = m.insert_instruction(ins, make_op("mul"), pphi_brcst, sic); it_before_actv = m.insert_instruction(ins, make_op("add"), it_before_actv, pphi_ct); auto pphf_ct = m.insert_instruction(ins, make_op("mul"), pphf_brcst, sic); ft_before_actv = m.insert_instruction(ins, make_op("add"), ft_before_actv, pphf_ct); } auto it = m.insert_instruction(ins, actv_func1, it_before_actv); auto ft = m.insert_instruction(ins, actv_func1, ft_before_actv); auto ct = m.insert_instruction(ins, actv_func2, ct_before_actv); // equation Ct = ft (.) Ct-1 + it (.) ct auto ft_cell = m.insert_instruction(ins, make_op("mul"), ft, sic); auto it_ct = m.insert_instruction(ins, make_op("mul"), it, ct); auto cellt = m.insert_instruction(ins, make_op("add"), ft_cell, it_ct); if(pph != m.end()) { auto ppho_cellt = m.insert_instruction(ins, make_op("mul"), ppho_brcst, cellt); ot_before_actv = m.insert_instruction(ins, make_op("add"), ot_before_actv, ppho_cellt); } auto ot = m.insert_instruction(ins, actv_func1, ot_before_actv); // Ht = ot (.) h(Ct) auto h_cellt = m.insert_instruction(ins, actv_func3, cellt); auto ht = m.insert_instruction(ins, make_op("mul"), ot, h_cellt); sic = cellt; sih = ht; last_hs_output = m.insert_instruction(ins, make_op("unsqueeze", {{"axes", {0, 1}}}), ht); last_cell_output = m.insert_instruction(ins, make_op("unsqueeze", {{"axes", {0, 1}}}), cellt); if(i < seq_len - 1) { if(i == 0) { hidden_states = last_hs_output; cell_outputs = last_cell_output; } else { auto concat_hs_arg0 = is_forward ? hidden_states : last_hs_output; auto concat_hs_arg1 = is_forward ? last_hs_output : hidden_states; hidden_states = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), concat_hs_arg0, concat_hs_arg1); auto concat_cell_arg0 = is_forward ? cell_outputs : last_cell_output; auto concat_cell_arg1 = is_forward ? last_cell_output : cell_outputs; cell_outputs = m.insert_instruction( ins, make_op("concat", {{"axis", 0}}), concat_cell_arg0, concat_cell_arg1); } } } return {hidden_states, last_hs_output, cell_outputs, last_cell_output}; } std::vector rewrite_rnn::lstm_actv_funcs(instruction_ref ins) const { auto lstm_op = any_cast(ins->get_operator()); // before rewrite the lstm operator, need to ensure // we have 6 actv funcs, even though a user does not // specifiy any actv func. If less than 46, use the // algorithm in parse_lstm to make 6 actv functions const auto& actv_funcs = lstm_op.actv_funcs; std::size_t num_actv_funcs = actv_funcs.size(); if(lstm_op.direction == op::rnn_direction::bidirectional) { switch(num_actv_funcs) { case 0: return {make_op("sigmoid"), make_op("tanh"), make_op("tanh"), make_op("sigmoid"), make_op("tanh"), make_op("tanh")}; case 1: return {actv_funcs.at(0), actv_funcs.at(0), actv_funcs.at(0), actv_funcs.at(0), actv_funcs.at(0), actv_funcs.at(0)}; case 2: return {actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(1), actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(1)}; case 3: return {actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(2), actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(2)}; case 4: return {actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(2), actv_funcs.at(3), actv_funcs.at(3), actv_funcs.at(3)}; case 5: return {actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(2), actv_funcs.at(3), actv_funcs.at(4), actv_funcs.at(4)}; default: return actv_funcs; } } else { switch(num_actv_funcs) { case 0: return {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}; case 1: return {actv_funcs.at(0), actv_funcs.at(0), actv_funcs.at(0)}; case 2: return {actv_funcs.at(0), actv_funcs.at(1), actv_funcs.at(1)}; default: return actv_funcs; } } } bool rewrite_rnn::is_variable_seq_lens(const module& m, instruction_ref seq_lens) const { bool is_var_lens = false; if(seq_lens != m.end()) { if(seq_lens->can_eval()) { auto arg_lens = seq_lens->eval(); std::vector vec_lens; arg_lens.visit([&](auto l) { vec_lens.assign(l.begin(), l.end()); }); int64_t l = 0; if(not vec_lens.empty()) { l = vec_lens[0]; } if(not std::all_of(vec_lens.begin(), vec_lens.end(), [&](auto v) { return v == l; })) { is_var_lens = true; } } else { is_var_lens = true; } } return is_var_lens; } std::size_t rewrite_rnn::get_seq_len(const module& m, instruction_ref input, instruction_ref seq_lens) const { bool is_var_lens = is_variable_seq_lens(m, seq_lens); auto input_shape = input->get_shape(); auto length = input_shape.lens()[0]; if(not is_var_lens and seq_lens != m.end()) { auto arg_len = seq_lens->eval(); std::vector vec_lens; arg_len.visit([&](auto l) { vec_lens.assign(l.begin(), l.end()); }); length = vec_lens.empty() ? length : vec_lens[0]; } return length; } instruction_ref rewrite_rnn::replace_last_hs_output(module& m, instruction_ref ins, instruction_ref seq_lens, instruction_ref last_hs_output, op::rnn_direction dirct) const { bool variable_seq_len = is_variable_seq_lens(m, seq_lens); instruction_ref result_ins{}; if(variable_seq_len) { result_ins = m.insert_instruction(std::next(ins), make_op("rnn_var_sl_shift_output", {{"output_name", "hidden_states"}, {"direction", dirct}}), ins, seq_lens); m.replace_instruction(ins, result_ins); auto hs_outputs = find_all(result_ins->outputs(), [&](auto i) { return i->name() == "rnn_last_hs_output"; }); for(auto& hs_out : hs_outputs) { auto inputs = hs_out->inputs(); m.replace_instruction(hs_out, make_op("rnn_var_sl_last_output", {{"direction", dirct}}), inputs.front(), seq_lens); } } else { auto hs_outputs = find_all(ins->outputs(), [&](auto i) { return i->name() == "rnn_last_hs_output"; }); for(auto& hs_out : hs_outputs) { m.replace_instruction(hs_out, last_hs_output); } result_ins = ins; } return result_ins; } void rewrite_rnn::replace_last_cell_output(module& m, instruction_ref ins, instruction_ref seq_lens, instruction_ref cell_outputs, instruction_ref last_cell_output, op::rnn_direction dirct) const { bool variable_seq_len = is_variable_seq_lens(m, seq_lens); auto ins_outputs = find_all(ins->outputs(), [&](auto i) { return i->name() == "rnn_last_cell_output"; }); if(variable_seq_len) { if(not ins_outputs.empty()) { cell_outputs = m.insert_instruction( std::next(ins), make_op("rnn_var_sl_shift_output", {{"output_name", "cell_outputs"}, {"direction", dirct}}), cell_outputs, seq_lens); } for(auto co : ins_outputs) { m.replace_instruction(co, make_op("rnn_var_sl_last_output", {{"direction", dirct}}), cell_outputs, seq_lens); } } // replace the rnn_last_cell_output with the last_cell_output. The while // loop is to handle the case of multiple rnn_last_cell_output operators else { for(auto co : ins_outputs) { m.replace_instruction(co, last_cell_output); } } } instruction_ref rewrite_rnn::pad_hidden_states(module& m, instruction_ref seq, instruction_ref seq_lens, instruction_ref hs) const { auto max_seq_len = seq->get_shape().lens()[0]; auto seq_len = get_seq_len(m, seq, seq_lens); // condition of all sequence are of the same length and // less than max_seq_len, we need to append the hs outputs auto hs_padded = hs; if(seq_len < max_seq_len) { auto s = hs->get_shape(); auto pad_lens = s.lens(); pad_lens[0] = static_cast(max_seq_len - seq_len); shape pad_s{s.type(), pad_lens}; std::vector pad_data(pad_s.elements(), 0.0f); auto pl = m.add_literal(pad_s, pad_data.begin(), pad_data.end()); hs_padded = m.insert_instruction(std::next(hs), make_op("concat", {{"axis", 0}}), hs, pl); m.replace_instruction(hs, hs_padded); } return hs_padded; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/rewrite_topk.cpp000066400000000000000000000101261510465702400205510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { struct find_large_topk { std::size_t n_threshold = 0; auto matcher() const { return match::name("topk"); } static std::size_t split_dim(std::size_t& r, std::size_t min_size) { std::size_t n = 1; auto factors = make_array(2, 3, 5, 7, 11); while(r > min_size) { // NOLINTNEXTLINE(readability-qualified-auto) auto it = std::find_if(factors.begin(), factors.end(), [&](auto d) { return r % d == 0; }); if(it == factors.end()) break; r /= *it; n *= *it; } return n; } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto input = ins->inputs().front(); auto op = ins->get_operator().to_value(); auto axis = op["axis"].to(); auto k = op["k"].to(); auto dims = input->get_shape().lens(); auto n = dims.at(axis); if(n < n_threshold) return; auto gdims = dims; // We have to sort at least k elements, so the min size is k*4 or half the threshold auto group = split_dim(gdims[axis], std::max(n_threshold / 2, k * 4)); if(group < 2) return; gdims.insert(gdims.begin() + axis, group); op["axis"] = axis + 1; auto fdims = dims; fdims[axis] = k * group; auto insert_final = [&](auto t, auto i) { auto elem = m.insert_instruction(ins, make_op("get_tuple_elem", {{"index", i}}), t); return m.insert_instruction(ins, make_op("reshape", {{"dims", fdims}}), elem); }; std::vector indices_data(n); std::iota(indices_data.begin(), indices_data.end(), 0); auto indices_lit = m.add_literal( shape{(n < 65536 ? shape::uint16_type : shape::uint32_type), {n}}, indices_data); auto indices = m.insert_instruction( ins, make_op("broadcast", {{"axis", axis}, {"out_lens", dims}}), indices_lit); auto gindices = m.insert_instruction(ins, make_op("reshape", {{"dims", gdims}}), indices); auto ginput = m.insert_instruction(ins, make_op("reshape", {{"dims", gdims}}), input); auto topk1 = m.insert_instruction(ins, make_op("topk", op), ginput, gindices); auto finput = insert_final(topk1, 0); auto findices = insert_final(topk1, 1); m.replace_instruction(ins, ins->get_operator(), finput, findices); } }; } // namespace void rewrite_topk::apply(module& m) const { match::find_matches(m, find_large_topk{split_threshold}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/schedule.cpp000066400000000000000000000535421510465702400176400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_SCHEDULE) static auto get_inputs() { return [](auto i) { return i->inputs(); }; } static auto get_outputs() { return [](auto i) { return i->outputs(); }; } struct stream_info { std::unordered_map ins2stream; std::unordered_map weights; std::unordered_map iweights; ins_dep_map mod_implicit_deps; void calc_implicit_deps(const module& m) { mod_implicit_deps = m.calc_implicit_deps(); } void accumulate_weights(instruction_ref last, const schedule_model& model) { fix([&](auto self, auto ins) -> std::size_t { if(not contains(weights, ins)) { std::size_t weight = 0; auto&& op = ins->get_operator(); if(not is_context_free(op) and op.name()[0] != '@') weight = model.weight(op); // This will ensure a stream will be assigned to return if(op.name() == "@return") weight = 1; iweights[ins] = weight; auto inputs = ins->inputs(); if(contains(mod_implicit_deps, ins)) { const auto& impl_deps = mod_implicit_deps.at(ins); inputs.insert(inputs.end(), impl_deps.begin(), impl_deps.end()); } weights[ins] = std::accumulate( inputs.begin(), inputs.end(), weight, [&](std::size_t w, instruction_ref i) { return w + self(i); }); } return weights[ins]; })(last); } template void sort_args_by_weight(std::vector& args, Compare compare) const { if(args.size() < 2) return; std::sort(args.begin(), args.end(), by(compare, [this](auto x) { return std::make_tuple( this->weights.at(x), x->inputs().size(), std::addressof(*x)); })); } std::vector::iterator sort_args(std::vector& args) { if(args.size() < 2) { return args.end(); } const std::size_t min_partition_threshold = 2; sort_args_by_weight(args, std::greater<>{}); auto it = std::lower_bound(std::next(args.begin()), args.end(), min_partition_threshold, [&](auto i, std::size_t w) { return this->weights[i] > w; }); assert(it == args.end() or this->weights[*it] <= min_partition_threshold); assert(it == args.end() or std::prev(it) == args.begin() or this->weights[*std::prev(it)] > min_partition_threshold); return it; } struct partition { std::size_t weight = 0; std::vector instructions{}; void add(instruction_ref ins, std::size_t w) { weight += w; instructions.push_back(ins); } }; std::size_t assign_streams(module& m, std::size_t n) { assert(n > 0); partition critical; std::unordered_map> partitions; partitions.reserve(weights.size()); fix([&](auto self, auto ins, auto& part) { assert(not is_end(ins, m.end())); if(not m.has_instruction(ins)) return; if(contains(partitions, ins)) return; // Add an entry so we know the instruction was visited partitions[ins]; part.add(ins, this->iweights[ins]); auto args = ins->inputs(); auto threshold_it = this->sort_args(args); if(not args.empty()) { assert(threshold_it != args.begin()); self(args.front(), part); for(auto i : range(std::next(args.begin()), threshold_it)) { partitions[ins].emplace_back(); self(i, partitions[ins].back()); } for(auto i : range(threshold_it, args.end())) { self(i, part); } } // Sort instructions m.move_instruction(ins, m.end()); })(std::prev(m.end()), critical); // Set the critical partition to stream 0 set_stream(critical, 0); if(n == 1) { // Assign streams for the other partitions for(auto&& ins_part : partitions) for(auto&& part : ins_part.second) set_stream(part, 0); return 1; } else { std::vector streams(n - 1); // Assign streams for the other partitions for(auto&& ins_part : partitions) { std::sort(ins_part.second.begin(), ins_part.second.end(), by(std::greater<>{}, [](auto&& x) { return std::make_tuple(x.weight, x.instructions.size()); })); for(auto&& part : ins_part.second) { auto stream = std::min_element(streams.begin(), streams.end()) - streams.begin(); set_stream(part, stream + 1); streams[stream] += part.weight; } } return 1 + std::count_if(streams.begin(), streams.end(), [](auto x) { return x > 0; }); } } using weight_ins = std::pair; struct compare_weight_ins { bool operator()(const weight_ins& x, const weight_ins& y) const { return std::make_pair(x.first, std::addressof(*x.second)) < std::make_pair(y.first, std::addressof(*y.second)); } }; void sort(module& m, std::size_t) { std::set children; std::unordered_map visited; auto last = std::prev(m.end()); auto mw = this->weights.at(last); auto nw = mw / (m.size() + 1); auto add_child = [&](auto ins) { auto x = 1 + (mw - this->weights.at(ins)) / (nw + 1); auto w = x * this->iweights.at(ins); auto& v = visited[ins]; auto it = children.find(std::make_pair(v * w, ins)); if(it == children.end()) { v++; children.insert(std::make_pair(v * w, ins)); } }; add_child(last); while(not children.empty()) { // Pop the first element auto top = children.begin()->second; children.erase(children.begin()); m.move_instruction(top, m.begin()); for(auto ins : top->inputs()) { if(not m.has_instruction(ins)) continue; add_child(ins); } if(contains(mod_implicit_deps, top)) { for(auto ins : mod_implicit_deps.at(top)) { assert(m.has_instruction(ins)); add_child(ins); } } } // move dangling parameter to the front so as not be removed auto ins = std::next(last); while(ins != m.end()) { auto next = std::next(ins); if(ins->name() == "@param") { m.move_instruction(ins, m.begin()); } ins = next; } } void set_stream(const partition& p, std::size_t n) { for(auto ins : p.instructions) if(iweights[ins] > 0) set_stream(ins, n); } void set_stream(instruction_ref ins, std::size_t n) { assert(iweights[ins] > 0); ins2stream[ins] = n; } std::size_t get_stream(instruction_ref ins) const { return ins2stream.at(ins); } bool has_stream(instruction_ref ins) const { return contains(ins2stream, ins); } template bool different(F f, std::size_t stream) const { bool result = false; f([&](auto s) { if(s != stream) { result = true; return false; } // cppcheck-suppress uselessAssignmentArg stream = s; return true; }); return result; } template bool different(F f) const { bool result = false; f([&](auto s) { result = this->different(f, s); return false; }); return result; } template auto get_streams_from(instruction_ref start, Selector select) const { return [=](auto f) { return fix([&](auto self, auto ins) { return all_of(select(ins), [&](auto i) { if(has_stream(i)) return f(this->get_stream(i)); else return self(i); }); })(start); }; } std::unordered_set get_streams(instruction_ref ins) const { if(has_stream(ins)) return {get_stream(ins)}; std::unordered_set result; get_streams_from(ins, get_inputs())([&](auto s) { result.insert(s); return true; }); return result; } template bool is_merge_point(instruction_ref ins, Ts... xs) const { return different(get_streams_from(ins, get_inputs()), xs...); } template bool is_split_point(instruction_ref ins, Ts... xs) const { return different(get_streams_from(ins, get_outputs()), xs...); } std::vector get_recorded_instructions(instruction_ref start) { std::vector result; std::unordered_map m; fix([&](auto self, auto ins) { for(auto i : ins->inputs()) { if(iweights.at(i) == 0) { self(i); continue; } auto stream = this->get_stream(i); if(not contains(m, stream)) m[stream] = i; else m[stream] = std::min(m[stream], i, by(std::less<>{}, [&](auto x) { return std::distance(x, start); })); } })(start); std::transform( m.begin(), m.end(), std::back_inserter(result), [](auto&& p) { return p.second; }); return result; } std::unordered_map>> find_concurrent_instructions(module& m) const { std::unordered_map>> result; std::unordered_map> merge_from; dominator_info di = compute_dominator(m); result.reserve(m.size()); merge_from.reserve(m.size()); for(auto ins : reverse_iterator_for(m)) { for(auto&& arg : ins->outputs()) { if(not m.has_instruction(arg)) continue; if(is_merge_point(arg)) merge_from[ins].insert(arg); merge_from[ins].insert(merge_from[arg].begin(), merge_from[arg].end()); } if(is_split_point(ins)) { erase_if(merge_from[ins], [&](auto merge) { return di.strictly_dominate(ins, merge); }); } auto streams = this->get_streams(ins); // Collect concur instructions for each merge point. for(const auto& merge : merge_from[ins]) { for(auto stream : streams) { if(result[merge].size() <= stream) result[merge].resize(stream + 1); auto&& r = result[merge][stream]; r.push_back(ins); // Copy inputs if they dont have a stream(and are not a builtin and context // free). Inputs without a stream can have a implicit dependency std::copy_if(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(r), [&](auto x) { return not this->has_stream(x) and not is_context_free(x->get_operator()) and x->name().front() != '@'; }); } } } return result; } std::unordered_map> get_conflicts(module& m) { using conflict_table_type = std::unordered_map>; conflict_table_type conflict_table; auto concur_ins = this->find_concurrent_instructions(m); // Compute an index for each instruction std::unordered_map ins2index; std::size_t index_total = 0; for(auto ins : iterator_for(m)) ins2index[ins] = index_total++; std::vector thread_conflict_tables( std::thread::hardware_concurrency()); std::vector index_to_ins; index_to_ins.reserve(concur_ins.size()); std::transform(concur_ins.begin(), concur_ins.end(), std::back_inserter(index_to_ins), [](auto&& it) { return it.first; }); simple_par_for(concur_ins.size(), [&](auto ins_index, auto tid) { auto merge_first = index_to_ins[ins_index]; assert(concur_ins.count(merge_first) > 0); auto& merge_second = concur_ins.at(merge_first); // ensure there are enough elements for different threads assert(tid < thread_conflict_tables.size()); auto& thrd_table = thread_conflict_tables.at(tid); std::unordered_set checked_ins_set; auto range_i = range(merge_second.begin(), std::prev(merge_second.end())); for(auto it_i : iterator_for(range_i)) { std::unordered_set ins1_set; std::copy_if(it_i->begin(), it_i->end(), std::inserter(ins1_set, ins1_set.end()), [&](auto i) { return not contains(checked_ins_set, i); }); checked_ins_set.insert(ins1_set.begin(), ins1_set.end()); auto range_j = range(std::next(it_i), merge_second.end()); std::unordered_set ins2_set; for(auto it_j : iterator_for(range_j)) { std::copy_if(it_j->begin(), it_j->end(), std::inserter(ins2_set, ins2_set.end()), [&](auto i) { return not contains(checked_ins_set, i); }); } for(auto ins1 : ins1_set) { auto p1 = ins2index.at(ins1); for(auto ins2 : ins2_set) { if(ins1 == ins2) continue; auto p2 = ins2index.at(ins2); if(p2 > p1) thrd_table[ins2].insert(ins1); else thrd_table[ins1].insert(ins2); } } } }); // merge thread_conflict_tables together for(auto& tbl : thread_conflict_tables) { for(auto& it : tbl) { conflict_table[it.first].insert(it.second.begin(), it.second.end()); } } // Remove instructions from the conflict table of an ealier instruction for(auto&& ip : conflict_table) { auto ins1 = ip.first; for(auto ins2 : ip.second) if(contains(conflict_table[ins2], ins1)) conflict_table[ins2].erase(ins1); } return conflict_table; } }; void schedule::apply(module& m) const { if(not enable) return; stream_info si; si.calc_implicit_deps(m); auto last = std::prev(m.end()); si.accumulate_weights(last, model); auto nstreams = si.assign_streams(m, model.concurrency()); si.sort(m, model.concurrency()); if(enabled(MIGRAPHX_TRACE_COMPILE{}) or enabled(MIGRAPHX_TRACE_SCHEDULE{})) { m.annotate(std::cout, [&](auto ins) { if(ins->name() == "@param" and not contains(si.weights, ins)) return; std::cout << ":"; std::cout << " weight=" << si.weights.at(ins); std::cout << " input={"; si.get_streams_from(ins, get_inputs())([&](auto s) { std::cout << s << ","; return true; }); std::cout << "}"; if(si.has_stream(ins)) std::cout << " stream=" << si.get_stream(ins); }); std::cout << std::endl; } // No concurrency if(nstreams < 2) return; // Schedule instructions std::size_t wait_id = 0; std::unordered_map ins2wait; std::unordered_map> waited_for; std::unordered_map> ins2waited; ins2wait.reserve(m.size()); ins2waited.reserve(m.size()); for(auto ins : iterator_for(m)) { // Only schedule instructions that have a stream if(not si.has_stream(ins)) continue; assert(si.weights[ins] > 0); // Schedule instruction on the stream auto stream = si.get_stream(ins); assert(stream < model.concurrency()); model.sched(m, ins, stream); // Insert wait instructions if(si.is_merge_point(ins, stream)) { for(auto i : si.get_recorded_instructions(ins)) { if(not si.has_stream(i) or si.get_stream(i) == stream) continue; // Create a new event if it hasn't been recorded if(not contains(ins2wait, i)) { ins2wait[i] = wait_id; model.record(m, i, wait_id); wait_id++; } auto w = ins2wait.at(i); // If we already waited for the event on this stream then dont // insert another wait event if(not contains(waited_for[stream], w)) model.wait(m, ins, w); // Store the event as waited waited_for[stream].insert(w); // Store all wait events that have been waited on prior to the recorded instruction waited_for[stream].insert(ins2waited[i].begin(), ins2waited[i].end()); } } // Store wait events that have already been waited on if(si.is_split_point(ins, stream)) { ins2waited[ins] = waited_for[stream]; } } // Add memory conflicts auto conflict_table = si.get_conflicts(m); for(auto&& ip : conflict_table) { if(ip.second.empty()) continue; std::vector args; args.push_back(ip.first); args.insert(args.end(), ip.second.begin(), ip.second.end()); m.insert_instruction(std::next(ip.first), make_op("identity"), args); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/serialize.cpp000066400000000000000000000046671510465702400200370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template static void raw_data_to_value(value& v, const RawData& rd) { value result; result["shape"] = migraphx::to_value(rd.get_shape()); if(rd.get_shape().type() == shape::tuple_type) result["sub"] = migraphx::to_value(rd.get_sub_objects()); else if(not rd.empty()) result["data"] = migraphx::value::binary(rd.data(), rd.get_shape().bytes()); v = result; } void migraphx_to_value(value& v, const literal& l) { raw_data_to_value(v, l); } void migraphx_from_value(const value& v, literal& l) { auto s = migraphx::from_value(v.at("shape")); l = literal(s, v.at("data").get_binary().data()); } void migraphx_to_value(value& v, const argument& a) { raw_data_to_value(v, a); } void migraphx_from_value(const value& v, argument& a) { if(v.contains("data")) { literal l = migraphx::from_value(v); a = l.get_argument(); } else if(v.contains("sub")) { a = migraphx::from_value>(v.at("sub")); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/shape.cpp000066400000000000000000000652001510465702400171360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct shape_impl { static std::shared_ptr default_shape() { static const std::shared_ptr result = std::make_shared(); return result; } shape_impl() : m_type(shape::float_type) {} shape_impl(shape::type_t t) : m_type(t), m_lens({1}), m_strides({0}), m_standard(true) { assert(t != shape::tuple_type); } shape_impl(shape::type_t t, std::vector l) : m_type(t), m_lens(std::move(l)), m_standard(true) { assert(t != shape::tuple_type); this->calculate_strides(); } shape_impl(shape::type_t t, std::vector l, std::vector s) : m_type(t), m_lens(std::move(l)), m_strides(std::move(s)) { assert(t != shape::tuple_type); assert(m_lens.size() == m_strides.size()); // Calculate standard shape flag for these lens/strides. Strides on size-1 // axes are ignored to support an MLIR rule. std::vector filtered_strides; for(size_t ind = 0; ind < m_strides.size(); ind++) if(m_lens[ind] != 1) filtered_strides.push_back(m_strides[ind]); m_standard = this->elements() == this->element_space() and not skips() and std::is_sorted(filtered_strides.rbegin(), filtered_strides.rend()); } shape_impl(shape::type_t t, std::vector dims) : m_type(t), m_dyn_dims(std::move(dims)) { } shape_impl(shape::type_t t, std::vector mins, std::vector maxes, std::vector> optimals_list) : m_type(t) { if(optimals_list.empty()) { for(size_t i = 0; i < mins.size(); ++i) { m_dyn_dims.push_back(shape::dynamic_dimension{mins[i], maxes[i]}); } } else { assert(mins.size() == maxes.size() and maxes.size() == optimals_list.size()); for(size_t i = 0; i < mins.size(); ++i) { m_dyn_dims.push_back(shape::dynamic_dimension{mins[i], maxes[i], optimals_list[i]}); } } } shape_impl(const std::vector& subs) : m_type(shape::tuple_type), m_shapes(subs) {} shape::type_t m_type; std::vector m_lens = {}; std::vector m_strides = {}; std::vector m_shapes = {}; bool m_standard = false; std::vector m_dyn_dims = {}; void calculate_strides() { m_strides.clear(); m_strides.resize(m_lens.size(), 0); if(m_strides.empty()) return; m_strides.back() = 1; std::partial_sum(m_lens.rbegin(), m_lens.rend() - 1, m_strides.rbegin() + 1, std::multiplies()); } std::size_t element_space() const { if(not m_dyn_dims.empty()) { auto maxes = max_lens(); std::size_t max_val = std::numeric_limits::max(); return std::accumulate( maxes.begin(), maxes.end(), std::size_t{1}, [&](std::size_t x, std::size_t y) { // overflow check and clip if(x != 0 and y > max_val / x) { return max_val; } return x * y; }); } assert(m_lens.size() == m_strides.size()); if(m_lens.empty()) return 0; return std::inner_product(m_lens.begin(), m_lens.end(), m_strides.begin(), std::size_t{0}, std::plus{}, [](std::size_t l, std::size_t s) { return (l - 1) * s; }) + 1; } std::size_t elements() const { if(not m_dyn_dims.empty()) { MIGRAPHX_THROW("SHAPE: elements() called on dynamic shape"); } assert(m_lens.size() == m_strides.size()); if(m_lens.empty()) return 0; return std::accumulate( m_lens.begin(), m_lens.end(), std::size_t{1}, std::multiplies()); } std::size_t get_index(size_t i) const { std::size_t result = 0; std::size_t s = 1; for(auto k : migraphx::reverse(migraphx::range(m_lens.size()))) { std::size_t stride = m_strides[k]; std::size_t len = m_lens[k]; std::size_t idx = (i % (s * len)) / s; result += stride * idx; s *= len; } return result; } std::vector min_lens() const { std::vector ret(m_dyn_dims.size()); std::transform(m_dyn_dims.cbegin(), m_dyn_dims.cend(), ret.begin(), [](const shape::dynamic_dimension& x) { return x.min; }); return ret; } std::vector max_lens() const { std::vector ret(m_dyn_dims.size()); std::transform(m_dyn_dims.cbegin(), m_dyn_dims.cend(), ret.begin(), [](const shape::dynamic_dimension& x) { return x.max; }); return ret; } std::vector> opt_lens() const { std::vector> ret(m_dyn_dims.size()); std::transform(m_dyn_dims.cbegin(), m_dyn_dims.cend(), ret.begin(), [](const shape::dynamic_dimension& x) { return x.optimals; }); return ret; } // Does the shape skip over elements? bool skips() const { assert(m_lens.size() == m_strides.size()); if(elements() == 1) return false; return std::none_of(m_strides.begin(), m_strides.end(), [](auto x) { return x == 1; }); } std::shared_ptr copy() const { return std::make_shared(*this); } }; std::string shape::to_sizes_string(const std::vector& shapes) { std::vector sizes; std::transform(shapes.begin(), shapes.end(), std::back_inserter(sizes), [&](const shape& s) { std::string r = to_string_range(s.lens(), "x"); if(not s.standard()) r += ":" + to_string_range(s.strides(), "x"); return r; }); return join_strings(sizes, ", "); } const std::vector& shape::types() { static const std::vector result = { // clang-format off #define MIGRAPHX_GENERATE_TYPE_VECTOR(x, t) x, MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_GENERATE_TYPE_VECTOR) tuple_type}; // clang-format on return result; } std::string shape::name(shape::type_t t) { switch(t) { case tuple_type: return "tuple_type"; case fp4x2_type: return "fp4x2_type"; #define MIGRAPHX_SHAPE_GENERATE_TYPE_NAME_CASE(x, t) \ case x: return #x; MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_TYPE_NAME_CASE) #undef MIGRAPHX_SHAPE_GENERATE_TYPE_NAME_CASE } MIGRAPHX_THROW("Invalid type"); } std::string shape::cpp_type(shape::type_t t) { switch(t) { case tuple_type: MIGRAPHX_THROW("No C++ type for tuple"); case fp4x2_type: return "uint8_t"; #define MIGRAPHX_SHAPE_GENERATE_CPP_TYPE_CASE(x, t) \ case x: return #t; MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_CPP_TYPE_CASE) #undef MIGRAPHX_SHAPE_GENERATE_CPP_TYPE_CASE } MIGRAPHX_THROW("Invalid type"); } bool shape::is_integral(shape::type_t t) { bool result = false; visit(t, [&](auto as) { result = as.is_integral(); }); return result; } bool shape::is_compatible(const shape& actual, const shape& expected) { // Check subshapes if(expected.type() == shape::tuple_type) return migraphx::equal(actual.sub_shapes(), expected.sub_shapes(), &is_compatible); if(actual == expected) return true; if(actual.type() != expected.type()) return false; // Only the expected can be dynamic if(expected.dynamic()) return actual.ndim() == expected.ndim(); if(actual.dynamic()) return false; if(actual.lens() != expected.lens()) return false; // Check strides from dimensions that are not 1 return all_of(range(actual.lens().size()), [&](auto i) { if(actual.lens()[i] == 1) return true; return actual.strides()[i] == expected.strides()[i]; }); } bool shape::is_unsigned(shape::type_t t) { bool result = false; visit(t, [&](auto as) { result = as.is_unsigned(); }); return result; } bool shape::is_computable(shape::type_t t) { return t != shape::fp4x2_type; } shape::shape() : impl(shape_impl::default_shape()) {} shape::shape(type_t t) : impl(std::make_shared(t)) {} shape::shape(type_t t, std::vector l) : impl(std::make_shared(t, std::move(l))) { } shape::shape(type_t t, std::vector l, std::vector s) : impl(std::make_shared(t, std::move(l), std::move(s))) { } shape::shape(type_t t, std::initializer_list d) : shape::shape(t, std::vector{d.begin(), d.end()}) { } shape::shape(type_t t, std::vector dims) : impl(std::make_shared(t, std::move(dims))) { } shape::shape(type_t t, std::vector mins, std::vector maxes, std::vector> optimals_list) : impl(std::make_shared( t, std::move(mins), std::move(maxes), std::move(optimals_list))) { } shape::shape(const std::vector& subs) : impl(std::make_shared(subs)) {} shape::shape(std::shared_ptr pimpl) : impl(std::move(pimpl)) {} shape shape::from_permutation(type_t t, const std::vector& l, const std::vector& perm) { auto new_lens = reorder_dims(l, perm); shape result = reorder_shape({t, new_lens}, invert_permutation(perm)); assert(result.lens() == l); return result; } shape::type_t shape::type() const { return impl->m_type; } const std::vector& shape::lens() const { if(this->dynamic()) { MIGRAPHX_THROW("SHAPE: lens() called on a dynamic shape"); } return impl->m_lens; } const std::vector& shape::strides() const { if(this->dynamic()) { MIGRAPHX_THROW("SHAPE: strides() called on a dynamic shape"); } return impl->m_strides; } std::size_t shape::ndim() const { if(this->dynamic()) { return dyn_dims().size(); } return lens().size(); } std::size_t shape::elements() const { return impl->elements(); } std::size_t shape::bytes() const { if(this->sub_shapes().empty()) { std::size_t n = 0; if(type() == fp4x2_type) { n = sizeof(uint8_t); } else { this->visit_type([&](auto as) { n = as.size(); }); } return n * this->element_space(); } else { return std::accumulate(this->sub_shapes().begin(), this->sub_shapes().end(), std::size_t{0}, [&](auto x, auto y) { return x + y.bytes(); }); } } std::size_t shape::type_size() const { std::size_t n = 0; if(this->sub_shapes().empty()) { if(this->computable()) { this->visit_type([&](auto as) { n = as.size(); }); } else { n = sizeof(uint8_t); } } return n; } std::size_t shape::index(std::initializer_list l) const { if(this->dynamic()) { MIGRAPHX_THROW("SHAPE: index() called on dynamic shape"); } assert(l.size() <= this->lens().size()); assert(this->lens().size() == this->strides().size()); return std::inner_product(l.begin(), l.end(), this->strides().begin(), std::size_t{0}); } std::size_t shape::index(const std::vector& l) const { if(this->dynamic()) { MIGRAPHX_THROW("SHAPE: index() called on dynamic shape"); } assert(l.size() <= this->lens().size()); assert(this->lens().size() == this->strides().size()); return std::inner_product(l.begin(), l.end(), this->strides().begin(), std::size_t{0}); } std::size_t shape::index(std::size_t i) const { if(this->dynamic()) { MIGRAPHX_THROW("SHAPE: index() called on dynamic shape"); } assert(this->lens().size() == this->strides().size()); if(this->standard()) return i; return impl->get_index(i); } std::vector shape::multi(std::size_t idx) const { assert(idx < elements()); std::vector indices(lens().size()); multi_copy(idx, indices.data(), indices.data() + lens().size()); return indices; } void shape::multi_copy(std::size_t idx, std::size_t* start, const std::size_t* end) const { size_t tidx = idx; (void)end; assert(idx < elements()); assert(lens().size() <= (end - start)); for(size_t ii = lens().size() - 1; ii > 0; ii--) { *(start + ii) = tidx % lens()[ii]; tidx = tidx / lens()[ii]; } *start = tidx; } bool shape::multi_within_bounds(std::vector multi) const { assert(this->lens().size() == multi.size()); return std::equal(multi.begin(), multi.end(), this->lens().begin(), std::less<>{}); } std::size_t shape::single(const std::vector& idx) const { return this->single(idx.begin(), idx.end()); } bool shape::packed() const { if(this->dynamic()) { return false; } return this->sub_shapes().empty() and not impl->skips() and this->elements() == this->element_space(); } bool shape::transposed() const { if(this->dynamic()) { return false; } if(this->broadcasted()) { // TODO: Use a filter_iterator instead std::vector s; s.reserve(this->strides().size()); std::copy_if(this->strides().begin(), this->strides().end(), std::back_inserter(s), [](std::size_t x) { return x != 0; }); return not std::is_sorted(s.rbegin(), s.rend()); } else { return not std::is_sorted(this->strides().rbegin(), this->strides().rend()); } } bool shape::broadcasted() const { if(this->dynamic()) { return false; } assert(this->lens().size() == this->strides().size()); return std::any_of( this->strides().begin(), this->strides().end(), [](auto x) { return x == 0; }); } bool shape::scalar() const { if(this->dynamic()) { return false; } assert(this->lens().size() == this->strides().size()); // if any stride > 0, then accumulate will return false return this->sub_shapes().empty() and std::accumulate(this->strides().begin(), this->strides().end(), std::size_t(0)) == 0; } bool shape::standard() const { return impl->m_standard; } shape shape::normalize_standard() const { if(this->standard()) return {this->type(), this->lens()}; else return *this; } shape shape::as_standard() const { if(not this->dynamic()) return {this->type(), this->lens()}; else return *this; } shape shape::with_lens(type_t t, const std::vector& l) const { if(this->dynamic()) { MIGRAPHX_THROW("SHAPE: with_lens() called on dynamic shape"); } assert(l.size() == this->lens().size()); auto perm = find_permutation(*this); return shape::from_permutation(t, l, perm); } shape shape::with_lens(const std::vector& l) const { if(this->dynamic()) { MIGRAPHX_THROW("SHAPE: with_lens() called on dynamic shape"); } return this->with_lens(this->type(), l); } shape shape::with_type(type_t t) const { auto c = impl->copy(); c->m_type = t; return {c}; } shape shape::to_dynamic() const { if(not sub_shapes().empty()) { std::vector subs; std::transform(sub_shapes().cbegin(), sub_shapes().cend(), std::back_inserter(subs), [](auto s) { return s.to_dynamic(); }); return shape(subs); } if(this->dynamic()) { return *this; } return {type(), lens(), lens(), {}}; } shape shape::to_static(std::size_t x) const { if(not sub_shapes().empty()) { std::vector subs; std::transform(sub_shapes().cbegin(), sub_shapes().cend(), std::back_inserter(subs), [&](auto s) { return s.to_static(x); }); return shape(subs); } if(not this->dynamic()) { return *this; } auto static_lens = this->max_lens(); std::transform(static_lens.begin(), static_lens.end(), this->dyn_dims().cbegin(), static_lens.begin(), [&](auto sl, const auto& dd) { return dd.is_fixed() ? sl : x; }); return {type(), static_lens}; } std::size_t shape::element_space() const { return impl->element_space(); } std::string shape::type_string() const { return name(this->type()); } bool shape::dynamic() const { return not impl->m_dyn_dims.empty(); } bool shape::any_of_dynamic() const { if(this->dynamic()) { return true; } return std::any_of(this->sub_shapes().cbegin(), this->sub_shapes().cend(), [](auto s) { return s.any_of_dynamic(); }); } bool shape::computable() const { return is_computable(this->type()); } const std::vector& shape::dyn_dims() const { if(not this->dynamic()) { MIGRAPHX_THROW("SHAPE: dyn_dims() called on a static shape"); } return impl->m_dyn_dims; } std::vector shape::min_lens() const { return this->dynamic() ? impl->min_lens() : this->lens(); } std::vector shape::max_lens() const { return this->dynamic() ? impl->max_lens() : this->lens(); } std::vector> shape::opt_lens() const { return impl->opt_lens(); } bool shape::dynamic_dimension::is_fixed() const { return this->min == this->max; } bool shape::dynamic_dimension::has_optimal() const { return not optimals.empty(); } shape::dynamic_dimension& shape::dynamic_dimension::operator+=(const std::size_t& x) { this->min += x; this->max += x; std::set new_optimals; std::transform(this->optimals.begin(), this->optimals.end(), std::inserter(new_optimals, new_optimals.begin()), [&x](const auto& opt) { return (opt + x); }); this->optimals = new_optimals; return *this; } shape::dynamic_dimension& shape::dynamic_dimension::operator-=(const std::size_t& x) { assert(this->min >= x); assert(this->max >= x); this->min -= x; this->max -= x; std::set new_optimals; std::transform(this->optimals.begin(), this->optimals.end(), std::inserter(new_optimals, new_optimals.begin()), [&x](const auto& opt) { assert(opt >= x); return (opt - x); }); this->optimals = new_optimals; return *this; } shape::dynamic_dimension& shape::dynamic_dimension::operator*=(const std::size_t& x) { this->min *= x; this->max *= x; std::set new_optimals; std::transform(this->optimals.begin(), this->optimals.end(), std::inserter(new_optimals, new_optimals.begin()), [&x](const auto& opt) { return (opt * x); }); this->optimals = new_optimals; return *this; } bool operator==(const shape::dynamic_dimension& x, const shape::dynamic_dimension& y) { // don't check optimals if both are fixed return (x.min == y.min and x.max == y.max and ((x.is_fixed() and y.is_fixed()) or (x.optimals == y.optimals))); } bool operator!=(const shape::dynamic_dimension& x, const shape::dynamic_dimension& y) { return not(x == y); } std::ostream& operator<<(std::ostream& os, const shape::dynamic_dimension& x) { os << "[ " << x.min << ", " << x.max << ", {" << migraphx::to_string_range(x.optimals) << "} ]"; return os; } bool operator==(const shape::dynamic_dimension& x, const std::size_t& y) { return x.min == y and x.max == y; } bool operator==(const std::size_t& x, const shape::dynamic_dimension& y) { return y == x; } bool operator!=(const shape::dynamic_dimension& x, const std::size_t& y) { return not(x == y); } bool operator!=(const std::size_t& x, const shape::dynamic_dimension& y) { return not(x == y); } shape::dynamic_dimension operator+(const shape::dynamic_dimension& x, const std::size_t& y) { auto dd = x; return dd += y; } shape::dynamic_dimension operator+(const std::size_t& x, const shape::dynamic_dimension& y) { return y + x; } shape::dynamic_dimension operator-(const shape::dynamic_dimension& x, const std::size_t& y) { auto dd = x; return dd -= y; } shape::dynamic_dimension operator*(const shape::dynamic_dimension& x, const std::size_t& y) { auto dd = x; return dd *= y; } shape::dynamic_dimension operator*(const std::size_t& x, const shape::dynamic_dimension& y) { return y * x; } bool operator==(const shape& x, const shape& y) { if(x.dynamic() and y.dynamic()) { return x.impl == y.impl or (x.type() == y.type() and x.dyn_dims() == y.dyn_dims() and x.sub_shapes() == y.sub_shapes()); } return x.impl == y.impl or (x.dynamic() == y.dynamic() and x.type() == y.type() and x.lens() == y.lens() and x.strides() == y.strides() and x.sub_shapes() == y.sub_shapes()); } bool operator!=(const shape& x, const shape& y) { return not(x == y); } std::ostream& operator<<(std::ostream& os, const shape& x) { if(x.sub_shapes().empty()) { if(x.dynamic()) { os << "dynamic, "; os << x.type_string() << ", "; os << "{" << to_string_range(x.dyn_dims()) << "}"; } else { os << x.type_string() << ", "; os << "{" << to_string_range(x.lens()) << "}, "; os << "{" << to_string_range(x.strides()) << "}"; } } else { os << "[" << to_string_range(x.sub_shapes()) << "]"; } return os; } shape::type_t shape::parse_type(const std::string& s) { static const std::unordered_map m = { // clang-format off #define MIGRAPHX_SHAPE_GENERATE_TYPE_STRING_MAP(x, t) {#x, x}, {#t, x}, MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_TYPE_STRING_MAP) {"fp4x2_type", fp4x2_type}, {"tuple_type", tuple_type}, {"tuple", tuple_type}}; // clang-format on return m.at(s); } const std::vector& shape::sub_shapes() const { return impl->m_shapes; } std::vector flatten(const std::vector& shapes) { std::vector result; for(const auto& s : shapes) { if(s.type() == shape::tuple_type) { auto subs = flatten(s.sub_shapes()); result.insert(result.end(), subs.begin(), subs.end()); } else { result.push_back(s); } } return result; } std::size_t shape::tuple_size() const { return impl->m_shapes.size(); } void migraphx_to_value(value& v, const shape& s) { value result; result["type"] = migraphx::to_value(s.type_string()); result["sub_shapes"] = migraphx::to_value(s.sub_shapes()); // avoid calling functions that will throw if(s.dynamic()) { result["lens"] = {}; result["strides"] = {}; result["dynamic_dimensions"] = migraphx::to_value(s.dyn_dims()); } else { result["lens"] = migraphx::to_value(s.lens()); result["strides"] = migraphx::to_value(s.strides()); result["dynamic_dimensions"] = {}; } v = result; } void migraphx_from_value(const value& v, shape& s) { auto t = v.at("type").get_string(); if(t == "tuple_type") { s = shape{migraphx::from_value>(v.at("sub_shapes"))}; } else { if(v.at("dynamic_dimensions").empty()) { s = shape{shape::parse_type(t), v.at("lens").to_vector(), v.at("strides").to_vector()}; } else { auto v_dd = v.at("dynamic_dimensions"); std::vector dyn_dims(v.at("dynamic_dimensions").size()); std::transform( v_dd.begin(), v_dd.end(), dyn_dims.begin(), [](const migraphx::value& x) { return from_value(x); }); s = shape{shape::parse_type(t), dyn_dims}; } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/shape_transform_descriptor.cpp000066400000000000000000001443521510465702400234750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { using dimension = shape_transform_descriptor::dimension; template static auto compute_end_dim(Iterator start, Iterator last, std::size_t dim, Projection proj) { std::size_t x = 1; auto it = std::find_if(start, last, [&](auto d) { x *= proj(d); return x == dim; }); if(it != last) return it; return start; } [[maybe_unused]] static void debug_print(const std::vector& subs) { std::cout << '[' << stream_range(subs) << "]\n"; } [[maybe_unused]] static void debug_print(const dimension& dim) { debug_print(dim.subdimensions); } [[maybe_unused]] static void debug_print(const std::vector& dims) { stream_write_value(std::cout, dims); std::cout << std::endl; } shape_transform_descriptor::shape_transform_descriptor(const std::vector& dims) : rank(dims.size()) { transform(dims, range(dims.size()), std::back_inserter(dimensions), [](std::size_t d, std::size_t a) -> dimension { return {{dimension::sub{d, {a}}}}; }); } template static auto for_each_subdimension(Dimensions&& dimensions, F f) -> decltype(dimensions.begin()->subdimensions, void()) { for(auto& dim : dimensions) { for(auto& s : dim.subdimensions) { f(s); } } } template static auto for_each_subdimension(SubDimensions&& subdimensions, F f) -> decltype(subdimensions.begin()->axis, void()) { for(auto& s : subdimensions) { f(s); } } static std::vector get_all_subdimensions(const std::vector& dimensions) { std::vector result; for(const auto& dim : dimensions) { result.insert(result.end(), dim.subdimensions.begin(), dim.subdimensions.end()); } return result; } template static void for_each_subdimension(Dimensions&& dimensions, Range&& r, F f) { auto start = r.begin(); auto last = r.end(); for(auto& dim : dimensions) { for(auto& s : dim.subdimensions) { if(start == last) return; f(s, *start); start++; } } } // Group all axes into a map with a key of the axis and the value is vector of // all subdimensions that have that axis. template static std::map> group_axes(Dimensions& dimensions) { using sub = std::conditional_t{}, const dimension::sub, dimension::sub>; std::map> axes_map; for_each_subdimension(dimensions, [&](auto& s) { if(s.origin_axis().empty()) return; axes_map[s.origin_axis().front()].push_back(&s); }); return axes_map; } static std::size_t len(const std::vector& subs) { return transform_accumulate( subs.begin(), subs.end(), std::size_t{1}, std::multiplies<>{}, [](const dimension::sub* s) { return s->len; }); } static std::size_t visible_len(const std::vector& subs) { return transform_accumulate( subs.begin(), subs.end(), std::size_t{1}, std::multiplies<>{}, [](const dimension::sub* s) { return s->has_hidden_axis() ? 1 : s->len; }); } static std::vector compute_dims(const operation& op, const std::vector& idims) { shape s{shape::float_type, idims}; return op.compute_shape({s}).lens(); } MIGRAPHX_DEBUG_USED static std::vector compute_dims(const std::vector& ops, const std::vector& idims) { shape s{shape::float_type, idims}; for(const auto& op : ops) s = op.compute_shape({s}); return s.lens(); } shape_transform_descriptor shape_transform_descriptor::create(const std::vector& dims, const std::vector& ops) { shape_transform_descriptor result{dims}; if(not result.apply(ops)) return {}; result.simplify(); assert(compute_dims(ops, dims) == compute_dims(result.generate(), dims)); return result; } shape_transform_descriptor shape_transform_descriptor::rebase(const std::vector& dims, bool broadcast) const { auto result = *this; for(auto& [axis, subs] : group_axes(result.dimensions)) { assert(axis < dims.size()); auto dim = dims[axis]; if(dim == len(subs)) { if(not broadcast) { for(auto* sub : subs) sub->expose(); } } else if(dim == 1) { for(auto* sub : subs) { if(not sub->has_hidden_axis()) sub->len = 1; } } else if(subs.size() == 1) { subs.front()->len = dim; if(broadcast) subs.front()->hide(); else subs.front()->expose(); } else if(dim == visible_len(subs)) { for(auto* sub : subs) { if(sub->has_hidden_axis()) { sub->expose(); sub->len = 1; } } } else return {}; } // TODO: Only simplify if the subs was changed result.simplify(); return result; } static dimension::sub* get_last_subdimension(std::vector& dims) { if(dims.empty()) return {}; auto& d = dims.back(); if(d.subdimensions.empty()) return nullptr; return &d.subdimensions.back(); } bool shape_transform_descriptor::apply(const std::vector& ops) { std::vector dims; std::transform(dimensions.begin(), dimensions.end(), std::back_inserter(dims), [](const dimension& d) { return d.len(); }); for(const auto& op : ops) { auto v = op.to_value(); if(contains({"reshape", "squeeze", "unsqueeze", "flatten"}, op.name())) { dims = compute_dims(op, dims); if(not apply_reshape(dims)) return false; } else if(op.name() == "transpose") { dims = compute_dims(op, dims); if(not apply_transpose(v["permutation"].to_vector())) return false; } else if(op.name() == "multibroadcast") { dims = compute_dims(op, dims); // cppcheck-suppress knownConditionTrueFalse if(not apply_broadcast(dims)) return false; } else if(op.name() == "broadcast") { dims = compute_dims(op, dims); // cppcheck-suppress knownConditionTrueFalse if(not apply_broadcast(dims, v["axis"].to())) return false; } else if(op.name() != "contiguous") { return false; } } return true; } bool shape_transform_descriptor::apply_reshape(const std::vector& rdims) { std::vector idims; transform(get_all_subdimensions(dimensions), std::back_inserter(idims), std::mem_fn(&dimension::sub::len)); auto cdims = common_dims::compute(idims, rdims).dims; if(not cdims.empty() and not apply_reshape_impl(cdims)) return false; return apply_reshape_impl(rdims); } bool shape_transform_descriptor::apply_reshape_impl(const std::vector& rdims) { assert(migraphx::elements(rdims) == this->elements()); if(migraphx::equal( dimensions, rdims, [](const dimension& d, std::size_t rdim) { return d.len() == rdim; })) return true; std::vector new_dims; auto subs = get_all_subdimensions(dimensions); std::size_t i = 0; std::size_t r = 0; while(i < subs.size() and r < rdims.size()) { const auto& sub = subs[i]; auto idim = sub.len; auto rdim = rdims[r]; if(idim == rdim) { new_dims.push_back({{sub}}); } // squeeze else if(rdim > idim) { auto start = subs.begin() + i; auto it = compute_end_dim(start, subs.end(), rdim, std::mem_fn(&dimension::sub::len)); if(it == start) return false; assert(it != subs.end()); auto n = it - start; i += n; new_dims.push_back({{start, it + 1}}); } // unsqueeze else // if(rdim < idim) { auto start = rdims.begin() + r; auto it = compute_end_dim(start, rdims.end(), idim, id{}); if(it == start) return false; assert(it != rdims.end()); auto n = it - start; r += n; transform(range(n + 1), std::back_inserter(new_dims), [&](auto j) -> dimension { auto new_sub = sub; new_sub.add_split_axis(j); new_sub.len = start[j]; return {{new_sub}}; }); } r++; i++; } // Handle trailing 1s if(new_dims.size() < rdims.size() and not new_dims.empty()) { auto* sub = get_last_subdimension(new_dims); auto axis = sub == nullptr ? std::vector{} : sub->axis; auto trailing_dims = range(rdims.begin() + new_dims.size(), rdims.end()); if(any_of(trailing_dims, [](auto d) { return d != 1; })) return false; if(distance(trailing_dims) > 1) sub->add_split_axis(0); transform(range(distance(trailing_dims)), std::back_inserter(new_dims), [&](std::size_t j) -> dimension { dimension::sub s{1, axis}; s.add_split_axis(j + 1); return {{s}}; }); } assert(rdims.size() == new_dims.size()); if(rdims.size() != new_dims.size()) return false; dimensions = new_dims; return true; } bool shape_transform_descriptor::apply_transpose(const std::vector& permutation) { if(permutation.size() != dimensions.size()) return false; dimensions = reorder_dims(dimensions, permutation); return true; } bool shape_transform_descriptor::apply_broadcast(const std::vector& out_lens, optional axis) { auto offset = axis.value_or(out_lens.size() - dimensions.size()); std::vector new_dims; std::transform(out_lens.begin(), out_lens.begin() + offset, std::back_inserter(new_dims), [&](auto len) -> dimension { return {{dimension::sub{len, {}}}}; }); std::transform(dimensions.begin(), dimensions.end(), out_lens.begin() + offset, std::back_inserter(new_dims), [&](const dimension& dim, auto len) -> dimension { if(len == dim.len()) return dim; if(dim.len() != 1) MIGRAPHX_THROW("Wrong out_lens for broadcast"); auto new_subs = dim.subdimensions; if(not new_subs.empty()) { new_subs.front().len = len; } for(auto& s : new_subs) { s.hide(); } return {new_subs}; }); std::transform(out_lens.begin() + offset + dimensions.size(), out_lens.end(), std::back_inserter(new_dims), [&](auto len) -> dimension { return {{dimension::sub{len, {}}}}; }); assert(out_lens.size() == new_dims.size()); dimensions = new_dims; return true; } // Remove subdimensions of 1 static void remove_1_sub_dims(std::vector& subdimensions) { subdimensions.erase(std::remove_if(subdimensions.begin(), subdimensions.end(), [&](const dimension::sub& d) { return d.len == 1; }), subdimensions.end()); } void dimension::simplify() { if(subdimensions.size() < 2) return; remove_1_sub_dims(subdimensions); // Flatten adjacent dimensions adjacent_for_each(subdimensions.begin(), subdimensions.end(), [&](sub& d1, sub& d2) { if(d1.origin_axis().size() < 2) return; if(d2.origin_axis().size() < 2) return; if(d1.has_hidden_axis() != d2.has_hidden_axis()) return; if(not std::equal(d1.origin_axis().begin(), d1.origin_axis().end() - 1, d2.origin_axis().begin(), d2.origin_axis().end() - 1)) return; auto a1 = d1.origin_axis().back(); auto a2 = d2.origin_axis().back(); assert(a2 != a1); if(a2 <= a1) return; if((a2 - a1) != 1) return; d2.len = d1.len * d2.len; d1.len = 1; }); remove_1_sub_dims(subdimensions); } // Search all subdimensions and return the subdimensions vector, an iterator // to the subdimension found and an optional iterator to the previous // subdimension if available. template static auto find_subdimension(shape_transform_descriptor& td, Predicate p) { dimension* prev_dim = nullptr; for(auto& d : td.dimensions) { auto it = std::find_if(d.subdimensions.begin(), d.subdimensions.end(), p); if(it != d.subdimensions.end()) { decltype(std::make_optional(it)) prev = nullopt; if(it == d.subdimensions.begin()) { if(prev_dim != nullptr and not prev_dim->subdimensions.empty()) { prev = std::prev(prev_dim->subdimensions.end()); } } else { prev = std::prev(it); } return std::make_tuple(&d.subdimensions, it, prev); } prev_dim = &d; } MIGRAPHX_THROW("Searching for non-existent subdimension"); } static bool is_broadcast_dim(const dimension& d) { if(d.len() == 1) return false; assert(not d.subdimensions.empty()); if(d.subdimensions.size() != 1) return false; const auto& sub = d.subdimensions.front(); return sub.axis.empty(); } static bool missing_leading_axis(const dimension& d) { if(d.subdimensions.empty()) return true; const auto& sub = d.subdimensions.front(); return sub.origin_axis().empty(); } static void set_broadcast_dim(dimension& d, std::size_t axis) { if(d.subdimensions.empty()) d.subdimensions.push_back({1, {axis}}); else { assert(d.subdimensions.front().hidden_axis.empty()); d.subdimensions.front().hidden_axis = {axis}; } } static void set_origin_axis(dimension::sub& s, const std::vector& axis) { if(s.has_hidden_axis()) s.hidden_axis = axis; else s.axis = axis; } // If an axis is split and some dimensions are hidden and others are not, then // remove the hidden axis so only the non-hidden axis is used in // simplificaiton static void remove_split_hidden_axes(std::map>& axes_map) { for(auto&& p : axes_map) { auto& subs = p.second; if(std::all_of(subs.begin(), subs.end(), [](const dimension::sub* s) { return s->has_hidden_axis(); })) continue; for(auto* sub : subs) { if(not sub->has_hidden_axis()) continue; sub->hidden_axis.clear(); } // Remove the subdimesions that no longer have an axis subs.erase(std::remove_if(subs.begin(), subs.end(), [](const dimension::sub* s) { return s->axis.empty() and s->hidden_axis.empty(); }), subs.end()); } // Remove axis from group if empty erase_if(axes_map, [](auto&& p) { return p.second.empty(); }); } // Replace the hidden axis that is split with an axis that is missing static void fill_split_hidden_axes(std::map>& axes_map, const std::vector& dimensions, std::size_t rank) { // Create a reverse map of the subdimensions to the position std::unordered_map sub_pos_map; for_each_subdimension(dimensions, range(std::numeric_limits::max()), [&](const dimension::sub& sub, std::size_t i) { sub_pos_map[&sub] = i; }); for(auto&& p : axes_map) { auto axis = p.first; auto subs = p.second; if(subs.size() < 2) continue; std::sort(subs.begin(), subs.end(), by(std::less<>{}, [](const dimension::sub* s) { return s->origin_axis(); })); if(not std::all_of(subs.begin(), subs.end(), [](const dimension::sub* s) { return s->has_hidden_axis(); })) continue; auto it = std::adjacent_find( subs.begin(), subs.end(), [&](const dimension::sub* s1, const dimension::sub* s2) { return sub_pos_map.at(s1) + 1 != sub_pos_map.at(s2); }); if(it != subs.end()) continue; auto needed_axes = range(axis + 1, axis + subs.size()); auto missing_axes = reverse(range(needed_axes.begin(), find_if(needed_axes, [&](auto a) { if(a >= rank) return true; return contains(axes_map, a); }))); for_each(missing_axes.begin(), missing_axes.end(), subs.rbegin(), [&](std::size_t axis, dimension::sub* sub) { sub->hidden_axis = {axis}; axes_map[axis].push_back(sub); }); // Remove the subdimansions that have a different axis auto& orig_subs = p.second; orig_subs.erase(std::remove_if(orig_subs.begin(), orig_subs.end(), [&](const dimension::sub* s) { return s->origin_axis().front() != axis; }), orig_subs.end()); } } // If this is scalar, then remove all axes static void remove_scalar_axis(std::vector& dimensions) { dimension::sub* s = nullptr; for(auto& d : dimensions) { auto has_axis = [](const dimension::sub& x) { return not x.origin_axis().empty(); }; auto it = std::find_if(d.subdimensions.begin(), d.subdimensions.end(), has_axis); if(it == d.subdimensions.end()) continue; if(s != nullptr) return; if(std::count_if(std::next(it), d.subdimensions.end(), has_axis) > 0) return; s = &*it; } if(s != nullptr) { if(s->has_hidden_axis()) s->hidden_axis.clear(); if(s->len == 1) s->axis.clear(); } } // Renumber all axes while preserving the order of the axes static void renumber_axes(std::map>& axes_map) { for(auto&& p : axes_map) { const auto& axis = p.first; auto& subs = p.second; if(subs.size() == 1) { set_origin_axis(*subs[0], {axis}); } else { std::sort(subs.begin(), subs.end(), by(std::less<>{}, [](const dimension::sub* s) { return s->origin_axis(); })); for(std::size_t i : range(subs.size())) set_origin_axis(*subs[i], {axis, i}); } } } static void renumber_axes(std::vector& dimensions) { auto axes_map = group_axes(dimensions); renumber_axes(axes_map); } static void collapse_1_dims(std::vector& dimensions) { // Find a dimension that ends with a subdimension of 1 with a single axis, // and is followed by subdimension in the next dimension of 1 that has a // split axis. It will remove the trailing subdimension and update the // leading subdimension to use the axis from the trailing subdimension. adjacent_for_each(dimensions.begin(), dimensions.end(), [&](dimension& d1, dimension& d2) { if(d1.subdimensions.size() < 2) return; if(d2.subdimensions.empty()) return; if(d2.len() != 1) return; const auto& sub1 = d1.subdimensions.back(); auto& sub2 = d2.subdimensions.front(); if(sub1.axis.size() != 1) return; if(sub2.axis.size() < 2) return; if(sub1.len != 1) return; if(sub2.len != 1) return; sub2.axis = sub1.axis; d1.subdimensions.pop_back(); }); renumber_axes(dimensions); } static void insert_empty_1s(std::vector& dimensions, std::size_t rank) { if(dimensions.empty()) return; transform(dimensions, range(rank), dimensions.begin(), [](const dimension& d, std::size_t i) -> dimension { auto result = dimension::sub{d.len(), {i}}; if(result.len > 1) result.hide(); return {{result}}; }); if(rank > dimensions.size()) { transform(range(dimensions.size(), rank), std::back_inserter(dimensions.back().subdimensions), [](std::size_t i) { return dimension::sub{1, {i}}; }); } } // Find missing axes. This will store a mapping between the missing // axis and the next available axis. static std::map find_missing_axes(const std::map>& axes_map, std::size_t rank) { std::map missing_axes; for(auto axis : range(rank)) { if(contains(axes_map, axis)) continue; auto it = axes_map.upper_bound(axis); missing_axes[axis] = it == axes_map.end() ? rank : it->first; } return missing_axes; } // Find broadcasted dimensions. This will store a map from the next axis // to the indices of the previous dimensions that are being broadcasted. static std::map> find_broadcasted_dims(const std::vector& dimensions, std::size_t rank) { std::map> broadcast_dims_map; group_find( dimensions.begin(), dimensions.end(), &missing_leading_axis, [&](auto start, auto last) { auto axis = rank; if(last != dimensions.end()) { assert(not last->subdimensions.empty()); const auto& sub = last->subdimensions.front(); assert(not sub.origin_axis().empty()); axis = sub.origin_axis().front(); } std::deque dims(std::distance(start, last)); std::iota(dims.begin(), dims.end(), std::distance(dimensions.begin(), start)); broadcast_dims_map[axis] = dims; }); return broadcast_dims_map; } void shape_transform_descriptor::simplify() { for(auto& d : dimensions) d.simplify(); remove_scalar_axis(dimensions); std::map missing_axes; std::vector last_axis; { // Group axes auto axes_map = group_axes(dimensions); if(axes_map.empty()) { insert_empty_1s(dimensions, rank); return; } remove_split_hidden_axes(axes_map); fill_split_hidden_axes(axes_map, dimensions, rank); renumber_axes(axes_map); // Find last axis last_axis = std::prev(axes_map.end())->second.back()->origin_axis(); missing_axes = find_missing_axes(axes_map, rank); } std::map> broadcast_dims_map = find_broadcasted_dims(dimensions, rank); // Reinsert removed axis of 1. This tries to insert the missing axis next // to an adjacent axis or used as one of the broadcasted axes in order to // minimize transposition. for(auto&& p : missing_axes) { auto missing_axis = p.first; auto next_axis = p.second; auto missing_sub = dimension::sub{1, {missing_axis}}; // If next_axis is the rank that means there isnt another axis to // search for, so instead try to insert the axis at the end. if(next_axis == rank) { auto [sub, it, prev] = find_subdimension( *this, [&](const dimension::sub& s) { return s.origin_axis() == last_axis; }); // Check if we can insert it at the end auto bdims = broadcast_dims_map.find(rank); if(bdims != broadcast_dims_map.end() and not bdims->second.empty()) { auto bdim = bdims->second.front(); bdims->second.pop_front(); set_broadcast_dim(dimensions[bdim], missing_axis); } else { auto next = std::find_if(std::next(it), sub->end(), [&](const dimension::sub& s) { if(s.len != 1) return true; if(s.axis.empty()) return true; return s.axis.front() > missing_axis; }); sub->insert(next, missing_sub); } // Update last_axis if this is inserted afterwards if(missing_axis > last_axis.front()) last_axis = {missing_axis}; } else { // Search for the subdimension that has the next axis and try to // insert the axis before it will be in order. auto [sub, it, prev] = find_subdimension(*this, [&](const dimension::sub& s) { if(s.origin_axis().empty()) return false; if(s.origin_axis().front() != next_axis) return false; if(s.origin_axis().size() == 1) return true; assert(s.origin_axis().size() == 2); return s.origin_axis().back() == 0; }); bool in_order = false; if(prev.has_value() and not(*prev)->origin_axis().empty()) in_order = (*prev)->origin_axis().front() == missing_axis - 1; // If the axis is not inorder then see if we can find a broadcast axis to place it auto bdims = in_order ? broadcast_dims_map.end() : broadcast_dims_map.upper_bound(missing_axis); if(bdims != broadcast_dims_map.end() and not bdims->second.empty()) { auto bdim = bdims->second.front(); bdims->second.pop_front(); set_broadcast_dim(dimensions[bdim], missing_axis); } else { sub->insert(it, missing_sub); } } } collapse_1_dims(dimensions); } static std::size_t get_len(const dimension::sub& s, const std::vector& input_dims) { if(input_dims.empty()) return s.len; if(s.axis.empty()) return s.len; auto dim = input_dims.at(s.axis.front()); if(dim == 0) return s.len; if(dim == 1) return 1; if(s.axis.size() == 1) return dim; return s.len; } static operation make_reshape_squeeze(const std::vector& new_dims) { // Can use squeeze if(std::all_of(new_dims.begin(), new_dims.end(), [](const dimension& d) { if(d.subdimensions.size() < 2) return true; auto n = std::count_if(d.subdimensions.begin(), d.subdimensions.end(), [&](const dimension::sub& s) { return s.len == 1; }); return n >= (d.subdimensions.size() - 1); })) { std::vector base_axes = {0}; transform_partial_sum( new_dims.begin(), std::prev(new_dims.end()), std::back_inserter(base_axes), std::plus<>{}, [](const dimension& d) { return std::max(1, d.subdimensions.size()); }); auto get_squeezed_axes = [](const dimension& d, std::size_t base_axis) { std::vector result; if(d.subdimensions.size() < 2) return result; auto idx = range(d.subdimensions.size()); transform_if( idx.begin(), idx.end(), std::back_inserter(result), [&](std::size_t i) { return d.subdimensions[i].len == 1; }, [&](std::size_t i) { return base_axis + i; }); if(result.size() == d.subdimensions.size()) result.pop_back(); return result; }; std::vector axes; std::transform(new_dims.begin(), new_dims.end(), base_axes.begin(), join_back_inserter(axes), get_squeezed_axes); return make_op("squeeze", {{"axes", axes}}); } else { std::vector dims; std::transform(new_dims.begin(), new_dims.end(), std::back_inserter(dims), [](const dimension& d) -> std::size_t { if(is_broadcast_dim(d)) return 1; return d.len(); }); return make_op("reshape", {{"dims", dims}}); } } static void flatten_broadcasted_dim(dimension::sub& s) { if(s.axis.empty()) { s.len = 1; s.expose(); } } static operation make_reshape_unsqueeze(const std::vector& subs) { bool use_reshape = false; std::unordered_set all_1s; // Check if split dimensions are all additional 1s if(std::any_of( subs.begin(), subs.end(), [](const dimension::sub& s) { return s.axis.size() > 1; })) { auto subs2 = subs; auto by_axis = by(std::equal_to<>{}, [](const dimension::sub& s) -> int64_t { if(s.axis.empty()) return -1; return s.axis.front(); }); group_by( subs2.begin(), subs2.end(), [&](auto start, auto last) { if(use_reshape) return; // Number of elements auto n = std::distance(start, last); if(n < 2) return; // Number of elements that are 1 auto n1 = std::count_if(start, last, [&](const dimension::sub& s) { return s.len == 1; }); if(n == n1 and not start->axis.empty()) all_1s.insert(start->axis.front()); use_reshape |= std::max(0, n - n1 - 1) > 0; }, by_axis); } if(use_reshape) { std::vector dims; std::transform(subs.begin(), subs.end(), std::back_inserter(dims), [&](const dimension::sub& s) -> std::size_t { if(s.axis.empty()) return 1; return s.len; }); return make_op("reshape", {{"dims", dims}}); } else { std::vector axes; for(auto i : range(subs.size())) { const auto& sub = subs[i]; if(sub.axis.size() == 1) continue; if(sub.len != 1 and not sub.axis.empty()) continue; if(not sub.axis.empty() and contains(all_1s, sub.axis.front()) and sub.axis.back() == 0) continue; axes.push_back(i); } return make_op("unsqueeze", {{"axes", axes}}); } } namespace { struct operation_list { std::vector ops; void push_back(const operation& op) { ops.push_back(op); } std::vector to_vector() && { std::reverse(ops.begin(), ops.end()); return std::move(ops); } }; } // namespace static bool has_no_axes(const dimension& d) { return std::all_of(d.subdimensions.begin(), d.subdimensions.end(), [](const dimension::sub& s) { return s.axis.empty() and s.hidden_axis.empty(); }); } static bool has_axes(const dimension& d) { return std::any_of(d.subdimensions.begin(), d.subdimensions.end(), [](const dimension::sub& s) { return not s.axis.empty(); }); } static std::vector attach_empty_axis(std::vector tsubs) { // Inject additonal axis to compute transpose permutation better auto is_empty_axis = [](const auto& s) { return s.axis.empty(); }; group_find(tsubs.begin(), tsubs.end(), is_empty_axis, [&](auto start, auto last) { if(start == tsubs.begin()) return; auto base = std::prev(start); auto axis = base->axis; axis.push_back(0); std::for_each(start, last, [&](auto& s) { s.axis = axis; axis.back()++; }); }); return tsubs; } static std::vector find_permutation(const std::vector& subs) { auto compare_sub = [](auto f) { return by(f, [](const dimension::sub& s) -> const auto& { return s.axis; }); }; return sort_permutation(subs, compare_sub(std::less<>{})); } // This will generate the operators to apply the shape transformation that is // represented by this class. This is the order of operators that will be // generated if needed: // // 1. Reshape/unsqueeze // 2. Transpose // 3. Broadcast // 4. Reshape/squeeze // 5. Broadcast // // This will generate operators backwards starting at 5 and going up. Steps 1-3 // are generated from the subdimensions and steps 4-5 are generated with the // dimensions. std::vector shape_transform_descriptor::generate(const std::vector& input_dims) const { operation_list result; std::vector new_dims = input_dims.empty() ? dimensions : this->rebase(input_dims).dimensions; // Need broadcast if(std::any_of(new_dims.begin(), new_dims.end(), &is_broadcast_dim)) { std::vector out_lens; std::transform(new_dims.begin(), new_dims.end(), std::back_inserter(out_lens), [](const dimension& d) { return d.len(); }); auto startb = std::find_if_not(new_dims.begin(), new_dims.end(), &has_no_axes); auto trailb = std::find_if_not(startb, new_dims.end(), &has_axes); auto axis = std::distance(new_dims.begin(), startb); auto extra_dims = axis + std::distance(trailb, new_dims.end()); // Use broadcast instead of multibroadcast if(std::all_of(trailb, new_dims.end(), &has_no_axes) and extra_dims > 0 and axis < new_dims.size()) { result.push_back(make_op("broadcast", {{"axis", axis}, {"out_lens", out_lens}})); new_dims.erase(trailb, new_dims.end()); new_dims.erase(new_dims.begin(), new_dims.begin() + axis); } else { result.push_back(make_op("multibroadcast", {{"out_lens", out_lens}})); } } // If all the dimensions have no axes then there isnt anthing else to do // so just clear the new_dims if(std::all_of(new_dims.begin(), new_dims.end(), &has_no_axes)) new_dims.clear(); // Flatten broadcasted dimensions for(auto& d : new_dims) { if(d.subdimensions.size() != 1) continue; flatten_broadcasted_dim(d.subdimensions.front()); } // Need squeeze reshape if(std::any_of(new_dims.begin(), new_dims.end(), [](const dimension& d) { if(d.subdimensions.size() != 1) return true; return is_broadcast_dim(d); })) { result.push_back(make_reshape_squeeze(new_dims)); } auto subs = get_all_subdimensions(new_dims); // Need multibroadcast if(std::any_of(subs.begin(), subs.end(), [&](const dimension::sub& s) { return s.axis.empty() and s.len != 1; })) { std::vector out_lens; std::transform(subs.begin(), subs.end(), std::back_inserter(out_lens), [&](const dimension::sub& s) { return s.len; }); result.push_back(make_op("multibroadcast", {{"out_lens", out_lens}})); } // Flatten broadcasted subdimensions std::for_each(subs.begin(), subs.end(), &flatten_broadcasted_dim); auto permutation = find_permutation(attach_empty_axis(subs)); // Need transpose if(not std::is_sorted(permutation.begin(), permutation.end())) { result.push_back(make_op("transpose", {{"permutation", invert_permutation(permutation)}})); subs = reorder_dims(subs, permutation); } // Need reshape unsqueeze if(std::any_of( subs.begin(), subs.end(), [](const dimension::sub& s) { return s.axis.size() != 1; })) { result.push_back(make_reshape_unsqueeze(subs)); } return std::move(result).to_vector(); } std::set shape_transform_descriptor::find_broadcasted_axes() const { std::set result; for_each_subdimension(dimensions, [&](const dimension::sub& s) { if(s.has_hidden_axis()) result.insert(s.hidden_axis.front()); }); return result; } bool shape_transform_descriptor::has_broadcast() const { return std::any_of(dimensions.begin(), dimensions.end(), [&](const dimension& d) { return std::any_of(d.subdimensions.begin(), d.subdimensions.end(), [&](const dimension::sub& s) { return s.axis.empty() and s.len != 1; }); }); } void shape_transform_descriptor::flatten_broadcast() { for(auto& d : dimensions) std::for_each(d.subdimensions.begin(), d.subdimensions.end(), &flatten_broadcasted_dim); } shape_transform_descriptor shape_transform_descriptor::to_common_from_src() const { shape_transform_descriptor result; auto subs = get_all_subdimensions(this->dimensions); std::transform(subs.begin(), subs.end(), std::back_inserter(result.dimensions), [&](const auto& x) -> dimension { return {{x}}; }); result.rank = this->rank; result.simplify(); return result; } shape_transform_descriptor shape_transform_descriptor::to_common_from_dst() const { shape_transform_descriptor result; result.rank = this->dimensions.size(); std::vector subs; // Update axes to point to the destination for(std::size_t i : range(dimensions.size())) { const auto& d = dimensions[i]; const bool mixed_visibility = std::any_of(d.subdimensions.begin(), d.subdimensions.end(), [](const dimension::sub& s) { return s.has_hidden_axis(); }) and std::any_of(d.subdimensions.begin(), d.subdimensions.end(), [](const dimension::sub& s) { return not s.has_hidden_axis(); }); std::transform(d.subdimensions.begin(), d.subdimensions.end(), range(d.subdimensions.size()).begin(), std::back_inserter(subs), [&](dimension::sub s, auto j) { set_origin_axis(s, {i}); s.add_split_axis(j); if(not mixed_visibility) s.expose(); return s; }); } if(dimensions.size() == 1 and subs.empty()) { transform(range(rank), std::back_inserter(subs), [](std::size_t i) -> dimension::sub { return {1, {0, i}}; }); } std::transform(subs.begin(), subs.end(), std::back_inserter(result.dimensions), [&](const auto& x) -> dimension { return {{x}}; }); renumber_axes(result.dimensions); return result; } shape_transform_descriptor shape_transform_descriptor::to_dst_from_common() const { shape_transform_descriptor result = *this; result.rank = result.common_rank(); if(result.rank == 0) { result.rank = 1; std::vector subs; transform(range(rank), std::back_inserter(subs), [](std::size_t i) -> dimension::sub { return {1, {i}}; }); result.dimensions.push_back({subs}); } else { for_each_subdimension(result.dimensions, range(result.rank), [&](auto& s, std::size_t i) { set_origin_axis(s, {i}); s.expose(); }); result.simplify(); } return result; } shape_transform_descriptor shape_transform_descriptor::to_src_from_common() const { shape_transform_descriptor result; auto subs = get_all_subdimensions(dimensions); result.rank = subs.size(); transform(group_axes(subs), std::back_inserter(result.dimensions), [&](auto&& p) -> dimension { const auto& [axis, gsubs] = p; std::vector subdimensions; transform(gsubs, std::back_inserter(subdimensions), [&](const dimension::sub* s) { dimension::sub result = *s; std::size_t i = s - subs.data(); set_origin_axis(result, {i}); result.expose(); return result; }); return {subdimensions}; }); result.simplify(); return result; } std::vector> shape_transform_descriptor::common_axes_map_from_src() const { std::vector> result; auto subs = get_all_subdimensions(dimensions); std::map> axes_map; for(const auto& s : subs) { if(not s.origin_axis().empty()) axes_map[s.origin_axis().front()].push_back(&s); } for(auto&& p : axes_map) { std::sort(p.second.begin(), p.second.end(), by(std::less<>{}, [](const dimension::sub* s) { return s->axis; })); } if(axes_map.empty() and dimensions.size() == 1) { transform(range(rank), std::back_inserter(result), [](std::size_t i) { return std::vector{i}; }); return result; } assert(not axes_map.empty()); auto max_axis = std::prev(axes_map.end())->first; result.resize(max_axis + 1); for(auto&& p : axes_map) { assert(p.first < result.size()); std::transform(p.second.begin(), p.second.end(), std::back_inserter(result[p.first]), [&](const dimension::sub* s) { return s - subs.data(); }); } return result; } std::vector> shape_transform_descriptor::common_axes_map_from_dst() const { std::vector> result; std::size_t start = 0; for(const auto& d : dimensions) { auto& v = result.emplace_back(d.subdimensions.size()); std::iota(v.begin(), v.end(), start); start += d.subdimensions.size(); } if(result.size() == 1 and result.front().empty()) { result.front().resize(rank); std::iota(result.front().begin(), result.front().end(), 0); } return result; } bool shape_transform_descriptor::empty() const { return dimensions.empty(); } std::vector shape_transform_descriptor::lens() const { std::vector result; std::transform(dimensions.begin(), dimensions.end(), std::back_inserter(result), [](const dimension& d) { return d.len(); }); return result; } std::size_t dimension::len() const { return transform_accumulate(subdimensions.begin(), subdimensions.end(), std::size_t{1}, std::multiplies<>{}, [](const auto& s) { return s.len; }); } std::size_t shape_transform_descriptor::elements() const { return transform_accumulate(dimensions.begin(), dimensions.end(), std::size_t{1}, std::multiplies<>{}, [](const auto& s) { return s.len(); }); } std::vector shape_transform_descriptor::common_dims(const std::vector& input_dims) const { std::vector result; for(const auto& d : dimensions) { std::transform(d.subdimensions.begin(), d.subdimensions.end(), std::back_inserter(result), [&](const dimension::sub& s) { return get_len(s, input_dims); }); } if(result.empty()) result.resize(rank, 1); return result; } std::size_t shape_transform_descriptor::common_rank() const { return transform_accumulate(dimensions.begin(), dimensions.end(), std::size_t{0}, std::plus<>{}, [&](const dimension& d) { return d.subdimensions.size(); }); } const std::vector& shape_transform_descriptor::dimension::sub::origin_axis() const { return axis.empty() ? hidden_axis : axis; } bool shape_transform_descriptor::dimension::sub::has_hidden_axis() const { return axis.empty() and not hidden_axis.empty(); } void shape_transform_descriptor::dimension::sub::add_split_axis(std::size_t i) { if(not axis.empty()) axis.push_back(i); if(not hidden_axis.empty()) hidden_axis.push_back(i); } void shape_transform_descriptor::dimension::sub::expose() { if(has_hidden_axis()) { axis = hidden_axis; hidden_axis.clear(); } } void shape_transform_descriptor::dimension::sub::hide() { if(not has_hidden_axis()) { hidden_axis = axis; axis.clear(); } } bool operator==(const dimension::sub& x, const dimension::sub& y) { return by(std::equal_to<>{}, [](const dimension::sub& s) { return std::tie(s.len, s.axis, s.hidden_axis); })(x, y); } bool operator!=(const dimension::sub& x, const dimension::sub& y) { return not(x == y); } std::ostream& operator<<(std::ostream& os, const dimension::sub& x) { os << x.len << ":" << to_string_range(x.axis, "x"); if(not x.hidden_axis.empty()) os << "$" << to_string_range(x.hidden_axis, "x"); return os; } bool operator==(const dimension& x, const dimension& y) { return x.subdimensions == y.subdimensions; } bool operator!=(const dimension& x, const dimension& y) { return not(x == y); } std::ostream& operator<<(std::ostream& os, const dimension& x) { os << '[' << stream_range(x.subdimensions) << ']'; return os; } bool operator==(const shape_transform_descriptor& x, const shape_transform_descriptor& y) { return by(std::equal_to<>{}, [](const shape_transform_descriptor& sd) { return std::tie(sd.dimensions, sd.rank); })(x, y); } bool operator!=(const shape_transform_descriptor& x, const shape_transform_descriptor& y) { return not(x == y); } std::ostream& operator<<(std::ostream& os, const shape_transform_descriptor& x) { stream_write_value(os, x.dimensions); return os; } std::vector optimize_shape_transforms(const std::vector& dims, const std::vector& ops) { auto sd = shape_transform_descriptor::create(dims, ops); if(sd.empty()) return ops; return sd.generate(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/simplify_algebra.cpp000066400000000000000000002400361510465702400213510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static auto lit_broadcast() { return match::any_of(match::is_constant(), match::name("broadcast")); } static auto not_lit_broadcast() { return match::none_of(match::is_constant(), match::name("broadcast")); } static auto op_lit_broadcast(std::string op, std::string x, std::string y) { return match::name(std::move(op))(match::either_arg(0, 1)( lit_broadcast().bind(std::move(x)), not_lit_broadcast().bind(std::move(y)))); } static auto conv_const_weights() { return match::name("convolution")( match::used_once(), match::args(match::none_of(match::is_constant()), match::is_constant().bind("w"))); } static auto from_int4() { return match::make_predicate_matcher([](instruction_ref start) { return fix([&](auto self, instruction_ref ins) { auto alias = instruction::get_output_alias(ins); if(contains({"reshape", "dequantizelinear"}, alias->name())) return self(alias->inputs().front()); if(alias->name() == "concat") return all_of(alias->inputs(), self); return alias->name() == "unpack_int4"; })(start); }); } static auto not_from_int4() { return match::none_of(from_int4()); } static auto reduction() { return match::name_contains("reduce"); } // Check that we wont concat across a broadcasted axis, since this leads to bad const folding template static bool concat_const_foldable(Iterator start, Iterator last, std::size_t iaxis) { auto n = (*start)->inputs().size(); return all_of(range(n), [&](auto i) { if(not std::all_of( start, last, [&](instruction_ref x) { return x->inputs().at(i)->can_eval(); })) return true; // Its ok if they are all scalars, TODO: Check if the axis is the same dim if(std::all_of(start, last, [&](instruction_ref x) { return x->inputs().at(i)->get_shape().scalar(); })) return true; // TODO: Allow concat across broadcasted axis if all them are the same size return std::none_of(start, last, [&](instruction_ref x) { auto s = x->inputs().at(i)->get_shape(); return s.strides()[iaxis] == 0; }); }); } // conv(x, w) * a => conv(x, a * w) struct find_mul_conv { auto matcher() const { return match::name("mul")( match::either_arg(0, 1)(conv_const_weights().bind("conv"), match::name("broadcast", "multibroadcast").bind("a"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto conv_ins = r.instructions["conv"]; auto a_ins = r.instructions["a"]; auto w_ins = r.instructions["w"]; const auto& a_input_lens = a_ins->inputs().front()->get_shape().lens(); std::size_t num_not_one_dims = std::count_if( a_input_lens.cbegin(), a_input_lens.cend(), [](auto dim) { return dim != 1; }); if(num_not_one_dims > 1) return; // check broadcasted along channels const auto& a_lens = a_ins->get_shape().lens(); const auto& a_strides = a_ins->get_shape().strides(); auto is_broadcasted_axis = [](auto len, auto stride) { return len == 1 or stride == 0; }; if(a_strides.at(1) != 1) return; if(not is_broadcasted_axis(a_lens.front(), a_strides.front())) return; if(not std::equal(a_lens.begin() + 2, a_lens.end(), a_strides.begin() + 2, a_strides.end(), is_broadcasted_axis)) return; auto sq = m.insert_instruction(ins, make_op("squeeze"), a_ins->inputs().front()); auto new_a = m.insert_instruction( ins, make_op("broadcast", {{"axis", 0}, {"out_lens", w_ins->get_shape().lens()}}), sq); auto new_mul = m.insert_instruction(ins, make_op("mul"), new_a, w_ins); auto new_conv = m.insert_instruction( ins, conv_ins->get_operator(), conv_ins->inputs().front(), new_mul); m.replace_instruction(ins, new_conv); } }; struct find_mul_slice_conv { static auto conv() { return match::name("convolution")( match::all_of[match::outputs()](match::name("slice")), match::args(match::any(), match::is_constant().bind("w"))); } auto matcher() const { return match::name("mul")(match::either_arg(0, 1)( match::name("slice")(match::used_once(), match::arg(0)(conv().bind("conv"))) .bind("slice"), match::name("broadcast")(match::is_constant()).bind("a"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto slice_ins = r.instructions["slice"]; auto conv_ins = r.instructions["conv"]; auto a_ins = r.instructions["a"]; auto w_ins = r.instructions["w"]; auto broadcast_op = any_cast(a_ins->get_operator()); if(broadcast_op.axis != 1) return; auto slice_op = any_cast(slice_ins->get_operator()); if(slice_op.axes.size() != 1) return; if(slice_op.axes.front() != 1) return; auto slice_idx = std::distance(conv_ins, slice_ins); if(std::any_of(conv_ins->outputs().begin(), conv_ins->outputs().end(), [&](auto i) { if(i == slice_ins) return false; if(std::distance(conv_ins, i) < slice_idx) return true; auto sop = any_cast(i->get_operator()); if(sop.axes != slice_op.axes) return true; if(std::max(sop.starts.front(), slice_op.starts.front()) < std::min(sop.ends.front(), slice_op.ends.front())) return true; return false; })) return; auto w_slice_op = slice_op; w_slice_op.axes = {0}; auto slice_w_ins = m.insert_instruction(ins, w_slice_op, w_ins); auto new_a = m.insert_instruction( ins, make_op("broadcast", {{"axis", 0}, {"out_lens", slice_w_ins->get_shape().lens()}}), a_ins->inputs().front()); auto new_mul = m.insert_instruction(ins, make_op("mul"), new_a, slice_w_ins); std::vector sliced_weights; if(slice_op.starts.front() != 0) sliced_weights.push_back(m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", slice_op.starts}}), w_ins)); sliced_weights.push_back(new_mul); int64_t end_axis = w_ins->get_shape().lens().at(0); if(slice_op.ends.front() != end_axis) sliced_weights.push_back(m.insert_instruction( ins, make_op("slice", {{"axes", {0}}, {"starts", slice_op.ends}, {"ends", {end_axis}}}), w_ins)); auto new_weights = m.insert_instruction(ins, make_op("concat", {{"axis", 0}}), sliced_weights); auto new_conv = m.insert_instruction( ins, conv_ins->get_operator(), conv_ins->inputs().front(), new_weights); assert(conv_ins->get_shape() == new_conv->get_shape()); auto slice1 = m.insert_instruction(ins, slice_op, new_conv); assert(ins->get_shape().lens() == slice1->get_shape().lens()); m.replace_instruction(ins, slice1); // TODO: Check each slice doesn't overlap and that it occurs after slice_ins auto outputs = conv_ins->outputs(); for(auto output : outputs) if(output != slice_ins) instruction::replace_argument(output, conv_ins, new_conv); } }; struct find_mul_dot { auto matcher() const { auto constant = match::is_constant(not_from_int4()); auto is_dot_const_inputs = match::name("dot")(match::any_of[match::inputs()](constant), match::used_once()); return match::name("mul")(match::either_arg(0, 1)( is_dot_const_inputs.bind("dot"), match::name("broadcast", "multibroadcast").bind("c"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto dot_ins = r.instructions["dot"]; auto a_ins = dot_ins->inputs()[0]; auto b_ins = dot_ins->inputs()[1]; auto c_ins = r.instructions["c"]; const auto& c_strides = c_ins->get_shape().strides(); // There should only be one stride that is not zero if(std::count_if(c_strides.begin(), c_strides.end(), [](auto s) { return s != 0; }) > 1) return; auto add_mul_const = [&](instruction_ref x_ins) { if(not x_ins->can_eval()) return m.end(); auto broadcast_v = c_ins->get_operator().to_value(); broadcast_v["out_lens"] = x_ins->get_shape().lens(); auto cb_ins = m.insert_instruction(ins, make_op(c_ins->name(), broadcast_v), c_ins->inputs()); return m.insert_instruction(ins, make_op("mul"), x_ins, cb_ins); }; if(c_strides.back() == 1) { b_ins = add_mul_const(b_ins); } else if(c_strides[c_strides.size() - 2] == 1) { a_ins = add_mul_const(a_ins); } else if(c_ins->get_shape().scalar()) { if(a_ins->can_eval()) a_ins = add_mul_const(a_ins); else b_ins = add_mul_const(b_ins); } else { return; } if(contains({a_ins, b_ins}, m.end())) return; m.replace_instruction(ins, make_op("dot"), a_ins, b_ins); } }; /* Moves the slice on the output of the Dot operation to slices on the inputs of the Dot operation to avoid computing redundant values. e.g. slice(gemm(a, b)) --> gemm(slice(a), slice(b)) */ struct find_dot_slice { auto matcher() const { return match::name("slice")( match::args(match::name("dot", "quant_dot")(match::used_once()).bind("dot_ins"))); } void apply(module& m, const match::matcher_result& r) const { auto slice_ins = r.result; auto dot_ins = r.instructions["dot_ins"]; auto slice_op = slice_ins->normalized_operator().to_value(); auto axes = slice_op["axes"].to_vector(); auto starts = slice_op["starts"].to_vector(); auto ends = slice_op["ends"].to_vector(); assert(starts.size() == ends.size() and starts.size() == axes.size()); auto has_neg_vals = [](auto vec) { return std::any_of(vec.begin(), vec.end(), [](auto i) { return i < 0; }); }; if(has_neg_vals(starts) or has_neg_vals(ends) or has_neg_vals(axes)) { MIGRAPHX_THROW("FIND_DOT_SLICE: slice is not normalized."); } auto dot_inputs = dot_ins->inputs(); auto num_batch_dims = dot_ins->get_shape().lens().size() - 2; std::vector slice_axes_1, starts_1, ends_1; // NOLINT std::vector slice_axes_2, starts_2, ends_2; // NOLINT for(auto i : range(axes.size())) { if(axes[i] < num_batch_dims) { slice_axes_1.push_back(axes[i]); starts_1.push_back(starts[i]); ends_1.push_back(ends[i]); slice_axes_2.push_back(axes[i]); starts_2.push_back(starts[i]); ends_2.push_back(ends[i]); } else if(axes[i] == num_batch_dims) { slice_axes_1.push_back(axes[i]); starts_1.push_back(starts[i]); ends_1.push_back(ends[i]); } else if(axes[i] == num_batch_dims + 1) { slice_axes_2.push_back(axes[i]); starts_2.push_back(starts[i]); ends_2.push_back(ends[i]); } else { MIGRAPHX_THROW("FIND_DOT_SLICE: invalid case"); } } auto slice_1 = dot_inputs.at(0); if(not slice_axes_1.empty()) { slice_1 = m.insert_instruction( slice_ins, migraphx::make_op("slice", {{"axes", slice_axes_1}, {"starts", starts_1}, {"ends", ends_1}}), dot_inputs.at(0)); } auto slice_2 = dot_inputs.at(1); if(not slice_axes_2.empty()) { slice_2 = m.insert_instruction( slice_ins, migraphx::make_op("slice", {{"axes", slice_axes_2}, {"starts", starts_2}, {"ends", ends_2}}), dot_inputs.at(1)); } m.replace_instruction(slice_ins, dot_ins->get_operator(), {slice_1, slice_2}); } }; struct find_dot_mul { auto matcher() const { auto const_broadcast = match::name("broadcast", "multibroadcast")(match::is_constant()); auto mul = match::name("mul")( match::used_once(), match::either_arg(0, 1)(const_broadcast.bind("d"), match::none_of(match::is_constant()).bind("z"))); return match::name("dot")( match::either_arg(0, 1)(mul, match::is_constant(not_from_int4()).bind("c"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto a_ins = ins->inputs()[0]; auto b_ins = ins->inputs()[1]; auto d_ins = r.instructions["d"]; auto c_ins = r.instructions["c"]; auto z_ins = r.instructions["z"]; const auto& d_strides = d_ins->get_shape().strides(); // There should only be one stride that is not zero if(std::count_if(d_strides.begin(), d_strides.end(), [](auto s) { return s != 0; }) > 1) return; if(not d_ins->get_shape().scalar()) { if(d_strides.back() == 1 and not b_ins->can_eval()) return; if(d_strides[d_strides.size() - 2] == 1 and not a_ins->can_eval()) return; } auto broadcast_v = d_ins->get_operator().to_value(); auto c_lens = c_ins->get_shape().lens(); std::vector permutation(c_lens.size()); std::iota(permutation.begin(), permutation.end(), 0); std::swap(permutation.back(), permutation[permutation.size() - 2]); c_lens = reorder_dims(c_lens, permutation); broadcast_v["out_lens"] = c_lens; auto db_ins = m.insert_instruction(ins, make_op(d_ins->name(), broadcast_v), d_ins->inputs()); auto db_transpose_ins = m.insert_instruction(ins, make_op("transpose", {{"permutation", permutation}}), db_ins); auto cd_ins = m.insert_instruction(ins, make_op("mul"), c_ins, db_transpose_ins); if(c_ins == b_ins) { a_ins = z_ins; b_ins = cd_ins; } else { a_ins = cd_ins; b_ins = z_ins; } m.replace_instruction(ins, make_op("dot"), a_ins, b_ins); } }; // ****************************** // a * (x + b) => a * x + a * b // ****************************** // When a * (x + b) is followed by another add of constant, then the // additional add can be const folded. Also, better fusions can be applied // when the add comes after. struct find_mul_add { auto matcher() const { return match::name("mul")(match::either_arg(0, 1)( match::name("add")( match::either_arg(0, 1)( match::any().bind("x"), match::any_of(conv_const_weights(), match::is_constant()).bind("b")), match::none_of(match::args(match::is_constant(), match::is_constant())), match::used_once()), match::is_constant().bind("a"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto a_ins = r.instructions["a"]; auto b_ins = r.instructions["b"]; auto x_ins = r.instructions["x"]; assert(x_ins != b_ins); auto ax_ins = m.insert_instruction(ins, make_op("mul"), a_ins, x_ins); auto ab_ins = m.insert_instruction(ins, make_op("mul"), a_ins, b_ins); m.replace_instruction(ins, make_op("add"), ax_ins, ab_ins); } }; struct find_dot_add { auto matcher() const { return match::name("dot")(match::either_arg(0, 1)( match::name("add")( match::either_arg(0, 1)(match::any().bind("x"), match::any_of(match::is_constant()).bind("b")), match::none_of(match::args(match::is_constant(), match::is_constant())), match::used_once()), match::is_constant().bind("a"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto a_ins = r.instructions["a"]; auto b_ins = r.instructions["b"]; auto x_ins = r.instructions["x"]; assert(x_ins != b_ins); const bool flipped = a_ins == ins->inputs().back(); auto insert_dot = [&](auto x, auto y) { if(flipped) return m.insert_instruction(ins, make_op("dot"), y, x); else return m.insert_instruction(ins, make_op("dot"), x, y); }; auto ax_ins = insert_dot(a_ins, x_ins); auto ab_ins = insert_dot(a_ins, b_ins); m.replace_instruction(ins, make_op("add"), ax_ins, ab_ins); } }; struct find_conv_add { auto matcher() const { auto add = match::name("add")( match::either_arg(0, 1)(match::any().bind("x"), match::any_of(match::is_constant()).bind("a")), match::used_once()); return match::name("convolution")(match::used_once(), match::args(add, match::is_constant().bind("w"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto a_ins = r.instructions["a"]; auto x_ins = r.instructions["x"]; auto w_ins = r.instructions["w"]; auto conv1 = m.insert_instruction(ins, ins->get_operator(), a_ins, w_ins); auto conv2 = m.insert_instruction(ins, ins->get_operator(), x_ins, w_ins); m.replace_instruction(ins, make_op("add"), conv1, conv2); } }; struct find_add_lit_broadcast { auto matcher() const { return match::name("add")( match::either_arg(0, 1)(op_lit_broadcast("add", "a", "x"), lit_broadcast().bind("b"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto x_ins = r.instructions["x"]; auto a_ins = r.instructions["a"]; auto b_ins = r.instructions["b"]; auto sumab = m.insert_instruction(ins, make_op("add"), a_ins, b_ins); m.replace_instruction(ins, make_op("add"), x_ins, sumab); } }; struct find_double_add_lit_broadcast { auto matcher() const { return match::name("add")( match::args(op_lit_broadcast("add", "a", "x"), op_lit_broadcast("add", "b", "y"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto x_ins = r.instructions["x"]; auto y_ins = r.instructions["y"]; auto a_ins = r.instructions["a"]; auto b_ins = r.instructions["b"]; instruction_ref sumab; if(a_ins->name() == "broadcast" and b_ins->name() == "broadcast") { if(a_ins->inputs().at(0)->get_shape() != b_ins->inputs().at(0)->get_shape()) return; auto op = a_ins->get_operator(); auto presum = m.insert_instruction( ins, make_op("add"), a_ins->inputs().at(0), b_ins->inputs().at(0)); sumab = m.insert_instruction(ins, op, presum); } else { sumab = m.insert_instruction(ins, make_op("add"), a_ins, b_ins); } auto sumxy = m.insert_instruction(ins, make_op("add"), x_ins, y_ins); m.replace_instruction(ins, make_op("add"), sumxy, sumab); } }; /// Find elementswise operators that have all broadcast inputs. It then /// rewrites the elementwise to do the computation on the non-broadcasted /// axes, and then broadcast that result. struct find_inner_broadcast { auto matcher() const { return pointwise(match::all_of[match::inputs()](match::broadcast())); } static auto get_non_broadcast_input(instruction_ref ins) { if(ins->inputs().size() != 1) return ins; auto input = ins->inputs().front(); if(contains(input->name(), "broadcast")) return get_non_broadcast_input(input); return input; } static bool is_unsqueeze_needed_for_multibroadcast(const shape& input, const shape& output) { if(input.elements() == 1) return false; auto shift = output.ndim() - input.ndim(); if(shift == 0) return false; if(std::equal(input.lens().begin(), input.lens().end(), output.lens().begin() + shift, output.lens().end())) { return std::all_of(output.lens().begin(), output.lens().begin() + shift, [](auto x) { return x == 1; }); } return true; } // Simple case void apply_same_broadcasts(module& m, instruction_ref ins) const { const auto& broadcasts = ins->inputs(); // Scalars can have different ndim, so find the largest ndim input auto max_broadcast = *std::max_element( broadcasts.begin(), broadcasts.end(), by(std::less<>{}, [](instruction_ref broadcast) { return get_non_broadcast_input(broadcast)->get_shape().ndim(); })); auto max_ndim = max_broadcast->get_shape().ndim(); std::vector inputs; std::transform(broadcasts.begin(), broadcasts.end(), std::back_inserter(inputs), [&](instruction_ref broadcast) { auto input = get_non_broadcast_input(broadcast); auto s = input->get_shape(); // If scalar doesnt match the other input dims then add a squeeze if(s.elements() == 1 and s.ndim() > 1 and s.ndim() != max_ndim) return m.insert_instruction(broadcast, make_op("squeeze"), input); return input; }); auto op = insert_common_op(m, ins, ins->get_operator(), inputs); // Find broadcast op on a non-scalar instruction if it exists auto first = std::find_if(broadcasts.begin(), broadcasts.end(), [&](instruction_ref broadcast) { return not broadcast->get_shape().scalar(); }); if(first != broadcasts.end()) { m.replace_instruction(ins, (*first)->get_operator(), op); } else { m.replace_instruction(ins, broadcasts.front()->get_operator(), op); } } void apply_diff_broadcasts(module& m, instruction_ref ins) const { const auto& broadcasts = ins->inputs(); auto ndim = ins->get_shape().ndim(); // Compute the inner dimensions and axes that the computation will // use. Also compute the axes that will be broadcasted std::vector idims; std::vector iaxes; std::vector axes; for(auto axis : range(ndim)) { if(std::all_of(broadcasts.begin(), broadcasts.end(), [&](instruction_ref i) { auto s = i->get_shape(); return s.lens()[axis] == 1 or s.strides()[axis] == 0; })) { axes.push_back(axis); } else { iaxes.push_back(axis); idims.push_back(ins->get_shape().lens()[axis]); } } // If the inner axes are the same as the original operator then // there is no reason to do this transformation. if(iaxes.size() == ndim) return; std::vector inputs; std::transform( broadcasts.begin(), broadcasts.end(), std::back_inserter(inputs), [&](instruction_ref broadcast) { auto input = broadcast->inputs().front(); auto s = input->get_shape(); // If its a single element then just return that as an input if(s.elements() == 1) { if(s.lens().size() > 1) return m.insert_instruction(broadcast, make_op("squeeze"), input); return input; } // Find how the axes are shifted from the broadcast std::int64_t shift = ndim - s.ndim(); if(broadcast->name() == "broadcast") shift = broadcast->get_operator().to_value()["axis"].to(); // Compute the squeeze axes to be used by taking the inner // axes and shifting to what the axes will be on the // input std::vector sq_axes; for(auto axis : axes) { auto iaxis = axis - shift; if(iaxis < 0) continue; if(iaxis >= s.ndim()) continue; sq_axes.push_back(iaxis); } instruction_ref result = input; if(not sq_axes.empty()) result = m.insert_instruction( broadcast, make_op("squeeze", {{"axes", sq_axes}}), result); // If the number of dimension are still smaller than the // number of inner axes, then we need to insert a // broadcast to have the same dimensions for all inputs. if(result->get_shape().ndim() < iaxes.size()) { // We find the first inner axis that can be mapped to the input auto start_axis = std::find_if(iaxes.begin(), iaxes.end(), [&](auto x) { return x >= shift; }) - iaxes.begin(); result = m.insert_instruction( broadcast, make_op("broadcast", {{"axis", start_axis}, {"out_lens", idims}}), result); } return result; }); auto op = insert_common_op(m, ins, ins->get_operator(), inputs); if(iaxes.size() == 1) { m.replace_instruction( ins, make_op("broadcast", {{"axis", iaxes.front()}, {"out_lens", ins->get_shape().lens()}}), op); } else { auto unsqueeze = is_unsqueeze_needed_for_multibroadcast(op->get_shape(), ins->get_shape()) ? m.insert_instruction(ins, make_op("unsqueeze", {{"axes", axes}}), op) : op; m.replace_instruction( ins, make_op("multibroadcast", {{"out_lens", ins->get_shape().lens()}}), unsqueeze); } } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; if(ins->get_operator().name() == "layout") return; const auto& broadcasts = ins->inputs(); if(broadcasts.empty()) return; // Skip if different data types are used if(any_of(broadcasts, [&](auto i) { return i->get_shape().type() != broadcasts.front()->get_shape().type(); })) return; // All inputs should have less elements if(not all_of(broadcasts, [&](instruction_ref broadcast) { auto input = broadcast->inputs().front(); return input->get_shape().elements() < ins->get_shape().elements(); })) return; // Find first broadcast that is not a scalar auto first = std::find_if(broadcasts.begin(), broadcasts.end(), [&](instruction_ref broadcast) { return not broadcast->get_shape().scalar(); }); // Try to see if we can do a simple case that just applies the op to // the inputs of the broadcasts, and then just put that same // broadcast after the op. For this case we need each of the // broadcasts to be the same and the inputs to have the same dimesion // (or be scalar). const bool same_broadcasts = std::all_of(first, broadcasts.end(), [&](instruction_ref broadcast) { if(broadcast->get_operator() != (*first)->get_operator()) return false; auto s1 = get_non_broadcast_input(broadcast)->get_shape(); auto s2 = get_non_broadcast_input(*first)->get_shape(); if(s1.elements() == 1) return true; return s1.lens() == s2.lens(); }); if(same_broadcasts) { apply_same_broadcasts(m, ins); } // Skip if any input to the broadcasted inputs is already broadcasted // as the below algorithm may not be able to handle such case. else if(std::none_of(broadcasts.begin(), broadcasts.end(), [](instruction_ref broadcast) { return broadcast->inputs().front()->get_shape().broadcasted(); })) { apply_diff_broadcasts(m, ins); } } }; struct find_dot_broadcast { auto matcher() const { return match::name("dot")(match::all_of[match::inputs()](match::broadcast())); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto a = ins->inputs()[0]; auto b = ins->inputs()[1]; if(ins->get_shape().lens().size() < 3) return; auto nbatch_axes = ins->get_shape().lens().size() - 2; const auto& a_strides = a->get_shape().strides(); const auto& b_strides = b->get_shape().strides(); // Find leading batch axes that are broadcasted auto p = std::mismatch(a_strides.begin(), a_strides.begin() + nbatch_axes, b_strides.begin(), b_strides.begin() + nbatch_axes, [](auto astride, auto bstride) { return astride == 0 and bstride == 0; }); auto naxes = p.first - a_strides.begin(); assert(naxes <= nbatch_axes); std::vector axes(naxes); std::iota(axes.begin(), axes.end(), 0); auto insert_broadcast = [&](instruction_ref x_ins) -> instruction_ref { auto input = x_ins->inputs()[0]; std::vector lens(x_ins->get_shape().lens().begin() + naxes, x_ins->get_shape().lens().end()); if(input->get_shape().lens() == lens) return input; auto input_naxis = input->get_shape().lens().size(); auto new_bc_naxis = lens.size(); if(input_naxis > new_bc_naxis) { std::vector axes_to_sq(input_naxis - new_bc_naxis); std::iota(axes_to_sq.begin(), axes_to_sq.end(), 0); input = m.insert_instruction(ins, make_op("squeeze", {{"axes", axes_to_sq}}), input); } if(x_ins->name() == "multibroadcast") { return m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", lens}}), input); } else if(x_ins->name() == "broadcast") { auto v = x_ins->get_operator().to_value(); auto axis = v.at("axis").to() - naxes; return m.insert_instruction( ins, make_op("broadcast", {{"axis", axis}, {"out_lens", lens}}), input); } assert(false); return m.end(); }; auto a1 = insert_broadcast(a); auto b1 = insert_broadcast(b); auto dot = m.insert_instruction(ins, make_op("dot"), a1, b1); auto broadcast = m.insert_instruction( ins, make_op("multibroadcast", {{"out_lens", ins->get_shape().lens()}}), dot); m.replace_instruction(ins, broadcast); } }; struct find_concat_op { auto matcher() const { return match::name("concat")(match::any_of[match::inputs()]( match::any_of(match::pointwise(), match::name("broadcast", "multibroadcast", "unpack_int4")), match::used_once())); } template static std::vector get_output_lens(Iterator start, Iterator last, std::size_t axis) { assert(start != last); std::size_t dim = 0; for(auto ins : range(start, last)) { dim += ins->get_shape().lens().at(axis); } auto lens = (*start)->get_shape().lens(); lens[axis] = dim; return lens; } static bool is_valid_op(const operation& op) { return contains({"broadcast", "multibroadcast", "unpack_int4"}, op.name()) or (op.attributes().contains("pointwise") and op.name() != "quantizelinear"); } static bool is_valid_concat(std::vector ins, size_t axis) { auto concat_lens = ins.front()->get_shape().lens(); concat_lens.erase(concat_lens.begin() + axis); return std::all_of(ins.begin(), ins.end(), [&](auto i) { auto lens = i->get_shape().lens(); lens.erase(lens.begin() + axis); return lens == concat_lens; }); } static bool rejected_inputs(const std::vector& inputs) { if(inputs.empty()) return true; if(inputs.size() < 3) return false; auto nonconst = std::count_if( inputs.begin(), inputs.end(), [](instruction_ref ins) { return not ins->can_eval(); }); return nonconst > 2; } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto axis = any_cast(ins->get_operator()).axis; auto each = [&](auto start, auto last) -> std::vector { if(std::distance(start, last) < 2) return {start, last}; auto x = *start; if(std::any_of(start, last, [](instruction_ref x) { return x->outputs().size() > 1 or rejected_inputs(x->inputs()); })) return {start, last}; auto op = x->get_operator(); if(not is_valid_op(op)) return {start, last}; auto iaxis = axis; // Adjust broadcast lens if(op.name() == "broadcast") { auto b = any_cast(op); if(b.axis != iaxis) return {start, last}; b.broadcast_lens = get_output_lens(start, last, iaxis); op = b; iaxis = 0; } else if(op.name() == "multibroadcast") { shape bshape = (*start)->get_shape(); auto input = (*start)->inputs()[0]; if(iaxis >= bshape.strides().size() or bshape.strides()[iaxis] == 0) return {start, last}; op.from_value({{"out_lens", get_output_lens(start, last, iaxis)}}); auto delta = bshape.lens().size() - input->get_shape().lens().size(); iaxis -= delta; } if(not concat_const_foldable(start, last, iaxis)) return {start, last}; std::vector concats; for(std::size_t i = 0; i < x->inputs().size(); i++) { std::vector inputs; std::transform(start, last, std::back_inserter(inputs), [&](auto j) { return j->inputs().at(i); }); if(not is_valid_concat(inputs, iaxis)) return {start, last}; auto concat = m.insert_instruction(ins, make_op("concat", {{"axis", iaxis}}), inputs); concats.push_back(concat); } auto y = m.insert_instruction(ins, op, concats); return {y}; }; std::vector args; auto update_args = [&](auto start, auto last) { auto x = each(start, last); args.insert(args.end(), x.begin(), x.end()); }; auto pred = [](auto i, auto j) { return i->get_operator() == j->get_operator() and i->inputs().size() == i->inputs().size() and i->outputs().size() == i->outputs().size(); }; group_unique(ins->inputs().begin(), ins->inputs().end(), update_args, pred); if(args.size() == 1) m.replace_instruction(ins, args.front()); else m.replace_instruction(ins, make_op("concat", {{"axis", axis}}), args); } }; struct find_concat_conv { auto matcher() const { return match::name("concat")( match::all_of[match::inputs()](match::used_once(), match::name("convolution"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto axis = ins->get_operator().to_value()["axis"].to(); if(axis != 1) return; if(ins->inputs().empty()) return; auto conv = ins->inputs().front()->get_operator(); if(std::any_of(ins->inputs().begin(), ins->inputs().end(), [&](auto conv_ins) { return conv_ins->get_operator() != conv; })) return; std::vector inputs; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(inputs), [](auto conv_ins) { return conv_ins->inputs()[0]; }); if(std::any_of(inputs.begin(), inputs.end(), [&](auto input) { return input->get_shape() != inputs.front()->get_shape(); })) return; std::vector weights; std::transform(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(weights), [](auto conv_ins) { return conv_ins->inputs()[1]; }); if(std::any_of(weights.begin(), weights.end(), [&](auto w) { return w->get_shape() != weights.front()->get_shape(); })) return; auto original_group = from_value(conv.to_value()["group"]); auto x = m.insert_instruction(ins, make_op("concat", {{"axis", 1}}), inputs); auto w = m.insert_instruction(ins, make_op("concat", {{"axis", 0}}), weights); conv.from_value({{"group", original_group * inputs.size()}}); m.replace_instruction(ins, conv, x, w); } }; static void move_instructions_back(module& m, instruction_ref pos, std::vector inss) { auto start = range(m.begin(), pos); for(auto ins : iterator_for(start)) { auto it = std::find(inss.begin(), inss.end(), ins); if(it != inss.end()) inss.erase(it); } for(auto ins : inss) { if(not m.has_instruction(ins)) continue; move_instructions_back(m, pos, ins->inputs()); m.move_instruction(ins, pos); } } /** Search for multiple "slice" instructions in an instruction's outputs * which are contiguous slices of the same tensor. */ static std::vector get_splits(instruction_ref ins) { std::vector result; std::copy_if(ins->outputs().begin(), ins->outputs().end(), std::back_inserter(result), [&](auto i) { return i->name() == "slice"; }); if(result.size() < 2) return {}; auto get_slice = [](auto& i) -> auto& { return any_cast(i->get_operator()); }; auto&& axes = get_slice(result.front()).axes; // "slice" instructions must all have the same axes if(std::any_of(result.begin(), result.end(), [&](auto i) { return get_slice(i).axes != axes; })) return {}; auto get_start = [&](auto& i) -> auto& { return get_slice(i).starts; }; auto get_end = [&](auto& i) -> auto& { return get_slice(i).ends; }; // Sort the "slice" instructions in order of starts std::sort( result.begin(), result.end(), [&](auto x, auto y) { return get_start(x) < get_start(y); }); if(std::any_of(get_start(result.front()).begin(), get_start(result.front()).end(), [&](auto i) { return i != 0; })) return {}; // one slice must "start" where the last slice "end" auto it = std::adjacent_find( result.begin(), result.end(), [&](auto x, auto y) { return get_end(x) != get_start(y); }); if(it != result.end()) return {}; for(std::size_t i = 0; i < axes.size(); i++) { auto axis = axes[i]; if(ins->get_shape().lens()[axis] != get_slice(result.back()).ends[i]) return {}; } return result; } struct find_splits { auto matcher() const { // match instruction with outputs of pointwise fusion, pointwise op with 1 or 2 args, or // reduction op auto pointwise_reduction = match::any_of[match::outputs()]( match::name("pointwise"), match::pointwise(match::any_of(match::nargs(1), match::nargs(2))), reduction()); // match instruction with slice output to pointwise_reduction return match::any( match::any_of[match::outputs()](match::name("slice")(pointwise_reduction))); } /** * Check if we can reach start from end by going through inputs of end. * `root` is the instruction before the slice instructions (what find_splits matcher matches). * This function is called by split_groups_are_dependent() many times depending on the size * of the split groups. */ static bool is_dependent(const module& m, instruction_ref root, instruction_ref start, instruction_ref end) { if(std::distance(root, end) < std::distance(root, start)) { return false; } return reaches(start, end, &m); } static auto get_matching_ins(instruction_ref split, instruction_ref out) { return std::find_if(split->outputs().begin(), split->outputs().end(), [&](auto i) { if(i->get_operator() == out->get_operator()) { if(i->name() == "pointwise") { return (*(i->module_inputs().front()) == *(out->module_inputs().front())); } return true; } return false; }); } /** * Returns empty vector if split group not found */ static std::vector make_group(const module& m, instruction_ref root, instruction_ref out, const std::vector& splits, instruction_ref start_split) { std::vector group; for(auto split : range(splits.cbegin(), splits.cend())) { if(split == start_split) { group.push_back(out); } else { auto it = get_matching_ins(split, out); if(it == split->outputs().end()) return {}; // Bail if there is a duplicate if(contains(group, *it)) return {}; assert((*it)->name() != "slice"); group.push_back(*it); } } // There should be no dependency between instructions in the group if(is_interdependent(group, &m, root)) return {}; return group; } /// Find groups of the same operator after the splits (slice instructions) static std::vector> get_split_groups( const module& m, instruction_ref root, const std::vector& splits) { std::vector> groups; auto split_with_least_outputs = *std::min_element(splits.cbegin(), splits.cend(), [](auto x, auto y) { return x->outputs().size() < y->outputs().size(); }); // Operator must be repeated over all splits, so better to start with split with least // outputs Preserving the order of the groups wrt. the splits for(auto out : split_with_least_outputs->outputs()) { if(out->name() == "slice") continue; std::vector group = make_group(m, root, out, splits, split_with_least_outputs); if(group.size() != splits.size()) continue; groups.push_back(group); } return groups; } /** * If instruction is fusable * start: instruction to check * split_front: slice operator input to start instruction */ bool is_fusable(instruction_ref start, instruction_ref split_front) const { auto op = start->get_operator(); if(contains(op.name(), "reduce")) { auto slc = any_cast(split_front->get_operator()); auto slc_axes = slc.axes; auto reduce_axes = start->get_operator().to_value()["axes"].to_vector(); // axes of slice and reduce op cannot have overlap return std::any_of(slc_axes.begin(), slc_axes.end(), [&](auto axis) { return (std::find(reduce_axes.begin(), reduce_axes.end(), axis) == reduce_axes.end()); }); } else if(op.name() == "pointwise" or op.attributes().contains("pointwise")) { return true; } return false; } /** Get argument index that has the split instruction for a group of instructions * If instructions in a group have different split indexes, return -1. */ int get_binary_op_split_idx(std::vector group, std::vector splits) const { auto first_group_inputs = group.front()->inputs(); auto arg_it = std::find_if(first_group_inputs.begin(), first_group_inputs.end(), [&](auto i) { return std::find(splits.begin(), splits.end(), i) != splits.end(); }); auto split_idx = arg_it - first_group_inputs.begin(); // All splits are at the same input index if(std::all_of(group.begin() + 1, group.end(), [&](auto i) { auto split_idx_input = i->inputs().at(split_idx); return std::find(splits.begin(), splits.end(), split_idx_input) != splits.end(); })) return split_idx; return -1; } /** * Align the arguments of binary commutative instructions so they * have the same operator on the same argument indexes. */ void align_commutative_op_args(module& m, std::vector group, std::vector splits, size_t split_idx) const { auto group_op = group.front()->get_operator(); assert(std::all_of( group.begin(), group.end(), [&](auto i) { return i->get_operator() == group_op; })); for(auto i : group) { if(std::find(splits.begin(), splits.end(), i->inputs().at(split_idx)) == splits.end()) { auto args = i->inputs(); assert(args.size() == 2); std::reverse(args.begin(), args.end()); m.replace_instruction(i, i->get_operator(), args); } } } /** * Check if any split group depends on instructions from another split group. * m : module containing instructions * root : "root" instruction that has contiguous slice instructions as outputs * groups : split groups from get_split_groups */ bool split_groups_are_dependent(const module& m, instruction_ref root, std::vector> groups) const { for(int i = 0; i < groups.size(); ++i) { const auto& group_i = groups.at(i); std::vector> groups_less_i(groups.size() - 1); std::copy(groups.cbegin(), groups.cbegin() + i, groups_less_i.begin()); std::copy(groups.cbegin() + i + 1, groups.cend(), groups_less_i.begin() + i); if(std::any_of(groups_less_i.begin(), groups_less_i.end(), [&](auto group_j) { return std::any_of(group_i.begin(), group_i.end(), [&](auto ins_i) { return std::any_of(group_j.begin(), group_j.end(), [&](auto ins_j) { return is_dependent(m, root, ins_i, ins_j); }); }); })) { return true; } } return false; } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto splits = get_splits(ins); if(splits.empty()) return; auto split_groups = get_split_groups(m, ins, splits); if(split_groups_are_dependent(m, ins, split_groups)) { return; } for(const auto& group : split_groups) { auto start = group.front(); auto split_front = splits.front(); auto op = start->get_operator(); if(not is_fusable(start, split_front)) { continue; } // Make sure there are no duplicates assert(std::none_of( std::next(group.begin()), group.end(), [&](auto i) { return i == start; })); auto split_idx = 0; instruction_ref c = m.end(); if(start->inputs().size() == 1) { c = m.insert_instruction(std::next(ins), op, {ins}, start->module_inputs()); } else if(start->inputs().size() == 2) { assert(not std::none_of(start->inputs().begin(), start->inputs().end(), [](auto i) { return i->name() == "slice"; }) and "one argument must be a split"); auto slice_op = any_cast(splits.front()->get_operator()); assert(not slice_op.axes.empty()); if(slice_op.axes.size() > 1) return; auto concat_axis = slice_op.axes.front(); if(not concat_const_foldable(group.begin(), group.end(), concat_axis)) return; split_idx = get_binary_op_split_idx(group, splits); assert(split_idx < 2); size_t data_idx; if(split_idx < 0 and op.attributes().contains("commutative")) { split_idx = 0; data_idx = 1; align_commutative_op_args(m, group, splits, split_idx); } else if(split_idx < 0) { return; } else { data_idx = split_idx == 0 ? 1 : 0; } std::vector data_args; std::transform(group.begin(), group.end(), std::back_inserter(data_args), [&](auto i) { return i->inputs()[data_idx]; }); // Data arguments must be a constant if(std::any_of(data_args.begin(), data_args.end(), [](auto i) { return not i->can_eval(); })) return; move_instructions_back(m, ins, data_args); // TODO: Check if axises match auto concat = m.insert_instruction( ins, make_op("concat", {{"axis", concat_axis}}), data_args); std::vector args; args.resize(2); args[split_idx] = ins; args[data_idx] = concat; c = m.insert_instruction(std::next(ins), op, {args}, start->module_inputs()); } if(c != m.end()) { for(auto i : group) { auto split = i->inputs()[split_idx]; assert(split->name() == "slice"); m.replace_instruction(i, split->get_operator(), c); } } } } }; /** * Matcher for a sequence of "slice" operations whose outputs are put back * together by a "concat". */ struct find_split_concat { auto matcher() const { auto concat = match::all_of[match::outputs()](match::name("concat")); return match::any(match::any_of[match::outputs()](match::name("slice")(concat))); } void apply(module& m, const match::matcher_result& r) const { // Verifies that the slices meet several conditions: they must all output to the same // concat instruction, slice on the same (1 only) axis, and the end of one slice // must match the start of the next. auto ins = r.result; auto splits = get_splits(ins); if(splits.empty()) return; // Each slice must output to only one instruction if(std::any_of( splits.begin(), splits.end(), [](auto i) { return i->outputs().size() != 1; })) return; // The single output instruction for all items in the list must be the same one auto concat = splits.front()->outputs().front(); if(std::any_of(splits.begin(), splits.end(), [&](auto i) { return i->outputs().front() != concat; })) return; // The axis for the common output instruction must be the same as for the split ops auto concat_op = any_cast(concat->get_operator()); auto split_op = any_cast(splits.front()->get_operator()); if(split_op.axes.size() != 1) return; if(split_op.axes.front() != concat_op.axis) return; // Find where the slices are in the concat instruction's inputs (concat can have // any number of inputs) auto args = concat->inputs(); auto it = std::find_if(args.begin(), args.end(), [&](auto i) { return i == splits.front(); }); // Verify the slices were found, and the list is long enough if(std::distance(it, args.end()) < splits.size()) return; // Don't do anything if the "slice" inputs to the concat op have other operations mixed in // among them if(std::any_of(it, it + splits.size(), [](instruction_ref x) { return x->get_operator().name() != "slice"; })) return; // Check that the slices passed to concat are in order. if(not std::is_sorted(it, it + splits.size(), [](instruction_ref x, instruction_ref y) { auto xop = any_cast(x->get_operator()); auto yop = any_cast(y->get_operator()); return std::tie(xop.starts, xop.ends) < std::tie(yop.starts, yop.ends); })) return; // Perform the substitution *it = splits.front()->inputs().front(); args.erase(std::next(it), it + splits.size()); if(args.size() == 1) m.replace_instruction(concat, args.front()); else m.replace_instruction(concat, concat->get_operator(), args); } }; static bool axis_equal(const std::vector& x, const std::vector& y, std::size_t axis) { return x.size() == y.size() and x.size() > axis and std::equal(x.begin(), x.begin() + axis, y.begin()) and std::equal(x.begin() + axis + 1, x.end(), y.begin() + axis + 1); } static bool axis_shape_equal(const shape& x, const shape& y, std::size_t axis) { // TODO: Check strides return axis_equal(x.lens(), y.lens(), axis); } struct find_add_convs { auto matcher() const { return match::name("add")( match::args(conv_const_weights().bind("a"), conv_const_weights().bind("b"))); } static bool symmetrical_strides(const op::convolution& op) { return op.stride[0] == op.stride[1]; } static std::size_t compute_stride_factor(const op::convolution& x, const op::convolution& y) { if(not symmetrical_strides(x)) return 0; if(not symmetrical_strides(y)) return 0; if((x.stride[0] % y.stride[0]) != 0) return 0; return x.stride[0] / y.stride[0]; } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto a_conv = r.instructions["a"]; auto a_input = a_conv->inputs().at(0); auto a_weights = a_conv->inputs().at(1); auto b_conv = r.instructions["b"]; auto b_input = b_conv->inputs().at(0); auto b_weights = b_conv->inputs().at(1); if(not axis_shape_equal(a_weights->get_shape(), b_weights->get_shape(), 1)) return; auto a_op = any_cast(a_conv->get_operator()); auto b_op = any_cast(b_conv->get_operator()); auto new_op = a_op; if(a_op != b_op) { if(std::tie(a_op.padding, a_op.dilation, a_op.group) == std::tie(b_op.padding, b_op.dilation, b_op.group) and a_weights->get_shape().lens()[2] == 1 and a_weights->get_shape().lens()[3] == 1) { if(a_op.stride < b_op.stride) { auto n = compute_stride_factor(b_op, a_op); if(n == 0) return; new_op = a_op; b_input = m.insert_instruction( ins, make_op("step", {{"axes", {2, 3}}, {"steps", {n, n}}}), b_input); } else if(b_op.stride < a_op.stride) { auto n = compute_stride_factor(a_op, b_op); if(n == 0) return; new_op = b_op; a_input = m.insert_instruction( ins, make_op("step", {{"axes", {2, 3}}, {"steps", {n, n}}}), a_input); } else return; } else return; } auto concat_input = m.insert_instruction(ins, make_op("concat", {{"axis", 1}}), a_input, b_input); auto concat_weights = m.insert_instruction(ins, make_op("concat", {{"axis", 1}}), a_weights, b_weights); m.replace_instruction(ins, new_op, concat_input, concat_weights); } }; MIGRAPHX_PRED_MATCHER(horiz_conv_dot, instruction_ref ins) { auto pred = [&](auto name) { return [=](auto i) { return i->name() == name and i->inputs().front() == ins and i->inputs().at(1)->can_eval(); }; }; auto dots = std::count_if(ins->outputs().begin(), ins->outputs().end(), pred("dot")); auto qdots = std::count_if(ins->outputs().begin(), ins->outputs().end(), pred("quant_dot")); auto convs = std::count_if(ins->outputs().begin(), ins->outputs().end(), pred("convolution")); return (dots >= 2 or convs >= 2 or qdots >= 2); } struct find_conv_dot_horiz_fusion { auto matcher() const { return horiz_conv_dot(); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto pred = [](auto i, auto j) { if(i->get_operator() != j->get_operator()) return false; if(not contains({"quant_dot", "dot", "convolution"}, i->name())) return true; auto x = i->inputs()[1]->get_shape().lens(); auto y = j->inputs()[1]->get_shape().lens(); if(x.size() != y.size()) return false; // Check that non-axes match int axis = 1; if(i->name() == "dot" or i->name() == "quant_dot") { axis = x.size() - 1; } return axis_equal(x, y, axis); }; auto each = [&](auto start, auto last) { if(std::distance(start, last) < 2) return; auto&& name = (*start)->name(); if(not contains({"quant_dot", "dot", "convolution"}, name)) return; auto op = (*start)->get_operator(); int group = 1; if(name == "convolution") group = any_cast(op).group; // Skip group convolution if(group != 1) return; auto input = (*start)->inputs().front(); std::vector args; std::transform( start, last, std::back_inserter(args), [&](auto x) { return x->inputs().at(1); }); int axis = 1; int concat_axis = 0; if(name == "dot" or name == "quant_dot") { axis = int(args.front()->get_shape().lens().size() - 1); concat_axis = axis; } move_instructions_back(m, input, args); // TODO: Check if axes match auto concat = m.insert_instruction(input, make_op("concat", {{"axis", concat_axis}}), args); auto fused = m.insert_instruction(std::next(input), op, input, concat); int64_t offset = 0; for(auto arg : range(start, last)) { auto outputs = arg->outputs(); int64_t len = arg->get_shape().lens()[axis]; m.replace_instruction( arg, make_op("slice", {{"axes", {axis}}, {"starts", {offset}}, {"ends", {offset + len}}}), fused); offset += len; } }; auto outputs = ins->outputs(); group_by(outputs.begin(), outputs.end(), each, pred); } }; struct find_div_const { auto matcher() const { return match::name("div")(match::arg(1)(match::is_constant().bind("c"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto c_ins = r.instructions["c"]; if(shape::is_integral(ins->get_shape().type())) return; auto recip = m.insert_instruction(std::next(c_ins), make_op("recip"), c_ins); auto args = ins->inputs(); m.replace_instruction(ins, make_op("mul"), args.front(), recip); } }; struct find_unit_ops { auto matcher() const { auto mul_1 = match::name("mul")( match::either_arg(0, 1)(match::has_value(1.0f), match::any().bind("x"))); auto div_1 = match::name("div")(match::args(match::any().bind("x"), match::has_value(1.0f))); auto add_0 = match::name("add")( match::either_arg(0, 1)(match::has_value(0.0f, 0, 0), match::any().bind("x"))); auto sub_0 = match::name("sub")(match::args(match::any().bind("x"), match::has_value(0.0f, 0, 0))); return match::any_of(mul_1, div_1, add_0, sub_0); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto c_in = r.instructions["x"]; m.replace_instruction(ins, c_in); } }; struct find_neg_unit_ops { auto matcher() const { auto mul_neg_1 = match::name("mul")( match::either_arg(0, 1)(match::has_value(-1.0f), match::any().bind("x"))); auto div_neg_1 = match::name("div")(match::args(match::any().bind("x"), match::has_value(-1.0f))); auto sub_0 = match::name("sub")(match::args(match::has_value(0.0f, 0, 0), match::any().bind("x"))); return match::any_of(mul_neg_1, div_neg_1, sub_0); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto c_in = r.instructions["x"]; auto neg = m.insert_instruction(ins, make_op("neg"), c_in); m.replace_instruction(ins, neg); } }; struct eliminate_zero_point { auto get_qlinear_ops_names() const { static std::unordered_set qdq_names = {"quantizelinear", "dequantizelinear"}; return qdq_names; } auto matcher() const { return match::name(get_qlinear_ops_names())(match::arg(0)(match::any().bind("x")), match::arg(1)(match::any().bind("scale")), match::arg(2)(match::has_value(0.0f, 0, 0))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto x = r.instructions["x"]; auto scale = r.instructions["scale"]; auto op = ins->get_operator().to_value(); if(ins->get_operator().name() == "quantizelinear") { op["out_type"] = to_value(ins->get_shape().type()); } auto qdq_ins = m.insert_instruction(ins, migraphx::make_op(ins->name(), op), {x, scale}); m.replace_instruction(ins, qdq_ins); } }; struct find_zero_ops { auto matcher() const { auto mul_zero = match::name("mul")( match::either_arg(0, 1)(match::has_value(0.0f, 0, 0).bind("x"), match::any())); auto div_zero = match::name("div")(match::args(match::has_value(0.0f, 0, 0).bind("x"), match::any())); return match::any_of(mul_zero, div_zero); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto zero_ins = r.instructions["x"]; if(zero_ins->get_shape().scalar()) zero_ins = m.insert_instruction( ins, make_op("reshape", {{"dims", ins->get_shape().lens()}}), zero_ins); m.replace_instruction(ins, zero_ins); } }; struct find_sub_const { auto matcher() const { return match::name("sub")(match::arg(1)(match::is_constant().bind("c"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto c_ins = r.instructions["c"]; auto neg = m.insert_instruction(std::next(c_ins), make_op("neg"), c_ins); auto args = ins->inputs(); m.replace_instruction(ins, make_op("add"), args.front(), neg); } }; struct find_rsqrt { auto matcher() const { auto bind_x = match::args(match::any().bind("x")); return match::name("recip")(match::args(match::name("sqrt")(match::used_once(), bind_x))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto x_ins = r.instructions["x"]; m.replace_instruction(ins, make_op("rsqrt"), x_ins); } }; static bool same_ops(const std::vector& vec_ins) { return std::all_of(vec_ins.begin(), vec_ins.end(), [&](auto i) { return i->get_operator() == vec_ins.front()->get_operator(); }); } struct find_split_reshape { auto matcher() const { auto slice_bind_slice = match::arg(0)(match::name("slice").bind("slice")); return match::name("reshape")(match::arg(0)(match::name("contiguous")(slice_bind_slice))) .bind("reshape"); } void apply(module& m, const match::matcher_result& r) const { auto slc = r.instructions["slice"]; auto rsp = r.instructions["reshape"]; auto input = slc->inputs().front(); // Only apply simplification when slices are on a single axis auto axes = any_cast(slc->get_operator()).axes; if(axes.size() > 1) { return; } auto split_outputs = get_splits(input); if(split_outputs.empty()) { return; } // Find all the reshapes (similar to rsp) that can be simplified std::vector conts; std::vector vec_rsp; // Iterate through slice and contiguous outputs to allow simplifications when // slice is followed by multiple reshapes for(auto& i : split_outputs) { std::copy_if(i->outputs().begin(), i->outputs().end(), std::back_inserter(conts), [](auto j) { return j->name() == "contiguous"; }); } for(auto& i : conts) { std::copy_if(i->outputs().begin(), i->outputs().end(), std::back_inserter(vec_rsp), [&](auto j) { return j->get_operator() == rsp->get_operator(); }); } // No simplification needed if there is only one slice -> cont -> reshape if(vec_rsp.size() <= 1) { return; } // ensure reshape happens after the axis dimension auto axis = axes[0]; auto slc_lens = slc->get_shape().lens(); auto slc_dim_size = std::accumulate( slc_lens.begin() + axis, slc_lens.end(), 1, std::multiplies()); auto input_lens = input->get_shape().lens(); auto input_size = input->get_shape().elements(); auto slc_axis_len = input_lens[axis]; // search the reshape output (standard shape) to decide which axis are // in its output corresponding to the slc_dim_size auto rsp_lens = rsp->get_shape().lens(); auto rsp_strides = rsp->get_shape().strides(); rsp_strides.insert(rsp_strides.begin(), rsp_strides[0] * rsp_lens[0]); auto ait = std::find(rsp_strides.begin(), rsp_strides.end(), slc_dim_size); int rsp_axis = -1; if(ait == rsp_strides.end()) { return; } else if(ait == rsp_strides.end() - 1) { // edge case // slice_dim == 1, in that case it could match with last stride of 1. // it should accumulate lengths from last dim in that case. discount 1 to avoid going // out of bounds. assert(slc_dim_size == 1); rsp_axis = std::distance(rsp_strides.begin(), ait) - 1; } else { rsp_axis = std::distance(rsp_strides.begin(), ait); } // Calculate reshape output shape // Need to find a reshape such that data represented by instructions in vec_rsp can be // written as slices of this new reshape. This is done by holding all the dims constant in // rsp_lens to compute the required dim for rsp_axis (axis that will be sliced) // ex 1: Input Shape: {2, 12, 4}, Slice Axis: 1, Slices are: (0:4), (4:8), (8:12), // Reshape Outputs: {2, 2, 2, 4}, {2, 2, 2, 4}, {2, 2, 2, 4} // rsp_axis = 1, rsp_out_lens (initial) = {2, 1, 2, 4}, rsp_fixed_size = 2*1*2*4 = 16 // rsp_axis_len = 2*12*4 / 16 = 6 // rsp_out_lens (final) = {2, 6, 2, 4} // ex 2: Input Shape: {2, 12, 4}, Slice Axis: 1, Slices are: (0:4), (4:8), (8:12), // Reshape Outputs: {2, 16}, {2, 16}, {2, 16} // rsp_axis = 1, rsp_out_lens (initial) = {2, 1}, rsp_fixed_size = 2*1 = 2 // rsp_axis_len = 2*12*4 / 2 = 48 // rsp_out_lens (final) = {2, 48} std::vector rsp_out_lens(rsp_lens.begin(), rsp_lens.end()); rsp_out_lens[rsp_axis] = 1; auto rsp_fixed_size = std::accumulate( rsp_out_lens.begin(), rsp_out_lens.end(), 1, std::multiplies()); // cannot create a valid reshape for simplification if(input_size % rsp_fixed_size != 0) { return; } auto rsp_axis_len = input_size / rsp_fixed_size; rsp_out_lens[rsp_axis] = rsp_axis_len; // Calculate new slice start and end indices. Indices are scaled using the new reshape axis // and the original slice axis. See examples: // ex 1: Input Shape: {2, 12, 4}, Slice Axis: 1, Slices are: (0:4), (4:8), (8:12), // Reshape Outputs: {2, 2, 2, 4}, {2, 2, 2, 4}, {2, 2, 2, 4} // slc_axis_len = 12, rsp_axis_len = 6 // New Starts: {0*6/12, 4*6/12, 8*6/12} = {0, 2, 4} // New Ends: {4*6/12, 8*6/12, 12*6/12} = {2, 4, 6} // ex 2: Input Shape: {2, 12, 4}, Slice Axis: 1, Slices are: (0:4), (4:8), (8:12), // Reshape Outputs: {2, 16}, {2, 16}, {2, 16} // slc_axis_len = 12, rsp_axis_len = 48 // New Starts: {0*48/12, 4*48/12, 8*48/12} = { 0, 16, 32} // New Ends: {4*48/12, 8*48/12, 12*48/12} = {16, 32, 48} std::vector new_starts(vec_rsp.size()); std::transform(vec_rsp.begin(), vec_rsp.end(), new_starts.begin(), [&](auto is) { auto cont = is->inputs().front(); auto og_slc = cont->inputs().front(); return any_cast(og_slc->get_operator()).starts[0] * rsp_axis_len / slc_axis_len; }); std::vector new_ends(vec_rsp.size()); std::transform(vec_rsp.begin(), vec_rsp.end(), new_ends.begin(), [&](auto is) { auto cont = is->inputs().front(); auto og_slc = cont->inputs().front(); return any_cast(og_slc->get_operator()).ends[0] * rsp_axis_len / slc_axis_len; }); auto rsp_ins = m.insert_instruction( std::next(input), make_op("reshape", {{"dims", rsp_out_lens}}), input); // replace the original reshape with slice for(std::size_t i = 0; i < vec_rsp.size(); ++i) { m.replace_instruction( vec_rsp[i], make_op( "slice", {{"axes", {rsp_axis}}, {"starts", {new_starts[i]}}, {"ends", {new_ends[i]}}}), rsp_ins); } } }; struct find_split_transpose { auto matcher() const { return match::name("transpose")(match::arg(0)(match::name("slice").bind("slice"))) .bind("trans"); } void apply(module& m, const match::matcher_result& r) const { auto slc = r.instructions["slice"]; auto trans = r.instructions["trans"]; auto input = slc->inputs().front(); auto split_outputs = get_splits(input); if(split_outputs.empty()) { return; } if(std::any_of(split_outputs.begin(), split_outputs.end(), [](auto i) { return i->outputs().size() != 1; })) return; std::vector vec_trans(split_outputs.size()); std::transform(split_outputs.begin(), split_outputs.end(), vec_trans.begin(), [](auto i) { return i->outputs().front(); }); // all transpose are the same auto perm = any_cast(trans->get_operator()).dims; if(not same_ops(vec_trans)) { return; } // insert an transpose instruction auto tr = m.insert_instruction( std::next(input), make_op("transpose", {{"permutation", perm}}), input); // compute the axis in the slice auto axis = any_cast(slc->get_operator()).axes.front(); auto it = std::find(perm.begin(), perm.end(), axis); assert(it != perm.end()); int64_t axis_new = std::distance(perm.begin(), it); for(auto in : split_outputs) { auto oper = any_cast(in->get_operator()); auto starts = oper.starts; auto ends = oper.ends; auto tr_orig = in->outputs().front(); m.replace_instruction( tr_orig, make_op("slice", {{"axes", {axis_new}}, {"starts", starts}, {"ends", ends}}), tr); } } }; void simplify_algebra::apply(module& m) const { // Run simplifications multiple times m.repeat_while_changes(8, [&] { match::find_matches(m, find_inner_broadcast{}, find_dot_broadcast{}, find_double_add_lit_broadcast{}, find_add_lit_broadcast{}, find_add_convs{}, find_conv_dot_horiz_fusion{}, find_mul_conv{}, find_mul_slice_conv{}, find_mul_dot{}, find_dot_slice{}, find_dot_mul{}, find_mul_add{}, find_unit_ops{}, find_neg_unit_ops{}, eliminate_zero_point{}, find_zero_ops{}, find_dot_add{}, find_conv_add{}, find_div_const{}, find_sub_const{}, find_rsqrt{}, find_concat_conv{}, find_concat_op{}, find_split_concat{}, find_splits{}, find_split_reshape{}, find_split_transpose{}); dead_code_elimination{}.apply(m); }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/simplify_dyn_ops.cpp000066400000000000000000000725251510465702400214350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { /** * Convert broadcast_with_dims operators with a static input tensor and a constant `dims` input * into multibroadcast op with a static output shape attribute. * */ struct find_broadcast_with_dims_static { auto matcher() const { return match::name("broadcast_with_dims")(match::nargs(2), match::arg(0)(match::static_shape()), match::arg(1)(match::is_constant())); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto inputs = ins->inputs(); // read the values of arg(1) to create input to multibroadcast std::vector sizes_vec; inputs.at(1)->eval().visit( [&](auto output) { sizes_vec.assign(output.begin(), output.end()); }); m.replace_instruction( ins, make_op("multibroadcast", {{"out_lens", sizes_vec}}), inputs.at(0)); } }; /** * Convert a Resize op. with Nearest mode to an implementation using Gather op. * From: resize[scales={...}/sizes={...},](static, constant) * To: * 0 = literal{ ... } computed_indices * ... * 2 = reshape[dims={45}](X) 1-dimensional * 3 = gather[axis=0](2,0) * * At the time of writing, this conversion is required for GPU targets because there * is not direct a GPU implementation of the Resize operation. * This matcher depends on a split_single_dyn_dim pass being run before it, which * will convert any dynamic-batch input to static inputs and make this conversion possible. * * At time of writing, Resize allows either 1 or 2 inputs * but the 1-input case is never created by Onnx parsing. */ struct find_resize_static { auto matcher() const { return match::name("resize")(match::nargs(2), match::arg(0)(match::static_shape()), match::arg(1)(match::is_constant())); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto inputs = ins->inputs(); auto resize_op = any_cast(ins->get_operator()); auto in_lens = inputs.at(0)->get_shape().lens(); std::vector sizes_vec(inputs.at(0)->get_shape().ndim()); std::vector scales_vec(inputs.at(0)->get_shape().ndim()); // populate both scales and sizes for the benefit of the algorithm. inputs.at(1)->eval().visit([&](auto input) { using type = typename decltype(input)::value_type; if constexpr(std::is_integral{}) { // read output sizes and use them to compute scales sizes_vec.assign(input.begin(), input.end()); std::transform( input.begin(), input.end(), in_lens.begin(), scales_vec.begin(), [](auto sz, size_t in_len) { return static_cast(sz) / in_len; }); } else { // read scales and use them to compute output sizes scales_vec.assign(input.begin(), input.end()); std::transform( input.begin(), input.end(), in_lens.begin(), sizes_vec.begin(), [](auto sz, size_t in_len) { return static_cast(sz * in_len); }); } }); auto in_s = inputs.at(0)->get_shape(); shape out_s{in_s.type(), sizes_vec}; std::vector ind(out_s.elements()); // map out_idx to in_idx auto nearest_op = op::resize::get_nearest_op(resize_op.nearest_mode); auto idx_op = op::resize::get_original_idx_op(resize_op.coordinate_transformation_mode); shape_for_each(out_s, [&](const auto& out_idx_v, size_t out_idx) { std::vector in_idx(out_idx_v.size()); for(auto ii = 0; ii < in_lens.size(); ++ii) { auto idx_val = idx_op(in_lens[ii], sizes_vec[ii], out_idx_v[ii], scales_vec[ii]); in_idx[ii] = nearest_op(in_lens[ii], idx_val); } ind[out_idx] = static_cast(in_s.index(in_idx)); }); // reshape input to one-dimension std::vector rsp_lens = {static_cast(in_s.elements())}; auto reshape_op = make_op("reshape", {{"dims", rsp_lens}}); auto rsp = m.insert_instruction(ins, reshape_op, ins->inputs().at(0)); // Add our computed indices as a literal. // ins_ind is a multi dimensional index that will restore original rank shape ind_s{shape::int32_type, sizes_vec}; auto ins_ind = m.add_literal(literal(ind_s, ind)); m.replace_instruction(ins, make_op("gather", {{"axis", 0}}), rsp, ins_ind); } }; /** * Convert 2 input static shape broadcast/multibroadcast into 1 input version. * Some compiler passes (ex. simplify_algebra) only support the 1 input versions * of the broadcasting operators. * From: * broadcast_op(argument_with_static_shape, argument_with_static_shape) * To: * broadcast_op(argument_with_static_shape); broadcast_op.out_lens = constant_output_dims */ struct find_static_2in_broadcasts { auto matcher() const { return match::broadcast(match::nargs(2), match::arg(0)(match::static_shape()), match::arg(1)(match::static_shape())); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto out_lens = ins->get_shape().lens(); auto broadcast_op = ins->get_operator(); if(broadcast_op.name() == "broadcast") { broadcast_op.from_value({{"out_lens", out_lens}}); } else { broadcast_op.from_value({{"out_lens", out_lens}, {"out_dyn_dims", {}}}); } m.replace_instruction(ins, broadcast_op, ins->inputs().at(0)); } }; /** * Simplify slice with 2 inputs to the 1 input version if inputs[1] is constant. * From: * slice(data, constant_input); two attributes set * To: * slice(data); slice.starts, slice.ends. slice.axes set */ struct find_const_2in_slice { auto matcher() const { return match::name("slice")(match::nargs(2), match::arg(1)(match::is_constant())); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto inputs = ins->inputs(); auto slice_op = any_cast(ins->get_operator()); auto set_attrs = slice_op.get_set_attributes(); std::vector starts_vec; std::vector ends_vec; std::vector axes_vec; if(set_attrs == op::slice::ends_axes) { // slice(data, starts) inputs.at(1)->eval().visit( [&](auto output) { starts_vec.assign(output.begin(), output.end()); }); ends_vec = slice_op.ends; axes_vec = slice_op.axes; } else if(set_attrs == op::slice::starts_axes) { // slice(data, ends) inputs.at(1)->eval().visit( [&](auto output) { ends_vec.assign(output.begin(), output.end()); }); starts_vec = slice_op.starts; axes_vec = slice_op.axes; } else { // slice(data, axes) inputs.at(1)->eval().visit( [&](auto output) { axes_vec.assign(output.begin(), output.end()); }); starts_vec = slice_op.starts; ends_vec = slice_op.ends; } m.replace_instruction( ins, make_op("slice", {{"starts", starts_vec}, {"ends", ends_vec}, {"axes", axes_vec}}), inputs.at(0)); } }; /** * Simplify slice with 3 inputs to the 1 input version if inputs[1:2] are constant. * From: * slice(data, constant_input1, constant_input2); one attribute set * To: * slice(data); slice.starts, slice.ends. slice.axes set */ struct find_const_3in_slice { auto matcher() const { return match::name("slice")(match::nargs(3), match::arg(1)(match::is_constant()), match::arg(2)(match::is_constant())); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto inputs = ins->inputs(); auto slice_op = any_cast(ins->get_operator()); auto set_attrs = slice_op.get_set_attributes(); std::vector starts_vec; std::vector ends_vec; std::vector axes_vec; if(set_attrs == op::slice::axes_only) { // slice(data, starts, ends) inputs.at(1)->eval().visit( [&](auto output) { starts_vec.assign(output.begin(), output.end()); }); inputs.at(2)->eval().visit( [&](auto output) { ends_vec.assign(output.begin(), output.end()); }); axes_vec = slice_op.axes; } else if(set_attrs == op::slice::ends_only) { // slice(data, starts, axes) inputs.at(1)->eval().visit( [&](auto output) { starts_vec.assign(output.begin(), output.end()); }); inputs.at(2)->eval().visit( [&](auto output) { axes_vec.assign(output.begin(), output.end()); }); ends_vec = slice_op.ends; } else { // slice(data, ends, axes) inputs.at(1)->eval().visit( [&](auto output) { ends_vec.assign(output.begin(), output.end()); }); inputs.at(2)->eval().visit( [&](auto output) { axes_vec.assign(output.begin(), output.end()); }); starts_vec = slice_op.starts; } m.replace_instruction( ins, make_op("slice", {{"starts", starts_vec}, {"ends", ends_vec}, {"axes", axes_vec}}), inputs.at(0)); } }; /** * Simplify slice with 4 inputs to the 1 input version if inputs[1:3] are constant. * From: * slice(data, constant_starts, constant_ends, constant_axes) * To: * slice(data); slice.starts, slice.ends. slice.axes set */ struct find_const_4in_slice { auto matcher() const { return match::name("slice")(match::nargs(4), match::arg(1)(match::is_constant()), match::arg(2)(match::is_constant()), match::arg(3)(match::is_constant())); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto inputs = ins->inputs(); argument starts_arg = inputs.at(1)->eval(false); argument ends_arg = inputs.at(2)->eval(false); argument axes_arg = inputs.at(3)->eval(false); if(not starts_arg.empty() and not ends_arg.empty() and not axes_arg.empty()) { std::vector starts_vec; std::vector ends_vec; std::vector axes_vec; starts_arg.visit([&](auto output) { starts_vec.assign(output.begin(), output.end()); }); ends_arg.visit([&](auto output) { ends_vec.assign(output.begin(), output.end()); }); axes_arg.visit([&](auto output) { axes_vec.assign(output.begin(), output.end()); }); m.replace_instruction( ins, make_op("slice", {{"starts", starts_vec}, {"ends", ends_vec}, {"axes", axes_vec}}), inputs.at(0)); } } }; /** * Simplify dimensions_of to a literal when the input arugment has a static shape * or the dynamic dimensions from `start` to `end` are fixed. */ struct find_static_dimensions_of { auto matcher() const { return match::name("dimensions_of")(); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto input = ins->inputs().at(0); auto dimensions_of_value = ins->get_operator().to_value(); auto start = dimensions_of_value.at("start").to(); auto end = dimensions_of_value.at("end").to(); if(input->get_shape().dynamic()) { // check if dynamic dimensions from start to end are fixed auto dds = input->get_shape().dyn_dims(); if(std::any_of(dds.begin() + start, dds.begin() + end, [](const auto& dd) { return not dd.is_fixed(); })) { return; } } std::size_t output_ndim = end - start; std::vector vec_shape(output_ndim); migraphx::shape s(migraphx::shape::int64_type, {output_ndim}); std::vector input_lens = input->get_shape().to_static(1).lens(); std::transform(input_lens.begin() + start, input_lens.begin() + end, vec_shape.begin(), [](auto i) { return int64_t(i); }); migraphx::shape output_shape{migraphx::shape::int64_type, {end - start}}; auto lit_ins = m.add_literal(migraphx::literal{output_shape, vec_shape}); m.replace_instruction(ins, lit_ins); } }; /** * Simplify allocate into 2 argument reshape that has constant output dimensions into a static 1 * argument reshape. Intended to simplify what ONNX parse_reshape creates for dynamic reshapes. * This matcher can be generalized to matching reshape(data, static_shape_output_tensor). * From: * x = allocate(constant_output_dims) -> reshape(data, x) * To: * reshape(data); reshape.dims = constant_output_dims */ struct find_const_alloc_reshapes { auto matcher() const { auto const_alloc = match::arg(1)(match::name("allocate")(match::is_constant())); return match::name("reshape")(match::nargs(2), const_alloc); } void apply(module& m, const match::matcher_result& mr) const { auto reshape_ins = mr.result; auto reshape_inputs = reshape_ins->inputs(); auto alloc_ins = reshape_inputs.at(1); argument output_dims_arg = alloc_ins->inputs().at(0)->eval(false); std::vector output_dims_vec; output_dims_arg.visit( [&](auto output) { output_dims_vec.assign(output.begin(), output.end()); }); m.replace_instruction( reshape_ins, make_op("reshape", {{"dims", output_dims_vec}}), reshape_inputs.at(0)); // have dead_code_elimination remove the previous allocate } }; /** * Simplify allocate into fill operator that has constant output dimensions and constant value. * The allocate into fill instructions is what is produced when parsing the ONNX * ConstantOfShape operator. This replacement could be handled with propagate_constant, but * would rather have the simplification happen earlier during compiling. * This matcher can be generalized to matching fill(constant_value, static_shape_output_tensor). * From: * x = allocate(constant_ouptut_dims) -> fill(constant_value, x) * To: * literal */ struct find_const_alloc_fill { auto matcher() const { auto const_alloc = match::arg(1)(match::name("allocate")(match::is_constant())); return match::name("fill")(match::arg(0)(match::is_constant()), const_alloc); } void apply(module& m, const match::matcher_result& mr) const { auto fill_ins = mr.result; auto fill_arg = fill_ins->eval(false); auto l = m.add_literal(fill_arg.get_shape(), fill_arg.data()); m.replace_instruction(fill_ins, l); } }; /** * Simplify broadcast_for_dot instructions with two static shaped arguments * From: * broadcast_for_dot(static_shape_arg, static_shape_arg) * To: * multibroadcast(static_shape_arg); output_lens = static_broadcast_for_doted_shape */ struct find_static_broadcast_for_dot { auto matcher() const { return match::name("broadcast_for_dot")(match::arg(0)(match::static_shape()), match::arg(1)(match::static_shape())); } void apply(module& m, const match::matcher_result& mr) const { auto broadcast_for_dot_ins = mr.result; auto inputs = broadcast_for_dot_ins->inputs(); auto s0 = inputs.at(0)->get_shape(); auto s1 = inputs.at(1)->get_shape(); auto l0_it = s0.lens().end() - 2; std::vector l0_broadcasted_lens(s0.lens().begin(), l0_it); auto l1_it = s1.lens().begin() + s1.ndim() - 2; std::vector l1_broadcasted_lens(s1.lens().begin(), l1_it); auto output_lens = compute_broadcasted_lens(l0_broadcasted_lens, l1_broadcasted_lens); output_lens.insert(output_lens.end(), l0_it, s0.lens().end()); m.replace_instruction(broadcast_for_dot_ins, make_op("multibroadcast", {{"out_lens", output_lens}}), inputs.at(0)); } }; /** * Simplify onehot instructions with static shape `indices` input and * a compile-time constant `depth` attribute or input. * From: * onehot(static_shape_arg, constant_arg, values) or * onehot(static_shape_arg, values) * To: * A = literal(shape = onehot_output_shape, value = 0) * B = unsqueeze(literal(lens = indices_lens, strides = broadcasted scalar, value = 1), * axis=onehot_axis) C = scatter(A, unsqueeze(indices, axis=onehot_axis), B) diff = on_value - * off_value D = mul(diff, C); return = add(D, off_value); * * NOTE: It might be cleaner to use some form of `fill` instead of * (on_value - off_value) * mask + off_value when we have `fill` working * on the GPU. */ struct find_static_onehot { auto matcher() const { auto match_2_args = match::nargs(2)(match::arg(0)(match::static_shape()), match::arg(1)(match::static_shape())); auto match_3_args = match::nargs(3)(match::arg(0)(match::static_shape()), match::arg(1)(match::is_constant()), match::arg(2)(match::static_shape())); return match::name("onehot")(match::any_of(match_2_args, match_3_args)); } void apply(module& m, const match::matcher_result& mr) const { auto onehot_ins = mr.result; auto onehot_inputs = onehot_ins->inputs(); auto onehot_op = any_cast(onehot_ins->get_operator()); auto indices_ins = onehot_inputs[0]; shape indices_shape = indices_ins->get_shape(); std::size_t depth_val; migraphx::instruction_ref values_ins; if(onehot_op.depth.has_value()) { assert(onehot_inputs.size() == 2); depth_val = onehot_op.depth.value(); values_ins = onehot_inputs[1]; } else { assert(onehot_inputs.size() == 3); auto depth_ins = onehot_inputs[1]; depth_ins->eval().visit([&](auto d) { depth_val = d[0]; }); values_ins = onehot_inputs[2]; } shape values_shape = values_ins->get_shape(); std::vector static_output_lens = indices_shape.lens(); auto normalized_axis = (onehot_op.axis < 0) ? onehot_op.axis + indices_shape.ndim() + 1 : onehot_op.axis; static_output_lens.insert(static_output_lens.begin() + normalized_axis, depth_val); shape output_shape{values_shape.type(), static_output_lens}; std::vector zeros(output_shape.elements(), 0); auto zeros_lit = m.add_literal(literal(output_shape, zeros)); auto unsqueeze_inds = m.insert_instruction( onehot_ins, migraphx::make_op("unsqueeze", {{"axes", {normalized_axis}}}), indices_ins); // broadcast the one scalar to the correct shape auto ones_lit = m.add_literal(literal(shape{values_shape.type(), {1}, {0}}, {1})); auto mb_ones = m.insert_instruction( onehot_ins, migraphx::make_op("multibroadcast", {{"out_lens", unsqueeze_inds->get_shape().lens()}}), ones_lit); auto mask = m.insert_instruction( onehot_ins, make_op("scatter_none", {{"axis", normalized_axis}, {"skip_out_of_bounds", true}}), zeros_lit, unsqueeze_inds, mb_ones); auto off_val = m.insert_instruction(onehot_ins, make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), values_ins); auto on_val = m.insert_instruction(onehot_ins, make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), values_ins); auto diff_val = m.insert_instruction(onehot_ins, make_op("sub"), on_val, off_val); auto mul_diff_mask = insert_common_op(m, onehot_ins, make_op("mul"), {diff_val, mask}); auto mb_off_val = m.insert_instruction( onehot_ins, make_op("multibroadcast", {{"out_lens", output_shape.lens()}}), off_val); m.replace_instruction(onehot_ins, make_op("add"), mb_off_val, mul_diff_mask); } }; /** * Go through `select_module` instructions and update the `output_dyn_shapes` attribute. * Checks the submodule output shapes and determines an appropriate `output_dyn_shapes` attribute. * This version ignores dynamic_dimension opt values. * Intended to be run after the other simplify_dyn_ops passes. */ struct simplify_select_module_output_shape { auto matcher() const { return match::name("select_module"); } void apply(module& m, const match::matcher_result& mr) const { auto sm_ins = mr.result; auto sm_module_inputs = sm_ins->module_inputs(); std::vector> all_output_shapes(sm_module_inputs.size()); std::transform(sm_module_inputs.begin(), sm_module_inputs.end(), all_output_shapes.begin(), [](auto submod) { return submod->get_output_shapes(); }); // check that all of the submodules have the same number of outputs and all respective // outputs have the same rank and type auto shapes_ndim = get_shapes_ndim(all_output_shapes.front()); auto shapes_types = get_shapes_types(all_output_shapes.front()); if(std::any_of( all_output_shapes.begin() + 1, all_output_shapes.end(), [&](const auto& out_shapes) { bool same_types = get_shapes_types(out_shapes) == shapes_types; bool same_ndim = get_shapes_ndim(out_shapes) == shapes_ndim; return not same_types or not same_ndim; })) { return; } auto num_out_shapes = shapes_ndim.size(); std::vector dyn_shapes(num_out_shapes); auto num_submod = sm_module_inputs.size(); // compare respective output shapes from each submodule to get a range for the output shape for(int i : range(num_out_shapes)) { std::vector shapes_at_index(num_submod); std::transform(all_output_shapes.begin(), all_output_shapes.end(), shapes_at_index.begin(), [&](auto output_shapes) { return output_shapes.at(i); }); dyn_shapes.at(i) = dyn_shape_from_shapes(shapes_at_index); } auto tuple_shape = shape{dyn_shapes}; m.replace_instruction( sm_ins, make_op("select_module", {{"output_dyn_shapes", to_value(tuple_shape)}}), sm_ins->inputs(), sm_module_inputs); } std::vector get_shapes_ndim(const std::vector& shapes) const { std::vector ret(shapes.size()); std::transform( shapes.cbegin(), shapes.cend(), ret.begin(), [](auto s) { return s.ndim(); }); return ret; } std::vector get_shapes_types(const std::vector& shapes) const { std::vector ret(shapes.size()); std::transform( shapes.cbegin(), shapes.cend(), ret.begin(), [](auto s) { return s.type(); }); return ret; } /** * Calculating an appropriate shape that encompasses all of the given vector of shapes. * Equivalent to creating a 2D matrix of shape lengths and do a reduce over each axis. * The shapes can be dynamic or static. * Assuming all shapes have the same ndim. */ shape dyn_shape_from_shapes(std::vector shape_vec) const { // making 2D matrices of min_lens and max_lens // specifically using uint64_t because we're going to put the values into a tensor_view // later std::vector all_min_lens; std::vector all_max_lens; for(const auto& s : shape_vec) { auto min_lens = s.min_lens(); auto max_lens = s.max_lens(); std::copy(min_lens.begin(), min_lens.end(), std::back_inserter(all_min_lens)); std::copy(max_lens.begin(), max_lens.end(), std::back_inserter(all_max_lens)); } assert(all_min_lens.size() == shape_vec.size() * shape_vec.front().ndim()); assert(all_max_lens.size() == shape_vec.size() * shape_vec.front().ndim()); auto num_rows = shape_vec.size(); auto num_cols = shape_vec.front().ndim(); shape tensor_shape{shape::uint64_type, {num_rows, num_cols}}; auto min_lens_matrix = make_view(tensor_shape, all_min_lens.data()); auto max_lens_matrix = make_view(tensor_shape, all_max_lens.data()); std::vector mins(num_cols); std::vector maxes(num_cols); // rearranging data into column vectors to reduce over // i = row, j = column for(int j : range(num_cols)) { std::vector reduce_min_vals(num_rows); std::vector reduce_max_vals(num_rows); for(int i : range(num_rows)) { reduce_min_vals.at(i) = min_lens_matrix(i, j); reduce_max_vals.at(i) = max_lens_matrix(i, j); } uint64_t max_int = std::numeric_limits::max(); uint64_t min_val = std::accumulate(reduce_min_vals.begin(), reduce_min_vals.end(), max_int, [](uint64_t x, uint64_t y) { return x < y ? x : y; }); uint64_t max_val = std::accumulate( reduce_max_vals.begin(), reduce_max_vals.end(), 0, [](uint64_t x, uint64_t y) { return x > y ? x : y; }); mins.at(j) = min_val; maxes.at(j) = max_val; } // fixed output shape case if(mins == maxes) { return shape{shape_vec.front().type(), mins}; } // dynamic output shape case return shape{shape_vec.front().type(), mins, maxes, {}}; } }; void simplify_dyn_ops::apply(module& m) const { match::find_matches(m, find_broadcast_with_dims_static{}, find_resize_static{}, find_static_dimensions_of{}, find_const_alloc_reshapes{}, find_static_2in_broadcasts{}, find_const_2in_slice{}, find_const_3in_slice{}, find_const_4in_slice{}, find_const_alloc_fill{}, find_static_broadcast_for_dot{}, find_static_onehot{}); match::find_matches(m, simplify_select_module_output_shape{}); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/simplify_qdq.cpp000066400000000000000000000571301510465702400205420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { std::unordered_set get_quantizable_op_names() { static std::unordered_set s = {"convolution", "dot"}; return s; } std::vector get_between_ins(const instruction_ref dqins, const instruction_ref qop_arg) { auto prev_ins = qop_arg; std::vector ins_between; while(prev_ins != dqins) { ins_between.push_back(prev_ins); prev_ins = prev_ins->inputs().front(); } return ins_between; } // Helper function to insert quantized versions of any broadcasts and transpose ops that // occur between dequantizelinear and the quantized op auto propagate_quantized_ins(module& m, const instruction_ref dqins, instruction_ref input_ins, std::vector ins_between, bool is_fp16_model = false) { for(auto ins : reverse_iterator_for(ins_between)) { if((*ins)->name() == "convert" and is_fp16_model) { continue; } input_ins = m.insert_instruction(dqins, (*ins)->get_operator(), {input_ins}); } return input_ins; } struct match_find_quantizable_ops { static bool is_valid_qparam(instruction_ref qparam, std::vector lens, std::size_t axis) { return qparam->get_shape().elements() == 1 or qparam->get_shape().elements() == lens.at(axis); } static bool is_symmetric_zero_point(instruction_ref zp) { if(not zp->can_eval()) return false; bool all_zeros = false; zp->eval().visit([&](auto z) { all_zeros = std::all_of(z.begin(), z.end(), [&](auto val) { return float_equal(val, 0); }); }); return all_zeros; } static auto qparam_broadcast_op(instruction_ref qparam, std::vector lens, std::size_t axis) { if(qparam->get_shape().scalar()) { return migraphx::make_op("multibroadcast", {{"out_lens", lens}}); } else { return migraphx::make_op("broadcast", {{"out_lens", lens}, {"axis", axis}}); } } auto matcher() const { auto dq1 = match::arg(0)( skip_post_dq_ops(match::dequantizelinear_op("scale1", "zp1").bind("dq1"))); auto dq2 = match::arg(1)( skip_post_dq_ops(match::dequantizelinear_op("scale2", "zp2").bind("dq2"))); return match::name(get_quantizable_op_names())(dq1, dq2); } void apply(module& m, const match::matcher_result& r) const { auto qop = r.result; auto dq1 = r.instructions["dq1"]; auto dq2 = r.instructions["dq2"]; auto scale1 = r.instructions["scale1"]; auto scale2 = r.instructions["scale2"]; auto zp1 = r.instructions["zp1"]; auto zp2 = r.instructions["zp2"]; // Propagate q1 and q2 through any broadcasts and transposes before qop auto qop_args = qop->inputs(); bool is_fp16_model = false; if(dq1->get_shape().type() != qop->get_shape().type() and qop->get_shape().type() == migraphx::shape::half_type) { assert(dq1->get_shape().type() == migraphx::shape::float_type); is_fp16_model = true; } auto qop_between_arg0 = get_between_ins(dq1, qop_args[0]); auto qop_between_arg1 = get_between_ins(dq2, qop_args[1]); qop_args.at(0) = propagate_quantized_ins(m, dq1, dq1->inputs().front(), qop_between_arg0, is_fp16_model); qop_args.at(1) = propagate_quantized_ins(m, dq2, dq2->inputs().front(), qop_between_arg1, is_fp16_model); auto arg1_lens = qop_args[0]->get_shape().lens(); auto arg2_lens = qop_args[1]->get_shape().lens(); std::set supported_types = fp8_types{}.get(); supported_types.insert(migraphx::shape::int8_type); auto in1 = dq1->inputs().front(); auto in2 = dq2->inputs().front(); if(not contains(supported_types, in1->get_shape().type()) or not contains(supported_types, in2->get_shape().type())) { return; } instruction_ref dq; instruction_ref out_scale; instruction_ref out_zp; if(qop->name() == "convolution") { auto conv_val = qop->get_operator().to_value(); dq = m.insert_instruction( qop, migraphx::make_op("quant_convolution", conv_val), qop_args); auto out_lens = dq->get_shape().lens(); // Ensure input and weight quantization paramaters are of a proper form // Input is of shape [n, c, x1, ..., xn]. Only scalar quantization allowed // Weight is of shape [k, c, y1, ... , yn]. Valid quantization axis is k if(not(scale1->get_shape().elements() == 1 and zp1->get_shape().elements() == 1 and is_valid_qparam(scale2, arg2_lens, 0) and is_valid_qparam(zp2, arg2_lens, 0))) return; // This implementation supports affine quantization for both input and weight // In practice, weight is quantized symmetrically auto s1_bcast = m.insert_instruction(qop, qparam_broadcast_op(scale1, out_lens, 1), scale1); auto s2_bcast = m.insert_instruction(qop, qparam_broadcast_op(scale2, out_lens, 1), scale2); out_scale = m.insert_instruction(qop, migraphx::make_op("mul"), s1_bcast, s2_bcast); // Compute the zero-point terms; initialize as 0 and add relevant terms auto zero_lit = m.add_literal(literal{shape{dq->get_shape().type()}, {0}}); out_zp = m.insert_instruction( qop, make_op("multibroadcast", {{"out_lens", dq->get_shape().lens()}}), zero_lit); auto inp_zp_bc = m.insert_instruction(qop, qparam_broadcast_op(zp1, arg1_lens, 1), zp1); auto w_zp_bc = m.insert_instruction(qop, qparam_broadcast_op(zp2, arg2_lens, 0), zp2); if(not is_symmetric_zero_point(zp1)) { auto out_zp_1 = m.insert_instruction( qop, migraphx::make_op("quant_convolution", conv_val), inp_zp_bc, qop_args[1]); out_zp = m.insert_instruction(qop, migraphx::make_op("add"), out_zp, out_zp_1); } if(not is_symmetric_zero_point(zp2)) { auto out_zp_2 = m.insert_instruction( qop, migraphx::make_op("quant_convolution", conv_val), qop_args[0], w_zp_bc); out_zp = m.insert_instruction(qop, migraphx::make_op("add"), out_zp, out_zp_2); } if(not is_symmetric_zero_point(zp1) and not is_symmetric_zero_point(zp2)) { auto out_zp_3 = m.insert_instruction( qop, migraphx::make_op("quant_convolution", conv_val), inp_zp_bc, w_zp_bc); out_zp = m.insert_instruction(qop, migraphx::make_op("sub"), out_zp, out_zp_3); } } else if(qop->name() == "dot") { dq = m.insert_instruction(qop, migraphx::make_op("quant_dot"), qop_args); auto out_lens = dq->get_shape().lens(); // For (..., M, N) x (..., N, K) dot, valid quantization axes are M for input1 and K for // input 2 if(not(is_valid_qparam(scale1, out_lens, out_lens.size() - 2) and is_valid_qparam(zp1, out_lens, out_lens.size() - 2) and is_valid_qparam(scale2, out_lens, out_lens.size() - 1) and is_valid_qparam(zp2, out_lens, out_lens.size() - 1))) { return; } // This implementation supports both arguments being per-axis affine quantized // In practice, inputs are per-tensor affine and weights are per-axis symmetric auto s1_bcast = m.insert_instruction( qop, qparam_broadcast_op(scale1, out_lens, out_lens.size() - 2), scale1); auto s2_bcast = m.insert_instruction( qop, qparam_broadcast_op(scale2, out_lens, out_lens.size() - 1), scale2); out_scale = m.insert_instruction(qop, migraphx::make_op("mul"), s1_bcast, s2_bcast); // Compute the zero-point terms; initialize as 0 and add relevant terms auto zero_lit = m.add_literal(literal{shape{dq->get_shape().type()}, {0}}); out_zp = m.insert_instruction( qop, make_op("multibroadcast", {{"out_lens", dq->get_shape().lens()}}), zero_lit); auto zp1_bc = m.insert_instruction( qop, qparam_broadcast_op(zp1, arg1_lens, arg1_lens.size() - 2), zp1); auto zp2_bc = m.insert_instruction( qop, qparam_broadcast_op(zp2, arg2_lens, arg2_lens.size() - 1), zp2); if(not is_symmetric_zero_point(zp1)) { auto out_zp_1 = m.insert_instruction(qop, migraphx::make_op("quant_dot"), zp1_bc, qop_args[1]); out_zp = m.insert_instruction(qop, migraphx::make_op("add"), out_zp, out_zp_1); } if(not is_symmetric_zero_point(zp2)) { auto out_zp_2 = m.insert_instruction(qop, migraphx::make_op("quant_dot"), qop_args[0], zp2_bc); out_zp = m.insert_instruction(qop, migraphx::make_op("add"), out_zp, out_zp_2); } if(not is_symmetric_zero_point(zp1) and not is_symmetric_zero_point(zp2)) { auto out_zp_3 = m.insert_instruction(qop, migraphx::make_op("quant_dot"), zp1_bc, zp2_bc); out_zp = m.insert_instruction(qop, migraphx::make_op("sub"), out_zp, out_zp_3); } } dq = m.insert_instruction(qop, make_op("dequantizelinear"), dq, out_scale, out_zp); if(is_fp16_model) { dq = m.insert_instruction( qop, make_op("convert", {{"target_type", migraphx::shape::half_type}}), dq); } m.replace_instruction(qop, dq); } }; // Checks for block quantized scales by checking scales are not scalar or 1D. inline auto block_dq(const std::string& scale) { // clang-format off return match::name("dequantizelinear")( match::nargs(2), match::arg(1)(match::skip_broadcasts(match::none_of( match::scalar_shape, match::ndim(1) ).bind(scale)))); // clang-format on } /** * Handles block quantization for MX types. * Matcher checks that dequantizelinear has no zero point and that * the scales are block quantized. * TODO: quant_convolution disabled until rocMLIR support */ struct match_find_mx_quantizable_ops { auto matcher() const { auto dq1 = match::arg(0)(skip_post_dq_ops(block_dq("scale1").bind("dq1"))); auto dq2 = match::arg(1)(skip_post_dq_ops(block_dq("scale2").bind("dq2"))); return match::name("dot")(dq1, dq2); } void apply(module& m, const match::matcher_result& r) const { auto qop = r.result; auto dq1 = r.instructions["dq1"]; auto dq2 = r.instructions["dq2"]; auto scale1 = r.instructions["scale1"]; auto scale2 = r.instructions["scale2"]; // Propagate q1 and q2 through any broadcasts and transposes before qop // Construct 4 arguments for block quantized op auto qop_args = qop->inputs(); bool is_fp16_model = false; if(dq1->get_shape().type() != qop->get_shape().type() and qop->get_shape().type() == migraphx::shape::half_type) { assert(dq1->get_shape().type() == migraphx::shape::float_type); is_fp16_model = true; } auto qop_between_arg0 = get_between_ins(dq1, qop_args[0]); qop_args.at(0) = propagate_quantized_ins(m, dq1, dq1->inputs().front(), qop_between_arg0, is_fp16_model); auto qop_between_arg1 = get_between_ins(dq2, qop_args[1]); qop_args.at(1) = propagate_quantized_ins(m, dq2, dq2->inputs().front(), qop_between_arg1, is_fp16_model); qop_args.push_back( propagate_quantized_ins(m, dq1, scale1, qop_between_arg0, is_fp16_model)); qop_args.push_back( propagate_quantized_ins(m, dq2, scale2, qop_between_arg1, is_fp16_model)); if(qop->name() == "convolution") { auto conv_val = qop->get_operator().to_value(); m.replace_instruction(qop, migraphx::make_op("quant_convolution", conv_val), qop_args); } else if(qop->name() == "dot") { m.replace_instruction(qop, migraphx::make_op("quant_dot"), qop_args); } } }; bool compare_literals(instruction_ref ins1, instruction_ref ins2) { if(ins1->name() == "broadcast" or ins1->name() == "multibroadcast") ins1 = ins1->inputs().front(); auto x = ins1->eval(); if(x.empty()) return false; if(ins2->name() == "broadcast" or ins2->name() == "multibroadcast") ins2 = ins2->inputs().front(); auto y = ins2->eval(); if(y.empty()) return false; bool diff_shapes_equal_vals = false; visit_all(x, y)([&](const auto l1, const auto l2) { diff_shapes_equal_vals = std::all_of(l1.begin() + 1, l1.end(), [&](auto v) { return ((float_equal(v, l1.front())) or (std::isinf(static_cast(l1.front())) and std::isinf(static_cast(v)))); }) and std::all_of(l2.begin(), l2.end(), [&](auto v) { return ((float_equal(v, l1.front())) or (std::isinf(static_cast(l1.front())) and std::isinf(static_cast(v)))); }); }); return (x == y) or diff_shapes_equal_vals; } template bool precedes(Iterator x, Iterator y, Iterator last) { auto r = range(std::next(x), last); return any_of(iterator_for(r), [&](auto it) { return it == y; }); } struct match_qlinear_reused { auto matcher() const { return match::name("quantizelinear")( match::used_once(), match::arg(0)(match::none_of(match::used_once()).bind("x"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto x_ins = r.instructions["x"]; assert(ins != x_ins); auto dq_inputs = ins->inputs(); dq_inputs[0] = ins; auto outputs = x_ins->outputs(); if(outputs.size() != 2) return; for(auto output : outputs) { if(output->name() == "quantizelinear") continue; if(not output->get_operator().attributes().contains("pointwise")) continue; if(not precedes(ins, output, m.end())) continue; auto dq = m.insert_instruction(std::next(ins), make_op("dequantizelinear"), dq_inputs); instruction::replace_argument(output, x_ins, dq); } } }; struct match_concat_qlinear { auto matcher() const { auto any_pointwise_input = match::any_of[match::inputs()](match::pointwise()); return match::name("quantizelinear")(match::arg(0)( match::name("concat")(match::used_once(), any_pointwise_input).bind("cat"))); } auto get_slices(instruction_ref cat_ins) const { std::vector>> slices; auto axis = any_cast(cat_ins->get_operator()).axis; size_t start = 0; for(auto cat_inp : cat_ins->inputs()) { auto end = start + cat_inp->get_shape().lens()[axis]; slices.push_back({{"axes", {axis}}, {"starts", {start}}, {"ends", {end}}}); start = end; } return slices; } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto cat_ins = r.instructions["cat"]; assert(ins->inputs().size() == 3); auto scale = ins->inputs()[1]; auto zp = ins->inputs()[2]; auto slices = get_slices(cat_ins); std::vector new_cat_inputs; std::transform( cat_ins->inputs().begin(), cat_ins->inputs().end(), slices.begin(), std::back_inserter(new_cat_inputs), [&](auto i, const auto& slc) { auto scale_slc = m.insert_instruction(ins, make_op("slice", slc), {scale}); auto zp_slc = m.insert_instruction(ins, make_op("slice", slc), {zp}); return m.insert_instruction(ins, ins->get_operator(), {i, scale_slc, zp_slc}); }); m.replace_instruction(ins, cat_ins->get_operator(), new_cat_inputs); } }; bool is_same_value(instruction_ref a, instruction_ref b) { if(a == b) return true; return compare_literals(a, b); } bool is_same_scale_zero(instruction_ref a, instruction_ref b) { if(a->inputs().size() != b->inputs().size()) return false; if(not is_same_value(a->inputs().at(1), b->inputs().at(1))) return false; if(a->inputs().size() == 2) return true; return is_same_value(a->inputs().at(2), b->inputs().at(2)); } // When an unpack instruction is inserted, its original input must be an int4/uint4. // Therefore check for an unpack_int4 operator -- while ignoring out shape related ops. bool is_any_input_int4(instruction_ref a) { static std::set ign = {"unsqueeze", "broadcast", "multibroadcast", "contiguous", "transpose", "reshape", "convert"}; return std::any_of(a->inputs().begin(), a->inputs().end(), [](auto i) { while(ign.find(i->name()) != ign.end()) i = i->inputs()[0]; return i->name() == "unpack_int4"; }); } /** Used to remove fake quantization pairs quantizelinear -> dequantizelinear. * * Extended to remove the fake quantization pattern for MXFP4: * quantizelinear * â–¼ * pad (optional) * â–¼ * pack_fp4 * â–¼ * unpack_fp4 * â–¼ * slice (optional) * â–¼ * dequantizelinear * * Doesn't match the pack/unpack and reshapes explicitly, instead skips instructions * that match the name with one input recursively. * Use this after selected quantizations have been made into real quantized instructions. */ struct remove_qdq_pairs { auto matcher() const { // clang-format off static const std::unordered_set skip_set = { "pack_fp4", "unpack_fp4", "broadcast", "slice", "reshape", "reshape_lazy", "pad", }; // clang-format on auto q_ins = match::skip(match::name(skip_set))(match::name("quantizelinear").bind("q_ins")); return match::name("dequantizelinear")(match::arg(0)(q_ins)); } auto apply(module&, const match::matcher_result& r) const { auto dq_ins = r.result; auto q_ins = r.instructions["q_ins"]; if(not is_same_scale_zero(dq_ins, q_ins)) { return; } // Need to copy outputs since will be modifying dq_ins outputs std::vector dq_outputs = dq_ins->outputs(); for(auto out : dq_outputs) { instruction::replace_argument(out, dq_ins, q_ins->inputs().front()); } } }; void remove_zero_point(module& m) { for(auto ins : iterator_for(m)) { if(ins->name() != "dequantizelinear") continue; if(ins->inputs().size() != 3) continue; auto zp = ins->inputs().at(2); if(not zp->can_eval()) continue; auto a = zp->eval(); bool is_zero = false; a.visit([&](auto t) { is_zero = std::all_of(t.begin(), t.end(), [](auto x) { return float_equal(x, 0); }); }); if(not is_zero) continue; m.replace_instruction(ins, ins->get_operator(), ins->inputs().at(0), ins->inputs().at(1)); } } void add_int4_pack_unpack_pair(module& m) { for(auto ins : iterator_for(m)) { if(ins->name() != "dequantizelinear") continue; for(auto&& inp : ins->inputs()) { if((inp->name() == "quantizelinear") and is_any_input_int4(inp)) { auto pk = m.insert_instruction(ins, make_op("pack_int4"), inp); auto unpk = m.insert_instruction(ins, make_op("unpack_int4"), pk); instruction::replace_argument(ins, inp, unpk); } } } } } // namespace void simplify_qdq::apply(module& m) const { // first step: add pack/unpack pair between qdq for int4 weights add_int4_pack_unpack_pair(m); match::find_matches(m, match_find_quantizable_ops{}); migraphx::run_passes(m, {migraphx::dead_code_elimination{}}); match::find_matches(m, match_find_mx_quantizable_ops{}); migraphx::run_passes(m, {migraphx::dead_code_elimination{}}); match::find_matches(m, remove_qdq_pairs{}); migraphx::run_passes(m, {migraphx::dead_code_elimination{}}); match::find_matches(m, match_qlinear_reused{}); migraphx::run_passes(m, {migraphx::dead_code_elimination{}}); match::find_matches(m, match_concat_qlinear{}); migraphx::run_passes(m, {migraphx::dead_code_elimination{}}); remove_zero_point(m); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/simplify_reshapes.cpp000066400000000000000000001461101510465702400215640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace { const auto& reshaper_names() { // clang-format off static const std::unordered_set names = { "flatten", "reshape", "contiguous", "squeeze", "unsqueeze" }; // clang-format on return names; } struct find_nested_shape_transforms { static const auto& shape_transform_ops() { static const std::unordered_set names = { "flatten", "reshape", "squeeze", "unsqueeze", "transpose", "broadcast", "multibroadcast", }; return names; } auto matcher() const { auto shape_transform = match::name(shape_transform_ops()); auto output_not_shape_transform = match::none_of(match::skip_output(match::name("contiguous"))(shape_transform)); auto input_has_shape_transform = match::args(match::skip(match::name("contiguous"))(shape_transform)); return shape_transform(output_not_shape_transform, input_has_shape_transform); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; std::vector ops; auto x = ins; while(contains(shape_transform_ops(), x->get_operator().name()) or x->get_operator().name() == "contiguous") { ops.push_back(x->get_operator()); x = x->inputs().front(); } if(x->get_shape().scalar()) { m.replace_instruction( ins, make_op("multibroadcast", {{"out_lens", ins->get_shape().lens()}}), x); } else if(x->get_shape().elements() == 1 and ins->get_shape().elements() == 1) { // TODO: Use squeeze or unsqueeze m.replace_instruction(ins, make_op("reshape", {{"dims", ins->get_shape().lens()}}), x); } else { std::reverse(ops.begin(), ops.end()); auto opt_ops = optimize_shape_transforms(x->get_shape().lens(), ops); if(ops == opt_ops) return; auto y = x; for(const auto& op : opt_ops) y = m.insert_instruction(ins, op, y); m.replace_instruction(ins, y); } } }; struct find_op_shape_transform_op { bool enable = true; static const auto& shape_transform_ops() { static const std::unordered_set names = { "reshape", "squeeze", "unsqueeze", "flatten", "transpose", "contiguous", "multibroadcast", "broadcast", }; return names; } static auto fusable_split() { return match::make_basic_pred_matcher([&](instruction_ref ins) { return any_of(ins->inputs(), [&](instruction_ref input_slice) { if(input_slice->name() != "slice") return false; return all_of(input_slice->inputs().front()->outputs(), [&](instruction_ref slice) { if(slice->name() != "slice") return true; return any_of(slice->outputs(), [&](instruction_ref x) { return x->name() == ins->name(); }); }); }); }); } auto matcher() const { auto reshapes = match::name(shape_transform_ops()); auto match_op = match::any_of(match::reduce(), match::pointwise()); auto x_op = match_op(match::none_of(fusable_split())); auto reshapes_x_op = reshapes(match::arg(0)(match::skip(reshapes())(x_op.bind("x")))); return match_op(match::any_of[match::inputs()](reshapes_x_op.bind("input"))); } static bool matches_op(instruction_ref ins) { return is_reduce(ins) or ins->get_operator().attributes().contains("pointwise"); } static bool is_reduce(instruction_ref ins) { return starts_with(ins->name(), "reduce_"); } template static instruction_ref find_input_if(instruction_ref start, instruction_ref last, F f) { while(start != last) { if(f(start)) return start; if(start->inputs().size() != 1) return last; start = start->inputs().front(); } return last; } template static bool any_input_of(instruction_ref start, instruction_ref last, F f) { return find_input_if(start, last, f) != last; } template static instruction_ref insert(module& m, instruction_ref ins, const std::vector& inputs, const AxesMap& am) { if(is_reduce(ins)) { auto v = ins->get_operator().to_value(); auto op_axes = v.at("axes").to_vector(); std::vector axes; for(auto axis : op_axes) { auto new_axes = am.at(axis); axes.insert(axes.end(), new_axes.begin(), new_axes.end()); } std::sort(axes.begin(), axes.end()); v["axes"] = axes; return m.insert_instruction(ins, make_op(ins->name(), v), inputs, ins->module_inputs()); } if(ins->name() == "layout") { auto v = ins->get_operator().to_value(); auto op_permutation = v.at("permutation").to_vector(); std::vector permutation; for(auto axis : op_permutation) { auto new_axes = am.at(axis); permutation.insert(permutation.end(), new_axes.begin(), new_axes.end()); } v["permutation"] = permutation; return m.insert_instruction(ins, make_op(ins->name(), v), inputs, ins->module_inputs()); } return m.insert_instruction(ins, ins->get_operator(), inputs, ins->module_inputs()); } static bool is_valid(instruction_ref ins, const shape_transform_descriptor& desc) { if(is_reduce(ins)) { auto v = ins->get_operator().to_value(); auto op_axes = v.at("axes").to_vector(); std::sort(op_axes.begin(), op_axes.end()); auto broadcasted_axes = desc.find_broadcasted_axes(); return equal(op_axes, broadcasted_axes); } return not desc.has_broadcast(); } static std::vector generate(const shape_transform_descriptor& desc, const shape& input_shape) { if(input_shape.scalar() and input_shape.elements() == 1 and input_shape.ndim() == 1) { return {make_op("multibroadcast", {{"out_lens", desc.lens()}})}; } else { return desc.generate(input_shape.lens()); } } static shape_transform_descriptor make_descriptor(instruction_ref x_ins, std::vector ops, instruction_ref input_ins) { auto desc1 = shape_transform_descriptor::create(x_ins->get_shape().lens(), ops); auto desc = desc1.rebase(x_ins->inputs().front()->get_shape().lens(), true); if(not desc.empty()) return desc; if(not is_reduce(x_ins)) return desc1; // Find a broadcast to append to improve the reduction analysis auto output_path = get_output_path(input_ins); auto it = std::find_if(output_path.begin(), output_path.end(), [&](instruction_ref ins) { if(ins->get_shape().lens() != input_ins->get_shape().lens()) return true; return contains({"multibroadcast", "broadcast"}, ins->name()); }); if(it == output_path.end()) return {}; if(not contains({"multibroadcast", "broadcast"}, (*it)->name())) return {}; ops.push_back((*it)->get_operator()); return shape_transform_descriptor::create(x_ins->get_shape().lens(), ops) .rebase(x_ins->inputs().front()->get_shape().lens(), true); } void apply(module& m, const match::matcher_result& r) const { if(not enable) return; auto ins = r.result; auto x_ins = r.instructions["x"]; auto input_ins = r.instructions["input"]; // shape_transform_descriptor doesnt handle scalars for now if(input_ins->get_shape().scalar() or x_ins->get_shape().scalar()) return; // If its just a broadcast then skip if(not any_input_of(input_ins, x_ins, [](instruction_ref x) { return not contains({"multibroadcast", "broadcast", "contiguous"}, x->name()); })) return; std::vector ops; auto next_ins = input_ins; while(next_ins != x_ins) { ops.push_back(next_ins->get_operator()); next_ins = next_ins->inputs().front(); } assert(next_ins == x_ins); std::reverse(ops.begin(), ops.end()); auto desc = make_descriptor(x_ins, ops, input_ins); if(desc.empty()) return; if(not is_valid(x_ins, desc)) return; // If we already in the common dimension space then skip if there are other outputs to avoid // infinite loop if(ins->get_shape().ndim() == desc.common_rank() and std::any_of(x_ins->outputs().begin(), x_ins->outputs().end(), [&](instruction_ref out) { return matches_op(out); })) { return; } auto reshape_input = [&](const auto& ins_to_insert, const auto& gdesc) { return [&](auto input) { auto gops = generate(gdesc, input->get_shape()); return std::accumulate( gops.begin(), gops.end(), input, [&](auto start, const auto& op) { return m.insert_instruction(ins_to_insert, op, start); }); }; }; auto x_inputs = x_ins->inputs(); std::transform(x_inputs.begin(), x_inputs.end(), x_inputs.begin(), reshape_input(x_ins, desc.to_common_from_src())); auto new_input_ins = insert(m, x_ins, x_inputs, desc.common_axes_map_from_src()); auto new_x_ins = reshape_input(x_ins, desc.to_src_from_common())(new_input_ins); if(new_input_ins->get_shape().elements() != input_ins->get_shape().elements()) { auto cdims = desc.common_dims(); new_input_ins = m.insert_instruction( x_ins, make_op("multibroadcast", {{"out_lens", cdims}}), new_input_ins); } auto inputs = ins->inputs(); std::transform(inputs.begin(), inputs.end(), inputs.begin(), [&](auto input) { if(input == input_ins) return new_input_ins; return reshape_input(ins, desc.to_common_from_dst())(input); }); // Replace old x_ins just in case it is used more than once assert(x_ins->get_shape().lens() == new_x_ins->get_shape().lens()); m.replace_instruction(x_ins, new_x_ins); // Replace final instruction auto pw = insert(m, ins, inputs, desc.common_axes_map_from_dst()); auto rins = reshape_input(ins, desc.to_dst_from_common())(pw); assert(ins->get_shape().lens() == rins->get_shape().lens()); m.replace_instruction(ins, rins); } }; struct find_nop_reshapes { auto matcher() const { auto reshapes = reshaper_names(); reshapes.insert("as_shape"); reshapes.insert("broadcast"); reshapes.insert("concat"); reshapes.insert("convert"); reshapes.insert("multibroadcast"); reshapes.insert("pad"); reshapes.insert("slice"); reshapes.insert("step"); reshapes.insert("transpose"); reshapes.insert("reduce_mean"); reshapes.insert("reduce_max"); reshapes.insert("reduce_min"); reshapes.insert("reduce_sum"); reshapes.insert("reduce_prod"); return match::name(reshapes)(match::same_shape(match::arg(0))); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; m.replace_instruction(ins, ins->inputs().front()); } }; struct find_nested_slice { auto matcher() const { return match::name("slice")(match::arg(0)(match::name("slice"))); } using axes_map = std::map>; static axes_map get_axes(instruction_ref ins) { axes_map result; auto op = any_cast(ins->get_operator()); for(std::size_t i = 0; i < op.axes.size(); i++) { result[op.axes[i]] = std::make_pair(op.starts[i], op.ends[i]); } return result; } static axes_map merge(const axes_map& m1, const axes_map& m2) { axes_map result; // Non overlapping for(auto&& p : m1) { if(contains(m2, p.first)) continue; result[p.first] = p.second; } for(auto&& p : m2) { if(contains(m1, p.first)) continue; result[p.first] = p.second; } // Overlapping for(auto&& p1 : m1) { if(not contains(m2, p1.first)) continue; auto&& v1 = p1.second; auto&& v2 = m2.at(p1.first); auto start = v1.first + v2.first; auto end = start + (v2.second - v2.first); result[p1.first] = std::make_pair(start, end); } return result; } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto slice = ins->inputs().front(); auto input = slice->inputs().front(); auto a1 = get_axes(ins); auto a2 = get_axes(slice); auto axes = merge(a2, a1); auto op = op::slice{}; for(auto&& pp : axes) { op.axes.push_back(pp.first); op.starts.push_back(pp.second.first); op.ends.push_back(pp.second.second); } m.replace_instruction(ins, op, input); } }; /** * Example case * From: * param0: lens = [3, 4], strides = [4, 1] * param1: lens = [3, 4], strides = [4, 1] * mb0: multibroadcast(param0, output_lens = [2, 3, 4]) * mb1: multibroadcast(param1, output_lens = [2, 3, 4]) * concat(mb0, mb1, axis = 2) * * To: * param0: lens = [3, 4], strides = [4, 1] * param1: lens = [3, 4], strides = [4, 1] * con0: concat(param0, param1, axis = 1) * multibroadcast(con0, lens = [2, 3, 4]) */ struct find_concat_multibroadcasts { auto matcher() const { return match::name("concat")( match::all_of[match::inputs()](match::name("multibroadcast", "broadcast"))); } void apply(module& m, const match::matcher_result& mr) const { auto concat_ins = mr.result; auto concat_op = any_cast(concat_ins->get_operator()); auto concat_out_lens = concat_ins->get_shape().lens(); auto concat_inputs = concat_ins->inputs(); auto front_mb_strides = concat_inputs.front()->get_shape().strides(); assert(concat_op.axis >= 0); // Only apply when concat axis is not a broadcasted dimension if(std::any_of(concat_inputs.begin(), concat_inputs.end(), [&](auto i) { return i->get_shape().strides()[concat_op.axis] == 0; })) { return; } // Skip if the broadcasts are different auto broadcast = concat_inputs.front()->get_operator(); auto broadcast_value = broadcast.to_value(); if(not std::all_of(concat_inputs.begin() + 1, concat_inputs.end(), [&](instruction_ref b) { if(b->name() != broadcast.name()) return false; if(broadcast.name() == "broadcast") return b->get_operator().to_value()["axis"] == broadcast_value["axis"]; return true; })) { return; } // Get the inputs of multibroadcast ops. Will be used as inputs to new concat op std::vector inputs(concat_inputs.size()); std::transform(concat_inputs.begin(), concat_inputs.end(), inputs.begin(), [](auto i) { return i->inputs().front(); }); // Check that the inputs into the broadcasts have the same rank const auto& first_shape = inputs.front()->get_shape(); if(not std::all_of(inputs.begin() + 1, inputs.end(), [&](auto input) { return input->get_shape().ndim() == first_shape.ndim(); })) { return; } // Reduce axis by number of leading broadcasted dimensions if(inputs.front()->get_shape().lens().size() < concat_out_lens.size()) { concat_op.axis -= std::count(front_mb_strides.begin(), front_mb_strides.begin() + concat_op.axis, 0); } // Inputs to broadcasts should have the same dimensions except for the axis to // concatenate over const auto& front_in_lens = inputs.front()->get_shape().lens(); if(not std::all_of(inputs.begin() + 1, inputs.end(), [&](auto input_to_mb) { const auto& lens = input_to_mb->get_shape().lens(); return std::equal( lens.begin(), lens.begin() + concat_op.axis, front_in_lens.begin()) and std::equal(lens.begin() + concat_op.axis + 1, lens.end(), front_in_lens.begin() + concat_op.axis + 1); })) { return; } auto new_concat_ins = m.insert_instruction(concat_ins, concat_op, inputs); broadcast.from_value({{"out_lens", concat_ins->get_shape().lens()}}); m.replace_instruction(concat_ins, broadcast, new_concat_ins); } }; struct find_concat_slice { auto matcher() const { return match::name("concat")(match::any_of[match::outputs()](match::name("slice"))); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto inputs = ins->inputs(); auto outs = ins->outputs(); std::vector slice_ins; migraphx::transform_if( outs.begin(), outs.end(), std::back_inserter(slice_ins), [&](const auto& oins) { return oins->name() == "slice"; }, [&](const auto& oins) { return oins; }); int concat_axis = any_cast(ins->get_operator()).axis; // prune slice candidates std::vector slice_candidates; for(const auto& sins : range(slice_ins.begin(), slice_ins.end())) { auto sop = any_cast(sins->get_operator()); // slices with only one axis is allowed, because concat happens only one axis if(sop.axes.size() != 1 or sop.axes.front() != concat_axis) { continue; } slice_candidates.push_back(sins); } if(slice_candidates.empty()) { return; } std::vector prefix_scan = {0}; std::transform( inputs.begin(), inputs.end(), std::back_inserter(prefix_scan), [&](const auto& i) { return prefix_scan.back() + i->get_shape().lens()[concat_axis]; }); for(const auto& sins : slice_candidates) { auto sop = any_cast(sins->get_operator()); size_t slice_start = sop.starts.front(); size_t slice_len = sop.ends.front() - slice_start; auto fii = std::find_if(prefix_scan.begin(), prefix_scan.end(), [&](const auto& j) { return j == slice_start; }); if(fii == prefix_scan.end()) { continue; } // slice_len == 0 else if(fii == prefix_scan.end() - 1) { assert(slice_len == 0 or slice_start >= prefix_scan.back()); continue; } else { size_t idx = std::distance(prefix_scan.begin(), fii); if(inputs[idx]->get_shape().lens()[concat_axis] == slice_len) { assert((prefix_scan[idx + 1] - prefix_scan[idx]) == slice_len); m.replace_instruction(sins, inputs[idx]); } } } } }; struct find_concat_transpose { auto matcher() const { return match::name("concat")(match::all_of[match::inputs()](match::name("transpose"))); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto trans_inputs = ins->inputs(); auto s = trans_inputs.front()->get_shape(); assert(s.transposed()); auto op = any_cast(ins->get_operator()); auto permutation = find_permutation(s); // permutation should be the same for all inputs if(not std::all_of(trans_inputs.begin(), trans_inputs.end(), [&](auto in) { return (find_permutation(in->get_shape()) == permutation); })) { return; } // axis could be a negative value int64_t n_dim = s.lens().size(); op.axis = tune_axis(n_dim, op.axis, op.name()); auto ipermutation = invert_permutation(permutation); op.axis = ipermutation[op.axis]; std::vector inputs; std::transform( ins->inputs().begin(), ins->inputs().end(), std::back_inserter(inputs), [&](auto i) { return m.insert_instruction( ins, make_op("transpose", {{"permutation", permutation}}), i); }); auto concat = m.insert_instruction(ins, op, inputs); auto t = m.insert_instruction( ins, make_op("transpose", {{"permutation", ipermutation}}), concat); assert(ins->get_shape().lens() == t->get_shape().lens()); m.replace_instruction(ins, t); } }; struct find_concat_reshape { auto matcher() const { return match::name("concat")(match::all_of[match::inputs()]( match::name("reshape", "unsqueeze", "squeeze", "reshape_lazy"))); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto concat_shape = ins->get_shape(); auto reshapes = ins->inputs(); if(reshapes.empty()) return; auto input_shape = reshapes.front()->inputs().front()->get_shape(); // All inputs should have the same dimensions if(not std::all_of( std::next(reshapes.begin()), reshapes.end(), [&](instruction_ref reshape) { return reshape->inputs().front()->get_shape().lens() == input_shape.lens(); })) return; // axis could be a negative value auto op = any_cast(ins->get_operator()); int64_t n_dim = reshapes.front()->get_shape().lens().size(); auto axis = tune_axis(n_dim, op.axis, op.name()); auto predims = std::accumulate(concat_shape.lens().begin(), concat_shape.lens().begin() + axis, std::size_t{1}, std::multiplies<>{}); auto postdims = std::accumulate(concat_shape.lens().begin() + axis + 1, concat_shape.lens().end(), std::size_t{1}, std::multiplies<>{}); // Find the axis on the input std::size_t x = 1; auto it = std::find_if(input_shape.lens().begin(), input_shape.lens().end(), [&](auto d) { x *= d; return x > predims; }); if(it == input_shape.lens().end()) return; op.axis = it - input_shape.lens().begin(); auto ipredims = std::accumulate(input_shape.lens().begin(), input_shape.lens().begin() + op.axis, std::size_t{1}, std::multiplies<>{}); if(ipredims != predims) return; auto ipostdims = std::accumulate(input_shape.lens().begin() + op.axis + 1, input_shape.lens().end(), std::size_t{1}, std::multiplies<>{}); if(ipostdims != postdims) return; std::vector inputs; std::transform(reshapes.begin(), reshapes.end(), std::back_inserter(inputs), [&](instruction_ref i) { return i->inputs().front(); }); auto concat = m.insert_instruction(ins, op, inputs); m.replace_instruction(ins, make_op("reshape", {{"dims", concat_shape.lens()}}), concat); } }; struct find_nested_concat { auto matcher() const { return match::name("concat")(match::any_of[match::inputs()](match::name("concat"))); } static std::size_t get_axis(instruction_ref ins) { auto op = any_cast(ins->get_operator()); return op.axis; } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; auto axis = get_axis(ins); std::vector args; fix([&](auto self, auto&& inputs) { for(auto&& i : inputs) { if(i->name() == "concat" and get_axis(i) == axis and i->outputs().size() == 1) self(i->inputs()); else args.push_back(i); } })(ins->inputs()); m.replace_instruction(ins, ins->get_operator(), args); } }; struct find_resize { auto matcher() const { return match::name("gather")( match::args(match::name("reshape").bind("data"), match::is_constant().bind("ind"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto ins_rsp = r.instructions["data"]; auto ins_ind = r.instructions["ind"]; // resize input shape if(ins_rsp->get_shape().lens().size() != 1) { return; } // resize output shape const auto& in_shape = ins_rsp->inputs().front()->get_shape(); const auto& out_shape = ins->get_shape(); // check if output shape is multiple of input shape const auto& in_lens = in_shape.lens(); const auto& out_lens = out_shape.lens(); if(in_lens.size() != out_lens.size()) { return; } // output shape must be multiple of input shape std::vector is_multi(in_lens.size()); std::transform( in_lens.begin(), in_lens.end(), out_lens.begin(), is_multi.begin(), [](auto x, auto y) { return (y % x == 0); }); if(not std::all_of(is_multi.begin(), is_multi.end(), [](auto b) { return b; })) { return; } // output must be multiple of inputs std::vector scales(in_lens.size()); std::transform( in_lens.begin(), in_lens.end(), out_lens.begin(), scales.begin(), [](auto x, auto y) { return y / x; }); // if ind is not constant, cannot optimize std::vector vec_ind; auto arg_ind = ins_ind->eval(); if(arg_ind.empty()) { return; } arg_ind.visit([&](auto v) { vec_ind.assign(v.begin(), v.end()); }); if(not all_of(range(out_shape.elements()), [&](auto i) { auto out_idx = out_shape.multi(i); auto in_idx = out_idx; std::transform(out_idx.begin(), out_idx.end(), scales.begin(), in_idx.begin(), [&](auto io, auto scale) { return io - (io % scale); }); return vec_ind[i] == vec_ind[out_shape.index(in_idx)]; })) { return; } // wrap up shapes for multibroadcast std::vector> dim_scales; std::transform(in_lens.begin(), in_lens.end(), out_lens.begin(), std::back_inserter(dim_scales), [](auto x, auto y) { return std::make_pair(x, y / x); }); std::vector in_dims; std::vector out_dims; for(auto& isp : dim_scales) { in_dims.push_back(isp.first); out_dims.push_back(isp.first * isp.second); if(isp.first == 1 or isp.second == 1) { continue; } out_dims.back() = isp.first; in_dims.push_back(1); out_dims.push_back(isp.second); } auto in_rsp = ins_rsp->inputs().front(); auto rsp_data = m.insert_instruction( ins_rsp, migraphx::make_op("reshape", {{"dims", in_dims}}), in_rsp); auto mb_rsp = m.insert_instruction( ins_rsp, migraphx::make_op("multibroadcast", {{"out_lens", out_dims}}), rsp_data); std::vector rsp_dims(out_lens.begin(), out_lens.end()); m.replace_instruction(ins, migraphx::make_op("reshape", {{"dims", rsp_dims}}), mb_rsp); } }; struct find_where_op { auto matcher() const { return match::name("gather")( match::args(match::name("reshape")(match::arg(0)(match::name("concat").bind("data"))), match::is_constant().bind("ind"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto concat = r.instructions["data"]; auto ins_ind = r.instructions["ind"]; std::vector vec_ind; auto arg_ind = ins_ind->eval(); arg_ind.visit([&](auto v) { vec_ind.assign(v.begin(), v.end()); }); // ind has to be the same value auto val = vec_ind.front(); if(not std::all_of(vec_ind.begin(), vec_ind.end(), [&](auto v) { return (v == val); })) { return; } // concat axis must be 0 auto op = any_cast(concat->get_operator()); if(op.axis != 0) { return; } // check concat inputs, it has to be 2 and have the same shape const auto& inputs = concat->inputs(); if(inputs.size() != 2) { return; } if(inputs.at(0)->get_shape() != inputs.at(1)->get_shape()) { return; } if(inputs.at(0)->get_shape().lens() != ins_ind->get_shape().lens()) { return; } if(val) { m.replace_instruction(ins, inputs.at(0)); } else { m.replace_instruction(ins, inputs.at(1)); } } }; struct find_reshape_cont { auto matcher() const { auto contiguous = match::skip(match::name("contiguous"))( match::none_of(match::standard_shape()).bind("input")); auto reshape_contiguous = match::name("reshape")(match::args(contiguous)); return match::pointwise( match::nargs(2), match::either_arg(0, 1)(reshape_contiguous.bind("rsp"), match::any())); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto cont_input = r.instructions["input"]; auto in_ins = r.instructions["rsp"]; auto lens = cont_input->get_shape().lens(); std::vector dims(lens.begin(), lens.end()); if(in_ins->get_shape() != ins->get_shape()) { return; } if(not std::all_of(ins->inputs().begin(), ins->inputs().end(), [](auto i) { return i->get_shape().standard(); })) { return; } auto out_lens = ins->get_shape().lens(); std::vector out_dims(out_lens.begin(), out_lens.end()); std::vector inputs; for(const auto& in : ins->inputs()) { if(in == in_ins) { inputs.push_back(cont_input); } else { inputs.push_back( m.insert_instruction(ins, make_op("reshape", {{"dims", dims}}), in)); } } auto out = m.insert_instruction(ins, ins->get_operator(), inputs); m.replace_instruction(ins, make_op("reshape", {{"dims", out_dims}}), out); } }; struct find_unary_shape_transforms { static const auto& shape_transforms() { static const std::unordered_set names = { "flatten", "reshape", "squeeze", "unsqueeze", "transpose", "broadcast", "multibroadcast", }; return names; } auto matcher() const { auto output_not_pointwise = match::none_of(match::skip_output(match::name("contiguous"))(match::pointwise())); auto shape_transform = match::name(shape_transforms()); auto input_has_shape_transform = match::args(match::skip(match::name("contiguous"))(shape_transform)); auto not_layout = match::none_of(match::name("layout")); return match::pointwise( match::used_once(), not_layout, input_has_shape_transform, output_not_pointwise); } static bool is_shape_transform(instruction_ref ins) { return ins->inputs().size() == 1 and (contains(shape_transforms(), ins->name()) or ins->name() == "contiguous"); } static bool can_fuse_unary(instruction_ref ins) { return ins->name() == "@literal" or ins->get_operator().attributes().contains("pointwise") or contains(ins->name(), "reduce"); } void apply(module& m, const match::matcher_result& mr) const { auto ins = mr.result; if(ins->outputs().empty()) return; auto input = ins->inputs().front(); auto output = ins->outputs().front(); auto insert_ops = [&](const auto& ops, instruction_ref z) { for(const auto& op : ops) { z = m.insert_instruction(ins, op, z); } return z; }; std::vector xops; auto x = input; while(is_shape_transform(x)) { xops.push_back(x->get_operator()); x = x->inputs().front(); } std::reverse(xops.begin(), xops.end()); std::vector yops; auto y = output; auto last_transform = m.end(); while(is_shape_transform(y) and y->outputs().size() == 1) { yops.push_back(y->get_operator()); last_transform = y; y = y->outputs().front(); } bool move_up = can_fuse_unary(x); bool move_down = can_fuse_unary(y); if(move_up and move_down) { if(x->name() == "@literal") move_down = false; // NOLINT(bugprone-branch-clone) else if(yops.empty()) move_up = false; else move_down = false; } else if(not move_up and not move_down) { if(not yops.empty()) move_up = true; } if(move_up) { auto z = m.insert_instruction(ins, ins->get_operator(), x); z = insert_ops(xops, z); m.replace_instruction(ins, z); } else if(move_down and not yops.empty()) { auto z = insert_ops(yops, input); m.replace_instruction(last_transform, ins->get_operator(), z); } } }; struct find_slice_transpose { auto matcher() const { auto transpose = match::output(match::name("transpose")); return match::any(match::any_of[match::outputs()](match::name("slice")(transpose))); } static std::vector find_common_perm(const std::vector& transposes) { std::map, int64_t> count; for(auto t : transposes) { auto perm = t->get_operator().to_value()["permutation"].to_vector(); count[perm]++; } return std::max_element( count.begin(), count.end(), by(std::less<>{}, [](auto&& p) { return p.second; })) ->first; } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; std::vector splits; std::copy_if(ins->outputs().begin(), ins->outputs().end(), std::back_inserter(splits), [&](instruction_ref out) { return out->name() == "slice" and out->outputs().size() == 1 and out->outputs().front()->name() == "transpose"; }); if(splits.size() < 2) return; std::vector transposes; std::transform(splits.begin(), splits.end(), std::back_inserter(transposes), [](auto split) { return split->outputs().front(); }); auto perm = find_common_perm(transposes); auto iperm = invert_permutation(perm); auto pre = m.insert_instruction( std::next(ins), make_op("transpose", {{"permutation", perm}}), ins); for(auto i : range(transposes.size())) { auto split = splits[i]; auto t = transposes[i]; auto op = any_cast(split->get_operator()); std::transform(op.axes.begin(), op.axes.end(), op.axes.begin(), [&](auto axis) { return iperm[axis]; }); auto new_ins = m.insert_instruction(t, op, pre); if(t->get_operator() != pre->get_operator()) { auto curr = t->get_operator().to_value()["permutation"].to_vector(); new_ins = m.insert_instruction( t, make_op("transpose", {{"permutation", reorder_dims(iperm, curr)}}), new_ins); } m.replace_instruction(t, new_ins); } } }; struct find_transpose_slice { auto matcher() const { return match::name("transpose")(match::all_of[match::outputs()](match::name("slice"))); } static std::vector slice_distance(const op::slice& op) { assert(op.starts.size() == op.ends.size()); std::vector result(op.starts.size()); std::transform( op.ends.begin(), op.ends.end(), op.starts.begin(), result.begin(), std::minus<>{}); return result; } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto slices = ins->outputs(); if(slices.empty()) return; auto slice = any_cast(slices.front()->get_operator()); auto sdistance = slice_distance(slice); // Check all distances and axes are the same if(std::any_of(slices.begin(), slices.end(), [&](auto sins) { auto s = any_cast(sins->get_operator()); return s.axes != slice.axes or slice_distance(s) != sdistance; })) return; // Check distances are divisible by lens of corresponding axes auto mod_by_distance = [&](const auto& v, auto f) { return std::inner_product(v.begin(), v.end(), sdistance.begin(), 0, std::plus<>{}, [&](auto x, auto d) -> uint64_t { if(d == 0) return 1; return f(x) % d; }); }; if(mod_by_distance(slice.axes, [&](auto x) { return ins->get_shape().lens()[x]; }) != 0 or mod_by_distance(slice.starts, id{}) != 0 or mod_by_distance(slice.ends, id{}) != 0) return; // TODO: Handle multiple axes if(sdistance.size() != 1) return; auto axis = slice.axes.front(); // Skip if axis would be packed if(std::all_of(ins->get_shape().lens().begin(), ins->get_shape().lens().begin() + axis, [](auto x) { return x == 1; })) return; // Compute axis before transpose to use for unsqueeze auto perm = ins->get_operator().to_value()["permutation"].to_vector(); auto preaxis = perm[axis]; // Make unsqueeze std::vector steps(sdistance.size()); std::transform( slice.axes.begin(), slice.axes.end(), sdistance.begin(), steps.begin(), [&](const auto ax, const auto sdis) { return ins->get_shape().lens().at(ax) / sdis; }); auto unsqueeze = m.insert_instruction( ins, make_op("unsqueeze", {{"axes", {preaxis}}, {"steps", steps}}), ins->inputs()); // Make transpose std::transform(perm.begin(), perm.end(), perm.begin(), [&](auto i) { if(i >= preaxis) return i + 1; return i; }); perm.insert(perm.begin(), preaxis); auto transpose = m.insert_instruction(ins, make_op("transpose", {{"permutation", perm}}), unsqueeze); // Slice and squeeze for(auto s : slices) { auto op = any_cast(s->get_operator()); op.axes = {0}; op.starts = {op.starts.front() / sdistance.front()}; op.ends = {op.ends.front() / sdistance.front()}; auto slice_ins = m.insert_instruction(ins, op, transpose); auto squeeze = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), slice_ins); m.replace_instruction(s, squeeze); } } }; struct find_reshape_dot { auto matcher() const { auto rsp = match::name("reshape").bind("rsp"); auto other = match::skip_broadcasts(match::any().bind("other")); return match::name("dot")(match::used_once(), match::either_arg(0, 1)(rsp, other)); } // Gemm axis should not be altered by the reshape auto is_valid_reshape(instruction_ref inp, instruction_ref rsp, size_t dot_axis) const { auto inp_lens = inp->get_shape().lens(); auto rsp_lens = rsp->get_shape().lens(); return (inp_lens.size() >= dot_axis and rsp_lens[rsp_lens.size() - dot_axis] == inp_lens[inp_lens.size() - dot_axis]); } // Same batch dims auto has_same_batch_dims(instruction_ref in1, instruction_ref in2) const { auto in1_lens = in1->get_shape().lens(); auto in2_lens = in2->get_shape().lens(); return ( in1_lens.size() == in2_lens.size() and std::equal(in1_lens.begin(), in1_lens.end() - 2, in2_lens.begin(), in2_lens.end() - 2)); } void apply(module& m, const match::matcher_result& r) const { auto dot = r.result; auto rsp = r.instructions["rsp"]; auto other = r.instructions["other"]; auto rsp_lens = rsp->get_shape().lens(); auto inp = rsp->inputs().front(); auto inp_lens = inp->get_shape().lens(); // Gemm axis should not be altered by the reshape bool flipped = rsp == dot->inputs().back(); size_t dot_axis = (flipped) ? 2 : 1; if(not is_valid_reshape(inp, rsp, dot_axis)) return; instruction_ref new_other; if(other->get_operator().name() == "reshape") { auto other_inp = other->inputs().front(); size_t other_dot_axis = (flipped) ? 1 : 2; if(not is_valid_reshape(other_inp, other, other_dot_axis) or not has_same_batch_dims(inp, other_inp)) return; new_other = other_inp; } else { auto other_lens = other->get_shape().lens(); if(other_lens.size() > 2) return; std::vector new_other_lens{inp_lens.begin(), inp_lens.end() - 2}; operation new_bc_op; auto bc_other = (flipped) ? dot->inputs().front() : dot->inputs().back(); auto bc_other_lens = bc_other->get_shape().lens(); new_other_lens.insert( new_other_lens.end(), bc_other_lens.end() - 2, bc_other_lens.end()); // if the original weight is one dimensional, look at the original broadcast // to determine the correct broadcast axis if(other_lens.size() == 1) { auto bc_other_strides = bc_other->get_shape().strides(); auto it = std::find_if(bc_other_strides.begin(), bc_other_strides.end(), [&](auto i) { return i != 0; }); auto orig_bc_axis = std::distance(bc_other_strides.begin(), it); auto new_bc_axis = new_other_lens.size() - (bc_other_lens.size() - orig_bc_axis); new_bc_op = make_op("broadcast", {{"axis", new_bc_axis}, {"out_lens", new_other_lens}}); } else { new_bc_op = make_op("multibroadcast", {{"out_lens", new_other_lens}}); } new_other = m.insert_instruction(dot, new_bc_op, other); } instruction_ref new_dot; if(flipped) { new_dot = m.insert_instruction(dot, make_op("dot"), new_other, inp); } else { new_dot = m.insert_instruction(dot, make_op("dot"), inp, new_other); } m.replace_instruction( dot, make_op("reshape", {{"dims", dot->get_shape().lens()}}), new_dot); } }; // Remove transposes and converts between mul/add -> dot so simplify_algebra can perform // const folding simplifications struct find_mul_add_shape_op_dot { auto matcher() const { auto shape_ops = match::name("transpose", "convert"); auto const_mul_add = match::name("mul", "add")(match::either_arg(0, 1)( match::is_constant().bind("const"), match::any().bind("input"))); auto match_shape_op = shape_ops(match::args(const_mul_add.bind("pw"))); auto skip_shape_op_outputs = match::skip_output(match::any_of(shape_ops)); return match_shape_op(skip_shape_op_outputs(match::name("dot"))); } void apply(module& m, const match::matcher_result& r) const { auto shape_ins = r.result; auto pw = r.instructions["pw"]; auto constant = r.instructions["const"]; auto input = r.instructions["input"]; auto shape_op = shape_ins->get_operator(); auto pw_op = pw->get_operator(); auto new_inp = m.insert_instruction(shape_ins, shape_op, input); auto new_const = m.insert_instruction(shape_ins, shape_op, constant); m.replace_instruction(shape_ins, pw_op, new_inp, new_const); } }; struct find_flatten { auto matcher() const { return match::name("flatten"); } void apply(module& m, const match::matcher_result& r) const { auto flatten = r.result; m.replace_instruction(flatten, make_op("reshape", {{"dims", flatten->get_shape().lens()}}), flatten->inputs()); } }; } // namespace void simplify_reshapes::apply(module& m) const { m.repeat_while_changes(depth, [&] { match::find_matches(m, find_where_op{}, find_resize{}, find_nop_reshapes{}, find_flatten{}, find_reshape_cont{}, find_nested_shape_transforms{}, find_concat_slice{}, find_concat_transpose{}, find_concat_reshape{}, find_concat_multibroadcasts{}, find_nested_slice{}, find_nested_concat{}, find_transpose_slice{}, find_slice_transpose{}, find_unary_shape_transforms{}, find_reshape_dot{}, find_mul_add_shape_op_dot{}, find_op_shape_transform_op{.enable = enable_op_shape_transform_op}); dead_code_elimination{}.apply(m); }); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/split_reduce.cpp000066400000000000000000000206641510465702400205250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct split_fused_reduce { std::vector axes{}; std::string assign = "assign_none"; template static auto reflect(Self& self, F f) { return pack(f(self.axes, "axes"), f(self.assign, "assign")); } value attributes() const { return {{"prefill", 0}}; } shape compute_shape(const std::vector& inputs, std::vector mods) const { if(mods.size() != 1) MIGRAPHX_THROW("should have one submodule."); const auto* sm = mods.front(); auto names = sm->get_parameter_names(); check_shapes{inputs, *this}.has(names.size()).same_ndims(); auto result = sm->compute_shapes(inputs, {.name = name(), .strict_type = true, .strict_lens = true}); if(result.size() == 1) return result.front(); return shape{result}; } std::string name() const { return "split_fused_reduce"; } }; MIGRAPHX_REGISTER_OP(split_fused_reduce); static bool is_reduce(const instruction& ins) { return contains(ins.name(), "reduce"); } namespace { struct splitter { const_module_ref rm; bool strictly_dominate(instruction_ref a, instruction_ref b) { if(not dom.has_value()) dom = compute_dominator(*rm); return dom->strictly_dominate(a, b); } std::vector find_splits() const { std::vector result; copy_if(iterator_for(*rm), std::back_inserter(result), [](auto ins) { return is_reduce(*ins); }); if(result.size() > 2) return {}; // Only handle reduce_sum for now // TODO: Support other reduction types if(not std::all_of(result.begin(), result.end(), [](instruction_ref ins) { return ins->name() == "reduce_sum"; })) return {}; if(result.size() < 2) return result; if(reaches(result[0], result[1])) return {}; return result; } std::vector find_alive(const std::vector& splits) { std::vector result; bool stop = false; liveness(*rm, [&](auto rins, const auto& live_set) { if(stop) return; if(rins == rm->begin()) return; // We want to know what instructions are live after the split instruction auto ins = instruction::get_output_alias(std::prev(rins)); if(not contains(splits, ins)) return; std::copy_if(live_set.begin(), live_set.end(), std::back_inserter(result), [&](instruction_ref live) { if(live->name() == "@param") return false; if(contains(splits, live)) return false; if(splits.size() > 1 and none_of(splits, [&](instruction_ref split) { return this->strictly_dominate(live, split); })) return false; return true; }); stop = true; }); return result; } std::optional dom = std::nullopt; }; } // namespace static std::string assign_op(const std::vector& splits) { static std::unordered_map m = { {"reduce_sum", "assign_add"}, {"reduce_mean", "assign_add"}, {"reduce_prod", "assign_mul"}, {"reduce_max", "assign_max"}, {"reduce_min", "assign_min"}, }; return m.at(splits.front()->name()); } static std::vector insert_module_inline(module& m, instruction_ref ins, const module::with_inputs& mwi) { auto param_map = mwi.mod.get_ins_param_map(mwi.inputs, true); return m.insert_instructions(ins, &mwi.mod, ¶m_map); } static std::size_t get_reduce_size(const_module_ref rm) { auto ins = std::find_if(rm->begin(), rm->end(), &is_reduce); assert(ins != rm->end()); return ins->inputs().front()->get_shape().elements() / ins->get_shape().elements(); } void split_reduce::apply(module_pass_manager& mpm) const { for(auto ins : iterator_for(mpm.get_module())) { if(ins->name() != "fused_reduce") continue; auto* rm = ins->module_inputs().front(); if(get_reduce_size(rm) < split_size) continue; splitter s{rm}; auto splits = s.find_splits(); if(splits.empty()) continue; // Only use split reduce with float for now // TODO: Support other data types if(not std::all_of(splits.begin(), splits.end(), [](instruction_ref split) { return contains({shape::float_type, shape::half_type}, split->get_shape().type()); })) continue; auto v = ins->get_operator().to_value(); auto axes = v["axes"].to_vector(); auto alive = s.find_alive(splits); std::array mods; if(not alive.empty()) { auto mods3 = rm->split(ins->inputs(), alive, splits); auto r = insert_module_inline(mpm.get_module(), ins, mods3[0]); mods3[1].replace(alive, r); mods3[2].replace(alive, r); mods = {std::move(mods3[1]), std::move(mods3[2])}; } else { mods = rm->split(ins->inputs(), splits); } auto* splitm = mpm.create_module(rm->name() + "_split", std::move(mods[0].mod)); splitm->set_bypass(); // Insert split reduce auto split_reduce = mpm.get_module().insert_instruction( ins, make_op("split_fused_reduce", {{"axes", axes}, {"assign", assign_op(splits)}}), mods[0].inputs, {splitm}); std::vector split_reduce_each; if(splits.size() == 1) { split_reduce_each = {split_reduce}; } else { transform(range(splits.size()), std::back_inserter(split_reduce_each), [&](auto i) { return mpm.get_module().insert_instruction( ins, make_op("get_tuple_elem", {{"index", i}}), split_reduce); }); } mods[1].replace(splits, split_reduce_each); auto replaced = insert_module_inline(mpm.get_module(), ins, mods[1]); assert(replaced.size() == 1); mpm.get_module().replace_instruction(ins, replaced.front()); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/split_single_dyn_dim.cpp000066400000000000000000000162101510465702400222320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct dynamic_dimensions_check { std::string dyn_param_str; shape::dynamic_dimension dd; }; /** * Returns value if the parameters contain non-fixed dynamic_dimensions that are the same between * all of the dynamic shape parameters. * In other words, each parameter can have one non-fixed dynamic_dimension `x` where `x` is the same * between all of the parameters with a non-fixed dynamic_dimension. * Returns the parameters and the dynamic dimension in a vector of dynamic_dimensions_check objects. */ static optional> has_one_unique_dyn_dim(const std::unordered_map& param_shapes) { auto is_dynamic = [](const auto& p) { return p.second.dynamic(); }; std::vector::value_type> dyn_params{}; std::copy_if( param_shapes.begin(), param_shapes.end(), std::back_inserter(dyn_params), is_dynamic); if(dyn_params.empty()) return std::nullopt; std::vector ret{}; // get non-fixed dynamic_dimension from all parameters for(const auto& param : dyn_params) { const auto& dds = param.second.dyn_dims(); auto num_non_fixed = std::count_if(dds.cbegin(), dds.cend(), [&](const auto& dd) { if(not dd.is_fixed()) { ret.push_back(dynamic_dimensions_check{param.first, dd}); return true; } return false; }); // catch more than one non-fixed dynamic_dimension if(num_non_fixed > 1) { return std::nullopt; } } if(ret.empty()) { return std::nullopt; } // check all the same dynamic_dimension bool same_dd = std::all_of( ret.begin() + 1, ret.end(), [&](const auto& ddc) { return ddc.dd == ret.at(0).dd; }); if(same_dd) { return ret; } return std::nullopt; } /** * Check the parameters in std::vector object to see if any of the * parameters outputs to a select_module operator. */ static bool any_sm_next(const_module_ref mm, const std::vector& ddcs) { for(const auto& ddc : ddcs) { auto p_outputs = mm->get_parameter(ddc.dyn_param_str)->outputs(); bool is_sm_next = std::any_of(p_outputs.cbegin(), p_outputs.cend(), [](auto ins) { return ins->name() == "select_module"; }); if(is_sm_next) { return true; }; } return false; } /** * Makes all the shapes in the dynamic_dimension range. Probably won't work for `if` * and `loop` instructions, depending on how the submodules for those * work. Inserts select_module instruction to the top. Replaces return, bypassing other * instructions. Skips if the dynamic parameter outputs to a select_module operator. */ void split_single_dyn_dim::apply(module_pass_manager& mpm) const { module_ref mm = &mpm.get_module(); auto param_names = mm->get_parameter_names(); auto param_shapes = mm->get_parameter_shapes(); optional> dd_check_vec = has_one_unique_dyn_dim(param_shapes); if(dd_check_vec.has_value() and not any_sm_next(mm, dd_check_vec.value())) { // all dynamic dimension objects should be the same for all parameters in dd_check_vec auto dyn_dim = dd_check_vec->at(0).dd; // create submodules for each dimension size std::vector submodules; for(size_t dim_size : migraphx::range(dyn_dim.min, dyn_dim.max + 1)) { auto* submod = mpm.create_module("dim_" + std::to_string(dim_size)); // instruction map for new static shaped submodule parameters std::unordered_map map_ins; for(const auto& dd_check : dd_check_vec.value()) { // create static shape using dim_size const auto& dyn_param = mm->get_parameter(dd_check.dyn_param_str); auto dyn_param_shape = mm->get_parameter_shape(dd_check.dyn_param_str); auto static_shape = dyn_param_shape.to_static(dim_size); map_ins[dyn_param] = submod->add_parameter(dd_check.dyn_param_str, static_shape); } auto outputs = submod->add_instructions(mm, &map_ins); submod->add_return({outputs}); submodules.push_back(submod); } // sort parameters by name for consistency (vs. parameter order attr) std::sort(param_names.begin(), param_names.end()); // redirect to select_module operator and return std::vector sm_inputs; std::transform(param_names.cbegin(), param_names.cend(), std::back_inserter(sm_inputs), [&](auto pn) { return mm->get_parameter(std::move(pn)); }); auto output_shapes = mm->get_output_shapes(); migraphx::shape out_attr = migraphx::shape{output_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), sm_inputs, submodules); std::vector outputs(output_shapes.size()); for(size_t i = 0; i < output_shapes.size(); ++i) { outputs.at(i) = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", i}}), sm_ins); } mm->replace_return(outputs); } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/sqlite.cpp000066400000000000000000000071041510465702400173360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { using sqlite3_ptr = MIGRAPHX_MANAGE_PTR(sqlite3*, sqlite3_close); struct sqlite_impl { sqlite3* get() const { return ptr.get(); } void open(const fs::path& p, int flags) { sqlite3* ptr_tmp = nullptr; int rc = sqlite3_open_v2(p.string().c_str(), &ptr_tmp, flags, nullptr); ptr = sqlite3_ptr{ptr_tmp}; if(rc != 0) MIGRAPHX_THROW("error opening " + p.string() + ": " + error_message()); } template void exec(const char* sql, F f) { // cppcheck-suppress constParameterPointer auto callback = [](void* obj, auto... xs) -> int { try { const auto* g = static_cast(obj); (*g)(xs...); return 0; } catch(...) { return -1; } }; int rc = sqlite3_exec(get(), sql, callback, &f, nullptr); if(rc != 0) MIGRAPHX_THROW(error_message()); } std::string error_message() const { std::string msg = "sqlite3: "; return msg + sqlite3_errmsg(get()); } sqlite3_ptr ptr; }; sqlite sqlite::read(const fs::path& p) { sqlite r; r.impl = std::make_shared(); r.impl->open(p, SQLITE_OPEN_READONLY); return r; } sqlite sqlite::write(const fs::path& p) { sqlite r; r.impl = std::make_shared(); // Using '+' instead of bitwise '|' to avoid compilation warning r.impl->open(p, SQLITE_OPEN_READWRITE + SQLITE_OPEN_CREATE); return r; } std::vector> sqlite::execute(const std::string& s) { std::vector> result; impl->exec(s.c_str(), [&](int n, char** texts, char** names) { std::unordered_map row; row.reserve(n); std::transform( names, names + n, texts, std::inserter(row, row.begin()), [&](const char* name, const char* text) { return std::make_pair(name, text); }); result.push_back(row); }); return result; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/target.cpp000066400000000000000000000030341510465702400173210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { void migraphx_to_value(value& v, const target& t) { v["name"] = t.name(); } void migraphx_from_value(const value& v, target& t) { t = make_target(v.at("name").to()); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/000077500000000000000000000000001510465702400170005ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/cpu/000077500000000000000000000000001510465702400175675ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/cpu/CMakeLists.txt000066400000000000000000000067231510465702400223370ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### include(CheckCXXCompilerFlag) add_library(migraphx_cpu allocate.cpp allocation_model.cpp binary.cpp concat.cpp convolution.cpp copy.cpp deconvolution.cpp dnnl.cpp eltwise.cpp erf.cpp fmod.cpp fuse_ops.cpp gather.cpp gemm.cpp layernorm.cpp logsoftmax.cpp lowering.cpp lrn.cpp mod.cpp preallocate.cpp pooling.cpp reduction.cpp reorder.cpp softmax.cpp sub.cpp target.cpp write_literals.cpp ) set_target_properties(migraphx_cpu PROPERTIES EXPORT_NAME cpu) rocm_set_soversion(migraphx_cpu ${MIGRAPHX_SO_VERSION}) set(MIGRAPHX_ENABLE_ZENDNN Off CACHE BOOL "") if(MIGRAPHX_ENABLE_ZENDNN) find_path(ZENDNN_INC_PATH zendnn.hpp) find_library(ZENDNN_LIB amdZenDNN) find_library(BLIS_LIB blis) else() find_package(dnnl REQUIRED) endif() rocm_clang_tidy_check(migraphx_cpu) if(MIGRAPHX_ENABLE_ZENDNN) target_compile_definitions(migraphx_cpu PRIVATE -DMIGRAPHX_ENABLE_ZENDNN) target_include_directories(migraphx_cpu PRIVATE ${ZENDNN_INC_PATH}) message(STATUS "ZENDNN_LIB: ${ZENDNN_LIB}") target_link_libraries(migraphx_cpu PRIVATE ${BLIS_LIB}) target_link_libraries(migraphx_cpu PRIVATE ${ZENDNN_LIB}) else() target_link_libraries(migraphx_cpu PUBLIC DNNL::dnnl) endif() target_link_libraries(migraphx_cpu PRIVATE migraphx) migraphx_generate_export_header(migraphx_cpu) find_package(OpenMP) if(WIN32) target_link_libraries(migraphx_cpu PUBLIC libomp) target_include_directories(migraphx_cpu PUBLIC ${OpenMP_CXX_INCLUDE_DIRS}) target_compile_options(migraphx_cpu PUBLIC ${OpenMP_CXX_FLAGS}) else() target_link_libraries(migraphx_cpu PUBLIC OpenMP::OpenMP_CXX) # Add library path to rpath to workaround issues with our broken packages foreach(LIBRARY ${OpenMP_CXX_LIBRARIES}) if(LIBRARY MATCHES "libomp") get_filename_component(LIBRARY_PATH "${LIBRARY}" PATH) target_link_libraries(migraphx_cpu PUBLIC -Wl,-rpath=${LIBRARY_PATH} -Wl,-rpath-link=${LIBRARY_PATH}) endif() endforeach() endif() rocm_install_targets( PRIVATE TARGETS migraphx_cpu INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include ) ROCm-AMDMIGraphX-46524e8/src/targets/cpu/allocate.cpp000066400000000000000000000041011510465702400220530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct cpu_allocate : auto_register_op { shape s; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape")); } std::string name() const { return "cpu::allocate"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(0); return s; } argument compute(context&, const shape& output_shape, const std::vector&) const { argument result{output_shape}; return result; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/allocation_model.cpp000066400000000000000000000035041510465702400236020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { std::string cpu_allocation_model::name() const { return "cpu::allocate"; } operation cpu_allocation_model::allocate(const shape& s) const { return make_op(name(), {{"shape", to_value(s)}}); } operation cpu_allocation_model::preallocate(const shape& s, const std::string& id) const { return make_op("cpu::preallocate", {{"shape", to_value(s)}, {"id", id}}); } std::string cpu_allocation_model::copy() const { return "cpu::copy"; } } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/binary.cpp000066400000000000000000000056311510465702400215640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_binary : dnnl_op { std::string algo; template static auto reflect(Self& self, F f) { return pack_join(self.reflect_base(self, f), pack(f(self.algo, "algo"))); } std::string group() const { return this->name() + "::" + algo; } std::string name() const { return "dnnl::binary"; } shape compute_shape(std::vector inputs) const { // Compensate for allocation inputs.pop_back(); check_shapes{this->trim_post_op_inputs(inputs), *this}.has(2); auto s0 = inputs.at(0); auto s1 = inputs.at(1); auto r = s0; if(s0 != s1 or not s0.packed()) { if(s0.packed() != s1.packed()) { r = s0.packed() ? s0 : s1; } else if(s0.broadcasted() != s1.broadcasted()) { r = s0.broadcasted() ? s1.with_lens(s0.lens()) : s0.with_lens(s0.lens()); } else { r = {s0.type(), s0.lens()}; } } // Call to get_primitive to make sure an algo is available this->get_primitive(this->to_memory_desc(r, inputs)); return r; } dnnl::binary::desc get_desc(const std::unordered_map& m) const { return {to_dnnl_algo(algo), m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC_0)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC_1)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST))}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/concat.cpp000066400000000000000000000047271510465702400215540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_concat : dnnl_extend_op { std::vector arg_map(int size) const { std::vector result(size); std::iota(result.begin(), result.end(), MIGRAPHX_DNNL_PREFIX(ARG_MULTIPLE_SRC)); return result; } // Custom desc class since its missing in dnnl struct desc { dnnl::memory::desc dst; std::size_t axis = 1; std::vector srcs; }; desc get_desc(const std::unordered_map& m) const { std::vector srcs; srcs.reserve(m.size() - 1); for(auto i = 0; i < m.size() - 1; i++) { srcs.push_back(m.at(MIGRAPHX_DNNL_PREFIX(ARG_MULTIPLE_SRC) + i)); } return {m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST)), std::size_t(op.axis), srcs}; } auto get_primitive_desc(const desc& d, const dnnl::primitive_attr& attr) const { return dnnl::concat::primitive_desc(d.dst, d.axis, d.srcs, get_dnnl_context().engine, attr); } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/convolution.cpp000066400000000000000000000065121510465702400226560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_convolution : dnnl_extend_op { std::vector arg_map(int) const { return {MIGRAPHX_DNNL_PREFIX(ARG_SRC), MIGRAPHX_DNNL_PREFIX(ARG_WEIGHTS)}; } shape adjust_shape(const shape& x, int i, const shape& output) const { auto s = base_adjust_shape(x, output); if(i == 1 and op.group > 1) { // TODO: Add support for transposed weights if(not s.standard()) MIGRAPHX_THROW("Weights for grouped convolution must be standard"); auto lens = s.lens(); lens.insert(lens.begin(), op.group); lens.at(1) /= op.group; return shape{s.type(), lens}; } return s; } dnnl::convolution_forward::desc get_desc(const std::unordered_map& m) const { // In DNNL dilation is zero-based auto dilation = op.dilation; std::transform( dilation.begin(), dilation.end(), dilation.begin(), [](auto x) { return x - 1; }); auto kdims = op.kdims(); std::vector padding_l(op.padding.begin(), op.padding.begin() + kdims); std::vector padding_r(op.padding.begin() + kdims, op.padding.end()); return {dnnl::prop_kind::forward_inference, dnnl::algorithm::convolution_auto, m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_WEIGHTS)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST)), to_dnnl_dims(op.stride), to_dnnl_dims(dilation), to_dnnl_dims(padding_l), to_dnnl_dims(padding_r)}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/copy.cpp000066400000000000000000000044161510465702400212520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct cpu_copy : reduce_dims_base, auto_register_op { template static auto reflect(Self&, F) { return pack(); } std::string name() const { return "cpu::copy"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2); return inputs.at(1); } argument compute(context& ctx, const shape& output_shape, const std::vector& args) const { argument result = get_arg(args, args.size() - 1); visit_all(result, get_arg(args, 0))([&](auto output, auto input) { pointwise(output, input)(ctx, output.get_shape(), 1024, [](auto& y, auto x) { y = x; }); }); return result.reshape(output_shape); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/deconvolution.cpp000066400000000000000000000055751510465702400231770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_deconvolution : dnnl_extend_op { std::vector arg_map(int) const { return {MIGRAPHX_DNNL_PREFIX(ARG_SRC), MIGRAPHX_DNNL_PREFIX(ARG_WEIGHTS)}; } shape adjust_shape(const shape& x, int i, const shape& output) const { auto s = base_adjust_shape(x, output); if(i == 1) { // The input and output channels are flipped for dnnl auto lens = s.lens(); std::swap(lens[0], lens[1]); auto strides = s.strides(); std::swap(strides[0], strides[1]); return {s.type(), lens, strides}; } return s; } dnnl::deconvolution_forward::desc get_desc(const std::unordered_map& m) const { // In DNNL dilation is zero-based auto dilation = op.dilation; std::transform( dilation.begin(), dilation.end(), dilation.begin(), [](auto x) { return x - 1; }); return {dnnl::prop_kind::forward_inference, dnnl::algorithm::deconvolution_direct, m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_WEIGHTS)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST)), to_dnnl_dims(op.stride), to_dnnl_dims(dilation), to_dnnl_dims(op.padding), to_dnnl_dims(op.padding)}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/dnnl.cpp000066400000000000000000000145301510465702400212310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #if defined(__GNUC__) && __GNUC__ <= 5 namespace std { #ifdef MIGRAPHX_ENABLE_ZENDNN namespace dnnl = zendnn; #endif template <> struct hash { using argument_type = dnnl::algorithm; using result_type = std::size_t; result_type operator()(const argument_type& x) const noexcept { return std::hash>{}( static_cast>(x)); } }; } // namespace std #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { dnnl_context& get_dnnl_context() { static dnnl_context ctx{}; // NOLINT return ctx; } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" #endif dnnl::memory::data_type to_dnnl_memory_data_type(shape::type_t t) { using dt = dnnl::memory::data_type; using st = shape::type_t; switch(t) { case st::half_type: return dt::f16; case st::float_type: return dt::f32; case st::int32_type: return dt::s32; case st::int8_type: return dt::s8; case st::uint8_type: return dt::u8; case st::fp8e4m3fnuz_type: MIGRAPHX_THROW("fp8e4m3fnuz unsupported in DNNL"); case st::bf16_type: return dt::bf16; default: MIGRAPHX_THROW("Unsupported data type"); } } #ifdef __clang__ #pragma clang diagnostic pop #endif dnnl::memory::format_tag to_dnnl_memory_format_tag(std::size_t n) { switch(n) { case 1: return dnnl::memory::format_tag::a; case 2: return dnnl::memory::format_tag::ab; case 3: return dnnl::memory::format_tag::abc; case 4: return dnnl::memory::format_tag::abcd; case 5: return dnnl::memory::format_tag::abcde; case 6: return dnnl::memory::format_tag::abcdef; default: MIGRAPHX_THROW("Unsupported tensor size: " + std::to_string(n)); } } dnnl::memory::desc to_dnnl_memory_desc(const shape& s) { return {to_dnnl_dims(s.lens()), to_dnnl_memory_data_type(s.type()), to_dnnl_dims(s.strides())}; } dnnl::memory to_dnnl_memory(const dnnl::memory::desc& desc, const argument& a) { return {desc, get_dnnl_context().engine, a.data()}; } dnnl::memory to_dnnl_memory(const argument& a) { return to_dnnl_memory(to_dnnl_memory_desc(a.get_shape()), a); } // clang-format off #define MIGRAPHX_VISIT_DNNL_ALGO(m) \ m(undef) \ m(convolution_auto) \ m(convolution_direct) \ m(convolution_winograd) \ m(deconvolution_direct) \ m(deconvolution_winograd) \ m(eltwise_relu) \ m(eltwise_tanh) \ m(eltwise_elu) \ m(eltwise_square) \ m(eltwise_abs) \ m(eltwise_sqrt) \ m(eltwise_swish) \ m(eltwise_linear) \ m(eltwise_bounded_relu) \ m(eltwise_soft_relu) \ m(eltwise_logistic) \ m(eltwise_exp) \ m(eltwise_gelu) \ m(eltwise_gelu_tanh) \ m(eltwise_gelu_erf) \ m(eltwise_log) \ m(eltwise_clip) \ m(eltwise_pow) \ m(eltwise_round) \ m(eltwise_relu_use_dst_for_bwd) \ m(eltwise_tanh_use_dst_for_bwd) \ m(eltwise_elu_use_dst_for_bwd) \ m(eltwise_sqrt_use_dst_for_bwd) \ m(eltwise_logistic_use_dst_for_bwd) \ m(eltwise_exp_use_dst_for_bwd) \ m(lrn_across_channels) \ m(lrn_within_channel) \ m(pooling_max) \ m(pooling_avg) \ m(pooling_avg_include_padding) \ m(pooling_avg_exclude_padding) \ m(vanilla_rnn) \ m(vanilla_lstm) \ m(vanilla_gru) \ m(lbr_gru) \ m(binary_add) \ m(binary_mul) \ m(binary_max) \ m(binary_min) \ m(binary_div) \ m(resampling_nearest) \ m(resampling_linear) \ m(reduction_max) \ m(reduction_min) \ m(reduction_sum) \ m(reduction_mul) \ m(reduction_mean) \ m(reduction_norm_lp_max) \ m(reduction_norm_lp_sum) \ m(reduction_norm_lp_power_p_max) \ m(reduction_norm_lp_power_p_sum) // clang-format on static const std::unordered_map& dnnl_algo_map() { static const std::unordered_map m = { #define MIGRAPHX_DNNL_ALGO_GENERATE_VISITOR(x) {#x, dnnl::algorithm::x}, MIGRAPHX_VISIT_DNNL_ALGO(MIGRAPHX_DNNL_ALGO_GENERATE_VISITOR) #undef MIGRAPHX_DNNL_ALGO_GENERATE_VISITOR }; return m; } dnnl::algorithm to_dnnl_algo(const std::string& name) { if(dnnl_algo_map().count(name) == 0) MIGRAPHX_THROW("Missing dnnl algo: " + name); return dnnl_algo_map().at(name); } static const std::unordered_map& dnnl_algo_string_map() { static const std::unordered_map m = { #define MIGRAPHX_DNNL_ALGO_GENERATE_VISITOR(x) {dnnl::algorithm::x, #x}, MIGRAPHX_VISIT_DNNL_ALGO(MIGRAPHX_DNNL_ALGO_GENERATE_VISITOR) #undef MIGRAPHX_DNNL_ALGO_GENERATE_VISITOR }; return m; } std::string to_string(const dnnl::algorithm& algo) { if(dnnl_algo_string_map().count(algo) == 0) return "unknown_" + std::to_string(static_cast(algo)); return dnnl_algo_string_map().at(algo); } } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/eltwise.cpp000066400000000000000000000052161510465702400217530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_eltwise : dnnl_op { std::string algo; float alpha = 0; float beta = 0; template static auto reflect(Self& self, F f) { return pack_join(self.reflect_base(self, f), pack(f(self.algo, "algo"), f(self.alpha, "alpha"), f(self.beta, "beta"))); } std::string group() const { return this->name() + "::" + algo; } std::string name() const { return "dnnl::eltwise"; } shape compute_shape(std::vector inputs) const { // Compensate for allocation inputs.pop_back(); check_shapes{this->trim_post_op_inputs(inputs), *this}.has(1).packed(); auto s = inputs.at(0); auto r = s; if(not s.packed()) r = shape{s.type(), s.lens()}; // Call to get_primitive to make sure an algo is available this->get_primitive(this->to_memory_desc(r, inputs)); return r; } dnnl::eltwise_forward::desc get_desc(const std::unordered_map& m) const { return {dnnl::prop_kind::forward_inference, to_dnnl_algo(algo), m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC_0)), alpha, beta}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/erf.cpp000066400000000000000000000027071510465702400210550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { template struct cpu_unary; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/fmod.cpp000066400000000000000000000027121510465702400212220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { template struct cpu_binary; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/fuse_ops.cpp000066400000000000000000000115051510465702400221200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_DNNL_POST_OPS_WORKAROUND); MIGRAPHX_PRED_MATCHER(has_post_ops, instruction_ref ins) { auto v = ins->get_operator().to_value(); return v.contains("post_ops"); } MIGRAPHX_PRED_MATCHER(without_post_ops, instruction_ref ins) { auto v = ins->get_operator().to_value(); return v.contains("post_ops") and v["post_ops"].empty(); } static bool workaround_dnnl_broken_post_ops(const operation& op, const operation& post_op) { if(contains({"dnnl::dot", "dnnl::convolution"}, op.name())) return true; auto pv = post_op.to_value(); if(not pv.at("post_ops").empty()) return true; auto v = op.to_value(); auto last_op = v.at("post_ops").empty() ? v : v.at("post_ops").back(); auto algo = last_op.contains("algo") ? last_op.at("algo").to() : op.name(); auto post_algo = pv["algo"].to(); if(starts_with(algo, "eltwise") and starts_with(post_algo, "eltwise")) return true; if(algo == post_algo) return true; return false; } static operation merge_post_ops(const operation& op, const operation& post_op) { auto pv = post_op.to_value(); auto v = op.to_value(); v["post_ops"].push_back({{"algo", pv["algo"]}, {"alpha", pv["alpha"].value_or(0.0f)}, {"beta", pv["beta"].value_or(0.0f)}}); auto post_ops = pv.at("post_ops"); for(const auto& po : post_ops) v["post_ops"].push_back(po); return make_op(op.name(), v); } struct find_post_ops { context* ctx = nullptr; match::any_matcher matcher() const { if(enabled(MIGRAPHX_DISABLE_DNNL_POST_OPS_WORKAROUND{})) return match::name("dnnl::eltwise", "dnnl::binary")(match::arg(0)(has_post_ops(), match::used_once())); else { auto dnnl_binary = match::name("dnnl::binary")(without_post_ops(), match::used_once()); return match::name("dnnl::eltwise")(without_post_ops(), match::arg(0)(dnnl_binary)); } } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto x_ins = ins->inputs().front(); auto x = x_ins->get_operator(); if(workaround_dnnl_broken_post_ops(x, ins->get_operator())) return; auto op = merge_post_ops(x, ins->get_operator()); auto inputs = x_ins->inputs(); inputs.back() = ins->inputs().back(); if(ins->name() == "dnnl::binary") inputs.insert(std::prev(inputs.end()), ins->inputs().at(1)); auto input_shapes = to_shapes(inputs); auto new_shape = try_compute_shape(op, input_shapes); if(new_shape.empty() or new_shape.front() != ins->get_shape()) return; auto info = compile(op, *ctx, new_shape.front(), input_shapes); if(info.contains("impl") and starts_with(info.at("impl").to(), "ref:")) return; m.replace_instruction(ins, op, inputs); } }; void fuse_ops::apply(module& m) const { for(std::size_t i = 0; i < 4; i++) { match::find_matches(m, find_post_ops{ctx}); dead_code_elimination{}.apply(m); } } } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/gather.cpp000066400000000000000000000063731510465702400215560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct cpu_gather : auto_register_op { op::gather op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "cpu::" + op.name(); } shape compute_shape(std::vector inputs) const { // Compensate for allocation inputs.pop_back(); check_shapes(inputs, *this).standard(); return migraphx::compute_shape(op, inputs); } argument compute(context& ctx, const shape& output_shape, const std::vector& args) const { std::size_t nelements = output_shape.elements(); auto lens = args[0].get_shape().lens(); auto axis_dim_size = lens[op.axis]; lens[op.axis] = args[1].get_shape().elements(); shape out_comp{output_shape.type(), lens}; visit_all(args.back(), args[0])([&](auto output, auto input) { args[1].visit([&](auto indices) { const auto* indices_ptr = indices.data(); auto* output_ptr = output.data(); ctx.bulk_execute(nelements, 1024, [=](auto start, auto end) { for(auto i = start; i < end; i++) { auto idx = out_comp.multi(i); auto in_index = indices_ptr[idx[op.axis]]; in_index = (in_index < 0) ? in_index + axis_dim_size : in_index; idx[op.axis] = in_index; output_ptr[i] = input(idx.begin(), idx.end()); } }); }); }); return args.back(); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/gemm.cpp000066400000000000000000000043351510465702400212250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_gemm : dnnl_extend_op { std::vector arg_map(int) const { return {MIGRAPHX_DNNL_PREFIX(ARG_SRC), MIGRAPHX_DNNL_PREFIX(ARG_WEIGHTS), MIGRAPHX_DNNL_PREFIX(ARG_BIAS)}; } template void required(const check_shapes& cs) const { cs.not_broadcasted(); } dnnl::matmul::desc get_desc(const std::unordered_map& m) const { return {m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_WEIGHTS)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST))}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/000077500000000000000000000000001510465702400212125ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/000077500000000000000000000000001510465702400230315ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/000077500000000000000000000000001510465702400236205ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/allocation_model.hpp000066400000000000000000000034331510465702400276410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_ALLOCATION_MODEL_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_ALLOCATION_MODEL_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct cpu_allocation_model { std::string name() const; std::string copy() const; operation allocate(const shape& s) const; operation preallocate(const shape& s, const std::string& id) const; bool needs_out_params() const { return false; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/context.hpp000066400000000000000000000036251510465702400260230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CONTEXT_HPP #define MIGRAPHX_GUARD_RTGLIB_CONTEXT_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct context { void finish() const {} template void bulk_execute(std::size_t n, std::size_t min_grain, F f) { cpu::parallel_for(n, min_grain, std::move(f)); } template void bulk_execute(std::size_t n, F f) { this->bulk_execute(n, 256, f); } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/dnnl.hpp000066400000000000000000000363051510465702400252730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_DNNL_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_DNNL_HPP #include #include #include #include #include #include #include #include #ifdef MIGRAPHX_ENABLE_ZENDNN #include #else #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { #ifdef MIGRAPHX_ENABLE_ZENDNN namespace dnnl = zendnn; #define MIGRAPHX_CONCAT_PREFIX(b) ZENDNN_##b // NOLINT #else #define MIGRAPHX_CONCAT_PREFIX(b) DNNL_##b // NOLINT #endif #define MIGRAPHX_DNNL_PREFIX(b) MIGRAPHX_CONCAT_PREFIX(b) // NOLINT struct dnnl_context { dnnl::engine engine; dnnl::stream stream; dnnl_context() : engine(dnnl::engine::kind::cpu, 0), stream(engine) {} }; dnnl_context& get_dnnl_context(); dnnl::memory::data_type to_dnnl_memory_data_type(shape::type_t t); dnnl::memory::format_tag to_dnnl_memory_format_tag(std::size_t n); template inline dnnl::memory::dims to_dnnl_dims(R&& r) { return {r.begin(), r.end()}; } dnnl::memory::desc to_dnnl_memory_desc(const shape& s); dnnl::memory to_dnnl_memory(const dnnl::memory::desc& desc, const argument& a); dnnl::memory to_dnnl_memory(const argument& a); dnnl::algorithm to_dnnl_algo(const std::string& name); std::string to_string(const dnnl::algorithm& algo); struct post_op : reflect_equality, reflect_stream { std::string algo; float alpha = 0; float beta = 0; template static auto reflect(Self& self, F f) { return pack(f(self.algo, "algo"), f(self.alpha, "alpha"), f(self.beta, "beta")); } }; template struct execute_wrapper { F f; argument operator()(context&, const std::vector& args) const { return f(args); } }; template execute_wrapper make_execute_wrapper(F f) { return {std::move(f)}; } template struct dnnl_op : auto_register_op { std::vector post_ops; std::function& args)> execute; template static auto reflect_base(Self& self, F f) { return pack(f(self.post_ops, "post_ops")); } template static auto reflect(Self& self, F f) { return reflect_base(self, f); } std::string group() const { const auto& self = static_cast(*this); return self.name(); } value attributes() const { std::vector names; std::transform(post_ops.begin(), post_ops.end(), std::back_inserter(names), [](auto&& op) { return op.algo; }); const auto& self = static_cast(*this); auto g = self.group(); if(not names.empty()) g += "<" + join_strings(names, ",") + ">"; return {{"group", g}}; } std::size_t get_extra_post_op_args() const { return std::count_if(post_ops.begin(), post_ops.end(), [](const auto& po) { return contains(po.algo, "binary"); }); } static std::size_t get_binary_post_op_arg(std::size_t pos) { return MIGRAPHX_DNNL_PREFIX(ARG_ATTR_MULTIPLE_POST_OP)(pos) | // NOLINT MIGRAPHX_DNNL_PREFIX(ARG_SRC_1); // NOLINT } static std::vector to_shapes(const std::vector& args) { std::vector shapes(args.size()); std::transform(args.begin(), args.end(), shapes.begin(), [](const argument& a) { return a.get_shape(); }); return shapes; } static std::string impl(const Primitive& prim) { auto desc = prim.get_primitive_desc(); const char* str = nullptr; #ifdef MIGRAPHX_ENABLE_ZENDNN zendnn_primitive_desc_query( desc, zendnn_query_impl_info_str, 0, reinterpret_cast(&str)); #else dnnl_primitive_desc_query(desc, dnnl_query_impl_info_str, 0, reinterpret_cast(&str)); #endif return str == nullptr ? "" : str; } // Map arg index to arg in dnnl std::vector arg_map(int size) const { std::vector result(size); std::iota(result.begin(), result.end(), MIGRAPHX_DNNL_PREFIX(ARG_SRC_0)); return result; } shape base_adjust_shape(const shape& s, const shape& output) const { if(s.broadcasted()) { auto lens = s.lens(); auto strides = s.strides(); std::transform(strides.begin(), strides.end(), lens.begin(), lens.begin(), [](auto stride, auto len) -> std::size_t { if(stride == 0) return 1; else return len; }); // Use the permutation of the output return output.with_lens(s.type(), lens); } return s; } template void for_each_post_op(F f) const { int i = 0; for(auto&& op : post_ops) { if(contains(op.algo, "binary")) { f(op, get_binary_post_op_arg(i)); } else { f(op, -1); } i++; } } shape adjust_shape(const shape& s, int, const shape& output) const { return base_adjust_shape(s, output); } std::vector create_arg_map(std::size_t input_size) const { const auto& self = static_cast(*this); auto npost_ops = get_extra_post_op_args(); auto prim_input_size = input_size - npost_ops; auto m = self.arg_map(prim_input_size); for_each_post_op([&](auto&&, auto arg) { if(arg < 0) return; m.push_back(arg); }); return m; } std::unordered_map to_memory_desc(const shape& output_shape, const std::vector& inputs) const { const auto& self = static_cast(*this); std::unordered_map result; result[MIGRAPHX_DNNL_PREFIX(ARG_DST)] = to_dnnl_memory_desc(self.adjust_shape(output_shape, inputs.size(), output_shape)); auto m = create_arg_map(inputs.size()); assert(m.size() >= inputs.size()); for(int i = 0; i < inputs.size(); i++) { result[m[i]] = to_dnnl_memory_desc(self.adjust_shape(inputs[i], i, output_shape)); } return result; } dnnl::primitive_attr get_primitive_attr(const std::unordered_map& m) const { dnnl::primitive_attr result; dnnl::post_ops po; for_each_post_op([&](auto&& op, auto arg) { if(contains(op.algo, "binary_add")) { auto desc = m.at(arg); if(desc == m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST))) po.append_sum(1.0f); else po.append_binary(to_dnnl_algo(op.algo), m.at(arg)); } else if(contains(op.algo, "binary")) { po.append_binary(to_dnnl_algo(op.algo), m.at(arg)); } else if(contains(op.algo, "eltwise")) po.append_eltwise(1.0f, to_dnnl_algo(op.algo), op.alpha, op.beta); else MIGRAPHX_THROW("Unknown post op algo: " + op.algo); }); result.set_post_ops(po); return result; } template auto get_primitive_desc(const T& desc, const dnnl::primitive_attr& attr) const -> decltype(typename Primitive::primitive_desc(desc, attr, get_dnnl_context().engine)) { return typename Primitive::primitive_desc(desc, attr, get_dnnl_context().engine); } Primitive get_primitive(const std::unordered_map& m) const { const auto& self = static_cast(*this); auto desc = self.get_desc(m); auto attr = MIGRAPHX_ASSERT_NO_THROW(this->get_primitive_attr(m)); auto pd = self.get_primitive_desc(desc, attr); return Primitive(pd); } argument compute(context& ctx, const shape&, const std::vector& args) const { return execute(ctx, args); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } value compile(context&, const shape& output_shape, std::vector inputs) { // Compensate for allocation inputs.pop_back(); auto md = to_memory_desc(output_shape, inputs); auto prim = get_primitive(md); auto impl_name = impl(prim); return {{"impl", impl_name}}; } void finalize(context&, const shape& output_shape, std::vector inputs) { // Compensate for allocation inputs.pop_back(); const auto& self = static_cast(*this); auto name = self.name(); auto md = to_memory_desc(output_shape, inputs); auto prim = get_primitive(md); auto arg_lookup = create_arg_map(inputs.size()); #ifndef NDEBUG auto prim_attr = get_primitive_attr(md); #endif execute = make_execute_wrapper([=](const std::vector& args) { #ifndef NDEBUG // Check that the memory descriptors have not changed auto debug_args = args; debug_args.pop_back(); auto debug_md = to_memory_desc(output_shape, to_shapes(debug_args)); for(auto&& p : debug_md) { if(md.count(p.first) == 0) MIGRAPHX_THROW(name + ": Missing memory descriptor for: " + std::to_string(p.first)); if(p.second == md.at(p.first)) continue; MIGRAPHX_THROW(name + ": Memory descriptor has changed for: " + std::to_string(p.first)); } // Check post_ops args are correct auto pos = prim_attr.get_post_ops(); auto prim_input_size = inputs.size() - this->get_extra_post_op_args(); int j = 0; for(int i = 0; i < pos.len(); i++) { auto arg = j + prim_input_size; auto kind = pos.kind(i); std::string mesg = "Post op " + std::to_string(i) + "@" + std::to_string(arg) + ": "; try { dnnl::algorithm algo; dnnl::memory::desc mdesc; float scale = 0; float alpha = 0; float beta = 0; if(kind == dnnl::primitive::kind::binary) { pos.get_params_binary(i, algo, mdesc); if(mdesc != md.at(arg_lookup.at(arg))) MIGRAPHX_THROW(mesg + "Memory descriptor doesn't match for binary post op"); j++; } else if(kind == dnnl::primitive::kind::eltwise) { pos.get_params_eltwise(i, scale, algo, alpha, beta); } else if(kind == dnnl::primitive::kind::sum) { pos.get_params_sum(i, scale); algo = dnnl::algorithm::binary_add; } else { MIGRAPHX_THROW("Unknown kind"); } if(to_dnnl_algo(post_ops[i].algo) != algo) MIGRAPHX_THROW(mesg + "Algorithm doesn't match for post op " + post_ops[i].algo + " != " + to_string(algo)); } catch(const dnnl::error& e) { MIGRAPHX_THROW(mesg + "Failed to get post ops argument " + ": " + e.what()); } } #endif std::unordered_map m; m[MIGRAPHX_DNNL_PREFIX(ARG_DST)] = to_dnnl_memory(md.at(MIGRAPHX_DNNL_PREFIX(ARG_DST)), args.back()); for(int i = 0; i < args.size() - 1; i++) m[arg_lookup[i]] = to_dnnl_memory(md.at(arg_lookup[i]), args[i]); prim.execute(get_dnnl_context().stream, m); return args.back(); }); } std::vector trim_post_op_inputs(const std::vector& inputs) const { auto prim_input_size = inputs.size() - this->get_extra_post_op_args(); return {inputs.begin(), inputs.begin() + prim_input_size}; } }; template struct dnnl_extend_op : dnnl_op { Op op; template static auto reflect(Self& self, F f) { return pack_join(self.reflect_base(self, f), migraphx::reflect(self.op, f)); } // dnnl has some issues with non-packed inputs template void required(const check_shapes& cs) const { cs.packed_or_broadcasted(); } std::string name() const { return "dnnl::" + op.name(); } shape compute_shape(std::vector inputs) const { const auto& self = static_cast(*this); // Compensate for allocation inputs.pop_back(); self.required(check_shapes(inputs, self)); auto r = migraphx::compute_shape(op, this->trim_post_op_inputs(inputs)); // Call to get_primitive to make sure an algo is available this->get_primitive(this->to_memory_desc(r, inputs)); return r; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/fuse_ops.hpp000066400000000000000000000032401510465702400261530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_CPU_FUSE_OPS_HPP #define MIGRAPHX_GUARD_CPU_FUSE_OPS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace cpu { struct MIGRAPHX_CPU_EXPORT fuse_ops { context* ctx = nullptr; std::string name() const { return "cpu::fuse_ops"; } void apply(module& m) const; }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_CPU_FUSE_OPS_HPP ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/lowering.hpp000066400000000000000000000031361510465702400261620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CPU_LOWERING_HPP #define MIGRAPHX_GUARD_RTGLIB_CPU_LOWERING_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace cpu { struct MIGRAPHX_CPU_EXPORT lowering { std::string name() const { return "cpu::lowering"; } void apply(module& m) const; }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/parallel.hpp000066400000000000000000000073631510465702400261360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_PARALLEL_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_PARALLEL_HPP // #define MIGRAPHX_DISABLE_OMP #include #include #include #include #ifdef MIGRAPHX_DISABLE_OMP #include #else #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreserved-identifier" #endif #include #ifdef __clang__ #pragma clang diagnostic pop #endif #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { #ifdef MIGRAPHX_DISABLE_OMP inline std::size_t max_threads() { return std::thread::hardware_concurrency(); } template void parallel_for_impl(std::size_t n, std::size_t threadsize, F f) { if(threadsize <= 1) { f(std::size_t{0}, n); } else { std::vector threads(threadsize); // Using const here causes gcc 5 to ICE #if(!defined(__GNUC__) || __GNUC__ != 5) const #endif std::size_t grainsize = std::ceil(static_cast(n) / threads.size()); std::size_t work = 0; std::generate(threads.begin(), threads.end(), [=, &work] { auto result = joinable_thread([=]() mutable { assert(work < n); f(work, std::min(n, work + grainsize)); }); work += grainsize; return result; }); // cppcheck-suppress unsignedLessThanZero assert(work >= n); } } #else inline std::size_t max_threads() { return omp_get_max_threads(); } template void parallel_for_impl(std::size_t n, std::size_t threadsize, F f) // NOLINT(performance-unnecessary-value-param) { if(threadsize <= 1) { f(std::size_t{0}, n); } else { std::size_t grainsize = std::ceil(static_cast(n) / threadsize); #pragma omp parallel for num_threads(threadsize) schedule(static, 1) for(std::size_t tid = 0; tid < threadsize; tid++) { std::size_t work = tid * grainsize; assert(work < n); f(work, std::min(n, work + grainsize)); } } } #endif template void parallel_for(std::size_t n, std::size_t min_grain, F f) { const auto threadsize = std::min(max_threads(), n / min_grain); parallel_for_impl(n, threadsize, std::move(f)); } template void parallel_for(std::size_t n, F f) { const int min_grain = 8; parallel_for(n, min_grain, f); } } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/pointwise.hpp000066400000000000000000000273421510465702400263620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_POINTWISE_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_POINTWISE_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct multi_index { constexpr multi_index() = default; multi_index(const shape& s, std::size_t i) : n(s.lens().size()) { assert(n < max_size); std::copy(s.lens().begin(), s.lens().end(), dims); s.multi_copy(i, index, index + max_size); } constexpr std::size_t size() const { return n; } constexpr std::size_t* begin() { return index; } constexpr const std::size_t* begin() const { return index; } constexpr std::size_t* end() { return index + size(); } constexpr const std::size_t* end() const { return index + size(); } std::size_t offset(const shape& s) const { return s.index(begin(), end()); } constexpr void carry() { std::size_t overflow = 0; for(std::ptrdiff_t i = size() - 1; i > 0; i--) { auto z = index[i] + overflow; // Reset overflow overflow = 0; // Compute overflow using while loop instead of mod // overflow = z / dims[i]; // z = z % dims[i]; while(z >= dims[i]) { z -= dims[i]; overflow += 1; } index[i] = z; // Exit if there is no overflow if(overflow == 0) return; } index[0] += overflow; } constexpr void increment(std::size_t i) { index[size() - 1] += i; carry(); } constexpr multi_index& operator+=(std::size_t i) { increment(i); return *this; } constexpr multi_index& operator++() { increment(1); return *this; } multi_index operator++(int) // NOLINT { multi_index result = *this; increment(1); return result; } private: static const std::size_t max_size = 5; std::size_t index[max_size] = {}; std::size_t dims[max_size] = {}; std::size_t n = 0; }; struct reduce_dims_base { std::vector reduce_shapes; void finalize(context&, const shape&, const std::vector& inputs) { reduce_shapes = reduce_dims(inputs); } argument get_arg(const std::vector& args, std::size_t i) const { if(reduce_shapes.empty()) return args[i]; return args.at(i).reshape(reduce_shapes.at(i)); } argument get_output() const { argument a{reduce_shapes[0]}; return a; } }; template struct vec { using array_type = std::array; using vector_type __attribute__((vector_size(N * sizeof(T)))) = T; union { array_type array; vector_type vector; }; static_assert(sizeof(array_type) == sizeof(vector_type), "Not the same size"); }; template constexpr std::integral_constant vec_size(const T&) { return {}; } template constexpr std::integral_constant vec_size(const vec&) { return {}; } template constexpr std::size_t vec_size() { return decltype(vec_size(std::declval())){}; } template () > 0))> void vec_apply(F f, V& v, Vs... vs) { assert(all_of({vec_size()...}, [&](auto n) { return n == vec_size(); })); assert(vec_size() == v.array.size()); for(std::size_t i = 0; i < vec_size(); i++) f(v.array[i], vs.vector[i]...); } template () == 0))> void vec_apply(F f, V& v, Vs&... vs) { f(v, vs...); } inline std::size_t find_packed_len(const shape& s) { for(std::size_t i = 0; i < s.lens().size(); i++) { if(s.lens()[i] > 1 and s.strides()[i] == 1) { return i; } } return -1; } template shape vectorize(const shape& s) { assert(s.standard() or s.broadcasted()); auto lens = s.lens(); if(s.broadcasted()) { auto n = find_packed_len(s); assert(n != -1); assert((lens[n] % N) == 0); lens[n] /= N; return {s.type(), lens, s.strides()}; } assert((lens.back() % N) == 0); lens.back() /= N; return {s.type(), lens}; } template tensor_view> vectorize(tensor_view tv) { return {vectorize(tv.get_shape()), reinterpret_cast*>(tv.data())}; } template struct is_vector_type : std::false_type { }; template <> struct is_vector_type : std::true_type { }; template struct is_vector_tensor_view : and_{}...> { }; template bool is_vectorizable(const Xs&... xs) { return all_of({xs...}, [](const auto& s) { if(s.standard() and (s.lens().back() % N) == 0) return true; if(s.broadcasted()) { auto n = std::inner_product(s.lens().begin(), s.lens().end(), s.strides().begin(), 0, std::plus<>{}, [&](auto len, auto stride) -> std::size_t { if(stride > 0 and len == 1) return 0; return stride; }); if(n == 1) { auto i = find_packed_len(s); assert(i != -1); return (s.lens()[i] % N) == 0; } } return false; }); } template {})> auto auto_vectorize(const shape& base_shape, Ts... xs) { return [=](auto f) { if(is_vectorizable<32>(base_shape, xs.get_shape()...)) f(vectorize<32>(base_shape), vectorize<32>(xs)...); else if(is_vectorizable<8>(base_shape, xs.get_shape()...)) f(vectorize<8>(base_shape), vectorize<8>(xs)...); else f(base_shape, xs...); }; } template {})> auto auto_vectorize(const shape& base_shape, Ts... xs) { return [=](auto f) { f(base_shape, xs...); }; } template bool is_standard_offset(const X& x, const Xs&... xs) { if(all_of({x, xs...}, [](const auto& s) { return s.standard(); })) return true; if(all_of({x, xs...}, [](const auto& s) { return s.packed(); }) and all_of({xs...}, [&](const auto& s) { return s == x; })) return true; return false; } template auto pointwise_apply(Ts... ts) { return [=](context& ctx, const shape& base_shape, std::size_t min_grain, auto f) mutable { if(is_standard_offset(ts.get_shape()...)) { ctx.bulk_execute(base_shape.elements(), min_grain, [=](auto start, auto end) mutable { for(auto i = start; i < end; i++) { vec_apply(f, ts.data()[i]...); } }); } else { assert(base_shape.lens().size() <= 6); ctx.bulk_execute(base_shape.elements(), min_grain, [=](auto start, auto end) mutable { multi_index mi(base_shape, start); for(auto i = start; i < end; i++) { vec_apply(f, ts.data()[mi.offset(ts.get_shape())]...); ++mi; } }); } }; } template auto pointwise(Ts... ts) { return [=](context& ctx, const shape& base_shape, std::size_t min_grain, auto f) mutable { auto_vectorize(base_shape, ts...)( [&](auto bs, auto... xs) { pointwise_apply(xs...)(ctx, bs, min_grain, f); }); }; } template struct cpu_unary : reduce_dims_base, auto_register_op> { Op op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "cpu::" + op.name(); } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2); const auto& s = inputs.at(0); return {s.type(), s.lens()}; } argument compute(context& ctx, const shape& output_shape, const std::vector& args) const { argument result = get_arg(args, args.size() - 1); visit_all(result, get_arg(args, 0))([&](auto output, auto input) { auto op2 = op; pointwise(output, input)( ctx, output.get_shape(), 1024, [op2](auto& y, auto x) { y = op2.apply()(x); }); }); return result.reshape(output_shape); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; template struct cpu_binary : reduce_dims_base, auto_register_op> { Op op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "cpu::" + op.name(); } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(3); const auto& s = inputs.at(0); return {s.type(), s.lens()}; } argument compute(context& ctx, const shape& output_shape, const std::vector& args) const { argument result = get_arg(args, args.size() - 1); visit_all(result, get_arg(args, 0), get_arg(args, 1))( [&](auto output, auto input1, auto input2) { auto op2 = op; pointwise(output, input1, input2)( ctx, output.get_shape(), 1024, [op2](auto& z, auto x, auto y) { z = op2.apply()(x, y); }); }); return result.reshape(output_shape); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/target.hpp000066400000000000000000000037301510465702400256220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_CPU_TARGET_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_CPU_TARGET_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct pass; namespace cpu { struct MIGRAPHX_CPU_EXPORT target { std::string name() const; std::vector get_passes(migraphx::context& gctx, const compile_options&) const; migraphx::context get_context() const { return context{}; } argument copy_to(const argument& arg) const { return arg; } argument copy_from(const argument& arg) const { return arg; } argument allocate(const shape& s) const; }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/cpu/include/migraphx/cpu/write_literals.hpp000066400000000000000000000031671510465702400273710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_WRITE_LITERALS_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_CPU_WRITE_LITERALS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace cpu { struct write_literals { std::string name() const { return "cpu::write_literals"; } void apply(module& m) const; }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/cpu/layernorm.cpp000066400000000000000000000045541510465702400223130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_layernorm : dnnl_op { float epsilon = 1e-12f; template static auto reflect(Self& self, F f) { return pack(f(self.epsilon, "epsilon")); } std::string name() const { return "dnnl::layernorm"; } shape compute_shape(std::vector inputs) const { // Compensate for allocation inputs.pop_back(); check_shapes{this->trim_post_op_inputs(inputs), *this}.has(1); auto s = inputs.at(0); // Call to get_primitive to make sure an algo is available this->get_primitive(this->to_memory_desc(s, inputs)); return s; } dnnl::layer_normalization_forward::desc get_desc(const std::unordered_map& m) const { return {dnnl::prop_kind::forward_inference, m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC)), 1e-12f, dnnl::normalization_flags::none}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/logsoftmax.cpp000066400000000000000000000034111510465702400224550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_logsoftmax : dnnl_extend_op { dnnl::logsoftmax_forward::desc get_desc(const std::unordered_map& m) const { int axis = this->op.axis; return {dnnl::prop_kind::forward_inference, m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC_0)), axis}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/lowering.cpp000066400000000000000000000453521510465702400221320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct cpu_im2col { op::im2col op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } static std::string name() { return "cpu::im2col"; } shape compute_shape(const std::vector& inputs) const { return op.normalize_compute_shape(inputs); } argument compute(context&, const shape& output_shape, std::vector args) const { argument result{output_shape}; auto input_shape = args[0].get_shape(); auto weights_shape = args[1].get_shape(); visit_all(result, args[0])([&](auto col, auto input) { const std::size_t& height = input_shape.lens()[2]; const std::size_t& width = input_shape.lens()[3]; const std::size_t& channels = weights_shape.lens()[1]; const std::size_t& kernel_h = weights_shape.lens()[2]; const std::size_t& kernel_w = weights_shape.lens()[3]; const std::size_t& pad_h = op.padding[0]; const std::size_t& pad_w = op.padding[1]; const std::size_t& stride_h = op.stride[0]; const std::size_t& stride_w = op.stride[1]; long kdiv2_h = long(kernel_h) / 2; long kdiv2_w = long(kernel_w) / 2; // calculate output sizes const std::size_t col_height = (height - kernel_h + 2 * pad_h) / stride_h + 1; const std::size_t col_width = (width - kernel_w + 2 * pad_w) / stride_w + 1; // account for padding for the starting position of the input pixels long iinput = kdiv2_h - long(pad_h); // loop over output pixels (ioutput, joutput) for(std::size_t ioutput = 0; ioutput < col_height; ioutput++, iinput += stride_h) { long jinput = kdiv2_w - long(pad_w); for(std::size_t joutput = 0; joutput < col_width; joutput++, jinput += stride_w) { // compute linear index for output std::size_t ldx = ioutput * col_width + joutput; std::size_t p = 0; dfor(channels, kernel_h, kernel_w)([&](std::size_t c, std::size_t koffset, std::size_t loffset) { auto idx = iinput + long(koffset) - kdiv2_h; auto jdx = jinput + long(loffset) - kdiv2_w; col(ldx, p) = ((idx >= 0) and (idx < height) and (jdx >= 0) and (jdx < width)) ? input(0, c, idx, jdx) : 0; p++; }); } } }); return result; } }; MIGRAPHX_REGISTER_OP(cpu_im2col) struct cpu_op { operation op = op::identity{}; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "cpu::op"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } argument compute(context&, const shape& output_shape, const std::vector& args) const { return op.compute(output_shape, args); } value to_value() const { value v; v["name"] = op.name(); v["operator"] = op.to_value(); return v; } void from_value(const value& v) { op = make_op(v.at("name").to(), v.at("operator")); } friend std::ostream& operator<<(std::ostream& os, const cpu_op& x) { os << "cpu::" << x.op; return os; } }; MIGRAPHX_REGISTER_OP(cpu_op) struct cpu_pad { op::pad op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "cpu::pad"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } argument compute(context&, const shape& output_shape, std::vector args) const { assert(output_shape.standard()); argument result{output_shape}; result.visit([&](auto output) { using type = typename decltype(output)::value_type; std::fill(output.begin(), output.end(), pad_clamp(op.value)); }); visit_all(result, args[0])([&](auto output, auto input) { shape_for_each(input.get_shape(), [&](const auto& idx) { std::vector new_idx(idx.size()); std::transform( idx.begin(), idx.end(), op.pads.begin(), new_idx.begin(), [](auto i, auto j) { return i + j; }); output(new_idx.begin(), new_idx.end()) = input(idx.begin(), idx.end()); }); }); return result; } }; MIGRAPHX_REGISTER_OP(cpu_pad) struct cpu_rnn_var_sl_last_output { op::rnn_var_sl_last_output op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "cpu::rnn_var_sl_last_output"; } shape compute_shape(std::vector inputs) const { return op.compute_shape(std::move(inputs)); } argument compute(const shape& output_shape, std::vector args) const { argument result{output_shape}; auto out_comp_lens = args[0].get_shape().lens(); out_comp_lens[0] = 1; shape out_comp_s{output_shape.type(), out_comp_lens}; visit_all(result, args[0])([&](auto output, auto input) { args[1].visit([&](auto seq_lens) { par_for(output_shape.elements(), [&](auto i) { auto idx = out_comp_s.multi(i); auto b = idx[2]; if(op.direction == op::rnn_direction::reverse or idx[1] == 1) { idx[0] = 0; } else { idx[0] = seq_lens[b] - 1; } output[i] = input(idx.begin(), idx.end()); }); }); }); return result; } }; MIGRAPHX_REGISTER_OP(cpu_rnn_var_sl_last_output) struct cpu_apply { module* modl; std::unordered_map> apply_map{}; instruction_ref last{}; void extend_op(const std::string& op_name, const std::string& cpu_name, bool allocate = true) { apply_map.emplace(op_name, [=](instruction_ref ins) { auto&& op = ins->get_operator(); if(allocate) return replace(ins, make_op(cpu_name, op.to_value())); return modl->replace_instruction(ins, make_op(cpu_name, op.to_value()), ins->inputs()); }); } void extend_dnnl_algos(const std::string& dnnl_name, const std::vector>& algos) { for(auto&& pp : algos) { std::string op_name = pp.first; std::string algo = pp.second; apply_map.emplace(op_name, [=](instruction_ref ins) { auto v = ins->get_operator().to_value(); if(not v.is_object()) return ins; v["algo"] = algo; auto op = make_op(dnnl_name, v); return replace(ins, op); }); } } template auto fuse_match(M matcher, const operation& op, const std::vector& bind_inputs) { return match::make_match_finder(matcher, [=](auto&, const auto& r) { auto ins = r.result; std::vector inputs; std::transform(bind_inputs.begin(), bind_inputs.end(), std::back_inserter(inputs), [&](const auto& s) { return r.instructions[s]; }); inputs.push_back(this->insert_allocation(ins, ins->get_shape())); modl->replace_instruction(ins, op, inputs); }); } void init() { extend_dnnl_algos("dnnl::binary", { {"add", "binary_add"}, {"div", "binary_div"}, {"max", "binary_max"}, {"min", "binary_min"}, {"mul", "binary_mul"}, }); extend_dnnl_algos("dnnl::eltwise", { {"abs", "eltwise_abs"}, {"elu", "eltwise_elu"}, {"exp", "eltwise_exp"}, {"log", "eltwise_log"}, {"relu", "eltwise_relu"}, {"sqrt", "eltwise_sqrt"}, {"tanh", "eltwise_tanh"}, }); extend_dnnl_algos("dnnl::reduction", { {"reduce_max", "reduction_max"}, {"reduce_mean", "reduction_mean"}, {"reduce_min", "reduction_min"}, {"reduce_sum", "reduction_sum"}, }); extend_op("concat", "dnnl::concat"); extend_op("contiguous", "dnnl::reorder"); extend_op("convolution", "dnnl::convolution"); #ifndef MIGRAPHX_ENABLE_ZENDNN extend_op("dot", "dnnl::dot"); #endif extend_op("erf", "cpu::erf"); extend_op("gather", "cpu::gather"); extend_op("logsoftmax", "dnnl::logsoftmax"); extend_op("lrn", "dnnl::lrn"); extend_op("softmax", "dnnl::softmax"); extend_op("im2col", "cpu::im2col", false); extend_op("leaky_relu", "cpu::leaky_relu", false); extend_op("pad", "cpu::pad", false); extend_op("rnn_var_sl_last_output", "cpu::rnn_var_sl_last_output", false); } void apply() { init(); // Apply fusion matchers first match::find_matches(*modl, fuse_match(match::gelu_erf(), make_op("dnnl::eltwise", {{"algo", "eltwise_gelu_erf"}}), {"x"}), fuse_match(match::gelu_tanh(), make_op("dnnl::eltwise", {{"algo", "eltwise_gelu_tanh"}}), {"x"}), fuse_match(match::layernorm(), make_op("dnnl::layernorm"), {"x"})); // Apply these operators first so the inputs can be const folded for(auto it : iterator_for(*modl)) { // skip lowering if input has fp8 as one of the inputs since oneDNN doesn't have fp8 // supported yet. if(std::any_of(it->inputs().begin(), it->inputs().end(), [](const auto& i) { return contains(fp8_types{}.get(), i->get_shape().type()); })) continue; if(it->name() == "pow") { apply_pow(it); } } for(auto it : iterator_for(*modl)) { // skip lowering if input has fp8 as one of the inputs since oneDNN doesn't have fp8 // supported yet. if(std::any_of(it->inputs().begin(), it->inputs().end(), [](const auto& i) { return contains(fp8_types{}.get(), i->get_shape().type()); })) continue; if(it->name() == "pooling") { apply_pooling(it); } #ifndef MIGRAPHX_ENABLE_ZENDNN else if(it->name() == "convolution_backwards") { apply_convolution_backwards(it); } #endif else if(it->name() == "reshape") { apply_reshape(it); } else if(apply_map.count(it->name()) > 0) { apply_map.at(it->name())(it); } } } instruction_ref apply_pow(instruction_ref ins) const { auto beta = read_scalar(ins->inputs()[1]); if(beta.empty()) return ins; return replace(ins, make_op("dnnl::eltwise", {{"algo", "eltwise_pow"}, {"alpha", 1.0}, {"beta", beta.front()}}), {ins->inputs().front()}); } // TODO: update lowering to run the reference // code when OneDNN can't execute pooling for a CPU // OneDNN has a limitation on padding size for pooling. see // https://oneapi-src.github.io/oneDNN/dev_guide_convolution.html#doxid-dev-guide-convolution // padding = {2}; stride = {1}; lengths = {3} succeeds in oneDNN but // padding = {2}; stride = {1}; lengths = {2} fails. // Also, the referenced documentation contains a max. dimension size of 14 for the kernel // ("weights tensor") that MIGraphX doesn't enforce. instruction_ref apply_pooling(instruction_ref ins) const { auto&& op = ins->get_operator(); auto v = op.to_value(); if(has_op("dnnl::pooling") and ins->get_shape().type() == shape::type_t::float_type and not v["ceil_mode"].to() and v["mode"].to() != op::pooling_mode::lpnorm) return replace(ins, make_op("dnnl::pooling", op.to_value())); return ins; } /* Lowers reshape copy operator to reshape lazy by inserting contiguous operators around it. Contiguous ops will later by removed by eliminate_contiguous pass. */ instruction_ref apply_reshape(instruction_ref ins) const { std::vector before_contiguous_args = ins->inputs(); auto before_alloc = insert_allocation(ins, before_contiguous_args.front()->get_shape().as_standard()); before_contiguous_args.push_back(before_alloc); auto before_contig = modl->insert_instruction(ins, make_op("dnnl::reorder"), {before_contiguous_args}); auto new_reshape_lazy = modl->insert_instruction( ins, make_op("reshape_lazy", {{"dims", {ins->get_operator().to_value().at("dims")}}}), before_contig); std::vector after_contiguous_args = {new_reshape_lazy}; auto after_alloc = insert_allocation(new_reshape_lazy, new_reshape_lazy->get_shape()); after_contiguous_args.push_back(after_alloc); return modl->replace_instruction(ins, make_op("dnnl::reorder"), after_contiguous_args); } /** * Lower to dnnl:convolution_backwards if supported. * OneDNN doesn't support group convolution transpose. */ instruction_ref apply_convolution_backwards(instruction_ref ins) const { auto&& op = ins->get_operator(); auto v = op.to_value(); if(has_op("dnnl::convolution_backwards") and v["group"].to() == 1) { return replace(ins, make_op("dnnl::convolution_backwards", op.to_value())); } return ins; } template static std::vector read_scalar(instruction_ref ins) { if(ins->name() == "contiguous") return read_scalar(ins->inputs().front()); if(ins->get_shape().elements() != 1 and not ins->get_shape().scalar()) return {}; auto r = ins->eval(); if(r.empty()) return {}; return {r.at()}; } instruction_ref replace(instruction_ref ins, const operation& op) const { return replace(ins, op, ins->inputs()); } instruction_ref replace(instruction_ref ins, const operation& op, std::vector inputs) const { inputs.push_back(insert_allocation(ins, ins->get_shape())); return modl->replace_instruction(ins, op, inputs); } instruction_ref insert_allocation(instruction_ref ins, const shape& s) const { return modl->insert_instruction(ins, make_op("allocate", {{"shape", to_value(s)}})); } }; void lowering::apply(module& m) const { cpu_apply{&m}.apply(); } } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/lrn.cpp000066400000000000000000000035661510465702400211000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_lrn : dnnl_extend_op { dnnl::lrn_forward::desc get_desc(const std::unordered_map& m) const { return {dnnl::prop_kind::forward_inference, dnnl::algorithm::lrn_across_channels, m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC_0)), this->op.size, this->op.alpha, this->op.beta, this->op.bias}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/mod.cpp000066400000000000000000000027101510465702400210520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { template struct cpu_binary; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/pooling.cpp000066400000000000000000000065631510465702400217540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_pooling : dnnl_extend_op { std::vector arg_map(int) const { return {MIGRAPHX_DNNL_PREFIX(ARG_SRC)}; } dnnl::algorithm get_algo() const { switch(op.mode) { case op::pooling_mode::max: return dnnl::algorithm::pooling_max; case op::pooling_mode::average: return op.count_include_pad ? dnnl::algorithm::pooling_avg_include_padding : dnnl::algorithm::pooling_avg_exclude_padding; case op::pooling_mode::lpnorm: MIGRAPHX_THROW("Lpnorn pooling mode not supported"); } MIGRAPHX_THROW("Unknown pooling mode"); } dnnl::pooling_v2_forward::desc get_desc(const std::unordered_map& m) const { auto algo = get_algo(); auto kdims = op.kdims(); std::vector padding_l(op.padding.begin(), op.padding.begin() + kdims); std::vector padding_r(op.padding.begin() + kdims, op.padding.end()); // Note: It is not documented, but the default dilation seems to be 0 instead of 1. // We need to offset dilations with -1. std::vector dilations; std::transform(op.dilations.cbegin(), op.dilations.cend(), std::back_inserter(dilations), [](size_t d) { return d - 1; }); return {dnnl::prop_kind::forward_inference, algo, m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST)), to_dnnl_dims(op.stride), to_dnnl_dims(op.lengths), to_dnnl_dims(dilations), to_dnnl_dims(padding_l), to_dnnl_dims(padding_r)}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/preallocate.cpp000066400000000000000000000043361510465702400225740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct cpu_preallocate : auto_register_op { shape s; std::string id = ""; argument data; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape"), f(self.id, "id")); } std::string name() const { return "cpu::preallocate"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(0); return s; } argument compute(context&, const shape&, const std::vector&) const { return data; } void finalize(context&, const shape&, const std::vector&) { data = argument(s); } lifetime get_lifetime() const { return lifetime::global; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/reduction.cpp000066400000000000000000000051301510465702400222660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_reduction : dnnl_op { std::string algo; std::vector axes{}; template static auto reflect(Self& self, F f) { return pack_join(self.reflect_base(self, f), pack(f(self.algo, "algo"), f(self.axes, "axes"))); } std::string name() const { return "dnnl::reduction"; } shape compute_shape(std::vector inputs) const { // Compensate for allocation inputs.pop_back(); check_shapes{this->trim_post_op_inputs(inputs), *this}.has(1).standard(); auto s = inputs.at(0); auto lens = s.lens(); for(auto axis : axes) { lens[axis] = 1; } auto r = shape{s.type(), lens}; // Call to get_primitive to make sure an algo is available this->get_primitive(this->to_memory_desc(r, inputs)); return r; } dnnl::reduction::desc get_desc(const std::unordered_map& m) const { return {to_dnnl_algo(algo), m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST)), 0, 0}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/reorder.cpp000066400000000000000000000046341510465702400217440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_reorder : dnnl_op { std::string name() const { return "dnnl::reorder"; } shape adjust_shape(const shape& x, int, const shape&) const { return x; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2); auto r = inputs.back(); // Call to get_primitive to make sure an algo is available this->get_primitive(this->to_memory_desc(r, inputs)); return r; } // Custom desc class since its missing in dnnl struct desc { dnnl::memory::desc src; dnnl::memory::desc dst; }; desc get_desc(const std::unordered_map& m) const { return {m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC)), m.at(MIGRAPHX_DNNL_PREFIX(ARG_DST))}; } auto get_primitive_desc(const desc& d, const dnnl::primitive_attr& attr) const { auto& engine = get_dnnl_context().engine; return dnnl::reorder::primitive_desc(engine, d.src, engine, d.dst, attr); } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/softmax.cpp000066400000000000000000000033631510465702400217610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct dnnl_softmax : dnnl_extend_op { dnnl::softmax_forward::desc get_desc(const std::unordered_map& m) const { int axis = this->op.axis; return {dnnl::prop_kind::forward_inference, m.at(MIGRAPHX_DNNL_PREFIX(ARG_SRC_0)), axis}; } }; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/sub.cpp000066400000000000000000000027101510465702400210640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { template struct cpu_binary; } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/target.cpp000066400000000000000000000113601510465702400215620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { std::string target::name() const { return "cpu"; } // cppcheck-suppress constParameterReference std::vector target::get_passes(migraphx::context& gctx, const compile_options&) const { auto& ctx = any_cast(gctx); std::set unsupported_types(shape::types().begin(), shape::types().end()); std::set unsupported_ops{ "all", "scatternd_add", "scatternd_mul", "scatternd_none"}; unsupported_types.erase(shape::type_t::float_type); return {normalize_ops{}, rewrite_quantization{}, dead_code_elimination{}, eliminate_data_type{unsupported_types, shape::type_t::float_type, unsupported_ops}, dead_code_elimination{}, simplify_reshapes{}, eliminate_convert{}, eliminate_identity{}, eliminate_pad{}, dead_code_elimination{}, rewrite_rnn{}, dead_code_elimination{}, eliminate_common_subexpression{}, dead_code_elimination{}, simplify_algebra{}, simplify_reshapes{}, eliminate_convert{}, dead_code_elimination{}, simplify_reshapes{}, eliminate_convert{}, dead_code_elimination{}, simplify_algebra{}, simplify_reshapes{}, eliminate_convert{}, dead_code_elimination{}, propagate_constant{}, dead_code_elimination{}, auto_contiguous{}, lowering{}, eliminate_contiguous{"dnnl::reorder"}, dead_code_elimination{}, replace_allocate{cpu_allocation_model{}}, dead_code_elimination{}, adjust_allocation{cpu_allocation_model{}}, dead_code_elimination{}, fuse_ops{&ctx}, dead_code_elimination{}, write_literals{}, dead_code_elimination{}, memory_coloring{"cpu::allocate"}, dead_code_elimination{}, preallocate_param{"scratch", cpu_allocation_model{}}, dead_code_elimination{}}; } argument target::allocate(const shape& s) const { return fill_argument(s, 0); } MIGRAPHX_REGISTER_TARGET(target); } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/cpu/write_literals.cpp000066400000000000000000000044461510465702400233340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace cpu { struct cpu_literal { argument data; template static auto reflect(Self& self, F f) { return pack(f(self.data, "data")); } std::string name() const { return "cpu::literal"; } shape compute_shape(const std::vector&) const { return data.get_shape(); } argument compute(const shape&, const std::vector&) const { return data; } friend std::ostream& operator<<(std::ostream& os, const cpu_literal& x) { os << x.name(); return os; } }; MIGRAPHX_REGISTER_OP(cpu_literal); void write_literals::apply(module& m) const { for(auto ins : iterator_for(m)) { if(ins->name() != "@literal") continue; m.replace_instruction(ins, cpu_literal{ins->get_literal().get_argument()}); } } } // namespace cpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/fpga/000077500000000000000000000000001510465702400177155ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/fpga/CMakeLists.txt000066400000000000000000000033351510465702400224610ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### add_library(migraphx_fpga target.cpp lowering.cpp subgraph.cpp vitis_ai_adapter.cpp ) set_target_properties(migraphx_fpga PROPERTIES EXPORT_NAME fpga) rocm_set_soversion(migraphx_fpga ${MIGRAPHX_SO_VERSION}) rocm_clang_tidy_check(migraphx_fpga) target_link_libraries(migraphx_fpga migraphx) rocm_install_targets( PRIVATE TARGETS migraphx_fpga INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include ) ROCm-AMDMIGraphX-46524e8/src/targets/fpga/include/000077500000000000000000000000001510465702400213405ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/fpga/include/migraphx/000077500000000000000000000000001510465702400231575ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/fpga/include/migraphx/fpga/000077500000000000000000000000001510465702400240745ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/fpga/include/migraphx/fpga/context.hpp000066400000000000000000000030351510465702400262720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_FPGA_CONTEXT_HPP #define MIGRAPHX_GUARD_FPGA_CONTEXT_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fpga { struct context { int id = 0; void finish() const {} }; } // namespace fpga } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_FPGA_CONTEXT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/fpga/include/migraphx/fpga/lowering.hpp000066400000000000000000000032611510465702400264350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_FPGA_LOWERING_HPP #define MIGRAPHX_GUARD_FPGA_LOWERING_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fpga { struct lowering { context* ctx = nullptr; std::string name() const { return "fpga::lowering"; } void apply(module& m) const; }; } // namespace fpga } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_FPGA_LOWERING_HPP ROCm-AMDMIGraphX-46524e8/src/targets/fpga/include/migraphx/fpga/subgraph.hpp000066400000000000000000000031771510465702400264300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_FPGA_SUBGRAPH_HPP #define MIGRAPHX_GUARD_FPGA_SUBGRAPH_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fpga { struct subgraph { std::string name() const { return "fpga::subgraph"; } void apply(module_pass_manager& mpm) const; }; } // namespace fpga } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_FPGA_SUBGRAPH_HPP ROCm-AMDMIGraphX-46524e8/src/targets/fpga/include/migraphx/fpga/target.hpp000066400000000000000000000041641510465702400261000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_FPGA_TARGET_HPP #define MIGRAPHX_GUARD_FPGA_TARGET_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct pass; namespace fpga { struct target { std::string name() const; std::vector get_passes(migraphx::context& ctx, const compile_options&) const; migraphx::context get_context() const { return context{}; } supported_segments find_supported(const_module_ref mod, support_metric m) const; argument copy_to(const argument& arg) const { return arg; } argument copy_from(const argument& arg) const { return arg; } argument allocate(const shape& s) const; }; } // namespace fpga } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_FPGA_TARGET_HPP ROCm-AMDMIGraphX-46524e8/src/targets/fpga/include/migraphx/fpga/vitis_ai_adapter.hpp000066400000000000000000000034751510465702400301250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_FPGA_VITIS_AI_ADAPTER_HPP #define MIGRAPHX_GUARD_FPGA_VITIS_AI_ADAPTER_HPP #include #include #include namespace vitis_ai { class x_model { migraphx::shape shape; public: migraphx::shape get_shape() const; void set_shape(migraphx::shape); }; x_model create_xmodel(migraphx::const_module_ref mod); migraphx::argument execute(const x_model& xmodel, const migraphx::shape& output_shape, std::vector& args); } // namespace vitis_ai #endif // MIGRAPHX_GUARD_FPGA_VITIS_AI_ADAPTER_HPP ROCm-AMDMIGraphX-46524e8/src/targets/fpga/lowering.cpp000066400000000000000000000055301510465702400222520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include "migraphx/fpga/vitis_ai_adapter.hpp" namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fpga { struct fpga_vitis_op { fpga_vitis_op() = default; explicit fpga_vitis_op(vitis_ai::x_model model) : xmodel(std::move(model)){}; vitis_ai::x_model xmodel; int dummy = 0; template static auto reflect(Self& self, F f) { // return pack(f(self.xmodel, "xmodel")); return pack(f(self.dummy, "dummy")); } std::string name() const { return "fpga::vitis_ai"; } shape compute_shape(const std::vector& inputs) const { (void)inputs; return xmodel.get_shape(); } argument compute(const context& ctx, const shape& output_shape, std::vector args) const { std::cout << "The context is " << ctx.id << std::endl; return ::vitis_ai::execute(xmodel, output_shape, args); } }; MIGRAPHX_REGISTER_OP(fpga_vitis_op) void lowering::apply(module& m) const { auto* mod = &m; // test modifying the context from a pass ctx->id = 2; for(auto it : iterator_for(*mod)) { if(it->name() == "fpga::vitis_placeholder") { assert(it->module_inputs().size() == 1); auto xmodel = ::vitis_ai::create_xmodel(it->module_inputs()[0]); mod->replace_instruction(it, fpga_vitis_op{xmodel}, it->inputs()); } } } } // namespace fpga } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/fpga/subgraph.cpp000066400000000000000000000101541510465702400222350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include "migraphx/iterator.hpp" #include #include "migraphx/make_op.hpp" #include "migraphx/module.hpp" #include "migraphx/ranges.hpp" #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fpga { struct fpga_placeholder_op { fpga_placeholder_op() = default; int dummy = 0; template static auto reflect(Self& self, F f) { return pack(f(self.dummy, "dummy")); } std::string name() const { return "fpga::vitis_placeholder"; } shape compute_shape(const std::vector& inputs, std::vector mods) const { (void)inputs; if(mods.size() != 1) { MIGRAPHX_THROW("should have one submodule."); } module_ref sm = mods.front(); if(sm->get_output_shapes().size() != 1) MIGRAPHX_THROW("Only one return"); return sm->get_output_shapes().front(); } }; MIGRAPHX_REGISTER_OP(fpga_placeholder_op) static bool is_fpga_instr(migraphx::instruction_ref it) { // assuming all instructions that aren't @param, @literal, or input data are fpga instrs if(migraphx::starts_with(it->name(), "@")) { return false; } // no inputs to the instr means it's input data if(it->inputs().empty()) { return false; } return true; } void subgraph::apply(module_pass_manager& mpm) const { auto& mod = mpm.get_module(); auto* pm = mpm.create_module(mod.name() + ":fpga"); pm->set_bypass(); migraphx::instruction_ref first = mod.end(); migraphx::instruction_ref last; std::vector literal_inputs; for(auto it : iterator_for(mod)) { // assuming we want all the params/literals as inputs to the FPGA submodule if(migraphx::starts_with(it->name(), "@param") or migraphx::starts_with(it->name(), "@literal")) { literal_inputs.push_back(it); } if(is_fpga_instr(it)) { if(first == mod.end()) { first = it; } last = it; } } // TODO(varunsh): this code may be replaceable by code in the fuse_pointwise pass // assuming all FPGA instructions are in one contiguous range pm->insert_instructions(pm->end(), first, std::next(last), {}); migraphx::instruction_ref placeholder_ins; for(auto it : iterator_for(mod)) { if(migraphx::starts_with(it->name(), "@return")) { placeholder_ins = mod.insert_instruction( it, migraphx::make_op("fpga::vitis_placeholder"), literal_inputs, {pm}); break; } } mod.replace_return({placeholder_ins}); } } // namespace fpga } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/fpga/target.cpp000066400000000000000000000055611510465702400217160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace fpga { std::string target::name() const { return "fpga"; } std::vector target::get_passes(migraphx::context& gctx, const compile_options&) const { // not sure if all these passes are needed but they were copied from ref/ auto& ctx = any_cast(gctx); return {normalize_ops{}, eliminate_pad{}, dead_code_elimination{}, insert_pad{}, dead_code_elimination{}, rewrite_rnn{}, dead_code_elimination{}, auto_contiguous{}, dead_code_elimination{}, subgraph{}, dead_code_elimination{}, lowering{&ctx}, dead_code_elimination{}}; } argument target::allocate(const shape& s) const { return fill_argument(s, 0); } supported_segments target::find_supported(const_module_ref mod, support_metric m) const { (void)m; supported_segment instrs; for(const auto ins : iterator_for(*mod)) { instrs.instructions.insert(ins); } instrs.metric = 1; // arbitrary value return {instrs}; } MIGRAPHX_REGISTER_TARGET(target); } // namespace fpga } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/fpga/vitis_ai_adapter.cpp000066400000000000000000000043541510465702400237360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/fpga/vitis_ai_adapter.hpp" #include "migraphx/module.hpp" #include "migraphx/stringutils.hpp" namespace vitis_ai { migraphx::shape x_model::get_shape() const { return shape; }; void x_model::set_shape(migraphx::shape s) { shape = s; } x_model create_xmodel(migraphx::const_module_ref mod) { std::cout << "Calling an external function: create_xmodel!\n"; x_model xmodel; xmodel.set_shape(migraphx::shape(mod->get_output_shapes())); return xmodel; } migraphx::argument execute(const x_model& xmodel, const migraphx::shape& output_shape, std::vector& args) { (void)xmodel; std::cout << "Calling an external function: execute!\n"; std::cout << "Output Shape: " << output_shape << std::endl; std::cout << "Args: " << args.size() << std::endl; for(const auto& arg : args) { std::cout << " " << arg.get_shape() << std::endl; } std::cout << std::endl; migraphx::argument result{output_shape}; return result; } } // namespace vitis_ai ROCm-AMDMIGraphX-46524e8/src/targets/gpu/000077500000000000000000000000001510465702400175735ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/CMakeLists.txt000066400000000000000000000357751510465702400223540ustar00rootroot00000000000000# #################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. # #################################################################################### find_package(hip REQUIRED) if(NOT GPU_TARGETS) set(fatal_msg "HIP package is broken and has no GPU_TARGETS. Please pass GPU_TARGETS to cmake.") if(NOT WIN32) set(fatal_msg "${fatal_msg}\nUse -DGPU_TARGETS=$(/opt/rocm/bin/rocminfo | grep -o -m1 'gfx.*') to build for your GPU.") endif() message(FATAL_ERROR ${fatal_msg}) endif() if(MIGRAPHX_USE_MIOPEN) find_package(miopen REQUIRED) message(STATUS "MIGraphX is using MIOpen") else() message(STATUS "MIGraphX is not using MIOpen") endif() if(MIGRAPHX_USE_ROCBLAS) # rocblas find_package(rocblas REQUIRED) message(STATUS "MIGraphX build with rocBLAS") else() message(STATUS "MIGraphX build without rocBLAS") endif() if(MIGRAPHX_USE_HIPBLASLT) # hipblaslt find_package(hipblaslt REQUIRED) # Making hipblas required to workaround the broken hipblaslt package. find_package(hipblas REQUIRED) message(STATUS "MIGraphx build with hipBLAS and hipBLASLt") else() message(STATUS "MIGraphX build without hipBLAS and hipBLASLt") endif() if(MIGRAPHX_USE_COMPOSABLEKERNEL) find_package(composable_kernel 1.0.0 REQUIRED COMPONENTS jit_library) endif() if(BUILD_DEV) set(MIGRAPHX_USE_HIPRTC OFF CACHE BOOL "Use hipRTC APIs") else() set(MIGRAPHX_USE_HIPRTC ON CACHE BOOL "Use hipRTC APIs") endif() file(GLOB KERNEL_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/kernels/include/migraphx/kernels/*.hpp) if(NOT MIGRAPHX_USE_COMPOSABLEKERNEL) list(REMOVE_ITEM KERNEL_FILES ${CMAKE_CURRENT_SOURCE_DIR}/kernels/include/migraphx/kernels/ck_gemm.hpp ${CMAKE_CURRENT_SOURCE_DIR}/kernels/include/migraphx/kernels/ck_gemm_softmax_gemm.hpp ${CMAKE_CURRENT_SOURCE_DIR}/kernels/include/migraphx/kernels/ck.hpp) endif() add_embed_library(migraphx_kernels ${KERNEL_FILES} RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/kernels/include/) configure_file(device/targets.hpp.in include/migraphx/gpu/device/targets.hpp) file(GLOB DEVICE_GPU_SRCS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/device/*.cpp) add_library(migraphx_device ${DEVICE_GPU_SRCS}) add_library(compile_for_gpu INTERFACE) target_compile_features(compile_for_gpu INTERFACE cxx_std_17) target_compile_options(compile_for_gpu INTERFACE -fno-gpu-rdc -Wno-cuda-compat -Wno-unused-command-line-argument -Xclang -fnative-half-arguments-and-returns) target_link_options(compile_for_gpu INTERFACE -fno-gpu-rdc -Wno-invalid-command-line-argument -Wno-unused-command-line-argument -Wno-option-ignored) target_link_libraries(compile_for_gpu INTERFACE hip::device) check_cxx_compiler_flag("--cuda-host-only -fhip-lambda-host-device -x hip" HAS_HIP_LAMBDA_HOST_DEVICE) if(HAS_HIP_LAMBDA_HOST_DEVICE) message(STATUS "Enable -fhip-lambda-host-device") target_compile_options(compile_for_gpu INTERFACE -fhip-lambda-host-device) endif() set_target_properties(migraphx_device PROPERTIES EXPORT_NAME device) rocm_set_soversion(migraphx_device ${MIGRAPHX_SO_VERSION}) rocm_clang_tidy_check(migraphx_device) target_link_libraries(migraphx_device PUBLIC migraphx) target_link_libraries(migraphx_device PRIVATE compile_for_gpu) if(NOT MIGRAPHX_USE_MIOPEN AND NOT MIGRAPHX_USE_ROCBLAS) target_link_libraries(migraphx_device INTERFACE hip::host) endif() target_include_directories(migraphx_device PUBLIC $) target_include_directories(migraphx_device PRIVATE $) target_include_directories(migraphx_device PRIVATE $) target_compile_options(migraphx_device PRIVATE -Wno-ignored-attributes) migraphx_generate_export_header(migraphx_device DIRECTORY migraphx/gpu/device) add_library(kernel_file_check EXCLUDE_FROM_ALL) foreach(KERNEL_FILE ${KERNEL_FILES}) get_filename_component(KERNEL_BASE_FILE ${KERNEL_FILE} NAME_WE) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/kernels/include/migraphx/kernels/${KERNEL_BASE_FILE}.cpp "#include \n") target_sources(kernel_file_check PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/kernels/include/migraphx/kernels/${KERNEL_BASE_FILE}.cpp) endforeach() target_compile_definitions(kernel_file_check PRIVATE -DMIGRAPHX_NLOCAL=256) target_compile_definitions(kernel_file_check PRIVATE -DMIGRAPHX_WAVEFRONTSIZE=64) target_include_directories(kernel_file_check PRIVATE $) target_link_libraries(kernel_file_check compile_for_gpu) if(MIGRAPHX_USE_COMPOSABLEKERNEL) target_link_libraries(kernel_file_check composable_kernel::jit_library) endif() rocm_clang_tidy_check(kernel_file_check) file(GLOB JIT_GPU_SRCS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/jit/*.cpp) if(NOT MIGRAPHX_USE_COMPOSABLEKERNEL) list(REMOVE_ITEM JIT_GPU_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/jit/ck_gemm.cpp ${CMAKE_CURRENT_SOURCE_DIR}/jit/ck_gemm_softmax_gemm.cpp) endif() if(MIGRAPHX_USE_MIOPEN) set(MIOPEN_SRCS abs.cpp) endif() add_library(migraphx_gpu analyze_streams.cpp allocation_model.cpp argmax.cpp argmin.cpp code_object_op.cpp compile_ops.cpp compile_gen.cpp compile_hip.cpp compile_hip_code_object.cpp compile_hipblaslt.cpp compile_miopen.cpp compile_pointwise.cpp compiler.cpp device_name.cpp fixed_pad.cpp fuse_ck.cpp fuse_mlir.cpp fuse_ops.cpp gemm_impl.cpp hip.cpp hipblaslt.cpp hip_gemm_impl.cpp kernel.cpp lowering.cpp logsoftmax.cpp loop.cpp lrn.cpp mlir.cpp multinomial.cpp no_device.cpp nonzero.cpp pack_args.cpp prefuse_ops.cpp prepare_reduce.cpp perfdb.cpp pooling.cpp problem_cache.cpp reverse.cpp rnn_variable_seq_lens.cpp rocblas.cpp schedule_model.cpp sync_device.cpp target.cpp time_op.cpp topk.cpp write_literals.cpp ${JIT_GPU_SRCS} ${MIOPEN_SRCS} ) set_target_properties(migraphx_gpu PROPERTIES EXPORT_NAME gpu) migraphx_generate_export_header(migraphx_gpu) function(register_migraphx_gpu_ops PREFIX) foreach(OP ${ARGN}) register_op(migraphx_gpu HEADER migraphx/gpu/${OP}.hpp OPERATORS gpu::${PREFIX}${OP} INCLUDES migraphx/gpu/context.hpp) endforeach() endfunction() register_migraphx_gpu_ops(hip_ argmax argmin fixed_pad logsoftmax loop multinomial nonzero prefix_scan_sum reverse topk ) if (MIGRAPHX_USE_MIOPEN) register_migraphx_gpu_ops(miopen_ abs contiguous lrn pooling ) else() register_migraphx_gpu_ops(miopen_ contiguous ) endif() register_op(migraphx_gpu HEADER migraphx/gpu/rnn_variable_seq_lens.hpp OPERATORS gpu::hip_rnn_var_sl_shift_sequence gpu::hip_rnn_var_sl_shift_output gpu::hip_rnn_var_sl_last_output INCLUDES migraphx/gpu/context.hpp) if(MIGRAPHX_USE_ROCBLAS) register_op(migraphx_gpu HEADER migraphx/gpu/gemm.hpp OPERATORS gpu::rocblas_gemm gpu::rocblas_gemm INCLUDES migraphx/gpu/context.hpp) endif() if(MIGRAPHX_USE_HIPBLASLT) register_op(migraphx_gpu HEADER migraphx/gpu/hip_gemm.hpp OPERATORS gpu::hip_gemm gpu::hip_gemm INCLUDES migraphx/gpu/context.hpp) endif() if (MIGRAPHX_USE_MIOPEN) register_op(migraphx_gpu HEADER migraphx/gpu/convolution.hpp OPERATORS gpu::miopen_convolution gpu::miopen_convolution gpu::miopen_convolution INCLUDES migraphx/gpu/context.hpp) endif() rocm_set_soversion(migraphx_gpu ${MIGRAPHX_SO_VERSION}) rocm_clang_tidy_check(migraphx_gpu) set(MIGRAPHX_ENABLE_MLIR ON CACHE BOOL "") if(MIGRAPHX_ENABLE_MLIR) # Find package rocMLIR find_package(rocMLIR 1.0.0 CONFIG REQUIRED) message(STATUS "Build with rocMLIR::rockCompiler ${rocMLIR_VERSION}") target_compile_definitions(migraphx_gpu PRIVATE "-DMIGRAPHX_MLIR") # Make this private to avoid multiple inclusions of LLVM symbols. # TODO: Fix rocMLIR's library to hide LLVM internals. target_link_libraries(migraphx_gpu PRIVATE rocMLIR::rockCompiler) endif() if(MIGRAPHX_USE_HIPRTC) find_package(hiprtc REQUIRED) message(STATUS "MIGraphX is using hipRTC") target_compile_definitions(migraphx_gpu PRIVATE -DMIGRAPHX_USE_HIPRTC=1) target_link_libraries(migraphx_gpu PUBLIC hiprtc::hiprtc) else() message(STATUS "MIGraphX is using HIP Clang") # Get flags needed to compile hip include(TargetFlags) target_flags(HIP_COMPILER_FLAGS hip::device) # Remove cuda arch flags string(REGEX REPLACE "--cuda-gpu-arch=[a-z0-9]+ ?" "" HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") string(REGEX REPLACE "--offload-arch=[a-z0-9:+-]+ ?" "" HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") # Skip library paths since hip will incorrectly treat it as a source file string(APPEND HIP_COMPILER_FLAGS " ") if(WIN32) string(REPLACE "\\" "/" HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") endif() foreach(_unused RANGE 2) string(REGEX REPLACE " /[^ ]+\\.(a|so) " " " HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") endforeach() message(STATUS "Hip compiler flags: \"${HIP_COMPILER_FLAGS}\"") target_compile_definitions(migraphx_gpu PRIVATE -DMIGRAPHX_HIP_COMPILER="${CMAKE_CXX_COMPILER}" -DMIGRAPHX_HIP_COMPILER_FLAGS="${HIP_COMPILER_FLAGS}" ) if(DEFINED CMAKE_CXX_COMPILER_LAUNCHER) if(WIN32) execute_process(COMMAND where ${CMAKE_CXX_COMPILER_LAUNCHER} OUTPUT_VARIABLE MIGRAPHX_HIP_COMPILER_LAUNCHER) else() execute_process(COMMAND which ${CMAKE_CXX_COMPILER_LAUNCHER} OUTPUT_VARIABLE MIGRAPHX_HIP_COMPILER_LAUNCHER) endif() string(STRIP "${MIGRAPHX_HIP_COMPILER_LAUNCHER}" MIGRAPHX_HIP_COMPILER_LAUNCHER) target_compile_definitions(migraphx_gpu PRIVATE -DMIGRAPHX_HIP_COMPILER_LAUNCHER="${MIGRAPHX_HIP_COMPILER_LAUNCHER}") endif() endif() target_compile_definitions(migraphx_gpu PUBLIC MIGRAPHX_CXX_COMPILER="${CMAKE_CXX_COMPILER}") # Check miopen find mode api include(CheckLibraryExists) if (MIGRAPHX_USE_MIOPEN) get_target_property(MIOPEN_LOCATION MIOpen LOCATION) target_compile_definitions(migraphx_gpu PUBLIC MIGRAPHX_USE_MIOPEN=1) check_library_exists(MIOpen "miopenHiddenSetConvolutionFindMode" "${MIOPEN_LOCATION}" HAS_FIND_MODE_API) check_library_exists(MIOpen "miopenFindSolutions" "${MIOPEN_LOCATION}" HAS_FIND_2_API) else() target_compile_definitions(migraphx_gpu PUBLIC MIGRAPHX_USE_MIOPEN=0) endif() if(MIGRAPHX_USE_ROCBLAS) get_target_property(ROCBLAS_LOCATION roc::rocblas LOCATION) target_compile_definitions(migraphx_gpu PUBLIC MIGRAPHX_USE_ROCBLAS=1) # Beta API for automated GEMM tuning check_library_exists(roc::rocblas "rocblas_gemm_ex_get_solutions" "${ROCBLAS_LOCATION}" HAS_ROCBLAS_TUNING_BETA_FEATURE_API) else() target_compile_definitions(migraphx_gpu PUBLIC MIGRAPHX_USE_ROCBLAS=0) endif() if(MIGRAPHX_USE_HIPBLASLT) target_compile_definitions(migraphx_gpu PUBLIC MIGRAPHX_USE_HIPBLASLT=1) else() target_compile_definitions(migraphx_gpu PUBLIC MIGRAPHX_USE_HIPBLASLT=0) endif() if(MIGRAPHX_USE_MIOPEN) set(MIGRAPHX_USE_FIND_2_API "${HAS_FIND_2_API}" CACHE BOOL "") if(MIGRAPHX_USE_FIND_2_API) check_library_exists(MIOpen "miopenSetFindOptionPreallocatedTensor" "${MIOPEN_LOCATION}" HAS_PREALLOCATION_API) if(HAS_PREALLOCATION_API) target_compile_definitions(migraphx_gpu PUBLIC -DMIGRAPHX_HAS_FIND_2_API -DMIGRAPHX_PREALLOCATE_MIOPEN_BUFFERS) else() target_compile_definitions(migraphx_gpu PUBLIC -DMIGRAPHX_HAS_FIND_2_API) endif() message(STATUS "MIGraphx is using Find-2.0 API of MIOpen") else() message(STATUS "MIGraphx is using legacy Find API in MIOpen") endif() if(HAS_FIND_MODE_API) target_compile_definitions(migraphx_gpu PUBLIC -DMIGRAPHX_HAS_FIND_MODE_API) message(STATUS "MIGraphx is using Find Mode API of MIOpen") else() message(STATUS "MIOpen does not have find mode api") endif() target_link_libraries(migraphx_gpu PUBLIC MIOpen) endif() if(MIGRAPHX_USE_ROCBLAS) if(HAS_ROCBLAS_TUNING_BETA_FEATURE_API) target_compile_definitions(migraphx_gpu PUBLIC -DMIGRAPHX_USE_ROCBLAS_TUNING_API -DROCBLAS_BETA_FEATURES_API -DROCBLAS_NO_DEPRECATED_WARNINGS) message(STATUS "MIGraphx is using Beta API of rocBLAS") else() message(STATUS "rocBLAS does not have User Tuning Beta API") endif() if(HAS_ROCBLAS_FP8_BETA_API) target_compile_definitions(migraphx_gpu PUBLIC -DMIGRAPHX_USE_ROCBLAS_FP8_API -DROCBLAS_BETA_FEATURES_API -DROCBLAS_NO_DEPRECATED_WARNINGS) message(STATUS "MIGraphX is using Beta API of rocBLAS for FP8 computations") else() message(STATUS "rocBLAS does not have Fp8 Beta API") endif() target_link_libraries(migraphx_gpu PUBLIC roc::rocblas) endif() if(MIGRAPHX_USE_HIPBLASLT) target_link_libraries(migraphx_gpu PUBLIC roc::hipblaslt) endif() if(WIN32) # Temporary workaround on rocMLIR not exporting correctly libraries it depends on. target_link_libraries(migraphx_gpu PRIVATE ntdll) endif() target_link_libraries(migraphx_gpu PUBLIC migraphx) if(NOT MIGRAPHX_USE_MIOPEN AND NOT MIGRAPHX_USE_ROCBLAS) target_link_libraries(migraphx_gpu PUBLIC migraphx_device) else() target_link_libraries(migraphx_gpu PRIVATE migraphx_device) endif() target_link_libraries(migraphx_gpu PRIVATE migraphx_kernels) if(MIGRAPHX_USE_COMPOSABLEKERNEL) target_link_libraries(migraphx_gpu PRIVATE composable_kernel::jit_library) target_compile_definitions(migraphx_gpu PRIVATE MIGRAPHX_USE_COMPOSABLEKERNEL=1) endif() add_subdirectory(driver) add_subdirectory(hiprtc) rocm_install_targets( PRIVATE TARGETS migraphx_gpu migraphx_device compile_for_gpu INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include ) ROCm-AMDMIGraphX-46524e8/src/targets/gpu/abs.cpp000066400000000000000000000045041510465702400210470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #if MIGRAPHX_USE_MIOPEN shape miopen_abs::compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2).packed(); return inputs.at(0); } argument miopen_abs::compute(context& ctx, const shape& output_shape, const std::vector& args) const { float alpha = 1; float beta = 0; auto x_desc = make_tensor(args[0].get_shape()); auto y_desc = make_tensor(output_shape); miopenActivationForward(ctx.get_stream().get_miopen(), ad.get(), &alpha, x_desc.get(), args[0].implicit(), &beta, y_desc.get(), args[1].implicit()); return args[1]; } void miopen_abs::finalize(context&, const shape&, const std::vector&) { ad = make_abs(); } #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/allocation_model.cpp000066400000000000000000000036171510465702400236130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { std::string gpu_allocation_model::name() const { return "hip::allocate"; } operation gpu_allocation_model::allocate(const shape& s) const { return make_op(name(), {{"shape", to_value(s)}}); } operation gpu_allocation_model::preallocate(const shape& s, const std::string& id) const { return make_op("hip::hip_allocate_memory", {{"shape", to_value(s)}, {"id", id}}); } std::string gpu_allocation_model::copy() const { return "hip::copy"; } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/analyze_streams.cpp000066400000000000000000000060561510465702400235070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct hip_stream_model { std::size_t max_stream = 0; std::unordered_map ins2stream{}; std::size_t get_nstream() const { return max_stream + 1; } std::size_t get_stream(migraphx::instruction_ref ins) const { return ins2stream.at(ins); } std::size_t get_event_id(migraphx::instruction_ref ins) const { auto v = ins->get_operator().to_value(); return v["event"].to(); } bool has_stream(migraphx::instruction_ref ins) const { return ins2stream.count(ins) > 0; } bool is_record(migraphx::instruction_ref ins) const { return ins->name() == "gpu::record_event"; } bool is_wait(migraphx::instruction_ref ins) const { return ins->name() == "gpu::wait_event"; } }; static stream_model make_stream_model(const module& m) { hip_stream_model hsm; std::size_t stream = 0; for(auto ins : iterator_for(m)) { if(ins->name() == "gpu::set_stream") { auto v = ins->get_operator().to_value(); stream = v["stream"].to(); hsm.max_stream = std::max(stream, hsm.max_stream); } if(ins->get_operator().is_context_free()) continue; if(contains({"hip::hip_allocate_memory", "hip::hip_copy_literal", "@param"}, ins->name())) continue; hsm.ins2stream[ins] = stream; } return hsm; } std::vector analyze_streams(const module& m) { return migraphx::analyze_streams(m, make_stream_model(m)); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/argmax.cpp000066400000000000000000000037561510465702400215710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_argmax::compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2); return op.normalize_compute_shape({inputs.at(0)}); } argument hip_argmax::compute(context& ctx, const shape&, const std::vector& args) const { auto n_dim = args.front().get_shape().lens().size(); int64_t tuned_axis = tune_axis(n_dim, op.axis, op.name()); device::argmax( ctx.get_stream().get(), args.back(), args.front(), tuned_axis, op.select_last_index); return args.back(); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/argmin.cpp000066400000000000000000000037561510465702400215670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_argmin::compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2); return op.normalize_compute_shape({inputs.at(0)}); } argument hip_argmin::compute(context& ctx, const shape&, const std::vector& args) const { auto n_dim = args.front().get_shape().lens().size(); int64_t tuned_axis = tune_axis(n_dim, op.axis, op.name()); device::argmin( ctx.get_stream().get(), args.back(), args.front(), tuned_axis, op.select_last_index); return args.back(); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/code_object_op.cpp000066400000000000000000000066541510465702400232500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_REGISTER_OP(code_object_op); shape code_object_op::compute_shape(std::vector inputs) const { std::transform(inputs.begin(), inputs.end(), inputs.begin(), [](const shape& s) { return s.normalize_standard(); }); auto einputs = expected_inputs; std::transform(einputs.begin(), einputs.end(), einputs.begin(), [](const shape& s) { return s.normalize_standard(); }); if(not migraphx::equal(flatten(einputs), flatten(inputs), &shape::is_compatible)) MIGRAPHX_THROW("Input shapes have changed: [" + to_string_range(einputs) + "] -> [" + to_string_range(inputs) + "]"); return output; } static bool needs_flatten(const std::vector& args) { return std::any_of(args.begin(), args.end(), [&](const argument& arg) { return arg.get_shape().type() == shape::tuple_type; }); } template static void visit_flatten_args(const std::vector& args, F f) { if(needs_flatten(args)) f(flatten(args)); else f(args); } argument code_object_op::compute(context& ctx, const shape&, const std::vector& args) const { #if MIGRAPHX_HAS_PMR std::array storage; std::pmr::monotonic_buffer_resource resource{storage.data(), storage.size()}; pmr::vector kargs(&resource); #else pmr::vector kargs; #endif visit_flatten_args(args, [&](const auto& fargs) { kargs.reserve(fargs.size()); std::transform(fargs.begin(), fargs.end(), std::back_inserter(kargs), [](const argument& a) { return a.data(); }); }); auto [start, stop] = ctx.get_perf_events(); k.launch(ctx.get_stream().get(), global, local, kargs, start, stop); return args[get_output_arg(args.size())]; } void code_object_op::finalize(context&, const shape&, const std::vector&) { assert(not code_object.empty()); k = kernel(code_object, symbol_name); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/compile_gen.cpp000066400000000000000000000512721510465702400225670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace gen { static std::vector vector_sizes(const std::vector& inputs) { // If all inputs are half then only use half2 if(std::all_of(inputs.begin(), inputs.end(), [](const auto& s) { return s.type() == shape::half_type; })) return {2}; return {4, 2}; } vectorize vectorize::elements(std::size_t axis, const std::vector& inputs, const std::vector& sizes) { // disable vectorization for fp8 types if(std::any_of(inputs.begin(), inputs.end(), [&](auto ishape) { return contains(fp8_types{}.get(), ishape.type()); })) return {1, axis}; if(std::all_of( inputs.begin(), inputs.end(), [&](const auto& s) { return s.lens()[axis] == 1; })) return {1, axis}; std::vector max_vec_size; std::transform(inputs.begin(), inputs.end(), std::back_inserter(max_vec_size), [&](const auto& input) -> std::size_t { auto stride = input.strides()[axis]; auto len = input.lens()[axis]; if(not contains({0, 1}, stride)) return 1; if(len == 1 and input.elements() > sizes.front()) return sizes.front(); auto it = std::find_if(sizes.begin(), sizes.end(), [&](auto vsize) { // The len is divisible by the size and all the strides are divisible by // the size return (len % vsize) == 0 and std::all_of( input.strides().begin(), input.strides().end(), [&](auto i) { return contains({0, 1}, i) or i % vsize == 0; }); }); if(it != sizes.end()) return *it; return 1; }); return {*std::min_element(max_vec_size.begin(), max_vec_size.end()), axis}; } vectorize vectorize::elements(context& ctx, std::size_t axis, const std::vector& inputs) { // disable vectorization for fp8 types if(std::any_of(inputs.begin(), inputs.end(), [&](auto ishape) { return contains(fp8_types{}.get(), ishape.type()); })) return {1, axis}; if(inputs.empty()) return {1, axis}; std::size_t n = std::max_element(inputs.begin(), inputs.end(), by(std::less<>{}, [](const auto& s) { return s.elements(); })) ->elements(); std::size_t max_global = ctx.get_current_device().get_cu_count() * ctx.get_current_device().get_max_workitems_per_cu(); std::size_t over = n / max_global; bool broadcasted = std::any_of(inputs.begin(), inputs.end(), [](const auto& s) { return s.broadcasted(); }); std::vector sizes; if(broadcasted and over > 8) sizes.push_back(8); if(over > 4) sizes.push_back(4); sizes.push_back(2); return elements(axis, inputs, sizes); } vectorize vectorize::elements(std::size_t axis, const std::vector& inputs) { return elements(axis, inputs, vector_sizes(inputs)); } std::string vectorize::str() const { return "vectorize<" + to_string(size) + ", " + to_string(axis) + ">()"; } preload preload::broadcasts(std::size_t axis, const std::vector& inputs) { const std::size_t max_lds_bytes = 4096; std::vector result(inputs.size()); std::vector preloaded; auto idxs = range(inputs.size()); std::copy_if(idxs.begin(), idxs.end(), std::back_inserter(preloaded), [&](auto i) { return inputs[i].strides()[axis] == 0; }); std::sort(preloaded.begin(), preloaded.end(), by(std::less<>{}, [&](auto i) { return inputs[i].bytes(); })); std::size_t bytes = 0; for(auto i : preloaded) { const auto& input = inputs[i]; bytes += input.bytes(); if(bytes > max_lds_bytes) break; result[i] = true; } return {result}; } std::string preload::str() const { std::vector bool_strs; std::transform(args.begin(), std::prev(args.end()), std::back_inserter(bool_strs), [](bool b) { if(b) return "true"; return "false"; }); return "auto_preload(idx)"; } bool preload::is_preloading() const { return std::accumulate(args.begin(), args.end(), false, std::logical_or<>{}); } static std::size_t integer_divide_ceil(std::size_t x, std::size_t y) { return (x + y - std::size_t{1}) / y; } static std::size_t compute_tile_factor(std::size_t r, std::size_t max_size = 64) { std::size_t n = 1; auto factors = make_array(2, 3, 5, 7, 11); while(n < max_size) { // NOLINTNEXTLINE(readability-qualified-auto) auto it = std::find_if(factors.begin(), factors.end(), [&](auto d) { return r % d == 0; }); if(it == factors.end()) break; r /= *it; n *= *it; } return n; } tile tile::elements(const std::vector& inputs, std::size_t noutputs) { tile result; auto ndim = inputs.front().ndim(); std::vector faxes; std::transform( inputs.begin(), inputs.end(), std::back_inserter(faxes), MIGRAPHX_LIFT(find_fast_axis)); result.axis = std::accumulate(faxes.begin(), faxes.end(), ndim, MIGRAPHX_LIFT(std::min)); if(result.axis >= (ndim - 1)) return {}; auto select = [&](auto m) { return [&, m](std::size_t faxis, shape input) { if(input.broadcasted()) return none; if(faxis < (ndim - 1)) return m; return none; }; }; std::transform(faxes.begin(), faxes.end() - noutputs, inputs.begin(), std::back_inserter(result.args), select(load)); std::transform(faxes.end() - noutputs, faxes.end(), inputs.end() - noutputs, std::back_inserter(result.args), select(store)); auto nargs = std::count_if( result.args.begin(), result.args.end(), [](auto m) { return m != mode::none; }); // TODO: Handle tiling more than one arguments if(nargs != 1) return {}; const auto& s = inputs.front(); auto dim1 = compute_tile_factor(s.lens()[result.axis]); auto dim2 = compute_tile_factor(s.lens().back(), 4096 / dim1); if(dim1 == 1 or dim2 == 1) return {}; result.inner = s.lens(); std::fill(result.inner.begin(), result.inner.end(), 1); result.inner[result.axis] = dim1; result.inner.back() = dim2; result.outer = s.lens(); result.outer[result.axis] /= dim1; result.outer.back() /= dim2; auto tile_size = dim1 * dim2; result.ntiles = s.elements() / tile_size; // equivalent to dim1 * (dim2 + 1) to avoid bank conflicts auto tile_bytes = (tile_size + dim1) * s.type_size(); if(tile_bytes > 65536) return {}; result.block_size = std::min(256, integer_divide_ceil(tile_size / 4, 64) * 64); return result; } std::string tile::str() const { if(args.empty()) return "transform_args()"; std::vector strs; std::transform(args.begin(), args.end(), std::back_inserter(strs), [](mode m) { switch(m) { case load: return "tile::load"; case store: return "tile::store"; case none: return "tile::none"; } MIGRAPHX_THROW("Invalid mode"); }); const std::string auto_tile = "auto_tile<${modes}>(${inner}, ${outer})"; return interpolate_string(auto_tile, {{"modes", join_strings(strs, ", ")}, {"inner", generate_index_ints(inner)}, {"outer", generate_index_ints(outer)}}); } std::size_t find_fast_axis(const shape& input) { if(input.scalar()) return input.ndim() - 1; if(input.broadcasted()) { auto stride_it = std::min_element( input.strides().begin(), input.strides().end(), by(std::less<>{}, [](std::size_t i) { if(i == 0) return std::numeric_limits::max(); return i; })); return stride_it - input.strides().begin(); } auto permutation = invert_permutation(find_permutation(input)); auto it = std::max_element(permutation.begin(), permutation.end()); return it - permutation.begin(); } std::size_t find_fast_axis(const std::vector& inputs) { auto permutation = invert_permutation(find_permutation(inputs)); auto it = std::max_element(permutation.begin(), permutation.end()); return it - permutation.begin(); } std::string make_transformer_args(std::vector transformers) { return join_strings(std::move(transformers), ", "); } static void generate_pointwise(cpp_generator& gg, const module& pm, const std::string& name, bool always_return_tuple = false) { module m = pm; run_passes(m, {rewrite_quantization{}, optimize_module{}}); m.sort(); cpp_generator g; g.always_return_tuple(always_return_tuple); g.fmap([](const std::string& fname) { return "migraphx::" + fname; }); g.add_point_op("where", "${function:where}(${0}, ${1}, ${2})"); g.add_point_op("prelu", "${function:where}(${0} < 0, ${0} * ${1}, ${0})"); g.add_point_op("sign", "${function:where}(${0} > 0, 1, ${function:where}(${0} < 0, -1, 0))"); g.add_point_op("equal", "migraphx::abs(${0} == ${1})"); g.add_point_op("less", "migraphx::abs(${0} < ${1})"); g.add_point_op("greater", "migraphx::abs(${0} > ${1})"); g.add_point_op("not", "migraphx::abs(not ${0})"); // Add explict conversions g.fresult( [](const shape& s) { return "migraphx::convert<" + shape::cpp_type(s.type()) + ">"; }); gg.create_function(g.generate_module(m) .set_attributes({"__device__", "__attribute__((const))"}) .set_generic_types(m) .set_name(name)); } std::string generate_pointwise(const module& pm, const std::string& name, bool always_return_tuple) { cpp_generator g; generate_pointwise(g, pm, name, always_return_tuple); return g.str(); } std::string reduce_op::str() const { return write + "(r.reduce(" + reduction + ", " + init + ", " + read + ")(" + join_strings(inputs, ", ") + "))"; } void reduce_op::set(const std::string& name, const shape& input, const shape& output) { assert(input.type() != shape::tuple_type); assert(output.type() != shape::tuple_type); if(name == "reduce_sum") { reduction = "op::sum{}"; } else if(name == "reduce_mean") { auto reduce_elements = input.elements() / output.elements(); auto reduce_type = input.type(); reduction = "op::sum{}"; std::string mean = "op::mean<" + std::to_string(reduce_elements) + ">{}"; // Use float accumulator when reduction size is too large for half if(reduce_type == shape::half_type and reduce_elements > 16384) read = "compose(" + mean + ", op::convert_to{})"; else if(contains({shape::float_type, shape::half_type, shape::double_type}, reduce_type)) read = mean; else write = mean; } else if(name == "reduce_max") { reduction = "op::max{}"; init = "lowest{}"; } else if(name == "reduce_min") { reduction = "op::min{}"; init = "highest{}"; } else if(name == "reduce_prod") { reduction = "op::product{}"; init = "1"; } else if(name == "reduce_any") { reduction = "op::logical_or{}"; init = "bool{false}"; } else if(name == "reduce_all") { reduction = "op::logical_and{}"; init = "bool{true}"; } else { MIGRAPHX_THROW("Unsupported reduce"); } } void reduce_op::set(instruction_ref ins, const operation& op) { if(op.name() == "gpu::parallel_reduce") { auto rop = from_value(op.to_value().at("op")); auto input = ins->inputs().front()->get_shape(); auto output = ins->get_shape().sub_shapes().front(); set(rop.name(), input, output); read = "compose(array_apply(" + read + "), MIGRAPHX_LIFT(make_array))"; } else { set(op.name(), ins->inputs().front()->get_shape(), ins->get_shape()); } } std::string reduce_op::generate(instruction_ref ins, const std::vector& x) { reduce_op r{x}; r.set(ins, ins->get_operator()); return r.str(); } static bool use_lazy_inner(instruction_ref ins) { if(ins->outputs().size() != 1) return false; // When the inputs are broadcasted, it means the lambda will capture SGPRs // when doing block/wave reduction. This can cause register spilling in // the compiler when the lambda is evaluated at a later time although it // shouldn't. Instead, use `inner` to workaround this issue in the // compiler. if(std::any_of(ins->inputs().begin(), ins->inputs().end(), [](instruction_ref input) { return input->get_shape().broadcasted(); })) return false; auto output = ins->outputs().front(); return contains(output->name(), "reduce") or output->name() == "@return"; } static void preload_params(module& m) { for(auto ins : iterator_for(m)) { if(ins->name() != "@param") continue; if(ins->outputs().size() <= 1) continue; auto id = m.insert_instruction(std::next(ins), make_op("identity"), ins); m.replace_instruction(ins, id); } } std::string generate_reduce(module m, const std::string& name) { preload_params(m); run_passes(m, {optimize_module{}, prepare_reduce{}, optimize_module{}}); m.sort(); cpp_generator g; g.always_return_tuple(); auto param_shapes = m.get_parameter_shapes(); auto max_shape = std::max_element(param_shapes.begin(), param_shapes.end(), by(std::less<>{}, [](const auto& p) { return p.second.elements(); })); auto ilens = max_shape->second.lens(); std::size_t i = 0; auto f = g.generate_module(m, [&](instruction_ref ins, const auto& names) { if(contains(ins->name(), "reduce")) { return reduce_op::generate(ins, cpp_generator::to_args(ins->inputs(), names)); } if(ins->name() == "pointwise") { auto pointwise_name = "pointwise" + std::to_string(i); i++; generate_pointwise(g, *ins->module_inputs().front(), pointwise_name); std::vector tensors; std::copy_if(ins->inputs().begin(), ins->inputs().end(), std::back_inserter(tensors), [&](auto input) { return input->get_shape().lens() == ilens and not input->get_shape().broadcasted(); }); auto inner_names = names; for(auto input : ins->inputs()) { if(input->name() != "@param") continue; if(contains(tensors, input)) continue; inner_names[input] += "[out_idx]"; } for(auto input : tensors) inner_names[input] += "_lambda_param"; auto call_function = pointwise_name + "(" + join_strings(cpp_generator::to_args(ins->inputs(), inner_names), ", ") + ")"; if(tensors.empty()) return call_function; const std::string inner_template = "r.${inner}([=](${params}) { return ${call}; })(${args})"; std::string inner_name = use_lazy_inner(ins) ? "lazy_inner" : "inner"; auto args = cpp_generator::to_args(tensors, names); auto params = cpp_generator::to_args(tensors, inner_names); std::transform(params.begin(), params.end(), params.begin(), [](const auto& s) { return "auto " + s; }); return interpolate_string(inner_template, {{"inner", inner_name}, {"params", join_strings(params, ", ")}, {"args", join_strings(args, ", ")}, {"call", call_function}}); } if(ins->name() == "multibroadcast") { return names.at(ins->inputs().front()); } if(ins->name() == "get_tuple_elem") { const auto& x = names.at(ins->inputs().front()); auto index = ins->get_operator().to_value()["index"].to(); return interpolate_string("${x}[${index}]", {{"x", x}, {"index", std::to_string(index)}}); } if(ins->name() == "identity") { const auto& x = names.at(ins->inputs().front()); return "r.inner(op::id{})(" + x + ")"; } MIGRAPHX_THROW("Unknown operator: " + ins->name()); }); f.set_attributes({"__device__", "__attribute__((const))"}).set_generic_types(m).set_name(name); f.add_generic_param("r"); f.add_generic_param("out_idx"); f.unused_param("out_idx"); g.create_function(f); return g.str(); } static std::vector get_op_names(const module& m) { std::vector result; for(auto& ins : m) { if(starts_with(ins.name(), "@")) continue; if(contains({"multibroadcast", "contiguous", "identity"}, ins.name())) continue; if(ins.name() == "pointwise") { auto names = get_op_names(*ins.module_inputs().front()); result.insert(result.end(), names.begin(), names.end()); } else { result.push_back(ins.name()); } } return result; } std::string generate_name_from_ops(const module& m, const std::string& postname) { auto op_names = get_op_names(m); if(not postname.empty()) op_names.push_back(postname); if(op_names.empty()) return "noop"; return join_strings(op_names, "_"); } } // namespace gen } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/compile_hip.cpp000066400000000000000000000313511510465702400225720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #ifdef MIGRAPHX_USE_HIPRTC #include #include #include #include #include #include #include #include #include #else #include #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_GPU_DEBUG); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_GPU_DEBUG_SYM); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_GPU_OPTIMIZE); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_GPU_DUMP_ASM); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_GPU_DUMP_SRC); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_GPU_HIP_FLAGS); #ifdef MIGRAPHX_USE_HIPRTC namespace { std::string hiprtc_error(hiprtcResult err, const std::string& msg) { return "hiprtc: " + (hiprtcGetErrorString(err) + (": " + msg)); } void hiprtc_check_error(hiprtcResult err, const std::string& msg, const std::string& ctx) { if(err != HIPRTC_SUCCESS) throw make_exception(ctx, hiprtc_error(err, msg)); } // NOLINTNEXTLINE #define MIGRAPHX_HIPRTC(...) \ hiprtc_check_error(__VA_ARGS__, #__VA_ARGS__, MIGRAPHX_MAKE_SOURCE_CTX()) #define MIGRAPHX_HIPRTC_THROW(error, msg) MIGRAPHX_THROW(hiprtc_error(error, msg)) // Workaround hiprtc's broken API void hiprtc_program_destroy(hiprtcProgram prog) { hiprtcDestroyProgram(&prog); } using hiprtc_program_ptr = MIGRAPHX_MANAGE_PTR(hiprtcProgram, hiprtc_program_destroy); template hiprtc_program_ptr hiprtc_program_create(Ts... xs) { hiprtcProgram prog = nullptr; auto result = hiprtcCreateProgram(&prog, xs...); hiprtc_program_ptr p{prog}; if(result != HIPRTC_SUCCESS) MIGRAPHX_HIPRTC_THROW(result, "Create program failed."); return p; } } // namespace struct hiprtc_program { struct string_array { std::deque strings{}; std::vector c_strs{}; string_array() {} string_array(const string_array&) = delete; std::size_t size() const { return strings.size(); } const char** data() { return c_strs.data(); } void push_back(std::string s) { strings.push_back(std::move(s)); c_strs.push_back(strings.back().c_str()); } }; hiprtc_program_ptr prog = nullptr; string_array headers{}; string_array include_names{}; std::string cpp_src = ""; std::string cpp_name = ""; hiprtc_program(const std::string& src, const std::string& name = "main.cpp") : cpp_src(src), cpp_name(name) { create_program(); } hiprtc_program(std::vector srcs) { for(auto&& src : srcs) { if(ends_with(src.path, ".cpp")) { cpp_src = std::move(src.content); cpp_name = std::move(src.path); } else { headers.push_back(std::move(src.content)); include_names.push_back(std::move(src.path)); } } create_program(); } void create_program() { assert(not cpp_src.empty()); assert(not cpp_name.empty()); assert(headers.size() == include_names.size()); prog = hiprtc_program_create(cpp_src.c_str(), cpp_name.c_str(), headers.size(), headers.data(), include_names.data()); } void compile(const std::vector& options, bool quiet = false) const { if(enabled(MIGRAPHX_TRACE_HIPRTC{})) std::cout << "hiprtc " << join_strings(options, " ") << " " << cpp_name << std::endl; std::vector c_options; std::transform(options.begin(), options.end(), std::back_inserter(c_options), [](const std::string& s) { return s.c_str(); }); auto result = hiprtcCompileProgram(prog.get(), c_options.size(), c_options.data()); auto prog_log = log(); if(not prog_log.empty() and not quiet) { std::cerr << prog_log << std::endl; } if(result != HIPRTC_SUCCESS) MIGRAPHX_HIPRTC_THROW(result, "Compilation failed."); } std::string log() const { std::size_t n = 0; MIGRAPHX_HIPRTC(hiprtcGetProgramLogSize(prog.get(), &n)); if(n == 0) return {}; std::string buffer(n, '\0'); MIGRAPHX_HIPRTC(hiprtcGetProgramLog(prog.get(), buffer.data())); assert(buffer.back() != 0); return buffer; } std::vector get_code_obj() const { std::size_t n = 0; MIGRAPHX_HIPRTC(hiprtcGetCodeSize(prog.get(), &n)); std::vector buffer(n); MIGRAPHX_HIPRTC(hiprtcGetCode(prog.get(), buffer.data())); return buffer; } }; std::vector> compile_hip_src_with_hiprtc(std::vector srcs, const std::vector& params, const std::string& arch, bool quiet) { hiprtc_program prog(std::move(srcs)); auto options = params; options.push_back("-DMIGRAPHX_USE_HIPRTC=1"); if(enabled(MIGRAPHX_GPU_DEBUG{})) options.push_back("-DMIGRAPHX_DEBUG"); if(std::none_of(options.begin(), options.end(), [](const std::string& s) { return starts_with(s, "--std=") or starts_with(s, "-std="); })) options.push_back("-std=c++17"); options.push_back("-fno-gpu-rdc"); options.push_back("-O" + string_value_of(MIGRAPHX_GPU_OPTIMIZE{}, "3")); options.push_back("-Wno-cuda-compat"); options.push_back("--offload-arch=" + arch); std::vector extra_flags = split_string(string_value_of(MIGRAPHX_GPU_HIP_FLAGS{}, ""), ' '); options.insert(options.end(), extra_flags.begin(), extra_flags.end()); prog.compile(options, quiet); return {prog.get_code_obj()}; } std::vector> compile_hip_src(const std::vector& srcs, const std::vector& params, const std::string& arch, bool quiet) { std::vector hsrcs{srcs.begin(), srcs.end()}; if(enabled(MIGRAPHX_GPU_DUMP_SRC{})) { for(const auto& src : srcs) { if(src.path.extension() != ".cpp") continue; std::cout << std::string(src.content) << std::endl; } } auto fname = make_executable_filename("migraphx-hiprtc-driver"); auto p = dynamic_loader::path(&compile_hip_src_with_hiprtc); auto driver = p.parent_path() / fname; bool found = fs::exists(driver); if(not found) { driver = p.parent_path().parent_path() / "bin" / fname; found = fs::exists(driver); } if(found) { value v; v["srcs"] = to_value(hsrcs); v["params"] = to_value(params); v["arch"] = to_value(arch); v["quiet"] = quiet; tmp_dir td{}; auto out = td.path / "output"; process(driver, {quote_string(out.string())}).write([&](auto writer) { to_msgpack(v, std::move(writer)); }); if(fs::exists(out)) return {read_buffer(out)}; MIGRAPHX_THROW("hiprtc compilation failed!"); } return compile_hip_src_with_hiprtc(std::move(hsrcs), params, arch, quiet); } #else // MIGRAPHX_USE_HIPRTC std::vector> compile_hip_src_with_hiprtc(std::vector, // NOLINT const std::vector&, // NOLINT const std::string&, bool) { MIGRAPHX_THROW("Not using hiprtc"); } static bool is_hip_clang_compiler() { static const auto result = fs::path{MIGRAPHX_HIP_COMPILER}.stem() == "clang++"; return result; } #ifdef MIGRAPHX_HIP_COMPILER_LAUNCHER static bool has_compiler_launcher() { static const auto result = fs::exists(MIGRAPHX_HIP_COMPILER_LAUNCHER); return result; } #endif static src_compiler assemble(src_compiler compiler) { compiler.out_ext = ".S"; std::replace(compiler.flags.begin(), compiler.flags.end(), "-c", "-S"); return compiler; } std::vector> compile_hip_src(const std::vector& srcs, const std::vector& params, const std::string& arch, bool) { assert(not srcs.empty()); if(not is_hip_clang_compiler()) MIGRAPHX_THROW("Unknown hip compiler: " MIGRAPHX_HIP_COMPILER); src_compiler compiler; compiler.flags = params; compiler.compiler = MIGRAPHX_HIP_COMPILER; #ifdef MIGRAPHX_HIP_COMPILER_LAUNCHER if(has_compiler_launcher()) compiler.launcher = MIGRAPHX_HIP_COMPILER_LAUNCHER; #endif if(std::none_of(params.begin(), params.end(), [](const std::string& s) { return starts_with(s, "--std=") or starts_with(s, "-std="); })) compiler.flags.emplace_back("--std=c++17"); compiler.flags.emplace_back(" -fno-gpu-rdc"); if(enabled(MIGRAPHX_GPU_DEBUG_SYM{})) compiler.flags.emplace_back("-g"); compiler.flags.emplace_back("-c"); compiler.flags.emplace_back("--offload-arch=" + arch); compiler.flags.emplace_back("--cuda-device-only"); compiler.flags.emplace_back("-O" + string_value_of(MIGRAPHX_GPU_OPTIMIZE{}, "3") + " "); if(enabled(MIGRAPHX_GPU_DEBUG{})) compiler.flags.emplace_back("-DMIGRAPHX_DEBUG"); compiler.flags.emplace_back("-Wno-unused-command-line-argument"); compiler.flags.emplace_back("-Wno-cuda-compat"); compiler.flags.emplace_back(MIGRAPHX_HIP_COMPILER_FLAGS); std::vector extra_flags = split_string(string_value_of(MIGRAPHX_GPU_HIP_FLAGS{}, ""), ' '); compiler.flags.insert(compiler.flags.end(), extra_flags.begin(), extra_flags.end()); if(enabled(MIGRAPHX_GPU_DUMP_SRC{})) { for(const auto& src : srcs) { if(src.path.extension() != ".cpp") continue; std::cout << std::string(src.content) << std::endl; } } if(enabled(MIGRAPHX_GPU_DUMP_ASM{})) { std::cout << assemble(compiler).compile(srcs).data() << std::endl; } return {compiler.compile(srcs)}; } #endif // MIGRAPHX_USE_HIPRTC bool hip_has_flags(const std::vector& flags) { std::string src = " "; src_file input{"main.cpp", src}; std::vector srcs = {input}; try { std::string arch = "gfx900"; compile_hip_src(srcs, flags, arch, true); return true; } catch(...) { return false; } } std::string enum_params(std::size_t count, std::string param) { std::vector items(count); transform(range(count), items.begin(), [&](auto i) { return param + std::to_string(i); }); return join_strings(items, ","); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/compile_hip_code_object.cpp000066400000000000000000000173721510465702400251210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { std::string generate_make_shape(const shape& s) { return "make_shape(" + generate_index_ints(s.lens()) + ", " + generate_index_ints(s.strides()) + ")"; } static const char* const make_tensor_template = R"__migraphx__( template<> struct make_tensor<${n}> { static __device__ auto apply(void* __restrict__ p) { return make_tensor_view(reinterpret_cast<${type}* __restrict__>(p), make_shape(${lens}, ${strides})); } }; )__migraphx__"; static std::string generate_make_tensor(std::size_t n, const shape& s) { return interpolate_string(make_tensor_template, {{"n", std::to_string(n)}, {"type", shape::cpp_type(s.type())}, {"lens", generate_index_ints(s.lens())}, {"strides", generate_index_ints(s.strides())}}); } static std::string generate_args_hpp(const std::vector& inputs) { std::string inner; for(std::size_t i = 0; i < inputs.size(); i++) { inner += generate_make_tensor(i, inputs[i]); } const std::string args_hpp = R"__migraphx__( #ifndef MIGRAPHX_GUARD_AUTO_ARGS_HPP #define MIGRAPHX_GUARD_AUTO_ARGS_HPP #include #include #include namespace migraphx { __content__ } // namespace migraphx #endif )__migraphx__"; return replace_string(args_hpp, "__content__", inner); } static std::vector get_compiler_warnings() { std::vector warnings = { "-Weverything", "-Wno-c++98-compat", "-Wno-c++98-compat-pedantic", "-Wno-conversion", "-Wno-double-promotion", "-Wno-exit-time-destructors", "-Wno-extra-semi", "-Wno-extra-semi-stmt", "-Wno-float-conversion", "-Wno-gnu-anonymous-struct", "-Wno-gnu-zero-variadic-macro-arguments", "-Wno-missing-prototypes", "-Wno-nested-anon-types", "-Wno-padded", "-Wno-shorten-64-to-32", "-Wno-sign-conversion", "-Wno-sign-compare", "-Wno-unused-command-line-argument", "-Wno-weak-vtables", "-Wno-c99-extensions", }; if(hip_has_flags({"-Werror", "-Wunsafe-buffer-usage"})) warnings.push_back("-Wno-unsafe-buffer-usage"); if(hip_has_flags({"-Werror", "-Wnrvo"})) warnings.push_back("-Wno-nrvo"); return warnings; } const static std::vector& compiler_warnings() { static std::vector warnings = get_compiler_warnings(); return warnings; } void hip_compile_options::set_launch_params( const value& v, const std::function& compute_global, std::size_t default_local) { local = v.get("local", default_local); if(v.contains("global")) global = v.at("global").to(); else global = compute_global(local); } static bool hip_accept_non_uniform_wg() { static bool non_uniform_wg = hip_has_flags({"-fno-offload-uniform-block"}); return non_uniform_wg; } std::function compute_global_for(const context& ctx, std::size_t n, std::size_t over) { assert(over > 0); std::size_t max_global = ctx.get_current_device().get_cu_count() * ctx.get_current_device().get_max_workitems_per_cu(); return [n, over, max_global](std::size_t local) { std::size_t num_elements = n; if(not hip_accept_non_uniform_wg()) { num_elements = (1 + (n - 1) / local) * local; } std::size_t groups = 1 + (num_elements - 1) / local; std::size_t max_blocks = max_global / local; std::size_t nglobal = std::min(max_blocks * over, groups) * local; return std::min(nglobal, num_elements); }; } std::size_t compute_block_size(const context& ctx, std::size_t n, std::size_t max_block_size) { const std::size_t min_block_size = ctx.get_current_device().get_wavefront_size(); auto block_size = (((n - 1) / min_block_size + 1)) * min_block_size; return std::min(std::max(min_block_size, block_size), max_block_size); } operation compile_hip_code_object(context& ctx, const std::string& content, hip_compile_options options) { assert(options.global > 0); assert(options.local > 0); assert(not options.inputs.empty()); assert(options.inputs.size() == options.virtual_inputs.size() or options.virtual_inputs.empty()); std::vector srcs = options.additional_src_files; static auto kernels{::migraphx_kernels()}; std::transform( kernels.begin(), kernels.end(), std::back_inserter(srcs), [](const std::pair& elem) { return src_file{elem}; }); srcs.emplace_back("main.cpp", content); auto args_hpp = generate_args_hpp(options.virtual_inputs.empty() ? options.inputs : options.virtual_inputs); srcs.emplace_back("args.hpp", args_hpp); if(options.global % options.local != 0 and hip_accept_non_uniform_wg()) options.emplace_param("-fno-offload-uniform-block"); else assert(options.global % options.local == 0); options.emplace_param("-DMIGRAPHX_NGLOBAL=" + std::to_string(options.global)); options.emplace_param("-DMIGRAPHX_NLOCAL=" + std::to_string(options.local)); options.emplace_param("-DMIGRAPHX_WAVEFRONTSIZE=" + std::to_string(ctx.get_current_device().get_wavefront_size())); const auto& warnings = compiler_warnings(); options.params.insert(options.params.end(), warnings.begin(), warnings.end()); options.emplace_param("-ftemplate-backtrace-limit=0"); options.emplace_param("-Werror"); auto cos = compile_hip_src(srcs, options.params, get_device_name()); if(cos.size() != 1) MIGRAPHX_THROW("No code object"); return code_object_op{value::binary{cos.front()}, options.kernel_name, options.global, options.local, options.inputs, options.output, options.output_arg}; } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/compile_hipblaslt.cpp000066400000000000000000000056121510465702400237750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #if MIGRAPHX_USE_HIPBLASLT #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { static size_t compile(migraphx::context& ctx, operation& op, instruction_ref ins) { auto v = op.compile(ctx, ins->get_shape(), to_shapes(ins->inputs())); return v.get("workspace", 0); } void compile_hipblaslt::apply(module& m) const { assert(ctx); for(auto ins : iterator_for(m)) { if(ins->name() != "gpu::hipblaslt_op") continue; auto op = any_cast(ins->get_operator()).op; auto inputs = ins->inputs(); std::size_t ws = hipblaslt_workspace_size; auto alloc = m.insert_instruction( ins, make_op("allocate", {{"shape", to_value(shape{shape::uint8_type, {ws}})}})); inputs.insert(std::prev(inputs.end()), alloc); m.replace_instruction(ins, op, inputs); // Calculate workspace size ws = compile(*ctx, op, ins); auto alloc_after = m.insert_instruction( ins, make_op("allocate", {{"shape", to_value(shape{shape::uint8_type, {ws}})}})); // Replace the workspace size with actual worksapce size needed. auto it = std::find(inputs.begin(), inputs.end(), alloc); if(it != inputs.end()) { *it = alloc_after; // Replace `alloc` with `alloc_after` } m.replace_instruction(ins, op, inputs); } } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_USE_HIPBLASLT ROCm-AMDMIGraphX-46524e8/src/targets/gpu/compile_miopen.cpp000066400000000000000000000056541510465702400233100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct miopen_op { operation op = op::identity{}; template static auto reflect(Self& self, F f) { return pack(f(self.op, "op")); } std::string name() const { return "gpu::miopen_op"; } shape compute_shape(std::vector inputs) const { inputs.push_back(inputs.back()); return op.compute_shape(inputs); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; MIGRAPHX_REGISTER_OP(miopen_op); std::size_t compile_miopen::compile(operation& op, instruction_ref ins) const { auto v = op.compile(*ctx, ins->get_shape(), to_shapes(ins->inputs())); return v.get("workspace", 0); } void compile_miopen::apply(module& m) const { assert(ctx); for(auto ins : iterator_for(m)) { if(ins->name() != "gpu::miopen_op") continue; auto op = any_cast(ins->get_operator()).op; std::size_t ws = 0; ws = compile(op, ins); auto inputs = ins->inputs(); auto alloc = m.insert_instruction( ins, make_op("allocate", {{"shape", to_value(shape{shape::int8_type, {ws}})}})); inputs.insert(std::prev(inputs.end()), alloc); m.replace_instruction(ins, op, inputs); } } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/compile_ops.cpp000066400000000000000000000321041510465702400226100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_GPU_COMPILE_PARALLEL); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_BENCHMARKING); struct precompile_op { operation op = op::identity{}; std::size_t additional_args = 1; bool ignore_modules = false; std::optional output_shape = nullopt; template static auto reflect(Self& self, F f) { return pack(f(self.op, "op"), f(self.additional_args, "additional_args"), f(self.ignore_modules, "ignore_modules"), f(self.output_shape, "output_shape")); } std::string name() const { return "gpu::precompile_op"; } shape compute_shape(std::vector inputs, const std::vector& mods) const { // Pop off additional args inputs.resize(inputs.size() - additional_args); if(output_shape.has_value()) return output_shape.value(); if(ignore_modules) return op.compute_shape(inputs); return op.compute_shape(inputs, mods); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; MIGRAPHX_REGISTER_OP(precompile_op); struct compiled_result { compiler_replace replace; instruction_ref ins; friend std::ostream& operator<<(std::ostream& os, const compiled_result& cr) { cr.replace.trace(os, cr.ins); return os; } }; struct compile_plan { context* ctx; operation preop; instruction_ref ins; module_ref mod; optional config = nullopt; std::vector> results = {}; void update_config(bool exhaustive) { config = get_tuning_config(*ctx, ins, preop, exhaustive); } template void insert_compiles(Vector& compiles, const value& solution, std::size_t i) { compiles.emplace_back([=] { try { results[i] = compiled_result{compile(*ctx, ins, preop, solution), ins}; } catch(const std::exception& e) { const auto trace_level = value_of(MIGRAPHX_TRACE_BENCHMARKING{}); if(trace_level > 0) std::cerr << "Exception in " + preop.name() + ": " + e.what() << std::endl; results[i] = nullopt; } catch(...) { results[i] = nullopt; } }); } template void add_compiles(Vector& compiles) { if(config.has_value()) { const auto& problem = config->problem; if(auto sol = ctx->get_problem_cache().get(preop.name(), problem)) { const auto& solution = sol.value(); // No solution yet until benchmarked so skip for now if(solution.is_null()) return; results.resize(1); insert_compiles(compiles, solution, 0); } else { ctx->get_problem_cache().mark(preop.name(), problem); const auto& solutions = config->solutions; if(solutions.empty()) MIGRAPHX_THROW("No solutions provided for " + preop.name() + " with " + problem_string() + "\n\n" + print_modules()); results.resize(solutions.size()); for(auto i : range(solutions.size())) { auto solution = solutions[i]; insert_compiles(compiles, solution, i); } } } else { results.resize(1); insert_compiles(compiles, value{}, 0); } } std::string problem_string() const { if(config) return to_string(config->problem); return ""; } std::string print_modules() const { std::stringstream current_module; for(auto* const m : ins->module_inputs()) { current_module << to_string(*m) << "\n"; } std::stringstream submodules; for(auto* const m : ins->module_inputs()) { for(auto* const sm : m->get_sub_modules()) { submodules << to_string(*sm) << "\n"; } } return config->detailed_problem_info + "\n\nModule:\n" + current_module.str() + (not submodules.str().empty() ? "\n" + submodules.str() : "") + "Input Shapes:\n" + print_input_shapes(); } std::string print_input_shapes() const { std::stringstream input_shapes; for(const auto& i : ins->inputs()) { input_shapes << i->get_shape() << "\n"; } return input_shapes.str(); } const compiled_result& benchmark() const { const auto trace_level = value_of(MIGRAPHX_TRACE_BENCHMARKING{}); if(trace_level > 0 and not results.empty()) { std::cout << "Benchmarking " << preop.name() << ": " << results.size() << " configs" << std::endl; } if(results.empty()) MIGRAPHX_THROW("No valid tuned compilation for " + preop.name() + " with " + problem_string() + "\n\n" + print_modules()); if(results.size() == 1) { if(not results.front().has_value()) MIGRAPHX_THROW("No valid tuned compilation for " + preop.name() + " with " + problem_string() + "\n\n" + print_modules()); return *results.front(); } if(not config) MIGRAPHX_THROW("Multiple kernels without config for " + preop.name()); if(trace_level > 1) std::cout << "Problem: " << config->problem << std::endl; std::vector times; times.reserve(results.size()); std::transform(results.begin(), results.end(), config->solutions.begin(), std::back_inserter(times), [&](const auto& cr, const auto& solution) { if(trace_level > 1) std::cout << "Benchmarking solution: " << solution << std::endl; if(not cr.has_value()) { if(trace_level > 1) std::cout << "No binary" << std::endl; return std::numeric_limits::max(); } if(trace_level > 2) std::cout << *cr << std::endl; /* create a small program with insturction being compiled and call "replace" on that which would insert all the compiled code objects, prefills etc. necessary to run candidate code object */ program bench_prog; auto* bench_mm = bench_prog.get_main_module(); std::vector bench_ins_inputs; std::transform(cr->ins->inputs().begin(), cr->ins->inputs().end(), std::back_inserter(bench_ins_inputs), [&](const auto& arg) { return bench_mm->add_parameter( std::to_string(bench_ins_inputs.size()), arg->get_shape()); }); auto bench_ins = bench_mm->add_instruction( cr->ins->get_operator(), bench_ins_inputs, cr->ins->module_inputs()); cr->replace.replace(*bench_mm, bench_ins); // do dead code elimination run_passes(*bench_mm, {dead_code_elimination{}}); // by default, measure runtime with bundle of 1 benchmark config, // repeat 20 times auto t = time_program(*ctx, bench_prog, cr->replace.fill_map, 1, 20); if(trace_level > 1) std::cout << t << "ms" << std::endl; return t; }); std::this_thread::sleep_for(std::chrono::milliseconds{50}); auto i = std::distance(times.begin(), std::min_element(times.begin(), times.end())); ctx->get_problem_cache().insert(preop.name(), config->problem, config->solutions.at(i)); if(trace_level > 0) { std::cout << "Fastest solution: " << config->solutions.at(i) << std::endl; ctx->get_problem_cache().save(); } if(not results[i].has_value()) MIGRAPHX_THROW("No valid tuned compilation for " + preop.name() + " with " + problem_string() + "\n\n" + print_modules()); auto skipped = std::count_if( results.begin(), results.end(), [](const auto& cr) { return not cr.has_value(); }); if(skipped > 0) std::cout << "Skipped " << skipped << " configs for " << preop.name() << std::endl; return *results[i]; } void replace(module& m) const { const auto& cr = benchmark(); cr.replace.replace(m, cr.ins); } }; template static void par_compile(std::size_t n, F f) { if(n == 0) return; auto d = value_of(MIGRAPHX_GPU_COMPILE_PARALLEL{}); if(d == 0) d = n; par_for(n, n / d, f); } struct compile_manager { std::vector cps; bool exhaustive = false; template void add_plan(Ts&&... xs) { cps.push_back({std::forward(xs)...}); } void update_configs() { par_compile(cps.size(), [&](auto i) { cps[i].update_config(exhaustive); }); } void compile(module& m) { std::vector> compiles; for(auto& cp : cps) { cp.add_compiles(compiles); } par_compile(compiles.size(), [&](auto i) { compiles[i](); }); // Replace and/or benchmark for(const auto& cp : cps) { if(cp.results.empty()) continue; cp.replace(m); } // Remove compile_plan already executed cps.erase(std::remove_if(cps.begin(), cps.end(), [](const auto& cp) { return not cp.results.empty(); }), cps.end()); } }; void compile_ops::apply(module& m) const { compile_manager cm; cm.exhaustive = exhaustive_tune; // Find all precompile ops for(auto ins : iterator_for(m)) { if(ins->name() != "gpu::precompile_op") continue; operation preop = any_cast(ins->get_operator()).op; cm.add_plan(ctx, preop, ins, &m); } cm.update_configs(); cm.compile(m); // Compile already tuned configs cm.compile(m); assert(cm.cps.empty()); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/compile_pointwise.cpp000066400000000000000000000041161510465702400240320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { operation compile_pointwise(context& ctx, const std::vector& in_shapes, const_module_ref pm) { auto pf = gen::generate_pointwise(*pm, "inner_pointwise", true); std::string lambda = "MIGRAPHX_LIFT(inner_pointwise)"; auto kernel_name = gen::generate_name_from_ops(*pm, "kernel"); return gpu::compile_op("pointwise", ctx, in_shapes, {{"lambda", lambda}, {"preamble", pf}, {"kernel", kernel_name}}); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/compiler.cpp000066400000000000000000000053371510465702400221210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace { struct compiler_handle { compiler_compile compile; compiler_compile_op compile_op; compiler_tuning_config get_tuning_config; }; } // namespace static auto& compiler_map() { static std::unordered_map m; // NOLINT return m; } void register_compiler(const std::string& name, compiler_compile c, compiler_compile_op cop, compiler_tuning_config ctg) { compiler_map()[name] = {std::move(c), std::move(cop), std::move(ctg)}; } bool has_compiler_for(const std::string& name) { return compiler_map().count(name) > 0; } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op, const value& solution) { assert(contains(compiler_map(), op.name())); return compiler_map().at(op.name()).compile(ctx, ins, op, solution); } operation compile_op(const std::string& name, context& ctx, const std::vector& inputs, const value& v) { assert(contains(compiler_map(), name)); return compiler_map().at(name).compile_op(ctx, inputs, v); } optional get_tuning_config(context& ctx, instruction_ref ins, const operation& op, bool exhaustive) { assert(contains(compiler_map(), op.name())); return compiler_map().at(op.name()).get_tuning_config(ctx, ins, op, exhaustive); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/000077500000000000000000000000001510465702400210325ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/argmax.cpp000066400000000000000000000037111510465702400230170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void argmax(hipStream_t stream, const argument& result, const argument& arg, int64_t axis, bool select_last_index) { if(select_last_index) arg_op(argmax_op_last_index{}, stream, result, arg, axis); else arg_op(argmax_op_first_index{}, stream, result, arg, axis); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/argmin.cpp000066400000000000000000000037111510465702400230150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void argmin(hipStream_t stream, const argument& result, const argument& arg, int64_t axis, bool select_last_index) { if(select_last_index) arg_op(argmin_op_last_index{}, stream, result, arg, axis); else arg_op(argmin_op_first_index{}, stream, result, arg, axis); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/contiguous.cpp000066400000000000000000000051411510465702400237360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { static void contiguous_nonstandard(hipStream_t stream, const argument& result, const argument& arg) { shape s{result.get_shape().type(), result.get_shape().lens()}; visit_all(result, arg)([&](auto output_v, auto input_v) { hip_visit_views(output_v, input_v, s)([&](auto output, auto input, auto standard_shape) { mi_gs_launch(stream, standard_shape)([=](auto idx) __device__ { output[idx] = input[idx]; }); }); }); } static void contiguous_packed(hipStream_t stream, const argument& result, const argument& arg) { index_int nelements = result.get_shape().elements(); visit_all(result, arg)([&](auto output_v, auto input_v) { const auto* input = device_cast(input_v.data()); auto* output = device_cast(output_v.data()); gs_launch(stream, nelements)([=](auto i) __device__ { output[i] = input[i]; }); }); } void contiguous(hipStream_t stream, const argument& result, const argument& arg) { if(result.get_shape() == arg.get_shape() and result.get_shape().packed()) contiguous_packed(stream, result, arg); else contiguous_nonstandard(stream, result, arg); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/fill.cpp000066400000000000000000000031011510465702400224570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void fill(hipStream_t stream, const argument& result, unsigned long val) { nary(stream, result)([=]() __device__ { return val; }); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/fixed_pad.cpp000066400000000000000000000063231510465702400234650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { static argument fixed_pad_base_impl(hipStream_t stream, const argument& result, const argument& arg) { hip_visit_all(result, arg)([&](auto output, auto input) { gs_launch(stream, result.get_shape().elements())([=](auto i) __device__ { auto input_bounds = input.get_shape().lens; auto idx = output.get_shape().multi(i); bool in_bounds = sequence( idx.size(), [&](auto... js) { return ((idx[js] < input_bounds[js]) and ...); }); output[idx] = in_bounds ? input[idx] : 0; }); }); return result; } static argument fixed_pad_standard_impl(hipStream_t stream, const argument& result, const argument& arg) { index_int nelements = result.get_shape().elements(); index_int ielements = arg.get_shape().elements(); hip_pointer_visit_all(result, arg)([&](auto output, auto input) { gs_launch(stream, nelements)( [=](auto i) __device__ { output[i] = (i < ielements) ? input[i] : 0; }); }); return result; } argument fixed_pad(hipStream_t stream, const argument& result, const argument& arg) { if(result.get_shape().standard() and arg.get_shape().standard()) { auto ilens = arg.get_shape().lens(); auto olens = result.get_shape().lens(); auto [istart, ostart] = std::mismatch(ilens.begin(), ilens.end(), olens.begin()); if(std::equal(istart, ilens.end(), ostart, olens.end())) return fixed_pad_standard_impl(stream, result, arg); } return fixed_pad_base_impl(stream, result, arg); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/000077500000000000000000000000001510465702400224555ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/000077500000000000000000000000001510465702400242745ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/000077500000000000000000000000001510465702400250675ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/000077500000000000000000000000001510465702400263265ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/array.hpp000066400000000000000000000165101510465702400301600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_ARRAY_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_ARRAY_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_ARRAY_OP(op, binary_op) \ MIGRAPHX_DEVICE_CONSTEXPR hip_array& operator op(const hip_array& x) \ { \ for(index_int i = 0; i < N; i++) \ d[i] op x[i]; \ return *this; \ } \ MIGRAPHX_DEVICE_CONSTEXPR hip_array& operator op(const T& x) \ { \ for(index_int i = 0; i < N; i++) \ d[i] op x; \ return *this; \ } \ friend MIGRAPHX_DEVICE_CONSTEXPR hip_array operator binary_op(hip_array x, const hip_array& y) \ { \ return x op y; \ } \ friend MIGRAPHX_DEVICE_CONSTEXPR hip_array operator binary_op(hip_array x, const T& y) \ { \ return x op y; \ } \ friend MIGRAPHX_DEVICE_CONSTEXPR hip_array operator binary_op(const T& y, hip_array x) \ { \ return x op y; \ } template struct hip_array { T d[N]; MIGRAPHX_DEVICE_CONSTEXPR T& operator[](index_int i) { return d[i]; } MIGRAPHX_DEVICE_CONSTEXPR const T& operator[](index_int i) const { return d[i]; } MIGRAPHX_DEVICE_CONSTEXPR T& front() { return d[0]; } MIGRAPHX_DEVICE_CONSTEXPR const T& front() const { return d[0]; } MIGRAPHX_DEVICE_CONSTEXPR T& back() { return d[N - 1]; } MIGRAPHX_DEVICE_CONSTEXPR const T& back() const { return d[N - 1]; } MIGRAPHX_DEVICE_CONSTEXPR T* data() { return d; } MIGRAPHX_DEVICE_CONSTEXPR const T* data() const { return d; } MIGRAPHX_DEVICE_CONSTEXPR std::integral_constant size() const { return {}; } MIGRAPHX_DEVICE_CONSTEXPR T* begin() { return d; } MIGRAPHX_DEVICE_CONSTEXPR const T* begin() const { return d; } MIGRAPHX_DEVICE_CONSTEXPR T* end() { return d + size(); } MIGRAPHX_DEVICE_CONSTEXPR const T* end() const { return d + size(); } MIGRAPHX_DEVICE_CONSTEXPR T dot(const hip_array& x) const { T result = 0; for(index_int i = 0; i < N; i++) result += x[i] * d[i]; return result; } MIGRAPHX_DEVICE_CONSTEXPR T product() const { T result = 1; for(index_int i = 0; i < N; i++) result *= d[i]; return result; } MIGRAPHX_DEVICE_CONSTEXPR T single(index_int width = 100) const { T result = 0; T a = 1; for(index_int i = 0; i < N; i++) { result += d[N - i - 1] * a; a *= width; } return result; } MIGRAPHX_DEVICE_ARRAY_OP(+=, +) MIGRAPHX_DEVICE_ARRAY_OP(*=, *) MIGRAPHX_DEVICE_ARRAY_OP(/=, /) MIGRAPHX_DEVICE_ARRAY_OP(%=, %) MIGRAPHX_DEVICE_ARRAY_OP(&=, &) MIGRAPHX_DEVICE_ARRAY_OP(|=, |) MIGRAPHX_DEVICE_ARRAY_OP(^=, ^) friend MIGRAPHX_DEVICE_CONSTEXPR bool operator==(const hip_array& x, const hip_array& y) { for(index_int i = 0; i < N; i++) { if(x[i] != y[i]) return false; } return true; } friend MIGRAPHX_DEVICE_CONSTEXPR bool operator!=(const hip_array& x, const hip_array& y) { return not(x == y); } // This uses the product order rather than lexical order friend MIGRAPHX_DEVICE_CONSTEXPR bool operator<(const hip_array& x, const hip_array& y) { for(index_int i = 0; i < N; i++) { if(not(x[i] < y[i])) return false; } return true; } friend MIGRAPHX_DEVICE_CONSTEXPR bool operator>(const hip_array& x, const hip_array& y) { return y < x; } friend MIGRAPHX_DEVICE_CONSTEXPR bool operator<=(const hip_array& x, const hip_array& y) { return (x < y) or (x == y); } friend MIGRAPHX_DEVICE_CONSTEXPR bool operator>=(const hip_array& x, const hip_array& y) { return (y < x) or (x == y); } MIGRAPHX_DEVICE_CONSTEXPR hip_array carry(hip_array result) const { uint32_t overflow = 0; for(std::ptrdiff_t i = result.size() - 1; i > 0; i--) { auto z = result[i] + overflow; // Reset overflow overflow = 0; // Compute overflow using while loop instead of mod while(z >= d[i]) { z -= d[i]; overflow += 1; } result[i] = z; } result[0] += overflow; return result; } }; } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/fast_div.hpp000066400000000000000000000046061510465702400306440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_FAST_DIV_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_FAST_DIV_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { constexpr const uint64_t fast_div_shift = 42; inline uint64_t encode_divisor(uint64_t divisor) { if(divisor == 0) return 0; auto p = uint64_t{1} << fast_div_shift; return (p + divisor - 1) / divisor; } constexpr bool is_divisor_encodable(uint64_t i) { return i < (uint64_t{1} << (fast_div_shift / 2)); } MIGRAPHX_DEVICE_CONSTEXPR uint64_t fast_div(uint64_t dividend, uint64_t encoded_divisor) { return (dividend * encoded_divisor) >> fast_div_shift; } MIGRAPHX_DEVICE_CONSTEXPR uint64_t remainder(uint64_t result, uint64_t dividend, uint64_t divisor) { return dividend - divisor * result; } MIGRAPHX_DEVICE_CONSTEXPR uint64_t fast_mod(uint64_t dividend, uint64_t divisor, uint64_t encoded_divisor) { return remainder(fast_div(dividend, encoded_divisor), dividend, divisor); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/float_equal.hpp000066400000000000000000000050641510465702400313400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_DEVICE_FLOAT_EQUAL_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_DEVICE_FLOAT_EQUAL_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template using common_type = typename std::common_type::type; template {})> __device__ bool float_equal_device(T x, T y) { return std::isfinite(x) and std::isfinite(y) and std::nextafter(x, std::numeric_limits::lowest()) <= y and std::nextafter(x, std::numeric_limits::max()) >= y; } template <> __device__ bool float_equal_device(__bf16 x, __bf16 y) // NOLINT(misc-definitions-in-headers) { float xf = x; float yf = y; return std::isfinite(xf) and std::isfinite(yf) and std::nextafter(xf, std::numeric_limits::lowest()) <= yf and std::nextafter(xf, std::numeric_limits::max()) >= yf; } template {})> __device__ bool float_equal_device(T x, T y) { return x == y; } template __device__ bool float_equal(T x, U y) { return float_equal_device>(x, y); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/launch.hpp000066400000000000000000000115001510465702400303060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_LAUNCH_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_LAUNCH_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { struct index { index_int global = 0; index_int local = 0; index_int group = 0; __device__ index_int nglobal() const { return blockDim.x * gridDim.x; } // NOLINT __device__ index_int nlocal() const { return blockDim.x; } // NOLINT template __device__ void global_stride(index_int n, F f) const { const auto stride = nglobal(); for(index_int i = global; i < n; i += stride) { f(i); } } template __device__ void local_stride(index_int n, F f) const { const auto stride = nlocal(); for(index_int i = local; i < n; i += stride) { f(i); } } }; template __global__ void launcher(F f) { index idx{blockIdx.x * blockDim.x + threadIdx.x, threadIdx.x, blockIdx.x}; // NOLINT f(idx); } inline auto launch(hipStream_t stream, index_int global, index_int local) { return [=](auto f) { assert(local > 0); assert(global > 0); using f_type = decltype(f); dim3 nblocks(global / local); dim3 nthreads(local); /* hipGetLastError() returns error for the first failed HIP call that happened previously. MIGraphX calls into various backend libraries and failed HIP calls can also happen there. Calling hipGetLastError() would reset error code to hipSuccess, so that inside MIGraphX failed call to hipLaunchKernelGGL() can be captured. */ hipError_t flush_call = hipGetLastError(); (void)(flush_call); // cppcheck-suppress migraphx-UseDeviceLaunch hipLaunchKernelGGL((launcher), nblocks, nthreads, 0, stream, f); hipError_t kernel_launch_status = hipGetLastError(); if(kernel_launch_status != hipSuccess) { std::string message = hipGetErrorString(kernel_launch_status); if(not contains(get_targets(), get_device_name())) { message += ". Trying to run a kernel for " + get_device_name() + " but MIGraphX was built for targets " + get_targets_as_string() + ". Please rebuild MIGraphX with -DGPU_TARGETS='" + get_device_name() + "'."; } MIGRAPHX_THROW("MIGraphX device kernel failed to launch with error: " + message); } }; } template MIGRAPHX_DEVICE_CONSTEXPR auto gs_invoke(F&& f, index_int i, index idx) -> decltype(f(i, idx)) { return f(i, idx); } template MIGRAPHX_DEVICE_CONSTEXPR auto gs_invoke(F&& f, index_int i, index) -> decltype(f(i)) { return f(i); } inline auto gs_launch(hipStream_t stream, index_int n, index_int local = 1024) { index_int groups = (n + local - 1) / local; // max possible number of blocks is set to 1B (1,073,741,824) index_int nglobal = std::min(1073741824, groups) * local; return [=](auto f) { launch(stream, nglobal, local)([=](auto idx) __device__ { idx.global_stride(n, [&](auto i) { gs_invoke(f, i, idx); }); }); }; } #ifdef MIGRAPHX_USE_CLANG_TIDY #define MIGRAPHX_DEVICE_SHARED #else #define MIGRAPHX_DEVICE_SHARED __shared__ #endif } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/multi_index.hpp000066400000000000000000000124351510465702400313650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MULTI_INDEX_HPP #define MIGRAPHX_GUARD_RTGLIB_MULTI_INDEX_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template struct multi_index { using hip_index = hip_array; hip_index id{}; hip_index stride{}; MIGRAPHX_DEVICE_CONSTEXPR auto for_stride(hip_index n) const { // f should return void, but this helps with type deduction return [=](auto f) -> decltype(f(hip_index{})) { for(hip_index i = id; i < n; i = n.carry(i + stride)) { f(i); } }; } }; template __device__ __host__ auto deduce_for_stride(ForStride fs) -> decltype(fs(id{})); MIGRAPHX_DEVICE_CONSTEXPR multi_index<1> make_multi_index(index_int i, index_int n) { return {{i}, {n}}; } template MIGRAPHX_DEVICE_CONSTEXPR multi_index make_multi_index(const hip_shape& s, index_int i, index_int n) { return {s.multi(i), s.multi(n)}; } template MIGRAPHX_DEVICE_CONSTEXPR multi_index make_multi_index(const hip_shape& s, index_int i, const hip_array& n) { return {s.multi(i), n}; } template inline auto mi_nglobal(const hip_shape& s, index_int nlocal) { assert(s.standard); assert(s.elements() > 0); index_int n = s.elements(); index_int groups = (n + nlocal - 1) / nlocal; // max possible number of blocks is set to 1B (1,073,741,824) index_int nglobal = std::min(1073741824, groups) * nlocal; assert(groups > 0); assert(nglobal > 0); auto nglobal_multi = s.multi(nglobal); // Skip checking this, since this will cause metadata to not be generated // for some unknown reason. // // assert(std::any_of(nglobal_multi.begin(), nglobal_multi.end(), [](auto x){return x>0;})); // cppcheck-suppress migraphx-RedundantLocalVariable return nglobal_multi; } template inline auto mi_nlocal(const hip_shape& s, index_int local) { assert(s.standard); assert(s.elements() > 0); auto nlocal_multi = s.multi(local); // Skip checking this, since this will cause metadata to not be generated // for some unknown reason. // // assert(std::any_of(nlocal_multi.begin(), nlocal_multi.end(), [](auto x){return x>0;})); // cppcheck-suppress migraphx-RedundantLocalVariable return nlocal_multi; } template inline auto mi_launch(hipStream_t stream, const hip_shape& global, index_int nlocal = 1024) { auto nglobal_multi = mi_nglobal(global, nlocal); auto nglobal = global.index(nglobal_multi); return [=](auto f) { launch(stream, nglobal, nlocal)([=](auto idx) __device__ { auto midx = make_multi_index(global, idx.global, nglobal_multi); f(idx, midx.for_stride(global.lens)); }); }; } template inline auto mi_launch(hipStream_t stream, const hip_shape& global, const hip_shape& local, index_int nlocal = 1024) { auto nglobal_multi = mi_nglobal(global, 1); auto nglobal = global.index(nglobal_multi); auto nlocal_multi = mi_nlocal(local, nlocal); return [=](auto f) { launch(stream, nglobal * nlocal, nlocal)([=](auto idx) { // TODO: Use fast div for nlocal auto midx = make_multi_index(global, idx.global / nlocal, nglobal_multi); auto lidx = make_multi_index(local, idx.local, nlocal_multi); f(idx, midx.for_stride(global.lens), lidx.for_stride(local.lens)); }); }; } template inline auto mi_gs_launch(hipStream_t stream, const hip_shape& global, index_int nlocal = 1024) { return [=](auto f) { mi_launch(stream, global, nlocal)([=](auto, auto g) { g([&](auto i) { f(i); }); }); }; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/nary.hpp000066400000000000000000000450501510465702400300140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_NARY_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_NARY_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_NARY); // NOLINTNEXTLINE #define MIGRAPHX_TRACE_NARY_FUNCTION \ if(enabled(MIGRAPHX_TRACE_NARY{})) \ std::cout << "nary device function: " << __PRETTY_FUNCTION__ << std::endl; template constexpr auto pack(Ts... xs) { return [=](auto f) { return f(xs...); }; } template auto nary_nonstandard_nonpacked_impl(hipStream_t stream, F f, argument result, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION shape s{result.get_shape().type(), result.get_shape().lens()}; hip_visit_all(s, result, args...)([&](auto standard_shape, auto output, auto... inputs) { mi_gs_launch(stream, standard_shape)([=](auto idx) __device__ { output[idx] = f(inputs[idx]...); }); }); } inline auto create_broadcast_index(index_int len, index_int stride) { auto next_stride = stride * len; auto e_next_stride = encode_divisor(next_stride); auto e_stride = encode_divisor(stride); return [=](auto i) __device__ { // ( i % next_stride) / stride return fast_div(i, e_stride) - len * fast_div(i, e_next_stride); }; } template auto nary_nonstandard_packed_impl(hipStream_t stream, F f, const argument& result, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION auto arg_shape = make_array(args...).front().get_shape(); auto perm = find_permutation(arg_shape); auto s = reorder_shape(arg_shape, perm); hip_visit_all(s, result.reshape(reorder_shape(result.get_shape(), perm)), args.reshape(s)...)( [&](auto standard_shape, auto output, auto... inputs) { mi_gs_launch(stream, standard_shape)( [=](auto idx) __device__ { output[idx] = f(inputs[idx]...); }); }); } template void nary_broadcast_vec_impl( hipStream_t stream, F f, argument result, argument barg, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION const auto& output_shape = result.get_shape(); const auto& b_shape = barg.get_shape(); auto bdim = std::distance(b_shape.strides().begin(), std::find_if(b_shape.strides().begin(), b_shape.strides().end(), [](auto x) { return x != 0; })); auto bdim_len = output_shape.lens()[bdim]; auto bdim_stride = output_shape.strides()[bdim]; auto broadcast_idx = create_broadcast_index(bdim_len, bdim_stride); const index_int vec_size = 4; const index_int nlocal = 1024; const index_int nglobal = 256 * nlocal; const index_int bdim_vec_len = bdim_len / vec_size; hip_vec_visit_all(result, barg, args...)( [&](auto output, auto binput, auto... inputs) { using type = typename decltype(output)::value_type; const index_int nelements = output.size() / vec_size; launch(stream, nglobal, nlocal)([=](auto idx) __device__ { MIGRAPHX_DEVICE_SHARED type buffer[2048 / vec_size]; // Load bias into LDS for(size_t i = idx.local; i < bdim_vec_len; i += nlocal) { buffer[i] = binput.data()[i]; } __syncthreads(); const auto* bp = as_pointer(buffer); // Process the data for(size_t i = idx.global; i < nelements; i += nglobal) { auto bidx = broadcast_idx(i * vec_size); auto b = bp[bidx]; auto out = output.data()[i]; for(index_int j = 0; j < vec_size; j++) { out[j] = f(inputs.data()[i][j]..., b); } output.data()[i] = out; } }); }); } template void nary_broadcast_impl(hipStream_t stream, F f, argument result, argument barg, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION const auto& output_shape = result.get_shape(); const auto& b_shape = barg.get_shape(); auto bdim = std::distance(b_shape.strides().begin(), std::find_if(b_shape.strides().begin(), b_shape.strides().end(), [](auto x) { return x != 0; })); auto bdim_len = output_shape.lens()[bdim]; auto bdim_stride = output_shape.strides()[bdim]; auto broadcast_idx = create_broadcast_index(bdim_len, bdim_stride); const index_int nlocal = 1024; const index_int nglobal = 256 * nlocal; index_int nelements = result.get_shape().elements(); hip_visit_all(result, barg, args...)([&](auto output, auto binput, auto... inputs) { using type = typename decltype(output)::value_type; launch(stream, nglobal, nlocal)([=](auto idx) __device__ { MIGRAPHX_DEVICE_SHARED type buffer[2048]; // Load bias into LDS for(size_t i = idx.local; i < bdim_len; i += nlocal) { buffer[i] = binput.data()[i]; } __syncthreads(); // Process the data for(size_t i = idx.global; i < nelements; i += nglobal) { auto bidx = broadcast_idx(i); auto b = buffer[bidx]; output.data()[i] = f(inputs.data()[i]..., b); } }); }); } template void nary_double_broadcast_vec_impl( hipStream_t stream, F f, argument result, argument barg1, argument barg2, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION assert(barg1.get_shape().broadcasted()); assert(barg2.get_shape().broadcasted()); assert(barg1.get_shape() == barg2.get_shape()); const auto& output_shape = result.get_shape(); const auto& b_shape = barg1.get_shape(); auto bdim = std::distance(b_shape.strides().begin(), std::find_if(b_shape.strides().begin(), b_shape.strides().end(), [](auto x) { return x != 0; })); auto bdim_len = output_shape.lens()[bdim]; auto bdim_stride = output_shape.strides()[bdim]; auto broadcast_idx = create_broadcast_index(bdim_len, bdim_stride); const index_int vec_size = 4; const index_int nlocal = 1024; const index_int nglobal = 256 * nlocal; const index_int bdim_vec_len = bdim_len / vec_size; hip_vec_visit_all(result, barg1, barg2, args...)( [&](auto output, auto binput1, auto binput2, auto... inputs) { using type = typename decltype(output)::value_type; const index_int nelements = output.size() / vec_size; launch(stream, nglobal, nlocal)([=](auto idx) __device__ { MIGRAPHX_DEVICE_SHARED type buffer[2048 / vec_size]; // Load bias into LDS for(size_t i = idx.local; i < bdim_vec_len; i += nlocal) { buffer[i] = binput1.data()[i]; } for(size_t i = idx.local; i < bdim_vec_len; i += nlocal) { buffer[i + bdim_vec_len] = binput2.data()[i]; } __syncthreads(); const auto* bp = as_pointer(buffer); // Process the data for(size_t i = idx.global; i < nelements; i += nglobal) { auto bidx = broadcast_idx(i * vec_size); auto b1 = bp[bidx]; auto b2 = bp[bidx + bdim_len]; auto out = output.data()[i]; for(index_int j = 0; j < vec_size; j++) { out[j] = f(inputs.data()[i][j]..., b2, b1); } output.data()[i] = out; } }); }); } template void nary_double_broadcast_impl( hipStream_t stream, F f, argument result, argument barg1, argument barg2, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION assert(barg1.get_shape().broadcasted()); assert(barg2.get_shape().broadcasted()); assert(barg1.get_shape() == barg2.get_shape()); const auto& output_shape = result.get_shape(); const auto& b_shape = barg1.get_shape(); auto bdim = std::distance(b_shape.strides().begin(), std::find_if(b_shape.strides().begin(), b_shape.strides().end(), [](auto x) { return x != 0; })); auto bdim_len = output_shape.lens()[bdim]; auto bdim_stride = output_shape.strides()[bdim]; auto broadcast_idx = create_broadcast_index(bdim_len, bdim_stride); const index_int nlocal = 1024; const index_int nglobal = 256 * nlocal; index_int nelements = result.get_shape().elements(); hip_visit_all(result, barg1, barg2, args...)( [&](auto output, auto binput1, auto binput2, auto... inputs) { using type = typename decltype(output)::value_type; launch(stream, nglobal, nlocal)([=](auto idx) __device__ { MIGRAPHX_DEVICE_SHARED type buffer[2048]; // Load bias into LDS for(size_t i = idx.local; i < bdim_len; i += nlocal) { buffer[i] = binput1.data()[i]; } for(size_t i = idx.local; i < bdim_len; i += nlocal) { buffer[i + bdim_len] = binput2.data()[i]; } __syncthreads(); // Process the data for(size_t i = idx.global; i < nelements; i += nglobal) { auto bidx = broadcast_idx(i); auto b1 = buffer[bidx]; auto b2 = buffer[bidx + bdim_len]; output.data()[i] = f(inputs.data()[i]..., b2, b1); } }); }); } template void nary_standard_vec_impl(hipStream_t stream, F f, argument result, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION const auto& output_shape = result.get_shape(); visit_all(result, args...)([&](auto output, auto... inputs) { using type = device_type>; const index_int vec_size = 4; auto data = pack_vec<4>(device_cast(inputs.data())...); auto* outp = as_vec<4>(device_cast(output.data())); gs_launch(stream, output_shape.elements() / vec_size)([=](auto i) __device__ { vec out = outp[i]; data( [&](auto... xs) { for(index_int j = 0; j < vec_size; j++) { out[j] = f(xs[j]...); } }, i); outp[i] = out; }); }); } template void nary_standard_impl(hipStream_t stream, F f, argument result, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION index_int nelements = result.get_shape().elements(); hip_pointer_visit_all(result, args...)([&](auto output, auto... inputs) { gs_launch(stream, nelements)([=](auto i) __device__ { output[i] = f(inputs[i]...); }); }); } template void nary_impl(hipStream_t stream, F f, argument result, Arguments... args) { MIGRAPHX_TRACE_NARY_FUNCTION const auto shapes = make_array(args.get_shape()...); const bool standard = all_of(shapes, [](const shape& s) { return s.standard(); }); const bool packed = all_of(shapes, [](const shape& s) { return s.packed() and not s.broadcasted(); }); const bool same_shapes = all_of(shapes, [&](const shape& s) { return s == result.get_shape(); }); const bool same_input_shapes = all_of(shapes, [&](const shape& s) { return s == shapes[0]; }); if((result.get_shape().standard() and standard) or (packed and same_shapes)) nary_standard_impl(stream, f, result, args...); else if(packed and same_input_shapes) nary_nonstandard_packed_impl(stream, f, result, args...); else nary_nonstandard_nonpacked_impl(stream, f, result, args...); } template auto nary_nonstandard(hipStream_t stream, argument result, Arguments... args) { return [=](auto f) { nary_nonstandard_nonpacked_impl(stream, f, result, args...); }; } template auto nary_standard(hipStream_t stream, argument result, Arguments... args) { return [=](auto f) { nary_standard_impl(stream, f, result, args...); }; } template bool broadcastable(bool& divisible_by_4, index_int max_size, const argument& result, const argument& barg, const Arguments&... args) { divisible_by_4 = false; auto bshape = barg.get_shape(); const bool standard = all_of({args.get_shape()...}, [](const shape& s) { return s.standard(); }); const bool same_shapes = all_of({args.get_shape()...}, [&](const shape& s) { return s == result.get_shape(); }); // TODO: Check result and args shape is the same if(standard and same_shapes and bshape.broadcasted() and not bshape.scalar()) { auto not_zero = [](auto x) { return x != 0; }; const auto& strides = bshape.strides(); auto b_it = std::find_if(strides.begin(), strides.end(), not_zero); auto b_idx = std::distance(strides.begin(), b_it); auto b_len = result.get_shape().lens()[b_idx]; auto b_stride = result.get_shape().strides()[b_idx]; assert(bshape.lens()[b_idx] == b_len); if(b_len <= max_size and std::none_of(std::next(b_it), strides.end(), not_zero) and is_divisor_encodable(b_stride * b_len)) { divisible_by_4 = (b_len % 4 == 0) and (b_stride % 4 == 0) and (front_args(args...).get_shape().elements() % 4 == 0); return true; } } return false; } inline bool broadcastable(bool& divisible_by_4, index_int, const argument&, const argument&) { divisible_by_4 = false; return false; } // Nullary inline auto nary(hipStream_t stream, argument result) { return [=](auto f) { nary_standard_impl(stream, f, result); }; } // Unary inline auto nary(hipStream_t stream, argument result, argument arg) { return [=](auto f) { nary_impl(stream, f, result, arg); }; } // Binary inline auto nary(hipStream_t stream, argument result, argument arg, argument barg) { return [=](auto f) { bool divisible_by_4 = false; if(broadcastable(divisible_by_4, 2048, result, barg, arg)) { if(divisible_by_4) nary_broadcast_vec_impl(stream, f, result, barg, arg); else nary_broadcast_impl(stream, f, result, barg, arg); } else { nary_impl(stream, f, result, arg, barg); } }; } template auto nary(hipStream_t stream, argument result, Arguments... args) { static_assert(sizeof...(args) > 2, "Args needs to be greater than 2"); return [=](auto f) { auto barg1 = back_args(args...); bool fallback1 = pop_back_args(args...)([&](auto&&... args2) { auto barg2 = back_args(args2...); bool fallback2 = barg2.get_shape() != barg1.get_shape() or not barg2.get_shape().broadcasted() or pop_back_args(args2...)([&](auto&&... args3) { bool divisible_by_4 = false; if(broadcastable(divisible_by_4, 1024, result, barg2, args3...)) { if(divisible_by_4) nary_double_broadcast_vec_impl( stream, f, result, barg1, barg2, args3...); else nary_double_broadcast_impl(stream, f, result, barg1, barg2, args3...); return false; } return true; }); if(not fallback2) return false; bool divisible_by_4 = false; if(broadcastable(divisible_by_4, 2048, result, barg1, args2...)) { if(divisible_by_4) nary_broadcast_vec_impl(stream, f, result, barg1, args2...); else nary_broadcast_impl(stream, f, result, barg1, args2...); return false; } return true; }); if(fallback1) nary_impl(stream, f, result, args...); }; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/reduce.hpp000066400000000000000000000244241510465702400303140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_REDUCE_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_REDUCE_HPP #if defined(__GFX10__) || defined(__GFX11__) || defined(__GFX12__) // NOLINTNEXTLINE #define MIGRAPHX_WAVEFRONT_SIZE 32 #else // NOLINTNEXTLINE #define MIGRAPHX_WAVEFRONT_SIZE 64 #endif #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { #ifdef MIGRAPHX_NO_DPP template {})> __device__ auto block_reduce(index idx, Op op, T init, ForStride fs, F f) { using type = decltype(f(deduce_for_stride(fs))); MIGRAPHX_DEVICE_SHARED type buffer[N]; type x = init; fs([&](auto i) { x = op(x, f(i)); }); buffer[idx.local] = x; __syncthreads(); for(index_int s = 1; s < idx.nlocal(); s *= 2) { const index_int index = 2 * s * idx.local; if(index + s < idx.nlocal()) { buffer[index] = op(buffer[index], buffer[index + s]); } __syncthreads(); } return buffer[0]; } #else constexpr unsigned int dpp_row_shr(unsigned int x) { return 0x110u | x; } constexpr unsigned int dpp_row_bcast(unsigned int x) { unsigned int y = 0; switch(x) { case 15: y = 0x142; break; case 31: y = 0x143; break; default: throw std::runtime_error("Unknown bcast"); } return y; } template __device__ T dpp_mov(T& x) { static const index_int n = sizeof(T) < 4 ? 1 : sizeof(T) / 4; union type { uint32_t reg[n]; T data; }; type output{}; type input{}; // cppcheck-suppress unreadVariable input.data = x; for(index_int i = 0; i < n; i++) { output.reg[i] = __hip_move_dpp(input.reg[i], DppCtrl, RowMask, BankMask, BoundCtrl); } return output.data; } template __device__ void dpp_reduce(T& in, Op op) { T out{}; out = dpp_mov(in); in = op(in, out); out = dpp_mov(in); in = op(in, out); out = dpp_mov(in); in = op(in, out); out = dpp_mov(in); in = op(in, out); #if MIGRAPHX_WAVEFRONT_SIZE == 64 out = dpp_mov(in); in = op(in, out); out = dpp_mov(in); in = op(in, out); #endif } __device__ inline void dpp_reduce(float& x, sum) { #if defined(MIGRAPHX_USE_CLANG_TIDY) || defined(CPPCHECK) x = 1; #else __asm__ volatile("s_nop 4\n" "v_add_f32 %0 %0 %0 row_shr:1\n" "s_nop 1\n" "v_add_f32 %0 %0 %0 row_shr:2\n" "s_nop 1\n" "v_add_f32 %0 %0 %0 row_shr:4 bank_mask:0xe\n" "s_nop 1\n" "v_add_f32 %0 %0 %0 row_shr:8 bank_mask:0xc\n" #if MIGRAPHX_WAVEFRONT_SIZE == 64 "s_nop 1\n" "v_add_f32 %0 %0 %0 row_bcast:15 row_mask:0xa\n" "s_nop 1\n" "v_add_f32 %0 %0 %0 row_bcast:31 row_mask:0xc\n" #endif "s_nop 1\n" : "=v"(x) : "0"(x)); #endif } template {})> __device__ auto block_reduce(index idx, Op op, T init, ForStride fs, F f) { #if MIGRAPHX_WAVEFRONT_SIZE == 32 constexpr index_int nthreads = 16; #else constexpr index_int nthreads = 64; #endif using type = decltype(f(deduce_for_stride(fs))); MIGRAPHX_DEVICE_SHARED type buffer[N / nthreads]; type x = init; fs([&](auto i) { x = op(x, f(i)); }); dpp_reduce(x, op); const auto ldsidx = idx.local / nthreads; if((idx.local % nthreads) == nthreads - 1) { buffer[ldsidx] = x; } __syncthreads(); type y = init; for(index_int i = 0; i < idx.nlocal() / nthreads; i++) { y = op(y, buffer[i]); } return y; } #endif template __device__ auto block_reduce(index idx, Op op, T init, index_int n, F f) { auto midx = make_multi_index(idx.local, idx.nlocal()); // Workaround hcc, create a local array auto fs = midx.id; fs[0] = n; return block_reduce( idx, op, init, midx.for_stride(fs), [&](auto mi) __device__ { return f(mi[0]); }); } constexpr index_int compute_block_size(index_int n, index_int max_block_size) { size_t block_size = 64; while(block_size < max_block_size and block_size < n) block_size *= 2; return block_size; } inline std::vector get_reduce_lens(const std::vector& input_lens, const std::vector& output_lens) { std::vector reduce_lens; std::transform(output_lens.begin(), output_lens.end(), input_lens.begin(), std::back_inserter(reduce_lens), [](auto x, auto y) -> index_int { if(x == y) return 1; else return y; }); return reduce_lens; } template void reduce_multi_impl(hipStream_t stream, const argument& result, const argument& arg, Op op, T init, Input read_input, Output read_output, const shape& reduce_slice) { hip_visit_all(result, arg, reduce_slice)([&](auto output, auto input, auto reduce_shape) { auto relements = reduce_slice.elements(); const index_int max_block_size = 256; const index_int block_size = compute_block_size(relements, max_block_size); mi_launch(stream, output.get_shape(), reduce_shape, block_size)( [=](auto idx, auto global, auto local) __device__ { global([&](auto i) __device__ { auto r = block_reduce(idx, op, init, local, [&](auto j) __device__ { return read_input(input[i + j]); }); if(idx.local == 0) output[i] = read_output(r); }); }); }); } template void reduce_standard_impl(hipStream_t stream, const argument& result, const argument& arg, Op op, T init, Input read_input, Output read_output, index_int relements) { hip_visit_all(result, arg)([&](auto output, auto input) { auto nelements = result.get_shape().elements(); const index_int max_block_size = 256; const index_int block_size = compute_block_size(relements, max_block_size); gs_launch(stream, nelements * block_size, block_size)([=](auto i, auto idx) __device__ { const auto out_idx = i / block_size; const auto base_idx = out_idx * relements; auto r = block_reduce(idx, op, init, relements, [&](auto j) __device__ { return read_input(input.data()[base_idx + j]); }); if(idx.local == 0) output.data()[out_idx] = read_output(r); }); }); } template void reduce(hipStream_t stream, const argument& result, const argument& arg, Op op, T init, Input read_input, Output read_output) { auto&& output_shape = result.get_shape(); auto&& input_shape = arg.get_shape(); auto input_lens = input_shape.lens(); auto output_lens = output_shape.lens(); assert(output_lens.size() == input_lens.size()); if(input_shape.standard() and output_shape.standard() and output_lens.back() != input_lens.back() and std::equal(output_lens.begin(), std::prev(output_lens.end()), input_lens.begin())) { reduce_standard_impl( stream, result, arg, op, init, read_input, read_output, input_lens.back()); } else { std::vector reduce_lens = get_reduce_lens(input_lens, output_lens); shape reduce_slice{output_shape.type(), reduce_lens}; reduce_multi_impl(stream, result, arg, op, init, read_input, read_output, reduce_slice); } } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_NO_DPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/reduce_ops.hpp000066400000000000000000000053741510465702400312000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_DEVICE_REDUCE_OPS_HPP #define MIGRAPHX_GUARD_DEVICE_REDUCE_OPS_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { struct sum { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { return x + y; } }; struct product { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { return x * y; } }; struct id { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x) const { return x; } }; struct mean { size_t item_num = 1; template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x) const { return x / static_cast(item_num); } }; struct max { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { return (x > y) ? x : y; } }; struct min { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { return (x < y) ? x : y; } }; struct lowest { template __device__ __host__ operator T() const { return device_cast(std::numeric_limits>::lowest()); } }; struct highest { template __device__ __host__ operator T() const { return device_cast(std::numeric_limits>::max()); } }; } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_DEVICE_REDUCE_OPS_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/scan.hpp000066400000000000000000000063531510465702400277720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_DEVICE_SCAN_HPP #define MIGRAPHX_GUARD_DEVICE_SCAN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template {})> __device__ void block_scan(index idx, Op op, T init, ForStride fs, Input input, Output output) { using type = decltype(input(deduce_for_stride(fs))); MIGRAPHX_DEVICE_SHARED type buffer[2][N]; type x = init; fs([&](auto i) { index_int iout = 0; index_int iin = 1; if(idx.local == 0) buffer[iout][idx.local] = op(input(i), x); else buffer[iout][idx.local] = input(i); __syncthreads(); for(index_int s = 1; s < idx.nlocal(); s *= 2) { iout = 1 - iout; iin = 1 - iin; if(idx.local >= s) { buffer[iout][idx.local] = op(buffer[iin][idx.local], buffer[iin][idx.local - s]); } else { buffer[iout][idx.local] = buffer[iin][idx.local]; } __syncthreads(); } x = buffer[iout][idx.nlocal() - 1]; output(i, buffer[iout][idx.local]); }); } template __device__ void block_scan(index idx, Op op, T init, index_int n, Input input, Output output) { block_scan( idx, op, init, [&](auto f) -> decltype(f(index_int{})) { return idx.local_stride(n, f); }, input, output); } template constexpr auto reverse_scan(index_int n, F f) { return [=](auto i, auto&&... xs) { return f(n - i - 1, xs...); }; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_DEVICE_SCAN_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/shape.hpp000066400000000000000000000100251510465702400301350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_SHAPE_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_SHAPE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template struct hip_shape { using hip_index = hip_array; hip_index lens = {}; hip_index strides = {}; hip_array divs = {}; bool standard = false; __device__ __host__ hip_shape() = default; hip_shape(const shape& s) : standard(s.standard()) { assert(s.lens().size() == N); assert(s.strides().size() == N); std::copy(s.lens().begin(), s.lens().end(), lens.begin()); std::copy(s.strides().begin(), s.strides().end(), strides.begin()); assert(std::all_of(s.lens().begin(), s.lens().end(), &is_divisor_encodable)); std::transform(s.lens().begin(), s.lens().end(), divs.begin(), &encode_divisor); } MIGRAPHX_DEVICE_CONSTEXPR index_int elements() const { return lens.product(); } MIGRAPHX_DEVICE_CONSTEXPR index_int index(hip_index x) const { return x.dot(strides); } MIGRAPHX_DEVICE_CONSTEXPR index_int index(std::initializer_list x) const { index_int idx = 0; for(index_int i = 0; i < x.size(); i++) idx += *(x.begin() + i) * strides[i]; return idx; } MIGRAPHX_DEVICE_CONSTEXPR index_int index(index_int i) const { if(this->standard) return i; else { const index_int rank = this->lens.size(); index_int s = 1; index_int result = 0; for(index_int j = 0; j < this->lens.size(); j++) { const index_int k = rank - j - 1; const index_int stride = this->strides[k]; const index_int len = this->lens[k]; const index_int slen = s * len; const index_int idx = (i % slen) / s; result += stride * idx; s = slen; } return result; } } MIGRAPHX_DEVICE_CONSTEXPR hip_index multi(index_int idx) const { hip_index result; index_int tidx = idx; for(std::ptrdiff_t is = result.size() - 1; is > 0; is--) { // result[is] = tidx % lens[is]; // tidx = tidx / lens[is]; auto q = fast_div(tidx, divs[is]); result[is] = remainder(q, tidx, lens[is]); tidx = q; } result[0] = tidx; return result; } }; template hip_shape make_hip_shape(const shape& x) { return x; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/tensor.hpp000066400000000000000000000047461510465702400303640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEAVICE_TENSOR_HPP #define MIGRAPHX_GUARD_RTGLIB_DEAVICE_TENSOR_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template using hip_tensor_index = hip_array; template struct hip_tensor_descriptor { __device__ __host__ hip_tensor_descriptor() = default; hip_tensor_descriptor(const shape& s) { std::copy(s.lens().begin(), s.lens().end(), lens); std::copy(s.strides().begin(), s.strides().end(), strides); } __device__ __host__ hip_tensor_index multi(index_int idx) const { hip_tensor_index result{}; index_int tidx = idx; for(index_int is = 0; is < NDim; is++) { result[is] = tidx / strides[is]; tidx = tidx % strides[is]; } return result; } __device__ __host__ index_int linear(hip_tensor_index s) const { index_int idx = 0; for(index_int i = 0; i < NDim; i++) idx += s[i] * strides[i]; return idx; } index_int lens[NDim] = {}; index_int strides[NDim] = {}; }; } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/tensor_view.hpp000066400000000000000000000052471510465702400314130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_TENSOR_VIEW_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_TENSOR_VIEW_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template struct hip_tensor_view { using value_type = T; using hip_index = typename hip_shape::hip_index; __device__ __host__ hip_tensor_view() = default; __host__ hip_tensor_view(tensor_view x) : d(x.data()), s(x.get_shape()) {} __host__ hip_tensor_view(T* x, const shape& ss) : d(x), s(ss) {} MIGRAPHX_DEVICE_CONSTEXPR const hip_shape& get_shape() const { return s; } MIGRAPHX_DEVICE_CONSTEXPR index_int size() const { return s.elements(); } MIGRAPHX_DEVICE_CONSTEXPR value_type* data() const { return d; } template MIGRAPHX_DEVICE_CONSTEXPR value_type& operator[](U i) const { return d[s.index(i)]; } MIGRAPHX_DEVICE_CONSTEXPR value_type* begin() const { return d; } MIGRAPHX_DEVICE_CONSTEXPR value_type* end() const { return d + size(); } private: value_type* d = nullptr; hip_shape s{}; }; template hip_tensor_view make_hip_view(const shape& s, T* x) { return {x, s}; } template hip_tensor_view make_hip_view(tensor_view x) { return {x}; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/types.hpp000066400000000000000000000110521510465702400302020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_DEVICE_TYPES_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_DEVICE_TYPES_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { using index_int = std::uint32_t; #define MIGRAPHX_DEVICE_CONSTEXPR constexpr __device__ __host__ // NOLINT template using vec = T __attribute__((ext_vector_type(N))); template __device__ __host__ T* as_pointer(vec* x) { return reinterpret_cast(x); } template __device__ __host__ vec* as_vec(T* x) { return reinterpret_cast*>(x); } template tensor_view> as_vec(tensor_view x) { return {x.get_shape(), as_vec(x.data())}; } template auto pack_vec(Ts... xs) { return [=](auto f, index_int n) { return f(as_vec(xs)[n]...); }; } using gpu_half = __fp16; using gpu_bf16 = __bf16; namespace detail { template struct device_type { using type = T; }; template struct device_type> { using type = vec::type, N>; }; template <> struct device_type { using type = gpu_half; }; template <> struct device_type { using type = gpu_bf16; }; template struct host_type { using type = T; }; template <> struct host_type { using type = half; }; template <> struct host_type { using type = bf16; }; } // namespace detail template using host_type = typename detail::host_type::type; template using device_type = typename detail::device_type::type; template host_type host_cast(T x) { return reinterpret_cast&>(x); } template host_type* host_cast(T* x) { return reinterpret_cast*>(x); } template __device__ __host__ device_type device_cast(const T& x) { return reinterpret_cast&>(x); } template __device__ __host__ device_type* device_cast(T* x) { return reinterpret_cast*>(x); } template __device__ __host__ tensor_view> device_cast(tensor_view x) { return {x.get_shape(), reinterpret_cast*>(x.data())}; } template __device__ __host__ T to_hip_type(T x) { return x; } // Hip doens't support __fp16 and __bf16 inline __device__ __host__ float to_hip_type(gpu_half x) { return x; } inline __device__ __host__ float to_hip_type(gpu_bf16 x) { return x; } template struct is_floating_point : std::is_floating_point { }; template <> struct is_floating_point<__fp16> : std::true_type { }; template struct is_signed : std::is_signed { }; template <> struct is_signed<__fp16> : std::true_type { }; template struct is_arithmetic : std::is_arithmetic { }; template <> struct is_arithmetic<__fp16> : std::true_type { }; // Redo for __bf16 template <> struct is_floating_point<__bf16> : std::true_type { }; template <> struct is_signed<__bf16> : std::true_type { }; template <> struct is_arithmetic<__bf16> : std::true_type { }; } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/vector.hpp000066400000000000000000000064251510465702400303500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_VECTOR_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_VECTOR_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template struct hip_vector { MIGRAPHX_DEVICE_CONSTEXPR hip_vector() = default; MIGRAPHX_DEVICE_CONSTEXPR hip_vector(index_int s) : len(s) {} template __device__ __host__ hip_vector(Iterator start, Iterator last) { auto it = std::copy(start, last, d); len = std::distance(d, it); } __device__ __host__ hip_vector(std::initializer_list x) { std::copy(x.begin(), x.end(), d); len = x.size(); } MIGRAPHX_DEVICE_CONSTEXPR T& operator[](index_int i) { return d[i]; } MIGRAPHX_DEVICE_CONSTEXPR const T& operator[](index_int i) const { return d[i]; } MIGRAPHX_DEVICE_CONSTEXPR T& front() { return d[0]; } MIGRAPHX_DEVICE_CONSTEXPR const T& front() const { return d[0]; } MIGRAPHX_DEVICE_CONSTEXPR T& back() { return d[size() - 1]; } MIGRAPHX_DEVICE_CONSTEXPR const T& back() const { return d[size() - 1]; } MIGRAPHX_DEVICE_CONSTEXPR T* data() { return d; } MIGRAPHX_DEVICE_CONSTEXPR const T* data() const { return d; } MIGRAPHX_DEVICE_CONSTEXPR index_int size() const { return len; } MIGRAPHX_DEVICE_CONSTEXPR T* begin() { return d; } MIGRAPHX_DEVICE_CONSTEXPR const T* begin() const { return d; } MIGRAPHX_DEVICE_CONSTEXPR T* end() { return d + size(); } MIGRAPHX_DEVICE_CONSTEXPR const T* end() const { return d + size(); } template MIGRAPHX_DEVICE_CONSTEXPR void push_back(U&& x) { d[len] = static_cast(x); len++; } private: T d[N] = {}; index_int len = 0; }; template hip_vector to_hip_vector(const std::vector& x) { hip_vector result(x.size()); std::copy(x.begin(), x.end(), result.begin()); return result; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/include/migraphx/gpu/device/visit.hpp000066400000000000000000000150531510465702400302010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_VISIT_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_VISIT_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template constexpr void visit_tensor_size(index_int n, F f) { switch(n) { case 1: { f(std::integral_constant{}); break; } case 2: { f(std::integral_constant{}); break; } case 3: { f(std::integral_constant{}); break; } case 4: { f(std::integral_constant{}); break; } case 5: { f(std::integral_constant{}); break; } default: throw std::runtime_error("Tensor dims " + std::to_string(n) + " out of range"); } } inline shape get_shape(const shape& x) { return x; } template auto get_shape(const T& x) -> decltype(x.get_shape()) { return x.get_shape(); } template struct is_hip_type : std::false_type { }; template <> struct is_hip_type : std::true_type { }; template <> struct is_hip_type : std::true_type { }; template <> struct is_hip_type : std::true_type { }; template <> struct is_hip_type : std::true_type { }; template <> struct is_hip_type : std::true_type { }; template <> struct is_hip_type : std::true_type { }; template <> struct is_hip_type : std::true_type { }; template {})> void hip_visitor_invoke(T as, V&& v) { v(as); } template {})> void hip_visitor_invoke(T, V&&) { MIGRAPHX_THROW(std::string("Unsupported data type on GPU: ") + __PRETTY_FUNCTION__); } template auto hip_visitor(V v) { return [=](auto as) { hip_visitor_invoke(as, v); }; } template void hip_visit_all_impl(const shape& s, F f, V&& v, Ts&&... xs) { std::initializer_list types = {get_shape(xs).type()...}; if(not std::all_of( types.begin(), types.end(), [&](migraphx::shape::type_t t) { return t == s.type(); })) MIGRAPHX_THROW("Types must be the same"); std::initializer_list ranks = {static_cast(get_shape(xs).ndim())...}; if(not std::all_of(ranks.begin(), ranks.end(), [&](index_int r) { return r == s.ndim(); })) MIGRAPHX_THROW("Ranks must be the same"); visit_tensor_size(s.ndim(), [&](auto ndim) { s.visit_type(hip_visitor([&](auto as) { v(f(xs, ndim, as)...); })); }); } template void hip_visit_views_impl(const shape& s, F f, V&& v, Ts&&... xs) { std::initializer_list ranks = {static_cast(get_shape(xs).ndim())...}; if(not std::all_of(ranks.begin(), ranks.end(), [&](index_int r) { return r == s.ndim(); })) MIGRAPHX_THROW("Ranks must be the same"); visit_tensor_size(s.ndim(), [&](auto ndim) { v(f(xs, ndim)...); }); } template struct hip_convert { F f; template auto operator()(const RawData& x, N ndim, As as) const -> decltype(make_hip_view(x.get_shape(), f(as.from(x.data())))) { return make_hip_view(x.get_shape(), f(as.from(x.data()))); } template auto operator()(const shape& s, N ndim, As) const { return make_hip_shape(s); } }; template hip_convert make_hip_convert(F f) { return {f}; } template struct hip_convert_view { F f; template auto operator()(tensor_view x, N ndim) const { return make_hip_view(f(x)); } template auto operator()(const shape& s, N ndim) const { return make_hip_shape(s); } }; template hip_convert_view make_hip_convert_view(F f) { return {f}; } template auto hip_visit_all(T&& x, Ts&&... xs) { return [&](auto f) { hip_visit_all_impl( get_shape(x), make_hip_convert([](auto* p) { return device_cast(p); }), f, x, xs...); }; } template auto hip_vec_visit_all(T&& x, Ts&&... xs) { return [&](auto f) { auto sx = get_shape(x); auto lens = sx.lens(); assert(lens.back() % N == 0); assert(sx.strides().back() == 1); lens.back() /= N; shape vec_sx{sx.type(), lens}; hip_visit_all_impl(vec_sx, make_hip_convert([](auto* p) { return as_vec(device_cast(p)); }), f, x, xs...); }; } template auto hip_pointer_visit_all(T&& x, Ts&&... xs) { return [&](auto f) { visit_all(x, xs...)([&](auto... vs) { f(device_cast(vs.data())...); }); }; } template auto hip_visit_views(T&& x, Ts&&... xs) { return [&](auto f) { hip_visit_views_impl(get_shape(x), make_hip_convert_view([](auto v) { return device_cast(v); }), f, x, xs...); }; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/logsoftmax.cpp000066400000000000000000000064571510465702400237350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void logsoftmax(hipStream_t stream, const argument& result, const argument& arg, int64_t axis) { auto batch_lens = result.get_shape().lens(); index_int batch_item_num = batch_lens[axis]; batch_lens[axis] = 1; migraphx::shape batch_shape{result.get_shape().type(), batch_lens}; hip_visit_all(result, arg, batch_shape)([&](auto output, auto input, auto batch) { const index_int max_block_size = 256; const index_int block_size = compute_block_size(batch_item_num, max_block_size); gs_launch(stream, batch_shape.elements() * block_size, block_size)([=](auto i, auto idx) __device__ { auto data_idx = batch.multi(i / block_size); using type = device_type>; type init = lowest(); auto batch_max = block_reduce( idx, max{}, init, batch_item_num, [&](auto j) __device__ { data_idx[axis] = j; return input[data_idx]; }); auto batch_sum = block_reduce(idx, sum{}, 0, batch_item_num, [&](auto j) __device__ { data_idx[axis] = j; auto val = input[data_idx] - batch_max; return ::exp(to_hip_type(val)); }); auto log_batch_sum = ::log(to_hip_type(batch_sum)) + batch_max; idx.local_stride(batch_item_num, [&](auto j) __device__ { data_idx[axis] = j; output[data_idx] = input[data_idx] - log_batch_sum; }); }); }); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/multinomial.cpp000066400000000000000000000064741510465702400241030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template constexpr static Iterator upper_bound(Iterator first, Iterator last, const T& value) { Iterator it; typename std::iterator_traits::difference_type count; typename std::iterator_traits::difference_type step; count = std::distance(first, last); while(count > 0) { it = first; step = count / 2; std::advance(it, step); if(not(value < *it)) { first = ++it; count -= step + 1; } else count = step; } return first; } void multinomial(hipStream_t stream, const argument& result, const argument& arg0, const argument& arg1) { size_t batch_size = arg0.get_shape().lens().front(); size_t class_size = arg0.get_shape().lens().back(); size_t sample_size = result.get_shape().lens().back(); visit_all(arg0, arg1)([&](auto cdf_host, auto dist_host) { result.visit([&](auto output_host) { hip_visit_views(cdf_host, dist_host, output_host)( [&](auto cdf, auto dist, auto output) { gs_launch(stream, batch_size * sample_size)([=](auto i) __device__ { auto idx = output.get_shape().multi(i); auto cdf_begin = cdf.begin() + (idx.front() * class_size); auto cdf_end = cdf_begin + class_size; auto* sample_iter = upper_bound(cdf_begin, cdf_end, dist[i] * *(std::prev(cdf_end))); output[i] = std::distance(cdf_begin, sample_iter); }); }); }); }); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/nonzero.cpp000066400000000000000000000056631510465702400232420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { argument nonzero(hipStream_t stream, const argument& result, const argument& arg_data) { auto s = arg_data.get_shape(); auto elem_num = s.elements(); auto out_elem_num = result.get_shape().elements(); // call the prefix_sum function to do a prefix_sum to compute // index in the output. Only 1 block can be used since we have // only one prefix sum const index_int block_size = 256; hip_visit_all(arg_data, s)([&](auto input, auto si) { const auto* in_ptr = device_cast(input.data()); auto* ptr = result.cast(); gs_launch(stream, block_size, block_size)([=](auto, auto idx) __device__ { // fill all output to 0 first idx.local_stride(out_elem_num, [&](auto j) { ptr[j] = 0; }); block_scan( idx, sum{}, 0, elem_num, [&](auto j) { return (float_equal(in_ptr[j], 0)) ? 0 : 1; }, [&](auto j, auto x) { auto out_loc = x - 1; if(float_equal(in_ptr[j], 0)) return; auto index = si.multi(j); for(size_t k = 0; k < index.size(); ++k) { ptr[k * elem_num + out_loc] = index[k]; } }); }); }); return result; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/prefix_scan_sum.cpp000066400000000000000000000140331510465702400247240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void prefix_scan_sum(hipStream_t stream, const argument& result, const argument& arg, int32_t axis, bool exclusive, bool reverse) { const index_int max_block_size = 256; const index_int n = arg.get_shape().lens()[axis]; auto rlens = result.get_shape().lens(); rlens[axis] = 1; hip_visit_all(result, arg, result.get_shape().with_lens(rlens))( [=](auto output, auto input, auto rshape) { const index_int block_size = compute_block_size(rshape.elements(), max_block_size); if(reverse and exclusive) { gs_launch(stream, rshape.elements() * block_size, block_size)( [=](auto i, auto idx) __device__ { const auto ridx = rshape.multi(i / block_size); auto compute_idx = [&](auto j) { auto k = ridx; k[axis] = j; return k; }; block_scan( idx, sum{}, 0, n, reverse_scan(n, [&](auto j) { return input[compute_idx(j)]; }), reverse_scan(n, [&](auto j, auto x) { if(j == n - 1) output[compute_idx(j)] = 0; if(j > 0) output[compute_idx(j - 1)] = x; })); }); } else if(reverse) { gs_launch(stream, rshape.elements() * block_size, block_size)( [=](auto i, auto idx) __device__ { const auto ridx = rshape.multi(i / block_size); auto compute_idx = [&](auto j) { auto k = ridx; k[axis] = j; return k; }; block_scan( idx, sum{}, 0, n, reverse_scan(n, [&](auto j) { return input[compute_idx(j)]; }), reverse_scan(n, [&](auto j, auto x) { output[compute_idx(j)] = x; })); }); } else if(exclusive) { gs_launch(stream, rshape.elements() * block_size, block_size)( [=](auto i, auto idx) __device__ { const auto ridx = rshape.multi(i / block_size); auto compute_idx = [&](auto j) { auto k = ridx; k[axis] = j; return k; }; block_scan( idx, sum{}, 0, n, [&](auto j) { return input[compute_idx(j)]; }, [&](auto j, auto x) { auto k = j + 1; if(j == 0) output[compute_idx(0)] = 0; if(k < n) output[compute_idx(k)] = x; }); }); } else { gs_launch(stream, rshape.elements() * block_size, block_size)( [=](auto i, auto idx) __device__ { const auto ridx = rshape.multi(i / block_size); auto compute_idx = [&](auto j) { auto k = ridx; k[axis] = j; return k; }; block_scan( idx, sum{}, 0, n, [&](auto j) { return input[compute_idx(j)]; }, [&](auto j, auto x) { output[compute_idx(j)] = x; }); }); } }); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/reverse.cpp000066400000000000000000000050651510465702400232170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/gpu/device/visit.hpp" #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { argument reverse(hipStream_t stream, argument result, argument arg1, const std::vector& axes) { auto s = arg1.get_shape(); // auto lens = s.lens(); std::vector axis_len(axes.begin(), axes.end()); shape sa{shape::float_type, axis_len}; std::size_t nelements = s.elements(); visit_all(result, arg1)([&](auto output1, auto input1) { hip_visit_views(output1, input1, s)([&](auto output, auto input, auto hs) { hip_visit_views(sa)([&](auto daxes) { auto lens = hs.lens; gs_launch(stream, nelements)([=](auto i) __device__ { auto idx = hs.multi(i); auto in_idx = idx; for(auto axis : daxes.lens) in_idx[axis] = lens[axis] - 1 - idx[axis]; output[idx] = input[in_idx]; }); }); }); }); return result; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/rnn_variable_seq_lens.cpp000066400000000000000000000126101510465702400260710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void rnn_var_sl_shift_sequence(hipStream_t stream, const argument& result, const argument& arg_hs, const argument& arg_sl) { auto output_shape = result.get_shape(); int64_t max_len = output_shape.lens()[0]; visit_all(result, arg_hs)([&](auto output, auto input) { const auto* in_data = device_cast(input.data()); auto* out_data = device_cast(output.data()); auto out_s = make_hip_shape<3>(output_shape); arg_sl.visit([&](auto sl) { const auto* sl_data = device_cast(sl.data()); gs_launch(stream, output_shape.elements(), 256)([=](auto i) __device__ { auto idx = out_s.multi(i); auto t = idx[0]; auto b = idx[1]; auto l = sl_data[b]; auto val = in_data[0]; val = 0; if(t >= max_len - l) { auto in_idx = idx; in_idx[0] -= (max_len - l); val = in_data[out_s.index(in_idx)]; } out_data[i] = val; }); }); }); } void rnn_var_sl_shift_output(hipStream_t stream, const argument& result, const argument& arg_hs, const argument& arg_sl, bool is_reverse) { auto output_shape = result.get_shape(); int64_t max_len = output_shape.lens()[0]; visit_all(result, arg_hs)([&](auto output, auto input) { const auto* in_data = device_cast(input.data()); auto* out_data = device_cast(output.data()); auto out_s = make_hip_shape<4>(output_shape); arg_sl.visit([&](auto sl) { const auto* sl_data = device_cast(sl.data()); gs_launch(stream, output_shape.elements(), 256)([=](auto i) __device__ { auto idx = out_s.multi(i); auto t = idx[0]; auto d = idx[1]; auto b = idx[2]; auto l = sl_data[b]; auto val = in_data[0]; val = 0; if(t < l) { int offset = (d == 1 or is_reverse) ? 1 : 0; auto in_idx = idx; in_idx[0] += offset * (max_len - l); val = in_data[out_s.index(in_idx)]; } out_data[i] = val; }); }); }); } void rnn_var_sl_last_output(hipStream_t stream, const argument& result, const argument& arg_hs, const argument& arg_sl, bool is_reverse) { auto input_shape = arg_hs.get_shape(); auto out_comp_lens = input_shape.lens(); out_comp_lens[0] = 1; shape out_comp_shape{input_shape.type(), out_comp_lens}; visit_all(result, arg_hs)([&](auto output, auto input) { const auto* in_data = device_cast(input.data()); auto* out_data = device_cast(output.data()); arg_sl.visit([&](auto sl) { const auto* sl_data = device_cast(sl.data()); auto in_s = make_hip_shape<4>(input_shape); auto out_s = make_hip_shape<4>(out_comp_shape); gs_launch(stream, result.get_shape().elements(), 256)([=](auto i) __device__ { auto idx = out_s.multi(i); auto d = idx[1]; auto b = idx[2]; auto l = sl_data[b]; if(is_reverse or d == 1) { idx[0] = 0; } else { idx[0] = l - 1; } out_data[i] = in_data[in_s.index(idx)]; }); }); }); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/targets.cpp000066400000000000000000000043061510465702400232120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { static std::vector parse_targets() { return split_string(MIGRAPHX_GPU_TARGETS, ';'); } const std::vector& get_targets() { static auto result = parse_targets(); return result; } std::string get_targets_as_string() { return join_strings(get_targets(), ", "); } static int get_device_id() { int device; auto status = hipGetDevice(&device); if(status != hipSuccess) MIGRAPHX_THROW("No device"); return device; } std::string get_device_name() { hipDeviceProp_t props{}; auto status = hipGetDeviceProperties(&props, get_device_id()); if(status != hipSuccess) MIGRAPHX_THROW("Failed to get device properties"); return props.gcnArchName; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/targets.hpp.in000066400000000000000000000034241510465702400236240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_DEVICE_TARGETS_CPP #define MIGRAPHX_GUARD_DEVICE_TARGETS_CPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { #define MIGRAPHX_GPU_TARGETS "@GPU_TARGETS@" // NOLINT MIGRAPHX_DEVICE_EXPORT const std::vector& get_targets(); MIGRAPHX_DEVICE_EXPORT std::string get_targets_as_string(); MIGRAPHX_DEVICE_EXPORT std::string get_device_name(); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_DEVICE_TARGETS_CPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device/topk.cpp000066400000000000000000000164231510465702400225210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template struct hip_heap_vector { MIGRAPHX_DEVICE_CONSTEXPR hip_heap_vector(T* val, index_int n, Index v_idx, Compare comp) : data(val), size(n), data_index(v_idx), compare(comp) { make_heap(size); } MIGRAPHX_DEVICE_CONSTEXPR void try_push(const T val) { if(compare(val, data[data_index(0)])) return; pop_heap(size - 1); data[data_index(size - 1)] = val; push_heap(size - 1); } MIGRAPHX_DEVICE_CONSTEXPR void sort() { sort_heap(size); } private: MIGRAPHX_DEVICE_CONSTEXPR inline static void swap(T& v1, T& v2) noexcept { T v = v1; v1 = v2; v2 = v; } MIGRAPHX_DEVICE_CONSTEXPR inline void heapify_down(index_int n, index_int index) { while(index < n) { auto pre_index = index; index_int l = 2 * index + 1; index_int r = 2 * index + 2; if(l < n and compare(data[data_index(l)], data[data_index(index)])) { index = l; } if(r < n and compare(data[data_index(r)], data[data_index(index)])) { index = r; if(compare(data[data_index(l)], data[data_index(r)])) { index = l; } } if(index == pre_index) { break; } swap(data[data_index(index)], data[data_index(pre_index)]); } } MIGRAPHX_DEVICE_CONSTEXPR inline void heapify_up(index_int index) { while(index > 0) { auto parent_idx = (index - 1) / 2; if(not compare(data[data_index(index)], data[data_index(parent_idx)])) { break; } swap(data[data_index(index)], data[data_index(parent_idx)]); index = parent_idx; } } MIGRAPHX_DEVICE_CONSTEXPR inline void make_heap(index_int n) { for(int j = n / 2 - 1; j >= 0; --j) { heapify_down(n, j); } } MIGRAPHX_DEVICE_CONSTEXPR inline void push_heap(index_int loc) { heapify_up(loc); } MIGRAPHX_DEVICE_CONSTEXPR inline void pop_heap(index_int loc) { swap(data[data_index(0)], data[data_index(loc)]); heapify_down(loc, 0); } MIGRAPHX_DEVICE_CONSTEXPR inline void sort_heap(index_int n) { for(int j = n - 1; j > 0; --j) { swap(data[data_index(0)], data[data_index(j)]); heapify_down(j, 0); } } T* data = nullptr; index_int size; Index data_index; Compare compare; }; template __device__ static hip_heap_vector make_heap(T* data, index_int n, Index idx, Compare compare) { return {data, n, idx, compare}; } template static std::vector topk(hipStream_t stream, const argument& val_res, const argument& ind_res, const argument& arg, int64_t k, int64_t axis, Compare compare) { auto in_s = arg.get_shape(); auto in_lens = in_s.lens(); auto out_s = val_res.get_shape(); auto axis_dim = in_s.lens()[axis]; auto comp_lens = in_lens; comp_lens[axis] = 1; shape comp_s{in_s.type(), comp_lens}; std::size_t elem_num = comp_s.elements(); hip_visit_all(val_res, arg, out_s, in_s, comp_s)( [&](auto out_val, auto input, auto oss, auto iss, auto css) { auto* data = device_cast(input.data()); auto* out = device_cast(out_val.data()); auto* const ind = ind_res.cast(); gs_launch(stream, elem_num)([=](auto i) __device__ { auto idx = css.multi(i); auto in_idx = [&](int ii) { auto iidx = idx; iidx[axis] = ii; return iss.index(iidx); }; auto out_idx = [&](int ii) { auto iidx = idx; iidx[axis] = ii; return oss.index(iidx); }; auto data_compare = [=](auto ii, auto jj) { return compare(data[in_idx(ii)], data[in_idx(jj)]); }; for(int j = 0; j < k; ++j) { ind[out_idx(j)] = j; } auto hp = make_heap(ind, k, out_idx, data_compare); for(int j = k; j < axis_dim; ++j) { hp.try_push(j); } hp.sort(); for(int j = 0; j < k; ++j) { out[out_idx(j)] = data[in_idx(ind[out_idx(j)])]; } }); }); return {val_res, ind_res}; } argument topk_largest(hipStream_t stream, const argument& val_res, const argument& ind_res, const argument& arg, int64_t k, int64_t axis) { return {topk(stream, val_res, ind_res, arg, k, axis, std::less<>{})}; } argument topk_smallest(hipStream_t stream, const argument& val_res, const argument& ind_res, const argument& arg, int64_t k, int64_t axis) { return {topk(stream, val_res, ind_res, arg, k, axis, std::greater<>{})}; } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/device_name.cpp000066400000000000000000000071531510465702400225440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_SET_GEMM_PROVIDER) int get_device_id() { int device; auto status = hipGetDevice(&device); if(status != hipSuccess) MIGRAPHX_THROW("No device"); return device; } std::string get_device_name() { hipDeviceProp_t props{}; auto status = hipGetDeviceProperties(&props, get_device_id()); if(status != hipSuccess) MIGRAPHX_THROW("Failed to get device properties"); return props.gcnArchName; } bool gfx_has_fp8fnuz_intrinsics() { const auto device_name = trim(split_string(get_device_name(), ':').front()); return (starts_with(device_name, "gfx94")); } bool gfx_has_fp8ocp_intrinsics() { const auto device_name = trim(split_string(get_device_name(), ':').front()); bool is_navi_with_fp8ocp = starts_with(device_name, "gfx12") and device_name >= "gfx1200"; bool is_mi_with_fp8ocp = starts_with(device_name, "gfx9") and device_name >= "gfx950"; return (is_navi_with_fp8ocp or is_mi_with_fp8ocp); } bool gfx_has_bf16_intrinsics() { const auto device_name = trim(split_string(get_device_name(), ':').front()); return not(starts_with(device_name, "gfx1030")); } #if MIGRAPHX_USE_HIPBLASLT // Archs that support hipBLASLt but are defaulted to use rocBLAS. bool gfx_default_rocblas() { const auto device_name = trim(split_string(get_device_name(), ':').front()); // Default to rocBLAS for gfx90a. return ((string_value_of(MIGRAPHX_SET_GEMM_PROVIDER{}) == "hipblaslt") ? false : (device_name == "gfx90a")); } #endif bool hipblaslt_supported() { #if !MIGRAPHX_USE_HIPBLASLT return false; #else const auto device_name = trim(split_string(get_device_name(), ':').front()); // hipblaslt is supported for MI200 and above, and Navi3x and above. return (device_name == "gfx90a" or (starts_with(device_name, "gfx94") and device_name >= "gfx942") or (starts_with(device_name, "gfx95") and device_name >= "gfx950") or starts_with(device_name, "gfx110") or starts_with(device_name, "gfx120")); #endif } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/000077500000000000000000000000001510465702400210665ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/CMakeLists.txt000066400000000000000000000031071510465702400236270ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### file(GLOB GPU_DRIVER_SRCS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) add_executable(gpu-driver ${GPU_DRIVER_SRCS} ) rocm_clang_tidy_check(gpu-driver) target_include_directories(gpu-driver PRIVATE include) target_link_libraries(gpu-driver PRIVATE migraphx_gpu) ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/action.cpp000066400000000000000000000034651510465702400230570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace driver { static auto& action_map() { static std::unordered_map m; return m; } action_function get_action(const std::string& name) { if(action_map().count(name) == 0) MIGRAPHX_THROW("Missing action: " + name); return action_map().at(name); } void register_action(const std::string& name, const action_function& a) { action_map()[name] = a; } } // namespace driver } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/compile_op.cpp000066400000000000000000000037361510465702400237310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace driver { struct compile_op : action { static void apply(const parser& p, const value& v) { context ctx; auto inputs = p.parse_shapes(v.at("inputs")); auto op = gpu::compile_op(v.at("name").to(), ctx, inputs, v); auto t = time_op(ctx, op, inputs, p.get(v, "iterations", 100)); std::cout << op << " -> " << op.compute_shape(inputs) << ": " << t << "ms" << std::endl; std::cout << std::endl; } }; } // namespace driver } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/include/000077500000000000000000000000001510465702400225115ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/include/migraphx/000077500000000000000000000000001510465702400243305ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/include/migraphx/gpu/000077500000000000000000000000001510465702400251235ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/include/migraphx/gpu/driver/000077500000000000000000000000001510465702400264165ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/include/migraphx/gpu/driver/action.hpp000066400000000000000000000042721510465702400304110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_DRIVER_ACTION_HPP #define MIGRAPHX_GUARD_GPU_DRIVER_ACTION_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace driver { using action_function = std::function; action_function get_action(const std::string& name); void register_action(const std::string& name, const action_function& a); struct auto_register_action { template static void apply() { const auto& name = get_type_name(); register_action(name.substr(name.rfind("::") + 2), [](auto&&... xs) { T::apply(std::forward(xs)...); }); } }; template using action = auto_register; } // namespace driver } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_DRIVER_ACTION_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/include/migraphx/gpu/driver/parser.hpp000066400000000000000000000042011510465702400304200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_DRIVER_PARSER_HPP #define MIGRAPHX_GUARD_GPU_DRIVER_PARSER_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace driver { [[noreturn]] void error(const std::string& msg); struct parser { parser() = default; template T get(const value& v, const std::string& key, const T& default_value) const { return v.get(key, settings.get(key, default_value)); } shape parse_shape(const value& v) const; std::vector parse_shapes(const value& v) const; void load_settings(const value& v); static void process(const value& v); private: value settings = value::object{}; }; } // namespace driver } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_DRIVER_PARSER_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/main.cpp000066400000000000000000000034351510465702400225230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include using namespace migraphx; // NOLINT using namespace migraphx::gpu; // NOLINT using namespace migraphx::gpu::driver; // NOLINT int main(int argc, char const* argv[]) { std::vector args(argv, argv + argc); if(args.size() < 2) { std::cout << "Usage: gpu-driver " << std::endl; std::abort(); } auto v = from_json_string(convert_to_json(read_string(args[1]))); parser::process(v); } ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/parser.cpp000066400000000000000000000050651510465702400230740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace driver { [[noreturn]] void error(const std::string& msg) { std::cout << msg << std::endl; std::abort(); } shape parser::parse_shape(const value& v) const { auto lens = get(v, "lens", std::vector{}); auto strides = get(v, "strides", std::vector{}); auto type = shape::parse_type(get(v, "type", "float")); if(strides.empty()) return shape{type, lens}; else return shape{type, lens, strides}; } std::vector parser::parse_shapes(const value& v) const { std::vector result; std::transform( v.begin(), v.end(), std::back_inserter(result), [&](auto&& x) { return parse_shape(x); }); return result; } void parser::load_settings(const value& v) { if(v.contains("settings")) settings = v.at("settings"); } void parser::process(const value& v) { if(not v.is_object()) error("Input is not an object"); parser p{}; p.load_settings(v); for(auto&& pp : v) { if(pp.get_key() == "settings") continue; get_action(pp.get_key())(p, pp.without_key()); } } } // namespace driver } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/precompile_op.cpp000066400000000000000000000064001510465702400244270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace driver { struct precompile_op : action { static program create_preop_program(const operation& preop, std::vector inputs) { program p; auto* mm = p.get_main_module(); std::vector args; inputs.pop_back(); transform(inputs, range(inputs.size()), std::back_inserter(args), [&](auto input, auto i) { return mm->add_parameter("x" + std::to_string(i), input); }); mm->add_instruction(preop, args); return p; } static operation get_code_object(const program& p) { MIGRAPHX_TIDY_CONST auto* mm = p.get_main_module(); auto it = std::find_if(mm->begin(), mm->end(), [](const auto& ins) { return (ins.name() == "gpu::code_object"); }); if(it == mm->end()) MIGRAPHX_THROW("Failed to create code object"); return it->get_operator(); } static void apply(const parser& p, const value& v) { context ctx; auto inputs = p.parse_shapes(v.at("inputs")); auto name = v.at("name").to(); auto preop = make_op(name); if(v.contains("fields")) preop.from_value(v.at("fields")); bool exhaustive = v.get("exhaustive", false); auto prog = create_preop_program(preop, inputs); run_passes(prog, {lowering{}, compile_ops{&ctx, exhaustive}}); auto op = get_code_object(prog); auto t = time_op(ctx, op, inputs, p.get(v, "iterations", 100)); std::cout << preop << ": " << t << "ms" << std::endl; } }; } // namespace driver } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/driver/run_op.cpp000066400000000000000000000041001510465702400230670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace driver { struct run_op : action { static void apply(const parser& p, const value& v) { context ctx; auto inputs = p.parse_shapes(v.at("inputs")); auto name = v.at("name").to(); if(not contains(name, "::")) name = "gpu::" + name; auto op = make_op(name); if(v.contains("fields")) op.from_value(v.at("fields")); auto t = time_op(ctx, op, inputs, p.get(v, "iterations", 100)); std::cout << op << " -> " << op.compute_shape(inputs) << ": " << t << "ms" << std::endl; } }; } // namespace driver } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/fixed_pad.cpp000066400000000000000000000035751510465702400222340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_fixed_pad::compute_shape(std::vector inputs) const { inputs.pop_back(); check_shapes{inputs, *this, true}.has(1); return op.compute_shape(inputs); } argument hip_fixed_pad::compute(context& ctx, const shape&, const std::vector& args) const { if(args.front().get_shape() == args.back().get_shape()) return args.front(); return device::fixed_pad(ctx.get_stream().get(), args.back(), args.front()); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/fuse_ck.cpp000066400000000000000000000172721510465702400217270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { struct ck_gemm { operation op = make_op("dot"); template static auto reflect(Self& self, F f) { return pack(f(self.op, "op")); } std::string name() const { return "gpu::ck_gemm"; } void check_gemm_shape(const shape& s) const { if(not contains(range(s.strides().rbegin(), s.strides().rbegin() + 3), 1)) MIGRAPHX_THROW("Invalid shape for ck_gemm"); } shape compute_shape(std::vector inputs, const std::vector& mods) const { check_shapes{inputs, *this}.same_ndims(); if(inputs.size() < 2) MIGRAPHX_THROW(name() + ": should have at least two inputs."); auto a = inputs[0]; auto b = inputs[1]; for(const auto& input : inputs) check_gemm_shape(input); auto r = op.compute_shape({a, b}); if(mods.empty()) return r; return r.with_type(mods.front()->get_output_shapes().front().type()); } static bool is_ck_supported_type(shape::type_t t) { return contains({shape::half_type, shape::int8_type, shape::int32_type}, t); } }; MIGRAPHX_REGISTER_OP(ck_gemm); struct ck_gemm_softmax_gemm : gemm_softmax_gemm { std::string name() const { return "gpu::ck_gemm_softmax_gemm"; } }; MIGRAPHX_REGISTER_OP(ck_gemm_softmax_gemm); namespace { MIGRAPHX_PRED_MATCHER(is_ck_gemm, instruction_ref ins) { if(ins->name() != "dot" and ins->name() != "quant_dot") return false; if(not ck_gemm::is_ck_supported_type(ins->get_shape().type())) return false; auto a = ins->inputs().front()->get_shape(); auto b = ins->inputs().back()->get_shape(); auto m = a.lens()[a.lens().size() - 2]; auto n = b.lens().back(); auto k = a.lens().back(); auto batch_size = std::accumulate( a.lens().rbegin() + 2, a.lens().rend(), std::size_t{1}, std::multiplies()); // Integer gemms must be divisible by 4 in ck if(contains({shape::int8_type, shape::int32_type}, ins->get_shape().type())) { if(m % 4 != 0) return false; if(n % 4 != 0) return false; if(k % 4 != 0) return false; } auto device_name = trim(split_string(get_device_name(), ':').front()); if(starts_with(device_name, "gfx94")) { if(ins->get_shape().type() == shape::half_type) { if(batch_size >= 64) return m < 2048 or k <= 64 or n <= 384 or n >= 2048; return true; } return true; } return k <= 2048; } struct find_ck_gemm_pointwise { // Find a gemm followed by a pointwise operation. auto matcher() const { auto gemm = match::skip(match::name("contiguous"))( match::name("dot", "quant_dot")(is_ck_gemm().bind("gemm"))); return match::name("pointwise")(match::any_of[match::inputs()](gemm.bind("x"))); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto gemm_ins = r.instructions["gemm"]; auto x_ins = r.instructions["x"]; // input after contiguous auto* pm = ins->module_inputs().front(); auto names = pm->get_parameter_names(); std::sort(names.begin(), names.end()); auto inputs = ins->inputs(); auto gemm_it = std::find(inputs.begin(), inputs.end(), x_ins); auto gemm_idx = gemm_it - inputs.begin(); if(gemm_ins->get_shape().type() != shape::int32_type and ins->get_shape().type() != gemm_ins->get_shape().type()) return; if(std::any_of(ins->inputs().begin(), ins->inputs().end(), [](auto input) { return not ck_gemm::is_ck_supported_type(input->get_shape().type()); })) return; if(std::any_of(ins->inputs().begin(), ins->inputs().end(), [](auto input) { return not input->inputs().empty() and input->inputs().front()->name() == "capture"; })) return; if(std::any_of(ins->inputs().begin(), ins->inputs().end(), [](auto input) { return not input->inputs().empty() and input->inputs().front()->name() == "capture"; })) return; assert(gemm_it != inputs.end()); if(gemm_idx != 0) { auto first_param = pm->get_parameter(names[0]); auto gemm_param = pm->get_parameter(names[gemm_idx]); auto new_gemm_param = pm->add_parameter(names[0] + "_0", gemm_param->get_shape()); auto new_first_param = pm->add_parameter(names[gemm_idx] + "_0", first_param->get_shape()); pm->replace_instruction(gemm_param, new_gemm_param); pm->replace_instruction(first_param, new_first_param); pm->remove_instruction(first_param); pm->remove_instruction(gemm_param); } inputs.erase(gemm_it); inputs.insert(inputs.begin(), gemm_ins->inputs().begin(), gemm_ins->inputs().end()); mpm.get_module().replace_instruction(ins, ck_gemm{gemm_ins->get_operator()}, inputs, {pm}); } }; struct find_ck_gemm { auto matcher() const { return match::name("dot", "quant_dot")(is_ck_gemm().bind("gemm")); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; mpm.get_module().replace_instruction(ins, ck_gemm{ins->get_operator()}, ins->inputs()); } }; struct find_ck_gemm_softmax_gemm { auto matcher() const { return match::name("gpu::pre_gemm_softmax_gemm"); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto v = ins->get_operator().to_value(); assert(v.contains("scale")); auto scale = v.at("scale").to(); mpm.get_module().replace_instruction( ins, ck_gemm_softmax_gemm{migraphx::make_op("dot"), scale}, ins->inputs()); } }; } // namespace void fuse_ck::apply(module_pass_manager& mpm) const { match::find_matches(mpm, find_ck_gemm_softmax_gemm{}, find_ck_gemm_pointwise{}); match::find_matches(mpm, find_ck_gemm{}); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/fuse_mlir.cpp000066400000000000000000001611441510465702400222730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_EXTRA_MLIR); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_MLIR_INPUT_FUSION); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_MLIR_REDUCE_FUSION); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_MLIR_GEG_FUSION); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_MLIR); /** * @brief Declares a new MIGraphX environment variable which forces to generate * only specific MLIR operations. * * The variable, if defined, forces MIGraphX to use only specific operations * with MLIR regardless of the underlying GPU architecture. The variable accepts * a list of operations separated by comma. The variable recognizes the following * operations: "fused", "convolution", "dot". If the variable is not defined MIGraphX * will decide by itself which operations to delegate to MLIR. The variable is * intended to be primarily used by rocMLIR developers. */ MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_USE_SPECIFIC_OPS); bool mlir_enabled() { #ifdef MIGRAPHX_MLIR const bool mlir_disabled = enabled(MIGRAPHX_DISABLE_MLIR{}); return not mlir_disabled; #else return false; #endif } namespace { struct requested { }; struct rejected { }; } // namespace static bool is_negated_op(const std::string& s) { if(s.empty()) return false; return contains({'!', '~'}, s[0]); } template static std::vector get_usage() { static const auto options = split_string(string_value_of(MIGRAPHX_MLIR_USE_SPECIFIC_OPS{}, ""), ','); static const bool enabled = std::is_same{}; std::vector result; auto remove_not_symbol = [&](const std::string& s) { if(is_negated_op(s)) return s.substr(1); return s; }; transform_if( options.begin(), options.end(), std::back_inserter(result), [&](const std::string& option) { if(option.empty()) return false; if(is_negated_op(option)) return not enabled; return enabled; }, remove_not_symbol); return result; } template static bool specific_op(std::string_view option, bool fallback = false) { static const auto options = get_usage(); if(options.empty()) return fallback; if(contains(option, "fused") and contains(options, "fused")) return true; return contains(options, option); } bool mlir_attention_enabled(context* ctx) { #ifdef MIGRAPHX_MLIR if(not mlir_enabled()) return false; if(specific_op("attention")) return false; // Enable attention by default for mi300 if(ctx != nullptr and starts_with(ctx->get_current_device().get_gfx_name(), "gfx94")) return true; return specific_op("attention"); #else return false; #endif } #ifdef MIGRAPHX_MLIR struct mlir_op { std::string name() const { return "gpu::mlir_op"; } operation op = make_op("convolution"); template static auto reflect(Self& self, F f) { return pack(f(self.op, "op")); } // Check if the shape can be created from a transpose/broadcast/slice static bool is_mlir_compatible(const shape& s) { if(s.standard() or s.packed() or s.scalar() or s.ndim() == 1) return true; auto ns = reorder_shape(s, find_permutation(s)); std::vector stride_ratios; auto last = std::find(ns.strides().begin(), ns.strides().end(), 0); if(*std::prev(last) != 1) return false; std::adjacent_difference(ns.strides().begin(), last, std::back_inserter(stride_ratios), [](auto y, auto x) -> std::size_t { assert(y != 0); if((x % y) != 0) return 0; return x / y; }); return std::equal(stride_ratios.begin() + 1, stride_ratios.end(), ns.lens().begin() + 1, [](auto ratio, auto len) { return ratio >= len; }); } shape compute_shape(const std::vector& inputs, const std::vector& mods) const { module_ref mod = mods[0]; check_shapes{inputs, *this}.has_at_least(1); if(mods.size() != 1) MIGRAPHX_THROW("should have one submodule."); if(not std::all_of(inputs.begin(), inputs.end(), &is_mlir_compatible)) MIGRAPHX_THROW("Shape is not mlir compatible."); auto result = mod->compute_shapes(inputs, {.name = name(), .strict_type = true, .strict_lens = true}); if(result.size() == 1) return result.front(); return shape{result}; } }; MIGRAPHX_REGISTER_OP(mlir_op); namespace { const auto& reshaper_names() { // clang-format off static const std::unordered_set names = { "transpose", "multibroadcast", "broadcast", "contiguous", "reshape", "reshape_lazy", "squeeze", "flatten", "unsqueeze" }; // clang-format on return names; } bool is_fusable_input_op(const std::string& name) { return contains(reshaper_names(), name) or contains({"slice"}, name); } std::tuple> get_fusable_input_op_stream(instruction_ref lower_input) { instruction_ref upper_input = lower_input; std::vector op_stream; while(is_fusable_input_op(upper_input->name())) { operation op = upper_input->get_operator(); op_stream.push_back(op); upper_input = upper_input->inputs().at(0); } return {upper_input, op_stream}; } void fuse_input_ops(module_ref mm, const std::vector& inputs, std::unordered_map* map_ins) { assert(map_ins != nullptr); size_t input_cnt = mm->get_parameters().size(); for(instruction_ref input : inputs) { if(contains(*map_ins, input)) continue; auto [upper_input, op_stream] = get_fusable_input_op_stream(input); if(not contains(*map_ins, upper_input)) (*map_ins)[upper_input] = mm->add_parameter(param_name(input_cnt++), upper_input->get_shape().as_standard()); instruction_ref prev_input = (*map_ins)[upper_input]; for(const auto& op : reverse(op_stream)) { prev_input = mm->add_instruction(op, {prev_input}); } (*map_ins)[input] = prev_input; } } std::tuple> fuse_input_ops_and_gemm_based_op(module_ref mm, const std::vector& gemm_based_op_inputs, const operation& gemm_based_op) { std::vector top_inputs; std::vector imm_inputs; size_t input_cnt = 0; for(instruction_ref input : gemm_based_op_inputs) { auto [upper_input, op_stream] = get_fusable_input_op_stream(input); top_inputs.push_back(upper_input); instruction_ref prev_input = mm->add_parameter(param_name(input_cnt++, "y"), upper_input->get_shape().as_standard()); for(const auto& op : reverse(op_stream)) { prev_input = mm->add_instruction(op, {prev_input}); } imm_inputs.push_back(prev_input); } instruction_ref new_gemm_based_op = mm->add_instruction(gemm_based_op, imm_inputs); return {new_gemm_based_op, top_inputs}; } enum class mlir_mode { all, fast, int8, none }; auto is_mlir_dot(mlir_mode mode) { return match::make_basic_pred_matcher([=](instruction_ref ins) { if(mode == mlir_mode::none) return false; if(ins->name() != "dot" and ins->name() != "quant_dot") return false; // dot operation where (FP8 * FP8 = FP8) is not available in MLIR. rocBLAS/hipBLASLt should // have the support for it. if(contains(fp8_types{}.get(), ins->get_shape().type())) return false; // MX types quantization has 4 inputs // having all MX GEMM go to MLIR if(ins->inputs().size() == 4) { return true; } if(mode != mlir_mode::fast) return true; auto a = ins->inputs().front()->get_shape(); auto b = ins->inputs().back()->get_shape(); auto g = std::accumulate(a.lens().begin(), a.lens().end() - 2, 1, std::multiplies<>{}); auto m = a.lens()[a.lens().size() - 2]; auto n = b.lens().back(); auto k = a.lens().back(); // Skipping GEMMs with a K dimension greater than 2048 is a course-grained strategy // to avoid poor-performing GEMM kernels from MLIR // TODO: Investigate a more precise strategy if(k > 1535) return false; if(k < 1024) return true; return (g * m * n) < (384 * 384); }); } auto is_mlir_conv(mlir_mode mode) { return match::make_basic_pred_matcher([=](instruction_ref ins) { if(mode == mlir_mode::none) return false; if(ins->name() != "convolution" and ins->name() != "quant_convolution") return false; auto input = ins->inputs().front()->get_shape(); value v = ins->get_operator().to_value(); auto group = v.at("group").to(); // Avoid MLIR assertion: Index < Length && "Invalid index!" if(ins->get_shape().lens().size() != 4 and group > 1) return false; std::set supported_types = fp8_types{}.get(); supported_types.insert(shape::int8_type); if(contains(supported_types, input.type())) return true; if(mode == mlir_mode::all) return true; // No winograd for group convolution if(group > 1) return true; auto w = ins->inputs().at(1)->get_shape(); if(w.lens().size() != 4) return true; if(w.lens()[2] != w.lens()[3]) return true; return (w.lens()[3] % 3) != 0; }); } auto is_mlir_conv_backwards(mlir_mode mode) { return match::make_basic_pred_matcher([=](instruction_ref ins) { if(mode == mlir_mode::none) return false; if(ins->name() != "convolution_backwards") return false; auto input = ins->inputs().front()->get_shape(); if(not contains( {shape::type_t::float_type, shape::type_t::half_type, shape::type_t::bf16_type}, input.type())) return false; auto w = ins->inputs().at(1)->get_shape(); // currently handle on 2D conv_backwards in MLIR if(w.lens().size() != 4) return false; value v = ins->get_operator().to_value(); auto group = v.at("group").to(); // currently handle only group == 1 return (group == 1); }); } std::unordered_map create_param_map_with_literals(module_ref mm, const module* pm, const shape& shape) { std::unordered_map ins_map; for(auto ins : iterator_for(*pm)) { if(ins->name() != "@literal") { continue; } literal r = ins->get_literal(); instruction_ref literal = mm->add_literal(r); instruction_ref mbcast = mm->add_instruction(make_op("multibroadcast", {{"out_lens", shape.lens()}}), literal); ins_map[ins] = mbcast; } return ins_map; } instruction_ref insert_pointwise(module& m, instruction_ref ins, const operation& op, const std::vector& inputs, const std::vector& mod_args) { // Only used in assert (void)mod_args; assert(mod_args.empty()); return insert_common_op(m, ins, op, inputs, {.common_type = false}); } instruction_ref unroll_pointwise(module& main_mod, instruction_ref pos, const operation& op, const std::vector& inputs, const std::vector& mod_args) { if(op.name() == "pointwise") { auto* sub_pm = mod_args.front(); auto param_map_2 = create_param_map_with_literals( &main_mod, sub_pm, op.compute_shape(to_shapes(inputs), mod_args)); return main_mod.insert_inline(pos, *sub_pm, inputs, ¶m_map_2) .front(); // cppcheck-suppress returnDanglingLifetime; } return main_mod.insert_instruction(pos, op, inputs, mod_args); } // Whitelist supported fusion options, including imposing type constraints // for cases where MLIR only supports an operation (usually a pointwise function) // on particular types. bool is_pointwise_op_supported_by_mlir(const instruction& i) { using type_t = shape::type_t; const auto& name = i.name(); const auto result_type = i.get_shape().type(); const std::initializer_list allowed_types = {type_t::float_type, type_t::bf16_type, type_t::half_type, type_t::fp8e4m3fnuz_type, type_t::fp8e5m2fnuz_type, type_t::fp8e4m3fn_type, type_t::fp8e5m2_type, type_t::int8_type, type_t::uint8_type, type_t::int32_type, type_t::uint32_type, type_t::bool_type}; // Preliminary type check. if(not contains(allowed_types, result_type)) { return false; } const std::initializer_list any_type_ops = {"@literal", "@param", "@return"}; const std::initializer_list no_bool_ops = { "convolution", "quant_convolution", "dot", "quant_dot", "add", "clip", "relu", "sub", "mul", "div", "pow", "where", "quantizelinear", "dequantizelinear", "abs", "neg", }; const std::initializer_list fp_only_ops = { "ceil", "erf", "exp", "floor", "log", "recip", "sqrt", "rsqrt", "sigmoid", "softmax", "tanh", }; std::set float_types = {type_t::float_type, type_t::half_type, type_t::bf16_type, type_t::fp8e4m3fnuz_type, type_t::fp8e5m2fnuz_type, type_t::fp8e4m3fn_type, type_t::fp8e5m2_type}; bool is_float = contains(float_types, result_type); if(contains(any_type_ops, name)) return true; if(result_type != type_t::bool_type and contains(no_bool_ops, name)) return true; if(is_float and contains(fp_only_ops, name)) return true; // Only conversions between floating types are known to be unambigiously // supported. if(is_float and name == "convert") { if(contains(fp8_types{}.get(), result_type)) { return false; } // else return std::all_of(i.inputs().begin(), i.inputs().end(), [](const auto& arg) { return contains({type_t::float_type, type_t::half_type, type_t::bf16_type}, arg->get_shape().type()); }); } return false; } bool is_reduce_op_supported_by_mlir(const instruction& i) { using type_t = shape::type_t; const auto& name = i.name(); const auto result_type = i.get_shape().type(); const std::initializer_list allowed_types = {type_t::float_type, type_t::half_type, type_t::bf16_type, type_t::fp8e4m3fnuz_type, type_t::fp8e5m2fnuz_type, type_t::fp8e4m3fn_type, type_t::fp8e5m2_type}; // Preliminary type check. if(not contains(allowed_types, result_type)) { return false; } const std::initializer_list reduce_ops = {"reduce_mean", "reduce_sum"}; return contains(reduce_ops, i.name()); } // A separate function so we can remove operators that are supported by mlir // but not supported for an input fusion. bool is_pointwise_op_supported_by_mlir_for_input(const instruction& i) { return is_pointwise_op_supported_by_mlir(i); } MIGRAPHX_PRED_MATCHER(mlir_split_reduce, instruction_ref ins) { if(ins->name() != "split_fused_reduce") return false; auto* mod_arg = ins->module_inputs().front(); std::unordered_set builtins = {"@param", "@literal", "@return"}; for(const auto i : iterator_for(*mod_arg)) { if(is_reduce(*i)) { if(not is_reduce_op_supported_by_mlir(*i)) return false; } else if(i->name() == "pointwise") { if(not std::all_of(i->module_inputs().front()->begin(), i->module_inputs().front()->end(), &is_pointwise_op_supported_by_mlir)) return false; } else if(not contains(reshaper_names(), i->name()) and not contains(builtins, i->name())) { return false; } } return true; } MIGRAPHX_PRED_MATCHER(mlir_pointwise, instruction_ref ins) { if(ins->name() != "pointwise") return false; auto* pm = ins->module_inputs().front(); return std::all_of(pm->begin(), pm->end(), &is_pointwise_op_supported_by_mlir); } MIGRAPHX_PRED_MATCHER(mlir_input_pointwise, instruction_ref ins) { if(ins->name() != "pointwise") return false; auto* pm = ins->module_inputs().front(); return std::all_of(pm->begin(), pm->end(), &is_pointwise_op_supported_by_mlir_for_input); } std::vector mlir_contiguous(module_pass_manager& mpm, const std::vector& inputs) { std::vector result; std::transform( inputs.begin(), inputs.end(), std::back_inserter(result), [&](instruction_ref input) { if(input->get_shape().packed() or input->get_shape().broadcasted()) return input; return mpm.get_module().insert_instruction( std::next(input), make_op("contiguous"), input); }); return result; } struct find_mlir_split_reduce { mlir_mode conv_mode = mlir_mode::none; mlir_mode dot_mode = mlir_mode::none; auto matcher() const { auto dot_or_conv = match::name("gpu::mlir_op"); // TODO: Handle reshapes inbetween return mlir_split_reduce()(match::any_of[match::inputs()](dot_or_conv.bind("gemm"))); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto reduce_ins = r.result; auto gemm_ins = r.instructions["gemm"]; assert(gemm_ins->get_shape().sub_shapes().empty()); auto* rm = reduce_ins->module_inputs().front(); auto names = rm->get_parameter_names(); std::sort(names.begin(), names.end()); module_ref gemm_old_mm = gemm_ins->module_inputs().front(); module_ref mm = mpm.create_module(gemm_old_mm->name() + "_" + rm->name(), *gemm_old_mm); // remove last return instruction if(std::prev(mm->end())->name() == "@return") { mm->remove_instruction(std::prev(mm->end())); } mm->set_bypass(); std::unordered_map param_map; param_map[gemm_ins] = std::prev(mm->end()); bool gemm_has_multi_outs = gemm_ins->outputs().size() > 1; auto return_vals = mm->fuse(*rm, reduce_ins->inputs(), ¶m_map, &unroll_pointwise); if(gemm_has_multi_outs) { return_vals.insert(return_vals.end(), param_map[gemm_ins]); } mm->add_return(return_vals); std::vector inputs; std::copy_if(reduce_ins->inputs().begin(), reduce_ins->inputs().end(), std::back_inserter(inputs), [&](auto input) { return input != gemm_ins; }); inputs.insert(inputs.end(), gemm_ins->inputs().begin(), gemm_ins->inputs().end()); if(gemm_has_multi_outs) { auto fused_ins = mpm.get_module().insert_instruction( reduce_ins, mlir_op{gemm_ins->get_operator()}, mlir_contiguous(mpm, inputs), {mm}); auto dot_ins = mpm.get_module().insert_instruction( reduce_ins, migraphx::make_op("get_tuple_elem", {{"index", return_vals.size() - 1}}), fused_ins); mpm.get_module().replace_instruction(gemm_ins, dot_ins); for(const auto& outs : reduce_ins->outputs()) { assert(outs->get_operator().name() == "get_tuple_elem"); mpm.get_module().replace_instruction(outs, outs->get_operator(), fused_ins); } } else { mpm.get_module().replace_instruction( reduce_ins, mlir_op{gemm_ins->get_operator()}, mlir_contiguous(mpm, inputs), {mm}); } } }; /** * Fuses rocMLIR compatible dot or conv op -> reshapes -> pointwise * into a mlir_op with submodule. */ struct find_mlir_fused_ops { mlir_mode conv_mode = mlir_mode::none; mlir_mode dot_mode = mlir_mode::none; static auto make_conv_dot_reshaper_names() { auto names = reshaper_names(); names.erase("broadcast"); names.erase("multibroadcast"); return names; } /** * Matches: * mlir_dot_or_conv -> * skip(conv_dot_reshaper_names) -> * mlir_pointwise */ auto matcher() const { static const auto conv_dot_reshaper_names = make_conv_dot_reshaper_names(); auto dot_or_conv = match::skip(match::name(conv_dot_reshaper_names))( match::any_of(is_mlir_dot(dot_mode), is_mlir_conv(conv_mode)).bind("gemm_based_op")); return mlir_pointwise()(match::any_of[match::inputs()](dot_or_conv.bind("x"))); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto pw_ins = r.result; auto gemm_based_op = r.instructions["gemm_based_op"]; auto x_ins = r.instructions["x"]; // input to pointwise after reshaper op stream auto* pm = pw_ins->module_inputs().front(); auto pw_inputs = pw_ins->inputs(); // only of one of the inputs to pointwise module should be dependent on conv/gemm that is // being fused, otherwise it can create invalid graph transformation if(std::any_of(pw_inputs.begin(), pw_inputs.end(), [&](const auto& i) { return i != x_ins and reaches(gemm_based_op, i); })) return; std::unordered_map map_ins; module_ref mm = mpm.create_module("mlir_" + pm->name()); mm->set_bypass(); fuse_input_ops(mm, gemm_based_op->inputs(), &map_ins); bool gemm_has_multi_outs = gemm_based_op->outputs().size() > 1; std::vector inss_to_insert; auto reshape_ins = x_ins; for(; reshape_ins != gemm_based_op; reshape_ins = reshape_ins->inputs().front()) { inss_to_insert.push_back(reshape_ins); gemm_has_multi_outs |= reshape_ins->outputs().size() > 1; } inss_to_insert.push_back(gemm_based_op); std::reverse(inss_to_insert.begin(), inss_to_insert.end()); mm->add_instructions(inss_to_insert, &map_ins); fuse_input_ops(mm, pw_ins->inputs(), &map_ins); auto rins = mm->fuse(*pm, pw_ins->inputs(), &map_ins, &insert_pointwise); if(gemm_has_multi_outs) { rins.push_back(map_ins.at(gemm_based_op)); } mm->add_return(rins); auto inputs = find_inputs(map_ins, &mpm.get_module(), mm); auto fused_ins = mpm.get_module().insert_instruction( pw_ins, mlir_op{gemm_based_op->get_operator()}, mlir_contiguous(mpm, inputs), {mm}); if(gemm_has_multi_outs) { auto dot_ins = mpm.get_module().insert_instruction( pw_ins, migraphx::make_op("get_tuple_elem", {{"index", rins.size() - 1}}), fused_ins); // move all the reshape instructions after the fused op to avoid // generating invalid migraphx program since the reshapes can be // used by the replaced dot_ins for(instruction_ref x : inss_to_insert) { if(x == gemm_based_op) continue; mpm.get_module().move_instruction(x, pw_ins); } mpm.get_module().replace_instruction(gemm_based_op, dot_ins); if(rins.size() == 2) { mpm.get_module().replace_instruction( pw_ins, migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused_ins); } } else { mpm.get_module().replace_instruction(pw_ins, fused_ins); } } }; /** * Fuses rocMLIR conv/dot -> pointwise -> dot chain * into a mlir_op with submodule. */ struct find_mlir_fused_geg_ops { mlir_mode conv_mode = mlir_mode::none; mlir_mode dot_mode = mlir_mode::none; /* * Matches: * mlir_dot_or_conv -> * pointwise -> * dot */ auto matcher() const { auto first_dot_or_conv = match::any_of(is_mlir_dot(dot_mode), is_mlir_conv(conv_mode)) .bind("first_gemm_based_op"); auto elemwise = mlir_pointwise()(match::any_of[match::inputs()](first_dot_or_conv)).bind("elemwise"); return is_mlir_dot(dot_mode)(match::any_of[match::inputs()](elemwise)) .bind("second_gemm_op"); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto second_gemm_ins = r.result; auto elemwise_ins = r.instructions["elemwise"]; auto first_gemm_ins = r.instructions["first_gemm_based_op"]; auto* elemwise_module = elemwise_ins->module_inputs().front(); auto elemwise_inputs = elemwise_ins->inputs(); // only one input to elemwise should depend on first_gemm if(std::any_of(elemwise_inputs.begin(), elemwise_inputs.end(), [&](const auto& i) { return i != first_gemm_ins and reaches(first_gemm_ins, i); })) return; // only one input to second_gemm should depend on elemwise auto second_gemm_inputs = second_gemm_ins->inputs(); if(std::any_of(second_gemm_inputs.begin(), second_gemm_inputs.end(), [&](const auto& i) { return i != elemwise_ins and reaches(elemwise_ins, i); })) return; std::unordered_map map_ins; module_ref mm = mpm.create_module("mlir_" + elemwise_ins->module_inputs().front()->name() + "_geg"); mm->set_bypass(); fuse_input_ops(mm, first_gemm_ins->inputs(), &map_ins); // need to track multi-user scenarios for both intermediates bool first_gemm_has_multi_outs = first_gemm_ins->outputs().size() > 1; bool elemwise_has_multi_outs = elemwise_ins->outputs().size() > 1; // add the first gemm to the module std::vector first_gemm_mapped_inputs; first_gemm_mapped_inputs.reserve(first_gemm_ins->inputs().size()); std::transform(first_gemm_ins->inputs().begin(), first_gemm_ins->inputs().end(), std::back_inserter(first_gemm_mapped_inputs), [&](auto input) { return map_ins.at(input); }); auto first_gemm_in_module = mm->add_instruction(first_gemm_ins->get_operator(), first_gemm_mapped_inputs); map_ins[first_gemm_ins] = first_gemm_in_module; // fuse external inputs for the elemwise operation fuse_input_ops(mm, elemwise_inputs, &map_ins); // fuse elemwise submodule auto elemwise_rins = mm->fuse(*elemwise_module, elemwise_inputs, &map_ins, &insert_pointwise); assert(elemwise_rins.size() == 1); map_ins[elemwise_ins] = elemwise_rins.front(); // fuse external inputs for the second gemm fuse_input_ops(mm, second_gemm_inputs, &map_ins); // add the second gemm to the new module std::vector second_gemm_mapped_inputs; second_gemm_mapped_inputs.reserve(second_gemm_inputs.size()); std::transform(second_gemm_inputs.begin(), second_gemm_inputs.end(), std::back_inserter(second_gemm_mapped_inputs), [&](auto input) { return map_ins.at(input); }); auto second_gemm_in_module = mm->add_instruction(second_gemm_ins->get_operator(), second_gemm_mapped_inputs); map_ins[second_gemm_ins] = second_gemm_in_module; // primary output is the last gemm, which should be the first output std::vector return_vals; return_vals.push_back(second_gemm_in_module); if(elemwise_has_multi_outs) { return_vals.push_back(map_ins[elemwise_ins]); } if(first_gemm_has_multi_outs) { return_vals.push_back(map_ins[first_gemm_ins]); } mm->add_return(return_vals); auto inputs = find_inputs(map_ins, &mpm.get_module(), mm); // sort fusion section of module such that any external inputs are moved before the fusion // so that we can safely place the fused mod in the multi-out case at the beginning of the // chain mpm.get_module().localized_sort(first_gemm_ins, second_gemm_ins); auto fused_ins = mpm.get_module().insert_instruction(first_gemm_ins, mlir_op{second_gemm_ins->get_operator()}, mlir_contiguous(mpm, inputs), {mm}); if(first_gemm_has_multi_outs or elemwise_has_multi_outs) { std::size_t output_idx = 0; if(elemwise_has_multi_outs) { auto elemwise_result = mpm.get_module().insert_instruction( first_gemm_ins, migraphx::make_op("get_tuple_elem", {{"index", ++output_idx}}), fused_ins); mpm.get_module().replace_instruction(elemwise_ins, elemwise_result); } if(first_gemm_has_multi_outs) { mpm.get_module().replace_instruction( first_gemm_ins, migraphx::make_op("get_tuple_elem", {{"index", ++output_idx}}), fused_ins); } mpm.get_module().replace_instruction( second_gemm_ins, migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused_ins); } else { // simple single output case mpm.get_module().replace_instruction(second_gemm_ins, fused_ins); } } }; template struct find_mlir_standalone_op { mlir_mode mode = mlir_mode::none; std::size_t* counter = nullptr; auto matcher() const { return Matcher(mode); } std::string get_count() const { if(counter == nullptr) MIGRAPHX_THROW("Invalid counter"); return std::to_string((*counter)++); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto gemm_based_op = r.result; // enable only for fp32/fp16/i8/fp8 types if(std::any_of(gemm_based_op->inputs().begin(), gemm_based_op->inputs().end(), [&](auto i) { return not contains({shape::type_t::float_type, shape::type_t::half_type, shape::type_t::bf16_type, shape::type_t::int8_type, shape::type_t::fp8e4m3fnuz_type, shape::type_t::fp8e5m2fnuz_type, shape::type_t::fp8e4m3fn_type, shape::type_t::fp8e5m2_type}, i->get_shape().type()); })) return; std::string module_name = "mlir_" + gemm_based_op->name() + get_count(); if(mpm.get_module().name() != "main") module_name = mpm.get_module().name() + ":" + module_name; module_ref mm = mpm.create_module(module_name); mm->set_bypass(); auto [anchor_op, top_inputs] = fuse_input_ops_and_gemm_based_op( mm, gemm_based_op->inputs(), gemm_based_op->get_operator()); mm->add_return({anchor_op}); mpm.get_module().replace_instruction(gemm_based_op, mlir_op{gemm_based_op->get_operator()}, mlir_contiguous(mpm, top_inputs), {mm}); } }; using find_mlir_standalone_conv_backwards_op = find_mlir_standalone_op<&is_mlir_conv_backwards>; using find_mlir_standalone_conv_op = find_mlir_standalone_op<&is_mlir_conv>; using find_mlir_standalone_dot_op = find_mlir_standalone_op<&is_mlir_dot>; struct find_mlir_attention_op { auto matcher() const { return match::name("group")(match::has_op_value("tag", "attention")).bind("group"); } std::unordered_map invert_map_ins(const std::unordered_map& map_ins) const { std::unordered_map inverse_map; for(auto const& [key, value] : map_ins) { assert(not contains(inverse_map, value)); inverse_map[value] = key; } return inverse_map; } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto group = r.instructions["group"]; auto* group_mod = group->module_inputs().front(); std::string module_name = "mlir_" + group_mod->name(); module_ref mlir_attn = mpm.create_module(module_name); mlir_attn->set_bypass(); // Fuse any input reshapes std::unordered_map map_main_to_mlir_attn; fuse_input_ops(mlir_attn, group->inputs(), &map_main_to_mlir_attn); std::unordered_map map_group_mod_to_mlir_attn( map_main_to_mlir_attn); auto attn_outs = mlir_attn->fuse(*group_mod, group->inputs(), &map_group_mod_to_mlir_attn); std::unordered_map inss_to_replace; // Only fuse attention outputs that are used once by pointwise ops if(group->outputs().size() == 1 or attn_outs.size() > 1) { std::size_t out_idx = 0; auto attn_output_ins = group; for(auto out : group->outputs()) { auto op = out->get_operator(); if(op.name() == "get_tuple_elem") { inss_to_replace[out] = out; out_idx = op.to_value()["index"].to(); auto tuple_elem_users = out->outputs(); if(tuple_elem_users.size() > 1) continue; attn_output_ins = out; out = tuple_elem_users.front(); } auto match_pw = match::match_instruction(mpm.get_module(), out, mlir_pointwise()); if(match_pw.result != out) continue; map_main_to_mlir_attn[attn_output_ins] = attn_outs[out_idx]; inss_to_replace[attn_output_ins] = out; auto lit_map = create_param_map_with_literals( mlir_attn, out->module_inputs().front(), out->get_shape()); mlir_attn->add_params(out->inputs(), &map_main_to_mlir_attn); map_main_to_mlir_attn.insert(lit_map.begin(), lit_map.end()); std::unordered_map map_pm_to_mlir_attn( map_main_to_mlir_attn); auto fused_pw_outs = mlir_attn->fuse( *out->module_inputs().front(), out->inputs(), &map_pm_to_mlir_attn); assert(fused_pw_outs.size() == 1); map_main_to_mlir_attn[out] = fused_pw_outs.front(); attn_outs[out_idx] = fused_pw_outs.front(); } } mlir_attn->add_return(attn_outs); auto map_mlir_attn_to_main = invert_map_ins(map_main_to_mlir_attn); auto new_inputs = mlir_attn->get_inputs(map_mlir_attn_to_main); auto mlir_ins = mpm.get_module().insert_instruction( group, mlir_op{make_op("dot")}, mlir_contiguous(mpm, new_inputs), {mlir_attn}); if(inss_to_replace.empty()) { mpm.get_module().replace_instruction(group, mlir_ins); } else { for(auto const& [attn_out, fused_user] : inss_to_replace) { auto replace_ins = mlir_ins; auto output_op = attn_out->get_operator(); if(output_op.name() == "get_tuple_elem") replace_ins = mpm.get_module().insert_instruction(group, output_op, {mlir_ins}); mpm.get_module().replace_instruction(fused_user, replace_ins); } } } }; /** * Input fusion of pointwise operators into a mlir_op. * Only fuses unary pointwise operators by default. * Fuses all fusable pw ops with MIGRAPHX_ENABLE_MLIR_INPUT_FUSION */ struct find_pointwise_mlir { auto supported_pointwise() const { return mlir_input_pointwise(match::used_once()); } auto matcher() const { return match::name("gpu::mlir_op")(match::any_of[match::inputs()](supported_pointwise())); } static bool is_simple_op(const_module_ref pm, std::initializer_list op_names) { auto last = std::prev(pm->end()); assert(last->name() == "@return"); if(last->inputs().size() != 1) return false; auto rins = last->inputs().front(); auto op_ins = std::find_if(pm->begin(), pm->end(), [](const instruction& x) { return not contains({"@param", "@literal", "broadcast", "multibroadcast"}, x.name()); }); if(op_ins != rins) return false; return contains(op_names, op_ins->name()); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto* mm = ins->module_inputs().front(); std::vector pws; std::copy_if( ins->inputs().begin(), ins->inputs().end(), std::back_inserter(pws), [&](instruction_ref input) { if(not match::instruction_matches(mpm.get_module(), input, supported_pointwise())) return false; auto* pm = input->module_inputs().front(); if(input->inputs().size() > 1 and not is_simple_op(pm, {"dequantizelinear"})) { if(not enabled(MIGRAPHX_ENABLE_MLIR_INPUT_FUSION{})) return false; } return true; }); if(pws.empty()) return; std::string module_name; std::transform( pws.begin(), pws.end(), join_back_inserter(module_name), [](instruction_ref pw) { return pw->module_inputs().front()->name() + ":"; }); module_name += mm->name(); module_ref m = mpm.create_module(module_name); m->set_bypass(); std::unordered_map map_ins; for(auto pw : pws) { auto* pm = pw->module_inputs().front(); fuse_input_ops(m, pw->inputs(), &map_ins); auto rins = m->fuse(*pm, pw->inputs(), &map_ins, &insert_pointwise).front(); map_ins[pw] = rins; } auto ret = m->fuse(*mm, ins->inputs(), &map_ins); m->add_return({ret}); auto inputs = find_inputs(map_ins, &mpm.get_module(), m); mpm.get_module().replace_instruction( ins, ins->get_operator(), mlir_contiguous(mpm, inputs), {m}); } }; struct find_unpack_int4_mlir_op { auto matcher() const { return match::name("gpu::mlir_op")( match::any_of[match::inputs()](match::name("unpack_int4").bind("unpack_int4"))); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto* mm = ins->module_inputs().front(); module_ref nm = mpm.create_module("int4:" + mm->name()); nm->set_bypass(); std::vector x_in; std::unordered_map map_ins; int ct = 0; for(auto input : ins->inputs()) { if(input->get_operator().name() == "unpack_int4") { auto unpack_input = input->inputs()[0]; instruction_ref t_ins = nm->add_parameter(param_name(++ct), unpack_input->get_shape().as_standard()); map_ins[input] = nm->add_instruction(input->get_operator(), t_ins); x_in.push_back(unpack_input); } else { map_ins[input] = nm->add_parameter(param_name(++ct), input->get_shape().as_standard()); x_in.push_back(input); } } auto ret = nm->fuse(*mm, ins->inputs(), &map_ins); nm->add_return({ret}); mpm.get_module().replace_instruction(ins, ins->get_operator(), x_in, {nm}); } }; /** * Move unpack_fp4 instructions into the mlir_op submodule. * Slice and reshape instructions should already be fused into the mlir_op. * rocMLIR will do the unpacking and dequantization. */ struct find_unpack_fp4_mlir_op { auto matcher() const { return match::name("gpu::mlir_op")( match::any_of[match::inputs()](match::name("unpack_fp4"))); } void apply(module_pass_manager& mpm, const match::matcher_result& mr) const { auto mlir_op = mr.result; auto* mm = mlir_op->module_inputs().front(); module_ref nm = mpm.create_module("fp4:" + mm->name()); nm->set_bypass(); std::vector new_mlir_op_args; std::unordered_map fuse_ins_map; int ct = 0; for(auto curr_ins : mlir_op->inputs()) { if(curr_ins->name() == "unpack_fp4") { auto unpack_ins = curr_ins; auto unpack_input = unpack_ins->inputs().at(0); auto param = nm->add_parameter(param_name(++ct), unpack_input->get_shape().as_standard()); auto new_unpack_ins = nm->add_instruction(unpack_ins->get_operator(), param); fuse_ins_map[unpack_ins] = new_unpack_ins; new_mlir_op_args.push_back(unpack_input); } else { fuse_ins_map[curr_ins] = nm->add_parameter(param_name(++ct), curr_ins->get_shape().as_standard()); new_mlir_op_args.push_back(curr_ins); } } auto ret = nm->fuse(*mm, mlir_op->inputs(), &fuse_ins_map); nm->add_return({ret}); mpm.get_module().replace_instruction( mlir_op, mlir_op->get_operator(), new_mlir_op_args, {nm}); } }; /** * Fuse single output reshape instructions on the output of mlir_op into the * mlir_op. */ struct find_mlir_output_reshape_ops { auto matcher() const { static const std::unordered_set output_reshapes = {"transpose", "contiguous", "reshape", "reshape_lazy", "squeeze", "flatten", "unsqueeze"}; auto atleast_one_reshape = match::all_of(match::output(match::name(output_reshapes)), match::skip_output(match::name(output_reshapes).bind("last_reshape"))); return match::name("gpu::mlir_op")(atleast_one_reshape); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto mlir_op_ins = r.result; auto last_reshape = r.instructions["last_reshape"]; auto* mlir_op_module = mlir_op_ins->module_inputs().front(); std::vector reshape_instructions; instruction_ref iter_ins = mlir_op_ins; while(iter_ins != last_reshape) { // should already be covered by skip_output assert(iter_ins->outputs().size() == 1); auto output_ins = iter_ins->outputs().front(); reshape_instructions.push_back(output_ins); iter_ins = output_ins; } assert(not reshape_instructions.empty()); std::string module_name = mlir_op_module->name(); std::transform(reshape_instructions.begin(), reshape_instructions.end(), join_back_inserter(module_name), [](instruction_ref ins) { return "_" + ins->name(); }); module_ref fused_module = mpm.create_module(module_name); fused_module->set_bypass(); std::unordered_map map_ins; auto new_back = fused_module->fuse(*mlir_op_module, mlir_op_ins->inputs(), &map_ins).back(); map_ins[mlir_op_ins] = new_back; auto fused_instructions = fused_module->fuse(reshape_instructions, &map_ins); fused_module->add_return({fused_instructions.back()}); auto inputs = find_inputs(map_ins, &mpm.get_module(), fused_module); mpm.get_module().replace_instruction( last_reshape, mlir_op{last_reshape->get_operator()}, inputs, {fused_module}); } }; /** * Find slices along the channels axis that go into a convolution. * Reshape input instructions to the slices such that the slice occurs over * the slowest dimension. * TODO: This can also be done for GEMM when NCHW is supported for it. */ struct find_channel_slice_convolution { auto matcher() const { return match::name("convolution")(match::arg(0)(match::name("slice").bind("slice"))); } /** * Number of groups the input to the slice instruction is split into. * `slice` should be over the channels axis only and split evenly. */ static std::size_t get_num_slice_groups(instruction_ref slice) { auto input = slice->inputs().front(); auto op = slice->get_operator().to_value(); auto axes = op["axes"].to_vector(); if(axes.size() != 1) return 0; if(axes.front() != 1) return 0; auto ichannels = input->get_shape().lens().at(1); auto channels = slice->get_shape().lens().at(1); if((ichannels % channels) != 0) return 0; return ichannels / channels; } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto slice = r.instructions["slice"]; auto input = slice->inputs().front(); auto num_slice_groups = get_num_slice_groups(slice); if(num_slice_groups == 0) { return; } // check that all slice instructions coming off from `input` are making // the same size slice. if(not all_of(input->outputs(), [&](instruction_ref output) { if(output->name() != "slice") return false; auto ichannels = output->inputs().front()->get_shape().lens().at(1); auto channels = output->get_shape().lens().at(1); return channels * num_slice_groups == ichannels; })) { return; } // check memory layout is in NCHW if(find_permutation(ins->get_shape()).back() != 1) { return; } auto dims = input->get_shape().lens(); dims[1] /= num_slice_groups; // inserts num_slice_groups dimension in front of channels to split channels correctly dims.insert(dims.begin() + 1, num_slice_groups); // first transpose permutation such that channels dimension goes to // last axis and num_slice_groups axis goes to first axis std::vector transpose_perm0(dims.size()); transpose_perm0.at(0) = 1; transpose_perm0.at(1) = 0; std::iota(transpose_perm0.begin() + 2, transpose_perm0.end() - 1, 3); transpose_perm0.back() = 2; // second transpose permutation such that last dimension // (the channels dimension below) goes to third axis std::vector transpose_perm1(dims.size()); transpose_perm1.at(0) = 0; transpose_perm1.at(1) = 1; transpose_perm1.at(2) = dims.size() - 1; std::iota(transpose_perm1.begin() + 3, transpose_perm1.end(), 2); auto outputs = input->outputs(); auto ins_to_insert = std::next(input); auto reshape1 = mpm.get_module().insert_instruction( ins_to_insert, make_op("reshape_lazy", {{"dims", dims}}), input); auto transpose_ins1 = mpm.get_module().insert_instruction( ins_to_insert, make_op("transpose", {{"permutation", transpose_perm0}}), reshape1); auto contiguous_ins = mpm.get_module().insert_instruction( ins_to_insert, make_op("contiguous"), transpose_ins1); auto transepose_ins2 = mpm.get_module().insert_instruction( ins_to_insert, make_op("transpose", {{"permutation", transpose_perm1}}), contiguous_ins); // spacer identity instruction auto identity_ins = mpm.get_module().insert_instruction( ins_to_insert, make_op("identity"), transepose_ins2); // Replace slice operators to 0 axis for(auto output : outputs) { auto v = output->get_operator().to_value(); auto starts = v["starts"].to_vector(); auto i = starts.front() / output->get_shape().lens()[1]; // note integer truncation auto s = mpm.get_module().insert_instruction( output, make_op("slice", {{"axes", {0}}, {"starts", {i}}, {"ends", {i + 1}}}), identity_ins); mpm.get_module().replace_instruction(output, make_op("squeeze", {{"axes", {0}}}), s); } } }; } // namespace #endif // MIGRAPHX_MLIR void fuse_mlir::apply(module_pass_manager& mpm) const { #ifdef MIGRAPHX_MLIR std::size_t counter = 0; const auto& device_name = ctx == nullptr ? "" : ctx->get_current_device().get_gfx_name(); const bool is_navi = starts_with(device_name, "gfx11") or starts_with(device_name, "gfx12"); auto get_mode = [&](std::string_view option, mlir_mode m1, mlir_mode m2 = mlir_mode::fast) { if(specific_op(option)) return mlir_mode::none; if(specific_op(option)) return mlir_mode::all; if(is_navi) return mlir_mode::all; return std::max(m1, m2); }; match::find_matches(mpm, find_channel_slice_convolution{}); mpm.run_pass(dead_code_elimination{}); match::find_matches(mpm, find_mlir_attention_op{}); mpm.run_pass(dead_code_elimination{}); if(enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) { match::find_matches( mpm, find_mlir_fused_geg_ops{.conv_mode = get_mode("fused_convolution", mlir_mode::fast), .dot_mode = get_mode("fused_dot", mlir_mode::fast)}); mpm.run_pass(dead_code_elimination{}); } match::find_matches( mpm, find_mlir_fused_ops{.conv_mode = get_mode("fused_convolution", mlir_mode::fast), .dot_mode = get_mode("fused_dot", mlir_mode::fast)}); match::find_matches( mpm, find_mlir_standalone_conv_op{.mode = get_mode("convolution", mlir_mode::fast), .counter = &counter}, find_mlir_standalone_conv_backwards_op{ .mode = get_mode("convolution_backwards", MIGRAPHX_USE_MIOPEN ? mlir_mode::none : mlir_mode::all), .counter = &counter}, find_mlir_standalone_dot_op{.mode = get_mode("dot", mlir_mode::fast), .counter = &counter}); mpm.run_pass(dead_code_elimination{}); if(enabled(MIGRAPHX_ENABLE_MLIR_REDUCE_FUSION{})) { match::find_matches( mpm, find_mlir_split_reduce{.conv_mode = get_mode("fused_convolution", mlir_mode::fast), .dot_mode = get_mode("fused_dot", mlir_mode::fast)}); } match::find_matches(mpm, find_pointwise_mlir{}); match::find_matches(mpm, find_unpack_int4_mlir_op{}); match::find_matches(mpm, find_unpack_fp4_mlir_op{}); match::find_matches(mpm, find_mlir_output_reshape_ops{}); #else (void)mpm; #endif } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/fuse_ops.cpp000066400000000000000000001065531510465702400221340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_MIOPEN_FUSION) #if MIGRAPHX_USE_MIOPEN struct fusion { using op_t = miopenFusionOpDescriptor_t; shared fp; // Used as a temporary hack to keep descriptor references alive std::vector> storage; template auto keep_alive(T x) { auto result = share(std::move(x)); storage.push_back(result); return result; } fusion() = default; fusion(const shape& input) { assert(input.standard()); auto t = make_tensor(input); fp = make_fusion_plan(t); assert(fp); keep_alive(std::move(t)); } bool empty() const { return fp == nullptr; } op_t operator[](std::size_t i) const { assert(fp); op_t result; auto status = miopenFusionPlanGetOp(fp.get(), i, &result); if(status != miopenStatusSuccess) MIGRAPHX_THROW("Failed retrieving operator at " + std::to_string(i)); return result; } auto get() const { assert(fp); return fp.get(); } op_t create_bias(const shape& bias) { assert(fp); op_t result; auto b = shape{bias.type(), {1, bias.lens().at(1), 1, 1}}; auto t = keep_alive(make_tensor(b)); auto status = miopenCreateOpBiasForward(fp.get(), &result, t.get()); if(status != miopenStatusSuccess) MIGRAPHX_THROW("Creating operator failed"); return result; } op_t create_relu() { assert(fp); op_t result; auto status = miopenCreateOpActivationForward(fp.get(), &result, miopenActivationRELU); if(status != miopenStatusSuccess) MIGRAPHX_THROW("Creating operator failed"); return result; } op_t create_conv(const op::convolution& op, const shape& weights) { assert(fp); op_t result; auto cd = keep_alive(make_conv(op)); auto t = keep_alive(make_tensor(weights)); auto status = miopenCreateOpConvForward(fp.get(), &result, cd.get(), t.get()); if(status != miopenStatusSuccess) MIGRAPHX_THROW("Creating operator failed"); return result; } shape get_workspace(context&) { // assert(fp); // TODO: Use zero workspace for now std::size_t ws_size = 0; // int algo_count = 1; // miopenConvFwdAlgorithm_t algo; // miopenFusionPlanConvolutionGetAlgo(fp.get(), 1, &algo_count, &algo); // miopenFusionPlanGetWorkSpaceSize(ctx.get_stream().get_miopen(), fp.get(), &ws_size, // algo); return shape{shape::int8_type, {ws_size}}; } bool compile(context& ctx) { assert(fp); return miopenCompileFusionPlan(ctx.get_stream().get_miopen(), fp.get()) == miopenStatusSuccess; } argument execute(context& ctx, const fused_operator_args& fargs, const argument& x, const argument& y) const { assert(fp); auto x_td = make_tensor(x.get_shape()); auto y_td = make_tensor(y.get_shape()); auto status = miopenExecuteFusionPlan(ctx.get_stream().get_miopen(), fp.get(), x_td.get(), x.implicit(), y_td.get(), y.implicit(), fargs.get()); if(status != miopenStatusSuccess) MIGRAPHX_THROW("Failed to execute fusion plan"); return y; } }; #endif #if MIGRAPHX_USE_MIOPEN static const std::unordered_set& get_supported_archs() { static std::unordered_set supported_archs{"gfx900", "gfx906", "gfx908", "gfx1030"}; return supported_archs; } MIGRAPHX_PRED_MATCHER(bias_shape, instruction_ref ins) { auto&& s = ins->get_shape(); return s.broadcasted() and s.strides().size() == 4 and s.strides()[0] == 0 and s.strides()[1] != 0 and s.strides()[2] == 0 and s.strides()[3] == 0; } MIGRAPHX_PRED_MATCHER(fusable_conv, instruction_ref ins) { const auto device_name = trim(split_string(get_device_name(), ':').front()); if(not contains(get_supported_archs(), device_name)) return false; if(enabled(MIGRAPHX_DISABLE_MIOPEN_FUSION{})) return false; if(ins->name() != "gpu::convolution") return false; if(ins->get_shape().type() != shape::float_type) return false; auto wei = ins->inputs().at(1)->get_shape(); assert(wei.lens().size() == 4); auto miopen_conv_op = ins->get_operator().to_value(); auto algo = miopen_conv_op.at("algo").to(); auto conv_op = from_value(miopen_conv_op["op"]); if(conv_op.group > 1) return false; if(wei.lens()[1] > 512 and algo != miopenConvolutionFwdAlgoWinograd) return false; // Do not fuse non-symmetric input auto input_lens = ins->inputs().at(0)->get_shape().lens(); if(input_lens[2] != input_lens[3] or wei.lens()[2] != wei.lens()[3]) return false; // Dont fuse winograd for non-3x3s since there is no fused windograd for those configs if(algo == miopenConvolutionFwdAlgoWinograd and wei.lens()[2] != 3 and wei.lens()[3] != 3 and contains({{1, 1}}, conv_op.stride)) return false; return contains({{0, 0, 0, 0}, {1, 1, 1, 1}, {2, 2, 2, 2}}, conv_op.padding) and contains({{0, 0}, {1, 1}}, conv_op.stride) and contains({{1, 1}}, conv_op.dilation); } #endif static void move_broadcasted_back(std::vector& args) { // Ensure the last arguments is the broadcasted one auto last = std::prev(args.end()); auto it = std::find_if(args.begin(), last, [](auto arg) { return arg->get_shape().broadcasted(); }); if(it != last) std::swap(*it, *std::prev(last)); } namespace { #if MIGRAPHX_USE_MIOPEN struct miopen_fusion { struct fuse_op_data { operation op; float alpha = 1; float beta = 0; }; struct fuse_op : fuse_op_data, reflect_equality, reflect_stream { template static auto reflect(Self& self, F f) { return pack(f(self.op, "op"), f(self.alpha, "alpha"), f(self.beta, "beta")); } }; std::vector ops = {}; fusion f = {}; std::function&)> execute; template static auto reflect(Self& self, F f) { return pack(f(self.ops, "ops")); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } value compile(context& ctx, const shape&, std::vector inputs) { // Compensate for allocation inputs.pop_back(); std::size_t i = 0; f = fusion(inputs[i]); i++; std::vector&)>> invokers; for(auto&& fop : ops) { if(i > inputs.size()) { f = {}; return {}; } if(fop.op.name() == "convolution") { auto* mop = f.create_conv(any_cast(fop.op), inputs[i]); invokers.push_back( [=](const fused_operator_args& fargs, const std::vector& args) { miopenSetOpArgsConvForward( fargs.get(), mop, &fop.alpha, &fop.beta, args[i].implicit()); }); i++; } else if(fop.op.name() == "add") { auto* mop = f.create_bias(inputs[i]); invokers.push_back( [=](const fused_operator_args& fargs, const std::vector& args) { miopenSetOpArgsBiasForward( fargs.get(), mop, &fop.alpha, &fop.beta, args[i].implicit()); }); i++; } else if(fop.op.name() == "relu") { auto* mop = f.create_relu(); invokers.push_back([=](const fused_operator_args& fargs, const std::vector&) { miopenSetOpArgsActivForward(fargs.get(), mop, &fop.alpha, &fop.beta, 0, 0, 0); }); } else { f = {}; return {}; } } if(not f.compile(ctx)) { f = {}; return {}; } execute = [invokers](context& c, const fusion& ff, const std::vector& args) { auto fargs = make_fused_args(); for(auto&& invoker : invokers) invoker(fargs, args); ff.execute(c, fargs, args.front(), args.back()); }; return {{"workspace", f.get_workspace(ctx).bytes()}}; } void finalize(context& ctx, const shape& output_shape, const std::vector& inputs) { if(not f.empty()) return; auto v = compile(ctx, output_shape, inputs); if(not v.is_object()) MIGRAPHX_THROW("Failed to compile fusion plan"); } std::string name() const { return "gpu::miopen_fusion"; } shape compute_shape(const std::vector& inputs) const { if(ops.empty()) return {}; // TODO: Check number of arguments return ops.front().op.compute_shape({inputs[0], inputs[1]}); } argument compute(context& ctx, const shape&, const std::vector& args) const { execute(ctx, f, args); return args.back(); } }; MIGRAPHX_REGISTER_OP(miopen_fusion) struct miopen_conv_bias { op::convolution op; fusion fp = {}; fusion::op_t conv = {}; fusion::op_t bias = {}; template static auto reflect(Self& self, F f) { return op::convolution::reflect(self.op, f); } std::string name() const { return "gpu::conv_bias"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(5); // TODO: Check slices return op.normalize_compute_shape({inputs.at(0), inputs.at(1)}); } argument compute(context& ctx, const shape&, const std::vector& args) const { auto fargs = make_fused_args(); float alpha = 1; float beta = 0; miopenSetOpArgsConvForward(fargs.get(), conv, &alpha, &beta, args[1].implicit()); miopenSetOpArgsBiasForward(fargs.get(), bias, &alpha, &beta, args[3].implicit()); return fp.execute(ctx, fargs, args[0], args[4]); } void finalize(context& ctx, const shape&, const std::vector& inputs) { fp = fusion(inputs[0]); conv = fp.create_conv(op, inputs[1]); bias = fp.create_bias(inputs[3]); if(not fp.compile(ctx)) MIGRAPHX_THROW("Failed to compile fusion plan"); } shape get_workspace(context& ctx) { return fp.get_workspace(ctx); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; MIGRAPHX_REGISTER_OP(miopen_conv_bias) struct miopen_conv_bias_relu { op::convolution op; fusion fp = {}; fusion::op_t conv = {}; fusion::op_t bias = {}; fusion::op_t relu = {}; template static auto reflect(Self& self, F f) { return op::convolution::reflect(self.op, f); } std::string name() const { return "gpu::conv_bias_relu"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(5); // TODO: Check slices return op.normalize_compute_shape({inputs.at(0), inputs.at(1)}); } argument compute(context& ctx, const shape&, const std::vector& args) const { auto fargs = make_fused_args(); float alpha = 1; float beta = 0; miopenSetOpArgsConvForward(fargs.get(), conv, &alpha, &beta, args[1].implicit()); miopenSetOpArgsBiasForward(fargs.get(), bias, &alpha, &beta, args[3].implicit()); miopenSetOpArgsActivForward(fargs.get(), relu, &alpha, &beta, 0, 0, 0); return fp.execute(ctx, fargs, args[0], args[4]); } void finalize(context& ctx, const shape&, const std::vector& inputs) { fp = fusion(inputs[0]); conv = fp.create_conv(op, inputs[1]); bias = fp.create_bias(inputs[3]); relu = fp.create_relu(); fp.compile(ctx); } shape get_workspace(context& ctx) { return fp.get_workspace(ctx); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; MIGRAPHX_REGISTER_OP(miopen_conv_bias_relu) template auto conv_bias(Ms... ms) { return match::name("gpu::add")( match::either_arg(0, 1)(bias_shape(match::used_once()).bind("bias"), fusable_conv(match::used_once()).bind("conv")), ms...); } template void apply_conv_bias(context& ctx, module& m, const match::matcher_result& r) { auto conv_ins = r.instructions["conv"]; auto bias_ins = r.instructions["bias"]; auto ins = r.result; auto input_ins = conv_ins->inputs().at(0); auto weights_ins = conv_ins->inputs().at(1); auto conv_op = from_value((conv_ins->get_operator()).to_value()["op"]); auto alloc_ins = ins->inputs().back(); auto old_ws_ins = conv_ins->inputs().at(2); Op cb{conv_op}; // TODO: Insert ws allocation auto ws = cb.get_workspace(ctx); (void)ws; m.replace_instruction(ins, cb, input_ins, weights_ins, old_ws_ins, bias_ins, alloc_ins); } #endif template inline auto precompile_name(Strings... names) // NOLINT { return match::make_basic_pred_matcher([=](instruction_ref ins) { if(ins->name() != "gpu::precompile_op") return false; auto op = from_value(ins->get_operator().to_value().at("op")); return (contains({names...}, op.name())); }); } #if MIGRAPHX_USE_MIOPEN struct find_conv_bias { context* ctx = nullptr; auto matcher() const { auto relu = match::name(std::unordered_set{"gpu::relu"}); return conv_bias(match::none_of(match::output(relu))); } void apply(module& m, const match::matcher_result& r) const { apply_conv_bias(*ctx, m, r); } }; struct find_conv_bias_relu { context* ctx = nullptr; auto matcher() const { return match::name("gpu::relu")(match::arg(0)(conv_bias())); } void apply(module& m, const match::matcher_result& r) const { apply_conv_bias(*ctx, m, r); } }; struct find_conv_pointwise { context* ctx = nullptr; auto matcher() const { return precompile_name("pointwise")( match::nargs(3), match::either_arg(0, 1)(bias_shape(match::used_once()).bind("bias"), fusable_conv(match::used_once()).bind("conv"))); } void apply(module& m, const match::matcher_result& r) const { auto conv_ins = r.instructions["conv"]; auto bias_ins = r.instructions["bias"]; auto ins = r.result; auto input_ins = conv_ins->inputs().at(0); auto weights_ins = conv_ins->inputs().at(1); auto conv_op = from_value(conv_ins->get_operator().to_value()["op"]); auto alloc_ins = ins->inputs().back(); module_ref pm = ins->module_inputs().front(); miopen_fusion op{}; op.ops.push_back({{conv_op}}); for(auto&& i : *pm) { if(i.name()[0] == '@') continue; op.ops.push_back({{i.get_operator()}}); } std::vector inputs = {input_ins, weights_ins, bias_ins, alloc_ins}; auto v = op.compile(*ctx, ins->get_shape(), to_shapes(inputs)); if(not v.is_object()) return; m.replace_instruction(ins, op, inputs); } }; #endif #if MIGRAPHX_USE_ROCBLAS or MIGRAPHX_USE_HIPBLASLT struct gemm_pointwise { // TODO: Move to matcher.hpp static auto match_param(const std::string& name) { return match::make_basic_pred_matcher([=](auto ins) { if(ins->name() != "@param") return false; auto p = any_cast(ins->get_operator()); return p.parameter == name; }); } template static auto match_mul_const(M m, const std::string& var) { return match::name("mul")(match::either_arg(0, 1)(match::name("@literal").bind(var), m)) .bind(var + "_mul"); } static auto match_add(const std::string& input, const std::string& output) { auto param = match::name("@param"); auto add = match::name("add")(match::args(param, param)); auto inner_mul = match::any_of(match_mul_const(match_param(input), "alpha"), match_mul_const(match_param(output), "beta")); auto mul_add = match::name("add")(match::either_arg(0, 1)(inner_mul, param)); auto add_mul = match_mul_const(add, "gamma"); return match::name("@return")(match::args(match::any_of(add, mul_add, add_mul))); } static auto match_mul(const std::string& input) { auto mul = match_mul_const(match_param(input), "alpha"); return match::name("@return")(match::args(mul)); } static float get_float(instruction_ref ins) { return ins->get_literal().at(); } template static bool update_gemm(Gemm& gemm, module_ref pm, unsigned input) { auto names = pm->get_parameter_names(); std::sort(names.begin(), names.end()); if(names.size() == 1) { auto mr = match::match_instruction(*pm, std::prev(pm->end()), match_mul(names[input])); if(mr.result == pm->end()) return false; gemm.alpha *= get_float(mr.instructions["alpha"]); return true; } else if(names.size() == 2) { unsigned output = input == 0 ? 1 : 0; auto mr = match::match_instruction( *pm, std::prev(pm->end()), match_add(names[input], names[output])); if(mr.result == pm->end()) return false; if(contains(mr.instructions, "alpha_mul")) gemm.alpha *= get_float(mr.instructions["alpha"]); else if(contains(mr.instructions, "beta_mul")) gemm.beta *= get_float(mr.instructions["beta"]); else if(contains(mr.instructions, "gamma_mul")) { gemm.alpha *= get_float(mr.instructions["gamma"]); gemm.beta *= get_float(mr.instructions["gamma"]); } return true; } else { return false; } } }; #endif #if MIGRAPHX_USE_ROCBLAS struct find_rocblas_gemm_pointwise : gemm_pointwise { auto matcher() const { auto gemm_op = match::name("gpu::gemm")(match::nargs(3), match::used_once()).bind("gemm"); auto binary_op = match::all_of( match::nargs(3), match::either_arg(0, 1)( match::any_of(match::standard_shape(), match::is_constant()).bind("c"), gemm_op)); auto unary_op = match::all_of(match::nargs(2), match::arg(0)(gemm_op)); return precompile_name("pointwise")(match::any_of(binary_op, unary_op)); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto gemm_ins = r.instructions["gemm"]; auto gemm = any_cast>(gemm_ins->get_operator()); // Already fused gemm if(not float_equal(gemm.beta, 0)) return; if(ins->inputs().size() == 3) gemm.beta = 1; if(not update_gemm( gemm, ins->module_inputs().front(), ins->inputs().front() == gemm_ins ? 0 : 1)) return; auto inputs = gemm_ins->inputs(); inputs.pop_back(); if(ins->inputs().size() == 3) { auto c_ins = r.instructions["c"]; shape s = c_ins->get_shape(); // const-fold input if not standard shape since rocblas can't handle it if(not s.standard()) { auto c = make_op("contiguous"); auto l = c.compute(c.compute_shape({c_ins->get_shape()}), {c_ins->eval()}); c_ins = m.add_literal(l.get_shape(), l.data()); } inputs.push_back(c_ins); } inputs.push_back(ins->inputs().back()); m.replace_instruction(ins, gemm, inputs); } }; #endif #if MIGRAPHX_USE_HIPBLASLT struct find_hipblas_gemm_pointwise : gemm_pointwise { auto matcher() const { auto gemm_op = match::name("gpu::hipblaslt_op")(match::nargs(3), match::used_once()).bind("hip_gemm"); auto binary_op = match::all_of( match::nargs(3), match::either_arg(0, 1)( match::any_of(match::standard_shape(), match::is_constant()).bind("c"), gemm_op)); auto unary_op = match::all_of(match::nargs(2), match::arg(0)(gemm_op)); return precompile_name("pointwise")(match::any_of(binary_op, unary_op)); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto gemm_ins = r.instructions["hip_gemm"]; auto gemm_op = any_cast(gemm_ins->get_operator()).op; if(gemm_op.name() != "gpu::hip_gemm") return; auto gemm = any_cast>(gemm_op); // Already fused gemm if(not float_equal(gemm.beta, 0)) return; if(ins->inputs().size() == 3) gemm.beta = 1; if(not update_gemm( gemm, ins->module_inputs().front(), ins->inputs().front() == gemm_ins ? 0 : 1)) { return; } auto inputs = gemm_ins->inputs(); inputs.pop_back(); if(ins->inputs().size() == 3) { auto c_ins = r.instructions["c"]; shape s = c_ins->get_shape(); // const-fold input if not standard shape if(not s.standard()) { auto c = make_op("contiguous"); auto l = c.compute(c.compute_shape({c_ins->get_shape()}), {c_ins->eval()}); c_ins = m.add_literal(l.get_shape(), l.data()); } inputs.push_back(c_ins); } inputs.push_back(ins->inputs().back()); operation new_gemm_op = gemm; auto new_ins = m.insert_instruction( ins, make_op("gpu::hipblaslt_op", {{"op", to_value(new_gemm_op)}}), inputs); m.replace_instruction(ins, new_ins); } }; #endif struct contiguous_transpose_gemm { template static bool is_swapped(const Vector& perm, std::size_t i, std::size_t j) { if(i >= perm.size() or j >= perm.size()) return false; auto perm2 = perm; std::iota(perm2.begin(), perm2.end(), 0); std::swap(perm2[i], perm2[j]); return perm2 == perm; } }; struct find_contiguous_transpose_rocblas_gemm : contiguous_transpose_gemm { auto matcher() const { return match::name("gpu::contiguous")(match::arg(0)( match::name("transpose")( match::arg(0)(match::name("gpu::gemm")(match::used_once()).bind("gemm"))) .bind("transpose"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto gemm = r.instructions["gemm"]; auto alloc = gemm->inputs().back(); auto transpose = r.instructions["transpose"]; auto perm = transpose->get_operator().to_value()["permutation"].to_vector(); auto iperm = invert_permutation(perm); if(perm.size() < 3) return; if(not is_swapped(perm, perm.size() - 3, perm.size() - 2)) return; auto lens = gemm->get_shape().lens(); if(lens.size() > 3 and not std::all_of(lens.begin(), lens.end() - 3, [](auto i) { return i == 1; })) return; auto gemmv = gemm->get_operator().to_value(); gemmv["trans_batch"] = 1; auto s = shape{alloc->get_shape().type(), reorder_dims(alloc->get_shape().lens(), iperm)}; auto new_alloc = m.insert_instruction(gemm, make_op("allocate", {{"shape", to_value(s)}})); auto alloc_transpose = m.insert_instruction(gemm, make_op("transpose", {{"permutation", perm}}), new_alloc); auto inputs = gemm->inputs(); inputs.back() = alloc_transpose; auto new_gemm = m.insert_instruction(gemm, make_op("gpu::gemm", gemmv), inputs); auto gemm_transpoe = m.insert_instruction(gemm, transpose->get_operator(), new_gemm); m.replace_instruction(ins, gemm_transpoe); } }; #if MIGRAPHX_USE_HIPBLASLT struct find_contiguous_transpose_hip_gemm : contiguous_transpose_gemm { auto matcher() const { return match::name("gpu::contiguous")(match::arg(0)( match::name("transpose")( match::arg(0)( match::name("gpu::hipblaslt_op")(match::used_once()).bind("hip_gemm"))) .bind("transpose"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto gemm_ins = r.instructions["hip_gemm"]; auto gemm_op = any_cast(gemm_ins->get_operator()).op; if(gemm_op.name() != "gpu::hip_gemm") return; auto gemm = any_cast>(gemm_op); auto alloc = gemm_ins->inputs().back(); auto transpose = r.instructions["transpose"]; auto perm = transpose->get_operator().to_value()["permutation"].to_vector(); auto iperm = invert_permutation(perm); if(perm.size() < 3) return; if(not is_swapped(perm, perm.size() - 3, perm.size() - 2)) return; auto lens = gemm_ins->get_shape().lens(); if(lens.size() > 3 and not std::all_of(lens.begin(), lens.end() - 3, [](auto i) { return i == 1; })) return; gemm.trans_batch = 1; auto s = shape{alloc->get_shape().type(), reorder_dims(alloc->get_shape().lens(), iperm)}; auto new_alloc = m.insert_instruction(gemm_ins, make_op("allocate", {{"shape", to_value(s)}})); auto alloc_transpose = m.insert_instruction( gemm_ins, make_op("transpose", {{"permutation", perm}}), new_alloc); auto inputs = gemm_ins->inputs(); inputs.back() = alloc_transpose; operation new_gemm_op = gemm; auto new_gemm = m.insert_instruction( gemm_ins, make_op("gpu::hipblaslt_op", {{"op", to_value(new_gemm_op)}}), inputs); auto gemm_transpoe = m.insert_instruction(gemm_ins, transpose->get_operator(), new_gemm); m.replace_instruction(ins, gemm_transpoe); } }; #endif struct find_commutative_broadcast { auto matcher() const { return match::name("gpu::add", "gpu::mul")(match::arg(1)(match::broadcast_shape())); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto args = ins->inputs(); move_broadcasted_back(args); m.replace_instruction(ins, ins->get_operator(), args); } }; } // namespace struct find_contiguous { auto matcher() const { return match::name("gpu::contiguous"); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; m.replace_instruction( ins, make_op("gpu::precompile_op", {{"op", to_value(make_op("contiguous"))}}), ins->inputs()); } }; struct find_contiguous_layout_pointwise { auto matcher() const { auto cont_pw = precompile_name("pointwise")(match::any_of[match::inputs()]( match::name("gpu::contiguous")(match::used_once()).bind("layout_ins"))); auto layout_pw = precompile_name("pointwise")(match::any_of[match::inputs()]( precompile_name("layout")(match::used_once()).bind("layout_ins"))); return match::any_of(cont_pw, layout_pw); } void apply(module& m, const match::matcher_result& r) const { auto pw_ins = r.result; auto layout_ins = r.instructions["layout_ins"]; auto layout_input = layout_ins->inputs().front(); auto pw_ins_inputs = pw_ins->inputs(); replace(pw_ins_inputs, layout_ins, layout_input); // Ensure the output shape of the pointwise module retains the memory layout auto pw_op_val = pw_ins->get_operator().to_value(); pw_op_val["output_shape"] = to_value(pw_ins->get_shape()); auto new_ins = m.insert_instruction( pw_ins, make_op(pw_ins->name(), pw_op_val), pw_ins_inputs, pw_ins->module_inputs()); m.replace_instruction(pw_ins, new_ins); } }; struct find_pointwise_layout_contiguous { auto matcher() const { auto is_layout = precompile_name("layout")( match::arg(0)(match::used_once(), precompile_name("pointwise"))); auto is_contiguous = match::name("gpu::contiguous")( match::arg(0)(match::used_once(), precompile_name("pointwise"))); return match::any_of(is_layout, is_contiguous); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto pw = ins->inputs().front(); auto alloc = ins->inputs().back(); auto args = pw->inputs(); args.back() = alloc; // Ensure the output shape of the pointwise module retains the memory layout auto pw_op_val = pw->get_operator().to_value(); pw_op_val["output_shape"] = to_value(ins->get_shape()); m.replace_instruction(ins, make_op(pw->name(), pw_op_val), args, pw->module_inputs()); } }; struct find_layernorm_pointwise { auto matcher() const { return precompile_name("pointwise")( match::not_tuple(), match::arg(0)( precompile_name("gpu::prelayernorm", "gpu::preadd_layernorm").bind("layernorm"))); } void apply(module& m, const match::matcher_result& r) const { auto pw_ins = r.result; auto layernorm = r.instructions["layernorm"]; if(not layernorm->module_inputs().empty()) return; auto* pm = pw_ins->module_inputs().front(); auto pw_inputs = pw_ins->inputs(); auto ln_pos = std::find(pw_inputs.begin(), pw_inputs.end(), layernorm); assert(ln_pos != pw_inputs.end()); pw_inputs.erase(ln_pos); auto inputs = layernorm->inputs(); inputs.pop_back(); inputs.insert(inputs.end(), pw_inputs.begin(), pw_inputs.end()); // Ensure the output shape retains the memory layout auto layernorm_op_val = layernorm->get_operator().to_value(); layernorm_op_val["output_shape"] = to_value(pw_ins->get_shape()); m.replace_instruction(pw_ins, make_op(layernorm->name(), layernorm_op_val), inputs, {pm}); } }; struct find_concat_pointwise { auto matcher() const { return precompile_name("pointwise")( match::arg(0)(precompile_name("concat").bind("concat"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto concat = r.instructions["concat"]; if(not concat->module_inputs().empty()) return; // TODO: Handle type conversions if(ins->get_shape().type() != concat->get_shape().type()) return; auto* pm = ins->module_inputs().front(); auto inputs = concat->inputs(); inputs.pop_back(); inputs.insert(inputs.end(), ins->inputs().begin() + 1, ins->inputs().end()); auto op = concat->get_operator(); op.from_value({{"additional_args", ins->inputs().size() - 1}, {"ignore_modules", true}, {"output_shape", to_value(ins->get_shape())}}); m.replace_instruction(ins, op, inputs, {pm}); } }; void fuse_ops::apply(module& m) const { match::find_matches(m, find_pointwise_layout_contiguous{}, find_contiguous_layout_pointwise{}); run_passes(m, {dead_code_elimination{}}); #if MIGRAPHX_USE_MIOPEN match::find_matches(m, find_conv_pointwise{ctx}, find_conv_bias_relu{ctx}, find_conv_bias{ctx}); run_passes(m, {dead_code_elimination{}}); #endif #if MIGRAPHX_USE_ROCBLAS match::find_matches(m, find_rocblas_gemm_pointwise{}); #endif #if MIGRAPHX_USE_HIPBLASLT match::find_matches(m, find_hipblas_gemm_pointwise{}); #endif match::find_matches(m, find_layernorm_pointwise{}, find_concat_pointwise{}, find_contiguous_transpose_rocblas_gemm{}, #if MIGRAPHX_USE_HIPBLASLT find_contiguous_transpose_hip_gemm{}, #endif find_commutative_broadcast{}); match::find_matches(m, find_contiguous{}); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/gemm_impl.cpp000066400000000000000000000617621510465702400222610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include using microseconds = std::chrono::duration; namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #if MIGRAPHX_USE_ROCBLAS /** * Regular rocBLAS API takes compute_type as `rocblas_datatype` enum value. * `rb_compute_type` is faciliator to implictly cast integer enum value to required type that can be * used inside `common_args` generator. Beta API was removed, so this struct is the same as the enum * directly. */ struct rb_compute_type { int type = 0; rb_compute_type(rocblas_datatype t) : type(static_cast(t)) {} operator rocblas_datatype() const { return static_cast(type); } }; // Convert rocBLAS datatypes to equivalent Migraphx data types static rocblas_datatype get_type(shape::type_t type) { switch(type) { case shape::double_type: return rocblas_datatype_f64_r; case shape::float_type: return rocblas_datatype_f32_r; case shape::half_type: return rocblas_datatype_f16_r; case shape::int8_type: return rocblas_datatype_i8_r; case shape::uint8_type: return rocblas_datatype_u8_r; case shape::int32_type: return rocblas_datatype_i32_r; case shape::uint32_type: return rocblas_datatype_u32_r; case shape::fp8e4m3fnuz_type: case shape::fp8e5m2fnuz_type: case shape::fp8e4m3fn_type: case shape::fp8e5m2_type: case shape::fp4x2_type: case shape::tuple_type: case shape::bool_type: case shape::uint16_type: case shape::int16_type: case shape::int64_type: case shape::uint64_type: MIGRAPHX_THROW("ROCBLAS_GEMM: data type not supported!"); case shape::bf16_type: return rocblas_datatype_bf16_r; } MIGRAPHX_THROW("ROCBLAS_GEMM: data type not supported!"); } void blas_shape(const shape& in_shape) { if(in_shape.lens().size() < 2) return; auto s = in_shape.normalize_standard(); if(std::none_of(s.strides().end() - 2, s.strides().end(), [](auto i) { return i == 1; })) MIGRAPHX_THROW("GPU_GEMM: needs to have one matrix stride as 1"); if(std::any_of(s.strides().end() - 2, s.strides().end(), [](auto i) { return i == 0; })) MIGRAPHX_THROW("GPU_GEMM: matrix dimensions can't be broadcasted"); if(s.lens().size() < 3) return; shape batch_shape{s.type(), {s.lens().begin(), s.lens().end() - 2}, {s.strides().begin(), s.strides().end() - 2}}; auto batch_shapes = reduce_dims({batch_shape}); if(batch_shapes.front().lens().size() != 1) MIGRAPHX_THROW("GPU_GEMM: Batch dimension is not collapsible"); } shape transpose_batch(const shape& s, unsigned trans_batch) { if(trans_batch == 0) return s; if(s.lens().size() < 3) return s; auto batch = s.lens().size() - 3; std::vector perm(s.lens().size()); std::iota(perm.begin(), perm.end(), 0); std::swap(perm[batch], perm[batch + trans_batch]); return shape::from_permutation(s.type(), s.lens(), perm); } /** * Returns results of rocblas_status_success, rocblas_status_perf_degraded, * or rocblas_status_invalid_value. Caller * is expected to check for invalid index. Any other result causes an exception. */ template static auto rocblas_invoke(F f, Pack p, Ts... xs) { return p([=](auto... ws) { auto status = f(ws..., xs...); if(status != rocblas_status_success and status != rocblas_status_invalid_value) { if(status == rocblas_status_perf_degraded) { std::cerr << "WARNING: degraded perf. in rocBLAS call" << std::endl; } else MIGRAPHX_THROW("rocblas_invoke: rocBLAS call failed with status " + std::to_string(status)); } return status; }); } static bool is_transposed(const shape& s) { if(s.transposed()) { return s.strides().back() != 1; } if(not s.broadcasted() and s.strides() != s.as_standard().strides()) { auto perm = find_permutation(s); return not std::is_sorted(perm.begin(), perm.end()); } return false; } static rocblas_int get_batch_stride(const shape& s) { // This value is not needed for non-strided inputs if(s.strides().size() < 3) return 0; else return s.strides()[s.strides().size() - 3]; } /** * Wrapper for multiple rocBLAS calls. The constructor creates parameters for * these calls based on data shapes and other values contained in the associated * instruction and operation. * * The template parameter T is not the type of the matrix data but of the weighting * coefficients alpha and beta (these are float in rocBLAS internals) */ template struct gemm_impl { gemm_impl(const shape& output_shape, std::vector input_shapes, T alpha_param, T beta_param, bool compute_fp32_flag) : alpha(alpha_param), beta(beta_param), is_3inputs(input_shapes.size() == 4), compute_fp32(compute_fp32_flag) { std::transform(input_shapes.begin(), input_shapes.end(), input_shapes.begin(), [&](const shape& s) { return s.normalize_standard(); }); if(not is_3inputs) { beta = 0; } // Create lambdas that will cast alpha, beta to the output shape's type // and retain the values being pointed to output_shape.visit_type([&](auto as) { auto alpha_r = as(alpha); auto beta_r = as(beta); if(compute_fp32) { get_alpha = [=] { return α }; get_beta = [=] { return β }; } else { get_alpha = [=] { return &alpha_r; }; get_beta = [=] { return &beta_r; }; } }); transa = is_transposed(input_shapes[0]); transb = is_transposed(input_shapes[1]); auto n_dim = output_shape.lens().size(); auto dim_0 = n_dim - 2; auto dim_1 = n_dim - 1; // Leading dimensions of matrices lda = input_shapes[0].strides()[transa ? dim_1 : dim_0]; ldb = input_shapes[1].strides()[transb ? dim_1 : dim_0]; ldc = input_shapes[2].strides()[dim_0]; ldd = is_3inputs ? input_shapes[3].strides()[dim_0] : ldc; arg_type = get_type(input_shapes[0].type()); output_type = get_type(input_shapes[2].type()); if(output_type == rocblas_datatype_i8_r or output_type == rocblas_datatype_u8_r) { output_type = rocblas_datatype_i32_r; } compute_type = rb_compute_type{output_type}; if(compute_fp32) { if(arg_type == rocblas_datatype_f16_r or arg_type == rocblas_datatype_bf16_r) compute_type = rocblas_datatype_f32_r; } auto a_lens = input_shapes[0].lens(); auto b_lens = input_shapes[1].lens(); auto out_lens = output_shape.lens(); m = out_lens[dim_0]; n = out_lens[dim_1]; k = input_shapes[0].lens()[dim_1]; a_stride = get_batch_stride(input_shapes[0]); b_stride = get_batch_stride(input_shapes[1]); c_stride = get_batch_stride(input_shapes[2]); d_stride = is_3inputs ? get_batch_stride(input_shapes[3]) : c_stride; num_matrices = std::accumulate( out_lens.rbegin() + 2, out_lens.rend(), std::size_t{1}, std::multiplies()); strided_batched = num_matrices > 1; if(strided_batched and b_stride == 0 and input_shapes[0].standard()) { // If the batch dimension of B is broadcasted, then we can // multiply m by the batch_size and use rocblas_gemm_ex // instead of rocblas_gemm_strided_batched_ex. m *= num_matrices; strided_batched = false; } } void run(context& ctx, const std::vector& input_args, int32_t solution_idx = 0) const { if(strided_batched) { auto common_args = create_strided_batched_args_common(ctx, compute_type, input_args); rocblas_invoke(&rocblas_gemm_strided_batched_ex, common_args, rocblas_gemm_algo_solution_index, solution_idx, gemm_flags); } else { auto common_args = create_gemm_ex_args_common(ctx, compute_type, input_args); rocblas_invoke(&rocblas_gemm_ex, common_args, rocblas_gemm_algo_solution_index, solution_idx, gemm_flags); } } #ifdef MIGRAPHX_USE_ROCBLAS_TUNING_API auto validate(context& ctx, const std::vector& input_shapes, int32_t solution_idx) const { // Create dummy arguments for the shapes, and call the overloaded method std::vector input_args; unsigned long seed = 0; std::transform(input_shapes.begin(), input_shapes.end(), std::back_inserter(input_args), [&](const shape& x) { return to_gpu(generate_argument(x, seed++, random_mode::random)); }); return validate(ctx, input_args, solution_idx); } /** * Checks a particular solution for validity by running it with the flag * rocblas_gemm_flags_check_solution_index (could be invalid if this model was * tuned with a different rocBLAS version) * * @return Returns either solution_idx if valid, or else the default value 0 * if not. The default does not mean list index 0, but tells the picker * to choose a solution. */ int32_t validate(context& ctx, const std::vector& input_args, int32_t solution_idx) const { rocblas_status_ check_valid(rocblas_status_success); if(strided_batched) { auto common_args = create_strided_batched_args_common(ctx, compute_type, input_args); check_valid = rocblas_invoke(&rocblas_gemm_strided_batched_ex, common_args, rocblas_gemm_algo_solution_index, solution_idx, rocblas_gemm_flags_check_solution_index); } else { auto common_args = create_gemm_ex_args_common(ctx, compute_type, input_args); check_valid = rocblas_invoke(&rocblas_gemm_ex, common_args, rocblas_gemm_algo_solution_index, solution_idx, rocblas_gemm_flags_check_solution_index); } if(check_valid == rocblas_status_invalid_value) { std::cerr << "WARNING: tuned solution is invalid; reverting to default" << std::endl; return 0; } return solution_idx; } #endif /** * Helper method to create that subset of a long rocBLAS argument list that is common * to multiple "...strided_batched..." calls. * * The rocblas_gemm API handles inputs and output matrices as * column-major format. When doing a C = A * B, we actually do * C^T = (B^T) * (A^T). That is the reason we input args[1] as * A and args[0] as B in calling the rocblas_gemm. * */ auto create_strided_batched_args_common(context& ctx, rb_compute_type rbcompute_type, const std::vector& args) const { return pack(ctx.get_stream().get_rocblas(), transb ? rocblas_operation_transpose : rocblas_operation_none, transa ? rocblas_operation_transpose : rocblas_operation_none, n, m, k, get_alpha(), args[1].data(), arg_type, ldb, b_stride, args[0].data(), arg_type, lda, a_stride, get_beta(), args[2].data(), output_type, ldc, c_stride, is_3inputs ? args[3].data() : args[2].data(), output_type, ldd, d_stride, num_matrices, rbcompute_type); } /** * Helper method to create that subset of a long rocBLAS argument list that is common * to multiple "gemm_ex..." calls. * * The rocblas_gemm API handles inputs and output matrices as * column-major format. When doing a C = A * B, we actually do * C^T = (B^T) * (A^T). That is the reason we input args[1] as * A and args[0] as B in calling the rocblas_gemm. * * */ auto create_gemm_ex_args_common(context& ctx, rb_compute_type rbcompute_type, const std::vector& args) const { return pack(ctx.get_stream().get_rocblas(), transb ? rocblas_operation_transpose : rocblas_operation_none, transa ? rocblas_operation_transpose : rocblas_operation_none, n, m, k, get_alpha(), args[1].data(), arg_type, ldb, args[0].data(), arg_type, lda, get_beta(), args[2].data(), output_type, ldc, is_3inputs ? args[3].data() : args[2].data(), output_type, ldd, rbcompute_type); } #ifdef MIGRAPHX_USE_ROCBLAS_TUNING_API /** * Find best rocBLAS solution: Get list of solutions and try them all, returning the index * of the fastest one. */ int tune(context& ctx, const std::vector& input_shapes) const { // tuning meta parameters const int hot_calls = 40; unsigned long seed = 0; std::vector input_args; std::transform(input_shapes.begin(), input_shapes.end(), std::back_inserter(input_args), [&](const shape& x) { return to_gpu(generate_argument(x, seed++, random_mode::random)); }); // Get the solutions list in 2 rocBLAS steps: // 1. Find out how many solutions there are and allocate the array // 2. Get the solutions // rocblas_int list_size = 0; std::vector solution_indices; rb_compute_type rbcompute_type = compute_type; if(strided_batched) { auto common_args = create_strided_batched_args_common(ctx, rbcompute_type, input_args); rocblas_invoke(&rocblas_gemm_strided_batched_ex_get_solutions, common_args, rocblas_gemm_algo_solution_index, gemm_flags, nullptr, &list_size); solution_indices.resize(list_size); auto common_sol_args = create_strided_batched_args_common(ctx, rbcompute_type, input_args); rocblas_invoke(&rocblas_gemm_strided_batched_ex_get_solutions, common_sol_args, rocblas_gemm_algo_solution_index, gemm_flags, solution_indices.data(), &list_size); } else { auto common_args = create_gemm_ex_args_common(ctx, rbcompute_type, input_args); rocblas_invoke(&rocblas_gemm_ex_get_solutions, common_args, rocblas_gemm_algo_solution_index, gemm_flags, nullptr, &list_size); solution_indices.resize(list_size); auto common_sol_args = create_gemm_ex_args_common(ctx, rbcompute_type, input_args); rocblas_invoke(&rocblas_gemm_ex_get_solutions, common_sol_args, rocblas_gemm_algo_solution_index, gemm_flags, solution_indices.data(), &list_size); } double best_time = std::numeric_limits::max(); double first_time = -1; // Initialize to default solution index rocblas_int best_sol = 0; for(auto sol : solution_indices) { // Warmup: the first call to an op. may not be representative since there is // more time taken initializing caches, etc. so we won't time it. run(ctx, input_args, sol); double host_time = time([&] { for([[maybe_unused]] int hc : range(hot_calls)) run(ctx, input_args, sol); ctx.finish(); }); host_time /= hot_calls; // dev/evaluation only: track time for first solution. if(first_time < 0) first_time = host_time; // track current best if(host_time < best_time) { best_sol = sol; best_time = host_time; } } std::cout << "Winning GEMM solution: " << best_sol << " in " << best_time << " ms, beats " << first_time << "ms" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds{50}); return best_sol; } #endif private: size_t num_matrices = 0; rocblas_int m = 0; rocblas_int n = 0; rocblas_int k = 0; bool transa = false; bool transb = false; T alpha = 0; T beta = 0; std::function get_alpha{}; std::function get_beta{}; rocblas_gemm_flags gemm_flags = rocblas_gemm_flags_none; rocblas_int lda = 0; rocblas_int ldb = 0; rocblas_int ldc = 0; rocblas_int ldd = 0; rocblas_int a_stride = 0; rocblas_int b_stride = 0; rocblas_int c_stride = 0; rocblas_int d_stride = 0; rocblas_datatype arg_type = rocblas_datatype_f32_r; rb_compute_type compute_type = rocblas_datatype_f32_r; rocblas_datatype output_type = rocblas_datatype_f32_r; bool strided_batched = true; bool is_3inputs = true; bool compute_fp32 = true; }; // gemm_impl void gemm_compute(context& ctx, const shape& output_shape, const std::vector& args, float alpha, float beta, bool compute_fp32, int32_t solution_idx) { std::vector input_shapes; std::transform(args.begin(), args.end(), std::back_inserter(input_shapes), [](const argument& x) { return x.get_shape(); }); auto gemm_item = gemm_impl(output_shape, input_shapes, alpha, beta, compute_fp32); gemm_item.run(ctx, args, solution_idx); } void gemm_compute(context& ctx, const shape& output_shape, const std::vector& args, int32_t alpha, int32_t beta, bool compute_fp32, int32_t solution_idx) { std::vector input_shapes; std::transform(args.begin(), args.end(), std::back_inserter(input_shapes), [](const argument& x) { return x.get_shape(); }); auto gemm_item = gemm_impl(output_shape, input_shapes, alpha, beta, compute_fp32); gemm_item.run(ctx, args, solution_idx); } static value gemm_problem(const shape& output_shape, std::vector input_shapes) { input_shapes.push_back(output_shape); return to_value(input_shapes); } #ifdef MIGRAPHX_USE_ROCBLAS_TUNING_API static void gemm_save_solution(context& ctx, const shape& output_shape, const std::vector& input_shapes, int32_t solution_idx) { ctx.get_problem_cache().insert( "rocblas", gemm_problem(output_shape, input_shapes), solution_idx); } #endif int32_t gemm_default_solution(context& ctx, const shape& output_shape, const std::vector& input_shapes) { auto sol = ctx.get_problem_cache().get("rocblas", gemm_problem(output_shape, input_shapes)); if(sol.has_value()) return sol->to(); return 0; } /** * Decides if the tune() or validate() method is appropriate and calls it. * Return value is the chosen solution index, or 0 to let picker choose it. */ template static int32_t gemm_finalize_impl(context& ctx, const shape& output_shape, const std::vector& input_shapes, T alpha, T beta, bool compute_fp32, int32_t solution_idx) { #ifdef MIGRAPHX_USE_ROCBLAS_TUNING_API // This code should be called only if either the environment var. // MIGRAPHX_ENABLE_GEMM_TUNING, or option --exhaustive-tune, is set if(solution_idx == 0) { auto gemm_item = gemm_impl(output_shape, input_shapes, alpha, beta, compute_fp32); solution_idx = gemm_item.tune(ctx, input_shapes); gemm_save_solution(ctx, output_shape, input_shapes, solution_idx); } else { // If a tuned solution index is already given, don't tune again but validate // in case the data was tuned with a different rocBLAS version auto gemm_item = gemm_impl(output_shape, input_shapes, alpha, beta, compute_fp32); solution_idx = gemm_item.validate(ctx, input_shapes, solution_idx); } #else (void)ctx, (void)output_shape, (void)input_shapes; (void)alpha, (void)beta, (void)compute_fp32; #endif return solution_idx; } int32_t gemm_finalize(context& ctx, const shape& output_shape, const std::vector& input_shapes, float alpha, float beta, bool compute_fp32, int32_t solution_idx) { return gemm_finalize_impl( ctx, output_shape, input_shapes, alpha, beta, compute_fp32, solution_idx); } int32_t gemm_finalize(context& ctx, const shape& output_shape, const std::vector& input_shapes, int32_t alpha, int32_t beta, bool compute_fp32, int32_t solution_idx) { return gemm_finalize_impl( ctx, output_shape, input_shapes, alpha, beta, compute_fp32, solution_idx); } #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/hip.cpp000066400000000000000000000230111510465702400210540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #if MIGRAPHX_USE_MIOPEN #include #endif #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_REGISTER_OP(hip_allocate) MIGRAPHX_REGISTER_OP(hip_fill) MIGRAPHX_REGISTER_OP(hip_sync_stream) MIGRAPHX_REGISTER_OP(hip_copy_to_gpu) MIGRAPHX_REGISTER_OP(hip_copy_from_gpu) MIGRAPHX_REGISTER_OP(hip_copy) MIGRAPHX_REGISTER_OP(hip_allocate_memory) MIGRAPHX_REGISTER_OP(hip_copy_literal) using hip_ptr = MIGRAPHX_MANAGE_PTR(void, hipFree); using hip_host_ptr = MIGRAPHX_MANAGE_PTR(void, hipHostUnregister); std::string hip_error(int error) { return hipGetErrorString(static_cast(error)); } static bool is_device_ptr(const void* ptr) { hipPointerAttribute_t attr; auto status = hipPointerGetAttributes(&attr, ptr); if(status != hipSuccess) return false; return attr.type == hipMemoryTypeDevice; } static void* get_device_ptr(void* hptr) { void* result = nullptr; auto status = hipHostGetDevicePointer(&result, hptr, 0); if(status != hipSuccess) MIGRAPHX_THROW("Failed getting device pointer: " + hip_error(status)); return result; } struct host_ptr_cache { std::unordered_map> cache; std::mutex m; std::shared_ptr get(void* ptr) { std::lock_guard lock(m); auto it = cache.find(ptr); if(it != cache.end()) return it->second.lock(); return nullptr; } void put(const std::shared_ptr& p) { std::lock_guard lock(m); cache[p.get()] = p; } }; static host_ptr_cache& get_host_ptr_cache() { static host_ptr_cache cache; return cache; } static std::shared_ptr allocate_gpu(std::size_t sz, bool host = false) { void* alloc_ptr = nullptr; auto status = host ? hipHostMalloc(&alloc_ptr, sz) : hipMalloc(&alloc_ptr, sz); if(status != hipSuccess) { if(host) MIGRAPHX_THROW("Gpu allocation failed: " + hip_error(status)); else return allocate_gpu(sz, true); } assert(alloc_ptr != nullptr); std::shared_ptr result = share(hip_ptr{alloc_ptr}); if(host) { get_host_ptr_cache().put(result); } return result; } static std::shared_ptr register_on_gpu(void* ptr, std::size_t sz) { std::shared_ptr result = get_host_ptr_cache().get(ptr); if(result) { return result; } auto status = hipHostRegister(ptr, sz, hipHostRegisterMapped); if(status != hipSuccess) MIGRAPHX_THROW("Gpu register failed: " + hip_error(status)); result = share(hip_host_ptr{ptr}); get_host_ptr_cache().put(result); return result; } template static std::vector read_from_gpu(const void* x, std::size_t sz) { gpu_sync(); std::vector result(sz); assert(not is_device_ptr(result.data())); if(not is_device_ptr(x)) { MIGRAPHX_THROW( "read_from_gpu() requires Src buffer to be on the GPU, Copy from gpu failed\n"); } auto status = hipMemcpy(result.data(), x, sz * sizeof(T), hipMemcpyDeviceToHost); if(status != hipSuccess) MIGRAPHX_THROW("Copy from gpu failed: " + hip_error(status)); // NOLINT return result; } static std::shared_ptr write_to_gpu(const void* x, std::size_t sz, bool host = false) { gpu_sync(); auto result = allocate_gpu(sz, host); assert(is_device_ptr(result.get())); assert(not is_device_ptr(x)); auto status = hipMemcpy(result.get(), x, sz, hipMemcpyHostToDevice); if(status != hipSuccess) MIGRAPHX_THROW("Copy to gpu failed: " + hip_error(status)); return result; } argument allocate_gpu(const shape& s, bool host) { auto p = allocate_gpu(s.bytes() + 1, host); return {s, [p]() mutable { return reinterpret_cast(p.get()); }}; } argument register_on_gpu(const argument& arg) { auto arg_shared = arg.share(); auto p = register_on_gpu(arg_shared.data(), arg_shared.get_shape().bytes()); auto s = arg_shared.get_shape(); return {s, [p, a = std::move(arg_shared)]() mutable { return get_device_ptr(p.get()); }}; } argument to_gpu(const argument& arg, bool host) { argument result; shape arg_shape = arg.get_shape(); if(arg_shape.type() == shape::tuple_type) { std::vector sub_obj = arg.get_sub_objects(); std::vector res_args; migraphx::transform( sub_obj, std::back_inserter(res_args), [&](const auto& x) { return to_gpu(x, host); }); result = argument{res_args}; } else { auto p = write_to_gpu(arg.data(), arg.get_shape().bytes(), host); result = {arg.get_shape(), p}; } return result; } argument from_gpu(const argument& arg) { argument result; shape arg_shape = arg.get_shape(); if(arg_shape.type() == shape::tuple_type) { std::vector sub_obj = arg.get_sub_objects(); std::vector res_args; migraphx::transform( sub_obj, std::back_inserter(res_args), [&](const auto& x) { return from_gpu(x); }); result = argument{res_args}; } else { auto v = read_from_gpu(arg.data(), arg.get_shape().bytes()); // cppcheck-suppress returnDanglingLifetime result = {arg.get_shape(), [v]() mutable { return v.data(); }}; } return result; } void set_device(std::size_t id) { auto status = hipSetDevice(id); if(status != hipSuccess) MIGRAPHX_THROW("Error setting device: " + hip_error(status)); } void gpu_sync() { auto status = hipDeviceSynchronize(); if(status != hipSuccess) MIGRAPHX_THROW("hip device synchronization failed: " + hip_error(status)); } void gpu_sync(const context& ctx) { ctx.finish(); } static void hip_async_memset(context& ctx, const argument& dst, int value) { std::size_t dst_size = dst.get_shape().bytes(); auto status = hipMemsetAsync(dst.data(), value, dst_size, ctx.get_stream().get()); if(status != hipSuccess) MIGRAPHX_THROW("Gpu fill failed: " + hip_error(status)); } static void hip_async_copy(context& ctx, const argument& src, const argument& dst, hipMemcpyKind kind) { std::size_t src_size = src.get_shape().bytes(); std::size_t dst_size = dst.get_shape().bytes(); if(src_size > dst_size) MIGRAPHX_THROW("Not enough memory available in destination to do copy"); auto status = hipMemcpyAsync(dst.data(), src.data(), src_size, kind, ctx.get_stream().get()); if(status != hipSuccess) MIGRAPHX_THROW("Gpu copy failed: " + hip_error(status)); } void gpu_copy(context& ctx, const argument& src, const argument& dst) { // Workaround: Use contiguous as hip's memcpy is broken device::contiguous(ctx.get_stream().get(), dst, src); // hip_async_copy(ctx, src, dst, hipMemcpyDeviceToDevice); } void copy_to_gpu(context& ctx, const argument& src, const argument& dst) { if(src.get_shape() == dst.get_shape() and dst.get_shape().packed()) { hip_async_copy(ctx, src, dst, hipMemcpyHostToDevice); } else { gpu_copy(ctx, register_on_gpu(src), dst); } } void copy_from_gpu(context& ctx, const argument& src, const argument& dst) { if(src.get_shape() == dst.get_shape() and dst.get_shape().packed()) { hip_async_copy(ctx, src, dst, hipMemcpyDeviceToHost); } else { gpu_copy(ctx, src, register_on_gpu(dst)); } } argument get_preallocation(context& ctx, const std::string& id) { return ctx.get_current_device().preallocations.at(id); } void gpu_fill(context& ctx, const argument& dst, int value) { if(dst.get_sub_objects().empty()) { // TODO: Handle non-packed tensor when value is not 0 assert(dst.get_shape().packed() and value == 0); hip_async_memset(ctx, dst, value); } else { for(const auto& arg : dst.get_sub_objects()) gpu_fill(ctx, arg, value); } } void store_preallocated_param(context& ctx, const std::string& id, const argument& a) { ctx.get_current_device().preallocations[id] = a; } // clang-format off } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/hip_gemm_impl.cpp000066400000000000000000000740541510465702400231170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #if MIGRAPHX_USE_HIPBLASLT #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using microseconds = std::chrono::duration; static hipDataType compute_to_hip_type(hipblasComputeType_t type) { if(type == HIPBLAS_COMPUTE_32F) return HIP_R_32F; if(type == HIPBLAS_COMPUTE_32I) return HIP_R_32I; MIGRAPHX_THROW("HIPBLAS_GEMM: conversion from hipComputeType_t to hipDataType failed"); } // Convert hipBLAS datatypes to equivalent MIGraphX data types static hipDataType get_type_hipblas(shape::type_t type) { switch(type) { case shape::double_type: return HIP_R_64F; case shape::float_type: return HIP_R_32F; case shape::half_type: return HIP_R_16F; case shape::int8_type: return HIP_R_8I; case shape::uint8_type: return HIP_R_8U; case shape::int32_type: return HIP_R_32I; case shape::uint32_type: return HIP_R_32U; case shape::fp8e4m3fnuz_type: return HIP_R_8F_E4M3_FNUZ; case shape::fp8e5m2fnuz_type: return HIP_R_8F_E5M2_FNUZ; case shape::fp8e4m3fn_type: return HIP_R_8F_E4M3; case shape::fp8e5m2_type: return HIP_R_8F_E5M2; case shape::fp4x2_type: case shape::tuple_type: case shape::bool_type: case shape::uint16_type: case shape::int16_type: case shape::int64_type: case shape::uint64_type: MIGRAPHX_THROW("HIPBLAS_GEMM: data type not supported!"); case shape::bf16_type: return HIP_R_16BF; } MIGRAPHX_THROW("HIPBLAS_GEMM: data type not supported!"); } void blas_shape_hip(const shape& in_shape) { if(in_shape.lens().size() < 2) return; auto s = in_shape.normalize_standard(); if(std::none_of(s.strides().end() - 2, s.strides().end(), [](auto i) { return i == 1; })) MIGRAPHX_THROW("GPU_GEMM: needs to have one matrix stride as 1"); if(std::any_of(s.strides().end() - 2, s.strides().end(), [](auto i) { return i == 0; })) MIGRAPHX_THROW("GPU_GEMM: matrix dimensions can't be broadcasted"); if(s.lens().size() < 3) return; shape batch_shape{s.type(), {s.lens().begin(), s.lens().end() - 2}, {s.strides().begin(), s.strides().end() - 2}}; auto batch_shapes = reduce_dims({batch_shape}); if(batch_shapes.front().lens().size() != 1) MIGRAPHX_THROW("GPU_GEMM: Batch dimension is not collapsible"); } shape transpose_batch_hip(const shape& s, unsigned trans_batch) { if(trans_batch == 0) return s; if(s.lens().size() < 3) return s; auto batch = s.lens().size() - 3; std::vector perm(s.lens().size()); std::iota(perm.begin(), perm.end(), 0); std::swap(perm[batch], perm[batch + trans_batch]); return shape::from_permutation(s.type(), s.lens(), perm); } static bool is_transposed_hip(const shape& s) { return s.transposed() and s.strides().back() != 1; } static int32_t get_batch_stride_hip(const shape& s) { // This value is not needed for non-strided inputs if(s.strides().size() < 3) return 0; else return s.strides()[s.strides().size() - 3]; } /** * Wrapper for multiple hipBLASLt calls. The constructor creates parameters for * these calls based on data shapes and other values contained in the associated * instruction and operation. */ struct hip_gemm_impl { hip_gemm_impl(const shape& output_shape, std::vector input_shapes, float alpha_param, float beta_param) : alpha(alpha_param), beta(beta_param), is_3inputs(input_shapes.size() == 5) { std::transform(input_shapes.begin(), input_shapes.end(), input_shapes.begin(), [&](const shape& s) { return s.normalize_standard(); }); if(not is_3inputs) { beta = 0; } // Create lambdas that will cast alpha, beta to the output shape's type // and retain the values being pointed to output_shape.visit_type([&](auto as) { if(as.is_integral()) { int32_t alpha_r = int32_t(alpha); int32_t beta_r = int32_t(beta); get_alpha = [=] { return &alpha_r; }; get_beta = [=] { return &beta_r; }; } else { get_alpha = [=] { return α }; get_beta = [=] { return β }; } }); transa = is_transposed_hip(input_shapes[0]); transb = is_transposed_hip(input_shapes[1]); op_a = transa ? HIPBLAS_OP_T : HIPBLAS_OP_N; op_b = transb ? HIPBLAS_OP_T : HIPBLAS_OP_N; auto n_dim = output_shape.lens().size(); auto dim_0 = n_dim - 2; auto dim_1 = n_dim - 1; // Leading dimensions of matrices lda = input_shapes[0].strides()[transa ? dim_1 : dim_0]; ldb = input_shapes[1].strides()[transb ? dim_1 : dim_0]; ldc = is_3inputs ? input_shapes[2].strides()[dim_0] : input_shapes[3].strides()[dim_0]; ldd = is_3inputs ? input_shapes[4].strides()[dim_0] : ldc; auto out_lens = output_shape.lens(); m = out_lens[dim_0]; n = out_lens[dim_1]; k = input_shapes[0].lens()[dim_1]; a_stride = get_batch_stride_hip(input_shapes[0]); b_stride = get_batch_stride_hip(input_shapes[1]); c_stride = is_3inputs ? get_batch_stride_hip(input_shapes[2]) : get_batch_stride_hip(input_shapes[3]); d_stride = is_3inputs ? get_batch_stride_hip(input_shapes[4]) : c_stride; num_matrices = std::accumulate( out_lens.rbegin() + 2, out_lens.rend(), std::size_t{1}, std::multiplies()); arg_type = get_type_hipblas(input_shapes[0].type()); output_type = is_3inputs ? get_type_hipblas(input_shapes[4].type()) : get_type_hipblas(input_shapes[3].type()); if(arg_type == HIP_R_8I or arg_type == HIP_R_8U) { compute_type = HIPBLAS_COMPUTE_32I; } else { compute_type = HIPBLAS_COMPUTE_32F; } if(op_a == HIPBLAS_OP_T) { hipblaslt_invoke( [&]() { return hipblasLtMatrixLayoutCreate(&mat_a, arg_type, m, k, lda); }); } else { hipblaslt_invoke( [&]() { return hipblasLtMatrixLayoutCreate(&mat_a, arg_type, k, m, lda); }); } if(op_b == HIPBLAS_OP_T) { hipblaslt_invoke( [&]() { return hipblasLtMatrixLayoutCreate(&mat_b, arg_type, k, n, ldb); }); } else { hipblaslt_invoke( [&]() { return hipblasLtMatrixLayoutCreate(&mat_b, arg_type, n, k, ldb); }); } hipblaslt_invoke( [&]() { return hipblasLtMatrixLayoutCreate(&mat_c, output_type, n, m, ldc); }); if(is_3inputs) { hipblaslt_invoke( [&]() { return hipblasLtMatrixLayoutCreate(&mat_d, output_type, n, m, ldd); }); } if(num_matrices > 1) { hipblaslt_invoke([&]() { return hipblasLtMatrixLayoutSetAttribute(mat_a, HIPBLASLT_MATRIX_LAYOUT_BATCH_COUNT, &num_matrices, sizeof(num_matrices)); }); hipblaslt_invoke([&]() { return hipblasLtMatrixLayoutSetAttribute(mat_b, HIPBLASLT_MATRIX_LAYOUT_BATCH_COUNT, &num_matrices, sizeof(num_matrices)); }); hipblaslt_invoke([&]() { return hipblasLtMatrixLayoutSetAttribute(mat_c, HIPBLASLT_MATRIX_LAYOUT_BATCH_COUNT, &num_matrices, sizeof(num_matrices)); }); hipblaslt_invoke([&]() { return hipblasLtMatrixLayoutSetAttribute( mat_a, HIPBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET, &a_stride, sizeof(a_stride)); }); hipblaslt_invoke([&]() { return hipblasLtMatrixLayoutSetAttribute( mat_b, HIPBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET, &b_stride, sizeof(b_stride)); }); hipblaslt_invoke([&]() { return hipblasLtMatrixLayoutSetAttribute( mat_c, HIPBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET, &c_stride, sizeof(c_stride)); }); if(is_3inputs) { hipblaslt_invoke([&]() { return hipblasLtMatrixLayoutSetAttribute(mat_d, HIPBLASLT_MATRIX_LAYOUT_BATCH_COUNT, &num_matrices, sizeof(num_matrices)); }); hipblaslt_invoke([&]() { return hipblasLtMatrixLayoutSetAttribute( mat_d, HIPBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET, &d_stride, sizeof(d_stride)); }); } } hipblaslt_invoke([&]() { return hipblasLtMatmulDescCreate( &hipblaslt_desc, compute_type, compute_to_hip_type(compute_type)); }); hipblaslt_invoke([&]() { return hipblasLtMatmulDescSetAttribute( hipblaslt_desc, HIPBLASLT_MATMUL_DESC_TRANSB, &op_a, sizeof(int32_t)); }); hipblaslt_invoke([&]() { return hipblasLtMatmulDescSetAttribute( hipblaslt_desc, HIPBLASLT_MATMUL_DESC_TRANSA, &op_b, sizeof(int32_t)); }); // Transfer ownership of raw pointers to managed pointers. managed_hipblaslt_desc.reset(hipblaslt_desc); managed_mat_a.reset(mat_a); managed_mat_b.reset(mat_b); managed_mat_c.reset(mat_c); if(is_3inputs) { managed_mat_d.reset(mat_d); } } ~hip_gemm_impl() {} struct solution { solution() : handle(nullptr), preference(nullptr) {} auto get_hipblaslt_preference() { if(hbltpreference == nullptr) { hbltpreference = create_hipblaslt_preference_ptr(); } assert(hbltpreference.get() != nullptr); return hbltpreference.get(); } void init(context& ctx) { if(handle == nullptr) { handle = ctx.get_stream().get_hipblaslt(); preference = get_hipblaslt_preference(); } } auto& get_result(context& ctx, hip_gemm_impl& gemm, int32_t idx) { init(ctx); if(idx == 0) { // use default solution const int n_sol = 1; int returned_algo_count; heuristic_result.resize(n_sol); uint64_t max_workspace = std::numeric_limits::max(); hipblaslt_invoke([&]() { return hipblasLtMatmulPreferenceSetAttribute( preference, HIPBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES, &max_workspace, sizeof(uint64_t)); }); hipblaslt_invoke([&]() { return hipblasLtMatmulAlgoGetHeuristic(handle, gemm.hipblaslt_desc, gemm.mat_b, gemm.mat_a, gemm.mat_c, gemm.is_3inputs ? gemm.mat_d : gemm.mat_c, preference, n_sol, heuristic_result.data(), &returned_algo_count); }); if(returned_algo_count != n_sol) { std::cout << "less solution found! request: " << n_sol << ", found: " << returned_algo_count << std::endl; } } else { // query for the solutions. 1st as the best. std::vector algo_index = {idx}; hipblaslt_invoke([&]() { return hipblaslt_ext::getAlgosFromIndex(handle, algo_index, heuristic_result); }); assert(heuristic_result.size() == 1); } return heuristic_result; } private: hipblasLtHandle_t handle; hipblasLtMatmulPreference_t preference; std::vector heuristic_result; shared hbltpreference = nullptr; } solution; /** * Helper method to create that subset of a long hipblaslt argument list that is common * to multiple "hipblasLtMatmul" calls. * * The hipblaslt GEMM API handles inputs and output matrices as * column-major format. When doing a C = A * B, we actually do * C^T = (B^T) * (A^T). That is the reason we input args[1] as * A and args[0] as B in calling the hipblaslt GEMM. * * */ auto create_hipblaslt_args_common(context& ctx, const std::vector& args, int32_t solution_idx) { auto* algo = &solution.get_result(ctx, *this, solution_idx)[0].algo; size_t workspace_size = ((is_3inputs ? args[3] : args[2]).get_shape()).bytes(); return pack(ctx.get_stream().get_hipblaslt(), hipblaslt_desc, get_alpha(), // alpha args[1].data(), // A mat_b, // Adesc args[0].data(), // B mat_a, // Bdesc get_beta(), // beta is_3inputs ? args[2].data() : args[3].data(), // C mat_c, // Cdesc is_3inputs ? args[4].data() : args[3].data(), // D is_3inputs ? mat_d : mat_c, // Ddesc algo, // algo is_3inputs ? args[3].data() : args[2].data(), // workspace workspace_size, // workspaceSizeInBytes ctx.get_stream().get() // stream ); } auto create_hipblaslt_supporting_args_common(context& ctx, const std::vector& args, hipblasLtMatmulAlgo_t& algo, size_t& workspace_size) const { (void)(args); return pack(ctx.get_stream().get_hipblaslt(), hipblaslt_desc, get_alpha(), mat_b, mat_a, get_beta(), mat_c, is_3inputs ? mat_d : mat_c, algo, workspace_size); } void run(context& ctx, const std::vector& input_args, int32_t solution_idx = 0) // const { auto common_args = create_hipblaslt_args_common(ctx, input_args, solution_idx); hipblaslt_invoke(&hipblasLtMatmul, common_args); } auto validate(context& ctx, const std::vector& input_shapes, int32_t solution_idx) // const { // Create dummy arguments for the shapes, and call the overloaded method std::vector input_args; std::transform(input_shapes.begin(), input_shapes.end(), std::back_inserter(input_args), [](const shape& x) { return to_gpu(generate_argument(x)); }); return validate(ctx, input_args, solution_idx); } /** * Checks a particular solution for validity by running it (could be invalid if this model was * tuned with a different hipBLASLt version) * * @return Returns either solution_idx if valid, or else the default value 0 * if not. The default does not mean list index 0, but tells the picker * to choose a solution. */ int32_t validate(context& ctx, const std::vector& input_args, int32_t solution_idx) // const { auto common_args = create_hipblaslt_args_common(ctx, input_args, solution_idx); auto check_valid = hipblaslt_invoke(&hipblasLtMatmul, common_args, false); if(check_valid != HIPBLAS_STATUS_SUCCESS) { std::cerr << "WARNING: tuned solution is invalid; reverting to default" << std::endl; return 0; } return solution_idx; } /** * Get workspace size for the solution index: Gets algo from the solution index, * and calls matmulIsAlgoSupported() to get the workspace size. */ size_t get_workspace_size(context& ctx, const std::vector& input_shapes, int32_t solution_idx) { size_t workspace_size = hipblaslt_workspace_size; std::vector input_args; std::transform(input_shapes.begin(), input_shapes.end(), std::back_inserter(input_args), [](const shape& x) { return to_gpu(generate_argument(x)); }); std::vector algo_index = {solution_idx}; std::vector heuristic_result; if(solution_idx == 0) { heuristic_result = solution.get_result(ctx, *this, 0); } else { hipblaslt_invoke([&]() { return hipblaslt_ext::getAlgosFromIndex( ctx.get_stream().get_hipblaslt(), algo_index, heuristic_result); }); } // Return default workspace size when no algo is provided. if(heuristic_result.empty()) { std::cout << "No hipBLASLt algo returned for solution index: " << solution_idx << std::endl; return workspace_size; } auto algo = heuristic_result[0].algo; size_t ret_workspace_size = 0; auto supporting_args = create_hipblaslt_supporting_args_common(ctx, input_args, algo, ret_workspace_size); auto status = hipblaslt_invoke(&hipblaslt_ext::matmulIsAlgoSupported, supporting_args, false); // If algo is supported, update the workspace size to the actual size needed. // Otherwise, use the default workspace size. if(status == HIPBLAS_STATUS_SUCCESS) { // TODO: Remove this check once issues with '0' workspace size are resolved. // Temporarily, we use the approach where, if the returned workspace size is '0', // we use the default workspace size. // Otherwise, we use the returned workspace size. if(ret_workspace_size != 0) workspace_size = ret_workspace_size; } return workspace_size; } /** * Find best hipBLASLt solution: Get list of solutions and try them all, returning the index * of the fastest one. */ int tune(context& ctx, const std::vector& input_shapes) { std::vector input_args; std::transform(input_shapes.begin(), input_shapes.end(), std::back_inserter(input_args), [](const shape& x) { return to_gpu(generate_argument(x)); }); std::vector result; hipblaslt_invoke([&]() { return hipblaslt_ext::getAllAlgos(ctx.get_stream().get_hipblaslt(), hipblaslt_ext::GemmType::HIPBLASLT_GEMM, op_a, op_b, arg_type, arg_type, output_type, output_type, compute_type, result); }); std::vector solution_indices; int returned_algo_count = result.size(); for(int i = 0; i < returned_algo_count; i++) { auto algo = result[i].algo; size_t ret_workspace_size = 0; if(hipblaslt_ext::matmulIsAlgoSupported(ctx.get_stream().get_hipblaslt(), hipblaslt_desc, get_alpha(), mat_b, mat_a, get_beta(), mat_c, is_3inputs ? mat_d : mat_c, algo, ret_workspace_size) == HIPBLAS_STATUS_SUCCESS) { // To balance performance and memory usage, solutions for exhaustive tuning // are only considered if their workspace size is less than or equal to 128MB. // This avoids using excessive memory for potentially minor speed improvements. if(ret_workspace_size <= hipblaslt_workspace_size / 2) solution_indices.push_back(hipblaslt_ext::getIndexFromAlgo(algo)); } } double best_time = std::numeric_limits::max(); double first_time = -1; // Initialize to default solution index int32_t best_sol = 0; // If no valid/supported solution is returned, use hipblasLtMatmulAlgoGetHeuristic // to get an algo and use solution index from that algo. if(solution_indices.empty()) { auto algo = solution.get_result(ctx, *this, 0)[0].algo; solution_indices.push_back(hipblaslt_ext::getIndexFromAlgo(algo)); } // Number of runs for separate time measurements. const int hot_calls = 40; const int number_of_bundles = 4; for(auto sol : solution_indices) { auto run_sol_idx_fn = [&] { run(ctx, input_args, sol); }; // Measure the time taken for the current solution index by running it // hot_calls x number_of_bundles times. // time_loop takes care of doing 1 warmup run. double host_time = time_loop(ctx, number_of_bundles, hot_calls, run_sol_idx_fn); // dev/evaluation only: track time for first solution. if(first_time < 0) first_time = host_time; // track current best if(host_time < best_time) { best_sol = sol; best_time = host_time; } } std::cout << "Winning GEMM solution: " << best_sol << " in " << best_time << " ms, beats " << first_time << "ms" << std::endl; return best_sol; } // hipblaslt size_t num_matrices = 0; uint64_t m = 0; uint64_t n = 0; uint64_t k = 0; bool transa = false; bool transb = false; float alpha = 0; float beta = 0; std::function get_alpha{}; std::function get_beta{}; int64_t lda = 0; int64_t ldb = 0; int64_t ldc = 0; int64_t ldd = 0; int64_t a_stride = 0; int64_t b_stride = 0; int64_t c_stride = 0; int64_t d_stride = 0; bool is_3inputs = true; hipDataType arg_type = HIP_R_32F; hipblasComputeType_t compute_type = HIPBLAS_COMPUTE_32F; hipDataType output_type = HIP_R_32F; hipblasLtMatmulDesc_t hipblaslt_desc; hipblasOperation_t op_a; hipblasOperation_t op_b; using hipblaslt_matrix_layout = MIGRAPHX_MANAGE_PTR(hipblasLtMatrixLayout_t, hipblasLtMatrixLayoutDestroy); using hipblaslt_mat_mul_desc = MIGRAPHX_MANAGE_PTR(hipblasLtMatmulDesc_t, hipblasLtMatmulDescDestroy); hipblaslt_matrix_layout managed_mat_a, managed_mat_b, managed_mat_c, managed_mat_d; hipblaslt_mat_mul_desc managed_hipblaslt_desc; hipblasLtMatrixLayout_t mat_a, mat_b, mat_c, mat_d; hipblasLtHandle_t handle; hipblasLtMatmulPreference_t preference; }; // hip_gemm_impl void hip_gemm_compute(context& ctx, const shape& output_shape, const std::vector& args, float alpha, float beta, int32_t solution_idx) { std::vector input_shapes; std::transform(args.begin(), args.end(), std::back_inserter(input_shapes), [](const argument& x) { return x.get_shape(); }); auto gemm_item = hip_gemm_impl(output_shape, input_shapes, alpha, beta); gemm_item.run(ctx, args, solution_idx); } static value hip_gemm_problem(const shape& output_shape, std::vector input_shapes) { input_shapes.push_back(output_shape); return to_value(input_shapes); } static void hip_gemm_save_solution(context& ctx, const shape& output_shape, const std::vector& input_shapes, int32_t solution_idx) { ctx.get_problem_cache().insert( "hipblaslt", hip_gemm_problem(output_shape, input_shapes), solution_idx); } int32_t hip_gemm_finalize(context& ctx, const shape& output_shape, const std::vector& input_shapes, float alpha, float beta, int32_t solution_idx) { auto gemm_item = hip_gemm_impl(output_shape, input_shapes, alpha, beta); if(solution_idx == 0) { solution_idx = gemm_item.tune(ctx, input_shapes); hip_gemm_save_solution(ctx, output_shape, input_shapes, solution_idx); } // If a tuned solution index is already given, don't tune again but validate // in case the data was tuned with a different hipBLASLt version. else { solution_idx = gemm_item.validate(ctx, input_shapes, solution_idx); } return solution_idx; } int32_t hip_gemm_default_solution(context& ctx, const shape& output_shape, const std::vector& input_shapes) { auto sol = ctx.get_problem_cache().get("hipblaslt", hip_gemm_problem(output_shape, input_shapes)); if(sol.has_value()) return sol->to(); return 0; } size_t hip_gemm_workspace_size(context& ctx, const shape& output_shape, const std::vector& input_shapes, float alpha, float beta, int32_t solution_idx) { auto gemm_item = hip_gemm_impl(output_shape, input_shapes, alpha, beta); return gemm_item.get_workspace_size(ctx, input_shapes, solution_idx); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_USE_HIPBLASLT ROCm-AMDMIGraphX-46524e8/src/targets/gpu/hipblaslt.cpp000066400000000000000000000044001510465702400222570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #if MIGRAPHX_USE_HIPBLASLT // for hipblaslt only static const size_t workspace_size = hipblaslt_workspace_size; hipblaslt_handle_ptr create_hipblaslt_handle_ptr() { hipblasLtHandle_t handle; hipblasLtCreate(&handle); return hipblaslt_handle_ptr{handle}; } hipblaslt_preference_ptr create_hipblaslt_preference_ptr() { hipblasLtMatmulPreference_t preference; hipblasLtMatmulPreferenceCreate(&preference); hipblaslt_invoke([&]() { return hipblasLtMatmulPreferenceSetAttribute(preference, HIPBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES, &workspace_size, sizeof(workspace_size)); }); return hipblaslt_preference_ptr{preference}; } #endif // MIGRAPHX_USE_HIPBLASLT } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/hiprtc/000077500000000000000000000000001510465702400210645ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/hiprtc/CMakeLists.txt000066400000000000000000000037141510465702400236310ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### add_executable(migraphx-hiprtc-driver main.cpp ) rocm_clang_tidy_check(migraphx-hiprtc-driver) # On Windows, the driver's default 1MB stack size is not enough - increasing to 4MB. set(STACK_SIZE 4194304) if(MSVC) target_link_options(migraphx-hiprtc-driver PRIVATE /STACK:${STACK_SIZE}) elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") target_link_options(migraphx-hiprtc-driver PRIVATE -Xlinker /stack:${STACK_SIZE}) endif() target_link_libraries(migraphx-hiprtc-driver PRIVATE migraphx_gpu) add_dependencies(migraphx_all_targets migraphx-hiprtc-driver) rocm_install_targets( TARGETS migraphx-hiprtc-driver ) ROCm-AMDMIGraphX-46524e8/src/targets/gpu/hiprtc/main.cpp000066400000000000000000000067731510465702400225310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif static std::vector read_stdin() { #ifdef _WIN32 // Set stream translation mode to BINARY to suppress translations. // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setmode?view=msvc-170 auto old_mode = _setmode(_fileno(stdin), _O_BINARY); if(old_mode == -1) MIGRAPHX_THROW(std::strerror(errno)); #endif std::vector result; std::array buffer{}; std::size_t len = 0; while((len = std::fread(buffer.data(), 1, buffer.size(), stdin)) > 0) { if(std::ferror(stdin) != 0 and std::feof(stdin) == 0) MIGRAPHX_THROW(std::strerror(errno)); result.insert(result.end(), buffer.data(), buffer.data() + len); } #ifdef _WIN32 // Reset to the previously set translation mode. _setmode(_fileno(stdin), old_mode); #endif return result; } int main(int argc, char const* argv[]) { if(argc < 2 or migraphx::contains({"-h", "--help", "-v", "--version"}, std::string(argv[1]))) { std::cout << "USAGE:" << std::endl; std::cout << " "; std::cout << "Used internally by migraphx to compile hip programs out-of-process." << std::endl; std::exit(0); } std::string output_name = argv[1]; bool quiet = false; try { auto v = migraphx::from_msgpack(read_stdin()); quiet = v.at("quiet").to(); std::vector srcs; migraphx::from_value(v.at("srcs"), srcs); auto out = migraphx::gpu::compile_hip_src_with_hiprtc(std::move(srcs), v.at("params").to_vector(), v.at("arch").to(), quiet); if(not out.empty()) migraphx::write_buffer(output_name, out.front()); } catch(const std::exception& err) { if(not quiet) std::cerr << err.what() << std::endl; } } ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/000077500000000000000000000000001510465702400212165ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/000077500000000000000000000000001510465702400230355ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/000077500000000000000000000000001510465702400236305ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/abs.hpp000066400000000000000000000042651510465702400251150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ABS_HPP #define MIGRAPHX_GUARD_RTGLIB_ABS_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; #if MIGRAPHX_USE_MIOPEN struct miopen_abs { op::abs op; shared ad; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::abs"; } shape compute_shape(const std::vector& inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; void finalize(context&, const shape&, const std::vector&); std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/allocation_model.hpp000066400000000000000000000035311510465702400276500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_GPU_ALLOCATION_MODEL_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_GPU_ALLOCATION_MODEL_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct MIGRAPHX_GPU_EXPORT gpu_allocation_model { std::string name() const; std::string copy() const; operation allocate(const shape& s) const; operation preallocate(const shape& s, const std::string& id) const; bool needs_out_params() const { return true; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/analyze_streams.hpp000066400000000000000000000031371510465702400275460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_ANALYZE_STREAMS_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_ANALYZE_STREAMS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { MIGRAPHX_GPU_EXPORT std::vector analyze_streams(const module& m); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/argmax.hpp000066400000000000000000000040631510465702400256230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ARGMAX_HPP #define MIGRAPHX_GUARD_RTGLIB_ARGMAX_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_argmax { op::argmax op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::argmax"; } shape compute_shape(const std::vector& inputs) const; argument compute(context& ctx, const shape&, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/argmin.hpp000066400000000000000000000040631510465702400256210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_ARGMIN_HPP #define MIGRAPHX_GUARD_RTGLIB_ARGMIN_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_argmin { op::argmin op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::argmin"; } shape compute_shape(const std::vector& inputs) const; argument compute(context& ctx, const shape&, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/ck.hpp000066400000000000000000000125441510465702400247440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_CK_HPP #define MIGRAPHX_GUARD_GPU_CK_HPP #include #include #include #include #include #include "ck/host/device_gemm_multiple_d.hpp" #include "ck/host/device_batched_gemm_softmax_gemm.hpp" namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #ifndef _WIN32 MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_CK); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_LOG_CK_GEMM); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_CK_DEBUG); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TUNE_CK); #endif // NOLINTNEXTLINE const char* const disable_warning_pragma = R"__migraphx__( #pragma clang diagnostic push #pragma clang diagnostic ignored "-Weverything" ${content} #pragma clang diagnostic pop )__migraphx__"; template std::string ck_disable_warnings(P p) { return interpolate_string(disable_warning_pragma, {{"content", std::string{p.data(), p.size()}}}); } static std::unordered_map create_ck_header_strings() { std::unordered_map result; auto ck_headers = ck::host::GetHeaders(); std::transform( ck_headers.begin(), ck_headers.end(), std::inserter(result, result.begin()), [&](auto& p) { return std::pair(p.first, ck_disable_warnings(p.second)); }); return result; } static std::vector create_ck_headers() { static const auto& header_strings = create_ck_header_strings(); std::vector srcs; std::transform(header_strings.begin(), header_strings.end(), std::back_inserter(srcs), [&](auto& p) { return src_file{p}; }); return srcs; } static inline const std::vector& ck_headers() { static const auto& headers = create_ck_headers(); return headers; } inline bool transposed_matrix(const shape& s) { return s.strides().back() != 1; } inline ck::host::DataType get_type(const shape& s) { if(s.type() == shape::half_type) return ck::host::DataType::Half; else if(s.type() == shape::float_type) return ck::host::DataType::Float; else if(s.type() == shape::int8_type) return ck::host::DataType::Int8; else if(s.type() == shape::int32_type) return ck::host::DataType::Int32; MIGRAPHX_THROW("Unsupported ck type"); } inline std::size_t get_batch_count(const shape& s) { return std::accumulate( s.lens().rbegin() + 2, s.lens().rend(), std::size_t{1}, std::multiplies()); } inline void fold_batch_dims(shape& s) { auto lens = s.lens(); if(lens.size() <= 2) return; auto batch_count = get_batch_count(s); auto m1 = lens.at(lens.size() - 2); auto m2 = lens.at(lens.size() - 1); if(transposed_matrix(s)) s = shape{s.type(), {m1, m2 * batch_count}}; else s = shape{s.type(), {m1 * batch_count, m2}}; } inline void remove_batch_dims(shape& s) { auto lens = s.lens(); if(lens.size() <= 2) return; auto m1 = lens.at(lens.size() - 2); auto m2 = lens.at(lens.size() - 1); s = shape{s.type(), {m1, m2}}; } inline bool standard_batch(const shape& s) { if(s.lens().size() < 3) return true; std::vector lens(s.lens().begin(), s.lens().end() - 2); std::vector strides(s.strides().begin(), s.strides().end() - 2); auto base = *(s.lens().end() - 2) * *(s.lens().end() - 1); std::transform(strides.begin(), strides.end(), strides.begin(), [&](auto stride) { return stride / base; }); return shape{s.type(), lens, strides}.standard(); } inline bool can_fold_batch(const std::vector& inputs) { const auto& b_shape = inputs[1]; if(std::any_of(inputs.begin() + 2, inputs.end() - 1, [](auto input) { return not standard_batch(input); })) return false; const auto& b_strides = b_shape.strides(); return std::all_of( b_strides.begin(), b_strides.end() - 2, [](auto stride) { return stride == 0; }); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_CK_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/code_object_op.hpp000066400000000000000000000067001510465702400273020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_CODE_OBJECT_OP_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_CODE_OBJECT_OP_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct code_object_op { value::binary code_object{}; std::string symbol_name = ""; std::size_t global = 0; std::size_t local = 0; std::vector expected_inputs{}; shape output{}; std::int64_t output_arg = -1; kernel k{}; template static auto reflect(Self& self, F f) { return pack(f(self.code_object, "code_object"), f(self.symbol_name, "symbol_name"), f(self.global, "global"), f(self.local, "local"), f(self.expected_inputs, "expected_inputs"), f(self.output, "output"), f(self.output_arg, "output_arg")); } value attributes() const { return {{"group", group()}}; } std::string group() const { return "gpu::code_object::" + symbol_name; } std::string name() const { return "gpu::code_object"; } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; void finalize(context&, const shape&, const std::vector&); std::int64_t get_output_arg(std::size_t n) const { return output_arg < 0 ? n + output_arg : output_arg; } std::ptrdiff_t output_alias(const std::vector& shapes) const { return get_output_arg(shapes.size()); } friend std::ostream& operator<<(std::ostream& os, const code_object_op& op) { os << op.name() << "["; os << "code_object=" << op.code_object.size() << ","; os << "symbol_name=" << op.symbol_name << ","; os << "global=" << op.global << ","; os << "local=" << op.local << ","; if(op.output_arg != -1) os << "output_arg=" << op.output_arg << ","; os << "]"; return os; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/compile_gen.hpp000066400000000000000000000076051510465702400266320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_COMPILE_GEN_HPP #define MIGRAPHX_GUARD_GPU_COMPILE_GEN_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct shape; struct operation; namespace gpu { struct context; namespace gen { struct vectorize { std::size_t size = 1; std::size_t axis = 0; static vectorize elements(std::size_t axis, const std::vector& inputs); static vectorize elements(context& ctx, std::size_t axis, const std::vector& inputs); static vectorize elements(std::size_t axis, const std::vector& inputs, const std::vector& sizes); std::string str() const; }; struct preload { std::vector args = {}; static preload broadcasts(std::size_t axis, const std::vector& inputs); bool is_preloading() const; std::string str() const; }; struct tile { enum mode { store, load, none }; std::vector args = {}; std::size_t axis = 0; std::size_t ntiles = 0; std::size_t block_size = 0; std::vector inner{}; std::vector outer{}; static tile elements(const std::vector& inputs, std::size_t noutputs); // bool is_preloading() const; std::string str() const; }; MIGRAPHX_GPU_EXPORT std::size_t find_fast_axis(const shape& input); MIGRAPHX_GPU_EXPORT std::size_t find_fast_axis(const std::vector& inputs); std::string make_transformer_args(std::vector transformers); template std::string make_transformer_args(const Ts&... xs) { return make_transformer_args({xs.str()...}); } std::string generate_pointwise(const module& pm, const std::string& name, bool always_return_tuple = false); std::string generate_reduce(module m, const std::string& name); std::string generate_name_from_ops(const module& m, const std::string& postname = ""); struct reduce_op { std::vector inputs = {}; std::string reduction = ""; std::string init = "0"; std::string read = "op::id{}"; std::string write = "op::id{}"; void set(instruction_ref ins, const operation& op); void set(const std::string& name, const shape& input, const shape& output); std::string str() const; static std::string generate(instruction_ref ins, const std::vector& x); }; } // namespace gen } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_COMPILE_GEN_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/compile_hip.hpp000066400000000000000000000053471510465702400266420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_COMPILE_HIP_HPP #define MIGRAPHX_GUARD_RTGLIB_COMPILE_HIP_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #ifdef MIGRAPHX_USE_HIPRTC MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_HIPRTC); #endif struct hiprtc_src_file { hiprtc_src_file() = default; hiprtc_src_file(const src_file& s) : path(s.path.string()), content(s.content) {} std::string path; std::string content; template static auto reflect(Self& self, F f) { return pack(f(self.path, "path"), f(self.content, "content")); } }; MIGRAPHX_GPU_EXPORT bool hip_has_flags(const std::vector& flags); MIGRAPHX_GPU_EXPORT std::vector> compile_hip_src_with_hiprtc(std::vector srcs, const std::vector& params, const std::string& arch, bool quiet = false); MIGRAPHX_GPU_EXPORT std::vector> compile_hip_src(const std::vector& srcs, const std::vector& params, const std::string& arch, bool quiet = false); MIGRAPHX_GPU_EXPORT std::string enum_params(std::size_t count, std::string param); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/compile_hip_code_object.hpp000066400000000000000000000072361510465702400311610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_COMPILE_HIP_CODE_OBJECT_HPP #define MIGRAPHX_GUARD_GPU_COMPILE_HIP_CODE_OBJECT_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_compile_options { std::size_t global; std::size_t local; std::vector inputs; shape output; std::string kernel_name = "kernel"; std::vector params = {}; std::vector virtual_inputs = {}; std::vector additional_src_files = {}; std::int64_t output_arg = -1; /** * @brief Set the launch parameters but allow v to override the values * * @param v A value class which can have a "global" and/or "local" keys to override the default * global and local * @param compute_global A function used to compute the global based on the local * @param default_local The defaul local to use if its missing from the v parameter */ void set_launch_params(const value& v, const std::function& compute_global, std::size_t default_local = 1024); void set_launch_params(const value& v, std::size_t default_global, std::size_t default_local = 1024) { set_launch_params( v, [=](auto) { return default_global; }, default_local); } void emplace_param(std::string_view s) { params.emplace_back(s); } }; /// Compute global for n elements, but max out on target-specific upper limit MIGRAPHX_GPU_EXPORT std::function compute_global_for(const context& ctx, std::size_t n, std::size_t over = 1); MIGRAPHX_GPU_EXPORT operation compile_hip_code_object(context& ctx, const std::string& content, hip_compile_options options); MIGRAPHX_GPU_EXPORT std::size_t compute_block_size(const context& ctx, std::size_t n, std::size_t max_block_size = 1024); template std::string generate_index_ints(const std::vector& v) { return "index_ints<" + to_string_range(v) + ">{}"; } MIGRAPHX_GPU_EXPORT std::string generate_make_shape(const shape& s); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_COMPILE_HIP_CODE_OBJECT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/compile_hipblaslt.hpp000066400000000000000000000045641510465702400300440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_COMPILE_HIPBLASLT_HPP #define MIGRAPHX_GUARD_GPU_COMPILE_HIPBLASLT_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct context; struct operation; namespace gpu { struct hipblaslt_op { operation op = op::identity{}; template static auto reflect(Self& self, F f) { return pack(f(self.op, "op")); } std::string name() const { return "gpu::hipblaslt_op"; } shape compute_shape(std::vector inputs) const { inputs.push_back(inputs.back()); return op.compute_shape(inputs); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; MIGRAPHX_REGISTER_OP(hipblaslt_op); struct compile_hipblaslt { context* ctx = nullptr; std::string name() const { return "gpu::compile_hipblaslt"; } void apply(module& m) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_COMPILE_HIPBLASLT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/compile_miopen.hpp000066400000000000000000000034621510465702400273450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_COMPILE_MIOPEN_HPP #define MIGRAPHX_GUARD_GPU_COMPILE_MIOPEN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct context; struct operation; namespace gpu { struct compile_miopen { context* ctx = nullptr; std::string name() const { return "gpu::compile_miopen"; } void apply(module& m) const; std::size_t compile(operation& op, instruction_ref ins) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_COMPILE_MIOPEN_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/compile_ops.hpp000066400000000000000000000033521510465702400266550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_COMPILE_OPS_HPP #define MIGRAPHX_GUARD_GPU_COMPILE_OPS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { struct context; struct MIGRAPHX_GPU_EXPORT compile_ops { context* ctx = nullptr; bool exhaustive_tune = false; std::string name() const { return "gpu::compile_ops"; } void apply(module& m) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_COMPILE_OPS_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/compile_pointwise.hpp000066400000000000000000000034231510465702400300740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_COMPILE_POINTWISE_HPP #define MIGRAPHX_GUARD_GPU_COMPILE_POINTWISE_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { operation compile_pointwise(context& ctx, const std::vector& in_shapes, const_module_ref pm); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_COMPILE_POINTWISE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/compiler.hpp000066400000000000000000000162551510465702400261640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_COMPILER_HPP #define MIGRAPHX_GUARD_GPU_COMPILER_HPP #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct compiler_replace { compiler_replace() = default; compiler_replace(const operation& op) : code_objects{{op}} {} template compiler_replace(const operation& op, F f) : code_objects{{op}}, replace_fn(make_replace(std::move(f))) { } template compiler_replace(const operation& op, F f, Trace t) : code_objects{{op}}, replace_fn(make_replace(f)), trace_fn(t) { } template compiler_replace(const std::vector& op, F f) : code_objects{op}, replace_fn(make_replace_all(std::move(f))) { } template compiler_replace(const std::vector& op, F f, Trace t) : code_objects{op}, replace_fn(make_replace_all(std::move(f))), trace_fn(t) { } std::vector code_objects = {}; std::function replace_fn = nullptr; std::function trace_fn = nullptr; std::unordered_map fill_map = {}; template static auto make_replace(F f) { return [f = std::move(f)](const compiler_replace& cr, module& m, instruction_ref ins) { f(m, ins, cr.code_objects.front()); }; } template static auto make_replace_all(F f) { return [f = std::move(f)](const compiler_replace& cr, module& m, instruction_ref ins) { f(m, ins, cr.code_objects); }; } void replace(module& m, instruction_ref ins) const { if(replace_fn) replace_fn(*this, m, ins); else { if(code_objects.size() != 1) { MIGRAPHX_THROW("Provide custom replace function to insert multiple code objects\n"); } m.replace_instruction(ins, code_objects.front(), ins->inputs()); } } void trace(std::ostream& os, instruction_ref ins) const { if(trace_fn) trace_fn(os, ins); } }; using compiler_compile = std::function; using compiler_compile_op = std::function& inputs, const value&)>; using compiler_tuning_config = std::function(context&, instruction_ref, const operation&, bool)>; MIGRAPHX_GPU_EXPORT void register_compiler(const std::string& name, compiler_compile c, compiler_compile_op cop, compiler_tuning_config ctg); MIGRAPHX_GPU_EXPORT bool has_compiler_for(const std::string& name); MIGRAPHX_GPU_EXPORT compiler_replace compile(context& ctx, instruction_ref ins, const operation& op, const value& solution); MIGRAPHX_GPU_EXPORT operation compile_op(const std::string& name, context& ctx, const std::vector& inputs, const value& v); MIGRAPHX_GPU_EXPORT optional get_tuning_config(context& ctx, instruction_ref ins, const operation& op, bool exhaustive); template void register_compiler() { T c; for(auto&& name : c.names()) { register_compiler( name, [=](auto&&... xs) { return c.invoke_compile(rank<1>{}, std::forward(xs)...); }, [=](auto&&... xs) { return c.compile_op(std::forward(xs)...); }, [=](auto&&... xs) { return c.get_tuning_config(std::forward(xs)...); }); } } struct register_compiler_action { template static void apply() { register_compiler(); } }; template using auto_register_compiler = auto_register; template struct compiler : auto_register_compiler { const Derived& derived() const { return static_cast(*this); } optional get_tuning_config(context&, instruction_ref, const operation&, bool) const { return nullopt; } // TODO: make it a compile error if this is not overridden by Derived rather than runtime error. // Could rename Derived function to something like compile_op_impl. // Or refactor to type-erased interface. operation compile_op(context&, const std::vector&, const value&) const { MIGRAPHX_THROW("Missing override function"); } template auto invoke_compile(rank<1>, context& ctx, instruction_ref ins, operation op, const value& solution) const -> decltype(std::declval().compile(ctx, ins, std::move(op), solution)) { return derived().compile(ctx, ins, std::move(op), solution); } template auto invoke_compile(rank<0>, context& ctx, instruction_ref ins, operation op, const value& solution) const -> decltype(std::declval().compile(ctx, ins, std::move(op))) { assert(solution.empty()); (void)solution; return derived().compile(ctx, ins, std::move(op)); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_COMPILER_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/concat_gpu_opt.hpp000066400000000000000000000036021510465702400273460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CONCAT_GPU_OPT_HPP #define MIGRAPHX_GUARD_RTGLIB_CONCAT_GPU_OPT_HPP #include #include #include namespace migraphx { namespace gpu { struct concat_gpu_optimization { std::string allocate() const { return "hip::allocate"; } optional get_concat(const migraphx::operation& op) const { if(op.name() != "gpu::precompile_op") return nullopt; auto r = from_value(op.to_value().at("op")); if(r.name() == "concat") return any_cast(r); return nullopt; } }; } // namespace gpu } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/config.hpp000066400000000000000000000025331510465702400256110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_CONFIG_HPP #define MIGRAPHX_GUARD_GPU_CONFIG_HPP #include #include #endif // MIGRAPHX_GUARD_GPU_CONFIG_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/context.hpp000066400000000000000000000306471510465702400260370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CONTEXT_HPP #define MIGRAPHX_GUARD_RTGLIB_CONTEXT_HPP #include #include #include #if !MIGRAPHX_USE_MIOPEN #include #include #include #endif #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_NULL_STREAM) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_NSTREAMS) using hip_event_ptr = MIGRAPHX_MANAGE_PTR(hipEvent_t, hipEventDestroy); struct hip_device { hip_device() : device_props{} { add_stream(); } hip_device(std::size_t id, std::size_t n) : device_id(id) { auto status = hipGetDeviceProperties(&device_props, device_id); if(status != hipSuccess) MIGRAPHX_THROW("Failed to get device properties: " + hip_error(status)); // Set the device prior to Events that get created within a Context. set_device(device_id); for(std::size_t i = 0; i < n; i++) add_stream(); } struct stream { using hip_stream_ptr = MIGRAPHX_MANAGE_PTR(hipStream_t, hipStreamDestroy); stream() {} stream(std::size_t device_number) : id(device_number) {} void setup() const { set_device(id); } static hip_stream_ptr create_stream() { hipStream_t result = nullptr; auto status = hipStreamCreateWithFlags(&result, hipStreamNonBlocking); if(status != hipSuccess) MIGRAPHX_THROW("Failed to allocate stream: " + hip_error(status)); return hip_stream_ptr{result}; } hipStream_t get() { if(not enabled(MIGRAPHX_ENABLE_NULL_STREAM{})) { setup(); if(s == nullptr) s = create_stream(); assert(s.get() != nullptr); return s.get(); } return nullptr; } #if MIGRAPHX_USE_MIOPEN auto create_miopen_handle() { if(not enabled(MIGRAPHX_ENABLE_NULL_STREAM{})) return make_obj(&miopenCreateWithStream, get()); else return make_obj(&miopenCreate); } auto get_miopen() { setup(); if(mihandle == nullptr) mihandle = create_miopen_handle(); assert(mihandle.get() != nullptr); return mihandle.get(); } #endif #if MIGRAPHX_USE_ROCBLAS auto get_rocblas() { setup(); if(rbhandle == nullptr) rbhandle = create_rocblas_handle_ptr(get()); assert(rbhandle.get() != nullptr); return rbhandle.get(); } #endif #if MIGRAPHX_USE_HIPBLASLT auto get_hipblaslt() { setup(); if(hblthandle == nullptr) { hblthandle = create_hipblaslt_handle_ptr(); } assert(hblthandle.get() != nullptr); return hblthandle.get(); } #endif void wait() const { if(s == nullptr) return; setup(); auto status = hipStreamSynchronize(s.get()); if(status != hipSuccess) MIGRAPHX_THROW("Failed to wait: " + hip_error(status)); } void wait(hipEvent_t event) { setup(); auto status = hipStreamWaitEvent(get(), event, 0); if(status != hipSuccess) MIGRAPHX_THROW("Failed to wait: " + hip_error(status)); } void record(hipEvent_t event) { setup(); auto status = hipEventRecord(event, get()); if(status != hipSuccess) MIGRAPHX_THROW("Failed to record: " + hip_error(status)); } private: std::size_t id = 0; shared s = nullptr; #if MIGRAPHX_USE_MIOPEN shared mihandle = nullptr; #endif #if MIGRAPHX_USE_ROCBLAS shared rbhandle = nullptr; #endif #if MIGRAPHX_USE_HIPBLASLT shared hblthandle = nullptr; #endif }; void add_stream() { streams.emplace_back(device_id); } stream& get_stream() { return streams.at(current_stream); } stream& get_stream(std::size_t n) { return streams.at(n); } const stream& get_stream() const { return streams.at(current_stream); } const stream& get_stream(std::size_t n) const { return streams.at(n); } void set_stream(std::size_t n) { current_stream = n; } std::size_t nstreams() const { return streams.size(); } std::size_t stream_id() const { return current_stream; } std::string get_device_name() const { return device_props.gcnArchName; } std::string get_gfx_name() const { return trim(split_string(get_device_name(), ':').front()); } std::size_t get_device_major() const { return device_props.major; } std::size_t get_device_minor() const { return device_props.minor; } std::size_t get_cu_count() const { return device_props.multiProcessorCount; } std::size_t get_max_workitems_per_cu() const { return device_props.maxThreadsPerMultiProcessor; } std::size_t get_max_workitems_per_block() const { return device_props.maxThreadsPerBlock; } std::size_t get_wavefront_size() const { return device_props.warpSize; } private: std::size_t device_id = 0; std::size_t current_stream = 0; std::vector streams; hipDeviceProp_t device_props; public: std::unordered_map preallocations{}; }; struct context { struct auto_save_problem_cache : problem_cache { auto_save_problem_cache() : problem_cache{} {} bool auto_save = false; auto_save_problem_cache(const auto_save_problem_cache&) = delete; auto_save_problem_cache& operator=(const auto_save_problem_cache&) = delete; virtual ~auto_save_problem_cache() { if(auto_save) this->save(); } }; context(std::size_t device_id = 0, std::size_t n = value_of(MIGRAPHX_NSTREAMS{}, 1)) : current_device(std::make_shared(device_id, n)), begin_event(create_event()), finish_event(create_event()), pc(std::make_shared()) { } hip_device& get_current_device() { assert(current_device != nullptr); return *current_device; } const hip_device& get_current_device() const { assert(current_device != nullptr); return *current_device; } bool get_exhaustive_tune_flag() const { return exhaustive_tune; } void set_exhaustive_tune_flag(bool t) { exhaustive_tune = t; } hip_device::stream& get_stream() { return get_current_device().get_stream(); } hip_device::stream& get_stream(std::size_t n) { return get_current_device().get_stream(n); } const hip_device::stream& get_stream() const { return get_current_device().get_stream(); } const hip_device::stream& get_stream(std::size_t n) const { return get_current_device().get_stream(n); } void set_stream(std::size_t n) { get_current_device().set_stream(n); } void create_events(std::size_t num_of_events) { for(std::size_t i = events.size(); i < num_of_events + 1; ++i) events.emplace_back(create_event()); } hipEvent_t get_event(std::size_t i) const { return events.at(i).get(); } std::vector literals{}; void finish() const { get_stream().wait(); } static hip_event_ptr create_event() { hipEvent_t event; auto status = hipEventCreateWithFlags(&event, hipEventDisableTiming); if(status != hipSuccess) MIGRAPHX_THROW("Failed to create event: " + hip_error(status)); return hip_event_ptr{event}; } static hip_event_ptr create_event_for_timing() { hipEvent_t event; auto status = hipEventCreate(&event); if(status != hipSuccess) MIGRAPHX_THROW("Failed to create event: " + hip_error(status)); return hip_event_ptr{event}; } value to_value() const { value result; result["events"] = events.size(); result["streams"] = current_device->nstreams(); return result; } void from_value(const value& v) { auto v_events = v.at("events"); std::size_t n_events = v_events.without_key().to(); this->create_events(n_events - 1); auto v_streams = v.at("streams"); std::size_t n_streams = v_streams.without_key().to(); auto device = get_device_id(); this->current_device = std::make_shared(device, n_streams); } void wait_for(any_ptr queue) { auto status = hipEventRecord(begin_event.get(), queue.get()); if(status != hipSuccess) MIGRAPHX_THROW("Failed to record: " + hip_error(status)); get_stream().wait(begin_event.get()); } void finish_on(any_ptr queue) { get_stream().record(finish_event.get()); auto status = hipStreamWaitEvent(queue.get(), finish_event.get(), 0); if(status != hipSuccess) MIGRAPHX_THROW("Failed to wait on event: " + hip_error(status)); } any_ptr get_queue() { return get_stream().get(); } std::pair get_perf_events() const { if(measure_perf) return std::make_pair(start_event.get(), stop_event.get()); return std::make_pair(nullptr, nullptr); } static float get_elapsed_ms(hipEvent_t start, hipEvent_t stop) { float result = 0; if(start != nullptr and stop != nullptr) { auto status = hipEventElapsedTime(&result, start, stop); if(status != hipSuccess) MIGRAPHX_THROW("Failed hipEventElapsedTime: " + hip_error(status)); } return result; } problem_cache& get_problem_cache() { return *pc; } void load_problem_cache() { pc->load(); pc->auto_save = true; } private: // TODO: Make this a vector to support multiple devices std::shared_ptr current_device; std::vector> events; bool exhaustive_tune = false; bool measure_perf = false; // for event perf timing shared start_event = nullptr; shared stop_event = nullptr; // for stream synchronization shared begin_event = nullptr; shared finish_event = nullptr; std::shared_ptr pc = nullptr; }; inline void migraphx_to_value(value& v, const context& ctx) { v = ctx.to_value(); } inline void migraphx_from_value(const value& v, context& ctx) { ctx.from_value(v); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/contiguous.hpp000066400000000000000000000036721510465702400265500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CONTIGUOUS_HPP #define MIGRAPHX_GUARD_RTGLIB_CONTIGUOUS_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct miopen_contiguous : unary_device { std::string name() const { return "gpu::contiguous"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2); auto lens = inputs.at(0).lens(); auto t = inputs.at(0).type(); return {t, lens}; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/convolution.hpp000066400000000000000000000365751510465702400267400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_CONVOLUTION_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_CONVOLUTION_HPP #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { inline shape reshape_if_1d(const shape& input) { shape new_shape{input}; auto dims = new_shape.lens(); if(dims.size() == 3) { std::vector new_dims = dims; new_dims.insert(new_dims.begin() + 2, 1); new_shape = shape{input.type(), new_dims}; } return new_shape; } #if MIGRAPHX_USE_MIOPEN template struct miopen_convolution { Op op; shared cd = nullptr; miopenConvFwdAlgorithm_t algo{}; #ifdef MIGRAPHX_HAS_FIND_2_API value::binary solution_object{}; shared solution_ptr = nullptr; #endif uint64_t solution_id = 0; template static auto reflect(Self& self, F f) { return pack(f(self.op, "op"), #ifdef MIGRAPHX_HAS_FIND_2_API f(self.solution_object, "solution_object"), #endif f(self.algo, "algo"), f(self.solution_id, "solution_id")); } std::string name() const { return "gpu::" + op.name(); } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, op}.has(4); std::vector conv_inputs(inputs.begin(), inputs.begin() + 2); check_shapes{conv_inputs, *this} .max_ndims(5) .packed_layouts({{0, 1, 2}, {0, 1, 2, 3}, {0, 2, 3, 1}, {0, 1, 2, 3, 4}}) .same_layout(); return migraphx::compute_shape(op, conv_inputs); } argument compute(context& ctx, const shape& output_shape, const std::vector& args) const { auto x_desc = make_tensor(reshape_if_1d(args[0].get_shape())); auto w_desc = make_tensor(reshape_if_1d(args[1].get_shape())); auto y_desc = make_tensor(reshape_if_1d(output_shape)); auto* miopen_stream_handle = ctx.get_stream().get_miopen(); auto workspace_size = args[2].get_shape().bytes(); #ifdef MIGRAPHX_HAS_FIND_2_API { const miopenTensorArgument_t tensor_args[3] = { {miopenTensorConvolutionX, nullptr, args[0].implicit()}, {miopenTensorConvolutionW, nullptr, args[1].implicit()}, {miopenTensorConvolutionY, nullptr, args[3].implicit()}, }; if(solution_ptr.get() == nullptr) MIGRAPHX_THROW("MIOpen " + op.name() + " : Load MIOpen Solution before running it"); auto status = miopenRunSolution(miopen_stream_handle, solution_ptr.get(), 3, tensor_args, args[2].implicit(), workspace_size); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen " + op.name() + " : running convolution using find_2.0 failed"); return args[3]; } #else // else use immediate mode if(solution_id == 0) MIGRAPHX_THROW("MIOpen " + op.name() + " : invalid solution ID"); auto status = miopenConvolutionForwardImmediate(miopen_stream_handle, w_desc.get(), args[1].implicit(), x_desc.get(), args[0].implicit(), cd.get(), y_desc.get(), args[3].implicit(), args[2].implicit(), workspace_size, solution_id); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen " + op.name() + ": running convolution failed"); return args[3]; #endif } void set_conv_descriptor() { cd = (op.name() == "convolution_backwards") ? make_convolution_backwards(op) : make_conv(op); } value compile(migraphx::context& ctx, const shape& output, const std::vector& input) { set_conv_descriptor(); auto ws = find(any_cast(ctx), output, input); return {{"workspace", ws.bytes()}}; } shape find(context& ctx, const shape& output_shape, const std::vector& inputs) { shape workspace_shape{}; auto x_desc = make_tensor(reshape_if_1d(inputs[0])); auto w_desc = make_tensor(reshape_if_1d(inputs[1])); auto y_desc = make_tensor(reshape_if_1d(output_shape)); auto* miopen_stream_handle = ctx.get_stream().get_miopen(); std::size_t workspace_size = 0; auto status = miopenConvolutionForwardGetWorkSpaceSize(miopen_stream_handle, w_desc.get(), x_desc.get(), cd.get(), y_desc.get(), &workspace_size); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen" + op.name() + " : Failed to get forward workspace size"); workspace_shape = shape{shape::int8_type, {workspace_size}}; const auto& x_shape = inputs[0]; const auto& w_shape = inputs[1]; unsigned long seed = 0; #ifdef MIGRAPHX_HAS_FIND_2_API { auto conv_problem = make_obj( &miopenCreateConvProblem, cd.get(), miopenProblemDirectionForward); set_tensor_descriptor(miopenTensorConvolutionX, x_desc, conv_problem); set_tensor_descriptor(miopenTensorConvolutionW, w_desc, conv_problem); bool preallocate = false; #ifdef MIGRAPHX_PREALLOCATE_MIOPEN_BUFFERS // MIOpen has APIs to pass pre-allocated buffers starting from rocm-5.6 preallocate = true; #endif auto x = preallocate ? to_gpu(generate_argument(x_shape, seed++, random_mode::random)) : argument{inputs[0]}; auto w = preallocate ? to_gpu(generate_argument(w_shape, seed++, random_mode::random)) : argument{inputs[1]}; auto y = preallocate ? allocate_gpu(output_shape) : argument{inputs[2]}; auto workspace = preallocate ? allocate_gpu(workspace_shape) : migraphx::argument(workspace_shape); set_tensor_descriptor(miopenTensorConvolutionY, y_desc, conv_problem); const miopenTensorArgument_t tensor_args[3] = { {miopenTensorConvolutionX, nullptr, x.implicit()}, {miopenTensorConvolutionW, nullptr, w.implicit()}, {miopenTensorConvolutionY, nullptr, y.implicit()}, }; solution_ptr = find_solution(miopen_stream_handle, 3, tensor_args, workspace.implicit(), workspace_size, conv_problem.get(), ctx.get_exhaustive_tune_flag()); status = miopenGetSolutionWorkspaceSize(solution_ptr.get(), &workspace_size); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen" + op.name() + " : failed to get solution's workspace size"); std::size_t solution_size; status = miopenGetSolutionSize(solution_ptr.get(), &solution_size); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen" + op.name() + ": Failed to fetch solution size"); auto solution_binary = std::vector{}; solution_binary.resize(solution_size); status = miopenSaveSolution(solution_ptr.get(), solution_binary.data()); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen" + op.name() + ": Saving solution failed"); solution_object = value::binary{solution_binary.data(), solution_size}; return shape{shape::int8_type, {workspace_size}}; } #else auto x = to_gpu(generate_argument(x_shape, seed++, random_mode::random)); auto w = to_gpu(generate_argument(w_shape, seed++, random_mode::random)); auto y = allocate_gpu(output_shape); auto workspace = allocate_gpu(workspace_shape); int algo_count = 1; miopenConvAlgoPerf_t perf; status = miopenFindConvolutionForwardAlgorithm(ctx.get_stream().get_miopen(), x_desc.get(), x.implicit(), w_desc.get(), w.implicit(), cd.get(), y_desc.get(), y.implicit(), 1, &algo_count, &perf, workspace.implicit(), workspace_size, ctx.get_exhaustive_tune_flag()); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen " + op.name() + " : find convolution failed"); algo = perf.fwd_algo; size_t solution_count; status = miopenConvolutionForwardGetSolutionCount(ctx.get_stream().get_miopen(), w_desc.get(), x_desc.get(), cd.get(), y_desc.get(), &solution_count); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen " + op.name() + ": get solution count failed"); std::vector solutions(solution_count); status = miopenConvolutionForwardGetSolution(ctx.get_stream().get_miopen(), w_desc.get(), x_desc.get(), cd.get(), y_desc.get(), solution_count, &solution_count, solutions.data()); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen " + op.name() + ": get solution failed"); solution_id = solutions.front().solution_id; return shape{shape::int8_type, {perf.memory}}; #endif } void finalize(context& ctx, const shape& output_shape, const std::vector& inputs) { #ifdef MIGRAPHX_HAS_FIND_2_API { (void)(ctx); // avoid warnings (void)(output_shape); (void)(inputs); // load solution if(solution_ptr == nullptr) { miopenSolution_t ptr; auto status = miopenLoadSolution(&ptr, reinterpret_cast(solution_object.data()), solution_object.size()); solution_ptr = miopen_solution{ptr}; if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen " + op.name() + ": loading convolution solution failed"); } } #else // Use immediate mode API { set_conv_descriptor(); if(solution_id == 0) { // Check that workspace hasn't changed auto size = inputs.at(2).bytes(); auto ws = find(ctx, output_shape, inputs); if(ws.bytes() > size) MIGRAPHX_THROW("MIOpen " + op.name() + ": workspace has changed during finalization."); } auto x_desc = make_tensor(reshape_if_1d(inputs[0])); auto w_desc = make_tensor(reshape_if_1d(inputs[1])); auto y_desc = make_tensor(reshape_if_1d(output_shape)); auto status = miopenConvolutionForwardCompileSolution(ctx.get_stream().get_miopen(), w_desc.get(), x_desc.get(), cd.get(), y_desc.get(), solution_id); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen Convolution: compile solution failed"); } #endif } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/000077500000000000000000000000001510465702400250675ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/arg_op.hpp000066400000000000000000000122001510465702400270420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_ARG_OP_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_ARG_OP_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { template struct val_index { T val; int64_t index; }; template MIGRAPHX_DEVICE_CONSTEXPR val_index make_val_index(T v) { return {v, -1}; } template MIGRAPHX_DEVICE_CONSTEXPR val_index make_val_index(T v, int64_t i) { return {v, i}; } struct argmax_op_first_index { template MIGRAPHX_DEVICE_CONSTEXPR val_index operator()(val_index x, val_index y) const { if(x.val > y.val) return x; else if(x.val < y.val) return y; else { return (x.index < y.index) ? x : y; } } MIGRAPHX_DEVICE_CONSTEXPR auto init() const { return lowest(); } }; struct argmax_op_last_index { template MIGRAPHX_DEVICE_CONSTEXPR val_index operator()(val_index x, val_index y) const { if(x.val > y.val) return x; else if(x.val < y.val) return y; else { return (x.index > y.index) ? x : y; } } MIGRAPHX_DEVICE_CONSTEXPR auto init() const { return lowest(); } }; struct argmin_op_first_index { template MIGRAPHX_DEVICE_CONSTEXPR val_index operator()(val_index x, val_index y) const { if(x.val < y.val) return x; else if(x.val > y.val) return y; else { return (x.index < y.index) ? x : y; } } MIGRAPHX_DEVICE_CONSTEXPR auto init() const { return highest(); } }; struct argmin_op_last_index { template MIGRAPHX_DEVICE_CONSTEXPR val_index operator()(val_index x, val_index y) const { if(x.val < y.val) return x; else if(x.val > y.val) return y; else { return (x.index > y.index) ? x : y; } } MIGRAPHX_DEVICE_CONSTEXPR auto init() const { return highest(); } }; template void arg_op(Op op, hipStream_t stream, const argument& result, const argument& arg, int64_t axis) { auto arg_shape = arg.get_shape(); auto batch_lens = arg_shape.lens(); size_t batch_item_num = batch_lens[axis]; batch_lens[axis] = 1; migraphx::shape batch_shape{arg_shape.type(), batch_lens}; migraphx::shape std_arg_shape{arg_shape.type(), arg_shape.lens()}; hip_visit_all(arg, std_arg_shape, batch_shape)([&](auto input, auto arg_s, auto batch_s) { auto* output = device_cast(result.get().data()); using type = device_type>; // use one block for items in one batch. const size_t max_block_size = 256; const std::size_t block_size = compute_block_size(batch_item_num, max_block_size); gs_launch(stream, batch_shape.elements() * block_size, block_size)([=](auto i, auto idx) __device__ { auto batch_idx = batch_s.multi(i / block_size); auto data_idx = batch_idx; auto init = make_val_index(op.init()); auto op_output = block_reduce(idx, op, init, batch_item_num, [&](auto j) __device__ { data_idx[axis] = j; return make_val_index(input[arg_s.index(data_idx)], j); }); if(idx.local == 0) { output[batch_s.index(batch_idx)] = op_output.index; } }); }); } } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/argmax.hpp000066400000000000000000000035241510465702400270630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_ARGMAX_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_ARGMAX_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void MIGRAPHX_DEVICE_EXPORT argmax(hipStream_t stream, const argument& result, const argument& arg, int64_t axis, bool select_last_index); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/argmin.hpp000066400000000000000000000035241510465702400270610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_ARGMIN_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_ARGMIN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void MIGRAPHX_DEVICE_EXPORT argmin(hipStream_t stream, const argument& result, const argument& arg, int64_t axis, bool select_last_index); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/config.hpp000066400000000000000000000025241510465702400270500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_CONFIG_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_CONFIG_HPP #include #include #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/contiguous.hpp000066400000000000000000000033601510465702400300010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_KERNELS_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_KERNELS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void MIGRAPHX_DEVICE_EXPORT contiguous(hipStream_t stream, const argument& result, const argument& arg); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/fill.hpp000066400000000000000000000032321510465702400265260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_FILL_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_FILL_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void MIGRAPHX_DEVICE_EXPORT fill(hipStream_t stream, const argument& result, unsigned long val); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/fixed_pad.hpp000066400000000000000000000034031510465702400275230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_FIXED_PAD_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_FIXED_PAD_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { argument MIGRAPHX_DEVICE_EXPORT fixed_pad(hipStream_t stream, const argument& result, const argument& arg); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/logsoftmax.hpp000066400000000000000000000034611510465702400277670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_LOGSOFTMAX_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_LOGSOFTMAX_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void MIGRAPHX_DEVICE_EXPORT logsoftmax(hipStream_t stream, const argument& result, const argument& arg, int64_t axis); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/multinomial.hpp000066400000000000000000000035001510465702400301300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_MULTINOMIAL_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_MULTINOMIAL_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void MIGRAPHX_DEVICE_EXPORT multinomial(hipStream_t stream, const argument& result, const argument& arg0, const argument& arg1); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/nonzero.hpp000066400000000000000000000033761510465702400273030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_NONZERO_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_NONZERO_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { argument MIGRAPHX_DEVICE_EXPORT nonzero(hipStream_t stream, const argument& result, const argument& arg_data); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/prefix_scan_sum.hpp000066400000000000000000000037431510465702400307740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_DEVICE_PREFIX_SCAN_SUM_HPP #define MIGRAPHX_GUARD_DEVICE_PREFIX_SCAN_SUM_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void MIGRAPHX_DEVICE_EXPORT prefix_scan_sum(hipStream_t stream, const argument& result, const argument& arg, int32_t axis, bool exclusive, bool reverse); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_DEVICE_PREFIX_SCAN_SUM_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/reverse.hpp000066400000000000000000000034661510465702400272640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_REVERSE_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_REVERSE_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { argument MIGRAPHX_DEVICE_EXPORT reverse(hipStream_t stream, argument result, argument arg1, const std::vector& axes); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/rnn_variable_seq_lens.hpp000066400000000000000000000051611510465702400321360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_RNN_VARIABLE_SEQ_LENS_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_RNN_VARIABLE_SEQ_LENS_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { void MIGRAPHX_DEVICE_EXPORT rnn_var_sl_shift_sequence(hipStream_t stream, const argument& result, const argument& arg_hs, const argument& arg_sl); void MIGRAPHX_DEVICE_EXPORT rnn_var_sl_shift_output(hipStream_t stream, const argument& result, const argument& arg_hs, const argument& arg_sl, bool is_reverse); void MIGRAPHX_DEVICE_EXPORT rnn_var_sl_last_output(hipStream_t stream, const argument& result, const argument& arg_hs, const argument& arg_sl, bool is_reverse); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device/topk.hpp000066400000000000000000000045061510465702400265620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_DEVICE_TOPK_HPP #define MIGRAPHX_GUARD_RTGLIB_DEVICE_TOPK_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace device { argument MIGRAPHX_DEVICE_EXPORT topk_smallest(hipStream_t stream, const argument& val_res, const argument& ind_res, const argument& arg, int64_t k, int64_t axis); argument MIGRAPHX_DEVICE_EXPORT topk_largest(hipStream_t stream, const argument& val_res, const argument& ind_res, const argument& arg, int64_t k, int64_t axis); } // namespace device } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/device_name.hpp000066400000000000000000000037131510465702400266040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_DEVICE_NAME_HPP #define MIGRAPHX_GUARD_GPU_DEVICE_NAME_HPP #include #include struct hipDeviceProp_t; namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_GPU_EXPORT std::string get_device_name(); MIGRAPHX_GPU_EXPORT int get_device_id(); MIGRAPHX_GPU_EXPORT bool gfx_has_fp8fnuz_intrinsics(); MIGRAPHX_GPU_EXPORT bool gfx_has_fp8ocp_intrinsics(); MIGRAPHX_GPU_EXPORT bool gfx_has_bf16_intrinsics(); MIGRAPHX_GPU_EXPORT bool gfx_has_fp8fnuz_support(); #if MIGRAPHX_USE_HIPBLASLT MIGRAPHX_GPU_EXPORT bool gfx_default_rocblas(); #endif MIGRAPHX_GPU_EXPORT bool hipblaslt_supported(); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_DEVICE_NAME_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/fixed_pad.hpp000066400000000000000000000041011510465702400262600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FIXED_PAD_HPP #define MIGRAPHX_GUARD_RTGLIB_FIXED_PAD_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_fixed_pad { op::fixed_pad op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::fixed_pad"; } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape&, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/fuse_ck.hpp000066400000000000000000000032611510465702400257620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_FUSE_CK_HPP #define MIGRAPHX_GUARD_GPU_FUSE_CK_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; namespace gpu { struct fuse_ck { context* ctx = nullptr; std::string name() const { return "gpu::fuse_ck"; } void apply(module_pass_manager& mpm) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_FUSE_CK_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/fuse_mlir.hpp000066400000000000000000000034751510465702400263370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_FUSE_MLIR_HPP #define MIGRAPHX_GUARD_GPU_FUSE_MLIR_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; namespace gpu { MIGRAPHX_GPU_EXPORT bool mlir_enabled(); MIGRAPHX_GPU_EXPORT bool mlir_attention_enabled(context* ctx); struct MIGRAPHX_GPU_EXPORT fuse_mlir { context* ctx = nullptr; bool enable_extra = false; std::string name() const { return "gpu::fuse_mlir"; } void apply(module_pass_manager& mpm) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_FUSE_MLIR_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/fuse_ops.hpp000066400000000000000000000032171510465702400261670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_FUSE_OPS_HPP #define MIGRAPHX_GUARD_RTGLIB_FUSE_OPS_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { struct MIGRAPHX_GPU_EXPORT fuse_ops { context* ctx = nullptr; bool fast_math = true; std::string name() const { return "gpu::fuse_ops"; } void apply(module& m) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/gemm.hpp000066400000000000000000000136521510465702400252750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_GEMM_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_GEMM_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; template struct rocblas_gemm { Op op; float alpha = 1; float beta = 0; bool compute_fp32 = false; unsigned trans_batch = 0; int32_t solution_idx = 0; template static auto reflect(Self& self, F f) { return pack_join(migraphx::reflect(self.op, f), pack(f(self.alpha, "alpha"), f(self.beta, "beta"), f(self.compute_fp32, "compute_fp32"), f(self.trans_batch, "trans_batch"), f(self.solution_idx, "solution_idx"))); } std::string name() const { if(contains(op.name(), "quant_")) { return "gpu::quant_gemm"; } return "gpu::gemm"; } shape compute_shape(const std::vector& inputs) const { std::vector in_shapes(inputs); in_shapes.pop_back(); // When input shapes are A, B, C the GEMM equation is C  =  α AB+ β C where α, β are // scalars check_shapes{in_shapes, *this}.has(2, 3); blas_shape(inputs[0]); blas_shape(inputs[1]); // if gemm and add are fused if(in_shapes.size() > 2) { auto cmat_shape = in_shapes.back(); check_shapes{{cmat_shape}, *this}.not_transposed().not_broadcasted(); in_shapes.pop_back(); blas_shape(cmat_shape); auto op_out_shape = op.compute_shape(in_shapes); if(cmat_shape.lens() != op_out_shape.lens()) { MIGRAPHX_THROW(this->name() + " : dimension mismatch, operand C: {" + to_string_range(cmat_shape.lens()) + "}, cannot add to operand A * B: {" + to_string_range(op_out_shape.lens()) + "}"); } if(cmat_shape.type() != op_out_shape.type()) { MIGRAPHX_THROW(this->name() + " : operand C type mismatch, operand C is of type: " + to_string(cmat_shape.type()) + ", it must be: " + to_string(op_out_shape.type())); } return transpose_batch(op_out_shape, trans_batch); } return transpose_batch(op.compute_shape(in_shapes), trans_batch); } argument compute(context& ctx, const shape& output_shape, const std::vector& args) const { if(this->name() == "gpu::gemm" or output_shape.type() == migraphx::shape::float_type) { gemm_compute(ctx, output_shape, args, alpha, beta, compute_fp32, solution_idx); } else { gemm_compute( ctx, output_shape, args, int32_t(alpha), int32_t(beta), compute_fp32, solution_idx); } return args.back(); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } void finalize(context& ctx, const shape& output_shape, const std::vector& input_shapes) { #ifdef MIGRAPHX_USE_ROCBLAS_TUNING_API if(solution_idx == 0) solution_idx = gemm_default_solution(ctx, output_shape, input_shapes); if(enabled(MIGRAPHX_ENABLE_GEMM_TUNING{}) or ctx.get_exhaustive_tune_flag()) { if(this->name() == "gpu::gemm") { solution_idx = gemm_finalize( ctx, output_shape, input_shapes, alpha, beta, compute_fp32, solution_idx); } else { solution_idx = gemm_finalize(ctx, output_shape, input_shapes, int32_t(alpha), int32_t(beta), compute_fp32, solution_idx); } } #else // suppress compiler warnings (void)ctx, (void)output_shape, (void)input_shapes; #endif // MIGRAPHX_USE_ROCBLAS_TUNING_API } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_RTGLIB_GPU_GEMM_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/gemm_impl.hpp000066400000000000000000000071611510465702400263140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GEMM_IMPL_HPP #define MIGRAPHX_GUARD_RTGLIB_GEMM_IMPL_HPP #include #include #include #include #include // Set this environment variable to "true" to perform GEMM tuning even when the // --exhaustive-tune option isn't set. Can be used to skip slow convolution tuning. MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_GEMM_TUNING); using milliseconds = std::chrono::duration; using microseconds = std::chrono::duration; namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { void blas_shape(const shape& in_shape); shape transpose_batch(const shape& s, unsigned trans_batch); /** * @brief Templated implementations of the compute() and finalize() methods of the Gemm operator. * For each function there are overloads using either float or int32_t for the arguments * alpha and beta. * * @param ctx . * @param output_shape . * @param args . * @param alpha . * @param beta . * @param compute_fp32 . */ void gemm_compute(context& ctx, const shape& output_shape, const std::vector& args, float alpha, float beta, bool compute_fp32, int32_t solution_idx); void gemm_compute(context& ctx, const shape& output_shape, const std::vector& args, int32_t alpha, int32_t beta, bool compute_fp32, int32_t solution_idx); int32_t gemm_finalize(context& ctx, const shape& output_shape, const std::vector& input_shapes, float alpha, float beta, bool compute_fp32, int32_t solution_idx); int32_t gemm_finalize(context& ctx, const shape& output_shape, const std::vector& input_shapes, int32_t alpha, int32_t beta, bool compute_fp32, int32_t solution_idx); int32_t gemm_default_solution(context& ctx, const shape& output_shape, const std::vector& input_shapes); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/gemm_softmax_gemm.hpp000066400000000000000000000104121510465702400300320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_GEMM_SOFTMAX_GEMM_HPP #define MIGRAPHX_GUARD_GPU_GEMM_SOFTMAX_GEMM_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct gemm_softmax_gemm { operation op = make_op("dot"); float scale = 1.0; template static auto reflect(Self& self, F f) { return pack(f(self.op, "op"), f(self.scale, "scale")); } std::string name() const { return "gpu::gemm_softmax_gemm"; } void check_gemm_shape(const shape& s) const { if(not contains(range(s.strides().rbegin(), s.strides().rbegin() + 3), 1) and not s.scalar()) MIGRAPHX_THROW("Invalid shape for " + name()); } shape compute_shape(std::vector inputs, const std::vector&) const { check_shapes{inputs, *this}.same_ndims(); if(inputs.size() < 3) MIGRAPHX_THROW(name() + ": Expected 3 inputs but got " + to_string(inputs.size())); const bool is_bias_enabled = inputs.size() == 4; const bool is_mul_where = inputs.size() == 5; auto a = inputs[0]; auto b = inputs[1]; auto b1 = inputs.back(); for(const auto& input : inputs) { check_gemm_shape(input); } auto gemm0_shape = op.compute_shape({a, b}); if(is_mul_where) { const auto& select_cond = inputs[2]; const auto& select_const = inputs[3]; if(select_cond.lens() != select_const.lens()) { std::stringstream err_msg; err_msg << name() << ": has inconsistent where op condition and constant size: " << select_cond << "!=" << select_const; MIGRAPHX_THROW(err_msg.str()); } if(select_cond.lens() != gemm0_shape.lens()) { std::stringstream err_msg; err_msg << name() << ": has inconsistent where op condition size" << ". Expected: " << gemm0_shape << ". Given: " << select_cond; MIGRAPHX_THROW(err_msg.str()); } } if(is_bias_enabled) { const auto& bias_shape = inputs[2]; if(bias_shape.lens() != gemm0_shape.lens()) { std::stringstream err_msg; err_msg << name() << ": has inconsistent bias size" << ". Expected: " << gemm0_shape << ". Given: " << bias_shape; MIGRAPHX_THROW(err_msg.str()); } } return op.compute_shape({gemm0_shape, b1}); } static bool is_ck_supported_type(shape::type_t t) { return contains({shape::half_type}, t); } static bool is_mlir_supported_type(shape::type_t t) { return contains({shape::type_t::float_type, shape::half_type}, t); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_GEMM_SOFTMAX_GEMM_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/group_query_attention.hpp000066400000000000000000000164751510465702400310240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_GROUP_QUERY_ATTENTION_HPP #define MIGRAPHX_GUARD_GPU_GROUP_QUERY_ATTENTION_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct gqa_parameters { float scale; std::uint32_t batch_size; // Batch size used by input std::uint32_t sequence_length; // Sequence length used by input std::uint32_t hidden_size; // Hidden size used by input std::uint32_t head_size; // Head size std::uint32_t rotary_embedding_dim; // Rotary embedding dimension. std::uint32_t num_heads; // num_heads = hidden_size / head_size std::uint32_t max_sequence_length; // Sequence length used by cos/sin cache std::uint32_t head_stride; // Head stride std::uint32_t seq_stride; // Sequence stride std::uint32_t batch_stride; // Batch stride std::uint32_t position_ids_format; // Format of position ids - 0 is (1), 1 is (batch_size, // sequence_length) std::uint32_t seqlen_present_kv_cache; // Sequence length of present kv-cache (4096 when using // shared buffer) bool do_rotary; // Whether to use rotary position embedding. Default value is 0. std::uint32_t kv_num_heads; // Number of attention heads for k and v int local_window_size; // left_window_size for local attention. Default value is -1 meaning // unused. bool rotary_interleaved; // Rotate using interleaved pattern. Default value is 0 (False). bool past_present_share_buffer; // Whether to use same buffer for KV-cache inputs and outputs std::string make_init_str() const { return "MIGRAPHX_MAKE_CONSTANT(float{" + std::to_string(scale) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(batch_size) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(sequence_length) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(hidden_size) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(head_size) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(rotary_embedding_dim) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(num_heads) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(max_sequence_length) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(head_stride) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(seq_stride) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(batch_stride) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(position_ids_format) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(seqlen_present_kv_cache) + "}), " + "MIGRAPHX_MAKE_CONSTANT(bool{" + std::to_string(static_cast(do_rotary)) + "}), " + "MIGRAPHX_MAKE_CONSTANT(uint32_t{" + std::to_string(kv_num_heads) + "}), " + "MIGRAPHX_MAKE_CONSTANT(int32_t{" + std::to_string(local_window_size) + "}), " + "MIGRAPHX_MAKE_CONSTANT(bool{" + std::to_string(static_cast(rotary_interleaved)) + "}), " + "MIGRAPHX_MAKE_CONSTANT(bool{" + std::to_string(static_cast(past_present_share_buffer)) + "})"; } }; static inline gqa_parameters init_params(const std::vector& inputs, const value& v) { auto num_heads = v.at("num_heads").to(); auto kv_num_heads = v.at("kv_num_heads").to(); auto do_rotary = v.at("do_rotary").to(); auto local_window_size = v.at("local_window_size").to(); auto rotary_interleaved = v.at("rotary_interleaved").to(); auto scale = v.at("scale").to(); auto present_kv_seqlen = inputs[1].lens().size() == 4 ? inputs[1].lens()[2] : 0; const auto& q_shape = inputs[0]; const auto& q_lens = q_shape.lens(); const std::size_t batch_size = q_lens[0]; const std::size_t sequence_length = q_lens[2]; std::size_t head_size = q_lens[3]; auto q_hidden_size = kv_num_heads * head_size; std::size_t rotary_dim = inputs.size() >= 4 ? inputs[3].lens()[1] * 2 : 0; if(inputs.size() == 3) { present_kv_seqlen = inputs[2].lens()[2]; } auto seq_stride = head_size; auto head_stride = sequence_length * seq_stride; auto batch_stride = (num_heads + 2 * kv_num_heads) * head_stride; auto position_ids_format = sequence_length == 1 ? 1 : 0; bool past_present_share_buffer = true; gqa_parameters gqa_params; gqa_params.batch_size = batch_size; gqa_params.sequence_length = sequence_length; gqa_params.hidden_size = q_hidden_size; gqa_params.head_size = head_size; gqa_params.rotary_embedding_dim = rotary_dim; gqa_params.num_heads = num_heads; gqa_params.max_sequence_length = sequence_length; gqa_params.seq_stride = head_size; gqa_params.head_stride = head_stride; gqa_params.batch_stride = batch_stride; gqa_params.position_ids_format = position_ids_format; gqa_params.seqlen_present_kv_cache = present_kv_seqlen; gqa_params.do_rotary = do_rotary; gqa_params.kv_num_heads = kv_num_heads; gqa_params.local_window_size = local_window_size; gqa_params.rotary_interleaved = rotary_interleaved; gqa_params.scale = scale; gqa_params.past_present_share_buffer = past_present_share_buffer; return gqa_params; } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_GROUP_QUERY_ATTENTION_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/hip.hpp000066400000000000000000000207551510465702400251320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_HIP_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_HIP_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; MIGRAPHX_GPU_EXPORT std::string hip_error(int error); MIGRAPHX_GPU_EXPORT argument allocate_gpu(const shape& s, bool host = false); MIGRAPHX_GPU_EXPORT argument register_on_gpu(const argument& arg); MIGRAPHX_GPU_EXPORT argument to_gpu(const argument& arg, bool host = false); MIGRAPHX_GPU_EXPORT argument from_gpu(const argument& arg); MIGRAPHX_GPU_EXPORT void set_device(std::size_t id); MIGRAPHX_GPU_EXPORT void gpu_sync(); MIGRAPHX_GPU_EXPORT void gpu_sync(const context& ctx); MIGRAPHX_GPU_EXPORT void gpu_copy(context& ctx, const argument& src, const argument& dst); MIGRAPHX_GPU_EXPORT void copy_to_gpu(context& ctx, const argument& src, const argument& dst); MIGRAPHX_GPU_EXPORT void copy_from_gpu(context& ctx, const argument& src, const argument& dst); MIGRAPHX_GPU_EXPORT argument get_preallocation(context& ctx, const std::string& id); MIGRAPHX_GPU_EXPORT void gpu_fill(context& ctx, const argument& dst, int value = 0); struct hip_allocate { shape s; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape")); } std::string name() const { return "hip::allocate"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(0); return s; } argument compute(context&, const shape& output_shape, const std::vector&) const { return allocate_gpu(output_shape); } }; struct hip_fill { int value = 0; template static auto reflect(Self& self, F f) { return pack(f(self.value, "value")); } std::string name() const { return "hip::fill"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(1); return inputs.front(); } argument compute(context& ctx, const shape&, const std::vector& args) const { gpu_fill(ctx, args.front(), value); return args.front(); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; struct hip_sync_stream { std::string name() const { return "hip::sync_stream"; } shape compute_shape(const std::vector& inputs) const { if(inputs.empty()) return {}; return inputs.front(); } argument compute(const context& ctx, const shape&, const std::vector& args) const { gpu_sync(ctx); if(args.empty()) return {}; return args.front(); } std::ptrdiff_t output_alias(const std::vector& args) const { if(args.empty()) return -1; return 0; } }; struct hip_copy_to_gpu { std::string name() const { return "hip::copy_to_gpu"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2).same_type(); return inputs.at(0); } argument compute(context& ctx, const shape&, const std::vector& args) const { auto input = register_on_gpu(args[0]); if(args.size() == 1) return input; argument result = args[1].share(); if(result.get_shape().dynamic()) { result = result.reshape(args[0].get_shape()); } gpu_copy(ctx, input, result); // Associate the input since it was registered with hip return {result.get_shape(), [input, result]() mutable { return result.data(); }}; } std::ptrdiff_t output_alias(const std::vector& args) const { if(args.size() == 1) return -1; return 1; } }; struct hip_copy_from_gpu { std::string name() const { return "hip::copy_from_gpu"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2).same_type(); return inputs.at(0); } argument compute(context& ctx, const dyn_output& dyn_out, const std::vector& args) const { if(args.size() == 1) { argument result = allocate_gpu(dyn_out.computed_shape, true); gpu_copy(ctx, args[0], result); return result; } argument input = args[0].share(); if(input.get_shape().dynamic()) { input = input.reshape(args[1].get_shape()); } copy_from_gpu(ctx, input, args[1]); return args[1]; } std::ptrdiff_t output_alias(const std::vector& args) const { if(args.size() == 1) return -1; return 1; } }; struct hip_copy { std::string name() const { return "hip::copy"; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(2).same_type(); return inputs.at(1); } argument compute(context& ctx, const shape&, std::vector args) const { argument result = args[1].share(); if(result.get_shape().dynamic()) { result = result.reshape(args[0].get_shape()); } gpu_copy(ctx, args[0], result); return args[1]; } std::ptrdiff_t output_alias(const std::vector&) const { return 1; } }; MIGRAPHX_GPU_EXPORT void store_preallocated_param(context& ctx, const std::string& id, const argument& a); struct hip_allocate_memory { shape s; std::string id{}; template static auto reflect(Self& self, F f) { return pack(f(self.s, "shape"), f(self.id, "id")); } std::string name() const { return "hip::hip_allocate_memory"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(0); return s; } argument compute(context& ctx, const shape&, const std::vector&) const { return get_preallocation(ctx, id); } void finalize(context& ctx, const shape&, const std::vector&) const { argument a = allocate_gpu(s); store_preallocated_param(ctx, id, a); } }; struct hip_copy_literal { literal l; std::string id{}; template static auto reflect(Self& self, F f) { return pack(f(self.l, "literal"), f(self.id, "id")); } std::string name() const { return "hip::hip_copy_literal"; } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(0); return l.get_shape(); } argument compute(context& ctx, const shape&, const std::vector&) const { return get_preallocation(ctx, id); } void finalize(context& ctx, const shape&, const std::vector&) const { argument a = to_gpu(l.get_argument()); store_preallocated_param(ctx, id, a); } friend std::ostream& operator<<(std::ostream& os, const hip_copy_literal& x) { os << x.name() << "[id=" << x.id << "]"; return os; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/hip_gemm.hpp000066400000000000000000000124541510465702400261340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_HIP_GEMM_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_HIP_GEMM_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; template struct MIGRAPHX_GPU_EXPORT hip_gemm { Op op; float alpha = 1; float beta = 0; unsigned trans_batch = 0; int32_t solution_idx = 0; template static auto reflect(Self& self, F f) { return pack_join(migraphx::reflect(self.op, f), pack(f(self.alpha, "alpha"), f(self.beta, "beta"), f(self.trans_batch, "trans_batch"), f(self.solution_idx, "solution_idx"))); } std::string name() const { if(contains(op.name(), "quant_")) { return "gpu::hip_quant_gemm"; } return "gpu::hip_gemm"; } shape compute_shape(const std::vector& inputs) const { std::vector in_shapes(inputs); in_shapes.pop_back(); in_shapes.pop_back(); // When input shapes are A, B, C the GEMM equation is C  =  α AB+ β C where α, β are // scalars check_shapes{in_shapes, *this}.has(2, 3); blas_shape_hip(inputs[0]); blas_shape_hip(inputs[1]); // if gemm and add are fused if(in_shapes.size() > 2) { auto cmat_shape = in_shapes.back(); check_shapes{{cmat_shape}, *this}.not_transposed().not_broadcasted(); in_shapes.pop_back(); blas_shape_hip(cmat_shape); auto op_out_shape = op.compute_shape(in_shapes); if(cmat_shape.lens() != op_out_shape.lens()) { MIGRAPHX_THROW(this->name() + " : dimension mismatch, operand C: {" + to_string_range(cmat_shape.lens()) + "}, cannot add to operand A * B: {" + to_string_range(op_out_shape.lens()) + "}"); } if(cmat_shape.type() != op_out_shape.type()) { MIGRAPHX_THROW(this->name() + " : operand C type mismatch, operand C is of type: " + to_string(cmat_shape.type()) + ", it must be: " + to_string(op_out_shape.type())); } return transpose_batch_hip(op_out_shape, trans_batch); } return transpose_batch_hip(op.compute_shape(in_shapes), trans_batch); } argument compute(context& ctx, const shape& output_shape, const std::vector& args) const { hip_gemm_compute(ctx, output_shape, args, alpha, beta, solution_idx); return args.back(); } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } void finalize(context& ctx, const shape& output_shape, const std::vector& input_shapes) { if(solution_idx == 0) solution_idx = hip_gemm_default_solution(ctx, output_shape, input_shapes); if(enabled(MIGRAPHX_ENABLE_HIP_GEMM_TUNING{}) or ctx.get_exhaustive_tune_flag()) { solution_idx = hip_gemm_finalize(ctx, output_shape, input_shapes, alpha, beta, solution_idx); } } value compile(migraphx::context& ctx, const shape& output, const std::vector& input_shapes) { finalize(any_cast(ctx), output, input_shapes); size_t ws = hip_gemm_workspace_size( any_cast(ctx), output, input_shapes, alpha, beta, solution_idx); return {{"workspace", ws}}; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_RTGLIB_GPU_HIP_GEMM_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/hip_gemm_impl.hpp000066400000000000000000000071751510465702400271610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_HIP_GEMM_IMPL_HPP #define MIGRAPHX_GUARD_RTGLIB_HIP_GEMM_IMPL_HPP #include #include #include #include // Set this environment variable to "true" to perform GEMM tuning even when the // --exhaustive-tune option isn't set. Can be used to skip slow convolution tuning. MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_HIP_GEMM_TUNING); namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using milliseconds = std::chrono::duration; using microseconds = std::chrono::duration; void blas_shape_hip(const shape& in_shape); shape transpose_batch_hip(const shape& s, unsigned trans_batch); /** * @brief Templated implementations of the compute() and finalize() methods of the Gemm operator. * For each function there are overloads using either float or int32_t for the arguments * alpha and beta. * * @param ctx . * @param output_shape . * @param args . * @param alpha . * @param beta . */ MIGRAPHX_GPU_EXPORT void hip_gemm_compute(context& ctx, const shape& output_shape, const std::vector& args, float alpha, float beta, int32_t solution_idx); MIGRAPHX_GPU_EXPORT int32_t hip_gemm_finalize(context& ctx, const shape& output_shape, const std::vector& input_shapes, float alpha, float beta, int32_t solution_idx); MIGRAPHX_GPU_EXPORT int32_t hip_gemm_default_solution(context& ctx, const shape& output_shape, const std::vector& input_shapes); MIGRAPHX_GPU_EXPORT size_t hip_gemm_workspace_size(context& ctx, const shape& output_shape, const std::vector& input_shapes, float alpha, float beta, int32_t solution_idx); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/hipblaslt.hpp000066400000000000000000000107061510465702400263270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_HIPBLASLT_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_HIPBLASLT_HPP #include #include #include #include #if MIGRAPHX_USE_HIPBLASLT #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { // TODO: Remove hipblas_status_to_string() function when hipblaslt // provides an API for doing this in hipBLASLt. // Convert hipblas_status to string inline const char* hipblas_status_to_string(hipblasStatus_t status) { switch(status) { case HIPBLAS_STATUS_SUCCESS: return "HIPBLAS_STATUS_SUCCESS"; case HIPBLAS_STATUS_NOT_INITIALIZED: return "HIPBLAS_STATUS_NOT_INITIALIZED"; case HIPBLAS_STATUS_ALLOC_FAILED: return "HIPBLAS_STATUS_ALLOC_FAILED"; case HIPBLAS_STATUS_INVALID_VALUE: return "HIPBLAS_STATUS_INVALID_VALUE"; case HIPBLAS_STATUS_MAPPING_ERROR: return "HIPBLAS_STATUS_MAPPING_ERROR"; case HIPBLAS_STATUS_EXECUTION_FAILED: return "HIPBLAS_STATUS_EXECUTION_FAILED"; case HIPBLAS_STATUS_INTERNAL_ERROR: return "HIPBLAS_STATUS_INTERNAL_ERROR"; case HIPBLAS_STATUS_NOT_SUPPORTED: return "HIPBLAS_STATUS_NOT_SUPPORTED"; case HIPBLAS_STATUS_ARCH_MISMATCH: return "HIPBLAS_STATUS_ARCH_MISMATCH"; case HIPBLAS_STATUS_HANDLE_IS_NULLPTR: return "HIPBLAS_STATUS_HANDLE_IS_NULLPTR"; case HIPBLAS_STATUS_INVALID_ENUM: return "HIPBLAS_STATUS_INVALID_ENUM"; case HIPBLAS_STATUS_UNKNOWN: return "HIPBLAS_STATUS_UNKNOWN"; } return ""; } template inline auto hipblaslt_invoke(F f, Ts... xs) { // Call the function `f` with `xs...` and capture the status auto status = f(xs...); if(status != HIPBLAS_STATUS_SUCCESS) { std::string error_message = "hipBLAS error: '" + std::string(hipblas_status_to_string(status)) + "'(" + std::to_string(status) + ") at " + __FILE__ + ":" + std::to_string(__LINE__); MIGRAPHX_THROW(EXIT_FAILURE, error_message); } return status; } // Invoke a hipBLASLt call. If used to validate a call, set fatal_error = false to prevent // throwing an exception on failure. template auto hipblaslt_invoke(F f, Pack p, Ts... xs, bool fatal_error = true) { return p([=](auto... ws) { auto status = f(ws..., xs...); if(status != HIPBLAS_STATUS_SUCCESS) { if(fatal_error) { MIGRAPHX_THROW("hipblaslt_invoke: hipBlasLt call failed with status " + std::to_string(status)); } } return status; }); } using hipblaslt_handle_ptr = MIGRAPHX_MANAGE_PTR(hipblasLtHandle_t, hipblasLtDestroy); using hipblaslt_preference_ptr = MIGRAPHX_MANAGE_PTR(hipblasLtMatmulPreference_t, hipblasLtMatmulPreferenceDestroy); hipblaslt_handle_ptr create_hipblaslt_handle_ptr(); hipblaslt_preference_ptr create_hipblaslt_preference_ptr(); const size_t hipblaslt_workspace_size = 2 * 128 * 1024 * 1024; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_USE_HIPBLASLT #endif // MIGRAPHX_GUARD_MIGRAPHLIB_HIPBLASLT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/kernel.hpp000066400000000000000000000064121510465702400256240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_KERNEL_HPP #define MIGRAPHX_GUARD_RTGLIB_KERNEL_HPP #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct kernel_impl; struct MIGRAPHX_GPU_EXPORT kernel { struct pointers { pointers() {} pointers(void** pp, std::size_t pn) : p(pp), n(pn) {} pointers(std::vector& v) : p(v.data()), n(v.size()) {} pointers(pmr::vector& v) : p(v.data()), n(v.size()) {} void** data() const { return p; } std::size_t size() const { return n; } std::size_t bytes() const { return n * sizeof(void*); } private: void** p = nullptr; std::size_t n = 0; }; kernel() = default; kernel(const char* image, const std::string& name); template kernel(const std::vector& image, const std::string& name) : kernel(reinterpret_cast(image.data()), name) { } void launch(hipStream_t stream, std::size_t global, std::size_t local, const std::vector& args, hipEvent_t start = nullptr, hipEvent_t stop = nullptr) const; void launch(hipStream_t stream, std::size_t global, std::size_t local, pointers args, hipEvent_t start = nullptr, hipEvent_t stop = nullptr) const; template {}...)> auto launch(hipStream_t stream, std::size_t global, std::size_t local, Ts... zs) const { return [=](auto&&... xs) { launch(stream, global, local, std::vector{xs...}, zs...); }; } private: std::shared_ptr impl; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/logsoftmax.hpp000066400000000000000000000041021510465702400265210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_LOGSOFTMAX_HPP #define MIGRAPHX_GUARD_RTGLIB_LOGSOFTMAX_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct hip_logsoftmax { op::logsoftmax op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::logsoftmax"; } shape compute_shape(const std::vector& inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/loop.hpp000066400000000000000000000043731510465702400253210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_LOOP_HPP #define MIGRAPHX_GUARD_RTGLIB_LOOP_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_loop { op::loop op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::loop"; } shape compute_shape(std::vector inputs, std::vector mods) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args, const std::vector& mods, const std::function( module_ref&, const std::unordered_map&)>& run) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/lowering.hpp000066400000000000000000000036431510465702400261750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MIOPEN_LOWERING_HPP #define MIGRAPHX_GUARD_RTGLIB_MIOPEN_LOWERING_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; namespace gpu { /** * Compiler pass that makes GPU-specific instruction changes. * * Copies to and from the device if `offload_copy` is true. * * Maps instructions to their GPU-specific counterparts. * * Inserts `allocate` instructions before GPU operators. */ struct MIGRAPHX_GPU_EXPORT lowering { context* ctx; bool offload_copy; std::string name() const { return "gpu::lowering"; } void apply(module_pass_manager& mpm) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/lrn.hpp000066400000000000000000000042221510465702400251340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_LRN_HPP #define MIGRAPHX_GUARD_RTGLIB_LRN_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; #if MIGRAPHX_USE_MIOPEN struct miopen_lrn { op::lrn op; shared ldesc; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::lrn"; } shape compute_shape(const std::vector& inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; void finalize(context&, const shape&, const std::vector&); std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/miopen.hpp000066400000000000000000000317541510465702400256420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_MIOPEN_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_MIOPEN_HPP #include #include #include #if MIGRAPHX_USE_MIOPEN #include #include #include #include #include #ifdef MIGRAPHX_HAS_FIND_MODE_API extern "C" miopenStatus_t miopenHiddenSetConvolutionFindMode(miopenConvolutionDescriptor_t convDesc, // NOLINT int findMode); // NOLINT #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using miopen_handle = MIGRAPHX_MANAGE_PTR(miopenHandle_t, miopenDestroy); using tensor_descriptor = MIGRAPHX_MANAGE_PTR(miopenTensorDescriptor_t, miopenDestroyTensorDescriptor); using convolution_descriptor = MIGRAPHX_MANAGE_PTR(miopenConvolutionDescriptor_t, miopenDestroyConvolutionDescriptor); using pooling_descriptor = MIGRAPHX_MANAGE_PTR(miopenPoolingDescriptor_t, miopenDestroyPoolingDescriptor); using activation_descriptor = MIGRAPHX_MANAGE_PTR(miopenActivationDescriptor_t, miopenDestroyActivationDescriptor); using fusion_plan_descriptor = MIGRAPHX_MANAGE_PTR(miopenFusionPlanDescriptor_t, miopenDestroyFusionPlan); using fused_operator_args = MIGRAPHX_MANAGE_PTR(miopenOperatorArgs_t, miopenDestroyOperatorArgs); using lrn_descriptor = MIGRAPHX_MANAGE_PTR(miopenLRNDescriptor_t, miopenDestroyLRNDescriptor); template Result make_obj(F f, Ts... xs) { typename Result::pointer x = nullptr; auto status = f(&x, xs...); Result r{x}; if(status != miopenStatusSuccess) MIGRAPHX_THROW("MAKE_OBJ: MIOpen call failed"); return r; } #ifdef MIGRAPHX_HAS_FIND_2_API using miopen_find_options = MIGRAPHX_MANAGE_PTR(miopenFindOptions_t, miopenDestroyFindOptions); using miopen_problem = MIGRAPHX_MANAGE_PTR(miopenProblem_t, miopenDestroyProblem); using miopen_solution = MIGRAPHX_MANAGE_PTR(miopenSolution_t, miopenDestroySolution); inline miopen_solution find_solution(miopenHandle_t handle, size_t num_inputs, const miopenTensorArgument_t* tensor_args, void* workspace, size_t workspace_size, miopenProblem_t problem, bool tune = false) { miopenSolution_t solution; size_t found = 0; miopen_find_options fo = make_obj(&miopenCreateFindOptions); if(tune) { miopenSetFindOptionTuning(fo.get(), 1); } #ifdef MIGRAPHX_PREALLOCATE_MIOPEN_BUFFERS for(auto i : range(num_inputs)) { auto status = miopenSetFindOptionPreallocatedTensor( fo.get(), tensor_args[i].id, tensor_args[i].buffer); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen: failed to preallocate tensors for the find process"); } auto status = miopenSetFindOptionPreallocatedWorkspace(fo.get(), workspace, workspace_size); if(status != miopenStatusSuccess) MIGRAPHX_THROW("MIOpen: failed to preallocate workspace for the find process"); #else miopenStatus_t status; (void)(num_inputs); (void)(tensor_args); (void)(workspace_size); (void)(workspace); #endif status = miopenFindSolutions(handle, problem, fo.get(), &solution, &found, 1); auto result = miopen_solution{solution}; if(status != miopenStatusSuccess or found == 0) MIGRAPHX_THROW("MIOpen: miopenFindSolutions failed"); return result; } inline void set_tensor_descriptor(miopenTensorArgumentId_t name, tensor_descriptor& desc, miopen_problem& problem_ptr) { auto status = miopenSetProblemTensorDescriptor(problem_ptr.get(), name, desc.get()); if(status != miopenStatusSuccess) { MIGRAPHX_THROW("setting problem tensor description failed"); } } #endif inline tensor_descriptor make_tensor(const migraphx::shape& os) { auto s = os.normalize_standard(); auto t = make_obj(&miopenCreateTensorDescriptor); // Convert to ints std::vector lens(s.lens().begin(), s.lens().end()); std::vector strides(s.strides().begin(), s.strides().end()); miopenDataType_t d; if(s.type() == shape::float_type) d = miopenFloat; else if(s.type() == shape::half_type) d = miopenHalf; else if(s.type() == shape::int32_type) d = miopenInt32; else if(s.type() == shape::int8_type) d = miopenInt8; else if(s.type() == shape::bf16_type) d = miopenBFloat16; else MIGRAPHX_THROW("MAKE_TENSOR: unsupported type"); miopenSetTensorDescriptor(t.get(), d, s.lens().size(), lens.data(), strides.data()); return t; } template inline convolution_descriptor make_conv(const T& op) { auto c = make_obj(&miopenCreateConvolutionDescriptor); miopenConvolutionMode_t c_mode = miopenConvolution; if(op.group > 1) c_mode = miopenGroupConv; int kdims = op.kdims(); std::vector padding(std::max(2, kdims), 0); std::vector stride(std::max(2, kdims), 1); std::vector dilation(std::max(2, kdims), 1); std::copy_backward(op.padding.begin(), op.padding.begin() + kdims, padding.end()); std::copy_backward(op.stride.begin(), op.stride.end(), stride.end()); std::copy_backward(op.dilation.begin(), op.dilation.end(), dilation.end()); miopenInitConvolutionNdDescriptor( c.get(), padding.size(), padding.data(), stride.data(), dilation.data(), c_mode); if(op.group > 1) miopenSetConvolutionGroupCount(c.get(), op.group); #ifdef MIGRAPHX_HAS_FIND_MODE_API miopenHiddenSetConvolutionFindMode(c.get(), 1); // Normal mode #endif return c; } template inline convolution_descriptor make_convolution_backwards(const T& op) { auto c = make_obj(&miopenCreateConvolutionDescriptor); miopenConvolutionMode_t c_mode = miopenTranspose; int kdims = op.kdims(); std::vector padding(std::max(2, kdims), 0); std::vector stride(std::max(2, kdims), 1); std::vector dilation(std::max(2, kdims), 1); std::copy_backward(op.padding.begin(), op.padding.end(), padding.end()); std::copy_backward(op.stride.begin(), op.stride.end(), stride.end()); std::copy_backward(op.dilation.begin(), op.dilation.end(), dilation.end()); miopenInitConvolutionNdDescriptor( c.get(), padding.size(), padding.data(), stride.data(), dilation.data(), c_mode); if(op.group > 1) miopenSetConvolutionGroupCount(c.get(), op.group); return c; } inline pooling_descriptor make_pooling(const migraphx::op::pooling& op) { miopenPoolingMode_t mode; if(op.mode == op::pooling_mode::max) mode = miopenPoolingMax; else if(op.mode == op::pooling_mode::average) mode = miopenPoolingAverage; else { std::stringstream ss("Unknown mode for pooling: "); ss << op.mode; MIGRAPHX_THROW(ss.str()); } if(not std::all_of( op.dilations.cbegin(), op.dilations.cend(), [](std::size_t d) { return d == 1; })) { MIGRAPHX_THROW("Unsupported dilations for pooling: [" + to_string_range(op.dilations) + "]"); } auto p = make_obj(&miopenCreatePoolingDescriptor); int kdims = op.kdims(); std::vector padding(std::max(2, kdims), 0); std::vector stride(std::max(2, kdims), 1); std::vector lengths(std::max(2, kdims), 1); std::copy_backward(op.padding.begin(), op.padding.begin() + kdims, padding.end()); std::copy_backward(op.stride.begin(), op.stride.end(), stride.end()); std::copy_backward(op.lengths.begin(), op.lengths.end(), lengths.end()); miopenSetNdPoolingDescriptor( p.get(), mode, padding.size(), lengths.data(), padding.data(), stride.data()); return p; } inline lrn_descriptor make_lrn(const migraphx::op::lrn& op) { auto ldesc = make_obj(&miopenCreateLRNDescriptor); miopenSetLRNDescriptor(ldesc.get(), miopenLRNCrossChannel, op.size, op.alpha, op.beta, op.bias); return ldesc; } inline activation_descriptor make_relu() { auto ad = make_obj(&miopenCreateActivationDescriptor); miopenSetActivationDescriptor(ad.get(), miopenActivationRELU, 0, 0, 0); return ad; } inline activation_descriptor make_sigmoid() { auto ad = make_obj(&miopenCreateActivationDescriptor); miopenSetActivationDescriptor(ad.get(), miopenActivationLOGISTIC, 0, 0, 0); return ad; } inline activation_descriptor make_tanh() { auto ad = make_obj(&miopenCreateActivationDescriptor); // onnx operator does not apply additional scaling for tanh // defaults for alpha and beta are therefore set to 1 miopenSetActivationDescriptor(ad.get(), miopenActivationTANH, 1, 1, 0); return ad; } inline activation_descriptor make_abs() { auto ad = make_obj(&miopenCreateActivationDescriptor); miopenSetActivationDescriptor(ad.get(), miopenActivationABS, 0, 0, 0); return ad; } inline activation_descriptor make_leaky_relu(double alpha) { auto ad = make_obj(&miopenCreateActivationDescriptor); miopenSetActivationDescriptor(ad.get(), miopenActivationLEAKYRELU, alpha, 0, 0); return ad; } inline activation_descriptor make_elu(double alpha) { auto ad = make_obj(&miopenCreateActivationDescriptor); miopenSetActivationDescriptor(ad.get(), miopenActivationELU, alpha, 0, 0); return ad; } inline fusion_plan_descriptor make_fusion_plan(const shape& input) { auto t = make_tensor(input); return make_obj(&miopenCreateFusionPlan, miopenVerticalFusion, t.get()); } // Temporary hack to workaround memory problems in miopen inline fusion_plan_descriptor make_fusion_plan(const tensor_descriptor& input) { return make_obj( &miopenCreateFusionPlan, miopenVerticalFusion, input.get()); } inline fused_operator_args make_fused_args() { return make_obj(&miopenCreateOperatorArgs); } template auto reflect(miopenActivationDescriptor_t ad, F f) { assert(ad != nullptr); miopenActivationMode_t mode = miopenActivationPASTHRU; double alpha = 0.0; double beta = 0.0; double gamma = 0.0; miopenGetActivationDescriptor(ad, &mode, &alpha, &beta, &gamma); return pack(f(std::move(mode), "mode"), // NOLINT f(std::move(alpha), "alpha"), // NOLINT f(std::move(beta), "beta"), // NOLINT f(std::move(gamma), "gamma")); // NOLINT } template auto reflect(miopenLRNDescriptor_t lrnd, F f) { assert(lrnd != nullptr); miopenLRNMode_t mode = miopenLRNWithinChannel; unsigned int n = 0; double alpha = 0.0; double beta = 0.0; double k = 0.0; miopenGetLRNDescriptor(lrnd, &mode, &n, &alpha, &beta, &k); return pack(f(std::move(mode), "mode"), // NOLINT f(std::move(n), "n"), // NOLINT f(std::move(alpha), "alpha"), // NOLINT f(std::move(beta), "beta"), // NOLINT f(std::move(k), "k")); // NOLINT } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/mlir.hpp000066400000000000000000000064171510465702400253140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_MLIR_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_MLIR_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { MIGRAPHX_GPU_EXPORT std::string dump_mlir(module m); MIGRAPHX_GPU_EXPORT std::string dump_mlir(module m, const std::vector& inputs); MIGRAPHX_GPU_EXPORT void dump_mlir_to_file(module m, const std::vector& inputs, const fs::path& location); MIGRAPHX_GPU_EXPORT bool is_module_fusible(const module& m, const context& migraphx_ctx, const value& solution); struct MIGRAPHX_GPU_EXPORT mlir_code_object { code_object_op cop; std::vector prefill_indices = {}; std::vector prefill_values = {}; }; MIGRAPHX_GPU_EXPORT bool is_reduce(const instruction& ins); MIGRAPHX_GPU_EXPORT mlir_code_object compile_mlir(const context& migraphx_ctx, module m, const std::vector& in_shapes, const value& solution); MIGRAPHX_GPU_EXPORT instruction_ref insert_mlir(module& m, instruction_ref ins, code_object_op co, const std::vector& inputs); MIGRAPHX_GPU_EXPORT tuning_config get_tuning_config_mlir(const context& migraphx_ctx, module m, const std::vector& inputs, bool exhaustive); MIGRAPHX_GPU_EXPORT void dump_mlir_to_mxr(module m, const std::vector& inputs, const fs::path& location); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/multinomial.hpp000066400000000000000000000037601510465702400267010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MULTINOMIAL_HPP #define MIGRAPHX_GUARD_RTGLIB_MULTINOMIAL_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_multinomial { op::multinomial op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::multinomial"; } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/name.hpp000066400000000000000000000046101510465702400252620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_OP_NAME_HPP #define MIGRAPHX_GUARD_RTGLIB_OP_NAME_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { template struct oper { // function to extract the name part of an operator. For example, we have // a operation "sin", then the get_type_name() will return // "migraphx::version_1::gpu::hip_sin", this functin will return the name // "gpu::sin" as the operator name std::string name() const { const std::string& name = get_type_name(); // search the namespace gpu (::gpu::) auto pos_ns = name.find("::gpu::"); if(pos_ns != std::string::npos) { auto pos_name = name.find("hip_", pos_ns + std::string("::gpu::").length()); if(pos_name != std::string::npos) { return std::string("gpu::") + name.substr(pos_name + 4); } else { return name.substr(pos_ns + 2); } } return "unknown_operator_name"; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/nonzero.hpp000066400000000000000000000040741510465702400260400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_NONZERO_HPP #define MIGRAPHX_GUARD_RTGLIB_NONZERO_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_nonzero { op::nonzero op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::nonzero"; } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/oper.hpp000066400000000000000000000123151510465702400253100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_UNARY_HPP #define MIGRAPHX_GUARD_RTGLIB_UNARY_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { template struct device_base : oper { template static auto reflect(Self&, F) { return pack(); } std::vector reduce_shapes; void finalize(context&, const shape&, const std::vector& inputs) { reduce_shapes = reduce_dims(inputs); } argument get_arg(const std::vector& args, std::size_t i) const { if(reduce_shapes.empty()) return args[i]; return args.at(i).reshape(reduce_shapes.at(i)); } shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(N + 1); auto s0 = inputs.at(0); if(std::all_of(inputs.begin(), inputs.end() - 1, [&](auto s) { return s == s0; }) and s0.packed()) return s0; else return {s0.type(), s0.lens()}; } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; template struct unary_device : device_base { argument compute(context& ctx, const shape&, const std::vector& args) const { F(ctx.get_stream().get(), this->get_arg(args, 1), this->get_arg(args, 0)); return args[1]; } }; template struct binary_device : device_base { argument compute(context& ctx, const shape&, const std::vector& args) const { F(ctx.get_stream().get(), this->get_arg(args, 2), this->get_arg(args, 0), this->get_arg(args, 1)); return args[2]; } }; template struct ternary_device : device_base { argument compute(context& ctx, const shape&, const std::vector& args) const { F(ctx.get_stream().get(), this->get_arg(args, 3), this->get_arg(args, 0), this->get_arg(args, 1), this->get_arg(args, 2)); return args[3]; } }; template struct quaternary_device : device_base { argument compute(context& ctx, const shape&, const std::vector& args) const { F(ctx.get_stream().get(), this->get_arg(args, 4), this->get_arg(args, 0), this->get_arg(args, 1), this->get_arg(args, 2), this->get_arg(args, 3)); return args[4]; } }; template struct quinary_device : device_base { argument compute(context& ctx, const shape&, const std::vector& args) const { F(ctx.get_stream().get(), this->get_arg(args, 5), this->get_arg(args, 0), this->get_arg(args, 1), this->get_arg(args, 2), this->get_arg(args, 3), this->get_arg(args, 4)); return args[5]; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/pack_args.hpp000066400000000000000000000036651510465702400263050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_PACK_ARGS_HPP #define MIGRAPHX_GUARD_RTGLIB_PACK_ARGS_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct kernel_argument { template , MIGRAPHX_REQUIRES(not std::is_base_of{})> kernel_argument(T&& x) : size(sizeof(U)), align(alignof(U)), data(&x) // NOLINT { } std::size_t size; std::size_t align; void* data; }; MIGRAPHX_GPU_EXPORT std::vector pack_args(const std::vector& args); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/perfdb.hpp000066400000000000000000000033411510465702400256040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_PERFDB_HPP #define MIGRAPHX_GUARD_GPU_PERFDB_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct problem_params { operation op; std::vector inputs; shape output; }; std::string get_mlir_perf_for_conv(const problem_params& pp, bool xdlops); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_PERFDB_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/pooling.hpp000066400000000000000000000043141510465702400260120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_POOLING_HPP #define MIGRAPHX_GUARD_RTGLIB_POOLING_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; #if MIGRAPHX_USE_MIOPEN struct miopen_pooling { op::pooling op; shared pd; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::pooling"; } shape compute_shape(const std::vector& inputs) const; void finalize(context&, const shape&, const std::vector&); argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/prefix_scan_sum.hpp000066400000000000000000000052321510465702400275300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_PREFIX_SCAN_SUM_HPP #define MIGRAPHX_GUARD_GPU_PREFIX_SCAN_SUM_HPP #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_prefix_scan_sum : oper { op::prefix_scan_sum op; template static auto reflect(Self& self, T f) { return migraphx::reflect(self.op, f); } shape compute_shape(const std::vector& inputs) const { std::vector in_shapes{inputs}; in_shapes.pop_back(); check_shapes{in_shapes, *this}.standard(); return op.normalize_compute_shape(in_shapes); } argument compute(context& ctx, const shape&, const std::vector& args) const { device::prefix_scan_sum( ctx.get_stream().get(), args[1], args[0], op.axis, op.exclusive, op.reverse); return args[1]; } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_PREFIX_SCAN_SUM_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/prefuse_ops.hpp000066400000000000000000000033221510465702400266730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_PREFUSE_OPS_HPP #define MIGRAPHX_GUARD_GPU_PREFUSE_OPS_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module_pass_manager; namespace gpu { struct MIGRAPHX_GPU_EXPORT prefuse_ops { bool enable_attention = false; std::string name() const { return "gpu::prefuse_ops"; } void apply(module_pass_manager& mpm) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_PREFUSE_OPS_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/prepare_reduce.hpp000066400000000000000000000032141510465702400273260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_GPU_PREPARE_REDUCE_HPP #define MIGRAPHX_GUARD_GPU_PREPARE_REDUCE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { struct prepare_reduce { std::string name() const { return "gpu::prepare_reduce"; } void apply(module& m) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_PREPARE_REDUCE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/problem_cache.hpp000066400000000000000000000037621510465702400271340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_GPU_PROBLEM_CACHE_HPP #define MIGRAPHX_GUARD_GPU_PROBLEM_CACHE_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct MIGRAPHX_GPU_EXPORT problem_cache { bool has(const std::string& name, const value& problem) const; void insert(const std::string& name, const value& problem, const value& solution); void mark(const std::string& name, const value& problem); optional get(const std::string& name, const value& problem) const; void load(); void save() const; std::unordered_map cache; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_PROBLEM_CACHE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/reduce_op.hpp000066400000000000000000000051011510465702400263030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REDUCE_OP_HPP #define MIGRAPHX_GUARD_RTGLIB_REDUCE_OP_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; template struct reduce_op : oper { Op op; template static auto reflect(Self& self, T f) { return migraphx::reflect(self.op, f); } shape compute_shape(const std::vector& inputs) const { std::vector in_shapes{inputs}; in_shapes.pop_back(); check_shapes{in_shapes, *this}.standard(); return op.normalize_compute_shape(in_shapes); } argument compute(context& ctx, const shape&, const std::vector& args) const { F(ctx.get_stream().get(), args[1], args[0]); return args[1]; } std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } reduce_op() {} reduce_op(const Op& op_ref) : op(op_ref) {} }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/reverse.hpp000066400000000000000000000040751510465702400260220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_REVERSE_HPP #define MIGRAPHX_GUARD_RTGLIB_REVERSE_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_reverse { op::reverse op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::reverse"; } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/rnn_variable_seq_lens.hpp000066400000000000000000000065131510465702400307010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_RNN_VARIABLE_SEQ_LENS_HPP #define MIGRAPHX_GUARD_RTGLIB_RNN_VARIABLE_SEQ_LENS_HPP #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct hip_rnn_var_sl_shift_sequence { op::rnn_var_sl_shift_sequence op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::rnn_var_sl_shift_sequence"; } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; struct hip_rnn_var_sl_shift_output { op::rnn_var_sl_shift_output op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::rnn_var_sl_shift_output"; } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; struct hip_rnn_var_sl_last_output { op::rnn_var_sl_last_output op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::" + op.name(); } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape&, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/rocblas.hpp000066400000000000000000000035171510465702400257740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_ROCBLAS_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_ROCBLAS_HPP #include #include #if MIGRAPHX_USE_ROCBLAS #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #if MIGRAPHX_USE_ROCBLAS using rocblas_handle_ptr = MIGRAPHX_MANAGE_PTR(rocblas_handle, rocblas_destroy_handle); rocblas_handle_ptr create_rocblas_handle_ptr(); rocblas_handle_ptr create_rocblas_handle_ptr(hipStream_t s); #endif struct context; MIGRAPHX_GPU_EXPORT bool get_compute_fp32_flag(); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/schedule_model.hpp000066400000000000000000000036311510465702400273200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_SCHEDULE_MODEL_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_SCHEDULE_MODEL_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct operation; namespace gpu { struct schedule_model { std::size_t streams = 0; std::size_t concurrency() const; void sched(module& m, instruction_ref ins, std::size_t n) const; void wait(module& m, instruction_ref ins, std::size_t wait_id) const; void record(module& m, instruction_ref ins, std::size_t wait_id) const; std::size_t weight(const operation& op) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/sync_device.hpp000066400000000000000000000031341510465702400266350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_GPU_SYNC_DEVICE_HPP #define MIGRAPHX_GUARD_RTGLIB_GPU_SYNC_DEVICE_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { struct sync_device { std::string name() const { return "sync_device"; } void apply(module& m) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/target.hpp000066400000000000000000000035751510465702400256410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_MIOPEN_TARGET_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_MIOPEN_TARGET_HPP #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct MIGRAPHX_GPU_EXPORT target { std::string name() const; std::vector get_passes(migraphx::context& gctx, const compile_options& options) const; migraphx::context get_context() const; argument copy_to(const argument& arg) const; argument copy_from(const argument& arg) const; argument allocate(const shape& s) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/time_op.hpp000066400000000000000000000047271510465702400260070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_DRIVER_PERF_HPP #define MIGRAPHX_GUARD_GPU_DRIVER_PERF_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_GPU_EXPORT double time_op(const context& ictx, operation op, const std::vector& inputs, int bundle = 1, int nruns = 100); MIGRAPHX_GPU_EXPORT double time_program(const context& ictx, program p, const std::unordered_map& fill_map, int bundle = 1, int nruns = 100); /* benchmark gpu::code_object with expected input shapes over n iterations */ MIGRAPHX_GPU_EXPORT double time_op(const context& ictx, operation op, int bundle = 1, int nruns = 100); MIGRAPHX_GPU_EXPORT double time_loop(migraphx::gpu::context& gctx, int bundle, int nruns, const std::function& f); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_DRIVER_PERF_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/topk.hpp000066400000000000000000000040531510465702400253200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_TOPK_HPP #define MIGRAPHX_GUARD_RTGLIB_TOPK_HPP #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct context; struct hip_topk { op::topk op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "gpu::topk"; } shape compute_shape(std::vector inputs) const; argument compute(context& ctx, const shape& output_shape, const std::vector& args) const; std::ptrdiff_t output_alias(const std::vector& shapes) const { return shapes.size() - 1; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/tuning_config.hpp000066400000000000000000000031741510465702400271770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_GPU_TUNING_CONFIG_HPP #define MIGRAPHX_GUARD_GPU_TUNING_CONFIG_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct tuning_config { value problem; std::vector solutions; std::string detailed_problem_info; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_GPU_TUNING_CONFIG_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/include/migraphx/gpu/write_literals.hpp000066400000000000000000000032301510465702400273700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_MIOPEN_WRITE_LITERALS_HPP #define MIGRAPHX_GUARD_RTGLIB_MIOPEN_WRITE_LITERALS_HPP #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; namespace gpu { struct MIGRAPHX_GPU_EXPORT write_literals { context* ctx = nullptr; std::string name() const { return "gpu::write_literals"; } void apply(module& m) const; }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/000077500000000000000000000000001510465702400203615ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/ck_gemm.cpp000066400000000000000000000231661510465702400224770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT // NOLINTNEXTLINE static const char* const ck_gemm_kernel = R"__migraphx__( #include #include #include #include #include <${include}> namespace migraphx { ${preamble} extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors(), rotate_last())(${args})([](auto... xs) { ck_gemm<${solution}, ${blocks_per_batch}>(xs...); }); } } } // namespace migraphx )__migraphx__"; struct ck_gemm_compiler : compiler { std::vector names() const { return {"ck_gemm", "gpu::ck_gemm"}; } ck::host::device_gemm_multiple_d::Problem create_problem(const std::vector& inputs, const value& v) const { const auto& a_shape = inputs[0]; const auto& b_shape = inputs[1]; const auto& c_shape = inputs.back(); // cppcheck-suppress unreadVariable auto rank = a_shape.ndim(); auto batch_count = get_batch_count(c_shape); auto m = c_shape.lens()[rank - 2]; m = can_fold_batch(inputs) ? m * batch_count : m; auto n = c_shape.lens().back(); auto k = a_shape.lens().back(); const bool trans_a = transposed_matrix(a_shape); const bool trans_b = transposed_matrix(b_shape); const bool trans_e = transposed_matrix(c_shape); const auto a_type = get_type(a_shape); const auto b_type = get_type(b_shape); const auto e_type = get_type(c_shape); std::vector ds_layout; std::transform(inputs.begin() + 2, inputs.end() - 1, std::back_inserter(ds_layout), [](const auto& i) { return transposed_matrix(i); }); std::vector ds_type; std::transform(inputs.begin() + 2, inputs.end() - 1, std::back_inserter(ds_type), [](const auto& i) { return get_type(i); }); std::string ck_passthrough = "ck_passthrough"; std::string cde_op = ck_passthrough; assert(inputs.size() < 4 or v.contains("post")); if(v.contains("post")) { cde_op = v.at("post").to(); } return ck::host::device_gemm_multiple_d::Problem{m, n, k, trans_a, trans_b, trans_e, ds_layout, a_type, b_type, e_type, ds_type, ck_passthrough, ck_passthrough, cde_op}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { const auto& c_shape = inputs.back(); auto tuning_value = v.get("tuning_value", 34); auto batch_count = get_batch_count(c_shape); auto problem = create_problem(inputs, v); const auto include_header = problem.GetIncludeHeader(); const auto solutions = problem.GetSolutions(ctx.get_current_device().get_gfx_name()); const auto& solution = solutions.at(tuning_value); const auto template_str = solution.template_str; const auto blocks_per_batch = solution.grid_size; const auto block_size = solution.block_size; hip_compile_options options; options.additional_src_files = ck_headers(); auto grid_size = can_fold_batch(inputs) ? blocks_per_batch : batch_count * blocks_per_batch; options.set_launch_params(v, grid_size * block_size, block_size); options.inputs = inputs; options.output = c_shape; options.kernel_name = v.get("kernel", "ck_gemm_kernel"); options.virtual_inputs = inputs; if(can_fold_batch(inputs)) { auto vinputs = inputs; fold_batch_dims(vinputs[0]); remove_batch_dims(vinputs[1]); std::for_each(vinputs.begin() + 2, vinputs.end(), fold_batch_dims); options.virtual_inputs = vinputs; } if(v.get("check", false) or enabled(MIGRAPHX_CK_DEBUG{})) options.emplace_param("-DMIGRAPHX_CK_CHECK=1"); auto src = interpolate_string(ck_gemm_kernel, {{"solution", template_str}, {"include", include_header}, {"params", enum_params(inputs.size(), "void * private_p")}, {"args", enum_params(inputs.size(), "private_p")}, {"blocks_per_batch", to_string(blocks_per_batch)}, {"preamble", v.get("preamble", std::string{})}, {"kernel", options.kernel_name}}); return compile_hip_code_object(ctx, src, options); } value create_settings(instruction_ref ins, const operation& op) const { auto v = op.to_value(); v["kernel"] = "ck_gemm_kernel"; if(not ins->module_inputs().empty()) { auto* pm = ins->module_inputs().front(); v["preamble"] = generate_pointwise(*pm, "post_ck_gemm_function") + "\nMIGRAPHX_LIFT_CLASS(post_ck_gemm, post_ck_gemm_function);"; v["post"] = "ck_function_adaptor"; v["kernel"] = to_c_id("ck_gemm_" + generate_name_from_ops(*pm) + "_kernel"); } return v; } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op, const value& solution) const { auto shapes = to_shapes(ins->inputs()); auto v = create_settings(ins, op); if(not solution.is_null()) v["tuning_value"] = solution; return {compile_op(ctx, shapes, v), [=](module& m, instruction_ref ins2, const operation& code_object) { if(enabled(MIGRAPHX_LOG_CK_GEMM{})) { std::vector gemm_shapes{ shapes[0], shapes[1], shapes.back().with_type(shapes[0].type())}; std::cout << "gpu::ck_gemm: " << to_json_string(to_value(gemm_shapes)) << std::endl; } m.replace_instruction(ins2, code_object, ins2->inputs()); }}; } optional get_tuning_config(context& ctx, instruction_ref ins, const operation& op, bool exhaustive) const { if(not exhaustive and not enabled(MIGRAPHX_TUNE_CK{})) return nullopt; tuning_config tc; auto shapes = to_shapes(ins->inputs()); auto problem = create_problem(shapes, create_settings(ins, op)); auto solutions = problem.GetSolutions(ctx.get_current_device().get_gfx_name()); tc.solutions.resize(solutions.size()); std::iota(tc.solutions.begin(), tc.solutions.end(), 0); std::vector gemm_shapes{shapes[0], shapes[1], shapes.back()}; tc.problem = to_value(gemm_shapes); return tc; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/ck_gemm_softmax_gemm.cpp000066400000000000000000000237511510465702400252450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT // NOLINTNEXTLINE static const char* const ck_gemm_softmax_gemm_kernel = R"__migraphx__( #include #include #include #include #include #include #include <${include}> namespace migraphx { ${preamble} extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors(), rotate_last())(${args})([](auto... xs) { auto settings = make_ck_gemm_softmax_gemm_settings(MIGRAPHX_MAKE_CONSTANT(float{SCALE})); ck_gemm_softmax_gemm<${solution}, ${blocks_per_batch}>(settings, xs...); }); } } } // namespace migraphx )__migraphx__"; struct ck_gemm_softmax_gemm_compiler : compiler { std::vector names() const { return {"ck_gemm_softmax_gemm", "gpu::ck_gemm_softmax_gemm"}; } ck::host::device_batched_gemm_softmax_gemm::Problem create_problem(const std::vector& inputs, const value&) const { const auto& a_shape = inputs[0]; const auto& b_shape = inputs[1]; const auto& b1_shape = inputs[2]; const auto& c_shape = inputs.back(); // cppcheck-suppress unreadVariable auto rank = a_shape.ndim(); auto batch_count = get_batch_count(c_shape); auto m = c_shape.lens()[rank - 2]; m = can_fold_batch(inputs) ? m * batch_count : m; auto n = c_shape.lens().back(); auto k = a_shape.lens().back(); auto o = c_shape.lens().back(); const bool trans_a = transposed_matrix(a_shape); const bool trans_b = transposed_matrix(b_shape); const bool trans_b1 = transposed_matrix(b1_shape); const bool trans_c = transposed_matrix(c_shape); const auto a_type = get_type(a_shape); const auto b_type = get_type(b_shape); const auto b1_type = get_type(b1_shape); const auto c_type = get_type(c_shape); std::string ck_passthrough = "ck_passthrough"; return ck::host::device_batched_gemm_softmax_gemm::Problem{m, n, k, o, trans_a, trans_b, trans_b1, trans_c, a_type, b_type, b1_type, c_type, ck_passthrough, ck_passthrough, ck_passthrough, ck_passthrough}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { const auto& c_shape = inputs.back(); auto tuning_value = v.get("tuning_value", 5); auto batch_count = get_batch_count(c_shape); auto problem = create_problem(inputs, v); const auto include_header = problem.GetIncludeHeader(); const auto solutions = problem.GetSolutions(ctx.get_current_device().get_gfx_name()); const auto& solution = solutions.at(tuning_value); const auto template_str = solution.template_str; const auto blocks_per_batch = solution.grid_size; const auto block_size = solution.block_size; hip_compile_options options; options.additional_src_files = ck_headers(); auto grid_size = can_fold_batch(inputs) ? blocks_per_batch : batch_count * blocks_per_batch; options.set_launch_params(v, grid_size * block_size, block_size); options.inputs = inputs; options.output = c_shape; options.kernel_name = v.get("kernel", "ck_gemm_softmax_gemm_kernel"); options.virtual_inputs = inputs; if(can_fold_batch(inputs)) { auto vinputs = inputs; fold_batch_dims(vinputs[0]); remove_batch_dims(vinputs[1]); std::for_each(vinputs.begin() + 2, vinputs.end(), fold_batch_dims); options.virtual_inputs = vinputs; } if(v.get("check", false) or enabled(MIGRAPHX_CK_DEBUG{})) options.emplace_param("-DMIGRAPHX_CK_CHECK=1"); // scale assert(v.contains("scale")); auto scale = v.at("scale").to(); options.emplace_param("-DSCALE=" + std::to_string(scale)); auto src = interpolate_string(ck_gemm_softmax_gemm_kernel, {{"solution", template_str}, {"include", include_header}, {"params", enum_params(inputs.size(), "void * private_p")}, {"args", enum_params(inputs.size(), "private_p")}, {"blocks_per_batch", to_string(blocks_per_batch)}, {"preamble", v.get("preamble", std::string{})}, {"kernel", options.kernel_name}}); return compile_hip_code_object(ctx, src, options); } value create_settings(instruction_ref ins, const operation& op) const { auto v = op.to_value(); v["kernel"] = "ck_gemm_softmax_gemm_kernel"; if(not ins->module_inputs().empty()) { auto* pm = ins->module_inputs().front(); v["preamble"] = generate_pointwise(*pm, "post_ck_gemm_softmax_gemm_function") + "\nMIGRAPHX_LIFT_CLASS(post_ck_gemm_softmax_gemm, " "post_ck_gemm_softmax_gemm_function);"; v["post"] = "ck_function_adaptor"; v["kernel"] = "ck_gemm_softmax_gemm_" + generate_name_from_ops(*pm) + "_kernel"; } return v; } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op, const value& solution) const { auto shapes = to_shapes(ins->inputs()); auto v = create_settings(ins, op); if(not solution.is_null()) v["tuning_value"] = solution; return {compile_op(ctx, shapes, v), [=](module& m, instruction_ref ins2, const operation& code_object) { if(enabled(MIGRAPHX_LOG_CK_GEMM{})) { std::vector gemm_shapes{ shapes[0], shapes[1], shapes.back().with_type(shapes[0].type())}; std::cout << "gpu::ck_gemm_softmax_gemm: " << to_json_string(to_value(gemm_shapes)) << std::endl; } m.replace_instruction(ins2, code_object, ins2->inputs()); }}; } optional get_tuning_config(context& ctx, instruction_ref ins, const operation& op, bool exhaustive) const { if(not exhaustive and not enabled(MIGRAPHX_TUNE_CK{})) return nullopt; tuning_config tc; auto shapes = to_shapes(ins->inputs()); auto problem = create_problem(shapes, create_settings(ins, op)); auto solutions = problem.GetSolutions(ctx.get_current_device().get_gfx_name()); tc.solutions.resize(solutions.size()); std::iota(tc.solutions.begin(), tc.solutions.end(), 0); std::vector gemm_shapes{shapes[0], shapes[1], shapes.back()}; tc.problem = to_value(gemm_shapes); return tc; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/concat.cpp000066400000000000000000000225001510465702400223330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT // NOLINTNEXTLINE static const char* const concat_kernel = R"__migraphx__( #include #include #include #include namespace migraphx { ${preamble} extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors(), rotate_last(), ${transformers})(${args})([](auto y, ${concat_params}, auto... xs) { concat<${axis}>(${concat_args})(${post}, y, xs...); }); } } } // namespace migraphx )__migraphx__"; struct concat_compiler : compiler { std::vector names() const { return {"fused_concat", "concat"}; } static std::vector normalize(std::vector inputs, std::size_t& axis) { auto s = inputs.back(); std::vector strides(s.lens().size()); strides[axis] = 1; inputs.push_back(shape{s.type(), s.lens(), strides}); auto result = reduce_dims(normalize_permutation(inputs)); auto rstrides = result.back().strides(); auto it = std::find_if(rstrides.begin(), rstrides.end(), [](auto x) { return x == 1; }); axis = it - rstrides.begin(); result.pop_back(); return result; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; options.inputs = inputs; options.output = inputs.back(); auto concat_axis = v.at("axis").to(); options.virtual_inputs = normalize(inputs, concat_axis); options.kernel_name = v.get("kernel", "concat_kernel"); auto axis = find_fast_axis(options.virtual_inputs); auto op_names = v.at("ops").to_vector(); auto args = v.at("args"); vectorize vec{}; if(axis != concat_axis) vec = vectorize::elements(ctx, axis, options.virtual_inputs); auto nelements_per_op = options.virtual_inputs.back().elements() / op_names.size(); options.set_launch_params(v, compute_global_for(ctx, nelements_per_op / vec.size, 256)); options.emplace_param("-Wno-float-equal"); std::vector concat_params; std::vector concat_args; for(auto i : range(op_names.size())) { const auto& name = op_names[i]; auto n = args.at(name).to(); auto prefix = to_c_id(name + std::to_string(i) + "_concat_x"); transform(range(n), std::back_inserter(concat_params), [&](auto j) { return "auto " + prefix + std::to_string(j); }); std::vector pack_args = {"MIGRAPHX_LIFT(" + name + ")"}; transform(range(n), std::back_inserter(pack_args), [&](auto j) { return prefix + std::to_string(j); }); concat_args.push_back("pack(" + join_strings(pack_args, ", ") + ")"); } auto src = interpolate_string(concat_kernel, {{"kernel", options.kernel_name}, {"params", enum_params(inputs.size(), "void * private_p")}, {"args", enum_params(inputs.size(), "private_p")}, {"concat_params", join_strings(concat_params, ", ")}, {"concat_args", join_strings(concat_args, ", ")}, {"post", v.get("post", std::string{"op::id{}"})}, {"transformers", make_transformer_args(vec)}, {"preamble", v.get("preamble", std::string{})}, {"axis", std::to_string(concat_axis)}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { auto v = op.to_value(); if(op.name() == "fused_concat") { std::unordered_map mod_names_lookup; transform(range(ins->module_inputs().size()), std::inserter(mod_names_lookup, mod_names_lookup.end()), [&](auto i) { return std::make_pair(ins->module_inputs()[i]->name(), "pointwise" + std::to_string(i)); }); v["preamble"] = transform_accumulate( ins->module_inputs().begin(), ins->module_inputs().end(), std::string{}, std::plus<>{}, [&](module_ref mod) { return generate_pointwise(*mod, mod_names_lookup.at(mod->name())) + "\n"; }); std::vector mod_names; std::transform(ins->module_inputs().begin(), ins->module_inputs().end() - 1, std::back_inserter(mod_names), [&](module_ref mod) { return mod_names_lookup.at(mod->name()); }); v["ops"] = mod_names; module_ref last_mod = ins->module_inputs().back(); v["post"] = "MIGRAPHX_LIFT(" + mod_names_lookup.at(last_mod->name()) + ")"; std::unordered_map mod_args; std::transform(ins->module_inputs().begin(), ins->module_inputs().end() - 1, std::inserter(mod_args, mod_args.end()), [&](module_ref mod) { const auto& name = mod_names_lookup.at(mod->name()); return std::make_pair(name, mod->get_parameter_names().size()); }); v["args"] = mod_args; auto prefix_name = transform_accumulate(ins->module_inputs().begin(), ins->module_inputs().end() - 1, std::string{}, std::plus<>{}, [&](module_ref mod) -> std::string { auto name = generate_name_from_ops(*mod); if(name.empty()) return ""; return name + "_"; }); v["kernel"] = prefix_name + "concat_" + generate_name_from_ops(*(ins->module_inputs().back())) + "_kernel"; } else if(op.name() == "concat") { auto concat_inputs = ins->inputs().size() - 1; if(not ins->module_inputs().empty()) { auto* pm = ins->module_inputs().front(); concat_inputs = ins->inputs().size() - pm->get_parameter_names().size(); v["preamble"] = generate_pointwise(*pm, "post_concat"); v["post"] = "MIGRAPHX_LIFT(post_concat)"; v["kernel"] = "concat_" + generate_name_from_ops(*pm) + "_kernel"; } std::vector mod_names(concat_inputs, "op::id{}"); v["ops"] = mod_names; std::unordered_map mod_args = {{"op::id{}", 1}}; v["args"] = mod_args; } return compile_op(ctx, to_shapes(ins->inputs()), v); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/concat_past_present.cpp000066400000000000000000000077311510465702400251330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT // NOLINTNEXTLINE static const char* const concat_past_present_kernel = R"__migraphx__( #include #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors(), rotate_last())(${args})([](auto... xs) { concat_past_present(xs..., make_gqa_parameters(${gqa_params})); }); } } } // namespace migraphx )__migraphx__"; struct concat_past_present_compiler : compiler { std::vector names() const { return {"concat_past_present", "gpu::concat_past_present"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { auto params = init_params(inputs, v); auto gqa_params_str = params.make_init_str(); hip_compile_options options; options.set_launch_params( v, compute_global_for(ctx, params.batch_size * params.kv_num_heads * params.sequence_length * params.head_size)); options.inputs = inputs; options.output = inputs.back(); options.kernel_name = v.get("kernel", "concat_past_present_kernel"); auto src = interpolate_string(concat_past_present_kernel, {{"params", enum_params(inputs.size(), "void * private_p")}, {"args", enum_params(inputs.size(), "private_p")}, {"gqa_params", gqa_params_str}, {"kernel", options.kernel_name}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { auto shapes = to_shapes(ins->inputs()); auto v = op.to_value(); return compile_op(ctx, shapes, v); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/gather.cpp000066400000000000000000000057071510465702400223500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { // NOLINTNEXTLINE static const char* const gather_kernel = R"__migraphx__( #include #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void gather_kernel(void* in_data, void* in_indices, void* output) { make_tensors()(in_data, in_indices, output)([](auto&&... xs) { gather<${axis}>(xs...); }); } } } // namespace migraphx )__migraphx__"; struct gather_compiler : compiler { std::vector names() const { return {"gather"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; const auto& out_s = inputs.back(); options.set_launch_params(v, compute_global_for(ctx, out_s.elements())); options.inputs = inputs; options.output = out_s; options.kernel_name = "gather_kernel"; options.virtual_inputs = inputs; auto axis = v.at("axis").to(); auto src = interpolate_string(gather_kernel, {{"axis", axis}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/gathernd.cpp000066400000000000000000000062151510465702400226650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { // NOLINTNEXTLINE static const char* const gathernd_kernel = R"__migraphx__( #include #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void gathernd_kernel(void* in_data, void* in_indices, void* output) { make_tensors()(in_data, in_indices, output)([](auto&&... xs) { auto settings = make_gathernd_settings(MIGRAPHX_MAKE_CONSTANT(int64_t{BATCH_DIMS})); gathernd(xs..., settings); }); } } } // namespace migraphx )__migraphx__"; struct gathernd_compiler : compiler { std::vector names() const { return {"gathernd"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; const auto& out_s = inputs.back(); options.set_launch_params(v, compute_global_for(ctx, out_s.elements())); options.inputs = inputs; options.output = out_s; options.kernel_name = "gathernd_kernel"; options.virtual_inputs = inputs; // batch_dims assert(v.contains("batch_dims")); auto batch_dims = v.at("batch_dims").to(); options.emplace_param("-DBATCH_DIMS=" + std::to_string(batch_dims)); return compile_hip_code_object(ctx, gathernd_kernel, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/gqa_rotary_embedding.cpp000066400000000000000000000075251510465702400252440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT // NOLINTNEXTLINE static const char* const gqa_rotary_embedding_kernel = R"__migraphx__( #include #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors(), rotate_last())(${args})([](auto... xs) { gqa_rotary_embedding(xs..., make_gqa_parameters(${gqa_params})); }); } } } // namespace migraphx )__migraphx__"; struct gqa_rotary_embedding_compiler : compiler { std::vector names() const { return {"gqa_rotary_embedding", "gpu::gqa_rotary_embedding"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { auto params = init_params(inputs, v); auto gqa_params_str = params.make_init_str(); hip_compile_options options; options.set_launch_params(v, compute_global_for(ctx, inputs.back().elements())); options.inputs = inputs; options.output = inputs.back(); options.kernel_name = v.get("kernel", "gqa_rotary_embedding_kernel"); auto src = interpolate_string(gqa_rotary_embedding_kernel, {{"params", enum_params(inputs.size(), "void * private_p")}, {"args", enum_params(inputs.size(), "private_p")}, {"gqa_params", gqa_params_str}, {"kernel", options.kernel_name}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { auto shapes = to_shapes(ins->inputs()); auto v = op.to_value(); return compile_op(ctx, shapes, v); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/layernorm.cpp000066400000000000000000000117701510465702400231030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT static const char* const layernorm_kernel = R"__migraphx__( #include #include #include #include #include namespace migraphx { ${preamble} extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors(), rotate_last(), ${transformers})(${args})([](auto... xs) { ${layernorm}<${axis}>(${post}, ${eps}, xs...); }); } } } // namespace migraphx )__migraphx__"; struct layernorm_compiler : compiler { std::vector names() const { return {"layernorm", "gpu::prelayernorm", "gpu::preadd_layernorm"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { // TODO: Use reduce_dims auto axis = inputs.front().lens().size() - 1; auto faxis = find_fast_axis({inputs.front()}); vectorize vec{}; // Vectorize if the axis is a reduction axis if(axis == faxis) { vec = vectorize::elements(ctx, faxis, inputs); } auto relements = inputs[0].lens()[axis] / vec.size; auto nelements = (inputs.back().elements() / inputs[0].lens()[axis]); auto block_size = compute_block_size(ctx, relements, 256); hip_compile_options options; options.set_launch_params( v, compute_global_for(ctx, nelements * block_size, 256), block_size); options.output = inputs.back(); options.inputs = inputs; options.kernel_name = v.get("kernel", "layernorm_kernel"); auto eps = v.get("epsilon", 1e-12f); auto src = interpolate_string(layernorm_kernel, {{"kernel", options.kernel_name}, {"params", enum_params(inputs.size(), "void * private_p")}, {"args", enum_params(inputs.size(), "private_p")}, {"transformers", make_transformer_args(vec)}, {"post", v.get("post", std::string{"op::id{}"})}, {"preamble", v.get("preamble", std::string{})}, {"layernorm", v.get("layernorm", std::string{"layernorm"})}, {"axis", to_string(axis)}, {"eps", to_string(eps)}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { auto v = op.to_value(); v["layernorm"] = "layernorm"; v["kernel"] = "layernorm_kernel"; if(op.name() == "gpu::preadd_layernorm") { v["layernorm"] = "add_layernorm"; v["kernel"] = "add_layernorm_kernel"; } if(not ins->module_inputs().empty()) { auto* pm = ins->module_inputs().front(); v["preamble"] = generate_pointwise(*pm, "post_layernorm"); v["post"] = "MIGRAPHX_LIFT(post_layernorm)"; v["kernel"] = v["layernorm"].to() + "_" + generate_name_from_ops(*pm) + "_kernel"; } return compile_op(ctx, to_shapes(ins->inputs()), v); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/mlir.cpp000066400000000000000000000357111510465702400220370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_DUMP_TO_MXR); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_DUMP); static module create_pointwise_module(module_ref in_mod) { module pw_mod; std::unordered_map map_ins; for(auto param : in_mod->get_parameters()) { map_ins[param] = pw_mod.add_parameter(any_cast(param->get_operator()).parameter, shape{param->get_shape().type()}); } auto return_args = pw_mod.add_instructions( in_mod, &map_ins, [](module& m, instruction_ref ins, const operation& op, const std::vector& inputs, const std::vector& mod_args) -> instruction_ref { if(op.name() == "multibroadcast" and inputs.front()->name() == "@literal") return inputs.front(); else return m.insert_instruction(ins, op, inputs, mod_args); }); pw_mod.add_return(return_args); return pw_mod; } struct mlir_compiler : compiler { std::vector names() const { return {"gpu::mlir_op"}; } operation compile_op(context&, const std::vector&, const value&) const { return {}; } std::optional input_is_param(const instruction_ref& ins) const { auto cur = instruction::get_output_alias(ins); while(contains({"reshape", "contiguous"}, cur->name())) { cur = instruction::get_output_alias(cur->inputs().at(0)); } if(cur->name() == "@param") { return cur; } return nullopt; } bool is_range_literal(const instruction_ref& ins) const { auto lit_elms = ins->get_shape().element_space(); if(not ins->can_eval() or lit_elms < 2) { return false; } bool is_range = false; ins->eval().visit([&](auto l) { is_range = std::adjacent_find(l.begin(), l.begin() + lit_elms, [](auto cur, auto next) { return not float_equal(next - cur, 1.0); }) == l.begin() + lit_elms; }); return is_range; } void set_fill_map(compiler_replace& cr, const module& m) const { for(auto ins : iterator_for(m)) { if(ins->name() != "greater") { continue; } auto fill_val = ins->get_shape().lens().back() - 1; bool has_range_lit = std::any_of(ins->inputs().begin(), ins->inputs().end(), [&](auto inp) { return is_range_literal(inp); }); for(auto inp : ins->inputs()) { auto param = input_is_param(inp); if(param.has_value() and has_range_lit) { auto id = param.value()->get_shape().type_string() + migraphx::shape::to_sizes_string({param.value()->get_shape()}); cr.fill_map[id] = static_cast(fill_val); } } } } compiler_replace compile(context& ctx, instruction_ref ins, const operation&, const value& solution) const { auto* smod = ins->module_inputs().front(); assert(smod->get_parameter_names().size() == ins->inputs().size() - 1); auto gemm_like_ins = std::find_if(smod->begin(), smod->end(), [&](const auto& i) { return contains({"dot", "quant_dot", "convolution", "quant_convolution"}, i.name()); }); auto pointwise_ins = std::find_if(gemm_like_ins, smod->end(), [&](const auto& i) { return i.get_operator().attributes().get("pointwise", false) == true; }); // check if (a) module is fused (b) contains a "gemm/conv" instruction and (c) // perfConfig can not allow fused module if(gemm_like_ins != smod->end() and pointwise_ins != smod->end() and not is_module_fusible(*smod, ctx, solution)) { auto input_args = ins->inputs(); // remove alloc buffer input_args.pop_back(); auto split_ins = std::prev(pointwise_ins); std::array mod_splits; mod_splits = smod->split(input_args, {split_ins}); auto dot_mlir_inputs = to_shapes(mod_splits[0].inputs); // add alloc for the gemm output dot_mlir_inputs.push_back(mod_splits[0].mod.get_output_shapes().front()); mlir_code_object cop1 = compile_mlir(ctx, mod_splits[0].mod, dot_mlir_inputs, solution); auto pw_shapes = to_shapes(mod_splits[1].inputs); if(mod_splits[1].mod.get_output_shapes().size() == 1) { pw_shapes.push_back(mod_splits[1].mod.get_output_shapes().front()); } else { pw_shapes.push_back(shape{mod_splits[1].mod.get_output_shapes()}); } assert(pw_shapes.back() == ins->get_shape()); auto pw_mod = create_pointwise_module(&mod_splits[1].mod); auto cop2 = compile_pointwise(ctx, pw_shapes, &pw_mod); std::vector cops = {cop1, mlir_code_object{any_cast(cop2)}}; return insert(cops, mod_splits, ins, split_ins); } auto cr = insert(compile_mlir(ctx, *smod, to_shapes(ins->inputs()), solution)); set_fill_map(cr, *smod); return cr; } compiler_replace insert(const mlir_code_object& mco) const { return {std::vector{mco.cop}, [=](module& m, instruction_ref ins, const std::vector& ops) { std::vector inputs = ins->inputs(); // Tuple inputs not supported assert(std::all_of(inputs.begin(), inputs.end() - 1, [](auto i) { return i->get_shape().sub_shapes().empty(); })); // Multiple output case (allocate ins will give a tuple) std::vector flat_inputs(inputs); bool multi_out = not flat_inputs.back()->get_shape().sub_shapes().empty(); if(multi_out) { auto allocs = flat_inputs.back(); flat_inputs.pop_back(); auto sub_shape_idx = range(allocs->get_shape().sub_shapes().size()); std::transform(sub_shape_idx.begin(), sub_shape_idx.end(), std::back_inserter(flat_inputs), [&](int i) { return m.insert_instruction( ins, migraphx::make_op("get_tuple_elem", {{"index", i}}), allocs); }); } std::vector tuple_replacements; for(const auto i : range(mco.prefill_indices.size())) { auto prefilled_ins = m.insert_instruction( ins, migraphx::make_op("hip::fill", {{"value", mco.prefill_values[i]}}), flat_inputs[mco.prefill_indices[i]]); if(not multi_out or mco.prefill_indices[i] < inputs.size() - 1) { replace(inputs, inputs[mco.prefill_indices[i]], prefilled_ins); } else { tuple_replacements.push_back(prefilled_ins); } } if(multi_out and not tuple_replacements.empty()) { // Add identity to make sure fill operations happen before kernel call tuple_replacements.insert(tuple_replacements.begin(), inputs.back()); inputs.back() = m.insert_instruction( ins, migraphx::make_op("identity"), tuple_replacements); } auto mlir = insert_mlir(m, ins, any_cast(ops.front()), inputs); return m.replace_instruction(ins, mlir); }, &trace}; } compiler_replace insert(const std::vector& mcos, const std::array& mods, instruction_ref precompile_ins, instruction_ref split_ins) const { std::vector cobjs(mcos.size()); std::transform( mcos.begin(), mcos.end(), cobjs.begin(), [](const auto& mco) { return mco.cop; }); auto precompiled_inputs = precompile_ins->inputs(); return { cobjs, [=](module& m, instruction_ref ins, const std::vector& ops) { auto compiled_inputs = ins->inputs(); std::unordered_map inputs_rep_map; for(const auto i : range(precompiled_inputs.size())) { inputs_rep_map[precompiled_inputs[i]] = compiled_inputs[i]; } auto dot_inputs = mods[0].inputs; auto dot_mod_out_shape = mods[0].mod.get_output_shapes().front(); auto dot_alloc = m.insert_instruction( ins, migraphx::make_op("hip::allocate", {{"shape", to_value(dot_mod_out_shape)}})); dot_inputs.push_back(dot_alloc); for(const auto i : range(mcos[0].prefill_indices.size())) { auto prefilled_ins = m.insert_instruction( ins, migraphx::make_op("hip::fill", {{"value", mcos[0].prefill_values[i]}}), dot_inputs[mcos[0].prefill_indices[i]]); replace(dot_inputs, dot_inputs[mcos[0].prefill_indices[i]], prefilled_ins); } std::vector dot_inputs_updated; std::transform(dot_inputs.begin(), dot_inputs.end(), std::back_inserter(dot_inputs_updated), [&](const auto& i) { if(inputs_rep_map.find(i) != inputs_rep_map.end()) { assert(inputs_rep_map.at(i)->get_shape() == i->get_shape()); return inputs_rep_map.at(i); } return i; }); auto mlir_ins = insert_mlir(m, ins, any_cast(ops[0]), dot_inputs_updated); auto pwm = mods[1]; pwm.replace(split_ins, mlir_ins); auto pw_inputs = pwm.inputs; pw_inputs.push_back(ins->inputs().back()); std::vector pw_inputs_updated; std::transform(pw_inputs.begin(), pw_inputs.end(), std::back_inserter(pw_inputs_updated), [&](const auto& i) { if(inputs_rep_map.find(i) != inputs_rep_map.end()) { assert(inputs_rep_map.at(i)->get_shape() == i->get_shape()); return inputs_rep_map.at(i); } return i; }); auto pw_ins = insert_mlir(m, ins, any_cast(ops[1]), pw_inputs_updated); return m.replace_instruction(ins, pw_ins); }}; } optional get_tuning_config(const context& ctx, instruction_ref ins, const operation&, bool exhaustive) const { static const auto mxr_loc = string_value_of(MIGRAPHX_MLIR_DUMP_TO_MXR{}); static const auto mlir_loc = string_value_of(MIGRAPHX_MLIR_DUMP{}); auto shapes = to_shapes(ins->inputs()); auto* smod = ins->module_inputs().front(); if(not mxr_loc.empty()) { dump_mlir_to_mxr(*smod, ins->inputs(), mxr_loc); } if(not mlir_loc.empty()) { dump_mlir_to_file(*smod, shapes, mlir_loc); } return get_tuning_config_mlir(ctx, *smod, shapes, exhaustive); } static void trace(std::ostream& os, instruction_ref ins) { auto shapes = to_shapes(ins->inputs()); auto* smod = ins->module_inputs().front(); os << dump_mlir(*smod, shapes); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/pack_fp4.cpp000066400000000000000000000062611510465702400225610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/instruction.hpp" #include "migraphx/instruction_ref.hpp" #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { static const char* const pack_fp4_kernel = R"__migraphx__( #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors())(${args})([](auto... xs) { pack_fp4<${axis}>(xs...); }); } } } // namespace migraphx )__migraphx__"; struct pack_fp4_compiler : compiler { std::vector names() const { return {"pack_fp4"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; options.inputs = inputs; options.output = inputs.back(); options.virtual_inputs = reduce_dims(normalize_permutation(options.inputs)); options.kernel_name = "pack_fp4_kernel"; options.set_launch_params(v, compute_global_for(ctx, inputs.back().elements())); auto src = interpolate_string(pack_fp4_kernel, {{"kernel", options.kernel_name}, {"params", enum_params(options.inputs.size(), "void * private_p")}, {"args", enum_params(options.inputs.size(), "private_p")}, {"axis", std::to_string(v.at("axis").to())}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/pad.cpp000066400000000000000000000110241510465702400216270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT static const char* const pointwise_kernel = R"__migraphx__( #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void pad_kernel(void* input_p, void* output_p) { auto offsets = index_ints<${offsets}>{}; auto idx = make_index(); make_tensors()(input_p, output_p)([&](auto input, auto output) { pad(idx, offsets, input, output, ${pad_val}); }); } } } // namespace migraphx )__migraphx__"; struct pad_compiler : compiler { std::vector names() const { return {"pad"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { auto padding = v.at("pads").to_vector(); auto input_lens = inputs.front().lens(); std::vector offsets(input_lens.size()); std::copy(padding.begin(), padding.begin() + offsets.size(), offsets.begin()); auto offset_lens = input_lens; std::transform(input_lens.begin(), input_lens.end(), offsets.begin(), offset_lens.begin(), [&](auto input, auto offset) { return input + offset; }); auto vinputs = inputs; vinputs.push_back(inputs.front().with_lens(offset_lens)); auto rinputs = reduce_dims(normalize_permutation(vinputs)); auto rinput_lens = rinputs.front().lens(); auto roffset_lens = rinputs.back().lens(); std::vector roffsets(roffset_lens.size()); std::transform(rinput_lens.begin(), rinput_lens.end(), roffset_lens.begin(), roffsets.begin(), [](auto input, auto offset_dim) { return offset_dim - input; }); rinputs.pop_back(); hip_compile_options options; options.inputs = inputs; options.output = inputs.back(); options.virtual_inputs = rinputs; options.kernel_name = "pad_kernel"; options.set_launch_params(v, compute_global_for(ctx, inputs.at(1).elements())); auto pad_val = v.get("value", 0.f); auto pad_val_string = to_string(pad_val); if(float_equal(pad_val, std::numeric_limits::lowest())) pad_val_string = "lowest{}"; if(float_equal(pad_val, std::numeric_limits::max())) pad_val_string = "highest{}"; auto src = interpolate_string( pointwise_kernel, {{"pad_val", to_string(pad_val_string)}, {"offsets", to_string_range(roffsets)}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/pointwise.cpp000066400000000000000000000114201510465702400231040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT static const char* const pointwise_kernel = R"__migraphx__( #include #include #include namespace migraphx { ${preamble} extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { auto idx = make_index(); pointwise<${noutputs}, ${tiled}>(idx, ${transformers})(${lambda}, ${args}); } } } // namespace migraphx )__migraphx__"; struct pointwise_compiler : compiler { std::vector names() const { return {"pointwise", "contiguous", "layout"}; } static std::size_t oversubscribe_if(bool b) { if(b) return 256; else return 1; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; options.inputs = flatten(inputs); options.output = inputs.back(); options.virtual_inputs = reduce_dims(normalize_permutation(options.inputs)); options.emplace_param("-Wno-float-equal"); auto axis = find_fast_axis(options.virtual_inputs); auto vec = vectorize::elements(ctx, axis, options.virtual_inputs); options.kernel_name = v.get("kernel", "kernel"); auto noutputs = options.inputs.size() - inputs.size() + 1; auto t = tile::elements(options.virtual_inputs, noutputs); // auto t = tile{}; if(t.ntiles == 0) options.set_launch_params( v, compute_global_for(ctx, options.inputs.front().elements() / vec.size, 256)); else options.set_launch_params( v, compute_global_for(ctx, t.ntiles * t.block_size, 256), t.block_size); auto src = interpolate_string(pointwise_kernel, {{"kernel", options.kernel_name}, {"params", enum_params(options.inputs.size(), "void * private_p")}, {"args", enum_params(options.inputs.size(), "private_p")}, {"lambda", v.at("lambda").to()}, {"transformers", make_transformer_args(t, vec)}, {"tiled", t.ntiles > 0 ? "true" : "false"}, {"noutputs", std::to_string(noutputs)}, {"preamble", v.get("preamble", std::string{})}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { if(contains({"layout", "contiguous"}, op.name())) { return compile_op(ctx, to_shapes(ins->inputs()), {{"lambda", "[](auto x) { return make_tuple(x); }"}, {"kernel", op.name() + "_kernel"}}); } else { assert(not ins->module_inputs().empty()); const_module_ref pm = ins->module_inputs().front(); return compile_pointwise(ctx, to_shapes(ins->inputs()), pm); } } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/pooling.cpp000066400000000000000000000157271510465702400225500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { // NOLINTNEXTLINE static const char* const pooling_kernel = R"__migraphx__( #include #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void pooling_kernel(void* in_data, void* output) { transform_args(make_tensors(), rotate_last())(in_data, output)([](auto&&... xs) { pooling<${algo}, ${group_size}>(${op}, make_window(index_ints<${window}>{}, index_ints<${stride}>{}, index_ints<${padding}>{}), xs...); }); } } } // namespace migraphx )__migraphx__"; struct pooling_compiler : compiler { static std::size_t compute_subwave_size(context& ctx, std::size_t n) { std::size_t max_wavefront_size = ctx.get_current_device().get_wavefront_size(); std::size_t wavefront_size = 1; while(wavefront_size <= n and wavefront_size < max_wavefront_size) wavefront_size *= 2; return wavefront_size / 2; } struct algorithm { std::string name = "reduce::lane"; std::size_t reduce_size = 1; std::size_t block_size = 256; std::size_t group_size = 1; static std::size_t compute_group_size(const shape& output) { auto n = output.lens().back(); const std::size_t max_group_size = 32; std::size_t group_size = 1; while((n % (group_size * 2) == 0) and group_size <= max_group_size) group_size *= 2; return group_size; } algorithm() {} algorithm(context& ctx, const shape& input, const std::vector& window) { if(input.strides().back() != 1) return; std::size_t max_wavefront_size = ctx.get_current_device().get_wavefront_size(); auto wsize = window.back(); if(wsize > max_wavefront_size) { block_size = compute_block_size(ctx, wsize, 256); reduce_size = block_size; name = "reduce::block"; } else { block_size = max_wavefront_size; reduce_size = compute_subwave_size(ctx, wsize); name = "reduce::subwave<" + to_string(reduce_size) + ">"; } } }; template static void normalize(std::vector& inputs, Ts&... xs) { auto perm = find_permutation(inputs); std::transform(inputs.begin(), inputs.end(), inputs.begin(), [&](auto s) { return reorder_shape(s, perm); }); each_args([&](auto& dims) { dims = reorder_dims(dims, perm); }, xs...); } std::vector names() const { return {"pooling"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; const auto& out_s = inputs.back(); options.inputs = inputs; options.output = out_s; options.kernel_name = "pooling_kernel"; options.virtual_inputs = inputs; auto ndim = out_s.ndim(); auto pool_ndim = ndim - 2; auto read_value = [&](const std::string& name, std::size_t def) { if(v.contains(name)) { std::vector result(2, def); auto x = v.at(name).to_vector(); if(x.size() >= pool_ndim) result.insert(result.end(), x.begin(), x.begin() + pool_ndim); return result; } else { std::vector result(ndim, def); return result; } }; auto padding = read_value("padding", 0); auto stride = read_value("stride", 1); auto window = read_value("lengths", 1); const auto& mode_v = v.at("mode"); std::string mode = mode_v.is_string() ? mode_v.get_string() : to_string(mode_v.to()); bool count_include_pad = v.get("count_include_pad", false); if(count_include_pad and mode == "average") mode = "average_include_pad"; std::string op = mode + "_pool"; if(mode == "lpnorm") op += "<" + v.at("lp_order").to() + ">"; algorithm algo{}; options.set_launch_params( v, compute_global_for(ctx, (out_s.elements() / algo.group_size) * algo.reduce_size, 256), algo.block_size); normalize(options.virtual_inputs, padding, stride, window); auto src = interpolate_string(pooling_kernel, {{"op", op + "{}"}, {"algo", algo.name}, {"group_size", to_string(algo.group_size)}, {"window", to_string_range(window)}, {"stride", to_string_range(stride)}, {"padding", to_string_range(padding)}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/reduce.cpp000066400000000000000000000427361510465702400223500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT static const char* const simple_reduce_kernel = R"__migraphx__( #include #include #include #include namespace migraphx { ${preamble} extern "C" { MIGRAPHX_GLOBAL void reduce_kernel(void* input_p, void* output_p) { transform_args(make_tensors(), ${transformers})(input_p, output_p)([](auto input, auto output) { simple_reduce(${reduction}, ${init}, input, output, ${read}, ${write}); }); } } } // namespace migraphx )__migraphx__"; static std::vector get_reduce_lens(const std::vector& input_lens, const std::vector& output_lens) { std::vector reduce_lens; std::transform(output_lens.begin(), output_lens.end(), input_lens.begin(), std::back_inserter(reduce_lens), [](auto x, auto y) -> std::size_t { if(x == y) return 1; else return y; }); return reduce_lens; } static shape get_input_shape(const std::vector& inputs) { auto it = std::max_element(inputs.begin(), inputs.end(), by(std::less<>{}, [](const shape& s) { return s.elements(); })); return *it; } template static shape get_reduced_shape(const shape& s, const std::vector& axes) { auto lens = s.lens(); std::fill(lens.begin(), lens.end(), 1); for(const auto& axis : axes) lens[axis] = s.lens()[axis]; return s.with_lens(lens); } template static shape get_output_shape(const shape& s, const std::vector& axes) { auto lens = s.lens(); for(const auto& axis : axes) lens[axis] = 1; return s.with_lens(lens); } template static std::string get_reduce_algo(context& ctx, const std::vector& inputs, ReduceLens rlens) { const auto init = std::numeric_limits::max(); auto relements = std::accumulate(rlens.begin(), rlens.end(), 1, std::multiplies<>{}); bool is_strided_reduce = std::all_of(inputs.begin(), inputs.end(), [&](const shape& input) { // The minimum stride auto min_stride = std::inner_product( rlens.begin(), rlens.end(), input.strides().begin(), init, [](auto x, auto y) { return std::min(x, y); }, [](auto len, auto stride) { return len == 1 ? init : stride; }); return min_stride > 2; }); if(is_strided_reduce) return "lane"; if(relements <= ctx.get_current_device().get_wavefront_size()) return "wave"; return "block"; } static std::string get_reduce_algo(context& ctx, const std::vector& inputs) { auto rlens = get_reduce_lens(inputs.front().lens(), inputs.back().lens()); return get_reduce_algo(ctx, inputs, rlens); } static std::size_t compute_subwave_size(context& ctx, std::size_t n) { std::size_t max_wavefront_size = ctx.get_current_device().get_wavefront_size(); std::size_t wavefront_size = 1; while(wavefront_size <= n and wavefront_size < max_wavefront_size) wavefront_size *= 2; return wavefront_size; } /// This will adjust the input shapes so a partial reduction is done per workgroup. /// This is done by splitting the reduction axis so each split group becomes /// part of the batch. So if we want to do a split redution of a tensor /// {K}, then this will create a tensor of {K/N, N} where N is the number of /// split groups. To compute the number of split groups it finds the largest /// divisor that can divide K to make it less than min_size. static std::vector split_reduce(const std::vector& inputs, std::size_t min_size = 1024) { std::vector result; auto input_shape = inputs.front(); const auto& reduce_shape = inputs[inputs.size() - 2]; const auto& output_shape = inputs[inputs.size() - 1]; auto is = range(reduce_shape.lens().size()); using array_type = std::array; auto initial = array_type{std::numeric_limits::max(), std::numeric_limits::max()}; auto faxis = transform_accumulate( is.begin(), is.end(), initial, MIGRAPHX_LIFT(std::min), [&](auto i) -> array_type { if(input_shape.lens()[i] == output_shape.lens()[i]) return initial; return {input_shape.strides()[i], std::size_t(i)}; })[1]; assert(faxis < reduce_shape.lens().size()); std::size_t n = 1; auto r = input_shape.lens()[faxis]; auto factors = make_array(2, 3, 5, 7, 11); while(r > min_size) { // NOLINTNEXTLINE(readability-qualified-auto) auto it = std::find_if(factors.begin(), factors.end(), [&](auto d) { return r % d == 0; }); if(it == factors.end()) break; r /= *it; n *= *it; } assert(n != 1); std::transform( inputs.begin(), inputs.end(), std::back_inserter(result), [&](const shape& s) -> shape { auto lens = s.lens(); auto strides = s.strides(); lens.push_back(n); if(lens[faxis] == 1) { strides.push_back(0); } else { lens[faxis] /= n; strides.push_back(strides[faxis] * lens[faxis]); } return {s.type(), lens, strides}; }); return reduce_dims(normalize_permutation(result)); } struct simple_reduce_compiler : compiler { std::vector names() const { return {"simple_reduce", "reduce_sum", "reduce_mean", "reduce_max", "reduce_min", "reduce_prod", "reduce_any", "reduce_all"}; } static std::size_t get_reduce_elements(const std::vector& inputs) { return inputs.front().elements() / inputs.back().elements(); } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; options.inputs = inputs; options.output = inputs.back(); options.virtual_inputs = reduce_dims(inputs); auto faxis = find_fast_axis({options.virtual_inputs.front()}); vectorize vec{}; auto nelements = options.virtual_inputs.back().elements(); auto algo = v.get("algo", get_reduce_algo(ctx, options.virtual_inputs)); if(algo == "block" or algo == "wave") { // Vectorize if the axis is a reduction axis if(options.virtual_inputs.back().lens()[faxis] == 1) vec = vectorize::elements(ctx, faxis, options.virtual_inputs); auto relements = get_reduce_elements(options.virtual_inputs) / vec.size; if(algo == "block") { auto block_size = compute_block_size(ctx, relements, 256); if(relements >= block_size * 256) algo = "block_large"; options.set_launch_params( v, compute_global_for(ctx, nelements * block_size, 256), block_size); } else { auto subwave_size = compute_subwave_size(ctx, relements); algo = "subwave<" + std::to_string(subwave_size) + ">"; options.set_launch_params(v, compute_global_for(ctx, nelements * subwave_size, 256), ctx.get_current_device().get_wavefront_size()); } } else if(algo == "lane") { options.set_launch_params(v, compute_global_for(ctx, nelements, 256)); } else { MIGRAPHX_THROW("Unknown reduce algo: " + algo); } options.kernel_name = "reduce_kernel"; std::string identity = "[](auto x) { return x; }"; auto src = interpolate_string(simple_reduce_kernel, {{"reduction", v.at("reduction").to()}, {"init", v.get("init", std::string{"0"})}, {"read", v.get("read", identity)}, {"write", v.get("write", identity)}, {"algo", algo}, {"transformers", make_transformer_args(vec)}, {"preamble", v.get("preamble", std::string{})}}); options.emplace_param("-Wno-float-equal"); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { value v = value::object{}; reduce_op r{}; r.set(ins, op); v["reduction"] = r.reduction; v["read"] = r.read; v["write"] = r.write; v["init"] = r.init; return compile_op(ctx, to_shapes(ins->inputs()), v); } }; static const char* const fused_reduce_kernel = R"__migraphx__( #include #include #include #include #include namespace migraphx { ${preamble} extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors(), ${transformers}, rotate_and_pack_last<${noutputs}>())(${args})([](auto y, auto... xs) { fused_reduce(y, ${assign}{}, partial(${lambda})(xs...)); }); } } } // namespace migraphx )__migraphx__"; struct fused_reduce_compiler : compiler { std::vector names() const { return {"fused_reduce", "split_fused_reduce"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { auto assign = v.get("assign", "assign_none"); auto axes = v.at("axes").to_vector(); auto finputs = flatten(inputs); auto noutputs = finputs.size() - inputs.size() + 1; auto virtual_inputs = finputs; virtual_inputs.push_back(get_reduced_shape(get_input_shape(finputs), axes)); virtual_inputs.push_back(get_output_shape(get_input_shape(finputs), axes)); virtual_inputs = reduce_dims(normalize_permutation(virtual_inputs)); if(assign != "assign_none") virtual_inputs = split_reduce(virtual_inputs); auto reduce_output_shape = virtual_inputs.back(); virtual_inputs.pop_back(); auto reduction_shape = virtual_inputs.back(); virtual_inputs.pop_back(); hip_compile_options options; options.inputs = finputs; options.output = inputs.back(); options.virtual_inputs = virtual_inputs; auto faxis = find_fast_axis({options.virtual_inputs.front()}); vectorize vec{}; auto nelements = reduce_output_shape.elements(); auto algo = v.get("algo", get_reduce_algo(ctx, options.virtual_inputs, reduction_shape.lens())); if(algo == "block" or algo == "wave") { // Vectorize if the axis is a reduction axis if(reduce_output_shape.lens()[faxis] == 1) vec = vectorize::elements(ctx, faxis, options.virtual_inputs); auto relements = reduction_shape.elements() / vec.size; if(algo == "block") { auto block_size = v.get("block_size", compute_block_size(ctx, relements, 256)); if(relements >= block_size * 256) algo = "block_large"; options.set_launch_params( v, compute_global_for(ctx, nelements * block_size, 256), block_size); } else { auto subwave_size = v.get("subwave_size", compute_subwave_size(ctx, relements)); algo = "subwave<" + std::to_string(subwave_size) + ">"; options.set_launch_params(v, compute_global_for(ctx, nelements * subwave_size, 256), ctx.get_current_device().get_wavefront_size()); } } else if(algo == "lane") { options.set_launch_params(v, compute_global_for(ctx, nelements, 256)); } else { MIGRAPHX_THROW("Unknown reduce algo: " + algo); } options.kernel_name = v.get("kernel", "reduce_kernel"); auto src = interpolate_string( fused_reduce_kernel, {{"kernel", options.kernel_name}, {"params", enum_params(finputs.size(), "void * private_p")}, {"args", enum_params(finputs.size(), "private_p")}, {"assign", assign}, {"algo", algo}, {"reduced", "decltype(" + generate_make_shape(reduce_output_shape) + ")"}, {"lambda", v.at("lambda").to()}, {"transformers", make_transformer_args(vec)}, {"noutputs", std::to_string(noutputs)}, {"preamble", v.get("preamble", std::string{})}}); options.emplace_param("-Wno-float-equal"); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op, const value& solution) const { assert(not ins->module_inputs().empty()); auto v = op.to_value(); for(const auto& x : solution) v.insert(x); auto* rm = ins->module_inputs().front(); v["preamble"] = generate_reduce(*rm, "fused_reduce_op"); v["lambda"] = "MIGRAPHX_LIFT(fused_reduce_op)"; v["kernel"] = generate_name_from_ops(*rm) + "_kernel"; return compile_op(ctx, to_shapes(ins->inputs()), v); } optional get_tuning_config(const context& ctx, instruction_ref ins, const operation& op, bool exhaustive) const { if(not exhaustive) return nullopt; if(op.name() != "fused_reduce") return nullopt; tuning_config tc; auto shapes = to_shapes(ins->inputs()); tc.problem = to_value(shapes); auto axes = op.to_value().at("axes").to_vector(); auto input_shape = get_input_shape(shapes); auto reduce_shape = get_reduced_shape(input_shape, axes); auto relements = reduce_shape.elements(); std::unordered_set tile_sizes; for(auto per_lane : {1, 2, 4, 8, 16}) { std::size_t x = relements / per_lane; for(auto max_block : {256, 512, 1024}) tile_sizes.insert(compute_block_size(ctx, x, max_block)); if(x < ctx.get_current_device().get_wavefront_size()) tile_sizes.insert(bit_ceil(x)); } for(auto tile_size : tile_sizes) { if(tile_size > ctx.get_current_device().get_wavefront_size()) tc.solutions.push_back({{"algo", "block"}, {"block_size", tile_size}}); else tc.solutions.push_back({{"algo", "wave"}, {"subwave_size", tile_size}}); } tc.solutions.push_back({{"algo", "lane"}}); return tc; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/roialign.cpp000066400000000000000000000075571510465702400227070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #if !MIGRAPHX_USE_MIOPEN #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { // NOLINTNEXTLINE static const char* const roialign_kernel = R"__migraphx__( #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void roialign_kernel(void* in_x, void* in_rois, void* in_ind, void* y) { make_tensors()(in_x, in_rois, in_ind, y)([](auto&&... xs) { auto settings = make_roalign_settings(MIGRAPHX_MAKE_CONSTANT(float{ROIS_OFFSET}), _c, _c, MIGRAPHX_MAKE_CONSTANT(float{SPATIAL_SCALE})); roialign(xs..., settings); }); } } } // namespace migraphx )__migraphx__"; struct roialign_compiler : compiler { std::vector names() const { return {"roialign"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; options.set_launch_params(v, compute_global_for(ctx, inputs.back().elements()), 128); options.output = inputs.back(); options.inputs = inputs; options.kernel_name = "roialign_kernel"; // sampling_ratio options.emplace_param("-DSAMPLING_RATIO=" + v.at("sampling_ratio").to()); // pooling_mode auto mode = v.at("mode").to(); std::string is_avg_pooling = (mode == migraphx::op::pooling_mode::average) ? "true" : "false"; options.emplace_param("-DIS_AVG_POOLING=" + is_avg_pooling); // coord_trans_mode auto ctm = v.at("coordinate_transformation_mode").to(); float rois_offset = (ctm == "half_pixel") ? -0.5f : 0.0f; options.emplace_param("-DROIS_OFFSET=" + std::to_string(rois_offset)); // spatial_scale options.emplace_param("-DSPATIAL_SCALE=" + v.at("spatial_scale").to()); return compile_hip_code_object(ctx, roialign_kernel, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/scatter.cpp000066400000000000000000000055021510465702400225340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "scatter.hpp" namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { // NOLINTNEXTLINE static const char* const scatter_elements_kernel = R"__migraphx__( #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void scatter_elements_kernel(void* in_indices, void* in_updates, void* output) { make_tensors()(in_indices, in_updates, output)([](auto&&... xs) { scatter<${axis}, ${skip_out_of_bounds}>(xs..., ${reduction}{}); }); } } } // namespace migraphx )__migraphx__"; struct scatter_elements_compiler : scatter_compiler { std::vector names() const { return {"scatter_none", "scatter_add", "scatter_mul", "scatter_min", "scatter_max"}; } std::string make_interpolated_string(const operation& op) const { const auto reduction = op.name().substr(std::char_traits::length("scatter_")); auto axis = std::to_string(op.to_value().get("axis", 0)); auto skip_out_of_bounds = std::to_string(op.to_value().get("skip_out_of_bounds", 0)); return interpolate_string(scatter_elements_kernel, {{"reduction", "assign_" + reduction}, {"axis", axis}, {"skip_out_of_bounds", skip_out_of_bounds}}); } std::string get_kernel_name(const operation&) const { return "scatter_elements_kernel"; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/scatter.hpp000066400000000000000000000067461510465702400225540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_JIT_SCATTER_HPP #define MIGRAPHX_GUARD_JIT_SCATTER_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { template struct scatter_compiler : compiler { compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { const auto inputs = to_shapes(std::vector{ins->inputs().begin() + 1, ins->inputs().end()}); hip_compile_options options; options.set_launch_params(op.to_value(), compute_global_for(ctx, inputs.at(1).elements())); options.inputs = inputs; options.output = inputs.back(); options.kernel_name = derived().get_kernel_name(op); options.virtual_inputs = inputs; options.emplace_param("-DMIGRAPHX_ALLOW_ATOMIC_CAS=1"); const auto src = derived().make_interpolated_string(op); return prepend_copy_data_to_output(compile_hip_code_object(ctx, src, options)); } // ONNX spec states the following for ScatterElements and ScatterND: // "The output of the operation is produced by creating a copy of the input data, ..." // The sole responsibility of the MIGraphX Scatter operator implementations being to perform the // update operations as specified by ONNX, it is necessary to place the copying of the input // data before the MIGraphX operator in the graph. compiler_replace prepend_copy_data_to_output(const operation& co) const { return {co, [](module& m, instruction_ref ins, const operation& op) { auto args = ins->inputs(); args.back() = m.insert_instruction(ins, make_op("hip::copy"), args.front(), args.back()); args.erase(args.begin()); return m.replace_instruction(ins, op, args); }}; } std::string get_kernel_name(const operation& op) const { return op.name() + "_kernel"; } const Derived& derived() const { return static_cast(*this); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/scatternd.cpp000066400000000000000000000047051510465702400230620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "scatter.hpp" namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { // NOLINTNEXTLINE static const char* const scatternd_kernel = R"__migraphx__( #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void scatternd_kernel(void* in_indices, void* in_updates, void* output) { make_tensors()(in_indices, in_updates, output)([](auto&&... xs) { scatternd(xs..., ${reduction}{}); }); } } } // namespace migraphx )__migraphx__"; struct scatternd_compiler : scatter_compiler { std::vector names() const { return { "scatternd_none", "scatternd_add", "scatternd_mul", "scatternd_min", "scatternd_max"}; } std::string make_interpolated_string(const operation& op) const { const auto reduction = op.name().substr(std::char_traits::length("scatternd_")); return interpolate_string(scatternd_kernel, {{"reduction", "assign_" + reduction}}); } std::string get_kernel_name(const operation&) const { return "scatternd_kernel"; } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/softmax.cpp000066400000000000000000000072401510465702400225510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_USE_FAST_SOFTMAX) using namespace migraphx::gpu::gen; // NOLINT static const char* const softmax_kernel = R"__migraphx__( #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void softmax_kernel(void* input_p, void* output_p) { transform_args(make_tensors(), ${transformers})(input_p, output_p)([](auto input, auto output) { softmax<${axis}>(input, output); }); } } } // namespace migraphx )__migraphx__"; struct softmax_compiler : compiler { std::vector names() const { return {"softmax"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { // TODO: Use reduce_dims auto axis = v.at("axis").to(); auto faxis = find_fast_axis({inputs.front()}); vectorize vec{}; // Vectorize if the axis is a reduction axis if(faxis == axis) { vec = vectorize::elements(ctx, faxis, inputs); } auto relements = inputs[0].lens()[axis] / vec.size; auto nelements = (inputs.back().elements() / inputs[0].lens()[axis]); auto block_size = compute_block_size(ctx, relements, 256); hip_compile_options options; options.set_launch_params( v, compute_global_for(ctx, nelements * block_size, 256), block_size); options.output = inputs.back(); options.inputs = inputs; options.kernel_name = "softmax_kernel"; if(enabled(MIGRAPHX_USE_FAST_SOFTMAX{})) options.emplace_param("-DMIGRAPHX_USE_FAST_SOFTMAX"); auto src = interpolate_string( softmax_kernel, {{"transformers", make_transformer_args(vec)}, {"axis", to_string(axis)}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/topk.cpp000066400000000000000000000076121510465702400220500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #if !MIGRAPHX_USE_MIOPEN #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { // NOLINTNEXTLINE static const char* const topk_kernel = R"__migraphx__( #include #include #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void topk_kernel(${params}) { transform_args(make_tensors(), rotate_last<2>())(${args})([](auto... xs) { topk<${axis}>(${compare}, ${init})(xs...); }); } } } // namespace migraphx )__migraphx__"; struct topk_compiler : compiler { std::vector names() const { return {"topk"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; options.output = inputs.back(); options.inputs = flatten(inputs); options.kernel_name = "topk_kernel"; auto axis = v.at("axis").to(); auto kelements = v.at("k").to(); auto relements = inputs.front().lens()[axis]; auto nelements = inputs.front().elements() / relements; auto max_wavefronts = std::max(1, 8192 / kelements); auto max_block_size = std::min( max_wavefronts * ctx.get_current_device().get_wavefront_size(), 1024); auto block_size = compute_block_size(ctx, relements / 4, max_block_size); options.set_launch_params(v, compute_global_for(ctx, block_size * nelements), block_size); std::string compare = "less{}"; std::string init = "highest{}"; if(v.at("largest").to()) { compare = "greater{}"; init = "lowest{}"; } auto src = interpolate_string(topk_kernel, {{"compare", compare}, {"init", init}, {"params", enum_params(options.inputs.size(), "void * private_p")}, {"args", enum_params(options.inputs.size(), "private_p")}, {"axis", std::to_string(axis)}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/unpack_fp4.cpp000066400000000000000000000063021510465702400231200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/instruction.hpp" #include "migraphx/instruction_ref.hpp" #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { static const char* const unpack_fp4_kernel = R"__migraphx__( #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors())(${args})([](auto... xs) { unpack_fp4<${axis}>(xs...); }); } } } // namespace migraphx )__migraphx__"; struct unpack_fp4_compiler : compiler { std::vector names() const { return {"unpack_fp4"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; options.inputs = inputs; options.output = inputs.back(); options.virtual_inputs = reduce_dims(normalize_permutation(options.inputs)); options.kernel_name = "unpack_fp4_kernel"; options.set_launch_params(v, compute_global_for(ctx, inputs.front().elements())); auto src = interpolate_string(unpack_fp4_kernel, {{"kernel", options.kernel_name}, {"params", enum_params(options.inputs.size(), "void * private_p")}, {"args", enum_params(options.inputs.size(), "private_p")}, {"axis", std::to_string(v.at("axis").to())}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/jit/unpack_int4.cpp000066400000000000000000000064101510465702400233050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/instruction.hpp" #include "migraphx/instruction_ref.hpp" #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { using namespace migraphx::gpu::gen; // NOLINT static const char* const unpack_int4_kernel = R"__migraphx__( #include #include namespace migraphx { extern "C" { MIGRAPHX_GLOBAL void ${kernel}(${params}) { transform_args(make_tensors(), rotate_last())(${args})([](auto... xs) { unpack_int4<${axis}>(xs...); }); } } } // namespace migraphx )__migraphx__"; struct unpack_int4_compiler : compiler { std::vector names() const { return {"unpack_int4"}; } operation compile_op(context& ctx, const std::vector& inputs, const value& v) const { hip_compile_options options; options.inputs = inputs; options.output = inputs.back(); options.virtual_inputs = reduce_dims(normalize_permutation(options.inputs)); options.kernel_name = "unpack_int4_kernel"; options.set_launch_params(v, compute_global_for(ctx, inputs.front().elements())); auto src = interpolate_string(unpack_int4_kernel, {{"kernel", options.kernel_name}, {"params", enum_params(options.inputs.size(), "void * private_p")}, {"args", enum_params(options.inputs.size(), "private_p")}, {"axis", std::to_string(v.at("axis").to())}}); return compile_hip_code_object(ctx, src, options); } compiler_replace compile(context& ctx, instruction_ref ins, const operation& op) const { return compile_op(ctx, to_shapes(ins->inputs()), op.to_value()); } }; } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernel.cpp000066400000000000000000000133431510465702400215630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #ifdef _WIN32 #include #else // extern declare the function since hip/hip_ext.h header is broken extern hipError_t hipExtModuleLaunchKernel(hipFunction_t, // NOLINT uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, size_t, hipStream_t, void**, void**, hipEvent_t = nullptr, hipEvent_t = nullptr, uint32_t = 0); #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { extern std::string hip_error(int error); using hip_module_ptr = MIGRAPHX_MANAGE_PTR(hipModule_t, hipModuleUnload); struct kernel_impl { hip_module_ptr module = nullptr; hipFunction_t fun = nullptr; }; static hip_module_ptr load_module(const char* image) { hipModule_t raw_m; auto status = hipModuleLoadData(&raw_m, image); hip_module_ptr m{raw_m}; if(status != hipSuccess) MIGRAPHX_THROW("Failed to load module: " + hip_error(status)); return m; } kernel::kernel(const char* image, const std::string& name) : impl(std::make_shared()) { impl->module = load_module(image); auto status = hipModuleGetFunction(&impl->fun, impl->module.get(), name.c_str()); if(hipSuccess != status) MIGRAPHX_THROW("Failed to get function: " + name + ": " + hip_error(status)); } static void launch_kernel(hipFunction_t fun, hipStream_t stream, std::size_t global, std::size_t local, void* kernargs, std::size_t size, hipEvent_t start, hipEvent_t stop) { assert(global > 0); assert(local > 0); void* config[] = { // HIP_LAUNCH_PARAM_* are macros that do horrible things #ifdef MIGRAPHX_USE_CLANG_TIDY nullptr, kernargs, nullptr, &size, nullptr #else HIP_LAUNCH_PARAM_BUFFER_POINTER, kernargs, HIP_LAUNCH_PARAM_BUFFER_SIZE, &size, HIP_LAUNCH_PARAM_END #endif }; auto status = hipExtModuleLaunchKernel(fun, global, 1, 1, local, 1, 1, 0, stream, nullptr, reinterpret_cast(&config), start, stop); if(status != hipSuccess) MIGRAPHX_THROW("Failed to launch kernel: " + hip_error(status)); if(stop != nullptr) { status = hipEventSynchronize(stop); if(status != hipSuccess) MIGRAPHX_THROW("Failed to sync event: " + hip_error(status)); } } void kernel::launch(hipStream_t stream, std::size_t global, std::size_t local, pointers args, hipEvent_t start, hipEvent_t stop) const { assert(impl != nullptr); void* kernargs = reinterpret_cast(args.data()); std::size_t size = args.bytes(); launch_kernel(impl->fun, stream, global, local, kernargs, size, start, stop); } void kernel::launch(hipStream_t stream, std::size_t global, std::size_t local, const std::vector& args, hipEvent_t start, hipEvent_t stop) const { assert(impl != nullptr); std::vector kernargs = pack_args(args); std::size_t size = kernargs.size(); launch_kernel(impl->fun, stream, global, local, kernargs.data(), size, start, stop); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/000077500000000000000000000000001510465702400212365ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/000077500000000000000000000000001510465702400226615ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/000077500000000000000000000000001510465702400245005ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/000077500000000000000000000000001510465702400261435ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/algorithm.hpp000066400000000000000000000216011510465702400306420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_ALGORITHM_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_ALGORITHM_HPP #include namespace migraphx { template constexpr void swap(T& a, T& b) noexcept { T old = a; a = b; b = old; } template constexpr void iter_swap(Iterator1 a, Iterator2 b) { if(a == b) return; swap(*a, *b); } struct less { template constexpr auto operator()(T x, U y) const { return x < y; } }; struct greater { template constexpr auto operator()(T x, U y) const { return x > y; } }; template constexpr T accumulate(InputIt first, InputIt last, T init, BinaryOperation op) { for(; first != last; ++first) { init = op(static_cast(init), *first); } return init; } template constexpr OutputIt copy(InputIt first, InputIt last, OutputIt d_first) { while(first != last) { *d_first++ = *first++; } return d_first; } template constexpr OutputIt copy_if(InputIt first, InputIt last, OutputIt d_first, UnaryPredicate pred) { for(; first != last; ++first) { if(pred(*first)) { *d_first = *first; ++d_first; } } return d_first; } template constexpr Iterator is_sorted_until(Iterator first, Iterator last, Compare comp) { if(first != last) { Iterator next = first; while(++next != last) { if(comp(*next, *first)) return next; first = next; } } return last; } template constexpr bool is_sorted(Iterator first, Iterator last, Compare comp) { return is_sorted_until(first, last, comp) == last; } template constexpr F for_each(Iterator first, Iterator last, F f) { for(; first != last; ++first) { f(*first); } return f; } template constexpr Iterator find_if(Iterator first, Iterator last, Predicate p) { for(; first != last; ++first) { if(p(*first)) { return first; } } return last; } template constexpr Iterator find(Iterator first, Iterator last, const T& value) { return find_if(first, last, [&](const auto& x) { return x == value; }); } template constexpr bool any_of(InputIt first, InputIt last, UnaryPredicate p) { return find_if(first, last, p) != last; } template constexpr bool none_of(InputIt first, InputIt last, UnaryPredicate p) { return find_if(first, last, p) == last; } template constexpr bool all_of(InputIt first, InputIt last, UnaryPredicate p) { return none_of(first, last, [=](auto&& x) { return not p(x); }); } template constexpr Iterator1 search(Iterator1 first, Iterator1 last, Iterator2 s_first, Iterator2 s_last) { for(;; ++first) { Iterator1 it = first; for(Iterator2 s_it = s_first;; ++it, ++s_it) { if(s_it == s_last) { return first; } if(it == last) { return last; } if(not(*it == *s_it)) { break; } } } } template constexpr T inner_product(InputIt1 first1, InputIt1 last1, InputIt2 first2, T init, BinaryOperation1 op1, BinaryOperation2 op2) { while(first1 != last1) { init = op1(init, op2(*first1, *first2)); ++first1; ++first2; } return init; } template constexpr T inner_product(InputIt1 first1, InputIt1 last1, InputIt2 first2, T init) { return inner_product( first1, last1, first2, init, [](auto x, auto y) { return x + y; }, [](auto x, auto y) { return x * y; }); } template constexpr bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryPred p) { for(; first1 != last1; ++first1, ++first2) if(not p(*first1, *first2)) { return false; } return true; } template constexpr void iota(Iterator first, Iterator last, T value) { for(; first != last; ++first, ++value) *first = value; } template constexpr Iterator min_element(Iterator first, Iterator last, Compare comp) { if(first == last) return last; Iterator smallest = first; while(++first != last) if(comp(*first, *smallest)) smallest = first; return smallest; } template constexpr Iterator rotate(Iterator first, Iterator middle, Iterator last) { if(first == middle) return last; if(middle == last) return first; Iterator write = first; Iterator next_read = first; for(Iterator read = middle; read != last; ++write, ++read) { if(write == next_read) next_read = read; iter_swap(write, read); } rotate(write, next_read, last); return write; } template constexpr Iterator upper_bound(Iterator first, Iterator last, const T& value, Compare comp) { auto count = last - first; while(count > 0) { // NOLINTNEXTLINE(readability-qualified-auto) auto it = first; auto step = count / 2; it += step; if(not comp(value, *it)) { first = ++it; count -= step + 1; } else count = step; } return first; } template constexpr void sort(Iterator first, Iterator last, Compare comp) { if(first == last) return; for(auto i = first; i != last - 1; ++i) iter_swap(i, min_element(i, last, comp)); MIGRAPHX_ASSERT(is_sorted(first, last, comp)); } template constexpr void sort(Iterator first, Iterator last) { sort(first, last, less{}); } template constexpr void stable_sort(Iterator first, Iterator last, Compare comp) { if(first == last) return; for(auto i = first; i != last; ++i) rotate(upper_bound(first, i, *i, comp), i, i + 1); MIGRAPHX_ASSERT(is_sorted(first, last, comp)); } template constexpr void stable_sort(Iterator first, Iterator last) { stable_sort(first, last, less{}); } template constexpr OutputIterator merge(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2, OutputIterator d_first, Compare comp) { for(; first1 != last1; ++d_first) { if(first2 == last2) return copy(first1, last1, d_first); if(comp(*first2, *first1)) { *d_first = *first2; ++first2; } else { *d_first = *first1; ++first1; } } return copy(first2, last2, d_first); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/args.hpp000066400000000000000000000035361510465702400276170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_ARGS_HPP #define MIGRAPHX_GUARD_KERNELS_ARGS_HPP #include #include namespace migraphx { // Use template specialization since ADL is broken on hcc template struct make_tensor; template __device__ auto make_tensors_impl(F f, detail::seq, Ts*... xs) { return f(make_tensor::apply(xs)...); } inline __device__ auto make_tensors() { return [](auto*... xs) { return [=](auto f) { return make_tensors_impl(f, detail::gens{}, xs...); }; }; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_ARGS_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/array.hpp000066400000000000000000000320121510465702400277700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_ARRAY_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_ARRAY_HPP #include #include #include #include #include namespace migraphx { // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_ARRAY_OP(op, binary_op) \ template \ constexpr array& operator op(const array& x) \ { \ array_detail::array_for_each(*this, x)([](auto& sy, auto sx) { sy op sx; }); \ return *this; \ } \ template {})> \ constexpr array& operator op(const U& x) \ { \ array_detail::array_for_each (*this)([&](auto& sy) { sy op x; }); \ return *this; \ } \ template \ friend constexpr auto operator binary_op(const array& x, const array& y) \ { \ array z{}; \ array_detail::array_for_each(z, x, y)( \ [&](auto& sz, auto sx, auto sy) { sz = sx binary_op sy; }); \ return z; \ } \ template {})> \ friend constexpr auto operator binary_op(const array& x, const U& y) \ { \ array z{}; \ array_detail::array_for_each(z, x)([&](auto& sz, auto sx) { sz = sx binary_op y; }); \ return z; \ } \ template {})> \ friend constexpr auto operator binary_op(const U& x, const array& y) \ { \ array z{}; \ array_detail::array_for_each(z, y)([&](auto& sz, auto sy) { sz = x binary_op sy; }); \ return z; \ } namespace array_detail { template constexpr auto is_vectorizable() { return not is_same{} and (is_fundamental{} or is_same{}); } template __device__ auto& array2vec(T& x) { using value_type = typename T::value_type; constexpr auto size = decltype(x.size()){}; using type = vec; if constexpr(is_const{}) return reinterpret_cast(x); else return reinterpret_cast(x); } template constexpr auto array_for_each(T& x, Ts&... xs) { MIGRAPHX_ASSERT(((x.size() == xs.size()) and ...)); return [&](auto f) { constexpr auto size = decltype(x.size()){}; if constexpr((is_vectorizable() or (is_vectorizable() or ...)) and size <= 8 and size > 1 and (size % 2 == 0)) { if(__builtin_is_constant_evaluated()) { for(index_int i = 0; i < size; i++) f(x[i], xs[i]...); } else { using vec_type = remove_reference_t; f(array2vec(x), __builtin_convertvector(array2vec(xs), vec_type)...); } } else { for(index_int i = 0; i < size; i++) f(x[i], xs[i]...); } }; } } // namespace array_detail template struct array { using value_type = T; T d[N]; constexpr array() = default; template {} and ...))> constexpr array(Ts... xs) : d{xs...} { } template {} and (N > 1))> constexpr explicit array(U x) { for(index_int i = 0; i < N; i++) d[i] = x; } constexpr T& operator[](index_int i) { MIGRAPHX_ASSERT(i < N); return d[i]; } constexpr const T& operator[](index_int i) const { MIGRAPHX_ASSERT(i < N); return d[i]; } constexpr T& front() { return d[0]; } constexpr const T& front() const { return d[0]; } constexpr T& back() { return d[N - 1]; } constexpr const T& back() const { return d[N - 1]; } constexpr T* data() { return d; } constexpr const T* data() const { return d; } constexpr index_constant size() const { return {}; } constexpr auto empty() const { return size() == _c<0>; } constexpr T* begin() { return d; } constexpr const T* begin() const { return d; } constexpr T* end() { return d + size(); } constexpr const T* end() const { return d + size(); } constexpr T dot(const array& x) const { auto r = x * (*this); return r.reduce([](auto a, auto b) { return a + b; }, 0); } constexpr T product() const { return reduce([](auto x, auto y) { return x * y; }, 1); } constexpr T single(index_int width = 100) const { T result = 0; T a = 1; for(index_int i = 0; i < N; i++) { result += d[N - i - 1] * a; a *= width; } return result; } template constexpr auto apply(F f) const { array result; for(index_int i = 0; i < N; i++) result[i] = f(d[i]); return result; } template constexpr auto reduce(F f, T init) const { T result = init; for(index_int i = 0; i < N; i++) result = f(result, d[i]); return result; } MIGRAPHX_DEVICE_ARRAY_OP(+=, +) MIGRAPHX_DEVICE_ARRAY_OP(-=, -) MIGRAPHX_DEVICE_ARRAY_OP(*=, *) MIGRAPHX_DEVICE_ARRAY_OP(/=, /) MIGRAPHX_DEVICE_ARRAY_OP(%=, %) MIGRAPHX_DEVICE_ARRAY_OP(&=, &) MIGRAPHX_DEVICE_ARRAY_OP(|=, |) MIGRAPHX_DEVICE_ARRAY_OP(^=, ^) friend constexpr bool operator==(const array& x, const array& y) { for(index_int i = 0; i < N; i++) { if(x[i] != y[i]) return false; } return true; } template {})> friend constexpr bool operator==(const array& x, const U& y) { for(index_int i = 0; i < N; i++) { if(x[i] != y) return false; } return true; } template {})> friend constexpr bool operator==(const U& x, const array& y) { return y == x; } template friend constexpr bool operator!=(const U& x, const array& y) { return not(x == y); } template friend constexpr bool operator!=(const array& x, const U& y) { return not(x == y); } // This uses the product order rather than lexical order friend constexpr bool operator<(const array& x, const array& y) { for(index_int i = 0; i < N; i++) { if(not(x[i] < y[i])) return false; } return true; } friend constexpr bool operator>(const array& x, const array& y) { return y < x; } friend constexpr bool operator<=(const array& x, const array& y) { return (x < y) or (x == y); } friend constexpr bool operator>=(const array& x, const array& y) { return (y < x) or (x == y); } constexpr array carry(array result) const { index_int overflow = 0; for(diff_int i = result.size() - 1; i > 0; i--) { auto z = result[i] + overflow; // Reset overflow overflow = 0; // Compute overflow using while loop instead of mod while(z >= d[i]) { z -= d[i]; overflow += 1; } result[i] = z; } result[0] += overflow; return result; } /// Get the multi-dimensional index from the given 1D index. constexpr array multi(T idx) const { array result; index_int tidx = idx; for(diff_int is = result.size() - 1; is > 0; is--) { result[is] = tidx % d[is]; tidx = tidx / d[is]; } result[0] = tidx; return result; } template friend constexpr const Stream& operator<<(const Stream& ss, const array& a) { for(index_int i = 0; i < N; i++) { if(i > 0) ss << ", "; ss << a[i]; } return ss; } }; template constexpr auto array_apply(F f) { return [=](auto&& x) { return x.apply(f); }; } template constexpr array make_array(T x, Ts... xs) { return {x, static_cast(xs)...}; } template struct integral_const_array : array { using base_array = array; MIGRAPHX_DEVICE_CONSTEXPR integral_const_array() : base_array({Xs...}) {} constexpr const base_array& base() const { return *this; } }; template constexpr auto make_const_array(T x, Ts... xs) { return integral_const_array{}; } template constexpr auto generate_array(N n, F f) { return sequence_c([=](auto... is) { return array{f(is)...}; }); } template constexpr auto unpack(integral_const_array, F f) { return f(_c...); } template constexpr auto transform(integral_const_array, F f) { return integral_const_array{}; } template constexpr auto transform_i(integral_const_array, F f) { return sequence_c( [=](auto... is) { return integral_const_array{}; }); } template constexpr auto transform(integral_const_array, integral_const_array, F f) { return integral_const_array{}; } template constexpr auto return_array_c(F f) { constexpr auto r = f(); return sequence(r.size(), [&](auto... is) { return make_const_array(_c...); }); } template using index_ints = integral_const_array; } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/atomic.hpp000066400000000000000000000113121510465702400301260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_ATOMIC_HPP #define MIGRAPHX_GUARD_KERNELS_ATOMIC_HPP #include #include #include #include #include #include #include #include #ifndef MIGRAPHX_ALLOW_ATOMIC_CAS // NOLINTNEXTLINE #define MIGRAPHX_ALLOW_ATOMIC_CAS 0 #endif // NOLINTNEXTLINE #define MIGRAPHX_ATOMIC_CAS_WARNING() \ MIGRAPHX_ASSERT(MIGRAPHX_ALLOW_ATOMIC_CAS and "Using atomicCAS is slow") namespace migraphx { namespace atomic { using cas_rank = rank<1>; template MIGRAPHX_DEVICE_CONSTEXPR void cas(rank<1>, T& x, T y, Op op) { MIGRAPHX_ATOMIC_CAS_WARNING(); using storage = conditional_t; storage* address = reinterpret_cast(&x); storage expected = __hip_atomic_load(address, __ATOMIC_RELAXED, __HIP_MEMORY_SCOPE_AGENT); while(not __hip_atomic_compare_exchange_strong(address, &expected, bit_cast(op(bit_cast(expected), y)), __ATOMIC_RELAXED, __ATOMIC_RELAXED, __HIP_MEMORY_SCOPE_AGENT)) { } } template MIGRAPHX_DEVICE_CONSTEXPR auto cas(rank<0>, vec& x, vec y, Op op) -> decltype(cas(cas_rank{}, x[0], y[0], op), void()) { for(index_int i = 0; i < N; i++) { cas(cas_rank{}, x[i], y[i], op); } } template MIGRAPHX_DEVICE_CONSTEXPR auto builtin_assign(T& x, T y, op::sum) MIGRAPHX_RETURNS(unsafeAtomicAdd(&x, y)); __device__ inline void builtin_assign(half2& x, half2 y, op::sum) { __builtin_amdgcn_global_atomic_fadd_v2f16(&x, y); } template constexpr bool is_aligned(const void* ptr) { auto iptr = bit_cast(ptr); return (iptr % alignof(T)) == 0; } __device__ inline void builtin_assign(half& x, half y, op::sum) { half* address = &x; if(is_aligned(address)) { __builtin_amdgcn_global_atomic_fadd_v2f16(address, half2{y, half(0)}); } else { __builtin_amdgcn_global_atomic_fadd_v2f16(address - 1, half2{half(0), y}); } } template MIGRAPHX_DEVICE_CONSTEXPR auto builtin_assign(T& x, T y, op::min) MIGRAPHX_RETURNS(unsafeAtomicMin(&x, y)); template MIGRAPHX_DEVICE_CONSTEXPR auto builtin_assign(T& x, T y, op::max) MIGRAPHX_RETURNS(unsafeAtomicMax(&x, y)); template MIGRAPHX_DEVICE_CONSTEXPR auto builtin_assign(vec& x, vec y, Op op) -> decltype(builtin_assign(x[0], y[0], op), void()) { for(index_int i = 0; i < N; i++) { builtin_assign(x[i], y[i], op); } } template MIGRAPHX_DEVICE_CONSTEXPR auto assign(rank<0>, T& x, T y, Op op) MIGRAPHX_RETURNS(cas(cas_rank{}, x, y, op)); template MIGRAPHX_DEVICE_CONSTEXPR auto assign(rank<1>, T& x, T y, Op op) MIGRAPHX_RETURNS(builtin_assign(x, y, op)); } // namespace atomic template MIGRAPHX_DEVICE_CONSTEXPR void atomic_assign(T& x, U y, Op op) { atomic::assign(rank<1>{}, x, T(y), op); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_ATOMIC_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/bit.hpp000066400000000000000000000044331510465702400274360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_BIT_HPP #define MIGRAPHX_GUARD_KERNELS_BIT_HPP #include #include namespace migraphx { constexpr bool get_bit(uint32_t x, uint32_t i) noexcept { MIGRAPHX_ASSERT(i < 32); return ((x >> i) & 1u) != 0; } constexpr uint64_t bit_ceil(uint64_t x) noexcept { if(x <= 1) return 1; --x; x |= x >> 1u; x |= x >> 2u; x |= x >> 4u; x |= x >> 8u; x |= x >> 16u; x |= x >> 32u; return x + 1; } constexpr uint32_t bit_ceil(uint32_t x) noexcept { if(x <= 1) return 1; --x; x |= x >> 1u; x |= x >> 2u; x |= x >> 4u; x |= x >> 8u; x |= x >> 16u; return x + 1; } constexpr uint32_t popcount(uint32_t x) noexcept { return __popc(x); } constexpr uint32_t popcount(uint64_t x) noexcept { return __popcll(x); } constexpr uint32_t countr_zero(uint32_t x) noexcept { // popcount(~(x | −x)) return __builtin_ctz(x); } constexpr uint32_t countr_zero(uint64_t x) noexcept { return __builtin_ctzll(x); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_BIT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/bit_cast.hpp000066400000000000000000000034011510465702400304420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_BITCAST_HPP #define MIGRAPHX_GUARD_KERNELS_BITCAST_HPP #include #include namespace migraphx { template {} and is_trivially_copyable{})> constexpr auto bit_cast(From fr) noexcept { return vec_transform(fr)([](auto x) -> To { static_assert(sizeof(To) == sizeof(decltype(x))); return __builtin_bit_cast(To, x); }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_BITCAST_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/ck.hpp000066400000000000000000000111771510465702400272600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_CK_HPP #define MIGRAPHX_GUARD_KERNELS_CK_HPP #include #include #include #include #include #include #include #include namespace migraphx { namespace detail { template struct to_ck_type_impl { using type = T; }; template <> struct to_ck_type_impl { using type = ck::half_t; }; template struct to_ck_type_impl { using type = const typename to_ck_type_impl::type; }; template constexpr bool is_row_major() { constexpr auto strides = Shape{}.strides; MIGRAPHX_ASSERT(strides.size() >= 2); if(strides.back() == 1) { MIGRAPHX_ASSERT(not Shape{}.is_transposed()); return true; } MIGRAPHX_ASSERT(strides[strides.size() - 2] == 1); return false; } } // namespace detail template using to_ck_type = typename detail::to_ck_type_impl::type; template constexpr auto to_ck_pointer(T* x) { return static_cast*>(x); } template constexpr auto to_ck_const_pointer(const T* x) { return static_cast*>(x); } template using to_ck_gemm_layout = conditional_t>(), ck::tensor_layout::gemm::RowMajor, ck::tensor_layout::gemm::ColumnMajor>; template constexpr auto to_ck_tensor() { constexpr auto s = get_shape_c{}; return sequence(s.lens.size(), [&](auto... is) { return ck::make_naive_tensor_descriptor(ck::make_tuple(s.lens[is]...), ck::make_tuple(s.strides[is]...)); }); } template struct ck_function_adaptor : F { template constexpr ck_function_adaptor(Ts&&... xs) : F(static_cast(xs)...) { } template constexpr void operator()(T& out, Ts&&... xs) const { out = static_cast(*this)(static_cast(xs)...); } }; struct ck_nop { template constexpr void operator()(T&) const { } }; struct ck_passthrough { template constexpr void operator()(T& y, U x) const { y = x; } }; struct ck_scale { constexpr ck_scale(float s) : scale(s) {} template constexpr void operator()(T& y, U x) const { y = x * static_cast(scale); } float scale; }; struct ck_add { template constexpr void operator()(T& y, U x) const { y += x; } }; // In CK, the B matrix is ordered as N,K instead of K,N template constexpr auto ck_transposeb_dims(Dims dims) { return unpack(dims, [](auto k, auto n) { return make_const_array(n, k); }); } template using ck_transposeb = decltype(make_shape(ck_transposeb_dims(get_shape_c{}.lens), ck_transposeb_dims(get_shape_c{}.strides))); #ifdef MIGRAPHX_CK_CHECK #define MIGRAPHX_CK_STATIC_ASSERT static_assert #else #define MIGRAPHX_CK_STATIC_ASSERT(...) #endif } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_CK_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/ck_gemm.hpp000066400000000000000000000046711510465702400302660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_CK_GEMM_HPP #define MIGRAPHX_GUARD_KERNELS_CK_GEMM_HPP #include #include #include #include #include #include namespace migraphx { template __device__ void ck_gemm_matrix(E e, A a, B b, Ds... ds) { constexpr auto desc = G::make_descriptor(to_ck_tensor(), to_ck_tensor>(), ck::make_tuple(to_ck_tensor()...), to_ck_tensor()); MIGRAPHX_STATIC_ASSERT_FOR(desc.IsValid()) { G::Run(desc, to_ck_const_pointer(a.data()), to_ck_const_pointer(b.data()), ck::make_tuple(to_ck_const_pointer(ds.data())...), to_ck_pointer(e.data())); } } template __device__ void ck_gemm(Ts... xs) { gemm_batch_args(make_index(), _c, xs...)( [](auto... ys) { ck_gemm_matrix(ys...); }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/ck_gemm_softmax_gemm.hpp000066400000000000000000000054121510465702400330260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_CK_GEMM_SOFTMAX_GEMM_HPP #define MIGRAPHX_GUARD_KERNELS_CK_GEMM_SOFTMAX_GEMM_HPP #include #include #include #include #include #include namespace migraphx { template struct ck_gemm_softmax_gemm_settings { T scale{}; }; template constexpr ck_gemm_softmax_gemm_settings make_ck_gemm_softmax_gemm_settings(Ts... xs) { return {xs...}; } template __device__ void ck_gemm_softmax_gemm_matrix(C c, A a, B b, B1 b1, Settings s) { constexpr auto desc = G::make_descriptor(to_ck_tensor(), to_ck_tensor>(), to_ck_tensor>(), to_ck_tensor()); MIGRAPHX_STATIC_ASSERT_FOR(desc.IsValid()) { G::Run(desc, s.scale, to_ck_const_pointer(a.data()), to_ck_const_pointer(b.data()), to_ck_const_pointer(b1.data()), to_ck_pointer(c.data())); } } template __device__ void ck_gemm_softmax_gemm(Settings s, Ts... xs) { gemm_batch_args(make_index(), _c, xs...)( [&](auto... ys) { ck_gemm_softmax_gemm_matrix(ys..., s); }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/concat.hpp000066400000000000000000000064631510465702400301340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #ifndef MIGRAPHX_GUARD_KERNELS_CONCAT_HPP #define MIGRAPHX_GUARD_KERNELS_CONCAT_HPP namespace migraphx { template constexpr auto concat_slice(Output out, Input, Start) { constexpr auto lens = get_shape_c{}.lens; constexpr auto strides = get_shape_c{}.strides; constexpr auto offset = return_c([] { constexpr auto output_shape = get_shape_c{}; return Start{} * output_shape.strides[Axis]; }); constexpr auto s = make_shape(lens, strides); MIGRAPHX_ASSERT(offset < out.get_shape().element_space()); MIGRAPHX_ASSERT((s.element_space() + offset) <= out.get_shape().element_space()); return make_tensor_view(out.data() + offset, s); } template constexpr auto concat_slices(Input input, Start start, Ts... xs) { return [=](auto f) { return f(concat_slice(xs, input, start)...); }; } template constexpr auto concat_ends(Input) { constexpr auto lens = get_shape_c{}.lens; return _c; } template __device__ auto concat_each(index idx, Start start, InputPack input_pack, F f, Ts... ts) { return input_pack([&](auto g, auto x, auto... xs) { return concat_slices(x, start, ts...)([&](auto z, auto... ys) { idx.global_stride(x.get_shape().elements(), [&](auto i) { z[i] = f(g(x[i], xs[i]...), ys[i]...); }); return start + concat_ends(x); }); }); } template __device__ auto concat(InputPacks... input_packs) { return [=](auto f, auto... ts) { auto idx = make_index(); fold([&](auto start, auto input_pack) { return concat_each(idx, start, input_pack, f, ts...); })(_c<0>, input_packs...); }; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_CONCAT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/concat_past_present.hpp000066400000000000000000000126621510465702400327210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_CONCAT_PAST_PRESENT_HPP #define MIGRAPHX_GUARD_KERNELS_CONCAT_PAST_PRESENT_HPP #include #include #include namespace migraphx { template __device__ void copy_data(Dest destination, const Src source, index_int n, index_int idx) { if(idx < n) { destination[idx] = source[idx]; } } struct concat_state_chunk { index_int present_buff_chunk_length; index_int past_buff_chunk_length; index_int past_chunk_length; index_int new_chunk_length; bool is_prompt; bool past_present_share_buffer; std::ptrdiff_t i; template __device__ Present compute(Past past, const Chunk chunk, Present present, index_int idx) { auto start = present + i * present_buff_chunk_length; auto p = start; if(not is_prompt) { if(not past_present_share_buffer) { const auto src_past = past + i * past_buff_chunk_length; copy_data(p, src_past, past_chunk_length, idx); } p += past_chunk_length; } copy_data(p, chunk, new_chunk_length, idx); return start; } }; template __device__ void update_cache(const Present present, SeqLensK seqlens_k, Cache cache, Params params, index_int idx) { const index_int batch_size = params.batch_size; const index_int sequence_length = params.sequence_length; const index_int head_size = params.head_size; const index_int past_buffer_sequence_length = params.seqlen_present_kv_cache; const index_int present_buffer_sequence_length = past_buffer_sequence_length; const index_int num_heads = params.num_heads; const index_int kv_num_heads = params.kv_num_heads; const bool is_prompt = sequence_length != 1; const index_int packed_batch_stride = (num_heads + 2 * kv_num_heads) * sequence_length * head_size; const index_int kv_num_heads_factor = num_heads / kv_num_heads; const index_int kv_input_chunk_length = sequence_length * head_size; // L x H const index_int past_buff_chunk_length = past_buffer_sequence_length * head_size; // L x H const index_int present_buff_chunk_length = present_buffer_sequence_length * head_size; // T x H const index_int loop_len = batch_size * num_heads; const index_int i = idx / (sequence_length * head_size); const index_int inner_i = idx % (sequence_length * head_size); if(i < loop_len) { const index_int batch_index = i / num_heads; const index_int head_index = i % num_heads; const index_int past_seqlen = sequence_length == 1 ? static_cast(seqlens_k[batch_index]) : past_buffer_sequence_length; const index_int past_chunk_length = past_seqlen * head_size; auto current = present + packed_batch_stride * batch_index + kv_input_chunk_length * (head_index / kv_num_heads_factor); concat_state_chunk concat{present_buff_chunk_length, past_buff_chunk_length, past_chunk_length, kv_input_chunk_length, is_prompt, params.past_present_share_buffer, i / kv_num_heads_factor}; concat.compute(cache, current, cache, inner_i); } } template __device__ void concat_past_present(Past past, const Present present, SeqLensK seqlens_k, Params params) { auto ind = make_index(); auto elements = params.batch_size * params.kv_num_heads * params.sequence_length * params.head_size; ind.global_stride(elements, [&](auto idx) { update_cache(present.begin(), seqlens_k, past.begin(), params, idx); }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/copy.hpp000066400000000000000000000052041510465702400276270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_COPY_HPP #define MIGRAPHX_GUARD_KERNELS_COPY_HPP #include namespace migraphx { template __device__ void local_vector_copy(Index idx, T* src, U* dst, Size size) { constexpr auto n = find_vectorize_size([&](auto i) { return (size % i) == 0; }); auto vsrc = as_vec(remove_bool(src)); auto vdst = as_vec(remove_bool(dst)); index_int vsize = size / n; idx.local_stride(vsize, [&](auto i) { vdst[i] = vsrc[i]; }); } template __device__ void local_tensor_copy(Index idx, T src, U dst) { constexpr auto src_shape = get_shape_c{}; constexpr auto dst_shape = get_shape_c{}; static_assert(src_shape.lens == dst_shape.lens); if constexpr(src_shape == dst_shape and (src_shape.packed() or src_shape.broadcasted())) { local_vector_copy(idx, src.data(), dst.data(), src_shape.element_space()); } else { constexpr auto perm = find_permutation(src_shape, dst_shape); auto new_src = reorder_tensor_view(src, perm); auto new_dst = reorder_tensor_view(dst, perm); auto_vectorize()(new_src, new_dst)([&](auto vsrc, auto vdst) { index_int size = vsrc.get_shape().elements(); idx.local_stride(size, [&](auto i) { vdst[i] = vsrc[i]; }); }); } } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_COPY_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/debug.hpp000066400000000000000000000145301510465702400277450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_DEBUG_HPP #define MIGRAPHX_GUARD_KERNELS_DEBUG_HPP #include namespace migraphx { #define MIGRAPHX_STRINGIZE_1(...) #__VA_ARGS__ #define MIGRAPHX_STRINGIZE(...) MIGRAPHX_STRINGIZE_1(__VA_ARGS__) // Workaround hip's broken abort on device code #ifdef __HIP_DEVICE_COMPILE__ // NOLINTNEXTLINE #define MIGRAPHX_HIP_NORETURN #else // NOLINTNEXTLINE #define MIGRAPHX_HIP_NORETURN [[noreturn]] #endif namespace debug { struct swallow { template constexpr swallow(Ts&&...) { } }; template struct print_buffer { char buffer[N + 1] = {0}; char* pos = buffer; constexpr void append(char c) { if(c == 0) return; if(pos < buffer + N) { *pos = c; pos++; } } static constexpr void reverse(char* first, char* last) { if(first == last) return; last--; while(first < last) { char tmp = *first; *first = *last; *last = tmp; first++; last--; } } template constexpr void append(T i) { if(i < 0) { append('-'); i = -i; } if(i == 0) { append('0'); return; } char* start = pos; while(i != 0) { char c = (i % 10) + '0'; append(c); i = i / 10; } reverse(start, pos); } constexpr void append(const char* str) { if(str == nullptr) return; int i = 512; while(*str != 0 and i > 0) { append(*str); str++; i--; } } template constexpr void append(const char (&array)[M]) { for(int i = 0; i < M; i++) append(array[i]); } }; template __host__ __device__ void print(const Ts&... xs) { print_buffer<1024> buffer; swallow{(buffer.append(xs), 0)...}; printf("%s", buffer.buffer); } } // namespace debug struct source_location { int line = __builtin_LINE(); const char* file = __builtin_FILE(); const char* function = __builtin_FUNCTION(); }; template struct source_location_capture { T x; source_location loc; // declval is a workaround since default constructor for "U" is not working with rocm-5.6 template static U&& declval(); template ()))> constexpr source_location_capture(U px, source_location ploc = source_location{}) : x(px), loc(ploc) { } template ()))> constexpr source_location_capture(source_location_capture slc) : x(slc.x), loc(slc.loc) { } constexpr operator source_location() const { return loc; } constexpr operator T() const { return x; } }; template constexpr auto capture_transform(source_location_capture slc, F f) { auto r = f(slc.x); return source_location_capture(r, slc.loc); } template constexpr auto capture_transform(T x, F f) { return f(x); } // noreturn cannot be used on this function because abort in hip is broken template MIGRAPHX_HIP_NORETURN inline __host__ __device__ void assert_fail(const T1& assertion, const T2& file, const T3& line, const T4& function) { // printf is broken on hip with more than one argument, so use a simple print functions instead debug::print(file, ":", line, ": ", function, ": assertion '", assertion, "' failed.\n"); // printf("%s:%s: %s: assertion '%s' failed.\n", file, line, function, assertion); abort(); } template MIGRAPHX_HIP_NORETURN inline __host__ __device__ void assert_fail(const source_location& loc, Ts... xs) { debug::print(loc.file, ":", loc.line, ": ", loc.function, ": error: ", xs..., "\n"); abort(); } // NOLINTNEXTLINE #define MIGRAPHX_ASSERT_FAIL(cond, ...) \ ((cond) ? void(0) : [](auto&&... private_migraphx_xs) { \ assert_fail(private_migraphx_xs...); \ }(__VA_ARGS__)) // NOLINTNEXTLINE #define MIGRAPHX_CHECK(cond) \ MIGRAPHX_ASSERT_FAIL(cond, #cond, __FILE__, __LINE__, __PRETTY_FUNCTION__) #ifdef MIGRAPHX_DEBUG // NOLINTNEXTLINE #define MIGRAPHX_CAPTURE_SOURCE_LOCATION(T) source_location_capture #define MIGRAPHX_WARN(cond, loc, ...) MIGRAPHX_ASSERT_FAIL(cond, loc, __VA_ARGS__) #define MIGRAPHX_ASSERT MIGRAPHX_CHECK #define MIGRAPHX_ASSUME MIGRAPHX_CHECK #define MIGRAPHX_UNREACHABLE() MIGRAPHX_ASSERT(false) #else // NOLINTNEXTLINE #define MIGRAPHX_CAPTURE_SOURCE_LOCATION(T) T #define MIGRAPHX_ASSUME __builtin_assume #define MIGRAPHX_UNREACHABLE __builtin_unreachable #define MIGRAPHX_ASSERT(cond) #define MIGRAPHX_WARN(...) #endif #define MIGRAPHX_STATIC_ASSERT_FOR(...) \ static_assert(__VA_ARGS__); \ if constexpr(__VA_ARGS__) } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_DEBUG_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/dfor.hpp000066400000000000000000000031611510465702400276070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_DFOR_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_DFOR_HPP namespace migraphx { // Multidimensional for loop constexpr auto dfor() { return [](auto f) { f(); }; } template constexpr auto dfor(T x, Ts... xs) { return [=](auto f) { for(T i = 0; i < x; i++) { dfor(xs...)([&](Ts... is) { f(i, is...); }); } }; } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/dpp.hpp000066400000000000000000000073261510465702400274470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_DPP_HPP #define MIGRAPHX_GUARD_KERNELS_DPP_HPP #include #include #include namespace migraphx { constexpr bool is_power_of_2(unsigned int x) { return x > 0 and (x & (x - 1)) == 0u; } #ifndef MIGRAPHX_HAS_DPP #define MIGRAPHX_HAS_DPP 1 #endif #if MIGRAPHX_HAS_DPP constexpr unsigned int dpp_row_shr(unsigned int x) { return 0x110u | x; } constexpr unsigned int dpp_row_bcast(unsigned int x) { unsigned int y = 0; switch(x) { case 15: y = 0x142; break; case 31: y = 0x143; break; default: MIGRAPHX_UNREACHABLE(); } return y; } template __device__ T dpp_op(T& x, F f) { static const index_int n = sizeof(T) < 4 ? 1 : sizeof(T) / 4; union type { uint32_t reg[n]; T data; }; type output{}; type input{}; // cppcheck-suppress unreadVariable input.data = x; for(index_int i = 0; i < n; i++) { output.reg[i] = f(input.reg[i]); } return output.data; } template __device__ T dpp_mov(T& x) { return dpp_op(x, [](auto i) { return __hip_move_dpp(i, DppCtrl, RowMask, BankMask, BoundCtrl); }); } template __device__ T dpp_swizzle(T& x) { return dpp_op(x, [](auto i) { return __hip_ds_swizzle(i, Mask); }); } template __device__ T readlane(T& x) { static_assert(is_power_of_2(Width), "Width must be a power of 2"); return dpp_op(x, [](auto i) { return __shfl(i, SrcLane, Width); }); } template __device__ T readlane(T& x, unsigned int src_lane) { return dpp_op(x, [&](auto i) { return __shfl(i, src_lane, MIGRAPHX_WAVEFRONTSIZE); }); } template __device__ T readlane_xor(T& x) { if constexpr(XorMask == 1) return dpp_swizzle<0x041F>(x); else if constexpr(XorMask == 2) return dpp_swizzle<0x081F>(x); else if constexpr(XorMask == 4) return dpp_swizzle<0x101F>(x); else if constexpr(XorMask == 8) return dpp_swizzle<0x201F>(x); else if constexpr(XorMask == 16) return dpp_swizzle<0x401F>(x); else return dpp_op(x, [&](auto i) { return __shfl_xor(i, XorMask, MIGRAPHX_WAVEFRONTSIZE); }); } #endif // MIGRAPHX_HAS_DPP } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_DPP_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/float8.hpp000066400000000000000000000500711510465702400300540ustar00rootroot00000000000000/* ************************************************************************ * * The MIT License (MIT) * * Copyright (C) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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 cop- * ies 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 IM- * PLIED, 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 CONNE- * CTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * ************************************************************************ */ #ifndef MIGRAPHX_GUARD_KERNELS_FLOAT8_HPP #define MIGRAPHX_GUARD_KERNELS_FLOAT8_HPP #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" #pragma clang diagnostic ignored "-Wc++20-extensions" // required for "asm" inside constexpr #endif // __clang__ // We are clipping in down conversion by default #define MIGRAPHX_F8_DOWNCAST_CLIPPING 1 // NOLINT #include #include #include namespace migraphx { namespace fp8 { enum class rounding_mode { standard, // standard rounding is doing RNE -- round to nearest even stochastic }; enum class f8_type { bf8 = 0, // s1e5m2 fp8 = 1 // s1e4m3 }; template class numeric_limits; template struct float8 { uint8_t data; // default constructor __device__ constexpr float8() = default; // default copy constructor __device__ constexpr float8(const float8& y) = default; struct from_bits_t { }; static constexpr __device__ from_bits_t from_bits() { return from_bits_t(); } __device__ explicit constexpr float8(uint8_t bits, from_bits_t) : data(bits) {} #if defined(__gfx942__) // device specific optimized F8 down-conversion code template static __device__ uint8_t cast_to_f8fnuz_from_f32(float v, uint32_t rng = 0) { uint8_t i8data = 0x00; union { float fval; uint32_t i32val; uint8_t i8val[4]; // NOTE: not endian independent } val; uint32_t ival = 0; val.fval = v; #ifdef MIGRAPHX_F8_DOWNCAST_CLIPPING if constexpr(T == migraphx::fp8::f8_type::fp8) { if((val.i32val & 0x7F800000) != 0x7F800000) /// propagate NAN/INF, no clipping val.fval = __builtin_amdgcn_fmed3f(val.fval, 240.0, -240.0); } else { if((val.i32val & 0x7F800000) != 0x7F800000) // propagate NAN/INF, no clipping val.fval = __builtin_amdgcn_fmed3f(val.fval, 57344.0, -57344.0); } #endif if(stochastic_rounding) { if constexpr(T == migraphx::fp8::f8_type::fp8) { ival = __builtin_amdgcn_cvt_sr_fp8_f32(val.fval, rng, ival, 0); // 0 pos } else { ival = __builtin_amdgcn_cvt_sr_bf8_f32(val.fval, rng, ival, 0); // 0 pos } } else // RNE CVT { if constexpr(T == migraphx::fp8::f8_type::fp8) { ival = __builtin_amdgcn_cvt_pk_fp8_f32( val.fval, val.fval, ival, false); // false -> WORD0 } else { ival = __builtin_amdgcn_cvt_pk_bf8_f32( val.fval, val.fval, ival, false); // false -> WORD0} } } val.i32val = ival; i8data = val.i8val[0]; // little endian return i8data; } #endif // __gfx942__ // constructor from float #if defined(__gfx942__) // NOTE: ON-DEVICE... always optimal bias explicit constexpr __device__ float8(const float v, migraphx::fp8::rounding_mode rm = migraphx::fp8::rounding_mode::standard, uint32_t rng = 0) { if(__builtin_is_constant_evaluated() or !FNUZ) { if constexpr(T == migraphx::fp8::f8_type::fp8) { #ifdef MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<3, 4, float, FNUZ /*negative_zero_nan*/, true /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #else // MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<3, 4, float, FNUZ /*negative_zero_nan*/, false /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #endif // MIGRAPHX_F8_DOWNCAST_CLIPPING } else { #ifdef MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<2, 5, float, FNUZ /*negative_zero_nan*/, true /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #else // MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<2, 5, float, FNUZ /*negative_zero_nan*/, false /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #endif // MIGRAPHX_FP8_DOWNCAST_CLIPPING} } } else { // runtime branch, use cast_to_f8fnuz_from_f32 if want to avoid it if(rm == migraphx::fp8::rounding_mode::stochastic) data = cast_to_f8fnuz_from_f32(v, rng); else data = cast_to_f8fnuz_from_f32(v); } } #else // DEVICE for non-gfx942 using s/w simulation explicit constexpr __device__ float8(const float v, migraphx::fp8::rounding_mode rm = migraphx::fp8::rounding_mode::standard, uint32_t rng = 0) { if constexpr(T == migraphx::fp8::f8_type::fp8) { #ifdef MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<3, 4, float, FNUZ /*negative_zero_nan*/, true /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #else // MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<3, 4, float, FNUZ /*negative_zero_nan*/, false /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #endif // MIGRAPHX_F8_DOWNCAST_CLIPPING } else { #ifdef MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<2, 5, float, FNUZ /*negative_zero_nan*/, true /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #else // MIGRAPHX_F8_DOWNCAST_CLIPPING data = migraphx::fp8::impl:: cast_to_f8<2, 5, float, FNUZ /*negative_zero_nan*/, false /*clip*/>( v, (rm == migraphx::fp8::rounding_mode::stochastic), rng); #endif // MIGRAPHX_FP8_DOWNCAST_CLIPPING} } } #endif // __gfx942___ // Constructor from half explicit constexpr __device__ float8(const _Float16 v, rounding_mode rm = rounding_mode::standard, uint32_t rng = 0) : float8(static_cast(v), rm, rng) { } // constructor from int explicit constexpr __device__ float8(const int v, rounding_mode rm = rounding_mode::standard, uint32_t rng = 0) : float8(static_cast(v), rm, rng) { } // constructor from uint explicit constexpr __device__ float8(const uint32_t v, rounding_mode rm = rounding_mode::standard, uint32_t rng = 0) : float8(static_cast(v), rm, rng) { } // constructor from double explicit constexpr __device__ float8(const double v, rounding_mode rm = rounding_mode::standard, uint32_t rng = 0) : float8(static_cast(v), rm, rng) { } // constructor from bool explicit constexpr __device__ float8(const bool v, rounding_mode rm = rounding_mode::standard, uint32_t rng = 0) : float8(static_cast(v), rm, rng) { } // convert to float #if defined(__gfx942__) // NOLINT // upcast using device specific intrinsic constexpr __device__ operator float() const { if(__builtin_is_constant_evaluated() or !FNUZ) { if constexpr(T == migraphx::fp8::f8_type::fp8) { return migraphx::fp8::impl::cast_from_f8<3, 4, float, FNUZ /*negative_zero_nan*/>( data); } // else return migraphx::fp8::impl::cast_from_f8<2, 5, float, FNUZ /*negative_zero_nan*/>(data); } else { float fval = 0; uint32_t i32val = static_cast(data); // upcast if constexpr(T == migraphx::fp8::f8_type::fp8) { __asm__ volatile("v_cvt_f32_fp8 %0, %1 src0_sel:BYTE_0" : "=v"(fval) : "v"(i32val)); } else { __asm__ volatile("v_cvt_f32_bf8 %0, %1 src0_sel:BYTE_0" : "=v"(fval) : "v"(i32val)); } return fval; } } #else // non gfx942 constexpr __device__ operator float() const { if constexpr(T == migraphx::fp8::f8_type::fp8) { return migraphx::fp8::impl::cast_from_f8<3, 4, float, FNUZ /*negative_zero_nan*/>(data); } // else return migraphx::fp8::impl::cast_from_f8<2, 5, float, FNUZ /*negative_zero_nan*/>(data); } #endif constexpr explicit __device__ operator bool() const { return not is_zero(); } // check for zero __device__ constexpr bool is_zero() const { if constexpr(FNUZ) { return data == 0x00; } else { return (data == 0x00) or (data == 0x80); } } // check for nan __device__ constexpr bool is_nan() const { if constexpr(FNUZ) { return data == 0x80; } else { if(T == migraphx::fp8::f8_type::bf8) { return (data == 0x7D) or (data == 0x7E) or (data == 0x7F) or (data == 0xFD) or (data == 0xFE) or (data == 0xFF); } else { return (data == 0x7F) or (data == 0xFF); } } } // check for inf __device__ constexpr bool is_inf() const { if constexpr(FNUZ) { return data == 0x80; } else { if(T == migraphx::fp8::f8_type::bf8) { return (data == 0x7C) or (data == 0xFC); } else { // no infinities in e4m3fn, represent them as NaNs return (data == 0x7F) or (data == 0xFF); } } } // NOLINTNEXTLINE #define MIGRAPHX_FP8_SHORT_UNARY_OP(unary_op, binary_op) \ constexpr float8& __device__ operator unary_op(const float8& rhs) \ { \ const auto tmp = static_cast(*this) binary_op static_cast(rhs); \ *this = static_cast(tmp); \ return *this; \ } \ constexpr float8& __device__ operator unary_op(const float& rhs) \ { \ const auto tmp = static_cast(*this) binary_op static_cast(rhs); \ *this = static_cast(tmp); \ return *this; \ } MIGRAPHX_FP8_SHORT_UNARY_OP(*=, *) MIGRAPHX_FP8_SHORT_UNARY_OP(-=, -) MIGRAPHX_FP8_SHORT_UNARY_OP(+=, +) MIGRAPHX_FP8_SHORT_UNARY_OP(/=, /) __device__ constexpr float8& operator=(const float8& rhs) = default; __device__ constexpr float8& operator=(float8&& rhs) noexcept = default; __device__ constexpr bool operator<(const float8& rhs) const { const auto we = static_cast(*this); const auto them = static_cast(rhs); return we < them; } __device__ constexpr bool operator>(const float8& rhs) const { const auto we = static_cast(*this); const auto them = static_cast(rhs); return we > them; } }; // https://onnx.ai/onnx/technical/float8.html using fp8e4m3fn = float8; using fp8e5m2 = float8; using fp8e4m3fnuz = float8; using fp8e5m2fnuz = float8; // NOLINTNEXTLINE #define MIGRAPHX_FP8_BINARY_OP(binary_op, T, U) \ constexpr U __device__ operator binary_op(const T& lhs, const T& rhs) \ { \ return U(static_cast(lhs) binary_op static_cast(rhs)); \ } // NOLINTNEXTLINE #define MIGRAPHX_FP8_OTHER_OPS(T) \ constexpr __device__ T fabs(T v) \ { \ /*NOLINTNEXTLINE*/ \ v.data = v.data & 0x7f; \ return v; \ } \ __device__ constexpr bool operator==(const T& lhs, const T& rhs) \ { \ if(rhs.is_nan() or rhs.is_inf() or lhs.is_nan() or lhs.is_inf()) \ return false; \ else if((rhs.is_zero() and lhs.is_zero()) or (lhs.data == rhs.data)) \ return true; \ return false; \ } // NOLINTNEXTLINE #define MIGRAPHX_FP8_GEN_OP_OVERLOADS(T) \ MIGRAPHX_FP8_BINARY_OP(*, T, T) \ MIGRAPHX_FP8_BINARY_OP(-, T, T) \ MIGRAPHX_FP8_BINARY_OP(/, T, T) \ MIGRAPHX_FP8_BINARY_OP(+, T, T) \ MIGRAPHX_FP8_BINARY_OP(>=, T, bool) \ MIGRAPHX_FP8_BINARY_OP(<=, T, bool) \ MIGRAPHX_FP8_BINARY_OP(!=, T, bool) \ MIGRAPHX_FP8_OTHER_OPS(T) MIGRAPHX_FP8_GEN_OP_OVERLOADS(fp8e5m2) MIGRAPHX_FP8_GEN_OP_OVERLOADS(fp8e5m2fnuz) MIGRAPHX_FP8_GEN_OP_OVERLOADS(fp8e4m3fn) MIGRAPHX_FP8_GEN_OP_OVERLOADS(fp8e4m3fnuz) template <> class numeric_limits { public: static constexpr bool has_infinity = false; static constexpr __device__ fp8e4m3fnuz epsilon() { return fp8e4m3fnuz(0x28, fp8e4m3fnuz::from_bits()); } // NOLINTNEXTLINE static constexpr __device__ fp8e4m3fnuz quiet_NaN() { return fp8e4m3fnuz(0x80, fp8e4m3fnuz::from_bits()); } static constexpr __device__ fp8e4m3fnuz max() { return fp8e4m3fnuz(0x7F, fp8e4m3fnuz::from_bits()); } // this is min value that is not DeNormalized(DeNorm). DeNorm min is 0x01 static constexpr __device__ fp8e4m3fnuz min() { return fp8e4m3fnuz(0x08, fp8e4m3fnuz::from_bits()); } static constexpr __device__ fp8e4m3fnuz lowest() { return fp8e4m3fnuz(0xFF, fp8e4m3fnuz::from_bits()); } }; template <> class numeric_limits { public: static constexpr bool has_infinity = false; static constexpr __device__ fp8e4m3fn epsilon() { return fp8e4m3fn(0x20, fp8e4m3fn::from_bits()); } // NOLINTNEXTLINE static constexpr __device__ fp8e4m3fn quiet_NaN() { return fp8e4m3fn(0x7F, fp8e4m3fn::from_bits()); } static constexpr __device__ fp8e4m3fn max() { return fp8e4m3fn(0x7E, fp8e4m3fn::from_bits()); } // this is min value that is not DeNormalized(DeNorm). DeNorm min is 0x01 static constexpr __device__ fp8e4m3fn min() { return fp8e4m3fn(0x08, fp8e4m3fn::from_bits()); } static constexpr __device__ fp8e4m3fn lowest() { return fp8e4m3fn(0xFE, fp8e4m3fn::from_bits()); } }; template <> class numeric_limits { public: static constexpr bool has_infinity = false; static constexpr __device__ fp8e5m2fnuz epsilon() { return fp8e5m2fnuz(0x34, fp8e5m2fnuz::from_bits()); } static constexpr __device__ fp8e5m2fnuz quiet_NaN() // NOLINT { return fp8e5m2fnuz(0x80, fp8e5m2fnuz::from_bits()); } static constexpr __device__ fp8e5m2fnuz max() { return fp8e5m2fnuz(0x7F, fp8e5m2fnuz::from_bits()); } // this is min value that is not DeNormalized(DeNorm). DeNorm min is 0x01. static constexpr __device__ fp8e5m2fnuz min() { return fp8e5m2fnuz(0x4, fp8e5m2fnuz::from_bits()); } static constexpr __device__ fp8e5m2fnuz lowest() { return fp8e5m2fnuz(0xFF, fp8e5m2fnuz::from_bits()); } }; template <> class numeric_limits { public: static constexpr bool has_infinity = true; static constexpr __device__ fp8e5m2 epsilon() { return fp8e5m2(0x34, fp8e5m2::from_bits()); } // 7D, 7E, 7F are positive NaNs and FD, FE, FF are negative NaNs static constexpr __device__ fp8e5m2 quiet_NaN() // NOLINT { return fp8e5m2(0xFF, fp8e5m2::from_bits()); } static constexpr __device__ fp8e5m2 max() { return fp8e5m2(0x7B, fp8e5m2::from_bits()); } // this is min value that is not DeNormalized(DeNorm). DeNorm min is 0x01. static constexpr __device__ fp8e5m2 min() { return fp8e5m2(0x4, fp8e5m2::from_bits()); } static constexpr __device__ fp8e5m2 lowest() { return fp8e5m2(0xFB, fp8e5m2::from_bits()); } // 7C and FC both are infinity static constexpr __device__ fp8e5m2 infinity() { return fp8e5m2(0x7C, fp8e5m2::from_bits()); } }; } // namespace fp8 template {} or is_same{} or is_same{} or is_same{})> constexpr T numeric_max(migraphx::fp8::f8_type unused = migraphx::fp8::f8_type::fp8) { // unused parameter is added to make this numeric_max different overload definition // compared to numeric_max defined in type_traits.hpp (void)(unused); return fp8::numeric_limits::max(); } template {} or is_same{} or is_same{} or is_same{})> constexpr T numeric_lowest(migraphx::fp8::f8_type unused = migraphx::fp8::f8_type::fp8) { // unused parameter is added to make this numeric_lowest different overload definition // compared to numeric_lowest defined in type_traits.hpp (void)(unused); return fp8::numeric_limits::lowest(); } } // namespace migraphx // ================================================================================================= #if defined(__clang__) #pragma clang diagnostic pop #endif // __clang__ #endif // MIGRAPHX_GUARD_KERNELS_FLOAT8_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/float8_impl.hpp000066400000000000000000000317241510465702400311010ustar00rootroot00000000000000/* ************************************************************************ * Copyright (C) 2016-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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 cop- * ies 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 IM- * PLIED, 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 CONNE- * CTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * ************************************************************************ */ #ifndef MIGRAPHX_GUARD_KERNELS_FP8_IMPL_HPP #define MIGRAPHX_GUARD_KERNELS_FP8_IMPL_HPP #include #include #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreserved-identifier" #endif namespace migraphx { namespace fp8 { namespace impl { // NOLINTBEGIN template __device__ constexpr uint8_t cast_to_f8(T f_x, bool stoch = false, uint32_t rng = 0) { constexpr bool is_float = true; // half is not supported for now constexpr bool is_half = false; static_assert(Wm + We == 7, "Wm+We==7"); static_assert(is_float or is_half, "Only float can be cast to f8"); const uint32_t mfmt = (sizeof(T) == 4) ? 23 : 10; typename migraphx::conditional_t x; if constexpr(sizeof(T) == 4) x = migraphx::bit_cast(f_x); else x = migraphx::bit_cast(f_x); uint32_t head = 0; uint32_t mantissa = 0; int exponent = 0; uint32_t bias = 0; uint32_t sign = 0; if constexpr(sizeof(T) == 4) { head = x & 0xFF800000; mantissa = x & 0x7FFFFF; exponent = (head >> 23) & 0xFF; sign = head >> 31; bias = 127; } else { head = x & 0xFC00; mantissa = x & 0x3FF; exponent = (head >> 10) & 0x1F; sign = head >> 15; bias = 15; } uint32_t signed_inf = (sign << 7) + (((1 << We) - 1) << Wm); uint32_t signed_all_ones = (sign << 7) + ((((1 << We) - 1) << Wm) + ((1 << Wm) - 1)); // Calcualte maximum singed value FLT_MAX, FLT_MIN uint32_t signed_max = signed_all_ones; if(not NegativeZeroNan) signed_max = (Wm == 2) ? (signed_max - 4) : (signed_max - 1); // Deal with inf and NaNs if(NegativeZeroNan) // For the FNUZ cases, it is simple just return NaNs { if((sizeof(T) == 4 and ((x & 0x7F800000) == 0x7F800000)) or (sizeof(T) == 2 and ((x & 0x7C00) == 0x7C00))) return 0x80; } else { // calculate most common NaN mantissa for FP8, which is all Ones in binary uint32_t nan_mantissa = 1; for(auto i = 1; i < Wm; ++i) { nan_mantissa |= (nan_mantissa << 1); } if((sizeof(T) == 4 and ((x & 0x7F800000) == 0x7F800000)) or (sizeof(T) == 2 and ((x & 0x7C00) == 0x7C00))) { // infinity if(mantissa == 0) { if(sign == 0) return (Wm == 2) ? 0x7B : 0x7E; else return (Wm == 2) ? 0xFB : 0xFE; } else // NaNs return signed_inf + nan_mantissa; } } // handle positive zero if(x == 0) return 0; // handle negative zero else if((sizeof(T) == 4 and x == 0x80000000) or (sizeof(T) == 2 and x == 0x8000)) { return NegativeZeroNan ? 0 : 0x80; // For FNUZ types neg zero is just positive zero } /* First need to check if it is normal or denorm as there is a difference of implict 1 Then need to adjust the exponent to align with the F8 exponent, in the meanwhile, shift The mantissa. Then for stochastic rounding, add rng to mantissa and truncate. And for RNE, no need to add rng. Then probably need to check whether there is carry and adjust exponent and mantissa again*/ // For IEEE bias mode, the bias is 2^(k-1) -1 where k is the width of exponent bits const int f8_bias = (1 << (We - 1u)) - 1 + (NegativeZeroNan ? 1 : 0); const int f8_denormal_act_exponent = 1 - f8_bias; // actual exponent of f8 denormal /* act_exponent is the actual exponent of fp32/fp16 (after subtracting bias) f8_exponent is the converted f8 exponent with bias encoding exponent_diff is the diff between fp32/fp16 exponent and f8 exponent, the difference needs to be adjusted and mantissa shifted*/ int act_exponent = 0; int f8_exponent = 0; int exponent_diff = 0; if(exponent == 0 and mantissa != 0) { // fp32/fp16 is in denormal. /* fp32 denormal is below 2^-127 so it is usually not a concern here, we mostly concern fp16 here. In this case, f8 is usually in denormal. But there could be exceptions. fp16 denormal has exponent bias 15 while bf8 with FNUZ has exponent bias 16. It means that there are some numbers in fp16 denormal but they are bf8 (FNUZ) normals - smallest bf8 (FNUZ) normal is 2^-15. fp16 numbers where exponent==0 (actual exponent -14) and highest bit of mantissa is 1 are bf8 (FNUZ) normal. In this case, the fp16 mantissa should be shift left by 1 */ act_exponent = 1 - bias; exponent_diff = f8_denormal_act_exponent - act_exponent; // actual exponent is exponent-bias+1 as it is denormal } else { // fp32/fp16 is normal with implicit 1 act_exponent = exponent - bias; if(act_exponent <= f8_denormal_act_exponent) { /* This is the case where fp32/fp16 is normal but it is in f8 denormal range. For example fp8 FNUZ mode, denormal exponent is -7, but if the fp32/fp16 actual exponent is -7, it is actually larger due to the implict 1, Therefore it needs to be adjust to -6 and mantissa shift right by 1. So for fp32/fp16, exponent -8 is the cut point to convert to fp8 FNUZ */ exponent_diff = f8_denormal_act_exponent - act_exponent; } else { // both fp32/fp16 and f8 are in normal range exponent_diff = 0; // exponent_diff=0 does not mean there is no difference for this case, // act_exponent could be larger. Just that it does not need shift mantissa } mantissa += (1 << mfmt); // Add the implicit 1 into mantissa } // need to know whether the number is right in the middle of two adjacent fp8 numbers. use max // value of 31 to avoid undefined behaviour bool midpoint = (mantissa & ((1u << (mfmt - Wm + exponent_diff)) - 1)) == (1u << (mfmt - Wm + exponent_diff - 1)); /* This part is a bit tricky. The judgment of whether it is a tie needs to be done before we shift right as shift right could rip off some residual part and make something not midpoint look like midpoint. For example, the fp16 number 0x1002 (0 00100 0000000010), it is larger than midpoint, but after shift right by 4 bits, it would look like midpoint. */ if(exponent_diff > 0) mantissa >>= exponent_diff; else if(exponent_diff == -1) mantissa <<= -exponent_diff; bool implicit_one = mantissa & (1 << mfmt); // if there is no implict 1, it means the f8 is denormal and need to adjust to denorm exponent f8_exponent = (act_exponent + exponent_diff) /*actual f8 exponent*/ + f8_bias - (implicit_one ? 0 : 1); // Now we have the exponent and mantissa adjusted uint32_t drop_mask = (1 << (mfmt - Wm)) - 1; bool odd = mantissa & (1 << (mfmt - Wm)); // if the least significant bit that is not truncated is 1 /* This part is doing rounding by adding mantissa part that is going to get dropped. e.g. if the dropped part for less than 0.5 than it would round down. if the dropped part is more than 0.5 then it would round up by rolling carry to LSB of retained mantissa. For the mid point when bit pattern is like this for Odd: `xy1:10000000` for Odd and `xy0:10000000` for the Even. where `:` is delimiter for dropped v/s retained part. For the odd case : this will add xy1:10000000 + 000:10000000 which would roll over carry to LSB of retained part making it RNE. For the even case : this will add xy0:10000000 + 000:01111111 which would round down and keep number Even */ mantissa += (stoch ? rng : (midpoint ? (odd ? mantissa : mantissa - 1) : mantissa)) & drop_mask; // Now we deal with overflow if(f8_exponent == 0 and ((1 << mfmt) & mantissa)) { f8_exponent = 1; // denormal overflow to become normal, promote exponent } else if((1 << (mfmt + 1)) & mantissa) { mantissa >>= 1; f8_exponent++; } mantissa >>= (mfmt - Wm); // above range: quantize to maximum possible float of the same sign // for e5m2 case, max_exp is 14, since exp = 15 is reserved for Infs and Nans const int max_exp = (1 << We) - ((NegativeZeroNan or Wm == 3) ? 1 : 2); if(f8_exponent > max_exp) { if(Clip) return signed_max; else { // https://onnx.ai/onnx/technical/float8.html#cast if(NegativeZeroNan) return 0x80; else return (Wm == 2) ? signed_inf : signed_all_ones; } } if(f8_exponent == 0 and mantissa == 0) return NegativeZeroNan ? 0 : (sign << 7); mantissa &= (1 << Wm) - 1; return (sign << 7) | (f8_exponent << Wm) | mantissa; } // NOLINTEND template __device__ constexpr T cast_from_f8(uint8_t x) { // half is not supported for now constexpr bool is_half = false; constexpr bool is_float = true; static_assert(is_float or is_half, "Only float are supported"); constexpr int weo = is_half ? 5 : 8; constexpr int wmo = is_half ? 10 : (is_float ? 23 : 7); // NOLINTNEXTLINE T f_inf, f_neg_inf, f_nan, f_neg0; if constexpr(is_float) { const uint32_t if_inf = 0x7F800000; const uint32_t if_neg_inf = 0xFF800000; const uint32_t if_nan = 0x7F800001; const uint32_t if_neg0 = 0x80000000; f_inf = migraphx::bit_cast(if_inf); f_neg_inf = migraphx::bit_cast(if_neg_inf); f_nan = migraphx::bit_cast(if_nan); f_neg0 = migraphx::bit_cast(if_neg0); } if(x == 0) return 0; uint32_t sign = x >> 7; // NOLINT uint32_t mantissa = x & ((1 << Wm) - 1); // NOLINT int exponent = (x & 0x7F) >> Wm; // NOLINT if(NegativeZeroNan) { if(x == 0x80) return f_nan; } else { if(x == 0x80) return f_neg0; if(exponent == ((1 << We) - 1) and Wm == 2) // NOLINT return (mantissa == 0) ? (sign ? f_neg_inf : f_inf) : f_nan; else if(Wm == 3 and (x == 0x7F or x == 0xFF)) return f_nan; } typename migraphx::conditional_t retval; const int exp_low_cutoff = (1 << (weo - 1)) - (1 << (We - 1)) + 1 - (NegativeZeroNan ? 1 : 0); // NOLINT // subnormal input if(exponent == 0) { // guaranteed mantissa!=0 since cases 0x0 and 0x80 are handled above int sh = 1 + __builtin_clz(mantissa) - (32 - Wm); mantissa <<= sh; // NOLINT exponent += 1 - sh; mantissa &= ((1 << Wm) - 1); // NOLINT } exponent += exp_low_cutoff - 1; mantissa <<= wmo - Wm; // NOLINT // subnormal output (occurs when T=half, We=5, negative_zero_nan=true) if(exponent <= 0) { mantissa |= 1 << wmo; // NOLINT mantissa >>= 1 - exponent; // NOLINT exponent = 0; } if(sizeof(T) == 2) retval = (sign << 15) | (exponent << 10) | mantissa; // NOLINT else retval = (sign << 31) | (exponent << 23) | mantissa; // NOLINT return migraphx::bit_cast(retval); } } // namespace impl } // namespace fp8 } // namespace migraphx #if defined(__clang__) #pragma clang diagnostic pop #endif #endif // MIGRAPHX_GUARD_KERNELS_FP8_IMPL_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/float_equal.hpp000066400000000000000000000031071510465702400311510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_FLOAT_EQUAL_HPP #define MIGRAPHX_GUARD_KERNELS_FLOAT_EQUAL_HPP #include namespace migraphx { template constexpr bool float_equal(T x, U y) { if constexpr(is_integral{} and is_integral{}) return x == y; return not(x < y or x > y); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_FLOAT_EQUAL_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/fp4_casts.hpp000066400000000000000000000102541510465702400305440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_FP4_CASTS_HPP #define MIGRAPHX_GUARD_KERNELS_FP4_CASTS_HPP #include #include #include #include #include namespace migraphx { namespace fp4_detail { static constexpr array fp4_lut = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f, -0.0f, -0.5f, -1.0f, -1.5f, -2.0f, -3.0f, -4.0f, -6.0f}; // pair is {fp4_tie_value, round_to_zero} // if round_to_zero round tie towards zero, else round tie away from zero static constexpr array, 7> fp4_even_round = {make_tuple(0.25, 1), make_tuple(0.75, 0), make_tuple(1.25, 1), make_tuple(1.75, 0), make_tuple(2.5, 1), make_tuple(3.5, 0), make_tuple(5, 1)}; } // namespace fp4_detail // NOTE: possible to remove float/T casts by making LUTs for each type // converts 4 LSB to float template __device__ constexpr T cast_from_fp4(uint8_t x) { return T(fp4_detail::fp4_lut[x % fp4_detail::fp4_lut.size()]); } // rounding mode = roundToNearestRoundTiesToEven template __device__ inline uint8_t cast_to_fp4(T x) { float f_x = float(x); using fp4_detail::fp4_even_round; using fp4_detail::fp4_lut; if(isnan(f_x)) { return 0; } bool sign = signbit(f_x); uint8_t sign_add = sign ? fp4_lut.size() / 2 : 0u; float abs_f = abs(f_x); // index value is the positive fp4 value uint8_t i = migraphx::upper_bound(fp4_even_round.begin(), fp4_even_round.end(), migraphx::make_tuple(abs_f, uint8_t{0}), [&](const auto& a, const auto& b) { return a < b; }) - fp4_even_round.begin(); return i + sign_add; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_FP4_CASTS_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/functional.hpp000066400000000000000000000251231510465702400310210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_FUNCTIONAL_HPP #define MIGRAPHX_GUARD_KERNELS_FUNCTIONAL_HPP #include // Similiar to decltype(auto) except it will propagate any substitution failures // NOLINTNEXTLINE #define MIGRAPHX_RETURNS(...) \ ->decltype(__VA_ARGS__) { return __VA_ARGS__; } // Lifts an expression into a function object so it can be passed to a higher-order function // NOLINTNEXTLINE #define MIGRAPHX_LIFT(...) \ [](auto&&... private_lifts_xs) MIGRAPHX_RETURNS( \ (__VA_ARGS__)(static_cast(private_lifts_xs)...)) // NOLINTNEXTLINE #define MIGRAPHX_LIFT_CLASS(name, ...) \ struct name \ { \ template \ constexpr auto operator()(PrivateLiftTs&&... private_lifts_xs) const MIGRAPHX_RETURNS( \ (__VA_ARGS__)(static_cast(private_lifts_xs)...)) \ } namespace migraphx { struct swallow { template constexpr swallow(Ts&&...) { } }; template using ignore = swallow; template struct overloaded : Fs... { using Fs::operator()...; constexpr overloaded(Fs... fs) : Fs(fs)... {} }; template constexpr overloaded overload(Fs... fs) { return {fs...}; } namespace detail { template struct eval_helper { R result; template constexpr eval_helper(const F& f, Ts&&... xs) : result(f(static_cast(xs)...)) { } }; template <> struct eval_helper { int result; template constexpr eval_helper(const F& f, Ts&&... xs) : result((f(static_cast(xs)...), 0)) { } }; template struct seq { using type = seq; }; template struct merge_seq; template struct merge_seq, seq> : seq { }; template struct gens : merge_seq::type, typename gens::type> { }; template <> struct gens<0> : seq<> { }; template <> struct gens<1> : seq<0> { }; template constexpr auto sequence_c_impl(F&& f, seq) { return f(index_constant{}...); } template constexpr auto args_at(seq) { return [](ignore..., auto x, auto...) { return x; }; } } // namespace detail template constexpr auto always(T x) { return [=](auto&&...) { return x; }; } template constexpr auto sequence_c(F&& f) { return detail::sequence_c_impl(f, detail::gens{}); } template constexpr auto sequence(IntegerConstant ic, F&& f) { return sequence_c(f); } template constexpr auto by(F f, G g) { return [=](auto... xs) { return detail::eval_helper{g, f(xs)...}.result; }; } template constexpr auto by(F f) { return by([=](auto x) { return (f(x), 0); }, always(0)); } template constexpr void each_args(F f, Ts&&... xs) { swallow{(f(static_cast(xs)), 0)...}; } template constexpr void each_args(F) { } template constexpr void unpack_each(F f) { f(); } template constexpr void unpack_each(F f, Pack p) { p([&](auto&&... xs) { each_args(f, static_cast(xs)...); }); } template constexpr void unpack_each(F f, Pack1 p1, Pack2 p2) { p1([&](auto&&... xs) { p2([&](auto&&... ys) { each_args( [&](auto&& p) { p(f); }, pack_forward(static_cast(xs), static_cast(ys))...); }); }); } template constexpr void unpack_each(F f, Pack1 p1, Pack2 p2, Packs... packs) { unpack_each( [&](auto&& x, auto&& y) { unpack_each( [&](auto&&... zs) { f(static_cast(x), static_cast(y), static_cast(zs)...); }, packs...); }, p1, p2); } template constexpr void repeat_c(F&& f) { sequence_c([&](auto... xs) { each_args(f, xs...); }); } template constexpr auto repeat(IntegerConstant ic, F&& f) { return repeat_c(f); } template constexpr void repeat_up_by_2_c(F&& f) { if constexpr(Start < Last) { f(_c); repeat_up_by_2_c(static_cast(f)); } } template constexpr void repeat_up_by_2_c(F&& f) { repeat_up_by_2_c<1, Last>(static_cast(f)); } template constexpr void repeat_down_by_2_c(F&& f) { if constexpr(Start >= Last) { f(_c); repeat_down_by_2_c(static_cast(f)); } } template constexpr void repeat_down_by_2_c(F&& f) { repeat_down_by_2_c(static_cast(f)); } template constexpr auto fold_impl(F&&, T&& x) { return static_cast(x); } template constexpr auto fold_impl(F&& f, T&& x, U&& y, Ts&&... xs) { return fold_impl(f, f(static_cast(x), static_cast(y)), static_cast(xs)...); } template constexpr auto fold(F f) { return [=](auto&&... xs) { return fold_impl(f, static_cast(xs)...); }; } template constexpr auto compose(Fs... fs) { return fold([](auto f, auto g) { return [=](auto&&... xs) { return f(g(static_cast(xs)...)); }; })(fs...); } template constexpr auto partial(F f) { return [=](auto... xs) { return [=](auto&&... ys) { return f(xs..., static_cast(ys)...); }; }; } template constexpr auto pack(Ts... xs) { return [=](auto f) { return f(xs...); }; } template constexpr auto pack_forward(Ts&&... xs) { return [&](auto f) { return f(static_cast(xs)...); }; } template constexpr auto join(G g, F f) { return f([=](auto... xs) { return g(xs...); }); } template constexpr auto join(G g, F f, Fs... fs) { // return f1([=](auto x) { return f2([=](auto y) { return g(x, y); }); }); return f([=](auto... xs) { return join([=](auto... ys) { return g(xs..., ys...); }, fs...); }); } template constexpr auto pack_compare(Compare compare, P1 p1, P2 p2) { return p1([&](auto... xs) { return p2([&](auto... ys) { auto c = [&](auto x, auto y) -> int { if(compare(x, y)) return 1; else if(compare(y, x)) return -1; else return 0; }; return fold([](auto x, auto y) { return x ? x : y; })(c(xs, ys)..., 0); }); }); } template constexpr auto arg_c() { return [](auto... xs) { return detail::args_at(detail::gens{})(xs...); }; } template constexpr auto arg(IntegralConstant ic) { return arg_c(); } template constexpr auto make_transform(F f) { return [=](auto... xs) { return [=](auto g) { return f(g, xs...); }; }; } // An arg transformation takes the arguments and then a function to take the new arguments: // transform(xs...)([](auto... ys) { ... }) // The transform_args function takes a list of transformations and continually applies them template constexpr auto transform_args(F f) { return f; } template constexpr auto transform_args(F f, Fs... fs) { return make_transform([=](auto g, auto... xs) { return f(xs...)([=](auto... ys) { return transform_args(fs...)(ys...)(g); }); }); } // identity transform constexpr auto transform_args() { return make_transform([](auto f, auto... xs) { return f(xs...); }); } // Rotate the last N arguments to the first N arguments template constexpr auto rotate_last() { return make_transform([](auto f, auto... xs) { return sequence_c([&](auto... is) { constexpr auto size = sizeof...(is); return f(arg_c<(is + size - N) % size>()(xs...)...); }); }); } constexpr auto rotate_last() { return rotate_last<1>(); } // Pack the first N arguments template constexpr auto pack_first() { return make_transform([](auto f, auto... xs) { return sequence_c([&](auto... is) { return sequence_c([&](auto... js) { return f(pack(arg_c()(xs...)...), arg_c()(xs...)...); }); }); }); } // Rotate the last N arguments as the first argument packed template constexpr auto rotate_and_pack_last() { return transform_args(rotate_last(), pack_first()); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_FUNCTIONAL_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/gather.hpp000066400000000000000000000044671510465702400301410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_GATHER_HPP #define MIGRAPHX_GUARD_KERNELS_GATHER_HPP #include #include #include #include namespace migraphx { template constexpr auto gather_shape(Input input, Indices indices) { auto lengths = input.lens; lengths[Axis] = indices.elements(); return make_shape(lengths, input.strides); } template __device__ void gather(Input input, Indices indices, Output output) { auto ind = make_index(); auto axis_dim_size = input.get_shape().lens[Axis]; constexpr auto out_comp = gather_shape(get_shape_c{}, get_shape_c{}); ind.global_stride(output.get_shape().elements(), [&](auto i) { auto idx = out_comp.multi(i); auto in_index = indices[idx[Axis]]; auto new_in_index = (in_index < 0) ? in_index + axis_dim_size : in_index; idx[Axis] = new_in_index; output[i] = input[idx]; }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/gathernd.hpp000066400000000000000000000101611510465702400304470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_GATHERND_HPP #define MIGRAPHX_GUARD_KERNELS_GATHERND_HPP #include #include #include namespace migraphx { template struct gathernd_settings { T batch_dims{}; }; template constexpr gathernd_settings make_gathernd_settings(Ts... xs) { return {xs...}; } template __device__ void gathernd(const T& data_t, const U& indices_t, const V& output_t, Settings s) { auto ind = make_index(); auto batch_dims = s.batch_dims; auto output_shape = output_t.get_shape(); auto indices_shape = indices_t.get_shape(); auto data_shape = data_t.get_shape(); auto indices_shape_lens = indices_shape.lens; auto data_shape_lens = data_shape.lens; auto num_slice_dims = indices_shape_lens.back(); size_t num_slices = accumulate(indices_shape_lens.begin(), indices_shape_lens.end() - 1, 1, op::product{}); size_t slice_size = accumulate(data_shape_lens.begin() + num_slice_dims + batch_dims, data_shape_lens.end(), 1, op::product{}); const size_t num_batches = accumulate(data_shape_lens.begin(), data_shape_lens.begin() + batch_dims, 1, op::product{}); const size_t data_batch_stride = accumulate(data_shape_lens.begin() + batch_dims, data_shape_lens.end(), 1, op::product{}); const auto num_slices_per_batch = num_slices / num_batches; ind.global_stride(output_shape.elements(), [&](auto i) { const auto* indices_ptr = indices_t.data(); const size_t j = i / slice_size; const size_t batch_idx = j / num_slices_per_batch; auto* slice_indices = indices_ptr + (j * num_slice_dims); size_t relative_slice_offset = 0; for(size_t idx = 0; idx < num_slice_dims; ++idx) { int64_t index = slice_indices[idx]; const size_t input_dim_idx = batch_dims + idx; const auto input_dim = data_shape_lens[input_dim_idx]; MIGRAPHX_ASSERT(index >= -static_cast(input_dim) and index < static_cast(input_dim)); if(index < 0) index += input_dim; size_t size_from_slice_dims = accumulate(data_shape_lens.begin() + batch_dims + idx + 1, data_shape_lens.begin() + batch_dims + num_slice_dims, slice_size, op::product{}); relative_slice_offset += index * size_from_slice_dims; } auto slice_offset = (batch_idx * data_batch_stride) + relative_slice_offset; output_t[i] = data_t[slice_offset + i % slice_size]; }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/gemm_batcher.hpp000066400000000000000000000073711510465702400313010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_GEMM_BATCHER_HPP #define MIGRAPHX_GUARD_KERNELS_GEMM_BATCHER_HPP #include #include #include namespace migraphx { template constexpr auto gemm_get_batches() { constexpr auto lens = get_shape_c{}.lens; constexpr auto strides = get_shape_c{}.strides; constexpr auto new_lens = sequence( lens.size() - _c<2>, [&](auto... is) { return make_const_array(_c...); }); constexpr auto new_strides = sequence( strides.size() - _c<2>, [&](auto... is) { return make_const_array(_c...); }); return make_shape(new_lens, new_strides); } template constexpr auto gemm_get_matrix() { constexpr auto lens = get_shape_c{}.lens; constexpr auto strides = get_shape_c{}.strides; constexpr auto m = lens.size() - _c<2>; constexpr auto n = lens.size() - _c<1>; constexpr auto new_lens = make_const_array(_c, _c); constexpr auto new_strides = make_const_array(_c, _c); return make_shape(new_lens, new_strides); } template constexpr auto gemm_batch_slice(Tensor t, T i) { constexpr auto batch = gemm_get_batches(); constexpr auto matrix = gemm_get_matrix(); MIGRAPHX_ASSERT((batch.index(i) + matrix.element_space()) <= t.get_shape().element_space()); return make_tensor_view(t.data() + batch.index(i), matrix); } template constexpr auto gemm_batch_args(index idx, BlocksPerBatch bpb, T x, Ts... xs) { return [=](auto f) { // All tensors should have the same rank static_assert( (true and ... and (get_shape_c{}.lens.size() == get_shape_c{}.lens.size()))); if constexpr(get_shape_c{}.lens.size() > 2) { // Get the first batch since all batches should have the same number of elements constexpr auto batch = gemm_get_batches(); static_assert( (true and ... and (batch.elements() == gemm_get_batches().elements()))); idx.group_stride(bpb * batch.elements(), [&](auto gidx) { const auto batch_idx = gidx / bpb; f(gemm_batch_slice(x, batch_idx), gemm_batch_slice(xs, batch_idx)...); }); } else { f(x, xs...); } }; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_GEMM_BATCHER_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/generic_constant.hpp000066400000000000000000000043521510465702400322050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_GENERIC_CONSTANT_HPP #define MIGRAPHX_GUARD_KERNELS_GENERIC_CONSTANT_HPP namespace migraphx { template struct generic_constant { static constexpr auto value = F{}(); using value_type = decltype(value); using type = generic_constant; constexpr operator value_type() const noexcept { return value; } constexpr value_type operator()() const noexcept { return value; } }; template constexpr generic_constant make_generic_constant(F) { return {}; } // NOLINTNEXTLINE #define MIGRAPHX_MAKE_CONSTANT(x) \ make_generic_constant([] { \ struct fun \ { \ constexpr auto operator()() const { return x; } \ }; \ return fun{}; \ }()) } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_GENERIC_CONSTANT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/gqa_rotary_embedding.hpp000066400000000000000000000175651510465702400330400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_ROTARY_EMBEDDING_HPP #define MIGRAPHX_GUARD_KERNELS_ROTARY_EMBEDDING_HPP #include #include #include namespace migraphx { template __device__ void run_rotary_embedding(Input input, CosCache cos_cache, SinCache sin_cache, Output output, PosIDs pos_ids, Params params, index_int idx, bool is_query = false) { const index_int batch_size = params.batch_size; const index_int sequence_length = params.sequence_length; const index_int n_heads = is_query ? params.num_heads : params.kv_num_heads; const index_int head_size = params.head_size; const index_int head_stride = params.head_stride; const index_int seq_stride = params.seq_stride; const index_int batch_stride = params.batch_stride; const int position_ids_format = params.position_ids_format; const index_int rotary_emb_dim = params.rotary_embedding_dim; const index_int half_rotary_emb_dim = rotary_emb_dim / 2; const index_int loop_len = batch_size * sequence_length * n_heads; const index_int i = idx / head_size; const index_int ii = idx % head_size; if(i < loop_len) { const index_int b = (i / n_heads) / sequence_length; const index_int s = (i / n_heads) % sequence_length; const index_int n = i % n_heads; const index_int block_offset = b * batch_stride + s * seq_stride + n * head_stride; auto input_data = input + block_offset; auto output_data = output + block_offset; // Cache is (M, H/2) or (M, rotary_embedding_dim/2) int position_id = (position_ids_format == 0) ? static_cast(pos_ids[0]) + s : static_cast(pos_ids[b * sequence_length + s]); position_id = (sequence_length == 1) ? position_id : s; const index_int cache_offset = position_id * half_rotary_emb_dim; auto cos_data = cos_cache + cache_offset; auto sin_data = sin_cache + cache_offset; int cache_idx = 0; double sign = 0.0; int j = 0; if(ii < rotary_emb_dim) { if(params.rotary_interleaved) { cache_idx = (ii / 2) % half_rotary_emb_dim; sign = (ii % 2 == 0) ? -1.0 : 1.0; j = (ii % 2 == 0) ? ii + 1 : ii - 1; // i - sign } else { cache_idx = ii % half_rotary_emb_dim; sign = (ii < half_rotary_emb_dim) ? -1.0 : 1.0; j = (ii + half_rotary_emb_dim) % rotary_emb_dim; } double out_data = static_cast(input_data[ii]) * static_cast(cos_data[cache_idx]) + sign * static_cast(input_data[j]) * static_cast(sin_data[cache_idx]); output_data[ii] = out_data; } else if(ii < head_size) { output_data[ii] = input_data[ii]; } } } template __device__ void pack_v_into_rotary_qkv(Params params, const Input input, Output output, index_int idx) { const index_int loop_len = params.batch_size * params.sequence_length * params.kv_num_heads; auto i = idx / params.head_size; auto ii = idx % params.head_size; if(i < loop_len) { const index_int b = (i / params.kv_num_heads) / params.sequence_length; const index_int s = (i / params.kv_num_heads) % params.sequence_length; const index_int n = i % params.kv_num_heads; const index_int block_offset = b * params.batch_stride + s * params.seq_stride + n * params.head_stride; const Input input_data = input + block_offset; Output output_data = output + block_offset; if(ii < params.head_size) { output_data[ii] = input_data[ii]; } } } template __device__ void gqa_rotary_embedding(Output output, Query query, SeqLensK seqlens_k, CosCache cos_cache, SinCache sin_cache, Params params) { auto ind = make_index(); ind.global_stride(output.get_shape().elements(), [&](auto idx) { auto q_input = query.begin(); auto q_rotary = output.begin(); auto k_input = q_input + params.num_heads * params.sequence_length * params.head_size; auto k_rotary = q_rotary + params.num_heads * params.sequence_length * params.head_size; auto v_input = k_input + params.kv_num_heads * params.sequence_length * params.head_size; auto v_rotary = k_rotary + params.kv_num_heads * params.sequence_length * params.head_size; auto q_chunk_size = params.batch_size * params.num_heads * params.sequence_length * params.head_size; auto kv_chunk_size = params.batch_size * params.kv_num_heads * params.sequence_length * params.head_size; if(idx < q_chunk_size) { run_rotary_embedding(q_input, cos_cache.begin(), sin_cache.begin(), q_rotary, seqlens_k.begin(), params, idx, true); } else if(idx < q_chunk_size + kv_chunk_size) { run_rotary_embedding(k_input, cos_cache.begin(), sin_cache.begin(), k_rotary, seqlens_k.begin(), params, idx - q_chunk_size); } else if(idx < output.get_shape().elements()) { pack_v_into_rotary_qkv(params, v_input, v_rotary, idx - (q_chunk_size + kv_chunk_size)); } }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/group_query_attention.hpp000066400000000000000000000066171510465702400333340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_GROUP_QUERY_ATTENTION_HPP #define MIGRAPHX_GUARD_KERNELS_GROUP_QUERY_ATTENTION_HPP #include #include #include #include #include namespace migraphx { template struct gqa_parameters { T1 scale; T2 batch_size; // Batch size used by input T3 sequence_length; // Sequence length used by input T4 hidden_size; // Hidden size used by input T5 head_size; // Head size T6 rotary_embedding_dim; // Rotary embedding dimension. T7 num_heads; // num_heads = hidden_size / head_size T8 max_sequence_length; // Sequence length used by cos/sin cache T9 head_stride; // Head stride T10 seq_stride; // Sequence stride T11 batch_stride; // Batch stride T12 position_ids_format; // Format of position ids - 0 is (1), 1 is (batch_size, // sequence_length) T13 seqlen_present_kv_cache; // Sequence length of present kv-cache (4096 when using // shared buffer) T14 do_rotary; // Whether to use rotary position embedding. Default value is 0. T15 kv_num_heads; // Number of attention heads for k and v T16 local_window_size; // left_window_size for local attention. Default value is -1 meaning // unused. T17 rotary_interleaved; // Rotate using interleaved pattern. Default value is 0 (False). T18 past_present_share_buffer; // Whether to use same buffer for KV-cache inputs and outputs }; template __device__ gqa_parameters make_gqa_parameters(Ts... ts) { return {ts...}; } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/hip.hpp000066400000000000000000000026271510465702400274430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_HIP_HPP #define MIGRAPHX_GUARD_KERNELS_HIP_HPP #ifndef MIGRAPHX_USE_HIPRTC #include #include #include #endif #endif // MIGRAPHX_GUARD_KERNELS_HIP_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/index.hpp000066400000000000000000000236651510465702400277770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_INDEX_HPP #define MIGRAPHX_GUARD_KERNELS_INDEX_HPP #include #include #include #include #include #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreserved-identifier" extern "C" __device__ size_t __ockl_get_enqueued_local_size(uint); // NOLINT extern "C" __device__ size_t __ockl_get_local_size(uint); // NOLINT #pragma clang diagnostic pop #endif namespace migraphx { #if defined(MIGRAPHX_NGLOBAL) && defined(MIGRAPHX_NLOCAL) #define MIGRAPHX_NGROUP ((MIGRAPHX_NGLOBAL + MIGRAPHX_NLOCAL - 1) / MIGRAPHX_NLOCAL) #endif inline __device__ __attribute__((const)) index_int compute_global_size() { #ifdef MIGRAPHX_NGLOBAL return MIGRAPHX_NGLOBAL; #else // This actually works even when global is not divisible by local size. // This doesnt actually do a multiplication. Instead it calls a device // function to get the global size, which is why it works. return blockDim.x * gridDim.x; // NOLINT #endif } #ifdef MIGRAPHX_NGROUP // If global is divisible by local then local can be a const #if(MIGRAPHX_NGLOBAL % MIGRAPHX_NLOCAL == 0) || (MIGRAPHX_NGROUP == 1) #define MIGRAPHX_HAS_CONST_LOCAL 1 #endif #endif inline __device__ __attribute__((const)) index_int compute_local_size() { #ifdef MIGRAPHX_HAS_CONST_LOCAL return MIGRAPHX_NLOCAL; #else // Returns block size. For the non-uniform block it returns the size of the non-uniform block. return __ockl_get_local_size(0); // NOLINT #endif } inline __device__ __attribute__((const)) index_int compute_max_local_size() { #ifdef MIGRAPHX_LOCAL return MIGRAPHX_NLOCAL; #else // Returns the block size. When workgrop has non-uniform block, this returns size of the uniform // block. return __ockl_get_enqueued_local_size(0); // NOLINT #endif } struct index { index_int global = 0; index_int local = 0; index_int group = 0; #ifdef MIGRAPHX_NGLOBAL constexpr index_constant nglobal() const { static_assert(MIGRAPHX_NGLOBAL > 0, "Global size must be greater than 0"); return {}; } #else __device__ index_int nglobal() const { MIGRAPHX_ASSERT(compute_global_size() > 0); return compute_global_size(); // NOLINT } #endif #ifdef MIGRAPHX_HAS_CONST_LOCAL constexpr index_constant nlocal() const { static_assert(MIGRAPHX_NLOCAL > 0, "Local size must be greater than 0"); return {}; } #else __device__ index_int nlocal() const { #ifdef MIGRAPHX_NGROUP static_assert((MIGRAPHX_NGLOBAL % MIGRAPHX_NLOCAL != 0) and (MIGRAPHX_NGROUP > 1), "Local size should be const"); #endif MIGRAPHX_ASSERT(compute_local_size() > 0); return compute_local_size(); // NOLINT } #endif #ifdef MIGRAPHX_NLOCAL constexpr index_constant max_nlocal() const { return {}; } #else __device__ index_int max_nlocal() const { MIGRAPHX_ASSERT(compute_max_local_size() > 0); return compute_max_local_size(); } #endif constexpr auto ngroup() const { return nglobal() / max_nlocal(); } template constexpr index_constant nlocal_subwave() const { return {}; } template constexpr auto local_subwave() const { #ifdef MIGRAPHX_HAS_CONST_LOCAL if constexpr(decltype(nlocal()){} == SubWaveSize) return local; #endif return local % nlocal_subwave(); } template constexpr auto nwave() const { return max_nlocal() / nlocal_subwave(); } constexpr index_constant nlocal_wave() const { return {}; } constexpr auto local_wave() const { return local % nlocal_wave(); } constexpr auto nwave() const { return max_nlocal() / nlocal_wave(); } constexpr auto wave() const { return local / nlocal_wave(); } template static constexpr auto max_stride_iterations(N n, Stride stride) { return (n - _c<1>) / stride + _c<1>; } template constexpr auto max_global_stride_iterations(N n) const { return max_stride_iterations(n, nglobal()); } template constexpr auto max_local_stride_iterations(N n) const { return max_stride_iterations(n, nlocal()); } template constexpr auto max_local_wave_stride_iterations(N n) const { return max_stride_iterations(n, nlocal_wave()); } template constexpr auto max_local_subwave_stride_iterations(N n) const { return max_stride_iterations(n, nlocal_subwave()); } template static constexpr auto invoke_loop(F f, I i, D d) -> decltype(f(i, d)) { return f(i, d); } template static constexpr auto invoke_loop(F f, I i, D) -> decltype(f(i)) { return f(i); } template static constexpr void for_stride_loop_unroll(index_int start, N n, Stride stride, F f) { sequence(max_stride_iterations(n, stride), [&](auto... ks) { fold([&](auto d, auto k) { auto i = start + stride * k; if(i < n) invoke_loop(f, i, d); return d + _c<1>; })(_c<0>, ks...); }); } template static constexpr void for_stride_loop(index_int start, N n, Stride stride, F f) { index_int k = 0; for(index_int i = start; i < n; i += stride) { invoke_loop(f, i, k); k++; } } template static constexpr void for_stride(index_int start, N n, Stride stride, F f) { MIGRAPHX_ASSERT(start < stride); if constexpr(not is_integral{} and n < 1) { return; } else if constexpr(not is_integral{} and not is_integral{}) { if constexpr(max_stride_iterations(n, stride) == 1) { if constexpr(stride > n) { if(start < n) invoke_loop(f, start, _c<0>); } else { invoke_loop(f, start, _c<0>); } } else if constexpr(Unroll) { MIGRAPHX_STATIC_ASSERT_FOR(max_stride_iterations(n, stride) < 256) { for_stride_loop_unroll(start, n, stride, f); } } else { for_stride_loop(start, n, stride, f); } } else { for_stride_loop(start, n, stride, f); } } template __device__ void global_stride(N n, F f) const { for_stride(global, n, nglobal(), f); } template __device__ void local_stride(N n, F f) const { for_stride(local, n, nlocal(), f); } template __device__ void group_stride(N n, F f) const { for_stride(group, n, ngroup(), f); } template __device__ void local_subwave_stride(N n, F f) const { for_stride(local_subwave(), n, nlocal_subwave(), f); } template __device__ void local_wave_stride(N n, F f) const { for_stride(local_wave(), n, nlocal_wave(), f); } }; #ifdef MIGRAPHX_NLOCAL #define MIGRAPHX_GLOBAL \ __global__ __attribute__((amdgpu_flat_work_group_size(MIGRAPHX_NLOCAL, MIGRAPHX_NLOCAL))) #else #define MIGRAPHX_GLOBAL __global__ #endif inline __device__ __attribute__((const)) index make_index() { return index{ blockIdx.x * compute_max_local_size() + threadIdx.x, threadIdx.x, blockIdx.x}; // NOLINT } struct per_block { index idx; constexpr auto local() const { return idx.local; } constexpr auto nlocal() const { return idx.nlocal(); } constexpr auto size() const { return idx.ngroup(); } template constexpr void group_stride(N n, F f) const { return idx.group_stride(n, f); } template constexpr void local_stride(N n, F f) const { return idx.local_stride(n, f); } }; } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_INDEX_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/integral_constant.hpp000066400000000000000000000116411510465702400323750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_INTEGRAL_CONSTANT_HPP #define MIGRAPHX_GUARD_KERNELS_INTEGRAL_CONSTANT_HPP #include namespace migraphx { template struct integral_constant { static constexpr T value = V; using value_type = T; using type = integral_constant; constexpr operator value_type() const noexcept { return value; } constexpr value_type operator()() const noexcept { return value; } static constexpr type to() { return {}; } }; // NOLINTNEXTLINE #define MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(op) \ template \ constexpr inline integral_constant operator op( \ integral_constant, integral_constant) noexcept \ { \ return {}; \ } // NOLINTNEXTLINE #define MIGRAPHX_INTEGRAL_CONSTANT_UNARY_OP(op) \ template \ constexpr inline integral_constant operator op( \ integral_constant) noexcept \ { \ return {}; \ } MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(+) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(-) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(*) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(/) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(%) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(>>) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(<<) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(>) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(<) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(<=) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(>=) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(==) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(!=) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(&) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(^) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(|) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(and) MIGRAPHX_INTEGRAL_CONSTANT_BINARY_OP(or) MIGRAPHX_INTEGRAL_CONSTANT_UNARY_OP(not ) MIGRAPHX_INTEGRAL_CONSTANT_UNARY_OP(~) MIGRAPHX_INTEGRAL_CONSTANT_UNARY_OP(+) MIGRAPHX_INTEGRAL_CONSTANT_UNARY_OP(-) template constexpr integral_constant where(integral_constant, integral_constant, integral_constant) { return {}; } template constexpr auto min(integral_constant a, integral_constant b) { return where(a < b, a, b); } template constexpr auto max(integral_constant a, integral_constant b) { return where(a < b, b, a); } template constexpr integral_constant min(integral_constant, integral_constant) { return {}; } template constexpr integral_constant max(integral_constant, integral_constant) { return {}; } template using bool_constant = integral_constant; using true_type = bool_constant; using false_type = bool_constant; template struct is_integral_constant : false_type { }; template struct is_integral_constant> : true_type { }; template using index_constant = integral_constant; template static constexpr auto _c = integral_constant{}; // NOLINT template constexpr auto return_c(F f) { return _c; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_INTEGRAL_CONSTANT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/iota_iterator.hpp000066400000000000000000000113211510465702400315170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_IOTA_ITERATOR_HPP #define MIGRAPHX_GUARD_KERNELS_IOTA_ITERATOR_HPP #include #include #include namespace migraphx { template struct basic_iota_iterator { Iterator index; F f; using difference_type = diff_int; using reference = decltype(f(declval())); using value_type = remove_reference_t; using pointer = add_pointer_t; constexpr basic_iota_iterator& operator+=(diff_int n) { index += n; return *this; } constexpr basic_iota_iterator& operator-=(diff_int n) { index -= n; return *this; } constexpr basic_iota_iterator& operator++() { index++; return *this; } constexpr basic_iota_iterator& operator--() { index--; return *this; } constexpr basic_iota_iterator operator++(int) // NOLINT { basic_iota_iterator it = *this; index++; return it; } constexpr basic_iota_iterator operator--(int) // NOLINT { basic_iota_iterator it = *this; index--; return it; } // TODO: operator-> constexpr reference operator*() const { return f(index); } constexpr reference operator[](MIGRAPHX_CAPTURE_SOURCE_LOCATION(index_int) x) const { return f(capture_transform(x, [&](auto y) { return index + y; })); } }; template constexpr basic_iota_iterator make_basic_iota_iterator(T x, F f) { return basic_iota_iterator{x, f}; } template constexpr basic_iota_iterator operator+(basic_iota_iterator x, diff_int y) { return x += y; } template constexpr basic_iota_iterator operator+(diff_int x, basic_iota_iterator y) { return y + x; } template constexpr diff_int operator-(basic_iota_iterator x, basic_iota_iterator y) { return x.index - y.index; } template constexpr basic_iota_iterator operator-(basic_iota_iterator x, diff_int y) { return x -= y; } template constexpr bool operator==(basic_iota_iterator x, basic_iota_iterator y) { return x.index == y.index; } template constexpr bool operator!=(basic_iota_iterator x, basic_iota_iterator y) { return x.index != y.index; } template constexpr bool operator<(basic_iota_iterator x, basic_iota_iterator y) { return x.index < y.index; } template constexpr bool operator>(basic_iota_iterator x, basic_iota_iterator y) { return x.index > y.index; } template constexpr bool operator>=(basic_iota_iterator x, basic_iota_iterator y) { return x.index >= y.index; } template constexpr bool operator<=(basic_iota_iterator x, basic_iota_iterator y) { return x.index <= y.index; } struct defaul_iota_iterator { template constexpr auto operator()(T x) const { return x; } }; using iota_iterator = basic_iota_iterator; } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_IOTA_ITERATOR_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/layernorm.hpp000066400000000000000000000103621510465702400306660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_LAYERNORM_HPP #define MIGRAPHX_GUARD_KERNELS_LAYERNORM_HPP #include #include #include #include namespace migraphx { template struct acc_type { using type = float; }; template <> struct acc_type { using type = double; }; template constexpr auto vec_reduce(const array& a, Op op) { return a.apply([&](auto x) { return vec_reduce(x, op); }); } template __device__ void generic_binary_layernorm( F compute, BinOp op, float eps, Output output, Input1 input1, Input2 input2, Inputs... inputs) { using block = reduce::auto_block()>; using reduce_output = reduce::with_axis; block::template run([&](auto, auto r) { using value_type = typename Input1::type; using vec_value_type = typename acc_type>::type; auto input = r.inner([&](auto x1, auto x2) { return migraphx::convert(op(x1, x2)); })(input1, input2); constexpr auto relements = r.template elements(); constexpr auto relements_r = vec_value_type{1.0 / relements}; auto relements_rsqrt = sqrt(relements_r); auto means = r.reduce(op::sum{}, make_array(0, 0), [&](auto x) { auto x_out = x * relements_r; // dividing x by sqrt(relements) before squaring allows computing // higher values before overflow in low precision auto x2_sqrt = x * relements_rsqrt; return make_array(x_out, x2_sqrt * x2_sqrt); })(input); auto mean_x = means[0]; auto mean_x2 = means[1]; auto variance = mean_x2 - (mean_x * mean_x); vec_value_type eps_val = implicit_conversion(eps); auto rsqrt_val = rsqrt(variance + eps_val); r.inner([&](auto& y, auto x, auto... xs) { y = compute(migraphx::convert>((x - mean_x) * rsqrt_val), xs...); })(output, input, inputs...); }); } template __device__ void layernorm(F compute, float eps, Output output, Input input, Inputs... inputs) { generic_binary_layernorm( compute, [](auto x, auto) { return x; }, eps, output, input, input, inputs...); } template __device__ void add_layernorm(F compute, float eps, Output output, Input1 input1, Input2 input2, Inputs... inputs) { generic_binary_layernorm( compute, [](auto x1, auto x2) { return x1 + x2; }, eps, output, input1, input2, inputs...); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_LAYERNORM_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/math.hpp000066400000000000000000000317631510465702400276170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_MATH_HPP #define MIGRAPHX_GUARD_KERNELS_MATH_HPP #include #include #include #include #include #include #include #include namespace migraphx { namespace math { template constexpr auto as_float(T x) { if constexpr(is_integral{}) return x; else return float(x); } template constexpr auto to_native(T x) { return x; } constexpr migraphx::half to_native(__half x) { return bit_cast(x); } template ())> __device__ auto wrap(F f, T x, Ts... xs) { if constexpr(is_integral{}) { return wrap(f, double(x), double(xs)...); } else if constexpr(is_callable{}) { return to_native(f(x, xs...)); } else { T result = f(as_float(x), as_float(xs)...); return result; } } } // namespace math // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_LIFT_IMPL(type, ...) \ [](type x, auto... xs) MIGRAPHX_RETURNS((__VA_ARGS__)(x, xs...)) // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_LIFT(...) MIGRAPHX_DEVICE_MATH_LIFT_IMPL(__VA_ARGS__) // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_PARSE(x) x, // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_EACH(f) MIGRAPHX_DEVICE_MATH_LIFT(MIGRAPHX_DEVICE_MATH_PARSE f) // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_WRAP(name, ...) \ namespace math { \ inline static constexpr auto wrap_##name = \ overload(MIGRAPHX_PP_TRANSFORM_ARGS(MIGRAPHX_DEVICE_MATH_EACH, __VA_ARGS__)); \ } \ template \ auto __device__ name(Ts... xs) MIGRAPHX_RETURNS(math::wrap(math::wrap_##name, xs...)) // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH(name, fname) \ template ())> \ auto __device__ name(Ts... xs) MIGRAPHX_RETURNS(fname(xs...)) // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_VEC(name) \ template ())> \ auto __device__ name(Ts... xs) \ { \ return vec_transform(xs...)([](auto... ys) { return name(ys...); }); \ } // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_FOR(type, name, fname) \ template ())> \ auto __device__ name(type x, Ts... xs) -> type \ { \ return fname(x, xs...); \ } // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_BINARY_FOR(type, name, fname) \ inline auto __device__ name(type x, type y) -> type { return fname(x, y); } // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_HALF2(name, fname) \ template \ auto __device__ name(migraphx::vec x, Ts... xs) \ MIGRAPHX_RETURNS(migraphx::vec{fname(x, xs...)}); \ template 2))> \ auto __device__ name(migraphx::vec x, Ts... xs) \ { \ return vec_packed_transform<2>(x, xs...)( \ [](auto... ys) -> migraphx::vec { return fname(ys...); }); \ } // Template with two overloads for math functions, one for half2 type and one for more generic // vectorization where N is 4 or another even number. // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_MATH_VEC2(type, name, fname) \ template \ auto __device__ name(migraphx::vec x, Ts... xs) \ MIGRAPHX_RETURNS(migraphx::vec{fname(x, xs...)}); \ template 2))> \ auto __device__ name(migraphx::vec x, Ts... xs) \ { \ return vec_packed_transform<2>(x, xs...)( \ [](auto... ys) -> migraphx::vec { return fname(ys...); }); \ } MIGRAPHX_DEVICE_MATH_WRAP(acos, (double)::acos, (float)::acosf); MIGRAPHX_DEVICE_MATH_WRAP(acosh, (double)::acosh, (float)::acoshf); MIGRAPHX_DEVICE_MATH_WRAP(asin, (double)::asin, (float)::asinf); MIGRAPHX_DEVICE_MATH_WRAP(asinh, (double)::asinh, (float)::asinh); MIGRAPHX_DEVICE_MATH_WRAP(atan, (double)::atan, (float)::atan); MIGRAPHX_DEVICE_MATH_WRAP(atanh, (double)::atanh, (float)::atanh); MIGRAPHX_DEVICE_MATH_WRAP(ceil, (double)::ceil, (float)::ceilf, (half)::hceil); MIGRAPHX_DEVICE_MATH_WRAP(cos, (double)::cos, (float)::cosf, (half)::hcos); MIGRAPHX_DEVICE_MATH_WRAP(cosh, (double)::cosh, (float)::coshf); MIGRAPHX_DEVICE_MATH_WRAP(erf, (double)::erf, (float)::erff); MIGRAPHX_DEVICE_MATH_WRAP(exp, (double)::exp, (float)::expf, (half)::hexp); MIGRAPHX_DEVICE_MATH_WRAP(floor, (double)::floor, (float)::floorf, (half)::hfloor); MIGRAPHX_DEVICE_MATH_WRAP(isnan, (double)::isnan, (float)::isnan, (half)::__hisnan); MIGRAPHX_DEVICE_MATH_WRAP(isinf, (double)::isinf, (float)::isinf, (half)::__hisinf); MIGRAPHX_DEVICE_MATH_WRAP(log, (double)::log, (float)::logf, (half)::hlog); MIGRAPHX_DEVICE_MATH_WRAP(log2, (double)::log2, (float)::log2f, (half)::hlog2); MIGRAPHX_DEVICE_MATH_WRAP(nearbyint, (double)::nearbyint, (float)::nearbyintf); MIGRAPHX_DEVICE_MATH_WRAP(pow, (double)::pow, (float)::powf); MIGRAPHX_DEVICE_MATH_WRAP(remainder, (double)::remainder, (float)::remainderf); MIGRAPHX_DEVICE_MATH_WRAP(round, (double)::round, (float)::roundf); MIGRAPHX_DEVICE_MATH_WRAP(rsqrt, (double)::rsqrt, (float)::rsqrtf, (half)::hrsqrt); MIGRAPHX_DEVICE_MATH_WRAP(sin, (double)::sin, (float)::sinf, (half)::hsin); MIGRAPHX_DEVICE_MATH_WRAP(sinh, (double)::sinh, (float)::sinhf); MIGRAPHX_DEVICE_MATH_WRAP(sqrt, (double)::sqrt, (float)::sqrtf, (half)::hsqrt); MIGRAPHX_DEVICE_MATH_WRAP(tan, (double)::tan, (float)::tanf); MIGRAPHX_DEVICE_MATH_WRAP(tanh, (double)::tanh, (float)::tanhf); MIGRAPHX_DEVICE_MATH_WRAP(fmod, (double)::fmod, (float)::fmodf); template constexpr auto where(bool cond, const T& a, const U& b) { return cond ? a : b; } MIGRAPHX_DEVICE_MATH_FOR(float, abs, ::abs) MIGRAPHX_DEVICE_MATH_FOR(double, abs, ::abs) MIGRAPHX_DEVICE_MATH_FOR(migraphx::half, abs, ::__habs) MIGRAPHX_DEVICE_MATH_FOR(migraphx::bf16, abs, ::fabsf) MIGRAPHX_DEVICE_MATH_BINARY_FOR(float, max, ::fmaxf) MIGRAPHX_DEVICE_MATH_BINARY_FOR(float, min, ::fminf) MIGRAPHX_DEVICE_MATH_BINARY_FOR(double, max, ::max) MIGRAPHX_DEVICE_MATH_BINARY_FOR(double, min, ::min) MIGRAPHX_DEVICE_MATH_BINARY_FOR(migraphx::half, max, ::__hmax) MIGRAPHX_DEVICE_MATH_BINARY_FOR(migraphx::half, min, ::__hmin) template () and is_integral{})> constexpr auto abs(const T& a) { return where(a < 0, -a, a); } template ())> constexpr auto max(const T& a, const T& b) { return where(a < b, b, a); } template ())> constexpr auto min(const T& a, const T& b) { return where(a < b, a, b); } template ())> constexpr auto max(const T& a, const T& b, Compare compare) { return where(compare(a, b), b, a); } template ())> constexpr auto min(const T& a, const T& b, Compare compare) { return where(compare(a, b), a, b); } template {} and not is_any_vec())> constexpr auto max(const T& a, const U& b, Compare... compare) { return max>(a, b, compare...); } template {} and not is_any_vec())> constexpr auto min(const T& a, const U& b, Compare... compare) { return min>(a, b, compare...); } template ())> constexpr T mod(const T& a, const T& b) { if constexpr(is_integral{}) // onnx mod operator requires numpy style modulus return ((a % b) + b) % b; return static_cast(fmod(remainder(a, b) + b, b)); } template {} and not is_any_vec())> constexpr auto mod(const T& a, const U& b) { return mod>(a, b); } MIGRAPHX_DEVICE_MATH_VEC(abs) MIGRAPHX_DEVICE_MATH_VEC(acos) MIGRAPHX_DEVICE_MATH_VEC(acosh) MIGRAPHX_DEVICE_MATH_VEC(asin) MIGRAPHX_DEVICE_MATH_VEC(asinh) MIGRAPHX_DEVICE_MATH_VEC(atan) MIGRAPHX_DEVICE_MATH_VEC(atanh) MIGRAPHX_DEVICE_MATH_VEC(ceil) MIGRAPHX_DEVICE_MATH_VEC(cos) MIGRAPHX_DEVICE_MATH_VEC(cosh) MIGRAPHX_DEVICE_MATH_VEC(erf) MIGRAPHX_DEVICE_MATH_VEC(exp) MIGRAPHX_DEVICE_MATH_VEC(floor) MIGRAPHX_DEVICE_MATH_VEC(fmod) MIGRAPHX_DEVICE_MATH_VEC(isinf) MIGRAPHX_DEVICE_MATH_VEC(isnan) MIGRAPHX_DEVICE_MATH_VEC(log) MIGRAPHX_DEVICE_MATH_VEC(log2) MIGRAPHX_DEVICE_MATH_VEC(max) MIGRAPHX_DEVICE_MATH_VEC(min) MIGRAPHX_DEVICE_MATH_VEC(mod) MIGRAPHX_DEVICE_MATH_VEC(nearbyint) MIGRAPHX_DEVICE_MATH_VEC(pow) MIGRAPHX_DEVICE_MATH_VEC(remainder) MIGRAPHX_DEVICE_MATH_VEC(round) MIGRAPHX_DEVICE_MATH_VEC(rsqrt) MIGRAPHX_DEVICE_MATH_VEC(sin) MIGRAPHX_DEVICE_MATH_VEC(sinh) MIGRAPHX_DEVICE_MATH_VEC(sqrt) MIGRAPHX_DEVICE_MATH_VEC(tan) MIGRAPHX_DEVICE_MATH_VEC(tanh) MIGRAPHX_DEVICE_MATH_VEC(where) // Map math functions to hip half2 functions // The half2 type is defined in include/hip/amd_detail/hip_fp16_gcc.h and is 2 16-bit floats // packed into a 32-bit number. See include/hip/amd_detail/hip_fp16_math_fwd.h for the HIP names // Most but not all of these math ops have operators of the same names. MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, abs, ::__habs2) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, ceil, ::h2ceil) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, cos, ::h2cos) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, exp, ::h2exp) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, exp10, ::h2exp10) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, exp2, ::h2exp2) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, floor, ::h2floor) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, isinf, ::__hisinf2) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, isnan, ::__hisnan2) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, log, ::h2log) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, log10, ::h2log10) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, log2, ::h2log2) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, rsqrt, ::h2rsqrt) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, sin, ::h2sin) MIGRAPHX_DEVICE_MATH_VEC2(migraphx::half, sqrt, ::h2sqrt) template constexpr auto convert(U v) { return vec_transform(v)([](auto x) -> T { return static_cast(x); }); } template constexpr auto ceil_div(T x, U y) { return (x + y - _c<1>) / y; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_MATH_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/operators.hpp000066400000000000000000000042651510465702400307010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_OPERATORS_HPP #define MIGRAPHX_GUARD_KERNELS_OPERATORS_HPP #include #include namespace migraphx { // NOLINTNEXTLINE #define MIGRAPHX_DEFINE_OPERATOR(op, expr) \ template \ friend constexpr auto operator op(const T& x, const U& y) MIGRAPHX_RETURNS(expr); \ template {} and is_same{})> \ friend constexpr auto operator op(const U& x, const V& y) MIGRAPHX_RETURNS(expr) template struct equality_comparable { MIGRAPHX_DEFINE_OPERATOR(!=, not(x == y)); }; template struct less_than_comparable { MIGRAPHX_DEFINE_OPERATOR(>, (y < x)); MIGRAPHX_DEFINE_OPERATOR(<=, not(y < x)); MIGRAPHX_DEFINE_OPERATOR(>=, not(x < y)); }; } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_OPERATORS_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/ops.hpp000066400000000000000000000077201510465702400274630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_OPS_HPP #define MIGRAPHX_GUARD_KERNELS_OPS_HPP #include namespace migraphx { namespace op { struct sum { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { return x + y; } }; struct product { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { return x * y; } }; struct id { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x) const { return x; } }; template struct convert_to { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(U x) const { return convert(x); } }; template struct mean { template MIGRAPHX_DEVICE_CONSTEXPR T operator()(T x) const { using type = vec_type; if constexpr(is_floating_point{}) { constexpr type d = 1.0 / N; return x * d; } else { return x / static_cast(N); } } }; struct max { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { return migraphx::max(x, y); } }; struct min { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { return migraphx::min(x, y); } }; struct logical_and { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { if(static_cast(x) and static_cast(y)) return static_cast(1); return static_cast(0); } }; struct logical_or { template MIGRAPHX_DEVICE_CONSTEXPR auto operator()(T x, U y) const { if(static_cast(x) or static_cast(y)) return static_cast(1); return static_cast(0); } }; } // namespace op // NOLINTNEXTLINE #define MIGRAPHX_OPS_DEFINE_COMMON_TYPE(T) \ template \ struct common_type \ { \ using type = U; \ }; \ template \ struct common_type \ { \ using type = U; \ }; struct lowest { template constexpr operator T() const { return numeric_lowest>(); } }; MIGRAPHX_OPS_DEFINE_COMMON_TYPE(lowest) struct highest { template constexpr operator T() const { return numeric_max>(); } }; MIGRAPHX_OPS_DEFINE_COMMON_TYPE(highest) } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_OPS_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/pack_fp4.hpp000066400000000000000000000045451510465702400303530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_PACK_FP4_HPP #define MIGRAPHX_GUARD_KERNELS_PACK_FP4_HPP #include #include #include #include // TODO: use hip_fp4 header // #include namespace migraphx { template __device__ void pack_fp4(Input input, Output output) { const auto output_shape = output.get_shape(); make_index().global_stride(output_shape.elements(), [&](auto i) { auto out_idx = output_shape.multi(i); auto in_idx = out_idx; in_idx[Axis] *= 2; auto inp_val0 = input[in_idx]; in_idx[Axis] += 1; auto inp_val1 = input[in_idx]; uint8_t out_val0 = cast_to_fp4(inp_val0); uint8_t out_val1 = cast_to_fp4(inp_val1); output[out_idx] = static_cast(out_val1 << 4u) | out_val0; // TODO: from hip_fp4 header // auto fp32x2_val = float2{input[idx], input[idx + 1]}; // output[idx] = __hip_cvt_float2_to_fp4x2(fp32x2_val, __HIP_E2M1, __HIP_SATFINITE); }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_PACK_FP4_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/pad.hpp000066400000000000000000000053121510465702400274210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_PAD_HPP #define MIGRAPHX_GUARD_KERNELS_PAD_HPP #include #include #include #include #include namespace migraphx { template __device__ void pad(const index& idx, const Offsets& offsets, const Input& input, Output& output, const PadVal& pad_val) { auto output_shape = output.get_shape(); idx.global_stride(output_shape.elements(), [&](auto i) { // 1. get current multi-index for output // 2. get the size of the input to determine input boundaries // 3. compute the corresponding multi-index for input by accounting for offsets // 4. if current multi-index is within offsets or input's new multi-index is out of bounds, // use pad value instead of input's value auto multi = output_shape.multi(i); auto input_bounds = input.get_shape().lens; auto input_idx = multi - offsets; auto range_multi = range(multi.size()); if(any_of(range_multi.begin(), range_multi.end(), [&](auto j) { return multi[j] < offsets[j] or input_idx[j] >= input_bounds[j]; })) output[multi] = implicit_conversion(pad_val); else output[multi] = implicit_conversion(input[input_idx]); }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/permutation.hpp000066400000000000000000000074641510465702400312360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_PERMUTATION_HPP #define MIGRAPHX_GUARD_KERNELS_PERMUTATION_HPP #include #include #include namespace migraphx { template constexpr auto reorder_dims(const Array1& dims, const Array2& permutation) { return generate_array( dims.size(), [&](auto i) { return dims[permutation[i]]; }); } template constexpr auto reorder_dims(integral_const_array, integral_const_array) { return return_array_c([] { constexpr integral_const_array dims{}; constexpr integral_const_array permutation{}; return reorder_dims(dims.base(), permutation.base()); }); } template constexpr auto invert_permutation(const Array& permutation) { Array result; for(index_int i = 0; i < result.size(); i++) result[permutation[i]] = i; return result; } template constexpr auto invert_permutation(integral_const_array) { return return_array_c([] { constexpr integral_const_array permutation{}; return invert_permutation(permutation.base()); }); } template struct find_permutation_impl { static constexpr auto compute() { return return_array_c([] { constexpr Shape s{}; typename Shape::index_array perm; iota(perm.begin(), perm.end(), 0); if constexpr(s.transposed() or s.broadcasted()) { stable_sort( perm.begin(), perm.end(), by([&](auto x) { return make_tuple(s.strides[x], s.lens[x]); }, greater{})); } return perm; }); } using type = decltype(compute()); }; template constexpr auto find_permutation(Shape) { return typename find_permutation_impl::type{}; } template constexpr auto find_permutation(Shape1, Shape2) { return return_array_c([] { constexpr Shape1 s1{}; constexpr Shape2 s2{}; auto perm1 = find_permutation(s1).base(); auto perm2 = find_permutation(s2).base(); if(perm1 == perm2) return perm1; if(s1.standard()) return perm1; if(s2.standard()) return perm2; if(s1.packed()) return perm1; if(s2.packed()) return perm2; return perm1; }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_PERMUTATION_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/pointwise.hpp000066400000000000000000000047141510465702400307030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_POINTWISE_HPP #define MIGRAPHX_GUARD_KERNELS_POINTWISE_HPP #include #include #include #include #include #include #include #include namespace migraphx { template __device__ void pointwise_tensor(Stride stride, F f, Output out, T x, Ts... xs) { stride(x.get_shape().elements(), [&](auto i) { auto r = f(x[i], xs[i]...); out([&](auto... outs) { r([&](auto... rs) { static_assert(sizeof...(outs) == sizeof...(rs)); swallow{(outs[i] = implicit_conversion(rs))...}; }); }); }); } template __device__ auto pointwise(index idx, Transforms... transforms) { return [=](auto f, auto*... ps) { auto t = transform_args(make_tensors(), transforms..., rotate_and_pack_last()); t(ps...)([&](auto... xs) { pointwise_tensor(tile_stride(idx), f, xs...); }); }; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_POINTWISE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/pooling.hpp000066400000000000000000000152301510465702400303240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_POOLING_HPP #define MIGRAPHX_GUARD_KERNELS_POOLING_HPP #include #include #include #include #include #include #include namespace migraphx { template struct pool_op { template MIGRAPHX_DEVICE_CONSTEXPR T apply(T x) const { return x; } MIGRAPHX_DEVICE_CONSTEXPR auto pad() const { const auto& self = static_cast(*this); return self.init(); } template MIGRAPHX_DEVICE_CONSTEXPR T final(T x, U) const { return x; } }; struct max_pool : pool_op { MIGRAPHX_DEVICE_CONSTEXPR auto init() const { return lowest{}; } MIGRAPHX_DEVICE_CONSTEXPR auto reduce() const { return op::max{}; } }; struct average_pool : pool_op { MIGRAPHX_DEVICE_CONSTEXPR auto init() const { return make_tuple(0.0, 0); } template MIGRAPHX_DEVICE_CONSTEXPR tuple apply(T x) const { return {x, 1}; } MIGRAPHX_DEVICE_CONSTEXPR auto reduce() const { return op::sum{}; } template MIGRAPHX_DEVICE_CONSTEXPR T final(tuple t, U) const { T x = t[_c<0>]; index_int y = t[_c<1>]; return (y == 0) ? T{0.0} : T{x / y}; } }; struct average_include_pad_pool : pool_op { MIGRAPHX_DEVICE_CONSTEXPR auto init() const { return 0.0; } MIGRAPHX_DEVICE_CONSTEXPR auto reduce() const { return op::sum{}; } template MIGRAPHX_DEVICE_CONSTEXPR T final(T x, U y) const { if constexpr(y == 0) return T{0.0}; constexpr auto scale = T{1.0} / y; return T{x * scale}; } }; struct lpnorm_pool_base { }; template struct lpnorm_pool : lpnorm_pool_base, pool_op> { MIGRAPHX_DEVICE_CONSTEXPR auto init() const { return 0.0; } template MIGRAPHX_DEVICE_CONSTEXPR T apply(T x) const { if constexpr(P == 0) return 1; else if constexpr(P == 1) return migraphx::abs(x); else if constexpr(P == 2) return x * x; else return migraphx::pow(migraphx::abs(x), T(P)); } MIGRAPHX_DEVICE_CONSTEXPR auto pad() const { return apply(init()); } MIGRAPHX_DEVICE_CONSTEXPR auto reduce() const { return op::sum{}; } template MIGRAPHX_DEVICE_CONSTEXPR T final(T x, U) const { if constexpr(P == 0) return 1; else if constexpr(P == 1) return x; else if constexpr(P == 2) return migraphx::sqrt(x); else return migraphx::pow(x, 1. / P); } }; template struct window { Window win = {}; Stride stride = {}; Padding padding = {}; using rank = decltype(Window{}.size()); constexpr auto size() const { return return_c([] { return Window{}.product(); }); } constexpr auto has_padding() const { return return_c([] { return Padding{} == 0; }); } template constexpr auto apply(OutputIndex i, F f) const { auto win_start = generate_array(rank{}, [&](auto j) { diff_int dim = i[j]; MIGRAPHX_ASSERT(win[j] >= 1); diff_int s = stride[j]; diff_int p = padding[j]; return (dim * s) - p; }); return [=](auto j) { return f(win_start + win.multi(j)); }; } template constexpr void visit(Index i, F f) const { repeat(size(), apply(i, f)); } }; template constexpr window make_window(Window w, Stride s, Padding p) { return {w, s, p}; } template __device__ void pooling_reduce(Output output, F f) { if constexpr(GroupSize < 2) { Algo::template run( [&](auto out_idx, auto r) { r.outer([&] { output[out_idx] = f(out_idx, r); }); }); } else { auto goutput = as_vec(output, output.get_shape().lens.size() - _c<1>); Algo::template run([&](auto out_idx, auto r) { auto i = out_idx; i.back() *= GroupSize; auto result = vec_generate([&](auto) { i.back()++; return f(i, r); }); r.outer([&] { goutput[out_idx] = result; }); }); } } template __device__ void pooling(Op op, Window w, Output output, Input input) { pooling_reduce(output, [&](auto out_idx, auto r) { auto x = r.reduce(op.reduce(), op.init(), w.apply(out_idx, [&](auto j) { using itype = decltype(op.apply(input[j])); if(j < input.get_shape().lens) { return op.apply(input[j]); } else { return itype(op.pad()); } }))(reduce::make_indices(w.size())); return op.final(x, w.size()); }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_POOLING_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/pp.hpp000066400000000000000000000162641510465702400273040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_PP_HPP #define MIGRAPHX_GUARD_KERNELS_PP_HPP // NOLINTBEGIN(*-macro-to-enum) #define MIGRAPHX_PP_PRIMITIVE_CAT(x, y) x##y #define MIGRAPHX_PP_CAT(x, y) MIGRAPHX_PP_PRIMITIVE_CAT(x, y) #define MIGRAPHX_PP_EAT(...) #define MIGRAPHX_PP_EXPAND(...) __VA_ARGS__ #define MIGRAPHX_PP_COMMA(...) , #define MIGRAPHX_PP_IIF(c) MIGRAPHX_PP_PRIMITIVE_CAT(MIGRAPHX_PP_IIF_, c) #define MIGRAPHX_PP_IIF_0(t, ...) __VA_ARGS__ #define MIGRAPHX_PP_IIF_1(t, ...) t #define MIGRAPHX_PP_COMPL(b) MIGRAPHX_PP_PRIMITIVE_CAT(MIGRAPHX_PP_COMPL_, b) #define MIGRAPHX_PP_COMPL_0 1 #define MIGRAPHX_PP_COMPL_1 0 #define MIGRAPHX_PP_BITAND(x) MIGRAPHX_PP_PRIMITIVE_CAT(MIGRAPHX_PP_BITAND_, x) #define MIGRAPHX_PP_BITAND_0(y) 0 #define MIGRAPHX_PP_BITAND_1(y) y #define MIGRAPHX_PP_CHECK(...) MIGRAPHX_PP_CHECK_N(__VA_ARGS__, 0, ) #define MIGRAPHX_PP_CHECK_N(x, n, ...) n #define MIGRAPHX_PP_PROBE(x) x, 1, #define MIGRAPHX_PP_IS_PAREN(x) MIGRAPHX_PP_CHECK(MIGRAPHX_PP_IS_PAREN_PROBE x) #define MIGRAPHX_PP_IS_PAREN_PROBE(...) MIGRAPHX_PP_PROBE(~) #define MIGRAPHX_PP_PRIMITIVE_IS_EMPTY(x) \ MIGRAPHX_PP_CHECK(MIGRAPHX_PP_PRIMITIVE_IS_EMPTY_PROBE x()) #define MIGRAPHX_PP_PRIMITIVE_IS_EMPTY_PROBE(...) MIGRAPHX_PP_PROBE(~) #define MIGRAPHX_PP_IS_EMPTY_ARG(x) \ MIGRAPHX_PP_BITAND(MIGRAPHX_PP_COMPL(MIGRAPHX_PP_IS_PAREN(x))) \ (MIGRAPHX_PP_PRIMITIVE_IS_EMPTY(x)) #define MIGRAPHX_PP_REPEAT0(m, ...) m(0, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT1(m, ...) MIGRAPHX_PP_REPEAT0(m, __VA_ARGS__) m(1, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT2(m, ...) MIGRAPHX_PP_REPEAT1(m, __VA_ARGS__) m(2, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT3(m, ...) MIGRAPHX_PP_REPEAT2(m, __VA_ARGS__) m(3, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT4(m, ...) MIGRAPHX_PP_REPEAT3(m, __VA_ARGS__) m(4, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT5(m, ...) MIGRAPHX_PP_REPEAT4(m, __VA_ARGS__) m(5, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT6(m, ...) MIGRAPHX_PP_REPEAT5(m, __VA_ARGS__) m(6, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT7(m, ...) MIGRAPHX_PP_REPEAT6(m, __VA_ARGS__) m(7, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT8(m, ...) MIGRAPHX_PP_REPEAT7(m, __VA_ARGS__) m(8, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT9(m, ...) MIGRAPHX_PP_REPEAT8(m, __VA_ARGS__) m(9, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT10(m, ...) MIGRAPHX_PP_REPEAT9(m, __VA_ARGS__) m(10, __VA_ARGS__) #define MIGRAPHX_PP_REPEAT(n, m, ...) \ MIGRAPHX_PP_PRIMITIVE_CAT(MIGRAPHX_PP_REPEAT, n)(m, __VA_ARGS__) #define MIGRAPHX_PP_RES_ARGS() , , , , , , , , , , , , , , , #define MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARGS(...) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARGS_IMPL(__VA_ARGS__) #define MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARGS_IMPL( \ m, delim, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, ...) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x0) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x1) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x1) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x2) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x2) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x3) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x3) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x4) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x4) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x5) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x5) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x6) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x6) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x7) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x7) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x8) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x8) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x9) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x9) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x10) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x10) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x11) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x11) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x12) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x12) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x13) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x13) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x14) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x14) \ MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(delim, x15) MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x15) #define MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARG(m, x) \ MIGRAPHX_PP_IIF(MIGRAPHX_PP_IS_EMPTY_ARG(x))(MIGRAPHX_PP_EAT, m)(x) #define MIGRAPHX_PP_EACH_ARGS(m, ...) \ MIGRAPHX_PP_EXPAND(MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARGS( \ m, MIGRAPHX_PP_EAT, __VA_ARGS__, MIGRAPHX_PP_RES_ARGS())) #define MIGRAPHX_PP_TRANSFORM_ARGS(m, ...) \ MIGRAPHX_PP_EXPAND(MIGRAPHX_PP_PRIMITIVE_TRANSFORM_ARGS( \ m, MIGRAPHX_PP_COMMA, __VA_ARGS__, MIGRAPHX_PP_RES_ARGS())) // NOLINTEND(*-macro-to-enum) #endif // MIGRAPHX_GUARD_KERNELS_PP_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/preload.hpp000066400000000000000000000141321510465702400303030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_PRELOAD_HPP #define MIGRAPHX_GUARD_KERNELS_PRELOAD_HPP #include #include #include #include namespace migraphx { template struct remove_vec_impl { using type = T; }; template struct remove_vec_impl> { using type = T; }; template using remove_vec = typename remove_vec_impl::type; template constexpr auto traverse_preload(Shapes... ss) { return [=](auto f, auto... g) { index_int offset = 0; auto each = [&](auto x) { using type = remove_vec; constexpr auto s = decltype(x.get_shape()){}; constexpr auto size = s.element_space(); if constexpr(not s.broadcasted() or (s.elements() - size) < 64 or not is_same{}) return f(x, offset, false_type{}); else { auto pre_offset = offset; offset += size; offset += offset % 4; return f(x, pre_offset, true_type{}); } }; return by(each, g...)(ss...); }; } template constexpr index_int compute_preload_size_c(Shapes...) { index_int size = 0; traverse_preload(Shapes{}...)( [&](auto s, auto offset, auto) { size = offset + s.element_space(); }); return size; } template constexpr auto compute_preload_size(Shapes...) { return _c(Shapes{}...)>; } template __device__ auto preload_copy(index idx, F f, __shared__ T* buffer, Ts... xs) { auto invoke = [&](auto... ys) { __syncthreads(); f(ys...); }; traverse_preload(xs...)( [&](auto x, auto offset, auto copy) { if constexpr(copy) { if constexpr(decltype(tensor_vec_size(x)){} == 0) { auto v = auto_vectorize(x); auto b = as_vec(tensor_vec_size(v), buffer + offset); idx.local_stride(v.get_shape().element_space(), [&](auto i) { b[i] = v.data()[i]; }); return x.with(buffer + offset); } else { auto b = as_vec(tensor_vec_size(x), buffer + offset); idx.local_stride(x.get_shape().element_space(), [&](auto i) { b[i] = x.data()[i]; }); return x.with(b); } } else { return x; } }, invoke); } template struct shape_type : Shape { using type = T; }; template constexpr auto make_shape_type(T) { return shape_type{}; } template __device__ auto preload(index idx, Ts... xs) { using type = remove_vec; constexpr auto size = decltype(compute_preload_size(make_shape_type(xs)...)){}; const index_int max_size = 512 * sizeof(type); return [=](auto f) { if constexpr(size > 0 and size < max_size) { __shared__ type buffer[size]; preload_copy(idx, f, buffer, xs...); } else { f(xs...); } }; } inline __device__ auto auto_preload(index idx) { return make_transform([=](auto f, auto out, auto... xs) { preload(idx, xs...)([&](auto... ys) { f(out, ys...); }); }); } template __device__ auto preload_copy(index idx, T x) { return [=](auto f) { if constexpr(B) { using type = typename T::type; constexpr auto size = get_shape_c{}.element_space(); __shared__ type buffer[size]; // TODO: Always vecotrize when size > 4, and then use a second loop for remainder constexpr auto n = find_vectorize_size([&](auto i) { return (size % i) == 0; }); auto input = as_vec(remove_bool(x.data())); auto b = as_vec(remove_bool(buffer)); idx.local_stride(size / n, [&](auto i) { b[i] = input[i]; }); return f(x.with(buffer)); } else { return f(x); } }; } template __device__ auto auto_preload(index idx) { return make_transform([=](auto f, auto... xs) { auto invoke = [=](auto... ys) { if constexpr((Bs or ...)) __syncthreads(); f(ys...); }; join(invoke, preload_copy(idx, xs)...); }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_PRELOAD_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/print.hpp000066400000000000000000000163241510465702400300160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_PRINT_HPP #define MIGRAPHX_GUARD_KERNELS_PRINT_HPP #include #include #include #include namespace migraphx { template struct on_exit { F f; G g; template __host__ __device__ auto operator()(T x) const { return f(x); } __host__ __device__ ~on_exit() { f(g); } }; template constexpr auto print_type_name_probe() { constexpr auto name = __PRETTY_FUNCTION__; constexpr auto size = sizeof(__PRETTY_FUNCTION__); constexpr auto parameter_name = "PrivateMIGraphXTypeNameProbe = "; constexpr auto parameter_name_size = sizeof("PrivateMIGraphXTypeNameProbe = ") - 1; constexpr auto begin = search(name, name + size, parameter_name, parameter_name + parameter_name_size); static_assert(begin < name + size, "Type probe not found."); constexpr auto start = begin + parameter_name_size; constexpr auto last = find_if(start, name + size, [](auto c) { return c == ']' or c == ';'; }); return [=](const auto& s) { s.print_string(start, last - start); }; } template struct type_printer { template friend constexpr const Stream& operator<<(const Stream& s, type_printer) { print_type_name_probe()(s); return s; } }; template constexpr type_printer type_of() { return {}; } template constexpr type_printer type_of(T) { return {}; } template constexpr type_printer sub_type_of() { return {}; } template constexpr type_printer sub_type_of(T) { return {}; } template struct basic_printer { F f; __host__ __device__ const basic_printer& print_long(long value) const { f([&] { printf("%li", value); }); return *this; } __host__ __device__ const basic_printer& print_ulong(unsigned long value) const { f([&] { printf("%lu", value); }); return *this; } __host__ __device__ const basic_printer& print_char(char value) const { f([&] { printf("%c", value); }); return *this; } __host__ __device__ const basic_printer& print_string(const char* value) const { f([&] { printf("%s", value); }); return *this; } __host__ __device__ const basic_printer& print_string(const char* value, int size) const { f([&] { printf("%.*s", size, value); }); return *this; } __host__ __device__ const basic_printer& print_double(double value) const { f([&] { printf("%f", value); }); return *this; } __host__ __device__ const basic_printer& print_bool(bool value) const { f([&] { if(value) printf("true"); else printf("false"); }); return *this; } __host__ __device__ const basic_printer& operator<<(short value) const { return print_long(value); } __host__ __device__ const basic_printer& operator<<(unsigned short value) const { return print_ulong(value); } __host__ __device__ const basic_printer& operator<<(int value) const { return print_long(value); } __host__ __device__ const basic_printer& operator<<(unsigned int value) const { return print_ulong(value); } __host__ __device__ const basic_printer& operator<<(long value) const { return print_long(value); } __host__ __device__ const basic_printer& operator<<(unsigned long value) const { return print_ulong(value); } __host__ __device__ const basic_printer& operator<<(migraphx::half value) const { return print_double(value); } __host__ __device__ const basic_printer& operator<<(float value) const { return print_double(value); } __host__ __device__ const basic_printer& operator<<(double value) const { return print_double(value); } __host__ __device__ const basic_printer& operator<<(bool value) const { return print_bool(value); } __host__ __device__ const basic_printer& operator<<(char value) const { return print_char(value); } __host__ __device__ const basic_printer& operator<<(unsigned char value) const { return print_char(value); } __host__ __device__ const basic_printer& operator<<(const char* value) const { return print_string(value); } }; template constexpr basic_printer make_printer(F f) { return {f}; } template constexpr basic_printer> make_printer(F f, G g) { return {{f, g}}; } inline __device__ auto cout() { return make_printer([](auto f) { f(); }); } inline __device__ auto coutln() { return make_printer([](auto f) { f(); }, [] { printf("\n"); }); } template __device__ void unsafe_print_each(Stream s, T x, Ts... xs) { s << x; each_args([&](auto xx) { s << ' ' << xx; }, xs...); } template __device__ void print_each(Stream s, Ts... xs) { auto idx = make_index(); for(auto i = 0; i < idx.nglobal(); i++) { if(i == idx.global) unsafe_print_each(s, xs...); __syncthreads(); } } template __device__ void print_each_once(Stream s, Ts... xs) { auto idx = make_index(); if(idx.global == 0) unsafe_print_each(s, xs...); } template __device__ void print(Ts... xs) { print_each(cout(), xs...); } template __device__ void print_once(Ts... xs) { print_each_once(cout(), xs...); } template __device__ void println(Ts... xs) { print_each(cout(), xs..., '\n'); } template __device__ void println_once(Ts... xs) { print_each_once(cout(), xs..., '\n'); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_PRINT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/ranges.hpp000066400000000000000000000034341510465702400301370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_RANGES_HPP #define MIGRAPHX_GUARD_KERNELS_RANGES_HPP #include namespace migraphx { template struct iterator_range { Iterator start; Iterator last; constexpr Iterator begin() const { return start; } constexpr Iterator end() const { return last; } }; constexpr iterator_range range(diff_int start, diff_int last) { return {{start, {}}, {last, {}}}; } constexpr iterator_range range(diff_int last) { return range(0, last); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_RANGES_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/rank.hpp000066400000000000000000000026421510465702400276130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_RANK_HPP #define MIGRAPHX_GUARD_KERNELS_RANK_HPP namespace migraphx { template struct rank : rank { }; template <> struct rank<0> { }; } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_RANK_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/reduce.hpp000066400000000000000000000631631510465702400301340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_REDUCE_HPP #define MIGRAPHX_GUARD_KERNELS_REDUCE_HPP #include #include #include #include #include #include #include namespace migraphx { #if MIGRAPHX_HAS_DPP template __device__ void dpp_reduce(T& in, Op op) { static_assert(SubWaveSize <= MIGRAPHX_WAVEFRONTSIZE, "Too large subwave size"); static_assert(is_power_of_2(SubWaveSize), "SubWaveSize is not a power of 2"); if constexpr(SubWaveSize > 1) { auto out = dpp_mov(in); in = op(in, out); } if constexpr(SubWaveSize > 2) { auto out = dpp_mov(in); in = op(in, out); } if constexpr(SubWaveSize > 4) { auto out = dpp_mov(in); in = op(in, out); } if constexpr(SubWaveSize > 8) { auto out = dpp_mov(in); in = op(in, out); } #if MIGRAPHX_WAVEFRONTSIZE == 32 if constexpr(SubWaveSize > 16) { auto out = dpp_swizzle<0x1e0>(in); in = op(in, out); } #else if constexpr(SubWaveSize > 16) { auto out = dpp_mov(in); in = op(in, out); } if constexpr(SubWaveSize > 32) { auto out = dpp_mov(in); in = op(in, out); } #endif } #if defined(MIGRAPHX_USE_CLANG_TIDY) || defined(CPPCHECK) // NOLINTNEXTLINE #define MIGRAPHX_DPP_REDUCE_ASM_FUN(type, op, ins) \ template \ __device__ inline void dpp_reduce(type& x, op f) \ { \ (void)f; \ x = 1; \ } #else #define MIGRAPHX_DPP_IIF64(then, ...) then #define MIGRAPHX_DPP_IIF32(then, ...) __VA_ARGS__ #define MIGRAPHX_DPP_IF_64(x) MIGRAPHX_PP_CAT(MIGRAPHX_DPP_IIF, x) #define MIGRAPHX_DPP_WHEN_64(x) MIGRAPHX_DPP_IF_64(x)(MIGRAPHX_PP_EXPAND, MIGRAPHX_PP_EAT) #define MIGRAPHX_DPP_REDUCE_ASM0(ins) #ins " %0 %0 %0 row_shr:1\n" #define MIGRAPHX_DPP_REDUCE_ASM1(ins) #ins " %0 %0 %0 row_shr:2\n" #define MIGRAPHX_DPP_REDUCE_ASM2(ins) #ins " %0 %0 %0 row_shr:4 bank_mask:0xe\n" #define MIGRAPHX_DPP_REDUCE_ASM3(ins) #ins " %0 %0 %0 row_shr:8 bank_mask:0xc\n" #define MIGRAPHX_DPP_REDUCE_ASM4(ins) #ins " %0 %0 %0 row_bcast:15 row_mask:0xa\n" #define MIGRAPHX_DPP_REDUCE_ASM5(ins) #ins " %0 %0 %0 row_bcast:31 row_mask:0xc\n" #define MIGRAPHX_DPP_REDUCE_ASM_REPEAT(i, ins) \ MIGRAPHX_PP_CAT(MIGRAPHX_DPP_REDUCE_ASM, i)(ins) "s_nop 1\n" #define MIGRAPHX_DPP_REDUCE_ASM(n, x, ins, ...) \ { \ __asm__ volatile("s_nop 4\n" MIGRAPHX_PP_REPEAT(n, MIGRAPHX_DPP_REDUCE_ASM_REPEAT, ins) \ : "=v"(x) \ : "0"(x)); \ __VA_ARGS__ \ } #if MIGRAPHX_WAVEFRONTSIZE == 64 #define MIGRAPHX_DPP_REDUCE_SWIZZLE(x, f) (void)f; #else #define MIGRAPHX_DPP_REDUCE_SWIZZLE(x, f) \ auto y = dpp_swizzle<0x1e0>(x); \ x = f(x, y); #endif #define MIGRAPHX_DPP_REDUCE_ASM_FUN(type, op, ins) \ template \ __device__ inline void dpp_reduce(type& x, op f) \ { \ if constexpr(SubWaveSize == 2) \ MIGRAPHX_DPP_REDUCE_ASM(0, x, ins, ); \ if constexpr(SubWaveSize == 4) \ MIGRAPHX_DPP_REDUCE_ASM(1, x, ins, ); \ if constexpr(SubWaveSize == 8) \ MIGRAPHX_DPP_REDUCE_ASM(2, x, ins, ); \ if constexpr(SubWaveSize == 16) \ MIGRAPHX_DPP_REDUCE_ASM(3, x, ins, ); \ if constexpr(SubWaveSize == 32) \ MIGRAPHX_DPP_REDUCE_ASM(MIGRAPHX_DPP_IF_64(MIGRAPHX_WAVEFRONTSIZE)(4, 3), \ x, \ ins, \ MIGRAPHX_DPP_REDUCE_SWIZZLE(x, f)); \ MIGRAPHX_DPP_WHEN_64(MIGRAPHX_WAVEFRONTSIZE) \ (if constexpr(SubWaveSize == 64) MIGRAPHX_DPP_REDUCE_ASM(5, x, ins, )); \ } #endif // Navi21 doesn't support int32 dpp #if defined(__gfx1030__) // NOLINTNEXTLINE #define MIGRAPHX_DPP_REDUCE(op, prefix, sign) \ MIGRAPHX_DPP_REDUCE_ASM_FUN(float, op, prefix##_f32); \ MIGRAPHX_DPP_REDUCE_ASM_FUN(half, op, prefix##_f16); \ MIGRAPHX_DPP_REDUCE_ASM_FUN(uint32_t, op, prefix##_u32); #else // NOLINTNEXTLINE #define MIGRAPHX_DPP_REDUCE(op, prefix, sign) \ MIGRAPHX_DPP_REDUCE_ASM_FUN(float, op, prefix##_f32); \ MIGRAPHX_DPP_REDUCE_ASM_FUN(half, op, prefix##_f16); \ MIGRAPHX_DPP_REDUCE_ASM_FUN(int32_t, op, prefix##sign##32); \ MIGRAPHX_DPP_REDUCE_ASM_FUN(uint32_t, op, prefix##_u32); #endif // Note: when max and min are in int32_t, signed version of instruction needs to be used. MIGRAPHX_DPP_REDUCE(op::sum, v_add, _u) MIGRAPHX_DPP_REDUCE(op::product, v_mul, _u) MIGRAPHX_DPP_REDUCE(op::max, v_max, _i) MIGRAPHX_DPP_REDUCE(op::min, v_min, _i) template __device__ void dpp_reduce(T& in, Op op) { dpp_reduce(in, op); } template __device__ auto subwave_reduce(index idx, Op op, T init, Index n, F f) { MIGRAPHX_ASSERT(idx.max_nlocal() == idx.nlocal() or (idx.nlocal() % SubWaveSize) == 0); using type = decltype(index::invoke_loop(f, 0, _c<0>)); auto x = type(init); idx.local_subwave_stride( n, [&](auto i, auto d) { x = op(x, index::invoke_loop(f, i, d)); }); dpp_reduce(x, op); return readlane(x); } template __device__ auto wave_reduce(index idx, Op op, T init, Index n, F f) { return subwave_reduce(idx, op, init, n, f); } template __device__ auto block_reduce(index idx, Op op, T init, Index n, F f) { MIGRAPHX_ASSERT(idx.max_nlocal() == idx.nlocal()); #ifdef MIGRAPHX_HAS_CONST_LOCAL if constexpr(decltype(idx.nlocal()){} == MIGRAPHX_WAVEFRONTSIZE) return wave_reduce(idx, op, init, n, f); #endif constexpr index_int lanes_per_thread = MIGRAPHX_WAVEFRONTSIZE; using type = decltype(index::invoke_loop(f, 0, _c<0>)); __shared__ type buffer[idx.max_nlocal() / lanes_per_thread]; auto x = type(init); idx.local_stride(n, [&](auto i, auto d) { x = op(x, index::invoke_loop(f, i, d)); }); dpp_reduce(x, op); const auto ldsidx = idx.local / lanes_per_thread; if((idx.local % lanes_per_thread) == lanes_per_thread - 1) { buffer[ldsidx] = x; } __syncthreads(); type y = type(init); for(index_int i = 0; i < idx.nlocal() / lanes_per_thread; i++) { y = op(y, buffer[i]); } return y; } #else template __device__ auto block_reduce(index idx, Op op, T init, Index n, F f) { MIGRAPHX_ASSERT(idx.max_nlocal() == idx.nlocal()); using type = decltype(index::invoke_loop(f, 0, _c<0>)); __shared__ type buffer[idx.max_nlocal()]; auto x = type(init); idx.local_stride(n, [&](auto i, auto d) { x = op(x, index::invoke_loop(f, i, d)); }); buffer[idx.local] = x; __syncthreads(); for(index_int s = 1; s < idx.nlocal(); s *= 2) { const index_int index = 2 * s * idx.local; if(index + s < idx.nlocal()) { buffer[index] = op(buffer[index], buffer[index + s]); } __syncthreads(); } return buffer[0]; } #endif template constexpr auto reduce_slice(Input input, T i) { constexpr auto lens = transform(get_shape_c{}.lens, get_shape_c{}.lens, [](index_int x, index_int y) -> index_int { if(x == y) return 1; return x; }); ; constexpr auto s = make_shape(lens, get_shape_c{}.strides); MIGRAPHX_ASSERT((input.get_shape().index(i) + s.element_space()) <= input.get_shape().element_space()); return make_tensor_view(&input[i], s); } namespace reduce { struct inner_storage_tag { }; template using is_inner_storage = is_base_of>>; template struct lazy_inner_storage : inner_storage_tag { using type = remove_reference_t()(0, _c<0>))>; F f; constexpr Size rsize() const { return {}; } template constexpr auto operator()(U j, V d) const { return f(j, d); } }; template constexpr lazy_inner_storage make_lazy_inner_storage(Size, F f) { return {{}, f}; } template constexpr auto make_indices(Size size) { return make_lazy_inner_storage(size, [](auto j, auto) { return j; }); } template struct storage_access : F { using type = R; }; template constexpr storage_access make_storage_access(F f) { return {{f}}; } template constexpr auto sliced(Slicer slicer, F f) { return [=](auto x, auto... xs) { // TODO: assert all elements are the same return f(slicer(x), slicer(xs)...); }; } template constexpr auto compute_reduce_axis() { constexpr auto lens = transform_i(get_shape_c{}.lens, [](index_int x, index_int i) -> index_int { if(i == Axis) return 1; return x; }); return make_shape(lens, get_shape_c{}.strides); } template constexpr auto final_reduce(T x, F f) { return vec_reduce(x, f); } template constexpr auto final_reduce(array a, F f) { return a.apply([&](auto x) { return final_reduce(x, f); }); } template using with_axis = decltype(compute_reduce_axis()); template struct reducer_base { template __device__ decltype(auto) make_inner_slice(T&& x) const { if constexpr(is_inner_storage{}) { return x; } else { auto&& derived = static_cast(*this); auto t = derived.slice(x); return make_storage_access( [=](auto i, auto...) -> auto& { return t[i]; }); } } template constexpr auto get_size(T&& x, [[maybe_unused]] Ts&&... xs) const { MIGRAPHX_ASSERT(get_size(x) == get_size(xs...)); return get_size(x); } template constexpr auto get_size(T&& x) const { if constexpr(is_inner_storage{}) { return x.rsize(); } else { auto&& derived = static_cast(*this); auto t = derived.slice(x); return t.size(); } } template __device__ auto inner_sliced(F f) const { return [=](auto&&... xs) { return f(get_size(xs...), make_inner_slice(xs)...); }; } template static __device__ typename T::type& decl_inner_storage(const T&); template __device__ auto inner(F f) const { return this->inner_sliced([=](auto n, auto&&... xs) { using result_type = decltype(f(decl_inner_storage(xs)...)); auto&& derived = static_cast(*this); if constexpr(is_void{}) { derived.inner_void_impl(f, n, xs...); } else { return derived.template inner_impl(f, n, xs...); } }); } template __device__ auto lazy_inner(F f) const { return this->inner_sliced([=](auto n, auto&&... xs) { return make_lazy_inner_storage(n, [=](auto j, auto d) { return f(xs(j, d)...); }); }); } template __device__ auto reduce(Op op, T init, Read read) const { return this->inner_sliced([=](auto n, auto&&... xs) { auto&& derived = static_cast(*this); return derived.reduce_impl(op, init, read, n, xs...); }); } template __device__ auto reduce(Op op, T init) const { return this->reduce(op, init, op::id{}); } template __device__ void outer(F f) const { f(); } template constexpr auto elements() const { auto&& derived = static_cast(*this); using reduce_type = decltype(derived.slice(Input{})); using value_type = typename Input::type; constexpr auto relements = get_shape_c{}.elements(); if constexpr(vec_size() > 1) return relements * vec_size(); else return relements; } }; struct block { template struct reducer : reducer_base> { index idx; Slicer slice; template struct inner_storage : inner_storage_tag { using type = T; array arr; constexpr Size rsize() const { return {}; } template constexpr auto& operator()(U, V d) const { return arr[d]; } template constexpr auto& operator()(U, V d) { return arr[d]; } }; template __device__ auto reduce_impl(Op op, T init, Read read, N n, Ts&&... xs) const { return block_reduce(idx, op, init, n, [&](auto j, auto d) { return final_reduce(read(xs(j, d)...), op); }); } template __device__ void outer(F f) const { if(idx.local == 0) f(); } template __device__ void inner_void_impl(F f, N n, Ts&&... xs) const { idx.local_stride(n, [&](auto j, auto d) { f(xs(j, d)...); }); } template __device__ auto inner_impl(F f, N n, Ts&&... xs) const { using max_iterations = decltype(idx.max_local_stride_iterations(n)); inner_storage storage; idx.local_stride(n, [&](auto j, auto d) { storage(j, d) = R{f(xs(j, d)...)}; }); return storage; } }; template static __device__ auto make(index idx, Slicer slicer) { return reducer{{}, idx, slicer}; } template static __device__ void run(F f) { auto idx = make_index(); constexpr auto nelements = get_shape_c{}.elements(); idx.global_stride(nelements * idx.nlocal(), [&](auto i) { const auto out_idx = get_shape_c{}.multi(i / idx.nlocal()); f(out_idx, make(idx, [&](auto input) { return reduce_slice(input, out_idx); })); }); } }; struct block_large { template struct reducer : reducer_base> { index idx; Slicer slice; template __device__ auto reduce_impl(Op op, T init, Read read, N n, Ts&&... xs) const { return block_reduce(idx, op, init, index_int{n}, [&](auto j, auto d) { return final_reduce(read(xs(j, d)...), op); }); } template __device__ void outer(F f) const { if(idx.local == 0) f(); } template __device__ void inner_void_impl(F f, N n, Ts&&... xs) const { idx.local_stride(index_int{n}, [&](auto j, auto d) { f(xs(j, d)...); }); } template __device__ auto inner_impl(F f, N n, Ts&&... xs) const { return make_lazy_inner_storage(n, [=](auto j, auto d) { return f(xs(j, d)...); }); } }; template static __device__ auto make(index idx, Slicer slicer) { return reducer{{}, idx, slicer}; } template static __device__ void run(F f) { auto idx = make_index(); constexpr auto nelements = get_shape_c{}.elements(); idx.global_stride(nelements * idx.nlocal(), [&](auto i) { const auto out_idx = get_shape_c{}.multi(i / idx.nlocal()); f(out_idx, make(idx, [&](auto input) { return reduce_slice(input, out_idx); })); }); } }; template struct subwave { template struct reducer : reducer_base> { index idx; Slicer slice; template struct inner_storage : inner_storage_tag { using type = T; array arr; constexpr Size rsize() const { return {}; } template constexpr auto& operator()(U, V d) const { return arr[d]; } template constexpr auto& operator()(U, V d) { return arr[d]; } }; template __device__ auto reduce_impl(Op op, T init, Read read, N n, Ts&&... xs) const { return subwave_reduce(idx, op, init, n, [&](auto j, auto d) { return final_reduce(read(xs(j, d)...), op); }); } template __device__ void outer(F f) const { if(idx.local_subwave() == 0) f(); } template __device__ void inner_void_impl(F f, N n, Ts&&... xs) const { idx.local_subwave_stride(n, [&](auto j, auto d) { f(xs(j, d)...); }); } template __device__ auto inner_impl(F f, N n, Ts&&... xs) const { using max_iterations = decltype(idx.max_local_subwave_stride_iterations(n)); inner_storage storage; idx.local_subwave_stride( n, [&](auto j, auto d) { storage(j, d) = f(xs(j, d)...); }); return storage; } }; template static __device__ auto make(index idx, Slicer slicer) { return reducer{{}, idx, slicer}; } template static __device__ void run(F f) { auto idx = make_index(); constexpr auto nelements = get_shape_c{}.elements(); idx.global_stride(nelements * idx.nlocal_subwave(), [&](auto i) { const auto out_idx = get_shape_c{}.multi(i / idx.nlocal_subwave()); f(out_idx, make(idx, [&](auto input) { return reduce_slice(input, out_idx); })); }); } }; using wave = subwave; struct lane { template struct reducer : reducer_base> { index idx; Slicer slice; template __device__ auto reduce_impl(Op op, T init, Read read, N n, U&& x, Us&&... xs) const { using type = remove_reference_t), xs(0, _c<0>)...))>; type r = type(init); for(index_int j = 0; j < n; j++) { r = op(r, read(x(j, _c<0>), xs(j, _c<0>)...)); } return r; } template __device__ void outer(F f) const { f(); } template __device__ void inner_void_impl(F f, N n, Ts&&... xs) const { for(index_int j = 0; j < n; j++) { f(xs(j, _c<0>)...); } } template __device__ auto inner_impl(F f, N n, Ts&&... xs) const { return make_lazy_inner_storage(n, [=](auto j, auto d) { return f(xs(j, d)...); }); } }; template static __device__ auto make(index idx, Slicer slicer) { return reducer{{}, idx, slicer}; } template static __device__ void run(F f) { auto idx = make_index(); constexpr auto nelements = get_shape_c{}.elements(); idx.global_stride(nelements, [&](auto i) { const auto out_idx = get_shape_c{}.multi(i); f(out_idx, make(idx, [&](auto input) { return reduce_slice(input, out_idx); })); }); } }; // TODO: Remove these in the future when they can be selected in the compiler class template constexpr auto pick_block() { using nlocal = decltype(index{}.max_nlocal()); if constexpr(RElements < nlocal{} * 256) return block{}; else return block_large{}; } template using auto_block = decltype(pick_block()); template constexpr auto reduce_elements_with_axis() { constexpr auto s = get_shape_c{}; return s.lens[Axis]; } } // namespace reduce template __device__ void simple_reduce(Op op, T init, Input input, Output output, ReadInput read, WriteOuput write) { Algo::template run([&](auto out_idx, auto r) { auto x = r.reduce(op, init, read)(input); r.outer([&] { output[out_idx] = write(x); }); }); } template __device__ void fused_reduce(Output output_pack, Assign assign, F f) { Algo::template run([&](auto out_idx, auto r) { auto result_tuple = f(r, out_idx); unpack_each( [&](auto output, auto result) { if constexpr(reduce::is_inner_storage{}) { r.inner([&](auto& y, auto x) { assign(y, x); })(output, result); } else { r.outer([&] { assign(output[out_idx], implicit_conversion(result)); }); } }, output_pack, result_tuple); }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_REDUCE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/roialign.hpp000066400000000000000000000177411510465702400304720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_ROIALIGN_HPP #define MIGRAPHX_GUARD_KERNELS_ROIALIGN_HPP #include #include #include #include #include namespace migraphx { struct max_pool { MIGRAPHX_DEVICE_CONSTEXPR auto init() { return lowest{}; } template MIGRAPHX_DEVICE_CONSTEXPR T operator()(T x, T y) { return max(x, y); } template MIGRAPHX_DEVICE_CONSTEXPR T final(T x, index_int) { return (x); } }; struct avg_pool { MIGRAPHX_DEVICE_CONSTEXPR auto init() { return 0.0; } template MIGRAPHX_DEVICE_CONSTEXPR T operator()(T x, T y) { return x + y; } template MIGRAPHX_DEVICE_CONSTEXPR T final(T x, index_int y) { return (y == 0) ? T{0.0} : T{x / y}; } }; template MIGRAPHX_DEVICE_CONSTEXPR typename Iterator::value_type bilinear_interpolate( const Iterator data, const array& dims, array xy, Op pooling) { array low{}; array high{}; for(index_int ii = 0; ii < xy.size(); ++ii) { if(xy[ii] < -1.0f or xy[ii] > dims[ii]) { return implicit_conversion(0); } xy[ii] = migraphx::max(xy[ii], 0.0f); low[ii] = xy[ii]; high[ii] = low[ii] + 1; if(low[ii] >= dims[ii] - 1) { xy[ii] = high[ii] = low[ii] = dims[ii] - 1; } } array locs = {low[0] * dims[1] + low[1], low[0] * dims[1] + high[1], high[0] * dims[1] + low[1], high[0] * dims[1] + high[1]}; float ly = xy[0] - low[0]; float lx = xy[1] - low[1]; float hy = 1.0f - ly; float hx = 1.0f - lx; // do calculations in floating point and convert final result to required type array ws = {hy * hx, hy * lx, ly * hx, ly * lx}; auto v01 = pooling(data[locs[0]] * ws[0], data[locs[1]] * ws[1]); auto v23 = pooling(data[locs[2]] * ws[2], data[locs[3]] * ws[3]); return implicit_conversion(pooling(v01, v23)); } template MIGRAPHX_DEVICE_CONSTEXPR auto calc_pooling(const Iterator& data, const array& roi_starts, const array& bin_size, const array& idx, const array& bin_grid_size, const array& dims, float roi_offset, Op op) { using in_dtype = typename Iterator::value_type; in_dtype output_val = in_dtype{op.init()}; const int64_t count = bin_grid_size[0] * bin_grid_size[1]; dfor(bin_grid_size[0], bin_grid_size[1])([&](auto iy, auto ix) { array id = {iy, ix}; array locs = roi_starts + idx * bin_size + bin_size * (id + 0.5f) / bin_grid_size + roi_offset; auto val = bilinear_interpolate(data, dims, locs, op); output_val = op(output_val, val); }); return op.final(output_val, count); } template struct roalign_settings { T1 roi_offset{}; T2 is_avg_pooling{}; T3 sampling_ratio{}; T4 spatial_scale{}; }; template constexpr roalign_settings make_roalign_settings(Ts... xs) { return {xs...}; } template __device__ void roialign(const T& x_t, const U& rois_t, const V& ind_t, W& y_t, Settings s) { auto index = make_index(); const auto x = x_t.begin(); const auto rois = rois_t.begin(); const auto ind = ind_t.begin(); // input shape auto x_lens = x_t.get_shape().lens; auto channel_num = x_lens[1]; // input dims of height and width, in all 2-dim arrays, the first dim // is for height and second dim is for width array in_dims = {x_lens[2], x_lens[3]}; const auto stride = index.nglobal(); auto out_s = y_t.get_shape(); auto roi_column_num = rois_t.get_shape().lens[1]; // output dims of height and width, in all 2-dim arrays, the first dim // is for height and second dim is for width const auto& out_lens = out_s.lens; array out_dims = {out_lens[2], out_lens[3]}; for(index_int i = index.global; i < out_s.elements(); i += stride) { auto idx = out_s.multi(i); int n = idx[0]; int c = idx[1]; int ph = idx[2]; int pw = idx[3]; const auto offset_rois = rois + (n * roi_column_num); const int batch_ind = ind[n]; array roi_starts = { static_cast(offset_rois[1]) * static_cast(s.spatial_scale), static_cast(offset_rois[0]) * static_cast(s.spatial_scale)}; array roi_ends = { static_cast(offset_rois[3]) * static_cast(s.spatial_scale), static_cast(offset_rois[2]) * static_cast(s.spatial_scale)}; array roi_size{}; array bin_size{}; array bin_grid_size{}; for(index_int ii = 0; ii < roi_size.size(); ++ii) { roi_size[ii] = roi_ends[ii] - roi_starts[ii]; roi_size[ii] = migraphx::max(roi_size[ii], 1.0f); bin_size[ii] = roi_size[ii] / out_dims[ii]; bin_grid_size[ii] = (s.sampling_ratio > 0) ? s.sampling_ratio : migraphx::ceil(roi_size[ii] / out_dims[ii]); } const auto offset_x = x + ((batch_ind * channel_num + c) * in_dims[0] * in_dims[1]); if constexpr(s.is_avg_pooling) { y_t[i] = calc_pooling(offset_x, roi_starts, bin_size, {ph, pw}, bin_grid_size, in_dims, s.roi_offset, avg_pool{}); } else { y_t[i] = calc_pooling(offset_x, roi_starts, bin_size, {ph, pw}, bin_grid_size, in_dims, s.roi_offset, max_pool{}); } } } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/scatter.hpp000066400000000000000000000052371510465702400303300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_SCATTER_ELEMENTS_HPP #define MIGRAPHX_GUARD_KERNELS_SCATTER_ELEMENTS_HPP #include #include #include namespace migraphx { // Checks and skips out of bounds indices if SkipOutOfBounds is true. // Otherwise does not check and underfined behavior if out of bounds. template __device__ void scatter(const T& indices_t, const U& updates_t, const V& output_t, F f) { auto gpu_index = make_index(); auto indices_shape = indices_t.get_shape(); auto output_shape = output_t.get_shape(); auto axis_dim_size = output_shape.lens[Axis]; gpu_index.global_stride(indices_shape.elements(), [&](auto i) { auto out_idx = indices_shape.multi(i); auto index = indices_t[i]; index = index < 0 ? index + axis_dim_size : index; if constexpr(SkipOutOfBounds) { if(index < 0) { return; } } out_idx[Axis] = index; if constexpr(SkipOutOfBounds) { if(not equal( out_idx.begin(), out_idx.end(), output_shape.lens.begin(), [](auto x, auto y) { return x < y; })) { return; } } f(output_t[out_idx], updates_t[i]); }); } } // namespace migraphx #endif scatter_reduction_modes.hpp000066400000000000000000000044311510465702400335070ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_SCATTER_REDUCTION_MODES_HPP #define MIGRAPHX_GUARD_KERNELS_SCATTER_REDUCTION_MODES_HPP #include #include #include namespace migraphx { struct assign_none { template MIGRAPHX_DEVICE_CONSTEXPR void operator()(T& x, U y) const { x = y; } }; struct assign_add { template MIGRAPHX_DEVICE_CONSTEXPR void operator()(T& x, U y) const { atomic_assign(x, y, op::sum{}); } }; struct assign_mul { template MIGRAPHX_DEVICE_CONSTEXPR void operator()(T& x, U y) const { atomic_assign(x, y, op::product{}); } }; struct assign_max { template MIGRAPHX_DEVICE_CONSTEXPR void operator()(T& x, U y) const { atomic_assign(x, y, op::max{}); } }; struct assign_min { template MIGRAPHX_DEVICE_CONSTEXPR void operator()(T& x, U y) const { atomic_assign(x, y, op::min{}); } }; } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/scatternd.hpp000066400000000000000000000047441510465702400306540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_SCATTERND_HPP #define MIGRAPHX_GUARD_KERNELS_SCATTERND_HPP #include #include #include namespace migraphx { template __device__ void scatternd(const T& indices_t, const U& updates_t, const V& output_t, F f) { auto index = make_index(); auto updates_shape = updates_t.get_shape(); index.global_stride(updates_shape.elements(), [&](auto i) { auto output_shape = output_t.get_shape(); auto indices_shape = indices_t.get_shape(); auto k = indices_shape.lens.back(); auto q = indices_shape.lens.size(); auto updates_idx = updates_shape.multi(i); auto indices_idx = indices_shape.multi(0); copy(updates_idx.begin(), updates_idx.begin() + q - 1, indices_idx.begin()); auto index_start = indices_t.begin() + indices_shape.index(indices_idx); auto index_end = index_start + k; auto out_idx = output_shape.multi(0); copy(index_start, index_end, out_idx.begin()); copy(updates_idx.begin() + q - 1, updates_idx.end(), out_idx.begin() + k); f(output_t[out_idx], updates_t[i]); }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/shape.hpp000066400000000000000000000152341510465702400277610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_SHAPE_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_SHAPE_HPP #include #include #include #include namespace migraphx { template struct shape : equality_comparable> { using shape_type = shape; using index_array = typename Lens::base_array; Lens lens = {}; Strides strides = {}; constexpr shape() = default; constexpr shape(Lens l, Strides s) : lens(l), strides(s) {} constexpr auto elements() const { return _c; } constexpr auto element_space() const { return _c; } constexpr auto packed() const { return not skips() and elements() == element_space(); } constexpr auto broadcasted() const { return _c; } constexpr auto transposed() const { return return_c([] { auto lstrides = Strides{}; if(shape{}.broadcasted()) { index_array s{}; auto out = copy_if( lstrides.begin(), lstrides.end(), s.begin(), [](auto x) { return x != 0; }); return not is_sorted(s.begin(), out, greater{}); } else { return not is_sorted(lstrides.begin(), lstrides.end(), greater{}); } }); } constexpr auto skips() const { return return_c([] { auto lstrides = Strides{}; return none_of(lstrides.begin(), lstrides.end(), [](auto x) { return x == 1; }); }); } constexpr auto standard() const { return packed() and not transposed(); } constexpr index_int index(index_array x) const { return x.dot(strides); } constexpr index_int index(index_int i) const { if(this->standard()) { MIGRAPHX_ASSERT(i >= elements() or i == compute_index(i)); return i; } else { return compute_index(i); } } constexpr index_int compute_index(index_int i) const { const auto rank = this->lens.size(); index_int s = 1; index_int result = 0; for(index_int j = 0; j < rank; j++) { const index_int k = rank - j - 1; const index_int stride = this->strides[k]; const index_int len = this->lens[k]; const index_int slen = s * len; const index_int idx = (i % slen) / s; result += stride * idx; s = slen; } return result; } /// Convert single index into a multi-index constexpr index_array multi(index_int idx) const { return lens.multi(idx); } /// Convert multi-index into a single index constexpr index_int single(index_array idx) const { MIGRAPHX_ASSERT(idx.size() == lens.size()); if(idx.empty()) return 0; return idx.back() + inner_product( lens.begin() + 1, lens.end(), idx.begin(), index_int{0}, [](const auto& a, const auto& b) { return (a + b[0]) * b[1]; }, [](auto len, auto i) -> array { return {i, len}; }); } constexpr shape get_shape() const { return *this; } template friend constexpr bool operator==(const shape& x, const shape& y) { return x.lens == y.lens and x.strides == y.strides; } template friend constexpr const Stream& operator<<(const Stream& ss, const shape& s) { ss << "{" << s.lens << "}, {" << s.strides << "}"; return ss; } }; template constexpr auto calculate_strides(Lens) { return return_array_c([] { Lens lens{}; array strides{1}; const auto n = lens.size() - 1; index_int stride = 1; for(index_int i = 0; i < n; i++) { auto ri = n - i; stride *= lens[ri]; strides[ri - 1] = stride; } return strides; }); } template constexpr shape make_shape(Lens lens, Strides strides) { return {lens, strides}; } template constexpr auto make_shape(Lens lens) { return make_shape(lens, calculate_strides(lens)); } template constexpr auto reorder_shape(Shape, Permutation) { constexpr auto lens = return_array_c([] { return reorder_dims(Shape{}.lens, Permutation{}); }); constexpr auto strides = return_array_c([] { return reorder_dims(Shape{}.strides, Permutation{}); }); return make_shape(lens, strides); } template constexpr auto make_shape_from_permutation(Lens, Permutation) { constexpr auto new_lens = reorder_dims(Lens{}, Permutation{}); constexpr auto result = reorder_shape(make_shape(new_lens), invert_permutation(Permutation{})); static_assert(result.lens == Lens{}); return result; } template constexpr auto make_packed_shape(Shape) { constexpr auto s = Shape{}; if constexpr(s.packed()) { return s; } else { return make_shape_from_permutation(s.lens, find_permutation(s)); } } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/slice.hpp000066400000000000000000000115311510465702400277540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_SLICE_HPP #define MIGRAPHX_GUARD_KERNELS_SLICE_HPP #include #include #include namespace migraphx { template constexpr auto slice_make_multi_lens(Shape, Size) { return return_array_c([] { auto n = Size{} - _c<1>; auto i = Shape{}.multi(n); using type = typename decltype(i)::value_type; return i + type{1}; }); } template constexpr auto slice_make_multi_lens(Shape, integral_const_array x) { return x; } template constexpr auto make_slice(Shape, Select select) { auto inner_lens = transform_i(Shape{}.lens, [=](index_int x, index_int ii) -> index_int { if(select(x, ii, Shape{}.lens.size())) return x; return 1; }); return make_shape(inner_lens, Shape{}.strides); } template constexpr auto make_slice(Shape input, Select select, Size size) { auto as = make_slice(input, select); auto lens = slice_make_multi_lens(as, size); return make_shape(lens, Shape{}.strides); } template struct slice_size_transform { F f; template constexpr auto operator()(Ts... xs) const { return f(xs...); } }; MIGRAPHX_AUTO_DEDUCE(slice_size_transform); template constexpr auto make_slice(Shape input, Select select, slice_size_transform t) { auto as = make_slice(input, select); auto lens = slice_make_multi_lens(as, decltype(t(input, as)){}); return make_shape(lens, Shape{}.strides); } template constexpr auto nslices(Shape input, Ss... ss) { auto as = make_slice(input, ss...); return input.elements() / as.elements(); } template constexpr auto slice_group() { return slice_size_transform{[](auto input, auto s) { auto r = return_array_c([] { auto lens = decltype(s){}.lens.base(); lens.back() *= N; lens -= 1; return decltype(input){}.lens.carry(lens) + index_int{1}; }); return r; }}; } template constexpr auto slice_split() { return slice_size_transform{[](auto, auto s) { return s.elements() / _c; }}; } template constexpr auto slice_axes() { return [](auto, auto i, auto n) { return ((Axes < 0 ? i == (n + Axes) : i == Axes) or ...); }; } template constexpr auto slice_tensor(Input input, T start, Ss... ss) { constexpr auto inner_shape = make_slice(get_shape_c{}, ss...); auto outer_lens = transform(get_shape_c{}.lens, inner_shape.lens, [=](auto x, auto inner) { return 1 + x - inner; }); auto outer_shape = make_shape(outer_lens, get_shape_c{}.strides); auto offset = outer_shape.index(start); MIGRAPHX_ASSERT((offset + inner_shape.element_space()) <= get_shape_c{}.element_space()); return make_tensor_view(input.data() + offset, inner_shape); } template constexpr auto slice_schedule(index idx, Ss... ss) { return [=](auto... xs) { return [=](auto f) { // TODO: Assert nslices is the same for all xs constexpr auto n = nslices(get_shape_c()(xs...))>{}, ss...); Schedule{idx}.group_stride(n, [&](auto i) { f(slice_tensor(xs, i, ss...)...); }); }; }; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_SLICE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/softmax.hpp000066400000000000000000000042511510465702400303370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_SOFTMAX_HPP #define MIGRAPHX_GUARD_KERNELS_SOFTMAX_HPP #include #include namespace migraphx { template __device__ void softmax(Input input1, Output output) { using block = reduce::auto_block()>; block::template run>([&](auto, auto r) { auto x = r.inner(op::id{})(input1); #ifdef MIGRAPHX_USE_FAST_SOFTMAX const auto c = vec_at(r.slice(input1)[0], 0); #else const auto c = r.reduce(op::max{}, lowest{}, op::id{})(x); #endif r.inner([&](auto& x1) { x1 = migraphx::exp(x1 - c); })(x); auto batch_sum = r.reduce(op::sum{}, 0, [](auto x1) { return migraphx::convert(x1); })(x); r.inner([&](auto& y, auto x1) { y = implicit_conversion(x1 / batch_sum); })(output, x); }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_SOFTMAX_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/sort.hpp000066400000000000000000000172421510465702400276510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_SORT_HPP #define MIGRAPHX_GUARD_KERNELS_SORT_HPP #include #include #include #include #include #include #include namespace migraphx { template struct bitonic_sort { Compare compare_function; template constexpr bool compare(const T& x, const T& y, Reverse reverse) const { return reverse ^ compare_function(y, x); } template constexpr bool compare(const T& x, const T& y) const { return compare(x, y); } template constexpr void lane_compare_swap(T& x, T& y, Reverse reverse) const { if(compare(x, y, reverse)) swap(x, y); } template constexpr auto compare(Reverse reverse) const { return [=](const auto& x, const auto& y) { return compare(x, y, reverse); }; } template constexpr void lane_shuffle(GroupSize group_size, Dir dir, Array& x) const { MIGRAPHX_ASSERT(is_power_of_2(x.size())); MIGRAPHX_ASSERT(is_power_of_2(group_size)); if constexpr(group_size >= 2) { repeat_down_by_2_c([&](auto offset) { constexpr auto step = _c<2 * offset>; repeat(x.size() / step, [&](auto q) { auto base = q * step; // The local direction must change every group_size items // and is flipped if dir is true const auto local_dir = ((base & group_size) > _c<0>) != dir; repeat(offset, [&](auto i) { lane_compare_swap(x[base + i], x[base + i + offset], local_dir); }); }); }); } } template constexpr void lane_merge(Dir dir, Array& x) const { if constexpr(decltype(x.size()){} < 2) return; lane_shuffle(x.size(), dir, x); } template constexpr void lane_sort(Dir dir, Array& x) const { repeat_up_by_2_c<2, decltype(x.size()){} * 2>([&](auto k) { lane_shuffle(k, dir, x); }); } template __device__ void dpp_swap(Mask mask, Dir dir, Array& x) const { MIGRAPHX_ASSERT(mask > 0); MIGRAPHX_ASSERT(mask < MIGRAPHX_WAVEFRONTSIZE); repeat(x.size(), [&](auto item) { auto& src = x[item]; auto partner = readlane_xor(src); if(compare(src, partner, dir)) src = partner; }); } template __device__ void wave_sort(index idx, Array& x) const { static_assert(is_power_of_2(decltype(x.size()){}), "Array size must be power of 2"); #if MIGRAPHX_WAVEFRONTSIZE == 64 constexpr auto max_width = _c<6>; #else constexpr auto max_width = _c<5>; #endif const auto id = idx.local_wave(); repeat(max_width + _c<1>, [&](auto w) { repeat(w, [&](auto i) { auto j = w - i - _c<1>; auto mask = _c<1u> << j; // pow(2, j) dpp_swap(mask, get_bit(id, w) != get_bit(id, j), x); }); if constexpr(w == 0) lane_sort(get_bit(id, w), x); else lane_merge(get_bit(id, w), x); }); } }; MIGRAPHX_AUTO_DEDUCE(bitonic_sort); template struct bitonic_topk { Compare compare_function; static_assert(K <= N, "K must be less than N"); // Constructor used to enable deduction guidelines constexpr bitonic_topk(index_constant, index_constant, Compare cmp) : compare_function(cmp) { } template constexpr bool compare(const T& x, const T& y, Reverse reverse) const { return reverse ^ compare_function(x, y); } template __device__ void sort_step(index idx, T* buf, Len len) const { auto dir = len * 2; repeat_down_by_2_c([&](auto inc) { idx.local_stride(N, [&](auto tid) { auto low = tid & (inc - 1); auto i = (tid * 2) - low; auto j = i + inc; if(j >= N) return; MIGRAPHX_ASSERT(i < N); MIGRAPHX_ASSERT(j < N); bool reverse = (dir & i) == 0; if(compare(buf[i], buf[j], reverse)) swap(buf[i], buf[j]); }); __syncthreads(); }); } template __device__ void merge_step(index idx, T* buf, Len len) const { auto dir = len * 2; idx.local_stride(N, [&](auto tid) { auto low = tid & (len - 1); auto i = (tid * 2) - low; auto j = i + len; if(j >= N) return; if(i % dir >= K) return; MIGRAPHX_ASSERT(i < N); buf[i] = min(buf[i], buf[j], compare_function); }); __syncthreads(); } template __device__ void rebuild_step(index idx, T* buf, Len len) const { auto dir = len * 2; repeat_down_by_2_c([&](auto inc) { idx.local_stride(N, [&](auto tid) { auto low = tid & (inc - 1); auto i = (tid * 2) - low; auto j = i + inc; if(j >= N) return; MIGRAPHX_ASSERT(i < N); if(i % dir >= K) return; bool reverse = (dir & i) == 0; if(compare(buf[i], buf[j], reverse)) swap(buf[i], buf[j]); }); __syncthreads(); }); } template __device__ void block_topk(index idx, T* buf) const { repeat_up_by_2_c([&](auto len) { sort_step(idx, buf, len); }); repeat_up_by_2_c([&](auto len) { merge_step(idx, buf, len); rebuild_step(idx, buf, len); }); } }; template bitonic_topk(N, K, Compare) -> bitonic_topk; } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_SORT_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/tensor_view.hpp000066400000000000000000000101551510465702400312220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_TENSOR_VIEW_HPP #define MIGRAPHX_GUARD_KERNELS_TENSOR_VIEW_HPP #include #include #include #include namespace migraphx { template struct tensor_view_iterator_read { T* view; constexpr auto& operator()(MIGRAPHX_CAPTURE_SOURCE_LOCATION(index_int) n) const { MIGRAPHX_ASSERT(view != nullptr); return (*view)[n]; } }; template using tensor_view_iterator = basic_iota_iterator, index_int>; template struct tensor_view { using type = T; using shape_type = Shape; using index_array = typename Shape::index_array; using iterator = tensor_view_iterator; constexpr Shape get_shape() const { return Shape{}; } constexpr auto size() const { return get_shape().elements(); } struct index_to_offset { index_int offset; #ifdef MIGRAPHX_DEBUG index_int idx = 0; template constexpr index_to_offset(U i) : offset(Shape{}.index(i)) { if constexpr(is_convertible{}) idx = i; else idx = Shape{}.single(i); } #else template constexpr index_to_offset(U i) : offset(Shape{}.index(i)) { } #endif }; constexpr T& operator[](MIGRAPHX_CAPTURE_SOURCE_LOCATION(index_to_offset) i) const { index_to_offset ito = i; MIGRAPHX_WARN( ito.idx < get_shape().elements(), i, "Out of bounds access at index: ", ito.idx); MIGRAPHX_WARN(ito.offset < get_shape().element_space(), i, "Out of bounds access at offset: ", ito.offset); return x[ito.offset]; } constexpr T* data() const { return x; } constexpr auto begin() const { return iterator{0, {this}}; } constexpr auto end() const { return iterator{this->size(), {this}}; } constexpr auto begin_at(index_array i) const { MIGRAPHX_ASSERT(get_shape().single(i) < get_shape().elements()); MIGRAPHX_ASSERT(get_shape().index(i) < get_shape().element_space()); return iterator{get_shape().single(i), {this}}; } template constexpr tensor_view with(U* y) const { static_assert(sizeof(T) == sizeof(U), "Not the same size"); return {y}; } T* x; }; template using get_shape_c = typename T::shape_type; template constexpr tensor_view make_tensor_view(T* x, Shape) { return {x}; } template constexpr auto reorder_tensor_view(T x, Permutation perm) { return make_tensor_view(x.data(), reorder_shape(x.get_shape(), perm)); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_TENSOR_VIEW_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/tile.hpp000066400000000000000000000132471510465702400276200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_TILE_HPP #define MIGRAPHX_GUARD_KERNELS_TILE_HPP #include #include #include #include namespace migraphx { struct tile { template static constexpr auto pad_shape(Shape) { constexpr Shape s{}; constexpr auto axis = s.strides.size() - _c<1>; constexpr auto strides = transform_i(s.strides, [](auto stride, auto i) { if constexpr(i == decltype(axis){}) { // Pad by 1 element extra to avoid memory bank conflicts return stride + 1; } else { return stride; } }); return make_shape(s.lens, strides); } struct load { template static __device__ auto copy(index idx, T x) { return [=](auto f) { using type = typename T::type; constexpr auto s = pad_shape(make_packed_shape(get_shape_c{})); constexpr auto size = s.element_space(); __shared__ type buffer[size]; auto b = make_tensor_view(buffer, s); local_tensor_copy(idx, x, b); f(b); }; } }; struct store { template static __device__ auto copy(index idx, T x) { return [=](auto f) { using type = typename T::type; constexpr auto s = pad_shape(make_packed_shape(get_shape_c{})); constexpr auto size = s.element_space(); __shared__ type buffer[size]; auto b = make_tensor_view(buffer, s); f(b); local_tensor_copy(idx, b, x); }; } }; struct none { template static __device__ auto copy(index, T x) { return [=](auto f) { f(x); }; } }; template static constexpr auto slice(T x, index_int group, InnerLens, OuterLens) { constexpr auto outer_strides = transform(x.get_shape().strides, InnerLens{}, [](auto stride, auto inner_len) { return stride * inner_len; }); constexpr auto is = make_shape(InnerLens{}, x.get_shape().strides); constexpr auto os = make_shape(OuterLens{}, outer_strides); auto offset = os.index(group); MIGRAPHX_ASSERT((os.element_space() + is.element_space()) == (x.get_shape().element_space() + _c<1>)); MIGRAPHX_ASSERT((is.elements() + group) <= x.get_shape().elements()); MIGRAPHX_ASSERT((is.element_space() + offset) <= x.get_shape().element_space()); return make_tensor_view(x.data() + offset, is); } template static __device__ auto auto_slice(index idx) { return make_transform([=](auto f, auto... xs) { idx.group_stride(OuterLens{}.product(), [=](auto group) { f(slice(xs, group, InnerLens{}, OuterLens{})...); }); }); } template static __device__ auto auto_copy(index idx) { return make_transform([=](auto f, auto... xs) { static_assert(sizeof...(Modes) == sizeof...(xs)); auto invoke = [=](auto... ys) { if constexpr((is_same{} or ...)) __syncthreads(); f(ys...); if constexpr((is_same{} or ...)) __syncthreads(); }; join(invoke, Modes::copy(idx, xs)...); }); } }; template __device__ auto tile_stride(index idx) { if constexpr(Tiled) { return [=](auto... xs) { return idx.local_stride(xs...); }; } else { return [=](auto... xs) { return idx.global_stride(xs...); }; } } template __device__ auto auto_tile(InnerLens, OuterLens) { if constexpr((is_same{} and ...)) { return transform_args(); } else { auto idx = make_index(); return transform_args(tile::auto_slice(idx), tile::auto_copy(idx)); } } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_TILE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/topk.hpp000066400000000000000000000170041510465702400276330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_TOPK_HPP #define MIGRAPHX_GUARD_KERNELS_TOPK_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { template struct topk_pair_t_u { T key; U val; }; template struct topk_pair_u_t { U val; T key; }; template struct topk_pair : conditional_t<(sizeof(T) >= sizeof(U)), topk_pair_t_u, topk_pair_u_t> { template friend constexpr const Stream& operator<<(const Stream& ss, const topk_pair& tp) { ss << "{ " << tp.key << ", " << tp.val << "}"; return ss; } }; constexpr auto select_key() { return [](const auto& p) { return p.key; }; } template constexpr auto compare_topk_pair(Compare compare) { return [=](const auto& x, const auto& y) { if(compare(x.key, y.key)) return true; if(compare(y.key, x.key)) return false; return x.val < y.val; }; } template constexpr auto get_index_type(T) -> conditional_t<(sizeof(Type) < sizeof(index_int)), Type, index_int>; constexpr auto get_index_type() -> uint16_t; template constexpr auto make_get_index(X... x_idxs) { if constexpr(sizeof...(x_idxs) == 1) { auto x_idx = arg_c<0>()(x_idxs...); return [=](auto i) { return i < x_idx.get_shape().elements() ? x_idx[i] : -1; }; } else { return [](auto i) { return i; }; } } template __device__ auto topk_impl(index idx, Compare compare, T init, Y y, YIndex y_idx, X x, XIndices... x_idxs) { using type = typename X::type; constexpr auto n = _c{}.get_shape().lens[Axis]>; constexpr auto k = _c{}.get_shape().lens[Axis]>; constexpr auto aligned_n = _c; constexpr auto aligned_k = _c; using pair = topk_pair 32768), index_int, decltype(get_index_type(x_idxs...))>>; auto get_index = make_get_index(x_idxs...); constexpr auto nlocal_wave = idx.nlocal_wave(); constexpr auto nwave = idx.nwave(); constexpr auto m = k * nwave; constexpr auto aligned_m = _c; if constexpr(aligned_m < aligned_n or nwave == 1) { constexpr auto extra_m = aligned_m - m; constexpr auto over_n = where(n == aligned_n, _c<0>, n - aligned_n / _c<2>); constexpr auto trimmed_n = max(where(over_n < extra_m, n - over_n, n), min(aligned_m, n)); constexpr auto nper_wave = trimmed_n / nwave; constexpr auto nper_lane = _c; MIGRAPHX_ASSERT(nper_wave >= k); MIGRAPHX_ASSERT(trimmed_n <= n); array local_buf; for(index_int i : range(nper_lane)) { local_buf[i].key = init; local_buf[i].val = -1; } // copy to registers idx.local_stride(trimmed_n, [&](auto j, auto i) { local_buf[i].key = x[j]; local_buf[i].val = get_index(j); }); bitonic_sort{compare_topk_pair(compare)}.wave_sort(idx, local_buf); if constexpr(nwave == 1) { const auto local_shape = make_shape(index_ints{}); MIGRAPHX_ASSERT(local_shape.elements() >= trimmed_n); // Copy to output for(index_int i : range(nper_lane)) { auto j = local_shape.index({idx.wave(), idx.local_wave(), i}); if(j >= k) continue; y[j] = local_buf[i].key; y_idx[j] = local_buf[i].val; } } else { __shared__ pair buf[aligned_m]; idx.local_stride(extra_m, [&](auto i) { auto in = i + trimmed_n; auto im = i + m; MIGRAPHX_ASSERT(im < aligned_m); buf[im].key = in < n ? x[in] : init; buf[im].val = get_index(in); }); auto shared_shape = make_shape(index_ints{}); const auto base = idx.local_wave() * nper_lane; for(index_int i : range(nper_lane)) { auto ibase = i + base; if(ibase >= k) continue; auto j = shared_shape.index({idx.wave(), ibase}); MIGRAPHX_ASSERT(j < m); buf[j] = local_buf[i]; } __syncthreads(); bitonic_topk{aligned_m, aligned_k, compare_topk_pair(compare)}.block_topk(idx, buf); // save top K idx.local_stride(k, [&](auto i) { y[i] = buf[i].key; y_idx[i] = buf[i].val; }); } } else { __shared__ pair buf[aligned_n]; // Copy to LDS idx.local_stride(aligned_n, [&](auto i) { auto key = i < x.get_shape().elements() ? x[i] : init; buf[i].key = key; buf[i].val = get_index(i); }); __syncthreads(); bitonic_topk{aligned_n, aligned_k, compare_topk_pair(compare)}.block_topk(idx, buf); // save top K idx.local_stride(k, [&](auto i) { y[i] = buf[i].key; y_idx[i] = buf[i].val; }); } } template __device__ auto topk(Compare compare, T init) { return [=](auto output, auto out_indices, auto input, auto... in_indices) { auto idx = make_index(); slice_schedule(idx, slice_axes())(output, out_indices, input, in_indices...)( [&](auto y, auto y_idx, auto x, auto... x_idxs) { topk_impl(idx, compare, init, y, y_idx, x, x_idxs...); }); }; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_TOPK_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/tuple.hpp000066400000000000000000000152311510465702400300070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_KERNELS_TUPLE_HPP #define MIGRAPHX_GUARD_KERNELS_TUPLE_HPP #include namespace migraphx { namespace tuple_detail { template struct element_storage { [[no_unique_address]] T element; }; template constexpr const auto& get_element(const element_storage& x) { return x.element; } template constexpr auto& get_element(element_storage& x) { return x.element; } struct unpack_t { }; template struct tuple_storage; template struct tuple_storage, Ts...> : element_storage... { template constexpr tuple_storage(Us... ys) : element_storage{static_cast(ys)}... { } template constexpr tuple_storage(unpack_t, U y) : element_storage{static_cast(y[_c])}... { } template constexpr auto operator()(F f) const { return f(static_cast&>(*this).element...); } template constexpr auto operator()(F f) { return f(static_cast&>(*this).element...); } template constexpr auto& operator[](IntegralConstant i) { static_assert(i < sizeof...(Ts), "Out of bounds tuple access"); return get_element(*this); } template constexpr auto& operator[](IntegralConstant i) const { static_assert(i < sizeof...(Ts), "Out of bounds tuple access"); return get_element(*this); } constexpr index_constant size() const { return {}; } constexpr auto empty() const { return size() == _c<0>; } }; template using tuple_base = tuple_detail::tuple_storage::type, Ts...>; } // namespace tuple_detail // NOLINTNEXTLINE #define MIGRAPHX_DEVICE_TUPLE_OP(op, binary_op) \ template \ constexpr tuple& operator op(const tuple& rhs) \ { \ (*this)( \ [&](auto&... xs) { rhs([&](const auto&... ys) { swallow{((xs op ys), 0)...}; }); }); \ return *this; \ } \ template \ friend constexpr auto operator binary_op(const tuple& lhs, const tuple& rhs) \ { \ using result = tuple() binary_op declval())...>; \ return lhs([&](auto&... xs) { \ return rhs([&](const auto&... ys) { return result{xs binary_op ys...}; }); \ }); \ } template struct tuple : tuple_detail::tuple_base { using base = tuple_detail::tuple_base; constexpr tuple() : base(Ts{}...) {} template {} and ...))> constexpr tuple(Us... ys) : base(ys...) { } template {} and ...))> constexpr tuple(tuple y) : base(tuple_detail::unpack_t{}, y) { } MIGRAPHX_DEVICE_TUPLE_OP(+=, +) MIGRAPHX_DEVICE_TUPLE_OP(-=, -) MIGRAPHX_DEVICE_TUPLE_OP(*=, *) MIGRAPHX_DEVICE_TUPLE_OP(/=, /) MIGRAPHX_DEVICE_TUPLE_OP(%=, %) MIGRAPHX_DEVICE_TUPLE_OP(&=, &) MIGRAPHX_DEVICE_TUPLE_OP(|=, |) MIGRAPHX_DEVICE_TUPLE_OP(^=, ^) friend constexpr bool operator==(const tuple& x, const tuple& y) { return x([&](const auto&... xs) { return y([&](const auto&... ys) { return ((xs == ys) and ...); }); }); } friend constexpr bool operator!=(const tuple& x, const tuple& y) { return not(x == y); } friend constexpr bool operator<(const tuple& x, const tuple& y) { return x([&](const auto&... xs) { return y([&](const auto&... ys) { return fold([&](auto a, auto b) { return a == 0 ? b() : a; })(0, [&] { return (xs < ys) ? -1 : (ys < xs) ? 1 : 0; }...); }); }) < 0; } friend constexpr bool operator>(const tuple& x, const tuple& y) { return y < x; } friend constexpr bool operator<=(const tuple& x, const tuple& y) { return not(x > y); } friend constexpr bool operator>=(const tuple& x, const tuple& y) { return not(x < y); } }; template constexpr tuple make_tuple(Ts... xs) { return {xs...}; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_TUPLE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/type_traits.hpp000066400000000000000000000176361510465702400312400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_TYPE_TRAITS_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_TYPE_TRAITS_HPP #include #include namespace migraphx { template using void_t = void; template U private_declval(int); template T private_declval(long); template auto declval() noexcept -> decltype(private_declval(0)); template struct is_callable_impl : false_type { }; template struct is_callable_impl()(declval()...))>, F, Ts...> : true_type { }; template using is_callable = is_callable_impl; template struct type_identity { using type = T; }; template struct enable_if { }; template struct enable_if { using type = T; }; template using enable_if_t = typename enable_if::type; template struct conditional { using type = T; }; template struct conditional { using type = F; }; template using conditional_t = typename conditional::type; // NOLINTNEXTLINE #define MIGRAPHX_BUILTIN_TYPE_TRAIT1(name) \ template \ struct name : bool_constant<__##name(T)> \ { \ } // NOLINTNEXTLINE #define MIGRAPHX_BUILTIN_TYPE_TRAIT2(name) \ template \ struct name : bool_constant<__##name(T, U)> \ { \ } // NOLINTNEXTLINE #define MIGRAPHX_BUILTIN_TYPE_TRAITN(name) \ template \ struct name : bool_constant<__##name(Ts...)> \ { \ } // MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_arithmetic); // MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_destructible); // MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_nothrow_destructible); // MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_pointer); // MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_scalar); // MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_signed); // MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_void); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_abstract); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_aggregate); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_array); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_class); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_compound); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_const); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_empty); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_enum); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_final); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_floating_point); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_function); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_fundamental); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_integral); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_literal_type); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_lvalue_reference); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_member_function_pointer); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_member_object_pointer); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_member_pointer); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_object); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_pod); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_polymorphic); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_reference); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_rvalue_reference); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_standard_layout); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_trivial); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_trivially_copyable); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_trivially_destructible); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_union); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_unsigned); MIGRAPHX_BUILTIN_TYPE_TRAIT1(is_volatile); MIGRAPHX_BUILTIN_TYPE_TRAIT2(is_assignable); MIGRAPHX_BUILTIN_TYPE_TRAIT2(is_base_of); MIGRAPHX_BUILTIN_TYPE_TRAIT2(is_convertible); MIGRAPHX_BUILTIN_TYPE_TRAIT2(is_nothrow_assignable); MIGRAPHX_BUILTIN_TYPE_TRAIT2(is_same); MIGRAPHX_BUILTIN_TYPE_TRAIT2(is_trivially_assignable); MIGRAPHX_BUILTIN_TYPE_TRAITN(is_constructible); MIGRAPHX_BUILTIN_TYPE_TRAITN(is_nothrow_constructible); MIGRAPHX_BUILTIN_TYPE_TRAITN(is_trivially_constructible); template struct remove_cv { using type = T; }; template struct remove_cv : remove_cv { }; template struct remove_cv : remove_cv { }; template using remove_cv_t = typename remove_cv::type; template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template struct remove_reference { using type = T; }; template using remove_reference_t = typename remove_reference::type; template struct add_pointer : type_identity::type*> { }; template using add_pointer_t = typename add_pointer::type; template struct is_void : is_same> { }; template struct common_type; template struct common_type { using type = T; }; template struct common_type { using type = decltype(true ? declval() : declval()); }; template struct common_type { using type = typename common_type::type, Us...>::type; }; template using common_type_t = typename common_type::type; #define MIGRAPHX_REQUIRES(...) enable_if_t<__VA_ARGS__, int> = 0 constexpr unsigned long long int_max(unsigned long n) { // Note, left shift cannot be used to get the maximum value of int64_type or // uint64_type because it is undefined behavior to left shift 64 bits for // these types if(n == sizeof(int64_t)) return -1; return (1ull << (n * 8)) - 1; } template {} or is_floating_point{} or is_same{})> constexpr T numeric_max() { if constexpr(is_integral{}) { if constexpr(is_unsigned{}) return int_max(sizeof(T)); else return int_max(sizeof(T)) / 2; } else if constexpr(is_same{}) return __DBL_MAX__; else if constexpr(is_same{}) return __FLT_MAX__; else if constexpr(is_same{}) return __FLT16_MAX__; else if constexpr(is_same{}) { unsigned short us = 0x7f7f; // Max +ve number encoding in BF16 return __builtin_bit_cast(T, us); } else return 0; } template constexpr auto numeric_lowest() -> decltype(numeric_max()) { if constexpr(is_integral{}) { if constexpr(is_unsigned{}) return 0; else return -numeric_max() - 1; } else { return -numeric_max(); } } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/types.hpp000066400000000000000000000056111510465702400300230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_TYPES_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_KERNELS_TYPES_HPP #include namespace migraphx { #if defined(MIGRAPHX_USE_HIPRTC) using int8_t = __hip_int8_t; using uint8_t = __hip_uint8_t; using int16_t = __hip_int16_t; using uint16_t = __hip_uint16_t; using int32_t = __hip_int32_t; using uint32_t = __hip_uint32_t; using int64_t = __hip_int64_t; using uint64_t = __hip_uint64_t; #else using int8_t = std::int8_t; using uint8_t = std::uint8_t; using int16_t = std::int16_t; using uint16_t = std::uint16_t; using int32_t = std::int32_t; using uint32_t = std::uint32_t; using int64_t = std::int64_t; using uint64_t = std::uint64_t; #endif // MIGRAPHX_USE_HIPRTC using index_int = uint32_t; using diff_int = int32_t; using uintptr_t = uint64_t; static_assert(sizeof(int8_t) == 1, "int8_t must be 1 bytes"); static_assert(sizeof(uint8_t) == 1, "uint8_t must be 1 bytes"); static_assert(sizeof(int16_t) == 2, "int16_t must be 2 bytes"); static_assert(sizeof(uint16_t) == 2, "uint16_t must be 2 bytes"); static_assert(sizeof(int32_t) == 4, "int32_t must be 4 bytes"); static_assert(sizeof(uint32_t) == 4, "uint32_t must be 4 bytes"); static_assert(sizeof(int64_t) == 8, "int64_t must be 8 bytes"); static_assert(sizeof(uint64_t) == 8, "uint64_t must be 8 bytes"); #define MIGRAPHX_DEVICE_CONSTEXPR constexpr __device__ __host__ // NOLINT // NOLINTNEXTLINE #define MIGRAPHX_AUTO_DEDUCE(name) \ template \ __host__ __device__ name(Ts...) -> name; template using vec = T __attribute__((ext_vector_type(N))); using half = _Float16; using half2 = migraphx::vec; using bf16 = __bf16; } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/unpack_fp4.hpp000066400000000000000000000043051510465702400307100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_UNPACK_FP4_HPP #define MIGRAPHX_GUARD_KERNELS_UNPACK_FP4_HPP #include #include #include #include #include namespace migraphx { template __device__ void unpack_fp4(Input input, Output output) { const auto input_shape = input.get_shape(); make_index().global_stride(input_shape.elements(), [&](auto i) { auto in_idx = input_shape.multi(i); auto out_idx = in_idx; out_idx[Axis] *= 2; // unpacking 2 unsigned parts // unpacking 4 least significant bits first uint8_t fp4_val = input[in_idx]; output[out_idx] = cast_from_fp4(fp4_val); out_idx[Axis] += 1; fp4_val = fp4_val >> 4u; output[out_idx] = cast_from_fp4(fp4_val); }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_UNPACK_FP4_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/unpack_int4.hpp000066400000000000000000000042771510465702400311050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_UNPACK_INT4_HPP #define MIGRAPHX_GUARD_KERNELS_UNPACK_INT4_HPP #include "migraphx/kernels/types.hpp" #include #include namespace migraphx { template __device__ void unpack_int4(Output output, Input input) { const auto input_shape = input.get_shape(); make_index().global_stride(input_shape.elements(), [&](auto i) { auto idx = input_shape.multi(i); idx[Axis] *= 2; const auto input_val = input[i]; // unpack_int4 op's normalize_compute_shape will ensure that Input::type is either uint8_t // or int8_t if constexpr(is_unsigned{}) output[idx] = input_val & 0xfu; else // NOLINTNEXTLINE (hicpp-signed-bitwise) output[idx] = static_cast(static_cast(input_val) << 4) >> 4; idx[Axis] += 1; output[idx] = input_val >> 4; }); } } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/vec.hpp000066400000000000000000000135701510465702400274370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_VEC_HPP #define MIGRAPHX_GUARD_KERNELS_VEC_HPP #include #include #include #include #include namespace migraphx { template constexpr auto vec_size(vec) { return index_constant{}; } template constexpr auto vec_size(T, ...) // NOLINT { return index_constant<0>{}; } template constexpr auto vec_size() { return decltype(vec_size(declval())){}; } template constexpr auto is_any_vec() { if constexpr(sizeof...(Ts) == 0) return false_type{}; else return bool_constant<((vec_size() + ...) > 0)>{}; } template constexpr auto vec_at(T x, I i) { if constexpr(vec_size() == 0) return x; else { MIGRAPHX_ASSERT(i < vec_size()); return x[i]; } } template using vec_type = decltype(vec_at(T{}, 0)); template constexpr auto common_vec_size() { return fold([](auto x, auto y) { if constexpr(x > y) return x; else return y; })(vec_size()...); } // Bools can not be used as a vector type so convert it to uint8 template __device__ __host__ T* remove_bool(T* x) { return x; } inline __device__ __host__ uint8_t* remove_bool(bool* x) { return reinterpret_cast(x); } template __device__ __host__ auto as_vec(T* x) { if constexpr(N < 2) return x; else return reinterpret_cast*>(x); } template using safe_vec = vec{}, uint8_t, T>, N>; template constexpr auto vec_transform(Ts... xs) { return [=](auto f) { if constexpr(is_any_vec()) { using type = decltype(f(vec_at(xs, 0)...)); constexpr auto size = common_vec_size(); safe_vec result = {0}; for(int i = 0; i < size; i++) result[i] = f(vec_at(xs, i)...); return result; } else { return f(xs...); } }; } // Return a vector type of N from index i in another larger vector // N will be 2 for half2 packing template constexpr vec, N> vec_packed_at(T x, I i) { if constexpr(vec_size() == 0) return vec{x}; else { MIGRAPHX_ASSERT((i + N) <= vec_size()); vec, N> result = {0}; for(int j = 0; j < N; j++) { result[j] = x[i + j]; } return result; } } template constexpr auto vec_packed_transform(Ts... xs) { return [=](auto f) { if constexpr(is_any_vec()) { using type = vec_type(xs, 0)...))>; constexpr auto size = common_vec_size(); safe_vec result = {0}; for(int i = 0; i < size / N; i++) { // Call the function with packed vectors safe_vec r = f(vec_packed_at(xs, i * N)...); // Copy the packed vectors to the result for(int j = 0; j < N; j++) result[i * N + j] = r[j]; } return result; } else { return f(xs...); } }; } template constexpr auto vec_reduce(T x, Op op) { if constexpr(vec_size() < 2) return vec_type{x}; else { vec_type result = x[0]; for(int i = 1; i < vec_size(); i++) result = op(result, x[i]); return result; } } template constexpr auto vec_generate(F f) { using type = decltype(f(_c<0>)); return sequence_c([&](auto... is) { return safe_vec{f(is)...}; }); } template struct implicit_conversion_op { T x; template constexpr operator vec() const { if constexpr(vec_size() == 0) { return x; } else { static_assert(vec_size() == N, "Vector mismatch size"); return __builtin_convertvector(x, vec); } } template constexpr operator U() const { return static_cast(x); } }; template constexpr implicit_conversion_op implicit_conversion(T x) { return {x}; } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_VEC_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/kernels/include/migraphx/kernels/vectorize.hpp000066400000000000000000000176021510465702400306740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_KERNELS_VECTORIZE_HPP #define MIGRAPHX_GUARD_KERNELS_VECTORIZE_HPP #include #include namespace migraphx { template constexpr auto tensor_vec_size() { return vec_size(); } template constexpr auto tensor_vec_size(T) { return tensor_vec_size(); } template constexpr auto shape_step(Shape s, Axis) { static_assert(N > 0, "Vector size must be non-zero"); return sequence(s.lens.size(), [&](auto... is) { auto lens = transform(s.lens, index_ints{}, [&](auto i, auto j) { constexpr auto axis = Axis::to(); MIGRAPHX_ASSERT(i != 0); MIGRAPHX_ASSERT(j != axis or i % N == 0); if(j == axis) return i / N; else return i; }); auto strides = transform(s.strides, index_ints{}, [&](auto i, auto j) { constexpr auto axis = Axis::to(); // If stride of the axis is zero then we dont need to adjust the other strides if(Shape{}.strides[axis] == 0) return i; MIGRAPHX_ASSERT(j == axis or i % N == 0); if(j == axis) return i; else return i / N; }); MIGRAPHX_ASSERT(make_shape(lens, strides).elements() * N == s.elements()); MIGRAPHX_ASSERT(strides[Axis{}] == 0 or make_shape(lens, strides).element_space() * N == s.element_space()); return make_shape(lens, strides); }); } template __device__ __host__ auto as_vec(T x, Axis axis) { if constexpr(N < 2) return x; else return make_tensor_view(as_vec(remove_bool(x.data())), shape_step(x.get_shape(), axis)); } template constexpr auto tensor_step(T x, Axis axis) { if constexpr(N < 2) { return x; } else { constexpr auto s = decltype(x.get_shape()){}; MIGRAPHX_ASSERT(s.strides[axis] == 0); return make_tensor_view(x.data(), shape_step(s, axis)); } } template __device__ __host__ auto as_vec(IntegralConstant ic, T&& x) { return as_vec(x); } template constexpr index_int find_vector_axis_c(Shape s) { // Find the fastest axis that is not broadcasted index_int axis = 0; for(index_int i = 1; i < s.lens.size(); i++) { if(s.strides[i] == 0) continue; if(s.strides[axis] == 0 or pack_compare(less{}, pack(s.strides[i], s.lens[i]), pack(s.strides[axis], s.lens[axis]))) axis = i; } return axis; } template constexpr index_int find_vector_axis_c(Shapes... ss) { const bool all_broadcasted = (ss.broadcasted() and ...); index_int axis = 0; bool b = false; by([&](auto s) { if(b) return; // Skip broadcasted shapes if there are shapes not broadcasted if(not all_broadcasted and s.broadcasted()) return; axis = find_vector_axis_c(s); if(s.strides[axis] == 1) b = true; })(ss...); if(not b) return -1; return axis; } template constexpr auto find_vector_axis(Shapes...) { return _c; } template constexpr auto is_vectorizable_c(Axis axis, Shapes... ss) { return ((axis < ss.lens.size() and ss.lens[axis] % N == 0 and // Only vectorize broadcasted types with stride 0, since this causes issues in the // preloader ((not ss.broadcasted() and ss.strides[axis] == 1) or ss.strides[axis] == 0)) and ...); } template constexpr auto is_vectorizable(Axis, Shapes...) { return _c(Axis::to(), Shapes{}...)>; } template constexpr auto find_vectorize_size(P pred) { if constexpr(decltype(pred(_c<4>)){}) return _c<4>; else if constexpr(decltype(pred(_c<2>)){}) return _c<2>; else return _c<1>; } template __host__ __device__ auto auto_vectorize(T x) { if constexpr(tensor_vec_size() == 0) { constexpr auto axis = find_vector_axis(x.get_shape()); constexpr auto n = find_vectorize_size([&](auto i) { return is_vectorizable(axis, x.get_shape()); }); return as_vec(x, axis); } else { return x; } } template inline __device__ __host__ auto auto_vectorize_impl(F f, Ts... xs) { // TODO: Just check there a single axis of 1 constexpr bool packed_or_broadcasted = ((xs.get_shape().packed() or xs.get_shape().broadcasted()) and ...); if constexpr(packed_or_broadcasted) { constexpr auto axis = decltype(find_vector_axis(xs.get_shape()...)){}; constexpr auto n = find_vectorize_size( [&](auto i) { return is_vectorizable(axis, xs.get_shape()...); }); by( [&](auto x) { constexpr auto s = decltype(x.get_shape()){}; if constexpr(axis < s.strides.size()) { MIGRAPHX_ASSERT(s.strides[axis] == 0 or s.strides[axis] == 1); MIGRAPHX_ASSERT(s.lens[axis] > 0); MIGRAPHX_ASSERT(n == 1 or s.lens[axis] % n == 0); if constexpr(s.strides[axis] == 0) return tensor_step(x, axis); else return as_vec(x, axis); } else { return x; } }, f)(xs...); } else { f(xs...); } } inline __device__ __host__ auto auto_vectorize() { return make_transform([](auto f, auto... xs) { auto_vectorize_impl(f, xs...); }); } template __device__ __host__ auto vectorize_tensor(T x) { constexpr auto shape = get_shape_c{}; if constexpr(shape.lens[Axis] == 1) return x; else if constexpr(shape.strides[Axis] == 0) return tensor_step(x, _c); else return as_vec(x, _c); } template __device__ __host__ auto vectorize() { return make_transform([](auto f, auto... xs) { if constexpr(N < 2) { f(xs...); } else { f(vectorize_tensor(xs)...); } }); } } // namespace migraphx #endif // MIGRAPHX_GUARD_KERNELS_VECTORIZE_HPP ROCm-AMDMIGraphX-46524e8/src/targets/gpu/logsoftmax.cpp000066400000000000000000000041031510465702400224600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_logsoftmax::compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2).standard(); return op.normalize_compute_shape({inputs.at(0)}); } argument hip_logsoftmax::compute(context& ctx, const shape&, const std::vector& args) const { auto n_dim = args.front().get_shape().lens().size(); auto tuned_axis = tune_axis(n_dim, op.axis, op.name()); device::logsoftmax(ctx.get_stream().get(), args.back(), args.front(), tuned_axis); return args.back(); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/loop.cpp000066400000000000000000000103131510465702400212460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_loop::compute_shape(std::vector inputs, std::vector mods) const { auto input_num = (inputs.size() - 2) / 2; inputs.erase(inputs.begin() + input_num, inputs.end()); return op.compute_shape(inputs, std::move(mods)); } struct gpu_loop { int64_t max_iterations = 0; template void copy(context& ctx, const argument& src, T& dst) const { argument arg_dst{src.get_shape(), &dst}; copy_from_gpu(ctx, src, arg_dst); } template void copy(context& ctx, T src, const argument& dst) const { argument arg_src{dst.get_shape(), &src}; copy_to_gpu(ctx, arg_src, dst); } void append(const std::vector&, const std::vector&, const std::vector&, int64_t, int64_t) const { } void set_zero(context& ctx, const std::vector& concatenated_outputs, int iter) const { if(iter >= max_iterations) return; auto elem_num = max_iterations - iter; for(const auto& out : concatenated_outputs) { auto s = out.get_shape(); auto size = s.bytes() / max_iterations; auto lens = s.lens(); lens[0] = elem_num; shape ss{s.type(), lens}; assert(ss.bytes() + iter * size <= out.get_shape().bytes()); device::fill(ctx.get_stream().get(), argument(ss, out.data() + iter * size), 0); } } std::unordered_map get_output_params(const module& m) const { auto get_output_index = [](const std::string& name) { std::string out_prefix = "#output_"; auto loc = name.find(out_prefix); if(loc != std::string::npos) { return std::stoi(name.substr(loc + out_prefix.size())); } return -1; }; const auto& param_names = m.get_parameter_names(); std::unordered_map result; for(const auto& name : param_names) { auto index = get_output_index(name); if(index == -1) continue; result[name] = index; } return result; } }; argument hip_loop::compute(context& ctx, const shape&, const std::vector& args, const std::vector& mods, const std::function( module_ref&, const std::unordered_map&)>& run) const { return run_loop(gpu_loop{op.max_iterations}, op.scan_output_directions, ctx, args, mods, run); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/lowering.cpp000066400000000000000000000540021510465702400221260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_SET_GEMM_PROVIDER) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_MIOPEN_POOLING) struct miopen_apply { module* mod = nullptr; module_pass_manager* mpm = nullptr; const lowering* pass = nullptr; std::unordered_map> apply_map{}; instruction_ref last{}; bool offload_copy = false; bool compute_fp32 = false; context& get_context() const { assert(pass != nullptr); assert(pass->ctx != nullptr); return *pass->ctx; } void check_shape(shape x, instruction_ref i) { assert(x == i->get_shape()); (void)x; (void)i; } void init() { assert(mod != nullptr); assert(pass != nullptr); #if MIGRAPHX_USE_ROCBLAS compute_fp32 = get_compute_fp32_flag(); #endif offload_copy = (mod == mpm->get_root_module()) ? pass->offload_copy : false; add_extend_op("argmax"); add_extend_op("argmin"); add_extend_op("fixed_pad"); add_extend_op("logsoftmax"); add_extend_op("multinomial"); add_extend_op("nonzero"); add_extend_op("prefix_scan_sum"); add_extend_op("reverse"); add_extend_op("rnn_var_sl_last_output"); add_extend_op("rnn_var_sl_shift_output"); add_extend_op("rnn_var_sl_shift_sequence"); add_generic_op("contiguous"); add_pooling_op(); #if MIGRAPHX_USE_MIOPEN add_convolution_op("convolution"); add_convolution_op("convolution_backwards"); add_convolution_op("quant_convolution"); add_extend_op("lrn"); #endif #if MIGRAPHX_USE_ROCBLAS or MIGRAPHX_USE_HIPBLASLT add_gemm_op("dot"); add_gemm_op("quant_dot"); #endif add_if_op(); add_loop_op(); add_neg_op(); add_nms_op(); add_lrn_op(); add_convolution_backwards_op(); add_select_module_op(); add_reshape_lazy_op(); add_group_query_attention_op(); add_scan_slice_op(); } void copy_params() const { if(not offload_copy) return; for(auto ins : iterator_for(*mod)) { if(ins->name() != "@param") continue; // parameter no outputs, no need to insert copy to gpu if(ins->outputs().empty()) continue; auto pos = std::next(ins); auto a = insert_allocation(pos, ins->get_shape()); auto c = mod->insert_instruction(pos, make_op("hip::copy_to_gpu"), ins, a); mod->replace_instruction(ins, c); } // return instruction auto ret = std::prev(mod->end()); if(ret->name() == "@return") { const auto& inputs = ret->inputs(); // each input of ret need to be copied from gpu to host, and replace // output with copy output for(const auto& in : inputs) { auto p_output = mod->insert_instruction(ret, make_op("hip::copy_from_gpu"), in); instruction::replace_argument(ret, in, p_output); } } // else branch to handle legacy program without the return instruction else { mod->add_instruction(make_op("hip::copy_from_gpu"), ret); } } void apply() { init(); for(auto it = mod->begin(); it != mod->end(); it++) { auto s = it->get_shape(); auto attrs = it->get_operator().attributes(); if(apply_map.count(it->name()) > 0) { check_shape(s, apply_map.at(it->name())(it)); } else if(has_compiler_for(it->name())) { check_shape(s, insert_precompile_op(it)); } else if(attrs.contains("target")) { check_shape(s, insert_custom_op(it, attrs)); } if(attrs.contains("prefill")) { insert_fill(it, attrs.at("prefill")); } } copy_params(); } void insert_fill(instruction_ref ins, value v) const { instruction_ref alloc = instruction::get_output_alias(ins, true); if(alloc == ins) return; auto fill = mod->insert_instruction(ins, make_op("hip::fill", {{"value", v}}), alloc); instruction::replace_argument(ins, alloc, fill); } instruction_ref insert_custom_op(instruction_ref ins, const value& attrs) const { const auto& custom_op = ins->get_operator(); if(attrs.at("target") == "cpu") { auto s = ins->get_shape(); std::vector cpu_inputs; auto inputs = ins->inputs(); auto output = inputs.back(); std::transform( inputs.begin(), inputs.end(), std::back_inserter(cpu_inputs), [&](auto in) { return mod->insert_instruction(ins, make_op("hip::copy_from_gpu"), in); }); cpu_inputs.front() = mod->insert_instruction(ins, make_op("hip::sync_stream"), cpu_inputs); auto cpu_out = mod->insert_instruction(ins, custom_op, cpu_inputs); auto gpu_out = mod->insert_instruction(ins, make_op("hip::copy_to_gpu"), cpu_out, output); return mod->replace_instruction(ins, gpu_out); } return ins; } instruction_ref insert_precompile_op(instruction_ref ins) const { auto output = insert_allocation(ins, ins->get_shape()); std::vector refs = ins->inputs(); refs.push_back(output); return mod->replace_instruction( ins, make_op("gpu::precompile_op", {{"op", to_value(ins->get_operator())}}), refs, ins->module_inputs()); } instruction_ref insert_allocation(instruction_ref ins, const shape& s) const { return mod->insert_instruction(ins, make_op("allocate", {{"shape", to_value(s)}})); } #if MIGRAPHX_USE_ROCBLAS or MIGRAPHX_USE_HIPBLASLT template void add_gemm_op(const std::string& name) { apply_map.emplace(name, [=](instruction_ref ins) { std::vector refs = ins->inputs(); assert(refs.size() == 2); auto output = insert_allocation(ins, ins->get_shape()); refs.push_back(output); bool has_fp8_inputs = std::any_of(ins->inputs().begin(), ins->inputs().end(), [](auto i_input) { return contains(fp8_types{}.get(), i_input->get_shape().type()); }); // Check if user explicitly sets rocBLAS as GEMM provider, or // if the hardware cannot support hipblaslt, or // if the hardware is defaulted to use rocBLAS (such as gfx90). if(not has_fp8_inputs and ((string_value_of(MIGRAPHX_SET_GEMM_PROVIDER{}) == "rocblas") or not hipblaslt_supported() or gpu::gfx_default_rocblas())) { return mod->replace_instruction( ins, rocblas_gemm{Op{}, 1, 0, compute_fp32}, refs); } std::string op_name = "gpu::hip_gemm"; if(contains(name, "quant_")) { op_name = "gpu::hip_quant_gemm"; } operation gemm_op = make_op(op_name); return mod->replace_instruction( ins, make_op("gpu::hipblaslt_op", {{"op", to_value(gemm_op)}}), ins->inputs().at(0), ins->inputs().at(1), output); }); } #endif #if MIGRAPHX_USE_MIOPEN void add_convolution_op(const std::string& name) { apply_map.emplace(name, [=](instruction_ref ins) { operation conv = make_op("gpu::" + name, {{"op", ins->get_operator().to_value()}}); auto output = insert_allocation(ins, ins->get_shape()); return mod->replace_instruction(ins, make_op("gpu::miopen_op", {{"op", to_value(conv)}}), ins->inputs().at(0), ins->inputs().at(1), output); }); } #endif // add_generic_op just constructs the operator with no fields whereas add_extend_op copies over // the fields Since it doesn't have fields its default constructed void add_generic_op(const std::string& name) { add_generic_op(name, "gpu::" + name); } void add_generic_op(const std::string& op_name, const std::string& gpu_name) { apply_map.emplace(op_name, [=](instruction_ref ins) { auto output = insert_allocation(ins, ins->get_shape()); std::vector refs = ins->inputs(); refs.push_back(output); return mod->replace_instruction(ins, make_op(gpu_name), refs); }); } void add_extend_op(const std::string& name) { add_extend_op(name, "gpu::" + name); } void add_extend_op(const std::string& op_name, const std::string& gpu_name) { apply_map.emplace(op_name, [=](instruction_ref ins) { auto&& op = ins->get_operator(); auto output = insert_allocation(ins, ins->get_shape()); std::vector refs = ins->inputs(); refs.push_back(output); return mod->replace_instruction(ins, make_op(gpu_name, op.to_value()), refs); }); } static bool use_miopen_pooling(instruction_ref ins) { if(enabled(MIGRAPHX_DISABLE_MIOPEN_POOLING{}) or not contains({shape::float_type, shape::half_type}, ins->get_shape().type())) return false; auto&& op = ins->get_operator(); auto op_val = op.to_value(); auto mode = op_val.at("mode").to(); if(op_val.at("count_include_pad").to() and mode == op::pooling_mode::average) return false; if(mode == op::pooling_mode::lpnorm) return false; auto op_padding = op_val.at("padding").to_vector(); auto kdims = ins->get_shape().lens().size() - 2; return std::equal(op_padding.begin(), op_padding.begin() + kdims, op_padding.begin() + kdims, op_padding.end()); } void add_pooling_op() { apply_map.emplace("pooling", [=](instruction_ref ins) { if(not use_miopen_pooling(ins)) return insert_precompile_op(ins); #if MIGRAPHX_USE_MIOPEN auto output = insert_allocation(ins, ins->get_shape()); std::vector refs = ins->inputs(); auto&& op = ins->get_operator(); refs.push_back(output); return mod->replace_instruction(ins, make_op("gpu::pooling", op.to_value()), refs); #else return insert_precompile_op(ins); #endif }); } // use 0 - input to represent neg void add_neg_op() { apply_map.emplace("neg", [=](instruction_ref ins) { auto s = ins->get_shape(); std::vector zeros(s.elements(), 0.0f); auto l0 = mod->add_literal(literal(s, zeros)); auto output = insert_allocation(ins, s); return mod->replace_instruction( ins, make_op("gpu::sub"), l0, ins->inputs().front(), output); }); } // add input and output argument for the if operator void add_if_op() { apply_map.emplace("if", [=](instruction_ref ins) { std::vector inputs = ins->inputs(); auto cpu_cond = mod->insert_instruction(ins, make_op("hip::copy_from_gpu"), inputs.front()); auto sync_cond = mod->insert_instruction(ins, make_op("hip::sync_stream"), cpu_cond); inputs.front() = sync_cond; return mod->replace_instruction(ins, ins->get_operator(), inputs, ins->module_inputs()); }); } // replace the loop operator with gpu_loop operator void add_loop_op() { apply_map.emplace("loop", [=](instruction_ref ins) { std::vector inputs = ins->inputs(); // copy max_iter from gpu to cpu auto cpu_max_iter = mod->insert_instruction(ins, make_op("hip::copy_from_gpu"), inputs.at(0)); auto cpu_cond = mod->insert_instruction(ins, make_op("hip::copy_from_gpu"), inputs.at(1)); auto synced_max_iter = mod->insert_instruction(ins, make_op("hip::sync_stream"), cpu_max_iter, cpu_cond); inputs.at(0) = synced_max_iter; inputs.at(1) = cpu_cond; auto copy_inputs = inputs; std::transform(copy_inputs.begin(), copy_inputs.end(), std::back_inserter(inputs), [&](auto in) { return insert_allocation(ins, in->get_shape()); }); auto mod_args = ins->module_inputs(); auto output = insert_allocation(ins, ins->get_shape()); const auto* sub_mod = mod_args.front(); auto cond_out = insert_allocation(ins, sub_mod->get_output_shapes().front()); // add cond and mod outputs to the argument list inputs.push_back(cond_out); inputs.push_back(output); return mod->replace_instruction( ins, make_op("gpu::loop", ins->get_operator().to_value()), inputs, mod_args); }); } void add_nms_op() { apply_map.emplace("nonmaxsuppression", [=](instruction_ref ins) { auto s = ins->get_shape(); auto output = insert_allocation(ins, s); std::vector cpu_inputs; auto inputs = ins->inputs(); std::transform( inputs.begin(), inputs.end(), std::back_inserter(cpu_inputs), [&](auto in) { return mod->insert_instruction(ins, make_op("hip::copy_from_gpu"), in); }); cpu_inputs.front() = mod->insert_instruction(ins, make_op("hip::sync_stream"), cpu_inputs); auto cpu_out = mod->insert_instruction(ins, ins->get_operator(), cpu_inputs); auto gpu_out = mod->insert_instruction(ins, make_op("hip::copy_to_gpu"), cpu_out, output); return mod->replace_instruction(ins, gpu_out); }); } void add_lrn_op() { apply_map.emplace("lrn", [=](instruction_ref ins) { auto s = ins->get_shape(); auto output = insert_allocation(ins, s); std::vector cpu_inputs; auto inputs = ins->inputs(); std::transform( inputs.begin(), inputs.end(), std::back_inserter(cpu_inputs), [&](auto in) { return mod->insert_instruction(ins, make_op("hip::copy_from_gpu"), in); }); cpu_inputs.front() = mod->insert_instruction(ins, make_op("hip::sync_stream"), cpu_inputs); auto cpu_out = mod->insert_instruction(ins, ins->get_operator(), cpu_inputs); auto gpu_out = mod->insert_instruction(ins, make_op("hip::copy_to_gpu"), cpu_out, output); return mod->replace_instruction(ins, gpu_out); }); } void add_convolution_backwards_op() { apply_map.emplace("convolution_backwards", [=](instruction_ref ins) { auto s = ins->get_shape(); auto output = insert_allocation(ins, s); std::vector cpu_inputs; auto inputs = ins->inputs(); std::transform( inputs.begin(), inputs.end(), std::back_inserter(cpu_inputs), [&](auto in) { return mod->insert_instruction(ins, make_op("hip::copy_from_gpu"), in); }); cpu_inputs.front() = mod->insert_instruction(ins, make_op("hip::sync_stream"), cpu_inputs); auto cpu_out = mod->insert_instruction(ins, ins->get_operator(), cpu_inputs); auto gpu_out = mod->insert_instruction(ins, make_op("hip::copy_to_gpu"), cpu_out, output); return mod->replace_instruction(ins, gpu_out); }); } /** * Adds dynamic allocation for submodule output parameter. */ void add_select_module_op() { apply_map.emplace("select_module", [=](instruction_ref ins) { auto s = ins->get_shape(); auto output = insert_allocation(ins, s); std::vector inputs = ins->inputs(); inputs.push_back(output); return mod->replace_instruction(ins, ins->get_operator(), inputs, ins->module_inputs()); }); } /** * Adds reshape lazy to reshape ops that can be aliased instead of copied. * `gpu::contiguous` are added before and after the reshape; these contiguous * instructions can be removed by the eliminate_contiguous pass. */ void add_reshape_lazy_op() { apply_map.emplace("reshape", [=](instruction_ref ins) { std::vector before_contiguous_args = ins->inputs(); auto before_alloc = insert_allocation(ins, std::prev(ins)->get_shape()); before_contiguous_args.push_back(before_alloc); auto before_contig = mod->insert_instruction(ins, make_op("gpu::contiguous"), {before_contiguous_args}); auto new_reshape_lazy = mod->insert_instruction( ins, make_op("reshape_lazy", {{"dims", {ins->get_operator().to_value().at("dims")}}}), before_contig); std::vector after_contiguous_args = {new_reshape_lazy}; auto after_alloc = insert_allocation(new_reshape_lazy, new_reshape_lazy->get_shape()); after_contiguous_args.push_back(after_alloc); return mod->replace_instruction(ins, make_op("gpu::contiguous"), after_contiguous_args); }); } void add_group_query_attention_op() { apply_map.emplace("gpu::gqa_rotary_embedding", [=](instruction_ref ins) { auto s = ins->get_shape(); auto output = insert_allocation(ins, s); auto new_inputs = ins->inputs(); new_inputs.push_back(output); return mod->replace_instruction( ins, make_op("gpu::precompile_op", {{"op", to_value(ins->get_operator())}}), new_inputs); }); apply_map.emplace("gpu::concat_past_present", [=](instruction_ref ins) { return mod->replace_instruction(ins, make_op("gpu::precompile_op", {{"op", to_value(ins->get_operator())}, {"output_shape", to_value(ins->get_shape())}}), ins->inputs()); }); } void add_scan_slice_op() { apply_map.emplace("scan_slice", [=](instruction_ref ins) { auto inputs = ins->inputs(); auto cpu_idx = mod->insert_instruction(ins, make_op("hip::copy_from_gpu"), inputs[1]); inputs[1] = mod->insert_instruction(ins, make_op("hip::sync_stream"), cpu_idx); return mod->replace_instruction( ins, mod->insert_instruction(ins, ins->get_operator(), inputs)); }); } }; void lowering::apply(module_pass_manager& mpm) const { miopen_apply{&mpm.get_module(), &mpm, this}.apply(); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/lrn.cpp000066400000000000000000000045331510465702400210770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #if MIGRAPHX_USE_MIOPEN shape miopen_lrn::compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2).not_broadcasted(); return inputs.at(1); } argument miopen_lrn::compute(context& ctx, const shape& output_shape, const std::vector& args) const { float alpha = 1; float beta = 0; auto x_desc = make_tensor(args[0].get_shape()); auto y_desc = make_tensor(output_shape); miopenLRNForward(ctx.get_stream().get_miopen(), ldesc.get(), &alpha, x_desc.get(), args[0].implicit(), &beta, y_desc.get(), args[1].implicit(), false, nullptr); return args[1]; } void miopen_lrn::finalize(context&, const shape&, const std::vector&) { ldesc = make_lrn(op); } #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/mlir.cpp000066400000000000000000001351151510465702400212500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef MIGRAPHX_MLIR #include #include #include #include #include #include #include #include #include #include #if !defined(MLIR_MIGRAPHX_DIALECT_API_VERSION) || MLIR_MIGRAPHX_DIALECT_API_VERSION != 4 #warning "Incompatible version of rocMLIR library used, disabling" // Only undefine when not using cppcheck #ifndef CPPCHECK #undef MIGRAPHX_MLIR #endif #else #include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_MLIR); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_TUNE_EXHAUSTIVE); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_TUNE_LIMIT); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_TUNING_DB); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_TUNING_CFG); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_ENABLE_SPLITK); #ifdef MIGRAPHX_MLIR template // NOLINT struct mlir_handle { struct ptr { ptr() = default; ptr(std::nullptr_t) {} ptr(T x) : obj(x) {} std::intptr_t get_value() const { static_assert(sizeof(T) == sizeof(std::intptr_t), "MLIR Handle different size"); return reinterpret_cast(obj); } T get() const { return obj; } friend bool operator==(ptr x, ptr y) { return x.get_value() == y.get_value(); } friend bool operator!=(ptr x, ptr y) { return not(x == y); } explicit operator bool() const noexcept { return obj != ptr(); } T obj{}; }; struct deleter { using pointer = ptr; void operator()(pointer x) const { if(x != nullptr) { (void)f(x.obj); } } }; mlir_handle() : handle(nullptr) {} mlir_handle(T p) : handle(ptr{p}) {} T get() const { return handle.get().get(); // NOLINT(readability-redundant-smartptr-get) } T release() { return handle.release().get(); } private: std::unique_ptr handle; }; #define MIGRAPHX_MANAGE_MLIR_HANDLE(T, F) migraphx::gpu::mlir_handle // NOLINT using mlir_context = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirContext, mlirContextDestroy); using mlir_thread_pool = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirLlvmThreadPool, mlirLlvmThreadPoolDestroy); using mlir_dialect_registry = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirDialectRegistry, mlirDialectRegistryDestroy); using mlir_module = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirModule, mlirModuleDestroy); using mlir_operation = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirOperation, mlirOperationDestroy); using mlir_op_printing_flags = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirOpPrintingFlags, mlirOpPrintingFlagsDestroy); using mlir_region = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirRegion, mlirRegionDestroy); using mlir_block = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirBlock, mlirBlockDestroy); using mlir_pass_manager = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirPassManager, mlirPassManagerDestroy); using mlir_tuning_table = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirRockTuningTable, mlirRockTuningTableDestroy); using mlir_tuning_space = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirRockTuningSpace, mlirRockTuningSpaceDestroy); using mlir_tuning_param = MIGRAPHX_MANAGE_MLIR_HANDLE(MlirRockTuningParam, mlirRockTuningParamDestroy); static std::atomic& dump_counter() { // NOLINTNEXTLINE static std::atomic c = 0; return c; } static std::string_view to_string_view(MlirStringRef s) { return {s.data, s.length}; } static MlirStringRef make_mlir_string_ref(const std::string_view& s) { return mlirStringRefCreate(s.data(), s.size()); } template static void mlir_print(F f, T x, Printer printer) { f( x, +[](MlirStringRef s, void* data) { (*reinterpret_cast(data))(to_string_view(s)); }, &printer); } template [[maybe_unused]] static void mlir_print(F f, T x, std::ostream& os) { mlir_print(f, x, [&](auto s) { os << s; }); } template static std::string mlir_print(F f, T x) { std::stringstream ss; mlir_print(f, x, [&](auto s) { ss << s; }); return ss.str(); } struct mlir_logger { std::stringstream ss; mlir_context* ctx; std::optional id; mlir_logger() : ctx(nullptr), id(std::nullopt) {} mlir_logger(mlir_context* context) : ctx(context) { id = mlirContextAttachDiagnosticHandler(ctx->get(), mlir_diagnostic_print_cb, this, nullptr); } ~mlir_logger() { if(id.has_value()) mlirContextDetachDiagnosticHandler(ctx->get(), *id); } mlir_logger(const mlir_logger& other) = delete; mlir_logger& operator=(const mlir_logger& other) = delete; mlir_logger(mlir_logger&& other) noexcept : ss(std::move(other.ss)), ctx(other.ctx), id(other.id) { other.ctx = nullptr; other.id = std::nullopt; } mlir_logger& operator=(mlir_logger other) noexcept { std::swap(ss, other.ss); std::swap(ctx, other.ctx); std::swap(id, other.id); return *this; } std::string str() const { return ss.str(); } void clear() { ss = std::stringstream{}; } static MlirLogicalResult mlir_diagnostic_print_cb(MlirDiagnostic diag, void* logger); MlirLogicalResult handle(MlirDiagnostic diag); }; MlirLogicalResult mlir_logger::mlir_diagnostic_print_cb(MlirDiagnostic diag, void* logger) { return reinterpret_cast(logger)->handle(diag); } MlirLogicalResult mlir_logger::handle(MlirDiagnostic diag) { MlirDiagnosticSeverity sev = mlirDiagnosticGetSeverity(diag); switch(sev) { case MlirDiagnosticSeverity::MlirDiagnosticError: ss << "Error: "; break; case MlirDiagnosticSeverity::MlirDiagnosticWarning: ss << "Warning: "; break; case MlirDiagnosticSeverity::MlirDiagnosticNote: ss << "Note: "; break; case MlirDiagnosticSeverity::MlirDiagnosticRemark: ss << "Remark: "; break; } mlir_print(mlirDiagnosticPrint, diag, [&](auto s) { ss << s; }); ss << std::endl; for(intptr_t i = 0, e = mlirDiagnosticGetNumNotes(diag); i < e; ++i) { (void)handle(mlirDiagnosticGetNote(diag, i)); } return mlirLogicalResultSuccess(); } struct mlir_program { mlir_program() : ctx(mlirContextCreateWithRegistry(get_dialect_registry().get(), /*threadingEnable=*/false)), location(mlirLocationUnknownGet(ctx.get())), mmodule(mlirModuleCreateEmpty(location)), logger(&ctx) { mlirContextSetThreadPool(ctx.get(), get_thread_pool().get()); mlirContextLoadAllAvailableDialects(ctx.get()); } static mlir_dialect_registry& get_dialect_registry() { static std::once_flag init_guard; static mlir_dialect_registry the_registry; // The MLIR registration functions (for dialects and passes) are not // necessarily thread-safe and need to be executed exactly once // (especially since they eventually call non-thread-safe LLVM // initilizations). std::call_once(init_guard, [&]() { the_registry = mlirDialectRegistryCreate(); mlirRegisterRocMLIRDialects(the_registry.get()); mlirRegisterRocMLIRPasses(); }); return the_registry; } static mlir_thread_pool& get_thread_pool() { // To save on overhead, we create one LLVM thread pool and reuse it // across all MLIR contexts as recommended by MLIR upstream. // Note that this is thread-safe as of C++11. static mlir_thread_pool the_pool = mlirLlvmThreadPoolCreate(); return the_pool; } MlirType make_type(shape::type_t t) const { MlirType result; shape::visit(t, [&](auto as) { if(as.type_enum() == shape::float_type) result = mlirF32TypeGet(ctx.get()); else if(as.type_enum() == shape::half_type) result = mlirF16TypeGet(ctx.get()); else if(as.type_enum() == shape::bf16_type) result = mlirBF16TypeGet(ctx.get()); else if(as.type_enum() == shape::fp8e4m3fnuz_type) result = mlirFloat8E4M3FNUZTypeGet(ctx.get()); else if(as.type_enum() == shape::fp8e5m2fnuz_type) result = mlirFloat8E5M2FNUZTypeGet(ctx.get()); else if(as.type_enum() == shape::fp8e4m3fn_type) result = mlirFloat8E4M3FNTypeGet(ctx.get()); else if(as.type_enum() == shape::fp8e5m2_type) result = mlirFloat8E5M2TypeGet(ctx.get()); else if(as.type_enum() == shape::double_type) result = mlirF64TypeGet(ctx.get()); else if(as.is_integral()) { if(as.is_unsigned()) { result = mlirIntegerTypeUnsignedGet(ctx.get(), as.size() * 8); } else { result = mlirIntegerTypeSignedGet(ctx.get(), as.size() * 8); } } else MIGRAPHX_THROW("Unsupported type: " + std::to_string(as.type_enum())); }); return result; } MlirType make_mlir_shaped(const shape& s) const { if(s.dynamic()) MIGRAPHX_THROW("MLIR does not support dynamic shapes"); std::vector lens(s.lens().begin(), s.lens().end()); std::vector strides(s.strides().begin(), s.strides().end()); return rocmlirMIXRShapedTypeGet( lens.size(), lens.data(), strides.data(), make_type(s.type())); } template std::vector make_mlir_shapeds(const Range& r) { std::vector result; std::transform(r.begin(), r.end(), std::back_inserter(result), [&](const auto& s) { return make_mlir_shaped(s); }); return result; } MlirType make_function_type(const std::vector& inputs, const std::vector& outputs) { auto in = make_mlir_shapeds(inputs); auto out = make_mlir_shapeds(outputs); return mlirFunctionTypeGet(ctx.get(), in.size(), in.data(), out.size(), out.data()); } MlirIdentifier id(const std::string_view& s) const { return mlirIdentifierGet(ctx.get(), make_mlir_string_ref(s)); } MlirAttribute attribute(std::int64_t i) const { return mlirIntegerAttrGet(mlirIntegerTypeGet(ctx.get(), 64), i); } MlirAttribute attribute(std::uint64_t i) const { if(i > (std::numeric_limits::max() / 2)) MIGRAPHX_THROW("MLIR cant handle large integer values since they are ambiguous"); return mlirIntegerAttrGet(mlirIntegerTypeGet(ctx.get(), 64), i); } MlirAttribute attribute(unsigned char i) const { return attribute(std::uint64_t(i)); } MlirAttribute attribute(bool b) const { return mlirBoolAttrGet(ctx.get(), b ? 1 : 0); } MlirAttribute attribute(double d) const { return mlirFloatAttrDoubleGet(ctx.get(), mlirF64TypeGet(ctx.get()), d); } MlirAttribute attribute(const std::string& s) const { return mlirStringAttrGet(ctx.get(), make_mlir_string_ref(s)); } MlirAttribute attribute(std::nullptr_t) const { return {}; } template MlirAttribute attribute(const std::vector& v) const { std::vector attributes; attributes.reserve(v.size()); std::transform(v.begin(), v.end(), std::back_inserter(attributes), [&](auto&& x) { return attribute(x); }); return mlirArrayAttrGet(ctx.get(), attributes.size(), attributes.data()); } MlirAttribute attribute(const value& v) const { MlirAttribute attr; v.visit_value([&](auto&& x) { attr = attribute(x); }); return attr; } MlirAttribute attribute(const std::vector& v) const { if(v.empty()) { return mlirArrayAttrGet(ctx.get(), 0, nullptr); } if(not v.front().get_key().empty()) { std::vector attributes = name_attributes(v); return mlirDictionaryAttrGet(ctx.get(), attributes.size(), attributes.data()); } else { std::vector attributes; attributes.reserve(v.size()); std::transform(v.begin(), v.end(), std::back_inserter(attributes), [&](auto&& x) { return attribute(x); }); return mlirArrayAttrGet(ctx.get(), attributes.size(), attributes.data()); } } MlirAttribute attribute(MlirType t) const { return mlirTypeAttrGet(t); } MlirAttribute attribute(MlirAttribute a) const { return a; } template MlirNamedAttribute name_attribute(const std::string_view& key, const T& x) const { MlirNamedAttribute attr; attr.name = id(key); attr.attribute = attribute(x); return attr; } using attribute_t = std::variant, MlirType, MlirAttribute>; using named_attribute_t = std::pair; MlirNamedAttribute name_attribute(const named_attribute_t& na) const { return name_attribute(na.first, std::visit([&](const auto& x) { return attribute(x); }, na.second)); } std::vector name_attributes(const std::vector& named_attrs) const { std::vector attributes; attributes.reserve(named_attrs.size()); std::transform(named_attrs.begin(), named_attrs.end(), std::back_inserter(attributes), [&](const named_attribute_t& a) { return name_attribute(a); }); return attributes; } std::vector name_attributes(const value& v) const { std::vector attributes; attributes.reserve(v.size()); migraphx::transform_if( v.begin(), v.end(), std::back_inserter(attributes), [&](const value& x) { return not x.is_null(); }, [&](const value& x) { return name_attribute(x.get_key(), x.without_key()); }); return attributes; } struct mlir_operation_state { mlir_operation_state(mlir_program& p, const std::string_view& name) : prog(&p), op_state(mlirOperationStateGet(make_mlir_string_ref(name), p.location)) { } mlir_operation_state& add_attributes(const std::vector& named_attrs) { auto attributes = prog->name_attributes(named_attrs); if(not attributes.empty()) { mlirOperationStateAddAttributes(&op_state, attributes.size(), attributes.data()); } return *this; } mlir_operation_state& add_attribute_value(const value& v) { auto attributes = prog->name_attributes(v); if(not attributes.empty()) { mlirOperationStateAddAttributes(&op_state, attributes.size(), attributes.data()); } return *this; } mlir_operation_state& add_regions(std::vector rs) { regions = std::move(rs); return *this; } mlir_operation_state& add_region(mlir_region r) { regions.emplace_back(std::move(r)); return *this; } mlir_operation_state& add_results(const std::vector& outputs) { auto x = prog->make_mlir_shapeds(outputs); if(not x.empty()) { mlirOperationStateAddResults(&op_state, x.size(), x.data()); } return *this; } mlir_operation_state& add_operands(const std::vector& inputs) { if(not inputs.empty()) { mlirOperationStateAddOperands(&op_state, inputs.size(), inputs.data()); } return *this; } mlir_operation create_operation() { std::vector mregions(regions.size()); std::transform(regions.begin(), regions.end(), mregions.begin(), [](const auto& r) { return r.get(); }); if(not mregions.empty()) { mlirOperationStateAddOwnedRegions(&op_state, mregions.size(), mregions.data()); } mlir_operation op(mlirOperationCreate(&op_state)); // Release memory since mlir_operation owns it for(auto& r : regions) r.release(); regions.clear(); return op; } mlir_program* prog; MlirOperationState op_state; std::vector regions = {}; }; mlir_operation_state create_operation_state(const std::string_view& name) { return {*this, name}; } std::vector insert(MlirBlock body, mlir_operation_state ops) { std::vector result; mlir_operation op = ops.create_operation(); auto weak_op = op.get(); mlirBlockAppendOwnedOperation(body, op.release()); auto n = mlirOperationGetNumResults(weak_op); result.reserve(n); transform(range(n), std::back_inserter(result), [&](auto i) { return mlirOperationGetResult(weak_op, i); }); return result; } MlirBlock insert(MlirBlock body, const module& m, std::unordered_map& ins_map) { auto names = m.get_parameter_names(); std::sort(names.begin(), names.end()); std::vector inputs; std::transform(names.begin(), names.end(), std::back_inserter(inputs), [&](const std::string& name) { return m.get_parameter_shape(name); }); std::vector outputs = m.get_output_shapes(); std::vector arg_locs(inputs.size(), location); auto body_inputs = make_mlir_shapeds(inputs); mlir_region region = mlirRegionCreate(); mlir_block fbody = mlirBlockCreate(body_inputs.size(), body_inputs.data(), arg_locs.data()); MlirBlock result = fbody.get(); mlirRegionAppendOwnedBlock(region.get(), fbody.release()); auto ops = create_operation_state("func.func"); ops.add_attributes({{"function_type", make_function_type(inputs, outputs)}, {"sym_name", sym_name}, {"kernel", std::string("mixr")}, {"arch", target_arch}, {"num_cu", num_cu}}); if(enabled(MIGRAPHX_MLIR_ENABLE_SPLITK{})) { ops.add_attributes({{"enable_splitk_for_tuning", mlirUnitAttrGet(ctx.get())}}); } ops.add_region(std::move(region)); insert(body, std::move(ops)); for(auto i : range(names.size())) ins_map[m.get_parameter(names[i])] = mlirBlockGetArgument(result, i); return result; } static bool is_reshape(const std::string& name) { return contains({"reshape", "reshape_lazy", "squeeze", "unsqueeze", "flatten"}, name); } static std::string get_name(instruction_ref ins) { if(ins->name() == "@return") return "func.return"; if(ins->name() == "@literal") return "migraphx.literal"; if(ins->name() == "unpack_int4") return "migraphx.unpack"; if(ins->name() == "convolution_backwards") return "migraphx.backwards_data_convolution"; if(is_reshape(ins->name())) return "migraphx.reshape"; return "migraphx." + ins->name(); } static value get_operator_value(instruction_ref ins) { const operation& op = ins->get_operator(); auto v = op.to_value(); // Reshape operator can have dim 0 or -1. // Avoid passing those on to MLIR: if(is_reshape(op.name())) v = {{"dims", ins->get_shape().lens()}}; if(contains({"convolution", "quant_convolution", "convolution_backwards"}, op.name())) { // Adjust symetrical padding if(v.at("padding").size() == v.at("stride").size()) { auto padding = v.at("padding"); std::copy(padding.begin(), padding.end(), std::back_inserter(v.at("padding"))); } } if(op.name() == "unpack_int4") v["axis"] = ins->get_shape().ndim() - 1; return v; } static shape get_shape(instruction_ref ins) { if(ins->name() == "@return") { assert(ins->inputs().size() == 1); return ins->inputs().front()->get_shape(); } return ins->get_shape(); } static std::string get_symbol_name(const module& m) { return "mlir_" + gen::generate_name_from_ops(m); } static void validate(const module& m) { if(m.begin() == m.end()) MIGRAPHX_THROW("Empty module"); auto last = std::prev(m.end()); if(last->name() != "@return") MIGRAPHX_THROW("Missing @return as last instruction."); } void parse(const module& m) { validate(m); sym_name = get_symbol_name(m); auto mbody = mlirModuleGetBody(mmodule.get()); std::unordered_map ins_map; auto fbody = insert(mbody, m, ins_map); for(auto ins : iterator_for(m)) { if(ins->name() == "@param") continue; if(ins->name() == "contiguous") { ins_map[ins] = ins_map[ins->inputs().at(0)]; continue; } auto name = get_name(ins); auto ops = create_operation_state(name); ops.add_attribute_value(get_operator_value(ins)); if(ins->name() != "@return") ops.add_results({get_shape(ins)}); if(ins->name() == "@literal") { literal r = ins->get_literal(); auto sh = ins->get_shape(); MlirType shaped_type = make_mlir_shaped(sh); MlirType tensor_type = rocmlirMIXRShapedTypeAsTensor(shaped_type); MlirAttribute mlir_value_attr = mlirDenseElementsAttrRawBufferGet(tensor_type, r.get_shape().bytes(), r.data()); ops.add_attributes({{"value", mlir_value_attr}}); } if(ins->name() == "convolution" or ins->name() == "dot") { pp = problem_params{ins->get_operator(), to_shapes(ins->inputs()), ins->get_shape()}; } std::vector inputs; transform( ins->inputs(), std::back_inserter(inputs), [&](auto i) { return ins_map.at(i); }); ops.add_operands(inputs); auto outputs = insert(fbody, std::move(ops)); if(ins->name() != "@return") { assert(outputs.size() == 1); ins_map[ins] = outputs.front(); } } } void run_high_level_pipeline() { mlir_pass_manager pm_front{mlirPassManagerCreate(ctx.get())}; mlirMIGraphXAddHighLevelPipeline(pm_front.get()); logger.clear(); if(mlirLogicalResultIsFailure( mlirPassManagerRunOnOp(pm_front.get(), mlirModuleGetOperation(mmodule.get())))) { std::string error = "Invalid MLIR created: " + logger.str(); if(enabled(MIGRAPHX_TRACE_MLIR{})) { std::cout << error << std::endl; } MIGRAPHX_THROW(error); } } void run_backend_pipeline() { mlir_pass_manager pm_back{mlirPassManagerCreate(ctx.get())}; mlirMIGraphXAddBackendPipeline(pm_back.get(), target_arch.c_str()); logger.clear(); const size_t trace = value_of(MIGRAPHX_TRACE_MLIR{}); static std::mutex mutex; auto mod_op = mlirModuleGetOperation(mmodule.get()); if(trace >= 2) { const std::lock_guard lock(mutex); std::cout << mlir_print(&mlirOperationPrint, mod_op) << std::endl; } if(mlirLogicalResultIsFailure(mlirPassManagerRunOnOp(pm_back.get(), mod_op))) { std::string error = "MLIR backend compilation failed: " + logger.str(); if(enabled(MIGRAPHX_TRACE_MLIR{})) { std::cout << error << std::endl; } MIGRAPHX_THROW(error); } } code_object_op compile(const value& solution) { // 1st pipeline to call run_high_level_pipeline(); std::string tuning_cfg_path = string_value_of(MIGRAPHX_MLIR_TUNING_CFG{}); if(not tuning_cfg_path.empty()) get_module_tuned(); if(not solution.is_null()) set_tuning(solution); // 2nd pipeline to call run_backend_pipeline(); code_object_op op{}; op.symbol_name = sym_name; op.code_object = get_binary(); std::tie(op.global, op.local) = get_launch_params(); return op; } void set_gpu_properties(const context& migraphx_ctx) { const auto& device = migraphx_ctx.get_current_device(); target_arch = device.get_device_name(); num_cu = device.get_cu_count(); } std::pair get_launch_params() const { uint32_t attrs[2]; // returns block and grid sizes mlirGetKernelAttrs(mmodule.get(), attrs); std::size_t local = attrs[0]; std::size_t global = local * attrs[1]; return {global, local}; } value::binary get_binary() const { size_t size = 0; mlirGetBinary(mmodule.get(), &size, nullptr); value::binary result(size); if(mlirGetBinary(mmodule.get(), &size, reinterpret_cast(result.data()))) return result; MIGRAPHX_THROW("Failed to compile mlir program"); } void set_tuning(const value& v) MIGRAPHX_TIDY_CONST { const auto* str = v.if_string(); if(str == nullptr) MIGRAPHX_THROW("mlir tuning solutions must be strings"); if(not mlirRockTuningSetFromStr(mmodule.get(), make_mlir_string_ref(*str))) MIGRAPHX_THROW("Failed setting tuning key: " + *str); } tuning_config get_tuning_config(bool exhaustive) { tuning_config tc; tc.detailed_problem_info = mlir_print(&mlirOperationPrint, mlirModuleGetOperation(mmodule.get())); run_high_level_pipeline(); auto tuning_mode = exhaustive ? RocmlirTuningParamSetKindFull : RocmlirTuningParamSetKindQuick; if(enabled(MIGRAPHX_MLIR_TUNE_EXHAUSTIVE{})) tuning_mode = RocmlirTuningParamSetKindExhaustive; mlir_tuning_space params{mlirRockTuningSpaceCreate(mmodule.get(), tuning_mode)}; const auto limit = value_of(MIGRAPHX_MLIR_TUNE_LIMIT{}, std::numeric_limits::max()); for(auto i : range(std::min(limit, mlirRockTuningGetNumParams(params.get())))) { mlir_tuning_param param{mlirRockTuningParamCreate()}; if(not mlirRockTuningParamGet(params.get(), i, param.get())) MIGRAPHX_THROW("Incorrect mlir tuning parameter: " + std::to_string(i)); std::array perf_key; size_t perf_key_bytes = mlirRockTuningParamToString(param.get(), perf_key.data(), perf_key.size()); if(perf_key_bytes > perf_key.size()) MIGRAPHX_THROW("Tuning perf key was " + std::to_string(perf_key_bytes) + " bytes and thus too long"); tc.solutions.emplace_back( std::string(perf_key.begin(), perf_key.begin() + perf_key_bytes)); } std::array tuning_key; size_t tuning_key_bytes = mlirRockTuningGetKey(mmodule.get(), tuning_key.data(), tuning_key.size()); if(tuning_key_bytes > tuning_key.size()) MIGRAPHX_THROW("Tuning table key was " + std::to_string(tuning_key_bytes) + " bytes and thus too long"); tc.problem = std::string(tuning_key.begin(), tuning_key.begin() + tuning_key_bytes); return tc; } std::string get_tune_params(bool xdlops) const { return get_mlir_perf_for_conv(pp, xdlops); } // This function appends to tuning cfg file that could be // used with rocMLIR tuning scripts. void dump_tuning_cfg(const std::string& prob_config) const { std::string tuning_cfg_path = string_value_of(MIGRAPHX_MLIR_TUNING_CFG{}); if(not tuning_cfg_path.empty()) { std::vector tokens = split_string(prob_config, '\t'); std::string prob = tokens[2]; if(starts_with(prob, "conv")) { tuning_cfg_path += ".conv"; } else { tuning_cfg_path += ".gemm"; } std::ofstream tuning_cfg(tuning_cfg_path, std::ios::app); prob = trim(prob, [](unsigned char c) { return (c == '\0') or (std::isspace(c) != 0); }); tuning_cfg << prob << std::endl; } } static std::pair load_tuning_table() { mlir_tuning_table tuning_table{mlirRockTuningTableCreate()}; bool found_table = false; std::string tuning_db_path = string_value_of(MIGRAPHX_MLIR_TUNING_DB{}); if(not tuning_db_path.empty()) { std::ifstream tuning_db_tsv(tuning_db_path); if(tuning_db_tsv) { found_table = true; std::string line; while(std::getline(tuning_db_tsv, line)) { std::vector tokens = split_string(line, '\t'); std::string arch = tokens[0]; const std::string& num_cu = tokens[1]; const std::string& prob = tokens[2]; const std::string& perf = tokens[3]; std::string key = arch.append("\t").append(num_cu).append("\t").append(prob); mlirRockTuningUpdateTable(tuning_table.get(), make_mlir_string_ref(key), make_mlir_string_ref(perf), 1.0); } } } else { found_table = false; std::cerr << "WARNING: MLIR tuning db not found. Please set MIGRAPHX_MLIR_TUNING_DB for " "optimal performance." << std::endl; } return std::make_pair(std::move(tuning_table), found_table); } bool get_module_tuned() const { static std::pair tuning_table = load_tuning_table(); if(not mlirRockTuningSetFromTable(tuning_table.first.get(), mmodule.get())) { std::array prob_config; size_t prob_config_bytes = mlirRockTuningGetKey(mmodule.get(), prob_config.data(), prob_config.size()); if(prob_config_bytes >= prob_config.size()) { std::cerr << "MLIR tuning key overflowed buffer, needed " << prob_config_bytes << " bytes" << std::endl; return false; } std::string prob_config_str(prob_config.begin(), prob_config.begin() + prob_config_bytes); if(tuning_table.second) { std::cerr << "NOTE: MLIR tuning table did not include a key for " << prob_config_str << std::endl; } dump_tuning_cfg(prob_config_str); return false; } return true; } mlir_context ctx; MlirLocation location; mlir_module mmodule; mlir_logger logger; problem_params pp; std::deque strings{}; std::string target_arch = ""; std::size_t num_cu = 0; std::string sym_name; }; bool is_reduce(const instruction& ins) { return contains(ins.name(), "reduce"); } static void rewrite_reduce(module& m) { for(auto i : iterator_for(m)) { if(is_reduce(*i)) { auto reduce_op = i->get_operator().to_value(); auto reduce_op_name = i->get_operator().name(); auto reduce_axes = reduce_op["axes"].to_vector(); auto reduce_lens = i->get_shape().lens(); auto in_shape = i->inputs().front()->get_shape(); const auto& in_lens = in_shape.lens(); assert(in_shape.standard()); assert(reduce_lens.size() == in_lens.size()); assert(std::adjacent_find( reduce_axes.begin(), reduce_axes.end(), [](auto axis_1, auto axis_2) { return axis_2 - axis_1 > 1; }) == reduce_axes.end()); std::vector new_rsp_dims; std::vector new_reduce_axes; for(const auto axis : range(in_shape.ndim())) { if(reduce_lens[axis] == in_lens[axis]) { new_rsp_dims.push_back(in_lens[axis]); } else if(new_reduce_axes.empty()) { assert(reduce_lens[axis] == 1); new_rsp_dims.push_back(-1); new_reduce_axes.push_back(axis); } } auto rsp_ins = m.insert_instruction( i, migraphx::make_op("reshape", {{"dims", new_rsp_dims}}), i->inputs().front()); auto collapsed_reduce = m.insert_instruction( i, migraphx::make_op(reduce_op_name, {{"axes", new_reduce_axes}}), rsp_ins); auto rsp_back = m.insert_instruction( i, migraphx::make_op("reshape", {{"dims", reduce_lens}}), collapsed_reduce); m.replace_instruction(i, rsp_back); } } migraphx::run_passes(m, {migraphx::dead_code_elimination{}}); } bool is_module_fusible(const module& m, const context& migraphx_ctx, const value& solution) { auto mm = m; rewrite_reduce(mm); mlir_program mp; mp.set_gpu_properties(migraphx_ctx); mp.parse(mm); mp.run_high_level_pipeline(); return mlirIsModuleFusible(mp.mmodule.get(), make_mlir_string_ref(*solution.if_string())); } static void adjust_param_shapes(module& m, const std::vector& inputs) { auto names = m.get_parameter_names(); std::sort(names.begin(), names.end()); for(auto i : range(names.size())) { const auto& name = names[i]; const auto& input = inputs[i]; auto param = m.get_parameter(name); assert(param->get_shape().standard()); if(input.standard()) continue; auto new_param = m.add_parameter(name + ".0", input); m.replace_instruction(param, new_param); m.remove_instruction(param); } } static void replace_params_with_literals(module& m, const std::vector& inputs) { auto names = m.get_parameter_names(); std::sort(names.begin(), names.end()); for(auto i : range(names.size())) { const auto& name = names[i]; const auto& input = inputs[i]; if(input->name() != "@literal") continue; auto param = m.get_parameter(name); auto lit = m.add_literal(input->get_literal()); m.replace_instruction(param, lit); m.remove_instruction(param); } } std::string dump_mlir(module m, const std::vector& inputs) { const_module_ref mr = &m; if(not inputs.empty()) { adjust_param_shapes(m, inputs); } rewrite_reduce(m); mlir_program mp; mp.parse(*mr); auto mod_op = mlirModuleGetOperation(mp.mmodule.get()); return mlir_print(&mlirOperationPrint, mod_op); } static void abbreviate_symbol_names(std::string& n) { static const std::vector> abbrs = { {"reduce_max_reshape_sub_exp_reshape_reduce_sum_reshape_div", "softmax"}, {"reduce_max_sub_exp_reduce_sum_div", "softmax"}, {"reshape", "rsp"}, {"transpose", "trp"}, {"slice", "slc"}}; for(auto const& [key, val] : abbrs) { replace_string_inplace(n, key, val); } } static std::string compute_dump_name(const module& m, const std::string& ext) { std::vector sizes; for(auto ins : iterator_for(m)) { if(contains({"quant_convolution", "quant_dot", "convolution", "dot"}, ins->name())) sizes.insert(sizes.end(), ins->inputs().begin(), ins->inputs().end()); } auto shape_str = "_" + shape::to_sizes_string(to_shapes(sizes)); auto sym_names = mlir_program::get_symbol_name(m); abbreviate_symbol_names(sym_names); // On most commonly used file systems, the max file name size is 255 characters const int max_file_length = 255; std::string fname = sym_names + shape_str; replace_string_inplace(fname, ", ", "_"); replace_string_inplace(fname, ":", "s"); if(fname.length() + ext.length() > max_file_length) { auto cnt = "_" + std::to_string(dump_counter()++); auto cutoff = max_file_length - ext.length() - cnt.length(); fname.resize(cutoff); fname += cnt; } fname += ext; return fname; } void dump_mlir_to_file(module m, const std::vector& inputs, const fs::path& location) { static std::mutex mutex; const std::lock_guard lock(mutex); if(not inputs.empty()) { adjust_param_shapes(m, inputs); } rewrite_reduce(m); auto name = compute_dump_name(m, ".mlir"); auto f = location / name; std::cout << "Dumping MLIR file to: " << f << std::endl; mlir_program mp; mp.parse(m); auto mod_op = mlirModuleGetOperation(mp.mmodule.get()); std::string mlir_str = mlir_print(&mlirOperationPrint, mod_op); write_string(f, mlir_str); } std::string dump_mlir(module m) { return dump_mlir(std::move(m), {}); } mlir_code_object compile_mlir(const context& migraphx_ctx, module m, const std::vector& in_shapes, const value& solution) { adjust_param_shapes(m, in_shapes); rewrite_reduce(m); const bool trace = enabled(MIGRAPHX_TRACE_MLIR{}); static std::mutex mutex; if(trace) { const std::lock_guard lock(mutex); std::cout << m << std::endl; } mlir_program mp; mp.set_gpu_properties(migraphx_ctx); mp.parse(m); auto mod_op = mlirModuleGetOperation(mp.mmodule.get()); if(trace) { const std::lock_guard lock(mutex); std::cout << mlir_print(&mlirOperationPrint, mod_op) << std::endl; } auto co = mp.compile(solution); co.expected_inputs = in_shapes; auto out_shapes = m.get_output_shapes(); if(out_shapes.size() == 1) { co.output = m.get_output_shapes().front(); } else { co.output = shape{out_shapes}; } mlir_code_object mco; mco.cop = co; size_t num_prefill_args = mlirGetNumPrefillArgs(mp.mmodule.get()); if(num_prefill_args > 0) { std::vector prefill_indices(num_prefill_args); std::vector prefill_mlir_values(num_prefill_args); mlirGetPrefillArgsInfo( mp.mmodule.get(), prefill_indices.data(), prefill_mlir_values.data(), num_prefill_args); std::vector prefill_values(prefill_mlir_values.size()); std::transform(prefill_mlir_values.begin(), prefill_mlir_values.end(), prefill_values.begin(), [](const auto& v) { // mlir sets fill attribute as float but migx hip::fill operator only // supports integer type. // TODO: Need to add checks that it is indeed an integer. double dv = mlirFloatAttrGetValueDouble(v); return static_cast(dv); }); mco.prefill_indices = prefill_indices; mco.prefill_values = prefill_values; } return mco; } instruction_ref insert_mlir(module& m, instruction_ref ins, code_object_op co, const std::vector& inputs) { std::vector refs; std::size_t last = 0; refs.reserve(inputs.size()); std::copy(inputs.begin(), inputs.end(), std::back_inserter(refs)); last = refs.size() - 1; co.expected_inputs = to_shapes(refs); co.output_arg = last; return m.insert_instruction(ins, co, refs); } tuning_config get_tuning_config_mlir(const context& migraphx_ctx, module m, const std::vector& inputs, bool exhaustive) { adjust_param_shapes(m, inputs); rewrite_reduce(m); mlir_program mp; mp.set_gpu_properties(migraphx_ctx); mp.parse(m); auto tc = mp.get_tuning_config(exhaustive); const bool trace = enabled(MIGRAPHX_TRACE_MLIR{}); static std::mutex mutex; if(trace) { const std::lock_guard lock(mutex); std::cout << "Problem: " << tc.problem << std::endl; auto mod_op = mlirModuleGetOperation(mp.mmodule.get()); std::cout << mlir_print(&mlirOperationPrint, mod_op) << std::endl; } return tc; } void dump_mlir_to_mxr(module m, const std::vector& inputs, const fs::path& location) { static std::mutex mutex; const std::lock_guard lock(mutex); adjust_param_shapes(m, to_shapes(inputs)); replace_params_with_literals(m, inputs); std::vector sizes; for(auto ins : iterator_for(m)) { if(contains({"quant_convolution", "quant_dot", "convolution", "dot"}, ins->name())) sizes.insert(sizes.end(), ins->inputs().begin(), ins->inputs().end()); } auto name = compute_dump_name(m, ".mxr"); auto f = location / name; std::cout << "Dumping MXR file to: " << f << std::endl; save(program{std::move(m)}, f.string()); } #else template void use(T&) { } std::string dump_mlir(module) { return {}; } std::string dump_mlir(module m, const std::vector& inputs) { use(m); use(inputs); return {}; } // Disabling clang-tidy warning on non-real useage. // NOLINTBEGIN(performance-unnecessary-value-param) mlir_code_object compile_mlir(const context&, module, const std::vector&, const value&) { return {}; } instruction_ref // cppcheck-suppress funcArgNamesDifferent insert_mlir(module& m, instruction_ref, code_object_op co, const std::vector&) { use(co); use(m); return m.end(); } tuning_config get_tuning_config_mlir(const context&, module, const std::vector&, bool) { return {}; } // NOLINTEND(performance-unnecessary-value-param) #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/multinomial.cpp000066400000000000000000000036401510465702400226340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_multinomial::compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(3).only_dims(2).standard(); inputs.pop_back(); return op.compute_shape(inputs); } argument hip_multinomial::compute(context& ctx, const shape&, const std::vector& args) const { device::multinomial(ctx.get_stream().get(), args.back(), args.front(), args[1]); return args.back(); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/no_device.cpp000066400000000000000000000025471510465702400222420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifdef __HIP_DEVICE_COMPILE__ #error \ "Device compilation not allowed for migraphx_gpu. Do not link with hip::device. Device code should go into migraphx_device or migraphx_kernels" #endif ROCm-AMDMIGraphX-46524e8/src/targets/gpu/nonzero.cpp000066400000000000000000000033361510465702400217760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_nonzero::compute_shape(std::vector inputs) const { return op.compute_shape({inputs.front()}); } argument hip_nonzero::compute(context& ctx, const shape&, const std::vector& args) const { return device::nonzero(ctx.get_stream().get(), args.back(), args.front()); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/pack_args.cpp000066400000000000000000000035431510465702400222360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { std::vector pack_args(const std::vector& args) { std::vector kernargs; for(auto&& arg : args) { std::size_t n = arg.size; const auto* p = static_cast(arg.data); // Insert padding std::size_t padding = (arg.align - (kernargs.size() % arg.align)) % arg.align; kernargs.insert(kernargs.end(), padding, 0); kernargs.insert(kernargs.end(), p, p + n); } return kernargs; } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/perfdb.cpp000066400000000000000000000124311510465702400215420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace { std::string get_layout(const shape& s, std::string labels) { auto result = labels; auto p = find_permutation(s); std::transform(p.begin(), p.end(), result.begin(), [&](auto i) { return labels[i]; }); return "'" + result + "'"; } std::string get_type(const shape& s) { static const std::unordered_map m = { {shape::float_type, "'FP32'"}, {shape::half_type, "'FP16'"}, {shape::double_type, "'FP64'"}, {shape::int8_type, "'INT8'"}, {shape::int32_type, "'INT32'"}, }; auto it = m.find(s.type()); if(it == m.end()) return "UNKNOWN"; return it->second; } std::string generate_miopen_config(const problem_params& pp) { value v = pp.op.to_value(); auto input = pp.inputs[0].lens(); auto weights = pp.inputs[1].lens(); auto padding = v["padding"].to_vector(); auto stride = v["stride"].to_vector(); auto dilation = v["dilation"].to_vector(); if(padding.size() != stride.size()) padding.erase(padding.begin() + padding.size() / 2, padding.end()); return to_string_range({std::string{" C.in_channels="}, to_string(input[1]), std::string{" AND C.in_h="}, to_string(input[2]), std::string{" AND C.in_w="}, to_string(input[3]), std::string{" AND C.fil_h="}, to_string(weights[2]), std::string{" AND C.fil_w="}, to_string(weights[3]), std::string{" AND C.out_channels="}, to_string(weights[0]), std::string{" AND C.batchsize="}, to_string(input[0]), std::string{" AND C.pad_h="}, to_string(padding[0]), std::string{" AND C.pad_w="}, to_string(padding[2]), std::string{" AND C.dilation_h="}, to_string(dilation[0]), std::string{" AND C.dilation_w="}, to_string(dilation[1]), std::string{" AND C.conv_stride_h="}, to_string(stride[0]), std::string{" AND C.conv_stride_w="}, to_string(stride[1]), std::string{" AND C.layout="}, get_layout(pp.inputs[0], "NCHW"), std::string{" AND C.data_type="}, get_type(pp.inputs[0]), std::string{" AND C.direction="}, std::string{"'F'"}}, " "); } auto query_miopen_db(const std::string& query) { static std::mutex g_db_mutex; // NOLINT const std::lock_guard lock(g_db_mutex); // TODO: Store db as a static variable const auto dbpath = fs::path{"/opt"} / "rocm" / "share" / "miopen" / "db" / "miopen.db"; // Check if db file exists. std::ifstream dbs(dbpath); if(dbs.is_open()) { dbs.close(); } else { std::vector> empty; return empty; } auto db = sqlite::read(dbpath); return db.execute(query); } } // namespace std::string get_mlir_perf_for_conv(const problem_params& pp, bool xdlops) { std::string solver = xdlops ? "ConvMlirIgemmFwdXdlops" : "ConvMlirIgemmFwd"; std::string query = "select P.* \ from perf_db P, config C \ where P.config = C.id AND \ P.solver = '${solver}' AND \ ${config}"; auto results = query_miopen_db( interpolate_string(query, {{"config", generate_miopen_config(pp)}, {"solver", solver}})); if(results.empty()) return ""; return results.front().at("params"); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/pooling.cpp000066400000000000000000000057151510465702400217560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #if MIGRAPHX_USE_MIOPEN shape miopen_pooling::compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(2).standard(); std::vector pooling_input = {inputs.at(0)}; check_shapes{pooling_input, *this}.max_ndims(5); return op.normalize_compute_shape(pooling_input); } inline static void reshape_if_1d(shape& input) { auto dims = input.lens(); if(dims.size() == 3) { std::vector new_dims = dims; new_dims.insert(new_dims.begin() + 2, 1); input = shape{input.type(), new_dims}; } } argument miopen_pooling::compute(context& ctx, const shape& output_shape, const std::vector& args) const { shape x_shape = args[0].get_shape(); shape y_shape = output_shape; reshape_if_1d(x_shape); reshape_if_1d(y_shape); auto x_desc = make_tensor(x_shape); auto y_desc = make_tensor(y_shape); float alpha = 1; float beta = 0; miopenPoolingForward(ctx.get_stream().get_miopen(), pd.get(), &alpha, x_desc.get(), args[0].implicit(), &beta, y_desc.get(), args[1].implicit(), false, nullptr, 0); return args[1]; } void miopen_pooling::finalize(context&, const shape&, const std::vector&) { if(pd == nullptr) pd = make_pooling(op); } #endif } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/prefuse_ops.cpp000066400000000000000000000513341510465702400226370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #ifdef MIGRAPHX_USE_COMPOSABLEKERNEL #include #endif #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_LAYERNORM_FUSION); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_MLIR); namespace { template struct layernorm_base { float epsilon = 1e-12f; template static auto reflect(Self& self, F f) { return pack(f(self.epsilon, "epsilon")); } shape compute_shape(std::vector inputs, std::vector mods) const { std::size_t nargs = N; if(not mods.empty()) { auto* pm = mods.front(); nargs += pm->get_parameter_names().size() - 1; } check_shapes{inputs, static_cast(*this)}.has(nargs); auto s = inputs.front(); auto t = s.type(); if(not mods.empty()) t = mods.front()->get_output_shapes().front().type(); // Scalar output if all inputs are scalar if(inputs.front().elements() == 1 and all_of(inputs, [](const auto& ss) { return ss.scalar(); })) return inputs.front(); auto l_s = shape::from_permutation( t, s.lens(), find_permutation(std::vector(inputs.begin(), inputs.begin() + N))); // just prelayernorm or preadd_layernorm if(nargs <= N) return l_s; // else, layernorm + pointwise fusion, preserve layout of fused op std::vector lp_s(inputs.begin() + N, inputs.end()); lp_s.insert(lp_s.begin(), l_s); return shape::from_permutation(t, s.lens(), find_permutation(lp_s)); } }; struct layernorm : layernorm_base { std::string name() const { return "gpu::prelayernorm"; } }; MIGRAPHX_REGISTER_OP(layernorm); struct add_layernorm : layernorm_base { std::string name() const { return "gpu::preadd_layernorm"; } }; MIGRAPHX_REGISTER_OP(add_layernorm); struct find_layernorm { auto matcher() const { return match::layernorm(); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto x_ins = r.instructions["x"]; float eps = 0; if(contains(r.instructions, "eps")) eps = r.instructions["eps"]->eval().at(); m.replace_instruction(ins, layernorm{eps}, x_ins); } }; struct find_add_layernorm { auto matcher() const { return match::name("gpu::prelayernorm")( match::args(match::name("add")(match::used_once()).bind("add"))); } void apply(module& m, const match::matcher_result& r) const { auto ins = r.result; auto add_ins = r.instructions["add"]; auto op = any_cast(ins->get_operator()); m.replace_instruction(ins, add_layernorm{op.epsilon}, add_ins->inputs()); } }; struct pre_gemm_softmax_gemm : gemm_softmax_gemm { std::string name() const { return "gpu::pre_gemm_softmax_gemm"; } }; MIGRAPHX_REGISTER_OP(pre_gemm_softmax_gemm); auto is_ck_gemm() { return match::make_basic_pred_matcher([=](instruction_ref ins) { #ifdef MIGRAPHX_USE_COMPOSABLEKERNEL if(not enabled(MIGRAPHX_ENABLE_CK{})) return false; if(ins->name() != "dot") return false; if(not pre_gemm_softmax_gemm::is_ck_supported_type(ins->get_shape().type())) return false; return true; #else (void)ins; return false; #endif }); } auto is_test_gemm(bool enable_attention) { return match::make_basic_pred_matcher([=](instruction_ref ins) { if(ins->name() != "dot") return false; return enable_attention; }); } auto is_bias_supported() { return match::make_basic_pred_matcher([=](instruction_ref) { #ifdef MIGRAPHX_USE_COMPOSABLEKERNEL return not enabled(MIGRAPHX_ENABLE_CK{}); #else return true; #endif }); } struct find_gemm_softmax_gemm { bool enable_attention = false; auto matcher() const { auto gemm1 = match::skip(match::name("contiguous"))(match::name("dot")( match::any_of(is_ck_gemm(), is_test_gemm(enable_attention)).bind("gemm1"))); auto mul = match::name("mul")( match::nargs(2), match::either_arg(0, 1)(match::is_constant().bind("scale"), gemm1)); auto where = match::name("where")(match::arg(2)(match::is_constant().bind("select_const")), match::arg(1)(mul), match::arg(0)(match::any().bind("select_cond"))); auto add = match::name("add")(is_bias_supported(), match::nargs(2), match::either_arg(0, 1)(match::none_of(mul).bind("bias"), mul)); auto softmax = match::name("softmax")(match::arg(0)(match::any_of(mul, add, gemm1, where))) .bind("softmax"); return match::name("dot")( match::any_of(is_ck_gemm(), is_test_gemm(enable_attention)).bind("gemm2"))( match::arg(0)(softmax)); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto gemm2_ins = r.instructions["gemm2"]; auto gemm1_ins = r.instructions["gemm1"]; float scale = 1.0; if(contains(r.instructions, "scale")) { auto scale_lit = r.instructions["scale"]; // CK only supports single-valued scale scale_lit->eval().visit([&](const auto s) { // CK only supports single-valued scale if(not std::all_of( s.begin() + 1, s.end(), [&](auto v) { return float_equal(v, s.front()); })) return; scale = s.front(); }); } auto inputs = gemm1_ins->inputs(); // A, B if(contains(r.instructions, "select_cond")) { inputs.push_back(r.instructions["select_cond"]); inputs.push_back(r.instructions["select_const"]); } if(contains(r.instructions, "bias")) { inputs.push_back(r.instructions["bias"]); } inputs.push_back(gemm2_ins->inputs().back()); // B1 mpm.get_module().replace_instruction( ins, pre_gemm_softmax_gemm{gemm2_ins->get_operator(), scale}, inputs); } }; struct base_group_query_attention { bool do_rotary = false; std::size_t kv_num_heads = 0; int local_window_size = -1; std::size_t num_heads = 1; bool rotary_interleaved = false; float scale = 1.0; template static auto reflect(Self& self, F f) { return pack(f(self.do_rotary, "do_rotary"), f(self.kv_num_heads, "kv_num_heads"), f(self.local_window_size, "local_window_size"), f(self.num_heads, "num_heads"), f(self.rotary_interleaved, "rotary_interleaved"), f(self.scale, "scale")); } }; struct gpu_gqa_rotary_embedding : base_group_query_attention { std::string name() const { return "gpu::gqa_rotary_embedding"; } shape compute_shape(std::vector inputs) const { return inputs.front(); } }; MIGRAPHX_REGISTER_OP(gpu_gqa_rotary_embedding); struct gpu_concat_past_present : base_group_query_attention { std::string name() const { return "gpu::concat_past_present"; } shape compute_shape(std::vector inputs) const { return inputs.back(); } std::ptrdiff_t output_alias(const std::vector&) const { return 0; } }; MIGRAPHX_REGISTER_OP(gpu_concat_past_present); struct find_group_query_attention { std::size_t* counter = nullptr; auto matcher() const { return match::name("group_query_attention"); } auto finalize_attention_module(module_ref m) const { eliminate_common_subexpression{}.apply(*m); dead_code_elimination{}.apply(*m); } std::string get_count() const { if(counter == nullptr) MIGRAPHX_THROW("Invalid counter"); return std::to_string((*counter)++); } void apply(module_pass_manager& mpm, const match::matcher_result& r) const { auto ins = r.result; auto inputs = ins->inputs(); auto val = ins->get_operator().to_value(); auto num_heads = val.at("num_heads").to(); auto kv_num_heads = val.at("kv_num_heads").to(); auto do_rotary = val.at("do_rotary").to(); auto local_window_size = val.at("local_window_size").to(); auto rotary_interleaved = val.at("rotary_interleaved").to(); auto scale = val.at("scale").to(); auto q_shape = inputs[0]->get_shape(); const auto& q_lens = q_shape.lens(); const std::size_t batch_size = q_lens[0]; const std::size_t sequence_length = q_lens[1]; std::size_t q_hidden_size = q_lens[2]; std::size_t head_size = q_hidden_size / (num_heads + 2 * kv_num_heads); std::vector bsnh{ batch_size, sequence_length, num_heads + 2 * kv_num_heads, head_size}; auto transposed_qkv = mpm.get_module().insert_instruction( ins, make_op("reshape", {{"dims", bsnh}}), inputs.at(0)); transposed_qkv = mpm.get_module().insert_instruction( ins, make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), transposed_qkv); auto rotary_qkv = transposed_qkv; if(do_rotary) { std::vector rotary_inputs{ transposed_qkv, inputs.at(5), inputs.at(7), inputs.at(8)}; rotary_qkv = mpm.get_module().insert_instruction(ins, gpu_gqa_rotary_embedding{do_rotary, kv_num_heads, local_window_size, num_heads, rotary_interleaved, scale}, rotary_inputs); } auto pres_k = inputs.at(3); auto pres_v = inputs.at(4); auto slk = inputs.at(5); auto rotary_k = mpm.get_module().insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {num_heads}}, {"ends", {num_heads + kv_num_heads}}}), rotary_qkv); auto rotary_v = mpm.get_module().insert_instruction( ins, make_op("slice", {{"axes", {1}}, {"starts", {num_heads + kv_num_heads}}, {"ends", {num_heads + (2 * kv_num_heads)}}}), rotary_qkv); std::vector concat_k_inputs{rotary_k, slk, pres_k}; std::vector concat_v_inputs{rotary_v, slk, pres_v}; pres_k = mpm.get_module().insert_instruction( ins, gpu_concat_past_present{ do_rotary, kv_num_heads, local_window_size, num_heads, rotary_interleaved, scale}, concat_k_inputs); pres_v = mpm.get_module().insert_instruction( ins, gpu_concat_past_present{ do_rotary, kv_num_heads, local_window_size, num_heads, rotary_interleaved, scale}, concat_v_inputs); // Adding 1 to seq_lens_k, aka past_seq_lens, to allow range literals to start at 0. // Putting the add inside the mlir module currently causes an error on their side, // so we're leaving it here until that can be solved. auto past_sl = mpm.get_module().insert_instruction( ins, make_op("convert", {{"target_type", shape::int32_type}}), inputs.at(5)); auto one_lit = mpm.get_module().insert_literal( ins, literal{shape{past_sl->get_shape().type(), {1}}, {1}}); one_lit = mpm.get_module().insert_instruction( ins, make_op("multibroadcast", {{"out_lens", past_sl->get_shape().lens()}}), one_lit); auto total_sl = mpm.get_module().insert_instruction(ins, make_op("add"), past_sl, one_lit); auto get_tuple_elm_0 = std::next(ins); auto get_tuple_elm_1 = std::next(get_tuple_elm_0); auto get_tuple_elm_2 = std::next(get_tuple_elm_1); mpm.get_module().replace_instruction(get_tuple_elm_2, pres_v); mpm.get_module().replace_instruction(get_tuple_elm_1, pres_k); auto kv_num_heads_factor = num_heads / kv_num_heads; auto max_seq_len = pres_k->get_shape().lens()[2]; total_sl = mpm.get_module().insert_instruction( ins, make_op("multibroadcast", {{"out_lens", {batch_size, num_heads}}}), total_sl); std::vector new_inputs{rotary_qkv, pres_k, pres_v, total_sl}; module m_attn; std::vector attn_inputs = {rotary_qkv, pres_k, pres_v, total_sl}; std::unordered_map map_main_to_mattn; m_attn.add_params(attn_inputs, &map_main_to_mattn); auto q = m_attn.add_instruction( make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {num_heads}}}), map_main_to_mattn.at(rotary_qkv)); auto k = map_main_to_mattn.at(pres_k); auto v = map_main_to_mattn.at(pres_v); if(kv_num_heads_factor != 1) { auto kv_new_lens = k->get_shape().lens(); kv_new_lens.at(1) = num_heads; k = m_attn.add_instruction(make_op("unsqueeze", {{"axes", {2}}}), k); v = m_attn.add_instruction(make_op("unsqueeze", {{"axes", {2}}}), v); auto kv_unsqueezed_lens = k->get_shape().lens(); kv_unsqueezed_lens.at(2) = kv_num_heads_factor; k = m_attn.add_instruction( make_op("multibroadcast", {{"out_lens", kv_unsqueezed_lens}}), k); v = m_attn.add_instruction( make_op("multibroadcast", {{"out_lens", kv_unsqueezed_lens}}), v); k = m_attn.add_instruction(make_op("reshape", {{"dims", kv_new_lens}}), k); v = m_attn.add_instruction(make_op("reshape", {{"dims", kv_new_lens}}), v); } auto kt = m_attn.add_instruction(make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), k); auto gemm1 = m_attn.add_instruction(make_op("dot"), q, kt); std::vector range_vec(max_seq_len); std::iota(range_vec.begin(), range_vec.end(), 0); shape range_s{total_sl->get_shape().type(), {max_seq_len}}; auto range = m_attn.add_literal(range_s, range_vec); std::vector bnsm{batch_size, num_heads, sequence_length, max_seq_len}; auto bc_range = m_attn.add_instruction(make_op("multibroadcast", {{"out_lens", bnsm}}), range); auto scalar_s = shape{rotary_qkv->get_shape().type(), {1}}; auto ninf = m_attn.add_literal(literal{scalar_s, {-std::numeric_limits::infinity()}}); ninf = m_attn.add_instruction(make_op("multibroadcast", {{"out_lens", bnsm}}), ninf); if(float_equal(scale, 0.0)) { scale = 1.0f / std::sqrt(static_cast(head_size)); } auto scale_ins = m_attn.add_literal(literal{scalar_s, {scale}}); scale_ins = m_attn.add_instruction(make_op("multibroadcast", {{"out_lens", bnsm}}), scale_ins); auto mul = m_attn.add_instruction(make_op("mul"), gemm1, scale_ins); if(sequence_length > 1) { std::vector seq_range_vec(sequence_length); std::iota(seq_range_vec.begin(), seq_range_vec.end(), 0); shape seq_range_s{total_sl->get_shape().type(), {sequence_length}}; auto seq_range = m_attn.add_literal(seq_range_s, seq_range_vec); seq_range = m_attn.add_instruction(make_op("reshape", {{"dims", {sequence_length, 1}}}), seq_range); seq_range = m_attn.add_instruction(make_op("multibroadcast", {{"out_lens", bnsm}}), seq_range); auto causal_mask = m_attn.add_instruction(make_op("greater"), bc_range, seq_range); causal_mask = m_attn.add_instruction( make_op("convert", {{"target_type", shape::bool_type}}), causal_mask); mul = m_attn.add_instruction(make_op("where"), causal_mask, ninf, mul); } auto bc_total_sl = m_attn.add_instruction(make_op("reshape", {{"dims", {batch_size, num_heads, 1, 1}}}), map_main_to_mattn.at(total_sl)); auto mask_comp = m_attn.add_instruction(make_op("multibroadcast", {{"out_lens", bnsm}}), bc_total_sl); auto mask = m_attn.add_instruction(make_op("greater"), bc_range, mask_comp); mask = m_attn.add_instruction(make_op("convert", {{"target_type", shape::bool_type}}), mask); auto where = m_attn.add_instruction(make_op("where"), mask, ninf, mul); auto softmax = m_attn.add_instruction(make_op("softmax", {{"axis", 3}}), where); auto scores = m_attn.add_instruction(make_op("dot"), softmax, v); auto out = m_attn.add_instruction(make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), scores); out = m_attn.add_instruction( make_op("reshape", {{"dims", get_tuple_elm_0->get_shape().lens()}}), out); m_attn.add_return({out}); finalize_attention_module(&m_attn); module_ref mpm_attn = mpm.create_module("mlir_attn" + get_count(), std::move(m_attn)); mpm_attn->set_bypass(); auto group_op = mpm.get_module().insert_instruction( ins, make_op("group", {{"tag", "attention"}}), new_inputs, {mpm_attn}); mpm.get_module().replace_instruction(get_tuple_elm_0, group_op); } }; void inline_group_sub_module(module_pass_manager& mpm) { auto& m = mpm.get_module(); for(auto ins : iterator_for(m)) { if(ins->name() != "group") continue; const auto& mod_inputs = ins->module_inputs(); auto inline_mod = m.insert_inline(ins, *mod_inputs.at(0), ins->inputs()); m.replace_instruction(ins, inline_mod.at(0)); } } } // namespace void prefuse_ops::apply(module_pass_manager& mpm) const { std::size_t counter = 0; if(enabled(MIGRAPHX_ENABLE_LAYERNORM_FUSION{})) { match::find_matches(mpm.get_module(), find_layernorm{}); mpm.run_pass(dead_code_elimination{}); match::find_matches(mpm.get_module(), find_add_layernorm{}); } match::find_matches(mpm, find_gemm_softmax_gemm{enable_attention}); match::find_matches(mpm, find_group_query_attention{.counter = &counter}); if(enabled(MIGRAPHX_DISABLE_MLIR{})) { inline_group_sub_module(mpm); mpm.run_pass(dead_code_elimination{}); } } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/prepare_reduce.cpp000066400000000000000000000076621510465702400232770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct parallel_reduce { operation op; template static auto reflect(Self& self, F f) { return pack(f(self.op, "op")); } std::string name() const { return "gpu::parallel_reduce"; } shape compute_shape(const std::vector& inputs) const { std::vector result; std::transform(inputs.begin(), inputs.end(), std::back_inserter(result), [&](auto input) { return op.compute_shape({input}); }); return shape{result}; } }; MIGRAPHX_REGISTER_OP(parallel_reduce); namespace { std::vector find_reduce(module& m) { std::vector result; auto im = iterator_for(m); std::copy_if(im.begin(), im.end(), std::back_inserter(result), [](auto ins) { if(contains({"gpu::parallel_reduce", "reduce_mean"}, ins->name())) return false; return contains(ins->name(), "reduce"); }); return result; } std::vector find_parallel_reduce(const std::vector& r) { std::vector result; auto ir = iterator_for(r); transform_if( ir.begin(), ir.end(), std::back_inserter(result), [&](auto x) { return std::none_of( std::next(x), r.end(), [&](auto reduce) { return reaches(*x, reduce); }); }, [](auto x) { return *x; }); return result; } void fuse_reductions(module& m) { auto rs = find_parallel_reduce(find_reduce(m)); if(rs.size() < 2) return; // Only handle the same reduction operator (and its data-type) for now if(std::any_of(std::next(rs.cbegin()), rs.cend(), [&](auto r) { return (*rs.cbegin())->name() != r->name() or (*rs.cbegin())->get_shape().type() != r->get_shape().type(); })) return; auto last = rs.front(); auto op = last->get_operator(); std::vector inputs; std::transform(rs.begin(), rs.end(), std::back_inserter(inputs), [&](auto r) { return r->inputs().front(); }); auto pr = m.insert_instruction(last, parallel_reduce{op}, inputs); int i = 0; for(auto r : rs) { m.replace_instruction(r, make_op("get_tuple_elem", {{"index", i}}), pr); i++; } m.sort(); } } // namespace void prepare_reduce::apply(module& m) const { fuse_reductions(m); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/problem_cache.cpp000066400000000000000000000056451510465702400230740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_PROBLEM_CACHE) void problem_cache::load() { auto pc_path = string_value_of(MIGRAPHX_PROBLEM_CACHE{}); if(pc_path.empty()) return; if(not fs::exists(pc_path)) { std::cout << "Problem cache not found. Creating new file.\n"; save(); return; } from_value(from_json_string(read_string(pc_path)), cache); } void problem_cache::save() const { auto pc_path = string_value_of(MIGRAPHX_PROBLEM_CACHE{}); if(pc_path.empty()) return; write_string(pc_path, to_pretty_json_string(to_value(cache))); } static value create_key(const std::string& name, const value& problem) { return {{"name", name}, {"problem", problem}}; } bool problem_cache::has(const std::string& name, const value& problem) const { return contains(cache, create_key(name, problem)); } void problem_cache::insert(const std::string& name, const value& problem, const value& solution) { assert(not solution.is_null()); cache[create_key(name, problem)] = solution; } void problem_cache::mark(const std::string& name, const value& problem) { cache.insert(std::make_pair(create_key(name, problem), value{})); } optional problem_cache::get(const std::string& name, const value& problem) const { auto it = cache.find(create_key(name, problem)); if(it == cache.end()) return nullopt; return it->second; } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/reverse.cpp000066400000000000000000000033711510465702400217560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_reverse::compute_shape(std::vector inputs) const { inputs.pop_back(); return op.normalize_compute_shape(inputs); } argument hip_reverse::compute(context& ctx, const shape&, const std::vector& args) const { return device::reverse(ctx.get_stream().get(), args.back(), args[0], op.axes); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/rnn_variable_seq_lens.cpp000066400000000000000000000063551510465702400246430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_rnn_var_sl_shift_output::compute_shape(std::vector inputs) const { inputs.pop_back(); return op.compute_shape(inputs); } argument hip_rnn_var_sl_shift_output::compute(context& ctx, const shape&, const std::vector& args) const { device::rnn_var_sl_shift_output(ctx.get_stream().get(), args.back(), args.at(0), args.at(1), (op.direction == op::rnn_direction::reverse)); return args.back(); } shape hip_rnn_var_sl_shift_sequence::compute_shape(std::vector inputs) const { inputs.pop_back(); return op.compute_shape(inputs); } argument hip_rnn_var_sl_shift_sequence::compute(context& ctx, const shape&, const std::vector& args) const { device::rnn_var_sl_shift_sequence(ctx.get_stream().get(), args.back(), args.at(0), args.at(1)); return args.back(); } shape hip_rnn_var_sl_last_output::compute_shape(std::vector inputs) const { inputs.pop_back(); return op.compute_shape(inputs); } argument hip_rnn_var_sl_last_output::compute(context& ctx, const shape&, const std::vector& args) const { device::rnn_var_sl_last_output(ctx.get_stream().get(), args.back(), args.at(0), args.at(1), (op.direction == op::rnn_direction::reverse)); return args.back(); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/rocblas.cpp000066400000000000000000000040301510465702400217210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { #if MIGRAPHX_USE_ROCBLAS rocblas_handle_ptr create_rocblas_handle_ptr() { rocblas_handle handle; rocblas_create_handle(&handle); return rocblas_handle_ptr{handle}; } rocblas_handle_ptr create_rocblas_handle_ptr(hipStream_t s) { rocblas_handle_ptr rb = create_rocblas_handle_ptr(); rocblas_set_stream(rb.get(), s); return rb; } #endif bool get_compute_fp32_flag() { const auto device_name = trim(split_string(get_device_name(), ':').front()); return (starts_with(device_name, "gfx9") and device_name >= "gfx908"); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/schedule_model.cpp000066400000000000000000000117141510465702400232570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { struct record_event { std::size_t event = 0; template static auto reflect(Self& self, F f) { return pack(f(self.event, "event")); } std::string name() const { return "gpu::record_event"; } shape compute_shape(const std::vector&) const { return {}; } argument compute(context& ctx, const shape&, const std::vector&) const { ctx.get_stream().record(ctx.get_event(event)); return {}; } void finalize(context& ctx, const shape&, const std::vector&) const { ctx.create_events(event); } }; struct wait_event { std::size_t event = 0; template static auto reflect(Self& self, F f) { return pack(f(self.event, "event")); } std::string name() const { return "gpu::wait_event"; } shape compute_shape(const std::vector&) const { return {}; } argument compute(context& ctx, const shape&, const std::vector&) const { ctx.get_stream().wait(ctx.get_event(event)); return {}; } }; struct set_stream { std::size_t stream = 0; template static auto reflect(Self& self, F f) { return pack(f(self.stream, "stream")); } std::string name() const { return "gpu::set_stream"; } shape compute_shape(const std::vector&) const { return {}; } argument compute(context& ctx, const shape&, const std::vector&) const { ctx.set_stream(stream); return {}; } void finalize(context& ctx, const shape&, const std::vector&) const { ctx.set_stream(stream); } }; MIGRAPHX_REGISTER_OP(record_event) MIGRAPHX_REGISTER_OP(wait_event) MIGRAPHX_REGISTER_OP(set_stream) std::size_t schedule_model::concurrency() const { return streams; } void schedule_model::sched(module& m, instruction_ref ins, std::size_t n) const { auto last_stream = std::find_if(std::make_reverse_iterator(ins), std::make_reverse_iterator(m.begin()), [&](auto&& i) { return i.name() == "gpu::set_stream"; }); if(last_stream != std::make_reverse_iterator(m.begin())) { auto&& op = any_cast(last_stream->get_operator()); // If the same stream was set earlier then skip if(op.stream == n) return; } m.insert_instruction(ins, set_stream{n}); } void schedule_model::wait(module& m, instruction_ref ins, std::size_t wait_id) const { m.insert_instruction(ins, wait_event{wait_id}); } void schedule_model::record(module& m, instruction_ref ins, std::size_t wait_id) const { m.insert_instruction(std::next(ins), record_event{wait_id}); } static std::unordered_map create_weight_map() { return {{"hip::load_literal", 0}, {"hip::hip_allocate_memory", 0}, {"hip::hip_load_memory", 0}, {"hip::allocate", 0}, {"gpu::convolution", 8}, {"gpu::conv_bias_relu", 8}, {"gpu::pooling", 4}, {"gpu::gemm", 4}}; } static const std::unordered_map& weight_map() { static const std::unordered_map m = create_weight_map(); return m; } std::size_t schedule_model::weight(const operation& op) const { if(weight_map().count(op.name()) == 0) { return 2; } return weight_map().at(op.name()); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/sync_device.cpp000066400000000000000000000040201510465702400225660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { void sync_device::apply(module& m) const { auto last = std::prev(m.end()); if(last->name() == "@return") { auto inputs = last->inputs(); if(std::any_of(inputs.begin(), inputs.end(), [](auto i) { return (i->name() == "hip::copy_from_gpu"); })) { auto sync_in = m.insert_instruction(last, make_op("hip::sync_stream"), inputs); if(not inputs.empty()) { m.replace_instruction(inputs.front(), sync_in); } } } } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/target.cpp000066400000000000000000000270521510465702400215730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DISABLE_SCHEDULE_PASS) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_NHWC) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_REWRITE_DOT) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_REWRITE_LRN) #ifndef _WIN32 MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_CK) #endif MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_SET_GEMM_PROVIDER) std::vector target::get_passes(migraphx::context& gctx, const compile_options& options) const { auto& ctx = any_cast(gctx); ctx.set_exhaustive_tune_flag(options.exhaustive_tune); ctx.load_problem_cache(); std::set unsupported_types(shape::types().begin(), shape::types().end()); unsupported_types.erase(shape::type_t::float_type); unsupported_types.erase(shape::type_t::fp8e4m3fnuz_type); unsupported_types.erase(shape::type_t::fp8e5m2fnuz_type); unsupported_types.erase(shape::type_t::fp8e4m3fn_type); unsupported_types.erase(shape::type_t::fp8e5m2_type); unsupported_types.erase(shape::type_t::half_type); unsupported_types.erase(shape::type_t::bool_type); unsupported_types.erase(shape::type_t::int8_type); unsupported_types.erase(shape::type_t::uint8_type); unsupported_types.erase(shape::type_t::int32_type); unsupported_types.erase(shape::type_t::tuple_type); // No BF-16 Support on Navi21 if(gpu::gfx_has_bf16_intrinsics()) { unsupported_types.erase(shape::type_t::bf16_type); } // whiltelist supported Ops for the FP8 types std::set unsupported_fp8fnuz_ops = {}; // disable dot & quant_dot if no hipblaslt if(not hipblaslt_supported()) { unsupported_fp8fnuz_ops.insert("dot"); unsupported_fp8fnuz_ops.insert("quant_dot"); } #if MIGRAPHX_USE_MIOPEN // MIOpen doesn't have support for fp8 pooling yet. unsupported_fp8fnuz_ops.insert("pooling"); #endif if(not gpu::gfx_has_fp8fnuz_intrinsics()) { unsupported_fp8fnuz_ops.insert("dot"); unsupported_fp8fnuz_ops.insert("quant_dot"); unsupported_fp8fnuz_ops.insert("convolution"); unsupported_fp8fnuz_ops.insert("quant_convolution"); } // add all device kernels unsupported_fp8fnuz_ops.insert("logsoftmax"); unsupported_fp8fnuz_ops.insert("nonzero"); unsupported_fp8fnuz_ops.insert("prefix_scan_sum"); unsupported_fp8fnuz_ops.insert("scatter_none"); unsupported_fp8fnuz_ops.insert("topk"); unsupported_fp8fnuz_ops.insert("rnn_var_sl_shift_output"); unsupported_fp8fnuz_ops.insert("multinomial"); unsupported_fp8fnuz_ops.insert("argmax"); unsupported_fp8fnuz_ops.insert("argmin"); std::set unsupported_fp8ocp_ops = {}; // disable dot & quant_dot if no hipblaslt if(not hipblaslt_supported()) { unsupported_fp8ocp_ops.insert("dot"); unsupported_fp8ocp_ops.insert("quant_dot"); } #if MIGRAPHX_USE_MIOPEN // MIOpen doesn't have support for fp8 pooling yet. unsupported_fp8ocp_ops.insert("pooling"); #endif if(not gpu::gfx_has_fp8ocp_intrinsics()) { unsupported_fp8ocp_ops.insert("convolution"); unsupported_fp8ocp_ops.insert("quant_convolution"); unsupported_fp8ocp_ops.insert("dot"); unsupported_fp8ocp_ops.insert("quant_dot"); } // add all device kernels unsupported_fp8ocp_ops.insert("logsoftmax"); unsupported_fp8ocp_ops.insert("nonzero"); unsupported_fp8ocp_ops.insert("prefix_scan_sum"); unsupported_fp8ocp_ops.insert("scatter_none"); unsupported_fp8ocp_ops.insert("topk"); unsupported_fp8ocp_ops.insert("rnn_var_sl_shift_output"); unsupported_fp8ocp_ops.insert("multinomial"); unsupported_fp8ocp_ops.insert("argmax"); unsupported_fp8ocp_ops.insert("argmin"); // clang-format off return { split_single_dyn_dim{}, dead_code_elimination{}, simplify_dyn_ops{}, dead_code_elimination{}, normalize_ops{}, dead_code_elimination{}, eliminate_identity{}, dead_code_elimination{}, enable_pass(not gpu::gfx_has_fp8ocp_intrinsics() and gpu::gfx_has_fp8fnuz_intrinsics(), fp8_ocp_to_fnuz{}), enable_pass(not gpu::gfx_has_fp8ocp_intrinsics() and gpu::gfx_has_fp8fnuz_intrinsics(), dead_code_elimination{}), simplify_qdq{}, enable_pass(not mlir_enabled(), rewrite_quantization{}), dead_code_elimination{}, rewrite_rnn{}, dead_code_elimination{}, // workaround for rocBLAS unsupported error when using uint8 in quant_dot, quant_convolution & pooling eliminate_data_type{{migraphx::shape::uint8_type}, shape::float_type, {"quant_convolution", "quant_dot", "pooling"}}, eliminate_data_type{unsupported_types, shape::type_t::float_type}, simplify_reshapes{}, eliminate_identity{}, eliminate_pad{}, dead_code_elimination{}, insert_pad{{"convolution"}}, dead_code_elimination{}, inline_module{}, rewrite_pooling{.rewrite_lrn = enabled(MIGRAPHX_REWRITE_LRN{})}, dead_code_elimination{}, rewrite_gelu{options.fast_math}, optimize_module{}, layout_convolution{.channels_last = enabled(MIGRAPHX_ENABLE_NHWC{})}, dead_code_elimination{}, prefuse_ops{}, dead_code_elimination{}, eliminate_data_type{{migraphx::shape::fp8e4m3fnuz_type, migraphx::shape::fp8e5m2fnuz_type}, shape::float_type, unsupported_fp8fnuz_ops}, eliminate_data_type{{migraphx::shape::fp8e4m3fn_type, migraphx::shape::fp8e5m2_type}, shape::float_type, unsupported_fp8ocp_ops}, dead_code_elimination{}, rewrite_reduce{}, rewrite_topk{}, rewrite_low_precision{}, enable_pass(enabled(MIGRAPHX_ENABLE_REWRITE_DOT{}), rewrite_dot{}), dead_code_elimination{}, propagate_precision{}, dead_code_elimination{}, simplify_reshapes{.enable_op_shape_transform_op=true}, dead_code_elimination{}, enable_pass(mlir_attention_enabled(&ctx), fuse_attention{}), dead_code_elimination{}, optimize_module{}, fuse_pointwise_reduce{}, dead_code_elimination{}, #ifndef _WIN32 enable_pass(enabled(MIGRAPHX_ENABLE_CK{}), fuse_ck{}), #endif dead_code_elimination{}, enable_pass(mlir_enabled(), fuse_mlir{&ctx}), dead_code_elimination{}, fuse_concat{}, dead_code_elimination{}, auto_contiguous{}, dead_code_elimination{}, lowering{&ctx, options.offload_copy}, eliminate_contiguous{"gpu::contiguous"}, dead_code_elimination{}, eliminate_concat{concat_gpu_optimization{}}, dead_code_elimination{}, #if MIGRAPHX_USE_MIOPEN compile_miopen{&gctx}, dead_code_elimination{}, #endif fuse_ops{&ctx, options.fast_math}, dead_code_elimination{}, #if MIGRAPHX_USE_HIPBLASLT compile_hipblaslt{&gctx}, dead_code_elimination{}, #endif replace_allocate{gpu_allocation_model{}, options.offload_copy}, dead_code_elimination{}, adjust_allocation{gpu_allocation_model{}}, dead_code_elimination{}, compile_ops{&ctx, options.exhaustive_tune}, dead_code_elimination{}, promote_literals{}, dead_code_elimination{}, write_literals{&ctx}, schedule{gpu::schedule_model{ctx.get_current_device().nstreams()}, not enabled(MIGRAPHX_DISABLE_SCHEDULE_PASS{})}, memory_coloring{"hip::allocate"}, sync_device{}, preallocate_param{"scratch", gpu_allocation_model{}}, dead_code_elimination{}, eliminate_allocation{"hip::allocate"}, check_context{}, normalize_ops{}, dead_code_elimination{}, eliminate_identity{} }; // clang-format on } std::string target::name() const { return "gpu"; } migraphx::context target::get_context() const { return context(gpu::get_device_id()); } argument target::copy_to(const argument& arg) const { return gpu::to_gpu(arg); } argument target::copy_from(const argument& arg) const { return gpu::from_gpu(arg); } argument target::allocate(const shape& s) const { return gpu::allocate_gpu(s); } MIGRAPHX_REGISTER_TARGET(target); } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/time_op.cpp000066400000000000000000000124251510465702400217370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_BENCHMARKING_BUNDLE); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_BENCHMARKING_NRUNS); static std::vector generate_arguments(const std::vector& shapes, unsigned long seed = 0, random_mode rm = random_mode::random) { std::vector args; std::transform(shapes.begin(), shapes.end(), std::back_inserter(args), [&](const auto& s) { return to_gpu(generate_argument(s, seed++, rm)); }); return args; } double time_loop(migraphx::gpu::context& gctx, int bundle, int nruns, const std::function& f) { // check for manual overrides bundle = value_of(MIGRAPHX_BENCHMARKING_BUNDLE{}, bundle); nruns = value_of(MIGRAPHX_BENCHMARKING_NRUNS{}, nruns); std::vector> events(nruns); std::generate(events.begin(), events.end(), [] { return std::make_pair(context::create_event_for_timing(), context::create_event_for_timing()); }); std::vector times; // Warmup f(); for(auto i : range(nruns)) { gctx.get_stream().record(events[i].first.get()); for(auto j : range(bundle)) { (void)j; f(); } gctx.get_stream().record(events[i].second.get()); } gctx.finish(); std::transform(events.begin(), events.end(), std::back_inserter(times), [&](const auto& p) { return context::get_elapsed_ms(p.first.get(), p.second.get()) / bundle; }); std::sort(times.begin(), times.end()); // compute common average by removing top and bottom 25% of values std::size_t quarters = times.size() / 4; double total = std::accumulate(times.begin() + quarters, times.end() - quarters, 0.0); return total / std::distance(times.begin() + quarters, times.end() - quarters); } double time_op(const context& ictx, operation op, const std::vector& inputs, int bundle, int nruns) { // TODO: Use std::ref migraphx::context ctx = ictx; auto& gctx = any_cast(ctx); auto output = op.compute_shape(inputs); op.finalize(ctx, output, inputs); auto args = generate_arguments(inputs); auto run = [&] { op.compute(ctx, output, args); }; return time_loop(gctx, bundle, nruns, run); } double time_op(const context& ictx, operation op, int bundle, int nruns) { auto inputs = any_cast(op).expected_inputs; return time_op(ictx, op, inputs, bundle, nruns); } double time_program(const context& ictx, program p, const std::unordered_map& fill_map, int bundle, int nruns) { std::vector ctx_vec = {ictx}; auto& gctx = any_cast(ctx_vec.front()); auto* mm = p.get_main_module(); mm->finalize(ctx_vec); auto in_shapes = p.get_parameter_shapes(); std::unordered_map param_map; unsigned long seed = 0; for(const auto& [name, shape] : in_shapes) { std::string id = ""; if(shape.type() != migraphx::shape::tuple_type) id = shape.type_string() + migraphx::shape::to_sizes_string({shape.as_standard()}); if(contains(fill_map, id)) param_map[name] = to_gpu(fill_argument(shape, fill_map.at(id))); else param_map[name] = to_gpu(generate_argument(shape, seed++, random_mode::random)); } auto run = [&] { p.eval_with_context(ctx_vec, param_map); }; return time_loop(gctx, bundle, nruns, run); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/topk.cpp000066400000000000000000000046041510465702400212600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { shape hip_topk::compute_shape(std::vector inputs) const { return op.normalize_compute_shape({inputs.front()}); } argument hip_topk::compute(context& ctx, const shape&, const std::vector& args) const { auto outputs = args.back().get_sub_objects(); return op.largest ? device::topk_largest(ctx.get_stream().get(), outputs.front(), outputs.back(), args[0], op.k, op.axis) : device::topk_smallest(ctx.get_stream().get(), outputs.front(), outputs.back(), args[0], op.k, op.axis); } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/gpu/write_literals.cpp000066400000000000000000000045151510465702400233350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace gpu { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_COPY_LITERALS) void write_literals::apply(module& m) const { assert(ctx != nullptr); std::size_t n = 0; for(auto ins : iterator_for(m)) { if(ins->name() == "@literal") { if(enabled(MIGRAPHX_COPY_LITERALS{})) { literal l = ins->get_literal(); auto pre = m.add_literal(l); auto alloc = m.insert_instruction(std::next(pre), hip_allocate{l.get_shape()}); m.replace_instruction(ins, hip_copy_to_gpu{}, pre, alloc); } else { std::string id = m.name() + ":@literal:" + std::to_string(n); m.replace_instruction(ins, hip_copy_literal{ins->get_literal(), id}); n++; } } } } } // namespace gpu } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/ref/000077500000000000000000000000001510465702400175545ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/ref/CMakeLists.txt000066400000000000000000000034311510465702400223150ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### add_library(migraphx_ref target.cpp lowering.cpp ) set_target_properties(migraphx_ref PROPERTIES EXPORT_NAME ref) rocm_set_soversion(migraphx_ref ${MIGRAPHX_SO_VERSION}) rocm_clang_tidy_check(migraphx_ref) target_link_libraries(migraphx_ref PRIVATE Threads::Threads) target_link_libraries(migraphx_ref PUBLIC migraphx) migraphx_generate_export_header(migraphx_ref) rocm_install_targets( PRIVATE TARGETS migraphx_ref INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include ) ROCm-AMDMIGraphX-46524e8/src/targets/ref/include/000077500000000000000000000000001510465702400211775ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/ref/include/migraphx/000077500000000000000000000000001510465702400230165ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/ref/include/migraphx/ref/000077500000000000000000000000001510465702400235725ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/targets/ref/include/migraphx/ref/context.hpp000066400000000000000000000030131510465702400257640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CONTEXT_HPP #define MIGRAPHX_GUARD_RTGLIB_CONTEXT_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace ref { struct context { void finish() const {} }; } // namespace ref } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/ref/include/migraphx/ref/lowering.hpp000066400000000000000000000031551510465702400261350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_RTGLIB_CPU_LOWERING_HPP #define MIGRAPHX_GUARD_RTGLIB_CPU_LOWERING_HPP #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace ref { struct MIGRAPHX_REF_EXPORT lowering { std::string name() const { return "ref::lowering"; } void apply(module& m) const; }; } // namespace ref } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/ref/include/migraphx/ref/target.hpp000066400000000000000000000037671510465702400256060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_CPU_TARGET_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_CPU_TARGET_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct pass; namespace ref { struct MIGRAPHX_REF_EXPORT target { std::string name() const; std::vector get_passes(migraphx::context& ctx, const compile_options&) const; migraphx::context get_context() const { return context{}; } argument copy_to(const argument& arg) const { return arg; } argument copy_from(const argument& arg) const { return arg; } argument allocate(const shape& s) const; }; } // namespace ref } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/targets/ref/lowering.cpp000066400000000000000000000401701510465702400221100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace ref { struct ref_lrn { op::lrn op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "ref::lrn"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } argument compute(context&, shape output_shape, std::vector args) const { argument result{output_shape}; visit_all(result, args[0])([&](auto output, auto input) { int n_batch = output_shape.lens()[0]; int channels = output_shape.lens()[1]; int height = output_shape.lens()[2]; int width = output_shape.lens()[3]; float alphaoverarea = op.alpha / float(op.size); int radius_lower = (op.size - 1) / 2; int radius_upper = op.size / 2 + 1; par_dfor(n_batch, height, width)([&](int b, int h, int w) { float scale = 0; dfor(channels)([&](int c) { auto start = (c - radius_lower) < 0 ? 0 : (c - radius_lower); auto end = (c + radius_upper) > channels ? channels : (c + radius_upper); for(auto k = start; k < end; ++k) { scale += std::pow(input(b, k, h, w), 2); } scale *= alphaoverarea; scale += op.bias; scale = std::pow(scale, -op.beta); output(b, c, h, w) = input(b, c, h, w) * scale; }); }); }); return result; } }; MIGRAPHX_REGISTER_OP(ref_lrn) struct ref_im2col { op::im2col op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } static std::string name() { return "ref::im2col"; } shape compute_shape(const std::vector& inputs) const { return op.normalize_compute_shape(inputs); } argument compute(context&, const shape& output_shape, std::vector args) const { argument result{output_shape}; auto input_shape = args[0].get_shape(); auto weights_shape = args[1].get_shape(); visit_all(result, args[0])([&](auto col, auto input) { const std::size_t& height = input_shape.lens()[2]; const std::size_t& width = input_shape.lens()[3]; const std::size_t& channels = weights_shape.lens()[1]; const std::size_t& kernel_h = weights_shape.lens()[2]; const std::size_t& kernel_w = weights_shape.lens()[3]; const std::size_t& pad_h = op.padding[0]; const std::size_t& pad_w = op.padding[1]; const std::size_t& stride_h = op.stride[0]; const std::size_t& stride_w = op.stride[1]; long kdiv2_h = long(kernel_h) / 2; long kdiv2_w = long(kernel_w) / 2; // calculate output sizes const std::size_t col_height = (height - kernel_h + 2 * pad_h) / stride_h + 1; const std::size_t col_width = (width - kernel_w + 2 * pad_w) / stride_w + 1; // account for padding for the starting position of the input pixels long iinput = kdiv2_h - long(pad_h); // loop over output pixels (ioutput, joutput) for(std::size_t ioutput = 0; ioutput < col_height; ioutput++, iinput += stride_h) { long jinput = kdiv2_w - long(pad_w); for(std::size_t joutput = 0; joutput < col_width; joutput++, jinput += stride_w) { // compute linear index for output std::size_t ldx = ioutput * col_width + joutput; std::size_t p = 0; dfor(channels, kernel_h, kernel_w)([&](std::size_t c, std::size_t koffset, std::size_t loffset) { auto idx = iinput + long(koffset) - kdiv2_h; auto jdx = jinput + long(loffset) - kdiv2_w; col(ldx, p) = ((idx >= 0) and (idx < height) and (jdx >= 0) and (jdx < width)) ? input(0, c, idx, jdx) : 0; p++; }); } } }); return result; } }; MIGRAPHX_REGISTER_OP(ref_im2col) struct ref_op { operation op = op::identity{}; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "ref::op"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } argument compute(context&, const shape& output_shape, const std::vector& args) const { return op.compute(output_shape, args); } value to_value() const { value v; v["name"] = op.name(); v["operator"] = op.to_value(); return v; } void from_value(const value& v) { op = make_op(v.at("name").to(), v.at("operator")); } friend std::ostream& operator<<(std::ostream& os, const ref_op& x) { os << "ref::" << x.op; return os; } }; MIGRAPHX_REGISTER_OP(ref_op) struct ref_pad { op::pad op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "ref::pad"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } argument compute(context&, const dyn_output& dyn_out, std::vector args) const { assert(dyn_out.computed_shape.standard()); argument result{dyn_out.computed_shape}; result.visit([&](auto output) { using type = typename decltype(output)::value_type; std::fill(output.begin(), output.end(), pad_clamp(op.value)); }); visit_all(result, args[0])([&](auto output, auto input) { shape_for_each(input.get_shape(), [&](const auto& idx) { std::vector new_idx(idx.size()); std::transform( idx.begin(), idx.end(), op.pads.begin(), new_idx.begin(), [](auto i, auto j) { return i + j; }); output(new_idx.begin(), new_idx.end()) = input(idx.begin(), idx.end()); }); }); return result; } }; MIGRAPHX_REGISTER_OP(ref_pad) struct ref_gemm { op::dot op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "ref::dot"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } argument compute(context&, const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; visit_all(result, args[0], args[1])( [&](auto cmat, auto amat, auto bmat) { gemm(cmat, amat, bmat, 1.0f, 0.0f); }); return result; } }; MIGRAPHX_REGISTER_OP(ref_gemm) struct ref_quant_gemm { op::quant_dot op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "ref::quant_dot"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } argument compute(context&, const shape& output_shape, std::vector args) const { argument result{output_shape}; result.visit([&](auto cmat) { visit_all(args.at(0), args.at(1))( [&](auto amat, auto bmat) { return gemm(cmat, amat, bmat, 1.0f, 0.0f); }); }); return result; } }; MIGRAPHX_REGISTER_OP(ref_gemm) template struct ref_softmax : auto_register_op> { ref_softmax() = default; ref_softmax(Op pop) : op(std::move(pop)) {} Op op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "ref::" + op.name(); } shape compute_shape(const std::vector& inputs) const { return op.normalize_compute_shape(inputs); } argument compute(context&, const dyn_output& dyn_out, std::vector args) const { argument result{dyn_out.computed_shape}; auto batch_lens = dyn_out.computed_shape.lens(); int64_t tuned_axis = tune_axis(args[0].get_shape().lens().size(), op.axis, op.name()); std::size_t n_dims = batch_lens[tuned_axis]; batch_lens[tuned_axis] = 1; shape batch_shape{shape::int32_type, batch_lens}; visit_all(result, args[0])([&](auto output, auto input) { using value_type = accumulator_type; std::vector batch_max(batch_shape.elements(), std::numeric_limits::lowest()); std::vector batch_sum(batch_shape.elements(), value_type(0)); par_for(batch_shape.elements(), [&](auto i) { auto idx = batch_shape.multi(i); for(std::size_t j = 0; j < n_dims; ++j) { idx[tuned_axis] = j; batch_max[i] = std::max(batch_max[i], input(idx.begin(), idx.end())); } for(std::size_t j = 0; j < n_dims; ++j) { idx[tuned_axis] = j; std::size_t index = dyn_out.computed_shape.index(idx); output[index] = std::exp(input[index] - batch_max[i]); } for(std::size_t j = 0; j < n_dims; ++j) { idx[tuned_axis] = j; batch_sum[i] += output(idx.begin(), idx.end()); } for(std::size_t j = 0; j < n_dims; ++j) { idx[tuned_axis] = j; output(idx.begin(), idx.end()) = op.output()(output(idx.begin(), idx.end()), batch_sum[i]); } }); }); return result; } }; struct ref_rnn_var_sl_last_output { op::rnn_var_sl_last_output op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } std::string name() const { return "ref::rnn_var_sl_last_output"; } shape compute_shape(std::vector inputs) const { return op.compute_shape(std::move(inputs)); } argument compute(const shape& output_shape, std::vector args) const { argument result{output_shape}; auto out_comp_lens = args[0].get_shape().lens(); out_comp_lens[0] = 1; shape out_comp_s{output_shape.type(), out_comp_lens}; visit_all(result, args[0])([&](auto output, auto input) { args[1].visit([&](auto seq_lens) { par_for(output_shape.elements(), [&](auto i) { auto idx = out_comp_s.multi(i); auto b = idx[2]; if(op.direction == op::rnn_direction::reverse or idx[1] == 1) { idx[0] = 0; } else { idx[0] = seq_lens[b] - 1; } output[i] = input(idx.begin(), idx.end()); }); }); }); return result; } }; MIGRAPHX_REGISTER_OP(ref_rnn_var_sl_last_output) struct ref_apply { module* mod; std::unordered_map> apply_map{}; template auto simple_op() { return [this](instruction_ref ins) { apply_simple_op(ins); }; } template auto extend_op() { return [this](instruction_ref ins) { apply_extend_op(ins); }; } void init() { apply_map["dot"] = extend_op(); apply_map["quant_dot"] = extend_op(); apply_map["im2col"] = extend_op(); apply_map["logsoftmax"] = extend_op, op::logsoftmax>(); apply_map["lrn"] = extend_op(); apply_map["pad"] = extend_op(); apply_map["softmax"] = extend_op, op::softmax>(); apply_map["rnn_var_sl_last_output"] = extend_op(); } void apply() { init(); for(auto it : iterator_for(*mod)) { if(apply_map.count(it->name()) > 0) { apply_map.at(it->name())(it); } else if(is_context_free(it->get_operator())) { apply_ref_op(it); } } } void apply_ref_op(instruction_ref ins) const { mod->replace_instruction(ins, ref_op{ins->get_operator()}, ins->inputs()); } template void apply_simple_op(instruction_ref ins) { mod->replace_instruction(ins, T{}, ins->inputs()); } template void apply_extend_op(instruction_ref ins) { auto&& op = any_cast(ins->get_operator()); mod->replace_instruction(ins, T{op}, ins->inputs()); } }; void lowering::apply(module& m) const { ref_apply{&m}.apply(); } } // namespace ref } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/targets/ref/target.cpp000066400000000000000000000046201510465702400215500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace ref { std::string target::name() const { return "ref"; } std::vector target::get_passes(migraphx::context&, const compile_options&) const { return {normalize_ops{}, eliminate_pad{}, dead_code_elimination{}, insert_pad{}, dead_code_elimination{}, rewrite_rnn{}, dead_code_elimination{}, auto_contiguous{}, dead_code_elimination{}, lowering{}, dead_code_elimination{}}; } argument target::allocate(const shape& s) const { return fill_argument(s, 0); } MIGRAPHX_REGISTER_TARGET(target); } // namespace ref } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/000077500000000000000000000000001510465702400157405ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/tf/CMakeLists.txt000066400000000000000000000064371510465702400205120ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### find_package(protobuf QUIET CONFIG) if(protobuf_FOUND) add_library(tf-proto STATIC ${PROTO_SRCS}) protobuf_generate( TARGET tf-proto PROTOS graph.proto node_def.proto attr_value.proto tensor.proto tensor_shape.proto resource_handle.proto types.proto function.proto op_def.proto versions.proto ) target_include_directories(tf-proto SYSTEM PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${PROTOBUF_INCLUDE_DIR}) target_link_libraries(tf-proto PRIVATE ${PROTOBUF_LIBRARY}) target_link_libraries(tf-proto PRIVATE protobuf::libprotobuf) else() find_package(Protobuf REQUIRED) protobuf_generate_cpp( PROTO_SRCS PROTO_HDRS graph.proto node_def.proto attr_value.proto tensor.proto tensor_shape.proto resource_handle.proto types.proto function.proto op_def.proto versions.proto ) add_library(tf-proto STATIC ${PROTO_SRCS}) target_include_directories(tf-proto SYSTEM PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${PROTOBUF_INCLUDE_DIR}) target_link_libraries(tf-proto PRIVATE ${PROTOBUF_LIBRARY}) endif() if(MSVC) target_compile_options(tf-proto PRIVATE /w) else() target_compile_options(tf-proto PRIVATE -w) endif() set_target_properties(tf-proto PROPERTIES POSITION_INDEPENDENT_CODE On) file(GLOB TF_SRCS CONFIGURE_DEPENDS *.cpp) add_library(migraphx_tf ${TF_SRCS}) migraphx_generate_export_header(migraphx_tf) target_include_directories(migraphx_tf PRIVATE include) set_target_properties(migraphx_tf PROPERTIES EXPORT_NAME tf) rocm_set_soversion(migraphx_tf ${MIGRAPHX_SO_VERSION}) rocm_clang_tidy_check(migraphx_tf) target_link_libraries(migraphx_tf PRIVATE tf-proto) if(NOT WIN32) target_link_libraries(migraphx_tf PRIVATE "-Wl,--exclude-libs,ALL") endif() target_link_libraries(migraphx_tf PUBLIC migraphx) rocm_install_targets( PRIVATE TARGETS migraphx_tf ) ROCm-AMDMIGraphX-46524e8/src/tf/attr_value.proto000066400000000000000000000047451510465702400212050ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "AttrValueProtos"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; import "tensor.proto"; import "tensor_shape.proto"; import "types.proto"; // Protocol buffer representing the value for an attr used to configure an Op. // Comment indicates the corresponding attr type. Only the field matching the // attr type may be filled. message AttrValue { // LINT.IfChange message ListValue { repeated bytes s = 2; // "list(string)" repeated int64 i = 3 [packed = true]; // "list(int)" repeated float f = 4 [packed = true]; // "list(float)" repeated bool b = 5 [packed = true]; // "list(bool)" repeated DataType type = 6 [packed = true]; // "list(type)" repeated TensorShapeProto shape = 7; // "list(shape)" repeated TensorProto tensor = 8; // "list(tensor)" repeated NameAttrList func = 9; // "list(attr)" } // LINT.ThenChange(https://www.tensorflow.org/code/tensorflow/c/c_api.cc) oneof value { bytes s = 2; // "string" int64 i = 3; // "int" float f = 4; // "float" bool b = 5; // "bool" DataType type = 6; // "type" TensorShapeProto shape = 7; // "shape" TensorProto tensor = 8; // "tensor" ListValue list = 1; // any "list(...)" // "func" represents a function. func.name is a function's name or // a primitive op's name. func.attr.first is the name of an attr // defined for that function. func.attr.second is the value for // that attr in the instantiation. NameAttrList func = 10; // This is a placeholder only used in nodes defined inside a // function. It indicates the attr value will be supplied when // the function is instantiated. For example, let us suppose a // node "N" in function "FN". "N" has an attr "A" with value // placeholder = "foo". When FN is instantiated with attr "foo" // set to "bar", the instantiated node N's attr A will have been // given the value "bar". string placeholder = 9; } } // A list of attr names and their values. The whole list is attached // with a string name. E.g., MatMul[T=float]. message NameAttrList { string name = 1; map attr = 2; } ROCm-AMDMIGraphX-46524e8/src/tf/function.proto000066400000000000000000000102041510465702400206470ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "FunctionProtos"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; import "attr_value.proto"; import "node_def.proto"; import "op_def.proto"; // A library is a set of named functions. message FunctionDefLibrary { repeated FunctionDef function = 1; repeated GradientDef gradient = 2; } // A function can be instantiated when the runtime can bind every attr // with a value. When a GraphDef has a call to a function, it must // have binding for every attr defined in the signature. // // TODO(zhifengc): // * device spec, etc. message FunctionDef { // The definition of the function's name, arguments, return values, // attrs etc. OpDef signature = 1; // Attributes specific to this function definition. map attr = 5; // NOTE: field id 2 deleted on Jan 11, 2017, GraphDef version 21. reserved 2; // In both of the following fields, there is the need to specify an // output that is used as either the input to another node (in // `node_def`) or as a return value of the function (in `ret`). // Unlike the NodeDefs in GraphDef, we need to be able to specify a // list in some cases (instead of just single outputs). Also, we // need to be able to deal with lists of unknown length (so the // output index may not be known at function definition time). So // we use the following format instead: // * "fun_in" where "fun_in" is the name of a function input arg in // the `signature` field above. This represents that input, whether // it is a single tensor or a list. // * "fun_in:0" gives the first element of a function input arg (a // non-list input is considered a list of length 1 for these // purposes). // * "node:out" where "node" is the name of a node in `node_def` and // "out" is the name one of its op's output arguments (the name // comes from the OpDef of the node's op). This represents that // node's output, whether it is a single tensor or a list. // Note: We enforce that an op's output arguments are never // renamed in the backwards-compatibility test. // * "node:out:0" gives the first element of a node output arg (a // non-list output is considered a list of length 1 for these // purposes). // // NOT CURRENTLY SUPPORTED (but may be in the future): // * "node:out:-1" gives last element in a node output list // * "node:out:1:" gives a list with all but the first element in a // node output list // * "node:out::-1" gives a list with all but the last element in a // node output list // The body of the function. Unlike the NodeDefs in a GraphDef, attrs // may have values of type `placeholder` and the `input` field uses // the "output" format above. // By convention, "op" in node_def is resolved by consulting with a // user-defined library first. If not resolved, "func" is assumed to // be a builtin op. repeated NodeDef node_def = 3; // A mapping from the output arg names from `signature` to the // outputs from `node_def` that should be returned by the function. map ret = 4; } // GradientDef defines the gradient function of a function defined in // a function library. // // A gradient function g (specified by gradient_func) for a function f // (specified by function_name) must follow the following: // // The function 'f' must be a numerical function which takes N inputs // and produces M outputs. Its gradient function 'g', which is a // function taking N + M inputs and produces N outputs. // // I.e. if we have // (y1, y2, ..., y_M) = f(x1, x2, ..., x_N), // then, g is // (dL/dx1, dL/dx2, ..., dL/dx_N) = g(x1, x2, ..., x_N, // dL/dy1, dL/dy2, ..., dL/dy_M), // where L is a scalar-value function of (x1, x2, ..., xN) (e.g., the // loss function). dL/dx_i is the partial derivative of L with respect // to x_i. message GradientDef { string function_name = 1; // The function name. string gradient_func = 2; // The gradient function's name. } ROCm-AMDMIGraphX-46524e8/src/tf/graph.proto000066400000000000000000000042021510465702400201240ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "GraphProtos"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; import "node_def.proto"; import "function.proto"; import "versions.proto"; // Represents the graph of operations message GraphDef { repeated NodeDef node = 1; // Compatibility versions of the graph. See core/public/version.h for version // history. The GraphDef version is distinct from the TensorFlow version, and // each release of TensorFlow will support a range of GraphDef versions. VersionDef versions = 4; // Deprecated single version field; use versions above instead. Since all // GraphDef changes before "versions" was introduced were forward // compatible, this field is entirely ignored. int32 version = 3 [deprecated = true]; // EXPERIMENTAL. DO NOT USE OR DEPEND ON THIS YET. // // "library" provides user-defined functions. // // Naming: // * library.function.name are in a flat namespace. // NOTE: We may need to change it to be hierarchical to support // different orgs. E.g., // { "/google/nn", { ... }}, // { "/google/vision", { ... }} // { "/org_foo/module_bar", { ... }} // map named_lib; // * If node[i].op is the name of one function in "library", // node[i] is deemed as a function call. Otherwise, node[i].op // must be a primitive operation supported by the runtime. // // // Function call semantics: // // * The callee may start execution as soon as some of its inputs // are ready. The caller may want to use Tuple() mechanism to // ensure all inputs are ready in the same time. // // * The consumer of return values may start executing as soon as // the return values the consumer depends on are ready. The // consumer may want to use Tuple() mechanism to ensure the // consumer does not start until all return values of the callee // function are ready. FunctionDefLibrary library = 2; }; ROCm-AMDMIGraphX-46524e8/src/tf/include/000077500000000000000000000000001510465702400173635ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/tf/include/migraphx/000077500000000000000000000000001510465702400212025ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/tf/include/migraphx/tf/000077500000000000000000000000001510465702400216135ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/src/tf/include/migraphx/tf/op_parser.hpp000066400000000000000000000065541510465702400243300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_TF_REGISTER_OP_PARSER_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_TF_REGISTER_OP_PARSER_HPP #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct op_desc { std::string tf_name = ""; std::string op_name = ""; }; void register_op_parser(const std::string& name, tf_parser::op_func f); tf_parser::op_func get_op_parser(const std::string& name); std::vector get_op_parsers(); inline std::vector implicit_multi_op(std::vector inss) { return inss; } inline std::vector implicit_multi_op(instruction_ref ins) { return {ins}; } template void register_op_parser() { T parser; for(auto&& opd : parser.operators()) register_op_parser(opd.tf_name, [opd, parser](auto&&... xs) { return parser.base_parse(opd, xs...); }); } struct register_op_parser_action { template static void apply() { register_op_parser(); } }; template struct op_parser : auto_register { bool transpose() const { return false; } std::vector base_parse(const op_desc& opd, const tf_parser& parser, const tf_parser::node_info& info, const std::vector& args) const { std::vector result; auto& self = static_cast(*this); if(self.transpose()) { result = implicit_multi_op(self.parse(opd, parser, info, parser.to_nchw(args))); std::transform(result.begin(), result.end(), result.begin(), [&](auto ins) { return parser.to_nhwc(ins); }); } else { result = implicit_multi_op(self.parse(opd, parser, info, args)); } return result; } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/tf/include/migraphx/tf/tf_parser.hpp000066400000000000000000000130671510465702400243200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AMDMIGRAPHX_TF_PARSER_HPP #define MIGRAPHX_GUARD_AMDMIGRAPHX_TF_PARSER_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { // namespace tf = tf_for_migraphx; struct tf_parser { std::string filename; std::string path = "."; using attribute_map = std::unordered_map; struct node_info { attribute_map attributes{}; std::string name = ""; module* mm = nullptr; instruction_ref make_contiguous(instruction_ref ins) const; instruction_ref add_broadcastable_binary_op(const std::string& op_name, instruction_ref arg0, instruction_ref arg1) const; instruction_ref add_common_op(const std::string& op_name, std::vector inputs) const; template instruction_ref add_common_op(const std::string& op_name, Ts... xs) const { return add_common_op(op_name, {xs...}); } instruction_ref add_instruction(const operation& op, const std::vector& args) const; template instruction_ref add_instruction(const operation& op, Ts... xs) const { return add_instruction(op, {xs...}); } instruction_ref add_literal(literal l) const; template instruction_ref add_literal(Ts&&... xs) const { return add_literal(literal{std::forward(xs)...}); } }; using node_map = std::map; using op_func = std::function( const tf_parser&, const node_info&, std::vector)>; node_map nodes; std::vector input_nodes; std::vector output_node_names; std::unordered_map instructions; program prog = program(); module* mm = prog.get_main_module(); bool is_nhwc = true; unsigned int batch_size = 1; std::size_t default_dim_value = 1; std::unordered_map> map_input_dims; std::unordered_map ops; tf_parser(); operation load(const std::string& name, const node_info& info) const; bool should_transpose(instruction_ref ins) const; instruction_ref to_nhwc(instruction_ref ins) const; instruction_ref to_nchw(instruction_ref ins) const; instruction_ref to_kcxy(instruction_ref ins) const; std::vector to_nchw(const std::vector& args) const; std::vector to_nhwc(const std::vector& args) const; int64_t parse_axis(int64_t dim, size_t num_dims) const; // tf stores certain attributes such as strides, dilations, as a 4D input. // The first and last dims are equal to 1, and the relevant data is in dims 2 and 3. // This helper function reorders the data to store for the respective operator member variables. template void reorder_data(std::vector& prev_data) const { std::vector new_data(prev_data.size()); for(size_t i = 0; i < new_data.size(); i++) { auto new_idx = parse_axis(i, new_data.size()); new_data.at(new_idx) = prev_data.at(i); } prev_data = new_data; } void parse_undefined(module* mm, const std::string& name); void parse_from(std::istream& is); void parse_from(const void* data, std::size_t size); void parse_graph(const tensorflow::GraphDef& graph); void parse_node(const std::string& name); literal parse_tensor(const tensorflow::TensorProto& t) const; shape::type_t parse_type(tensorflow::DataType t) const; std::vector find_outputs() const; }; std::vector get_axes_from_mask(size_t num_axes, uint32_t mask); } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/src/tf/node_def.proto000066400000000000000000000051311510465702400205700ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "NodeProto"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; import "attr_value.proto"; message NodeDef { // The name given to this operator. Used for naming inputs, // logging, visualization, etc. Unique within a single GraphDef. // Must match the regexp "[A-Za-z0-9.][A-Za-z0-9_./]*". string name = 1; // The operation name. There may be custom parameters in attrs. // Op names starting with an underscore are reserved for internal use. string op = 2; // Each input is "node:src_output" with "node" being a string name and // "src_output" indicating which output tensor to use from "node". If // "src_output" is 0 the ":0" suffix can be omitted. Regular inputs // may optionally be followed by control inputs that have the format // "^node". repeated string input = 3; // A (possibly partial) specification for the device on which this // node should be placed. // The expected syntax for this string is as follows: // // DEVICE_SPEC ::= PARTIAL_SPEC // // PARTIAL_SPEC ::= ("/" CONSTRAINT) * // CONSTRAINT ::= ("job:" JOB_NAME) // | ("replica:" [1-9][0-9]*) // | ("task:" [1-9][0-9]*) // | ("device:" [A-Za-z]* ":" ([1-9][0-9]* | "*") ) // // Valid values for this string include: // * "/job:worker/replica:0/task:1/device:GPU:3" (full specification) // * "/job:worker/device:GPU:3" (partial specification) // * "" (no specification) // // If the constraints do not resolve to a single device (or if this // field is empty or not present), the runtime will attempt to // choose a device automatically. string device = 4; // Operation-specific graph-construction-time configuration. // Note that this should include all attrs defined in the // corresponding OpDef, including those with a value matching // the default -- this allows the default to change and makes // NodeDefs easier to interpret on their own. However, if // an attr with a default is not specified in this list, the // default will be used. // The "names" (keys) must match the regexp "[a-z][a-z0-9_]+" (and // one of the names from the corresponding OpDef's attr field). // The values must have a type matching the corresponding OpDef // attr's type field. // TODO(josh11b): Add some examples here showing best practices. map attr = 5; }; ROCm-AMDMIGraphX-46524e8/src/tf/op_def.proto000066400000000000000000000147171510465702400202730ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "OpDefProtos"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; import "attr_value.proto"; import "types.proto"; // Defines an operation. A NodeDef in a GraphDef specifies an Op by // using the "op" field which should match the name of a OpDef. // LINT.IfChange message OpDef { // Op names starting with an underscore are reserved for internal use. // Names should be CamelCase and match the regexp "[A-Z][a-zA-Z0-9_]*". string name = 1; // For describing inputs and outputs. message ArgDef { // Name for the input/output. Should match the regexp "[a-z][a-z0-9_]*". string name = 1; // Human readable description. string description = 2; // Describes the type of one or more tensors that are accepted/produced // by this input/output arg. The only legal combinations are: // * For a single tensor: either the "type" field is set or the // "type_attr" field is set to the name of an attr with type "type". // * For a sequence of tensors with the same type: the "number_attr" // field will be set to the name of an attr with type "int", and // either the "type" or "type_attr" field will be set as for // single tensors. // * For a sequence of tensors, the "type_list_attr" field will be set // to the name of an attr with type "list(type)". DataType type = 3; string type_attr = 4; // if specified, attr must have type "type" string number_attr = 5; // if specified, attr must have type "int" // If specified, attr must have type "list(type)", and none of // type, type_attr, and number_attr may be specified. string type_list_attr = 6; // For inputs: if true, the inputs are required to be refs. // By default, inputs can be either refs or non-refs. // For outputs: if true, outputs are refs, otherwise they are not. bool is_ref = 16; }; // Description of the input(s). repeated ArgDef input_arg = 2; // Description of the output(s). repeated ArgDef output_arg = 3; // Description of the graph-construction-time configuration of this // Op. That is to say, this describes the attr fields that will // be specified in the NodeDef. message AttrDef { // A descriptive name for the argument. May be used, e.g. by the // Python client, as a keyword argument name, and so should match // the regexp "[a-z][a-z0-9_]+". string name = 1; // One of the type names from attr_value.proto ("string", "list(string)", // "int", etc.). string type = 2; // A reasonable default for this attribute if the user does not supply // a value. If not specified, the user must supply a value. AttrValue default_value = 3; // Human-readable description. string description = 4; // TODO(josh11b): bool is_optional? // --- Constraints --- // These constraints are only in effect if specified. Default is no // constraints. // For type == "int", this is a minimum value. For "list(___)" // types, this is the minimum length. bool has_minimum = 5; int64 minimum = 6; // The set of allowed values. Has type that is the "list" version // of the "type" field above (uses the "list" field of AttrValue). // If type == "type" or "list(type)" above, then the "type" field // of "allowed_values.list" has the set of allowed DataTypes. // If type == "string" or "list(string)", then the "s" field of // "allowed_values.list" has the set of allowed strings. AttrValue allowed_values = 7; } repeated AttrDef attr = 4; // Optional deprecation based on GraphDef versions. OpDeprecation deprecation = 8; // One-line human-readable description of what the Op does. string summary = 5; // Additional, longer human-readable description of what the Op does. string description = 6; // ------------------------------------------------------------------------- // Which optimizations this operation can participate in. // True if the operation is commutative ("op(a,b) == op(b,a)" for all inputs) bool is_commutative = 18; // If is_aggregate is true, then this operation accepts N >= 2 // inputs and produces 1 output all of the same type. Should be // associative and commutative, and produce output with the same // shape as the input. The optimizer may replace an aggregate op // taking input from multiple devices with a tree of aggregate ops // that aggregate locally within each device (and possibly within // groups of nearby devices) before communicating. // TODO(josh11b): Implement that optimization. bool is_aggregate = 16; // for things like add // Other optimizations go here, like // can_alias_input, rewrite_when_output_unused, partitioning_strategy, etc. // ------------------------------------------------------------------------- // Optimization constraints. // Ops are marked as stateful if their behavior depends on some state beyond // their input tensors (e.g. variable reading op) or if they have // a side-effect (e.g. printing or asserting ops). Equivalently, stateless ops // must always produce the same output for the same input and have // no side-effects. // // By default Ops may be moved between devices. Stateful ops should // either not be moved, or should only be moved if that state can also // be moved (e.g. via some sort of save / restore). // Stateful ops are guaranteed to never be optimized away by Common // Subexpression Elimination (CSE). bool is_stateful = 17; // for things like variables, queue // ------------------------------------------------------------------------- // Non-standard options. // By default, all inputs to an Op must be initialized Tensors. Ops // that may initialize tensors for the first time should set this // field to true, to allow the Op to take an uninitialized Tensor as // input. bool allows_uninitialized_input = 19; // for Assign, etc. }; // LINT.ThenChange( // https://www.tensorflow.org/code/tensorflow/core/framework/op_def_util.cc) // Information about version-dependent deprecation of an op message OpDeprecation { // First GraphDef version at which the op is disallowed. int32 version = 1; // Explanation of why it was deprecated and what to use instead. string explanation = 2; }; // A collection of OpDefs message OpList { repeated OpDef op = 1; }; ROCm-AMDMIGraphX-46524e8/src/tf/op_parser.cpp000066400000000000000000000041031510465702400204340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { static std::unordered_map& op_parser_map() { static std::unordered_map m; // NOLINT return m; } void register_op_parser(const std::string& name, tf_parser::op_func f) { op_parser_map()[name] = std::move(f); } tf_parser::op_func get_op_parser(const std::string& name) { return op_parser_map().at(name); } std::vector get_op_parsers() { std::vector result; std::transform(op_parser_map().begin(), op_parser_map().end(), std::back_inserter(result), [&](auto&& p) { return p.first; }); std::sort(result.begin(), result.end()); return result; } } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_addn.cpp000066400000000000000000000040321510465702400205430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_addn : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"AddN"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { instruction_ref sum = args[0]; for(auto i = 1; i < args.size(); i++) { sum = info.add_instruction(make_op("add"), sum, args[i]); } return sum; } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_arg_op.cpp000066400000000000000000000041311510465702400211040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_arg_op : op_parser { std::vector operators() const { return {{"ArgMax", "argmax"}, {"ArgMin", "argmin"}}; } instruction_ref parse(const op_desc& opd, const tf_parser& /*parser*/, const tf_parser::node_info& info, const std::vector& args) const { int64_t axis = 0; axis = args[1]->eval().at(); auto ins = info.add_instruction(make_op(opd.op_name, {{"axis", axis}}), args.front()); return info.add_instruction(make_op("squeeze", {{"axes", {axis}}}), ins); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_batchnorm.cpp000066400000000000000000000065451510465702400216250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_batchnorm : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"FusedBatchNorm"}, {"FusedBatchNormV3"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { // different default epsilon than from ONNX float epsilon = 1e-4f; if(contains(info.attributes, "epsilon")) { epsilon = info.attributes.at("epsilon").f(); } auto x_lens = args[0]->get_shape().lens(); auto x_type = args[0]->get_shape().type(); // unsqueeze tensors of shape (C) to broadcast correctly auto eps = info.add_literal(migraphx::literal{migraphx::shape{x_type}, {epsilon}}); auto scale_unsqueeze = info.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), args[1]); auto bias_unsqueeze = info.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), args[2]); auto mean_unsqueeze = info.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), args[3]); auto var_unsqueeze = info.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), args[4]); auto x_sub_mean = info.add_broadcastable_binary_op("sub", args[0], mean_unsqueeze); auto var_eps = info.add_broadcastable_binary_op("add", var_unsqueeze, eps); auto rsqrt = info.add_instruction(make_op("rsqrt"), var_eps); auto mul0 = info.add_broadcastable_binary_op("mul", scale_unsqueeze, rsqrt); auto r0 = info.add_broadcastable_binary_op("mul", x_sub_mean, mul0); return info.add_broadcastable_binary_op("add", r0, bias_unsqueeze); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_biasadd.cpp000066400000000000000000000042401510465702400212250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_biasadd : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"BiasAdd"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { uint64_t axis = 1; // assume output of previous layer is in NCHW (broadcast on channel) auto l0 = info.add_instruction( make_op("broadcast", {{"axis", axis}, {"out_lens", args[0]->get_shape().lens()}}), args[1]); return info.add_instruction(make_op("add"), args[0], l0); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_binary_op.cpp000066400000000000000000000043321510465702400216220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_binary_op : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"Add", "add"}, {"AddV2", "add"}, {"Mul", "mul"}, {"Pow", "pow"}, {"SquaredDifference", "sqdiff"}, {"Sub", "sub"}}; } instruction_ref parse(const op_desc& opd, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { if(args.size() != 2) MIGRAPHX_THROW("binary operators should have 2 operands"); return info.add_broadcastable_binary_op(opd.op_name, args[0], args[1]); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_cast.cpp000066400000000000000000000037671510465702400206050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_cast : op_parser { std::vector operators() const { return {{"Cast"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& parser, tf_parser::node_info info, const std::vector& args) const { shape::type_t type = parser.parse_type(info.attributes.at("DstT").type()); return info.add_instruction(make_op("convert", {{"target_type", type}}), args); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_concat.cpp000066400000000000000000000044041510465702400211070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_concat : op_parser { std::vector operators() const { return {{"ConcatV2"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { // get index for axis within args size_t axis_idx = info.attributes.at("N").i(); int64_t axis = args[axis_idx]->eval().at(); auto op = make_op("concat", {{"axis", axis}}); // return only first N arguments (assuming last index is the axis value) return info.add_instruction( op, std::vector(args.begin(), args.begin() + args.size() - 1)); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_constant.cpp000066400000000000000000000037321510465702400214740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_constant_op : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"Const"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& parser, tf_parser::node_info info, const std::vector& /*args*/) const { literal v = parser.parse_tensor(info.attributes.at("value").tensor()); return info.add_literal(v); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_conv.cpp000066400000000000000000000106731510465702400206120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_conv : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"Conv2D"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& parser, tf_parser::node_info info, std::vector args) const { op::convolution op; if(contains(info.attributes, "strides")) { std::vector stride; copy(info.attributes.at("strides").list().i(), std::back_inserter(stride)); parser.reorder_data(stride); if(stride.size() != 4) { MIGRAPHX_THROW("strides should have 4 values"); } op.stride[0] = stride[2]; op.stride[1] = stride[3]; } if(contains(info.attributes, "dilations")) { std::vector dilation; copy(info.attributes.at("dilations").list().i(), std::back_inserter(dilation)); parser.reorder_data(dilation); if(dilation.size() != 4) { MIGRAPHX_THROW("dilation should have 4 values"); } op.dilation[0] = dilation[2]; op.dilation[1] = dilation[3]; } auto weights = parser.to_kcxy(args[1]); auto l0 = args[0]; if(contains(info.attributes, "padding")) { const std::string& pad_mode = info.attributes.at("padding").s(); if(pad_mode.find("SAME") != std::string::npos) { std::vector weight_dims = weights->get_shape().lens(); size_t weight_h = weight_dims[2]; size_t weight_w = weight_dims[3]; auto input_dims = l0->get_shape().lens(); std::vector pads(input_dims.size()); calculate_padding(0, pads, input_dims[2], op.stride[0], op.dilation[0], weight_h); calculate_padding(1, pads, input_dims[3], op.stride[1], op.dilation[1], weight_w); op.padding = std::vector(pads.begin(), pads.end()); } else if(pad_mode.find("EXPLICIT") != std::string::npos) { std::vector padding; copy(info.attributes.at("explicit_paddings").list().i(), std::back_inserter(padding)); if(padding.size() != 4) { MIGRAPHX_THROW("padding should have 4 values"); } if(padding[0] != padding[2] or padding[1] != padding[3]) { MIGRAPHX_THROW("migraphx does not support asymetric padding"); } op.padding[0] = padding[0]; op.padding[1] = padding[1]; } } return info.add_instruction(op, {l0, weights}); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_depthwiseconv.cpp000066400000000000000000000120151510465702400225170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_depthwiseconv : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"DepthwiseConv2dNative"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& parser, tf_parser::node_info info, std::vector args) const { op::convolution op; size_t num_channels = args[0]->get_shape().lens()[1]; op.group = num_channels; if(contains(info.attributes, "strides")) { std::vector stride; copy(info.attributes.at("strides").list().i(), std::back_inserter(stride)); parser.reorder_data(stride); if(stride.size() != 4) { MIGRAPHX_THROW("strides should have 4 values"); } op.stride[0] = stride[2]; op.stride[1] = stride[3]; } auto weights = parser.to_kcxy(args[1]); if(contains(info.attributes, "dilations")) { std::vector dilation; copy(info.attributes.at("dilations").list().i(), std::back_inserter(dilation)); parser.reorder_data(dilation); if(dilation.size() != 4) { MIGRAPHX_THROW("dilation should have 4 values"); } op.dilation[0] = dilation[2]; op.dilation[1] = dilation[3]; } auto l0 = args[0]; if(contains(info.attributes, "padding")) { const std::string& pad_mode = info.attributes.at("padding").s(); if(pad_mode.find("SAME") != std::string::npos) { std::vector weight_dims = weights->get_shape().lens(); size_t weight_h = weight_dims[2]; size_t weight_w = weight_dims[3]; auto input_dims = l0->get_shape().lens(); std::vector pads(input_dims.size()); calculate_padding(0, pads, input_dims[2], op.stride[0], op.dilation[0], weight_h); calculate_padding(1, pads, input_dims[3], op.stride[1], op.dilation[1], weight_w); if(pads[0] != pads[2] or pads[1] != pads[3]) { std::vector padding = {0, 0, pads[0], pads[1], 0, 0, pads[2], pads[3]}; l0 = info.add_instruction(migraphx::make_op("pad", {{"pads", padding}}), l0); } else { op.padding[0] = pads[0]; op.padding[1] = pads[1]; } } } std::vector new_weights_shape; copy(weights->get_shape().lens(), std::back_inserter(new_weights_shape)); // weight format is (out_channels, in_channels, h, w), but in depthwise_conv, // out_channels is equal to the multiplier. Adjust by inserting a reshape and // setting in_channels to 1 int64_t multiplier = new_weights_shape[0]; int64_t out_channels = num_channels * multiplier; new_weights_shape[0] = out_channels; new_weights_shape[1] = 1; // Make sure weights are contiguous before doing reshape auto new_weights = info.add_instruction(make_op("reshape", {{"dims", new_weights_shape}}), info.make_contiguous(weights)); return info.add_instruction(op, {l0, new_weights}); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_expanddims.cpp000066400000000000000000000045761510465702400220060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_expanddims : op_parser { std::vector operators() const { return {{"ExpandDims"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { std::vector input_dims = args[0]->get_shape().lens(); std::vector new_dims(input_dims.begin(), input_dims.end()); size_t num_dims = input_dims.size(); int32_t dim = args[1]->eval().at(); if(dim < 0) { new_dims.insert(new_dims.begin() + (num_dims + dim + 1), 1); } else { new_dims.insert(new_dims.begin() + dim, 1); } return info.add_instruction(make_op("reshape", {{"dims", new_dims}}), args[0]); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_gather.cpp000066400000000000000000000037501510465702400211150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_gather : op_parser { std::vector operators() const { return {{"GatherV2"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { int axis = args[2]->eval().at(); return info.add_instruction(make_op("gather", {{"axis", axis}}), {args[0], args[1]}); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_generic_op.cpp000066400000000000000000000043261510465702400217550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_generic_op : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"All", "identity"}, {"Identity", "identity"}, {"LessEqual", "identity"}, {"Relu", "relu"}, {"Rsqrt", "rsqrt"}, {"Sigmoid", "sigmoid"}, {"StopGradient", "identity"}, {"Tanh", "tanh"}}; } instruction_ref parse(const op_desc& opd, const tf_parser& /*parser*/, const tf_parser::node_info& info, const std::vector& args) const { return info.add_instruction(make_op(opd.op_name), args); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_matmul.cpp000066400000000000000000000060331510465702400211370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_matmul : op_parser { std::vector operators() const { return {{"BatchMatMul"}, {"BatchMatMulV2"}, {"MatMul"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { bool transa = false; bool transb = false; if(contains(info.attributes, "transpose_a")) { transa = info.attributes.at("transpose_a").b(); } if(contains(info.attributes, "transpose_b")) { transb = info.attributes.at("transpose_b").b(); } if(contains(info.attributes, "adj_x")) { transa = info.attributes.at("adj_x").b(); } if(contains(info.attributes, "adj_y")) { transb = info.attributes.at("adj_y").b(); } std::vector perm(args[0]->get_shape().lens().size()); std::iota(perm.begin(), perm.end(), int64_t{0}); // swap the last two elements std::iter_swap(perm.end() - 1, perm.end() - 2); auto l1 = (transa) ? info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[0]) : args[0]; auto l2 = (transb) ? info.add_instruction(make_op("transpose", {{"permutation", perm}}), args[1]) : args[1]; return info.add_instruction(make_op("dot"), l1, l2); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_mean.cpp000066400000000000000000000042631510465702400205630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_mean : op_parser { std::vector operators() const { return {{"Mean"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { bool keep_dims = info.attributes.at("keep_dims").b(); auto axes = args[1]->eval().get().to_vector(); auto ins = info.add_instruction(make_op("reduce_mean", {{"axes", axes}}), args[0]); if(not keep_dims) ins = info.add_instruction(make_op("squeeze", {{"axes", axes}}), ins); return ins; } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_onehot.cpp000066400000000000000000000051331510465702400211340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_onehot : op_parser { std::vector operators() const { return {{"OneHot"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { size_t depth = args[1]->eval().at(); int64_t axis = -1; float on_value = args[2]->eval().at(); float off_value = args[3]->eval().at(); std::vector depth_input(depth * depth, off_value); for(int i = 0; i < depth; i++) { depth_input[depth * i + i] = on_value; } if(contains(info.attributes, "axis")) axis = info.attributes.at("axis").i(); if(axis == -1) { shape s{shape::float_type, {depth, depth}}; auto l0 = info.add_literal({s, depth_input}); return info.add_instruction(make_op("gather", {{"axis", 0}}), {l0, args[0]}); } MIGRAPHX_THROW("MIGraphX does not support axis != -1"); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_pack.cpp000066400000000000000000000053541510465702400205630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_pack : op_parser { std::vector operators() const { return {{"Pack"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& parser, tf_parser::node_info info, std::vector args) const { // reinterpret as unsqueeze with concat std::vector unsqueezed_args; int64_t axis = 0; if(contains(info.attributes, "axis")) axis = info.attributes.at("axis").i(); size_t input_size = args.front()->get_shape().lens().size(); if(axis > input_size) { MIGRAPHX_THROW("TF_PARSER: axis value of " + to_string(axis) + " must be smaller than input size " + to_string(input_size)); } std::transform( args.begin(), args.end(), std::back_inserter(unsqueezed_args), [&](instruction_ref arg) { return info.add_instruction(make_op("unsqueeze", {{"axes", {axis}}}), arg); }); return parser.to_nhwc( info.add_instruction(make_op("concat", {{"axis", axis}}), unsqueezed_args)); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_pad.cpp000066400000000000000000000053441510465702400204100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_pad : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"Pad"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& parser, const tf_parser::node_info& info, std::vector args) const { size_t ndims = args.front()->get_shape().lens().size(); // in tf, the paddings are arranged as a 2d shape (ndims, 2), // the last dim contains the left padding and right padding respectively std::vector> pad_per_dim(ndims); auto tf_padding = args[1]->eval().get().to_vector(); for(size_t i = 0; i < 2 * ndims; i += 2) { pad_per_dim[i / 2].first = tf_padding[i]; pad_per_dim[i / 2].second = tf_padding[i + 1]; } parser.reorder_data(pad_per_dim); std::vector pads(ndims * 2); for(size_t i = 0; i < ndims; i++) { pads[i] = pad_per_dim[i].first; pads[i + ndims] = pad_per_dim[i].second; } return info.add_instruction(make_op("pad", {{"pads", pads}}), args.front()); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_pooling.cpp000066400000000000000000000074151510465702400213140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_pooling : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"AvgPool"}, {"MaxPool"}}; } instruction_ref parse(const op_desc& opd, const tf_parser& parser, tf_parser::node_info info, std::vector args) const { if(not starts_with(opd.tf_name, "Max") and not starts_with(opd.tf_name, "Av")) { MIGRAPHX_THROW("tf pooling mode must be Max or Average"); } op::pooling op{starts_with(opd.tf_name, "Max") ? op::pooling_mode::max : op::pooling_mode::average}; if(contains(info.attributes, "strides")) { std::vector stride; copy(info.attributes.at("strides").list().i(), std::back_inserter(stride)); parser.reorder_data(stride); if(stride.size() != 4) { MIGRAPHX_THROW("strides should have 4 values"); } op.stride[0] = stride[2]; op.stride[1] = stride[3]; } if(contains(info.attributes, "ksize")) { std::vector ksize; copy(info.attributes.at("ksize").list().i(), std::back_inserter(ksize)); parser.reorder_data(ksize); if(ksize.size() != 4) { MIGRAPHX_THROW("ksize should have 4 values"); } op.lengths[0] = ksize[2]; op.lengths[1] = ksize[3]; } auto l0 = args[0]; if(contains(info.attributes, "padding")) { const std::string& pad_mode = info.attributes.at("padding").s(); if(pad_mode.find("SAME") != std::string::npos) { auto input_dims = l0->get_shape().lens(); std::vector pads(input_dims.size()); calculate_padding(0, pads, input_dims[2], op.stride[0], 1, op.lengths[0]); calculate_padding(1, pads, input_dims[3], op.stride[1], 1, op.lengths[1]); op.padding = std::vector(pads.begin(), pads.end()); } } return info.add_instruction(op, l0); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_relu6.cpp000066400000000000000000000043131510465702400206740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_relu6 : op_parser { bool transpose() const { return true; } std::vector operators() const { return {{"Relu6"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { shape::type_t output_type = args[0]->get_shape().type(); auto min_val = info.add_literal(migraphx::literal{migraphx::shape{output_type}, {0.0f}}); auto max_val = info.add_literal(migraphx::literal{migraphx::shape{output_type}, {6.0f}}); return info.add_common_op("clip", args[0], min_val, max_val); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_reshape.cpp000066400000000000000000000042401510465702400212650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_reshape : op_parser { std::vector operators() const { return {{"Reshape"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { if(args.size() != 2) MIGRAPHX_THROW("reshape needs 2 arguments (input, new_shape)"); auto s = args[1]->eval(); std::vector dims; s.visit([&](auto v) { copy(v, std::back_inserter(dims)); }); return info.add_instruction(make_op("reshape", {{"dims", dims}}), args[0]); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_shape.cpp000066400000000000000000000045201510465702400207370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_shape : op_parser { std::vector operators() const { return {{"Shape"}}; } // Use a literal instruction to replace the shape since output of // shape operator are literals in migraphx instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { std::vector arg_shape = args[0]->get_shape().lens(); std::vector vec_shape(arg_shape.size()); migraphx::shape s(migraphx::shape::int32_type, {arg_shape.size()}); std::transform( arg_shape.begin(), arg_shape.end(), vec_shape.begin(), [](auto i) { return i; }); return info.add_literal(migraphx::literal{s, vec_shape}); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_slice.cpp000066400000000000000000000055131510465702400207410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_slice : op_parser { std::vector operators() const { return {{"Slice"}}; } // Use a literal instruction to replace the shape since output of // shape operator are literals in migraphx instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { auto starts = args[1]->eval().get().to_vector(); auto size = args[2]->eval().get().to_vector(); auto axes = args[0]->get_shape().lens(); size_t num_axes = axes.size(); std::vector axes_int64(axes.begin(), axes.end()); std::vector starts_int64(starts.begin(), starts.end()); std::vector ends(num_axes); std::vector op_axes(num_axes); std::iota(op_axes.begin(), op_axes.end(), 0); for(size_t i = 0; i < num_axes; i++) { if(size[i] == -1) ends[i] = axes_int64[i]; else ends[i] = starts_int64[i] + size[i]; } auto op = make_op("slice", {{"starts", starts_int64}, {"ends", ends}, {"axes", op_axes}}); return info.add_instruction(op, info.make_contiguous(args[0])); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_softmax.cpp000066400000000000000000000044411510465702400213220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_softmax : op_parser { std::vector operators() const { return {{"Softmax"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { int axis = -1; auto num_dims = args[0]->get_shape().lens().size(); if(contains(info.attributes, "axis")) { axis = static_cast(info.attributes.at("axis").i()); } axis = tune_axis(num_dims, axis, "tf_parse_softmax"); return info.add_instruction(make_op("softmax", {{"axis", axis}}), info.make_contiguous(args[0])); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_split.cpp000066400000000000000000000103001510465702400207630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_split : op_parser { std::vector operators() const { return {{"Split"}, {"SplitV"}}; } std::vector parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { bool vector_as_input = args.size() == 3; int num_outputs = 1; auto axis_arg = args[0]; auto input_arg = args[1]; if(vector_as_input) { input_arg = args[0]; axis_arg = args[2]; } if(contains(info.attributes, "num_split")) num_outputs = info.attributes.at("num_split").i(); std::vector splits(num_outputs); std::vector slice_pos{0}; if(vector_as_input) { splits = args[1]->eval().get().to_vector(); num_outputs = splits.size(); } assert(num_outputs > 0); if(num_outputs == 1) return std::vector{ info.add_instruction(make_op("identity"), input_arg)}; auto lens = input_arg->get_shape().lens(); auto num_dims = lens.size(); int axis = axis_arg->eval().at(); // ensure split is made evenly if "num_split" is used assert(vector_as_input or lens[axis] % num_outputs == 0); auto split_size = lens[axis] / num_outputs; // push back first end point of slice if(vector_as_input) { slice_pos.push_back(splits[0]); } else { slice_pos.push_back(split_size); } // calculate remaining end points for each slice for(auto i = 1; i < num_outputs; i++) { if(vector_as_input) { splits[i] += splits[i - 1]; slice_pos.push_back(splits[i]); } else { slice_pos.push_back((i + 1) * split_size); } } std::vector result; for(auto i = 0; i < num_outputs; i++) { std::vector axes(num_dims); std::iota(axes.begin(), axes.end(), 0); std::vector starts(num_dims, 0); std::vector ends(lens.begin(), lens.end()); starts[axis] = slice_pos[i]; ends[axis] = slice_pos[i + 1]; auto op = make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}); result.push_back(info.add_instruction(op, input_arg)); } return result; } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_squeeze.cpp000066400000000000000000000047341510465702400213270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_squeeze : op_parser { std::vector operators() const { return {{"Squeeze"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { auto input_dims = args[0]->get_shape().lens(); auto axes = info.attributes.at("squeeze_dims").list().i(); std::vector op_axes(axes.begin(), axes.end()); if(op_axes.empty()) // no squeeze_dims provided, remove any dim that equals 1 { for(size_t i = 0; i < input_dims.size(); i++) { if(input_dims.at(i) == 1) { op_axes.push_back(i); } } } return info.add_instruction(make_op("squeeze", {{"axes", op_axes}}), info.make_contiguous(args[0])); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_stridedslice.cpp000066400000000000000000000077521510465702400223270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_strideslice : op_parser { std::vector operators() const { return {{"StridedSlice"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, tf_parser::node_info info, std::vector args) const { auto starts = args[1]->eval().get().to_vector(); auto ends = args[2]->eval().get().to_vector(); auto l0 = args[0]; size_t num_axes = l0->get_shape().lens().size(); std::vector axes = l0->get_shape().lens(); std::vector op_starts(starts.begin(), starts.end()); std::vector op_ends(ends.begin(), ends.end()); std::vector op_axes(num_axes); std::iota(op_axes.begin(), op_axes.end(), 0); uint32_t begin_mask = 0; uint32_t end_mask = 0; uint32_t shrink_axis_mask = 0; uint32_t bitwise_compare = 1; std::vector squeeze_axes; if(contains(info.attributes, "begin_mask")) begin_mask = static_cast(info.attributes.at("begin_mask").i()); if(contains(info.attributes, "end_mask")) end_mask = static_cast(info.attributes.at("end_mask").i()); if(contains(info.attributes, "shrink_axis_mask")) shrink_axis_mask = static_cast(info.attributes.at("shrink_axis_mask").i()); std::vector begin_axes = get_axes_from_mask(num_axes, begin_mask); std::vector end_axes = get_axes_from_mask(num_axes, end_mask); for(size_t i = 0; i < num_axes; i++) { if(begin_axes.at(i) == 1) { op_starts.at(i) = 0; } if(end_axes.at(i) == 1) { op_ends.at(i) = axes.at(i); } } auto op = make_op("slice", {{"starts", op_starts}, {"ends", op_ends}, {"axes", op_axes}}); auto l1 = info.add_instruction(op, l0); if(shrink_axis_mask == 0) return l1; for(size_t i = 0; i < num_axes; i++) { // the LSB corresponds to axis 0 when determining which axes to squeeze if(((shrink_axis_mask >> i) & bitwise_compare) == 1) squeeze_axes.push_back(i); } return info.add_instruction(make_op("squeeze", {{"axes", squeeze_axes}}), l1); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/parse_transpose.cpp000066400000000000000000000040771510465702400216640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { struct parse_transpose : op_parser { std::vector operators() const { return {{"Transpose"}}; } instruction_ref parse(const op_desc& /*opd*/, const tf_parser& /*parser*/, const tf_parser::node_info& info, std::vector args) const { auto perm = args[1]->eval().get().to_vector(); std::vector dims(perm.begin(), perm.end()); return info.add_instruction(make_op("transpose", {{"permutation", dims}}), args.front()); } }; } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/resource_handle.proto000066400000000000000000000017021510465702400221670ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "ResourceHandle"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; // Protocol buffer representing a handle to a tensorflow resource. Handles are // not valid across executions, but can be serialized back and forth from within // a single run. message ResourceHandleProto { // Unique name for the device containing the resource. string device = 1; // Container in which this resource is placed. string container = 2; // Unique name of this resource. string name = 3; // Hash code for the type of the resource. Is only valid in the same device // and in the same execution. uint64 hash_code = 4; // For debug-only, the name of the type pointed to by this handle, if // available. string maybe_type_name = 5; }; ROCm-AMDMIGraphX-46524e8/src/tf/tensor.proto000066400000000000000000000063201510465702400203400ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "TensorProtos"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; import "resource_handle.proto"; import "tensor_shape.proto"; import "types.proto"; // Protocol buffer representing a tensor. message TensorProto { DataType dtype = 1; // Shape of the tensor. TODO(touts): sort out the 0-rank issues. TensorShapeProto tensor_shape = 2; // Only one of the representations below is set, one of "tensor_contents" and // the "xxx_val" attributes. We are not using oneof because as oneofs cannot // contain repeated fields it would require another extra set of messages. // Version number. // // In version 0, if the "repeated xxx" representations contain only one // element, that element is repeated to fill the shape. This makes it easy // to represent a constant Tensor with a single value. int32 version_number = 3; // Serialized raw tensor content from either Tensor::AsProtoTensorContent or // memcpy in tensorflow::grpc::EncodeTensorToByteBuffer. This representation // can be used for all tensor types. The purpose of this representation is to // reduce serialization overhead during RPC call by avoiding serialization of // many repeated small items. bytes tensor_content = 4; // Type specific representations that make it easy to create tensor protos in // all languages. Only the representation corresponding to "dtype" can // be set. The values hold the flattened representation of the tensor in // row major order. // DT_HALF, DT_BFLOAT16. Note that since protobuf has no int16 type, we'll // have some pointless zero padding for each value here. repeated int32 half_val = 13 [packed = true]; // DT_FLOAT. repeated float float_val = 5 [packed = true]; // DT_DOUBLE. repeated double double_val = 6 [packed = true]; // DT_INT32, DT_INT16, DT_INT8, DT_UINT8. repeated int32 int_val = 7 [packed = true]; // DT_STRING repeated bytes string_val = 8; // DT_COMPLEX64. scomplex_val(2*i) and scomplex_val(2*i+1) are real // and imaginary parts of i-th single precision complex. repeated float scomplex_val = 9 [packed = true]; // DT_INT64 repeated int64 int64_val = 10 [packed = true]; // DT_BOOL repeated bool bool_val = 11 [packed = true]; // DT_COMPLEX128. dcomplex_val(2*i) and dcomplex_val(2*i+1) are real // and imaginary parts of i-th double precision complex. repeated double dcomplex_val = 12 [packed = true]; // DT_RESOURCE repeated ResourceHandleProto resource_handle_val = 14; // DT_VARIANT repeated VariantTensorDataProto variant_val = 15; // DT_UINT32 repeated uint32 uint32_val = 16 [packed = true]; // DT_UINT64 repeated uint64 uint64_val = 17 [packed = true]; }; // Protocol buffer representing the serialization format of DT_VARIANT tensors. message VariantTensorDataProto { // Name of the type of objects being serialized. string type_name = 1; // Portions of the object that are not Tensors. bytes metadata = 2; // Tensors contained within objects being serialized. repeated TensorProto tensors = 3; } ROCm-AMDMIGraphX-46524e8/src/tf/tensor_shape.proto000066400000000000000000000031511510465702400215170ustar00rootroot00000000000000// Protocol buffer representing the shape of tensors. syntax = "proto3"; option cc_enable_arenas = true; option java_outer_classname = "TensorShapeProtos"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; package tensorflow; // Dimensions of a tensor. message TensorShapeProto { // One dimension of the tensor. message Dim { // Size of the tensor in that dimension. // This value must be >= -1, but values of -1 are reserved for "unknown" // shapes (values of -1 mean "unknown" dimension). Certain wrappers // that work with TensorShapeProto may fail at runtime when deserializing // a TensorShapeProto containing a dim value of -1. int64 size = 1; // Optional name of the tensor dimension. string name = 2; }; // Dimensions of the tensor, such as {"input", 30}, {"output", 40} // for a 30 x 40 2D tensor. If an entry has size -1, this // corresponds to a dimension of unknown size. The names are // optional. // // The order of entries in "dim" matters: It indicates the layout of the // values in the tensor in-memory representation. // // The first entry in "dim" is the outermost dimension used to layout the // values, the last entry is the innermost dimension. This matches the // in-memory layout of RowMajor Eigen tensors. // // If "dim.size()" > 0, "unknown_rank" must be false. repeated Dim dim = 2; // If true, the number of dimensions in the shape is unknown. // // If true, "dim.size()" must be 0. bool unknown_rank = 3; }; ROCm-AMDMIGraphX-46524e8/src/tf/tf.cpp000066400000000000000000000053431510465702400170620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template static program parse_tf_from(const tf_options& options, Ts&&... xs) { tf::tf_parser parser; parser.is_nhwc = options.is_nhwc; parser.batch_size = options.batch_size; parser.map_input_dims = options.map_input_dims; parser.output_node_names = options.output_node_names; #ifndef NDEBUG // Log the program when it can't be parsed try { parser.parse_from(std::forward(xs)...); } catch(...) { std::cerr << parser.prog << std::endl; throw; } #else parser.parse_from(std::forward(xs)...); #endif return std::move(parser.prog); } program parse_tf(const std::string& name, const tf_options& options) { std::fstream input(name.c_str(), std::ios::in | std::ios::binary); return parse_tf_from(options, input); } program parse_tf_buffer(const std::string& buffer, const tf_options& options) { return parse_tf_from(options, buffer.data(), buffer.size()); } program parse_tf_buffer(const void* data, std::size_t size, const tf_options& options) { return parse_tf_from(options, data, size); } std::vector get_tf_operators() { return tf::get_op_parsers(); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/tf_parser.cpp000066400000000000000000000542111510465702400204340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace tf { bool tf_parser::should_transpose(instruction_ref ins) const { return is_nhwc and ins->get_shape().lens().size() == 4; } instruction_ref tf_parser::to_nhwc(instruction_ref ins) const { if(should_transpose(ins)) return mm->add_instruction(make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), ins); return ins; } instruction_ref tf_parser::to_nchw(instruction_ref ins) const { if(should_transpose(ins)) return mm->add_instruction(make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), ins); return ins; } instruction_ref tf_parser::to_kcxy(instruction_ref ins) const { return mm->add_instruction(make_op("transpose", {{"permutation", {3, 2, 0, 1}}}), ins); } std::vector tf_parser::to_nchw(const std::vector& args) const { std::vector result(args.size()); std::transform( args.begin(), args.end(), result.begin(), [&](auto ins) { return this->to_nchw(ins); }); return result; } std::vector tf_parser::to_nhwc(const std::vector& args) const { std::vector result(args.size()); std::transform( args.begin(), args.end(), result.begin(), [&](auto ins) { return this->to_nhwc(ins); }); return result; } instruction_ref tf_parser::node_info::make_contiguous(instruction_ref ins) const { if(ins->get_shape().standard()) return ins; else return mm->add_instruction(make_op("contiguous"), ins); } instruction_ref tf_parser::node_info::add_broadcastable_binary_op(const std::string& op_name, instruction_ref arg0, instruction_ref arg1) const { return this->add_common_op(op_name, arg0, arg1); } instruction_ref tf_parser::node_info::add_common_op(const std::string& op_name, std::vector inputs) const { return migraphx::add_common_op(*mm, make_op(op_name), std::move(inputs)); } int64_t tf_parser::parse_axis(const int64_t dim, const size_t num_dims) const { int64_t new_dim = dim; if(is_nhwc and num_dims >= 4) { switch(dim) { case 0: new_dim = 0; break; case 1: new_dim = 2; break; case 2: new_dim = 3; break; case 3: new_dim = 1; break; default: break; } } return new_dim; } instruction_ref tf_parser::node_info::add_instruction(const operation& op, const std::vector& args) const { return mm->add_instruction(op, args); } instruction_ref tf_parser::node_info::add_literal(literal l) const { return mm->add_literal(std::move(l)); } std::vector get_axes_from_mask(const size_t num_axes, const uint32_t mask) { uint32_t bitwise_compare = 1; std::vector axes; for(size_t i = 0; i < num_axes; i++) { // the LSB corresponds to axis 0 when determining which axes to begin if(((mask >> i) & bitwise_compare) == 1) axes.push_back(1); else axes.push_back(0); } return axes; } tf_parser::tf_parser() { // Add all registered op parsers for(auto&& name : get_op_parsers()) ops.emplace(name, get_op_parser(name)); } static std::string get_name(const tensorflow::NodeDef& node) { return node.name(); } static tf_parser::node_map get_nodes(const tensorflow::GraphDef& graph, std::vector& input_nodes) { tf_parser::node_map result; for(auto&& node : graph.node()) { auto node_name = get_name(node); // assume each node in graph has an associated name if(node_name.empty()) MIGRAPHX_THROW("tf node with no name found"); result[node_name] = node; if(node.op() == "Placeholder") { input_nodes.push_back(node); } } return result; } static tf_parser::attribute_map get_attributes(const tensorflow::NodeDef& node) { tf_parser::attribute_map result; for(auto&& attr : node.attr()) { result[attr.first] = attr.second; } return result; } static std::vector parse_dims(const tensorflow::TensorShapeProto& s) { std::vector dims; auto input_dims = s.dim(); std::transform(input_dims.begin(), input_dims.end(), std::back_inserter(dims), [](const tensorflow::TensorShapeProto_Dim& dim) { return dim.size(); }); return dims; } template static std::vector get_data_vals(const google::protobuf::RepeatedField& data, const size_t& shape_size) { std::vector data_vals(shape_size); // check if shape has enough data values given existing fields if(data.size() == 1) { std::fill(data_vals.begin(), data_vals.end(), data[0]); } else copy(data.begin(), data.end(), data_vals.begin()); return data_vals; } template static literal create_literal(shape::type_t shape_type, const std::vector& dims, const std::vector& data) { // assume if explicit value is mentioned in protobuf and dim size <= 1, treat as scalar if(dims.empty() or (dims.size() == 1 and dims.front() == 1)) return literal{{shape_type}, data}; return literal{{shape_type, dims}, data}; } static bool is_valid_op(const tensorflow::NodeDef& node) { std::vector ignored{"NoOp", "Assert"}; return none_of(ignored, [&](const auto& op) { const auto& name = get_name(node); return node.op() == op or contains(name, op); }); } std::vector tf_parser::find_outputs() const { std::unordered_set inputs; for(auto&& p : nodes) { auto&& node = p.second; std::copy(node.input().begin(), node.input().end(), std::inserter(inputs, inputs.end())); } std::vector outputs; for(auto&& p : nodes) { const auto& name = p.first; const auto& node = p.second; if(not is_valid_op(node)) continue; // control flow related, ignore this node if(contains(name, "^")) continue; // literals are valid ops, but they are not outputs unless specified if(node.op() == "Const") continue; if(inputs.count(name) == 0) outputs.push_back(name); } return outputs; } void tf_parser::parse_graph(const tensorflow::GraphDef& graph) { nodes = get_nodes(graph, input_nodes); for(auto&& input : input_nodes) { const std::string& name = input.name(); attribute_map input_attrs = get_attributes(input); shape::type_t shape_type = parse_type(input_attrs.at("dtype").type()); std::vector dims = parse_dims(input_attrs.at("shape").shape()); if(contains(map_input_dims, name)) { dims = map_input_dims.at(name); } else { if(is_nhwc and dims.size() >= 4) { this->reorder_data(dims); } std::transform(dims.begin(), dims.end(), dims.begin(), [&](auto dim) { return static_cast(dim) <= 0 ? batch_size : dim; }); } shape s = shape{shape_type, dims}; instructions[name] = to_nhwc(mm->add_parameter(name, s)); } for(auto&& p : nodes) { this->parse_node(p.first); } if(mm->size() == 0) return; // Needs to add a ret instruction at the end of // the program if(output_node_names.empty()) { output_node_names = find_outputs(); } std::vector output_ins; std::transform(output_node_names.begin(), output_node_names.end(), std::back_inserter(output_ins), [&](const auto& output_name) { if(not contains(instructions, output_name)) MIGRAPHX_THROW("PARSE_TF: output name " + output_name + " not found in graph!"); return this->to_nchw(instructions[output_name]); }); mm->add_return(output_ins); } void tf_parser::parse_node(const std::string& name) { if(instructions.count(name) == 0) { auto&& node = nodes.at(name); if(not is_valid_op(node)) return; std::vector args; for(auto&& input : node.input()) { // control dependencies (signified by ^ before the name) are ignored if(contains(input, "^")) continue; std::string input_name = input; // if input has trailing `:0` index then remove it auto multi_out_idx = input.find(':'); if(multi_out_idx != std::string::npos and input.substr(multi_out_idx + 1) == "0") { input_name = input.substr(0, multi_out_idx); } if(nodes.count(input_name) > 0) { // input was from a node with multiple outputs if(contains(input_name, ':')) { input_name.resize(input.find(':')); } else { input_name = get_name(nodes.at(input_name)); } assert(name != input_name); this->parse_node(input_name); args.push_back(instructions.at(input_name)); } else { args.push_back(instructions.at(input_name)); } } std::vector result; if(ops.count(node.op()) == 0) { result.push_back(mm->add_instruction(op::unknown{node.op()}, args)); } else { result = ops[node.op()](*this, {get_attributes(node), node.op(), mm}, args); } assert(not result.empty()); // First output has no ":" delimiter instructions[name] = result.front(); for(size_t i = 1; i < result.size(); i++) { instructions[name + ":" + std::to_string(i)] = result.at(i); } } } void tf_parser::parse_from(std::istream& is) { tensorflow::GraphDef graph; if(graph.ParseFromIstream(&is)) { this->parse_graph(graph); } else { throw std::runtime_error("Failed reading tf file"); } } void tf_parser::parse_from(const void* data, std::size_t size) { tensorflow::GraphDef graph; if(graph.ParseFromArray(data, size)) { this->parse_graph(graph); } else { throw std::runtime_error("Failed reading tf buffer array"); } } shape::type_t tf_parser::parse_type(const tensorflow::DataType t) const { shape::type_t shape_type{}; switch(t) { case tensorflow::DataType::DT_FLOAT: shape_type = shape::float_type; break; case tensorflow::DataType::DT_DOUBLE: shape_type = shape::double_type; break; case tensorflow::DataType::DT_INT32: shape_type = shape::int32_type; break; case tensorflow::DataType::DT_INT16: shape_type = shape::int16_type; break; case tensorflow::DataType::DT_INT8: shape_type = shape::int8_type; break; case tensorflow::DataType::DT_INT64: shape_type = shape::int64_type; break; case tensorflow::DataType::DT_UINT16: shape_type = shape::uint16_type; break; case tensorflow::DataType::DT_HALF: shape_type = shape::half_type; break; case tensorflow::DataType::DT_UINT32: shape_type = shape::uint32_type; break; case tensorflow::DataType::DT_UINT64: shape_type = shape::uint64_type; break; case tensorflow::DataType::DT_INVALID: case tensorflow::DataType::DT_UINT8: case tensorflow::DataType::DT_STRING: case tensorflow::DataType::DT_COMPLEX64: case tensorflow::DataType::DT_BOOL: case tensorflow::DataType::DT_QINT8: case tensorflow::DataType::DT_QUINT8: case tensorflow::DataType::DT_QINT32: case tensorflow::DataType::DT_BFLOAT16: case tensorflow::DataType::DT_QINT16: case tensorflow::DataType::DT_QUINT16: case tensorflow::DataType::DT_COMPLEX128: case tensorflow::DataType::DT_RESOURCE: case tensorflow::DataType::DT_VARIANT: // tf pb should not use these types case tensorflow::DataType::DT_FLOAT_REF: case tensorflow::DataType::DT_DOUBLE_REF: case tensorflow::DataType::DT_INT32_REF: case tensorflow::DataType::DT_UINT8_REF: case tensorflow::DataType::DT_INT16_REF: case tensorflow::DataType::DT_INT8_REF: case tensorflow::DataType::DT_STRING_REF: case tensorflow::DataType::DT_COMPLEX64_REF: case tensorflow::DataType::DT_INT64_REF: case tensorflow::DataType::DT_BOOL_REF: case tensorflow::DataType::DT_QINT8_REF: case tensorflow::DataType::DT_QUINT8_REF: case tensorflow::DataType::DT_QINT32_REF: case tensorflow::DataType::DT_BFLOAT16_REF: case tensorflow::DataType::DT_QINT16_REF: case tensorflow::DataType::DT_QUINT16_REF: case tensorflow::DataType::DT_UINT16_REF: case tensorflow::DataType::DT_COMPLEX128_REF: case tensorflow::DataType::DT_HALF_REF: case tensorflow::DataType::DT_RESOURCE_REF: case tensorflow::DataType::DT_VARIANT_REF: case tensorflow::DataType::DT_UINT32_REF: case tensorflow::DataType::DT_UINT64_REF: case tensorflow::DataType::DataType_INT_MAX_SENTINEL_DO_NOT_USE_: case tensorflow::DataType::DataType_INT_MIN_SENTINEL_DO_NOT_USE_: break; } return shape_type; } literal tf_parser::parse_tensor(const tensorflow::TensorProto& t) const { std::vector dims = parse_dims(t.tensor_shape()); size_t shape_size = std::accumulate(dims.begin(), dims.end(), 1, std::multiplies()); if(not t.tensor_content().empty()) // has raw data { const std::string& s = t.tensor_content(); switch(t.dtype()) { case tensorflow::DataType::DT_FLOAT: return literal{{shape::float_type, dims}, s.data()}; case tensorflow::DataType::DT_BOOL: case tensorflow::DataType::DT_INT8: return literal{{shape::int8_type, dims}, s.data()}; case tensorflow::DataType::DT_UINT16: case tensorflow::DataType::DT_INT16: return literal{{shape::int16_type, dims}, s.data()}; case tensorflow::DataType::DT_INT32: return literal{{shape::int32_type, dims}, s.data()}; case tensorflow::DataType::DT_INT64: return literal{{shape::int64_type, dims}, s.data()}; case tensorflow::DataType::DT_HALF: return literal{{shape::half_type, dims}, s.data()}; case tensorflow::DataType::DT_DOUBLE: return literal{{shape::double_type, dims}, s.data()}; case tensorflow::DataType::DT_INVALID: case tensorflow::DataType::DT_UINT8: case tensorflow::DataType::DT_STRING: case tensorflow::DataType::DT_UINT32: case tensorflow::DataType::DT_UINT64: case tensorflow::DataType::DT_COMPLEX64: case tensorflow::DataType::DT_COMPLEX128: case tensorflow::DataType::DT_QINT8: case tensorflow::DataType::DT_QUINT8: case tensorflow::DataType::DT_QINT32: case tensorflow::DataType::DT_BFLOAT16: case tensorflow::DataType::DT_QINT16: case tensorflow::DataType::DT_QUINT16: case tensorflow::DataType::DT_RESOURCE: case tensorflow::DataType::DT_VARIANT: case tensorflow::DataType::DT_FLOAT_REF: case tensorflow::DataType::DT_DOUBLE_REF: case tensorflow::DataType::DT_INT32_REF: case tensorflow::DataType::DT_UINT8_REF: case tensorflow::DataType::DT_INT16_REF: case tensorflow::DataType::DT_INT8_REF: case tensorflow::DataType::DT_STRING_REF: case tensorflow::DataType::DT_COMPLEX64_REF: case tensorflow::DataType::DT_INT64_REF: case tensorflow::DataType::DT_BOOL_REF: case tensorflow::DataType::DT_QINT8_REF: case tensorflow::DataType::DT_QUINT8_REF: case tensorflow::DataType::DT_QINT32_REF: case tensorflow::DataType::DT_BFLOAT16_REF: case tensorflow::DataType::DT_QINT16_REF: case tensorflow::DataType::DT_QUINT16_REF: case tensorflow::DataType::DT_UINT16_REF: case tensorflow::DataType::DT_COMPLEX128_REF: case tensorflow::DataType::DT_HALF_REF: case tensorflow::DataType::DT_RESOURCE_REF: case tensorflow::DataType::DT_VARIANT_REF: case tensorflow::DataType::DT_UINT32_REF: case tensorflow::DataType::DT_UINT64_REF: case tensorflow::DataType::DataType_INT_MAX_SENTINEL_DO_NOT_USE_: case tensorflow::DataType::DataType_INT_MIN_SENTINEL_DO_NOT_USE_: throw std::runtime_error(""); } MIGRAPHX_THROW("Invalid tensor type"); } switch(t.dtype()) { case tensorflow::DataType::DT_FLOAT: return create_literal(shape::float_type, dims, get_data_vals(t.float_val(), shape_size)); case tensorflow::DataType::DT_INT8: return create_literal(shape::int8_type, dims, get_data_vals(t.int_val(), shape_size)); case tensorflow::DataType::DT_UINT16: return create_literal(shape::uint16_type, dims, get_data_vals(t.int_val(), shape_size)); case tensorflow::DataType::DT_INT16: return create_literal(shape::int16_type, dims, get_data_vals(t.int_val(), shape_size)); case tensorflow::DataType::DT_INT32: return create_literal(shape::int32_type, dims, get_data_vals(t.int_val(), shape_size)); case tensorflow::DataType::DT_INT64: return create_literal(shape::int64_type, dims, get_data_vals(t.int64_val(), shape_size)); case tensorflow::DataType::DT_BOOL: return create_literal(shape::int32_type, dims, get_data_vals(t.bool_val(), shape_size)); case tensorflow::DataType::DT_HALF: { std::vector data_int32 = get_data_vals(t.half_val(), shape_size); std::vector data_uint16(data_int32.begin(), data_int32.end()); std::vector data_half; std::transform(data_uint16.begin(), data_uint16.end(), std::back_inserter(data_half), [](uint16_t raw_val) { return *reinterpret_cast(&raw_val); }); return create_literal(shape::half_type, dims, data_half); } case tensorflow::DataType::DT_DOUBLE: return literal{{shape::double_type, dims}, get_data_vals(t.double_val(), shape_size)}; case tensorflow::DataType::DT_INVALID: case tensorflow::DataType::DT_UINT8: case tensorflow::DataType::DT_STRING: case tensorflow::DataType::DT_UINT32: case tensorflow::DataType::DT_UINT64: case tensorflow::DataType::DT_COMPLEX64: case tensorflow::DataType::DT_COMPLEX128: case tensorflow::DataType::DT_QINT8: case tensorflow::DataType::DT_QUINT8: case tensorflow::DataType::DT_QINT32: case tensorflow::DataType::DT_BFLOAT16: case tensorflow::DataType::DT_QINT16: case tensorflow::DataType::DT_QUINT16: case tensorflow::DataType::DT_RESOURCE: case tensorflow::DataType::DT_VARIANT: case tensorflow::DataType::DT_FLOAT_REF: case tensorflow::DataType::DT_DOUBLE_REF: case tensorflow::DataType::DT_INT32_REF: case tensorflow::DataType::DT_UINT8_REF: case tensorflow::DataType::DT_INT16_REF: case tensorflow::DataType::DT_INT8_REF: case tensorflow::DataType::DT_STRING_REF: case tensorflow::DataType::DT_COMPLEX64_REF: case tensorflow::DataType::DT_INT64_REF: case tensorflow::DataType::DT_BOOL_REF: case tensorflow::DataType::DT_QINT8_REF: case tensorflow::DataType::DT_QUINT8_REF: case tensorflow::DataType::DT_QINT32_REF: case tensorflow::DataType::DT_BFLOAT16_REF: case tensorflow::DataType::DT_QINT16_REF: case tensorflow::DataType::DT_QUINT16_REF: case tensorflow::DataType::DT_UINT16_REF: case tensorflow::DataType::DT_COMPLEX128_REF: case tensorflow::DataType::DT_HALF_REF: case tensorflow::DataType::DT_RESOURCE_REF: case tensorflow::DataType::DT_VARIANT_REF: case tensorflow::DataType::DT_UINT32_REF: case tensorflow::DataType::DT_UINT64_REF: case tensorflow::DataType::DataType_INT_MAX_SENTINEL_DO_NOT_USE_: case tensorflow::DataType::DataType_INT_MIN_SENTINEL_DO_NOT_USE_: throw std::runtime_error(""); } MIGRAPHX_THROW("Invalid tensor type"); } } // namespace tf } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/tf/types.proto000066400000000000000000000045051510465702400201750ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "TypesProtos"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; // LINT.IfChange enum DataType { // Not a legal value for DataType. Used to indicate a DataType field // has not been set. DT_INVALID = 0; // Data types that all computation devices are expected to be // capable to support. DT_FLOAT = 1; DT_DOUBLE = 2; DT_INT32 = 3; DT_UINT8 = 4; DT_INT16 = 5; DT_INT8 = 6; DT_STRING = 7; DT_COMPLEX64 = 8; // Single-precision complex DT_INT64 = 9; DT_BOOL = 10; DT_QINT8 = 11; // Quantized int8 DT_QUINT8 = 12; // Quantized uint8 DT_QINT32 = 13; // Quantized int32 DT_BFLOAT16 = 14; // Float32 truncated to 16 bits. Only for cast ops. DT_QINT16 = 15; // Quantized int16 DT_QUINT16 = 16; // Quantized uint16 DT_UINT16 = 17; DT_COMPLEX128 = 18; // Double-precision complex DT_HALF = 19; DT_RESOURCE = 20; DT_VARIANT = 21; // Arbitrary C++ data types DT_UINT32 = 22; DT_UINT64 = 23; // Do not use! These are only for parameters. Every enum above // should have a corresponding value below (verified by types_test). DT_FLOAT_REF = 101; DT_DOUBLE_REF = 102; DT_INT32_REF = 103; DT_UINT8_REF = 104; DT_INT16_REF = 105; DT_INT8_REF = 106; DT_STRING_REF = 107; DT_COMPLEX64_REF = 108; DT_INT64_REF = 109; DT_BOOL_REF = 110; DT_QINT8_REF = 111; DT_QUINT8_REF = 112; DT_QINT32_REF = 113; DT_BFLOAT16_REF = 114; DT_QINT16_REF = 115; DT_QUINT16_REF = 116; DT_UINT16_REF = 117; DT_COMPLEX128_REF = 118; DT_HALF_REF = 119; DT_RESOURCE_REF = 120; DT_VARIANT_REF = 121; DT_UINT32_REF = 122; DT_UINT64_REF = 123; } // LINT.ThenChange( // https://www.tensorflow.org/code/tensorflow/c/c_api.h, // https://www.tensorflow.org/code/tensorflow/go/tensor.go, // https://www.tensorflow.org/code/tensorflow/core/framework/tensor.cc, // https://www.tensorflow.org/code/tensorflow/core/framework/types.h, // https://www.tensorflow.org/code/tensorflow/core/framework/types.cc, // https://www.tensorflow.org/code/tensorflow/python/framework/dtypes.py, // https://www.tensorflow.org/code/tensorflow/python/framework/function.py) ROCm-AMDMIGraphX-46524e8/src/tf/versions.proto000066400000000000000000000020171510465702400206750ustar00rootroot00000000000000syntax = "proto3"; package tensorflow; option cc_enable_arenas = true; option java_outer_classname = "VersionsProtos"; option java_multiple_files = true; option java_package = "org.tensorflow.framework"; option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework"; // Version information for a piece of serialized data // // There are different types of versions for each type of data // (GraphDef, etc.), but they all have the same common shape // described here. // // Each consumer has "consumer" and "min_producer" versions (specified // elsewhere). A consumer is allowed to consume this data if // // producer >= min_producer // consumer >= min_consumer // consumer not in bad_consumers // message VersionDef { // The version of the code that produced this data. int32 producer = 1; // Any consumer below this version is not allowed to consume this data. int32 min_consumer = 2; // Specific consumer versions which are disallowed (e.g. due to bugs). repeated int32 bad_consumers = 3; }; ROCm-AMDMIGraphX-46524e8/src/tmp_dir.cpp000066400000000000000000000070241510465702400174740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 // cppcheck-suppress definePrefix #define WIN32_LEAN_AND_MEAN #include #undef getpid // cppcheck-suppress [definePrefix, defineUpperCase] #define getpid _getpid #else #include #include #endif namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DEBUG_SAVE_TEMP_DIR) static std::string random_string(std::string::size_type length) { static const std::string& chars = "0123456789" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::mt19937 rg{std::random_device{}()}; std::uniform_int_distribution pick(0, chars.length() - 1); std::string str(length, 0); std::generate(str.begin(), str.end(), [&] { return chars[pick(rg)]; }); return str; } static std::string unique_string(const std::string& prefix) { auto pid = getpid(); auto tid = std::this_thread::get_id(); auto clk = std::chrono::steady_clock::now().time_since_epoch().count(); std::stringstream ss; ss << std::hex << prefix << "-" << pid << "-" << tid << "-" << clk << "-" << random_string(16); return ss.str(); } tmp_dir::tmp_dir(std::string_view prefix) : path(fs::temp_directory_path() / unique_string(prefix.empty() ? "migraphx" : "migraphx-" + std::string{prefix})) { fs::create_directories(this->path); } void tmp_dir::execute(std::string_view cmd, const std::vector& args) const { process{cmd, args}.cwd(this->path).exec(); } tmp_dir::~tmp_dir() { if(not enabled(MIGRAPHX_DEBUG_SAVE_TEMP_DIR{})) { constexpr int max_retries_count = 5; for([[maybe_unused]] auto count : range(max_retries_count)) { std::error_code ec; fs::remove_all(path, ec); if(not ec) break; std::cerr << "Failed to remove " << path << ": " << ec.message() << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(125)); } } } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/truncate_float.cpp000066400000000000000000000102771510465702400210540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { static void quantize_module(module& m, const std::vector& ins_names, shape::type_t float_type) { for(auto ins : iterator_for(m)) { // instructions are not in the set to be quantized if(not(contains(ins_names, ins->name()) or contains(ins_names, "all"))) continue; // skip return and convert instructions if(contains({"@return", "convert"}, ins->name())) continue; if(ins->inputs().empty()) continue; auto mod_inputs = ins->module_inputs(); auto s = ins->get_shape(); // Convert each of the inputs that are floating point to float type auto inputs = ins->inputs(); std::transform(inputs.begin(), inputs.end(), inputs.begin(), [&](auto input) { auto input_type = input->get_shape().type(); if(input_type != shape::float_type and input_type != shape::double_type) return input; return m.insert_instruction( ins, make_op("convert", {{"target_type", float_type}}), input); }); // Insert quantized ins auto converted_ins = m.insert_instruction(ins, ins->get_operator(), inputs, mod_inputs); // tuple can't be directly converted, get_tuple_elem needs conversion if(ins->get_shape().type() == shape::tuple_type) { auto outputs = ins->outputs(); std::transform( outputs.begin(), outputs.end(), outputs.begin(), [&](const auto gte_ins) { auto gte_ins_float_type = m.insert_instruction(ins, gte_ins->get_operator(), converted_ins); // Convert back to output type after quantizing auto gte_converted = m.insert_instruction( ins, make_op("convert", {{"target_type", gte_ins->get_shape().type()}}), gte_ins_float_type); // Replace output instruction return m.replace_instruction(gte_ins, gte_converted); }); } else { // Convert back to original type after quantizing if(mod_inputs.empty()) { converted_ins = m.insert_instruction( ins, make_op("convert", {{"target_type", s.type()}}), converted_ins); } // Replace original instruction m.replace_instruction(ins, converted_ins); } } } void truncate_float_pass::apply(module& m) const { quantize_module(m, ins_names, float_type); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/value.cpp000066400000000000000000000437361510465702400171640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct value_base_impl : cloneable { virtual value::type_t get_type() { return value::null_type; } #define MIGRAPHX_VALUE_GENERATE_BASE_FUNCTIONS(vt, cpp_type) \ virtual const cpp_type* if_##vt() const { return nullptr; } MIGRAPHX_VISIT_VALUE_TYPES(MIGRAPHX_VALUE_GENERATE_BASE_FUNCTIONS) virtual std::vector* if_array() { return nullptr; } virtual std::map* if_object() { return nullptr; } virtual value_base_impl* if_value() const { return nullptr; } value_base_impl() = default; value_base_impl(const value_base_impl&) = default; value_base_impl& operator=(const value_base_impl&) = default; virtual ~value_base_impl() override {} }; #define MIGRAPHX_VALUE_GENERATE_BASE_TYPE(vt, cpp_type) \ struct vt##_value_holder : value_base_impl::share \ { \ vt##_value_holder(cpp_type d) : data(std::move(d)) {} \ virtual value::type_t get_type() override { return value::vt##_type; } \ virtual const cpp_type* if_##vt() const override { return &data; } \ cpp_type data; \ }; MIGRAPHX_VISIT_VALUE_TYPES(MIGRAPHX_VALUE_GENERATE_BASE_TYPE) struct array_value_holder : value_base_impl::derive { array_value_holder() {} array_value_holder(std::vector d) : data(std::move(d)) {} virtual value::type_t get_type() override { return value::array_type; } virtual std::vector* if_array() override { return &data; } std::vector data; }; struct object_value_holder : value_base_impl::derive { object_value_holder() {} object_value_holder(std::vector d, std::map l) : data(std::move(d)), lookup(std::move(l)) { } virtual value::type_t get_type() override { return value::object_type; } virtual std::vector* if_array() override { return &data; } virtual std::map* if_object() override { return &lookup; } std::vector data; std::map lookup; }; value::value(const value& rhs) : x(rhs.x ? rhs.x->clone() : nullptr), key(rhs.key) {} value& value::operator=(value rhs) { std::swap(rhs.x, x); if(not rhs.key.empty()) std::swap(rhs.key, key); return *this; } static void set_vector(std::shared_ptr& x, const std::vector& v, bool array_on_empty = true) { if(v.empty()) { if(array_on_empty) x = std::make_shared(); else x = std::make_shared(); return; } if(v.front().get_key().empty()) { x = std::make_shared(v); } else { std::map lookup; std::size_t i = 0; for(auto&& e : v) { lookup[e.get_key()] = i; i++; } x = std::make_shared(v, lookup); } } value::value(const std::initializer_list& i) : x(nullptr) { if(i.size() == 2 and i.begin()->is_string() and i.begin()->get_key().empty()) { key = i.begin()->get_string(); auto r = (i.begin() + 1)->x; x = r ? r->clone() : nullptr; return; } set_vector(x, std::vector(i.begin(), i.end())); } value::value(const std::vector& v, bool array_on_empty) : x(nullptr) { set_vector(x, v, array_on_empty); } value::value(const std::unordered_map& m) : value(std::vector(m.begin(), m.end()), false) { } value::value(const std::string& pkey, const std::vector& v, bool array_on_empty) : x(nullptr), key(pkey) { set_vector(x, v, array_on_empty); } value::value(const std::string& pkey, const std::unordered_map& m) : value(pkey, std::vector(m.begin(), m.end()), false) { } value::value(const std::string& pkey, std::nullptr_t) : x(nullptr), key(pkey) {} value::value(std::nullptr_t) : x(nullptr) {} value::value(const std::string& pkey, const value& rhs) : x(rhs.x ? rhs.x->clone() : nullptr), key(pkey) { } value::value(const std::string& pkey, const char* i) : value(pkey, std::string(i)) {} value::value(const char* i) : value(std::string(i)) {} #define MIGRAPHX_VALUE_GENERATE_DEFINE_METHODS(vt, cpp_type) \ value::value(cpp_type i) : x(std::make_shared(std::move(i))) {} \ value::value(const std::string& pkey, cpp_type i) \ : x(std::make_shared(std::move(i))), key(pkey) \ { \ } \ value& value::operator=(cpp_type rhs) \ { \ x = std::make_shared(std::move(rhs)); \ return *this; \ } \ bool value::is_##vt() const { return x ? x->get_type() == vt##_type : false; } \ const cpp_type& value::get_##vt() const \ { \ auto* r = this->if_##vt(); \ assert(r); \ return *r; \ } \ const cpp_type* value::if_##vt() const { return x ? x->if_##vt() : nullptr; } MIGRAPHX_VISIT_VALUE_TYPES(MIGRAPHX_VALUE_GENERATE_DEFINE_METHODS) value& value::operator=(const char* c) { *this = std::string{c}; return *this; } value& value::operator=(std::nullptr_t) { x = nullptr; return *this; } value& value::operator=(const std::initializer_list& i) { value rhs = i; std::swap(rhs.x, x); return *this; } bool value::is_array() const { return x ? x->get_type() == array_type : false; } const std::vector& value::value::get_array() const { const auto* r = this->if_array(); assert(r); return *r; } const std::vector* value::if_array() const { return x ? x->if_array() : nullptr; } bool value::is_object() const { return x ? x->get_type() == object_type : false; } const std::vector& value::get_object() const { const auto* r = this->if_object(); assert(r); return *r; } const std::vector* value::if_object() const { auto* r = x ? x->if_array() : nullptr; assert(r == nullptr or std::none_of(r->begin(), r->end(), [](auto&& v) { return v.get_key().empty(); })); return r; } bool value::is_null() const { return x == nullptr; } const std::string& value::get_key() const { return key; } static std::vector* if_array_impl(const std::shared_ptr& x) { if(x == nullptr) return nullptr; return x->if_array(); } static std::vector& get_array_impl(const std::shared_ptr& x) { auto* a = if_array_impl(x); assert(a); return *a; } static std::vector& get_array_throw(const std::shared_ptr& x) { auto* a = if_array_impl(x); if(a == nullptr) MIGRAPHX_THROW("Expected an array or object"); return *a; } template static T* find_impl(const std::shared_ptr& x, const std::string& key, T* end) { auto* a = if_array_impl(x); if(a == nullptr) return end; auto* lookup = x->if_object(); if(lookup == nullptr) return end; auto it = lookup->find(key); if(it == lookup->end()) return end; return std::addressof((*a)[it->second]); } value* value::find(const std::string& pkey) { return find_impl(x, pkey, this->end()); } const value* value::find(const std::string& pkey) const { return find_impl(x, pkey, this->end()); } bool value::contains(const std::string& pkey) const { const auto* it = find(pkey); if(it == nullptr) return false; if(it == end()) return false; return true; } std::size_t value::size() const { const auto* a = if_array_impl(x); if(a == nullptr) return 0; return a->size(); } bool value::empty() const { return size() == 0; } const value* value::data() const { auto* a = if_array_impl(x); if(a == nullptr) return nullptr; return a->data(); } value* value::data() { auto* a = if_array_impl(x); if(a == nullptr) return nullptr; return a->data(); } value* value::begin() { // cppcheck-suppress assertWithSideEffect assert(data() or empty()); return data(); } const value* value::begin() const { assert(data() or empty()); return data(); } value* value::end() { return begin() + size(); } const value* value::end() const { return begin() + size(); } value& value::front() { assert(this->size() > 0); return *begin(); } const value& value::front() const { assert(this->size() > 0); return *begin(); } value& value::back() { assert(this->size() > 0); return *std::prev(end()); } const value& value::back() const { assert(this->size() > 0); return *std::prev(end()); } value& value::at(std::size_t i) { auto* a = if_array_impl(x); if(a == nullptr) MIGRAPHX_THROW("Not an array"); return a->at(i); } const value& value::at(std::size_t i) const { auto* a = if_array_impl(x); if(a == nullptr) MIGRAPHX_THROW("Not an array"); return a->at(i); } value& value::at(const std::string& pkey) { auto* r = find(pkey); if(r == nullptr) MIGRAPHX_THROW("Not an object"); if(r == end()) MIGRAPHX_THROW("Key not found: " + pkey); return *r; } const value& value::at(const std::string& pkey) const { const auto* r = find(pkey); if(r == nullptr) MIGRAPHX_THROW("Not an object for field: " + pkey); if(r == end()) MIGRAPHX_THROW("Key not found: " + pkey); return *r; } value& value::operator[](std::size_t i) { assert(i < this->size()); return *(begin() + i); } const value& value::operator[](std::size_t i) const { assert(i < this->size()); return *(begin() + i); } value& value::operator[](const std::string& pkey) { return *emplace(pkey, nullptr).first; } void value::clear() { get_array_throw(x).clear(); } void value::resize(std::size_t n) { if(not is_array()) MIGRAPHX_THROW("Expected an array."); get_array_impl(x).resize(n); } void value::resize(std::size_t n, const value& v) { if(not is_array()) MIGRAPHX_THROW("Expected an array."); get_array_impl(x).resize(n, v); } std::pair value::insert(const value& v) { if(v.key.empty()) { if(not x) x = std::make_shared(); get_array_impl(x).push_back(v); assert(this->if_array()); return std::make_pair(&back(), true); } else { if(not x) x = std::make_shared(); auto p = x->if_object()->emplace(v.key, get_array_impl(x).size()); if(p.second) get_array_impl(x).push_back(v); assert(this->if_object()); return std::make_pair(&get_array_impl(x)[p.first->second], p.second); } } value* value::insert(const value* pos, const value& v) { assert(v.key.empty()); if(not x) x = std::make_shared(); auto&& a = get_array_impl(x); auto it = a.insert(a.begin() + (pos - begin()), v); return std::addressof(*it); } value value::without_key() const { value result = *this; result.key = ""; return result; } value value::with_key(const std::string& pkey) const { value result = *this; result.key = pkey; return result; } const std::shared_ptr& value::get_impl() const { return this->x; } static auto object_range(const std::shared_ptr& x) { auto* a = if_array_impl(x); assert(a != nullptr); auto* lookup = x->if_object(); assert(lookup != nullptr); #if defined(__clang_major__) && __clang_major__ > 19 std::vector result; std::transform(lookup->begin(), lookup->end(), std::back_inserter(result), [=](const auto& p) -> decltype(auto) { return (*a)[p.second]; }); return result; #else return views::transform(*lookup, [=](const auto& p) -> decltype(auto) { return (*a)[p.second]; }); #endif } template static void visit_for_compare(const value& x, F f) { if(x.is_object()) { f(object_range(x.get_impl())); } else { x.visit_value(f); } } template const static T& compare_decay(const T& x) { return x; } static int compare_decay(std::nullptr_t) { return 0; } template static bool compare(const value& x, const value& y, F f) { bool result = false; visit_for_compare(x, [&](const auto& a) { visit_for_compare(y, [&](const auto& b) { if constexpr(std::is_same{}) result = f(std::forward_as_tuple(x.get_key(), compare_decay(a)), std::forward_as_tuple(y.get_key(), compare_decay(b))); else assert(false); // NOLINT }); }); return result; } value::type_t value::get_type() const { if(not x) return null_type; return x->get_type(); } bool operator==(const value& x, const value& y) { if(x.get_type() != y.get_type()) return false; return compare(x, y, std::equal_to<>{}); } bool operator!=(const value& x, const value& y) { return not(x == y); } bool operator<(const value& x, const value& y) { if(x.get_type() != y.get_type()) return x.get_type() < y.get_type(); return compare(x, y, std::less<>{}); } bool operator<=(const value& x, const value& y) { return not(x > y); } bool operator>(const value& x, const value& y) { return y < x; } bool operator>=(const value& x, const value& y) { return not(x < y); } static void print_value(std::ostream& os, std::nullptr_t) { os << "null"; } template static void print_value(std::ostream& os, const T& x) { os << x; } template static void print_value(std::ostream& os, const std::pair& x) { os << x.first; os << ": "; print_value(os, x.second); } static void print_value(std::ostream& os, const std::vector& x) { os << "{"; os << to_string_range(x); os << "}"; } static void print_value(std::ostream& os, const value::binary& x) { os << x; } std::ostream& operator<<(std::ostream& os, const value& d) { d.visit([&](auto&& y) { print_value(os, y); }); return os; } template static std::size_t compute_hash(rank<0>, const std::string& key, const T& x) { std::size_t h = hash_value(key); hash_combine(h, x); return h; } static std::size_t compute_hash(rank<0>, const std::string& key, std::nullptr_t) { return hash_value(key); } template static auto compute_hash(rank<1>, const std::string& key, const Range& x) -> decltype(hash_value(*x.begin())) { std::size_t h = hash_value(key); for(const auto& v : x) hash_combine(h, v); return h; } std::size_t value::hash() const { std::size_t h = 0; visit_for_compare(*this, [&](const auto& a) { h = compute_hash(rank<2>{}, this->get_key(), a); }); return h; } void value::debug_print(bool show_type) const { if(show_type) { switch(get_type()) { #define MIGRAPHX_VALUE_GENERATE_TYPE_STRING_CASE(vt, cpp_type) \ case vt##_type: std::cout << #vt << ": "; break; MIGRAPHX_VISIT_VALUE_TYPES(MIGRAPHX_VALUE_GENERATE_TYPE_STRING_CASE) MIGRAPHX_VALUE_GENERATE_TYPE_STRING_CASE(null, ) MIGRAPHX_VALUE_GENERATE_TYPE_STRING_CASE(array, ) MIGRAPHX_VALUE_GENERATE_TYPE_STRING_CASE(object, ) } } std::cout << *this << std::endl; } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/verify_args.cpp000066400000000000000000000124401510465702400203540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_VERIFY_DUMP_DIFF); namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { bool verify_args(const std::string& name, const argument& target_arg, const verify::expected& ref_arg, verify::tolerance tols) { bool passed = true; argument t_arg = target_arg; argument r_arg = ref_arg.data(); if(not t_arg.get_shape().computable()) { shape o_t_shape = t_arg.get_shape(); shape o_r_shape = r_arg.get_shape(); assert(o_t_shape.type() == o_r_shape.type()); t_arg = t_arg.reshape(shape{shape::uint8_type, o_t_shape.lens(), o_t_shape.strides()}); r_arg = r_arg.reshape(shape{shape::uint8_type, o_r_shape.lens(), o_r_shape.strides()}); } visit_all(r_arg, t_arg)([&](auto ref, auto target) { double rms_error; passed = verify::verify_range_with_tolerance(target, verify::expected{ref}, tols, &rms_error); if(not passed) { // TODO: Check for nans std::cout << "FAILED: " << name << std::endl; std::cout << "RMS Error: " << rms_error << std::endl; if(ref.size() < 32 or enabled(MIGRAPHX_VERIFY_DUMP_DIFF{})) std::cout << "ref:" << ref << std::endl; if(target.size() < 32 or enabled(MIGRAPHX_VERIFY_DUMP_DIFF{})) std::cout << "target:" << target << std::endl; if(verify::range_zero(ref)) std::cout << "Ref data is all zeros" << std::endl; if(verify::range_zero(target)) std::cout << "Target data is all zeros" << std::endl; auto mxdiff = verify::max_diff(ref, target); std::cout << "Max diff: " << mxdiff << std::endl; auto idx = verify::mismatch_idx(ref, target, float_equal); if(idx < verify::range_distance(ref)) { std::cout << "Mismatch at " << idx << ": " << ref[idx] << " != " << target[idx] << std::endl; } auto ref_nan_idx = find_idx(ref, verify::not_finite); if(ref_nan_idx >= 0) std::cout << "Non finite number found in ref at " << ref_nan_idx << ": " << ref[ref_nan_idx] << std::endl; auto target_nan_idx = find_idx(target, verify::not_finite); if(target_nan_idx >= 0) std::cout << "Non finite number found in target at " << target_nan_idx << ": " << target[target_nan_idx] << std::endl; std::cout << std::endl; } else { if(verify::range_zero(ref)) std::cout << "Ref data is all zeros" << std::endl; if(verify::range_zero(target)) std::cout << "Target data is all zeros" << std::endl; auto ref_nan_idx = find_idx(ref, verify::not_finite); if(ref_nan_idx >= 0) std::cout << "Non finite number found in ref at " << ref_nan_idx << ": " << ref[ref_nan_idx] << std::endl; auto target_nan_idx = find_idx(target, verify::not_finite); if(target_nan_idx >= 0) std::cout << "Non finite number found in target at " << target_nan_idx << ": " << target[target_nan_idx] << std::endl; } }); return passed; } bool verify_args_with_tolerance(const std::string& name, const argument& target_arg, const verify::expected& ref_arg, std::size_t tolerance) { double rms_tol = 0.001; argument t_arg = target_arg; if(not t_arg.get_shape().computable()) { shape o_t_shape = t_arg.get_shape(); t_arg = t_arg.reshape(shape{shape::uint8_type, o_t_shape.lens(), o_t_shape.strides()}); } t_arg.visit([&](auto ta) { rms_tol = verify::get_rms_tol(ta, tolerance); }); verify::tolerance tols{rms_tol}; return verify_args(name, target_arg, ref_arg, tols); } } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx ROCm-AMDMIGraphX-46524e8/src/version.h.in000066400000000000000000000031171510465702400175740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ // clang-format off #define MIGRAPHX_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define MIGRAPHX_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define MIGRAPHX_VERSION_PATCH @PROJECT_VERSION_PATCH@ #define MIGRAPHX_VERSION_TWEAK "@PROJECT_VERSION_TWEAK@" #define MIGRAPHX_SO_MAJOR_VERSION \ @PROJECT_VERSION_MAJOR@ * 1000 * 1000 + \ @PROJECT_VERSION_MINOR@ * 1000 + \ @PROJECT_VERSION_PATCH@ // clang-format on ROCm-AMDMIGraphX-46524e8/test/000077500000000000000000000000001510465702400155175ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/CMakeLists.txt000066400000000000000000000145341510465702400202660ustar00rootroot00000000000000# #################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # #################################################################################### cmake_policy(SET CMP0057 NEW) find_package(Threads REQUIRED) rocm_test_link_libraries(Threads::Threads migraphx migraphx_onnx migraphx_tf) rocm_test_include_directories(include) set(MIGRAPHX_DISABLE_LARGE_BUFFER_TESTS Off CACHE BOOL "") if(MIGRAPHX_DISABLE_LARGE_BUFFER_TESTS) add_compile_definitions(MIGRAPHX_DISABLE_LARGE_BUFFER_TESTS) endif() add_library(register_targets STATIC register_target.cpp) target_link_libraries(register_targets PRIVATE migraphx migraphx_all_targets) file(GLOB TESTS CONFIGURE_DEPENDS *.cpp) list(REMOVE_ITEM TESTS ${CMAKE_CURRENT_SOURCE_DIR}/register_target.cpp) foreach(TEST ${TESTS}) get_filename_component(BASE_NAME ${TEST} NAME_WE) rocm_add_test_executable(test_${BASE_NAME} ${TEST}) rocm_clang_tidy_check(test_${BASE_NAME}) endforeach() if(MIGRAPHX_ENABLE_GPU) # gpu tests file(GLOB GPU_TESTS CONFIGURE_DEPENDS gpu/*.cpp) foreach(TEST ${GPU_TESTS}) get_filename_component(BASE_NAME ${TEST} NAME_WE) rocm_add_test_executable(test_gpu_${BASE_NAME} ${TEST}) rocm_clang_tidy_check(test_gpu_${BASE_NAME}) set_tests_properties(test_gpu_${BASE_NAME} PROPERTIES COST 10 RESOURCE_LOCK gpu ) if(MIGRAPHX_USE_HIPRTC) target_compile_definitions(test_gpu_${BASE_NAME} PUBLIC -DMIGRAPHX_USE_HIPRTC) endif() target_link_libraries(test_gpu_${BASE_NAME} migraphx_gpu migraphx_kernels register_targets) endforeach() endif() if(MIGRAPHX_ENABLE_FPGA) # fpga tests file(GLOB FPGA_TESTS CONFIGURE_DEPENDS fpga/*.cpp) foreach(TEST ${FPGA_TESTS}) get_filename_component(BASE_NAME ${TEST} NAME_WE) rocm_add_test_executable(test_fpga_${BASE_NAME} ${TEST}) rocm_clang_tidy_check(test_fpga_${BASE_NAME}) set_tests_properties(test_fpga_${BASE_NAME} PROPERTIES COST 10 RESOURCE_LOCK fpga ) target_link_libraries(test_fpga_${BASE_NAME} migraphx_fpga) endforeach() endif() # Onnx test set(TEST_ONNX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/onnx) add_subdirectory(onnx) # tf test set(TEST_TF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tf) add_subdirectory(tf) add_subdirectory(api) add_subdirectory(verify) add_subdirectory(ref) if(MIGRAPHX_ENABLE_PYTHON) add_subdirectory(py) endif() # multitarget test if(MIGRAPHX_ENABLE_GPU AND MIGRAPHX_ENABLE_CPU AND MIGRAPHX_ENABLE_FPGA) set(TEST_MULTI_TARGET_DIR ${CMAKE_CURRENT_SOURCE_DIR}/multi_target) file(GLOB MULTI_TARGET_TESTS CONFIGURE_DEPENDS ${TEST_MULTI_TARGET_DIR}/*.cpp) foreach(MULTI_TARGET_TEST ${MULTI_TARGET_TESTS}) get_filename_component(BASE_NAME ${MULTI_TARGET_TEST} NAME_WE) set(TEST_NAME test_${BASE_NAME}) add_executable(${TEST_NAME} ${MULTI_TARGET_TEST}) rocm_clang_tidy_check(${TEST_NAME}) target_link_libraries(${TEST_NAME} migraphx migraphx_onnx migraphx_tf migraphx_all_targets register_targets) target_include_directories(${TEST_NAME} PUBLIC include) add_test(NAME ${TEST_NAME} COMMAND $ WORKING_DIRECTORY ${TEST_MULTI_TARGET_DIR}) rocm_mark_as_test(${TEST_NAME}) endforeach() endif() function(test_header NAME HEADER) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/header-main-include-${NAME}.cpp " #include <${HEADER}> int main() {}\n" ) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/header-static-include-${NAME}.cpp " #include <${HEADER}> #if defined(min) || defined(max) || defined(near) || defined(far) #error \"Do not include windows.h in header files\" #endif \n" ) rocm_add_test_executable(${NAME} ${CMAKE_CURRENT_BINARY_DIR}/header-main-include-${NAME}.cpp ${CMAKE_CURRENT_BINARY_DIR}/header-static-include-${NAME}.cpp ) endfunction() function(test_headers PREFIX) file(GLOB HEADERS CONFIGURE_DEPENDS ${ARGN}) if(NOT MIGRAPHX_USE_COMPOSABLEKERNEL) list(REMOVE_ITEM HEADERS ${CMAKE_SOURCE_DIR}/src/targets/gpu/include/migraphx/gpu/ck.hpp) endif() list(REMOVE_ITEM HEADERS ${CMAKE_SOURCE_DIR}/src/include/migraphx/float8_impl.hpp) foreach(HEADER ${HEADERS}) file(RELATIVE_PATH HEADER_REL ${CMAKE_SOURCE_DIR} ${HEADER}) string(MAKE_C_IDENTIFIER ${HEADER_REL} TEST_NAME) get_filename_component(BASE_NAME ${HEADER} NAME_WE) test_header(header_${TEST_NAME} ${PREFIX}/${BASE_NAME}.hpp) target_link_libraries(header_${TEST_NAME} migraphx migraphx_onnx migraphx_tf migraphx_all_targets register_targets) endforeach() endfunction() test_headers(migraphx ${CMAKE_SOURCE_DIR}/src/include/migraphx/*.hpp) test_headers(migraphx/ref ${CMAKE_SOURCE_DIR}/src/targets/ref/include/migraphx/ref/*.hpp) if(MIGRAPHX_ENABLE_GPU) test_headers(migraphx/gpu HEADERS ${CMAKE_SOURCE_DIR}/src/targets/gpu/include/migraphx/gpu/*.hpp DEPENDS migraphx_gpu) endif() if(MIGRAPHX_ENABLE_CPU) test_headers(migraphx/cpu HEADERS ${CMAKE_SOURCE_DIR}/src/targets/cpu/include/migraphx/cpu/*.hpp migraphx_cpu) endif() if(MIGRAPHX_ENABLE_FPGA) test_headers(migraphx/fpga HEADERS ${CMAKE_SOURCE_DIR}/src/targets/fpga/include/migraphx/fpga/*.hpp migraphx_fpga) endif() ROCm-AMDMIGraphX-46524e8/test/algorithm.cpp000066400000000000000000000067451510465702400202250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include // NOLINTNEXTLINE #define MIGRAPHX_FORWARD_CONTAINER_TEST_CASE(name, type) \ template \ static void name(); \ TEST_CASE_REGISTER(name>); \ TEST_CASE_REGISTER(name>); \ TEST_CASE_REGISTER(name>); \ template \ void name() template static auto erase_iterator(Container& c, Iterator pos, Iterator last) -> decltype(c.erase_after(pos, last)) { auto n = std::distance(c.begin(), pos); auto it = n == 0 ? c.before_begin() : std::next(c.begin(), n - 1); return c.erase_after(it, last); } template static auto erase_iterator(Container& c, Iterator pos, Iterator last) -> decltype(c.erase(pos, last)) { return c.erase(pos, last); } MIGRAPHX_FORWARD_CONTAINER_TEST_CASE(adjacent_remove_if1, int) { Container v = {0, 1, 1, 1, 4, 2, 2, 4, 2}; erase_iterator(v, migraphx::adjacent_remove_if(v.begin(), v.end(), std::equal_to<>{}), v.end()); EXPECT(v == Container{0, 1, 4, 2, 4, 2}); } MIGRAPHX_FORWARD_CONTAINER_TEST_CASE(adjacent_remove_if2, int) { Container v = {0, 1, 1, 1, 4, 2, 2, 4, 2, 5, 5}; erase_iterator(v, migraphx::adjacent_remove_if(v.begin(), v.end(), std::equal_to<>{}), v.end()); EXPECT(v == Container{0, 1, 4, 2, 4, 2, 5}); } MIGRAPHX_FORWARD_CONTAINER_TEST_CASE(adjacent_remove_if3, int) { Container v = {0, 1, 1, 1, 4, 2, 2, 4, 2, 5, 5, 6}; erase_iterator(v, migraphx::adjacent_remove_if(v.begin(), v.end(), std::equal_to<>{}), v.end()); EXPECT(v == Container{0, 1, 4, 2, 4, 2, 5, 6}); } MIGRAPHX_FORWARD_CONTAINER_TEST_CASE(adjacent_remove_if_non_equivalence, int) { Container v = {0, 1, 1, 1, 4, 2, 2, 3, 4, 2, 5, 5, 6}; auto pred = [](int a, int b) { return (b - a) == 1; }; erase_iterator(v, migraphx::adjacent_remove_if(v.begin(), v.end(), pred), v.end()); EXPECT(v == Container{1, 1, 1, 4, 2, 4, 2, 5, 6}); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/analyze_streams.cpp000066400000000000000000000463661510465702400214430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" #include "basic_ops.hpp" struct record_event { std::size_t event = 0; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.event, "event")); } std::string name() const { return "record_event"; } migraphx::shape compute_shape(const std::vector&) const { return {}; } migraphx::argument compute(const migraphx::shape&, const std::vector&) const { return {}; } }; struct wait_event { std::size_t event = 0; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.event, "event")); } std::string name() const { return "wait_event"; } migraphx::shape compute_shape(const std::vector&) const { return {}; } migraphx::argument compute(const migraphx::shape&, const std::vector&) const { return {}; } }; struct set_stream { std::size_t stream = 0; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.stream, "stream")); } std::string name() const { return "set_stream"; } migraphx::shape compute_shape(const std::vector&) const { return {}; } migraphx::argument compute(const migraphx::shape&, const std::vector&) const { return {}; } }; struct test_stream_model { std::size_t max_stream = 0; std::unordered_map ins2stream{}; std::size_t get_nstream() const { return max_stream + 1; } std::size_t get_stream(migraphx::instruction_ref ins) const { return ins2stream.at(ins); } std::size_t get_event_id(migraphx::instruction_ref ins) const { auto v = ins->get_operator().to_value(); return v["event"].to(); } bool has_stream(migraphx::instruction_ref ins) const { return ins2stream.count(ins) > 0; } bool is_record(migraphx::instruction_ref ins) const { return ins->name() == "record_event"; } bool is_wait(migraphx::instruction_ref ins) const { return ins->name() == "wait_event"; } }; struct program_model { migraphx::program p; migraphx::module* mm = p.get_main_module(); std::unordered_map ins2stream{}; std::size_t max_stream = 0; template migraphx::instruction_ref add_literal(Ts... xs) { return mm->add_literal(xs...); } template migraphx::instruction_ref add_instruction(Ts... xs) { return mm->add_instruction(xs...); } template migraphx::instruction_ref add_instruction_stream(std::size_t n, Ts... xs) { max_stream = std::max(max_stream, n); auto ins = mm->add_instruction(xs...); ins2stream[ins] = n; return ins; } template migraphx::instruction_ref add_return(Ts... xs) { return mm->add_return({xs...}); } template migraphx::instruction_ref add_return_stream(std::size_t n, Ts... xs) { max_stream = std::max(max_stream, n); auto ins = mm->add_return({xs...}); ins2stream[ins] = n; return ins; } test_stream_model get_stream_model() const { return {max_stream, ins2stream}; } std::vector analyze() const { return migraphx::analyze_streams(*p.get_main_module(), get_stream_model()); } void debug_print() const { p.debug_print(); } void debug_print(const std::vector& races) const { for(auto&& race : races) { std::cout << "Race:\n"; mm->debug_print(race.ins); mm->debug_print(race.before); } } }; TEST_CASE(simple_race1) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); auto pass3 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass3); EXPECT(races.front().before == pass2); } TEST_CASE(simple_race2) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); auto pass21 = pm.add_instruction_stream(1, pass_op{}, pass2); auto pass3 = pm.add_instruction_stream(0, pass_op{}, pass1, pass21); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass3); EXPECT(races.front().before == pass21); } TEST_CASE(simple_race3) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass11 = pm.add_instruction_stream(0, pass_op{}, pass1); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); auto pass3 = pm.add_instruction_stream(0, pass_op{}, pass11, pass2); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass3); EXPECT(races.front().before == pass2); } TEST_CASE(simple_race4) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass11 = pm.add_instruction_stream(0, pass_op{}, pass1); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); auto pass21 = pm.add_instruction_stream(1, pass_op{}, pass2); auto pass3 = pm.add_instruction_stream(0, pass_op{}, pass11, pass21); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass3); EXPECT(races.front().before == pass21); } TEST_CASE(simple_race5) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); auto pass11 = pm.add_instruction_stream(0, pass_op{}, pass1); auto pass21 = pm.add_instruction_stream(1, pass_op{}, pass2); auto pass3 = pm.add_instruction_stream(0, pass_op{}, pass11, pass21); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass3); EXPECT(races.front().before == pass21); } TEST_CASE(simple_race_record_wait_wrong_stream) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(0, record_event{1}); pm.add_instruction_stream(1, wait_event{1}); auto pass3 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass3); EXPECT(races.front().before == pass2); } TEST_CASE(simple_race_record_wait_same_stream1) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); pm.add_instruction_stream(1, wait_event{1}); auto pass3 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass3); EXPECT(races.front().before == pass2); } TEST_CASE(simple_race_record_wait_same_stream2) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(0, record_event{1}); pm.add_instruction_stream(0, wait_event{1}); auto pass3 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass3); EXPECT(races.front().before == pass2); } TEST_CASE(simple_race_sync) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); pm.add_instruction_stream(0, wait_event{1}); pm.add_instruction_stream(0, pass_op{}, pass1, pass2); auto races = pm.analyze(); EXPECT(races.empty()); } TEST_CASE(race_return) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); auto r = pm.add_return_stream(0, pass1, pass2); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == r); EXPECT(races.front().before == pass2); } TEST_CASE(race_return_sync) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); pm.add_instruction_stream(0, wait_event{1}); pm.add_return_stream(0, pass1, pass2); auto races = pm.analyze(); EXPECT(races.empty()); } TEST_CASE(race_double_wait1) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one); pm.add_instruction_stream(2, wait_event{1}); auto pass4 = pm.add_instruction_stream(2, pass_op{}, pass3, pass2); pm.add_instruction_stream(2, record_event{2}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); pm.add_instruction_stream(0, record_event{3}); pm.add_instruction_stream(1, wait_event{3}); pm.add_instruction_stream(1, wait_event{2}); pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass5); EXPECT(races.front().before == pass2); } TEST_CASE(race_double_wait2) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one); auto pass4 = pm.add_instruction_stream(2, pass_op{}, pass3, pass2); pm.add_instruction_stream(2, record_event{2}); pm.add_instruction_stream(0, wait_event{1}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); pm.add_instruction_stream(0, record_event{3}); pm.add_instruction_stream(1, wait_event{3}); pm.add_instruction_stream(1, wait_event{2}); pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass4); EXPECT(races.front().before == pass2); } TEST_CASE(race_double_wait3) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one); pm.add_instruction_stream(2, wait_event{1}); auto pass4 = pm.add_instruction_stream(2, pass_op{}, pass3, pass2); pm.add_instruction_stream(2, record_event{2}); pm.add_instruction_stream(0, wait_event{1}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); pm.add_instruction_stream(1, wait_event{2}); auto pass6 = pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass6); EXPECT(races.front().before == pass5); } TEST_CASE(race_double_wait4) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one); pm.add_instruction_stream(2, wait_event{1}); auto pass4 = pm.add_instruction_stream(2, pass_op{}, pass3, pass2); pm.add_instruction_stream(2, record_event{2}); pm.add_instruction_stream(0, wait_event{1}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); pm.add_instruction_stream(0, record_event{3}); pm.add_instruction_stream(1, wait_event{3}); auto pass6 = pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass6); EXPECT(races.front().before == pass4); } TEST_CASE(race_double_wait_sync) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one); pm.add_instruction_stream(2, wait_event{1}); auto pass4 = pm.add_instruction_stream(2, pass_op{}, pass3, pass2); pm.add_instruction_stream(2, record_event{2}); pm.add_instruction_stream(0, wait_event{1}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass1, pass2); pm.add_instruction_stream(0, record_event{3}); pm.add_instruction_stream(1, wait_event{3}); pm.add_instruction_stream(1, wait_event{2}); pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.empty()); } TEST_CASE(race_multi_wait1) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); pm.add_instruction_stream(0, record_event{5}); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); pm.add_instruction_stream(2, wait_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one, pass2); pm.add_instruction_stream(2, record_event{2}); pm.add_instruction_stream(3, wait_event{5}); auto pass4 = pm.add_instruction_stream(3, pass_op{}, one, pass1); pm.add_instruction_stream(3, record_event{3}); pm.add_instruction_stream(0, wait_event{2}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass3, pass1); pm.add_instruction_stream(0, record_event{4}); pm.add_instruction_stream(1, wait_event{3}); auto pass6 = pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass6); EXPECT(races.front().before == pass5); } TEST_CASE(race_multi_wait2) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); pm.add_instruction_stream(0, record_event{5}); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); pm.add_instruction_stream(2, wait_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one, pass2); pm.add_instruction_stream(2, record_event{2}); pm.add_instruction_stream(3, wait_event{5}); auto pass4 = pm.add_instruction_stream(3, pass_op{}, one, pass1); pm.add_instruction_stream(3, record_event{3}); pm.add_instruction_stream(0, wait_event{2}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass3, pass1); pm.add_instruction_stream(0, record_event{4}); pm.add_instruction_stream(1, wait_event{4}); auto pass6 = pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass6); EXPECT(races.front().before == pass4); } TEST_CASE(race_multi_wait3) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); pm.add_instruction_stream(2, wait_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one, pass2); pm.add_instruction_stream(2, record_event{2}); auto pass4 = pm.add_instruction_stream(3, pass_op{}, one, pass1); pm.add_instruction_stream(3, record_event{3}); pm.add_instruction_stream(0, wait_event{2}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass3, pass1); pm.add_instruction_stream(0, record_event{4}); pm.add_instruction_stream(1, wait_event{3}); pm.add_instruction_stream(1, wait_event{4}); pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.size() == 1); EXPECT(races.front().ins == pass4); EXPECT(races.front().before == pass1); } TEST_CASE(race_multi_wait_sync) { program_model pm; auto one = pm.add_literal(1); auto pass1 = pm.add_instruction_stream(0, pass_op{}, one); pm.add_instruction_stream(0, record_event{5}); auto pass2 = pm.add_instruction_stream(1, pass_op{}, one); pm.add_instruction_stream(1, record_event{1}); pm.add_instruction_stream(2, wait_event{1}); auto pass3 = pm.add_instruction_stream(2, pass_op{}, one, pass2); pm.add_instruction_stream(2, record_event{2}); pm.add_instruction_stream(3, wait_event{5}); auto pass4 = pm.add_instruction_stream(3, pass_op{}, one, pass1); pm.add_instruction_stream(3, record_event{3}); pm.add_instruction_stream(0, wait_event{2}); auto pass5 = pm.add_instruction_stream(0, pass_op{}, pass3, pass1); pm.add_instruction_stream(0, record_event{4}); pm.add_instruction_stream(1, wait_event{3}); pm.add_instruction_stream(1, wait_event{4}); pm.add_instruction_stream(1, pass_op{}, pass4, pass5); auto races = pm.analyze(); EXPECT(races.empty()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/any_ptr.cpp000066400000000000000000000040401510465702400176750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(test_int_id) { int i = 1; migraphx::any_ptr p = &i; EXPECT(p.get() == &i); EXPECT(p.get(migraphx::get_type_name(i)) == &i); EXPECT(p.unsafe_get() == &i); EXPECT(test::throws([&] { p.get(); })); EXPECT(test::throws([&] { p.get(migraphx::get_type_name(&i)); })); } TEST_CASE(test_int_name) { int i = 1; void* vp = &i; migraphx::any_ptr p{vp, migraphx::get_type_name(i)}; EXPECT(p.get() == &i); EXPECT(p.get(migraphx::get_type_name(i)) == &i); EXPECT(p.unsafe_get() == &i); EXPECT(test::throws([&] { p.get(); })); EXPECT(test::throws([&] { p.get(migraphx::get_type_name(&i)); })); EXPECT(test::throws([&] { p.get(migraphx::get_type_name(float{})); })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/000077500000000000000000000000001510465702400162705ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/api/CMakeLists.txt000066400000000000000000000070071510465702400210340ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### function(add_api_test TEST_NAME TEST_SRC TEST_DIR) set(NAME test_api_${TEST_NAME}) add_executable(${NAME} EXCLUDE_FROM_ALL ${TEST_SRC}) rocm_clang_tidy_check(${NAME}) target_link_libraries(${NAME} migraphx_c migraphx migraphx_all_targets onnx_files pb_files) target_include_directories(${NAME} PUBLIC ../include include) add_test(NAME ${NAME} COMMAND $ WORKING_DIRECTORY ${TEST_DIR}) add_dependencies(tests ${NAME}) add_dependencies(check ${NAME}) if(WIN32) target_compile_definitions(${NAME} PRIVATE _CRT_SECURE_NO_WARNINGS) endif() endfunction() set(MIGRAPHX_ENABLE_C_API_TEST On CACHE BOOL "") # Workaround: C file dont work with clang-tidy right now, need a fix in rocm-cmake function(add_c_api_test TEST_NAME TEST_SRC TEST_DIR) if(MIGRAPHX_ENABLE_C_API_TEST) set(NAME test_api_${TEST_NAME}) add_executable(${NAME} EXCLUDE_FROM_ALL ${TEST_SRC}) target_link_libraries(${NAME} migraphx_c) target_include_directories(${NAME} PUBLIC ../include) add_test(NAME ${NAME} COMMAND $ WORKING_DIRECTORY ${TEST_DIR}) add_dependencies(tests ${NAME}) add_dependencies(check ${NAME}) if(WIN32) target_compile_definitions(${NAME} PRIVATE _CRT_SECURE_NO_WARNINGS) endif() endif() endfunction() add_api_test(array_base test_array_base.cpp ${TEST_ONNX_DIR}) add_api_test(assign test_assign.cpp ${TEST_ONNX_DIR}) add_api_test(compile_options test_compile_options.cpp ${TEST_ONNX_DIR}) add_api_test(lookup test_lookup.cpp ${TEST_ONNX_DIR}) add_api_test(module_construct test_module_construct.cpp ${TEST_ONNX_DIR}) add_api_test(dynamic_shape test_dynamic_shape.cpp ${TEST_ONNX_DIR}) add_api_test(ref test_cpu.cpp ${TEST_ONNX_DIR}) add_api_test(save_load test_save_load.cpp ${TEST_ONNX_DIR}) add_api_test(op test_op_construct.cpp ${TEST_ONNX_DIR}) add_c_api_test(c_op test_c_op_construct.c ${TEST_ONNX_DIR}) add_api_test(custom_op test_custom_op.cpp ${TEST_ONNX_DIR}) add_api_test(tf_parser test_tf_parser.cpp ${TEST_TF_DIR}) # GPU-based tests if(MIGRAPHX_ENABLE_GPU) add_api_test(gpu test_gpu.cpp ${TEST_ONNX_DIR}) add_api_test(custom_op_gpu test_custom_op_gpu.cpp ${TEST_ONNX_DIR}) endif() ROCm-AMDMIGraphX-46524e8/test/api/include/000077500000000000000000000000001510465702400177135ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/api/include/read_onnx.hpp000066400000000000000000000035731510465702400224110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_INCLUDE_READ_ONNX_HPP #define MIGRAPHX_GUARD_INCLUDE_READ_ONNX_HPP #include #include inline migraphx::program read_onnx(const std::string& name, const migraphx::onnx_options& options = migraphx::onnx_options{}) { static auto onnx_files{::onnx_files()}; if(onnx_files.find(name) == onnx_files.end()) { std::cerr << "Can not find onnx file by name: " << name << " , aborting the program\n" << std::endl; std::abort(); } auto prog = migraphx::parse_onnx_buffer(std::string{onnx_files.at(name)}, options); return prog; } #endif // MIGRAPHX_GUARD_INCLUDE_READ_ONNX_HPP ROCm-AMDMIGraphX-46524e8/test/api/include/read_tf.hpp000066400000000000000000000035111510465702400220300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_INCLUDE_READ_TF_HPP #define MIGRAPHX_GUARD_INCLUDE_READ_TF_HPP #include inline migraphx::program read_tf(const std::string& name, const migraphx::tf_options& options = migraphx::tf_options{}) { static auto pb_files{::pb_files()}; if(pb_files.find(name) == pb_files.end()) { std::cerr << "Can not find TensorFlow Protobuf file by name: " << name << " , aborting the program\n" << std::endl; std::abort(); } return migraphx::parse_tf_buffer(std::string{pb_files.at(name)}, options); } #endif // MIGRAPHX_GUARD_INCLUDE_READ_TF_HPP ROCm-AMDMIGraphX-46524e8/test/api/test_array_base.cpp000066400000000000000000000035011510465702400221420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "test.hpp" struct array2 : migraphx::array_base { std::vector v; array2() = default; array2(std::initializer_list x) : v(x) {} std::size_t size() const { return v.size(); } int operator[](std::size_t i) const { return v[i]; } }; TEST_CASE(iterators) { array2 a = {1, 2, 3}; EXPECT(std::equal(a.begin(), a.end(), a.v.begin())); } TEST_CASE(front_back) { array2 a = {1, 2, 3}; EXPECT(a.front() == 1); EXPECT(a.back() == 3); } TEST_CASE(empty) { array2 a = {1, 2, 3}; EXPECT(not a.empty()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_assign.cpp000066400000000000000000000037511510465702400213250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include "test.hpp" TEST_CASE(shape_assign) { auto s1_cpp = migraphx::shape{migraphx_shape_float_type, {1, 3}}; std::vector lens{2, 3}; // handle ptr is const, workaround to construct shape using C API migraphx_shape_t s2; migraphx_shape_create(&s2, migraphx_shape_float_type, lens.data(), lens.size()); auto s2_cpp = migraphx::shape(s2, migraphx::own{}); CHECK(s1_cpp != s2_cpp); // use C++ API for assignment s1_cpp.assign_to_handle(s2); CHECK(s1_cpp == s2_cpp); auto s3_cpp = migraphx::shape{migraphx_shape_float_type, lens}; // use C API for assignment migraphx_shape_assign_to(s2, s3_cpp.get_handle_ptr()); CHECK(s2_cpp == s3_cpp); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_c_op_construct.c000066400000000000000000000030331510465702400225160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include void expect_equal(const char* x, const char* y) { if(strcmp(x, y) != 0) abort(); } int main(void) { char name[1024]; migraphx_operation_t op; migraphx_operation_create(&op, "add", 0); migraphx_operation_name(name, 1024, op); migraphx_operation_destroy(op); expect_equal(name, "add"); } ROCm-AMDMIGraphX-46524e8/test/api/test_compile_options.cpp000066400000000000000000000033351510465702400232420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include "test.hpp" TEST_CASE(compile_options_api_test) { migraphx::api::compile_options options; options.set_offload_copy(false); options.set_fast_math(false); const auto* s_options = reinterpret_cast( options.get_handle_ptr()); CHECK(s_options->fast_math == false); CHECK(s_options->offload_copy == false); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_cpu.cpp000066400000000000000000000233351510465702400206300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" TEST_CASE(load_and_run) { auto p = read_onnx("conv_relu_maxpool_test.onnx"); auto shapes_before = p.get_output_shapes(); p.compile(migraphx::target("ref")); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 1); CHECK(shapes_before.size() == shapes_after.size()); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { pp.add(name, migraphx::argument::generate(param_shapes[name])); } auto outputs = p.eval(pp); CHECK(shapes_before.size() == outputs.size()); CHECK(shapes_before.front() == outputs.front().get_shape()); } TEST_CASE(load_and_run_init_list) { auto p = read_onnx("conv_relu_maxpool_test.onnx"); auto shapes_before = p.get_output_shapes(); p.compile(migraphx::target("ref")); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 1); CHECK(shapes_before.size() == shapes_after.size()); CHECK(shapes_before.front() == shapes_after.front()); auto param_shapes = p.get_parameter_shapes(); EXPECT(param_shapes.size() == 3); auto names = param_shapes.names(); auto outputs = p.eval({{names[0], migraphx::argument::generate(param_shapes[names[0]])}, {names[1], migraphx::argument::generate(param_shapes[names[1]])}, {names[2], migraphx::argument::generate(param_shapes[names[2]])}}); CHECK(shapes_before.size() == outputs.size()); CHECK(shapes_before.front() == outputs.front().get_shape()); } TEST_CASE(quantize_fp16) { auto p1 = read_onnx("gemm_test.onnx"); const auto& p2 = p1; const auto& p3 = p1; migraphx::quantize_fp16(p1); migraphx::quantize_op_names names; migraphx::quantize_fp16(p2, names); CHECK(p1 == p2); names.add("dot"); migraphx::quantize_fp16(p3, names); CHECK(p1 == p3); } TEST_CASE(quantize_int8) { auto p1 = read_onnx("gemm_test.onnx"); const auto& p2 = p1; auto t = migraphx::target("ref"); migraphx::quantize_int8_options options; migraphx::quantize_int8(p1, t, options); migraphx::program_parameters pp; auto param_shapes = p1.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { pp.add(name, migraphx::argument::generate(param_shapes[name])); } options.add_calibration_data(pp); options.add_op_name("dot"); migraphx::quantize_int8(p2, t, options); CHECK(p1 == p2); } TEST_CASE(quantize_fp8) { auto p1 = read_onnx("gemm_test.onnx"); const auto& p2 = p1; auto t = migraphx::target("ref"); migraphx::quantize_fp8_options options; migraphx::quantize_fp8(p1, t, options); migraphx::program_parameters pp; auto param_shapes = p1.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { pp.add(name, migraphx::argument::generate(param_shapes[name])); } options.add_calibration_data(pp); migraphx::quantize_fp8(p2, t, options); CHECK(p1 == p2); } TEST_CASE(load_and_run_user_input_shape) { migraphx::onnx_options options; options.set_input_parameter_shape("0", {2, 3, 64, 64}); auto p = read_onnx("conv_relu_maxpool_test.onnx", options); auto shapes_before = p.get_output_shapes(); p.compile(migraphx::target("ref")); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 1); CHECK(shapes_before.size() == shapes_after.size()); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { pp.add(name, migraphx::argument::generate(param_shapes[name])); } auto outputs = p.eval(pp); CHECK(shapes_before.size() == outputs.size()); CHECK(shapes_before.front() == outputs.front().get_shape()); } TEST_CASE(zero_parameter) { auto p = read_onnx("constant_fill_test.onnx"); auto shapes_before = p.get_output_shapes(); p.compile(migraphx::target("ref")); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 1); CHECK(shapes_before.size() == shapes_after.size()); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { pp.add(name, migraphx::argument::generate(param_shapes[name])); } auto outputs = p.eval(pp); CHECK(shapes_before.size() == outputs.size()); CHECK(shapes_before.front() == outputs.front().get_shape()); } TEST_CASE(set_scalar_parameter) { auto p1 = read_onnx("implicit_add_bcast_test.onnx"); migraphx::shape s1(migraphx_shape_float_type, {3, 4, 1}); auto param_shapes = p1.get_parameter_shapes(); auto s1_orig = param_shapes["1"]; CHECK(s1 == s1_orig); migraphx::onnx_options option; option.set_input_parameter_shape("1", {}); auto p2 = read_onnx("implicit_add_bcast_test.onnx", option); migraphx::shape s_scalar(migraphx_shape_float_type); auto param_shapes_1 = p2.get_parameter_shapes(); auto s_scalar_after = param_shapes_1["1"]; CHECK(s_scalar == s_scalar_after); } TEST_CASE(scalar_shape) { auto s = migraphx::shape(migraphx_shape_float_type); EXPECT(s.lengths().size() == 1); EXPECT(s.strides().size() == 1); EXPECT(s.lengths().front() == 1); EXPECT(s.strides().front() == 0); } TEST_CASE(strided_shape) { std::vector lens = {2, 2}; std::vector strides = {1, 2}; auto s = migraphx::shape(migraphx_shape_float_type, lens, strides); EXPECT(s.lengths() == lens); EXPECT(s.strides() == strides); } TEST_CASE(get_main_module) { auto p = read_onnx("constant_fill_test.onnx"); migraphx::module mm = p.get_main_module(); mm.print(); p.print(); } TEST_CASE(set_loop_default_iter_num) { migraphx::onnx_options option; option.set_default_loop_iterations(15); auto p = read_onnx("loop_default_test.onnx", option); auto out_shapes = p.get_output_shapes(); std::vector out_lens0 = {1}; EXPECT(out_shapes[0].lengths() == out_lens0); std::vector out_lens1 = {15, 1}; EXPECT(out_shapes[1].lengths() == out_lens1); } TEST_CASE(set_loop_limit_iterations) { migraphx::onnx_options option; option.set_default_loop_iterations(15); option.set_limit_loop_iterations(10); auto p = read_onnx("loop_default_test.onnx", option); auto out_shapes = p.get_output_shapes(); std::vector out_lens0 = {1}; EXPECT(out_shapes[0].lengths() == out_lens0); std::vector out_lens1 = {10, 1}; EXPECT(out_shapes[1].lengths() == out_lens1); } TEST_CASE(set_loop_limit_iterations2) { migraphx::onnx_options option; option.set_limit_loop_iterations(10); auto p = read_onnx("loop_test_implicit_tripcnt.onnx", option); auto out_shapes = p.get_output_shapes(); std::vector out_lens0 = {1}; EXPECT(out_shapes[0].lengths() == out_lens0); std::vector out_lens1 = {10, 1}; EXPECT(out_shapes[1].lengths() == out_lens1); } TEST_CASE(set_external_data_path) { migraphx::onnx_options options; std::string model_path = "ext_path/external_data_test.onnx"; auto onnx_buffer = migraphx::read_string(model_path); options.set_external_data_path(migraphx::fs::path(model_path).parent_path().string()); auto p = migraphx::parse_onnx_buffer(onnx_buffer, options); auto shapes_before = p.get_output_shapes(); p.compile(migraphx::target("ref")); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 1); CHECK(shapes_before.size() == shapes_after.size()); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { pp.add(name, migraphx::argument::generate(param_shapes[name])); } auto outputs = p.eval(pp); CHECK(shapes_before.size() == outputs.size()); CHECK(shapes_before.front() == outputs.front().get_shape()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_custom_op.cpp000066400000000000000000000137111510465702400220460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include "test.hpp" struct sigmoid_custom_op final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "sigmoid_custom_op"; } virtual migraphx::argument compute(migraphx::context, migraphx::shape, migraphx::arguments inputs) const override { auto* output_ptr = reinterpret_cast(inputs[1].data()); auto input_vec = inputs[0].as_vector(); std::transform(input_vec.begin(), input_vec.end(), output_ptr, [](auto x) { return 1.f / (1.f + std::exp(-x)); }); return inputs[1]; } virtual bool runs_on_offload_target() const override { return true; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(inputs.size() != 2) { throw std::runtime_error("op must have two inputs"); } if(inputs[0].lengths().size() != 1) { throw std::runtime_error("input arg must be a vector or scalar"); } if(inputs[0].type() != migraphx_shape_float_type) { throw std::runtime_error("input arg must be of type float"); } if(inputs[0] != inputs[1]) { throw std::runtime_error("input arg and buffer allocation must be of same shape"); } return inputs.back(); } }; TEST_CASE(register_custom_op) { sigmoid_custom_op sigmoid_op; migraphx::register_experimental_custom_op(sigmoid_op); auto op = migraphx::operation("sigmoid_custom_op"); EXPECT(op.name() == "sigmoid_custom_op"); } TEST_CASE(run_sigmoid_custom_op) { migraphx::program p; migraphx::shape s{migraphx_shape_float_type, {12}}; migraphx::module m = p.get_main_module(); auto x = m.add_parameter("x", s); auto alloc = m.add_allocation(s); auto custom_kernel = m.add_instruction(migraphx::operation("sigmoid_custom_op"), {x, alloc}); p.compile(migraphx::target("ref")); // run program migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); migraphx::argument input_arg = migraphx::argument::generate(param_shapes["x"]); pp.add("x", input_arg); auto results = p.eval(pp); auto result = results[0]; auto expected_result = input_arg.as_vector(); std::transform(expected_result.begin(), expected_result.end(), expected_result.begin(), [](auto y) { return 1.f / (1.f + std::exp(-y)); }); EXPECT(result == migraphx::argument(s, expected_result.data())); } extern "C" MIGRAPHX_C_EXPORT void migraphx_test_private_disable_exception_catch(bool); TEST_CASE(run_sigmoid_with_incorrect_shape) { migraphx::program p; migraphx::shape s{migraphx_shape_float_type, {12}}; migraphx::module m = p.get_main_module(); auto x = m.add_parameter("x", s); migraphx_test_private_disable_exception_catch(true); EXPECT(test::throws( [&] { m.add_instruction(migraphx::operation("sigmoid_custom_op"), {x}); }, "Error in compute_shape of: sigmoid_custom_op: op must have two inputs")); } struct identity_custom_op final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "identity_custom_op"; } virtual migraphx::argument compute(migraphx::context, migraphx::shape, migraphx::arguments inputs) const override { return inputs[0]; } virtual bool runs_on_offload_target() const override { return true; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(inputs.size() != 1) { throw std::runtime_error("Identity op must have only one input"); } return inputs.back(); } virtual std::vector output_alias(migraphx::shapes) const override { return {0, 1}; } }; TEST_CASE(run_custom_op_with_invalid_output_alias) { identity_custom_op i_op; migraphx::register_experimental_custom_op(i_op); auto op = migraphx::operation("identity_custom_op"); EXPECT(op.name() == "identity_custom_op"); migraphx::program p; migraphx::shape s{migraphx_shape_float_type, {12}}; migraphx::module m = p.get_main_module(); auto x = m.add_parameter("x", s); auto i_ins = m.add_instruction(migraphx::operation("identity_custom_op"), {x}); migraphx_test_private_disable_exception_catch(true); EXPECT(test::throws( [&] { p.compile(migraphx::target("ref")); }, "Currently, CustomOps in MIGraphX only supports one output_alias")); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_custom_op_gpu.cpp000066400000000000000000000323721510465702400227250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include "test.hpp" #define MIGRAPHX_HIP_ASSERT(x) (EXPECT(x == hipSuccess)) struct half_copy_host final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "half_copy_host"; } virtual bool runs_on_offload_target() const override { return false; } virtual migraphx::argument compute(migraphx::context ctx, migraphx::shape, migraphx::arguments inputs) const override { // This custom op simply sets first half size_bytes of the input to 0, and rest of the half // bytes are copied. for this custom_op, it does its computation on the host. Therefore, // `runs_on_offload_target()` is set to false. MIGraphX would inject necessary buffer copies // to and from GPU to Host based on `runs_on_offload_targe()` flag for input buffers as well // as the output buffers auto* input_buffer_ptr = inputs[0].data(); auto* output_buffer_ptr = inputs[1].data(); auto input_bytes = inputs[0].get_shape().bytes(); auto copy_bytes = input_bytes / 2; MIGRAPHX_HIP_ASSERT(hipSetDevice(0)); MIGRAPHX_HIP_ASSERT(hipMemcpyAsync(output_buffer_ptr, input_buffer_ptr, input_bytes, hipMemcpyHostToHost, ctx.get_queue())); MIGRAPHX_HIP_ASSERT(hipDeviceSynchronize()); MIGRAPHX_HIP_ASSERT( hipMemsetAsync(output_buffer_ptr, 0, copy_bytes, ctx.get_queue())); MIGRAPHX_HIP_ASSERT(hipDeviceSynchronize()); return inputs[1]; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(not inputs[0].standard() or not inputs[1].standard()) { throw std::runtime_error("Input args must be standard shaped"); } if(inputs.size() != 2) { throw std::runtime_error("number of inputs must be 2"); } return inputs.back(); } }; struct half_copy_device final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "half_copy_device"; } virtual bool runs_on_offload_target() const override { return true; } virtual migraphx::argument compute(migraphx::context ctx, migraphx::shape, migraphx::arguments inputs) const override { // This custom op simply sets first half size_bytes of the input to 0, and rest of the half // bytes are copied. for this custom_op, it does its computation on the "GPU". Therefore, // `runs_on_offload_target()` is set to "true". auto* input_buffer_ptr = inputs[0].data(); auto* output_buffer_ptr = inputs[1].data(); auto input_bytes = inputs[0].get_shape().bytes(); auto copy_bytes = input_bytes / 2; MIGRAPHX_HIP_ASSERT(hipSetDevice(0)); MIGRAPHX_HIP_ASSERT(hipMemcpyAsync(output_buffer_ptr, input_buffer_ptr, input_bytes, hipMemcpyDeviceToDevice, ctx.get_queue())); MIGRAPHX_HIP_ASSERT(hipDeviceSynchronize()); MIGRAPHX_HIP_ASSERT( hipMemsetAsync(output_buffer_ptr, 0, copy_bytes, ctx.get_queue())); MIGRAPHX_HIP_ASSERT(hipDeviceSynchronize()); return inputs[1]; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(not inputs[0].standard() or not inputs[1].standard()) { throw std::runtime_error("Input args must be standard shaped"); } if(inputs.size() != 2) { throw std::runtime_error("number of inputs must be 2"); } return inputs.back(); } }; // overwrites input buffer struct half_copy_device_same_buffer final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "half_copy_device_same_buffer"; } virtual bool runs_on_offload_target() const override { return true; } virtual migraphx::argument compute(migraphx::context ctx, migraphx::shape, migraphx::arguments inputs) const override { // This custom op simply sets first half size_bytes of the input 0, and rest of the half // bytes are copied. for this custom_op, it does its computation on the "device". Therefore, // `runs_on_offload_target()` is set to "true" auto* buffer_ptr = inputs[0].data(); auto input_bytes = inputs[0].get_shape().bytes(); auto copy_bytes = input_bytes / 2; MIGRAPHX_HIP_ASSERT(hipSetDevice(0)); MIGRAPHX_HIP_ASSERT( hipMemsetAsync(buffer_ptr, 0, copy_bytes, ctx.get_queue())); MIGRAPHX_HIP_ASSERT(hipDeviceSynchronize()); return inputs[0]; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(not inputs[0].standard()) { throw std::runtime_error("Input arg must be standard shaped"); } return inputs.front(); } }; TEST_CASE(register_half_copy_op) { half_copy_host hch; migraphx::register_experimental_custom_op(hch); auto op = migraphx::operation("half_copy_host"); EXPECT(op.name() == "half_copy_host"); half_copy_device hcd; migraphx::register_experimental_custom_op(hcd); op = migraphx::operation("half_copy_device"); EXPECT(op.name() == "half_copy_device"); half_copy_device_same_buffer hcdsb; migraphx::register_experimental_custom_op(hcdsb); op = migraphx::operation("half_copy_device_same_buffer"); EXPECT(op.name() == "half_copy_device_same_buffer"); } TEST_CASE(half_copy_custom_op_test) { auto run_test_prog = [](const std::string& op_name, bool buffer_alloc) { migraphx::program p; migraphx::module m = p.get_main_module(); migraphx::shape s{migraphx_shape_float_type, {4, 3}}; auto x = m.add_parameter("x", s); migraphx::instructions inputs = {x}; if(buffer_alloc) { auto alloc = m.add_allocation(s); inputs = {x, alloc}; } auto half_copy_ins = m.add_instruction(migraphx::operation(op_name.c_str()), inputs); m.add_return({half_copy_ins}); migraphx::compile_options options; options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); migraphx::program_parameters pp; std::vector x_data(12); std::iota(x_data.begin(), x_data.end(), 0); pp.add("x", migraphx::argument(s, x_data.data())); auto results = p.eval(pp); auto result = results[0]; auto result_vec = result.as_vector(); std::vector expected_result(12, 0); std::iota(expected_result.begin() + 6, expected_result.end(), 6); EXPECT(result == migraphx::argument(s, expected_result.data())); }; // register all the ops half_copy_host hch; migraphx::register_experimental_custom_op(hch); half_copy_device hcd; migraphx::register_experimental_custom_op(hcd); half_copy_device_same_buffer hcdsb; migraphx::register_experimental_custom_op(hcdsb); std::vector> tests_config = { {"half_copy_host", true}, {"half_copy_device", true}, {"half_copy_device_same_buffer", false}}; for(const auto& i : tests_config) { run_test_prog(i.first, i.second); } } struct stride_two final : migraphx::experimental_custom_op_base { virtual std::string name() const override { return "stride_two"; } virtual migraphx::argument compute(migraphx::context, migraphx::shape out_shape, migraphx::arguments inputs) const override { return {out_shape, inputs[0].data()}; } virtual migraphx::shape compute_shape(migraphx::shapes inputs) const override { if(inputs.size() != 1) { throw std::runtime_error("stride_two op must have only one input argument"); }; if(not inputs[0].standard()) { throw std::runtime_error("stride_two op only works on the standard input shapes"); } migraphx::shape input_s = inputs[0]; std::vector dims = input_s.lengths(); std::vector new_dims; std::vector strides = input_s.strides(); std::vector new_strides; std::for_each(dims.begin(), dims.end(), [&](auto i) { new_dims.push_back(i / 2); }); std::for_each( strides.begin(), strides.end(), [&](auto i) { new_strides.push_back(i * 2); }); migraphx::shape output_shape{input_s.type(), new_dims, new_strides}; return output_shape; } virtual bool runs_on_offload_target() const override { return true; } virtual std::vector output_alias(migraphx::shapes) const override { return {0}; }; }; TEST_CASE(stride_two_custom_op_test) { stride_two st; migraphx::register_experimental_custom_op(st); migraphx::program p; migraphx::module m = p.get_main_module(); migraphx::shape s{migraphx_shape_float_type, {4, 4, 4}}; auto x = m.add_parameter("x", s); auto stride_two_ins = m.add_instruction(migraphx::operation("stride_two"), {x}); m.add_return({stride_two_ins}); migraphx::compile_options options; options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); migraphx::program_parameters pp; std::vector x_data(64); std::iota(x_data.begin(), x_data.end(), 0); pp.add("x", migraphx::argument(s, x_data.data())); auto results = p.eval(pp); auto result = results[0]; auto result_vec = result.as_vector(); std::vector expected_result = {0, 2, 8, 10, 32, 34, 40, 42}; EXPECT(result_vec == expected_result); } TEST_CASE(custom_op_with_pre_and_post_subgraph_test) { half_copy_host hco; migraphx::register_experimental_custom_op(hco); stride_two st; migraphx::register_experimental_custom_op(st); migraphx::program p; migraphx::shape s{migraphx_shape_float_type, {4, 6}}; migraphx::module m = p.get_main_module(); auto x = m.add_parameter("x", s); // pre-subgraph auto neg_ins = m.add_instruction(migraphx::operation("neg"), x); auto trans_ins = m.add_instruction(migraphx::operation("transpose", "{permutation: [1, 0]}"), {neg_ins}); auto cont_ins = m.add_instruction(migraphx::operation("contiguous"), {trans_ins}); // custom_op migraphx::shape trans_shape{migraphx_shape_float_type, {6, 4}}; auto alloc = m.add_allocation(trans_shape); auto half_copy_ins = m.add_instruction(migraphx::operation("half_copy_host"), {cont_ins, alloc}); // post-subgraph auto abs_ins = m.add_instruction(migraphx::operation("abs"), {half_copy_ins}); // another custom_op auto stride_two_ins = m.add_instruction(migraphx::operation("stride_two"), {abs_ins}); // post-subgraph auto relu_ins = m.add_instruction(migraphx::operation("relu"), {stride_two_ins}); m.add_return({relu_ins}); migraphx::compile_options options; options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); migraphx::program_parameters pp; std::vector x_data(s.elements()); std::iota(x_data.begin(), x_data.end(), 0); pp.add("x", migraphx::argument(s, x_data.data())); auto results = p.eval(pp); auto result = results[0]; auto result_vec = result.as_vector(); std::vector expected_result = {0, 0, 0, 0, 4, 16}; EXPECT(bool{result == migraphx::argument(migraphx::shape{migraphx_shape_float_type, {3, 2}}, expected_result.data())}); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_dynamic_shape.cpp000066400000000000000000000045361510465702400226470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include "test.hpp" TEST_CASE(create_dynamic_dimensions) { migraphx::dynamic_dimension dd0{1, 4}; EXPECT(not dd0.is_fixed()); migraphx::dynamic_dimension dd1{4, 4}; EXPECT(dd1.is_fixed()); migraphx::optimals opts{1, 2, 4}; migraphx::dynamic_dimension dd2{1, 4, opts}; migraphx::dynamic_dimensions dyn_dims0{dd0, dd1, dd2}; CHECK(dyn_dims0[0] == dd0); CHECK(dyn_dims0[1] == dd1); CHECK(dyn_dims0[2] == dd2); CHECK(dyn_dims0[2] != dd0); EXPECT(dyn_dims0.size() == 3); } TEST_CASE(create_dynamic_shape) { migraphx::dynamic_dimensions dyn_dims(migraphx::dynamic_dimension{1, 4}, migraphx::dynamic_dimension{78, 92}, migraphx::dynamic_dimension{1, 4, {1, 4}}); migraphx::shape dyn_shape{migraphx_shape_float_type, dyn_dims}; CHECK(dyn_shape.dynamic()); CHECK(dyn_shape.dyn_dims()[0] == migraphx::dynamic_dimension{1, 4}); migraphx::shape static_shape{migraphx_shape_float_type, {3, 8}}; EXPECT(not static_shape.dynamic()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_gpu.cpp000066400000000000000000000336241510465702400206360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include "test.hpp" TEST_CASE(load_and_run) { auto p = read_onnx("conv_relu_maxpool_test.onnx"); auto shapes_before = p.get_output_shapes(); migraphx::compile_options options; options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 1); CHECK(shapes_before.size() == shapes_after.size()); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { pp.add(name, migraphx::argument::generate(param_shapes[name])); } auto outputs = p.eval(pp); CHECK(shapes_before.size() == outputs.size()); CHECK(shapes_before.front() == outputs.front().get_shape()); } using hip_ptr = MIGRAPHX_MANAGE_PTR(void, hipFree); using stream_ptr = MIGRAPHX_MANAGE_PTR(hipStream_t, hipStreamDestroy); static stream_ptr get_stream() { hipStream_t stream; auto err = hipStreamCreateWithFlags(&stream, 0); EXPECT(err == hipSuccess); return stream_ptr{stream}; } static hip_ptr get_hip_buffer(size_t size) { void* ptr; auto err = hipMalloc(&ptr, size); EXPECT(err == hipSuccess); return hip_ptr{ptr}; } // TODO: placeholder until we have a way to copy tuple arguments to/from device through c++ api // TEST_CASE(dynamic_batch_load_and_run) //{ // migraphx::onnx_options o_options; // migraphx::dynamic_dimensions dyn_dims = {{1, 4, {2, 4}}, {3, 3}, {4, 4}, {4, 4}}; // o_options.set_dyn_input_parameter_shape("0", dyn_dims); // dyn_dims = {{2, 2}, {3, 3}, {3, 3}, {3, 3}}; // o_options.set_dyn_input_parameter_shape("1", dyn_dims); // auto p = read_onnx("conv_dynamic_batch_test.onnx", o_options); // migraphx::compile_options c_options; // c_options.set_split_single_dyn_dim(); // p.compile(migraphx::target("gpu"), c_options); // auto out_shapes = p.get_output_shapes(); // CHECK(out_shapes.size() == 1); // EXPECT(out_shapes[0].dynamic()); // // std::vector a(0.12, 2*3*4*4); // std::vector c(0.75, 2*3*3*3); // // auto param_shapes = p.get_parameter_shapes(); // int batch_size = 2; // std::unordered_map arg_map; // // arg_map["0"] = migraphx::argument(param_shapes["0"].to_static(batch_size), a.data()); // arg_map["1"] = migraphx::argument(param_shapes["1"].to_static(batch_size), c.data()); // // migraphx::program_parameters pp; // std::vector buffs; // std::vector args; // // // copy to GPU and create parameter map // for(auto&& name : param_shapes.names()) // { // if(arg_map.find(name) != arg_map.end()) // { // args.push_back(arg_map.at(name)); // } // else // { // migraphx::shape static_shape = param_shapes[name].to_static(batch_size); // auto output_arg = migraphx::argument(static_shape); // args.push_back(output_arg); // } // buffs.push_back(get_hip_buffer(args.rbegin()->get_shape().bytes())); // auto err = hipMemcpy(buffs.rbegin()->get(), // args.rbegin()->data(), // args.rbegin()->get_shape().bytes(), // hipMemcpyHostToDevice); // EXPECT(err == hipSuccess); // pp.add(name, migraphx::argument(args.rbegin()->get_shape(), buffs.rbegin()->get())); // } // // auto output = p.eval(pp)[0]; // // // copy output back to host // auto host_arg = migraphx::argument(output.get_shape()); // auto err = hipMemcpy( // host_arg.data(), output.data(), output.get_shape().bytes(), hipMemcpyDeviceToHost); // EXPECT(err == hipSuccess); //} TEST_CASE(dynamic_batch_load_and_run_offload) { migraphx::onnx_options o_options; migraphx::dynamic_dimensions dyn_dims = {migraphx::dynamic_dimension{1, 4, {2, 4}}, migraphx::dynamic_dimension{3, 3}, migraphx::dynamic_dimension{4, 4}, migraphx::dynamic_dimension{4, 4}}; o_options.set_dyn_input_parameter_shape("0", dyn_dims); dyn_dims = {migraphx::dynamic_dimension{2, 2}, migraphx::dynamic_dimension{3, 3}, migraphx::dynamic_dimension{3, 3}, migraphx::dynamic_dimension{3, 3}}; o_options.set_dyn_input_parameter_shape("1", dyn_dims); auto p = read_onnx("conv_dynamic_batch_test.onnx", o_options); auto shapes_before = p.get_output_shapes(); migraphx::compile_options c_options; c_options.set_offload_copy(); p.compile(migraphx::target("gpu"), c_options); auto out_shapes = p.get_output_shapes(); EXPECT(out_shapes.size() == 1); EXPECT(out_shapes[0].dynamic()); // batch size = 2 std::vector a(2 * 3 * 4 * 4, 0.12); std::vector c(2 * 3 * 3 * 3, 0.75); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); pp.add("0", migraphx::argument(migraphx::shape(migraphx_shape_float_type, {2, 3, 4, 4}), a.data())); pp.add("1", migraphx::argument(migraphx::shape(migraphx_shape_float_type, {2, 3, 3, 3}), c.data())); auto outputs = p.eval(pp); EXPECT(shapes_before.size() == outputs.size()); EXPECT(bool{outputs.front().get_shape() == migraphx::shape(migraphx_shape_float_type, {2, 2, 2, 2})}); } TEST_CASE(load_and_run_async) { auto p = read_onnx("conv_relu_maxpool_test.onnx"); auto shapes_before = p.get_output_shapes(); migraphx::compile_options options; options.set_offload_copy(false); p.compile(migraphx::target("gpu"), options); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 1); CHECK(shapes_before.size() == shapes_after.size()); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); stream_ptr stream = get_stream(); std::vector buffs; std::vector args; for(auto&& name : param_shapes.names()) { args.push_back(migraphx::argument::generate(param_shapes[name])); buffs.push_back(get_hip_buffer(args.rbegin()->get_shape().bytes())); auto err = hipMemcpy(buffs.rbegin()->get(), args.rbegin()->data(), args.rbegin()->get_shape().bytes(), hipMemcpyHostToDevice); EXPECT(err == hipSuccess); pp.add(name, migraphx::argument(args.rbegin()->get_shape(), buffs.rbegin()->get())); } auto outputs = p.run_async(pp, stream.get()); CHECK(shapes_before.size() == outputs.size()); CHECK(shapes_before.front() == outputs.front().get_shape()); } TEST_CASE(load_and_run_ctx) { auto p = read_onnx("conv_relu_maxpool_test.onnx"); migraphx::compile_options options; options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); for(auto&& name : param_shapes.names()) { pp.add(name, migraphx::argument::generate(param_shapes[name])); } auto ctx = p.experimental_get_context(); EXPECT(ctx.get_queue() != nullptr); p.eval(pp); ctx.finish(); } TEST_CASE(if_pl_test) { auto run_prog = [&](auto cond) { auto p = read_onnx("if_pl_test.onnx"); auto shapes_before = p.get_output_shapes(); migraphx::compile_options options; options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 1); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); auto xs = param_shapes["x"]; std::vector xd(xs.elements(), 1.0); pp.add("x", migraphx::argument(xs, xd.data())); auto ys = param_shapes["y"]; std::vector yd(ys.elements(), 2.0); pp.add("y", migraphx::argument(ys, yd.data())); char ccond = cond; pp.add("cond", migraphx::argument(param_shapes["cond"], &ccond)); auto outputs = p.eval(pp); auto output = outputs[0]; return output.as_vector(); }; // then branch { auto result_vector = run_prog(true); std::vector gold = {2, 3, 4, 5, 6, 7}; EXPECT(result_vector == gold); } // else branch { auto result_vector = run_prog(false); std::vector gold = {1, 2, 3, 4, 5, 6}; EXPECT(result_vector == gold); } } TEST_CASE(loop_test) { auto run_prog = [&](int64_t max_iter_num) { migraphx::onnx_options parse_options; parse_options.set_default_loop_iterations(max_iter_num); auto p = read_onnx("loop_default_test.onnx", parse_options); auto shapes_before = p.get_output_shapes(); migraphx::compile_options options; options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 2); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); auto aas = param_shapes["a"]; std::vector xd = {1.0f}; pp.add("a", migraphx::argument(aas, xd.data())); auto bbs = param_shapes["b"]; std::vector yd = {2.0}; pp.add("b", migraphx::argument(bbs, yd.data())); auto outputs = p.eval(pp); auto output = outputs[0]; std::vector> ret; ret.push_back(output.as_vector()); output = outputs[1]; ret.push_back(output.as_vector()); return ret; }; { auto result_vector = run_prog(10); std::vector gold0 = {2.0f}; EXPECT(result_vector.at(0) == gold0); std::vector gold1 = {-2, 4, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(result_vector.at(1) == gold1); } { auto result_vector = run_prog(15); std::vector gold0 = {2.0f}; EXPECT(result_vector.at(0) == gold0); std::vector gold1 = {-2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(result_vector.at(1) == gold1); } } TEST_CASE(loop_test_limit_max_iter) { auto run_prog = [&](int64_t limit_max_iterations) { migraphx::onnx_options parse_options; parse_options.set_limit_loop_iterations(limit_max_iterations); auto p = read_onnx("loop_test_implicit_tripcnt.onnx", parse_options); auto shapes_before = p.get_output_shapes(); migraphx::compile_options options; options.set_offload_copy(); p.compile(migraphx::target("gpu"), options); auto shapes_after = p.get_output_shapes(); CHECK(shapes_before.size() == 2); CHECK(shapes_before.front() == shapes_after.front()); migraphx::program_parameters pp; auto param_shapes = p.get_parameter_shapes(); auto aas = param_shapes["a"]; std::vector xd = {1.0f}; pp.add("a", migraphx::argument(aas, xd.data())); auto bbs = param_shapes["b"]; std::vector yd = {2.0}; pp.add("b", migraphx::argument(bbs, yd.data())); auto cs = param_shapes["keep_going_cond"]; bool cond = true; pp.add("keep_going_cond", migraphx::argument(cs, &cond)); auto outputs = p.eval(pp); auto output = outputs[0]; std::vector> ret; ret.push_back(output.as_vector()); output = outputs[1]; ret.push_back(output.as_vector()); return ret; }; { auto result_vector = run_prog(5); std::vector gold0 = {2.0f}; EXPECT(result_vector.at(0) == gold0); std::vector gold1 = {-2, 4, 0, 0, 0}; EXPECT(result_vector.at(1) == gold1); } { auto result_vector = run_prog(20); std::vector gold0 = {2.0f}; EXPECT(result_vector.at(0) == gold0); std::vector gold1 = {-2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(result_vector.at(1) == gold1); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_lookup.cpp000066400000000000000000000040741510465702400213510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "test.hpp" template static std::false_type has_handle(migraphx::rank<0>, T) { return {}; } template static auto has_handle(migraphx::rank<1>, T*) -> decltype(migraphx::as_handle{}, std::true_type{}) { return {}; } TEST_CASE(shape) { static_assert(std::is_same, migraphx::shape>{}, "Failed"); static_assert(std::is_same, migraphx::shape>{}, "Failed"); static_assert(std::is_same, migraphx::shape>{}, "Failed"); } TEST_CASE(non_handle) { int i = 0; EXPECT(has_handle(migraphx::rank<1>{}, migraphx_shape_t{})); EXPECT(not has_handle(migraphx::rank<1>{}, &i)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_module_construct.cpp000066400000000000000000000075041510465702400234320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include "test.hpp" TEST_CASE(add_literals) { migraphx::program p; migraphx::module m = p.get_main_module(); migraphx::shape param_shape{migraphx_shape_float_type, {3, 3}}; std::vector x_values(9, 1); auto x = m.add_literal(param_shape, x_values.data()); std::vector y_values(9, -1); auto y = m.add_literal(param_shape, y_values.data()); auto add_op = migraphx::operation("add"); auto r = m.add_instruction(add_op, {x, y}); m.add_return({r}); // run on ref target p.compile(migraphx::target("ref")); migraphx::program_parameters pp; auto outputs = p.eval(pp); auto output = outputs[0]; std::vector expected(9, 0); CHECK(output == migraphx::argument(param_shape, expected.data())); } TEST_CASE(if_then_else_op) { migraphx::shape param_shape{migraphx_shape_float_type, {3, 3}}; migraphx::shape cond_s{migraphx_shape_bool_type}; auto create_program = [&]() { migraphx::program p; auto mm = p.get_main_module(); auto cond = mm.add_parameter("cond", cond_s); auto x = mm.add_parameter("x", param_shape); auto y = mm.add_parameter("y", param_shape); auto then_mod = p.create_module("If_0_if"); auto x_identity = then_mod.add_instruction(migraphx::operation("identity"), {x}); then_mod.add_return({x_identity}); auto else_mod = p.create_module("If_0_else"); auto y_identity = else_mod.add_instruction(migraphx::operation("identity"), {y}); else_mod.add_return({y_identity}); auto if_ins = mm.add_instruction(migraphx::operation("if"), {cond}, {then_mod, else_mod}); auto get_tuple_op = migraphx::operation("get_tuple_elem", "{index: 0}"); auto ret = mm.add_instruction(get_tuple_op, {if_ins}); mm.add_return({ret}); return p; }; std::vector x_data(9, 1); std::vector y_data(9, -1); auto x_arg = migraphx::argument(param_shape, x_data.data()); auto y_arg = migraphx::argument(param_shape, y_data.data()); auto run_prog = [&](bool cond) { auto p = create_program(); p.compile(migraphx::target("ref")); auto outputs = p.eval({{"cond", migraphx::argument(cond_s, &cond)}, {"x", x_arg}, {"y", y_arg}}); return outputs[0]; }; // then branch auto then_res = run_prog(true); CHECK(then_res == x_arg); // else branch auto else_res = run_prog(false); CHECK(else_res == y_arg); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_op_construct.cpp000066400000000000000000000036001510465702400225540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include "test.hpp" TEST_CASE(add_op) { auto add_op = migraphx::operation("add"); EXPECT(add_op.name() == "add"); } TEST_CASE(reduce_mean_without_quotes) { auto rm = migraphx::operation("reduce_mean", "{axes : [1, 2, 3, 4]}"); EXPECT(rm.name() == "reduce_mean"); } TEST_CASE(reduce_mean) { auto rm = migraphx::operation("reduce_mean", "{\"axes\" : [1, 2, 3, 4]}"); EXPECT(rm.name() == "reduce_mean"); } TEST_CASE(reduce_mean_with_format) { auto rm = migraphx::operation("reduce_mean", "{axes : [%i, %i, %i, %i]}", 1, 2, 3, 4); EXPECT(rm.name() == "reduce_mean"); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_save_load.cpp000066400000000000000000000054041510465702400217730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include "test.hpp" TEST_CASE(load_save_default) { std::string filename = "migraphx_api_load_save.mxr"; auto p1 = migraphx::parse_onnx("conv_relu_maxpool_test.onnx"); auto s1 = p1.get_output_shapes(); migraphx::save(p1, filename.c_str()); auto p2 = migraphx::load(filename.c_str()); auto s2 = p2.get_output_shapes(); EXPECT(s1.size() == s2.size()); EXPECT(s1.front() == s2.front()); EXPECT(p1.sort() == p2.sort()); std::remove(filename.c_str()); } TEST_CASE(load_save_json) { std::string filename = "migraphx_api_load_save.json"; auto p1 = migraphx::parse_onnx("conv_relu_maxpool_test.onnx"); auto s1 = p1.get_output_shapes(); migraphx::file_options options; options.set_file_format("json"); migraphx::save(p1, filename.c_str(), options); auto p2 = migraphx::load(filename.c_str(), options); auto s2 = p2.get_output_shapes(); EXPECT(s1.size() == s2.size()); EXPECT(s1.front() == s2.front()); EXPECT(p1.sort() == p2.sort()); std::remove(filename.c_str()); } TEST_CASE(load_save_argument) { migraphx::shape s1{migraphx_shape_float_type, {2, 2}}; std::vector data{1, 2, 3, 4}; migraphx::argument a1{s1, data.data()}; migraphx::argument::save_argument(a1, "migraphx_api_load_save_argument.msgpack"); migraphx::argument a2 = migraphx::argument::load_argument("migraphx_api_load_save_argument.msgpack"); EXPECT(a1 == a2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/api/test_tf_parser.cpp000066400000000000000000000047311510465702400220250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include "test.hpp" TEST_CASE(load_tf) { auto p = read_tf("add_test.pb"); auto shapes = p.get_output_shapes(); CHECK(shapes.size() == 1); } TEST_CASE(load_tf_default_dim) { migraphx::tf_options tf_options; size_t batch = 2; tf_options.set_default_dim_value(batch); tf_options.set_nhwc(); auto p = read_tf("conv_batch_test.pb", tf_options); auto shapes = p.get_output_shapes(); CHECK(shapes.size() == 1); CHECK(shapes.front().lengths().front() == batch); } TEST_CASE(load_tf_param_shape) { migraphx::tf_options tf_options; std::vector new_shape{1, 3}; tf_options.set_input_parameter_shape("0", new_shape); tf_options.set_input_parameter_shape("1", new_shape); auto p = read_tf("add_test.pb", tf_options); auto shapes = p.get_output_shapes(); CHECK(shapes.size() == 1); CHECK(shapes.front().lengths() == new_shape); } TEST_CASE(load_tf_multi_outputs) { migraphx::tf_options tf_options; tf_options.set_output_names({"relu", "tanh"}); auto p = read_tf("multi_output_test.pb", tf_options); auto shapes = p.get_output_shapes(); CHECK(shapes.size() == 2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/argument_test.cpp000066400000000000000000000170531510465702400211120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include "test.hpp" static migraphx::argument as_argument(migraphx::argument a) { return a; } template static migraphx::argument as_argument(T x) { return migraphx::literal{x}.get_argument(); } template static migraphx::argument make_tuple(Ts... xs) { return migraphx::argument{{as_argument(std::move(xs))...}}; } TEST_CASE(copy_eq) { auto a1 = as_argument(3); auto a2 = as_argument(3); auto a3 = as_argument(1); auto a4 = a1; // NOLINT EXPECT(a1 == a2); EXPECT(a2 != a3); EXPECT(a1 == a4); EXPECT(a4 != a3); EXPECT(a1.get_sub_objects().empty()); EXPECT(a2.get_sub_objects().empty()); EXPECT(a3.get_sub_objects().empty()); EXPECT(a4.get_sub_objects().empty()); } TEST_CASE(default_construct) { migraphx::argument a1{}; migraphx::argument a2{}; EXPECT(a1.empty()); EXPECT(a2.empty()); EXPECT(a1 == a2); EXPECT(a1.to_string().empty()); EXPECT(a2.to_string().empty()); EXPECT(a1.get_sub_objects().empty()); EXPECT(a2.get_sub_objects().empty()); } TEST_CASE(fallback_visit_computable) { migraphx::shape s{migraphx::shape::float_type, {3, 4}}; migraphx::argument a1(s); a1.fallback_visit([&](auto&& view) { EXPECT(view.get_shape().type() == migraphx::shape::float_type); EXPECT(view.get_shape().lens().size() == 2); EXPECT(view.get_shape().lens() == s.lens()); }); } TEST_CASE(fallback_visit_non_computable) { migraphx::shape s{migraphx::shape::fp4x2_type, {3, 4}}; migraphx::argument a1(s); a1.fallback_visit([&](auto&& view) { EXPECT(view.get_shape().type() == migraphx::shape::uint8_type); EXPECT(view.get_shape().lens().size() == 1); EXPECT(view.get_shape().lens().at(0) == 12); }); } TEST_CASE(string_elems) { migraphx::shape s{migraphx::shape::int64_type, {3}}; migraphx::literal l{s, {1, 2, 3}}; auto a = l.get_argument(); EXPECT(a.to_string() == "1, 2, 3"); } TEST_CASE(tuple) { auto a1 = make_tuple(3, 3.0); EXPECT(a1.get_shape().type() == migraphx::shape::tuple_type); EXPECT(a1.get_sub_objects().size() == 2); EXPECT(a1.get_sub_objects()[0] == as_argument(3)); EXPECT(a1.get_sub_objects()[1] == as_argument(3.0)); auto a2 = make_tuple(3, 3.0); EXPECT(a1 == a2); EXPECT(a1.to_string() == a2.to_string()); auto a3 = make_tuple(3, 4.0); EXPECT(a1 != a3); EXPECT(a1.to_string() != a3.to_string()); } TEST_CASE(nested_tuple) { auto a1 = make_tuple(3, make_tuple(5, 4)); EXPECT(a1.get_shape().type() == migraphx::shape::tuple_type); EXPECT(a1.get_sub_objects().size() == 2); EXPECT(a1.get_sub_objects()[0] == as_argument(3)); EXPECT(a1.get_sub_objects()[1] == make_tuple(5, 4)); auto a2 = make_tuple(3, make_tuple(5, 4)); EXPECT(a1 == a2); EXPECT(a1.to_string() == a2.to_string()); auto a3 = make_tuple(3, make_tuple(5, 6)); EXPECT(a1 != a3); EXPECT(a1.to_string() != a3.to_string()); } TEST_CASE(tuple_construct) { migraphx::shape s{{migraphx::shape{migraphx::shape::float_type, {4}}, migraphx::shape{migraphx::shape::int8_type, {3}}}}; migraphx::argument a{s}; EXPECT(a.get_sub_objects().size() == 2); EXPECT(a.get_shape() == s); auto b = a; // NOLINT EXPECT(a.get_shape() == b.get_shape()); EXPECT(a.get_sub_objects().size() == 2); EXPECT(a.get_sub_objects()[0] == b.get_sub_objects()[0]); EXPECT(a.get_sub_objects()[1] == b.get_sub_objects()[1]); EXPECT(a == b); } TEST_CASE(tuple_visit) { auto a1 = make_tuple(3, 3.0); EXPECT(test::throws([&] { a1.visit([](auto&&) {}); })); EXPECT(test::throws([&] { a1.at(); })); bool reaches = false; a1.visit([&](auto&&) { EXPECT(false); }, [&](auto&& xs) { reaches = true; EXPECT(xs.size() == 2); EXPECT(xs[0] == as_argument(3)); EXPECT(xs[1] == as_argument(3.0)); }); EXPECT(reaches); } TEST_CASE(tuple_visit_all) { auto a1 = make_tuple(3, 3.0); auto a2 = make_tuple(1, 2, 3); EXPECT(test::throws([&] { visit_all(a1, a2)([](auto&&, auto&&) {}); })); bool reaches = false; visit_all(a1, a2)([&](auto&&, auto&&) { EXPECT(false); }, [&](auto&& xs, auto&& ys) { reaches = true; EXPECT(xs.size() == 2); EXPECT(xs[0] == as_argument(3)); EXPECT(xs[1] == as_argument(3.0)); EXPECT(ys.size() == 3); EXPECT(ys[0] == as_argument(1)); EXPECT(ys[1] == as_argument(2)); EXPECT(ys[2] == as_argument(3)); }); EXPECT(reaches); } TEST_CASE(value_argument) { migraphx::shape s{migraphx::shape::int64_type, {3}}; migraphx::literal l1{s, {1, 2, 3}}; auto a1 = l1.get_argument(); auto v1 = migraphx::to_value(a1); migraphx::literal l2{1}; auto a2 = l2.get_argument(); auto v2 = migraphx::to_value(a2); EXPECT(v1 != v2); auto a3 = migraphx::from_value(v1); EXPECT(a3 == a1); auto a4 = migraphx::from_value(v2); EXPECT(a4 == a2); } TEST_CASE(value_empty_argument) { migraphx::argument a5; EXPECT(a5.empty()); auto v3 = migraphx::to_value(a5); auto a6 = migraphx::from_value(v3); EXPECT(a6 == a5); } TEST_CASE(value_tuple) { auto a1 = make_tuple(3, 3.0, make_tuple(3, 4)); auto a2 = make_tuple(1, 2, 3); auto v1 = migraphx::to_value(a1); auto v2 = migraphx::to_value(a2); EXPECT(v1 != v2); auto a3 = migraphx::from_value(v1); EXPECT(a3 == a1); auto a4 = migraphx::from_value(v2); EXPECT(a4 == a2); } TEST_CASE(argument_share) { migraphx::shape s{migraphx::shape::int64_type, {3}}; std::vector buffer(s.bytes()); migraphx::argument a1(s, [=]() mutable { return buffer.data(); }); auto a2 = a1; // NOLINT EXPECT(a1.data() != a2.data()); auto a3 = a1.share(); EXPECT(a1.data() != a3.data()); auto a4 = a3; // NOLINT EXPECT(a4.data() == a3.data()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/auto_contiguous_test.cpp000066400000000000000000000247761510465702400225310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::auto_contiguous{}}); } // TODO: Add this test case [[maybe_unused]] static void literal_broadcast() { migraphx::module m; m.add_literal(get_2_broadcasted()); EXPECT(not m.get_output_shapes().back().standard()); EXPECT(m.get_output_shapes().back().broadcasted()); run_pass(m); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().broadcasted()); } TEST_CASE(literal_transpose) { migraphx::module m; m.add_literal(get_2x2_transposed()); EXPECT(not m.get_output_shapes().back().standard()); EXPECT(m.get_output_shapes().back().transposed()); run_pass(m); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().transposed()); } TEST_CASE(after_literal_transpose) { migraphx::module m; auto l = m.add_literal(get_2x2()); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().transposed()); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); m.add_instruction(pass_op{}, t); EXPECT(not m.get_output_shapes().back().standard()); EXPECT(m.get_output_shapes().back().transposed()); run_pass(m); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().transposed()); } TEST_CASE(after_literal_broadcast) { migraphx::module m; auto l1 = m.add_literal(get_2x2()); auto l2 = m.add_literal(get_2()); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().broadcasted()); auto b = m.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", l1->get_shape().lens()}}), l2); m.add_instruction(pass_op{}, b); EXPECT(not m.get_output_shapes().back().standard()); EXPECT(m.get_output_shapes().back().broadcasted()); run_pass(m); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().broadcasted()); } TEST_CASE(after_param_transpose) { migraphx::module m; auto l = m.add_parameter("2x2", {migraphx::shape::float_type, {2, 2}}); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().transposed()); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); m.add_instruction(pass_op{}, t); EXPECT(not m.get_output_shapes().back().standard()); EXPECT(m.get_output_shapes().back().transposed()); run_pass(m); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().transposed()); } TEST_CASE(after_param_broadcast) { migraphx::module m; auto l1 = m.add_parameter("2x2", {migraphx::shape::float_type, {2, 2}}); auto l2 = m.add_parameter("2", {migraphx::shape::float_type, {2}}); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().broadcasted()); auto b = m.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", l1->get_shape().lens()}}), l2); m.add_instruction(pass_op{}, b); EXPECT(not m.get_output_shapes().back().standard()); EXPECT(m.get_output_shapes().back().broadcasted()); run_pass(m); EXPECT(m.get_output_shapes().back().standard()); EXPECT(not m.get_output_shapes().back().broadcasted()); } TEST_CASE(two_transpose_gather) { migraphx::module m1; { auto data = m1.add_parameter("2x2", {migraphx::shape::float_type, {2, 3, 4, 5}}); auto ind = m1.add_parameter("ind", {migraphx::shape::float_type, {2, 3}}); auto td = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), data); auto sd = m1.add_instruction(migraphx::make_op("softmax", {{"axis", 2}}), td); auto bd = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), sd); auto r = m1.add_instruction(migraphx::make_op("gather", {{"axis", 2}}), bd, ind); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { auto data = m2.add_parameter("2x2", {migraphx::shape::float_type, {2, 3, 4, 5}}); auto ind = m2.add_parameter("ind", {migraphx::shape::float_type, {2, 3}}); auto td = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), data); auto ctd = m2.add_instruction(migraphx::make_op("contiguous"), td); auto sd = m2.add_instruction(migraphx::make_op("softmax", {{"axis", 2}}), ctd); auto bd = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), sd); auto cbd = m2.add_instruction(migraphx::make_op("contiguous"), bd); auto r = m2.add_instruction(migraphx::make_op("gather", {{"axis", 2}}), cbd, ind); m2.add_return({r}); } EXPECT(m1 == m2); } TEST_CASE(two_transpose_gather2) { // A shape may be standard but have mixed strides if they're on axes with size 1. A // contiguous instruction should still be added in this case. migraphx::module m1; { auto data = m1.add_parameter("2x2", {migraphx::shape::float_type, {11, 8, 1, 1}, {8, 1, 1, 1}}); auto ind = m1.add_parameter("ind", {migraphx::shape::float_type, {2, 3}}); auto td = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), data); auto sd = m1.add_instruction(migraphx::make_op("softmax", {{"axis", 2}}), td); auto bd = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), sd); auto r = m1.add_instruction(migraphx::make_op("gather", {{"axis", 2}}), bd, ind); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { auto data = m2.add_parameter("2x2", {migraphx::shape::float_type, {11, 8, 1, 1}, {8, 1, 1, 1}}); auto ind = m2.add_parameter("ind", {migraphx::shape::float_type, {2, 3}}); auto td = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), data); auto ctd = m2.add_instruction(migraphx::make_op("contiguous"), td); auto sd = m2.add_instruction(migraphx::make_op("softmax", {{"axis", 2}}), ctd); auto bd = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), sd); auto cbd = m2.add_instruction(migraphx::make_op("contiguous"), bd); auto r = m2.add_instruction(migraphx::make_op("gather", {{"axis", 2}}), cbd, ind); m2.add_return({r}); } EXPECT(m1 == m2); } TEST_CASE(standard_reshape_lazy) { migraphx::module m1; { auto data = m1.add_parameter("2x2", {migraphx::shape::float_type, {2, 3, 4, 5}}); auto add = m1.add_instruction(migraphx::make_op("add"), data, data); auto r = m1.add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {2, 1, 12, 5}}}), add); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { auto data = m2.add_parameter("2x2", {migraphx::shape::float_type, {2, 3, 4, 5}}); auto add = m2.add_instruction(migraphx::make_op("add"), data, data); auto ca = m2.add_instruction(migraphx::make_op("contiguous"), add); auto r = m2.add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {2, 1, 12, 5}}}), ca); m2.add_return({r}); } EXPECT(m1 == m2); } TEST_CASE(standard_reshape) { migraphx::module m1; { auto data = m1.add_parameter("2x2", {migraphx::shape::float_type, {2, 3, 4, 5}}); auto add = m1.add_instruction(migraphx::make_op("add"), data, data); auto r = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 1, 12, 5}}}), add); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { auto data = m2.add_parameter("2x2", {migraphx::shape::float_type, {2, 3, 4, 5}}); auto add = m2.add_instruction(migraphx::make_op("add"), data, data); auto r = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 1, 12, 5}}}), add); m2.add_return({r}); } EXPECT(m1 == m2); } TEST_CASE(dead_instruction) { migraphx::module m1; { auto data = m1.add_parameter("2x2", {migraphx::shape::float_type, {2, 3, 4, 5}}); m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 0, 1, 3}}}), data); auto r = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 0, 1, 3}}}), data); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { auto data = m2.add_parameter("2x2", {migraphx::shape::float_type, {2, 3, 4, 5}}); m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 0, 1, 3}}}), data); auto r = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 0, 1, 3}}}), data); auto cr = m2.add_instruction(migraphx::make_op("contiguous"), r); m2.add_return({cr}); } EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/autocast_fp8.cpp000066400000000000000000000200321510465702400206200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::autocast_fp8_pass{}, migraphx::eliminate_identity{}}); } // with return template static void autocast_fp8_1() { migraphx::module m1; { auto x = m1.add_parameter("x", {DType, {1}}); auto y = m1.add_parameter("y", {DType, {1}}); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); m1.add_return({sum}); } run_pass(m1); migraphx::module m2; { auto y_fp32 = m2.add_parameter("y", {migraphx::shape::float_type, {1}}); auto x_fp32 = m2.add_parameter("x", {migraphx::shape::float_type, {1}}); auto y_fp8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), y_fp32); auto x_fp8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), x_fp32); auto sum_fp8 = m2.add_instruction(migraphx::make_op("add"), x_fp8, y_fp8); auto sum_fp32 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), sum_fp8); m2.add_return({sum_fp32}); } EXPECT(m1 == m2); } TEST_CASE_REGISTER(autocast_fp8_1); TEST_CASE_REGISTER(autocast_fp8_1); TEST_CASE_REGISTER(autocast_fp8_1); TEST_CASE_REGISTER(autocast_fp8_1); // without return template static void autocast_fp8_2() { migraphx::module m1; { auto x = m1.add_parameter("x", {DType, {1}}); auto y = m1.add_parameter("y", {DType, {1}}); m1.add_instruction(migraphx::make_op("sub"), x, y); } run_pass(m1); migraphx::module m2; { auto y_fp32 = m2.add_parameter("y", {migraphx::shape::float_type, {1}}); auto x_fp32 = m2.add_parameter("x", {migraphx::shape::float_type, {1}}); auto y_fp8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), y_fp32); auto x_fp8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), x_fp32); m2.add_instruction(migraphx::make_op("sub"), x_fp8, y_fp8); } EXPECT(m1 == m2); } TEST_CASE_REGISTER(autocast_fp8_2); TEST_CASE_REGISTER(autocast_fp8_2); TEST_CASE_REGISTER(autocast_fp8_2); TEST_CASE_REGISTER(autocast_fp8_2); // multiple inputs (of same type) to return template static void autocast_fp8_3() { migraphx::module m1; { auto x = m1.add_parameter("x", {DType, {1}}); auto y = m1.add_parameter("y", {DType, {1}}); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); auto diff = m1.add_instruction(migraphx::make_op("sub"), x, y); m1.add_return({sum, diff}); } run_pass(m1); migraphx::module m2; { auto y_fp32 = m2.add_parameter("y", {migraphx::shape::float_type, {1}}); auto x_fp32 = m2.add_parameter("x", {migraphx::shape::float_type, {1}}); auto y_fp8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), y_fp32); auto x_fp8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), x_fp32); auto sum_fp8 = m2.add_instruction(migraphx::make_op("add"), x_fp8, y_fp8); auto diff_fp8 = m2.add_instruction(migraphx::make_op("sub"), x_fp8, y_fp8); auto sum_fp32 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), sum_fp8); auto diff_fp32 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), diff_fp8); m2.add_return({sum_fp32, diff_fp32}); } EXPECT(m1 == m2); } TEST_CASE_REGISTER(autocast_fp8_3); TEST_CASE_REGISTER(autocast_fp8_3); TEST_CASE_REGISTER(autocast_fp8_3); TEST_CASE_REGISTER(autocast_fp8_3); // multiple inputs (of different types) to return template static void autocast_fp8_4() { migraphx::module m1; { auto x1 = m1.add_parameter("x1", {DType, {3, 4}, {1, 3}}); auto y1 = m1.add_parameter("y1", {DType, {3, 4}, {1, 3}}); auto x2 = m1.add_parameter("x2", {migraphx::shape::float_type, {3, 4}, {1, 3}}); auto y2 = m1.add_parameter("y2", {migraphx::shape::float_type, {3, 4}, {1, 3}}); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x1, y1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), x2, y2); m1.add_return({sum1, sum2}); } run_pass(m1); migraphx::module m2; { auto x2 = m2.add_parameter("x2", {migraphx::shape::float_type, {3, 4}, {1, 3}}); auto y2 = m2.add_parameter("y2", {migraphx::shape::float_type, {3, 4}, {1, 3}}); auto y1 = m2.add_parameter("y1", {migraphx::shape::float_type, {3, 4}, {1, 3}}); auto x1 = m2.add_parameter("x1", {migraphx::shape::float_type, {3, 4}, {1, 3}}); auto y1_fp8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), y1); auto x1_fp8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), x1); auto sum1 = m2.add_instruction(migraphx::make_op("add"), x1_fp8, y1_fp8); auto sum2 = m2.add_instruction(migraphx::make_op("add"), x2, y2); auto result_sum1 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), sum1); m2.add_return({result_sum1, sum2}); } EXPECT(m1 == m2); } TEST_CASE_REGISTER(autocast_fp8_4); TEST_CASE_REGISTER(autocast_fp8_4); TEST_CASE_REGISTER(autocast_fp8_4); TEST_CASE_REGISTER(autocast_fp8_4); // autocast pass does not do any changes TEST_CASE(autocast_fp8_5) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::float_type, {1}}); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); m1.add_return({sum}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/base64_test.cpp000066400000000000000000000122731510465702400203530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "test.hpp" TEST_CASE(base64_encoding) { EXPECT(migraphx::base64_encode("abc") == "YWJj"); EXPECT(migraphx::base64_encode("abcd") == "YWJjZA=="); EXPECT(migraphx::base64_encode("convolution") == "Y29udm9sdXRpb24="); EXPECT(migraphx::base64_encode("https://www.amd.com/en/products/software/rocm.html") == "aHR0cHM6Ly93d3cuYW1kLmNvbS9lbi9wcm9kdWN0cy9zb2Z0d2FyZS9yb2NtLmh0bWw="); EXPECT(migraphx::base64_encode("{1, 3, 7, 9}") == "ezEsIDMsIDcsIDl9"); } TEST_CASE(base64_rfc_test_vectors) { EXPECT(migraphx::base64_encode("") == ""); EXPECT(migraphx::base64_encode("f") == "Zg=="); EXPECT(migraphx::base64_encode("fo") == "Zm8="); EXPECT(migraphx::base64_encode("foo") == "Zm9v"); EXPECT(migraphx::base64_encode("foob") == "Zm9vYg=="); EXPECT(migraphx::base64_encode("fooba") == "Zm9vYmE="); EXPECT(migraphx::base64_encode("foobar") == "Zm9vYmFy"); } // Following tests altered from // https://github.com/tobiaslocker/base64/blob/master/test/base64_tests.cpp TEST_CASE(base64_encodes_three_bytes_zeros) { std::array const input{0x00, 0x00, 0x00}; std::string expected{"AAAA"}; std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } TEST_CASE(base64_encodes_three_bytes_random) { std::array const input{0xFE, 0xE9, 0x72}; std::string const expected{"/uly"}; std::string const actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } TEST_CASE(base64_encodes_two_bytes) { std::array const input{0x00, 0x00}; std::string expected{"AAA="}; std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } TEST_CASE(base64_encodes_one_byte) { std::array const input{0x00}; std::string expected{"AA=="}; std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } TEST_CASE(base64_encodes_four_bytes) { std::array const input{0x74, 0x68, 0x65, 0x20}; std::string expected{"dGhlIA=="}; std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } TEST_CASE(base64_encodes_five_bytes) { std::array const input{0x20, 0x62, 0x72, 0x6f, 0x77}; std::string expected{"IGJyb3c="}; std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } TEST_CASE(base64_encodes_six_bytes) { std::array const input{0x20, 0x6a, 0x75, 0x6d, 0x70, 0x73}; std::string expected{"IGp1bXBz"}; std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } TEST_CASE(base64_encodes_brown_fox) { std::array const input{ 0x74, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x20, 0x62, 0x72, 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x20, 0x6a, 0x75, 0x6d, 0x70, 0x73, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x67}; std::string expected{"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="}; std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } TEST_CASE(base64_encodes_encodes_brown_fast_fox_null_in_middle) { std::array const input{ 0x74, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x21, 0x20, 0x62, 0x72, 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x20, 0x6a, 0x75, 0x6d, 0x70, 0x73, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x00, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x67}; std::string expected{"dGhlIHF1aWNrISBicm93biBmb3gganVtcHMgb3ZlciB0aGUAIGxhenkgZG9n"}; std::string actual{migraphx::base64_encode({input.begin(), input.end()})}; EXPECT(expected == actual); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/bf16.cpp000066400000000000000000001711611510465702400167700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" #include #include #include #include #include #include template static bool bit_equal(const T& x, const U& y) { static_assert(sizeof(T) == sizeof(U)); using type = std::array; return migraphx::bit_cast(x) == migraphx::bit_cast(y); } TEST_CASE(check_numeric_limits) { CHECK(bit_equal(std::numeric_limits::min(), uint16_t{0x0080})); CHECK(bit_equal(std::numeric_limits::lowest(), uint16_t{0xff7f})); CHECK(bit_equal(std::numeric_limits::max(), uint16_t{0x7f7f})); CHECK(bit_equal(std::numeric_limits::epsilon(), uint16_t{0x3c00})); CHECK(bit_equal(std::numeric_limits::denorm_min(), uint16_t{0x0001})); CHECK(bit_equal(std::numeric_limits::infinity(), uint16_t{0x7f80})); CHECK(bit_equal(std::numeric_limits::quiet_NaN(), uint16_t{0x7fc0})); CHECK(bit_equal(std::numeric_limits::signaling_NaN(), uint16_t{0x7fa0})); } const static std::map& bf16_lut() // NOLINT(readability-function-size) { static const std::map result = { {0x0000, 0.0}, {0x002d, 0.00000000000000000000000000000000000000413259732711}, {0x004e, 0.00000000000000000000000000000000000000716316870032}, {0x005b, 0.00000000000000000000000000000000000000835703015038}, {0x00cd, 0.00000000000000000000000000000000000001882627671239}, {0x00ce, 0.00000000000000000000000000000000000001891811220855}, {0x0170, 0.00000000000000000000000000000000000004408103815584}, {0x01be, 0.00000000000000000000000000000000000006979497708007}, {0x01fe, 0.00000000000000000000000000000000000009330486409652}, {0x0211, 0.00000000000000000000000000000000000010652917554327}, {0x028f, 0.00000000000000000000000000000000000021011961520948}, {0x02bc, 0.00000000000000000000000000000000000027624117244324}, {0x02e8, 0.00000000000000000000000000000000000034089336173846}, {0x039c, 0.00000000000000000000000000000000000091688559364138}, {0x03a9, 0.00000000000000000000000000000000000099329272644483}, {0x03cf, 0.00000000000000000000000000000000000121663665310107}, {0x03da, 0.00000000000000000000000000000000000128128884239629}, {0x03f3, 0.00000000000000000000000000000000000142822563624908}, {0x0427, 0.00000000000000000000000000000000000196307556587322}, {0x044c, 0.00000000000000000000000000000000000239800847567747}, {0x0485, 0.00000000000000000000000000000000000312681497318728}, {0x0498, 0.00000000000000000000000000000000000357350282649975}, {0x04da, 0.00000000000000000000000000000000000512515536958517}, {0x051b, 0.00000000000000000000000000000000000728806497509818}, {0x0533, 0.00000000000000000000000000000000000841653955188758}, {0x0536, 0.00000000000000000000000000000000000855759887398625}, {0x0577, 0.00000000000000000000000000000000001161388418612420}, {0x0587, 0.00000000000000000000000000000000001269533898888071}, {0x065a, 0.00000000000000000000000000000000004100124295668139}, {0x0735, 0.00000000000000000000000000000000013616926559925378}, {0x075e, 0.00000000000000000000000000000000016701423736483061}, {0x0765, 0.00000000000000000000000000000000017228045205651446}, {0x076b, 0.00000000000000000000000000000000017679435036367204}, {0x07ba, 0.00000000000000000000000000000000027986169504377021}, {0x07d1, 0.00000000000000000000000000000000031446824873197835}, {0x07db, 0.00000000000000000000000000000000032951457642250363}, {0x07eb, 0.00000000000000000000000000000000035358870072734408}, {0x080d, 0.00000000000000000000000000000000042430644087281290}, {0x0841, 0.00000000000000000000000000000000058078824885427581}, {0x08ff, 0.00000000000000000000000000000000153472542443357857}, {0x09e1, 0.00000000000000000000000000000000541667796858910084}, {0x0a0b, 0.00000000000000000000000000000000669260655674564459}, {0x0a0f, 0.00000000000000000000000000000000688519955118436817}, {0x0a12, 0.00000000000000000000000000000000702964429701341086}, {0x0a1d, 0.00000000000000000000000000000000755927503171990072}, {0x0aa2, 0.00000000000000000000000000000001560003254953661041}, {0x0aaf, 0.00000000000000000000000000000001685188701338831371}, {0x0ab4, 0.00000000000000000000000000000001733336949948512268}, {0x0ade, 0.00000000000000000000000000000002137782238269831797}, {0x0b6e, 0.00000000000000000000000000000004583713267641621330}, {0x0bb6, 0.00000000000000000000000000000007010384997569538505}, {0x0c8c, 0.00000000000000000000000000000021570415377137041554}, {0x0cf7, 0.00000000000000000000000000000038056375701091780456}, {0x0d06, 0.00000000000000000000000000000041291938007662336690}, {0x0d20, 0.00000000000000000000000000000049303806576313237838}, {0x0d5e, 0.00000000000000000000000000000068409031624634617501}, {0x0d60, 0.00000000000000000000000000000069025329206838532974}, {0x0d6a, 0.00000000000000000000000000000072106817117858110338}, {0x0d76, 0.00000000000000000000000000000075804602611081603176}, {0x0d7e, 0.00000000000000000000000000000078269792939897265068}, {0x0dcc, 0.00000000000000000000000000000125724706769598756487}, {0x0dfc, 0.00000000000000000000000000000155306990715386699190}, {0x0e09, 0.00000000000000000000000000000168865537523872839596}, {0x0e3c, 0.00000000000000000000000000000231727890908672217840}, {0x0e69, 0.00000000000000000000000000000287194673307024610408}, {0x0f42, 0.00000000000000000000000000000956493847580476814062}, {0x0f8f, 0.00000000000000000000000000001410088868082558602173}, {0x0fa2, 0.00000000000000000000000000001597443333072548905959}, {0x1007, 0.00000000000000000000000000002662405555120914843265}, {0x1015, 0.00000000000000000000000000002938506871948268975159}, {0x1030, 0.00000000000000000000000000003470987982972451943812}, {0x1042, 0.00000000000000000000000000003825975390321907256247}, {0x1056, 0.00000000000000000000000000004220405842932413158953}, {0x1166, 0.00000000000000000000000000018143800820083271524470}, {0x1182, 0.00000000000000000000000000020510383535746306940705}, {0x128a, 0.00000000000000000000000000087090243936399703317455}, {0x1295, 0.00000000000000000000000000094032219902344607205078}, {0x129e, 0.00000000000000000000000000099712018419935892204042}, {0x12b0, 0.00000000000000000000000000111071615455118462201971}, {0x12bf, 0.00000000000000000000000000120537946317770603866912}, {0x1315, 0.00000000000000000000000000188064439804689214410156}, {0x1343, 0.00000000000000000000000000246124602428955683288459}, {0x13e2, 0.00000000000000000000000000570504206655835737673762}, {0x13ee, 0.00000000000000000000000000600796465416322591001572}, {0x1445, 0.00000000000000000000000000994595829302651684263107}, {0x1469, 0.00000000000000000000000001176349381865572804229970}, {0x14bd, 0.00000000000000000000000001908412301910671759652054}, {0x1541, 0.00000000000000000000000003897603960515975128178268}, {0x1546, 0.00000000000000000000000003998578156384264639270970}, {0x1569, 0.00000000000000000000000004705397527462291216919879}, {0x16a1, 0.00000000000000000000000026010952855671378057479844}, {0x16b6, 0.00000000000000000000000029403685836845905630194606}, {0x16f4, 0.00000000000000000000000039420326066980225130590570}, {0x1703, 0.00000000000000000000000042328382907986963050060367}, {0x1720, 0.00000000000000000000000051698788284564229679463043}, {0x178e, 0.00000000000000000000000091765349205101507681046902}, {0x17f6, 0.00000000000000000000000158973773975035006264348858}, {0x18e8, 0.00000000000000000000000599705944100945064281771302}, {0x18f1, 0.00000000000000000000000622970398828998967637529671}, {0x1927, 0.00000000000000000000000863369764352222635647032822}, {0x1a39, 0.00000000000000000000003825710333057752996280265201}, {0x1a60, 0.00000000000000000000004632211430296954979279888676}, {0x1a69, 0.00000000000000000000004818327068121386206125955631}, {0x1b1f, 0.00000000000000000000013152171739593140030455398204}, {0x1b43, 0.00000000000000000000016130021944784039659992469495}, {0x1ba0, 0.00000000000000000000026469779601696885595885078146}, {0x1bc0, 0.00000000000000000000031763735522036262715062093775}, {0x1bea, 0.00000000000000000000038712052667481695183981926789}, {0x1c25, 0.00000000000000000000054593920428499826541512973677}, {0x1c32, 0.00000000000000000000058895259613775570450844298875}, {0x1cbe, 0.00000000000000000000125731453108060206580454121195}, {0x1ccd, 0.00000000000000000000135657620458696538678911025499}, {0x1cd6, 0.00000000000000000000141613320869078337937985168082}, {0x1d23, 0.00000000000000000000215728703753829617606463386892}, {0x1d38, 0.00000000000000000000243521972335611347482142718945}, {0x1dce, 0.00000000000000000000545277459794955843275232609813}, {0x1e7c, 0.00000000000000000001334076891925523034032607938570}, {0x1e9e, 0.00000000000000000001672890070827243169659936938842}, {0x1eb1, 0.00000000000000000001874060395800139500188663532754}, {0x1f33, 0.00000000000000000003790472438962994017330743190541}, {0x1f56, 0.00000000000000000004531626267810506814015525378636}, {0x1fa8, 0.00000000000000000007115076756936122848173909005709}, {0x1fed, 0.00000000000000000010037340424963459017959621633054}, {0x2001, 0.00000000000000000010926725019580474373981360258767}, {0x213f, 0.00000000000000000064713317170228545904819839051925}, {0x2154, 0.00000000000000000071828393927164668752993748057634}, {0x216b, 0.00000000000000000079621097041904231872422315063886}, {0x219f, 0.00000000000000000107742590890747003129490622086450}, {0x2225, 0.00000000000000000223616698075135289514037140179425}, {0x2229, 0.00000000000000000229037708937562811684074404183775}, {0x2274, 0.00000000000000000330681662608078852372273104265332}, {0x227b, 0.00000000000000000340168431617327016169838316272944}, {0x2294, 0.00000000000000000401154803819636640582757536321878}, {0x22b7, 0.00000000000000000496022493912118278558409656397998}, {0x2379, 0.00000000000000001349831704744453020339278737083077}, {0x2389, 0.00000000000000001485356976305141074590210337191820}, {0x2464, 0.00000000000000004943961906533900219073984771966934}, {0x2475, 0.00000000000000005312590645178971726636518724262714}, {0x2491, 0.00000000000000006288372600415925717243226245045662}, {0x24f6, 0.00000000000000010668549377257363630633335560560226}, {0x2567, 0.00000000000000020036056147532121940457727760076523}, {0x256a, 0.00000000000000020296264668928643004619516432285309}, {0x257f, 0.00000000000000022117724318704290453752037137746811}, {0x25f9, 0.00000000000000043194614551822496650856919586658478}, {0x263b, 0.00000000000000064878658001532585331005975604057312}, {0x270c, 0.00000000000000194289029309402394574135541915893555}, {0x272c, 0.00000000000000238697950294408656191080808639526367}, {0x2745, 0.00000000000000273392419813944798079319298267364502}, {0x2791, 0.00000000000000402455846426619245903566479682922363}, {0x2792, 0.00000000000000405231403988182137254625558853149414}, {0x27a0, 0.00000000000000444089209850062616169452667236328125}, {0x27a3, 0.00000000000000452415882534751290222629904747009277}, {0x27d4, 0.00000000000000588418203051332966424524784088134766}, {0x27dd, 0.00000000000000613398221105398988584056496620178223}, {0x2821, 0.00000000000000893729534823251015041023492813110352}, {0x28d9, 0.00000000000002409183963436589692719280719757080078}, {0x2981, 0.00000000000005728750807065807748585939407348632812}, {0x29ca, 0.00000000000008970602038971264846622943878173828125}, {0x2a5b, 0.00000000000019451107391432742588222026824951171875}, {0x2aa8, 0.00000000000029842794901924207806587219238281250}, {0x2ac4, 0.000000000000348165940522449091076850891113281250}, {0x2ae7, 0.00000000000041033842990145785734057426452636718750}, {0x2af1, 0.00000000000042810199829546036198735237121582031250}, {0x2afe, 0.0000000000004511946372076636180281639099121093750}, {0x2b24, 0.00000000000058264504332328215241432189941406250}, {0x2b4c, 0.00000000000072475359047530218958854675292968750}, {0x2b85, 0.000000000000945021838560933247208595275878906250}, {0x2bed, 0.000000000001683986283751437440514564514160156250}, {0x2c18, 0.00000000000216004991671070456504821777343750}, {0x2cdf, 0.0000000000063380412029800936579704284667968750}, {0x2d6b, 0.000000000013358203432289883494377136230468750}, {0x2d96, 0.0000000000170530256582424044609069824218750}, {0x2da2, 0.0000000000184172677109017968177795410156250}, {0x2db8, 0.00000000002091837814077734947204589843750}, {0x2de1, 0.00000000002557953848736360669136047363281250}, {0x2e90, 0.00000000006548361852765083312988281250}, {0x2ea3, 0.000000000074123818194493651390075683593750}, {0x2ef0, 0.00000000010913936421275138854980468750}, {0x2f09, 0.00000000012460077414289116859436035156250}, {0x2f6b, 0.00000000021373125491663813591003417968750}, {0x303b, 0.000000000680302036926150321960449218750}, {0x308f, 0.00000000104046193882822990417480468750}, {0x309c, 0.000000001135049387812614440917968750}, {0x30a9, 0.00000000122963683679699897766113281250}, {0x312a, 0.000000002473825588822364807128906250}, {0x313d, 0.0000000027503119781613349914550781250}, {0x3159, 0.0000000031577656045556068420410156250}, {0x31c6, 0.00000000576255843043327331542968750}, {0x3212, 0.0000000084983184933662414550781250}, {0x3245, 0.00000001146690919995307922363281250}, {0x329b, 0.0000000180443748831748962402343750}, {0x32ba, 0.000000021653249859809875488281250}, {0x32cc, 0.00000002374872565269470214843750}, {0x3332, 0.00000004144385457038879394531250}, {0x33c4, 0.000000091269612312316894531250}, {0x3424, 0.00000015273690223693847656250}, {0x3589, 0.0000010207295417785644531250}, {0x3594, 0.00000110268592834472656250}, {0x368b, 0.00000414252281188964843750}, {0x36a0, 0.000004768371582031250}, {0x36e9, 0.00000694394111633300781250}, {0x36ed, 0.00000706315040588378906250}, {0x3750, 0.000012397766113281250}, {0x375f, 0.0000132918357849121093750}, {0x37ce, 0.00002455711364746093750}, {0x37d2, 0.00002503395080566406250}, {0x37f0, 0.00002861022949218750}, {0x380e, 0.0000338554382324218750}, {0x3826, 0.0000395774841308593750}, {0x387f, 0.00006079673767089843750}, {0x38e1, 0.0001072883605957031250}, {0x38e9, 0.0001111030578613281250}, {0x3964, 0.0002174377441406250}, {0x3994, 0.000282287597656250}, {0x3a26, 0.000633239746093750}, {0x3a2c, 0.00065612792968750}, {0x3a6a, 0.000892639160156250}, {0x3a85, 0.001014709472656250}, {0x3ab9, 0.001411437988281250}, {0x3aba, 0.00141906738281250}, {0x3af7, 0.001884460449218750}, {0x3b03, 0.00199890136718750}, {0x3bb3, 0.0054626464843750}, {0x3bbf, 0.0058288574218750}, {0x3be8, 0.0070800781250}, {0x3c06, 0.00817871093750}, {0x3c29, 0.010314941406250}, {0x3c3f, 0.011657714843750}, {0x3c73, 0.014831542968750}, {0x3ce9, 0.02844238281250}, {0x3cfa, 0.0305175781250}, {0x3cfb, 0.03063964843750}, {0x3d0f, 0.0349121093750}, {0x3d2a, 0.041503906250}, {0x3d43, 0.0476074218750}, {0x3dd0, 0.10156250}, {0x3dd9, 0.105957031250}, {0x3de9, 0.113769531250}, {0x3df9, 0.121582031250}, {0x3e1e, 0.1542968750}, {0x3e77, 0.24121093750}, {0x3e95, 0.2910156250}, {0x3f38, 0.718750}, {0x3fb3, 1.39843750}, {0x3fc5, 1.53906250}, {0x3fd3, 1.64843750}, {0x3fd7, 1.67968750}, {0x400b, 2.1718750}, {0x40bf, 5.968750}, {0x40c7, 6.218750}, {0x4123, 10.18750}, {0x412b, 10.68750}, {0x41bf, 23.8750}, {0x41ca, 25.250}, {0x421b, 38.750}, {0x4226, 41.50}, {0x42a7, 83.50}, {0x42b7, 91.50}, {0x4311, 145.0}, {0x431f, 159.0}, {0x4334, 180.0}, {0x434f, 207.0}, {0x43b1, 354.0}, {0x43e5, 458.0}, {0x4476, 984.0}, {0x4496, 1200.0}, {0x44a4, 1312.0}, {0x458b, 4448.0}, {0x45a9, 5408.0}, {0x45df, 7136.0}, {0x45f6, 7872.0}, {0x45fa, 8000.0}, {0x4602, 8320.0}, {0x4640, 12288.0}, {0x4648, 12800.0}, {0x46a7, 21376.0}, {0x46b1, 22656.0}, {0x4742, 49664.0}, {0x4744, 50176.0}, {0x475e, 56832.0}, {0x477a, 64000.0}, {0x4837, 187392.0}, {0x488a, 282624.0}, {0x488f, 292864.0}, {0x48ea, 479232.0}, {0x495c, 901120.0}, {0x49aa, 1392640.0}, {0x49b9, 1515520.0}, {0x4a1e, 2588672.0}, {0x4a2b, 2801664.0}, {0x4a4c, 3342336.0}, {0x4ab6, 5963776.0}, {0x4b34, 11796480.0}, {0x4b73, 15925248.0}, {0x4b7b, 16449536.0}, {0x4bcd, 26869760.0}, {0x4bd0, 27262976.0}, {0x4c07, 35389440.0}, {0x4c17, 39583744.0}, {0x4c53, 55312384.0}, {0x4cad, 90701824.0}, {0x4d1c, 163577856.0}, {0x4dc0, 402653184.0}, {0x4dde, 465567744.0}, {0x4eef, 2004877312.0}, {0x4efc, 2113929216.0}, {0x4f12, 2449473536.0}, {0x4f2f, 2936012800.0}, {0x4f92, 4898947072.0}, {0x4fad, 5804916736.0}, {0x4fdc, 7381975040.0}, {0x4feb, 7885291520.0}, {0x5076, 16508780544.0}, {0x5083, 17582522368.0}, {0x5215, 159987531776.0}, {0x52a9, 362924736512.0}, {0x5394, 1271310319616.0}, {0x53a0, 1374389534720.0}, {0x53b7, 1571958030336.0}, {0x540e, 2439541424128.0}, {0x542f, 3006477107200.0}, {0x5465, 3934190043136.0}, {0x5529, 11613591568384.0}, {0x554c, 14018773254144.0}, {0x5596, 20615843020800.0}, {0x55ae, 23914377904128.0}, {0x55be, 26113401159680.0}, {0x55da, 29961691856896.0}, {0x568d, 77515569758208.0}, {0x5690, 79164837199872.0}, {0x56ad, 95107755802624.0}, {0x5718, 167125767421952.0}, {0x571c, 171523813933056.0}, {0x571d, 172623325560832.0}, {0x5826, 730075720843264.0}, {0x587c, 1108307720798208.0}, {0x5890, 1266637395197952.0}, {0x58a8, 1477743627730944.0}, {0x58f6, 2163838883463168.0}, {0x5966, 4046202790215680.0}, {0x5985, 4679521487814656.0}, {0x59ad, 6086896371367936.0}, {0x59b0, 6192449487634432.0}, {0x59bc, 6614661952700416.0}, {0x5a93, 20688410788233216.0}, {0x5ab0, 24769797950537728.0}, {0x5ab6, 25614222880669696.0}, {0x5ae3, 31947409856659456.0}, {0x5af5, 34480684647055360.0}, {0x5afd, 35606584553897984.0}, {0x5bb6, 102456891522678784.0}, {0x5c3d, 212795082393255936.0}, {0x5d57, 968273919884656640.0}, {0x5d76, 1107885508333142016.0}, {0x5d86, 1206964700135292928.0}, {0x5db2, 1603281467343896576.0}, {0x5dc1, 1738389456165011456.0}, {0x5dc5, 1774418253183975424.0}, {0x5e5d, 3981182070595518464.0}, {0x5fa4, 23634890844440363008.0}, {0x5fa8, 24211351596743786496.0}, {0x5ff8, 35740566642812256256.0}, {0x6006, 38622870404329373696.0}, {0x6051, 60240148615707754496.0}, {0x60ed, 136621198295911366656.0}, {0x610b, 160256089140351729664.0}, {0x6114, 170632382681813352448.0}, {0x613b, 215596321361480384512.0}, {0x6148, 230584300921369395200.0}, {0x61ad, 398910840593969053696.0}, {0x61fd, 583378281331064569856.0}, {0x629f, 1466516153859909353472.0}, {0x62a4, 1512633014044183232512.0}, {0x62fb, 2315066381250548727808.0}, {0x634b, 3744689046963038978048.0}, {0x635a, 4021390208068682252288.0}, {0x635f, 4113623928437230010368.0}, {0x637c, 4648579506574807007232.0}, {0x638d, 5201981828786093555712.0}, {0x63a9, 6234999496913828446208.0}, {0x6469, 17192365476697302106112.0}, {0x64c0, 28334198897217871282176.0}, {0x64d1, 30842956091242370301952.0}, {0x64dd, 32613843522318487257088.0}, {0x64ee, 35122600716342986276864.0}, {0x64ef, 35270174668932662689792.0}, {0x64fb, 37041062100008779644928.0}, {0x6510, 42501298345826806923264.0}, {0x6581, 76148159536273029070848.0}, {0x65d4, 125142711796045598162944.0}, {0x6612, 172366376624742050299904.0}, {0x661c, 184172292831916163334144.0}, {0x66c6, 467514281804094876155904.0}, {0x66ed, 559600428220052957822976.0}, {0x66f9, 587934627117270829105152.0}, {0x6703, 618630009255923522994176.0}, {0x6752, 991696961402625494876160.0}, {0x6797, 1426154677826632854536192.0}, {0x679c, 1473378342655329306673152.0}, {0x67e3, 2143954383222818927017984.0}, {0x6862, 4269019300514159273181184.0}, {0x692f, 13222626152035006598348800.0}, {0x693d, 14280436244197807126216704.0}, {0x6943, 14733783426553293066731520.0}, {0x695e, 16773845747152979799048192.0}, {0x69ec, 35663311678631560653832192.0}, {0x69f4, 36872237498246189828538368.0}, {0x6a5c, 66490920078804604608839680.0}, {0x6a7a, 75557863725914323419136000.0}, {0x6ac3, 117870267412426344533852160.0}, {0x6ad8, 130563988518379950868267008.0}, {0x6ae3, 137213080526260411329150976.0}, {0x6b37, 221233424989477138971230208.0}, {0x6b77, 298604677444813406152425472.0}, {0x6bd7, 519838102434290545123655680.0}, {0x6be7, 558523728661958678714253312.0}, {0x6c15, 720519788490318988124880896.0}, {0x6c33, 865590886844074489089622016.0}, {0x6c43, 942962139299410756270817280.0}, {0x6c45, 952633545856327789668466688.0}, {0x6c48, 967140655691703339764940800.0}, {0x6da6, 6421813953792910176039206912.0}, {0x6dba, 7195526478346272847851159552.0}, {0x6def, 9245864668412683928152834048.0}, {0x6e24, 12688885402675147817716023296.0}, {0x6e28, 12998370412496492886440804352.0}, {0x6e3d, 14623166714058554497245904896.0}, {0x6ea0, 24758800785707605497982484480.0}, {0x6eef, 36983458673650735712611336192.0}, {0x6ef9, 38530883722757461056235241472.0}, {0x6f55, 65920307091946499638378364928.0}, {0x6f5c, 68086702160695915119451832320.0}, {0x6f65, 70872067249088020737974861824.0}, {0x6f9e, 97797263103545041717030813696.0}, {0x6fdc, 136173404321391830238903664640.0}, {0x70ab, 423375493435600054015500484608.0}, {0x70dc, 544693617285567320955614658560.0}, {0x714a, 1000255551742587262118492372992.0}, {0x71ba, 1842054778456645849049896845312.0}, {0x71d3, 2089642786313721904029721690112.0}, {0x71ee, 2357037834799364043407932522496.0}, {0x71f6, 2436265997313628381001476472832.0}, {0x7251, 4139671491370311639262671405056.0}, {0x72b8, 7288990951312319058606043430912.0}, {0x731f, 12597277839768029677373488103424.0}, {0x7328, 13310331302396408715715383656448.0}, {0x7356, 16954826778052568245018405371904.0}, {0x7358, 17113283103081096920205493272576.0}, {0x7375, 19410899815994762710418267832320.0}, {0x737c, 19965496953594613073573075484672.0}, {0x7467, 73206822163180247936434610110464.0}, {0x74a4, 103947349218714810922729662840832.0}, {0x74b1, 112187078120198302032458233675776.0}, {0x7530, 223106505640168374663419764146176.0}, {0x7563, 287756686251808074139751627620352.0}, {0x756f, 302968493454546826957712066084864.0}, {0x7571, 305503794655003285760705472495616.0}, {0x7626, 841719998551544322593810928369664.0}, {0x7660, 1135814937804493543741046072016896.0}, {0x7675, 1242297588223664813466769141268480.0}, {0x76b2, 1805134454724998667731305364455424.0}, {0x772c, 3488574451828087312918927221194752.0}, {0x77b2, 7220537818899994670925221457821696.0}, {0x783a, 15090112745116842795416754956795904.0}, {0x795d, 71718600358512306619077480547352576.0}, {0x796e, 77235415770705560974391132897148928.0}, {0x798c, 90865195024359483499283685761351680.0}, {0x79af, 113581493780449354374104607201689600.0}, {0x7aa5, 428364490829123279353765947160657920.0}, {0x7acf, 537402724858354659552906370074279936.0}, {0x7adc, 571152654438831039138354596214210560.0}, {0x7b07, 700960075902201729851617004444712960.0}, {0x7b0f, 742498450770480350879860975078473728.0}, {0x7b12, 758075341346084833765452464066134016.0}, {0x7b30, 913844247102129662621367353942736896.0}, {0x7bc2, 2014611181111513119869832575737397248.0}, {0x7bd1, 2170380086867557948725747465614000128.0}, {0x7bd5, 2211918461735836569753991436247760896.0}, {0x7cf1, 10010748343255147667806796922736345088.0}, {0x7d2f, 14538431203897517359885389721816268800.0}, {0x7da0, 26584559915698317458076141205606891520.0}, {0x7e58, 71778311772385457136805581255138607104.0}, {0x7e81, 85735205728127073802295555388082225152.0}, {0x7f09, 182104235422533474587821567258407206912.0}, {0x7f24, 217993391308726203156224357885976510464.0}, {0x7f86, std::numeric_limits::quiet_NaN()}, {0x7f88, std::numeric_limits::quiet_NaN()}, {0x7f8f, std::numeric_limits::quiet_NaN()}, {0x7fa0, std::numeric_limits::quiet_NaN()}, {0x7fcd, std::numeric_limits::quiet_NaN()}, {0x8023, -0.00000000000000000000000000000000000000321424236553}, {0x8074, -0.00000000000000000000000000000000000001065291755433}, {0x8080, -0.00000000000000000000000000000000000001175494350822}, {0x80a5, -0.00000000000000000000000000000000000001515285686607}, {0x80d2, -0.00000000000000000000000000000000000001928545419318}, {0x80fd, -0.00000000000000000000000000000000000002323438052797}, {0x810a, -0.00000000000000000000000000000000000002534659693961}, {0x8124, -0.00000000000000000000000000000000000003012204273982}, {0x81e3, -0.00000000000000000000000000000000000008338663051146}, {0x81f1, -0.00000000000000000000000000000000000008852941829630}, {0x8285, -0.00000000000000000000000000000000000019542593582421}, {0x828b, -0.00000000000000000000000000000000000020424214345537}, {0x829f, -0.00000000000000000000000000000000000023362950222593}, {0x82bc, -0.00000000000000000000000000000000000027624117244324}, {0x82ee, -0.00000000000000000000000000000000000034970956936963}, {0x82f9, -0.00000000000000000000000000000000000036587261669344}, {0x8393, -0.00000000000000000000000000000000000086398834785438}, {0x8394, -0.00000000000000000000000000000000000086986581960849}, {0x843e, -0.00000000000000000000000000000000000223343926656235}, {0x8451, -0.00000000000000000000000000000000000245678319321858}, {0x847f, -0.00000000000000000000000000000000000299751059459683}, {0x8483, -0.00000000000000000000000000000000000307979519915439}, {0x84a0, -0.00000000000000000000000000000000000376158192263132}, {0x84aa, -0.00000000000000000000000000000000000399668079279578}, {0x84ba, -0.00000000000000000000000000000000000437283898505891}, {0x84e4, -0.00000000000000000000000000000000000536025423974963}, {0x8510, -0.00000000000000000000000000000000000677084746073638}, {0x854d, -0.00000000000000000000000000000000000963905367674276}, {0x8557, -0.00000000000000000000000000000000001010925141707167}, {0x8584, -0.00000000000000000000000000000000001241322034468336}, {0x85b7, -0.00000000000000000000000000000000001720923729603829}, {0x8656, -0.00000000000000000000000000000000004024892657215512}, {0x87c7, -0.00000000000000000000000000000000029942192104145307}, {0x88d9, -0.00000000000000000000000000000000130602124353759431}, {0x896c, -0.00000000000000000000000000000000284074666797117288}, {0x89a0, -0.00000000000000000000000000000000385185988877447171}, {0x89b2, -0.00000000000000000000000000000000428519412626159977}, {0x89fe, -0.00000000000000000000000000000000611482757342947383}, {0x8a31, -0.00000000000000000000000000000000852224000391351865}, {0x8a55, -0.00000000000000000000000000000001025557695386203092}, {0x8a68, -0.00000000000000000000000000000001117039367744596795}, {0x8b4a, -0.00000000000000000000000000000003890378487662216423}, {0x8b4c, -0.00000000000000000000000000000003928897086549961140}, {0x8b5b, -0.00000000000000000000000000000004217786578208046518}, {0x8bbf, -0.00000000000000000000000000000007357052387559240959}, {0x8bff, -0.00000000000000000000000000000009822242716374902851}, {0x8c43, -0.00000000000000000000000000000015022253566220439654}, {0x8c6b, -0.00000000000000000000000000000018103741477240017019}, {0x8d1d, -0.00000000000000000000000000000048379360203007364629}, {0x8d23, -0.00000000000000000000000000000050228252949619111048}, {0x8d30, -0.00000000000000000000000000000054234187233944561622}, {0x8dee, -0.00000000000000000000000000000146678824564531882569}, {0x8e54, -0.00000000000000000000000000000261310174854460160543}, {0x8e68, -0.00000000000000000000000000000285962078142616779462}, {0x8eb0, -0.00000000000000000000000000000433873497871556492976}, {0x8efe, -0.00000000000000000000000000000626158343519178120546}, {0x8f3d, -0.00000000000000000000000000000931841944292320195143}, {0x8f95, -0.00000000000000000000000000001469253435974134487579}, {0x8fa3, -0.00000000000000000000000000001607304094387811553526}, {0x8fbe, -0.00000000000000000000000000001873544649899903037853}, {0x9009, -0.00000000000000000000000000002701848600381965433535}, {0x9017, -0.00000000000000000000000000002977949917209319565429}, {0x90a8, -0.00000000000000000000000000006626431603856499165459}, {0x90d2, -0.00000000000000000000000000008283039504820623956823}, {0x9260, -0.00000000000000000000000000070681937107802657764891}, {0x92d0, -0.00000000000000000000000000131266454628776364420512}, {0x92ed, -0.00000000000000000000000000149568027629903838306064}, {0x9356, -0.00000000000000000000000000270105973947674442172976}, {0x94af, -0.00000000000000000000000001767048427695066444122272}, {0x9503, -0.00000000000000000000000002645523931749185190628773}, {0x953c, -0.00000000000000000000000003796629764647685617085567}, {0x9556, -0.00000000000000000000000004321695583162791074767614}, {0x9598, -0.00000000000000000000000006139231108792002274436236}, {0x9599, -0.00000000000000000000000006179620787139318078873317}, {0x95cf, -0.00000000000000000000000008360663417894371518475664}, {0x95df, -0.00000000000000000000000009006898271451424389468952}, {0x95f4, -0.00000000000000000000000009855081516745056282647643}, {0x960f, -0.00000000000000000000000011551448007332320069005024}, {0x9615, -0.00000000000000000000000012036124147500109722249990}, {0x966a, -0.00000000000000000000000018902369466543796476553675}, {0x968c, -0.00000000000000000000000022618219874496850484765081}, {0x96b3, -0.00000000000000000000000028919009696678115976949640}, {0x96d7, -0.00000000000000000000000034735123378691591815889232}, {0x9719, -0.00000000000000000000000049436966297114544630986535}, {0x9761, -0.00000000000000000000000072701421025168447986744905}, {0x97f0, -0.00000000000000000000000155096364853692689038389130}, {0x97fd, -0.00000000000000000000000163497417949934376361301874}, {0x983c, -0.00000000000000000000000242984304937451879493476303}, {0x987d, -0.00000000000000000000000326994835899868752722603749}, {0x9895, -0.00000000000000000000000385155972720003511111999672}, {0x98b3, -0.00000000000000000000000462704155146849855631194237}, {0x98fa, -0.00000000000000000000000646234853557052870993288041}, {0x998f, -0.00000000000000000000001478585344938536968832643037}, {0x999b, -0.00000000000000000000001602662436821491120063354341}, {0x99e4, -0.00000000000000000000002357464745776128873383514772}, {0x9a8b, -0.00000000000000000000005748905257243542340356290410}, {0x9a95, -0.00000000000000000000006162495563520056177791994756}, {0x9a9f, -0.00000000000000000000006576085869796570015227699102}, {0x9bb2, -0.00000000000000000000029447629806887785225422149438}, {0x9bc4, -0.00000000000000000000032425480012078684854959220729}, {0x9c09, -0.00000000000000000000045329497567905916582953196325}, {0x9c79, -0.00000000000000000000082387189010281556417192305730}, {0x9cca, -0.00000000000000000000133672386988569272259219644639}, {0x9d21, -0.00000000000000000000213081725793659929046874879077}, {0x9d2d, -0.00000000000000000000228963593554678060404405925965}, {0x9d53, -0.00000000000000000000279256174797902143036587574443}, {0x9d59, -0.00000000000000000000287197108678411208715353097887}, {0x9d9b, -0.00000000000000000000410281583826301726736218711267}, {0x9dc9, -0.00000000000000000000532042569994107400477290070739}, {0x9e3a, -0.00000000000000000000984675801183124144166924907040}, {0x9eb4, -0.00000000000000000001905824131322175762903725626529}, {0x9eb7, -0.00000000000000000001937587866844212025618787720305}, {0x9ec3, -0.00000000000000000002064642808932357076479036095407}, {0x9ee0, -0.00000000000000000002371692252312040949391303001903}, {0x9f0a, -0.00000000000000000002922263668027336169785712627345}, {0x9f5b, -0.00000000000000000004637505386217294356399065691221}, {0x9fb7, -0.00000000000000000007750351467376848102475150881219}, {0xa007, -0.00000000000000000011434944787933054577422353759175}, {0xa06c, -0.00000000000000000019989977555201488002012411016040}, {0xa07a, -0.00000000000000000021175823681357508476708062516991}, {0xa0c0, -0.00000000000000000032526065174565133020223584026098}, {0xa0e5, -0.00000000000000000038794108984246955529329170531128}, {0xa108, -0.00000000000000000046078592330633938445316744036973}, {0xa128, -0.00000000000000000056920614055488982785391272045672}, {0xa178, -0.00000000000000000084025668367626593635577592067420}, {0xa1f6, -0.00000000000000000166696084019646306728645868133754}, {0xa305, -0.00000000000000000720994444702860448614956112578511}, {0xa312, -0.00000000000000000791467585914418236825440544635057}, {0xa3f2, -0.00000000000000002623769257414920730298035778105259}, {0xa3f3, -0.00000000000000002634611279139775774638110306113958}, {0xa40a, -0.00000000000000002992397996059992237860569730401039}, {0xa412, -0.00000000000000003165870343657672947301762178540230}, {0xa413, -0.00000000000000003187554387107383035981911234557629}, {0xa421, -0.00000000000000003491130995403324277503998018801212}, {0xa450, -0.00000000000000004510281037539698445471003651618958}, {0xa456, -0.00000000000000004640385298237958977551897987723351}, {0xa4a4, -0.00000000000000007112366251504909087088890373706818}, {0xa4c9, -0.00000000000000008716985466783455649419920518994331}, {0xa4ed, -0.00000000000000010278236595162582034390652552247047}, {0xa522, -0.00000000000000014051260155412137464736588299274445}, {0xa53a, -0.00000000000000016132928326584305978030897676944733}, {0xa541, -0.0000000000000001674008154317618846107507124543190}, {0xa6f7, -0.00000000000000171390679426508540927898138761520386}, {0xa76c, -0.00000000000000327515792264421179424971342086791992}, {0xa7a3, -0.00000000000000452415882534751290222629904747009277}, {0xa7ae, -0.00000000000000482947015711943095084279775619506836}, {0xa7bc, -0.00000000000000521804821573823573999106884002685547}, {0xa837, -0.00000000000001015854067532018234487622976303100586}, {0xa83e, -0.00000000000001054711873393898713402450084686279297}, {0xa881, -0.00000000000001432187701766451937146484851837158203}, {0xa8b9, -0.00000000000002053912595556539599783718585968017578}, {0xa8c2, -0.00000000000002153832667772803688421845436096191406}, {0xa8ca, -0.00000000000002242650509742816211655735969543457031}, {0xa8d6, -0.00000000000002375877272697834996506571769714355469}, {0xa8e4, -0.00000000000002531308496145356912165880203247070312}, {0xa92d, -0.00000000000003841371665203041629865765571594238281}, {0xa933, -0.00000000000003974598428158060414716601371765136719}, {0xa937, -0.00000000000004063416270128072937950491905212402344}, {0xa950, -0.0000000000000461852778244065120816230773925781250}, {0xa956, -0.00000000000004751754545395669993013143539428710938}, {0xa9b8, -0.0000000000000817124146124115213751792907714843750}, {0xa9c6, -0.00000000000008792966355031239800155162811279296875}, {0xa9f0, -0.000000000000106581410364015027880668640136718750}, {0xaa2d, -0.00000000000015365486660812166519463062286376953125}, {0xaae6, -0.0000000000004085620730620576068758964538574218750}, {0xaaff, -0.00000000000045297099404706386849284172058105468750}, {0xab12, -0.000000000000518696197104873135685920715332031250}, {0xab42, -0.000000000000689226453687297180294990539550781250}, {0xabfa, -0.00000000000177635683940025046467781066894531250}, {0xac3a, -0.0000000000026432189770275726914405822753906250}, {0xadbb, -0.00000000002125943865394219756126403808593750}, {0xadc3, -0.00000000002216893335571512579917907714843750}, {0xadda, -0.0000000000247837306233122944831848144531250}, {0xaddf, -0.00000000002535216481192037463188171386718750}, {0xae16, -0.000000000034106051316484808921813964843750}, {0xae77, -0.0000000000561612978344783186912536621093750}, {0xaee2, -0.00000000010277290130034089088439941406250}, {0xb03e, -0.00000000069121597334742546081542968750}, {0xb050, -0.00000000075669959187507629394531250}, {0xb075, -0.000000000891304807737469673156738281250}, {0xb11d, -0.0000000022846506908535957336425781250}, {0xb125, -0.0000000024010660126805305480957031250}, {0xb139, -0.0000000026921043172478675842285156250}, {0xb155, -0.0000000030995579436421394348144531250}, {0xb18d, -0.000000004103640094399452209472656250}, {0xb23c, -0.000000010943040251731872558593750}, {0xb2a8, -0.0000000195577740669250488281250}, {0xb341, -0.000000044936314225196838378906250}, {0xb369, -0.000000054249539971351623535156250}, {0xb37b, -0.000000058440491557121276855468750}, {0xb3c6, -0.0000000922009348869323730468750}, {0xb3c9, -0.00000009359791874885559082031250}, {0xb3dc, -0.000000102445483207702636718750}, {0xb3e2, -0.0000001052394509315490722656250}, {0xb404, -0.00000012293457984924316406250}, {0xb42d, -0.0000001611188054084777832031250}, {0xb487, -0.000000251457095146179199218750}, {0xb499, -0.000000284984707832336425781250}, {0xb49b, -0.000000288709998130798339843750}, {0xb4be, -0.00000035390257835388183593750}, {0xb599, -0.0000011399388313293457031250}, {0xb5be, -0.000001415610313415527343750}, {0xb661, -0.000003352761268615722656250}, {0xb67f, -0.000003799796104431152343750}, {0xb6f4, -0.000007271766662597656250}, {0xb70f, -0.0000085234642028808593750}, {0xb729, -0.0000100731849670410156250}, {0xb731, -0.0000105500221252441406250}, {0xb735, -0.0000107884407043457031250}, {0xb76f, -0.0000142455101013183593750}, {0xb770, -0.000014305114746093750}, {0xb7a4, -0.0000195503234863281250}, {0xb7b1, -0.000021100044250488281250}, {0xb829, -0.00004029273986816406250}, {0xb882, -0.000061988830566406250}, {0xb9a6, -0.0003166198730468750}, {0xb9c5, -0.00037574768066406250}, {0xb9cc, -0.000389099121093750}, {0xb9d3, -0.00040245056152343750}, {0xb9dd, -0.00042152404785156250}, {0xbb04, -0.002014160156250}, {0xbb14, -0.002258300781250}, {0xbb19, -0.00233459472656250}, {0xbb33, -0.00273132324218750}, {0xbb66, -0.0035095214843750}, {0xbbc5, -0.0060119628906250}, {0xbc0d, -0.008605957031250}, {0xbcb0, -0.0214843750}, {0xbcc8, -0.02441406250}, {0xbce0, -0.027343750}, {0xbce8, -0.02832031250}, {0xbd06, -0.032714843750}, {0xbd77, -0.0603027343750}, {0xbe31, -0.17285156250}, {0xbe3a, -0.1816406250}, {0xbe5d, -0.21582031250}, {0xbe85, -0.2597656250}, {0xbe9a, -0.300781250}, {0xbea5, -0.3222656250}, {0xbeb0, -0.343750}, {0xbebf, -0.3730468750}, {0xbeee, -0.464843750}, {0xbf2b, -0.667968750}, {0xbfac, -1.343750}, {0xc022, -2.531250}, {0xc026, -2.593750}, {0xc05e, -3.468750}, {0xc07e, -3.968750}, {0xc07f, -3.9843750}, {0xc086, -4.18750}, {0xc0ae, -5.43750}, {0xc0c2, -6.06250}, {0xc0e6, -7.18750}, {0xc13e, -11.8750}, {0xc198, -19.0}, {0xc1be, -23.750}, {0xc1c1, -24.1250}, {0xc1eb, -29.3750}, {0xc225, -41.250}, {0xc276, -61.50}, {0xc27f, -63.750}, {0xc29f, -79.50}, {0xc313, -147.0}, {0xc31b, -155.0}, {0xc324, -164.0}, {0xc35b, -219.0}, {0xc394, -296.0}, {0xc39d, -314.0}, {0xc3b5, -362.0}, {0xc3be, -380.0}, {0xc429, -676.0}, {0xc444, -784.0}, {0xc44b, -812.0}, {0xc4b5, -1448.0}, {0xc4eb, -1880.0}, {0xc523, -2608.0}, {0xc557, -3440.0}, {0xc55e, -3552.0}, {0xc56d, -3792.0}, {0xc58b, -4448.0}, {0xc64d, -13120.0}, {0xc6b8, -23552.0}, {0xc6ca, -25856.0}, {0xc777, -63232.0}, {0xc7d6, -109568.0}, {0xc868, -237568.0}, {0xc8ca, -413696.0}, {0xc910, -589824.0}, {0xc9c5, -1613824.0}, {0xc9c8, -1638400.0}, {0xc9df, -1826816.0}, {0xca3a, -3047424.0}, {0xca42, -3178496.0}, {0xca6b, -3850240.0}, {0xcaa0, -5242880.0}, {0xcaa2, -5308416.0}, {0xcaac, -5636096.0}, {0xcb3a, -12189696.0}, {0xcb84, -17301504.0}, {0xcc50, -54525952.0}, {0xcc89, -71827456.0}, {0xcc94, -77594624.0}, {0xccaf, -91750400.0}, {0xcce0, -117440512.0}, {0xcce1, -117964800.0}, {0xcd6d, -248512512.0}, {0xcda8, -352321536.0}, {0xcdba, -390070272.0}, {0xcdd0, -436207616.0}, {0xcde5, -480247808.0}, {0xcdf7, -517996544.0}, {0xce30, -738197504.0}, {0xcec2, -1627389952.0}, {0xcf03, -2197815296.0}, {0xcf25, -2768240640.0}, {0xcf57, -3607101440.0}, {0xd036, -12213813248.0}, {0xd09e, -21206401024.0}, {0xd103, -35165044736.0}, {0xd104, -35433480192.0}, {0xd11f, -42681237504.0}, {0xd125, -44291850240.0}, {0xd19c, -83751862272.0}, {0xd1c7, -106837311488.0}, {0xd1cf, -111132278784.0}, {0xd1d8, -115964116992.0}, {0xd231, -190052302848.0}, {0xd28a, -296352743424.0}, {0xd294, -317827579904.0}, {0xd2be, -408021893120.0}, {0xd2c1, -414464344064.0}, {0xd2c6, -425201762304.0}, {0xd2db, -470298918912.0}, {0xd334, -773094113280.0}, {0xd36f, -1026497183744.0}, {0xd375, -1052266987520.0}, {0xd3c3, -1675037245440.0}, {0xd3d5, -1829656068096.0}, {0xd3f2, -2078764171264.0}, {0xd44c, -3504693313536.0}, {0xd49b, -5325759447040.0}, {0xd4cd, -7043746365440.0}, {0xd4e8, -7971459301376.0}, {0xd538, -12644383719424.0}, {0xd54c, -14018773254144.0}, {0xd554, -14568529068032.0}, {0xd5a3, -22402549415936.0}, {0xd5bf, -26250840113152.0}, {0xd64a, -55525337202688.0}, {0xd74f, -227598906949632.0}, {0xd75f, -245191092994048.0}, {0xd762, -248489627877376.0}, {0xd7d6, -470590976688128.0}, {0xd7db, -481586092965888.0}, {0xd819, -672901116198912.0}, {0xd82d, -760862046420992.0}, {0xd85b, -963172185931776.0}, {0xd936, -3201777860083712.0}, {0xd967, -4063794976260096.0}, {0xd976, -4327677766926336.0}, {0xd985, -4679521487814656.0}, {0xd98c, -4925812092436480.0}, {0xd9c9, -7072058789855232.0}, {0xda1b, -10907155347537920.0}, {0xda5a, -15340386230730752.0}, {0xdab6, -25614222880669696.0}, {0xdacc, -28710447624486912.0}, {0xdb00, -36028797018963968.0}, {0xdb0d, -39687971716202496.0}, {0xdb24, -46161896180547584.0}, {0xdb4c, -57420895248973824.0}, {0xdbbd, -106397541196627968.0}, {0xdbbf, -107523441103470592.0}, {0xdbcc, -114841790497947648.0}, {0xdbf4, -137359788634800128.0}, {0xdc88, -306244774661193728.0}, {0xdc9c, -351280770934898688.0}, {0xdd24, -738590338888761344.0}, {0xdd71, -1085367510196289536.0}, {0xdd86, -1206964700135292928.0}, {0xdd8f, -1288029493427961856.0}, {0xdea9, -6088866696204910592.0}, {0xded6, -7710162562058289152.0}, {0xdedf, -8034421735228964864.0}, {0xdef6, -8863084066665136128.0}, {0xdf62, -16285016252571713536.0}, {0xdf6a, -16861477004875137024.0}, {0xdf71, -17365880163140632576.0}, {0xdfc9, -28967152803247030272.0}, {0xdff0, -34587645138205409280.0}, {0xdff5, -35308221078584688640.0}, {0xe037, -52746158835763249152.0}, {0xe07e, -73210515542534782976.0}, {0xe09a, -88774955854727217152.0}, {0xe09d, -90504338111637487616.0}, {0xe0cc, -117597993469898391552.0}, {0xe113, -169479461177206505472.0}, {0xe18f, -329735550317558235136.0}, {0xe1aa, -391993311566327971840.0}, {0xe21a, -710199646837817737216.0}, {0xe233, -825491797298502434816.0}, {0xe238, -848550227390639374336.0}, {0xe247, -917725517667050192896.0}, {0xe262, -1042241040164589666304.0}, {0xe26a, -1079134528312008769536.0}, {0xe271, -1111416330441000484864.0}, {0xe28b, -1282048713122813837312.0}, {0xe290, -1328165573307087716352.0}, {0xe2b2, -1641760222560150093824.0}, {0xe2eb, -2167492428660872314880.0}, {0xe2f5, -2259726149029420072960.0}, {0xe34b, -3744689046963038978048.0}, {0xe363, -4187410904732068216832.0}, {0xe388, -5017514388048998039552.0}, {0xe3a0, -5902958103587056517120.0}, {0xe3bd, -6972869259862210510848.0}, {0xe3d6, -7895206463547688091648.0}, {0xe3e4, -8411715297611555536896.0}, {0xe406, -9887454823508319666176.0}, {0xe5a3, -96218217088469021229056.0}, {0xe5ae, -102711471002414783397888.0}, {0xe5d7, -126913599227121715118080.0}, {0xe5d9, -128094190847839126421504.0}, {0xe644, -231395957660612615471104.0}, {0xe66e, -280980805730743890214912.0}, {0xe6b8, -434457716424007359660032.0}, {0xe7f4, -2304514843640386864283648.0}, {0xe824, -3097872412762487260184576.0}, {0xe827, -3154540810556923002748928.0}, {0xe880, -4835703278458516698824704.0}, {0xe8f2, -9142501510835633133715456.0}, {0xe928, -12693721105953606334414848.0}, {0xe980, -19342813113834066795298816.0}, {0xe9d1, -31583187037432187189198848.0}, {0xe9fa, -37778931862957161709568000.0}, {0xea15, -45032486780644936757805056.0}, {0xea77, -74651169361203351538106368.0}, {0xeaca, -122101507781077546645323776.0}, {0xeacd, -123914896510499490407383040.0}, {0xead9, -131168451428187265455620096.0}, {0xeada, -131772914337994580042973184.0}, {0xeb79, -301022529084042664501837824.0}, {0xeba3, -394109817194369110954213376.0}, {0xebf3, -587537948332709778907201536.0}, {0xec09, -662491349148816787738984448.0}, {0xec13, -710848381933401954727231488.0}, {0xec48, -967140655691703339764940800.0}, {0xece9, -2253437727761668781652312064.0}, {0xed1f, -3075507285099616620452511744.0}, {0xed24, -3172221350668786954429005824.0}, {0xed9f, -6151014570199233240905023488.0}, {0xede6, -8897694032363670725837455360.0}, {0xedfe, -9826149061827705932011798528.0}, {0xeea3, -25223028300439623101069656064.0}, {0xeea5, -25532513310260968169794437120.0}, {0xeec4, -30329530962491816735028543488.0}, {0xeee0, -34662321099990647697175478272.0}, {0xef23, -50446056600879246202139312128.0}, {0xefa0, -99035203142830421991929937920.0}, {0xefab, -105843873358900013503875121152.0}, {0xf020, -198070406285660843983859875840.0}, {0xf099, -378809652021326364119132012544.0}, {0xf0a9, -418423733278458532915903987712.0}, {0xf0cc, -505079536028435152158842683392.0}, {0xf0e6, -569452418071274926453597143040.0}, {0xf16b, -1163663636928257458405176770560.0}, {0xf19b, -1535045648713871540874914037760.0}, {0xf1bc, -1861861819085211933448282832896.0}, {0xf1da, -2158967428513703199424072646656.0}, {0xf231, -3505846191256196938514319802368.0}, {0xf253, -4179285572627443808059443380224.0}, {0xf287, -5347900969712842787564216647680.0}, {0xf296, -5942112188569825319515796275200.0}, {0xf2f3, -9626221745483117017615589965824.0}, {0xf339, -14657210065138902454805630812160.0}, {0xf36e, -18856302678394912347263460179968.0}, {0xf3b3, -28363682180106632858488734220288.0}, {0xf3c0, -30423614405477505635920876929024.0}, {0xf3da, -34543478856219251190785162346496.0}, {0xf3eb, -37237236381704238668965656657920.0}, {0xf407, -42783207757702742300513733181440.0}, {0xf418, -48170722808672717256874721804288.0}, {0xf425, -52290587259414462811739007221760.0}, {0xf473, -77009773963864936140924719726592.0}, {0xf515, -188879939434006180823008777601024.0}, {0xf57a, -316912650057057350374175801344000.0}, {0xf594, -375224577667555902843024148791296.0}, {0xf61c, -791013974542415146533942800154624.0}, {0xf634, -912708432164325169077626307870720.0}, {0xf69f, -1612451563490307798703806477238272.0}, {0xf6b2, -1805134454724998667731305364455424.0}, {0xf6f0, -2433889152438200450873670154321920.0}, {0xf71e, -3204620717376963926983665703190528.0}, {0xf735, -3671116138260952346734452482768896.0}, {0xf748, -4056481920730334084789450257203200.0}, {0xf75f, -4522977341614322504540237036781568.0}, {0xf796, -6084722881095501127184175385804800.0}, {0xf8a3, -26448262123161778232827215676964864.0}, {0xf8d0, -33749929580476379585448226139930624.0}, {0xf8ef, -38779967162181993850587144458862592.0}, {0xf9a3, -105793048492647112931308862707859456.0}, {0xfa0d, -183028464263352673905699995605008384.0}, {0xfa18, -197307280624323449884158860510363648.0}, {0xfa31, -229759135990166122562474462567989248.0}, {0xfaa9, -438749084546192934610826939819098112.0}, {0xfab5, -469902865697401900382009917794418688.0}, {0xfacd, -532210427999819831924375873745059840.0}, {0xfb84, -1370766370653194493932051030914105344.0}, {0xfba8, -1744611744467702083186246766617952256.0}, {0xfbfa, -2596148429267413814265248164610048000.0}, {0xfc20, -3323069989462289682259517650700861440.0}, {0xfc76, -5109220108798270386474008387952574464.0}, {0xfc93, -6106141105636957291151863683162832896.0}, {0xfccf, -8598443597733674552846501921188478976.0}, {0xfcdc, -9138442471021296626213673539427368960.0}, {0xfcf0, -9969209968386869046778552952102584320.0}, {0xfd5b, -18193808192306036010370859137587216384.0}, {0xfda9, -28079941410956347815092924148422279168.0}, {0xfdca, -33563006893569125790821128272078700544.0}, {0xfe1d, -52172198834557948011474427116003524608.0}, {0xfe24, -54498347827181550789056089471494127616.0}, {0xfe4b, -67458320786084480549868208309227487232.0}, {0xfe4f, -68787548781869396422772015369507831808.0}, {0xfe64, -75765995759740204755517002435979640832.0}, {0xfea2, -107667467658578185705208371882707910656.0}, {0xfec2, -128935115591136839671669284847193423872.0}, {0xfee3, -150867377521587951574582101341819109376.0}, {0xfee9, -154855061508942699193293522522660143104.0}, {0xff57, -285784019093756912674318517960274083840.0}, {0xff60, -297747071055821155530452781502797185024.0}, {0xff8e, std::numeric_limits::quiet_NaN()}, {0xffb0, std::numeric_limits::quiet_NaN()}, {0xfffa, std::numeric_limits::quiet_NaN()}, }; return result; } TEST_CASE(check_bf16_values) { for(auto [x, f] : bf16_lut()) { auto h = migraphx::bit_cast(x); if(std::isnan(f)) { CHECK(std::isnan(h)); } else if(std::isinf(f)) { CHECK(std::isinf(h)); CHECK((h < 0) == (f < 0)); CHECK(bit_equal(x, migraphx::bf16(f))); } else { CHECK(bit_equal(x, migraphx::bf16(f))); CHECK(migraphx::float_equal(float(h), f)); } } } TEST_CASE(check_flows) { // check positive underflow CHECK(bit_equal(std::numeric_limits::min() * std::numeric_limits::min(), migraphx::bf16(0))); // check overflow CHECK(bit_equal(std::numeric_limits::infinity() + std::numeric_limits::infinity(), std::numeric_limits::infinity())); CHECK(bit_equal(std::numeric_limits::max() + std::numeric_limits::max(), std::numeric_limits::infinity())); CHECK(bit_equal(std::numeric_limits::max() / std::numeric_limits::epsilon(), std::numeric_limits::infinity())); CHECK(bit_equal(std::numeric_limits::max() + std::numeric_limits::min(), std::numeric_limits::max())); // check negative underflow CHECK(bit_equal(std::numeric_limits::lowest() + std::numeric_limits::lowest(), -std::numeric_limits::infinity())); CHECK(bit_equal(-std::numeric_limits::infinity() - std::numeric_limits::infinity(), -std::numeric_limits::infinity())); CHECK(bit_equal(std::numeric_limits::lowest() - std::numeric_limits::min(), std::numeric_limits::lowest())); } TEST_CASE(test_nan) { float f_qnan = std::numeric_limits::quiet_NaN(); migraphx::bf16 bf16_qnan(f_qnan); EXPECT(bf16_qnan.is_nan()); EXPECT(std::isnan(bf16_qnan)); float f_snan = std::numeric_limits::signaling_NaN(); migraphx::bf16 bf16_snan(f_snan); EXPECT(bf16_snan.is_nan()); EXPECT(std::isnan(bf16_snan)); } TEST_CASE(test_bool) { float zero = 0.0; float two = 2.0; float other = -0.375; migraphx::bf16 bf16_zero(zero); migraphx::bf16 bf16_two(two); migraphx::bf16 bf16_other(other); EXPECT(not static_cast(bf16_zero)); EXPECT(static_cast(bf16_two)); EXPECT(static_cast(bf16_other)); } TEST_CASE(test_pos_infinity) { float finf = std::numeric_limits::infinity(); migraphx::bf16 bf16_inf_1(finf); CHECK(bit_equal(bf16_inf_1, std::numeric_limits::infinity())); } TEST_CASE(test_neg_infinity) { float finf = -1.0 * std::numeric_limits::infinity(); migraphx::bf16 bf16_neginf_1(finf); CHECK(bit_equal(bf16_neginf_1, -std::numeric_limits::infinity())); } TEST_CASE(test_numeric_max_1) { float fmax = std::numeric_limits::max(); // fp32 max is fp16 inf migraphx::bf16 bf16_inf(fmax); CHECK(bit_equal(bf16_inf, std::numeric_limits::max())); } TEST_CASE(test_numeric_lowest_1) { float flowest = std::numeric_limits::lowest(); migraphx::bf16 bf16_neginf(flowest); CHECK(bit_equal(bf16_neginf, std::numeric_limits::lowest())); } TEST_CASE(test_max_eq_lowest) { EXPECT(migraphx::float_equal(std::numeric_limits::lowest(), -1 * std::numeric_limits::max())); } TEST_CASE(test_isfinite) { EXPECT(std::isfinite(migraphx::bf16(0.0))); EXPECT(std::isfinite(migraphx::bf16(-0.0))); EXPECT(not std::isfinite(migraphx::bf16(std::numeric_limits::quiet_NaN()))); } TEST_CASE(test_binary_ops) { auto a = migraphx::bf16(-1.0); auto b = migraphx::bf16(1.0); auto c = migraphx::bf16(0.0); auto d = migraphx::bf16(-0.0); EXPECT(migraphx::float_equal((c + d), c)); EXPECT(migraphx::float_equal((c + d), d)); EXPECT(migraphx::float_equal((a + b), c)); EXPECT(migraphx::float_equal((a + b), d)); auto e = migraphx::bf16(10.0); auto f = migraphx::bf16(-10.0); EXPECT(e > f); EXPECT(f < e); EXPECT(f <= e); EXPECT(e >= f); EXPECT(e <= e); EXPECT(f >= f); EXPECT(not migraphx::float_equal(f, e)); } TEST_CASE(test_stream_op) { auto a = migraphx::bf16(-1.0); std::stringstream ss; ss << a; EXPECT(std::string("-1") == ss.str()); ss = std::stringstream(); auto b = std::numeric_limits::quiet_NaN(); ss << b; EXPECT(std::string("nan") == ss.str()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/bit_signal.cpp000066400000000000000000000032331510465702400203370ustar00rootroot00000000000000#include #include TEST_CASE(triggered) { migraphx::bit_signal<64> signals; EXPECT(signals.nslots() == 0); auto slot = signals.subscribe(); EXPECT(signals.nslots() == 1); EXPECT(slot.valid()); EXPECT(not slot.triggered()); signals.notify(); EXPECT(slot.triggered()); } TEST_CASE(default_slot) { auto slot = migraphx::bit_signal<64>::slot(); EXPECT(not slot.valid()); } TEST_CASE(copy_slot) { migraphx::bit_signal<64> signals; EXPECT(signals.nslots() == 0); auto slot1 = signals.subscribe(); auto slot2 = slot1; // NOLINT EXPECT(signals.nslots() == 2); EXPECT(slot1.i != slot2.i); EXPECT(slot1.valid()); EXPECT(slot2.valid()); EXPECT(not slot1.triggered()); EXPECT(not slot2.triggered()); signals.notify(); EXPECT(slot1.triggered()); EXPECT(slot2.triggered()); } TEST_CASE(move_slot) { migraphx::bit_signal<64> signals; EXPECT(signals.nslots() == 0); auto slot1 = signals.subscribe(); auto slot2 = std::move(slot1); EXPECT(signals.nslots() == 1); EXPECT(not slot1.valid()); // cppcheck-suppress accessMoved EXPECT(slot2.valid()); EXPECT(not slot2.triggered()); signals.notify(); EXPECT(slot2.triggered()); } TEST_CASE(over_subscribe) { migraphx::bit_signal<1> signals; EXPECT(signals.nslots() == 0); auto slot = signals.subscribe(); EXPECT(signals.nslots() == 1); EXPECT(slot.valid()); EXPECT(not slot.triggered()); EXPECT(test::throws([&] { signals.subscribe(); })); EXPECT(test::throws([&] { auto slot2 = slot; })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/byte_test.cpp000066400000000000000000000361501510465702400202320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include "test.hpp" #include #include using migraphx::byte; TEST_CASE(byte_basic_construction) { // Test default construction constexpr byte b1{}; EXPECT(migraphx::to_integer(b1) == 0); // Test explicit construction from values constexpr byte b2{static_cast(42)}; EXPECT(migraphx::to_integer(b2) == 42); constexpr byte b3{static_cast(255)}; EXPECT(migraphx::to_integer(b3) == 255); constexpr byte b4{static_cast(128)}; EXPECT(migraphx::to_integer(b4) == 128); } TEST_CASE(byte_left_shift_operator) { constexpr byte b{static_cast(0b00000001)}; // Test left shift with different unsigned integer types EXPECT(migraphx::to_integer(b << 0u) == 0b00000001); EXPECT(migraphx::to_integer(b << 1u) == 0b00000010); EXPECT(migraphx::to_integer(b << 2u) == 0b00000100); EXPECT(migraphx::to_integer(b << 3u) == 0b00001000); EXPECT(migraphx::to_integer(b << 7u) == 0b10000000); // Test with different unsigned types EXPECT(migraphx::to_integer(b << static_cast(1)) == 0b00000010); EXPECT(migraphx::to_integer(b << static_cast(2)) == 0b00000100); EXPECT(migraphx::to_integer(b << static_cast(3)) == 0b00001000); // Test edge case - shift by 8 or more (implementation defined behavior) constexpr byte b2{static_cast(0b11111111)}; EXPECT(migraphx::to_integer(b2 << 8u) == 0); // Should wrap around } TEST_CASE(byte_right_shift_operator) { constexpr byte b{static_cast(0b10000000)}; // Test right shift with different unsigned integer types EXPECT(migraphx::to_integer(b >> 0u) == 0b10000000); EXPECT(migraphx::to_integer(b >> 1u) == 0b01000000); EXPECT(migraphx::to_integer(b >> 2u) == 0b00100000); EXPECT(migraphx::to_integer(b >> 3u) == 0b00010000); EXPECT(migraphx::to_integer(b >> 7u) == 0b00000001); // Test with different unsigned types EXPECT(migraphx::to_integer(b >> static_cast(1)) == 0b01000000); EXPECT(migraphx::to_integer(b >> static_cast(2)) == 0b00100000); EXPECT(migraphx::to_integer(b >> static_cast(3)) == 0b00010000); // Test edge case - shift by 8 or more EXPECT(migraphx::to_integer(b >> 8u) == 0); } TEST_CASE(byte_left_shift_assignment) { byte b{static_cast(0b00000001)}; // Test left shift assignment with different unsigned integer types b <<= 1u; EXPECT(migraphx::to_integer(b) == unsigned{0b00000010}); b <<= static_cast(2); EXPECT(migraphx::to_integer(b) == unsigned{0b00001000}); b <<= static_cast(1); EXPECT(migraphx::to_integer(b) == unsigned{0b00010000}); b <<= static_cast(3); EXPECT(migraphx::to_integer(b) == unsigned{0b10000000}); } TEST_CASE(byte_right_shift_assignment) { byte b{static_cast(0b10000000)}; // Test right shift assignment with different unsigned integer types b >>= 1u; EXPECT(migraphx::to_integer(b) == unsigned{0b01000000}); b >>= static_cast(2); EXPECT(migraphx::to_integer(b) == unsigned{0b00010000}); b >>= static_cast(1); EXPECT(migraphx::to_integer(b) == unsigned{0b00001000}); b >>= static_cast(3); EXPECT(migraphx::to_integer(b) == unsigned{0b00000001}); } TEST_CASE(byte_bitwise_or_operator) { constexpr byte b1{static_cast(0b10101010)}; constexpr byte b2{static_cast(0b01010101)}; constexpr byte b3{static_cast(0b11110000)}; constexpr byte b4{static_cast(0b00001111)}; EXPECT(migraphx::to_integer(b1 | b2) == 0b11111111); EXPECT(migraphx::to_integer(b3 | b4) == 0b11111111); EXPECT(migraphx::to_integer(b1 | b3) == 0b11111010); EXPECT(migraphx::to_integer(b2 | b4) == 0b01011111); // Test with zero constexpr byte zero{static_cast(0)}; EXPECT(migraphx::to_integer(b1 | zero) == 0b10101010); EXPECT(migraphx::to_integer(zero | b1) == 0b10101010); } TEST_CASE(byte_bitwise_or_assignment) { byte b{static_cast(0b10101010)}; constexpr byte mask{static_cast(0b01010101)}; b |= mask; EXPECT(migraphx::to_integer(b) == 0b11111111); byte b2{static_cast(0b11110000)}; b2 |= byte{0b00001111}; EXPECT(migraphx::to_integer(b2) == 0b11111111); } TEST_CASE(byte_bitwise_and_operator) { constexpr byte b1{static_cast(0b11111111)}; constexpr byte b2{static_cast(0b10101010)}; constexpr byte b3{static_cast(0b11110000)}; constexpr byte b4{static_cast(0b00001111)}; EXPECT(migraphx::to_integer(b1 & b2) == 0b10101010); EXPECT(migraphx::to_integer(b2 & b3) == 0b10100000); EXPECT(migraphx::to_integer(b3 & b4) == 0b00000000); // Test with zero constexpr byte zero{static_cast(0)}; EXPECT(migraphx::to_integer(b1 & zero) == 0); EXPECT(migraphx::to_integer(zero & b1) == 0); } TEST_CASE(byte_bitwise_and_assignment) { byte b{static_cast(0b11111111)}; constexpr byte mask{static_cast(0b10101010)}; b &= mask; EXPECT(migraphx::to_integer(b) == 0b10101010); byte b2{static_cast(0b11110000)}; b2 &= byte{0b10101010}; EXPECT(migraphx::to_integer(b2) == 0b10100000); } TEST_CASE(byte_bitwise_xor_operator) { constexpr byte b1{static_cast(0b10101010)}; constexpr byte b2{static_cast(0b01010101)}; constexpr byte b3{static_cast(0b11111111)}; EXPECT(migraphx::to_integer(b1 ^ b2) == 0b11111111); EXPECT(migraphx::to_integer(b1 ^ b3) == 0b01010101); EXPECT(migraphx::to_integer(b2 ^ b3) == 0b10101010); // Test XOR with self (should be zero) EXPECT(migraphx::to_integer(b1 ^ b1) == 0); EXPECT(migraphx::to_integer(b2 ^ b2) == 0); // Test XOR with zero constexpr byte zero{static_cast(0)}; EXPECT(migraphx::to_integer(b1 ^ zero) == 0b10101010); EXPECT(migraphx::to_integer(zero ^ b1) == 0b10101010); } TEST_CASE(byte_bitwise_xor_assignment) { byte b{static_cast(0b10101010)}; constexpr byte mask{static_cast(0b01010101)}; b ^= mask; EXPECT(migraphx::to_integer(b) == 0b11111111); byte b2{static_cast(0b11111111)}; b2 ^= byte{0b10101010}; EXPECT(migraphx::to_integer(b2) == 0b01010101); // Test XOR assignment with self (should be zero) byte b3{static_cast(0b10101010)}; b3 ^= byte{0b10101010}; EXPECT(migraphx::to_integer(b3) == 0); } TEST_CASE(byte_bitwise_not_operator) { constexpr byte b1{static_cast(0b00000000)}; constexpr byte b2{static_cast(0b11111111)}; constexpr byte b3{static_cast(0b10101010)}; constexpr byte b4{static_cast(0b01010101)}; EXPECT(migraphx::to_integer(~b1) == 0b11111111); EXPECT(migraphx::to_integer(~b2) == 0b00000000); EXPECT(migraphx::to_integer(~b3) == 0b01010101); EXPECT(migraphx::to_integer(~b4) == 0b10101010); } TEST_CASE(byte_to_integer_function) { constexpr byte b1{static_cast(42)}; constexpr byte b2{static_cast(255)}; constexpr byte b3{static_cast(0)}; constexpr byte b4{static_cast(128)}; // Test conversion to different unsigned integer types EXPECT(migraphx::to_integer(b1) == 42); EXPECT(migraphx::to_integer(b1) == 42); EXPECT(migraphx::to_integer(b1) == 42); EXPECT(migraphx::to_integer(b1) == 42); EXPECT(migraphx::to_integer(b2) == 255); EXPECT(migraphx::to_integer(b2) == 255); EXPECT(migraphx::to_integer(b2) == 255); EXPECT(migraphx::to_integer(b2) == 255); EXPECT(migraphx::to_integer(b3) == 0); EXPECT(migraphx::to_integer(b3) == 0); EXPECT(migraphx::to_integer(b3) == 0); EXPECT(migraphx::to_integer(b3) == 0); EXPECT(migraphx::to_integer(b4) == 128); EXPECT(migraphx::to_integer(b4) == 128); EXPECT(migraphx::to_integer(b4) == 128); EXPECT(migraphx::to_integer(b4) == 128); } TEST_CASE(byte_stream_insertion_operator) { constexpr byte b1{static_cast(42)}; constexpr byte b2{static_cast(48)}; constexpr byte b3{static_cast(65)}; constexpr byte b4{static_cast(0)}; std::ostringstream oss; // Test stream insertion - outputs as characters (uint8_t behavior) oss << b1; EXPECT(oss.str() == "42"); oss.str(""); oss << b2; EXPECT(oss.str() == "48"); oss.str(""); oss << b3; EXPECT(oss.str() == "65"); oss.str(""); oss << b4; EXPECT(oss.str().length() == 1 and oss.str() == "0"); // 0 is null character // Test multiple bytes in sequence oss.str(""); oss << b2 << ", " << b3; EXPECT(oss.str() == "48, 65"); } TEST_CASE(byte_boundary_values) { // Test boundary values constexpr byte min_byte{static_cast(0)}; constexpr byte max_byte{static_cast(255)}; constexpr byte mid_byte{static_cast(128)}; EXPECT(migraphx::to_integer(min_byte) == 0); EXPECT(migraphx::to_integer(max_byte) == 255); EXPECT(migraphx::to_integer(mid_byte) == 128); // Test operations with boundary values EXPECT(migraphx::to_integer(min_byte | max_byte) == 255); EXPECT(migraphx::to_integer(min_byte & max_byte) == 0); EXPECT(migraphx::to_integer(min_byte ^ max_byte) == 255); EXPECT(migraphx::to_integer(~min_byte) == 255); EXPECT(migraphx::to_integer(~max_byte) == 0); } TEST_CASE(byte_constexpr_evaluation) { // Test that operations can be evaluated at compile time constexpr byte b1{static_cast(42)}; constexpr byte b2{static_cast(128)}; constexpr auto or_result = b1 | b2; constexpr auto and_result = b1 & b2; constexpr auto xor_result = b1 ^ b2; constexpr auto not_result = ~b1; constexpr auto shift_left_result = b1 << 2u; constexpr auto shift_right_result = b2 >> 1u; static_assert(migraphx::to_integer(or_result) == (42u | 128u), "Constexpr OR evaluation failed"); static_assert(migraphx::to_integer(and_result) == (42u & 128u), "Constexpr AND evaluation failed"); static_assert(migraphx::to_integer(xor_result) == (42u ^ 128u), "Constexpr XOR evaluation failed"); static_assert(migraphx::to_integer(not_result) == (~42u & 0xFFu), "Constexpr NOT evaluation failed"); static_assert(migraphx::to_integer(shift_left_result) == ((42u << 2u) & 0xFFu), "Constexpr left shift evaluation failed"); static_assert(migraphx::to_integer(shift_right_result) == (128u >> 1u), "Constexpr right shift evaluation failed"); } TEST_CASE(byte_various_bit_patterns) { // Test with various bit patterns constexpr byte all_ones{static_cast(0b11111111)}; constexpr byte alternating1{static_cast(0b10101010)}; constexpr byte alternating2{static_cast(0b01010101)}; constexpr byte lower_nibble{static_cast(0b00001111)}; constexpr byte upper_nibble{static_cast(0b11110000)}; // Test OR operations EXPECT(migraphx::to_integer(alternating1 | alternating2) == 0b11111111); EXPECT(migraphx::to_integer(lower_nibble | upper_nibble) == 0b11111111); // Test AND operations EXPECT(migraphx::to_integer(alternating1 & alternating2) == 0b00000000); EXPECT(migraphx::to_integer(lower_nibble & upper_nibble) == 0b00000000); EXPECT(migraphx::to_integer(all_ones & alternating1) == 0b10101010); // Test XOR operations EXPECT(migraphx::to_integer(alternating1 ^ alternating2) == 0b11111111); EXPECT(migraphx::to_integer(lower_nibble ^ upper_nibble) == 0b11111111); EXPECT(migraphx::to_integer(all_ones ^ alternating1) == 0b01010101); // Test NOT operations EXPECT(migraphx::to_integer(~alternating1) == 0b01010101); EXPECT(migraphx::to_integer(~alternating2) == 0b10101010); EXPECT(migraphx::to_integer(~lower_nibble) == 0b11110000); EXPECT(migraphx::to_integer(~upper_nibble) == 0b00001111); } // Note: Type safety tests cannot be implemented as proper unit tests because // they would result in compilation errors. The following commented tests // demonstrate what should NOT compile: /* TEST_CASE(byte_type_safety) { constexpr byte b1{static_cast(42)}; constexpr byte b2{static_cast(128)}; // These operations should NOT compile (arithmetic operations not allowed): // auto sum = b1 + b2; // Should not compile // auto diff = b2 - b1; // Should not compile // auto prod = b1 * b2; // Should not compile // auto quot = b2 / b1; // Should not compile // auto mod = b2 % b1; // Should not compile // ++b1; // Should not compile // b1++; // Should not compile // --b1; // Should not compile // b1--; // Should not compile // b1 += b2; // Should not compile // b1 -= b2; // Should not compile // b1 *= b2; // Should not compile // b1 /= b2; // Should not compile // b1 %= b2; // Should not compile } */ int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/check_shapes_test.cpp000066400000000000000000000043561510465702400217120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "test.hpp" #include #include /*! * Tests for check_shapes object handling dynamic shapes */ using migraphx::shape; static void create_shapes(bool dynamic_allowed) { shape a{shape::int64_type, {3}}; shape b{shape::float_type, {{3, 6}, {4, 4}}}; migraphx::check_shapes{{a, b}, "", dynamic_allowed}.has(2); } TEST_CASE(allow_dynamic_shape) { EXPECT(not test::throws([] { create_shapes(true); })); } TEST_CASE(fail_dynamic_shape) { EXPECT(test::throws([] { create_shapes(false); })); } TEST_CASE(same_layout_fail) { EXPECT(test::throws([] { shape a{shape::float_type, {2, 3}}; shape b{shape::float_type, {2, 3}, {1, 2}}; migraphx::check_shapes{{a, b}, ""}.same_layout(); })); } TEST_CASE(same_layout_pass) { EXPECT(not test::throws([] { shape a{shape::float_type, {2, 3}, {1, 2}}; shape b{shape::float_type, {2, 3}, {1, 2}}; migraphx::check_shapes{{a, b}, ""}.same_layout(); })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/common_dims.cpp000066400000000000000000000106631510465702400205350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include using axes_map = std::vector>; TEST_CASE(common_d1_less) { auto cd = migraphx::common_dims::compute({2, 32, 40, 8}, {2, 1280, 8}); EXPECT(cd.dims == std::vector{2, 32, 40, 8}); EXPECT(cd.axes_map1 == axes_map{{0}, {1}, {2}, {3}}); EXPECT(cd.axes_map2 == axes_map{{0}, {1, 2}, {3}}); } static void verify_common(const migraphx::common_dims& cd) { EXPECT(cd.get_dimensions_for({2, 32, 40, 8, 8}) == std::vector{2, 32, 40, 8, 8}); EXPECT(cd.get_dimensions_for({64, 2560}) == std::vector{2, 32, 40, 8, 8}); EXPECT(cd.get_dimensions_for({2, 32, 1}) == std::vector{2, 32, 1, 1, 1}); EXPECT(cd.get_dimensions_for({2, 1, 2560}) == std::vector{2, 1, 40, 8, 8}); EXPECT(cd.get_dimensions_for({2, 8, 2560}) == std::vector{2, 8, 40, 8, 8}); EXPECT(cd.get_dimensions_for({2, 1, 8, 8}) == std::vector{2, 1, 1, 8, 8}); EXPECT(cd.get_dimensions_for({2, 32, 8}).empty()); EXPECT(cd.get_dimensions_for({2, 8, 8, 8}).empty()); EXPECT(cd.get_dimensions_for({2, 1, 40, 8, 8}) == std::vector{2, 1, 40, 8, 8}); EXPECT(cd.get_dimensions_for({2, 32, 256, 8, 8}) == std::vector{2, 32, 256, 8, 8}); } TEST_CASE(common1) { auto cd = migraphx::common_dims::compute({2, 32, 2560}, {2, 1280, 8, 8}); EXPECT(cd.dims == std::vector{2, 32, 40, 8, 8}); EXPECT(cd.axes_map1 == axes_map{{0}, {1}, {2, 3, 4}}); EXPECT(cd.axes_map2 == axes_map{{0}, {1, 2}, {3}, {4}}); verify_common(cd); } TEST_CASE(common2) { auto cd = migraphx::common_dims::compute({2, 1280, 8, 8}, {2, 32, 2560}); EXPECT(cd.dims == std::vector{2, 32, 40, 8, 8}); EXPECT(cd.axes_map1 == axes_map{{0}, {1, 2}, {3}, {4}}); EXPECT(cd.axes_map2 == axes_map{{0}, {1}, {2, 3, 4}}); verify_common(cd); } TEST_CASE(common3) { auto cd = migraphx::common_dims::compute({2, 32, 4096}, {4, 16, 64, 64}); EXPECT(cd.dims == std::vector{2, 2, 16, 64, 64}); EXPECT(cd.axes_map1 == axes_map{{0}, {1, 2}, {3, 4}}); EXPECT(cd.axes_map2 == axes_map{{0, 1}, {2}, {3}, {4}}); } TEST_CASE(common4) { auto cd = migraphx::common_dims::compute({4, 16, 64, 64}, {2, 32, 4096}); EXPECT(cd.dims == std::vector{2, 2, 16, 64, 64}); EXPECT(cd.axes_map1 == axes_map{{0, 1}, {2}, {3}, {4}}); EXPECT(cd.axes_map2 == axes_map{{0}, {1, 2}, {3, 4}}); } TEST_CASE(common_same_dims) { auto cd = migraphx::common_dims::compute({{2, 32, 4}}, {64, 2, 2}); EXPECT(cd.dims == std::vector{2, 32, 2, 2}); EXPECT(cd.get_dimensions_for({64, 2, 2}) == std::vector{2, 32, 2, 2}); EXPECT(cd.get_dimensions_for({2, 32, 4}) == std::vector{2, 32, 2, 2}); // TODO: CHeck for similiarity EXPECT(cd.get_dimensions_for({2, 32, 1}).empty()); EXPECT(cd.get_dimensions_for({64, 2, 1}).empty()); } TEST_CASE(common_error1) { auto cd = migraphx::common_dims::compute({6, 35}, {3, 7, 2, 5}); EXPECT(cd.dims.empty()); } TEST_CASE(common_error2) { auto cd = migraphx::common_dims::compute({3, 7, 2, 5}, {6, 35}); EXPECT(cd.dims.empty()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/const_eval_test.cpp000066400000000000000000000115341510465702400214230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include "test.hpp" #include struct sum_cf_op { std::string name() const { return "sum_cf"; } migraphx::argument compute(const migraphx::shape&, std::vector args) const { migraphx::argument result; if(args.size() != 2) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape() != args[1].get_shape()) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape().lens().size() != 1) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape().lens().front() != 1) MIGRAPHX_THROW("Wrong args"); args[0].visit_at([&](auto x) { args[1].visit_at([&](auto y) { result = migraphx::literal{x + y}.get_argument(); }); }); return result; } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.size() != 2) MIGRAPHX_THROW("Wrong inputs"); return inputs.front(); } }; struct non_computable_cf { std::string name() const { return "non_computable"; } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } }; struct test_context { void finish() const {} }; TEST_CASE(literal_test) { migraphx::program p; auto* mm = p.get_main_module(); auto lit = mm->add_literal(1); CHECK(lit->eval() == migraphx::literal{1}); } TEST_CASE(param_test) { migraphx::program p; auto* mm = p.get_main_module(); auto lit = mm->add_parameter("param", migraphx::shape{migraphx::shape::float_type, {1}}); CHECK(lit->eval().empty()); } TEST_CASE(op_test1) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum = mm->add_instruction(sum_cf_op{}, one, two); CHECK(sum->eval() == migraphx::literal{3}); } TEST_CASE(op_test2) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("param", migraphx::shape{migraphx::shape::float_type, {1}}); auto two = mm->add_literal(2); auto sum = mm->add_instruction(sum_cf_op{}, x, two); CHECK(sum->eval().empty()); } TEST_CASE(op_test3) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum1 = mm->add_instruction(sum_op{}, one, two); auto sum2 = mm->add_instruction(sum_cf_op{}, sum1, two); CHECK(sum2->eval().empty()); } TEST_CASE(compute_op_c) { migraphx::operation op = sum_op{}; auto one = migraphx::literal{1}.get_argument(); auto two = migraphx::literal{2}.get_argument(); EXPECT(test::throws([&] { op.compute(migraphx::shape{migraphx::shape::float_type, {1}}, {one, two}); })); } TEST_CASE(compute_nop_c) { migraphx::operation op = non_computable_cf{}; auto one = migraphx::literal{1}.get_argument(); auto two = migraphx::literal{2}.get_argument(); EXPECT(test::throws([&] { op.compute(migraphx::shape{migraphx::shape::float_type, {1}}, {one, two}); })); } TEST_CASE(compute_nop_context) { migraphx::operation op = non_computable_cf{}; auto one = migraphx::literal{1}.get_argument(); auto two = migraphx::literal{2}.get_argument(); migraphx::context ctx = test_context{}; EXPECT(test::throws([&] { op.compute(ctx, migraphx::shape{migraphx::shape::float_type, {1}}, {one, two}); })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/convert_to_json.cpp000066400000000000000000000103361510465702400214410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(key_int) { std::string str = "{abc:{key:1}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":1}}"); } TEST_CASE(key_negative_int) { std::string str = "{abc:{key:-1}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":-1}}"); } TEST_CASE(key_float) { std::string str = "{abc:{key:1.0}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":1.0}}"); } TEST_CASE(key_negative_float) { std::string str = "{abc:{key:-1.0}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":-1.0}}"); } TEST_CASE(key_exp) { std::string str = "{abc:{key:1e+10}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":1e+10}}"); } TEST_CASE(key_exp_1) { std::string str = "{abc:{key:1E-10}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":1E-10}}"); } TEST_CASE(key_null) { std::string str = "{abc:{key:null}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":null}}"); } TEST_CASE(key_inf) { std::string str = "{abc:{key:inf}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":inf}}"); } TEST_CASE(key_neg_inf) { std::string str = "{abc:{key:-inf}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":-inf}}"); } TEST_CASE(key_true) { std::string str = "{abc:{key:true}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":true}}"); } TEST_CASE(key_false) { std::string str = "{abc:{key:false}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":false}}"); } TEST_CASE(key_nan) { std::string str = "{abc:{key:nan}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":nan}}"); } TEST_CASE(quote_key_num) { std::string str = R"({"abc":{"key":1}})"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\":{\"key\":1}}"); } TEST_CASE(quote_with_space_key_num) { std::string str = R"({"abc key":{"key":1}})"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc key\":{\"key\":1}}"); } TEST_CASE(key_value_num_space) { std::string str = "{abc : { key : 1}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\" : { \"key\" : 1}}"); } TEST_CASE(key_value_str) { std::string str = "{abc : {key : value}}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\" : {\"key\" : \"value\"}}"); } TEST_CASE(key_space_value) { std::string str = "{abc : [key, value]}"; auto jstr = migraphx::convert_to_json(str); EXPECT(jstr == "{\"abc\" : [\"key\", \"value\"]}"); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/dead_code_elimination_test.cpp000066400000000000000000000255641510465702400235550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes(p, {migraphx::dead_code_elimination{}}); } TEST_CASE(simple_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == count); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(simple_test_nop) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(nop{}); mm->add_instruction(migraphx::make_op("add"), one, two); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == count); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(simple_test_nop2) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(nop{}); mm->add_instruction(migraphx::make_op("add"), one, two); mm->add_instruction(nop{}); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == 2); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(duplicate_test1) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); mm->add_instruction(migraphx::make_op("add"), one, two); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == (count - 1)); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(duplicate_test2) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); mm->add_instruction(migraphx::make_op("sub"), one, two); mm->add_instruction(migraphx::make_op("add"), one, two); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == (count - 2)); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(depth_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto x1 = mm->add_instruction(migraphx::make_op("add"), one, two); auto x2 = mm->add_instruction(migraphx::make_op("add"), one, two); mm->add_instruction(migraphx::make_op("sub"), x1, x2); mm->add_instruction(migraphx::make_op("sub"), x1, x2); mm->add_instruction(migraphx::make_op("add"), one, two); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == (count - 4)); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(undefined_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction(migraphx::make_op("add"), one, two); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == count - 1); EXPECT( std::none_of(mm->begin(), mm->end(), [](auto&& ins) { return ins.name() == "undefined"; })); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(duplicate_args1) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(0); auto l3 = mm->add_literal(3); mm->add_instruction(migraphx::make_op("add"), l3, l3); mm->add_instruction(migraphx::make_op("identity"), l0); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) != count); EXPECT(std::distance(mm->begin(), mm->end()) == 2); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{0}); } TEST_CASE(duplicate_args2) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(0); auto l3 = mm->add_literal(3); auto sum1 = mm->add_instruction(migraphx::make_op("add"), l0, l3); mm->add_instruction(migraphx::make_op("add"), sum1, l3); mm->add_instruction(migraphx::make_op("identity"), l0); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) != count); EXPECT(std::distance(mm->begin(), mm->end()) == 2); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{0}); } TEST_CASE(duplicate_args3) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(0); auto l3 = mm->add_literal(3); auto sum1 = mm->add_instruction(migraphx::make_op("add"), l0, l3); auto sum2 = mm->add_instruction(migraphx::make_op("add"), l0, sum1); mm->add_instruction(migraphx::make_op("add"), sum2, l3); mm->add_instruction(migraphx::make_op("identity"), l0); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) != count); EXPECT(std::distance(mm->begin(), mm->end()) == 2); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{0}); } TEST_CASE(reused_twice) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 2, 2}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dims}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, dims}); auto z = mm->add_parameter("z", migraphx::shape{migraphx::shape::float_type, dims}); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, z); auto epsilon = mm->add_literal(1e-12f); auto exponent = mm->add_literal(2.0f); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), add2); auto mean_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), mean); auto sub = mm->add_instruction(migraphx::make_op("sub"), add2, mean_mbcast); auto exponent_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), exponent); auto pow = mm->add_instruction(migraphx::make_op("pow"), sub, exponent_mbcast); auto var = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), pow); auto epsilon_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, dims.at(1), 1}}}), epsilon); auto add_epsilon = mm->add_instruction(migraphx::make_op("add"), var, epsilon_mbcast); mm->add_instruction(migraphx::make_op("sqrt"), add_epsilon); mm->add_instruction(migraphx::make_op("add"), x, y); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) != count); EXPECT(std::distance(mm->begin(), mm->end()) == 4); } TEST_CASE(unused_module) { migraphx::program p; auto* mm = p.get_main_module(); auto* m1 = p.create_module("unused"); auto* m2 = p.create_module("used"); auto l0 = mm->add_literal(0); m1->add_literal(0); m2->add_literal(0); mm->add_instruction(mod_pass_op{}, {l0}, {m2}); EXPECT(migraphx::contains(p.get_modules(), m1)); EXPECT(migraphx::contains(p.get_modules(), m2)); run_pass(p); EXPECT(migraphx::contains(p.get_modules(), m2)); EXPECT(not migraphx::contains(p.get_modules(), m1)); } TEST_CASE(param_not_eliminated) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {2, 2}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_parameter("z", s); auto sum = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_return({sum}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_program()); } TEST_CASE(tuple_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(tuple_op{}, one, two); mm->add_return({one, two}); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == (count - 1)); } TEST_CASE(empty_literal) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(); mm->add_instruction(migraphx::make_op("convert"), x); mm->add_return({x}); auto count = std::distance(mm->begin(), mm->end()); run_pass(p); EXPECT(std::distance(mm->begin(), mm->end()) == (count - 1)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/dom.cpp000066400000000000000000000117101510465702400170020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include static bool strictly_dominates_self(const migraphx::dominator_info& dom, const migraphx::module& m) { return migraphx::any_of(migraphx::iterator_for(m), [&](auto ins) { return dom.strictly_dominate(ins, ins); }); } // clang-format off // ┌────┠// │ins1│ // └┬───┘ // ┌▽────────────┠// │ins2 │ // └┬─────┬─────┬┘ // ┌▽───â”┌▽───â”┌▽───┠// │ins4││ins3││ins6│ // └┬───┘└┬───┘└────┘ // ┌▽─────▽┠// │ins5 │ // └───────┘ // clang-format on TEST_CASE(dom1) { migraphx::module mm; auto ins1 = mm.add_parameter("entry", {migraphx::shape::float_type}); // ins1 -> ins2 auto ins2 = mm.add_instruction(pass_op{}, ins1); // ins2 -> ins3, ins2 -> ins4, ins2 -> ins6 auto ins3 = mm.add_instruction(pass_op{}, ins2); // ins3 -> ins5 auto ins4 = mm.add_instruction(pass_op{}, ins2); // ins4 -> ins5 auto ins5 = mm.add_instruction(pass_op{}, ins3, ins4); auto ins6 = mm.add_instruction(pass_op{}, ins2); auto dom = migraphx::compute_dominator(mm); CHECK(not strictly_dominates_self(dom, mm)); // ins1 CHECK(dom.strictly_dominate(ins1, ins2)); CHECK(dom.strictly_dominate(ins1, ins3)); CHECK(dom.strictly_dominate(ins1, ins4)); CHECK(dom.strictly_dominate(ins1, ins5)); CHECK(dom.strictly_dominate(ins1, ins6)); // ins2 CHECK(dom.strictly_dominate(ins2, ins3)); CHECK(dom.strictly_dominate(ins2, ins4)); CHECK(dom.strictly_dominate(ins2, ins5)); CHECK(dom.strictly_dominate(ins2, ins6)); CHECK(not dom.strictly_dominate(ins3, ins6)); CHECK(not dom.strictly_dominate(ins4, ins6)); CHECK(not dom.strictly_dominate(ins3, ins5)); CHECK(not dom.strictly_dominate(ins4, ins5)); } // clang-format off // ┌────┠// │ins1│ // └┬───┘ // ┌▽───┠// │ins2│ // └┬─┬─┘ // │┌▽───┠// ││ins3│ // │└┬───┘ // │┌▽─────┠// ││ins4 │ // │└┬───┬─┘ // ┌▽─▽─â”┌▽───┠// │ins5││ins6│ // └────┘└────┘ // clang-format on TEST_CASE(dom2) { migraphx::module mm; auto ins1 = mm.add_parameter("entry", {migraphx::shape::float_type}); // ins1 -> ins2 auto ins2 = mm.add_instruction(pass_op{}, ins1); // ins2 -> ins3, ins2 -> ins5 auto ins3 = mm.add_instruction(pass_op{}, ins2); // ins3 -> ins4 auto ins4 = mm.add_instruction(pass_op{}, ins3); // ins4 -> ins5, ins4 -> ins6 auto ins5 = mm.add_instruction(pass_op{}, ins2, ins4); auto ins6 = mm.add_instruction(pass_op{}, ins4); auto dom = migraphx::compute_dominator(mm); CHECK(not strictly_dominates_self(dom, mm)); // ins1 CHECK(dom.strictly_dominate(ins1, ins2)); CHECK(dom.strictly_dominate(ins1, ins3)); CHECK(dom.strictly_dominate(ins1, ins4)); CHECK(dom.strictly_dominate(ins1, ins5)); CHECK(dom.strictly_dominate(ins1, ins6)); // ins2 CHECK(dom.strictly_dominate(ins2, ins3)); CHECK(dom.strictly_dominate(ins2, ins4)); CHECK(dom.strictly_dominate(ins2, ins5)); CHECK(dom.strictly_dominate(ins2, ins6)); // ins3 CHECK(dom.strictly_dominate(ins3, ins4)); // ins4 CHECK(dom.strictly_dominate(ins4, ins6)); CHECK(not dom.strictly_dominate(ins5, ins6)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/dot_apply_alpha_beta_test.cpp000066400000000000000000000203641510465702400234220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include TEST_CASE(dot_apply_alpha_beta_half) { migraphx::module m1; { auto x = m1.add_parameter("x", migraphx::shape{migraphx::shape::half_type, {2, 2}}); auto y = m1.add_parameter("y", migraphx::shape{migraphx::shape::half_type, {2, 2}}); auto z = m1.add_parameter("z", migraphx::shape{migraphx::shape::half_type, {2, 2}}); auto dot_res = migraphx::insert_apply_alpha_beta( m1, m1.end(), {x, y, z}, migraphx::make_op("dot"), 3.0f, 2.0f); m1.add_instruction(migraphx::make_op("identity"), dot_res); } migraphx::module m2; { auto ht = migraphx::shape::half_type; auto ft = migraphx::shape::float_type; auto x = m2.add_parameter("x", migraphx::shape{ht, {2, 2}}); auto y = m2.add_parameter("y", migraphx::shape{ht, {2, 2}}); auto z = m2.add_parameter("z", migraphx::shape{ht, {2, 2}}); auto alpha_literal = m2.add_literal(3.0f); auto alpha_broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), alpha_literal); auto x_float = m2.add_instruction(migraphx::make_op("convert", {{"target_type", ft}}), x); auto x_alpha_float = m2.add_instruction(migraphx::make_op("mul"), alpha_broadcast, x_float); auto x_half = m2.add_instruction(migraphx::make_op("convert", {{"target_type", ht}}), x_alpha_float); auto dot_res = m2.add_instruction(migraphx::make_op("dot"), x_half, y); auto beta_literal = m2.add_literal(2.0f); auto z_float = m2.add_instruction(migraphx::make_op("convert", {{"target_type", ft}}), z); auto beta_broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", z->get_shape().lens()}}), beta_literal); auto z_beta_float = m2.add_instruction(migraphx::make_op("mul"), z_float, beta_broadcast); auto z_beta_half = m2.add_instruction(migraphx::make_op("convert", {{"target_type", ht}}), z_beta_float); auto z_add = m2.add_instruction(migraphx::make_op("add"), dot_res, z_beta_half); m2.add_instruction(migraphx::make_op("identity"), z_add); } EXPECT(m1 == m2); } TEST_CASE(dot_apply_alpha_beta_double) { migraphx::module m1; { auto x = m1.add_parameter("x", migraphx::shape{migraphx::shape::double_type, {2, 2}}); auto y = m1.add_parameter("y", migraphx::shape{migraphx::shape::double_type, {2, 2}}); auto z = m1.add_parameter("z", migraphx::shape{migraphx::shape::double_type, {2, 1}}); auto dot_res = migraphx::add_apply_alpha_beta(m1, {x, y, z}, migraphx::make_op("dot"), 3.0f, 2.0f); m1.add_instruction(migraphx::make_op("identity"), dot_res); } migraphx::module m2; { auto dt = migraphx::shape::double_type; auto x = m2.add_parameter("x", migraphx::shape{dt, {2, 2}}); auto y = m2.add_parameter("y", migraphx::shape{dt, {2, 2}}); auto z = m2.add_parameter("z", migraphx::shape{dt, {2, 1}}); auto alpha_literal = m2.add_literal(3.0f); auto alpha_broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), alpha_literal); auto alpha_double = m2.add_instruction(migraphx::make_op("convert", {{"target_type", dt}}), alpha_broadcast); auto x_alpha_double = m2.add_instruction(migraphx::make_op("mul"), alpha_double, x); auto dot_res = m2.add_instruction(migraphx::make_op("dot"), x_alpha_double, y); auto z_broadcast = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2}}}), z); auto beta_literal = m2.add_literal(2.0f); auto beta_broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", z_broadcast->get_shape().lens()}}), beta_literal); auto beta_double = m2.add_instruction(migraphx::make_op("convert", {{"target_type", dt}}), beta_broadcast); auto z_beta_double = m2.add_instruction(migraphx::make_op("mul"), z_broadcast, beta_double); auto z_add = m2.add_instruction(migraphx::make_op("add"), dot_res, z_beta_double); m2.add_instruction(migraphx::make_op("identity"), z_add); } EXPECT(m1 == m2); } TEST_CASE(quant_dot_apply_alpha_beta) { migraphx::module m1; { auto x = m1.add_parameter("x", migraphx::shape{migraphx::shape::int8_type, {2, 2}}); auto y = m1.add_parameter("y", migraphx::shape{migraphx::shape::int8_type, {2, 2}}); auto z = m1.add_parameter("z", migraphx::shape{migraphx::shape::int32_type, {2, 2}}); auto dot_res = migraphx::insert_apply_alpha_beta(m1, m1.end(), {x, y, z}, migraphx::make_op("quant_dot"), migraphx::literal{int32_t{3}}, migraphx::literal{int32_t{2}}); m1.add_instruction(migraphx::make_op("identity"), dot_res); } migraphx::module m2; { auto i8 = migraphx::shape::int8_type; auto i32 = migraphx::shape::int32_type; auto x = m2.add_parameter("x", migraphx::shape{i8, {2, 2}}); auto y = m2.add_parameter("y", migraphx::shape{i8, {2, 2}}); auto z = m2.add_parameter("z", migraphx::shape{i32, {2, 2}}); auto alpha_literal = m2.add_literal(int32_t(3)); auto alpha_broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), alpha_literal); auto x_i32 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", i32}}), x); auto x_alpha_i32 = m2.add_instruction(migraphx::make_op("mul"), alpha_broadcast, x_i32); auto x_i8 = m2.add_instruction(migraphx::make_op("convert", {{"target_type", i8}}), x_alpha_i32); auto dot_res = m2.add_instruction(migraphx::make_op("quant_dot"), x_i8, y); auto beta_literal = m2.add_literal(int32_t(2)); auto beta_broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", z->get_shape().lens()}}), beta_literal); auto z_beta_i32 = m2.add_instruction(migraphx::make_op("mul"), z, beta_broadcast); auto z_add = m2.add_instruction(migraphx::make_op("add"), dot_res, z_beta_i32); m2.add_instruction(migraphx::make_op("identity"), z_add); } EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_allocation_test.cpp000066400000000000000000000117101510465702400234360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static void run_pass(migraphx::module& m, std::size_t align = 32) { migraphx::run_passes( m, {migraphx::eliminate_allocation{"allocate", align}, migraphx::dead_code_elimination{}}); } struct allocate { migraphx::shape s{}; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.s, "shape")); } std::string name() const { return "allocate"; } migraphx::shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this}.has(0); return s; } migraphx::argument compute(migraphx::context&, const migraphx::shape& output_shape, const std::vector&) const { return migraphx::argument{output_shape}; } }; TEST_CASE(basic) { migraphx::module m; auto a1 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {8}}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {40}}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); auto a3 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {200}}}); m.add_instruction(pass_op{}, a3, m2); run_pass(m); EXPECT(m.get_output_shapes().back() == migraphx::shape{migraphx::shape::float_type, {200}}); EXPECT(m.get_parameter_shape("memory").bytes() == (8 * 4 + 40 * 4 + 200 * 4)); } TEST_CASE(aligned) { migraphx::module m; auto a1 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {1}}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2}}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); auto a3 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {200}}}); m.add_instruction(pass_op{}, a3, m2); run_pass(m); EXPECT(m.get_output_shapes().back() == migraphx::shape{migraphx::shape::float_type, {200}}); EXPECT(m.get_parameter_shape("memory").bytes() == (32 + 32 + 200 * 4)); } TEST_CASE(unaligned) { migraphx::module m; auto a1 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {1}}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2}}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); auto a3 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {200}}}); m.add_instruction(pass_op{}, a3, m2); run_pass(m, 1); EXPECT(m.get_output_shapes().back() == migraphx::shape{migraphx::shape::float_type, {200}}); EXPECT(m.get_parameter_shape("memory").bytes() == (1 * 4 + 2 * 4 + 200 * 4)); } TEST_CASE(float_aligned) { migraphx::module m; auto a1 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {1}}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2}}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); auto a3 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {200}}}); m.add_instruction(pass_op{}, a3, m2); run_pass(m, 4); EXPECT(m.get_output_shapes().back() == migraphx::shape{migraphx::shape::float_type, {200}}); EXPECT(m.get_parameter_shape("memory").bytes() == (1 * 4 + 2 * 4 + 200 * 4)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_common_subexpression_test.cpp000066400000000000000000000211141510465702400255710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes( p, {migraphx::eliminate_common_subexpression{}, migraphx::dead_code_elimination{}}); } static void run_pass(migraphx::module& m) { migraphx::run_passes( m, {migraphx::eliminate_common_subexpression{}, migraphx::dead_code_elimination{}}); } TEST_CASE(cse_test1) { migraphx::module m1; { auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m1.add_instruction(migraphx::make_op("add"), one, two); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, two); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum1, sum1); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(cse_test2) { migraphx::module m1; { auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m1.add_instruction(migraphx::make_op("add"), two, one); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m2.add_instruction(migraphx::make_op("add"), two, one); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum1, sum2); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(cse_test3) { migraphx::module m1; { auto one = m1.add_literal(1); auto two = m1.add_literal(1); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m1.add_instruction(migraphx::make_op("add"), two, one); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto one = m2.add_literal(1); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, one); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum1, sum1); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(cse_test4) { migraphx::module m1; { auto one = m1.add_literal(1); auto two = m1.add_literal(1); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m1.add_instruction(migraphx::make_op("add"), two, one); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, one); auto sum4 = m1.add_instruction(migraphx::make_op("add"), sum2, two); auto sum5 = m1.add_instruction(migraphx::make_op("add"), sum4, sum3); m1.add_instruction(pass_op{}, sum5); } run_pass(m1); migraphx::module m2; { auto one = m2.add_literal(1); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, one); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum1, one); auto sum5 = m2.add_instruction(migraphx::make_op("add"), sum3, sum3); m2.add_instruction(pass_op{}, sum5); } EXPECT(m1 == m2); } TEST_CASE(cse_test_literal) { migraphx::module m1; { auto six1 = m1.add_literal(6); auto zero1 = m1.add_literal(0); auto six2 = m1.add_literal(6); auto zero2 = m1.add_literal(0); auto six3 = m1.add_literal(6); auto zero3 = m1.add_literal(0); auto sum1 = m1.add_instruction(migraphx::make_op("add"), six1, zero1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), six2, zero2); auto sum3 = m1.add_instruction(migraphx::make_op("add"), six3, zero3); auto sum4 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); auto sum5 = m1.add_instruction(migraphx::make_op("add"), sum3, sum4); m1.add_instruction(pass_op{}, sum5); } run_pass(m1); migraphx::module m2; { auto six = m2.add_literal(6); auto zero = m2.add_literal(0); auto sum1 = m2.add_instruction(migraphx::make_op("add"), six, zero); auto sum2 = m2.add_instruction(migraphx::make_op("add"), sum1, sum1); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum1, sum2); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(cse_test_submodule) { migraphx::shape si{migraphx::shape::int64_type}; migraphx::shape s{migraphx::shape::int64_type, {1}}; migraphx::shape sc{migraphx::shape::bool_type}; auto create_program = [&](bool remove_literal = false) { migraphx::program p; std::vector vc = {true}; std::vector vd = {3}; auto* mm = p.get_main_module(); auto in_cond = mm->add_parameter("ccond", sc); auto in_val = mm->add_parameter("val", s); auto b0 = mm->add_literal(migraphx::literal(sc, vc)); auto b1 = b0; if(not(remove_literal)) b1 = mm->add_literal(migraphx::literal(sc, vc)); auto* body1 = p.create_module("loop_module1"); body1->add_parameter("#loop_module_in_1", sc); auto in_v1 = body1->add_parameter("#loop_module_in_2", s); auto l1 = body1->add_literal(migraphx::literal(si, vd)); auto ad1 = body1->add_instruction(migraphx::make_op("add"), l1, l1); auto val1 = body1->add_instruction(migraphx::make_op("add"), in_v1, ad1); auto cond1 = body1->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), b0); auto cond2 = body1->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), b1); body1->add_return({cond1, cond2, val1, val1}); auto* body2 = p.create_module("loop_module2"); body2->add_parameter("#loop_module_in_1", sc); auto in_v2 = body2->add_parameter("#loop_module_in_2", s); auto l2 = body2->add_literal(migraphx::literal(si, vd)); auto ad2 = body2->add_instruction(migraphx::make_op("add"), l2, l2); auto val2 = body2->add_instruction(migraphx::make_op("add"), in_v2, ad2); auto cond3 = body2->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), b1); body2->add_return({cond3, val2, val2}); auto loop1 = mm->add_instruction( migraphx::make_op("loop", {{"max_iterations", 1}}), {in_cond, in_val}, {body1}); auto loop2 = mm->add_instruction( migraphx::make_op("loop", {{"max_iterations", 1}}), {in_cond, in_val}, {body2}); mm->add_return({loop1, loop2}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_program(true)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_concat_test.cpp000066400000000000000000000360751510465702400225730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include struct concat { concat(std::size_t axis) { op.axis = axis; } migraphx::op::concat op; template static auto reflect(Self& self, F f) { return migraphx::reflect(self.op, f); } migraphx::value attributes() const { migraphx::value normalize; normalize["axis"] = migraphx::value::array{migraphx::op::normalize_attribute::include_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "eliminate_concat::concat"; } migraphx::shape normalize_compute_shape(std::vector inputs) const { inputs.pop_back(); return op.normalize_compute_shape(std::move(inputs)); } migraphx::argument compute(migraphx::context&, const migraphx::shape& output_shape, const std::vector&) const { return migraphx::argument{output_shape}; } }; struct concat_test_optimization { /// A unique name used to identify the allocate operator std::string allocate() const { return "allocate"; } /// Return the lowered concat operator std::optional get_concat(const migraphx::operation& op) const { if(op.name() != "eliminate_concat::concat") return std::nullopt; return migraphx::any_cast(op).op; } }; static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::eliminate_concat{concat_test_optimization{}}, migraphx::dead_code_elimination{}}); } struct allocate { migraphx::shape s{}; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.s, "shape")); } std::string name() const { return "allocate"; } migraphx::shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this}.has(0); return s; } migraphx::argument compute(migraphx::context&, const migraphx::shape& output_shape, const std::vector&) const { return migraphx::argument{output_shape}; } }; struct simple_op { std::string name() const { return "simple_op"; } migraphx::shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this}.has(1); return inputs.at(0); } migraphx::argument compute(migraphx::context&, const migraphx::shape&, const std::vector& args) const { return args.at(0); } int output_alias(const std::vector&) const { return 0; } }; template static migraphx::shape create_shape(Ts... xs) { return migraphx::shape{migraphx::shape::float_type, {std::size_t(xs)...}}; } using load = migraphx::op::load; using identity = migraphx::op::identity; TEST_CASE(simple) { auto create_test_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(1)}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{create_shape(1)}); auto m2 = m.add_instruction(simple_op{}, a2); std::size_t axis = 0; auto a3 = m.add_instruction(allocate{create_shape(2)}); m.add_instruction(concat(axis), m1, m2, a3); return m; }; auto create_control_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(2)}); auto l1 = m.add_instruction(load{create_shape(1), 0}, a1); auto m1 = m.add_instruction(simple_op{}, l1); auto l2 = m.add_instruction(load{create_shape(1), 4}, a1); auto m2 = m.add_instruction(simple_op{}, l2); m.add_instruction(identity{}, a1, m1, m2); return m; }; auto m1 = create_test_program(); auto m2 = create_control_program(); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(negative_axis1) { auto create_test_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(2, 2)}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{create_shape(2, 2)}); auto m2 = m.add_instruction(simple_op{}, a2); std::size_t axis = -1; auto a3 = m.add_instruction(allocate{create_shape(4, 2)}); m.add_instruction(concat(axis), m1, m2, a3); return m; }; auto create_control_program = create_test_program; auto m1 = create_test_program(); auto m2 = create_control_program(); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(negative_axis2) { auto create_test_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(2, 2)}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{create_shape(2, 2)}); auto m2 = m.add_instruction(simple_op{}, a2); std::size_t axis = -2; auto a3 = m.add_instruction(allocate{create_shape(4, 2)}); m.add_instruction(concat(axis), m1, m2, a3); return m; }; auto create_control_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(4, 2)}); auto l1 = m.add_instruction(load{create_shape(2, 2), 0}, a1); auto m1 = m.add_instruction(simple_op{}, l1); auto l2 = m.add_instruction(load{create_shape(2, 2), 16}, a1); auto m2 = m.add_instruction(simple_op{}, l2); m.add_instruction(identity{}, a1, m1, m2); return m; }; auto m1 = create_test_program(); auto m2 = create_control_program(); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(negative_axis3) { auto create_test_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(1, 2, 2)}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{create_shape(1, 2, 2)}); auto m2 = m.add_instruction(simple_op{}, a2); std::size_t axis = -2; auto a3 = m.add_instruction(allocate{create_shape(1, 4, 2)}); m.add_instruction(concat(axis), m1, m2, a3); return m; }; auto create_control_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(1, 4, 2)}); auto l1 = m.add_instruction(load{create_shape(1, 2, 2), 0}, a1); auto m1 = m.add_instruction(simple_op{}, l1); auto l2 = m.add_instruction(load{create_shape(1, 2, 2), 16}, a1); auto m2 = m.add_instruction(simple_op{}, l2); m.add_instruction(identity{}, a1, m1, m2); return m; }; auto m1 = create_test_program(); auto m2 = create_control_program(); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(reversed) { auto create_test_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(1)}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{create_shape(1)}); auto m2 = m.add_instruction(simple_op{}, a2); std::size_t axis = 0; auto a3 = m.add_instruction(allocate{create_shape(2)}); m.add_instruction(concat(axis), m2, m1, a3); return m; }; auto create_control_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(2)}); auto l1 = m.add_instruction(load{create_shape(1), 4}, a1); auto m1 = m.add_instruction(simple_op{}, l1); auto l2 = m.add_instruction(load{create_shape(1), 0}, a1); auto m2 = m.add_instruction(simple_op{}, l2); m.add_instruction(identity{}, a1, m2, m1); return m; }; auto m1 = create_test_program(); auto m2 = create_control_program(); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(nested) { auto concat_test_program = [](auto& m) { auto a1 = m.add_instruction(allocate{create_shape(1)}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{create_shape(1)}); auto m2 = m.add_instruction(simple_op{}, a2); std::size_t axis = 0; auto a3 = m.add_instruction(allocate{create_shape(2)}); return m.add_instruction(concat(axis), m1, m2, a3); }; auto create_test_program = [&] { migraphx::module m; auto concat1 = concat_test_program(m); auto concat2 = concat_test_program(m); std::size_t axis = 0; auto a1 = m.add_instruction(allocate{create_shape(4)}); m.add_instruction(concat(axis), concat1, concat2, a1); return m; }; auto concat_control_program = [](auto& m, auto a1) { auto l1 = m.add_instruction(load{create_shape(1), 0}, a1); auto m1 = m.add_instruction(simple_op{}, l1); auto l2 = m.add_instruction(load{create_shape(1), 4}, a1); auto m2 = m.add_instruction(simple_op{}, l2); return m.add_instruction(identity{}, a1, m1, m2); }; auto create_control_program = [&] { migraphx::module m; auto a1 = m.add_instruction(allocate{create_shape(4)}); auto l1 = m.add_instruction(load{create_shape(2), 0}, a1); auto concat1 = concat_control_program(m, l1); auto l2 = m.add_instruction(load{create_shape(2), 8}, a1); auto concat2 = concat_control_program(m, l2); m.add_instruction(identity{}, a1, concat1, concat2); return m; }; auto m1 = create_test_program(); auto m2 = create_control_program(); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(basic) { auto create_test_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {1, 2, 8, 8}}}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {1, 3, 8, 8}}}); auto m2 = m.add_instruction(simple_op{}, a2); auto a3 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {1, 5, 8, 8}}}); auto p3 = m.add_instruction(simple_op{}, a3); std::size_t axis = 1; auto a4 = m.add_instruction( allocate{migraphx::shape{migraphx::shape::float_type, {1, 10, 8, 8}}}); m.add_instruction(concat(axis), m1, m2, p3, a4); return m; }; auto create_control_program = [] { migraphx::module m; auto a1 = m.add_instruction( allocate{migraphx::shape{migraphx::shape::float_type, {1, 10, 8, 8}}}); auto l1 = m.add_instruction( load{migraphx::shape{migraphx::shape::float_type, {1, 2, 8, 8}}, 0}, {a1}); auto m1 = m.add_instruction(simple_op{}, l1); auto l2 = m.add_instruction( load{migraphx::shape{migraphx::shape::float_type, {1, 3, 8, 8}}, 512}, {a1}); auto m2 = m.add_instruction(simple_op{}, l2); auto l3 = m.add_instruction( load{migraphx::shape{migraphx::shape::float_type, {1, 5, 8, 8}}, 1280}, {a1}); auto p3 = m.add_instruction(simple_op{}, l3); m.add_instruction(identity{}, {a1, m1, m2, p3}); return m; }; auto m1 = create_test_program(); auto m2 = create_control_program(); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(wont_work) { auto create_test_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2, 2, 8, 8}}}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2, 3, 8, 8}}}); auto m2 = m.add_instruction(simple_op{}, a2); auto a3 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2, 5, 8, 8}}}); auto p3 = m.add_instruction(simple_op{}, a3); std::size_t axis = 1; auto a4 = m.add_instruction( allocate{migraphx::shape{migraphx::shape::float_type, {2, 10, 8, 8}}}); m.add_instruction(concat(axis), m1, m2, p3, a4); return m; }; auto create_control_program = [] { migraphx::module m; auto a1 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2, 2, 8, 8}}}); auto m1 = m.add_instruction(simple_op{}, a1); auto a2 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2, 3, 8, 8}}}); auto m2 = m.add_instruction(simple_op{}, a2); auto a3 = m.add_instruction(allocate{migraphx::shape{migraphx::shape::float_type, {2, 5, 8, 8}}}); auto p3 = m.add_instruction(simple_op{}, a3); std::size_t axis = 1; auto a4 = m.add_instruction( allocate{migraphx::shape{migraphx::shape::float_type, {2, 10, 8, 8}}}); m.add_instruction(concat(axis), m1, m2, p3, a4); return m; }; auto m1 = create_test_program(); auto m2 = create_control_program(); run_pass(m1); EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_contiguous_test.cpp000066400000000000000000000264541510465702400235230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes( m, {migraphx::eliminate_contiguous{"contiguous"}, migraphx::dead_code_elimination{}}); } TEST_CASE(standard_op) { migraphx::module m; auto l = m.add_parameter("x", {migraphx::shape::float_type, {2, 2}}); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); m.add_instruction(pass_standard_op{}, c); auto count = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == count); } TEST_CASE(standard_op_const) { migraphx::module m; auto l = m.add_literal(get_2x2()); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); m.add_instruction(pass_standard_op{}, c); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == 2); } TEST_CASE(non_standard_op) { migraphx::module m; auto l = m.add_parameter("x", {migraphx::shape::float_type, {2, 2}}); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); m.add_instruction(pass_op{}, c); auto count = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == count); } TEST_CASE(non_standard_op_const) { migraphx::module m; auto l = m.add_literal(get_2x2()); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); m.add_instruction(pass_op{}, c); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == 2); } TEST_CASE(transpose_gem) { migraphx::module m; auto l = m.add_literal(get_2x2()); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); auto ic = m.add_instruction(migraphx::make_op("identity"), c); m.add_instruction(migraphx::make_op("dot"), ic, l); auto count = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == (count - 1)); } TEST_CASE(transpose_standard_op) { migraphx::module m; auto l = m.add_parameter("x", {migraphx::shape::float_type, {2, 2}}); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); auto sn = m.add_instruction(migraphx::make_op("sin"), c); m.add_instruction(pass_standard_op{}, sn); auto count = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == count); } TEST_CASE(transpose_standard_op_const) { migraphx::module m; auto l = m.add_literal(get_2x2()); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); auto sn = m.add_instruction(migraphx::make_op("sin"), c); m.add_instruction(pass_standard_op{}, sn); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == 3); } TEST_CASE(no_packed_unary_op) { migraphx::module m; auto l = m.add_literal(get_2x2()); auto t = m.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); auto sn = m.add_instruction(migraphx::make_op("sin"), c); m.add_instruction(pass_standard_op{}, sn); auto count = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == count - 1); } TEST_CASE(non_standard_return_input) { migraphx::module m; auto l = m.add_literal(get_2x2()); auto tl = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), tl); m.add_return({c}); auto count = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == count); } TEST_CASE(non_standard_flatten_op) { migraphx::module m; auto l = m.add_parameter("x", {migraphx::shape::float_type, {2, 6, 6, 6}}); auto t = m.add_instruction( migraphx::make_op("slice", {{"axes", {2, 3}}, {"starts", {1, 1}}, {"ends", {6, 6}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); m.add_instruction(migraphx::make_op("flatten"), c); auto count = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == (count - 1)); } TEST_CASE(standard_flatten_op) { migraphx::module m; auto l = m.add_parameter("x", {migraphx::shape::float_type, {2, 6, 6, 6}}); auto t = m.add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {1, 1}}, {"ends", {6, 6}}}), l); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); m.add_instruction(migraphx::make_op("flatten"), c); auto count = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == (count - 1)); } TEST_CASE(transpose_contiguous_reshape_add) { migraphx::shape s1{migraphx::shape::float_type, {2, 10}}; migraphx::shape s2{migraphx::shape::float_type, {5, 4}}; migraphx::program p1; auto* m1 = p1.get_main_module(); { auto x = m1->add_parameter("x", s1); auto y = m1->add_parameter("y", s2); auto xt = m1->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), x); auto xc = m1->add_instruction(migraphx::make_op("contiguous"), xt); auto br = m1->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {10, 2}}}), y); auto bc = m1->add_instruction(migraphx::make_op("contiguous"), br); m1->add_instruction(sum_std_op{}, xc, bc); } auto count = std::distance(m1->begin(), m1->end()); run_pass(*m1); EXPECT(std::distance(m1->begin(), m1->end()) == (count - 1)); migraphx::program p2; auto* m2 = p2.get_main_module(); { auto x = m2->add_parameter("x", s1); auto y = m2->add_parameter("y", s2); auto xt = m2->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), x); auto xc = m2->add_instruction(migraphx::make_op("contiguous"), xt); auto br = m2->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {10, 2}}}), y); m2->add_instruction(sum_std_op{}, xc, br); } EXPECT(p1 == p2); } TEST_CASE(contiguous_pointwise) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 8, 8}}; migraphx::program p; auto* mm = p.get_main_module(); { auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {3}}); auto yb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 3, 8, 8}}}), y); auto yc = mm->add_instruction(migraphx::make_op("contiguous"), yb); auto add = add_pointwise(p, "main:pointwise0", {x, yc}, single_pointwise("add")); auto cadd = mm->add_instruction(migraphx::make_op("contiguous"), add); mm->add_instruction(pass_op{}, cadd); } auto count = std::distance(mm->begin(), mm->end()); run_pass(*mm); EXPECT(std::distance(mm->begin(), mm->end()) == (count - 2)); EXPECT(std::none_of( mm->begin(), mm->end(), [](auto&& ins) { return ins.name() == "contiguous"; })); } TEST_CASE(contiguous_nhwc_pointwise) { auto s = migraphx::shape::from_permutation(migraphx::shape::float_type, {2, 3, 8, 8}, {0, 2, 3, 1}); migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {3}}); auto yb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 3, 8, 8}}}), y); auto yc = mm->add_instruction(migraphx::make_op("contiguous"), yb); auto add = add_pointwise(p1, "main:pointwise0", {x, yc}, single_pointwise("add")); auto cadd = mm->add_instruction(migraphx::make_op("contiguous"), add); mm->add_instruction(pass_op{}, cadd); } run_pass(*p1.get_main_module()); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {3}}); auto yb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 3, 8, 8}}}), y); auto add = add_pointwise(p2, "main:pointwise0", {x, yb}, single_pointwise("add")); auto cadd = mm->add_instruction(migraphx::make_op("contiguous"), add); mm->add_instruction(pass_op{}, cadd); } EXPECT(p1 == p2); } TEST_CASE(slice_contiguous) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {4, 2}}; auto x = m.add_parameter("x", s); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), x); auto c = m.add_instruction(migraphx::make_op("contiguous"), t); auto s1 = m.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), c); auto s2 = m.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), c); auto c1 = m.add_instruction(migraphx::make_op("contiguous"), s1); auto c2 = m.add_instruction(migraphx::make_op("contiguous"), s2); m.add_instruction(pass_standard_op{}, c1, c2); run_pass(m); EXPECT(std::count_if( m.begin(), m.end(), [](auto&& ins) { return ins.name() == "contiguous"; }) == 1); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_convert_test.cpp000066400000000000000000000300651510465702400227750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::eliminate_convert{}, migraphx::dead_code_elimination{}}); } TEST_CASE(nop_convert) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto t = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), x); m0.add_return({t}); } run_pass(m0); migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); m1.add_return({x}); } EXPECT(m0 == m1); } TEST_CASE(nested_convert0) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto a = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto b = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), a); m0.add_return({b}); } run_pass(m0); migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); m1.add_return({x}); } EXPECT(m0 == m1); } TEST_CASE(nested_convert1) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto a = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto b = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), a); m0.add_return({b}); } run_pass(m0); migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); auto a = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), x); m1.add_return({a}); } EXPECT(m0 == m1); } TEST_CASE(nested3_convert0) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto a = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto b = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), a); auto c = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), b); m0.add_return({c}); } run_pass(m0); migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); auto a = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); m1.add_return({a}); } EXPECT(m0 == m1); } TEST_CASE(nested3_convert1) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto a = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto b = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), a); auto c = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), b); m0.add_return({c}); } run_pass(m0); migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); auto a = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), x); m1.add_return({a}); } EXPECT(m0 == m1); } TEST_CASE(nested3_convert2) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto a = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto b = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), a); auto c = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::half_type)}}), b); m0.add_return({c}); } run_pass(m0); migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); m1.add_return({x}); } EXPECT(m0 == m1); } TEST_CASE(nested3_nop_convert) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto a = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto b = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), a); auto c = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::half_type)}}), b); m0.add_return({c}); } run_pass(m0); migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); m1.add_return({x}); } EXPECT(m0 == m1); } TEST_CASE(nested_branch_convert0) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto a = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto b = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::half_type)}}), a); auto c = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), b); auto d = m0.add_instruction(migraphx::make_op("add"), a, c); m0.add_return({d}); } run_pass(m0); // TODO: Less than optimal end result, would need to look horizontally to reduce to a single // convert migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); auto a = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto b = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto d = m1.add_instruction(migraphx::make_op("add"), a, b); m1.add_return({d}); } EXPECT(m0 == m1); } TEST_CASE(nested_branch_convert1) { migraphx::module m0; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m0.add_parameter("x", s); auto dbl_convert0 = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto half_convert0 = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::half_type)}}), dbl_convert0); auto dbl_convert1 = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), half_convert0); auto cos_ins = m0.add_instruction(migraphx::make_op("cos"), half_convert0); auto dbl_convert2 = m0.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), cos_ins); auto add_ins = m0.add_instruction(migraphx::make_op("add"), dbl_convert2, dbl_convert1); m0.add_return({add_ins}); } run_pass(m0); migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::half_type, {1, 2, 3}}; auto x = m1.add_parameter("x", s); auto dbl_convert0 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), x); auto cos_ins = m1.add_instruction(migraphx::make_op("cos"), x); auto dbl_convert1 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), cos_ins); auto add_ins = m1.add_instruction(migraphx::make_op("add"), dbl_convert1, dbl_convert0); m1.add_return({add_ins}); } EXPECT(m0 == m1); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_data_type_test.cpp000066400000000000000000000072161510465702400232710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static void run_pass(migraphx::module& m, std::set types) { migraphx::run_passes( m, {migraphx::eliminate_data_type{std::move(types), migraphx::shape::float_type}, migraphx::eliminate_identity{}, migraphx::dead_code_elimination{}}); } TEST_CASE(simple) { migraphx::shape s{migraphx::shape::int8_type, {2, 2}}; migraphx::module mm1; { auto x = mm1.add_parameter("x", s); auto y = mm1.add_parameter("y", s); mm1.add_instruction(migraphx::make_op("add"), x, y); } run_pass(mm1, {migraphx::shape::int8_type}); migraphx::module mm2; { auto x = mm2.add_parameter("x", s); auto y = mm2.add_parameter("y", s); auto floatx = mm2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); auto floaty = mm2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), y); auto add = mm2.add_instruction(migraphx::make_op("add"), floatx, floaty); mm2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), add); } EXPECT(mm1 == mm2); } TEST_CASE(quant) { migraphx::shape s{migraphx::shape::int8_type, {2, 2}}; migraphx::module mm1; { auto x = mm1.add_parameter("x", s); auto y = mm1.add_parameter("y", s); mm1.add_instruction(migraphx::make_op("quant_dot"), x, y); } run_pass(mm1, {migraphx::shape::int8_type}); migraphx::module mm2; { auto x = mm2.add_parameter("x", s); auto y = mm2.add_parameter("y", s); auto floatx = mm2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); auto floaty = mm2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), y); auto add = mm2.add_instruction(migraphx::make_op("dot"), floatx, floaty); mm2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int32_type}}), add); } EXPECT(mm1 == mm2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_identity_test.cpp000066400000000000000000000075311510465702400231500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::eliminate_identity{}}); } static void run_pass(migraphx::program& p) { run_pass(*p.get_main_module()); } TEST_CASE(simple_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto one_identity = mm->add_instruction(migraphx::make_op("identity"), one); auto two = mm->add_literal(2); auto two_identity = mm->add_instruction(migraphx::make_op("identity"), two); mm->add_instruction(migraphx::make_op("add"), one_identity, two_identity); run_pass(p); EXPECT(std::none_of(mm->begin(), mm->end(), [](const migraphx::instruction& ins) { return ins.name() == "identity"; })); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); } TEST_CASE(simple_test_end) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto ans = mm->add_instruction(migraphx::make_op("add"), one, two); mm->add_instruction(migraphx::make_op("identity"), ans); run_pass(p); EXPECT(std::none_of(mm->begin(), mm->end(), [](const migraphx::instruction& ins) { return ins.name() == "identity"; })); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); } TEST_CASE(simple_test_end_dependency) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1.0); auto two = mm->add_literal(2.0); auto three = mm->add_literal(3.0); auto ans = mm->add_instruction(migraphx::make_op("add"), one, two); mm->add_instruction(migraphx::make_op("add"), ans, three); mm->add_instruction(migraphx::make_op("identity"), ans); run_pass(p); EXPECT(std::any_of(mm->begin(), mm->end(), [](const migraphx::instruction& ins) { return ins.name() == "identity"; })); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3.0}); } TEST_CASE(input_first_ins) { migraphx::module m1; { m1.add_parameter("x", {migraphx::shape::float_type}); auto lit = m1.add_literal(0); m1.add_instruction(migraphx::make_op("identity"), lit); } run_pass(m1); migraphx::module m2; { m2.add_literal(0); m2.add_parameter("x", {migraphx::shape::float_type}); } EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_pad_test.cpp000066400000000000000000000122471510465702400220630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes( m, {migraphx::normalize_ops{}, migraphx::eliminate_pad{}, migraphx::dead_code_elimination{}}); } static migraphx::instruction_ref create_im2col(migraphx::instruction_ref& l_img, size_t channels, migraphx::module& m) { size_t f[2] = {1, 1}; std::vector weights(channels * f[0] * f[1]); migraphx::shape s_weights{migraphx::shape::int32_type, {1, channels, f[0], f[1]}}; auto l_weights = m.add_literal(migraphx::literal{s_weights, weights}); return m.add_instruction(migraphx::make_op("im2col"), l_img, l_weights); } static migraphx::instruction_ref create_conv(migraphx::instruction_ref& l_img, size_t channels, migraphx::module& m, migraphx::op::padding_mode_t padding_mode = migraphx::op::padding_mode_t::default_) { migraphx::shape s_weights{migraphx::shape::int32_type, {4, channels, 3, 3}}; std::vector weights(4 * channels * 3 * 3); auto l_weights = m.add_literal(migraphx::literal{s_weights, weights}); return m.add_instruction( migraphx::make_op("convolution", {{"padding_mode", padding_mode}}), l_img, l_weights); } TEST_CASE(rewrite_pad) { migraphx::module m; size_t img_dim[2] = {2, 2}; size_t channels = 1; std::vector input(channels * img_dim[0] * img_dim[1]); std::iota(input.begin(), input.end(), 0); migraphx::shape s_img{migraphx::shape::int32_type, {1, channels, img_dim[0], img_dim[1]}}; auto l_img = m.add_literal(migraphx::literal{s_img, input}); auto padded_img = m.add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 1, 1, 0, 0, 1, 1}}}), l_img); auto l0 = create_im2col(padded_img, channels, m); auto l1 = create_conv(padded_img, channels, m); auto l2 = m.add_instruction( migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}}), padded_img); m.add_instruction(migraphx::make_op("identity"), l0, l1, l2); auto s0 = l0->get_shape(); auto s1 = l1->get_shape(); auto s2 = l2->get_shape(); run_pass(m); EXPECT(l0->get_shape() == s0); EXPECT(l1->get_shape() == s1); EXPECT(l2->get_shape() == s2); auto op0 = l0->get_operator().to_value(); auto om1 = l1->get_operator().to_value(); auto om2 = l2->get_operator().to_value(); EXPECT(op0["padding"].to_vector() == std::vector{1, 1, 1, 1}); EXPECT(om1["padding"].to_vector() == std::vector{1, 1, 1, 1}); EXPECT(om2["padding"].to_vector() == std::vector{1, 1, 1, 1}); EXPECT(std::none_of( m.begin(), m.end(), [](const migraphx::instruction& ins) { return ins.name() == "pad"; })); } TEST_CASE(rewrite_pad_im2col_asymmetric) { migraphx::module m; size_t img_dim[2] = {2, 2}; size_t channels = 1; std::vector input(channels * img_dim[0] * img_dim[1]); std::iota(input.begin(), input.end(), 0); migraphx::shape s_img{migraphx::shape::int32_type, {1, channels, img_dim[0], img_dim[1]}}; auto l_img = m.add_literal(migraphx::literal{s_img, input}); auto padded_img = m.add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 0, 0, 0, 2, 2}}}), l_img); auto l0 = create_im2col(padded_img, channels, m); auto s0 = l0->get_shape(); run_pass(m); EXPECT(l0->get_shape() == s0); auto op0 = l0->get_operator().to_value(); EXPECT(op0["padding"].to_vector() == std::vector{0, 0, 2, 2}); run_pass(m); EXPECT(std::none_of( m.begin(), m.end(), [](const migraphx::instruction& ins) { return ins.name() == "pad"; })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eliminate_zero_point_test.cpp000066400000000000000000000371221510465702400235060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/shape.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { run_passes(m, {migraphx::simplify_algebra{}}); } static migraphx::instruction_ref add_quantize_op(migraphx::module& m, const std::string& name, migraphx::instruction_ref x, migraphx::instruction_ref scale, migraphx::instruction_ref shift) { auto lens = x->get_shape().lens(); auto scale_mb = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), scale); auto shift_mb = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), shift); return m.add_instruction(migraphx::make_op(name), x, scale_mb, shift_mb); } static migraphx::instruction_ref add_quantize_op(migraphx::module& m, const std::string& name, migraphx::instruction_ref x, migraphx::instruction_ref scale, migraphx::shape::type_t out_type = migraphx::shape::int8_type) { auto lens = x->get_shape().lens(); auto scale_mb = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), scale); auto op = migraphx::make_op(name); auto op_val = op.to_value(); if(name == "quantizelinear") { op_val["out_type"] = to_value(out_type); } return m.add_instruction(migraphx::make_op(name, op_val), x, scale_mb); } static migraphx::instruction_ref add_scale_mul(migraphx::module& m, migraphx::instruction_ref scale1, migraphx::instruction_ref scale2, const std::vector& out_lens) { auto mul_ins = m.add_instruction(migraphx::make_op("mul"), scale1, scale2); if(mul_ins->get_shape().lens() != out_lens) { mul_ins = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", out_lens}}), mul_ins); } return mul_ins; } static migraphx::instruction_ref init_zero_point(migraphx::module& m, migraphx::instruction_ref q_ins) { auto zp = m.add_literal(migraphx::literal{migraphx::shape{q_ins->get_shape().type()}, {0}}); return m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", q_ins->get_shape().lens()}}), zp); } TEST_CASE(quantizelinear_ins_no_zp) { migraphx::shape s{migraphx::shape::float_type, {100, 100}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto scale = m1.add_literal(0.5f); auto q_ins = add_quantize_op(m1, "quantizelinear", x, scale); m1.add_return({q_ins}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(quantizelinear_ins_with_zp) { migraphx::shape s{migraphx::shape::float_type, {100, 100}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto scale = m1.add_literal(0.5f); auto zero_point = m1.add_literal(std::int8_t{0}); auto q_ins = add_quantize_op(m1, "quantizelinear", x, scale, zero_point); m1.add_return({q_ins}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto scale = m2.add_literal(0.5f); auto q_ins = add_quantize_op(m2, "quantizelinear", x, scale, migraphx::shape::int8_type); m2.add_return({q_ins}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(quantizelinear_ins_multi_zp_use) { migraphx::shape s{migraphx::shape::float_type, {100, 100}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", migraphx::shape{migraphx::shape::int8_type, s.lens()}); auto scale = m1.add_literal(0.5f); auto zero_point = m1.add_literal(std::int8_t{0}); auto q_ins = add_quantize_op(m1, "quantizelinear", x, scale, zero_point); auto add_ins = migraphx::add_common_op(m1, migraphx::make_op("add"), {zero_point, y}); auto sub_ins = m1.add_instruction(migraphx::make_op("sub"), {add_ins, q_ins}); m1.add_return({sub_ins}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", migraphx::shape{migraphx::shape::int8_type, s.lens()}); auto scale = m2.add_literal(0.5f); auto q_ins = add_quantize_op(m2, "quantizelinear", x, scale, migraphx::shape::int8_type); auto sub_ins = m2.add_instruction(migraphx::make_op("sub"), {y, q_ins}); m2.add_return({sub_ins}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dequantizelinear_ins_no_zp) { migraphx::shape s{migraphx::shape::int32_type, {100, 100}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto scale = m1.add_literal(0.5f); auto dq_ins = add_quantize_op(m1, "dequantizelinear", x, scale); m1.add_return({dq_ins}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dequantizelinear_ins_with_zp) { migraphx::shape s{migraphx::shape::int32_type, {100, 100}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto scale = m1.add_literal(0.5f); auto zero_point = m1.add_literal(std::int32_t{0}); auto dq_ins = add_quantize_op(m1, "dequantizelinear", x, scale, zero_point); m1.add_return({dq_ins}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto scale = m2.add_literal(0.5f); auto dq_ins = add_quantize_op(m2, "dequantizelinear", x, scale); m2.add_return({dq_ins}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dequantizelinear_ins_neg_zero) { migraphx::shape s{migraphx::shape::float_type, {100, 100}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto scale = m1.add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {1}, {0}}, {1.125f}}); auto zero_point = m1.add_literal(-0.0f); auto dq_ins = add_quantize_op(m1, "dequantizelinear", x, scale, zero_point); m1.add_return({dq_ins}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto scale = m2.add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {1}, {0}}, {1.125f}}); auto dq_ins = add_quantize_op(m2, "dequantizelinear", x, scale); m2.add_return({dq_ins}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale1 = m1.add_literal(0.5f); auto scale2 = m1.add_literal(1.5f); auto zero1 = m1.add_literal(std::int8_t{0}); auto zero2 = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale1, zero1); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale2, zero2); auto dot = m1.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m1, scale1, scale2, dot->get_shape().lens()); auto out_zp = init_zero_point(m1, dot); auto d3 = add_quantize_op(m1, "dequantizelinear", dot, out_scale, out_zp); m1.add_return({d3}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale1 = m2.add_literal(0.5f); auto scale2 = m2.add_literal(1.5f); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale1, migraphx::shape::int8_type); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale2, migraphx::shape::int8_type); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale1, scale2, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_asymmetric_first_arg) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale1 = m1.add_literal(0.5f); auto scale2 = m1.add_literal(1.5f); auto zp1 = m1.add_literal(std::int8_t{1}); auto zp2 = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale1, zp1); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale2, zp2); auto dot = m1.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m1, scale1, scale2, dot->get_shape().lens()); auto out_zp = init_zero_point(m1, dot); auto zp1_bc = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", t1->get_shape().lens()}}), zp1); auto zp_term = m1.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, q2); out_zp = m1.add_instruction(migraphx::make_op("add"), out_zp, zp_term); auto d3 = add_quantize_op(m1, "dequantizelinear", dot, out_scale, out_zp); m1.add_return({d3}); } run_pass(m1); migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale1 = m2.add_literal(0.5f); auto scale2 = m2.add_literal(1.5f); auto zp1 = m2.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale1, zp1); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale2, migraphx::shape::int8_type); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale1, scale2, dot->get_shape().lens()); auto zp1_bc = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", t1->get_shape().lens()}}), zp1); auto zp_term = m2.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, q2); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale, zp_term); m2.add_return({d3}); } EXPECT(m1 == m2); } TEST_CASE(dot_asymmetric_both_args) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale1 = m1.add_literal(0.5f); auto scale2 = m1.add_literal(1.5f); auto zp1 = m1.add_literal(std::int8_t{2}); auto zp2 = m1.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale1, zp1); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale2, zp2); auto dot = m1.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m1, scale1, scale2, dot->get_shape().lens()); auto out_zp = init_zero_point(m1, dot); auto zp1_bc = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", t1->get_shape().lens()}}), zp1); auto zp2_bc = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", t2->get_shape().lens()}}), zp2); auto zp_term1 = m1.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, q2); out_zp = m1.add_instruction(migraphx::make_op("add"), out_zp, zp_term1); auto zp_term2 = m1.add_instruction(migraphx::make_op("quant_dot"), q1, zp2_bc); out_zp = m1.add_instruction(migraphx::make_op("add"), out_zp, zp_term2); auto zp_term3 = m1.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, zp2_bc); out_zp = m1.add_instruction(migraphx::make_op("sub"), out_zp, zp_term3); auto d3 = add_quantize_op(m1, "dequantizelinear", dot, out_scale, out_zp); m1.add_return({d3}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale1 = m2.add_literal(0.5f); auto scale2 = m2.add_literal(1.5f); auto zp1 = m2.add_literal(std::int8_t{2}); auto zp2 = m2.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale1, zp1); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale2, zp2); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale1, scale2, dot->get_shape().lens()); auto zp1_bc = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", t1->get_shape().lens()}}), zp1); auto zp2_bc = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", t2->get_shape().lens()}}), zp2); auto zp_term1 = m2.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, q2); auto zp_term2 = m2.add_instruction(migraphx::make_op("quant_dot"), q1, zp2_bc); auto out_zp = m2.add_instruction(migraphx::make_op("add"), zp_term1, zp_term2); auto zp_term3 = m2.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, zp2_bc); out_zp = m2.add_instruction(migraphx::make_op("sub"), out_zp, zp_term3); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale, out_zp); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/eval_test.cpp000066400000000000000000000412331510465702400202140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include "test.hpp" #include struct id_target { struct context { void finish() const {} }; migraphx::context ctx = context{}; std::string name() const { return "id"; } std::vector get_passes(migraphx::context&, const migraphx::compile_options&) const { return {}; } migraphx::context get_context() const { return ctx; } }; struct id_ctx_op { std::string name() const { return ""; } migraphx::argument compute(id_target::context&, const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } int output_alias(const std::vector&) const { return 0; } }; struct id_ctx_final_op { std::string name() const { return "id_ctx_final_op"; } migraphx::argument compute(const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } void finalize(id_target::context&, const migraphx::shape&, const std::vector&) { } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } int output_alias(const std::vector&) const { return 0; } }; struct reverse_pass { std::string name() const { return "reverse_pass"; } void apply(migraphx::module& m) const { std::reverse(m.begin(), m.end()); } }; struct reverse_target { std::string name() const { return "reverse"; } std::vector get_passes(migraphx::context&, const migraphx::compile_options&) const { return {reverse_pass{}}; } migraphx::context get_context() const { return {}; } }; struct invert_pass { std::string name() const { return "invert_pass"; } void apply(migraphx::module& m) const { for(auto ins : migraphx::iterator_for(m)) { if(ins->name() == "sum") { m.replace_instruction(ins, minus_op{}, ins->inputs()); } else if(ins->name() == "minus") { m.replace_instruction(ins, sum_op{}, ins->inputs()); } } } }; struct invert_target { std::string name() const { return "invert"; } std::vector get_passes(migraphx::context&, const migraphx::compile_options&) const { return {invert_pass{}}; } migraphx::context get_context() const { return {}; } }; struct double_invert_target { std::string name() const { return "double_invert"; } std::vector get_passes(migraphx::context&, const migraphx::compile_options&) const { return {invert_pass{}, invert_pass{}}; } migraphx::context get_context() const { return {}; } }; TEST_CASE(literal_test1) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(literal_test2) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum1 = mm->add_instruction(migraphx::make_op("add"), one, two); mm->add_instruction(migraphx::make_op("add"), sum1, two); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{5}); EXPECT(result != migraphx::literal{3}); } TEST_CASE(print_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::int32_type}); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), x, two); std::stringstream ss; ss << p; std::string s = ss.str(); EXPECT(not s.empty()); } TEST_CASE(param_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::int32_type}); auto y = mm->add_parameter("y", {migraphx::shape::int32_type}); mm->add_instruction(migraphx::make_op("add"), x, y); auto result = p.eval({{"x", migraphx::literal{1}.get_argument()}, {"y", migraphx::literal{2}.get_argument()}}) .back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(param_error_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::int32_type}); auto y = mm->add_parameter("y", {migraphx::shape::int32_type}); mm->add_instruction(sum_op{}, x, y); EXPECT(test::throws( [&] { p.eval({{"x", migraphx::literal{1}.get_argument()}}); }, "Parameter not found: y")); } TEST_CASE(param_error_shape_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::int32_type, {1, 1}}); auto y = mm->add_parameter("y", {migraphx::shape::int32_type, {1, 1}}); mm->add_instruction(migraphx::make_op("add"), x, y); EXPECT(test::throws( [&] { p.eval({ {"x", migraphx::literal{1}.get_argument()}, {"y", migraphx::literal{{migraphx::shape::int32_type, {1, 1}}, {2}}.get_argument()}, }); }, "Incorrect shape {int32_type, {1}, {0}} for parameter: x")); } TEST_CASE(get_param1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {1, 2}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("add"), x, y); EXPECT(p.get_parameter("x") == x); EXPECT(p.get_parameter("y") == y); EXPECT(p.get_parameter("nonexistent") == mm->end()); } TEST_CASE(get_param2) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); EXPECT(p.get_parameter("nonexistent") == mm->end()); } TEST_CASE(get_param_shapes) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {1, 2}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("add"), x, y); auto m = p.get_parameter_shapes(); EXPECT(m.count("nonexistent") == 0); EXPECT(m.at("x") == s); EXPECT(m.at("y") == s); } TEST_CASE(replace_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum = mm->add_instruction(migraphx::make_op("add"), one, two); mm->replace_instruction(sum, migraphx::make_op("sub"), two, one); EXPECT(p.validate() == mm->end()); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{1}); EXPECT(result != migraphx::literal{3}); } TEST_CASE(replace_ins_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum = mm->add_instruction(migraphx::make_op("add"), one, two); auto minus = mm->add_instruction(migraphx::make_op("sub"), two, one); mm->replace_instruction(sum, minus); EXPECT(p.validate() == mm->end()); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{1}); EXPECT(result != migraphx::literal{3}); } TEST_CASE(replace_ins_test2) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum = mm->add_instruction(migraphx::make_op("add"), one, two); auto minus = mm->add_instruction(migraphx::make_op("sub"), two, one); mm->add_instruction(pass_op{}, minus); mm->replace_instruction(two, sum); EXPECT(p.validate() == mm->end()); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{2}); EXPECT(result != migraphx::literal{3}); } TEST_CASE(replace_op_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum = mm->add_instruction(migraphx::make_op("add"), two, one); sum->replace(migraphx::make_op("sub")); EXPECT(p.validate() == mm->end()); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{1}); EXPECT(result != migraphx::literal{3}); } TEST_CASE(replace_op_recompute_shape_throw) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum = mm->add_instruction(migraphx::make_op("add"), one, two); EXPECT(test::throws([&] { sum->replace(unary_pass_op{}); })); } TEST_CASE(insert_replace_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum1 = mm->add_instruction(migraphx::make_op("add"), one, two); mm->add_instruction(migraphx::make_op("add"), sum1, two); auto sum0 = mm->insert_instruction(sum1, migraphx::make_op("add"), two, two); mm->replace_instruction(sum1, migraphx::make_op("sub"), sum0, two); EXPECT(p.validate() == mm->end()); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{4}); EXPECT(result != migraphx::literal{5}); } TEST_CASE(remove_test1) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto sum = mm->add_instruction(migraphx::make_op("add"), one, two); auto removed = mm->add_instruction(migraphx::make_op("sub"), sum, one); mm->remove_instruction(removed); EXPECT(p.validate() == mm->end()); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{1}); } TEST_CASE(remove_test2) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto removed = mm->add_instruction(migraphx::make_op("sub"), two, one); mm->add_instruction(migraphx::make_op("add"), one, two); mm->remove_instruction(removed); EXPECT(p.validate() == mm->end()); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{1}); } TEST_CASE(target_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); p.compile(id_target{}); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(invert_target_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(sum_op{}, two, one); p.compile(invert_target{}); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{1}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(double_invert_target_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(sum_op{}, two, one); p.compile(double_invert_target{}); auto result = p.eval({}).back(); EXPECT(result == migraphx::literal{3}); EXPECT(result != migraphx::literal{4}); } TEST_CASE(reverse_target_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(sum_op{}, one, two); EXPECT(test::throws([&] { p.compile(reverse_target{}); })); } // Check that the program doesnt modify the context directly, and only the operators modify the // context TEST_CASE(eval_context1) { migraphx::program p; auto* mm = p.get_main_module(); id_target t{}; EXPECT(is_shared(t.ctx, t.get_context())); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(sum_op{}, one, two); p.compile(t); EXPECT(is_shared(t.ctx, p.get_context())); std::ignore = p.eval({}).back(); EXPECT(is_shared(t.ctx, p.get_context())); } TEST_CASE(eval_context2) { migraphx::program p; auto* mm = p.get_main_module(); id_target t{}; EXPECT(is_shared(t.ctx, t.get_context())); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(id_ctx_op{}, one, two); p.compile(t); EXPECT(is_shared(t.ctx, p.get_context())); std::ignore = p.eval({}).back(); // id_ctx_op will modify the context EXPECT(not is_shared(t.ctx, p.get_context())); } TEST_CASE(eval_context3) { migraphx::program p; auto* mm = p.get_main_module(); id_target t{}; EXPECT(is_shared(t.ctx, t.get_context())); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(id_ctx_final_op{}, one, two); p.compile(t); // Finalizer will modify the context EXPECT(not is_shared(t.ctx, p.get_context())); auto ctx = p.get_context(); std::ignore = p.eval({}).back(); EXPECT(is_shared(ctx, p.get_context())); EXPECT(not is_shared(t.ctx, p.get_context())); } struct cout_redirect { cout_redirect() = delete; cout_redirect(const cout_redirect&) = delete; template cout_redirect(T& stream) : old(std::cout.rdbuf(stream.rdbuf())) { } ~cout_redirect() { std::cout.rdbuf(old); } private: std::streambuf* old; }; template static std::string capture_output(F f) { std::stringstream ss; cout_redirect cr{ss}; f(); return ss.str(); } TEST_CASE(debug_print_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); std::vector onev = {one}; migraphx::program p2; auto* mm2 = p2.get_main_module(); auto one2 = mm2->add_literal(1); auto program_out = migraphx::trim(capture_output([&] { mm->debug_print(); })); auto ins_out = migraphx::trim(capture_output([&] { mm->debug_print(one); })); auto inss_out = migraphx::trim(capture_output([&] { mm->debug_print(onev); })); auto end_out = migraphx::trim(capture_output([&] { mm->debug_print(mm->end()); })); auto p2_ins_out = migraphx::trim(capture_output([&] { mm->debug_print(one2); })); EXPECT(program_out == ins_out); EXPECT(inss_out == ins_out); EXPECT(end_out == "End instruction"); EXPECT(p2_ins_out == "Instruction not part of module"); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fileutils.cpp000066400000000000000000000066721510465702400202360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include namespace fs = migraphx::fs; constexpr std::string_view baze_name{"test"}; constexpr std::string_view txt{".txt"}; constexpr std::string_view bz2{".bz2"}; constexpr std::string_view separator{": "}; #ifdef _WIN32 constexpr std::string_view executable_postfix{".exe"}; constexpr std::string_view library_prefix{""}; constexpr std::string_view shared_object_postfix{".dll"}; constexpr std::string_view static_library_postfix{".lib"}; constexpr std::string_view object_file_postfix{".obj"}; #else constexpr std::string_view executable_postfix{""}; constexpr std::string_view library_prefix{"lib"}; constexpr std::string_view shared_object_postfix{".so"}; constexpr std::string_view static_library_postfix{".a"}; constexpr std::string_view object_file_postfix{".o"}; #endif TEST_CASE(executable_filename) { auto name = migraphx::make_executable_filename(baze_name); EXPECT(name == std::string{baze_name}.append(executable_postfix)); } TEST_CASE(shared_object_filename) { auto name = migraphx::make_shared_object_filename(baze_name); EXPECT(name == std::string{library_prefix}.append(baze_name).append(shared_object_postfix)); } TEST_CASE(object_filename) { auto name = migraphx::make_object_file_filename(baze_name); EXPECT(name == std::string{baze_name}.append(object_file_postfix)); } TEST_CASE(static_library_filename) { auto name = migraphx::make_static_library_filename(baze_name); EXPECT(name == std::string{library_prefix}.append(baze_name).append(static_library_postfix)); } TEST_CASE(append_to_string) { // 'using namespace' required for '+' operator using namespace migraphx::MIGRAPHX_INLINE_NS; // NOLINT auto cwd = fs::current_path(); std::string prefix{baze_name}; auto s1 = prefix + separator + cwd; EXPECT(s1 == prefix + separator + cwd.string()); auto s2 = cwd + std::string{separator} + prefix; EXPECT(s2 == cwd.string() + separator + prefix); } TEST_CASE(append_file_extension) { auto name = fs::path{baze_name}.replace_extension(txt); auto updated = migraphx::MIGRAPHX_INLINE_NS::append_extension(name, bz2); EXPECT(updated == std::string{baze_name}.append(txt).append(bz2)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/float32.cpp000066400000000000000000000052471510465702400175050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" #include #include using fp32 = migraphx::generic_float<23, 8>; template static bool bit_equal(const T& x, const U& y) { static_assert(sizeof(T) == sizeof(U)); using type = std::array; return migraphx::bit_cast(x) == migraphx::bit_cast(y); } // NOLINTNEXTLINE #define MIGRAPHX_CHECK_FLOAT(x, y) \ CHECK(bit_equal(x, y)); \ CHECK(bit_equal(x, y.to_float())); \ CHECK(bit_equal(fp32{x}, y)); \ CHECK(bit_equal(fp32{x}.to_float(), y.to_float())) TEST_CASE(fp32_values_working) { MIGRAPHX_CHECK_FLOAT(1.0f, fp32{1.0f}); MIGRAPHX_CHECK_FLOAT(-1.0f, fp32{-1.0f}); MIGRAPHX_CHECK_FLOAT(std::numeric_limits::min(), fp32::min()); MIGRAPHX_CHECK_FLOAT(std::numeric_limits::lowest(), fp32::lowest()); MIGRAPHX_CHECK_FLOAT(std::numeric_limits::max(), fp32::max()); MIGRAPHX_CHECK_FLOAT(std::numeric_limits::epsilon(), fp32::epsilon()); MIGRAPHX_CHECK_FLOAT(std::numeric_limits::denorm_min(), fp32::denorm_min()); MIGRAPHX_CHECK_FLOAT(std::numeric_limits::infinity(), fp32::infinity()); MIGRAPHX_CHECK_FLOAT(std::numeric_limits::quiet_NaN(), fp32::qnan()); MIGRAPHX_CHECK_FLOAT(std::numeric_limits::signaling_NaN(), fp32::snan()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/float_equal.cpp000066400000000000000000000143071510465702400205240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include "test.hpp" #include template struct float_equal_expression { T lhs; U rhs; operator bool() const { return migraphx::float_equal(lhs, rhs); } bool operator not() const { return not bool(*this); } friend std::ostream& operator<<(std::ostream& s, const float_equal_expression& self) { s << "migraphx::float_equal(" << self.lhs << ", " << self.rhs << ")"; return s; } }; template static auto test_float_equal(T x, U y) { return test::make_lhs_expression(float_equal_expression{x, y}); } template static void test_equality() { auto x1 = T(0.125); auto x2 = U(0.0); auto x3 = U(1.0); EXPECT(test_float_equal(x1, x1)); EXPECT(test_float_equal(x2, x2)); EXPECT(test_float_equal(x3, x3)); EXPECT(not test_float_equal(x1, x2)); EXPECT(not test_float_equal(x2, x1)); EXPECT(not test_float_equal(x1, x3)); EXPECT(not test_float_equal(x3, x1)); EXPECT(not test_float_equal(x2, x3)); EXPECT(not test_float_equal(x3, x2)); } TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); TEST_CASE_REGISTER(test_equality); template static void test_limits() { auto max1 = std::numeric_limits::max(); auto max2 = std::numeric_limits::max(); auto min1 = std::numeric_limits::lowest(); auto min2 = std::numeric_limits::lowest(); EXPECT(test_float_equal(max1, max1)); EXPECT(test_float_equal(max2, max2)); EXPECT(not test_float_equal(max1, max2)); EXPECT(not test_float_equal(max2, max1)); EXPECT(test_float_equal(min1, min1)); EXPECT(test_float_equal(min2, min2)); EXPECT(not test_float_equal(min1, min2)); EXPECT(not test_float_equal(min2, min1)); EXPECT(not test_float_equal(max1, min1)); EXPECT(not test_float_equal(min1, max1)); EXPECT(not test_float_equal(max2, min2)); EXPECT(not test_float_equal(min2, max2)); EXPECT(not test_float_equal(max1, min2)); EXPECT(not test_float_equal(min2, max1)); EXPECT(not test_float_equal(max2, min1)); EXPECT(not test_float_equal(min1, max2)); } TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); TEST_CASE_REGISTER(test_limits); #ifndef _WIN32 // On Windows, types int and long have the same min and max values. TEST_CASE_REGISTER(test_limits); #endif TEST_CASE_REGISTER(test_limits); int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fp4_casts.cpp000066400000000000000000000167431510465702400201240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" #include #include namespace test_fp4_casts { static constexpr std::array e2m1_lut = { 0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, -0.0, -0.5, -1.0, -1.5, -2.0, -3.0, -4.0, -6.0}; } // namespace test_fp4_casts static float fp4e2m1_to_fp32_value(uint8_t input) { return test_fp4_casts::e2m1_lut[input]; } TEST_CASE(test_fp4_to_float) { std::vector bit_vals(16); std::iota(bit_vals.begin(), bit_vals.end(), 0); EXPECT(std::all_of(bit_vals.begin(), bit_vals.end(), [](uint8_t bit_val) { float float_val = migraphx::fp4_to_float(bit_val); return migraphx::float_equal(float_val, fp4e2m1_to_fp32_value(bit_val)); })); } TEST_CASE(test_fp4_to_fp8) { std::vector bit_vals(16); std::iota(bit_vals.begin(), bit_vals.end(), 0); EXPECT(std::all_of(bit_vals.begin(), bit_vals.end(), [](uint8_t bit_val) { auto float_val = migraphx::fp4_to_fp8(bit_val); return migraphx::float_equal(float_val, fp4e2m1_to_fp32_value(bit_val)); })); } TEST_CASE(test_constexpr_fp4_to_float) { constexpr std::array res_array = {migraphx::fp4_to_float(0x0), migraphx::fp4_to_float(0x1), migraphx::fp4_to_float(0x2), migraphx::fp4_to_float(0x3), migraphx::fp4_to_float(0x4), migraphx::fp4_to_float(0x5), migraphx::fp4_to_float(0x6), migraphx::fp4_to_float(0x7), migraphx::fp4_to_float(0x8), migraphx::fp4_to_float(0x9), migraphx::fp4_to_float(0xA), migraphx::fp4_to_float(0xB), migraphx::fp4_to_float(0xC), migraphx::fp4_to_float(0xD), migraphx::fp4_to_float(0xE), migraphx::fp4_to_float(0xF)}; EXPECT(std::equal(res_array.begin(), res_array.end(), test_fp4_casts::e2m1_lut.begin())); } TEST_CASE(test_constexpr_fp4_to_fp8) { constexpr std::array res_array = {migraphx::fp4_to_fp8(0x0), migraphx::fp4_to_fp8(0x1), migraphx::fp4_to_fp8(0x2), migraphx::fp4_to_fp8(0x3), migraphx::fp4_to_fp8(0x4), migraphx::fp4_to_fp8(0x5), migraphx::fp4_to_fp8(0x6), migraphx::fp4_to_fp8(0x7), migraphx::fp4_to_fp8(0x8), migraphx::fp4_to_fp8(0x9), migraphx::fp4_to_fp8(0xA), migraphx::fp4_to_fp8(0xB), migraphx::fp4_to_fp8(0xC), migraphx::fp4_to_fp8(0xD), migraphx::fp4_to_fp8(0xE), migraphx::fp4_to_fp8(0xF)}; EXPECT(std::equal(res_array.begin(), res_array.end(), test_fp4_casts::e2m1_lut.begin())); } TEST_CASE(test_float_to_fp4) { std::vector> test_vals = { {10.f, 0x7}, {-20.f, 0xF}, {0.11, 0x0}, {-0.11, 0x8}, {2.5, 0x4}, {-2.5, 0xC}, {2.6, 0x5}, {-2.6, 0xD}, {0.f, 0x0}, {-0.f, 0x8}, {4.212, 0x6}, {0.387, 0x1}, {0.00128, 0x0}, {-0.5, 0x9}, {-5.25, 0xF}, {0.25, 0x0}, {0.5, 0x1}, {0.75, 0x2}, {1.25, 0x2}, {1.75, 0x4}, {2.5, 0x4}, {3.5, 0x6}, {5.0, 0x6}, {-0.25, 0x8}, {-0.5, 0x9}, {-0.75, 0xA}, {-1.25, 0xA}, {-1.75, 0xC}, {-2.5, 0xC}, {-3.5, 0xE}, {-5.0, 0xE}, {std::numeric_limits::infinity(), 0x7}, {-std::numeric_limits::infinity(), 0xF}, {std::numeric_limits::signaling_NaN(), 0x0}, {std::numeric_limits::quiet_NaN(), 0x0}}; EXPECT(bool{std::all_of(test_vals.begin(), test_vals.end(), [](const auto sample) { uint8_t conv = migraphx::cast_to_fp4(sample.first); uint8_t gold = sample.second; if(conv != gold) std::cout << "conv: " << int(conv) << ", gold: " << int(gold) << "\n"; return conv == gold; })}); } // Disabled test for constexpr version of float_to_fp4 // TEST_CASE(test_constexpr_float_to_fp4) //{ // constexpr std::array res_array = { // migraphx::cast_to_fp4(10.f), // migraphx::cast_to_fp4(-20.f), // migraphx::cast_to_fp4(0.11f), // migraphx::cast_to_fp4(-0.11f), // migraphx::cast_to_fp4(0.25f), // migraphx::cast_to_fp4(1.75f), // migraphx::cast_to_fp4(-0.25f), // migraphx::cast_to_fp4(-1.75f), // migraphx::cast_to_fp4(3.5f), // migraphx::cast_to_fp4(-3.5f), // migraphx::cast_to_fp4(0.00128f), // migraphx::cast_to_fp4(82910.0f), // }; // // std::array gold_array = { // 0x7, 0xF, 0x0, 0x8, 0x0, 0x4, 0x8, 0xC, 0x6, 0xE, 0x0, 0x7}; // EXPECT(std::equal(res_array.begin(), res_array.end(), gold_array.begin())); //} int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fp8_ocp_to_fnuz_test.cpp000066400000000000000000000240371510465702400223720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include using migraphx::make_op; using migraphx::shape; using migraphx::fp8::fp8e4m3fnuz; static void run_fp8_ocp_to_fnuz(migraphx::module& m) { migraphx::run_passes(m, {migraphx::fp8_ocp_to_fnuz{}, migraphx::dead_code_elimination{}}); } static void run_simplify_qdq(migraphx::module& m) { run_passes(m, {migraphx::simplify_qdq{}, migraphx::dead_code_elimination{}}); } static void run_cse_pc(migraphx::module& m, const std::unordered_set& skip_ops = {}) { run_passes(m, {migraphx::eliminate_common_subexpression{}, migraphx::dead_code_elimination{}, migraphx::propagate_constant{skip_ops}, migraphx::dead_code_elimination{}}); } static auto bit_cast_and_handle_specials(migraphx::module& m, const migraphx::instruction_ref x, const migraphx::instruction_ref bits_0x80_lit, const migraphx::instruction_ref bits_0x7f_lit, const migraphx::instruction_ref bits_0xff_lit, const migraphx::instruction_ref bits_0x00_lit) { auto x_lens = x->get_shape().lens(); auto cast_input = m.add_instruction(make_op("bit_cast", {{"target_type", shape::fp8e4m3fnuz_type}}), x); auto mb_bits_0x80_lit = m.add_instruction(make_op("multibroadcast", {{"out_lens", x_lens}}), bits_0x80_lit); auto mb_bits_0x7f_lit = m.add_instruction(make_op("multibroadcast", {{"out_lens", x_lens}}), bits_0x7f_lit); auto mb_bits_0xff_lit = m.add_instruction(make_op("multibroadcast", {{"out_lens", x_lens}}), bits_0xff_lit); auto mb_zero_lit = m.add_instruction(make_op("multibroadcast", {{"out_lens", x_lens}}), bits_0x00_lit); // negative zero in fp8e4m3fn to zero in fp8e4m3fnuz // a == 0x80 ? 0x0 : a auto is_neg_zero = m.add_instruction(make_op("equal"), cast_input, mb_bits_0x80_lit); auto ret = m.add_instruction(make_op("where"), is_neg_zero, mb_zero_lit, cast_input); // positive and negative NaN in fp8e4m3fn to NaN in fp8e4m3fnuz // (a == 0x7f or a == 0xff) ? 0x80 : a auto eq_0x7f = m.add_instruction(make_op("equal"), ret, mb_bits_0x7f_lit); auto eq_0xff = m.add_instruction(make_op("equal"), ret, mb_bits_0xff_lit); auto cond = m.add_instruction(make_op("logical_or"), eq_0x7f, eq_0xff); ret = m.add_instruction(make_op("where"), cond, mb_bits_0x80_lit, ret); return ret; } static auto cast_fp8_helper(migraphx::module& m, const migraphx::instruction_ref dq_input, const migraphx::instruction_ref dq_scale, const migraphx::instruction_ref dq_zp) { auto dq_input_lens = dq_input->get_shape().lens(); std::vector bits_0x80 = {fp8e4m3fnuz(0x80, fp8e4m3fnuz::from_bits())}; std::vector bits_0x7f = {fp8e4m3fnuz(0x7f, fp8e4m3fnuz::from_bits())}; std::vector bits_0xff = {fp8e4m3fnuz(0xff, fp8e4m3fnuz::from_bits())}; std::vector bits_0x00 = {fp8e4m3fnuz(0x00, fp8e4m3fnuz::from_bits())}; auto bits_0x80_lit = m.add_literal(shape{shape::fp8e4m3fnuz_type, {1}, {0}}, bits_0x80); auto bits_0x7f_lit = m.add_literal(shape{shape::fp8e4m3fnuz_type, {1}, {0}}, bits_0x7f); auto bits_0xff_lit = m.add_literal(shape{shape::fp8e4m3fnuz_type, {1}, {0}}, bits_0xff); auto bits_0x00_lit = m.add_literal(shape{shape::fp8e4m3fnuz_type, {1}, {0}}, bits_0x00); auto cast_input = bit_cast_and_handle_specials( m, dq_input, bits_0x80_lit, bits_0x7f_lit, bits_0xff_lit, bits_0x00_lit); auto adj_zp = bit_cast_and_handle_specials( m, dq_zp, bits_0x80_lit, bits_0x7f_lit, bits_0xff_lit, bits_0x00_lit); auto two_lit = m.add_literal(migraphx::literal{shape{dq_scale->get_shape().type()}, {2}}); two_lit = m.add_instruction( make_op("multibroadcast", {{"out_lens", dq_scale->get_shape().lens()}}), two_lit); auto adj_dq_scale = m.add_instruction(make_op("mul"), dq_scale, two_lit); return std::vector{cast_input, adj_dq_scale, adj_zp}; } TEST_CASE(fp8_gemm_conversion) { using migraphx::fp8::fp8e4m3fn; using migraphx::fp8::fp8e4m3fnuz; std::vector data_lens = {2, 3, 8, 8}; migraphx::module m1; { auto a = m1.add_parameter("a", {migraphx::shape::float_type, data_lens}); auto b = m1.add_parameter("b", {migraphx::shape::float_type, data_lens}); auto scale = m1.add_literal(0.5f); std::vector data; data.push_back(fp8e4m3fn{0.f}); auto zero = m1.add_literal(migraphx::shape{migraphx::shape::fp8e4m3fn_type, {1}, {0}}, data); auto qa = add_quantize_op(m1, "quantizelinear", a, scale, zero); auto qb = add_quantize_op(m1, "quantizelinear", b, scale, zero); auto da = add_quantize_op(m1, "dequantizelinear", qa, qa->inputs().at(1), qa->inputs().at(2)); auto db = add_quantize_op(m1, "dequantizelinear", qb, qb->inputs().at(1), qb->inputs().at(2)); auto dot = m1.add_instruction(migraphx::make_op("dot"), da, db); m1.add_return({dot}); } run_fp8_ocp_to_fnuz(m1); // expected after fp8_ocp_to_fnuz migraphx::module m2; { auto a = m2.add_parameter("a", {migraphx::shape::float_type, data_lens}); auto b = m2.add_parameter("b", {migraphx::shape::float_type, data_lens}); auto scale = m2.add_literal(0.5f); std::vector data; data.push_back(fp8e4m3fn{0.f}); auto zero = m2.add_literal(migraphx::shape{migraphx::shape::fp8e4m3fn_type, {1}, {0}}, data); auto qa = add_quantize_op(m2, "quantizelinear", a, scale, zero); auto qb = add_quantize_op(m2, "quantizelinear", b, scale, zero); auto outs_a = cast_fp8_helper(m2, qa, scale, zero); auto adj_a = outs_a.at(0); auto mb_scales_a = m2.add_instruction(make_op("multibroadcast", {{"out_lens", data_lens}}), outs_a.at(1)); auto mb_zp_a = m2.add_instruction(make_op("multibroadcast", {{"out_lens", data_lens}}), outs_a.at(2)); auto da = m2.add_instruction(make_op("dequantizelinear"), adj_a, mb_scales_a, mb_zp_a); auto outs_b = cast_fp8_helper(m2, qb, scale, zero); auto adj_b = outs_b.at(0); auto mb_scales_b = m2.add_instruction(make_op("multibroadcast", {{"out_lens", data_lens}}), outs_b.at(1)); auto mb_zp_b = m2.add_instruction(make_op("multibroadcast", {{"out_lens", data_lens}}), outs_b.at(2)); auto db = m2.add_instruction(make_op("dequantizelinear"), adj_b, mb_scales_b, mb_zp_b); auto dot = m2.add_instruction(migraphx::make_op("dot"), da, db); m2.add_return({dot}); } EXPECT(m1 == m2); // expected after simplify_qdq migraphx::module m3; { auto a = m3.add_parameter("a", {migraphx::shape::float_type, {2, 3, 8, 8}}); auto b = m3.add_parameter("b", {migraphx::shape::float_type, {2, 3, 8, 8}}); auto scale = m3.add_literal(0.5f); std::vector data; data.push_back(fp8e4m3fn{0.f}); auto zero = m3.add_literal(migraphx::shape{migraphx::shape::fp8e4m3fn_type, {1}, {0}}, data); auto qa = add_quantize_op(m3, "quantizelinear", a, scale, zero); auto qb = add_quantize_op(m3, "quantizelinear", b, scale, zero); auto outs_a = cast_fp8_helper(m3, qa, qa->inputs().at(1), qa->inputs().at(2)); auto outs_b = cast_fp8_helper(m3, qb, qb->inputs().at(1), qb->inputs().at(2)); auto adj_qa = outs_a.at(0); auto adj_scale_a = outs_a.at(1); auto adj_qb = outs_b.at(0); auto adj_scale_b = outs_b.at(1); auto dot = m3.add_instruction(migraphx::make_op("quant_dot"), adj_qa, adj_qb); auto out_scale = add_scale_mul(m3, adj_scale_a, adj_scale_b, 1, 1, dot->get_shape().lens()); auto dq_out = add_quantize_op(m3, "dequantizelinear", dot, out_scale); m3.add_return({dq_out}); } run_simplify_qdq(m1); // running propagate constant to simplify adjustments to literals // could pass the test without, but a tedious amount of instructions to rearrange run_cse_pc(m1); run_cse_pc(m3); EXPECT(m1 == m3); m1.debug_print(); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fp8e4m3fn.cpp000066400000000000000000000267531510465702400177520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include "test.hpp" #include static float fp8e4m3fn_to_fp32_value(uint8_t input) { constexpr std::array e4m3fnuz_lut = { 0.0, 0.001953125, 0.00390625, 0.005859375, 0.0078125, 0.009765625, 0.01171875, 0.013671875, 0.015625, 0.017578125, 0.01953125, 0.021484375, 0.0234375, 0.025390625, 0.02734375, 0.029296875, 0.03125, 0.03515625, 0.0390625, 0.04296875, 0.046875, 0.05078125, 0.0546875, 0.05859375, 0.0625, 0.0703125, 0.078125, 0.0859375, 0.09375, 0.1015625, 0.109375, 0.1171875, 0.125, 0.140625, 0.15625, 0.171875, 0.1875, 0.203125, 0.21875, 0.234375, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.4375, 0.46875, 0.5, 0.5625, 0.625, 0.6875, 0.75, 0.8125, 0.875, 0.9375, 1.0, 1.125, 1.25, 1.375, 1.5, 1.625, 1.75, 1.875, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5, 3.75, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 30.0, 32.0, 36.0, 40.0, 44.0, 48.0, 52.0, 56.0, 60.0, 64.0, 72.0, 80.0, 88.0, 96.0, 104.0, 112.0, 120.0, 128.0, 144.0, 160.0, 176.0, 192.0, 208.0, 224.0, 240.0, 256.0, 288.0, 320.0, 352.0, 384.0, 416.0, 448.0, std::numeric_limits::quiet_NaN(), -0.0, -0.001953125, -0.00390625, -0.005859375, -0.0078125, -0.009765625, -0.01171875, -0.013671875, -0.015625, -0.017578125, -0.01953125, -0.021484375, -0.0234375, -0.025390625, -0.02734375, -0.029296875, -0.03125, -0.03515625, -0.0390625, -0.04296875, -0.046875, -0.05078125, -0.0546875, -0.05859375, -0.0625, -0.0703125, -0.078125, -0.0859375, -0.09375, -0.1015625, -0.109375, -0.1171875, -0.125, -0.140625, -0.15625, -0.171875, -0.1875, -0.203125, -0.21875, -0.234375, -0.25, -0.28125, -0.3125, -0.34375, -0.375, -0.40625, -0.4375, -0.46875, -0.5, -0.5625, -0.625, -0.6875, -0.75, -0.8125, -0.875, -0.9375, -1.0, -1.125, -1.25, -1.375, -1.5, -1.625, -1.75, -1.875, -2.0, -2.25, -2.5, -2.75, -3.0, -3.25, -3.5, -3.75, -4.0, -4.5, -5.0, -5.5, -6.0, -6.5, -7.0, -7.5, -8.0, -9.0, -10.0, -11.0, -12.0, -13.0, -14.0, -15.0, -16.0, -18.0, -20.0, -22.0, -24.0, -26.0, -28.0, -30.0, -32.0, -36.0, -40.0, -44.0, -48.0, -52.0, -56.0, -60.0, -64.0, -72.0, -80.0, -88.0, -96.0, -104.0, -112.0, -120.0, -128.0, -144.0, -160.0, -176.0, -192.0, -208.0, -224.0, -240.0, -256.0, -288.0, -320.0, -352.0, -384.0, -416.0, -448.0, std::numeric_limits::quiet_NaN(), }; return e4m3fnuz_lut[input]; } TEST_CASE(test_fp8_cast_to_float) { std::vector bit_vals(256); std::iota(bit_vals.begin(), bit_vals.end(), 0); EXPECT(bool{std::all_of(bit_vals.begin(), bit_vals.end(), [](uint8_t bit_val) { migraphx::fp8::fp8e4m3fn fp8_val(bit_val, migraphx::fp8::fp8e4m3fn::from_bits()); if(std::isnan(float(fp8_val)) and std::isnan(fp8e4m3fn_to_fp32_value(bit_val))) { return true; } return migraphx::float_equal(float(fp8_val), fp8e4m3fn_to_fp32_value(bit_val)); })}); } TEST_CASE(test_fp8_cast_from_float) { std::unordered_map test_vals = { {{512, 0x7e}, {-512, 0xfe}, {448, 0x7e}, {-448, 0xfe}, {256, 0x78}, {-256, 0xf8}, {240, 0x77}, {-240, 0xf7}, {1e-07, 0x0}, {1e+07, 0x7e}, {1, 0x38}, {-1, 0xb8}, {0.1, 0x1d}, {0.11, 0x1e}, {0.111, 0x1e}, {0.1111, 0x1e}, {-0.1, 0x9d}, {-0.11, 0x9e}, {-0.111, 0x9e}, {-0.1111, 0x9e}, {0.2, 0x25}, {2, 0x40}, {20, 0x5a}, {200, 0x74}, {-0.2, 0xa5}, {-2, 0xc0}, {-20, 0xda}, {-200, 0xf4}, {0.5, 0x30}, {-0.5, 0xb0}, {1.17549e-38, 0x0}, {1.4013e-45, 0x0}, {0.0078125, 0x4}, {-0.0078125, 0x84}, {0.000976562, 0x0}, {-0.000976562, 0x80}, {0.000488281, 0x0}, {-0.000488281, 0x80}}}; EXPECT(bool{std::all_of(test_vals.begin(), test_vals.end(), [](const auto sample) { return migraphx::float_equal( migraphx::fp8::fp8e4m3fn(sample.first), migraphx::fp8::fp8e4m3fn(sample.second, migraphx::fp8::fp8e4m3fn::from_bits())); })}); } TEST_CASE(test_positive_zero) { float zero = 0.0; migraphx::fp8::fp8e4m3fn fp8_zero(zero); EXPECT(fp8_zero.is_zero()); EXPECT(migraphx::float_equal(zero, float(fp8_zero))); } TEST_CASE(test_negative_zero) { float nzero = -0.0; migraphx::fp8::fp8e4m3fn fp8_nzero(nzero); EXPECT(fp8_nzero.is_zero()); // negative zero is preserved for fp8e4m3fn EXPECT(migraphx::float_equal(nzero, float(fp8_nzero))); } TEST_CASE(test_pos_zero_eq_neg_zero) { float nzero = -0.0; float pzero = 0.0; migraphx::fp8::fp8e5m2 fp8_nzero(nzero); migraphx::fp8::fp8e5m2 fp8_pzero(pzero); EXPECT(fp8_nzero == fp8_pzero); } TEST_CASE(test_nan_1) { float fnan = std::numeric_limits::quiet_NaN(); migraphx::fp8::fp8e4m3fn fp8_nan(fnan); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(fp8_nan)); } TEST_CASE(test_nan_2) { auto fnan = std::numeric_limits::quiet_NaN(); migraphx::fp8::fp8e4m3fn fp8_nan(fnan.data, migraphx::fp8::fp8e4m3fn::from_bits()); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(fp8_nan)); EXPECT(std::isnan(float(fp8_nan))); } TEST_CASE(test_bool) { float zero = 0.0; float two = 2.0; float other = -0.375; migraphx::fp8::fp8e4m3fn fp8_zero(zero); migraphx::fp8::fp8e4m3fn fp8_two(two); migraphx::fp8::fp8e4m3fn fp8_other(other); EXPECT(not static_cast(fp8_zero)); EXPECT(static_cast(fp8_two)); EXPECT(static_cast(fp8_other)); } TEST_CASE(test_infinity_1) { float finf = std::numeric_limits::infinity(); // no inf in fp8e4m3fn, it gets clipped to max() migraphx::fp8::fp8e4m3fn fp8_max(finf); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_infinity_2) { // neg inf float finf = -1.0 * std::numeric_limits::infinity(); // no inf in fp8e4m3fn, it gets clipped to lowest migraphx::fp8::fp8e4m3fn fp8_lowest(finf); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_numeric_max_1) { float fmax = std::numeric_limits::max(); migraphx::fp8::fp8e4m3fn fp8_max(fmax); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_numeric_max_2) { // gets clipped to max float fmax = 2 * std::numeric_limits::max(); migraphx::fp8::fp8e4m3fn fp8_max(fmax); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_numeric_lowest_1) { float flowest = std::numeric_limits::lowest(); migraphx::fp8::fp8e4m3fn fp8_lowest(flowest); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_numeric_lowest_2) { // gets clipped to lowest float fmin = 2.0 * std::numeric_limits::lowest(); migraphx::fp8::fp8e4m3fn fp8_lowest(fmin); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_max_eq_lowest) { EXPECT(migraphx::float_equal(std::numeric_limits::lowest(), -1 * std::numeric_limits::max())); } TEST_CASE(test_isfinite) { EXPECT(std::isfinite(migraphx::fp8::fp8e4m3fn(0.0))); EXPECT(std::isfinite(migraphx::fp8::fp8e4m3fn(-0.0))); EXPECT(not std::isfinite( migraphx::fp8::fp8e4m3fn(std::numeric_limits::quiet_NaN()))); } TEST_CASE(test_no_infinity) { EXPECT(not bool{std::numeric_limits::has_infinity}); } TEST_CASE(test_binary_ops) { auto a = migraphx::fp8::fp8e4m3fn(-1.0); auto b = migraphx::fp8::fp8e4m3fn(1.0); auto c = migraphx::fp8::fp8e4m3fn(0.0); auto d = migraphx::fp8::fp8e4m3fn(-0.0); EXPECT(migraphx::float_equal((c + d), c)); EXPECT(migraphx::float_equal((c + d), d)); EXPECT(migraphx::float_equal((a + b), c)); EXPECT(migraphx::float_equal((a + b), d)); auto e = migraphx::fp8::fp8e4m3fn(10.0); auto f = migraphx::fp8::fp8e4m3fn(-10.0); EXPECT(e > f); EXPECT(f < e); EXPECT(f <= e); EXPECT(e >= f); EXPECT(e <= e); EXPECT(f >= f); EXPECT(not migraphx::float_equal(f, e)); } TEST_CASE(test_fabs) { auto a = migraphx::fp8::fp8e4m3fn(-1.0); auto b = migraphx::fp8::fp8e4m3fn(1.0); EXPECT(migraphx::float_equal(b, migraphx::fp8::fabs(a))); } TEST_CASE(test_stream_op) { auto a = migraphx::fp8::fp8e4m3fn(-1.0); std::stringstream ss; ss << a; EXPECT(std::string("-1") == ss.str()); ss = std::stringstream(); auto b = std::numeric_limits::quiet_NaN(); ss << b; EXPECT(std::string("nan") == ss.str()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fp8e4m3fnuz.cpp000066400000000000000000000312601510465702400203160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include "test.hpp" #include static float fp8e4m3fnuz_to_fp32_value(uint8_t input) { constexpr std::array e4m3fnuz_lut = { 0.0f, 0.0009765625f, 0.001953125f, 0.0029296875f, 0.00390625f, 0.0048828125f, 0.005859375f, 0.0068359375f, 0.0078125f, 0.0087890625f, 0.009765625f, 0.0107421875f, 0.01171875f, 0.0126953125f, 0.013671875f, 0.0146484375f, 0.015625f, 0.017578125f, 0.01953125f, 0.021484375f, 0.0234375f, 0.025390625f, 0.02734375f, 0.029296875f, 0.03125f, 0.03515625f, 0.0390625f, 0.04296875f, 0.046875f, 0.05078125f, 0.0546875f, 0.05859375f, 0.0625f, 0.0703125f, 0.078125f, 0.0859375f, 0.09375f, 0.1015625f, 0.109375f, 0.1171875f, 0.125f, 0.140625f, 0.15625f, 0.171875f, 0.1875f, 0.203125f, 0.21875f, 0.234375f, 0.25f, 0.28125f, 0.3125f, 0.34375f, 0.375f, 0.40625f, 0.4375f, 0.46875f, 0.5f, 0.5625f, 0.625f, 0.6875f, 0.75f, 0.8125f, 0.875f, 0.9375f, 1.0f, 1.125f, 1.25f, 1.375f, 1.5f, 1.625f, 1.75f, 1.875f, 2.0f, 2.25f, 2.5f, 2.75f, 3.0f, 3.25f, 3.5f, 3.75f, 4.0f, 4.5f, 5.0f, 5.5f, 6.0f, 6.5f, 7.0f, 7.5f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 18.0f, 20.0f, 22.0f, 24.0f, 26.0f, 28.0f, 30.0f, 32.0f, 36.0f, 40.0f, 44.0f, 48.0f, 52.0f, 56.0f, 60.0f, 64.0f, 72.0f, 80.0f, 88.0f, 96.0f, 104.0f, 112.0f, 120.0f, 128.0f, 144.0f, 160.0f, 176.0f, 192.0f, 208.0f, 224.0f, 240.0f, std::numeric_limits::quiet_NaN(), -0.0009765625f, -0.001953125f, -0.0029296875f, -0.00390625f, -0.0048828125f, -0.005859375f, -0.0068359375f, -0.0078125f, -0.0087890625f, -0.009765625f, -0.0107421875f, -0.01171875f, -0.0126953125f, -0.013671875f, -0.0146484375f, -0.015625f, -0.017578125f, -0.01953125f, -0.021484375f, -0.0234375f, -0.025390625f, -0.02734375f, -0.029296875f, -0.03125f, -0.03515625f, -0.0390625f, -0.04296875f, -0.046875f, -0.05078125f, -0.0546875f, -0.05859375f, -0.0625f, -0.0703125f, -0.078125f, -0.0859375f, -0.09375f, -0.1015625f, -0.109375f, -0.1171875f, -0.125f, -0.140625f, -0.15625f, -0.171875f, -0.1875f, -0.203125f, -0.21875f, -0.234375f, -0.25f, -0.28125f, -0.3125f, -0.34375f, -0.375f, -0.40625f, -0.4375f, -0.46875f, -0.5f, -0.5625f, -0.625f, -0.6875f, -0.75f, -0.8125f, -0.875f, -0.9375f, -1.0f, -1.125f, -1.25f, -1.375f, -1.5f, -1.625f, -1.75f, -1.875f, -2.0f, -2.25f, -2.5f, -2.75f, -3.0f, -3.25f, -3.5f, -3.75f, -4.0f, -4.5f, -5.0f, -5.5f, -6.0f, -6.5f, -7.0f, -7.5f, -8.0f, -9.0f, -10.0f, -11.0f, -12.0f, -13.0f, -14.0f, -15.0f, -16.0f, -18.0f, -20.0f, -22.0f, -24.0f, -26.0f, -28.0f, -30.0f, -32.0f, -36.0f, -40.0f, -44.0f, -48.0f, -52.0f, -56.0f, -60.0f, -64.0f, -72.0f, -80.0f, -88.0f, -96.0f, -104.0f, -112.0f, -120.0f, -128.0f, -144.0f, -160.0f, -176.0f, -192.0f, -208.0f, -224.0f, -240.0f, }; return e4m3fnuz_lut[input]; } TEST_CASE(test_fp8_cast_to_float) { std::vector bit_vals(256); std::iota(bit_vals.begin(), bit_vals.end(), 0); EXPECT(bool{std::all_of(bit_vals.begin(), bit_vals.end(), [](uint8_t bit_val) { migraphx::fp8::fp8e4m3fnuz fp8_val(bit_val, migraphx::fp8::fp8e4m3fnuz::from_bits()); if(std::isnan(float(fp8_val)) and std::isnan(fp8e4m3fnuz_to_fp32_value(bit_val))) { return true; } return migraphx::float_equal(float(fp8_val), fp8e4m3fnuz_to_fp32_value(bit_val)); })}); } TEST_CASE(test_fp8_cast_from_float) { std::unordered_map test_vals = {{256, 0x7f}, {-256, 0xff}, {240, 0x7f}, {-240, 0xff}, {1e-07, 0x0}, {1e+07, 0x7f}, {1, 0x40}, {-1, 0xc0}, {0.1, 0x25}, {0.11, 0x26}, {0.111, 0x26}, {0.1111, 0x26}, {-0.1, 0xa5}, {-0.11, 0xa6}, {-0.111, 0xa6}, {-0.1111, 0xa6}, {0.2, 0x2d}, {2, 0x48}, {20, 0x62}, {200, 0x7c}, {-0.2, 0xad}, {-2, 0xc8}, {-20, 0xe2}, {-200, 0xfc}, {0.5, 0x38}, {-0.5, 0xb8}, {1.17549e-38, 0x0}, {1.4013e-45, 0x0}, {0.00390625, 0x4}, {-0.00390625, 0x84}, {0.00195312, 0x2}, {-0.00195312, 0x82}, {0.000976562, 0x1}, {-0.000976562, 0x81}, {0.000488281, 0x0}, {-0.000488281, 0x0}}; EXPECT(bool{std::all_of(test_vals.begin(), test_vals.end(), [](const auto sample) { return migraphx::float_equal( migraphx::fp8::fp8e4m3fnuz(sample.first), migraphx::fp8::fp8e4m3fnuz(sample.second, migraphx::fp8::fp8e4m3fnuz::from_bits())); })}); } TEST_CASE(test_positive_zero) { float zero = 0.0; migraphx::fp8::fp8e4m3fnuz fp8_zero(zero); EXPECT(fp8_zero.is_zero()); EXPECT(migraphx::float_equal(zero, float(fp8_zero))); } TEST_CASE(test_negative_zero) { float nzero = -0.0; float pzero = 0.0; migraphx::fp8::fp8e4m3fnuz fp8_nzero(nzero); EXPECT(fp8_nzero.is_zero()); // negative zero gets converted to positive zero EXPECT(migraphx::float_equal(pzero, float(fp8_nzero))); } TEST_CASE(test_nan_1) { float fnan = std::numeric_limits::quiet_NaN(); migraphx::fp8::fp8e4m3fnuz fp8_nan(fnan); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(fp8_nan)); } TEST_CASE(test_nan_2) { auto fnan = std::numeric_limits::quiet_NaN(); migraphx::fp8::fp8e4m3fnuz fp8_nan(fnan.data, migraphx::fp8::fp8e4m3fnuz::from_bits()); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(fp8_nan)); EXPECT(std::isnan(float(fp8_nan))); } TEST_CASE(test_bool) { float zero = 0.0; float two = 2.0; float other = -0.375; migraphx::fp8::fp8e4m3fnuz fp8_zero(zero); migraphx::fp8::fp8e4m3fnuz fp8_two(two); migraphx::fp8::fp8e4m3fnuz fp8_other(other); EXPECT(not static_cast(fp8_zero)); EXPECT(static_cast(fp8_two)); EXPECT(static_cast(fp8_other)); } TEST_CASE(test_infinity_1) { float finf = std::numeric_limits::infinity(); // no inf in fp8e4m3fnuz it gets clipped to Nans migraphx::fp8::fp8e4m3fnuz fp8_nan(finf); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(float(fp8_nan))); } TEST_CASE(test_infinity_2) { // neg inf float finf = -1.0 * std::numeric_limits::infinity(); // no inf in fp8e4m3fnuz it gets clipped to NaNs migraphx::fp8::fp8e4m3fnuz fp8_nan(finf); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(float(fp8_nan))); } TEST_CASE(test_numeric_max_1) { float fmax = std::numeric_limits::max(); migraphx::fp8::fp8e4m3fnuz fp8_max(fmax); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_numeric_max_2) { // gets clipped to max float fmax = 2 * std::numeric_limits::max(); migraphx::fp8::fp8e4m3fnuz fp8_max(fmax); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_numeric_lowest_1) { float flowest = std::numeric_limits::lowest(); migraphx::fp8::fp8e4m3fnuz fp8_lowest(flowest); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_numeric_lowest_2) { // gets clipped to lowest float fmin = 2.0 * std::numeric_limits::lowest(); migraphx::fp8::fp8e4m3fnuz fp8_lowest(fmin); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_max_eq_lowest) { EXPECT(migraphx::float_equal(std::numeric_limits::lowest(), -1 * std::numeric_limits::max())); } TEST_CASE(test_isfinite) { EXPECT(std::isfinite(migraphx::fp8::fp8e4m3fnuz(0.0))); EXPECT(std::isfinite(migraphx::fp8::fp8e4m3fnuz(-0.0))); EXPECT(not std::isfinite( migraphx::fp8::fp8e4m3fnuz(std::numeric_limits::quiet_NaN()))); } TEST_CASE(test_no_infinity) { EXPECT(not bool{std::numeric_limits::has_infinity}); } TEST_CASE(test_binary_ops) { auto a = migraphx::fp8::fp8e4m3fnuz(-1.0); auto b = migraphx::fp8::fp8e4m3fnuz(1.0); auto c = migraphx::fp8::fp8e4m3fnuz(0.0); auto d = migraphx::fp8::fp8e4m3fnuz(-0.0); EXPECT(migraphx::float_equal((c + d), c)); EXPECT(migraphx::float_equal((c + d), d)); EXPECT(migraphx::float_equal((a + b), c)); EXPECT(migraphx::float_equal((a + b), d)); auto e = migraphx::fp8::fp8e4m3fnuz(10.0); auto f = migraphx::fp8::fp8e4m3fnuz(-10.0); EXPECT(e > f); EXPECT(f < e); EXPECT(f <= e); EXPECT(e >= f); EXPECT(e <= e); EXPECT(f >= f); EXPECT(not migraphx::float_equal(f, e)); } TEST_CASE(test_fabs) { auto a = migraphx::fp8::fp8e4m3fnuz(-1.0); auto b = migraphx::fp8::fp8e4m3fnuz(1.0); EXPECT(migraphx::float_equal(b, migraphx::fp8::fabs(a))); } TEST_CASE(test_stream_op) { auto a = migraphx::fp8::fp8e4m3fnuz(-1.0); std::stringstream ss; ss << a; EXPECT(std::string("-1") == ss.str()); ss = std::stringstream(); auto b = std::numeric_limits::quiet_NaN(); ss << b; EXPECT(std::string("nan") == ss.str()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fp8e5m2.cpp000066400000000000000000000322701510465702400174150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include "test.hpp" #include #include static float fp8e5m2_to_fp32_value(uint8_t input) { constexpr std::array e4m3fnuz_lut = { 0.0, 1.52587890625e-05, 3.0517578125e-05, 4.57763671875e-05, 6.103515625e-05, 7.62939453125e-05, 9.1552734375e-05, 0.0001068115234375, 0.0001220703125, 0.000152587890625, 0.00018310546875, 0.000213623046875, 0.000244140625, 0.00030517578125, 0.0003662109375, 0.00042724609375, 0.00048828125, 0.0006103515625, 0.000732421875, 0.0008544921875, 0.0009765625, 0.001220703125, 0.00146484375, 0.001708984375, 0.001953125, 0.00244140625, 0.0029296875, 0.00341796875, 0.00390625, 0.0048828125, 0.005859375, 0.0068359375, 0.0078125, 0.009765625, 0.01171875, 0.013671875, 0.015625, 0.01953125, 0.0234375, 0.02734375, 0.03125, 0.0390625, 0.046875, 0.0546875, 0.0625, 0.078125, 0.09375, 0.109375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.3125, 0.375, 0.4375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 12.0, 14.0, 16.0, 20.0, 24.0, 28.0, 32.0, 40.0, 48.0, 56.0, 64.0, 80.0, 96.0, 112.0, 128.0, 160.0, 192.0, 224.0, 256.0, 320.0, 384.0, 448.0, 512.0, 640.0, 768.0, 896.0, 1024.0, 1280.0, 1536.0, 1792.0, 2048.0, 2560.0, 3072.0, 3584.0, 4096.0, 5120.0, 6144.0, 7168.0, 8192.0, 10240.0, 12288.0, 14336.0, 16384.0, 20480.0, 24576.0, 28672.0, 32768.0, 40960.0, 49152.0, 57344.0, std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), -0.0, -1.52587890625e-05, -3.0517578125e-05, -4.57763671875e-05, -6.103515625e-05, -7.62939453125e-05, -9.1552734375e-05, -0.0001068115234375, -0.0001220703125, -0.000152587890625, -0.00018310546875, -0.000213623046875, -0.000244140625, -0.00030517578125, -0.0003662109375, -0.00042724609375, -0.00048828125, -0.0006103515625, -0.000732421875, -0.0008544921875, -0.0009765625, -0.001220703125, -0.00146484375, -0.001708984375, -0.001953125, -0.00244140625, -0.0029296875, -0.00341796875, -0.00390625, -0.0048828125, -0.005859375, -0.0068359375, -0.0078125, -0.009765625, -0.01171875, -0.013671875, -0.015625, -0.01953125, -0.0234375, -0.02734375, -0.03125, -0.0390625, -0.046875, -0.0546875, -0.0625, -0.078125, -0.09375, -0.109375, -0.125, -0.15625, -0.1875, -0.21875, -0.25, -0.3125, -0.375, -0.4375, -0.5, -0.625, -0.75, -0.875, -1.0, -1.25, -1.5, -1.75, -2.0, -2.5, -3.0, -3.5, -4.0, -5.0, -6.0, -7.0, -8.0, -10.0, -12.0, -14.0, -16.0, -20.0, -24.0, -28.0, -32.0, -40.0, -48.0, -56.0, -64.0, -80.0, -96.0, -112.0, -128.0, -160.0, -192.0, -224.0, -256.0, -320.0, -384.0, -448.0, -512.0, -640.0, -768.0, -896.0, -1024.0, -1280.0, -1536.0, -1792.0, -2048.0, -2560.0, -3072.0, -3584.0, -4096.0, -5120.0, -6144.0, -7168.0, -8192.0, -10240.0, -12288.0, -14336.0, -16384.0, -20480.0, -24576.0, -28672.0, -32768.0, -40960.0, -49152.0, -57344.0, -1.0f * std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), }; return e4m3fnuz_lut[input]; } TEST_CASE(test_fp8_cast_to_float) { std::vector bit_vals(256); std::iota(bit_vals.begin(), bit_vals.end(), 0); EXPECT(bool{std::all_of(bit_vals.begin(), bit_vals.end(), [](uint8_t bit_val) { migraphx::fp8::fp8e5m2 fp8_val(bit_val, migraphx::fp8::fp8e5m2::from_bits()); if(std::isnan(float(fp8_val)) and std::isnan(fp8e5m2_to_fp32_value(bit_val))) { return true; } else if(std::isinf(float(fp8_val)) and std::isinf(fp8e5m2_to_fp32_value(bit_val))) { return true; } return migraphx::float_equal(float(fp8_val), fp8e5m2_to_fp32_value(bit_val)); })}); } TEST_CASE(test_fp8_cast_from_float) { std::unordered_map test_vals = { {-60000, 0xfb}, {-57344, 0xfb}, {-448, 0xdf}, {-256, 0xdc}, {-240, 0xdc}, {-200, 0xda}, {-20, 0xcd}, {-2, 0xc0}, {-1, 0xbc}, {-0.5, 0xb8}, {-0.2, 0xb2}, {-0.1111, 0xaf}, {-0.111, 0xaf}, {-0.11, 0xaf}, {-0.1, 0xae}, {6.10351e-05, 0x4}, {-6.10351e-05, 0x84}, {3.05176e-05, 0x2}, {-3.05176e-05, 0x82}, {1.52588e-05, 0x1}, {-1.52588e-05, 0x81}, {7.62939e-06, 0x0}, {-7.62939e-06, 0x80}, {0.1, 0x2e}, {0.11, 0x2f}, {0.111, 0x2f}, {0.1111, 0x2f}, {0.2, 0x32}, {0.5, 0x38}, {1, 0x3c}, {2, 0x40}, {20, 0x4d}, {200, 0x5a}, {240, 0x5c}, {256, 0x5c}, {448, 0x5f}, {57344, 0x7b}, {60000, 0x7b}, {1e+07, 0x7b}, }; EXPECT(bool{std::all_of(test_vals.begin(), test_vals.end(), [](const auto sample) { return migraphx::float_equal( migraphx::fp8::fp8e5m2(sample.first), migraphx::fp8::fp8e5m2(sample.second, migraphx::fp8::fp8e5m2::from_bits())); })}); } TEST_CASE(test_positive_zero) { float zero = 0.0; migraphx::fp8::fp8e5m2 fp8_zero(zero); EXPECT(fp8_zero.is_zero()); EXPECT(migraphx::float_equal(zero, float(fp8_zero))); } TEST_CASE(test_negative_zero) { float nzero = -0.0; migraphx::fp8::fp8e5m2 fp8_nzero(nzero); EXPECT(fp8_nzero.is_zero()); // negative zero is preserved for fp8e5m2 EXPECT(migraphx::float_equal(nzero, float(fp8_nzero))); } TEST_CASE(test_pos_zero_eq_neg_zero) { float nzero = -0.0; float pzero = 0.0; migraphx::fp8::fp8e5m2 fp8_nzero(nzero); migraphx::fp8::fp8e5m2 fp8_pzero(pzero); EXPECT(fp8_nzero == fp8_pzero); } TEST_CASE(test_nan_1) { float fnan = std::numeric_limits::quiet_NaN(); migraphx::fp8::fp8e5m2 fp8_nan(fnan); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(fp8_nan)); } TEST_CASE(test_nan_2) { auto fnan = std::numeric_limits::quiet_NaN(); migraphx::fp8::fp8e5m2 fp8_nan(fnan.data, migraphx::fp8::fp8e5m2::from_bits()); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(fp8_nan)); EXPECT(std::isnan(float(fp8_nan))); } TEST_CASE(test_bool) { float zero = 0.0; float two = 2.0; float other = -0.375; migraphx::fp8::fp8e5m2 fp8_zero(zero); migraphx::fp8::fp8e5m2 fp8_two(two); migraphx::fp8::fp8e5m2 fp8_other(other); EXPECT(not static_cast(fp8_zero)); EXPECT(static_cast(fp8_two)); EXPECT(static_cast(fp8_other)); } TEST_CASE(test_infinity_1) { // float infinity should get clipped to max float finf = std::numeric_limits::infinity(); migraphx::fp8::fp8e5m2 fp8_max(finf); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_infinity_2) { // neg inf float finf = -1.0 * std::numeric_limits::infinity(); // no inf in fp8e5m2, it gets clipped to lowest migraphx::fp8::fp8e5m2 fp8_lowest(finf); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_numeric_max_1) { float fmax = std::numeric_limits::max(); migraphx::fp8::fp8e5m2 fp8_max(fmax); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_numeric_max_2) { // gets clipped to max float fmax = 2 * std::numeric_limits::max(); migraphx::fp8::fp8e5m2 fp8_max(fmax); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_numeric_lowest_1) { float flowest = std::numeric_limits::lowest(); migraphx::fp8::fp8e5m2 fp8_lowest(flowest); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_numeric_lowest_2) { // gets clipped to lowest float fmin = 2.0 * std::numeric_limits::lowest(); migraphx::fp8::fp8e5m2 fp8_lowest(fmin); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_max_eq_lowest) { EXPECT(migraphx::float_equal(std::numeric_limits::lowest(), -1 * std::numeric_limits::max())); } TEST_CASE(test_isfinite) { EXPECT(std::isfinite(migraphx::fp8::fp8e5m2(0.0))); EXPECT(std::isfinite(migraphx::fp8::fp8e5m2(-0.0))); EXPECT(not std::isfinite( migraphx::fp8::fp8e5m2(std::numeric_limits::quiet_NaN()))); EXPECT(not std::isfinite(std::numeric_limits::infinity())); // -1.0 * inf is float(-inf) which with clipping/saturation gets converted into fp8::lowest() EXPECT(std::isfinite( migraphx::fp8::fp8e5m2(-1.0 * std::numeric_limits::infinity()))); EXPECT(not std::isfinite(migraphx::fp8::fp8e5m2(0xFC, migraphx::fp8::fp8e5m2::from_bits()))); } TEST_CASE(test_binary_ops) { auto a = migraphx::fp8::fp8e5m2(-1.0); auto b = migraphx::fp8::fp8e5m2(1.0); auto c = migraphx::fp8::fp8e5m2(0.0); auto d = migraphx::fp8::fp8e5m2(-0.0); EXPECT(migraphx::float_equal((c + d), c)); EXPECT(migraphx::float_equal((c + d), d)); EXPECT(migraphx::float_equal((a + b), c)); EXPECT(migraphx::float_equal((a + b), d)); auto e = migraphx::fp8::fp8e5m2(10.0); auto f = migraphx::fp8::fp8e5m2(-10.0); EXPECT(e > f); EXPECT(f < e); EXPECT(f <= e); EXPECT(e >= f); EXPECT(e <= e); EXPECT(f >= f); EXPECT(not migraphx::float_equal(f, e)); } TEST_CASE(test_fabs) { auto a = migraphx::fp8::fp8e5m2(-1.0); auto b = migraphx::fp8::fp8e5m2(1.0); EXPECT(migraphx::float_equal(b, migraphx::fp8::fabs(a))); } TEST_CASE(test_stream_op) { auto a = migraphx::fp8::fp8e5m2(-1.0); std::stringstream ss; ss << a; EXPECT(std::string("-1") == ss.str()); ss = std::stringstream(); auto b = std::numeric_limits::quiet_NaN(); ss << b; EXPECT(std::string("nan") == ss.str()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fp8e5m2fnuz.cpp000066400000000000000000000311071510465702400203160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include "test.hpp" #include static float fp8e5m2fnuz_to_fp32_value(uint8_t input) { constexpr std::array e4m3fnuz_lut = { 0.0, 7.62939453125e-06, 1.52587890625e-05, 2.288818359375e-05, 3.0517578125e-05, 3.814697265625e-05, 4.57763671875e-05, 5.340576171875e-05, 6.103515625e-05, 7.62939453125e-05, 9.1552734375e-05, 0.0001068115234375, 0.0001220703125, 0.000152587890625, 0.00018310546875, 0.000213623046875, 0.000244140625, 0.00030517578125, 0.0003662109375, 0.00042724609375, 0.00048828125, 0.0006103515625, 0.000732421875, 0.0008544921875, 0.0009765625, 0.001220703125, 0.00146484375, 0.001708984375, 0.001953125, 0.00244140625, 0.0029296875, 0.00341796875, 0.00390625, 0.0048828125, 0.005859375, 0.0068359375, 0.0078125, 0.009765625, 0.01171875, 0.013671875, 0.015625, 0.01953125, 0.0234375, 0.02734375, 0.03125, 0.0390625, 0.046875, 0.0546875, 0.0625, 0.078125, 0.09375, 0.109375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.3125, 0.375, 0.4375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 12.0, 14.0, 16.0, 20.0, 24.0, 28.0, 32.0, 40.0, 48.0, 56.0, 64.0, 80.0, 96.0, 112.0, 128.0, 160.0, 192.0, 224.0, 256.0, 320.0, 384.0, 448.0, 512.0, 640.0, 768.0, 896.0, 1024.0, 1280.0, 1536.0, 1792.0, 2048.0, 2560.0, 3072.0, 3584.0, 4096.0, 5120.0, 6144.0, 7168.0, 8192.0, 10240.0, 12288.0, 14336.0, 16384.0, 20480.0, 24576.0, 28672.0, 32768.0, 40960.0, 49152.0, 57344.0, std::numeric_limits::quiet_NaN(), -7.62939453125e-06, -1.52587890625e-05, -2.288818359375e-05, -3.0517578125e-05, -3.814697265625e-05, -4.57763671875e-05, -5.340576171875e-05, -6.103515625e-05, -7.62939453125e-05, -9.1552734375e-05, -0.0001068115234375, -0.0001220703125, -0.000152587890625, -0.00018310546875, -0.000213623046875, -0.000244140625, -0.00030517578125, -0.0003662109375, -0.00042724609375, -0.00048828125, -0.0006103515625, -0.000732421875, -0.0008544921875, -0.0009765625, -0.001220703125, -0.00146484375, -0.001708984375, -0.001953125, -0.00244140625, -0.0029296875, -0.00341796875, -0.00390625, -0.0048828125, -0.005859375, -0.0068359375, -0.0078125, -0.009765625, -0.01171875, -0.013671875, -0.015625, -0.01953125, -0.0234375, -0.02734375, -0.03125, -0.0390625, -0.046875, -0.0546875, -0.0625, -0.078125, -0.09375, -0.109375, -0.125, -0.15625, -0.1875, -0.21875, -0.25, -0.3125, -0.375, -0.4375, -0.5, -0.625, -0.75, -0.875, -1.0, -1.25, -1.5, -1.75, -2.0, -2.5, -3.0, -3.5, -4.0, -5.0, -6.0, -7.0, -8.0, -10.0, -12.0, -14.0, -16.0, -20.0, -24.0, -28.0, -32.0, -40.0, -48.0, -56.0, -64.0, -80.0, -96.0, -112.0, -128.0, -160.0, -192.0, -224.0, -256.0, -320.0, -384.0, -448.0, -512.0, -640.0, -768.0, -896.0, -1024.0, -1280.0, -1536.0, -1792.0, -2048.0, -2560.0, -3072.0, -3584.0, -4096.0, -5120.0, -6144.0, -7168.0, -8192.0, -10240.0, -12288.0, -14336.0, -16384.0, -20480.0, -24576.0, -28672.0, -32768.0, -40960.0, -49152.0, -57344.0, }; return e4m3fnuz_lut[input]; } TEST_CASE(test_fp8_cast_to_float) { std::vector bit_vals(256); std::iota(bit_vals.begin(), bit_vals.end(), 0); EXPECT(bool{std::all_of(bit_vals.begin(), bit_vals.end(), [](uint8_t bit_val) { migraphx::fp8::fp8e5m2fnuz fp8_val(bit_val, migraphx::fp8::fp8e5m2fnuz::from_bits()); if(std::isnan(float(fp8_val)) and std::isnan(fp8e5m2fnuz_to_fp32_value(bit_val))) { return true; } return migraphx::float_equal(float(fp8_val), fp8e5m2fnuz_to_fp32_value(bit_val)); })}); } TEST_CASE(test_fp8_cast_from_float) { std::unordered_map test_vals = { {57344, 0x7f}, {-57344, 0xff}, {60000, 0x7f}, {-60000, 0xff}, {448, 0x63}, {-448, 0xe3}, {256, 0x60}, {-256, 0xe0}, {240, 0x60}, {-240, 0xe0}, {3.05176e-05, 0x4}, {-3.05176e-05, 0x84}, {1.52588e-05, 0x2}, {-1.52588e-05, 0x82}, {7.62939e-06, 0x1}, {-7.62939e-06, 0x81}, {3.81469e-06, 0x0}, {-3.81469e-06, 0x0}, {1e+07, 0x7f}, {1, 0x40}, {-1, 0xc0}, {0.1, 0x32}, {0.11, 0x33}, {0.111, 0x33}, {0.1111, 0x33}, {-0.1, 0xb2}, {-0.11, 0xb3}, {-0.111, 0xb3}, {-0.1111, 0xb3}, {0.2, 0x36}, {2, 0x44}, {20, 0x51}, {200, 0x5e}, {-0.2, 0xb6}, {-2, 0xc4}, {-20, 0xd1}, {-200, 0xde}, {0.5, 0x3c}, {-0.5, 0xbc}, {1.17549e-38, 0x0}, {1.4013e-45, 0x0}, }; EXPECT(bool{std::all_of(test_vals.begin(), test_vals.end(), [](const auto sample) { return migraphx::float_equal( migraphx::fp8::fp8e5m2fnuz(sample.first), migraphx::fp8::fp8e5m2fnuz(sample.second, migraphx::fp8::fp8e5m2fnuz::from_bits())); })}); } TEST_CASE(test_positive_zero) { float zero = 0.0; migraphx::fp8::fp8e5m2fnuz fp8_zero(zero); EXPECT(fp8_zero.is_zero()); EXPECT(migraphx::float_equal(zero, float(fp8_zero))); } TEST_CASE(test_negative_zero) { float nzero = -0.0; float pzero = 0.0; migraphx::fp8::fp8e5m2fnuz fp8_nzero(nzero); EXPECT(fp8_nzero.is_zero()); // negative zero gets converted to positive zero EXPECT(migraphx::float_equal(pzero, float(fp8_nzero))); } TEST_CASE(test_nan_1) { float fnan = std::numeric_limits::quiet_NaN(); migraphx::fp8::fp8e5m2fnuz fp8_nan(fnan); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(fp8_nan)); } TEST_CASE(test_nan_2) { auto fnan = std::numeric_limits::quiet_NaN(); migraphx::fp8::fp8e5m2fnuz fp8_nan(fnan.data, migraphx::fp8::fp8e5m2fnuz::from_bits()); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(fp8_nan)); EXPECT(std::isnan(float(fp8_nan))); } TEST_CASE(test_bool) { float zero = 0.0; float two = 2.0; float other = -0.375; migraphx::fp8::fp8e5m2fnuz fp8_zero(zero); migraphx::fp8::fp8e5m2fnuz fp8_two(two); migraphx::fp8::fp8e5m2fnuz fp8_other(other); EXPECT(not static_cast(fp8_zero)); EXPECT(static_cast(fp8_two)); EXPECT(static_cast(fp8_other)); } TEST_CASE(test_infinity_1) { float finf = std::numeric_limits::infinity(); // no inf in fp8e5m2fnuz it gets clipped to Nans migraphx::fp8::fp8e5m2fnuz fp8_nan(finf); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(float(fp8_nan))); } TEST_CASE(test_infinity_2) { // neg inf float finf = -1.0 * std::numeric_limits::infinity(); // no inf in fp8e5m2fnuz it gets clipped to NaNs migraphx::fp8::fp8e5m2fnuz fp8_nan(finf); EXPECT(fp8_nan.is_nan()); EXPECT(std::isnan(float(fp8_nan))); } TEST_CASE(test_numeric_max_1) { float fmax = std::numeric_limits::max(); migraphx::fp8::fp8e5m2fnuz fp8_max(fmax); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_numeric_max_2) { // gets clipped to max float fmax = 2 * std::numeric_limits::max(); migraphx::fp8::fp8e5m2fnuz fp8_max(fmax); EXPECT(fp8_max == std::numeric_limits::max()); } TEST_CASE(test_numeric_lowest_1) { float flowest = std::numeric_limits::lowest(); migraphx::fp8::fp8e5m2fnuz fp8_lowest(flowest); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_numeric_lowest_2) { // gets clipped to lowest float fmin = 2.0 * std::numeric_limits::lowest(); migraphx::fp8::fp8e5m2fnuz fp8_lowest(fmin); EXPECT(fp8_lowest == std::numeric_limits::lowest()); } TEST_CASE(test_max_eq_lowest) { EXPECT(migraphx::float_equal(std::numeric_limits::lowest(), -1 * std::numeric_limits::max())); } TEST_CASE(test_isfinite) { EXPECT(std::isfinite(migraphx::fp8::fp8e5m2fnuz(0.0))); EXPECT(std::isfinite(migraphx::fp8::fp8e5m2fnuz(-0.0))); EXPECT(not std::isfinite( migraphx::fp8::fp8e5m2fnuz(std::numeric_limits::quiet_NaN()))); } TEST_CASE(test_no_infinity) { EXPECT(not bool{std::numeric_limits::has_infinity}); } TEST_CASE(test_binary_ops) { auto a = migraphx::fp8::fp8e5m2fnuz(-1.0); auto b = migraphx::fp8::fp8e5m2fnuz(1.0); auto c = migraphx::fp8::fp8e5m2fnuz(0.0); auto d = migraphx::fp8::fp8e5m2fnuz(-0.0); EXPECT(migraphx::float_equal((c + d), c)); EXPECT(migraphx::float_equal((c + d), d)); EXPECT(migraphx::float_equal((a + b), c)); EXPECT(migraphx::float_equal((a + b), d)); auto e = migraphx::fp8::fp8e5m2fnuz(10.0); auto f = migraphx::fp8::fp8e5m2fnuz(-10.0); EXPECT(e > f); EXPECT(f < e); EXPECT(f <= e); EXPECT(e >= f); EXPECT(e <= e); EXPECT(f >= f); EXPECT(not migraphx::float_equal(f, e)); } TEST_CASE(test_fabs) { auto a = migraphx::fp8::fp8e5m2fnuz(-1.0); auto b = migraphx::fp8::fp8e5m2fnuz(1.0); EXPECT(migraphx::float_equal(b, migraphx::fp8::fabs(a))); } TEST_CASE(test_stream_op) { auto a = migraphx::fp8::fp8e5m2fnuz(-1.0); std::stringstream ss; ss << a; EXPECT(std::string("-1") == ss.str()); ss = std::stringstream(); auto b = std::numeric_limits::quiet_NaN(); ss << b; EXPECT(std::string("nan") == ss.str()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fpga/000077500000000000000000000000001510465702400164345ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/fpga/get_target_assignments.cpp000066400000000000000000000045201510465702400237010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "test.hpp" #include #include #include #include #include static migraphx::program create_program() { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto diff = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_instruction(migraphx::make_op("add"), diff, z); return p; } TEST_CASE(is_supported) { auto p = create_program(); auto targets = migraphx::get_targets(); EXPECT(not targets.empty()); auto t = migraphx::make_target("fpga"); const auto assignments = p.get_target_assignments({t}); const auto* mod = p.get_main_module(); EXPECT(mod->size() == assignments.size()); for(const auto ins : iterator_for(*mod)) { const auto& target = assignments.at(ins); EXPECT(target == "fpga"); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fpga/test_compile.cpp000066400000000000000000000040501510465702400216260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "test.hpp" #include #include #include #include #include static migraphx::program create_program() { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto sum = mm->add_instruction(migraphx::make_op("add"), x, y); auto sum_2 = mm->add_instruction(migraphx::make_op("add"), sum, z); mm->add_return({sum_2}); return p; } TEST_CASE(compile) { auto p = create_program(); auto t = migraphx::make_target("fpga"); p.compile(t); EXPECT(p.is_compiled()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fuse_attention.cpp000066400000000000000000000534471510465702400212670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes(p, {migraphx::fuse_attention{}, migraphx::dead_code_elimination{}}); } TEST_CASE(gemm_softmax_gemm) { migraphx::shape s1{migraphx::shape::half_type, {1, 12, 256, 256}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto rmax = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), gemm1); rmax = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = mm->add_instruction(migraphx::make_op("sub"), gemm1, rmax); auto exp = mm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = mm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = mm->add_instruction(migraphx::make_op("dot"), div, b1); mm->add_return({gemm2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto group = add_group( p2, "attn0", "attention", {a, b, b1}, {"x0", "x1", "x2"}, [=](auto* gm, const auto& inputs) { auto gemm1 = gm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto rmax = gm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), gemm1); rmax = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = gm->add_instruction(migraphx::make_op("sub"), gemm1, rmax); auto exp = gm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = gm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = gm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = gm->add_instruction(migraphx::make_op("dot"), div, inputs[2]); return std::vector{gemm2}; }); mm->add_return({group}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(gemm_pw_softmax_gemm) { migraphx::shape s1{migraphx::shape::half_type, {1, 12, 256, 256}}; migraphx::shape s2{migraphx::shape::bool_type, {1, 12, 256, 256}}; auto s1_elements = s1.elements(); migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto select = mm->add_parameter("4", s2); std::vector eights(s1_elements, 0.125); std::vector tens(s1_elements, 10); auto eight = mm->add_literal(migraphx::literal{s1, eights}); auto ten = mm->add_literal(migraphx::literal{s1, tens}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto mul = mm->add_instruction(migraphx::make_op("mul"), gemm1, eight); auto where = mm->add_instruction(migraphx::make_op("where"), select, mul, ten); auto rmax = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), where); rmax = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = mm->add_instruction(migraphx::make_op("sub"), where, rmax); auto exp = mm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = mm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = mm->add_instruction(migraphx::make_op("dot"), div, b1); mm->add_return({gemm2}); } run_pass(p1); // Same result as gemm_softmax_gemm, but here the fused_reduce is unrolled into // pointwise ops + softmax migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto select = mm->add_parameter("4", s2); std::vector eights(s1_elements, 0.125); std::vector tens(s1_elements, 10); auto eight = mm->add_literal(migraphx::literal{s1, eights}); auto ten = mm->add_literal(migraphx::literal{s1, tens}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto group = add_group( p2, "attn0", "attention", {a, b, eight, select, ten, b1}, [=](auto* gm, const auto& inputs) { auto gemm1 = gm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto mul = gm->add_instruction(migraphx::make_op("mul"), gemm1, inputs[2]); auto where = gm->add_instruction(migraphx::make_op("where"), inputs[3], mul, inputs[4]); auto rmax = gm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), where); rmax = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = gm->add_instruction(migraphx::make_op("sub"), where, rmax); auto exp = gm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = gm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = gm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = gm->add_instruction(migraphx::make_op("dot"), div, inputs[5]); return std::vector{gemm2}; }); mm->add_return({group}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(gemm_multi_use_pw_softmax_gemm) { migraphx::shape s1{migraphx::shape::float_type, {2, 4, 16, 8}}; migraphx::shape s2{migraphx::shape::float_type, {2, 4, 8, 16}}; migraphx::shape s3{migraphx::shape::float_type, {2, 4, 16, 16}}; migraphx::shape s_mask{migraphx::shape::int64_type, {2, 16}}; auto s1_elements = s1.elements(); migraphx::program p1; { auto* mm = p1.get_main_module(); auto mask = mm->add_parameter("mask", s_mask); auto x = mm->add_parameter("x", s1); std::vector c1_vec(s1_elements, 0.125); std::vector c2_vec(s1_elements, 10); auto c1 = mm->add_literal(migraphx::literal(s2, c1_vec)); auto c2 = mm->add_literal(migraphx::literal(s1, c2_vec)); auto ten = mm->add_literal(migraphx::literal(10.0f)); auto zero = mm->add_literal(migraphx::literal(0.0f)); auto zero_int = mm->add_literal(migraphx::literal(0)); auto scale = mm->add_literal(migraphx::literal(0.25f)); mask = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int32_type}}), mask); mask = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), mask); zero_int = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", mask->get_shape().lens()}}), zero_int); auto eq = mm->add_instruction(migraphx::make_op("equal"), mask, zero_int); eq = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), eq); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), x, c1); eq = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), eq); ten = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), ten); zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), zero); auto where = mm->add_instruction(migraphx::make_op("where"), eq, ten, zero); scale = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), scale); auto add = mm->add_instruction(migraphx::make_op("add"), gemm1, where); auto mul = mm->add_instruction(migraphx::make_op("mul"), add, scale); auto rmax = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), mul); rmax = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", mul->get_shape().lens()}}), rmax); auto sub = mm->add_instruction(migraphx::make_op("sub"), mul, rmax); auto exp = mm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", mul->get_shape().lens()}}), rsum); auto div = mm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = mm->add_instruction(migraphx::make_op("dot"), div, c2); mm->add_return({gemm2, zero, eq, scale}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto mask = mm->add_parameter("mask", s_mask); auto x = mm->add_parameter("x", s1); std::vector c1_vec(s1_elements, 0.125); std::vector c2_vec(s1_elements, 10); auto c1 = mm->add_literal(migraphx::literal(s2, c1_vec)); auto c2 = mm->add_literal(migraphx::literal(s1, c2_vec)); auto ten = mm->add_literal(migraphx::literal(10.0f)); auto zero = mm->add_literal(migraphx::literal(0.0f)); auto zero_int = mm->add_literal(migraphx::literal(0)); auto scale = mm->add_literal(migraphx::literal(0.25f)); mask = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int32_type}}), mask); mask = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), mask); zero_int = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", mask->get_shape().lens()}}), zero_int); auto eq = mm->add_instruction(migraphx::make_op("equal"), mask, zero_int); eq = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), eq); eq = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), eq); ten = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), ten); zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), zero); auto where = mm->add_instruction(migraphx::make_op("where"), eq, ten, zero); scale = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), scale); auto group = add_group( p2, "attn0", "attention", {x, c1, where, scale, c2}, [=](auto* gm, const auto& inputs) { auto gemm1 = gm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = gm->add_instruction(migraphx::make_op("add"), gemm1, inputs[2]); auto mul = gm->add_instruction(migraphx::make_op("mul"), add, inputs[3]); auto rmax = gm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), mul); rmax = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), rmax); auto sub = gm->add_instruction(migraphx::make_op("sub"), mul, rmax); auto exp = gm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = gm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), rsum); auto div = gm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = gm->add_instruction(migraphx::make_op("dot"), div, inputs[4]); return std::vector{gemm2}; }); mm->add_return({group, zero, eq, scale}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(gemm_pw_softmax_lse_gemm) { migraphx::shape s1{migraphx::shape::half_type, {1, 12, 256, 256}}; migraphx::shape s2{migraphx::shape::bool_type, {1, 12, 256, 256}}; migraphx::shape s3{migraphx::shape::half_type, {1, 12, 256, 1}}; auto s1_elements = s1.elements(); auto s3_elements = s3.elements(); migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto select = mm->add_parameter("4", s2); std::vector eights(s1_elements, 0.125); std::vector tens(s1_elements, 10); std::vector log2s(s3_elements, 1.44238); auto eight = mm->add_literal(migraphx::literal{s1, eights}); auto ten = mm->add_literal(migraphx::literal{s1, tens}); auto log2 = mm->add_literal(migraphx::literal{s3, log2s}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto mul = mm->add_instruction(migraphx::make_op("mul"), gemm1, eight); auto where = mm->add_instruction(migraphx::make_op("where"), select, mul, ten); auto rmax = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), where); auto rmax_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = mm->add_instruction(migraphx::make_op("sub"), where, rmax_mb); auto exp = mm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); auto log = mm->add_instruction(migraphx::make_op("log"), rsum); rsum = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = mm->add_instruction(migraphx::make_op("div"), exp, rsum); auto adjust_lse = mm->add_instruction(migraphx::make_op("add"), log, rmax); auto log2se = mm->add_instruction(migraphx::make_op("mul"), adjust_lse, log2); auto convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), log2se); auto lse = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), convert); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto gemm2 = mm->add_instruction(migraphx::make_op("dot"), div, b1); mm->add_return({gemm2, lse}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto select = mm->add_parameter("4", s2); std::vector eights(s1_elements, 0.125); std::vector tens(s1_elements, 10); std::vector log2s(s3_elements, 1.44238); auto eight = mm->add_literal(migraphx::literal{s1, eights}); auto ten = mm->add_literal(migraphx::literal{s1, tens}); auto log2 = mm->add_literal(migraphx::literal{s3, log2s}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto group = add_group( p2, "attn0", "attention", {a, b, eight, select, ten, b1}, [=](auto* gm, const auto& inputs) { auto gemm1 = gm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto mul = gm->add_instruction(migraphx::make_op("mul"), gemm1, inputs[2]); auto where = gm->add_instruction(migraphx::make_op("where"), inputs[3], mul, inputs[4]); auto rmax = gm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), where); auto rmax_mb = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = gm->add_instruction(migraphx::make_op("sub"), where, rmax_mb); auto exp = gm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = gm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); auto rsum_mb = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = gm->add_instruction(migraphx::make_op("div"), exp, rsum_mb); auto gemm2 = gm->add_instruction(migraphx::make_op("dot"), div, inputs[5]); auto log = gm->add_instruction(migraphx::make_op("log"), rsum); auto add = gm->add_instruction(migraphx::make_op("add"), log, rmax); return std::vector{gemm2, add}; }); auto adjust_lse = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), group); auto log2se = mm->add_instruction(migraphx::make_op("mul"), adjust_lse, log2); auto convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), log2se); auto lse = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), convert); auto gemm2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), group); mm->add_return({gemm2, lse}); } EXPECT(p1.sort() == p2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); return 0; } ROCm-AMDMIGraphX-46524e8/test/fuse_concat.cpp000066400000000000000000000400271510465702400205170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes(p, {migraphx::fuse_concat{}, migraphx::dead_code_elimination{}}); } template struct concat_arg { std::string name; std::vector inputs; F f; }; template static concat_arg arg(std::string name, std::vector inputs, F f) { return {std::move(name), std::move(inputs), std::move(f)}; } template static migraphx::instruction_ref add_pointwise_concat(migraphx::program& p, std::size_t axis, Arg post_arg, const Args&... args) { std::vector module_inputs; std::vector ins_inputs; migraphx::each_args( [&](auto arg) { module_inputs.push_back(create_pointwise_module(p, arg.name, arg.inputs, arg.f)); ins_inputs.insert(ins_inputs.end(), arg.inputs.begin(), arg.inputs.end()); }, args...); module_inputs.push_back(create_pointwise_module(p, post_arg.name, {}, [&](auto* pm, auto&&) { std::vector params; params.push_back( pm->add_parameter("!x0", migraphx::shape{ins_inputs.back()->get_shape().type()})); std::transform(post_arg.inputs.begin(), post_arg.inputs.end(), std::back_inserter(params), [&](auto input) { return pm->add_parameter("x" + std::to_string(params.size()), migraphx::shape{input->get_shape().type()}); }); return post_arg.f(pm, params); })); auto* mm = p.get_main_module(); return mm->add_instruction( migraphx::make_op("fused_concat", {{"axis", axis}}), ins_inputs, module_inputs); } TEST_CASE(simple_concat_pointwise) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto sub = add_pointwise(p1, "main:pointwise1", {x, y}, single_pointwise("sub")); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), add, sub); mm->add_return({concat}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto fused_concat = add_pointwise_concat(p2, 1, arg("noop:concat0", {}, noop_pointwise()), arg("concat:main:pointwise0", {x, y}, single_pointwise("add")), arg("concat:main:pointwise1", {x, y}, single_pointwise("sub"))); mm->add_return({fused_concat}); } EXPECT(p1 == p2); } TEST_CASE(partial_pointwise_concat) { migraphx::shape s1{migraphx::shape::float_type, {1, 4, 8, 8}}; migraphx::shape s2{migraphx::shape::float_type, {1, 4, 16, 16}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto pooling = mm->add_instruction( migraphx::make_op("pooling", {{"lengths", {2, 2}}, {"stride", {2, 2}}}), z); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), add, pooling); mm->add_return({concat}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto pooling = mm->add_instruction( migraphx::make_op("pooling", {{"lengths", {2, 2}}, {"stride", {2, 2}}}), z); auto fused_concat = add_pointwise_concat(p2, 1, arg("noop:concat2", {}, noop_pointwise()), arg("concat:main:pointwise0", {x, y}, single_pointwise("add")), arg("concat:noop0", {pooling}, noop_pointwise())); mm->add_return({fused_concat}); } EXPECT(p1 == p2); } TEST_CASE(skip_pointwise_concat) { // number of no-ops are two in this case and therefore pointwise+concat fusion wouldn't be // applicable. migraphx::shape s1{migraphx::shape::float_type, {1, 4, 8, 8}}; migraphx::shape s2{migraphx::shape::float_type, {1, 4, 16, 16}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto w = mm->add_parameter("w", s1); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto reduce_ins = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), w); auto pooling = mm->add_instruction( migraphx::make_op("pooling", {{"lengths", {2, 2}}, {"stride", {2, 2}}}), z); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", 1}}), reduce_ins, add, pooling); mm->add_return({concat}); } migraphx::program p2 = p1; run_pass(p1); EXPECT(p1 == p2); } TEST_CASE(simple_pointwise_concat_pointwise) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto sub = add_pointwise(p1, "main:pointwise1", {x, y}, single_pointwise("sub")); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), add, sub); auto relu = add_pointwise(p1, "main:pointwise2", {concat}, single_pointwise("relu")); mm->add_return({relu}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto fused_concat = add_pointwise_concat(p2, 1, arg("main:pointwise2:concat", {}, single_pointwise("relu")), arg("concat:main:pointwise0", {x, y}, single_pointwise("add")), arg("concat:main:pointwise1", {x, y}, single_pointwise("sub"))); mm->add_return({fused_concat}); } EXPECT(p1 == p2); } TEST_CASE(pointwise_concat_pointwise_multi_out) { migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::float_type, {2, 6}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto sub = add_pointwise(p1, "main:pointwise1", {x, y}, single_pointwise("sub")); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), add, sub); auto r = add_pointwise( p1, "main:pointwise2", {concat, z}, [=](auto* pm, const auto& inputs) -> std::vector { auto mul = pm->add_instruction(migraphx::make_op("mul"), inputs[0], inputs[1]); auto relu = pm->add_instruction(migraphx::make_op("relu"), mul); return {mul, relu}; }); auto mul = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto relu = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({mul, relu}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto fused_concat = add_pointwise_concat(p2, 1, arg("noop:concat0", {}, noop_pointwise()), arg("concat:main:pointwise0", {x, y}, single_pointwise("add")), arg("concat:main:pointwise1", {x, y}, single_pointwise("sub"))); auto r = add_pointwise( p2, "main:pointwise2", {fused_concat, z}, [=](auto* pm, const auto& inputs) -> std::vector { auto mul = pm->add_instruction(migraphx::make_op("mul"), inputs[0], inputs[1]); auto relu = pm->add_instruction(migraphx::make_op("relu"), mul); return {mul, relu}; }); auto mul = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto relu = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({mul, relu}); } EXPECT(p1 == p2); } TEST_CASE(partial_pointwise_concat_pointwise) { migraphx::shape s1{migraphx::shape::float_type, {1, 4, 8, 8}}; migraphx::shape s2{migraphx::shape::float_type, {1, 4, 16, 16}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto pooling = mm->add_instruction( migraphx::make_op("pooling", {{"lengths", {2, 2}}, {"stride", {2, 2}}}), z); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), add, pooling); auto relu = add_pointwise(p1, "main:pointwise2", {concat}, single_pointwise("relu")); mm->add_return({relu}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto pooling = mm->add_instruction( migraphx::make_op("pooling", {{"lengths", {2, 2}}, {"stride", {2, 2}}}), z); auto fused_concat = add_pointwise_concat(p2, 1, arg("main:pointwise2:concat", {}, single_pointwise("relu")), arg("concat:main:pointwise0", {x, y}, single_pointwise("add")), arg("concat:noop1", {pooling}, noop_pointwise())); mm->add_return({fused_concat}); } EXPECT(p1 == p2); } TEST_CASE(multiple_use_pointwise_concat_pointwise) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto sub = add_pointwise(p1, "main:pointwise1", {x, y}, single_pointwise("sub")); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), add, sub); auto relu = add_pointwise(p1, "main:pointwise2", {concat}, single_pointwise("relu")); auto slice = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {4}}}), relu); auto mul = add_pointwise(p1, "main:pointwise3", {slice, sub}, single_pointwise({"mul"})); mm->add_return({mul}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto sub = add_pointwise(p2, "main:pointwise1", {x, y}, single_pointwise("sub")); auto fused_concat = add_pointwise_concat(p2, 1, arg("main:pointwise2:concat", {}, single_pointwise("relu")), arg("concat:main:pointwise0", {x, y}, single_pointwise("add")), arg("concat:noop1", {sub}, noop_pointwise())); auto slice = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {4}}}), fused_concat); auto mul = add_pointwise(p2, "main:pointwise3", {slice, sub}, single_pointwise({"mul"})); mm->add_return({mul}); } EXPECT(p1 == p2); } TEST_CASE(pointwise_concat_fusion) { migraphx::shape s1{migraphx::shape::half_type, {2, 3}}; migraphx::shape s2{migraphx::shape::half_type, {2, 3}, {1, 2}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto yc = mm->add_instruction(migraphx::make_op("contiguous"), y); auto sins = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("sigmoid")); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), sins, yc); auto relu = add_pointwise(p1, "main:pointwise2", {concat}, single_pointwise("relu")); mm->add_return({relu}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto yc = mm->add_instruction(migraphx::make_op("contiguous"), y); auto fused_concat = add_pointwise_concat(p2, 1, arg("main:pointwise2:concat", {}, single_pointwise("relu")), arg("concat:main:pointwise0", {x}, single_pointwise("sigmoid")), arg("concat:noop1", {yc}, noop_pointwise())); mm->add_return({fused_concat}); } EXPECT(p1 == p2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fuse_pointwise.cpp000066400000000000000000001320461510465702400212740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::program& p, migraphx::fuse_pointwise pass = {}) { migraphx::run_passes(p, {pass, migraphx::dead_code_elimination{}}); } TEST_CASE(single) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto pass = mm->add_instruction(pass_op{}, add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), pass, z); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add1 = add_pointwise(p2, "main:pointwise0", {x, y}, single_pointwise("add")); auto pass = mm->add_instruction(pass_op{}, add1); auto add2 = add_pointwise(p2, "main:pointwise1", {pass, z}, single_pointwise("add")); mm->add_return({add2}); } EXPECT(p1 == p2); } TEST_CASE(double_add) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, z); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto fadd = add_pointwise(p2, "main:pointwise0", {x, y, z}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); }); mm->add_return({fadd}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(double_add_crossmodule) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto* sm = p1.create_module("sub"); auto add2 = sm->add_instruction(migraphx::make_op("add"), add1, z); sm->add_return({add2}); auto r = mm->add_instruction(mod_pass_op{}, {}, {sm}); mm->add_return({r}); } run_pass(p1); // TODO: Handle crossmodule fusion // migraphx::program p2; // { // auto* mm = p2.get_main_module(); // auto x = mm->add_parameter("x", s); // auto y = mm->add_parameter("y", s); // auto z = mm->add_parameter("z", s); // auto* sm = p2.create_module("sub"); // auto fadd = // add_pointwise(p2, sm, "sub:pointwise0", {x, y, z}, [=](auto* pm, const auto& inputs) // { // auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); // return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); // }); // sm->add_return({fadd}); // auto r = mm->add_instruction(mod_pass_op{}, {}, {sm}); // mm->add_return({r}); // } migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add1 = add_pointwise(p2, mm, "main:pointwise0", {x, y}, single_pointwise("add")); auto* sm = p2.create_module("sub"); auto add2 = add_pointwise(p2, sm, "sub:pointwise0", {add1, z}, single_pointwise("add")); // auto add2 = sm->add_instruction(migraphx::make_op("add"), add1, z); sm->add_return({add2}); auto r = mm->add_instruction(mod_pass_op{}, {}, {sm}); mm->add_return({r}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(double_add_without_return) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_instruction(migraphx::make_op("add"), add1, z); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto fadd = add_pointwise(p2, "main:pointwise0", {x, y, z}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); }); mm->add_instruction(migraphx::make_op("identity"), fadd); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(convert_add_convert) { migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::half_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto convert1 = mm->add_instruction(migraphx::make_op("convert", {{"target_type", s2.type()}}), x); auto add = mm->add_instruction(migraphx::make_op("add"), convert1, y); auto convert2 = mm->add_instruction(migraphx::make_op("convert", {{"target_type", s1.type()}}), add); mm->add_return({convert2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto fadd = add_pointwise(p2, "main:pointwise0", {x, y}, [=](auto* pm, const auto& inputs) { auto convert1 = pm->add_instruction( migraphx::make_op("convert", {{"target_type", s2.type()}}), inputs[0]); auto add = pm->add_instruction(migraphx::make_op("add"), convert1, inputs[1]); return pm->add_instruction(migraphx::make_op("convert", {{"target_type", s1.type()}}), add); }); mm->add_return({fadd}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(used_twice_not_fused) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto pass = mm->add_instruction(pass_op{}, add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, y); auto add3 = mm->add_instruction(migraphx::make_op("add"), pass, add2); mm->add_return({add3}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = add_pointwise(p2, "main:pointwise0", {x, y}, single_pointwise("add")); auto pass = mm->add_instruction(pass_op{}, add1); auto fadd = add_pointwise( p2, "main:pointwise1", {add1, y, pass}, [=](auto* pm, const auto& inputs) { auto add2 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), inputs[2], add2); }); mm->add_return({fadd}); } EXPECT(p1 == p2); } TEST_CASE(used_twice_fused) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, x); auto add3 = mm->add_instruction(migraphx::make_op("add"), add1, y); auto add4 = mm->add_instruction(migraphx::make_op("add"), add2, add3); mm->add_return({add4}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto fadd = add_pointwise(p2, "main:pointwise0", {x, y}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); auto add2 = pm->add_instruction(migraphx::make_op("add"), add1, inputs[0]); auto add3 = pm->add_instruction(migraphx::make_op("add"), add1, inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add2, add3); }); mm->add_return({fadd}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(used_twice_mutli_out_fused) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, x); mm->add_return({add1, add2}); } run_pass(p1, {.enable_multi_output = true}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto fadd = add_pointwise( p2, "main:pointwise0", {x, y}, [=](auto* pm, const auto& inputs) -> std::vector { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); auto add2 = pm->add_instruction(migraphx::make_op("add"), add1, inputs[0]); return {add2, add1}; }); auto add1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fadd); auto add2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fadd); mm->add_return({add1, add2}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(used_twice_mutli_out_fused_reorder) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto pass = mm->add_instruction(pass_op{}, add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, y); auto add3 = mm->add_instruction(migraphx::make_op("add"), pass, add2); mm->add_return({add3}); } run_pass(p1, {.enable_multi_output = true}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto fadd = add_pointwise( p2, "main:pointwise0", {x, y}, [=](auto* pm, const auto& inputs) -> std::vector { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); auto add2 = pm->add_instruction(migraphx::make_op("add"), add1, inputs[1]); return {add2, add1}; }); auto add1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fadd); auto add2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fadd); auto pass = mm->add_instruction(pass_op{}, add1); auto add3 = add_pointwise(p2, "main:pointwise2", {pass, add2}, single_pointwise("add")); mm->add_return({add3}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(used_twice_mutli_out_not_fused) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto pass = mm->add_instruction(pass_op{}, add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, pass); mm->add_return({add2}); } run_pass(p1, {.enable_multi_output = true}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = add_pointwise(p2, "main:pointwise0", {x, y}, single_pointwise("add")); auto pass = mm->add_instruction(pass_op{}, add1); auto add2 = add_pointwise(p2, "main:pointwise1", {add1, pass}, single_pointwise("add")); mm->add_return({add2}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(horizontal_mutli_out_fused1) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = mm->add_instruction(migraphx::make_op("add"), x, z); mm->add_return({add1, add2}); } run_pass(p1, {.enable_multi_output = true}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto fadd = add_pointwise( p2, "main:pointwise0", {x, y, z}, [=](auto* pm, const auto& inputs) -> std::vector { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); auto add2 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[2]); return {add2, add1}; }); auto add1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fadd); auto add2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fadd); mm->add_return({add1, add2}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(horizontal_mutli_out_fused2) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y1 = mm->add_parameter("y1", s); auto y2 = mm->add_parameter("y2", s); auto y3 = mm->add_parameter("y3", s); auto y4 = mm->add_parameter("y4", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y1); auto add2 = mm->add_instruction(migraphx::make_op("add"), x, y2); auto add3 = mm->add_instruction(migraphx::make_op("add"), x, y3); auto add4 = mm->add_instruction(migraphx::make_op("add"), x, y4); mm->add_return({add1, add2, add3, add4}); } run_pass(p1, {.enable_multi_output = true}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y1 = mm->add_parameter("y1", s); auto y2 = mm->add_parameter("y2", s); auto y3 = mm->add_parameter("y3", s); auto y4 = mm->add_parameter("y4", s); auto fadd = add_pointwise( p2, "main:pointwise0", {x, y1, y2, y3, y4}, [=](auto* pm, const auto& inputs) -> std::vector { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); auto add2 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[2]); auto add3 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[3]); auto add4 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[4]); return {add4, add3, add2, add1}; }); auto add1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 3}}), fadd); auto add2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), fadd); auto add3 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fadd); auto add4 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fadd); mm->add_return({add1, add2, add3, add4}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(horizontal_mutli_out_fused3) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, a); auto add3 = mm->add_instruction(migraphx::make_op("add"), add1, b); mm->add_return({add2, add3}); } run_pass(p1, {.enable_multi_output = true}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto fadd = add_pointwise( p2, "main:pointwise0", {x, y, a, b}, [=](auto* pm, const auto& inputs) -> std::vector { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); auto add2 = pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); auto add3 = pm->add_instruction(migraphx::make_op("add"), add1, inputs[3]); return {add3, add2}; }); auto add2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fadd); auto add3 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fadd); mm->add_return({add2, add3}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(horizontal_mutli_out_fused_submodule) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto input = mm->add_parameter("input", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto* sm = p1.create_module("sub"); auto x = sm->add_parameter("x", s); auto add1 = sm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = sm->add_instruction(migraphx::make_op("add"), x, z); sm->add_return({add1, add2}); auto r = mm->add_instruction(mod_pass_op{}, {input}, {sm}); auto elem1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto elem2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({elem1, elem2}); } run_pass(p1, {.enable_multi_output = true}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto input = mm->add_parameter("input", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto* sm = p2.create_module("sub"); auto x = sm->add_parameter("x", s); auto fadd = add_pointwise( p2, sm, "sub:pointwise0", {x, y, z}, [=](auto* pm, const auto& inputs) -> std::vector { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); auto add2 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[2]); return {add2, add1}; }); auto add1 = sm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fadd); auto add2 = sm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fadd); sm->add_return({add1, add2}); auto r = mm->add_instruction(mod_pass_op{}, {input}, {sm}); auto elem1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto elem2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({elem1, elem2}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(horizontal_mutli_out_fused_crossmodule) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto* sm = p1.create_module("sub"); auto add1 = sm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = sm->add_instruction(migraphx::make_op("add"), x, z); sm->add_return({add1, add2}); auto r = mm->add_instruction(mod_pass_op{}, {}, {sm}); auto elem1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto elem2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({elem1, elem2}); } run_pass(p1, {.enable_multi_output = true}); // TODO: Handle cross module fusions // migraphx::program p2; // { // auto* mm = p2.get_main_module(); // auto x = mm->add_parameter("x", s); // auto y = mm->add_parameter("y", s); // auto z = mm->add_parameter("z", s); // auto* sm = p2.create_module("sub"); // auto fadd = add_pointwise( // p2, // sm, // "sub:pointwise0", // {x, y, z}, // [=](auto* pm, const auto& inputs) -> std::vector { // auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); // auto add2 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[2]); // return {add2, add1}; // }); // auto add1 = sm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), // fadd); auto add2 = sm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", // 0}}), fadd); sm->add_return({add1, add2}); auto r = mm->add_instruction(mod_pass_op{}, // {}, {sm}); auto elem1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", // {{"index", 0}}), r); auto elem2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", // {{"index", 1}}), r); mm->add_return({elem1, elem2}); // } migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto* sm = p2.create_module("sub"); auto add1 = add_pointwise(p2, sm, "sub:pointwise0", {x, y}, single_pointwise("add")); auto add2 = add_pointwise(p2, sm, "sub:pointwise1", {x, z}, single_pointwise("add")); sm->add_return({add1, add2}); auto r = mm->add_instruction(mod_pass_op{}, {}, {sm}); auto elem1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto elem2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({elem1, elem2}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(duplicate_inputs) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, x); auto pass = mm->add_instruction(pass_op{}, add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), pass, y); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = add_pointwise(p2, "main:pointwise0", {x}, [=](auto* pm, const auto& inputs) { return pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[0]); }); auto pass = mm->add_instruction(pass_op{}, add1); auto add2 = add_pointwise(p2, "main:pointwise1", {pass, y}, single_pointwise("add")); mm->add_return({add2}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(scalar_input) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto one = mm->add_literal(1.0f); auto y = mm->add_instruction(migraphx::make_op("scalar", {{"scalar_bcst_dims", s.lens()}}), one); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_return({add1}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto add1 = add_pointwise(p2, "main:pointwise0", {x}, [=](auto* pm, const auto& inputs) { auto y = pm->add_literal(1.0f); return pm->add_instruction(migraphx::make_op("add"), inputs[0], y); }); mm->add_return({add1}); } EXPECT(p1 == p2); } TEST_CASE(scalar_like_input) { migraphx::shape s{migraphx::shape::float_type, {2, 1}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto one = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {1}}, {1.0f}}); auto y = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), one); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_return({add1}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto add1 = add_pointwise(p2, "main:pointwise0", {x}, [=](auto* pm, const auto& inputs) { auto y = pm->add_literal(1.0f); return pm->add_instruction(migraphx::make_op("add"), inputs[0], y); }); mm->add_return({add1}); } EXPECT(p1 == p2); } TEST_CASE(contiguous_input) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto one = mm->add_literal(1.0f); auto yb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), one); auto y = mm->add_instruction(migraphx::make_op("contiguous"), yb); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_return({add1}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto add1 = add_pointwise(p2, "main:pointwise0", {x}, [=](auto* pm, const auto& inputs) { auto y = pm->add_literal(1.0f); return pm->add_instruction(migraphx::make_op("add"), inputs[0], y); }); mm->add_return({add1}); } EXPECT(p1 == p2); } TEST_CASE(contiguous_boolean_input) { migraphx::shape s{migraphx::shape::bool_type, {2, 3}}; migraphx::shape s_lit{migraphx::shape::bool_type, {1}, {0}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto one = mm->add_literal(migraphx::literal(s_lit, {1.0})); auto yb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), one); auto y = mm->add_instruction(migraphx::make_op("contiguous"), yb); auto xor1 = mm->add_instruction(migraphx::make_op("logical_xor"), x, y); mm->add_return({xor1}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto xor1 = add_pointwise(p2, "main:pointwise0", {x}, [=](auto* pm, const auto& inputs) { auto y = pm->add_literal(migraphx::literal(s_lit, {1})); return pm->add_instruction(migraphx::make_op("logical_xor"), inputs[0], y); }); mm->add_return({xor1}); } } TEST_CASE(all_scalar_input) { migraphx::shape s{migraphx::shape::float_type}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_return({add1}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add1 = add_pointwise(p2, "main:pointwise0", {x, y}, [=](auto* pm, const auto& inputs) { return pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); }); mm->add_return({add1}); } EXPECT(p1.get_output_shapes().size() == 1); EXPECT(p1.get_output_shapes().front().scalar()); EXPECT(p1.get_output_shapes() == p2.get_output_shapes()); EXPECT(p1 == p2); } TEST_CASE(no_input) { migraphx::program p; { auto* mm = p.get_main_module(); migraphx::shape g_shape{migraphx::shape::int64_type, {1}, {0}}; migraphx::shape s_indices{migraphx::shape::int32_type, {3}}; std::vector indices{3, 800, 800}; auto a0 = mm->add_literal(migraphx::literal{s_indices, indices}); auto a1 = mm->add_literal(migraphx::literal{g_shape, {1}}); int axis = 0; auto out = mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); mm->add_return({out}); } run_pass(p); // This should NOT create a pointwise module if there are no inputs here. migraphx::program p2; { auto* mm = p2.get_main_module(); migraphx::shape g_shape{migraphx::shape::int64_type, {1}, {0}}; migraphx::shape s_indices{migraphx::shape::int32_type, {3}}; std::vector indices{3, 800, 800}; auto a0 = mm->add_literal(migraphx::literal{s_indices, indices}); auto a1 = mm->add_literal(migraphx::literal{g_shape, {1}}); int axis = 0; auto out = mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); mm->add_return({out}); } EXPECT(p == p2); } TEST_CASE(add_reshape_add) { migraphx::shape s1{migraphx::shape::float_type, {3, 10, 16}}; migraphx::shape s2{migraphx::shape::float_type, {3, 40, 2, 2}}; migraphx::shape s3{migraphx::shape::float_type, {3, 10, 4, 2, 2}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), reshape, z); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto x2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), x); auto y2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), y); auto z2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), z); auto fadd = add_pointwise(p2, "main:pointwise0", {x2, y2, z2}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); }); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), fadd); mm->add_return({reshape}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(add_transpose_reshape_add) { migraphx::shape s1{migraphx::shape::float_type, {3, 16, 10}}; migraphx::shape s2{migraphx::shape::float_type, {3, 40, 2, 2}}; migraphx::shape s3{migraphx::shape::float_type, {3, 10, 4, 2, 2}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), add1); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), transpose); auto add2 = mm->add_instruction(migraphx::make_op("add"), reshape, z); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto x2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 4, 2, 2, 10}}}), x); auto x3 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 4, 1, 2, 3}}}), x2); auto y2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 4, 2, 2, 10}}}), y); auto y3 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 4, 1, 2, 3}}}), y2); auto z2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), z); auto fadd = add_pointwise(p2, "main:pointwise0", {x3, y3, z2}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); }); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), fadd); mm->add_return({reshape}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(add_contiguous_reshape_add) { auto s1 = migraphx::shape::from_permutation(migraphx::shape::float_type, {3, 10, 16}, {0, 2, 1}); auto s2 = migraphx::shape{migraphx::shape::float_type, {3, 40, 2, 2}}; auto s3 = migraphx::shape{migraphx::shape::float_type, {3, 10, 4, 2, 2}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto contiguous = mm->add_instruction(migraphx::make_op("contiguous"), add1); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), contiguous); auto add2 = mm->add_instruction(migraphx::make_op("add"), reshape, z); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto x2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), x); auto y2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), y); auto z2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), z); auto fadd = add_pointwise(p2, "main:pointwise0", {x2, y2, z2}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); }); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), fadd); mm->add_return({reshape}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(add_reshape_add_nonstandard) { migraphx::shape s1 = migraphx::shape::from_permutation(migraphx::shape::float_type, {3, 10, 16}, {2, 0, 1}); migraphx::shape s2{migraphx::shape::float_type, {3, 40, 2, 2}}; migraphx::shape s3{migraphx::shape::float_type, {3, 10, 4, 2, 2}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), reshape, z); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto x2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), x); auto y2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), y); auto z2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), z); auto fadd = add_pointwise(p2, "main:pointwise0", {x2, y2, z2}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); }); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), fadd); mm->add_return({reshape}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(add_unsqueeze_add_nonstandard) { migraphx::shape s1 = migraphx::shape::from_permutation(migraphx::shape::float_type, {3, 10, 16}, {2, 0, 1}); migraphx::shape s2{migraphx::shape::float_type, {3, 10, 1, 16}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto unsqueeze = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), unsqueeze, z); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto x2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), x); auto y2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), y); auto fadd = add_pointwise(p2, "main:pointwise0", {x2, y2, z}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); }); mm->add_return({fadd}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(add_reshape_add_error) { migraphx::shape s1{migraphx::shape::float_type, {6, 35}}; migraphx::shape s2{migraphx::shape::float_type, {3, 7, 2, 5}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), reshape, z); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto fadd1 = add_pointwise(p2, "main:pointwise0", {x, y}, single_pointwise("add")); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), fadd1); auto fadd2 = add_pointwise(p2, "main:pointwise1", {reshape, z}, single_pointwise("add")); mm->add_return({fadd2}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(add_broadcast_add) { migraphx::shape s1{migraphx::shape::float_type, {2, 1}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto badd1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s2.lens()}}), add1); auto add2 = mm->add_instruction(migraphx::make_op("add"), badd1, z); mm->add_return({add2}); } run_pass(p1, {.enable_rewrite_broadcasts = true}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto bx = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s2.lens()}}), x); auto by = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s2.lens()}}), y); auto fadd = add_pointwise(p2, "main:pointwise0", {bx, by, z}, [=](auto* pm, const auto& inputs) { auto add1 = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("add"), add1, inputs[2]); }); mm->add_return({fadd}); } EXPECT(p1.sort() == p2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/fuse_reduce.cpp000066400000000000000000001443251510465702400205250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes(p, {migraphx::fuse_reduce{}, migraphx::dead_code_elimination{}}); } TEST_CASE(single) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), y); mm->add_return({rsum1, rsum2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto rsum1 = add_reduce(p2, "main:reduce_sum0", {x}, {1}, single_reduce("reduce_sum")); auto rsum2 = add_reduce(p2, "main:reduce_sum1", {y}, {1}, single_reduce("reduce_sum")); mm->add_return({rsum1, rsum2}); } EXPECT(p1 == p2); } TEST_CASE(pointwise_reduce) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), add); mm->add_return({rsum}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto rsum = add_reduce( p2, "main:pointwise0:main:reduce_sum0", {x, y}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto add = add_pointwise(p2, rm, "main:pointwise0", inputs, single_pointwise("add")); return rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), add); }); mm->add_return({rsum}); } EXPECT(p1 == p2); } TEST_CASE(pointwise_reduce_unfusable_broadcast) { migraphx::shape s{migraphx::shape::float_type, {2, 1, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto addb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), add); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), addb); mm->add_return({rsum}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = add_pointwise(p2, "main:pointwise0", {x, y}, single_pointwise("add")); auto addb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), add); auto rsum = add_reduce(p2, "main:reduce_sum0", {addb}, {2}, [&](auto* rm, const auto& inputs, const auto& axes) { return rm->add_instruction( migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); }); mm->add_return({rsum}); } EXPECT(p1 == p2); } TEST_CASE(scalar_multibroadcast) { // Matches the find_pointwise_reduce matcher, but input x has a (scalar) shape // incompatible with the multibroadcast instruction; therefore it // creates a fused_reduce module but does not add a submodule for the // multibroadcast instruction. migraphx::shape sdot{migraphx::shape::double_type, {80, 204, 204}}; migraphx::shape sdot_double{migraphx::shape::double_type, {80, 204, 204}}; migraphx::shape scalar{migraphx::shape::double_type, {1}, {0}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", scalar); auto zap = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("sqrt")); auto pow = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sdot.lens()}}), zap); auto bip = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1, 2}}}), pow); mm->add_return({bip}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", scalar); auto zap = add_pointwise(p2, mm, "main:pointwise0", {x}, single_pointwise("sqrt")); auto pow = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sdot.lens()}}), zap); // Add a reduce module. These are created by fuse_reduce::apply() for any reduce // instruction whether the individual matchers do anything or not. auto* reduce_mod = p2.create_module("main:reduce_sum0"); auto x0 = reduce_mod->add_parameter("x0", sdot_double); auto sqrtbc = reduce_mod->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1, 2}}}), x0); reduce_mod->add_return({sqrtbc}); EXPECT(test::throws([&] { mm->add_instruction( migraphx::make_op("fused_reduce", {{"axes", {1, 2}}}), {pow}, {reduce_mod}); })); // reduce modules must be flagged for bypass when running subsequent passes reduce_mod->set_bypass(); auto bip = mm->add_instruction( migraphx::make_op("fused_reduce", {{"axes", {1, 2}}}), {pow}, {reduce_mod}); mm->add_return({bip}); } EXPECT(p1 == p2); } TEST_CASE(scalar_multibroadcast_contiguous) { // Contains a contiguous op which is not passed through. migraphx::shape sdot{migraphx::shape::double_type, {80, 204, 204}}; migraphx::shape scalar{migraphx::shape::double_type, {1}, {0}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", scalar); auto zap = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("sqrt")); auto pow = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sdot.lens()}}), zap); auto bip = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1, 2}}}), pow); auto sqrtbc = mm->add_instruction(migraphx::make_op("contiguous"), bip); mm->add_return({sqrtbc}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", scalar); auto zap = add_pointwise(p2, mm, "main:pointwise0", {x}, single_pointwise("sqrt")); auto pow = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sdot.lens()}}), zap); // Add a reduce module. These are created by fuse_reduce::apply() for any reduce // instruction whether the individual matchers do anything or not. auto* reduce_mod = p2.create_module("main:reduce_sum0"); auto x0 = reduce_mod->add_parameter("x0", sdot); auto sqrtbc = reduce_mod->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1, 2}}}), x0); reduce_mod->add_return({sqrtbc}); EXPECT(test::throws([&] { mm->add_instruction( migraphx::make_op("fused_reduce", {{"axes", {1, 2}}}), {pow}, {reduce_mod}); })); // reduce modules must be flagged for bypass when running subsequent passes reduce_mod->set_bypass(); auto bip = mm->add_instruction( migraphx::make_op("fused_reduce", {{"axes", {1, 2}}}), {pow}, {reduce_mod}); mm->add_return({bip}); } EXPECT(p1 == p2); } TEST_CASE(pointwise_broadcast_reduce_reshape) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::shape rs{migraphx::shape::float_type, {2, 1}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", rs); auto sqrt = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("sqrt")); auto sqrtb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sqrtb); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto add = add_pointwise(p1, "main:pointwise1", {sqrtb, rsumb}, single_pointwise("add")); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {6}}}), add); mm->add_return({reshape}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", rs); auto add = add_reduce( p2, "main:pointwise0:main:reduce_sum0:main:pointwise1", {x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto sqrt = add_pointwise(p2, rm, "main:pointwise0", inputs, single_pointwise("sqrt")); auto sqrtb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto rsum = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), sqrtb); auto rsumb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); return add_pointwise( p2, rm, "main:pointwise1", {sqrtb, rsumb}, single_pointwise("add")); }); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {6}}}), add); mm->add_return({reshape}); } EXPECT(p1 == p2); } TEST_CASE(reduce_pointwise) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto add = add_pointwise(p1, "main:pointwise0", {rsumb, y}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = add_reduce( p2, "main:reduce_sum0:main:pointwise0", {x, y}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsumb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); return add_pointwise( p2, rm, "main:pointwise0", {rsumb, inputs[1]}, single_pointwise("add")); }); mm->add_return({add}); } EXPECT(p1 == p2); } TEST_CASE(reduce_pointwise_unfusable_broadcast) { migraphx::shape s{migraphx::shape::float_type, {2, 1, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), rsum); auto yb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), y); auto add = add_pointwise(p1, "main:pointwise0", {rsumb, yb}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto rsum = add_reduce( p2, "main:reduce_sum0", {x}, {2}, [&](auto* rm, const auto& inputs, const auto& axes) { return rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); }); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), rsum); auto yb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), y); auto add = add_pointwise(p2, "main:pointwise0", {rsumb, yb}, single_pointwise("add")); mm->add_return({add}); } EXPECT(p1 == p2); } TEST_CASE(reduce_reduce) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto rsumdiff = add_pointwise(p1, "main:pointwise0", {rsumb, x}, single_pointwise("sub")); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), rsumdiff); auto sqrt = add_pointwise(p1, "main:pointwise1", {rsum2}, single_pointwise("sqrt")); mm->add_return({sqrt}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto sqrt = add_reduce( p2, "main:reduce_sum1:main:reduce_sum0:main:pointwise0:main:pointwise1", {x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsumb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto rsumdiff = add_pointwise( p2, rm, "main:pointwise0", {rsumb, inputs[0]}, single_pointwise("sub")); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), rsumdiff); return add_pointwise(p2, rm, "main:pointwise1", {rsum2}, single_pointwise("sqrt")); }); mm->add_return({sqrt}); } EXPECT(p1 == p2); } TEST_CASE(reduce_reduce_unfusable_broadcast) { migraphx::shape s{migraphx::shape::float_type, {2, 1, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), rsum); auto xb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), x); auto rsumdiff = add_pointwise(p1, "main:pointwise0", {rsumb, xb}, single_pointwise("sub")); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), rsumdiff); auto sqrt = add_pointwise(p1, "main:pointwise1", {rsum2}, single_pointwise("sqrt")); mm->add_return({sqrt}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = add_reduce( p2, "main:reduce_sum0", {x}, {2}, [&](auto* rm, const auto& inputs, const auto& axes) { return rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); }); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), rsum); auto xb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 3}}}), x); auto sqrt = add_reduce( p2, "main:pointwise0:main:reduce_sum1:main:pointwise1", {rsumb, xb}, {2}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsumdiff = add_pointwise( p2, rm, "main:pointwise0", {inputs[0], inputs[1]}, single_pointwise("sub")); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), rsumdiff); return add_pointwise(p2, rm, "main:pointwise1", {rsum2}, single_pointwise("sqrt")); }); mm->add_return({sqrt}); } EXPECT(p1 == p2); } TEST_CASE(parallel_reduce_reduce) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto xx = add_pointwise(p1, "main:pointwise0", {x, x}, single_pointwise("mul")); auto rsumx = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto rsumxx = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), xx); auto add = add_pointwise(p1, "main:pointwise1", {rsumx, rsumxx}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto add = add_reduce( p2, "main:reduce_sum0:main:pointwise1:main:pointwise0:main:reduce_sum1", {x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto xx = add_pointwise( p2, rm, "main:pointwise0", {inputs[0], inputs[0]}, single_pointwise("mul")); auto rsumx = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsumxx = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), xx); return add_pointwise( p2, rm, "main:pointwise1", {rsumx, rsumxx}, single_pointwise("add")); }); mm->add_return({add}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(parallel_reduce_reduce_broadcast) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto sqrt = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("sqrt")); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sqrt); auto rsum1b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto relu = add_pointwise(p1, "main:pointwise1", {x}, single_pointwise("relu")); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), relu); auto add = add_pointwise(p1, "main:pointwise2", {rsum1, rsum2}, single_pointwise("add")); auto addb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), add); auto clip = add_pointwise(p1, "main:pointwise3", {x, rsum1b, addb}, single_pointwise("clip")); mm->add_return({clip}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto clip = add_reduce( p2, "main:pointwise1:main:reduce_sum1:main:pointwise2:main:pointwise3:main:pointwise0:main:" "reduce_sum0", {x}, {1}, [&](auto* rm, const auto& inputs, const auto&) { auto sqrt = add_pointwise(p2, rm, "main:pointwise0", {inputs[0]}, single_pointwise("sqrt")); auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sqrt); auto rsum1b = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto relu = add_pointwise(p2, rm, "main:pointwise1", {inputs[0]}, single_pointwise("relu")); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), relu); auto add = add_pointwise( p2, rm, "main:pointwise2", {rsum1, rsum2}, single_pointwise("add")); auto addb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), add); return add_pointwise( p2, rm, "main:pointwise3", {inputs[0], rsum1b, addb}, single_pointwise("clip")); }); mm->add_return({clip}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(parallel_reduce_reduce_broadcast_contiguous) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto sqrt = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("sqrt")); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sqrt); auto rsum1b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto rsum1bc = mm->add_instruction(migraphx::make_op("contiguous"), rsum1b); auto relu = add_pointwise(p1, "main:pointwise1", {x}, single_pointwise("relu")); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), relu); auto add = add_pointwise(p1, "main:pointwise2", {rsum1, rsum2}, single_pointwise("add")); auto addb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), add); auto clip = add_pointwise(p1, "main:pointwise3", {x, rsum1bc, addb}, single_pointwise("clip")); mm->add_return({clip}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto clip = add_reduce( p2, "main:pointwise1:main:reduce_sum1:main:pointwise2:main:pointwise3:main:pointwise0:main:" "reduce_sum0", {x}, {1}, [&](auto* rm, const auto& inputs, const auto&) { auto sqrt = add_pointwise(p2, rm, "main:pointwise0", {inputs[0]}, single_pointwise("sqrt")); auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sqrt); auto rsum1b = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto relu = add_pointwise(p2, rm, "main:pointwise1", {inputs[0]}, single_pointwise("relu")); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), relu); auto add = add_pointwise( p2, rm, "main:pointwise2", {rsum1, rsum2}, single_pointwise("add")); auto addb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), add); return add_pointwise( p2, rm, "main:pointwise3", {inputs[0], rsum1b, addb}, single_pointwise("clip")); }); mm->add_return({clip}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(reduce_reduce_mismatch_axis) { migraphx::shape s{migraphx::shape::float_type, {4, 2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), rsum1); mm->add_return({rsum2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum1 = add_reduce(p2, "main:reduce_sum0", {x}, {1}, single_reduce("reduce_sum")); auto rsum2 = add_reduce(p2, "main:reduce_sum1", {rsum1}, {2}, single_reduce("reduce_sum")); mm->add_return({rsum2}); } EXPECT(p1 == p2); } TEST_CASE(pointwise_reduce_broadcast) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto sqrt = add_pointwise(p1, "main:pointwise0", {rsum1}, single_pointwise("sqrt")); auto sqrtb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto add1 = add_pointwise(p1, "main:pointwise1", {sqrtb, x}, single_pointwise("add")); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), add1); auto add2 = add_pointwise(p1, "main:pointwise2", {rsum2, rsum1}, single_pointwise("add")); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto add2 = add_reduce( p2, "main:pointwise0:main:pointwise1:main:reduce_sum1:main:pointwise2:main:reduce_sum0", {x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto sqrt = add_pointwise(p2, rm, "main:pointwise0", {rsum1}, single_pointwise("sqrt")); auto sqrtb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto add1 = add_pointwise( p2, rm, "main:pointwise1", {sqrtb, inputs[0]}, single_pointwise("add")); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), add1); return add_pointwise( p2, rm, "main:pointwise2", {rsum2, rsum1}, single_pointwise("add")); }); mm->add_return({add2}); } EXPECT(p1 == p2); } TEST_CASE(pointwise_reduce_broadcast_contiguous) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto sqrt = add_pointwise(p1, "main:pointwise0", {rsum1}, single_pointwise("sqrt")); auto sqrtb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto sqrtbc = mm->add_instruction(migraphx::make_op("contiguous"), sqrtb); auto add1 = add_pointwise(p1, "main:pointwise1", {sqrtbc, x}, single_pointwise("add")); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), add1); auto add2 = add_pointwise(p1, "main:pointwise2", {rsum2, rsum1}, single_pointwise("add")); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto add2 = add_reduce( p2, "main:pointwise0:main:pointwise1:main:reduce_sum1:main:pointwise2:main:reduce_sum0", {x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto sqrt = add_pointwise(p2, rm, "main:pointwise0", {rsum1}, single_pointwise("sqrt")); auto sqrtb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto add1 = add_pointwise( p2, rm, "main:pointwise1", {sqrtb, inputs[0]}, single_pointwise("add")); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), add1); return add_pointwise( p2, rm, "main:pointwise2", {rsum2, rsum1}, single_pointwise("add")); }); mm->add_return({add2}); } EXPECT(p1 == p2); } TEST_CASE(reduce_reduce_broadcast) { migraphx::shape s{migraphx::shape::float_type, {4, 2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum1 = add_reduce(p1, "test:reduce_sum0", {x}, {1}, single_reduce("reduce_sum")); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto add = add_reduce( p1, "test:reduce_sum1", {rsumb, x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto add2 = add_pointwise(p1, rm, "test:pointwise0", inputs, single_pointwise("add")); return rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), add2); }); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = add_reduce( p2, "test:reduce_sum1:test:reduce_sum0", {x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsumb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto add = add_pointwise( p2, rm, "test:pointwise0", {rsumb, inputs[0]}, single_pointwise("add")); return rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), add); }); mm->add_return({rsum}); } EXPECT(p1 == p2); } TEST_CASE(reduce_reduce_broadcast_contiguous) { migraphx::shape s{migraphx::shape::float_type, {4, 2, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum1 = add_reduce(p1, "test:reduce_sum0", {x}, {1}, single_reduce("reduce_sum")); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto rsumbc = mm->add_instruction(migraphx::make_op("contiguous"), rsumb); auto add = add_reduce( p1, "test:reduce_sum1", {rsumbc, x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto add2 = add_pointwise(p1, rm, "test:pointwise0", inputs, single_pointwise("add")); return rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), add2); }); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = add_reduce( p2, "test:reduce_sum1:test:reduce_sum0", {x}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsumb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto add = add_pointwise( p2, rm, "test:pointwise0", {rsumb, inputs[0]}, single_pointwise("add")); return rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), add); }); mm->add_return({rsum}); } EXPECT(p1 == p2); } TEST_CASE(reduce_reshape_pointwise1) { migraphx::shape s1{migraphx::shape::float_type, {64, 4}}; migraphx::shape s2{migraphx::shape::float_type, {8, 8, 2, 2}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto rsumr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), rsumb); auto add = add_pointwise(p1, "main:pointwise0", {rsumr, y}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto xr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), x); auto add = add_reduce( p2, "main:reduce_sum0_reshape:main:pointwise0", {xr, y}, {2, 3}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsumb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s2.lens()}}), rsum); return add_pointwise( p2, rm, "main:pointwise0", {rsumb, inputs[1]}, single_pointwise("add")); }); mm->add_return({add}); } EXPECT(p1 == p2); } TEST_CASE(reduce_reshape_pointwise2) { migraphx::shape s1{migraphx::shape::float_type, {2, 32, 40960}}; migraphx::shape s2{migraphx::shape::float_type, {2, 320, 64, 64}}; migraphx::shape s3{migraphx::shape::float_type, {2, 32, 10, 64, 64}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto rsumr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), rsumb); auto add = add_pointwise(p1, "main:pointwise0", {rsumr, y}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto xr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), x); auto yr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), y); auto add = add_reduce( p2, "main:reduce_sum0_reshape:main:pointwise0", {xr, yr}, {2, 3, 4}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsumb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), rsum); return add_pointwise( p2, rm, "main:pointwise0", {rsumb, inputs[1]}, single_pointwise("add")); }); auto addr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), add); mm->add_return({addr}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(reduce_contiguous_reshape_pointwise) { migraphx::shape s1 = migraphx::shape::from_permutation(migraphx::shape::float_type, {2, 32, 40960}, {1, 0, 2}); auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 320, 64, 64}}; auto s3 = migraphx::shape{migraphx::shape::float_type, {2, 32, 10, 64, 64}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto rsumc = mm->add_instruction(migraphx::make_op("contiguous"), rsum); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsumc); auto rsumr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), rsumb); auto add = add_pointwise(p1, "main:pointwise0", {rsumr, y}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto xr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), x); auto yr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), y); auto add = add_reduce( p2, "main:reduce_sum0_reshape:main:pointwise0", {xr, yr}, {2, 3, 4}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsumb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), rsum); return add_pointwise( p2, rm, "main:pointwise0", {rsumb, inputs[1]}, single_pointwise("add")); }); auto addr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), add); mm->add_return({addr}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(reduce_reshape_reduce) { migraphx::shape s1{migraphx::shape::float_type, {2, 32, 4096}}; migraphx::shape s1r{migraphx::shape::float_type, {2, 32, 1}}; migraphx::shape s2{migraphx::shape::float_type, {4, 16, 64, 64}}; migraphx::shape s2r{migraphx::shape::float_type, {4, 16, 1, 1}}; migraphx::shape s3{migraphx::shape::float_type, {2, 2, 16, 64, 64}}; migraphx::shape s3r{migraphx::shape::float_type, {2, 2, 16, 1, 1}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x1 = mm->add_parameter("x1", s1); auto x2 = mm->add_parameter("x2", s1r); auto y = mm->add_parameter("y", s2); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x1); auto rsum1_add = add_pointwise(p1, "main:pointwise0", {rsum1, x2}, single_pointwise("add")); auto rsum1_addb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum1_add); auto rsum1_sub = add_pointwise(p1, "main:pointwise1", {rsum1_addb, x1}, single_pointwise("sub")); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), rsum1_sub); auto rsum2b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum2); auto rsum2_sub = add_pointwise(p1, "main:pointwise2", {rsum2b, x1}, single_pointwise("sub")); auto rsum2_subr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), rsum2_sub); auto rsum3 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3}}}), rsum2_subr); auto rsum3b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s2.lens()}}), rsum3); auto rsum3_add = add_pointwise(p1, "main:pointwise3", {rsum3b, y}, single_pointwise("add")); mm->add_return({rsum3_add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x1 = mm->add_parameter("x1", s1); auto x2 = mm->add_parameter("x2", s1r); auto y = mm->add_parameter("y", s2); auto x1r = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), x1); auto x2r = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3r.lens()}}), x2); auto yr = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), y); auto freduce = add_reduce( p2, "main:pointwise2:main:reduce_sum2_reshape_reshape:main:pointwise3_reshape:main:reduce_" "sum1:main:reduce_sum0:main:pointwise0:main:pointwise1_reshape", {x1r, x2r, yr}, {3, 4}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto add = add_pointwise( p2, rm, "main:pointwise0", {rsum1, inputs[1]}, single_pointwise("add")); auto addb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), add); auto sub1 = add_pointwise( p2, rm, "main:pointwise1", {addb, inputs[0]}, single_pointwise("sub")); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), sub1); auto rsum2b = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), rsum2); auto sub2 = add_pointwise( p2, rm, "main:pointwise2", {rsum2b, inputs[0]}, single_pointwise("sub")); auto rsum3 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), sub2); auto rsum3b = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), rsum3); return add_pointwise( p2, rm, "main:pointwise3", {rsum3b, inputs[2]}, single_pointwise("add")); }); auto freducer = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), freduce); mm->add_return({freducer}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(reshape_reduce_reduce_reduce_diff_axes) { migraphx::shape s1{migraphx::shape::float_type, {128, 196, 256}}; migraphx::shape s2{migraphx::shape::float_type, {1}}; migraphx::shape s3{migraphx::shape::float_type, {25088, 256}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x1 = mm->add_parameter("x1", s1); auto x2 = mm->add_parameter("x2", s1); auto l1 = mm->add_literal(migraphx::literal(s2, {1.0})); auto l2 = mm->add_literal(migraphx::literal(s2, {2.0})); auto x1_rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), x1); auto l2_mb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), l2); auto l2_ct = mm->add_instruction(migraphx::make_op("contiguous"), l2_mb); auto pw0 = add_pointwise(p1, "main:pointwise0", {l2_ct, x1_rsp}, single_pointwise("add")); auto pw0_rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s1.lens()}}), pw0); auto pw1 = add_pointwise(p1, "main:pointwise1", {x2, pw0_rsp}, single_pointwise("mul")); auto pw2 = add_pointwise(p1, "main:pointwise2", {pw1}, single_pointwise("log")); auto rsum0 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), pw2); auto rsum0_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum0); auto rsum0_ct = mm->add_instruction(migraphx::make_op("contiguous"), rsum0_mb); auto pw3 = add_pointwise(p1, "main:pointwise3", {pw0_rsp, rsum0_ct}, single_pointwise("add")); auto pw4 = add_pointwise(p1, "main:pointwise4", {pw3}, single_pointwise("log")); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), pw4); auto pw5 = add_pointwise(p1, "main:pointwise5", {rsum1}, single_pointwise("log")); auto pw5_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), pw5); auto pw5_ct = mm->add_instruction(migraphx::make_op("contiguous"), pw5_mb); auto l1_mb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), l1); auto pw6 = add_pointwise(p1, "main:pointwise6", {pw5_ct, l1_mb}, single_pointwise("mul")); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), pw6); mm->add_return({rsum2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x1 = mm->add_parameter("x1", s1); auto x2 = mm->add_parameter("x2", s1); auto l1 = mm->add_literal(migraphx::literal(s2, {1.0})); auto l2 = mm->add_literal(migraphx::literal(s2, {2.0})); auto l1_mb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), l1); auto l2_mb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), l2); auto reduce0 = add_reduce( p2, "main:pointwise0:main:pointwise1:main:reduce_sum1:main:pointwise2:main:reduce_sum0:" "main:pointwise3:main:pointwise4:main:pointwise5:main:pointwise6_reshape", {l2_mb, x1, x2, l1_mb}, {2}, [&](auto* rm, const auto& inputs, const auto& axes) { auto pw0 = add_pointwise( p2, rm, "main:pointwise0", {inputs[0], inputs[1]}, single_pointwise("add")); auto pw1 = add_pointwise( p2, rm, "main:pointwise1", {inputs[2], pw0}, single_pointwise("mul")); auto pw2 = add_pointwise(p2, rm, "main:pointwise2", {pw1}, single_pointwise("log")); auto rsum0 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), pw2); auto rsum0_mb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum0); auto pw3 = add_pointwise( p2, rm, "main:pointwise3", {pw0, rsum0_mb}, single_pointwise("add")); auto pw4 = add_pointwise(p2, rm, "main:pointwise4", {pw3}, single_pointwise("log")); auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), pw4); auto pw5 = add_pointwise(p2, rm, "main:pointwise5", {rsum1}, single_pointwise("log")); auto pw5_mb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), pw5); return add_pointwise( p2, rm, "main:pointwise6", {pw5_mb, inputs[3]}, single_pointwise("mul")); }); auto reduce1 = add_reduce(p2, "main:reduce_sum2", {reduce0}, {1}, [&](auto* rm, const auto& inputs, const auto& axes) { return rm->add_instruction( migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); }); mm->add_return({reduce1}); } EXPECT(p1.sort() == p2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/generate.cpp000066400000000000000000000057671510465702400200340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "test.hpp" TEST_CASE(generate) { migraphx::shape s{migraphx::shape::float_type, {4, 4, 1, 1}}; EXPECT(migraphx::generate_literal(s, 1) == migraphx::generate_argument(s, 1)); EXPECT(migraphx::generate_literal(s, 1) != migraphx::generate_argument(s, 0)); } TEST_CASE(fill_tuple) { migraphx::shape s0{migraphx::shape::float_type, {4, 4, 1, 1}}; migraphx::shape s1{migraphx::shape::int32_type, {2, 3}}; migraphx::shape s2{migraphx::shape::bool_type, {3, 2}}; migraphx::shape s({s0, s1, s2}); auto arg = migraphx::fill_argument(s, 1); const auto& args = arg.get_sub_objects(); EXPECT(args.at(0) == migraphx::fill_argument(s0, 1)); EXPECT(args.at(1) == migraphx::fill_argument(s1, 1)); EXPECT(args.at(2) == migraphx::fill_argument(s2, 1)); } TEST_CASE(generate_non_computable) { migraphx::shape s{migraphx::shape::fp4x2_type, {4, 4, 1, 1}}; auto arg = migraphx::generate_argument(s, 1); EXPECT(migraphx::generate_literal(s, 1) == migraphx::generate_argument(s, 1)); } TEST_CASE(generate_tuple) { migraphx::shape s0{migraphx::shape::float_type, {4, 4, 1, 1}}; migraphx::shape s1{migraphx::shape::int32_type, {2, 3}}; migraphx::shape s2{migraphx::shape::bool_type, {3, 2}}; migraphx::shape s({s0, s1, s2}); auto arg = migraphx::generate_argument(s, 1); const auto& args = arg.get_sub_objects(); EXPECT(args.at(0) == migraphx::generate_argument(s0, 1)); EXPECT(args.at(1) == migraphx::generate_argument(s1, 1)); EXPECT(args.at(2) == migraphx::generate_argument(s2, 1)); EXPECT(args.at(0) != migraphx::generate_argument(s0, 0)); EXPECT(args.at(1) != migraphx::generate_argument(s1, 2)); EXPECT(args.at(2) != migraphx::generate_argument(s2, 0)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/000077500000000000000000000000001510465702400163125ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/gpu/adjust_allocation.cpp000066400000000000000000000120301510465702400225110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "make_precompile_op.hpp" // Treat some operators as compilable to enable lowering MIGRAPHX_GPU_TEST_PRECOMPILE("add", "mul", "convert") static void run_lowering(migraphx::program& p, bool offload_copy = false) { auto ctx = migraphx::gpu::context{}; migraphx::run_passes( *p.get_main_module(), {migraphx::auto_contiguous{}, migraphx::gpu::lowering{&ctx, offload_copy}, migraphx::dead_code_elimination{}, migraphx::eliminate_contiguous{"gpu::contiguous"}, migraphx::dead_code_elimination{}, migraphx::replace_allocate{migraphx::gpu::gpu_allocation_model{}, offload_copy}, migraphx::dead_code_elimination{}}); } TEST_CASE(tanh_shape) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", s); auto tx = mm->add_instruction(migraphx::op::transpose{{1, 0}}, x); auto txh = mm->add_instruction(migraphx::op::tanh{}, tx); auto sum = mm->add_instruction(migraphx::op::add{}, txh, txh); mm->add_instruction(migraphx::op::contiguous{}, sum); return p; }; auto p1 = create_program(); auto p2 = create_program(); EXPECT(p1 == p2); run_lowering(p1); run_lowering(p2); EXPECT(p1 == p2); for(auto ins : iterator_for(*p1.get_main_module())) { if(ins->name() == "hip::allocate") { migraphx::shape new_s{migraphx::shape::float_type, {3, 2}, {1, 3}}; ins->replace(migraphx::gpu::hip_allocate{new_s}); } } EXPECT(p1 != p2); migraphx::run_passes(*p2.get_main_module(), {migraphx::adjust_allocation{migraphx::gpu::gpu_allocation_model{}}, migraphx::dead_code_elimination{}}); EXPECT(p1 == p2); } TEST_CASE(no_copy_dead_param) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", s); mm->add_parameter("y", s); auto sum = mm->add_instruction(migraphx::make_op("add"), x, x); mm->add_return({sum}); return p; }; auto create_gpu_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", s); mm->add_parameter("y", s); auto xb = mm->add_instruction(migraphx::make_op("hip::allocate", {{"shape", to_value(s)}})); auto gx = mm->add_instruction(migraphx::make_op("hip::copy_to_gpu"), x, xb); auto ab = mm->add_instruction(migraphx::make_op("hip::allocate", {{"shape", to_value(s)}})); auto sum = mm->add_instruction(make_precompile_op("add"), gx, gx, ab); auto r = mm->add_instruction(migraphx::make_op("hip::copy_from_gpu"), sum); mm->add_return({r}); return p; }; auto p1 = create_program(); auto p2 = create_gpu_program(); run_lowering(p1, true); EXPECT(p1 == p2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/codegen_literal.cpp000066400000000000000000000060671510465702400221470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include static void run_prog(migraphx::program p, const migraphx::target& t, migraphx::parameter_map& m_in, std::vector& res) { p.compile(t); migraphx::parameter_map m; for(auto&& x : p.get_parameter_shapes()) { if(m_in.count(x.first) > 0) { m[x.first] = t.copy_to(m_in[x.first]); } else { m[x.first] = t.allocate(x.second); } } auto result = t.copy_from(p.eval(m).back()); result.visit([&](auto v) { res.assign(v.begin(), v.end()); }); } // This test ensures that the codegen path doesn't round up literals, // otherwise there are accuracy differences compared to ref. // The values being passed in are 0.5 * (1/0.00787402), // and after rounding must equal 63, not 64. TEST_CASE(mul_literal_round_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1}}; auto l0 = mm->add_parameter("a", s0); auto l1 = mm->add_literal(1 / 0.00787402f); auto mul = mm->add_instruction(migraphx::make_op("mul"), l0, l1); auto round = mm->add_instruction(migraphx::make_op("nearbyint"), mul); mm->add_return({round}); migraphx::parameter_map m; std::vector a = {0.5f}; m["a"] = migraphx::argument{s0, a.data()}; std::vector ref_result; migraphx::target ref_t = migraphx::make_target("ref"); run_prog(p, ref_t, m, ref_result); std::vector gpu_result; migraphx::target gpu_t = migraphx::make_target("gpu"); run_prog(p, gpu_t, m, gpu_result); EXPECT(migraphx::verify::verify_rms_range(gpu_result, ref_result)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/compile_gen.cpp000066400000000000000000000037341510465702400213060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include static const auto find_fast_axis = test::make_function("find_fast_axis", [](auto&&... xs) { return migraphx::gpu::gen::find_fast_axis(static_cast(xs)...); }); TEST_CASE(test_find_fast_axis) { EXPECT(find_fast_axis(migraphx::shape{migraphx::shape::float_type, {2, 2, 2, 6, 3}}) == 4); EXPECT(find_fast_axis(migraphx::shape{ migraphx::shape::float_type, {2, 2, 2, 6, 3}, {72, 6, 1, 12, 2}}) == 2); EXPECT(find_fast_axis( migraphx::shape{migraphx::shape::float_type, {64, 512, 32, 32}, {0, 1, 0, 0}}) == 1); EXPECT(find_fast_axis( migraphx::shape{migraphx::shape::float_type, {64, 512, 32, 32}, {0, 0, 0, 0}}) == 3); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/compile_hipblaslt.cpp000066400000000000000000000064421510465702400225160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_SET_GEMM_PROVIDER) #if MIGRAPHX_USE_HIPBLASLT static void run_lowering(migraphx::module& m, bool offload_copy = false) { auto ctx = migraphx::gpu::context{}; migraphx::run_passes(m, {migraphx::gpu::lowering{&ctx, offload_copy}}); } TEST_CASE(hipblaslt_op) { if(not(migraphx::string_value_of(MIGRAPHX_SET_GEMM_PROVIDER{}) == "rocblas") and migraphx::gpu::hipblaslt_supported() and not migraphx::gpu::gfx_default_rocblas()) { migraphx::module m1; { migraphx::shape sa{migraphx::shape::float_type, {4, 2}}; migraphx::shape sb{migraphx::shape::float_type, {2, 3}}; migraphx::shape s_output{migraphx::shape::float_type, {4, 3}}; auto a = m1.add_parameter("a", sa); auto b = m1.add_parameter("b", sb); migraphx::operation dot_op = migraphx::make_op("dot"); m1.add_instruction(dot_op, a, b); } run_lowering(m1); migraphx::module m2; { auto a = m2.add_parameter("a", {migraphx::shape::float_type, {4, 2}}); auto b = m2.add_parameter("b", {migraphx::shape::float_type, {2, 3}}); migraphx::shape output_shape{migraphx::shape::float_type, {4, 3}, {3, 1}}; // Add an allocate instruction for the output auto output = m2.add_instruction(migraphx::op::allocate{output_shape, std::nullopt}); migraphx::op::dot dot_instance; migraphx::gpu::hipblaslt_op hipblaslt_operator; hipblaslt_operator.op = migraphx::gpu::hip_gemm{dot_instance, 1, 0}; m2.add_instruction(hipblaslt_operator, a, b, output); } EXPECT(m1 == m2); } } #endif int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/context_serialize.cpp000066400000000000000000000037541510465702400225620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include "test.hpp" TEST_CASE(gpu_context_serialize) { migraphx::context ctx = migraphx::gpu::context{0, 3}; auto v = ctx.to_value(); EXPECT(v.size() == 2); EXPECT(v.contains("events")); EXPECT(v.at("events").without_key().to() == 0); EXPECT(v.contains("streams")); EXPECT(v.at("streams").without_key().to() == 3); migraphx::gpu::context g_ctx; g_ctx.from_value(v); auto v1 = g_ctx.to_value(); EXPECT(v == v1); } TEST_CASE(context_queue) { migraphx::context ctx = migraphx::gpu::context{0, 3}; EXPECT(ctx.get_queue().get() != nullptr); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/fuse_gemm.cpp000066400000000000000000000101611510465702400207640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_SET_GEMM_PROVIDER) #if MIGRAPHX_USE_HIPBLASLT static void run_lowering(migraphx::program& p, bool offload_copy = false) { auto ctx = migraphx::gpu::context{}; migraphx::run_passes( *p.get_main_module(), {migraphx::auto_contiguous{}, migraphx::gpu::lowering{&ctx, offload_copy}}); } static void run_fuse_ops(migraphx::program& p) { migraphx::run_passes(p, {migraphx::gpu::fuse_ops{}, migraphx::dead_code_elimination{}}); } TEST_CASE(gemm_pointwise_add) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot, x}, single_pointwise("add")); mm->add_return({add}); } run_lowering(p1); run_fuse_ops(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto output = mm->add_instruction(migraphx::op::allocate{s, std::nullopt}); if(not(migraphx::string_value_of(MIGRAPHX_SET_GEMM_PROVIDER{}) == "rocblas") and migraphx::gpu::hipblaslt_supported() and not migraphx::gpu::gfx_default_rocblas()) { migraphx::op::dot dot_instance; migraphx::gpu::hipblaslt_op hipblaslt_operator; hipblaslt_operator.op = migraphx::gpu::hip_gemm{dot_instance, 1, 1}; auto add = mm->add_instruction(hipblaslt_operator, a, b, x, output); mm->add_return({add}); } else { auto gemm_oper = migraphx::make_op("gpu::gemm", {{"alpha", 1}, {"beta", 1}, {"compute_fp32", migraphx::gpu::get_compute_fp32_flag()}}); auto add = mm->add_instruction(gemm_oper, a, b, x, output); mm->add_return({add}); } } EXPECT(p1.sort() == p2.sort()); } #endif int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/fuse_mlir.cpp000066400000000000000000003672531510465702400210230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_MLIR_INPUT_FUSION); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_MLIR_REDUCE_FUSION); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_USE_SPECIFIC_OPS); MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_MLIR_GEG_FUSION); struct non_mlir_op { std::string name() const { return "non_mlir_op"; } migraphx::shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this}.has(1); return inputs.at(0); } }; static void run_pass(migraphx::program& p) { migraphx::run_passes( p, {migraphx::gpu::fuse_mlir{.enable_extra = true}, migraphx::dead_code_elimination{}}); } template static migraphx::instruction_ref add_mlir(migraphx::program& p, const std::string& name, std::vector inputs, std::vector arg_names, const F& f) { assert(inputs.size() == arg_names.size() and "One interior parameter name given per input."); auto* mm = p.get_main_module(); auto* pm = p.create_module(name); pm->set_bypass(); std::vector params; for(size_t i = 0, e = inputs.size(); i < e; ++i) { params.push_back(pm->add_parameter(arg_names[i], inputs[i]->get_shape().as_standard())); } auto values = f(pm, params); auto root = std::get<0>(values); auto r = std::get<1>(values); auto_add_return(pm, r); return mm->add_instruction( migraphx::make_op("gpu::mlir_op", {{"op", migraphx::to_value(root)}}), inputs, {pm}); } template static migraphx::instruction_ref add_mlir(migraphx::program& p, const std::string& name, std::vector inputs, const F& f) { std::vector arg_names; migraphx::transform(migraphx::range(inputs.size()), std::back_inserter(arg_names), [&](auto i) { return migraphx::param_name(i); }); return add_mlir(p, name, std::move(inputs), std::move(arg_names), std::move(f)); } TEST_CASE(dot_reshapes_add) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 3}}); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto dot_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot); auto dot_sq = mm->add_instruction(migraphx::make_op("squeeze"), dot_trans); auto add = add_pointwise(p1, "main:pointwise0", {dot_sq, x}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 3}}); auto fused = add_mlir(p2, "mlir_main:pointwise0", {a, b, x}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto dot_trans = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot); auto dot_rsp = pm->add_instruction(migraphx::make_op("squeeze"), dot_trans); auto add = pm->add_instruction(migraphx::make_op("add"), dot_rsp, inputs[2]); return std::make_tuple(dot->get_operator(), add); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot, x}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto fused = add_mlir(p2, "mlir_main:pointwise0", {a, b, x}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot, inputs[2]); return std::make_tuple(dot->get_operator(), add); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_transpose_reshape_add) { migraphx::shape s1{migraphx::shape::float_type, {1, 6, 6}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3, 6}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s1); auto x = mm->add_parameter("x", s1); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto xtranspose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), x); auto xreshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", s1.lens()}}), xtranspose); auto add = add_pointwise(p1, "main:pointwise0", {dot, xreshape}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s1); auto x = mm->add_parameter("x", s1); auto fused = add_mlir(p2, "mlir_main:pointwise0", {a, b, x}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto xtranspose = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), inputs[2]); auto xreshape = pm->add_instruction( migraphx::make_op("reshape", {{"dims", s1.lens()}}), xtranspose); auto add = pm->add_instruction(migraphx::make_op("add"), dot, xreshape); return std::make_tuple(dot->get_operator(), add); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_reshape_lazy_add) { migraphx::shape s1{migraphx::shape::float_type, {1, 6, 6}}; migraphx::shape s2{migraphx::shape::float_type, {1, 36}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s1); auto x = mm->add_parameter("x", s2); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto xreshape_lazy = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", s1.lens()}}), x); auto add = add_pointwise(p1, "main:pointwise0", {dot, xreshape_lazy}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s1); auto x = mm->add_parameter("x", s2); auto fused = add_mlir(p2, "mlir_main:pointwise0", {a, b, x}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto xreshape_lazy = pm->add_instruction( migraphx::make_op("reshape_lazy", {{"dims", s1.lens()}}), inputs[2]); auto add = pm->add_instruction(migraphx::make_op("add"), dot, xreshape_lazy); return std::make_tuple(dot->get_operator(), add); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(conv_backwards) { migraphx::shape os{migraphx::shape::float_type, {{1, 1, 5, 5}}}; migraphx::shape is{migraphx::shape::float_type, {1, 1, 3, 3}}; migraphx::shape ws{migraphx::shape::float_type, {1, 1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", is); auto w = mm->add_parameter("w", ws); auto conv_bk = mm->add_instruction(migraphx::make_op("convolution_backwards"), x, w); mm->add_return({conv_bk}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", is); auto w = mm->add_parameter("w", ws); auto conv_bk = add_mlir(p2, "mlir_convolution_backwards0", {x, w}, {"y0", "y1"}, [=](auto* pm, const auto& inputs) { auto c = pm->add_instruction( migraphx::make_op("convolution_backwards"), inputs[0], inputs[1]); return std::make_tuple(c->get_operator(), c); }); mm->add_return({conv_bk}); } std::string opt = migraphx::string_value_of(MIGRAPHX_MLIR_USE_SPECIFIC_OPS{}, ""); if(opt.find("convolution_backwards") != std::string::npos) EXPECT(p1.sort() == p2.sort()); } TEST_CASE(conv_broadcast_mul) { migraphx::shape os{migraphx::shape::float_type, {4, 56, 122, 122}}; migraphx::shape is{migraphx::shape::float_type, {4, 14, 1, 1}}; migraphx::shape ws{migraphx::shape::float_type, {56, 14, 1, 1}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", is); auto y = mm->add_parameter("y", os); auto w = mm->add_parameter("w", ws); auto conv = mm->add_instruction(migraphx::make_op("convolution"), x, w); auto convb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", os.lens()}}), conv); auto mul = add_pointwise(p1, "main:pointwise0", {convb, y}, single_pointwise("mul")); mm->add_return({mul}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", is); auto y = mm->add_parameter("y", os); auto w = mm->add_parameter("w", ws); auto conv = add_mlir( p2, "mlir_convolution0", {x, w}, {"y0", "y1"}, [=](auto* pm, const auto& inputs) { auto c = pm->add_instruction(migraphx::make_op("convolution"), inputs[0], inputs[1]); return std::make_tuple(c->get_operator(), c); }); auto convb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", os.lens()}}), conv); auto mul = add_pointwise(p2, "main:pointwise0", {convb, y}, single_pointwise("mul")); mm->add_return({mul}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(multi_use_dot_trans_add_pooling_sub) { migraphx::shape s1{migraphx::shape::float_type, {1, 1, 4, 5}}; migraphx::shape s2{migraphx::shape::float_type, {1, 1, 5, 5}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 4}}); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto dot_trans = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), dot); auto add = add_pointwise(p1, "main:pointwise0", {dot_trans, x}, single_pointwise("add")); auto pooling = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {0, 0, 0, 1}}, {"stride", {1, 1}}, {"lengths", {2, 1}}, {"lp_order", 2}}), add); auto sub = add_pointwise(p1, "main:pointwise1", {dot, pooling}, single_pointwise("sub")); mm->add_return({sub}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 4}}); auto fused = add_mlir(p2, "mlir_main:pointwise0", {a, b, x}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto dot_trans = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), dot); auto add = pm->add_instruction(migraphx::make_op("add"), dot_trans, inputs[2]); return std::make_tuple(dot->get_operator(), std::vector{add, dot}); }); auto fused_dot_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto pooling = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {0, 0, 0, 1}}, {"stride", {1, 1}}, {"lengths", {2, 1}}, {"lp_order", 2}}), fused_dot_add); auto dot = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto sub = add_pointwise(p2, "main:pointwise1", {dot, pooling}, single_pointwise("sub")); mm->add_return({sub}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_multi_use_trans_add_pooling_sub) { migraphx::shape s1{migraphx::shape::float_type, {1, 4, 5}}; migraphx::shape s2{migraphx::shape::float_type, {1, 5, 5}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 4}}); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto dot_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot); auto dot_unsq = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 1, 5, 4}}}), dot_trans); auto add = add_pointwise(p1, "main:pointwise0", {dot_unsq, x}, single_pointwise("add")); auto pooling = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {1, 0, 0, 0}}, {"stride", {1, 1}}, {"lengths", {2, 1}}, {"lp_order", 2}}), add); auto sub = add_pointwise(p1, "main:pointwise1", {dot_unsq, pooling}, single_pointwise("sub")); mm->add_return({sub}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 4}}); auto fused = add_mlir(p2, "mlir_main:pointwise0", {a, b, x}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto dot_trans = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot); auto dot_unsq = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {1, 1, 5, 4}}}), dot_trans); auto add = pm->add_instruction(migraphx::make_op("add"), dot_unsq, inputs[2]); return std::make_tuple(dot->get_operator(), std::vector{add, dot}); }); auto fused_dot_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto pooling = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {1, 0, 0, 0}}, {"stride", {1, 1}}, {"lengths", {2, 1}}, {"lp_order", 2}}), fused_dot_add); auto dot = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto dot_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot); auto dot_reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 1, 5, 4}}}), dot_trans); auto sub = add_pointwise(p2, "main:pointwise1", {dot_reshape, pooling}, single_pointwise("sub")); mm->add_return({sub}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_dot_pointwise) { migraphx::shape s1{migraphx::shape::float_type, {1, 4, 5}}; migraphx::shape s2{migraphx::shape::float_type, {1, 5, 5}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto c = mm->add_parameter("c", s2); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), dot1, c); auto add = add_pointwise(p1, "main:pointwise0", {dot1, dot2}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto c = mm->add_parameter("c", s2); auto dot1 = add_mlir(p2, "mlir_dot0", {a, b}, {"y0", "y1"}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); return std::make_tuple(dot->get_operator(), dot); }); auto dot2 = add_mlir(p2, "mlir_dot1", {dot1, c}, {"y0", "y1"}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); return std::make_tuple(dot->get_operator(), dot); }); auto add = add_pointwise(p2, "main:pointwise0", {dot1, dot2}, single_pointwise("add")); mm->add_return({add}); } EXPECT(p1 == p2); } TEST_CASE(dot_dot_pointwise_pointwise) { migraphx::shape s1{migraphx::shape::float_type, {1, 4, 5}}; migraphx::shape s2{migraphx::shape::float_type, {1, 5, 5}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto c = mm->add_parameter("c", s2); auto x = mm->add_parameter("d", s1); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), dot1, c); auto add1 = add_pointwise(p1, "main:pointwise0", {dot2, x}, single_pointwise("add")); auto add2 = add_pointwise(p1, "main:pointwise1", {dot1, add1}, single_pointwise("add")); mm->add_return({add2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto c = mm->add_parameter("c", s2); auto x = mm->add_parameter("d", s1); auto dot1 = add_mlir(p2, "mlir_dot0", {a, b}, {"y0", "y1"}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); return std::make_tuple(dot->get_operator(), dot); }); auto fused = add_mlir(p2, "mlir_main:pointwise0", {dot1, c, x}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot, inputs[2]); return std::make_tuple(dot->get_operator(), add); }); auto add2 = add_pointwise(p2, "main:pointwise1", {dot1, fused}, single_pointwise("add")); mm->add_return({add2}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(add_dot) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = add_pointwise(p1, "main:pointwise0", {x, y}, single_pointwise("add")); auto dot = mm->add_instruction(migraphx::make_op("dot"), add, b); mm->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto fused = add_mlir(p2, "main:pointwise0:mlir_dot0", {x, y, b}, {"x0", "x1", "x2"}, [=](auto* pm, const auto& inputs) { auto add = pm->add_instruction(migraphx::make_op("add"), inputs[0], inputs[1]); auto dot = pm->add_instruction(migraphx::make_op("dot"), add, inputs[2]); return std::make_tuple(dot->get_operator(), dot); }); mm->add_return({fused}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_INPUT_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(relu_dot) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto relu = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("relu")); auto dot = mm->add_instruction(migraphx::make_op("dot"), relu, b); mm->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto fused = add_mlir(p2, "main:pointwise0:mlir_dot0", {x, b}, {"x0", "x1"}, [=](auto* pm, const auto& inputs) { auto relu = pm->add_instruction(migraphx::make_op("relu"), inputs[0]); auto dot = pm->add_instruction(migraphx::make_op("dot"), relu, inputs[1]); return std::make_tuple(dot->get_operator(), dot); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(relu_relu_dot) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto relux = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("relu")); auto reluy = add_pointwise(p1, "main:pointwise1", {y}, single_pointwise("relu")); auto dot = mm->add_instruction(migraphx::make_op("dot"), relux, reluy); mm->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto fused = add_mlir(p2, "main:pointwise0:main:pointwise1:mlir_dot0", {x, y}, {"x0", "x1"}, [=](auto* pm, const auto& inputs) { auto relux = pm->add_instruction(migraphx::make_op("relu"), inputs[0]); auto reluy = pm->add_instruction(migraphx::make_op("relu"), inputs[1]); auto dot = pm->add_instruction(migraphx::make_op("dot"), relux, reluy); return std::make_tuple(dot->get_operator(), dot); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dequantizelinear_dot) { migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 3, 5}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::int8_type, {2, 5, 2}}); auto scalelit = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 2, 2}})); auto zplit = mm->add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {2, 2, 2}})); auto unsqueeze1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scalelit); auto broadcast1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze1); auto reshape1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto unsqueeze2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), zplit); auto broadcast2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze2); auto reshape2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto dq = add_pointwise( p1, "main:pointwise0", {y, scale, zp}, single_pointwise("dequantizelinear")); auto dot = mm->add_instruction(migraphx::make_op("dot"), x, dq); mm->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 3, 5}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::int8_type, {2, 5, 2}}); auto scalelit = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 2, 2}})); auto zplit = mm->add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {2, 2, 2}})); auto fused = add_mlir( p2, "main:pointwise0:mlir_dot0", {y, scalelit, zplit, x}, {"x0", "x1", "x2", "x3"}, [=](auto* pm, const auto& inputs) { auto unsqueeze1 = pm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), inputs[1]); auto broadcast1 = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze1); auto reshape1 = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = pm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto unsqueeze2 = pm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), inputs[2]); auto broadcast2 = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze2); auto reshape2 = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = pm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto dq = pm->add_instruction( migraphx::make_op("dequantizelinear"), inputs[0], scale, zp); auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[3], dq); return std::make_tuple(dot->get_operator(), dot); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(unsigned_dequantizelinear_dot) { migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 3, 5}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::uint8_type, {2, 5, 2}}); auto scalelit = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 2, 2}})); auto zplit = mm->add_literal(migraphx::generate_literal({migraphx::shape::uint8_type, {2, 2, 2}})); auto unsqueeze1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scalelit); auto broadcast1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze1); auto reshape1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto unsqueeze2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), zplit); auto broadcast2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze2); auto reshape2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto dq = add_pointwise( p1, "main:pointwise0", {y, scale, zp}, single_pointwise("dequantizelinear")); auto dot = mm->add_instruction(migraphx::make_op("dot"), x, dq); mm->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 3, 5}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::uint8_type, {2, 5, 2}}); auto scalelit = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 2, 2}})); auto zplit = mm->add_literal(migraphx::generate_literal({migraphx::shape::uint8_type, {2, 2, 2}})); auto fused = add_mlir( p2, "main:pointwise0:mlir_dot0", {y, scalelit, zplit, x}, {"x0", "x1", "x2", "x3"}, [=](auto* pm, const auto& inputs) { auto unsqueeze1 = pm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), inputs[1]); auto broadcast1 = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze1); auto reshape1 = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = pm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto unsqueeze2 = pm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), inputs[2]); auto broadcast2 = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze2); auto reshape2 = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = pm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto dq = pm->add_instruction( migraphx::make_op("dequantizelinear"), inputs[0], scale, zp); auto dot = pm->add_instruction(migraphx::make_op("dot"), inputs[3], dq); return std::make_tuple(dot->get_operator(), dot); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(unpack_int4_dot) { migraphx::program p1; { auto* m = p1.get_main_module(); auto x = m->add_parameter("x", {migraphx::shape::int8_type, {1, 8, 4, 4}}); auto pk_w = m->add_parameter("wt_int4", {migraphx::shape::int8_type, {1, 8, 4, 2}}); auto w = m->add_instruction(migraphx::make_op("unpack_int4"), pk_w); auto dot = m->add_instruction(migraphx::make_op("quant_dot"), x, w); // w: {1,8,4,4} m->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* m = p2.get_main_module(); auto x = m->add_parameter("x", {migraphx::shape::int8_type, {1, 8, 4, 4}}); auto pk_w = m->add_parameter("wt_int4", {migraphx::shape::int8_type, {1, 8, 4, 2}}); auto fused = add_mlir( p2, "int4:mlir_quant_dot0", {x, pk_w}, {"x1", "x2"}, [=](auto* pm, const auto& inputs) { auto unpk_w = pm->add_instruction(migraphx::make_op("unpack_int4"), inputs[1]); auto q = pm->add_instruction(migraphx::make_op("quant_dot"), inputs[0], unpk_w); return std::make_tuple(q->get_operator(), q); }); m->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(unpack_int4_dot_2) { migraphx::program p1; { auto* m = p1.get_main_module(); auto pk_x = m->add_parameter("x", {migraphx::shape::int8_type, {1, 8, 4, 2}}); auto x = m->add_instruction(migraphx::make_op("unpack_int4"), pk_x); // {1,8,4,4} auto pk_w = m->add_parameter("wt_int4", {migraphx::shape::int8_type, {1, 8, 4, 2}}); auto w = m->add_instruction(migraphx::make_op("unpack_int4"), pk_w); // {1,8,4,4} auto dot = m->add_instruction(migraphx::make_op("quant_dot"), x, w); m->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* m = p2.get_main_module(); auto x = m->add_parameter("x", {migraphx::shape::int8_type, {1, 8, 4, 2}}); auto pk_w = m->add_parameter("wt_int4", {migraphx::shape::int8_type, {1, 8, 4, 2}}); auto fused = add_mlir( p2, "int4:mlir_quant_dot0", {x, pk_w}, {"x1", "x2"}, [=](auto* pm, const auto& inputs) { auto unpk_x = pm->add_instruction(migraphx::make_op("unpack_int4"), inputs[0]); auto unpk_w = pm->add_instruction(migraphx::make_op("unpack_int4"), inputs[1]); auto q = pm->add_instruction(migraphx::make_op("quant_dot"), unpk_x, unpk_w); return std::make_tuple(q->get_operator(), q); }); m->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(int_quant_dot_abs) { migraphx::shape s_a{migraphx::shape::int8_type, {5, 4}}; migraphx::shape s_b{migraphx::shape::int8_type, {4, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s_a); auto b = mm->add_parameter("b", s_b); auto dot = mm->add_instruction(migraphx::make_op("quant_dot"), a, b); auto abs = add_pointwise(p1, "main:pointwise0", {dot}, single_pointwise("abs")); mm->add_return({abs}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s_a); auto b = mm->add_parameter("b", s_b); auto fused = add_mlir(p2, "mlir_main:pointwise0", {a, b}, [=](auto* pm, const auto& inputs) { auto dot = pm->add_instruction(migraphx::make_op("quant_dot"), inputs[0], inputs[1]); auto abs = pm->add_instruction(migraphx::make_op("abs"), dot); return std::make_tuple(dot->get_operator(), abs); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(int_quant_dot_tanh_fails) { migraphx::shape s_a{migraphx::shape::int8_type, {5, 4}}; migraphx::shape s_b{migraphx::shape::int8_type, {4, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s_a); auto b = mm->add_parameter("b", s_b); auto dot = mm->add_instruction(migraphx::make_op("quant_dot"), a, b); auto tanh = add_pointwise(p1, "main:pointwise0", {dot}, single_pointwise("tanh")); mm->add_return({tanh}); } // This pass should not fuse as int32_t tanh isn't supported. run_pass(p1); auto* mm = p1.get_main_module(); bool has_pointwise = std::any_of(mm->begin(), mm->end(), [&](const auto& i) { return i.name() == "pointwise"; }); EXPECT(has_pointwise); } TEST_CASE(conv_split_reduce) { migraphx::shape s_x{migraphx::shape::float_type, {2, 4, 64, 64}}; migraphx::shape s_w{migraphx::shape::float_type, {320, 4, 3, 3}}; migraphx::shape s_b{migraphx::shape::float_type, {32}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s_x); auto w = mm->add_parameter("w", s_w); auto b = mm->add_literal(migraphx::generate_literal(s_b)); auto mb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 32, 10, 64, 64}}}), b); auto conv = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), x, w); auto reshape = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), conv); auto add = add_pointwise(p1, "main:pointwise0", {reshape, mb}, single_pointwise("add")); auto mean_var = add_reduce( p1, "main:split_reduce0", {add}, {2, 3, 4}, "assign_add", [&](auto* rm, const auto& inputs, const auto& axes) -> std::vector { auto xx = add_pointwise(p1, rm, "main:pointwise1", {inputs[0]}, squared()); auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), xx); return {rsum2, rsum1}; }); auto var = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), mean_var); auto mean = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), mean_var); mm->add_return({var, mean}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s_x); auto w = mm->add_parameter("w", s_w); auto b = mm->add_literal(migraphx::generate_literal(s_b)); auto fused = add_mlir(p2, "mlir_main:pointwise0_main:split_reduce0", {x, w, b}, {"x0", "x1", "x2"}, [=](auto* pm, const auto& inputs) { auto conv = pm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), inputs[0], inputs[1]); auto reshape = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), conv); auto mb = pm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 32, 10, 64, 64}}}), inputs[2]); auto add = pm->add_instruction(migraphx::make_op("add"), reshape, mb); auto mul = pm->add_instruction(migraphx::make_op("mul"), add, add); auto mean = pm->add_instruction( migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), add); auto var = pm->add_instruction( migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), mul); return std::make_tuple( migraphx::make_op("gpu::mlir_op", {{"op", migraphx::to_value(conv->get_operator())}}), std::vector{var, mean}); }); auto mean = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto var = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); mm->add_return({var, mean}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_REDUCE_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(conv_add_split_reduce_multi_use) { migraphx::shape s_x{migraphx::shape::float_type, {2, 4, 64, 64}}; migraphx::shape s_w{migraphx::shape::float_type, {320, 4, 3, 3}}; migraphx::shape s_b{migraphx::shape::float_type, {32}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s_x); auto w = mm->add_parameter("w", s_w); auto b = mm->add_literal(migraphx::generate_literal(s_b)); auto mb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 32, 10, 64, 64}}}), b); auto conv = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), x, w); auto reshape = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), conv); auto add = add_pointwise(p1, "main:pointwise0", {reshape, mb}, single_pointwise("add")); auto mean_var = add_reduce( p1, "main:split_reduce0", {add}, {2, 3, 4}, "assign_add", [&](auto* rm, const auto& inputs, const auto& axes) -> std::vector { auto xx = add_pointwise(p1, rm, "main:pointwise1", {inputs[0]}, squared()); auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), xx); return {rsum2, rsum1}; }); auto var = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), mean_var); auto mean = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), mean_var); auto mean_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", add->get_shape().lens()}}), mean); auto var_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", add->get_shape().lens()}}), var); auto norm = add_pointwise( p1, "main:pointwise2", {add, mean_mb, var_mb}, [=](auto* pm, const auto& inputs) { auto sub = pm->add_instruction(migraphx::make_op("sub"), inputs.at(0), inputs.at(1)); return pm->add_instruction(migraphx::make_op("div"), sub, inputs.at(2)); }); mm->add_return({norm}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s_x); auto w = mm->add_parameter("w", s_w); auto b = mm->add_literal(migraphx::generate_literal(s_b)); auto fused = add_mlir(p2, "mlir_main:pointwise0_main:split_reduce0", {x, w, b}, {"x0", "x1", "x2"}, [=](auto* pm, const auto& inputs) { auto conv = pm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), inputs[0], inputs[1]); auto reshape = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), conv); auto mb = pm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 32, 10, 64, 64}}}), inputs[2]); auto add = pm->add_instruction(migraphx::make_op("add"), reshape, mb); auto mul = pm->add_instruction(migraphx::make_op("mul"), add, add); auto mean = pm->add_instruction( migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), add); auto var = pm->add_instruction( migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), mul); return std::make_tuple( migraphx::make_op("gpu::mlir_op", {{"op", migraphx::to_value(conv->get_operator())}}), std::vector{var, mean, add}); }); auto cba = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), fused); auto var = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto mean = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto mean_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", cba->get_shape().lens()}}), mean); auto var_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", cba->get_shape().lens()}}), var); auto norm = add_pointwise( p2, "main:pointwise2", {cba, mean_mb, var_mb}, [=](auto* pm, const auto& inputs) { auto sub = pm->add_instruction(migraphx::make_op("sub"), inputs.at(0), inputs.at(1)); return pm->add_instruction(migraphx::make_op("div"), sub, inputs.at(2)); }); mm->add_return({norm}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_REDUCE_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(conv_add_split_reduce_multi_use_conv) { migraphx::shape s_x{migraphx::shape::float_type, {2, 4, 64, 64}}; migraphx::shape s_w1{migraphx::shape::float_type, {320, 4, 3, 3}}; migraphx::shape s_w2{migraphx::shape::float_type, {320, 320, 3, 3}}; migraphx::shape s_b{migraphx::shape::float_type, {32}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s_x); auto w1 = mm->add_parameter("w1", s_w1); auto w2 = mm->add_parameter("w2", s_w2); auto b = mm->add_literal(migraphx::generate_literal(s_b)); auto mb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 32, 10, 64, 64}}}), b); auto conv = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), x, w1); auto reshape = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), conv); auto add = add_pointwise(p1, "main:pointwise0", {reshape, mb}, single_pointwise("add")); auto mean_var = add_reduce( p1, "main:split_reduce0", {add}, {2, 3, 4}, "assign_add", [&](auto* rm, const auto& inputs, const auto& axes) -> std::vector { auto xx = add_pointwise(p1, rm, "main:pointwise1", {inputs[0]}, squared()); auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), xx); return {rsum2, rsum1}; }); auto var = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), mean_var); auto mean = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), mean_var); auto mean_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", add->get_shape().lens()}}), mean); auto mean_rsp = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), mean_mb); auto var_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", add->get_shape().lens()}}), var); auto var_rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), var_mb); auto add_rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), add); auto norm = add_pointwise( p1, "main:pointwise2", {add_rsp, mean_rsp, var_rsp}, [=](auto* pm, const auto& inputs) { auto sub = pm->add_instruction(migraphx::make_op("sub"), inputs.at(0), inputs.at(1)); return pm->add_instruction(migraphx::make_op("div"), sub, inputs.at(2)); }); auto conv_2 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), norm, w2); mm->add_return({conv_2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s_x); auto w1 = mm->add_parameter("w1", s_w1); auto w2 = mm->add_parameter("w2", s_w2); auto b = mm->add_literal(migraphx::generate_literal(s_b)); auto fused = add_mlir(p2, "mlir_main:pointwise0_main:split_reduce0", {x, w1, b}, {"x0", "x1", "x2"}, [=](auto* pm, const auto& inputs) { auto conv = pm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), inputs[0], inputs[1]); auto reshape = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), conv); auto mb = pm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 32, 10, 64, 64}}}), inputs[2]); auto add = pm->add_instruction(migraphx::make_op("add"), reshape, mb); auto mul = pm->add_instruction(migraphx::make_op("mul"), add, add); auto mean = pm->add_instruction( migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), add); auto var = pm->add_instruction( migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), mul); return std::make_tuple( migraphx::make_op("gpu::mlir_op", {{"op", migraphx::to_value(conv->get_operator())}}), std::vector{var, mean, add}); }); auto cba = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), fused); auto var = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto mean = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto input_fused_conv = add_mlir( p2, "main:pointwise2:mlir_convolution1", {cba, mean, var, w2}, {"x0", "x1", "x2", "x3"}, [=](auto* pm, const auto& inputs) { auto mean_mb = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", cba->get_shape().lens()}}), inputs.at(1)); auto mean_rsp = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), mean_mb); auto var_mb = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", cba->get_shape().lens()}}), inputs.at(2)); auto var_rsp = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), var_mb); auto cba_rsp = pm->add_instruction( migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), inputs.at(0)); auto sub = pm->add_instruction(migraphx::make_op("sub"), cba_rsp, mean_rsp); auto div = pm->add_instruction(migraphx::make_op("div"), sub, var_rsp); auto conv = pm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), div, inputs.at(3)); return std::make_tuple(conv->get_operator(), conv); }); mm->add_return({input_fused_conv}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_REDUCE_FUSION{}) or not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_INPUT_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(standalone_attention) { migraphx::shape s1{migraphx::shape::half_type, {1, 12, 256, 256}}; migraphx::shape s2{migraphx::shape::bool_type, {1, 12, 256, 256}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto group = add_group( p1, "attn0", "attention", {a, b, b1}, {"x0", "x1", "x2"}, [=](auto* gm, const auto& inputs) { auto gemm1 = gm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto rmax = gm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), gemm1); rmax = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = gm->add_instruction(migraphx::make_op("sub"), gemm1, rmax); auto exp = gm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = gm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = gm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = gm->add_instruction(migraphx::make_op("dot"), div, inputs[2]); return std::vector{gemm2}; }); mm->add_return({group}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto fused = add_mlir( p2, "mlir_attn0", {a, b, b1}, {"x0", "x1", "x2"}, [=](auto* pm, const auto& inputs) { auto fb = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), inputs[1]); auto fb1 = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), inputs[2]); auto gemm1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], fb); auto rmax = pm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), gemm1); rmax = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = pm->add_instruction(migraphx::make_op("sub"), gemm1, rmax); auto exp = pm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = pm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = pm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = pm->add_instruction(migraphx::make_op("dot"), div, fb1); return std::make_tuple(gemm2->get_operator(), gemm2); }); mm->add_return({fused}); } EXPECT(p1 == p2); } TEST_CASE(fused_attention) { migraphx::shape s1{migraphx::shape::half_type, {1, 12, 256, 256}}; migraphx::shape s2{migraphx::shape::bool_type, {1, 12, 256, 256}}; auto s1_elements = s1.elements(); migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto select = mm->add_parameter("4", s2); auto c = mm->add_parameter("5", s1); std::vector eights(s1_elements, 0.125); std::vector tens(s1_elements, 10); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto group = add_group( p1, "attn0", "attention", {a, b, select, b1}, [=](auto* gm, const auto& inputs) { auto ten = gm->add_literal(migraphx::literal{s1, tens}); auto eight = gm->add_literal(migraphx::literal{s1, eights}); auto gemm1 = gm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto mul = gm->add_instruction(migraphx::make_op("mul"), gemm1, eight); auto where = gm->add_instruction(migraphx::make_op("where"), inputs[2], mul, ten); auto rmax = gm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), where); rmax = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = gm->add_instruction(migraphx::make_op("sub"), gemm1, rmax); auto exp = gm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = gm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = gm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = gm->add_instruction(migraphx::make_op("dot"), div, inputs[3]); return std::vector{gemm2}; }); auto trailing_pw = add_pointwise(p1, mm, "main:pointwise0", {group, c}, single_pointwise("add")); mm->add_return({trailing_pw}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto select = mm->add_parameter("4", s2); auto c = mm->add_parameter("5", s1); std::vector eights(s1_elements, 0.125); std::vector tens(s1_elements, 10); auto fused = add_mlir( p2, "mlir_attn0", {a, b, select, b1, c}, {"x0", "x1", "x2", "x3", "x4"}, [=](auto* pm, const auto& inputs) { auto ten = pm->add_literal(migraphx::literal{s1, tens}); auto eight = pm->add_literal(migraphx::literal{s1, eights}); auto fb = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), inputs[1]); auto fb1 = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), inputs[3]); auto gemm1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], fb); auto mul = pm->add_instruction(migraphx::make_op("mul"), gemm1, eight); auto where = pm->add_instruction(migraphx::make_op("where"), inputs[2], mul, ten); auto rmax = pm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), where); rmax = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = pm->add_instruction(migraphx::make_op("sub"), gemm1, rmax); auto exp = pm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = pm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); rsum = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = pm->add_instruction(migraphx::make_op("div"), exp, rsum); auto gemm2 = pm->add_instruction(migraphx::make_op("dot"), div, fb1); auto add = pm->add_instruction(migraphx::make_op("add"), gemm2, inputs[4]); return std::make_tuple(gemm2->get_operator(), add); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(lse_attention) { migraphx::shape s1{migraphx::shape::float_type, {1, 12, 256, 256}}; migraphx::shape s2{migraphx::shape::bool_type, {1, 12, 256, 256}}; auto s1_elements = s1.elements(); migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto select = mm->add_parameter("4", s2); std::vector eights(s1_elements, 0.125); std::vector tens(s1_elements, 10); auto eight = mm->add_literal(migraphx::literal{s1, eights}); auto ten = mm->add_literal(migraphx::literal{s1, tens}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); b1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b1); auto group = add_group( p1, "attn0", "attention", {a, b, eight, select, ten, b1}, [=](auto* gm, const auto& inputs) { auto gemm1 = gm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto mul = gm->add_instruction(migraphx::make_op("mul"), gemm1, inputs[2]); auto where = gm->add_instruction(migraphx::make_op("where"), inputs[3], mul, inputs[4]); auto rmax = gm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), where); auto rmax_mb = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = gm->add_instruction(migraphx::make_op("sub"), where, rmax_mb); auto exp = gm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = gm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); auto rsum_mb = gm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = gm->add_instruction(migraphx::make_op("div"), exp, rsum_mb); auto gemm2 = gm->add_instruction(migraphx::make_op("dot"), div, inputs[5]); auto log = gm->add_instruction(migraphx::make_op("log"), rsum); auto add = gm->add_instruction(migraphx::make_op("add"), log, rmax); return std::vector{gemm2, add}; }); auto adjust_lse = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), group); auto lse = add_pointwise(p1, "main:pointwise0", {adjust_lse}, [=](auto* pm, const auto& inputs) { auto log2 = pm->add_literal(1.44238f); auto log2se = pm->add_instruction(migraphx::make_op("mul"), inputs.at(0), log2); return pm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), log2se); }); auto lse_squeeze = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), lse); auto gemm2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), group); mm->add_return({gemm2, lse_squeeze}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("1", s1); auto b = mm->add_parameter("2", s1); auto b1 = mm->add_parameter("3", s1); auto select = mm->add_parameter("4", s2); std::vector eights(s1_elements, 0.125); std::vector tens(s1_elements, 10); auto eight = mm->add_literal(migraphx::literal{s1, eights}); auto ten = mm->add_literal(migraphx::literal{s1, tens}); auto fused = add_mlir( p2, "mlir_attn0", {a, b, eight, select, ten, b1}, {"x0", "x1", "x2", "x3", "x4", "x5"}, [=](auto* pm, const auto& inputs) { auto log2 = pm->add_literal(1.44238f); auto fb = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), inputs[1]); auto fb1 = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), inputs[5]); auto gemm1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], fb); auto mul = pm->add_instruction(migraphx::make_op("mul"), gemm1, inputs[2]); auto where = pm->add_instruction(migraphx::make_op("where"), inputs[3], mul, inputs[4]); auto rmax = pm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {3}}}), where); auto rmax_mb = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rmax); auto sub = pm->add_instruction(migraphx::make_op("sub"), where, rmax_mb); auto exp = pm->add_instruction(migraphx::make_op("exp"), sub); auto rsum = pm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {3}}}), exp); auto rsum_mb = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), rsum); auto div = pm->add_instruction(migraphx::make_op("div"), exp, rsum_mb); auto gemm2 = pm->add_instruction(migraphx::make_op("dot"), div, fb1); auto log = pm->add_instruction(migraphx::make_op("log"), rsum); auto add = pm->add_instruction(migraphx::make_op("add"), log, rmax); log2 = pm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", add->get_shape().lens()}}), log2); auto log2se = pm->add_instruction(migraphx::make_op("mul"), add, log2); auto lse = pm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), log2se); return std::make_tuple(gemm2->get_operator(), std::vector{gemm2, lse}); }); auto lse = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto lse_squeeze = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), lse); auto gemm2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); mm->add_return({gemm2, lse_squeeze}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(conv_output_reshapes) { migraphx::shape s1 = migraphx::shape::from_permutation( migraphx::shape::half_type, {64, 64, 160, 160}, {0, 2, 3, 1}); // equivalent: migraphx::shape s1{migraphx::shape::half_type, {64, 64, 160, 160}, {1638400, 1, // 10240, 64}}; migraphx::shape s2{migraphx::shape::half_type, {64, 64, 1, 1}, {0, 1, 0, 0}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("x", s1); auto b = mm->add_parameter("w", s2); auto c = mm->add_parameter("b", s1); auto conv = mm->add_instruction(migraphx::make_op("convolution"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {conv, c}, single_pointwise("add")); auto reshape = mm->add_instruction( migraphx::make_op("reshape_lazy", {{"dims", {64, 2, 32, 160, 160}}}), add); auto transpose_0 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {1, 0, 3, 4, 2}}}), reshape); auto contiguous = mm->add_instruction(migraphx::make_op("contiguous"), transpose_0); auto transpose_1 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 4, 2, 3}}}), contiguous); auto slice_0 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), transpose_1); auto slice_1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), transpose_1); mm->add_return({slice_0, slice_1}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("x", s1); auto b = mm->add_parameter("w", s2); auto c = mm->add_parameter("b", s1); auto mlir_op = add_mlir( p2, "mlir_main:pointwise0_reshape_lazy_transpose_contiguous_transpose", {a, b, c}, {"x0", "x1", "x2"}, [=](auto* pm, const auto& inputs) { auto conv = pm->add_instruction(migraphx::make_op("convolution"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), conv, inputs[2]); auto reshape = pm->add_instruction( migraphx::make_op("reshape_lazy", {{"dims", {64, 2, 32, 160, 160}}}), add); auto transpose_0 = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {1, 0, 3, 4, 2}}}), reshape); auto contiguous = pm->add_instruction(migraphx::make_op("contiguous"), transpose_0); auto transpose_1 = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 4, 2, 3}}}), contiguous); return std::make_tuple(transpose_1->get_operator(), transpose_1); }); auto slice_0 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), mlir_op); auto slice_1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), mlir_op); mm->add_return({slice_0, slice_1}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(channel_slice_convolution) { migraphx::shape s1 = migraphx::shape::from_permutation( migraphx::shape::half_type, {64, 64, 160, 160}, {0, 2, 3, 1}); // equivalent: migraphx::shape s1{migraphx::shape::half_type, {64, 64, 160, 160}, {1638400, 1, // 10240, 64}}; migraphx::shape s2{migraphx::shape::half_type, {64, 64, 1, 1}, {0, 1, 0, 0}}; migraphx::shape s3{migraphx::shape::half_type, {32, 32, 1, 1}, {0, 1, 0, 0}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("x", s1); auto b = mm->add_parameter("w", s2); auto c = mm->add_parameter("b", s1); auto d = mm->add_parameter("g", s3); auto conv = mm->add_instruction(migraphx::make_op("convolution"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {conv, c}, single_pointwise("add")); auto slice_0 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {32}}}), add); auto slice_1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {32}}, {"ends", {64}}}), add); auto conv_1 = mm->add_instruction(migraphx::make_op("convolution"), slice_0, d); auto conv_2 = mm->add_instruction(migraphx::make_op("convolution"), slice_1, d); mm->add_return({conv_1, conv_2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("x", s1); auto b = mm->add_parameter("w", s2); auto c = mm->add_parameter("b", s1); auto d = mm->add_parameter("g", s3); auto mlir_op = add_mlir( p2, "mlir_main:pointwise0_reshape_lazy_transpose_contiguous_transpose", {a, b, c}, {"x0", "x1", "x2"}, [=](auto* pm, const auto& inputs) { auto conv = pm->add_instruction(migraphx::make_op("convolution"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), conv, inputs[2]); auto reshape = pm->add_instruction( migraphx::make_op("reshape_lazy", {{"dims", {64, 2, 32, 160, 160}}}), add); auto transpose_0 = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {1, 0, 3, 4, 2}}}), reshape); auto contiguous = pm->add_instruction(migraphx::make_op("contiguous"), transpose_0); auto transpose_1 = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 4, 2, 3}}}), contiguous); return std::make_tuple(transpose_1->get_operator(), transpose_1); }); auto identity = mm->add_instruction(migraphx::make_op("identity"), mlir_op); auto mlir_conv0 = add_mlir( p2, "mlir_convolution1", {identity, d}, {"y0", "y1"}, [=](auto* pm, const auto& inputs) { auto slice_0 = pm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), inputs[0]); auto squeeze_0 = pm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), slice_0); auto conv_0 = pm->add_instruction(migraphx::make_op("convolution"), squeeze_0, inputs[1]); return std::make_tuple(conv_0->get_operator(), conv_0); }); auto mlir_conv1 = add_mlir( p2, "mlir_convolution2", {identity, d}, {"y0", "y1"}, [=](auto* pm, const auto& inputs) { auto slice_1 = pm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), inputs[0]); auto squeeze_1 = pm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), slice_1); auto conv_1 = pm->add_instruction(migraphx::make_op("convolution"), squeeze_1, inputs[1]); return std::make_tuple(conv_1->get_operator(), conv_1); }); mm->add_return({mlir_conv0, mlir_conv1}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(unpack_fp4_dot_even) { migraphx::program p1; { auto* m = p1.get_main_module(); auto packed_a = m->add_parameter("a", {migraphx::shape::fp4x2_type, {1, 3, 8, 4}}); auto packed_b = m->add_parameter("b", {migraphx::shape::fp4x2_type, {1, 3, 8, 4}}); auto scale_a = m->add_parameter("scale_a", {migraphx::shape::float_type, {1, 3, 8, 8}}); auto scale_b = m->add_parameter("scale_b", {migraphx::shape::float_type, {1, 3, 8, 8}}); auto unpack_a = m->add_instruction(migraphx::make_op("unpack_fp4"), packed_a); auto unpack_b = m->add_instruction(migraphx::make_op("unpack_fp4"), packed_b); auto dot = m->add_instruction( migraphx::make_op("quant_dot"), unpack_a, unpack_b, scale_a, scale_b); m->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* m = p2.get_main_module(); auto packed_a = m->add_parameter("a", {migraphx::shape::fp4x2_type, {1, 3, 8, 4}}); auto packed_b = m->add_parameter("b", {migraphx::shape::fp4x2_type, {1, 3, 8, 4}}); auto scale_a = m->add_parameter("scale_a", {migraphx::shape::float_type, {1, 3, 8, 8}}); auto scale_b = m->add_parameter("scale_b", {migraphx::shape::float_type, {1, 3, 8, 8}}); auto fused = add_mlir( p2, "fp4:mlir_quant_dot0", {packed_a, packed_b, scale_a, scale_b}, {"x1", "x2", "x3", "x4"}, [=](auto* pm, const auto& inputs) { auto unpack_a = pm->add_instruction(migraphx::make_op("unpack_fp4"), inputs[0]); auto unpack_b = pm->add_instruction(migraphx::make_op("unpack_fp4"), inputs[1]); auto dot = pm->add_instruction( migraphx::make_op("quant_dot"), unpack_a, unpack_b, inputs[2], inputs[3]); return std::make_tuple(dot->get_operator(), dot); }); m->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(unpack_fp4_dot_odd) { migraphx::program p1; { auto* m = p1.get_main_module(); auto packed_a = m->add_parameter("a", {migraphx::shape::fp4x2_type, {1, 3, 7, 4}}); auto packed_b = m->add_parameter("b", {migraphx::shape::fp4x2_type, {1, 3, 7, 4}}); auto scale_a = m->add_parameter("scale_a", {migraphx::shape::float_type, {1, 3, 7, 7}}); auto scale_b = m->add_parameter("scale_b", {migraphx::shape::float_type, {1, 3, 7, 7}}); auto unpack_a = m->add_instruction(migraphx::make_op("unpack_fp4"), packed_a); auto unpack_b = m->add_instruction(migraphx::make_op("unpack_fp4"), packed_b); auto slice_a = m->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {7}}}), unpack_a); auto slice_b = m->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {7}}}), unpack_b); auto dot = m->add_instruction(migraphx::make_op("quant_dot"), slice_a, slice_b, scale_a, scale_b); m->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* m = p2.get_main_module(); auto packed_a = m->add_parameter("a", {migraphx::shape::fp4x2_type, {1, 3, 7, 4}}); auto packed_b = m->add_parameter("b", {migraphx::shape::fp4x2_type, {1, 3, 7, 4}}); auto scale_a = m->add_parameter("scale_a", {migraphx::shape::float_type, {1, 3, 7, 7}}); auto scale_b = m->add_parameter("scale_b", {migraphx::shape::float_type, {1, 3, 7, 7}}); auto fused = add_mlir( p2, "fp4:mlir_quant_dot0", {packed_a, packed_b, scale_a, scale_b}, {"x1", "x2", "x3", "x4"}, [=](auto* pm, const auto& inputs) { auto unpack_a = pm->add_instruction(migraphx::make_op("unpack_fp4"), inputs[0]); auto slice_a = pm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {7}}}), unpack_a); auto unpack_b = pm->add_instruction(migraphx::make_op("unpack_fp4"), inputs[1]); auto slice_b = pm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {7}}}), unpack_b); auto dot = pm->add_instruction( migraphx::make_op("quant_dot"), slice_a, slice_b, inputs[2], inputs[3]); return std::make_tuple(dot->get_operator(), dot); }); m->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_dot) { migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::float_type, {3, 4}}; migraphx::shape s3{migraphx::shape::float_type, {2, 4}}; migraphx::shape s4{migraphx::shape::float_type, {4, 2}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", s3); auto y = mm->add_parameter("y", s4); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); // {2,4} auto add = add_pointwise(p1, "main:pointwise0", {dot1, x}, single_pointwise("add")); // {2,4} auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, y); // {4,2} mm->add_return({dot2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", s3); auto y = mm->add_parameter("y", s4); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, x, y}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot2->get_operator(), dot2); }); mm->add_return({fused}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_dot_square) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, x}, single_pointwise("add")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, y); mm->add_return({dot2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, x, y}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot2->get_operator(), dot2); }); mm->add_return({fused}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_mul_dot) { migraphx::shape s1{migraphx::shape::float_type, {3, 3}}; migraphx::shape s2{migraphx::shape::float_type, {3, 4}}; migraphx::shape s3{migraphx::shape::float_type, {4, 5}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", s2); auto y = mm->add_parameter("y", s3); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto mul = add_pointwise(p1, "main:pointwise0", {dot1, x}, single_pointwise("mul")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), mul, y); mm->add_return({dot2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", s2); auto y = mm->add_parameter("y", s3); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, x, y}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto mul = pm->add_instruction(migraphx::make_op("mul"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), mul, inputs[3]); return std::make_tuple(dot2->get_operator(), dot2); }); mm->add_return({fused}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(conv_add) { migraphx::shape is{migraphx::shape::float_type, {4, 14, 122, 122}}; migraphx::shape ys{migraphx::shape::float_type, {4, 56, 122, 122}}; migraphx::shape ws{migraphx::shape::float_type, {56, 14, 1, 1}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", is); auto y = mm->add_parameter("y", ys); auto w = mm->add_parameter("w", ws); auto conv = mm->add_instruction(migraphx::make_op("convolution"), x, w); auto add = add_pointwise(p1, "main:pointwise0", {conv, y}, single_pointwise("add")); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", is); auto y = mm->add_parameter("y", ys); auto w = mm->add_parameter("w", ws); auto fused = add_mlir(p2, "mlir_main:pointwise0", {x, w, y}, {"x0", "x1", "x2"}, [=](auto* pm, const auto& inputs) { auto c = pm->add_instruction( migraphx::make_op("convolution"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), c, inputs[2]); return std::make_tuple(c->get_operator(), add); }); mm->add_return({fused}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(conv_add_dot) { migraphx::shape is{migraphx::shape::float_type, {2, 4, 8, 8}}; migraphx::shape ys{migraphx::shape::float_type, {2, 8, 8, 8}}; migraphx::shape ws{migraphx::shape::float_type, {8, 4, 1, 1}}; migraphx::shape zs{migraphx::shape::float_type, {2, 8, 8, 4}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", is); auto y = mm->add_parameter("y", ys); auto w = mm->add_parameter("w", ws); auto z = mm->add_parameter("z", zs); auto conv = mm->add_instruction(migraphx::make_op("convolution"), x, w); auto add = add_pointwise(p1, "main:pointwise0", {conv, y}, single_pointwise("add")); auto dot = mm->add_instruction(migraphx::make_op("dot"), add, z); mm->add_return({dot}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", is); auto y = mm->add_parameter("y", ys); auto w = mm->add_parameter("w", ws); auto z = mm->add_parameter("z", zs); auto fused = add_mlir(p2, "mlir_main:pointwise0_geg", {x, w, y, z}, {"x0", "x1", "x2", "x3"}, [=](auto* pm, const auto& inputs) { auto c = pm->add_instruction( migraphx::make_op("convolution"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), c, inputs[2]); auto dot = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot->get_operator(), dot); }); mm->add_return({fused}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_multi_user_add) // G ->optional R -> E fusion // G has two users, one external to fusion { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot1); mm->add_return({add, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto fused = add_mlir(p2, "mlir_main:pointwise0", {a, b, c}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); return std::make_tuple(dot1->get_operator(), std::vector{add, dot1}); }); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_dot = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot); mm->add_return({get_add, transpose}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_multi_user_dot) // GEG fusion has two outputs, E has external user { migraphx::shape s1{migraphx::shape::float_type, {3, 3}}; migraphx::shape s2{migraphx::shape::float_type, {3, 5}}; migraphx::shape s3{migraphx::shape::float_type, {5, 2}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto c = mm->add_parameter("c", s2); auto d = mm->add_parameter("d", s3); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, d); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), dot2); mm->add_return({add, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto c = mm->add_parameter("c", s2); auto d = mm->add_parameter("d", s3); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, c, d}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {1, 0}}}), get_dot2); mm->add_return({get_add, transpose}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_multi_user_dot_with_transpose) // GEG fusion has two outputs, E has external user { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto d_t = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), d); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, d_t); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot2); mm->add_return({add, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, c, d}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto d_t = pm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), inputs[3]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, d_t); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_add, transpose}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_multi_user_dot_two_externals) // GEG fusion has two outputs, E has external user { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto external_t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), d); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, d); auto external_t2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot2); mm->add_return({add, external_t1, external_t2}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, c, d}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto external_t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), d); auto external_t2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_add, external_t1, external_t2}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_multi_user_dot_input_used_before) // GEG fusion has two outputs, E has external user. // Base case for testing inputs being defined within the span // of will-be-fused ops // This also shows the relu being fused, since it is a unary op { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto external_relu = add_pointwise(p1, "main:pointwise1", {d}, single_pointwise("relu")); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, external_relu); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot2); mm->add_return({add, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto fused = add_mlir( p2, "main:pointwise1:mlir_main:pointwise0_geg", {d, a, b, c}, [=](auto* pm, const auto& inputs) { auto external_relu = pm->add_instruction(migraphx::make_op("relu"), inputs[0]); auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[1], inputs[2]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[3]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, external_relu); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto external_t = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_add, external_t}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_multi_user_dot_input_used_after) // GEG fusion has two outputs, E has external user // Testing inputs being defined within the span of will-be-fused ops // This also shows the relu being fused, since it is a unary op. // Result should be, and is, equivalent to the previous test { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto external_relu = add_pointwise(p1, "main:pointwise1", {d}, single_pointwise("relu")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, external_relu); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot2); mm->add_return({add, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto fused = add_mlir( p2, "main:pointwise1:mlir_main:pointwise0_geg", {d, a, b, c}, [=](auto* pm, const auto& inputs) { auto external_relu = pm->add_instruction(migraphx::make_op("relu"), inputs[0]); auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[1], inputs[2]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[3]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, external_relu); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto external_t = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_add, external_t}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_multi_user_dot_input_used_before_in_chain) // GEG fusion has two outputs, E has external user // Base case for inputs being defined within the span of will-be-fused ops, including // longer chain of logic, for both cases of input fusion. When enabled, // the mul gets fused into the GEG fusion. { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto external_relu = add_pointwise(p1, "main:pointwise1", {d}, single_pointwise("relu")); auto external_mul = add_pointwise(p1, "main:pointwise2", {external_relu, d}, single_pointwise("mul")); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, external_mul); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot2); mm->add_return({add, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto external_relu = add_pointwise(p2, "main:pointwise1", {d}, single_pointwise("relu")); auto external_mul = add_pointwise(p2, "main:pointwise2", {external_relu, d}, single_pointwise("mul")); auto fused = add_mlir(p2, "mlir_main:pointwise0_geg", {a, b, c, external_mul}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto external_t = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_add, external_t}); } migraphx::program p3; { auto* mm = p3.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto external_relu = add_pointwise(p3, "main:pointwise1", {d}, single_pointwise("relu")); auto fused = add_mlir( p3, "main:pointwise2:mlir_main:pointwise0_geg", {external_relu, d, a, b, c}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[2], inputs[3]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[4]); auto mul = pm->add_instruction(migraphx::make_op("mul"), inputs[0], inputs[1]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, mul); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto external_t = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_add, external_t}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; if(migraphx::enabled(MIGRAPHX_ENABLE_MLIR_INPUT_FUSION{})) EXPECT(p1.sort() == p3.sort()); else EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_multi_user_dot_input_used_after_in_chain) // GEG fusion has two outputs, E has external user // Testing inputs being defined within the span of will-be-fused ops, including // longer chain of logic { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto external_relu = add_pointwise(p1, "main:pointwise1", {d}, single_pointwise("relu")); auto external_mul = add_pointwise(p1, "main:pointwise2", {external_relu, d}, single_pointwise("mul")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, external_mul); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot2); mm->add_return({add, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto external_relu = add_pointwise(p2, "main:pointwise1", {d}, single_pointwise("relu")); auto external_mul = add_pointwise(p2, "main:pointwise2", {external_relu, d}, single_pointwise("mul")); auto fused = add_mlir(p2, "mlir_main:pointwise0_geg", {a, b, c, external_mul}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto external_t = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_add, external_t}); } migraphx::program p3; { auto* mm = p3.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto external_relu = add_pointwise(p3, "main:pointwise1", {d}, single_pointwise("relu")); auto fused = add_mlir( p3, "main:pointwise2:mlir_main:pointwise0_geg", {external_relu, d, a, b, c}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[2], inputs[3]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[4]); auto mul = pm->add_instruction(migraphx::make_op("mul"), inputs[0], inputs[1]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, mul); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_add = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto external_t = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_add, external_t}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; if(migraphx::enabled(MIGRAPHX_ENABLE_MLIR_INPUT_FUSION{})) EXPECT(p1.sort() == p3.sort()); else EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_pw_multi_user_dot) // GEG fusion has two outputs, E has external user, E is multiple elemwise ops { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto e = mm->add_parameter("e", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto elemwise = add_pointwise(p1, "main:pointwise0", {dot1, c, d}, [=](auto* pm, const auto& inputs) { auto add = pm->add_instruction(migraphx::make_op("add"), inputs.at(0), inputs.at(1)); return pm->add_instruction(migraphx::make_op("mul"), add, inputs.at(2)); }); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), elemwise, e); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot2); mm->add_return({elemwise, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto e = mm->add_parameter("e", s); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, c, d, e}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto mul = pm->add_instruction(migraphx::make_op("mul"), add, inputs[3]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), mul, inputs[4]); return std::make_tuple(dot1->get_operator(), std::vector{dot2, mul}); }); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto get_mul = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot2); mm->add_return({get_mul, transpose}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_multi_user_add_dot) // GEG fusion has two outputs (first G has external user) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, d); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot1); mm->add_return({dot2, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, c, d}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot1->get_operator(), std::vector{dot2, dot1}); }); auto get_dot1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot1); mm->add_return({get_dot2, transpose}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } TEST_CASE(dot_add_dot_both_multi_user) // GEG fusion has three outputs (first G has external user, E has external user) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 3}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = add_pointwise(p1, "main:pointwise0", {dot1, c}, single_pointwise("add")); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, d); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dot1); mm->add_return({add, dot2, transpose}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto fused = add_mlir( p2, "mlir_main:pointwise0_geg", {a, b, c, d}, [=](auto* pm, const auto& inputs) { auto dot1 = pm->add_instruction(migraphx::make_op("dot"), inputs[0], inputs[1]); auto add = pm->add_instruction(migraphx::make_op("add"), dot1, inputs[2]); auto dot2 = pm->add_instruction(migraphx::make_op("dot"), add, inputs[3]); return std::make_tuple(dot1->get_operator(), std::vector{dot2, add, dot1}); }); auto get_dot1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), fused); auto get_elemwise = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), fused); auto get_dot2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), fused); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), get_dot1); mm->add_return({get_elemwise, get_dot2, transpose}); } if(not migraphx::enabled(MIGRAPHX_ENABLE_MLIR_GEG_FUSION{})) return; EXPECT(p1.sort() == p2.sort()); } int main(int argc, const char* argv[]) { if(migraphx::gpu::mlir_enabled()) { test::run(argc, argv); } return 0; } ROCm-AMDMIGraphX-46524e8/test/gpu/fuse_ops.cpp000066400000000000000000000462511510465702400206510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "make_precompile_op.hpp" #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes(p, {migraphx::gpu::fuse_ops{}, migraphx::dead_code_elimination{}}); } TEST_CASE(layernorm_pointwise) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 4}}; auto create_program = [=](bool first_arg_layernorm) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pw_add1 = create_pointwise_module(p, "main:pointwise0", {x, y}, single_pointwise("add")); auto add1 = mm->add_instruction(make_precompile_op("pointwise"), {x, y, alloc_ins}, {pw_add1}); auto alloc_ins2 = mm->add_instruction(alloc); auto layernorm_ins = mm->add_instruction(make_precompile_op("gpu::prelayernorm"), add1, alloc_ins2); std::vector pw_inputs = {layernorm_ins, z}; if(not first_arg_layernorm) { pw_inputs = {z, layernorm_ins}; } auto* pw_add2 = create_pointwise_module(p, "main:pointwise1", pw_inputs, single_pointwise("add")); auto alloc_ins3 = mm->add_instruction(alloc); pw_inputs.push_back(alloc_ins3); auto add2 = mm->add_instruction(make_precompile_op("pointwise"), pw_inputs, {pw_add2}); mm->add_return({add2}); return p; }; auto create_fused_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pw_add1 = create_pointwise_module(p, "main:pointwise0", {x, y}, single_pointwise("add")); auto add1 = mm->add_instruction(make_precompile_op("pointwise"), {x, y, alloc_ins}, {pw_add1}); auto alloc_ins2 = mm->add_instruction(alloc); auto* pw_add2 = create_pointwise_module(p, "main:pointwise1", {x, z}, single_pointwise("add")); auto layernorm_op = migraphx::make_op("gpu::prelayernorm"); auto pre_comp_op = migraphx::make_op( "gpu::precompile_op", {{"op", migraphx::to_value(layernorm_op)}, {"output_shape", migraphx::to_value(s)}}); auto layernorm_ins = mm->add_instruction(pre_comp_op, {add1, z, alloc_ins2}, {pw_add2}); mm->add_return({layernorm_ins}); return p; }; { migraphx::program p1 = create_program(true); run_pass(p1); migraphx::program p2 = create_fused_program(); EXPECT(p1 == p2); } { migraphx::program p1 = create_program(false); run_pass(p1); EXPECT(p1 == create_program(false)); } } TEST_CASE(pointwise_contiguous) { migraphx::shape s1{migraphx::shape::float_type, {128, 4, 196, 32}}; migraphx::shape s2{migraphx::shape::float_type, {128, 196, 4, 32}}; auto create_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto x_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), x); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s2)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pw_add1 = create_pointwise_module(p, "main:pointwise0", {x_trans, y}, single_pointwise("add")); auto add1 = mm->add_instruction( make_precompile_op("pointwise"), {x_trans, y, alloc_ins}, {pw_add1}); auto alloc_ins2 = mm->add_instruction(alloc); auto cont = mm->add_instruction(migraphx::make_op("gpu::contiguous"), add1, alloc_ins2); auto rsp = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {25088, 128}}}), cont); mm->add_return({rsp}); return p; }; auto create_fused_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto x_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), x); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s2)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pw_add1 = create_pointwise_module(p, "main:pointwise0", {x_trans, y}, single_pointwise("add")); auto pw_op = migraphx::make_op("pointwise"); auto pre_comp_op = migraphx::make_op( "gpu::precompile_op", {{"op", migraphx::to_value(pw_op)}, {"output_shape", migraphx::to_value(s2)}}); auto add1 = mm->add_instruction(pre_comp_op, {x_trans, y, alloc_ins}, {pw_add1}); auto rsp = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {25088, 128}}}), add1); mm->add_return({rsp}); return p; }; migraphx::program p1 = create_program(); run_pass(p1); migraphx::program p2 = create_fused_program(); EXPECT(p1 == p2); } TEST_CASE(layout_pointwise) { migraphx::shape s1{migraphx::shape::float_type, {1, 8, 4, 4}, {128, 1, 32, 8}}; migraphx::shape s2{migraphx::shape::float_type, {1, 8, 4, 4}}; auto create_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s2); auto y = mm->add_parameter("y", s2); auto layout_op = migraphx::make_op("layout", {{"permutation", {0, 2, 3, 1}}}); auto alloc1 = migraphx::make_op("allocate", {{"shape", to_value(s1)}}); auto alloc_ins1 = mm->add_instruction(alloc1); auto x_nhwc = mm->add_instruction( migraphx::make_op("gpu::precompile_op", {{"op", migraphx::to_value(layout_op)}}), x, alloc_ins1); auto alloc2 = migraphx::make_op("allocate", {{"shape", to_value(s2)}}); auto alloc_ins2 = mm->add_instruction(alloc2); auto* pw_add = create_pointwise_module(p, "main:pointwise0", {x_nhwc, y}, single_pointwise("add")); auto add = mm->add_instruction(make_precompile_op("pointwise"), {x_nhwc, y, alloc_ins2}, {pw_add}); auto rsp = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {1, 8, 16}}}), add); mm->add_return({rsp}); return p; }; auto create_fused_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s2); auto y = mm->add_parameter("y", s2); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s2)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pw_add = create_pointwise_module(p, "main:pointwise0", {x, y}, single_pointwise("add")); auto pw_op = migraphx::make_op("pointwise"); auto pre_comp_op = migraphx::make_op( "gpu::precompile_op", {{"op", migraphx::to_value(pw_op)}, {"output_shape", migraphx::to_value(s2)}}); auto add = mm->add_instruction(pre_comp_op, {x, y, alloc_ins}, {pw_add}); auto rsp = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {1, 8, 16}}}), add); mm->add_return({rsp}); return p; }; migraphx::program p1 = create_program(); run_pass(p1); migraphx::program p2 = create_fused_program(); EXPECT(p1 == p2); } TEST_CASE(contiguous_pointwise) { migraphx::shape s1{migraphx::shape::float_type, {1, 4, 4, 8}}; migraphx::shape s2{migraphx::shape::float_type, {1, 8, 4, 4}}; auto create_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto x_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), x); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s2)}}); auto alloc_ins1 = mm->add_instruction(alloc); auto x_cont = mm->add_instruction(migraphx::make_op("gpu::contiguous"), x_trans, alloc_ins1); auto alloc_ins2 = mm->add_instruction(alloc); auto* pw_add = create_pointwise_module(p, "main:pointwise0", {x_cont, y}, single_pointwise("add")); auto add = mm->add_instruction(make_precompile_op("pointwise"), {x_cont, y, alloc_ins2}, {pw_add}); auto rsp = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {1, 8, 16}}}), add); mm->add_return({rsp}); return p; }; auto create_fused_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); auto x_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), x); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s2)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pw_add = create_pointwise_module(p, "main:pointwise0", {x_trans, y}, single_pointwise("add")); auto pw_op = migraphx::make_op("pointwise"); auto pre_comp_op = migraphx::make_op( "gpu::precompile_op", {{"op", migraphx::to_value(pw_op)}, {"output_shape", migraphx::to_value(s2)}}); auto add = mm->add_instruction(pre_comp_op, {x_trans, y, alloc_ins}, {pw_add}); auto rsp = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {1, 8, 16}}}), add); mm->add_return({rsp}); return p; }; } // gpu::convolution not supported since MIOpen is OFF #if MIGRAPHX_USE_MIOPEN TEST_CASE(pointwise_layout_convolution) { migraphx::shape s1{migraphx::shape::float_type, {2, 320, 128, 128}}; migraphx::shape s2{migraphx::shape::float_type, {320, 320, 3, 3}, {2880, 1, 960, 320}}; migraphx::shape s3{migraphx::shape::float_type, {2, 320, 128, 128}, {5242880, 1, 40960, 320}}; // workspace for gpu::convolution, memory space can change based on gfx arch and rocm version, // For the unit-test just use some random number. migraphx::shape s4{migraphx::shape::int8_type, {41943040}}; auto create_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x1 = mm->add_parameter("x1", s1); auto x2 = mm->add_parameter("x2", s1); auto weights = mm->add_parameter("weights", s2); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s1)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pwm = create_pointwise_module( p, "main:pointwise0", {x1, x2}, [=](auto* pm, const auto& inputs) { auto mul_ins = pm->add_instruction(migraphx::make_op("mul"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("sigmoid"), mul_ins); }); auto pw_ins = mm->add_instruction(make_precompile_op("pointwise"), {x1, x2, alloc_ins}, {pwm}); auto alloc_ins2 = mm->add_instruction(migraphx::make_op("allocate", {{"shape", to_value(s3)}})); auto layout_op = migraphx::make_op("layout", {{"permutation", {0, 2, 3, 1}}}); auto layout_ins = mm->add_instruction(make_precompile_op(layout_op), {pw_ins, alloc_ins2}); auto conv_op = migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}); auto alloc_ins3 = mm->add_instruction(migraphx::make_op("allocate", {{"shape", to_value(s4)}})); auto alloc_ins4 = mm->add_instruction(migraphx::make_op("allocate", {{"shape", to_value(s3)}})); auto conv = mm->add_instruction(migraphx::make_op("gpu::convolution", {{"op", conv_op.to_value()}}), layout_ins, weights, alloc_ins3, alloc_ins4); mm->add_return({conv}); return p; }; auto create_fused_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x1 = mm->add_parameter("x1", s1); auto x2 = mm->add_parameter("x2", s1); auto weights = mm->add_parameter("weights", s2); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s3)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pwm = create_pointwise_module( p, "main:pointwise0", {x1, x2}, [=](auto* pm, const auto& inputs) { auto mul_ins = pm->add_instruction(migraphx::make_op("mul"), inputs[0], inputs[1]); return pm->add_instruction(migraphx::make_op("sigmoid"), mul_ins); }); auto pw_op = migraphx::make_op("pointwise"); auto pre_comp_op = migraphx::make_op( "gpu::precompile_op", {{"op", migraphx::to_value(pw_op)}, {"output_shape", migraphx::to_value(s3)}}); auto pw_ins = mm->add_instruction(pre_comp_op, {x1, x2, alloc_ins}, {pwm}); auto conv_op = migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}); auto alloc_ins2 = mm->add_instruction(migraphx::make_op("allocate", {{"shape", to_value(s4)}})); auto alloc_ins3 = mm->add_instruction(migraphx::make_op("allocate", {{"shape", to_value(s3)}})); auto conv = mm->add_instruction(migraphx::make_op("gpu::convolution", {{"op", conv_op.to_value()}}), pw_ins, weights, alloc_ins2, alloc_ins3); mm->add_return({conv}); return p; }; migraphx::program p1 = create_program(); run_pass(p1); migraphx::program p2 = create_fused_program(); EXPECT(p1 == p2); } #endif TEST_CASE(concat_pointwise_contiguous) { migraphx::shape s1 = migraphx::shape::from_permutation( migraphx::shape::float_type, {128, 2, 196, 32}, {0, 2, 1, 3}); migraphx::shape s2 = migraphx::shape::from_permutation( migraphx::shape::float_type, {128, 4, 196, 32}, {0, 2, 1, 3}); migraphx::shape s3{migraphx::shape::float_type, {128, 4, 196, 32}}; auto create_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x1 = mm->add_parameter("x1", s1); auto x2 = mm->add_parameter("x2", s1); auto y = mm->add_parameter("y", s2); auto concat_op = migraphx::make_op("concat", {{"axis", 1}}); auto concat_precompile_op = migraphx::make_op("gpu::precompile_op", {{"op", migraphx::to_value(concat_op)}}); auto x_alloc = mm->add_instruction(migraphx::make_op("allocate", {{"shape", to_value(s2)}})); auto x = mm->add_instruction(concat_precompile_op, {x1, x2, x_alloc}); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s3)}}); auto alloc_ins = mm->add_instruction(alloc); auto* pw_add1 = create_pointwise_module(p, "main:pointwise0", {x, y}, single_pointwise("add")); auto pw_op = migraphx::make_op("pointwise"); auto pre_comp_op = migraphx::make_op( "gpu::precompile_op", {{"op", migraphx::to_value(pw_op)}, {"output_shape", migraphx::to_value(s3)}}); auto add1 = mm->add_instruction(pre_comp_op, {x, y, alloc_ins}, {pw_add1}); auto rsp = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {25088, 128}}}), add1); mm->add_return({rsp}); return p; }; auto create_fused_program = [=]() { migraphx::program p; auto* mm = p.get_main_module(); auto x1 = mm->add_parameter("x1", s1); auto x2 = mm->add_parameter("x2", s1); auto y = mm->add_parameter("y", s2); auto concat_op = migraphx::make_op("concat", {{"axis", 1}}); auto concat_precompile_op = migraphx::make_op("gpu::precompile_op", {{"op", migraphx::to_value(concat_op)}, {"additional_args", 2}, {"ignore_modules", true}, {"output_shape", migraphx::to_value(s3)}}); auto alloc = migraphx::make_op("allocate", {{"shape", to_value(s3)}}); auto alloc_ins = mm->add_instruction(alloc); // use y's input shape for creating pointwise module for both the params auto* pw_add1 = create_pointwise_module(p, "main:pointwise0", {y, y}, single_pointwise("add")); auto x = mm->add_instruction(concat_precompile_op, {x1, x2, y, alloc_ins}, {pw_add1}); auto pw_op = migraphx::make_op("pointwise"); auto rsp = mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {25088, 128}}}), x); mm->add_return({rsp}); return p; }; migraphx::program p1 = create_program(); run_pass(p1); migraphx::program p2 = create_fused_program(); EXPECT(p1 == p2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/gemm_tune.cpp000066400000000000000000000201421510465702400207750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include // includes needed for run_lowering #include #include #include #include #if MIGRAPHX_USE_ROCBLAS or MIGRAPHX_USE_HIPBLASLT // Abbreviated lowering; we don't need the usual cleanup passes for this test static void run_lowering(migraphx::program& p, bool offload_copy = false) { auto ctx = migraphx::gpu::context{}; migraphx::run_passes( *p.get_main_module(), {migraphx::auto_contiguous{}, migraphx::gpu::lowering{&ctx, offload_copy}}); } /** * Tests the automatic GEMM tuning feature for rocBLAS and hipBLASLt. * In the finalize() method of the gemm op, * rocBLAS API functions are called to quickly benchmark all the GEMM solutions * available in the currently installed rocBLAS library and choose the index of the fastest. */ TEST_CASE(gemm_tune) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {4, 2}}; migraphx::shape sb{migraphx::shape::float_type, {2, 3}}; auto a = mm->add_parameter("a", sa); auto b = mm->add_parameter("b", sb); migraphx::operation dot_op = migraphx::make_op("dot"); mm->add_instruction(dot_op, a, b); // lowering adds gemm implementation for dot operator run_lowering(p); migraphx::target gpu_t = migraphx::gpu::target{}; migraphx::compile_options options; options.exhaustive_tune = true; p.compile(gpu_t, options); migraphx::value solution_idx(0); for(auto ins : iterator_for(*p.get_main_module())) { if(ins->name() == "gpu::gemm" or ins->name() == "gpu::hip_gemm") { auto gemm_op = migraphx::get_operation(ins); // tuned solution index is not deterministic, but anything other than 0 // (default, invalid, or not available) is good. // gemm_op.to_value().debug_print(); solution_idx = gemm_op.to_value()["solution_idx"]; break; } } #if defined(MIGRAPHX_USE_ROCBLAS_TUNING_API) or MIGRAPHX_USE_HIPBLASLT EXPECT(0 != solution_idx.to()); #else EXPECT(0 == solution_idx.to()); #endif } // GEMM tuning of a strided-batch matrix; invokes rocblas_gemm_strided_batched_ex TEST_CASE(gemm_tune_strided) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {4, 2, 2}}; migraphx::shape sb{migraphx::shape::float_type, {4, 2, 2}}; migraphx::shape s_output{migraphx::shape::float_type, {4, 2, 2}}; auto a = mm->add_parameter("a", sa); auto b = mm->add_parameter("b", sb); auto output = mm->add_parameter("out", s_output); auto gemm_oper = migraphx::make_op("gpu::gemm", {{"beta", 2}}); mm->add_instruction(gemm_oper, a, b, output); migraphx::target gpu_t = migraphx::gpu::target{}; migraphx::compile_options options; options.exhaustive_tune = true; p.compile(gpu_t, options); migraphx::value solution_idx(0); for(auto ins : iterator_for(*p.get_main_module())) { if(ins->name() == "gpu::gemm" or ins->name() == "gpu::hip_gemm") { auto gemm_op = migraphx::get_operation(ins); auto gemmv = gemm_op.to_value(); // tuned solution index is not deterministic, but anything other than 0 // (default, invalid, or not available) is good. solution_idx = gemm_op.to_value()["solution_idx"]; break; } } #if defined(MIGRAPHX_USE_ROCBLAS_TUNING_API) or MIGRAPHX_USE_HIPBLASLT EXPECT(0 != solution_idx.to()); #else EXPECT(0 == solution_idx.to()); #endif } // GEMM tuning of a strided-batch matrix; created by lowering TEST_CASE(gemm_tune_strided_lowered) { migraphx::program p; auto* mm = p.get_main_module(); // At time of writing this test, gemm_impl considers a shape is strided if it has // at least three dimensions and the 3rd-to-last is nonzero, invoking // rocblas_gemm_strided_batched_ex. Also, DOT operator requires all dimensions except the last // two to be equal. migraphx::shape sa{migraphx::shape::float_type, {4, 2, 5}}; migraphx::shape sb{migraphx::shape::float_type, {4, 5, 3}}; auto a = mm->add_parameter("a", sa); auto b = mm->add_parameter("b", sb); migraphx::operation dot_op = migraphx::make_op("dot"); mm->add_instruction(dot_op, a, b); // lowering adds gemm implementation for dot operator run_lowering(p); migraphx::target gpu_t = migraphx::gpu::target{}; migraphx::compile_options options; options.exhaustive_tune = true; p.compile(gpu_t, options); migraphx::value solution_idx(0); for(auto ins : iterator_for(*p.get_main_module())) { if(ins->name() == "gpu::gemm" or ins->name() == "gpu::hip_gemm") { auto gemm_op = migraphx::get_operation(ins); // tuned solution index is not deterministic, but anything other than 0 // (default, invalid, or not available) is good. solution_idx = gemm_op.to_value()["solution_idx"]; break; } } #if defined(MIGRAPHX_USE_ROCBLAS_TUNING_API) or MIGRAPHX_USE_HIPBLASLT EXPECT(0 != solution_idx.to()); #else EXPECT(0 == solution_idx.to()); #endif } TEST_CASE(gemm_tune_invalid_sol_index) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {4, 2}}; migraphx::shape sb{migraphx::shape::float_type, {2, 3}}; migraphx::shape s_output{migraphx::shape::float_type, {4, 3}}; auto a = mm->add_parameter("a", sa); auto b = mm->add_parameter("b", sb); auto output = mm->add_parameter("out", s_output); auto gemm_oper = migraphx::make_op("gpu::gemm", {{"solution_idx", 987654321}}); mm->add_instruction(gemm_oper, a, b, output); migraphx::target gpu_t = migraphx::gpu::target{}; migraphx::compile_options options; options.exhaustive_tune = true; p.compile(gpu_t, options); migraphx::value solution_idx(0); for(auto ins : iterator_for(*p.get_main_module())) { if(ins->name() == "gpu::gemm" or ins->name() == "gpu::hip_gemm") { auto gemm_op = migraphx::get_operation(ins); auto gemmv = gemm_op.to_value(); // given invalid starting index, should return default 0 solution_idx = gemm_op.to_value()["solution_idx"]; break; } } #if defined(MIGRAPHX_USE_ROCBLAS_TUNING_API) or MIGRAPHX_USE_HIPBLASLT EXPECT(0 == solution_idx.to()); #else EXPECT(0 != solution_idx.to()); #endif } #endif int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/hip.cpp000066400000000000000000000065361510465702400176100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(tuple_from_gpu) { migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::int32_type, {2, 4}}; std::vector p1_data = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6}; std::vector p2_data = {1, 2, 3, 4, 5, 6, 7, 8}; auto p1 = migraphx::argument{s1, p1_data.data()}; auto p2 = migraphx::argument{s2, p2_data.data()}; auto p1_gpu = migraphx::gpu::to_gpu(p1); auto p2_gpu = migraphx::gpu::to_gpu(p2); auto p_tuple = migraphx::gpu::from_gpu(migraphx::argument({p1_gpu, p2_gpu})); std::vector results = p_tuple.get_sub_objects(); std::vector result1; results[0].visit([&](auto output) { result1.assign(output.begin(), output.end()); }); std::vector result2; results[1].visit([&](auto output) { result2.assign(output.begin(), output.end()); }); EXPECT(result1 == p1_data); EXPECT(result2 == p2_data); } TEST_CASE(tuple_to_gpu) { migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::int32_type, {2, 4}}; std::vector p1_data = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6}; std::vector p2_data = {1, 2, 3, 4, 5, 6, 7, 8}; auto p1 = migraphx::argument{s1, p1_data.data()}; auto p2 = migraphx::argument{s2, p2_data.data()}; auto p_gpu = migraphx::gpu::to_gpu(migraphx::argument({p1, p2})); auto p_host = migraphx::gpu::from_gpu(p_gpu); std::vector results = p_host.get_sub_objects(); std::vector result1; results[0].visit([&](auto output) { result1.assign(output.begin(), output.end()); }); std::vector result2; results[1].visit([&](auto output) { result2.assign(output.begin(), output.end()); }); EXPECT(result1 == p1_data); EXPECT(result2 == p2_data); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/jit.cpp000066400000000000000000000363351510465702400176160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // NOLINTNEXTLINE const std::string write_2s = R"__migraphx__( #ifndef __HIPCC_RTC__ #include #endif extern "C" { __global__ void write(char* data) { int num = threadIdx.x + blockDim.x * blockIdx.x; data[num] = 2; } } int main() {} )__migraphx__"; // NOLINTNEXTLINE const std::string add_2s_binary = R"__migraphx__( #ifndef __HIPCC_RTC__ #include #endif extern "C" { __global__ void add_2(char* x, char* y) { int num = threadIdx.x + blockDim.x * blockIdx.x; y[num] = x[num] + 2; } } int main() {} )__migraphx__"; // NOLINTNEXTLINE const std::string simple_pointwise_increment = R"__migraphx__( #include #include using namespace migraphx; extern "C" { __global__ void kernel(void* x, void* y) { make_tensors()(x, y)([](auto xt, auto yt) __device__ { auto idx = make_index(); const auto stride = idx.nglobal(); for(index_int i = idx.global; i < xt.get_shape().elements(); i += stride) { yt[i] = xt[i] + 1; } }); } } int main() {} )__migraphx__"; // NOLINTNEXTLINE const std::string check_define = R"__migraphx__( #ifndef __DEFINE__ #error __DEFINE__ was not defined #endif int main() {} )__migraphx__"; // NOLINTNEXTLINE const std::string unused_param = R"__migraphx__( extern "C" { __global__ void kernel(void* x, void* y) {} } int main() {} )__migraphx__"; // NOLINTNEXTLINE const std::string incorrect_program = R"__migraphx__( extern "C" { __global__ void kernel(void* x) { x += y; } } int main() {} )__migraphx__"; // NOLINTNEXTLINE const std::string math_template = R"__migraphx__( #include #include #include namespace migraphx { template struct test_implicit_conversion_op { T x; template constexpr operator vec() const { if constexpr(vec_size() == 0) { return x; } else { static_assert(vec_size() == N, "Vector mismatch size"); return __builtin_convertvector(x, vec); } } template constexpr operator U() const { static_assert(is_same{} or not is_same{} or is_same{}); return static_cast(x); } }; template constexpr test_implicit_conversion_op test_implicit_conversion(T x) { return {x}; } extern "C" { __global__ void kernel(${type}* p) { auto x = *p; *p = migraphx::test_implicit_conversion(migraphx::${invoke}); (void)(1.f + migraphx::vec_at(migraphx::${invoke}, 0)); } } } int main() {} )__migraphx__"; static migraphx::src_file make_src_file(const std::string& name, const std::string& content) { return {name, content}; } TEST_CASE(simple_compile_hip) { auto binaries = migraphx::gpu::compile_hip_src( {make_src_file("main.cpp", write_2s)}, {}, migraphx::gpu::get_device_name()); EXPECT(binaries.size() == 1); migraphx::argument input{{migraphx::shape::int8_type, {5}}}; auto ginput = migraphx::gpu::to_gpu(input); migraphx::gpu::kernel k{binaries.front(), "write"}; k.launch(nullptr, input.get_shape().elements(), 1024)(ginput.cast()); auto output = migraphx::gpu::from_gpu(ginput); EXPECT(output != input); auto data = output.get(); EXPECT(migraphx::all_of(data, [](auto x) { return x == 2; })); } static auto check_target(const std::string& arch) { auto define = "__" + arch + "__"; auto content = migraphx::replace_string(check_define, "__DEFINE__", define); return migraphx::gpu::compile_hip_src({make_src_file("main.cpp", content)}, {}, arch); } TEST_CASE(compile_target) { EXPECT(not check_target("gfx900").empty()); EXPECT(not check_target("gfx906").empty()); } TEST_CASE(compile_errors) { EXPECT(test::throws([&] { migraphx::gpu::compile_hip_src( {make_src_file("main.cpp", incorrect_program)}, {}, migraphx::gpu::get_device_name()); })); } TEST_CASE(compile_warnings) { auto compile = [](const std::vector& params) { return migraphx::gpu::compile_hip_src( {make_src_file("main.cpp", unused_param)}, params, migraphx::gpu::get_device_name()); }; EXPECT(not compile({}).empty()); EXPECT(not compile({"-Wunused-parameter", "-Wno-error"}).empty()); EXPECT(not compile({"-Wno-unused-parameter", "-Werror"}).empty()); #ifdef MIGRAPHX_USE_HIPRTC EXPECT(test::throws([&] { compile({"-Werror=unused-parameter"}); })); EXPECT(test::throws([&] { compile({"-Wunused-parameter", "-Werror"}); })); #else EXPECT(test::throws([&] { compile({"-Werror=unused-parameter"}); })); EXPECT(test::throws([&] { compile({"-Wunused-parameter", "-Werror"}); })); #endif } TEST_CASE(has_flags) { EXPECT(migraphx::gpu::hip_has_flags({"--std=c++17"})); EXPECT(not migraphx::gpu::hip_has_flags({"--non-existent-flag-to-test-in-migraphx"})); EXPECT(migraphx::gpu::hip_has_flags({"-Wunused-parameter"})); EXPECT(not migraphx::gpu::hip_has_flags( {"-Wnon-existent-warnings-flag-to-test-in-migraphx", "-Werror"})); } TEST_CASE(code_object_hip) { auto binaries = migraphx::gpu::compile_hip_src( {make_src_file("main.cpp", add_2s_binary)}, {}, migraphx::gpu::get_device_name()); EXPECT(binaries.size() == 1); migraphx::shape input{migraphx::shape::int8_type, {5}}; std::vector expected_inputs = {input, input}; auto co = migraphx::make_op("gpu::code_object", {{"code_object", migraphx::value::binary{binaries.front()}}, {"symbol_name", "add_2"}, {"global", input.elements()}, {"local", 1024}, {"expected_inputs", migraphx::to_value(expected_inputs)}, {"output", migraphx::to_value(input)}}); migraphx::program p; auto* mm = p.get_main_module(); auto input_literal = migraphx::generate_literal(input); auto output_literal = migraphx::transform(input_literal, [](auto x) { return x + 2; }); auto x = mm->add_literal(input_literal); auto y = mm->add_parameter("output", input); mm->add_instruction(co, x, y); migraphx::compile_options options; p.compile(migraphx::make_target("gpu"), options); auto result = migraphx::gpu::from_gpu(p.eval({{"output", migraphx::gpu::allocate_gpu(input)}}).front()); EXPECT(result == output_literal.get_argument()); } TEST_CASE(compile_code_object_hip) { migraphx::shape input{migraphx::shape::float_type, {5, 2}}; migraphx::gpu::hip_compile_options options; migraphx::gpu::context ctx; options.global = 256 * 1024; options.local = 1024; options.inputs = {input, input}; options.output = input; auto co = migraphx::gpu::compile_hip_code_object(ctx, simple_pointwise_increment, options); migraphx::program p; auto* mm = p.get_main_module(); auto input_literal = migraphx::generate_literal(input); auto output_literal = migraphx::transform(input_literal, [](auto x) { return x + 1; }); auto x = mm->add_literal(input_literal); auto y = mm->add_parameter("output", input); mm->add_instruction(co, x, y); p.compile(migraphx::make_target("gpu"), migraphx::compile_options{}); auto result = migraphx::gpu::from_gpu(p.eval({{"output", migraphx::gpu::allocate_gpu(input)}}).front()); EXPECT(result == output_literal.get_argument()); } TEST_CASE(compile_pointwise) { migraphx::shape input{migraphx::shape::float_type, {5, 2}}; migraphx::gpu::context ctx; auto co = migraphx::gpu::compile_op( "pointwise", ctx, {input, input}, {{"lambda", "[](auto x) { return make_tuple(x + 1); }"}}); migraphx::program p; auto* mm = p.get_main_module(); auto input_literal = migraphx::generate_literal(input); auto output_literal = migraphx::transform(input_literal, [](auto x) { return x + 1; }); auto x = mm->add_literal(input_literal); auto y = mm->add_parameter("output", input); mm->add_instruction(co, x, y); p.compile(migraphx::make_target("gpu"), migraphx::compile_options{}); auto result = migraphx::gpu::from_gpu(p.eval({{"output", migraphx::gpu::allocate_gpu(input)}}).front()); EXPECT(result == output_literal.get_argument()); } TEST_CASE(compile_math) { std::vector math_invoke = { // clang-format off "abs(x)", "acos(x)", "acosh(x)", "asin(x)", "asinh(x)", "atan(x)", "atanh(x)", "ceil(x)", "cos(x)", "cosh(x)", "erf(x)", "exp(x)", "floor(x)", "fmod(x, x)", "isnan(x)", "log(x)", "max(x, x)", "min(x, x)", "pow(x, 0)", "pow(x, x)", "remainder(x,x)", "round(x)", "rsqrt(x)", "sin(x)", "sinh(x)", "sqrt(x)", "tan(x)", "tanh(x)", "where(true, x, x)", // clang-format on }; std::vector data_types; auto vec_sizes = {2, 4, 6}; for(auto&& t : migraphx::shape::types()) { if(contains({migraphx::shape::bool_type, migraphx::shape::tuple_type}, t)) continue; auto name = migraphx::shape::cpp_type(t); if(contains({migraphx::shape::half_type, migraphx::shape::bf16_type}, t)) name.insert(0, "migraphx::"); data_types.push_back(name); // fp8 doesn't have vectorization support yet, therefore skip it for now. std::set fp8_types = {migraphx::shape::fp8e4m3fnuz_type, migraphx::shape::fp8e5m2fnuz_type, migraphx::shape::fp8e4m3fn_type, migraphx::shape::fp8e5m2_type}; if(not contains(fp8_types, t)) { migraphx::transform(vec_sizes, std::back_inserter(data_types), [&](auto i) { return "migraphx::vec<" + name + ", " + std::to_string(i) + ">"; }); } } migraphx::shape input{migraphx::shape::float_type, {5, 2}}; migraphx::gpu::hip_compile_options options; migraphx::gpu::context ctx; options.global = 1024; options.local = 1024; options.inputs = {input}; options.output = input; migraphx::par_for(math_invoke.size() * data_types.size(), 1, [&](auto i) { const auto& t = data_types[i % data_types.size()]; const auto& invoke = math_invoke[i / data_types.size()]; auto src = migraphx::interpolate_string(math_template, {{"type", t}, {"invoke", invoke}}); auto co = migraphx::gpu::compile_hip_code_object(ctx, src, options); (void)co; }); } // NOLINTNEXTLINE const std::string assert_template = R"__migraphx__( #include #include using namespace migraphx; extern "C" { __global__ void kernel(void*) { static_assert(numeric_max<${type}>() == ${max}, ""); static_assert(numeric_lowest<${type}>() == ${min}, ""); } } int main() {} )__migraphx__"; TEST_CASE(assert_type_min_max) { std::vector data_types; migraphx::gpu::hip_compile_options options; migraphx::gpu::context ctx; for(auto&& t : migraphx::shape::types()) { if(contains( { migraphx::shape::bool_type, migraphx::shape::tuple_type, }, t)) continue; auto name = migraphx::shape::cpp_type(t); if(contains({migraphx::shape::half_type, migraphx::shape::bf16_type}, t)) name.insert(0, "migraphx::"); migraphx::shape::visit(t, [&](auto as) { std::string min = ""; std::string max = ""; // Note 9223372036854775808 is a constant literal that is outside the range of long // long type For the same reason, 18446744073709551616 needs postfix ULL to be parsed // correctly if(t == migraphx::shape::int64_type) { min = "(" + std::to_string(as.min() + 1) + "LL - 1)"; max = std::to_string(as.max()); } else if(t == migraphx::shape::uint64_type) { min = std::to_string(as.min()); max = std::to_string(as.max()) + "ULL"; } else { min = std::to_string(as.min()); max = std::to_string(as.max()); } auto src = migraphx::interpolate_string(assert_template, {{"type", name}, {"max", max}, {"min", min}}); migraphx::shape input{migraphx::shape::float_type, {5, 2}}; options.global = 1024; options.local = 1024; options.inputs = {input}; options.output = input; options.emplace_param("-Wno-float-equal"); auto co = migraphx::gpu::compile_hip_code_object(ctx, src, options); }); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/literal.cpp000066400000000000000000000037751510465702400204660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(gpu_literal_test) { migraphx::program p; auto* mm = p.get_main_module(); auto lit = generate_literal(migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); mm->add_literal(lit); p.compile(migraphx::make_target("gpu")); auto scratch = p.get_parameter("scratch"); if(scratch == mm->end()) { auto result = p.eval({}).back(); EXPECT(lit == migraphx::gpu::from_gpu(result)); } else { EXPECT(scratch->get_shape().bytes() == lit.get_shape().bytes()); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/make_precompile_op.hpp000066400000000000000000000061301510465702400226550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_GPU_MAKE_PRECOMPILE_OP_HPP #define MIGRAPHX_GUARD_TEST_GPU_MAKE_PRECOMPILE_OP_HPP #include #include #include // NOLINTNEXTLINE #define MIGRAPHX_GPU_TEST_PRECOMPILE(...) \ struct test_compiler : migraphx::gpu::compiler \ { \ std::vector names() const { return {__VA_ARGS__}; } \ \ template \ migraphx::operation compile_op(Ts&&...) const \ { \ MIGRAPHX_THROW("Not compilable"); \ } \ \ template \ migraphx::gpu::compiler_replace compile(Ts&&...) const \ { \ MIGRAPHX_THROW("Not compilable"); \ } \ }; inline migraphx::operation make_precompile_op(migraphx::rank<0>, const migraphx::operation& op) { return migraphx::make_op("gpu::precompile_op", {{"op", migraphx::to_value(op)}}); } inline migraphx::operation make_precompile_op(migraphx::rank<1>, const std::string& name) { return make_precompile_op(migraphx::rank<0>{}, migraphx::make_op(name)); } template auto make_precompile_op(const T& x) { return make_precompile_op(migraphx::rank<1>{}, x); } #endif // MIGRAPHX_GUARD_TEST_GPU_MAKE_PRECOMPILE_OP_HPP ROCm-AMDMIGraphX-46524e8/test/gpu/manage_host_buffer.cpp000066400000000000000000000101131510465702400226300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #define MIGRAPHX_HIP_ASSERT(x) (EXPECT(x == hipSuccess)) TEST_CASE(host_same_buffer_copy) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ss{migraphx::shape::float_type, {4, 2}}; auto a = mm->add_parameter("a", ss); auto b = mm->add_parameter("b", ss); auto aa = mm->add_instruction(migraphx::make_op("add"), a, a); auto gpu_out = mm->add_instruction(migraphx::make_op("hip::copy_from_gpu"), aa); auto stream_sync = mm->add_instruction(migraphx::make_op("hip::sync_stream"), gpu_out); auto pass = mm->add_instruction(unary_pass_op{}, stream_sync); auto alloc = mm->add_instruction( migraphx::make_op("hip::allocate", {{"shape", migraphx::to_value(ss)}})); auto gpu_in = mm->add_instruction(migraphx::make_op("hip::copy_to_gpu"), pass, alloc); auto aab = mm->add_instruction(migraphx::make_op("add"), gpu_in, b); mm->add_return({aab}); migraphx::parameter_map pp; std::vector a_vec(ss.elements(), -1); std::vector b_vec(ss.elements(), 2); pp["a"] = migraphx::argument(ss, a_vec.data()); pp["b"] = migraphx::argument(ss, b_vec.data()); std::vector gpu_result; migraphx::target gpu_t = migraphx::make_target("gpu"); migraphx::compile_options options; options.offload_copy = true; p.compile(gpu_t, options); auto result = p.eval(pp).back(); std::vector results_vector(ss.elements(), -1); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_vec(ss.elements(), 0); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_vec)); } TEST_CASE(arguments_lifetime) { auto use_on_gpu = [](const migraphx::argument& arg, int c) { auto* arg_ptr = arg.data(); MIGRAPHX_HIP_ASSERT(hipSetDevice(0)); MIGRAPHX_HIP_ASSERT(hipMemset(arg_ptr, c, arg.get_shape().bytes())); MIGRAPHX_HIP_ASSERT(hipDeviceSynchronize()); return; }; auto f = [use_on_gpu](const migraphx::argument& input) { auto a = migraphx::gpu::register_on_gpu(input); auto s = a.get_shape(); { auto b = migraphx::gpu::register_on_gpu(input); use_on_gpu(b, 0); std::vector expected_b(s.elements(), 0); auto gold = migraphx::argument(s, expected_b.data()); } use_on_gpu(a, 1); return true; }; migraphx::shape ss{migraphx::shape::float_type, {4, 2}}; std::vector x_data(ss.elements(), -1); migraphx::argument x{ss, x_data.data()}; EXPECT(f(x)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/mlir.cpp000066400000000000000000001000261510465702400177600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_MLIR_ENABLE_SPLITK); struct mlir_gpu_target : migraphx::gpu::target { std::string name() const { return "mlir"; } std::vector get_passes(migraphx::context& gctx, const migraphx::compile_options&) const { auto& ctx = migraphx::any_cast(gctx); return {migraphx::gpu::write_literals{&ctx}}; } }; static std::string encode(const std::string& s) { std::stringstream ss; bool prespace = false; for(auto c : s) { if(std::isspace(c) != 0) { if(not prespace) ss << " "; prespace = true; } else if(std::isprint(c) != 0) { ss << c; prespace = false; } } return migraphx::trim(ss.str()); } static migraphx::module create_mlir_submodule(const migraphx::module& mmlir) { migraphx::module m; std::unordered_map map_ins; auto params = mmlir.get_parameter_names(); for(const auto& name : params) { auto param = mmlir.get_parameter(name); map_ins[param] = m.add_parameter(name, param->get_shape().as_standard()); } auto y = m.add_instructions(&mmlir, &map_ins); m.add_return(y); return m; } static migraphx::program create_program_from_mlir(const migraphx::module& mmlir) { migraphx::program p; auto* mm = p.get_main_module(); auto names = mmlir.get_parameter_names(); std::vector inputs; std::transform(names.begin(), names.end(), std::back_inserter(inputs), [&](const auto& name) { return mm->add_parameter(name, mmlir.get_parameter_shape(name)); }); std::sort(inputs.begin(), inputs.end(), migraphx::by(std::less<>{}, [](auto ins) { return to_string(ins->get_operator()); })); inputs.push_back(mm->add_parameter("output", mmlir.get_output_shapes().front())); migraphx::gpu::context ctx; migraphx::gpu::mlir_code_object mco = compile_mlir(ctx, create_mlir_submodule(mmlir), to_shapes(inputs), {}); migraphx::gpu::insert_mlir(*mm, mm->end(), mco.cop, inputs); return p; } static migraphx::parameter_map generate_params(const migraphx::program& p) { migraphx::parameter_map m; std::size_t i = 0; for(auto&& x : p.get_parameter_shapes()) { // m[x.first] = migraphx::fill_argument(x.second, 1); m[x.first] = migraphx::generate_argument(x.second, i++); } return m; } static migraphx::argument run_gpu(migraphx::program p, const migraphx::parameter_map& inputs) { mlir_gpu_target t; p.compile(t); migraphx::parameter_map m; for(auto&& input : inputs) { m[input.first] = t.copy_to(input.second); } for(auto&& x : p.get_parameter_shapes()) { if(m.count(x.first) == 0) { m[x.first] = t.allocate(x.second); } } return t.copy_from(p.eval(m).front()); } static migraphx::argument run_ref(migraphx::program p, const migraphx::parameter_map& inputs) { p.compile(migraphx::make_target("ref")); return p.eval(inputs).front(); } static bool verify_mlir(const migraphx::module& mmlir) { migraphx::program ref; ref.get_main_module()->insert_instructions(ref.get_main_module()->end(), &mmlir); auto inputs = generate_params(ref); auto mlir = create_program_from_mlir(mmlir); return migraphx::verify_args_with_tolerance( "mlir", run_gpu(mlir, inputs), migraphx::verify::expected{run_ref(ref, inputs)}); } static std::string get_attrs() { if(migraphx::enabled(MIGRAPHX_MLIR_ENABLE_SPLITK{})) { return R"({arch = "", enable_splitk_for_tuning, kernel = "mixr", num_cu = 0 : i64})"; } return R"({arch = "", kernel = "mixr", num_cu = 0 : i64})"; } TEST_CASE(conv) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_convolution(%arg0: !migraphx.shaped<2x8x3x3xf32, 72x9x3x1>, %arg1: !migraphx.shaped<1x8x4x4xf32, 128x16x4x1>) -> !migraphx.shaped<1x2x2x2xf32, 8x4x2x1> attributes ${attrs} { %0 = migraphx.convolution %arg1, %arg0 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x8x4x4xf32, 128x16x4x1>, <2x8x3x3xf32, 72x9x3x1> -> <1x2x2x2xf32, 8x4x2x1> return %0 : !migraphx.shaped<1x2x2x2xf32, 8x4x2x1> } } )__migraphx__"; migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 8, 4, 4}}); auto w = m.add_parameter("w", {migraphx::shape::float_type, {2, 8, 3, 3}}); auto conv = m.add_instruction(migraphx::make_op("convolution"), x, w); m.add_return({conv}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(conv_nhwc) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_convolution(%arg0: !migraphx.shaped<2x8x3x3xf32, 72x1x24x8>, %arg1: !migraphx.shaped<1x8x4x4xf32, 128x1x32x8>) -> !migraphx.shaped<1x2x2x2xf32, 8x1x4x2> attributes ${attrs} { %0 = migraphx.convolution %arg1, %arg0 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x8x4x4xf32, 128x1x32x8>, <2x8x3x3xf32, 72x1x24x8> -> <1x2x2x2xf32, 8x1x4x2> return %0 : !migraphx.shaped<1x2x2x2xf32, 8x1x4x2> } } )__migraphx__"; migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 8, 4, 4}, {128, 1, 32, 8}}); auto w = m.add_parameter("w", {migraphx::shape::float_type, {2, 8, 3, 3}, {72, 1, 24, 8}}); auto conv = m.add_instruction(migraphx::make_op("convolution"), x, w); m.add_return({conv}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(conv_add_relu) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_convolution_add_relu(%arg0: !migraphx.shaped<1x2x2x2xf32, 8x4x2x1>, %arg1: !migraphx.shaped<2x8x3x3xf32, 72x9x3x1>, %arg2: !migraphx.shaped<1x8x4x4xf32, 128x16x4x1>) -> !migraphx.shaped<1x2x2x2xf32, 8x4x2x1> attributes ${attrs} { %0 = migraphx.convolution %arg2, %arg1 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x8x4x4xf32, 128x16x4x1>, <2x8x3x3xf32, 72x9x3x1> -> <1x2x2x2xf32, 8x4x2x1> %1 = migraphx.add %0, %arg0 : <1x2x2x2xf32, 8x4x2x1>, <1x2x2x2xf32, 8x4x2x1> -> <1x2x2x2xf32, 8x4x2x1> %2 = migraphx.relu %1 : <1x2x2x2xf32, 8x4x2x1> -> <1x2x2x2xf32, 8x4x2x1> return %2 : !migraphx.shaped<1x2x2x2xf32, 8x4x2x1> } } )__migraphx__"; migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 8, 4, 4}}); auto w = m.add_parameter("w", {migraphx::shape::float_type, {2, 8, 3, 3}}); auto b = m.add_parameter("b", {migraphx::shape::float_type, {1, 2, 2, 2}}); auto conv = m.add_instruction(migraphx::make_op("convolution"), x, w); auto add = m.add_instruction(migraphx::make_op("add"), conv, b); auto relu = m.add_instruction(migraphx::make_op("relu"), add); m.add_return({relu}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } // The following test checks that a dimension -1, within reshape operator is handled properly.. TEST_CASE(conv_reshape_dim_minus_one) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_convolution_reshape(%arg0: !migraphx.shaped<2x8x3x3xf32, 72x9x3x1>, %arg1: !migraphx.shaped<1x8x4x4xf32, 128x16x4x1>) -> !migraphx.shaped<1x4x1x2xf32, 8x2x2x1> attributes ${attrs} { %0 = migraphx.convolution %arg1, %arg0 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x8x4x4xf32, 128x16x4x1>, <2x8x3x3xf32, 72x9x3x1> -> <1x2x2x2xf32, 8x4x2x1> %1 = migraphx.reshape %0 {dims = [1, 4, 1, 2]} : <1x2x2x2xf32, 8x4x2x1> -> <1x4x1x2xf32, 8x2x2x1> return %1 : !migraphx.shaped<1x4x1x2xf32, 8x2x2x1> } } )__migraphx__"; migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 8, 4, 4}}); auto w = m.add_parameter("w", {migraphx::shape::float_type, {2, 8, 3, 3}}); auto conv = m.add_instruction(migraphx::make_op("convolution"), x, w); auto reshape = m.add_instruction(migraphx::make_op("reshape", {{"dims", {1, -1, 1, 2}}}), conv); m.add_return({reshape}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(conv_reduce_sum) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_convolution_reshape_reduce_sum_reshape(%arg0: !migraphx.shaped<2x8x3x3xf32, 72x9x3x1>, %arg1: !migraphx.shaped<1x8x4x4xf32, 128x16x4x1>) -> !migraphx.shaped<1x2x1x1xf32, 2x1x1x1> attributes ${attrs} { %0 = migraphx.convolution %arg1, %arg0 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x8x4x4xf32, 128x16x4x1>, <2x8x3x3xf32, 72x9x3x1> -> <1x2x2x2xf32, 8x4x2x1> %1 = migraphx.reshape %0 {dims = [1, 2, 4]} : <1x2x2x2xf32, 8x4x2x1> -> <1x2x4xf32, 8x4x1> %2 = migraphx.reduce_sum %1 {axes = [2]} : <1x2x4xf32, 8x4x1> -> <1x2x1xf32, 2x1x1> %3 = migraphx.reshape %2 {dims = [1, 2, 1, 1]} : <1x2x1xf32, 2x1x1> -> <1x2x1x1xf32, 2x1x1x1> return %3 : !migraphx.shaped<1x2x1x1xf32, 2x1x1x1> } } )__migraphx__"; migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 8, 4, 4}}); auto w = m.add_parameter("w", {migraphx::shape::float_type, {2, 8, 3, 3}}); auto conv = m.add_instruction(migraphx::make_op("convolution"), x, w); auto reduce_sum = m.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3}}}), conv); m.add_return({reduce_sum}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); // EXPECT(verify_mlir(m)); } TEST_CASE(conv_backwards) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_convolution_backwards(%arg0: !migraphx.shaped<1x1x3x3xf32, 9x9x3x1>, %arg1: !migraphx.shaped<1x1x3x3xf32, 9x9x3x1>) -> !migraphx.shaped<1x1x5x5xf32, 25x25x5x1> attributes ${attrs} { %0 = migraphx.backwards_data_convolution %arg1, %arg0 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x1x3x3xf32, 9x9x3x1>, <1x1x3x3xf32, 9x9x3x1> -> <1x1x5x5xf32, 25x25x5x1> return %0 : !migraphx.shaped<1x1x5x5xf32, 25x25x5x1> } } )__migraphx__"; migraphx::module m; auto x = m.add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 3, 3}}); auto w = m.add_parameter("w", migraphx::shape{migraphx::shape::float_type, {1, 1, 3, 3}}); auto conv_b = m.add_instruction(migraphx::make_op("convolution_backwards"), x, w); m.add_return({conv_b}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(quant_dot_add) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_quant_dot_add(%arg0: !migraphx.shaped<1x5x4xsi8, 20x4x1>, %arg1: !migraphx.shaped<1x4x3xsi8, 12x3x1>, %arg2: !migraphx.shaped<1x5x3xsi32, 15x3x1>) -> !migraphx.shaped<1x5x3xsi32, 15x3x1> attributes ${attrs} { %0 = migraphx.quant_dot %arg0, %arg1 : <1x5x4xsi8, 20x4x1>, <1x4x3xsi8, 12x3x1> -> <1x5x3xsi32, 15x3x1> %1 = migraphx.add %0, %arg2 : <1x5x3xsi32, 15x3x1>, <1x5x3xsi32, 15x3x1> -> <1x5x3xsi32, 15x3x1> return %1 : !migraphx.shaped<1x5x3xsi32, 15x3x1> } } )__migraphx__"; migraphx::module m; auto arg0 = m.add_parameter("arg0", {migraphx::shape::int8_type, {1, 5, 4}}); auto arg1 = m.add_parameter("arg1", {migraphx::shape::int8_type, {1, 4, 3}}); auto arg2 = m.add_parameter("arg2", {migraphx::shape::int32_type, {1, 5, 3}}); auto conv = m.add_instruction(migraphx::make_op("quant_dot"), arg0, arg1); auto add = m.add_instruction(migraphx::make_op("add"), conv, arg2); m.add_return({add}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(dot_add) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_dot_add(%arg0: !migraphx.shaped<1x5x4xf32, 20x4x1>, %arg1: !migraphx.shaped<1x4x3xf32, 12x3x1>, %arg2: !migraphx.shaped<1x5x3xf32, 15x3x1>) -> !migraphx.shaped<1x5x3xf32, 15x3x1> attributes ${attrs} { %0 = migraphx.dot %arg0, %arg1 : <1x5x4xf32, 20x4x1>, <1x4x3xf32, 12x3x1> -> <1x5x3xf32, 15x3x1> %1 = migraphx.add %0, %arg2 : <1x5x3xf32, 15x3x1>, <1x5x3xf32, 15x3x1> -> <1x5x3xf32, 15x3x1> return %1 : !migraphx.shaped<1x5x3xf32, 15x3x1> } } )__migraphx__"; migraphx::module m; auto arg0 = m.add_parameter("arg0", {migraphx::shape::float_type, {1, 5, 4}}); auto arg1 = m.add_parameter("arg1", {migraphx::shape::float_type, {1, 4, 3}}); auto arg2 = m.add_parameter("arg2", {migraphx::shape::float_type, {1, 5, 3}}); auto conv = m.add_instruction(migraphx::make_op("dot"), arg0, arg1); auto add = m.add_instruction(migraphx::make_op("add"), conv, arg2); m.add_return({add}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(unsqueeze_dot_add) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_unsqueeze_dot_add(%arg0: !migraphx.shaped<5x4xf32, 4x1>, %arg1: !migraphx.shaped<1x4x3xf32, 12x3x1>, %arg2: !migraphx.shaped<1x5x3xf32, 15x3x1>) -> !migraphx.shaped<1x5x3xf32, 15x3x1> attributes ${attrs} { %0 = migraphx.reshape %arg0 {dims = [1, 5, 4]} : <5x4xf32, 4x1> -> <1x5x4xf32, 20x4x1> %1 = migraphx.dot %0, %arg1 : <1x5x4xf32, 20x4x1>, <1x4x3xf32, 12x3x1> -> <1x5x3xf32, 15x3x1> %2 = migraphx.add %1, %arg2 : <1x5x3xf32, 15x3x1>, <1x5x3xf32, 15x3x1> -> <1x5x3xf32, 15x3x1> return %2 : !migraphx.shaped<1x5x3xf32, 15x3x1> } } )__migraphx__"; migraphx::module m; auto arg0 = m.add_parameter("arg0", {migraphx::shape::float_type, {5, 4}}); auto arg1 = m.add_parameter("arg1", {migraphx::shape::float_type, {1, 4, 3}}); auto arg2 = m.add_parameter("arg2", {migraphx::shape::float_type, {1, 5, 3}}); auto unsqueeze = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), arg0); auto dot = m.add_instruction(migraphx::make_op("dot"), unsqueeze, arg1); auto add = m.add_instruction(migraphx::make_op("add"), dot, arg2); m.add_return({add}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(conv_int8_dequantize_quantize) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_quant_convolution_dequantizelinear_quantizelinear(%arg0: !migraphx.shaped<2x8x3x3xsi8, 72x9x3x1>, %arg1: !migraphx.shaped<1x8x4x4xsi8, 128x16x4x1>, %arg2: !migraphx.shaped<1x2x2x2xf32, 8x4x2x1>, %arg3: !migraphx.shaped<1x2x2x2xsi32, 8x4x2x1>) -> !migraphx.shaped<1x2x2x2xsi32, 8x4x2x1> attributes ${attrs} { %0 = migraphx.quant_convolution %arg1, %arg0 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x8x4x4xsi8, 128x16x4x1>, <2x8x3x3xsi8, 72x9x3x1> -> <1x2x2x2xsi32, 8x4x2x1> %1 = migraphx.dequantizelinear %0, %arg2, %arg3 : <1x2x2x2xsi32, 8x4x2x1>, <1x2x2x2xf32, 8x4x2x1>, !migraphx.shaped<1x2x2x2xsi32, 8x4x2x1> -> <1x2x2x2xf32, 8x4x2x1> %2 = migraphx.quantizelinear %1, %arg2, %arg3 : <1x2x2x2xf32, 8x4x2x1>, <1x2x2x2xf32, 8x4x2x1>, !migraphx.shaped<1x2x2x2xsi32, 8x4x2x1> -> <1x2x2x2xsi32, 8x4x2x1> return %2 : !migraphx.shaped<1x2x2x2xsi32, 8x4x2x1> } } )__migraphx__"; migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::int8_type, {1, 8, 4, 4}}); auto w = m.add_parameter("w", {migraphx::shape::int8_type, {2, 8, 3, 3}}); auto conv = m.add_instruction(migraphx::make_op("quant_convolution"), x, w); migraphx::shape ss{migraphx::shape::float_type, {1, 2, 2, 2}}; migraphx::shape sz{migraphx::shape::int32_type, {1, 2, 2, 2}}; auto input2 = m.add_parameter("x_scale", ss); auto input3 = m.add_parameter("x_zero_point", sz); auto dequant = m.add_instruction(migraphx::make_op("dequantizelinear"), conv, input2, input3); auto r = m.add_instruction(migraphx::make_op("quantizelinear"), dequant, input2, input3); m.add_return({r}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(dot_convert) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_dot_convert(%arg0: !migraphx.shaped<1x5x4xf32, 20x4x1>, %arg1: !migraphx.shaped<1x4x3xf32, 12x3x1>) -> !migraphx.shaped<1x5x3xf16, 15x3x1> attributes ${attrs} { %0 = migraphx.dot %arg0, %arg1 : <1x5x4xf32, 20x4x1>, <1x4x3xf32, 12x3x1> -> <1x5x3xf32, 15x3x1> %1 = migraphx.convert %0 {target_type = 1 : i64} : <1x5x3xf32, 15x3x1> to <1x5x3xf16, 15x3x1> return %1 : !migraphx.shaped<1x5x3xf16, 15x3x1> } } )__migraphx__"; migraphx::module m; auto arg0 = m.add_parameter("arg0", {migraphx::shape::float_type, {1, 5, 4}}); auto arg1 = m.add_parameter("arg1", {migraphx::shape::float_type, {1, 4, 3}}); auto dot = m.add_instruction(migraphx::make_op("dot"), arg0, arg1); auto trunc = m.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), dot); m.add_return({trunc}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(dot_where) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_dot_where(%arg0: !migraphx.shaped<1x5x4xf32, 20x4x1>, %arg1: !migraphx.shaped<1x4x3xf32, 12x3x1>, %arg2: !migraphx.shaped<1x5x3xsi8, 15x3x1>, %arg3: !migraphx.shaped<1x5x3xf32, 15x3x1>) -> !migraphx.shaped<1x5x3xf32, 15x3x1> attributes ${attrs} { %0 = migraphx.dot %arg0, %arg1 : <1x5x4xf32, 20x4x1>, <1x4x3xf32, 12x3x1> -> <1x5x3xf32, 15x3x1> %1 = migraphx.where %arg2, %0, %arg3 : <1x5x3xsi8, 15x3x1>, <1x5x3xf32, 15x3x1>, <1x5x3xf32, 15x3x1> -> <1x5x3xf32, 15x3x1> return %1 : !migraphx.shaped<1x5x3xf32, 15x3x1> } } )__migraphx__"; migraphx::module m; auto arg0 = m.add_parameter("arg0", {migraphx::shape::float_type, {1, 5, 4}}); auto arg1 = m.add_parameter("arg1", {migraphx::shape::float_type, {1, 4, 3}}); auto arg2 = m.add_parameter("arg2", {migraphx::shape::bool_type, {1, 5, 3}}); auto arg3 = m.add_parameter("arg3", {migraphx::shape::float_type, {1, 5, 3}}); auto dot = m.add_instruction(migraphx::make_op("dot"), arg0, arg1); auto where = m.add_instruction(migraphx::make_op("where"), arg2, dot, arg3); m.add_return({where}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(int4_unpack_ir) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_unpack_int4(%arg0: !migraphx.shaped<2x1xsi8, 1x1>) -> !migraphx.shaped<2x2xsi8, 2x1> attributes ${attrs} { %0 = migraphx.unpack %arg0 {axis = 1 : i64} : <2x1xsi8, 1x1> -> <2x2xsi8, 2x1> return %0 : !migraphx.shaped<2x2xsi8, 2x1> } } )__migraphx__"; migraphx::module m; auto arg0 = m.add_parameter("arg0", {migraphx::shape::int8_type, {2, 1}}); auto unpk = m.add_instruction(migraphx::make_op("unpack_int4"), arg0); m.add_return({unpk}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); } TEST_CASE(int4_unpack_conv) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_unpack_int4_quant_convolution(%arg0: !migraphx.shaped<2x8x2x1xsi8, 16x2x1x1>, %arg1: !migraphx.shaped<1x8x4x4xsi8, 128x16x4x1>) -> !migraphx.shaped<1x2x3x3xsi32, 18x9x3x1> attributes ${attrs} { %0 = migraphx.unpack %arg0 {axis = 3 : i64} : <2x8x2x1xsi8, 16x2x1x1> -> <2x8x2x2xsi8, 32x4x2x1> %1 = migraphx.quant_convolution %arg1, %0 {dilation = [1, 1], group = 1 : i64, padding = [0, 0, 0, 0], padding_mode = 0 : i64, stride = [1, 1]} : <1x8x4x4xsi8, 128x16x4x1>, <2x8x2x2xsi8, 32x4x2x1> -> <1x2x3x3xsi32, 18x9x3x1> return %1 : !migraphx.shaped<1x2x3x3xsi32, 18x9x3x1> } } )__migraphx__"; migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::int8_type, {1, 8, 4, 4}}); auto pk_w = m.add_parameter("w", {migraphx::shape::int8_type, {2, 8, 2, 1}}); auto w = m.add_instruction(migraphx::make_op("unpack_int4"), pk_w); auto conv = m.add_instruction(migraphx::make_op("quant_convolution"), x, w); // w: {2,8,2,2} m.add_return({conv}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(int4_unpack_dequantizelinear) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_unsqueeze_reshape_slice_unsqueeze_reshape_slice_unpack_int4_dequantizelinear_dot(%arg0: !migraphx.shaped<2x3x5xf32, 15x5x1>, %arg1: !migraphx.shaped<2x5x1xsi8, 5x1x1>, %arg2: !migraphx.shaped<2x2x2xf32, 4x2x1>, %arg3: !migraphx.shaped<2x2x2xsi8, 4x2x1>) -> !migraphx.shaped<2x3x2xf32, 6x2x1> attributes ${attrs} { %0 = migraphx.reshape %arg2 {dims = [2, 2, 1, 2]} : <2x2x2xf32, 4x2x1> -> <2x2x1x2xf32, 4x2x2x1> %1 = migraphx.multibroadcast %0 {out_dyn_dims = [], out_lens = [2, 2, 3, 2]} : <2x2x1x2xf32, 4x2x2x1> -> <2x2x3x2xf32, 4x2x0x1> %2 = migraphx.reshape %1 {dims = [2, 6, 2]} : <2x2x3x2xf32, 4x2x0x1> -> <2x6x2xf32, 12x2x1> %3 = migraphx.slice %2 {axes = [1], ends = [5], starts = [0]} : <2x6x2xf32, 12x2x1> -> <2x5x2xf32, 12x2x1> %4 = migraphx.reshape %arg3 {dims = [2, 2, 1, 2]} : <2x2x2xsi8, 4x2x1> -> <2x2x1x2xsi8, 4x2x2x1> %5 = migraphx.multibroadcast %4 {out_dyn_dims = [], out_lens = [2, 2, 3, 2]} : <2x2x1x2xsi8, 4x2x2x1> -> <2x2x3x2xsi8, 4x2x0x1> %6 = migraphx.reshape %5 {dims = [2, 6, 2]} : <2x2x3x2xsi8, 4x2x0x1> -> <2x6x2xsi8, 12x2x1> %7 = migraphx.slice %6 {axes = [1], ends = [5], starts = [0]} : <2x6x2xsi8, 12x2x1> -> <2x5x2xsi8, 12x2x1> %8 = migraphx.unpack %arg1 {axis = 2 : i64} : <2x5x1xsi8, 5x1x1> -> <2x5x2xsi8, 10x2x1> %9 = migraphx.dequantizelinear %8, %3, %7 : <2x5x2xsi8, 10x2x1>, <2x5x2xf32, 12x2x1>, !migraphx.shaped<2x5x2xsi8, 12x2x1> -> <2x5x2xf32, 10x2x1> %10 = migraphx.dot %arg0, %9 : <2x3x5xf32, 15x5x1>, <2x5x2xf32, 10x2x1> -> <2x3x2xf32, 6x2x1> return %10 : !migraphx.shaped<2x3x2xf32, 6x2x1> } } )__migraphx__"; migraphx::module m; auto x0 = m.add_parameter("x0", migraphx::shape{migraphx::shape::float_type, {2, 3, 5}}); auto x1 = m.add_parameter("x1", migraphx::shape{migraphx::shape::int8_type, {2, 5, 1}}); auto x2 = m.add_parameter("x2", migraphx::shape{migraphx::shape::float_type, {2, 2, 2}}); auto x3 = m.add_parameter("x3", migraphx::shape{migraphx::shape::int8_type, {2, 2, 2}}); auto unsqueeze1 = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), x2); auto broadcast1 = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze1); auto reshape1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = m.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto unsqueeze2 = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), x3); auto broadcast2 = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze2); auto reshape2 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = m.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto unpack = m.add_instruction(migraphx::make_op("unpack_int4"), x1); auto dq = m.add_instruction(migraphx::make_op("dequantizelinear"), unpack, scale, zp); auto dot = m.add_instruction(migraphx::make_op("dot"), x0, dq); m.add_return({dot}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } TEST_CASE(uint4_unpack_dequantizelinear) { std::string mlir_output = R"__migraphx__( module { func.func @mlir_unsqueeze_reshape_slice_unsqueeze_reshape_slice_unpack_int4_dequantizelinear_dot(%arg0: !migraphx.shaped<2x3x5xf32, 15x5x1>, %arg1: !migraphx.shaped<2x5x1xui8, 5x1x1>, %arg2: !migraphx.shaped<2x2x2xf32, 4x2x1>, %arg3: !migraphx.shaped<2x2x2xui8, 4x2x1>) -> !migraphx.shaped<2x3x2xf32, 6x2x1> attributes ${attrs} { %0 = migraphx.reshape %arg2 {dims = [2, 2, 1, 2]} : <2x2x2xf32, 4x2x1> -> <2x2x1x2xf32, 4x2x2x1> %1 = migraphx.multibroadcast %0 {out_dyn_dims = [], out_lens = [2, 2, 3, 2]} : <2x2x1x2xf32, 4x2x2x1> -> <2x2x3x2xf32, 4x2x0x1> %2 = migraphx.reshape %1 {dims = [2, 6, 2]} : <2x2x3x2xf32, 4x2x0x1> -> <2x6x2xf32, 12x2x1> %3 = migraphx.slice %2 {axes = [1], ends = [5], starts = [0]} : <2x6x2xf32, 12x2x1> -> <2x5x2xf32, 12x2x1> %4 = migraphx.reshape %arg3 {dims = [2, 2, 1, 2]} : <2x2x2xui8, 4x2x1> -> <2x2x1x2xui8, 4x2x2x1> %5 = migraphx.multibroadcast %4 {out_dyn_dims = [], out_lens = [2, 2, 3, 2]} : <2x2x1x2xui8, 4x2x2x1> -> <2x2x3x2xui8, 4x2x0x1> %6 = migraphx.reshape %5 {dims = [2, 6, 2]} : <2x2x3x2xui8, 4x2x0x1> -> <2x6x2xui8, 12x2x1> %7 = migraphx.slice %6 {axes = [1], ends = [5], starts = [0]} : <2x6x2xui8, 12x2x1> -> <2x5x2xui8, 12x2x1> %8 = migraphx.unpack %arg1 {axis = 2 : i64} : <2x5x1xui8, 5x1x1> -> <2x5x2xui8, 10x2x1> %9 = migraphx.dequantizelinear %8, %3, %7 : <2x5x2xui8, 10x2x1>, <2x5x2xf32, 12x2x1>, !migraphx.shaped<2x5x2xui8, 12x2x1> -> <2x5x2xf32, 10x2x1> %10 = migraphx.dot %arg0, %9 : <2x3x5xf32, 15x5x1>, <2x5x2xf32, 10x2x1> -> <2x3x2xf32, 6x2x1> return %10 : !migraphx.shaped<2x3x2xf32, 6x2x1> } } )__migraphx__"; migraphx::module m; auto x0 = m.add_parameter("x0", migraphx::shape{migraphx::shape::float_type, {2, 3, 5}}); auto x1 = m.add_parameter("x1", migraphx::shape{migraphx::shape::uint8_type, {2, 5, 1}}); auto x2 = m.add_parameter("x2", migraphx::shape{migraphx::shape::float_type, {2, 2, 2}}); auto x3 = m.add_parameter("x3", migraphx::shape{migraphx::shape::uint8_type, {2, 2, 2}}); auto unsqueeze1 = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), x2); auto broadcast1 = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze1); auto reshape1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = m.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto unsqueeze2 = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), x3); auto broadcast2 = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze2); auto reshape2 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = m.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto unpack = m.add_instruction(migraphx::make_op("unpack_int4"), x1); auto dq = m.add_instruction(migraphx::make_op("dequantizelinear"), unpack, scale, zp); auto dot = m.add_instruction(migraphx::make_op("dot"), x0, dq); m.add_return({dot}); auto s = migraphx::gpu::dump_mlir(m); // Skip test if MLIR is not enabled if(s.empty()) return; auto mlir_output_with_attrs = migraphx::interpolate_string(mlir_output, {{"attrs", get_attrs()}}); CHECK(encode(s) == encode(mlir_output_with_attrs)); EXPECT(verify_mlir(m)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/pack_args.cpp000066400000000000000000000040651510465702400207550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include template static std::size_t packed_sizes() { return sizeof(T); } template static std::size_t packed_sizes() { return sizeof(T) + packed_sizes(); } template static std::size_t sizes() { return migraphx::gpu::pack_args({Ts{}...}).size(); } template static std::size_t padding() { EXPECT(sizes() >= packed_sizes()); return sizes() - packed_sizes(); } struct float_struct { float x, y; }; TEST_CASE(alignment_padding) { EXPECT(padding() == 0); EXPECT(padding() == 0); EXPECT(padding() == 2); EXPECT(padding() == 2); EXPECT(padding() == 1); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/prefuse_ops.cpp000066400000000000000000000120671510465702400213560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include struct pre_gemm_softmax_gemm : migraphx::gpu::gemm_softmax_gemm { std::string name() const { return "gpu::pre_gemm_softmax_gemm"; } }; static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::gpu::prefuse_ops{true}, migraphx::dead_code_elimination{}}); } TEST_CASE(find_gemm_softmax_gemm) { migraphx::shape s1{migraphx::shape::float_type, {8, 16, 32}}; migraphx::shape s2{migraphx::shape::float_type, {8, 32, 16}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto z = m1.add_parameter("z", s1); auto scale = m1.add_literal(2.0f); auto dot1 = m1.add_instruction(migraphx::make_op("dot"), x, y); auto scale_mb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot1->get_shape().lens()}}), scale); auto mul = m1.add_instruction(migraphx::make_op("mul"), dot1, scale_mb); auto sm = m1.add_instruction(migraphx::make_op("softmax", {{"axis", 2}}), mul); m1.add_instruction(migraphx::make_op("dot"), sm, z); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto z = m2.add_parameter("z", s1); m2.add_instruction(pre_gemm_softmax_gemm{migraphx::make_op("dot"), 2}, x, y, z); } EXPECT(m1 == m2); } TEST_CASE(find_gemm_softmax_gemm_multi_scale) { migraphx::shape s1{migraphx::shape::float_type, {8, 16, 32}}; migraphx::shape s2{migraphx::shape::float_type, {8, 32, 16}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto z = m1.add_parameter("z", s1); auto scale = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {16}}, 10)); auto dot1 = m1.add_instruction(migraphx::make_op("dot"), x, y); auto scale_mb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot1->get_shape().lens()}}), scale); auto mul = m1.add_instruction(migraphx::make_op("mul"), dot1, scale_mb); auto sm = m1.add_instruction(migraphx::make_op("softmax", {{"axis", 2}}), mul); m1.add_instruction(migraphx::make_op("dot"), sm, z); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto z = m2.add_parameter("z", s1); m2.add_instruction(pre_gemm_softmax_gemm{migraphx::make_op("dot"), 1}, x, y, z); } EXPECT(m1 == m2); } TEST_CASE(find_gemm_softmax_gemm_no_scale) { migraphx::shape s1{migraphx::shape::float_type, {8, 16, 32}}; migraphx::shape s2{migraphx::shape::float_type, {8, 32, 16}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto z = m1.add_parameter("z", s1); auto dot1 = m1.add_instruction(migraphx::make_op("dot"), x, y); auto sm = m1.add_instruction(migraphx::make_op("softmax", {{"axis", 2}}), dot1); m1.add_instruction(migraphx::make_op("dot"), sm, z); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto z = m2.add_parameter("z", s1); m2.add_instruction(pre_gemm_softmax_gemm{migraphx::make_op("dot"), 1}, x, y, z); } EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/quantization.cpp000066400000000000000000000176061510465702400215560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include TEST_CASE(gpu_target_copy) { migraphx::target gpu_t = migraphx::make_target("gpu"); migraphx::shape s{migraphx::shape::int8_type, {2, 3, 4, 5}}; auto ref_arg_orig = migraphx::generate_argument(s, 0x123456L); auto gpu_arg = gpu_t.copy_to(ref_arg_orig); auto ref_arg_final = gpu_t.copy_from(gpu_arg); std::vector val_orig; ref_arg_orig.visit([&](auto v) { val_orig.assign(v.begin(), v.end()); }); std::vector val_final; ref_arg_final.visit([&](auto v) { val_final.assign(v.begin(), v.end()); }); EXPECT(migraphx::verify::verify_rms_range(val_orig, val_final)); } TEST_CASE(int8_quantization) { auto run_prog = [](migraphx::program p, const migraphx::target& t, migraphx::parameter_map& m_in, std::vector& res) { std::vector cali_data; cali_data.push_back(m_in); migraphx::quantize_int8(p, t, cali_data); p.compile(t); migraphx::parameter_map m; for(auto&& x : p.get_parameter_shapes()) { if(m_in.count(x.first) > 0) { m[x.first] = t.copy_to(m_in[x.first]); } else { m[x.first] = t.allocate(x.second); } } auto result = t.copy_from(p.eval(m).back()); result.visit([&](auto v) { res.assign(v.begin(), v.end()); }); }; auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {5, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; migraphx::shape sc{migraphx::shape::float_type, {5, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); mm->add_instruction(migraphx::make_op("dot"), pa, pb); return p; }; { auto p = create_program(); migraphx::parameter_map m; migraphx::shape sa{migraphx::shape::float_type, {5, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; migraphx::shape sc{migraphx::shape::float_type, {5, 8}}; m["a"] = migraphx::generate_argument(sa); m["b"] = migraphx::generate_argument(sb); std::vector ref_result; migraphx::target ref_t = migraphx::make_target("ref"); run_prog(p, ref_t, m, ref_result); std::vector gpu_result; migraphx::target gpu_t = migraphx::make_target("gpu"); run_prog(p, gpu_t, m, gpu_result); // Note: the tolerance for mlir_enabled result is temporarily bumped // higher because the lowering pipeline between mlir fallback and // regular non-mlir pipeline diverged. MLIR fallback uses the // rewrite_quantization at the very end of the pipeline, whereas // the regular pipeline uses the rewrite_quantization in the much // earlier stage. if(migraphx::gpu::mlir_enabled()) EXPECT(migraphx::verify::verify_range_with_tolerance( gpu_result, migraphx::verify::expected{ref_result}, migraphx::verify::tolerance{0.01})); else EXPECT(migraphx::verify::verify_rms_range(gpu_result, ref_result)); } } TEST_CASE(fp8_quantization) { if(migraphx::gpu::gfx_has_fp8fnuz_intrinsics() or migraphx::gpu::gfx_has_fp8ocp_intrinsics()) { auto run_prog = [](migraphx::program p, const migraphx::target& t, migraphx::parameter_map& m_in, std::vector& res) { std::vector cali_data; cali_data.push_back(m_in); migraphx::quantize_fp8(p, t, cali_data); p.compile(t); migraphx::parameter_map m; for(auto&& x : p.get_parameter_shapes()) { if(m_in.count(x.first) > 0) { m[x.first] = t.copy_to(m_in[x.first]); } else { m[x.first] = t.allocate(x.second); } } auto result = t.copy_from(p.eval(m).back()); result.visit([&](auto v) { res.assign(v.begin(), v.end()); }); }; auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {5, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); mm->add_instruction(migraphx::make_op("dot"), pa, pb); return p; }; { auto p = create_program(); migraphx::parameter_map m; migraphx::shape sa{migraphx::shape::float_type, {5, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; m["a"] = migraphx::generate_argument(sa); m["b"] = migraphx::generate_argument(sb); std::vector ref_result; migraphx::target ref_t = migraphx::make_target("ref"); run_prog(p, ref_t, m, ref_result); std::vector gpu_result; migraphx::target gpu_t = migraphx::make_target("gpu"); run_prog(p, gpu_t, m, gpu_result); // Note: the tolerance for mlir_enabled result is temporarily bumped // higher because the lowering pipeline between mlir fallback and // regular non-mlir pipeline diverged. MLIR fallback uses the // rewrite_quantization at the very end of the pipeline, whereas // the regular pipeline uses the rewrite_quantization in the much // earlier stage. if(migraphx::gpu::mlir_enabled()) EXPECT(migraphx::verify::verify_range_with_tolerance( gpu_result, migraphx::verify::expected{ref_result}, migraphx::verify::tolerance{0.01})); else EXPECT(migraphx::verify::verify_rms_range(gpu_result, ref_result)); } } else { EXPECT(true); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/gpu/stream_sync.cpp000066400000000000000000000114021510465702400213430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test.hpp" using hip_stream_ptr = MIGRAPHX_MANAGE_PTR(hipStream_t, hipStreamDestroy); constexpr uint32_t stream_sync_test_val = 1337; // NOLINTNEXTLINE const std::string compare_numbers = R"__migraphx__( #ifndef __HIPCC_RTC__ #include #endif extern "C" { __global__ void compare(float* data) { int i = threadIdx.x + blockDim.x * blockIdx.x; if (data[i] != 1337) { abort(); } } } int main() {} )__migraphx__"; static migraphx::src_file make_src_file(const std::string& name, const std::string& content) { return {name, content}; } static hip_stream_ptr get_stream() { hipStream_t stream; auto status = hipStreamCreate(&stream); if(status != hipSuccess) { MIGRAPHX_THROW("Failed to get stream"); } return hip_stream_ptr{stream}; } TEST_CASE(test_stream_sync_compare_kernel) { auto binaries = migraphx::gpu::compile_hip_src( {make_src_file("check_stuff.cpp", compare_numbers)}, {}, migraphx::gpu::get_device_name()); EXPECT(binaries.size() == 1); migraphx::gpu::kernel k1{binaries.front(), "compare"}; auto input = migraphx::fill_argument({migraphx::shape::float_type, {128}}, stream_sync_test_val); auto ginput = migraphx::gpu::to_gpu(input); hip_stream_ptr pstream = get_stream(); k1.launch(pstream.get(), input.get_shape().elements(), 1024)(ginput.cast()); auto output = migraphx::gpu::from_gpu(ginput); EXPECT(output == input); } TEST_CASE(test_stream_sync) { auto binaries = migraphx::gpu::compile_hip_src( {make_src_file("check_stuff.cpp", compare_numbers)}, {}, migraphx::gpu::get_device_name()); EXPECT(binaries.size() == 1); migraphx::gpu::kernel k1{binaries.front(), "compare"}; const unsigned int m = 128; const unsigned int k = 8192; // Setup empty GPU memory buffer migraphx::shape input_shape{migraphx::shape::float_type, {m, k}}; migraphx::shape output_shape{migraphx::shape::float_type, {m, m}}; auto input = migraphx::fill_argument(input_shape, 0); auto ginput = migraphx::gpu::to_gpu(input); auto output = migraphx::fill_argument(output_shape, 0); auto goutput = migraphx::gpu::to_gpu(output); hip_stream_ptr pstream = get_stream(); migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {m, k}}); auto y = mm->add_literal( migraphx::generate_literal(migraphx::shape{migraphx::shape::float_type, {k, m}})); std::vector data(m * m, stream_sync_test_val); auto test_val = mm->add_literal(output_shape, data); auto mult_out = mm->add_instruction(migraphx::make_op("dot"), x, y); mm->add_instruction(migraphx::make_op("add"), mult_out, test_val); p.compile(migraphx::make_target("gpu")); // Run network and then verify with kernel auto args = p.eval({{"x", ginput}, {"main:#output_0", goutput}}, {pstream.get(), true}); k1.launch(pstream.get(), m * m, 1024)(goutput.cast()); output = migraphx::gpu::from_gpu(goutput); EXPECT(output != input); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/half.cpp000066400000000000000000001205161510465702400171420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" #include #include #include template static bool bit_equal(const T& x, const U& y) { static_assert(sizeof(T) == sizeof(U)); using type = std::array; return migraphx::bit_cast(x) == migraphx::bit_cast(y); } TEST_CASE(check_numeric_limits) { CHECK(bit_equal(std::numeric_limits::min(), uint16_t{0x0400})); CHECK(bit_equal(std::numeric_limits::lowest(), uint16_t{0xfbff})); CHECK(bit_equal(std::numeric_limits::max(), uint16_t{0x7bff})); CHECK(bit_equal(std::numeric_limits::epsilon(), uint16_t{0x1400})); CHECK(bit_equal(std::numeric_limits::denorm_min(), uint16_t{0x0001})); CHECK(bit_equal(std::numeric_limits::infinity(), uint16_t{0x7c00})); CHECK(bit_equal(std::numeric_limits::quiet_NaN(), uint16_t{0x7e00})); CHECK(bit_equal(std::numeric_limits::signaling_NaN(), uint16_t{0x7d00})); } const static std::map& half_lut() // NOLINT(readability-function-size) { static const std::map result = { {0x0000, 0}, {0x0058, 0.0000052452087402}, {0x0079, 0.0000072121620178}, {0x0097, 0.0000090003013611}, {0x009e, 0.0000094175338745}, {0x0125, 0.0000174641609192}, {0x0167, 0.0000213980674744}, {0x0196, 0.0000241994857788}, {0x01c4, 0.0000269412994385}, {0x01c8, 0.0000271797180176}, {0x0236, 0.0000337362289429}, {0x029f, 0.0000399947166443}, {0x02bf, 0.0000419020652771}, {0x02d6, 0.0000432729721069}, {0x03a6, 0.0000556707382202}, {0x03b7, 0.0000566840171814}, {0x03d4, 0.0000584125518799}, {0x03d8, 0.000058650970459}, {0x03ed, 0.0000599026679993}, {0x0427, 0.0000633597373962}, {0x0430, 0.0000638961791992}, {0x0435, 0.0000641942024231}, {0x0454, 0.0000660419464111}, {0x047a, 0.0000683069229126}, {0x04b6, 0.0000718832015991}, {0x056a, 0.0000826120376587}, {0x056f, 0.0000829100608826}, {0x0584, 0.0000841617584229}, {0x05a1, 0.0000858902931213}, {0x05a4, 0.0000860691070557}, {0x05b8, 0.0000872611999512}, {0x05bc, 0.0000874996185303}, {0x0635, 0.0000947117805481}, {0x0641, 0.0000954270362854}, {0x0686, 0.0000995397567749}, {0x0694, 0.0001003742218018}, {0x06db, 0.0001046061515808}, {0x0725, 0.0001090168952942}, {0x0777, 0.0001139044761658}, {0x07b2, 0.0001174211502075}, {0x0812, 0.0001242160797119}, {0x082e, 0.0001275539398193}, {0x0859, 0.00013267993927}, {0x0895, 0.0001398324966431}, {0x08af, 0.0001429319381714}, {0x08fc, 0.0001521110534668}, {0x092e, 0.0001580715179443}, {0x0971, 0.0001660585403442}, {0x0991, 0.0001698732376099}, {0x09ca, 0.0001766681671143}, {0x0a63, 0.0001949071884155}, {0x0a8e, 0.0002000331878662}, {0x0a93, 0.000200629234314}, {0x0b2a, 0.0002186298370361}, {0x0b3a, 0.0002205371856689}, {0x0b3c, 0.000220775604248}, {0x0b4e, 0.00022292137146}, {0x0bae, 0.0002343654632568}, {0x0bff, 0.0002440214157104}, {0x0c08, 0.0002460479736328}, {0x0c56, 0.0002646446228027}, {0x0c61, 0.0002672672271729}, {0x0c70, 0.0002708435058594}, {0x0c7c, 0.0002737045288086}, {0x0cd8, 0.0002956390380859}, {0x0cdd, 0.0002968311309814}, {0x0d05, 0.0003063678741455}, {0x0d61, 0.0003283023834229}, {0x0d85, 0.0003368854522705}, {0x0d8c, 0.0003385543823242}, {0x0d90, 0.0003395080566406}, {0x0d9e, 0.000342845916748}, {0x0da5, 0.0003445148468018}, {0x0dda, 0.0003571510314941}, {0x0dde, 0.0003581047058105}, {0x0df6, 0.000363826751709}, {0x0eec, 0.000422477722168}, {0x0f1c, 0.0004339218139648}, {0x0f99, 0.0004637241363525}, {0x0fac, 0.0004682540893555}, {0x0fb0, 0.0004692077636719}, {0x0ff5, 0.0004856586456299}, {0x107f, 0.0005488395690918}, {0x1096, 0.0005598068237305}, {0x10c8, 0.0005836486816406}, {0x10e9, 0.0005993843078613}, {0x110a, 0.000615119934082}, {0x118a, 0.000676155090332}, {0x11b5, 0.0006966590881348}, {0x1293, 0.0008025169372559}, {0x133f, 0.0008845329284668}, {0x1342, 0.0008859634399414}, {0x1372, 0.0009088516235352}, {0x13cf, 0.000953197479248}, {0x140c, 0.0009880065917969}, {0x1437, 0.0010290145874023}, {0x14a3, 0.0011320114135742}, {0x14a6, 0.0011348724365234}, {0x14b2, 0.0011463165283203}, {0x14ba, 0.0011539459228516}, {0x14d9, 0.0011835098266602}, {0x14da, 0.0011844635009766}, {0x14e7, 0.0011968612670898}, {0x14fe, 0.0012187957763672}, {0x1521, 0.0012521743774414}, {0x153d, 0.0012788772583008}, {0x15ad, 0.0013856887817383}, {0x15fd, 0.0014619827270508}, {0x1649, 0.0015344619750977}, {0x1658, 0.0015487670898438}, {0x168a, 0.0015964508056641}, {0x169d, 0.0016145706176758}, {0x16b3, 0.0016355514526367}, {0x16c9, 0.0016565322875977}, {0x16d1, 0.0016641616821289}, {0x16e0, 0.001678466796875}, {0x170a, 0.0017185211181641}, {0x176d, 0.0018129348754883}, {0x185b, 0.0021266937255859}, {0x185e, 0.0021324157714844}, {0x187e, 0.0021934509277344}, {0x18ca, 0.0023384094238281}, {0x18e9, 0.0023975372314453}, {0x1901, 0.0024433135986328}, {0x191e, 0.0024986267089844}, {0x1963, 0.0026302337646484}, {0x199f, 0.0027446746826172}, {0x19b2, 0.0027809143066406}, {0x19d4, 0.0028457641601562}, {0x1a31, 0.0030231475830078}, {0x1a4a, 0.0030708312988281}, {0x1a7a, 0.0031623840332031}, {0x1ace, 0.0033226013183594}, {0x1b03, 0.0034236907958984}, {0x1b22, 0.0034828186035156}, {0x1d49, 0.0051612854003906}, {0x1d5a, 0.0052261352539062}, {0x1d6c, 0.0052947998046875}, {0x1e02, 0.0058670043945312}, {0x1e19, 0.0059547424316406}, {0x1e4c, 0.0061492919921875}, {0x1eb3, 0.0065422058105469}, {0x1f32, 0.0070266723632812}, {0x1f36, 0.0070419311523438}, {0x1f41, 0.0070838928222656}, {0x1f7a, 0.0073013305664062}, {0x1f8d, 0.0073738098144531}, {0x200b, 0.0078964233398438}, {0x205f, 0.0085372924804688}, {0x2060, 0.008544921875}, {0x2067, 0.0085983276367188}, {0x20e2, 0.0095367431640625}, {0x2164, 0.010528564453125}, {0x22a4, 0.012969970703125}, {0x22b4, 0.013092041015625}, {0x22f2, 0.0135650634765625}, {0x230c, 0.013763427734375}, {0x2314, 0.013824462890625}, {0x2341, 0.0141677856445312}, {0x2356, 0.0143280029296875}, {0x236e, 0.0145111083984375}, {0x2371, 0.0145339965820312}, {0x23cd, 0.0152359008789062}, {0x2405, 0.0157012939453125}, {0x24a2, 0.018096923828125}, {0x24ba, 0.018463134765625}, {0x24e7, 0.0191497802734375}, {0x266c, 0.02508544921875}, {0x26a2, 0.025909423828125}, {0x26cc, 0.02655029296875}, {0x26f0, 0.027099609375}, {0x271e, 0.027801513671875}, {0x2798, 0.0296630859375}, {0x287d, 0.035064697265625}, {0x28a2, 0.03619384765625}, {0x28ca, 0.03741455078125}, {0x2933, 0.040618896484375}, {0x298d, 0.043365478515625}, {0x299e, 0.04388427734375}, {0x29c0, 0.044921875}, {0x29c2, 0.04498291015625}, {0x29cf, 0.045379638671875}, {0x29fa, 0.04669189453125}, {0x2a06, 0.04705810546875}, {0x2aa5, 0.051910400390625}, {0x2bcb, 0.060882568359375}, {0x2c18, 0.06396484375}, {0x2c65, 0.06866455078125}, {0x2c66, 0.0687255859375}, {0x2c93, 0.07147216796875}, {0x2d24, 0.080322265625}, {0x2d35, 0.08135986328125}, {0x2d4c, 0.082763671875}, {0x2db7, 0.08929443359375}, {0x2dec, 0.092529296875}, {0x2e31, 0.09674072265625}, {0x2ec9, 0.10601806640625}, {0x2f85, 0.11749267578125}, {0x2f94, 0.118408203125}, {0x302b, 0.1302490234375}, {0x3094, 0.14306640625}, {0x3096, 0.143310546875}, {0x30ae, 0.146240234375}, {0x30b9, 0.1475830078125}, {0x310c, 0.15771484375}, {0x31bd, 0.1793212890625}, {0x3213, 0.1898193359375}, {0x325b, 0.1986083984375}, {0x32aa, 0.208251953125}, {0x32c0, 0.2109375}, {0x32d7, 0.2137451171875}, {0x3391, 0.2364501953125}, {0x340d, 0.253173828125}, {0x343d, 0.264892578125}, {0x3566, 0.33740234375}, {0x35e6, 0.36865234375}, {0x35f4, 0.3720703125}, {0x363b, 0.389404296875}, {0x363e, 0.39013671875}, {0x3650, 0.39453125}, {0x3698, 0.412109375}, {0x36e7, 0.431396484375}, {0x36fe, 0.43701171875}, {0x374a, 0.45556640625}, {0x3760, 0.4609375}, {0x3761, 0.461181640625}, {0x379e, 0.47607421875}, {0x37cc, 0.4873046875}, {0x37fd, 0.499267578125}, {0x3828, 0.51953125}, {0x3841, 0.53173828125}, {0x3877, 0.55810546875}, {0x38a4, 0.580078125}, {0x38d3, 0.60302734375}, {0x39b2, 0.7119140625}, {0x3a60, 0.796875}, {0x3aa3, 0.82958984375}, {0x3aa6, 0.8310546875}, {0x3ac9, 0.84814453125}, {0x3acf, 0.85107421875}, {0x3b14, 0.884765625}, {0x3b42, 0.9072265625}, {0x3b5c, 0.919921875}, {0x3bde, 0.9833984375}, {0x3c67, 1.1005859375}, {0x3cb5, 1.1767578125}, {0x3cca, 1.197265625}, {0x3cdd, 1.2158203125}, {0x3cfc, 1.24609375}, {0x3d1f, 1.2802734375}, {0x3e0c, 1.51171875}, {0x3e1c, 1.52734375}, {0x3e5b, 1.5888671875}, {0x3e7f, 1.6240234375}, {0x3eae, 1.669921875}, {0x3efe, 1.748046875}, {0x3f3e, 1.810546875}, {0x3f9d, 1.9033203125}, {0x400a, 2.01953125}, {0x4070, 2.21875}, {0x40a0, 2.3125}, {0x40ce, 2.40234375}, {0x40e6, 2.44921875}, {0x410e, 2.52734375}, {0x4129, 2.580078125}, {0x4144, 2.6328125}, {0x41a4, 2.8203125}, {0x41f3, 2.974609375}, {0x42f1, 3.470703125}, {0x438f, 3.779296875}, {0x43b0, 3.84375}, {0x43c3, 3.880859375}, {0x43de, 3.93359375}, {0x4483, 4.51171875}, {0x44f8, 4.96875}, {0x4505, 5.01953125}, {0x45dd, 5.86328125}, {0x45f3, 5.94921875}, {0x460e, 6.0546875}, {0x46ce, 6.8046875}, {0x4704, 7.015625}, {0x471a, 7.1015625}, {0x475e, 7.3671875}, {0x4761, 7.37890625}, {0x479f, 7.62109375}, {0x47ca, 7.7890625}, {0x47db, 7.85546875}, {0x47fc, 7.984375}, {0x481e, 8.234375}, {0x4839, 8.4453125}, {0x483d, 8.4765625}, {0x48ac, 9.34375}, {0x48da, 9.703125}, {0x4919, 10.1953125}, {0x4950, 10.625}, {0x4987, 11.0546875}, {0x49bb, 11.4609375}, {0x4a14, 12.15625}, {0x4a92, 13.140625}, {0x4b25, 14.2890625}, {0x4b81, 15.0078125}, {0x4b99, 15.1953125}, {0x4bbe, 15.484375}, {0x4bf8, 15.9375}, {0x4c1f, 16.484375}, {0x4c49, 17.140625}, {0x4d21, 20.515625}, {0x4d4a, 21.15625}, {0x4d51, 21.265625}, {0x4de2, 23.53125}, {0x4e05, 24.078125}, {0x4ea3, 26.546875}, {0x4eb0, 26.75}, {0x4f0e, 28.21875}, {0x4f4a, 29.15625}, {0x4f6b, 29.671875}, {0x4fa6, 30.59375}, {0x4fae, 30.71875}, {0x4ff6, 31.84375}, {0x503c, 33.875}, {0x50e4, 39.125}, {0x514e, 42.4375}, {0x516b, 43.34375}, {0x51d3, 46.59375}, {0x5213, 48.59375}, {0x526e, 51.4375}, {0x52a6, 53.1875}, {0x52b4, 53.625}, {0x52b6, 53.6875}, {0x52bc, 53.875}, {0x5300, 56}, {0x5389, 60.28125}, {0x5406, 64.375}, {0x5498, 73.5}, {0x54bd, 75.8125}, {0x54cf, 76.9375}, {0x5502, 80.125}, {0x558e, 88.875}, {0x5597, 89.4375}, {0x55eb, 94.6875}, {0x55f6, 95.375}, {0x5629, 98.5625}, {0x562b, 98.6875}, {0x5635, 99.3125}, {0x564e, 100.875}, {0x5671, 103.0625}, {0x5681, 104.0625}, {0x56d1, 109.0625}, {0x571c, 113.75}, {0x5756, 117.375}, {0x5790, 121}, {0x57fd, 127.8125}, {0x582d, 133.625}, {0x5869, 141.125}, {0x58ab, 149.375}, {0x58ad, 149.625}, {0x58c9, 153.125}, {0x58f7, 158.875}, {0x5904, 160.5}, {0x59c2, 184.25}, {0x59e6, 188.75}, {0x5a88, 209}, {0x5ada, 219.25}, {0x5aef, 221.875}, {0x5af5, 222.625}, {0x5b7f, 239.875}, {0x5ba4, 244.5}, {0x5c08, 258}, {0x5cbf, 303.75}, {0x5d4d, 339.25}, {0x5dc2, 368.5}, {0x5dc4, 369}, {0x5e31, 396.25}, {0x5e38, 398}, {0x5e7c, 415}, {0x5e8d, 419.25}, {0x5ead, 427.25}, {0x5eb4, 429}, {0x5ec0, 432}, {0x5eef, 443.75}, {0x5f04, 449}, {0x5f41, 464.25}, {0x5f58, 470}, {0x5f61, 472.25}, {0x5f77, 477.75}, {0x5f7b, 478.75}, {0x6029, 532.5}, {0x6046, 547}, {0x6055, 554.5}, {0x60a8, 596}, {0x60d7, 619.5}, {0x6139, 668.5}, {0x6167, 691.5}, {0x61b5, 730.5}, {0x61c0, 736}, {0x61e6, 755}, {0x625b, 813.5}, {0x62c4, 866}, {0x62fd, 894.5}, {0x62fe, 895}, {0x6332, 921}, {0x636a, 949}, {0x6374, 954}, {0x6376, 955}, {0x639f, 975.5}, {0x63d6, 1003}, {0x6417, 1047}, {0x642e, 1070}, {0x6431, 1073}, {0x644f, 1103}, {0x6459, 1113}, {0x645b, 1115}, {0x6480, 1152}, {0x648d, 1165}, {0x649f, 1183}, {0x64bb, 1211}, {0x6516, 1302}, {0x6571, 1393}, {0x6585, 1413}, {0x65aa, 1450}, {0x660c, 1548}, {0x6694, 1684}, {0x66d0, 1744}, {0x6721, 1825}, {0x672d, 1837}, {0x6734, 1844}, {0x6766, 1894}, {0x6773, 1907}, {0x677d, 1917}, {0x679a, 1946}, {0x690f, 2590}, {0x6934, 2664}, {0x6955, 2730}, {0x697d, 2810}, {0x698e, 2844}, {0x6a3a, 3188}, {0x6a63, 3270}, {0x6a67, 3278}, {0x6a7c, 3320}, {0x6a87, 3342}, {0x6b07, 3598}, {0x6b11, 3618}, {0x6b36, 3692}, {0x6b3c, 3704}, {0x6b75, 3818}, {0x6b88, 3856}, {0x6be6, 4044}, {0x6bee, 4060}, {0x6c62, 4488}, {0x6c8b, 4652}, {0x6d30, 5312}, {0x6d48, 5408}, {0x6ddd, 6004}, {0x6de9, 6052}, {0x6e39, 6372}, {0x6e7e, 6648}, {0x6ea5, 6804}, {0x6ec5, 6932}, {0x6ee1, 7044}, {0x6ef1, 7108}, {0x6fa2, 7816}, {0x6fbc, 7920}, {0x704c, 8800}, {0x7083, 9240}, {0x7108, 10304}, {0x7115, 10408}, {0x7128, 10560}, {0x71af, 11640}, {0x7222, 12560}, {0x7228, 12608}, {0x72a5, 13608}, {0x72e0, 14080}, {0x72e6, 14128}, {0x731e, 14576}, {0x7377, 15288}, {0x741d, 16848}, {0x7423, 16944}, {0x7424, 16960}, {0x7466, 18016}, {0x74b0, 19200}, {0x74ce, 19680}, {0x74f0, 20224}, {0x754b, 21680}, {0x7575, 22352}, {0x7594, 22848}, {0x75b1, 23312}, {0x7614, 24896}, {0x7618, 24960}, {0x7631, 25360}, {0x7660, 26112}, {0x76c8, 27776}, {0x7773, 30512}, {0x77af, 31472}, {0x77b9, 31632}, {0x77de, 32224}, {0x7844, 34944}, {0x78d2, 39488}, {0x7924, 42112}, {0x793b, 42848}, {0x79db, 47968}, {0x7a0f, 49632}, {0x7a1a, 49984}, {0x7a6c, 52608}, {0x7a99, 54048}, {0x7ada, 56128}, {0x7b0f, 57824}, {0x7b15, 58016}, {0x7b41, 59424}, {0x7b51, 59936}, {0x7b9c, 62336}, {0x7ba3, 62560}, {0x7c00, std::numeric_limits::infinity()}, {0x7c05, std::numeric_limits::quiet_NaN()}, {0x7c0e, std::numeric_limits::quiet_NaN()}, {0x7c3e, std::numeric_limits::quiet_NaN()}, {0x7c4e, std::numeric_limits::quiet_NaN()}, {0x7c55, std::numeric_limits::quiet_NaN()}, {0x7c58, std::numeric_limits::quiet_NaN()}, {0x7c66, std::numeric_limits::quiet_NaN()}, {0x7cc9, std::numeric_limits::quiet_NaN()}, {0x7cd8, std::numeric_limits::quiet_NaN()}, {0x7d2d, std::numeric_limits::quiet_NaN()}, {0x7d60, std::numeric_limits::quiet_NaN()}, {0x7d79, std::numeric_limits::quiet_NaN()}, {0x7dc7, std::numeric_limits::quiet_NaN()}, {0x7dcf, std::numeric_limits::quiet_NaN()}, {0x7dd8, std::numeric_limits::quiet_NaN()}, {0x7dfb, std::numeric_limits::quiet_NaN()}, {0x7e0f, std::numeric_limits::quiet_NaN()}, {0x7e56, std::numeric_limits::quiet_NaN()}, {0x7e89, std::numeric_limits::quiet_NaN()}, {0x7e9c, std::numeric_limits::quiet_NaN()}, {0x7eb2, std::numeric_limits::quiet_NaN()}, {0x7ec3, std::numeric_limits::quiet_NaN()}, {0x7ef9, std::numeric_limits::quiet_NaN()}, {0x7f36, std::numeric_limits::quiet_NaN()}, {0x8040, -0.0000038146972656}, {0x8101, -0.0000153183937073}, {0x813d, -0.0000188946723938}, {0x81a8, -0.0000252723693848}, {0x81bc, -0.0000264644622803}, {0x81c2, -0.0000268220901489}, {0x8259, -0.00003582239151}, {0x8330, -0.0000486373901367}, {0x8366, -0.0000518560409546}, {0x8392, -0.0000544786453247}, {0x83e4, -0.0000593662261963}, {0x83ee, -0.000059962272644}, {0x8402, -0.0000611543655396}, {0x845e, -0.0000666379928589}, {0x84ac, -0.0000712871551514}, {0x84b1, -0.0000715851783752}, {0x84fb, -0.0000759959220886}, {0x8546, -0.0000804662704468}, {0x856f, -0.0000829100608826}, {0x85b5, -0.0000870823860168}, {0x8638, -0.0000948905944824}, {0x8656, -0.0000966787338257}, {0x86b9, -0.0001025795936584}, {0x86ba, -0.0001026391983032}, {0x86fe, -0.0001066923141479}, {0x8731, -0.0001097321510315}, {0x8740, -0.0001106262207031}, {0x8793, -0.0001155734062195}, {0x87bd, -0.0001180768013}, {0x87f1, -0.0001211762428284}, {0x87f4, -0.0001213550567627}, {0x8809, -0.000123143196106}, {0x882a, -0.0001270771026611}, {0x8848, -0.0001306533813477}, {0x8852, -0.0001318454742432}, {0x8874, -0.0001358985900879}, {0x8892, -0.0001394748687744}, {0x88a7, -0.000141978263855}, {0x88c8, -0.0001459121704102}, {0x8927, -0.0001572370529175}, {0x892a, -0.0001575946807861}, {0x8989, -0.0001689195632935}, {0x89b9, -0.0001746416091919}, {0x8b18, -0.0002164840698242}, {0x8b4b, -0.0002225637435913}, {0x8b62, -0.000225305557251}, {0x8b7f, -0.0002287626266479}, {0x8bca, -0.0002377033233643}, {0x8bcf, -0.000238299369812}, {0x8bff, -0.0002440214157104}, {0x8c0b, -0.0002467632293701}, {0x8c55, -0.0002644062042236}, {0x8c63, -0.0002677440643311}, {0x8d53, -0.0003249645233154}, {0x8dba, -0.0003495216369629}, {0x8e03, -0.0003669261932373}, {0x8e82, -0.0003972053527832}, {0x8e9c, -0.0004034042358398}, {0x8faa, -0.0004677772521973}, {0x902f, -0.0005106925964355}, {0x9051, -0.0005269050598145}, {0x9066, -0.0005369186401367}, {0x907e, -0.0005483627319336}, {0x9080, -0.00054931640625}, {0x908e, -0.0005559921264648}, {0x9102, -0.0006113052368164}, {0x91eb, -0.0007224082946777}, {0x9215, -0.0007424354553223}, {0x9252, -0.0007715225219727}, {0x9294, -0.0008029937744141}, {0x9297, -0.0008044242858887}, {0x933d, -0.0008835792541504}, {0x936f, -0.0009074211120605}, {0x93aa, -0.0009355545043945}, {0x93f2, -0.0009698867797852}, {0x941d, -0.0010042190551758}, {0x945a, -0.0010623931884766}, {0x94ad, -0.0011415481567383}, {0x94d2, -0.0011768341064453}, {0x951c, -0.0012474060058594}, {0x9520, -0.001251220703125}, {0x952f, -0.0012655258178711}, {0x953f, -0.0012807846069336}, {0x9549, -0.0012903213500977}, {0x95c6, -0.0014095306396484}, {0x9602, -0.0014667510986328}, {0x969b, -0.001612663269043}, {0x96fa, -0.0017032623291016}, {0x977d, -0.0018281936645508}, {0x97c3, -0.0018949508666992}, {0x97c6, -0.0018978118896484}, {0x97db, -0.001917839050293}, {0x97f9, -0.0019464492797852}, {0x983f, -0.0020732879638672}, {0x984e, -0.0021018981933594}, {0x985a, -0.0021247863769531}, {0x988c, -0.0022201538085938}, {0x990d, -0.0024662017822266}, {0x9958, -0.0026092529296875}, {0x9971, -0.0026569366455078}, {0x9a4e, -0.0030784606933594}, {0x9a8f, -0.0032024383544922}, {0x9abe, -0.0032920837402344}, {0x9ace, -0.0033226013183594}, {0x9b1e, -0.0034751892089844}, {0x9b3e, -0.0035362243652344}, {0x9b77, -0.0036449432373047}, {0x9b89, -0.0036792755126953}, {0x9b90, -0.003692626953125}, {0x9bec, -0.0038681030273438}, {0x9c03, -0.0039176940917969}, {0x9c75, -0.0043525695800781}, {0x9d6c, -0.0052947998046875}, {0x9d74, -0.0053253173828125}, {0x9da7, -0.0055198669433594}, {0x9e73, -0.0062980651855469}, {0x9e94, -0.0064239501953125}, {0x9f17, -0.0069236755371094}, {0x9f3a, -0.0070571899414062}, {0x9f6c, -0.0072479248046875}, {0x9f89, -0.0073585510253906}, {0x9fbd, -0.0075569152832031}, {0xa003, -0.0078353881835938}, {0xa014, -0.007965087890625}, {0xa019, -0.0080032348632812}, {0xa01d, -0.0080337524414062}, {0xa090, -0.0089111328125}, {0xa1cf, -0.0113449096679688}, {0xa1dd, -0.0114517211914062}, {0xa249, -0.0122756958007812}, {0xa26d, -0.0125503540039062}, {0xa288, -0.01275634765625}, {0xa2fb, -0.0136337280273438}, {0xa390, -0.0147705078125}, {0xa3b3, -0.0150375366210938}, {0xa3ed, -0.0154800415039062}, {0xa434, -0.01641845703125}, {0xa476, -0.017425537109375}, {0xa571, -0.0212554931640625}, {0xa57d, -0.0214385986328125}, {0xa597, -0.0218353271484375}, {0xa5d1, -0.0227203369140625}, {0xa5f9, -0.0233306884765625}, {0xa680, -0.025390625}, {0xa6e3, -0.0269012451171875}, {0xa6f0, -0.027099609375}, {0xa72d, -0.0280303955078125}, {0xa77e, -0.029266357421875}, {0xa7d0, -0.030517578125}, {0xa7ee, -0.030975341796875}, {0xa7f3, -0.0310516357421875}, {0xa80c, -0.0316162109375}, {0xa827, -0.032440185546875}, {0xa89f, -0.036102294921875}, {0xa8a0, -0.0361328125}, {0xa8a5, -0.036285400390625}, {0xa948, -0.041259765625}, {0xaa0c, -0.0472412109375}, {0xaa16, -0.04754638671875}, {0xaa9a, -0.05157470703125}, {0xaaeb, -0.054046630859375}, {0xab5c, -0.0574951171875}, {0xac7e, -0.0701904296875}, {0xad33, -0.08123779296875}, {0xad37, -0.08148193359375}, {0xad90, -0.0869140625}, {0xada0, -0.087890625}, {0xade5, -0.09210205078125}, {0xadf8, -0.09326171875}, {0xae02, -0.0938720703125}, {0xae04, -0.093994140625}, {0xae4f, -0.09857177734375}, {0xae63, -0.09979248046875}, {0xaebe, -0.1053466796875}, {0xaee1, -0.10748291015625}, {0xaef9, -0.10894775390625}, {0xaf0b, -0.11004638671875}, {0xaf78, -0.11669921875}, {0xaf7d, -0.11700439453125}, {0xaf7f, -0.11712646484375}, {0xaf8c, -0.117919921875}, {0xafcb, -0.12176513671875}, {0xb06b, -0.1380615234375}, {0xb07b, -0.1400146484375}, {0xb088, -0.1416015625}, {0xb0b2, -0.146728515625}, {0xb0ed, -0.1539306640625}, {0xb0f9, -0.1553955078125}, {0xb16c, -0.16943359375}, {0xb189, -0.1729736328125}, {0xb1c5, -0.1802978515625}, {0xb1f7, -0.1864013671875}, {0xb22d, -0.1929931640625}, {0xb23c, -0.19482421875}, {0xb258, -0.1982421875}, {0xb2c7, -0.2117919921875}, {0xb2de, -0.214599609375}, {0xb2e1, -0.2149658203125}, {0xb317, -0.2215576171875}, {0xb31d, -0.2222900390625}, {0xb3ef, -0.2479248046875}, {0xb3f8, -0.2490234375}, {0xb45a, -0.27197265625}, {0xb548, -0.330078125}, {0xb5d8, -0.365234375}, {0xb64e, -0.39404296875}, {0xb69f, -0.413818359375}, {0xb6e6, -0.43115234375}, {0xb6ed, -0.432861328125}, {0xb6f7, -0.435302734375}, {0xb79a, -0.47509765625}, {0xb7b6, -0.48193359375}, {0xb7ee, -0.49560546875}, {0xb856, -0.5419921875}, {0xb8c0, -0.59375}, {0xb96f, -0.67919921875}, {0xb9a5, -0.70556640625}, {0xba1e, -0.7646484375}, {0xba2d, -0.77197265625}, {0xba48, -0.78515625}, {0xba65, -0.79931640625}, {0xbaaf, -0.83544921875}, {0xbab0, -0.8359375}, {0xbb12, -0.8837890625}, {0xbb35, -0.90087890625}, {0xbb47, -0.90966796875}, {0xbb97, -0.94873046875}, {0xbba3, -0.95458984375}, {0xbbcb, -0.97412109375}, {0xbbe8, -0.98828125}, {0xbbee, -0.9912109375}, {0xbd03, -1.2529296875}, {0xbd4b, -1.3232421875}, {0xbd4c, -1.32421875}, {0xbd8a, -1.384765625}, {0xbdb6, -1.427734375}, {0xbde1, -1.4697265625}, {0xbe04, -1.50390625}, {0xbe50, -1.578125}, {0xbe54, -1.58203125}, {0xbe6a, -1.603515625}, {0xbf31, -1.7978515625}, {0xbf87, -1.8818359375}, {0xbfa2, -1.908203125}, {0xc016, -2.04296875}, {0xc074, -2.2265625}, {0xc0ca, -2.39453125}, {0xc100, -2.5}, {0xc1b7, -2.857421875}, {0xc1b9, -2.861328125}, {0xc1d3, -2.912109375}, {0xc23f, -3.123046875}, {0xc2d5, -3.416015625}, {0xc32f, -3.591796875}, {0xc3e3, -3.943359375}, {0xc412, -4.0703125}, {0xc49a, -4.6015625}, {0xc4ca, -4.7890625}, {0xc4cf, -4.80859375}, {0xc523, -5.13671875}, {0xc55d, -5.36328125}, {0xc5aa, -5.6640625}, {0xc604, -6.015625}, {0xc61b, -6.10546875}, {0xc642, -6.2578125}, {0xc68b, -6.54296875}, {0xc69e, -6.6171875}, {0xc6b0, -6.6875}, {0xc6ca, -6.7890625}, {0xc71e, -7.1171875}, {0xc721, -7.12890625}, {0xc73b, -7.23046875}, {0xc7d4, -7.828125}, {0xc831, -8.3828125}, {0xc89a, -9.203125}, {0xc8be, -9.484375}, {0xc8dc, -9.71875}, {0xc8e4, -9.78125}, {0xc8fa, -9.953125}, {0xc8fe, -9.984375}, {0xc969, -10.8203125}, {0xca0f, -12.1171875}, {0xca1a, -12.203125}, {0xca6f, -12.8671875}, {0xca7b, -12.9609375}, {0xca8f, -13.1171875}, {0xcaca, -13.578125}, {0xcafd, -13.9765625}, {0xcb05, -14.0390625}, {0xcb6b, -14.8359375}, {0xcbaf, -15.3671875}, {0xcbb4, -15.40625}, {0xcbdf, -15.7421875}, {0xcc2d, -16.703125}, {0xcc74, -17.8125}, {0xccac, -18.6875}, {0xcd11, -20.265625}, {0xce04, -24.0625}, {0xce0f, -24.234375}, {0xceaf, -26.734375}, {0xceb8, -26.875}, {0xcf36, -28.84375}, {0xcfad, -30.703125}, {0xd019, -32.78125}, {0xd08d, -36.40625}, {0xd115, -40.65625}, {0xd119, -40.78125}, {0xd128, -41.25}, {0xd1a4, -45.125}, {0xd1b7, -45.71875}, {0xd1b8, -45.75}, {0xd203, -48.09375}, {0xd20a, -48.3125}, {0xd28b, -52.34375}, {0xd2ac, -53.375}, {0xd2ae, -53.4375}, {0xd2c5, -54.15625}, {0xd2f2, -55.5625}, {0xd326, -57.1875}, {0xd337, -57.71875}, {0xd343, -58.09375}, {0xd34e, -58.4375}, {0xd40c, -64.75}, {0xd43b, -67.6875}, {0xd45a, -69.625}, {0xd464, -70.25}, {0xd4c3, -76.1875}, {0xd505, -80.3125}, {0xd52d, -82.8125}, {0xd5cf, -92.9375}, {0xd5f0, -95}, {0xd607, -96.4375}, {0xd635, -99.3125}, {0xd63d, -99.8125}, {0xd644, -100.25}, {0xd658, -101.5}, {0xd789, -120.5625}, {0xd863, -140.375}, {0xd866, -140.75}, {0xd884, -144.5}, {0xd88d, -145.625}, {0xd89b, -147.375}, {0xd8da, -155.25}, {0xd93b, -167.375}, {0xd982, -176.25}, {0xd995, -178.625}, {0xd99d, -179.625}, {0xd9cf, -185.875}, {0xdaaf, -213.875}, {0xdabd, -215.625}, {0xdb54, -234.5}, {0xdc10, -260}, {0xdca1, -296.25}, {0xdd0a, -322.5}, {0xdd56, -341.5}, {0xddcf, -371.75}, {0xde04, -385}, {0xde0d, -387.25}, {0xde3d, -399.25}, {0xde4f, -403.75}, {0xde66, -409.5}, {0xdeae, -427.5}, {0xdf52, -468.5}, {0xdf63, -472.75}, {0xdf6a, -474.5}, {0xdf77, -477.75}, {0xdf7b, -478.75}, {0xdfc5, -497.25}, {0xdfcf, -499.75}, {0xdfd2, -500.5}, {0xdfd8, -502}, {0xdfe1, -504.25}, {0xe022, -529}, {0xe046, -547}, {0xe092, -585}, {0xe0b0, -600}, {0xe0be, -607}, {0xe0f4, -634}, {0xe11b, -653.5}, {0xe19c, -718}, {0xe213, -777.5}, {0xe232, -793}, {0xe25b, -813.5}, {0xe262, -817}, {0xe279, -828.5}, {0xe2cc, -870}, {0xe2da, -877}, {0xe326, -915}, {0xe330, -920}, {0xe3c3, -993.5}, {0xe3cc, -998}, {0xe566, -1382}, {0xe57e, -1406}, {0xe5c8, -1480}, {0xe609, -1545}, {0xe628, -1576}, {0xe663, -1635}, {0xe6ac, -1708}, {0xe710, -1808}, {0xe77f, -1919}, {0xe7e7, -2023}, {0xe868, -2256}, {0xe885, -2314}, {0xe8ea, -2516}, {0xe919, -2610}, {0xe92c, -2648}, {0xea60, -3264}, {0xeac1, -3458}, {0xeacb, -3478}, {0xeb22, -3652}, {0xeb2c, -3672}, {0xeb59, -3762}, {0xeba5, -3914}, {0xec53, -4428}, {0xec97, -4700}, {0xed16, -5208}, {0xed4a, -5416}, {0xed69, -5540}, {0xee14, -6224}, {0xee59, -6500}, {0xee8a, -6696}, {0xee93, -6732}, {0xeed7, -7004}, {0xef0b, -7212}, {0xef59, -7524}, {0xef61, -7556}, {0xef67, -7580}, {0xefb6, -7896}, {0xf03a, -8656}, {0xf04e, -8816}, {0xf05f, -8952}, {0xf09f, -9464}, {0xf0c0, -9728}, {0xf173, -11160}, {0xf1d7, -11960}, {0xf225, -12584}, {0xf2ca, -13904}, {0xf2d8, -14016}, {0xf2e5, -14120}, {0xf317, -14520}, {0xf35d, -15080}, {0xf3bd, -15848}, {0xf3d3, -16024}, {0xf3e6, -16176}, {0xf3fb, -16344}, {0xf477, -18288}, {0xf4e0, -19968}, {0xf4e5, -20048}, {0xf50b, -20656}, {0xf5a2, -23072}, {0xf5c1, -23568}, {0xf634, -25408}, {0xf651, -25872}, {0xf68a, -26784}, {0xf69c, -27072}, {0xf6ce, -27872}, {0xf816, -33472}, {0xf849, -35104}, {0xf869, -36128}, {0xf878, -36608}, {0xf8cf, -39392}, {0xf90a, -41280}, {0xf916, -41664}, {0xf91e, -41920}, {0xf9c1, -47136}, {0xfa0a, -49472}, {0xfa11, -49696}, {0xfa1d, -50080}, {0xfa51, -51744}, {0xfa86, -53440}, {0xfaac, -54656}, {0xfb95, -62112}, {0xfbd1, -64032}, {0xfbe0, -64512}, {0xfbf5, -65184}, {0xfc00, -std::numeric_limits::infinity()}, {0xfca5, std::numeric_limits::quiet_NaN()}, {0xfcb9, std::numeric_limits::quiet_NaN()}, {0xfcc6, std::numeric_limits::quiet_NaN()}, {0xfd72, std::numeric_limits::quiet_NaN()}, {0xfd77, std::numeric_limits::quiet_NaN()}, {0xfda3, std::numeric_limits::quiet_NaN()}, {0xfe3e, std::numeric_limits::quiet_NaN()}, {0xfe89, std::numeric_limits::quiet_NaN()}, {0xfe91, std::numeric_limits::quiet_NaN()}, {0xfe93, std::numeric_limits::quiet_NaN()}, {0xfed1, std::numeric_limits::quiet_NaN()}, {0xff7a, std::numeric_limits::quiet_NaN()}, {0xffa3, std::numeric_limits::quiet_NaN()}, }; return result; } TEST_CASE(check_half_values) { for(auto [x, f] : half_lut()) { auto h = migraphx::bit_cast(x); if(std::isnan(f)) { CHECK(std::isnan(h)); } else if(std::isinf(f)) { CHECK(std::isinf(h)); CHECK((h < 0) == (f < 0)); CHECK(bit_equal(x, migraphx::half(f))); } else { CHECK(bit_equal(x, migraphx::half(f))); CHECK(migraphx::float_equal(float(h), f)); } } } TEST_CASE(check_flows) { // check positive underflow CHECK(bit_equal(std::numeric_limits::min() * std::numeric_limits::min(), migraphx::half(0))); // check overflow CHECK(bit_equal(std::numeric_limits::infinity() + std::numeric_limits::infinity(), std::numeric_limits::infinity())); CHECK(bit_equal(std::numeric_limits::max() + std::numeric_limits::max(), std::numeric_limits::infinity())); CHECK(bit_equal(std::numeric_limits::max() / std::numeric_limits::epsilon(), std::numeric_limits::infinity())); CHECK(bit_equal(std::numeric_limits::max() + std::numeric_limits::min(), std::numeric_limits::max())); // check negative underflow CHECK(bit_equal(std::numeric_limits::lowest() + std::numeric_limits::lowest(), -std::numeric_limits::infinity())); CHECK(bit_equal(-std::numeric_limits::infinity() - std::numeric_limits::infinity(), -std::numeric_limits::infinity())); CHECK(bit_equal(std::numeric_limits::lowest() - std::numeric_limits::min(), std::numeric_limits::lowest())); } TEST_CASE(test_nan) { float f_qnan = std::numeric_limits::quiet_NaN(); migraphx::half half_qnan(f_qnan); EXPECT(half_qnan.is_nan()); EXPECT(std::isnan(half_qnan)); float f_snan = std::numeric_limits::signaling_NaN(); migraphx::half half_snan(f_snan); EXPECT(half_snan.is_nan()); EXPECT(std::isnan(half_snan)); } TEST_CASE(test_bool) { float zero = 0.0; float two = 2.0; float other = -0.375; migraphx::half half_zero(zero); migraphx::half half_two(two); migraphx::half half_other(other); EXPECT(not static_cast(half_zero)); EXPECT(static_cast(half_two)); EXPECT(static_cast(half_other)); } TEST_CASE(test_pos_infinity) { float finf = std::numeric_limits::infinity(); migraphx::half half_inf_1(finf); CHECK(bit_equal(half_inf_1, std::numeric_limits::infinity())); } TEST_CASE(test_neg_infinity) { float finf = -1.0 * std::numeric_limits::infinity(); migraphx::half half_neginf_1(finf); CHECK(bit_equal(half_neginf_1, -std::numeric_limits::infinity())); } TEST_CASE(test_numeric_max_1) { float fmax = std::numeric_limits::max(); // fp32 max is fp16 inf migraphx::half half_inf(fmax); CHECK(bit_equal(half_inf, std::numeric_limits::infinity())); } TEST_CASE(test_numeric_lowest_1) { float flowest = std::numeric_limits::lowest(); migraphx::half half_neginf(flowest); CHECK(bit_equal(half_neginf, -std::numeric_limits::infinity())); } TEST_CASE(test_max_eq_lowest) { EXPECT(migraphx::float_equal(std::numeric_limits::lowest(), -1 * std::numeric_limits::max())); } TEST_CASE(test_isfinite) { EXPECT(std::isfinite(migraphx::half(0.0))); EXPECT(std::isfinite(migraphx::half(-0.0))); EXPECT(not std::isfinite(migraphx::half(std::numeric_limits::quiet_NaN()))); } TEST_CASE(test_binary_ops) { auto a = migraphx::half(-1.0); auto b = migraphx::half(1.0); auto c = migraphx::half(0.0); auto d = migraphx::half(-0.0); EXPECT(migraphx::float_equal((c + d), c)); EXPECT(migraphx::float_equal((c + d), d)); EXPECT(migraphx::float_equal((a + b), c)); EXPECT(migraphx::float_equal((a + b), d)); auto e = migraphx::half(10.0); auto f = migraphx::half(-10.0); EXPECT(e > f); EXPECT(f < e); EXPECT(f <= e); EXPECT(e >= f); EXPECT(e <= e); EXPECT(f >= f); EXPECT(not migraphx::float_equal(f, e)); } TEST_CASE(test_stream_op) { auto a = migraphx::half(-1.0); std::stringstream ss; ss << a; EXPECT(std::string("-1") == ss.str()); ss = std::stringstream(); auto b = std::numeric_limits::quiet_NaN(); ss << b; EXPECT(std::string("nan") == ss.str()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/include/000077500000000000000000000000001510465702400171425ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/include/basic_ops.hpp000066400000000000000000000203061510465702400216160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include struct sum_op { std::string name() const { return "sum"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { migraphx::argument result; if(args.size() != 2) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape() != args[1].get_shape()) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape().lens().size() != 1) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape().lens().front() != 1) MIGRAPHX_THROW("Wrong args"); args[0].visit_at([&](auto x) { args[1].visit_at([&](auto y) { result = migraphx::literal{x + y}.get_argument(); }); }); return result; } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.size() != 2) MIGRAPHX_THROW("Wrong inputs"); return inputs.front(); } }; struct sum_std_op { std::string name() const { return "sum_std"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { migraphx::argument result; args[0].visit_at([&](auto x) { args[1].visit_at([&](auto y) { result = migraphx::literal{x + y}.get_argument(); }); }); return result; } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.size() != 2) MIGRAPHX_THROW("Wrong inputs"); for(auto&& input : inputs) { if(not input.standard()) MIGRAPHX_THROW("Not standard shape"); } if(inputs.at(0) != inputs.at(1)) { MIGRAPHX_THROW("Invalid shapes"); } return inputs.front(); } }; struct minus_op { std::string name() const { return "minus"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { migraphx::argument result; if(args.size() != 2) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape() != args[1].get_shape()) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape().lens().size() != 1) MIGRAPHX_THROW("Wrong args"); if(args[0].get_shape().lens().front() != 1) MIGRAPHX_THROW("Wrong args"); args[0].visit_at([&](auto x) { args[1].visit_at([&](auto y) { result = migraphx::literal{x - y}.get_argument(); }); }); return result; } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.size() != 2) MIGRAPHX_THROW("Wrong inputs"); return inputs.front(); } }; struct pass_op { std::string name() const { return "pass"; } migraphx::argument compute(const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } int output_alias(const std::vector& s) const { return s.empty() ? -1 : 0; } }; struct non_const_pass_op { std::string name() const { return "pass"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } int output_alias(const std::vector& s) const { return s.empty() ? -1 : 0; } }; struct mod_pass_op { std::string name() const { return "mod_pass"; } migraphx::shape compute_shape(std::vector inputs, std::vector mods) const { if(not mods.empty()) { auto out_shapes = mods[0]->get_output_shapes(); if(out_shapes.size() > 1) return migraphx::shape{out_shapes}; return out_shapes[0]; } if(not inputs.empty()) { return inputs.front(); } return {}; } int output_alias(const std::vector&) const { return 0; } }; struct unary_pass_op { std::string name() const { return "unary_pass"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.size() != 1) MIGRAPHX_THROW("Wrong inputs"); return inputs.front(); } int output_alias(const std::vector&) const { return 0; } }; struct pass_standard_op { std::string name() const { return "pass"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { for(auto&& input : inputs) { if(not input.standard()) throw std::runtime_error("Not standard shape"); } if(inputs.empty()) return {}; return inputs.front(); } int output_alias(const std::vector&) const { return 0; } }; struct nop { std::string name() const { return "nop"; } migraphx::argument compute(const migraphx::shape&, const std::vector&) const { return {}; } migraphx::shape compute_shape(const std::vector&) const { return {}; } }; struct tuple_op { std::string name() const { return "tuple_op"; } migraphx::shape compute_shape(const std::vector& inputs) const { return migraphx::shape(inputs); } migraphx::argument compute(migraphx::context&, const migraphx::shape&, const std::vector& input_args) const { return input_args; } }; inline migraphx::literal get_2x2(int base = 0) { return migraphx::literal{{migraphx::shape::float_type, {2, 2}}, {base + 1, base + 2, base + 3, base + 4}}; } inline migraphx::literal get_2x2_transposed() { return migraphx::literal{{migraphx::shape::float_type, {2, 2}, {1, 2}}, {1, 2, 3, 4}}; } inline migraphx::literal get_2() { return migraphx::literal{{migraphx::shape::float_type, {2}}, {1, 2}}; } inline migraphx::literal get_2_broadcasted() { return migraphx::literal{{migraphx::shape::float_type, {2, 1}, {1, 0}}, {1, 2}}; } ROCm-AMDMIGraphX-46524e8/test/include/group.hpp000066400000000000000000000060361510465702400210140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_GPU_MAKE_GROUP_OP_HPP #define MIGRAPHX_GUARD_TEST_GPU_MAKE_GROUP_OP_HPP #include #include #include #include #include template migraphx::instruction_ref add_group(migraphx::program& p, const std::string& name, const std::string& group_tag, std::vector inputs, std::vector arg_names, const F& f) { assert(inputs.size() == arg_names.size() and "One interior parameter name given per input."); auto* mm = p.get_main_module(); auto* pm = p.create_module(name); pm->set_bypass(); std::vector params; for(size_t i = 0, e = inputs.size(); i < e; ++i) { params.push_back(pm->add_parameter(arg_names[i], inputs[i]->get_shape().as_standard())); } auto r = f(pm, params); pm->add_return(r); return mm->add_instruction(migraphx::make_op("group", {{"tag", group_tag}}), inputs, {pm}); } template migraphx::instruction_ref add_group(migraphx::program& p, const std::string& name, const std::string& group_tag, std::vector inputs, const F& f) { std::vector arg_names; migraphx::transform(migraphx::range(inputs.size()), std::back_inserter(arg_names), [&](auto i) { return migraphx::param_name(i); }); return add_group(p, name, group_tag, std::move(inputs), std::move(arg_names), std::move(f)); } #endif // MIGRAPHX_GUARD_TEST_GPU_MAKE_GROUP_OP_HPP ROCm-AMDMIGraphX-46524e8/test/include/layernorm.hpp000066400000000000000000000133521510465702400216670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include inline migraphx::instruction_ref add_layernorm(migraphx::module& m, migraphx::instruction_ref x, const std::vector& dims, float eps = 1e-12f) { auto mgx_type = x->get_shape().type(); auto scale = m.add_parameter("scale", migraphx::shape{mgx_type, {dims.back()}}); auto bias = m.add_parameter("bias", migraphx::shape{mgx_type, {dims.back()}}); auto epsilon = m.add_literal(migraphx::literal{migraphx::shape{mgx_type}, {eps}}); auto exponent = m.add_literal(migraphx::literal{migraphx::shape{mgx_type}, {2.0f}}); auto mean = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto mean_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), mean); auto sub = m.add_instruction(migraphx::make_op("sub"), x, mean_mbcast); auto exponent_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), exponent); auto pow = m.add_instruction(migraphx::make_op("pow"), sub, exponent_mbcast); auto var = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), pow); auto epsilon_mbcast = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", var->get_shape().lens()}}), epsilon); auto add_epsilon = m.add_instruction(migraphx::make_op("add"), var, epsilon_mbcast); auto sqrt = m.add_instruction(migraphx::make_op("sqrt"), add_epsilon); auto sqrt_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), sqrt); auto div = m.add_instruction(migraphx::make_op("div"), sub, sqrt_mbcast); auto scale_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), scale); auto mul = m.add_instruction(migraphx::make_op("mul"), div, scale_mbcast); auto bias_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), bias); return m.add_instruction(migraphx::make_op("add"), mul, bias_mbcast); } inline migraphx::instruction_ref add_pointwise_layernorm(migraphx::module& m, migraphx::instruction_ref x, const std::vector& dims, float eps = 1e-12f) { auto mgx_type = x->get_shape().type(); auto scale = m.add_parameter("scale", migraphx::shape{mgx_type, {1, 1, dims.back()}}); auto bias = m.add_parameter("bias", migraphx::shape{mgx_type, {1, 1, dims.back()}}); auto epsilon = m.add_literal(migraphx::literal{migraphx::shape{mgx_type}, {eps}}); auto one = m.add_literal(migraphx::literal{migraphx::shape{mgx_type}, {1}}); auto mean = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto mean_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), mean); auto x_minus_mean = m.add_instruction(migraphx::make_op("sub"), x, mean_mbcast); auto sqdiff = m.add_instruction(migraphx::make_op("sqdiff"), x, mean_mbcast); auto var = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), sqdiff); auto epsilon_mbcast = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", var->get_shape().lens()}}), epsilon); auto var_stable = m.add_instruction(migraphx::make_op("add"), var, epsilon_mbcast); auto inv_stddev_x = m.add_instruction(migraphx::make_op("rsqrt"), var_stable); auto inv_stddev_x_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), inv_stddev_x); auto norm = m.add_instruction(migraphx::make_op("mul"), x_minus_mean, inv_stddev_x_mbcast); auto one_mbcast = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scale->get_shape().lens()}}), one); auto add_scale = m.add_instruction(migraphx::make_op("add"), scale, one_mbcast); auto scale_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), add_scale); auto scale_norm = m.add_instruction(migraphx::make_op("mul"), norm, scale_mbcast); auto bias_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", dims}}), bias); return m.add_instruction(migraphx::make_op("add"), scale_norm, bias_mbcast); } ROCm-AMDMIGraphX-46524e8/test/include/pointwise.hpp000066400000000000000000000065101510465702400216760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_INCLUDE_POINTWISE_HPP #define MIGRAPHX_GUARD_TEST_INCLUDE_POINTWISE_HPP #include #include #include #include #include template migraphx::module_ref create_pointwise_module(migraphx::program& p, const std::string& name, std::vector inputs, const F& f) { auto* pm = p.create_module(name); pm->set_bypass(); std::vector params; std::transform(inputs.begin(), inputs.end(), std::back_inserter(params), [&](auto input) { return pm->add_parameter("x" + std::to_string(params.size()), migraphx::shape{input->get_shape().type()}); }); auto r = f(pm, params); pm->add_return({r}); return pm; } template migraphx::instruction_ref add_pointwise(migraphx::program& p, migraphx::module_ref mm, const std::string& name, const std::vector& inputs, const F& f) { auto* pm = create_pointwise_module(p, name, inputs, std::move(f)); return mm->add_instruction(migraphx::make_op("pointwise"), inputs, {pm}); } template migraphx::instruction_ref add_pointwise(migraphx::program& p, const std::string& name, const std::vector& inputs, const F& f) { return add_pointwise(p, p.get_main_module(), name, inputs, std::move(f)); } inline auto noop_pointwise() { return [=](auto*, const auto& inputs) { return inputs; }; } inline auto single_pointwise(const std::string& name) { return [=](auto* pm, const auto& inputs) { return pm->add_instruction(migraphx::make_op(name), inputs); }; } #endif // MIGRAPHX_GUARD_TEST_INCLUDE_POINTWISE_HPP ROCm-AMDMIGraphX-46524e8/test/include/quantize_helpers.hpp000066400000000000000000000110541510465702400232360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #ifndef MIGRAPHX_GUARD_TEST_INCLUDE_QUANTIZE_HELPERS_HPP #define MIGRAPHX_GUARD_TEST_INCLUDE_QUANTIZE_HELPERS_HPP inline migraphx::instruction_ref broadcast_scale(migraphx::module& m, migraphx::instruction_ref scale, const std::vector& out_lens, std::size_t axis) { if(scale->get_shape().lens() == out_lens) return scale; migraphx::instruction_ref scale_mb; auto scale_lens = scale->get_shape().lens(); if(scale_lens.front() == 1 and scale_lens.size() == 1) scale_mb = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", out_lens}}), scale); else scale_mb = m.add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", out_lens}}), scale); return scale_mb; } inline migraphx::instruction_ref broadcast_shift(migraphx::module& m, migraphx::instruction_ref shift, const std::vector& out_lens) { if(shift->get_shape().lens() == out_lens) return shift; return m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", out_lens}}), shift); } inline migraphx::instruction_ref add_scale_mul(migraphx::module& m, migraphx::instruction_ref scale1, migraphx::instruction_ref scale2, std::size_t axis1, std::size_t axis2, const std::vector& out_lens) { auto scale1_mb = broadcast_scale(m, scale1, out_lens, axis1); auto scale2_mb = broadcast_scale(m, scale2, out_lens, axis2); return m.add_instruction(migraphx::make_op("mul"), scale1_mb, scale2_mb); } inline migraphx::instruction_ref add_quantize_op(migraphx::module& m, const std::string& name, migraphx::instruction_ref x, migraphx::instruction_ref scale, migraphx::instruction_ref shift, std::size_t q_axis = 1) { auto lens = x->get_shape().lens(); auto scale_mb = broadcast_scale(m, scale, lens, q_axis); auto shift_mb = broadcast_shift(m, shift, lens); return m.add_instruction(migraphx::make_op(name), x, scale_mb, shift_mb); } inline migraphx::instruction_ref add_quantize_op(migraphx::module& m, const std::string& name, migraphx::instruction_ref x, migraphx::instruction_ref scale, std::size_t q_axis = 1) { auto lens = x->get_shape().lens(); auto scale_mb = broadcast_scale(m, scale, lens, q_axis); return m.add_instruction(migraphx::make_op(name), x, scale_mb); } #endif // MIGRAPHX_GUARD_TEST_INCLUDE_QUANTIZE_HELPERS_HPP ROCm-AMDMIGraphX-46524e8/test/include/reduce.hpp000066400000000000000000000106571510465702400211330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_INCLUDE_REDUCE_HPP #define MIGRAPHX_GUARD_TEST_INCLUDE_REDUCE_HPP #include #include #include #include #include #include inline bool all_instructions_are_local(const migraphx::module& m) { return std::all_of(m.begin(), m.end(), [&](const auto& ins) { return std::all_of(ins.inputs().begin(), ins.inputs().end(), [&](auto input) { return m.has_instruction(input); }); }); } inline void auto_add_return(migraphx::module_ref m, migraphx::instruction_ref ins) { m->add_return({ins}); } inline void auto_add_return(migraphx::module_ref m, std::vector inss) { m->add_return(std::move(inss)); } template migraphx::module_ref add_reduce_module(migraphx::program& p, const std::string& name, std::vector inputs, const std::vector& axes, const F& f) { auto* rm = p.create_module(name); rm->set_bypass(); std::vector params; std::transform(inputs.begin(), inputs.end(), std::back_inserter(params), [&](auto input) { return rm->add_parameter( "x" + std::to_string(params.size()), migraphx::shape{input->get_shape().type(), input->get_shape().lens()}); }); auto r = f(rm, params, axes); auto_add_return(rm, r); EXPECT(all_instructions_are_local(*rm)); return rm; } template migraphx::instruction_ref add_reduce(migraphx::program& p, const std::string& name, const std::vector& inputs, const std::vector& axes, const F& f) { auto* mm = p.get_main_module(); auto rm = add_reduce_module(p, name, inputs, axes, std::move(f)); return mm->add_instruction(migraphx::make_op("fused_reduce", {{"axes", axes}}), inputs, {rm}); } template migraphx::instruction_ref add_reduce(migraphx::program& p, const std::string& name, const std::vector& inputs, const std::vector& axes, const std::string& assign, const F& f) { auto* mm = p.get_main_module(); auto rm = add_reduce_module(p, name, inputs, axes, std::move(f)); return mm->add_instruction( migraphx::make_op("split_fused_reduce", {{"axes", axes}, {"assign", assign}}), inputs, {rm}); } inline auto squared() { return [](auto* pm, const auto& inputs) { return pm->add_instruction(migraphx::make_op("mul"), inputs[0], inputs[0]); }; } inline auto single_reduce(const std::string& name) { return [=](auto* rm, const auto& inputs, const auto& axes) { return rm->add_instruction(migraphx::make_op(name, {{"axes", axes}}), inputs); }; } #endif // MIGRAPHX_GUARD_TEST_INCLUDE_REDUCE_HPP ROCm-AMDMIGraphX-46524e8/test/include/rob.hpp000066400000000000000000000047751510465702400204520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_ROB_HPP #define MIGRAPHX_GUARD_ROB_HPP #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wglobal-constructors" #endif // Used to access private member variables template struct stowed { // NOLINTNEXTLINE static typename Tag::type value; }; template // NOLINTNEXTLINE typename Tag::type stowed::value; template struct stow_private { stow_private() noexcept { stowed::value = X; } // NOLINTNEXTLINE static stow_private instance; }; template // NOLINTNEXTLINE stow_private stow_private::instance; template struct mem_data_ptr { using type = T C::*; }; // NOLINTNEXTLINE #define MIGRAPHX_ROB(name, Type, C, mem) \ struct name##_tag : mem_data_ptr \ { \ }; \ template struct stow_private; \ template \ static auto& name(T && x) \ { \ return x.*stowed::value; \ } #ifdef __clang__ #pragma clang diagnostic pop #endif #endif ROCm-AMDMIGraphX-46524e8/test/include/test.hpp000066400000000000000000000630241510465702400206370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #endif #ifndef MIGRAPHX_GUARD_TEST_TEST_HPP #define MIGRAPHX_GUARD_TEST_TEST_HPP namespace test { template struct rank : rank { }; template <> struct rank<0> { }; // clang-format off // NOLINTNEXTLINE #define TEST_FOREACH_BINARY_OPERATORS(m) \ m(==, equal) \ m(!=, not_equal) \ m(<=, less_than_equal) \ m(>=, greater_than_equal) \ m(<, less_than) \ m(>, greater_than) \ m(and, and_op) \ m(or, or_op) // clang-format on // clang-format off // NOLINTNEXTLINE #define TEST_FOREACH_UNARY_OPERATORS(m) \ m(not, not_op) // clang-format on // NOLINTNEXTLINE #define TEST_EACH_BINARY_OPERATOR_OBJECT(op, name) \ struct name \ { \ static std::string as_string() { return #op; } \ template \ static decltype(auto) call(T&& x, U&& y) \ { \ return x op y; \ } \ }; // NOLINTNEXTLINE #define TEST_EACH_UNARY_OPERATOR_OBJECT(op, name) \ struct name \ { \ static std::string as_string() { return #op; } \ template \ static decltype(auto) call(T&& x) \ { \ return op x; \ } \ }; TEST_FOREACH_BINARY_OPERATORS(TEST_EACH_BINARY_OPERATOR_OBJECT) TEST_FOREACH_UNARY_OPERATORS(TEST_EACH_UNARY_OPERATOR_OBJECT) struct nop { static std::string as_string() { return ""; } template static auto call(T&& x) { return static_cast(x); } }; struct function { static std::string as_string() { return ""; } template static decltype(auto) call(T&& x) { return x(); } }; template void print_stream(Stream& s, const T& x); template Stream& print_stream_impl(rank<0>, Stream& s, const T&) { // TODO: Print typename s << '?'; return s; } template auto print_stream_impl(rank<1>, Stream& s, const T& x) -> decltype(s << x) // NOLINT(bugprone-multi-level-implicit-pointer-conversion) { if constexpr(std::is_pointer{}) { return s << static_cast(x); } else if constexpr(std::is_same{}) { if(x) s << "true"; else s << "false"; return s; } else { return s << x; } } template Stream& print_stream_impl(rank<2>, Stream& s, const std::pair& p) { s << "{"; s << p.first << ", " << p.second; s << "}"; return s; } template Stream& print_stream_impl(rank<3>, Stream& s, std::nullptr_t) { s << "nullptr"; return s; } template {}>::type> auto print_stream_impl(rank<4>, Stream& s, const Range& v) -> decltype(v.end(), s << *v.begin(), void()) { auto start = v.begin(); auto last = v.end(); s << "{ "; if(start != last) { print_stream(s, *start); std::for_each(std::next(start), last, [&](auto&& x) { s << ", "; print_stream(s, x); }); } s << "}"; } template void print_stream(Stream& s, const T& x) { print_stream_impl(rank<5>{}, s, x); } template const T& get_value(const T& x) { return x; } template struct lhs_expression; template lhs_expression make_lhs_expression(T&& lhs); template lhs_expression make_lhs_expression(T&& lhs, Operator); // NOLINTNEXTLINE #define TEST_EXPR_BINARY_OPERATOR(op, name) \ template \ auto operator op(V&& rhs2) const \ { \ return make_expression(*this, std::forward(rhs2), name{}); /* NOLINT */ \ } // NOLINTNEXTLINE #define TEST_EXPR_UNARY_OPERATOR(op, name) \ auto operator op() const { return make_lhs_expression(lhs, name{}); /* NOLINT */ } template struct expression { T lhs; U rhs; friend std::ostream& operator<<(std::ostream& s, const expression& self) { print_stream(s, self.lhs); s << " " << Operator::as_string() << " "; print_stream(s, self.rhs); return s; } friend decltype(auto) get_value(const expression& e) { return e.value(); } decltype(auto) value() const { return Operator::call(get_value(lhs), get_value(rhs)); }; TEST_FOREACH_UNARY_OPERATORS(TEST_EXPR_UNARY_OPERATOR) TEST_FOREACH_BINARY_OPERATORS(TEST_EXPR_BINARY_OPERATOR) }; // TODO: Remove rvalue references template expression make_expression(T&& lhs, U&& rhs, Operator) { return {std::forward(lhs), std::forward(rhs)}; } // TODO: Remove rvalue reference template lhs_expression make_lhs_expression(T&& lhs) { return lhs_expression{std::forward(lhs)}; } template lhs_expression make_lhs_expression(T&& lhs, Operator) { return lhs_expression{std::forward(lhs)}; } template struct lhs_expression { T lhs; explicit lhs_expression(T e) : lhs(std::move(e)) {} friend std::ostream& operator<<(std::ostream& s, const lhs_expression& self) { std::string op = Operator::as_string(); if(not op.empty()) s << Operator::as_string() << " "; print_stream(s, self.lhs); return s; } friend decltype(auto) get_value(const lhs_expression& e) { return e.value(); } decltype(auto) value() const { return Operator::call(get_value(lhs)); } TEST_FOREACH_BINARY_OPERATORS(TEST_EXPR_BINARY_OPERATOR) TEST_FOREACH_UNARY_OPERATORS(TEST_EXPR_UNARY_OPERATOR) // NOLINTNEXTLINE #define TEST_LHS_REOPERATOR(op) \ template \ auto operator op(const U& rhs) const \ { \ return make_lhs_expression(lhs op rhs); \ } TEST_LHS_REOPERATOR(+) TEST_LHS_REOPERATOR(-) TEST_LHS_REOPERATOR(*) TEST_LHS_REOPERATOR(/) TEST_LHS_REOPERATOR(%) TEST_LHS_REOPERATOR(&) TEST_LHS_REOPERATOR(|) TEST_LHS_REOPERATOR(^) }; template struct predicate { std::string msg; F f; friend std::ostream& operator<<(std::ostream& s, const predicate& self) { s << self.msg; return s; } decltype(auto) operator()() const { return f(); } operator decltype(auto)() const { return f(); } }; template auto make_predicate(const std::string& msg, F f) { return make_lhs_expression(predicate{msg, std::move(f)}, function{}); } template std::string as_string(const T& x) { std::stringstream ss; print_stream(ss, x); return ss.str(); } template auto make_function(const std::string& name, F f) { return [=](auto&&... xs) { std::vector args = {as_string(xs)...}; return make_predicate(name + "(" + as_string(args) + ")", [=] { return f(xs...); }); }; } struct capture { template auto operator->*(const T& x) const { return make_lhs_expression(x); } template auto operator->*(const lhs_expression& x) const { return x; } }; enum class color { reset = 0, bold = 1, underlined = 4, fg_red = 31, fg_green = 32, fg_yellow = 33, fg_blue = 34, fg_default = 39, bg_red = 41, bg_green = 42, bg_yellow = 43, bg_blue = 44, bg_default = 49 }; inline std::ostream& operator<<(std::ostream& os, const color& c) { #ifndef _WIN32 static const bool use_color = isatty(STDOUT_FILENO) != 0; if(use_color) return os << "\033[" << static_cast(c) << "m"; #else (void)c; #endif return os; } inline std::atomic& failures() { // NOLINTNEXTLINE static std::atomic f = 0; return f; } template void failed(const T& x, const char* msg, const char* func, const char* file, int line, F f) { if(not bool(x.value())) { failures()++; std::cout << func << std::endl; std::cout << file << ":" << line << ":" << std::endl; std::cout << color::bold << color::fg_red << " FAILED: " << color::reset << msg << " " << "[ " << x << " ]" << std::endl; f(); } } template bool throws(F f) { try { f(); return false; } catch(...) { return true; } } template bool throws(F f, const std::string& msg = "") { try { f(); return false; } catch(const Exception& ex) { return std::string(ex.what()).find(msg) != std::string::npos; } } template auto within_abs(T px, U py, double ptol = 1e-6f) { return make_function("near", [](auto x, auto y, auto tol) { return std::abs(x - y) < tol; })( px, py, ptol); } // This implements the basic globbing algorithm where `*` matches any number // of characters(including none) and `?` matches any single character. It // doesnt support character classes. // // This is a simple recursive implementation that scans the string where the // string and pattern matches. When a `*` is found in the pattern, the // `glob_match` function is called recursively to compare the rest of the // pattern to the rest of the string. If the recursive call returns true, // then we have a match. However, if it returns false, then we advance one // character and call the recusrsive call again. This is referred to as a // star-loop, which will consume zero or more characters. // // This simple recursive implementation works well for short string and // patterns with few stars. First, it is unlikely to use many stars to glob // test names. Secondly, using many stars is still signficantly faster than // using the equivalent std::regex, which has a much slower time complexity. template bool glob_match(Iterator1 start, Iterator1 last, Iterator2 pattern_start, Iterator2 pattern_last) { std::tie(start, pattern_start) = std::mismatch(start, last, pattern_start, pattern_last, [](auto c, auto m) { if(m == '?') return true; // We need a loop for star, so bail and handle the loop below if(m == '*') return false; return c == m; }); // If there is no more pattern then return true if there is no more string to match if(pattern_start == pattern_last) return start == last; // If the pattern is not a star then its a mismatch if(*pattern_start != '*') return false; // Multiple stars are the same as a single star so skip over multiple stars pattern_start = std::find_if(pattern_start, pattern_last, [](auto c) { return c != '*'; }); // If the star is at the end then return true if(pattern_start == pattern_last) return true; // star-loop: match the rest of the pattern and text while(not glob_match(start, last, pattern_start, pattern_last) and start != last) start++; // If the string is empty then it means a match was never found return start != last; } using string_map = std::unordered_map>; template string_map generic_parse(const std::vector& as, Keyword keyword) { string_map result; std::string flag; for(auto&& x : as) { auto f = keyword(x); if(f.empty()) { result[flag].push_back(x); } else { flag = f.front(); result[flag]; // Ensure the flag exists flag = f.back(); } } return result; } using test_case = std::function; inline auto& get_test_cases() { // NOLINTNEXTLINE static std::vector> cases; return cases; } inline void add_test_case(std::string name, test_case f) { get_test_cases().emplace_back(std::move(name), std::move(f)); } struct auto_register_test_case { template auto_register_test_case(const char* name, F f) noexcept { add_test_case(name, f); } }; struct failure_error { }; [[noreturn]] inline void fail() { throw failure_error{}; } struct driver { driver() { add_flag({"--help", "-h"}, "Show help"); add_flag({"--list", "-l"}, "List all test cases"); add_flag({"--continue", "-c"}, "Continue after failure"); add_flag({"--quiet", "-q"}, "Don't print out extra output"); } struct argument { std::vector flags = {}; std::string help = ""; int nargs = 1; }; void add_arg(const std::vector& flags, const std::string& help = "") { arguments.push_back(argument{flags, help, 1}); } void add_flag(const std::vector& flags, const std::string& help = "") { arguments.push_back(argument{flags, help, 0}); } static void wrap(std::ostream& os, const std::string& text, const std::string& prefix = "", unsigned int line_length = 80) { std::istringstream iss(text); std::string line = prefix; do { std::string word; iss >> word; if(line.length() + word.length() > line_length) { os << line << std::endl; line = prefix; } line += word + " "; } while(iss); if(not line.empty()) os << line << std::endl; } void show_help(const std::string& exe) const { const std::string prefix = " "; std::cout << std::endl; std::cout << color::fg_yellow << "USAGE:" << color::reset << std::endl; std::cout << " "; std::cout << exe << " ... " << std::endl; std::cout << std::endl; std::cout << color::fg_yellow << "ARGS:" << color::reset << std::endl; std::cout << " "; std::cout << color::fg_green << "..." << color::reset; std::cout << std::endl; wrap(std::cout, "Test cases to run. A test case can be either the exact test case name or a glob. A " "glob expression uses a '*' to select zero or more characters or a '?' to select any " "single character.", prefix + prefix); std::cout << std::endl; std::cout << color::fg_yellow << "OPTIONS:" << color::reset << std::endl; for(auto&& arg : arguments) { std::cout << color::fg_green; std::string arg_prefix = prefix; for(const std::string& a : arg.flags) { std::cout << arg_prefix; std::cout << a; arg_prefix = ", "; } std::cout << color::reset << std::endl; wrap(std::cout, arg.help, prefix + prefix); } } std::ostream& out() const { struct null_buffer : std::streambuf { virtual int overflow(int c) override { return c; } }; static null_buffer buffer; static std::ostream null_stream(&buffer); if(quiet) return null_stream; return std::cout; } string_map parse(int argc, const char* argv[]) const { std::vector args(argv + 1, argv + argc); string_map keys; for(auto&& arg : arguments) { for(auto&& flag : arg.flags) { keys[flag] = {arg.flags.front()}; if(arg.nargs == 0) keys[flag].push_back(""); } } auto result = generic_parse(args, [&](auto&& s) -> std::vector { if(keys.count(s) > 0) return keys[s]; else return {}; }); result["__exe__"].push_back(argv[0]); return result; } static std::string create_command(const string_map& args) { std::stringstream ss; ss << args.at("__exe__").front(); if(args.count("") > 0) { for(auto&& arg : args.at("")) ss << " \"" << arg << "\""; } for(auto&& p : args) { if(p.first == "__exe__") continue; if(p.first.empty()) continue; ss << " " << p.first; for(auto&& arg : p.second) ss << " \"" << arg << "\""; } return ss.str(); } static std::string fork(const std::string& name, string_map args) { std::string msg; args[""] = {name}; args.erase("--continue"); args["--quiet"]; auto cmd = create_command(args); auto r = std::system(cmd.c_str()); // NOLINT if(r != 0) msg = "Exited with " + std::to_string(r); return msg; } static std::vector> glob_tests(const std::string& pattern) { std::vector> result; std::copy_if(get_test_cases().begin(), get_test_cases().end(), std::back_inserter(result), [&](auto&& p) { return glob_match( p.first.begin(), p.first.end(), pattern.begin(), pattern.end()); }); return result; } void run_test_case(const std::string& name, const test_case& f, const string_map& args) { ran++; out() << color::fg_green << "[ RUN ] " << color::reset << color::bold << name << color::reset << std::endl; std::string msg; auto start = std::chrono::steady_clock::now(); if(args.count("--continue") > 0) { msg = fork(name, args); } else { try { failures() = 0; f(); } // cppcheck-suppress migraphx-EmptyCatchStatement catch(const failure_error&) { } } auto finish = std::chrono::steady_clock::now(); auto elapsed_ms = std::chrono::duration_cast>(finish - start) .count(); if(msg.empty() and failures() != 0) { if(failures() == 1) msg = "Test failure"; else msg = std::to_string(failures()) + " test failures"; } if(msg.empty()) { out() << color::fg_green << "[ COMPLETE ] " << color::reset; } else { failed.push_back(name); out() << color::fg_red << "[ FAILED ] " << color::reset; } out() << color::bold << name << color::reset; out() << color::fg_blue << " (" << elapsed_ms << "ms)" << color::reset; if(not msg.empty()) out() << ": " << color::fg_yellow << msg << color::reset; out() << std::endl; } void run(int argc, const char* argv[]) { auto args = parse(argc, argv); if(args.count("--help") > 0) { show_help(args.at("__exe__").front()); return; } if(args.count("--list") > 0) { for(auto&& tc : get_test_cases()) out() << tc.first << std::endl; return; } if(args.count("--quiet") > 0) quiet = true; auto cases = args[""]; if(cases.empty()) { for(auto&& tc : get_test_cases()) run_test_case(tc.first, tc.second, args); } else { std::unordered_map m(get_test_cases().begin(), get_test_cases().end()); for(auto&& iname : cases) { std::vector> found_cases; for(auto&& pattern : get_case_names(iname)) { auto f = m.find(pattern); if(f == m.end()) { found_cases = glob_tests(pattern); } else { found_cases.push_back(*f); } } if(found_cases.empty()) { out() << color::fg_red << "[ ERROR ] Test case '" << iname << "' not found." << color::reset << std::endl; failed.push_back(iname); } for(auto&& p : found_cases) run_test_case(p.first, p.second, args); } } out() << color::fg_green << "[==========] " << color::fg_yellow << ran << " tests ran" << color::reset << std::endl; if(not failed.empty()) { out() << color::fg_red << "[ FAILED ] " << color::fg_yellow << failed.size() << " tests failed" << color::reset << std::endl; for(auto&& name : failed) out() << color::fg_red << "[ FAILED ] " << color::fg_yellow << name << color::reset << std::endl; std::exit(1); } } std::function(const std::string&)> get_case_names = [](const std::string& name) -> std::vector { return {name}; }; std::vector arguments = {}; std::vector failed = {}; std::size_t ran = 0; bool quiet = false; }; inline void run(int argc, const char* argv[]) { driver d{}; d.run(argc, argv); } } // namespace test // NOLINTNEXTLINE #define TEST_CAPTURE(...) test::capture{}->*__VA_ARGS__ #ifdef _WIN32 // NOLINTNEXTLINE #define TEST_PRETTY_FUNCTION __FUNCSIG__ #else // NOLINTNEXTLINE #define TEST_PRETTY_FUNCTION __PRETTY_FUNCTION__ #endif // NOLINTNEXTLINE #define CHECK(...) \ test::failed( \ TEST_CAPTURE(__VA_ARGS__), #__VA_ARGS__, TEST_PRETTY_FUNCTION, __FILE__, __LINE__, [] {}) // NOLINTNEXTLINE #define EXPECT(...) \ test::failed(TEST_CAPTURE(__VA_ARGS__), \ #__VA_ARGS__, \ TEST_PRETTY_FUNCTION, \ __FILE__, \ __LINE__, \ &test::fail) // NOLINTNEXTLINE #define STATUS(...) EXPECT((__VA_ARGS__) == 0) // NOLINTNEXTLINE #define TEST_CAT(x, ...) TEST_PRIMITIVE_CAT(x, __VA_ARGS__) // NOLINTNEXTLINE #define TEST_PRIMITIVE_CAT(x, ...) x##__VA_ARGS__ // NOLINTNEXTLINE #define TEST_CASE_REGISTER(...) \ static test::auto_register_test_case TEST_CAT(register_test_case_, __COUNTER__) = \ test::auto_register_test_case(#__VA_ARGS__, &__VA_ARGS__); // NOLINTNEXTLINE #define TEST_CASE(...) \ static void __VA_ARGS__(); \ TEST_CASE_REGISTER(__VA_ARGS__) \ static void __VA_ARGS__() #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wglobal-constructors" #endif #endif ROCm-AMDMIGraphX-46524e8/test/inline_module_test.cpp000066400000000000000000000525161510465702400221160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes(p, {migraphx::inline_module{}, migraphx::dead_code_elimination{}}); } TEST_CASE(cannot_inline_both) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", sd); std::vector one(sd.elements(), 1); std::vector two(sd.elements(), 2); auto* then_smod = p.create_module("then_smod"); auto l1 = then_smod->add_literal(migraphx::literal{sd, one}); auto r1 = then_smod->add_instruction(migraphx::make_op("add"), x, l1); then_smod->add_return({r1}); auto* else_smod = p.create_module("else_smod"); auto l2 = else_smod->add_literal(migraphx::literal{sd, two}); auto r2 = else_smod->add_instruction(migraphx::make_op("mul"), x, l2); else_smod->add_return({r2}); migraphx::shape s_cond{migraphx::shape::bool_type, {1}}; auto cond = mm->add_parameter("cond", s_cond); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_smod, else_smod}); mm->add_return({ret}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_program()); } TEST_CASE(cannot_inline_one) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape s{migraphx::shape::float_type, {5}}; auto cond = mm->add_parameter("cond", cond_s); auto x = mm->add_parameter("x", s); auto* then_mod = p.create_module("If_0_if"); std::vector data1 = {1, 2, 3, 4, 5}; auto l1 = then_mod->add_literal(migraphx::literal(s, data1)); then_mod->add_return({l1, x}); auto* else_mod = p.create_module("If_0_else"); std::vector data2 = {5, 4, 3, 2, 1}; auto l2 = else_mod->add_literal(migraphx::literal(s, data2)); auto s2 = else_mod->add_instruction(migraphx::make_op("add"), x, l2); else_mod->add_return({s2, l2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); mm->add_return({ret}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_program()); } TEST_CASE(inline_if_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; auto cond = mm->add_literal(migraphx::literal(sc, {1})); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector ones(s.elements(), 1.0f); auto l1 = mm->add_literal(s, ones); std::vector rand = {-1.26487, -2.42279, 0.990835, 1.63072, 0.812238, -0.174946}; auto l2 = mm->add_literal(s, rand); auto x = mm->add_parameter("x", s); auto sm = mm->add_instruction(migraphx::make_op("add"), l1, x); auto y = mm->add_parameter("y", s); auto* then_mod = p.create_module("If_5_if"); auto rt = then_mod->add_instruction(migraphx::make_op("add"), x, sm); then_mod->add_outline(s); then_mod->add_return({rt}); auto* else_mod = p.create_module("If_5_else"); auto re = else_mod->add_instruction(migraphx::make_op("mul"), y, l2); else_mod->add_return({re}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); return p; }; auto create_inline = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector ones(s.elements(), 1.0f); auto l1 = mm->add_literal(s, ones); std::vector rand = {-1.26487, -2.42279, 0.990835, 1.63072, 0.812238, -0.174946}; mm->add_literal(s, rand); auto x = mm->add_parameter("x", s); auto sm = mm->add_instruction(migraphx::make_op("add"), l1, x); mm->add_parameter("y", s); auto r = mm->add_instruction(migraphx::make_op("add"), x, sm); mm->add_return({r}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_inline()); } TEST_CASE(inline_else_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; auto cond = mm->add_literal(migraphx::literal(sc, {0})); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector ones(s.elements(), 1.0f); auto l1 = mm->add_literal(s, ones); std::vector rand = {-1.26487, -2.42279, 0.990835, 1.63072, 0.812238, -0.174946}; auto l2 = mm->add_literal(s, rand); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto* then_mod = p.create_module("If_5_if"); auto rt = then_mod->add_instruction(migraphx::make_op("add"), x, l1); then_mod->add_return({rt}); auto* else_mod = p.create_module("If_5_else"); else_mod->add_parameter("e", s); else_mod->add_literal(migraphx::literal(s, ones)); auto re = else_mod->add_instruction(migraphx::make_op("mul"), y, l2); else_mod->add_return({re}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); return p; }; auto create_inline = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector ones(s.elements(), 1.0f); mm->add_literal(s, ones); std::vector rand = {-1.26487, -2.42279, 0.990835, 1.63072, 0.812238, -0.174946}; auto l2 = mm->add_literal(s, rand); mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_parameter("e", s); auto r = mm->add_instruction(migraphx::make_op("mul"), y, l2); mm->add_return({r}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_inline()); } TEST_CASE(if_recursive_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; migraphx::shape ys{migraphx::shape::float_type, {3, 3}}; std::vector datax = {1, 2, 3, 4, 5, 6}; std::vector datay = {8, 7, 6, 5, 4, 3, 2, 1, 0}; auto lx = mm->add_literal(migraphx::literal(xs, datax)); auto ly = mm->add_literal(migraphx::literal(ys, datay)); auto cond = mm->add_literal(migraphx::literal(cond_s, {0})); auto x1 = mm->add_parameter("x1", xs); auto x2 = mm->add_parameter("x2", xs); auto y2 = mm->add_parameter("y2", ys); auto cond1 = mm->add_parameter("cond", cond_s); auto* then_mod = p.create_module("If_5_if"); auto l1 = then_mod->add_literal(migraphx::literal(ys, datay)); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), x1, lx); then_mod->add_return({a1, l1}); auto* then_mod1 = p.create_module("If_6_if"); auto l11 = then_mod1->add_literal(migraphx::literal(ys, datay)); auto a11 = then_mod1->add_instruction(migraphx::make_op("add"), x2, lx); then_mod1->add_return({a11, l11}); auto* else_mod1 = p.create_module("If_6_else"); auto l21 = else_mod1->add_literal(migraphx::literal(xs, datax)); auto a21 = else_mod1->add_instruction(migraphx::make_op("mul"), y2, ly); else_mod1->add_return({l21, a21}); auto* else_mod = p.create_module("If_5_else"); auto l2 = else_mod->add_literal(migraphx::literal(xs, datax)); auto a2 = else_mod->add_instruction(migraphx::make_op("if"), {cond1}, {then_mod1, else_mod1}); auto a3 = else_mod->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), a2); else_mod->add_return({l2, a3}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r}); return p; }; auto create_inline = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; migraphx::shape ys{migraphx::shape::float_type, {3, 3}}; std::vector datax = {1, 2, 3, 4, 5, 6}; std::vector datay = {8, 7, 6, 5, 4, 3, 2, 1, 0}; auto lx = mm->add_literal(migraphx::literal(xs, datax)); auto ly = mm->add_literal(migraphx::literal(ys, datay)); mm->add_parameter("x1", xs); auto x2 = mm->add_parameter("x2", xs); auto y2 = mm->add_parameter("y2", ys); auto cond1 = mm->add_parameter("cond", cond_s); auto* then_mod1 = p.create_module("If_6_if"); auto l11 = then_mod1->add_literal(migraphx::literal(ys, datay)); auto a11 = then_mod1->add_instruction(migraphx::make_op("add"), x2, lx); then_mod1->add_return({a11, l11}); auto* else_mod1 = p.create_module("If_6_else"); auto l21 = else_mod1->add_literal(migraphx::literal(xs, datax)); auto a21 = else_mod1->add_instruction(migraphx::make_op("mul"), y2, ly); else_mod1->add_return({l21, a21}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond1}, {then_mod1, else_mod1}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_inline()); } TEST_CASE(if_recursive_cond0_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; migraphx::shape ys{migraphx::shape::float_type, {3, 3}}; std::vector datax = {1, 2, 3, 4, 5, 6}; std::vector datay = {8, 7, 6, 5, 4, 3, 2, 1, 0}; auto lx = mm->add_literal(migraphx::literal(xs, datax)); auto ly = mm->add_literal(migraphx::literal(ys, datay)); auto cond = mm->add_literal(migraphx::literal(cond_s, {0})); auto x1 = mm->add_parameter("x1", xs); auto x2 = mm->add_parameter("x2", xs); auto y2 = mm->add_parameter("y2", ys); auto* then_mod = p.create_module("If_5_if"); auto l1 = then_mod->add_literal(migraphx::literal(ys, datay)); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), x1, lx); then_mod->add_return({a1, l1}); auto* then_mod1 = p.create_module("If_6_if"); auto l11 = then_mod1->add_literal(migraphx::literal(ys, datay)); auto a11 = then_mod1->add_instruction(migraphx::make_op("add"), x2, lx); then_mod1->add_return({a11, l11}); auto* else_mod1 = p.create_module("If_6_else"); auto l21 = else_mod1->add_literal(migraphx::literal(xs, datax)); auto a21 = else_mod1->add_instruction(migraphx::make_op("mul"), y2, ly); else_mod1->add_return({l21, a21}); auto* else_mod = p.create_module("If_5_else"); auto l2 = else_mod->add_literal(migraphx::literal(xs, datax)); auto a2 = else_mod->add_instruction(migraphx::make_op("if"), {cond}, {then_mod1, else_mod1}); auto a3 = else_mod->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), a2); else_mod->add_return({l2, a3}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r}); return p; }; auto create_inline = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; migraphx::shape ys{migraphx::shape::float_type, {3, 3}}; std::vector datax = {1, 2, 3, 4, 5, 6}; std::vector datay = {8, 7, 6, 5, 4, 3, 2, 1, 0}; mm->add_literal(migraphx::literal(xs, datax)); auto ly = mm->add_literal(migraphx::literal(ys, datay)); mm->add_parameter("x1", xs); mm->add_parameter("x2", xs); auto y2 = mm->add_parameter("y2", ys); auto m = mm->add_instruction(migraphx::make_op("mul"), y2, ly); mm->add_return({m}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_inline()); } TEST_CASE(inline_tuple_true_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; auto cond = mm->add_literal(migraphx::literal(sc, {1})); migraphx::shape sd{migraphx::shape::float_type, {1}}; auto l1 = mm->add_literal(migraphx::literal(sd, {1})); auto l2 = mm->add_literal(migraphx::literal(sd, {2})); auto l3 = mm->add_literal(migraphx::literal(sd, {3})); migraphx::shape sx{migraphx::shape::float_type, {1, 4}}; migraphx::shape sy{migraphx::shape::float_type, {3, 4}}; auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sy); auto* then_mod = p.create_module("If_6_if"); auto m1 = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l1); auto add0 = then_mod->add_instruction(migraphx::make_op("add"), x, m1); auto m2 = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l2); auto mul0 = then_mod->add_instruction(migraphx::make_op("mul"), y, m2); then_mod->add_return({add0, mul0}); auto* else_mod = p.create_module("If_6_else"); auto me1 = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l3); auto mul1 = else_mod->add_instruction(migraphx::make_op("mul"), x, me1); auto me2 = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l3); auto add1 = else_mod->add_instruction(migraphx::make_op("add"), y, me2); else_mod->add_return({mul1, add1}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r0, r1}); return p; }; auto create_inline = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {1}}; auto l1 = mm->add_literal(migraphx::literal(sd, {1})); auto l2 = mm->add_literal(migraphx::literal(sd, {2})); mm->add_literal(migraphx::literal(sd, {3})); migraphx::shape sx{migraphx::shape::float_type, {1, 4}}; migraphx::shape sy{migraphx::shape::float_type, {3, 4}}; auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sy); auto m1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l1); auto add = mm->add_instruction(migraphx::make_op("add"), x, m1); auto m2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l2); auto mul = mm->add_instruction(migraphx::make_op("mul"), y, m2); mm->add_return({add, mul}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_inline()); } TEST_CASE(inline_tuple_false_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; auto cond = mm->add_literal(migraphx::literal(sc, {0})); migraphx::shape sd{migraphx::shape::float_type, {1}}; auto l1 = mm->add_literal(migraphx::literal(sd, {1})); auto l2 = mm->add_literal(migraphx::literal(sd, {2})); auto l3 = mm->add_literal(migraphx::literal(sd, {3})); migraphx::shape sx{migraphx::shape::float_type, {1, 4}}; migraphx::shape sy{migraphx::shape::float_type, {3, 4}}; auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sy); auto* then_mod = p.create_module("If_6_if"); auto m1 = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l1); auto add0 = then_mod->add_instruction(migraphx::make_op("add"), x, m1); auto m2 = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l2); auto mul0 = then_mod->add_instruction(migraphx::make_op("mul"), y, m2); then_mod->add_return({add0, mul0}); auto* else_mod = p.create_module("If_6_else"); auto me1 = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l3); auto mul1 = else_mod->add_instruction(migraphx::make_op("mul"), x, me1); auto me2 = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l3); auto add1 = else_mod->add_instruction(migraphx::make_op("add"), y, me2); else_mod->add_return({mul1, add1}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r0, r1}); return p; }; auto create_inline = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; migraphx::shape sd{migraphx::shape::float_type, {1}}; mm->add_literal(migraphx::literal(sd, {1})); mm->add_literal(migraphx::literal(sd, {2})); auto l3 = mm->add_literal(migraphx::literal(sd, {3})); migraphx::shape sx{migraphx::shape::float_type, {1, 4}}; migraphx::shape sy{migraphx::shape::float_type, {3, 4}}; auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sy); auto m1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l3); auto mul = mm->add_instruction(migraphx::make_op("mul"), x, m1); auto m2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l3); auto add = mm->add_instruction(migraphx::make_op("add"), y, m2); mm->add_return({mul, add}); return p; }; auto p = create_program(); run_pass(p); EXPECT(p == create_inline()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/insert_pad_test.cpp000066400000000000000000000105521510465702400214150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes( m, {migraphx::normalize_ops{}, migraphx::insert_pad{}, migraphx::dead_code_elimination{}}); } static migraphx::instruction_ref create_im2col(migraphx::instruction_ref& l_img, size_t channels, migraphx::module& m) { size_t f[2] = {1, 1}; std::vector weights(channels * f[0] * f[1]); migraphx::shape s_weights{migraphx::shape::int32_type, {1, channels, f[0], f[1]}}; auto l_weights = m.add_literal(migraphx::literal{s_weights, weights}); return m.add_instruction( migraphx::make_op("im2col", {{"padding", {0, 0, 1, 1}}}), l_img, l_weights); } static migraphx::instruction_ref create_conv(migraphx::instruction_ref& l_img, size_t channels, migraphx::module& m, migraphx::op::padding_mode_t padding_mode = migraphx::op::padding_mode_t::default_) { migraphx::shape s_weights{migraphx::shape::int32_type, {4, channels, 3, 3}}; std::vector weights(4 * channels * 3 * 3); auto l_weights = m.add_literal(migraphx::literal{s_weights, weights}); return m.add_instruction( migraphx::make_op("convolution", {{"padding_mode", padding_mode}, {"padding", {0, 0, 1, 1}}}), l_img, l_weights); } TEST_CASE(rewrite_pad) { migraphx::module m; size_t img_dim[2] = {2, 2}; size_t channels = 1; std::vector input(channels * img_dim[0] * img_dim[1]); std::iota(input.begin(), input.end(), 0); migraphx::shape s_img{migraphx::shape::int32_type, {1, channels, img_dim[0], img_dim[1]}}; auto l_img = m.add_literal(migraphx::literal{s_img, input}); auto l0 = create_im2col(l_img, channels, m); auto l1 = create_conv(l_img, channels, m); auto l2 = m.add_instruction( migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 1, 1}}}), l_img); m.add_instruction(migraphx::make_op("identity"), l0, l1, l2); run_pass(m); EXPECT(std::any_of( m.begin(), m.end(), [](const migraphx::instruction& ins) { return ins.name() == "pad"; })); } TEST_CASE(rewrite_pad_symmetric) { migraphx::module m; size_t img_dim[2] = {2, 2}; size_t channels = 1; std::vector input(channels * img_dim[0] * img_dim[1]); std::iota(input.begin(), input.end(), 0); migraphx::shape s_img{migraphx::shape::int32_type, {1, channels, img_dim[0], img_dim[1]}}; auto l_img = m.add_literal(migraphx::literal{s_img, input}); m.add_instruction( migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {1, 1, 1, 1}}}), l_img); run_pass(m); EXPECT(std::none_of( m.begin(), m.end(), [](const migraphx::instruction& ins) { return ins.name() == "pad"; })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/instruction.cpp000066400000000000000000000435141510465702400206130ustar00rootroot00000000000000// // The MIT License (MIT) // // Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. // // 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. // #include #include #include #include "test.hpp" TEST_CASE(check_undefined) { migraphx::module m; auto und = m.add_instruction(migraphx::make_op("undefined")); auto cov = m.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), und); auto abs = m.add_instruction(migraphx::make_op("abs"), cov); migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; std::vector datax = {1, 2, 3, 4, 5, 6}; auto lit = m.add_literal(migraphx::literal(xs, datax)); auto mul = m.add_instruction(migraphx::make_op("mul"), lit, lit); EXPECT(und->is_undefined()); EXPECT(cov->is_undefined()); EXPECT(abs->is_undefined()); EXPECT(not lit->is_undefined()); EXPECT(not mul->is_undefined()); } TEST_CASE(check_replace_shape) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 2}}; auto input = m.add_parameter("x", s); auto reduce = m.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), input); auto abs = m.add_instruction(migraphx::make_op("abs"), reduce); auto sin = m.add_instruction(migraphx::make_op("sin"), reduce); auto add = m.add_instruction(migraphx::make_op("add"), abs, sin); reduce->replace(migraphx::make_op("reduce_sum", {{"axes", {1}}})); migraphx::shape r{migraphx::shape::float_type, {3, 1}}; EXPECT(reduce->get_shape() == r); EXPECT(abs->get_shape() == r); EXPECT(sin->get_shape() == r); EXPECT(add->get_shape() == r); } TEST_CASE(check_replace_dag) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 2}}; auto input = m.add_parameter("x", s); auto reduce = m.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), input); auto abs = m.add_instruction(migraphx::make_op("abs"), reduce); auto sin = m.add_instruction(migraphx::make_op("sin"), reduce); auto add = m.add_instruction(migraphx::make_op("add"), abs, sin); auto add2 = m.add_instruction(migraphx::make_op("add"), add, reduce); reduce->replace(migraphx::make_op("reduce_sum", {{"axes", {1}}})); migraphx::shape r{migraphx::shape::float_type, {3, 1}}; EXPECT(reduce->get_shape() == r); EXPECT(abs->get_shape() == r); EXPECT(sin->get_shape() == r); EXPECT(add->get_shape() == r); EXPECT(add2->get_shape() == r); } // Tests for the reaches function // // Linear graph: // // x --> relu --> tanh --> abs // TEST_CASE(reaches_direct_connection) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto relu = m.add_instruction(migraphx::make_op("relu"), x); auto tanh = m.add_instruction(migraphx::make_op("tanh"), relu); auto abs = m.add_instruction(migraphx::make_op("abs"), tanh); // Direct connections EXPECT(migraphx::reaches(x, relu, &m)); EXPECT(migraphx::reaches(relu, tanh, &m)); EXPECT(migraphx::reaches(tanh, abs, &m)); // Transitive connections EXPECT(migraphx::reaches(x, tanh, &m)); EXPECT(migraphx::reaches(x, abs, &m)); EXPECT(migraphx::reaches(relu, abs, &m)); // Same instruction EXPECT(migraphx::reaches(x, x, &m)); EXPECT(migraphx::reaches(abs, abs, &m)); } // // Branched graph: // // x y // \ / // \ / // v v // add // / \ // v v // relu1 relu2 // \ / // \ / // v v // concat // TEST_CASE(reaches_branched_connections) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto add = m.add_instruction(migraphx::make_op("add"), x, y); auto relu1 = m.add_instruction(migraphx::make_op("relu"), add); auto relu2 = m.add_instruction(migraphx::make_op("relu"), add); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), relu1, relu2); // Branch connections EXPECT(migraphx::reaches(x, add, &m)); EXPECT(migraphx::reaches(y, add, &m)); EXPECT(migraphx::reaches(add, relu1, &m)); EXPECT(migraphx::reaches(add, relu2, &m)); EXPECT(migraphx::reaches(relu1, concat, &m)); EXPECT(migraphx::reaches(relu2, concat, &m)); // Transitive connections EXPECT(migraphx::reaches(x, relu1, &m)); EXPECT(migraphx::reaches(x, relu2, &m)); EXPECT(migraphx::reaches(y, relu1, &m)); EXPECT(migraphx::reaches(y, relu2, &m)); EXPECT(migraphx::reaches(x, concat, &m)); EXPECT(migraphx::reaches(y, concat, &m)); // No connections EXPECT(not migraphx::reaches(relu1, relu2, &m)); } // // Complex diamond graph: // // x y // \ / // \ / // v v // add // / \ // v v // relu tanh // | | // v v // abs sin // \ / // \ / // vv // concat // TEST_CASE(reaches_complex_graph1) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto add = m.add_instruction(migraphx::make_op("add"), x, y); auto relu = m.add_instruction(migraphx::make_op("relu"), add); auto tanh = m.add_instruction(migraphx::make_op("tanh"), add); auto abs = m.add_instruction(migraphx::make_op("abs"), relu); auto sin = m.add_instruction(migraphx::make_op("sin"), tanh); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), abs, sin); // Test complex paths EXPECT(migraphx::reaches(x, concat, &m)); EXPECT(migraphx::reaches(y, concat, &m)); EXPECT(migraphx::reaches(add, concat, &m)); EXPECT(migraphx::reaches(relu, concat, &m)); EXPECT(migraphx::reaches(tanh, concat, &m)); EXPECT(migraphx::reaches(abs, concat, &m)); EXPECT(migraphx::reaches(sin, concat, &m)); // Test paths through different branches EXPECT(migraphx::reaches(x, abs, &m)); EXPECT(migraphx::reaches(y, sin, &m)); EXPECT(migraphx::reaches(add, abs, &m)); EXPECT(migraphx::reaches(add, sin, &m)); // Test non-existing paths EXPECT(not migraphx::reaches(relu, sin, &m)); EXPECT(not migraphx::reaches(relu, tanh, &m)); EXPECT(not migraphx::reaches(tanh, abs, &m)); EXPECT(not migraphx::reaches(abs, sin, &m)); } TEST_CASE(reaches_complex_graph2) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto add = m.add_instruction(migraphx::make_op("add"), x, y); auto relu = m.add_instruction(migraphx::make_op("relu"), add); auto tanh = m.add_instruction(migraphx::make_op("tanh"), add); auto abs = m.add_instruction(migraphx::make_op("abs"), relu); auto sin = m.add_instruction(migraphx::make_op("sin"), tanh); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), abs, sin); // Test complex paths EXPECT(migraphx::reaches(x, concat)); EXPECT(migraphx::reaches(y, concat)); EXPECT(migraphx::reaches(add, concat)); EXPECT(migraphx::reaches(relu, concat)); EXPECT(migraphx::reaches(tanh, concat)); EXPECT(migraphx::reaches(abs, concat)); EXPECT(migraphx::reaches(sin, concat)); // Test paths through different branches EXPECT(migraphx::reaches(x, abs)); EXPECT(migraphx::reaches(y, sin)); EXPECT(migraphx::reaches(add, abs)); EXPECT(migraphx::reaches(add, sin)); // Test non-existing paths EXPECT(not migraphx::reaches(relu, sin)); EXPECT(not migraphx::reaches(relu, tanh)); EXPECT(not migraphx::reaches(tanh, abs)); EXPECT(not migraphx::reaches(abs, sin)); } // Tests for the is_interdependent function // // Linear chain: // // x --> relu --> tanh --> abs // TEST_CASE(is_interdependent_simple) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto relu = m.add_instruction(migraphx::make_op("relu"), x); auto tanh = m.add_instruction(migraphx::make_op("tanh"), relu); auto abs = m.add_instruction(migraphx::make_op("abs"), tanh); // Sequential chain - these should be interdependent std::vector seq_chain = {x, relu, tanh, abs}; EXPECT(migraphx::is_interdependent(seq_chain, &m, m.begin())); // Subset of chain - also interdependent std::vector sub_chain = {x, relu, abs}; EXPECT(migraphx::is_interdependent(sub_chain, &m, m.begin())); // Single instruction is always interdependent std::vector single = {x}; EXPECT(migraphx::is_interdependent(single, &m, m.begin())); // Empty vector is also interdependent (vacuously true) std::vector empty = {}; EXPECT(migraphx::is_interdependent(empty, &m, m.begin())); } // // Branched Y graph: // // x y // \ / // \ / // v v // add // / \ // v v // relu tanh // TEST_CASE(is_interdependent_branched) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto add = m.add_instruction(migraphx::make_op("add"), x, y); auto relu = m.add_instruction(migraphx::make_op("relu"), add); auto tanh = m.add_instruction(migraphx::make_op("tanh"), add); // Interdependent branches std::vector interdep = {add, relu, tanh}; EXPECT(migraphx::is_interdependent(interdep, &m, m.begin())); // Independent parameters std::vector indep = {x, y}; EXPECT(not migraphx::is_interdependent(indep, &m, m.begin())); // Mixed dependent and independent std::vector mixed = {x, relu, tanh}; EXPECT(migraphx::is_interdependent(mixed, &m, m.begin())); } // // Complex graph with multiple paths: // // x y z // \ / \ / // \ / \ / // v v v v // add1 add2 // | | // v v // relu1 relu2 // \ / // \ / // v v // concat // TEST_CASE(is_interdependent_complex) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto z = m.add_parameter("z", s); auto add1 = m.add_instruction(migraphx::make_op("add"), x, y); auto add2 = m.add_instruction(migraphx::make_op("add"), y, z); auto relu1 = m.add_instruction(migraphx::make_op("relu"), add1); auto relu2 = m.add_instruction(migraphx::make_op("relu"), add2); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), relu1, relu2); // Complex interdependent set std::vector complex_dep = {y, add1, add2, relu1, relu2, concat}; EXPECT(migraphx::is_interdependent(complex_dep, &m, m.begin())); // Independent branches std::vector indep_branches = {add1, add2}; EXPECT(not migraphx::is_interdependent(indep_branches, &m, m.begin())); // Independent outputs std::vector indep_outputs = {relu1, relu2}; EXPECT(not migraphx::is_interdependent(indep_outputs, &m, m.begin())); } // // Long chain: // // x y // \ / // \ / // v // add --> relu1 --> relu2 --> ... --> relu9 // TEST_CASE(is_interdependent_large_set) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); // Create a chain of 10 instructions auto curr = m.add_instruction(migraphx::make_op("add"), x, y); std::vector chain = {curr}; for(int i = 0; i < 9; i++) { curr = m.add_instruction(migraphx::make_op("relu"), curr); chain.push_back(curr); } // Test with large chain EXPECT(migraphx::is_interdependent(chain, &m, m.begin())); // Take a subset that skips some elements std::vector subset = { chain[0], chain[2], chain[4], chain[6], chain[8]}; EXPECT(migraphx::is_interdependent(subset, &m, m.begin())); } // Tests for the find_instructions_between function // // Linear chain: // // x --> relu --> tanh --> abs // TEST_CASE(find_instructions_between_simple) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto relu = m.add_instruction(migraphx::make_op("relu"), x); auto tanh = m.add_instruction(migraphx::make_op("tanh"), relu); auto abs = m.add_instruction(migraphx::make_op("abs"), tanh); // Find instructions between x and abs auto result = migraphx::find_instructions_between(x, abs, &m); // Should include x, relu, tanh, abs EXPECT(result.count(x) == 1); EXPECT(result.count(relu) == 1); EXPECT(result.count(tanh) == 1); EXPECT(result.count(abs) == 1); EXPECT(result.size() == 4); // Find instructions between relu and abs auto result2 = migraphx::find_instructions_between(relu, abs, &m); // Should include relu, tanh, abs EXPECT(result2.count(x) == 0); EXPECT(result2.count(relu) == 1); EXPECT(result2.count(tanh) == 1); EXPECT(result2.count(abs) == 1); EXPECT(result2.size() == 3); } // // Branched Y graph: // // x y // \ / // \ / // v v // add // / \ // v v // relu tanh // \ / // \ / // v v // concat // TEST_CASE(find_instructions_between_branched) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto add = m.add_instruction(migraphx::make_op("add"), x, y); auto relu = m.add_instruction(migraphx::make_op("relu"), add); auto tanh = m.add_instruction(migraphx::make_op("tanh"), add); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), relu, tanh); // Find instructions between add and concat auto result = migraphx::find_instructions_between(add, concat, &m); // Should include add, relu, tanh, concat EXPECT(result.count(add) == 1); EXPECT(result.count(relu) == 1); EXPECT(result.count(tanh) == 1); EXPECT(result.count(concat) == 1); EXPECT(result.size() == 4); // Find instructions between x and concat auto result2 = migraphx::find_instructions_between(x, concat, &m); // Should include x, add, relu, tanh, concat but not y EXPECT(result2.count(x) == 1); EXPECT(result2.count(y) == 0); EXPECT(result2.count(add) == 1); EXPECT(result2.count(relu) == 1); EXPECT(result2.count(tanh) == 1); EXPECT(result2.count(concat) == 1); EXPECT(result2.size() == 5); } // // Complex diamond graph with multiple inputs: // // w x y z // \ / \ / // \ / \ / // v v // add1 add2 // \ / // \ / // \ / // \ / // v v // mul // / \ // v v // relu tanh // \ / // \ / // v v // concat // TEST_CASE(find_instructions_between_complex) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto w = m.add_parameter("w", s); auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto z = m.add_parameter("z", s); auto add1 = m.add_instruction(migraphx::make_op("add"), w, x); auto add2 = m.add_instruction(migraphx::make_op("add"), y, z); auto mul = m.add_instruction(migraphx::make_op("mul"), add1, add2); auto relu = m.add_instruction(migraphx::make_op("relu"), mul); auto tanh = m.add_instruction(migraphx::make_op("tanh"), mul); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), relu, tanh); // Find instructions between w and concat auto result = migraphx::find_instructions_between(w, concat, &m); // Should include w, add1, mul, relu, tanh, concat but not x, y, z, add2 EXPECT(result.count(w) == 1); EXPECT(result.count(add1) == 1); EXPECT(result.count(mul) == 1); EXPECT(result.count(relu) == 1); EXPECT(result.count(tanh) == 1); EXPECT(result.count(concat) == 1); EXPECT(result.size() == 6); EXPECT(result.count(x) == 0); EXPECT(result.count(y) == 0); EXPECT(result.count(z) == 0); EXPECT(result.count(add2) == 0); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/int4_test.cpp000066400000000000000000000071751510465702400201520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include namespace match = migraphx::match; TEST_CASE(int4_pass_test) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {16, 8, 6, 6}})); auto conv = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); m1.add_instruction(migraphx::make_op("relu"), conv); } migraphx::run_passes(m1, {migraphx::quantize_int4_pass{}}); auto chk_1 = match::name("quantizelinear")( match::output(match::name("pack_int4")(match::output(match::name( "unpack_int4")(match::output(match::name("dequantizelinear"))))))) .bind("q"); auto res = find_match(m1, chk_1); EXPECT(migraphx::contains(res.instructions, "q")); } TEST_CASE(int4_const_prop_test) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {16, 8, 6, 6}})); auto conv = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); m1.add_instruction(migraphx::make_op("relu"), conv); } migraphx::run_passes(m1, {migraphx::quantize_int4_pass{}, migraphx::propagate_constant{}, migraphx::dead_code_elimination{}}); auto chk_1 = match::name("pack_int4").bind("pack_int4"); auto res_1 = find_match(m1, chk_1); EXPECT(not migraphx::contains(res_1.instructions, "pack_int4")); auto chk_2 = match::name("unpack_int4").bind("unpack_int4"); auto res_2 = find_match(m1, chk_2); EXPECT(migraphx::contains(res_2.instructions, "unpack_int4")); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/iota_iterator.cpp000066400000000000000000000232431510465702400210740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include TEST_CASE(iota_iterator_basic) { // Test basic iota_iterator with identity function migraphx::iota_iterator begin{0}; migraphx::iota_iterator end{5}; // Check initial values EXPECT(*begin == 0); EXPECT(*(end - 5) == 0); // Check dereferencing EXPECT(*(begin + 1) == 1); EXPECT(*(end - 1) == 4); // Check distance EXPECT(end - begin == 5); // Collect values using the iterator std::vector result; std::copy(begin, end, std::back_inserter(result)); // Verify results std::vector expected = {0, 1, 2, 3, 4}; EXPECT(result == expected); } TEST_CASE(iota_iterator_custom_function) { // Test with a custom function that doubles the index auto double_it = [](std::ptrdiff_t i) { return i * 2; }; auto begin = migraphx::make_basic_iota_iterator(0, double_it); auto end = migraphx::make_basic_iota_iterator(5, double_it); // Check dereferencing EXPECT(*begin == 0); EXPECT(*(begin + 1) == 2); EXPECT(*(begin + 2) == 4); EXPECT(*(begin + 3) == 6); EXPECT(*(begin + 4) == 8); // Collect values using the iterator std::vector result; std::copy(begin, end, std::back_inserter(result)); // Verify results std::vector expected = {0, 2, 4, 6, 8}; EXPECT(result == expected); } TEST_CASE(iota_iterator_increment_decrement) { migraphx::iota_iterator it{3}; // Pre-increment auto& ref1 = ++it; EXPECT(*it == 4); EXPECT(&ref1 == &it); // Check that pre-increment returns reference // Post-increment auto it2 = it++; EXPECT(*it == 5); EXPECT(*it2 == 4); // Pre-decrement auto& ref2 = --it; EXPECT(*it == 4); EXPECT(&ref2 == &it); // Check that pre-decrement returns reference // Post-decrement auto it3 = it--; EXPECT(*it == 3); EXPECT(*it3 == 4); } TEST_CASE(iota_iterator_increment_decrement_temp) { auto dec = --migraphx::iota_iterator{3}; EXPECT(*dec == 2); // Test pre-decrement on temporary auto inc = ++migraphx::iota_iterator{3}; EXPECT(*inc == 4); // Test pre-increment on temporary } TEST_CASE(iota_iterator_compound_assignment) { migraphx::iota_iterator it{5}; // += operator auto& ref1 = it += 3; EXPECT(*it == 8); EXPECT(&ref1 == &it); // Check that += returns reference // -= operator auto& ref2 = it -= 5; EXPECT(*it == 3); EXPECT(&ref2 == &it); // Check that -= returns reference } TEST_CASE(iota_iterator_arithmetic) { migraphx::iota_iterator it{5}; // Addition (it + n) auto it1 = it + 3; EXPECT(*it1 == 8); EXPECT(*it == 5); // Original unchanged // Addition (n + it) auto it2 = 2 + it; EXPECT(*it2 == 7); EXPECT(*it == 5); // Original unchanged // Subtraction (it - n) auto it3 = it - 2; EXPECT(*it3 == 3); EXPECT(*it == 5); // Original unchanged // Difference (it1 - it2) EXPECT(it1 - it2 == 1); EXPECT(it2 - it1 == -1); EXPECT(it1 - it == 3); } TEST_CASE(iota_iterator_comparisons) { migraphx::iota_iterator it1{5}; migraphx::iota_iterator it2{5}; migraphx::iota_iterator it3{8}; // Equality EXPECT(it1 == it2); EXPECT(not(it1 == it3)); // Inequality EXPECT(it1 != it3); EXPECT(not(it1 != it2)); // Less than EXPECT(it1 < it3); EXPECT(not(it3 < it1)); EXPECT(not(it1 < it2)); // Greater than EXPECT(it3 > it1); EXPECT(not(it1 > it3)); EXPECT(not(it1 > it2)); // Less than or equal EXPECT(it1 <= it3); EXPECT(it1 <= it2); EXPECT(not(it3 <= it1)); // Greater than or equal EXPECT(it3 >= it1); EXPECT(it1 >= it2); EXPECT(not(it1 >= it3)); } TEST_CASE(iota_iterator_indexing) { migraphx::iota_iterator it{10}; // Test subscript operator EXPECT(it[0] == 10); EXPECT(it[1] == 11); EXPECT(it[5] == 15); EXPECT(it[-2] == 8); } TEST_CASE(iota_iterator_indexing_custom_function) { std::array data; std::iota(data.begin(), data.end(), 0); auto it = migraphx::make_basic_iota_iterator(10, [&](auto i) -> const int& { return data[i]; }); // Test subscript operator EXPECT(it[0] == 10); EXPECT(it[1] == 11); EXPECT(it[5] == 15); EXPECT(it[-2] == 8); } TEST_CASE(iota_iterator_custom_type) { // Using a custom struct as the function result struct point { int x; int y; }; auto point_maker = [](std::ptrdiff_t i) -> point { return {static_cast(i), static_cast(i * i)}; }; auto begin = migraphx::make_basic_iota_iterator(0, point_maker); // Test dereferencing and arrow operator EXPECT((*begin).x == 0); EXPECT((*begin).y == 0); EXPECT(begin->x == 0); EXPECT(begin->y == 0); // Advance and check auto it = begin + 3; EXPECT(it->x == 3); EXPECT(it->y == 9); // Check subscript EXPECT(begin[4].x == 4); EXPECT(begin[4].y == 16); } TEST_CASE(iota_iterator_algorithm_compatibility) { // Test that iota_iterator works with standard algorithms migraphx::iota_iterator begin{0}; migraphx::iota_iterator end{10}; // Test with std::accumulate auto sum = std::accumulate(begin, end, std::ptrdiff_t{0}); EXPECT(sum == 45); // Sum of 0-9 is 45 // Test with std::transform std::vector result(10); std::transform(begin, end, result.begin(), [](int i) { return i * i; }); std::vector expected = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}; EXPECT(result == expected); // Test with std::find auto found = std::find_if(begin, end, [](int i) { return i > 5; }); EXPECT(*found == 6); EXPECT(found - begin == 6); // Test with std::sort (using a vector of iota_iterators) std::vector iters; for(int i = 9; i >= 0; --i) iters.push_back(migraphx::iota_iterator{i}); std::sort(iters.begin(), iters.end()); for(size_t i = 0; i < iters.size(); ++i) EXPECT(*iters[i] == static_cast(i)); } TEST_CASE(iota_iterator_large_values) { // Define values larger than uint32_t max const std::ptrdiff_t uint32_max = std::numeric_limits::max(); const std::ptrdiff_t base_val = uint32_max + 10; const std::ptrdiff_t offset1 = 500; const std::ptrdiff_t offset2 = 1000; // Test with + operator (it + n) { migraphx::iota_iterator it{base_val}; auto it_plus = it + offset1; EXPECT(*it_plus == base_val + offset1); EXPECT(*it == base_val); // Original unchanged } // Test with + operator (n + it) { migraphx::iota_iterator it{base_val}; auto it_plus = offset1 + it; EXPECT(*it_plus == base_val + offset1); EXPECT(*it == base_val); // Original unchanged } // Test with - operator { migraphx::iota_iterator it{base_val + offset2}; auto it_minus = it - offset1; EXPECT(*it_minus == base_val + offset2 - offset1); EXPECT(*it == base_val + offset2); // Original unchanged } // Test with += operator { migraphx::iota_iterator it{base_val}; it += offset2; EXPECT(*it == base_val + offset2); } // Test with -= operator { migraphx::iota_iterator it{base_val + offset2}; it -= offset1; EXPECT(*it == base_val + offset2 - offset1); } // Test with [] operator { migraphx::iota_iterator it{base_val}; EXPECT(it[0] == base_val); EXPECT(it[offset1] == base_val + offset1); EXPECT(it[-offset1] == base_val - offset1); } // Test iterator difference { migraphx::iota_iterator it1{base_val}; migraphx::iota_iterator it2{base_val + offset2}; EXPECT(it2 - it1 == offset2); EXPECT(it1 - it2 == -offset2); } // Test with larger than int32_t offsets const std::ptrdiff_t large_offset = static_cast(3) * uint32_max; // Test with + operator migraphx::iota_iterator it_large{1}; auto it_large_plus = it_large + large_offset; EXPECT(*it_large_plus == 1 + large_offset); // Test with += operator migraphx::iota_iterator it_large2{1}; it_large2 += large_offset; EXPECT(*it_large2 == 1 + large_offset); // Test with [] operator EXPECT(it_large[large_offset] == 1 + large_offset); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/jit.cpp000066400000000000000000000076201510465702400170160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include // NOLINTNEXTLINE const std::string_view add_42_src = R"migraphx( EXPORT extern "C" int add(int x) { return x+42; } )migraphx"; // NOLINTNEXTLINE const std::string_view preamble = R"migraphx( #include )migraphx"; template static std::function compile_function(std::string_view src, const std::string& symbol_name) { migraphx::src_compiler compiler; compiler.flags.emplace_back("-std=c++14"); #ifndef _WIN32 compiler.flags.emplace_back("-fPIC"); compiler.flags.emplace_back("-DEXPORT=\"\""); #else compiler.flags.emplace_back("-DEXPORT=__declspec(dllexport)"); #endif compiler.flags.emplace_back("-shared"); compiler.output = migraphx::make_shared_object_filename("simple"); migraphx::src_file f{"main.cpp", src}; auto image = compiler.compile({f}); return migraphx::dynamic_loader{image}.get_function(symbol_name); } template static std::function compile_module(const migraphx::module& m) { migraphx::cpp_generator g; g.fmap([](auto&& name) { return "std::" + name; }); g.create_function(g.generate_module(m).set_attributes({"EXPORT extern \"C\""})); return compile_function(g.str().insert(0, preamble), m.name()); } TEST_CASE(simple_run) { auto f = compile_function(add_42_src, "add"); EXPECT(f(8) == 50); EXPECT(f(10) == 52); } TEST_CASE(generate_module) { migraphx::module m("foo"); auto x = m.add_parameter("x", migraphx::shape::float_type); auto y = m.add_parameter("y", migraphx::shape::float_type); auto sum = m.add_instruction(migraphx::make_op("add"), x, y); m.add_instruction(migraphx::make_op("sqrt"), sum); auto f = compile_module(m); EXPECT(test::within_abs(f(2, 2), 2)); EXPECT(test::within_abs(f(10, 6), 4)); EXPECT(test::within_abs(f(1, 2), std::sqrt(3))); } TEST_CASE(generate_module_with_literals) { migraphx::module m("foo"); auto x = m.add_parameter("x", migraphx::shape::float_type); auto y = m.add_parameter("y", migraphx::shape::float_type); auto z = m.add_literal(1.f); auto sum1 = m.add_instruction(migraphx::make_op("add"), x, z); auto sum2 = m.add_instruction(migraphx::make_op("add"), sum1, y); m.add_instruction(migraphx::make_op("sqrt"), sum2); auto f = compile_module(m); EXPECT(test::within_abs(f(1, 2), 2)); EXPECT(test::within_abs(f(9, 6), 4)); EXPECT(test::within_abs(f(0, 2), std::sqrt(3))); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/json_test.cpp000066400000000000000000000172551510465702400202450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(null_value) { migraphx::value v; auto json_str = migraphx::to_json_string(v); EXPECT(json_str == "null"); } TEST_CASE(null_value_rev) { std::string json_str = "null"; migraphx::value v = migraphx::from_json_string(json_str); migraphx::value ev; EXPECT(v == ev); } TEST_CASE(null_array) { migraphx::value v; migraphx::value arr = {v, v}; auto json_str = migraphx::to_json_string(arr); EXPECT(json_str == "[null,null]"); } TEST_CASE(null_array_rev) { std::string json_str = "[null,null]"; migraphx::value v = migraphx::from_json_string(json_str); migraphx::value e; migraphx::value ev = {e, e}; EXPECT(ev == v); } TEST_CASE(empty_object1) { migraphx::value val = migraphx::from_json_string("{}"); EXPECT(val == migraphx::value::object{}); EXPECT(migraphx::to_json_string(migraphx::value::object{}) == "{}"); } TEST_CASE(empty_array1) { migraphx::value val = migraphx::from_json_string("[]"); EXPECT(val == migraphx::value::array{}); EXPECT(migraphx::to_json_string(migraphx::value::array{}) == "[]"); } TEST_CASE(int_value) { migraphx::value v = -1; std::string json_str = migraphx::to_json_string(v); EXPECT(json_str == "-1"); } TEST_CASE(int_value_rev) { std::string json_str = "-1"; migraphx::value v = migraphx::from_json_string(json_str); migraphx::value ev = -1; EXPECT(v == ev); } TEST_CASE(unsigned_value) { migraphx::value v = 1; std::string json_str = migraphx::to_json_string(v); EXPECT(json_str == "1"); } TEST_CASE(unsigned_value_rev) { std::string json_str = "1"; migraphx::value v = migraphx::from_json_string(json_str); EXPECT(v.is_uint64()); EXPECT(v.get_uint64() == 1); } TEST_CASE(float_value) { migraphx::value v = 1.5; std::string json_str = migraphx::to_json_string(v); EXPECT(json_str == "1.5"); } TEST_CASE(float_value_rev) { std::string json_str = "1.5"; migraphx::value v = migraphx::from_json_string(json_str); migraphx::value ev = 1.5; EXPECT(v == ev); } TEST_CASE(array_value) { migraphx::value v = {1, 2}; std::string json_str = migraphx::to_json_string(v); EXPECT(json_str == "[1,2]"); } TEST_CASE(array_value_rev) { std::string json_str = "[1,2]"; migraphx::value v = migraphx::from_json_string(json_str); EXPECT(v.is_array()); EXPECT(v.size() == 2); EXPECT(v[0].get_uint64() == 1); EXPECT(v[1].get_uint64() == 2); } TEST_CASE(object_value) { migraphx::value v = {{"a", 1.2}, {"b", true}}; std::string json_str = migraphx::to_json_string(v); EXPECT(json_str == "{\"a\":1.2,\"b\":true}"); } TEST_CASE(object_value_rev) { std::string json_str = R"({"a":1.2,"b":true})"; migraphx::value v = migraphx::from_json_string(json_str); migraphx::value ev = {{"a", 1.2}, {"b", true}}; EXPECT(v == ev); } TEST_CASE(null_object) { migraphx::value v; migraphx::value v1 = {{"a", v}}; std::string json_str = migraphx::to_json_string(v1); EXPECT(json_str == "{\"a\":null}"); } TEST_CASE(null_object_rev) { std::string json_str = R"({"a":null})"; migraphx::value eo = migraphx::from_json_string(json_str); migraphx::value v; migraphx::value ev = {{"a", v}}; EXPECT(eo == ev); } TEST_CASE(string_value) { migraphx::value v = "string_test"; std::string json_str = migraphx::to_json_string(v); EXPECT(json_str == "\"string_test\""); } TEST_CASE(string_value_rev) { std::string json_str = "\"string_test\""; migraphx::value v = migraphx::from_json_string(json_str); migraphx::value ev = "string_test"; EXPECT(v == ev); } TEST_CASE(array_of_objects) { migraphx::value obj1 = {"key1", uint64_t{1}}; migraphx::value obj2 = {"key2", uint64_t{2}}; migraphx::value arr = {obj1, obj2}; std::string json_str = migraphx::to_json_string(arr); EXPECT(json_str == "{\"key1\":1,\"key2\":2}"); } TEST_CASE(array_of_objects_rev) { std::string json_str = R"({"key1":1,"key2":2})"; migraphx::value v = migraphx::from_json_string(json_str); migraphx::value obj1 = {"key1", uint64_t{1}}; migraphx::value obj2 = {"key2", uint64_t{2}}; migraphx::value arr = {obj1, obj2}; EXPECT(arr == v); } TEST_CASE(object_of_array) { migraphx::value obj1 = {"key1", 1}; migraphx::value obj2 = {"key2", 2}; migraphx::value obj; obj["key"] = {obj1, obj2}; std::string json_str = migraphx::to_json_string(obj); EXPECT(json_str == "{\"key\":{\"key1\":1,\"key2\":2}}"); } TEST_CASE(object_of_array_rev) { std::string json_str = R"({"key":{"key1":1,"key2":2}})"; migraphx::value v = migraphx::from_json_string(json_str); migraphx::value obj1 = {"key1", uint64_t{1}}; migraphx::value obj2 = {"key2", uint64_t{2}}; migraphx::value obj; obj["key"] = {obj1, obj2}; EXPECT(v == obj); } TEST_CASE(shape_value) { migraphx::shape s{migraphx::shape::int32_type, {2, 3, 4, 5}}; migraphx::value val = migraphx::to_value(s); std::string json_str = migraphx::to_json_string(val); migraphx::value val_rev = migraphx::from_json_string(json_str); migraphx::shape s_rev; migraphx::from_value(val_rev, s_rev); EXPECT(s == s_rev); } TEST_CASE(argument_value) { migraphx::shape s{migraphx::shape::int32_type, {2, 3, 4, 5}}; std::vector data(s.elements()); std::iota(data.begin(), data.end(), 1); migraphx::argument argu = migraphx::argument(s, data.data()); migraphx::value val = migraphx::to_value(argu); std::string json_str = migraphx::to_json_string(val); migraphx::value val_rev = migraphx::from_json_string(json_str); migraphx::argument argu_rev; migraphx::from_value(val_rev, argu_rev); EXPECT(argu == argu_rev); } TEST_CASE(literal_value) { migraphx::shape s{migraphx::shape::int32_type, {2, 3, 4, 5}}; std::vector data(s.elements()); std::iota(data.begin(), data.end(), 1); migraphx::literal l = migraphx::literal(s, data); migraphx::value val = migraphx::to_value(l); std::string json_str = migraphx::to_json_string(val); migraphx::value val_rev = migraphx::from_json_string(json_str); migraphx::literal l_rev; migraphx::from_value(val_rev, l_rev); EXPECT(l == l_rev); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/layout_convolution.cpp000066400000000000000000000471171510465702400222110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m, migraphx::layout_convolution lc = {}) { migraphx::run_passes(m, {lc, migraphx::dead_code_elimination{}}); } static migraphx::operation layout(std::vector permutation = {0, 1, 2, 3}) { return migraphx::make_op("layout", {{"permutation", permutation}}); } static migraphx::instruction_ref add_layout_nhwc(migraphx::module& m, migraphx::instruction_ref ins) { return m.add_instruction(layout({0, 2, 3, 1}), ins); } static migraphx::instruction_ref add_layout_nchw(migraphx::module& m, migraphx::instruction_ref ins) { return m.add_instruction(layout({0, 1, 2, 3}), ins); } TEST_CASE(auto_conv_nchw) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {16, 8, 3, 3}})); auto conv = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto relu = m1.add_instruction(migraphx::make_op("relu"), conv); m1.add_return({relu}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(auto_conv_nhwc) { auto transpose = migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 16, 16, 8}}); auto xtranspose = m1.add_instruction(transpose, x); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {16, 3, 3, 8}})); auto wtranspose = m1.add_instruction(transpose, w); auto conv = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), xtranspose, wtranspose); auto relu = m1.add_instruction(migraphx::make_op("relu"), conv); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {1, 16, 16, 8}}); auto xtranspose = add_layout_nchw(m2, m2.add_instruction(transpose, x)); auto w = m2.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {16, 3, 3, 8}})); auto wtranspose = add_layout_nchw(m2, m2.add_instruction(transpose, w)); auto conv = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), xtranspose, wtranspose); auto relu = add_layout_nhwc(m2, m2.add_instruction(migraphx::make_op("relu"), conv)); m2.add_return({relu}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(auto_conv_mixed) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {3, 3, 16, 8}})); auto wtranspose = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 3, 0, 1}}}), w); auto conv = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, wtranspose); auto relu = m1.add_instruction(migraphx::make_op("relu"), conv); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto w = m2.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {3, 3, 16, 8}})); auto wtranspose = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 3, 0, 1}}}), w); auto wlayout = m2.add_instruction( migraphx::make_op("layout", {{"permutation", {0, 1, 2, 3}}}), wtranspose); auto conv = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, wlayout); auto relu = m2.add_instruction(migraphx::make_op("relu"), conv); m2.add_return({relu}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(auto_quant_conv_mixed) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int8_type, {1, 8, 16, 16}}); auto w = m1.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {3, 3, 16, 8}})); auto wtranspose = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 3, 0, 1}}}), w); auto conv = m1.add_instruction( migraphx::make_op("quant_convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, wtranspose); auto relu = m1.add_instruction(migraphx::make_op("relu"), conv); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int8_type, {1, 8, 16, 16}}); auto w = m2.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {3, 3, 16, 8}})); auto wtranspose = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 3, 0, 1}}}), w); auto wlayout = m2.add_instruction( migraphx::make_op("layout", {{"permutation", {0, 1, 2, 3}}}), wtranspose); auto conv = m2.add_instruction( migraphx::make_op("quant_convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, wlayout); auto relu = m2.add_instruction(migraphx::make_op("relu"), conv); m2.add_return({relu}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(nhwc_conv_relu) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {16, 8, 3, 3}})); auto conv = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); m1.add_instruction(migraphx::make_op("relu"), conv); } run_pass(m1, {.channels_last = true}); migraphx::module m2; { auto x = add_layout_nhwc( m2, m2.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}})); auto w = add_layout_nhwc(m2, m2.add_literal(migraphx::generate_literal( {migraphx::shape::float_type, {16, 8, 3, 3}}))); auto conv = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto relu = m2.add_instruction(migraphx::make_op("relu"), conv); m2.add_instruction(layout(), relu); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(nhwc_conv_add) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {16, 8, 3, 3}})); auto y = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {16}})); auto conv = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto b = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv->get_shape().lens()}}), y); m1.add_instruction(migraphx::make_op("add"), conv, b); } run_pass(m1, {.channels_last = true}); migraphx::module m2; { auto x = add_layout_nhwc( m2, m2.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}})); auto w = add_layout_nhwc(m2, m2.add_literal(migraphx::generate_literal( {migraphx::shape::float_type, {16, 8, 3, 3}}))); auto y = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {16}})); auto conv = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv->get_shape().lens()}}), y); auto add = m2.add_instruction(migraphx::make_op("add"), conv, b); m2.add_instruction(layout(), add); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(nhwc_quant_conv_add) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int8_type, {1, 8, 16, 16}}); auto w = m1.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {16, 8, 3, 3}})); auto y = m1.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {16}})); auto conv = m1.add_instruction( migraphx::make_op("quant_convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto b = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv->get_shape().lens()}}), y); m1.add_instruction(migraphx::make_op("add"), conv, b); } run_pass(m1, {.channels_last = true}); migraphx::module m2; { auto x = add_layout_nhwc( m2, m2.add_parameter("x", {migraphx::shape::int8_type, {1, 8, 16, 16}})); auto w = add_layout_nhwc(m2, m2.add_literal(migraphx::generate_literal( {migraphx::shape::int8_type, {16, 8, 3, 3}}))); auto y = m2.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {16}})); auto conv = m2.add_instruction( migraphx::make_op("quant_convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv->get_shape().lens()}}), y); auto add = m2.add_instruction(migraphx::make_op("add"), conv, b); m2.add_instruction(layout(), add); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(nhwc_conv_conv) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 2048, 7, 7}}); auto w1 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {512, 2048, 1, 1}})); auto conv1 = m1.add_instruction(migraphx::make_op("convolution"), x, w1); auto y1 = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {512}})); auto b1 = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv1->get_shape().lens()}}), y1); auto add1 = m1.add_instruction(migraphx::make_op("add"), conv1, b1); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), add1); auto w2 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {512, 512, 3, 3}})); auto conv2 = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), relu1, w2); auto y2 = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {512}})); auto b2 = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv1->get_shape().lens()}}), y2); auto add2 = m1.add_instruction(migraphx::make_op("add"), conv2, b2); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), add2); m1.add_return({relu2}); } run_pass(m1, {.channels_last = true}); migraphx::module m2; { auto x = add_layout_nhwc( m2, m2.add_parameter("x", {migraphx::shape::float_type, {1, 2048, 7, 7}})); auto w1 = add_layout_nhwc(m2, m2.add_literal(migraphx::generate_literal( {migraphx::shape::float_type, {512, 2048, 1, 1}}))); auto conv1 = m2.add_instruction(migraphx::make_op("convolution"), x, w1); auto y1 = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {512}})); auto b1 = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv1->get_shape().lens()}}), y1); auto add1 = m2.add_instruction(migraphx::make_op("add"), conv1, b1); auto relu1 = m2.add_instruction(migraphx::make_op("relu"), add1); auto w2 = add_layout_nhwc(m2, m2.add_literal(migraphx::generate_literal( {migraphx::shape::float_type, {512, 512, 3, 3}}))); auto conv2 = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), relu1, w2); auto y2 = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {512}})); auto b2 = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv1->get_shape().lens()}}), y2); auto add2 = m2.add_instruction(migraphx::make_op("add"), conv2, b2); auto relu2 = m2.add_instruction(migraphx::make_op("relu"), add2); auto relu_layout = m2.add_instruction(layout(), relu2); m2.add_return({relu_layout}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(nhwc_conv_reduce) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 2048, 7, 7}}); auto w1 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {512, 2048, 1, 1}})); auto conv1 = m1.add_instruction(migraphx::make_op("convolution"), x, w1); auto y1 = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {512}})); auto b1 = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv1->get_shape().lens()}}), y1); auto add1 = m1.add_instruction(migraphx::make_op("add"), conv1, b1); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), add1); auto reduce = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), relu1); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), reduce); m1.add_return({squeeze}); } run_pass(m1, {.channels_last = true}); migraphx::module m2; { auto x = add_layout_nhwc( m2, m2.add_parameter("x", {migraphx::shape::float_type, {1, 2048, 7, 7}})); auto w1 = add_layout_nhwc(m2, m2.add_literal(migraphx::generate_literal( {migraphx::shape::float_type, {512, 2048, 1, 1}}))); auto conv1 = m2.add_instruction(migraphx::make_op("convolution"), x, w1); auto y1 = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {512}})); auto b1 = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv1->get_shape().lens()}}), y1); auto add1 = m2.add_instruction(migraphx::make_op("add"), conv1, b1); auto relu1 = m2.add_instruction(migraphx::make_op("relu"), add1); auto reduce = m2.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), relu1); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), reduce); m2.add_return({squeeze}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(nhwc_group_conv) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {64, 96, 80, 80}}); auto w1 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {64, 96, 1, 1}})); auto w2 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {64, 1, 3, 3}})); auto conv1 = m1.add_instruction(migraphx::make_op("convolution"), x, w1); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), conv1); auto conv2 = m1.add_instruction(migraphx::make_op("convolution", {{"group", 64}}), relu1, w2); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), conv2); m1.add_return({relu2}); } run_pass(m1, {.channels_last = true}); migraphx::module m2; { auto x = add_layout_nhwc( m2, m2.add_parameter("x", {migraphx::shape::float_type, {64, 96, 80, 80}})); auto w1 = add_layout_nhwc(m2, m2.add_literal(migraphx::generate_literal( {migraphx::shape::float_type, {64, 96, 1, 1}}))); auto w2 = add_layout_nchw(m2, m2.add_literal(migraphx::generate_literal( {migraphx::shape::float_type, {64, 1, 3, 3}}))); auto conv1 = m2.add_instruction(migraphx::make_op("convolution"), x, w1); auto relu1 = add_layout_nhwc(m2, m2.add_instruction(migraphx::make_op("relu"), conv1)); auto conv2 = m2.add_instruction(migraphx::make_op("convolution", {{"group", 64}}), relu1, w2); auto relu2 = add_layout_nchw(m2, m2.add_instruction(migraphx::make_op("relu"), conv2)); m2.add_return({relu2}); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/literal_test.cpp000066400000000000000000000123231510465702400207170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" TEST_CASE(literal_test) { EXPECT(migraphx::literal{1} == migraphx::literal{1}); EXPECT(migraphx::literal{1} != migraphx::literal{2}); EXPECT(migraphx::literal{} == migraphx::literal{}); EXPECT(migraphx::literal{} != migraphx::literal{2}); migraphx::literal l1{1}; migraphx::literal l2 = l1; // NOLINT EXPECT(l1 == l2); EXPECT(l1.at(0) == 1); EXPECT(not l1.empty()); EXPECT(not l2.empty()); migraphx::literal l3{}; migraphx::literal l4{}; EXPECT(l3 == l4); EXPECT(l3.empty()); EXPECT(l4.empty()); } TEST_CASE(literal_nstd_shape_vector) { migraphx::shape nstd_shape{migraphx::shape::float_type, {1, 3, 2, 2}, {12, 1, 6, 3}}; std::vector data(12); std::iota(data.begin(), data.end(), 0); auto l0 = migraphx::literal{nstd_shape, data}; // check data buffer is read in correctly std::vector expected_buffer = {0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11}; const auto* start = reinterpret_cast(l0.data()); std::vector l0_data{start, start + 12}; EXPECT(l0_data == expected_buffer); // check that using visit() (that uses a tensor view) gives data in correct order std::vector results_vector(12); l0.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(results_vector == data); } TEST_CASE(literal_os1) { migraphx::literal l{1}; std::stringstream ss; ss << l; EXPECT(ss.str() == "1"); } TEST_CASE(literal_os2) { migraphx::literal l{}; std::stringstream ss; ss << l; EXPECT(ss.str().empty()); } TEST_CASE(literal_os3) { migraphx::shape s{migraphx::shape::int64_type, {3}}; migraphx::literal l{s, {1, 2, 3}}; std::stringstream ss; ss << l; EXPECT(ss.str() == "1, 2, 3"); } TEST_CASE(literal_visit_at) { migraphx::literal x{1}; bool visited = false; x.visit_at([&](int i) { visited = true; EXPECT(i == 1); }); EXPECT(visited); } TEST_CASE(literal_visit) { migraphx::literal x{1}; migraphx::literal y{1}; bool visited = false; x.visit([&](auto i) { y.visit([&](auto j) { visited = true; EXPECT(i == j); }); }); EXPECT(visited); } TEST_CASE(literal_visit_all) { migraphx::literal x{1}; migraphx::literal y{1}; bool visited = false; migraphx::visit_all(x, y)([&](auto i, auto j) { visited = true; EXPECT(i == j); }); EXPECT(visited); } TEST_CASE(literal_visit_mismatch_shape) { migraphx::literal x{1}; migraphx::shape s{migraphx::shape::int64_type, {3}}; migraphx::literal y{s, {1, 2, 3}}; bool visited = false; x.visit([&](auto i) { y.visit([&](auto j) { visited = true; EXPECT(i != j); }); }); EXPECT(visited); } TEST_CASE(literal_visit_all_mismatch_type) { migraphx::shape s1{migraphx::shape::int32_type, {1}}; migraphx::literal x{s1, {1}}; migraphx::shape s2{migraphx::shape::int8_type, {1}}; migraphx::literal y{s2, {1}}; EXPECT( test::throws([&] { migraphx::visit_all(x, y)([&](auto, auto) {}); })); } TEST_CASE(literal_visit_empty) { migraphx::literal x{}; EXPECT(test::throws([&] { x.visit([](auto) {}); })); EXPECT(test::throws([&] { x.visit_at([](auto) {}); })); } TEST_CASE(value_literal) { migraphx::shape s{migraphx::shape::int64_type, {3}}; migraphx::literal l1{s, {1, 2, 3}}; auto v1 = migraphx::to_value(l1); migraphx::literal l2{1}; auto v2 = migraphx::to_value(l2); EXPECT(v1 != v2); auto l3 = migraphx::from_value(v1); EXPECT(l3 == l1); auto l4 = migraphx::from_value(v2); EXPECT(l4 == l2); } TEST_CASE(literal_to_string_float_precision) { migraphx::literal x{126.99993142003703f}; EXPECT(x.to_string() != "127"); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/main.cpp000066400000000000000000000023731510465702400171540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "test.hpp" int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/marker.cpp000066400000000000000000000055421510465702400175120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include "test.hpp" struct mock_marker { std::shared_ptr ss = std::make_shared(); void mark_start(migraphx::instruction_ref ins_ref) { std::string text = "Mock marker instruction start:" + ins_ref->name(); (*ss) << text; } void mark_stop(migraphx::instruction_ref) { std::string text = "Mock marker instruction stop."; (*ss) << text; } void mark_start(const migraphx::program&) { std::string text = "Mock marker program start."; (*ss) << text; } void mark_stop(const migraphx::program&) { std::string text = "Mock marker program stop."; (*ss) << text; } }; TEST_CASE(marker) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); p.compile(migraphx::make_target("ref")); mock_marker temp_marker; p.mark({}, temp_marker); std::string output = temp_marker.ss->str(); EXPECT(migraphx::contains(output, "Mock marker instruction start:@literal")); EXPECT(migraphx::contains(output, "Mock marker instruction start:ref::op")); EXPECT(migraphx::contains(output, "Mock marker instruction stop.")); EXPECT(migraphx::contains(output, "Mock marker program start.")); EXPECT(migraphx::contains(output, "Mock marker program stop.")); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/matcher.cpp000066400000000000000000001172611510465702400176560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include namespace match = migraphx::match; MIGRAPHX_PRED_MATCHER(throws, migraphx::instruction_ref) { MIGRAPHX_THROW("Matcher throws"); } [[maybe_unused]] static void match1() { migraphx::module mm; auto l = mm.add_literal(1); auto m = match::standard_shape(); auto r = find_match(mm, m); EXPECT(r.result == l); } TEST_CASE(match_name1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum"); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_name2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("min"); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_name3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_arg1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::arg(0)(match::name("@literal")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_arg2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::arg(0)(match::name("sum")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_arg3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::arg(1)(match::name("@literal")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_arg4) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); auto pass = mm.add_instruction(pass_op{}, sum); auto m = match::name("pass")(match::arg(0)(match::name("sum")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == pass); } TEST_CASE(match_arg5) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("pass")(match::arg(1)(match::name("sum")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_arg6) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::arg(0)(match::name("@literal"))); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_arg7) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::arg(0)(match::name("@literal")), match::arg(1)(match::name("@literal"))); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_arg8) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::all_of(match::arg(0)(match::name("@literal")), match::arg(1)(match::name("@literal"))), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_nargs1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::nargs(2)); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_nargs2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::nargs(2), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_nargs3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::all_of(match::nargs(2))); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_args1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::args(match::name("@literal"), match::name("@literal")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_args2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::args(match::name("@literal"), match::name("sum")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_args3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::args(match::name("@literal")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_args4) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")(match::args(match::name("sum"), match::name("@literal")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == sum2); } TEST_CASE(match_args5) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::args(match::name("sum"), match::name("@literal")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_args6) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); auto pass = mm.add_instruction(pass_op{}, sum); auto m = match::name("pass")(match::args(match::name("sum")), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == pass); } TEST_CASE(match_args7) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); auto pass = mm.add_instruction(pass_op{}, sum); auto m = match::name("pass")(match::args(match::name("sum")(match::args( match::name("@literal"), match::name("@literal")))), match::standard_shape()); auto r = find_match(mm, m); EXPECT(r.result == pass); } TEST_CASE(match_either_args1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")(match::either_arg(0, 1)(match::name("sum"), match::name("@literal"))); auto r = find_match(mm, m); EXPECT(r.result == sum2); } TEST_CASE(match_either_args2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")(match::either_arg(0, 1)(match::name("@literal"), match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == sum2); } TEST_CASE(match_either_args3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")(match::either_arg(0, 1)(match::name("pass"), match::name("@literal"))); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_either_args_any1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")(match::either_arg(0, 1)(match::any().bind("x"), match::any().bind("y"))); auto r = find_match(mm, m); EXPECT(r.result == sum1); EXPECT(r.instructions["x"] != r.instructions["y"]); } TEST_CASE(match_either_args_any2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")( match::either_arg(0, 1)(match::any().bind("x"), match::name("@literal").bind("y"))); auto r = find_match(mm, m); EXPECT(r.result == sum1); EXPECT(r.instructions["x"] != r.instructions["y"]); } TEST_CASE(match_either_args_any3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")( match::either_arg(0, 1)(match::name("@literal").bind("x"), match::any().bind("y"))); auto r = find_match(mm, m); EXPECT(r.result == sum1); EXPECT(r.instructions["x"] != r.instructions["y"]); } TEST_CASE(match_either_args_any4) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")( match::either_arg(0, 1)(match::name("sum").bind("x"), match::any().bind("y"))); auto r = find_match(mm, m); EXPECT(r.result == sum2); EXPECT(r.instructions["x"] != r.instructions["y"]); } TEST_CASE(match_either_args_any5) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")( match::either_arg(0, 1)(match::any().bind("x"), match::name("sum").bind("y"))); auto r = find_match(mm, m); EXPECT(r.result == sum2); EXPECT(r.instructions["x"] != r.instructions["y"]); } TEST_CASE(match_all_of1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::all_of(match::arg(0)(match::name("@literal")), match::arg(1)(match::name("@literal")))); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_all_of2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")( match::all_of(match::arg(0)(match::name("sum")), match::arg(1)(match::name("@literal")))); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_all_of3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::all_of(match::all_of( match::arg(0)(match::name("@literal")), match::arg(1)(match::name("@literal"))))); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_lazy_any_of) { migraphx::module mm; auto one = mm.add_literal(1); mm.add_instruction(pass_op{}, one); auto m = match::any_of(match::any(), throws()); auto r = find_match(mm, m); EXPECT(r.result == one); } TEST_CASE(match_lazy_all_of) { migraphx::module mm; auto one = mm.add_literal(1); mm.add_instruction(pass_op{}, one); auto m = match::all_of(match::none(), throws()); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_lazy_none_of) { migraphx::module mm; auto one = mm.add_literal(1); mm.add_instruction(pass_op{}, one); auto m = match::none_of(match::any(), throws()); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_any_of1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")( match::any_of(match::arg(0)(match::name("sum")), match::arg(1)(match::name("@literal")))); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_any_of2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")( match::any_of(match::arg(0)(match::name("sum")), match::arg(1)(match::name("sum")))); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_any_of_lazy1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")( match::any_of(match::args(match::any(), match::any()).bind("x"), match::args(match::name("sum"), match::name("sum")).bind("y"))); auto r = find_match(mm, m); EXPECT(r.result == sum); EXPECT(migraphx::contains(r.instructions, "x")); EXPECT(r.instructions["x"] == sum); EXPECT(not migraphx::contains(r.instructions, "y")); } TEST_CASE(match_any_of_lazy2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")( match::any_of(match::args(match::name("@literal"), match::name("@literal")).bind("x"), match::args(match::any(), match::any()).bind("y"))); auto r = find_match(mm, m); EXPECT(r.result == sum); EXPECT(migraphx::contains(r.instructions, "x")); EXPECT(r.instructions["x"] == sum); EXPECT(not migraphx::contains(r.instructions, "y")); } TEST_CASE(match_any_of_lazy3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")( match::any_of(match::args(match::any(), match::any()).bind("x"), match::args(match::name("@literal"), match::name("@literal")).bind("y"))); auto r = find_match(mm, m); EXPECT(r.result == sum); EXPECT(migraphx::contains(r.instructions, "x")); EXPECT(r.instructions["x"] == sum); EXPECT(not migraphx::contains(r.instructions, "y")); } TEST_CASE(match_any_of_lazy4) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::any_of( match::args(match::name("@literal").bind("x1"), match::name("@literal").bind("y1")), match::args(match::any().bind("x2"), match::any().bind("y2")))); auto r = find_match(mm, m); EXPECT(r.result == sum); EXPECT(migraphx::contains(r.instructions, "x1")); EXPECT(migraphx::contains(r.instructions, "y1")); EXPECT(r.instructions["x1"] == one); EXPECT(r.instructions["y1"] == two); EXPECT(not migraphx::contains(r.instructions, "x2")); EXPECT(not migraphx::contains(r.instructions, "y2")); } TEST_CASE(match_any_of_lazy5) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::any_of( match::args(match::any().bind("x1"), match::any().bind("y1")), match::args(match::name("@literal").bind("x2"), match::name("@literal").bind("y2")))); auto r = find_match(mm, m); EXPECT(r.result == sum); EXPECT(migraphx::contains(r.instructions, "x1")); EXPECT(migraphx::contains(r.instructions, "y1")); EXPECT(r.instructions["x1"] == one); EXPECT(r.instructions["y1"] == two); EXPECT(not migraphx::contains(r.instructions, "x2")); EXPECT(not migraphx::contains(r.instructions, "y2")); } TEST_CASE(match_none_of1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")( match::none_of(match::arg(0)(match::name("sum")), match::arg(1)(match::name("sum")))); auto r = find_match(mm, m); EXPECT(r.result == sum); } TEST_CASE(match_none_of2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("sum")(match::none_of(match::arg(0)(match::name("@literal")), match::arg(1)(match::name("@literal")))); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_output1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto minus = mm.add_instruction(minus_op{}, two, one); auto sum = mm.add_instruction(sum_op{}, minus, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("minus")(match::output(match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == minus); } TEST_CASE(match_output2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto minus = mm.add_instruction(minus_op{}, two, one); auto sum = mm.add_instruction(sum_op{}, minus, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("@literal")(match::output(match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_skip_output1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto minus = mm.add_instruction(minus_op{}, two, one); auto sum = mm.add_instruction(sum_op{}, minus, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("minus")(match::skip_output(match::name("pass"))(match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == minus); } TEST_CASE(match_skip_output2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto minus = mm.add_instruction(minus_op{}, two, one); auto minus_pass = mm.add_instruction(pass_op{}, minus); auto sum = mm.add_instruction(sum_op{}, minus_pass, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("minus")(match::skip_output(match::name("pass"))(match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == minus); } TEST_CASE(match_skip_output3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto minus = mm.add_instruction(minus_op{}, two, one); auto minus_pass1 = mm.add_instruction(pass_op{}, minus); auto minus_pass2 = mm.add_instruction(pass_op{}, minus_pass1); auto minus_pass3 = mm.add_instruction(pass_op{}, minus_pass2); auto sum = mm.add_instruction(sum_op{}, minus_pass3, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("minus")(match::skip_output(match::name("pass"))(match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == minus); } TEST_CASE(match_skip_output4) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto pass = mm.add_instruction(pass_op{}, one); auto sum = mm.add_instruction(sum_op{}, pass, two); mm.add_instruction(pass_op{}, sum); auto m = match::name("@literal")(match::skip_output(match::name("pass"))(match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == two); } TEST_CASE(match_skip_output5) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto pass = mm.add_instruction(pass_op{}, one); auto sum1 = mm.add_instruction(sum_op{}, pass, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, one); auto sum3 = mm.add_instruction(sum_op{}, sum2, two); mm.add_instruction(pass_op{}, sum3); auto m = match::name("@literal")(match::skip_output(match::name("pass"))(match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_skip_output6) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto minus = mm.add_instruction(minus_op{}, two, one); auto sum1 = mm.add_instruction(sum_op{}, minus, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, one); auto sum3 = mm.add_instruction(sum_op{}, sum2, two); mm.add_instruction(pass_op{}, sum3); auto m = match::name("minus")(match::skip_output(match::name("pass"))(match::name("sum"))); auto r = find_match(mm, m); EXPECT(r.result == minus); } TEST_CASE(match_skip_output7) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto minus1 = mm.add_instruction(minus_op{}, two, one); auto minus2 = mm.add_instruction(minus_op{}, two, minus1); auto sum = mm.add_instruction(sum_op{}, one, minus2); mm.add_instruction(pass_op{}, sum); auto m = match::name("minus")(match::skip_output(match::name("pass"))(match::name("minus"))); auto r = find_match(mm, m); EXPECT(r.result == minus1); } TEST_CASE(match_bind1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); auto pass = mm.add_instruction(pass_op{}, sum); auto m = match::name("pass")( match::args(match::name("sum")(match::args(match::name("@literal").bind("one"), match::name("@literal").bind("two"))) .bind("sum")), match::standard_shape()) .bind("pass"); auto r = find_match(mm, m); EXPECT(r.instructions["one"] == one); EXPECT(r.instructions["two"] == two); EXPECT(r.instructions["sum"] == sum); EXPECT(r.instructions["pass"] == pass); EXPECT(r.result == pass); } TEST_CASE(match_bind_modules1) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto* child = p.create_module("child"); auto two = child->add_literal(2); auto sum = child->add_instruction(sum_op{}, one, two); child->add_instruction(pass_op{}, sum); mm->add_instruction(mod_pass_op{}, {one}, {child}); auto m = match::name("pass")( match::args(match::name("sum")(match::args(match::name("@literal").bind("one"), match::name("@literal").bind("two"))) .bind("sum")), match::standard_shape()) .bind("pass"); auto r = find_match(*child, m); EXPECT(not migraphx::contains(r.instructions, "one")); EXPECT(not migraphx::contains(r.instructions, "two")); EXPECT(not migraphx::contains(r.instructions, "sum")); EXPECT(not migraphx::contains(r.instructions, "pass")); EXPECT(r.result == child->end()); } TEST_CASE(match_bind_modules2) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto* child = p.create_module("child"); auto two = child->add_literal(2); auto sum = child->add_instruction(sum_op{}, one, two); auto pass = child->add_instruction(pass_op{}, sum); mm->add_instruction(mod_pass_op{}, {one}, {child}); auto m = match::name("pass")( match::args(match::name("sum")(match::args(match::name("@literal"), match::name("@literal").bind("two"))) .bind("sum")), match::standard_shape()) .bind("pass"); auto r = find_match(*child, m); EXPECT(r.instructions["two"] == two); EXPECT(r.instructions["sum"] == sum); EXPECT(r.instructions["pass"] == pass); EXPECT(r.result == pass); } // Note that mm.add_literal(1) makes a scalar int32 literal with value 1 TEST_CASE(match_has_value1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::has_value(1); auto r = find_match(mm, m); EXPECT(r.result == one); } TEST_CASE(match_has_value2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::has_value(2); auto r = find_match(mm, m); EXPECT(r.result == two); } TEST_CASE(match_has_value3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")(match::args(match::has_value(1), match::has_value(2))); auto r = find_match(mm, m); EXPECT(r.result == sum1); } TEST_CASE(match_has_value4) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::has_value(3); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_has_value5) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")(match::args(match::has_value(1), match::has_value(3))); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_has_value6) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, two); mm.add_instruction(pass_op{}, sum2); auto m = match::name("sum")(match::args(match::has_value(2), match::has_value(1))); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_has_value7) { // zero detection migraphx::module mm; auto s = migraphx::shape{migraphx::shape::half_type, {1}, {0}}; auto zero = mm.add_literal(migraphx::literal{s, {0.00239754}}); auto one = mm.add_literal(migraphx::literal{s, {1.0}}); auto sum1 = mm.add_instruction(sum_op{}, one, zero); mm.add_instruction(pass_op{}, sum1); auto m1 = match::has_value(0.0f, 0, 0); auto r1 = find_match(mm, m1); EXPECT(r1.result == mm.end()); // increase tolerance auto m2 = match::has_value(0.0f); auto r2 = find_match(mm, m2); EXPECT(r2.result == zero); } TEST_CASE(match_has_value8) { // zero detection migraphx::module mm; auto s = migraphx::shape{migraphx::shape::half_type, {1}, {0}}; auto zero = mm.add_literal(migraphx::literal{s, {9.07183e-05}}); auto one = mm.add_literal(migraphx::literal{s, {1.0}}); auto sum1 = mm.add_instruction(sum_op{}, one, zero); mm.add_instruction(pass_op{}, sum1); auto m1 = match::has_value(0.0f, 0, 0); auto r1 = find_match(mm, m1); EXPECT(r1.result == mm.end()); // increase tolerance auto m2 = match::has_value(0.0f); auto r2 = find_match(mm, m2); EXPECT(r2.result == zero); } TEST_CASE(match_has_value9) { migraphx::module mm; auto s = migraphx::shape{migraphx::shape::half_type, {1}, {0}}; auto n_five = mm.add_literal(migraphx::literal{s, {-5.0}}); mm.add_instruction(pass_op{}, n_five); auto m1 = match::has_value(5.0f); auto r1 = find_match(mm, m1); EXPECT(r1.result == mm.end()); // increase tolerance auto m2 = match::has_value(-5.0f); auto r2 = find_match(mm, m2); EXPECT(r2.result == n_five); // do exact match auto m3 = match::has_value(-5.0f, 0, 0); auto r3 = find_match(mm, m3); EXPECT(r3.result == n_five); // do exact match auto m4 = match::has_value(5.0f, 0, 0); auto r4 = find_match(mm, m4); EXPECT(r4.result == mm.end()); } TEST_CASE(match_has_value_eps1) { migraphx::module mm; migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data0{7.f, 7.f, 7.f}; auto l0 = mm.add_literal(migraphx::literal{s, data0}); std::vector data1{3.f, 3.f, 3.f}; auto l1 = mm.add_literal(migraphx::literal{s, data1}); auto sum1 = mm.add_instruction(sum_op{}, l0, l1); mm.add_return({sum1}); auto m = match::has_value(7.f, 1, 0); auto r = find_match(mm, m); EXPECT(r.result == l0); } TEST_CASE(match_has_value_eps2) { migraphx::module mm; migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data0{7.f, 7.f, 7.f}; auto l0 = mm.add_literal(migraphx::literal{s, data0}); std::vector data1{3.f, 3.f, 3.f}; auto l1 = mm.add_literal(migraphx::literal{s, data1}); auto sum1 = mm.add_instruction(sum_op{}, l0, l1); mm.add_return({sum1}); auto m = match::has_value(3.f, 10, 10); auto r = find_match(mm, m); EXPECT(r.result == l1); } TEST_CASE(match_has_value_eps3) { migraphx::module mm; migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data0{7.f, 7.f, 7.f}; auto l0 = mm.add_literal(migraphx::literal{s, data0}); std::vector data1{3.f, 3.f, 3.f}; auto l1 = mm.add_literal(migraphx::literal{s, data1}); auto sum1 = mm.add_instruction(sum_op{}, l0, l1); mm.add_return({sum1}); auto eps = std::numeric_limits::epsilon(); auto m = match::has_value(7.0 + 100 * eps, 10, 10); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_tree1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, three); mm.add_instruction(pass_op{}, sum2); auto m = match::tree( match::name("sum"), match::has_value(1), match::has_value(2), match::has_value(3)); auto r = find_match(mm, m); EXPECT(r.result == sum2); } TEST_CASE(match_tree2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, three); mm.add_instruction(pass_op{}, sum2); auto m = match::tree( match::name("sum"), match::has_value(2), match::has_value(1), match::has_value(3)); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_tree3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, three, sum1); mm.add_instruction(pass_op{}, sum2); auto m = match::tree( match::name("sum"), match::has_value(3), match::has_value(1), match::has_value(2)); auto r = find_match(mm, m); EXPECT(r.result == sum2); } TEST_CASE(match_tree4) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, three); mm.add_instruction(pass_op{}, sum2); auto m = match::tree(match::name("sum"), match::has_value(1), match::has_value(2), match::has_value(3), match::has_value(4)); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_tree5) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, three); mm.add_instruction(pass_op{}, sum2); auto m = match::tree(match::name("sum"), match::has_value(2), match::has_value(3)); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_tree6) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, three); mm.add_instruction(pass_op{}, sum2); auto m = match::tree(match::name("sum"), match::has_value(1), match::has_value(3)); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } TEST_CASE(match_unordered_tree1) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, three); mm.add_instruction(pass_op{}, sum2); auto m = match::unordered_tree( match::name("sum"), match::has_value(3), match::has_value(2), match::has_value(1)); auto r = find_match(mm, m); EXPECT(r.result == sum2); } TEST_CASE(match_unordered_tree2) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, three, sum1); mm.add_instruction(pass_op{}, sum2); auto m = match::unordered_tree( match::name("sum"), match::has_value(3), match::has_value(2), match::has_value(1)); auto r = find_match(mm, m); EXPECT(r.result == sum2); } TEST_CASE(match_unordered_tree3) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, two, one); auto sum2 = mm.add_instruction(sum_op{}, sum1, three); mm.add_instruction(pass_op{}, sum2); auto m = match::unordered_tree( match::name("sum"), match::has_value(3), match::has_value(2), match::has_value(1)); auto r = find_match(mm, m); EXPECT(r.result == sum2); } TEST_CASE(match_unordered_tree4) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto three = mm.add_literal(3); auto sum1 = mm.add_instruction(sum_op{}, one, two); auto sum2 = mm.add_instruction(sum_op{}, sum1, three); mm.add_instruction(pass_op{}, sum2); auto m = match::unordered_tree( match::name("sum"), match::has_value(4), match::has_value(2), match::has_value(1)); auto r = find_match(mm, m); EXPECT(r.result == mm.end()); } struct match_find_sum { migraphx::instruction_ref ins; auto matcher() const { return match::name("sum"); } void apply(migraphx::module&, const match::matcher_result& r) const { EXPECT(r.result == ins); } }; struct match_find_literal { migraphx::instruction_ref ins; auto matcher() const { return match::name("@literal"); } void apply(migraphx::module&, const match::matcher_result& r) const { EXPECT(r.result != ins); EXPECT(r.result->name() == "@literal"); } }; TEST_CASE(match_finder) { migraphx::module mm; auto one = mm.add_literal(1); auto two = mm.add_literal(2); auto sum = mm.add_instruction(sum_op{}, one, two); mm.add_instruction(pass_op{}, sum); match::find_matches(mm, match_find_sum{sum}, match_find_literal{sum}); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/memory_coloring_test.cpp000066400000000000000000003626551510465702400225070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::memory_coloring{"allocate", true}}); } struct allocate { migraphx::shape s{}; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.s, "shape")); } std::string name() const { return "allocate"; } migraphx::shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this}.has(0); return s; } migraphx::argument compute(migraphx::context&, const migraphx::shape& output_shape, const std::vector&) const { return migraphx::argument{output_shape}; } }; static migraphx::instruction_ref add_alloc(migraphx::module& m, const migraphx::shape& s) { return m.add_instruction(allocate{s}); } static bool no_allocate(const migraphx::module& m) { return std::none_of(m.begin(), m.end(), [](auto&& ins) { return ins.name() == "allocate"; }); } static bool is_overlap(std::pair x, std::pair y) { return std::max(x.first, y.first) < std::min(x.second, y.second); } static std::pair get_load_interval(migraphx::instruction_ref a) { auto v = a->get_operator().to_value(); auto offset = v.at("offset").to(); auto s = migraphx::from_value(v.at("shape")); return {offset, offset + s.bytes()}; } static bool is_overlap_load(migraphx::instruction_ref a, migraphx::instruction_ref b) { return is_overlap(get_load_interval(a), get_load_interval(b)); } static bool is_disjoint(const std::vector& inss) { return std::none_of(inss.begin(), inss.end(), [&](auto ins1) { return std::none_of(inss.begin(), inss.end(), [&](auto ins2) { if(ins1 == ins2) return true; return is_overlap_load(ins1, ins2); }); }); } TEST_CASE(test1) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); CHECK(is_disjoint({a1, a2})); } TEST_CASE(test2) { migraphx::module m; auto input = m.add_parameter("input", migraphx::shape{migraphx::shape::float_type, {16}}); auto a1 = add_alloc(m, {migraphx::shape::float_type, {128}}); auto m1 = m.add_instruction(pass_op{}, a1, input); auto m2 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, m2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 672); CHECK(no_allocate(m)); } TEST_CASE(test3) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m2 = add_alloc(m, {migraphx::shape::float_type, {128}}); auto m1 = m.add_instruction(pass_op{}, m2, a1); auto p3 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, p3, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 672); CHECK(no_allocate(m)); } TEST_CASE(test4) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {0}}); auto m2 = add_alloc(m, {migraphx::shape::float_type, {128}}); auto m1 = m.add_instruction(pass_op{}, m2, a1); auto p3 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, p3, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 672); CHECK(no_allocate(m)); } TEST_CASE(test5) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m1 = m.add_instruction(pass_op{}, a1); auto m2 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, m2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test6) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto m2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto p3 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, p3, m2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 352); CHECK(no_allocate(m)); } TEST_CASE(test7) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto m2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto p3 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, p3, m2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 224); CHECK(no_allocate(m)); } TEST_CASE(test8) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto m2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto p3 = add_alloc(m, {migraphx::shape::float_type, {192}}); m.add_instruction(pass_op{}, p3, m2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 960); CHECK(no_allocate(m)); } TEST_CASE(test9) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto m2 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto p3 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, p3, m2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 96); CHECK(no_allocate(m)); } TEST_CASE(test10) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, a1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 32); CHECK(no_allocate(m)); } TEST_CASE(test11) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); m.add_instruction(pass_op{}, a3, m2); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 224); CHECK(no_allocate(m)); } TEST_CASE(test12) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); m.add_instruction(pass_op{}, a3, m2); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 352); CHECK(no_allocate(m)); } TEST_CASE(test13) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); m.add_instruction(pass_op{}, a3, m2); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 224); CHECK(no_allocate(m)); } TEST_CASE(test14) { migraphx::module m; auto a3 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto m2 = m.add_instruction(pass_op{}, a2, m1); m.add_instruction(pass_op{}, a3, m2); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 224); CHECK(no_allocate(m)); } TEST_CASE(test15) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a2); auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a3, m1, m2); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 352); CHECK(no_allocate(m)); } TEST_CASE(test16) { migraphx::module m; auto a1 = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {8}})); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {40}})); auto m2 = m.add_instruction(pass_op{}, a2); auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a3, m1, m2); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 160); CHECK(no_allocate(m)); } TEST_CASE(test17) { migraphx::module m; auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto a1 = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {8}})); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {40}})); auto m2 = m.add_instruction(pass_op{}, a2); m.add_instruction(pass_op{}, a3, m1, m2); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 160); CHECK(no_allocate(m)); } TEST_CASE(test18) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto m2 = m.add_instruction(pass_op{}, a1, m1); auto p3 = m.add_instruction(pass_op{}, m2, m1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a2, m1, m2, p3); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test19) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a3, m2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 352); CHECK(no_allocate(m)); } TEST_CASE(test20) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto m1 = m.add_instruction(pass_op{}, a1, a2, a3); auto a4 = add_alloc(m, {migraphx::shape::float_type, {32}}); m.add_instruction(pass_op{}, a4, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 384); CHECK(no_allocate(m)); } TEST_CASE(test21) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto m1 = m.add_instruction(pass_op{}, a1, a2, a3); auto a4 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, a4, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 288); CHECK(no_allocate(m)); } TEST_CASE(test22) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1, a2, a3); auto a4 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, a4, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 288); CHECK(no_allocate(m)); } TEST_CASE(test23) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto m1 = m.add_instruction(pass_op{}, a1, a2, a3); auto a4 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, a4, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 288); CHECK(no_allocate(m)); } TEST_CASE(test24) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {32}}); auto m1 = m.add_instruction(pass_op{}, a1, a2, a3); auto a4 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, a4, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 384); CHECK(no_allocate(m)); } TEST_CASE(test25) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(nop{}); auto m1 = m.add_instruction(pass_op{}, a1); m.add_instruction(nop{}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test26) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(nop{}, a1); auto m1 = m.add_instruction(pass_op{}, a1); m.add_instruction(nop{}, a1, m1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test27) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(nop{}, a2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test28) { migraphx::module m; auto output = m.add_parameter("output", {migraphx::shape::float_type, {8}}); auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); m.add_instruction(pass_op{}, m2, output); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test29) { migraphx::module m; auto output = m.add_parameter("output", {migraphx::shape::float_type, {8}}); auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); m.move_instruction(output, m2); m.add_instruction(pass_op{}, m2, output); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test30) { migraphx::module m; auto output = m.add_parameter("x", {migraphx::shape::float_type, {8}}); auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a2, m1); m.move_instruction(output, m2); m.add_instruction(pass_op{}, m2, output); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test31) { migraphx::module m; auto output = m.add_parameter("output", {migraphx::shape::float_type, {8}}); auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.move_instruction(output, a2); m.add_instruction(pass_op{}, a2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test32) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m1 = m.add_instruction(pass_op{}, a2, a1, a3); auto a5 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a5, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 352); CHECK(no_allocate(m)); } TEST_CASE(test33) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a2, a1, a3); auto a5 = add_alloc(m, {migraphx::shape::float_type, {40}}); m.add_instruction(pass_op{}, a5, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 192); CHECK(no_allocate(m)); } TEST_CASE(test34) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m1 = m.add_instruction(pass_op{}, a2, a1, a3); auto a5 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, a5, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 480); CHECK(no_allocate(m)); } TEST_CASE(test35) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto a3 = add_alloc(m, {migraphx::shape::float_type, {8}}); auto m1 = m.add_instruction(pass_op{}, a2, a1, a3); auto a5 = add_alloc(m, {migraphx::shape::float_type, {8}}); m.add_instruction(pass_op{}, a5, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 224); CHECK(no_allocate(m)); } TEST_CASE(test36) { migraphx::module m; auto output = m.add_parameter("output", {migraphx::shape::float_type, {20}}); auto a1 = add_alloc(m, {migraphx::shape::float_type, {0}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m1 = m.add_instruction(pass_op{}, a2, a1); auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a3, m1); auto a4 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto p3 = m.add_instruction(pass_op{}, a4, m2); m.add_instruction(pass_op{}, output, p3); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 320); CHECK(no_allocate(m)); } TEST_CASE(test37) { migraphx::module m; auto output = m.add_parameter("output", {migraphx::shape::float_type, {20}}); auto a1 = add_alloc(m, {migraphx::shape::float_type, {4}}); auto a2 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m1 = m.add_instruction(pass_op{}, a2, a1); auto a3 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto m2 = m.add_instruction(pass_op{}, a3, m1); auto a4 = add_alloc(m, {migraphx::shape::float_type, {40}}); auto p3 = m.add_instruction(pass_op{}, a4, m2); m.add_instruction(pass_op{}, output, p3); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 320); CHECK(no_allocate(m)); } TEST_CASE(test38) { migraphx::module m; auto output = m.add_parameter("output", {migraphx::shape::float_type, {1, 64, 56, 56}}); auto m29 = add_alloc(m, {migraphx::shape::float_type, {0}}); auto p30 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 112, 112}}); auto p31 = m.add_instruction(pass_op{}, p30, m29); auto p32 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 112, 112}}); auto p37 = m.add_instruction(pass_op{}, p32, p31); auto p38 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 112, 112}}); auto p39 = m.add_instruction(pass_op{}, p38, p37); auto p40 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p41 = m.add_instruction(pass_op{}, p40, p39); auto p42 = add_alloc(m, {migraphx::shape::float_type, {0}}); auto p43 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p44 = m.add_instruction(pass_op{}, p43, p41, p42); auto p45 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p50 = m.add_instruction(pass_op{}, p45, p44); auto p51 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p52 = m.add_instruction(pass_op{}, p51, p50); auto p53 = add_alloc(m, {migraphx::shape::float_type, {0}}); auto p54 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p55 = m.add_instruction(pass_op{}, p54, p52, p53); auto p56 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p61 = m.add_instruction(pass_op{}, p56, p55); auto p62 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p63 = m.add_instruction(pass_op{}, p62, p61, p41); auto p64 = add_alloc(m, {migraphx::shape::float_type, {0}}); auto p65 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p66 = m.add_instruction(pass_op{}, p65, p63, p64); auto p67 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p72 = m.add_instruction(pass_op{}, p67, p66); auto p73 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p74 = m.add_instruction(pass_op{}, p73, p72); auto p75 = add_alloc(m, {migraphx::shape::float_type, {0}}); auto p76 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p77 = m.add_instruction(pass_op{}, p76, p74, p75); auto p78 = add_alloc(m, {migraphx::shape::float_type, {1, 64, 56, 56}}); auto p83 = m.add_instruction(pass_op{}, p78, p77); m.add_instruction(pass_op{}, output, p83, p63); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 6422528); CHECK(no_allocate(m)); } TEST_CASE(test39) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = add_alloc(*mm, cond_s); auto output = mm->add_parameter("output", {migraphx::shape::float_type, {20}}); migraphx::shape ds{migraphx::shape::float_type, {2, 3}}; std::vector data1 = {0.384804, -1.77948, -0.453775, 0.477438, -1.06333, -1.12893}; auto l1 = mm->add_literal(migraphx::literal(ds, data1)); std::vector data2 = {-0.258047, 0.360394, 0.536804, -0.577762, 1.0217, 1.02442}; auto l2 = mm->add_literal(migraphx::literal(ds, data2)); auto* then_mod = p.create_module("If_0_if"); auto i1 = add_alloc(*then_mod, ds); auto a1 = then_mod->add_instruction(pass_op{}, i1, l1); then_mod->add_return({a1, output}); auto* else_mod = p.create_module("If_0_else"); auto i2 = add_alloc(*else_mod, ds); auto a2 = else_mod->add_instruction(pass_op{}, i2, l2); else_mod->add_return({a2, output}); auto ret = mm->add_instruction(mod_pass_op{}, {cond}, {then_mod, else_mod}); mm->add_return({ret, output}); auto sub_modules = p.get_modules(); std::reverse(sub_modules.begin(), sub_modules.end()); for(const auto& smod : sub_modules) { run_pass(*smod); } CHECK(mm->get_parameter_shape("scratch").bytes() == 1); CHECK(then_mod->get_parameter_shape("scratch").bytes() == 24); CHECK(else_mod->get_parameter_shape("scratch").bytes() == 24); CHECK(no_allocate(*mm)); CHECK(no_allocate(*then_mod)); CHECK(no_allocate(*else_mod)); } // NOLINTNEXTLINE TEST_CASE(rnn_dom) { migraphx::module m; auto mx0 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 10}}); auto mx1 = m.add_instruction(pass_op{}); auto mr = m.add_parameter("r", migraphx::shape{migraphx::shape::float_type, {1, 15, 5}}); auto mx2 = m.add_instruction(pass_op{}, mr); auto mx3 = m.add_instruction(pass_op{}, mx2); auto mx4 = m.add_instruction(pass_op{}, mx3); m.add_instruction(pass_op{}); auto mx6 = m.add_instruction(pass_op{}, mx0, mx1, mx4); m.add_instruction(pass_op{}); auto mx8 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 15}}); m.add_instruction(pass_op{}, mx8, mx1, mx0, mx6); auto mseq = m.add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {3, 2, 8}}); auto mx10 = m.add_instruction(pass_op{}, mseq); auto mx11 = m.add_instruction(pass_op{}, mx10); auto mw = m.add_parameter("w", migraphx::shape{migraphx::shape::float_type, {1, 15, 8}}); auto mx12 = m.add_instruction(pass_op{}, mw); auto mx13 = m.add_instruction(pass_op{}, mx12); m.add_instruction(pass_op{}); auto mx15 = m.add_instruction(pass_op{}, mx8, mx11, mx13); m.add_instruction(pass_op{}, mx15, mx1, mx0, mx6); m.add_instruction(pass_op{}); auto mx18 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx18, mx6, mx15, mx0, mx1, mx8); auto mx20 = m.add_instruction(pass_op{}, mx6); m.add_instruction(pass_op{}, mx20, mx8, mx15, mx18); auto mx22 = m.add_instruction(pass_op{}, mx15); m.add_instruction(pass_op{}, mx22, mx1, mx0, mx20, mx6, mx18); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx27 = m.add_instruction(pass_op{}, mx18, mx22, mx20); m.add_instruction(pass_op{}, mx27, mx15, mx8, mx6, mx20, mx1, mx22, mx0); m.add_instruction(pass_op{}); auto mx30 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx30, mx20, mx22, mx1, mx15, mx8, mx6, mx27, mx0, mx18); auto mx32 = m.add_instruction(pass_op{}, mx15); m.add_instruction(pass_op{}, mx32, mx20, mx30, mx0, mx18, mx1, mx27, mx6); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx36 = m.add_instruction(pass_op{}, mx30, mx32); m.add_instruction(pass_op{}, mx36, mx32, mx0, mx27, mx8, mx1, mx15, mx6, mx20, mx22, mx18); auto mx38 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx38, mx32, mx0, mx27, mx8, mx1, mx15, mx6, mx20, mx22, mx18); auto mx40 = m.add_instruction(pass_op{}, mx38, mx36); m.add_instruction(pass_op{}, mx40, mx32, mx0, mx27, mx8, mx1, mx15, mx6, mx20, mx22, mx18); m.add_instruction(pass_op{}); auto mx43 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx43, mx15, mx32, mx27, mx30, mx18, mx8, mx40, mx36, mx22, mx38); auto mx45 = m.add_instruction(pass_op{}, mx6); m.add_instruction(pass_op{}, mx45, mx32, mx27, mx30, mx18, mx40, mx36, mx22, mx8, mx15, mx38); auto mx47 = m.add_instruction(pass_op{}, mx15); m.add_instruction( pass_op{}, mx47, mx30, mx18, mx43, mx6, mx1, mx45, mx0, mx27, mx36, mx20, mx40, mx38); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx51 = m.add_instruction(pass_op{}, mx43, mx47, mx45); m.add_instruction( pass_op{}, mx51, mx15, mx47, mx32, mx27, mx30, mx18, mx8, mx36, mx22, mx40, mx38); auto mx53 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction( pass_op{}, mx53, mx15, mx47, mx32, mx27, mx30, mx18, mx8, mx36, mx22, mx40, mx38); auto mx55 = m.add_instruction(pass_op{}, mx53, mx51, mx1); m.add_instruction( pass_op{}, mx55, mx15, mx47, mx32, mx27, mx30, mx18, mx8, mx36, mx22, mx40, mx38); auto mx57 = m.add_instruction(pass_op{}, mx3); m.add_instruction(pass_op{}); auto mx59 = m.add_instruction(pass_op{}, mx40, mx55, mx57, mx40); m.add_instruction( pass_op{}, mx59, mx15, mx8, mx38, mx18, mx30, mx27, mx47, mx32, mx40, mx36, mx22); auto mx61 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx61, mx30, mx15, mx1, mx51, mx20, mx59, mx32, mx45, mx22, mx8, mx47, mx40, mx53, mx6, mx55, mx0, mx43, mx38, mx36); m.add_instruction(pass_op{}); auto mx64 = m.add_instruction(pass_op{}, mx61, mx27, mx1); m.add_instruction(pass_op{}, mx64, mx30, mx15, mx1, mx51, mx20, mx59, mx32, mx45, mx22, mx8, mx47, mx40, mx53, mx6, mx55, mx0, mx43, mx38, mx36); m.add_instruction(pass_op{}); auto mx67 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx67, mx18, mx6, mx1, mx51, mx20, mx59, mx27, mx55, mx43, mx38, mx0, mx61, mx45, mx36, mx40, mx53, mx64, mx30); auto mx69 = m.add_instruction(pass_op{}); m.add_instruction(pass_op{}, mx69, mx18, mx6, mx1, mx51, mx20, mx59, mx27, mx55, mx43, mx38, mx0, mx61, mx45, mx36, mx40, mx53, mx64, mx30); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx73 = m.add_instruction(pass_op{}, mx67, mx69, mx27); m.add_instruction(pass_op{}, mx73, mx18, mx6, mx1, mx51, mx20, mx59, mx27, mx55, mx43, mx38, mx0, mx61, mx45, mx36, mx40, mx53, mx64, mx30); m.add_instruction(pass_op{}); auto mx76 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx76, mx64, mx30, mx18, mx40, mx8, mx61, mx38, mx69, mx67, mx73, mx27, mx47, mx32, mx36, mx15, mx22); m.add_instruction(pass_op{}); auto mx79 = m.add_instruction(pass_op{}, mx76, mx59); m.add_instruction(pass_op{}, mx79, mx64, mx30, mx18, mx40, mx8, mx61, mx38, mx69, mx67, mx73, mx27, mx47, mx32, mx36, mx15, mx22); auto mx81 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx81, mx36, mx32, mx27, mx47, mx18, mx30, mx73, mx67, mx22, mx15, mx61, mx8, mx64, mx40, mx69, mx38); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx85 = m.add_instruction(pass_op{}, mx81, mx73, mx79, mx64); m.add_instruction(pass_op{}, mx85, mx36, mx32, mx27, mx47, mx18, mx30, mx73, mx67, mx22, mx15, mx61, mx8, mx64, mx40, mx69, mx38); m.add_instruction(pass_op{}); auto mx88 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 10}}); m.add_instruction(pass_op{}, mx88, mx36, mx32, mx27, mx47, mx18, mx30, mx73, mx67, mx22, mx15, mx61, mx8, mx64, mx40, mx69, mx38); auto mx90 = m.add_instruction(pass_op{}, mx88, mx85, mx4); m.add_instruction(pass_op{}, mx90, mx36, mx32, mx27, mx47, mx18, mx30, mx73, mx67, mx22, mx15, mx61, mx8, mx64, mx40, mx69, mx38); m.add_instruction(pass_op{}); auto mx93 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 15}}); m.add_instruction(pass_op{}, mx93, mx51, mx88, mx20, mx64, mx43, mx61, mx53, mx81, mx47, mx6, mx45, mx0, mx55, mx18, mx76, mx1, mx79, mx85, mx90, mx8, mx69, mx67, mx73, mx32, mx59, mx22, mx15, mx27); auto mx95 = m.add_instruction(pass_op{}, mseq); auto mx96 = m.add_instruction(pass_op{}, mx95); m.add_instruction(pass_op{}); auto mx98 = m.add_instruction(pass_op{}, mx93, mx96, mx13); m.add_instruction(pass_op{}, mx98, mx51, mx88, mx20, mx64, mx43, mx61, mx53, mx81, mx47, mx6, mx45, mx0, mx55, mx18, mx76, mx1, mx79, mx85, mx90, mx8, mx69, mx67, mx73, mx32, mx59, mx22, mx15, mx27); m.add_instruction(pass_op{}); auto mx101 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx101, mx43, mx40, mx53, mx59, mx51, mx6, mx61, mx81, mx38, mx45, mx20, mx0, mx76, mx55, mx18, mx85, mx1, mx93, mx79, mx90, mx27, mx88, mx64, mx30, mx98, mx36); auto mx103 = m.add_instruction(pass_op{}, mx90); m.add_instruction(pass_op{}, mx103, mx64, mx101, mx15, mx67, mx73, mx18, mx40, mx8, mx47, mx98, mx27, mx32, mx61, mx22, mx93, mx69, mx36, mx38, mx30); auto mx105 = m.add_instruction(pass_op{}, mx98); m.add_instruction(pass_op{}, mx105, mx43, mx88, mx53, mx64, mx59, mx6, mx76, mx61, mx81, mx47, mx103, mx22, mx45, mx0, mx55, mx18, mx85, mx51, mx20, mx1, mx79, mx90, mx8, mx101, mx15, mx69, mx67, mx73, mx32, mx27); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx110 = m.add_instruction(pass_op{}, mx101, mx105, mx103); m.add_instruction(pass_op{}, mx110, mx88, mx40, mx93, mx59, mx43, mx61, mx53, mx81, mx103, mx6, mx45, mx0, mx55, mx18, mx64, mx20, mx76, mx1, mx79, mx38, mx85, mx90, mx27, mx30, mx105, mx98, mx51, mx36); m.add_instruction(pass_op{}); auto mx113 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx113, mx59, mx20, mx51, mx1, mx79, mx90, mx55, mx85, mx76, mx81, mx47, mx6, mx38, mx88, mx43, mx40, mx0, mx45, mx53, mx93, mx8, mx101, mx15, mx69, mx67, mx73, mx32, mx110, mx22, mx103, mx30, mx36, mx98, mx105); auto mx115 = m.add_instruction(pass_op{}, mx98); m.add_instruction(pass_op{}, mx115, mx59, mx20, mx51, mx1, mx79, mx90, mx55, mx18, mx85, mx76, mx61, mx81, mx47, mx6, mx88, mx43, mx0, mx45, mx53, mx64, mx8, mx101, mx15, mx69, mx67, mx73, mx113, mx32, mx110, mx22, mx103, mx27); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx119 = m.add_instruction(pass_op{}, mx113, mx115); m.add_instruction(pass_op{}, mx119, mx59, mx20, mx51, mx1, mx79, mx90, mx55, mx85, mx76, mx81, mx47, mx6, mx38, mx88, mx43, mx40, mx0, mx45, mx53, mx93, mx8, mx101, mx15, mx69, mx67, mx73, mx32, mx110, mx22, mx103, mx30, mx36, mx115, mx98, mx105); auto mx121 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx121, mx59, mx20, mx51, mx1, mx79, mx90, mx55, mx85, mx76, mx81, mx47, mx6, mx38, mx88, mx43, mx40, mx0, mx45, mx53, mx93, mx8, mx101, mx15, mx69, mx67, mx73, mx32, mx110, mx22, mx103, mx30, mx36, mx115, mx98, mx105); auto mx123 = m.add_instruction(pass_op{}, mx121, mx119); m.add_instruction(pass_op{}, mx123, mx59, mx20, mx51, mx1, mx79, mx90, mx55, mx85, mx76, mx81, mx47, mx6, mx38, mx88, mx43, mx40, mx0, mx45, mx53, mx93, mx8, mx101, mx15, mx69, mx67, mx73, mx32, mx110, mx22, mx103, mx30, mx36, mx115, mx98, mx105); m.add_instruction(pass_op{}); auto mx126 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx126, mx115, mx113, mx8, mx67, mx61, mx73, mx18, mx123, mx119, mx32, mx15, mx36, mx110, mx27, mx101, mx22, mx98, mx47, mx40, mx93, mx38, mx69, mx121, mx64, mx30, mx105); auto mx128 = m.add_instruction(pass_op{}, mx90); m.add_instruction(pass_op{}, mx128, mx93, mx98, mx8, mx67, mx73, mx18, mx123, mx61, mx40, mx47, mx27, mx32, mx101, mx22, mx15, mx110, mx36, mx119, mx38, mx64, mx30, mx69, mx121, mx113, mx115, mx105); auto mx130 = m.add_instruction(pass_op{}, mx98); m.add_instruction(pass_op{}, mx130, mx119, mx64, mx22, mx110, mx126, mx128, mx121, mx113, mx67, mx90, mx69, mx15, mx20, mx8, mx27, mx51, mx85, mx79, mx123, mx103, mx18, mx55, mx32, mx0, mx45, mx61, mx53, mx76, mx6, mx47, mx59, mx73, mx81, mx88, mx1, mx43, mx101); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx134 = m.add_instruction(pass_op{}, mx126, mx130, mx128); m.add_instruction(pass_op{}, mx134, mx130, mx8, mx67, mx61, mx73, mx18, mx123, mx119, mx32, mx15, mx36, mx110, mx27, mx101, mx22, mx113, mx115, mx98, mx47, mx40, mx93, mx38, mx69, mx121, mx64, mx30, mx105); auto mx136 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx136, mx130, mx8, mx67, mx61, mx73, mx18, mx123, mx119, mx32, mx15, mx36, mx110, mx27, mx101, mx22, mx113, mx115, mx98, mx47, mx40, mx93, mx38, mx69, mx121, mx64, mx30, mx105); auto mx138 = m.add_instruction(pass_op{}, mx136, mx134, mx85); m.add_instruction(pass_op{}, mx138, mx130, mx8, mx67, mx61, mx73, mx18, mx123, mx119, mx32, mx15, mx36, mx110, mx27, mx101, mx22, mx113, mx115, mx98, mx47, mx40, mx93, mx38, mx69, mx121, mx64, mx30, mx105); m.add_instruction(pass_op{}); auto mx141 = m.add_instruction(pass_op{}, mx123, mx138, mx57, mx123); m.add_instruction(pass_op{}, mx141, mx113, mx115, mx130, mx105, mx38, mx93, mx61, mx98, mx27, mx64, mx30, mx119, mx121, mx69, mx8, mx67, mx40, mx47, mx32, mx101, mx22, mx36, mx110, mx15, mx73, mx18, mx123); auto mx143 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx143, mx8, mx73, mx121, mx67, mx101, mx110, mx69, mx15, mx138, mx88, mx43, mx79, mx53, mx61, mx45, mx18, mx0, mx6, mx27, mx22, mx134, mx32, mx1, mx119, mx59, mx85, mx103, mx126, mx64, mx128, mx55, mx76, mx47, mx81, mx90, mx136, mx51, mx141, mx20, mx113, mx123); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx147 = m.add_instruction(pass_op{}, mx143, mx69, mx110); m.add_instruction(pass_op{}, mx147, mx8, mx73, mx121, mx67, mx101, mx110, mx69, mx15, mx138, mx88, mx43, mx79, mx53, mx61, mx45, mx18, mx0, mx6, mx27, mx22, mx134, mx32, mx1, mx119, mx59, mx85, mx103, mx126, mx64, mx128, mx55, mx76, mx47, mx81, mx90, mx136, mx51, mx141, mx20, mx113, mx123); m.add_instruction(pass_op{}); auto mx150 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx150, mx30, mx121, mx115, mx98, mx130, mx85, mx88, mx90, mx79, mx1, mx93, mx64, mx18, mx53, mx61, mx38, mx27, mx147, mx0, mx6, mx51, mx40, mx134, mx43, mx119, mx59, mx45, mx76, mx128, mx81, mx136, mx55, mx138, mx123, mx126, mx141, mx103, mx20, mx105, mx113, mx143, mx36); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx154 = m.add_instruction(pass_op{}, mx150, mx110, mx85); m.add_instruction(pass_op{}, mx154, mx30, mx121, mx115, mx98, mx130, mx85, mx88, mx90, mx79, mx1, mx93, mx64, mx18, mx53, mx61, mx38, mx27, mx147, mx0, mx6, mx51, mx40, mx134, mx43, mx119, mx59, mx45, mx76, mx128, mx81, mx136, mx55, mx138, mx123, mx126, mx141, mx103, mx20, mx105, mx113, mx143, mx36); m.add_instruction(pass_op{}); auto mx157 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx157, mx101, mx8, mx115, mx130, mx105, mx38, mx147, mx93, mx64, mx61, mx98, mx40, mx27, mx121, mx30, mx154, mx113, mx73, mx119, mx36, mx150, mx69, mx67, mx47, mx110, mx32, mx22, mx15, mx18, mx123, mx143); m.add_instruction(pass_op{}); auto mx160 = m.add_instruction(pass_op{}, mx157, mx141); m.add_instruction(pass_op{}, mx160, mx101, mx8, mx115, mx130, mx105, mx38, mx147, mx93, mx64, mx61, mx98, mx40, mx27, mx121, mx30, mx154, mx113, mx73, mx119, mx36, mx150, mx69, mx67, mx47, mx110, mx32, mx22, mx15, mx18, mx123, mx143); auto mx162 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx162, mx101, mx8, mx115, mx130, mx105, mx38, mx147, mx93, mx64, mx61, mx98, mx40, mx27, mx121, mx30, mx154, mx113, mx73, mx119, mx36, mx150, mx69, mx67, mx47, mx110, mx32, mx22, mx15, mx18, mx123, mx143); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx166 = m.add_instruction(pass_op{}, mx162, mx147, mx160, mx154); m.add_instruction(pass_op{}, mx166, mx101, mx8, mx115, mx130, mx105, mx38, mx147, mx93, mx64, mx61, mx98, mx40, mx27, mx121, mx30, mx154, mx113, mx73, mx119, mx36, mx150, mx69, mx67, mx47, mx110, mx32, mx22, mx15, mx18, mx123, mx143); m.add_instruction(pass_op{}); auto mx169 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 15}}); m.add_instruction(pass_op{}, mx169, mx154, mx90, mx88, mx79, mx126, mx15, mx103, mx22, mx134, mx166, mx30, mx73, mx20, mx128, mx160, mx8, mx45, mx0, mx6, mx157, mx53, mx136, mx93, mx47, mx81, mx141, mx85, mx110, mx59, mx1, mx162, mx101, mx36, mx38, mx76, mx143, mx67, mx147, mx150, mx138, mx115, mx105, mx51, mx69, mx40, mx32, mx43, mx55, mx130, mx98); auto mx171 = m.add_instruction(pass_op{}, mseq); auto mx172 = m.add_instruction(pass_op{}, mx171); m.add_instruction(pass_op{}); auto mx174 = m.add_instruction(pass_op{}, mx169, mx172, mx13); m.add_instruction(pass_op{}, mx174, mx154, mx90, mx88, mx79, mx126, mx15, mx103, mx22, mx134, mx166, mx30, mx73, mx20, mx128, mx160, mx8, mx45, mx0, mx6, mx157, mx53, mx136, mx93, mx47, mx81, mx141, mx85, mx110, mx59, mx1, mx162, mx101, mx36, mx38, mx76, mx143, mx67, mx147, mx150, mx138, mx115, mx105, mx51, mx69, mx40, mx32, mx43, mx55, mx130, mx98); m.add_instruction(pass_op{}); auto mx177 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 10}}); m.add_instruction(pass_op{}, mx177, mx101, mx8, mx115, mx130, mx105, mx38, mx147, mx93, mx64, mx154, mx61, mx98, mx40, mx27, mx174, mx121, mx30, mx113, mx73, mx119, mx36, mx150, mx69, mx67, mx47, mx110, mx32, mx22, mx169, mx15, mx18, mx123, mx143); m.add_instruction(pass_op{}); auto mx180 = m.add_instruction(pass_op{}, mx177, mx166, mx4); m.add_instruction(pass_op{}, mx180, mx101, mx8, mx115, mx130, mx105, mx38, mx147, mx93, mx64, mx154, mx61, mx98, mx40, mx27, mx174, mx121, mx30, mx113, mx73, mx119, mx36, mx150, mx69, mx67, mx47, mx110, mx32, mx22, mx169, mx15, mx18, mx123, mx143); m.add_instruction(pass_op{}); auto mx183 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx183, mx67, mx90, mx150, mx138, mx88, mx79, mx126, mx15, mx103, mx22, mx134, mx180, mx166, mx174, mx73, mx20, mx154, mx32, mx43, mx55, mx157, mx18, mx0, mx113, mx6, mx76, mx53, mx61, mx177, mx136, mx81, mx141, mx85, mx110, mx64, mx45, mx8, mx169, mx59, mx1, mx162, mx101, mx119, mx51, mx69, mx128, mx160, mx27, mx47, mx123, mx121); auto mx185 = m.add_instruction(pass_op{}, mx180); m.add_instruction(pass_op{}, mx185, mx101, mx8, mx115, mx130, mx105, mx38, mx147, mx93, mx64, mx154, mx61, mx98, mx40, mx27, mx183, mx174, mx121, mx30, mx113, mx73, mx119, mx36, mx150, mx69, mx67, mx47, mx110, mx32, mx22, mx169, mx15, mx18, mx123, mx143); auto mx187 = m.add_instruction(pass_op{}, mx174); m.add_instruction(pass_op{}, mx187, mx150, mx128, mx67, mx15, mx88, mx43, mx79, mx126, mx103, mx22, mx90, mx180, mx183, mx166, mx141, mx30, mx20, mx59, mx55, mx38, mx160, mx0, mx32, mx85, mx6, mx76, mx157, mx45, mx162, mx138, mx154, mx53, mx177, mx136, mx51, mx47, mx81, mx93, mx73, mx8, mx110, mx101, mx69, mx185, mx36, mx143, mx147, mx134, mx1, mx130, mx115, mx105, mx40, mx98); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx192 = m.add_instruction(pass_op{}, mx183, mx187, mx185); m.add_instruction(pass_op{}, mx192, mx150, mx128, mx67, mx187, mx15, mx88, mx43, mx79, mx126, mx103, mx64, mx22, mx90, mx180, mx141, mx20, mx59, mx134, mx1, mx55, mx113, mx160, mx0, mx32, mx85, mx6, mx76, mx157, mx45, mx162, mx138, mx154, mx53, mx61, mx177, mx174, mx136, mx119, mx185, mx51, mx47, mx81, mx73, mx8, mx110, mx18, mx169, mx101, mx69, mx27, mx123, mx166, mx121); m.add_instruction(pass_op{}); auto mx195 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx195, mx115, mx105, mx98, mx123, mx27, mx126, mx103, mx64, mx183, mx174, mx136, mx177, mx141, mx51, mx93, mx113, mx38, mx160, mx55, mx30, mx61, mx138, mx53, mx76, mx85, mx6, mx20, mx59, mx0, mx40, mx43, mx88, mx79, mx180, mx90, mx187, mx81, mx128, mx157, mx45, mx162, mx134, mx1, mx130, mx147, mx166, mx121, mx18, mx169, mx143, mx119, mx36, mx185, mx192); auto mx197 = m.add_instruction(pass_op{}, mx174); m.add_instruction(pass_op{}, mx197, mx128, mx150, mx101, mx69, mx126, mx103, mx22, mx166, mx183, mx136, mx177, mx141, mx30, mx73, mx93, mx38, mx160, mx55, mx76, mx32, mx85, mx6, mx20, mx59, mx0, mx43, mx15, mx88, mx79, mx180, mx90, mx67, mx81, mx138, mx154, mx53, mx157, mx45, mx162, mx51, mx47, mx195, mx110, mx8, mx143, mx147, mx134, mx1, mx130, mx115, mx105, mx40, mx98, mx36, mx185, mx192); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx201 = m.add_instruction(pass_op{}, mx195, mx197); m.add_instruction(pass_op{}, mx201, mx115, mx105, mx98, mx123, mx27, mx126, mx103, mx64, mx183, mx174, mx136, mx177, mx141, mx51, mx93, mx113, mx38, mx160, mx55, mx30, mx61, mx138, mx53, mx76, mx85, mx6, mx20, mx59, mx0, mx40, mx43, mx197, mx88, mx79, mx180, mx90, mx187, mx81, mx128, mx157, mx45, mx162, mx134, mx1, mx130, mx147, mx166, mx121, mx18, mx169, mx143, mx119, mx36, mx185, mx192); auto mx203 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx203, mx115, mx105, mx98, mx123, mx27, mx126, mx103, mx64, mx183, mx174, mx136, mx177, mx141, mx51, mx93, mx113, mx38, mx160, mx55, mx30, mx61, mx138, mx53, mx76, mx85, mx6, mx20, mx59, mx0, mx40, mx43, mx197, mx88, mx79, mx180, mx90, mx187, mx81, mx128, mx157, mx45, mx162, mx134, mx1, mx130, mx147, mx166, mx121, mx18, mx169, mx143, mx119, mx36, mx185, mx192); auto mx205 = m.add_instruction(pass_op{}, mx203, mx201); m.add_instruction(pass_op{}, mx205, mx115, mx105, mx98, mx123, mx27, mx126, mx103, mx64, mx183, mx174, mx136, mx177, mx141, mx51, mx93, mx113, mx38, mx160, mx55, mx30, mx61, mx138, mx53, mx76, mx85, mx6, mx20, mx59, mx0, mx40, mx43, mx197, mx88, mx79, mx180, mx90, mx187, mx81, mx128, mx157, mx45, mx162, mx134, mx1, mx130, mx147, mx166, mx121, mx18, mx169, mx143, mx119, mx36, mx185, mx192); m.add_instruction(pass_op{}); auto mx208 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx208, mx30, mx40, mx64, mx93, mx18, mx98, mx115, mx143, mx38, mx147, mx183, mx197, mx150, mx119, mx32, mx8, mx105, mx101, mx110, mx195, mx47, mx27, mx22, mx205, mx121, mx67, mx187, mx113, mx73, mx201, mx130, mx203, mx169, mx69, mx15, mx154, mx61, mx174, mx123, mx36, mx192); auto mx210 = m.add_instruction(pass_op{}, mx180); m.add_instruction(pass_op{}, mx210, mx143, mx115, mx18, mx93, mx150, mx47, mx187, mx15, mx169, mx69, mx205, mx32, mx119, mx113, mx73, mx201, mx30, mx67, mx121, mx22, mx27, mx40, mx98, mx174, mx61, mx154, mx64, mx147, mx38, mx203, mx130, mx8, mx110, mx105, mx101, mx195, mx183, mx197, mx123, mx36, mx192); auto mx212 = m.add_instruction(pass_op{}, mx174); m.add_instruction(pass_op{}, mx212, mx32, mx67, mx90, mx15, mx138, mx126, mx103, mx38, mx136, mx180, mx141, mx51, mx30, mx22, mx201, mx59, mx134, mx154, mx150, mx1, mx160, mx45, mx6, mx76, mx88, mx53, mx47, mx183, mx81, mx157, mx93, mx79, mx85, mx0, mx210, mx73, mx8, mx110, mx20, mx69, mx177, mx36, mx143, mx162, mx147, mx130, mx115, mx55, mx105, mx40, mx98, mx208, mx203, mx128, mx205, mx195, mx101, mx185, mx43, mx166, mx192); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx216 = m.add_instruction(pass_op{}, mx208, mx212, mx210); m.add_instruction(pass_op{}, mx216, mx121, mx30, mx64, mx93, mx123, mx143, mx119, mx36, mx150, mx8, mx101, mx169, mx147, mx110, mx27, mx61, mx40, mx205, mx115, mx32, mx69, mx67, mx98, mx187, mx195, mx73, mx105, mx183, mx197, mx22, mx113, mx201, mx47, mx130, mx154, mx15, mx212, mx18, mx174, mx38, mx203, mx192); auto mx218 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx218, mx121, mx30, mx64, mx93, mx123, mx143, mx119, mx36, mx150, mx8, mx101, mx169, mx147, mx110, mx27, mx61, mx40, mx205, mx115, mx32, mx69, mx67, mx98, mx187, mx195, mx73, mx105, mx183, mx197, mx22, mx113, mx201, mx47, mx130, mx154, mx15, mx212, mx18, mx174, mx38, mx203, mx192); auto mx220 = m.add_instruction(pass_op{}, mx218, mx216, mx166); m.add_instruction(pass_op{}, mx220, mx121, mx30, mx64, mx93, mx123, mx143, mx119, mx36, mx150, mx8, mx101, mx169, mx147, mx110, mx27, mx61, mx40, mx205, mx115, mx32, mx69, mx67, mx98, mx187, mx195, mx73, mx105, mx183, mx197, mx22, mx113, mx201, mx47, mx130, mx154, mx15, mx212, mx18, mx174, mx38, mx203, mx192); m.add_instruction(pass_op{}); auto mx223 = m.add_instruction(pass_op{}, mx205, mx220, mx57, mx205); m.add_instruction(pass_op{}, mx223, mx38, mx192, mx203, mx130, mx47, mx143, mx123, mx169, mx121, mx147, mx110, mx27, mx36, mx150, mx119, mx101, mx8, mx64, mx61, mx115, mx32, mx69, mx67, mx98, mx187, mx195, mx73, mx105, mx183, mx197, mx22, mx113, mx201, mx174, mx18, mx93, mx205, mx40, mx30, mx154, mx15, mx212); auto mx225 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx225, mx45, mx59, mx76, mx90, mx218, mx67, mx126, mx103, mx136, mx138, mx15, mx32, mx1, mx160, mx150, mx110, mx51, mx30, mx6, mx157, mx93, mx79, mx85, mx88, mx53, mx154, mx134, mx141, mx180, mx38, mx81, mx223, mx183, mx220, mx210, mx0, mx208, mx20, mx69, mx73, mx185, mx101, mx201, mx22, mx203, mx47, mx128, mx205, mx195, mx8, mx177, mx36, mx55, mx216, mx105, mx115, mx130, mx40, mx98, mx43, mx166, mx192, mx162, mx147, mx143); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx229 = m.add_instruction(pass_op{}, mx225, mx69, mx192); m.add_instruction(pass_op{}, mx229, mx45, mx59, mx76, mx90, mx218, mx67, mx126, mx103, mx136, mx138, mx15, mx32, mx1, mx160, mx150, mx110, mx51, mx30, mx6, mx157, mx93, mx79, mx85, mx88, mx53, mx154, mx134, mx141, mx180, mx38, mx81, mx223, mx183, mx220, mx210, mx0, mx208, mx20, mx69, mx73, mx185, mx101, mx201, mx22, mx203, mx47, mx128, mx205, mx195, mx8, mx177, mx36, mx55, mx216, mx105, mx115, mx130, mx40, mx98, mx43, mx166, mx192, mx162, mx147, mx143); m.add_instruction(pass_op{}); auto mx232 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx232, mx160, mx154, mx76, mx43, mx67, mx55, mx187, mx88, mx126, mx197, mx225, mx136, mx59, mx64, mx15, mx212, mx128, mx32, mx218, mx150, mx216, mx110, mx169, mx103, mx113, mx141, mx79, mx223, mx90, mx6, mx18, mx138, mx210, mx85, mx53, mx61, mx45, mx134, mx119, mx180, mx166, mx20, mx0, mx177, mx81, mx208, mx157, mx185, mx1, mx69, mx201, mx174, mx101, mx51, mx22, mx162, mx220, mx203, mx47, mx195, mx73, mx27, mx205, mx229, mx8, mx123, mx121); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx236 = m.add_instruction(pass_op{}, mx232, mx192, mx166); m.add_instruction(pass_op{}, mx236, mx160, mx154, mx76, mx43, mx67, mx55, mx187, mx88, mx126, mx197, mx225, mx136, mx59, mx64, mx15, mx212, mx128, mx32, mx218, mx150, mx216, mx110, mx169, mx103, mx113, mx141, mx79, mx223, mx90, mx6, mx18, mx138, mx210, mx85, mx53, mx61, mx45, mx134, mx119, mx180, mx166, mx20, mx0, mx177, mx81, mx208, mx157, mx185, mx1, mx69, mx201, mx174, mx101, mx51, mx22, mx162, mx220, mx203, mx47, mx195, mx73, mx27, mx205, mx229, mx8, mx123, mx121); m.add_instruction(pass_op{}); auto mx239 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}, mx239, mx38, mx192, mx232, mx203, mx229, mx183, mx154, mx201, mx113, mx174, mx110, mx197, mx36, mx115, mx150, mx98, mx130, mx32, mx101, mx169, mx8, mx64, mx27, mx225, mx22, mx147, mx67, mx205, mx73, mx61, mx105, mx18, mx47, mx123, mx93, mx195, mx119, mx69, mx40, mx187, mx30, mx15, mx143, mx236, mx121, mx212); m.add_instruction(pass_op{}); auto mx242 = m.add_instruction(pass_op{}, mx239, mx223); m.add_instruction(pass_op{}, mx242, mx38, mx192, mx232, mx203, mx229, mx183, mx154, mx201, mx113, mx174, mx110, mx197, mx36, mx115, mx150, mx98, mx130, mx32, mx101, mx169, mx8, mx64, mx27, mx225, mx22, mx147, mx67, mx205, mx73, mx61, mx105, mx18, mx47, mx123, mx93, mx195, mx119, mx69, mx40, mx187, mx30, mx15, mx143, mx236, mx121, mx212); auto mx244 = add_alloc(m, migraphx::shape{migraphx::shape::float_type, {2, 5}}); m.add_instruction(pass_op{}); m.add_instruction(pass_op{}); auto mx247 = m.add_instruction(pass_op{}, mx244, mx229, mx242, mx236); auto moutput = m.add_parameter("output", migraphx::shape{migraphx::shape::float_type, {3, 1, 2, 5}}); auto mx248 = m.add_instruction(pass_op{}, mx247); auto mx249 = m.add_instruction(pass_op{}, mx166); auto mx250 = m.add_instruction(pass_op{}, mx85); m.add_instruction(pass_op{}, moutput, mx250, mx249, mx248); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 1824); // Optimal is 1600 CHECK(no_allocate(m)); CHECK(is_disjoint({mx0, mx8})); CHECK(is_disjoint({mx0, mx8})); CHECK(is_disjoint({mx0, mx18, mx8})); CHECK(is_disjoint({mx0, mx18, mx8})); CHECK(is_disjoint({mx0, mx18, mx8})); CHECK(is_disjoint({mx0, mx18, mx8})); CHECK(is_disjoint({mx0, mx18, mx8})); CHECK(is_disjoint({mx0, mx18, mx30, mx8})); CHECK(is_disjoint({mx0, mx18, mx30, mx8})); CHECK(is_disjoint({mx30, mx8})); CHECK(is_disjoint({mx0, mx18, mx30, mx8})); CHECK(is_disjoint({mx0, mx18, mx38, mx8})); CHECK(is_disjoint({mx30, mx38})); CHECK(is_disjoint({mx0, mx18, mx38, mx8})); CHECK(is_disjoint({mx18, mx30, mx38, mx43, mx8})); CHECK(is_disjoint({mx0, mx18, mx30, mx38, mx8})); CHECK(is_disjoint({mx0, mx18, mx30, mx38, mx43, mx8})); CHECK(is_disjoint({mx0, mx43, mx8})); CHECK(is_disjoint({mx18, mx30, mx38, mx43, mx8})); CHECK(is_disjoint({mx18, mx30, mx38, mx53, mx8})); CHECK(is_disjoint({mx43, mx53})); CHECK(is_disjoint({mx18, mx30, mx38, mx53, mx8})); CHECK(is_disjoint({mx38, mx53})); CHECK(is_disjoint({mx18, mx30, mx38, mx8})); CHECK(is_disjoint({mx0, mx30, mx38, mx43, mx53, mx61, mx8})); CHECK(is_disjoint({mx18, mx61})); CHECK(is_disjoint({mx0, mx30, mx38, mx43, mx53, mx61, mx8})); CHECK(is_disjoint({mx0, mx18, mx30, mx38, mx43, mx53, mx61, mx67})); CHECK(is_disjoint({mx0, mx18, mx30, mx38, mx43, mx53, mx61})); CHECK(is_disjoint({mx18, mx67})); CHECK(is_disjoint({mx0, mx18, mx30, mx38, mx43, mx53, mx61, mx67})); CHECK(is_disjoint({mx18, mx30, mx38, mx61, mx67, mx76, mx8})); CHECK(is_disjoint({mx38, mx76})); CHECK(is_disjoint({mx18, mx30, mx38, mx61, mx67, mx76, mx8})); CHECK(is_disjoint({mx18, mx30, mx38, mx61, mx67, mx8, mx81})); CHECK(is_disjoint({mx61, mx67, mx76, mx81})); CHECK(is_disjoint({mx18, mx30, mx38, mx61, mx67, mx8, mx81})); CHECK(is_disjoint({mx18, mx30, mx38, mx61, mx67, mx8, mx88})); CHECK(is_disjoint({mx81, mx88})); CHECK(is_disjoint({mx18, mx30, mx38, mx61, mx67, mx8, mx88})); CHECK(is_disjoint({mx0, mx18, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx0, mx18, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx0, mx101, mx18, mx30, mx38, mx43, mx53, mx61, mx76, mx81, mx88, mx93})); CHECK(is_disjoint({mx101, mx18, mx30, mx38, mx61, mx67, mx8, mx88, mx93})); CHECK( is_disjoint({mx0, mx101, mx18, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx101, mx88, mx93})); CHECK(is_disjoint({mx0, mx101, mx18, mx30, mx38, mx43, mx53, mx61, mx76, mx81, mx88, mx93})); CHECK(is_disjoint( {mx0, mx101, mx113, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint( {mx0, mx101, mx113, mx18, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx113, mx93})); CHECK(is_disjoint( {mx0, mx101, mx113, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint( {mx0, mx101, mx121, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx113, mx121})); CHECK(is_disjoint( {mx0, mx101, mx121, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx101, mx113, mx121, mx126, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx101, mx113, mx121, mx18, mx30, mx38, mx61, mx67, mx8, mx88, mx93})); CHECK(is_disjoint({mx0, mx101, mx113, mx121, mx126, mx18, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx126, mx88, mx93})); CHECK(is_disjoint({mx101, mx113, mx121, mx126, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx101, mx113, mx121, mx136, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx126, mx136, mx81})); CHECK(is_disjoint({mx101, mx113, mx121, mx136, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx121, mx136})); CHECK(is_disjoint({mx101, mx113, mx121, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx0, mx101, mx113, mx121, mx126, mx136, mx143, mx18, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88})); CHECK(is_disjoint({mx101, mx143})); CHECK(is_disjoint({mx0, mx101, mx113, mx121, mx126, mx136, mx143, mx18, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88})); CHECK(is_disjoint({mx0, mx113, mx121, mx126, mx136, mx143, mx150, mx18, mx30, mx38, mx43, mx53, mx61, mx76, mx81, mx88, mx93})); CHECK(is_disjoint({mx101, mx150, mx81})); CHECK(is_disjoint({mx0, mx113, mx121, mx126, mx136, mx143, mx150, mx18, mx30, mx38, mx43, mx53, mx61, mx76, mx81, mx88, mx93})); CHECK(is_disjoint( {mx101, mx113, mx121, mx143, mx150, mx157, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx121, mx157})); CHECK(is_disjoint( {mx101, mx113, mx121, mx143, mx150, mx157, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint( {mx101, mx113, mx121, mx143, mx150, mx162, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx143, mx150, mx157, mx162})); CHECK(is_disjoint( {mx101, mx113, mx121, mx143, mx150, mx162, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx0, mx101, mx121, mx126, mx136, mx143, mx150, mx157, mx162, mx169, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx0, mx101, mx121, mx126, mx136, mx143, mx150, mx157, mx162, mx169, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx177, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx162, mx177})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx177, mx18, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx0, mx101, mx113, mx121, mx126, mx136, mx150, mx157, mx162, mx169, mx177, mx18, mx183, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx177, mx18, mx183, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK( is_disjoint({mx0, mx101, mx121, mx126, mx136, mx143, mx150, mx157, mx162, mx169, mx177, mx183, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx169, mx177, mx183})); CHECK(is_disjoint({mx0, mx101, mx113, mx121, mx126, mx136, mx150, mx157, mx162, mx169, mx177, mx18, mx183, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88})); CHECK( is_disjoint({mx0, mx113, mx121, mx126, mx136, mx143, mx157, mx162, mx169, mx177, mx18, mx183, mx195, mx30, mx38, mx43, mx53, mx61, mx76, mx81, mx88, mx93})); CHECK(is_disjoint({mx0, mx101, mx121, mx126, mx136, mx143, mx150, mx157, mx162, mx169, mx177, mx183, mx195, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx169, mx195})); CHECK( is_disjoint({mx0, mx113, mx121, mx126, mx136, mx143, mx157, mx162, mx169, mx177, mx18, mx183, mx195, mx30, mx38, mx43, mx53, mx61, mx76, mx81, mx88, mx93})); CHECK( is_disjoint({mx0, mx113, mx121, mx126, mx136, mx143, mx157, mx162, mx169, mx177, mx18, mx183, mx203, mx30, mx38, mx43, mx53, mx61, mx76, mx81, mx88, mx93})); CHECK(is_disjoint({mx195, mx203})); CHECK( is_disjoint({mx0, mx113, mx121, mx126, mx136, mx143, mx157, mx162, mx169, mx177, mx18, mx183, mx203, mx30, mx38, mx43, mx53, mx61, mx76, mx81, mx88, mx93})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx18, mx183, mx195, mx203, mx208, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx177, mx18, mx183, mx195, mx203, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx0, mx101, mx121, mx126, mx136, mx143, mx150, mx157, mx162, mx169, mx177, mx183, mx195, mx203, mx208, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx169, mx177, mx208})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx18, mx183, mx195, mx203, mx208, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx18, mx183, mx195, mx203, mx218, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx162, mx208, mx218})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx18, mx183, mx195, mx203, mx218, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx203, mx218})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx18, mx183, mx195, mx203, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx0, mx101, mx121, mx126, mx136, mx143, mx150, mx157, mx162, mx177, mx183, mx195, mx203, mx208, mx218, mx225, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx183, mx225})); CHECK(is_disjoint({mx0, mx101, mx121, mx126, mx136, mx143, mx150, mx157, mx162, mx177, mx183, mx195, mx203, mx208, mx218, mx225, mx30, mx38, mx43, mx53, mx67, mx76, mx8, mx81, mx88, mx93})); CHECK(is_disjoint({mx0, mx101, mx113, mx121, mx126, mx136, mx150, mx157, mx162, mx169, mx177, mx18, mx195, mx203, mx208, mx218, mx225, mx232, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88})); CHECK(is_disjoint({mx162, mx183, mx232})); CHECK(is_disjoint({mx0, mx101, mx113, mx121, mx126, mx136, mx150, mx157, mx162, mx169, mx177, mx18, mx195, mx203, mx208, mx218, mx225, mx232, mx38, mx43, mx53, mx61, mx67, mx76, mx8, mx81, mx88})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx18, mx183, mx195, mx203, mx225, mx232, mx239, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx203, mx239})); CHECK(is_disjoint({mx101, mx113, mx121, mx143, mx150, mx169, mx18, mx183, mx195, mx203, mx225, mx232, mx239, mx30, mx38, mx61, mx67, mx8, mx93})); CHECK(is_disjoint({mx225, mx232, mx239, mx244})); CHECK(is_disjoint({mx162, mx244, mx81})); } TEST_CASE(literal_test) { migraphx::program p; auto* mm = p.get_main_module(); auto lit = generate_literal(migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); mm->add_literal(lit); run_pass(*mm); auto result = p.eval({}).back(); CHECK(lit == result); } TEST_CASE(test_tuple) { migraphx::module m; auto s1 = migraphx::shape{migraphx::shape::float_type, {8}}; auto s2 = migraphx::shape{migraphx::shape::half_type, {10}}; auto s = migraphx::shape{{s1, s2}}; auto a1 = add_alloc(m, s); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {4}}); m.add_instruction(pass_op{}, a2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 68); CHECK(no_allocate(m)); CHECK(is_disjoint({a1, a2})); } TEST_CASE(test_large_offsets) { migraphx::module m; auto a1 = add_alloc(m, {migraphx::shape::float_type, {10000000000}}); auto m1 = m.add_instruction(pass_op{}, a1); auto a2 = add_alloc(m, {migraphx::shape::float_type, {10000000000}}); m.add_instruction(pass_op{}, a2, m1); run_pass(m); CHECK(m.get_parameter_shape("scratch").bytes() == 80000000000); CHECK(no_allocate(m)); CHECK(is_disjoint({a1, a2})); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/module_test.cpp000066400000000000000000001407471510465702400205640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include // Check the module is topologically sorted // TODO: Use test::make_predicate static bool is_sorted(migraphx::module& m) { std::unordered_set visited; for(auto ins : migraphx::iterator_for(m)) { visited.insert(ins); if(std::any_of(ins->inputs().begin(), ins->inputs().end(), [&](auto i) { return not visited.count(i); })) { return false; // Found an input that has not been visited yet } } return true; } static void shuffle_module(migraphx::module& m) { if(m.size() < 2) return; std::vector permutation(m.size() - 1); std::iota(permutation.begin(), permutation.end(), 0); std::mt19937 g(permutation.size()); std::shuffle(permutation.begin(), permutation.end(), g); permutation.push_back(permutation.size()); m.shuffle(permutation); } static void reverse_module(migraphx::module& m) { if(m.size() < 2) return; std::vector permutation(m.size() - 1); std::iota(permutation.begin(), permutation.end(), 0); std::reverse(permutation.begin(), permutation.end()); permutation.push_back(permutation.size()); m.shuffle(permutation); } static migraphx::program create_program() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::int64_type}); auto y = mm->add_parameter("y", {migraphx::shape::int64_type}); auto sum = mm->add_instruction(sum_op{}, x, y); auto one = mm->add_literal(1); mm->add_instruction(sum_op{}, sum, one); return p; } TEST_CASE(calc_implict_deps) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; migraphx::shape ys{migraphx::shape::float_type, {3, 3}}; std::vector datax = {1, 2, 3, 4, 5, 6}; std::vector datay = {8, 7, 6, 5, 4, 3, 2, 1, 0}; auto lx = mm->add_literal(migraphx::literal(xs, datax)); auto ly = mm->add_literal(migraphx::literal(ys, datay)); auto cond = mm->add_parameter("cond", cond_s); auto x1 = mm->add_parameter("x1", xs); auto x2 = mm->add_parameter("x2", xs); auto y2 = mm->add_parameter("y2", ys); auto* then_mod = p.create_module("If_5_if"); auto l1 = then_mod->add_literal(migraphx::literal(ys, datay)); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), x1, lx); then_mod->add_return({a1, l1}); auto* then_mod1 = p.create_module("If_6_if"); auto l11 = then_mod1->add_literal(migraphx::literal(ys, datay)); auto a11 = then_mod1->add_instruction(migraphx::make_op("add"), x2, lx); then_mod1->add_return({a11, l11}); auto* else_mod1 = p.create_module("If_6_else"); auto l21 = else_mod1->add_literal(migraphx::literal(xs, datax)); auto a21 = else_mod1->add_instruction(migraphx::make_op("mul"), y2, ly); else_mod1->add_return({l21, a21}); auto* else_mod = p.create_module("If_5_else"); auto l2 = else_mod->add_literal(migraphx::literal(ys, datay)); auto a2 = else_mod->add_instruction(migraphx::make_op("if"), {cond}, {then_mod1, else_mod1}); auto a3 = else_mod->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), a2); else_mod->add_return({a3, l2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); auto implicit_deps = mm->calc_implicit_deps(); EXPECT(migraphx::contains(implicit_deps, ret)); EXPECT(migraphx::contains(implicit_deps.at(ret), x1)); EXPECT(migraphx::contains(implicit_deps.at(ret), x2)); EXPECT(migraphx::contains(implicit_deps.at(ret), y2)); EXPECT(migraphx::contains(implicit_deps.at(ret), lx)); EXPECT(migraphx::contains(implicit_deps.at(ret), ly)); // test for sorting p.sort(); auto ret_inputs = ret->inputs(); ret_inputs.insert(ret_inputs.end(), implicit_deps.at(ret).begin(), implicit_deps.at(ret).end()); EXPECT(std::all_of(ret_inputs.begin(), ret_inputs.end(), [&](const auto i) { return std::distance(mm->begin(), i) < std::distance(mm->begin(), ret); })); } TEST_CASE(module_annotate) { migraphx::program p1 = create_program(); migraphx::program p2 = create_program(); auto* mm1 = p1.get_main_module(); auto* mm2 = p2.get_main_module(); EXPECT(*mm1 == *mm2); std::stringstream ss1; mm1->annotate(ss1, [](auto ins) { std::cout << ins->name() << "_1" << std::endl; }); std::stringstream ss2; mm2->annotate(ss2, [](auto ins) { std::cout << ins->name() << "_1" << std::endl; }); EXPECT(ss1.str() == ss2.str()); } TEST_CASE(module_ins_clear) { migraphx::program p1 = create_program(); migraphx::program p2; p2 = p1; EXPECT(p1 == p2); } TEST_CASE(module_name) { migraphx::module m1("name"); EXPECT(m1.name() == "name"); auto m2 = m1; // NOLINT EXPECT(m2.name() == "name"); migraphx::module m3; m3 = m1; EXPECT(m3.name() == "name"); } TEST_CASE(module_name_main) { migraphx::program p; auto* mm = p.get_main_module(); EXPECT(mm->name() == "main"); } TEST_CASE(module_print_cpp) { migraphx::program p1 = create_program(); migraphx::program p2 = create_program(); auto* mm1 = p1.get_main_module(); auto* mm2 = p2.get_main_module(); std::stringstream ss1; mm1->print_cpp(ss1); std::stringstream ss2; mm2->print_cpp(ss2); EXPECT(ss1.str() == ss2.str()); } TEST_CASE(module_print_graph) { migraphx::program p1 = create_program(); migraphx::program p2 = create_program(); auto* mm1 = p1.get_main_module(); auto* mm2 = p2.get_main_module(); std::stringstream ss1; mm1->print_graph(ss1, true); std::stringstream ss2; mm2->print_graph(ss2, true); EXPECT(ss1.str() == ss2.str()); } TEST_CASE(program_module_assign) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", sd); std::vector one(sd.elements(), 1); std::vector two(sd.elements(), 2); auto* then_smod = p.create_module("then_smod"); auto l1 = then_smod->add_literal(migraphx::literal{sd, one}); auto r1 = then_smod->add_instruction(migraphx::make_op("add"), x, l1); then_smod->add_return({r1}); auto* else_smod = p.create_module("else_smod"); auto l2 = else_smod->add_literal(migraphx::literal{sd, two}); auto r2 = else_smod->add_instruction(migraphx::make_op("mul"), x, l2); else_smod->add_return({r2}); migraphx::shape s_cond{migraphx::shape::bool_type, {1}}; auto cond = mm->add_parameter("cond", s_cond); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_smod, else_smod}); mm->add_return({ret}); migraphx::program p1 = p; EXPECT(p == p1); } TEST_CASE(program_module_replace) { auto create_program = [](bool use_if) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", sd); std::vector one(sd.elements(), 1); std::vector two(sd.elements(), 2); auto* then_smod = p.create_module("then_smod"); auto l1 = then_smod->add_literal(migraphx::literal{sd, one}); auto r1 = then_smod->add_instruction(migraphx::make_op("add"), x, l1); then_smod->add_return({r1}); auto* else_smod = p.create_module("else_smod"); auto l2 = else_smod->add_literal(migraphx::literal{sd, two}); auto r2 = else_smod->add_instruction(migraphx::make_op("mul"), x, l2); else_smod->add_return({r2}); migraphx::shape s_cond{migraphx::shape::bool_type, {1}}; auto cond = mm->add_parameter("cond", s_cond); migraphx::instruction_ref ret{}; if(use_if) { ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_smod, else_smod}); } else { ret = mm->add_instruction(mod_pass_op{}, {cond}, {then_smod, else_smod}); } mm->add_return({ret}); return p; }; migraphx::program p1 = create_program(false); migraphx::program p2 = create_program(true); EXPECT(p1 != p2); auto* m1 = p1.get_main_module(); auto ins_pass = std::prev(std::prev(m1->end())); const auto& inputs = ins_pass->inputs(); const auto& mod_inputs = ins_pass->module_inputs(); m1->replace_instruction(ins_pass, migraphx::make_op("if"), inputs, mod_inputs); EXPECT(p1 == p2); } TEST_CASE(submodule_copy) { migraphx::module mm("main"); auto x = mm.add_parameter("x", {migraphx::shape::int64_type}); migraphx::module sm("sub"); sm.add_instruction(migraphx::make_op("sin"), x); mm.add_instruction(migraphx::make_op("if"), {x}, {&sm, &sm}); auto mm2 = mm; EXPECT(mm == mm2); EXPECT(mm.get_sub_modules() == mm2.get_sub_modules()); } TEST_CASE(parameter_name_order) { migraphx::shape s{migraphx::shape::int32_type, {1}}; migraphx::module mm("main"); auto x1 = mm.add_parameter("x1", s); auto x2 = mm.add_parameter("x2", s); auto x3 = mm.add_parameter("x3", s); auto x4 = mm.add_parameter("x4", s); std::vector param_names = {"x1", "x2", "x3", "x4"}; auto sum1 = mm.add_instruction(migraphx::make_op("add"), x1, x2); auto sum2 = mm.add_instruction(migraphx::make_op("add"), x3, x4); auto r = mm.add_instruction(migraphx::make_op("mul"), sum1, sum2); mm.add_return({r}); auto names = mm.get_parameter_names(); EXPECT(param_names == names); auto m1 = mm; auto names1 = m1.get_parameter_names(); EXPECT(param_names == names1); } struct map_ins { using type = std::unordered_map; map_ins(std::initializer_list x) : m(x) {} operator type*() { return &m; } type m; }; TEST_CASE(insert_instructions_module) { migraphx::shape s{migraphx::shape::int32_type, {1}}; migraphx::module m1("m1"); auto x1 = m1.add_parameter("x1", s); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), {x1}); m1.add_instruction(migraphx::make_op("add"), {sqrt, x1}); migraphx::module m2("m2"); auto x2 = m2.add_parameter("x2", s); m2.add_instruction(migraphx::make_op("sqrt"), {x2}); m1.insert_instructions(sqrt, &m2, map_ins{{x2, x1}}); EXPECT(std::prev(sqrt)->name() == "sqrt"); EXPECT(std::count_if(m1.begin(), m1.end(), [](auto&& ins) { return ins.name() == "sqrt"; }) == 2); EXPECT(std::count_if(m1.begin(), m1.end(), [](auto&& ins) { return ins.name() == "@param"; }) == 1); EXPECT(contains(m1.get_parameter_shapes(), "x1")); EXPECT(not contains(m1.get_parameter_shapes(), "x2")); } TEST_CASE(add_instructions_module) { migraphx::shape s{migraphx::shape::int32_type, {1}}; migraphx::module m1("m1"); auto x1 = m1.add_parameter("x1", s); m1.add_instruction(migraphx::make_op("sqrt"), {x1}); migraphx::module m2("m2"); auto x2 = m2.add_parameter("x2", s); m2.add_instruction(migraphx::make_op("sqrt"), {x2}); m1.add_instructions(&m2, map_ins{{x2, x1}}); EXPECT(std::count_if(m1.begin(), m1.end(), [](auto&& ins) { return ins.name() == "sqrt"; }) == 2); EXPECT(std::count_if(m1.begin(), m1.end(), [](auto&& ins) { return ins.name() == "@param"; }) == 1); EXPECT(contains(m1.get_parameter_shapes(), "x1")); EXPECT(not contains(m1.get_parameter_shapes(), "x2")); } TEST_CASE(add_instructions_range) { migraphx::shape s{migraphx::shape::int32_type, {1}}; migraphx::module m1("m1"); auto x1 = m1.add_parameter("x1", s); m1.add_instruction(migraphx::make_op("sqrt"), {x1}); migraphx::module m2("m2"); auto x2 = m2.add_parameter("x2", s); auto sqrt2 = m2.add_instruction(migraphx::make_op("sqrt"), {x2}); m1.add_instructions(sqrt2, m2.end(), map_ins{{x2, x1}}); EXPECT(std::any_of( m1.begin(), m1.end(), [&](auto&& ins) { return migraphx::contains(ins.inputs(), x1); })); EXPECT(std::count_if(m1.begin(), m1.end(), [](auto&& ins) { return ins.name() == "sqrt"; }) == 2); EXPECT(std::count_if(m1.begin(), m1.end(), [](auto&& ins) { return ins.name() == "@param"; }) == 1); EXPECT(contains(m1.get_parameter_shapes(), "x1")); EXPECT(not contains(m1.get_parameter_shapes(), "x2")); } TEST_CASE(add_instructions_vector) { migraphx::shape s{migraphx::shape::int32_type, {1}}; migraphx::module m1("m1"); auto x1 = m1.add_parameter("x1", s); m1.add_instruction(migraphx::make_op("sqrt"), {x1}); migraphx::module m2("m2"); auto x2 = m2.add_parameter("x2", s); auto sqrt2 = m2.add_instruction(migraphx::make_op("sqrt"), {x2}); m1.add_instructions({sqrt2}, map_ins{{x2, x1}}); EXPECT(std::any_of( m1.begin(), m1.end(), [&](auto&& ins) { return migraphx::contains(ins.inputs(), x1); })); EXPECT(std::count_if(m1.begin(), m1.end(), [](auto&& ins) { return ins.name() == "sqrt"; }) == 2); EXPECT(std::count_if(m1.begin(), m1.end(), [](auto&& ins) { return ins.name() == "@param"; }) == 1); EXPECT(contains(m1.get_parameter_shapes(), "x1")); EXPECT(not contains(m1.get_parameter_shapes(), "x2")); } struct check_for_pass_op { bool* found = nullptr; std::string name() const { return "check_for_pass_op"; } void apply(migraphx::module& m) const { *found |= std::any_of(m.begin(), m.end(), [](auto&& ins) { return ins.name() == "pass"; }); } }; TEST_CASE(module_bypass) { migraphx::program p; auto* mm = p.get_main_module(); auto* sub = p.create_module("sub"); sub->set_bypass(); sub->add_instruction(pass_op{}); mm->add_instruction(mod_pass_op{}, {}, {sub}); bool found = false; migraphx::run_passes(p, {check_for_pass_op{&found}}); EXPECT(not found); } TEST_CASE(module_without_bypass) { migraphx::program p; auto* mm = p.get_main_module(); auto* sub = p.create_module("sub"); sub->add_instruction(pass_op{}); mm->add_instruction(mod_pass_op{}, {}, {sub}); bool found = false; migraphx::run_passes(p, {check_for_pass_op{&found}}); EXPECT(found); } TEST_CASE(multiple_module_dependency) { // Test when an instruction from a submodule depends on previous module migraphx::program p; auto* mm = p.get_main_module(); auto* sub = p.create_module("sub"); auto l1 = mm->add_literal(migraphx::literal(3)); // second same literal to make sure instruction_ref is being compared, rather than the // instructions sub->add_literal(migraphx::literal(3)); sub->add_instruction(sum_op{}, l1, l1); EXPECT((sub->validate() == sub->end())); } TEST_CASE(module_split2) { migraphx::shape s{migraphx::shape::float_type, {1}}; migraphx::module input_m; std::vector inputs; { auto x1 = input_m.add_parameter("x1", s); auto x2 = input_m.add_parameter("x2", s); auto x3 = input_m.add_parameter("x3", s); auto sx1 = input_m.add_instruction(migraphx::make_op("sqrt"), x1); auto sx2 = input_m.add_instruction(migraphx::make_op("sqrt"), x2); auto sx3 = input_m.add_instruction(migraphx::make_op("sqrt"), x3); inputs = {sx1, sx2, sx3}; } migraphx::module m; std::vector splits; { auto x1 = m.add_parameter("x1", s); auto x2 = m.add_parameter("x2", s); auto x3 = m.add_parameter("x3", s); auto add = m.add_instruction(migraphx::make_op("add"), x1, x2); auto mul = m.add_instruction(migraphx::make_op("mul"), add, x3); m.add_return({mul}); splits.push_back(add); } auto mods = m.split(inputs, splits); migraphx::module m1; { auto x1 = m1.add_parameter("x1", s); auto x2 = m1.add_parameter("x2", s); auto add = m1.add_instruction(migraphx::make_op("add"), x1, x2); m1.add_return({add}); } migraphx::module m2; { auto x0 = m2.add_parameter("x0", s); auto x1 = m2.add_parameter("x1", s); auto mul = m2.add_instruction(migraphx::make_op("mul"), x0, x1); m2.add_return({mul}); } EXPECT(mods[0].mod.sort() == m1.sort()); EXPECT(mods[1].mod.sort() == m2.sort()); EXPECT(mods[0].inputs[0] == inputs[0]); EXPECT(mods[0].inputs[1] == inputs[1]); EXPECT(mods[1].inputs[0] == splits.front()); EXPECT(mods[1].inputs[1] == inputs[2]); } TEST_CASE(module_split_2_dot_ins) { std::vector inputs; std::vector mod_0_expected_inputs; std::vector mod_1_expected_inputs; migraphx::shape s1 = migraphx::shape{migraphx::shape::float_type, {2, 5}}; migraphx::shape s2 = migraphx::shape{migraphx::shape::float_type, {5, 5}}; migraphx::module input_m; { auto x1 = input_m.add_parameter("x1", s1); auto x2 = input_m.add_parameter("x2", s1); auto x3 = input_m.add_parameter("x3", s1); auto x4 = input_m.add_parameter("x4", s1); auto y0 = input_m.add_parameter("y0", s1); auto y1 = input_m.add_parameter("y1", s2); auto sx1 = input_m.add_instruction(migraphx::make_op("sqrt"), x1); auto sx2 = input_m.add_instruction(migraphx::make_op("sqrt"), x2); auto sx3 = input_m.add_instruction(migraphx::make_op("sqrt"), x3); auto sx4 = input_m.add_instruction(migraphx::make_op("sqrt"), x4); auto sy0 = input_m.add_instruction(migraphx::make_op("sqrt"), y0); auto sy1 = input_m.add_instruction(migraphx::make_op("sqrt"), y1); inputs = {sx1, sx2, sx3, sx4, sy0, sy1}; mod_0_expected_inputs = {sy0, sy1}; mod_1_expected_inputs = {sx4, sx3, sx2, sx1}; } std::vector splits; migraphx::module m1; { // params --> {x1, x2, x3, x4, y0, y1} binds to input args {sx1, sx2, sx3, sx4, sy0, sy1} auto m1_y0 = m1.add_parameter("y0", s1); auto m1_y1 = m1.add_parameter("y1", s2); auto m1_x1 = m1.add_parameter("x1", s1); auto m1_x2 = m1.add_parameter("x2", s1); auto m1_x3 = m1.add_parameter("x3", s1); auto m1_x4 = m1.add_parameter("x4", s1); // m1_dot = dot(y0, y1) --> dot(sy0, sy1) auto m1_dot = m1.add_instruction(migraphx::make_op("dot"), m1_y0, m1_y1); // m1_add = add(x0, m1_dot) --> add(sx1, m1_dot) auto m1_add_1 = m1.add_instruction(migraphx::make_op("add"), m1_x1, m1_dot); // m1_add_2 = add(x2, x3) --> add(sx2, sx3) auto m1_add_2 = m1.add_instruction(migraphx::make_op("add"), m1_x2, m1_x3); // m1_sub = sub(x4, m1_relu_2) --> sub(sx4, m1_add_2) auto m1_sub = m1.add_instruction(migraphx::make_op("sub"), m1_x4, m1_add_2); // m1_mul = mul(m1_sub, m1_add_1) auto m1_mul = m1.add_instruction(migraphx::make_op("mul"), m1_sub, m1_add_1); m1.add_return({m1_mul}); splits.push_back(m1_dot); } migraphx::module mod_0; { auto mod_0_y1 = mod_0.add_parameter("y1", s2); auto mod_0_y0 = mod_0.add_parameter("y0", s1); // mod_0_dot(y0, y1) --> dot(sy0, sy1) auto mod_0_dot = mod_0.add_instruction(migraphx::make_op("dot"), mod_0_y0, mod_0_y1); mod_0.add_return({mod_0_dot}); } migraphx::module mod_1; { // expected input args are {dot_ins, sx4, sx3, sx2, sx1} auto mod_1_x0 = mod_1.add_parameter("x0", s1); auto mod_1_x1 = mod_1.add_parameter("x1", s1); auto mod_1_x2 = mod_1.add_parameter("x2", s1); auto mod_1_x3 = mod_1.add_parameter("x3", s1); auto mod_1_x4 = mod_1.add_parameter("x4", s1); // m1_add = add(x4, m1_dot) --> add(sx1, m1_dot) auto m1_add = mod_1.add_instruction(migraphx::make_op("add"), mod_1_x4, mod_1_x0); // m1_add_2 = add(x3, x2) --> add(sx2, sx3) auto m1_add_2 = mod_1.add_instruction(migraphx::make_op("add"), mod_1_x3, mod_1_x2); // m1_sub = sub(x1, m1_relu_2) --> sub(sx4, m1_add_2) auto m1_sub = mod_1.add_instruction(migraphx::make_op("sub"), mod_1_x1, m1_add_2); // m1_mul = mul(m1_sub, m1_add) auto m1_mul = mod_1.add_instruction(migraphx::make_op("mul"), m1_sub, m1_add); mod_1.add_return({m1_mul}); } auto mods = m1.split(inputs, splits); EXPECT(mods[0].mod.sort() == mod_0.sort()); const auto mod_0_inputs = mods[0].inputs; EXPECT(mod_0_inputs[0] == mod_0_expected_inputs[0]); EXPECT(mod_0_inputs[1] == mod_0_expected_inputs[1]); const auto mod_1_inputs = mods[1].inputs; // first input arg should be the split instruction EXPECT(mods[1].mod.sort() == mod_1.sort()); EXPECT(mod_1_inputs[0] == splits.front()); EXPECT(mod_1_inputs[1] == mod_1_expected_inputs[0]); EXPECT(mod_1_inputs[2] == mod_1_expected_inputs[1]); EXPECT(mod_1_inputs[3] == mod_1_expected_inputs[2]); EXPECT(mod_1_inputs[4] == mod_1_expected_inputs[3]); } TEST_CASE(module_split3) { migraphx::shape s{migraphx::shape::float_type, {1}}; migraphx::module input_m; std::vector inputs; { auto x1 = input_m.add_parameter("x1", s); auto x2 = input_m.add_parameter("x2", s); auto sx1 = input_m.add_instruction(migraphx::make_op("sqrt"), x1); auto sx2 = input_m.add_instruction(migraphx::make_op("sqrt"), x2); inputs = {sx1, sx2}; } migraphx::module m; std::vector splits1; std::vector splits2; { auto x1 = m.add_parameter("x1", s); auto x2 = m.add_parameter("x2", s); auto mul = m.add_instruction(migraphx::make_op("mul"), x1, x2); auto sqrt = m.add_instruction(migraphx::make_op("sqrt"), mul); auto add = m.add_instruction(migraphx::make_op("add"), sqrt, mul); m.add_return({add}); splits1.push_back(mul); splits2.push_back(sqrt); } auto mods = m.split(inputs, splits1, splits2); migraphx::module m1; { auto x1 = m1.add_parameter("x1", s); auto x2 = m1.add_parameter("x2", s); auto mul = m1.add_instruction(migraphx::make_op("mul"), x1, x2); m1.add_return({mul}); } migraphx::module m2; { auto x0 = m2.add_parameter("x0", s); auto sqrt = m2.add_instruction(migraphx::make_op("sqrt"), x0); m2.add_return({sqrt}); } migraphx::module m3; { auto x0 = m3.add_parameter("x0", s); auto x1 = m3.add_parameter("x1", s); auto add = m3.add_instruction(migraphx::make_op("add"), x0, x1); m3.add_return({add}); } EXPECT(mods[0].mod.sort() == m1.sort()); EXPECT(mods[1].mod.sort() == m2.sort()); EXPECT(mods[2].mod.sort() == m3.sort()); EXPECT(mods[0].inputs[0] == inputs[0]); EXPECT(mods[0].inputs[1] == inputs[1]); EXPECT(mods[1].inputs[0] == splits1.front()); EXPECT(mods[2].inputs[0] == splits2.front()); EXPECT(mods[2].inputs[1] == splits1.front()); } TEST_CASE(fuse_module) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::module m1; { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add = add_pointwise(p, "main:pointwise0", {x, y}, single_pointwise("add")); auto mul = add_pointwise(p, "main:pointwise1", {add, z}, single_pointwise("mul")); std::unordered_map map_ins; auto rins = m1.fuse(*add->module_inputs().front(), add->inputs(), &map_ins).front(); map_ins[add] = rins; auto ret = m1.fuse(*mul->module_inputs().front(), mul->inputs(), &map_ins); m1.add_return(ret); } migraphx::module m2; { auto x = m2.add_parameter("x0", s); auto y = m2.add_parameter("x1", s); auto z = m2.add_parameter("x2", s); auto add = m2.add_instruction(migraphx::make_op("add"), x, y); auto mul = m2.add_instruction(migraphx::make_op("mul"), add, z); m2.add_return({mul}); } EXPECT(m1 == m2); } TEST_CASE(get_inputs) { // Test a use case for get_inputs migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::module m1; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add = add_pointwise(p, "main:pointwise0", {x, z}, single_pointwise("add")); auto mul = add_pointwise(p, "main:pointwise1", {add, y}, single_pointwise("mul")); std::unordered_map map_ins; auto rins = m1.fuse(*add->module_inputs().front(), add->inputs(), &map_ins).front(); map_ins[add] = rins; auto ret = m1.fuse(*mul->module_inputs().front(), mul->inputs(), &map_ins); m1.add_return(ret); // After using the fuse methods above, map_ins contains the following mappings: // - instruction in mm -> instruction in m1 // - instruction in add -> instruction in m1 // - instruction in mul -> instruction in m1 // create a map of instructions in m1 to instruction in mm (all the parameters will // map to some instruction in mm) std::unordered_map map_m1_to_mm; for(auto const& [i1, i2] : map_ins) { if(contains(*mm, i1)) map_m1_to_mm[i2] = i1; } // get_inputs should return the instructions from mm in the correct order that should // be the new inputs to m1 auto inputs = m1.get_inputs(map_m1_to_mm); EXPECT(inputs.size() == 3); EXPECT(inputs[0] == x); EXPECT(inputs[1] == z); EXPECT(inputs[2] == y); } TEST_CASE(add_params) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::module m1; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); auto mul = mm->add_instruction(migraphx::make_op("mul"), add, z); // use case: add and mul are to be used as input parameters to a new submodule m1 std::unordered_map map_ins; m1.add_params({mul, add}, &map_ins); migraphx::module m2; m2.add_parameter("x0", mul->get_shape()); m2.add_parameter("x1", add->get_shape()); // m1 should have parameters x0 and x1 with the shapes of mul and add outputs, respectively EXPECT(m1 == m2); // map_ins should contain a mapping: mul (in mm) -> x0 (in m1) EXPECT(m1.get_parameter("x0") == map_ins[mul]); // map_ins should contain a mapping: add (in mm) -> x1 (in m1) EXPECT(m1.get_parameter("x1") == map_ins[add]); } TEST_CASE(linear_graph_sort) { // // Linear chain test - graph structure: // // x → abs → neg → tanh → return // // Tests the most basic case of topological sorting. // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; auto x = m.add_parameter("x", s); auto a = m.add_instruction(migraphx::make_op("abs"), x); auto n = m.add_instruction(migraphx::make_op("neg"), a); auto t = m.add_instruction(migraphx::make_op("tanh"), n); m.add_return({t}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(diamond_graph_sort) { // // Diamond graph test - graph structure: // // ┌─→ abs ─┠// │ ↓ // x ───────┼───────→ add → return // │ ↑ // └─→ neg ─┘ // // Tests handling of branches and reconvergent paths. // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; auto x = m.add_parameter("x", s); auto a = m.add_instruction(migraphx::make_op("abs"), x); auto n = m.add_instruction(migraphx::make_op("neg"), x); auto add = m.add_instruction(migraphx::make_op("add"), a, n); m.add_return({add}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(multiple_outputs_sort) { // // Multiple outputs test - graph structure: // // ┌─→ abs → tanh ─┠// │ │ // x ───────┤ ├─→ return // │ │ // └─→ neg ─────────┘ // // Tests handling of multiple outputs from a single instruction. // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; auto x = m.add_parameter("x", s); auto a = m.add_instruction(migraphx::make_op("abs"), x); auto n = m.add_instruction(migraphx::make_op("neg"), x); auto t = m.add_instruction(migraphx::make_op("tanh"), a); m.add_return({t, n}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(dead_code_sort) { // // Dead code // // ┌─→ abs → tanh ─┠// │ │ // x ───────┤ ├─→ return // │ // └─→ neg // // Tests handling of dead code // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; auto x = m.add_parameter("x", s); auto a = m.add_instruction(migraphx::make_op("abs"), x); m.add_instruction(migraphx::make_op("neg"), x); auto t = m.add_instruction(migraphx::make_op("tanh"), a); m.add_return({t}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(disconnected_components_sort) { // // Disconnected components test - graph structure: // // x1 → abs1 ─┠// ├─→ return // x2 → abs2 ─┘ // // Tests sorting of disconnected subgraphs. // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; // First subgraph auto x1 = m.add_parameter("x1", s); auto a1 = m.add_instruction(migraphx::make_op("abs"), x1); // Second subgraph (disconnected) auto x2 = m.add_parameter("x2", s); auto a2 = m.add_instruction(migraphx::make_op("abs"), x2); m.add_return({a1, a2}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(empty_graph_sort) { // // Empty graph test - graph structure: // // (empty module) // // Tests sorting an empty module. // migraphx::module m; m.sort(); // No assertions should fail EXPECT(is_sorted(m)); } TEST_CASE(single_node_sort) { // // Single node test - graph structure: // // x → return // // Tests the simplest possible non-empty case. // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; auto x = m.add_parameter("x", s); m.add_return({x}); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(sort_with_non_direct_dependencies) { // // Non-direct dependencies test - graph structure: // // x → abs ─────────┠// │ │ // ↓ ↓ // neg → add → return // // Tests handling of both direct and indirect dependencies. // (A is a direct dependency of B and C, and an indirect dependency of C via B) // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; auto x = m.add_parameter("x", s); auto a = m.add_instruction(migraphx::make_op("abs"), x); auto b = m.add_instruction(migraphx::make_op("neg"), a); auto c = m.add_instruction(migraphx::make_op("add"), a, b); m.add_return({c}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(dfs_without_visited_set_infinite_loop) { // // Highly Connected DAG test - graph structure: // // x0 // /|\ // / | \ // / | \ // v v v // a1 b1 c1 // /|\ /|\ /|\ // / | X | X | \ (crossing connections) // / |/ \|/ \| \ // v v v v v // a2 a3 b2 b3 c2 // \ \ | / / // \ \ | / / // \ \ | / / // \ \|/ / // \ v / // \ d / // \ | / // \|/ // v // return // // This creates a highly connected directed acyclic graph (DAG) where // traversing up from the return node would encounter the same nodes // multiple times through different paths. // // Without a proper visited set, a DFS-based topological sort would // potentially re-process the same nodes repeatedly, leading to an // exponential runtime or infinite loop in pathological implementations. // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; auto x0 = m.add_parameter("x0", s); // First layer of operations auto a1 = m.add_instruction(migraphx::make_op("add"), x0, x0); auto b1 = m.add_instruction(migraphx::make_op("mul"), x0, x0); auto c1 = m.add_instruction(migraphx::make_op("tanh"), x0); // Second layer with cross-connections auto a2 = m.add_instruction(migraphx::make_op("sqrt"), a1); auto a3 = m.add_instruction(migraphx::make_op("mul"), a1, b1); auto b2 = m.add_instruction(migraphx::make_op("where"), a1, b1, c1); auto b3 = m.add_instruction(migraphx::make_op("mul"), b1, c1); auto c2 = m.add_instruction(migraphx::make_op("exp"), c1); m.add_return({a2, a3, b2, b3, c2}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(recursive_dag_revisit_test) { // // Recursive DAG structure - graph structure: // // x // / \ // v v // a1 b1 // / \ / \ // v vv v // a2 c1 b2 // | /\ | // | / \ | // v v v v // a3 b3 // | | // v v // a4 b4 // | | // v v // a5 b5 // \ / // \ / // v v // d1 // | // v // return // // This test creates a deeper recursive structure with many opportunities // for revisiting nodes. Each node in the middle layers (a2-a5, b2-b5, c1) // has multiple paths leading to it when traversing up from the return node. // // A naive DFS implementation without a visited set would potentially // revisit these nodes many times, leading to an exponential number of // recursive calls, which could manifest as an infinite loop in practice. // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; auto x = m.add_parameter("x", s); // Create two main branches auto a1 = m.add_instruction(migraphx::make_op("add"), x, x); auto b1 = m.add_instruction(migraphx::make_op("mul"), x, x); // Create deeper levels with cross-connections auto a2 = m.add_instruction(migraphx::make_op("sqrt"), a1); auto c1 = m.add_instruction(migraphx::make_op("add"), a1, b1); auto b2 = m.add_instruction(migraphx::make_op("neg"), b1); auto a3 = m.add_instruction(migraphx::make_op("add"), a2, c1); auto b3 = m.add_instruction(migraphx::make_op("add"), b2, c1); // Add more layers to increase the number of paths auto a4 = m.add_instruction(migraphx::make_op("log"), a3); auto b4 = m.add_instruction(migraphx::make_op("exp"), b3); auto a5 = m.add_instruction(migraphx::make_op("cos"), a4); auto b5 = m.add_instruction(migraphx::make_op("sin"), b4); // Final convergence auto d1 = m.add_instruction(migraphx::make_op("add"), a5, b5); m.add_return({d1}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(exponential_growth_graph_sort) { // // Exponential growth graph structure - graph pattern: // // Each level i has 2^i nodes, with each node connecting to // multiple nodes in the previous level. This creates an exponential // number of paths through the graph. // // This structure is particularly problematic for naive DFS implementations // without visited node tracking. // // Define a large enough graph to potentially cause problems // but not so large that it takes too long to create const int depth = 10; migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; // Available operations std::vector operations = {migraphx::make_op("add"), migraphx::make_op("mul"), migraphx::make_op("min"), migraphx::make_op("max"), migraphx::make_op("div")}; auto x = m.add_parameter("x", s); std::vector first_level = {x}; // Number of nodes at each level which doubles at each level std::array num_nodes; num_nodes.fill(2); std::partial_sum(num_nodes.begin(), num_nodes.end(), num_nodes.begin(), std::multiplies<>{}); // Build all layers, accumulating just the last level auto last_level = std::accumulate(num_nodes.begin(), num_nodes.end(), first_level, [&](const auto& prev_level, std::size_t nodes_at_level) { // Transform indices into nodes std::vector current_level; current_level.reserve(nodes_at_level); transform(migraphx::range(nodes_at_level), std::back_inserter(current_level), [&](std::size_t node) { // Select inputs from previous level int input1_idx = node % prev_level.size(); int input2_idx = (node / 2) % prev_level.size(); auto input1 = prev_level[input1_idx]; auto input2 = prev_level[input2_idx]; // Select operation based on node index const auto& op = operations[node % operations.size()]; // Create the new node return m.add_instruction(op, input1, input2); }); // Return the current level (to become prev_level in next iteration) return current_level; }); // Add return node connected to multiple nodes from the last level std::vector final_inputs; std::copy_n(last_level.begin(), last_level.size() / 2, std::back_inserter(final_inputs)); m.add_return(final_inputs); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(pathological_dfs_graph_sort) { // // Pathological DFS Graph - designed to create the maximum number // of revisits when traversing from the return node without a visited set // // This creates a graph where the number of unique paths to each node // increases exponentially as you traverse up from the return node, // making it a worst-case scenario for DFS without visited tracking. // migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1}}; const int num_layers = 20; const int nodes_per_layer = 20; const int num_params = 10; // Create parameters std::vector params; transform(migraphx::range(num_params), std::back_inserter(params), [&](int i) { return m.add_parameter("x" + std::to_string(i), s); }); // Available operations std::vector operations = {migraphx::make_op("add"), migraphx::make_op("mul"), migraphx::make_op("min"), migraphx::make_op("max"), migraphx::make_op("div")}; std::vector first_layer; transform( migraphx::range(nodes_per_layer), std::back_inserter(first_layer), [&](std::size_t i) { // Each node connects to two random parameters std::size_t param1 = i % num_params; std::size_t param2 = (i + 3) % num_params; const auto& op = operations[i % operations.size()]; return m.add_instruction(op, params[param1], params[param2]); }); // Build all layers, accumulating just the last layer auto last_layer = std::accumulate( migraphx::iota_iterator{0}, migraphx::iota_iterator{num_layers}, first_layer, [&](const auto& prev_layer, std::size_t) { std::vector node_indices(nodes_per_layer); std::iota(node_indices.begin(), node_indices.end(), 0); std::vector current_layer; transform(migraphx::range(nodes_per_layer), std::back_inserter(current_layer), [&](std::size_t i) { // Connect to multiple nodes from previous layer to create path explosion const std::size_t num_inputs = std::min(4, nodes_per_layer); std::vector inputs; // Transform indices to actual input nodes transform(migraphx::range(num_inputs), std::back_inserter(inputs), [&](std::size_t j) { std::size_t input_idx = (i + j * 3) % prev_layer.size(); return prev_layer[input_idx]; }); // Create intermediate nodes with pairs of inputs auto op1 = migraphx::make_op("add"); auto op2 = migraphx::make_op("mul"); auto ins1 = m.add_instruction(op1, inputs[0], inputs[1]); auto ins2 = m.add_instruction( op2, inputs[2 % inputs.size()], inputs[3 % inputs.size()]); // Combine the results const auto& op3 = operations[(i % 3) + 2]; // Use min, max, or div return m.add_instruction(op3, ins1, ins2); }); // Return the current layer (to become prev_layer in next iteration) return current_layer; }); // Create a sequence of operations that combine all nodes from the last layer auto final_result = std::accumulate( last_layer.begin() + 1, last_layer.end(), last_layer[0], [&](auto x, auto y) { return m.add_instruction(migraphx::make_op("add"), x, y); }); m.add_return({final_result}); m.sort(); EXPECT(is_sorted(m)); reverse_module(m); m.sort(); EXPECT(is_sorted(m)); shuffle_module(m); m.sort(); EXPECT(is_sorted(m)); } TEST_CASE(localized_sort) { migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::float_type, {3, 4}}; migraphx::shape s3{migraphx::shape::float_type, {2, 4}}; migraphx::shape s4{migraphx::shape::float_type, {4, 2}}; migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto c = mm->add_parameter("c", s3); auto d = mm->add_parameter("d", s4); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); // {2, 4} auto external_relu = add_pointwise(p, "main:pointwise1", {d}, single_pointwise("relu")); // {4, 3} auto external_mul = add_pointwise(p, "main:pointwise2", {external_relu, d}, single_pointwise("mul")); // {4, 3} auto add = add_pointwise(p, "main:pointwise0", {dot1, c}, single_pointwise("add")); // {2, 4} auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, external_mul); // {2, 3} auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), dot2); mm->add_return({add, transpose}); // Perform localized sort between dot1 and dot2 mm->localized_sort(dot1, dot2); // Verify the module is still topologically sorted overall EXPECT(is_sorted(*mm)); // Verify external operations moved before the fusion chain EXPECT(std::distance(mm->begin(), external_relu) < std::distance(mm->begin(), dot1)); EXPECT(std::distance(mm->begin(), external_mul) < std::distance(mm->begin(), dot1)); // Verify the fusion chain ordering is preserved: dot1 < add < dot2 EXPECT(std::distance(mm->begin(), dot1) < std::distance(mm->begin(), add)); EXPECT(std::distance(mm->begin(), add) < std::distance(mm->begin(), dot2)); // Verify external_mul is before dot1 (since dot2 depends on external_mul) EXPECT(std::distance(mm->begin(), external_mul) < std::distance(mm->begin(), dot1)); // Verify transpose remains after dot2 EXPECT(std::distance(mm->begin(), dot2) < std::distance(mm->begin(), transpose)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/msgpack.cpp000066400000000000000000000162161510465702400176560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include "test.hpp" template , T>{})> static void write_msgpack(std::ostream& os, const T& src) { msgpack::pack(os, src); } static void write_msgpack(std::ostream& os, const std::vector& src) { const auto limit = std::numeric_limits::max() - 1; std::vector> chunks; if(src.size() > limit) { // Only test two chunks assert(std::distance(src.begin() + limit, src.end()) < limit); chunks.emplace_back(src.begin(), src.begin() + limit); chunks.emplace_back(src.begin() + limit, src.end()); } else { chunks = {src}; } write_msgpack(os, chunks); } template static std::vector msgpack_buffer(const T& src) { std::stringstream buffer; write_msgpack(buffer, src); buffer.seekg(0); std::string str = buffer.str(); return std::vector(str.data(), str.data() + str.size()); // NOLINT } TEST_CASE(test_msgpack_empty_value) { migraphx::value v; auto buffer = migraphx::to_msgpack(v); auto mp = migraphx::from_msgpack(buffer); EXPECT(mp == v); EXPECT(v.is_null()); EXPECT(mp.is_null()); } TEST_CASE(test_msgpack_int) { migraphx::value v = 3; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(3)); EXPECT(migraphx::from_msgpack(buffer).to() == v.to()); } TEST_CASE(test_msgpack_int_negative) { migraphx::value v = -3; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(-3)); EXPECT(migraphx::from_msgpack(buffer).to() == v.to()); } TEST_CASE(test_msgpack_bool) { migraphx::value v = true; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(true)); EXPECT(migraphx::from_msgpack(buffer) == v); } TEST_CASE(test_msgpack_float) { // changed all double values in this code to not end with .0 because on msgpack for Windows if // input type is double and ends with .0 it could be converted to uint64_t or int64_t and the // goal of these functions is to test double without conversions migraphx::value v = 3.01; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(3.01)); EXPECT(migraphx::from_msgpack(buffer) == v); } TEST_CASE(test_msgpack_string) { migraphx::value v = "abc"; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer("abc")); EXPECT(migraphx::from_msgpack(buffer) == v); } TEST_CASE(test_msgpack_array) { migraphx::value v = {1, 2, 3}; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(std::vector{1, 2, 3})); EXPECT(migraphx::from_msgpack(buffer).to_vector() == v.to_vector()); } TEST_CASE(test_msgpack_empty_array) { migraphx::value v = migraphx::value::array{}; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(std::vector{})); EXPECT(migraphx::from_msgpack(buffer) == v); } TEST_CASE(test_msgpack_object) { migraphx::value v = {{"one", 1.01}, {"three", 3.01}, {"two", 2.01}}; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(std::map{ {"one", 1.01}, {"three", 3.01}, {"two", 2.01}})); EXPECT(migraphx::from_msgpack(buffer) == v); } TEST_CASE(test_msgpack_empty_object) { migraphx::value v = migraphx::value::object{}; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(std::vector{})); auto u = migraphx::from_msgpack(buffer); // This is not equal since an empty object becomes an empty array EXPECT(u != v); EXPECT(u.is_array()); EXPECT(u.size() == 0); } struct foo { double a; std::string b; MSGPACK_DEFINE_MAP(a, b); }; TEST_CASE(test_msgpack_object_class) { migraphx::value v = {{"a", 1.01}, {"b", "abc"}}; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(foo{1.01, "abc"})); EXPECT(migraphx::from_msgpack(buffer) == v); } TEST_CASE(test_msgpack_array_class) { migraphx::value v = {{{"a", 1.01}, {"b", "abc"}}, {{"a", 3.01}, {"b", "xyz"}}}; auto buffer = migraphx::to_msgpack(v); EXPECT(buffer == msgpack_buffer(std::vector{foo{1.01, "abc"}, foo{3.01, "xyz"}})); EXPECT(migraphx::from_msgpack(buffer) == v); } TEST_CASE(test_msgpack_binary) { migraphx::value::binary bin{64}; std::iota(bin.begin(), bin.end(), 1); auto buffer = migraphx::to_msgpack(bin); EXPECT(buffer == msgpack_buffer(bin)); EXPECT(migraphx::from_msgpack(buffer) == bin); } #ifndef MIGRAPHX_DISABLE_LARGE_BUFFER_TESTS TEST_CASE(test_msgpack_large_binary1) { const std::size_t n = 4LL * 1024 * 1024 * 1024 + 2; const char fill_value = 2; migraphx::value v; { std::vector buffer; { migraphx::value::binary bin{n}; std::fill(bin.begin(), bin.begin() + n / 2, fill_value); std::fill(bin.begin() + n / 2, bin.end(), fill_value + 1); buffer = migraphx::to_msgpack(std::move(bin)); } v = migraphx::from_msgpack(buffer); } EXPECT(v.is_binary()); EXPECT(v.get_binary().size() == n); EXPECT(std::all_of(v.get_binary().begin(), v.get_binary().begin() + n / 2, [](auto c) { return c == fill_value; })); EXPECT(std::all_of(v.get_binary().begin() + n / 2, v.get_binary().end(), [](auto c) { return c == fill_value + 1; })); } TEST_CASE(test_msgpack_binary2) { const std::size_t n = 4LL * 1024 * 1024 * 1024 + 2; migraphx::value::binary bin{n}; std::size_t i = 0; std::generate(bin.begin(), bin.end(), [&] { i++; return i % 256; }); EXPECT(migraphx::to_msgpack(bin) == msgpack_buffer(bin)); } #endif int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/multi_target/000077500000000000000000000000001510465702400202175ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/multi_target/multitarget_test.cpp000066400000000000000000000643471510465702400243410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test.hpp" // check if it is custom_op or run_on_module operator static bool has_target_attr(const migraphx::instruction& ins) { return ins.get_operator().attributes().contains("target"); } static auto nonprefixed_ops() { // ops without prefixes static std::unordered_set op_map = { "select_module", "load", "if", "nonmaxsuppression", "multibroadcast"}; return op_map; } static bool is_compiled_gpu_module(const migraphx::module& m) { return std::all_of(m.begin(), m.end(), [](const auto& ins) { auto ins_name = ins.name(); if(not migraphx::starts_with(ins_name, "@")) { if(not migraphx::starts_with(ins_name, "gpu::") and not migraphx::starts_with(ins_name, "hip::") and not migraphx::starts_with(ins_name, "check_context") and not migraphx::contains(nonprefixed_ops(), ins_name) and not has_target_attr(ins)) { return false; } } return true; }); } static bool is_compiled_fpga_module(const migraphx::module& m) { return std::all_of(m.begin(), m.end(), [](const auto& ins) { auto ins_name = ins.name(); if(not migraphx::starts_with(ins_name, "@")) { if(not migraphx::starts_with(ins_name, "fpga::") and not migraphx::starts_with(ins_name, "check_context") and not migraphx::contains(nonprefixed_ops(), ins_name) and not has_target_attr(ins)) { return false; } } return true; }); } static bool is_compiled_cpu_module(const migraphx::module& m) { return std::all_of(m.begin(), m.end(), [](const auto& ins) { auto ins_name = ins.name(); // sub is not lowered on CPU backend due to vectorization on non-aligned memory. if(not migraphx::starts_with(ins_name, "@") and ins_name != "sub") { if(not migraphx::starts_with(ins_name, "cpu::") and not migraphx::starts_with(ins_name, "dnnl::") and not migraphx::starts_with(ins_name, "check_context") and not has_target_attr(ins) and not migraphx::contains(nonprefixed_ops(), ins_name)) { return false; } } return true; }); } static bool is_compiled_ref_module(const migraphx::module& m) { return std::all_of(m.begin(), m.end(), [](const auto& ins) { auto ins_name = ins.name(); if(not migraphx::starts_with(ins_name, "@")) { if((not migraphx::starts_with(ins_name, "ref::") and not migraphx::starts_with(ins_name, "check_context") and not has_target_attr(ins)) and not migraphx::contains(nonprefixed_ops(), ins_name)) { return false; } } return true; }); } // NOLINT static bool check_compiled_program(const migraphx::program& p, const std::vector& targets) { auto mods = p.get_modules(); bool check_compiled = true; for(const auto* mod : mods) { for(const auto& ins : *mod) { if(ins.name() == "run_on_target") { auto* mod_input = ins.module_inputs().front(); std::size_t target_id = ins.get_operator().to_value()["target_id"].to(); auto target_name = targets.at(target_id).name(); if(target_name == "gpu") check_compiled &= is_compiled_gpu_module(*mod_input); else if(target_name == "cpu") check_compiled &= is_compiled_cpu_module(*mod_input); else if(target_name == "fpga") check_compiled &= is_compiled_fpga_module(*mod_input); else if(target_name == "ref") check_compiled &= is_compiled_ref_module(*mod_input); } } } return check_compiled; } TEST_CASE(multitarget_compile_cpu_gpu) { migraphx::program p; auto* mm = p.get_main_module(); auto* cpu_mod = p.create_module("cpu_mod"); auto s = migraphx::shape{migraphx::shape::float_type, {8}}; auto x_cpu = cpu_mod->add_parameter("cpu_x", s); auto y_cpu = cpu_mod->add_parameter("cpu_y", s); auto cpu_add = cpu_mod->add_instruction(migraphx::make_op("add"), x_cpu, y_cpu); cpu_mod->add_return({cpu_add}); auto* gpu_mod = p.create_module("gpu_mod"); auto x_gpu = gpu_mod->add_parameter("gpu_x", s); auto y_gpu = gpu_mod->add_parameter("gpu_y", s); auto gpu_add = gpu_mod->add_instruction(migraphx::make_op("add"), x_gpu, y_gpu); gpu_mod->add_return({gpu_add}); auto x_param = mm->add_parameter("x", s); auto y_param = mm->add_parameter("y", s); auto z_param = mm->add_parameter("z", s); auto cpu_ins = mm->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 1}}), {x_param, y_param}, {cpu_mod}); auto cpu_ins_0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), cpu_ins); auto gpu_ins = mm->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 0}}), {cpu_ins_0, z_param}, {gpu_mod}); auto gpu_ins_0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), gpu_ins); mm->add_return({gpu_ins_0}); migraphx::compile_options gpu_opts; gpu_opts.offload_copy = true; p.compile({migraphx::make_target("gpu"), migraphx::make_target("cpu")}, {gpu_opts}); EXPECT(check_compiled_program(p, {migraphx::make_target("gpu"), migraphx::make_target("cpu")})); migraphx::parameter_map params; params["x"] = migraphx::fill_argument(s, 1); params["y"] = migraphx::fill_argument(s, 2); params["z"] = migraphx::fill_argument(s, 3); auto result = p.eval(params).back(); auto gold = migraphx::fill_argument(s, 6); EXPECT(gold == result); } TEST_CASE(single_target_multi_compile) { migraphx::program p; migraphx::shape boxes_s{migraphx::shape::float_type, {1, 6, 4}}; auto* mm = p.get_main_module(); auto boxes_param = mm->add_parameter("boxes", boxes_s); auto* gpu_mod = p.create_module("gpu_mod"); auto boxes_param_gpu = gpu_mod->add_parameter("boxes_param_gpu", boxes_s); migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; std::vector scores_vec = {0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; auto scores_l = gpu_mod->add_literal(migraphx::literal(scores_s, scores_vec)); auto max_out_l = gpu_mod->add_literal(int64_t{4}); auto iou_threshold = gpu_mod->add_literal(0.5f); auto score_threshold = gpu_mod->add_literal(0.0f); auto r = gpu_mod->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_param_gpu, scores_l, max_out_l, iou_threshold, score_threshold); gpu_mod->add_return({r}); auto run_on_gpu = mm->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 0}}), {boxes_param}, {gpu_mod}); auto run_on_gpu_0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_on_gpu); mm->add_return({run_on_gpu_0}); // compile using multi-target compilation path migraphx::compile_options gpu_opts; gpu_opts.offload_copy = true; // need to add "ref" to avoid ambigious call to "compile()" p.compile({migraphx::make_target("gpu"), migraphx::make_target("ref")}, {gpu_opts}); EXPECT(check_compiled_program(p, {migraphx::make_target("gpu"), migraphx::make_target("ref")})); // eval migraphx::parameter_map params; std::vector boxes_vec = {0.5, 0.5, 1.0, 1.0, 0.5, 0.6, 1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 10.5, 1.0, 1.0, 0.5, 10.6, 1.0, 1.0, 0.5, 100.5, 1.0, 1.0}; params["boxes"] = migraphx::argument(boxes_s, boxes_vec.data()); auto output = p.eval(params).back(); std::vector gold_vec = {0, 0, 3, 0, 0, 0, 0, 0, 5}; auto gold = migraphx::argument(migraphx::shape{migraphx::shape::int64_type, {3, 3}}, gold_vec.data()); EXPECT(output == gold); } TEST_CASE(multitarget_compile_if_then_else) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", cond_s); migraphx::shape ds{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", ds); auto y = mm->add_parameter("y", ds); auto* then_mod = p.create_module("if_gpu_mod"); std::vector data1(ds.elements(), 1); auto l1 = then_mod->add_literal(migraphx::literal(ds, data1)); auto gpu_x = then_mod->add_parameter("gpu_x", ds); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), gpu_x, l1); then_mod->add_return({a1}); auto* else_mod = p.create_module("else_cpu_mod"); std::vector data2(ds.elements(), 2); auto l2 = else_mod->add_literal(migraphx::literal(ds, data2)); auto cpu_y = else_mod->add_parameter("cpu_y", ds); auto a2 = else_mod->add_instruction(migraphx::make_op("mul"), cpu_y, l2); else_mod->add_return({a2}); auto* run_on_cpu_mod = p.create_module("run_on_cpu"); auto run_cpu_ins = run_on_cpu_mod->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 1}}), {y}, {else_mod}); auto run_cpu_ins_0 = run_on_cpu_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_cpu_ins); run_on_cpu_mod->add_return({run_cpu_ins_0}); auto* run_on_gpu_mod = p.create_module("run_on_gpu"); auto run_gpu_ins = run_on_gpu_mod->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 0}}), {x}, {then_mod}); auto run_gpu_ins_0 = run_on_gpu_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_gpu_ins); run_on_gpu_mod->add_return({run_gpu_ins_0}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {run_on_gpu_mod, run_on_cpu_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); // compile migraphx::compile_options gpu_opts; gpu_opts.offload_copy = true; p.compile({migraphx::make_target("gpu"), migraphx::make_target("cpu")}, {gpu_opts}); EXPECT(check_compiled_program(p, {migraphx::make_target("gpu"), migraphx::make_target("cpu")})); migraphx::parameter_map params; params["x"] = migraphx::fill_argument(ds, 2); params["y"] = migraphx::fill_argument(ds, 3); for(bool cond_val : {true, false}) { params["cond"] = migraphx::argument(cond_s, &cond_val); auto result = p.eval(params).back(); auto gold = migraphx::fill_argument(ds, (cond_val ? 3 : 6)); EXPECT(gold == result); } } // TODO : FPGA compilation is broken right now, below test mentions fpga but doesn't compile for it TEST_CASE(multitarget_compile_nested_if_then_else) { std::unordered_map counter_map = {{0, 0}, {1, 0}}; migraphx::shape ds{migraphx::shape::float_type, {2, 3}}; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond_0 = mm->add_parameter("cond_0", cond_s); auto cond_1 = mm->add_parameter("cond_1", cond_s); auto x = mm->add_parameter("x", ds); auto y = mm->add_parameter("y", ds); auto z = mm->add_parameter("z", ds); auto create_test_module = [&](migraphx::program& prog, const std::vector& inputs, std::size_t tid) { std::string mod_name = "target_" + std::to_string(tid) + "_" + std::to_string(counter_map[tid]++); auto* test_mod = prog.create_module(mod_name); std::vector data(ds.elements(), -1); auto l1 = test_mod->add_literal(migraphx::literal(ds, data)); auto test_mod_param_0 = test_mod->add_parameter(mod_name + "_param_0", ds); auto test_mod_param_1 = test_mod->add_parameter(mod_name + "_param_1", ds); auto test_mod_param_2 = test_mod->add_parameter(mod_name + "_param_2", ds); auto ins1 = test_mod->add_instruction(migraphx::make_op("add"), test_mod_param_0, l1); auto ins2 = test_mod->add_instruction(migraphx::make_op("mul"), ins1, test_mod_param_1); auto ins3 = test_mod->add_instruction(migraphx::make_op("sub"), ins2, test_mod_param_2); test_mod->add_return({ins3}); auto* run_on_target_mod = prog.create_module("run_on_" + mod_name); auto run_ins = run_on_target_mod->add_instruction( migraphx::make_op("run_on_target", {{"target_id", tid}}), inputs, {test_mod}); auto run_ins_0 = run_on_target_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_ins); run_on_target_mod->add_return({run_ins_0}); return run_on_target_mod; }; // create nested module with multiple targets. // then_mod has one instruction that runs a module on "ref" and another instruction that // creates nested modules using "If" that runs on "cpu" and "gpu" auto* ref_mod = p.create_module("ref_mod"); auto ref_x = ref_mod->add_parameter("ref_x", ds); auto ref_y = ref_mod->add_parameter("ref_y", ds); auto ref_add = ref_mod->add_instruction(migraphx::make_op("add"), ref_x, ref_y); ref_mod->add_return({ref_add}); auto* then_mod = p.create_module("then_mod"); auto then_mod_cond = then_mod->add_parameter("then_mod_cond", cond_s); auto then_mod_param_0 = then_mod->add_parameter("then_mod_param_0", ds); auto then_mod_param_1 = then_mod->add_parameter("then_mod_param_1", ds); auto then_mod_param_2 = then_mod->add_parameter("then_mod_param_2", ds); auto then_mod_ref_ins = then_mod->add_instruction(migraphx::make_op("run_on_target", {{"target_id", 3}}), {then_mod_param_0, then_mod_param_1}, {ref_mod}); auto then_mod_ref_ins_0 = then_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), then_mod_ref_ins); auto then_mod_if = then_mod->add_instruction( migraphx::make_op("if"), {then_mod_cond, then_mod_param_0, then_mod_param_1, then_mod_param_2, then_mod_ref_ins_0, then_mod_param_1, then_mod_param_2}, {create_test_module(p, {then_mod_param_0, then_mod_param_1, then_mod_param_2}, 1), create_test_module(p, {then_mod_ref_ins_0, then_mod_param_1, then_mod_param_2}, 0)}); auto then_mod_if_0 = then_mod->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), then_mod_if); then_mod->add_return({then_mod_if_0}); // create nested else_mod with multiple targets. // else_mod has one instruction that runs a module on "fpga" and another instruction that // creates nested modules using "If" that runs on "cpu" and "gpu" auto* fpga_mod = p.create_module("fpga_mod"); auto fpga_x = fpga_mod->add_parameter("fpga_x", ds); auto fpga_y = fpga_mod->add_parameter("fpga_y", ds); auto fpga_add = fpga_mod->add_instruction(migraphx::make_op("add"), fpga_x, fpga_y); fpga_mod->add_return({fpga_add}); auto* else_mod = p.create_module("else_mod"); auto else_mod_cond = else_mod->add_parameter("else_mod_cond", cond_s); auto else_mod_param_0 = else_mod->add_parameter("else_mod_param_0", ds); auto else_mod_param_1 = else_mod->add_parameter("else_mod_param_1", ds); auto else_mod_param_2 = else_mod->add_parameter("else_mod_param_2", ds); auto else_mod_fpga_ins = else_mod->add_instruction(migraphx::make_op("run_on_target", {{"target_id", 2}}), {else_mod_param_0, else_mod_param_2}, {fpga_mod}); auto else_mod_fpga_ins_0 = else_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), else_mod_fpga_ins); auto else_mod_if = else_mod->add_instruction( migraphx::make_op("if"), {else_mod_cond, else_mod_fpga_ins_0, else_mod_param_0, else_mod_param_1, else_mod_param_2, else_mod_param_1, else_mod_param_0}, {create_test_module(p, {else_mod_fpga_ins_0, else_mod_param_0, else_mod_param_1}, 0), create_test_module(p, {else_mod_param_2, else_mod_param_1, else_mod_param_0}, 1)}); auto else_mod_if_0 = else_mod->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), else_mod_if); else_mod->add_return({else_mod_if_0}); // Create nested and multi-target main module using "If" auto main_if_ins = mm->add_instruction( migraphx::make_op("if"), {cond_0, cond_1, x, y, z, cond_1, x, y, z}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), main_if_ins); mm->add_return({r}); // compile migraphx::compile_options gpu_opts; gpu_opts.offload_copy = true; p.compile({migraphx::make_target("gpu"), migraphx::make_target("cpu"), migraphx::make_target("ref"), migraphx::make_target("ref")}, {gpu_opts}); EXPECT(check_compiled_program(p, {migraphx::make_target("gpu"), migraphx::make_target("cpu"), migraphx::make_target("ref"), migraphx::make_target("ref")})); // do evaluation using different conditions // TODO: make two conditional to cover all the paths migraphx::parameter_map params; float x_i = 2.0; float y_i = 3.0; float z_i = 4.0; params["x"] = migraphx::fill_argument(ds, x_i); params["y"] = migraphx::fill_argument(ds, y_i); params["z"] = migraphx::fill_argument(ds, z_i); // cover all paths with different combination of conditions std::vector> test_conds = { {true, true}, {true, false}, {false, true}, {false, false}}; for(auto [cond_val_0, cond_val_1] : test_conds) { params["cond_0"] = migraphx::argument(cond_s, &cond_val_0); params["cond_1"] = migraphx::argument(cond_s, &cond_val_1); auto result = p.eval(params).back(); // main has one instruction that is : if_then_else // then mod is doing : {tmp = x+y; (cond) ? (((x-1)*y)-z) : (((tmp-1)*y)-z);} // else mod is doing : {tmp = x+z; (cond) ? (((tmp-1)*x)-y) : (((z-1)*y)-x);} float gold_i = -1.0; if(cond_val_0) { float tmp_i = x_i + y_i; gold_i = (cond_val_1) ? (((x_i - 1) * y_i) - z_i) : (((tmp_i - 1) * y_i) - z_i); } else { float tmp_i = x_i + z_i; gold_i = (cond_val_1) ? (((tmp_i - 1) * x_i) - y_i) : (((z_i - 1) * y_i) - x_i); } auto gold = migraphx::fill_argument(ds, gold_i); EXPECT(gold == result); } } // TODO : FPGA compilation is broken right now, below test mentions fpga but doesn't compile for it TEST_CASE(multitarget_select_module) { migraphx::program p; // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins0 = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); auto add_ins1 = submod->add_instruction(migraphx::make_op("add"), add_ins0, broadcast_lit); submod->add_return({add_ins1}); return submod; }; auto* batch1 = create_submodule(1, "batch_1"); auto* batch2 = create_submodule(2, "batch_2"); auto* batch3 = create_submodule(3, "batch_3"); auto* batch4 = create_submodule(4, "batch_4"); auto* run_cpu_mod = p.create_module("cpu_mod"); auto cpu_param = run_cpu_mod->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {1, 4}}); auto run_cpu_ins = run_cpu_mod->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 1}}), {cpu_param}, {batch1}); auto run_cpu_ins_0 = run_cpu_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_cpu_ins); run_cpu_mod->add_return({run_cpu_ins_0}); auto* run_gpu_mod = p.create_module("gpu_mod"); auto gpu_param = run_gpu_mod->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {2, 4}}); auto run_gpu_ins = run_gpu_mod->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 0}}), {gpu_param}, {batch2}); auto run_gpu_ins_0 = run_gpu_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_gpu_ins); run_gpu_mod->add_return({run_gpu_ins_0}); auto* run_fpga_mod = p.create_module("fpga_mod"); auto fpga_param = run_fpga_mod->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {3, 4}}); auto run_fpga_ins = run_fpga_mod->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 2}}), {fpga_param}, {batch3}); auto run_fpga_ins_0 = run_fpga_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_fpga_ins); run_fpga_mod->add_return({run_fpga_ins_0}); auto* run_ref_mod = p.create_module("ref_mod"); auto ref_param = run_ref_mod->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {4, 4}}); auto run_ref_ins = run_ref_mod->add_instruction( migraphx::make_op("run_on_target", {{"target_id", 3}}), {ref_param}, {batch4}); auto run_ref_ins_0 = run_ref_mod->add_instruction( migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_ref_ins); run_ref_mod->add_return({run_ref_ins_0}); auto* mm = p.get_main_module(); migraphx::shape dyn_s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input = mm->add_parameter("data", dyn_s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input}, {run_cpu_mod, run_gpu_mod, run_fpga_mod, run_ref_mod}); auto ret0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm->add_return({ret0}); // compile migraphx::compile_options gpu_opts; gpu_opts.offload_copy = true; p.compile({migraphx::make_target("gpu"), migraphx::make_target("cpu"), migraphx::make_target("ref"), migraphx::make_target("ref")}, {gpu_opts}); EXPECT(check_compiled_program(p, {migraphx::make_target("gpu"), migraphx::make_target("cpu"), migraphx::make_target("ref"), migraphx::make_target("ref")})); // program does the 12+x where x has dynamic shape {{1, 4}, {4, 4}} for(const size_t bs : {1, 2, 3, 4}) { migraphx::shape arg_shape{migraphx::shape::float_type, {bs, 4}}; migraphx::parameter_map params; params["data"] = migraphx::generate_argument(arg_shape, arg_shape.elements()); std::vector input_data; params["data"].visit([&](const auto& vec) { input_data.assign(vec.begin(), vec.end()); }); std::transform(input_data.begin(), input_data.end(), input_data.begin(), [](const auto& i) { return i + 12.0; }); auto result = p.eval(params).back(); EXPECT(migraphx::argument(arg_shape, input_data.data()) == result); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/normalize_ops_test.cpp000066400000000000000000000137551510465702400221560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include struct normalize_test_op { std::vector axes = {}; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.axes, "axes")); } migraphx::value attributes() const { migraphx::value normalize; normalize["axes"] = migraphx::value::array{migraphx::op::normalize_attribute::clip_max, migraphx::op::normalize_attribute::clip_min}; return {{"normalize_axes", normalize}}; } std::string name() const { return "normalize_ops_test::test_op"; } migraphx::shape normalize_compute_shape(std::vector inputs) const { return inputs[0]; } migraphx::argument compute(migraphx::context&, const migraphx::shape& output_shape, const std::vector&) const { return migraphx::argument{output_shape}; } }; static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::normalize_ops{}, migraphx::dead_code_elimination{}}); } static migraphx::module create_gather(int64_t axis) { migraphx::module m; migraphx::shape sd{migraphx::shape::float_type, {2, 3, 4}}; migraphx::shape si{migraphx::shape::int64_type, {2, 3}}; auto di = m.add_parameter("data", sd); auto ii = m.add_parameter("ind", si); auto r = m.add_instruction(migraphx::make_op("gather", {{"axis", axis}}), di, ii); m.add_return({r}); return m; } TEST_CASE(gather_test) { auto m1 = create_gather(-3); auto m2 = create_gather(0); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(gather_test_1) { auto m1 = create_gather(1); auto m2 = create_gather(1); run_pass(m1); EXPECT(m1 == m2); } static migraphx::module create_padded_op(const std::vector& pad_vals) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {2, 3, 4, 5}}; auto si = m.add_parameter("data", s); auto r = m.add_instruction(migraphx::make_op("pooling", {{"padding", pad_vals}}), si); m.add_return({r}); return m; } TEST_CASE(padding_attr_test) { migraphx::module m1 = create_padded_op({0, 1}); migraphx::module m2 = create_padded_op({0, 1, 0, 1}); run_pass(m1); EXPECT(m1 == m2); } static migraphx::module create_reduce_mean(const std::vector& axes) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {2, 3, 4, 5}}; auto si = m.add_parameter("data", s); auto r = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), si); m.add_return({r}); return m; } TEST_CASE(reduce_mean_test) { migraphx::module m1 = create_reduce_mean({0, 1, -1}); migraphx::module m2 = create_reduce_mean({0, 1, 3}); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(reduce_mean_test_1) { migraphx::module m1 = create_reduce_mean({0, 1, 2}); migraphx::module m2 = create_reduce_mean({0, 1, 2}); run_pass(m1); EXPECT(m1 == m2); } static migraphx::module create_slice(const std::vector& axes, const std::vector& starts, const std::vector& ends) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {2, 3, 4, 5}}; auto si = m.add_parameter("data", s); auto r = m.add_instruction( migraphx::make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}), si); m.add_return({r}); return m; } TEST_CASE(slice_test) { migraphx::module m1 = create_slice({0, 1, -1}, {-5, 1, -3}, {2, 2, 8}); migraphx::module m2 = create_slice({0, 1, 3}, {0, 1, 2}, {2, 2, 5}); run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(slice_test_1) { migraphx::module m1 = create_slice({0, 1, 3}, {0, 1, -3}, {1, 2, 5}); migraphx::module m2 = create_slice({0, 1, 3}, {0, 1, 2}, {1, 2, 5}); run_pass(m1); EXPECT(m1 == m2); } static migraphx::module create_test_op(const std::vector& axes) { migraphx::module m; migraphx::shape sd{migraphx::shape::float_type, {2, 3, 4}}; auto di = m.add_parameter("data", sd); auto r = m.add_instruction(normalize_test_op{axes}, di); m.add_return({r}); return m; } TEST_CASE(test_op) { std::vector axes1 = {-4, 5}; auto m1 = create_test_op(axes1); std::vector axes2 = {1, 2}; auto m2 = create_test_op(axes2); run_pass(m1); EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/onnx/000077500000000000000000000000001510465702400165015ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/.onnxrt-commit000066400000000000000000000000511510465702400213140ustar00rootroot00000000000000be655f69f6c8623eebcac094626d0c8545951e6d ROCm-AMDMIGraphX-46524e8/test/onnx/CMakeLists.txt000066400000000000000000000040631510465702400212440ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### function(add_onnx_test TEST_NAME) rocm_add_test_executable(${TEST_NAME} ${ARGN}) rocm_clang_tidy_check(${TEST_NAME}) target_link_libraries(${TEST_NAME} migraphx_onnx migraphx_ref onnx_files) target_include_directories(${TEST_NAME} PUBLIC ../include include) endfunction() file(GLOB_RECURSE ONNX_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.onnx ${CMAKE_CURRENT_SOURCE_DIR}/*.weight) add_embed_library(onnx_files ${ONNX_FILES} RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}) file(GLOB ONNX_PARSE_TESTS CONFIGURE_DEPENDS parse/*.cpp) file(GLOB ONNX_VERIFY_TESTS CONFIGURE_DEPENDS verify/*.cpp) add_onnx_test(test_onnx_test ${ONNX_PARSE_TESTS}) add_onnx_test(test_onnx_rnn_test onnx_rnn_test.cpp) add_onnx_test(test_verify_onnx ${ONNX_VERIFY_TESTS}) ROCm-AMDMIGraphX-46524e8/test/onnx/acos_test.onnx000066400000000000000000000001211510465702400213630ustar00rootroot00000000000000 acos-example:; xy"Acos test_acosZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/acosh_test.onnx000066400000000000000000000001211510465702400215330ustar00rootroot00000000000000 acosh_test:= xy"Acosh acosh_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/add_bcast_test.onnx000066400000000000000000000002521510465702400223470ustar00rootroot00000000000000add_bcast-example:Ž - 0 12"Add* axis * broadcast test-add_bcastZ 0     Z 1   b 2     BROCm-AMDMIGraphX-46524e8/test/onnx/add_bf16_test.onnx000066400000000000000000000001511510465702400220070ustar00rootroot00000000000000  add_bf16_test:R  0 12"Add add_bf16_testZ 0  Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/add_fp16_test.onnx000066400000000000000000000002071510465702400220270ustar00rootroot00000000000000add-fp16-example:m  0 12"Add test-add-fp16*  *€|B0*  *€‚B1Z 0   Z 1   b 2   BROCm-AMDMIGraphX-46524e8/test/onnx/add_fp8_test.onnx000066400000000000000000000001471510465702400217530ustar00rootroot00000000000000  add_fp8_test:Q  0 12"Add add_fp8_testZ 0  Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/add_scalar_test.onnx000066400000000000000000000002011510465702400225120ustar00rootroot00000000000000add_scalar_test:h  0 12"Addadd_scalar_testZ 0     Z 1 b 2     B ROCm-AMDMIGraphX-46524e8/test/onnx/argmax_dyn_test.onnx000066400000000000000000000002121510465702400225700ustar00rootroot00000000000000argmax_dyn_test:q , xy"ArgMax* axis * keepdims argmax_dyn_testZ x    b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/argmax_select_last_index_test.onnx000066400000000000000000000003051510465702400254720ustar00rootroot00000000000000 argmax_select_last_index_test: F xy"ArgMax* axis * keepdims * select_last_index argmax_select_last_index_testZ x     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/argmax_test.onnx000066400000000000000000000002111510465702400217150ustar00rootroot00000000000000argmax-example:q , xy"ArgMax* axis * keepdims  test_argmaxZ x     b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/argmin_select_last_index_test.onnx000066400000000000000000000003051510465702400254700ustar00rootroot00000000000000 argmin_select_last_index_test: F xy"ArgMin* axis * keepdims * select_last_index argmin_select_last_index_testZ x     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/argmin_test.onnx000066400000000000000000000002111510465702400217130ustar00rootroot00000000000000argmin-example:q , xy"ArgMin* axis * keepdims  test_argminZ x     b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/asin_test.onnx000066400000000000000000000001211510465702400213700ustar00rootroot00000000000000 asin-example:; xy"Asin test_asinZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/asinh_test.onnx000066400000000000000000000001211510465702400215400ustar00rootroot00000000000000 asinh_test:= xy"Asinh asinh_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/atan_test.onnx000066400000000000000000000001211510465702400213610ustar00rootroot00000000000000 atan-example:; xy"Atan test_atanZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/atanh_test.onnx000066400000000000000000000001211510465702400215310ustar00rootroot00000000000000 atanh_test:= xy"Atanh atanh_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_batch1_test.onnx000066400000000000000000000003731510465702400262110ustar00rootroot00000000000000 !attention_double_head_batch1_test:Ï E input weights biasy" Attention* num_heads : com.microsoft!attention_double_head_batch1_testZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_3d_mask_test.onnx000066400000000000000000000004651510465702400273700ustar00rootroot00000000000000 'attention_double_head_bias_3d_mask_test:ƒ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft'attention_double_head_bias_3d_mask_testZ input    Z weights    Z bias   Z mask_index    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_4d_mask_test.onnx000066400000000000000000000004711510465702400273660ustar00rootroot00000000000000 'attention_double_head_bias_4d_mask_test:‡ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft'attention_double_head_bias_4d_mask_testZ input    Z weights    Z bias   Z$ mask_index     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_asym_left_pad_mask_test.onnx000066400000000000000000000005031510465702400316620ustar00rootroot00000000000000 2attention_double_head_bias_asym_left_pad_mask_test:† Q input weights bias mask_indexy" Attention* num_heads : com.microsoft2attention_double_head_bias_asym_left_pad_mask_testZ input    Z weights    Z bias   Z mask_index  b y    Battention_double_head_bias_asym_mask_bad_rotary_embedding_dim_test.onnx000066400000000000000000000006041510465702400350040ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx Battention_double_head_bias_asym_mask_bad_rotary_embedding_dim_test:· n input weights bias mask_indexy" Attention* num_heads * rotary_embedding_dim0 : com.microsoftBattention_double_head_bias_asym_mask_bad_rotary_embedding_dim_testZ input    Z weights    Z bias   Z mask_index   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_asym_mask_filter_val_test.onnx000066400000000000000000000005501510465702400322350ustar00rootroot00000000000000 4attention_double_head_bias_asym_mask_filter_val_test:© n input weights bias mask_indexy" Attention* num_heads * mask_filter_value@œÅ : com.microsoft4attention_double_head_bias_asym_mask_filter_val_testZ input    Z weights    Z bias   Z mask_index   b y    Battention_double_head_bias_asym_mask_rotary_embedding_dim_test.onnx000066400000000000000000000005741510465702400342040ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx >attention_double_head_bias_asym_mask_rotary_embedding_dim_test:³ n input weights bias mask_indexy" Attention* num_heads * rotary_embedding_dim  : com.microsoft>attention_double_head_bias_asym_mask_rotary_embedding_dim_testZ input    Z weights    Z bias   Z mask_index   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_asym_mask_rotary_test.onnx000066400000000000000000000005251510465702400314300ustar00rootroot00000000000000 0attention_double_head_bias_asym_mask_rotary_test:š c input weights bias mask_indexy" Attention* num_heads * do_rotary : com.microsoft0attention_double_head_bias_asym_mask_rotary_testZ input    Z weights    Z bias   Z mask_index   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_asym_mask_scale_test.onnx000066400000000000000000000005221510465702400311740ustar00rootroot00000000000000 /attention_double_head_bias_asym_mask_scale_test:˜ b input weights bias mask_indexy" Attention* num_heads * scale$¹ü= : com.microsoft/attention_double_head_bias_asym_mask_scale_testZ input    Z weights    Z bias   Z mask_index   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_asym_mask_test.onnx000066400000000000000000000004651510465702400300330ustar00rootroot00000000000000 )attention_double_head_bias_asym_mask_test: Q input weights bias mask_indexy" Attention* num_heads : com.microsoft)attention_double_head_bias_asym_mask_testZ input    Z weights    Z bias   Z mask_index   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_asym_mask_unidirectional_test.onnx000066400000000000000000000005521510465702400331210ustar00rootroot00000000000000 8attention_double_head_bias_asym_mask_unidirectional_test:§ h input weights bias mask_indexy" Attention* num_heads * unidirectional : com.microsoft8attention_double_head_bias_asym_mask_unidirectional_testZ input    Z weights    Z bias   Z mask_index   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_asym_right_pad_mask_test.onnx000066400000000000000000000005051510465702400320470ustar00rootroot00000000000000 3attention_double_head_bias_asym_right_pad_mask_test:‡ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft3attention_double_head_bias_asym_right_pad_mask_testZ input    Z weights    Z bias   Z mask_index  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_mask_batch1_test.onnx000066400000000000000000000004711510465702400302210ustar00rootroot00000000000000 +attention_double_head_bias_mask_batch1_test:ƒ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft+attention_double_head_bias_mask_batch1_testZ input    Z weights    Z bias   Z mask_index   b y    Battention_double_head_bias_mask_past_attn_bias_shared_past_seq_len_test.onnx000066400000000000000000000007761510465702400360660ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx Gattention_double_head_bias_mask_past_attn_bias_shared_past_seq_len_test:¬ } input weights bias mask_index past attention_bias past_sequence_lengthy" Attention* num_heads : com.microsoftGattention_double_head_bias_mask_past_attn_bias_shared_past_seq_len_testZ input    Z weights    Z bias   Z mask_index   Z past   Z$ attention_bias    Z! past_seqence_length  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_mask_past_attn_bias_shared_test.onnx000066400000000000000000000007711510465702400334030ustar00rootroot00000000000000 :attention_double_head_bias_mask_past_attn_bias_shared_test:´  input weights bias mask_index past attention_bias past_sequence_lengthy" Attention* num_heads * past_present_share_buffer :attention_double_head_bias_mask_past_attn_bias_shared_testZ input    Z weights    Z bias   Z mask_index   Z past   Z$ attention_bias    Z" past_sequence_length  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_mask_past_test.onnx000066400000000000000000000005041510465702400300230ustar00rootroot00000000000000 )attention_double_head_bias_mask_past_test: H input weights bias mask_index pasty" Attention* num_heads )attention_double_head_bias_mask_past_testZ input    Z weights    Z bias   Z mask_index   Z past   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_mask_test.onnx000066400000000000000000000004531510465702400267770ustar00rootroot00000000000000 $attention_double_head_bias_mask_test:ü Q input weights bias mask_indexy" Attention* num_heads : com.microsoft$attention_double_head_bias_mask_testZ input    Z weights    Z bias   Z mask_index   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_bias_test.onnx000066400000000000000000000003671510465702400257700ustar00rootroot00000000000000 attention_double_head_bias_test:Í E input weights biasy" Attention* num_heads : com.microsoftattention_double_head_bias_testZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_double_head_test.onnx000066400000000000000000000003041510465702400247610ustar00rootroot00000000000000 attention_double_head_test:Ÿ 0 input weightsy" Attention* num_heads attention_double_head_testZ input    Z weights    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_bias_dims_size.onnx000066400000000000000000000004011510465702400261570ustar00rootroot00000000000000  attention_invalid_bias_dims_size:Ö E input weights biasy" Attention* num_heads : com.microsoft attention_invalid_bias_dims_sizeZ input    Z weights    Z bias    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_bias_value_size.onnx000066400000000000000000000003731510465702400263470ustar00rootroot00000000000000 !attention_invalid_bias_value_size:Ï E input weights biasy" Attention* num_heads : com.microsoft!attention_invalid_bias_value_sizeZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_input_dimension.onnx000066400000000000000000000003771510465702400264130ustar00rootroot00000000000000 !attention_invalid_input_dimension:Ó E input weights biasy" Attention* num_heads : com.microsoft!attention_invalid_input_dimensionZ input     Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_input_num.onnx000066400000000000000000000002611510465702400252150ustar00rootroot00000000000000 attention_invalid_input_num:‹ 6 inputy" Attention* num_heads : com.microsoftattention_invalid_input_numZ input    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_mask_2d_dims_test.onnx000066400000000000000000000004511510465702400265730ustar00rootroot00000000000000 #attention_invalid_mask_2d_dims_test:û Q input weights bias mask_indexy" Attention* num_heads : com.microsoft#attention_invalid_mask_2d_dims_testZ input    Z weights    Z bias   Z mask_index    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_mask_3d_dims_test.onnx000066400000000000000000000004551510465702400266000ustar00rootroot00000000000000 #attention_invalid_mask_3d_dims_test:ÿ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft#attention_invalid_mask_3d_dims_testZ input    Z weights    Z bias   Z mask_index    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_mask_4d_dims_test.onnx000066400000000000000000000004611510465702400265760ustar00rootroot00000000000000 #attention_invalid_mask_4d_dims_test:ƒ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft#attention_invalid_mask_4d_dims_testZ input    Z weights    Z bias   Z$ mask_index     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_mask_4d_last_dims_test.onnx000066400000000000000000000004731510465702400276240ustar00rootroot00000000000000 (attention_invalid_mask_4d_last_dims_test:ˆ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft(attention_invalid_mask_4d_last_dims_testZ input    Z weights    Z bias   Z$ mask_index     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_mask_5d_dims_test.onnx000066400000000000000000000004651510465702400266030ustar00rootroot00000000000000 #attention_invalid_mask_5d_dims_test:‡ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft#attention_invalid_mask_5d_dims_testZ input    Z weights    Z bias   Z( mask_index      b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_mask_type_test.onnx000066400000000000000000000004431510465702400262340ustar00rootroot00000000000000  attention_invalid_mask_type_test:ø Q input weights bias mask_indexy" Attention* num_heads : com.microsoft attention_invalid_mask_type_testZ input    Z weights    Z bias   Z mask_index   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_no_num_heads.onnx000066400000000000000000000003431510465702400256370ustar00rootroot00000000000000 attention_invalid_no_num_heads:º 3 input weights biasy" Attention: com.microsoftattention_invalid_no_num_headsZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_qkv_attr_test.onnx000066400000000000000000000004221510465702400260700ustar00rootroot00000000000000 attention_invalid_qkv_attr_test:è ` input weights biasy" Attention* num_heads * qkv_hidden_sizes@@ : com.microsoftattention_invalid_qkv_attr_testZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_qkv_attr_test2.onnx000066400000000000000000000004261510465702400261560ustar00rootroot00000000000000  attention_invalid_qkv_attr_test2:ë b input weights biasy" Attention* num_heads * qkv_hidden_sizes@@@ : com.microsoft attention_invalid_qkv_attr_test2Z input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_qkv_attr_test3.onnx000066400000000000000000000004371510465702400261610ustar00rootroot00000000000000  attention_invalid_qkv_attr_test3:ô k input weights biasy" Attention* num_heads *$ qkv_hidden_sizes@@@ýÿÿÿÿÿÿÿÿ : com.microsoft attention_invalid_qkv_attr_test3Z input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_uneven_weight_no_qkv_hidden.onnx000066400000000000000000000004231510465702400307360ustar00rootroot00000000000000 -attention_invalid_uneven_weight_no_qkv_hidden:Û E input weights biasy" Attention* num_heads : com.microsoft-attention_invalid_uneven_weight_no_qkv_hiddenZ input    Z weights   Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_invalid_weight_hidden_size.onnx000066400000000000000000000004011510465702400270270ustar00rootroot00000000000000 $attention_invalid_weight_hidden_size:Ò E input weights biasy" Attention* num_heads : com.microsoft$attention_invalid_weight_hidden_sizeZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_multihead_bias_mask_test.onnx000066400000000000000000000004571510465702400265240ustar00rootroot00000000000000 "attention_multihead_bias_mask_test:‚ Q input weights bias mask_indexy" Attention* num_heads : com.microsoft"attention_multihead_bias_mask_testZ input   € €Z weights  € €Z bias  €Z mask_index   €b y   € €BROCm-AMDMIGraphX-46524e8/test/onnx/attention_multihead_test.onnx000066400000000000000000000003251510465702400245050ustar00rootroot00000000000000 attention_multihead_test:² ? input weightsy" Attention* num_heads : com.microsoftattention_multihead_testZ input   € €Z weights  € €b y   € €BROCm-AMDMIGraphX-46524e8/test/onnx/attention_single_head_batch1_test.onnx000066400000000000000000000003731510465702400262200ustar00rootroot00000000000000 !attention_single_head_batch1_test:Ï E input weights biasy" Attention* num_heads : com.microsoft!attention_single_head_batch1_testZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_single_head_batch2_test.onnx000066400000000000000000000003731510465702400262210ustar00rootroot00000000000000 !attention_single_head_batch2_test:Ï E input weights biasy" Attention* num_heads : com.microsoft!attention_single_head_batch2_testZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/attention_single_head_test.onnx000066400000000000000000000003551510465702400247760ustar00rootroot00000000000000 attention_single_head_test:È E input weights biasy" Attention* num_heads : com.microsoftattention_single_head_testZ input    Z weights    Z bias   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_1d_test.onnx000066400000000000000000000002161510465702400233330ustar00rootroot00000000000000averagepool_1d_test:q ( 01" AveragePool* kernel_shape@ averagepool_1d_testZ 0    b 1    B ROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_3d_test.onnx000066400000000000000000000002431510465702400233350ustar00rootroot00000000000000averagepool_3d_test:… , 01" AveragePool* kernel_shape@@@ averagepool_3d_testZ 0      b 1      B ROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_dilate_test.onnx000066400000000000000000000003101510465702400242640ustar00rootroot00000000000000averagepool_dilate_test:¦ Y xy" AveragePool* dilations@ * kernel_shape@ * pads@@ * strides@ averagepool_dilate_testZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_dyn_asym_padding_error_test.onnx000066400000000000000000000003421510465702400275510ustar00rootroot00000000000000'averagepool_dyn_asym_padding_error_test:° O xy" AveragePool* kernel_shape@@ * pads@@@@ * strides@@ 'averagepool_dyn_asym_padding_error_testZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_dyn_autopad_test.onnx000066400000000000000000000003401510465702400253340ustar00rootroot00000000000000averagepool_dyn_autopad_test:¹ [ 01" AveragePool* auto_pad" SAME_UPPER * kernel_shape@@@ * strides@@@ averagepool_dyn_autopad_testZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_dyn_cip_error_test.onnx000066400000000000000000000003051510465702400256640ustar00rootroot00000000000000averagepool_dyn_cip_error_test:œ D xy" AveragePool* count_include_pad * kernel_shape@@ averagepool_dyn_cip_error_testZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_dyn_test.onnx000066400000000000000000000003141510465702400236200ustar00rootroot00000000000000averagepool_dyn_test:­ W 01" AveragePool* kernel_shape@@@ * pads@@@@@@ * strides@@@ averagepool_dyn_testZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_notset_test.onnx000066400000000000000000000003351510465702400243450ustar00rootroot00000000000000averagepool_notset_test:» f xy" AveragePool* auto_pad"NOTSET * kernel_shape@@ * pads@@@@ * strides@@ averagepool_notset_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_nt_cip_test.onnx000066400000000000000000000003701510465702400243040ustar00rootroot00000000000000averagepool_nt_cip_test:Ö € xy" AveragePool* auto_pad"NOTSET * count_include_pad * kernel_shape@@ * pads@@@@ * strides@@ averagepool_nt_cip_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_same_lower_test.onnx000066400000000000000000000003041510465702400251620ustar00rootroot00000000000000averagepool_same_lower_test:ž E xy" AveragePool* auto_pad" SAME_LOWER * kernel_shape@@ averagepool_same_lower_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_same_upper_test.onnx000066400000000000000000000003041510465702400251650ustar00rootroot00000000000000averagepool_same_upper_test:ž E xy" AveragePool* auto_pad" SAME_UPPER * kernel_shape@@ averagepool_same_upper_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/averagepool_sl_cip_test.onnx000066400000000000000000000003261510465702400243020ustar00rootroot00000000000000averagepool_sl_cip_test:´ _ xy" AveragePool* auto_pad" SAME_LOWER * count_include_pad * kernel_shape@@ averagepool_sl_cip_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/batch_norm_1d_test.onnx000066400000000000000000000003611510465702400231440ustar00rootroot00000000000000batch_norm_1d_test:Ô 7 x scale bias mean variancey"BatchNormalizationbatch_norm_1d_testZ x     Z scale  Z bias  Z mean  Z variance  b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/batch_norm_2d_test.onnx000066400000000000000000000003711510465702400231460ustar00rootroot00000000000000batch_norm_2d_test:Ü 7 x scale bias mean variancey"BatchNormalizationbatch_norm_2d_testZ x     Z scale  Z bias  Z mean  Z variance  b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/batch_norm_3d_test.onnx000066400000000000000000000004241510465702400231460ustar00rootroot00000000000000batch_norm_3d_test:÷ J x scale bias mean variancey"BatchNormalization* epsilon½7†5 batch_norm_3d_testZ x       Z scale   Z bias   Z mean   Z variance   b y       B ROCm-AMDMIGraphX-46524e8/test/onnx/batch_norm_flat_test.onnx000066400000000000000000000003701510465702400235660ustar00rootroot00000000000000batch_norm_flat_test:Ù J x scale bias mean variancey"BatchNormalization* epsilon½7†5 batch_norm_flat_testZ x   Z scale  Z bias  Z mean  Z variance  b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/batch_norm_invalid_bias_rank_test.onnx000066400000000000000000000004331510465702400262770ustar00rootroot00000000000000!batch_norm_invalid_bias_rank_test:ï 7 x scale bias mean variancey"BatchNormalization!batch_norm_invalid_bias_rank_testZ x     Z scale  Z bias   Z mean  Z variance  b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/batch_norm_rank_2_test.onnx000066400000000000000000000004041510465702400240120ustar00rootroot00000000000000batch_norm_rank_2_test:ã J x scale bias mean variancey"BatchNormalization* epsilon½7†5 batch_norm_rank_2_testZ x   Z scale  Z bias  Z mean  Z variance  b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/biasadd_test.onnx000066400000000000000000000002631510465702400220340ustar00rootroot00000000000000  biasadd_test:œ * x bias skipy"BiasAdd: com.microsoft biasadd_testZ x    Z bias  Z skip    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/binary_dyn_brcst_add_test.onnx000066400000000000000000000002351510465702400246070ustar00rootroot00000000000000binary_dyn_brcst_add_test:z  0 1out"Addbinary_dyn_brcst_add_testZ 0    Z 1    b out    B ROCm-AMDMIGraphX-46524e8/test/onnx/binary_dyn_brcst_attr_error_test.onnx000066400000000000000000000003131510465702400262370ustar00rootroot00000000000000 binary_dyn_brcst_attr_error_test:  / 0 1out"Add* axis * broadcast  binary_dyn_brcst_attr_error_testZ 0    Z 1    b out    B ROCm-AMDMIGraphX-46524e8/test/onnx/binary_dyn_brcst_mul_fp8_test.onnx000066400000000000000000000002451510465702400254320ustar00rootroot00000000000000 binary_dyn_brcst_mul_fp8_test:~  0 1out"Mulbinary_dyn_brcst_mul_fp8_testZ 0    Z 1   b out    BROCm-AMDMIGraphX-46524e8/test/onnx/binary_dyn_brcst_mul_test.onnx000066400000000000000000000002351510465702400246540ustar00rootroot00000000000000binary_dyn_brcst_mul_test:z  0 1out"Mulbinary_dyn_brcst_mul_testZ 0    Z 1   b out    B ROCm-AMDMIGraphX-46524e8/test/onnx/binary_dyn_brcst_prelu_test.onnx000066400000000000000000000002431510465702400252050ustar00rootroot00000000000000binary_dyn_brcst_prelu_test:~  0 1out"PRelubinary_dyn_brcst_prelu_testZ 0    Z 1   b out    B ROCm-AMDMIGraphX-46524e8/test/onnx/bitwise_and_bcast_test.onnx000066400000000000000000000002361510465702400241110ustar00rootroot00000000000000 bitwise_and_bcast_test:~  0 12" BitwiseAndbitwise_and_bcast_testZ 0     Z 1   b 2     BROCm-AMDMIGraphX-46524e8/test/onnx/cast_test.onnx000066400000000000000000000001341510465702400213740ustar00rootroot00000000000000 cast-example:F  xy"Cast* to  test_castZ x    b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/castlike_error_test.onnx000066400000000000000000000001521510465702400234520ustar00rootroot00000000000000 castlike_error_test:M  0out"CastLikecastlike_error_testZ 0    b out   BROCm-AMDMIGraphX-46524e8/test/onnx/castlike_test.onnx000066400000000000000000000001621510465702400222420ustar00rootroot00000000000000  castlike_test:[  0 1out"CastLike castlike_testZ 0    Z 1   b out   BROCm-AMDMIGraphX-46524e8/test/onnx/ceil_test.onnx000066400000000000000000000001161510465702400213560ustar00rootroot00000000000000 ceil_test:; xy"Ceil ceil_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/celu_alpha_test.onnx000066400000000000000000000001531510465702400225400ustar00rootroot00000000000000celu_alpha_test:R  xy"Celu* alphaÍÌL? celu_alpha_testZ x  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/celu_default_test.onnx000066400000000000000000000001461510465702400231010ustar00rootroot00000000000000celu_default_test:K xy"Celucelu_default_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/celu_verify_test.onnx000066400000000000000000000001651510465702400227620ustar00rootroot00000000000000celu_verify_test:[  xy"Celu* alpha? celu_verify_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/celu_wrong_type_test.onnx000066400000000000000000000001541510465702400236510ustar00rootroot00000000000000celu_wrong_type_test:N xy"Celucelu_wrong_type_testZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/celu_zero_alpha_test.onnx000066400000000000000000000001751510465702400236030ustar00rootroot00000000000000celu_zero_alpha_test:_  xy"Celu* alpha celu_zero_alpha_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/clip_dyn_min_max_test.onnx000066400000000000000000000002121510465702400237500ustar00rootroot00000000000000clip_dyn_min_max_test:k  0 min max1"Clipclip_dyn_min_max_test* "Bmin* "À@BmaxZ 0  b 1  BROCm-AMDMIGraphX-46524e8/test/onnx/clip_dyn_min_only_test.onnx000066400000000000000000000001701510465702400241470ustar00rootroot00000000000000clip_dyn_min_only_test:X  0 min1"Clipclip_dyn_min_only_test* "BminZ 0  b 1  BROCm-AMDMIGraphX-46524e8/test/onnx/clip_test.onnx000066400000000000000000000001541510465702400213730ustar00rootroot00000000000000 clip_test:Y * 01"Clip* maxÀ@ * min  clip_testZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/clip_test_args_type_mismatch.onnx000066400000000000000000000002641510465702400253370ustar00rootroot00000000000000clip_test_args_type_mismatch:  0 min max1"Clipclip_test_args_type_mismatch*" À? @`@Bmin*:BmaxZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/clip_test_op11.onnx000066400000000000000000000002001510465702400222230ustar00rootroot00000000000000clip_test_op11:h  0 min max1"Clipclip_test_op11* "Bmin* "À@BmaxZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/clip_test_op11_max_only.onnx000066400000000000000000000002001510465702400241310ustar00rootroot00000000000000clip_test_op11_max_only:_  0 max1"Clipclip_test_op11_max_only* "BmaxZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/clip_test_op11_min_only.onnx000066400000000000000000000001761510465702400241430ustar00rootroot00000000000000clip_test_op11_min_only:]  0 min1"Clipclip_test_op11_min_only* "BminZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/clip_test_op11_no_args.onnx000066400000000000000000000001501510465702400237370ustar00rootroot00000000000000clip_test_op11_no_args:H 01"Clipclip_test_op11_no_argsZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/clip_test_op11_no_args1.onnx000066400000000000000000000001561510465702400240260ustar00rootroot00000000000000clip_test_op11_no_args1:M  0 1"Clipclip_test_op11_no_args1Z 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/concat_dyn_test.onnx000066400000000000000000000002071510465702400225640ustar00rootroot00000000000000concat_dyn_test:n  0 12"Concat* axis concat_dyn_testZ 0  Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/concat_test.onnx000066400000000000000000000002201510465702400217050ustar00rootroot00000000000000concat-example:x  0 12"Concat* axis  test-concatZ 0    Z 1    b 2    BROCm-AMDMIGraphX-46524e8/test/onnx/const_of_shape_default_test.onnx000066400000000000000000000002611510465702400251410ustar00rootroot00000000000000const_of_shape_default_test:‹ 6shape"Constant*# value*:B shape_tensor   shapey"ConstantOfShapeconst_of_shape_default_testb y    BROCm-AMDMIGraphX-46524e8/test/onnx/const_of_shape_dyn_float_test.onnx000066400000000000000000000002751510465702400255010ustar00rootroot00000000000000const_of_shape_dyn_float_test:• @ output_dimsy"ConstantOfShape* value*" ABvalue const_of_shape_dyn_float_testZ output_dims  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/const_of_shape_dyn_int64_test.onnx000066400000000000000000000002721510465702400253350ustar00rootroot00000000000000const_of_shape_dyn_int64_test:’ = output_dimsy"ConstantOfShape* value*: Bvalue const_of_shape_dyn_int64_testZ output_dims  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/const_of_shape_empty_input_test.onnx000066400000000000000000000003201510465702400260660ustar00rootroot00000000000000const_of_shape_empty_input_test:¦ 1shape"Constant* value*B empty_tensor  7 shapey"ConstantOfShape* value*: Bvalue const_of_shape_empty_input_testb y    BROCm-AMDMIGraphX-46524e8/test/onnx/const_of_shape_float_test.onnx000066400000000000000000000003141510465702400246210ustar00rootroot00000000000000const_of_shape_float_test:¨ 6shape"Constant*# value*:B shape_tensor  : shapey"ConstantOfShape* value*" ABvalue const_of_shape_float_testb y    BROCm-AMDMIGraphX-46524e8/test/onnx/const_of_shape_int64_test.onnx000066400000000000000000000003111510465702400244550ustar00rootroot00000000000000const_of_shape_int64_test:¥ 6shape"Constant*# value*:B shape_tensor  7 shapey"ConstantOfShape* value*: Bvalue const_of_shape_int64_testb y    BROCm-AMDMIGraphX-46524e8/test/onnx/const_of_shape_no_value_attr_test.onnx000066400000000000000000000002751510465702400263640ustar00rootroot00000000000000!const_of_shape_no_value_attr_test:‘ 6shape"Constant*# value*:B shape_tensor   shapey"ConstantOfShape!const_of_shape_no_value_attr_testb y    BROCm-AMDMIGraphX-46524e8/test/onnx/constant_empty_scalar_int64_test.onnx000066400000000000000000000001661510465702400260670ustar00rootroot00000000000000constant_scalar_test2:W -0"Constant* value*B empty_tensor constant_scalar_test2b 0  B ROCm-AMDMIGraphX-46524e8/test/onnx/constant_fill_input_as_shape_test.onnx000066400000000000000000000003631510465702400263670ustar00rootroot00000000000000!constant_fill_input_as_shape_test:Ç 5shape"Constant*" value**B shape_tensor  R shapevalue" ConstantFill* dtype * input_as_shape * value€? !constant_fill_input_as_shape_testb value   BROCm-AMDMIGraphX-46524e8/test/onnx/constant_fill_test.onnx000066400000000000000000000002451510465702400233040ustar00rootroot00000000000000constant-fill-example:… [value" ConstantFill* dtype * input_as_shape * shape@@ * value€?  constant_fillb value   B ROCm-AMDMIGraphX-46524e8/test/onnx/constant_multiple_attributes_test.onnx000066400000000000000000000002731510465702400264600ustar00rootroot00000000000000!constant_multiple_attributes_test: j"Constant*, value* " €?@B const_tensor * value_floats=€?=@ * value_ints@@ !constant_multiple_attributes_testBROCm-AMDMIGraphX-46524e8/test/onnx/constant_no_attributes_test.onnx000066400000000000000000000001161510465702400252350ustar00rootroot00000000000000constant_no_attributes_test:) "Constantconstant_no_attributes_testBROCm-AMDMIGraphX-46524e8/test/onnx/constant_one_val_int64_test.onnx000066400000000000000000000002051510465702400250210ustar00rootroot00000000000000constant_one_val_int64_test:` 00"Constant*! value*:B empty_tensor constant_one_val_int64_testb 0  B ROCm-AMDMIGraphX-46524e8/test/onnx/constant_scalar_test.onnx000066400000000000000000000001671510465702400236260ustar00rootroot00000000000000constant_scalar_test:Y 00"Constant*! value**B const_tensor constant_scalar_testb 0  B ROCm-AMDMIGraphX-46524e8/test/onnx/constant_test.onnx000066400000000000000000000001671510465702400223010ustar00rootroot00000000000000constant-example:] ;0"Constant*, value* " €?@B const_tensor  test-constantb 0  BROCm-AMDMIGraphX-46524e8/test/onnx/constant_value_float_test.onnx000066400000000000000000000001411510465702400246520ustar00rootroot00000000000000constant_value_float_test:> !"Constant* value_float=€? constant_value_float_testBROCm-AMDMIGraphX-46524e8/test/onnx/constant_value_floats_test.onnx000066400000000000000000000001561510465702400250430ustar00rootroot00000000000000constant_value_floats_test:J ,"Constant* value_floats=€?=@=@@ constant_value_floats_testBROCm-AMDMIGraphX-46524e8/test/onnx/constant_value_int_test.onnx000066400000000000000000000001301510465702400243350ustar00rootroot00000000000000constant_value_int_test:7 "Constant* value_int@ constant_value_int_testBROCm-AMDMIGraphX-46524e8/test/onnx/constant_value_ints_test.onnx000066400000000000000000000001371510465702400245270ustar00rootroot00000000000000constant_value_ints_test:= !"Constant* value_ints@@@ constant_value_ints_testBROCm-AMDMIGraphX-46524e8/test/onnx/conv.weight000066400000000000000000000113501510465702400206570ustar00rootroot00000000000000€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?ROCm-AMDMIGraphX-46524e8/test/onnx/conv_1d_fp8_test.onnx000066400000000000000000000002101510465702400225430ustar00rootroot00000000000000 conv_1d_fp8_test:n  0 12"Convconv_1d_fp8_testZ 0    Z 1    b 2    BROCm-AMDMIGraphX-46524e8/test/onnx/conv_1d_test.onnx000066400000000000000000000002001510465702400217650ustar00rootroot00000000000000 conv_1d_test:j  0 12"Conv conv_1d_testZ 0    Z 1    b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_3d_test.onnx000066400000000000000000000002311510465702400217730ustar00rootroot00000000000000 conv_3d_test:‚  0 12"Conv conv_3d_testZ 0      Z 1      b 2      B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_attr_fail_test.onnx000066400000000000000000000002411510465702400234330ustar00rootroot00000000000000conv_attr_fail_test:ƒ ! 0 12"Conv* strides@@ conv_attr_fail_testZ 0    Z 1    b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_autopad_fail_test.onnx000066400000000000000000000003401510465702400241160ustar00rootroot00000000000000 conv-example:É e 0 12"Conv* auto_pad"SAME * dilations@@ * pads@@@@@@@@ * strides@@  test_convZ 0      Z 1     b 2    " "B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_autopad_same_test.onnx000066400000000000000000000003341510465702400241330ustar00rootroot00000000000000conv_autopad_same_test:» J 0 12"Conv* auto_pad"SAME * dilations@@ * strides@@ conv_autopad_same_testZ 0      Z 1     b 2      B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_bad_bias_test.onnx000066400000000000000000000003231510465702400232130ustar00rootroot00000000000000 conv_bad_bias_test:¶ 8 0 1 23"Conv* dilations@@ * strides@@ conv_bad_bias_testZ 0      Z 1     Z 2  b 3     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_bias_test.onnx000066400000000000000000000003131510465702400224040ustar00rootroot00000000000000conv_bias_test:² 8 0 1 23"Conv* dilations@@ * strides@@ conv_bias_testZ 0      Z 1     Z 2  b 3     B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_bn_relu_maxpool_test.onnx000066400000000000000000000007241510465702400246610ustar00rootroot00000000000000conv_relu-example:¸ K 0 1 27"Conv* dilations@@ * pads@@@@ * strides@@  M 7 3 4 5 68"BatchNormalization* epsilon¬Å'7 * momentumfff?  89"Relu L 910"MaxPool* kernel_shape@@ * pads@@@@ * strides@@ test_conv_bn_reluZ 0      Z 1     Z 2  Z 3  Z 4  Z 5  Z 6  b 10     B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_bn_relu_maxpool_unordered_nonvalue_optional_ios_test.onnx000066400000000000000000000010511510465702400334100ustar00rootroot00000000000000 9conv_bn_relu_maxpool_unordered_nonvalue_optional_ios_test:å J 0 1 7"Conv* dilations@@ * pads@@@@ * strides@@  N 910"MaxPool* kernel_shape@@ * pads@@@@ * strides@@  Q 7 3 4 5 68"BatchNormalization* epsilon¬Å'7 * momentumfff?  89"Relu9conv_bn_relu_maxpool_unordered_nonvalue_optional_ios_testZ 0      Z 1     Z 2  Z 3  Z 4  Z 5  Z 6  b 10     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_bn_relu_maxpool_unordered_test.onnx000066400000000000000000000007701510465702400267310ustar00rootroot00000000000000 #conv_bn_relu_maxpool_unordered_test:Ê K 0 1 27"Conv* dilations@@ * pads@@@@ * strides@@  L 910"MaxPool* kernel_shape@@ * pads@@@@ * strides@@  M 7 3 4 5 68"BatchNormalization* epsilon¬Å'7 * momentumfff?  89"Relu#conv_bn_relu_maxpool_unordered_testZ 0      Z 1     Z 2  Z 3  Z 4  Z 5  Z 6  b 10     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_dynamic_batch_same_upper_test.onnx000066400000000000000000000003221510465702400264730ustar00rootroot00000000000000"conv_dynamic_batch_same_upper_test:¥ * 0 12"Conv* auto_pad" SAME_UPPER "conv_dynamic_batch_same_upper_testZ 0    Z 1     b 2     B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_dynamic_batch_test.onnx000066400000000000000000000002361510465702400242570ustar00rootroot00000000000000conv_dynamic_batch_test:}  0 12"Convconv_dynamic_batch_testZ 0    Z 1     b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_dynamic_bias_test.onnx000066400000000000000000000003271510465702400241150ustar00rootroot00000000000000conv_dynamic_bias_test:¶ 8 0 1 23"Conv* dilations@@ * strides@@ conv_dynamic_bias_testZ 0     Z 1     Z 2  b 3    BROCm-AMDMIGraphX-46524e8/test/onnx/conv_dynamic_img_and_weights_test.onnx000066400000000000000000000002521510465702400263240ustar00rootroot00000000000000!conv_dynamic_img_and_weights_test:  0 12"Conv!conv_dynamic_img_and_weights_testZ 0    Z 1    b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_dynamic_img_same_upper_test.onnx000066400000000000000000000003101510465702400261630ustar00rootroot00000000000000 conv_dynamic_img_same_upper_test: * 0 12"Conv* auto_pad" SAME_UPPER  conv_dynamic_img_same_upper_testZ 0    Z 1     b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_dynamic_img_test.onnx000066400000000000000000000002261510465702400237510ustar00rootroot00000000000000conv_dynamic_img_test:w  0 12"Convconv_dynamic_img_testZ 0    Z 1     b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_dynamic_kernel_same_lower_test.onnx000066400000000000000000000003221510465702400266670ustar00rootroot00000000000000#conv_dynamic_kernel_same_lower_test:¤ * 0 12"Conv* auto_pad" SAME_LOWER #conv_dynamic_kernel_same_lower_testZ 0     Z 1    b 2     B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_dynamic_weights_test.onnx000066400000000000000000000002361510465702400246500ustar00rootroot00000000000000conv_dynamic_weights_test:{  0 12"Convconv_dynamic_weights_testZ 0     Z 1    b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_relu_maxpool_test.onnx000066400000000000000000000004741510465702400242040ustar00rootroot00000000000000conv_relu-example:  K 0 1 23"Conv* dilations@@ * pads@@@@ * strides@@  34"Relu K 45"MaxPool* kernel_shape@@ * pads@@@@ * strides@@ test_conv_reluZ 0      Z 1     Z 2  b 5     B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_relu_maxpool_x2_test.onnx000066400000000000000000000010251510465702400246060ustar00rootroot00000000000000conv_relu-example:ù K 0 1 25"Conv* dilations@@ * pads@@@@ * strides@@  56"Relu K 67"MaxPool* kernel_shape@@ * pads@@@@ * strides@@  K 7 3 48"Conv* dilations@@ * pads@@@@ * strides@@  89"Relu L 910"MaxPool* kernel_shape@@ * pads@@@@ * strides@@ test_conv_relu2Z 0      Z 1     Z 2  Z 3     Z 4  b 10     B ROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_auto_pad_test.onnx000066400000000000000000000003301510465702400253570ustar00rootroot00000000000000conv_transpose_auto_pad_test:± : x wyconv1" ConvTranspose* auto_pad" SAME_UPPER conv_transpose_auto_pad_testZ x     Z w     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_bias_test.onnx000066400000000000000000000003111510465702400245000ustar00rootroot00000000000000conv_transpose_bias_test:¦ " x w byconv1" ConvTransposeconv_transpose_bias_testZ x     Z w     Z b  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_dyn_asym_padding_test.onnx000066400000000000000000000003511510465702400270770ustar00rootroot00000000000000$conv_transpose_dyn_asym_padding_test:º = x wy" ConvTranspose* pads@@@@ * strides@@ $conv_transpose_dyn_asym_padding_testZ x    Z w     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_dyn_batch_test.onnx000066400000000000000000000002731510465702400255240ustar00rootroot00000000000000conv_transpose_dyn_batch_test:“  x wyconv1" ConvTransposeconv_transpose_dyn_batch_testZ x    Z w     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_dyn_img_test.onnx000066400000000000000000000002631510465702400252160ustar00rootroot00000000000000conv_transpose_dyn_img_test:  x wyconv1" ConvTransposeconv_transpose_dyn_img_testZ x    Z w     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_dyn_output_shape_test.onnx000066400000000000000000000003531510465702400271620ustar00rootroot00000000000000$conv_transpose_dyn_output_shape_test:¼ A x wy" ConvTranspose* output_shape@ @ * strides@@ $conv_transpose_dyn_output_shape_testZ x    Z w     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_input_pads_asymm_1d_test.onnx000066400000000000000000000003611510465702400275270ustar00rootroot00000000000000'conv_transpose_input_pads_asymm_1d_test:¿ I x wy" ConvTranspose* dilations@ * pads@@ * strides@ 'conv_transpose_input_pads_asymm_1d_testZ x    Z w    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_input_pads_asymm_test.onnx000066400000000000000000000003531510465702400271440ustar00rootroot00000000000000$conv_transpose_input_pads_asymm_test:¼ = x wy" ConvTranspose* pads@@@@ * strides@@ $conv_transpose_input_pads_asymm_testZ x     Z w     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_input_pads_strides_test.onnx000066400000000000000000000003571510465702400274770ustar00rootroot00000000000000&conv_transpose_input_pads_strides_test:¾ = x wy" ConvTranspose* pads@@@@ * strides@@ &conv_transpose_input_pads_strides_testZ x     Z w     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_output_padding_3d_test.onnx000066400000000000000000000004031510465702400272000ustar00rootroot00000000000000%conv_transpose_output_padding_3d_test:Ó G x wy" ConvTranspose* output_padding@@@ * strides@@@ %conv_transpose_output_padding_3d_testZ x      Z w      b y      BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_output_padding_test.onnx000066400000000000000000000003551510465702400266200ustar00rootroot00000000000000"conv_transpose_output_padding_test:À C x wy" ConvTranspose* output_padding@@ * strides@@ "conv_transpose_output_padding_testZ x     Z w     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_output_shape_3d_test.onnx000066400000000000000000000003751510465702400267020ustar00rootroot00000000000000#conv_transpose_output_shape_3d_test:Ï E x wy" ConvTranspose* output_shape@ @@ * strides@@@ #conv_transpose_output_shape_3d_testZ x      Z w      b y      BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_output_shape_test.onnx000066400000000000000000000003471510465702400263130ustar00rootroot00000000000000 conv_transpose_output_shape_test:¼ A x wy" ConvTranspose* output_shape@ @ * strides@@  conv_transpose_output_shape_testZ x     Z w     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_stride_test.onnx000066400000000000000000000003041510465702400250560ustar00rootroot00000000000000conv_transpose_stride_test:Ÿ * x wy" ConvTranspose* strides@@ conv_transpose_stride_testZ x     Z w     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/conv_transpose_test.onnx000066400000000000000000000002531510465702400235070ustar00rootroot00000000000000conv_transpose_test:  x wyconv1" ConvTransposeconv_transpose_testZ x     Z w     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_bias_test.onnx000066400000000000000000000003401510465702400237620ustar00rootroot00000000000000 convinteger_bias_test:À ? 0 1 23" ConvInteger* dilations@@ * strides@@ convinteger_bias_testZ 0      Z 1     Z 2  b 3     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_dual_bias_simple_test.onnx000066400000000000000000000004141510465702400263420ustar00rootroot00000000000000 !convinteger_dual_bias_simple_test:à B 0 1 2 34" ConvInteger* dilations@@ * strides@@ !convinteger_dual_bias_simple_testZ 0     Z 1     Z 2  Z 3  b 4     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_dual_bias_test.onnx000066400000000000000000000003761510465702400250000ustar00rootroot00000000000000 convinteger_dual_bias_test:Ù B 0 1 2 34" ConvInteger* dilations@@ * strides@@ convinteger_dual_bias_testZ 0      Z 1     Z 2  Z 3  b 4     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_mismatched_data_bias_test.onnx000066400000000000000000000004241510465702400271540ustar00rootroot00000000000000 %convinteger_mismatched_data_bias_test:ä B 0 1 2 34" ConvInteger* dilations@@ * strides@@ %convinteger_mismatched_data_bias_testZ 0      Z 1     Z 2  Z 3  b 4     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_mismatched_input_types_test.onnx000066400000000000000000000003601510465702400276270ustar00rootroot00000000000000 'convinteger_mismatched_input_types_test:¾ < 0 14" ConvInteger* dilations@@ * strides@@ 'convinteger_mismatched_input_types_testZ 0      Z 1     b 4     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_mismatched_inputs_dual_bias_test.onnx000066400000000000000000000004421510465702400305720ustar00rootroot00000000000000 ,convinteger_mismatched_inputs_dual_bias_test:ë B 0 1 2 34" ConvInteger* dilations@@ * strides@@ ,convinteger_mismatched_inputs_dual_bias_testZ 0     Z 1     Z 2  Z 3  b 4     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_mismatched_inputs_dual_symmetric_bias_test.onnx000066400000000000000000000005661510465702400326750ustar00rootroot00000000000000 6convinteger_mismatched_inputs_dual_symmetric_bias_test:µ B 0 1 2 34" ConvInteger* dilations@@ * strides@@  .2"Constant* value**B const_tensor  03"Constant*! value**€B const_tensor2 6convinteger_mismatched_inputs_dual_symmetric_bias_testZ 0      Z 1     b 4     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_mismatched_weight_bias_test.onnx000066400000000000000000000004301510465702400275270ustar00rootroot00000000000000 'convinteger_mismatched_weight_bias_test:æ B 0 1 2 34" ConvInteger* dilations@@ * strides@@ 'convinteger_mismatched_weight_bias_testZ 0      Z 1     Z 2  Z 3  b 4     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_no_bias_test.onnx000066400000000000000000000003221510465702400244560ustar00rootroot00000000000000 convinteger_no_bias_test:¯ < 0 13" ConvInteger* dilations@@ * strides@@ convinteger_no_bias_testZ 0     Z 1     b 3     BROCm-AMDMIGraphX-46524e8/test/onnx/convinteger_no_bias_uint8_test.onnx000066400000000000000000000003361510465702400256120ustar00rootroot00000000000000 convinteger_no_bias_uint8_test:µ < 0 13" ConvInteger* dilations@@ * strides@@ convinteger_no_bias_uint8_testZ 0      Z 1     b 3     BROCm-AMDMIGraphX-46524e8/test/onnx/cos_fp8_test.onnx000066400000000000000000000001231510465702400220010ustar00rootroot00000000000000  cos_fp8_test:= xy"Cos cos_fp8_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/cos_test.onnx000066400000000000000000000001161510465702400212260ustar00rootroot00000000000000 cos-example:9 xy"Costest_cosZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/cosh_test.onnx000066400000000000000000000001211510465702400213720ustar00rootroot00000000000000 cosh-example:; xy"Cosh test_coshZ x  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/depthtospace_crd_test.onnx000066400000000000000000000002511510465702400237550ustar00rootroot00000000000000depthtospace_crd_test:‰ 6 xy" DepthToSpace* blocksize * mode"CRD depthtospace_crd_testZ x     b y      BROCm-AMDMIGraphX-46524e8/test/onnx/depthtospace_simple_test.onnx000066400000000000000000000002571510465702400245040ustar00rootroot00000000000000depthtospace_simple_test:Œ 6 xy" DepthToSpace* blocksize * mode"DCR depthtospace_simple_testZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/depthtospace_test.onnx000066400000000000000000000002411510465702400231240ustar00rootroot00000000000000depthtospace_test:… 6 xy" DepthToSpace* blocksize * mode"DCR depthtospace_testZ x     b y      BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_2d_blocked_with_zp_test.onnx000066400000000000000000000003731510465702400276270ustar00rootroot00000000000000 (dequantizelinear_2d_blocked_with_zp_test:È C x scale zpy"DequantizeLinear* axis * block_size (dequantizelinear_2d_blocked_with_zp_testZ x   Z scale   Z zp   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_3d_blocked_with_zp_runt_block_test.onnx000066400000000000000000000004411510465702400320460ustar00rootroot00000000000000 3dequantizelinear_3d_blocked_with_zp_runt_block_test:ã C x scale zpy"DequantizeLinear* axis * block_size 3dequantizelinear_3d_blocked_with_zp_runt_block_testZ x    Z scale    Z zp    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_axis_test.onnx000066400000000000000000000003161510465702400250340ustar00rootroot00000000000000dequantizelinear_axis_test:© - 0 1 2out"DequantizeLinear* axis dequantizelinear_axis_testZ 0     Z 1  Z 2  b out     BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_neg_axis_test.onnx000066400000000000000000000003371510465702400256700ustar00rootroot00000000000000dequantizelinear_neg_axis_test:¶ 6 0 1 2out"DequantizeLinear* axisþÿÿÿÿÿÿÿÿ dequantizelinear_neg_axis_testZ 0     Z 1  Z 2  b out     BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_scale_and_zp_shape_mismatch_test.onnx000066400000000000000000000004151510465702400315570ustar00rootroot00000000000000 1dequantizelinear_scale_and_zp_shape_mismatch_test:Ñ C x scale zpy"DequantizeLinear* axis * block_size 1dequantizelinear_scale_and_zp_shape_mismatch_testZ x   Z scale   Z zp   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_test.onnx000066400000000000000000000002121510465702400240030ustar00rootroot00000000000000dequantizelinear_test:k  0 1out"DequantizeLineardequantizelinear_testZ 0  Z 1  b out  BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_too_few_inputs_test.onnx000066400000000000000000000002711510465702400271340ustar00rootroot00000000000000 $dequantizelinear_too_few_inputs_test:Š 8 xy"DequantizeLinear* axis * block_size $dequantizelinear_too_few_inputs_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_too_many_inputs_test.onnx000066400000000000000000000004211510465702400273140ustar00rootroot00000000000000 %dequantizelinear_too_many_inputs_test:á H x scale zp zp2y"DequantizeLinear* axis * block_size %dequantizelinear_too_many_inputs_testZ x   Z scale   Z zp   Z zp2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_x_and_zp_type_mismatch_test.onnx000066400000000000000000000004031510465702400306150ustar00rootroot00000000000000 ,dequantizelinear_x_and_zp_type_mismatch_test:Ì C x scale zpy"DequantizeLinear* axis * block_size ,dequantizelinear_x_and_zp_type_mismatch_testZ x   Z scale   Z zp   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/dequantizelinear_zero_point_test.onnx000066400000000000000000000002651510465702400262630ustar00rootroot00000000000000 dequantizelinear_zero_point_test:Š 0 1 2out"DequantizeLinear dequantizelinear_zero_point_testZ 0  Z 1  Z 2  b out  BROCm-AMDMIGraphX-46524e8/test/onnx/dim_param_test.onnx000066400000000000000000000001421510465702400223720ustar00rootroot00000000000000 dim_param_test:Jdim_param_testZ 0  dim0 dim1b 0  dim0 dim1BROCm-AMDMIGraphX-46524e8/test/onnx/div_fp8_test.onnx000066400000000000000000000001671510465702400220070ustar00rootroot00000000000000  div_fp8_test:a  0 1out"Div div_fp8_testZ 0   Z 1   b out   BROCm-AMDMIGraphX-46524e8/test/onnx/dropout_test.onnx000066400000000000000000000001621510465702400221370ustar00rootroot00000000000000dropout-example:Y  01"Dropout test-dropoutZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/dynamicquantizelinear_1d_test.onnx000066400000000000000000000003221510465702400254250ustar00rootroot00000000000000 dynamicquantizelinear_1d_test:ª 4 xyy_scale y_zero_point"DynamicQuantizeLineardynamicquantizelinear_1d_testZ x  b y  b y_scale  b y_zero_point  BROCm-AMDMIGraphX-46524e8/test/onnx/dynamicquantizelinear_2d_test.onnx000066400000000000000000000003321510465702400254270ustar00rootroot00000000000000 dynamicquantizelinear_2d_test:² 4 xyy_scale y_zero_point"DynamicQuantizeLineardynamicquantizelinear_2d_testZ x   b y   b y_scale  b y_zero_point  BROCm-AMDMIGraphX-46524e8/test/onnx/dynamicscale_even_test.onnx000066400000000000000000000004421510465702400241150ustar00rootroot00000000000000 dynamicscale_even_test: £ inputoutput" DynamicScale* group_dim * group_size  * output_dtype *" scale_selection_method"floor *& zero_point_selection_method"None dynamicscale_even_testZ input   @  b output   @  BROCm-AMDMIGraphX-46524e8/test/onnx/dynamicscale_odd_test.onnx000066400000000000000000000004301510465702400237230ustar00rootroot00000000000000 dynamicscale_odd_test:ø £ inputoutput" DynamicScale* group_dim * group_size  * output_dtype *" scale_selection_method"floor *& zero_point_selection_method"None dynamicscale_odd_testZ input  G  b output  G  BROCm-AMDMIGraphX-46524e8/test/onnx/dynamicscale_small_test.onnx000066400000000000000000000004351510465702400242720ustar00rootroot00000000000000 dynamicscale_small_test:û ¬ inputoutput" DynamicScale* group_dimÿÿÿÿÿÿÿÿÿ * group_size  * output_dtype *" scale_selection_method"floor *& zero_point_selection_method"None dynamicscale_small_testZ input   b output   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_2d_3d_multiplication_test.onnx000066400000000000000000000002721510465702400260350ustar00rootroot00000000000000  einsum_2d_3d_multiplication_test: * x1 x2y"Einsum* equation"ij,jkl  einsum_2d_3d_multiplication_testZ x1   Z x2    b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_3_inputs_test.onnx000066400000000000000000000003211510465702400235640ustar00rootroot00000000000000 einsum_3_inputs_test:² 7 x1 x2 x3y"Einsum* equation"bac,cd,def->ebc einsum_3_inputs_testZ x1    Z x2   Z x3    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_3d_broadcast_test.onnx000066400000000000000000000002741510465702400243570ustar00rootroot00000000000000 einsum_3d_broadcast_test:™ 0 x1 x2y"Einsum* equation" bik,bkj->bij einsum_3d_broadcast_testZ x1    Z x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_3d_diagonal_test.onnx000066400000000000000000000002171510465702400241700ustar00rootroot00000000000000 einsum_3d_diagonal_test:n % xy"Einsum* equation"iii->i einsum_3d_diagonal_testZ x    b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_3d_opposite_broadcast_test.onnx000066400000000000000000000003161510465702400262760ustar00rootroot00000000000000 !einsum_3d_opposite_broadcast_test:¢ 0 x1 x2y"Einsum* equation" bik,bkj->bij !einsum_3d_opposite_broadcast_testZ x1    Z x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_batch_matrix_diagonal_test.onnx000066400000000000000000000002501510465702400263240ustar00rootroot00000000000000 !einsum_batch_matrix_diagonal_test:} * xy"Einsum* equation" ...ii->...i !einsum_batch_matrix_diagonal_testZ x    b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_batch_matrix_multiplication_test.onnx000066400000000000000000000003321510465702400276040ustar00rootroot00000000000000 'einsum_batch_matrix_multiplication_test:¨ 0 x1 x2y"Einsum* equation" ijk,ikl->ijl 'einsum_batch_matrix_multiplication_testZ x1    Z x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_bilinear_transformation_test.onnx000066400000000000000000000003451510465702400267410ustar00rootroot00000000000000 #einsum_bilinear_transformation_test:· 5 x1 x2 x3y"Einsum* equation" ik,jkl,il->ij #einsum_bilinear_transformation_testZ x1   Z x2    Z x3   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_broadcast_test.onnx000066400000000000000000000002521510465702400237650ustar00rootroot00000000000000 einsum_broadcast_test:Š 0 x1 x2y"Einsum* equation" ij, jk -> ik einsum_broadcast_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_column_sum_test.onnx000066400000000000000000000002041510465702400242010ustar00rootroot00000000000000 einsum_column_sum_test:d $ xy"Einsum* equation"ij->j einsum_column_sum_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_comma_in_output_negative_test.onnx000066400000000000000000000003061510465702400271070ustar00rootroot00000000000000 $einsum_comma_in_output_negative_test:— . x1 x2y"Einsum* equation" ii,jj->i,j $einsum_comma_in_output_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_common_1_test.onnx000066400000000000000000000003031510465702400235300ustar00rootroot00000000000000 einsum_common_1_test:¤ 3 x1 x2y"Einsum* equation"bsnh,btnh->bnts einsum_common_1_testZ x1     Z x2     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_common_2_test.onnx000066400000000000000000000002761510465702400235420ustar00rootroot00000000000000 einsum_common_2_test:Ÿ 2 x1 x2y"Einsum* equation"bsnh,ctnh->nts einsum_common_2_testZ x1     Z x2     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_common_3_test.onnx000066400000000000000000000002761510465702400235430ustar00rootroot00000000000000 einsum_common_3_test:Ÿ 2 x1 x2y"Einsum* equation"bnst,chst->shn einsum_common_3_testZ x1     Z x2     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_common_4_test.onnx000066400000000000000000000003031510465702400235330ustar00rootroot00000000000000 einsum_common_4_test:¤ 3 x1 x2y"Einsum* equation"bcxd,bcyd->bcxy einsum_common_4_testZ x1     Z x2     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_common_5_test.onnx000066400000000000000000000003111510465702400235330ustar00rootroot00000000000000 einsum_common_5_test:ª 9 x1 x2y"Einsum*$ equation"...qhd,...khd->...hqk einsum_common_5_testZ x1     Z x2     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_common_6_test.onnx000066400000000000000000000002721510465702400235420ustar00rootroot00000000000000 einsum_common_6_test:› 6 x1 x2y"Einsum*! equation"i...k,k...j->i...j einsum_common_6_testZ x1    Z x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_common_7_test.onnx000066400000000000000000000002041510465702400235360ustar00rootroot00000000000000 einsum_common_7_test:f ( xy"Einsum* equation" ...j->... einsum_common_7_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_common_8_test.onnx000066400000000000000000000002451510465702400235440ustar00rootroot00000000000000 einsum_common_8_test:† - x1 x2y"Einsum* equation" ii,jj->ij einsum_common_8_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_diag_vector_multiply_test.onnx000066400000000000000000000002631510465702400262520ustar00rootroot00000000000000  einsum_diag_vector_multiply_test:ˆ + x1 x2y"Einsum* equation"ii,i->i  einsum_diag_vector_multiply_testZ x1   Z x2  b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_diagonal_dim_mismatch_negative_test.onnx000066400000000000000000000002541510465702400302030ustar00rootroot00000000000000 *einsum_diagonal_dim_mismatch_negative_test:x $ xy"Einsum* equation"ii->i *einsum_diagonal_dim_mismatch_negative_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_element_wise_multiplication_and_row_sum_test.onnx000066400000000000000000000003311510465702400322130ustar00rootroot00000000000000 3einsum_element_wise_multiplication_and_row_sum_test:› + x1 x2y"Einsum* equation"i,ij->i 3einsum_element_wise_multiplication_and_row_sum_testZ x1  Z x2   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_ellipsis_implicit_form_test.onnx000066400000000000000000000003311510465702400265620ustar00rootroot00000000000000 "einsum_ellipsis_implicit_form_test:¬ 1 x1 x2y"Einsum* equation" ...qhd,...khd "einsum_ellipsis_implicit_form_testZ x1     Z x2     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_ellipsis_mismatch_negative_test.onnx000066400000000000000000000003421510465702400274160ustar00rootroot00000000000000 &einsum_ellipsis_mismatch_negative_test:± 6 x1 x2y"Einsum*! equation"...ii,...jj->...ij &einsum_ellipsis_mismatch_negative_testZ x1    Z x2     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_ellipsis_multidim_test.onnx000066400000000000000000000003301510465702400255500ustar00rootroot00000000000000 einsum_ellipsis_multidim_test:° 6 x1 x2y"Einsum*! equation"...ik,kj...->ij... einsum_ellipsis_multidim_testZ x1     Z x2     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_ellipsis_scalar_multiplication_test.onnx000066400000000000000000000003201510465702400303050ustar00rootroot00000000000000 *einsum_ellipsis_scalar_multiplication_test:› , x1 x2y"Einsum* equation"..., ... *einsum_ellipsis_scalar_multiplication_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_ellipsis_test.onnx000066400000000000000000000002721510465702400236510ustar00rootroot00000000000000 einsum_ellipsis_test:› 6 x1 x2y"Einsum*! equation"...ik,kj...->ij... einsum_ellipsis_testZ x1    Z x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_ellipsis_zero_test.onnx000066400000000000000000000003071510465702400247070ustar00rootroot00000000000000 einsum_ellipsis_zero_test:£ 9 x1 x2y"Einsum*$ equation"...qhd,...khd->...hqk einsum_ellipsis_zero_testZ x1    Z x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_empty_term_before_arrow_negative_test.onnx000066400000000000000000000003231510465702400306250ustar00rootroot00000000000000 ,einsum_empty_term_before_arrow_negative_test:œ + x1 x2y"Einsum* equation"ii,->ij ,einsum_empty_term_before_arrow_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_empty_term_before_comma_negative_test.onnx000066400000000000000000000003261510465702400305720ustar00rootroot00000000000000 ,einsum_empty_term_before_comma_negative_test:Ÿ . x1 x2y"Einsum* equation" ii,,jj->ij ,einsum_empty_term_before_comma_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_hadamard_product_test.onnx000066400000000000000000000002651510465702400253300ustar00rootroot00000000000000 einsum_hadamard_product_test:Ž - x1 x2y"Einsum* equation" ij,ij->ij einsum_hadamard_product_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_last_input_missing_negative_test.onnx000066400000000000000000000003101510465702400276130ustar00rootroot00000000000000 'einsum_last_input_missing_negative_test:– * x1 x2y"Einsum* equation"ii,jj, 'einsum_last_input_missing_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_matrix_diagonal_test.onnx000066400000000000000000000002161510465702400251650ustar00rootroot00000000000000 einsum_matrix_diagonal_test:i $ xy"Einsum* equation"ii->i einsum_matrix_diagonal_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_matrix_dot_product_test.onnx000066400000000000000000000002631510465702400257370ustar00rootroot00000000000000 einsum_matrix_dot_product_test:Š + x1 x2y"Einsum* equation"ij,ij-> einsum_matrix_dot_product_testZ x1   Z x2   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_matrix_matrix_multiplication_test.onnx000066400000000000000000000003151510465702400300300ustar00rootroot00000000000000 (einsum_matrix_matrix_multiplication_test:š - x1 x2y"Einsum* equation" ij,kj->ik (einsum_matrix_matrix_multiplication_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_matrix_outer_product_test.onnx000066400000000000000000000003071510465702400263060ustar00rootroot00000000000000  einsum_matrix_outer_product_test:œ / x1 x2y"Einsum* equation" ij,kl->ijkl  einsum_matrix_outer_product_testZ x1   Z x2   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_matrix_trace_implicit_test.onnx000066400000000000000000000002271510465702400264010ustar00rootroot00000000000000 !einsum_matrix_trace_implicit_test:l ! xy"Einsum* equation"ii !einsum_matrix_trace_implicit_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_matrix_trace_test.onnx000066400000000000000000000002071510465702400245050ustar00rootroot00000000000000 einsum_matrix_trace_test:e # xy"Einsum* equation"ii-> einsum_matrix_trace_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_matrix_vector_multiplication_test.onnx000066400000000000000000000003031510465702400300230ustar00rootroot00000000000000 (einsum_matrix_vector_multiplication_test: ) x vy"Einsum* equation"ij,j->i (einsum_matrix_vector_multiplication_testZ x   Z v  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_missing_equation_negative_test.onnx000066400000000000000000000002541510465702400272650ustar00rootroot00000000000000 %einsum_missing_equation_negative_test:}  x1 x2y"Einsum%einsum_missing_equation_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_multiple_arrows_negative_test.onnx000066400000000000000000000003071510465702400271360ustar00rootroot00000000000000 $einsum_multiple_arrows_negative_test:˜ / x1 x2y"Einsum* equation" ii,jj->->ij $einsum_multiple_arrows_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_multiple_diagonals_negative_test.onnx000066400000000000000000000002661510465702400275660ustar00rootroot00000000000000 'einsum_multiple_diagonals_negative_test:„ ' xy"Einsum* equation"iijj->ij 'einsum_multiple_diagonals_negative_testZ x     b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_multiple_ellipses_negative_test.onnx000066400000000000000000000003411510465702400274370ustar00rootroot00000000000000 &einsum_multiple_ellipses_negative_test:° 9 x1 x2y"Einsum*$ equation"......ii,...jj->...ij &einsum_multiple_ellipses_negative_testZ x1    Z x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_output_missing_ellipsis_negative_test.onnx000066400000000000000000000003431510465702400307030ustar00rootroot00000000000000 ,einsum_output_missing_ellipsis_negative_test:¬ 3 x1 x2y"Einsum* equation"...ii,...jj->ij ,einsum_output_missing_ellipsis_negative_testZ x1    Z x2    b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_output_surplus_label_negative_test.onnx000066400000000000000000000003201510465702400301750ustar00rootroot00000000000000 )einsum_output_surplus_label_negative_test:œ . x1 x2y"Einsum* equation" ii,jj->ijk )einsum_output_surplus_label_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_permute_sd3_test.onnx000066400000000000000000000002641510465702400242600ustar00rootroot00000000000000 einsum_permute_sd3_test:’ - xy"Einsum* equation"nhwpqc->nchpwq einsum_permute_sd3_testZ# x   @ @   b# y    @  @ BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_permute_test.onnx000066400000000000000000000002031510465702400235000ustar00rootroot00000000000000 einsum_permute_test:f % xy"Einsum* equation"ij->ji einsum_permute_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_rank_mismatch_negative_test.onnx000066400000000000000000000003021510465702400265210ustar00rootroot00000000000000 "einsum_rank_mismatch_negative_test:• . x1 x2y"Einsum* equation" iik,jj->ij "einsum_rank_mismatch_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_right_batch_diagonal_negative_test.onnx000066400000000000000000000002711510465702400300220ustar00rootroot00000000000000 )einsum_right_batch_diagonal_negative_test:… * xy"Einsum* equation" ii...->i... )einsum_right_batch_diagonal_negative_testZ x    b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_row_sum_test.onnx000066400000000000000000000001761510465702400235230ustar00rootroot00000000000000 einsum_row_sum_test:a $ xy"Einsum* equation"ij->i einsum_row_sum_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_summation_test.onnx000066400000000000000000000002011510465702400240310ustar00rootroot00000000000000 einsum_summation_test:b # xy"Einsum* equation"ij-> einsum_summation_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_tensor_contraction_test.onnx000066400000000000000000000003411510465702400257370ustar00rootroot00000000000000 einsum_tensor_contraction_test:¸ 5 x1 x2y"Einsum* equation"pqrs,tuqvr->pstuv einsum_tensor_contraction_testZ x1     Z x2      b y      BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_term_input_mismatch_negative_test.onnx000066400000000000000000000003211510465702400277550ustar00rootroot00000000000000 (einsum_term_input_mismatch_negative_test:ž 1 x1 x2y"Einsum* equation" ii,jj,kk->ijk (einsum_term_input_mismatch_negative_testZ x1   Z x2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_vector_dot_product_test.onnx000066400000000000000000000002511510465702400257320ustar00rootroot00000000000000 einsum_vector_dot_product_test:€ ) x1 x2y"Einsum* equation"i,i-> einsum_vector_dot_product_testZ x1  Z x2  b y  BROCm-AMDMIGraphX-46524e8/test/onnx/einsum_vector_outer_product_test.onnx000066400000000000000000000002631510465702400263050ustar00rootroot00000000000000  einsum_vector_outer_product_test:ˆ + x1 x2y"Einsum* equation"i,j->ij  einsum_vector_outer_product_testZ x1  Z x2  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/elu_test.onnx000066400000000000000000000001411510465702400212250ustar00rootroot00000000000000 elu-example:L  01"Elu* alpha ×#<  test-modelZ 0  b 1  BROCm-AMDMIGraphX-46524e8/test/onnx/embedding_bag_offset_test.onnx000066400000000000000000000004451510465702400245440ustar00rootroot00000000000000embedding_bag_offset_test: 2index"Constant* value**B index_val  4offset"Constant* value**B offset_val  K weight index offsety"ATen* mode * operator" embedding_bag embedding_bag_offset_testZ weight   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/embedding_bag_test.onnx000066400000000000000000000007371510465702400232020ustar00rootroot00000000000000embedding_bag_test: 3index"Constant* value**B index_val  1offset"Constant* value**B offset_val  L weight index offsety1"ATen* mode * operator" embedding_bag  L weight index offsety2"ATen* mode * operator" embedding_bag  L weight index offsety3"ATen* mode * operator" embedding_bag embedding_bag_testZ weight   b y1   b y2   b y3   B ROCm-AMDMIGraphX-46524e8/test/onnx/equal_bool_test.onnx000066400000000000000000000002351510465702400225660ustar00rootroot00000000000000equal_bool_test:ƒ  x1bx1"Cast* to    bx1 x2y"Equalequal_bool_testZ x1   Z x2    b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/equal_test.onnx000066400000000000000000000002051510465702400215500ustar00rootroot00000000000000 equal_test:q  x1 x2y"Equal equal_test*$"€?@@@€@ @À@Bx1Z x2   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/erf_test.onnx000066400000000000000000000001261510465702400212170ustar00rootroot00000000000000 erf-example:A xy"Erftest_erfZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/exp_test.onnx000066400000000000000000000001161510465702400212360ustar00rootroot00000000000000 exp-example:9 xy"Exptest_expZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/expand_dyn_input_dyn_output_test.onnx000066400000000000000000000002531510465702400263060ustar00rootroot00000000000000  expand_dyn_input_dyn_output_test:€  x dimsy"Expand expand_dyn_input_dyn_output_testZ x   Z dims  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/expand_dyn_input_static_dims_throw.onnx000066400000000000000000000003201510465702400265560ustar00rootroot00000000000000 "expand_dyn_input_static_dims_throw:£ 6shape"Constant*# value**B shape_tensor   x shapey"Expand"expand_dyn_input_static_dims_throwZ x   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/expand_static_input_dyn_output_test.onnx000066400000000000000000000002631510465702400270040ustar00rootroot00000000000000 #expand_static_input_dyn_output_test:…  x dimsy"Expand#expand_static_input_dyn_output_testZ x    Z dims  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/expand_test.onnx000066400000000000000000000002371510465702400217250ustar00rootroot00000000000000expand:Ž 7shape"Constant*$ value**B shape_tensor   x shapey"ExpandexpandZ x    b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/ext_path/000077500000000000000000000000001510465702400203155ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/ext_path/conv.weight000066400000000000000000000113501510465702400224730ustar00rootroot00000000000000€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?ROCm-AMDMIGraphX-46524e8/test/onnx/ext_path/external_data_test.onnx000066400000000000000000000005531510465702400250760ustar00rootroot00000000000000pytorch1.5:Ô Ž input conv.weight conv.bias3Conv_0"Conv* dilations@@ * group * kernel_shape@ @  * pads@@@@ * strides@@ torch-jit-export*9 B conv.biasJ(€?€?€?€?€?€?€?€?€?€?*2   B conv.weightj location conv.weightpZ! input    à àb 3    Ö ÖB ROCm-AMDMIGraphX-46524e8/test/onnx/external_constant_test.onnx000066400000000000000000000003021510465702400241720ustar00rootroot00000000000000external_constant_test:¡ v0"Constant*g value*[B const_tensorj) locationexternal_constant_test.weightj offset48j length24p external_constant_testb 0  BROCm-AMDMIGraphX-46524e8/test/onnx/external_constant_test.weight000066400000000000000000000001101510465702400244740ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/external_data_test.onnx000066400000000000000000000005531510465702400232620ustar00rootroot00000000000000pytorch1.5:Ô Ž input conv.weight conv.bias3Conv_0"Conv* dilations@@ * group * kernel_shape@ @  * pads@@@@ * strides@@ torch-jit-export*9 B conv.biasJ(€?€?€?€?€?€?€?€?€?€?*2   B conv.weightj location conv.weightpZ! input    à àb 3    Ö ÖB ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_bf16_test.onnx000066400000000000000000000001551510465702400227120ustar00rootroot00000000000000 eyelike_bf16_test:R  T1T2"EyeLikeeyelike_bf16_testZ T1   b T2   BROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_default_test.onnx000066400000000000000000000001631510465702400235770ustar00rootroot00000000000000eyelike_default_test:U  T1T2"EyeLikeeyelike_default_testZ T1   b T2   B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_double_test.onnx000066400000000000000000000001611510465702400234230ustar00rootroot00000000000000eyelike_double_test:T  T1T2"EyeLikeeyelike_double_testZ T1    b T2    B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_half_test.onnx000066400000000000000000000001551510465702400230660ustar00rootroot00000000000000eyelike_half_test:R  T1T2"EyeLikeeyelike_half_testZ T1    b T2    B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_k_outofbounds_neg_test.onnx000066400000000000000000000002321510465702400256620ustar00rootroot00000000000000eyelike_k_outofbounds_neg_test:r $ T1T2"EyeLike* kþÿÿÿÿÿÿÿÿ eyelike_k_outofbounds_neg_testZ T1   b T2   B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_k_outofbounds_pos_test.onnx000066400000000000000000000002211510465702400257100ustar00rootroot00000000000000eyelike_k_outofbounds_pos_test:i  T1T2"EyeLike* k eyelike_k_outofbounds_pos_testZ T1   b T2   B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_k_test.onnx000066400000000000000000000001611510465702400224030ustar00rootroot00000000000000eyelike_k_test:Y  T1T2"EyeLike* k eyelike_k_testZ T1   b T2   B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_not_rank2_test.onnx000066400000000000000000000001731510465702400240510ustar00rootroot00000000000000eyelike_not_rank2_test:[  T1T2"EyeLikeeyelike_not_rank2_testZ T1    b T2   B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_set_dtype_test.onnx000066400000000000000000000002051510465702400241500ustar00rootroot00000000000000eyelike_set_dtype_test:e  T1T2"EyeLike* dtype  eyelike_set_dtype_testZ T1   b T2    B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_verify_negk_test.onnx000066400000000000000000000002161510465702400244620ustar00rootroot00000000000000eyelike_verify_negk_test:l $ T1T2"EyeLike* kþÿÿÿÿÿÿÿÿ eyelike_verify_negk_testZ T1   b T2   B ROCm-AMDMIGraphX-46524e8/test/onnx/eyelike_verify_test.onnx000066400000000000000000000001731510465702400234600ustar00rootroot00000000000000eyelike_verify_test:^  T1T2"EyeLike* k eyelike_verify_testZ T1   b T2   B ROCm-AMDMIGraphX-46524e8/test/onnx/flatten_dyn_test.onnx000066400000000000000000000001701510465702400227510ustar00rootroot00000000000000flatten_dyn_test:^  02"Flatten* axis flatten_dyn_testZ 0    b 2  B ROCm-AMDMIGraphX-46524e8/test/onnx/flatten_nonstd_test.onnx000066400000000000000000000003221510465702400234630ustar00rootroot00000000000000flatten_nonstd_test:´ % 0tx" Transpose* perm@@@@   tx2"Flatten* axis   tx3"Flattenflatten_nonstd_testZ 0     b 2   b 3    A BY"Gemm* alpha@ * transA * transB gemm_dyn_outer_testZ A   Z B   b Y   B ROCm-AMDMIGraphX-46524e8/test/onnx/gemm_fp8_test.onnx000066400000000000000000000002771510465702400221540ustar00rootroot00000000000000  gemm_fp8_test:§ B A B CY"Gemm* alpha? * betaÍÌL? * transA  gemm_fp8_testZ A   Z B   Z C   b Y   BROCm-AMDMIGraphX-46524e8/test/onnx/gemm_half_test.onnx000066400000000000000000000003011510465702400223550ustar00rootroot00000000000000gemm_half_test:¨ B A B CY"Gemm* alpha? * betaÍÌL? * transA gemm_half_testZ A    Z B    Z C    b Y    B ROCm-AMDMIGraphX-46524e8/test/onnx/gemm_no_C_test.onnx000066400000000000000000000003101510465702400223210ustar00rootroot00000000000000gemm_no_C_test:¯ Q A B CY"Gemm* alpha@ * beta@ * transA * transB gemm_no_C_testZ A   Z B   Z C b Y    B ROCm-AMDMIGraphX-46524e8/test/onnx/gemm_rank_error.onnx000066400000000000000000000003331510465702400225550ustar00rootroot00000000000000gemm_rank_error:Á B A B CY"Gemm* alpha? * betaÍÌL? * transA gemm_rank_errorZ A     Z B     Z C   b Y     B ROCm-AMDMIGraphX-46524e8/test/onnx/gemm_test.onnx000066400000000000000000000002671510465702400213760ustar00rootroot00000000000000 gemm_test:£ B A B CY"Gemm* alpha? * betaÍÌL? * transA  gemm_testZ A   Z B   Z C   b Y   B ROCm-AMDMIGraphX-46524e8/test/onnx/gen_onnx.py000066400000000000000000023024031510465702400206720ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### # This script generates onnx files for MIGraphX onnx operator tests. # To generate an individual onnx file, you can use the following # command: python3 -c "import gen_onnx; gen_onnx.{test_name}_test()" import numpy as np import onnx from onnx import helper from onnx import TensorProto from onnx.numpy_helper import from_array def onnx_test(external_data=False): def create_onnx_test(op_test): def run_test(): op_info = op_test() if len(op_info) > 3: graph_def = helper.make_graph(op_info[0], op_test.__name__, op_info[1], op_info[2], initializer=op_info[3]) else: graph_def = helper.make_graph(op_info[0], op_test.__name__, op_info[1], op_info[2]) model_def = helper.make_model(graph_def, producer_name=op_test.__name__) onnx.save_model(model_def, '{}.onnx'.format(op_test.__name__), save_as_external_data=external_data, location='{}.weight'.format(op_test.__name__), size_threshold=0, convert_attribute=True) return run_test return create_onnx_test @onnx_test() def acos_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Acos', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def acosh_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Acosh', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def add_bcast_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 4]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node('Add', inputs=['0', '1'], broadcast=1, axis=1, outputs=['2']) return ([node], [x, y], [z]) @onnx_test() def add_fp16_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [1]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT16, [1]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [1]) node = onnx.helper.make_node( 'Add', inputs=['0', '1'], outputs=['2'], ) return ( [node], [x, y], [z], # '0' -> 1.5, '1' -> 2.5 [ onnx.helper.make_tensor('0', TensorProto.FLOAT16, [1], [15872]), onnx.helper.make_tensor('1', TensorProto.FLOAT16, [1], [16640]) ]) @onnx_test() def add_fp8_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT8E4M3FNUZ, [1]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT8E4M3FNUZ, [1]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT8E4M3FNUZ, [1]) node = onnx.helper.make_node( 'Add', inputs=['0', '1'], outputs=['2'], ) return ([node], [x, y], [z]) @onnx_test() def add_bf16_test(): x = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [1]) y = helper.make_tensor_value_info('1', TensorProto.BFLOAT16, [1]) z = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [1]) node = onnx.helper.make_node( 'Add', inputs=['0', '1'], outputs=['2'], ) return ([node], [x, y], [z]) @onnx_test() def add_scalar_test(): x = helper.make_tensor_value_info('0', TensorProto.UINT8, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.UINT8, []) z = helper.make_tensor_value_info('2', TensorProto.UINT8, [2, 3, 4, 5]) node = onnx.helper.make_node('Add', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [z]) @onnx_test() def argmax_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 6]) node = onnx.helper.make_node('ArgMax', inputs=['x'], outputs=['y'], axis=2, keepdims=0) return ([node], [x], [y]) @onnx_test() def argmax_select_last_index_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 6]) node = onnx.helper.make_node('ArgMax', inputs=['x'], outputs=['y'], axis=2, keepdims=0, select_last_index=1) return ([node], [x], [y]) @onnx_test() def argmax_dyn_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, 4, 6]) node = onnx.helper.make_node('ArgMax', inputs=['x'], outputs=['y'], axis=2, keepdims=0) return ([node], [x], [y]) @onnx_test() def argmin_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 5]) node = onnx.helper.make_node('ArgMin', inputs=['x'], outputs=['y'], axis=3, keepdims=0) return ([node], [x], [y]) @onnx_test() def argmin_select_last_index_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 5]) node = onnx.helper.make_node('ArgMin', inputs=['x'], outputs=['y'], axis=3, keepdims=0, select_last_index=1) return ([node], [x], [y]) @onnx_test() def asin_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Asin', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def asinh_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Asinh', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def atan_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Atan', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def atanh_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Atanh', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) def attention_test( x_dims, weight_dims=[], bias_dims=[], mask_dims=[], past_dims=[], attention_bias_dims=[], past_sequence_length_dims=[], num_heads=None, qkv_hidden_sizes=None, do_rotary=None, mask_filter_value=None, past_present_share_buffer=None, scale=None, unidirectional=None, rotary_embedding_dim=None, present_dims=[], dtype=TensorProto.FLOAT, test_type=TensorProto.INT32): # (Batch_size, sequence_lenth, input_hidden_size) x = helper.make_tensor_value_info('input', dtype, x_dims) input_list = [x] input_name_list = ['input'] # Needed for output vector dims batch = x_dims[0] seq_len = x_dims[1] v_hidden_size = int(0) if len(weight_dims) > 0: # (input_hidden_size, hidden_size + hidden_size, v_hidden_size) weights = helper.make_tensor_value_info('weights', dtype, weight_dims) input_name_list.append('weights') input_list.append(weights) # default is to assume that q, k ,v sizes are the same unless otherwise stated v_hidden_size = int(weight_dims[1] / 3) # (Batch_size, sequence_lenth, v_hidden_size) y = helper.make_tensor_value_info('y', dtype, [batch, seq_len, v_hidden_size]) output_list = [y] output_name_list = ['y'] # Additional arguments/options to adjust attention block if len(bias_dims) > 0: # Bias shape should be (hidden_size + hiddeN_size + v_hidden_size) bias = helper.make_tensor_value_info('bias', dtype, bias_dims) input_name_list.append('bias') input_list.append(bias) if len(mask_dims) > 0: # allowable shapes # (batch_size, 1, max_sequence_length, max_sequence_length) # (batch_size, total_sequence_length) # (batch_size, sequence_length, total_sequence_length) # (batch_size) or (2*batch_size) or (3* batch_size + 2) mask_index = helper.make_tensor_value_info('mask_index', test_type, mask_dims) input_name_list.append('mask_index') input_list.append(mask_index) if len(past_dims) > 0: # (2, batch_size, num_heads, past_sequence_length, head_size) # (2, batch_size, num_heads, max_seq_length, head_size) when past/present share buffer past = helper.make_tensor_value_info('past', dtype, past_dims) input_name_list.append('past') input_list.append(past) if len(attention_bias_dims) > 0: # (batch_size, or 1, num_heads or 1, sequence_length, total_sequence_length) attention_bias = helper.make_tensor_value_info('attention_bias', dtype, attention_bias_dims) input_name_list.append('attention_bias') input_list.append(attention_bias) if len(past_sequence_length_dims) > 0: # (batch_size, or 1, num_heads or 1, sequence_length, total_sequence_length) past_sequence_length = helper.make_tensor_value_info('past_seqence_length', test_type, past_sequence_length_dims) input_name_list.append('past_sequence_length') input_list.append(past_sequence_length) # Additional output vector if present_dims: output_name_list.append('present') output_list.append('present') node = onnx.helper.make_node( 'Attention', inputs=input_name_list, outputs=output_name_list, domain="com.microsoft") # Append attributes based on input to this function. Parser should assume defaults # This is the only attribute that's required others are not if num_heads is not None: node.attribute.append(onnx.helper.make_attribute("num_heads", num_heads)) if scale is not None: node.attribute.append(onnx.helper.make_attribute("scale", scale)) if qkv_hidden_sizes is not None: node.attribute.append(onnx.helper.make_attribute("qkv_hidden_sizes", qkv_hidden_sizes)) if unidirectional is not None: node.attribute.append(onnx.helper.make_attribute("unidirectional", unidirectional)) if mask_filter_value is not None: node.attribute.append(onnx.helper.make_attribute("mask_filter_value", mask_filter_value)) if do_rotary is not None: node.attribute.append(onnx.helper.make_attribute("do_rotary", do_rotary)) if rotary_embedding_dim is not None: node.attribute.append(onnx.helper.make_attribute("rotary_embedding_dim", rotary_embedding_dim)) if past_present_share_buffer is not None: node.attribute.append(onnx.helper.make_attribute("past_present_share_buffer", past_present_share_buffer)) return ([node], input_list, output_list) @onnx_test() def attention_invalid_input_num(): return attention_test([2, 2, 4], num_heads=1) @onnx_test() def attention_invalid_input_dimension(): return attention_test([2, 2, 4, 2], [4, 12], bias_dims=[12], num_heads=1) @onnx_test() def attention_invalid_no_num_heads(): return attention_test([2, 2, 4], [4, 12], bias_dims=[12]) @onnx_test() def attention_invalid_weight_hidden_size(): return attention_test([2, 2, 5], [4, 12], bias_dims=[12], num_heads=2) @onnx_test() def attention_invalid_uneven_weight_no_qkv_hidden(): return attention_test([2, 2, 5], [4, 14], bias_dims=[12], num_heads=2) @onnx_test() def attention_invalid_bias_dims_size(): return attention_test([2, 2, 4], [4, 12], bias_dims=[12, 2, 2], num_heads=2) @onnx_test() def attention_invalid_bias_value_size(): return attention_test([2, 2, 4], [4, 12], bias_dims=[11], num_heads=2) @onnx_test() def attention_invalid_mask_type_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], mask_dims=[1, 2], num_heads=2, test_type=TensorProto.FLOAT) @onnx_test() def attention_invalid_mask_2d_dims_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], mask_dims=[1, 9], num_heads=2) @onnx_test() def attention_invalid_mask_3d_dims_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], mask_dims=[2, 2, 4], num_heads=2) @onnx_test() def attention_invalid_mask_4d_dims_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], mask_dims=[1, 9, 2, 2], num_heads=2) @onnx_test() def attention_invalid_mask_4d_last_dims_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], mask_dims=[1, 9, 2, 3], num_heads=2) @onnx_test() def attention_invalid_mask_5d_dims_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], mask_dims=[1, 9, 2, 2, 2], num_heads=2) @onnx_test() def attention_invalid_qkv_attr_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], num_heads=2, qkv_hidden_sizes=[1, 3]) @onnx_test() def attention_invalid_qkv_attr_test2(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], num_heads=2, qkv_hidden_sizes=[1, 2, 3]) @onnx_test() def attention_invalid_qkv_attr_test3(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], num_heads=2, qkv_hidden_sizes=[2, 2, -3]) @onnx_test() def attention_single_head_batch1_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], num_heads=1) @onnx_test() def attention_single_head_batch2_test(): return attention_test([2, 2, 4], [4, 12], bias_dims=[12], num_heads=1) @onnx_test() def attention_double_head_batch1_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], num_heads=2) @onnx_test() def attention_double_head_test(): return attention_test([2, 2, 4], [4, 12], num_heads=2) @onnx_test() def attention_double_head_bias_test(): return attention_test([2, 2, 4], [4, 12], bias_dims=[12], num_heads=2) @onnx_test() def attention_double_head_bias_mask_batch1_test(): return attention_test([1, 2, 4], [4, 12], bias_dims=[12], mask_dims=[1, 2], num_heads=2) @onnx_test() def attention_double_head_bias_mask_test(): return attention_test([2, 2, 4], [4, 12], bias_dims=[12], mask_dims=[2, 2], num_heads=2) @onnx_test() def attention_double_head_bias_asym_mask_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 3], num_heads=2) @onnx_test() def attention_double_head_bias_3d_mask_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 3, 3], num_heads=2) @onnx_test() def attention_double_head_bias_4d_mask_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 1, 3, 3], num_heads=2) @onnx_test() def attention_double_head_bias_asym_left_pad_mask_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2], num_heads=2) @onnx_test() def attention_double_head_bias_asym_right_pad_mask_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[4], num_heads=2) @onnx_test() def attention_double_head_bias_asym_mask_unidirectional_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 3], num_heads=2, unidirectional=1) @onnx_test() def attention_double_head_bias_asym_mask_rotary_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 3], num_heads=2, do_rotary=1) @onnx_test() def attention_double_head_bias_asym_mask_rotary_embedding_dim_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 3], num_heads=2, rotary_embedding_dim=32) @onnx_test() def attention_double_head_bias_asym_mask_bad_rotary_embedding_dim_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 3], num_heads=2, rotary_embedding_dim=48) @onnx_test() def attention_double_head_bias_asym_mask_scale_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 3], num_heads=2, scale=float(0.1234)) @onnx_test() def attention_double_head_bias_asym_mask_filter_val_test(): return attention_test([2, 3, 4], [4, 12], bias_dims=[12], mask_dims=[2, 3], num_heads=2, mask_filter_value=float(-5000.0)) @onnx_test() def attention_double_head_bias_mask_past_test(): # Should error out because we only support shared buffer modes return attention_test([2, 2, 4], [4, 12], bias_dims=[12], mask_dims=[2, 2], past_dims=[2, 4], num_heads=2) @onnx_test() def attention_double_head_bias_mask_past_attn_bias_shared_test(): return attention_test([2, 4, 4], [4, 12], bias_dims=[12], mask_dims=[2, 4], past_dims=[2, 4], attention_bias_dims=[2, 2, 4], past_present_share_buffer=1, num_heads=2) @onnx_test() def attention_double_head_bias_mask_past_attn_bias_shared_past_seq_len_test(): return attention_test([2, 4, 4], [4, 12], bias_dims=[12], mask_dims=[2, 4], past_dims=[2, 4], attention_bias_dims=[2, 2, 4], past_sequence_length_dims=[1], num_heads=2) @onnx_test() def attention_multihead_test(): return attention_test([32, 512, 1024], [1024, 3072], num_heads=16) @onnx_test() def attention_multihead_bias_mask_test(): return attention_test([32, 512, 1024], [1024, 3072], bias_dims=[3072], mask_dims=[32, 512], num_heads=16) @onnx_test() def averagepool_1d_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 5]) out = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3]) node = onnx.helper.make_node('AveragePool', inputs=['0'], outputs=['1'], kernel_shape=[3]) return ([node], [x], [out]) @onnx_test() def averagepool_dilate_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 4, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 4, 2]) node = onnx.helper.make_node('AveragePool', inputs=['x'], outputs=['y'], kernel_shape=[2], strides=[1], pads=[1, 1], dilations=[3]) return ([node], [x], [y]) @onnx_test() def averagepool_3d_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 5, 5, 5]) out = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3, 3, 3]) node = onnx.helper.make_node('AveragePool', inputs=['0'], outputs=['1'], kernel_shape=[3, 3, 3]) return ([node], [x], [out]) @onnx_test() def averagepool_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 5, 5, 5]) out = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 3, 3, 3]) node = onnx.helper.make_node('AveragePool', inputs=['0'], outputs=['1'], kernel_shape=[3, 3, 3], strides=[2, 2, 2], pads=[1, 1, 1, 1, 1, 1]) return ([node], [x], [out]) @onnx_test() def averagepool_dyn_autopad_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 5, 5, 5]) out = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 3, 3, 3]) node = onnx.helper.make_node('AveragePool', inputs=['0'], outputs=['1'], kernel_shape=[3, 3, 3], strides=[2, 2, 2], auto_pad='SAME_UPPER') return ([node], [x], [out]) @onnx_test() def averagepool_dyn_asym_padding_error_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, 1, 3, 3]) node = onnx.helper.make_node('AveragePool', inputs=['x'], outputs=['y'], kernel_shape=[2, 2], strides=[2, 2], pads=[0, 0, 1, 1]) return ([node], [x], [y]) @onnx_test() def averagepool_dyn_cip_error_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, 1, 1, 1]) node = onnx.helper.make_node('AveragePool', inputs=['x'], outputs=['y'], kernel_shape=[2, 2], count_include_pad=1) return ([node], [x], [y]) @onnx_test() def averagepool_notset_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 1, 1]) node = onnx.helper.make_node('AveragePool', inputs=['x'], outputs=['y'], kernel_shape=[6, 6], strides=[2, 2], pads=[0, 0, 1, 1], auto_pad='NOTSET') return ([node], [x], [y]) @onnx_test() def averagepool_nt_cip_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 1, 1]) node = onnx.helper.make_node('AveragePool', inputs=['x'], outputs=['y'], kernel_shape=[6, 6], strides=[2, 2], pads=[0, 0, 1, 1], auto_pad='NOTSET', count_include_pad=1) return ([node], [x], [y]) @onnx_test() def averagepool_same_lower_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 5, 5]) node = onnx.helper.make_node('AveragePool', inputs=['x'], outputs=['y'], kernel_shape=[2, 2], auto_pad='SAME_LOWER') return ([node], [x], [y]) @onnx_test() def averagepool_sl_cip_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 5, 5]) node = onnx.helper.make_node('AveragePool', inputs=['x'], outputs=['y'], kernel_shape=[2, 2], auto_pad='SAME_LOWER', count_include_pad=1) return ([node], [x], [y]) @onnx_test() def averagepool_same_upper_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 5, 5]) node = onnx.helper.make_node('AveragePool', inputs=['x'], outputs=['y'], kernel_shape=[2, 2], auto_pad='SAME_UPPER') return ([node], [x], [y]) @onnx_test() def batch_norm_flat_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [1]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [1]) var = helper.make_tensor_value_info('variance', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'BatchNormalization', inputs=['x', 'scale', 'bias', 'mean', 'variance'], outputs=['y'], epsilon=1e-6) return ([node], [x, scale, bias, mean, var], [out]) @onnx_test() def batch_norm_rank_2_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 5]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [5]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [5]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [5]) var = helper.make_tensor_value_info('variance', TensorProto.FLOAT, [5]) out = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 5]) node = onnx.helper.make_node( 'BatchNormalization', inputs=['x', 'scale', 'bias', 'mean', 'variance'], outputs=['y'], epsilon=1e-6) return ([node], [x, scale, bias, mean, var], [out]) @onnx_test() def batch_norm_1d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 3, 4]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [3]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [3]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [3]) var = helper.make_tensor_value_info('variance', TensorProto.FLOAT, [3]) out = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 3, 4]) node = onnx.helper.make_node( 'BatchNormalization', inputs=['x', 'scale', 'bias', 'mean', 'variance'], outputs=['y']) return ([node], [x, scale, bias, mean, var], [out]) @onnx_test() def batch_norm_2d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3, 4, 4]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [3]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [3]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [3]) var = helper.make_tensor_value_info('variance', TensorProto.FLOAT, [3]) out = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4, 4]) node = onnx.helper.make_node( 'BatchNormalization', inputs=['x', 'scale', 'bias', 'mean', 'variance'], outputs=['y']) return ([node], [x, scale, bias, mean, var], [out]) @onnx_test() def batch_norm_3d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 2, 2, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT16, [2]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT16, [2]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT16, [2]) var = helper.make_tensor_value_info('variance', TensorProto.FLOAT16, [2]) out = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 2, 2, 2]) node = onnx.helper.make_node( 'BatchNormalization', inputs=['x', 'scale', 'bias', 'mean', 'variance'], outputs=['y'], epsilon=1e-6) return ([node], [x, scale, bias, mean, var], [out]) @onnx_test() def batch_norm_invalid_bias_rank_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3, 4, 4]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [3]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [3, 1]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [3]) var = helper.make_tensor_value_info('variance', TensorProto.FLOAT, [3]) out = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4, 4]) node = onnx.helper.make_node( 'BatchNormalization', inputs=['x', 'scale', 'bias', 'mean', 'variance'], outputs=['y']) return ([node], [x, scale, bias, mean, var], [out]) @onnx_test() def biasadd_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3, 4]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT, [2, 3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node( 'BiasAdd', inputs=['x', 'bias', 'skip'], outputs=['y'], domain='com.microsoft') return ([node], [x, bias, skip], [y]) @onnx_test() def binary_dyn_brcst_prelu_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [None, 3, 4, 5]) node = onnx.helper.make_node( 'PRelu', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def binary_dyn_brcst_add_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [None, 3, 4, 5]) node = onnx.helper.make_node( 'Add', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def binary_dyn_brcst_attr_error_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [None, 3, 4, 5]) node = onnx.helper.make_node('Add', inputs=['0', '1'], outputs=['out'], broadcast=1, axis=1) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def binary_dyn_brcst_mul_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 1]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [None, 3, 4, 5]) node = onnx.helper.make_node( 'Mul', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def binary_dyn_brcst_mul_fp8_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT8E4M3FNUZ, [None, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT8E4M3FNUZ, [4, 1]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT8E4M3FNUZ, [None, 3, 4, 5]) node = onnx.helper.make_node( 'Mul', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def bitwise_and_bcast_test(): x = helper.make_tensor_value_info('0', TensorProto.INT32, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 5]) z = helper.make_tensor_value_info('2', TensorProto.INT32, [2, 3, 4, 5]) node = onnx.helper.make_node('BitwiseAnd', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [z]) @onnx_test() def div_fp8_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT8E4M3FNUZ, [2, 3]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT8E4M3FNUZ, [2, 3]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT8E4M3FNUZ, [2, 3]) node = onnx.helper.make_node( 'Div', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def cast_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node('Cast', inputs=['x'], outputs=['y'], to=1) return ([node], [x], [y]) @onnx_test() def castlike_test(): input = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [10]) target_type = helper.make_tensor_value_info('1', TensorProto.FLOAT, [10]) output = helper.make_tensor_value_info('out', TensorProto.FLOAT, [10]) node = onnx.helper.make_node('CastLike', inputs=['0', '1'], outputs=['out']) return ([node], [input, target_type], [output]) @onnx_test() def castlike_error_test(): input = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [10]) output = helper.make_tensor_value_info('out', TensorProto.FLOAT, [10]) node = onnx.helper.make_node('CastLike', inputs=['0'], outputs=['out']) return ([node], [input], [output]) @onnx_test() def ceil_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Ceil', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def celu_alpha_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Celu', inputs=['x'], outputs=['y'], alpha=0.8) return ([node], [x], [y]) @onnx_test() def celu_default_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node('Celu', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def celu_verify_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node('Celu', inputs=['x'], outputs=['y'], alpha=0.5) return ([node], [x], [y]) @onnx_test() def celu_wrong_type_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 3]) node = onnx.helper.make_node('Celu', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def celu_zero_alpha_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node('Celu', inputs=['x'], outputs=['y'], alpha=0.0) return ([node], [x], [y]) @onnx_test() def clip_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Clip', inputs=['0'], outputs=['1'], max=6.0, min=0.0) return ([node], [x], [y]) @onnx_test() def clip_test_op11(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) min_val = helper.make_tensor('min', TensorProto.FLOAT, [], [0.0]) max_val = helper.make_tensor('max', TensorProto.FLOAT, [], [6.0]) node = onnx.helper.make_node('Clip', inputs=['0', 'min', 'max'], outputs=['1']) return ([node], [x], [y], [min_val, max_val]) @onnx_test() def clip_test_op11_max_only(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) max_val = helper.make_tensor('max', TensorProto.FLOAT, [], [0.0]) node = onnx.helper.make_node('Clip', inputs=['0', '', 'max'], outputs=['1']) return ([node], [x], [y], [max_val]) @onnx_test() def clip_test_op11_min_only(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) min_val = helper.make_tensor('min', TensorProto.FLOAT, [], [0.0]) node = onnx.helper.make_node('Clip', inputs=['0', 'min'], outputs=['1']) return ([node], [x], [y], [min_val]) @onnx_test() def clip_test_op11_no_args(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Clip', inputs=['0'], outputs=['1']) return ([node], [x], [y]) @onnx_test() def clip_test_op11_no_args1(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Clip', inputs=['0', '', ''], outputs=['1']) return ([node], [x], [y]) @onnx_test() def clip_test_args_type_mismatch(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 3]) min_val = helper.make_tensor('min', TensorProto.FLOAT, [1, 3], [1.5, 2.5, 3.5]) max_val = helper.make_tensor('max', TensorProto.INT64, [3, 1], [2, 3, 4]) node = onnx.helper.make_node('Clip', inputs=['0', 'min', 'max'], outputs=['1']) return ([node], [x], [y], [min_val, max_val]) @onnx_test() def clip_dyn_min_max_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None]) min_val = helper.make_tensor('min', TensorProto.FLOAT, [], [0.0]) max_val = helper.make_tensor('max', TensorProto.FLOAT, [], [6.0]) node = onnx.helper.make_node('Clip', inputs=['0', 'min', 'max'], outputs=['1']) return ([node], [x], [y], [min_val, max_val]) @onnx_test() def clip_dyn_min_only_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None]) min_val = helper.make_tensor('min', TensorProto.FLOAT, [], [0.0]) node = onnx.helper.make_node('Clip', inputs=['0', 'min'], outputs=['1']) return ([node], [x], [y], [min_val]) @onnx_test() def concat_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 4, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [7, 4, 3]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [9, 4, 3]) node = onnx.helper.make_node( 'Concat', inputs=['0', '1'], axis=0, outputs=['2'], ) return ([node], [x, y], [z]) @onnx_test() def concat_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, None, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, None, 3]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [None, None, 3]) node = onnx.helper.make_node( 'Concat', inputs=['0', '1'], axis=0, outputs=['2'], ) return ([node], [x, y], [z]) @onnx_test() def constant_test(): x = np.array([0, 1, 2]) y = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['0'], value=onnx.helper.make_tensor( name='const_tensor', data_type=TensorProto.FLOAT, dims=x.shape, vals=x.flatten().astype(float), ), ) return ([node], [], [y]) @onnx_test() def constant_value_float_test(): node = onnx.helper.make_node('Constant', inputs=[], outputs=[], value_float=[1.0]) return ([node], [], []) @onnx_test() def constant_value_floats_test(): node = onnx.helper.make_node('Constant', inputs=[], outputs=[], value_floats=[1.0, 2.0, 3.0]) return ([node], [], []) @onnx_test() def constant_value_int_test(): node = onnx.helper.make_node('Constant', inputs=[], outputs=[], value_int=[1]) return ([node], [], []) @onnx_test() def constant_value_ints_test(): node = onnx.helper.make_node('Constant', inputs=[], outputs=[], value_ints=[1, 2, 3]) return ([node], [], []) @onnx_test() def constant_no_attributes_test(): node = onnx.helper.make_node('Constant', inputs=[], outputs=[]) return ([node], [], []) @onnx_test() def constant_multiple_attributes_test(): x = np.array([0, 1, 2]) node = onnx.helper.make_node('Constant', inputs=[], outputs=[], value_floats=[1.0, 2.0], value_ints=[1, 2], value=onnx.helper.make_tensor( name='const_tensor', data_type=TensorProto.FLOAT, dims=x.shape, vals=x.flatten().astype(float))) return ([node], [], []) @onnx_test() def constant_fill_test(): value = helper.make_tensor_value_info('value', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node( 'ConstantFill', inputs=[], outputs=['value'], dtype=1, value=1.0, shape=[2, 3], input_as_shape=0, ) return ([node], [], [value]) @onnx_test() def constant_fill_input_as_shape_test(): np_shape = np.array([2, 3]) value = helper.make_tensor_value_info('value', TensorProto.FLOAT, [2, 3]) ts_shape = helper.make_tensor(name='shape_tensor', data_type=TensorProto.INT32, dims=np_shape.shape, vals=np_shape.flatten().astype(int)) const_shape_node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['shape'], value=ts_shape, ) node = onnx.helper.make_node( 'ConstantFill', inputs=['shape'], outputs=['value'], dtype=1, value=1.0, input_as_shape=1, ) return ([const_shape_node, node], [], [value]) @onnx_test() def constant_scalar_test(): x = np.array([1]) y = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1]) node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['0'], value=onnx.helper.make_tensor( name='const_tensor', data_type=TensorProto.INT32, dims=x.shape, vals=x.flatten().astype(int), ), ) return ([node], [], [y]) @onnx_test() def constant_empty_scalar_int64_test(): x = np.array([]).astype(np.int64) y = helper.make_tensor_value_info('0', TensorProto.INT64, [0]) node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['0'], value=onnx.helper.make_tensor( name='one_element_tensor', data_type=TensorProto.INT64, dims=x.shape, vals=x.flatten().astype(np.int64), ), ) return ([node], [], [y]) @onnx_test() def constant_one_val_int64_test(): x = np.array([1]).astype(np.int64) y = helper.make_tensor_value_info('0', TensorProto.INT64, [0]) node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['0'], value=onnx.helper.make_tensor( name='empty_tensor', data_type=TensorProto.INT64, dims=x.shape, vals=x.flatten().astype(np.int64), ), ) return ([node], [], [y]) @onnx_test() def const_of_shape_empty_input_test(): tensor_val = onnx.helper.make_tensor('value', onnx.TensorProto.INT64, [1], [10]) empty_val = np.array([]).astype(np.int64) empty_ts = helper.make_tensor(name='empty_tensor', data_type=TensorProto.INT64, dims=empty_val.shape, vals=empty_val.flatten().astype(np.int64)) shape_const = helper.make_node( 'Constant', inputs=[], outputs=['shape'], value=empty_ts, ) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node( 'ConstantOfShape', inputs=['shape'], outputs=['y'], value=tensor_val, ) return ([shape_const, node], [], [y]) @onnx_test() def const_of_shape_float_test(): tensor_val = onnx.helper.make_tensor('value', onnx.TensorProto.FLOAT, [1], [10]) shape_val = np.array([2, 3, 4]).astype(np.int64) shape_ts = helper.make_tensor(name='shape_tensor', data_type=TensorProto.INT64, dims=shape_val.shape, vals=shape_val.flatten().astype(np.int64)) shape_const = helper.make_node( 'Constant', inputs=[], outputs=['shape'], value=shape_ts, ) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node('ConstantOfShape', inputs=['shape'], outputs=['y'], value=tensor_val) return ([shape_const, node], [], [y]) @onnx_test() def const_of_shape_default_test(): shape_val = np.array([2, 3, 4]).astype(np.int64) shape_ts = helper.make_tensor(name='shape_tensor', data_type=TensorProto.INT64, dims=shape_val.shape, vals=shape_val.flatten().astype(np.int64)) shape_const = helper.make_node( 'Constant', inputs=[], outputs=['shape'], value=shape_ts, ) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2, 3, 4]) node = onnx.helper.make_node('ConstantOfShape', inputs=['shape'], outputs=['y']) return ([shape_const, node], [], [y]) @onnx_test() def const_of_shape_int64_test(): tensor_val = onnx.helper.make_tensor('value', onnx.TensorProto.INT64, [1], [10]) shape_val = np.array([2, 3, 4]).astype(np.int64) shape_ts = helper.make_tensor(name='shape_tensor', data_type=TensorProto.INT64, dims=shape_val.shape, vals=shape_val.flatten().astype(np.int64)) shape_const = helper.make_node( 'Constant', inputs=[], outputs=['shape'], value=shape_ts, ) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2, 3, 4]) node = onnx.helper.make_node('ConstantOfShape', inputs=['shape'], outputs=['y'], value=tensor_val) return ([shape_const, node], [], [y]) @onnx_test() def const_of_shape_no_value_attr_test(): shape_val = np.array([2, 3, 4]).astype(np.int64) shape_ts = helper.make_tensor(name='shape_tensor', data_type=TensorProto.INT64, dims=shape_val.shape, vals=shape_val.flatten().astype(np.int64)) shape_const = helper.make_node( 'Constant', inputs=[], outputs=['shape'], value=shape_ts, ) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node( 'ConstantOfShape', inputs=['shape'], outputs=['y'], ) return ([shape_const, node], [], [y]) @onnx_test() def const_of_shape_dyn_float_test(): tensor_val = onnx.helper.make_tensor('value', onnx.TensorProto.FLOAT, [1], [10]) output_dims = helper.make_tensor_value_info('output_dims', TensorProto.INT64, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node('ConstantOfShape', inputs=['output_dims'], outputs=['y'], value=tensor_val) return ([node], [output_dims], [y]) @onnx_test() def const_of_shape_dyn_int64_test(): tensor_val = onnx.helper.make_tensor('value', onnx.TensorProto.INT64, [1], [10]) output_dims = helper.make_tensor_value_info('output_dims', TensorProto.INT64, [3]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2, 3, 4]) node = onnx.helper.make_node('ConstantOfShape', inputs=['output_dims'], outputs=['y'], value=tensor_val) return ([node], [output_dims], [y]) @onnx_test() def conv_1d_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, 3]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [out]) @onnx_test() def conv_3d_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 5, 5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, 3, 3, 3]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [out]) @onnx_test() def conv_1d_fp8_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT8E4M3FNUZ, [1, 3, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT8E4M3FNUZ, [1, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT8E4M3FNUZ, [1, 1, 3]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [out]) @onnx_test() def conv_attr_fail_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, 3]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], strides=[1, 1], outputs=['2']) return ([node], [x, y], [out]) @onnx_test() def conv_autopad_fail_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 1, 1]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, 34, 34]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2'], dilations=[1, 1], strides=[1, 1], auto_pad='SAME', pads=[0, 0, 1, 1, 0, 0, 1, 1]) return ([node], [x, y], [out]) @onnx_test() def conv_autopad_same_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, 32, 32]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2'], dilations=[1, 1], strides=[1, 1], auto_pad='SAME') return ([node], [x, y], [out]) @onnx_test() def conv_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1, 2, 28, 28]) node = onnx.helper.make_node('Conv', inputs=['0', '1', '2'], outputs=['3'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z], [out]) @onnx_test() def conv_bad_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.INT32, [1]) out = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1, 2, 28, 28]) node = onnx.helper.make_node('Conv', inputs=['0', '1', '2'], outputs=['3'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z], [out]) @onnx_test() def conv_bn_relu_maxpool_unordered_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1]) m = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1]) n = helper.make_tensor_value_info('4', TensorProto.FLOAT, [1]) k = helper.make_tensor_value_info('5', TensorProto.FLOAT, [1]) l = helper.make_tensor_value_info('6', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('10', TensorProto.FLOAT, [1, 1, 14, 14]) node0 = onnx.helper.make_node('Conv', inputs=['0', '1', '2'], outputs=['7'], dilations=[1, 1], strides=[1, 1], pads=[0, 0, 0, 0]) node1 = onnx.helper.make_node('BatchNormalization', inputs=['7', '3', '4', '5', '6'], outputs=['8'], epsilon=9.99999974737875e-06, momentum=0.899999976158142) node2 = onnx.helper.make_node('Relu', inputs=['8'], outputs=['9']) node3 = onnx.helper.make_node('MaxPool', inputs=['9'], outputs=['10'], pads=[0, 0, 0, 0], strides=[2, 2], kernel_shape=[2, 2]) return ([node0, node3, node1, node2], [x, y, z, m, n, k, l], [out]) @onnx_test() def conv_bn_relu_maxpool_unordered_nonvalue_optional_ios_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1]) m = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1]) n = helper.make_tensor_value_info('4', TensorProto.FLOAT, [1]) k = helper.make_tensor_value_info('5', TensorProto.FLOAT, [1]) l = helper.make_tensor_value_info('6', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('10', TensorProto.FLOAT, [1, 1, 14, 14]) node0 = onnx.helper.make_node('Conv', inputs=['0', '1', ''], outputs=['7'], dilations=[1, 1], strides=[1, 1], pads=[0, 0, 0, 0]) node1 = onnx.helper.make_node('BatchNormalization', inputs=['7', '3', '4', '5', '6'], outputs=['8', '', ''], epsilon=9.99999974737875e-06, momentum=0.899999976158142) node2 = onnx.helper.make_node('Relu', inputs=['8'], outputs=['9']) node3 = onnx.helper.make_node('MaxPool', inputs=['9'], outputs=['10', ''], pads=[0, 0, 0, 0], strides=[2, 2], kernel_shape=[2, 2]) return ([node0, node3, node1, node2], [x, y, z, m, n, k, l], [out]) @onnx_test() def conv_bn_relu_maxpool_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1]) m = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1]) n = helper.make_tensor_value_info('4', TensorProto.FLOAT, [1]) k = helper.make_tensor_value_info('5', TensorProto.FLOAT, [1]) l = helper.make_tensor_value_info('6', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('10', TensorProto.FLOAT, [1, 1, 14, 14]) node0 = onnx.helper.make_node('Conv', inputs=['0', '1', '2'], outputs=['7'], dilations=[1, 1], strides=[1, 1], pads=[0, 0, 0, 0]) node1 = onnx.helper.make_node('BatchNormalization', inputs=['7', '3', '4', '5', '6'], outputs=['8'], epsilon=9.99999974737875e-06, momentum=0.899999976158142) node2 = onnx.helper.make_node('Relu', inputs=['8'], outputs=['9']) node3 = onnx.helper.make_node('MaxPool', inputs=['9'], outputs=['10'], pads=[0, 0, 0, 0], strides=[2, 2], kernel_shape=[2, 2]) return ([node0, node1, node2, node3], [x, y, z, m, n, k, l], [out]) @onnx_test() def conv_dynamic_batch_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [None, 1, 3, 3]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [out]) @onnx_test() def conv_dynamic_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('3', TensorProto.FLOAT, [None, 2, 28, 28]) node = onnx.helper.make_node('Conv', inputs=['0', '1', '2'], outputs=['3'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z], [out]) @onnx_test() def conv_dynamic_img_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, None, None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, None, None]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [out]) @onnx_test() def conv_dynamic_weights_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, None, None]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, None, None]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [out]) @onnx_test() def conv_dynamic_img_and_weights_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, None, None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, None, None]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, None, None]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [out]) @onnx_test() def conv_dynamic_batch_same_upper_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, 5, 5]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2'], auto_pad='SAME_UPPER') return ([node], [x, y], [out]) @onnx_test() def conv_dynamic_img_same_upper_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, None, None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 3, 3]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, None, None]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2'], auto_pad='SAME_UPPER') return ([node], [x, y], [out]) @onnx_test() def conv_dynamic_kernel_same_lower_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, None, None]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, 5, 5]) node = onnx.helper.make_node('Conv', inputs=['0', '1'], outputs=['2'], auto_pad='SAME_LOWER') return ([node], [x, y], [out]) @onnx_test() def conv_relu_maxpool_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('5', TensorProto.FLOAT, [1, 1, 14, 14]) node1 = onnx.helper.make_node('Conv', inputs=['0', '1', '2'], outputs=['3'], dilations=[1, 1], strides=[1, 1], pads=[0, 0, 0, 0]) node2 = onnx.helper.make_node('Relu', inputs=['3'], outputs=['4']) node3 = onnx.helper.make_node('MaxPool', inputs=['4'], outputs=['5'], pads=[0, 0, 0, 0], strides=[2, 2], kernel_shape=[2, 2]) return ([node1, node2, node3], [x, y, z], [out]) @onnx_test() def conv_relu_maxpool_x2_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [5, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [5]) m = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1, 5, 5, 5]) n = helper.make_tensor_value_info('4', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('10', TensorProto.FLOAT, [1, 1, 5, 5]) node1 = onnx.helper.make_node('Conv', inputs=['0', '1', '2'], outputs=['5'], dilations=[1, 1], strides=[1, 1], pads=[0, 0, 0, 0]) node2 = onnx.helper.make_node('Relu', inputs=['5'], outputs=['6']) node3 = onnx.helper.make_node('MaxPool', inputs=['6'], outputs=['7'], pads=[0, 0, 0, 0], strides=[2, 2], kernel_shape=[2, 2]) node4 = onnx.helper.make_node('Conv', inputs=['7', '3', '4'], outputs=['8'], dilations=[1, 1], strides=[1, 1], pads=[0, 0, 0, 0]) node5 = onnx.helper.make_node('Relu', inputs=['8'], outputs=['9']) node6 = onnx.helper.make_node('MaxPool', inputs=['9'], outputs=['10'], pads=[0, 0, 0, 0], strides=[2, 2], kernel_shape=[2, 2]) return ([node1, node2, node3, node4, node5, node6], [x, y, z, m, n], [out]) @onnx_test() def convinteger_no_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.INT8, [1, 3, 5, 5]) y = helper.make_tensor_value_info('1', TensorProto.INT8, [1, 3, 2, 2]) out = helper.make_tensor_value_info('3', TensorProto.INT32, [1, 1, 4, 4]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1'], outputs=['3'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y], [out]) @onnx_test() def convinteger_no_bias_uint8_test(): x = helper.make_tensor_value_info('0', TensorProto.UINT8, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.UINT8, [1, 3, 5, 5]) out = helper.make_tensor_value_info('3', TensorProto.INT32, [1, 2, 28, 28]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1'], outputs=['3'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y], [out]) @onnx_test() def convinteger_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.INT8, [2, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.INT8, [1]) out = helper.make_tensor_value_info('3', TensorProto.INT32, [2, 4, 28, 28]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1', '2'], outputs=['3'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z], [out]) @onnx_test() def convinteger_dual_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.INT8, [2, 3, 10, 10]) y = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3, 3, 3]) z = helper.make_tensor_value_info('2', TensorProto.INT8, [1]) w = helper.make_tensor_value_info('3', TensorProto.INT8, [1]) out = helper.make_tensor_value_info('4', TensorProto.INT32, [2, 4, 8, 8]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1', '2', '3'], outputs=['4'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z, w], [out]) @onnx_test() def convinteger_dual_bias_simple_test(): x = helper.make_tensor_value_info('0', TensorProto.INT8, [1, 3, 5, 5]) y = helper.make_tensor_value_info('1', TensorProto.INT8, [1, 3, 2, 2]) z = helper.make_tensor_value_info('2', TensorProto.INT8, [1]) w = helper.make_tensor_value_info('3', TensorProto.INT8, [1]) out = helper.make_tensor_value_info('4', TensorProto.INT32, [1, 1, 4, 4]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1', '2', '3'], outputs=['4'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z, w], [out]) @onnx_test() def convinteger_mismatched_input_types_test(): x = helper.make_tensor_value_info('0', TensorProto.INT8, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.UINT8, [1, 3, 5, 5]) out = helper.make_tensor_value_info('4', TensorProto.INT32, [1, 2, 28, 28]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1'], outputs=['4'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y], [out]) @onnx_test() def convinteger_mismatched_inputs_dual_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.UINT8, [1, 3, 5, 5]) y = helper.make_tensor_value_info('1', TensorProto.INT8, [1, 3, 2, 2]) z = helper.make_tensor_value_info('2', TensorProto.UINT8, [1]) w = helper.make_tensor_value_info('3', TensorProto.INT8, [1]) out = helper.make_tensor_value_info('4', TensorProto.INT32, [1, 1, 4, 4]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1', '2', '3'], outputs=['4'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z, w], [out]) @onnx_test() def convinteger_mismatched_data_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.INT8, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.INT8, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.UINT8, [1]) w = helper.make_tensor_value_info('3', TensorProto.INT8, [1]) out = helper.make_tensor_value_info('4', TensorProto.INT32, [1, 2, 28, 28]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1', '2', '3'], outputs=['4'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z, w], [out]) @onnx_test() def convinteger_mismatched_weight_bias_test(): x = helper.make_tensor_value_info('0', TensorProto.INT8, [1, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.INT8, [1, 3, 5, 5]) z = helper.make_tensor_value_info('2', TensorProto.INT8, [1]) w = helper.make_tensor_value_info('3', TensorProto.UINT8, [1]) out = helper.make_tensor_value_info('4', TensorProto.INT32, [1, 2, 28, 28]) node = onnx.helper.make_node('ConvInteger', inputs=['0', '1', '2', '3'], outputs=['4'], dilations=[1, 1], strides=[1, 1]) return ([node], [x, y, z, w], [out]) @onnx_test() def cos_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Cos', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def cos_fp8_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT8E4M3FNUZ, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT8E4M3FNUZ, [10]) node = onnx.helper.make_node( 'Cos', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def cosh_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node( 'Cosh', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def conv_transpose_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 1, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 5, 5]) node = onnx.helper.make_node('ConvTranspose', name='conv1', inputs=['x', 'w'], outputs=['y']) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_bias_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 1, 3, 3]) b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 5, 5]) node = onnx.helper.make_node('ConvTranspose', name='conv1', inputs=['x', 'w', 'b'], outputs=['y']) return ([node], [x, w, b], [y]) @onnx_test() def conv_transpose_input_pads_strides_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 7, 5]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2], pads=[1, 1, 1, 1]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_input_pads_asymm_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 8, 6]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2], pads=[0, 0, 1, 1]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_input_pads_asymm_1d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 6]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[2], pads=[0, 1], dilations=[1]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_output_padding_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 10, 8]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2], output_padding=[1, 1]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_output_padding_3d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 10, 8, 8]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2, 2], output_padding=[1, 1, 1]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_output_shape_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 10, 8]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2], output_shape=[10, 8]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_output_shape_3d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 10, 8, 8]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2, 2], output_shape=[10, 8, 8]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_stride_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 7, 3]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_auto_pad_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 1, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 3, 3]) node = onnx.helper.make_node('ConvTranspose', name='conv1', inputs=['x', 'w'], outputs=['y'], auto_pad='SAME_UPPER') return ([node], [x, w], [y]) @onnx_test() def conv_transpose_dyn_asym_padding_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 8, 6]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2], pads=[0, 0, 1, 1]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_dyn_output_shape_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 2, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, 2, 10, 8]) node = onnx.helper.make_node('ConvTranspose', inputs=['x', 'w'], outputs=['y'], strides=[3, 2], output_shape=[10, 8]) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_dyn_batch_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 1, 3, 3]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 1, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, 1, 5, 5]) node = onnx.helper.make_node('ConvTranspose', name='conv1', inputs=['x', 'w'], outputs=['y']) return ([node], [x, w], [y]) @onnx_test() def conv_transpose_dyn_img_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, None, None]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 1, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, None, None]) node = onnx.helper.make_node('ConvTranspose', name='conv1', inputs=['x', 'w'], outputs=['y']) return ([node], [x, w], [y]) @onnx_test() def depthtospace_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 8, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 10, 10]) node = onnx.helper.make_node('DepthToSpace', inputs=['x'], outputs=['y'], blocksize=2, mode='DCR') return ([node], [x], [y]) @onnx_test() def depthtospace_simple_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 8, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 4, 6]) node = onnx.helper.make_node('DepthToSpace', inputs=['x'], outputs=['y'], blocksize=2, mode='DCR') return ([node], [x], [y]) @onnx_test() def depthtospace_crd_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 8, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 10, 10]) node = onnx.helper.make_node('DepthToSpace', inputs=['x'], outputs=['y'], blocksize=2, mode='CRD') return ([node], [x], [y]) @onnx_test() def spacetodepth_test(): x = helper.make_tensor_value_info('x', TensorProto.float, [2, 2, 10, 10]) y = helper.make_tensor_value_info('y', TensorProto.float, [2, 8, 5, 5]) node = onnx.helper.make_node('spacetodepth', inputs=['x'], outputs=['y'], blocksize=2) return ([node], [x], [y]) @onnx_test() def spacetodepth_simple_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 2, 4, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 8, 2, 3]) node = onnx.helper.make_node('SpaceToDepth', inputs=['x'], outputs=['y'], blocksize=2) return ([node], [x], [y]) @onnx_test() def spacetodepth_invalid_blocksize_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 2, 4, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 8, 2, 3]) node = onnx.helper.make_node('SpaceToDepth', inputs=['x'], outputs=['y'], blocksize=0.3) return ([node], [x], [y]) @onnx_test() def spacetodepth_nondivisibility_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 2, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 8, 2, 2]) node = onnx.helper.make_node('SpaceToDepth', inputs=['x'], outputs=['y'], blocksize=2) return ([node], [x], [y]) @onnx_test() def dequantizelinear_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.INT8, [5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [5]) node = onnx.helper.make_node( 'DequantizeLinear', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def dequantizelinear_zero_point_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.INT8, [5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1]) arg2 = helper.make_tensor_value_info('2', TensorProto.INT8, [1]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [5]) node = onnx.helper.make_node( 'DequantizeLinear', inputs=['0', '1', '2'], outputs=['out'], ) return ([node], [arg0, arg1, arg2], [arg_out]) def make_dequantizelinear_axis_graph(axis): arg0 = helper.make_tensor_value_info('0', TensorProto.INT8, [1, 1, 5, 1]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [5]) arg2 = helper.make_tensor_value_info('2', TensorProto.INT8, [5]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [1, 1, 5, 1]) node = onnx.helper.make_node('DequantizeLinear', inputs=['0', '1', '2'], outputs=['out'], axis=axis) return ([node], [arg0, arg1, arg2], [arg_out]) @onnx_test() def dequantizelinear_axis_test(): return make_dequantizelinear_axis_graph(2) @onnx_test() def dequantizelinear_neg_axis_test(): return make_dequantizelinear_axis_graph(-2) @onnx_test() def dequantizelinear_2d_blocked_with_zp_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [2, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node('DequantizeLinear', inputs=['x', 'scale', 'zp'], outputs=['y'], axis=0, block_size=1) return ([node], [x, scale, zp], [y]) @onnx_test() def dequantizelinear_3d_blocked_with_zp_runt_block_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [2, 5, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 5, 2]) node = onnx.helper.make_node('DequantizeLinear', inputs=['x', 'scale', 'zp'], outputs=['y'], axis=1, block_size=3) return ([node], [x, scale, zp], [y]) @onnx_test() def dequantizelinear_too_few_inputs_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node('DequantizeLinear', inputs=['x'], outputs=['y'], axis=0, block_size=1) return ([node], [x], [y]) @onnx_test() def dequantizelinear_too_many_inputs_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [2, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [2, 2]) zp2 = helper.make_tensor_value_info('zp2', TensorProto.INT8, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node('DequantizeLinear', inputs=['x', 'scale', 'zp', 'zp2'], outputs=['y'], axis=0, block_size=1) return ([node], [x, scale, zp, zp2], [y]) @onnx_test() def dequantizelinear_x_and_zp_type_mismatch_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [2, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.UINT8, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node('DequantizeLinear', inputs=['x', 'scale', 'zp'], outputs=['y'], axis=0, block_size=1) return ([node], [x, scale, zp], [y]) @onnx_test() def dequantizelinear_scale_and_zp_shape_mismatch_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [2, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node('DequantizeLinear', inputs=['x', 'scale', 'zp'], outputs=['y'], axis=0, block_size=1) return ([node], [x, scale, zp], [y]) @onnx_test() def quantizelinear_2d_blocked_with_zp_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [6, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [6, 2]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'scale', 'zp'], outputs=['y'], axis=0, block_size=3) return ([node], [x, scale, zp], [y]) @onnx_test() def quantizelinear_2d_blocked_runt_block_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 5]) y_scale = helper.make_tensor_value_info('y_scale', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [3, 5]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'y_scale'], outputs=['y'], axis=1, block_size=2) return ([node], [x, y_scale], [y]) @onnx_test() def quantizelinear_3d_blocked_with_zp_runt_block_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 5, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [2, 5, 2]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'scale', 'zp'], outputs=['y'], axis=1, block_size=3) return ([node], [x, scale, zp], [y]) @onnx_test() def quantizelinear_too_few_inputs_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [6, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [6, 2]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x'], outputs=['y'], axis=0, block_size=3) return ([node], [x], [y]) @onnx_test() def quantizelinear_too_many_inputs_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [6, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [2, 2]) zp2 = helper.make_tensor_value_info('zp2', TensorProto.INT8, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [6, 2]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'scale', 'zp', 'zp2'], outputs=['y'], axis=0, block_size=3) return ([node], [x, scale, zp, zp2], [y]) @onnx_test() def quantizelinear_scales_and_zp_shape_mismatch_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [6, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [1, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [6, 2]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'scale', 'zp'], outputs=['y'], axis=0, block_size=3) return ([node], [x, scale, zp], [y]) @onnx_test() def quantizelinear_output_dtype_and_zp_type_mismatch_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [6, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2, 2]) zp = helper.make_tensor_value_info('zp', TensorProto.INT8, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [6, 2]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'scale', 'zp'], outputs=['y'], axis=0, block_size=3, output_dtype=2) return ([node], [x, scale, zp], [y]) @onnx_test() def quantizelinear_per_axis_shape_mismatch_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 5]) y_scale = helper.make_tensor_value_info('y_scale', TensorProto.FLOAT, [4]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [3, 5]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'y_scale'], outputs=['y'], axis=1) return ([node], [x, y_scale], [y]) @onnx_test() def quantizelinear_blocked_zero_block_size_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 5]) y_scale = helper.make_tensor_value_info('y_scale', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [3, 5]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'y_scale'], outputs=['y'], axis=1, block_size=0) return ([node], [x, y_scale], [y]) @onnx_test() def quantizelinear_blocked_x_and_scales_rank_mismatch_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 5]) y_scale = helper.make_tensor_value_info('y_scale', TensorProto.FLOAT, [3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [3, 5]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'y_scale'], outputs=['y'], axis=1, block_size=2) return ([node], [x, y_scale], [y]) @onnx_test() def quantizelinear_blocked_non_bc_axis_size_mismatch_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 5]) y_scale = helper.make_tensor_value_info('y_scale', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [3, 5]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'y_scale'], outputs=['y'], axis=1, block_size=2) return ([node], [x, y_scale], [y]) @onnx_test() def quantizelinear_blocked_invalid_block_size_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 5]) y_scale = helper.make_tensor_value_info('y_scale', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [3, 5]) node = onnx.helper.make_node('QuantizeLinear', inputs=['x', 'y_scale'], outputs=['y'], axis=1, block_size=3) return ([node], [x, y_scale], [y]) @onnx_test() def dim_param_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, ["dim0", "dim1"]) return ([], [x], [x]) @onnx_test() def dropout_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 2, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 2, 2]) node = onnx.helper.make_node( 'Dropout', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def dynamicquantizelinear_1d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [6]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [6]) y_scale = helper.make_tensor_value_info('y_scale', TensorProto.FLOAT, [1]) y_zero_point = helper.make_tensor_value_info('y_zero_point', TensorProto.UINT8, [1]) node = onnx.helper.make_node( 'DynamicQuantizeLinear', inputs=['x'], outputs=['y', 'y_scale', 'y_zero_point'], ) return ([node], [x], [y, y_scale, y_zero_point]) @onnx_test() def dynamicquantizelinear_2d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [3, 4]) y_scale = helper.make_tensor_value_info('y_scale', TensorProto.FLOAT, [1]) y_zero_point = helper.make_tensor_value_info('y_zero_point', TensorProto.UINT8, [1]) node = onnx.helper.make_node( 'DynamicQuantizeLinear', inputs=['x'], outputs=['y', 'y_scale', 'y_zero_point'], ) return ([node], [x], [y, y_scale, y_zero_point]) @onnx_test() def einsum_permute_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 2]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ij->ji') return ([node], [x], [y]) def einsum_permute_sd3_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 64, 64, 2, 2, 16]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 16, 64, 2, 64, 2]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='nhwpqc->nchpwq') return ([node], [x], [y]) @onnx_test() def einsum_summation_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ij->') return ([node], [x], [y]) @onnx_test() def einsum_column_sum_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ij->j') return ([node], [x], [y]) @onnx_test() def einsum_row_sum_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ij->i') return ([node], [x], [y]) @onnx_test() def einsum_matrix_vector_multiplication_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) v = helper.make_tensor_value_info('v', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 1]) node = onnx.helper.make_node('Einsum', inputs=['x', 'v'], outputs=['y'], equation='ij,j->i') return ([node], [x, v], [y]) @onnx_test() def einsum_matrix_matrix_multiplication_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ij,kj->ik') return ([node], [x1, x2], [y]) @onnx_test() def einsum_vector_dot_product_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='i,i->') return ([node], [x1, x2], [y]) @onnx_test() def einsum_matrix_dot_product_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ij,ij->') return ([node], [x1, x2], [y]) @onnx_test() def einsum_hadamard_product_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ij,ij->ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_vector_outer_product_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 5]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='i,j->ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_matrix_outer_product_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 2, 5]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ij,kl->ijkl') return ([node], [x1, x2], [y]) @onnx_test() def einsum_batch_matrix_multiplication_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 2, 5]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 5, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 2, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ijk,ikl->ijl') return ([node], [x1, x2], [y]) @onnx_test() def einsum_tensor_contraction_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3, 5, 7]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [1, 3, 3, 7, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 7, 1, 3, 7]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='pqrs,tuqvr->pstuv') return ([node], [x1, x2], [y]) @onnx_test() def einsum_matrix_diagonal_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ii->i') return ([node], [x], [y]) @onnx_test() def einsum_batch_matrix_diagonal_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='...ii->...i') return ([node], [x], [y]) @onnx_test() def einsum_3d_diagonal_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 3]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='iii->i') return ([node], [x], [y]) @onnx_test() def einsum_diag_vector_multiply_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,i->i') return ([node], [x1, x2], [y]) @onnx_test() def einsum_matrix_trace_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ii->') return ([node], [x], [y]) @onnx_test() def einsum_matrix_trace_implicit_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ii') return ([node], [x], [y]) @onnx_test() def einsum_2d_3d_multiplication_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 5]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ij,jkl') return ([node], [x1, x2], [y]) @onnx_test() def einsum_element_wise_multiplication_and_row_sum_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='i,ij->i') return ([node], [x1, x2], [y]) @onnx_test() def einsum_broadcast_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 1]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ij, jk -> ik') return ([node], [x1, x2], [y]) @onnx_test() def einsum_3d_broadcast_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [1, 3, 1]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 2, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='bik,bkj->bij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_3d_opposite_broadcast_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [1, 3, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 1, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='bik,bkj->bij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_3_inputs_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 2, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 2]) x3 = helper.make_tensor_value_info('x3', TensorProto.FLOAT, [2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2', 'x3'], outputs=['y'], equation='bac,cd,def->ebc') return ([node], [x1, x2, x3], [y]) @onnx_test() def einsum_bilinear_transformation_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [5, 3, 7]) x3 = helper.make_tensor_value_info('x3', TensorProto.FLOAT, [2, 7]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 5]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2', 'x3'], outputs=['y'], equation='ik,jkl,il->ij') return ([node], [x1, x2, x3], [y]) @onnx_test() def einsum_ellipsis_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='...ik,kj...->ij...') return ([node], [x1, x2], [y]) @onnx_test() def einsum_ellipsis_multidim_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 2, 3, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 4, 3, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 3, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='...ik,kj...->ij...') return ([node], [x1, x2], [y]) @onnx_test() def einsum_ellipsis_zero_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [4, 3, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 2, 4]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='...qhd,...khd->...hqk') return ([node], [x1, x2], [y]) @onnx_test() def einsum_ellipsis_implicit_form_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 2, 3, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 4, 3, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='...qhd,...khd') return ([node], [x1, x2], [y]) @onnx_test() def einsum_ellipsis_scalar_multiplication_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='..., ...') return ([node], [x1, x2], [y]) @onnx_test() def einsum_common_1_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 2, 2, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 2, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='bsnh,btnh->bnts') return ([node], [x1, x2], [y]) @onnx_test() def einsum_common_2_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 2, 2, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='bsnh,ctnh->nts') return ([node], [x1, x2], [y]) @onnx_test() def einsum_common_3_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 2, 2, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 2]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='bnst,chst->shn') return ([node], [x1, x2], [y]) @onnx_test() def einsum_common_4_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 2, 3, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 3, 4]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='bcxd,bcyd->bcxy') return ([node], [x1, x2], [y]) @onnx_test() def einsum_common_5_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 2, 3, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 4, 3, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3, 2, 4]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='...qhd,...khd->...hqk') return ([node], [x1, x2], [y]) @onnx_test() def einsum_common_6_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 2, 2]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 2, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='i...k,k...j->i...j') return ([node], [x1, x2], [y]) @onnx_test() def einsum_common_7_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='...j->...') return ([node], [x], [y]) @onnx_test() def einsum_common_8_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,jj->ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_missing_equation_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y']) return ([node], [x1, x2], [y]) @onnx_test() def einsum_multiple_arrows_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,jj->->ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_empty_term_before_arrow_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,->ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_multiple_ellipses_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='......ii,...jj->...ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_comma_in_output_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,jj->i,j') return ([node], [x1, x2], [y]) @onnx_test() def einsum_empty_term_before_comma_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,,jj->ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_last_input_missing_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,jj,') return ([node], [x1, x2], [y]) @onnx_test() def einsum_term_input_mismatch_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,jj,kk->ijk') return ([node], [x1, x2], [y]) @onnx_test() def einsum_ellipsis_mismatch_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='...ii,...jj->...ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_rank_mismatch_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='iik,jj->ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_output_surplus_label_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='ii,jj->ijk') return ([node], [x1, x2], [y]) @onnx_test() def einsum_output_missing_ellipsis_negative_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3, 3, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x1', 'x2'], outputs=['y'], equation='...ii,...jj->ij') return ([node], [x1, x2], [y]) @onnx_test() def einsum_multiple_diagonals_negative_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='iijj->ij') return ([node], [x], [y]) @onnx_test() def einsum_diagonal_dim_mismatch_negative_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ii->i') return ([node], [x], [y]) @onnx_test() def einsum_right_batch_diagonal_negative_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node('Einsum', inputs=['x'], outputs=['y'], equation='ii...->i...') return ([node], [x], [y]) @onnx_test() def elu_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('Elu', inputs=['0'], outputs=['1'], alpha=0.01) return ([node], [x], [y]) @onnx_test() def embedding_bag_test(): index_val = np.array([1, 0, 2]) offset_val = np.array([0]) index_tensor = helper.make_tensor(name='index_val', data_type=TensorProto.INT32, dims=index_val.shape, vals=index_val.astype(np.int32)) index = onnx.helper.make_node('Constant', inputs=[], outputs=['index'], value=index_tensor) offset_tensor = helper.make_tensor(name='offset_val', data_type=TensorProto.INT32, dims=offset_val.reshape(()).shape, vals=offset_val.astype(np.int32)) offset = onnx.helper.make_node('Constant', inputs=[], outputs=['offset'], value=offset_tensor) weight = helper.make_tensor_value_info('weight', TensorProto.FLOAT, [4, 2]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [1, 2]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [1, 2]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [1, 2]) node1 = onnx.helper.make_node('ATen', inputs=['weight', 'index', 'offset'], outputs=['y1'], mode=0, operator='embedding_bag') node2 = onnx.helper.make_node('ATen', inputs=['weight', 'index', 'offset'], outputs=['y2'], mode=1, operator='embedding_bag') node3 = onnx.helper.make_node('ATen', inputs=['weight', 'index', 'offset'], outputs=['y3'], mode=2, operator='embedding_bag') return ([index, offset, node1, node2, node3], [weight], [y1, y2, y3]) @onnx_test() def embedding_bag_offset_test(): index_val = np.array([1, 0]) offset_val = np.array([0, 1]) index_tensor = helper.make_tensor(name='index_val', data_type=TensorProto.INT32, dims=index_val.shape, vals=index_val.astype(np.int32)) index = onnx.helper.make_node('Constant', inputs=[], outputs=['index'], value=index_tensor) offset_tensor = helper.make_tensor(name='offset_val', data_type=TensorProto.INT32, dims=offset_val.shape, vals=offset_val.astype(np.int32)) offset = onnx.helper.make_node('Constant', inputs=[], outputs=['offset'], value=offset_tensor) weight = helper.make_tensor_value_info('weight', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node('ATen', inputs=['weight', 'index', 'offset'], outputs=['y'], mode=0, operator='embedding_bag') return ([index, offset, node], [weight], [y]) @onnx_test() def equal_test(): ax1 = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) x1 = helper.make_tensor("x1", data_type=TensorProto.FLOAT, dims=(2, 3), vals=ax1.astype(np.float32)) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node( 'Equal', inputs=['x1', 'x2'], outputs=['y'], ) return ([node], [x2], [y], [x1]) @onnx_test() def equal_bool_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.BOOL, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node1 = onnx.helper.make_node('Cast', inputs=['x1'], outputs=['bx1'], to=9) node2 = onnx.helper.make_node( 'Equal', inputs=['bx1', 'x2'], outputs=['y'], ) return ([node1, node2], [x1, x2], [y]) @onnx_test() def erf_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 15]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10, 15]) node = onnx.helper.make_node( 'Erf', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def exp_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Exp', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def expand_test(): shape_val = np.array([2, 3, 4, 5]).astype(np.int64) shape_ts = helper.make_tensor(name='shape_tensor', data_type=TensorProto.INT32, dims=shape_val.shape, vals=shape_val.flatten().astype(int)) shape_const = helper.make_node( 'Constant', inputs=[], outputs=['shape'], value=shape_ts, ) x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 1, 1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node('Expand', inputs=['x', 'shape'], outputs=['y']) return ([shape_const, node], [x], [y]) @onnx_test() def expand_static_input_dyn_output_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 1, 1]) dims_in = helper.make_tensor_value_info('dims', TensorProto.INT64, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node('Expand', inputs=['x', 'dims'], outputs=['y']) return ([node], [x, dims_in], [y]) @onnx_test() def expand_dyn_input_dyn_output_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 1, 1]) dims_in = helper.make_tensor_value_info('dims', TensorProto.INT64, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node('Expand', inputs=['x', 'dims'], outputs=['y']) return ([node], [x, dims_in], [y]) @onnx_test() def expand_dyn_input_static_dims_throw(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 1, 1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 4]) shape_val = np.array([3, 4, 4]).astype(np.int64) shape_ts = helper.make_tensor(name='shape_tensor', data_type=TensorProto.INT32, dims=shape_val.shape, vals=shape_val.flatten().astype(int)) shape_const = helper.make_node( 'Constant', inputs=[], outputs=['shape'], value=shape_ts, ) node = onnx.helper.make_node('Expand', inputs=['x', 'shape'], outputs=['y']) return ([shape_const, node], [x], [y]) @onnx_test(True) def external_constant_test(): x = np.array([0, 1, 2]) y = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) tensor = from_array(x) tensor.name = 'const_tensor' node = onnx.helper.make_node('Constant', inputs=[], outputs=['0'], value=tensor) return ([node], [], [y]) @onnx_test() def eyelike_default_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT, [3, 4]) T2 = helper.make_tensor_value_info('T2', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node( 'EyeLike', inputs=['T1'], outputs=['T2'], ) return ([node], [T1], [T2]) @onnx_test() def eyelike_double_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.DOUBLE, [6, 15]) T2 = helper.make_tensor_value_info('T2', TensorProto.DOUBLE, [6, 15]) node = onnx.helper.make_node( 'EyeLike', inputs=['T1'], outputs=['T2'], ) return ([node], [T1], [T2]) @onnx_test() def eyelike_half_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT16, [8, 8]) T2 = helper.make_tensor_value_info('T2', TensorProto.FLOAT16, [8, 8]) node = onnx.helper.make_node( 'EyeLike', inputs=['T1'], outputs=['T2'], ) return ([node], [T1], [T2]) @onnx_test() def eyelike_bf16_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.BFLOAT16, [8, 8]) T2 = helper.make_tensor_value_info('T2', TensorProto.BFLOAT16, [8, 8]) node = onnx.helper.make_node( 'EyeLike', inputs=['T1'], outputs=['T2'], ) return ([node], [T1], [T2]) @onnx_test() def eyelike_k_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT, [3, 4]) T2 = helper.make_tensor_value_info('T2', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node('EyeLike', inputs=['T1'], outputs=['T2'], k=1) return ([node], [T1], [T2]) @onnx_test() def eyelike_k_outofbounds_neg_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT, [2, 4]) T2 = helper.make_tensor_value_info('T2', TensorProto.FLOAT, [2, 4]) node = onnx.helper.make_node('EyeLike', inputs=['T1'], outputs=['T2'], k=-2) return ([node], [T1], [T2]) @onnx_test() def eyelike_k_outofbounds_pos_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT, [3, 4]) T2 = helper.make_tensor_value_info('T2', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node('EyeLike', inputs=['T1'], outputs=['T2'], k=4) return ([node], [T1], [T2]) @onnx_test() def eyelike_not_rank2_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT, [3, 4, 2]) T2 = helper.make_tensor_value_info('T2', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node( 'EyeLike', inputs=['T1'], outputs=['T2'], ) return ([node], [T1], [T2]) @onnx_test() def eyelike_verify_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT, [3, 4]) T2 = helper.make_tensor_value_info('T2', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node('EyeLike', inputs=['T1'], outputs=['T2'], k=1) return ([node], [T1], [T2]) @onnx_test() def eyelike_verify_negk_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT, [3, 4]) T2 = helper.make_tensor_value_info('T2', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node('EyeLike', inputs=['T1'], outputs=['T2'], k=-2) return ([node], [T1], [T2]) @onnx_test() def eyelike_set_dtype_test(): T1 = helper.make_tensor_value_info('T1', TensorProto.FLOAT, [3, 4]) T2 = helper.make_tensor_value_info('T2', TensorProto.DOUBLE, [3, 4]) node = onnx.helper.make_node('EyeLike', inputs=['T1'], outputs=['T2'], dtype=TensorProto.DOUBLE) return ([node], [T1], [T2]) @onnx_test() def flatten_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [6, 20]) y2 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [2, 60]) node = onnx.helper.make_node('Flatten', inputs=['0'], axis=2, outputs=['2']) node2 = onnx.helper.make_node('Flatten', inputs=['0'], outputs=['3']) return ([node, node2], [x], [y, y2]) @onnx_test() def flatten_nonstd_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 5, 4]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [6, 20]) y2 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [2, 60]) trans = helper.make_node( 'Transpose', inputs=['0'], outputs=['tx'], perm=[0, 1, 3, 2], ) node = onnx.helper.make_node('Flatten', inputs=['tx'], axis=2, outputs=['2']) node2 = onnx.helper.make_node('Flatten', inputs=['tx'], outputs=['3']) return ([trans, node, node2], [x], [y, y2]) @onnx_test() def flatten_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 4, 5]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [None, 20]) node = onnx.helper.make_node('Flatten', inputs=['0'], axis=2, outputs=['2']) return ([node], [x], [y]) @onnx_test() def floor_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Floor', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def gather_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 4, 5, 6]) i = helper.make_tensor_value_info('indices', TensorProto.INT32, [2, 3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Gather', inputs=['data', 'indices'], outputs=['y'], axis=1, ) return ([node], [x, i], [y]) @onnx_test() def gather_scalar_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 4, 5, 6]) i = helper.make_tensor_value_info('indices', TensorProto.INT32, []) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 5, 6]) node = onnx.helper.make_node( 'Gather', inputs=['data', 'indices'], outputs=['y'], axis=1, ) return ([node], [x, i], [y]) @onnx_test() def gather_dyn_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [None, 4, 5, 6]) i = helper.make_tensor_value_info('indices', TensorProto.INT32, [None, 3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Gather', inputs=['data', 'indices'], outputs=['y'], axis=1, ) return ([node], [x, i], [y]) @onnx_test() def gather_elements_axis0_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 4]) i = helper.make_tensor_value_info('indices', TensorProto.INT32, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node( 'GatherElements', inputs=['data', 'indices'], outputs=['y'], axis=0, ) return ([node], [x, i], [y]) @onnx_test() def gather_elements_axis1_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 4]) i = helper.make_tensor_value_info('indices', TensorProto.INT32, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node( 'GatherElements', inputs=['data', 'indices'], outputs=['y'], axis=1, ) return ([node], [x, i], [y]) @onnx_test() def gathernd_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [2, 2]) i = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2]) node = onnx.helper.make_node('GatherND', inputs=['data', 'indices'], outputs=['y']) return ([node], [x, i], [y]) @onnx_test() def gathernd_dyn_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [None, 2]) i = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2]) node = onnx.helper.make_node('GatherND', inputs=['data', 'indices'], outputs=['y']) return ([node], [x, i], [y]) @onnx_test() def gathernd_batch_dims_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [2, 2, 2]) i = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node( 'GatherND', inputs=['data', 'indices'], outputs=['y'], batch_dims=1, ) return ([node], [x, i], [y]) @onnx_test() def gelu_default_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node("Gelu", inputs=["x"], outputs=["y"]) return ([node], [x], [y]) @onnx_test() def gelu_default_half_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [3, 3]) node = onnx.helper.make_node("Gelu", inputs=["x"], outputs=["y"]) return ([node], [x], [y]) # @onnx_test() # def gelu_default_bf16_test(): # x = helper.make_tensor_value_info('x', TensorProto.BFLOAT16, [3, 3]) # y = helper.make_tensor_value_info('y', TensorProto.BFLOAT16, [3, 3]) # node = onnx.helper.make_node("Gelu", inputs=["x"], outputs=["y"]) # return ([node], [x], [y]) @onnx_test() def gelu_tanh_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node("Gelu", inputs=["x"], outputs=["y"], approximate="tanh") return ([node], [x], [y]) @onnx_test() def gelu_tanh_double_test(): x = helper.make_tensor_value_info('x', TensorProto.DOUBLE, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.DOUBLE, [3, 3]) node = onnx.helper.make_node("Gelu", inputs=["x"], outputs=["y"], approximate="tanh") return ([node], [x], [y]) @onnx_test() def gelu_invalid_input_type_test(): x = helper.make_tensor_value_info('x', TensorProto.INT32, [3]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [3]) node = onnx.helper.make_node("Gelu", inputs=["x"], outputs=["y"]) return ([node], [x], [y]) @onnx_test() def gelu_add_bias_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node("BiasGelu", inputs=["x", "y"], outputs=["z"], domain="com.microsoft") return ([node], [x, y], [z]) @onnx_test() def gelu_bias_invalid_type_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [3]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node("BiasGelu", inputs=["x", "y"], outputs=["z"], domain="com.microsoft") return ([node], [x, y], [z]) @onnx_test() def gelu_add_bias_split_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 4, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [6]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [2, 4, 3]) node = onnx.helper.make_node("BiasSplitGelu", inputs=["x", "y"], outputs=["z"], domain="com.microsoft") return ([node], [x, y], [z]) @onnx_test() def gelu_add_bias_split_invalid_dims_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [2, 4, 2]) node = onnx.helper.make_node("BiasSplitGelu", inputs=["x", "y"], outputs=["z"], domain="com.microsoft") return ([node], [x, y], [z]) @onnx_test() def gelu_fast_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node("FastGelu", inputs=["x"], outputs=["y"], domain="com.microsoft") return ([node], [x], [y]) @onnx_test() def gelu_fast_bias_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [3, 3]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT16, [3, 3]) node = onnx.helper.make_node("FastGelu", inputs=["x", "y"], outputs=["z"], domain="com.microsoft") return ([node], [x, y], [z]) @onnx_test() def gelu_fast_invalid_x_test(): x = helper.make_tensor_value_info('x', TensorProto.DOUBLE, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node("FastGelu", inputs=["x", "y"], outputs=["z"], domain="com.microsoft") return ([node], [x, y], [z]) @onnx_test() def gelu_fast_invalid_bias_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.DOUBLE, [3, 3]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node("FastGelu", inputs=["x", "y"], outputs=["z"], domain="com.microsoft") return ([node], [x, y], [z]) @onnx_test() def gelu_quick_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 3]) node = onnx.helper.make_node("QuickGelu", inputs=["x"], outputs=["y"], alpha=0.5, domain="com.microsoft") return ([node], [x], [y]) @onnx_test() def gemm_test(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT, [8, 6]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT, [8, 7]) C = helper.make_tensor_value_info('C', TensorProto.FLOAT, [6, 7]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [6, 7]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B', 'C'], outputs=['Y'], alpha=0.5, beta=0.8, transA=1) return ([node], [A, B, C], [Y]) @onnx_test() def gemm_no_C_test(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT, [5, 7]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT, [11, 5]) C = helper.make_tensor_value_info('C', TensorProto.FLOAT, []) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [7, 11]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B', 'C'], outputs=['Y'], alpha=2.0, beta=2.0, transA=1, transB=1) return ([node], [A, B, C], [Y]) @onnx_test() def gemm_brcst_C_test(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT, [5, 6]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT, [5, 7]) C = helper.make_tensor_value_info('C', TensorProto.FLOAT, [6, 1]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [6, 7]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B', 'C'], outputs=['Y'], alpha=0.5, beta=0.8, transA=1) return ([node], [A, B, C], [Y]) @onnx_test() def gemm_half_test(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT16, [8, 6]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT16, [8, 7]) C = helper.make_tensor_value_info('C', TensorProto.FLOAT16, [6, 1]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT16, [6, 7]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B', 'C'], outputs=['Y'], alpha=0.5, beta=0.8, transA=1) return ([node], [A, B, C], [Y]) @onnx_test() def gemm_bf16_test(): A = helper.make_tensor_value_info('A', TensorProto.BFLOAT16, [8, 6]) B = helper.make_tensor_value_info('B', TensorProto.BFLOAT16, [8, 7]) C = helper.make_tensor_value_info('C', TensorProto.BFLOAT16, [6, 1]) Y = helper.make_tensor_value_info('Y', TensorProto.BFLOAT16, [6, 7]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B', 'C'], outputs=['Y'], alpha=0.5, beta=0.8, transA=1) return ([node], [A, B, C], [Y]) @onnx_test() def gemm_fp8_test(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT8E4M3FNUZ, [8, 6]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT8E4M3FNUZ, [8, 7]) C = helper.make_tensor_value_info('C', TensorProto.FLOAT8E4M3FNUZ, [6, 1]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT8E4M3FNUZ, [6, 7]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B', 'C'], outputs=['Y'], alpha=0.5, beta=0.8, transA=1) return ([node], [A, B, C], [Y]) @onnx_test() def gemm_dyn_inner_test(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT, [None, 6]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT, [None, 7]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [6, 7]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B'], outputs=['Y'], alpha=0.5, transA=1) return ([node], [A, B], [Y]) @onnx_test() def gemm_dyn_outer_test(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT, [5, None]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT, [11, 5]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [None, 11]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B'], outputs=['Y'], alpha=2.0, transA=1, transB=1) return ([node], [A, B], [Y]) @onnx_test() def gemm_dyn_bias_test(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT, [8, None]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT, [8, 7]) C = helper.make_tensor_value_info('C', TensorProto.FLOAT, [1, 7]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [None, 7]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B', 'C'], outputs=['Y'], alpha=1.0, beta=1.0, transA=1) return ([node], [A, B, C], [Y]) @onnx_test() def gemm_rank_error(): A = helper.make_tensor_value_info('A', TensorProto.FLOAT, [4, 1, 8, 6]) B = helper.make_tensor_value_info('B', TensorProto.FLOAT, [4, 1, 8, 7]) C = helper.make_tensor_value_info('C', TensorProto.FLOAT, [6, 7]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [4, 1, 6, 7]) node = onnx.helper.make_node('Gemm', inputs=['A', 'B', 'C'], outputs=['Y'], alpha=0.5, beta=0.8, transA=1) return ([node], [A, B, C], [Y]) @onnx_test() def globalavgpool_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 1, 1]) node = onnx.helper.make_node( 'GlobalAveragePool', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def globalavgpool_fp8_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT8E4M3FNUZ, [1, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT8E4M3FNUZ, [1, 3, 1, 1]) node = onnx.helper.make_node( 'GlobalAveragePool', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def globalavgpool_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 1, 1]) node = onnx.helper.make_node( 'GlobalAveragePool', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def globallppool_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 1, 1]) node = onnx.helper.make_node( 'GlobalLpPool', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def globallppool_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, None, None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 1, 1]) node = onnx.helper.make_node( 'GlobalLpPool', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def globalmaxpool_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 1, 1]) node = onnx.helper.make_node( 'GlobalMaxPool', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def globalmaxpool_fp8_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT8E4M3FNUZ, [1, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT8E4M3FNUZ, [1, 3, 1, 1]) node = onnx.helper.make_node( 'GlobalMaxPool', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def globalmaxpool_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 32, 32]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 1, 1]) node = onnx.helper.make_node( 'GlobalMaxPool', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def greater_test(): ax1 = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) x1 = helper.make_tensor("x1", data_type=TensorProto.FLOAT, dims=(2, 3), vals=ax1.astype(np.float32)) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node( 'Greater', inputs=['x1', 'x2'], outputs=['y'], ) return ([node], [x2], [y], [x1]) @onnx_test() def greater_bool_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.BOOL, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node1 = onnx.helper.make_node('Cast', inputs=['x1'], outputs=['bx1'], to=9) node2 = onnx.helper.make_node( 'Greater', inputs=['bx1', 'x2'], outputs=['y'], ) return ([node1, node2], [x1, x2], [y]) @onnx_test() def greaterorequal_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( 'GreaterOrEqual', inputs=['x1', 'x2'], outputs=['y'], ) return ([node], [x1, x2], [y]) @onnx_test() def gridsample_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 4, 4]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 6, 6, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 6, 6]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="linear", padding_mode="zeros", align_corners=0, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_channel_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 3, 4, 4]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 6, 6, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 3, 6, 6]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="bilinear", padding_mode="border", align_corners=1, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_512x512_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 3, 512, 512]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 512, 512, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 3, 512, 512]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="bilinear", padding_mode="border", align_corners=1, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_half_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [1, 1, 4, 4]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 6, 6, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [1, 1, 6, 6]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="linear", padding_mode="zeros", align_corners=0, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_bf16_test(): x = helper.make_tensor_value_info('x', TensorProto.BFLOAT16, [1, 1, 4, 4]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 6, 6, 2]) y = helper.make_tensor_value_info('y', TensorProto.BFLOAT16, [1, 1, 6, 6]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="linear", padding_mode="zeros", align_corners=0, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_int_test(): x = helper.make_tensor_value_info('x', TensorProto.INT32, [1, 1, 4, 4]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 6, 6, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [1, 1, 6, 6]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="linear", padding_mode="zeros", align_corners=0, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_simple_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 2, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 1, 1, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 1, 1]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="linear", ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_bilinear_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], align_corners=0, mode="linear", ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_aligncorners_true_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="linear", align_corners=1, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_nearest_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="nearest", ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_bicubic_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="cubic", ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_bicubic_align_corners_0_additional_1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="cubic", align_corners=0, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_bicubic_align_corners_1_additional_1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="cubic", align_corners=1, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_nearest_align_corners_0_additional_1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="nearest", align_corners=0, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_nearest_align_corners_1_additional_1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="nearest", align_corners=1, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_bilinear_align_corners_0_additional_1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="linear", align_corners=0, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_bilinear_align_corners_1_additional_1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="linear", align_corners=1, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_zeros_padding_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], padding_mode="zeros", ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_border_padding_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], padding_mode="border", ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_reflection_padding_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], padding_mode="reflection", ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_volumetric_nearest_align_corners_0_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4, 2]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], mode="nearest", align_corners=0, ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_wrong_grid_type_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.INT32, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], ) return ([node], [x, grid], [y]) @onnx_test() def gridsample_mismatching_dims_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 3, 2, 2]) grid = helper.make_tensor_value_info('grid', TensorProto.FLOAT, [1, 2, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 2, 4]) node = onnx.helper.make_node( "GridSample", inputs=["x", "grid"], outputs=["y"], ) return ([node], [x, grid], [y]) @onnx_test() def group_conv_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 4, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 1, 3, 3]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 4, 14, 14]) node = onnx.helper.make_node( 'Conv', inputs=['0', '1'], group=4, outputs=['2'], ) return ([node], [x, y], [z]) def group_norm_test(x_dims, scale_dims, bias_dims, y_dims, num_groups, eps_value=1e-5, dtype=TensorProto.FLOAT): x = helper.make_tensor_value_info('x', dtype, x_dims) scale = helper.make_tensor_value_info('scale', dtype, scale_dims) bias = helper.make_tensor_value_info('bias', dtype, bias_dims) y = helper.make_tensor_value_info('y', dtype, y_dims) node = onnx.helper.make_node('GroupNormalization', inputs=['x', 'scale', 'bias'], outputs=['y'], num_groups=num_groups, epsilon=eps_value) return ([node], [x, scale, bias], [y]) @onnx_test() def group_norm_3d_test(): return group_norm_test([1, 4, 2], [4], [4], [1, 4, 2], 2) @onnx_test() def group_norm_3d_half_test(): return group_norm_test([1, 4, 2], [4], [4], [1, 4, 2], 2, dtype=TensorProto.FLOAT16) @onnx_test() def group_norm_3d_bf16_test(): return group_norm_test([1, 4, 2], [4], [4], [1, 4, 2], 2, dtype=TensorProto.BFLOAT16) @onnx_test() def group_norm_4d_test(): return group_norm_test([1, 4, 3, 3], [4], [4], [1, 4, 3, 3], 2) @onnx_test() def group_norm_4d_half_test(): return group_norm_test([1, 4, 3, 3], [4], [4], [1, 4, 3, 3], 2, dtype=TensorProto.FLOAT16) @onnx_test() def group_norm_4d_bf16_test(): return group_norm_test([1, 4, 3, 3], [4], [4], [1, 4, 3, 3], 2, dtype=TensorProto.BFLOAT16) @onnx_test() def group_norm_5d_test(): return group_norm_test([3, 3, 3, 3, 3], [3], [3], [3, 3, 3, 3, 3], 1) @onnx_test() def group_norm_5d_half_test(): return group_norm_test([3, 3, 3, 3, 3], [3], [3], [3, 3, 3, 3, 3], 1, dtype=TensorProto.FLOAT16) @onnx_test() def group_norm_5d_bf16_test(): return group_norm_test([3, 3, 3, 3, 3], [3], [3], [3, 3, 3, 3, 3], 1, dtype=TensorProto.BFLOAT16) @onnx_test() def group_norm_small_eps_half_test(): return group_norm_test([1, 4, 2], [4], [4], [1, 4, 2], 2, eps_value=1e-12, dtype=TensorProto.FLOAT16) @onnx_test() def group_norm_small_eps_bf16_test(): return group_norm_test([1, 4, 2], [4], [4], [1, 4, 2], 2, eps_value=1e-7, dtype=TensorProto.BFLOAT16) @onnx_test() def group_norm_invalid_num_groups_error_test(): return group_norm_test([1, 4, 3, 3], [2], [2], [1, 4, 3, 3], 3) @onnx_test() def group_norm_missing_attribute_error_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 4]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 4]) node = onnx.helper.make_node('GroupNormalization', inputs=['x', 'scale', 'bias'], outputs=['y']) return ([node], [x, scale, bias], [y]) @onnx_test() def group_norm_invalid_input_count_error_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 4, 3, 3]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 4, 3, 3]) node = onnx.helper.make_node('GroupNormalization', inputs=['x', 'scale'], outputs=['y'], num_groups=2) return ([node], [x, scale], [y]) @onnx_test() def group_norm_invalid_input_shape_error_test(): return group_norm_test([1, 4], [2], [2], [1, 4], 2) @onnx_test() def group_norm_invalid_scale_shape_test(): return group_norm_test([1, 4, 3, 3], [1], [2], [1, 4, 3, 3], 2) @onnx_test() def group_norm_invalid_bias_shape_test(): return group_norm_test([1, 4, 3, 3], [4], [3], [1, 4, 3, 3], 2) def group_norm_contrib_test(x_dims, gamma_dims, beta_dims, y_dims, num_groups, activation, channels_last, eps_value=1e-5, dtype=TensorProto.FLOAT, gamma_dtype=TensorProto.FLOAT, beta_dtype=TensorProto.FLOAT): x = helper.make_tensor_value_info('x', dtype, x_dims) gamma = helper.make_tensor_value_info('gamma', gamma_dtype, gamma_dims) beta = helper.make_tensor_value_info('beta', beta_dtype, beta_dims) y = helper.make_tensor_value_info('y', dtype, y_dims) node = onnx.helper.make_node('GroupNorm', inputs=['x', 'gamma', 'beta'], outputs=['y'], activation=activation, channels_last=channels_last, groups=num_groups, epsilon=eps_value) return ([node], [x, gamma, beta], [y]) @onnx_test() def group_norm_contrib_3d_test(): return group_norm_contrib_test([1, 4, 2], [4], [4], [1, 4, 2], 2, 0, 0) @onnx_test() def group_norm_contrib_3d_channel_last_test(): return group_norm_contrib_test([1, 4, 2], [2], [2], [1, 4, 2], 2, 0, 1) @onnx_test() def group_norm_contrib_3d_channel_last_half_test(): return group_norm_contrib_test([1, 4, 2], [2], [2], [1, 4, 2], 2, 0, 1, dtype=TensorProto.FLOAT16, gamma_dtype=TensorProto.FLOAT16, beta_dtype=TensorProto.FLOAT16) @onnx_test() def group_norm_contrib_3d_channel_last_bf16_test(): return group_norm_contrib_test([1, 4, 2], [2], [2], [1, 4, 2], 2, 0, 1, dtype=TensorProto.BFLOAT16, gamma_dtype=TensorProto.BFLOAT16, beta_dtype=TensorProto.BFLOAT16) @onnx_test() def group_norm_contrib_gamma_beta_float_xy_half_test(): return group_norm_contrib_test([1, 4, 2], [4], [4], [1, 4, 2], 2, 0, 0, dtype=TensorProto.FLOAT16) @onnx_test() def group_norm_contrib_silu_3d_test(): return group_norm_contrib_test([1, 4, 2], [4], [4], [1, 4, 2], 2, 1, 0) @onnx_test() def group_norm_contrib_channels_last_3d_test(): return group_norm_contrib_test([1, 4, 2], [2], [2], [1, 4, 2], 2, 0, 1) @onnx_test() def group_norm_contrib_channels_last_4d_test(): return group_norm_contrib_test([1, 3, 3, 4], [4], [4], [1, 3, 3, 4], 2, 0, 1) @onnx_test() def group_norm_contrib_channels_last_and_silu_3d_test(): return group_norm_contrib_test([1, 4, 2], [2], [2], [1, 4, 2], 2, 1, 1) @onnx_test() def group_norm_contrib_no_activation_attr_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 4, 2]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT, [2]) beta = helper.make_tensor_value_info('beta', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 4, 2]) node = onnx.helper.make_node('GroupNorm', inputs=['x', 'gamma', 'beta'], outputs=['y'], channels_last=0, groups=2) return ([node], [x, gamma, beta], [y]) @onnx_test() def group_norm_contrib_no_num_groups_attr_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 4, 2]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT, [2]) beta = helper.make_tensor_value_info('beta', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 4, 2]) node = onnx.helper.make_node('GroupNorm', inputs=['x', 'gamma', 'beta'], outputs=['y'], activation=0, channels_last=0) return ([node], [x, gamma, beta], [y]) @onnx_test() def group_query_attention_test(): qkv = helper.make_tensor_value_info('qkv', TensorProto.FLOAT16, [1, 1, 12288]) key = helper.make_tensor_value_info('key', TensorProto.FLOAT, [1]) value = helper.make_tensor_value_info('value', TensorProto.FLOAT, [1]) past_key_values_key = helper.make_tensor_value_info( 'past_key_values_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) past_key_values_value = helper.make_tensor_value_info( 'past_key_values_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) slk_val = np.array([1]) seqlens_k = helper.make_tensor(name="seqlens_k", data_type=TensorProto.INT32, dims=slk_val.shape, vals=slk_val.astype(int)) tsl_val = np.array([2]) total_sequence_length = helper.make_tensor(name="total_sequence_length", data_type=TensorProto.INT32, dims=tsl_val.shape, vals=tsl_val.astype(int)) cc_val = np.ones([4096, 64], dtype=np.float16) cos_cache = helper.make_tensor(name="cos_cache", data_type=TensorProto.FLOAT16, dims=cc_val.shape, vals=cc_val) sin_cache = helper.make_tensor(name="sin_cache", data_type=TensorProto.FLOAT16, dims=cc_val.shape, vals=cc_val) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 1, 4096]) present_key = helper.make_tensor_value_info('present_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) present_value = helper.make_tensor_value_info('present_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) node = onnx.helper.make_node( 'GroupQueryAttention', inputs=[ 'qkv', 'key', 'value', 'past_key_values_key', 'past_key_values_value', 'seqlens_k', 'total_sequence_length', 'cos_cache', 'sin_cache' ], outputs=['output', 'present_key', 'present_value'], do_rotary=1, kv_num_heads=32, local_window_size=-1, num_heads=32, rotary_interleaved=0, scale=1.0, domain="com.microsoft") return ([node ], [qkv, key, value, past_key_values_key, past_key_values_value], [output, present_key, present_value], [seqlens_k, total_sequence_length, cos_cache, sin_cache]) @onnx_test() def group_query_attention_non_packed_qkv_test(): query = helper.make_tensor_value_info('query', TensorProto.FLOAT16, [1, 1, 4096]) key = helper.make_tensor_value_info('key', TensorProto.FLOAT16, [1, 1, 4096]) value = helper.make_tensor_value_info('value', TensorProto.FLOAT16, [1, 1, 4096]) past_key_values_key = helper.make_tensor_value_info( 'past_key_values_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) past_key_values_value = helper.make_tensor_value_info( 'past_key_values_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) slk_val = np.array([1]) seqlens_k = helper.make_tensor(name="seqlens_k", data_type=TensorProto.INT32, dims=slk_val.shape, vals=slk_val.astype(int)) tsl_val = np.array([2]) total_sequence_length = helper.make_tensor(name="total_sequence_length", data_type=TensorProto.INT32, dims=tsl_val.shape, vals=tsl_val.astype(int)) cc_val = np.ones([4096, 64], dtype=np.float16) cos_cache = helper.make_tensor(name="cos_cache", data_type=TensorProto.FLOAT16, dims=cc_val.shape, vals=cc_val) sin_cache = helper.make_tensor(name="sin_cache", data_type=TensorProto.FLOAT16, dims=cc_val.shape, vals=cc_val) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 1, 4096]) present_key = helper.make_tensor_value_info('present_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) present_value = helper.make_tensor_value_info('present_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) node = onnx.helper.make_node( 'GroupQueryAttention', inputs=[ 'query', 'key', 'value', 'past_key_values_key', 'past_key_values_value', 'seqlens_k', 'total_sequence_length', 'cos_cache', 'sin_cache' ], outputs=['output', 'present_key', 'present_value'], do_rotary=1, kv_num_heads=32, local_window_size=-1, num_heads=32, rotary_interleaved=0, scale=1.0, domain="com.microsoft") return ([node], [query, key, value, past_key_values_key, past_key_values_value], [output, present_key, present_value], [seqlens_k, total_sequence_length, cos_cache, sin_cache]) @onnx_test() def group_query_attention_defaults_test(): qkv = helper.make_tensor_value_info('qkv', TensorProto.FLOAT16, [1, 1, 12288]) key = helper.make_tensor_value_info('key', TensorProto.FLOAT, [1]) value = helper.make_tensor_value_info('value', TensorProto.FLOAT, [1]) past_key_values_key = helper.make_tensor_value_info( 'past_key_values_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) past_key_values_value = helper.make_tensor_value_info( 'past_key_values_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) slk_val = np.array([1]) seqlens_k = helper.make_tensor(name="seqlens_k", data_type=TensorProto.INT32, dims=slk_val.shape, vals=slk_val.astype(int)) tsl_val = np.array([2]) total_sequence_length = helper.make_tensor(name="total_sequence_length", data_type=TensorProto.INT32, dims=tsl_val.shape, vals=tsl_val.astype(int)) cc_val = np.ones([4096, 64], dtype=np.float16) cos_cache = helper.make_tensor(name="cos_cache", data_type=TensorProto.FLOAT16, dims=cc_val.shape, vals=cc_val) sin_cache = helper.make_tensor(name="sin_cache", data_type=TensorProto.FLOAT16, dims=cc_val.shape, vals=cc_val) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 1, 4096]) present_key = helper.make_tensor_value_info('present_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) present_value = helper.make_tensor_value_info('present_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) node = onnx.helper.make_node( 'GroupQueryAttention', inputs=[ 'qkv', 'key', 'value', 'past_key_values_key', 'past_key_values_value', 'seqlens_k', 'total_sequence_length', 'cos_cache', 'sin_cache' ], outputs=['output', 'present_key', 'present_value'], domain="com.microsoft") return ([node ], [qkv, key, value, past_key_values_key, past_key_values_value], [output, present_key, present_value], [seqlens_k, total_sequence_length, cos_cache, sin_cache]) @onnx_test() def group_query_attention_invalid_test(): qkv = helper.make_tensor_value_info('qkv', TensorProto.FLOAT16, [1, 1, 12288]) past_key_values_key = helper.make_tensor_value_info( 'past_key_values_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) past_key_values_value = helper.make_tensor_value_info( 'past_key_values_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) slk_val = np.array([1]) seqlens_k = helper.make_tensor(name="seqlens_k", data_type=TensorProto.INT32, dims=slk_val.shape, vals=slk_val.astype(int)) tsl_val = np.array([2]) total_sequence_length = helper.make_tensor(name="total_sequence_length", data_type=TensorProto.INT32, dims=tsl_val.shape, vals=tsl_val.astype(int)) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 1, 4096]) present_key = helper.make_tensor_value_info('present_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) present_value = helper.make_tensor_value_info('present_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) node = onnx.helper.make_node( 'GroupQueryAttention', inputs=[ 'qkv', 'past_key_values_key', 'past_key_values_value', 'seqlens_k', 'total_sequence_length', 'cos_cache', 'sin_cache' ], outputs=['output', 'present_key', 'present_value'], do_rotary=1, kv_num_heads=32, local_window_size=-1, num_heads=32, rotary_interleaved=0, scale=1.0, domain="com.microsoft") return ([node], [qkv, past_key_values_key, past_key_values_value], [output, present_key, present_value], [seqlens_k, total_sequence_length]) @onnx_test() def group_query_attention_softcap_test(): qkv = helper.make_tensor_value_info('qkv', TensorProto.FLOAT16, [1, 1, 12288]) key = helper.make_tensor_value_info('key', TensorProto.FLOAT, [1]) value = helper.make_tensor_value_info('value', TensorProto.FLOAT, [1]) past_key_values_key = helper.make_tensor_value_info( 'past_key_values_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) past_key_values_value = helper.make_tensor_value_info( 'past_key_values_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) slk_val = np.array([1]) seqlens_k = helper.make_tensor(name="seqlens_k", data_type=TensorProto.INT32, dims=slk_val.shape, vals=slk_val.astype(int)) tsl_val = np.array([2]) total_sequence_length = helper.make_tensor(name="total_sequence_length", data_type=TensorProto.INT32, dims=tsl_val.shape, vals=tsl_val.astype(int)) cc_val = np.ones([4096, 64], dtype=np.float16) cos_cache = helper.make_tensor(name="cos_cache", data_type=TensorProto.FLOAT16, dims=cc_val.shape, vals=cc_val) sin_cache = helper.make_tensor(name="sin_cache", data_type=TensorProto.FLOAT16, dims=cc_val.shape, vals=cc_val) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 1, 4096]) present_key = helper.make_tensor_value_info('present_key', TensorProto.FLOAT16, [1, 32, 4096, 128]) present_value = helper.make_tensor_value_info('present_value', TensorProto.FLOAT16, [1, 32, 4096, 128]) node = onnx.helper.make_node( 'GroupQueryAttention', inputs=[ 'qkv', 'key', 'value', 'past_key_values_key', 'past_key_values_value', 'seqlens_k', 'total_sequence_length', 'cos_cache', 'sin_cache' ], outputs=['output', 'present_key', 'present_value'], softcap=1.0, domain="com.microsoft") return ([node ], [qkv, key, value, past_key_values_key, past_key_values_value], [output, present_key, present_value], [seqlens_k, total_sequence_length, cos_cache, sin_cache]) @onnx_test() def gru_bi_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [2, 60, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [2, 60, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [2, 120]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 2, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 2, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 2, 20]) node = onnx.helper.make_node( 'GRU', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0'], outputs=['hs', 'output'], activations=['tanh', 'sigmoid', 'relu', 'tanh'], clip=0, direction='bidirectional', hidden_size=20, layout=1) return ([node], [seq, w, r, bias, seq_len, h0], [hs, output]) @onnx_test() def gru_bi_5arg_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [2, 60, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [2, 60, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [2, 120]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 2, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 2, 20]) node = onnx.helper.make_node( 'GRU', inputs=['seq', 'w', 'r', 'bias', 'seq_len'], outputs=['hs', 'output'], activations=['tanh', 'sigmoid', 'relu', 'tanh'], clip=0, direction='bidirectional', hidden_size=20, linear_before_reset=1, layout=1) return ([node], [seq, w, r, bias, seq_len], [hs, output]) @onnx_test() def gru_f_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 60, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 60, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 120]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 1, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node( 'GRU', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0'], outputs=['hs', 'output'], activations=['tanh', 'sigmoid'], clip=0, direction='forward', hidden_size=20, linear_before_reset=1, layout=1) return ([node], [seq, w, r, bias, seq_len, h0], [hs, output]) @onnx_test() def gru_f_3arg_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 60, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 60, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node('GRU', inputs=['seq', 'w', 'r'], outputs=['hs', 'output'], activations=['tanh', 'sigmoid'], clip=0, direction='forward', hidden_size=20, layout=1) return ([node], [seq, w, r], [hs, output]) @onnx_test() def gru_f_1af_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [5, 3, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 60, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 60, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [5, 1, 3, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 3, 20]) node = onnx.helper.make_node('GRU', inputs=['seq', 'w', 'r'], outputs=['hs', 'output'], activations=['tanh'], clip=0, direction='forward', hidden_size=20) return ([node], [seq, w, r], [hs, output]) @onnx_test() def gru_r_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 60, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 60, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 120]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 1, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node( 'GRU', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0'], outputs=['hs', 'output'], activations=['tanh', 'sigmoid'], clip=0, direction='reverse', hidden_size=20, layout=1) return ([node], [seq, w, r, bias, seq_len, h0], [hs, output]) @onnx_test() def gru_r_4arg_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 60, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 60, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 120]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node('GRU', inputs=['seq', 'w', 'r', 'bias'], outputs=['hs', 'output'], activations=['relu', 'tanh'], clip=0, direction='reverse', hidden_size=20, linear_before_reset=1, layout=1) return ([node], [seq, w, r, bias], [hs, output]) @onnx_test() def hardmax_default_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 2, 3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 3, 4]) node = onnx.helper.make_node('Hardmax', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def hardmax_axis_test(): x = helper.make_tensor_value_info('x', TensorProto.DOUBLE, [1, 2, 3, 4]) y = helper.make_tensor_value_info('y', TensorProto.DOUBLE, [1, 2, 3, 4]) node = onnx.helper.make_node('Hardmax', inputs=['x'], outputs=['y'], axis=2) return ([node], [x], [y]) @onnx_test() def hardmax_axis_neg_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [1, 2, 3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [1, 2, 3, 4]) node = onnx.helper.make_node('Hardmax', inputs=['x'], outputs=['y'], axis=-3) return ([node], [x], [y]) @onnx_test() def hardsigmoid_default_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 3, 4, 5]) node = onnx.helper.make_node('HardSigmoid', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def hardsigmoid_double_test(): x = helper.make_tensor_value_info('x', TensorProto.DOUBLE, [1, 3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.DOUBLE, [1, 3, 4, 5]) node = onnx.helper.make_node('HardSigmoid', inputs=['x'], outputs=['y'], alpha=0.3, beta=0.7) return ([node], [x], [y]) @onnx_test() def hardsigmoid_half_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [1, 3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [1, 3, 4, 5]) node = onnx.helper.make_node('HardSigmoid', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def hardsigmoid_bf16_test(): x = helper.make_tensor_value_info('x', TensorProto.BFLOAT16, [1, 3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.BFLOAT16, [1, 3, 4, 5]) node = onnx.helper.make_node('HardSigmoid', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def hardsigmoid_verify_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 5]) node = onnx.helper.make_node('HardSigmoid', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def hardswish_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 5]) node = onnx.helper.make_node('HardSwish', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def if_else_test(): x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [2, 3]) then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [2, 3]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [2, 3]) xt = np.ones((2, 3)).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt = np.random.randn(2, 3).astype(np.float) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['then_out']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['else_out']) then_body = onnx.helper.make_graph([then_add_node], 'then_body', [], [then_out]) else_body = onnx.helper.make_graph([else_mul_node], 'else_body', [], [else_out]) cond_tensor = onnx.helper.make_tensor_value_info("cond", onnx.TensorProto.BOOL, [1]) res = onnx.helper.make_tensor_value_info('res', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['res'], then_branch=then_body, else_branch=else_body) return ([node], [x, y, cond_tensor], [res], [xt_tensor, yt_tensor]) @onnx_test() def if_else_test_inlined(): x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [2, 3]) then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [2, 3]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [2, 3]) xt = np.ones((2, 3)).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt = np.random.randn(2, 3).astype(np.float) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['then_out']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['else_out']) then_body = onnx.helper.make_graph([then_add_node], 'then_body', [], [then_out]) else_body = onnx.helper.make_graph([else_mul_node], 'else_body', [], [else_out]) cond = np.array([0]).astype(bool) cond_tensor = helper.make_tensor(name="cond", data_type=TensorProto.BOOL, dims=cond.shape, vals=cond.astype(bool)) res = onnx.helper.make_tensor_value_info('res', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['res'], then_branch=then_body, else_branch=else_body) return ([node], [x, y], [res], [cond_tensor, xt_tensor, yt_tensor]) @onnx_test() def if_then_else_multi_output_shapes_inlined_test(): x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3, 1]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [2, 3]) then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [2, 3, 1]) then_out2 = onnx.helper.make_tensor_value_info('then_out2', onnx.TensorProto.FLOAT, [2, 3, 1]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [2, 3]) else_out2 = onnx.helper.make_tensor_value_info('else_out2', onnx.TensorProto.FLOAT, [2, 3]) xt = np.ones((2, 3, 1)).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt = np.random.randn(2, 3).astype(np.float) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['then_out']) then_add_node2 = onnx.helper.make_node('Add', inputs=['x', 'x'], outputs=['then_out2']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['else_out']) else_sub_node = onnx.helper.make_node('Sub', inputs=['y', 'yt'], outputs=['else_out2']) then_body = onnx.helper.make_graph([then_add_node, then_add_node2], 'then_body', [], [then_out, then_out2]) else_body = onnx.helper.make_graph([else_mul_node, else_sub_node], 'else_body', [], [else_out, else_out2]) cond = np.array([1]).astype(bool) cond_tensor = helper.make_tensor(name="cond", data_type=TensorProto.BOOL, dims=cond.shape, vals=cond.astype(bool)) res1 = onnx.helper.make_tensor_value_info('res1', TensorProto.FLOAT, []) res2 = onnx.helper.make_tensor_value_info('res2', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['res1', 'res2'], then_branch=then_body, else_branch=else_body) return ([node], [x, y], [res1, res2], [cond_tensor, xt_tensor, yt_tensor]) @onnx_test() def if_then_else_multi_output_shapes_test(): x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3, 1]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [2, 3, 1]) then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [2, 3, 1]) then_out2 = onnx.helper.make_tensor_value_info('then_out2', onnx.TensorProto.FLOAT, [2, 3, 1]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [2, 3, 1]) else_out2 = onnx.helper.make_tensor_value_info('else_out2', onnx.TensorProto.FLOAT, [2, 3, 1]) xt = np.ones((2, 3, 1)).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt = np.random.randn(2, 3, 1).astype(np.float) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['then_out']) then_add_node2 = onnx.helper.make_node('Add', inputs=['x', 'x'], outputs=['then_out2']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['else_out']) else_sub_node = onnx.helper.make_node('Sub', inputs=['y', 'yt'], outputs=['else_out2']) then_body = onnx.helper.make_graph([then_add_node, then_add_node2], 'then_body', [], [then_out, then_out2]) else_body = onnx.helper.make_graph([else_mul_node, else_sub_node], 'else_body', [], [else_out, else_out2]) cond_tensor = onnx.helper.make_tensor_value_info("cond", onnx.TensorProto.BOOL, [1]) res1 = onnx.helper.make_tensor_value_info('res1', TensorProto.FLOAT, []) res2 = onnx.helper.make_tensor_value_info('res2', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['res1', 'res2'], then_branch=then_body, else_branch=else_body) return ([node], [x, y, cond_tensor], [res1, res2], [xt_tensor, yt_tensor]) @onnx_test() def if_literal_test(): then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [5]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [5]) x = np.array([1, 2, 3, 4, 5]).astype(np.float32) y = np.array([5, 4, 3, 2, 1]).astype(np.float32) z = np.array([]).astype(np.float32) then_const_node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['then_out'], value=onnx.numpy_helper.from_array(x)) else_const_node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['else_out'], value=onnx.numpy_helper.from_array(y)) empty_const_node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['empty_out'], value=onnx.numpy_helper.from_array(z)) then_body = onnx.helper.make_graph([then_const_node, empty_const_node], 'then_body', [], [then_out]) else_body = onnx.helper.make_graph([else_const_node, empty_const_node], 'else_body', [], [else_out]) cond_input = onnx.helper.make_tensor_value_info('cond', onnx.TensorProto.BOOL, []) ret = onnx.helper.make_tensor_value_info('ret', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['ret'], then_branch=then_body, else_branch=else_body) return ([node], [cond_input], [ret]) @onnx_test() def if_param_excp_test(): then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [2, 3]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [2, 3]) x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [2, 4]) yt = np.random.randn(2, 4).astype(np.float) xt = np.random.randn(2, 3).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['then_out']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['else_out']) then_body = onnx.helper.make_graph([then_add_node], 'then_body', [], [then_out], [xt_tensor]) else_body = onnx.helper.make_graph([else_mul_node], 'else_body', [], [else_out], [yt_tensor]) cond_input = onnx.helper.make_tensor_value_info('cond', onnx.TensorProto.BOOL, []) ret = onnx.helper.make_tensor_value_info('ret', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['ret'], then_branch=then_body, else_branch=else_body) return ([node], [cond_input, x, y], [ret]) @onnx_test() def if_param_excp1_test(): then_out = onnx.helper.make_tensor_value_info('sub_out', onnx.TensorProto.FLOAT, [2, 3]) x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3]) xt = np.random.randn(2, 3).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['sub_out']) sub_body = onnx.helper.make_graph([then_add_node], 'sub_body', [], [then_out], [xt_tensor]) cond_input = onnx.helper.make_tensor_value_info('cond', onnx.TensorProto.BOOL, [2]) ret = onnx.helper.make_tensor_value_info('ret', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['ret'], then_branch=sub_body, else_branch=sub_body) return ([node], [cond_input, x], [ret]) @onnx_test() def if_param_test(): then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [2, 3]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [2, 3]) x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [2, 3]) yt = np.random.randn(2, 3).astype(np.float) xt = np.random.randn(2, 3).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['then_out']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['else_out']) then_body = onnx.helper.make_graph([then_add_node], 'then_body', [], [then_out], [xt_tensor]) else_body = onnx.helper.make_graph([else_mul_node], 'else_body', [], [else_out], [yt_tensor]) cond_input = onnx.helper.make_tensor_value_info('cond', onnx.TensorProto.BOOL, []) ret = onnx.helper.make_tensor_value_info('ret', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['ret'], then_branch=then_body, else_branch=else_body) return ([node], [cond_input, x, y], [ret]) @onnx_test() def if_pl_test(): out_x = onnx.helper.make_tensor_value_info('out_x', onnx.TensorProto.FLOAT, [2, 3]) out_l_x = onnx.helper.make_tensor_value_info('out_l_x', onnx.TensorProto.FLOAT, [2, 3]) out_y = onnx.helper.make_tensor_value_info('out_y', onnx.TensorProto.FLOAT, [3, 3]) out_l_y = onnx.helper.make_tensor_value_info('out_l_y', onnx.TensorProto.FLOAT, [3, 3]) x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [3, 3]) xt = np.array([[1, 2, 3], [4, 5, 6]]).astype(np.float32) yt = np.array([[8, 7, 6], [5, 4, 3], [2, 1, 0]]).astype(np.float32) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['out_x']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['out_y']) then_const_node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['out_l_y'], value=onnx.numpy_helper.from_array(yt)) else_const_node = onnx.helper.make_node( 'Constant', inputs=[], outputs=['out_l_x'], value=onnx.numpy_helper.from_array(xt)) then_body = onnx.helper.make_graph([then_add_node, then_const_node], 'then_body', [], [out_x, out_l_y]) else_body = onnx.helper.make_graph([else_mul_node, else_const_node], 'else_body', [], [out_l_x, out_y]) cond_input = onnx.helper.make_tensor_value_info('cond', onnx.TensorProto.BOOL, []) ret = onnx.helper.make_tensor_value_info('ret', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['ret'], then_branch=then_body, else_branch=else_body) return ([node], [cond_input, x, y], [ret], [xt_tensor, yt_tensor]) @onnx_test() def if_then_test(): x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [2, 3]) then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [2, 3]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [2, 3]) xt = np.ones((2, 3)).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt = np.random.randn(2, 3).astype(np.float) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['then_out']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['else_out']) then_body = onnx.helper.make_graph([then_add_node], 'then_body', [], [then_out]) else_body = onnx.helper.make_graph([else_mul_node], 'else_body', [], [else_out]) cond_tensor = onnx.helper.make_tensor_value_info("cond", onnx.TensorProto.BOOL, [1]) res = onnx.helper.make_tensor_value_info('res', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['res'], then_branch=then_body, else_branch=else_body) return ([node], [x, y, cond_tensor], [res], [xt_tensor, yt_tensor]) @onnx_test() def if_then_test_inlined(): x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [2, 3]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [2, 3]) then_out = onnx.helper.make_tensor_value_info('then_out', onnx.TensorProto.FLOAT, [2, 3]) else_out = onnx.helper.make_tensor_value_info('else_out', onnx.TensorProto.FLOAT, [2, 3]) xt = np.ones((2, 3)).astype(np.float) xt_tensor = helper.make_tensor(name='xt', data_type=TensorProto.FLOAT, dims=xt.shape, vals=xt.flatten().astype(np.float32)) yt = np.random.randn(2, 3).astype(np.float) yt_tensor = helper.make_tensor(name='yt', data_type=TensorProto.FLOAT, dims=yt.shape, vals=yt.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'xt'], outputs=['then_out']) else_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'yt'], outputs=['else_out']) then_body = onnx.helper.make_graph([then_add_node], 'then_body', [], [then_out]) else_body = onnx.helper.make_graph([else_mul_node], 'else_body', [], [else_out]) cond = np.array([1]).astype(bool) cond_tensor = helper.make_tensor(name="cond", data_type=TensorProto.BOOL, dims=cond.shape, vals=cond.astype(bool)) res = onnx.helper.make_tensor_value_info('res', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['res'], then_branch=then_body, else_branch=else_body) return ([node], [x, y], [res], [cond_tensor, xt_tensor, yt_tensor]) @onnx_test() def if_tuple_test(): x = onnx.helper.make_tensor_value_info('x', onnx.TensorProto.FLOAT, [1, 4]) y = onnx.helper.make_tensor_value_info('y', onnx.TensorProto.FLOAT, [3, 4]) cond_input = onnx.helper.make_tensor_value_info('cond', onnx.TensorProto.BOOL, []) then_out0 = onnx.helper.make_tensor_value_info('then_out0', onnx.TensorProto.FLOAT, [1, 4]) then_out1 = onnx.helper.make_tensor_value_info('then_out1', onnx.TensorProto.FLOAT, [3, 4]) else_out0 = onnx.helper.make_tensor_value_info('else_out0', onnx.TensorProto.FLOAT, [1, 4]) else_out1 = onnx.helper.make_tensor_value_info('else_out1', onnx.TensorProto.FLOAT, [3, 4]) one = np.ones([1]).astype(np.float) one_tensor = helper.make_tensor(name='one', data_type=TensorProto.FLOAT, dims=one.shape, vals=one.flatten().astype(np.float32)) two = np.array([2]).astype(np.float) two_tensor = helper.make_tensor(name='two', data_type=TensorProto.FLOAT, dims=two.shape, vals=two.flatten().astype(np.float32)) three = np.array([3]).astype(np.float) three_tensor = helper.make_tensor(name='three', data_type=TensorProto.FLOAT, dims=three.shape, vals=three.flatten().astype(np.float32)) then_add_node = onnx.helper.make_node('Add', inputs=['x', 'one'], outputs=['then_out0']) then_mul_node = onnx.helper.make_node('Mul', inputs=['y', 'two'], outputs=['then_out1']) else_mul_node = onnx.helper.make_node('Mul', inputs=['x', 'three'], outputs=['else_out0']) else_add_node = onnx.helper.make_node('Add', inputs=['y', 'three'], outputs=['else_out1']) then_body = onnx.helper.make_graph([then_add_node, then_mul_node], 'then_body', [], [then_out0, then_out1]) else_body = onnx.helper.make_graph([else_mul_node, else_add_node], 'else_body', [], [else_out0, else_out1]) res0 = onnx.helper.make_tensor_value_info('res0', TensorProto.FLOAT, []) res1 = onnx.helper.make_tensor_value_info('res1', TensorProto.FLOAT, []) node = onnx.helper.make_node('If', inputs=['cond'], outputs=['res0', 'res1'], then_branch=then_body, else_branch=else_body) return ([node], [cond_input, x, y], [res0, res1], [one_tensor, two_tensor, three_tensor]) @onnx_test() def imagescaler_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 16, 16]) node = onnx.helper.make_node('ImageScaler', inputs=['0'], outputs=['1'], bias=[0.01, 0.02, 0.03], scale=0.5) return ([node], [x], [y]) @onnx_test() def imagescaler_half_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [1, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT16, [1, 3, 16, 16]) node = onnx.helper.make_node('ImageScaler', inputs=['0'], outputs=['1'], bias=[0.01, 0.02, 0.03], scale=0.5) return ([node], [x], [y]) @onnx_test() def imagescaler_bf16_test(): x = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [1, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.BFLOAT16, [1, 3, 16, 16]) node = onnx.helper.make_node('ImageScaler', inputs=['0'], outputs=['1'], bias=[0.01, 0.02, 0.03], scale=0.5) return ([node], [x], [y]) @onnx_test() def implicit_add_bcast_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 4, 1]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Add', inputs=['0', '1'], outputs=['2'], ) return ([node], [x, y], [z]) @onnx_test() def implicit_pow_bcast_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 4, 1]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Pow', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def implicit_sub_bcast_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.UINT64, [2, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.UINT64, [4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.UINT64, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Sub', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def initializer_not_an_input(): values = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]) w = helper.make_tensor(name='w', data_type=TensorProto.FLOAT, dims=values.shape, vals=values.flatten().astype(np.float)) x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [5, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5, 4]) node = onnx.helper.make_node( 'Gemm', inputs=['x', 'w'], outputs=['y'], ) return ([node], [x], [y], [w]) @onnx_test() def instance_norm_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2]) bias = helper.make_tensor_value_info('2', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_half_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [1, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.FLOAT16, [2]) bias = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [2]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [1, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_bf16_test(): x = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [1, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.BFLOAT16, [2]) bias = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [2]) y = helper.make_tensor_value_info('3', TensorProto.BFLOAT16, [1, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_type_mismatch_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.FLOAT16, [2]) bias = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [2]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_dyn_batch_test(): # the batch size is a dynamic dimension x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2]) bias = helper.make_tensor_value_info('2', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT, [None, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_dyn_batch_half_test(): # the batch size is a dynamic dimension x = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [None, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.FLOAT16, [2]) bias = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [2]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [None, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_dyn_batch_bf16_test(): # the batch size is a dynamic dimension x = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [None, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.BFLOAT16, [2]) bias = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [2]) y = helper.make_tensor_value_info('3', TensorProto.BFLOAT16, [None, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_invalid_type_test(): x = helper.make_tensor_value_info('0', TensorProto.INT32, [1, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2]) bias = helper.make_tensor_value_info('2', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_nonbroadcastable_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 2, 3, 3]) scale = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4]) bias = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1, 2, 3, 3]) node = onnx.helper.make_node('InstanceNormalization', inputs=['0', '1', '2'], outputs=['3']) return ([node], [x, scale, bias], [y]) @onnx_test() def instance_norm_val_test(): x = np.array([[[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [[0, 1, 2], [3, 4, 5], [6, 7, 8]]]]) scale = np.array([1, 2]) bias = np.array([0, 1]) x_tensor = helper.make_tensor(name='x_tensor', data_type=TensorProto.FLOAT, dims=x.shape, vals=x.flatten().astype(np.float)) scale_tensor = helper.make_tensor(name='scale_tensor', data_type=TensorProto.FLOAT, dims=scale.shape, vals=scale.flatten().astype(np.float)) bias_tensor = helper.make_tensor(name='bias_tensor', data_type=TensorProto.FLOAT, dims=bias.shape, vals=bias.flatten().astype(np.float)) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 3, 3]) node = onnx.helper.make_node( 'InstanceNormalization', inputs=['x_tensor', 'scale_tensor', 'bias_tensor'], outputs=['y']) return ([node], [], [y], [x_tensor, scale_tensor, bias_tensor]) @onnx_test() def instance_norm_val_3d_test(): x = np.array([[[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[0, 1], [2, 3]], [[4, 5], [6, 7]]]]]) scale = np.array([1, 2]) bias = np.array([0, 1]) x_tensor = helper.make_tensor(name='x_tensor', data_type=TensorProto.FLOAT, dims=x.shape, vals=x.flatten().astype(np.float)) scale_tensor = helper.make_tensor(name='scale_tensor', data_type=TensorProto.FLOAT, dims=scale.shape, vals=scale.flatten().astype(np.float)) bias_tensor = helper.make_tensor(name='bias_tensor', data_type=TensorProto.FLOAT, dims=bias.shape, vals=bias.flatten().astype(np.float)) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2, 2, 2, 2]) node = onnx.helper.make_node( 'InstanceNormalization', inputs=['x_tensor', 'scale_tensor', 'bias_tensor'], outputs=['y']) return ([node], [], [y], [x_tensor, scale_tensor, bias_tensor]) @onnx_test() def int4_const_identity_qdq_test(): # Graph for int4, with an identity opr + QDQ zp_values = np.array([0, 0, 0, 0]) x_t = helper.make_tensor(name='i_x', data_type=TensorProto.INT4, dims=zp_values.shape, vals=zp_values.flatten().astype(np.int32)) i_node = onnx.helper.make_node( 'Identity', inputs=['i_x'], outputs=['i_y_zp'], ) data_values = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) data_t = helper.make_tensor(name='data', data_type=TensorProto.FLOAT16, dims=data_values.shape, vals=data_values.flatten().astype(np.float16)) sc_values = np.array([1.0, 0.5, 1.0, 0.25]) sc_t = helper.make_tensor(name='sc_q', data_type=TensorProto.FLOAT16, dims=sc_values.shape, vals=sc_values.flatten().astype(np.float16)) q_node = onnx.helper.make_node( 'QuantizeLinear', inputs=['data', 'sc_q', 'i_y_zp'], outputs=['q_y'], ) #dequantizer uses same scale values as the quantizer: dq_node = onnx.helper.make_node( 'DequantizeLinear', inputs=['q_y', 'sc_q', 'i_y_zp'], outputs=['dq_y'], ) t_node = helper.make_node( 'Transpose', inputs=['dq_y'], outputs=['t_y'], perm=[1, 0], ) x2_t = helper.make_tensor_value_info('x2', TensorProto.FLOAT16, [4, 4]) dot_node = helper.make_node( 'MatMul', inputs=['t_y', 'x2'], outputs=['y'], ) y_t = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [4, 4]) return ([i_node, q_node, dq_node, t_node, dot_node], [x2_t], [y_t], [x_t, data_t, sc_t]) @onnx_test() def int4_const_identity_block_sz_1_qdq_test(): # Graph for int4, with an identity opr + QDQ. Quantization Block size = 1 zp_values = np.array([[0, 0, 0, 0], [0, 0, 0, 0]]) x_t = helper.make_tensor(name='i_x', data_type=TensorProto.INT4, dims=zp_values.shape, vals=zp_values.flatten().astype(np.int32)) i_node = onnx.helper.make_node( 'Identity', inputs=['i_x'], outputs=['i_y_zp'], ) data_values = np.array([[1, 0, 0, 0], [0, 1, 0, 0]]) data_t = helper.make_tensor(name='data', data_type=TensorProto.FLOAT16, dims=data_values.shape, vals=data_values.flatten().astype(np.float16)) sc_values = np.array([[0.5, 0.25, 0.5, 0.125], [0.25, 0.5, 0.5, 0.25]]) sc_t = helper.make_tensor(name='sc_q', data_type=TensorProto.FLOAT16, dims=sc_values.shape, vals=sc_values.flatten().astype(np.float16)) q_node = onnx.helper.make_node( 'QuantizeLinear', inputs=['data', 'sc_q', 'i_y_zp'], outputs=['q_y'], ) # dequantizer uses same scale values as the quantizer: dq_node = onnx.helper.make_node( 'DequantizeLinear', inputs=['q_y', 'sc_q', 'i_y_zp'], outputs=['dq_y'], ) t_node = helper.make_node( 'Transpose', inputs=['dq_y'], outputs=['t_y'], perm=[1, 0], ) x2_t = helper.make_tensor_value_info('x2', TensorProto.FLOAT16, [2, 4]) dot_node = helper.make_node( 'MatMul', inputs=['t_y', 'x2'], outputs=['y'], ) y_t = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [4, 4]) return ([i_node, q_node, dq_node, t_node, dot_node], [x2_t], [y_t], [x_t, data_t, sc_t]) @onnx_test() def int4_const_identity_block_sz_2_qdq_test(): # Graph for int4, with an identity opr + QDQ. Quantization Block size = 2 zp_values = np.array([[0, 0], [0, 0]]) x_t = helper.make_tensor(name='i_x', data_type=TensorProto.INT4, dims=zp_values.shape, vals=zp_values.flatten().astype(np.int32)) i_node = onnx.helper.make_node( 'Identity', inputs=['i_x'], outputs=['i_y_zp'], ) data_values = np.array([[0, 0, 1, 0], [0, 0, 0, 1]]) data_t = helper.make_tensor(name='data', data_type=TensorProto.FLOAT16, dims=data_values.shape, vals=data_values.flatten().astype(np.float16)) sc_values = np.array([[0.5, 0.125], [0.5, 0.25]]) sc_t = helper.make_tensor(name='sc_q', data_type=TensorProto.FLOAT16, dims=sc_values.shape, vals=sc_values.flatten().astype(np.float16)) q_node = onnx.helper.make_node( 'QuantizeLinear', inputs=['data', 'sc_q', 'i_y_zp'], outputs=['q_y'], ) # dequantizer uses same scale values as the quantizer: dq_node = onnx.helper.make_node( 'DequantizeLinear', inputs=['q_y', 'sc_q', 'i_y_zp'], outputs=['dq_y'], ) t_node = helper.make_node( 'Transpose', inputs=['dq_y'], outputs=['t_y'], perm=[1, 0], ) x2_t = helper.make_tensor_value_info('x2', TensorProto.FLOAT16, [2, 4]) dot_node = helper.make_node( 'MatMul', inputs=['t_y', 'x2'], outputs=['y'], ) y_t = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [4, 4]) return ([i_node, q_node, dq_node, t_node, dot_node], [x2_t], [y_t], [x_t, data_t, sc_t]) @onnx_test() def isinf_half_test(): t1 = helper.make_tensor_value_info('t1', TensorProto.FLOAT16, [2, 3]) t2 = helper.make_tensor_value_info('t2', TensorProto.BOOL, [2, 3]) node = onnx.helper.make_node( 'IsInf', inputs=['t1'], outputs=['t2'], ) return ([node], [t1], [t2]) @onnx_test() def isinf_bf16_test(): t1 = helper.make_tensor_value_info('t1', TensorProto.BFLOAT16, [2, 3]) t2 = helper.make_tensor_value_info('t2', TensorProto.BOOL, [2, 3]) node = onnx.helper.make_node( 'IsInf', inputs=['t1'], outputs=['t2'], ) return ([node], [t1], [t2]) @onnx_test() def isinf_neg_test(): t1 = helper.make_tensor_value_info('t1', TensorProto.FLOAT, [2, 3]) t2 = helper.make_tensor_value_info('t2', TensorProto.BOOL, [2, 3]) node = onnx.helper.make_node( 'IsInf', detect_negative=[1], detect_positive=[0], inputs=['t1'], outputs=['t2'], ) return ([node], [t1], [t2]) @onnx_test() def isinf_double_pos_test(): t1 = helper.make_tensor_value_info('t1', TensorProto.DOUBLE, [2, 3]) t2 = helper.make_tensor_value_info('t2', TensorProto.BOOL, [2, 3]) node = onnx.helper.make_node( 'IsInf', detect_negative=[0], detect_positive=[1], inputs=['t1'], outputs=['t2'], ) return ([node], [t1], [t2]) @onnx_test() def isinf_no_detect_test(): t1 = helper.make_tensor_value_info('t1', TensorProto.FLOAT, [2, 3]) t2 = helper.make_tensor_value_info('t2', TensorProto.BOOL, [2, 3]) node = onnx.helper.make_node( 'IsInf', detect_negative=[0], detect_positive=[0], inputs=['t1'], outputs=['t2'], ) return ([node], [t1], [t2]) @onnx_test() def isnan_float_test(): t1 = helper.make_tensor_value_info('t1', TensorProto.FLOAT, [2, 3]) t2 = helper.make_tensor_value_info('t2', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node( 'IsNaN', inputs=['t1'], outputs=['t2'], ) return ([node], [t1], [t2]) @onnx_test() def isnan_half_test(): t1 = helper.make_tensor_value_info('t1', TensorProto.FLOAT16, [2, 3]) t2 = helper.make_tensor_value_info('t2', TensorProto.FLOAT16, [2, 3]) node = onnx.helper.make_node( 'IsNaN', inputs=['t1'], outputs=['t2'], ) return ([node], [t1], [t2]) @onnx_test() def isnan_bf16_test(): t1 = helper.make_tensor_value_info('t1', TensorProto.BFLOAT16, [2, 3]) t2 = helper.make_tensor_value_info('t2', TensorProto.BFLOAT16, [2, 3]) node = onnx.helper.make_node( 'IsNaN', inputs=['t1'], outputs=['t2'], ) return ([node], [t1], [t2]) @onnx_test() def layernorm_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 1, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 1, 5]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [5]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [5]) axes = [2] pow_2 = np.array([[[2, 2, 2, 2, 2]]]) epsilon = np.array([1e-12]) pow_tensor = helper.make_tensor(name='pow', data_type=TensorProto.FLOAT, dims=pow_2.shape, vals=pow_2.flatten().astype(np.float)) epsilon_tensor = helper.make_tensor(name='epsilon', data_type=TensorProto.FLOAT, dims=epsilon.shape, vals=epsilon.flatten().astype( np.float)) mean = onnx.helper.make_node('ReduceMean', inputs=['0'], outputs=['mean_out'], axes=axes) sub_mean = onnx.helper.make_node('Sub', inputs=['0', 'mean_out'], outputs=['sub_out']) sub_pow = onnx.helper.make_node('Pow', inputs=['sub_out', 'pow'], outputs=['pow_out']) var = onnx.helper.make_node('ReduceMean', inputs=['pow_out'], outputs=['var_out'], axes=axes) add = onnx.helper.make_node('Add', inputs=['var_out', 'epsilon'], outputs=['add_out']) sqrt = onnx.helper.make_node('Sqrt', inputs=['add_out'], outputs=['sqrt_out']) div = onnx.helper.make_node('Div', inputs=['sub_out', 'sqrt_out'], outputs=['div_out']) mul = onnx.helper.make_node('Mul', inputs=['scale', 'div_out'], outputs=['mul_out']) bias_add = onnx.helper.make_node('Add', inputs=['mul_out', 'bias'], outputs=['1']) return ([mean, sub_mean, sub_pow, var, add, sqrt, div, mul, bias_add], [x, scale, bias], [y], [pow_tensor, epsilon_tensor]) def make_layer_norm(shape, axis=-1, dtype=TensorProto.FLOAT, scale_shape=None, bias_shape=None, stash_type=None, epsilon=None, scale_type=None, bias_type=None): if scale_type is None: scale_type = dtype if bias_type is None: bias_type = dtype norm_axis = axis + len(shape) if axis < 0 else axis x = helper.make_tensor_value_info('x', dtype, shape) if scale_shape is None: scale_shape = shape[norm_axis:] if bias_shape is None: bias_shape = shape[norm_axis:] scale = helper.make_tensor_value_info('scale', scale_type, scale_shape) bias = helper.make_tensor_value_info('bias', bias_type, bias_shape) y = helper.make_tensor_value_info('y', dtype, shape) node = onnx.helper.make_node('LayerNormalization', inputs=['x', 'scale', 'bias'], outputs=['y']) # Attributes node.attribute.append(onnx.helper.make_attribute("axis", axis)) if stash_type is not None: node.attribute.append(onnx.helper.make_attribute("stash_type", stash_type)) if epsilon is not None: node.attribute.append(onnx.helper.make_attribute("epsilon", epsilon)) return ([node], [x, scale, bias], [y]) @onnx_test() def layer_norm_invalid_shape_error_test(): return make_layer_norm([3], 0) @onnx_test() def layer_norm_2d_axis_zero_test(): return make_layer_norm([3, 4], 0) @onnx_test() def layer_norm_2d_axis_one_test(): return make_layer_norm([3, 4], 1) @onnx_test() def layer_norm_2d_axis_minus_one_test(): return make_layer_norm([3, 4], -1) @onnx_test() def layer_norm_3d_test(): return make_layer_norm([1, 4, 2], -1) @onnx_test() def layer_norm_3d_scale_bias_test(): return make_layer_norm([2, 5, 7], scale_shape=[2, 1, 7], bias_shape=[2, 1, 7]) @onnx_test() def layer_norm_3d_invalid_int8_test(): return make_layer_norm([1, 4, 2], -1, TensorProto.INT8) @onnx_test() def layer_norm_3d_invalid_scale_test(): return make_layer_norm([1, 4, 2], -1, scale_type=TensorProto.INT8) @onnx_test() def layer_norm_3d_invalid_bias_test(): return make_layer_norm([1, 4, 2], -1, bias_type=TensorProto.INT8) @onnx_test() def layer_norm_3d_half_test(): return make_layer_norm([1, 4, 2], -1, TensorProto.FLOAT16) @onnx_test() def layer_norm_3d_half_stash_off_test(): return make_layer_norm([1, 4, 2], -1, TensorProto.FLOAT16, stash_type=int(0)) @onnx_test() def layer_norm_3d_half_stash_off_epsilon_test(): return make_layer_norm([1, 4, 2], -1, TensorProto.FLOAT16, stash_type=int(0), epsilon=float(1e-4)) @onnx_test() def layer_norm_3d_bf16_test(): return make_layer_norm([1, 4, 2], -1, TensorProto.BFLOAT16) @onnx_test() def layer_norm_4d_test(): return make_layer_norm([3, 3, 3, 3], -1) @onnx_test() def layer_norm_4d_half_test(): return make_layer_norm([3, 3, 3, 3], -1, TensorProto.FLOAT16) @onnx_test() def layer_norm_4d_bf16_test(): return make_layer_norm([3, 3, 3, 3], -1, TensorProto.BFLOAT16) @onnx_test() def layer_norm_invalid_axis_error_test(): return make_layer_norm([1, 4, 2], 1000) @onnx_test() def layer_norm_invalid_minus_axis_error_test(): return make_layer_norm([1, 4, 2], -1000) @onnx_test() def layer_norm_invalid_input_count_error_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node('LayerNormalization', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def layer_norm_without_bias_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node('LayerNormalization', inputs=['x', 'scale'], outputs=['y']) return ([node], [x, scale], [y]) @onnx_test() def layer_norm_small_eps_half_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [1, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT16, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [1, 2]) node = onnx.helper.make_node('LayerNormalization', inputs=['x', 'scale'], outputs=['y'], epsilon=1e-12) return ([node], [x, scale], [y]) @onnx_test() def layer_norm_small_eps_bf16_test(): x = helper.make_tensor_value_info('x', TensorProto.BFLOAT16, [1, 2]) scale = helper.make_tensor_value_info('scale', TensorProto.BFLOAT16, [2]) y = helper.make_tensor_value_info('y', TensorProto.BFLOAT16, [1, 2]) node = onnx.helper.make_node('LayerNormalization', inputs=['x', 'scale'], outputs=['y'], epsilon=1e-7) return ([node], [x, scale], [y]) @onnx_test() def leaky_relu_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) node = onnx.helper.make_node('LeakyRelu', inputs=['0'], outputs=['1'], alpha=0.01) return ([node], [x], [y]) @onnx_test() def less_test(): ax1 = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) x1 = helper.make_tensor("x1", data_type=TensorProto.FLOAT, dims=(2, 3), vals=ax1.astype(np.float32)) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node( 'Less', inputs=['x1', 'x2'], outputs=['y'], ) return ([node], [x2], [y], [x1]) @onnx_test() def less_bool_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [2, 3]) x2 = helper.make_tensor_value_info('x2', TensorProto.BOOL, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node1 = onnx.helper.make_node('Cast', inputs=['x1'], outputs=['bx1'], to=9) node2 = onnx.helper.make_node( 'Less', inputs=['bx1', 'x2'], outputs=['y'], ) return ([node1, node2], [x1, x2], [y]) @onnx_test() def lessorequal_test(): x1 = helper.make_tensor_value_info('x1', TensorProto.FLOAT, [3]) x2 = helper.make_tensor_value_info('x2', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( 'LessOrEqual', inputs=['x1', 'x2'], outputs=['y'], ) return ([node], [x1, x2], [y]) @onnx_test() def log_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Log', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def logical_and_bcast_test(): x = helper.make_tensor_value_info('0', TensorProto.BOOL, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.BOOL, [4, 5]) z = helper.make_tensor_value_info('2', TensorProto.BOOL, [2, 3, 4, 5]) node = onnx.helper.make_node('And', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [z]) @onnx_test() def logical_or_test(): x = helper.make_tensor_value_info('0', TensorProto.BOOL, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.BOOL, [2, 3, 4, 5]) z = helper.make_tensor_value_info('2', TensorProto.BOOL, [2, 3, 4, 5]) node = onnx.helper.make_node('Or', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [z]) @onnx_test() def logical_xor_bcast_test(): x = helper.make_tensor_value_info('0', TensorProto.BOOL, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.BOOL, [4, 1]) z = helper.make_tensor_value_info('2', TensorProto.BOOL, [2, 3, 4, 5]) node = onnx.helper.make_node('Xor', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [z]) @onnx_test() def logsoftmax_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 5, 6]) node = onnx.helper.make_node('LogSoftmax', inputs=['x'], outputs=['y'], axis=1) return ([node], [x], [y]) @onnx_test() def logsoftmax_nonstd_input_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [6, 9]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3, 4]) node0 = onnx.helper.make_node('Slice', inputs=['0'], axes=[0, 1], starts=[1, 0], ends=[4, 4], outputs=['1']) node1 = onnx.helper.make_node('LogSoftmax', inputs=['1'], outputs=['2'], axis=-1) return ([node0, node1], [x], [y]) @onnx_test() def loop_default_test(): body = helper.make_graph([ helper.make_node("Add", ["a", "b_in"], ["my_local"]), helper.make_node("Sub", ["a", "b_in"], ["a_sub_b_in"]), helper.make_node("Greater", ["my_local", "a_sub_b_in"], ["keep_going"]), helper.make_node("Add", ["a_sub_b_in", "a_sub_b_in"], ["user_defined_vals"]), ], "body", [ helper.make_tensor_value_info('iteration_num', TensorProto.INT64, []), helper.make_tensor_value_info('keep_going_inp', TensorProto.BOOL, []), helper.make_tensor_value_info('b_in', TensorProto.FLOAT, []) ], [ helper.make_tensor_value_info('keep_going', TensorProto.BOOL, []), helper.make_tensor_value_info('a_sub_b_in', TensorProto.FLOAT, []), helper.make_tensor_value_info('my_local', TensorProto.FLOAT, []), helper.make_tensor_value_info('user_defined_vals', TensorProto.FLOAT, []), ]) node = helper.make_node( "Loop", inputs=["", "", "b"], outputs=["b_loop", "my_local_loop", "user_defined_vals_loop"], body=body) a = helper.make_tensor_value_info('a', TensorProto.FLOAT, []) b = helper.make_tensor_value_info('b', TensorProto.FLOAT, []) b_loop = helper.make_tensor_value_info('b_loop', TensorProto.FLOAT, []) uout = helper.make_tensor_value_info('user_defined_vals_loop', TensorProto.FLOAT, [2, 1]) return ([node], [a, b], [b_loop, uout]) @onnx_test() def loop_test(): body = helper.make_graph([ helper.make_node("Add", ["a", "b_in"], ["my_local"]), helper.make_node("Sub", ["a", "b_in"], ["a_sub_b_in"]), helper.make_node("Greater", ["my_local", "a_sub_b_in"], ["keep_going"]), helper.make_node("Add", ["a_sub_b_in", "a_sub_b_in"], ["user_defined_vals"]), ], "body", [ helper.make_tensor_value_info('iteration_num', TensorProto.INT64, [1]), helper.make_tensor_value_info('keep_going_inp', TensorProto.BOOL, [1]), helper.make_tensor_value_info('b_in', TensorProto.FLOAT, [1]) ], [ helper.make_tensor_value_info('keep_going', TensorProto.BOOL, [1]), helper.make_tensor_value_info('a_sub_b_in', TensorProto.FLOAT, [1]), helper.make_tensor_value_info('my_local', TensorProto.FLOAT, [1]), helper.make_tensor_value_info('user_defined_vals', TensorProto.FLOAT, [1]), ]) node = helper.make_node( "Loop", inputs=["max_trip_count", "keep_going_cond", "b"], outputs=["b_loop", "my_local_loop", "user_defined_vals_loop"], body=body) a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [1]) b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [1]) cond = helper.make_tensor_value_info('keep_going_cond', TensorProto.BOOL, [1]) iter = helper.make_tensor_value_info('max_trip_count', TensorProto.INT64, [1]) b_loop = helper.make_tensor_value_info('b_loop', TensorProto.FLOAT, [1]) uout = helper.make_tensor_value_info('user_defined_vals_loop', TensorProto.FLOAT, [2, 1]) return ([node], [iter, cond, a, b], [b_loop, uout]) @onnx_test() def loop_test_implicit_tripcnt(): body = helper.make_graph([ helper.make_node("Add", ["a", "b_in"], ["my_local"]), helper.make_node("Sub", ["a", "b_in"], ["a_sub_b_in"]), helper.make_node("Greater", ["my_local", "a_sub_b_in"], ["keep_going"]), helper.make_node("Add", ["a_sub_b_in", "a_sub_b_in"], ["user_defined_vals"]), ], "body", [ helper.make_tensor_value_info('iteration_num', TensorProto.INT64, [1]), helper.make_tensor_value_info('keep_going_inp', TensorProto.BOOL, [1]), helper.make_tensor_value_info('b_in', TensorProto.FLOAT, [1]) ], [ helper.make_tensor_value_info('keep_going', TensorProto.BOOL, [1]), helper.make_tensor_value_info('a_sub_b_in', TensorProto.FLOAT, [1]), helper.make_tensor_value_info('my_local', TensorProto.FLOAT, [1]), helper.make_tensor_value_info('user_defined_vals', TensorProto.FLOAT, [1]), ]) iter = helper.make_tensor(name='max_trip_count', data_type=TensorProto.INT64, dims=[1], vals=[15]) node = helper.make_node( "Loop", inputs=["max_trip_count", "keep_going_cond", "b"], outputs=["b_loop", "my_local_loop", "user_defined_vals_loop"], body=body) a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [1]) b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [1]) cond = helper.make_tensor_value_info('keep_going_cond', TensorProto.BOOL, [1]) b_loop = helper.make_tensor_value_info('b_loop', TensorProto.FLOAT, [1]) uout = helper.make_tensor_value_info('user_defined_vals_loop', TensorProto.FLOAT, [2, 1]) return ([node], [cond, a, b], [b_loop, uout], [iter]) @onnx_test() def lpnormalization_axis_error_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node('LpNormalization', inputs=['x'], outputs=['y'], axis=2) return ([node], [x], [y]) @onnx_test() def lpnormalization_default_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node( 'LpNormalization', inputs=['x'], outputs=['y'], axis=0, ) return ([node], [x], [y]) @onnx_test() def lpnormalization_l1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node( 'LpNormalization', inputs=['x'], outputs=['y'], p=1, ) return ([node], [x], [y]) @onnx_test() def lpnormalization_l2_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node('LpNormalization', inputs=['x'], outputs=['y'], p=2) return ([node], [x], [y]) @onnx_test() def lpnormalization_p_error_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 3]) node = onnx.helper.make_node('LpNormalization', inputs=['x'], outputs=['y'], p=3) return ([node], [x], [y]) @onnx_test() def lppool_l1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 3, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 3, 3]) node = onnx.helper.make_node('LpPool', inputs=['x'], outputs=['y'], kernel_shape=[3], p=1) return ([node], [x], [y]) @onnx_test() def lppool_l2_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 3, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 3, 3]) node = onnx.helper.make_node('LpPool', inputs=['x'], outputs=['y'], kernel_shape=[3], p=2) return ([node], [x], [y]) @onnx_test() def lrn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 28, 24, 24]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 28, 24, 24]) node = onnx.helper.make_node('LRN', inputs=['0'], size=5, alpha=0.0001, beta=0.75, bias=1.0, outputs=['1']) return ([node], [x], [y]) @onnx_test() def lstm_bi_layout_cell_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [2, 80, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [2, 80, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [2, 160]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 2, 20]) c0 = helper.make_tensor_value_info('c0', TensorProto.FLOAT, [3, 2, 20]) pph = helper.make_tensor_value_info('pph', TensorProto.FLOAT, [2, 60]) cellout = helper.make_tensor_value_info('cellout', TensorProto.FLOAT, [3, 2, 20]) node = onnx.helper.make_node( 'LSTM', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0', 'c0', 'pph'], outputs=['', '', 'cellout'], activations=['sigmoid', 'tanh', 'tanh', 'sigmoid', 'tanh', 'tanh'], clip=0, direction='bidirectional', hidden_size=20, input_forget=1, layout=1) return ([node], [seq, w, r, bias, seq_len, h0, c0, pph], [cellout]) @onnx_test() def lstm_bi_layout_last_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [2, 80, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [2, 80, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [2, 160]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 2, 20]) c0 = helper.make_tensor_value_info('c0', TensorProto.FLOAT, [3, 2, 20]) pph = helper.make_tensor_value_info('pph', TensorProto.FLOAT, [2, 60]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 2, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 2, 20]) node = onnx.helper.make_node( 'LSTM', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0', 'c0', 'pph'], outputs=['hs', 'output'], activations=['sigmoid', 'tanh', 'tanh', 'sigmoid', 'tanh', 'tanh'], clip=0, direction='bidirectional', hidden_size=20, input_forget=1, layout=1) return ([node], [seq, w, r, bias, seq_len, h0, c0, pph], [hs, output]) @onnx_test() def lstm_f_layout_hs_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 80, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 80, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 160]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 1, 20]) c0 = helper.make_tensor_value_info('c0', TensorProto.FLOAT, [3, 1, 20]) pph = helper.make_tensor_value_info('pph', TensorProto.FLOAT, [1, 60]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node( 'LSTM', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0', 'c0', 'pph'], outputs=['hs', 'output'], activations=['sigmoid', 'tanh', 'tanh'], clip=0, direction='forward', hidden_size=20, input_forget=1, layout=1) return ([node], [seq, w, r, bias, seq_len, h0, c0, pph], [hs, output]) @onnx_test() def lstm_f_layout_cell_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 80, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 80, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 160]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 1, 20]) c0 = helper.make_tensor_value_info('c0', TensorProto.FLOAT, [3, 1, 20]) pph = helper.make_tensor_value_info('pph', TensorProto.FLOAT, [1, 60]) cellout = helper.make_tensor_value_info('cellout', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node( 'LSTM', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0', 'c0', 'pph'], outputs=['', '', 'cellout'], activations=['sigmoid', 'tanh', 'tanh'], clip=0, direction='forward', hidden_size=20, input_forget=1, layout=1) return ([node], [seq, w, r, bias, seq_len, h0, c0, pph], [cellout]) @onnx_test() def lstm_f_1af_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [5, 3, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 80, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 80, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [5, 1, 3, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 3, 20]) node = onnx.helper.make_node('LSTM', inputs=['seq', 'w', 'r'], outputs=['hs', 'output'], activations=['sigmoid'], clip=0, direction='forward', hidden_size=20, input_forget=1) return ([node], [seq, w, r], [hs, output]) @onnx_test() def lstm_r_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 80, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 80, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 160]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 1, 20]) c0 = helper.make_tensor_value_info('c0', TensorProto.FLOAT, [3, 1, 20]) pph = helper.make_tensor_value_info('pph', TensorProto.FLOAT, [1, 60]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) node = onnx.helper.make_node( 'LSTM', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0', 'c0', 'pph'], outputs=['hs'], activations=['sigmoid', 'tanh', 'tanh'], clip=0, direction='reverse', hidden_size=20, input_forget=1, layout=1) return ([node], [seq, w, r, bias, seq_len, h0, c0, pph], [hs]) @onnx_test() def lstm_r_layout_hs_cell_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 80, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 80, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 160]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 1, 20]) c0 = helper.make_tensor_value_info('c0', TensorProto.FLOAT, [3, 1, 20]) pph = helper.make_tensor_value_info('pph', TensorProto.FLOAT, [1, 60]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) cellout = helper.make_tensor_value_info('cellout', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node( 'LSTM', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0', 'c0', 'pph'], outputs=['', 'output', 'cellout'], activations=['sigmoid', 'tanh', 'tanh'], clip=0, direction='reverse', hidden_size=20, input_forget=1, layout=1) return ([node], [seq, w, r, bias, seq_len, h0, c0, pph], [output, cellout]) @onnx_test() def matmul_bmbm_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 6, 7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [5, 2, 1, 7, 8]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5, 2, 3, 6, 8]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_bmv_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 6, 7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [7]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 6]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_mv_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [6, 7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [7]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [6]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_vbm_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [5, 7, 8]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5, 8]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_vm_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [7, 8]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [8]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_vv_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [7]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_dyn_mm_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [7, None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, None]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_dyn_mv_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [7]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, 1]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_dyn_vm_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [7, None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, None]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_dyn_vv_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmul_dyn_broadcast_test(): m1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [7]) m2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [5, 7, None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5, 1, None]) node = onnx.helper.make_node( 'MatMul', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmulinteger_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [3, 6, 16]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [3, 16, 8]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [3, 6, 8]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmulinteger_dyn_error(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [None, 6, 16]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [None, 16, 8]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [None, 6, 8]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmulinteger_invalid_type_error(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [None, 6, 16]) m2 = helper.make_tensor_value_info('2', TensorProto.INT16, [None, 16, 8]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [None, 6, 8]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmulinteger_uns_test(): m1 = helper.make_tensor_value_info('1', TensorProto.UINT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmulinteger_int8_uint8_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2'], outputs=['y'], ) return ([node], [m1, m2], [y]) @onnx_test() def matmulinteger_uns_zp_test(): m1 = helper.make_tensor_value_info('1', TensorProto.UINT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) zp1 = helper.make_tensor('3', TensorProto.UINT8, [], [0]) zp2 = helper.make_tensor('4', TensorProto.UINT8, [], [1]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2', '3', '4'], outputs=['y'], ) return ([node], [m1, m2], [y], [zp1, zp2]) @onnx_test() def matmulinteger_int8_uint8_one_zp_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) zp1 = helper.make_tensor('3', TensorProto.INT8, [], [5]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2', '3'], outputs=['y'], ) return ([node], [m1, m2], [y], [zp1]) @onnx_test() def matmulinteger_int8_uint8_one_zp_zero_vec_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) zp1 = helper.make_tensor('3', TensorProto.INT8, [4, 3], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2', '3'], outputs=['y'], ) return ([node], [m1, m2], [y], [zp1]) @onnx_test() def matmulinteger_int8_uint8_one_zp_zero_vec_test2(): m1 = helper.make_tensor_value_info('1', TensorProto.UINT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [3, 2]) zp1 = helper.make_tensor( '3', TensorProto.UINT8, [4, 3], [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2', '3'], outputs=['y'], ) return ([node], [m1, m2], [y], [zp1]) @onnx_test() def matmulinteger_int8_uint8_one_zp_error_test(): m1 = helper.make_tensor_value_info('1', TensorProto.UINT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) zp1 = helper.make_tensor('3', TensorProto.INT8, [], [5]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2', '3'], outputs=['y'], ) return ([node], [m1, m2], [y], [zp1]) @onnx_test() def matmulinteger_int8_uint8_dual_zp_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) zp1 = helper.make_tensor('3', TensorProto.INT8, [], [1]) zp2 = helper.make_tensor('4', TensorProto.UINT8, [], [1]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2', '3', '4'], outputs=['y'], ) return ([node], [m1, m2], [y], [zp1, zp2]) @onnx_test() def matmulinteger_int8_uint8_dual_zero_zp_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) zp1 = helper.make_tensor('3', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('4', TensorProto.UINT8, [], [128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulInteger', inputs=['1', '2', '3', '4'], outputs=['y'], ) return ([node], [m1, m2], [y], [zp1, zp2]) @onnx_test() def matmulintegertofloat_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], []) @onnx_test() def matmulintegertofloat_zp_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [2]) zp1 = helper.make_tensor_value_info('5', TensorProto.INT8, [3]) zp2 = helper.make_tensor_value_info('6', TensorProto.INT8, [2]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2, zp1, zp2], [y], []) @onnx_test() def matmulintegertofloat_scalar_zp_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [2]) zp1 = helper.make_tensor_value_info('5', TensorProto.INT8, [3]) zp2 = helper.make_tensor('6', TensorProto.INT8, [], [129]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2, zp1], [y], [zp2]) @onnx_test() def matmulintegertofloat_scalar_scale_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) s2 = helper.make_tensor('4', TensorProto.FLOAT, [], [10]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4'], outputs=['y'], ) return ([node], [m1, m2, s1], [y], [s2]) @onnx_test() def matmulintegertofloat_zp_bias_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [2]) zp1 = helper.make_tensor_value_info('5', TensorProto.INT8, [3]) zp2 = helper.make_tensor_value_info('6', TensorProto.UINT8, [2]) b1 = helper.make_tensor_value_info('7', TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6', '7'], outputs=['y'], ) return ([node], [m1, m2, s1, s2, zp1, zp2, b1], [y], []) @onnx_test() def matmulintegertofloat_bad_scale_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.INT8, [4, 3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT16, [3, 2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2]) @onnx_test() def matmulintegertofloat_bad_scale2_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.INT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4, 3]) s2 = helper.make_tensor_value_info('4', TensorProto.INT8, [3, 2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2]) @onnx_test() def matmulintegertofloat_bad_scale3_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4, 3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [3, 2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2]) @onnx_test() def matmulintegertofloat_bad_scale4_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.INT8, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.UINT8, [2, 2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2]) @onnx_test() def matmulintegertofloat_bad_scale5_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.INT8, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.UINT8, [7]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2]) @onnx_test() def matmulintegertofloat_bad_bias_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4, 3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [3, 2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) b1 = helper.make_tensor('7', TensorProto.UINT8, [2], [128, 128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6', '7'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2, b1]) @onnx_test() def matmulintegertofloat_bad_bias_test2(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4, 3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [3, 2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) b1 = helper.make_tensor('7', TensorProto.FLOAT16, [2], [128, -128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6', '7'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2, b1]) @onnx_test() def matmulintegertofloat_bad_bias_test3(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4, 3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [3, 2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) b1 = helper.make_tensor('7', TensorProto.FLOAT16, [], [128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6', '7'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2, b1]) @onnx_test() def matmulintegertofloat_half_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT16, [2]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], []) @onnx_test() def matmulintegertofloat_half_zp_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT16, [2]) zp1 = helper.make_tensor_value_info('5', TensorProto.INT8, [3]) zp2 = helper.make_tensor_value_info('6', TensorProto.UINT8, [2]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2, zp1, zp2], [y], []) @onnx_test() def matmulintegertofloat_half_scalar_zp_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT16, [2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2]) @onnx_test() def matmulintegertofloat_half_zp_bias_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [3, 2]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [3]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT16, [2]) zp1 = helper.make_tensor('5', TensorProto.INT8, [], [0]) zp2 = helper.make_tensor('6', TensorProto.UINT8, [], [128]) b1 = helper.make_tensor('7', TensorProto.FLOAT16, [2], [128, -128]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [4, 2]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6', '7'], outputs=['y'], ) return ([node], [m1, m2, s1, s2], [y], [zp1, zp2, b1]) @onnx_test() def matmulintegertofloat_zp_bias_3d_test(): m1 = helper.make_tensor_value_info('1', TensorProto.INT8, [4, 3, 2]) m2 = helper.make_tensor_value_info('2', TensorProto.UINT8, [4, 2, 3]) s1 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [2]) s2 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [3]) zp1 = helper.make_tensor_value_info('5', TensorProto.INT8, [2]) zp2 = helper.make_tensor_value_info('6', TensorProto.UINT8, [3]) b1 = helper.make_tensor_value_info('7', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 3, 3]) node = onnx.helper.make_node( 'MatMulIntegerToFloat', inputs=['1', '2', '3', '4', '5', '6', '7'], outputs=['y'], ) return ([node], [m1, m2, s1, s2, zp1, zp2, b1], [y]) @onnx_test() def max_test(): a = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) b = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) c = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( 'Max', inputs=['0', '1', '2'], outputs=['3'], ) return ([node], [a, b, c], [y]) @onnx_test() def maxpool_notset_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 1, 1]) node = onnx.helper.make_node('MaxPool', inputs=['x'], outputs=['y'], kernel_shape=[6, 6], strides=[2, 2], pads=[0, 0, 1, 1], auto_pad='NOTSET') return ([node], [x], [y]) @onnx_test() def maxpool_dilate_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 4, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 4, 2]) node = onnx.helper.make_node('MaxPool', inputs=['x'], outputs=['y'], kernel_shape=[2], strides=[1], pads=[1, 1], dilations=[3]) return ([node], [x], [y]) @onnx_test() def maxpool_same_upper_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 1, 5, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 1, 5, 5]) node = onnx.helper.make_node('MaxPool', inputs=['x'], outputs=['y'], kernel_shape=[2, 2], auto_pad='SAME_UPPER') return ([node], [x], [y]) @onnx_test() def mean_broadcast_test(): data_0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 4]) data_1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 2, 3, 4]) data_2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) data_3 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [1]) data_4 = helper.make_tensor_value_info('4', TensorProto.FLOAT, [2, 3, 1]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [1, 2, 3, 4]) node = onnx.helper.make_node("Mean", inputs=["0", "1", "2", "3", "4"], outputs=["mean"]) return ([node], [data_0, data_1, data_2, data_3, data_4], [mean]) @onnx_test() def mean_fp16_test(): data_0 = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [1, 2, 3]) data_1 = helper.make_tensor_value_info('1', TensorProto.FLOAT16, [1, 2, 3]) data_2 = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [1, 2, 3]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT16, [1, 2, 3]) node = onnx.helper.make_node("Mean", inputs=["0", "1", "2"], outputs=["mean"]) return ([node], [data_0, data_1, data_2], [mean]) @onnx_test() def mean_bf16_test(): data_0 = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [1, 2, 3]) data_1 = helper.make_tensor_value_info('1', TensorProto.BFLOAT16, [1, 2, 3]) data_2 = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [1, 2, 3]) mean = helper.make_tensor_value_info('mean', TensorProto.BFLOAT16, [1, 2, 3]) node = onnx.helper.make_node("Mean", inputs=["0", "1", "2"], outputs=["mean"]) return ([node], [data_0, data_1, data_2], [mean]) @onnx_test() def mean_invalid_broadcast_test(): data_0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 2, 3]) data_1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 2, 3]) data_2 = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [1, 2, 3]) node = onnx.helper.make_node("Mean", inputs=["0", "1", "2"], outputs=["mean"]) return ([node], [data_0, data_1, data_2], [mean]) @onnx_test() def mean_single_input_test(): data_0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 2, 3]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [1, 2, 3]) node = onnx.helper.make_node("Mean", inputs=["0"], outputs=["mean"]) return ([node], [data_0], [mean]) @onnx_test() def mean_test(): data = [ helper.make_tensor_value_info(str(i), TensorProto.DOUBLE, [2, 2, 2]) for i in range(10) ] data_names = [str(i) for i in range(10)] mean = helper.make_tensor_value_info('mean', TensorProto.DOUBLE, [2, 2, 2]) node = onnx.helper.make_node("Mean", inputs=data_names, outputs=["mean"]) return ([node], data, [mean]) @onnx_test() def mean_integral_test(): data = [ helper.make_tensor_value_info(str(i), TensorProto.INT32, [2, 2, 2]) for i in range(10) ] data_names = [str(i) for i in range(10)] mean = helper.make_tensor_value_info('mean', TensorProto.INT32, [2, 2, 2]) node = onnx.helper.make_node("Mean", inputs=data_names, outputs=["mean"]) return ([node], data, [mean]) def mvn_default_axes_test_base(dims, type=TensorProto.FLOAT): data = helper.make_tensor_value_info("data", type, dims) out = helper.make_tensor_value_info("out", type, dims) node = helper.make_node("MeanVarianceNormalization", inputs=["data"], outputs=["out"]) return ([node], [data], [out]) @onnx_test() def mvn_default_axes_test(): return mvn_default_axes_test_base([2, 2, 2, 2]) @onnx_test() def mvn_default_axes_fp16_test(): return mvn_default_axes_test_base([2, 2, 2, 2], TensorProto.FLOAT16) @onnx_test() def mvn_default_axes_bf16_test(): return mvn_default_axes_test_base([2, 2, 2, 2], TensorProto.BFLOAT16) @onnx_test() def mvn_default_axes_rank_too_small_test(): return mvn_default_axes_test_base([2, 2, 2]) @onnx_test() def mvn_default_axes_rank_too_big_test(): return mvn_default_axes_test_base([2, 2, 2, 2, 2]) def mvn_n_rank_test_base(axes, dims, type=TensorProto.FLOAT): data = helper.make_tensor_value_info("data", type, dims) out = helper.make_tensor_value_info("out", type, dims) node = helper.make_node("MeanVarianceNormalization", inputs=["data"], outputs=["out"], axes=axes) return ([node], [data], [out]) @onnx_test() def mvn_rank_2_test(): return mvn_n_rank_test_base([1], [2, 2]) @onnx_test() def mvn_rank_2_fp16_test(): return mvn_n_rank_test_base([1], [2, 2], TensorProto.FLOAT16) @onnx_test() def mvn_rank_2_bf16_test(): return mvn_n_rank_test_base([1], [2, 2], TensorProto.BFLOAT16) @onnx_test() def mvn_rank_3_test(): return mvn_n_rank_test_base([0, 1], [2, 2, 2]) @onnx_test() def mvn_rank_3_fp16_test(): return mvn_n_rank_test_base([0, 1], [2, 2, 2], TensorProto.FLOAT16) @onnx_test() def mvn_rank_3_bf16_test(): return mvn_n_rank_test_base([0, 1], [2, 2, 2], TensorProto.BFLOAT16) @onnx_test() def mvn_axes_rank_too_small_test(): return mvn_n_rank_test_base([0, 1, 2], [2, 2, 2]) @onnx_test() def mvn_axes_rank_too_big_test(): return mvn_n_rank_test_base([0], [2, 2, 2]) @onnx_test() def min_test(): a = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) b = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) c = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( 'Min', inputs=['0', '1', '2'], outputs=['3'], ) return ([node], [a, b, c], [y]) @onnx_test() def mod_test(): a = helper.make_tensor_value_info('0', TensorProto.INT32, [3, 3, 3]) b = helper.make_tensor_value_info('1', TensorProto.INT32, [3, 3, 3]) y = helper.make_tensor_value_info('2', TensorProto.INT32, [3, 3, 3]) node = onnx.helper.make_node('Mod', inputs=['0', '1'], outputs=['2']) return ([node], [a, b], [y]) @onnx_test() def mod_test_half(): a = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [3, 3, 3]) b = helper.make_tensor_value_info('1', TensorProto.FLOAT16, [3, 3, 3]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [3, 3, 3]) node = onnx.helper.make_node('Mod', inputs=['0', '1'], outputs=['2']) return ([node], [a, b], [y]) # @onnx_test() # def mod_test_bf16(): # a = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [3, 3, 3]) # b = helper.make_tensor_value_info('1', TensorProto.BFLOAT16, [3, 3, 3]) # y = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [3, 3, 3]) # node = onnx.helper.make_node('Mod', inputs=['0', '1'], outputs=['2']) # return ([node], [a, b], [y]) @onnx_test() def mod_test_different_dtypes(): a = helper.make_tensor_value_info('0', TensorProto.INT16, [3, 3, 3]) b = helper.make_tensor_value_info('1', TensorProto.INT32, [3, 3, 3]) y = helper.make_tensor_value_info('2', TensorProto.INT32, [3, 3, 3]) node = onnx.helper.make_node( 'Mod', inputs=['0', '1'], outputs=['2'], ) return ([node], [a, b], [y]) @onnx_test() def mod_test_fmod(): a = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 3, 3]) b = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 3, 3]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3, 3, 3]) node = onnx.helper.make_node( 'Mod', inputs=['0', '1'], outputs=['2'], fmod=1 #fmod flag = 1 ) return ([node], [a, b], [y]) @onnx_test() def mod_test_fmod_half(): a = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [3, 3, 3]) b = helper.make_tensor_value_info('1', TensorProto.FLOAT16, [3, 3, 3]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [3, 3, 3]) node = onnx.helper.make_node('Mod', inputs=['0', '1'], outputs=['2'], fmod=1) return ([node], [a, b], [y]) @onnx_test() def mod_test_fmod_bf16(): a = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [3, 3, 3]) b = helper.make_tensor_value_info('1', TensorProto.BFLOAT16, [3, 3, 3]) y = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [3, 3, 3]) node = onnx.helper.make_node('Mod', inputs=['0', '1'], outputs=['2'], fmod=1) return ([node], [a, b], [y]) @onnx_test() def mod_test_fmod_different_dtypes(): a = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 3, 3]) b = helper.make_tensor_value_info('1', TensorProto.INT32, [3, 3, 3]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3, 3, 3]) node = onnx.helper.make_node( 'Mod', inputs=['0', '1'], outputs=['2'], fmod=1 #fmod flag = 1 ) return ([node], [a, b], [y]) @onnx_test() def mha_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 2, 4]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 2, 4]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [1, 2, 4]) out = helper.make_tensor_value_info("out", TensorProto.FLOAT, [1, 2, 4]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=['out'], num_heads=2, domain='com.microsoft') return ([node], [query, key, value], [out]) @onnx_test() def mha_cross_attention_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 2, 4]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 2, 2, 2]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [1, 2, 2, 2]) out = helper.make_tensor_value_info("out", TensorProto.FLOAT, [1, 2, 4]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=['out'], num_heads=2, domain='com.microsoft') return ([node], [query, key, value], [out]) @onnx_test() def mha_kv_packed_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 2, 4]) kv = helper.make_tensor_value_info("kv", TensorProto.FLOAT, [1, 2, 2, 2, 2]) out = helper.make_tensor_value_info("out", TensorProto.FLOAT, [1, 2, 4]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'kv'], outputs=['out'], num_heads=2, domain='com.microsoft') return ([node], [query, kv], [out]) @onnx_test() def mha_qkv_packed_test(): qkv = helper.make_tensor_value_info("qkv", TensorProto.FLOAT, [1, 2, 2, 3, 2]) out = helper.make_tensor_value_info("out", TensorProto.FLOAT, [1, 2, 4]) node = helper.make_node('MultiHeadAttention', inputs=['qkv'], outputs=['out'], num_heads=2, domain='com.microsoft') return ([node], [qkv], [out]) def mha_scale_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 2, 4]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 2, 4]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [1, 2, 4]) out = helper.make_tensor_value_info("out", TensorProto.FLOAT, [1, 2, 4]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=['out'], num_heads=2, scale=0.1, domain='com.microsoft') return ([node], [query, key, value], [out]) @onnx_test() def mha_invalid_attribute_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 2, 4]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 2, 4]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [1, 2, 4]) out = helper.make_tensor_value_info("out", TensorProto.FLOAT, [1, 2, 4]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=['out'], domain='com.microsoft') return ([node], [query, key, value], [out]) @onnx_test() def mha_invalid_input_test(): node = helper.make_node('MultiHeadAttention', inputs=[], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [], []) @onnx_test() def mha_invalid_query_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query], []) @onnx_test() def mha_invalid_qkv_test(): qkv = helper.make_tensor_value_info("qkv", TensorProto.FLOAT, [1, 1, 1, 1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['qkv'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [qkv], []) @onnx_test() def mha_invalid_key_missing_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query], []) @onnx_test() def mha_invalid_key_ndim_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query, key], []) @onnx_test() def mha_invalid_kv_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) kv = helper.make_tensor_value_info("kv", TensorProto.FLOAT, [1, 1, 1, 1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'kv'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query, kv], []) @onnx_test() def mha_invalid_key_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 1, 2]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [1, 1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query, key, value], []) @onnx_test() def mha_invalid_value_missing_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query, key], []) @onnx_test() def mha_invalid_value_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 1, 1]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [2, 1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query, key, value], []) @onnx_test() def mha_invalid_value_ndim_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 1, 1]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [1, 1, 1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query, key, value], []) @onnx_test() def mha_invalid_cross_key_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 2, 1, 1]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [1, 1, 1, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query, key, value], []) @onnx_test() def mha_invalid_cross_value_test(): query = helper.make_tensor_value_info("q", TensorProto.FLOAT, [1, 1, 1]) key = helper.make_tensor_value_info("k", TensorProto.FLOAT, [1, 1, 1, 1]) value = helper.make_tensor_value_info("v", TensorProto.FLOAT, [1, 1, 2, 1]) node = helper.make_node('MultiHeadAttention', inputs=['q', 'k', 'v'], outputs=[], num_heads=1, domain='com.microsoft') return ([node], [query, key, value], []) @onnx_test() def multinomial_test(): sample_size = 13 seed = 0. input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [3, 10]) output = helper.make_tensor_value_info("output", TensorProto.INT32, [1, 10]) node = onnx.helper.make_node('Multinomial', inputs=['input'], sample_size=sample_size, seed=seed, outputs=['output']) return ([node], [input], [output]) @onnx_test() def multinomial_dyn_test(): sample_size = 100000 seed = 1.3 categories = 5 input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [None, categories]) output = helper.make_tensor_value_info("output", TensorProto.FLOAT, [None, categories]) node = onnx.helper.make_node( 'Multinomial', inputs=['input'], sample_size=sample_size, dtype=1, # shape::float_type seed=seed, outputs=['output']) return ([node], [input], [output]) @onnx_test() def multinomial_autoseed_dyn_test(): # If seed attribute is not given, device should auto generate one at runtime sample_size = 12 input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [None, 10]) output = helper.make_tensor_value_info("output", TensorProto.INT32, [None, 10]) node = onnx.helper.make_node('Multinomial', inputs=['input'], sample_size=sample_size, outputs=['output']) return ([node], [input], [output]) @onnx_test() def multinomial_generated_seed_test(): sample_size = 10 input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [1, 10]) output = helper.make_tensor_value_info("output", TensorProto.INT32, [1, 10]) node = onnx.helper.make_node('Multinomial', inputs=['input'], sample_size=sample_size, outputs=['output']) return ([node], [input], [output]) @onnx_test() def multinomial_dtype_error_test(): sample_size = 10 dtype = 0 input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [1, 10]) output = helper.make_tensor_value_info("output", TensorProto.INT64, [1, 10]) node = onnx.helper.make_node('Multinomial', inputs=['input'], sample_size=sample_size, dtype=dtype, outputs=['output']) return ([node], [input], [output]) @onnx_test() def multinomial_int64_test(): sample_size = 10 dtype = 7 seed = 1.0 input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [1, 10]) output = helper.make_tensor_value_info("output", TensorProto.INT64, [1, 10]) node = onnx.helper.make_node('Multinomial', inputs=['input'], sample_size=sample_size, dtype=dtype, seed=seed, outputs=['output']) return ([node], [input], [output]) @onnx_test() def mxfixneuron_even_test(): in_tv = helper.make_tensor_value_info('input', TensorProto.FLOAT, [3, 64, 4, 4]) out_tv = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 64, 4, 4]) node = onnx.helper.make_node('MXFixNeuron', inputs=['input'], axis=1, block_size=32, element_dtype='fp4_e2m1', rounding_mode=2, outputs=['output']) return ([node], [in_tv], [out_tv]) @onnx_test() def mxfixneuron_odd_test(): in_tv = helper.make_tensor_value_info('input', TensorProto.FLOAT, [71, 5, 5]) out_tv = helper.make_tensor_value_info('output', TensorProto.FLOAT, [71, 5, 5]) node = onnx.helper.make_node('MXFixNeuron', inputs=['input'], axis=0, block_size=32, element_dtype='fp4_e2m1', rounding_mode=2, outputs=['output']) return ([node], [in_tv], [out_tv]) @onnx_test() def mxfixneuron_small_test(): in_tv = helper.make_tensor_value_info('input', TensorProto.FLOAT, [4, 4]) out_tv = helper.make_tensor_value_info('output', TensorProto.FLOAT, [4, 4]) node = onnx.helper.make_node('MXFixNeuron', inputs=['input'], axis=1, block_size=32, element_dtype='fp4_e2m1', rounding_mode=2, outputs=['output']) return ([node], [in_tv], [out_tv]) @onnx_test() def dynamicscale_even_test(): in_tv = helper.make_tensor_value_info('input', TensorProto.FLOAT, [3, 64, 4, 4]) out_tv = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 64, 4, 4]) node = onnx.helper.make_node('DynamicScale', inputs=['input'], group_dim=1, group_size=32, output_dtype=23, scale_selection_method='floor', zero_point_selection_method='None', outputs=['output']) return ([node], [in_tv], [out_tv]) @onnx_test() def dynamicscale_odd_test(): in_tv = helper.make_tensor_value_info('input', TensorProto.FLOAT, [71, 5, 5]) out_tv = helper.make_tensor_value_info('output', TensorProto.FLOAT, [71, 5, 5]) node = onnx.helper.make_node('DynamicScale', inputs=['input'], group_dim=0, group_size=32, output_dtype=23, scale_selection_method='floor', zero_point_selection_method='None', outputs=['output']) return ([node], [in_tv], [out_tv]) @onnx_test() def dynamicscale_small_test(): in_tv = helper.make_tensor_value_info('input', TensorProto.FLOAT, [4, 4]) out_tv = helper.make_tensor_value_info('output', TensorProto.FLOAT, [4, 4]) node = onnx.helper.make_node('DynamicScale', inputs=['input'], group_dim=-1, group_size=32, output_dtype=23, scale_selection_method='floor', zero_point_selection_method='None', outputs=['output']) return ([node], [in_tv], [out_tv]) @onnx_test() def neg_test(): x = helper.make_tensor_value_info('0', TensorProto.INT64, [2, 3]) y = helper.make_tensor_value_info('1', TensorProto.INT64, [2, 3]) node = onnx.helper.make_node('Neg', inputs=['0'], outputs=['1']) return ([node], [x], [y]) @onnx_test() def neg_dynamic_test(): x = helper.make_tensor_value_info('0', TensorProto.INT64, [None, 3]) y = helper.make_tensor_value_info('1', TensorProto.INT64, [None, 3]) node = onnx.helper.make_node('Neg', inputs=['0'], outputs=['1']) return ([node], [x], [y]) @onnx_test() def nhwcconv_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 7, 7, 1]) w = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 1, 1, 1]) out = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 7, 7, 1]) node = onnx.helper.make_node('NhwcConv', inputs=['0', '1'], outputs=['2']) return ([node], [x, w], [out]) @onnx_test() def nms_test(): b = helper.make_tensor_value_info('boxes', TensorProto.FLOAT, [1, 6, 4]) s = helper.make_tensor_value_info('scores', TensorProto.FLOAT, [1, 1, 6]) mo = helper.make_tensor_value_info('max_output_boxes_per_class', TensorProto.INT64, [1]) iou = helper.make_tensor_value_info('iou_threshold', TensorProto.FLOAT, [1]) st = helper.make_tensor_value_info('score_threshold', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('selected_indices', TensorProto.INT64, [None, 3]) node = onnx.helper.make_node('NonMaxSuppression', inputs=[ 'boxes', 'scores', 'max_output_boxes_per_class', 'iou_threshold', 'score_threshold' ], outputs=['selected_indices'], center_point_box=1) return ([node], [b, s, mo, iou, st], [out]) @onnx_test() def nms_use_dyn_output_false_test(): b = helper.make_tensor_value_info('boxes', TensorProto.FLOAT, [1, 6, 4]) s = helper.make_tensor_value_info('scores', TensorProto.FLOAT, [1, 1, 6]) mo = helper.make_tensor_value_info('max_output_boxes_per_class', TensorProto.INT64, [1]) iou = helper.make_tensor_value_info('iou_threshold', TensorProto.FLOAT, [1]) st = helper.make_tensor_value_info('score_threshold', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('selected_indices', TensorProto.INT64, [None, 3]) node = onnx.helper.make_node('NonMaxSuppression', inputs=[ 'boxes', 'scores', 'max_output_boxes_per_class', 'iou_threshold', 'score_threshold' ], outputs=['selected_indices'], use_dyn_output=0) return ([node], [b, s, mo, iou, st], [out]) @onnx_test() def nms_dynamic_batch_test(): b = helper.make_tensor_value_info('boxes', TensorProto.FLOAT, [None, 6, 4]) s = helper.make_tensor_value_info('scores', TensorProto.FLOAT, [None, 1, 6]) mo = helper.make_tensor_value_info('max_output_boxes_per_class', TensorProto.INT64, [1]) iou = helper.make_tensor_value_info('iou_threshold', TensorProto.FLOAT, [1]) st = helper.make_tensor_value_info('score_threshold', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('selected_indices', TensorProto.INT64, [None, 3]) node = onnx.helper.make_node('NonMaxSuppression', inputs=[ 'boxes', 'scores', 'max_output_boxes_per_class', 'iou_threshold', 'score_threshold' ], outputs=['selected_indices'], center_point_box=1, use_dyn_output=1) return ([node], [b, s, mo, iou, st], [out]) @onnx_test() def nms_dynamic_boxes_test(): b = helper.make_tensor_value_info('boxes', TensorProto.FLOAT, [1, None, 4]) s = helper.make_tensor_value_info('scores', TensorProto.FLOAT, [1, 1, None]) mo = helper.make_tensor_value_info('max_output_boxes_per_class', TensorProto.INT64, [1]) iou = helper.make_tensor_value_info('iou_threshold', TensorProto.FLOAT, [1]) st = helper.make_tensor_value_info('score_threshold', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('selected_indices', TensorProto.INT64, [None, 3]) node = onnx.helper.make_node('NonMaxSuppression', inputs=[ 'boxes', 'scores', 'max_output_boxes_per_class', 'iou_threshold', 'score_threshold' ], outputs=['selected_indices']) return ([node], [b, s, mo, iou, st], [out]) @onnx_test() def nms_dynamic_classes_test(): b = helper.make_tensor_value_info('boxes', TensorProto.FLOAT, [1, 6, 4]) s = helper.make_tensor_value_info('scores', TensorProto.FLOAT, [1, None, 6]) mo = helper.make_tensor_value_info('max_output_boxes_per_class', TensorProto.INT64, [1]) iou = helper.make_tensor_value_info('iou_threshold', TensorProto.FLOAT, [1]) st = helper.make_tensor_value_info('score_threshold', TensorProto.FLOAT, [1]) out = helper.make_tensor_value_info('selected_indices', TensorProto.INT64, [None, 3]) node = onnx.helper.make_node('NonMaxSuppression', inputs=[ 'boxes', 'scores', 'max_output_boxes_per_class', 'iou_threshold', 'score_threshold' ], outputs=['selected_indices']) return ([node], [b, s, mo, iou, st], [out]) @onnx_test() def not_test(): x = helper.make_tensor_value_info('0', TensorProto.INT32, [4]) y = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) node = onnx.helper.make_node('Not', inputs=['0'], outputs=['1']) return ([node], [x], [y]) @onnx_test() def not_bool_test(): x = helper.make_tensor_value_info('0', TensorProto.BOOL, [4]) y = helper.make_tensor_value_info('1', TensorProto.BOOL, [4]) node = onnx.helper.make_node('Not', inputs=['0'], outputs=['1']) return ([node], [x], [y]) @onnx_test() def no_pad_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node('Pad', inputs=['0'], pads=[0, 0, 0, 0], outputs=['1']) return ([node], [x], [y]) @onnx_test() def nonzero_dynamic_test(): x = helper.make_tensor_value_info('data', TensorProto.BOOL, [2, 2]) y = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 3]) node = onnx.helper.make_node('NonZero', inputs=['data'], outputs=['indices']) return ([node], [x], [y]) @onnx_test() def nonzero_test(): data1 = np.array([[1., 0.], [1., 1.]]) data = helper.make_tensor(name='data', data_type=TensorProto.FLOAT, dims=data1.shape, vals=data1.flatten().astype(np.float)) y = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 3]) node = onnx.helper.make_node('NonZero', inputs=['data'], outputs=['indices']) return ([node], [], [y], [data]) @onnx_test() def nonzero_int_test(): data1 = np.array([[1, 1, 0], [1, 0, 1]]) data = helper.make_tensor(name='data', data_type=TensorProto.INT16, dims=data1.shape, vals=data1.flatten().astype(np.int16)) y = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 4]) node = onnx.helper.make_node('NonZero', inputs=['data'], outputs=['indices']) return ([node], [], [y], [data]) @onnx_test() def onehot_static_test(): axis_value = 0 depth = np.array([3]) indices = helper.make_tensor_value_info("indices", TensorProto.INT32, [5, 2]) values = helper.make_tensor_value_info("values", TensorProto.FLOAT16, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [3, 5, 2]) depth_tensor = helper.make_tensor(name="depth", data_type=TensorProto.INT32, dims=depth.shape, vals=depth.astype(int)) node = onnx.helper.make_node('OneHot', inputs=['indices', 'depth', 'values'], outputs=['y'], axis=axis_value) return ([node], [indices, values], [y], [depth_tensor]) @onnx_test() def onehot_dyn_test0(): axis_value = -1 depth = np.array([3]) indices = helper.make_tensor_value_info("indices", TensorProto.INT32, [None, 2]) values = helper.make_tensor_value_info("values", TensorProto.FLOAT16, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [3, 5, 2]) depth_tensor = helper.make_tensor(name="depth", data_type=TensorProto.INT32, dims=depth.shape, vals=depth.astype(int)) node = onnx.helper.make_node('OneHot', inputs=['indices', 'depth', 'values'], outputs=['y'], axis=axis_value) return ([node], [indices, values], [y], [depth_tensor]) @onnx_test() def onehot_dyn_test1(): axis_value = 1 indices = helper.make_tensor_value_info("indices", TensorProto.INT32, [None, 2]) depth = helper.make_tensor_value_info("depth", TensorProto.INT64, [1]) values = helper.make_tensor_value_info("values", TensorProto.FLOAT, [2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 5, 2]) node = onnx.helper.make_node('OneHot', inputs=['indices', 'depth', 'values'], outputs=['y'], axis=axis_value) return ([node], [indices, values, depth], [y]) @onnx_test() def pad_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 4]) node = onnx.helper.make_node('Pad', inputs=['0'], pads=[1, 1, 1, 1], outputs=['1']) return ([node], [x], [y]) @onnx_test() def pad_asym_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 6, 4, 12]) node = onnx.helper.make_node('Pad', inputs=['0'], pads=[0, 1, 0, 3, 0, 2, 0, 4], outputs=['1']) return ([node], [x], [y]) @onnx_test() def pad_asym_invalid_pads_error_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 6, 4, 12]) node = onnx.helper.make_node('Pad', inputs=['0'], pads=[0, 1, 0, 3, 0, 2], outputs=['1']) return ([node], [x], [y]) @onnx_test() def pad_3arg_test(): values = np.array([1]) val_tensor = helper.make_tensor(name='val', data_type=TensorProto.FLOAT, dims=values.reshape(()).shape, vals=values.astype(float)) arg_val = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_val'], value=val_tensor) sizes = np.array([1, 1, 2, 2]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [5, 5]) node = onnx.helper.make_node('Pad', inputs=['0', 'arg_pad', 'arg_val'], outputs=['1']) return ([arg_val, arg_pad, node], [x], [y]) @onnx_test() def pad_undef_const_val_test(): sizes = np.array([1, 1, 1, 1]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 4]) node = onnx.helper.make_node('Pad', inputs=['0', 'arg_pad', ''], outputs=['1']) return ([arg_pad, node], [x], [y]) @onnx_test() def pad_4arg_axes_test(): values = np.array([1]) val_tensor = helper.make_tensor(name='val', data_type=TensorProto.FLOAT, dims=values.reshape(()).shape, vals=values.astype(float)) arg_val = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_val'], value=val_tensor) sizes = np.array([1, 3, 2, 4]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) axes = np.array([1, 3]) axes_tensor = helper.make_tensor(name='pad_axes', data_type=TensorProto.INT32, dims=axes.shape, vals=axes.astype(int)) arg_axes = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_axes'], value=axes_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 6, 4, 12]) node = onnx.helper.make_node( 'Pad', inputs=['0', 'arg_pad', 'arg_val', 'arg_axes'], outputs=['1']) return ([arg_axes, arg_val, arg_pad, node], [x], [y]) @onnx_test() def pad_4arg_invalid_axes_error_test(): values = np.array([1]) val_tensor = helper.make_tensor(name='val', data_type=TensorProto.FLOAT, dims=values.reshape(()).shape, vals=values.astype(float)) arg_val = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_val'], value=val_tensor) sizes = np.array([1, 3, 2, 4]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) axes = np.array([1, 2, 3]) axes_tensor = helper.make_tensor(name='pad_axes', data_type=TensorProto.INT32, dims=axes.shape, vals=axes.astype(int)) arg_axes = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_axes'], value=axes_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 6, 4, 12]) node = onnx.helper.make_node( 'Pad', inputs=['0', 'arg_pad', 'arg_val', 'arg_axes'], outputs=['1']) return ([arg_axes, arg_val, arg_pad, node], [x], [y]) @onnx_test() def pad_4arg_neg_axes_test(): values = np.array([1]) val_tensor = helper.make_tensor(name='val', data_type=TensorProto.FLOAT, dims=values.reshape(()).shape, vals=values.astype(float)) arg_val = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_val'], value=val_tensor) sizes = np.array([1, 3, 2, 4]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) axes = np.array([-3, -1]) axes_tensor = helper.make_tensor(name='pad_axes', data_type=TensorProto.INT32, dims=axes.shape, vals=axes.astype(int)) arg_axes = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_axes'], value=axes_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 6, 4, 12]) node = onnx.helper.make_node( 'Pad', inputs=['0', 'arg_pad', 'arg_val', 'arg_axes'], outputs=['1']) return ([arg_axes, arg_val, arg_pad, node], [x], [y]) @onnx_test() def pad_reflect_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2, 5]) sizes = np.array([0, 2, 0, 1]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) node = onnx.helper.make_node('Pad', mode='reflect', inputs=['0', 'arg_pad'], outputs=['1']) return ([arg_pad, node], [x], [y]) @onnx_test() def pad_reflect_with_axes_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2, 5]) sizes = np.array([2, 1]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) axes = np.array([1]) axes_tensor = helper.make_tensor(name='pad_axes', data_type=TensorProto.INT32, dims=axes.shape, vals=axes.astype(int)) arg_axes = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_axes'], value=axes_tensor) node = onnx.helper.make_node('Pad', mode='reflect', inputs=['0', 'arg_pad', 'arg_axes'], outputs=['1']) return ([arg_axes, arg_pad, node], [x], [y]) @onnx_test() def pad_reflect_multiaxis_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 5]) sizes = np.array([0, 2, 2, 0]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) node = onnx.helper.make_node('Pad', mode='reflect', inputs=['0', 'arg_pad'], outputs=['1']) return ([arg_pad, node], [x], [y]) @onnx_test() def pad_edge_1d_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [9]) sizes = np.array([2, 3]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) node = onnx.helper.make_node('Pad', mode='edge', inputs=['0', 'arg_pad'], outputs=['1']) return ([arg_pad, node], [x], [y]) @onnx_test() def pad_edge_2d_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [5, 7]) sizes = np.array([1, 2, 1, 2]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) node = onnx.helper.make_node('Pad', mode='edge', inputs=['0', 'arg_pad'], outputs=['1']) return ([arg_pad, node], [x], [y]) @onnx_test() def pad_edge_2d_with_axes_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 6]) sizes = np.array([1, 2]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) axes = np.array([1]) axes_tensor = helper.make_tensor(name='pad_axes', data_type=TensorProto.INT32, dims=axes.shape, vals=axes.astype(int)) arg_axes = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_axes'], value=axes_tensor) node = onnx.helper.make_node('Pad', mode='edge', inputs=['0', 'arg_pad', 'arg_axes'], outputs=['1']) return ([arg_axes, arg_pad, node], [x], [y]) @onnx_test() def pad_attr_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, None]) node = onnx.helper.make_node('Pad', inputs=['0'], pads=[1, 1, 1, 1], outputs=['1']) return ([node], [x], [y]) @onnx_test() def pad_cnst_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, None]) sizes = np.array([0, 2, 0, 1]) pad_tensor = helper.make_tensor(name='pad_size', data_type=TensorProto.INT32, dims=sizes.shape, vals=sizes.astype(int)) arg_pad = onnx.helper.make_node('Constant', inputs=[], outputs=['arg_pad'], value=pad_tensor) node = onnx.helper.make_node('Pad', inputs=['0', 'arg_pad'], outputs=['1']) return ([arg_pad, node], [x], [y]) @onnx_test() def pad_dyn_reflect_error(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, None]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, None]) node = onnx.helper.make_node('Pad', mode='reflect', inputs=['0'], pads=[0, 2, 0, 1], outputs=['1']) return ([node], [x], [y]) @onnx_test() def pow_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2, 3, 4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Pow', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def pow_fp32_i64_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.INT64, [2, 3, 4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Pow', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def pow_i64_fp32_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.INT64, [2, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2, 3, 4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.INT64, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Pow', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def prefix_scan_sum_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 2]) axis_val = np.array([0]) axis_tensor = helper.make_tensor(name="axis", data_type=TensorProto.INT32, dims=axis_val.shape, vals=axis_val.astype(int)) node = onnx.helper.make_node('CumSum', inputs=['x', 'axis'], outputs=['y'], exclusive=1, reverse=1) return ([node], [x], [y], [axis_tensor]) @onnx_test() def prelu_brcst_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node( 'PRelu', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def qlinearadd_test(): a = helper.make_tensor_value_info('A', TensorProto.UINT8, [64]) sc_a = helper.make_tensor('A_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_a = helper.make_tensor('A_zero_point', TensorProto.UINT8, [], [0]) b = helper.make_tensor_value_info('B', TensorProto.UINT8, [64]) sc_b = helper.make_tensor('B_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_b = helper.make_tensor('B_zero_point', TensorProto.UINT8, [], [128]) sc_c = helper.make_tensor('C_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_c = helper.make_tensor('C_zero_point', TensorProto.UINT8, [], [64]) c = helper.make_tensor_value_info('C', TensorProto.UINT8, [64]) node = onnx.helper.make_node( 'QLinearAdd', inputs=[ 'A', 'A_scale', 'A_zero_point', 'B', 'B_scale', 'B_zero_point', 'C_scale', 'C_zero_point' ], outputs=['C'], ) return ([node], [a, b], [c], [sc_a, zero_pt_a, sc_b, zero_pt_b, sc_c, zero_pt_c]) @onnx_test() def qlinearadd_bcast_test(): a = helper.make_tensor_value_info('A', TensorProto.INT8, [64]) sc_a = helper.make_tensor('A_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_a = helper.make_tensor('A_zero_point', TensorProto.INT8, [], [0]) b = helper.make_tensor_value_info('B', TensorProto.INT8, [1, 1, 64]) sc_b = helper.make_tensor('B_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_b = helper.make_tensor('B_zero_point', TensorProto.INT8, [], [32]) sc_c = helper.make_tensor('C_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_c = helper.make_tensor('C_zero_point', TensorProto.INT8, [], [-64]) c = helper.make_tensor_value_info('C', TensorProto.INT8, [1, 1, 64]) node = onnx.helper.make_node( 'QLinearAdd', inputs=[ 'A', 'A_scale', 'A_zero_point', 'B', 'B_scale', 'B_zero_point', 'C_scale', 'C_zero_point' ], outputs=['C'], ) return ([node], [a, b], [c], [sc_a, zero_pt_a, sc_b, zero_pt_b, sc_c, zero_pt_c]) @onnx_test() def qlinearaveragepool_1d_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [1, 3, 32]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.05]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.INT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 3, 31]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.05]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.INT8, [], [16]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[2], ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_2d_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [1, 3, 4, 4]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.05]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.INT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [1, 3, 3, 3]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.015]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.INT8, [], [16]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[2, 2], ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_2d_ceil_test(): x = helper.make_tensor_value_info('x', TensorProto.UINT8, [1, 1, 4, 4]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.5]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.UINT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [1, 1, 2, 2]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.05]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.UINT8, [], [0]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[3, 3], strides=[2, 2], ceil_mode=True, ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_2d_dilations_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [1, 1, 4, 4]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.5]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.INT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [1, 1, 2, 2]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.25]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.INT8, [], [84]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[2, 2], strides=[1, 1], dilations=[2, 2], ceil_mode=True, ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_2d_pads_count_include_pad_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [1, 3, 4, 4]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.05]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.INT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [1, 3, 6, 6]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.01]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.INT8, [], [32]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[3, 3], pads=[2, 2, 2, 2], count_include_pad=1, ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_2d_same_lower_test(): x = helper.make_tensor_value_info('x', TensorProto.UINT8, [1, 3, 4, 4]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.5]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.UINT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [1, 3, 4, 4]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.5]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.UINT8, [], [0]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[2, 2], auto_pad="SAME_LOWER", ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_2d_same_upper_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [1, 3, 4, 4]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.5]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.INT8, [], [32]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [1, 3, 4, 4]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.25]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.INT8, [], [0]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[2, 2], auto_pad="SAME_UPPER", ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_2d_strides_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [1, 3, 8, 8]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.05]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.INT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [1, 3, 2, 2]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.05]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.INT8, [], [8]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[5, 5], strides=[2, 2], ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_3d_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [1, 3, 3, 3, 3]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.05]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.INT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [1, 3, 2, 2, 2]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.02]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.INT8, [], [0]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[2, 2, 2], ) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_notset_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [1, 1, 5, 5]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.5]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.INT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [1, 1, 1, 1]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.5]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.INT8, [], [10]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[6, 6], strides=[2, 2], pads=[0, 0, 1, 1], channels_last=0, auto_pad='NOTSET') return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearaveragepool_nt_cip_test(): x = helper.make_tensor_value_info('x', TensorProto.UINT8, [1, 1, 5, 5]) x_scale = helper.make_tensor('x_scale', TensorProto.FLOAT, [], [0.5]) x_zero_point = helper.make_tensor('x_zero_point', TensorProto.UINT8, [], [0]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [1, 1, 1, 1]) y_scale = helper.make_tensor('y_scale', TensorProto.FLOAT, [], [0.5]) y_zero_point = helper.make_tensor('y_zero_point', TensorProto.UINT8, [], [10]) node = onnx.helper.make_node( 'QLinearAveragePool', inputs=['x', 'x_scale', 'x_zero_point', 'y_scale', 'y_zero_point'], outputs=['y'], kernel_shape=[6, 6], strides=[2, 2], pads=[0, 0, 1, 1], channels_last=0, auto_pad='NOTSET', count_include_pad=1) return ([node], [x], [y], [x_scale, x_zero_point, y_scale, y_zero_point]) @onnx_test() def qlinearconcat_test(): y_scale = helper.make_tensor('1', TensorProto.FLOAT, [], [0.5]) y_zero_point = helper.make_tensor('2', TensorProto.INT8, [], [2]) t0 = helper.make_tensor_value_info('t0', TensorProto.INT8, [2]) s0 = helper.make_tensor('3', TensorProto.FLOAT, [], [0.5]) zp0 = helper.make_tensor('4', TensorProto.INT8, [], [1]) t1 = helper.make_tensor_value_info('t1', TensorProto.INT8, [3]) s1 = helper.make_tensor('5', TensorProto.FLOAT, [], [0.25]) zp1 = helper.make_tensor('6', TensorProto.INT8, [], [0]) y = helper.make_tensor_value_info('out', TensorProto.INT8, [5]) node = onnx.helper.make_node( 'QLinearConcat', inputs=['1', '2', 't0', '3', '4', 't1', '5', '6'], axis=0, outputs=['out'], ) return ([node], [t0, t1], [y], [y_scale, y_zero_point, s0, zp0, s1, zp1]) @onnx_test() def qlinearconcat_3d_test(): y_scale = helper.make_tensor('1', TensorProto.FLOAT, [], [0.5]) y_zero_point = helper.make_tensor('2', TensorProto.INT8, [], [2]) t0 = helper.make_tensor_value_info('t0', TensorProto.INT8, [3, 4, 2]) s0 = helper.make_tensor('3', TensorProto.FLOAT, [], [0.5]) zp0 = helper.make_tensor('4', TensorProto.INT8, [], [10]) t1 = helper.make_tensor_value_info('t1', TensorProto.INT8, [3, 2, 2]) s1 = helper.make_tensor('5', TensorProto.FLOAT, [], [0.4]) zp1 = helper.make_tensor('6', TensorProto.INT8, [], [20]) y = helper.make_tensor_value_info('out', TensorProto.UINT8, [3, 6, 2]) node = onnx.helper.make_node( 'QLinearConcat', inputs=['1', '2', 't0', '3', '4', 't1', '5', '6'], axis=1, outputs=['out'], ) return ([node], [t0, t1], [y], [y_scale, y_zero_point, s0, zp0, s1, zp1]) @onnx_test() def qlinearconv_test(): # https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__QLinearConv.html x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 1, 7, 7]) sc_x = helper.make_tensor('1', TensorProto.FLOAT, [], [0.00369204697]) zero_pt_x = helper.make_tensor('2', TensorProto.UINT8, [], [132]) wt = helper.make_tensor('3', TensorProto.UINT8, [1, 1, 1, 1], [0]) sc_wt = helper.make_tensor('4', TensorProto.FLOAT, [], [0.00172794575]) zero_pt_wt = helper.make_tensor('5', TensorProto.UINT8, [], [255]) sc_y = helper.make_tensor('6', TensorProto.FLOAT, [], [0.00162681262]) zero_pt_y = helper.make_tensor('7', TensorProto.UINT8, [], [123]) out = helper.make_tensor_value_info('out', TensorProto.UINT8, [1, 1, 7, 7]) node = onnx.helper.make_node( 'QLinearConv', inputs=['X', '1', '2', '3', '4', '5', '6', '7'], outputs=['out'], ) return ([node], [x], [out], [sc_x, zero_pt_x, wt, sc_wt, zero_pt_wt, sc_y, zero_pt_y]) @onnx_test() def qlinearconv_pad_1_test(): # https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 1, 5, 5]) sc_x = helper.make_tensor('1', TensorProto.FLOAT, [], [0.09411764705882353]) zero_pt_x = helper.make_tensor('2', TensorProto.UINT8, [], [0]) wt = helper.make_tensor('3', TensorProto.UINT8, [1, 1, 3, 3], [1, 1, 1, 1, 1, 1, 1, 1, 1]) sc_wt = helper.make_tensor('4', TensorProto.FLOAT, [], [1.0]) zero_pt_wt = helper.make_tensor('5', TensorProto.UINT8, [], [0]) sc_y = helper.make_tensor('6', TensorProto.FLOAT, [], [0.6352941176470588]) zero_pt_y = helper.make_tensor('7', TensorProto.UINT8, [], [0]) out = helper.make_tensor_value_info('out', TensorProto.UINT8, [1, 1, 5, 5]) node = onnx.helper.make_node( 'QLinearConv', inputs=['X', '1', '2', '3', '4', '5', '6', '7'], outputs=['out'], pads=[1, 1, 1, 1], ) return ([node], [x], [out], [sc_x, zero_pt_x, wt, sc_wt, zero_pt_wt, sc_y, zero_pt_y]) @onnx_test() def qlinearconv_pad_0_test(): # https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 1, 5, 5]) sc_x = helper.make_tensor('1', TensorProto.FLOAT, [], [0.09411764705882353]) zero_pt_x = helper.make_tensor('2', TensorProto.UINT8, [], [0]) wt = helper.make_tensor('3', TensorProto.UINT8, [1, 1, 3, 3], [1, 1, 1, 1, 1, 1, 1, 1, 1]) sc_wt = helper.make_tensor('4', TensorProto.FLOAT, [], [1.0]) zero_pt_wt = helper.make_tensor('5', TensorProto.UINT8, [], [0]) sc_y = helper.make_tensor('6', TensorProto.FLOAT, [], [0.6352941176470588]) zero_pt_y = helper.make_tensor('7', TensorProto.INT8, [], [-128]) out = helper.make_tensor_value_info('out', TensorProto.INT8, [1, 1, 3, 3]) node = onnx.helper.make_node( 'QLinearConv', inputs=['X', '1', '2', '3', '4', '5', '6', '7'], outputs=['out'], pads=[0, 0, 0, 0], ) return ([node], [x], [out], [sc_x, zero_pt_x, wt, sc_wt, zero_pt_wt, sc_y, zero_pt_y]) @onnx_test() def qlinearconv_scale_1D_test(): # https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 1, 5, 5]) sc_x = helper.make_tensor('1', TensorProto.FLOAT, [], [0.09411764705882353]) zero_pt_x = helper.make_tensor('2', TensorProto.UINT8, [], [0]) wt = helper.make_tensor( '3', TensorProto.UINT8, [2, 1, 3, 3], [1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2]) sc_wt = helper.make_tensor('4', TensorProto.FLOAT, [2], [1.0, 0.5]) zero_pt_wt = helper.make_tensor('5', TensorProto.UINT8, [2], [0, 0]) sc_y = helper.make_tensor('6', TensorProto.FLOAT, [], [0.6352941176470588]) zero_pt_y = helper.make_tensor('7', TensorProto.INT8, [], [-128]) out = helper.make_tensor_value_info('out', TensorProto.INT8, [1, 2, 3, 3]) node = onnx.helper.make_node( 'QLinearConv', inputs=['X', '1', '2', '3', '4', '5', '6', '7'], outputs=['out'], pads=[0, 0, 0, 0], ) return ([node], [x], [out], [sc_x, zero_pt_x, wt, sc_wt, zero_pt_wt, sc_y, zero_pt_y]) @onnx_test() def qlinearglobalavgpool_test(): x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 3, 4, 4]) sc_x = helper.make_tensor('X_scale', TensorProto.FLOAT, [], [0.05]) z_pt_x = helper.make_tensor('X_zero_point', TensorProto.UINT8, [], [128]) y = helper.make_tensor_value_info('Y', TensorProto.UINT8, [1, 3, 1, 1]) sc_y = helper.make_tensor('Y_scale', TensorProto.FLOAT, [], [0.025]) z_pt_y = helper.make_tensor('Y_zero_point', TensorProto.UINT8, [], [64]) n = onnx.helper.make_node( 'QLinearGlobalAveragePool', inputs=['X', 'X_scale', 'X_zero_point', 'Y_scale', 'Y_zero_point'], outputs=['Y'], channels_last=0, ) return ([n], [x], [y], [sc_x, z_pt_x, sc_y, z_pt_y]) @onnx_test() def qlinearleakyrelu_test(): x = helper.make_tensor_value_info('X', TensorProto.INT8, [64]) sc_x = helper.make_tensor('X_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_x = helper.make_tensor('X_zero_point', TensorProto.INT8, [], [0]) sc_y = helper.make_tensor('Y_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_y = helper.make_tensor('Y_zero_point', TensorProto.INT8, [], [10]) y = helper.make_tensor_value_info('Y', TensorProto.INT8, [64]) node = onnx.helper.make_node( 'QLinearLeakyRelu', inputs=['X', 'X_scale', 'X_zero_point', 'Y_scale', 'Y_zero_point'], outputs=['Y'], alpha=1.1, ) return ([node], [x], [y], [sc_x, zero_pt_x, sc_y, zero_pt_y]) def qlinearmatmul_1D_test(): a = helper.make_tensor_value_info('A', TensorProto.UINT8, [8]) sc_a = helper.make_tensor('A_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_a = helper.make_tensor('A_zero_point', TensorProto.UINT8, [], [0]) b = helper.make_tensor_value_info('B', TensorProto.UINT8, [8]) sc_b = helper.make_tensor('B_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_b = helper.make_tensor('B_zero_point', TensorProto.UINT8, [], [128]) sc_c = helper.make_tensor('C_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_c = helper.make_tensor('C_zero_point', TensorProto.UINT8, [], [64]) c = helper.make_tensor_value_info('C', TensorProto.UINT8, [1]) node = onnx.helper.make_node( 'QLinearMatMul', inputs=[ 'A', 'A_scale', 'A_zero_point', 'B', 'B_scale', 'B_zero_point', 'C_scale', 'C_zero_point' ], outputs=['C'], ) return ([node], [a, b], [c], [sc_a, zero_pt_a, sc_b, zero_pt_b, sc_c, zero_pt_c]) @onnx_test() def qlinearmatmul_2D_test(): a = helper.make_tensor_value_info('A', TensorProto.UINT8, [1, 8]) sc_a = helper.make_tensor('A_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_a = helper.make_tensor('A_zero_point', TensorProto.UINT8, [], [0]) b = helper.make_tensor_value_info('B', TensorProto.UINT8, [8, 1]) sc_b = helper.make_tensor('B_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_b = helper.make_tensor('B_zero_point', TensorProto.UINT8, [], [128]) sc_c = helper.make_tensor('C_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_c = helper.make_tensor('C_zero_point', TensorProto.UINT8, [], [64]) c = helper.make_tensor_value_info('C', TensorProto.UINT8, [1, 1]) node = onnx.helper.make_node( 'QLinearMatMul', inputs=[ 'A', 'A_scale', 'A_zero_point', 'B', 'B_scale', 'B_zero_point', 'C_scale', 'C_zero_point' ], outputs=['C'], ) return ([node], [a, b], [c], [sc_a, zero_pt_a, sc_b, zero_pt_b, sc_c, zero_pt_c]) @onnx_test() def qlinearmatmul_3D_test(): a = helper.make_tensor_value_info('A', TensorProto.UINT8, [2, 2, 4]) sc_a = helper.make_tensor('A_scale', TensorProto.FLOAT, [], [0.0066]) zero_pt_a = helper.make_tensor('A_zero_point', TensorProto.UINT8, [], [113]) b = helper.make_tensor_value_info('B', TensorProto.UINT8, [2, 4, 3]) sc_b = helper.make_tensor('B_scale', TensorProto.FLOAT, [], [0.00705]) zero_pt_b = helper.make_tensor('B_zero_point', TensorProto.UINT8, [], [114]) sc_c = helper.make_tensor('C_scale', TensorProto.FLOAT, [], [0.0107]) zero_pt_c = helper.make_tensor('C_zero_point', TensorProto.UINT8, [], [118]) c = helper.make_tensor_value_info('C', TensorProto.UINT8, [2, 2, 3]) node = onnx.helper.make_node( 'QLinearMatMul', inputs=[ 'A', 'A_scale', 'A_zero_point', 'B', 'B_scale', 'B_zero_point', 'C_scale', 'C_zero_point' ], outputs=['C'], ) return ([node], [a, b], [c], [sc_a, zero_pt_a, sc_b, zero_pt_b, sc_c, zero_pt_c]) @onnx_test() def matmulnbits_mm_test(): a = onnx.helper.make_tensor_value_info("a", onnx.TensorProto.FLOAT, [2, 16]) b = onnx.helper.make_tensor_value_info("b", onnx.TensorProto.UINT8, [4, 1, 8]) scales = onnx.helper.make_tensor_value_info("scales", onnx.TensorProto.FLOAT, [4]) zp = onnx.helper.make_tensor_value_info("zp", onnx.TensorProto.UINT8, [4]) c = onnx.helper.make_tensor_value_info("c", onnx.TensorProto.FLOAT, [2, 4]) node = onnx.helper.make_node("MatMulNBits", inputs=["a", "b", "scales", "zp"], outputs=["c"], bits=4, block_size=16, K=16, N=4, domain='com.microsoft') return ([node], [a, b, scales, zp], [c]) @onnx_test() def matmulnbits_mm2_test(): a = onnx.helper.make_tensor_value_info("a", onnx.TensorProto.FLOAT, [2, 33]) b = onnx.helper.make_tensor_value_info("b", onnx.TensorProto.UINT8, [2, 3, 8]) scales = onnx.helper.make_tensor_value_info("scales", onnx.TensorProto.FLOAT, [6]) c = onnx.helper.make_tensor_value_info("c", onnx.TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node("MatMulNBits", inputs=["a", "b", "scales"], outputs=["c"], bits=4, block_size=16, K=33, N=2, domain='com.microsoft') return ([node], [a, b, scales], [c]) @onnx_test() def matmulnbits_vm_test(): a = onnx.helper.make_tensor_value_info("a", onnx.TensorProto.FLOAT, [20]) b = onnx.helper.make_tensor_value_info("b", onnx.TensorProto.UINT8, [3, 2, 8]) scales = onnx.helper.make_tensor_value_info("scales", onnx.TensorProto.FLOAT, [6]) zp = onnx.helper.make_tensor_value_info("zp", onnx.TensorProto.UINT8, [3]) c = onnx.helper.make_tensor_value_info("c", onnx.TensorProto.FLOAT, [3]) node = onnx.helper.make_node("MatMulNBits", inputs=["a", "b", "scales", "zp"], outputs=["c"], bits=4, block_size=16, K=20, N=3, domain='com.microsoft') return ([node], [a, b, scales, zp], [c]) @onnx_test() def matmulnbits_bmm_test(): a = onnx.helper.make_tensor_value_info("a", onnx.TensorProto.FLOAT, [2, 3, 8]) b = onnx.helper.make_tensor_value_info("b", onnx.TensorProto.UINT8, [2, 1, 8]) scales = onnx.helper.make_tensor_value_info("scales", onnx.TensorProto.FLOAT, [2]) c = onnx.helper.make_tensor_value_info("c", onnx.TensorProto.FLOAT, [2, 3, 2]) node = onnx.helper.make_node("MatMulNBits", inputs=["a", "b", "scales"], outputs=["c"], bits=4, block_size=16, K=8, N=2, domain='com.microsoft') return ([node], [a, b, scales], [c]) def matmulnbits_negative_test(bits=4, block_size=16, a_dims=[2, 16], b_dims=[4, 1, 8], scales_dims=[4], zp_dims=[4], out_dims=[2, 4]): a = onnx.helper.make_tensor_value_info("a", onnx.TensorProto.FLOAT, a_dims) b = onnx.helper.make_tensor_value_info("b", onnx.TensorProto.UINT8, b_dims) scales = onnx.helper.make_tensor_value_info("scales", onnx.TensorProto.FLOAT, scales_dims) zp = onnx.helper.make_tensor_value_info("zp", onnx.TensorProto.UINT8, zp_dims) c = onnx.helper.make_tensor_value_info("c", onnx.TensorProto.FLOAT, out_dims) node = onnx.helper.make_node("MatMulNBits", inputs=["a", "b", "scales", "zp"], outputs=["c"], bits=bits, block_size=block_size, K=16, N=4, domain='com.microsoft') return ([node], [a, b, scales, zp], [c]) @onnx_test() def matmulnbits_invalid_bits_value_test(): return matmulnbits_negative_test(bits=5) @onnx_test() def matmulnbits_block_size_too_small_test(): return matmulnbits_negative_test(block_size=8) @onnx_test() def matmulnbits_block_size_not_power_of_two_test(): return matmulnbits_negative_test(block_size=20) @onnx_test() def matmulnbits_invalid_b_dims_test(): return matmulnbits_negative_test(b_dims=[4, 2, 8]) @onnx_test() def matmulnbits_invalid_scales_dims_test(): return matmulnbits_negative_test(scales_dims=[3]) @onnx_test() def matmulnbits_invalid_zp_dims_test(): return matmulnbits_negative_test(zp_dims=[5]) @onnx_test() def qlinearmul_test(): a = helper.make_tensor_value_info('A', TensorProto.UINT8, [64]) sc_a = helper.make_tensor('A_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_a = helper.make_tensor('A_zero_point', TensorProto.UINT8, [], [0]) b = helper.make_tensor_value_info('B', TensorProto.UINT8, [64]) sc_b = helper.make_tensor('B_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_b = helper.make_tensor('B_zero_point', TensorProto.UINT8, [], [16]) sc_c = helper.make_tensor('C_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_c = helper.make_tensor('C_zero_point', TensorProto.UINT8, [], [100]) c = helper.make_tensor_value_info('C', TensorProto.UINT8, [64]) node = onnx.helper.make_node( 'QLinearMul', inputs=[ 'A', 'A_scale', 'A_zero_point', 'B', 'B_scale', 'B_zero_point', 'C_scale', 'C_zero_point' ], outputs=['C'], ) return ([node], [a, b], [c], [sc_a, zero_pt_a, sc_b, zero_pt_b, sc_c, zero_pt_c]) @onnx_test() def qlinearmul_bcast_test(): a = helper.make_tensor_value_info('A', TensorProto.INT8, [64]) sc_a = helper.make_tensor('A_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_a = helper.make_tensor('A_zero_point', TensorProto.INT8, [], [0]) b = helper.make_tensor_value_info('B', TensorProto.INT8, [1, 1, 64]) sc_b = helper.make_tensor('B_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_b = helper.make_tensor('B_zero_point', TensorProto.INT8, [], [128]) sc_c = helper.make_tensor('C_scale', TensorProto.FLOAT, [], [0.15]) zero_pt_c = helper.make_tensor('C_zero_point', TensorProto.INT8, [], [32]) c = helper.make_tensor_value_info('C', TensorProto.INT8, [1, 1, 64]) node = onnx.helper.make_node( 'QLinearMul', inputs=[ 'A', 'A_scale', 'A_zero_point', 'B', 'B_scale', 'B_zero_point', 'C_scale', 'C_zero_point' ], outputs=['C'], ) return ([node], [a, b], [c], [sc_a, zero_pt_a, sc_b, zero_pt_b, sc_c, zero_pt_c]) @onnx_test() def qlinearsigmoid_test(): x = helper.make_tensor_value_info('X', TensorProto.INT8, [64]) sc_x = helper.make_tensor('X_scale', TensorProto.FLOAT, [], [0.05]) zero_pt_x = helper.make_tensor('X_zero_point', TensorProto.INT8, [], [0]) sc_y = helper.make_tensor('Y_scale', TensorProto.FLOAT, [], [0.0035]) zero_pt_y = helper.make_tensor('Y_zero_point', TensorProto.INT8, [], [-128]) y = helper.make_tensor_value_info('Y', TensorProto.INT8, [64]) node = onnx.helper.make_node( 'QLinearSigmoid', inputs=['X', 'X_scale', 'X_zero_point', 'Y_scale', 'Y_zero_point'], outputs=['Y'], ) return ([node], [x], [y], [sc_x, zero_pt_x, sc_y, zero_pt_y]) @onnx_test() def quantizelinear_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1]) arg_out = helper.make_tensor_value_info('out', TensorProto.INT8, [5]) node = onnx.helper.make_node( 'QuantizeLinear', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def quantizelinear_int32_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.INT32, [5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1]) arg_out = helper.make_tensor_value_info('out', TensorProto.INT8, [5]) node = onnx.helper.make_node( 'QuantizeLinear', inputs=['0', '1'], outputs=['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def quantizelinear_zero_point_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1]) arg2 = helper.make_tensor_value_info('2', TensorProto.INT8, [1]) arg_out = helper.make_tensor_value_info('out', TensorProto.INT8, [5]) node = onnx.helper.make_node( 'QuantizeLinear', inputs=['0', '1', '2'], outputs=['out'], ) return ([node], [arg0, arg1, arg2], [arg_out]) def make_quantizelinear_axis_graph(axis): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 1, 5, 1]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [5]) arg2 = helper.make_tensor_value_info('2', TensorProto.INT8, [5]) arg_out = helper.make_tensor_value_info('out', TensorProto.INT8, [1, 1, 5, 1]) node = onnx.helper.make_node('QuantizeLinear', inputs=['0', '1', '2'], outputs=['out'], axis=axis) return ([node], [arg0, arg1, arg2], [arg_out]) @onnx_test() def quantizelinear_axis_test(): return make_quantizelinear_axis_graph(2) @onnx_test() def quantizelinear_neg_axis_test(): return make_quantizelinear_axis_graph(-2) @onnx_test() def quantizelinear_mxfp4_even_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 64, 4, 4]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 2, 4, 4]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT4E2M1, [3, 64, 4, 4]) node = onnx.helper.make_node( 'QuantizeLinear', inputs = ['0', '1'], axis = 1, block_size = 32, output_dtype = 23, outputs = ['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def quantizelinear_mxfp4_odd_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 64, 4, 7]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 2, 4, 7]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT4E2M1, [3, 64, 4, 7]) node = onnx.helper.make_node( 'QuantizeLinear', inputs = ['0', '1'], axis = 1, block_size = 32, output_dtype = 23, outputs = ['out'], ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def randomnormal_test(): dtype = 11 mean = 10.0 scale = 1.5 seed = 0.0 shape = [2, 3, 4] output = helper.make_tensor_value_info('output', TensorProto.DOUBLE, [2, 3, 4]) node = onnx.helper.make_node('RandomNormal', inputs=[], outputs=['output'], dtype=dtype, mean=mean, scale=scale, seed=seed, shape=shape) return ([node], [], [output]) @onnx_test() def randomnormal_dtype_error_test(): dtype = 6 shape = [2, 3, 4] output = helper.make_tensor_value_info('output', TensorProto.INT32, [2, 3, 4]) node = onnx.helper.make_node('RandomNormal', inputs=[], outputs=['output'], dtype=dtype, shape=shape) return ([node], [], [output]) @onnx_test() def randomnormal_generated_seed_test(): sample_size = 10 input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [1, 10]) output = helper.make_tensor_value_info("output", TensorProto.INT32, [1, 10]) node = onnx.helper.make_node('RandomNormal', inputs=['input'], sample_size=sample_size, outputs=['output']) return ([node], [input], [output]) @onnx_test() def randomnormal_shape_error_test(): dtype = 1 output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node('RandomNormal', inputs=[], outputs=['output'], dtype=dtype) return ([node], [], [output]) @onnx_test() def randomnormallike_test(): dtype = 10 mean = 10.0 scale = 1.5 seed = 0.0 input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [2, 3, 4]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [2, 3, 4]) node = onnx.helper.make_node('RandomNormalLike', inputs=['input'], outputs=['output'], dtype=dtype, mean=mean, scale=scale, seed=seed) return ([node], [input], [output]) @onnx_test() def randomnormallike_type_error_test(): seed = 0 input = helper.make_tensor_value_info('input', TensorProto.INT32, [2, 3, 4]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node('RandomNormalLike', inputs=['input'], outputs=['output'], seed=seed) return ([node], [input], [output]) @onnx_test() def randomuniform_test(): dtype = 11 high = 1.0 low = 0.0 seed = 0.0 shape = [2, 3, 4] output = helper.make_tensor_value_info('output', TensorProto.DOUBLE, [2, 3, 4]) node = onnx.helper.make_node('RandomUniform', inputs=[], outputs=['output'], dtype=dtype, high=high, low=low, seed=seed, shape=shape) return ([node], [], [output]) @onnx_test() def randomuniform_dtype_error_test(): dtype = 6 shape = [2, 3, 4] output = helper.make_tensor_value_info('output', TensorProto.INT32, [2, 3, 4]) node = onnx.helper.make_node('RandomUniform', inputs=[], outputs=['output'], dtype=dtype, shape=shape) return ([node], [], [output]) @onnx_test() def randomuniform_generated_seed_test(): sample_size = 10 input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [1, 10]) output = helper.make_tensor_value_info("output", TensorProto.INT32, [1, 10]) node = onnx.helper.make_node('RandomUniform', inputs=['input'], sample_size=sample_size, outputs=['output']) return ([node], [input], [output]) @onnx_test() def randomuniform_shape_error_test(): dtype = 1 output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node('RandomUniform', inputs=[], outputs=['output'], dtype=dtype) return ([node], [], [output]) @onnx_test() def randomuniformlike_test(): dtype = 10 high = 10.0 low = 1.0 seed = 0.0 input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [2, 3, 4]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [2, 3, 4]) node = onnx.helper.make_node('RandomUniformLike', inputs=['input'], outputs=['output'], dtype=dtype, high=high, low=low, seed=seed) return ([node], [input], [output]) @onnx_test() def randomuniformlike_type_error_test(): seed = 0 input = helper.make_tensor_value_info('input', TensorProto.INT32, [2, 3, 4]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [2, 3, 4]) node = onnx.helper.make_node('RandomUniformLike', inputs=['input'], outputs=['output'], seed=seed) return ([node], [input], [output]) @onnx_test() def range_test(): start_val = np.array([10]) limit_val = np.array([6]) delta_val = np.array([-3]) start_tensor = helper.make_tensor(name='start_val', data_type=TensorProto.INT64, dims=start_val.reshape(()).shape, vals=start_val.astype(np.int64)) start = onnx.helper.make_node('Constant', inputs=[], outputs=['start'], value=start_tensor) limit_tensor = helper.make_tensor(name='limit_val', data_type=TensorProto.INT64, dims=limit_val.reshape(()).shape, vals=limit_val.astype(np.int64)) limit = onnx.helper.make_node('Constant', inputs=[], outputs=['limit'], value=limit_tensor) delta_tensor = helper.make_tensor(name='delta_val', data_type=TensorProto.INT64, dims=delta_val.reshape(()).shape, vals=delta_val.astype(np.int64)) delta = onnx.helper.make_node('Constant', inputs=[], outputs=['delta'], value=delta_tensor) node = onnx.helper.make_node('Range', inputs=['start', 'limit', 'delta'], outputs=['1']) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) return ([start, limit, delta, node], [], [y]) @onnx_test() def range_float_test(): start_val = np.array([2]) limit_val = np.array([11]) delta_val = np.array([2]) start_tensor = helper.make_tensor(name='start_val', data_type=TensorProto.FLOAT, dims=start_val.reshape(()).shape, vals=start_val.astype(np.float)) start = onnx.helper.make_node('Constant', inputs=[], outputs=['start'], value=start_tensor) limit_tensor = helper.make_tensor(name='limit_val', data_type=TensorProto.FLOAT, dims=limit_val.reshape(()).shape, vals=limit_val.astype(np.float)) limit = onnx.helper.make_node('Constant', inputs=[], outputs=['limit'], value=limit_tensor) delta_tensor = helper.make_tensor(name='delta_val', data_type=TensorProto.FLOAT, dims=delta_val.reshape(()).shape, vals=delta_val.astype(np.float)) delta = onnx.helper.make_node('Constant', inputs=[], outputs=['delta'], value=delta_tensor) node = onnx.helper.make_node('Range', inputs=['start', 'limit', 'delta'], outputs=['1']) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) return ([start, limit, delta, node], [], [y]) @onnx_test() def recip_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( 'Reciprocal', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) def reduceop_variable_axes_test(op_name, axes_len=1, keepdims=1, noop_with_empty_axes=0): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) axes = helper.make_tensor_value_info('axes', TensorProto.INT64, [axes_len]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 1, 6]) node = onnx.helper.make_node(op_name, inputs=['x', 'axes'], outputs=['y'], keepdims=keepdims, noop_with_empty_axes=noop_with_empty_axes) return ([node], [x, axes], [y]) @onnx_test() def reducel1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 6]) axes = [-2] node = onnx.helper.make_node('ReduceL1', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test def reducel1_dyn_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None]) axes = [-2] node = onnx.helper.make_node('ReduceL1', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test def reducel1_dyn_noaxes_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None]) node = onnx.helper.make_node('ReduceL1', inputs=['x'], outputs=['y'], keepdims=0) return ([node], [x], [y]) @onnx_test() def reducel2_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 5]) axes = [-1] node = onnx.helper.make_node('ReduceL2', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test() def reduce_log_sum_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 1, 5, 6]) axes = [-3] node = onnx.helper.make_node('ReduceLogSum', inputs=['x'], outputs=['y'], axes=axes, keepdims=1) return ([node], [x], [y]) @onnx_test() def reduce_log_sum_exp_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 5, 6]) axes = [-4] node = onnx.helper.make_node('ReduceLogSumExp', inputs=['x'], outputs=['y'], axes=axes, keepdims=1) return ([node], [x], [y]) @onnx_test() def reducemax_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 6]) axes = [2] node = onnx.helper.make_node('ReduceMax', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test() def reducemax_fp8_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT8E4M3FNUZ, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT8E4M3FNUZ, [3, 4, 6]) axes = [2] node = onnx.helper.make_node('ReduceMax', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test def reducemax_dyn_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, 4, 6]) axes = [2] node = onnx.helper.make_node('ReduceMax', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test() def reducemean_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) axes = [2, 3] node = onnx.helper.make_node('ReduceMean', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test() def reducemean_keepdims_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 1, 6]) axes = [2] node = onnx.helper.make_node('ReduceMean', inputs=['x'], outputs=['y'], axes=axes, keepdims=1) return ([node], [x], [y]) @onnx_test() def reducemin_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 1, 5, 1]) axes = [1, 3] node = onnx.helper.make_node('ReduceMin', inputs=['x'], outputs=['y'], axes=axes, keepdims=1) return ([node], [x], [y]) @onnx_test() def reduceprod_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 1, 6]) axes = [2] node = onnx.helper.make_node('ReduceProd', inputs=['x'], outputs=['y'], axes=axes, keepdims=1) return ([node], [x], [y]) @onnx_test() def reducesum_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 1, 6]) node = onnx.helper.make_node('ReduceSum', inputs=['x'], outputs=['y'], axes=[2], keepdims=0) return ([node], [x], [y]) @onnx_test() def reducesum_fp8_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT8E4M3FNUZ, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT8E4M3FNUZ, [3, 4, 1, 6]) node = onnx.helper.make_node('ReduceSum', inputs=['x'], outputs=['y'], axes=[2], keepdims=0) return ([node], [x], [y]) @onnx_test() def reducesum_empty_axes_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 1, 6]) axes = np.array([], dtype=np.int64) axes_tensor = helper.make_tensor(name="axes", data_type=TensorProto.INT64, dims=axes.shape, vals=axes.astype(np.int64)) node = onnx.helper.make_node('ReduceSum', inputs=['x', 'axes'], outputs=['y'], keepdims=0, noop_with_empty_axes=False) return ([node], [x], [y], [axes_tensor]) @onnx_test() def reducesum_noop_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 1, 6]) axes = np.array([], dtype=np.int64) axes_tensor = helper.make_tensor(name="axes", data_type=TensorProto.INT64, dims=axes.shape, vals=axes.astype(np.int64)) node = onnx.helper.make_node('ReduceSum', inputs=['x', 'axes'], outputs=['y'], keepdims=0, noop_with_empty_axes=True) return ([node], [x], [y], [axes_tensor]) @onnx_test() def reducesum_keepdims_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 1, 1]) axes = [2, 3] node = onnx.helper.make_node('ReduceSum', inputs=['x'], outputs=['y'], axes=axes, keepdims=1) return ([node], [x], [y]) @onnx_test() def reducesum_multiaxis_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 1, 1]) axes = [2, 3] node = onnx.helper.make_node('ReduceSum', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test() def reducesum_variable_axes_test(): return reduceop_variable_axes_test('ReduceSum') @onnx_test() def reducesum_variable_axes_noop_test(): return reduceop_variable_axes_test('ReduceSum', noop_with_empty_axes=1) @onnx_test() def reducesum_variable_axes_keepdims_clear_test(): return reduceop_variable_axes_test('ReduceSum', keepdims=0) @onnx_test() def reducesum_variable_dynamic_axes_test(): return reduceop_variable_axes_test('ReduceSum', None) @onnx_test() def reducesum_variable_dynamic_axes_verify_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 2]) axes = helper.make_tensor_value_info('axes', TensorProto.INT64, [None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None]) node = onnx.helper.make_node('ReduceSum', inputs=['x', 'axes'], outputs=['y']) return ([node], [x, axes], [y]) @onnx_test() def reducesum_variable_dynamic_axes_noop_set_verify_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 2]) axes = helper.make_tensor_value_info('axes', TensorProto.INT64, [None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None]) node = onnx.helper.make_node('ReduceSum', inputs=['x', 'axes'], outputs=['y'], noop_with_empty_axes=1) return ([node], [x, axes], [y]) @onnx_test() def reducesum_square_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 6]) axes = [-2] node = onnx.helper.make_node('ReduceSumSquare', inputs=['x'], outputs=['y'], axes=axes, keepdims=0) return ([node], [x], [y]) @onnx_test() def reshape_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 2, 3]) x_shape = helper.make_tensor_value_info('1', TensorProto.INT64, [2]) x_shape_list = [3, 8] y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3, 8]) y2 = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3, 8]) node = onnx.helper.make_node('Reshape', inputs=['0', '1'], outputs=['2']) node2 = onnx.helper.make_node('Reshape', inputs=['0'], shape=x_shape_list, outputs=['3']) return ([node, node2], [x, x_shape], [y, y2], [helper.make_tensor('1', TensorProto.INT64, [2], [3, 8])]) @onnx_test() def reshape_non_standard_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 3, 2]) trans = helper.make_node( 'Transpose', inputs=['x'], outputs=['trans_x'], perm=[0, 2, 1], ) res = onnx.helper.make_node('Reshape', inputs=['trans_x'], outputs=['y'], shape=[4, 3, 2]) return ([trans, res], [x], [y]) @onnx_test() def reshape_variable_input_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 2, 3]) x_shape = helper.make_tensor_value_info('1', TensorProto.INT64, [2]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3, 8]) node = onnx.helper.make_node('Reshape', inputs=['0', '1'], outputs=['2']) return ([node], [x, x_shape], [y]) @onnx_test() def reshape_variable_input_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 2, 3]) x_shape = helper.make_tensor_value_info('1', TensorProto.INT64, [2]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [None, 6]) node = onnx.helper.make_node('Reshape', inputs=['0', '1'], outputs=['2']) return ([node], [x, x_shape], [y]) @onnx_test() def resize_downsample_c_test(): scales = np.array([1.0, 1.0, 0.6, 0.6], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 1, 2]) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='asymmetric', mode='nearest', nearest_mode='ceil') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_downsample_f_test(): scales = np.array([1.0, 1.0, 0.6, 0.6], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node( 'Resize', inputs=['X', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='align_corners', mode='nearest', nearest_mode='floor') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_downsample_f_dyn_test(): # scales is a compile-time input scales = np.array([1.0, 1.0, 0.601, 0.601], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [None, 1, 5, 9]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='asymmetric', mode='nearest', nearest_mode='floor') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_downsample_f_dyn2_test(): # output shape is an input sizes = np.array([2, 1, 3, 5], dtype=np.int64) sizes_tensor = helper.make_tensor(name='sizes', data_type=TensorProto.INT64, dims=sizes.shape, vals=sizes.flatten().astype(np.int64)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [None, 1, 5, 9]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', '', 'sizes'], outputs=['Y'], coordinate_transformation_mode='asymmetric', mode='nearest', nearest_mode='floor') return ([node], [X], [Y], [sizes_tensor]) @onnx_test() def resize_downsample_f_dyn3_test(): # scales is a runtime input scalesX = helper.make_tensor_value_info('scales', TensorProto.FLOAT, [4]) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [None, 1, 5, 9]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='asymmetric', mode='nearest', nearest_mode='floor') return ([node], [X, scalesX], [Y]) @onnx_test() def resize_downsample_f_ref_test(): # Same as resize_downsample_f_dyn_test but with static input scales = np.array([1.0, 1.0, 0.601, 0.601], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [2, 1, 5, 9]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='asymmetric', mode='nearest', nearest_mode='floor') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_downsample_f_ref2_test(): # output shape is an input sizes = np.array([2, 1, 3, 5], dtype=np.int64) sizes_tensor = helper.make_tensor(name='sizes', data_type=TensorProto.INT64, dims=sizes.shape, vals=sizes.flatten().astype(np.int64)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [2, 1, 5, 9]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', '', 'sizes'], outputs=['Y'], coordinate_transformation_mode='asymmetric', mode='nearest', nearest_mode='floor') return ([node], [X], [Y], [sizes_tensor]) @onnx_test() def resize_dyn_err1_test(): scales = np.array([1.601], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [None, 3]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='half_pixel', mode='nearest', nearest_mode='round_prefer_ceil') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_upsample_f_dyn_test(): scales = np.array([1.0, 1.0, 1.601, 1.601], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [None, 1, 3, 5]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='half_pixel', mode='nearest', nearest_mode='round_prefer_ceil') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_no_scale_test(): # node has no scales or shapes input scales = np.array([1.0, 1.0, 1.601, 1.601], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [None, 1, 3, 5]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', ''], outputs=['Y'], coordinate_transformation_mode='half_pixel', mode='nearest', nearest_mode='round_prefer_ceil') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_downsample_linear_test(): scales = np.array([1.0, 1.0, 0.6, 0.5], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], mode='linear') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_downsample_linear_half_invalid_scale_test(): scales = np.array([1.0, 1.0, 0.6, 0.5], dtype=np.float16) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT16, dims=scales.shape, vals=scales.flatten().astype(np.float16)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT16, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT16, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], mode='linear') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_downsample_linear_half_test(): scales = np.array([1.0, 1.0, 0.6, 0.5], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT16, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT16, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], mode='linear') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_linear_non_const_test(): # scales is a runtime input scalesX = helper.make_tensor_value_info('scales', TensorProto.FLOAT, [4]) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], mode='linear') return ([node], [X, scalesX], [Y]) @onnx_test() def resize_nonstd_input_test(): scales = np.array([1.0, 1.0, 0.6, 0.6], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 4, 2]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 1, 2]) trn = onnx.helper.make_node('Transpose', inputs=['X'], outputs=['TX'], perm=[0, 1, 3, 2]) node = onnx.helper.make_node('Resize', inputs=['TX', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='asymmetric', mode='nearest', nearest_mode='ceil') return ([trn, node], [X], [Y], [scale_tensor]) @onnx_test() def resize_outsize_test(): out_lens = np.array([1, 1, 4, 6], dtype=np.int64) out_lens_tensor = helper.make_tensor(name='out_lens', data_type=TensorProto.INT64, dims=out_lens.shape, vals=out_lens.flatten().astype( np.int64)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 2]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 4, 6]) node = onnx.helper.make_node( 'Resize', inputs=['X', '', '', 'out_lens'], outputs=['Y'], coordinate_transformation_mode='tf_half_pixel_for_nn', mode='nearest', nearest_mode='round_prefer_floor') return ([node], [X], [Y], [out_lens_tensor]) @onnx_test() def resize_upsample_linear_ac_test(): scales = np.array([1.0, 1.0, 2.0, 2.0], dtype=np.float32) scales_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype( np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 2]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node( 'Resize', inputs=['X', '', 'scales'], outputs=['Y'], mode='linear', coordinate_transformation_mode='align_corners') return ([node], [X], [Y], [scales_tensor]) @onnx_test() def resize_upsample_linear_test(): scales = np.array([1.0, 1.0, 2.0, 2.0], dtype=np.float32) scales_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype( np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 2]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], mode='linear') return ([node], [X], [Y], [scales_tensor]) @onnx_test() def resize_upsample_linear_large_test(): x = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 1024, 1024]) s = helper.make_tensor('scales', TensorProto.FLOAT, [4], [1, 1, 2, 2]) y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 2048, 2048]) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], mode='linear') return ([node], [x], [y], [s]) @onnx_test() def resize_upsample_pf_test(): scales = np.array([1.0, 1.0, 2.0, 3.0], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 2]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 4, 6]) node = onnx.helper.make_node('Resize', inputs=['X', '', 'scales'], outputs=['Y'], mode='nearest') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_upsample_pc_test(): scales = np.array([1.0, 1.0, 2.0, 1.5], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 4, 6]) node = onnx.helper.make_node( 'Resize', inputs=['X', '', 'scales'], outputs=['Y'], coordinate_transformation_mode='pytorch_half_pixel', mode='nearest', exclude_outside=0, nearest_mode='round_prefer_ceil') return ([node], [X], [Y], [scale_tensor]) @onnx_test() def resize_aspect_ratio_err_test(): sizes = np.array([1, 1, 3, 5], dtype=np.int64) size_tensor = helper.make_tensor(name='sizes', data_type=TensorProto.INT64, dims=sizes.shape, vals=sizes.flatten().astype(np.int64)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 3, 5]) node = onnx.helper.make_node('Resize', inputs=['X', '', '', 'sizes'], outputs=['Y'], coordinate_transformation_mode='asymmetric', mode='nearest', keep_aspect_ratio_policy='stretch', nearest_mode='ceil') return ([node], [X], [Y], [size_tensor]) @onnx_test() def resize_roi_skip_test(): # special testcase for resize op with roi value but skip it scales = np.array([1.0, 1.0, 2.0, 2.0], dtype=np.float32) roi = np.array([1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) roi_tensor = helper.make_tensor(name='roi', data_type=TensorProto.FLOAT, dims=roi.shape, vals=roi.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 4]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 4, 8]) node = onnx.helper.make_node('Resize', inputs=['X', 'roi', 'scales'], outputs=['Y'], mode='nearest') return ([node], [X], [Y], [roi_tensor, scale_tensor]) @onnx_test() def resize_with_same_inout_shapes_test(): sizes = np.array([1, 3, 5], dtype=np.int64) sizes_tensor = helper.make_tensor(name='sizes', data_type=TensorProto.INT64, dims=sizes.shape, vals=sizes.flatten().astype(np.int64)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 3, 5]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 3, 5]) node = onnx.helper.make_node('Resize', inputs=['X', '', '', 'sizes'], outputs=['Y'], coordinate_transformation_mode='half_pixel', mode='linear', nearest_mode='floor') return ([node], [X], [Y], [sizes_tensor]) @onnx_test() def reversesequence_4D_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 2, 2]) node = onnx.helper.make_node( 'ReverseSequence', inputs=['x'], outputs=['y'], time_axis=0, batch_axis=1, sequence_lens=[2, 1], ) return ([node], [x], [y]) @onnx_test() def reversesequence_batch_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [4, 4]) seq_lens = np.array([1, 2, 3, 4]) seq_lens_tensor = helper.make_tensor( name="sequence_lens", data_type=TensorProto.INT64, dims=seq_lens.shape, vals=seq_lens.astype(np.int64), ) arg_seq_lens = helper.make_node( "Constant", inputs=[], outputs=['arg_seq_lens'], value=seq_lens_tensor, ) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 4]) node = onnx.helper.make_node( 'ReverseSequence', inputs=['x', 'arg_seq_lens'], outputs=['y'], time_axis=1, batch_axis=0, ) return ([arg_seq_lens, node], [x], [y]) @onnx_test() def reversesequence_batch_axis_err_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [4, 4, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 4, 2]) node = onnx.helper.make_node( 'ReverseSequence', inputs=['x'], outputs=['y'], time_axis=0, batch_axis=2, sequence_lens=[4, 3, 2, 1], ) return ([node], [x], [y]) @onnx_test() def reversesequence_rank_err_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( 'ReverseSequence', inputs=['x'], outputs=['y'], sequence_lens=[4, 3, 2, 1], ) return ([node], [x], [y]) @onnx_test() def reversesequence_sequence_lens_shape_err_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [4, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 4]) node = onnx.helper.make_node( 'ReverseSequence', inputs=['x'], outputs=['y'], sequence_lens=[4, 3, 2], ) return ([node], [x], [y]) @onnx_test() def reversesequence_same_axis_err_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [4, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 4]) node = onnx.helper.make_node( 'ReverseSequence', inputs=['x'], outputs=['y'], time_axis=1, batch_axis=1, sequence_lens=[4, 3, 2, 1], ) return ([node], [x], [y]) @onnx_test() def reversesequence_time_axis_err_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [4, 4, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 4, 2, 3]) node = onnx.helper.make_node( 'ReverseSequence', inputs=['x'], outputs=['y'], time_axis=3, batch_axis=0, sequence_lens=[4, 3, 2, 1], ) return ([node], [x], [y]) @onnx_test() def reversesequence_time_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [4, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [4, 4]) node = onnx.helper.make_node( 'ReverseSequence', inputs=['x'], outputs=['y'], time_axis=0, batch_axis=1, sequence_lens=[4, 3, 2, 1], ) return ([node], [x], [y]) @onnx_test() def rnn_bi_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [2, 20, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [2, 20, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [2, 40]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 2, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 2, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 2, 20]) node = onnx.helper.make_node( 'RNN', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0'], outputs=['hs', 'output'], activations=['tanh', 'sigmoid'], clip=0, direction='bidirectional', hidden_size=20, layout=1) return ([node], [seq, w, r, bias, seq_len, h0], [hs, output]) @onnx_test() def rnn_bi_1af_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [5, 3, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [2, 20, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [2, 20, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [5, 2, 3, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [2, 3, 20]) node = onnx.helper.make_node('RNN', inputs=['seq', 'w', 'r'], outputs=['hs', 'output'], activations=['tanh'], clip=0, direction='bidirectional', hidden_size=20) return ([node], [seq, w, r], [hs, output]) @onnx_test() def rnn_f_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 20, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 20, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 40]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 1, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node( 'RNN', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0'], outputs=['hs', 'output'], activations=['tanh'], clip=0, direction='forward', hidden_size=20, layout=1) return ([node], [seq, w, r, bias, seq_len, h0], [hs, output]) @onnx_test() def rnn_f_5arg_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 20, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 20, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 40]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node('RNN', inputs=['seq', 'w', 'r', 'bias', 'seq_len'], outputs=['hs', 'output'], activations=['tanh'], clip=0, direction='forward', hidden_size=20, layout=1) return ([node], [seq, w, r, bias, seq_len], [hs, output]) @onnx_test() def rnn_f_default_af_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [5, 3, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 20, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 20, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [5, 1, 3, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 3, 20]) node = onnx.helper.make_node('RNN', inputs=['seq', 'w', 'r'], outputs=['hs', 'output'], clip=0, direction='forward', hidden_size=20) return ([node], [seq, w, r], [hs, output]) @onnx_test() def rnn_r_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 20, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 20, 20]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [1, 40]) seq_len = helper.make_tensor_value_info('seq_len', TensorProto.INT32, [3]) h0 = helper.make_tensor_value_info('h0', TensorProto.FLOAT, [3, 1, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node( 'RNN', inputs=['seq', 'w', 'r', 'bias', 'seq_len', 'h0'], outputs=['hs', 'output'], activations=['tanh'], clip=0, direction='reverse', hidden_size=20, layout=1) return ([node], [seq, w, r, bias, seq_len, h0], [hs, output]) @onnx_test() def rnn_r_3arg_layout_test(): seq = helper.make_tensor_value_info('seq', TensorProto.FLOAT, [3, 5, 10]) w = helper.make_tensor_value_info('w', TensorProto.FLOAT, [1, 20, 10]) r = helper.make_tensor_value_info('r', TensorProto.FLOAT, [1, 20, 20]) hs = helper.make_tensor_value_info('hs', TensorProto.FLOAT, [3, 5, 1, 20]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [3, 1, 20]) node = onnx.helper.make_node('RNN', inputs=['seq', 'w', 'r'], outputs=['hs', 'output'], activations=['tanh'], clip=0, direction='reverse', hidden_size=20, layout=1) return ([node], [seq, w, r], [hs, output]) @onnx_test() def roialign_default_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 4, 7, 8]) roi = helper.make_tensor_value_info('rois', TensorProto.FLOAT, [8, 4]) bi = helper.make_tensor_value_info('batch_ind', TensorProto.INT64, [8]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [8, 4, 1, 1]) node = onnx.helper.make_node('RoiAlign', inputs=['x', 'rois', 'batch_ind'], outputs=['y']) return ([node], [x, roi, bi], [y]) @onnx_test() def roialign_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 5, 4, 7]) roi = helper.make_tensor_value_info('rois', TensorProto.FLOAT, [8, 4]) bi = helper.make_tensor_value_info('batch_ind', TensorProto.INT64, [8]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [8, 4, 5, 5]) node = onnx.helper.make_node( 'RoiAlign', inputs=['x', 'rois', 'batch_ind'], outputs=['y'], spatial_scale=2.0, output_height=5, output_width=5, sampling_ratio=3, mode="avg", coordinate_transformation_mode="output_half_pixel") return ([node], [x, roi, bi], [y]) @onnx_test() def rotary_embedding_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 18]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 2]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [4, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 18]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_interleaved_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 3, 8]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [8, 2]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [8, 2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 3, 8]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=1, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_interleaved_large_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [2, 8, 24]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [16, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [16, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [2, 8, 24]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=1, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_dim_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 6]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [2, 2]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [2, 2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 6]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, rotary_embedding_dim=4, num_heads=1, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_packed_batching_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 3, 6]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 3]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [2, 2]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [2, 2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 3, 6]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, rotary_embedding_dim=4, num_heads=1, is_packed_batching=1, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_float_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT, [1, 3, 2, 6]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 2]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT, [4, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 3, 2, 6]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_scale_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 18]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 2]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [4, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 18]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, scale=0.0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_num_heads_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 18]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 2]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [4, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 18]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, num_heads=0, rotary_embedding_dim=2, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_input_dims_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 1, 1, 1, 1]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 1]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [1, 1]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [1, 1]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 1, 1, 1]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_pos_ids_1_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 18]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 1, 1]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [4, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 18]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_pos_ids_2_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 18]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [2]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [4, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 18]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_pos_ids_3_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [2, 2, 18]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 1]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [4, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 18]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_dim_size_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 18]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 2]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [4, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 18]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, num_heads=3, rotary_embedding_dim=8, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_cache_1_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 18]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 2]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [2, 3]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 3]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 18]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_cache_2_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 8]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 2]) cos_cache = helper.make_tensor_value_info('cos_cache', TensorProto.FLOAT16, [4, 4]) sin_cache = helper.make_tensor_value_info('sin_cache', TensorProto.FLOAT16, [4, 6]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 8]) node = onnx.helper.make_node( 'RotaryEmbedding', inputs=['input', 'pos_ids', 'cos_cache', 'sin_cache'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids, cos_cache, sin_cache], [output]) @onnx_test() def rotary_embedding_wrong_n_inputs_test(): input = helper.make_tensor_value_info('input', TensorProto.FLOAT16, [1, 2, 8]) pos_ids = helper.make_tensor_value_info('pos_ids', TensorProto.INT32, [1, 2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT16, [1, 2, 8]) node = onnx.helper.make_node('RotaryEmbedding', inputs=['input', 'pos_ids'], outputs=['output'], interleaved=0, domain="com.microsoft") return ([node], [input, pos_ids], [output]) @onnx_test() def round_half_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [4, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [4, 4]) node = onnx.helper.make_node('Round', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def round_bf16_test(): x = helper.make_tensor_value_info('x', TensorProto.BFLOAT16, [4, 4]) y = helper.make_tensor_value_info('y', TensorProto.BFLOAT16, [4, 4]) node = onnx.helper.make_node('Round', inputs=['x'], outputs=['y']) return ([node], [x], [y]) def make_scatter_elements_test(reduction="none"): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 4, 5, 6]) i = helper.make_tensor_value_info('indices', TensorProto.INT32, [2, 3, 4, 5]) u = helper.make_tensor_value_info('update', TensorProto.FLOAT, [2, 3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4, 5, 6]) node = onnx.helper.make_node( 'ScatterElements', reduction=reduction, inputs=['data', 'indices', 'update'], outputs=['y'], axis=-2, ) return ([node], [x, i, u], [y]) @onnx_test() def scatter_add_test(): return make_scatter_elements_test("add") @onnx_test() def scatter_mul_test(): return make_scatter_elements_test("mul") @onnx_test() def scatter_min_test(): return make_scatter_elements_test("min") @onnx_test() def scatter_max_test(): return make_scatter_elements_test("max") @onnx_test() def scatter_none_test(): return make_scatter_elements_test() @onnx_test() def scatter_elements_invalid_reduction_test(): return make_scatter_elements_test("invalid") def make_scatternd_test(reduction="none"): data = helper.make_tensor_value_info('data', TensorProto.FLOAT, [2, 2, 2]) indices = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 1, 2]) updates = helper.make_tensor_value_info('updates', TensorProto.FLOAT, [2, 1, 2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [2, 2, 2]) node = onnx.helper.make_node('ScatterND', inputs=['data', 'indices', 'updates'], outputs=['output'], reduction=reduction) return ([node], [data, indices, updates], [output]) @onnx_test() def scatternd_add_test(): return make_scatternd_test("add") @onnx_test() def scatternd_mul_test(): return make_scatternd_test("mul") @onnx_test() def scatternd_max_test(): return make_scatternd_test("max") @onnx_test() def scatternd_min_test(): return make_scatternd_test("min") @onnx_test() def scatternd_test(): return make_scatternd_test() @onnx_test() def scatternd_invalid_reduction_test(): return make_scatternd_test("invalid") @onnx_test() def scatternd_dyn_test(): data = helper.make_tensor_value_info('data', TensorProto.FLOAT, [None, 2, 2]) indices = helper.make_tensor_value_info('indices', TensorProto.INT64, [None, 1, 2]) updates = helper.make_tensor_value_info('updates', TensorProto.FLOAT, [None, 1, 2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [None, 2, 2]) node = onnx.helper.make_node('ScatterND', inputs=['data', 'indices', 'updates'], outputs=['output']) return ([node], [data, indices, updates], [output]) @onnx_test() def selu_test(): x = helper.make_tensor_value_info('x', TensorProto.DOUBLE, [2, 3]) y = helper.make_tensor_value_info('y', TensorProto.DOUBLE, [2, 3]) node = onnx.helper.make_node('Selu', inputs=['x'], outputs=['y'], alpha=0.3, gamma=0.5) return ([node], [x], [y]) @onnx_test() def shape_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4, 5, 6]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [4]) node = onnx.helper.make_node( 'Shape', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def shape_dyn_test0(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, None, None]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [4]) node = onnx.helper.make_node( 'Shape', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def shape_dyn_test1(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, None, None]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2]) node = onnx.helper.make_node('Shape', inputs=['x'], outputs=['y'], start=2) return ([node], [x], [y]) @onnx_test() def shape_dyn_test2(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, None, None]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2]) node = onnx.helper.make_node('Shape', inputs=['x'], outputs=['y'], start=-2) return ([node], [x], [y]) @onnx_test() def shape_dyn_test3(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, None, None]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2]) node = onnx.helper.make_node('Shape', inputs=['x'], outputs=['y'], start=1, end=2) return ([node], [x], [y]) @onnx_test() def shape_end_oob_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, None, None]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2]) node = onnx.helper.make_node('Shape', inputs=['x'], outputs=['y'], end=5) return ([node], [x], [y]) @onnx_test() def shape_start_oob_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, None, None]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2]) node = onnx.helper.make_node('Shape', inputs=['x'], outputs=['y'], start=-6) return ([node], [x], [y]) @onnx_test() def shape_end_less_start_error(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 4, None, None]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2]) node = onnx.helper.make_node('Shape', inputs=['x'], outputs=['y'], start=3, end=1) return ([node], [x], [y]) @onnx_test() def shape_gather_test(): values = np.array([1]) # value = helper.make_tensor_value_info('value', TensorProto.INT32, [1]) x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [7, 3, 10]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [1]) value_tensor = helper.make_tensor(name='const_tensor', data_type=TensorProto.INT32, dims=values.shape, vals=values.flatten().astype(int)) node_const = onnx.helper.make_node( 'Constant', inputs=[], outputs=['value'], value=value_tensor, ) node_shape = onnx.helper.make_node( 'Shape', inputs=['x'], outputs=['y'], ) node_gather = helper.make_node( 'Gather', inputs=['y', 'value'], outputs=['z'], axis=0, ) return ([node_const, node_shape, node_gather], [x], [z]) @onnx_test() def shrink_hard_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5]) node = onnx.helper.make_node( "Shrink", inputs=["x"], outputs=["y"], lambd=1.5, ) return ([node], [x], [y]) @onnx_test() def shrink_soft_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5]) node = onnx.helper.make_node( "Shrink", inputs=["x"], outputs=["y"], lambd=1.5, bias=1.5, ) return ([node], [x], [y]) @onnx_test() def shrink_verify_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [5]) node = onnx.helper.make_node( "Shrink", inputs=["x"], outputs=["y"], lambd=-5.0, bias=1.0, ) return ([node], [x], [y]) @onnx_test() def shrink_verify2_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [5]) node = onnx.helper.make_node( "Shrink", inputs=["x"], outputs=["y"], lambd=-6.0, bias=5.0, ) return ([node], [x], [y]) @onnx_test() def shrink_int8_test(): x = helper.make_tensor_value_info('x', TensorProto.INT8, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.INT8, [3, 3]) node = onnx.helper.make_node( "Shrink", inputs=["x"], outputs=["y"], lambd=1.5, bias=1.5, ) return ([node], [x], [y]) @onnx_test() def shrink_fp8_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT8E4M3FNUZ, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT8E4M3FNUZ, [3, 3]) node = onnx.helper.make_node( "Shrink", inputs=["x"], outputs=["y"], lambd=1.5, bias=1.5, ) return ([node], [x], [y]) @onnx_test() def shrink_uint8_test(): x = helper.make_tensor_value_info('x', TensorProto.UINT8, [3, 3]) y = helper.make_tensor_value_info('y', TensorProto.UINT8, [3, 3]) node = onnx.helper.make_node( "Shrink", inputs=["x"], outputs=["y"], lambd=5.0, bias=-4.5, ) return ([node], [x], [y]) @onnx_test() def sign_test(): x = helper.make_tensor_value_info('x', TensorProto.DOUBLE, [10, 5]) y = helper.make_tensor_value_info('y', TensorProto.DOUBLE, [10, 5]) node = onnx.helper.make_node( 'Sign', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def simplified_layer_normalization_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SimplifiedLayerNormalization', inputs=['x', 'scale'], outputs=['y'], axis=-1, epsilon=1e-5, stash_type=1, ) return ([node], [x, scale], [y]) @onnx_test() def simplified_layer_normalization_4d_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 3, 5, 7]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT16, [2, 1, 5, 7]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 3, 5, 7]) node = onnx.helper.make_node( 'SimplifiedLayerNormalization', inputs=['x', 'scale'], outputs=['y'], axis=-1, epsilon=1e-5, stash_type=1, ) return ([node], [x, scale], [y]) @onnx_test() def simplified_layer_normalization_invalid_input_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [1, 2, 2, 4]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [1, 2, 2, 4]) node = onnx.helper.make_node( 'SimplifiedLayerNormalization', inputs=['x', 'scale'], outputs=['y'], ) return ([node], [x, scale], [y]) @onnx_test() def simplified_layer_normalization_invalid_n_args_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) scale = helper.make_tensor_value_info('scale', TensorProto.FLOAT16, [4]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT16, [1, 2, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SimplifiedLayerNormalization', inputs=['x', 'scale', 'bias'], outputs=['y'], ) return ([node], [x, scale, bias], [y]) @onnx_test() def sin_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Sin', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def sin_fp8_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT8E4M3FNUZ, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT8E4M3FNUZ, [10]) node = onnx.helper.make_node( 'Sin', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def sinh_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Sinh', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def sinh_dynamic_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None]) node = onnx.helper.make_node( 'Sinh', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def size_float_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 3, 4]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [1]) node = onnx.helper.make_node( 'Size', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def size_half_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [3, 1]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [1]) node = onnx.helper.make_node( 'Size', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def size_bf16_test(): x = helper.make_tensor_value_info('x', TensorProto.BFLOAT16, [3, 1]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [1]) node = onnx.helper.make_node( 'Size', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def size_int_test(): x = helper.make_tensor_value_info('x', TensorProto.INT32, [8, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [1]) node = onnx.helper.make_node( 'Size', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def size_fp8_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT8E4M3FNUZ, [2, 5, 3]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [1]) node = onnx.helper.make_node( 'Size', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def size_verify_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 5, 3]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [1]) node = onnx.helper.make_node( 'Size', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def skip_simplified_layer_normalization_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipSimplifiedLayerNormalization', inputs=['x', 'skip', 'gamma'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_simplified_layer_normalization_bias_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipSimplifiedLayerNormalization', inputs=['x', 'skip', 'gamma', 'bias'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma, bias], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_simplified_layer_normalization_invalid_n_args_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node('SkipSimplifiedLayerNormalization', inputs=['x', 'skip'], outputs=['y'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip], [y]) @onnx_test() def skip_simplified_layer_normalization_invalid_input_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [2, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 2, 4]) node = onnx.helper.make_node('SkipSimplifiedLayerNormalization', inputs=['x', 'skip', 'gamma'], outputs=['y'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma], [y]) @onnx_test() def skip_layer_normalization_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipLayerNormalization', inputs=['x', 'skip', 'gamma'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_layer_normalization_2d_skip_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipLayerNormalization', inputs=['x', 'skip', 'gamma'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_layer_normalization_skip_batch_size_1_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [1, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipLayerNormalization', inputs=['x', 'skip', 'gamma'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_layer_normalization_beta_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) beta = helper.make_tensor_value_info('beta', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipLayerNormalization', inputs=['x', 'skip', 'gamma', 'beta'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma, beta], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_layer_normalization_invalid_beta_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) beta = helper.make_tensor_value_info('beta', TensorProto.FLOAT16, [4, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipLayerNormalization', inputs=['x', 'skip', 'gamma', 'beta'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma, beta], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_layer_normalization_beta_bias_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) beta = helper.make_tensor_value_info('beta', TensorProto.FLOAT16, [4]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT16, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipLayerNormalization', inputs=['x', 'skip', 'gamma', 'beta', 'bias'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma, beta, bias], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_layer_normalization_invalid_bias_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [4]) beta = helper.make_tensor_value_info('beta', TensorProto.FLOAT16, [4]) bias = helper.make_tensor_value_info('bias', TensorProto.FLOAT, [4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) mean = helper.make_tensor_value_info('mean', TensorProto.FLOAT, [2, 2, 1]) inv_std_var = helper.make_tensor_value_info('inv_std_var', TensorProto.FLOAT, [2, 2, 1]) input_skip_bias_sum = helper.make_tensor_value_info( 'input_skip_bias_sum', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node( 'SkipLayerNormalization', inputs=['x', 'skip', 'gamma', 'beta', 'bias'], outputs=['y', 'mean', 'inv_std_var', 'input_skip_bias_sum'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma, beta, bias], [y, mean, inv_std_var, input_skip_bias_sum]) @onnx_test() def skip_layer_normalization_invalid_n_args_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 4]) node = onnx.helper.make_node('SkipLayerNormalization', inputs=['x', 'skip'], outputs=['y'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip], [y]) @onnx_test() def skip_layer_normalization_invalid_input_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [2, 2, 2, 4]) skip = helper.make_tensor_value_info('skip', TensorProto.FLOAT16, [2, 2, 4]) gamma = helper.make_tensor_value_info('gamma', TensorProto.FLOAT16, [2, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [2, 2, 2, 4]) node = onnx.helper.make_node('SkipLayerNormalization', inputs=['x', 'skip', 'gamma'], outputs=['y'], epsilon=1e-5, domain="com.microsoft") return ([node], [x, skip, gamma], [y]) @onnx_test() def slice_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node('Slice', inputs=['0'], axes=[0, 1], starts=[1, 0], ends=[2, 2], outputs=['1']) return ([node], [x], [y]) @onnx_test() def slice_constant_test(): y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 2]) x_tensor = helper.make_tensor(name='x_tensor', data_type=TensorProto.FLOAT, dims=[3, 2], vals=[0, 1, 2, 3, 4, 5]) x = onnx.helper.make_node('Constant', inputs=[], outputs=['x'], value=x_tensor) node = onnx.helper.make_node('Slice', inputs=['x'], axes=[0, 1], starts=[1, 0], ends=[2, 2], outputs=['1']) return ([x, node], [], [y]) @onnx_test() def slice_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, None, 2]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, None, 2]) node = onnx.helper.make_node('Slice', inputs=['0'], axes=[0], starts=[1], ends=[2], outputs=['1']) return ([node], [x], [y]) @onnx_test def slice_step_dyn_test(): # A slice command with non - default steps will have a "Step" # instruction added in parsing. step = np.array([2, 1]) step_tensor = helper.make_tensor(name="step", data_type=TensorProto.INT32, dims=step.shape, vals=step.astype(int)) arg_step = helper.make_node("Constant", inputs=[], outputs=['arg_step'], value=step_tensor) axis = np.array([-1, -2]) axis_tensor = helper.make_tensor(name="axis", data_type=TensorProto.INT32, dims=axis.shape, vals=axis.astype(int)) arg_axis = helper.make_node("Constant", inputs=[], outputs=['arg_axis'], value=axis_tensor) end = np.array([-1, -1]) end_tensor = helper.make_tensor(name="end", data_type=TensorProto.INT32, dims=end.shape, vals=end.astype(int)) arg_end = helper.make_node("Constant", inputs=[], outputs=['arg_end'], value=end_tensor) start = np.array([-5, -3]) start_tensor = helper.make_tensor(name="start", data_type=TensorProto.INT32, dims=start.shape, vals=start.astype(int)) arg_start = helper.make_node("Constant", inputs=[], outputs=['arg_start'], value=start_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 2]) node = onnx.helper.make_node( 'Slice', inputs=['0', 'arg_start', 'arg_end', 'arg_axis', 'arg_step'], outputs=['1']) return ([arg_step, arg_axis, arg_end, arg_start, node], [x], [y]) @onnx_test def slice_reverse_dyn_test(): # A slice command with negative step on any axis will have # a "Reverse" instruction added in parsing. step = np.array([-1, 1]) step_tensor = helper.make_tensor(name="step", data_type=TensorProto.INT32, dims=step.shape, vals=step.astype(int)) arg_step = helper.make_node("Constant", inputs=[], outputs=['arg_step'], value=step_tensor) axis = np.array([-1, -2]) axis_tensor = helper.make_tensor(name="axis", data_type=TensorProto.INT32, dims=axis.shape, vals=axis.astype(int)) arg_axis = helper.make_node("Constant", inputs=[], outputs=['arg_axis'], value=axis_tensor) end = np.array([-1, -1]) end_tensor = helper.make_tensor(name="end", data_type=TensorProto.INT32, dims=end.shape, vals=end.astype(int)) arg_end = helper.make_node("Constant", inputs=[], outputs=['arg_end'], value=end_tensor) start = np.array([-5, -3]) start_tensor = helper.make_tensor(name="start", data_type=TensorProto.INT32, dims=start.shape, vals=start.astype(int)) arg_start = helper.make_node("Constant", inputs=[], outputs=['arg_start'], value=start_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 2]) node = onnx.helper.make_node( 'Slice', inputs=['0', 'arg_start', 'arg_end', 'arg_axis', 'arg_step'], outputs=['1']) return ([arg_step, arg_axis, arg_end, arg_start, node], [x], [y]) @onnx_test() def slice_3arg_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2, 5]) start = np.array([0, 0]) start_tensor = helper.make_tensor(name="start", data_type=TensorProto.INT32, dims=start.shape, vals=start.astype(int)) arg_start = helper.make_node("Constant", inputs=[], outputs=['arg_start'], value=start_tensor) end = np.array([2, 5]) end_tensor = helper.make_tensor(name="end", data_type=TensorProto.INT32, dims=end.shape, vals=end.astype(int)) arg_end = helper.make_node("Constant", inputs=[], outputs=['arg_end'], value=end_tensor) node = onnx.helper.make_node('Slice', inputs=['0', 'arg_start', 'arg_end'], outputs=['1']) return ([arg_start, arg_end, node], [x], [y]) @onnx_test() def slice_5arg_test(): step = np.array([1, 1]) step_tensor = helper.make_tensor(name="step", data_type=TensorProto.INT32, dims=step.shape, vals=step.astype(int)) arg_step = helper.make_node("Constant", inputs=[], outputs=['arg_step'], value=step_tensor) axis = np.array([-1, -2]) axis_tensor = helper.make_tensor(name="axis", data_type=TensorProto.INT32, dims=axis.shape, vals=axis.astype(int)) arg_axis = helper.make_node("Constant", inputs=[], outputs=['arg_axis'], value=axis_tensor) end = np.array([-1, -1]) end_tensor = helper.make_tensor(name="end", data_type=TensorProto.INT32, dims=end.shape, vals=end.astype(int)) arg_end = helper.make_node("Constant", inputs=[], outputs=['arg_end'], value=end_tensor) start = np.array([-5, -3]) start_tensor = helper.make_tensor(name="start", data_type=TensorProto.INT32, dims=start.shape, vals=start.astype(int)) arg_start = helper.make_node("Constant", inputs=[], outputs=['arg_start'], value=start_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 2]) node = onnx.helper.make_node( 'Slice', inputs=['0', 'arg_start', 'arg_end', 'arg_axis', 'arg_step'], outputs=['1']) return ([arg_step, arg_axis, arg_end, arg_start, node], [x], [y]) @onnx_test() def slice_5arg_reverse_test(): step = np.array([-1, 1]) step_tensor = helper.make_tensor(name="step", data_type=TensorProto.INT32, dims=step.shape, vals=step.astype(int)) arg_step = helper.make_node("Constant", inputs=[], outputs=['arg_step'], value=step_tensor) axis = np.array([-1, -2]) axis_tensor = helper.make_tensor(name="axis", data_type=TensorProto.INT32, dims=axis.shape, vals=axis.astype(int)) arg_axis = helper.make_node("Constant", inputs=[], outputs=['arg_axis'], value=axis_tensor) end = np.array([-5, -1]) end_tensor = helper.make_tensor(name="end", data_type=TensorProto.INT32, dims=end.shape, vals=end.astype(int)) arg_end = helper.make_node("Constant", inputs=[], outputs=['arg_end'], value=end_tensor) start = np.array([-1, -3]) start_tensor = helper.make_tensor(name="start", data_type=TensorProto.INT32, dims=start.shape, vals=start.astype(int)) arg_start = helper.make_node("Constant", inputs=[], outputs=['arg_start'], value=start_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 2]) node = onnx.helper.make_node( 'Slice', inputs=['0', 'arg_start', 'arg_end', 'arg_axis', 'arg_step'], outputs=['1']) return ([arg_step, arg_axis, arg_end, arg_start, node], [x], [y]) @onnx_test() def slice_5arg_step_test(): step = np.array([-2, 2]) step_tensor = helper.make_tensor(name="step", data_type=TensorProto.INT32, dims=step.shape, vals=step.astype(int)) arg_step = helper.make_node("Constant", inputs=[], outputs=['arg_step'], value=step_tensor) axis = np.array([-1, -2]) axis_tensor = helper.make_tensor(name="axis", data_type=TensorProto.INT32, dims=axis.shape, vals=axis.astype(int)) arg_axis = helper.make_node("Constant", inputs=[], outputs=['arg_axis'], value=axis_tensor) end = np.array([-5, -1]) end_tensor = helper.make_tensor(name="end", data_type=TensorProto.INT32, dims=end.shape, vals=end.astype(int)) arg_end = helper.make_node("Constant", inputs=[], outputs=['arg_end'], value=end_tensor) start = np.array([-1, -3]) start_tensor = helper.make_tensor(name="start", data_type=TensorProto.INT32, dims=start.shape, vals=start.astype(int)) arg_start = helper.make_node("Constant", inputs=[], outputs=['arg_start'], value=start_tensor) x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [5, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [4, 2]) node = onnx.helper.make_node( 'Slice', inputs=['0', 'arg_start', 'arg_end', 'arg_axis', 'arg_step'], outputs=['1']) return ([arg_step, arg_axis, arg_end, arg_start, node], [x], [y]) @onnx_test() def slice_max_end_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [10, 20]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [9, 17]) node = onnx.helper.make_node('Slice', inputs=['0'], axes=[0, 1], starts=[1, 2], ends=[3000000000, -1], outputs=['1']) return ([node], [x], [y]) @onnx_test() def slice_var_input_static0(): data = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 2]) starts = helper.make_tensor_value_info('starts', TensorProto.INT32, [2]) ends = helper.make_tensor_value_info('ends', TensorProto.INT32, [2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node('Slice', inputs=['data', 'starts', 'ends'], axes=[0, 1], outputs=['output']) return ([node], [data, starts, ends], [output]) @onnx_test() def slice_var_input_static1(): data = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 2]) starts = helper.make_tensor_value_info('starts', TensorProto.INT64, [2]) ends = helper.make_tensor_value_info('ends', TensorProto.INT64, [2]) axes = helper.make_tensor_value_info('axes', TensorProto.INT64, [2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node('Slice', inputs=['data', 'starts', 'ends', 'axes'], outputs=['output']) return ([node], [data, starts, ends, axes], [output]) @onnx_test() def slice_var_input_dyn0(): data = helper.make_tensor_value_info('data', TensorProto.FLOAT, [None, 2]) starts = helper.make_tensor_value_info('starts', TensorProto.INT32, [2]) ends = helper.make_tensor_value_info('ends', TensorProto.INT32, [2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node('Slice', inputs=['data', 'starts', 'ends'], axes=[0, 1], outputs=['output']) return ([node], [data, starts, ends], [output]) @onnx_test() def slice_var_input_dyn1(): data = helper.make_tensor_value_info('data', TensorProto.FLOAT, [None, 2]) starts = helper.make_tensor_value_info('starts', TensorProto.INT32, [2]) ends = helper.make_tensor_value_info('ends', TensorProto.INT32, [2]) axes = helper.make_tensor_value_info('axes', TensorProto.INT32, [2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node('Slice', inputs=['data', 'starts', 'ends', 'axes'], outputs=['output']) return ([node], [data, starts, ends, axes], [output]) @onnx_test() def slice_var_input_default_steps(): step = np.array([1, 1]) step_tensor = helper.make_tensor(name="step", data_type=TensorProto.INT64, dims=step.shape, vals=step.astype(int)) arg_step = helper.make_node("Constant", inputs=[], outputs=['arg_step'], value=step_tensor) data = helper.make_tensor_value_info('data', TensorProto.FLOAT, [None, 2]) starts = helper.make_tensor_value_info('starts', TensorProto.INT64, [2]) ends = helper.make_tensor_value_info('ends', TensorProto.INT64, [2]) axes = helper.make_tensor_value_info('axes', TensorProto.INT64, [2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node( 'Slice', inputs=['data', 'starts', 'ends', 'axes', 'arg_step'], outputs=['output']) return ([arg_step, node], [data, starts, ends, axes], [output]) @onnx_test() def slice_var_input_steps_error(): step = np.array([2, 1]) step_tensor = helper.make_tensor(name="step", data_type=TensorProto.INT32, dims=step.shape, vals=step.astype(int)) arg_step = helper.make_node("Constant", inputs=[], outputs=['arg_step'], value=step_tensor) data = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 2]) starts = helper.make_tensor_value_info('starts', TensorProto.INT64, [2]) ends = helper.make_tensor_value_info('ends', TensorProto.INT64, [2]) axes = helper.make_tensor_value_info('axes', TensorProto.INT64, [2]) output = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 2]) node = onnx.helper.make_node( 'Slice', inputs=['data', 'starts', 'ends', 'axes', 'arg_step'], outputs=['output']) return ([arg_step, node], [data, starts, ends, axes], [output]) @onnx_test() def softmax_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3]) node = onnx.helper.make_node('Softmax', inputs=['0'], outputs=['1']) return ([node], [x], [y]) @onnx_test() def softmax_nonstd_input_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [6, 8]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3, 4]) node0 = onnx.helper.make_node('Slice', inputs=['0'], axes=[0, 1], starts=[1, 0], ends=[4, 4], outputs=['1']) node1 = onnx.helper.make_node('Softmax', inputs=['1'], outputs=['2']) return ([node0, node1], [x], [y]) @onnx_test() def softmax_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 4, 4]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 4, 4]) node = onnx.helper.make_node('Softmax', inputs=['0'], outputs=['1']) return ([node], [x], [y]) @onnx_test() def softmaxcrossentropyloss_score_dim_err_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [2]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [2]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1"], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_score_label_mismatch_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [1]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [2]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1"], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_score_label_wrong_k_dims_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 4, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [2]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [2]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1"], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_weight_wrong_dims_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) weight = helper.make_tensor_value_info('3', TensorProto.FLOAT, [2]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1", "3"], outputs=["2"], reduction="none", ) return ([node], [scores, labels, weight], [loss]) @onnx_test() def softmaxcrossentropyloss_label_wrong_type_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT8, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) weight = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1", "3"], outputs=["2"], reduction="none", ) return ([node], [scores, labels, weight], [loss]) @onnx_test() def softmaxcrossentropyloss_scores_wrong_type_test(): scores = helper.make_tensor_value_info('0', TensorProto.INT32, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1"], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_weight_wrong_type_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) weight = helper.make_tensor_value_info('3', TensorProto.INT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1", "3"], outputs=["2"], reduction="none", ) return ([node], [scores, labels, weight], [loss]) @onnx_test() def softmaxcrossentropyloss_weight_score_mismatch_valid_type_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) weight = helper.make_tensor_value_info('3', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1", "3"], outputs=["2"], reduction="none", ) return ([node], [scores, labels, weight], [loss]) @onnx_test() def softmaxcrossentropyloss_invalid_reduction_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) weight = helper.make_tensor_value_info('3', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=["0", "1", "3"], outputs=["2"], reduction="BadReductionName", ) return ([node], [scores, labels, weight], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_asym_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [3]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_double_test(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_half_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_bf16_test(): scores = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="none", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_sum_reduction_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="sum", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_sum_reduction_double_test(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="sum", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_sum_reduction_half_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="sum", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_sum_reduction_bf16_test(): scores = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="sum", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_mean_reduction_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="mean", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_mean_reduction_double_test(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="mean", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_mean_reduction_half_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="mean", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_mean_reduction_bf16_test(): scores = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) loss = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", ], outputs=["2"], reduction="mean", ) return ([node], [scores, labels], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_weighted_out_bounds_ignore_idx_test( ): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ignore_index=5, ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_weighted_neg_out_bounds_ignore_idx_test( ): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ignore_index=-5, ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_weighted_asym_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [3]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_weighted_asym_class_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 3]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_weighted_ignore_idx_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ignore_index=2, ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_idx_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ignore_index=-2, ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_idx_test2(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ignore_index=-1, ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_double_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [4]) loss = helper.make_tensor_value_info('3', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_no_reduction_half_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_sum_reduction_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="sum", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_sum_reduction_double_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [4]) loss = helper.make_tensor_value_info('3', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="sum", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_sum_reduction_bf16_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="sum", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_mean_reduction_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_mean_reduction_double_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [4]) loss = helper.make_tensor_value_info('3', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_2d_mean_reduction_half_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_kdim_not_equal_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4, 2, 1]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 1, 2]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_kd_mean_reduction_half_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4, 2, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_kd_mean_reduction_bf16_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [4, 4, 2, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [4]) loss = helper.make_tensor_value_info('3', TensorProto.BFLOAT16, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_kd_sum_reduction_double_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [4, 4, 2, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [4]) loss = helper.make_tensor_value_info('3', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="sum", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softmaxcrossentropyloss_kd_no_reduction_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4, 2, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "SoftmaxCrossEntropyLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [4, 4, 2, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [4]) node = onnx.helper.make_node( "NegativeLogLikelihoodLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [4, 4, 2, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [4]) loss = helper.make_tensor_value_info('3', TensorProto.BFLOAT16, [4]) node = onnx.helper.make_node( "NegativeLogLikelihoodLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test2(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT16, [2, 3, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT16, [3]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT16, [2]) node = onnx.helper.make_node( "NegativeLogLikelihoodLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test2(): scores = helper.make_tensor_value_info('0', TensorProto.BFLOAT16, [2, 3, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.BFLOAT16, [3]) loss = helper.make_tensor_value_info('3', TensorProto.BFLOAT16, [2]) node = onnx.helper.make_node( "NegativeLogLikelihoodLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="mean", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def negativeloglikelihoodloss_kd_sum_reduction_double_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [4, 4, 2, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [4]) loss = helper.make_tensor_value_info('3', TensorProto.DOUBLE, [4]) node = onnx.helper.make_node( "NegativeLogLikelihoodLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="sum", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def negativeloglikelihoodloss_kd_sum_reduction_double_weighted_test2(): scores = helper.make_tensor_value_info('0', TensorProto.DOUBLE, [2, 3, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.DOUBLE, [3]) loss = helper.make_tensor_value_info('3', TensorProto.DOUBLE, [1]) node = onnx.helper.make_node( "NegativeLogLikelihoodLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="sum", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def negativeloglikelihoodloss_kd_no_reduction_weighted_test(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [4, 4, 2, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [4, 2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [4]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [4]) node = onnx.helper.make_node( "NegativeLogLikelihoodLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def negativeloglikelihoodloss_kd_no_reduction_weighted_test2(): scores = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 2]) labels = helper.make_tensor_value_info('1', TensorProto.INT32, [2, 2]) weights = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3]) loss = helper.make_tensor_value_info('3', TensorProto.FLOAT, [2, 2]) node = onnx.helper.make_node( "NegativeLogLikelihoodLoss", inputs=[ "0", "1", "2", ], outputs=["3"], reduction="none", ) return ([node], [scores, labels, weights], [loss]) @onnx_test() def softsign_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5]) node = onnx.helper.make_node('Softsign', inputs=['x'], outputs=['y']) return ([node], [x], [y]) def softplus_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [5]) node = onnx.helper.make_node('Softplus', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def softsign_nd_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [3, 4, 5]) node = onnx.helper.make_node('Softsign', inputs=['x'], outputs=['y']) return ([node], [x], [y]) def softplus_nd_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT16, [3, 4, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT16, [3, 4, 5]) node = onnx.helper.make_node('Softplus', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def split_minus_axis_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [10, 5]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [10, 5]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [10, 5]) node = onnx.helper.make_node( 'Split', inputs=['x'], outputs=['y1', 'y2', 'y3'], axis=-1, ) return ([node], [x], [y1, y2, y3]) @onnx_test() def split_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [10, 7]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [10, 4]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [10, 4]) node = onnx.helper.make_node('Split', inputs=['x'], outputs=['y1', 'y2', 'y3'], axis=1, split=[7, 4, 4]) return ([node], [x], [y1, y2, y3]) @onnx_test() def split_test_default(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [5, 15]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [5, 15]) node = onnx.helper.make_node( 'Split', inputs=['x'], outputs=['y1', 'y2'], ) return ([node], [x], [y1, y2]) @onnx_test() def split_test_no_attribute(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [300, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [75, 15]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [75, 15]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [75, 15]) y4 = helper.make_tensor_value_info('y4', TensorProto.FLOAT, [75, 15]) split = np.ones(4) * 75 split_tensor = helper.make_tensor(name="split", data_type=TensorProto.INT64, dims=split.shape, vals=split.astype(np.int64)) const_node = helper.make_node("Constant", inputs=[], outputs=['split'], value=split_tensor) node = onnx.helper.make_node( 'Split', inputs=['x', 'split'], outputs=['y1', 'y2', 'y3', 'y4'], ) return ([const_node, node], [x], [y1, y2, y3, y4]) @onnx_test() def split_test_uneven(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [12, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [3, 15]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [3, 15]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [3, 15]) y4 = helper.make_tensor_value_info('y4', TensorProto.FLOAT, [3, 15]) y5 = helper.make_tensor_value_info('y5', TensorProto.FLOAT, [0, 15]) node = onnx.helper.make_node( 'Split', inputs=['x'], outputs=['y1', 'y2', 'y3', 'y4', 'y5'], ) return ([node], [x], [y1, y2, y3, y4, y5]) @onnx_test() def split_test_uneven_num_outputs(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [11, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [3, 15]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [3, 15]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [3, 15]) y4 = helper.make_tensor_value_info('y4', TensorProto.FLOAT, [2, 15]) node = onnx.helper.make_node( 'Split', inputs=['x'], outputs=['y1', 'y2', 'y3', 'y4'], num_outputs=4, ) return ([node], [x], [y1, y2, y3, y4]) @onnx_test() def split_test_no_attribute_invalid_split(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [300, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [75, 15]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [75, 15]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [75, 15]) y4 = helper.make_tensor_value_info('y4', TensorProto.FLOAT, [75, 15]) split = np.ones(4) split_tensor = helper.make_tensor(name="split", data_type=TensorProto.INT64, dims=split.shape, vals=split.astype(np.int64)) const_node = helper.make_node("Constant", inputs=[], outputs=['split'], value=split_tensor) node = onnx.helper.make_node( 'Split', inputs=['x', 'split'], outputs=['y1', 'y2', 'y3', 'y4'], ) return ([const_node, node], [x], [y1, y2, y3, y4]) @onnx_test() def split_test_invalid_split(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [10, 7]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [10, 4]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [10, 4]) node = onnx.helper.make_node('Split', inputs=['x'], outputs=['y1', 'y2', 'y3'], axis=1, split=[1, 1, 1]) return ([node], [x], [y1, y2, y3]) @onnx_test() def split_test_no_attribute_invalid_input_split(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [10, 7]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [10, 4]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [10, 4]) node = onnx.helper.make_node('Split', inputs=['x'], outputs=['y1', 'y2', 'y3'], axis=1, split=[]) return ([node], [x], [y1, y2, y3]) @onnx_test() def split_test_invalid_num_outputs(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [11, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [3, 15]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [3, 15]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [3, 15]) y4 = helper.make_tensor_value_info('y4', TensorProto.FLOAT, [2, 15]) node = onnx.helper.make_node( 'Split', inputs=['x'], outputs=['y1', 'y2', 'y3', 'y4'], num_outputs=5, ) return ([node], [x], [y1, y2, y3, y4]) @onnx_test() def split_dyn_input_fixed_split_axis_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [None, 5]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [None, 5]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [None, 5]) node = onnx.helper.make_node('Split', inputs=['x'], outputs=['y1', 'y2', 'y3'], axis=1) return ([node], [x], [y1, y2, y3]) @onnx_test() def split_dyn_input_dyn_split_axis_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [None, 5]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [None, 5]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [None, 5]) node = onnx.helper.make_node('Split', inputs=['x'], outputs=['y1', 'y2', 'y3'], axis=0) return ([node], [x], [y1, y2, y3]) @onnx_test() def split_dyn_input_split_attr_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [None, 5]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [None, 5]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [None, 5]) node = onnx.helper.make_node('Split', inputs=['x'], outputs=['y1', 'y2', 'y3'], axis=0, split=[7, 4, 4]) return ([node], [x], [y1, y2, y3]) @onnx_test() def split_dyn_input_split_input_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 15]) y1 = helper.make_tensor_value_info('y1', TensorProto.FLOAT, [None, 5]) y2 = helper.make_tensor_value_info('y2', TensorProto.FLOAT, [None, 5]) y3 = helper.make_tensor_value_info('y3', TensorProto.FLOAT, [None, 5]) split = np.ones(3) * 5 split_tensor = helper.make_tensor(name="split", data_type=TensorProto.INT64, dims=split.shape, vals=split.astype(np.int64)) const_node = helper.make_node("Constant", inputs=[], outputs=['split'], value=split_tensor) node = onnx.helper.make_node('Split', inputs=['x', 'split'], outputs=['y1', 'y2', 'y3'], axis=0) return ([const_node, node], [x], [y1, y2, y3]) @onnx_test() def sqrt_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 15]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10, 15]) node = onnx.helper.make_node( 'Sqrt', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def sqrt_fp8_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT8E4M3FNUZ, [10, 15]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT8E4M3FNUZ, [10, 15]) node = onnx.helper.make_node( 'Sqrt', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def squeeze_axes_input_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 1, 5, 1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 5]) axes = np.array([1, 3], dtype=np.int64) axes_tensor = helper.make_tensor(name="axes", data_type=TensorProto.INT64, dims=axes.shape, vals=axes.astype(np.int64)) node = onnx.helper.make_node('Squeeze', inputs=['x', 'axes'], outputs=['y']) return ([node], [x], [y], [axes_tensor]) @onnx_test() def squeeze_empty_axes_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 1, 5, 1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 5]) axes = np.array([], dtype=np.int64) axes_tensor = helper.make_tensor(name="axes", data_type=TensorProto.INT64, dims=axes.shape, vals=axes.astype(np.int64)) node = onnx.helper.make_node('Squeeze', inputs=['x', 'axes'], outputs=['y']) return ([node], [x], [y], [axes_tensor]) @onnx_test() def squeeze_unsqueeze_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 3, 1, 1, 2, 1]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, 3, 1, 2, 1]) node = onnx.helper.make_node('Squeeze', inputs=['0'], axes=[0, 2, 3, 5], outputs=['1']) node2 = onnx.helper.make_node('Unsqueeze', inputs=['1'], axes=[0, 1, 3, 5], outputs=['2']) return ([node, node2], [x], [y]) @onnx_test() def squeeze_unsqueeze_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, None, 1, 1, None, 1]) y = helper.make_tensor_value_info('2', TensorProto.FLOAT, [1, 1, None, 1, None, 1]) node = onnx.helper.make_node('Squeeze', inputs=['0'], axes=[0, 2, 3, 5], outputs=['1']) node2 = onnx.helper.make_node('Unsqueeze', inputs=['1'], axes=[0, 1, 3, 5], outputs=['2']) return ([node, node2], [x], [y]) @onnx_test() def sub_bcast_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) arg1 = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 4]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node( 'Sub', inputs=['0', '1'], outputs=['out'], broadcast=1, axis=1, ) return ([node], [arg0, arg1], [arg_out]) @onnx_test() def sub_scalar_test(): values = np.array([1]) arg_node = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) arg_out = helper.make_tensor_value_info('out', TensorProto.FLOAT, [2, 3, 4, 5]) values_tensor = helper.make_tensor(name='const', data_type=TensorProto.FLOAT, dims=values.reshape(()).shape, vals=values.flatten().astype(float)) arg_const = onnx.helper.make_node( 'Constant', inputs=[], outputs=['arg_const'], value=values_tensor, ) node = onnx.helper.make_node( 'Sub', inputs=['0', 'arg_const'], outputs=['out'], ) return ([arg_const, node], [arg_node], [arg_out]) @onnx_test() def sum_int_test(): a = helper.make_tensor_value_info('0', TensorProto.INT16, [3]) b = helper.make_tensor_value_info('1', TensorProto.UINT16, [3]) c = helper.make_tensor_value_info('2', TensorProto.UINT32, [3]) y = helper.make_tensor_value_info('3', TensorProto.UINT32, [3]) cnode1 = onnx.helper.make_node('Cast', inputs=['0'], outputs=['c0'], to=12) cnode2 = onnx.helper.make_node('Cast', inputs=['1'], outputs=['c1'], to=12) node = onnx.helper.make_node( 'Sum', inputs=['c0', 'c1', '2'], outputs=['3'], ) return ([cnode1, cnode2, node], [a, b, c], [y]) @onnx_test() def sum_test(): a = helper.make_tensor_value_info('0', TensorProto.FLOAT, [3]) b = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3]) c = helper.make_tensor_value_info('2', TensorProto.FLOAT, [3]) y = helper.make_tensor_value_info('3', TensorProto.FLOAT, [3]) node = onnx.helper.make_node( 'Sum', inputs=['0', '1', '2'], outputs=['3'], ) return ([node], [a, b, c], [y]) @onnx_test() def sum_type_test(): valb = np.array([1, 0]) t_bool = helper.make_tensor(name="bool", data_type=TensorProto.BOOL, dims=valb.shape, vals=valb.astype(bool)) val = np.array([1, 1]) t_int8 = helper.make_tensor(name="int8", data_type=TensorProto.INT8, dims=val.shape, vals=val.astype(np.int8)) t_uint8 = helper.make_tensor(name="uint8", data_type=TensorProto.UINT8, dims=val.shape, vals=val.astype(np.uint8)) t_uint16 = helper.make_tensor(name="uint16", data_type=TensorProto.UINT16, dims=val.shape, vals=val.astype(np.uint16)) t_uint32 = helper.make_tensor(name="uint32", data_type=TensorProto.UINT32, dims=val.shape, vals=val.astype(np.uint32)) t_uint64 = helper.make_tensor(name="uint64", data_type=TensorProto.UINT64, dims=val.shape, vals=val.astype(np.uint64)) t_double = helper.make_tensor(name="double", data_type=TensorProto.DOUBLE, dims=val.shape, vals=val.astype(np.float64)) valr = np.array([1.5, 2.0]) t_raw = helper.make_tensor(name="raw", data_type=TensorProto.DOUBLE, dims=valr.shape, vals=valr.tobytes(), raw=True) n_bool = onnx.helper.make_node('Cast', inputs=['bool'], outputs=['o_bool'], to=11) n_int8 = onnx.helper.make_node('Cast', inputs=['int8'], outputs=['o_int8'], to=11) n_uint8 = onnx.helper.make_node('Cast', inputs=['uint8'], outputs=['o_uint8'], to=11) n_uint16 = onnx.helper.make_node('Cast', inputs=['uint16'], outputs=['o_uint16'], to=11) n_uint32 = onnx.helper.make_node('Cast', inputs=['uint32'], outputs=['o_uint32'], to=11) n_uint64 = onnx.helper.make_node('Cast', inputs=['uint64'], outputs=['o_uint64'], to=11) node = onnx.helper.make_node( 'Sum', inputs=[ 'o_bool', 'o_int8', 'o_uint8', 'o_uint16', 'o_uint32', 'o_uint64', 'double', 'raw' ], outputs=['out'], ) y = helper.make_tensor_value_info('out', TensorProto.DOUBLE, [2]) return ([n_bool, n_int8, n_uint8, n_uint16, n_uint32, n_uint64, node], [], [y], [ t_bool, t_int8, t_uint8, t_uint16, t_uint32, t_uint64, t_double, t_raw ]) @onnx_test() def tan_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10]) node = onnx.helper.make_node( 'Tan', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def tanh_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1]) node = onnx.helper.make_node( 'Tanh', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def thresholdedrelu_default_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 3]) node = onnx.helper.make_node('ThresholdedRelu', inputs=['x'], outputs=['y']) return ([node], [x], [y]) @onnx_test() def thresholdedrelu_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 3]) alpha = 3.0 node = onnx.helper.make_node('ThresholdedRelu', inputs=['x'], outputs=['y'], alpha=alpha) return ([node], [x], [y]) @onnx_test() def thresholdedrelu_int_test(): x = helper.make_tensor_value_info('x', TensorProto.INT32, [2, 2, 3]) y = helper.make_tensor_value_info('y', TensorProto.INT32, [2, 2, 3]) alpha = 3.0 node = onnx.helper.make_node('ThresholdedRelu', inputs=['x'], outputs=['y'], alpha=alpha) return ([node], [x], [y]) @onnx_test() def tile_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [2, 4]) node = onnx.helper.make_node('Tile', inputs=['x', 'y'], outputs=['z']) return ([node], [x, y], [z], [helper.make_tensor('y', TensorProto.INT64, [2], [1, 2])]) @onnx_test() def tile_test_3x2(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2]) y = helper.make_tensor_value_info('y', TensorProto.INT64, [2]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [6, 4]) node = onnx.helper.make_node('Tile', inputs=['x', 'y'], outputs=['z']) return ([node], [x, y], [z], [helper.make_tensor('y', TensorProto.INT64, [2], [3, 2])]) @onnx_test() def topk_attrk_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [2, 5, 3, 2]) val = helper.make_tensor_value_info('val', TensorProto.FLOAT, [2, 2, 3, 2]) ind = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 2, 3, 2]) node = onnx.helper.make_node('TopK', inputs=['data'], outputs=['val', 'indices'], k=2) return ([node], [x], [val, ind]) @onnx_test() def topk_neg_axis_test(): k = np.array([3]) x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 4, 5, 6]) val = helper.make_tensor_value_info('val', TensorProto.FLOAT, [3, 3, 5, 6]) ind = helper.make_tensor_value_info('indices', TensorProto.INT64, [3, 3, 5, 6]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node('TopK', inputs=['data', 'k'], outputs=['val', 'indices'], axis=-2, sorted=0) return ([node], [x], [val, ind], [k_tensor]) @onnx_test() def topk_test(): k = np.array([4]) x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [2, 5, 3, 2]) val = helper.make_tensor_value_info('val', TensorProto.FLOAT, [2, 4, 3, 2]) ind = helper.make_tensor_value_info('indices', TensorProto.INT64, [2, 4, 3, 2]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node('TopK', inputs=['data', 'k'], outputs=['val', 'indices'], largest=0, axis=1) return ([node], [x], [val, ind], [k_tensor]) def transpose_default_perm_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 5, 2, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 2, 5, 1]) node = onnx.helper.make_node( 'Transpose', inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def transpose_invalid_perm_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 2, 4, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 2, 2]) node = onnx.helper.make_node( 'Transpose', perm=[0, 2, 1], inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def transpose_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [1, 2, 2, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [1, 3, 2, 2]) node = onnx.helper.make_node( 'Transpose', perm=[0, 3, 1, 2], inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test() def transpose_dyn_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 2, 2, 3]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 2, 2]) node = onnx.helper.make_node( 'Transpose', perm=[0, 3, 1, 2], inputs=['0'], outputs=['1'], ) return ([node], [x], [y]) @onnx_test def transpose_gather_test(): x = helper.make_tensor_value_info('data', TensorProto.FLOAT, [3, 5, 4, 6]) i = helper.make_tensor_value_info('indices', TensorProto.INT32, [2, 4, 3, 5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 2, 3, 4, 5, 4, 5, 6]) td = onnx.helper.make_node( 'Transpose', inputs=['data'], outputs=['tdata'], perm=[0, 2, 1, 3], ) ti = onnx.helper.make_node('Transpose', inputs=['indices'], outputs=['tindices'], perm=[0, 2, 1, 3]) node = onnx.helper.make_node( 'Gather', inputs=['tdata', 'tindices'], outputs=['y'], axis=1, ) return ([td, ti, node], [x, i], [y]) @onnx_test() def triu_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node( 'Trilu', inputs=['x'], outputs=['y'], ) return ([node], [x], [y]) @onnx_test() def triu_batch_diff_k_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 3]) k = np.array([2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 3]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node( 'Trilu', inputs=['x', 'k'], outputs=['y'], ) return ([node], [x], [y], [k_tensor]) @onnx_test() def tril_batch_diff_k_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 3]) k = np.array([2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 2, 3]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node('Trilu', inputs=['x', 'k'], outputs=['y'], upper=0) return ([node], [x], [y], [k_tensor]) @onnx_test() def tril_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) node = onnx.helper.make_node('Trilu', inputs=['x'], outputs=['y'], upper=0) return ([node], [x], [y]) @onnx_test() def triu_neg_k_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) k = np.array([-1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node('Trilu', inputs=['x', 'k'], outputs=['y']) return ([node], [x], [y], [k_tensor]) @onnx_test() def tril_neg_k_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) k = np.array([-1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node('Trilu', inputs=['x', 'k'], outputs=['y'], upper=0) return ([node], [x], [y], [k_tensor]) @onnx_test() def triu_out_k_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) k = np.array([5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node('Trilu', inputs=['x', 'k'], outputs=['y']) return ([node], [x], [y], [k_tensor]) @onnx_test() def tril_out_k_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [3, 4]) k = np.array([5]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 4]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node('Trilu', inputs=['x', 'k'], outputs=['y'], upper=0) return ([node], [x], [y], [k_tensor]) @onnx_test() def triu_row_one_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 4]) k = np.array([1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 4]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node( 'Trilu', inputs=['x', 'k'], outputs=['y'], ) return ([node], [x], [y], [k_tensor]) @onnx_test() def tril_row_one_test(): x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [1, 4]) k = np.array([1]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [1, 4]) k_tensor = helper.make_tensor(name='k', data_type=TensorProto.INT64, dims=k.shape, vals=k.astype(np.int64)) node = onnx.helper.make_node('Trilu', inputs=['x', 'k'], outputs=['y'], upper=0) return ([node], [x], [y], [k_tensor]) @onnx_test() def undefined_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node('Identity', inputs=[''], outputs=['1']) return ([node], [x], [y]) @onnx_test() def unique_dynamic_sorted_test(): x = helper.make_tensor_value_info('X', TensorProto.FLOAT, [6]) y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [4]) y_ind = helper.make_tensor_value_info('indices', TensorProto.INT64, [4]) x_ind = helper.make_tensor_value_info('inverse_indices', TensorProto.INT64, [6]) count = helper.make_tensor_value_info('counts', TensorProto.INT64, [4]) node = onnx.helper.make_node( 'Unique', inputs=['X'], outputs=['Y', 'indices', 'inverse_indices', 'counts'], axis=0, sorted=1) return ([node], [x], [y, y_ind, x_ind, count]) @onnx_test() def unique_dynamic_sorted_3D_test(): x = helper.make_tensor_value_info('X', TensorProto.INT64, [4, 4, 4]) y = helper.make_tensor_value_info('Y', TensorProto.INT64, [16]) y_ind = helper.make_tensor_value_info('indices', TensorProto.INT64, [16]) x_ind = helper.make_tensor_value_info('inverse_indices', TensorProto.INT64, [64]) count = helper.make_tensor_value_info('counts', TensorProto.INT64, [16]) node = onnx.helper.make_node( 'Unique', inputs=['X'], outputs=['Y', 'indices', 'inverse_indices', 'counts'], sorted=1) return ([node], [x], [y, y_ind, x_ind, count]) @onnx_test() def unique_dynamic_unsorted_test(): x = helper.make_tensor_value_info('X', TensorProto.FLOAT, [6]) y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [4]) y_ind = helper.make_tensor_value_info('indices', TensorProto.INT64, [4]) x_ind = helper.make_tensor_value_info('inverse_indices', TensorProto.INT64, [6]) count = helper.make_tensor_value_info('counts', TensorProto.INT64, [4]) node = onnx.helper.make_node( 'Unique', inputs=['X'], outputs=['Y', 'indices', 'inverse_indices', 'counts'], axis=0, sorted=0) return ([node], [x], [y, y_ind, x_ind, count]) @onnx_test() def unique_sorted_test(): x = helper.make_tensor('X', TensorProto.FLOAT, [6], [2, 1, 1, 3, 4, 3]) y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [4]) y_ind = helper.make_tensor_value_info('indices', TensorProto.INT64, [4]) x_ind = helper.make_tensor_value_info('inverse_indices', TensorProto.INT64, [6]) count = helper.make_tensor_value_info('counts', TensorProto.INT64, [4]) node = onnx.helper.make_node( 'Unique', inputs=['X'], outputs=['Y', 'indices', 'inverse_indices', 'counts'], axis=0, sorted=1) return ([node], [], [y, y_ind, x_ind, count], [x]) @onnx_test() def unique_unsorted_test(): x = helper.make_tensor('X', TensorProto.FLOAT, [6], [2, 1, 1, 3, 4, 3]) y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [4]) y_ind = helper.make_tensor_value_info('indices', TensorProto.INT64, [4]) x_ind = helper.make_tensor_value_info('inverse_indices', TensorProto.INT64, [6]) count = helper.make_tensor_value_info('counts', TensorProto.INT64, [4]) node = onnx.helper.make_node( 'Unique', inputs=['X'], outputs=['Y', 'indices', 'inverse_indices', 'counts'], axis=0, sorted=0) return ([node], [], [y, y_ind, x_ind, count], [x]) @onnx_test() def unknown_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 4]) helper.make_tensor_value_info('2', TensorProto.FLOAT, [2, 3, 4, 5]) a = helper.make_tensor_value_info('3', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node('Unknown', inputs=['0', '1'], outputs=['2']) node2 = onnx.helper.make_node('Unknown', inputs=['2'], outputs=['3']) return ([node, node2], [x, y], [a]) @onnx_test() def unknown_aten_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [2, 3, 4, 5]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [3, 4]) helper.make_tensor_value_info('2', TensorProto.FLOAT, [2, 3, 4, 5]) a = helper.make_tensor_value_info('3', TensorProto.FLOAT, [2, 3, 4, 5]) node = onnx.helper.make_node('ATen', inputs=['0', '1'], outputs=['2'], operator='unknown') return ([node], [x, y], [a]) @onnx_test() def upsample_linear_test(): scales = np.array([1.0, 1.0, 2.0, 2.0], dtype=np.float32) scales_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype( np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 2]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, []) node = onnx.helper.make_node('Upsample', inputs=['X', '', 'scales'], outputs=['Y'], mode='linear') return ([node], [X], [Y], [scales_tensor]) @onnx_test() def upsample_test(): scales = np.array([1.0, 1.0, 2.0, 3.0], dtype=np.float32) scale_tensor = helper.make_tensor(name='scales', data_type=TensorProto.FLOAT, dims=scales.shape, vals=scales.flatten().astype(np.float32)) X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 2]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 4, 6]) node = onnx.helper.make_node( 'Upsample', inputs=['X', 'scales'], outputs=['Y'], mode='nearest', ) return ([node], [X], [Y], [scale_tensor]) @onnx_test() def upsample_ver7_test(): X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 1, 2, 2]) Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 1, 4, 6]) node = onnx.helper.make_node('Upsample', inputs=['X'], outputs=['Y'], mode='nearest', scales=[1.0, 1.0, 2.0, 3.0]) return ([node], [X], [Y]) @onnx_test() def variable_batch_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [None, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [None, 3, 16, 16]) node = onnx.helper.make_node('Identity', inputs=['0'], outputs=['1']) return ([node], [x], [y]) @onnx_test() def variable_batch_leq_zero_test(): x = helper.make_tensor_value_info('0', TensorProto.FLOAT, [0, 3, 16, 16]) y = helper.make_tensor_value_info('1', TensorProto.FLOAT, [-1, 3, 16, 16]) z = helper.make_tensor_value_info('2', TensorProto.FLOAT, [-1, 3, 16, 16]) node = onnx.helper.make_node('Add', inputs=['0', '1'], outputs=['2']) return ([node], [x, y], [z]) @onnx_test() def where_test(): c = helper.make_tensor_value_info('c', TensorProto.BOOL, [2]) x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [2, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [2, 1, 2, 2]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [2, 2, 2, 2]) node = onnx.helper.make_node('Where', inputs=['c', 'x', 'y'], outputs=['z']) return ([node], [c, x, y], [z]) @onnx_test() def where_dyn_test(): c = helper.make_tensor_value_info('c', TensorProto.BOOL, [None, 2, 2]) x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [None, 2, 2]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [None, 2, 2]) node = onnx.helper.make_node('Where', inputs=['c', 'x', 'y'], outputs=['z']) return ([node], [c, x, y], [z]) @onnx_test() def where_mixed_test(): # mixture of static and dynamic input shapes is not supported c = helper.make_tensor_value_info('c', TensorProto.BOOL, [None, 2, 2]) x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [None, 2, 2]) y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [3, 2, 2]) z = helper.make_tensor_value_info('z', TensorProto.FLOAT, [None, 2, 2]) node = onnx.helper.make_node('Where', inputs=['c', 'x', 'y'], outputs=['z']) return ([node], [c, x, y], [z]) def scan_test(scan_input_axes=[0, 0], scan_input_directions=[0, 0], scan_output_axes=[0, 0], scan_output_directions=[0, 0]): sum_in = helper.make_tensor_value_info("sum_in", TensorProto.FLOAT, [2, 2]) scan_in1 = helper.make_tensor_value_info("scan_in1", TensorProto.FLOAT, [2, 2]) scan_in2 = helper.make_tensor_value_info("scan_in2", TensorProto.FLOAT, [1]) sum_out = helper.make_tensor_value_info("sum_out", TensorProto.FLOAT, [2, 2]) scan_out1 = helper.make_tensor_value_info("scan_out1", TensorProto.FLOAT, [2, 2]) scan_out2 = helper.make_tensor_value_info("scan_out2", TensorProto.FLOAT, [2]) add1 = helper.make_node("Add", inputs=["sum_in", "scan_in1"], outputs=["add1_out"]) add2 = helper.make_node("Add", inputs=["add1_out", "scan_in2"], outputs=["sum_out"]) id = helper.make_node("Identity", inputs=["sum_out"], outputs=["scan_out1"]) reduce_sum = helper.make_node("ReduceSum", axes=[0], keepdims=0, inputs=["sum_out"], outputs=["scan_out2"]) scan_body = helper.make_graph([add1, add2, id, reduce_sum], "scan_body", [sum_in, scan_in1, scan_in2], [sum_out, scan_out1, scan_out2]) init_state = helper.make_tensor_value_info("init_state", TensorProto.FLOAT, [2, 2]) scan_ins1_sh = [2, 2, 2] scan_ins1_sh[scan_input_axes[0]] = 3 scan_ins1 = helper.make_tensor_value_info("scan_ins1", TensorProto.FLOAT, scan_ins1_sh) scan_ins2_sh = [1, 1] scan_ins2_sh[scan_input_axes[1]] = 3 scan_ins2 = helper.make_tensor_value_info("scan_ins2", TensorProto.FLOAT, scan_ins2_sh) final_state = helper.make_tensor_value_info("final_state", TensorProto.FLOAT, [2, 2]) scan_outs1_sh = [2, 2, 2] scan_outs1_sh[scan_output_axes[0]] = 3 scan_outs1 = helper.make_tensor_value_info("scan_outs1", TensorProto.FLOAT, scan_outs1_sh) scan_outs2_sh = [2, 2] scan_outs2_sh[scan_output_axes[1]] = 3 scan_outs2 = helper.make_tensor_value_info("scan_outs2", TensorProto.FLOAT, scan_outs2_sh) node = helper.make_node( "Scan", inputs=["init_state", "scan_ins1", "scan_ins2"], outputs=["final_state", "scan_outs1", "scan_outs2"], num_scan_inputs=2, scan_input_axes=scan_input_axes, scan_input_directions=scan_input_directions, scan_output_axes=scan_output_axes, scan_output_directions=scan_output_directions, body=scan_body, ) return ([node], [init_state, scan_ins1, scan_ins2], [final_state, scan_outs1, scan_outs2]) @onnx_test() def scan_test1(): return scan_test() @onnx_test() def scan_test2(): return scan_test(scan_output_directions=[1, 0]) @onnx_test() def scan_test3(): return scan_test(scan_output_axes=[1, -1]) @onnx_test() def scan_test4(): return scan_test(scan_input_directions=[1, 0]) @onnx_test() def scan_test5(): return scan_test(scan_input_axes=[2, -1]) @onnx_test() def scan_test6(): return scan_test(scan_input_axes=[-2, 0], scan_input_directions=[0, 1], scan_output_directions=[1, 1], scan_output_axes=[2, 1]) @onnx_test() def scan_test7(): sum_in = helper.make_tensor_value_info("sum_in", TensorProto.FLOAT, [2, 2]) scan_in = helper.make_tensor_value_info("scan_in", TensorProto.FLOAT, [2, 2]) sum_out = helper.make_tensor_value_info("sum_out", TensorProto.FLOAT, [2, 2]) scan_out = helper.make_tensor_value_info("scan_out", TensorProto.FLOAT, [2, 2]) add1 = helper.make_node("Add", inputs=["sum_in", "scan_in"], outputs=["add1_out"]) add2 = helper.make_node("Add", inputs=["add1_out", "scan_in"], outputs=["sum_out"]) id = helper.make_node("Identity", inputs=["scan_in"], outputs=["scan_out"]) scan_body = helper.make_graph([add1, add2, id], "scan_body", [sum_in, scan_in], [sum_out, scan_out]) init_state = helper.make_tensor_value_info("init_state", TensorProto.FLOAT, [2, 2]) scan_ins_sh = [3, 2, 2] scan_ins = helper.make_tensor_value_info("scan_ins", TensorProto.FLOAT, scan_ins_sh) final_state = helper.make_tensor_value_info("final_state", TensorProto.FLOAT, [2, 2]) scan_outs_sh = [3, 2, 2] scan_outs = helper.make_tensor_value_info("scan_outs", TensorProto.FLOAT, scan_outs_sh) node = helper.make_node( "Scan", inputs=["init_state", "scan_ins"], outputs=["final_state", "scan_outs"], num_scan_inputs=1, scan_input_axes=[0], scan_input_directions=[0], scan_output_axes=[0], scan_output_directions=[0], body=scan_body, ) return ([node], [init_state, scan_ins], [final_state, scan_outs]) def scan_negative_test(scan_input_axes=[0], scan_input_directions=[0], scan_output_axes=[0], scan_output_directions=[0]): sum_in = helper.make_tensor_value_info("sum_in", TensorProto.FLOAT, [2, 2]) scan_in = helper.make_tensor_value_info("scan_in", TensorProto.FLOAT, [2, 2]) sum_out = helper.make_tensor_value_info("sum_out", TensorProto.FLOAT, [2, 2]) scan_out = helper.make_tensor_value_info("scan_out", TensorProto.FLOAT, [2, 2]) add = helper.make_node("Add", inputs=["sum_in", "scan_in"], outputs=["sum_out"]) id = helper.make_node("Identity", inputs=["sum_out"], outputs=["scan_out"]) scan_body = helper.make_graph([add, id], "scan_body", [sum_in, scan_in], [sum_out, scan_out]) init_state = helper.make_tensor_value_info("init_state", TensorProto.FLOAT, [2, 2]) scan_ins = helper.make_tensor_value_info("scan_ins", TensorProto.FLOAT, [3, 2, 2]) final_state = helper.make_tensor_value_info("final_state", TensorProto.FLOAT, [2, 2]) scan_outs = helper.make_tensor_value_info("scan_outs", TensorProto.FLOAT, [3, 2, 2]) node = helper.make_node( "Scan", inputs=["init_state", "scan_ins"], outputs=["final_state", "scan_outs"], num_scan_inputs=1, scan_input_axes=scan_input_axes, scan_input_directions=scan_input_directions, scan_output_axes=scan_output_axes, scan_output_directions=scan_output_directions, body=scan_body, ) return ([node], [init_state, scan_ins], [final_state, scan_outs]) @onnx_test() def scan_invalid_input_axes_len_test(): return scan_negative_test(scan_input_axes=[0, 0]) @onnx_test() def scan_invalid_input_dirs_len_test(): return scan_negative_test(scan_input_directions=[0, 0]) @onnx_test() def scan_invalid_output_axes_len_test(): return scan_negative_test(scan_output_axes=[0, 0]) @onnx_test() def scan_invalid_output_dirs_len_test(): return scan_negative_test(scan_output_directions=[0, 0]) @onnx_test() def scan_invalid_input_axes_vals_test(): return scan_negative_test(scan_input_axes=[3]) @onnx_test() def scan_invalid_input_dirs_vals_test(): return scan_negative_test(scan_input_directions=[2]) @onnx_test() def scan_invalid_output_axes_vals_test(): return scan_negative_test(scan_output_axes=[-4]) @onnx_test() def scan_invalid_output_dirs_vals_test(): return scan_negative_test(scan_output_directions=[-1]) @onnx_test() def scan_arg_count_mismatch_test(): sum_in = helper.make_tensor_value_info("sum_in", TensorProto.FLOAT, [2, 2]) scan_in1 = helper.make_tensor_value_info("scan_in1", TensorProto.FLOAT, [2, 2]) sum_out = helper.make_tensor_value_info("sum_out", TensorProto.FLOAT, [2, 2]) scan_out = helper.make_tensor_value_info("scan_out", TensorProto.FLOAT, [2, 2]) add = helper.make_node("Add", inputs=["sum_in", "scan_in1"], outputs=["sum_out"]) id = helper.make_node("Identity", inputs=["sum_out"], outputs=["scan_out"]) scan_body = helper.make_graph([add, id], "scan_body", [sum_in, scan_in1], [sum_out, scan_out]) init_state = helper.make_tensor_value_info("init_state", TensorProto.FLOAT, [2, 2]) scan_ins1 = helper.make_tensor_value_info("scan_ins1", TensorProto.FLOAT, [3, 2, 2]) scan_ins2 = helper.make_tensor_value_info("scan_ins2", TensorProto.FLOAT, [2, 3, 2]) final_state = helper.make_tensor_value_info("final_state", TensorProto.FLOAT, [2, 2]) scan_outs = helper.make_tensor_value_info("scan_outs", TensorProto.FLOAT, [3, 2, 2]) node = helper.make_node( "Scan", inputs=["init_state", "scan_ins1", "scan_ins2"], outputs=["final_state", "scan_outs"], num_scan_inputs=2, scan_input_axes=[0, 0], scan_input_directions=[0, 0], scan_output_axes=[0], scan_output_directions=[0], body=scan_body, ) return ([node], [init_state, scan_ins1, scan_ins2], [final_state, scan_outs]) @onnx_test() def scan_input_axes_lens_mismatch_test(): sum_in = helper.make_tensor_value_info("sum_in", TensorProto.FLOAT, [2, 2]) scan_in1 = helper.make_tensor_value_info("scan_in1", TensorProto.FLOAT, [2, 2]) scan_in2 = helper.make_tensor_value_info("scan_in2", TensorProto.FLOAT, [2, 2]) sum_out = helper.make_tensor_value_info("sum_out", TensorProto.FLOAT, [2, 2]) scan_out = helper.make_tensor_value_info("scan_out", TensorProto.FLOAT, [2, 2]) add = helper.make_node("Add", inputs=["sum_in", "scan_in1"], outputs=["sum_out"]) id = helper.make_node("Identity", inputs=["sum_out"], outputs=["scan_out"]) scan_body = helper.make_graph([add, id], "scan_body", [sum_in, scan_in1, scan_in2], [sum_out, scan_out]) init_state = helper.make_tensor_value_info("init_state", TensorProto.FLOAT, [2, 2]) scan_ins1 = helper.make_tensor_value_info("scan_ins1", TensorProto.FLOAT, [3, 2, 2]) scan_ins2 = helper.make_tensor_value_info("scan_ins2", TensorProto.FLOAT, [2, 3, 2]) final_state = helper.make_tensor_value_info("final_state", TensorProto.FLOAT, [2, 2]) scan_outs = helper.make_tensor_value_info("scan_outs", TensorProto.FLOAT, [3, 2, 2]) node = helper.make_node( "Scan", inputs=["init_state", "scan_ins1", "scan_ins2"], outputs=["final_state", "scan_outs"], num_scan_inputs=2, scan_input_axes=[0, 0], scan_input_directions=[0, 0], scan_output_axes=[0], scan_output_directions=[0], body=scan_body, ) return ([node], [init_state, scan_ins1, scan_ins2], [final_state, scan_outs]) @onnx_test() def scan_arg_shapes_mismatch_test(): sum_in = helper.make_tensor_value_info("sum_in", TensorProto.FLOAT, [2, 2]) scan_in1 = helper.make_tensor_value_info("scan_in1", TensorProto.FLOAT, [2, 2]) scan_in2 = helper.make_tensor_value_info("scan_in2", TensorProto.FLOAT, [2, 2]) sum_out = helper.make_tensor_value_info("sum_out", TensorProto.FLOAT, [2, 2]) scan_out = helper.make_tensor_value_info("scan_out", TensorProto.FLOAT, [2, 2]) add = helper.make_node("Add", inputs=["sum_in", "scan_in1"], outputs=["sum_out"]) id = helper.make_node("Identity", inputs=["sum_out"], outputs=["scan_out"]) scan_body = helper.make_graph([add, id], "scan_body", [sum_in, scan_in1, scan_in2], [sum_out, scan_out]) init_state = helper.make_tensor_value_info("init_state", TensorProto.FLOAT, [2, 2]) scan_ins1 = helper.make_tensor_value_info("scan_ins1", TensorProto.FLOAT, [3, 2, 2]) scan_ins2 = helper.make_tensor_value_info("scan_ins2", TensorProto.FLOAT, [3, 2]) final_state = helper.make_tensor_value_info("final_state", TensorProto.FLOAT, [2, 2]) scan_outs = helper.make_tensor_value_info("scan_outs", TensorProto.FLOAT, [3, 2, 2]) node = helper.make_node( "Scan", inputs=["init_state", "scan_ins1", "scan_ins2"], outputs=["final_state", "scan_outs"], num_scan_inputs=2, scan_input_axes=[0, 0], scan_input_directions=[0, 0], scan_output_axes=[0], scan_output_directions=[0], body=scan_body, ) return ([node], [init_state, scan_ins1, scan_ins2], [final_state, scan_outs]) ROCm-AMDMIGraphX-46524e8/test/onnx/globalavgpool_dyn_test.onnx000066400000000000000000000002111510465702400241400ustar00rootroot00000000000000globalavgpool_dyn_test:i  01"GlobalAveragePoolglobalavgpool_dyn_testZ 0    b 1    B ROCm-AMDMIGraphX-46524e8/test/onnx/globalavgpool_fp8_test.onnx000066400000000000000000000002151510465702400240470ustar00rootroot00000000000000 globalavgpool_fp8_test:m  01"GlobalAveragePoolglobalavgpool_fp8_testZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/globalavgpool_test.onnx000066400000000000000000000002101510465702400232650ustar00rootroot00000000000000globalavgpool-example:i  01"GlobalAveragePooltest-globalavgpoolZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/globallppool_dyn_test.onnx000066400000000000000000000002021510465702400237760ustar00rootroot00000000000000globallppool_dyn_test:c  01" GlobalLpPoolgloballppool_dyn_testZ 0    b 1     B ROCm-AMDMIGraphX-46524e8/test/onnx/globallppool_test.onnx000066400000000000000000000001761510465702400231360ustar00rootroot00000000000000globallppool_test:c  01" GlobalLpPoolgloballppool_testZ 0     b 1     B ROCm-AMDMIGraphX-46524e8/test/onnx/globalmaxpool_dyn_test.onnx000066400000000000000000000002051510465702400241530ustar00rootroot00000000000000globalmaxpool_dyn_test:e  01" GlobalMaxPoolglobalmaxpool_dyn_testZ 0     b 1    B ROCm-AMDMIGraphX-46524e8/test/onnx/globalmaxpool_fp8_test.onnx000066400000000000000000000002111510465702400240530ustar00rootroot00000000000000 globalmaxpool_fp8_test:i  01" GlobalMaxPoolglobalmaxpool_fp8_testZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/globalmaxpool_test.onnx000066400000000000000000000002041510465702400233000ustar00rootroot00000000000000globalmaxpool-example:e  01" GlobalMaxPooltest-globalmaxpoolZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/greater_bool_test.onnx000066400000000000000000000002431510465702400231070ustar00rootroot00000000000000greater_bool_test:‡  x1bx1"Cast* to    bx1 x2y"Greatergreater_bool_testZ x1   Z x2    b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/greater_test.onnx000066400000000000000000000002131510465702400220710ustar00rootroot00000000000000 greater_test:u  x1 x2y"Greater greater_test*$"€?@@@€@ @À@Bx1Z x2   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/greaterorequal_test.onnx000066400000000000000000000002041510465702400234620ustar00rootroot00000000000000greaterorequal_test:g  x1 x2y"GreaterOrEqualgreaterorequal_testZ x1  Z x2  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_512x512_test.onnx000066400000000000000000000003731510465702400236050ustar00rootroot00000000000000 gridsample_512x512_test:Ù ^ x gridy" GridSample* align_corners * mode"bilinear * padding_mode"border gridsample_512x512_testZ x    € €Z grid   € € b y    € €BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_aligncorners_true_test.onnx000066400000000000000000000003541510465702400264020ustar00rootroot00000000000000 !gridsample_aligncorners_true_test:À A x gridy" GridSample* align_corners * mode"linear !gridsample_aligncorners_true_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_bf16_test.onnx000066400000000000000000000003541510465702400234130ustar00rootroot00000000000000 gridsample_bf16_test:Í [ x gridy" GridSample* align_corners * mode"linear * padding_mode"zeros gridsample_bf16_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_bicubic_align_corners_0_additional_1_test.onnx000066400000000000000000000004211510465702400320040ustar00rootroot00000000000000 4gridsample_bicubic_align_corners_0_additional_1_test:Ò @ x gridy" GridSample* align_corners * mode"cubic 4gridsample_bicubic_align_corners_0_additional_1_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_bicubic_align_corners_1_additional_1_test.onnx000066400000000000000000000004211510465702400320050ustar00rootroot00000000000000 4gridsample_bicubic_align_corners_1_additional_1_test:Ò @ x gridy" GridSample* align_corners * mode"cubic 4gridsample_bicubic_align_corners_1_additional_1_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_bicubic_test.onnx000066400000000000000000000003011510465702400242450ustar00rootroot00000000000000 gridsample_bicubic_test:Ÿ * x gridy" GridSample* mode"cubic gridsample_bicubic_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_bilinear_align_corners_0_additional_1_test.onnx000066400000000000000000000004241510465702400321740ustar00rootroot00000000000000 5gridsample_bilinear_align_corners_0_additional_1_test:Ô A x gridy" GridSample* align_corners * mode"linear 5gridsample_bilinear_align_corners_0_additional_1_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_bilinear_align_corners_1_additional_1_test.onnx000066400000000000000000000004241510465702400321750ustar00rootroot00000000000000 5gridsample_bilinear_align_corners_1_additional_1_test:Ô A x gridy" GridSample* align_corners * mode"linear 5gridsample_bilinear_align_corners_1_additional_1_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_bilinear_test.onnx000066400000000000000000000003321510465702400244360ustar00rootroot00000000000000 gridsample_bilinear_test:· A x gridy" GridSample* align_corners * mode"linear gridsample_bilinear_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_border_padding_test.onnx000066400000000000000000000003301510465702400256120ustar00rootroot00000000000000 gridsample_border_padding_test:¯ 3 x gridy" GridSample* padding_mode"border gridsample_border_padding_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_channel_test.onnx000066400000000000000000000003651510465702400242670ustar00rootroot00000000000000 gridsample_channel_test:Ó ^ x gridy" GridSample* align_corners * mode"bilinear * padding_mode"border gridsample_channel_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_half_test.onnx000066400000000000000000000003541510465702400235670ustar00rootroot00000000000000 gridsample_half_test:Í [ x gridy" GridSample* align_corners * mode"linear * padding_mode"zeros gridsample_half_testZ x      Z grid     b y      BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_int_test.onnx000066400000000000000000000003521510465702400234450ustar00rootroot00000000000000 gridsample_int_test:Ì [ x gridy" GridSample* align_corners * mode"linear * padding_mode"zeros gridsample_int_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_mismatching_dims_test.onnx000066400000000000000000000003051510465702400261700ustar00rootroot00000000000000  gridsample_mismatching_dims_test:š  x gridy" GridSample gridsample_mismatching_dims_testZ x      Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_nearest_align_corners_0_additional_1_test.onnx000066400000000000000000000004231510465702400320470ustar00rootroot00000000000000 4gridsample_nearest_align_corners_0_additional_1_test:Ô B x gridy" GridSample* align_corners * mode"nearest 4gridsample_nearest_align_corners_0_additional_1_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_nearest_align_corners_1_additional_1_test.onnx000066400000000000000000000004231510465702400320500ustar00rootroot00000000000000 4gridsample_nearest_align_corners_1_additional_1_test:Ô B x gridy" GridSample* align_corners * mode"nearest 4gridsample_nearest_align_corners_1_additional_1_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_nearest_test.onnx000066400000000000000000000003031510465702400243100ustar00rootroot00000000000000 gridsample_nearest_test:¡ , x gridy" GridSample* mode"nearest gridsample_nearest_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_reflection_padding_test.onnx000066400000000000000000000003441510465702400264740ustar00rootroot00000000000000 "gridsample_reflection_padding_test:· 7 x gridy" GridSample* padding_mode" reflection "gridsample_reflection_padding_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_simple_test.onnx000066400000000000000000000003001510465702400241350ustar00rootroot00000000000000 gridsample_simple_test:Ÿ + x gridy" GridSample* mode"linear gridsample_simple_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_test.onnx000066400000000000000000000003421510465702400225720ustar00rootroot00000000000000 gridsample_test:È [ x gridy" GridSample* align_corners * mode"linear * padding_mode"zeros gridsample_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_volumetric_nearest_align_corners_0_test.onnx000066400000000000000000000004331510465702400317110ustar00rootroot00000000000000 2gridsample_volumetric_nearest_align_corners_0_test:Þ B x gridy" GridSample* align_corners * mode"nearest 2gridsample_volumetric_nearest_align_corners_0_testZ x      Z" grid      b y      BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_wrong_grid_type_test.onnx000066400000000000000000000002771510465702400260630ustar00rootroot00000000000000 gridsample_wrong_grid_type_test:•  x gridy" GridSamplegridsample_wrong_grid_type_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/gridsample_zeros_padding_test.onnx000066400000000000000000000003251510465702400255030ustar00rootroot00000000000000 gridsample_zeros_padding_test:­ 2 x gridy" GridSample* padding_mode"zeros gridsample_zeros_padding_testZ x     Z grid     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_conv_test.onnx000066400000000000000000000002441510465702400226250ustar00rootroot00000000000000group_conv-example:‡  0 12"Conv* group test-group_convZ 0     Z 1     b 2     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_3d_bf16_test.onnx000066400000000000000000000003451510465702400240410ustar00rootroot00000000000000 group_norm_3d_bf16_test:à M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_3d_bf16_testZ x    Z scale  Z bias  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_3d_half_test.onnx000066400000000000000000000003451510465702400242150ustar00rootroot00000000000000 group_norm_3d_half_test:à M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_3d_half_testZ x     Z scale   Z bias   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_3d_test.onnx000066400000000000000000000003331510465702400232200ustar00rootroot00000000000000 group_norm_3d_test:¾ M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_3d_testZ x    Z scale  Z bias  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_4d_bf16_test.onnx000066400000000000000000000003551510465702400240430ustar00rootroot00000000000000 group_norm_4d_bf16_test:Ë M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_4d_bf16_testZ x     Z scale  Z bias  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_4d_half_test.onnx000066400000000000000000000003551510465702400242170ustar00rootroot00000000000000 group_norm_4d_half_test:Ë M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_4d_half_testZ x      Z scale   Z bias   b y      BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_4d_test.onnx000066400000000000000000000003431510465702400232220ustar00rootroot00000000000000 group_norm_4d_test:Æ M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_4d_testZ x     Z scale  Z bias  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_5d_bf16_test.onnx000066400000000000000000000003651510465702400240450ustar00rootroot00000000000000 group_norm_5d_bf16_test:Ó M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_5d_bf16_testZ x      Z scale  Z bias  b y      BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_5d_half_test.onnx000066400000000000000000000003651510465702400242210ustar00rootroot00000000000000 group_norm_5d_half_test:Ó M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_5d_half_testZ x       Z scale   Z bias   b y       BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_5d_test.onnx000066400000000000000000000003531510465702400232240ustar00rootroot00000000000000 group_norm_5d_test:Î M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups group_norm_5d_testZ x      Z scale  Z bias  b y      BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_3d_channel_last_bf16_test.onnx000066400000000000000000000004531510465702400302740ustar00rootroot00000000000000 ,group_norm_contrib_3d_channel_last_bf16_test:ô i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups ,group_norm_contrib_3d_channel_last_bf16_testZ x    Z gamma  Z beta  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_3d_channel_last_half_test.onnx000066400000000000000000000004531510465702400304500ustar00rootroot00000000000000 ,group_norm_contrib_3d_channel_last_half_test:ô i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups ,group_norm_contrib_3d_channel_last_half_testZ x     Z gamma   Z beta   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_3d_channel_last_test.onnx000066400000000000000000000004411510465702400274530ustar00rootroot00000000000000 'group_norm_contrib_3d_channel_last_test:ï i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups 'group_norm_contrib_3d_channel_last_testZ x    Z gamma  Z beta  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_3d_test.onnx000066400000000000000000000004071510465702400247420ustar00rootroot00000000000000 group_norm_contrib_3d_test:â i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups group_norm_contrib_3d_testZ x    Z gamma  Z beta  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_channels_last_3d_test.onnx000066400000000000000000000004431510465702400276400ustar00rootroot00000000000000 (group_norm_contrib_channels_last_3d_test:ð i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups (group_norm_contrib_channels_last_3d_testZ x    Z gamma  Z beta  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_channels_last_4d_test.onnx000066400000000000000000000004531510465702400276420ustar00rootroot00000000000000 (group_norm_contrib_channels_last_4d_test:ø i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups (group_norm_contrib_channels_last_4d_testZ x     Z gamma  Z beta  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_channels_last_and_silu_3d_test.onnx000066400000000000000000000004651510465702400315220ustar00rootroot00000000000000 1group_norm_contrib_channels_last_and_silu_3d_test:ù i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups 1group_norm_contrib_channels_last_and_silu_3d_testZ x    Z gamma  Z beta  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_gamma_beta_float_xy_half_test.onnx000066400000000000000000000004631510465702400314120ustar00rootroot00000000000000 0group_norm_contrib_gamma_beta_float_xy_half_test:ø i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups 0group_norm_contrib_gamma_beta_float_xy_half_testZ x     Z gamma  Z beta  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_no_activation_attr_test.onnx000066400000000000000000000004011510465702400303150ustar00rootroot00000000000000 *group_norm_contrib_no_activation_attr_test:Ì C x gamma betay" GroupNorm* channels_last * groups *group_norm_contrib_no_activation_attr_testZ x    Z gamma  Z beta  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_no_num_groups_attr_test.onnx000066400000000000000000000004051510465702400303560ustar00rootroot00000000000000 *group_norm_contrib_no_num_groups_attr_test:Ð G x gamma betay" GroupNorm* activation * channels_last *group_norm_contrib_no_num_groups_attr_testZ x    Z gamma  Z beta  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_contrib_silu_3d_test.onnx000066400000000000000000000004211510465702400257720ustar00rootroot00000000000000 group_norm_contrib_silu_3d_test:ç i x gamma betay" GroupNorm* activation * channels_last * epsilon¬Å'7 * groups group_norm_contrib_silu_3d_testZ x    Z gamma  Z beta  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_invalid_bias_shape_test.onnx000066400000000000000000000004031510465702400265140ustar00rootroot00000000000000 "group_norm_invalid_bias_shape_test:Ö M x scale biasy"GroupNormalization* epsilon¬Å'7 * num_groups "group_norm_invalid_bias_shape_testZ x     Z scale  Z bias  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_invalid_input_count_error_test.onnx000066400000000000000000000003441510465702400302020ustar00rootroot00000000000000 )group_norm_invalid_input_count_error_test:° 4 x scaley"GroupNormalization* num_groups )group_norm_invalid_input_count_error_testZ x     Z scale  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_invalid_input_shape_error_test.onnx000066400000000000000000000003561510465702400301550ustar00rootroot00000000000000 )group_norm_invalid_input_shape_error_test:º : x scale biasy"GroupNormalization* num_groups )group_norm_invalid_input_shape_error_testZ x   Z scale  Z bias  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_invalid_num_groups_error_test.onnx000066400000000000000000000003741510465702400300340ustar00rootroot00000000000000 (group_norm_invalid_num_groups_error_test:É : x scale biasy"GroupNormalization* num_groups (group_norm_invalid_num_groups_error_testZ x     Z scale  Z bias  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_invalid_scale_shape_test.onnx000066400000000000000000000003621510465702400266710ustar00rootroot00000000000000 #group_norm_invalid_scale_shape_test:Ä : x scale biasy"GroupNormalization* num_groups #group_norm_invalid_scale_shape_testZ x     Z scale  Z bias  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_missing_attribute_error_test.onnx000066400000000000000000000003271510465702400276620ustar00rootroot00000000000000 'group_norm_missing_attribute_error_test:¥ ' x scale biasy"GroupNormalization'group_norm_missing_attribute_error_testZ x   Z scale  Z bias  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_small_eps_bf16_test.onnx000066400000000000000000000003631510465702400255120ustar00rootroot00000000000000 group_norm_small_eps_bf16_test:Ê M x scale biasy"GroupNormalization* epsilon•¿Ö3 * num_groups group_norm_small_eps_bf16_testZ x    Z scale  Z bias  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/group_norm_small_eps_half_test.onnx000066400000000000000000000003631510465702400256660ustar00rootroot00000000000000 group_norm_small_eps_half_test:Ê M x scale biasy"GroupNormalization* epsilon̼Œ+ * num_groups group_norm_small_eps_half_testZ x     Z scale   Z bias   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/group_query_attention_defaults_test.onnx000066400000000000000000040012271510465702400270070ustar00rootroot00000000000000 #group_query_attention_defaults_test:è„@ ½ qkv key value past_key_values_key past_key_values_value seqlens_k total_sequence_length cos_cache sin_cacheoutput present_key present_value"GroupQueryAttention: com.microsoft#group_query_attention_defaults_test**B seqlens_k**Btotal_sequence_length*–€ € @ *€€ €x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€xB cos_cache*–€ € @ *€€ €x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€xB sin_cacheZ qkv     €`Z key  Z value  Z/ past_key_values_key     € €Z1 past_key_values_value     € €b output     € b' present_key     € €b) present_value     € €BROCm-AMDMIGraphX-46524e8/test/onnx/group_query_attention_invalid_test.onnx000066400000000000000000000012641510465702400266230ustar00rootroot00000000000000 "group_query_attention_invalid_test:‡ ¹ qkv past_key_values_key past_key_values_value seqlens_k total_sequence_length cos_cache sin_cacheoutput present_key present_value"GroupQueryAttention* do_rotary * kv_num_heads  *! local_window_sizeÿÿÿÿÿÿÿÿÿ * num_heads  * rotary_interleaved * scale€? : com.microsoft"group_query_attention_invalid_test**B seqlens_k**Btotal_sequence_lengthZ qkv     €`Z/ past_key_values_key     € €Z1 past_key_values_value     € €b output     € b' present_key     € €b) present_value     € €BROCm-AMDMIGraphX-46524e8/test/onnx/group_query_attention_non_packed_qkv_test.onnx000066400000000000000000040015011510465702400301550ustar00rootroot00000000000000 )group_query_attention_non_packed_qkv_test:Œ†@ Ç query key value past_key_values_key past_key_values_value seqlens_k total_sequence_length cos_cache sin_cacheoutput present_key present_value"GroupQueryAttention* do_rotary * kv_num_heads  *! local_window_sizeÿÿÿÿÿÿÿÿÿ * num_heads  * rotary_interleaved * scale€? : com.microsoft)group_query_attention_non_packed_qkv_test**B seqlens_k**Btotal_sequence_length*–€ € @ *€€ €x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€xB cos_cache*–€ € @ *€€ €x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€xB sin_cacheZ query     € Z key     € Z value     € Z/ past_key_values_key     € €Z1 past_key_values_value     € €b output     € b' present_key     € €b) present_value     € €BROCm-AMDMIGraphX-46524e8/test/onnx/group_query_attention_softcap_test.onnx000066400000000000000000040012501510465702400266330ustar00rootroot00000000000000 "group_query_attention_softcap_test:ú„@ Ð qkv key value past_key_values_key past_key_values_value seqlens_k total_sequence_length cos_cache sin_cacheoutput present_key present_value"GroupQueryAttention* softcap€? : com.microsoft"group_query_attention_softcap_test**B seqlens_k**Btotal_sequence_length*–€ € @ *€€ €x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€xB cos_cache*–€ € @ *€€ €x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€xB sin_cacheZ qkv     €`Z key  Z value  Z/ past_key_values_key     € €Z1 past_key_values_value     € €b output     € b' present_key     € €b) present_value     € €BROCm-AMDMIGraphX-46524e8/test/onnx/group_query_attention_test.onnx000066400000000000000000040014151510465702400251170ustar00rootroot00000000000000 group_query_attention_test:ç…@ Å qkv key value past_key_values_key past_key_values_value seqlens_k total_sequence_length cos_cache sin_cacheoutput present_key present_value"GroupQueryAttention* do_rotary * kv_num_heads  *! local_window_sizeÿÿÿÿÿÿÿÿÿ * num_heads  * rotary_interleaved * scale€? : com.microsoftgroup_query_attention_test**B seqlens_k**Btotal_sequence_length*–€ € @ *€€ €x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€xB cos_cache*–€ € @ *€€ €x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€x€xB sin_cacheZ qkv     €`Z key  Z value  Z/ past_key_values_key     € €Z1 past_key_values_value     € €b output     € b' present_key     € €b) present_value     € €BROCm-AMDMIGraphX-46524e8/test/onnx/gru_bi_5arg_layout_test.onnx000066400000000000000000000006711510465702400242320ustar00rootroot00000000000000 gru_bi_5arg_layout_test:— à seq w r bias seq_lenhsoutput"GRU*+ activationsJtanhJsigmoidJreluJtanh * clip * direction" bidirectional * hidden_size * layout * linear_before_reset gru_bi_5arg_layout_testZ seq     Z w   <  Z r   < Z bias   xZ seq_len  b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/gru_bi_layout_test.onnx000066400000000000000000000006611510465702400233130ustar00rootroot00000000000000 gru_bi_layout_test:” « seq w r bias seq_len h0hsoutput"GRU*+ activationsJtanhJsigmoidJreluJtanh * clip * direction" bidirectional * hidden_size * layout gru_bi_layout_testZ seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/gru_f_1af_test.onnx000066400000000000000000000004421510465702400222750ustar00rootroot00000000000000 gru_f_1af_test:‰ n seq w rhsoutput"GRU* activationsJtanh * clip * direction"forward * hidden_size gru_f_1af_testZ seq     Z w   <  Z r   < b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/gru_f_3arg_layout_test.onnx000066400000000000000000000005131510465702400240560ustar00rootroot00000000000000 gru_f_3arg_layout_test:ª † seq w rhsoutput"GRU* activationsJtanhJsigmoid * clip * direction"forward * hidden_size * layout gru_f_3arg_layout_testZ seq     Z w   <  Z r   < b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/gru_f_layout_test.onnx000066400000000000000000000006711510465702400231470ustar00rootroot00000000000000 gru_f_layout_test: µ seq w r bias seq_len h0hsoutput"GRU* activationsJtanhJsigmoid * clip * direction"forward * hidden_size * layout * linear_before_reset gru_f_layout_testZ seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/gru_r_4arg_layout_test.onnx000066400000000000000000000006021510465702400240720ustar00rootroot00000000000000 gru_r_4arg_layout_test:á ¥ seq w r biashsoutput"GRU* activationsJreluJtanh * clip * direction"reverse * hidden_size * layout * linear_before_reset gru_r_4arg_layout_testZ seq     Z w   <  Z r   < Z bias   xb hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/gru_r_layout_test.onnx000066400000000000000000000006351510465702400231630ustar00rootroot00000000000000 gru_r_layout_test: ™ seq w r bias seq_len h0hsoutput"GRU* activationsJtanhJsigmoid * clip * direction"reverse * hidden_size * layout gru_r_layout_testZ seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/hardmax_axis_neg_test.onnx000066400000000000000000000002271510465702400237460ustar00rootroot00000000000000 hardmax_axis_neg_test:x % xy"Hardmax* axisýÿÿÿÿÿÿÿÿ hardmax_axis_neg_testZ x      b y      BROCm-AMDMIGraphX-46524e8/test/onnx/hardmax_axis_neg_ver11_test.onnx000066400000000000000000000002431510465702400247620ustar00rootroot00000000000000hardmax_axis_neg_ver11_test:~ % xy"Hardmax* axisýÿÿÿÿÿÿÿÿ hardmax_axis_neg_ver11_testZ x      b y      B ROCm-AMDMIGraphX-46524e8/test/onnx/hardmax_axis_test.onnx000066400000000000000000000002061510465702400231120ustar00rootroot00000000000000 hardmax_axis_test:k  xy"Hardmax* axis hardmax_axis_testZ x      b y      BROCm-AMDMIGraphX-46524e8/test/onnx/hardmax_axis_ver11_test.onnx000066400000000000000000000002221510465702400241260ustar00rootroot00000000000000hardmax_axis_ver11_test:q  xy"Hardmax* axis hardmax_axis_ver11_testZ x      b y      B ROCm-AMDMIGraphX-46524e8/test/onnx/hardmax_default_test.onnx000066400000000000000000000001771510465702400236010ustar00rootroot00000000000000 hardmax_default_test:a  xy"Hardmaxhardmax_default_testZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/hardmax_default_ver11_test.onnx000066400000000000000000000002131510465702400246060ustar00rootroot00000000000000hardmax_default_ver11_test:g  xy"Hardmaxhardmax_default_ver11_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/hardsigmoid_bf16_test.onnx000066400000000000000000000002051510465702400235510ustar00rootroot00000000000000 hardsigmoid_bf16_test:f  xy" HardSigmoidhardsigmoid_bf16_testZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/hardsigmoid_default_test.onnx000066400000000000000000000002131510465702400244360ustar00rootroot00000000000000hardsigmoid_default_test:i  xy" HardSigmoidhardsigmoid_default_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/hardsigmoid_double_test.onnx000066400000000000000000000002531510465702400242700ustar00rootroot00000000000000hardsigmoid_double_test:‰ 4 xy" HardSigmoid* alphaš™™> * beta333? hardsigmoid_double_testZ x      b y      B ROCm-AMDMIGraphX-46524e8/test/onnx/hardsigmoid_half_test.onnx000066400000000000000000000002051510465702400237250ustar00rootroot00000000000000hardsigmoid_half_test:f  xy" HardSigmoidhardsigmoid_half_testZ x      b y      B ROCm-AMDMIGraphX-46524e8/test/onnx/hardsigmoid_verify_test.onnx000066400000000000000000000001711510465702400243210ustar00rootroot00000000000000hardsigmoid_verify_test:X  xy" HardSigmoidhardsigmoid_verify_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/hardswish_test.onnx000066400000000000000000000001451510465702400224400ustar00rootroot00000000000000hardswish_test:M  xy" HardSwishhardswish_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/if_else_test.onnx000066400000000000000000000005661510465702400220610ustar00rootroot00000000000000 if_else_test:ß µ condres"If*Q else_branch2?  y ytelse_out"Mul else_bodyb else_out    *Q then_branch2?  x xtthen_out"Add then_bodyb then_out     if_else_test*$"€?€?€?€?€?€?Bxt*$"õx±?³Pý¾f'‘¾£MM>cåú¾èt©?BytZ x   Z y   Z cond   b res B ROCm-AMDMIGraphX-46524e8/test/onnx/if_else_test_inlined.onnx000066400000000000000000000006011510465702400235510ustar00rootroot00000000000000if_else_test_inlined:â µ condres"If*Q else_branch2?  y ytelse_out"Mul else_bodyb else_out    *Q then_branch2?  x xtthen_out"Add then_bodyb then_out    if_else_test_inlined*  *Bcond*$"€?€?€?€?€?€?Bxt*$"«¸O?*$s¿œV-¾o%»>¾Ü„@BytZ x   Z y   b res B ROCm-AMDMIGraphX-46524e8/test/onnx/if_literal_test.onnx000066400000000000000000000006401510465702400225560ustar00rootroot00000000000000if_literal_test:† Ó condret"If*Ÿ else_branch2Œ <else_out"Constant*& value*J @€@@@@€?  ) empty_out"Constant* value*J  else_bodyb else_out   *Ÿ then_branch2Œ <then_out"Constant*& value*J€?@@@€@ @  ) empty_out"Constant* value*J  then_bodyb then_out   if_literal_testZ cond  b ret B ROCm-AMDMIGraphX-46524e8/test/onnx/if_param_excp1_test.onnx000066400000000000000000000005511510465702400233230ustar00rootroot00000000000000if_param_excp1_test:Ë û condret"If*t else_branch2b  x xtsub_out"Addsub_body*$"™õ?¤³é=¢¾:?Y±.>E±l?òÕ‹¿Bxtb sub_out    *t then_branch2b  x xtsub_out"Addsub_body*$"™õ?¤³é=¢¾:?Y±.>E±l?òÕ‹¿Bxtb sub_out    if_param_excp1_testZ cond   Z x   b ret B ROCm-AMDMIGraphX-46524e8/test/onnx/if_param_excp_test.onnx000066400000000000000000000006061510465702400232430ustar00rootroot00000000000000if_param_excp_test:é ‰ condret"If* else_branch2m  y ytelse_out"Mul else_body*," ñ#?]V»?ÁW*¾:@7?Ï1ž>\:Ztž?¦%c¾Bytb else_out    *w then_branch2e  x xtthen_out"Add then_body*$"…°@Ó¹J¿\þÉ>=Ýš½7±­>>ˆ½Bxtb then_out    if_param_excp_testZ cond  Z x   Z y   b ret B ROCm-AMDMIGraphX-46524e8/test/onnx/if_param_test.onnx000066400000000000000000000005641510465702400222270ustar00rootroot00000000000000 if_param_test:Ü  condret"If*w else_branch2e  y ytelse_out"Mul else_body*$"Ì„¾„…¸>l ?<è¿Ç‚?W ƒ?Bytb else_out    *w then_branch2e  x xtthen_out"Add then_body*$"Å> Æã¿BUè¾Çrô>=ˆ¿Ô€¿Bxtb then_out     if_param_testZ cond  Z x   Z y   b ret B ROCm-AMDMIGraphX-46524e8/test/onnx/if_pl_test.onnx000066400000000000000000000010721510465702400215350ustar00rootroot00000000000000 if_pl_test:¥ õ condret"If*ª else_branch2—  y ytout_y"Mul Aout_l_x"Constant*, value* J€?@@@€@ @À@  else_bodyb out_l_x   b out_y    *¶ then_branch2£  x xtout_x"Add Mout_l_y"Constant*8 value*,J$Aà@À@ @€@@@@€?  then_bodyb out_x   b out_l_y     if_pl_test*$"€?@@@€@ @À@Bxt*0"$Aà@À@ @€@@@@€?BytZ cond  Z x   Z y   b ret B ROCm-AMDMIGraphX-46524e8/test/onnx/if_then_else_multi_output_shapes_inlined_test.onnx000066400000000000000000000011061510465702400307650ustar00rootroot00000000000000-if_then_else_multi_output_shapes_inlined_test:Ž ± condres1res2"If*‡ else_branch2u  y ytelse_out"Mul  y yt else_out2"Sub else_bodyb else_out   b else_out2    *Ž then_branch2|  x xtthen_out"Add  x x then_out2"Add then_bodyb then_out    b then_out2     -if_then_else_multi_output_shapes_inlined_test*  *Bcond*&"€?€?€?€?€?€?Bxt*$" Z‚¿ßoœ¾‚¾¶˜d?¼±?xŽ¿BytZ x    Z y   b res1 b res2 BROCm-AMDMIGraphX-46524e8/test/onnx/if_then_else_multi_output_shapes_test.onnx000066400000000000000000000011111510465702400272570ustar00rootroot00000000000000%if_then_else_multi_output_shapes_test:™ ¹ condres1res2"If* else_branch2}  y ytelse_out"Mul  y yt else_out2"Sub else_bodyb else_out    b else_out2     *Ž then_branch2|  x xtthen_out"Add  x x then_out2"Add then_bodyb then_out    b then_out2     %if_then_else_multi_output_shapes_test*&"€?€?€?€?€?€?Bxt*&"÷A¿l45?·¤]¿Fº@< î>tË,¾BytZ x    Z y    Z cond   b res1 b res2 BROCm-AMDMIGraphX-46524e8/test/onnx/if_then_test.onnx000066400000000000000000000005661510465702400220670ustar00rootroot00000000000000 if_then_test:ß µ condres"If*Q else_branch2?  y ytelse_out"Mul else_bodyb else_out    *Q then_branch2?  x xtthen_out"Add then_bodyb then_out     if_then_test*$"€?€?€?€?€?€?Bxt*$"Õ¨ˆ¾ï§8¾€þ½\lž¿8êŸ>Æ¥—?BytZ x   Z y   Z cond   b res B ROCm-AMDMIGraphX-46524e8/test/onnx/if_then_test_inlined.onnx000066400000000000000000000005611510465702400235640ustar00rootroot00000000000000 if_then_test:Ú µ condres"If*Q else_branch2?  y ytelse_out"Mul else_bodyb else_out    *Q then_branch2?  x xtthen_out"Add then_bodyb then_out     if_then_test*  *Bcond*$"€?€?€?€?€?€?Bxt*$"Mç¡¿À]§}?K»Ð?ÍîO?ý$3¾BytZ x   Z y   b res B ROCm-AMDMIGraphX-46524e8/test/onnx/if_tuple_test.onnx000066400000000000000000000007571510465702400222640ustar00rootroot00000000000000 if_tuple_test:× ¶ condres0res1"If* else_branch2}  x three else_out0"Mul  y three else_out1"Add else_bodyb else_out0   b else_out1    *‹ then_branch2y  x one then_out0"Add  y two then_out1"Mul then_bodyb then_out0   b then_out1     if_tuple_test*"€?Bone*"@Btwo*"@@BthreeZ cond  Z x   Z y   b res0 b res1 B ROCm-AMDMIGraphX-46524e8/test/onnx/imagescaler_bf16_test.onnx000066400000000000000000000002611510465702400235350ustar00rootroot00000000000000 imagescaler_bf16_test:‘ > 01" ImageScaler* bias= ×#<= ×£<=Âõ< * scale? imagescaler_bf16_testZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/imagescaler_half_test.onnx000066400000000000000000000002611510465702400237110ustar00rootroot00000000000000imagescaler_half_test:‘ > 01" ImageScaler* bias= ×#<= ×£<=Âõ< * scale? imagescaler_half_testZ 0      b 1      B ROCm-AMDMIGraphX-46524e8/test/onnx/imagescaler_test.onnx000066400000000000000000000002521510465702400227170ustar00rootroot00000000000000imagescaler-example:Œ > 01" ImageScaler* bias= ×#<= ×£<=Âõ< * scale? test-imagescalerZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/implicit_add_bcast_test.onnx000066400000000000000000000002251510465702400242410ustar00rootroot00000000000000implicit_bcast-example:u  0 12"Addtest-multi_bcastZ 0     Z 1    b 2     B ROCm-AMDMIGraphX-46524e8/test/onnx/implicit_pow_bcast_test.onnx000066400000000000000000000001771510465702400243240ustar00rootroot00000000000000pow2:q  0 1out"Powpow_testZ 0     Z 1    b out     B ROCm-AMDMIGraphX-46524e8/test/onnx/implicit_sub_bcast_test.onnx000066400000000000000000000002351510465702400243030ustar00rootroot00000000000000implicit_sub_bcast_test:|  0 1out"Subimplicit_sub_bcast_testZ 0      Z 1    b out      B ROCm-AMDMIGraphX-46524e8/test/onnx/include/000077500000000000000000000000001510465702400201245ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/include/onnx_test.hpp000066400000000000000000000072061510465702400226630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_ONNX_ONNX_TEST_HPP #define MIGRAPHX_GUARD_TEST_ONNX_ONNX_TEST_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include inline static std::string read_weight_files(const std::unordered_map& onnx_files) { static migraphx::tmp_dir td{"weights"}; for(const auto& i : onnx_files) { if(not migraphx::ends_with(std::string{i.first}, "weight")) continue; migraphx::fs::path full_path = td.path / i.first; migraphx::fs::path parent_path = full_path.parent_path(); migraphx::fs::create_directories(parent_path); migraphx::write_buffer(full_path, i.second.data(), i.second.size()); } return td.path.string(); } inline migraphx::program read_onnx(const std::string& name, migraphx::onnx_options options = migraphx::onnx_options{}) { static auto onnx_files{::onnx_files()}; static std::string external_data_path = read_weight_files(onnx_files); options.external_data_path = external_data_path; if(onnx_files.find(name) == onnx_files.end()) { std::cerr << "ONNX model file: " << name << " not found, aborting the test." << std::endl; std::abort(); } auto prog = migraphx::parse_onnx_buffer(std::string{onnx_files.at(name)}, options); return prog; } inline migraphx::program optimize_onnx(const std::string& name, bool run_passes = false) { migraphx::onnx_options options; options.skip_unknown_operators = true; auto prog = read_onnx(name, options); auto* mm = prog.get_main_module(); if(run_passes) migraphx::run_passes(*mm, {migraphx::rewrite_quantization{}, migraphx::dead_code_elimination{}}); // remove the last identity instruction auto last_ins = std::prev(mm->end()); if(last_ins->name() == "@return") { mm->remove_instruction(last_ins); } return prog; } #endif ROCm-AMDMIGraphX-46524e8/test/onnx/include/onnx_test_utils.hpp000066400000000000000000000761001510465702400241020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_ONNX_ONNX_TEST_UTILS_HPP #define MIGRAPHX_GUARD_TEST_ONNX_ONNX_TEST_UTILS_HPP #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_CK_WORKAROUNDS); inline migraphx::program make_attention_program(const uint64_t batch, const uint64_t sequence_length, const uint64_t num_heads, const uint64_t embedding_size, bool bias_arg = false, bool key_pad_mask = false, const int64_t mask_value = -10000, // Default based on OnnxRT spec const float scale_value = std::numeric_limits::quiet_NaN(), const migraphx::shape::type_t dtype = migraphx::shape::float_type) { // Also known as "head size" in literature uint64_t query_size = embedding_size / num_heads; // Assumes K=Q=V sizes for now (some cases V can be different) uint64_t weight_size = 3 * embedding_size; migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter( "input", migraphx::shape{dtype, {batch, sequence_length, embedding_size}}); auto weights = mm->add_parameter("weights", migraphx::shape{dtype, {embedding_size, weight_size}}); auto bias = input; if(bias_arg) { bias = mm->add_parameter("bias", migraphx::shape{dtype, {3 * embedding_size}}); } // Masking depeends on what type of masked used. Currently have key_pad raw masking here // Others down the line can be either left/right padded, or 3d masking (masking per batch) auto mask = input; if(key_pad_mask) { mask = mm->add_parameter( "mask_index", migraphx::shape{migraphx::shape::int32_type, {batch, sequence_length}}); } // Input Projection auto unsq_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), weights); auto bc_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch, embedding_size, weight_size}}}), unsq_weights); auto pre_qkv = mm->add_instruction(migraphx::make_op("dot"), input, bc_weights); auto qkv_biased = pre_qkv; if(bias_arg) { auto bc_bias = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch, sequence_length, weight_size}}}), bias); qkv_biased = mm->add_instruction(migraphx::make_op("add"), pre_qkv, bc_bias); } // Extract out QKV matrcies after input projection add in head dimension auto q = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {embedding_size}}}), qkv_biased); auto k = mm->add_instruction( migraphx::make_op( "slice", {{"axes", {2}}, {"starts", {embedding_size}}, {"ends", {embedding_size * 2}}}), qkv_biased); auto v = mm->add_instruction( migraphx::make_op( "slice", {{"axes", {2}}, {"starts", {embedding_size * 2}}, {"ends", {embedding_size * 3}}}), qkv_biased); auto attention_mask = input; if(key_pad_mask) { auto zero = mm->add_literal(migraphx::literal(migraphx::shape{dtype, {1}}, {0})); auto mask_lit = mm->add_literal(migraphx::literal(migraphx::shape{dtype, {1}}, {mask_value})); auto bc_pass = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch, num_heads, sequence_length, sequence_length}}}), zero); auto bc_mask = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch, num_heads, sequence_length, sequence_length}}}), mask_lit); // For raw masks we just need to mask out key value padding thus the 3d mask isn't needed // here. auto raw_mask = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {batch, 1, 1, sequence_length}}}), mask); raw_mask = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch, num_heads, sequence_length, sequence_length}}}), raw_mask); raw_mask = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {batch, num_heads, sequence_length, sequence_length}}}), raw_mask); // Reuse "0" broadcasted converted to int32 to check if input mask is greater than 0 for // where condition auto in_pass = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int32_type}}), bc_pass); auto in_bool = mm->add_instruction(migraphx::make_op("equal"), raw_mask, in_pass); // Need this for mlir to allow us to use "Where" in_bool = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), in_bool); attention_mask = mm->add_instruction(migraphx::make_op("where"), in_bool, bc_mask, bc_pass); } migraphx::instruction_ref scale; if(not std::isnan(scale_value)) { // No Need for sqrt now scale = mm->add_literal(migraphx::literal(migraphx::shape{dtype, {1}, {0}}, {scale_value})); } else { auto sl_literal = mm->add_literal(migraphx::literal(migraphx::shape{dtype, {1}, {0}}, {query_size})); scale = mm->add_instruction(migraphx::make_op("sqrt"), sl_literal); scale = mm->add_instruction(migraphx::make_op("recip"), scale); } q = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {batch, sequence_length, num_heads, query_size}}}), q); k = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {batch, sequence_length, num_heads, query_size}}}), k); v = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {batch, sequence_length, num_heads, query_size}}}), v); // Get this into (batch, head, sequence_length, query_size) auto q_rsh = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), q); auto k_rsh = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), k); auto v_rsh = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), v); // Block for scale dot attention auto k_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), k_rsh); auto qk = mm->add_instruction(migraphx::make_op("dot"), q_rsh, k_trans); // Apply mask before scale and softmax if(key_pad_mask) { qk = mm->add_instruction(migraphx::make_op("add"), qk, attention_mask); } // Scale before softmax auto bc_scale = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch, num_heads, sequence_length, sequence_length}}}), scale); auto qk_scaled = mm->add_instruction(migraphx::make_op("mul"), qk, bc_scale); auto smax_score = mm->add_instruction(migraphx::make_op("softmax", {{"axis", 3}}), qk_scaled); auto score = mm->add_instruction(migraphx::make_op("dot"), smax_score, v_rsh); // Get back into final shape of batch, sequence_length, embedding_size score = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), score); mm->add_instruction( migraphx::make_op("reshape", {{"dims", {batch, sequence_length, embedding_size}}}), score); return p; } inline void add_celu_instruction(migraphx::module* mm, const migraphx::shape& s, float alpha) { auto x = mm->add_parameter("x", s); const auto& input_lens = s.lens(); const auto& input_type = s.type(); auto zero_lit = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0.}})); auto one_lit = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1.}})); auto alpha_lit = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {alpha}})); auto linear_part = mm->add_instruction(migraphx::make_op("max"), zero_lit, x); auto divi = mm->add_instruction(migraphx::make_op("div"), x, alpha_lit); auto expo = mm->add_instruction(migraphx::make_op("exp"), divi); auto sub = mm->add_instruction(migraphx::make_op("sub"), expo, one_lit); auto mul = mm->add_instruction(migraphx::make_op("mul"), alpha_lit, sub); auto exp_part = mm->add_instruction(migraphx::make_op("min"), zero_lit, mul); mm->add_instruction(migraphx::make_op("add"), linear_part, exp_part); } inline std::vector make_r_eyelike(size_t num_rows, size_t num_cols, size_t k) { std::vector eyelike_mat(num_rows * num_cols, 0); for(size_t i = 0; i < num_rows; ++i) { if(i + k < num_cols) eyelike_mat[(num_cols + 1) * i + k] = 1.; } return eyelike_mat; } inline migraphx::program make_dequantizelinear_axis_prog() { migraphx::program p; std::vector input_lens{1, 1, 5, 1}; int axis = 2; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::int8_type, input_lens}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {5}}); auto l2 = mm->add_parameter("2", {migraphx::shape::int8_type, {5}}); auto l1_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", input_lens}}), l1); auto l2_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", input_lens}}), l2); l2_bcast = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l2_bcast); l0 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l0); auto sub = mm->add_instruction(migraphx::make_op("sub"), l0, l2_bcast); mm->add_instruction(migraphx::make_op("mul"), sub, l1_bcast); return p; } inline migraphx::program create_external_data_prog() { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s(migraphx::shape::float_type, {1, 1, 224, 224}); migraphx::shape s2(migraphx::shape::float_type, {10, 1, 11, 11}); std::vector weight_data(1210, 1); std::vector bias_data(10, 1); auto bias = mm->add_literal(migraphx::literal({migraphx::shape::float_type, {10}}, bias_data)); auto weights = mm->add_literal(migraphx::literal(s2, weight_data)); auto param = mm->add_parameter("input", s); auto conv = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}}), param, weights); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 10, 214, 214}}}), bias); mm->add_instruction(migraphx::make_op("add"), conv, bias_bcast); return p; } inline migraphx::program make_group_norm( const std::vector& input_dims, const std::vector& scale_dims, const std::vector& bias_dims, const std::vector& reshape_dims, const std::vector& reduce_axes, const float eps_value = 1e-5f, const migraphx::shape::type_t dtype = migraphx::shape::float_type, const std::pair& param1 = {"scale", migraphx::shape::float_type}, const std::pair& param2 = {"bias", migraphx::shape::float_type}) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {dtype, input_dims}); auto scale = mm->add_parameter(param1.first, {param1.second, scale_dims}); auto bias = mm->add_parameter(param2.first, {param2.second, bias_dims}); auto x_dims = x->get_shape().lens(); auto eps = mm->add_literal(migraphx::literal{dtype, {eps_value}}); if(scale->get_shape().type() != dtype) scale = mm->add_instruction(migraphx::make_op("convert", {{"target_type", dtype}}), scale); if(bias->get_shape().type() != dtype) bias = mm->add_instruction(migraphx::make_op("convert", {{"target_type", dtype}}), bias); auto x_reshaped = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_dims}}), x); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_reshaped); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x_reshaped, mean}); auto x_sqdiff_mean = add_common_op(*mm, migraphx::make_op("sqdiff"), {x_reshaped, mean}); auto var = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_sqdiff_mean); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), {var_eps}); auto result = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, rsqrt}); auto result_reshaped = mm->add_instruction(migraphx::make_op("reshape", {{"dims", x_dims}}), result); auto scale_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), scale); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), bias); auto scaled = mm->add_instruction(migraphx::make_op("mul"), {result_reshaped, scale_bcast}); mm->add_instruction(migraphx::make_op("add"), {scaled, bias_bcast}); return p; } inline migraphx::program make_layer_norm(const std::vector& input_shape, const std::vector& scale_bias_shape, const std::vector& reduce_axes, size_t skipped_axis, bool skip_bias = false, const bool stash_type = true, const float eps_value = 1e-5f, const migraphx::shape::type_t dtype = migraphx::shape::float_type) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {dtype, input_shape}); auto scale = mm->add_parameter("scale", {dtype, scale_bias_shape}); migraphx::instruction_ref bias; if(not skip_bias) { bias = mm->add_parameter("bias", {dtype, scale_bias_shape}); } if(stash_type and dtype != migraphx::shape::float_type) { x = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); } auto eps = mm->add_literal(migraphx::literal{dtype, {eps_value}}); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto x_sqdiff_mean = add_common_op(*mm, migraphx::make_op("sqdiff"), {x, mean}); auto var = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_sqdiff_mean); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), {var_eps}); auto result = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, rsqrt}); if(stash_type and dtype != migraphx::shape::float_type) { result = mm->add_instruction(migraphx::make_op("convert", {{"target_type", dtype}}), result); } migraphx::instruction_ref scale_bcast = scale; migraphx::instruction_ref bias_bcast = bias; if(skipped_axis > 0) { if(scale_bias_shape.size() == 1) { scale_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", skipped_axis}, {"out_lens", input_shape}}), scale); } if(not skip_bias) { if(scale_bias_shape.size() == 1) { bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", skipped_axis}, {"out_lens", input_shape}}), bias); } } } auto scaled = add_common_op(*mm, migraphx::make_op("mul"), {result, scale_bcast}); if(not skip_bias) { add_common_op(*mm, migraphx::make_op("add"), {scaled, bias_bcast}); } return p; } inline migraphx::program make_skip_layer_norm(const std::vector& input_dims, const std::vector& skip_dims, const std::vector& gamma_dims, const std::vector& beta_dims, const std::vector& bias_dims, const int axes, const float eps_value = 1e-5f, const migraphx::shape::type_t dtype = migraphx::shape::half_type) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {dtype, input_dims}); auto skip = mm->add_parameter("skip", {dtype, skip_dims}); auto scale = mm->add_parameter("gamma", {dtype, gamma_dims}); migraphx::instruction_ref beta; migraphx::instruction_ref bias; if(not beta_dims.empty()) { beta = mm->add_parameter("beta", {dtype, beta_dims}); } if(not bias_dims.empty()) { bias = mm->add_parameter("bias", {dtype, bias_dims}); } x = add_common_op(*mm, migraphx::make_op("add"), {x, skip}); if(not bias_dims.empty()) x = add_common_op(*mm, migraphx::make_op("add"), {x, bias}); auto eps = mm->add_literal(migraphx::literal{migraphx::shape{dtype}, {eps_value}}); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {axes}}}), x); auto x_sqdiff_mean = add_common_op(*mm, migraphx::make_op("sqdiff"), {x, mean}); auto var = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {axes}}}), x_sqdiff_mean); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), {var_eps}); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto result = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, rsqrt}); result = add_common_op(*mm, migraphx::make_op("mul"), {result, scale}); if(not beta_dims.empty()) { result = add_common_op(*mm, migraphx::make_op("add"), {result, beta}); } return p; } inline migraphx::program make_simplified_layer_norm(const std::vector& input_shape, const std::vector& skip_shape, const std::vector& scale_shape, const int axis, const float eps_value = 1e-5f, const migraphx::shape::type_t dtype = migraphx::shape::half_type) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {dtype, input_shape}); migraphx::instruction_ref skip; migraphx::instruction_ref scale; if(skip_shape.empty()) { scale = mm->add_parameter("scale", {dtype, scale_shape}); } else { skip = mm->add_parameter("skip", {dtype, skip_shape}); scale = mm->add_parameter("gamma", {dtype, scale_shape}); x = add_common_op(*mm, migraphx::make_op("add"), {x, skip}); } auto eps = mm->add_literal(migraphx::literal{dtype, {eps_value}}); auto float_x = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); auto x_sq = add_common_op(*mm, migraphx::make_op("mul"), {float_x, float_x}); auto norm_axis = axis < 0 ? axis + x->get_shape().lens().size() : axis; auto rms = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {norm_axis}}}), x_sq); rms = mm->add_instruction(migraphx::make_op("convert", {{"target_type", dtype}}), rms); rms = add_common_op(*mm, migraphx::make_op("add"), {rms, eps}); auto rrms = mm->add_instruction(migraphx::make_op("rsqrt"), {rms}); auto result = add_common_op(*mm, migraphx::make_op("mul"), {x, rrms}); result = add_common_op(*mm, migraphx::make_op("mul"), {result, scale}); return p; } inline void mvn_n_rank_test(std::vector axes, std::vector input_shape, const migraphx::program& prog) { using migraphx::make_op; migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("data", {migraphx::shape::float_type, std::move(input_shape)}); auto data_mean = mm->add_instruction(make_op("reduce_mean", {{"axes", axes}}), data); auto data_mean_squared = add_common_op(*mm, make_op("mul"), {data_mean, data_mean}); auto data_squared = add_common_op(*mm, make_op("mul"), {data, data}); auto data_squared_mean = mm->add_instruction(make_op("reduce_mean", {{"axes", axes}}), data_squared); auto mean_sub = add_common_op(*mm, make_op("sub"), {data_squared_mean, data_mean_squared}); auto std = add_common_op(*mm, make_op("sqrt"), {mean_sub}); auto dividend = add_common_op(*mm, make_op("sub"), {data, data_mean}); auto epsilon = mm->add_literal({migraphx::shape::float_type, {1e-9}}); auto divisor = add_common_op(*mm, make_op("add"), {std, epsilon}); add_common_op(*mm, make_op("div"), {dividend, divisor}); EXPECT(p == prog); } inline migraphx::instruction_ref insert_quantizelinear_clip(migraphx::module& m, const migraphx::instruction_ref ins, const migraphx::instruction_ref round, const migraphx::shape s, const int64_t min_quant, const int64_t max_quant) { migraphx::instruction_ref min_arg; migraphx::instruction_ref max_arg; if(migraphx::enabled(MIGRAPHX_ENABLE_CK_WORKAROUNDS{})) { std::vector min_data(s.elements(), min_quant); std::vector max_data(s.elements(), max_quant); min_arg = m.add_literal(migraphx::literal(s, min_data)); max_arg = m.add_literal(migraphx::literal(s, max_data)); } else { min_arg = m.add_literal(migraphx::literal{migraphx::shape{s.type()}, {min_quant}}); max_arg = m.add_literal(migraphx::literal{migraphx::shape{s.type()}, {max_quant}}); } return migraphx::insert_common_op(m, ins, migraphx::make_op("clip"), {round, min_arg, max_arg}); } inline migraphx::program make_quantizelinear_axis_prog() { migraphx::program p; std::vector input_lens{1, 1, 5, 1}; int axis = 2; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, input_lens}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {5}}); auto l2 = mm->add_parameter("2", {migraphx::shape::int8_type, {5}}); auto l1_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", input_lens}}), l1); auto div = mm->add_instruction(migraphx::make_op("div"), l0, l1_bcast); auto round = mm->add_instruction(migraphx::make_op("nearbyint"), div); auto l2_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", input_lens}}), l2); l2_bcast = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l2_bcast); auto add = mm->add_instruction(migraphx::make_op("add"), round, l2_bcast); auto s = round->get_shape(); auto clip = insert_quantizelinear_clip(*mm, div, add, s, -128, 127); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::int8_type)}}), clip); return p; } /* Parsed IR equivalent of create_upsample_linear_prog() module: "main" @0 = @literal{ ... } -> float_type, {1, 1, 4, 4}, {16, 16, 4, 1} @1 = @literal{ ... } -> float_type, {2, 1, 4, 4}, {16, 16, 4, 1} @2 = @literal{ ... } -> int32_type, {4, 1, 4, 4}, {16, 16, 4, 1} X = @param:X -> float_type, {1, 1, 2, 2}, {4, 4, 2, 1} @4 = @literal{1, 1, 2, 2} -> float_type, {4}, {1} @5 = undefined -> float_type, {}, {} @6 = reshape[dims={4}](X) -> float_type, {4}, {1} @7 = gather[axis=0](@6,@2) -> float_type, {4, 1, 4, 4}, {16, 16, 4, 1} @8 = slice[axes={0},starts={0},ends={2}](@7) -> float_type, {2, 1, 4, 4}, {16, 16, 4, 1} @9 = slice[axes={0},starts={2},ends={4}](@7) -> float_type, {2, 1, 4, 4}, {16, 16, 4, 1} @10 = sub(@9,@8) -> float_type, {2, 1, 4, 4}, {16, 16, 4, 1} @11 = mul(@10,@1) -> float_type, {2, 1, 4, 4}, {16, 16, 4, 1} @12 = add(@11,@8) -> float_type, {2, 1, 4, 4}, {16, 16, 4, 1} @13 = slice[axes={0},starts={0},ends={1}](@12) -> float_type, {1, 1, 4, 4}, {16, 16, 4, 1} @14 = slice[axes={0},starts={1},ends={2}](@12) -> float_type, {1, 1, 4, 4}, {16, 16, 4, 1} @15 = sub(@14,@13) -> float_type, {1, 1, 4, 4}, {16, 16, 4, 1} @16 = mul(@15,@0) -> float_type, {1, 1, 4, 4}, {16, 16, 4, 1} @17 = add(@16,@13) -> float_type, {1, 1, 4, 4}, {16, 16, 4, 1} @18 = @return(@17) */ inline auto create_upsample_linear_prog() { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ss{migraphx::shape::float_type, {4}}; std::vector ds = {1, 1, 2, 2}; mm->add_literal(migraphx::literal(ss, ds)); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; auto x = mm->add_parameter("X", sx); migraphx::shape s_ind{migraphx::shape::int32_type, {4, 1, 4, 4}}; std::vector d_ind = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 2, 2, 3, 0, 0, 0, 1, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 2, 3, 3, 3, 0, 1, 1, 1, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3}; auto l_ind = mm->add_literal(migraphx::literal(s_ind, d_ind)); migraphx::shape s2{migraphx::shape::float_type, {2, 1, 4, 4}}; std::vector d2 = {-0.25, 0.25, 0.75, 0.25, -0.25, 0.25, 0.75, 0.25, -0.25, 0.25, 0.75, 0.25, -0.25, 0.25, 0.75, 0.25, -0.25, 0.25, 0.75, 0.25, -0.25, 0.25, 0.75, 0.25, -0.25, 0.25, 0.75, 0.25, -0.25, 0.25, 0.75, 0.25}; auto l2 = mm->add_literal(migraphx::literal(s2, d2)); migraphx::shape s1{migraphx::shape::float_type, {1, 1, 4, 4}}; std::vector d1 = {-0.25, -0.25, -0.25, -0.25, 0.25, 0.25, 0.25, 0.25, 0.75, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25, 0.25}; auto l1 = mm->add_literal(migraphx::literal(s1, d1)); mm->add_instruction(migraphx::make_op("undefined")); auto rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), x); auto data = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), rsp, l_ind); auto slc20 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {2}}}), data); auto slc21 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {4}}}), data); auto diff2 = mm->add_instruction(migraphx::make_op("sub"), slc21, slc20); auto mul2 = mm->add_instruction(migraphx::make_op("mul"), diff2, l2); auto add2 = mm->add_instruction(migraphx::make_op("add"), mul2, slc20); auto slc10 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), add2); auto slc11 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), add2); auto diff1 = mm->add_instruction(migraphx::make_op("sub"), slc11, slc10); auto mul1 = mm->add_instruction(migraphx::make_op("mul"), diff1, l1); auto add1 = mm->add_instruction(migraphx::make_op("add"), mul1, slc10); mm->add_return({add1}); return p; } // the ScatterElements op has 3 reduction modes, which map to separate reference ops inline void scatter_test_base(const std::string& reduction, int axis, const std::string& onnx_file) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto l1 = mm->add_parameter("indices", migraphx::shape{migraphx::shape::int32_type, {2, 3, 4, 5}}); auto l2 = mm->add_parameter("update", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto r = mm->add_instruction( migraphx::make_op("scatter_" + reduction, {{"axis", axis}}), l0, l1, l2); mm->add_return({r}); auto prog = read_onnx(onnx_file); EXPECT(p == prog); } #endif ROCm-AMDMIGraphX-46524e8/test/onnx/include/onnx_verify_utils.hpp000066400000000000000000000071321510465702400244260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_ONNX_ONNX_VERIFY_UTILS_HPP #define MIGRAPHX_GUARD_TEST_ONNX_ONNX_VERIFY_UTILS_HPP #include #include #include template std::vector norm_test(const std::vector& x_dims, std::vector& scale, std::vector& bias, migraphx::program p, const std::string& scale_str = std::string{"scale"}, const std::string& bias_str = std::string{"bias"}) { p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::get_type{}, x_dims}; migraphx::shape s_s{migraphx::shape::get_type{}, {scale.size()}}; migraphx::shape s_b{migraphx::shape::get_type{}, {scale.size()}}; std::vector x(s_x.elements()); std::iota(std::begin(x), std::end(x), 1); migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp[scale_str] = migraphx::argument(s_s, scale.data()); pp[bias_str] = migraphx::argument(s_b, bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); return result_vector; } template std::vector mvn_test(std::vector data_lens, migraphx::program p) { p.compile(migraphx::make_target("ref")); migraphx::shape data_shape(migraphx::shape::get_type{}, std::move(data_lens)); std::vector data(data_shape.elements()); std::iota(begin(data), end(data), 0); migraphx::parameter_map pm; pm["data"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pm).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); return result_vector; } inline std::vector gen_trilu_test(const migraphx::shape& s, const migraphx::program& p) { // input data filled with values 1 to nelements std::vector x_data(s.elements()); std::iota(x_data.begin(), x_data.end(), 1); migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, x_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); return result_vector; } #endif ROCm-AMDMIGraphX-46524e8/test/onnx/initializer_not_an_input.onnx000066400000000000000000000002451510465702400245060ustar00rootroot00000000000000initializer_not_an_input:‚  x wy"Gemminitializer_not_an_input*+" €?@@@€@ @À@à@ABwZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_bf16_test.onnx000066400000000000000000000002741510465702400241240ustar00rootroot00000000000000 instance_norm_bf16_test:š # 0 1 23"InstanceNormalizationinstance_norm_bf16_testZ 0     Z 1  Z 2  b 3     BROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_dyn_batch_bf16_test.onnx000066400000000000000000000003141510465702400261320ustar00rootroot00000000000000 !instance_norm_dyn_batch_bf16_test:  # 0 1 23"InstanceNormalization!instance_norm_dyn_batch_bf16_testZ 0    Z 1  Z 2  b 3    BROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_dyn_batch_half_test.onnx000066400000000000000000000003141510465702400263060ustar00rootroot00000000000000!instance_norm_dyn_batch_half_test:  # 0 1 23"InstanceNormalization!instance_norm_dyn_batch_half_testZ 0     Z 1   Z 2   b 3     BROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_dyn_batch_test.onnx000066400000000000000000000003021510465702400253110ustar00rootroot00000000000000instance_norm_dyn_batch_test:› # 0 1 23"InstanceNormalizationinstance_norm_dyn_batch_testZ 0    Z 1  Z 2  b 3    BROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_half_test.onnx000066400000000000000000000002741510465702400243000ustar00rootroot00000000000000instance_norm_half_test:š # 0 1 23"InstanceNormalizationinstance_norm_half_testZ 0      Z 1   Z 2   b 3      B ROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_invalid_type_test.onnx000066400000000000000000000003141510465702400260500ustar00rootroot00000000000000instance_norm_invalid_type_test:¢ # 0 1 23"InstanceNormalizationinstance_norm_invalid_type_testZ 0     Z 1  Z 2  b 3     B ROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_nonbroadcastable_test.onnx000066400000000000000000000003241510465702400266630ustar00rootroot00000000000000#instance_norm_nonbroadcastable_test:¦ # 0 1 23"InstanceNormalization#instance_norm_nonbroadcastable_testZ 0     Z 1  Z 2  b 3     B ROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_test.onnx000066400000000000000000000002621510465702400233030ustar00rootroot00000000000000instance_norm_test:• # 0 1 23"InstanceNormalizationinstance_norm_testZ 0     Z 1  Z 2  b 3     B ROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_type_mismatch_test.onnx000066400000000000000000000003161510465702400262310ustar00rootroot00000000000000 instance_norm_type_mismatch_test:£ # 0 1 23"InstanceNormalization instance_norm_type_mismatch_testZ 0     Z 1   Z 2   b 3     B ROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_val_3d_test.onnx000066400000000000000000000004661510465702400245410ustar00rootroot00000000000000instance_norm_val_3d_test:’ ? x_tensor scale_tensor bias_tensory"InstanceNormalizationinstance_norm_val_3d_test*X"@€?@@@€@ @À@à@€?@@@€@ @À@à@Bx_tensor*"€?@B scale_tensor*"€?B bias_tensorb y      B ROCm-AMDMIGraphX-46524e8/test/onnx/instance_norm_val_test.onnx000066400000000000000000000004621510465702400241470ustar00rootroot00000000000000instance_norm_val_test:‘ ? x_tensor scale_tensor bias_tensory"InstanceNormalizationinstance_norm_val_test*^"H€?@@@€@ @À@à@A€?@@@€@ @À@à@ABx_tensor*"€?@B scale_tensor*"€?B bias_tensorb y     B ROCm-AMDMIGraphX-46524e8/test/onnx/int4_const_identity_block_sz_1_qdq_test.onnx000066400000000000000000000006011510465702400274110ustar00rootroot00000000000000 'int4_const_identity_block_sz_1_qdq_test:Ï  i_xi_y_zp"Identity ) data sc_q i_y_zpq_y"QuantizeLinear + q_y sc_q i_y_zpdq_y"DequantizeLinear % dq_yt_y" Transpose* perm@@   t_y x2y"MatMul'int4_const_identity_block_sz_1_qdq_test**Bi_x* * €x€xBdata* *€p€h€p€`€h€p€p€hBsc_qZ x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/int4_const_identity_block_sz_2_qdq_test.onnx000066400000000000000000000005671510465702400274250ustar00rootroot00000000000000 'int4_const_identity_block_sz_2_qdq_test:Å  i_xi_y_zp"Identity ) data sc_q i_y_zpq_y"QuantizeLinear + q_y sc_q i_y_zpdq_y"DequantizeLinear % dq_yt_y" Transpose* perm@@   t_y x2y"MatMul'int4_const_identity_block_sz_2_qdq_test**Bi_x* * €x€xBdata* *€p€`€p€hBsc_qZ x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/int4_const_identity_qdq_test.onnx000066400000000000000000000005471510465702400253140ustar00rootroot00000000000000 int4_const_identity_qdq_test:À  i_xi_y_zp"Identity ) data sc_q i_y_zpq_y"QuantizeLinear + q_y sc_q i_y_zpdq_y"DequantizeLinear % dq_yt_y" Transpose* perm@@   t_y x2y"MatMulint4_const_identity_qdq_test* *Bi_x*" *€x€x€x€xBdata* *€x€p€x€hBsc_qZ x2    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/isinf_bf16_test.onnx000066400000000000000000000001471510465702400223740ustar00rootroot00000000000000 isinf_bf16_test:N  t1t2"IsInfisinf_bf16_testZ t1   b t2    BROCm-AMDMIGraphX-46524e8/test/onnx/isinf_double_pos_test.onnx000066400000000000000000000002441510465702400237670ustar00rootroot00000000000000 isinf_double_pos_test:„ ? t1t2"IsInf* detect_negative@ * detect_positive@ isinf_double_pos_testZ t1    b t2    BROCm-AMDMIGraphX-46524e8/test/onnx/isinf_half_neg_test.onnx000066400000000000000000000002401510465702400233730ustar00rootroot00000000000000 isinf_half_neg_test:‚ ? t1t2"IsInf* detect_negative@ * detect_positive@ isinf_half_neg_testZ t1    b t2    BROCm-AMDMIGraphX-46524e8/test/onnx/isinf_half_pos_test.onnx000066400000000000000000000002401510465702400234230ustar00rootroot00000000000000 isinf_half_pos_test:‚ ? t1t2"IsInf* detect_negative@ * detect_positive@ isinf_half_pos_testZ t1    b t2    BROCm-AMDMIGraphX-46524e8/test/onnx/isinf_half_test.onnx000066400000000000000000000001471510465702400225500ustar00rootroot00000000000000 isinf_half_test:N  t1t2"IsInfisinf_half_testZ t1    b t2    BROCm-AMDMIGraphX-46524e8/test/onnx/isinf_neg_test.onnx000066400000000000000000000002251510465702400224040ustar00rootroot00000000000000 isinf_neg_test:} ? t1t2"IsInf* detect_negative@ * detect_positive@ isinf_neg_testZ t1   b t2    BROCm-AMDMIGraphX-46524e8/test/onnx/isinf_no_detect_test.onnx000066400000000000000000000002421510465702400235760ustar00rootroot00000000000000 isinf_no_detect_test:ƒ ? t1t2"IsInf* detect_negative@ * detect_positive@ isinf_no_detect_testZ t1   b t2    BROCm-AMDMIGraphX-46524e8/test/onnx/isnan_bf16_test.onnx000066400000000000000000000001471510465702400223740ustar00rootroot00000000000000 isnan_bf16_test:N  t1t2"IsNaNisnan_bf16_testZ t1   b t2   BROCm-AMDMIGraphX-46524e8/test/onnx/isnan_float_test.onnx000066400000000000000000000001511510465702400227360ustar00rootroot00000000000000isnan_float_test:O  t1t2"IsNaNisnan_float_testZ t1   b t2   B ROCm-AMDMIGraphX-46524e8/test/onnx/isnan_half_test.onnx000066400000000000000000000001471510465702400225500ustar00rootroot00000000000000isnan_half_test:N  t1t2"IsNaNisnan_half_testZ t1    b t2    B ROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_2d_axis_minus_one_test.onnx000066400000000000000000000003411510465702400262760ustar00rootroot00000000000000 !layer_norm_2d_axis_minus_one_test:µ = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ !layer_norm_2d_axis_minus_one_testZ x   Z scale  Z bias  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_2d_axis_one_test.onnx000066400000000000000000000003141510465702400250630ustar00rootroot00000000000000 layer_norm_2d_axis_one_test:¦ 4 x scale biasy"LayerNormalization* axis layer_norm_2d_axis_one_testZ x   Z scale  Z bias  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_2d_axis_zero_test.onnx000066400000000000000000000003261510465702400252640ustar00rootroot00000000000000 layer_norm_2d_axis_zero_test:¯ 4 x scale biasy"LayerNormalization* axis layer_norm_2d_axis_zero_testZ x   Z scale   Z bias   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_bf16_test.onnx000066400000000000000000000003251510465702400240170ustar00rootroot00000000000000 layer_norm_3d_bf16_test:³ = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_3d_bf16_testZ x    Z scale  Z bias  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_half_stash_off_epsilon_test.onnx000066400000000000000000000004371510465702400277640ustar00rootroot00000000000000 )layer_norm_3d_half_stash_off_epsilon_test:ë c x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ * stash_type * epsilon·Ñ8 )layer_norm_3d_half_stash_off_epsilon_testZ x     Z scale   Z bias   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_half_stash_off_test.onnx000066400000000000000000000003741510465702400262330ustar00rootroot00000000000000 !layer_norm_3d_half_stash_off_test:Ð P x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ * stash_type !layer_norm_3d_half_stash_off_testZ x     Z scale   Z bias   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_half_test.onnx000066400000000000000000000003251510465702400241730ustar00rootroot00000000000000layer_norm_3d_half_test:³ = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_3d_half_testZ x     Z scale   Z bias   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_invalid_bias_test.onnx000066400000000000000000000003451510465702400257070ustar00rootroot00000000000000 layer_norm_3d_invalid_bias_test:» = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_3d_invalid_bias_testZ x    Z scale  Z bias  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_invalid_int8_test.onnx000066400000000000000000000003451510465702400256530ustar00rootroot00000000000000 layer_norm_3d_invalid_int8_test:» = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_3d_invalid_int8_testZ x    Z scale  Z bias  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_invalid_scale_test.onnx000066400000000000000000000003471510465702400260620ustar00rootroot00000000000000  layer_norm_3d_invalid_scale_test:¼ = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ  layer_norm_3d_invalid_scale_testZ x    Z scale  Z bias  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_scale_bias_test.onnx000066400000000000000000000003611510465702400253460ustar00rootroot00000000000000 layer_norm_3d_scale_bias_test:É = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_3d_scale_bias_testZ x    Z scale    Z bias    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_3d_test.onnx000066400000000000000000000003131510465702400231760ustar00rootroot00000000000000 layer_norm_3d_test:® = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_3d_testZ x    Z scale  Z bias  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_4d_bf16_test.onnx000066400000000000000000000003351510465702400240210ustar00rootroot00000000000000 layer_norm_4d_bf16_test:» = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_4d_bf16_testZ x     Z scale  Z bias  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_4d_half_test.onnx000066400000000000000000000003351510465702400241750ustar00rootroot00000000000000layer_norm_4d_half_test:» = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_4d_half_testZ x      Z scale   Z bias   b y      BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_4d_test.onnx000066400000000000000000000003231510465702400232000ustar00rootroot00000000000000 layer_norm_4d_test:¶ = x scale biasy"LayerNormalization* axisÿÿÿÿÿÿÿÿÿ layer_norm_4d_testZ x     Z scale  Z bias  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_invalid_axis_error_test.onnx000066400000000000000000000003331510465702400265550ustar00rootroot00000000000000 "layer_norm_invalid_axis_error_test:® 5 x scale biasy"LayerNormalization* axisè "layer_norm_invalid_axis_error_testZ x    Z scale Z bias b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_invalid_input_count_error_test.onnx000066400000000000000000000002441510465702400301610ustar00rootroot00000000000000 )layer_norm_invalid_input_count_error_test:q  xy"LayerNormalization)layer_norm_invalid_input_count_error_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_invalid_minus_axis_error_test.onnx000066400000000000000000000004071510465702400277720ustar00rootroot00000000000000 (layer_norm_invalid_minus_axis_error_test:Ô = x scale biasy"LayerNormalization* axis˜øÿÿÿÿÿÿÿ (layer_norm_invalid_minus_axis_error_testZ x    Z scale    Z bias    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_invalid_shape_error_test.onnx000066400000000000000000000003241510465702400267110ustar00rootroot00000000000000 #layer_norm_invalid_shape_error_test:¦ 4 x scale biasy"LayerNormalization* axis #layer_norm_invalid_shape_error_testZ x  Z scale  Z bias  b y  BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_small_eps_bf16_test.onnx000066400000000000000000000002761510465702400254750ustar00rootroot00000000000000 layer_norm_small_eps_bf16_test:• 4 x scaley"LayerNormalization* epsilon•¿Ö3 layer_norm_small_eps_bf16_testZ x   Z scale  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_small_eps_half_test.onnx000066400000000000000000000002761510465702400256510ustar00rootroot00000000000000layer_norm_small_eps_half_test:• 4 x scaley"LayerNormalization* epsilon̼Œ+ layer_norm_small_eps_half_testZ x    Z scale   b y    BROCm-AMDMIGraphX-46524e8/test/onnx/layer_norm_without_bias_test.onnx000066400000000000000000000002471510465702400253770ustar00rootroot00000000000000 layer_norm_without_bias_test:€ ! x scaley"LayerNormalizationlayer_norm_without_bias_testZ x   Z scale  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/leaky_relu_test.onnx000066400000000000000000000001561510465702400226020ustar00rootroot00000000000000leaky_relu-example:R " 01" LeakyRelu* alpha ×#<  test-modelZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/less_bool_test.onnx000066400000000000000000000002321510465702400224220ustar00rootroot00000000000000less_bool_test:  x1bx1"Cast* to    bx1 x2y"Lessless_bool_testZ x1   Z x2    b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/less_test.onnx000066400000000000000000000002021510465702400214040ustar00rootroot00000000000000 less_test:o  x1 x2y"Less less_test*$"€?@@@€@ @À@Bx1Z x2   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/lessorequal_test.onnx000066400000000000000000000001731510465702400230040ustar00rootroot00000000000000lessorequal_test:a  x1 x2y" LessOrEquallessorequal_testZ x1  Z x2  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/log_test.onnx000066400000000000000000000001161510465702400212230ustar00rootroot00000000000000 log-example:9 xy"Logtest_logZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/logical_and_bcast_test.onnx000066400000000000000000000002271510465702400240550ustar00rootroot00000000000000logical_and_bcast_test:w  0 12"Andlogical_and_bcast_testZ 0      Z 1    b 2      B ROCm-AMDMIGraphX-46524e8/test/onnx/logical_or_test.onnx000066400000000000000000000002201510465702400225500ustar00rootroot00000000000000logical_or_test:w 0 12"Orlogical_or_testZ 0      Z 1      b 2      B ROCm-AMDMIGraphX-46524e8/test/onnx/logical_xor_bcast_test.onnx000066400000000000000000000002271510465702400241230ustar00rootroot00000000000000logical_xor_bcast_test:w  0 12"Xorlogical_xor_bcast_testZ 0      Z 1    b 2      B ROCm-AMDMIGraphX-46524e8/test/onnx/logsoftmax_nonstd_input_test.onnx000066400000000000000000000003271510465702400254350ustar00rootroot00000000000000logsoftmax_nonstd_input_test:° < 01"Slice* axes@@ * ends@@ * starts@@  ( 12" LogSoftmax* axisÿÿÿÿÿÿÿÿÿ logsoftmax_nonstd_input_testZ 0    b 2   BROCm-AMDMIGraphX-46524e8/test/onnx/logsoftmax_test.onnx000066400000000000000000000002101510465702400226200ustar00rootroot00000000000000logsoftmax-example:l  xy" LogSoftmax* axis test_logsoftmaxZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/loop_default_test.onnx000066400000000000000000000010161510465702400231170ustar00rootroot00000000000000loop_default_test:ò † bb_loop my_local_loopuser_defined_vals_loop"Loop*Ç body2»  a b_inmy_local"Add  a b_in a_sub_b_in"Sub + my_local a_sub_b_in keep_going"Greater 0 a_sub_b_in a_sub_b_inuser_defined_vals"AddbodyZ iteration_num Z keep_going_inp  Z b_in b keep_going  b a_sub_b_in b my_local b user_defined_vals  loop_default_testZ a Z b b b_loop b( user_defined_vals_loop   B ROCm-AMDMIGraphX-46524e8/test/onnx/loop_test.onnx000066400000000000000000000012001510465702400214060ustar00rootroot00000000000000 loop_test:ì ¿ max_trip_count keep_going_cond bb_loop my_local_loopuser_defined_vals_loop"Loop*ã body2×  a b_inmy_local"Add  a b_in a_sub_b_in"Sub + my_local a_sub_b_in keep_going"Greater 0 a_sub_b_in a_sub_b_inuser_defined_vals"AddbodyZ iteration_num  Z keep_going_inp   Z b_in  b keep_going   b a_sub_b_in  b my_local  b user_defined_vals    loop_testZ max_trip_count  Z keep_going_cond   Z a  Z b  b b_loop  b( user_defined_vals_loop   B ROCm-AMDMIGraphX-46524e8/test/onnx/loop_test_implicit_tripcnt.onnx000066400000000000000000000012351510465702400250530ustar00rootroot00000000000000 loop_test_implicit_tripcnt:ø ¿ max_trip_count keep_going_cond bb_loop my_local_loopuser_defined_vals_loop"Loop*ã body2×  a b_inmy_local"Add  a b_in a_sub_b_in"Sub + my_local a_sub_b_in keep_going"Greater 0 a_sub_b_in a_sub_b_inuser_defined_vals"AddbodyZ iteration_num  Z keep_going_inp   Z b_in  b keep_going   b a_sub_b_in  b my_local  b user_defined_vals   loop_test_implicit_tripcnt*:Bmax_trip_countZ keep_going_cond   Z a  Z b  b b_loop  b( user_defined_vals_loop   BROCm-AMDMIGraphX-46524e8/test/onnx/lpnormalization_axis_error_test.onnx000066400000000000000000000002321510465702400261200ustar00rootroot00000000000000lpnormalization_axis_error_test:q $ xy"LpNormalization* axis lpnormalization_axis_error_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/lpnormalization_default_test.onnx000066400000000000000000000002241510465702400253700ustar00rootroot00000000000000lpnormalization_default_test:n $ xy"LpNormalization* axis lpnormalization_default_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/lpnormalization_l1_test.onnx000066400000000000000000000002071510465702400242610ustar00rootroot00000000000000lpnormalization_l1_test:f ! xy"LpNormalization* p lpnormalization_l1_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/lpnormalization_l2_test.onnx000066400000000000000000000002071510465702400242620ustar00rootroot00000000000000lpnormalization_l2_test:f ! xy"LpNormalization* p lpnormalization_l2_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/lpnormalization_p_error_test.onnx000066400000000000000000000002211510465702400254110ustar00rootroot00000000000000lpnormalization_p_error_test:k ! xy"LpNormalization* p lpnormalization_p_error_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/lppool_l1_test.onnx000066400000000000000000000002111510465702400223370ustar00rootroot00000000000000lppool_l1_test:q - xy"LpPool* kernel_shape@ * p lppool_l1_testZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/lppool_l2_test.onnx000066400000000000000000000002111510465702400223400ustar00rootroot00000000000000lppool_l2_test:q - xy"LpPool* kernel_shape@ * p lppool_l2_testZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/lrn_test.onnx000066400000000000000000000002451510465702400212400ustar00rootroot00000000000000 lrn-example: I 01"LRN* alpha·Ñ8 * beta@? * bias€? * size test-lrnZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/lstm_bi_layout_cell_test.onnx000066400000000000000000000010011510465702400244610ustar00rootroot00000000000000 lstm_bi_layout_cell_test:Þ Ú seq w r bias seq_len h0 c0 pphcellout"LSTM*: activationsJsigmoidJtanhJtanhJsigmoidJtanhJtanh * clip * direction" bidirectional * hidden_size * input_forget * layout lstm_bi_layout_cell_testZ seq     Z w   P  Z r   P Z bias    Z seq_len  Z h0    Z c0    Z pph    q k v"MultiHeadAttention* num_heads : com.microsoftmha_invalid_cross_key_testZ q    Z k     Z v     BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_cross_value_test.onnx000066400000000000000000000003301510465702400253200ustar00rootroot00000000000000 mha_invalid_cross_value_test:± > q k v"MultiHeadAttention* num_heads : com.microsoftmha_invalid_cross_value_testZ q    Z k     Z v     BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_input_test.onnx000066400000000000000000000001571510465702400241410ustar00rootroot00000000000000 mha_invalid_input_test:O 5"MultiHeadAttention* num_heads : com.microsoftmha_invalid_input_testBROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_key_missing_test.onnx000066400000000000000000000002271510465702400253210ustar00rootroot00000000000000 mha_invalid_key_missing_test:q 8 q"MultiHeadAttention* num_heads : com.microsoftmha_invalid_key_missing_testZ q    BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_key_ndim_test.onnx000066400000000000000000000002521510465702400245750ustar00rootroot00000000000000 mha_invalid_key_ndim_test:† ; q k"MultiHeadAttention* num_heads : com.microsoftmha_invalid_key_ndim_testZ q    Z k   BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_key_test.onnx000066400000000000000000000003001510465702400235600ustar00rootroot00000000000000 mha_invalid_key_test:¡ > q k v"MultiHeadAttention* num_heads : com.microsoftmha_invalid_key_testZ q    Z k    Z v    BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_kv_test.onnx000066400000000000000000000002541510465702400234200ustar00rootroot00000000000000 mha_invalid_kv_test:Ž < q kv"MultiHeadAttention* num_heads : com.microsoftmha_invalid_kv_testZ q    Z kv      BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_qkv_test.onnx000066400000000000000000000002231510465702400235750ustar00rootroot00000000000000 mha_invalid_qkv_test:u : qkv"MultiHeadAttention* num_heads : com.microsoftmha_invalid_qkv_testZ! qkv      BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_query_test.onnx000066400000000000000000000002071510465702400241430ustar00rootroot00000000000000 mha_invalid_query_test:g 8 q"MultiHeadAttention* num_heads : com.microsoftmha_invalid_query_testZ q   BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_value_missing_test.onnx000066400000000000000000000002701510465702400256430ustar00rootroot00000000000000 mha_invalid_value_missing_test: ; q k"MultiHeadAttention* num_heads : com.microsoftmha_invalid_value_missing_testZ q    Z k    BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_value_ndim_test.onnx000066400000000000000000000003221510465702400251170ustar00rootroot00000000000000 mha_invalid_value_ndim_test:¬ > q k v"MultiHeadAttention* num_heads : com.microsoftmha_invalid_value_ndim_testZ q    Z k    Z v     BROCm-AMDMIGraphX-46524e8/test/onnx/mha_invalid_value_test.onnx000066400000000000000000000003041510465702400241100ustar00rootroot00000000000000 mha_invalid_value_test:£ > q k v"MultiHeadAttention* num_heads : com.microsoftmha_invalid_value_testZ q    Z k    Z v    BROCm-AMDMIGraphX-46524e8/test/onnx/mha_kv_packed_test.onnx000066400000000000000000000003121510465702400232140ustar00rootroot00000000000000 mha_kv_packed_test:­ A q kvout"MultiHeadAttention* num_heads : com.microsoftmha_kv_packed_testZ q    Z kv      b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mha_qkv_packed_test.onnx000066400000000000000000000002621510465702400234010ustar00rootroot00000000000000 mha_qkv_packed_test:” ? qkvout"MultiHeadAttention* num_heads : com.microsoftmha_qkv_packed_testZ! qkv      b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mha_scale_test.onnx000066400000000000000000000003451510465702400223620ustar00rootroot00000000000000 mha_scale_test:Ì T q k vout"MultiHeadAttention* num_heads * scaleÍÌÌ= : com.microsoftmha_scale_testZ q    Z k    Z v    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mha_test.onnx000066400000000000000000000003101510465702400212030ustar00rootroot00000000000000 mha_test:µ C q k vout"MultiHeadAttention* num_heads : com.microsoftmha_testZ q    Z k    Z v    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/min_test.onnx000066400000000000000000000001631510465702400212270ustar00rootroot00000000000000min_test:a  0 1 23"Minmin_testZ 0  Z 1  Z 2  b 3  B ROCm-AMDMIGraphX-46524e8/test/onnx/mod_test.onnx000066400000000000000000000001671510465702400212270ustar00rootroot00000000000000mod_test:e  0 12"Modmod_testZ 0    Z 1    b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/mod_test_different_dtypes.onnx000066400000000000000000000002311510465702400246350ustar00rootroot00000000000000mod_test_different_dtypes:v  0 12"Modmod_test_different_dtypesZ 0    Z 1    b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/mod_test_fmod.onnx000066400000000000000000000002161510465702400222270ustar00rootroot00000000000000 mod_test_fmod:w  0 12"Mod* fmod  mod_test_fmodZ 0    Z 1    b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/mod_test_fmod_bf16.onnx000066400000000000000000000002301510465702400230410ustar00rootroot00000000000000 mod_test_fmod_bf16:|  0 12"Mod* fmod mod_test_fmod_bf16Z 0    Z 1    b 2    BROCm-AMDMIGraphX-46524e8/test/onnx/mod_test_fmod_different_dtypes.onnx000066400000000000000000000002611510465702400256450ustar00rootroot00000000000000mod_test_fmod_different_dtypes:ˆ  0 12"Mod* fmod mod_test_fmod_different_dtypesZ 0    Z 1    b 2    B ROCm-AMDMIGraphX-46524e8/test/onnx/mod_test_fmod_half.onnx000066400000000000000000000002301510465702400232150ustar00rootroot00000000000000mod_test_fmod_half:|  0 12"Mod* fmod mod_test_fmod_halfZ 0     Z 1     b 2     B ROCm-AMDMIGraphX-46524e8/test/onnx/mod_test_half.onnx000066400000000000000000000002011510465702400222060ustar00rootroot00000000000000 mod_test_half:j  0 12"Mod mod_test_halfZ 0     Z 1     b 2     B ROCm-AMDMIGraphX-46524e8/test/onnx/multinomial_autoseed_dyn_test.onnx000066400000000000000000000002501510465702400255360ustar00rootroot00000000000000multinomial_autoseed_dyn_test:€ 0 inputoutput" Multinomial* sample_size  multinomial_autoseed_dyn_testZ input   b output   BROCm-AMDMIGraphX-46524e8/test/onnx/multinomial_dtype_error_test.onnx000066400000000000000000000002701510465702400254130ustar00rootroot00000000000000multinomial_dtype_error_test:‘ > inputoutput" Multinomial* dtype * sample_size  multinomial_dtype_error_testZ input    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/multinomial_dyn_test.onnx000066400000000000000000000002661510465702400236540ustar00rootroot00000000000000 multinomial_dyn_test:— P inputoutput" Multinomial* dtype * sample_size  * seedff¦? multinomial_dyn_testZ input  b output  BROCm-AMDMIGraphX-46524e8/test/onnx/multinomial_generated_seed_test.onnx000066400000000000000000000002601510465702400260120ustar00rootroot00000000000000multinomial_generated_seed_test:† 0 inputoutput" Multinomial* sample_size  multinomial_generated_seed_testZ input    b output    B ROCm-AMDMIGraphX-46524e8/test/onnx/multinomial_int64_test.onnx000066400000000000000000000002741510465702400240250ustar00rootroot00000000000000 multinomial_int64_test:› N inputoutput" Multinomial* dtype * sample_size  * seed€? multinomial_int64_testZ input    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/multinomial_test.onnx000066400000000000000000000002421510465702400227740ustar00rootroot00000000000000 multinomial_test:‡ @ inputoutput" Multinomial* sample_size  * seed multinomial_testZ input    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_axes_rank_too_big_test.onnx000066400000000000000000000002551510465702400250030ustar00rootroot00000000000000 mvn_axes_rank_too_big_test:ˆ 3 dataout"MeanVarianceNormalization* axes@ mvn_axes_rank_too_big_testZ data    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_axes_rank_too_small_test.onnx000066400000000000000000000002651510465702400253530ustar00rootroot00000000000000 mvn_axes_rank_too_small_test:Ž 7 dataout"MeanVarianceNormalization* axes@@@ mvn_axes_rank_too_small_testZ data    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_default_axes_bf16_test.onnx000066400000000000000000000002501510465702400246030ustar00rootroot00000000000000 mvn_default_axes_bf16_test:ƒ & dataout"MeanVarianceNormalizationmvn_default_axes_bf16_testZ data     b out     BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_default_axes_fp16_test.onnx000066400000000000000000000002501510465702400246210ustar00rootroot00000000000000 mvn_default_axes_fp16_test:ƒ & dataout"MeanVarianceNormalizationmvn_default_axes_fp16_testZ data      b out      BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_default_axes_rank_too_big_test.onnx000066400000000000000000000003001510465702400264760ustar00rootroot00000000000000 "mvn_default_axes_rank_too_big_test:“ & dataout"MeanVarianceNormalization"mvn_default_axes_rank_too_big_testZ" data      b! out      BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_default_axes_rank_too_small_test.onnx000066400000000000000000000002641510465702400270560ustar00rootroot00000000000000 $mvn_default_axes_rank_too_small_test:… & dataout"MeanVarianceNormalization$mvn_default_axes_rank_too_small_testZ data    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_default_axes_test.onnx000066400000000000000000000002351510465702400237700ustar00rootroot00000000000000 mvn_default_axes_test:~ & dataout"MeanVarianceNormalizationmvn_default_axes_testZ data     b out     BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_rank_2_bf16_test.onnx000066400000000000000000000002301510465702400233110ustar00rootroot00000000000000 mvn_rank_2_bf16_test:z 3 dataout"MeanVarianceNormalization* axes@ mvn_rank_2_bf16_testZ data   b out   BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_rank_2_fp16_test.onnx000066400000000000000000000002301510465702400233270ustar00rootroot00000000000000 mvn_rank_2_fp16_test:z 3 dataout"MeanVarianceNormalization* axes@ mvn_rank_2_fp16_testZ data    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_rank_2_test.onnx000066400000000000000000000002161510465702400224770ustar00rootroot00000000000000 mvn_rank_2_test:u 3 dataout"MeanVarianceNormalization* axes@ mvn_rank_2_testZ data   b out   BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_rank_3_bf16_test.onnx000066400000000000000000000002431510465702400233160ustar00rootroot00000000000000 mvn_rank_3_bf16_test:„ 5 dataout"MeanVarianceNormalization* axes@@ mvn_rank_3_bf16_testZ data    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_rank_3_fp16_test.onnx000066400000000000000000000002431510465702400233340ustar00rootroot00000000000000 mvn_rank_3_fp16_test:„ 5 dataout"MeanVarianceNormalization* axes@@ mvn_rank_3_fp16_testZ data     b out     BROCm-AMDMIGraphX-46524e8/test/onnx/mvn_rank_3_test.onnx000066400000000000000000000002301510465702400224740ustar00rootroot00000000000000 mvn_rank_3_test: 5 dataout"MeanVarianceNormalization* axes@@ mvn_rank_3_testZ data    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/mxfixneuron_even_test.onnx000066400000000000000000000003541510465702400240450ustar00rootroot00000000000000 mxfixneuron_even_test:Ì p inputoutput" MXFixNeuron* axis * block_size  * element_dtype"fp4_e2m1 * rounding_mode mxfixneuron_even_testZ input   @  b output   @  BROCm-AMDMIGraphX-46524e8/test/onnx/mxfixneuron_odd_test.onnx000066400000000000000000000003421510465702400236530ustar00rootroot00000000000000 mxfixneuron_odd_test:à p inputoutput" MXFixNeuron* axis * block_size  * element_dtype"fp4_e2m1 * rounding_mode mxfixneuron_odd_testZ input  G  b output  G  BROCm-AMDMIGraphX-46524e8/test/onnx/mxfixneuron_small_test.onnx000066400000000000000000000003361510465702400242200ustar00rootroot00000000000000 mxfixneuron_small_test:½ p inputoutput" MXFixNeuron* axis * block_size  * element_dtype"fp4_e2m1 * rounding_mode mxfixneuron_small_testZ input   b output   BROCm-AMDMIGraphX-46524e8/test/onnx/neg_dynamic_test.onnx000066400000000000000000000001371510465702400227220ustar00rootroot00000000000000neg_dynamic_test:E 01"Negneg_dynamic_testZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/neg_test.onnx000066400000000000000000000001231510465702400212110ustar00rootroot00000000000000neg_test:A 01"Negneg_testZ 0   b 1   B negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test.onnx000066400000000000000000000004401510465702400340640ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx >negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test:× = 0 1 23"NegativeLogLikelihoodLoss* reduction"mean >negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_testZ 0     Z 1    Z 2  b 3  Bnegativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test2.onnx000066400000000000000000000004321510465702400341470ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx ?negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test2:Ð = 0 1 23"NegativeLogLikelihoodLoss* reduction"mean ?negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test2Z 0    Z 1   Z 2  b 3  Bnegativeloglikelihoodloss_kd_mean_reduction_half_weighted_test.onnx000066400000000000000000000004401510465702400342400ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx >negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test:× = 0 1 23"NegativeLogLikelihoodLoss* reduction"mean >negativeloglikelihoodloss_kd_mean_reduction_half_weighted_testZ 0      Z 1    Z 2   b 3   Bnegativeloglikelihoodloss_kd_mean_reduction_half_weighted_test2.onnx000066400000000000000000000004321510465702400343230ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx ?negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test2:Ð = 0 1 23"NegativeLogLikelihoodLoss* reduction"mean ?negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test2Z 0     Z 1   Z 2   b 3   BROCm-AMDMIGraphX-46524e8/test/onnx/negativeloglikelihoodloss_kd_no_reduction_weighted_test.onnx000066400000000000000000000004221510465702400330210ustar00rootroot00000000000000 7negativeloglikelihoodloss_kd_no_reduction_weighted_test:Ð = 0 1 23"NegativeLogLikelihoodLoss* reduction"none 7negativeloglikelihoodloss_kd_no_reduction_weighted_testZ 0     Z 1    Z 2  b 3  BROCm-AMDMIGraphX-46524e8/test/onnx/negativeloglikelihoodloss_kd_no_reduction_weighted_test2.onnx000066400000000000000000000004201510465702400331010ustar00rootroot00000000000000 8negativeloglikelihoodloss_kd_no_reduction_weighted_test2:Í = 0 1 23"NegativeLogLikelihoodLoss* reduction"none 8negativeloglikelihoodloss_kd_no_reduction_weighted_test2Z 0    Z 1   Z 2  b 3   Bnegativeloglikelihoodloss_kd_sum_reduction_double_weighted_test.onnx000066400000000000000000000004411510465702400344650ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx ?negativeloglikelihoodloss_kd_sum_reduction_double_weighted_test:× < 0 1 23"NegativeLogLikelihoodLoss* reduction"sum ?negativeloglikelihoodloss_kd_sum_reduction_double_weighted_testZ 0      Z 1    Z 2   b 3   Bnegativeloglikelihoodloss_kd_sum_reduction_double_weighted_test2.onnx000066400000000000000000000004331510465702400345500ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx @negativeloglikelihoodloss_kd_sum_reduction_double_weighted_test2:Ð < 0 1 23"NegativeLogLikelihoodLoss* reduction"sum @negativeloglikelihoodloss_kd_sum_reduction_double_weighted_test2Z 0     Z 1   Z 2   b 3   BROCm-AMDMIGraphX-46524e8/test/onnx/nhwcconv_test.onnx000066400000000000000000000002221510465702400222650ustar00rootroot00000000000000  nhwcconv_test:{  0 12"NhwcConv nhwcconv_testZ 0     Z 1     b 2     BROCm-AMDMIGraphX-46524e8/test/onnx/nms_dynamic_batch_test.onnx000066400000000000000000000006331510465702400241100ustar00rootroot00000000000000nms_dynamic_batch_test:ú   boxes scores max_output_boxes_per_class iou_threshold score_thresholdselected_indices"NonMaxSuppression* center_point_box * use_dyn_output nms_dynamic_batch_testZ boxes   Z scores   Z( max_output_boxes_per_class  Z iou_threshold  Z score_threshold  b selected_indices  B ROCm-AMDMIGraphX-46524e8/test/onnx/nms_dynamic_boxes_test.onnx000066400000000000000000000005521510465702400241470ustar00rootroot00000000000000nms_dynamic_boxes_test:É p boxes scores max_output_boxes_per_class iou_threshold score_thresholdselected_indices"NonMaxSuppressionnms_dynamic_boxes_testZ boxes   Z scores    Z( max_output_boxes_per_class  Z iou_threshold  Z score_threshold  b selected_indices  B ROCm-AMDMIGraphX-46524e8/test/onnx/nms_dynamic_classes_test.onnx000066400000000000000000000005601510465702400244630ustar00rootroot00000000000000nms_dynamic_classes_test:Í p boxes scores max_output_boxes_per_class iou_threshold score_thresholdselected_indices"NonMaxSuppressionnms_dynamic_classes_testZ boxes    Z scores   Z( max_output_boxes_per_class  Z iou_threshold  Z score_threshold  b selected_indices  B ROCm-AMDMIGraphX-46524e8/test/onnx/nms_test.onnx000066400000000000000000000005541510465702400212450ustar00rootroot00000000000000nms_test:Ù ‰ boxes scores max_output_boxes_per_class iou_threshold score_thresholdselected_indices"NonMaxSuppression* center_point_box nms_testZ boxes    Z scores    Z( max_output_boxes_per_class  Z iou_threshold  Z score_threshold  b selected_indices  B ROCm-AMDMIGraphX-46524e8/test/onnx/nms_use_dyn_output_false_test.onnx000066400000000000000000000006241510465702400255630ustar00rootroot00000000000000nms_use_dyn_output_false_test:ì ‡ boxes scores max_output_boxes_per_class iou_threshold score_thresholdselected_indices"NonMaxSuppression* use_dyn_output nms_use_dyn_output_false_testZ boxes    Z scores    Z( max_output_boxes_per_class  Z iou_threshold  Z score_threshold  b selected_indices  B ROCm-AMDMIGraphX-46524e8/test/onnx/no_pad_test.onnx000066400000000000000000000001571510465702400217070ustar00rootroot00000000000000no-pad-example:W  01"Pad* pads@@@@  test-no-padZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/nonzero_dynamic_test.onnx000066400000000000000000000002011510465702400236330ustar00rootroot00000000000000nonzero_dynamic_test:c  dataindices"NonZerononzero_dynamic_testZ data    b indices   B ROCm-AMDMIGraphX-46524e8/test/onnx/nonzero_int_test.onnx000066400000000000000000000001671510465702400230140ustar00rootroot00000000000000nonzero_int_test:]  dataindices"NonZerononzero_int_test**Bdatab indices   B ROCm-AMDMIGraphX-46524e8/test/onnx/nonzero_test.onnx000066400000000000000000000001711510465702400221350ustar00rootroot00000000000000 nonzero_test:c  dataindices"NonZero nonzero_test*"€?€?€?Bdatab indices   B ROCm-AMDMIGraphX-46524e8/test/onnx/not_bool_test.onnx000066400000000000000000000001251510465702400222550ustar00rootroot00000000000000 not_bool_test:> 01"Not not_bool_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/not_test.onnx000066400000000000000000000001131510465702400212370ustar00rootroot00000000000000not_test:9 01"Notnot_testZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/onehot_dyn_test0.onnx000066400000000000000000000003001510465702400226630ustar00rootroot00000000000000 onehot_dyn_test0:¥ 9 indices depth valuesy"OneHot* axisÿÿÿÿÿÿÿÿÿ onehot_dyn_test0**BdepthZ indices  Z values   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/onehot_dyn_test1.onnx000066400000000000000000000002741510465702400226760ustar00rootroot00000000000000 onehot_dyn_test1:¡ 0 indices depth valuesy"OneHot* axis onehot_dyn_test1Z indices  Z values  Z depth  b y    BROCm-AMDMIGraphX-46524e8/test/onnx/onehot_static_test.onnx000066400000000000000000000002751510465702400233130ustar00rootroot00000000000000 onehot_static_test:  0 indices depth valuesy"OneHot* axis onehot_static_test**BdepthZ indices   Z values   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_3arg.onnx000066400000000000000000000004451510465702400221630ustar00rootroot00000000000000 gru-example: z seq w rhsoutput"GRU* activationsJtanhJsigmoid * clip * direction"forward * hidden_size test_gruZ seq     Z w   <  Z r   < b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_4arg.onnx000066400000000000000000000005001510465702400221540ustar00rootroot00000000000000 gru-example:ª } seq w r biashsoutput"GRU* activationsJreluJtanh * clip * direction"reverse * hidden_size test_gruZ seq     Z w   <  Z r   < Z bias   xb hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_5arg.onnx000066400000000000000000000005661510465702400221710ustar00rootroot00000000000000 gru-example:à › seq w r bias seq_lenhsoutput"GRU*+ activationsJtanhJsigmoidJreluJtanh * clip * direction" bidirectional * hidden_size test_gruZ seq     Z w   <  Z r   < Z bias   xZ seq_len  b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_bi.onnx000066400000000000000000000006241510465702400217200ustar00rootroot00000000000000 gru-example:þ Ÿ seq w r bias seq_len h0hsoutput"GRU*+ activationsJtanhJsigmoidJreluJtanh * clip * direction" bidirectional * hidden_size test_gruZ seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_bi_0.onnx000066400000000000000000000005461510465702400221420ustar00rootroot00000000000000 gru-example:Ð r seq w r bias seq_len h0hsoutput"GRU* clip * direction" bidirectional * hidden_size test_gruZ seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_bi_1.onnx000066400000000000000000000006411510465702400221370ustar00rootroot00000000000000  onnx_gru_bi_1:‰ ¥ seq w r bias seq_len h0hsoutput"GRU*4 activationsJsigmoidJsigmoidJsigmoidJsigmoid * clip * direction" bidirectional * hidden_size  onnx_gru_bi_1Z seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_bi_2.onnx000066400000000000000000000006331510465702400221410ustar00rootroot00000000000000  onnx_gru_bi_2:ƒ Ÿ seq w r bias seq_len h0hsoutput"GRU*. activationsJsigmoidJsigmoidJtanhJtanh * clip * direction" bidirectional * hidden_size  onnx_gru_bi_2Z seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_forward.onnx000066400000000000000000000006361510465702400227750ustar00rootroot00000000000000 gru-example:ˆ © seq w r bias seq_len h0hsoutput"GRU* activationsJtanhJsigmoid * clip * direction"forward * hidden_size * linear_before_reset test_gruZ seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_forward_0.onnx000066400000000000000000000005401510465702400232060ustar00rootroot00000000000000 gru-example:Ê l seq w r bias seq_len h0hsoutput"GRU* clip * direction"forward * hidden_size test_gruZ seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_reverse.onnx000066400000000000000000000006021510465702400227750ustar00rootroot00000000000000 gru-example:ì  seq w r bias seq_len h0hsoutput"GRU* activationsJtanhJsigmoid * clip * direction"reverse * hidden_size test_gruZ seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_gru_reverse_1.onnx000066400000000000000000000006151510465702400232210ustar00rootroot00000000000000 onnx_gru_reverse_1:ð ‡ seq w r bias seq_len h0hsoutput"GRU* activationsJreluJrelu * clip * direction"reverse * hidden_size onnx_gru_reverse_1Z seq     Z w   <  Z r   < Z bias   xZ seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/onnx_lstm_bi.onnx000066400000000000000000000007771510465702400221130ustar00rootroot00000000000000  onnx_lstm_bi:è Ó seq w r bias seq_len h0 c0 pphhsoutputcellout"LSTM*: activationsJsigmoidJtanhJtanhJsigmoidJtanhJtanh * clip * direction" bidirectional * hidden_size * input_forget  onnx_lstm_biZ seq     Z w   P  Z r   P Z bias    Z seq_len  Z h0    Z c0    Z pph    #include #include #include #include #include #include #include #include #include #include #include #include #include "test.hpp" static migraphx::program read_rnn_onnx(const std::string& name, bool eliminate_deadcode = true) { auto prog = read_onnx(name); auto* mm = prog.get_main_module(); if(eliminate_deadcode) migraphx::run_passes(*mm, {migraphx::dead_code_elimination{}}); // remove the last identity instruction auto last_ins = std::prev(mm->end()); if(last_ins->name() == "@return") { mm->remove_instruction(last_ins); } return prog; } TEST_CASE(rnn_test_bidirectional) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; migraphx::shape seq_shape{migraphx::shape::float_type, {sl, bs, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 2 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {nd, bs, hs}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_rnn_bi.onnx"); EXPECT(p == prog); } TEST_CASE(rnn_test_bidirectional_layout) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; migraphx::shape seq_shape{migraphx::shape::float_type, {bs, sl, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 2 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {bs, nd, hs}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("rnn_bi_layout_test.onnx"); EXPECT(p == prog); } TEST_CASE(rnn_test_one_direction) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 1; // num directions float clip = 0.0f; migraphx::shape seq_shape{migraphx::shape::float_type, {sl, bs, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 2 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {nd, bs, hs}}; // forward { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_rnn_forward.onnx"); EXPECT(p == prog); } // forward, default activation functions { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("rnn_f_default_af_test.onnx"); EXPECT(p == prog); } // reverse { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_rnn_reverse.onnx"); EXPECT(p == prog); } // 3 arguments { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_rnn_3args.onnx"); EXPECT(p == prog); } // 5 arguments { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, seq_len, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_rnn_5args.onnx"); EXPECT(p == prog); } } TEST_CASE(rnn_test_one_direction_layout) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 1; // num directions float clip = 0.0f; migraphx::shape seq_shape{migraphx::shape::float_type, {bs, sl, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 2 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {bs, nd, hs}}; // forward { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("rnn_f_layout_test.onnx"); EXPECT(p == prog); } // reverse { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("rnn_r_layout_test.onnx"); EXPECT(p == prog); } // 3 arguments { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, und, und, und); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("rnn_r_3arg_layout_test.onnx"); EXPECT(p == prog); } // 5 arguments { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, seq_len, und); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("rnn_f_5arg_layout_test.onnx"); EXPECT(p == prog); } } TEST_CASE(rnn_invalid_af_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("rnn_bi_1af_test.onnx"); })); } TEST_CASE(gru_test) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; // forward { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {nd, bs, hs}}); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_forward.onnx"); EXPECT(p == prog); } // reverse { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {nd, bs, hs}}); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_reverse.onnx"); EXPECT(p == prog); } // bidirectional { nd = 2; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {nd, bs, hs}}); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("relu"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_bi.onnx"); EXPECT(p == prog); } } TEST_CASE(gru_layout_test) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; // forward { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {bs, sl, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {bs, nd, hs}}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, seq_len, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("gru_f_layout_test.onnx"); EXPECT(p == prog); } // reverse { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {bs, sl, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {bs, nd, hs}}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("gru_r_layout_test.onnx"); EXPECT(p == prog); } // bidirectional { nd = 2; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {bs, sl, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {bs, nd, hs}}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("relu"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("gru_bi_layout_test.onnx"); EXPECT(p == prog); } } TEST_CASE(gru_test_args) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; // 3 arguments { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_3arg.onnx"); EXPECT(p == prog); } // 4 arguments { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("relu"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_4arg.onnx"); EXPECT(p == prog); } // 5 arguments { nd = 2; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("relu"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, seq_len, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_5arg.onnx"); EXPECT(p == prog); } } TEST_CASE(gru_test_args_layout) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; // 3 arguments { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {bs, sl, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, und, und, und); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("gru_f_3arg_layout_test.onnx"); EXPECT(p == prog); } // 4 arguments { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {bs, sl, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("relu"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, und); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("gru_r_4arg_layout_test.onnx"); EXPECT(p == prog); } // 5 arguments { nd = 2; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {bs, sl, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("relu"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, seq_len, und); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("gru_bi_5arg_layout_test.onnx"); EXPECT(p == prog); } } TEST_CASE(gru_test_actv_funcs) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; // bidirection, 0 actv function { nd = 2; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {nd, bs, hs}}); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_bi_0.onnx"); EXPECT(p == prog); } // bidirection, 1 actv function { nd = 2; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {nd, bs, hs}}); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_bi_1.onnx"); EXPECT(p == prog); } // bidirection, 2 actv functions { nd = 2; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {nd, bs, hs}}); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_bi_2.onnx"); EXPECT(p == prog); } // forward, 0 actv function { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {nd, bs, hs}}); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_forward_0.onnx"); EXPECT(p == prog); } // reverse, 1 actv function { nd = 1; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", migraphx::shape{migraphx::shape::float_type, {sl, bs, is}}); auto w = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, is}}); auto r = mm->add_parameter("r", migraphx::shape{migraphx::shape::float_type, {nd, 3 * hs, hs}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {nd, 6 * hs}}); auto seq_len = mm->add_parameter("seq_len", migraphx::shape{migraphx::shape::int32_type, {bs}}); auto ih = mm->add_parameter("h0", migraphx::shape{migraphx::shape::float_type, {nd, bs, hs}}); auto out_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("relu"), migraphx::make_op("relu")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, seq_len, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_gru_reverse_1.onnx"); EXPECT(p == prog); } } TEST_CASE(gru_invalid_af_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("gru_f_1af_test.onnx"); })); } TEST_CASE(lstm_forward) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 1; // num directions float clip = 0.0f; int input_forget = 1; migraphx::shape seq_shape{migraphx::shape::float_type, {sl, bs, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, 4 * hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, 4 * hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 8 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {nd, bs, hs}}; migraphx::shape pph_shape{migraphx::shape::float_type, {nd, 3 * hs}}; { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_forward.onnx"); EXPECT(p == prog); } // 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_f3args.onnx"); EXPECT(p == prog); } // 3 args, hs output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); auto prog = read_rnn_onnx("onnx_lstm_hs.onnx"); EXPECT(p == prog); } // 3 args, last output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_last.onnx"); EXPECT(p == prog); } // 3 args, cell output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_cell.onnx"); EXPECT(p == prog); } // 4 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_f4args.onnx"); EXPECT(p == prog); } // 5 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_f5args.onnx"); EXPECT(p == prog); } // 6 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_f6args.onnx"); EXPECT(p == prog); } // 7 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_f7args.onnx"); EXPECT(p == prog); } } TEST_CASE(lstm_forward_layout) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 1; // num directions float clip = 0.0f; int input_forget = 1; migraphx::shape seq_shape{migraphx::shape::float_type, {bs, sl, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, 4 * hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, 4 * hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 8 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {bs, nd, hs}}; migraphx::shape pph_shape{migraphx::shape::float_type, {nd, 3 * hs}}; // 8 args, hs and last output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("lstm_f_layout_hs_test.onnx"); EXPECT(p == prog); } // 8 args, cell output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); auto last_cell = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_cell); auto prog = read_rnn_onnx("lstm_f_layout_cell_test.onnx"); EXPECT(p == prog); } } // activation functions TEST_CASE(lstm_forward_actv_func) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 1; // num directions float clip = 0.0f; int input_forget = 1; migraphx::shape seq_shape{migraphx::shape::float_type, {sl, bs, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, 4 * hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, 4 * hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 8 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; // no activation function specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); // auto seq_len = mm->add_parameter("seq_len", sl_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_f0af.onnx"); EXPECT(p == prog); } // 1 activation function specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_f1af.onnx"); EXPECT(p == prog); } // 2 non-default activation function specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_f2af.onnx"); EXPECT(p == prog); } } TEST_CASE(lstm_reverse) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 1; // num directions float clip = 0.0f; int input_forget = 1; migraphx::shape seq_shape{migraphx::shape::float_type, {sl, bs, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, 4 * hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, 4 * hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 8 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {nd, bs, hs}}; migraphx::shape pph_shape{migraphx::shape::float_type, {nd, 3 * hs}}; { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_reverse.onnx"); EXPECT(p == prog); } // 5 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_r5args.onnx"); EXPECT(p == prog); } // no activation function specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_r0af.onnx"); EXPECT(p == prog); } } TEST_CASE(lstm_reverse_layout) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 1; // num directions float clip = 0.0f; int input_forget = 1; migraphx::shape seq_shape{migraphx::shape::float_type, {bs, sl, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, 4 * hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, 4 * hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 8 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {bs, nd, hs}}; migraphx::shape pph_shape{migraphx::shape::float_type, {nd, 3 * hs}}; // 8 args, hs output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); auto prog = read_rnn_onnx("lstm_r_layout_test.onnx"); EXPECT(p == prog); } // 8 args, last and cell output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto last_cell = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); last_output = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_cell); auto prog = read_rnn_onnx("lstm_r_layout_hs_cell_test.onnx"); EXPECT(p == prog); } } TEST_CASE(lstm_bidirectional) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; int input_forget = 1; migraphx::shape seq_shape{migraphx::shape::float_type, {sl, bs, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, 4 * hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, 4 * hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 8 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {nd, bs, hs}}; migraphx::shape pph_shape{migraphx::shape::float_type, {nd, 3 * hs}}; { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi.onnx"); EXPECT(p == prog); } // 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi3args.onnx"); EXPECT(p == prog); } // 4 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi4args.onnx"); EXPECT(p == prog); } // 5 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi5args.onnx"); EXPECT(p == prog); } // 6 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi6args.onnx"); EXPECT(p == prog); } // 7 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi7args.onnx"); EXPECT(p == prog); } } TEST_CASE(lstm_bidirectional_layout) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; int input_forget = 1; migraphx::shape seq_shape{migraphx::shape::float_type, {bs, sl, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, 4 * hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, 4 * hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 8 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {bs, nd, hs}}; migraphx::shape pph_shape{migraphx::shape::float_type, {nd, 3 * hs}}; // 0 activation function { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); auto prog = read_rnn_onnx("lstm_bi_layout_last_test.onnx"); EXPECT(p == prog); } { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto pph = mm->add_parameter("pph", pph_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, pph); auto last_cell = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_cell); auto prog = read_rnn_onnx("lstm_bi_layout_cell_test.onnx"); EXPECT(p == prog); } } TEST_CASE(lstm_bi_actv_funcs) { std::size_t sl = 5; // sequence len std::size_t bs = 3; // batch size std::size_t hs = 20; // hidden size std::size_t is = 10; // input size std::size_t nd = 2; // num directions float clip = 0.0f; int input_forget = 1; migraphx::shape seq_shape{migraphx::shape::float_type, {sl, bs, is}}; migraphx::shape w_shape{migraphx::shape::float_type, {nd, 4 * hs, is}}; migraphx::shape r_shape{migraphx::shape::float_type, {nd, 4 * hs, hs}}; migraphx::shape bias_shape{migraphx::shape::float_type, {nd, 8 * hs}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {bs}}; migraphx::shape ih_shape{migraphx::shape::float_type, {nd, bs, hs}}; migraphx::shape pph_shape{migraphx::shape::float_type, {nd, 3 * hs}}; // 0 activation function { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi0af.onnx"); EXPECT(p == prog); } // all activation functions - sigmoid { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi1af.onnx"); EXPECT(p == prog); } // all forward direction functions are tanh, default reverse direction { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi2af.onnx"); EXPECT(p == prog); } // default forward direction, all reverse direction functions are tanh { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi3af.onnx"); EXPECT(p == prog); } // no-default forward direction functions, default reverse direction { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", bias_shape); auto seq_len = mm->add_parameter("seq_len", sl_shape); auto ih = mm->add_parameter("h0", ih_shape); auto ic = mm->add_parameter("c0", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, bias, seq_len, ih, ic, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi4af.onnx"); EXPECT(p == prog); } // default forward direction, no-default reverse direction functions { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_parameter("seq", seq_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hs}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", input_forget}}), seq, w, r, und, und, und, und, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto prog = read_rnn_onnx("onnx_lstm_bi5af.onnx"); EXPECT(p == prog); } } TEST_CASE(lstm_invalid_af_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("lstm_f_1af_test.onnx"); })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/onnx/onnx_sinh.onnx000066400000000000000000000001211510465702400214020ustar00rootroot00000000000000 sinh-example:; xy"Sinh test_sinhZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/pad_3arg_test.onnx000066400000000000000000000003271510465702400221260ustar00rootroot00000000000000 pad_3arg_test:¿ .arg_val"Constant* value* "€?Bval  5arg_pad"Constant* value**Bpad_size   0 arg_pad arg_val1"Pad pad_3arg_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/pad_4arg_axes_test.onnx000066400000000000000000000004611510465702400231460ustar00rootroot00000000000000 pad_4arg_axes_test:” 4arg_axes"Constant* value**Bpad_axes  .arg_val"Constant* value* "€?Bval  5arg_pad"Constant* value**Bpad_size  ' 0 arg_pad arg_val arg_axes1"Padpad_4arg_axes_testZ 0     b 1      BROCm-AMDMIGraphX-46524e8/test/onnx/pad_4arg_invalid_axes_error_test.onnx000066400000000000000000000005161510465702400260660ustar00rootroot00000000000000  pad_4arg_invalid_axes_error_test:£ 5arg_axes"Constant* value**Bpad_axes  .arg_val"Constant* value* "€?Bval  5arg_pad"Constant* value**Bpad_size  ' 0 arg_pad arg_val arg_axes1"Pad pad_4arg_invalid_axes_error_testZ 0     b 1      BROCm-AMDMIGraphX-46524e8/test/onnx/pad_4arg_neg_axes_test.onnx000066400000000000000000000005131510465702400237750ustar00rootroot00000000000000 pad_4arg_neg_axes_test:ª Farg_axes"Constant*0 value*$*ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBpad_axes  .arg_val"Constant* value* "€?Bval  5arg_pad"Constant* value**Bpad_size  ' 0 arg_pad arg_val arg_axes1"Padpad_4arg_neg_axes_testZ 0     b 1      BROCm-AMDMIGraphX-46524e8/test/onnx/pad_asym_invalid_pads_error_test.onnx000066400000000000000000000002531510465702400261670ustar00rootroot00000000000000  pad_asym_invalid_pads_error_test:€ " 01"Pad* pads@@@@@@  pad_asym_invalid_pads_error_testZ 0     b 1      BROCm-AMDMIGraphX-46524e8/test/onnx/pad_asym_test.onnx000066400000000000000000000002101510465702400222320ustar00rootroot00000000000000  pad_asym_test:q & 01"Pad* pads@@@@@@@@  pad_asym_testZ 0     b 1      BROCm-AMDMIGraphX-46524e8/test/onnx/pad_attr_dyn_test.onnx000066400000000000000000000001601510465702400231110ustar00rootroot00000000000000pad_attr_dyn_test:U  01"Pad* pads@@@@ pad_attr_dyn_testZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/pad_cnst_dyn_test.onnx000066400000000000000000000002361510465702400231120ustar00rootroot00000000000000pad_cnst_dyn_test:‚ 5arg_pad"Constant* value**Bpad_size   0 arg_pad1"Padpad_cnst_dyn_testZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/pad_dyn_reflect_error.onnx000066400000000000000000000002141510465702400237350ustar00rootroot00000000000000pad_dyn_reflect_error:m 2 01"Pad* mode"reflect * pads@@@@ pad_dyn_reflect_errorZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/pad_edge_1d_test.onnx000066400000000000000000000002531510465702400225600ustar00rootroot00000000000000 pad_edge_1d_test: 3arg_pad"Constant* value**Bpad_size  % 0 arg_pad1"Pad* mode"edge pad_edge_1d_testZ 0  b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/pad_edge_2d_test.onnx000066400000000000000000000002651510465702400225640ustar00rootroot00000000000000 pad_edge_2d_test:š 5arg_pad"Constant* value**Bpad_size  % 0 arg_pad1"Pad* mode"edge pad_edge_2d_testZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/pad_edge_2d_with_axes_test.onnx000066400000000000000000000004061510465702400246340ustar00rootroot00000000000000 pad_edge_2d_with_axes_test:á 3arg_axes"Constant* value**Bpad_axes  3arg_pad"Constant* value**Bpad_size  / 0 arg_pad arg_axes1"Pad* mode"edge pad_edge_2d_with_axes_testZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/pad_empty_const_val_test.onnx000066400000000000000000000002661510465702400245020ustar00rootroot00000000000000 pad_empty_const_val_test:“ 5arg_pad"Constant* value**Bpad_size   0 arg_pad 1"Padpad_empty_const_val_testZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/pad_reflect_multiaxis_test.onnx000066400000000000000000000003141510465702400250110ustar00rootroot00000000000000pad_reflect_multiaxis_test:§ 5arg_pad"Constant* value**Bpad_size  ( 0 arg_pad1"Pad* mode"reflect pad_reflect_multiaxis_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/pad_reflect_test.onnx000066400000000000000000000002701510465702400227130ustar00rootroot00000000000000pad_reflect_test: 5arg_pad"Constant* value**Bpad_size  ( 0 arg_pad1"Pad* mode"reflect pad_reflect_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/pad_reflect_with_axes_test.onnx000066400000000000000000000004111510465702400247630ustar00rootroot00000000000000 pad_reflect_with_axes_test:ä 3arg_axes"Constant* value**Bpad_axes  3arg_pad"Constant* value**Bpad_size  2 0 arg_pad arg_axes1"Pad* mode"reflect pad_reflect_with_axes_testZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/pad_test.onnx000066400000000000000000000001511510465702400212050ustar00rootroot00000000000000 pad-example:T  01"Pad* pads@@@@ test-padZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/pad_undef_const_val_test.onnx000066400000000000000000000002661510465702400244450ustar00rootroot00000000000000 pad_undef_const_val_test:“ 5arg_pad"Constant* value**Bpad_size   0 arg_pad 1"Padpad_undef_const_val_testZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/parse/000077500000000000000000000000001510465702400176135ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/parse/acos_test.cpp000066400000000000000000000027711510465702400223120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(acos_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("acos"), input); auto prog = optimize_onnx("acos_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/acosh_test.cpp000066400000000000000000000027741510465702400224650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(acosh_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("acosh"), input); auto prog = optimize_onnx("acosh_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/add_bcast_test.cpp000066400000000000000000000033521510465702400232650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(add_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3, 4}}); auto l2 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", l0->get_shape().lens()}}), l1); mm->add_instruction(migraphx::make_op("add"), l0, l2); auto prog = optimize_onnx("add_bcast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/add_bf16_test.cpp000066400000000000000000000031251510465702400227250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(add_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); auto p0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::bf16_type, {1}}); auto p1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::bf16_type, {1}}); mm->add_instruction(migraphx::make_op("add"), p0, p1); auto prog = optimize_onnx("add_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/add_fp16_test.cpp000066400000000000000000000032071510465702400227440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(add_fp16_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type, {1}}, {1.5}}); auto l1 = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type, {1}}, {2.5}}); mm->add_instruction(migraphx::make_op("add"), l0, l1); auto prog = optimize_onnx("add_fp16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/add_fp8_test.cpp000066400000000000000000000031411510465702400226620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(add_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto p0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {1}}); auto p1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {1}}); mm->add_instruction(migraphx::make_op("add"), p0, p1); auto prog = optimize_onnx("add_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/add_scalar_test.cpp000066400000000000000000000033171510465702400234370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(add_scalar_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::uint8_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::uint8_type}); auto m1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 5}}}), l1); mm->add_instruction(migraphx::make_op("add"), l0, m1); auto prog = optimize_onnx("add_scalar_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/argmax_dyn_test.cpp000066400000000000000000000034331510465702400235120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(argmax_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "x", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}, {5, 5}, {6, 6}}}); auto ins = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 2}}), l0); auto ret = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), ins); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("argmax_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/argmax_select_last_index_test.cpp000066400000000000000000000032601510465702400264070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(argmax_select_last_index_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto ins = mm->add_instruction( migraphx::make_op("argmax", {{"axis", 2}, {"select_last_index", true}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), ins); auto prog = optimize_onnx("argmax_select_last_index_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/argmax_test.cpp000066400000000000000000000031461510465702400226410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(argmax_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto ins = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 2}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), ins); auto prog = optimize_onnx("argmax_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/argmin_select_last_index_test.cpp000066400000000000000000000032601510465702400264050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(argmin_select_last_index_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto ins = mm->add_instruction( migraphx::make_op("argmin", {{"axis", 3}, {"select_last_index", true}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), ins); auto prog = optimize_onnx("argmin_select_last_index_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/argmin_test.cpp000066400000000000000000000031461510465702400226370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(argmin_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto ins = mm->add_instruction(migraphx::make_op("argmin", {{"axis", 3}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), ins); auto prog = optimize_onnx("argmin_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/asin_test.cpp000066400000000000000000000027711510465702400223170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(asin_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("asin"), input); auto prog = optimize_onnx("asin_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/asinh_test.cpp000066400000000000000000000027741510465702400224720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(asinh_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("asinh"), input); auto prog = optimize_onnx("asinh_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/atan_test.cpp000066400000000000000000000027711510465702400223100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(atan_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("atan"), input); auto prog = optimize_onnx("atan_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/atanh_test.cpp000066400000000000000000000027741510465702400224630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(atanh_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("atanh"), input); auto prog = optimize_onnx("atanh_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_batch1_test.cpp000066400000000000000000000030531510465702400271210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_double_head_batch1_test) { // Batch 1, sequence length =2 , num_heads = 1, embedding_size =4 migraphx::program p = make_attention_program(1, 2, 2, 4, true); auto prog = optimize_onnx("attention_double_head_batch1_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_left_pad_mask_test.cpp000066400000000000000000000027121510465702400315470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(attention_double_head_bias_left_pad_mask_test) { // We currently don't support left padding masks EXPECT(test::throws( [&] { optimize_onnx("attention_double_head_bias_asym_left_pad_mask_test.onnx"); })); } attention_double_head_bias_mask_bad_rotary_embedding_test.cpp000066400000000000000000000027711510465702400340430ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/parse/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(attention_double_head_bias_mask_bad_rotary_embedding_dim_test) { // We currently don't support rotary embedding dim attribute EXPECT(test::throws([&] { optimize_onnx("attention_double_head_bias_asym_mask_bad_rotary_embedding_dim_test.onnx"); })); } attention_double_head_bias_mask_past_attn_bias_past_seq_len.cpp000066400000000000000000000027671510465702400344150ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/parse/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_double_head_bias_mask_past_attn_bias_shared_past_seq_len_test) { EXPECT(test::throws([&] { optimize_onnx( "attention_double_head_bias_mask_past_attn_bias_shared_past_seq_len_test.onnx"); })); } attention_double_head_bias_mask_past_attn_bias_shared_test.cpp000066400000000000000000000027151510465702400342360ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/parse/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_double_head_bias_mask_past_attn_bias_shared_test) { EXPECT(test::throws( [&] { optimize_onnx("attention_double_head_bias_mask_past_attn_bias_shared_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_mask_past_test.cpp000066400000000000000000000030011510465702400307300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(attention_double_head_bias_mask_past_test) { // We currently don't support just past inputs when past/present buffer not shared // Likely this requires dynamic shapes EXPECT(test::throws([&] { optimize_onnx("attention_double_head_bias_mask_past_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_mask_rotary_embedding_test.cpp000066400000000000000000000027731510465702400333160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(attention_double_head_bias_mask_rotary_embedding_dim_test) { // We currently don't support rotary embedding dim attribute right now EXPECT(test::throws([&] { optimize_onnx("attention_double_head_bias_asym_mask_rotary_embedding_dim_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_mask_rotary_test.cpp000066400000000000000000000027231510465702400313130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(attention_double_head_bias_mask_past_rotary_test) { // We currently don't support rotary embedding parameter EXPECT(test::throws( [&] { optimize_onnx("attention_double_head_bias_asym_mask_rotary_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_mask_scale_val_test.cpp000066400000000000000000000033151510465702400317220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_double_head_bias_mask_scale_value_test) { // Batch 2, sequence length 3 num_heads 2, embedding_size 4 // Key pad masking and bias true float scale_value = 0.1234f; auto mask_value = -10000; migraphx::program p = make_attention_program(2, 3, 2, 4, true, true, mask_value, scale_value); auto prog = optimize_onnx("attention_double_head_bias_asym_mask_scale_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_mask_test.cpp000066400000000000000000000036211510465702400277110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_double_head_bias_mask_test) { // Batch 2, sequence length 2 num_heads 2, embedding_size 4 // Key pad masking and bias true migraphx::program p = make_attention_program(2, 2, 2, 4, true, true); auto prog = optimize_onnx("attention_double_head_bias_mask_test.onnx"); EXPECT(p == prog); } TEST_CASE(attention_double_head_3d_mask_unsupported) { EXPECT(test::throws([&] { optimize_onnx("attention_double_head_bias_3d_mask_test.onnx"); })); } TEST_CASE(attention_double_head_4d_mask_unsupported) { EXPECT(test::throws([&] { optimize_onnx("attention_double_head_bias_4d_mask_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_mask_unidirectional_test.cpp000066400000000000000000000027411510465702400330040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(attention_double_head_bias_mask_past_unidirectional_test) { // We currently don't support unidirectional parameter EXPECT(test::throws( [&] { optimize_onnx("attention_double_head_bias_asym_mask_unidirectional_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_mask_value_test.cpp000066400000000000000000000032211510465702400311010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_double_head_bias_mask_value_test) { // Batch 2, sequence length 3 num_heads 2, embedding_size 4 // Key pad masking and bias true auto mask_value = -5000; migraphx::program p = make_attention_program(2, 3, 2, 4, true, true, mask_value); auto prog = optimize_onnx("attention_double_head_bias_asym_mask_filter_val_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_right_pad_mask_test.cpp000066400000000000000000000027101510465702400317300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(attention_double_head_bias_right_pad_mask_test) { // We currently don't support right pad mask EXPECT(test::throws( [&] { optimize_onnx("attention_double_head_bias_asym_right_pad_mask_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_bias_test.cpp000066400000000000000000000031431510465702400266750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_double_head_bias_test) { // Batch 2, sequenec_length 2, Num Heads 2, embedding_size = 4 // Bias generated should be 12 (4 * 3 assumes Q=K=V sizes) migraphx::program p = make_attention_program(2, 2, 2, 4, true); auto prog = optimize_onnx("attention_double_head_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_double_head_test.cpp000066400000000000000000000030241510465702400256750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_double_head_test) { // Batch 2, sequenec_length 4, Num Heads 2, embedding_size = 4 migraphx::program p = make_attention_program(2, 4, 2, 4); auto prog = optimize_onnx("attention_double_head_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_invalid_inputs_attr_tests.cpp000066400000000000000000000107041510465702400277120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include // Input must always be of dimension 3 to pull batch, sequence and hidden size information TEST_CASE(attention_invalid_input_dimensions) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_input_dimension.onnx"); })); } // We expect failure if the num_heads attribute is not set TEST_CASE(attention_invalid_no_num_heads) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_no_num_heads.onnx"); })); } // Hidden sizes of weights must match that of input vector TEST_CASE(attention_invalid_weight_hidden_size) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_weight_hidden_size.onnx"); })); } // Hidden sizes of weights if uneven must be defined by qkv TEST_CASE(attention_invalid_weight_no_qkv_hidden_attr) { EXPECT( test::throws([&] { optimize_onnx("attention_invalid_uneven_weight_no_qkv_hidden.onnx"); })); } // Bias dimensions must always be of size 1 TEST_CASE(attention_invalid_bias_dims_size) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_bias_dims_size.onnx"); })); } // Bias dimension value must always be equal to the sum of qkv sizes (3*hidden size if qkv aren't // set) TEST_CASE(attention_invalid_bias_value_size) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_bias_value_size.onnx"); })); } // Attention key pad mask (2d) must use (batch, sequence_length) as dimensions TEST_CASE(attention_invalid_key_pad_mask) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_mask_2d_dims_test.onnx"); })); } // Attention key pad mask (3d) must use (batch, sequence_length, total_sequence_length) as // dimensions TEST_CASE(attention_invalid_key_pad_mask2) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_mask_3d_dims_test.onnx"); })); } // Attention 4D mask must use (batch, 1, sequence_length sequence_length) as dimensions TEST_CASE(attention_invalid_4d_raw_mask) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_mask_4d_dims_test.onnx"); })); } // Attention 4D mask is (batch, 1, sequence_length sequence_length) thus last two dims must be equal TEST_CASE(attention_invalid_4d_raw_mask2) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_mask_4d_last_dims_test.onnx"); })); } // Attention mask can have at most 5 dimensions TEST_CASE(attention_invalid_5d_raw_mask) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_mask_5d_dims_test.onnx"); })); } // qkv_hidden_sizes attribute must be of dimension 3 TEST_CASE(attention_invalid_qkv_attr_dims) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_qkv_attr_test.onnx"); })); } // qkv_hidden_sizes attribute must be identical for Q and K values TEST_CASE(attention_invalid_qkv_attr_values) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_qkv_attr_test2.onnx"); })); } // qkv_hidden_sizes attribute values my be greater than 0 TEST_CASE(attention_invalid_qkv_attr_values2) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_qkv_attr_test3.onnx"); })); } TEST_CASE(attention_invalid_input_num) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_input_num.onnx"); })); } TEST_CASE(attention_invalid_mask_type) { EXPECT(test::throws([&] { optimize_onnx("attention_invalid_mask_type_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_multi_head_bias_mask_test.cpp000066400000000000000000000030661510465702400275740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_multi_head_bias_mask_test) { // Batch 32, Sequence Len 512, Heads 16, Embedding_size 1024 migraphx::program p = make_attention_program(32, 512, 16, 1024, true, true); auto prog = optimize_onnx("attention_multihead_bias_mask_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_multi_head_test.cpp000066400000000000000000000030261510465702400255570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_multi_head_test) { // Batch 32, Sequence Len 512, Heads 16, Embedding_size 1024 migraphx::program p = make_attention_program(32, 512, 16, 1024); auto prog = optimize_onnx("attention_multihead_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/attention_single_head_test.cpp000066400000000000000000000030331510465702400257040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(attention_single_head_test) { // Batch 2, sequence length =2 , num_heads = 1, embedding_size =4 migraphx::program p = make_attention_program(2, 2, 1, 4, true); auto prog = optimize_onnx("attention_single_head_batch2_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_1d_test.cpp000066400000000000000000000036141510465702400242520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_1d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 5}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0}}, {"stride", {1}}, {"lengths", {3}}, {"dilations", {1}}}), l0); auto prog = optimize_onnx("averagepool_1d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_3d_test.cpp000066400000000000000000000036601510465702400242550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_3d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 5, 5, 5}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 0, 0, 0, 0}}, {"stride", {1, 1, 1}}, {"lengths", {3, 3, 3}}, {"dilations", {1, 1, 1}}}), l0); auto prog = optimize_onnx("averagepool_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_dilate_test.cpp000066400000000000000000000036531510465702400252130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_dilate_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 4, 3}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {1, 1}}, {"stride", {1}}, {"lengths", {2}}, {"dilations", {3}}}), input); auto prog = optimize_onnx("averagepool_dilate_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_dyn_asym_padding_error_test.cpp000066400000000000000000000026661510465702400304760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(averagepool_dyn_asym_padding_error_test) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT( test::throws([&] { read_onnx("averagepool_dyn_asym_padding_error_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_dyn_autopad_test.cpp000066400000000000000000000044331510465702400262550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_dyn_autopad_test) { // Pooling with dynamic input and auto padding. Default padding values will be overridden. migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", {migraphx::shape::float_type, {{1, 4}, {3, 3}, {5, 5}, {5, 5}, {5, 5}}}); auto ret = mm->add_instruction( migraphx::make_op("pooling", { {"mode", migraphx::op::pooling_mode::average}, {"stride", {2, 2, 2}}, {"lengths", {3, 3, 3}}, {"dilations", {1, 1, 1}}, {"padding", {0, 0, 0, 0, 0, 0}}, {"padding_mode", migraphx::op::padding_mode_t::same_upper}, }), l0); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("averagepool_dyn_autopad_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_dyn_cip_error_test.cpp000066400000000000000000000026331510465702400266040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(averagepool_dyn_cip_error_test) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT(test::throws([&] { read_onnx("averagepool_dyn_cip_error_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_dyn_test.cpp000066400000000000000000000045571510465702400245470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_dyn_test) { // Pooling with dynamic input and no auto padding migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", {migraphx::shape::float_type, {{1, 4}, {3, 3}, {5, 5}, {5, 5}, {5, 5}}}); auto ret = mm->add_instruction(migraphx::make_op("pooling", { {"mode", migraphx::op::pooling_mode::average}, {"stride", {2, 2, 2}}, {"lengths", {3, 3, 3}}, {"dilations", {1, 1, 1}}, {"padding", {1, 1, 1, 1, 1, 1}}, {"padding_mode", 0}, }), l0); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("averagepool_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_notset_test.cpp000066400000000000000000000042661510465702400252660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_notset_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}); auto ins = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {2, 2, 2, 2}}, {"stride", {2, 2}}, {"lengths", {6, 6}}, {"dilations", {1, 1}}}), input); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2, 3}}, {"starts", {1, 1}}, {"ends", {2, 2}}}), ins); mm->add_return({ret}); auto prog = read_onnx("averagepool_notset_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_nt_cip_test.cpp000066400000000000000000000043171510465702400252230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_nt_cip_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}); std::vector pads = {0, 0, 0, 0, 0, 0, 1, 1}; auto ins_pad = mm->add_instruction(migraphx::make_op("pad", {{"pads", pads}}), input); auto ret = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 0, 0}}, {"stride", {2, 2}}, {"lengths", {6, 6}}, {"dilations", {1, 1}}}), ins_pad); mm->add_return({ret}); auto prog = read_onnx("averagepool_nt_cip_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_same_lower_test.cpp000066400000000000000000000045251510465702400261050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_same_lower_test) { // auto_pad mode of SAME_LOWER with a static input shape is handled in parsing and // padding_mode is set to default_ when the operation is created migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}); auto ins = mm->add_instruction( migraphx::make_op("pooling", { {"mode", migraphx::op::pooling_mode::average}, {"padding", {1, 1, 1, 1}}, {"stride", {1, 1}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::default_}, }), input); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2, 3}}, {"starts", {0, 0}}, {"ends", {5, 5}}}), ins); mm->add_return({ret}); auto prog = read_onnx("averagepool_same_lower_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_same_upper_test.cpp000066400000000000000000000042761510465702400261130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_same_upper_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}); auto ins = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {1, 1, 1, 1}}, {"stride", {1, 1}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}}), input); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2, 3}}, {"starts", {1, 1}}, {"ends", {6, 6}}}), ins); mm->add_return({ret}); auto prog = read_onnx("averagepool_same_upper_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/averagepool_sl_cip_test.cpp000066400000000000000000000043171510465702400252200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(averagepool_sl_cip_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}); std::vector pads = {0, 0, 1, 1, 0, 0, 0, 0}; auto ins_pad = mm->add_instruction(migraphx::make_op("pad", {{"pads", pads}}), input); auto ret = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}}), ins_pad); mm->add_return({ret}); auto prog = read_onnx("averagepool_sl_cip_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/batch_norm_1d_test.cpp000066400000000000000000000053201510465702400240560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(batch_norm_1d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::half_type, {2, 3, 4}}); auto scale = mm->add_parameter("scale", {migraphx::shape::float_type, {3}}); auto bias = mm->add_parameter("bias", {migraphx::shape::float_type, {3}}); auto mean = mm->add_parameter("mean", {migraphx::shape::float_type, {3}}); auto var = mm->add_parameter("variance", {migraphx::shape::float_type, {3}}); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::half_type, {1e-5f}}); auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), scale); auto usq_bias = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), bias); auto usq_mean = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), mean); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), var); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), var_eps); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {usq_scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); auto prog = optimize_onnx("batch_norm_1d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/batch_norm_2d_test.cpp000066400000000000000000000053411510465702400240620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(batch_norm_2d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 3, 4, 4}}); auto scale = mm->add_parameter("scale", {migraphx::shape::float_type, {3}}); auto bias = mm->add_parameter("bias", {migraphx::shape::float_type, {3}}); auto mean = mm->add_parameter("mean", {migraphx::shape::float_type, {3}}); auto var = mm->add_parameter("variance", {migraphx::shape::float_type, {3}}); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {1e-5f}}); auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); auto usq_bias = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), bias); auto usq_mean = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), mean); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), var); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), var_eps); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {usq_scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); auto prog = optimize_onnx("batch_norm_2d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/batch_norm_3d_test.cpp000066400000000000000000000053771510465702400240740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(batch_norm_3d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::half_type, {2, 2, 2, 2, 2}}); auto scale = mm->add_parameter("scale", {migraphx::shape::half_type, {2}}); auto bias = mm->add_parameter("bias", {migraphx::shape::half_type, {2}}); auto mean = mm->add_parameter("mean", {migraphx::shape::half_type, {2}}); auto var = mm->add_parameter("variance", {migraphx::shape::half_type, {2}}); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::half_type, {1e-6f}}); auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), scale); auto usq_bias = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), bias); auto usq_mean = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), mean); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), var); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), var_eps); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {usq_scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); auto prog = optimize_onnx("batch_norm_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/batch_norm_flat_test.cpp000066400000000000000000000044761510465702400245130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(batch_norm_flat_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {10}}); auto scale = mm->add_parameter("scale", {migraphx::shape::float_type, {1}}); auto bias = mm->add_parameter("bias", {migraphx::shape::float_type, {1}}); auto mean = mm->add_parameter("mean", {migraphx::shape::float_type, {1}}); auto var = mm->add_parameter("variance", {migraphx::shape::float_type, {1}}); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {1e-6f}}); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), {var_eps}); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); add_common_op(*mm, migraphx::make_op("add"), {r0, bias}); auto prog = optimize_onnx("batch_norm_flat_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/batch_norm_invalid_bias_rank.cpp000066400000000000000000000025161510465702400261560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(batch_norm_invalid_bias_rank) { EXPECT(test::throws([&] { migraphx::parse_onnx("batch_norm_invalid_bias_rank_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/batch_norm_rank_2_test.cpp000066400000000000000000000045041510465702400247310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(batch_norm_rank_2_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 5}}); auto scale = mm->add_parameter("scale", {migraphx::shape::float_type, {5}}); auto bias = mm->add_parameter("bias", {migraphx::shape::float_type, {5}}); auto mean = mm->add_parameter("mean", {migraphx::shape::float_type, {5}}); auto var = mm->add_parameter("variance", {migraphx::shape::float_type, {5}}); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {1e-6f}}); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), {var_eps}); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); add_common_op(*mm, migraphx::make_op("add"), {r0, bias}); auto prog = optimize_onnx("batch_norm_rank_2_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/biasadd_test.cpp000066400000000000000000000035131510465702400227470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(biasadd_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 3, 4}}); auto bias = mm->add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {4}}); auto skip = mm->add_parameter("skip", migraphx::shape{migraphx::shape::float_type, {2, 3, 4}}); auto x_plus_bias = add_common_op(*mm, migraphx::make_op("add"), {x, bias}); auto ret = add_common_op(*mm, migraphx::make_op("add"), {x_plus_bias, skip}); mm->add_return({ret}); auto prog = read_onnx("biasadd_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/binary_dyn_brcst_add_test.cpp000066400000000000000000000034471510465702400255310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(binary_dyn_brcst_add_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::half_type, {4, 5}}); auto l1 = mm->add_parameter( "1", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}, {5, 5}}}); auto ret = add_common_op(*mm, migraphx::make_op("add"), {l0, l1}); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("binary_dyn_brcst_add_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/binary_dyn_brcst_attr_error_test.cpp000066400000000000000000000026371510465702400271640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(binary_dyn_brcst_attr_error_test) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT(test::throws([&] { read_onnx("binary_dyn_brcst_attr_error_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/binary_dyn_brcst_mul_fp8_test.cpp000066400000000000000000000042711510465702400263470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(binary_dyn_brcst_mul_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {{1, 4}, {3, 3}, {4, 4}, {5, 5}}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {4, 1}}); auto bl0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(l0->get_shape().dyn_dims())}}), l0, l1); auto bl1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(l0->get_shape().dyn_dims())}}), l1, bl0); auto ret = mm->add_instruction(migraphx::make_op("mul"), bl0, bl1); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("binary_dyn_brcst_mul_fp8_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/binary_dyn_brcst_mul_test.cpp000066400000000000000000000042451510465702400255730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(binary_dyn_brcst_mul_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}, {5, 5}}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {4, 1}}); auto bl0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(l0->get_shape().dyn_dims())}}), l0, l1); auto bl1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(l0->get_shape().dyn_dims())}}), l1, bl0); auto ret = mm->add_instruction(migraphx::make_op("mul"), bl0, bl1); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("binary_dyn_brcst_mul_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/binary_dyn_brcst_prelu_test.cpp000066400000000000000000000034551510465702400261270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(binary_dyn_brcst_prelu_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}, {5, 5}}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {4, 5}}); auto ret = add_common_op(*mm, migraphx::make_op("prelu"), {l0, l1}); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("binary_dyn_brcst_prelu_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/bitwise_and.cpp000066400000000000000000000034341510465702400226130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(bitwise_and_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::int32_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4, 5}}); auto l2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", l0->get_shape().lens()}}), l1); auto ret = mm->add_instruction(migraphx::make_op("bitwise_and"), l0, l2); mm->add_return({ret}); auto prog = read_onnx("bitwise_and_bcast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/cast_test.cpp000066400000000000000000000031411510465702400223070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(cast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_parameter("x", migraphx::shape{migraphx::shape::half_type, {10}}); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l); auto prog = optimize_onnx("cast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/castlike_error_test.cpp000066400000000000000000000024541510465702400243730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(castlike_error_test) { EXPECT(test::throws([&] { read_onnx("castlike_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/castlike_test.cpp000066400000000000000000000032711510465702400231600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(castlike_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_parameter("0", migraphx::shape{migraphx::shape::half_type, {10}}); mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l); auto prog = optimize_onnx("castlike_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/ceil_test.cpp000066400000000000000000000027711510465702400223010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(ceil_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("ceil"), input); auto prog = optimize_onnx("ceil_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/celu_alpha_test.cpp000066400000000000000000000032071510465702400234550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(celu_alpha_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens = {3}; auto input_type = migraphx::shape::float_type; migraphx::shape s{input_type, input_lens}; float alpha = 0.8; add_celu_instruction(mm, s, alpha); auto prog = optimize_onnx("celu_alpha_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/celu_default_test.cpp000066400000000000000000000032161510465702400240140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(celu_default_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens = {2, 3}; auto input_type = migraphx::shape::float_type; migraphx::shape s{input_type, input_lens}; float alpha = 1.0; add_celu_instruction(mm, s, alpha); auto prog = optimize_onnx("celu_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/celu_wrong_type_test.cpp000066400000000000000000000024561510465702400245720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(celu_wrong_type_test) { EXPECT(test::throws([&] { read_onnx("celu_wrong_type_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/celu_zero_alpha_test.cpp000066400000000000000000000024561510465702400245210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(celu_zero_alpha_test) { EXPECT(test::throws([&] { read_onnx("celu_zero_alpha_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_dyn_min_max_test.cpp000066400000000000000000000046471510465702400247020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_dyn_min_max_test) { migraphx::program p; auto* mm = p.get_main_module(); auto min_val = mm->add_literal(0.0f); auto max_val = mm->add_literal(6.0f); std::vector dds = {{2, 8, {3}}}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, dds}); auto bl0 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(dds)}}), l0, min_val, max_val); min_val = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(dds)}}), min_val, bl0); max_val = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(dds)}}), max_val, bl0); auto ret = mm->add_instruction(migraphx::make_op("clip"), bl0, min_val, max_val); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {2, 8, {3}}; auto prog = read_onnx("clip_dyn_min_max_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_dyn_min_only_test.cpp000066400000000000000000000041601510465702400250640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_dyn_min_only_test) { migraphx::program p; auto* mm = p.get_main_module(); auto min_val = mm->add_literal(0.0f); std::vector dds = {{2, 8, {3}}}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, dds}); auto bl0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(dds)}}), l0, min_val); min_val = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_dyn_dims", to_value(dds)}}), min_val, bl0); auto ret = mm->add_instruction(migraphx::make_op("max"), bl0, min_val); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {2, 8, {3}}; auto prog = read_onnx("clip_dyn_min_only_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_test.cpp000066400000000000000000000034721510465702400223130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); auto min_val = mm->add_literal(0.0f); auto max_val = mm->add_literal(6.0f); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), max_val); mm->add_instruction(migraphx::make_op("clip"), l0, min_val, max_val); auto prog = optimize_onnx("clip_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_test_args_type_mismatch.cpp000066400000000000000000000042711510465702400262530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_test_args_type_mismatch) { migraphx::program p; auto* mm = p.get_main_module(); auto min_val = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {1, 3}}, {1.5, 2.5, 3.5}}); auto max_val = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {3, 1}}, {2, 3, 4}}); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3, 3}}); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 3}}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 3}}}), max_val); max_val = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), max_val); auto r = mm->add_instruction(migraphx::make_op("clip"), l0, min_val, max_val); mm->add_return({r}); auto prog = read_onnx("clip_test_args_type_mismatch.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_test_op11.cpp000066400000000000000000000035041510465702400231470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_test_op11) { migraphx::program p; auto* mm = p.get_main_module(); auto min_val = mm->add_literal(0.0f); auto max_val = mm->add_literal(6.0f); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), max_val); mm->add_instruction(migraphx::make_op("clip"), l0, min_val, max_val); auto prog = optimize_onnx("clip_test_op11.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_test_op11_max_only.cpp000066400000000000000000000034141510465702400250550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_test_op11_max_only) { migraphx::program p; auto* mm = p.get_main_module(); auto max_val = mm->add_literal(0.0f); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); mm->add_instruction(migraphx::make_op("undefined")); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), max_val); auto r = mm->add_instruction(migraphx::make_op("min"), l0, max_val); mm->add_return({r}); auto prog = read_onnx("clip_test_op11_max_only.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_test_op11_min_only.cpp000066400000000000000000000032641510465702400250560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_test_op11_min_only) { migraphx::program p; auto* mm = p.get_main_module(); auto min_val = mm->add_literal(0.0f); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), min_val); mm->add_instruction(migraphx::make_op("max"), l0, min_val); auto prog = optimize_onnx("clip_test_op11_min_only.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_test_op11_no_args.cpp000066400000000000000000000030161510465702400246550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_test_op11_no_args) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); mm->add_instruction(migraphx::make_op("identity"), l0); auto prog = optimize_onnx("clip_test_op11_no_args.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/clip_test_op11_no_args1.cpp000066400000000000000000000031471510465702400247430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(clip_test_op11_no_args1) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); mm->add_instruction(migraphx::make_op("undefined")); auto r = mm->add_instruction(migraphx::make_op("identity"), l0); mm->add_return({r}); auto prog = read_onnx("clip_test_op11_no_args1.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/concat_dyn_test.cpp000066400000000000000000000034501510465702400235010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(concat_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {1, 4}, {3, 3}}}); auto l1 = mm->add_parameter( "1", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {1, 4}, {3, 3}}}); auto ret = mm->add_instruction(migraphx::make_op("concat"), l0, l1); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("concat_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/concat_test.cpp000066400000000000000000000031571510465702400226330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(concat_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 4, 3}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {7, 4, 3}}); mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), l0, l1); auto prog = optimize_onnx("concat_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/const_of_shape_default_test.cpp000066400000000000000000000033201510465702400260520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(const_of_shape_default_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape output_dims_shape(migraphx::shape::int64_type, {3}); mm->add_literal(migraphx::literal(output_dims_shape, {2, 3, 4})); migraphx::shape output_shape{migraphx::shape::float_type, {2, 3, 4}}; std::vector vec(output_shape.elements(), 0.0); mm->add_literal(migraphx::literal(output_shape, vec)); auto prog = optimize_onnx("const_of_shape_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/const_of_shape_dyn_float_test.cpp000066400000000000000000000036551510465702400264200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(const_of_shape_dyn_float_test) { migraphx::program p; auto* mm = p.get_main_module(); auto od_param = mm->add_parameter("output_dims", migraphx::shape{migraphx::shape::int64_type, {3}}); auto alloc_ins = mm->add_instruction( migraphx::make_op("allocate", {{"buf_type", migraphx::shape::float_type}}), od_param); migraphx::shape dv_shape(migraphx::shape::float_type, {1}, {0}); auto dv_lit = mm->add_literal(migraphx::literal(dv_shape, {10})); auto fill_ins = mm->add_instruction(migraphx::make_op("fill"), dv_lit, alloc_ins); mm->add_return({fill_ins}); migraphx::onnx_options options; auto prog = read_onnx("const_of_shape_dyn_float_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/const_of_shape_dyn_int64_test.cpp000066400000000000000000000036551510465702400262570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(const_of_shape_dyn_int64_test) { migraphx::program p; auto* mm = p.get_main_module(); auto od_param = mm->add_parameter("output_dims", migraphx::shape{migraphx::shape::int64_type, {3}}); auto alloc_ins = mm->add_instruction( migraphx::make_op("allocate", {{"buf_type", migraphx::shape::int64_type}}), od_param); migraphx::shape dv_shape(migraphx::shape::int64_type, {1}, {0}); auto dv_lit = mm->add_literal(migraphx::literal(dv_shape, {10})); auto fill_ins = mm->add_instruction(migraphx::make_op("fill"), dv_lit, alloc_ins); mm->add_return({fill_ins}); migraphx::onnx_options options; auto prog = read_onnx("const_of_shape_dyn_int64_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/const_of_shape_empty_input_test.cpp000066400000000000000000000031551510465702400270110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(const_of_shape_empty_input_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal(migraphx::shape::int64_type)); migraphx::shape s(migraphx::shape::int64_type, {1}, {0}); std::vector vec(s.elements(), 10); mm->add_literal(migraphx::literal(s, vec)); auto prog = optimize_onnx("const_of_shape_empty_input_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/const_of_shape_float_test.cpp000066400000000000000000000032171510465702400255400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(const_of_shape_float_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ss(migraphx::shape::int64_type, {3}); mm->add_literal(migraphx::literal(ss, {2, 3, 4})); migraphx::shape s(migraphx::shape::float_type, {2, 3, 4}); std::vector vec(s.elements(), 10.0f); mm->add_literal(migraphx::literal(s, vec)); auto prog = optimize_onnx("const_of_shape_float_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/const_of_shape_int64_test.cpp000066400000000000000000000032771510465702400254050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(const_of_shape_int64_test) { migraphx::program p; auto* mm = p.get_main_module(); // output_dims migraphx::shape ss(migraphx::shape::int64_type, {3}); mm->add_literal(migraphx::literal(ss, {2, 3, 4})); // constant shape literal migraphx::shape s(migraphx::shape::int64_type, {2, 3, 4}); std::vector vec(s.elements(), 10); mm->add_literal(migraphx::literal(s, vec)); auto prog = optimize_onnx("const_of_shape_int64_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/const_of_shape_no_value_attr_test.cpp000066400000000000000000000032361510465702400272760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(const_of_shape_no_value_attr_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ss(migraphx::shape::int64_type, {3}); mm->add_literal(migraphx::literal(ss, {2, 3, 4})); migraphx::shape s(migraphx::shape::float_type, {2, 3, 4}); std::vector vec(s.elements(), 0.0f); mm->add_literal(migraphx::literal(s, vec)); auto prog = optimize_onnx("const_of_shape_no_value_attr_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_empty_scalar_int64_test.cpp000066400000000000000000000027211510465702400270000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_empty_scalar_int64_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape::int64_type}); auto prog = optimize_onnx("constant_empty_scalar_int64_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_fill_input_as_shape_test.cpp000066400000000000000000000034671510465702400273110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_fill_input_as_shape_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(migraphx::literal{{migraphx::shape::int32_type, {2}}, {2, 3}}); std::vector dims(l0->get_shape().elements()); migraphx::literal ls = l0->get_literal(); ls.visit([&](auto s) { dims.assign(s.begin(), s.end()); }); migraphx::shape s{migraphx::shape::float_type, dims}; std::vector value(s.elements(), 1.0); mm->add_literal(migraphx::literal{s, value}); auto prog = optimize_onnx("constant_fill_input_as_shape_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_fill_test.cpp000066400000000000000000000030171510465702400242160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_fill_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector value(s.elements(), 1.0); mm->add_literal(migraphx::literal{s, value}); auto prog = optimize_onnx("constant_fill_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_multiple_attributes_test.cpp000066400000000000000000000025141510465702400273720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_multiple_attributes_test) { EXPECT(test::throws([&] { optimize_onnx("constant_multiple_attributes_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_no_attributes_test.cpp000066400000000000000000000025001510465702400261460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_no_attributes_test) { EXPECT(test::throws([&] { optimize_onnx("constant_no_attributes_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_one_val_int64_test.cpp000066400000000000000000000027421510465702400257430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_one_val_int64_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {1}}); auto prog = optimize_onnx("constant_one_val_int64_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_scalar_test.cpp000066400000000000000000000027241510465702400245410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_scalar_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int32_type, {1}}, {1}}); auto prog = optimize_onnx("constant_scalar_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_test.cpp000066400000000000000000000027251510465702400232150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {3}}, {0, 1, 2}}); auto prog = optimize_onnx("constant_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_value_float_test.cpp000066400000000000000000000027411510465702400255740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_value_float_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type, {1}}, {1.0f}}); auto prog = optimize_onnx("constant_value_float_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_value_floats_test.cpp000066400000000000000000000027701510465702400257610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_value_floats_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {3}}, {1.0f, 2.0f, 3.0f}}); auto prog = optimize_onnx("constant_value_floats_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_value_int_test.cpp000066400000000000000000000027321510465702400252610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_value_int_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {1}}); auto prog = optimize_onnx("constant_value_int_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/constant_value_ints_test.cpp000066400000000000000000000027531510465702400254470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(constant_value_ints_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {3}}, {1, 2, 3}}); auto prog = optimize_onnx("constant_value_ints_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_1d_fp8_test.cpp000066400000000000000000000032561510465702400234720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_1d_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::fp8e4m3fnuz_type, {1, 3, 5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::fp8e4m3fnuz_type, {1, 3, 3}}); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), l0, l1); auto prog = optimize_onnx("conv_1d_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_1d_test.cpp000066400000000000000000000032321510465702400227070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_1d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 3}}); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), l0, l1); auto prog = optimize_onnx("conv_1d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_3d_test.cpp000066400000000000000000000033221510465702400227110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_3d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 5, 5, 5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 3, 3, 3}}); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"dilation", {1, 1, 1}}}), l0, l1); auto prog = optimize_onnx("conv_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_attr_fail_test.cpp000066400000000000000000000024541510465702400243550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_attr_fail_test) { EXPECT(test::throws([&] { read_onnx("conv_attr_fail_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_autopad_fail_test.cpp000066400000000000000000000024661510465702400250430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_autopad_fail_test) { EXPECT(test::throws([&] { optimize_onnx("conv_autopad_fail_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_autopad_same_test.cpp000066400000000000000000000032571510465702400250540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(conv_autopad_same_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 32, 32}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 3, 3}}); migraphx::op::convolution op; op.padding = {1, 1, 1, 1}; mm->add_instruction(op, l0, l1); auto prog = optimize_onnx("conv_autopad_same_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_bad_bias_test.cpp000066400000000000000000000024521510465702400241320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_bad_bias_test) { EXPECT(test::throws([&] { read_onnx("conv_bad_bias_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_bias_test.cpp000066400000000000000000000036441510465702400233300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_bias_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 32, 32}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 5, 5}}); auto l2 = mm->add_parameter("2", {migraphx::shape::float_type, {1}}); uint64_t axis = 1; auto l3 = mm->add_instruction(migraphx::make_op("convolution"), l0, l1); auto l4 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l3->get_shape().lens()}}), l2); mm->add_instruction(migraphx::make_op("add"), l3, l4); auto prog = optimize_onnx("conv_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_bn_relu_maxpool_test.cpp000066400000000000000000000110461510465702400255720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include static migraphx::program create_conv_bn_relu_maxpool(bool has_bias_value = true) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 32, 32}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 5, 5}}); auto l2 = mm->add_parameter("2", {migraphx::shape::float_type, {1}}); auto p3 = mm->add_parameter("3", {migraphx::shape::float_type, {1}}); auto p4 = mm->add_parameter("4", {migraphx::shape::float_type, {1}}); auto p5 = mm->add_parameter("5", {migraphx::shape::float_type, {1}}); auto p6 = mm->add_parameter("6", {migraphx::shape::float_type, {1}}); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {1e-5f}}); uint64_t axis = 1; auto no_bias = has_bias_value ? migraphx::instruction_ref() : mm->add_instruction(migraphx::make_op("undefined")); auto l3 = mm->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}}), l0, l1); auto l4 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l3->get_shape().lens()}}), has_bias_value ? l2 : no_bias); auto l5 = mm->add_instruction(migraphx::make_op("add"), l3, l4); auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), p3); auto usq_bias = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), p4); auto usq_mean = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), p5); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), p6); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {l5, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), var_eps); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {usq_scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); auto l6 = add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); auto l7 = mm->add_instruction(migraphx::make_op("relu"), l6); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 0, 0}}, {"stride", {2, 2}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}}), l7); return p; } TEST_CASE(conv_bn_relu_maxpool_test) { migraphx::program p = create_conv_bn_relu_maxpool(); auto prog = optimize_onnx("conv_bn_relu_maxpool_test.onnx"); EXPECT(p == prog); } TEST_CASE(conv_bn_relu_maxpool_unordered_test) { migraphx::program p = create_conv_bn_relu_maxpool(); auto prog = optimize_onnx("conv_bn_relu_maxpool_unordered_test.onnx"); EXPECT(p == prog); } TEST_CASE(conv_bn_relu_maxpool_unordered_nonvalue_optional_ios_test) { migraphx::program p = create_conv_bn_relu_maxpool(false); auto prog = optimize_onnx("conv_bn_relu_maxpool_unordered_nonvalue_optional_ios_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_dynamic_batch_same_upper_test.cpp000066400000000000000000000036061510465702400274150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_dynamic_batch_same_upper) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {{1, 10}, {3, 3}, {5, 5}, {5, 5}}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 3, 3}}); auto c0 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), l0, l1); mm->add_return({c0}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; auto prog = read_onnx("conv_dynamic_batch_same_upper_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_dynamic_batch_test.cpp000066400000000000000000000035551510465702400252000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_dynamic_batch_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {{1, 6}, {3, 3}, {5, 5}, {5, 5}}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 3, 3}}); auto c0 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), l0, l1); mm->add_return({c0}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 6}; auto prog = read_onnx("conv_dynamic_batch_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_dynamic_bias_test.cpp000066400000000000000000000037671510465702400250420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_dynamic_bias_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x0 = mm->add_parameter("0", {migraphx::shape::float_type, {{1, 6}, {3, 3}, {32, 32}, {32, 32}}}); auto x1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 5, 5}}); auto x2 = mm->add_parameter("2", {migraphx::shape::float_type, {1}}); auto x3 = mm->add_instruction(migraphx::make_op("convolution"), x0, x1); auto x4 = mm->add_instruction(migraphx::make_op("broadcast", {{"axis", 1}}), x2, x3); auto x5 = mm->add_instruction(migraphx::make_op("add"), x3, x4); mm->add_return({x5}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 6}; auto prog = read_onnx("conv_dynamic_bias_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_dynamic_img_and_weights_test.cpp000066400000000000000000000037521510465702400272460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_dynamic_img_and_weights_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {{1, 1}, {3, 3}, {5, 10}, {5, 10}}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 4}, {2, 4}}}); auto c0 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), l0, l1); mm->add_return({c0}); migraphx::onnx_options options; options.default_dyn_dim_value = {5, 10}; options.map_dyn_input_dims["1"] = {{1, 1}, {3, 3}, {2, 4}, {2, 4}}; auto prog = read_onnx("conv_dynamic_img_and_weights_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_dynamic_img_same_upper_test.cpp000066400000000000000000000040541510465702400271060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(conv_dynamic_img_same_upper) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {{1, 1}, {3, 3}, {5, 10}, {5, 10}}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 3, 3}}); auto c0 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::same_upper}}), l0, l1); mm->add_return({c0}); migraphx::onnx_options options; options.default_dyn_dim_value = {5, 10}; auto prog = read_onnx("conv_dynamic_img_same_upper_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_dynamic_img_test.cpp000066400000000000000000000035541510465702400246720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_dynamic_img_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {{1, 1}, {3, 3}, {5, 10}, {5, 10}}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 3, 3}}); auto c0 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), l0, l1); mm->add_return({c0}); migraphx::onnx_options options; options.default_dyn_dim_value = {5, 10}; auto prog = read_onnx("conv_dynamic_img_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_dynamic_kernel_same_lower_test.cpp000066400000000000000000000041031510465702400276020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(conv_dynamic_kernel_same_lower) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 5, 5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 4}, {2, 4}}}); auto c0 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::same_lower}}), l0, l1); mm->add_return({c0}); migraphx::onnx_options options; options.default_dyn_dim_value = {2, 4}; auto prog = read_onnx("conv_dynamic_kernel_same_lower_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_dynamic_weights_test.cpp000066400000000000000000000035621510465702400255670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_dynamic_weights_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 5, 5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 4}, {2, 4}}}); auto c0 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), l0, l1); mm->add_return({c0}); migraphx::onnx_options options; options.default_dyn_dim_value = {2, 4}; auto prog = read_onnx("conv_dynamic_weights_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_relu_maxpool_test.cpp000066400000000000000000000047551510465702400251240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(conv_relu_maxpool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 32, 32}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1, 3, 5, 5}}); auto l2 = mm->add_parameter("2", {migraphx::shape::float_type, {1}}); uint64_t axis = 1; auto l3 = mm->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}}), l0, l1); auto l4 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l3->get_shape().lens()}}), l2); auto l5 = mm->add_instruction(migraphx::make_op("add"), l3, l4); auto l6 = mm->add_instruction(migraphx::make_op("relu"), l5); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 0, 0}}, {"stride", {2, 2}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}}), l6); auto prog = optimize_onnx("conv_relu_maxpool_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_relu_maxpool_x2_test.cpp000066400000000000000000000070331510465702400255250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(conv_relu_maxpool_x2_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {1, 3, 32, 32}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {5, 3, 5, 5}}); auto l2 = mm->add_parameter("2", {migraphx::shape::float_type, {5}}); uint64_t axis = 1; auto l3 = mm->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}}), l0, l1); auto l4 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l3->get_shape().lens()}}), l2); auto l5 = mm->add_instruction(migraphx::make_op("add"), l3, l4); auto l6 = mm->add_instruction(migraphx::make_op("relu"), l5); auto l7 = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 0, 0}}, {"stride", {2, 2}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}}), l6); auto l8 = mm->add_parameter("3", {migraphx::shape::float_type, {1, 5, 5, 5}}); auto l9 = mm->add_parameter("4", {migraphx::shape::float_type, {1}}); auto l10 = mm->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}}), l7, l8); auto l11 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l10->get_shape().lens()}}), l9); auto l12 = mm->add_instruction(migraphx::make_op("add"), l10, l11); auto l13 = mm->add_instruction(migraphx::make_op("relu"), l12); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 0, 0}}, {"stride", {2, 2}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}}), l13); auto prog = optimize_onnx("conv_relu_maxpool_x2_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_auto_pad_test.cpp000066400000000000000000000024771510465702400263070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_auto_pad_error) { EXPECT(test::throws([&] { read_onnx("conv_transpose_auto_pad_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_bias_test.cpp000066400000000000000000000037001510465702400254170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_bias_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto l2 = mm->add_parameter("b", {migraphx::shape::float_type, {1}}); uint64_t axis = 1; auto l3 = mm->add_instruction(migraphx::make_op("convolution_backwards"), l0, l1); auto l4 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l3->get_shape().lens()}}), l2); mm->add_instruction(migraphx::make_op("add"), l3, l4); auto prog = optimize_onnx("conv_transpose_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_dyn_asym_padding_test.cpp000066400000000000000000000026501510465702400300150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_dyn_asym_padding_error) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT(test::throws([&] { read_onnx("conv_transpose_dyn_asym_padding_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_dyn_batch_test.cpp000066400000000000000000000034451510465702400264420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_dyn_batch_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {{1, 4}, {1, 1}, {3, 3}, {3, 3}}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto ret = mm->add_instruction(migraphx::make_op("convolution_backwards"), l0, l1); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("conv_transpose_dyn_batch_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_dyn_img_test.cpp000066400000000000000000000034411510465702400261310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_dyn_img_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {{1, 1}, {1, 1}, {3, 6}, {3, 6}}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto ret = mm->add_instruction(migraphx::make_op("convolution_backwards"), l0, l1); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {3, 6}; auto prog = read_onnx("conv_transpose_dyn_img_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_dyn_output_shape_test.cpp000066400000000000000000000026501510465702400300760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_dyn_output_shape_error) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT(test::throws([&] { read_onnx("conv_transpose_dyn_output_shape_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_input_pads_asymm_1d_test.cpp000066400000000000000000000036021510465702400304420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_input_pads_asymm_1d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 2, 3}}); auto l2 = mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0}}, {"stride", {2}}, {"dilation", {1}}}), l0, l1); mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {6}}}), l2); auto prog = optimize_onnx("conv_transpose_input_pads_asymm_1d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_input_pads_asymm_test.cpp000066400000000000000000000035241510465702400300610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_input_pads_asymm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 2, 3, 3}}); auto l2 = mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0, 0}}, {"stride", {3, 2}}}), l0, l1); mm->add_instruction( migraphx::make_op("slice", {{"axes", {2, 3}}, {"starts", {0, 0}}, {"ends", {8, 6}}}), l2); auto prog = optimize_onnx("conv_transpose_input_pads_asymm_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_input_pads_strides_test.cpp000066400000000000000000000033211510465702400304030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_input_pads_strides_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 2, 3, 3}}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {1, 1}}, {"stride", {3, 2}}}), l0, l1); auto prog = optimize_onnx("conv_transpose_input_pads_strides_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_output_padding_3d_test.cpp000066400000000000000000000035751510465702400301270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_output_padding_3d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3, 3, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 2, 3, 3, 3}}); auto l2 = mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0, 0, 0}}, {"stride", {3, 2, 2}}, {"dilation", {1, 1, 1}}}), l0, l1); mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 0, 0, 0, 0, 1, 1, 1}}}), l2); auto prog = optimize_onnx("conv_transpose_output_padding_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_output_padding_test.cpp000066400000000000000000000034611510465702400275330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_output_padding_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 2, 3, 3}}); auto l2 = mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0, 0}}, {"stride", {3, 2}}}), l0, l1); mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 0, 0, 0, 1, 1}}}), l2); auto prog = optimize_onnx("conv_transpose_output_padding_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_output_shape_3d_test.cpp000066400000000000000000000035711510465702400276150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_output_shape_3d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3, 3, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 2, 3, 3, 3}}); auto l2 = mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0, 0, 0}}, {"stride", {3, 2, 2}}, {"dilation", {1, 1, 1}}}), l0, l1); mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 0, 0, 0, 0, 1, 1, 1}}}), l2); auto prog = optimize_onnx("conv_transpose_output_shape_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_output_shape_test.cpp000066400000000000000000000034551510465702400272300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_output_shape_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 2, 3, 3}}); auto l2 = mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0, 0}}, {"stride", {3, 2}}}), l0, l1); mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 0, 0, 0, 1, 1}}}), l2); auto prog = optimize_onnx("conv_transpose_output_shape_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/conv_transpose_test.cpp000066400000000000000000000031471510465702400244260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(conv_transpose_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 3, 3}}); auto l1 = mm->add_parameter("w", {migraphx::shape::float_type, {1, 1, 3, 3}}); mm->add_instruction(migraphx::make_op("convolution_backwards"), l0, l1); auto prog = optimize_onnx("conv_transpose_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/convinteger_bad_data_bias_type.cpp000066400000000000000000000025421510465702400265030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(convinteger_bad_data_bias_type_test) { EXPECT( test::throws([&] { migraphx::parse_onnx("convinteger_mismatched_data_bias_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/convinteger_bad_weight_bias_type.cpp000066400000000000000000000025461510465702400270650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(convinteger_bad_weight_bias_type_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("convinteger_mismatched_weight_bias_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/convinteger_bias_test.cpp000066400000000000000000000042161510465702400247020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(convinteger_bias_test) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("0", {migraphx::shape::int8_type, {2, 3, 32, 32}}); auto weights = mm->add_parameter("1", {migraphx::shape::int8_type, {4, 3, 5, 5}}); auto data_bias = mm->add_parameter("2", {migraphx::shape::int8_type, {1}, {1}}); mm->add_literal(migraphx::literal{migraphx::shape{data->get_shape().type(), {1}, {0}}, {0}}); auto quant = mm->add_instruction(migraphx::make_op("quant_convolution"), data, weights); auto bcast_data_bias = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", data->get_shape().lens()}}), data_bias); auto quant2 = mm->add_instruction(migraphx::make_op("quant_convolution"), bcast_data_bias, weights); mm->add_instruction(migraphx::make_op("sub"), quant, quant2); auto prog = optimize_onnx("convinteger_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/convinteger_dual_bias_test.cpp000066400000000000000000000060051510465702400257050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(convinteger_dual_bias_test) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("0", {migraphx::shape::int8_type, {2, 3, 10, 10}}); auto weight = mm->add_parameter("1", {migraphx::shape::int8_type, {4, 3, 3, 3}}); auto data_bias = mm->add_parameter("2", {migraphx::shape::int8_type, {1}, {1}}); auto weight_bias = mm->add_parameter("3", {migraphx::shape::int8_type, {1}, {1}}); auto quant = mm->add_instruction(migraphx::make_op("quant_convolution"), data, weight); auto mbcast_data_bias = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", data->get_shape().lens()}}), data_bias); auto quant_mb_w = mm->add_instruction(migraphx::make_op("quant_convolution"), mbcast_data_bias, weight); quant = mm->add_instruction(migraphx::make_op("sub"), quant, quant_mb_w); auto mbcast_weight_bias = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", weight->get_shape().lens()}}), weight_bias); auto quant_md_wb = mm->add_instruction(migraphx::make_op("quant_convolution"), data, mbcast_weight_bias); quant = mm->add_instruction(migraphx::make_op("sub"), quant, quant_md_wb); mbcast_data_bias = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", data->get_shape().lens()}}), data_bias); mbcast_weight_bias = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", weight->get_shape().lens()}}), weight_bias); auto bias_quant = mm->add_instruction( migraphx::make_op("quant_convolution"), mbcast_data_bias, mbcast_weight_bias); mm->add_instruction(migraphx::make_op("add"), quant, bias_quant); auto prog = optimize_onnx("convinteger_dual_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/convinteger_no_bias.cpp000066400000000000000000000034721510465702400243420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(convinteger_no_bias) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("0", {migraphx::shape::int8_type, {1, 3, 5, 5}}); auto weight = mm->add_parameter("1", {migraphx::shape::int8_type, {1, 3, 2, 2}}); mm->add_literal(migraphx::literal{migraphx::shape{data->get_shape().type(), {1}, {0}}, {0}}); mm->add_literal(migraphx::literal{migraphx::shape{data->get_shape().type(), {1}, {0}}, {0}}); mm->add_instruction(migraphx::make_op("quant_convolution"), data, weight); auto prog = optimize_onnx("convinteger_no_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/convinteger_no_bias_mismatched_inputs.cpp000066400000000000000000000051211510465702400301330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(convinteger_no_bias_mismatched_data_inputs_test) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("0", {migraphx::shape::int8_type, {1, 3, 32, 32}}); auto weight = mm->add_parameter("1", {migraphx::shape::uint8_type, {1, 3, 5, 5}}); mm->add_literal(migraphx::literal{migraphx::shape{data->get_shape().type(), {1}, {0}}, {0}}); mm->add_literal( migraphx::literal{migraphx::shape{weight->get_shape().type(), {1}, {0}}, {128}}); // shift uint8 input auto int8_shift2 = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {-128}}); // shift uint8 input auto unshifted_input_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), weight); auto mbr2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", weight->get_shape().lens()}}), int8_shift2); auto input_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_input_half, mbr2); weight = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); mm->add_instruction(migraphx::make_op("quant_convolution"), data, weight); auto prog = optimize_onnx("convinteger_mismatched_input_types_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/convinteger_no_bias_uint8.cpp000066400000000000000000000061251510465702400254670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(convinteger_no_bias_uint8) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("0", {migraphx::shape::uint8_type, {1, 3, 32, 32}}); auto weights = mm->add_parameter("1", {migraphx::shape::uint8_type, {1, 3, 5, 5}}); mm->add_literal(migraphx::literal{migraphx::shape{data->get_shape().type(), {1}, {0}}, {128}}); mm->add_literal(migraphx::literal{migraphx::shape{data->get_shape().type(), {1}, {0}}, {128}}); // Shift uint8 input auto int8_shift2 = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {-128}}); // Shift uint8 input auto unshifted_input_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), data); auto mbr2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 3, 32, 32}}}), int8_shift2); auto input_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_input_half, mbr2); data = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); // Shift uint8 weights auto unshifted_weights_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), weights); auto mbr3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 3, 5, 5}}}), int8_shift2); auto weights_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_weights_half, mbr3); weights = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), weights_shifted_half); mm->add_instruction(migraphx::make_op("quant_convolution"), data, weights); auto prog = optimize_onnx("convinteger_no_bias_uint8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/cos_fp8_test.cpp000066400000000000000000000030031510465702400227130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(cos_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {10}}); mm->add_instruction(migraphx::make_op("cos"), input); auto prog = optimize_onnx("cos_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/cos_test.cpp000066400000000000000000000027651510465702400221540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(cos_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("cos"), input); auto prog = optimize_onnx("cos_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/cosh_test.cpp000066400000000000000000000027701510465702400223200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(cosh_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1}}); mm->add_instruction(migraphx::make_op("cosh"), input); auto prog = optimize_onnx("cosh_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/depthtospace_crd_test.cpp000066400000000000000000000034141510465702400246730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(depthtospace_crd_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {2, 8, 5, 5}}); auto tmp1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), l0); auto tmp2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 4, 2, 5, 3}}}), tmp1); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 10, 10}}}), tmp2); auto prog = optimize_onnx("depthtospace_crd_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/depthtospace_simple_test.cpp000066400000000000000000000034201510465702400254110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(depthtospace_simple_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 8, 2, 3}}); auto tmp1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 2, 2, 2, 3}}}), l0); auto tmp2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), tmp1); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 4, 6}}}), tmp2); auto prog = optimize_onnx("depthtospace_simple_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/depthtospace_test.cpp000066400000000000000000000034041510465702400240420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(depthtospace_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {2, 8, 5, 5}}); auto tmp1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), l0); auto tmp2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), tmp1); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 10, 10}}}), tmp2); auto prog = optimize_onnx("depthtospace_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dequantizelinear_2d_blocked_with_zp_test.cpp000066400000000000000000000034541510465702400305440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/make_op.hpp" #include TEST_CASE(dequantizelinear_2d_blocked_with_zp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::int8_type, {2, 2}}); auto scale = mm->add_parameter("scale", migraphx::shape{migraphx::shape::float_type, {2, 2}}); auto zp = mm->add_parameter("zp", migraphx::shape{migraphx::shape::int8_type, {2, 2}}); mm->add_instruction(migraphx::make_op("dequantizelinear"), x, scale, zp); auto prog = optimize_onnx("dequantizelinear_2d_blocked_with_zp_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dequantizelinear_3d_blocked_with_zp_runt_block_test.cpp000066400000000000000000000052241510465702400327640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/make_op.hpp" #include TEST_CASE(dequantizelinear_3d_blocked_with_zp_runt_block_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::int8_type, {2, 5, 2}}); auto scale = mm->add_parameter("scale", migraphx::shape{migraphx::shape::float_type, {2, 2, 2}}); auto zp = mm->add_parameter("zp", migraphx::shape{migraphx::shape::int8_type, {2, 2, 2}}); scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scale); scale = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), scale); scale = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), scale); scale = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), scale); zp = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), zp); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), zp); zp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), zp); zp = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), zp); mm->add_instruction(migraphx::make_op("dequantizelinear"), x, scale, zp); auto prog = optimize_onnx("dequantizelinear_3d_blocked_with_zp_runt_block_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dequantizelinear_axis_test.cpp000066400000000000000000000026631510465702400257550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(dequantizelinear_axis_test) { migraphx::program p = make_dequantizelinear_axis_prog(); auto prog = optimize_onnx("dequantizelinear_axis_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dequantizelinear_neg_axis_test.cpp000066400000000000000000000026731510465702400266070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(dequantizelinear_neg_axis_test) { migraphx::program p = make_dequantizelinear_axis_prog(); auto prog = optimize_onnx("dequantizelinear_neg_axis_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dequantizelinear_negative_tests.cpp000066400000000000000000000034631510465702400267750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(dequantizelinear_too_few_inputs_test) { EXPECT(test::throws([&] { read_onnx("dequantizelinear_too_few_inputs_test.onnx"); })); } TEST_CASE(dequantizelinear_too_many_inputs_test) { EXPECT(test::throws([&] { read_onnx("dequantizelinear_too_many_inputs_test.onnx"); })); } TEST_CASE(dequantizelinear_x_and_zp_type_mismatch_test) { EXPECT(test::throws([&] { read_onnx("dequantizelinear_x_and_zp_type_mismatch_test.onnx"); })); } TEST_CASE(dequantizelinear_scale_and_zp_shape_mismatch_test) { EXPECT( test::throws([&] { read_onnx("dequantizelinear_scale_and_zp_shape_mismatch_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dequantizelinear_test.cpp000066400000000000000000000036171510465702400247310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(dequantizelinear_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::int8_type, {5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1}}); auto l1_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), l1); auto dequant = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l0); mm->add_instruction(migraphx::make_op("mul"), dequant, l1_mbcast); auto prog = optimize_onnx("dequantizelinear_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dequantizelinear_zero_point_test.cpp000066400000000000000000000045341510465702400272000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(dequantizelinear_zero_point_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::int8_type, {5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1}}); auto l2 = mm->add_parameter("2", {migraphx::shape::int8_type, {1}}); auto l1_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), l1); auto l2_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), l2); l2_mbcast = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l2_mbcast); l0 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l0); auto sub = mm->add_instruction(migraphx::make_op("sub"), l0, l2_mbcast); mm->add_instruction(migraphx::make_op("mul"), sub, l1_mbcast); auto prog = optimize_onnx("dequantizelinear_zero_point_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dim_param_test.cpp000066400000000000000000000045521510465702400233150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(dim_param_fixed_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 4}}); mm->add_return({input}); migraphx::onnx_options opt; opt.dim_params = {{"dim0", migraphx::shape::dynamic_dimension{2, 2}}, {"dim1", migraphx::shape::dynamic_dimension{4, 4}}}; auto prog = read_onnx("dim_param_test.onnx", opt); EXPECT(p == prog); } TEST_CASE(dim_param_dynamic_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {migraphx::shape::dynamic_dimension{1, 2}, migraphx::shape::dynamic_dimension{2, 4}}}); mm->add_return({input}); migraphx::onnx_options opt; opt.dim_params = {{"dim0", migraphx::shape::dynamic_dimension{1, 2}}, {"dim1", migraphx::shape::dynamic_dimension{2, 4}}}; auto prog = read_onnx("dim_param_test.onnx", opt); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/div_fp8_test.cpp000066400000000000000000000031471510465702400227220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(div_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto p0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {2, 3}}); auto p1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {2, 3}}); mm->add_instruction(migraphx::make_op("div"), p0, p1); auto prog = optimize_onnx("div_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dropout_test.cpp000066400000000000000000000033151510465702400230540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(dropout_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 2, 2}}); auto out = mm->add_instruction(migraphx::make_op("identity"), input); migraphx::shape s{migraphx::shape::bool_type, {1, 3, 2, 2}}; std::vector vec(s.elements(), 1); mm->add_literal(migraphx::literal(s, vec)); mm->add_return({out}); auto prog = read_onnx("dropout_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dynamicquantizelinear_2d_test.cpp000066400000000000000000000070061510465702400263460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(dynamicquantizelinear_2d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x_dims = {3, 4}; auto x_type = migraphx::shape::float_type; auto x = mm->add_parameter("x", {x_type, x_dims}); auto l0 = mm->add_literal({0.f}); std::vector axes(x->get_shape().lens().size()); std::iota(axes.begin(), axes.end(), 0); auto reduce_max_x = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", axes}}), x); auto max_x = add_common_op(*mm, migraphx::make_op("max"), {l0, reduce_max_x}); auto reduce_min_x = mm->add_instruction(migraphx::make_op("reduce_min", {{"axes", axes}}), x); auto min_x = add_common_op(*mm, migraphx::make_op("min"), {l0, reduce_min_x}); auto q_range = mm->add_literal(migraphx::literal{ migraphx::shape{x_type, max_x->get_shape().lens()}, {std::numeric_limits::max() - std::numeric_limits::min()}}); auto q_min = mm->add_literal(migraphx::literal{ migraphx::shape{x_type, max_x->get_shape().lens()}, {std::numeric_limits::min()}}); auto q_max = mm->add_literal(migraphx::literal{ migraphx::shape{x_type, min_x->get_shape().lens()}, {std::numeric_limits::max()}}); auto sub0 = mm->add_instruction(migraphx::make_op("sub"), max_x, min_x); auto y_scale = mm->add_instruction(migraphx::make_op("div"), sub0, q_range); auto div1 = add_common_op(*mm, migraphx::make_op("div"), {min_x, y_scale}); auto interm_zp = add_common_op(*mm, migraphx::make_op("sub"), {q_min, div1}); auto saturate = mm->add_instruction(migraphx::make_op("clip"), interm_zp, q_min, q_max); auto round = mm->add_instruction(migraphx::make_op("nearbyint"), saturate); auto y_zero_point = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::uint8_type}}), round); auto scale_y_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", x_dims}}), y_scale); auto y_pt_c_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x_dims}}), y_zero_point); mm->add_instruction(migraphx::make_op("quantizelinear"), x, scale_y_bcast, y_pt_c_bcast); auto prog = optimize_onnx("dynamicquantizelinear_2d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/dynamicscale_test.cpp000066400000000000000000000106741510465702400240220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(dynamicscale_even_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("input", migraphx::shape{migraphx::shape::float_type, {3, 64, 4, 4}}); auto reduce_reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 2, 32, 4, 4}}}), input); auto abs_ins = mm->add_instruction(migraphx::make_op("abs"), reduce_reshape); auto reduce_max_ins = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2}}}), abs_ins); auto log2_ins = mm->add_instruction(migraphx::make_op("log2"), reduce_max_ins); auto floor_ins = mm->add_instruction(migraphx::make_op("floor"), log2_ins); auto lit_2_ins = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {2.f}}); auto broadcast_lit_2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_2_ins); auto pow_ins = mm->add_instruction(migraphx::make_op("pow"), broadcast_lit_2, floor_ins); auto lit_4_ins = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {4.f}}); auto broadcast_lit_4 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_4_ins); auto block_scales_ins = mm->add_instruction(migraphx::make_op("div"), pow_ins, broadcast_lit_4); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), block_scales_ins); auto prog = optimize_onnx("dynamicscale_even_test.onnx"); EXPECT(p == prog); } TEST_CASE(dynamicscale_odd_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("input", migraphx::shape{migraphx::shape::float_type, {71, 5, 5}}); auto padded_input = mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 25, 0, 0}}}), input); auto reduce_reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 32, 5, 5}}}), padded_input); auto abs_ins = mm->add_instruction(migraphx::make_op("abs"), reduce_reshape); auto reduce_max_ins = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), abs_ins); auto log2_ins = mm->add_instruction(migraphx::make_op("log2"), reduce_max_ins); auto floor_ins = mm->add_instruction(migraphx::make_op("floor"), log2_ins); auto lit_2_ins = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {2.f}}); auto broadcast_lit_2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_2_ins); auto pow_ins = mm->add_instruction(migraphx::make_op("pow"), broadcast_lit_2, floor_ins); auto lit_4_ins = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {4.f}}); auto broadcast_lit_4 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_4_ins); auto block_scales_ins = mm->add_instruction(migraphx::make_op("div"), pow_ins, broadcast_lit_4); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), block_scales_ins); auto prog = optimize_onnx("dynamicscale_odd_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/einsum_negative_tests.cpp000066400000000000000000000066301510465702400247300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(einsum_missing_equation_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_missing_equation_negative_test.onnx"); })); } TEST_CASE(einsum_multiple_arrows_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_multiple_arrows_negative_test.onnx"); })); } TEST_CASE(einsum_empty_term_before_arrow_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_empty_term_before_arrow_negative_test.onnx"); })); } TEST_CASE(einsum_multiple_ellipses_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_multiple_ellipses_negative_test.onnx"); })); } TEST_CASE(einsum_comma_in_output_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_comma_in_output_negative_test.onnx"); })); } TEST_CASE(einsum_empty_term_before_comma_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_empty_term_before_comma_negative_test.onnx"); })); } TEST_CASE(einsum_last_input_missing_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_last_input_missing_negative_test.onnx"); })); } TEST_CASE(einsum_term_input_mismatch_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_term_input_mismatch_negative_test.onnx"); })); } TEST_CASE(einsum_ellipsis_mismatch_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_ellipsis_mismatch_negative_test.onnx"); })); } TEST_CASE(einsum_rank_mismatch_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_rank_mismatch_negative_test.onnx"); })); } TEST_CASE(einsum_output_surplus_label_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_output_surplus_label_negative_test.onnx"); })); } TEST_CASE(einsum_output_missing_ellipsis_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_output_missing_ellipsis_negative_test.onnx"); })); } TEST_CASE(einsum_multiple_diagonals_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_multiple_diagonals_negative_test.onnx"); })); } TEST_CASE(einsum_diagonal_dim_mismatch_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_diagonal_dim_mismatch_negative_test.onnx"); })); } TEST_CASE(einsum_right_batch_diagonal_negative_test) { EXPECT(test::throws([&] { read_onnx("einsum_right_batch_diagonal_negative_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/elu_test.cpp000066400000000000000000000030101510465702400221350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(elu_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); mm->add_instruction(migraphx::make_op("elu", {{"alpha", 0.01}}), input); auto prog = optimize_onnx("elu_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/embedding_bag_offset_test.cpp000066400000000000000000000024701510465702400254560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(embedding_bag_offset_test) { EXPECT(test::throws([&] { read_onnx("embedding_bag_offset_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/embedding_bag_test.cpp000066400000000000000000000041461510465702400241120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(embedding_bag_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("weight", migraphx::shape{migraphx::shape::float_type, {4, 2}}); migraphx::literal l{migraphx::shape{migraphx::shape::int32_type, {3}}, {1, 0, 2}}; auto l1 = mm->add_literal(l); mm->add_literal(0); auto l4 = mm->add_instruction(migraphx::make_op("gather"), l0, l1); auto r1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), l4); auto l5 = mm->add_instruction(migraphx::make_op("gather"), l0, l1); auto r2 = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0}}}), l5); auto l6 = mm->add_instruction(migraphx::make_op("gather"), l0, l1); auto r3 = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {0}}}), l6); mm->add_return({r1, r2, r3}); auto prog = read_onnx("embedding_bag_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/equal_bool_test.cpp000066400000000000000000000035451510465702400235070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(equal_bool_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sf{migraphx::shape::float_type, {2, 3}}; migraphx::shape sb{migraphx::shape::bool_type, {2, 3}}; auto input1 = mm->add_parameter("x1", sf); auto input2 = mm->add_parameter("x2", sb); auto cin1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), input1); auto ret = mm->add_instruction(migraphx::make_op("equal"), cin1, input2); mm->add_return({ret}); auto prog = read_onnx("equal_bool_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/equal_test.cpp000066400000000000000000000036461510465702400224760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(equal_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; auto input1 = mm->add_literal(migraphx::literal(s, data)); auto input2 = mm->add_parameter("x2", migraphx::shape{migraphx::shape::float_type, {2, 3}}); auto eq = mm->add_instruction(migraphx::make_op("equal"), input1, input2); auto ret = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), eq); mm->add_return({ret}); auto prog = read_onnx("equal_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/erf_test.cpp000066400000000000000000000027711510465702400221410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(erf_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10, 15}}); mm->add_instruction(migraphx::make_op("erf"), input); auto prog = optimize_onnx("erf_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/exp_test.cpp000066400000000000000000000027651510465702400221640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(exp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("exp"), input); auto prog = optimize_onnx("exp_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/expand_test.cpp000066400000000000000000000060221510465702400226350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(expand_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s(migraphx::shape::float_type, {3, 1, 1}); auto param = mm->add_parameter("x", s); migraphx::shape ss(migraphx::shape::int32_type, {4}); mm->add_literal(migraphx::literal(ss, {2, 3, 4, 5})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 5}}}), param); auto prog = optimize_onnx("expand_test.onnx"); EXPECT(p == prog); } TEST_CASE(expand_static_input_dyn_output_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s(migraphx::shape::float_type, {3, 1, 1}); auto param = mm->add_parameter("x", s); migraphx::shape ss(migraphx::shape::int64_type, {4}); auto dims = mm->add_parameter("dims", ss); mm->add_instruction(migraphx::make_op("broadcast_with_dims"), param, dims); auto prog = optimize_onnx("expand_static_input_dyn_output_test.onnx"); EXPECT(p == prog); } TEST_CASE(expand_dyn_input_dyn_output_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s(migraphx::shape::float_type, {{3, 8}, {1, 1}, {1, 1}}); auto param = mm->add_parameter("x", s); migraphx::shape ss(migraphx::shape::int64_type, {4}); auto dims = mm->add_parameter("dims", ss); auto ret = mm->add_instruction(migraphx::make_op("broadcast_with_dims"), param, dims); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {3, 8}; auto prog = read_onnx("expand_dyn_input_dyn_output_test.onnx", options); EXPECT(p == prog); } TEST_CASE(expand_dyn_input_static_dims_throw) { migraphx::onnx_options options; options.default_dyn_dim_value = {3, 8}; EXPECT(test::throws([&] { read_onnx("expand_dyn_input_static_dims_throw.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/ext_path_external_data_test.cpp000066400000000000000000000030711510465702400260660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "onnx_test.hpp" #include #include #include #include #include #include TEST_CASE(external_data_diff_path_test) { migraphx::program p = create_external_data_prog(); auto prog = optimize_onnx("ext_path/external_data_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/external_constant_test.cpp000066400000000000000000000027171510465702400251200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(external_constant_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{{migraphx::shape::int64_type, {3}}, {0, 1, 2}}); auto prog = optimize_onnx("external_constant_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/external_data_test.cpp000066400000000000000000000026111510465702400241710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(external_data_test) { migraphx::program p = create_external_data_prog(); auto prog = optimize_onnx("external_data_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_bf16_test.cpp000066400000000000000000000035761510465702400236360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(eyelike_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{8, 8}; const size_t k = 0; auto num_rows = input_lens.front(); auto num_cols = input_lens.back(); auto input_type = migraphx::shape::bf16_type; auto output_type = migraphx::shape::bf16_type; migraphx::shape s{input_type, input_lens}; mm->add_parameter("T1", s); auto eyelike_mat = make_r_eyelike(num_rows, num_cols, k); mm->add_literal(migraphx::literal{migraphx::shape{output_type, input_lens}, eyelike_mat}); auto prog = optimize_onnx("eyelike_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_default_test.cpp000066400000000000000000000036061510465702400245160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(eyelike_default_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{3, 4}; const size_t k = 0; auto num_rows = input_lens.front(); auto num_cols = input_lens.back(); auto input_type = migraphx::shape::float_type; auto output_type = migraphx::shape::float_type; migraphx::shape s{input_type, input_lens}; mm->add_parameter("T1", s); auto eyelike_mat = make_r_eyelike(num_rows, num_cols, k); mm->add_literal(migraphx::literal{migraphx::shape{output_type, input_lens}, eyelike_mat}); auto prog = optimize_onnx("eyelike_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_double_test.cpp000066400000000000000000000036071510465702400243450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(eyelike_double_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{6, 15}; const size_t k = 0; auto num_rows = input_lens.front(); auto num_cols = input_lens.back(); auto input_type = migraphx::shape::double_type; auto output_type = migraphx::shape::double_type; migraphx::shape s{input_type, input_lens}; mm->add_parameter("T1", s); auto eyelike_mat = make_r_eyelike(num_rows, num_cols, k); mm->add_literal(migraphx::literal{migraphx::shape{output_type, input_lens}, eyelike_mat}); auto prog = optimize_onnx("eyelike_double_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_half_test.cpp000066400000000000000000000035761510465702400240120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(eyelike_half_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{8, 8}; const size_t k = 0; auto num_rows = input_lens.front(); auto num_cols = input_lens.back(); auto input_type = migraphx::shape::half_type; auto output_type = migraphx::shape::half_type; migraphx::shape s{input_type, input_lens}; mm->add_parameter("T1", s); auto eyelike_mat = make_r_eyelike(num_rows, num_cols, k); mm->add_literal(migraphx::literal{migraphx::shape{output_type, input_lens}, eyelike_mat}); auto prog = optimize_onnx("eyelike_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_k_outofbounds_neg_test.cpp000066400000000000000000000025021510465702400265760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(eyelike_k_outofbounds_neg_test) { EXPECT(test::throws([&] { read_onnx("eyelike_k_outofbounds_neg_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_k_outofbounds_pos_test.cpp000066400000000000000000000025021510465702400266260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(eyelike_k_outofbounds_pos_test) { EXPECT(test::throws([&] { read_onnx("eyelike_k_outofbounds_pos_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_k_test.cpp000066400000000000000000000035721510465702400233260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(eyelike_k_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{3, 4}; const size_t k = 1; auto num_rows = input_lens.front(); auto num_cols = input_lens.back(); auto input_type = migraphx::shape::float_type; auto output_type = migraphx::shape::float_type; migraphx::shape s{input_type, input_lens}; mm->add_parameter("T1", s); auto eyelike_mat = make_r_eyelike(num_rows, num_cols, k); mm->add_literal(migraphx::literal{migraphx::shape{output_type, input_lens}, eyelike_mat}); auto prog = optimize_onnx("eyelike_k_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_not_rank2_test.cpp000066400000000000000000000024621510465702400247660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(eyelike_not_rank2_test) { EXPECT(test::throws([&] { read_onnx("eyelike_not_rank2_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/eyelike_set_dtype_test.cpp000066400000000000000000000036131510465702400250700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(eyelike_set_dtype_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{3, 4}; const size_t k = 0; auto num_rows = input_lens.front(); auto num_cols = input_lens.back(); auto input_type = migraphx::shape::float_type; auto output_type = migraphx::shape::double_type; migraphx::shape s{input_type, input_lens}; mm->add_parameter("T1", s); auto eyelike_mat = make_r_eyelike(num_rows, num_cols, k); mm->add_literal(migraphx::literal{migraphx::shape{output_type, input_lens}, eyelike_mat}); auto prog = optimize_onnx("eyelike_set_dtype_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/flatten_dyn_test.cpp000066400000000000000000000034161510465702400236710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(flatten_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}, {5, 5}}}); auto c0 = mm->add_instruction(migraphx::make_op("contiguous"), l0); auto ret = mm->add_instruction(migraphx::make_op("flatten", {{"axis", 2}}), c0); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("flatten_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/flatten_nonstd_test.cpp000066400000000000000000000035511510465702400244040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(flatten_nonstd_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 5, 4}}); auto l1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l0); auto l2 = mm->add_instruction(migraphx::make_op("contiguous"), l1); mm->add_instruction(migraphx::make_op("flatten", {{"axis", 2}}), l2); auto l3 = mm->add_instruction(migraphx::make_op("contiguous"), l1); mm->add_instruction(migraphx::make_op("flatten", {{"axis", 1}}), l3); auto prog = optimize_onnx("flatten_nonstd_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/flatten_test.cpp000066400000000000000000000031331510465702400230130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(flatten_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); mm->add_instruction(migraphx::make_op("flatten", {{"axis", 2}}), l0); mm->add_instruction(migraphx::make_op("flatten", {{"axis", 1}}), l0); auto prog = optimize_onnx("flatten_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/floor_test.cpp000066400000000000000000000027741510465702400225110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(floor_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("floor"), input); auto prog = optimize_onnx("floor_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gather_dyn_test.cpp000066400000000000000000000040651510465702400235070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gather_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "data", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}, {5, 5}, {6, 6}}}); auto l1 = mm->add_parameter( "indices", migraphx::shape{migraphx::shape::int32_type, {{1, 4}, {3, 3}, {4, 4}, {5, 5}}}); auto cont_l0 = mm->add_instruction(migraphx::make_op("contiguous"), l0); auto cont_l1 = mm->add_instruction(migraphx::make_op("contiguous"), l1); int axis = 1; auto gather_op = migraphx::make_op("gather", {{"axis", axis}}); auto ret = mm->add_instruction(gather_op, cont_l0, cont_l1); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("gather_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gather_elements_axis0_test.cpp000066400000000000000000000052631510465702400256360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gather_elements_axis0_test) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("data", {migraphx::shape::float_type, {3, 4}}); auto indices = mm->add_parameter("indices", {migraphx::shape::int32_type, {2, 3}}); std::vector ind_indices{0, 1, 2, 4, 5, 6}; std::vector ind_axis_indices{0, 0, 0, 1, 1, 1}; migraphx::shape ind_s{migraphx::shape::int32_type, {2, 3}}; auto l_data_indices = mm->add_literal(migraphx::literal{ind_s, ind_indices.begin(), ind_indices.end()}); auto l_ind_axis_indices = mm->add_literal(migraphx::literal{ind_s, ind_axis_indices.begin(), ind_axis_indices.end()}); auto l_stride = mm->add_literal(migraphx::literal{{migraphx::shape::int32_type, {1}}, {4}}); auto rsp_data = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {12}}}), data); auto lbst_stride = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", ind_s.lens()}}), l_stride); auto axis_delta = mm->add_instruction(migraphx::make_op("sub"), indices, l_ind_axis_indices); auto mul_delta = mm->add_instruction(migraphx::make_op("mul"), axis_delta, lbst_stride); auto ind = mm->add_instruction(migraphx::make_op("add"), l_data_indices, mul_delta); auto ret = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), rsp_data, ind); mm->add_return({ret}); auto prog = read_onnx("gather_elements_axis0_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gather_elements_axis1_test.cpp000066400000000000000000000052631510465702400256370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gather_elements_axis1_test) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("data", {migraphx::shape::float_type, {3, 4}}); auto indices = mm->add_parameter("indices", {migraphx::shape::int32_type, {2, 3}}); std::vector ind_indices{0, 1, 2, 4, 5, 6}; std::vector ind_axis_indices{0, 1, 2, 0, 1, 2}; migraphx::shape ind_s{migraphx::shape::int32_type, {2, 3}}; auto l_data_indices = mm->add_literal(migraphx::literal{ind_s, ind_indices.begin(), ind_indices.end()}); auto l_ind_axis_indices = mm->add_literal(migraphx::literal{ind_s, ind_axis_indices.begin(), ind_axis_indices.end()}); auto l_stride = mm->add_literal(migraphx::literal{{migraphx::shape::int32_type, {1}}, {1}}); auto rsp_data = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {12}}}), data); auto lbst_stride = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", ind_s.lens()}}), l_stride); auto axis_delta = mm->add_instruction(migraphx::make_op("sub"), indices, l_ind_axis_indices); auto mul_delta = mm->add_instruction(migraphx::make_op("mul"), axis_delta, lbst_stride); auto ind = mm->add_instruction(migraphx::make_op("add"), l_data_indices, mul_delta); auto ret = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), rsp_data, ind); mm->add_return({ret}); auto prog = read_onnx("gather_elements_axis1_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gather_scalar_test.cpp000066400000000000000000000033071510465702400241600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gather_scalar_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); std::vector idims{1}; auto l1 = mm->add_parameter("indices", migraphx::shape{migraphx::shape::int32_type, idims, {0}}); int axis = 1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), l0, l1); auto prog = optimize_onnx("gather_scalar_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gather_test.cpp000066400000000000000000000032131510465702400226270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gather_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto l1 = mm->add_parameter("indices", migraphx::shape{migraphx::shape::int32_type, {2, 3}}); int axis = 1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), l0, l1); auto prog = optimize_onnx("gather_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gathernd_batch_dims_test.cpp000066400000000000000000000032701510465702400253310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gathernd_batch_dims_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {2, 2, 2}}); auto l1 = mm->add_parameter("indices", migraphx::shape{migraphx::shape::int64_type, {2, 1}}); int batch_dims = 1; mm->add_instruction(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), l0, l1); auto prog = optimize_onnx("gathernd_batch_dims_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gathernd_dyn_test.cpp000066400000000000000000000036331510465702400240310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gathernd_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "data", migraphx::shape{migraphx::shape::float_type, {{2, 4, {2}}, {2, 4}}}); auto l1 = mm->add_parameter("indices", migraphx::shape{migraphx::shape::int64_type, {{1, 3}, {2, 2}}}); auto r = mm->add_instruction(migraphx::make_op("gathernd"), l0, l1); mm->add_return({r}); migraphx::onnx_options options; options.map_dyn_input_dims["data"] = {{2, 4, {2}}, {2, 4}}; options.map_dyn_input_dims["indices"] = {{1, 3}, {2, 2}}; auto prog = read_onnx("gathernd_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gathernd_test.cpp000066400000000000000000000031511510465702400231520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gathernd_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {2, 2}}); auto l1 = mm->add_parameter("indices", migraphx::shape{migraphx::shape::int64_type, {2, 2}}); mm->add_instruction(migraphx::make_op("gathernd"), l0, l1); auto prog = optimize_onnx("gathernd_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_add_bias_split_invalid_dims_test.cpp000066400000000000000000000025201510465702400300540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_add_bias_split_invalid_dims_test) { EXPECT(test::throws([&] { read_onnx("gelu_add_bias_split_invalid_dims_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_add_bias_split_test.cpp000066400000000000000000000053471510465702400253440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_add_bias_split_test) { migraphx::program p; auto type = migraphx::shape::float_type; auto lens = {2, 4, 6}; auto shape = migraphx::shape{type, lens}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", shape); auto bias = mm->add_parameter("y", migraphx::shape{type, {6}}); auto add = add_common_op(*mm, migraphx::make_op("add"), {x, bias}); auto split_left = mm->add_instruction( migraphx::make_op("slice", {{"axes", {-1}}, {"starts", {0}}, {"ends", {3}}}), add); auto split_right = mm->add_instruction( migraphx::make_op("slice", {{"axes", {-1}}, {"starts", {3}}, {"ends", {6}}}), add); auto half = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.5f}}); auto one = mm->add_literal(migraphx::literal{migraphx::shape{type}, {1.0f}}); auto sqrt2 = mm->add_literal(migraphx::literal{migraphx::shape{type}, {static_cast(M_SQRT2)}}); auto mul_half = add_common_op(*mm, migraphx::make_op("mul"), {split_right, half}); auto div = add_common_op(*mm, migraphx::make_op("div"), {split_right, sqrt2}); auto erf = mm->add_instruction(migraphx::make_op("erf"), div); auto add_one = add_common_op(*mm, migraphx::make_op("add"), {erf, one}); auto gelu_out = add_common_op(*mm, migraphx::make_op("mul"), {mul_half, add_one}); add_common_op(*mm, migraphx::make_op("mul"), {split_left, gelu_out}); auto prog = optimize_onnx("gelu_add_bias_split_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_add_bias_test.cpp000066400000000000000000000044761510465702400241330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_add_bias_test) { migraphx::program p; auto type = migraphx::shape::float_type; auto lens = {3, 3}; auto shape = migraphx::shape{type, lens}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", shape); auto bias = mm->add_parameter("y", migraphx::shape{type, {3}}); auto add = add_common_op(*mm, migraphx::make_op("add"), {x, bias}); auto half = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.5f}}); auto one = mm->add_literal(migraphx::literal{migraphx::shape{type}, {1.0f}}); auto sqrt2 = mm->add_literal(migraphx::literal{migraphx::shape{type}, {static_cast(M_SQRT2)}}); auto mul_half = add_common_op(*mm, migraphx::make_op("mul"), {add, half}); auto div = add_common_op(*mm, migraphx::make_op("div"), {add, sqrt2}); auto erf = mm->add_instruction(migraphx::make_op("erf"), div); auto add_one = add_common_op(*mm, migraphx::make_op("add"), {erf, one}); add_common_op(*mm, migraphx::make_op("mul"), {mul_half, add_one}); auto prog = optimize_onnx("gelu_add_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_bias_invalid_type_test.cpp000066400000000000000000000024741510465702400260660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_bias_invalid_type_test) { EXPECT(test::throws([&] { read_onnx("gelu_bias_invalid_type_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_default_test.cpp000066400000000000000000000042511510465702400240200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_default_test) { migraphx::program p; auto type = migraphx::shape::float_type; auto lens = {3, 3}; auto shape = migraphx::shape{type, lens}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", shape); auto half = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.5f}}); auto one = mm->add_literal(migraphx::literal{migraphx::shape{type}, {1.0f}}); auto sqrt2 = mm->add_literal(migraphx::literal{migraphx::shape{type}, {static_cast(M_SQRT2)}}); auto mul_half = add_common_op(*mm, migraphx::make_op("mul"), {x, half}); auto div = add_common_op(*mm, migraphx::make_op("div"), {x, sqrt2}); auto erf = mm->add_instruction(migraphx::make_op("erf"), div); auto add_one = add_common_op(*mm, migraphx::make_op("add"), {erf, one}); add_common_op(*mm, migraphx::make_op("mul"), {mul_half, add_one}); auto prog = optimize_onnx("gelu_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_fast_bias_test.cpp000066400000000000000000000053461510465702400243350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_fast_bias_test) { migraphx::program p; auto type = migraphx::shape::half_type; auto lens = {3, 3}; auto shape = migraphx::shape{type, lens}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", shape); auto bias = mm->add_parameter("y", shape); x = add_common_op(*mm, migraphx::make_op("add"), {x, bias}); auto fit_const = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.035677f}}); auto sqrt_2_rpi = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.797885}}); auto one = mm->add_literal(migraphx::literal{migraphx::shape{type}, {1.0f}}); auto half = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.5f}}); auto three = mm->add_literal(migraphx::literal{migraphx::shape{type}, {3.0f}}); auto pow0 = add_common_op(*mm, migraphx::make_op("pow"), {x, three}); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {pow0, fit_const}); auto mul1 = add_common_op(*mm, migraphx::make_op("mul"), {sqrt_2_rpi, x}); auto tanh_in = add_common_op(*mm, migraphx::make_op("add"), {mul0, mul1}); auto tanh0 = mm->add_instruction(migraphx::make_op("tanh"), tanh_in); auto add1 = add_common_op(*mm, migraphx::make_op("add"), {tanh0, one}); auto mul2 = add_common_op(*mm, migraphx::make_op("mul"), {x, half}); add_common_op(*mm, migraphx::make_op("mul"), {add1, mul2}); auto prog = optimize_onnx("gelu_fast_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_fast_invald_bias_test.cpp000066400000000000000000000024741510465702400256710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_fast_invalid_bias_test) { EXPECT(test::throws([&] { read_onnx("gelu_fast_invalid_bias_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_fast_invald_x_test.cpp000066400000000000000000000024661510465702400252230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_fast_invalid_x_test) { EXPECT(test::throws([&] { read_onnx("gelu_fast_invalid_x_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_fast_test.cpp000066400000000000000000000051311510465702400233270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_fast_test) { migraphx::program p; auto type = migraphx::shape::float_type; auto lens = {3, 3}; auto shape = migraphx::shape{type, lens}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", shape); auto fit_const = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.035677f}}); auto sqrt_2_rpi = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.797885}}); auto one = mm->add_literal(migraphx::literal{migraphx::shape{type}, {1.0f}}); auto half = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.5f}}); auto three = mm->add_literal(migraphx::literal{migraphx::shape{type}, {3.0f}}); auto pow0 = add_common_op(*mm, migraphx::make_op("pow"), {x, three}); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {pow0, fit_const}); auto mul1 = add_common_op(*mm, migraphx::make_op("mul"), {sqrt_2_rpi, x}); auto tanh_in = add_common_op(*mm, migraphx::make_op("add"), {mul0, mul1}); auto tanh0 = mm->add_instruction(migraphx::make_op("tanh"), tanh_in); auto add1 = add_common_op(*mm, migraphx::make_op("add"), {tanh0, one}); auto mul2 = add_common_op(*mm, migraphx::make_op("mul"), {x, half}); add_common_op(*mm, migraphx::make_op("mul"), {add1, mul2}); auto prog = optimize_onnx("gelu_fast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_invalid_input_type_test.cpp000066400000000000000000000024761510465702400263110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_invalid_input_type_test) { EXPECT(test::throws([&] { read_onnx("gelu_invalid_input_type_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_quick_test.cpp000066400000000000000000000035431510465702400235130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_quick_test) { migraphx::program p; auto type = migraphx::shape::float_type; auto lens = {3, 3}; auto shape = migraphx::shape{type, lens}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", shape); auto alpha = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.5f}}); auto mul_alpha = add_common_op(*mm, migraphx::make_op("mul"), {alpha, x}); auto sigmoid = mm->add_instruction(migraphx::make_op("sigmoid"), {mul_alpha}); add_common_op(*mm, migraphx::make_op("mul"), {x, sigmoid}); auto prog = optimize_onnx("gelu_quick_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gelu_tanh_double_test.cpp000066400000000000000000000051541510465702400246630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gelu_tanh_double_test) { migraphx::program p; auto type = migraphx::shape::double_type; auto lens = {3, 3}; auto shape = migraphx::shape{type, lens}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", shape); auto fit_const = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.044715f}}); auto sqrt_2_rpi = mm->add_literal(migraphx::literal{migraphx::shape{type}, {sqrt(M_2_PI)}}); auto one = mm->add_literal(migraphx::literal{migraphx::shape{type}, {1.0f}}); auto half = mm->add_literal(migraphx::literal{migraphx::shape{type}, {0.5f}}); auto three = mm->add_literal(migraphx::literal{migraphx::shape{type}, {3.0f}}); auto pow0 = add_common_op(*mm, migraphx::make_op("pow"), {x, three}); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {pow0, fit_const}); auto add0 = add_common_op(*mm, migraphx::make_op("add"), {mul0, x}); auto tanh_in = add_common_op(*mm, migraphx::make_op("mul"), {add0, sqrt_2_rpi}); auto tanh0 = mm->add_instruction(migraphx::make_op("tanh"), tanh_in); auto add1 = add_common_op(*mm, migraphx::make_op("add"), {tanh0, one}); auto mul2 = add_common_op(*mm, migraphx::make_op("mul"), {x, half}); add_common_op(*mm, migraphx::make_op("mul"), {add1, mul2}); auto prog = optimize_onnx("gelu_tanh_double_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_bf16_test.cpp000066400000000000000000000054641510465702400231320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gemm_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("A", migraphx::shape{migraphx::shape::bf16_type, {8, 6}}); auto l1 = mm->add_parameter("B", migraphx::shape{migraphx::shape::bf16_type, {8, 7}}); auto l2 = mm->add_parameter("C", migraphx::shape{migraphx::shape::bf16_type, {6, 1}}); auto alpha = 0.5f; auto beta = 0.8f; auto a_l = mm->add_literal(alpha); auto t_a = add_common_op(*mm, migraphx::make_op("mul"), {a_l, l0}); t_a = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bf16_type}}), t_a); t_a = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t_a); std::vector lens = {6, 7}; auto dot = migraphx::add_apply_alpha_beta(*mm, {t_a, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); l2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), l2); l2 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), l2); auto b_l = mm->add_literal(beta); auto b_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), b_l); auto l2_b = mm->add_instruction(migraphx::make_op("mul"), l2, b_b); l2_b = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bf16_type}}), l2_b); mm->add_instruction(migraphx::make_op("add"), dot, l2_b); auto prog = optimize_onnx("gemm_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_brcst_C_test.cpp000066400000000000000000000047461510465702400237550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gemm_brcst_c_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("A", migraphx::shape{migraphx::shape::float_type, {5, 6}}); auto l1 = mm->add_parameter("B", migraphx::shape{migraphx::shape::float_type, {5, 7}}); auto l2 = mm->add_parameter("C", migraphx::shape{migraphx::shape::float_type, {6, 1}}); std::vector out_lens{6, 7}; auto alpha = 0.5f; auto beta = 0.8f; auto a_l = mm->add_literal(alpha); auto t_a = add_common_op(*mm, migraphx::make_op("mul"), {a_l, l0}); t_a = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t_a); auto dot = migraphx::add_apply_alpha_beta(*mm, {t_a, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto b_l = mm->add_literal(beta); auto l2_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", out_lens}}), l2); auto b_b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", l2_b->get_shape().lens()}}), b_l); auto l2_bb = mm->add_instruction(migraphx::make_op("mul"), l2_b, b_b); mm->add_instruction(migraphx::make_op("add"), dot, l2_bb); auto prog = optimize_onnx("gemm_brcst_C_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_dyn_bias_test.cpp000066400000000000000000000041521510465702400241550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gemm_dyn_bias_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x0 = mm->add_parameter("A", migraphx::shape{migraphx::shape::float_type, {{8, 8}, {1, 10}}}); auto x1 = mm->add_parameter("B", migraphx::shape{migraphx::shape::float_type, {8, 7}}); auto x2 = mm->add_parameter("C", migraphx::shape{migraphx::shape::float_type, {1, 7}}); auto x0_t = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), x0); auto dot = mm->add_instruction(migraphx::make_op("dot"), x0_t, x1); auto x2_b = mm->add_instruction(migraphx::make_op("multibroadcast"), x2, dot); auto ret = mm->add_instruction(migraphx::make_op("add"), dot, x2_b); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; auto prog = read_onnx("gemm_dyn_bias_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_dyn_inner_test.cpp000066400000000000000000000041431510465702400243520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gemm_dyn_inner_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "A", migraphx::shape{migraphx::shape::float_type, {{1, 10, {8}}, {6, 6}}}); auto l1 = mm->add_parameter( "B", migraphx::shape{migraphx::shape::float_type, {{1, 10, {8}}, {7, 7}}}); auto alpha = 0.5f; auto a_l = mm->add_literal(alpha); auto t_a = add_common_op(*mm, migraphx::make_op("mul"), {a_l, l0}); t_a = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t_a); auto dot = migraphx::add_apply_alpha_beta(*mm, {t_a, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); mm->add_return({dot}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10, {8}}; auto prog = read_onnx("gemm_dyn_inner_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_dyn_outer_test.cpp000066400000000000000000000042601510465702400243750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gemm_dyn_outer_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "A", migraphx::shape{migraphx::shape::float_type, {{5, 5}, {5, 10, {7}}}}); auto l1 = mm->add_parameter("B", migraphx::shape{migraphx::shape::float_type, {11, 5}}); auto alpha = 2.f; auto a_l = mm->add_literal(alpha); auto t_a = add_common_op(*mm, migraphx::make_op("mul"), {a_l, l0}); t_a = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t_a); auto t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto dot = migraphx::add_apply_alpha_beta(*mm, {t_a, t1}, migraphx::make_op("dot"), 1.0f, 0.0f); mm->add_return({dot}); migraphx::onnx_options options; options.default_dyn_dim_value = {5, 10, {7}}; auto prog = read_onnx("gemm_dyn_outer_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_fp8_test.cpp000066400000000000000000000051431510465702400230630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gemm_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("A", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {8, 6}}); auto l1 = mm->add_parameter("B", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {8, 7}}); auto l2 = mm->add_parameter("C", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {6, 1}}); auto alpha = 0.5f; auto beta = 0.8f; auto a_l = mm->add_literal(alpha); auto t_a = add_common_op(*mm, migraphx::make_op("mul"), {a_l, l0}); t_a = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::fp8e4m3fnuz_type}}), t_a); t_a = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t_a); std::vector lens = {6, 7}; auto dot = mm->add_instruction(migraphx::make_op("dot"), t_a, l1); l2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), l2); auto b_l = mm->add_literal(beta); auto l2_b = add_common_op(*mm, migraphx::make_op("mul"), {l2, b_l}); l2_b = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::fp8e4m3fnuz_type}}), l2_b); mm->add_instruction(migraphx::make_op("add"), dot, l2_b); auto prog = optimize_onnx("gemm_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_half_test.cpp000066400000000000000000000054641510465702400233060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gemm_half_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("A", migraphx::shape{migraphx::shape::half_type, {8, 6}}); auto l1 = mm->add_parameter("B", migraphx::shape{migraphx::shape::half_type, {8, 7}}); auto l2 = mm->add_parameter("C", migraphx::shape{migraphx::shape::half_type, {6, 1}}); auto alpha = 0.5f; auto beta = 0.8f; auto a_l = mm->add_literal(alpha); auto t_a = add_common_op(*mm, migraphx::make_op("mul"), {a_l, l0}); t_a = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), t_a); t_a = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t_a); std::vector lens = {6, 7}; auto dot = migraphx::add_apply_alpha_beta(*mm, {t_a, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); l2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), l2); l2 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), l2); auto b_l = mm->add_literal(beta); auto b_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), b_l); auto l2_b = mm->add_instruction(migraphx::make_op("mul"), l2, b_b); l2_b = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l2_b); mm->add_instruction(migraphx::make_op("add"), dot, l2_b); auto prog = optimize_onnx("gemm_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_no_C_test.cpp000066400000000000000000000050251510465702400232430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gemm_no_c_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("A", migraphx::shape{migraphx::shape::float_type, {5, 7}}); auto l1 = mm->add_parameter("B", migraphx::shape{migraphx::shape::float_type, {11, 5}}); auto l2 = mm->add_parameter("C", migraphx::shape{migraphx::shape::float_type}); auto alpha = 2.f; auto beta = 2.0f; auto a_l = mm->add_literal(alpha); auto t_a = add_common_op(*mm, migraphx::make_op("mul"), {a_l, l0}); t_a = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t_a); auto t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto dot = migraphx::add_apply_alpha_beta(*mm, {t_a, t1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto b_l = mm->add_literal(beta); auto l2_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {7, 11}}}), l2); auto b_b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", l2_b->get_shape().lens()}}), b_l); auto l2_bb = mm->add_instruction(migraphx::make_op("mul"), l2_b, b_b); mm->add_instruction(migraphx::make_op("add"), dot, l2_bb); auto prog = optimize_onnx("gemm_no_C_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_rank_error.cpp000066400000000000000000000024441510465702400234740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gemm_rank_error) { EXPECT(test::throws([&] { read_onnx("gemm_rank_error.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gemm_test.cpp000066400000000000000000000044731510465702400223130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gemm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("A", migraphx::shape{migraphx::shape::float_type, {8, 6}}); auto l1 = mm->add_parameter("B", migraphx::shape{migraphx::shape::float_type, {8, 7}}); auto l2 = mm->add_parameter("C", migraphx::shape{migraphx::shape::float_type, {6, 7}}); auto alpha = 0.5f; auto beta = 0.8f; auto a_l = mm->add_literal(alpha); auto t_a = add_common_op(*mm, migraphx::make_op("mul"), {a_l, l0}); t_a = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t_a); auto dot = migraphx::add_apply_alpha_beta(*mm, {t_a, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto b_l = mm->add_literal(beta); auto b_b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", l2->get_shape().lens()}}), b_l); auto l2_b = mm->add_instruction(migraphx::make_op("mul"), l2, b_b); mm->add_instruction(migraphx::make_op("add"), dot, l2_b); auto prog = optimize_onnx("gemm_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/globalavgpool_dyn_test.cpp000066400000000000000000000040301510465702400250550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(globalavgpool_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {16, 16}, {16, 16}}}); auto ret = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"lengths", {16, 16}}, {"padding", {0, 0, 0, 0}}}), input); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("globalavgpool_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/globalavgpool_fp8_test.cpp000066400000000000000000000033621510465702400247670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(globalavgpool_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {1, 3, 16, 16}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; auto lens = input->get_shape().lens(); op.lengths = {lens[2], lens[3]}; op.padding = {0, 0, 0, 0}; mm->add_instruction(op, input); auto prog = optimize_onnx("globalavgpool_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/globalavgpool_test.cpp000066400000000000000000000033441510465702400242120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(globalavgpool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; auto lens = input->get_shape().lens(); op.lengths = {lens[2], lens[3]}; op.padding = {0, 0, 0, 0}; mm->add_instruction(op, input); auto prog = optimize_onnx("globalavgpool_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/globallppool_dyn_test.cpp000066400000000000000000000041351510465702400247210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(globallppool_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {16, 32}, {16, 32}}}); auto ret = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"dyn_global", true}, {"padding", {0, 0, 0, 0}}, {"lengths", {}}}), input); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {16, 32}; auto prog = read_onnx("globallppool_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/globallppool_test.cpp000066400000000000000000000033411510465702400240450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(globallppool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::lpnorm}; auto lens = input->get_shape().lens(); op.lengths = {lens[2], lens[3]}; op.padding = {0, 0, 0, 0}; mm->add_instruction(op, input); auto prog = optimize_onnx("globallppool_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/globalmaxpool_dyn_test.cpp000066400000000000000000000040241510465702400250700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(globalmaxpool_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {32, 32}, {32, 32}}}); auto ret = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"lengths", {32, 32}}, {"padding", {0, 0, 0, 0}}}), input); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("globalmaxpool_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/globalmaxpool_fp8_test.cpp000066400000000000000000000033561510465702400250020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(globalmaxpool_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {1, 3, 16, 16}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; auto lens = input->get_shape().lens(); op.lengths = {lens[2], lens[3]}; op.padding = {0, 0, 0, 0}; mm->add_instruction(op, input); auto prog = optimize_onnx("globalmaxpool_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/globalmaxpool_test.cpp000066400000000000000000000033401510465702400242160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(globalmaxpool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; auto lens = input->get_shape().lens(); op.lengths = {lens[2], lens[3]}; op.padding = {0, 0, 0, 0}; mm->add_instruction(op, input); auto prog = optimize_onnx("globalmaxpool_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/greater_bool_test.cpp000066400000000000000000000035521510465702400240270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(greater_bool_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sf{migraphx::shape::float_type, {2, 3}}; migraphx::shape sb{migraphx::shape::bool_type, {2, 3}}; auto input1 = mm->add_parameter("x1", sf); auto input2 = mm->add_parameter("x2", sb); auto cin1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), input1); auto ret = mm->add_instruction(migraphx::make_op("greater"), cin1, input2); mm->add_return({ret}); auto prog = read_onnx("greater_bool_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/greater_test.cpp000066400000000000000000000036531510465702400230160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(greater_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; auto input1 = mm->add_literal(migraphx::literal(s, data)); auto input2 = mm->add_parameter("x2", migraphx::shape{migraphx::shape::float_type, {2, 3}}); auto gr = mm->add_instruction(migraphx::make_op("greater"), input1, input2); auto ret = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), gr); mm->add_return({ret}); auto prog = read_onnx("greater_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/greaterorequal_test.cpp000066400000000000000000000035341510465702400244050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(greaterorequal_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input1 = mm->add_parameter("x1", migraphx::shape{migraphx::shape::float_type, {3}}); auto input2 = mm->add_parameter("x2", migraphx::shape{migraphx::shape::float_type, {3}}); auto temp = mm->add_instruction(migraphx::make_op("less"), input1, input2); auto bt = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), temp); auto ge = mm->add_instruction(migraphx::make_op("not"), bt); mm->add_return({ge}); auto prog = read_onnx("greaterorequal_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gridsample_512x512_test.cpp000066400000000000000000000233331510465702400245200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gridsample_512x512_test) { migraphx::program p; auto* mm = p.get_main_module(); // gridsample constructor auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 512, 512}}); auto grid = mm->add_parameter("grid", migraphx::shape{migraphx::shape::float_type, {1, 512, 512, 2}}); auto m_zero_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {0.0f}}); auto m_one_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {1.0f}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type}, {2}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {-0.5f}}); auto m_width_max_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {511}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {512}}); auto m_height_max_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {511}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {512}}); auto x_coords = mm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {1}}}), grid); auto y_coords = mm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {1}}, {"ends", {2}}}), grid); x_coords = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), x_coords); y_coords = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), y_coords); // unnorm x (align corners) auto m_unnorm_x = add_common_op(*mm, migraphx::make_op("add"), {x_coords, m_one_l}); auto mul_const_x = mm->add_literal( migraphx::literal{migraphx::shape{x_coords->get_shape().type()}, {(512.f - 1) / 2}}); m_unnorm_x = add_common_op(*mm, migraphx::make_op("mul"), {m_unnorm_x, mul_const_x}); // unnorm y (align corners) auto m_unnorm_y = add_common_op(*mm, migraphx::make_op("add"), {y_coords, m_one_l}); auto mul_const_y = mm->add_literal( migraphx::literal{migraphx::shape{y_coords->get_shape().type()}, {(512.f - 1) / 2}}); m_unnorm_y = add_common_op(*mm, migraphx::make_op("mul"), {m_unnorm_y, mul_const_y}); // border padding m_unnorm_x = add_common_op(*mm, migraphx::make_op("clip"), {m_unnorm_x, m_zero_l, m_width_max_l}); m_unnorm_y = add_common_op(*mm, migraphx::make_op("clip"), {m_unnorm_y, m_zero_l, m_height_max_l}); // linear sampler auto m_floor_x = add_common_op(*mm, migraphx::make_op("floor"), {m_unnorm_x}); auto m_floor_y = add_common_op(*mm, migraphx::make_op("floor"), {m_unnorm_y}); auto m_ceil_x = add_common_op(*mm, migraphx::make_op("add"), {m_floor_x, m_one_l}); auto m_ceil_y = add_common_op(*mm, migraphx::make_op("add"), {m_floor_y, m_one_l}); auto fract_x = add_common_op(*mm, migraphx::make_op("sub"), {m_unnorm_x, m_floor_x}); auto fract_y = add_common_op(*mm, migraphx::make_op("sub"), {m_unnorm_y, m_floor_y}); auto one_minus_fract_x = add_common_op(*mm, migraphx::make_op("sub"), {m_one_l, fract_x}); auto one_minus_fract_y = add_common_op(*mm, migraphx::make_op("sub"), {m_one_l, fract_y}); std::array m_corner_weights; m_corner_weights[0] = add_common_op(*mm, migraphx::make_op("mul"), {one_minus_fract_y, one_minus_fract_x}); m_corner_weights[1] = add_common_op(*mm, migraphx::make_op("mul"), {one_minus_fract_y, fract_x}); m_corner_weights[2] = add_common_op(*mm, migraphx::make_op("mul"), {fract_y, one_minus_fract_x}); m_corner_weights[3] = add_common_op(*mm, migraphx::make_op("mul"), {fract_y, fract_x}); std::vector xy_indices_data(786432 * 3); std::vector weight_indices_data(786432 * 3); std::vector nc_values_data(786432 * 2); auto xy_indices_t = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {786432, 3}}, xy_indices_data}); auto weight_index_t = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {786432, 3}}, weight_indices_data}); auto nc = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {786432, 2}}, nc_values_data}); auto y0_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_floor_y, xy_indices_t); auto x0_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_floor_x, xy_indices_t); auto y1_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_ceil_y, xy_indices_t); auto x1_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_ceil_x, xy_indices_t); auto validate_samples = [&](auto& samples, auto& max) { auto clip = add_common_op(*mm, migraphx::make_op("clip"), {samples, m_zero_l, max}); auto validation = add_common_op(*mm, migraphx::make_op("equal"), {samples, clip}); samples = clip; return validation; }; auto y0_validation = validate_samples(y0_samples, m_height_max_l); auto x0_validation = validate_samples(x0_samples, m_width_max_l); auto y1_validation = validate_samples(y1_samples, m_height_max_l); auto x1_validation = validate_samples(x1_samples, m_width_max_l); y0_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {y0_samples->get_shape().elements(), 1}}}), y0_samples); x0_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {x0_samples->get_shape().elements(), 1}}}), x0_samples); y1_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {y1_samples->get_shape().elements(), 1}}}), y1_samples); x1_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {x1_samples->get_shape().elements(), 1}}}), x1_samples); auto make_corner_indices = [&](auto& x, auto& y) { auto hw = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), y, x); return mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), nc, hw); }; std::array corner_indices{ make_corner_indices(x0_samples, y0_samples), make_corner_indices(x1_samples, y0_samples), make_corner_indices(x0_samples, y1_samples), make_corner_indices(x1_samples, y1_samples)}; std::array corner_validations{ add_common_op(*mm, migraphx::make_op("logical_and"), {x0_validation, y0_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x1_validation, y0_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x0_validation, y1_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x1_validation, y1_validation})}; std::array corner_samples; std::transform( corner_indices.begin(), corner_indices.end(), corner_validations.begin(), corner_samples.begin(), [&](const auto& indices, const auto& validations) { auto samples = mm->add_instruction(migraphx::make_op("gathernd"), x, indices); return add_common_op(*mm, migraphx::make_op("where"), {validations, samples, m_zero_l}); }); std::transform(corner_samples.begin(), corner_samples.end(), m_corner_weights.begin(), corner_samples.begin(), [&](const auto& samples, const auto& weights) { auto weights_t = mm->add_instruction( migraphx::make_op("gathernd"), weights, weight_index_t); return mm->add_instruction(migraphx::make_op("mul"), samples, weights_t); }); auto samples = std::accumulate( std::next(corner_samples.begin()), corner_samples.end(), corner_samples.front(), [&](auto acc, auto s) { return mm->add_instruction(migraphx::make_op("add"), acc, s); }); samples = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 512, 512, 3}}}), samples); samples = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), samples); samples = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), samples); auto prog = optimize_onnx("gridsample_512x512_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gridsample_channel_test.cpp000066400000000000000000000232471510465702400252050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gridsample_channel_test) { migraphx::program p; auto* mm = p.get_main_module(); // gridsample constructor auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 4, 4}}); auto grid = mm->add_parameter("grid", migraphx::shape{migraphx::shape::float_type, {1, 6, 6, 2}}); auto m_zero_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {0.0f}}); auto m_one_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {1.0f}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type}, {2}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {-0.5f}}); auto m_width_max_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {3}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {4}}); auto m_height_max_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {3}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {4}}); auto x_coords = mm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {1}}}), grid); auto y_coords = mm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {1}}, {"ends", {2}}}), grid); x_coords = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), x_coords); y_coords = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), y_coords); // unnorm x (align corners) auto m_unnorm_x = add_common_op(*mm, migraphx::make_op("add"), {x_coords, m_one_l}); auto mul_const_x = mm->add_literal( migraphx::literal{migraphx::shape{x_coords->get_shape().type()}, {(4.f - 1) / 2}}); m_unnorm_x = add_common_op(*mm, migraphx::make_op("mul"), {m_unnorm_x, mul_const_x}); // unnorm y (align corners) auto m_unnorm_y = add_common_op(*mm, migraphx::make_op("add"), {y_coords, m_one_l}); auto mul_const_y = mm->add_literal( migraphx::literal{migraphx::shape{y_coords->get_shape().type()}, {(4.f - 1) / 2}}); m_unnorm_y = add_common_op(*mm, migraphx::make_op("mul"), {m_unnorm_y, mul_const_y}); // border padding m_unnorm_x = add_common_op(*mm, migraphx::make_op("clip"), {m_unnorm_x, m_zero_l, m_width_max_l}); m_unnorm_y = add_common_op(*mm, migraphx::make_op("clip"), {m_unnorm_y, m_zero_l, m_height_max_l}); // linear sampler auto m_floor_x = add_common_op(*mm, migraphx::make_op("floor"), {m_unnorm_x}); auto m_floor_y = add_common_op(*mm, migraphx::make_op("floor"), {m_unnorm_y}); auto m_ceil_x = add_common_op(*mm, migraphx::make_op("add"), {m_floor_x, m_one_l}); auto m_ceil_y = add_common_op(*mm, migraphx::make_op("add"), {m_floor_y, m_one_l}); auto fract_x = add_common_op(*mm, migraphx::make_op("sub"), {m_unnorm_x, m_floor_x}); auto fract_y = add_common_op(*mm, migraphx::make_op("sub"), {m_unnorm_y, m_floor_y}); auto one_minus_fract_x = add_common_op(*mm, migraphx::make_op("sub"), {m_one_l, fract_x}); auto one_minus_fract_y = add_common_op(*mm, migraphx::make_op("sub"), {m_one_l, fract_y}); std::array m_corner_weights; m_corner_weights[0] = add_common_op(*mm, migraphx::make_op("mul"), {one_minus_fract_y, one_minus_fract_x}); m_corner_weights[1] = add_common_op(*mm, migraphx::make_op("mul"), {one_minus_fract_y, fract_x}); m_corner_weights[2] = add_common_op(*mm, migraphx::make_op("mul"), {fract_y, one_minus_fract_x}); m_corner_weights[3] = add_common_op(*mm, migraphx::make_op("mul"), {fract_y, fract_x}); std::vector xy_indices_data(108 * 3); std::vector weight_indices_data(108 * 3); std::vector nc_values_data(108 * 2); auto xy_indices_t = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {108, 3}}, xy_indices_data}); auto weight_index_t = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {108, 3}}, weight_indices_data}); auto nc = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {108, 2}}, nc_values_data}); auto y0_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_floor_y, xy_indices_t); auto x0_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_floor_x, xy_indices_t); auto y1_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_ceil_y, xy_indices_t); auto x1_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_ceil_x, xy_indices_t); auto validate_samples = [&](auto& samples, auto& max) { auto clip = add_common_op(*mm, migraphx::make_op("clip"), {samples, m_zero_l, max}); auto validation = add_common_op(*mm, migraphx::make_op("equal"), {samples, clip}); samples = clip; return validation; }; auto y0_validation = validate_samples(y0_samples, m_height_max_l); auto x0_validation = validate_samples(x0_samples, m_width_max_l); auto y1_validation = validate_samples(y1_samples, m_height_max_l); auto x1_validation = validate_samples(x1_samples, m_width_max_l); y0_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {y0_samples->get_shape().elements(), 1}}}), y0_samples); x0_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {x0_samples->get_shape().elements(), 1}}}), x0_samples); y1_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {y1_samples->get_shape().elements(), 1}}}), y1_samples); x1_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {x1_samples->get_shape().elements(), 1}}}), x1_samples); auto make_corner_indices = [&](auto& x, auto& y) { auto hw = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), y, x); return mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), nc, hw); }; std::array corner_indices{ make_corner_indices(x0_samples, y0_samples), make_corner_indices(x1_samples, y0_samples), make_corner_indices(x0_samples, y1_samples), make_corner_indices(x1_samples, y1_samples)}; std::array corner_validations{ add_common_op(*mm, migraphx::make_op("logical_and"), {x0_validation, y0_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x1_validation, y0_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x0_validation, y1_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x1_validation, y1_validation})}; std::array corner_samples; std::transform( corner_indices.begin(), corner_indices.end(), corner_validations.begin(), corner_samples.begin(), [&](const auto& indices, const auto& validations) { auto samples = mm->add_instruction(migraphx::make_op("gathernd"), x, indices); return add_common_op(*mm, migraphx::make_op("where"), {validations, samples, m_zero_l}); }); std::transform(corner_samples.begin(), corner_samples.end(), m_corner_weights.begin(), corner_samples.begin(), [&](const auto& samples, const auto& weights) { auto weights_t = mm->add_instruction( migraphx::make_op("gathernd"), weights, weight_index_t); return mm->add_instruction(migraphx::make_op("mul"), samples, weights_t); }); auto samples = std::accumulate( std::next(corner_samples.begin()), corner_samples.end(), corner_samples.front(), [&](auto acc, auto s) { return mm->add_instruction(migraphx::make_op("add"), acc, s); }); samples = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 6, 6, 3}}}), samples); samples = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), samples); samples = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), samples); auto prog = optimize_onnx("gridsample_channel_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gridsample_mismatching_dims_test.cpp000066400000000000000000000025061510465702400271070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gridsample_mismatching_dims_test) { EXPECT(test::throws([&] { read_onnx("gridsample_mismatching_dims_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gridsample_test.cpp000066400000000000000000000231071510465702400235100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(gridsample_test) { migraphx::program p; auto* mm = p.get_main_module(); // gridsample constructor auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 4, 4}}); auto grid = mm->add_parameter("grid", migraphx::shape{migraphx::shape::float_type, {1, 6, 6, 2}}); auto m_zero_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {0.0f}}); auto m_one_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {1.0f}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type}, {2}}); auto m_minus_half_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {-0.5f}}); auto m_width_max_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {3}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {4}}); auto m_height_max_l = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {3}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {4}}); auto x_coords = mm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {1}}}), grid); auto y_coords = mm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {1}}, {"ends", {2}}}), grid); x_coords = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), x_coords); y_coords = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), y_coords); // unnorm x auto m_unnorm_x = add_common_op(*mm, migraphx::make_op("add"), {x_coords, m_one_l}); auto mul_const_x = mm->add_literal( migraphx::literal{migraphx::shape{x_coords->get_shape().type()}, {4.f / 2}}); m_unnorm_x = add_common_op(*mm, migraphx::make_op("mul"), {m_unnorm_x, mul_const_x}); m_unnorm_x = add_common_op(*mm, migraphx::make_op("add"), {m_unnorm_x, m_minus_half_l}); // unnorm y auto m_unnorm_y = add_common_op(*mm, migraphx::make_op("add"), {y_coords, m_one_l}); auto mul_const_y = mm->add_literal( migraphx::literal{migraphx::shape{y_coords->get_shape().type()}, {4.f / 2}}); m_unnorm_y = add_common_op(*mm, migraphx::make_op("mul"), {m_unnorm_y, mul_const_y}); m_unnorm_y = add_common_op(*mm, migraphx::make_op("add"), {m_unnorm_y, m_minus_half_l}); // linear sampler auto m_floor_x = add_common_op(*mm, migraphx::make_op("floor"), {m_unnorm_x}); auto m_floor_y = add_common_op(*mm, migraphx::make_op("floor"), {m_unnorm_y}); auto m_ceil_x = add_common_op(*mm, migraphx::make_op("add"), {m_floor_x, m_one_l}); auto m_ceil_y = add_common_op(*mm, migraphx::make_op("add"), {m_floor_y, m_one_l}); auto fract_x = add_common_op(*mm, migraphx::make_op("sub"), {m_unnorm_x, m_floor_x}); auto fract_y = add_common_op(*mm, migraphx::make_op("sub"), {m_unnorm_y, m_floor_y}); auto one_minus_fract_x = add_common_op(*mm, migraphx::make_op("sub"), {m_one_l, fract_x}); auto one_minus_fract_y = add_common_op(*mm, migraphx::make_op("sub"), {m_one_l, fract_y}); std::array m_corner_weights; m_corner_weights[0] = add_common_op(*mm, migraphx::make_op("mul"), {one_minus_fract_y, one_minus_fract_x}); m_corner_weights[1] = add_common_op(*mm, migraphx::make_op("mul"), {one_minus_fract_y, fract_x}); m_corner_weights[2] = add_common_op(*mm, migraphx::make_op("mul"), {fract_y, one_minus_fract_x}); m_corner_weights[3] = add_common_op(*mm, migraphx::make_op("mul"), {fract_y, fract_x}); std::vector xy_indices_data(36 * 3); std::vector weight_indices_data(36 * 3); std::vector nc_values_data(36 * 2); auto xy_indices_t = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {36, 3}}, xy_indices_data}); auto weight_index_t = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {36, 3}}, weight_indices_data}); auto nc = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {36, 2}}, nc_values_data}); auto y0_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_floor_y, xy_indices_t); auto x0_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_floor_x, xy_indices_t); auto y1_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_ceil_y, xy_indices_t); auto x1_samples = mm->add_instruction(migraphx::make_op("gathernd"), m_ceil_x, xy_indices_t); auto validate_samples = [&](auto& samples, auto& max) { auto clip = add_common_op(*mm, migraphx::make_op("clip"), {samples, m_zero_l, max}); auto validation = add_common_op(*mm, migraphx::make_op("equal"), {samples, clip}); samples = clip; return validation; }; auto y0_validation = validate_samples(y0_samples, m_height_max_l); auto x0_validation = validate_samples(x0_samples, m_width_max_l); auto y1_validation = validate_samples(y1_samples, m_height_max_l); auto x1_validation = validate_samples(x1_samples, m_width_max_l); y0_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {y0_samples->get_shape().elements(), 1}}}), y0_samples); x0_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {x0_samples->get_shape().elements(), 1}}}), x0_samples); y1_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {y1_samples->get_shape().elements(), 1}}}), y1_samples); x1_samples = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {x1_samples->get_shape().elements(), 1}}}), x1_samples); auto make_corner_indices = [&](auto& x, auto& y) { auto hw = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), y, x); return mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), nc, hw); }; std::array corner_indices{ make_corner_indices(x0_samples, y0_samples), make_corner_indices(x1_samples, y0_samples), make_corner_indices(x0_samples, y1_samples), make_corner_indices(x1_samples, y1_samples)}; std::array corner_validations{ add_common_op(*mm, migraphx::make_op("logical_and"), {x0_validation, y0_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x1_validation, y0_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x0_validation, y1_validation}), add_common_op(*mm, migraphx::make_op("logical_and"), {x1_validation, y1_validation})}; std::array corner_samples; std::transform( corner_indices.begin(), corner_indices.end(), corner_validations.begin(), corner_samples.begin(), [&](const auto& indices, const auto& validations) { auto samples = mm->add_instruction(migraphx::make_op("gathernd"), x, indices); return add_common_op(*mm, migraphx::make_op("where"), {validations, samples, m_zero_l}); }); std::transform(corner_samples.begin(), corner_samples.end(), m_corner_weights.begin(), corner_samples.begin(), [&](const auto& samples, const auto& weights) { auto weights_t = mm->add_instruction( migraphx::make_op("gathernd"), weights, weight_index_t); return mm->add_instruction(migraphx::make_op("mul"), samples, weights_t); }); auto samples = std::accumulate( std::next(corner_samples.begin()), corner_samples.end(), corner_samples.front(), [&](auto acc, auto s) { return mm->add_instruction(migraphx::make_op("add"), acc, s); }); samples = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 6, 6, 1}}}), samples); samples = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), samples); samples = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), samples); auto prog = optimize_onnx("gridsample_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gridsample_volumetric_nearest_align_corners_0_test.cpp000066400000000000000000000025641510465702400326320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. *s * 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. */ #include TEST_CASE(gridsample_volumetric_nearest_align_corners_0_test) { EXPECT(test::throws( [&] { read_onnx("gridsample_volumetric_nearest_align_corners_0_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/gridsample_wrong_grid_type_test.cpp000066400000000000000000000025041510465702400267700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(gridsample_wrong_grid_type_test) { EXPECT(test::throws([&] { read_onnx("gridsample_wrong_grid_type_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_conv_test.cpp000066400000000000000000000032621510465702400235420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_conv_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 4, 16, 16}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {4, 1, 3, 3}}); migraphx::op::convolution op; op.group = 4; mm->add_instruction(op, l0, l1); auto prog = optimize_onnx("group_conv_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_3d_bf16_test.cpp000066400000000000000000000035611510465702400247560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_3d_bf16_test) { migraphx::program p = make_group_norm({1, 4, 2}, {4}, {4}, {1, 2, 2, 2}, {2, 3}, 1e-5f, migraphx::shape::bf16_type, {"scale", migraphx::shape::bf16_type}, {"bias", migraphx::shape::bf16_type}); auto prog = optimize_onnx("group_norm_3d_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_3d_half_test.cpp000066400000000000000000000035611510465702400251320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_3d_half_test) { migraphx::program p = make_group_norm({1, 4, 2}, {4}, {4}, {1, 2, 2, 2}, {2, 3}, 1e-5f, migraphx::shape::half_type, {"scale", migraphx::shape::half_type}, {"bias", migraphx::shape::half_type}); auto prog = optimize_onnx("group_norm_3d_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_3d_test.cpp000066400000000000000000000027241510465702400241400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_3d_test) { migraphx::program p = make_group_norm( {1, 4, 2}, {4}, {4}, {1, 2, 2, 2}, {2, 3}, 1e-5f, migraphx::shape::float_type); auto prog = optimize_onnx("group_norm_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_4d_bf16_test.cpp000066400000000000000000000035721510465702400247610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_4d_bf16_test) { migraphx::program p = make_group_norm({1, 4, 3, 3}, {4}, {4}, {1, 2, 2, 3, 3}, {2, 3, 4}, 1e-5f, migraphx::shape::bf16_type, {"scale", migraphx::shape::bf16_type}, {"bias", migraphx::shape::bf16_type}); auto prog = optimize_onnx("group_norm_4d_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_4d_half_test.cpp000066400000000000000000000035721510465702400251350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_4d_half_test) { migraphx::program p = make_group_norm({1, 4, 3, 3}, {4}, {4}, {1, 2, 2, 3, 3}, {2, 3, 4}, 1e-5f, migraphx::shape::half_type, {"scale", migraphx::shape::half_type}, {"bias", migraphx::shape::half_type}); auto prog = optimize_onnx("group_norm_4d_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_4d_test.cpp000066400000000000000000000027351510465702400241430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_4d_test) { migraphx::program p = make_group_norm( {1, 4, 3, 3}, {4}, {4}, {1, 2, 2, 3, 3}, {2, 3, 4}, 1e-5f, migraphx::shape::float_type); auto prog = optimize_onnx("group_norm_4d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_5d_bf16_test.cpp000066400000000000000000000036151510465702400247600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_5d_bf16_test) { migraphx::program p = make_group_norm({3, 3, 3, 3, 3}, {3}, {3}, {3, 1, 3, 3, 3, 3}, {2, 3, 4, 5}, 1e-5f, migraphx::shape::bf16_type, {"scale", migraphx::shape::bf16_type}, {"bias", migraphx::shape::bf16_type}); auto prog = optimize_onnx("group_norm_5d_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_5d_half_test.cpp000066400000000000000000000036151510465702400251340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_5d_half_test) { migraphx::program p = make_group_norm({3, 3, 3, 3, 3}, {3}, {3}, {3, 1, 3, 3, 3, 3}, {2, 3, 4, 5}, 1e-5f, migraphx::shape::half_type, {"scale", migraphx::shape::half_type}, {"bias", migraphx::shape::half_type}); auto prog = optimize_onnx("group_norm_5d_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_5d_test.cpp000066400000000000000000000033431510465702400241400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_5d_test) { migraphx::program p = make_group_norm({3, 3, 3, 3, 3}, {3}, {3}, {3, 1, 3, 3, 3, 3}, {2, 3, 4, 5}, 1e-5f, migraphx::shape::float_type); auto prog = optimize_onnx("group_norm_5d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_contrib_3d_no_activation_err_test.cpp000066400000000000000000000025701510465702400314440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_contrib_no_activation_err_test) { EXPECT(test::throws([&] { read_onnx("group_norm_contrib_no_activation_attr_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_contrib_3d_no_num_groups_err_test.cpp000066400000000000000000000025701510465702400315010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_contrib_no_num_groups_err_test) { EXPECT(test::throws([&] { read_onnx("group_norm_contrib_no_num_groups_attr_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_contrib_3d_test.cpp000066400000000000000000000036041510465702400256560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_contrib_3d_test) { migraphx::program p = make_group_norm({1, 4, 2}, {4}, {4}, {1, 2, 2, 2}, {2, 3}, 1e-5f, migraphx::shape::float_type, {"gamma", migraphx::shape::float_type}, {"beta", migraphx::shape::float_type}); auto prog = optimize_onnx("group_norm_contrib_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_contrib_channels_last_3d_test.cpp000066400000000000000000000072161510465702400305570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_contrib_channels_last_3d_test) { const std::vector input_dims{1, 4, 2}; const std::vector scale_dims{2}; const std::vector bias_dims{2}; const std::vector reshape_dims{1, 2, 1, 4}; const std::vector reduce_axes{2, 3}; const float eps_value = 1e-5f; const migraphx::shape::type_t dtype = migraphx::shape::float_type; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {dtype, input_dims}); auto scale = mm->add_parameter("gamma", {dtype, scale_dims}); auto bias = mm->add_parameter("beta", {dtype, bias_dims}); auto eps = mm->add_literal(migraphx::literal{dtype, {eps_value}}); auto x_transp = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), x); auto x_dims = x_transp->get_shape().lens(); auto x_reshaped = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_dims}}), x_transp); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_reshaped); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x_reshaped, mean}); auto x_sqdiff_mean = add_common_op(*mm, migraphx::make_op("sqdiff"), {x_reshaped, mean}); auto var = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_sqdiff_mean); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), {var_eps}); auto result = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, rsqrt}); auto result_reshaped = mm->add_instruction(migraphx::make_op("reshape", {{"dims", x_dims}}), result); auto scale_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), scale); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), bias); auto scaled = mm->add_instruction(migraphx::make_op("mul"), {result_reshaped, scale_bcast}); auto y = mm->add_instruction(migraphx::make_op("add"), {scaled, bias_bcast}); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), y); auto prog = optimize_onnx("group_norm_contrib_channels_last_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_contrib_channels_last_4d_test.cpp000066400000000000000000000072301510465702400305540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_contrib_channels_last_4d_test) { const std::vector input_dims{1, 3, 3, 4}; const std::vector scale_dims{4}; const std::vector bias_dims{4}; const std::vector reshape_dims{1, 2, 2, 3, 3}; const std::vector reduce_axes{2, 3, 4}; const float eps_value = 1e-5f; const migraphx::shape::type_t dtype = migraphx::shape::float_type; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {dtype, input_dims}); auto scale = mm->add_parameter("gamma", {dtype, scale_dims}); auto bias = mm->add_parameter("beta", {dtype, bias_dims}); auto eps = mm->add_literal(migraphx::literal{dtype, {eps_value}}); auto x_transp = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), x); auto x_dims = x_transp->get_shape().lens(); auto x_reshaped = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_dims}}), x_transp); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_reshaped); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x_reshaped, mean}); auto x_sqdiff_mean = add_common_op(*mm, migraphx::make_op("sqdiff"), {x_reshaped, mean}); auto var = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_sqdiff_mean); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), {var_eps}); auto result = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, rsqrt}); auto result_reshaped = mm->add_instruction(migraphx::make_op("reshape", {{"dims", x_dims}}), result); auto scale_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), scale); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), bias); auto scaled = mm->add_instruction(migraphx::make_op("mul"), {result_reshaped, scale_bcast}); auto y = mm->add_instruction(migraphx::make_op("add"), {scaled, bias_bcast}); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), y); auto prog = optimize_onnx("group_norm_contrib_channels_last_4d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_contrib_channels_last_silu_3d_test.cpp000066400000000000000000000077171510465702400316210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_contrib_channels_last_silu_3d_test) { const std::vector input_dims{1, 4, 2}; const std::vector scale_dims{2}; const std::vector bias_dims{2}; const std::vector reshape_dims{1, 2, 1, 4}; const std::vector reduce_axes{2, 3}; const float eps_value = 1e-5f; const migraphx::shape::type_t dtype = migraphx::shape::float_type; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {dtype, input_dims}); auto scale = mm->add_parameter("gamma", {dtype, scale_dims}); auto bias = mm->add_parameter("beta", {dtype, bias_dims}); auto eps = mm->add_literal(migraphx::literal{dtype, {eps_value}}); auto x_transp = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), x); auto x_dims = x_transp->get_shape().lens(); auto x_reshaped = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_dims}}), x_transp); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_reshaped); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x_reshaped, mean}); auto x_sqdiff_mean = add_common_op(*mm, migraphx::make_op("sqdiff"), {x_reshaped, mean}); auto var = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", reduce_axes}}), x_sqdiff_mean); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), {var_eps}); auto result = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, rsqrt}); auto result_reshaped = mm->add_instruction(migraphx::make_op("reshape", {{"dims", x_dims}}), result); auto scale_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), scale); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x_dims}}), bias); auto scaled = mm->add_instruction(migraphx::make_op("mul"), {result_reshaped, scale_bcast}); auto y = mm->add_instruction(migraphx::make_op("add"), {scaled, bias_bcast}); auto group_out = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), y); // Order matters for this operator as we MUST have SILU after a group norm is done when the // activation flag is set auto sigmoid_out = mm->add_instruction(migraphx::make_op("sigmoid"), group_out); mm->add_instruction(migraphx::make_op("mul"), group_out, sigmoid_out); auto prog = optimize_onnx("group_norm_contrib_channels_last_and_silu_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_contrib_gamma_beta_float_xy_half_test.cpp000066400000000000000000000036571510465702400323340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_contrib_gamma_beta_float_xy_half_test) { migraphx::program p = make_group_norm({1, 4, 2}, {4}, {4}, {1, 2, 2, 2}, {2, 3}, 1e-5f, migraphx::shape::half_type, {"gamma", migraphx::shape::float_type}, {"beta", migraphx::shape::float_type}); auto prog = optimize_onnx("group_norm_contrib_gamma_beta_float_xy_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_contrib_silu_3d_test.cpp000066400000000000000000000043071510465702400267130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_contrib_silu_3d_test) { migraphx::program p = make_group_norm({1, 4, 2}, {4}, {4}, {1, 2, 2, 2}, {2, 3}, 1e-5f, migraphx::shape::float_type, {"gamma", migraphx::shape::float_type}, {"beta", migraphx::shape::float_type}); // Add sigmoid at the end of the program to represent the added SILU block auto* mm = p.get_main_module(); auto output = std::prev(mm->end()); auto sigmoid = mm->add_instruction(migraphx::make_op("sigmoid"), output); output = mm->add_instruction(migraphx::make_op("mul"), output, sigmoid); auto prog = optimize_onnx("group_norm_contrib_silu_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_invalid_bias_shape_test.cpp000066400000000000000000000025121510465702400274310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_norm_invalid_bias_shape_test) { EXPECT(test::throws([&] { read_onnx("group_norm_invalid_bias_shape_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_invalid_input_count_error_test.cpp000066400000000000000000000025301510465702400311130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_norm_invalid_input_count_error_test) { EXPECT(test::throws([&] { read_onnx("group_norm_invalid_input_count_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_invalid_input_shape_error_test.cpp000066400000000000000000000025301510465702400310630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_norm_invalid_input_shape_error_test) { EXPECT(test::throws([&] { read_onnx("group_norm_invalid_input_shape_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_invalid_num_groups_error_test.cpp000066400000000000000000000025261510465702400307470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_norm_invalid_num_groups_error_test) { EXPECT(test::throws([&] { read_onnx("group_norm_invalid_num_groups_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_invalid_scale_shape_test.cpp000066400000000000000000000025141510465702400276040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_norm_invalid_scale_shape_test) { EXPECT(test::throws([&] { read_onnx("group_norm_invalid_scale_shape_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_missing_attribute_error_test.cpp000066400000000000000000000025241510465702400305750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_norm_missing_attribute_error_test) { EXPECT(test::throws([&] { read_onnx("group_norm_missing_attribute_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_small_eps_bf16_test.cpp000066400000000000000000000035771510465702400264360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_small_eps_bf16_test) { migraphx::program p = make_group_norm({1, 4, 2}, {4}, {4}, {1, 2, 2, 2}, {2, 3}, 1e-7f, migraphx::shape::bf16_type, {"scale", migraphx::shape::bf16_type}, {"bias", migraphx::shape::bf16_type}); auto prog = optimize_onnx("group_norm_small_eps_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_norm_small_eps_half_test.cpp000066400000000000000000000035771510465702400266120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(group_norm_small_eps_half_test) { migraphx::program p = make_group_norm({1, 4, 2}, {4}, {4}, {1, 2, 2, 2}, {2, 3}, 1e-7f, migraphx::shape::half_type, {"scale", migraphx::shape::half_type}, {"bias", migraphx::shape::half_type}); auto prog = optimize_onnx("group_norm_small_eps_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_query_attention_defaults_test.cpp000066400000000000000000000067461510465702400277300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_query_attention_defaults_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape qkvs{migraphx::shape::half_type, {1, 1, 12288}}; migraphx::shape pkvs{migraphx::shape::half_type, {1, 32, 4096, 128}}; migraphx::shape kvs{migraphx::shape::float_type, {1}}; migraphx::shape consts{migraphx::shape::int32_type, {1}}; migraphx::shape cs{migraphx::shape::half_type, {4096, 64}}; migraphx::shape outs{migraphx::shape::half_type, {1, 1, 4096}}; std::vector cs_data(cs.elements(), 1.0); auto slk = mm->add_literal(migraphx::literal{consts, {1}}); auto tsl = mm->add_literal(migraphx::literal{consts, {2}}); auto cc = mm->add_literal(migraphx::literal{cs, cs_data}); auto sc = mm->add_literal(migraphx::literal{cs, cs_data}); auto qkv = mm->add_parameter("qkv", qkvs); auto key = mm->add_parameter("key", kvs); auto value = mm->add_parameter("value", kvs); auto pk = mm->add_parameter("past_key_values_key", pkvs); auto pv = mm->add_parameter("past_key_values_value", pkvs); auto gqa = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 0}, {"kv_num_heads", 0}, {"local_window_size", -1}, {"num_heads", 1}, {"rotary_interleaved", 0}, {"scale", 0.0}}), qkv, key, value, pk, pv, slk, tsl, cc, sc); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), gqa); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), gqa); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), gqa); auto prog = optimize_onnx("group_query_attention_defaults_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_query_attention_invalid_test.cpp000066400000000000000000000027261510465702400275410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_query_attention_invalid_test) { EXPECT(test::throws([&] { read_onnx("group_query_attention_invalid_test.onnx"); })); } TEST_CASE(group_query_attention_softcap_test) { EXPECT(test::throws([&] { read_onnx("group_query_attention_softcap_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_query_attention_non_packed_qkv.cpp000066400000000000000000000070371510465702400300360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_query_attention_non_packed_qkv_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape qkvs{migraphx::shape::half_type, {1, 1, 4096}}; migraphx::shape pkvs{migraphx::shape::half_type, {1, 32, 4096, 128}}; migraphx::shape consts{migraphx::shape::int32_type, {1}}; migraphx::shape cs{migraphx::shape::half_type, {4096, 64}}; migraphx::shape outs{migraphx::shape::half_type, {1, 1, 4096}}; std::vector cs_data(cs.elements(), 1.0); auto slk = mm->add_literal(migraphx::literal{consts, {1}}); auto tsl = mm->add_literal(migraphx::literal{consts, {2}}); auto cc = mm->add_literal(migraphx::literal{cs, cs_data}); auto sc = mm->add_literal(migraphx::literal{cs, cs_data}); auto qkv = mm->add_parameter("query", qkvs); auto key = mm->add_parameter("key", qkvs); auto value = mm->add_parameter("value", qkvs); auto pk = mm->add_parameter("past_key_values_key", pkvs); auto pv = mm->add_parameter("past_key_values_value", pkvs); qkv = mm->add_instruction(migraphx::make_op("concat", {{"axis", 2}}), qkv, key, value); auto gqa = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 1}, {"kv_num_heads", 32}, {"local_window_size", -1}, {"num_heads", 32}, {"rotary_interleaved", 0}, {"scale", 1.0}}), qkv, key, value, pk, pv, slk, tsl, cc, sc); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), gqa); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), gqa); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), gqa); auto prog = optimize_onnx("group_query_attention_non_packed_qkv_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/group_query_attention_test.cpp000066400000000000000000000067261510465702400260370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(group_query_attention_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape qkvs{migraphx::shape::half_type, {1, 1, 12288}}; migraphx::shape pkvs{migraphx::shape::half_type, {1, 32, 4096, 128}}; migraphx::shape kvs{migraphx::shape::float_type, {1}}; migraphx::shape consts{migraphx::shape::int32_type, {1}}; migraphx::shape cs{migraphx::shape::half_type, {4096, 64}}; migraphx::shape outs{migraphx::shape::half_type, {1, 1, 4096}}; std::vector cs_data(cs.elements(), 1.0); auto slk = mm->add_literal(migraphx::literal{consts, {1}}); auto tsl = mm->add_literal(migraphx::literal{consts, {2}}); auto cc = mm->add_literal(migraphx::literal{cs, cs_data}); auto sc = mm->add_literal(migraphx::literal{cs, cs_data}); auto qkv = mm->add_parameter("qkv", qkvs); auto key = mm->add_parameter("key", kvs); auto value = mm->add_parameter("value", kvs); auto pk = mm->add_parameter("past_key_values_key", pkvs); auto pv = mm->add_parameter("past_key_values_value", pkvs); auto gqa = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 1}, {"kv_num_heads", 32}, {"local_window_size", -1}, {"num_heads", 32}, {"rotary_interleaved", 0}, {"scale", 1.0}}), qkv, key, value, pk, pv, slk, tsl, cc, sc); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), gqa); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), gqa); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), gqa); auto prog = optimize_onnx("group_query_attention_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardmax_axis_neg_test.cpp000066400000000000000000000042771510465702400246710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardmax_axis_neg_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::half_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", -3}}), input); auto zero_data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", -3}}), zero_data, indices, updates); auto prog = optimize_onnx("hardmax_axis_neg_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardmax_axis_neg_ver11_test.cpp000066400000000000000000000046201510465702400256770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardmax_axis_neg_ver11_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::half_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); input = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 24}}}), input); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 1}}), input); auto zero_data = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto output = mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 1}}), zero_data, indices, updates); mm->add_instruction(migraphx::make_op("reshape", {{"dims", input_lens}}), output); auto prog = optimize_onnx("hardmax_axis_neg_ver11_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardmax_axis_test.cpp000066400000000000000000000042671510465702400240370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardmax_axis_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::double_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 2}}), input); auto zero_data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 2}}), zero_data, indices, updates); auto prog = optimize_onnx("hardmax_axis_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardmax_axis_ver11_test.cpp000066400000000000000000000046121510465702400250470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardmax_axis_ver11_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::double_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); input = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 12}}}), input); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 1}}), input); auto zero_data = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto output = mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 1}}), zero_data, indices, updates); mm->add_instruction(migraphx::make_op("reshape", {{"dims", input_lens}}), output); auto prog = optimize_onnx("hardmax_axis_ver11_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardmax_default_test.cpp000066400000000000000000000042761510465702400245170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardmax_default_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", -1}}), input); auto zero_data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", -1}}), zero_data, indices, updates); auto prog = optimize_onnx("hardmax_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardmax_default_ver11_test.cpp000066400000000000000000000046171510465702400255340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardmax_default_ver11_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); input = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 24}}}), input); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 1}}), input); auto zero_data = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto output = mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 1}}), zero_data, indices, updates); mm->add_instruction(migraphx::make_op("reshape", {{"dims", input_lens}}), output); auto prog = optimize_onnx("hardmax_default_ver11_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardsigmoid_bf16_test.cpp000066400000000000000000000051631510465702400244730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardsigmoid_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 3, 4, 5}; auto input_type = migraphx::shape::bf16_type; migraphx::shape s{input_type, input_lens}; auto x = mm->add_parameter("x", s); float alpha = 0.2; float beta = 0.5; auto mb_alpha = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {alpha}})); auto mb_beta = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {beta}})); auto mb_zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto mb_one = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto mul = mm->add_instruction(migraphx::make_op("mul"), mb_alpha, x); auto add = mm->add_instruction(migraphx::make_op("add"), mb_beta, mul); mm->add_instruction(migraphx::make_op("clip"), add, mb_zero, mb_one); auto prog = optimize_onnx("hardsigmoid_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardsigmoid_default_test.cpp000066400000000000000000000051721510465702400253610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardsigmoid_default_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 3, 4, 5}; auto input_type = migraphx::shape::float_type; migraphx::shape s{input_type, input_lens}; auto x = mm->add_parameter("x", s); float alpha = 0.2; float beta = 0.5; auto mb_alpha = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {alpha}})); auto mb_beta = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {beta}})); auto mb_zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto mb_one = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto mul = mm->add_instruction(migraphx::make_op("mul"), mb_alpha, x); auto add = mm->add_instruction(migraphx::make_op("add"), mb_beta, mul); mm->add_instruction(migraphx::make_op("clip"), add, mb_zero, mb_one); auto prog = optimize_onnx("hardsigmoid_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardsigmoid_double_test.cpp000066400000000000000000000051711510465702400252060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardsigmoid_double_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 3, 4, 5}; auto input_type = migraphx::shape::double_type; migraphx::shape s{input_type, input_lens}; auto x = mm->add_parameter("x", s); float alpha = 0.3; float beta = 0.7; auto mb_alpha = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {alpha}})); auto mb_beta = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {beta}})); auto mb_zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto mb_one = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto mul = mm->add_instruction(migraphx::make_op("mul"), mb_alpha, x); auto add = mm->add_instruction(migraphx::make_op("add"), mb_beta, mul); mm->add_instruction(migraphx::make_op("clip"), add, mb_zero, mb_one); auto prog = optimize_onnx("hardsigmoid_double_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardsigmoid_half_test.cpp000066400000000000000000000051631510465702400246470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardsigmoid_half_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 3, 4, 5}; auto input_type = migraphx::shape::half_type; migraphx::shape s{input_type, input_lens}; auto x = mm->add_parameter("x", s); float alpha = 0.2; float beta = 0.5; auto mb_alpha = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {alpha}})); auto mb_beta = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {beta}})); auto mb_zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto mb_one = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto mul = mm->add_instruction(migraphx::make_op("mul"), mb_alpha, x); auto add = mm->add_instruction(migraphx::make_op("add"), mb_beta, mul); mm->add_instruction(migraphx::make_op("clip"), add, mb_zero, mb_one); auto prog = optimize_onnx("hardsigmoid_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/hardswish_test.cpp000066400000000000000000000053151510465702400233560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(hardswish_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{2, 5}; auto input_type = migraphx::shape::float_type; migraphx::shape s{input_type, input_lens}; auto x = mm->add_parameter("x", s); float alpha = 1.0 / 6.0; float beta = 0.5; auto mb_alpha = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {alpha}})); auto mb_beta = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {beta}})); auto mb_zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto mb_one = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto mul = mm->add_instruction(migraphx::make_op("mul"), mb_alpha, x); auto add = mm->add_instruction(migraphx::make_op("add"), mb_beta, mul); auto hardsigmoid = mm->add_instruction(migraphx::make_op("clip"), add, mb_zero, mb_one); mm->add_instruction(migraphx::make_op("mul"), x, hardsigmoid); auto prog = optimize_onnx("hardswish_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_else_test.cpp000066400000000000000000000045301510465702400227660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_else_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector ones(s.elements(), 1.0f); std::vector rand = {1.3865, -0.494756, -0.283504, 0.200491, -0.490031, 1.32388}; auto l1 = mm->add_literal(s, ones); auto l2 = mm->add_literal(s, rand); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto cond = mm->add_parameter("cond", sc); auto* then_mod = p.create_module("If_5_if"); auto rt = then_mod->add_instruction(migraphx::make_op("add"), x, l1); then_mod->add_return({rt}); auto* else_mod = p.create_module("If_5_else"); auto re = else_mod->add_instruction(migraphx::make_op("mul"), y, l2); else_mod->add_return({re}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); auto prog = read_onnx("if_else_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_else_test_inlined.cpp000066400000000000000000000036441510465702400244750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_else_test_inlined) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; mm->add_literal(migraphx::literal(sc, {0})); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector ones(s.elements(), 1.0f); mm->add_literal(s, ones); std::vector rand = {0.811412, -0.949771, -0.169276, 0.36552, -0.14801, 2.07061}; auto l2 = mm->add_literal(s, rand); mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto re = mm->add_instruction(migraphx::make_op("mul"), y, l2); mm->add_return({re}); auto prog = read_onnx("if_else_test_inlined.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_literal_test.cpp000066400000000000000000000043341510465702400234740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_literal_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", cond_s); migraphx::shape s{migraphx::shape::float_type, {5}}; auto* then_mod = p.create_module("If_1_if"); std::vector data1 = {1, 2, 3, 4, 5}; auto l1 = then_mod->add_literal(migraphx::literal(s, data1)); then_mod->add_literal({}); then_mod->add_return({l1}); auto* else_mod = p.create_module("If_1_else"); std::vector data2 = {5, 4, 3, 2, 1}; auto l2 = else_mod->add_literal(migraphx::literal(s, data2)); else_mod->add_literal({}); else_mod->add_return({l2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); auto prog = read_onnx("if_literal_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_param_excp1_test.cpp000066400000000000000000000024541510465702400242410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_param_excp1_test) { EXPECT(test::throws([&] { read_onnx("if_param_excp1_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_param_excp_test.cpp000066400000000000000000000024521510465702400241560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_param_excp_test) { EXPECT(test::throws([&] { read_onnx("if_param_excp_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_param_test.cpp000066400000000000000000000050011510465702400231300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_param_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", cond_s); migraphx::shape ds{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", ds); auto y = mm->add_parameter("y", ds); auto* then_mod = p.create_module("If_3_if"); std::vector data1 = {0.384804, -1.77948, -0.453775, 0.477438, -1.06333, -1.12893}; auto l1 = then_mod->add_literal(migraphx::literal(ds, data1)); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), x, l1); then_mod->add_return({a1}); auto* else_mod = p.create_module("If_3_else"); std::vector data2 = {-0.258047, 0.360394, 0.536804, -0.577762, 1.0217, 1.02442}; auto l2 = else_mod->add_literal(migraphx::literal(ds, data2)); auto a2 = else_mod->add_instruction(migraphx::make_op("mul"), y, l2); else_mod->add_return({a2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); auto prog = read_onnx("if_param_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_pl_test.cpp000066400000000000000000000052211510465702400224470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_pl_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; migraphx::shape ys{migraphx::shape::float_type, {3, 3}}; std::vector datax = {1, 2, 3, 4, 5, 6}; std::vector datay = {8, 7, 6, 5, 4, 3, 2, 1, 0}; auto lx = mm->add_literal(migraphx::literal(xs, datax)); auto ly = mm->add_literal(migraphx::literal(ys, datay)); auto cond = mm->add_parameter("cond", cond_s); auto x = mm->add_parameter("x", xs); auto y = mm->add_parameter("y", ys); auto* then_mod = p.create_module("If_5_if"); auto l1 = then_mod->add_literal(migraphx::literal(ys, datay)); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), x, lx); then_mod->add_return({a1, l1}); auto* else_mod = p.create_module("If_5_else"); auto l2 = else_mod->add_literal(migraphx::literal(xs, datax)); auto a2 = else_mod->add_instruction(migraphx::make_op("mul"), y, ly); else_mod->add_return({l2, a2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r}); auto prog = read_onnx("if_pl_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_then_else_multi_output_shapes_inlined_test.cpp000066400000000000000000000041631510465702400317050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_then_else_multi_output_shapes_inlined_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; mm->add_literal(migraphx::literal(sc, {1})); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::shape s_trail{migraphx::shape::float_type, {2, 3, 1}}; std::vector ones(s.elements(), 1.0f); auto l1 = mm->add_literal(s_trail, ones); std::vector rand = {-1.01837, -0.305541, -0.254105, 0.892955, 1.38714, -0.584205}; mm->add_literal(s, rand); auto x = mm->add_parameter("x", s_trail); mm->add_parameter("y", s); auto rt = mm->add_instruction(migraphx::make_op("add"), x, l1); auto rt2 = mm->add_instruction(migraphx::make_op("add"), x, x); mm->add_return({rt, rt2}); auto prog = read_onnx("if_then_else_multi_output_shapes_inlined_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_then_else_multi_output_shapes_test.cpp000066400000000000000000000054641510465702400302100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_then_else_multi_output_shapes_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; migraphx::shape s{migraphx::shape::float_type, {2, 3, 1}}; migraphx::shape s_trail{migraphx::shape::float_type, {2, 3, 1}}; std::vector ones(s.elements(), 1.0f); auto l1 = mm->add_literal(s_trail, ones); std::vector rand = {-0.753997, 0.707831, -0.865795, 2.49574, 0.464937, -0.168745}; auto l2 = mm->add_literal(s, rand); auto x = mm->add_parameter("x", s_trail); auto y = mm->add_parameter("y", s); auto cond = mm->add_parameter("cond", sc); auto* then_mod = p.create_module("If_5_if"); auto rt = then_mod->add_instruction(migraphx::make_op("add"), x, l1); auto rt2 = then_mod->add_instruction(migraphx::make_op("add"), x, x); then_mod->add_return({rt, rt2}); auto* else_mod = p.create_module("If_5_else"); auto re = else_mod->add_instruction(migraphx::make_op("mul"), y, l2); auto re2 = else_mod->add_instruction(migraphx::make_op("sub"), y, l2); else_mod->add_return({re, re2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r1, r2}); auto prog = read_onnx("if_then_else_multi_output_shapes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_then_test.cpp000066400000000000000000000045321510465702400227760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_then_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector ones(s.elements(), 1.0f); std::vector rand = {-0.266913, -0.180328, -0.124268, -1.23768, 0.312334, 1.18475}; auto l1 = mm->add_literal(s, ones); auto l2 = mm->add_literal(s, rand); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto cond = mm->add_parameter("cond", sc); auto* then_mod = p.create_module("If_5_if"); auto rt = then_mod->add_instruction(migraphx::make_op("add"), x, l1); then_mod->add_return({rt}); auto* else_mod = p.create_module("If_5_else"); auto re = else_mod->add_instruction(migraphx::make_op("mul"), y, l2); else_mod->add_return({re}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); auto prog = read_onnx("if_then_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_then_test_inlined.cpp000066400000000000000000000036441510465702400245030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_then_test_inlined) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sc{migraphx::shape::bool_type, {1}}; mm->add_literal(migraphx::literal(sc, {1})); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector ones(s.elements(), 1.0f); auto l1 = mm->add_literal(s, ones); std::vector rand = {-1.26487, -2.42279, 0.990835, 1.63072, 0.812238, -0.174946}; mm->add_literal(s, rand); auto x = mm->add_parameter("x", s); mm->add_parameter("y", s); auto rt = mm->add_instruction(migraphx::make_op("add"), x, l1); mm->add_return({rt}); auto prog = read_onnx("if_then_test_inlined.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/if_tuple_test.cpp000066400000000000000000000061521510465702400231710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(if_tuple_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {1}}; auto l1 = mm->add_literal(migraphx::literal(sd, {1})); auto l2 = mm->add_literal(migraphx::literal(sd, {2})); auto l3 = mm->add_literal(migraphx::literal(sd, {3})); migraphx::shape sx{migraphx::shape::float_type, {1, 4}}; migraphx::shape sy{migraphx::shape::float_type, {3, 4}}; migraphx::shape sc{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", sc); auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sy); auto* then_mod = p.create_module("If_6_if"); auto m1 = then_mod->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l1); auto add0 = then_mod->add_instruction(migraphx::make_op("add"), x, m1); auto m2 = then_mod->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l2); auto mul0 = then_mod->add_instruction(migraphx::make_op("mul"), y, m2); then_mod->add_return({add0, mul0}); auto* else_mod = p.create_module("If_6_else"); auto me1 = else_mod->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l3); auto mul1 = else_mod->add_instruction(migraphx::make_op("mul"), x, me1); auto me2 = else_mod->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l3); auto add1 = else_mod->add_instruction(migraphx::make_op("add"), y, me2); else_mod->add_return({mul1, add1}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r0, r1}); auto prog = read_onnx("if_tuple_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/imagescaler_bf16_test.cpp000066400000000000000000000042131510465702400244500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(imagescaler_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {1, 3, 16, 16}}; auto l0 = mm->add_parameter("0", s); auto scale_val = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::bf16_type}, {0.5f}}); auto bias_vals = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::bf16_type, {3}}, {0.01, 0.02, 0.03}}); auto scaled_tensor = mm->add_instruction( migraphx::make_op("scalar", {{"scalar_bcst_dims", s.lens()}}), scale_val); auto img_scaled = mm->add_instruction(migraphx::make_op("mul"), l0, scaled_tensor); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), bias_vals); mm->add_instruction(migraphx::make_op("add"), img_scaled, bias_bcast); auto prog = optimize_onnx("imagescaler_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/imagescaler_half_test.cpp000066400000000000000000000042131510465702400246240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(imagescaler_half_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {1, 3, 16, 16}}; auto l0 = mm->add_parameter("0", s); auto scale_val = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {0.5f}}); auto bias_vals = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::half_type, {3}}, {0.01, 0.02, 0.03}}); auto scaled_tensor = mm->add_instruction( migraphx::make_op("scalar", {{"scalar_bcst_dims", s.lens()}}), scale_val); auto img_scaled = mm->add_instruction(migraphx::make_op("mul"), l0, scaled_tensor); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), bias_vals); mm->add_instruction(migraphx::make_op("add"), img_scaled, bias_bcast); auto prog = optimize_onnx("imagescaler_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/imagescaler_test.cpp000066400000000000000000000041001510465702400236250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(imagescaler_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 3, 16, 16}}; auto l0 = mm->add_parameter("0", s); auto scale_val = mm->add_literal(0.5f); auto bias_vals = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {3}}, {0.01, 0.02, 0.03}}); auto scaled_tensor = mm->add_instruction( migraphx::make_op("scalar", {{"scalar_bcst_dims", s.lens()}}), scale_val); auto img_scaled = mm->add_instruction(migraphx::make_op("mul"), l0, scaled_tensor); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), bias_vals); mm->add_instruction(migraphx::make_op("add"), img_scaled, bias_bcast); auto prog = optimize_onnx("imagescaler_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/implicit_add_bcast_test.cpp000066400000000000000000000047431510465702400251640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(implicit_add_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3, 4, 1}}); auto l3 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 5}}}), l1); mm->add_instruction(migraphx::make_op("add"), l0, l3); auto prog = optimize_onnx("implicit_add_bcast_test.onnx"); EXPECT(p == prog); } TEST_CASE(implicit_add_bcast_user_input_shape_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {4, 5, 1}}); auto l3 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 4, 5, 6}}}), l1); auto r = mm->add_instruction(migraphx::make_op("add"), l0, l3); mm->add_return({r}); migraphx::onnx_options options; options.map_input_dims["0"] = {3, 4, 5, 6}; options.map_input_dims["1"] = {4, 5, 1}; auto prog = read_onnx("implicit_add_bcast_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/implicit_pow_bcast_test.cpp000066400000000000000000000033531510465702400252350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(implicit_pow_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3, 4, 1}}); auto l3 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 5}}}), l1); mm->add_instruction(migraphx::make_op("pow"), l0, l3); auto prog = optimize_onnx("implicit_pow_bcast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/implicit_sub_bcast_test.cpp000066400000000000000000000033521510465702400252200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(implicit_sub_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::uint64_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::uint64_type, {4, 5}}); auto l3 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 5}}}), l1); mm->add_instruction(migraphx::make_op("sub"), l0, l3); auto prog = optimize_onnx("implicit_sub_bcast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/initializer_not_an_input.cpp000066400000000000000000000033671510465702400254300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(initializer_not_an_input) { migraphx::program p; auto* mm = p.get_main_module(); std::vector w = {1, 2, 3, 4, 5, 6, 7, 8}; auto l1 = mm->add_literal(migraphx::literal({migraphx::shape::float_type, {2, 4}}, w)); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {5, 2}}); migraphx::add_apply_alpha_beta(*mm, {l0, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto prog = optimize_onnx("initializer_not_an_input.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_bf16_test.cpp000066400000000000000000000055271510465702400250440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_bf16_test) { std::vector dims{1, 2, 3, 3}; migraphx::shape s1{migraphx::shape::bf16_type, dims}; migraphx::shape s2{migraphx::shape::bf16_type, {2}}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("0", s1); auto scale = mm->add_parameter("1", s2); auto bias = mm->add_parameter("2", s2); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto l0 = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto l1 = add_common_op(*mm, migraphx::make_op("sqdiff"), {x, mean}); auto variance = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), l1); // type of epsilon_literal is same as 0'th input; convert instruction will be added by // add_common_op auto epsilon_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::bf16_type}, {1e-5}}); auto l2 = add_common_op(*mm, migraphx::make_op("add"), {variance, epsilon_literal}); auto l3 = mm->add_instruction(migraphx::make_op("rsqrt"), l2); auto l4 = add_common_op(*mm, migraphx::make_op("mul"), {l0, l3}); auto scale_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", dims}}), scale); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", dims}}), bias); auto l5 = mm->add_instruction(migraphx::make_op("mul"), l4, scale_bcast); mm->add_instruction(migraphx::make_op("add"), l5, bias_bcast); auto prog = optimize_onnx("instance_norm_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_dyn_batch_bf16_test.cpp000066400000000000000000000060721510465702400270530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_dyn_batch_bf16_test) { // instancenorm with bf16 type, dynamic input in the 0'th (batch) dimension migraphx::shape s1{migraphx::shape::bf16_type, {{1, 2, {2}}, {2, 2}, {3, 3}, {3, 3}}}; migraphx::shape s2{migraphx::shape::bf16_type, {2}}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("0", s1); auto scale = mm->add_parameter("1", s2); auto bias = mm->add_parameter("2", s2); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto l0 = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto l1 = add_common_op(*mm, migraphx::make_op("sqdiff"), {x, mean}); auto variance = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), l1); // type of epsilon_literal is same as 0'th input; convert instruction will be added by // add_common_op auto epsilon_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::bf16_type}, {1e-5}}); auto l2 = add_common_op(*mm, migraphx::make_op("add"), {variance, epsilon_literal}); auto l3 = mm->add_instruction(migraphx::make_op("rsqrt"), l2); auto l4 = add_common_op(*mm, migraphx::make_op("mul"), {l0, l3}); auto scale_bcast = mm->add_instruction(migraphx::make_op("broadcast", {{"axis", 1}}), scale, x); auto bias_bcast = mm->add_instruction(migraphx::make_op("broadcast", {{"axis", 1}}), bias, x); auto l5 = mm->add_instruction(migraphx::make_op("mul"), l4, scale_bcast); auto instance_norm_bf16 = mm->add_instruction(migraphx::make_op("add"), l5, bias_bcast); mm->add_return({instance_norm_bf16}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 2, {2}}; auto prog = read_onnx("instance_norm_dyn_batch_bf16_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_dyn_batch_half_test.cpp000066400000000000000000000072641510465702400272330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_dyn_batch_half_test) { // instancenorm with half type, dynamic input in the 0'th (batch) dimension migraphx::shape s1{migraphx::shape::half_type, {{1, 2, {2}}, {2, 2}, {3, 3}, {3, 3}}}; migraphx::shape s2{migraphx::shape::half_type, {2}}; migraphx::program p; auto* mm = p.get_main_module(); auto x_fp16 = mm->add_parameter("0", s1); auto scale_fp16 = mm->add_parameter("1", s2); auto bias_fp16 = mm->add_parameter("2", s2); // conversion of half type to float is enabled by default auto x = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x_fp16); auto scale = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), scale_fp16); auto bias = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), bias_fp16); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto l0 = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto l1 = add_common_op(*mm, migraphx::make_op("sqdiff"), {x, mean}); auto variance = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), l1); // type of epsilon_literal is same as 0'th input; convert instruction will be added by // add_common_op auto epsilon_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {1e-5}}); auto l2 = add_common_op(*mm, migraphx::make_op("add"), {variance, epsilon_literal}); auto l3 = mm->add_instruction(migraphx::make_op("rsqrt"), l2); auto l4 = add_common_op(*mm, migraphx::make_op("mul"), {l0, l3}); auto scale_bcast = mm->add_instruction(migraphx::make_op("broadcast", {{"axis", 1}}), scale, x); auto bias_bcast = mm->add_instruction(migraphx::make_op("broadcast", {{"axis", 1}}), bias, x); auto l5 = mm->add_instruction(migraphx::make_op("mul"), l4, scale_bcast); auto instance_norm_fp32 = mm->add_instruction(migraphx::make_op("add"), l5, bias_bcast); auto ret = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), instance_norm_fp32); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 2, {2}}; auto prog = read_onnx("instance_norm_dyn_batch_half_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_dyn_batch_test.cpp000066400000000000000000000056551510465702400262430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_dyn_batch_test) { // instancenorm with dynamic input in the 0'th (batch) dimension migraphx::shape s1{migraphx::shape::float_type, {{1, 2, {2}}, {2, 2}, {3, 3}, {3, 3}}}; migraphx::shape s2{migraphx::shape::float_type, {2}}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("0", s1); auto scale = mm->add_parameter("1", s2); auto bias = mm->add_parameter("2", s2); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto l1 = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto l0 = add_common_op(*mm, migraphx::make_op("sqdiff"), {x, mean}); auto variance = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), l0); auto epsilon_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {1e-5}}); auto l2 = add_common_op(*mm, migraphx::make_op("add"), {variance, epsilon_literal}); auto l3 = mm->add_instruction(migraphx::make_op("rsqrt"), l2); auto l4 = add_common_op(*mm, migraphx::make_op("mul"), {l1, l3}); auto scale_bcast = mm->add_instruction(migraphx::make_op("broadcast", {{"axis", 1}}), scale, x); auto bias_bcast = mm->add_instruction(migraphx::make_op("broadcast", {{"axis", 1}}), bias, x); auto l5 = mm->add_instruction(migraphx::make_op("mul"), l4, scale_bcast); auto ret = mm->add_instruction(migraphx::make_op("add"), l5, bias_bcast); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 2, {2}}; auto prog = read_onnx("instance_norm_dyn_batch_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_half_test.cpp000066400000000000000000000067671510465702400252270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_half_test) { std::vector dims{1, 2, 3, 3}; migraphx::shape s1{migraphx::shape::half_type, dims}; migraphx::shape s2{migraphx::shape::half_type, {2}}; migraphx::program p; auto* mm = p.get_main_module(); auto x_fp16 = mm->add_parameter("0", s1); auto scale_fp16 = mm->add_parameter("1", s2); auto bias_fp16 = mm->add_parameter("2", s2); // conversion of half type to float is enabled by default auto x = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x_fp16); auto scale = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), scale_fp16); auto bias = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), bias_fp16); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto l0 = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto l1 = add_common_op(*mm, migraphx::make_op("sqdiff"), {x, mean}); auto variance = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), l1); // type of epsilon_literal is same as 0'th input; convert instruction will be added by // add_common_op auto epsilon_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {1e-5}}); auto l2 = add_common_op(*mm, migraphx::make_op("add"), {variance, epsilon_literal}); auto l3 = mm->add_instruction(migraphx::make_op("rsqrt"), l2); auto l4 = add_common_op(*mm, migraphx::make_op("mul"), {l0, l3}); auto scale_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", dims}}), scale); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", dims}}), bias); auto l5 = mm->add_instruction(migraphx::make_op("mul"), l4, scale_bcast); auto instance_norm_fp32 = mm->add_instruction(migraphx::make_op("add"), l5, bias_bcast); mm->add_instruction(migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), instance_norm_fp32); auto prog = optimize_onnx("instance_norm_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_invalid_type_test.cpp000066400000000000000000000025041510465702400267650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_invalid_type_test) { EXPECT(test::throws([&] { read_onnx("instance_norm_invalid_type_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_nonbroadcastable_test.cpp000066400000000000000000000025141510465702400276000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_nonbroadcastable_test) { EXPECT(test::throws([&] { read_onnx("instance_norm_nonbroadcastable_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_test.cpp000066400000000000000000000054611510465702400242230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_test) { std::vector dims{1, 2, 3, 3}; migraphx::shape s1{migraphx::shape::float_type, dims}; migraphx::shape s2{migraphx::shape::float_type, {2}}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("0", s1); auto scale = mm->add_parameter("1", s2); auto bias = mm->add_parameter("2", s2); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto l1 = add_common_op(*mm, migraphx::make_op("sub"), {x, mean}); auto l0 = add_common_op(*mm, migraphx::make_op("sqdiff"), {x, mean}); auto variance = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), l0); auto epsilon_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {1e-5}}); auto l2 = add_common_op(*mm, migraphx::make_op("add"), {variance, epsilon_literal}); auto l3 = mm->add_instruction(migraphx::make_op("rsqrt"), l2); auto l4 = add_common_op(*mm, migraphx::make_op("mul"), {l1, l3}); auto scale_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", dims}}), scale); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", dims}}), bias); auto l5 = mm->add_instruction(migraphx::make_op("mul"), l4, scale_bcast); auto ret = mm->add_instruction(migraphx::make_op("add"), l5, bias_bcast); mm->add_return({ret}); migraphx::onnx_options options; auto prog = read_onnx("instance_norm_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/instance_norm_type_mismatch_test.cpp000066400000000000000000000025061510465702400271460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(instance_norm_type_mismatch_test) { EXPECT(test::throws([&] { read_onnx("instance_norm_type_mismatch_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/isinf_bf16_test.cpp000066400000000000000000000030531510465702400233050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(isinf_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {2, 3}}; auto t1 = mm->add_parameter("t1", s); auto ret = mm->add_instruction(migraphx::make_op("isinf"), t1); mm->add_return({ret}); auto prog = read_onnx("isinf_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/isinf_double_pos_test.cpp000066400000000000000000000042151510465702400247030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(isinf_double_pos_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 3}}; auto t1 = mm->add_parameter("t1", s); auto is_inf = mm->add_instruction(migraphx::make_op("isinf"), t1); auto zero_l = mm->add_literal(migraphx::literal{migraphx::shape::double_type, {0}}); auto mb_zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), zero_l); auto is_neg = mm->add_instruction(migraphx::make_op("greater"), t1, mb_zero); if(is_neg->get_shape().type() != migraphx::shape::bool_type) { is_neg = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), is_neg); } auto ret = mm->add_instruction(migraphx::make_op("logical_and"), is_inf, is_neg); mm->add_return({ret}); auto prog = read_onnx("isinf_double_pos_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/isinf_half_test.cpp000066400000000000000000000030531510465702400234610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(isinf_half_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 3}}; auto t1 = mm->add_parameter("t1", s); auto ret = mm->add_instruction(migraphx::make_op("isinf"), t1); mm->add_return({ret}); auto prog = read_onnx("isinf_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/isinf_neg_test.cpp000066400000000000000000000041721510465702400233230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(isinf_neg_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto t1 = mm->add_parameter("t1", s); auto is_inf = mm->add_instruction(migraphx::make_op("isinf"), t1); auto zero_l = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0}}); auto mb_zero = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), zero_l); auto is_neg = mm->add_instruction(migraphx::make_op("less"), t1, mb_zero); if(is_neg->get_shape().type() != migraphx::shape::bool_type) { is_neg = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), is_neg); } auto ret = mm->add_instruction(migraphx::make_op("logical_and"), is_inf, is_neg); mm->add_return({ret}); auto prog = read_onnx("isinf_neg_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/isinf_no_detect_test.cpp000066400000000000000000000032651510465702400245200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(isinf_no_detect_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; mm->add_parameter("t1", s); auto ret = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::bool_type}, {false}})); mm->add_return({ret}); auto prog = read_onnx("isinf_no_detect_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/isnan_bf16_test.cpp000066400000000000000000000030531510465702400233050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(isnan_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {2, 3}}; auto t1 = mm->add_parameter("t1", s); auto ret = mm->add_instruction(migraphx::make_op("isnan"), t1); mm->add_return({ret}); auto prog = read_onnx("isnan_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/isnan_float_test.cpp000066400000000000000000000030561510465702400236570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(isnan_float_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto t1 = mm->add_parameter("t1", s); auto ret = mm->add_instruction(migraphx::make_op("isnan"), t1); mm->add_return({ret}); auto prog = read_onnx("isnan_float_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/isnan_half_test.cpp000066400000000000000000000030531510465702400234610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(isnan_half_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 3}}; auto t1 = mm->add_parameter("t1", s); auto ret = mm->add_instruction(migraphx::make_op("isnan"), t1); mm->add_return({ret}); auto prog = read_onnx("isnan_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_2d_axis_one_test.cpp000066400000000000000000000031611510465702400260000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_2d_axis_one_test) { migraphx::program p = make_layer_norm({3, 4}, {4}, {1}, 1); auto prog = optimize_onnx("layer_norm_2d_axis_one_test.onnx"); EXPECT(p == prog); } TEST_CASE(layer_norm_2d_axis_minus_one_test) { migraphx::program p = make_layer_norm({3, 4}, {4}, {1}, 1); auto prog = optimize_onnx("layer_norm_2d_axis_one_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_2d_axis_zero_test.cpp000066400000000000000000000026541510465702400262040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_2d_axis_zero_test) { migraphx::program p = make_layer_norm({3, 4}, {3, 4}, {0, 1}, 0); auto prog = optimize_onnx("layer_norm_2d_axis_zero_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_3d_bf16_test.cpp000066400000000000000000000027271510465702400247410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_3d_bf16_test) { migraphx::program p = make_layer_norm({1, 4, 2}, {2}, {2}, 2, false, true, 1e-5f, migraphx::shape::bf16_type); auto prog = optimize_onnx("layer_norm_3d_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_3d_half_stash_off_epsilon_test.cpp000066400000000000000000000027741510465702400307040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_3d_half_stash_off_epsilon_test) { migraphx::program p = make_layer_norm({1, 4, 2}, {2}, {2}, 2, false, false, 1e-4f, migraphx::shape::half_type); auto prog = optimize_onnx("layer_norm_3d_half_stash_off_epsilon_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_3d_half_stash_off_test.cpp000066400000000000000000000027541510465702400271510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_3d_half_stash_off_test) { migraphx::program p = make_layer_norm({1, 4, 2}, {2}, {2}, 2, false, false, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("layer_norm_3d_half_stash_off_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_3d_half_test.cpp000066400000000000000000000027271510465702400251150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_3d_half_test) { migraphx::program p = make_layer_norm({1, 4, 2}, {2}, {2}, 2, false, true, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("layer_norm_3d_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_3d_scale_bias_test.cpp000066400000000000000000000026611510465702400262650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_3d_scale_bias_test) { migraphx::program p = make_layer_norm({2, 5, 7}, {2, 1, 7}, {2}, 2); auto prog = optimize_onnx("layer_norm_3d_scale_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_3d_test.cpp000066400000000000000000000026251510465702400241200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_3d_test) { migraphx::program p = make_layer_norm({1, 4, 2}, {2}, {2}, 2); auto prog = optimize_onnx("layer_norm_3d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_4d_bf16_test.cpp000066400000000000000000000027321510465702400247360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_4d_bf16_test) { migraphx::program p = make_layer_norm({3, 3, 3, 3}, {3}, {3}, 3, false, true, 1e-5f, migraphx::shape::bf16_type); auto prog = optimize_onnx("layer_norm_4d_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_4d_half_test.cpp000066400000000000000000000027321510465702400251120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_4d_half_test) { migraphx::program p = make_layer_norm({3, 3, 3, 3}, {3}, {3}, 3, false, true, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("layer_norm_4d_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_4d_test.cpp000066400000000000000000000026301510465702400241150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_4d_test) { migraphx::program p = make_layer_norm({3, 3, 3, 3}, {3}, {3}, 3); auto prog = optimize_onnx("layer_norm_4d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_invalid_axis_error_test.cpp000066400000000000000000000025121510465702400274700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(layer_norm_invalid_axis_error_test) { EXPECT(test::throws([&] { read_onnx("layer_norm_invalid_axis_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_invalid_bias_type_test.cpp000066400000000000000000000025061510465702400272750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(layer_norm_invalid_bias_type_test) { EXPECT(test::throws([&] { read_onnx("layer_norm_3d_invalid_bias_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_invalid_input_count_error_test.cpp000066400000000000000000000025301510465702400310730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(layer_norm_invalid_input_count_error_test) { EXPECT(test::throws([&] { read_onnx("layer_norm_invalid_input_count_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_invalid_input_type_test.cpp000066400000000000000000000025071510465702400275170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(layer_norm_invalid_input_type_test) { EXPECT(test::throws([&] { read_onnx("layer_norm_3d_invalid_int8_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_invalid_minus_axis_error_test.cpp000066400000000000000000000025261510465702400307100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(layer_norm_invalid_minus_axis_error_test) { EXPECT(test::throws([&] { read_onnx("layer_norm_invalid_minus_axis_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_invalid_scale_type_test.cpp000066400000000000000000000025101510465702400274410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(layer_norm_invalid_scale_type_test) { EXPECT(test::throws([&] { read_onnx("layer_norm_3d_invalid_scale_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_small_eps_bf16_test.cpp000066400000000000000000000027401510465702400264050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_small_eps_bf16_test) { migraphx::program p = make_layer_norm({1, 2}, {2}, {1}, 1, true, true, 1e-7, migraphx::shape::bf16_type); auto prog = optimize_onnx("layer_norm_small_eps_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_small_eps_half_test.cpp000066400000000000000000000027401510465702400265610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_small_eps_half_test) { migraphx::program p = make_layer_norm({1, 2}, {2}, {1}, 1, true, true, 1e-7, migraphx::shape::half_type); auto prog = optimize_onnx("layer_norm_small_eps_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/layer_norm_without_bias_test.cpp000066400000000000000000000026541510465702400263150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(layer_norm_without_bias_test) { migraphx::program p = make_layer_norm({1, 2}, {2}, {1}, 1, true); auto prog = optimize_onnx("layer_norm_without_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/leaky_relu_test.cpp000066400000000000000000000030471510465702400235160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(leaky_relu_test) { migraphx::program p; auto* mm = p.get_main_module(); float alpha = 0.01f; auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {3}}); mm->add_instruction(migraphx::make_op("leaky_relu", {{"alpha", alpha}}), l0); auto prog = optimize_onnx("leaky_relu_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/less_bool_test.cpp000066400000000000000000000035411510465702400233420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(less_bool_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sf{migraphx::shape::float_type, {2, 3}}; migraphx::shape sb{migraphx::shape::bool_type, {2, 3}}; auto input1 = mm->add_parameter("x1", sf); auto input2 = mm->add_parameter("x2", sb); auto cin1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), input1); auto ret = mm->add_instruction(migraphx::make_op("less"), cin1, input2); mm->add_return({ret}); auto prog = read_onnx("less_bool_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/less_test.cpp000066400000000000000000000036421510465702400223310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(less_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; auto input1 = mm->add_literal(migraphx::literal(s, data)); auto input2 = mm->add_parameter("x2", migraphx::shape{migraphx::shape::float_type, {2, 3}}); auto le = mm->add_instruction(migraphx::make_op("less"), input1, input2); auto ret = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), le); mm->add_return({ret}); auto prog = read_onnx("less_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/lessorequal_test.cpp000066400000000000000000000035311510465702400237170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(lessorequal_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input1 = mm->add_parameter("x1", migraphx::shape{migraphx::shape::float_type, {3}}); auto input2 = mm->add_parameter("x2", migraphx::shape{migraphx::shape::float_type, {3}}); auto temp = mm->add_instruction(migraphx::make_op("greater"), input1, input2); auto bt = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), temp); auto le = mm->add_instruction(migraphx::make_op("not"), bt); mm->add_return({le}); auto prog = read_onnx("lessorequal_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/log_test.cpp000066400000000000000000000027651510465702400221510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(log_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("log"), input); auto prog = optimize_onnx("log_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/logical_and_bcast_test.cpp000066400000000000000000000034321510465702400247700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(logical_and_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::bool_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::bool_type, {4, 5}}); auto l2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", l0->get_shape().lens()}}), l1); auto ret = mm->add_instruction(migraphx::make_op("logical_and"), l0, l2); mm->add_return({ret}); auto prog = read_onnx("logical_and_bcast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/logical_or_test.cpp000066400000000000000000000032231510465702400234700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(logical_or_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::bool_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::bool_type, {2, 3, 4, 5}}); auto ret = mm->add_instruction(migraphx::make_op("logical_or"), l0, l1); mm->add_return({ret}); auto prog = read_onnx("logical_or_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/logical_xor_bcast_test.cpp000066400000000000000000000034321510465702400250360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(logical_xor_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::bool_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::bool_type, {4, 1}}); auto l2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", l0->get_shape().lens()}}), l1); auto ret = mm->add_instruction(migraphx::make_op("logical_xor"), l0, l2); mm->add_return({ret}); auto prog = read_onnx("logical_xor_bcast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/logsoftmax_nonstd_input_test.cpp000066400000000000000000000033271510465702400263520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(logsoftmax_nonstd_input_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {6, 9}}); auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {1, 0}}, {"ends", {4, 4}}}), l0); auto l2 = mm->add_instruction(migraphx::make_op("logsoftmax", {{"axis", -1}}), l1); mm->add_return({l2}); auto prog = read_onnx("logsoftmax_nonstd_input_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/logsoftmax_test.cpp000066400000000000000000000030571510465702400235460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(logsoftmax_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); int axis = 1; mm->add_instruction(migraphx::make_op("logsoftmax", {{"axis", axis}}), l0); auto prog = optimize_onnx("logsoftmax_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/loop_default_test.cpp000066400000000000000000000055731510465702400240450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(loop_default_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape su{migraphx::shape::float_type}; auto a = mm->add_parameter("a", su); auto b = mm->add_parameter("b", su); migraphx::shape si{migraphx::shape::int64_type}; auto max_iter = mm->add_literal(migraphx::literal(si, {10})); migraphx::shape sc{migraphx::shape::bool_type}; auto icond = mm->add_literal(migraphx::literal(sc, {1})); mm->add_instruction(migraphx::make_op("undefined")); auto* body = p.create_module("Loop_3_loop"); body->add_parameter("iteration_num", {migraphx::shape::int64_type}); body->add_parameter("keep_going_inp", {migraphx::shape::bool_type}); auto var = body->add_parameter("b_in", su); auto ad = body->add_instruction(migraphx::make_op("add"), a, var); auto sb = body->add_instruction(migraphx::make_op("sub"), a, var); auto gt = body->add_instruction(migraphx::make_op("greater"), ad, sb); auto cv = body->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), gt); auto ad1 = body->add_instruction(migraphx::make_op("add"), sb, sb); body->add_return({cv, sb, ad, ad1}); auto lp = mm->add_instruction( migraphx::make_op("loop", {{"max_iterations", 10}}), {max_iter, icond, b}, {body}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), lp); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), lp); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), lp); mm->add_return({r0, r2}); auto prog = read_onnx("loop_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/loop_test.cpp000066400000000000000000000054031510465702400223310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(loop_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape si{migraphx::shape::int64_type, {1}}; auto max_iter = mm->add_parameter("max_trip_count", si); migraphx::shape sc{migraphx::shape::bool_type, {1}}; auto icond = mm->add_parameter("keep_going_cond", sc); migraphx::shape su{migraphx::shape::float_type, {1}}; auto a = mm->add_parameter("a", su); auto b = mm->add_parameter("b", su); auto* body = p.create_module("Loop_4_loop"); body->add_parameter("iteration_num", si); body->add_parameter("keep_going_inp", sc); auto var = body->add_parameter("b_in", su); auto ad = body->add_instruction(migraphx::make_op("add"), a, var); auto sb = body->add_instruction(migraphx::make_op("sub"), a, var); auto gt = body->add_instruction(migraphx::make_op("greater"), ad, sb); auto cv = body->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), gt); auto ad1 = body->add_instruction(migraphx::make_op("add"), sb, sb); body->add_return({cv, sb, ad, ad1}); auto lp = mm->add_instruction( migraphx::make_op("loop", {{"max_iterations", 10}}), {max_iter, icond, b}, {body}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), lp); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), lp); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), lp); mm->add_return({r0, r2}); auto prog = read_onnx("loop_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/lpnormalization_axis_error_test.cpp000066400000000000000000000025041510465702400270360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(lpnormalization_axis_error_test) { EXPECT(test::throws([&] { read_onnx("lpnormalization_axis_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/lpnormalization_default_test.cpp000066400000000000000000000051711510465702400263100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(lpnormalization_default_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape s{input_type, input_lens}; auto x = mm->add_parameter("x", s); std::ptrdiff_t axis = 0; auto p_val = mm->add_instruction(migraphx::make_op("mul"), x, x); auto norms = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {axis}}}), p_val); norms = mm->add_instruction(migraphx::make_op("sqrt"), norms); norms = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), norms); auto zero_mb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0.}})); auto one_mb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1.}})); auto is_zero = mm->add_instruction(migraphx::make_op("equal"), norms, zero_mb); auto norms_zeros_to_one = mm->add_instruction(migraphx::make_op("where"), is_zero, one_mb, norms); mm->add_instruction(migraphx::make_op("div"), x, norms_zeros_to_one); auto prog = optimize_onnx("lpnormalization_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/lpnormalization_p_error_test.cpp000066400000000000000000000024761510465702400263410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(lpnormalization_p_error_test) { EXPECT(test::throws([&] { read_onnx("lpnormalization_p_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/lppool_l1_test.cpp000066400000000000000000000036741510465702400232710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(lppool_l1_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 3, 5}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {0, 0}}, {"stride", {1}}, {"lengths", {3}}, {"dilations", {1}}, {"lp_order", 1}}), l0); auto prog = optimize_onnx("lppool_l1_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/lppool_l2_test.cpp000066400000000000000000000036741510465702400232720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(lppool_l2_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 3, 5}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {0, 0}}, {"stride", {1}}, {"lengths", {3}}, {"dilations", {1}}, {"lp_order", 2}}), l0); auto prog = optimize_onnx("lppool_l2_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/lrn_test.cpp000066400000000000000000000031551510465702400221550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(lrn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 28, 24, 24}}); migraphx::op::lrn op; op.size = 5; op.alpha = 0.0001; op.beta = 0.75; op.bias = 1.0; mm->add_instruction(op, l0); auto prog = optimize_onnx("lrn_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/main.cpp000066400000000000000000000023741510465702400212510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_bmbm_test.cpp000066400000000000000000000036431510465702400236600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_bmbm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3, 6, 7}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {5, 2, 1, 7, 8}}); auto bl0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {5, 2, 3, 6, 7}}}), l0); auto bl1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {5, 2, 3, 7, 8}}}), l1); migraphx::add_apply_alpha_beta(*mm, {bl0, bl1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto prog = optimize_onnx("matmul_bmbm_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_bmv_test.cpp000066400000000000000000000037241510465702400235270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_bmv_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3, 6, 7}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {7}}); auto sl1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l1); auto bsl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 7, 1}}}), sl1); auto res = migraphx::add_apply_alpha_beta(*mm, {l0, bsl1}, migraphx::make_op("dot"), 1.0f, 0.0f); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), res); auto prog = optimize_onnx("matmul_bmv_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_dyn_broadcast_test.cpp000066400000000000000000000043351510465702400255560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmul_dyn_broadcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto p0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {7}}); auto p1 = mm->add_parameter( "2", migraphx::shape{migraphx::shape::float_type, {{5, 5}, {7, 7}, {4, 8, {6}}}}); auto usp0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), p0); auto broadcast_p0 = mm->add_instruction(migraphx::make_op("broadcast_for_dot"), usp0, p1); auto broadcast_p1 = mm->add_instruction(migraphx::make_op("broadcast_for_dot"), p1, usp0); auto dot_ins = mm->add_instruction(migraphx::make_op("dot"), broadcast_p0, broadcast_p1); auto ret = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), dot_ins); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["2"] = {{5, 5}, {7, 7}, {4, 8, {6}}}; auto prog = read_onnx("matmul_dyn_broadcast_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_dyn_mm_test.cpp000066400000000000000000000036701510465702400242260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_dyn_mm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {{4, 8, {6}}, {7, 7}}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {{7, 7}, {1, 5, {3}}}}); auto ret = migraphx::add_apply_alpha_beta(*mm, {l0, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["1"] = {{4, 8, {6}}, {7, 7}}; options.map_dyn_input_dims["2"] = {{7, 7}, {1, 5, {3}}}; auto prog = read_onnx("matmul_dyn_mm_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_dyn_mv_test.cpp000066400000000000000000000040241510465702400242310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_dyn_mv_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {{4, 8, {6}}, {7, 7}}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {7}}); auto sl1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l1); auto res = migraphx::add_apply_alpha_beta(*mm, {l0, sl1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto ret = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), res); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["1"] = {{4, 8, {6}}, {7, 7}}; auto prog = read_onnx("matmul_dyn_mv_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_dyn_vm_test.cpp000066400000000000000000000040301510465702400242260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_dyn_vm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {7}}); auto l1 = mm->add_parameter( "2", migraphx::shape{migraphx::shape::float_type, {{7, 7}, {4, 10, {8}}}}); auto sl0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), l0); auto res = migraphx::add_apply_alpha_beta(*mm, {sl0, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto ret = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), res); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["2"] = {{7, 7}, {4, 10, {8}}}; auto prog = read_onnx("matmul_dyn_vm_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_dyn_vv_test.cpp000066400000000000000000000043261510465702400242470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_dyn_vv_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{5, 8, {7}}; auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {dd}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {dd}}); auto sl0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), l0); auto sl1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l1); auto res = migraphx::add_apply_alpha_beta(*mm, {sl0, sl1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto sr0 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), res); auto ret = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), sr0); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = dd; auto prog = read_onnx("matmul_dyn_vv_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_mv_test.cpp000066400000000000000000000035241510465702400233630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_mv_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {6, 7}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {7}}); auto sl1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l1); auto res = migraphx::add_apply_alpha_beta(*mm, {l0, sl1}, migraphx::make_op("dot"), 1.0f, 0.0f); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), res); auto prog = optimize_onnx("matmul_mv_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_vbm_test.cpp000066400000000000000000000037241510465702400235270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_vbm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {7}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {5, 7, 8}}); auto sl0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), l0); auto bsl0 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5, 1, 7}}}), sl0); auto res = migraphx::add_apply_alpha_beta(*mm, {bsl0, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), res); auto prog = optimize_onnx("matmul_vbm_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_vm_test.cpp000066400000000000000000000035241510465702400233630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_vm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {7}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {7, 8}}); auto sl0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), l0); auto res = migraphx::add_apply_alpha_beta(*mm, {sl0, l1}, migraphx::make_op("dot"), 1.0f, 0.0f); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), res); auto prog = optimize_onnx("matmul_vm_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmul_vv_test.cpp000066400000000000000000000040131510465702400233660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(matmul_vv_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {7}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {7}}); auto sl0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), l0); auto sl1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l1); auto res = migraphx::add_apply_alpha_beta(*mm, {sl0, sl1}, migraphx::make_op("dot"), 1.0f, 0.0f); auto sr0 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), res); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), sr0); auto prog = optimize_onnx("matmul_vv_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegerToFloat_bad_bias_test.cpp000066400000000000000000000025251510465702400273340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_bad_boas_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("matmulintegertofloat_bad_bias_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegerToFloat_bad_bias_test2.cpp000066400000000000000000000025271510465702400274200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_bad_bias_test2) { EXPECT(test::throws([&] { migraphx::parse_onnx("matmulintegertofloat_bad_boas_test2.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegerToFloat_bad_bias_test3.cpp000066400000000000000000000025261510465702400274200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_bad_bias_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("matmulintegertofloat_bad_bias_test3.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegerToFloat_bad_scale2_test.cpp000066400000000000000000000025421510465702400275660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_bad_scale2_test) { EXPECT( test::throws([&] { migraphx::parse_onnx("matmulintegertofloat_bad_scale3_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegerToFloat_bad_scale3_test.cpp000066400000000000000000000025421510465702400275670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_bad_scale3_test) { EXPECT( test::throws([&] { migraphx::parse_onnx("matmulintegertofloat_bad_scale3_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegerToFloat_bad_scale4_test.cpp000066400000000000000000000025421510465702400275700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_bad_scale4_test) { EXPECT( test::throws([&] { migraphx::parse_onnx("matmulintegertofloat_bad_scale4_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegerToFloat_bad_scale5_test.cpp000066400000000000000000000025421510465702400275710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_bad_scale5_test) { EXPECT( test::throws([&] { migraphx::parse_onnx("matmulintegertofloat_bad_scale5_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegerToFloat_bad_scale_test.cpp000066400000000000000000000025271510465702400275070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_bad_scale_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("matmulintegertofloat_bad_scale_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_dual_zero_zp_test.cpp000066400000000000000000000050661510465702400270170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_dual_zero_zp_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal(migraphx::shape{migraphx::shape::int8_type, {1}, {0}}, {0})); mm->add_literal( migraphx::literal(migraphx::shape{migraphx::shape::uint8_type, {1}, {0}}, {128})); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::uint8_type, {3, 2}}); // Shift uint8 input auto int8_shift2 = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {-128}}); // Shift uint8 input auto unshifted_input_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l1); auto mbr2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2}}}), int8_shift2); auto input_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_input_half, mbr2); l1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); mm->add_instruction(migraphx::make_op("quant_dot"), l0, l1); auto prog = optimize_onnx("matmulinteger_int8_uint8_dual_zero_zp_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_dual_zp_test.cpp000066400000000000000000000067361510465702400257650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_dual_zp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l2 = mm->add_literal( migraphx::literal(migraphx::shape{migraphx::shape::int8_type, {1}, {0}}, {1})); auto l3 = mm->add_literal( migraphx::literal(migraphx::shape{migraphx::shape::uint8_type, {1}, {0}}, {1})); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::uint8_type, {3, 2}}); // Shift uint8 input auto int8_shift = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {-128}}); // Shift uint8 input auto unshifted_input_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l1); auto mbr2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2}}}), int8_shift); auto input_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_input_half, mbr2); l1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); // Shift uint8 zero point auto unshifted_zp_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l3); auto zp_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_zp_half, int8_shift); l3 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), zp_shifted_half); // int8 input1 just simple sub of bias auto mbr1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 3}}}), l2); auto sub1 = mm->add_instruction(migraphx::make_op("sub"), l0, mbr1); // do input 2 sub after conversion for input and zero point auto mbr3 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2}}}), l3); auto sub2 = mm->add_instruction(migraphx::make_op("sub"), l1, mbr3); mm->add_instruction(migraphx::make_op("quant_dot"), sub1, sub2); auto prog = optimize_onnx("matmulinteger_int8_uint8_dual_zp_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_dyn_error.cpp000066400000000000000000000026151510465702400252630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_dyn_error) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT(test::throws([&] { read_onnx("matmulinteger_dyn_error.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_invalid_type_error.cpp000066400000000000000000000025161510465702400271600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_type_error_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("matmulinteger_invalid_type_error.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_one_zero_zp_vec_test.cpp000066400000000000000000000050211510465702400274770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_one_zero_zp_vec_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal(migraphx::shape{migraphx::shape::int8_type, {4, 3}}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::uint8_type, {3, 2}}); // Shift uint8 input auto int8_shift2 = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {-128}}); // Shift uint8 input auto unshifted_input_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l1); auto mbr2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2}}}), int8_shift2); auto input_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_input_half, mbr2); l1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); mm->add_instruction(migraphx::make_op("quant_dot"), l0, l1); auto prog = optimize_onnx("matmulinteger_int8_uint8_one_zp_zero_vec_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_one_zero_zp_vec_test2.cpp000066400000000000000000000050511510465702400275640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_one_zero_zp_vec_test2) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal( migraphx::literal(migraphx::shape{migraphx::shape::uint8_type, {4, 3}}, {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128})); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::uint8_type, {4, 3}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::int8_type, {3, 2}}); // Shift uint8 input auto int8_shift2 = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {-128}}); // Shift uint8 input auto unshifted_input_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l0); auto mbr2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 3}}}), int8_shift2); auto input_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_input_half, mbr2); l0 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); mm->add_instruction(migraphx::make_op("quant_dot"), l0, l1); auto prog = optimize_onnx("matmulinteger_int8_uint8_one_zp_zero_vec_test2.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_one_zp_test.cpp000066400000000000000000000052711510465702400256120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_one_zp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l2 = mm->add_literal( migraphx::literal(migraphx::shape{migraphx::shape::int8_type, {1}, {0}}, {5})); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::uint8_type, {3, 2}}); // Convert uint8 input 2 to int8 auto int8_shift = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {-128}}); auto unshifted_input_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l1); auto mbr3 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2}}}), int8_shift); auto input_shifted_half = mm->add_instruction(migraphx::make_op("add"), unshifted_input_half, mbr3); l1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), input_shifted_half); // Handle bias offset for valid input that's already int8 auto mb1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 3}}}), l2); auto sub = mm->add_instruction(migraphx::make_op("sub"), l0, mb1); mm->add_instruction(migraphx::make_op("quant_dot"), sub, l1); auto prog = optimize_onnx("matmulinteger_int8_uint8_one_zp_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_test.cpp000066400000000000000000000031621510465702400242350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {3, 6, 16}}); auto l1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::int8_type, {3, 16, 8}}); mm->add_instruction(migraphx::make_op("quant_dot"), l0, l1); auto prog = optimize_onnx("matmulinteger_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulinteger_zp_mismatch_error_test.cpp000066400000000000000000000025501510465702400300440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulinteger_zp_mismatch_error_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("matmulinteger_int8_uint8_one_zp_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegertofloat_half_test.cpp000066400000000000000000000047761510465702400266340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_half_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto x1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::uint8_type, {3, 2}}); auto scale_x0 = mm->add_parameter("3", migraphx::shape{migraphx::shape::half_type, {3}}); auto scale_x1 = mm->add_parameter("4", migraphx::shape{migraphx::shape::half_type, {2}}); auto sq_scale_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x0); auto sq_scale_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x1); auto bc_scale_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_scale_x0); auto r0 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x0, bc_scale_x0); auto bc_scale_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_scale_x1); auto r1 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x1, bc_scale_x1); mm->add_instruction(migraphx::make_op("dot"), r0, r1); auto prog = optimize_onnx("matmulintegertofloat_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegertofloat_half_zp_test.cpp000066400000000000000000000062531510465702400273350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_half_zp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto x1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::uint8_type, {3, 2}}); auto scale_x0 = mm->add_parameter("3", migraphx::shape{migraphx::shape::half_type, {3}}); auto scale_x1 = mm->add_parameter("4", migraphx::shape{migraphx::shape::half_type, {2}}); auto zp_x0 = mm->add_parameter("5", migraphx::shape{migraphx::shape::int8_type, {3}}); auto zp_x1 = mm->add_parameter("6", migraphx::shape{migraphx::shape::uint8_type, {2}}); auto sq_scale_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x0); auto sq_scale_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x1); auto sq_zp_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), zp_x0); auto sq_zp_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), zp_x1); auto bc_scale_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_scale_x0); auto bc_zp_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_zp_x0); auto r0 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x0, bc_scale_x0, bc_zp_x0); auto bc_scale_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_scale_x1); auto bc_zp_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_zp_x1); auto r1 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x1, bc_scale_x1, bc_zp_x1); mm->add_instruction(migraphx::make_op("dot"), r0, r1); auto prog = optimize_onnx("matmulintegertofloat_half_zp_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegertofloat_scalar_scale_test.cpp000066400000000000000000000052261510465702400303250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_scalar_scale_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scale_x1 = mm->add_literal( migraphx::literal(migraphx::shape{migraphx::shape::float_type, {1}, {0}}, {10})); auto x0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto x1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::int8_type, {3, 2}}); auto scale_x0 = mm->add_parameter("3", migraphx::shape{migraphx::shape::float_type, {3}}); auto sq_scale_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x0); auto sq_scale_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x1); sq_scale_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), sq_scale_x1); auto bc_scale_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_scale_x0); auto r0 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x0, bc_scale_x0); auto bc_scale_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_scale_x1); auto r1 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x1, bc_scale_x1); mm->add_instruction(migraphx::make_op("dot"), r0, r1); auto prog = optimize_onnx("matmulintegertofloat_scalar_scale_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegertofloat_scalar_zp_test.cpp000066400000000000000000000064561510465702400276750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_scalar_zp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto zp_x1 = mm->add_literal( migraphx::literal(migraphx::shape{migraphx::shape::int8_type, {1}, {0}}, {129})); auto x0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto x1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::int8_type, {3, 2}}); auto scale_x0 = mm->add_parameter("3", migraphx::shape{migraphx::shape::float_type, {3}}); auto scale_x1 = mm->add_parameter("4", migraphx::shape{migraphx::shape::float_type, {2}}); auto zp_x0 = mm->add_parameter("5", migraphx::shape{migraphx::shape::int8_type, {3}}); auto sq_scale_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x0); auto sq_scale_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x1); auto sq_zp_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), zp_x0); auto sq_zp_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), zp_x1); sq_zp_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), sq_zp_x1); auto bc_scale_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_scale_x0); auto bc_zp_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_zp_x0); auto r0 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x0, bc_scale_x0, bc_zp_x0); auto bc_scale_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_scale_x1); auto bc_zp_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_zp_x1); auto r1 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x1, bc_scale_x1, bc_zp_x1); mm->add_instruction(migraphx::make_op("dot"), r0, r1); auto prog = optimize_onnx("matmulintegertofloat_scalar_zp_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegertofloat_test.cpp000066400000000000000000000047641510465702400256370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto x1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::int8_type, {3, 2}}); auto scale_x0 = mm->add_parameter("3", migraphx::shape{migraphx::shape::float_type, {3}}); auto scale_x1 = mm->add_parameter("4", migraphx::shape{migraphx::shape::float_type, {2}}); auto sq_scale_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x0); auto sq_scale_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x1); auto bc_scale_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_scale_x0); auto r0 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x0, bc_scale_x0); auto bc_scale_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_scale_x1); auto r1 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x1, bc_scale_x1); mm->add_instruction(migraphx::make_op("dot"), r0, r1); auto prog = optimize_onnx("matmulintegertofloat_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegertofloat_zp_bias_test.cpp000066400000000000000000000067161510465702400273450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_zp_bias_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto x1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::uint8_type, {3, 2}}); auto scale_x0 = mm->add_parameter("3", migraphx::shape{migraphx::shape::float_type, {3}}); auto scale_x1 = mm->add_parameter("4", migraphx::shape{migraphx::shape::float_type, {2}}); auto zp_x0 = mm->add_parameter("5", migraphx::shape{migraphx::shape::int8_type, {3}}); auto zp_x1 = mm->add_parameter("6", migraphx::shape{migraphx::shape::uint8_type, {2}}); auto bias = mm->add_parameter("7", migraphx::shape{migraphx::shape::float_type, {2}}); auto sq_scale_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x0); auto sq_scale_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x1); auto sq_zp_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), zp_x0); auto sq_zp_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), zp_x1); auto bc_scale_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_scale_x0); auto bc_zp_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_zp_x0); auto r0 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x0, bc_scale_x0, bc_zp_x0); auto bc_scale_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_scale_x1); auto bc_zp_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_zp_x1); auto r1 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x1, bc_scale_x1, bc_zp_x1); auto dot = mm->add_instruction(migraphx::make_op("dot"), r0, r1); auto mb_bias = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 2}}}), bias); mm->add_instruction(migraphx::make_op("sub"), dot, mb_bias); auto prog = optimize_onnx("matmulintegertofloat_zp_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulintegertofloat_zp_test.cpp000066400000000000000000000062411510465702400263400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulintegertofloat_zp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x0 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int8_type, {4, 3}}); auto x1 = mm->add_parameter("2", migraphx::shape{migraphx::shape::int8_type, {3, 2}}); auto scale_x0 = mm->add_parameter("3", migraphx::shape{migraphx::shape::float_type, {3}}); auto scale_x1 = mm->add_parameter("4", migraphx::shape{migraphx::shape::float_type, {2}}); auto zp_x0 = mm->add_parameter("5", migraphx::shape{migraphx::shape::int8_type, {3}}); auto zp_x1 = mm->add_parameter("6", migraphx::shape{migraphx::shape::int8_type, {2}}); auto sq_scale_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x0); auto sq_scale_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), scale_x1); auto sq_zp_x0 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), zp_x0); auto sq_zp_x1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), zp_x1); auto bc_scale_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_scale_x0); auto bc_zp_x0 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x0->get_shape().lens()}}), sq_zp_x0); auto r0 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x0, bc_scale_x0, bc_zp_x0); auto bc_scale_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_scale_x1); auto bc_zp_x1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x1->get_shape().lens()}}), sq_zp_x1); auto r1 = mm->add_instruction(migraphx::make_op("dequantizelinear"), x1, bc_scale_x1, bc_zp_x1); mm->add_instruction(migraphx::make_op("dot"), r0, r1); auto prog = optimize_onnx("matmulintegertofloat_zp_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulnbits_negative_tests.cpp000066400000000000000000000040341510465702400257630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(matmulnbits_invalid_bits_value_test) { EXPECT(test::throws([&] { read_onnx("matmulnbits_invalid_bits_value_test.onnx"); })); } TEST_CASE(matmulnbits_block_size_too_small_test) { EXPECT(test::throws([&] { read_onnx("matmulnbits_block_size_too_small_test.onnx"); })); } TEST_CASE(matmulnbits_block_size_not_power_of_two_test) { EXPECT(test::throws([&] { read_onnx("matmulnbits_block_size_not_power_of_two_test.onnx"); })); } TEST_CASE(matmulnbits_invalid_b_dims_test) { EXPECT(test::throws([&] { read_onnx("matmulnbits_invalid_b_dims_test.onnx"); })); } TEST_CASE(matmulnbits_invalid_scales_dims_test) { EXPECT(test::throws([&] { read_onnx("matmulnbits_invalid_scales_dims_test.onnx"); })); } TEST_CASE(matmulnbits_invalid_zp_dims_test) { EXPECT(test::throws([&] { read_onnx("matmulnbits_invalid_zp_dims_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/matmulnbits_tests.cpp000066400000000000000000000217441510465702400241100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/make_op.hpp" #include TEST_CASE(matmulnbits_mm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{migraphx::shape::float_type, {2, 16}}); auto b = mm->add_parameter("b", migraphx::shape{migraphx::shape::uint8_type, {4, 1, 8}}); auto scales = mm->add_parameter("scales", migraphx::shape{migraphx::shape::float_type, {4}}); auto zp = mm->add_parameter("zp", migraphx::shape{migraphx::shape::uint8_type, {4}}); scales = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4, -1}}}), scales); scales = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scales); scales = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 1, 16}}}), scales); scales = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4, -1}}}), scales); zp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4, -1}}}), zp); zp = mm->add_instruction(migraphx::make_op("unpack_int4"), zp); zp = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), zp); zp = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), zp); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 1, 16}}}), zp); zp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4, -1}}}), zp); b = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4, -1}}}), b); b = mm->add_instruction(migraphx::make_op("unpack_int4"), b); b = mm->add_instruction(migraphx::make_op("dequantizelinear"), b, scales, zp); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), b); mm->add_instruction(migraphx::make_op("dot"), a, b); auto prog = optimize_onnx("matmulnbits_mm_test.onnx"); p.sort(); prog.sort(); EXPECT(p == prog); } TEST_CASE(matmulnbits_mm2_test) { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{migraphx::shape::float_type, {2, 33}}); auto b = mm->add_parameter("b", migraphx::shape{migraphx::shape::uint8_type, {2, 3, 8}}); auto scales = mm->add_parameter("scales", migraphx::shape{migraphx::shape::float_type, {6}}); scales = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, -1}}}), scales); scales = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scales); scales = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 16}}}), scales); scales = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, -1}}}), scales); scales = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {33}}}), scales); auto zp = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::uint8_type, {1}}, {8}}); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 33}}}), zp); b = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, -1}}}), b); b = mm->add_instruction(migraphx::make_op("unpack_int4"), b); b = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {33}}}), b); b = mm->add_instruction(migraphx::make_op("dequantizelinear"), b, scales, zp); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), b); mm->add_instruction(migraphx::make_op("dot"), a, b); auto prog = optimize_onnx("matmulnbits_mm2_test.onnx"); p.sort(); prog.sort(); EXPECT(p == prog); } TEST_CASE(matmulnbits_vm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{migraphx::shape::float_type, {20}}); auto b = mm->add_parameter("b", migraphx::shape{migraphx::shape::uint8_type, {3, 2, 8}}); auto scales = mm->add_parameter("scales", migraphx::shape{migraphx::shape::float_type, {6}}); auto zp = mm->add_parameter("zp", migraphx::shape{migraphx::shape::uint8_type, {3}}); scales = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, -1}}}), scales); scales = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scales); scales = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2, 16}}}), scales); scales = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, -1}}}), scales); scales = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {20}}}), scales); zp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, -1}}}), zp); zp = mm->add_instruction(migraphx::make_op("unpack_int4"), zp); zp = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), zp); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2, 16}}}), zp); zp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, -1}}}), zp); zp = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {20}}}), zp); b = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, -1}}}), b); b = mm->add_instruction(migraphx::make_op("unpack_int4"), b); b = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {20}}}), b); b = mm->add_instruction(migraphx::make_op("dequantizelinear"), b, scales, zp); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), b); a = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), a); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), dot); auto prog = optimize_onnx("matmulnbits_vm_test.onnx"); p.sort(); prog.sort(); EXPECT(p == prog); } TEST_CASE(matmulnbits_bmm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{migraphx::shape::float_type, {2, 3, 8}}); auto b = mm->add_parameter("b", migraphx::shape{migraphx::shape::uint8_type, {2, 1, 8}}); auto scales = mm->add_parameter("scales", migraphx::shape{migraphx::shape::float_type, {2}}); scales = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, -1}}}), scales); scales = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scales); scales = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 1, 16}}}), scales); scales = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, -1}}}), scales); scales = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {8}}}), scales); auto zp = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::uint8_type, {1}}, {8}}); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 8}}}), zp); b = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, -1}}}), b); b = mm->add_instruction(migraphx::make_op("unpack_int4"), b); b = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {8}}}), b); b = mm->add_instruction(migraphx::make_op("dequantizelinear"), b, scales, zp); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), b); b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 8, 2}}}), b); mm->add_instruction(migraphx::make_op("dot"), a, b); auto prog = optimize_onnx("matmulnbits_bmm_test.onnx"); p.sort(); prog.sort(); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/max_test.cpp000066400000000000000000000034071510465702400221470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(max_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3}}); auto input2 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {3}}); auto l0 = mm->add_instruction(migraphx::make_op("max"), input0, input1); mm->add_instruction(migraphx::make_op("max"), l0, input2); auto prog = optimize_onnx("max_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/maxpool_dilate_test.cpp000066400000000000000000000036371510465702400243700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(maxpool_dilate_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 4, 3}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {1, 1}}, {"stride", {1}}, {"lengths", {2}}, {"dilations", {3}}}), input); auto prog = optimize_onnx("maxpool_dilate_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/maxpool_notset_test.cpp000066400000000000000000000036611510465702400244370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(maxpool_notset_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 1, 1}}, {"stride", {2, 2}}, {"lengths", {6, 6}}, {"dilations", {1, 1}}}), input); auto prog = optimize_onnx("maxpool_notset_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/maxpool_same_upper_test.cpp000066400000000000000000000036711510465702400252640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(maxpool_same_upper_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 1, 1}}, {"stride", {1, 1}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}}), input); auto prog = optimize_onnx("maxpool_same_upper_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mean_bf16_test.cpp000066400000000000000000000040231510465702400231130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mean_bf16_test) { const std::size_t num_data = 3; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {1, 2, 3}}; auto data0 = mm->add_parameter("0", s); auto data1 = mm->add_parameter("1", s); auto data2 = mm->add_parameter("2", s); auto add1 = mm->add_instruction(migraphx::make_op("add"), data0, data1); auto mean = mm->add_instruction(migraphx::make_op("add"), add1, data2); auto div_lit = mm->add_literal(migraphx::literal{migraphx::shape{s.type()}, {num_data}}); auto divisor = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), div_lit); mean = mm->add_instruction(migraphx::make_op("div"), mean, divisor); auto prog = optimize_onnx("mean_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mean_fp16_test.cpp000066400000000000000000000046011510465702400231330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mean_test) { const std::size_t num_data = 3; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {1, 2, 3}}; auto data0 = mm->add_parameter("0", s); auto data1 = mm->add_parameter("1", s); auto data2 = mm->add_parameter("2", s); auto div_lit = mm->add_literal(migraphx::literal{migraphx::shape{s.type()}, {num_data}}); auto divisor = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), div_lit); auto mean = mm->add_instruction(migraphx::make_op("div"), data0, divisor); divisor = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), div_lit); data1 = mm->add_instruction(migraphx::make_op("div"), data1, divisor); mean = mm->add_instruction(migraphx::make_op("add"), mean, data1); divisor = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), div_lit); data2 = mm->add_instruction(migraphx::make_op("div"), data2, divisor); mean = mm->add_instruction(migraphx::make_op("add"), mean, data2); auto prog = optimize_onnx("mean_fp16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mean_integral_test.cpp000066400000000000000000000037471510465702400241760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mean_integral_test) { const std::size_t num_data = 10; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {2, 2, 2}}; auto mean = mm->add_parameter("0", s); for(std::size_t i = 1; i < num_data; ++i) { auto data = mm->add_parameter(std::to_string(i), s); mean = mm->add_instruction(migraphx::make_op("add"), mean, data); } auto div_lit = mm->add_literal(migraphx::literal{migraphx::shape{s.type()}, {num_data}}); auto divisor = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), div_lit); mean = mm->add_instruction(migraphx::make_op("div"), mean, divisor); auto prog = optimize_onnx("mean_integral_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mean_invalid_broadcast_test.cpp000066400000000000000000000024741510465702400260350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mean_invalid_broadcast_test) { EXPECT(test::throws([&] { read_onnx("mean_invalid_broadcast_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mean_single_input_test.cpp000066400000000000000000000027661510465702400250710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mean_single_input_test) { migraphx::program p; auto* mm = p.get_main_module(); auto data0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}); mm->add_return({data0}); auto prog = read_onnx("mean_single_input_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mha_cross_attention_test.cpp000066400000000000000000000064431510465702400254300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multi_head_attention_cross_attention_test) { const int64_t batch_size = 1; const int64_t sequence_length = 2; const int64_t hidden_size = 4; const int64_t num_heads = 2; const int64_t head_size = 2; const std::vector q_lens{batch_size, sequence_length, hidden_size}; const std::vector kv_lens{batch_size, num_heads, sequence_length, head_size}; const std::vector reshape_lens{batch_size, sequence_length, num_heads, head_size}; const std::vector permutation{0, 2, 1, 3}; migraphx::program p; auto* mm = p.get_main_module(); auto q = mm->add_parameter("q", {migraphx::shape::float_type, q_lens}); auto k = mm->add_parameter("k", {migraphx::shape::float_type, kv_lens}); auto v = mm->add_parameter("v", {migraphx::shape::float_type, kv_lens}); q = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_lens}}), q); q = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), q); const float scale = 1 / std::sqrt(head_size); auto scale_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {scale}}); auto key_transposed = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), k); auto result = mm->add_instruction(migraphx::make_op("dot"), q, key_transposed); scale_literal = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", result->get_shape().lens()}}), scale_literal); result = mm->add_instruction(migraphx::make_op("mul"), result, scale_literal); result = mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), result); result = mm->add_instruction(migraphx::make_op("dot"), result, v); result = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), result); result = mm->add_instruction(migraphx::make_op("reshape", {{"dims", q_lens}}), result); auto prog = optimize_onnx("mha_cross_attention_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mha_invalid_tests.cpp000066400000000000000000000056711510465702400240250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multi_head_attention_invalid_attribute_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_attribute_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_input_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_input_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_query_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_query_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_qkv_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_qkv_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_key_missing_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_key_missing_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_key_ndim_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_key_ndim_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_kv_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_kv_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_key_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_key_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_value_missing_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_value_missing_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_value_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_value_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_value_ndim_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_value_ndim_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_cross_key_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_cross_key_test.onnx"); })); } TEST_CASE(multi_head_attention_invalid_cross_value_test) { EXPECT(test::throws([&] { read_onnx("mha_invalid_cross_value_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mha_kv_packed_test.cpp000066400000000000000000000076271510465702400241460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multi_head_attention_kv_packed_test) { const int64_t batch_size = 1; const int64_t sequence_length = 2; const int64_t hidden_size = 4; const int64_t num_heads = 2; const int64_t head_size = 2; const std::vector q_lens{batch_size, sequence_length, hidden_size}; const std::vector kv_lens{batch_size, sequence_length, num_heads, 2, head_size}; const std::vector reshape_lens{batch_size, sequence_length, num_heads, head_size}; const std::vector permutation{0, 2, 1, 3}; migraphx::program p; auto* mm = p.get_main_module(); auto q = mm->add_parameter("q", {migraphx::shape::float_type, q_lens}); auto kv = mm->add_parameter("kv", {migraphx::shape::float_type, kv_lens}); q = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_lens}}), q); kv = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {3, 0, 1, 2, 4}}}), kv); auto k = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), kv); k = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), k); auto v = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), kv); v = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), v); q = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), q); k = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), k); v = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), v); const float scale = 1 / std::sqrt(head_size); auto scale_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {scale}}); auto key_transposed = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), k); auto result = mm->add_instruction(migraphx::make_op("dot"), q, key_transposed); scale_literal = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", result->get_shape().lens()}}), scale_literal); result = mm->add_instruction(migraphx::make_op("mul"), result, scale_literal); result = mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), result); result = mm->add_instruction(migraphx::make_op("dot"), result, v); result = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), result); result = mm->add_instruction(migraphx::make_op("reshape", {{"dims", q_lens}}), result); auto prog = optimize_onnx("mha_kv_packed_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mha_qkv_packed_test.cpp000066400000000000000000000075431510465702400243240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multi_head_attention_qkv_packed_test) { const int64_t batch_size = 1; const int64_t sequence_length = 2; const int64_t hidden_size = 4; const int64_t num_heads = 2; const int64_t head_size = 2; const std::vector input_lens{batch_size, sequence_length, num_heads, 3, head_size}; const std::vector permutation{0, 2, 1, 3}; migraphx::program p; auto* mm = p.get_main_module(); auto qkv = mm->add_parameter("qkv", {migraphx::shape::float_type, input_lens}); qkv = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {3, 0, 1, 2, 4}}}), qkv); auto q = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), qkv); q = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), q); auto k = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), qkv); k = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), k); auto v = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {3}}}), qkv); v = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), v); q = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), q); k = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), k); v = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), v); const float scale = 1 / std::sqrt(head_size); auto scale_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {scale}}); auto key_transposed = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), k); auto result = mm->add_instruction(migraphx::make_op("dot"), q, key_transposed); scale_literal = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", result->get_shape().lens()}}), scale_literal); result = mm->add_instruction(migraphx::make_op("mul"), result, scale_literal); result = mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), result); result = mm->add_instruction(migraphx::make_op("dot"), result, v); result = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), result); result = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {batch_size, sequence_length, hidden_size}}}), result); auto prog = optimize_onnx("mha_qkv_packed_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mha_test.cpp000066400000000000000000000070471510465702400221330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multi_head_attention_test) { const int64_t batch_size = 1; const int64_t sequence_length = 2; const int64_t hidden_size = 4; const int64_t num_heads = 2; const int64_t head_size = 2; const std::vector input_lens{batch_size, sequence_length, hidden_size}; const std::vector reshape_lens{batch_size, sequence_length, num_heads, head_size}; const std::vector permutation{0, 2, 1, 3}; migraphx::program p; auto* mm = p.get_main_module(); auto q = mm->add_parameter("q", {migraphx::shape::float_type, input_lens}); auto k = mm->add_parameter("k", {migraphx::shape::float_type, input_lens}); auto v = mm->add_parameter("v", {migraphx::shape::float_type, input_lens}); q = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_lens}}), q); k = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_lens}}), k); v = mm->add_instruction(migraphx::make_op("reshape", {{"dims", reshape_lens}}), v); q = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), q); k = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), k); v = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), v); const float scale = 1 / std::sqrt(head_size); auto scale_literal = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {scale}}); auto key_transposed = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), k); auto result = mm->add_instruction(migraphx::make_op("dot"), q, key_transposed); scale_literal = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", result->get_shape().lens()}}), scale_literal); result = mm->add_instruction(migraphx::make_op("mul"), result, scale_literal); result = mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), result); result = mm->add_instruction(migraphx::make_op("dot"), result, v); result = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", permutation}}), result); result = mm->add_instruction(migraphx::make_op("reshape", {{"dims", input_lens}}), result); auto prog = optimize_onnx("mha_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/min_test.cpp000066400000000000000000000034071510465702400221450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(min_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3}}); auto input2 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {3}}); auto l0 = mm->add_instruction(migraphx::make_op("min"), input0, input1); mm->add_instruction(migraphx::make_op("min"), l0, input2); auto prog = optimize_onnx("min_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mod_test.cpp000066400000000000000000000031511510465702400221350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mod_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::int32_type, {3, 3, 3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {3, 3, 3}}); mm->add_instruction(migraphx::make_op("mod"), input0, input1); auto prog = optimize_onnx("mod_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mod_test_different_dtypes.cpp000066400000000000000000000032141510465702400255530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mod_test_different_dtypes) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::int16_type, {3, 3, 3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {3, 3, 3}}); add_common_op(*mm, migraphx::make_op("mod"), {input0, input1}); auto prog = optimize_onnx("mod_test_different_dtypes.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mod_test_fmod.cpp000066400000000000000000000031641510465702400231460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mod_test_fmod) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3, 3, 3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3, 3, 3}}); mm->add_instruction(migraphx::make_op("fmod"), input0, input1); auto prog = optimize_onnx("mod_test_fmod.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mod_test_fmod_bf16.cpp000066400000000000000000000031741510465702400237650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mod_test_fmod_bf16) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::bf16_type, {3, 3, 3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::bf16_type, {3, 3, 3}}); mm->add_instruction(migraphx::make_op("fmod"), input0, input1); auto prog = optimize_onnx("mod_test_fmod_bf16.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mod_test_fmod_different_dtypes.cpp000066400000000000000000000032271510465702400265640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mod_test_fmod_different_dtypes) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3, 3, 3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {3, 3, 3}}); add_common_op(*mm, migraphx::make_op("fmod"), {input0, input1}); auto prog = optimize_onnx("mod_test_fmod_different_dtypes.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mod_test_fmod_half.cpp000066400000000000000000000031741510465702400241410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mod_test_fmod_half) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::half_type, {3, 3, 3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::half_type, {3, 3, 3}}); mm->add_instruction(migraphx::make_op("fmod"), input0, input1); auto prog = optimize_onnx("mod_test_fmod_half.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mod_test_half.cpp000066400000000000000000000024401510465702400231270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mod_test_half) { EXPECT(test::throws([&] { read_onnx("mod_test_half.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/multinomial_autoseed_dyn_test.cpp000066400000000000000000000067111510465702400264600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multinomial_autoseed_dyn_test) { // runtime random seed migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size = 12; size_t categories = 10; auto input = mm->add_parameter( "input", migraphx::shape{migraphx::shape::float_type, {{1, 10}, {10, 10}}}); auto maxes = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), input); auto cdf = add_common_op(*mm, migraphx::make_op("sub"), {input, maxes}); cdf = mm->add_instruction(migraphx::make_op("exp"), cdf); cdf = mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), cdf); auto seed_input = mm->add_instruction(migraphx::make_op("random_seed")); // dynamic input only: must calculate alloc_shape as (batch_size, sample_size) // read the runtime input dimensions auto dim_of = mm->add_instruction(migraphx::make_op("dimensions_of", {{"end", 2}}), input); // make an argument of (1, 0) migraphx::shape lit_shape(migraphx::shape::int64_type, {2}); std::vector data1{1, 0}; auto l1 = mm->add_literal(lit_shape, data1); auto batch_arg = mm->add_instruction(migraphx::make_op("mul"), dim_of, l1); std::vector data2(2, 0); // make an argument of (0, sample_size) data2[1] = sample_size; auto l2 = mm->add_literal(lit_shape, data2); auto alloc_shape = mm->add_instruction(migraphx::make_op("add"), batch_arg, l2); migraphx::shape compile_shape = migraphx::shape(migraphx::shape::float_type, {input->get_shape().dyn_dims().front(), {sample_size, sample_size}}); auto alloc = mm->add_instruction( migraphx::make_op("allocate", {{"shape", to_value(compile_shape)}}), alloc_shape); auto randoms = mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, alloc); auto ret = mm->add_instruction(migraphx::make_op("multinomial"), cdf, randoms); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, categories}; options.print_program_on_error = true; auto prog = read_onnx("multinomial_autoseed_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/multinomial_dtype_error_test.cpp000066400000000000000000000024761510465702400263370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multinomial_dtype_error_test) { EXPECT(test::throws([&] { read_onnx("multinomial_dtype_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/multinomial_dyn_test.cpp000066400000000000000000000072361510465702400245720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multinomial_dyn_test) { // compile-time random seed migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size = 100000; size_t categories = 5; float seed = 1.3f; auto input = mm->add_parameter( "input", migraphx::shape{migraphx::shape::float_type, {{1, categories}, {categories, categories}}}); auto maxes = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), input); auto cdf = add_common_op(*mm, migraphx::make_op("sub"), {input, maxes}); cdf = mm->add_instruction(migraphx::make_op("exp"), cdf); cdf = mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), cdf); migraphx::shape s{migraphx::shape::float_type, {1}}; std::vector seed_data = {seed}; auto seed_input = mm->add_literal(migraphx::literal(s, seed_data)); // dynamic input only: must calculate alloc_shape as (batch_size, sample_size) // read the runtime input dimensions auto dim_of = mm->add_instruction(migraphx::make_op("dimensions_of", {{"end", 2}}), input); // make an argument of (1, 0) migraphx::shape lit_shape(migraphx::shape::int64_type, {2}); std::vector data1{1, 0}; auto l1 = mm->add_literal(lit_shape, data1); auto batch_arg = mm->add_instruction(migraphx::make_op("mul"), dim_of, l1); std::vector data2(2, 0); // make an argument of (0, sample_size) data2[1] = sample_size; auto l2 = mm->add_literal(lit_shape, data2); auto alloc_shape = mm->add_instruction(migraphx::make_op("add"), batch_arg, l2); migraphx::shape compile_shape = migraphx::shape(migraphx::shape::float_type, {input->get_shape().dyn_dims().front(), {sample_size, sample_size}}); auto alloc = mm->add_instruction( migraphx::make_op("allocate", {{"shape", to_value(compile_shape)}}), alloc_shape); auto randoms = mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, alloc); auto ret = mm->add_instruction( migraphx::make_op("multinomial", {{"dtype", migraphx::shape::float_type}}), cdf, randoms); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, categories}; options.print_program_on_error = true; auto prog = read_onnx("multinomial_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/multinomial_generated_seed_test.cpp000066400000000000000000000027121510465702400267300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multinomial_generated_seed_test) { // multinomial op. no longer generates its own randoms auto p1 = optimize_onnx("multinomial_generated_seed_test.onnx"); auto p2 = optimize_onnx("multinomial_generated_seed_test.onnx"); EXPECT(p1 == p2); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/multinomial_int64_test.cpp000066400000000000000000000051651510465702400247430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multinomial_int64_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size = 10; float seed = 1.0; uint32_t batch_size = 1; migraphx::shape::type_t dtype = migraphx::shape::type_t::int64_type; auto input = mm->add_parameter("input", migraphx::shape{migraphx::shape::float_type, {1, 10}}); auto maxes = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), input); auto cdf = add_common_op(*mm, migraphx::make_op("sub"), {input, maxes}); cdf = mm->add_instruction(migraphx::make_op("exp"), cdf); cdf = mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), cdf); migraphx::shape s{migraphx::shape::float_type, {1}}; std::vector data = {seed}; auto seed_input = mm->add_literal(migraphx::literal(s, data)); // static size auto rand_dummy = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {batch_size, sample_size}}, std::vector(batch_size * sample_size)}); auto randoms = mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, rand_dummy); mm->add_instruction(migraphx::make_op("multinomial", {{"dtype", dtype}}), cdf, randoms); auto prog = optimize_onnx("multinomial_int64_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/multinomial_test.cpp000066400000000000000000000052421510465702400237130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(multinomial_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size = 13; size_t batch_size = 3; size_t categories = 10; float seed = 0; auto input = mm->add_parameter( "input", migraphx::shape{migraphx::shape::float_type, {batch_size, categories}}); auto maxes = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), input); auto mb_maxes = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 10}}}), maxes); auto cdf = mm->add_instruction(migraphx::make_op("sub"), input, mb_maxes); cdf = mm->add_instruction(migraphx::make_op("exp"), cdf); cdf = mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), cdf); migraphx::shape s{migraphx::shape::float_type, {1}}; std::vector seed_data = {seed}; auto seed_input = mm->add_literal(migraphx::literal(s, seed_data)); auto rand_dummy = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {batch_size, sample_size}}, std::vector(batch_size * sample_size)}); auto randoms = mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, rand_dummy); mm->add_instruction(migraphx::make_op("multinomial"), cdf, randoms); auto prog = optimize_onnx("multinomial_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mvn_axes_rank_too_big_test.cpp000066400000000000000000000025051510465702400257150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mvn_axes_rank_too_big_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("mvn_axes_rank_too_big_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mvn_axes_rank_too_small_test.cpp000066400000000000000000000024761510465702400262730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mvn_axes_rank_too_small_test) { EXPECT(test::throws([&] { read_onnx("mvn_axes_rank_too_small_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mvn_default_axes_rank_too_big_test.cpp000066400000000000000000000025161510465702400274230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mvn_default_axes_rank_too_big_test) { EXPECT(test::throws([&] { optimize_onnx("mvn_default_axes_rank_too_big_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mvn_default_axes_rank_too_small_test.cpp000066400000000000000000000025421510465702400277710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mvn_default_axes_rank_too_small_test) { EXPECT( test::throws([&] { migraphx::parse_onnx("mvn_default_axes_rank_too_small_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mvn_default_axes_test.cpp000066400000000000000000000025361510465702400247100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(mvn_default_axes_test) { mvn_n_rank_test({0, 2, 3}, {2, 2, 2, 2}, optimize_onnx("mvn_default_axes_test.onnx")); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mvn_rank_3_test.cpp000066400000000000000000000025141510465702400234150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(mvn_rank_3_test) { mvn_n_rank_test({0, 1}, {2, 2, 2}, optimize_onnx("mvn_rank_3_test.onnx")); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/mxfixneuron_test.cpp000066400000000000000000000143471510465702400237510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(mxfixneuron_even_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("input", migraphx::shape{migraphx::shape::float_type, {3, 64, 4, 4}}); auto reduce_reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 2, 32, 4, 4}}}), input); auto abs_ins = mm->add_instruction(migraphx::make_op("abs"), reduce_reshape); auto reduce_max_ins = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2}}}), abs_ins); auto log2_ins = mm->add_instruction(migraphx::make_op("log2"), reduce_max_ins); auto floor_ins = mm->add_instruction(migraphx::make_op("floor"), log2_ins); auto lit_2_ins = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {2.f}}); auto broadcast_lit_2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_2_ins); auto pow_ins = mm->add_instruction(migraphx::make_op("pow"), broadcast_lit_2, floor_ins); auto lit_4_ins = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {4.f}}); auto broadcast_lit_4 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_4_ins); auto block_scales_ins = mm->add_instruction(migraphx::make_op("div"), pow_ins, broadcast_lit_4); block_scales_ins = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 2, 32, 4, 4}}}), block_scales_ins); block_scales_ins = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 64, 4, 4}}}), block_scales_ins); auto q_ins = mm->add_instruction( migraphx::make_op("quantizelinear", {{"out_type", migraphx::shape::float_type}}), input, block_scales_ins); auto quantized_shape = q_ins->get_shape(); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_fp4", {{"axis", 3}}), q_ins); auto unpack_ins = mm->add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), pack_ins); mm->add_instruction(migraphx::make_op("dequantizelinear"), unpack_ins, block_scales_ins); auto prog = optimize_onnx("mxfixneuron_even_test.onnx"); EXPECT(p == prog); } TEST_CASE(mxfixneuron_odd_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("input", migraphx::shape{migraphx::shape::float_type, {71, 5, 5}}); auto padded_input = mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 25, 0, 0}}}), input); auto reduce_reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 32, 5, 5}}}), padded_input); auto abs_ins = mm->add_instruction(migraphx::make_op("abs"), reduce_reshape); auto reduce_max_ins = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), abs_ins); auto log2_ins = mm->add_instruction(migraphx::make_op("log2"), reduce_max_ins); auto floor_ins = mm->add_instruction(migraphx::make_op("floor"), log2_ins); auto lit_2_ins = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {2.f}}); auto broadcast_lit_2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_2_ins); auto pow_ins = mm->add_instruction(migraphx::make_op("pow"), broadcast_lit_2, floor_ins); auto lit_4_ins = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {4.f}}); auto broadcast_lit_4 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", reduce_max_ins->get_shape().lens()}}), lit_4_ins); auto block_scales_ins = mm->add_instruction(migraphx::make_op("div"), pow_ins, broadcast_lit_4); block_scales_ins = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 32, 5, 5}}}), block_scales_ins); block_scales_ins = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {96, 5, 5}}}), block_scales_ins); block_scales_ins = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {71}}}), block_scales_ins); auto q_ins = mm->add_instruction( migraphx::make_op("quantizelinear", {{"out_type", migraphx::shape::float_type}}), input, block_scales_ins); auto quantized_shape = q_ins->get_shape(); auto pad_ins = mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 0, 0, 1}}}), q_ins); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_fp4", {{"axis", 2}}), pad_ins); auto unpack_ins = mm->add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 2}}), pack_ins); auto slice_ins = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {5}}}), unpack_ins); mm->add_instruction(migraphx::make_op("dequantizelinear"), slice_ins, block_scales_ins); auto prog = optimize_onnx("mxfixneuron_odd_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/neg_dynamic_test.cpp000066400000000000000000000032531510465702400236360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(neg_dynamic_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int64_type, {{1, 10}, {3, 3}}}; auto input = mm->add_parameter("0", s); auto ret = mm->add_instruction(migraphx::make_op("neg"), input); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; auto prog = read_onnx("neg_dynamic_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/neg_test.cpp000066400000000000000000000030431510465702400221270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(neg_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int64_type, {2, 3}}; auto input = mm->add_parameter("0", s); auto ret = mm->add_instruction(migraphx::make_op("neg"), input); mm->add_return({ret}); auto prog = read_onnx("neg_test.onnx"); EXPECT(p == prog); } negativeloglikelihoodloss_kd_all_reduction_weighted_test.cpp000066400000000000000000000401051510465702400340120ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/parse/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(negativeloglikelihoodloss_kd_sum_reduction_weighted_double_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::double_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {class_size, 2, 2}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::double_type, {class_size}}); auto weights_dflt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::double_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int32_type, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), scores); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), gathernd, gathernd2); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), weighted_loss); auto prog = optimize_onnx("negativeloglikelihoodloss_kd_sum_reduction_double_weighted_test.onnx"); EXPECT(p == prog); } TEST_CASE(negativeloglikelihoodloss_kd_no_reduction_weighted_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {class_size, 2, 2}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {class_size}}); auto weights_dflt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::float_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int32_type, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), scores); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); mm->add_instruction(migraphx::make_op("mul"), gathernd, gathernd2); auto prog = optimize_onnx("negativeloglikelihoodloss_kd_no_reduction_weighted_test.onnx"); EXPECT(p == prog); } TEST_CASE(negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::half_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {class_size, 2, 2}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::half_type, {class_size}}); auto weights_dflt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::half_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int32_type, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), scores); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), gathernd, gathernd2); auto loss_x = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), weighted_loss); auto loss_w = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), gathernd2); loss_w = mm->add_instruction(migraphx::make_op("neg"), loss_w); mm->add_instruction(migraphx::make_op("div"), loss_x, loss_w); auto prog = optimize_onnx("negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test.onnx"); EXPECT(p == prog); } TEST_CASE(negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::bf16_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {class_size, 2, 2}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::bf16_type, {class_size}}); auto weights_dflt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::bf16_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int32_type, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), scores); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), gathernd, gathernd2); auto loss_x = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), weighted_loss); auto loss_w = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), gathernd2); loss_w = mm->add_instruction(migraphx::make_op("neg"), loss_w); mm->add_instruction(migraphx::make_op("div"), loss_x, loss_w); auto prog = optimize_onnx("negativeloglikelihoodloss_kd_mean_reduction_bf16_weighted_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nhwcconv_test.cpp000066400000000000000000000037131510465702400232070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nhwcconv_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {1, 7, 7, 1}}; auto x = mm->add_parameter("0", x_shape); x = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), x); migraphx::shape w_shape{migraphx::shape::float_type, {1, 1, 1, 1}}; auto w = mm->add_parameter("1", w_shape); w = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), w); auto y = mm->add_instruction(migraphx::make_op("convolution"), x, w); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), y); migraphx::program prog = optimize_onnx("nhwcconv_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nms_dynamic_batch_test.cpp000066400000000000000000000045321510465702400250240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nms_dynamic_batch_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::float_type, {{1, 10}, {6, 6}, {4, 4}}}; auto b = mm->add_parameter("boxes", sb); migraphx::shape ss{migraphx::shape::float_type, {{1, 10}, {1, 1}, {6, 6}}}; auto s = mm->add_parameter("scores", ss); migraphx::shape smo{migraphx::shape::int64_type, {1}}; auto mo = mm->add_parameter("max_output_boxes_per_class", smo); migraphx::shape siou{migraphx::shape::float_type, {1}}; auto iou = mm->add_parameter("iou_threshold", siou); migraphx::shape sst{migraphx::shape::float_type, {1}}; auto st = mm->add_parameter("score_threshold", sst); auto ret = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), b, s, mo, iou, st); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; options.use_dyn_output = true; auto prog = read_onnx("nms_dynamic_batch_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nms_dynamic_boxes_test.cpp000066400000000000000000000043741510465702400250670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nms_dynamic_boxes_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::float_type, {{1, 1}, {6, 20}, {4, 4}}}; auto b = mm->add_parameter("boxes", sb); migraphx::shape ss{migraphx::shape::float_type, {{1, 1}, {1, 1}, {6, 20}}}; auto s = mm->add_parameter("scores", ss); migraphx::shape smo{migraphx::shape::int64_type, {1}}; auto mo = mm->add_parameter("max_output_boxes_per_class", smo); migraphx::shape siou{migraphx::shape::float_type, {1}}; auto iou = mm->add_parameter("iou_threshold", siou); migraphx::shape sst{migraphx::shape::float_type, {1}}; auto st = mm->add_parameter("score_threshold", sst); auto ret = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"use_dyn_output", true}}), b, s, mo, iou, st); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {6, 20}; options.use_dyn_output = true; auto prog = read_onnx("nms_dynamic_boxes_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nms_dynamic_classes_test.cpp000066400000000000000000000043601510465702400253770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nms_dynamic_classes_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::float_type, {1, 6, 4}}; auto b = mm->add_parameter("boxes", sb); migraphx::shape ss{migraphx::shape::float_type, {{1, 1}, {1, 10}, {6, 6}}}; auto s = mm->add_parameter("scores", ss); migraphx::shape smo{migraphx::shape::int64_type, {1}}; auto mo = mm->add_parameter("max_output_boxes_per_class", smo); migraphx::shape siou{migraphx::shape::float_type, {1}}; auto iou = mm->add_parameter("iou_threshold", siou); migraphx::shape sst{migraphx::shape::float_type, {1}}; auto st = mm->add_parameter("score_threshold", sst); auto ret = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"use_dyn_output", true}}), b, s, mo, iou, st); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; options.use_dyn_output = true; auto prog = read_onnx("nms_dynamic_classes_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nms_test.cpp000066400000000000000000000041011510465702400221470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nms_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::float_type, {1, 6, 4}}; auto b = mm->add_parameter("boxes", sb); migraphx::shape ss{migraphx::shape::float_type, {1, 1, 6}}; auto s = mm->add_parameter("scores", ss); migraphx::shape smo{migraphx::shape::int64_type, {1}}; auto mo = mm->add_parameter("max_output_boxes_per_class", smo); migraphx::shape siou{migraphx::shape::float_type, {1}}; auto iou = mm->add_parameter("iou_threshold", siou); migraphx::shape sst{migraphx::shape::float_type, {1}}; auto st = mm->add_parameter("score_threshold", sst); auto ret = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}}), b, s, mo, iou, st); mm->add_return({ret}); auto prog = read_onnx("nms_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nms_use_dyn_output_false_test.cpp000066400000000000000000000042761510465702400265040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nms_overwrite_use_dyn_output_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::float_type, {1, 6, 4}}; auto b = mm->add_parameter("boxes", sb); migraphx::shape ss{migraphx::shape::float_type, {1, 1, 6}}; auto s = mm->add_parameter("scores", ss); migraphx::shape smo{migraphx::shape::int64_type, {1}}; auto mo = mm->add_parameter("max_output_boxes_per_class", smo); migraphx::shape siou{migraphx::shape::float_type, {1}}; auto iou = mm->add_parameter("iou_threshold", siou); migraphx::shape sst{migraphx::shape::float_type, {1}}; auto st = mm->add_parameter("score_threshold", sst); auto ret = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"use_dyn_output", true}}), b, s, mo, iou, st); mm->add_return({ret}); migraphx::onnx_options options; options.use_dyn_output = true; auto prog = read_onnx("nms_use_dyn_output_false_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/no_pad_test.cpp000066400000000000000000000027731510465702400226270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(no_pad_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 2}}); mm->add_instruction(migraphx::make_op("identity"), l0); auto prog = optimize_onnx("no_pad_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nonzero_dynamic_test.cpp000066400000000000000000000030731510465702400245570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nonzero_dynamic_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {2, 2}}; auto data = mm->add_parameter("data", s); auto r = mm->add_instruction(migraphx::make_op("nonzero"), data); mm->add_return({r}); auto prog = read_onnx("nonzero_dynamic_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nonzero_int_test.cpp000066400000000000000000000034011510465702400237200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nonzero_int_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int16_type, {2, 3}}; std::vector data = {1, 1, 0, 1, 0, 1}; mm->add_literal(migraphx::literal(s, data.begin(), data.end())); migraphx::shape si{migraphx::shape::int64_type, {2, 4}}; std::vector indices = {0, 0, 1, 1, 0, 1, 0, 2}; auto r = mm->add_literal(migraphx::literal(si, indices)); mm->add_return({r}); auto prog = read_onnx("nonzero_int_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/nonzero_test.cpp000066400000000000000000000033331510465702400230520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(nonzero_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector data = {1, 0, 1, 1}; mm->add_literal(migraphx::literal(s, data)); migraphx::shape si{migraphx::shape::int64_type, {2, 3}}; std::vector indices = {0, 1, 1, 0, 0, 1}; auto r = mm->add_literal(migraphx::literal(si, indices)); mm->add_return({r}); auto prog = read_onnx("nonzero_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/not_bool_test.cpp000066400000000000000000000030311510465702400231660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(not_bool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::bool_type, {4}}); auto ret = mm->add_instruction(migraphx::make_op("not"), l0); mm->add_return({ret}); auto prog = read_onnx("not_bool_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/not_test.cpp000066400000000000000000000030201510465702400221510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(not_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::int32_type, {4}}); auto ret = mm->add_instruction(migraphx::make_op("not"), l0); mm->add_return({ret}); auto prog = read_onnx("not_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/onehot_test.cpp000066400000000000000000000065561510465702400226660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(onehot_static_test) { migraphx::program p; auto* mm = p.get_main_module(); // depth literal that is parsed but not used mm->add_literal(migraphx::literal(migraphx::shape{migraphx::shape::int32_type, {1}, {1}}, {3})); auto indices = mm->add_parameter("indices", migraphx::shape{migraphx::shape::int32_type, {5, 2}}); auto values = mm->add_parameter("values", migraphx::shape{migraphx::shape::half_type, {2}}); auto ret = mm->add_instruction( migraphx::make_op("onehot", {{"axis", 0}, {"depth", 3}}), indices, values); mm->add_return({ret}); auto prog = read_onnx("onehot_static_test.onnx"); EXPECT(p == prog); } TEST_CASE(onehot_dyn_test0) { migraphx::program p; auto* mm = p.get_main_module(); // depth literal that is parsed but not used mm->add_literal(migraphx::literal(migraphx::shape{migraphx::shape::int32_type, {1}, {1}}, {3})); auto indices = mm->add_parameter( "indices", migraphx::shape{migraphx::shape::int32_type, {{2, 10}, {2, 2}}}); auto values = mm->add_parameter("values", migraphx::shape{migraphx::shape::half_type, {2}}); auto ret = mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}, {"depth", 3}}), indices, values); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {2, 10}; auto prog = read_onnx("onehot_dyn_test0.onnx", options); EXPECT(p == prog); } TEST_CASE(onehot_dyn_test1) { migraphx::program p; auto* mm = p.get_main_module(); auto indices = mm->add_parameter( "indices", migraphx::shape{migraphx::shape::int32_type, {{2, 10}, {2, 2}}}); auto values = mm->add_parameter("values", migraphx::shape{migraphx::shape::float_type, {2}}); auto depth = mm->add_parameter("depth", migraphx::shape{migraphx::shape::int64_type, {1}}); auto ret = mm->add_instruction(migraphx::make_op("onehot", {{"axis", 1}}), indices, depth, values); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {2, 10}; auto prog = read_onnx("onehot_dyn_test1.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_3arg_test.cpp000066400000000000000000000033621510465702400230420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_3arg_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 2}}); mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {1.0f}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {4}}, {1, 1, 2, 2}}); auto r = mm->add_instruction( migraphx::make_op("pad", {{"pads", {1, 1, 2, 2}}, {"value", 1.0f}}), l0); mm->add_return({r}); auto prog = read_onnx("pad_3arg_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_4arg_axes_test.cpp000066400000000000000000000036401510465702400240620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_4arg_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 4, 5}}); // axes=[1,3] mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {2}}, {1, 3}}); // constant_value=1 mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {1.0f}}); // pads=[1,3,2,4] mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {4}}, {1, 3, 2, 4}}); auto r = mm->add_instruction( migraphx::make_op("pad", {{"pads", {0, 1, 0, 3, 0, 2, 0, 4}}, {"value", 1.0f}}), l0); mm->add_return({r}); auto prog = read_onnx("pad_4arg_axes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_4arg_invalid_axes_error_test.cpp000066400000000000000000000025061510465702400270010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_4arg_invalid_axes_error_test) { EXPECT(test::throws([&] { read_onnx("pad_4arg_invalid_axes_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_4arg_neg_axes_test.cpp000066400000000000000000000036541510465702400247200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_4arg_neg_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 4, 5}}); // axes=[-3,-1] mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {2}}, {-3, -1}}); // constant_value=1 mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {1.0f}}); // pads=[1,3,2,4] mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {4}}, {1, 3, 2, 4}}); auto r = mm->add_instruction( migraphx::make_op("pad", {{"pads", {0, 1, 0, 3, 0, 2, 0, 4}}, {"value", 1.0f}}), l0); mm->add_return({r}); auto prog = read_onnx("pad_4arg_neg_axes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_asym_invalid_pads_error_test.cpp000066400000000000000000000025061510465702400271040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_asym_invalid_pads_error_test) { EXPECT(test::throws([&] { read_onnx("pad_asym_invalid_pads_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_asym_test.cpp000066400000000000000000000030461510465702400231560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_asym_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 4, 5}}); mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 1, 0, 3, 0, 2, 0, 4}}}), l0); auto prog = optimize_onnx("pad_asym_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_attr_dyn_test.cpp000066400000000000000000000033371510465702400240340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_attr_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{2, 4, {2}}, {2, 4, {2}}}}); auto ret = mm->add_instruction(migraphx::make_op("pad", {{"pads", {1, 1, 1, 1}}}), x); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["0"] = {{2, 4, {2}}, {2, 4, {2}}}; auto prog = read_onnx("pad_attr_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_cnst_dyn_test.cpp000066400000000000000000000034671510465702400240350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_cnst_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{2, 4, {2}}, {2, 4, {2}}}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {4}}, {0, 2, 0, 1}}); auto ret = mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 2, 0, 1}}}), x); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["0"] = {{2, 4, {2}}, {2, 4, {2}}}; auto prog = read_onnx("pad_cnst_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_dyn_reflect_error.cpp000066400000000000000000000026161510465702400246570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_dyn_reflect_error) { migraphx::onnx_options options; options.default_dyn_dim_value = {2, 4, {2}}; EXPECT(test::throws([&] { read_onnx("pad_dyn_reflect_error.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_edge_1d_test.cpp000066400000000000000000000036261510465702400235010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_edge_1d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {2}}, {2, 3}}); auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {3}}, {"ends", {4}}}), l0); auto r = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), l1, l1, l0, l2, l2, l2); mm->add_return({r}); auto prog = read_onnx("pad_edge_1d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_edge_2d_test.cpp000066400000000000000000000044141510465702400234760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_edge_2d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3, 3}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {4}}, {1, 2, 1, 2}}); auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {3, 1}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 2}}, {"ends", {3, 3}}}), l0); auto l4 = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l1, l1, l0, l2, l2); auto l5 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {1, 7}}}), l4); auto l6 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {2, 0}}, {"ends", {3, 7}}}), l4); auto r = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), l5, l4, l6); mm->add_return({r}); auto prog = read_onnx("pad_edge_2d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_edge_2d_with_axes_test.cpp000066400000000000000000000037761510465702400255630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_edge_2d_with_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3, 3}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {1}}, {1}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {2}}, {1, 2}}); auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {3, 1}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 2}}, {"ends", {3, 3}}}), l0); auto r = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l1, l0, l2, l2); mm->add_return({r}); auto prog = read_onnx("pad_edge_2d_with_axes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_reflect_multiaxis_test.cpp000066400000000000000000000044301510465702400257260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_reflect_multiaxis_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {4}}, {0, 2, 2, 0}}); auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 1}}, {"ends", {2, 2}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 2}}, {"ends", {2, 3}}}), l0); auto l3 = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l2, l1, l0); auto l4 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {1, 5}}}), l3); auto l5 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {1, 0}}, {"ends", {2, 5}}}), l3); auto r = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), l3, l4, l5); mm->add_return({r}); auto prog = read_onnx("pad_reflect_multiaxis_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_reflect_test.cpp000066400000000000000000000040471510465702400236330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_reflect_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 2}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {4}}, {0, 2, 0, 1}}); auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 1}}, {"ends", {2, 2}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {2, 1}}}), l0); auto l3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {2, 1}}}), l0); auto r = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l2, l1, l0, l3); mm->add_return({r}); auto prog = read_onnx("pad_reflect_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_reflect_with_axes_test.cpp000066400000000000000000000042041510465702400257010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_reflect_with_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 2}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {1}}, {1}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {2}}, {2, 1}}); auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 1}}, {"ends", {2, 2}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {2, 1}}}), l0); auto l3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {2, 1}}}), l0); auto r = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l2, l1, l0, l3); mm->add_return({r}); auto prog = read_onnx("pad_reflect_with_axes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_test.cpp000066400000000000000000000030121510465702400221160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 2}}); mm->add_instruction(migraphx::make_op("pad", {{"pads", {1, 1, 1, 1}}}), l0); auto prog = optimize_onnx("pad_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pad_undef_const_val_test.cpp000066400000000000000000000032731510465702400253600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pad_undef_const_val_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 2}}); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {4}}, {1, 1, 1, 1}}); mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction(migraphx::make_op("pad", {{"pads", {1, 1, 1, 1}}}), l0); auto prog = optimize_onnx("pad_undef_const_val_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pow_fp32_i64_test.cpp000066400000000000000000000034221510465702400235000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pow_fp32_i64_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int64_type, {2, 3, 4, 5}}); auto l1f = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), l1); auto ret = mm->add_instruction(migraphx::make_op("pow"), l0, l1f); mm->add_return({ret}); auto prog = read_onnx("pow_fp32_i64_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pow_i64_fp32_test.cpp000066400000000000000000000036161510465702400235050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pow_i64_fp32_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::int64_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l0f = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), l0); auto fr = mm->add_instruction(migraphx::make_op("pow"), l0f, l1); auto ir = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int64_type}}), fr); mm->add_return({ir}); auto prog = read_onnx("pow_i64_fp32_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/pow_test.cpp000066400000000000000000000031361510465702400221660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(pow_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); mm->add_instruction(migraphx::make_op("pow"), l0, l1); auto prog = optimize_onnx("pow_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/prefix_scan_sum_test.cpp000066400000000000000000000033201510465702400245410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(prefix_scan_sum) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal({migraphx::shape{migraphx::shape::int32_type, {1}, {1}}, {0}}); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 2, 2}}); auto ret = mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", true}, {"reverse", true}}), l0); mm->add_return({ret}); auto prog = read_onnx("prefix_scan_sum_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/prelu_brcst_test.cpp000066400000000000000000000034131510465702400237030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(prelu_brcst_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {4, 5}}); auto bl1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", l0->get_shape().lens()}}), l1); auto ret = mm->add_instruction(migraphx::make_op("prelu"), l0, bl1); mm->add_return({ret}); auto prog = read_onnx("prelu_brcst_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearadd_test.cpp000066400000000000000000000062771510465702400234760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(qlinearadd_test) { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("A", {migraphx::shape::uint8_type, {64}}); auto b = mm->add_parameter("B", {migraphx::shape::uint8_type, {64}}); auto sc_a = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_a = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {0}}); auto sc_b = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_b = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {128}}); auto sc_c = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_c = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {64}}); auto scale_a_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_a); auto z_pt_a_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_a); auto fp_a = mm->add_instruction(migraphx::make_op("dequantizelinear"), a, scale_a_bcast, z_pt_a_bcast); auto scale_b_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_b); auto z_pt_b_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_b); auto fp_b = mm->add_instruction(migraphx::make_op("dequantizelinear"), b, scale_b_bcast, z_pt_b_bcast); auto fp_c = mm->add_instruction(migraphx::make_op("add"), fp_a, fp_b); auto scale_c_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_c); auto z_pt_c_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_c); auto c = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_c, scale_c_bcast, z_pt_c_bcast); mm->add_return({c}); auto prog = read_onnx("qlinearadd_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearaveragepool_notset_test.cpp000066400000000000000000000062311510465702400266340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(qlinearaveragepool_notset_test) { migraphx::program p; auto* mm = p.get_main_module(); auto sc_x = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.5}}); auto z_pt_x = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {0}}); auto sc_y = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.5}}); auto z_pt_y = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {10}}); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::int8_type, {1, 1, 5, 5}}); auto scale_x_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 5, 5}}}), sc_x); auto z_pt_x_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 5, 5}}}), z_pt_x); auto fp_x = mm->add_instruction(migraphx::make_op("dequantizelinear"), x, scale_x_bcast, z_pt_x_bcast); auto fp_y = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {2, 2, 2, 2}}, {"stride", {2, 2}}, {"lengths", {6, 6}}}), fp_x); fp_y = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2, 3}}, {"starts", {1, 1}}, {"ends", {2, 2}}}), fp_y); auto scale_y_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 1, 1}}}), sc_y); auto z_pt_y_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 1, 1}}}), z_pt_y); auto y = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_y, scale_y_bcast, z_pt_y_bcast); mm->add_return({y}); auto prog = read_onnx("qlinearaveragepool_notset_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearconcat_test.cpp000066400000000000000000000062751510465702400242130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(qlinearconcat_test) { migraphx::program p; auto* mm = p.get_main_module(); auto sc_y = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.5}}); auto z_pt_y = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {2}}); auto sc_0 = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.5}}); auto z_pt_0 = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {1}}); auto sc_1 = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.25}}); auto z_pt_1 = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {0}}); auto t0 = mm->add_parameter("t0", {migraphx::shape::int8_type, {2}}); auto t1 = mm->add_parameter("t1", {migraphx::shape::int8_type, {3}}); auto scale_0_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2}}}), sc_0); auto z_pt_0_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2}}}), z_pt_0); auto fp_0 = mm->add_instruction(migraphx::make_op("dequantizelinear"), t0, scale_0_bcast, z_pt_0_bcast); auto scale_1_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), sc_1); auto z_pt_1_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), z_pt_1); auto fp_1 = mm->add_instruction(migraphx::make_op("dequantizelinear"), t1, scale_1_bcast, z_pt_1_bcast); auto fp_y = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), fp_0, fp_1); auto scale_y_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), sc_y); auto z_pt_y_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), z_pt_y); auto y = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_y, scale_y_bcast, z_pt_y_bcast); mm->add_return({y}); auto prog = read_onnx("qlinearconcat_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearconv_test.cpp000066400000000000000000000065271510465702400237110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(qlinearconv_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("X", {migraphx::shape::uint8_type, {1, 1, 7, 7}}); auto sc_x = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.00369204697}}); auto z_pt_x = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {132}}); auto w = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::uint8_type, {1, 1, 1, 1}}, {0}}); auto sc_w = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.00172794575}}); auto z_pt_w = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {255}}); auto sc_y = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.00162681262}}); auto z_pt_y = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {123}}); auto scale_x_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 7, 7}}}), sc_x); auto z_pt_x_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 7, 7}}}), z_pt_x); auto fp_x = mm->add_instruction(migraphx::make_op("dequantizelinear"), x, scale_x_bcast, z_pt_x_bcast); auto scale_w_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 1, 1}}}), sc_w); auto z_pt_w_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 1, 1}}}), z_pt_w); auto fp_w = mm->add_instruction(migraphx::make_op("dequantizelinear"), w, scale_w_bcast, z_pt_w_bcast); auto fp_y = mm->add_instruction(migraphx::make_op("convolution"), fp_x, fp_w); auto scale_y_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 7, 7}}}), sc_y); auto z_pt_y_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 7, 7}}}), z_pt_y); auto y = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_y, scale_y_bcast, z_pt_y_bcast); mm->add_return({y}); auto prog = read_onnx("qlinearconv_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearglobalavgpool_test.cpp000066400000000000000000000057161510465702400255730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(qlinearglobalavgpool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("X", {migraphx::shape::uint8_type, {1, 3, 4, 4}}); auto sc_x = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_x = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {128}}); auto sc_y = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.025}}); auto z_pt_y = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {64}}); auto scale_x_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 3, 4, 4}}}), sc_x); auto z_pt_x_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 3, 4, 4}}}), z_pt_x); auto fp_x = mm->add_instruction(migraphx::make_op("dequantizelinear"), x, scale_x_bcast, z_pt_x_bcast); auto fp_y = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 0, 0}}, {"lengths", {4, 4}}}), fp_x); auto scale_y_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 3, 1, 1}}}), sc_y); auto z_pt_y_bcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 3, 1, 1}}}), z_pt_y); auto y = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_y, scale_y_bcast, z_pt_y_bcast); mm->add_return({y}); auto prog = read_onnx("qlinearglobalavgpool_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearleakyrelu_test.cpp000066400000000000000000000051651510465702400247360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(qlinearleakyrelu_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("X", {migraphx::shape::int8_type, {64}}); auto sc_x = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_x = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {0}}); auto sc_y = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_y = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {10}}); auto scale_x_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_x); auto z_pt_x_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_x); auto fp_x = mm->add_instruction(migraphx::make_op("dequantizelinear"), x, scale_x_bcast, z_pt_x_bcast); auto fp_y = mm->add_instruction(migraphx::make_op("leaky_relu", {{"alpha", 1.1}}), fp_x); auto scale_y_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_y); auto z_pt_y_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_y); auto y = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_y, scale_y_bcast, z_pt_y_bcast); mm->add_return({y}); auto prog = read_onnx("qlinearleakyrelu_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearmatmul_1D_test.cpp000066400000000000000000000067311510465702400245640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(qlinearmatmul_1_d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("A", {migraphx::shape::uint8_type, {8}}); auto b = mm->add_parameter("B", {migraphx::shape::uint8_type, {8}}); auto sc_a = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_a = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {0}}); auto sc_b = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_b = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {128}}); auto sc_c = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_c = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {64}}); auto scale_a_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8}}}), sc_a); auto z_pt_a_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8}}}), z_pt_a); auto fp_a = mm->add_instruction(migraphx::make_op("dequantizelinear"), a, scale_a_bcast, z_pt_a_bcast); auto scale_b_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8}}}), sc_b); auto z_pt_b_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8}}}), z_pt_b); auto fp_b = mm->add_instruction(migraphx::make_op("dequantizelinear"), b, scale_b_bcast, z_pt_b_bcast); auto sq_a = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), fp_a); auto sq_b = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), fp_b); auto fp_c = mm->add_instruction(migraphx::make_op("dot"), sq_a, sq_b); auto sq_c = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), fp_c); auto scale_c_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1}}}), sc_c); auto z_pt_c_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1}}}), z_pt_c); auto c = mm->add_instruction(migraphx::make_op("quantizelinear"), sq_c, scale_c_bcast, z_pt_c_bcast); mm->add_return({c}); auto prog = read_onnx("qlinearmatmul_1D_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearmatmul_2D_test.cpp000066400000000000000000000063341510465702400245640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(qlinearmatmul_2_d_test) { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("A", {migraphx::shape::uint8_type, {1, 8}}); auto b = mm->add_parameter("B", {migraphx::shape::uint8_type, {8, 1}}); auto sc_a = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_a = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {0}}); auto sc_b = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_b = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {128}}); auto sc_c = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_c = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {64}}); auto scale_a_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 8}}}), sc_a); auto z_pt_a_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 8}}}), z_pt_a); auto fp_a = mm->add_instruction(migraphx::make_op("dequantizelinear"), a, scale_a_bcast, z_pt_a_bcast); auto scale_b_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8, 1}}}), sc_b); auto z_pt_b_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8, 1}}}), z_pt_b); auto fp_b = mm->add_instruction(migraphx::make_op("dequantizelinear"), b, scale_b_bcast, z_pt_b_bcast); auto fp_c = mm->add_instruction(migraphx::make_op("dot"), fp_a, fp_b); auto scale_c_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 1}}}), sc_c); auto z_pt_c_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 1}}}), z_pt_c); auto c = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_c, scale_c_bcast, z_pt_c_bcast); mm->add_return({c}); auto prog = read_onnx("qlinearmatmul_2D_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearmul_test.cpp000066400000000000000000000062771510465702400235430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(qlinearmul_test) { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("A", {migraphx::shape::uint8_type, {64}}); auto b = mm->add_parameter("B", {migraphx::shape::uint8_type, {64}}); auto sc_a = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_a = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {0}}); auto sc_b = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_b = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {16}}); auto sc_c = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_c = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {100}}); auto scale_a_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_a); auto z_pt_a_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_a); auto fp_a = mm->add_instruction(migraphx::make_op("dequantizelinear"), a, scale_a_bcast, z_pt_a_bcast); auto scale_b_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_b); auto z_pt_b_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_b); auto fp_b = mm->add_instruction(migraphx::make_op("dequantizelinear"), b, scale_b_bcast, z_pt_b_bcast); auto fp_c = mm->add_instruction(migraphx::make_op("mul"), fp_a, fp_b); auto scale_c_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_c); auto z_pt_c_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_c); auto c = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_c, scale_c_bcast, z_pt_c_bcast); mm->add_return({c}); auto prog = read_onnx("qlinearmul_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/qlinearsigmoid_test.cpp000066400000000000000000000051401510465702400243650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(qlinearsigmoid_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("X", {migraphx::shape::int8_type, {64}}); auto sc_x = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.05}}); auto z_pt_x = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {0}}); auto sc_y = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.0035}}); auto z_pt_y = mm->add_literal(migraphx::literal{migraphx::shape::int8_type, {-128}}); auto scale_x_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_x); auto z_pt_x_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_x); auto fp_x = mm->add_instruction(migraphx::make_op("dequantizelinear"), x, scale_x_bcast, z_pt_x_bcast); auto fp_y = mm->add_instruction(migraphx::make_op("sigmoid"), fp_x); auto scale_y_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), sc_y); auto z_pt_y_bcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {64}}}), z_pt_y); auto y = mm->add_instruction(migraphx::make_op("quantizelinear"), fp_y, scale_y_bcast, z_pt_y_bcast); mm->add_return({y}); auto prog = read_onnx("qlinearsigmoid_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_2d_blocked_with_zp_test.cpp000066400000000000000000000045251510465702400302330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/make_op.hpp" #include TEST_CASE(quantizelinear_2d_blocked_with_zp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {6, 2}}); auto scale = mm->add_parameter("scale", migraphx::shape{migraphx::shape::float_type, {2, 2}}); auto zp = mm->add_parameter("zp", migraphx::shape{migraphx::shape::int8_type, {2, 2}}); scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), scale); scale = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 2}}}), scale); scale = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {6, 2}}}), scale); zp = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), zp); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 2}}}), zp); zp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {6, 2}}}), zp); mm->add_instruction(migraphx::make_op("quantizelinear"), x, scale, zp); auto prog = optimize_onnx("quantizelinear_2d_blocked_with_zp_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_3d_blocked_with_zp_runt_block_test.cpp000066400000000000000000000052171510465702400324550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/make_op.hpp" #include TEST_CASE(quantizelinear_3d_blocked_with_zp_runt_block_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 5, 2}}); auto scale = mm->add_parameter("scale", migraphx::shape{migraphx::shape::float_type, {2, 2, 2}}); auto zp = mm->add_parameter("zp", migraphx::shape{migraphx::shape::int8_type, {2, 2, 2}}); scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scale); scale = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), scale); scale = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), scale); scale = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), scale); zp = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), zp); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), zp); zp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), zp); zp = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), zp); mm->add_instruction(migraphx::make_op("quantizelinear"), x, scale, zp); auto prog = optimize_onnx("quantizelinear_3d_blocked_with_zp_runt_block_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_axis_test.cpp000066400000000000000000000026551510465702400254450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(quantizelinear_axis_test) { migraphx::program p = make_quantizelinear_axis_prog(); auto prog = optimize_onnx("quantizelinear_axis_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_int32_test.cpp000066400000000000000000000044621510465702400254360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(quantizelinear_int32_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::int32_type, {5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1}}); auto l1_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), l1); l0 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l0); auto div = mm->add_instruction(migraphx::make_op("div"), l0, l1_mbcast); auto nearbyint = mm->add_instruction(migraphx::make_op("nearbyint"), div); auto s = nearbyint->get_shape(); auto clip = insert_quantizelinear_clip(*mm, div, nearbyint, s, 0, 255); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::uint8_type)}}), clip); auto prog = optimize_onnx("quantizelinear_int32_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_mx_type_test.cpp000066400000000000000000000073731510465702400261700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include // even fastest dimension TEST_CASE(quantizelinear_mxfp4_even_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {3, 64, 4, 4}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {3, 2, 4, 4}}); auto l1_reshape = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), l1); l1_reshape = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 2, 32, 4, 4}}}), l1_reshape); l1_reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 64, 4, 4}}}), l1_reshape); auto q_ins = mm->add_instruction( migraphx::make_op("quantizelinear", {{"out_type", migraphx::shape::float_type}}), l0, l1_reshape); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_fp4", {{"axis", 3}}), q_ins); auto unpack_ins = mm->add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), pack_ins); mm->add_return({unpack_ins}); auto prog = read_onnx("quantizelinear_mxfp4_even_test.onnx"); EXPECT(p.sort() == prog.sort()); } // odd fastest dimension TEST_CASE(quantizelinear_mxfp4_odd_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {3, 64, 4, 7}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {3, 2, 4, 7}}); auto l1_reshape = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), l1); l1_reshape = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 2, 32, 4, 7}}}), l1_reshape); l1_reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {3, 64, 4, 7}}}), l1_reshape); auto q_ins = mm->add_instruction( migraphx::make_op("quantizelinear", {{"out_type", migraphx::shape::float_type}}), l0, l1_reshape); auto pad_ins = mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 0, 0, 0, 0, 1}}}), q_ins); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_fp4", {{"axis", 3}}), pad_ins); auto unpack_ins = mm->add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), pack_ins); auto slice_ins = mm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {7}}}), unpack_ins); mm->add_return({slice_ins}); auto prog = read_onnx("quantizelinear_mxfp4_odd_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_neg_axis_test.cpp000066400000000000000000000026651510465702400262770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(quantizelinear_neg_axis_test) { migraphx::program p = make_quantizelinear_axis_prog(); auto prog = optimize_onnx("quantizelinear_neg_axis_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_negative_tests.cpp000066400000000000000000000047761510465702400264740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(quantizelinear_too_few_inputs_test) { EXPECT(test::throws([&] { read_onnx("quantizelinear_too_few_inputs_test.onnx"); })); } TEST_CASE(quantizelinear_too_many_inputs_test) { EXPECT(test::throws([&] { read_onnx("quantizelinear_too_many_inputs_test.onnx"); })); } TEST_CASE(quantizelinear_scales_and_zp_shape_mismatch_test) { EXPECT( test::throws([&] { read_onnx("quantizelinear_scales_and_zp_shape_mismatch_test.onnx"); })); } TEST_CASE(quantizelinear_output_dtype_and_zp_type_mismatch_test) { EXPECT(test::throws( [&] { read_onnx("quantizelinear_output_dtype_and_zp_type_mismatch_test.onnx"); })); } TEST_CASE(quantizelinear_per_axis_shape_mismatch_test) { EXPECT(test::throws([&] { read_onnx("quantizelinear_per_axis_shape_mismatch_test.onnx"); })); } TEST_CASE(quantizelinear_blocked_x_and_scales_rank_mismatch_test) { EXPECT(test::throws( [&] { read_onnx("quantizelinear_blocked_x_and_scales_rank_mismatch_test.onnx"); })); } TEST_CASE(quantizelinear_blocked_non_bc_axis_size_mismatch_test) { EXPECT(test::throws( [&] { read_onnx("quantizelinear_blocked_non_bc_axis_size_mismatch_test.onnx"); })); } TEST_CASE(quantizelinear_blocked_invalid_block_size_test) { EXPECT(test::throws([&] { read_onnx("quantizelinear_blocked_invalid_block_size_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_q_block_sz_1.cpp000066400000000000000000000027761510465702400260140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include // This is a snippet off a quark generated model, which uses block quantization. // The unit-test checks there is no exception thrown for this boundary case (block size =1). TEST_CASE(quantizelinear_qblock_sz_1_test) { EXPECT(not test::throws([&] { read_onnx("int4_const_identity_block_sz_1_qdq_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_test.cpp000066400000000000000000000041671510465702400244210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(quantizelinear_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1}}); auto l1_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), l1); auto div = mm->add_instruction(migraphx::make_op("div"), l0, l1_mbcast); auto nearbyint = mm->add_instruction(migraphx::make_op("nearbyint"), div); auto s = nearbyint->get_shape(); auto clip = insert_quantizelinear_clip(*mm, div, nearbyint, s, 0, 255); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::uint8_type)}}), clip); auto prog = optimize_onnx("quantizelinear_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/quantizelinear_zero_point_test.cpp000066400000000000000000000050731510465702400266660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(quantizelinear_zero_point_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", {migraphx::shape::float_type, {5}}); auto l1 = mm->add_parameter("1", {migraphx::shape::float_type, {1}}); auto l2 = mm->add_parameter("2", {migraphx::shape::int8_type, {1}}); auto l1_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), l1); auto div = mm->add_instruction(migraphx::make_op("div"), l0, l1_mbcast); auto round = mm->add_instruction(migraphx::make_op("nearbyint"), div); auto l2_mbcast = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), l2); l2_mbcast = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), l2_mbcast); auto add = mm->add_instruction(migraphx::make_op("add"), round, l2_mbcast); auto s = round->get_shape(); auto clip = insert_quantizelinear_clip(*mm, div, add, s, -128, 127); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::int8_type)}}), clip); auto prog = optimize_onnx("quantizelinear_zero_point_test.onnx", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomnormal_dtype_error_test.cpp000066400000000000000000000025001510465702400264620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(randomnormal_dtype_error_test) { EXPECT(test::throws([&] { read_onnx("randomnormal_dtype_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomnormal_generated_seed_test.cpp000066400000000000000000000026221510465702400270670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(randomnormal_generated_seed_test) { auto p1 = optimize_onnx("randomnormal_generated_seed_test.onnx"); auto p2 = optimize_onnx("randomnormal_generated_seed_test.onnx"); EXPECT(p1 != p2); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomnormal_shape_error_test.cpp000066400000000000000000000025001510465702400264350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(randomnormal_shape_error_test) { EXPECT(test::throws([&] { read_onnx("randomnormal_shape_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomnormal_test.cpp000066400000000000000000000034701510465702400240530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(randomnormal_test) { float mean = 10.0; float scale = 1.5; float seed = 0.0; std::vector shape_attr{2, 3, 4}; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, shape_attr}; std::vector rand_vals(s.elements()); std::mt19937 gen(seed); std::normal_distribution<> d(mean, scale); std::generate(rand_vals.begin(), rand_vals.end(), [&]() { return d(gen); }); mm->add_literal(migraphx::literal{s, rand_vals}); auto prog = optimize_onnx("randomnormal_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomnormallike_test.cpp000066400000000000000000000035411510465702400247170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(randomnormallike_test) { float mean = 10.0; float scale = 1.5; float seed = 0.0; std::vector shape_attr{2, 3, 4}; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, shape_attr}; std::vector rand_vals(s.elements()); std::mt19937 gen(seed); std::normal_distribution<> d(mean, scale); std::generate(rand_vals.begin(), rand_vals.end(), [&]() { return d(gen); }); mm->add_parameter("input", s); mm->add_literal(migraphx::literal{s, rand_vals}); auto prog = optimize_onnx("randomnormallike_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomnormallike_type_error_test.cpp000066400000000000000000000025061510465702400271710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(randomnormallike_type_error_test) { EXPECT(test::throws([&] { read_onnx("randomnormallike_type_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomuniform_dtype_error_test.cpp000066400000000000000000000025021510465702400266530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(randomuniform_dtype_error_test) { EXPECT(test::throws([&] { read_onnx("randomuniform_dtype_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomuniform_generated_seed_test.cpp000066400000000000000000000026251510465702400272610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(randomuniform_generated_seed_test) { auto p1 = optimize_onnx("randomuniform_generated_seed_test.onnx"); auto p2 = optimize_onnx("randomuniform_generated_seed_test.onnx"); EXPECT(p1 != p2); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomuniform_shape_error_test.cpp000066400000000000000000000025021510465702400266260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(randomuniform_shape_error_test) { EXPECT(test::throws([&] { read_onnx("randomuniform_shape_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomuniform_test.cpp000066400000000000000000000034721510465702400242440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(randomuniform_test) { float high = 1.0; float low = 0.0; float seed = 0.0; std::vector shape_attr{2, 3, 4}; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, shape_attr}; std::vector rand_vals(s.elements()); std::mt19937 gen(seed); std::uniform_real_distribution<> d(low, high); std::generate(rand_vals.begin(), rand_vals.end(), [&]() { return d(gen); }); mm->add_literal(migraphx::literal{s, rand_vals}); auto prog = optimize_onnx("randomuniform_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomuniformlike_test.cpp000066400000000000000000000035441510465702400251110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(randomuniformlike_test) { float high = 10.0; float low = 1.0; float seed = 0.0; std::vector shape_attr{2, 3, 4}; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, shape_attr}; std::vector rand_vals(s.elements()); std::mt19937 gen(seed); std::uniform_real_distribution<> d(low, high); std::generate(rand_vals.begin(), rand_vals.end(), [&]() { return d(gen); }); mm->add_parameter("input", s); mm->add_literal(migraphx::literal{s, rand_vals}); auto prog = optimize_onnx("randomuniformlike_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/randomuniformlike_type_error_test.cpp000066400000000000000000000025101510465702400273530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(randomuniformlike_type_error_test) { EXPECT(test::throws([&] { read_onnx("randomuniformlike_type_error_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/range_float_test.cpp000066400000000000000000000030511510465702400236360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(range_float_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(float{2}); mm->add_literal(float{11}); mm->add_literal(float{2}); mm->add_literal(migraphx::literal{{migraphx::shape::float_type, {5}}, {2, 4, 6, 8, 10}}); auto prog = optimize_onnx("range_float_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/range_test.cpp000066400000000000000000000030331510465702400224510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(range_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(int64_t{10}); mm->add_literal(int64_t{6}); mm->add_literal(int64_t{-3}); mm->add_literal(migraphx::literal{{migraphx::shape::int64_type, {2}}, {10, 7}}); auto prog = optimize_onnx("range_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/recip_test.cpp000066400000000000000000000027731510465702400224710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(recip_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3}}); mm->add_instruction(migraphx::make_op("recip"), input); auto prog = optimize_onnx("recip_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reduce_log_sum_exp_test.cpp000066400000000000000000000032771510465702400252370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reduce_log_sum_exp_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto exp_l0 = mm->add_instruction(migraphx::make_op("exp"), l0); auto sum_l0 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {-4}}}), exp_l0); mm->add_instruction(migraphx::make_op("log"), sum_l0); auto prog = optimize_onnx("reduce_log_sum_exp_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reduce_log_sum_test.cpp000066400000000000000000000031561510465702400243570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reduce_log_sum_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto sum_l0 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {-3}}}), l0); mm->add_instruction(migraphx::make_op("log"), sum_l0); auto prog = optimize_onnx("reduce_log_sum_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducel1_dyn_test.cpp000066400000000000000000000061341510465702400237400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducel1_dyn_test) { { migraphx::program p; auto* mm = p.get_main_module(); // a shape with 4 dynamic dimensions auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {{3, 3}, {3, 5}, {4, 6, {5}}, {5, 7, {6}}}}); auto abs_ins = mm->add_instruction(migraphx::make_op("abs"), l0); auto sum_ins = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {-2}}}), abs_ins); auto sq_ins = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {-2}}}), sum_ins); mm->add_return({sq_ins}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{3, 3}, {3, 5}, {4, 6, {5}}, {5, 7, {6}}}; auto prog = read_onnx("reducel1_dyn_test.onnx", options); EXPECT(p == prog); } { migraphx::program p; auto* mm = p.get_main_module(); // No axes given in the onnx file. Parser should default to all axes. auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {{3, 3}, {3, 5}, {4, 6, {5}}, {5, 7, {6}}}}); auto abs_ins = mm->add_instruction(migraphx::make_op("abs"), l0); auto sum_ins = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2, 3}}}), abs_ins); auto sq_ins = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0, 1, 2, 3}}}), sum_ins); mm->add_return({sq_ins}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{3, 3}, {3, 5}, {4, 6, {5}}, {5, 7, {6}}}; auto prog = read_onnx("reducel1_dyn_noaxes_test.onnx", options); EXPECT(p == prog); } } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducel1_test.cpp000066400000000000000000000033011510465702400230570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducel1_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto abs_l0 = mm->add_instruction(migraphx::make_op("abs"), l0); auto sum_l0 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {-2}}}), abs_l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {-2}}}), sum_l0); auto prog = optimize_onnx("reducel1_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducel2_test.cpp000066400000000000000000000034251510465702400230670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducel2_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto square_l0 = mm->add_instruction(migraphx::make_op("mul"), l0, l0); auto sum_l0 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {-1}}}), square_l0); auto squ_l0 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {-1}}}), sum_l0); mm->add_instruction(migraphx::make_op("sqrt"), squ_l0); auto prog = optimize_onnx("reducel2_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducemax_dyn_test.cpp000066400000000000000000000035561510465702400242160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducemax_dyn_test) { // input shape with 4 dynamic dimensions migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "x", migraphx::shape{migraphx::shape::float_type, {{3, 5}, {4, 4}, {5, 5}, {6, 6}}}); auto r0 = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2}}}), l0); auto r1 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), r0); mm->add_return({r1}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{3, 5}, {4, 4}, {5, 5}, {6, 6}}; auto prog = read_onnx("reducemax_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducemax_fp8_test.cpp000066400000000000000000000032051510465702400241100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducemax_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {3, 4, 5, 6}}); auto l1 = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2}}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), l1); auto prog = optimize_onnx("reducemax_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducemax_test.cpp000066400000000000000000000030321510465702400233310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducemax_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2}}}), l0); auto prog = optimize_onnx("reducemax_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducemean_keepdims_test.cpp000066400000000000000000000030571510465702400253540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducemean_keepdims_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), l0); auto prog = optimize_onnx("reducemean_keepdims_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducemean_test.cpp000066400000000000000000000031721510465702400234710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducemean_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto l1 = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), l1); auto prog = optimize_onnx("reducemean_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducemin_test.cpp000066400000000000000000000031671510465702400233400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducemin_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto l1 = mm->add_instruction(migraphx::make_op("reduce_min", {{"axes", {2, 3}}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), l1); auto prog = optimize_onnx("reducemin_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reduceprod_test.cpp000066400000000000000000000030351510465702400235130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reduceprod_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); mm->add_instruction(migraphx::make_op("reduce_prod", {{"axes", {2}}}), l0); auto prog = optimize_onnx("reduceprod_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_empty_axes_test.cpp000066400000000000000000000033731510465702400256160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_empty_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape::int64_type}); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto l1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2, 3}}}), x); auto r = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0, 1, 2, 3}}}), l1); mm->add_return({r}); auto prog = read_onnx("reducesum_empty_axes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_fp8_test.cpp000066400000000000000000000032051510465702400241270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {3, 4, 5, 6}}); auto l1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), l1); auto prog = optimize_onnx("reducesum_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_keepdims_test.cpp000066400000000000000000000030571510465702400252400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_keepdims_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3}}}), l0); auto prog = optimize_onnx("reducesum_keepdims_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_multiaxis_test.cpp000066400000000000000000000032131510465702400254500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_multiaxis_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto l1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3}}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), l1); auto prog = optimize_onnx("reducesum_multiaxis_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_noop_test.cpp000066400000000000000000000030551510465702400244100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_noop_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape::int64_type}); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); mm->add_return({x}); auto prog = read_onnx("reducesum_noop_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_square_test.cpp000066400000000000000000000033251510465702400247350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_square_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto squ_l0 = mm->add_instruction(migraphx::make_op("mul"), l0, l0); auto sum_l0 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {-2}}}), squ_l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {-2}}}), sum_l0); auto prog = optimize_onnx("reducesum_square_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_test.cpp000066400000000000000000000031611510465702400233530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto l1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), l1); auto prog = optimize_onnx("reducesum_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_variable_axes_keepdims_clear_test.cpp000066400000000000000000000025331510465702400312710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_variable_axes_keepdims_clear_test) { EXPECT(test::throws([&] { read_onnx("reducesum_variable_axes_keepdims_clear_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_variable_axes_noop_test.cpp000066400000000000000000000032431510465702400272740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_variable_axes_noop_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int64_type, {1}}); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {}}}), x, axes); auto prog = optimize_onnx("reducesum_variable_axes_noop_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_variable_axes_test.cpp000066400000000000000000000032311510465702400262360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_variable_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int64_type, {1}}); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {}}}), x, axes); auto prog = optimize_onnx("reducesum_variable_axes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_variable_dynamic_axes_test.cpp000066400000000000000000000053451510465702400277520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_variable_dynamic_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); const std::vector axes_dims{{0, 3}}; auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int64_type, axes_dims}); auto reduce_input_axes = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {}}}), x, axes); std::vector all_axes(x->get_shape().ndim()); std::iota(all_axes.begin(), all_axes.end(), 0); auto all_axes_lit = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::type_t::int64_type, {all_axes.size()}}, all_axes}); auto reduce_all_axes = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {}}}), x, all_axes_lit); auto zero_lit = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type}, {0u}}); auto axes_size = mm->add_instruction(migraphx::make_op("dimensions_of", {{"end", 1}}), axes); auto is_axes_empty = mm->add_instruction(migraphx::make_op("equal"), axes_size, zero_lit); auto where = mm->add_instruction( migraphx::make_op("where"), is_axes_empty, reduce_all_axes, reduce_input_axes); mm->add_return({where}); migraphx::onnx_options options; options.map_dyn_input_dims["axes"] = axes->get_shape().dyn_dims(); auto prog = read_onnx("reducesum_variable_dynamic_axes_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reducesum_variable_empty_axes_test.cpp000066400000000000000000000041511510465702400274560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reducesum_variable_empty_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int64_type, {0}}); std::vector all_axes(x->get_shape().ndim()); std::iota(all_axes.begin(), all_axes.end(), 0); auto all_axes_lit = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::int64_type, {all_axes.size()}}, all_axes}); auto reduce_all_axes = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {}}}), x, all_axes_lit); mm->add_return({reduce_all_axes}); migraphx::onnx_options options; options.map_input_dims["axes"] = axes->get_shape().lens(); auto prog = read_onnx("reducesum_variable_axes_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reshape_non_standard_test.cpp000066400000000000000000000033541510465702400255440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(reshape_non_standard_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::op::reshape op; migraphx::shape s{migraphx::shape::float_type, {2, 3, 4}}; auto x = mm->add_parameter("x", s); auto tran_x = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), x); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4, 3, 2}}}), tran_x); auto prog = optimize_onnx("reshape_non_standard_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reshape_test.cpp000066400000000000000000000034001510465702400230020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(reshape_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::op::reshape op; std::vector reshape_dims{3, 8}; mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {2}}, reshape_dims}); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 2, 3}}); op.dims = reshape_dims; mm->add_instruction(op, l0); mm->add_instruction(op, l0); auto prog = optimize_onnx("reshape_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reshape_variable_input_dyn_test.cpp000066400000000000000000000036661510465702400267560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reshape_variable_input_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto p0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}, {3, 3}}}); auto p1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int64_type, {2}}); auto alloc = mm->add_instruction( migraphx::make_op("allocate", {{"buf_type", migraphx::shape::float_type}}), p1); auto reshape = mm->add_instruction(migraphx::make_op("reshape"), p0, alloc); mm->add_return({reshape}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("reshape_variable_input_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reshape_variable_input_test.cpp000066400000000000000000000034031510465702400260710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reshape_variable_input_test) { migraphx::program p; auto* mm = p.get_main_module(); auto p0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 2, 3}}); auto p1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::int64_type, {2}}); auto alloc = mm->add_instruction( migraphx::make_op("allocate", {{"buf_type", migraphx::shape::float_type}}), p1); mm->add_instruction(migraphx::make_op("reshape"), p0, alloc); auto prog = optimize_onnx("reshape_variable_input_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_aspect_ratio_err_test.cpp000066400000000000000000000024761510465702400262750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_aspect_ratio_err_test) { EXPECT(test::throws([&] { read_onnx("resize_aspect_ratio_err_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_downsample_c_test.cpp000066400000000000000000000041001510465702400254050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_downsample_c_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector ds = {1.0f, 1.0f, 0.6f, 0.6f}; migraphx::shape ss{migraphx::shape::float_type, {4}}; mm->add_literal(migraphx::literal{ss, ds}); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 4}}; auto inx = mm->add_parameter("X", sx); mm->add_instruction(migraphx::make_op("undefined")); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 1, 2}}; std::vector ind = {0, 2}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto lrsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {8}}}), inx); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); mm->add_return({r}); auto prog = read_onnx("resize_downsample_c_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_downsample_f_dyn3_test.cpp000066400000000000000000000042761510465702400263630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_downsample_f_dyn3_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ss{migraphx::shape::float_type, {4}}; mm->add_instruction(migraphx::make_op("undefined")); migraphx::shape sx{migraphx::shape::float_type, {{1, 4, {1, 4}}, {1, 1}, {5, 5}, {9, 9}}}; auto inx = mm->add_parameter("X", sx); auto li = mm->add_parameter("scales", ss); auto r = mm->add_instruction(migraphx::make_op("resize", {{"mode", "nearest"}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "asymmetric"}}), inx, li); mm->add_return({r}); migraphx::onnx_options options; options.map_dyn_input_dims["X"] = {{1, 4, {1, 4}}, {1, 1}, {5, 5}, {9, 9}}; auto prog = read_onnx("resize_downsample_f_dyn3_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_downsample_f_dyn_test.cpp000066400000000000000000000044141510465702400262720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_downsample_f_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector ds = {1.f, 1.f, 0.601, 0.601}; migraphx::shape ss{migraphx::shape::float_type, {4}}; auto li = mm->add_literal(migraphx::literal{ss, ds}); mm->add_instruction(migraphx::make_op("undefined")); migraphx::shape sx{migraphx::shape::float_type, {{1, 4, {1, 4}}, {1, 1}, {5, 5}, {9, 9}}}; auto inx = mm->add_parameter("X", sx); auto r = mm->add_instruction(migraphx::make_op("resize", {{"mode", "nearest"}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "asymmetric"}}), inx, li); mm->add_return({r}); migraphx::onnx_options options; options.map_dyn_input_dims["X"] = {{1, 4, {1, 4}}, {1, 1}, {5, 5}, {9, 9}}; auto prog = read_onnx("resize_downsample_f_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_downsample_f_test.cpp000066400000000000000000000041141510465702400254150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_downsample_f_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector ds = {1.0f, 1.0f, 0.6f, 0.6f}; migraphx::shape ss{migraphx::shape::float_type, {4}}; mm->add_literal(migraphx::literal{ss, ds}); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 4}}; auto inx = mm->add_parameter("X", sx); mm->add_instruction(migraphx::make_op("undefined")); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 1, 2}}; std::vector ind = {0, 3}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto lrsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {8}}}), inx); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); mm->add_return({r}); auto prog = read_onnx("resize_downsample_f_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_downsample_linear_half_test.cpp000066400000000000000000000112421510465702400274340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include /* IR for the test case below: module: "main" @0 = @literal{0.333008, 0.333008} -> half_type, {1, 1, 1, 2}, {2, 2, 2, 1} @1 = @literal{0.5, 0.5, 0.5, 0.5} -> half_type, {2, 1, 1, 2}, {2, 2, 2, 1} @2 = @literal{0, 2, 4, 6, 1, 3, 5, 7} -> int32_type, {4, 1, 1, 2}, {2, 2, 2, 1} X = @param:X -> half_type, {1, 1, 2, 4}, {8, 8, 4, 1} @4 = @literal{1, 1, 0.600098, 0.5} -> half_type, {4}, {1} @5 = undefined -> float_type, {}, {} @6 = reshape[dims={8}](X) -> half_type, {8}, {1} @7 = gather[axis=0](@6,@2) -> half_type, {4, 1, 1, 2}, {2, 2, 2, 1} @8 = slice[axes={0},starts={0},ends={2}](@7) -> half_type, {2, 1, 1, 2}, {2, 2, 2, 1} @9 = slice[axes={0},starts={2},ends={4}](@7) -> half_type, {2, 1, 1, 2}, {2, 2, 2, 1} @10 = sub(@9,@8) -> half_type, {2, 1, 1, 2}, {2, 2, 2, 1} @11 = mul(@10,@1) -> half_type, {2, 1, 1, 2}, {2, 2, 2, 1} @12 = add(@11,@8) -> half_type, {2, 1, 1, 2}, {2, 2, 2, 1} @13 = slice[axes={0},starts={0},ends={1}](@12) -> half_type, {1, 1, 1, 2}, {2, 2, 2, 1} @14 = slice[axes={0},starts={1},ends={2}](@12) -> half_type, {1, 1, 1, 2}, {2, 2, 2, 1} @15 = sub(@14,@13) -> half_type, {1, 1, 1, 2}, {2, 2, 2, 1} @16 = mul(@15,@0) -> half_type, {1, 1, 1, 2}, {2, 2, 2, 1} @17 = add(@16,@13) -> half_type, {1, 1, 1, 2}, {2, 2, 2, 1} @18 = @return(@17) */ TEST_CASE(resize_downsample_linear_half_test) { using migraphx::half; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ss{migraphx::shape::float_type, {4}}; std::vector ds = {1.0f, 1.0f, 0.6f, 0.5f}; mm->add_literal(migraphx::literal(ss, ds)); migraphx::shape sx{migraphx::shape::half_type, {1, 1, 2, 4}}; auto x = mm->add_parameter("X", sx); migraphx::shape s_ind{migraphx::shape::int32_type, {4, 1, 1, 2}}; std::vector d_ind = {0, 2, 4, 6, 1, 3, 5, 7}; auto l_ind = mm->add_literal(migraphx::literal(s_ind, d_ind)); migraphx::shape s2{migraphx::shape::half_type, {2, 1, 1, 2}}; std::vector d2(4, 0.5f); auto l2 = mm->add_literal(migraphx::literal(s2, d2)); migraphx::shape s1{migraphx::shape::half_type, {1, 1, 1, 2}}; std::vector d1(2, 0.5 / 0.6 - 0.5); auto l1 = mm->add_literal(migraphx::literal(s1, d1)); mm->add_instruction(migraphx::make_op("undefined")); auto rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {8}}}), x); auto data = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), rsp, l_ind); auto slc20 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {2}}}), data); auto slc21 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {4}}}), data); auto diff2 = mm->add_instruction(migraphx::make_op("sub"), slc21, slc20); auto mul2 = mm->add_instruction(migraphx::make_op("mul"), diff2, l2); auto add2 = mm->add_instruction(migraphx::make_op("add"), mul2, slc20); auto slc10 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), add2); auto slc11 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), add2); auto diff1 = mm->add_instruction(migraphx::make_op("sub"), slc11, slc10); auto mul1 = mm->add_instruction(migraphx::make_op("mul"), diff1, l1); auto add1 = mm->add_instruction(migraphx::make_op("add"), mul1, slc10); mm->add_return({add1}); auto prog = read_onnx("resize_downsample_linear_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_downsample_linear_invalid_scale_test.cpp000066400000000000000000000027441510465702400313260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_downsample_linear_half_invalid_scale_test) { // runtime (non-constant) input is only supported in "nearest" mode migraphx::onnx_options options; EXPECT(test::throws( [&] { read_onnx("resize_downsample_linear_half_invalid_scale_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_downsample_linear_test.cpp000066400000000000000000000112001510465702400264340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include /* IR for the test case below: module: "main" @0 = @literal{0.333333, 0.333333} -> float_type, {1, 1, 1, 2}, {2, 2, 2, 1} @1 = @literal{0.5, 0.5, 0.5, 0.5} -> float_type, {2, 1, 1, 2}, {2, 2, 2, 1} @2 = @literal{0, 2, 4, 6, 1, 3, 5, 7} -> int32_type, {4, 1, 1, 2}, {2, 2, 2, 1} X = @param:X -> float_type, {1, 1, 2, 4}, {8, 8, 4, 1} @4 = @literal{1, 1, 0.6, 0.5} -> float_type, {4}, {1} @5 = undefined -> float_type, {}, {} @6 = reshape[dims={8}](X) -> float_type, {8}, {1} @7 = gather[axis=0](@6,@2) -> float_type, {4, 1, 1, 2}, {2, 2, 2, 1} @8 = slice[axes={0},starts={0},ends={2}](@7) -> float_type, {2, 1, 1, 2}, {2, 2, 2, 1} @9 = slice[axes={0},starts={2},ends={4}](@7) -> float_type, {2, 1, 1, 2}, {2, 2, 2, 1} @10 = sub(@9,@8) -> float_type, {2, 1, 1, 2}, {2, 2, 2, 1} @11 = mul(@10,@1) -> float_type, {2, 1, 1, 2}, {2, 2, 2, 1} @12 = add(@11,@8) -> float_type, {2, 1, 1, 2}, {2, 2, 2, 1} @13 = slice[axes={0},starts={0},ends={1}](@12) -> float_type, {1, 1, 1, 2}, {2, 2, 2, 1} @14 = slice[axes={0},starts={1},ends={2}](@12) -> float_type, {1, 1, 1, 2}, {2, 2, 2, 1} @15 = sub(@14,@13) -> float_type, {1, 1, 1, 2}, {2, 2, 2, 1} @16 = mul(@15,@0) -> float_type, {1, 1, 1, 2}, {2, 2, 2, 1} @17 = add(@16,@13) -> float_type, {1, 1, 1, 2}, {2, 2, 2, 1} @18 = @return(@17) */ TEST_CASE(resize_downsample_linear_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ss{migraphx::shape::float_type, {4}}; std::vector ds = {1, 1, 0.6, 0.5}; mm->add_literal(migraphx::literal(ss, ds)); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 4}}; auto x = mm->add_parameter("X", sx); migraphx::shape s_ind{migraphx::shape::int32_type, {4, 1, 1, 2}}; std::vector d_ind = {0, 2, 4, 6, 1, 3, 5, 7}; auto l_ind = mm->add_literal(migraphx::literal(s_ind, d_ind)); migraphx::shape s2{migraphx::shape::float_type, {2, 1, 1, 2}}; std::vector d2(4, 0.5f); auto l2 = mm->add_literal(migraphx::literal(s2, d2)); migraphx::shape s1{migraphx::shape::float_type, {1, 1, 1, 2}}; std::vector d1(2, 1.0f / 3.0f); auto l1 = mm->add_literal(migraphx::literal(s1, d1)); mm->add_instruction(migraphx::make_op("undefined")); auto rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {8}}}), x); auto data = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), rsp, l_ind); auto slc20 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {2}}}), data); auto slc21 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {4}}}), data); auto diff2 = mm->add_instruction(migraphx::make_op("sub"), slc21, slc20); auto mul2 = mm->add_instruction(migraphx::make_op("mul"), diff2, l2); auto add2 = mm->add_instruction(migraphx::make_op("add"), mul2, slc20); auto slc10 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), add2); auto slc11 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), add2); auto diff1 = mm->add_instruction(migraphx::make_op("sub"), slc11, slc10); auto mul1 = mm->add_instruction(migraphx::make_op("mul"), diff1, l1); auto add1 = mm->add_instruction(migraphx::make_op("add"), mul1, slc10); mm->add_return({add1}); auto prog = read_onnx("resize_downsample_linear_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_dyn_err1test.cpp000066400000000000000000000027501510465702400243270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_dyn_err1_test) { // dimensions of input and scales don't match migraphx::shape::dynamic_dimension dd{1, 10}; migraphx::onnx_options options; options.default_dyn_dim_value = dd; EXPECT(test::throws([&] { read_onnx("resize_dyn_err1_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_linear_non_const_test.cpp000066400000000000000000000026631510465702400263000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_linear_non_const_test) { // runtime (non-constant) input is only supported in "nearest" mode migraphx::onnx_options options; EXPECT(test::throws([&] { read_onnx("resize_linear_non_const_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_no_scale_test.cpp000066400000000000000000000025361510465702400245300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_no_scale_test) { // input node has neither shapes nor scales EXPECT(test::throws([&] { read_onnx("resize_no_scale_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_nonstd_input_test.cpp000066400000000000000000000042601510465702400254650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_nonstd_input_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector ds = {1.0f, 1.0f, 0.6f, 0.6f}; migraphx::shape ss{migraphx::shape::float_type, {4}}; mm->add_literal(migraphx::literal{ss, ds}); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 4, 2}}; auto inx = mm->add_parameter("X", sx); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 1, 2}}; std::vector ind = {0, 4}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto tx = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), inx); mm->add_instruction(migraphx::make_op("undefined")); auto lrsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {8}}}), tx); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); mm->add_return({r}); auto prog = read_onnx("resize_nonstd_input_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_outsize_test.cpp000066400000000000000000000041701510465702400244430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_outsize_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector out_len = {1, 1, 4, 6}; migraphx::shape so{migraphx::shape::int64_type, {4}}; mm->add_literal(migraphx::literal(so, out_len)); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; auto inx = mm->add_parameter("X", sx); mm->add_instruction(migraphx::make_op("undefined")); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 6}}; std::vector ind = {0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto lrsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), inx); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); mm->add_return({r}); auto prog = read_onnx("resize_outsize_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_roi_skip_test.cpp000066400000000000000000000044611510465702400245630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_roi_skip_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sroi{migraphx::shape::float_type, {8}}; std::vector roid = {1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8}; mm->add_literal(migraphx::literal(sroi, roid)); migraphx::shape sscale{migraphx::shape::float_type, {4}}; std::vector scaled = {1, 1, 2, 2}; mm->add_literal(migraphx::literal(sscale, scaled)); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 4}}; auto inx = mm->add_parameter("X", sx); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 8}}; std::vector ind = {0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 2, 3, 3, 3, 4, 4, 4}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto lrsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {8}}}), inx); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); mm->add_return({r}); auto prog = read_onnx("resize_roi_skip_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_upsample_linear_ac_test.cpp000066400000000000000000000026241510465702400265660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(resize_upsample_linear_ac_test) { auto p = create_upsample_linear_prog(); auto prog = read_onnx("resize_upsample_linear_ac_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_upsample_linear_test.cpp000066400000000000000000000026611510465702400261240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(resize_upsample_linear_test) { auto p = create_upsample_linear_prog(); // same net IR as upsample version auto prog = read_onnx("resize_upsample_linear_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_upsample_pc_test.cpp000066400000000000000000000042001510465702400252430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_upsample_pc_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector ds = {1.0f, 1.0f, 2.0f, 1.5f}; migraphx::shape ss{migraphx::shape::float_type, {4}}; mm->add_literal(migraphx::literal{ss, ds}); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 4}}; auto inx = mm->add_parameter("X", sx); mm->add_instruction(migraphx::make_op("undefined")); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 6}}; std::vector ind = {0, 1, 1, 2, 3, 3, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 4, 5, 5, 6, 7, 7}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto lrsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {8}}}), inx); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); mm->add_return({r}); auto prog = read_onnx("resize_upsample_pc_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_upsample_pf_test.cpp000066400000000000000000000042001510465702400252460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_upsample_pf_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector ds = {1.0f, 1.0f, 2.0f, 3.0f}; migraphx::shape ss{migraphx::shape::float_type, {4}}; mm->add_literal(migraphx::literal{ss, ds}); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; auto inx = mm->add_parameter("X", sx); mm->add_instruction(migraphx::make_op("undefined")); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 6}}; std::vector ind = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto lrsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), inx); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); mm->add_return({r}); auto prog = read_onnx("resize_upsample_pf_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/resize_with_same_inout_shapes_test.cpp000066400000000000000000000033411510465702400275010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(resize_with_same_inout_shapes_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector ds = {1, 3, 5}; migraphx::shape ss{migraphx::shape::int64_type, {3}}; mm->add_literal(migraphx::literal{ss, ds}); migraphx::shape sx{migraphx::shape::float_type, {1, 3, 5}}; auto inx = mm->add_parameter("X", sx); mm->add_instruction(migraphx::make_op("undefined")); mm->add_return({inx}); auto prog = read_onnx("resize_with_same_inout_shapes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reversesequence_batch_axis_err_test.cpp000066400000000000000000000025141510465702400276210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reversesequence_batch_axis_err_test) { EXPECT(test::throws([&] { read_onnx("reversesequence_batch_axis_err_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reversesequence_batch_test.cpp000066400000000000000000000055231510465702400257300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reversesequence_batch_test) { migraphx::program p; auto* mm = p.get_main_module(); int batch_axis = 0; int time_axis = 1; migraphx::shape sx{migraphx::shape::float_type, {4, 4}}; auto input = mm->add_parameter("x", sx); std::vector sequence_lens = {1, 2, 3, 4}; mm->add_literal({{migraphx::shape::int64_type, {4}}, sequence_lens}); int batch_size = sx.lens()[batch_axis]; int time_size = sx.lens()[time_axis]; auto add_slice = [&mm, &input, batch_axis, time_axis](int b_start, int b_end, int t_start, int t_end) { return mm->add_instruction(migraphx::make_op("slice", {{"axes", {batch_axis, time_axis}}, {"starts", {b_start, t_start}}, {"ends", {b_end, t_end}}}), input); }; auto ret = add_slice(0, 1, 0, time_size); for(int b = 1; b < batch_size; ++b) { auto s0 = add_slice(b, b + 1, 0, sequence_lens[b]); s0 = mm->add_instruction(migraphx::make_op("reverse", {{"axes", {time_axis}}}), s0); if(sequence_lens[b] < time_size) { auto s1 = add_slice(b, b + 1, sequence_lens[b], time_size); s0 = mm->add_instruction(migraphx::make_op("concat", {{"axis", time_axis}}), s0, s1); } ret = mm->add_instruction(migraphx::make_op("concat", {{"axis", batch_axis}}), ret, s0); } mm->add_return({ret}); auto prog = read_onnx("reversesequence_batch_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reversesequence_rank_err_test.cpp000066400000000000000000000025001510465702400264420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reversesequence_rank_err_test) { EXPECT(test::throws([&] { read_onnx("reversesequence_rank_err_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reversesequence_same_axis_err_test.cpp000066400000000000000000000025121510465702400274630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reversesequence_same_axis_err_test) { EXPECT(test::throws([&] { read_onnx("reversesequence_same_axis_err_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reversesequence_sequence_lens_shape_err_test.cpp000066400000000000000000000025361510465702400315310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reversesequence_sequence_lens_shape_err_test) { EXPECT(test::throws([&] { read_onnx("reversesequence_sequence_lens_shape_err_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reversesequence_time_axis_err_test.cpp000066400000000000000000000025121510465702400274740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reversesequence_time_axis_err_test) { EXPECT(test::throws([&] { read_onnx("reversesequence_time_axis_err_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/reversesequence_time_test.cpp000066400000000000000000000060561510465702400256070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(reversesequence_time_test) { migraphx::program p; auto* mm = p.get_main_module(); int batch_axis = 1; int time_axis = 0; migraphx::shape sx{migraphx::shape::float_type, {4, 4}}; auto input = mm->add_parameter("x", sx); int batch_size = sx.lens()[batch_axis]; int time_size = sx.lens()[time_axis]; std::vector sequence_lens = {4, 3, 2, 1}; auto add_slice = [&mm, &input, batch_axis, time_axis](int b_start, int b_end, int t_start, int t_end) { return mm->add_instruction(migraphx::make_op("slice", {{"axes", {batch_axis, time_axis}}, {"starts", {b_start, t_start}}, {"ends", {b_end, t_end}}}), input); }; migraphx::instruction_ref ret; for(int b = 0; b < batch_size - 1; ++b) { auto s0 = add_slice(b, b + 1, 0, sequence_lens[b]); s0 = mm->add_instruction(migraphx::make_op("reverse", {{"axes", {time_axis}}}), s0); if(sequence_lens[b] < time_size) { auto s1 = add_slice(b, b + 1, sequence_lens[b], time_size); s0 = mm->add_instruction(migraphx::make_op("concat", {{"axis", time_axis}}), s0, s1); } if(b == 0) { ret = s0; } else { ret = mm->add_instruction(migraphx::make_op("concat", {{"axis", batch_axis}}), ret, s0); } } auto s0 = add_slice(batch_size - 1, batch_size, 0, time_size); ret = mm->add_instruction(migraphx::make_op("concat", {{"axis", batch_axis}}), ret, s0); mm->add_return({ret}); auto prog = read_onnx("reversesequence_time_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/roialign_default_test.cpp000066400000000000000000000040051510465702400246650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(roialign_default_test) { migraphx::shape sx{migraphx::shape::float_type, {10, 4, 7, 8}}; migraphx::shape srois{migraphx::shape::float_type, {8, 4}}; migraphx::shape sbi{migraphx::shape::int64_type, {8}}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", sx); auto rois = mm->add_parameter("rois", srois); auto bi = mm->add_parameter("batch_ind", sbi); // Due to the onnx model using opset 12, the coordinate_transformation_mode should be set to // output_half_pixel auto r = mm->add_instruction( migraphx::make_op("roialign", {{"coordinate_transformation_mode", "output_half_pixel"}}), x, rois, bi); mm->add_return({r}); auto prog = read_onnx("roialign_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/roialign_test.cpp000066400000000000000000000041341510465702400231640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(roialign_test) { migraphx::shape sx{migraphx::shape::float_type, {10, 5, 4, 7}}; migraphx::shape srois{migraphx::shape::float_type, {8, 4}}; migraphx::shape sbi{migraphx::shape::int64_type, {8}}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", sx); auto rois = mm->add_parameter("rois", srois); auto bi = mm->add_parameter("batch_ind", sbi); auto r = mm->add_instruction( migraphx::make_op("roialign", {{"coordinate_transformation_mode", "output_half_pixel"}, {"spatial_scale", 2.0f}, {"output_height", 5}, {"output_width", 5}, {"sampling_ratio", 3}}), x, rois, bi); mm->add_return({r}); auto prog = read_onnx("roialign_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/rotary_embedding_errors_test.cpp000066400000000000000000000052061510465702400262730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(rotary_embedding_packed_batching_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_packed_batching_test.onnx"); })); } TEST_CASE(rotary_embedding_scale_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_scale_test.onnx"); })); } TEST_CASE(rotary_embedding_num_heads_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_num_heads_test.onnx"); })); } TEST_CASE(rotary_embedding_input_dims_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_input_dims_test.onnx"); })); } TEST_CASE(rotary_embedding_pos_ids_1_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_pos_ids_1_test.onnx"); })); } TEST_CASE(rotary_embedding_pos_ids_2_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_pos_ids_2_test.onnx"); })); } TEST_CASE(rotary_embedding_pos_ids_3_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_pos_ids_3_test.onnx"); })); } TEST_CASE(rotary_embedding_dim_size_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_dim_size_test.onnx"); })); } TEST_CASE(rotary_embedding_cache_1_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_cache_1_test.onnx"); })); } TEST_CASE(rotary_embedding_cache_2_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_cache_2_test.onnx"); })); } TEST_CASE(rotary_embedding_wrong_n_inputs_test) { EXPECT(test::throws([&] { read_onnx("rotary_embedding_wrong_n_inputs_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/round_test.cpp000066400000000000000000000030031510465702400225010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(round_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::double_type, {10, 5}}); mm->add_instruction(migraphx::make_op("nearbyint"), input); auto prog = optimize_onnx("round_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scan_test.cpp000066400000000000000000000134421510465702400223060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(scan_test) { namespace mgx = migraphx; migraphx::program prog; auto* mm = prog.get_main_module(); auto init_state = mm->add_parameter("init_state", mgx::shape{mgx::shape::float_type, {2, 2}}); auto scan_ins1 = mm->add_parameter("scan_ins1", mgx::shape{mgx::shape::float_type, {2, 3, 2}}); auto scan_ins2 = mm->add_parameter("scan_ins2", mgx::shape{mgx::shape::float_type, {3, 1}}); auto* body = prog.create_module("Scan_3_scan"); auto iter = body->add_parameter("iter", mgx::shape{mgx::shape::int64_type}); auto cond = body->add_parameter("cond", mgx::shape{mgx::shape::bool_type}); auto sum_in = body->add_parameter("state_var0", mgx::shape{mgx::shape::float_type, {2, 2}}); auto scan_in2 = body->add_instruction( mgx::make_op("scan_slice", {{"axis", 0}, {"direction", 1}}), scan_ins2, iter); scan_in2 = body->add_instruction(mgx::make_op("squeeze", {{"axes", {0}}}), scan_in2); auto scan_in1 = body->add_instruction( mgx::make_op("scan_slice", {{"axis", 1}, {"direction", 0}}), scan_ins1, iter); scan_in1 = body->add_instruction(mgx::make_op("squeeze", {{"axes", {1}}}), scan_in1); auto add1 = mgx::add_common_op(*body, mgx::make_op("add"), {sum_in, scan_in1}); auto add2 = mgx::add_common_op(*body, mgx::make_op("add"), {add1, scan_in2}); auto id = body->add_instruction(mgx::make_op("identity"), add2); auto reduce_sum = body->add_instruction(mgx::make_op("reduce_sum", {{"axes", {0}}}), add2); reduce_sum = body->add_instruction(mgx::make_op("squeeze", {{"axes", {0}}}), reduce_sum); body->add_return({cond, add2, id, reduce_sum}); auto iter_lit = mm->add_literal(mgx::literal{mgx::shape{mgx::shape::int64_type}, {3}}); auto cond_lit = mm->add_literal(mgx::literal{mgx::shape{mgx::shape::bool_type}, {true}}); auto loop = mm->add_instruction( mgx::make_op("loop", {{"max_iterations", 3}, {"scan_output_directions", {1, 1}}}), {iter_lit, cond_lit, init_state}, {body}); auto final_state = mm->add_instruction(mgx::make_op("get_tuple_elem", {{"index", 0}}), loop); auto scan_outs1 = mm->add_instruction(mgx::make_op("get_tuple_elem", {{"index", 1}}), loop); scan_outs1 = mm->add_instruction(mgx::make_op("transpose", {{"permutation", {1, 2, 0}}}), scan_outs1); auto scan_outs2 = mm->add_instruction(mgx::make_op("get_tuple_elem", {{"index", 2}}), loop); scan_outs2 = mm->add_instruction(mgx::make_op("transpose", {{"permutation", {1, 0}}}), scan_outs2); mm->add_return({final_state, scan_outs1, scan_outs2}); auto prog_gold = read_onnx("scan_test6.onnx"); EXPECT(prog == prog_gold); } TEST_CASE(scan_invalid_input_axes_len_test) { EXPECT(test::throws( [] { read_onnx("scan_invalid_input_axes_len_test.onnx"); }, "scan_input_axes")); } TEST_CASE(scan_invalid_input_dirs_len_test) { EXPECT(test::throws( [] { read_onnx("scan_invalid_input_dirs_len_test.onnx"); }, "scan_input_directions")); } TEST_CASE(scan_invalid_output_axes_len_test) { EXPECT(test::throws( [] { read_onnx("scan_invalid_output_axes_len_test.onnx"); }, "scan_output_axes")); } TEST_CASE(scan_invalid_output_dirs_len_test) { EXPECT(test::throws( [] { read_onnx("scan_invalid_output_dirs_len_test.onnx"); }, "scan_output_directions")); } TEST_CASE(scan_invalid_input_axes_vals_test) { EXPECT(test::throws( [] { read_onnx("scan_invalid_input_axes_vals_test.onnx"); }, "scan_input_axes")); } TEST_CASE(scan_invalid_input_dirs_vals_test) { EXPECT(test::throws( [] { read_onnx("scan_invalid_input_dirs_vals_test.onnx"); }, "scan_input_directions")); } TEST_CASE(scan_invalid_output_axes_vals_test) { EXPECT(test::throws( [] { read_onnx("scan_invalid_output_axes_vals_test.onnx"); }, "scan_output_axes")); } TEST_CASE(scan_invalid_output_dirs_vals_test) { EXPECT(test::throws( [] { read_onnx("scan_invalid_output_dirs_vals_test.onnx"); }, "scan_output_directions")); } TEST_CASE(scan_arg_count_mismatch_test) { EXPECT(test::throws([] { read_onnx("scan_arg_count_mismatch_test.onnx"); })); } TEST_CASE(scan_arg_shapes_mismatch_test) { EXPECT(test::throws([] { read_onnx("scan_arg_shapes_mismatch_test.onnx"); })); } TEST_CASE(scan_input_axes_lens_mismatch_test) { EXPECT(test::throws([] { read_onnx("scan_input_axes_lens_mismatch_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scatter_add_test.cpp000066400000000000000000000024651510465702400236420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(scatter_add_test) { scatter_test_base("add", -2, "scatter_add_test.onnx"); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scatter_invalid_reduction_test.cpp000066400000000000000000000025561510465702400266150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(scatter_invalid_reduction_test) { EXPECT(test::throws([&] { optimize_onnx("scatter_elements_invalid_reduction_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scatter_max_test.cpp000066400000000000000000000024651510465702400236770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(scatter_max_test) { scatter_test_base("max", -2, "scatter_max_test.onnx"); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scatter_min_test.cpp000066400000000000000000000024651510465702400236750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(scatter_min_test) { scatter_test_base("min", -2, "scatter_min_test.onnx"); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scatter_mul_test.cpp000066400000000000000000000024651510465702400237070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(scatter_mul_test) { scatter_test_base("mul", -2, "scatter_mul_test.onnx"); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scatter_none_test.cpp000066400000000000000000000024701510465702400240450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(scatter_none_test) { scatter_test_base("none", -2, "scatter_none_test.onnx"); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scatternd_dyn_test.cpp000066400000000000000000000043051510465702400242210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(scatternd_dyn_test) { // dynamic input. migraphx::program p; auto* mm = p.get_main_module(); // parameters with dynamic dimensions auto l0 = mm->add_parameter( "data", migraphx::shape{migraphx::shape::float_type, {{1, 3, {2}}, {2, 2}, {2, 2}}}); auto l1 = mm->add_parameter( "indices", migraphx::shape{migraphx::shape::int64_type, {{2, 1, {2}}, {1, 1}, {2, 2}}}); auto l2 = mm->add_parameter( "updates", migraphx::shape{migraphx::shape::float_type, {{2, 1, {2}}, {1, 1}, {2, 2}}}); auto r = mm->add_instruction(migraphx::make_op("scatternd_none"), l0, l1, l2); mm->add_return({r}); migraphx::onnx_options options; options.map_dyn_input_dims["data"] = {{1, 3, {2}}, {2, 2}, {2, 2}}; options.map_dyn_input_dims["indices"] = {{2, 1, {2}}, {1, 1}, {2, 2}}; options.map_dyn_input_dims["updates"] = {{2, 1, {2}}, {1, 1}, {2, 2}}; auto prog = read_onnx("scatternd_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/scatternd_invalid_reduction_test.cpp000066400000000000000000000025061510465702400271320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(scatternd_invalid_reduction_test) { EXPECT(test::throws([&] { read_onnx("scatternd_invalid_reduction_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/selu_test.cpp000066400000000000000000000050421510465702400223270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(selu_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector lens = {2, 3}; migraphx::shape s{migraphx::shape::double_type, lens}; auto x = mm->add_parameter("x", s); migraphx::shape ls{migraphx::shape::double_type, {1}}; auto la = mm->add_literal({ls, {0.3}}); auto lg = mm->add_literal({ls, {0.25}}); auto mbla = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), la); auto mblg = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), lg); auto sign_x = mm->add_instruction(migraphx::make_op("sign"), x); auto exp_x = mm->add_instruction(migraphx::make_op("exp"), x); auto mlax = mm->add_instruction(migraphx::make_op("mul"), mbla, exp_x); auto smlax = mm->add_instruction(migraphx::make_op("sub"), mlax, mbla); auto item1 = mm->add_instruction(migraphx::make_op("add"), smlax, x); auto item2 = mm->add_instruction(migraphx::make_op("sub"), smlax, x); auto sitem2 = mm->add_instruction(migraphx::make_op("mul"), sign_x, item2); auto item12 = mm->add_instruction(migraphx::make_op("sub"), item1, sitem2); auto r = mm->add_instruction(migraphx::make_op("mul"), item12, mblg); mm->add_return({r}); auto prog = read_onnx("selu_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_dyn_test0.cpp000066400000000000000000000034651510465702400234200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_dyn_test0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}}; auto p0 = mm->add_parameter("x", s); migraphx::shape s_shape{migraphx::shape::int64_type, {4}}; auto ret = mm->add_instruction(migraphx::make_op("dimensions_of", {{"end", 4}}), p0); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}; auto prog = read_onnx("shape_dyn_test0.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_dyn_test1.cpp000066400000000000000000000035131510465702400234130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_dyn_test1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}}; auto p0 = mm->add_parameter("x", s); migraphx::shape s_shape{migraphx::shape::int64_type, {4}}; auto ret = mm->add_instruction(migraphx::make_op("dimensions_of", {{"start", 2}, {"end", 4}}), p0); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}; auto prog = read_onnx("shape_dyn_test1.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_dyn_test2.cpp000066400000000000000000000035131510465702400234140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_dyn_test2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}}; auto p0 = mm->add_parameter("x", s); migraphx::shape s_shape{migraphx::shape::int64_type, {4}}; auto ret = mm->add_instruction(migraphx::make_op("dimensions_of", {{"start", 2}, {"end", 4}}), p0); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}; auto prog = read_onnx("shape_dyn_test2.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_dyn_test3.cpp000066400000000000000000000035131510465702400234150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_dyn_test3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}}; auto p0 = mm->add_parameter("x", s); migraphx::shape s_shape{migraphx::shape::int64_type, {4}}; auto ret = mm->add_instruction(migraphx::make_op("dimensions_of", {{"start", 1}, {"end", 2}}), p0); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}; auto prog = read_onnx("shape_dyn_test3.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_end_less_start_error.cpp000066400000000000000000000026671510465702400257340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_end_less_start_error) { migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}; EXPECT(test::throws([&] { read_onnx("shape_end_less_start_error.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_end_oob_test.cpp000066400000000000000000000034731510465702400241520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_end_oob_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}}; auto p0 = mm->add_parameter("x", s); migraphx::shape s_shape{migraphx::shape::int64_type, {4}}; auto ret = mm->add_instruction(migraphx::make_op("dimensions_of", {{"end", 4}}), p0); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}; auto prog = read_onnx("shape_end_oob_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_gather_test.cpp000066400000000000000000000034521510465702400240140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_gather_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {7, 3, 10}}); migraphx::shape const_shape{migraphx::shape::int32_type, {1}}; auto l2 = mm->add_literal(migraphx::literal{const_shape, {1}}); auto l1 = mm->add_literal(migraphx::shape{migraphx::shape::int64_type, {3}}, l0->get_shape().lens()); int axis = 0; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), l1, l2); auto prog = optimize_onnx("shape_gather_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_start_oob_test.cpp000066400000000000000000000034771510465702400245450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_start_oob_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}}; auto p0 = mm->add_parameter("x", s); migraphx::shape s_shape{migraphx::shape::int64_type, {4}}; auto ret = mm->add_instruction(migraphx::make_op("dimensions_of", {{"end", 4}}), p0); mm->add_return({ret}); migraphx::onnx_options options; options.map_dyn_input_dims["x"] = {{1, 4, {1, 4}}, {4, 4}, {2, 4}, {2, 4}}; auto prog = read_onnx("shape_start_oob_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shape_test.cpp000066400000000000000000000031001510465702400224500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shape_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 4, 5, 6}}; auto l0 = mm->add_parameter("x", s); migraphx::shape s_shape{migraphx::shape::int64_type, {4}}; mm->add_literal(s_shape, l0->get_shape().lens()); auto prog = optimize_onnx("shape_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shrink_fp8_test.cpp000066400000000000000000000052721510465702400234370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shrink_fp8_test) { migraphx::program p; float bias = 1.5; float lambd = 1.5; std::vector lens{3, 3}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, lens}); auto lit_bias = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {bias}}); auto lit_neg_lambd = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {-lambd}}); auto lit_lambd = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {lambd}}); auto x_plus_bias = add_common_op(*mm, migraphx::make_op("add"), {x, lit_bias}); auto x_min_bias = add_common_op(*mm, migraphx::make_op("sub"), {x, lit_bias}); auto cond1 = add_common_op(*mm, migraphx::make_op("less"), {x, lit_neg_lambd}); auto cond2_a = add_common_op(*mm, migraphx::make_op("not"), {cond1}); auto cond2_b = add_common_op(*mm, migraphx::make_op("greater"), {x, lit_lambd}); auto cond2 = add_common_op(*mm, migraphx::make_op("logical_and"), {cond2_a, cond2_b}); auto first = add_common_op(*mm, migraphx::make_op("mul"), {cond1, x_plus_bias}); auto second = add_common_op(*mm, migraphx::make_op("mul"), {cond2, x_min_bias}); auto ret = add_common_op(*mm, migraphx::make_op("add"), {first, second}); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::fp8e4m3fnuz_type}}), ret); auto prog = optimize_onnx("shrink_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shrink_hard_test.cpp000066400000000000000000000050751510465702400236610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shrink_hard_test) { migraphx::program p; float bias = 0.0; float lambd = 1.5; std::vector lens{5}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, lens}); auto lit_bias = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {bias}}); auto lit_neg_lambd = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {-lambd}}); auto lit_lambd = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {lambd}}); auto x_plus_bias = add_common_op(*mm, migraphx::make_op("add"), {x, lit_bias}); auto x_min_bias = add_common_op(*mm, migraphx::make_op("sub"), {x, lit_bias}); auto cond1 = add_common_op(*mm, migraphx::make_op("less"), {x, lit_neg_lambd}); auto cond2_a = add_common_op(*mm, migraphx::make_op("not"), {cond1}); auto cond2_b = add_common_op(*mm, migraphx::make_op("greater"), {x, lit_lambd}); auto cond2 = add_common_op(*mm, migraphx::make_op("logical_and"), {cond2_a, cond2_b}); auto first = add_common_op(*mm, migraphx::make_op("mul"), {cond1, x_plus_bias}); auto second = add_common_op(*mm, migraphx::make_op("mul"), {cond2, x_min_bias}); add_common_op(*mm, migraphx::make_op("add"), {first, second}); auto prog = optimize_onnx("shrink_hard_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/shrink_int8_test.cpp000066400000000000000000000053211510465702400236170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(shrink_int8_test) { migraphx::program p; float bias = 1.5; float lambd = 1.5; std::vector lens{3, 3}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::int8_type, lens}); auto lit_bias = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {bias}}); auto lit_neg_lambd = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {-lambd}}); auto lit_lambd = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {lambd}}); auto x_plus_bias = add_common_op(*mm, migraphx::make_op("add"), {x, lit_bias}); auto x_min_bias = add_common_op(*mm, migraphx::make_op("sub"), {x, lit_bias}); auto cond1 = add_common_op(*mm, migraphx::make_op("less"), {x, lit_neg_lambd}); auto cond2_a = add_common_op(*mm, migraphx::make_op("not"), {cond1}); auto cond2_b = add_common_op(*mm, migraphx::make_op("greater"), {x, lit_lambd}); auto cond2 = add_common_op(*mm, migraphx::make_op("logical_and"), {cond2_a, cond2_b}); auto first = add_common_op(*mm, migraphx::make_op("mul"), {cond1, x_plus_bias}); auto second = add_common_op(*mm, migraphx::make_op("mul"), {cond2, x_min_bias}); auto ret = add_common_op(*mm, migraphx::make_op("add"), {first, second}); mm->add_instruction(migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), ret); auto prog = optimize_onnx("shrink_int8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sign_test.cpp000066400000000000000000000027741510465702400223300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sign_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::double_type, {10, 5}}); mm->add_instruction(migraphx::make_op("sign"), input); auto prog = optimize_onnx("sign_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/simplified_layer_normalization_4d_test.cpp000066400000000000000000000030001510465702400302250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(simplified_layer_normalization_4d_test) { migraphx::program p = make_simplified_layer_norm( {2, 3, 5, 7}, {}, {2, 1, 5, 7}, -1, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("simplified_layer_normalization_4d_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/simplified_layer_normalization_invalid_input_test.cpp000066400000000000000000000025741510465702400326020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(simplified_layer_normalization_invalid_input_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("simplified_layer_normalization_invalid_input_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/simplified_layer_normalization_invalid_n_args_test.cpp000066400000000000000000000025761510465702400327160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(simplified_layer_normalization_invalid_n_args_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("simplified_layer_normalization_invalid_n_args_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/simplified_layer_normalization_test.cpp000066400000000000000000000027551510465702400276560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(simplified_layer_normalization_test) { migraphx::program p = make_simplified_layer_norm({2, 2, 4}, {}, {4}, -1, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("simplified_layer_normalization_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sin_fp8_test.cpp000066400000000000000000000030031510465702400227200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sin_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {10}}); mm->add_instruction(migraphx::make_op("sin"), input); auto prog = optimize_onnx("sin_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sin_test.cpp000066400000000000000000000027651510465702400221610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sin_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("sin"), input); auto prog = optimize_onnx("sin_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sinh_dynamic_test.cpp000066400000000000000000000034441510465702400240300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sinh_dynamic_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{1, 10}; std::vector dyn_dims; dyn_dims.push_back(dd); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dyn_dims}); auto ret = mm->add_instruction(migraphx::make_op("sinh"), input); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = dd; auto prog = read_onnx("sinh_dynamic_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sinh_test.cpp000066400000000000000000000027711510465702400223260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sinh_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("sinh"), input); auto prog = optimize_onnx("sinh_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/size_bf16_test.cpp000066400000000000000000000030371510465702400231510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(size_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::bf16_type, {3, 1}}; mm->add_parameter("x", s); mm->add_literal(migraphx::literal{migraphx::shape::int64_type, {s.elements()}}); auto prog = optimize_onnx("size_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/size_float_test.cpp000066400000000000000000000030461510465702400235200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(size_float_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {2, 3, 4}}; mm->add_parameter("x", s); mm->add_literal(migraphx::literal{migraphx::shape::int64_type, {s.elements()}}); auto prog = optimize_onnx("size_float_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/size_fp8_test.cpp000066400000000000000000000030501510465702400231030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(size_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {2, 5, 3}}; mm->add_parameter("x", s); mm->add_literal(migraphx::literal{migraphx::shape::int64_type, {s.elements()}}); auto prog = optimize_onnx("size_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/size_half_test.cpp000066400000000000000000000030371510465702400233250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(size_half_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::half_type, {3, 1}}; mm->add_parameter("x", s); mm->add_literal(migraphx::literal{migraphx::shape::int64_type, {s.elements()}}); auto prog = optimize_onnx("size_half_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/size_int_test.cpp000066400000000000000000000030411510465702400232000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(size_int_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::int32_type, {8, 2, 3}}; mm->add_parameter("x", s); mm->add_literal(migraphx::literal{migraphx::shape::int64_type, {s.elements()}}); auto prog = optimize_onnx("size_int_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_2d_skip_test.cpp000066400000000000000000000027661510465702400301140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(skip_layer_normalization_2d_skip_test) { migraphx::program p = make_skip_layer_norm({2, 2, 4}, {2, 4}, {4}, {}, {}, 2, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("skip_layer_normalization_2d_skip_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_beta_bias_test.cpp000066400000000000000000000030001510465702400304500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(skip_layer_normalization_beta_bias_test) { migraphx::program p = make_skip_layer_norm( {2, 2, 4}, {2, 2, 4}, {4}, {4}, {4}, 2, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("skip_layer_normalization_beta_bias_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_beta_test.cpp000066400000000000000000000027651510465702400274730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(skip_layer_normalization_beta_test) { migraphx::program p = make_skip_layer_norm( {2, 2, 4}, {2, 2, 4}, {4}, {4}, {}, 2, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("skip_layer_normalization_beta_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_invalid_beta_test.cpp000066400000000000000000000025561510465702400311770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(skip_layer_normalization_invalid_beta_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("skip_layer_normalization_invalid_beta_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_invalid_bias_test.cpp000066400000000000000000000025561510465702400312020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(skip_layer_normalization_invalid_bias_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("skip_layer_normalization_invalid_bias_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_invalid_input_test.cpp000066400000000000000000000025601510465702400314160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(skip_layer_normalization_invalid_input_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("skip_layer_normalization_invalid_input_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_invalid_n_args_test.cpp000066400000000000000000000025621510465702400315320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(skip_layer_normalization_invalid_n_args_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("skip_layer_normalization_invalid_n_args_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_skip_batch_size_1_test.cpp000066400000000000000000000030161510465702400321270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(skip_layer_normalization_skip_batch_size_1_test) { migraphx::program p = make_skip_layer_norm( {2, 2, 4}, {1, 2, 4}, {4}, {}, {}, 2, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("skip_layer_normalization_skip_batch_size_1_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_layer_normalization_test.cpp000066400000000000000000000027521510465702400264740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(skip_layer_normalization_test) { migraphx::program p = make_skip_layer_norm( {2, 2, 4}, {2, 2, 4}, {4}, {}, {}, 2, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("skip_layer_normalization_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_simplified_layer_normalization_invalid_input_test.cpp000066400000000000000000000026111510465702400336200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(skip_simplified_layer_normalization_invalid_input_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("skip_simplified_layer_normalization_invalid_input_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_simplified_layer_normalization_invalid_n_args_test.cpp000066400000000000000000000026131510465702400337340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(skip_simplified_layer_normalization_invalid_n_args_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("skip_simplified_layer_normalization_invalid_n_args_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/skip_simplified_layer_normalization_test.cpp000066400000000000000000000027771510465702400307100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(skip_simplified_layer_normalization_test) { migraphx::program p = make_simplified_layer_norm( {2, 2, 4}, {2, 2, 4}, {4}, -1, 1e-5f, migraphx::shape::half_type); auto prog = optimize_onnx("skip_simplified_layer_normalization_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_3arg_test.cpp000066400000000000000000000033541510465702400233760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_3arg_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {5, 5}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {0, 0}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {2, 5}}); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 0}}, {"ends", {2, 5}}}), l0); mm->add_return({ret}); auto prog = read_onnx("slice_3arg_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_5arg_reverse_test.cpp000066400000000000000000000040361510465702400251310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_5arg_reverse_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {5, 5}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-1, 1}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-1, -2}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-5, -1}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-1, -3}}); auto slice_out = mm->add_instruction( migraphx::make_op("slice", {{"axes", {-1, -2}}, {"starts", {-4, -3}}, {"ends", {2147483647, -1}}}), l0); auto ret = mm->add_instruction(migraphx::make_op("reverse", {{"axes", {-1}}}), slice_out); mm->add_return({ret}); auto prog = read_onnx("slice_5arg_reverse_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_5arg_step_test.cpp000066400000000000000000000042601510465702400244300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_5arg_step_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {5, 5}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-2, 2}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-1, -2}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-5, -1}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-1, -3}}); auto slice_out = mm->add_instruction( migraphx::make_op("slice", {{"axes", {-1, -2}}, {"starts", {-4, -3}}, {"ends", {2147483647, -1}}}), l0); auto reverse_out = mm->add_instruction(migraphx::make_op("reverse", {{"axes", {-1}}}), slice_out); auto step_out = mm->add_instruction( migraphx::make_op("step", {{"axes", {-1, -2}}, {"steps", {2, 2}}}), reverse_out); mm->add_return({step_out}); auto prog = read_onnx("slice_5arg_step_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_5arg_test.cpp000066400000000000000000000036061510465702400234000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_5arg_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {5, 5}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {1, 1}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-1, -2}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-1, -1}}); mm->add_literal({{migraphx::shape::int32_type, {2}}, {-5, -3}}); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {-1, -2}}, {"starts", {-5, -3}}, {"ends", {-1, -1}}}), l0); mm->add_return({ret}); auto prog = read_onnx("slice_5arg_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_constant_test.cpp000066400000000000000000000031641510465702400243720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_constant_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {3, 2}}, {0, 1, 2, 3, 4, 5}}); mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {1, 0}}, {"ends", {2, 2}}}), l0); auto prog = optimize_onnx("slice_constant_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_dyn_test.cpp000066400000000000000000000036461510465702400233400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{3, 3}, {1, 3}, {2, 2}}}); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), l0); mm->add_return({ret}); migraphx::onnx_options options; // Parser converts the dynamic input shape to static unless there is at least one non-fixed // dynamic dimension. Slicing is not allowed along the non-fixed axis 1. options.map_dyn_input_dims["0"] = {{3, 3}, {1, 3}, {2, 2}}; auto prog = read_onnx("slice_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_max_end_test.cpp000066400000000000000000000031671510465702400241570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_max_end_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {10, 20}}); mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {1, 2}}, {"ends", {3000000000, -1}}}), l0); auto prog = optimize_onnx("slice_max_end_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_reverse_dyn_test.cpp000066400000000000000000000031011510465702400250550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_reverse_dyn_test) { // A slice command with negative step on any axis will have a "Reverse" instruction added in // parsing. At the time of writing, Reverse doesn't support dynamic shape input. migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT(test::throws([&] { read_onnx("slice_reverse_dyn_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_step_dyn_test.cpp000066400000000000000000000030551510465702400243650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_step_dyn_test) { // A slice command with non-default steps will have a "Step" instruction added in parsing. // At the time of writing, Step doesn't support dynamic shape input. migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT(test::throws([&] { read_onnx("slice_step_dyn_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_test.cpp000066400000000000000000000030711510465702400224560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3, 2}}); mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {1, 0}}, {"ends", {2, 2}}}), l0); auto prog = optimize_onnx("slice_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_var_input_default_steps.cpp000066400000000000000000000040741510465702400264340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_var_input_default_steps) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {{3, 8}, {2, 2}}}); auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int64_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int64_type, {2}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int64_type, {2}}); mm->add_literal({{migraphx::shape::int64_type, {2}}, {1, 1}}); auto ret = mm->add_instruction(migraphx::make_op("slice"), data, starts, ends, axes); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {3, 8}; auto prog = read_onnx("slice_var_input_default_steps.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_var_input_dyn0.cpp000066400000000000000000000036351510465702400244460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_var_input_dyn0) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {{3, 8}, {2, 2}}}); auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int32_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int32_type, {2}}); auto ret = mm->add_instruction(migraphx::make_op("slice", {{"axes", {0, 1}}}), data, starts, ends); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {3, 8}; auto prog = read_onnx("slice_var_input_dyn0.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_var_input_dyn1.cpp000066400000000000000000000037521510465702400244470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_var_input_dyn1) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {{3, 8}, {2, 2}}}); auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int32_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int32_type, {2}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int32_type, {2}}); auto ret = mm->add_instruction(migraphx::make_op("slice"), data, starts, ends, axes); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {3, 8}; auto prog = read_onnx("slice_var_input_dyn1.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_var_input_static0.cpp000066400000000000000000000033771510465702400251460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_var_input_static0) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {3, 2}}); auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int32_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int32_type, {2}}); mm->add_instruction(migraphx::make_op("slice", {{"axes", {0, 1}}}), data, starts, ends); auto prog = optimize_onnx("slice_var_input_static0.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_var_input_static1.cpp000066400000000000000000000035211510465702400251360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_var_input_static1) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {3, 2}}); auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int64_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int64_type, {2}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int64_type, {2}}); mm->add_instruction(migraphx::make_op("slice"), data, starts, ends, axes); auto prog = optimize_onnx("slice_var_input_static1.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/slice_var_input_steps_error.cpp000066400000000000000000000024741510465702400261430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(slice_var_input_steps_error) { EXPECT(test::throws([&] { read_onnx("slice_var_input_steps_error.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softmax_dyn_test.cpp000066400000000000000000000033071510465702400237140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softmax_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}, {4, 4}}}); auto ret = mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), l0); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("softmax_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softmax_nonstd_input_test.cpp000066400000000000000000000033161510465702400256460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softmax_nonstd_input_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {6, 8}}); auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {1, 0}}, {"ends", {4, 4}}}), l0); auto l2 = mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), l1); mm->add_return({l2}); auto prog = read_onnx("softmax_nonstd_input_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softmax_test.cpp000066400000000000000000000030131510465702400230340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softmax_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3}}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), l0); auto prog = optimize_onnx("softmax_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softmaxcrossentropy_err_test.cpp000066400000000000000000000060271510465702400264070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softmaxcrossentropyloss_label_wrong_type_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("softmaxcrossentropyloss_label_wrong_type_test.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_score_dim_err_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("softmaxcrossentropyloss_score_dim_err_test.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_score_label_mismatch_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("softmaxcrossentropyloss_score_label_mismatch_test.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_score_label_wrong_k_dims_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("softmaxcrossentropyloss_score_label_wrong_k_dims_test.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_scores_wrong_type_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("softmaxcrossentropyloss_scores_wrong_type.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_weight_score_mismatch_valid_type_test) { EXPECT(test::throws([&] { migraphx::parse_onnx("softmaxcrossentropyloss_weight_score_mismatch_valid_type_test.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_weight_wrong_dims_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("softmaxcrossentropyloss_weight_wrong_dims_test.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_weight_wrong_type_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("softmaxcrossentropyloss_weight_wrong_type_test.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_invalid_reduction_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("softmaxcrossentropyloss_invalid_reduction_test.onnx"); })); } TEST_CASE(softmaxcrossentropyloss_invalid_k_dimensions_test) { EXPECT(test::throws( [&] { migraphx::parse_onnx("softmaxcrossentropyloss_kdim_not_equal_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softmaxcrossentropyloss_2d_mean_reduction_test.cpp000066400000000000000000000236761510465702400321120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::float_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_double_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::double_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::double_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_double_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_half_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::half_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::half_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_half_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::bf16_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::bf16_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softmaxcrossentropyloss_2d_no_reduction_test.cpp000066400000000000000000000307051510465702400315750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::float_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_negative_ignore_idx_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {4}}); auto ignore_index = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int64_type, {1}, {0}), {-2})); auto wght_lt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::float_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), wght_lt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); auto ignore_idx_bc = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), ignore_index); auto conv_labels = mm->add_instruction( migraphx::make_op("convert", {{"target_type", ignore_index->get_shape().type()}}), labels); std::vector zero_val_vect(labels->get_shape().elements(), 0); auto zero_vector = mm->add_literal(migraphx::literal(loss->get_shape(), zero_val_vect)); auto equals_mask = mm->add_instruction(migraphx::make_op("equal"), conv_labels, ignore_idx_bc); mm->add_instruction(migraphx::make_op("where"), equals_mask, zero_vector, loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_idx_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_double_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::double_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::double_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_double_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_half_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::half_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::half_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_half_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::bf16_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::bf16_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softmaxcrossentropyloss_2d_sum_reduction_test.cpp000066400000000000000000000236501510465702400317660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::float_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_double_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::double_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::double_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_double_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_half_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::half_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::half_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_half_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); auto scores = mm->add_parameter("0", migraphx::shape{migraphx::shape::bf16_type, {4, 4}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {4}}); auto weights = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::bf16_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {4}, {1}), {0, 1, 2, 3})); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", labels->get_shape().lens()}}), weights); mb_weights = mm->add_instruction(migraphx::make_op("neg"), mb_weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_bf16_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softmaxcrossentropyloss_kd_all_reduction_weighted_test.cpp000066400000000000000000000412371510465702400337040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softmaxcrossentropyloss_kd_sum_reduction_weighted_double_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::double_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {class_size, 2, 2}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::double_type, {class_size}}); auto weights_dflt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::double_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int32_type, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), softmax); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), weighted_loss); auto prog = optimize_onnx("softmaxcrossentropyloss_kd_sum_reduction_double_weighted_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_kd_no_reduction_weighted_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {class_size, 2, 2}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {class_size}}); auto weights_dflt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::float_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int32_type, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), softmax); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); auto prog = optimize_onnx("softmaxcrossentropyloss_kd_no_reduction_weighted_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_kd_mean_reduction_half_weighted_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::half_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {class_size, 2, 2}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::half_type, {class_size}}); auto weights_dflt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::half_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int32_type, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), softmax); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); auto loss_x = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), weighted_loss); auto loss_w = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), gathernd2); loss_w = mm->add_instruction(migraphx::make_op("neg"), loss_w); mm->add_instruction(migraphx::make_op("div"), loss_x, loss_w); auto prog = optimize_onnx("softmaxcrossentropyloss_kd_mean_reduction_half_weighted_test.onnx"); EXPECT(p == prog); } TEST_CASE(softmaxcrossentropyloss_kd_mean_reduction_bf16_weighted_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::bf16_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_parameter("1", migraphx::shape{migraphx::shape::int32_type, {class_size, 2, 2}}); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::bf16_type, {class_size}}); auto weights_dflt = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::bf16_type, {1}, {0}), {1})); auto labels_idx = mm->add_literal(migraphx::literal( migraphx::shape(migraphx::shape::int32_type, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal( migraphx::literal(migraphx::shape(migraphx::shape::int32_type, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); weights = mm->add_instruction(migraphx::make_op("neg"), weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", unsq_labels->get_shape().lens()}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), softmax); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", scores->get_shape().lens()}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), logsoftmax, gathernd2); auto loss_x = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), weighted_loss); auto loss_w = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), gathernd2); loss_w = mm->add_instruction(migraphx::make_op("neg"), loss_w); mm->add_instruction(migraphx::make_op("div"), loss_x, loss_w); auto prog = optimize_onnx("softmaxcrossentropyloss_kd_mean_reduction_bf16_weighted_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softplus_nd_test.cpp000066400000000000000000000036701510465702400237240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softplus_nd_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{3, 4, 5}; auto input_type = migraphx::shape::half_type; auto x = mm->add_parameter("x", migraphx::shape{input_type, input_lens}); auto mb_ones = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto exp = mm->add_instruction(migraphx::make_op("exp"), x); auto add = mm->add_instruction(migraphx::make_op("add"), exp, mb_ones); mm->add_instruction(migraphx::make_op("log"), add); auto prog = optimize_onnx("softplus_nd_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softplus_test.cpp000066400000000000000000000036551510465702400232460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softplus_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{5}; auto input_type = migraphx::shape::float_type; auto x = mm->add_parameter("x", migraphx::shape{input_type, input_lens}); auto mb_ones = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto exp = mm->add_instruction(migraphx::make_op("exp"), x); auto add = mm->add_instruction(migraphx::make_op("add"), exp, mb_ones); mm->add_instruction(migraphx::make_op("log"), add); auto prog = optimize_onnx("softplus_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softsign_nd_test.cpp000066400000000000000000000036731510465702400237040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softsign_nd_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{3, 4, 5}; auto input_type = migraphx::shape::half_type; auto x = mm->add_parameter("x", migraphx::shape{input_type, input_lens}); auto mb_ones = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto abs = mm->add_instruction(migraphx::make_op("abs"), x); auto add = mm->add_instruction(migraphx::make_op("add"), abs, mb_ones); mm->add_instruction(migraphx::make_op("div"), x, add); auto prog = optimize_onnx("softsign_nd_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/softsign_test.cpp000066400000000000000000000036601510465702400232170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(softsign_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{5}; auto input_type = migraphx::shape::float_type; auto x = mm->add_parameter("x", migraphx::shape{input_type, input_lens}); auto mb_ones = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); auto abs = mm->add_instruction(migraphx::make_op("abs"), x); auto add = mm->add_instruction(migraphx::make_op("add"), abs, mb_ones); mm->add_instruction(migraphx::make_op("div"), x, add); auto prog = optimize_onnx("softsign_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/spacetodepth_invalid_blocksize_test.cpp000066400000000000000000000025071510465702400276200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(spacetodepth_invalid_blocksize) { EXPECT(test::throws([&] { read_onnx("spacetodepth_invalid_blocksize_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/spacetodepth_nondivisibility_test.cpp000066400000000000000000000025101510465702400273360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(spacetodepth_nondivisibility_test) { EXPECT(test::throws([&] { read_onnx("spacetodepth_nondivisibility_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/spacetodepth_simple_test.cpp000066400000000000000000000034201510465702400254110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(spacetodepth_simple_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {1, 2, 4, 6}}); auto tmp1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 2, 2, 3, 2}}}), l0); auto tmp2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 5, 1, 2, 4}}}), tmp1); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 8, 2, 3}}}), tmp2); auto prog = optimize_onnx("spacetodepth_simple_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/spacetodepth_test.cpp000066400000000000000000000034041510465702400240420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(spacetodepth_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("x", {migraphx::shape::float_type, {2, 2, 10, 10}}); auto tmp1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 5, 2, 5, 2}}}), l0); auto tmp2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 5, 1, 2, 4}}}), tmp1); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 8, 5, 5}}}), tmp2); auto prog = optimize_onnx("spacetodepth_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_dyn_input.cpp000066400000000000000000000114251510465702400235460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_dyn_input_fixed_split_axis_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {{10, 30}, {15, 15}}}); auto r1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), input); auto r2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {5}}, {"ends", {10}}}), input); auto r3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {10}}, {"ends", {15}}}), input); mm->add_return({r1, r2, r3}); migraphx::onnx_options options; options.default_dyn_dim_value = {10, 30}; auto prog = read_onnx("split_dyn_input_fixed_split_axis_test.onnx", options); EXPECT(p == prog); } TEST_CASE(split_dyn_input_dyn_split_axis_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {{10, 30}, {15, 15}}}); auto split_dim = mm->add_instruction(migraphx::make_op("dimensions_of", {{"start", 0}, {"end", 1}}), input); migraphx::shape int64_scalar_shape{migraphx::shape::int64_type, {1}, {0}}; auto num_outputs_lit = mm->add_literal(migraphx::literal{int64_scalar_shape, {3}}); auto num_outputs_minus_1_lit = mm->add_literal(migraphx::literal{int64_scalar_shape, {2}}); auto chunk_size = mm->add_instruction( migraphx::make_op("div"), mm->add_instruction(migraphx::make_op("add"), split_dim, num_outputs_minus_1_lit), num_outputs_lit); auto r1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}}), input, mm->add_instruction(migraphx::make_op("mul"), chunk_size, mm->add_literal(migraphx::literal{int64_scalar_shape, {0}})), mm->add_instruction(migraphx::make_op("mul"), chunk_size, mm->add_literal(migraphx::literal{int64_scalar_shape, {1}}))); auto r2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}}), input, mm->add_instruction(migraphx::make_op("mul"), chunk_size, mm->add_literal(migraphx::literal{int64_scalar_shape, {1}})), mm->add_instruction(migraphx::make_op("mul"), chunk_size, mm->add_literal(migraphx::literal{int64_scalar_shape, {2}}))); auto r3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"ends", {std::numeric_limits::max()}}}), input, mm->add_instruction(migraphx::make_op("mul"), chunk_size, mm->add_literal(migraphx::literal{int64_scalar_shape, {2}}))); mm->add_return({r1, r2, r3}); migraphx::onnx_options options; options.default_dyn_dim_value = {10, 30}; auto prog = read_onnx("split_dyn_input_dyn_split_axis_test.onnx", options); EXPECT(p == prog); } TEST_CASE(split_dyn_input_split_attr_error) { migraphx::onnx_options options; options.default_dyn_dim_value = {10, 30}; EXPECT(test::throws([&] { read_onnx("split_dyn_input_split_attr_test.onnx", options); })); } TEST_CASE(split_dyn_input_split_input_error) { migraphx::onnx_options options; options.default_dyn_dim_value = {10, 30}; EXPECT(test::throws([&] { read_onnx("split_dyn_input_split_input_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_minus_axis_test.cpp000066400000000000000000000035761510465702400247630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_minus_axis_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10, 15}}); auto r1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), input); auto r2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {5}}, {"ends", {10}}}), input); auto r3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {10}}, {"ends", {15}}}), input); mm->add_return({r1, r2, r3}); auto prog = read_onnx("split_minus_axis_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test.cpp000066400000000000000000000035471510465702400225220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10, 15}}); auto r1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {7}}}), input); auto r2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {7}}, {"ends", {11}}}), input); auto r3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {11}}, {"ends", {15}}}), input); mm->add_return({r1, r2, r3}); auto prog = read_onnx("split_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test_default.cpp000066400000000000000000000033611510465702400242200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test_default) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10, 15}}); auto r1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {5}}}), input); auto r2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {5}}, {"ends", {10}}}), input); mm->add_return({r1, r2}); auto prog = read_onnx("split_test_default.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test_invalid_num_outputs.cpp000066400000000000000000000025021510465702400267000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test_invalid_num_outputs) { EXPECT(test::throws([&] { read_onnx("split_test_invalid_num_outputs.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test_invalid_split.cpp000066400000000000000000000024661510465702400254420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test_invalid_split) { EXPECT(test::throws([&] { read_onnx("split_test_invalid_split.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test_no_attribute.cpp000066400000000000000000000042521510465702400252730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test_no_attribute) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape si{migraphx::shape::int64_type, {4}, {1}}; std::vector ind = {75, 75, 75, 75}; auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {300, 15}}); mm->add_literal(migraphx::literal(si, ind)); auto r1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {75}}}), input); auto r2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {75}}, {"ends", {150}}}), input); auto r3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {150}}, {"ends", {225}}}), input); auto r4 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {225}}, {"ends", {300}}}), input); mm->add_return({r1, r2, r3, r4}); auto prog = read_onnx("split_test_no_attribute.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test_no_attribute_invalid_input_split.cpp000066400000000000000000000025341510465702400314340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test_no_attribute_invalid_input_split) { EXPECT(test::throws([&] { read_onnx("split_test_no_attribute_invalid_input_split.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test_no_attribute_invalid_split.cpp000066400000000000000000000025201510465702400302100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test_no_attribute_invalid_split) { EXPECT(test::throws([&] { read_onnx("split_test_no_attribute_invalid_split.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test_uneven.cpp000066400000000000000000000041751510465702400241000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test_uneven) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {12, 15}}); auto r1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {3}}}), input); auto r2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {3}}, {"ends", {6}}}), input); auto r3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {6}}, {"ends", {9}}}), input); auto r4 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {9}}, {"ends", {12}}}), input); auto r5 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {12}}, {"ends", {12}}}), input); mm->add_return({r1, r2, r3, r4, r5}); auto prog = read_onnx("split_test_uneven.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/split_test_uneven_num_outputs.cpp000066400000000000000000000040171510465702400265550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(split_test_uneven_num_outputs) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {11, 15}}); auto r1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {3}}}), input); auto r2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {3}}, {"ends", {6}}}), input); auto r3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {6}}, {"ends", {9}}}), input); auto r4 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {9}}, {"ends", {11}}}), input); mm->add_return({r1, r2, r3, r4}); auto prog = read_onnx("split_test_uneven_num_outputs.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sqrt_fp8_test.cpp000066400000000000000000000030201510465702400231170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sqrt_fp8_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, {10, 15}}); mm->add_instruction(migraphx::make_op("sqrt"), input); auto prog = optimize_onnx("sqrt_fp8_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sqrt_test.cpp000066400000000000000000000027741510465702400223610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sqrt_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10, 15}}); mm->add_instruction(migraphx::make_op("sqrt"), input); auto prog = optimize_onnx("sqrt_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/squeeze_axes_input_test.cpp000066400000000000000000000032401510465702400252750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(squeeze_axes_input_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal({migraphx::shape::int64_type, {2}}, {1, 3})); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 1, 5, 1}}); auto l1 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1, 3}}}), l0); mm->add_return({l1}); auto prog = read_onnx("squeeze_axes_input_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/squeeze_empty_axes_test.cpp000066400000000000000000000031751510465702400253030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(squeeze_empty_axes_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape::int64_type}); auto l0 = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3, 1, 5, 1}}); auto l1 = mm->add_instruction(migraphx::make_op("squeeze"), l0); mm->add_return({l1}); auto prog = read_onnx("squeeze_empty_axes_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/squeeze_unsqueeze_dyn_test.cpp000066400000000000000000000042331510465702400260170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(squeeze_unsqueeze_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector squeeze_axes{0, 2, 3, 5}; std::vector unsqueeze_axes{0, 1, 3, 5}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {{1, 1}, {1, 4}, {1, 1}, {1, 1}, {1, 4}, {1, 1}}}); auto c0 = mm->add_instruction(migraphx::make_op("contiguous"), l0); auto l1 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", squeeze_axes}}), c0); auto c1 = mm->add_instruction(migraphx::make_op("contiguous"), l1); auto ret = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", unsqueeze_axes}}), c1); mm->add_return({ret}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("squeeze_unsqueeze_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/squeeze_unsqueeze_test.cpp000066400000000000000000000034101510465702400251410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(squeeze_unsqueeze_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector squeeze_axes{0, 2, 3, 5}; std::vector unsqueeze_axes{0, 1, 3, 5}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 1, 1, 2, 1}}); auto l1 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", squeeze_axes}}), l0); mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", unsqueeze_axes}}), l1); auto prog = optimize_onnx("squeeze_unsqueeze_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sub_bcast_test.cpp000066400000000000000000000033521510465702400233260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sub_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3, 4}}); auto l2 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", l0->get_shape().lens()}}), l1); mm->add_instruction(migraphx::make_op("sub"), l0, l2); auto prog = optimize_onnx("sub_bcast_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sub_scalar_test.cpp000066400000000000000000000033371510465702400235020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sub_scalar_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::float_type}, {1}}); auto m1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 5}}}), l1); mm->add_instruction(migraphx::make_op("sub"), l0, m1); auto prog = optimize_onnx("sub_scalar_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sum_int_test.cpp000066400000000000000000000042021510465702400230320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sum_int_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::int16_type, {3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::uint16_type, {3}}); auto input2 = mm->add_parameter("2", migraphx::shape{migraphx::shape::uint32_type, {3}}); auto cin0 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::uint32_type)}}), input0); auto cin1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::uint32_type)}}), input1); auto l0 = mm->add_instruction(migraphx::make_op("add"), cin0, cin1); mm->add_instruction(migraphx::make_op("add"), l0, input2); auto prog = optimize_onnx("sum_int_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sum_test.cpp000066400000000000000000000034061510465702400221650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sum_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3}}); auto input1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3}}); auto input2 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {3}}); auto l0 = mm->add_instruction(migraphx::make_op("add"), input0, input1); mm->add_instruction(migraphx::make_op("add"), l0, input2); auto prog = optimize_onnx("sum_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/sum_type_test.cpp000066400000000000000000000074361510465702400232350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(sum_type_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l_bool = mm->add_literal({migraphx::shape{migraphx::shape::bool_type, {2}}, {1, 0}}); auto l_int8 = mm->add_literal({migraphx::shape{migraphx::shape::int8_type, {2}}, {1, 1}}); auto l_uint8 = mm->add_literal({migraphx::shape{migraphx::shape::uint8_type, {2}}, {1, 1}}); auto l_uint16 = mm->add_literal({migraphx::shape{migraphx::shape::uint16_type, {2}}, {1, 1}}); auto l_uint32 = mm->add_literal({migraphx::shape{migraphx::shape::uint32_type, {2}}, {1, 1}}); auto l_uint64 = mm->add_literal({migraphx::shape{migraphx::shape::uint64_type, {2}}, {1, 1}}); auto l_double = mm->add_literal({migraphx::shape{migraphx::shape::double_type, {2}}, {1, 1}}); auto l_raw = mm->add_literal({migraphx::shape{migraphx::shape::double_type, {2}}, {1.5, 2.0}}); auto o_bool = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), l_bool); auto o_int8 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), l_int8); auto o_uint8 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), l_uint8); auto o_uint16 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), l_uint16); auto o_uint32 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), l_uint32); auto o_uint64 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::double_type)}}), l_uint64); auto s0 = mm->add_instruction(migraphx::make_op("add"), o_bool, o_int8); auto s1 = mm->add_instruction(migraphx::make_op("add"), s0, o_uint8); auto s2 = mm->add_instruction(migraphx::make_op("add"), s1, o_uint16); auto s3 = mm->add_instruction(migraphx::make_op("add"), s2, o_uint32); auto s4 = mm->add_instruction(migraphx::make_op("add"), s3, o_uint64); auto s5 = mm->add_instruction(migraphx::make_op("add"), s4, l_double); auto s6 = mm->add_instruction(migraphx::make_op("add"), s5, l_raw); mm->add_return({s6}); auto prog = read_onnx("sum_type_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/tan_test.cpp000066400000000000000000000027651510465702400221520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(tan_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {10}}); mm->add_instruction(migraphx::make_op("tan"), input); auto prog = optimize_onnx("tan_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/tanh_test.cpp000066400000000000000000000027701510465702400223160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(tanh_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1}}); mm->add_instruction(migraphx::make_op("tanh"), input); auto prog = optimize_onnx("tanh_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/thresholdedrelu_default_test.cpp000066400000000000000000000040721510465702400262620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(thresholdedrelu_default_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 2, 3}}); auto lz = mm->add_literal(migraphx::literal{migraphx::shape{x->get_shape().type()}, {0}}); auto la = mm->add_literal(migraphx::literal{migraphx::shape{x->get_shape().type()}, {1.0f}}); auto mbz = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), lz); auto mba = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), la); auto condition = mm->add_instruction(migraphx::make_op("greater"), x, mba); mm->add_instruction(migraphx::make_op("where"), condition, x, mbz); auto prog = optimize_onnx("thresholdedrelu_default_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/thresholdedrelu_int_test.cpp000066400000000000000000000040571510465702400254330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(thresholdedrelu_int_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::int32_type, {2, 2, 3}}); auto lz = mm->add_literal(migraphx::literal{migraphx::shape{x->get_shape().type()}, {0}}); auto la = mm->add_literal(migraphx::literal{migraphx::shape{x->get_shape().type()}, {3}}); auto mbz = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), lz); auto mba = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), la); auto condition = mm->add_instruction(migraphx::make_op("greater"), x, mba); mm->add_instruction(migraphx::make_op("where"), condition, x, mbz); auto prog = optimize_onnx("thresholdedrelu_int_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/thresholdedrelu_test.cpp000066400000000000000000000040521510465702400245540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(thresholdedrelu_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 2, 3}}); auto lz = mm->add_literal(migraphx::literal{migraphx::shape{x->get_shape().type()}, {0}}); auto la = mm->add_literal(migraphx::literal{migraphx::shape{x->get_shape().type()}, {3.0f}}); auto mbz = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), lz); auto mba = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", x->get_shape().lens()}}), la); auto condition = mm->add_instruction(migraphx::make_op("greater"), x, mba); mm->add_instruction(migraphx::make_op("where"), condition, x, mbz); auto prog = optimize_onnx("thresholdedrelu_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/tile_test.cpp000066400000000000000000000031641510465702400223170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(tile_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {2}}, {1, 2}}); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 2}}); mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), input, input); auto prog = optimize_onnx("tile_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/tile_test_3x2.cpp000066400000000000000000000034631510465702400230150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(tile_test_3x2) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {2}}, {3, 2}}); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 2}}); auto l0 = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), input, input); auto l1 = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), l0, input); mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l1, l1); auto prog = optimize_onnx("tile_test_3x2.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/topk_attrk_test.cpp000066400000000000000000000034251510465702400235440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(topk_attrk_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 5, 3, 2}}; auto data = mm->add_parameter("data", s); auto out = mm->add_instruction(migraphx::make_op("topk", {{"k", 2}, {"axis", -1}}), data); auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); mm->add_return({val, ind}); auto prog = read_onnx("topk_attrk_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/topk_neg_axis_test.cpp000066400000000000000000000036351510465702400242170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(topk_neg_axis_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sk{migraphx::shape::int64_type, {1}}; mm->add_literal(migraphx::literal(sk, {3})); migraphx::shape s{migraphx::shape::float_type, {3, 4, 5, 6}}; auto data = mm->add_parameter("data", s); auto out = mm->add_instruction( migraphx::make_op("topk", {{"k", 3}, {"axis", -2}, {"largest", 1}}), data); auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); mm->add_return({val, ind}); auto prog = read_onnx("topk_neg_axis_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/topk_test.cpp000066400000000000000000000036121510465702400223350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(topk_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sk{migraphx::shape::int64_type, {1}}; mm->add_literal(migraphx::literal(sk, {4})); migraphx::shape s{migraphx::shape::float_type, {2, 5, 3, 2}}; auto data = mm->add_parameter("data", s); auto out = mm->add_instruction( migraphx::make_op("topk", {{"k", 4}, {"axis", 1}, {"largest", 0}}), data); auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); mm->add_return({val, ind}); auto prog = read_onnx("topk_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/transpose_default_perm_test.cpp000066400000000000000000000032141510465702400261230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(transpose_default_perm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 5, 2, 3}}); std::vector perm{3, 2, 1, 0}; auto r = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), input); mm->add_return({r}); auto prog = read_onnx("transpose_default_perm_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/transpose_dyn_test.cpp000066400000000000000000000034061510465702400242510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(transpose_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}, {2, 2}, {3, 3}}}); std::vector perm{0, 3, 1, 2}; auto t0 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), input); mm->add_return({t0}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("transpose_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/transpose_gather_test.cpp000066400000000000000000000044031510465702400247270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(transpose_gather_test) { migraphx::program p; auto* mm = p.get_main_module(); auto make_contiguous = [&mm](migraphx::instruction_ref ins) { if(ins->get_shape().standard()) { return ins; } return mm->add_instruction(migraphx::make_op("contiguous"), ins); }; auto data = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {3, 5, 4, 6}}); auto ind = mm->add_parameter("indices", migraphx::shape{migraphx::shape::int32_type, {2, 4, 3, 5}}); auto tr_data = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), data); auto tr_ind = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), ind); int axis = 1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), make_contiguous(tr_data), make_contiguous(tr_ind)); auto prog = optimize_onnx("transpose_gather_test.onnx"); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/transpose_invalid_perm_test.cpp000066400000000000000000000024741510465702400261340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(transpose_invalid_perm_test) { EXPECT(test::throws([&] { read_onnx("transpose_invalid_perm_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/transpose_test.cpp000066400000000000000000000031241510465702400233740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(transpose_test) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); std::vector perm{0, 3, 1, 2}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), input); auto prog = optimize_onnx("transpose_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/undefined_test.cpp000066400000000000000000000031401510465702400233150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(undefined_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_instruction(migraphx::make_op("undefined")); auto l2 = mm->add_instruction(migraphx::make_op("identity"), l1); mm->add_return({l2}); auto prog = read_onnx("undefined_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/unique_dynamic_sorted_3D_test.cpp000066400000000000000000000037621510465702400263060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(unique_dynamic_sorted_3_d_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int64_type, {4, 4, 4}}; auto x = mm->add_parameter("X", s); auto out = mm->add_instruction(migraphx::make_op("unique", {{"sorted", 1}}), x); auto y = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto y_ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); auto x_ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), out); auto count = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 3}}), out); mm->add_return({y, y_ind, x_ind, count}); auto prog = read_onnx("unique_dynamic_sorted_3D_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/unique_dynamic_sorted_test.cpp000066400000000000000000000037621510465702400257600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(unique_dynamic_sorted_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {6}}; auto x = mm->add_parameter("X", s); auto out = mm->add_instruction(migraphx::make_op("unique", {{"sorted", 1}, {"axis", 0}}), x); auto y = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto y_ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); auto x_ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), out); auto count = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 3}}), out); mm->add_return({y, y_ind, x_ind, count}); auto prog = read_onnx("unique_dynamic_sorted_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/unique_sorted_test.cpp000066400000000000000000000041001510465702400242370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(unique_sorted_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_x{migraphx::shape::float_type, {6}}; std::vector x_data = {2, 1, 1, 3, 4, 3}; auto x = mm->add_literal(migraphx::literal(s_x, x_data)); auto out = mm->add_instruction(migraphx::make_op("unique", {{"sorted", 1}, {"axis", 0}}), x); auto y = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto y_idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); auto x_idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), out); auto count = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 3}}), out); mm->add_return({y, y_idx, x_idx, count}); auto prog = read_onnx("unique_sorted_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/unique_unsorted_test.cpp000066400000000000000000000041041510465702400246060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(unique_unsorted_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_x{migraphx::shape::float_type, {6}}; std::vector x_data = {2, 1, 1, 3, 4, 3}; auto x = mm->add_literal(migraphx::literal(s_x, x_data)); auto out = mm->add_instruction(migraphx::make_op("unique", {{"sorted", 0}, {"axis", 0}}), x); auto y = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto y_idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); auto x_idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), out); auto count = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 3}}), out); mm->add_return({y, y_idx, x_idx, count}); auto prog = read_onnx("unique_unsorted_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/unknown_aten_test.cpp000066400000000000000000000024501510465702400240650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(unknown_aten_test) { EXPECT(test::throws([&] { read_onnx("unknown_aten_test.onnx"); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/unknown_test.cpp000066400000000000000000000040041510465702400230530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(unknown_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {3, 4}}); auto l2 = mm->add_instruction(migraphx::op::unknown{"Unknown"}, l0, l1); mm->add_instruction(migraphx::op::unknown{"Unknown"}, l2); auto prog = optimize_onnx("unknown_test.onnx"); EXPECT(p == prog); } TEST_CASE(unknown_test_throw) { EXPECT(test::throws([&] { read_onnx("unknown_test.onnx"); })); } TEST_CASE(unknown_test_throw_print_error) { migraphx::onnx_options options; options.print_program_on_error = true; EXPECT(test::throws([&] { read_onnx("unknown_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/upsample_linear_test.cpp000066400000000000000000000026451510465702400245450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(upsample_linear_test) { auto p = create_upsample_linear_prog(); // same net IR for resize & upsample auto prog = read_onnx("upsample_linear_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/upsample_test.cpp000066400000000000000000000040001510465702400231760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(upsample_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ss{migraphx::shape::float_type, {4}}; mm->add_literal(migraphx::literal(ss, {1.0f, 1.0f, 2.0f, 3.0f})); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; auto ix = mm->add_parameter("X", sx); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 6}}; std::vector ind = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), ix); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), rsp, li); mm->add_return({r}); auto prog = read_onnx("upsample_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/upsample_ver7_test.cpp000066400000000000000000000036121510465702400241510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(upsample_ver7_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; auto ix = mm->add_parameter("X", sx); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 6}}; std::vector ind = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; auto li = mm->add_literal(migraphx::literal(si, ind)); auto rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), ix); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), rsp, li); mm->add_return({r}); auto prog = read_onnx("upsample_ver7_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/variable_batch_leq_zero_test.cpp000066400000000000000000000032111510465702400262010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(variable_batch_leq_zero_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction(migraphx::make_op("add"), l0, l1); auto prog = optimize_onnx("variable_batch_leq_zero_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/variable_batch_test.cpp000066400000000000000000000113421510465702400243050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(variable_batch_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction(migraphx::make_op("identity"), l0); auto prog = optimize_onnx("variable_batch_test.onnx"); EXPECT(p == prog); } TEST_CASE(variable_batch_user_input_test1) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 16, 16}}); auto r = mm->add_instruction(migraphx::make_op("identity"), l0); mm->add_return({r}); migraphx::onnx_options options; options.default_dyn_dim_value = {2, 2}; auto prog = read_onnx("variable_batch_test.onnx", options); EXPECT(p == prog); } TEST_CASE(variable_batch_user_input_test2) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{2, 5}, {3, 3}, {16, 16}, {16, 16}}}); auto r = mm->add_instruction(migraphx::make_op("identity"), l0); mm->add_return({r}); migraphx::onnx_options options; options.default_dyn_dim_value = {2, 5}; auto prog = read_onnx("variable_batch_test.onnx", options); EXPECT(p == prog); } TEST_CASE(variable_batch_user_input_test3) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {{2, 5}, {3, 3}, {16, 16}, {16, 16}}}); auto r = mm->add_instruction(migraphx::make_op("identity"), l0); mm->add_return({r}); migraphx::onnx_options options; options.map_dyn_input_dims["0"] = {{2, 5}, {3, 3}, {16, 16}, {16, 16}}; auto prog = read_onnx("variable_batch_test.onnx", options); EXPECT(p == prog); } TEST_CASE(variable_batch_user_input_test4) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 16, 16}}); auto r = mm->add_instruction(migraphx::make_op("identity"), l0); mm->add_return({r}); migraphx::onnx_options options; options.default_dim_value = 2; auto prog = read_onnx("variable_batch_test.onnx", options); EXPECT(p == prog); } TEST_CASE(variable_batch_user_input_test5) { // Error using default_dim_value and default_dyn_dim_value migraphx::onnx_options options; options.default_dim_value = 2; options.default_dyn_dim_value = {1, 2}; EXPECT(test::throws([&] { read_onnx("variable_batch_test.onnx", options); })); } TEST_CASE(variable_batch_user_input_test6) { // Error using both map_dyn_input_dims and map_input_dims migraphx::onnx_options options; options.map_dyn_input_dims["0"] = {{2, 5}, {3, 3}, {16, 16}, {16, 16}}; options.map_input_dims["0"] = {2, 3, 16, 16}; EXPECT(test::throws([&] { read_onnx("variable_batch_test.onnx", options); })); } TEST_CASE(variable_batch_user_input_test7) { // if entry in map_dyn_input_dims is all fixed dynamic_dimensions, convert it to a static // shape migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 16, 16}}); auto r = mm->add_instruction(migraphx::make_op("identity"), l0); mm->add_return({r}); migraphx::onnx_options options; options.map_dyn_input_dims["0"] = {{2, 2, {2}}, {3, 3}, {16, 16}, {16, 16}}; auto prog = read_onnx("variable_batch_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/where_dyn_test.cpp000066400000000000000000000041041510465702400233410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(where_dyn_test) { // TODO: broadcasting for dynamic shapes isn't implemented at time of writing. // Update this test case to use shapes that require broadcasting, when available. migraphx::program p; auto* mm = p.get_main_module(); auto lc = mm->add_parameter( "c", migraphx::shape{migraphx::shape::bool_type, {{1, 4}, {2, 2}, {2, 2}}}); auto lx = mm->add_parameter( "x", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}, {2, 2}}}); auto ly = mm->add_parameter( "y", migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}, {2, 2}}}); auto r = mm->add_instruction(migraphx::make_op("where"), lc, lx, ly); mm->add_return({r}); migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto prog = read_onnx("where_dyn_test.onnx", options); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/where_mixed_test.cpp000066400000000000000000000027031510465702400236600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(where_mixed_test) { // mixture of static and dynamic input shapes is not supported migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; EXPECT(test::throws([&] { read_onnx("where_mixed_test.onnx", options); })); } ROCm-AMDMIGraphX-46524e8/test/onnx/parse/where_test.cpp000066400000000000000000000040741510465702400224750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include TEST_CASE(where_test) { migraphx::program p; auto* mm = p.get_main_module(); auto lc = mm->add_parameter("c", migraphx::shape{migraphx::shape::bool_type, {2}}); auto lx = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 2, 2}}); auto ly = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {2, 1, 2, 2}}); auto lccm = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 2, 2}}}), lc); auto lxm = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 2, 2}}}), lx); auto lym = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 2, 2}}}), ly); auto r = mm->add_instruction(migraphx::make_op("where"), lccm, lxm, lym); mm->add_return({r}); auto prog = read_onnx("where_test.onnx"); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/onnx/pow_bcast_test.onnx000066400000000000000000000001771510465702400224320ustar00rootroot00000000000000pow2:q  0 1out"Powpow_testZ 0     Z 1    b out     B ROCm-AMDMIGraphX-46524e8/test/onnx/pow_bcast_test1.onnx000066400000000000000000000002031510465702400225010ustar00rootroot00000000000000pow2:u  0 1out"Powpow_testZ 0     Z 1     b out     B ROCm-AMDMIGraphX-46524e8/test/onnx/pow_fp32_i64_test.onnx000066400000000000000000000002311510465702400225610ustar00rootroot00000000000000pow_fp32_i64_test:~  0 1out"Powpow_fp32_i64_testZ 0     Z 1     b out     B ROCm-AMDMIGraphX-46524e8/test/onnx/pow_i64_fp32_test.onnx000066400000000000000000000002311510465702400225610ustar00rootroot00000000000000pow_i64_fp32_test:~  0 1out"Powpow_i64_fp32_testZ 0     Z 1     b out     B ROCm-AMDMIGraphX-46524e8/test/onnx/pow_test.onnx000066400000000000000000000002031510465702400212440ustar00rootroot00000000000000pow2:u  0 1out"Powpow_testZ 0     Z 1     b out     B ROCm-AMDMIGraphX-46524e8/test/onnx/prefix_scan_sum_test.onnx000066400000000000000000000002561510465702400236340ustar00rootroot00000000000000prefix_scan_sum_test: 6 x axisy"CumSum* exclusive * reverse prefix_scan_sum_test* *BaxisZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/prelu_brcst_test.onnx000066400000000000000000000002211510465702400227630ustar00rootroot00000000000000prelu_brcst_test:w  0 1out"PReluprelu_brcst_testZ 0     Z 1   b out     B ROCm-AMDMIGraphX-46524e8/test/onnx/qlinearadd_bcast_test.onnx000066400000000000000000000005271510465702400237300ustar00rootroot00000000000000qlinearadd_bcast_test:· Z A A_scale A_zero_point B B_scale B_zero_point C_scale C_zero_pointC" QLinearAddqlinearadd_bcast_test*"ÍÌL=BA_scale**B A_zero_point*"ÍÌL=BB_scale** B B_zero_point*"ÍÌL=BC_scale** ÀÿÿÿÿÿÿÿÿB C_zero_pointZ A  @Z B    @b C    @BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearadd_test.onnx000066400000000000000000000004631510465702400225530ustar00rootroot00000000000000qlinearadd_test:™ Z A A_scale A_zero_point B B_scale B_zero_point C_scale C_zero_pointC" QLinearAddqlinearadd_test*"ÍÌL=BA_scale**B A_zero_point*"ÍÌL=BB_scale**€B B_zero_point*"ÍÌL=BC_scale**@B C_zero_pointZ A  @Z B  @b C  @BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearaveragepool_1d_test.onnx000066400000000000000000000004421510465702400247100ustar00rootroot00000000000000 qlinearaveragepool_1d_test:ý ] x x_scale x_zero_point y_scale y_zero_pointy"QLinearAveragePool* kernel_shape@ qlinearaveragepool_1d_test*"ÍÌL=Bx_scale**B x_zero_point*"ÍÌL=By_scale**B y_zero_pointZ x     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearaveragepool_2d_ceil_test.onnx000066400000000000000000000005331510465702400257060ustar00rootroot00000000000000 qlinearaveragepool_2d_ceil_test:± ƒ x x_scale x_zero_point y_scale y_zero_pointy"QLinearAveragePool* ceil_mode * kernel_shape@@ * strides@@ qlinearaveragepool_2d_ceil_test*"?Bx_scale**B x_zero_point*"ÍÌL=By_scale**B y_zero_pointZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearaveragepool_2d_dilations_test.onnx000066400000000000000000000005711510465702400267620ustar00rootroot00000000000000 $qlinearaveragepool_2d_dilations_test:Ê — x x_scale x_zero_point y_scale y_zero_pointy"QLinearAveragePool* ceil_mode * dilations@@ * kernel_shape@@ * strides@@ $qlinearaveragepool_2d_dilations_test*"?Bx_scale**B x_zero_point*"€>By_scale**TB y_zero_pointZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearaveragepool_2d_pads_count_include_pad_test.onnx000066400000000000000000000006101510465702400314540ustar00rootroot00000000000000 1qlinearaveragepool_2d_pads_count_include_pad_test:Ì Œ x x_scale x_zero_point y_scale y_zero_pointy"QLinearAveragePool* count_include_pad * kernel_shape@@ * pads@@@@ 1qlinearaveragepool_2d_pads_count_include_pad_test*"ÍÌL=Bx_scale**B x_zero_point*" ×#By_scale**B y_zero_pointZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearaveragepool_2d_strides_test.onnx000066400000000000000000000005161510465702400264500ustar00rootroot00000000000000 "qlinearaveragepool_2d_strides_test:¡ q x x_scale x_zero_point y_scale y_zero_pointy"QLinearAveragePool* kernel_shape@@ * strides@@ "qlinearaveragepool_2d_strides_test*"ÍÌL=Bx_scale**B x_zero_point*"ÍÌL=By_scale**B y_zero_pointZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearaveragepool_2d_test.onnx000066400000000000000000000004541510465702400247140ustar00rootroot00000000000000 qlinearaveragepool_2d_test:‡ _ x x_scale x_zero_point y_scale y_zero_pointy"QLinearAveragePool* kernel_shape@@ qlinearaveragepool_2d_test*"ÍÌL=Bx_scale**B x_zero_point*"ÂuB5**B6Z t0    Z t1    b out    BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearconcat_test.onnx000066400000000000000000000003521510465702400232670ustar00rootroot00000000000000 qlinearconcat_test:Í ; 1 2 t0 3 4 t1 5 6out" QLinearConcat* axis qlinearconcat_test* "?B1**B2* "?B3**B4* "€>B5**B6Z t0  Z t1  b out  BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearconv_pad_0_test.onnx000066400000000000000000000004341510465702400240310ustar00rootroot00000000000000qlinearconv_pad_0_test:û = X 1 2 3 4 5 6 7out" QLinearConv* pads@@@@ qlinearconv_pad_0_test* "ÁÀÀ=B1**B2** B3* "€?B4**B5* "£¢"?B6** €ÿÿÿÿÿÿÿÿB7Z X     b out     BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearconv_pad_1_test.onnx000066400000000000000000000004231510465702400240300ustar00rootroot00000000000000qlinearconv_pad_1_test:ò = X 1 2 3 4 5 6 7out" QLinearConv* pads@@@@ qlinearconv_pad_1_test* "ÁÀÀ=B1**B2** B3* "€?B4**B5* "£¢"?B6**B7Z X     b out     BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearconv_scale_1D_test.onnx000066400000000000000000000004641510465702400244640ustar00rootroot00000000000000qlinearconv_scale_1D_test: = X 1 2 3 4 5 6 7out" QLinearConv* pads@@@@ qlinearconv_scale_1D_test* "ÁÀÀ=B1**B2*!*B3*"€??B4* *B5* "£¢"?B6** €ÿÿÿÿÿÿÿÿB7Z X     b out     BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearconv_test.onnx000066400000000000000000000003561510465702400227710ustar00rootroot00000000000000qlinearconv_test:Ó * X 1 2 3 4 5 6 7out" QLinearConvqlinearconv_test* "Eöq;B1* *„B2**B3* "=|â:B4* *ÿB5* "Æ:Õ:B6**{B7Z X     b out     BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearglobalavgpool_test.onnx000066400000000000000000000004601510465702400246500ustar00rootroot00000000000000qlinearglobalavgpool_test:Œ d X X_scale X_zero_point Y_scale Y_zero_pointY"QLinearGlobalAveragePool* channels_last qlinearglobalavgpool_test*"ÍÌL=BX_scale**€B X_zero_point*"ÍÌÌBC_scale** B C_zero_pointZ A  @Z B    @b C    @BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearmul_test.onnx000066400000000000000000000004621510465702400226170ustar00rootroot00000000000000 qlinearmul_test:˜ Z A A_scale A_zero_point B B_scale B_zero_point C_scale C_zero_pointC" QLinearMulqlinearmul_test*"ÍÌL=BA_scale**B A_zero_point*"ÍÌL=BB_scale**B B_zero_point*"ÍÌL=BC_scale**dB C_zero_pointZ A  @Z B  @b C  @BROCm-AMDMIGraphX-46524e8/test/onnx/qlinearsigmoid_test.onnx000066400000000000000000000003641510465702400234560ustar00rootroot00000000000000 qlinearsigmoid_test:Ö D X X_scale X_zero_point Y_scale Y_zero_pointY"QLinearSigmoidqlinearsigmoid_test*"ÍÌL=BX_scale**B X_zero_point*"B`e;BY_scale** €ÿÿÿÿÿÿÿÿB Y_zero_pointZ X  @b Y  @BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_2d_blocked_runt_block_test.onnx000066400000000000000000000003451510465702400277730ustar00rootroot00000000000000 )quantizelinear_2d_blocked_runt_block_test:± ? x y_scaley"QuantizeLinear* axis * block_size )quantizelinear_2d_blocked_runt_block_testZ x   Z y_scale   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_2d_blocked_with_zp_test.onnx000066400000000000000000000003651510465702400273170ustar00rootroot00000000000000 &quantizelinear_2d_blocked_with_zp_test:Ä A x scale zpy"QuantizeLinear* axis * block_size &quantizelinear_2d_blocked_with_zp_testZ x   Z scale   Z zp   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_3d_blocked_with_zp_runt_block_test.onnx000066400000000000000000000004331510465702400315360ustar00rootroot00000000000000 1quantizelinear_3d_blocked_with_zp_runt_block_test:ß A x scale zpy"QuantizeLinear* axis * block_size 1quantizelinear_3d_blocked_with_zp_runt_block_testZ x    Z scale    Z zp    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_axis_test.onnx000066400000000000000000000003101510465702400245150ustar00rootroot00000000000000quantizelinear_axis_test:¥ + 0 1 2out"QuantizeLinear* axis quantizelinear_axis_testZ 0     Z 1  Z 2  b out     BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_blocked_invalid_block_size_test.onnx000066400000000000000000000003571510465702400311010ustar00rootroot00000000000000 .quantizelinear_blocked_invalid_block_size_test:¶ ? x y_scaley"QuantizeLinear* axis * block_size .quantizelinear_blocked_invalid_block_size_testZ x   Z y_scale   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_blocked_non_bc_axis_size_mismatch_test.onnx000066400000000000000000000003751510465702400324500ustar00rootroot00000000000000 5quantizelinear_blocked_non_bc_axis_size_mismatch_test:½ ? x y_scaley"QuantizeLinear* axis * block_size 5quantizelinear_blocked_non_bc_axis_size_mismatch_testZ x   Z y_scale   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_blocked_x_and_scales_rank_mismatch_test.onnx000066400000000000000000000004031510465702400325620ustar00rootroot00000000000000 6quantizelinear_blocked_x_and_scales_rank_mismatch_test: ? x y_scaley"QuantizeLinear* axis * block_size 6quantizelinear_blocked_x_and_scales_rank_mismatch_testZ x   Z y_scale    b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_blocked_zero_block_size_test.onnx000066400000000000000000000003511510465702400304240ustar00rootroot00000000000000 +quantizelinear_blocked_zero_block_size_test:³ ? x y_scaley"QuantizeLinear* axis * block_size +quantizelinear_blocked_zero_block_size_testZ x   Z y_scale   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_int32_test.onnx000066400000000000000000000002201510465702400245100ustar00rootroot00000000000000quantizelinear_int32_test:m  0 1out"QuantizeLinearquantizelinear_int32_testZ 0  Z 1  b out  BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_mxfp4_even_test.onnx000066400000000000000000000003641510465702400256350ustar00rootroot00000000000000 quantizelinear_mxfp4_even_test:Ë P 0 1out"QuantizeLinear* axis * block_size  * output_dtype quantizelinear_mxfp4_even_testZ 0   @  Z 1     b out   @  BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_mxfp4_odd_test.onnx000066400000000000000000000003621510465702400254440ustar00rootroot00000000000000 quantizelinear_mxfp4_odd_test:Ê P 0 1out"QuantizeLinear* axis * block_size  * output_dtype quantizelinear_mxfp4_odd_testZ 0   @  Z 1     b out   @  BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_neg_axis_test.onnx000066400000000000000000000003311510465702400253510ustar00rootroot00000000000000quantizelinear_neg_axis_test:² 4 0 1 2out"QuantizeLinear* axisþÿÿÿÿÿÿÿÿ quantizelinear_neg_axis_testZ 0     Z 1  Z 2  b out     BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_output_dtype_and_zp_type_mismatch_test.onnx000066400000000000000000000004501510465702400326040ustar00rootroot00000000000000 5quantizelinear_output_dtype_and_zp_type_mismatch_test:è V x scale zpy"QuantizeLinear* axis * block_size * output_dtype 5quantizelinear_output_dtype_and_zp_type_mismatch_testZ x   Z scale   Z zp   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_per_axis_shape_mismatch_test.onnx000066400000000000000000000003221510465702400304330ustar00rootroot00000000000000 +quantizelinear_per_axis_shape_mismatch_test:œ , x y_scaley"QuantizeLinear* axis +quantizelinear_per_axis_shape_mismatch_testZ x   Z y_scale  b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_scales_and_zp_shape_mismatch_test.onnx000066400000000000000000000004111510465702400314250ustar00rootroot00000000000000 0quantizelinear_scales_and_zp_shape_mismatch_test:Î A x scale zpy"QuantizeLinear* axis * block_size 0quantizelinear_scales_and_zp_shape_mismatch_testZ x   Z scale   Z zp   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_test.onnx000066400000000000000000000002041510465702400234730ustar00rootroot00000000000000quantizelinear_test:g  0 1out"QuantizeLinearquantizelinear_testZ 0  Z 1  b out  BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_too_few_inputs_test.onnx000066400000000000000000000002631510465702400266240ustar00rootroot00000000000000 "quantizelinear_too_few_inputs_test:† 6 xy"QuantizeLinear* axis * block_size "quantizelinear_too_few_inputs_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_too_many_inputs_test.onnx000066400000000000000000000004131510465702400270040ustar00rootroot00000000000000 #quantizelinear_too_many_inputs_test:Ý F x scale zp zp2y"QuantizeLinear* axis * block_size #quantizelinear_too_many_inputs_testZ x   Z scale   Z zp   Z zp2   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/quantizelinear_zero_point_test.onnx000066400000000000000000000002571510465702400257530ustar00rootroot00000000000000quantizelinear_zero_point_test:†  0 1 2out"QuantizeLinearquantizelinear_zero_point_testZ 0  Z 1  Z 2  b out  BROCm-AMDMIGraphX-46524e8/test/onnx/randomnormal_dtype_error_test.onnx000066400000000000000000000002341510465702400255520ustar00rootroot00000000000000randomnormal_dtype_error_test:u 6output" RandomNormal* dtype * shape@@@ randomnormal_dtype_error_testb output    BROCm-AMDMIGraphX-46524e8/test/onnx/randomnormal_generated_seed_test.onnx000066400000000000000000000002631510465702400261540ustar00rootroot00000000000000 randomnormal_generated_seed_test:ˆ 1 inputoutput" RandomNormal* sample_size   randomnormal_generated_seed_testZ input    b output    B ROCm-AMDMIGraphX-46524e8/test/onnx/randomnormal_shape_error_test.onnx000066400000000000000000000002121510465702400255210ustar00rootroot00000000000000randomnormal_shape_error_test:c $output" RandomNormal* dtype randomnormal_shape_error_testb output    BROCm-AMDMIGraphX-46524e8/test/onnx/randomnormal_test.onnx000066400000000000000000000002661510465702400231410ustar00rootroot00000000000000randomnormal_test:š goutput" RandomNormal* dtype  * mean A * scaleÀ? * seed * shape@@@ randomnormal_testb output     BROCm-AMDMIGraphX-46524e8/test/onnx/randomnormallike_dtype_fallback_test.onnx000066400000000000000000000003001510465702400270170ustar00rootroot00000000000000$randomnormallike_dtype_fallback_test:‘ . inputoutput"RandomNormalLike* seed $randomnormallike_dtype_fallback_testZ input    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/randomnormallike_test.onnx000066400000000000000000000003241510465702400240010ustar00rootroot00000000000000randomnormallike_test:´ ` inputoutput"RandomNormalLike* dtype  * mean A * scaleÀ? * seed randomnormallike_testZ input     b output     BROCm-AMDMIGraphX-46524e8/test/onnx/randomnormallike_type_error_test.onnx000066400000000000000000000002701510465702400262530ustar00rootroot00000000000000 randomnormallike_type_error_test: . inputoutput"RandomNormalLike* seed  randomnormallike_type_error_testZ input    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/randomuniform_dtype_error_test.onnx000066400000000000000000000002371510465702400257440ustar00rootroot00000000000000randomuniform_dtype_error_test:w 7output" RandomUniform* dtype * shape@@@ randomuniform_dtype_error_testb output    BROCm-AMDMIGraphX-46524e8/test/onnx/randomuniform_generated_seed_test.onnx000066400000000000000000000002661510465702400263460ustar00rootroot00000000000000!randomuniform_generated_seed_test:Š 2 inputoutput" RandomUniform* sample_size  !randomuniform_generated_seed_testZ input    b output    B ROCm-AMDMIGraphX-46524e8/test/onnx/randomuniform_shape_error_test.onnx000066400000000000000000000002151510465702400257130ustar00rootroot00000000000000randomuniform_shape_error_test:e %output" RandomUniform* dtype randomuniform_shape_error_testb output    BROCm-AMDMIGraphX-46524e8/test/onnx/randomuniform_test.onnx000066400000000000000000000002671510465702400233310ustar00rootroot00000000000000randomuniform_test:š foutput" RandomUniform* dtype  * high€? * low * seed * shape@@@ randomuniform_testb output     BROCm-AMDMIGraphX-46524e8/test/onnx/randomuniformlike_dtype_fallback_test.onnx000066400000000000000000000003031510465702400272110ustar00rootroot00000000000000%randomuniformlike_dtype_fallback_test:“ / inputoutput"RandomUniformLike* seed %randomuniformlike_dtype_fallback_testZ input    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/randomuniformlike_test.onnx000066400000000000000000000003251510465702400241710ustar00rootroot00000000000000randomuniformlike_test:´ _ inputoutput"RandomUniformLike* dtype  * high A * low€? * seed randomuniformlike_testZ input     b output     BROCm-AMDMIGraphX-46524e8/test/onnx/randomuniformlike_type_error_test.onnx000066400000000000000000000002731510465702400264450ustar00rootroot00000000000000!randomuniformlike_type_error_test: / inputoutput"RandomUniformLike* seed !randomuniformlike_type_error_testZ input    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/range_float_test.onnx000066400000000000000000000003731510465702400227300ustar00rootroot00000000000000range_float_test:à 2start"Constant* value*"@B start_val  2limit"Constant* value*"0AB limit_val  2delta"Constant* value*"@B delta_val   start limit delta1"Rangerange_float_testb 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/range_test.onnx000066400000000000000000000003571510465702400215450ustar00rootroot00000000000000 range_test:Ú /start"Constant* value*: B start_val  /limit"Constant* value*:B limit_val  8delta"Constant*% value*: ýÿÿÿÿÿÿÿÿB delta_val   start limit delta1"Range range_testb 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/recip_test.onnx000066400000000000000000000001261510465702400215450ustar00rootroot00000000000000 recip_test:B  xy" Reciprocal recip_testZ x  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/reduce_log_sum_exp_test.onnx000066400000000000000000000002611510465702400243130ustar00rootroot00000000000000reduce_log_sum_exp_test: > xy"ReduceLogSumExp* axes@üÿÿÿÿÿÿÿÿ * keepdims reduce_log_sum_exp_testZ x     b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/reduce_log_sum_test.onnx000066400000000000000000000002521510465702400234370ustar00rootroot00000000000000reduce_log_sum_test:Œ ; xy" ReduceLogSum* axes@ýÿÿÿÿÿÿÿÿ * keepdims reduce_log_sum_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducel1_dyn_noaxes_test.onnx000066400000000000000000000001751510465702400244020ustar00rootroot00000000000000reducel1_dyn_noaxes_test:[ ! xy"ReduceL1* keepdims reducel1_dyn_noaxes_testZ x  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/reducel1_dyn_test.onnx000066400000000000000000000002051510465702400230170ustar00rootroot00000000000000reducel1_dyn_test:j 7 xy"ReduceL1* axes@þÿÿÿÿÿÿÿÿ * keepdims reducel1_dyn_testZ x  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/reducel1_test.onnx000066400000000000000000000002251510465702400221470ustar00rootroot00000000000000 reducel1_test:~ 7 xy"ReduceL1* axes@þÿÿÿÿÿÿÿÿ * keepdims  reducel1_testZ x     b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/reducel2_test.onnx000066400000000000000000000002251510465702400221500ustar00rootroot00000000000000 reducel2_test:~ 7 xy"ReduceL2* axes@ÿÿÿÿÿÿÿÿÿ * keepdims  reducel2_testZ x     b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/reducemax_dyn_test.onnx000066400000000000000000000002231510465702400232700ustar00rootroot00000000000000reducemax_dyn_test:w / xy" ReduceMax* axes@ * keepdims reducemax_dyn_testZ x    b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/reducemax_fp8_test.onnx000066400000000000000000000002271510465702400231770ustar00rootroot00000000000000 reducemax_fp8_test:{ / xy" ReduceMax* axes@ * keepdims reducemax_fp8_testZ x     b y    BROCm-AMDMIGraphX-46524e8/test/onnx/reducemax_test.onnx000066400000000000000000000002261510465702400224210ustar00rootroot00000000000000reducemax-example:{ / xy" ReduceMax* axes@ * keepdims test_reducemaxZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducemean_keepdims_test.onnx000066400000000000000000000002311510465702400244310ustar00rootroot00000000000000reducemean-example:} 0 xy" ReduceMean* axes@ * keepdims test_reducemeanZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducemean_test.onnx000066400000000000000000000002231510465702400225510ustar00rootroot00000000000000reducemean-example:w 2 xy" ReduceMean* axes@@ * keepdims test_reducemeanZ x     b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/reducemin_test.onnx000066400000000000000000000002201510465702400224110ustar00rootroot00000000000000reducemin-example:u 1 xy" ReduceMin* axes@@ * keepdims test_reduceminZ x     b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/reduceprod_test.onnx000066400000000000000000000002261510465702400226000ustar00rootroot00000000000000reduceprod_test:} 0 xy" ReduceProd* axes@ * keepdims reduceprod_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_empty_axes_test.onnx000066400000000000000000000003141510465702400246740ustar00rootroot00000000000000reducesum_empty_axes_test:¨ E x axesy" ReduceSum* keepdims * noop_with_empty_axes reducesum_empty_axes_test* BaxesZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_fp8_test.onnx000066400000000000000000000002331510465702400232130ustar00rootroot00000000000000 reducesum_fp8_test: / xy" ReduceSum* axes@ * keepdims reducesum_fp8_testZ x     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_keepdims_test.onnx000066400000000000000000000002301510465702400243140ustar00rootroot00000000000000reducesum-example:} 1 xy" ReduceSum* axes@@ * keepdims test_reducesumZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_multiaxis_test.onnx000066400000000000000000000002301510465702400245320ustar00rootroot00000000000000reducesum-example:} 1 xy" ReduceSum* axes@@ * keepdims test_reducesumZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_noop_test.onnx000066400000000000000000000003001510465702400234640ustar00rootroot00000000000000reducesum_noop_test:¢ E x axesy" ReduceSum* keepdims * noop_with_empty_axes reducesum_noop_test* BaxesZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_square_test.onnx000066400000000000000000000002551510465702400240220ustar00rootroot00000000000000reducesum_square_test: > xy"ReduceSumSquare* axes@þÿÿÿÿÿÿÿÿ * keepdims reducesum_square_testZ x     b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_test.onnx000066400000000000000000000002231510465702400224350ustar00rootroot00000000000000reducesum_test:{ / xy" ReduceSum* axes@ * keepdims reducesum_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_variable_axes_keepdims_clear_test.onnx000066400000000000000000000003701510465702400303540ustar00rootroot00000000000000 +reducesum_variable_axes_keepdims_clear_test: E x axesy" ReduceSum* keepdims * noop_with_empty_axes +reducesum_variable_axes_keepdims_clear_testZ x     Z axes  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_variable_axes_noop_test.onnx000066400000000000000000000003441510465702400263610ustar00rootroot00000000000000 !reducesum_variable_axes_noop_test:¸ E x axesy" ReduceSum* keepdims * noop_with_empty_axes !reducesum_variable_axes_noop_testZ x     Z axes  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_variable_axes_test.onnx000066400000000000000000000003321510465702400253230ustar00rootroot00000000000000 reducesum_variable_axes_test:³ E x axesy" ReduceSum* keepdims * noop_with_empty_axes reducesum_variable_axes_testZ x     Z axes  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_variable_dynamic_axes_noop_set_verify_test.onnx000066400000000000000000000003451510465702400323250ustar00rootroot00000000000000 4reducesum_variable_dynamic_axes_noop_set_verify_test:¦ 4 x axesy" ReduceSum* noop_with_empty_axes 4reducesum_variable_dynamic_axes_noop_set_verify_testZ x    Z axes  b y  BROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_variable_dynamic_axes_test.onnx000066400000000000000000000003501510465702400270270ustar00rootroot00000000000000 $reducesum_variable_dynamic_axes_test:¹ E x axesy" ReduceSum* keepdims * noop_with_empty_axes $reducesum_variable_dynamic_axes_testZ x     Z axes  b y     BROCm-AMDMIGraphX-46524e8/test/onnx/reducesum_variable_dynamic_axes_verify_test.onnx000066400000000000000000000002661510465702400304210ustar00rootroot00000000000000 +reducesum_variable_dynamic_axes_verify_test:€  x axesy" ReduceSum+reducesum_variable_dynamic_axes_verify_testZ x    Z axes  b y  BROCm-AMDMIGraphX-46524e8/test/onnx/reshape_non_standard_test.onnx000066400000000000000000000003041510465702400246220ustar00rootroot00000000000000reshape_non_standard_test:  ( xtrans_x" Transpose* perm@@@  ' trans_xy"Reshape* shape@@@ reshape_non_standard_testZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/reshape_test.onnx000066400000000000000000000002761510465702400221000ustar00rootroot00000000000000reshape-example:¤  0 12"Reshape  03"Reshape* shape@@  test-reshape* :B1Z 0    Z 1  b 2   b 3   BROCm-AMDMIGraphX-46524e8/test/onnx/reshape_variable_input_dyn_test.onnx000066400000000000000000000002311510465702400260250ustar00rootroot00000000000000reshape_variable_input_dyn_test:p  0 12"Reshapereshape_variable_input_dyn_testZ 0   Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/reshape_variable_input_test.onnx000066400000000000000000000002251510465702400251560ustar00rootroot00000000000000reshape_variable_input_test:p  0 12"Reshapereshape_variable_input_testZ 0    Z 1  b 2   BROCm-AMDMIGraphX-46524e8/test/onnx/reshape_variable_input_test0.onnx000066400000000000000000000002271510465702400252400ustar00rootroot00000000000000reshape_variable_input_test0:q  0 12"Reshapereshape_variable_input_test0Z 0    Z 1  b 2   BROCm-AMDMIGraphX-46524e8/test/onnx/reshape_variable_input_test1.onnx000066400000000000000000000002231510465702400252350ustar00rootroot00000000000000reshape_variable_input_test1:m  0 12"Reshapereshape_variable_input_test1Z 0   Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/resize_aspect_ratio_err_test.onnx000066400000000000000000000004641510465702400253560ustar00rootroot00000000000000 resize_aspect_ratio_err_test: Ÿ X sizesY"Resize*/ coordinate_transformation_mode" asymmetric *& keep_aspect_ratio_policy"stretch * mode"nearest * nearest_mode"ceil resize_aspect_ratio_err_test*:BsizesZ X     b Y     BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_c_test.onnx000066400000000000000000000004171510465702400245020ustar00rootroot00000000000000resize_downsample_c_test:ì v X scalesY"Resize*/ coordinate_transformation_mode" asymmetric * mode"nearest * nearest_mode"ceil resize_downsample_c_test*"€?€?š™?š™?BscalesZ X     b Y     B ROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_f_dyn2_test.onnx000066400000000000000000000003741510465702400254430ustar00rootroot00000000000000 resize_downsample_f_dyn2_test:Ô x X sizesY"Resize*/ coordinate_transformation_mode" asymmetric * mode"nearest * nearest_mode"floor resize_downsample_f_dyn2_test*:BsizesZ X     b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_f_dyn3_test.onnx000066400000000000000000000003761510465702400254460ustar00rootroot00000000000000 resize_downsample_f_dyn3_test:Ö w X scalesY"Resize*/ coordinate_transformation_mode" asymmetric * mode"nearest * nearest_mode"floor resize_downsample_f_dyn3_testZ X     Z scales  b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_f_dyn_test.onnx000066400000000000000000000004061510465702400253550ustar00rootroot00000000000000 resize_downsample_f_dyn_test:ß w X scalesY"Resize*/ coordinate_transformation_mode" asymmetric * mode"nearest * nearest_mode"floor resize_downsample_f_dyn_test*"€?€?#Û?#Û?BscalesZ X     b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_f_ref2_test.onnx000066400000000000000000000003761510465702400254270ustar00rootroot00000000000000 resize_downsample_f_ref2_test:Ö x X sizesY"Resize*/ coordinate_transformation_mode" asymmetric * mode"nearest * nearest_mode"floor resize_downsample_f_ref2_test*:BsizesZ X      b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_f_ref_test.onnx000066400000000000000000000004101510465702400253320ustar00rootroot00000000000000 resize_downsample_f_ref_test:á w X scalesY"Resize*/ coordinate_transformation_mode" asymmetric * mode"nearest * nearest_mode"floor resize_downsample_f_ref_test*"€?€?#Û?#Û?BscalesZ X      b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_f_test.onnx000066400000000000000000000004031510465702400245000ustar00rootroot00000000000000 resize_downsample_f_test:à z X scalesY"Resize*2 coordinate_transformation_mode" align_corners * mode"nearest * nearest_mode"floor resize_downsample_f_test*"€?€?š™?š™?BscalesZ X     b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_linear_half_invalid_scale_test.onnx000066400000000000000000000003341510465702400313770ustar00rootroot00000000000000 0resize_downsample_linear_half_invalid_scale_test:¡ + X scalesY"Resize* mode"linear 0resize_downsample_linear_half_invalid_scale_test* *€x€xÍq€pBscalesZ X      b Y  BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_linear_half_test.onnx000066400000000000000000000003101510465702400265140ustar00rootroot00000000000000 "resize_downsample_linear_half_test:› + X scalesY"Resize* mode"linear "resize_downsample_linear_half_test*"€?€?š™??BscalesZ X      b Y  BROCm-AMDMIGraphX-46524e8/test/onnx/resize_downsample_linear_test.onnx000066400000000000000000000002761510465702400255350ustar00rootroot00000000000000resize_downsample_linear_test:– + X scalesY"Resize* mode"linear resize_downsample_linear_test*"€?€?š™??BscalesZ X     b Y B ROCm-AMDMIGraphX-46524e8/test/onnx/resize_dyn_err1_test.onnx000066400000000000000000000003571510465702400235550ustar00rootroot00000000000000 resize_dyn_err3_test:Ð ƒ X scalesY"Resize*/ coordinate_transformation_mode" half_pixel * mode"nearest *$ nearest_mode"round_prefer_ceil resize_dyn_err3_test*"‘íÌ?BscalesZ X  b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_dyn_err2_test.onnx000066400000000000000000000003531510465702400235520ustar00rootroot00000000000000 resize_dyn_err2_test:Ì ƒ X scalesY"Resize*/ coordinate_transformation_mode" half_pixel * mode"nearest *$ nearest_mode"round_prefer_ceil resize_dyn_err2_test*"‘íÌ?BscalesZ X  b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_linear_non_const_test.onnx000066400000000000000000000002701510465702400253560ustar00rootroot00000000000000 resize_linear_non_constant_test:Ž + X scalesY"Resize* mode"linear resize_linear_non_constant_testZ X     Z scales  b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_no_scale_test.onnx000066400000000000000000000003741510465702400236140ustar00rootroot00000000000000 resize_no_scale_test:Ý } X Y"Resize*/ coordinate_transformation_mode" half_pixel * mode"nearest *$ nearest_mode"round_prefer_ceil resize_no_scale_test*"€?€?‘íÌ?‘íÌ?BscalesZ X    b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_nonstd_input_test.onnx000066400000000000000000000004671510465702400245600ustar00rootroot00000000000000resize_nonstd_input_test:” % XTX" Transpose* perm@@@@  w TX scalesY"Resize*/ coordinate_transformation_mode" asymmetric * mode"nearest * nearest_mode"ceil resize_nonstd_input_test*"€?€?š™?š™?BscalesZ X     b Y     B ROCm-AMDMIGraphX-46524e8/test/onnx/resize_outsize_test.onnx000066400000000000000000000004301510465702400235240ustar00rootroot00000000000000 resize_outsize_test:ú ’ X out_lensY"Resize*9 coordinate_transformation_mode"tf_half_pixel_for_nn * mode"nearest *% nearest_mode"round_prefer_floor resize_outsize_test*:Bout_lensZ X     b Y     BROCm-AMDMIGraphX-46524e8/test/onnx/resize_roi_skip_test.onnx000066400000000000000000000003551510465702400236470ustar00rootroot00000000000000 resize_roi_skip_test:Î / X roi scalesY"Resize* mode"nearest resize_roi_skip_test*+" ÍÌŒ?š™™?ff¦?33³?À?ÍÌÌ?š™Ù?ffæ?Broi*"€?€?@@BscalesZ X     b Y     BROCm-AMDMIGraphX-46524e8/test/onnx/resize_upsample_f_dyn_test.onnx000066400000000000000000000004171510465702400250340ustar00rootroot00000000000000 resize_upsample_f_dyn_test:ê ƒ X scalesY"Resize*/ coordinate_transformation_mode" half_pixel * mode"nearest *$ nearest_mode"round_prefer_ceil resize_upsample_f_dyn_test*"€?€?‘íÌ?‘íÌ?BscalesZ X    b Y BROCm-AMDMIGraphX-46524e8/test/onnx/resize_upsample_linear_ac_test.onnx000066400000000000000000000003641510465702400256530ustar00rootroot00000000000000resize_upsample_linear_ac_test:Ë _ X scalesY"Resize*2 coordinate_transformation_mode" align_corners * mode"linear resize_upsample_linear_ac_test*"€?€?@@BscalesZ X     b Y B ROCm-AMDMIGraphX-46524e8/test/onnx/resize_upsample_linear_large_test.onnx000066400000000000000000000003321510465702400263550ustar00rootroot00000000000000 !resize_upsample_linear_large_test:® + X scalesY"Resize* mode"linear !resize_upsample_linear_large_test*"€?€?@@BscalesZ X    € €b Y    € €BROCm-AMDMIGraphX-46524e8/test/onnx/resize_upsample_linear_test.onnx000066400000000000000000000002721510465702400252060ustar00rootroot00000000000000resize_upsample_linear_test:” + X scalesY"Resize* mode"linear resize_upsample_linear_test*"€?€?@@BscalesZ X     b Y B ROCm-AMDMIGraphX-46524e8/test/onnx/resize_upsample_pc_test.onnx000066400000000000000000000004731510465702400243410ustar00rootroot00000000000000resize_upsample_pc_test:™ £ X scalesY"Resize*7 coordinate_transformation_mode"pytorch_half_pixel * exclude_outside * mode"nearest *$ nearest_mode"round_prefer_ceil resize_upsample_pc_test*"€?€?@À?BscalesZ X     b Y     B ROCm-AMDMIGraphX-46524e8/test/onnx/resize_upsample_pf_test.onnx000066400000000000000000000003031510465702400243340ustar00rootroot00000000000000resize_upsample_pf_test:¡ , X scalesY"Resize* mode"nearest resize_upsample_pf_test*"€?€?@@@BscalesZ X     b Y     B ROCm-AMDMIGraphX-46524e8/test/onnx/resize_with_same_inout_shapes_test.onnx000066400000000000000000000004161510465702400265670ustar00rootroot00000000000000 "resize_with_same_inout_shapes_test:á w X sizesY"Resize*/ coordinate_transformation_mode" half_pixel * mode"linear * nearest_mode"floor "resize_with_same_inout_shapes_test*:BsizesZ X    b Y    BROCm-AMDMIGraphX-46524e8/test/onnx/reversesequence_4D_test.onnx000066400000000000000000000003131510465702400241740ustar00rootroot00000000000000reversesequence_4D_test:© T xy"ReverseSequence* batch_axis * sequence_lens@@ * time_axis reversesequence_4D_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reversesequence_batch_axis_err_test.onnx000066400000000000000000000003371510465702400267100ustar00rootroot00000000000000#reversesequence_batch_axis_err_test:± X xy"ReverseSequence* batch_axis * sequence_lens@@@@ * time_axis #reversesequence_batch_axis_err_testZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/reversesequence_batch_test.onnx000066400000000000000000000003701510465702400250110ustar00rootroot00000000000000reversesequence_batch_test:Ó ? arg_seq_lens"Constant*% value*:B sequence_lens  J x arg_seq_lensy"ReverseSequence* batch_axis * time_axis reversesequence_batch_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/reversesequence_rank_err_test.onnx000066400000000000000000000002351510465702400255330ustar00rootroot00000000000000reversesequence_rank_err_test:v 3 xy"ReverseSequence* sequence_lens@@@@ reversesequence_rank_err_testZ x  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/reversesequence_same_axis_err_test.onnx000066400000000000000000000003251510465702400265510ustar00rootroot00000000000000"reversesequence_same_axis_err_test:¨ X xy"ReverseSequence* batch_axis * sequence_lens@@@@ * time_axis "reversesequence_same_axis_err_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/reversesequence_sequence_lens_shape_err_test.onnx000066400000000000000000000003021510465702400306040ustar00rootroot00000000000000,reversesequence_sequence_lens_shape_err_test:‹ 1 xy"ReverseSequence* sequence_lens@@@ ,reversesequence_sequence_lens_shape_err_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/reversesequence_time_axis_err_test.onnx000066400000000000000000000003451510465702400265640ustar00rootroot00000000000000"reversesequence_time_axis_err_test:¸ X xy"ReverseSequence* batch_axis * sequence_lens@@@@ * time_axis "reversesequence_time_axis_err_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/reversesequence_time_test.onnx000066400000000000000000000003031510465702400246620ustar00rootroot00000000000000reversesequence_time_test:Ÿ X xy"ReverseSequence* batch_axis * sequence_lens@@@@ * time_axis reversesequence_time_testZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/rnn_bi_1af_test.onnx000066400000000000000000000004521510465702400224430ustar00rootroot00000000000000 rnn_bi_1af_test: t seq w rhsoutput"RNN* activationsJtanh * clip * direction" bidirectional * hidden_size rnn_bi_1af_testZ seq     Z w     Z r    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/rnn_bi_layout_test.onnx000066400000000000000000000006451510465702400233150ustar00rootroot00000000000000 rnn_bi_layout_test:ˆ Ÿ seq w r bias seq_len h0hsoutput"RNN* activationsJtanhJsigmoid * clip * direction" bidirectional * hidden_size * layout rnn_bi_layout_testZ seq     Z w     Z r    Z bias   (Z seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/rnn_f_5arg_layout_test.onnx000066400000000000000000000006001510465702400240550ustar00rootroot00000000000000 rnn_f_5arg_layout_test:ß Œ seq w r bias seq_lenhsoutput"RNN* activationsJtanh * clip * direction"forward * hidden_size * layout rnn_f_5arg_layout_testZ seq     Z w     Z r    Z bias   (Z seq_len  b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/rnn_f_default_af_test.onnx000066400000000000000000000004301510465702400237150ustar00rootroot00000000000000 rnn_f_default_af_test:ø V seq w rhsoutput"RNN* clip * direction"forward * hidden_size rnn_f_default_af_testZ seq     Z w     Z r    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/rnn_f_layout_test.onnx000066400000000000000000000006241510465702400231450ustar00rootroot00000000000000 rnn_f_layout_test:ø  seq w r bias seq_len h0hsoutput"RNN* activationsJtanh * clip * direction"forward * hidden_size * layout rnn_f_layout_testZ seq     Z w     Z r    Z bias   (Z seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/rnn_r_3arg_layout_test.onnx000066400000000000000000000005011510465702400240670ustar00rootroot00000000000000 rnn_r_3arg_layout_test:  } seq w rhsoutput"RNN* activationsJtanh * clip * direction"reverse * hidden_size * layout rnn_r_3arg_layout_testZ seq     Z w     Z r    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/rnn_r_layout_test.onnx000066400000000000000000000006241510465702400231610ustar00rootroot00000000000000 rnn_r_layout_test:ø  seq w r bias seq_len h0hsoutput"RNN* activationsJtanh * clip * direction"reverse * hidden_size * layout rnn_r_layout_testZ seq     Z w     Z r    Z bias   (Z seq_len  Z h0    b hs     b output    BROCm-AMDMIGraphX-46524e8/test/onnx/roialign_default_test.onnx000066400000000000000000000003051510465702400237520ustar00rootroot00000000000000roialign_default_test:¥ ! x rois batch_indy"RoiAlignroialign_default_testZ x     Z rois   Z batch_ind  b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/roialign_test.onnx000066400000000000000000000005311510465702400222470ustar00rootroot00000000000000 roialign_test:Á Ä x rois batch_indy"RoiAlign*6 coordinate_transformation_mode"output_half_pixel * mode"avg * output_height * output_width * sampling_ratio * spatial_scale@  roialign_testZ x     Z rois   Z batch_ind  b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_cache_1_test.onnx000066400000000000000000000004731510465702400253310ustar00rootroot00000000000000 rotary_embedding_cache_1_test:“ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoftrotary_embedding_cache_1_testZ input     Z pos_ids   Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_cache_2_test.onnx000066400000000000000000000004731510465702400253320ustar00rootroot00000000000000 rotary_embedding_cache_2_test:“ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoftrotary_embedding_cache_2_testZ input     Z pos_ids   Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_dim_size_test.onnx000066400000000000000000000005551510465702400256520ustar00rootroot00000000000000 rotary_embedding_dim_size_test:Ä ‘ input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved * num_heads * rotary_embedding_dim : com.microsoftrotary_embedding_dim_size_testZ input     Z pos_ids   Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_dim_test.onnx000066400000000000000000000005371510465702400246200ustar00rootroot00000000000000 rotary_embedding_dim_test:» ‘ input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved * num_heads * rotary_embedding_dim : com.microsoftrotary_embedding_dim_testZ input     Z pos_ids  Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_float_test.onnx000066400000000000000000000004771510465702400251570ustar00rootroot00000000000000 rotary_embedding_float_test:™ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoftrotary_embedding_float_testZ input     Z pos_ids   Z cos_cache   Z sin_cache   b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_input_dims_test.onnx000066400000000000000000000005151510465702400262160ustar00rootroot00000000000000  rotary_embedding_input_dims_test:¢ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoft rotary_embedding_input_dims_testZ# input       Z pos_ids   Z cos_cache    Z sin_cache    b output      BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_interleaved_large_test.onnx000066400000000000000000000005131510465702400275150ustar00rootroot00000000000000 'rotary_embedding_interleaved_large_test:™ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoft'rotary_embedding_interleaved_large_testZ input     Z pos_ids  Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_interleaved_test.onnx000066400000000000000000000004771510465702400263540ustar00rootroot00000000000000 !rotary_embedding_interleaved_test:“ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoft!rotary_embedding_interleaved_testZ input     Z pos_ids  Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_num_heads_test.onnx000066400000000000000000000005571510465702400260140ustar00rootroot00000000000000 rotary_embedding_num_heads_test:Å ‘ input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved * num_heads * rotary_embedding_dim : com.microsoftrotary_embedding_num_heads_testZ input     Z pos_ids   Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_packed_batching_test.onnx000066400000000000000000000006261510465702400271340ustar00rootroot00000000000000 %rotary_embedding_packed_batching_test:æ ¬ input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved * is_packed_batching * num_heads * rotary_embedding_dim : com.microsoft%rotary_embedding_packed_batching_testZ input     Z pos_ids   Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_pos_ids_1_test.onnx000066400000000000000000000005031510465702400257200ustar00rootroot00000000000000 rotary_embedding_pos_ids_1_test:™ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoftrotary_embedding_pos_ids_1_testZ input     Z pos_ids    Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_pos_ids_2_test.onnx000066400000000000000000000004731510465702400257270ustar00rootroot00000000000000 rotary_embedding_pos_ids_2_test:‘ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoftrotary_embedding_pos_ids_2_testZ input     Z pos_ids  Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_pos_ids_3_test.onnx000066400000000000000000000004771510465702400257340ustar00rootroot00000000000000 rotary_embedding_pos_ids_3_test:• b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoftrotary_embedding_pos_ids_3_testZ input     Z pos_ids   Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_scale_test.onnx000066400000000000000000000005101510465702400251250ustar00rootroot00000000000000 rotary_embedding_scale_test:¢ s input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved * scale : com.microsoftrotary_embedding_scale_testZ input     Z pos_ids   Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_test.onnx000066400000000000000000000004531510465702400237640ustar00rootroot00000000000000 rotary_embedding_test:‹ b input pos_ids cos_cache sin_cacheoutput"RotaryEmbedding* interleaved : com.microsoftrotary_embedding_testZ input     Z pos_ids   Z cos_cache    Z sin_cache    b output     BROCm-AMDMIGraphX-46524e8/test/onnx/rotary_embedding_wrong_n_inputs_test.onnx000066400000000000000000000003711510465702400271160ustar00rootroot00000000000000 $rotary_embedding_wrong_n_inputs_test:Ê L input pos_idsoutput"RotaryEmbedding* interleaved : com.microsoft$rotary_embedding_wrong_n_inputs_testZ input     Z pos_ids   b output     BROCm-AMDMIGraphX-46524e8/test/onnx/round_bf16_test.onnx000066400000000000000000000001431510465702400224070ustar00rootroot00000000000000 round_bf16_test:J xy"Roundround_bf16_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/round_half_test.onnx000066400000000000000000000001431510465702400225630ustar00rootroot00000000000000 round_half_test:J xy"Roundround_half_testZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/round_test.onnx000066400000000000000000000001341510465702400215710ustar00rootroot00000000000000 round-example:E xy"Round test_roundZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/scan_arg_count_mismatch_test.onnx000066400000000000000000000011721510465702400253170ustar00rootroot00000000000000 scan_arg_count_mismatch_test:Ó ’ init_state scan_ins1 scan_ins2 final_state scan_outs"Scan*Å body2¹ sum_in scan_in1sum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in1   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@@ * scan_input_directions@@ * scan_output_axes@ * scan_output_directions@ scan_arg_count_mismatch_testZ init_state   Z scan_ins1    Z scan_ins2    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_arg_shapes_mismatch_test.onnx000066400000000000000000000012241510465702400254500ustar00rootroot00000000000000 scan_arg_shapes_mismatch_test:ì ® init_state scan_ins1 scan_ins2 final_state scan_outs"Scan*á body2Õ sum_in scan_in1sum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in1   Z scan_in2   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@@ * scan_input_directions@@ * scan_output_axes@ * scan_output_directions@ scan_arg_shapes_mismatch_testZ init_state   Z scan_ins1    Z scan_ins2   b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_input_axes_lens_mismatch_test.onnx000066400000000000000000000012421510465702400265340ustar00rootroot00000000000000 "scan_input_axes_lens_mismatch_test:õ ® init_state scan_ins1 scan_ins2 final_state scan_outs"Scan*á body2Õ sum_in scan_in1sum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in1   Z scan_in2   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@@ * scan_input_directions@@ * scan_output_axes@ * scan_output_directions@ "scan_input_axes_lens_mismatch_testZ init_state   Z scan_ins1    Z scan_ins2    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_invalid_input_axes_len_test.onnx000066400000000000000000000011201510465702400261650ustar00rootroot00000000000000  scan_invalid_input_axes_len_test:¥ ‚ init_state scan_ins final_state scan_outs"Scan*à body2·  sum_in scan_insum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@@ * scan_input_directions@ * scan_output_axes@ * scan_output_directions@  scan_invalid_input_axes_len_testZ init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_invalid_input_axes_vals_test.onnx000066400000000000000000000011201510465702400263540ustar00rootroot00000000000000 !scan_invalid_input_axes_vals_test:¤ € init_state scan_ins final_state scan_outs"Scan*à body2·  sum_in scan_insum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@ * scan_input_directions@ * scan_output_axes@ * scan_output_directions@ !scan_invalid_input_axes_vals_testZ init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_invalid_input_dirs_len_test.onnx000066400000000000000000000011201510465702400261660ustar00rootroot00000000000000  scan_invalid_input_dirs_len_test:¥ ‚ init_state scan_ins final_state scan_outs"Scan*à body2·  sum_in scan_insum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@ * scan_input_directions@@ * scan_output_axes@ * scan_output_directions@  scan_invalid_input_dirs_len_testZ init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_invalid_input_dirs_vals_test.onnx000066400000000000000000000011201510465702400263550ustar00rootroot00000000000000 !scan_invalid_input_dirs_vals_test:¤ € init_state scan_ins final_state scan_outs"Scan*à body2·  sum_in scan_insum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@ * scan_input_directions@ * scan_output_axes@ * scan_output_directions@ !scan_invalid_input_dirs_vals_testZ init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_invalid_output_axes_len_test.onnx000066400000000000000000000011221510465702400263700ustar00rootroot00000000000000 !scan_invalid_output_axes_len_test:¦ ‚ init_state scan_ins final_state scan_outs"Scan*à body2·  sum_in scan_insum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@ * scan_input_directions@ * scan_output_axes@@ * scan_output_directions@ !scan_invalid_output_axes_len_testZ init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_invalid_output_axes_vals_test.onnx000066400000000000000000000011331510465702400265610ustar00rootroot00000000000000 "scan_invalid_output_axes_vals_test:® ‰ init_state scan_ins final_state scan_outs"Scan*à body2·  sum_in scan_insum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@ * scan_input_directions@ * scan_output_axes@üÿÿÿÿÿÿÿÿ * scan_output_directions@ "scan_invalid_output_axes_vals_testZ init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_invalid_output_dirs_len_test.onnx000066400000000000000000000011221510465702400263710ustar00rootroot00000000000000 !scan_invalid_output_dirs_len_test:¦ ‚ init_state scan_ins final_state scan_outs"Scan*à body2·  sum_in scan_insum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@ * scan_input_directions@ * scan_output_axes@ * scan_output_directions@@ !scan_invalid_output_dirs_len_testZ init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_invalid_output_dirs_vals_test.onnx000066400000000000000000000011331510465702400265620ustar00rootroot00000000000000 "scan_invalid_output_dirs_vals_test:® ‰ init_state scan_ins final_state scan_outs"Scan*à body2·  sum_in scan_insum_out"Add  sum_outscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@ * scan_input_directions@ * scan_output_axes@ *& scan_output_directions@ÿÿÿÿÿÿÿÿÿ "scan_invalid_output_dirs_vals_testZ init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scan_test1.onnx000066400000000000000000000014311510465702400214500ustar00rootroot00000000000000  scan_test1:„ º init_state scan_ins1 scan_ins2 final_state scan_outs1 scan_outs2"Scan*Ü body2Ð ! sum_in scan_in1add1_out"Add " add1_out scan_in2sum_out"Add  sum_out scan_out1"Identity = sum_out scan_out2" ReduceSum* axes@ * keepdims  scan_bodyZ sum_in   Z scan_in1   Z scan_in2  b sum_out   b scan_out1   b scan_out2   * num_scan_inputs * scan_input_axes@@ * scan_input_directions@@ * scan_output_axes@@ * scan_output_directions@@  scan_test1Z init_state   Z scan_ins1    Z scan_ins2   b final_state   b scan_outs1    b scan_outs2   BROCm-AMDMIGraphX-46524e8/test/onnx/scan_test2.onnx000066400000000000000000000014311510465702400214510ustar00rootroot00000000000000  scan_test2:„ º init_state scan_ins1 scan_ins2 final_state scan_outs1 scan_outs2"Scan*Ü body2Ð ! sum_in scan_in1add1_out"Add " add1_out scan_in2sum_out"Add  sum_out scan_out1"Identity = sum_out scan_out2" ReduceSum* axes@ * keepdims  scan_bodyZ sum_in   Z scan_in1   Z scan_in2  b sum_out   b scan_out1   b scan_out2   * num_scan_inputs * scan_input_axes@@ * scan_input_directions@@ * scan_output_axes@@ * scan_output_directions@@  scan_test2Z init_state   Z scan_ins1    Z scan_ins2   b final_state   b scan_outs1    b scan_outs2   BROCm-AMDMIGraphX-46524e8/test/onnx/scan_test3.onnx000066400000000000000000000014421510465702400214540ustar00rootroot00000000000000  scan_test3: à init_state scan_ins1 scan_ins2 final_state scan_outs1 scan_outs2"Scan*Ü body2Ð ! sum_in scan_in1add1_out"Add " add1_out scan_in2sum_out"Add  sum_out scan_out1"Identity = sum_out scan_out2" ReduceSum* axes@ * keepdims  scan_bodyZ sum_in   Z scan_in1   Z scan_in2  b sum_out   b scan_out1   b scan_out2   * num_scan_inputs * scan_input_axes@@ * scan_input_directions@@ *" scan_output_axes@@ÿÿÿÿÿÿÿÿÿ * scan_output_directions@@  scan_test3Z init_state   Z scan_ins1    Z scan_ins2   b final_state   b scan_outs1    b scan_outs2   BROCm-AMDMIGraphX-46524e8/test/onnx/scan_test4.onnx000066400000000000000000000014311510465702400214530ustar00rootroot00000000000000  scan_test4:„ º init_state scan_ins1 scan_ins2 final_state scan_outs1 scan_outs2"Scan*Ü body2Ð ! sum_in scan_in1add1_out"Add " add1_out scan_in2sum_out"Add  sum_out scan_out1"Identity = sum_out scan_out2" ReduceSum* axes@ * keepdims  scan_bodyZ sum_in   Z scan_in1   Z scan_in2  b sum_out   b scan_out1   b scan_out2   * num_scan_inputs * scan_input_axes@@ * scan_input_directions@@ * scan_output_axes@@ * scan_output_directions@@  scan_test4Z init_state   Z scan_ins1    Z scan_ins2   b final_state   b scan_outs1    b scan_outs2   BROCm-AMDMIGraphX-46524e8/test/onnx/scan_test5.onnx000066400000000000000000000014421510465702400214560ustar00rootroot00000000000000  scan_test5: à init_state scan_ins1 scan_ins2 final_state scan_outs1 scan_outs2"Scan*Ü body2Ð ! sum_in scan_in1add1_out"Add " add1_out scan_in2sum_out"Add  sum_out scan_out1"Identity = sum_out scan_out2" ReduceSum* axes@ * keepdims  scan_bodyZ sum_in   Z scan_in1   Z scan_in2  b sum_out   b scan_out1   b scan_out2   * num_scan_inputs *! scan_input_axes@@ÿÿÿÿÿÿÿÿÿ * scan_input_directions@@ * scan_output_axes@@ * scan_output_directions@@  scan_test5Z init_state   Z scan_ins1    Z scan_ins2   b final_state   b scan_outs1    b scan_outs2   BROCm-AMDMIGraphX-46524e8/test/onnx/scan_test6.onnx000066400000000000000000000014421510465702400214570ustar00rootroot00000000000000  scan_test6: à init_state scan_ins1 scan_ins2 final_state scan_outs1 scan_outs2"Scan*Ü body2Ð ! sum_in scan_in1add1_out"Add " add1_out scan_in2sum_out"Add  sum_out scan_out1"Identity = sum_out scan_out2" ReduceSum* axes@ * keepdims  scan_bodyZ sum_in   Z scan_in1   Z scan_in2  b sum_out   b scan_out1   b scan_out2   * num_scan_inputs *! scan_input_axes@þÿÿÿÿÿÿÿÿ@ * scan_input_directions@@ * scan_output_axes@@ * scan_output_directions@@  scan_test6Z init_state   Z scan_ins1    Z scan_ins2   b final_state   b scan_outs1    b scan_outs2   BROCm-AMDMIGraphX-46524e8/test/onnx/scan_test7.onnx000066400000000000000000000011061510465702400214550ustar00rootroot00000000000000  scan_test7:± ¤ init_state scan_ins final_state scan_outs"Scan*ç body2Û sum_in scan_inadd1_out"Add ! add1_out scan_insum_out"Add  scan_inscan_out"Identity scan_bodyZ sum_in   Z scan_in   b sum_out   b scan_out    * num_scan_inputs * scan_input_axes@ * scan_input_directions@ * scan_output_axes@ * scan_output_directions@  scan_test7Z init_state   Z scan_ins    b final_state   b scan_outs    BROCm-AMDMIGraphX-46524e8/test/onnx/scatter_add_test.onnx000066400000000000000000000004071510465702400227220ustar00rootroot00000000000000scatter_add_test:ì V data indices updatey"ScatterElements* axisþÿÿÿÿÿÿÿÿ * reduction"add scatter_add_testZ data     Z! indices     Z update     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/scatter_elements_invalid_reduction_test.onnx000066400000000000000000000004711510465702400275710ustar00rootroot00000000000000 'scatter_elements_invalid_reduction_test:‡ Z data indices updatey"ScatterElements* axisþÿÿÿÿÿÿÿÿ * reduction"invalid 'scatter_elements_invalid_reduction_testZ data     Z! indices     Z update     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/scatter_max_test.onnx000066400000000000000000000004071510465702400227570ustar00rootroot00000000000000 scatter_max_test:ì V data indices updatey"ScatterElements* axisþÿÿÿÿÿÿÿÿ * reduction"max scatter_max_testZ data     Z! indices     Z update     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/scatter_min_test.onnx000066400000000000000000000004071510465702400227550ustar00rootroot00000000000000 scatter_min_test:ì V data indices updatey"ScatterElements* axisþÿÿÿÿÿÿÿÿ * reduction"min scatter_min_testZ data     Z! indices     Z update     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/scatter_mul_test.onnx000066400000000000000000000004071510465702400227670ustar00rootroot00000000000000scatter_mul_test:ì V data indices updatey"ScatterElements* axisþÿÿÿÿÿÿÿÿ * reduction"mul scatter_mul_testZ data     Z! indices     Z update     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/scatter_none_test.onnx000066400000000000000000000004121510465702400231250ustar00rootroot00000000000000scatter_none_test:î W data indices updatey"ScatterElements* axisþÿÿÿÿÿÿÿÿ * reduction"none scatter_none_testZ data     Z! indices     Z update     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/scatternd_add_test.onnx000066400000000000000000000003531510465702400232440ustar00rootroot00000000000000scatternd_add_test:Î @ data indices updatesoutput" ScatterND* reduction"add scatternd_add_testZ data    Z indices    Z updates    b output    B ROCm-AMDMIGraphX-46524e8/test/onnx/scatternd_dyn_test.onnx000066400000000000000000000003161510465702400233050ustar00rootroot00000000000000scatternd_dyn_test:± + data indices updatesoutput" ScatterNDscatternd_dyn_testZ data   Z indices   Z updates   b output   B ROCm-AMDMIGraphX-46524e8/test/onnx/scatternd_invalid_reduction_test.onnx000066400000000000000000000004131510465702400262130ustar00rootroot00000000000000  scatternd_invalid_reduction_test:à D data indices updatesoutput" ScatterND* reduction"invalid  scatternd_invalid_reduction_testZ data    Z indices    Z updates    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/scatternd_max_test.onnx000066400000000000000000000003531510465702400233010ustar00rootroot00000000000000 scatternd_max_test:Î @ data indices updatesoutput" ScatterND* reduction"max scatternd_max_testZ data    Z indices    Z updates    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/scatternd_min_test.onnx000066400000000000000000000003531510465702400232770ustar00rootroot00000000000000 scatternd_min_test:Î @ data indices updatesoutput" ScatterND* reduction"min scatternd_min_testZ data    Z indices    Z updates    b output    BROCm-AMDMIGraphX-46524e8/test/onnx/scatternd_mul_test.onnx000066400000000000000000000003531510465702400233110ustar00rootroot00000000000000scatternd_mul_test:Î @ data indices updatesoutput" ScatterND* reduction"mul scatternd_mul_testZ data    Z indices    Z updates    b output    B ROCm-AMDMIGraphX-46524e8/test/onnx/scatternd_test.onnx000066400000000000000000000003161510465702400224330ustar00rootroot00000000000000scatternd_test:µ + data indices updatesoutput" ScatterNDscatternd_testZ data    Z indices    Z updates    b output    B ROCm-AMDMIGraphX-46524e8/test/onnx/selu_test.onnx000066400000000000000000000001701510465702400214120ustar00rootroot00000000000000  selu_test:e . xy"Selu* alphaš™™> * gamma?  selu_testZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/shape_dyn_test0.onnx000066400000000000000000000001411510465702400224720ustar00rootroot00000000000000shape_dyn_test0:H xy"Shapeshape_dyn_test0Z x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shape_dyn_test1.onnx000066400000000000000000000001571510465702400225020ustar00rootroot00000000000000 shape_dyn_test1:V  xy"Shape* start shape_dyn_test1Z x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shape_dyn_test2.onnx000066400000000000000000000001701510465702400224760ustar00rootroot00000000000000 shape_dyn_test2:_ $ xy"Shape* startþÿÿÿÿÿÿÿÿ shape_dyn_test2Z x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shape_dyn_test3.onnx000066400000000000000000000001731510465702400225020ustar00rootroot00000000000000 shape_dyn_test3:b ' xy"Shape* end * start shape_dyn_test3Z x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shape_end_less_start_error.onnx000066400000000000000000000002211510465702400250020ustar00rootroot00000000000000 shape_end_less_start_error:m ' xy"Shape* end * start shape_end_less_start_errorZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shape_end_oob_test.onnx000066400000000000000000000001631510465702400232310ustar00rootroot00000000000000 shape_end_oob_test:W  xy"Shape* end shape_end_oob_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shape_gather_test.onnx000066400000000000000000000003021510465702400230710ustar00rootroot00000000000000shape_gather_test:¦ 4value"Constant*! value**B const_tensor  xy"Shape " y valuez"Gather* axis shape_gather_testZ x     b z  BROCm-AMDMIGraphX-46524e8/test/onnx/shape_start_oob_test.onnx000066400000000000000000000002021510465702400236120ustar00rootroot00000000000000 shape_start_oob_test:d $ xy"Shape* startúÿÿÿÿÿÿÿÿ shape_start_oob_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shape_test.onnx000066400000000000000000000001401510465702400215370ustar00rootroot00000000000000 shape-example:I xy"Shape test_shapeZ x     b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shrink_fp8_test.onnx000066400000000000000000000002051510465702400225140ustar00rootroot00000000000000 shrink_fp8_test:l / xy"Shrink* biasÀ? * lambdÀ? shrink_fp8_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/shrink_hard_test.onnx000066400000000000000000000001571510465702400227430ustar00rootroot00000000000000 shrink_hard_test:U  xy"Shrink* lambdÀ? shrink_hard_testZ x  b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shrink_int8_test.onnx000066400000000000000000000002071510465702400227030ustar00rootroot00000000000000 shrink_int8_test:m / xy"Shrink* biasÀ? * lambdÀ? shrink_int8_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/shrink_soft_test.onnx000066400000000000000000000001771510465702400230020ustar00rootroot00000000000000 shrink_soft_test:e / xy"Shrink* biasÀ? * lambdÀ? shrink_soft_testZ x  b y  BROCm-AMDMIGraphX-46524e8/test/onnx/shrink_uint8_test.onnx000066400000000000000000000002111510465702400230630ustar00rootroot00000000000000 shrink_uint8_test:n / xy"Shrink* biasÀ * lambd @ shrink_uint8_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/shrink_verify2_test.onnx000066400000000000000000000002051510465702400234050ustar00rootroot00000000000000 shrink_verify2_test:h / xy"Shrink* bias @ * lambdÀÀ shrink_verify2_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/shrink_verify_test.onnx000066400000000000000000000002031510465702400233210ustar00rootroot00000000000000 shrink_verify_test:g / xy"Shrink* bias€? * lambd À shrink_verify_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/sign_test.onnx000066400000000000000000000001311510465702400213770ustar00rootroot00000000000000 sign-example:C xy"Sign test_signZ x    b y    B ROCm-AMDMIGraphX-46524e8/test/onnx/simplified_layer_normalization_4d_test.onnx000066400000000000000000000004351510465702400273240ustar00rootroot00000000000000 &simplified_layer_normalization_4d_test:ì g x scaley"SimplifiedLayerNormalization* axisÿÿÿÿÿÿÿÿÿ * epsilon¬Å'7 * stash_type &simplified_layer_normalization_4d_testZ x      Z scale      b y      BROCm-AMDMIGraphX-46524e8/test/onnx/simplified_layer_normalization_invalid_input_test.onnx000066400000000000000000000003531510465702400316610ustar00rootroot00000000000000 1simplified_layer_normalization_invalid_input_test:¯ + x scaley"SimplifiedLayerNormalization1simplified_layer_normalization_invalid_input_testZ x      Z scale   b y      BROCm-AMDMIGraphX-46524e8/test/onnx/simplified_layer_normalization_invalid_n_args_test.onnx000066400000000000000000000004071510465702400317730ustar00rootroot00000000000000 2simplified_layer_normalization_invalid_n_args_test:Ê 1 x scale biasy"SimplifiedLayerNormalization2simplified_layer_normalization_invalid_n_args_testZ x     Z scale   Z bias     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/simplified_layer_normalization_test.onnx000066400000000000000000000004031510465702400267300ustar00rootroot00000000000000 #simplified_layer_normalization_test:Õ g x scaley"SimplifiedLayerNormalization* axisÿÿÿÿÿÿÿÿÿ * epsilon¬Å'7 * stash_type #simplified_layer_normalization_testZ x     Z scale   b y     BROCm-AMDMIGraphX-46524e8/test/onnx/sin_fp8_test.onnx000066400000000000000000000001231510465702400220060ustar00rootroot00000000000000  sin_fp8_test:= xy"Sin sin_fp8_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/sin_test.onnx000066400000000000000000000001161510465702400212330ustar00rootroot00000000000000 sin-example:9 xy"Sintest_sinZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/sinh_dynamic_test.onnx000066400000000000000000000001321510465702400231050ustar00rootroot00000000000000sinh_dynamic_test:? xy"Sinhsinh_dynamic_testZ x  b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/sinh_test.onnx000066400000000000000000000001211510465702400213770ustar00rootroot00000000000000 sinh-example:; xy"Sinh test_sinhZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/size_bf16_test.onnx000066400000000000000000000001341510465702400222320ustar00rootroot00000000000000 size_bf16_test:D xy"Sizesize_bf16_testZ x   b y  BROCm-AMDMIGraphX-46524e8/test/onnx/size_float_test.onnx000066400000000000000000000001421510465702400226000ustar00rootroot00000000000000size_float_test:I xy"Sizesize_float_testZ x    b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/size_fp8_test.onnx000066400000000000000000000001361510465702400221730ustar00rootroot00000000000000  size_fp8_test:G xy"Size size_fp8_testZ x    b y  BROCm-AMDMIGraphX-46524e8/test/onnx/size_half_test.onnx000066400000000000000000000001341510465702400224060ustar00rootroot00000000000000size_half_test:D xy"Sizesize_half_testZ x    b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/size_int_test.onnx000066400000000000000000000001361510465702400222700ustar00rootroot00000000000000 size_int_test:G xy"Size size_int_testZ x    b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/size_verify_test.onnx000066400000000000000000000001441510465702400230010ustar00rootroot00000000000000size_verify_test:J xy"Sizesize_verify_testZ x    b y  B ROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_2d_skip_test.onnx000066400000000000000000000006271510465702400271740ustar00rootroot00000000000000 %skip_layer_normalization_2d_skip_test:ç u x skip gammaymean inv_std_varinput_skip_bias_sum"SkipLayerNormalization* epsilon¬Å'7 : com.microsoft%skip_layer_normalization_2d_skip_testZ x     Z skip    Z gamma   b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_beta_bias_test.onnx000066400000000000000000000007241510465702400275500ustar00rootroot00000000000000 'skip_layer_normalization_beta_bias_test:¢  x skip gamma beta biasymean inv_std_varinput_skip_bias_sum"SkipLayerNormalization* epsilon¬Å'7 : com.microsoft'skip_layer_normalization_beta_bias_testZ x     Z skip     Z gamma   Z beta   Z bias   b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_beta_test.onnx000066400000000000000000000006571510465702400265570ustar00rootroot00000000000000 "skip_layer_normalization_beta_test:‚ { x skip gamma betaymean inv_std_varinput_skip_bias_sum"SkipLayerNormalization* epsilon¬Å'7 : com.microsoft"skip_layer_normalization_beta_testZ x     Z skip     Z gamma   Z beta   b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_invalid_beta_test.onnx000066400000000000000000000007031510465702400302550ustar00rootroot00000000000000 *skip_layer_normalization_invalid_beta_test:Ž { x skip gamma betaymean inv_std_varinput_skip_bias_sum"SkipLayerNormalization* epsilon¬Å'7 : com.microsoft*skip_layer_normalization_invalid_beta_testZ x     Z skip     Z gamma   Z beta    b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_invalid_bias_test.onnx000066400000000000000000000007321510465702400302620ustar00rootroot00000000000000 *skip_layer_normalization_invalid_bias_test:¥  x skip gamma beta biasymean inv_std_varinput_skip_bias_sum"SkipLayerNormalization* epsilon¬Å'7 : com.microsoft*skip_layer_normalization_invalid_bias_testZ x     Z skip     Z gamma   Z beta   Z bias  b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_invalid_input_test.onnx000066400000000000000000000004411510465702400305000ustar00rootroot00000000000000 +skip_layer_normalization_invalid_input_test:ë M x skip gammay"SkipLayerNormalization* epsilon¬Å'7 : com.microsoft+skip_layer_normalization_invalid_input_testZ x      Z skip     Z gamma    b y      BROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_invalid_n_args_test.onnx000066400000000000000000000003731510465702400306160ustar00rootroot00000000000000 ,skip_layer_normalization_invalid_n_args_test:Ä F x skipy"SkipLayerNormalization* epsilon¬Å'7 : com.microsoft,skip_layer_normalization_invalid_n_args_testZ x     Z skip     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_skip_batch_size_1_test.onnx000066400000000000000000000006571510465702400312250ustar00rootroot00000000000000 /skip_layer_normalization_skip_batch_size_1_test:õ u x skip gammaymean inv_std_varinput_skip_bias_sum"SkipLayerNormalization* epsilon¬Å'7 : com.microsoft/skip_layer_normalization_skip_batch_size_1_testZ x     Z skip     Z gamma   b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_layer_normalization_test.onnx000066400000000000000000000006131510465702400255540ustar00rootroot00000000000000 skip_layer_normalization_test:ã u x skip gammaymean inv_std_varinput_skip_bias_sum"SkipLayerNormalization* epsilon¬Å'7 : com.microsoftskip_layer_normalization_testZ x     Z skip     Z gamma   b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_simplified_layer_normalization_bias_test.onnx000066400000000000000000000007201510465702400307560ustar00rootroot00000000000000 -skip_simplified_layer_normalization_bias_test:˜ … x skip gamma biasymean inv_std_varinput_skip_bias_sum" SkipSimplifiedLayerNormalization* epsilon¬Å'7 : com.microsoft-skip_simplified_layer_normalization_bias_testZ x     Z skip     Z gamma   Z bias   b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_simplified_layer_normalization_invalid_input_test.onnx000066400000000000000000000005011510465702400327020ustar00rootroot00000000000000 6skip_simplified_layer_normalization_invalid_input_test:€ W x skip gammay" SkipSimplifiedLayerNormalization* epsilon¬Å'7 : com.microsoft6skip_simplified_layer_normalization_invalid_input_testZ x      Z skip     Z gamma    b y      BROCm-AMDMIGraphX-46524e8/test/onnx/skip_simplified_layer_normalization_invalid_n_args_test.onnx000066400000000000000000000004331510465702400330200ustar00rootroot00000000000000 7skip_simplified_layer_normalization_invalid_n_args_test:Ù P x skipy" SkipSimplifiedLayerNormalization* epsilon¬Å'7 : com.microsoft7skip_simplified_layer_normalization_invalid_n_args_testZ x     Z skip     b y     BROCm-AMDMIGraphX-46524e8/test/onnx/skip_simplified_layer_normalization_test.onnx000066400000000000000000000006531510465702400277650ustar00rootroot00000000000000 (skip_simplified_layer_normalization_test:ø  x skip gammaymean inv_std_varinput_skip_bias_sum" SkipSimplifiedLayerNormalization* epsilon¬Å'7 : com.microsoft(skip_simplified_layer_normalization_testZ x     Z skip     Z gamma   b y     b mean    b! inv_std_var    b) input_skip_bias_sum     BROCm-AMDMIGraphX-46524e8/test/onnx/slice_3arg_test.onnx000066400000000000000000000003341510465702400224570ustar00rootroot00000000000000slice_3arg_test: 2 arg_start"Constant* value**Bstart  .arg_end"Constant* value* *Bend  ! 0 arg_start arg_end1"Sliceslice_3arg_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/slice_5arg_reverse_test.onnx000066400000000000000000000006431510465702400242170ustar00rootroot00000000000000slice_5arg_reverse_test: 9arg_step"Constant*# value** ÿÿÿÿÿÿÿÿÿBstep  Barg_axis"Constant*, value* *ÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿBaxis  @arg_end"Constant*+ value**ûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBend  D arg_start"Constant*- value*!*ÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿBstart  5 0 arg_start arg_end arg_axis arg_step1"Sliceslice_5arg_reverse_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/slice_5arg_step_test.onnx000066400000000000000000000006351510465702400235200ustar00rootroot00000000000000slice_5arg_step_test:þ 9arg_step"Constant*# value** þÿÿÿÿÿÿÿÿBstep  Barg_axis"Constant*, value* *ÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿBaxis  @arg_end"Constant*+ value**ûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBend  D arg_start"Constant*- value*!*ÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿBstart  5 0 arg_start arg_end arg_axis arg_step1"Sliceslice_5arg_step_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/slice_5arg_test.onnx000066400000000000000000000006121510465702400224600ustar00rootroot00000000000000slice_5arg_test:ð 0arg_step"Constant* value**Bstep  Barg_axis"Constant*, value* *ÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿBaxis  @arg_end"Constant*+ value**ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBend  D arg_start"Constant*- value*!*ûÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿBstart  5 0 arg_start arg_end arg_axis arg_step1"Sliceslice_5arg_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/slice_constant_test.onnx000066400000000000000000000003151510465702400234530ustar00rootroot00000000000000slice_constant_test:¯ Ex"Constant*6 value**"€?@@@€@ @Bx_tensor  < x1"Slice* axes@@ * ends@@ * starts@@ slice_constant_testb 1   BROCm-AMDMIGraphX-46524e8/test/onnx/slice_dyn_test.onnx000066400000000000000000000002121510465702400224100ustar00rootroot00000000000000slice_dyn_test:r 6 01"Slice* axes@ * ends@ * starts@ slice_dyn_testZ 0  b 1  BROCm-AMDMIGraphX-46524e8/test/onnx/slice_max_end_test.onnx000066400000000000000000000002461510465702400232400ustar00rootroot00000000000000slice_max_end_test:‰ I 01"Slice* axes@@ * ends@€¼Á– @ÿÿÿÿÿÿÿÿÿ * starts@@ slice_max_end_testZ 0   b 1   B ROCm-AMDMIGraphX-46524e8/test/onnx/slice_reverse_dyn_test.onnx000066400000000000000000000006351510465702400241540ustar00rootroot00000000000000slice_reverse_dyn_test:ü 9arg_step"Constant*# value** ÿÿÿÿÿÿÿÿÿBstep  Barg_axis"Constant*, value* *ÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿBaxis  @arg_end"Constant*+ value**ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBend  D arg_start"Constant*- value*!*ûÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿBstart  5 0 arg_start arg_end arg_axis arg_step1"Sliceslice_reverse_dyn_testZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/slice_step_dyn_test.onnx000066400000000000000000000006161510465702400234530ustar00rootroot00000000000000slice_step_dyn_test:ð 0arg_step"Constant* value**Bstep  Barg_axis"Constant*, value* *ÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿBaxis  @arg_end"Constant*+ value**ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBend  D arg_start"Constant*- value*!*ûÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿBstart  5 0 arg_start arg_end arg_axis arg_step1"Sliceslice_step_dyn_testZ 0  b 1  B ROCm-AMDMIGraphX-46524e8/test/onnx/slice_test.onnx000066400000000000000000000002131510465702400215370ustar00rootroot00000000000000 slice-example:t < 01"Slice* axes@@ * ends@@ * starts@@  test-sliceZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/slice_var_input_default_steps.onnx000066400000000000000000000004341510465702400255160ustar00rootroot00000000000000 slice_var_input_default_steps:ô 0arg_step"Constant* value*:Bstep  3 data starts ends axes arg_stepoutput"Sliceslice_var_input_default_stepsZ data  Z starts  Z ends  Z axes  b output   BROCm-AMDMIGraphX-46524e8/test/onnx/slice_var_input_dyn0.onnx000066400000000000000000000003031510465702400235210ustar00rootroot00000000000000 slice_var_input_dyn0:¤ 2 data starts endsoutput"Slice* axes@@ slice_var_input_dyn0Z data  Z starts  Z ends  b output   BROCm-AMDMIGraphX-46524e8/test/onnx/slice_var_input_dyn1.onnx000066400000000000000000000003161510465702400235260ustar00rootroot00000000000000 slice_var_input_dyn1:¯ ) data starts ends axesoutput"Sliceslice_var_input_dyn1Z data  Z starts  Z ends  Z axes  b output   BROCm-AMDMIGraphX-46524e8/test/onnx/slice_var_input_static0.onnx000066400000000000000000000003131510465702400242170ustar00rootroot00000000000000 slice_var_input_static0:© 2 data starts endsoutput"Slice* axes@@ slice_var_input_static0Z data   Z starts  Z ends  b output   BROCm-AMDMIGraphX-46524e8/test/onnx/slice_var_input_static1.onnx000066400000000000000000000003261510465702400242240ustar00rootroot00000000000000 slice_var_input_static1:´ ) data starts ends axesoutput"Sliceslice_var_input_static1Z data   Z starts  Z ends  Z axes  b output   BROCm-AMDMIGraphX-46524e8/test/onnx/slice_var_input_steps_error.onnx000066400000000000000000000004321510465702400252210ustar00rootroot00000000000000 slice_var_input_steps_error:ô 0arg_step"Constant* value**Bstep  3 data starts ends axes arg_stepoutput"Sliceslice_var_input_steps_errorZ data   Z starts  Z ends  Z axes  b output   BROCm-AMDMIGraphX-46524e8/test/onnx/softmax_dyn_test.onnx000066400000000000000000000001631510465702400227770ustar00rootroot00000000000000softmax_dyn_test:Y  01"Softmaxsoftmax_dyn_testZ 0    b 1    B ROCm-AMDMIGraphX-46524e8/test/onnx/softmax_nonstd_input_test.onnx000066400000000000000000000002701510465702400247300ustar00rootroot00000000000000softmax_nonstd_input_test:” < 01"Slice* axes@@ * ends@@ * starts@@   12"Softmaxsoftmax_nonstd_input_testZ 0   b 2   BROCm-AMDMIGraphX-46524e8/test/onnx/softmax_test.onnx000066400000000000000000000001421510465702400221220ustar00rootroot00000000000000softmax-example:I  01"Softmax test-softmaxZ 0   b 1   BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_mean_reduction_bf16_test.onnx000066400000000000000000000003441510465702400320010ustar00rootroot00000000000000 3softmaxcrossentropyloss_2d_mean_reduction_bf16_test:¦ 8 0 12"SoftmaxCrossEntropyLoss* reduction"mean 3softmaxcrossentropyloss_2d_mean_reduction_bf16_testZ 0   Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_mean_reduction_double_test.onnx000066400000000000000000000003501510465702400325120ustar00rootroot00000000000000 5softmaxcrossentropyloss_2d_mean_reduction_double_test:¨ 8 0 12"SoftmaxCrossEntropyLoss* reduction"mean 5softmaxcrossentropyloss_2d_mean_reduction_double_testZ 0    Z 1  b 2   Bsoftmaxcrossentropyloss_2d_mean_reduction_double_weighted_test.onnx000066400000000000000000000004161510465702400343160ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx >softmaxcrossentropyloss_2d_mean_reduction_double_weighted_test:Å ; 0 1 23"SoftmaxCrossEntropyLoss* reduction"mean >softmaxcrossentropyloss_2d_mean_reduction_double_weighted_testZ 0    Z 1  Z 2   b 3   BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_mean_reduction_half_test.onnx000066400000000000000000000003441510465702400321550ustar00rootroot00000000000000 3softmaxcrossentropyloss_2d_mean_reduction_half_test:¦ 8 0 12"SoftmaxCrossEntropyLoss* reduction"mean 3softmaxcrossentropyloss_2d_mean_reduction_half_testZ 0    Z 1  b 2   BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_mean_reduction_test.onnx000066400000000000000000000003321510465702400311600ustar00rootroot00000000000000 .softmaxcrossentropyloss_2d_mean_reduction_test:¡ 8 0 12"SoftmaxCrossEntropyLoss* reduction"mean .softmaxcrossentropyloss_2d_mean_reduction_testZ 0   Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_mean_reduction_weighted_test.onnx000066400000000000000000000004001510465702400330340ustar00rootroot00000000000000 7softmaxcrossentropyloss_2d_mean_reduction_weighted_test:¾ ; 0 1 23"SoftmaxCrossEntropyLoss* reduction"mean 7softmaxcrossentropyloss_2d_mean_reduction_weighted_testZ 0   Z 1  Z 2  b 3  BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_no_reduction_asym_test.onnx000066400000000000000000000003401510465702400317040ustar00rootroot00000000000000 1softmaxcrossentropyloss_2d_no_reduction_asym_test:¤ 8 0 12"SoftmaxCrossEntropyLoss* reduction"none 1softmaxcrossentropyloss_2d_no_reduction_asym_testZ 0   Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_no_reduction_bf16_test.onnx000066400000000000000000000003401510465702400314710ustar00rootroot00000000000000 1softmaxcrossentropyloss_2d_no_reduction_bf16_test:¤ 8 0 12"SoftmaxCrossEntropyLoss* reduction"none 1softmaxcrossentropyloss_2d_no_reduction_bf16_testZ 0   Z 1  b 2  BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_no_reduction_double_test.onnx000066400000000000000000000003441510465702400322110ustar00rootroot00000000000000 3softmaxcrossentropyloss_2d_no_reduction_double_test:¦ 8 0 12"SoftmaxCrossEntropyLoss* reduction"none 3softmaxcrossentropyloss_2d_no_reduction_double_testZ 0    Z 1  b 2   BROCm-AMDMIGraphX-46524e8/test/onnx/softmaxcrossentropyloss_2d_no_reduction_double_weighted_test.onnx000066400000000000000000000004121510465702400340650ustar00rootroot00000000000000  #spacetodepth_invalid_blocksize_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/spacetodepth_nondivisibility_test.onnx000066400000000000000000000002611510465702400264250ustar00rootroot00000000000000!spacetodepth_nondivisibility_test:… & xy" SpaceToDepth* blocksize !spacetodepth_nondivisibility_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/spacetodepth_simple_test.onnx000066400000000000000000000002361510465702400245010ustar00rootroot00000000000000spacetodepth_simple_test:| & xy" SpaceToDepth* blocksize spacetodepth_simple_testZ x     b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/spacetodepth_test.onnx000066400000000000000000000002201510465702400231210ustar00rootroot00000000000000spacetodepth_test:u & xy" SpaceToDepth* blocksize spacetodepth_testZ x      b y     B ROCm-AMDMIGraphX-46524e8/test/onnx/split_dyn_input_dyn_split_axis_test.onnx000066400000000000000000000003071510465702400270010ustar00rootroot00000000000000 #split_dyn_input_dyn_split_axis_test:™ # xy1y2y3"Split* axis #split_dyn_input_dyn_split_axis_testZ x  b y1  b y2  b y3  BROCm-AMDMIGraphX-46524e8/test/onnx/split_dyn_input_fixed_split_axis_test.onnx000066400000000000000000000003131510465702400273030ustar00rootroot00000000000000 %split_dyn_input_fixed_split_axis_test:› # xy1y2y3"Split* axis %split_dyn_input_fixed_split_axis_testZ x  b y1  b y2  b y3  BROCm-AMDMIGraphX-46524e8/test/onnx/split_dyn_input_split_attr_test.onnx000066400000000000000000000003211510465702400261310ustar00rootroot00000000000000 split_dyn_input_split_attr_test:§ 5 xy1y2y3"Split* axis * split@@@ split_dyn_input_split_attr_testZ x  b y1  b y2  b y3  BROCm-AMDMIGraphX-46524e8/test/onnx/split_dyn_input_split_input_test.onnx000066400000000000000000000003711510465702400263230ustar00rootroot00000000000000  split_dyn_input_split_input_test:Î /split"Constant* value*:Bsplit  * x splity1y2y3"Split* axis  split_dyn_input_split_input_testZ x  b y1  b y2  b y3  BROCm-AMDMIGraphX-46524e8/test/onnx/split_minus_axis_test.onnx000066400000000000000000000002741510465702400240410ustar00rootroot00000000000000split_minus_axis_test:œ , xy1y2y3"Split* axisÿÿÿÿÿÿÿÿÿ split_minus_axis_testZ x   b y1   b y2   b y3   B ROCm-AMDMIGraphX-46524e8/test/onnx/split_test.onnx000066400000000000000000000002571510465702400216030ustar00rootroot00000000000000 split_test:š 5 xy1y2y3"Split* axis * split@@@  split_testZ x   b y1   b y2   b y3   B ROCm-AMDMIGraphX-46524e8/test/onnx/split_test_default.onnx000066400000000000000000000002051510465702400233000ustar00rootroot00000000000000split_test_default:i  xy1y2"Splitsplit_test_defaultZ x   b y1   b y2   B ROCm-AMDMIGraphX-46524e8/test/onnx/split_test_invalid_num_outputs.onnx000066400000000000000000000003461510465702400257720ustar00rootroot00000000000000 split_test_invalid_num_outputs:½ . xy1y2y3y4"Split* num_outputs split_test_invalid_num_outputsZ x   b y1   b y2   b y3   b y4   BROCm-AMDMIGraphX-46524e8/test/onnx/split_test_invalid_split.onnx000066400000000000000000000003131510465702400245150ustar00rootroot00000000000000split_test_invalid_split:¨ 5 xy1y2y3"Split* axis * split@@@ split_test_invalid_splitZ x   b y1   b y2   b y3   B ROCm-AMDMIGraphX-46524e8/test/onnx/split_test_no_attribute.onnx000066400000000000000000000003761510465702400243640ustar00rootroot00000000000000split_test_no_attribute:Ü 0split"Constant* value*:KKKKBsplit  ! x splity1y2y3y4"Splitsplit_test_no_attributeZ x  ¬ b y1  K b y2  K b y3  K b y4  K B ROCm-AMDMIGraphX-46524e8/test/onnx/split_test_no_attribute_invalid_input_split.onnx000066400000000000000000000003531510465702400305170ustar00rootroot00000000000000+split_test_no_attribute_invalid_input_split:µ / xy1y2y3"Split* axis * split +split_test_no_attribute_invalid_input_splitZ x   b y1   b y2   b y3   B ROCm-AMDMIGraphX-46524e8/test/onnx/split_test_no_attribute_invalid_split.onnx000066400000000000000000000004321510465702400272760ustar00rootroot00000000000000%split_test_no_attribute_invalid_split:ê 0split"Constant* value*:Bsplit  ! x splity1y2y3y4"Split%split_test_no_attribute_invalid_splitZ x  ¬ b y1  K b y2  K b y3  K b y4  K B ROCm-AMDMIGraphX-46524e8/test/onnx/split_test_uneven.onnx000066400000000000000000000003221510465702400231540ustar00rootroot00000000000000 split_test_uneven:¶  xy1y2y3y4y5"Splitsplit_test_unevenZ x   b y1   b y2   b y3   b y4   b y5   BROCm-AMDMIGraphX-46524e8/test/onnx/split_test_uneven_num_outputs.onnx000066400000000000000000000003441510465702400256420ustar00rootroot00000000000000 split_test_uneven_num_outputs:¼ . xy1y2y3y4"Split* num_outputs split_test_uneven_num_outputsZ x   b y1   b y2   b y3   b y4   BROCm-AMDMIGraphX-46524e8/test/onnx/sqrt_fp8_test.onnx000066400000000000000000000001361510465702400222120ustar00rootroot00000000000000  sqrt_fp8_test:G xy"Sqrt sqrt_fp8_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/sqrt_test.onnx000066400000000000000000000001311510465702400214300ustar00rootroot00000000000000 sqrt-example:C xy"Sqrt test_sqrtZ x   b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/squeeze_axes_input_test.onnx000066400000000000000000000002231510465702400243610ustar00rootroot00000000000000squeeze_axes_input_test:r  x axesy"Squeezesqueeze_axes_input_test*:BaxesZ x     b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/squeeze_empty_axes_test.onnx000066400000000000000000000002171510465702400243630ustar00rootroot00000000000000squeeze_empty_axes_test:n  x axesy"Squeezesqueeze_empty_axes_test* BaxesZ x     b y   B ROCm-AMDMIGraphX-46524e8/test/onnx/squeeze_unsqueeze_dyn_test.onnx000066400000000000000000000003151510465702400251020ustar00rootroot00000000000000squeeze_unsqueeze_dyn_test:¨ " 01"Squeeze* axes@@@@  $ 12" Unsqueeze* axes@@@@ squeeze_unsqueeze_dyn_testZ 0     b 2     B ROCm-AMDMIGraphX-46524e8/test/onnx/squeeze_unsqueeze_test.onnx000066400000000000000000000003151510465702400242300ustar00rootroot00000000000000squeeze_unsqueeze_test:¬ " 01"Squeeze* axes@@@@  $ 12" Unsqueeze* axes@@@@ squeeze_unsqueeze_testZ# 0       b# 2       BROCm-AMDMIGraphX-46524e8/test/onnx/sub_bcast_test.onnx000066400000000000000000000002471510465702400224140ustar00rootroot00000000000000 subtraction2: / 0 1out"Sub* axis * broadcast  subtraction2Z 0     Z 1   b out     BROCm-AMDMIGraphX-46524e8/test/onnx/sub_scalar_test.onnx000066400000000000000000000002651510465702400225650ustar00rootroot00000000000000sub_scalar_test:› 2 arg_const"Constant* value*"€?Bconst   0 arg_constout"Subsub_scalar_testZ 0     b out     B ROCm-AMDMIGraphX-46524e8/test/onnx/sum_int_test.onnx000066400000000000000000000002621510465702400221220ustar00rootroot00000000000000 sum_int_test:›  0c0"Cast* to    1c1"Cast* to    c0 c1 23"Sum sum_int_testZ 0  Z 1  Z 2   b 3   B ROCm-AMDMIGraphX-46524e8/test/onnx/sum_test.onnx000066400000000000000000000001661510465702400212530ustar00rootroot00000000000000 sum-example:a  0 1 23"Sumtest-sumZ 0  Z 1  Z 2  b 3  BROCm-AMDMIGraphX-46524e8/test/onnx/sum_type_test.onnx000066400000000000000000000010021510465702400223020ustar00rootroot00000000000000 sum_type_test:ê  boolo_bool"Cast* to    int8o_int8"Cast* to   ! uint8o_uint8"Cast* to   # uint16o_uint16"Cast* to   # uint32o_uint32"Cast* to   # uint64o_uint64"Cast* to   N o_bool o_int8 o_uint8 o_uint16 o_uint32 o_uint64 double rawout"Sum sum_type_test* *Bbool**Bint8**Buint8**Buint16* Buint32Z* Buint64Z* BdoubleRð?ð?* BrawJø?@b out   B ROCm-AMDMIGraphX-46524e8/test/onnx/tan_test.onnx000066400000000000000000000001161510465702400212240ustar00rootroot00000000000000 tan-example:9 xy"Tantest_tanZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/tanh_test.onnx000066400000000000000000000001211510465702400213700ustar00rootroot00000000000000 tanh-example:; xy"Tanh test_tanhZ x  b y  BROCm-AMDMIGraphX-46524e8/test/onnx/thresholdedrelu_default_test.onnx000066400000000000000000000002171510465702400253450ustar00rootroot00000000000000thresholdedrelu_default_test:i  xy"ThresholdedReluthresholdedrelu_default_testZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/thresholdedrelu_int_test.onnx000066400000000000000000000002301510465702400245060ustar00rootroot00000000000000thresholdedrelu_int_test:v ( xy"ThresholdedRelu* alpha@@ thresholdedrelu_int_testZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/thresholdedrelu_test.onnx000066400000000000000000000002201510465702400236330ustar00rootroot00000000000000thresholdedrelu_test:r ( xy"ThresholdedRelu* alpha@@ thresholdedrelu_testZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/tile_test.onnx000066400000000000000000000001671510465702400214050ustar00rootroot00000000000000 tile_test:d  x yz"Tile tile_test* :ByZ x   Z y  b z   B ROCm-AMDMIGraphX-46524e8/test/onnx/tile_test_3x2.onnx000066400000000000000000000001771510465702400221020ustar00rootroot00000000000000 tile_test_3x2:h  x yz"Tile tile_test_3x2* :ByZ x   Z y  b z   B ROCm-AMDMIGraphX-46524e8/test/onnx/topk_attrk_test.onnx000066400000000000000000000002631510465702400226270ustar00rootroot00000000000000topk_attrk_test:™ $ datavalindices"TopK* k topk_attrk_testZ data     b val     b! indices     B ROCm-AMDMIGraphX-46524e8/test/onnx/topk_neg_axis_test.onnx000066400000000000000000000003431510465702400232760ustar00rootroot00000000000000topk_neg_axis_test:Æ B data kvalindices"TopK* axisþÿÿÿÿÿÿÿÿ * sorted topk_neg_axis_test* :BkZ data     b val     b! indices     B ROCm-AMDMIGraphX-46524e8/test/onnx/topk_test.onnx000066400000000000000000000003111510465702400214140ustar00rootroot00000000000000 topk_test:µ : data kvalindices"TopK* axis * largest  topk_test* :BkZ data     b val     b! indices     B ROCm-AMDMIGraphX-46524e8/test/onnx/transpose_default_perm_test.onnx000066400000000000000000000002171510465702400252110ustar00rootroot00000000000000transpose_default_perm_test:j  01" Transposetranspose_default_perm_testZ 0     b 1     B ROCm-AMDMIGraphX-46524e8/test/onnx/transpose_dyn_test.onnx000066400000000000000000000002141510465702400233310ustar00rootroot00000000000000transpose_dyn_test:p $ 01" Transpose* perm@@@@ transpose_dyn_testZ 0    b 1    B ROCm-AMDMIGraphX-46524e8/test/onnx/transpose_gather_test.onnx000066400000000000000000000004411510465702400240130ustar00rootroot00000000000000gather-example:ˆ + datatdata" Transpose* perm@@@@  1 indicestindices" Transpose* perm@@@@  ) tdata tindicesy"Gather* axis  test_gatherZ data     Z! indices     b+ y& $        B ROCm-AMDMIGraphX-46524e8/test/onnx/transpose_invalid_perm_test.onnx000066400000000000000000000002401510465702400252070ustar00rootroot00000000000000transpose_invalid_perm_test:{ " 01" Transpose* perm@@@ transpose_invalid_perm_testZ 0     b 1     B ROCm-AMDMIGraphX-46524e8/test/onnx/transpose_test.onnx000066400000000000000000000002131510465702400224560ustar00rootroot00000000000000transpose-example:p $ 01" Transpose* perm@@@@ test-transposeZ 0     b 1     BROCm-AMDMIGraphX-46524e8/test/onnx/tril_batch_diff_k_test.onnx000066400000000000000000000002261510465702400240610ustar00rootroot00000000000000tril_batch_diff_k_test:v  x ky"Trilu* upper tril_batch_diff_k_test* :BkZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/tril_neg_k_test.onnx000066400000000000000000000002111510465702400225530ustar00rootroot00000000000000tril_neg_k_test:p  x ky"Trilu* upper tril_neg_k_test*: ÿÿÿÿÿÿÿÿÿBkZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/tril_out_k_test.onnx000066400000000000000000000002001510465702400226070ustar00rootroot00000000000000tril_out_k_test:g  x ky"Trilu* upper tril_out_k_test* :BkZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/tril_row_one_test.onnx000066400000000000000000000002041510465702400231420ustar00rootroot00000000000000tril_row_one_test:i  x ky"Trilu* upper tril_row_one_test* :BkZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/tril_test.onnx000066400000000000000000000001451510465702400214160ustar00rootroot00000000000000 tril_test:R  xy"Trilu* upper  tril_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/triu_batch_diff_k_test.onnx000066400000000000000000000002101510465702400240630ustar00rootroot00000000000000triu_batch_diff_k_test:h  x ky"Trilutriu_batch_diff_k_test* :BkZ x    b y    BROCm-AMDMIGraphX-46524e8/test/onnx/triu_neg_k_test.onnx000066400000000000000000000001731510465702400225730ustar00rootroot00000000000000triu_neg_k_test:b  x ky"Trilutriu_neg_k_test*: ÿÿÿÿÿÿÿÿÿBkZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/triu_out_k_test.onnx000066400000000000000000000001621510465702400226270ustar00rootroot00000000000000triu_out_k_test:Y  x ky"Trilutriu_out_k_test* :BkZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/triu_row_one_test.onnx000066400000000000000000000001661510465702400231620ustar00rootroot00000000000000triu_row_one_test:[  x ky"Trilutriu_row_one_test* :BkZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/triu_test.onnx000066400000000000000000000001271510465702400214270ustar00rootroot00000000000000 triu_test:D xy"Trilu triu_testZ x   b y   BROCm-AMDMIGraphX-46524e8/test/onnx/undefined_test.onnx000066400000000000000000000001631510465702400224050ustar00rootroot00000000000000undefined_test:[  1"Identityundefined_testZ 0     b 1     B ROCm-AMDMIGraphX-46524e8/test/onnx/unique_dynamic_sorted_3D_test.onnx000066400000000000000000000003761510465702400253720ustar00rootroot00000000000000 unique_dynamic_sorted_3D_test:Ö ? XYindicesinverse_indicescounts"Unique* sorted unique_dynamic_sorted_3D_testZ X    b Y  b indices  b inverse_indices  @b counts  BROCm-AMDMIGraphX-46524e8/test/onnx/unique_dynamic_sorted_test.onnx000066400000000000000000000003751510465702400250430ustar00rootroot00000000000000 unique_dynamic_sorted_test:Ø L XYindicesinverse_indicescounts"Unique* axis * sorted unique_dynamic_sorted_testZ X  b Y  b indices  b inverse_indices  b counts  BROCm-AMDMIGraphX-46524e8/test/onnx/unique_dynamic_unsorted_test.onnx000066400000000000000000000004011510465702400253740ustar00rootroot00000000000000 unique_dynamic_unsorted_test:Ú L XYindicesinverse_indicescounts"Unique* axis * sorted unique_dynamic_unsorted_testZ X  b Y  b indices  b inverse_indices  b counts  BROCm-AMDMIGraphX-46524e8/test/onnx/unique_sorted_test.onnx000066400000000000000000000003771510465702400233410ustar00rootroot00000000000000 unique_sorted_test:â L XYindicesinverse_indicescounts"Unique* axis * sorted unique_sorted_test*!"@€?€?@@€@@@BXb Y  b indices  b inverse_indices  b counts  BROCm-AMDMIGraphX-46524e8/test/onnx/unique_unsorted_test.onnx000066400000000000000000000004031510465702400236720ustar00rootroot00000000000000 unique_unsorted_test:ä L XYindicesinverse_indicescounts"Unique* axis * sorted unique_unsorted_test*!"@€?€?@@€@@@BXb Y  b indices  b inverse_indices  b counts  BROCm-AMDMIGraphX-46524e8/test/onnx/unknown_aten_test.onnx000066400000000000000000000002471510465702400231550ustar00rootroot00000000000000unknown_aten_test:‹ ' 0 12"ATen* operator"unknown unknown_aten_testZ 0     Z 1   b 3     B ROCm-AMDMIGraphX-46524e8/test/onnx/unknown_test.onnx000066400000000000000000000002341510465702400221420ustar00rootroot00000000000000unknown-example:‚  0 12"Unknown  23"Unknown test-unknownZ 0     Z 1   b 3     BROCm-AMDMIGraphX-46524e8/test/onnx/upsample_linear_test.onnx000066400000000000000000000002561510465702400236270ustar00rootroot00000000000000upsample_linear_test: - X scalesY"Upsample* mode"linear upsample_linear_test*"€?€?@@BscalesZ X     b Y B ROCm-AMDMIGraphX-46524e8/test/onnx/upsample_test.onnx000066400000000000000000000002571510465702400222760ustar00rootroot00000000000000  upsample_test:— , X scalesY"Upsample* mode"nearest  upsample_test*"€?€?@@@BscalesZ X     b Y     BROCm-AMDMIGraphX-46524e8/test/onnx/upsample_ver7_test.onnx000066400000000000000000000002621510465702400232350ustar00rootroot00000000000000 upsample_ver7_test:• E XY"Upsample* mode"nearest * scales=€?=€?=@=@@ upsample_ver7_testZ X     b Y     BROCm-AMDMIGraphX-46524e8/test/onnx/variable_batch_leq_zero_test.onnx000066400000000000000000000002761510465702400252770ustar00rootroot00000000000000variable_batch_leq_zero_test:—  0 12"Addvariable_batch_leq_zero_testZ 0     Z$ 1  ÿÿÿÿÿÿÿÿÿ   b$ 2  ÿÿÿÿÿÿÿÿÿ   B ROCm-AMDMIGraphX-46524e8/test/onnx/variable_batch_test.onnx000066400000000000000000000001721510465702400233720ustar00rootroot00000000000000variable_batch_test:]  01"Identityvariable_batch_testZ 0    b 1    B ROCm-AMDMIGraphX-46524e8/test/onnx/verify/000077500000000000000000000000001510465702400200055ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/onnx/verify/add_bf16_test.cpp000066400000000000000000000037041510465702400231220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(add_bf16_test) { auto p = optimize_onnx("add_bf16_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::bf16_type, {1}}; migraphx::parameter_map pp; migraphx::literal l1{s, {3.25}}; migraphx::literal l2{s, {2.25}}; pp["0"] = l1.get_argument(); pp["1"] = l2.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{static_cast(5.5)}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/add_fp8_test.cpp000066400000000000000000000037571510465702400230710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(add_fp8_test) { auto p = optimize_onnx("add_fp8_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::fp8e4m3fnuz_type, {1}}; migraphx::parameter_map pp; migraphx::literal l1{s, {3.25}}; migraphx::literal l2{s, {2.25}}; pp["0"] = l1.get_argument(); pp["1"] = l2.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{static_cast(5.5)}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/attention_double_head_asym_test.cpp000066400000000000000000000206701510465702400271260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(attention_double_head_bias_mask_asym_test1) { auto p = optimize_onnx("attention_double_head_bias_asym_mask_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 3, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.424442f, 0.391239f, 0.895206f, 0.329658f, 0.806407f, 0.841954f, 0.166734f, 0.3947177f, 0.769718f, 0.1397568f, 0.1470478f, 0.8689365f, 0.0794171f, 0.3475261f, 0.32916f, 0.8890806f, 0.2026632f, 0.9164701f, 0.381084f, 0.3943821f, 0.1804103f, 0.3630361f, 0.217396f, 0.71882147f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.5328653f, 0.990889f, 0.3436559f, 0.0316787f, 0.710585f, 0.2747402f, 0.9005202f, 0.1460364f, 0.0912429f, 0.2431252f, 0.583230f, 0.1790725f, 0.436488f, 0.857843f, 0.9507031f, 0.7371746f, 0.4792084f, 0.3621650f, 0.266098f, 0.0429294f, 0.8168309f, 0.878928f, 0.777824f, 0.2294480f, 0.619469f, 0.8848825f, 0.5321201f, 0.261897f, 0.0185219f, 0.1786854f, 0.5919701f, 0.636265f, 0.047953f, 0.895432f, 0.4190886f, 0.870233f, 0.0969924f, 0.718856f, 0.4210773f, 0.885644f, 0.5691978f, 0.626694f, 0.255162f, 0.7583785f, 0.638615f, 0.810659f, 0.573155f, 0.73166305f}; // Make Bias all zero since ONNX-RT doesn't have non-biased working (CPU/ROCm EP results in // segfault) using zero bias allows us to ensure the scale dot attention head is correct before // testing with batching/multiple heads/additional parameters migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = {0.64068f, 0.740358f, 0.4191080f, 0.4902010f, 0.41921f, 0.306204f, 0.5357299f, 0.276288f, 0.311639f, 0.271845f, 0.982978f, 0.998660f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {2, 3}}; std::vector mask_data = {0, 1, 0, 0, 1, 1}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from Accuracy checker with CPU and ROCm EP std::vector gold = {1.333021f, 1.677202f, 2.40430f, 1.770149f, 1.333021f, 1.677202f, 2.40430f, 1.770149f, 1.333021f, 1.677202f, 2.40430f, 1.770149f, 1.229508f, 1.6116f, 2.03985f, 1.847749f, 1.23127f, 1.614291f, 2.044070f, 1.848215f, 1.229239f, 1.61129f, 2.040097f, 1.8477759f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(attention_double_head_asym_mask_test2) { auto p = optimize_onnx("attention_double_head_bias_asym_mask_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 3, 4}}; // Inputs generated via Accuracy checker tool with CPU/ ROCm EP std::vector input_data = {0.563641f, 0.596089f, 0.6873016f, 0.2283578f, 0.586944f, 0.3032279f, 0.0856082f, 0.2830461f, 0.4797580f, 0.5876359f, 0.1328294f, 0.657682f, 0.1587376f, 0.1097770f, 0.9658276f, 0.3364122f, 0.936998f, 0.902808f, 0.4177504f, 0.9092307f, 0.4622655f, 0.631753f, 0.0453660f, 0.990014f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.550476f, 0.2990797f, 0.3499473f, 0.502457f, 0.8914552f, 0.6897412f, 0.350504f, 0.838558f, 0.966813f, 0.8194670f, 0.3466599f, 0.10317f, 0.0214707f, 0.3867859f, 0.991419f, 0.963052f, 0.640068f, 0.5958870f, 0.2184076f, 0.7763474f, 0.0521373f, 0.0766689f, 0.815702f, 0.3086021f, 0.2658478f, 0.954776f, 0.5995340f, 0.1700607f, 0.0902024f, 0.4917013f, 0.6761719f, 0.1209063f, 0.887309f, 0.1192176f, 0.4385506f, 0.313657f, 0.0626774f, 0.3990616f, 0.5720665f, 0.5780725f, 0.0506778f, 0.4353910f, 0.463869f, 0.449338f, 0.673546f, 0.9808122f, 0.977306f, 0.76408f}; // Make Bias all zero since ONNX-RT doesn't have non-biased working (CPU/ROCm EP results in // segfault) using zero bias allows us to ensure the scale dot attention head is correct before // testing with batching/multiple heads/additional parameters migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data{0.2705385f, 0.253350f, 0.4282024f, 0.8318862f, 0.957445f, 0.1495394f, 0.05389f, 0.6314289f, 0.5862025f, 0.820197f, 0.3228435f, 0.15375982f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {2, 3}}; std::vector mask_data = {0, 1, 1, 0, 0, 1}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data accuracy checker run against ROCm and CPU EP std::vector gold = {1.556113f, 1.791649f, 1.477053f, 0.8038040f, 1.548904f, 1.780873f, 1.45414f, 0.788925f, 1.551593f, 1.784892f, 1.477318f, 0.803976f, 1.773139f, 2.22387f, 1.985858f, 1.16709f, 1.773139f, 2.22387f, 1.985858f, 1.16709f, 1.773139f, 2.22387f, 1.985858f, 1.16709f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/attention_double_head_batch1_test.cpp000066400000000000000000000066631510465702400273250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(attention_double_head_batch1_test) { auto p = optimize_onnx("attention_double_head_batch1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {1, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.8f, -0.5f, 0.0f, 1.f, 0.5f, 0.2f, 0.3f, -0.6f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = {0.1f, -0.2f, 0.3f, 1.0f, 1.1f, 0.3f, 0.5f, 0.2f, 0.3f, -0.6f, 1.5f, 2.0f, 0.5f, 0.1f, 0.4f, 1.6f, 1.0f, 2.0f, 0.4f, 0.8f, 0.9f, 0.1f, -1.3f, 0.7f, 0.3f, 0.2f, 4.0f, 2.2f, 1.6f, 1.1f, 0.7f, 0.2f, 0.4f, 1.0f, 1.2f, 0.5f, 0.2f, 0.1f, 0.4f, 1.6f, 2.4f, 3.3f, 2.1f, 4.2f, 8.4f, 0.0f, 2.1f, 3.2f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = { -0.5f, 0.6f, 1.2f, 2.1f, 0.5f, 0.7f, 0.2f, 1.2f, 0.5f, 0.4f, 0.3f, 1.2f}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = {3.1495983600616455f, 0.10843668878078461f, 4.25f, 5.6499996185302734f, 3.9696791172027588f, 0.073143675923347473f, 4.2499995231628418f, 5.6499991416931152f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/attention_double_head_bias_asym_mask_filter_val_test.cpp000066400000000000000000000112031510465702400333360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(attention_double_head_bias_asym_mask_filter_val_test) { auto p = optimize_onnx("attention_double_head_bias_asym_mask_filter_val_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 3, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.900670f, 0.448478f, 0.603542f, 0.5393899f, 0.4175282f, 0.98499f, 0.3315366f, 0.671495f, 0.4700915f, 0.371528f, 0.547780f, 0.935466f, 0.539521f, 0.4525090f, 0.722473f, 0.0401732f, 0.4163277f, 0.748433f, 0.837911f, 0.0789781f, 0.6254225f, 0.621954f, 0.3697444f, 0.13887641f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.7571687f, 0.2359257f, 0.3641960f, 0.7332285f, 0.2369418f, 0.7827402f, 0.0390439f, 0.732264f, 0.549397f, 0.1185614f, 0.8941f, 0.844365f, 0.9183342f, 0.2338227f, 0.1152721f, 0.0582466f, 0.629299f, 0.608058f, 0.8386450f, 0.2993283f, 0.369621f, 0.510179f, 0.951667f, 0.5053690f, 0.1303317f, 0.0906144f, 0.771531f, 0.705696f, 0.1723916f, 0.5167796f, 0.0805798f, 0.1879250f, 0.6520960f, 0.3181830f, 0.948946f, 0.59911f, 0.621624f, 0.1569286f, 0.1357425f, 0.5584053f, 0.5210206f, 0.5264289f, 0.795218f, 0.1389356f, 0.729620f, 0.716727f, 0.5536462f, 0.992480f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data{0.846411f, 0.1964233f, 0.719782f, 0.1685972f, 0.5629161f, 0.669332f, 0.755575f, 0.4544251f, 0.4017672f, 0.7687284f, 0.0021288f, 0.534924f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {2, 3}}; std::vector mask_data = {0, 0, 1, 1, 1, 1}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = {1.837100f, 1.858778f, 1.813764f, 2.376228f, 1.837100f, 1.858778f, 1.813764f, 2.376228f, 1.837100f, 1.858778f, 1.813764f, 2.3762286f, 1.405702f, 1.417828f, 1.722106f, 1.765331f, 1.406624f, 1.419057f, 1.722792f, 1.765720f, 1.40663f, 1.41908f, 1.720432f, 1.7643524f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/attention_double_head_bias_asym_mask_scale_test.cpp000066400000000000000000000107701510465702400323060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(attention_double_head_bias_asym_mask_scale_test) { auto p = optimize_onnx("attention_double_head_bias_asym_mask_scale_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 3, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = { 0.0029872f, 0.880535f, 0.766311f, 0.9520083f, 0.09028f, 0.754762f, 0.2297131f, 0.26980f, 0.3421925f, 0.738659f, 0.661078f, 0.3622329f, 0.024412f, 0.5054599f, 0.1434262f, 0.62552f, 0.553510f, 0.441841f, 0.437590f, 0.587748f, 0.595663f, 0.473892f, 0.504267f, 0.505959f, }; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.051867f, 0.348283f, 0.088763f, 0.9173639f, 0.38668f, 0.0204705f, 0.349724f, 0.0139212f, 0.6570288f, 0.901269f, 0.4489016f, 0.5793316f, 0.388074f, 0.3070760f, 0.0078492f, 0.4900729f, 0.924945f, 0.0638553f, 0.3836499f, 0.1902723f, 0.0636417f, 0.2222438f, 0.9386154f, 0.2971302f, 0.2408046f, 0.2437261f, 0.155429f, 0.8300618f, 0.773222f, 0.9903515f, 0.2512911f, 0.504405f, 0.0147009f, 0.708178f, 0.9458579f, 0.4706725f, 0.1297135f, 0.0521970f, 0.0465745f, 0.2692676f, 0.849494f, 0.565883f, 0.982996f, 0.44857f, 0.2589197f, 0.2094598f, 0.5639232f, 0.48233464f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data{0.3945137f, 0.959043f, 0.4768401f, 0.644274f, 0.1149479f, 0.2881487f, 0.142187f, 0.797775f, 0.816875f, 0.774307f, 0.664817f, 0.818296f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {2, 3}}; std::vector mask_data = {0, 0, 1, 1, 1, 1}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = {1.19222f, 1.790912f, 2.341303f, 1.721886f, 1.19222f, 1.790912f, 2.341303f, 1.721886f, 1.19222f, 1.790912f, 2.341303f, 1.7218862f, 1.263277f, 1.619741f, 1.953595f, 1.635238f, 1.264119f, 1.621471f, 1.954919f, 1.636430f, 1.264281f, 1.62180f, 1.955085f, 1.6365795f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/attention_double_head_bias_mask_test.cpp000066400000000000000000000706141510465702400301110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(attention_double_head_bias_mask_batch1_passthrough_mask_test) { auto p = optimize_onnx("attention_double_head_bias_mask_batch1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {1, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.836348f, 0.3581245f, 0.4475229f, 0.5970712f, 0.598615f, 0.2951546f, 0.2121896f, 0.09142321}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.7183016f, 0.577417f, 0.3676456f, 0.878583f, 0.8927927f, 0.3989088f, 0.2036403f, 0.4419216f, 0.508894f, 0.2171135f, 0.3508348f, 0.3410549f, 0.8376661f, 0.896282f, 0.750656f, 0.9309627f, 0.1768383f, 0.831951f, 0.7498408f, 0.9859409f, 0.669683f, 0.387659f, 0.397969f, 0.82948f, 0.776944f, 0.2565615f, 0.5149419f, 0.3952615f, 0.467096f, 0.69897f, 0.764025f, 0.5253237f, 0.9064581f, 0.588643f, 0.1440694f, 0.2109225f, 0.645562f, 0.7252325f, 0.972994f, 0.463554f, 0.9931276f, 0.6490803f, 0.4276592f, 0.22789f, 0.7321448f, 0.89267f, 0.3418082f, 0.36943f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data(12, 0.0f); migraphx::shape mask_shape{migraphx::shape::int32_type, {1, 2}}; std::vector mask_data = {1, 1}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = { 1.380390f, 1.002805f, 0.6146411f, 0.791859f, 1.28671f, 0.919257f, 0.5846517f, 0.756680f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(attention_double_head_bias_mask_batch1_last_mask_test) { auto p = optimize_onnx("attention_double_head_bias_mask_batch1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {1, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = { 0.2365631f, 0.2897484f, 0.461962f, 0.547217f, 0.0343506f, 0.217421f, 0.2617837f, 0.642011f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.740227f, 0.4794638f, 0.7512885f, 0.850956f, 0.1834944f, 0.0166223f, 0.506032f, 0.307133f, 0.809147f, 0.4110304f, 0.0074044f, 0.4610349f, 0.4528455f, 0.7661606f, 0.435064f, 0.88811f, 0.424238f, 0.2526340f, 0.3349141f, 0.896244f, 0.2684531f, 0.4195141f, 0.234627f, 0.3939499f, 0.5542862f, 0.668919f, 0.6118851f, 0.4075400f, 0.2731890f, 0.632885f, 0.3904807f, 0.5439004f, 0.118005f, 0.625134f, 0.4839466f, 0.3752088f, 0.5442267f, 0.812204f, 0.687263f, 0.1925360f, 0.822273f, 0.8223247f, 0.9237169f, 0.961031f, 0.1192676f, 0.88399f, 0.7545891f, 0.42134193}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = {0.561730f, 0.3377643f, 0.2519498f, 0.2072306f, 0.5650865f, 0.347968f, 0.1985114f, 0.6656775f, 0.3533229f, 0.5744964f, 0.5860762f, 0.76156753f}; // 0 = mask,1 = pass through migraphx::shape mask_shape{migraphx::shape::int32_type, {1, 2}}; std::vector mask_data = {1, 0}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = { 0.7423008f, 1.565812f, 1.292300f, 1.388675f, 0.7423008f, 1.565812f, 1.292300f, 1.388675f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(attention_double_head_bias_mask_batch1_first_mask_test) { auto p = optimize_onnx("attention_double_head_bias_mask_batch1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {1, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.4530325f, 0.0649147f, 0.127795f, 0.3183984f, 0.589117f, 0.5000232f, 0.3351452f, 0.15613051f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.778988f, 0.7876636f, 0.0258449f, 0.4360487f, 0.131283f, 0.859008f, 0.985208f, 0.3964851f, 0.4431120f, 0.714212f, 0.7847860f, 0.5370770f, 0.952801f, 0.7038981f, 0.593976f, 0.3858227f, 0.2331966f, 0.7834450f, 0.914282f, 0.923162f, 0.612781f, 0.0974504f, 0.6551792f, 0.0908233f, 0.2610949f, 0.5107522f, 0.2366336f, 0.1124516f, 0.0359591f, 0.7155804f, 0.643442f, 0.5108542f, 0.9312866f, 0.379560f, 0.244760f, 0.86605f, 0.0146525f, 0.3647071f, 0.6877476f, 0.651158f, 0.3327819f, 0.4332133f, 0.0284249f, 0.2950900f, 0.868766f, 0.167157f, 0.5624779f, 0.96631414f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = {0.71639f, 0.6984057f, 0.641569f, 0.5584603f, 0.807863f, 0.9522143f, 0.964991f, 0.0536422f, 0.6487126f, 0.924860f, 0.73642f, 0.85529f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {1, 2}}; std::vector mask_data = {0, 1}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = { 1.6639f, 1.547649f, 1.696211f, 1.658239f, 1.6639f, 1.547649f, 1.696211f, 1.6582391f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(attention_double_head_bias_mask_batch1_all_mask_test) { auto p = optimize_onnx("attention_double_head_bias_mask_batch1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {1, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = { 0.550855f, 0.9449020f, 0.4138677f, 0.609427f, 0.0278122f, 0.929195f, 0.72592f, 0.98518866f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.1919869f, 0.97087f, 0.0650908f, 0.4648978f, 0.784916f, 0.866638f, 0.9574522f, 0.618995f, 0.333331f, 0.465269f, 0.945676f, 0.1423377f, 0.913690f, 0.472937f, 0.739001f, 0.3276381f, 0.8793762f, 0.883888f, 0.7565744f, 0.607640f, 0.418141f, 0.471184f, 0.4038756f, 0.893745f, 0.445011f, 0.929533f, 0.0949846f, 0.690924f, 0.9738619f, 0.822521f, 0.4315533f, 0.884698f, 0.0233254f, 0.5688004f, 0.3722963f, 0.3832031f, 0.506166f, 0.1660704f, 0.4618730f, 0.0794496f, 0.0844472f, 0.6387809f, 0.7633692f, 0.7961660f, 0.786498f, 0.71053f, 0.344582f, 0.44591686f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = {0.751496f, 0.557292f, 0.6720010f, 0.1879267f, 0.352546f, 0.600021f, 0.0552079f, 0.5959239f, 0.0404032f, 0.1882552f, 0.2718655f, 0.84921235f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {1, 2}}; std::vector mask_data = {0, 0}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = { 1.166096f, 1.650388f, 1.406106f, 2.30548f, 1.165592f, 1.649586f, 1.406728f, 2.304997}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(attention_double_head_bias_mask_batch2_test) { auto p = optimize_onnx("attention_double_head_bias_mask_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.82405f, 0.4764252f, 0.377304f, 0.4109286f, 0.2003798f, 0.767443f, 0.0175668f, 0.7279092f, 0.294912f, 0.774890f, 0.8893084f, 0.71005f, 0.2514481f, 0.768277f, 0.3042429f, 0.47951823f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.1694838f, 0.9370f, 0.0493409f, 0.0579776f, 0.4779790f, 0.121594f, 0.4990379f, 0.224993f, 0.7370581f, 0.2758335f, 0.6196964f, 0.2212499f, 0.2567361f, 0.9864421f, 0.1344247f, 0.0343491f, 0.997883f, 0.0671764f, 0.745450f, 0.0054291f, 0.507925f, 0.6584495f, 0.576835f, 0.5474540f, 0.3751728f, 0.820518f, 0.0764405f, 0.9172129f, 0.3914677f, 0.1670402f, 0.651346f, 0.0744788f, 0.9613833f, 0.1876087f, 0.5287391f, 0.1745295f, 0.4547757f, 0.005437f, 0.267799f, 0.709826f, 0.1078507f, 0.603933f, 0.50390f, 0.2069911f, 0.4061629f, 0.0048359f, 0.3187260f, 0.13678697}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = {0.78651f, 0.809411f, 0.982877f, 0.3211297f, 0.0238379f, 0.640969f, 0.9006f, 0.1837699f, 0.529567f, 0.927664f, 0.5510397f, 0.83173823f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {2, 2}}; std::vector mask_data = {0, 0, 0, 0}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = {1.642277f, 1.518098f, 1.52985f, 1.397790f, 1.646151f, 1.518438f, 1.529783f, 1.397790f, 2.06642f, 1.649505f, 1.733022f, 1.522298f, 2.042337f, 1.645073f, 1.724378f, 1.5192288f}; // Adjust tolerance on this since "all mask" case is not common but also likely due to use // adding -10000 to numbers and then doing the softmax , resulting in loss of precision // Other mask values seem to be valid from 1e-5 range EXPECT(migraphx::verify::verify_rms_range(result_vector, gold, 200)); } TEST_CASE(attention_double_head_bias_mask_batch2_all_pass_test) { auto p = optimize_onnx("attention_double_head_bias_mask_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.2773895f, 0.5486851f, 0.451570f, 0.99696f, 0.406118f, 0.4839315f, 0.755350f, 0.8436214f, 0.6108430f, 0.1231699f, 0.46070f, 0.7012386f, 0.759620f, 0.2762067f, 0.7038193f, 0.569161f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.3845310f, 0.9619347f, 0.4936712f, 0.6283104f, 0.841628f, 0.350351f, 0.4579307f, 0.2874027f, 0.2029373f, 0.4477961f, 0.2471958f, 0.624741f, 0.3650294f, 0.4590854f, 0.2959658f, 0.4803923f, 0.9737639f, 0.4497354f, 0.830746f, 0.3309565f, 0.4202715f, 0.930299f, 0.998847f, 0.9334816f, 0.8441676f, 0.52829f, 0.4335649f, 0.2783789f, 0.9052453f, 0.0463431f, 0.975376f, 0.6249313f, 0.8802423f, 0.699552f, 0.638825f, 0.959790f, 0.8345969f, 0.1295447f, 0.616319f, 0.433175f, 0.0142569f, 0.4430008f, 0.1237481f, 0.25647f, 0.369231f, 0.5137827f, 0.0363198f, 0.13236965f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = {0.612893f, 0.777793f, 0.3126879f, 0.4798205f, 0.776248f, 0.5968348f, 0.459095f, 0.1061234f, 0.807113f, 0.3901378f, 0.5912092f, 0.04816633f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {2, 2}}; std::vector mask_data = {1, 1, 1, 1}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = {1.986912f, 1.932524f, 1.627824f, 1.477383f, 1.988329f, 1.93341f, 1.628718f, 1.479056f, 1.823226f, 1.671619f, 1.403065f, 1.369300f, 1.828217f, 1.677599f, 1.408375f, 1.3763521f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(attention_double_head_bias_mask_batch2_diag_test) { auto p = optimize_onnx("attention_double_head_bias_mask_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.872664f, 0.5373890f, 0.4955406f, 0.3421586f, 0.2306984f, 0.826305f, 0.4227518f, 0.567517f, 0.1019400f, 0.8496197f, 0.895266f, 0.136645f, 0.9690335f, 0.5001573f, 0.932205f, 0.67461586f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.0345085f, 0.4419922f, 0.5818482f, 0.2626504f, 0.348432f, 0.0842294f, 0.182248f, 0.968666f, 0.418183f, 0.0105558f, 0.6986581f, 0.1786073f, 0.1195501f, 0.4313392f, 0.6098367f, 0.662050f, 0.469720f, 0.0641924f, 0.1400848f, 0.8684591f, 0.4107752f, 0.2899719f, 0.7876609f, 0.634067f, 0.367954f, 0.1173279f, 0.858620f, 0.663137f, 0.2342181f, 0.1797239f, 0.918695f, 0.4486411f, 0.0721015f, 0.731420f, 0.769667f, 0.617797f, 0.2715653f, 0.6987223f, 0.0972901f, 0.2079825f, 0.0658583f, 0.330616f, 0.3264711f, 0.4618025f, 0.68533f, 0.0782526f, 0.0477516f, 0.08156187f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = {0.9885801f, 0.2215420f, 0.4647936f, 0.629216f, 0.1154782f, 0.5222471f, 0.734802f, 0.2114844f, 0.333944f, 0.2471313f, 0.7204917f, 0.433187f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {2, 2}}; std::vector mask_data = {1, 0, 0, 1}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = {1.189846f, 0.801394f, 2.151206f, 1.26384f, 1.189846f, 0.801394f, 2.151206f, 1.26384f, 1.474180f, 1.137016f, 2.541171f, 1.554334f, 1.474180f, 1.137016f, 2.541171f, 1.5543344f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(attention_double_head_bias_mask_batch2_off_diag_test) { auto p = optimize_onnx("attention_double_head_bias_mask_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.2540857f, 0.0256114f, 0.6211387f, 0.4503982f, 0.4017042f, 0.555549f, 0.896553f, 0.4662611f, 0.179541f, 0.0961751f, 0.715316f, 0.2744848f, 0.1165528f, 0.790455f, 0.5210529f, 0.30495727f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.3110327f, 0.854108f, 0.816163f, 0.960607f, 0.004508f, 0.4068033f, 0.9540065f, 0.975203f, 0.9766478f, 0.9929510f, 0.9142452f, 0.4771942f, 0.1567466f, 0.2185167f, 0.0457491f, 0.773614f, 0.6616419f, 0.595781f, 0.3763947f, 0.3090001f, 0.1318927f, 0.373421f, 0.3436092f, 0.362673f, 0.8083966f, 0.5987018f, 0.0699282f, 0.8792643f, 0.910243f, 0.0060881f, 0.4817900f, 0.5833030f, 0.5045705f, 0.2815545f, 0.904394f, 0.9449780f, 0.4684918f, 0.1758799f, 0.0536868f, 0.6681251f, 0.5750992f, 0.747650f, 0.809778f, 0.899023f, 0.371502f, 0.1342116f, 0.4682391f, 0.886880f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = {0.0209900f, 0.8722936f, 0.2948170f, 0.689064f, 0.1642694f, 0.758328f, 0.606924f, 0.099455f, 0.4234262f, 0.2398736f, 0.692330f, 0.950954f}; migraphx::shape mask_shape{migraphx::shape::int32_type, {2, 2}}; std::vector mask_data = {0, 1, 1, 0}; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; migraphx::literal mask{mask_shape, mask_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); pp["mask_index"] = mask.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = {1.514614f, 1.161206f, 2.279638f, 2.6048f, 1.514614f, 1.161206f, 2.279638f, 2.6048f, 1.074359f, 0.692303f, 1.66497f, 1.9909048f, 1.074359f, 0.692303f, 1.66497f, 1.9909048f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/attention_double_head_test.cpp000066400000000000000000000114311510465702400260700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(attention_double_head_batch2_test) { auto p = optimize_onnx("attention_double_head_bias_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.09382452f, 0.91307306f, 0.0450552f, 0.4325229f, 0.6958981f, 0.08963848f, 0.78016704f, 0.6676332f, 0.640169f, 0.79590225f, 0.96877795f, 0.61622876f, 0.93758523f, 0.03348486f, 0.04337639f, 0.7014834f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.03996459f, 0.47680363f, 0.9262067f, 0.3939682f, 0.5910101f, 0.9245723f, 0.8277026f, 0.8104753f, 0.6631966f, 0.9758513f, 0.9751434f, 0.17177974f, 0.38831252f, 0.8211175f, 0.43048874f, 0.78231025f, 0.17060293f, 0.38664082f, 0.7114742f, 0.54639995f, 0.07888859f, 0.85774803f, 0.00584858f, 0.96129435f, 0.04915032f, 0.25172415f, 0.50097096f, 0.27563626f, 0.8513271f, 0.4637983f, 0.5921937f, 0.35282055f, 0.49395546f, 0.81918603f, 0.10324866f, 0.46248904f, 0.77152f, 0.24577223f, 0.2999466f, 0.64094317f, 0.5150664f, 0.4463948f, 0.8547919f, 0.17810917f, 0.0822628f, 0.61511374f, 0.44340402f, 0.34248054f}; migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data = { 0.28379261f, 0.2949806f, 0.5498075f, 0.4487797f, 0.6147562f, 0.01953102f, 0.11069784f, 0.8256148f, 0.4151909f, 0.9846566f, 0.61103475f, 0.9475537f, }; migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from AttentionNoMaskIndex from attention_op_test.cc from Onnxruntime std::vector gold = {1.1643721f, 2.6504831f, 1.4329824f, 1.8247899f, 1.1484636f, 2.636544f, 1.4694388f, 1.8119928f, 1.350189f, 3.1860104f, 1.6439152f, 2.3360693f, 1.3286103f, 3.1122856f, 1.6598269f, 2.2605965f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/attention_single_head_test.cpp000066400000000000000000000163221510465702400261030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(attention_single_head_batch1_test) { auto p = optimize_onnx("attention_single_head_batch1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {1, 2, 4}}; // Taken from attention_op_test.cc from Onnxruntime repo std::vector input_data = {0.552612f, 0.0190766f, 0.1843486f, 0.785303f, 0.3964166f, 0.8213836f, 0.3575855f, 0.553267f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.2094991f, 0.1440841f, 0.7808530f, 0.9267818f, 0.5590372f, 0.307970f, 0.5298086f, 0.926752f, 0.5599791f, 0.3038375f, 0.756784f, 0.1927229f, 0.773385f, 0.713124f, 0.1607708f, 0.612250f, 0.797840f, 0.7062700f, 0.1150899f, 0.459506f, 0.5240009f, 0.750125f, 0.4189021f, 0.9421159f, 0.487010f, 0.714613f, 0.1044489f, 0.720409f, 0.3246490f, 0.8285f, 0.5867274f, 0.3617129f, 0.0103818f, 0.3994490f, 0.789325f, 0.0984387f, 0.601747f, 0.3892322f, 0.723926f, 0.2909479f, 0.0136847f, 0.7251270f, 0.4336005f, 0.4485420f, 0.4666978f, 0.9596705f, 0.9241839f, 0.973919f}; // Make Bias all zero since ONNX-RT doesn't have non-biased working (CPU/ROCm EP results in // segfault) using zero bias allows us to ensure the scale dot attention head is correct before // testing with batching/multiple heads/additional parameters migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data(12, 0.0f); migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data from Accuracy checker with CPU and ROCm EP std::vector gold = { 0.824904f, 1.252098f, 1.382309f, 1.220220f, 0.8425703f, 1.283372f, 1.393244f, 1.260538f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(attention_single_head_batch2_test) { auto p = optimize_onnx("attention_single_head_batch2_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; migraphx::shape input_shape{migraphx::shape::float_type, {2, 2, 4}}; // Inputs generated via Accuracy checker tool with CPU/ ROCm EP std::vector input_data = {0.801922f, 0.3853472f, 0.370015f, 0.860284f, 0.263503f, 0.795997f, 0.5618871f, 0.3625675f, 0.779154f, 0.2929783f, 0.894270f, 0.0289014f, 0.554448f, 0.006574f, 0.9899347f, 0.788727f}; migraphx::shape weight_shape{migraphx::shape::float_type, {4, 12}}; std::vector weight_data = { 0.0310612f, 0.2073076f, 0.422496f, 0.5100093f, 0.445159f, 0.863975f, 0.5272242f, 0.520853f, 0.670335f, 0.322454f, 0.1155668f, 0.1083195f, 0.228852f, 0.661951f, 0.524143f, 0.9668951f, 0.1439316f, 0.499265f, 0.114529f, 0.3760084f, 0.0013527f, 0.0608055f, 0.0439942f, 0.5202921f, 0.71258f, 0.811452f, 0.470202f, 0.778556f, 0.824370f, 0.2653890f, 0.0469467f, 0.1800754f, 0.5910899f, 0.3669454f, 0.8135282f, 0.2441214f, 0.6245076f, 0.3581591f, 0.647997f, 0.989607f, 0.4919585f, 0.3192114f, 0.3586453f, 0.8427194f, 0.2083097f, 0.738242f, 0.866889f, 0.50372523f}; // Make Bias all zero since ONNX-RT doesn't have non-biased working (CPU/ROCm EP results in // segfault) using zero bias allows us to ensure the scale dot attention head is correct before // testing with batching/multiple heads/additional parameters migraphx::shape bias_shape{migraphx::shape::float_type, {12}}; std::vector bias_data(12, 0.0f); migraphx::literal input{input_shape, input_data}; migraphx::literal weights{weight_shape, weight_data}; migraphx::literal bias{bias_shape, bias_data}; pp["input"] = input.get_argument(); pp["weights"] = weights.get_argument(); pp["bias"] = bias.get_argument(); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Gold data accuracy checker run against ROCm and CPU EP std::vector gold = {0.8458339f, 0.938286f, 1.074253f, 0.798552f, 0.8394189f, 0.9301322f, 1.068407f, 0.7976641f, 1.096127f, 0.926494f, 1.280131f, 0.6113027f, 1.098830f, 0.9479323f, 1.309692f, 0.621160f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/averagepool_notset_test.cpp000066400000000000000000000037451510465702400254610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(averagepool_notset_test) { auto p = read_onnx("averagepool_notset_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; migraphx::shape s_x{migraphx::shape::float_type, {1, 1, 5, 5}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {12}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/averagepool_nt_cip_test.cpp000066400000000000000000000037521510465702400254170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(averagepool_nt_cip_test) { auto p = read_onnx("averagepool_nt_cip_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; migraphx::shape s_x{migraphx::shape::float_type, {1, 1, 5, 5}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {8.33333}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/batch_norm_1d_test.cpp000066400000000000000000000062411510465702400242530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(batch_norm_1d_test) { migraphx::program p = read_onnx("batch_norm_1d_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::half_type, {2, 3, 4}}; migraphx::shape c_shape(migraphx::shape::float_type, {3}); std::vector tmp = {1.652, -0.5103, 0.3254, 2.441, 2.084, 0.4497, 1.005, -0.2401, -0.4307, 0.07623, -0.02927, 0.4236, -0.004498, -0.4282, -0.5527, 0.02205, -1.472, -1.7295, 0.796, 0.9507, 0.2312, 0.664, -0.06964, 1.035}; std::vector x_data{tmp.cbegin(), tmp.cend()}; std::vector scale_data = {-1.336926, -1.0679098, 0.10368501}; std::vector bias_data = {0.20240043, -0.70175606, -0.8859727}; std::vector mean_data = {0.30854642, -0.36574763, -0.9463552}; std::vector variance_data = {0.43428132, 0.97773486, 0.30332062}; migraphx::parameter_map params; params["x"] = migraphx::argument(x_shape, x_data.data()); params["scale"] = migraphx::argument(c_shape, scale_data.data()); params["bias"] = migraphx::argument(c_shape, bias_data.data()); params["mean"] = migraphx::argument(c_shape, mean_data.data()); params["variance"] = migraphx::argument(c_shape, variance_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {-2.523, 1.863, 0.1681, -4.125, -3.348, -1.582, -2.182, -0.8374, -0.789, -0.6934, -0.7134, -0.628, 0.8374, 1.697, 1.949, 0.7837, 0.4927, 0.771, -1.956, -2.123, -0.664, -0.583, -0.7207, -0.5127}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/batch_norm_2d_test.cpp000066400000000000000000000130761510465702400242600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(batch_norm_2d_test) { migraphx::program p = read_onnx("batch_norm_2d_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; migraphx::shape c_shape(migraphx::shape::float_type, {3}); std::vector x_data = { 1.6524342, -0.51048076, 0.32543048, 2.4410043, 2.0833702, 0.44981122, 1.0044622, -0.24006313, -0.43065986, 0.07626268, -0.02927135, 0.42347777, -0.00449735, -0.4281568, -0.5527635, 0.02204161, -1.4719028, -1.7298799, 0.79596406, 0.9505461, 0.23115851, 0.6639593, -0.06963254, 1.0348768, -1.336926, -1.0679098, 0.10368501, 0.20240043, -0.70175606, -0.8859727, 0.30854642, -0.36574763, -0.9463552, 0.9476916, 0.37686515, -0.05184272, -0.7151244, -0.37341377, 0.59440356, 0.10051094, -0.20755945, 0.9098465, 1.1664004, 1.4075205, -1.1522529, -0.34607422, 0.32027543, -0.6885485, 0.5404544, 0.10012514, 0.8767704, 1.0032021, -1.2755303, 0.23577735, 0.74239916, 1.0146079, 0.60875916, -0.29163074, 1.4872868, 0.20466477, -0.26367408, -0.56394804, -0.56043875, 0.7763664, -0.9626441, 0.29653943, -3.2231965, 0.03322164, 0.03402911, 0.77308357, -0.0654009, -0.30463725, 0.22182712, -0.22594836, -0.5807543, -0.22390617, -0.24484141, -2.0761833, 1.8459716, 0.2455878, 0.99913245, -0.9266217, -0.1938893, 0.6417983, -1.0880078, 0.49565446, 2.1584804, 1.2276239, 3.3091128, 0.14217089, 0.9425477, 0.07578196, 0.4067431, 0.71984154, -0.20796849, 0.90003085}; std::vector scale_data = {0.658487, 0.03700604, 2.463201}; std::vector bias_data = {0.03497279, 0.17080553, 0.5636415}; std::vector mean_data = {0.1954783, 0.6203974, 0.8116831}; std::vector variance_data = {0.30558077, 0.04536599, 0.05461315}; migraphx::parameter_map params; params["x"] = migraphx::argument(x_shape, x_data.data()); params["scale"] = migraphx::argument(c_shape, scale_data.data()); params["bias"] = migraphx::argument(c_shape, bias_data.data()); params["mean"] = migraphx::argument(c_shape, mean_data.data()); params["variance"] = migraphx::argument(c_shape, variance_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 1.77046824e+00, -8.05950999e-01, 1.89769119e-01, 2.70979643e+00, 2.28379035e+00, 3.37928861e-01, 9.98617530e-01, -4.83835101e-01, -7.10869908e-01, -1.07034385e-01, -2.32744321e-01, 3.06560963e-01, -2.03234047e-01, -7.07888365e-01, -8.56317282e-01, -1.71621382e-01, -1.92677066e-01, -2.37493858e-01, 2.01305658e-01, 2.28160262e-01, 1.03185430e-01, 1.78373277e-01, 5.09308279e-02, 2.42810518e-01, -1.69228360e-01, -1.22493818e-01, 8.10402334e-02, 9.81894583e-02, -5.88841513e-02, -9.08869803e-02, 1.16629556e-01, -5.11445105e-04, -1.79648399e+01, 1.99707508e+00, -4.01903248e+00, -8.53731060e+00, -1.55278311e+01, -1.19264421e+01, -1.72633123e+00, -6.93161058e+00, -1.01784554e+01, 1.59821415e+00, 4.30211163e+00, 6.84334660e+00, -2.01348572e+01, -1.16383028e+01, -4.61544800e+00, -1.52477398e+01, 4.45901126e-01, -7.86099210e-02, 8.46513629e-01, 9.97116446e-01, -1.71726203e+00, 8.29761624e-02, 6.86453462e-01, 1.01070285e+00, 5.27264357e-01, -5.45261383e-01, 1.57374811e+00, 4.59154993e-02, -5.11959970e-01, -8.69639993e-01, -8.65459919e-01, 7.26914644e-01, -1.04206637e-01, 1.14543661e-01, -4.96918678e-01, 6.87990561e-02, 6.89393356e-02, 1.97330773e-01, 5.16659655e-02, 1.01048872e-02, 1.01564340e-01, 2.37750299e-02, -3.78632471e-02, 2.41298079e-02, 2.04928555e-02, -2.97655046e-01, 3.83717060e-01, 1.05692141e-01, 2.53922558e+00, -1.77568626e+01, -1.00343809e+01, -1.22682428e+00, -1.94577579e+01, -2.76707697e+00, 1.47579327e+01, 4.94736385e+00, 2.68847847e+01, -6.49254417e+00, 1.94286156e+00, -7.19223642e+00, -3.70413971e+00, -4.04303551e-01, -1.01827660e+01, 1.49476433e+00}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/batch_norm_3d_test.cpp000066400000000000000000000063411510465702400242560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(batch_norm_3d_test) { migraphx::program p = read_onnx("batch_norm_3d_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::half_type, {2, 2, 2, 2, 2}}; migraphx::shape c_shape(migraphx::shape::half_type, {2}); // using migraphx::half copy conversion since it doesn't have initializer_list constructor std::vector tmp = {5., 5., 8., 7., 3., 4., 1., 7., 5., 5., 9., 4., 7., 2., 2., 2., 6., 1., 4., 9., 2., 8., 0., 2., 1., 4., 8., 8., 3., 3., 0., 8.}; std::vector x_data{tmp.cbegin(), tmp.cend()}; tmp = {1., 1.}; std::vector scale_data{tmp.cbegin(), tmp.cend()}; tmp = { 0., 0., }; std::vector bias_data{tmp.cbegin(), tmp.cend()}; tmp = {-0.75, 0.29}; std::vector mean_data{tmp.cbegin(), tmp.cend()}; tmp = {0.31, 0.37}; std::vector variance_data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map params; params["x"] = migraphx::argument(x_shape, x_data.data()); params["scale"] = migraphx::argument(c_shape, scale_data.data()); params["bias"] = migraphx::argument(c_shape, bias_data.data()); params["mean"] = migraphx::argument(c_shape, mean_data.data()); params["variance"] = migraphx::argument(c_shape, variance_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {10.33, 10.33, 15.71, 13.914, 6.734, 8.53, 3.143, 13.914, 7.742, 7.742, 14.32, 6.098, 11.03, 2.81, 2.81, 2.81, 12.125, 3.143, 8.53, 17.52, 4.938, 15.71, 1.347, 4.938, 1.167, 6.098, 12.67, 12.67, 4.453, 4.453, -0.4768, 12.67}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/batch_norm_flat_test.cpp000066400000000000000000000064411510465702400246770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(batch_norm_flat_test) { migraphx::program p = read_onnx("batch_norm_flat_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {10}}; migraphx::shape c_shape(migraphx::shape::float_type, {1}); std::vector x_data = {1.6524342, -0.51048076, 0.32543048, 2.4410043, 2.0833702, 0.44981122, 1.0044622, -0.24006313, -0.43065986, 0.07626268}; std::vector scale_data = {-0.02927135}; std::vector bias_data = {0.42347777}; std::vector mean_data = {-0.00449735}; std::vector variance_data = {0.5184545}; migraphx::parameter_map params; params["x"] = migraphx::argument(x_shape, x_data.data()); params["scale"] = migraphx::argument(c_shape, scale_data.data()); params["bias"] = migraphx::argument(c_shape, bias_data.data()); params["mean"] = migraphx::argument(c_shape, mean_data.data()); params["variance"] = migraphx::argument(c_shape, variance_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.35612, 0.44404706, 0.4100655, 0.32406294, 0.33860153, 0.40500915, 0.38246143, 0.43305403, 0.4408022, 0.42019472}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/batch_norm_rank_2_test.cpp000066400000000000000000000055031510465702400251230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(batch_norm_rank_2_test) { migraphx::program p = read_onnx("batch_norm_rank_2_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 5}}; migraphx::shape c_shape(migraphx::shape::float_type, {5}); std::vector x_data = {1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}; std::vector scale_data(5, 1.); std::vector bias_data(5, 0.); std::vector mean_data = {1., 2., 1., 2., 1.}; std::vector variance_data(5, 0.5); migraphx::parameter_map params; params["x"] = migraphx::argument(x_shape, x_data.data()); params["scale"] = migraphx::argument(c_shape, scale_data.data()); params["bias"] = migraphx::argument(c_shape, bias_data.data()); params["mean"] = migraphx::argument(c_shape, mean_data.data()); params["variance"] = migraphx::argument(c_shape, variance_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0., 0., 2.8284243, 2.8284243, 5.65684859, 7.07106074, 7.07106074, 9.89948504, 9.89948504, 12.72790933}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/biasadd_test.cpp000066400000000000000000000055111510465702400231410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(biasadd_test) { migraphx::program p = read_onnx("biasadd_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {2, 3, 4}}; migraphx::shape bias_shape{input_type, {4}}; std::vector data(24); std::iota(data.begin(), data.end(), -12); // range from -12 to 11 std::vector bias = {-2, -1, 0, 1}; std::vector skip(24); std::iota(skip.begin(), skip.end(), -11); // range from -11 to 12 migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["bias"] = migraphx::argument(bias_shape, bias.data()); pp["skip"] = migraphx::argument(data_shape, skip.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // gold values generated using numpy: // >>> import numpy as np // >>> x = np.arange(-12, 12, dtype=np.float32) // >>> x = np.reshape(x, [2, 3, 4]) // >>> bias = np.array([-2, -1, 0, 1]).astype(np.float32) // >>> skip = np.arange(-11, 13, dtype=np.float32) // >>> skip = np.reshape(skip, [2, 3, 4]) // >>> x_plus_bias = x + bias // >>> np.ndarray.flatten(x_plus_bias + skip) std::vector gold = {-25., -22., -19., -16., -17., -14., -11., -8., -9., -6., -3., 0., -1., 2., 5., 8., 7., 10., 13., 16., 15., 18., 21., 24.}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/celu_verify_test.cpp000066400000000000000000000040531510465702400240660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(celu_verify_test) { migraphx::program p = read_onnx("celu_verify_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data = {-5.5, 2.0, 100., 7.0, 0., -1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold(6); float alpha = 0.5; std::transform(data.begin(), data.end(), gold.begin(), [&](auto x) { return std::max(0.0f, x) + std::min(0.0f, alpha * std::expm1(x / alpha)); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/clip_test_args_type_mismatch.cpp000066400000000000000000000037161510465702400264500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(clip_args_type_mismatch) { auto p = read_onnx("clip_test_args_type_mismatch.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_0{migraphx::shape::float_type, {3, 3}}; migraphx::parameter_map pp; std::vector data_0 = {0.9, 1.2, 1.7, 1.9, 2.2, 2.7, 2.9, 3.2, 3.7}; pp["0"] = migraphx::argument(s_0, data_0.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.5, 2, 2, 1.9, 2.5, 3, 2.9, 3.2, 3.7}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/depthtospace_simple_test.cpp000066400000000000000000000042331510465702400256060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(depthtospace_simple_test) { auto p = read_onnx("depthtospace_simple_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_in(48); std::iota(std::begin(data_in), std::end(data_in), 0); migraphx::shape s_x{migraphx::shape::float_type, {1, 8, 2, 3}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_in.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 12, 1, 13, 2, 14, 24, 36, 25, 37, 26, 38, 3, 15, 4, 16, 5, 17, 27, 39, 28, 40, 29, 41, 6, 18, 7, 19, 8, 20, 30, 42, 31, 43, 32, 44, 9, 21, 10, 22, 11, 23, 33, 45, 34, 46, 35, 47}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/dequantizelinear_blocked_test.cpp000066400000000000000000000074141510465702400266050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(dequantizelinear_2d_blocked_with_zp_test) { migraphx::program p = read_onnx("dequantizelinear_2d_blocked_with_zp_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::int8_type, {2, 2}}; std::vector x = {4, 8, 20, 2}; migraphx::shape scale_shape{migraphx::shape::float_type, {2, 2}}; std::vector scale = {1.5f, 2.5f, 3.0f, 4.9f}; migraphx::shape zp_shape{migraphx::shape::int8_type, {2, 2}}; std::vector zp = {0, 1, 2, 3}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x.data()}; pm["scale"] = migraphx::argument{scale_shape, scale.data()}; pm["zp"] = migraphx::argument{zp_shape, zp.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::float_type, {2, 2}}); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {6.0f, 17.5f, 54.0f, -4.9f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(dequantizelinear_3d_blocked_with_zp_runt_block_test) { migraphx::program p = read_onnx("dequantizelinear_3d_blocked_with_zp_runt_block_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::int8_type, {2, 5, 2}}; std::vector x = {4, 1, 8, 4, 33, 3, 4, 4, 15, 4, 3, 6, 14, 14, 10, 9, 8, 5, 14, 8}; migraphx::shape scale_shape{migraphx::shape::float_type, {2, 2, 2}}; std::vector scale = {1.5f, 2.5f, 3.0f, 4.9f, 1.8f, 3.6f, 2.3f, 4.1f}; migraphx::shape zp_shape{migraphx::shape::int8_type, {2, 2, 2}}; std::vector zp = {0, 1, 2, 3, 3, 2, 1, 0}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x.data()}; pm["scale"] = migraphx::argument{scale_shape, scale.data()}; pm["zp"] = migraphx::argument{zp_shape, zp.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::float_type, {2, 5, 2}}); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {6.0f, 0.0f, 12.0f, 7.5f, 49.5f, 5.0f, 6.0f, 4.9f, 39.0f, 4.9f, 0.0f, 14.4f, 19.8f, 43.2f, 12.6f, 25.2f, 16.1f, 20.5f, 29.9f, 32.8f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/dynamicquantizelinear_1d_test.cpp000066400000000000000000000067771510465702400265550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(dynamicquantizelinear_1d_test) { auto p = read_onnx("dynamicquantizelinear_1d_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data{0, 2, -3, -2.5, 1.34, 0.5}; migraphx::shape s_x{migraphx::shape::float_type, {6}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data.data()); auto results = p.eval(pp); std::vector y_results; results.at(0).visit([&](auto output) { y_results.assign(output.begin(), output.end()); }); std::vector y_gold = {153, 255, 0, 26, 221, 179}; EXPECT(migraphx::verify::verify_rms_range(y_results, y_gold)); std::vector y_scale; results.at(1).visit([&](auto output) { y_scale.assign(output.begin(), output.end()); }); std::vector y_scale_gold = {0.0196078438}; EXPECT(migraphx::verify::verify_rms_range(y_scale, y_scale_gold)); std::vector y_zpt; results.at(2).visit([&](auto output) { y_zpt.assign(output.begin(), output.end()); }); std::vector y_zpt_gold = {153}; EXPECT(migraphx::verify::verify_rms_range(y_zpt, y_zpt_gold)); } TEST_CASE(dynamicquantizelinear_1d_max_adjusted_test) { auto p = read_onnx("dynamicquantizelinear_1d_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data{-1.0, -2.1, -1.3, -2.5, -3.34, -4.0}; migraphx::shape s_x{migraphx::shape::float_type, {6}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data.data()); auto results = p.eval(pp); std::vector y_results; results.at(0).visit([&](auto output) { y_results.assign(output.begin(), output.end()); }); std::vector y_gold = {191, 121, 172, 96, 42, 0}; EXPECT(migraphx::verify::verify_rms_range(y_results, y_gold)); std::vector y_scale; results.at(1).visit([&](auto output) { y_scale.assign(output.begin(), output.end()); }); std::vector y_scale_gold = {0.0156862754}; EXPECT(migraphx::verify::verify_rms_range(y_scale, y_scale_gold)); std::vector y_zpt; results.at(2).visit([&](auto output) { y_zpt.assign(output.begin(), output.end()); }); std::vector y_zpt_gold = {255}; EXPECT(migraphx::verify::verify_rms_range(y_zpt, y_zpt_gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/dynamicquantizelinear_2d_test.cpp000066400000000000000000000046721510465702400265460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(dynamicquantizelinear_2d_test) { auto p = read_onnx("dynamicquantizelinear_2d_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data{1.0, 2.1, 1.3, 2.5, 3.34, 4.0, 1.5, 2.6, 3.9, 4.0, 3.0, 2.345}; migraphx::shape s_x{migraphx::shape::float_type, {3, 4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data.data()); auto results = p.eval(pp); std::vector y_results; results.at(0).visit([&](auto output) { y_results.assign(output.begin(), output.end()); }); std::vector y_gold = {64, 134, 83, 159, 213, 255, 96, 166, 249, 255, 191, 149}; EXPECT(migraphx::verify::verify_rms_range(y_results, y_gold)); std::vector y_scale; results.at(1).visit([&](auto output) { y_scale.assign(output.begin(), output.end()); }); std::vector y_scale_gold = {0.0156862754}; EXPECT(migraphx::verify::verify_rms_range(y_scale, y_scale_gold)); std::vector y_zpt; results.at(2).visit([&](auto output) { y_zpt.assign(output.begin(), output.end()); }); std::vector y_zpt_gold = {0}; EXPECT(migraphx::verify::verify_rms_range(y_zpt, y_zpt_gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/dynamicscale_small_test.cpp000066400000000000000000000050771510465702400254050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(dynamicscale_small_test) { migraphx::program p = read_onnx("dynamicscale_small_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{4, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; std::vector data = {-100.f, -12.f, 32.f, 819.f, -6.f, -5.75f, -5.50f, -5.25f, -5.f, -0.30f, -1.40f, -1.20f, 2.0f, 0.25f, 0.33f, 2.20f}; migraphx::parameter_map pp; pp["input"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // hand calculated values std::vector gold = {128.f, 1.f, 1.f, 0.5f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/einsum_tests.cpp000066400000000000000000002354231510465702400232440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include static migraphx::shape make_shape(std::vector lens) { return migraphx::shape{migraphx::shape::float_type, std::move(lens)}; } TEST_CASE(einsum_permute_test) { migraphx::program p = read_onnx("einsum_permute_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.06727745, 0.21160052, 0.1340474, 0.74153227, 0.40337096, 0.81284493}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.06727745, 0.74153227, 0.21160052, 0.40337096, 0.1340474, 0.81284493}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_summation_test) { migraphx::program p = read_onnx("einsum_summation_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.79413969, 0.45169144, 0.06846618, 0.67973967, 0.83375529, 0.44838823}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape().scalar()); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {3.2761804984270566}; EXPECT(result_vector == gold); } TEST_CASE(einsum_column_sum_test) { migraphx::program p = read_onnx("einsum_column_sum_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.22235926, 0.83263138, 0.04747776, 0.96030827, 0.18947713, 0.48815767}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.18266753, 1.0221085, 0.53563543}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_row_sum_test) { migraphx::program p = read_onnx("einsum_row_sum_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.17123185, 0.59008514, 0.37948294, 0.73022965, 0.22919172, 0.27532941}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.14079993, 1.23475077}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_matrix_vector_multiplication_test) { migraphx::program p = read_onnx("einsum_matrix_vector_multiplication_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.4834133, 0.14106742, 0.50055824, 0.91764271, 0.95528452, 0.98199955}; migraphx::shape v_shape{migraphx::shape::float_type, {3}}; std::vector v_data = {0.73961958, 0.53071864, 0.34152803}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; pm["v"] = migraphx::argument{v_shape, v_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.60336371, 1.52107419}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_matrix_matrix_multiplication_test) { migraphx::program p = read_onnx("einsum_matrix_matrix_multiplication_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.45176257, 0.84846429, 0.4374105, 0.25132236, 0.70519571, 0.4902031}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x_shape, x_data.data()}; pm["x2"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.11530901, 0.92629139, 0.92629139, 0.80076299}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_vector_dot_product_test) { migraphx::program p = read_onnx("einsum_vector_dot_product_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {3}}; std::vector x_data = {0.45263196, 0.90876706, 0.9584567}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x_shape, x_data.data()}; pm["x2"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape().scalar()); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.94937252}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_matrix_dot_product_test) { migraphx::program p = read_onnx("einsum_matrix_dot_product_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.50001808, 0.12468059, 0.85439214, 0.00773521, 0.84764693, 0.87185525}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x_shape, x_data.data()}; pm["x2"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape().scalar()); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {2.47424599}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_hadamard_product_test) { migraphx::program p = read_onnx("einsum_hadamard_product_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.86162928, 0.76609605, 0.03362172, 0.21778614, 0.27204858, 0.83778314}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x_shape, x_data.data()}; pm["x2"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.74240502, 0.58690315, 0.00113042, 0.0474308, 0.07401043, 0.70188058}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_vector_outer_product_test) { migraphx::program p = read_onnx("einsum_vector_outer_product_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3}}; std::vector x1_data = {0.35935151, 0.51298139, 0.46076789}; migraphx::shape x2_shape{migraphx::shape::float_type, {5}}; std::vector x2_data = {0.82417482, 0.17984153, 0.17680769, 0.55499376, 0.74447638}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 5})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.29616847, 0.06462632, 0.06353611, 0.19943785, 0.26752871, 0.42278634, 0.09225536, 0.09069905, 0.28470147, 0.38190252, 0.37975329, 0.0828652, 0.08146731, 0.2557233, 0.34303081}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_matrix_outer_product_test) { migraphx::program p = read_onnx("einsum_matrix_outer_product_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 3}}; std::vector x1_data = { 0.25870501, 0.06755926, 0.18247427, 0.19436556, 0.61580192, 0.20010939}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 5}}; std::vector x2_data = {0.30771264, 0.86270274, 0.55251869, 0.35880608, 0.3234085, 0.24642323, 0.82411907, 0.33488431, 0.69288027, 0.21717812}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 3, 2, 5})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.0796068, 0.22318552, 0.14293935, 0.09282493, 0.0836674, 0.06375092, 0.21320373, 0.08663625, 0.17925159, 0.05618507, 0.02078884, 0.05828356, 0.03732775, 0.02424067, 0.02184924, 0.01664817, 0.05567687, 0.02262453, 0.04681048, 0.01467239, 0.05614964, 0.15742105, 0.10082044, 0.06547288, 0.05901373, 0.0449659, 0.15038052, 0.06110777, 0.12643282, 0.03962942, 0.05980874, 0.1676797, 0.1073906, 0.06973954, 0.06285947, 0.04789619, 0.16018036, 0.06508997, 0.13467206, 0.04221195, 0.18949004, 0.53125401, 0.34024207, 0.22095347, 0.19915557, 0.1517479, 0.50749411, 0.2062224, 0.426677, 0.1337387, 0.06157619, 0.17263492, 0.11056418, 0.07180047, 0.06471708, 0.0493116, 0.16491396, 0.06701349, 0.13865185, 0.04345938}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_batch_matrix_multiplication_test) { migraphx::program p = read_onnx("einsum_batch_matrix_multiplication_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 2, 5}}; std::vector x1_data = {0.99236023, 0.6848901, 0.37916487, 0.35448254, 0.06103943, 0.88991707, 0.20816843, 0.12124124, 0.90632983, 0.88490338, 0.93530363, 0.41393917, 0.95269137, 0.95556378, 0.63113954, 0.87936215, 0.66831395, 0.38079353, 0.74128241, 0.05493966, 0.12545692, 0.77418839, 0.17562823, 0.5558762, 0.95698858, 0.49207445, 0.81934147, 0.50168285, 0.13782384, 0.71351839}; migraphx::shape x2_shape{migraphx::shape::float_type, {3, 5, 3}}; std::vector x2_data = { 0.72870257, 0.44635711, 0.05938103, 0.7031737, 0.52116502, 0.01719079, 0.99837568, 0.29989025, 0.63673246, 0.39255282, 0.39796917, 0.03082538, 0.20994321, 0.11431396, 0.06561894, 0.99749458, 0.45970296, 0.76957234, 0.98073012, 0.63154904, 0.22862209, 0.71098086, 0.68895963, 0.92763041, 0.61730666, 0.54453456, 0.99719059, 0.05984043, 0.64232788, 0.9754334, 0.39450223, 0.1005812, 0.11753032, 0.59885466, 0.75932222, 0.45269589, 0.26201765, 0.39022748, 0.96507247, 0.55260731, 0.42233854, 0.50671452, 0.60313192, 0.32628192, 0.40066181}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 2, 3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.73524908, 1.06164644, 0.32706016, 1.45746952, 1.00391812, 0.21962538, 2.64391179, 2.27348666, 3.26667873, 2.26421769, 1.52761296, 1.97554961, 1.44350867, 1.21602803, 1.19981019, 1.32274886, 1.15842452, 1.2686234}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_tensor_contraction_test) { migraphx::program p = read_onnx("einsum_tensor_contraction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 3, 5, 7}}; std::vector x1_data = {0.95685496, 0.40756636, 0.65360334, 0.96968506, 0.50135366, 0.50255377, 0.54263245, 0.40919774, 0.0512559, 0.18771721, 0.79265052, 0.76059609, 0.31619353, 0.62297555, 0.70398181, 0.82378161, 0.50388425, 0.56257752, 0.29233331, 0.98995162, 0.38240504, 0.29803141, 0.23344604, 0.78356941, 0.67958479, 0.10005701, 0.15588056, 0.29163352, 0.90480928, 0.35649064, 0.77419322, 0.56301202, 0.133201, 0.33165803, 0.37175546, 0.63959881, 0.6058814, 0.43169871, 0.65272681, 0.17943427, 0.30863453, 0.39029972, 0.66189176, 0.2311467, 0.77007359, 0.33601537, 0.28087721, 0.65732174, 0.67537887, 0.65066593, 0.89716601, 0.92921684, 0.69368177, 0.86772161, 0.82583412, 0.32274594, 0.0739795, 0.7573278, 0.82209441, 0.44979001, 0.52619926, 0.68870551, 0.8586619, 0.32302478, 0.30437449, 0.22181276, 0.41919667, 0.16351355, 0.10825966, 0.20406509, 0.32577585, 0.89748513, 0.78650319, 0.55487763, 0.74600253, 0.68125503, 0.59796741, 0.75181214, 0.27655496, 0.87750203, 0.50401991, 0.30561784, 0.82724439, 0.04727558, 0.9224091, 0.24823561, 0.05547919, 0.93431458, 0.51550858, 0.64800403, 0.95942825, 0.04009098, 0.55616792, 0.71433063, 0.0753035, 0.0479713, 0.19538077, 0.29627466, 0.47649694, 0.49999562, 0.05246693, 0.29663604, 0.29992186, 0.62328915, 0.00265317, 0.50642525, 0.73613139, 0.5998967, 0.37132279, 0.02788106, 0.99984085, 0.87220473, 0.08963238, 0.20698509, 0.17961793, 0.32962012, 0.8046416, 0.96530006, 0.27079326, 0.07223538, 0.72336279, 0.54842596, 0.38904735, 0.21660217, 0.05165004, 0.60308648, 0.98992912, 0.01950237, 0.19094762, 0.2928557, 0.18129261, 0.23948649, 0.65970424, 0.0217831, 0.89637346, 0.25872699, 0.98701943, 0.43783966, 0.65803132, 0.06773888, 0.11277457, 0.68990466, 0.80914248, 0.66815968, 0.10671669, 0.15578704, 0.78813393, 0.71601124, 0.41304412, 0.93551562, 0.28607031, 0.16353775, 0.54597636, 0.10405413, 0.05332971, 0.8301183, 0.0991274, 0.1152268, 0.86477572, 0.20824363, 0.77115011, 0.62202978, 0.87562719, 0.17638816, 0.00798768, 0.46176706, 0.33432177, 0.93926911, 0.60557399, 0.38483151, 0.23797486, 0.83815198, 0.27293845, 0.62067518, 0.56702013, 0.80762545, 0.47669687, 0.13692723, 0.40838777, 0.3148337, 0.55255245, 0.24319153, 0.39330312, 0.22781179, 0.101221, 0.80367016, 0.08707603, 0.90069816, 0.28595044, 0.57599756, 0.71276499, 0.04032091, 0.50101916, 0.94582167, 0.2091183, 0.17698968, 0.72687874, 0.08878026, 0.16422912, 0.34543801, 0.28480515, 0.8740834, 0.18413319, 0.60564407, 0.94070861, 0.21143538, 0.2715485, 0.76848231, 0.0064918, 0.36614132 }; migraphx::shape x2_shape{migraphx::shape::float_type, {1, 3, 3, 7, 5}}; std::vector x2_data = { 0.31719105, 0.44506343, 0.59957066, 0.00373946, 0.06497482, 0.30887562, 0.04364479, 0.09203816, 0.0778086, 0.58357676, 0.49651904, 0.10000999, 0.16565024, 0.46539611, 0.82516851, 0.64563229, 0.26637135, 0.2141455, 0.69189904, 0.75060041, 0.75433425, 0.69215069, 0.18186255, 0.89800939, 0.93269204, 0.63033347, 0.9423835, 0.90530682, 0.07135205, 0.57649693, 0.44479805, 0.94513207, 0.89856664, 0.79120729, 0.63383186, 0.97271015, 0.69211656, 0.91893391, 0.07601606, 0.90099522, 0.31441974, 0.70932527, 0.68997715, 0.33528514, 0.24921017, 0.09703337, 0.54714714, 0.98431729, 0.27753988, 0.78936545, 0.51031898, 0.30604168, 0.53546681, 0.95644451, 0.79345859, 0.3444766, 0.19356174, 0.41127976, 0.15782141, 0.65660564, 0.76540504, 0.21572256, 0.29864542, 0.01153175, 0.06708682, 0.82473386, 0.45034386, 0.96212735, 0.5969872, 0.35962495, 0.60466663, 0.52630816, 0.73655946, 0.11649375, 0.32456538, 0.64199728, 0.08340919, 0.2237889, 0.09521117, 0.91767416, 0.22842615, 0.46863323, 0.00293057, 0.13495504, 0.68305119, 0.80013148, 0.24702202, 0.83619373, 0.94419611, 0.25176846, 0.74292949, 0.68404465, 0.23097011, 0.09664962, 0.44346347, 0.31467353, 0.37099949, 0.54412241, 0.76552126, 0.1443158, 0.03555697, 0.43584746, 0.10575715, 0.1046359, 0.43291613, 0.03007743, 0.55544576, 0.80022343, 0.42529416, 0.47484557, 0.84443037, 0.99362024, 0.78040286, 0.16341681, 0.98059931, 0.64114384, 0.27438947, 0.51972672, 0.24844974, 0.11630196, 0.86696682, 0.62380654, 0.23221499, 0.93125653, 0.53386878, 0.14323035, 0.46524576, 0.24347234, 0.43592108, 0.68938894, 0.83452471, 0.67473429, 0.11704585, 0.01223517, 0.61133307, 0.19640497, 0.94062148, 0.09548036, 0.27914148, 0.28533241, 0.32062872, 0.27619432, 0.18284111, 0.73646915, 0.07043039, 0.10841211, 0.25284529, 0.73262578, 0.63395762, 0.75505585, 0.66397536, 0.60934204, 0.17561379, 0.44185177, 0.90064761, 0.87593443, 0.04697443, 0.90844936, 0.4878133, 0.17061924, 0.37868238, 0.03991319, 0.99918374, 0.05644218, 0.11533688, 0.36478255, 0.74207249, 0.02537966, 0.73720329, 0.41510019, 0.87408442, 0.0902388, 0.77849296, 0.22027469, 0.66811554, 0.535826, 0.40478544, 0.47295354, 0.53722756, 0.81697433, 0.17400588, 0.52628511, 0.57033592, 0.74645826, 0.58147372, 0.25898702, 0.03268815, 0.37127404, 0.04316943, 0.86187713, 0.33330374, 0.58282901, 0.32484663, 0.8295674, 0.34023535, 0.48430125, 0.5626468, 0.48469659, 0.16184832, 0.71399316, 0.5417521, 0.11897383, 0.84953376, 0.98761605, 0.58273874, 0.89537346, 0.83282794, 0.78849938, 0.42528756, 0.08624209, 0.7689597, 0.92518944, 0.25278458, 0.0732656, 0.0057378, 0.74097687, 0.13263284, 0.73757523, 0.01510422, 0.8650508, 0.21755823, 0.38417346, 0.77236815, 0.80464568, 0.23389132, 0.24982259, 0.3034747, 0.99357576, 0.69974824, 0.62271656, 0.43386392, 0.3517672, 0.01739671, 0.54493487, 0.07725586, 0.75756086, 0.86409372, 0.50906544, 0.87797418, 0.41355064, 0.11812738, 0.9809903, 0.67759122, 0.44601677, 0.53664097, 0.75512155, 0.27589464, 0.12141359, 0.74533628, 0.95179317, 0.31788316, 0.41200016, 0.81161753, 0.84035926, 0.42866542, 0.97692811, 0.14777789, 0.54256825, 0.03691842, 0.71298109, 0.27676914, 0.31342084, 0.09905633, 0.01056144, 0.28488026, 0.39330704, 0.07871612, 0.61847332, 0.48494692, 0.14455078, 0.53627478, 0.78087393, 0.24899241, 0.78534409, 0.29844719, 0.33439453, 0.62448919, 0.21187341, 0.21381023, 0.25570138, 0.67919933, 0.73611559, 0.45109776, 0.25360901, 0.17702297, 0.41635495, 0.80213947, 0.01236559, 0.0112422, 0.03389217, 0.87942468, 0.25273501, 0.511234, 0.82734509, 0.58747506, 0.31687443, 0.89906645, 0.96090575, 0.04004779, 0.02298561, 0.10433042, 0.7104134, 0.79670464, 0.9930637, 0.5446879, 0.06004139, 0.41158374, 0.17676018, 0.10056314, 0.01345726, 0.82521847, 0.76125409, 0.17694037, 0.05363529, 0.32265118}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 7, 1, 3, 7})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 4.37385737, 3.07363193, 3.61847664, 4.34283839, 4.26894546, 4.00093768, 4.51345157, 3.28585485, 4.98955956, 3.40062413, 4.32430907, 3.58727315, 4.01024983, 4.04214073, 3.71183284, 4.04117845, 3.9304425, 4.05446572, 3.19462145, 3.75153593, 3.63370359, 3.6737565, 2.89999382, 3.0174889, 4.35349886, 3.165444, 3.10185148, 3.86251195, 3.3873455, 4.06622752, 2.90101219, 3.93475191, 3.00084537, 3.36253104, 3.50215565, 3.2272778, 3.63297086, 4.11360191, 2.55025226, 2.89909597, 2.8134455, 2.91506006, 3.7938589, 3.12994095, 3.93469812, 5.19912284, 4.38534872, 3.50334177, 4.71274384, 3.59957887, 4.82387001, 2.82827241, 5.04315375, 3.42817516, 3.97827684, 4.0792739, 3.73622444, 4.59885202, 4.20690004, 3.39733812, 3.56861724, 4.18875149, 3.80445766, 4.34760619, 2.83154296, 3.39897749, 4.91619741, 4.55085299, 4.02356989, 4.83137925, 3.49172193, 5.09758452, 3.46814603, 4.8534725, 3.58561246, 4.17459184, 4.57103074, 4.31924652, 3.86027525, 4.33725934, 3.88334716, 3.51074837, 4.2163728, 3.76365513, 3.13004972, 2.27159717, 2.35669807, 3.25755431, 2.85534261, 2.56412151, 3.19951963, 2.50814311, 3.53231318, 2.20002443, 3.12059903, 2.63204045, 2.90076584, 3.36582992, 3.06683373, 2.76686275, 2.77506122, 2.09060484, 2.37978869, 2.59300135, 2.73194814, 4.12941618, 3.09876995, 3.26773346, 4.15566501, 3.49722972, 3.46654242, 4.2842499, 3.77358659, 4.61660476, 3.14276911, 3.88478492, 3.36244681, 3.70141846, 3.77154536, 3.59743975, 4.07663608, 3.81503321, 3.53650377, 3.19912915, 3.41346893, 3.6696098, 3.22521498, 2.26604057, 2.16539957, 4.2136737, 2.91410526, 3.02978768, 3.33819415, 2.9409972, 3.83464087, 2.65153712, 3.32360785, 2.24438948, 3.95703137, 3.35290512, 3.41760415, 2.86825506, 3.08274974, 2.72484017, 2.65706605, 3.36092398, 2.83630318, 2.89697041, 2.50152336, 2.73918816, 4.5120665, 3.40255688, 2.21408714, 2.82712268, 3.04826657, 3.41090928, 2.96534728, 3.52745057, 2.24957446, 3.84521048, 3.08574989, 3.28188229, 2.31822221, 3.76298328, 2.57778028, 3.19081461, 3.07155158, 2.73609241, 4.19950589, 3.6560231, 3.78387066, 4.79181063, 3.83391543, 3.55914169, 4.5795992, 3.80991087, 5.12966262, 3.81299104, 4.21955081, 3.59584019, 4.29810986, 3.70353926, 3.70364291, 4.26908068, 3.98312417, 3.12472346, 3.16217195, 3.4642648, 3.22122407, 2.62355294, 1.82932863, 1.87920164, 2.36533037, 2.06395846, 2.33422825, 2.78131656, 1.83772458, 2.43196754, 2.45650722, 2.37074638, 1.36516771, 2.47311739, 1.85973378, 2.28547527, 2.22058881, 2.42265217, 1.82521576, 1.42674238, 2.63853633, 2.09125692, 3.43987729, 2.19115419, 2.93461373, 3.85600443, 3.76977612, 3.15357479, 3.3520207, 2.6665599, 4.023041, 2.68187355, 3.41405847, 2.72865504, 3.23944437, 3.64514952, 3.347772, 3.08780622, 3.59354671, 3.2772289, 2.50492638, 2.77853552, 3.07724088, 3.03408917, 2.45574117, 2.5493586, 3.48528482, 2.74493899, 2.611099, 3.26765525, 2.93502233, 3.93585413, 2.32960219, 3.09824088, 3.03519943, 3.21090064, 3.3114777, 2.58394431, 2.2187237, 3.00954904, 2.23092399, 2.83426168, 2.27217761, 2.5014613, 3.19291058, 2.17091072, 3.02885277, 4.41008881, 4.12811972, 3.61970552, 3.53615268, 2.78509447, 4.861919, 2.54172549, 4.17995171, 2.56407684, 4.31953876, 3.98183007, 4.18525975, 3.4355, 3.32306034, 2.80758129, 3.17616352, 3.6386068, 3.45497304, 3.46339678, 2.31062665, 2.98872364, 4.14619218, 3.33730406, 2.814647, 4.28392461, 2.85391039, 3.99487077, 3.22812695, 4.24891978, 2.57924025, 3.05409494, 3.2767709, 3.64664984, 3.49454643, 3.69300505, 2.42169066, 2.93327166, 3.5987843, 2.52333694}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_matrix_diagonal_test) { migraphx::program p = read_onnx("einsum_matrix_diagonal_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {3, 3}}; std::vector x_data = {0.47776573, 0.63448645, 0.89651875, 0.23679368, 0.99918665, 0.27613904, 0.57251725, 0.30676534, 0.01097199}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.47776573, 0.99918665, 0.01097199}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_batch_matrix_diagonal_test) { migraphx::program p = read_onnx("einsum_batch_matrix_diagonal_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {3, 3, 3}}; std::vector x_data = { 0.28876273, 0.35989686, 0.87975286, 0.4636637, 0.42481418, 0.15188883, 0.19336828, 0.24970656, 0.85099181, 0.26858692, 0.70659505, 0.28920736, 0.44962699, 0.02807534, 0.36833006, 0.41504379, 0.00211731, 0.78780266, 0.23482163, 0.16543172, 0.29376553, 0.8090205, 0.08804924, 0.16924385, 0.07311857, 0.52459502, 0.66098314}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.28876273, 0.42481418, 0.85099181, 0.26858692, 0.02807534, 0.78780266, 0.23482163, 0.08804924, 0.66098314}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_3d_diagonal_test) { migraphx::program p = read_onnx("einsum_3d_diagonal_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {3, 3, 3}}; std::vector x_data = { 0.0865182, 0.38083222, 0.67805353, 0.0585945, 0.74171412, 0.1304194, 0.00526353, 0.43741816, 0.95075246, 0.56668103, 0.66687595, 0.73297639, 0.06474291, 0.27579944, 0.13203794, 0.01323116, 0.18004087, 0.67450993, 0.86813684, 0.88677573, 0.67944271, 0.38633242, 0.92832963, 0.02932602, 0.45013121, 0.36562681, 0.0411488}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0865182, 0.27579944, 0.0411488}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_diag_vector_multiply_test) { migraphx::program p = read_onnx("einsum_diag_vector_multiply_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 3}}; std::vector x1_data = {0.8628764, 0.96045198, 0.14103307, 0.89249896, 0.97520951, 0.7015561, 0.06408759, 0.59921615, 0.76173894}; migraphx::shape x2_shape{migraphx::shape::float_type, {3}}; std::vector x2_data = {0.79284103, 0.61505765, 0.70876231}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.68412382, 0.59981008, 0.53989185}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_matrix_trace_test) { migraphx::program p = read_onnx("einsum_matrix_trace_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {3, 3}}; std::vector x_data = {0.90812557, 0.40719192, 0.71678312, 0.78176503, 0.57731702, 0.23585615, 0.06292936, 0.46016886, 0.37753559}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape().scalar()); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.86297818}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_matrix_trace_implicit_test) { migraphx::program p = read_onnx("einsum_matrix_trace_implicit_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {3, 3}}; std::vector x_data = {0.78947898, 0.56206428, 0.18337164, 0.58397232, 0.68795372, 0.11615468, 0.22114439, 0.84875979, 0.08248506}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape().scalar()); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.559917763052301}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_2d_3d_multiplication_test) { migraphx::program p = read_onnx("einsum_2d_3d_multiplication_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 3}}; std::vector x1_data = {0.77117604, 0.10042859, 0.68555583, 0.93192629, 0.39255794, 0.99285767, 0.88129697, 0.56599014, 0.03828527}; migraphx::shape x2_shape{migraphx::shape::float_type, {3, 4, 5}}; std::vector x2_data = { 0.19665868, 0.49490562, 0.73175228, 0.89251999, 0.08735652, 0.25944536, 0.37003717, 0.09387889, 0.75490936, 0.81022481, 0.9987667, 0.04082882, 0.26160334, 0.85590193, 0.80221833, 0.11203218, 0.31701572, 0.45973754, 0.3452479, 0.85151585, 0.86455042, 0.19206577, 0.09922319, 0.58911914, 0.15871974, 0.61540675, 0.21682354, 0.69036427, 0.77451157, 0.91950467, 0.52659111, 0.80857867, 0.63179264, 0.10085509, 0.96412482, 0.42412458, 0.0330562, 0.13279482, 0.39372801, 0.80698385, 0.1182876, 0.75943908, 0.59421519, 0.66827559, 0.09009574, 0.66649037, 0.43015355, 0.37795428, 0.11304274, 0.37406792, 0.33043231, 0.32357327, 0.38079892, 0.42659918, 0.55308245, 0.49437723, 0.95926415, 0.99762983, 0.70624046, 0.24298556}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 4, 5})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.3195768, 0.92158614, 0.98164236, 1.20559466, 0.14507291, 0.71879884, 0.60203336, 0.40083822, 0.73744823, 0.97361497, 1.04963956, 0.33451816, 0.5262512, 0.96263736, 1.09464615, 0.46791396, 0.90542384, 1.05180592, 0.78995572, 0.90429304, 0.64010028, 1.29062741, 1.31086115, 1.72652878, 0.23316878, 1.14509684, 0.85704442, 0.73375098, 1.1197959, 1.48742487, 1.46556673, 0.67672563, 0.86988939, 1.26078125, 1.67521536, 0.76174542, 1.26082452, 1.47107559, 1.17750291, 1.351588, 0.66717038, 0.57394148, 0.72380011, 1.1455959, 0.17027018, 0.60247933, 0.46530117, 0.48794463, 1.10799312, 1.24880054, 1.19090614, 0.50601796, 0.60271763, 0.82771923, 1.27385264, 0.35771131, 0.33482015, 0.51852039, 0.5541507, 1.21648601}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_element_wise_multiplication_and_row_sum_test) { migraphx::program p = read_onnx("einsum_element_wise_multiplication_and_row_sum_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3}}; std::vector x1_data = {0.66866322, 0.01371844, 0.85036724}; migraphx::shape x2_shape{migraphx::shape::float_type, {3, 4}}; std::vector x2_data = {0.72487469, 0.24707426, 0.8735483, 0.04525622, 0.52379655, 0.32056461, 0.51596208, 0.10696902, 0.08682559, 0.95054461, 0.16377484, 0.61029108}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.2642773, 0.02012896, 1.54038595}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_broadcast_test) { migraphx::program p = read_onnx("einsum_broadcast_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 1}}; std::vector x1_data = {0.39430774, 0.13914788, 0.48328062}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 2}}; std::vector x2_data = {0.71903989, 0.19490621, 0.56431641, 0.09180231}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.50603732, 0.11305139, 0.17857631, 0.03989488, 0.62022123, 0.13856067}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_3d_broadcast_test) { migraphx::program p = read_onnx("einsum_3d_broadcast_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {1, 3, 1}}; std::vector x1_data = {0.6306304, 0.92378069, 0.09156996}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 2, 4}}; std::vector x2_data = {0.07905765, 0.27054262, 0.42684231, 0.96296392, 0.20374812, 0.95058412, 0.26180494, 0.65115589, 0.19317509, 0.60143068, 0.54864825, 0.36401264, 0.20867305, 0.90065616, 0.26377379, 0.16009663}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 3, 4})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.17834592, 0.77007964, 0.43428189, 1.01791302, 0.26125051, 1.1280533, 0.63615903, 1.49109271, 0.02589651, 0.11181853, 0.0630594, 0.14780488, 0.25341766, 0.94726162, 0.51233803, 0.33051924, 0.37121956, 1.38759882, 0.75049978, 0.48416203, 0.03679722, 0.13754603, 0.07439345, 0.04799266}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_3d_opposite_broadcast_test) { migraphx::program p = read_onnx("einsum_3d_opposite_broadcast_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {1, 3, 2}}; std::vector x1_data = { 0.89996837, 0.62380433, 0.38499382, 0.82576167, 0.71647773, 0.74190884}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 1, 4}}; std::vector x2_data = {0.83902045, 0.3002842, 0.46254963, 0.42754638, 0.54720295, 0.6184629, 0.99604709, 0.94529622}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 3, 4})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.27847646, 0.45756486, 0.7048205, 0.65148351, 1.01584862, 0.36357074, 0.56003451, 0.51765413, 1.22361616, 0.43793044, 0.67457618, 0.62352791, 0.83381291, 0.94239689, 1.51774936, 1.44041657, 0.66252897, 0.74880736, 1.20596948, 1.14452259, 0.79803343, 0.90195799, 1.4526217, 1.37860731}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_3_inputs_test) { migraphx::program p = read_onnx("einsum_3_inputs_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 2, 2}}; std::vector x1_data = {0.78808491, 0.6661874, 0.4170594, 0.80972418, 0.22687053, 0.52144567, 0.70463225, 0.8934412}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 2}}; std::vector x2_data = {0.98518483, 0.61526655, 0.89011461, 0.02600793}; migraphx::shape x3_shape{migraphx::shape::float_type, {2, 2, 2}}; std::vector x3_data = {0.04135729, 0.36723732, 0.82196749, 0.35332048, 0.92673273, 0.50014512, 0.91129541, 0.97557965}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; pm["x3"] = migraphx::argument{x3_shape, x3_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 2, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.54312876, 0.59155446, 1.19274407, 0.56709538, 2.79449706, 1.61644006, 2.15997517, 1.5496049}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_bilinear_transformation_test) { migraphx::program p = read_onnx("einsum_bilinear_transformation_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 3}}; std::vector x1_data = { 0.34096073, 0.38172764, 0.36543085, 0.28104558, 0.0556053, 0.23574725}; migraphx::shape x2_shape{migraphx::shape::float_type, {5, 3, 7}}; std::vector x2_data = { 0.27525548, 0.55922006, 0.28504873, 0.48681888, 0.7527785, 0.76094518, 0.99365312, 0.76470274, 0.44406814, 0.24103473, 0.25141801, 0.51590554, 0.78834812, 0.96411404, 0.01325493, 0.21739615, 0.25936655, 0.23025532, 0.85856546, 0.33609085, 0.33413049, 0.60163776, 0.61253489, 0.84028869, 0.2593441, 0.53611056, 0.05595679, 0.30129639, 0.44404875, 0.71431542, 0.95123376, 0.71387725, 0.05743836, 0.35266739, 0.53284905, 0.07799213, 0.3639559, 0.72199632, 0.0920087, 0.71882463, 0.09804492, 0.79378518, 0.2149909, 0.62017677, 0.57284093, 0.1480283, 0.65038853, 0.47830376, 0.18202239, 0.37421293, 0.65768777, 0.2465394, 0.80183419, 0.65855262, 0.40956847, 0.36430994, 0.4464513, 0.65720017, 0.29603235, 0.21994904, 0.31797431, 0.64774027, 0.71807814, 0.67456442, 0.37665375, 0.84645173, 0.10965697, 0.57469259, 0.68129292, 0.28780513, 0.50772577, 0.67820423, 0.92720621, 0.52615601, 0.5507361, 0.55419857, 0.37244191, 0.52378246, 0.29057448, 0.14684616, 0.60456568, 0.79814119, 0.51783395, 0.69921548, 0.12310853, 0.18934048, 0.98081268, 0.51493817, 0.1279986, 0.3868668, 0.42396674, 0.04160038, 0.56299233, 0.40414454, 0.73163413, 0.3126024, 0.75276068, 0.88847181, 0.96703089, 0.34357903, 0.34495332, 0.73431682, 0.01318382, 0.15232141, 0.88949811}; migraphx::shape x3_shape{migraphx::shape::float_type, {2, 7}}; std::vector x3_data = {0.22897831, 0.68897913, 0.55615068, 0.77395085, 0.44879247, 0.42608676, 0.45303661, 0.04397996, 0.44780993, 0.98314993, 0.32980751, 0.57814391, 0.91010863, 0.53235916}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; pm["x3"] = migraphx::argument{x3_shape, x3_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 5})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.82915577, 1.88971744, 1.84172272, 2.0310065, 1.91888787, 1.11119172, 1.03903856, 1.03828167, 1.17052253, 0.98080627}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_ellipsis_test) { migraphx::program p = read_onnx("einsum_ellipsis_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 3, 2}}; std::vector x1_data = {0.04249489, 0.55406728, 0.19941733, 0.73459709, 0.85098409, 0.57610406, 0.20316778, 0.43422309, 0.83122325, 0.26004847, 0.75534733, 0.96759149}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 4, 2}}; std::vector x2_data = {0.92094713, 0.79225215, 0.74592229, 0.44132894, 0.33642643, 0.7196803, 0.52841641, 0.19646611, 0.85507066, 0.69714208, 0.61092676, 0.10550163, 0.1895, 0.67025347, 0.01897078, 0.63833372}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 4, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.51290222, 0.4636753, 0.37019241, 0.13547507, 0.11929215, 0.43725538, 0.03296608, 0.31709483, 0.81178524, 0.83982914, 0.59753485, 0.39427841, 0.20629541, 0.77251339, 0.11931127, 0.3293049, 1.27632103, 1.27297429, 0.98672538, 0.43543911, 0.39546526, 1.19214015, 0.4606031, 0.76604642}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_ellipsis_multidim_test) { migraphx::program p = read_onnx("einsum_ellipsis_multidim_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 2, 3, 2}}; std::vector x1_data = { 0.98667534, 0.26757447, 0.97607513, 0.82605353, 0.49444144, 0.01681133, 0.77774229, 0.75994986, 0.11125708, 0.1130032, 0.63612414, 0.1262558, 0.58148571, 0.03373236, 0.97679914, 0.96362191, 0.81985409, 0.49089541, 0.20980484, 0.54484447, 0.86032374, 0.03736589, 0.21250823, 0.61016893, 0.35060633, 0.66305752, 0.15096292, 0.13044199, 0.85426735, 0.35063898, 0.62050398, 0.42931425, 0.78397709, 0.30081415, 0.13172537, 0.97078161}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 4, 3, 2}}; std::vector x2_data = { 0.57040198, 0.53550748, 0.45591515, 0.56752322, 0.50931221, 0.81220443, 0.00733681, 0.3914752, 0.56944863, 0.57929432, 0.7376043, 0.07466457, 0.62632235, 0.93106704, 0.75973908, 0.06791374, 0.4220263, 0.30228231, 0.12644542, 0.17381266, 0.6764365, 0.7179303, 0.78075755, 0.45183063, 0.03752228, 0.54431596, 0.08627314, 0.8015124, 0.74214063, 0.99574465, 0.26469823, 0.77350918, 0.29052469, 0.38834888, 0.13962948, 0.7043763, 0.98259846, 0.59013313, 0.67843048, 0.60183051, 0.75242782, 0.49615042, 0.74438165, 0.99080336, 0.09669321, 0.63712064, 0.45491748, 0.81021691}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 4, 3, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.57284157, 0.83013964, 0.26801834, 0.55576872, 0.67065001, 0.93146345, 0.07806553, 0.89229501, 0.34092632, 0.3331285, 0.35119111, 0.34872845, 0.88089507, 1.1726018, 0.46466248, 0.34215266, 0.64686801, 0.40057183, 0.3239381, 0.88814233, 0.39659985, 0.49775691, 0.57537499, 0.62820037, 0.58775059, 0.12108844, 0.52847222, 0.51820293, 0.17369356, 0.93628374, 0.22581618, 0.1309634, 0.83619289, 0.51289166, 0.12956445, 0.27042167, 1.4230166, 0.17027473, 1.39586296, 0.08091573, 0.1618585, 0.38623148, 0.73831932, 0.13130184, 0.75391828, 0.64145906, 0.17720578, 0.59794957, 0.28266118, 0.40937228, 0.41613499, 0.60966132, 0.69531223, 1.07363852, 0.00807755, 0.34668684, 0.60948202, 0.36006323, 0.67907081, 0.69363078, 0.32619851, 0.66678194, 0.9559136, 0.38165051, 0.62435381, 0.52147196, 0.0750339, 0.2356611, 0.60204548, 0.54131732, 0.82648748, 0.84606124}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_ellipsis_zero_test) { migraphx::program p = read_onnx("einsum_ellipsis_zero_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 3, 2}}; std::vector x1_data = {0.66350493, 0.23942871, 0.92238018, 0.62110235, 0.32076099, 0.96309398, 0.52844268, 0.34438311, 0.65616714, 0.20566103, 0.27886952, 0.65970714}; migraphx::shape x2_shape{migraphx::shape::float_type, {4, 3, 2}}; std::vector x2_data = {0.80308382, 0.54059368, 0.37399569, 0.1005526, 0.76379294, 0.67375565, 0.35891999, 0.84426002, 0.09043876, 0.90878662, 0.94432809, 0.79103325, 0.1105734, 0.4352484, 0.33998431, 0.05210384, 0.99372845, 0.38982222, 0.99214395, 0.66699468, 0.11299297, 0.64553585, 0.39052278, 0.66001129}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 2, 4})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.66228372, 0.44028527, 0.17757696, 0.81799008, 0.61055509, 0.48041753, 0.2083239, 0.7539929, 0.40741967, 0.64786843, 0.34595661, 0.50516631, 0.26608343, 0.24624494, 0.23380226, 0.20690385, 0.89388499, 1.06474297, 0.69418476, 0.76091737, 0.65747998, 0.7851946, 0.53428908, 0.54431906}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_ellipsis_implicit_form_test) { migraphx::program p = read_onnx("einsum_ellipsis_implicit_form_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 2, 3, 2}}; std::vector x1_data = { 0.23521871, 0.98377414, 0.89254812, 0.97761717, 0.05081862, 0.68622971, 0.10890005, 0.2268622, 0.49600579, 0.2676526, 0.42904501, 0.37749836, 0.79665579, 0.95331325, 0.86434957, 0.79121832, 0.28486632, 0.12174202, 0.70187, 0.14436634, 0.03751946, 0.61306538, 0.13534059, 0.27080258, 0.2651645, 0.29432102, 0.04611007, 0.58113752, 0.24878511, 0.17095365, 0.0815941, 0.29892262, 0.11160549, 0.27367858, 0.36888151, 0.16212635}; migraphx::shape x2_shape{migraphx::shape::float_type, {3, 4, 3, 2}}; std::vector x2_data = { 0.44591065, 0.88061357, 0.701782, 0.57534276, 0.65403074, 0.81415861, 0.68154153, 0.55451648, 0.81680318, 0.54274041, 0.44267802, 0.204258, 0.38894043, 0.26743358, 0.9689122, 0.16832771, 0.70924974, 0.13868791, 0.52965739, 0.41611994, 0.59251147, 0.03544427, 0.86559268, 0.68808533, 0.01154378, 0.50244414, 0.20684438, 0.15988138, 0.28233231, 0.10307361, 0.90725685, 0.94720523, 0.42599834, 0.93168414, 0.82026755, 0.22099913, 0.46835316, 0.90021715, 0.5152653, 0.51409383, 0.33123306, 0.3003667, 0.07429799, 0.79805729, 0.17255054, 0.29718065, 0.92965361, 0.36905318, 0.69877278, 0.77362919, 0.14773139, 0.23016429, 0.02718606, 0.39449785, 0.93450467, 0.34742404, 0.35372862, 0.07290892, 0.79728572, 0.15650619, 0.53751043, 0.44802221, 0.77646259, 0.65170074, 0.49278255, 0.36228251, 0.17940834, 0.66284468, 0.15208601, 0.83560697, 0.51165061, 0.14598895}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 4, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {2.75198731, 1.33836971, 2.12812296, 1.01745957, 1.51515599, 0.98532013, 1.61362211, 1.08658677, 0.88644536, 0.2525403, 2.99170324, 1.53155007, 2.21435937, 0.91935904, 1.51402355, 0.58178573, 0.62775842, 0.4417366, 0.63384035, 0.55901237, 0.87345202, 0.68330958, 0.88752551, 0.67084639}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_ellipsis_scalar_multiplication_test) { migraphx::program p = read_onnx("einsum_ellipsis_scalar_multiplication_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 3}}; std::vector x_data = { 0.2766607, 0.76752867, 0.28231295, 0.30409753, 0.37753377, 0.73576867}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x_shape, x_data.data()}; pm["x2"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.07654114, 0.58910026, 0.0797006, 0.09247531, 0.14253175, 0.54135554}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_common_1_test) { migraphx::program p = read_onnx("einsum_common_1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 2, 2, 2}}; std::vector x1_data = {0.35498396, 0.92145607, 0.81807284, 0.37990484, 0.22314499, 0.90337144, 0.02492543, 0.36666091, 0.33262049, 0.37052745, 0.01950226, 0.83690205, 0.61551503, 0.55244304, 0.62696715, 0.74933671}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 2, 2, 2}}; std::vector x2_data = {0.44903857, 0.47304138, 0.63679145, 0.78101353, 0.41525864, 0.57356733, 0.83636479, 0.01236986, 0.10068789, 0.46623025, 0.29825429, 0.56816588, 0.00558546, 0.91900877, 0.74972012, 0.4509882}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 2, 2, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.59528833, 0.52753278, 0.67592725, 0.61080723, 0.81765261, 0.30223943, 0.68890669, 0.0253823, 0.20624196, 0.31954056, 0.34237582, 0.51113793, 0.48131582, 0.6127432, 0.39205418, 0.8079919}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_common_2_test) { migraphx::program p = read_onnx("einsum_common_2_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 2, 2, 2}}; std::vector x1_data = {0.77858647, 0.8659616, 0.89981848, 0.45454779, 0.27364842, 0.69225887, 0.01304595, 0.14404551, 0.47394644, 0.39058325, 0.977306, 0.90298946, 0.01456065, 0.70478062, 0.92796867, 0.00407166}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 2, 2, 2}}; std::vector x2_data = {0.12299003, 0.42677007, 0.84213152, 0.26884624, 0.85685616, 0.53033816, 0.61543941, 0.00586418, 0.79310638, 0.66468861, 0.22797244, 0.32789713, 0.01537162, 0.28328088, 0.39257709, 0.83954883}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 2, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {2.51890769, 1.78883817, 2.11484282, 1.38804189, 2.81881969, 1.09537142, 3.0398521, 1.07377846}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_common_3_test) { migraphx::program p = read_onnx("einsum_common_3_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 2, 2, 2}}; std::vector x1_data = {0.22151958, 0.19284961, 0.8126814, 0.02360209, 0.99137254, 0.0550951, 0.34794661, 0.03083101, 0.03127261, 0.04609321, 0.02422953, 0.30878066, 0.42532866, 0.02191982, 0.34276933, 0.66997637}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 2, 2, 2}}; std::vector x2_data = {0.76051399, 0.92365044, 0.14703117, 0.07201171, 0.81879942, 0.91050362, 0.90936259, 0.94197062, 0.73971579, 0.08809791, 0.17392649, 0.36623704, 0.23731799, 0.67476051, 0.97480632, 0.35175013}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 2, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.62099637, 2.20329706, 0.6457657, 1.61829179, 0.4142793, 0.52881853, 2.00689201, 2.20807455}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_common_4_test) { migraphx::program p = read_onnx("einsum_common_4_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {2, 2, 3, 2}}; std::vector x1_data = {0.56144416, 0.70795103, 0.10800643, 0.85461707, 0.53053745, 0.42957473, 0.2801385, 0.91878799, 0.51160639, 0.90354742, 0.83131358, 0.84237736, 0.01078178, 0.75952001, 0.74426499, 0.70506648, 0.65528756, 0.54674358, 0.3923791, 0.33558121, 0.18089114, 0.41982192, 0.50568299, 0.83929267}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 2, 4, 2}}; std::vector x2_data = { 0.71114916, 0.10373848, 0.85011488, 0.08836512, 0.01426097, 0.63389153, 0.3714056, 0.42466907, 0.5412509, 0.12682203, 0.88595126, 0.09839624, 0.10689487, 0.1196194, 0.5887543, 0.51683836, 0.50278953, 0.94187525, 0.98227159, 0.57961915, 0.12739494, 0.59140361, 0.34997506, 0.43158845, 0.60170823, 0.06098434, 0.24573198, 0.15357368, 0.99864135, 0.92721276, 0.81457582, 0.49836327}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 2, 3, 4})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.4727123, 0.53985021, 0.4567709, 0.50916841, 0.16546536, 0.16733621, 0.5432748, 0.40304363, 0.42185469, 0.48897721, 0.27986976, 0.37947168, 0.26814778, 0.33859434, 0.13985024, 0.63979763, 0.39149714, 0.54216399, 0.1627699, 0.76819843, 0.55678123, 0.81939007, 0.18962783, 0.92481237, 0.72079407, 0.45082298, 0.45055642, 0.33157342, 1.03829331, 1.13974038, 0.51179445, 0.56477273, 0.84443597, 0.9605734, 0.40682645, 0.46530252, 0.25656293, 0.14795654, 0.70300118, 0.48686388, 0.13444625, 0.10892434, 0.56990961, 0.35657337, 0.35545733, 0.25315575, 1.28319881, 0.83018978}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_common_5_test) { migraphx::program p = read_onnx("einsum_common_5_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 2, 3, 2}}; std::vector x1_data = { 0.54568637, 0.37482154, 0.04235242, 0.65373642, 0.33087863, 0.31717808, 0.95558492, 0.04292704, 0.41062909, 0.15678733, 0.42269055, 0.52439126, 0.79640916, 0.84653066, 0.07768967, 0.27527369, 0.89984151, 0.51484382, 0.16384989, 0.91806877, 0.21812376, 0.11357245, 0.54908942, 0.31401177, 0.65491277, 0.28771509, 0.78575018, 0.79237873, 0.46273786, 0.76982106, 0.09757821, 0.22590816, 0.07358939, 0.10590534, 0.83561014, 0.46470277}; migraphx::shape x2_shape{migraphx::shape::float_type, {3, 4, 3, 2}}; std::vector x2_data = { 0.8106741, 0.59851071, 0.01563264, 0.59371323, 0.92144669, 0.13810113, 0.30200611, 0.04771728, 0.27000965, 0.15975859, 0.79296359, 0.8423782, 0.14653939, 0.97910498, 0.92130026, 0.98351422, 0.36302145, 0.34644287, 0.552259, 0.8590351, 0.32266987, 0.05450608, 0.37737409, 0.28476044, 0.12639262, 0.68674546, 0.36657116, 0.95912161, 0.25702418, 0.36058756, 0.68556443, 0.71449807, 0.15664292, 0.14519584, 0.96284277, 0.08696439, 0.21784017, 0.35219703, 0.33682869, 0.65550335, 0.58188946, 0.15934059, 0.4108815, 0.73728006, 0.18921976, 0.00133056, 0.56921019, 0.10649676, 0.63103856, 0.06864912, 0.38452259, 0.44953274, 0.53725327, 0.75235172, 0.71780644, 0.56919235, 0.14419679, 0.27101719, 0.03290223, 0.13075588, 0.99856136, 0.76185492, 0.29195496, 0.45779837, 0.670453, 0.20837162, 0.90747364, 0.53769863, 0.37493214, 0.46571204, 0.89671548, 0.16910057}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 3, 2, 4})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.66670851, 0.18268608, 0.44695419, 0.62334507, 0.80036024, 0.29064084, 0.18206091, 0.56460621, 0.38879404, 0.11587557, 0.68197836, 0.04929846, 0.09950593, 0.13592194, 0.53251525, 0.1410435, 0.34868967, 0.52955861, 0.23000012, 0.21518479, 0.46190584, 0.77691399, 0.33511735, 0.30883835, 0.68201133, 1.15083431, 0.47163549, 0.95135997, 0.65118898, 0.76828803, 0.35903419, 0.74419669, 0.29249974, 0.05213813, 0.20661094, 0.01506669, 0.18888767, 0.05065779, 0.14791746, 0.04142444, 0.4169273, 0.91117897, 0.60564381, 0.56702816, 0.25435799, 0.55599462, 0.36954417, 0.34598853, 0.4330266, 0.63386583, 0.87316774, 0.74902009, 0.07708401, 0.19862746, 0.26954707, 0.21002016, 0.65833888, 0.32805091, 0.59215335, 0.66362331, 0.0759047, 0.03931352, 0.06996808, 0.07691242, 0.82778363, 0.11588374, 0.47065285, 0.54512138, 0.79855421, 0.08825606, 0.65706819, 0.82788605}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_common_6_test) { migraphx::program p = read_onnx("einsum_common_6_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 2, 2}}; std::vector x1_data = {0.05474463, 0.22797254, 0.87786654, 0.5430384, 0.7145002, 0.27575673, 0.74687312, 0.49764738, 0.3077794, 0.83018295, 0.42118662, 0.04536079}; migraphx::shape x2_shape{migraphx::shape::float_type, {2, 2, 3}}; std::vector x2_data = {0.51540488, 0.78670115, 0.71049908, 0.51739133, 0.75638524, 0.50107731, 0.15112663, 0.55976972, 0.09744345, 0.63967998, 0.56295837, 0.95296606}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 2, 3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.06266837, 0.17067979, 0.06111044, 0.80157133, 0.96971331, 0.95737617, 0.40993108, 0.7164584, 0.53452242, 0.70476074, 0.84507857, 0.84848224, 0.28409375, 0.70684169, 0.29957287, 0.24693469, 0.34411558, 0.25427435}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_common_7_test) { migraphx::program p = read_onnx("einsum_common_7_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {5, 5}}; std::vector x_data = {0.45661163, 0.49868523, 0.8806857, 0.45253824, 0.61711842, 0.19736463, 0.55164341, 0.84964635, 0.50090015, 0.49506288, 0.19423388, 0.76448901, 0.65602353, 0.2169867, 0.99645268, 0.62749812, 0.67396942, 0.69806385, 0.23727109, 0.23524408, 0.84425561, 0.67866378, 0.20223278, 0.34088997, 0.22209943}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({5})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {2.90563922, 2.5946174, 2.82818581, 2.47204655, 2.28814157}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(einsum_common_8_test) { migraphx::program p = read_onnx("einsum_common_8_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x1_shape{migraphx::shape::float_type, {3, 3}}; std::vector x1_data = {0.31281588, 0.34922652, 0.79181082, 0.55581571, 0.34963734, 0.39777707, 0.43040396, 0.19965846, 0.68818176}; migraphx::shape x2_shape{migraphx::shape::float_type, {3, 3}}; std::vector x2_data = {0.94199384, 0.06564557, 0.36439139, 0.30556677, 0.25776106, 0.59531702, 0.21481152, 0.09608821, 0.41203512}; migraphx::parameter_map pm; pm["x1"] = migraphx::argument{x1_shape, x1_data.data()}; pm["x2"] = migraphx::argument{x2_shape, x2_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({3, 3})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.29467063, 0.08063175, 0.12889113, 0.32935622, 0.09012289, 0.14406286, 0.64826297, 0.17738646, 0.28355505}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/eyelike_verify_negk_test.cpp000066400000000000000000000036531510465702400255760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(eyelike_verify_negk_test) { migraphx::program p = read_onnx("eyelike_verify_negk_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {3, 4}}; std::vector data{12, 0}; migraphx::parameter_map pp; pp["T1"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold_eyelike_mat = {0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold_eyelike_mat)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/eyelike_verify_test.cpp000066400000000000000000000036411510465702400245670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(eyelike_verify_test) { migraphx::program p = read_onnx("eyelike_verify_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {3, 4}}; std::vector data{12, 0}; migraphx::parameter_map pp; pp["T1"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold_eyelike_mat = {0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold_eyelike_mat)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gather_elements_axis0_test.cpp000066400000000000000000000042451510465702400260270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gather_elements) { migraphx::program p = read_onnx("gather_elements_axis0_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_data{migraphx::shape::float_type, {3, 4}}; std::vector data = { 0.25, 0.75, 0.9375, 0.4375, 0.6875, 0.5625, -0.875, 0.1875, -0.125, 0.5, -0.9375, -0.0625}; migraphx::shape s_ind{migraphx::shape::int32_type, {2, 3}}; std::vector ind = {2, 1, 2, 0, 1, 0}; migraphx::parameter_map pp; pp["data"] = migraphx::argument(s_data, data.data()); pp["indices"] = migraphx::argument(s_ind, ind.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-0.125, 0.5625, -0.9375, 0.25, 0.5625, 0.9375}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gelu_add_bias_split_test.cpp000066400000000000000000000056101510465702400255270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gelu_add_bias_split_test) { migraphx::program p = read_onnx("gelu_add_bias_split_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {2, 4, 6}}; migraphx::shape bias_shape{input_type, {6}}; std::vector data(48); std::iota(data.begin(), data.end(), -24); std::vector bias = {-3, -2, -1, 0, 1, 2}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["y"] = migraphx::argument(bias_shape, bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // gold values generated using numpy: // >>> import numpy as np // >>> import math // >>> x = np.arange(-24, 24, dtype=np.float32) // >>> x = np.reshape(x, [2, 4, 6]) // >>> y = np.array([-3, -2, -1, 0, 1, 2]).astype(np.float32) // >>> sum = x + y // >>> sum_left, sum_right = np.split(sum, 2, axis=-1) // >>> gelu_out = 0.5 * sum_right * (1 + np.vectorize(math.erf)(sum_right / np.sqrt(2))) // >>> np.set_printoptions(suppress=True) // >>> np.ndarray.flatten(sum_left * gelu_out) std::vector gold = { 0, 0, 0, 0, 0, 0, 0, 0, 0.00001577, 0.03644725, 1.11058678, -4.20672373, -8.98785092, -4.99999857, 7, 27, 55, 91, 135, 187, 247, 315, 391, 475}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gelu_add_bias_test.cpp000066400000000000000000000047361510465702400243240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gelu_add_bias_test) { migraphx::program p = read_onnx("gelu_add_bias_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {3, 3}}; migraphx::shape bias_shape{input_type, {3}}; std::vector data = {-1, -1, -1, 0, 0, 0, 1, 1, 1}; std::vector bias = {-1, 0, 1}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["y"] = migraphx::argument(bias_shape, bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // gold values generated using numpy: // >>> import numpy as np // >>> import math // >>> x = np.array([[-1, -1, -1],[0, 0, 0], [1, 1, 1]]).astype(np.float32) // >>> y = np.array([-1, 0, 1]).astype(np.float32) // >>> sum = x + y // >>> 0.5 * sum * (1 + np.vectorize(math.erf)(sum / np.sqrt(2))) std::vector gold = { -0.04550027, -0.15865526, 0.0, -0.15865526, 0.0, 0.8413447, 0.0, 0.8413447, 1.9544997}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gelu_default_bf16_test.cpp000066400000000000000000000050731510465702400250330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gelu_default_bf16_test) { migraphx::program p = read_onnx("gelu_default_bf16_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{3, 3}; auto input_type = migraphx::shape::bf16_type; migraphx::shape data_shape{input_type, input_lens}; std::vector tmp = {-100.0f, -7.5f, -5.2f, -1.0f, 0.0f, 1.5f, 4.9f, 8.2f, 1000.0f}; std::vector data = {tmp.begin(), tmp.end()}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // gold values according to specification: // https://github.com/onnx/onnx/blob/main/docs/Operators.md#examples-59 // x = np.array([-100.0, -7.5, -5.2, -1.0, 0.0, 1.5, 4.9, 8.2, 1000.0]).astype(np.float16) // (0.5 * x * (1 + np.vectorize(math.erf)(x / np.sqrt(2)))).astype(np.float16) // tmp = {0.0f, 0.0f, -5.364e-07f, -0.1587f, 0.0f, 1.399f, 4.898f, 8.203f, 1000.0f}; tmp = {0.0f, 0.0f, 0.0f, -0.160156f, 0.0f, 1.399f, 4.84375f, 8.203f, 1000.0f}; std::vector gold = {tmp.begin(), tmp.end()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gelu_default_half_test.cpp000066400000000000000000000047461510465702400252150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gelu_default_half_test) { migraphx::program p = read_onnx("gelu_default_half_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{3, 3}; auto input_type = migraphx::shape::half_type; migraphx::shape data_shape{input_type, input_lens}; std::vector tmp = {-100.0f, -7.5f, -5.2f, -1.0f, 0.0f, 1.5f, 4.9f, 8.2f, 1000.0f}; std::vector data = {tmp.begin(), tmp.end()}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // gold values according to specification: // https://github.com/onnx/onnx/blob/main/docs/Operators.md#examples-59 // x = np.array([-100.0, -7.5, -5.2, -1.0, 0.0, 1.5, 4.9, 8.2, 1000.0]).astype(np.float16) // (0.5 * x * (1 + np.vectorize(math.erf)(x / np.sqrt(2)))).astype(np.float16) tmp = {0.0f, 0.0f, -5.364e-07f, -0.1587f, 0.0f, 1.399f, 4.898f, 8.203f, 1000.0f}; std::vector gold = {tmp.begin(), tmp.end()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gelu_default_test.cpp000066400000000000000000000051571510465702400242200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gelu_default_test) { migraphx::program p = read_onnx("gelu_default_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{3, 3}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; std::vector data = {-100.0f, -7.5f, -5.2f, -1.0f, 0.0f, 1.5f, 4.9f, 8.2f, 1000.0f}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // gold values according to specification: // https://github.com/onnx/onnx/blob/main/docs/Operators.md#examples-59 // x = np.array([-100.0, -7.5, -5.2, -1.0, 0.0, 1.5, 4.9, 8.2, 1000.0]).astype(np.float32) // 0.5 * x * (1 + np.vectorize(math.erf)(x / np.sqrt(2))) std::vector gold = {0.0, -2.3939184e-13, -5.1815033e-07f, -0.15865526f, 0.0f, 1.3997892f, 4.8999977f, 8.1999998f, 1000.0f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gelu_fast_bias_test.cpp000066400000000000000000000051541510465702400245240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gelu_fast_bias_test) { migraphx::program p = read_onnx("gelu_fast_bias_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape shape{migraphx::shape::half_type, {3, 3}}; std::vector tmp = {-1, -1, -1, 0, 0, 0, 1, 1, 1}; std::vector x = {tmp.begin(), tmp.end()}; tmp = {-10, 0.5, 10, -2, 0.5, 2, -1, 0.5, 1}; std::vector bias = {tmp.begin(), tmp.end()}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(shape, x.data()); pp["y"] = migraphx::argument(shape, bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // gold values generated using numpy: // >>> import numpy as np // >>> import math // >>> x = np.array([[-1, -1, -1],[0, 0, 0], [1, 1, 1]]).astype(np.float16) // >>> bias = np.array([[-10, 0.5, 10], [-2, 0.5, 2], [-1, 0.5, 1]]).astype(np.float16) // >>> x = x + bias // >>> 0.5 * x * (1 + np.tanh(0.797885 * x + 0.035677 * np.power(x, 3))) tmp = {0.0, -0.1543, 9.0, -0.0454, 0.3457, 1.955, 0.0, 1.399, 1.955}; std::vector gold = {tmp.begin(), tmp.end()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gelu_quick_test.cpp000066400000000000000000000050411510465702400237000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gelu_quick_test) { migraphx::program p = read_onnx("gelu_quick_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape shape{migraphx::shape::float_type, {3, 3}}; std::vector x = {0.0400717, 0.76666826, 0.75319463, 0.13215327, 0.37472633, 0.77117795, 0.95669776, 0.09139277, 0.37507972}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(shape, x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // output from onnxruntime CPU EP std::vector gold = {0.02023656, 0.45591995, 0.4466837, 0.0682589, 0.20486447, 0.45902082, 0.5906249, 0.04674028, 0.2050741}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gelu_tanh_test.cpp000066400000000000000000000046051510465702400235230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gelu_tanh_test) { migraphx::program p = read_onnx("gelu_tanh_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{3, 3}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; std::vector data = {-100.0f, -7.5f, -5.2f, -1.0f, 0.0f, 1.5f, 4.9f, 8.2f, 1000.0f}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // gold values according to specification: // https://github.com/onnx/onnx/blob/main/docs/Operators.md#examples-59 // x = np.array([-100.0, -7.5, -5.2, -1.0, 0.0, 1.5, 4.9, 8.2, 1000.0]).astype(np.float32) // 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * np.power(x, 3)))) std::vector gold = { 0.0f, 0.0f, -1.5497207e-07, -0.15880799f, 0.0, 1.3995717f, 4.8999996f, 8.1999998f, 1000.0f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gemm_bf16_test.cpp000066400000000000000000000074321510465702400233210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gemm_bf16_test) { migraphx::program p = read_onnx("gemm_bf16_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a_shape{migraphx::shape::bf16_type, {8, 6}}; std::vector tmp = {0.2646, 0.8525, 0.4192, 0.1415, 0.4321, 0.675, 0.4248, 0.8203, 0.978, 0.5796, 0.6626, 0.479, 0.924, 0.734, 0.674, 0.8716, 0.3733, 0.3328, 0.4272, 0.0247, 0.7583, 0.4873, 0.5835, 0.694, 0.4375, 0.2406, 0.269, 0.6763, 0.542, 0.8994, 0.657, 0.5425, 0.1412, 0.8994, 0.2183, 0.812, 0.937, 0.3438, 0.712, 0.9033, 0.266, 0.8013, 0.803, 0.4993, 0.07196, 0.635, 0.7344, 0.3213}; std::vector a_data{tmp.cbegin(), tmp.cend()}; migraphx::shape b_shape{migraphx::shape::bf16_type, {8, 7}}; tmp = {0.7095, 0.612, 0.741, 0.02121, 0.3872, 0.4482, 0.6235, 0.02249, 0.2332, 0.7656, 0.8955, 0.8154, 0.2239, 0.9277, 0.4622, 0.708, 0.566, 0.0736, 0.138, 0.8574, 0.4055, 0.382, 0.6206, 0.424, 0.3674, 0.435, 0.998, 0.3594, 0.701, 0.6216, 0.01826, 0.6313, 0.514, 0.1095, 0.3203, 0.01636, 0.537, 0.01952, 0.4502, 0.8965, 0.5415, 0.7456, 0.793, 0.756, 0.9, 0.5264, 0.05368, 0.4214, 0.276, 0.1517, 0.08453, 0.83, 0.417, 0.1682, 0.845, 0.1729}; std::vector b_data{tmp.cbegin(), tmp.cend()}; migraphx::shape c_shape{migraphx::shape::bf16_type, {6, 1}}; tmp = {0.10846, 0.672, 0.527, 0.94, 0.429, 0.2291}; std::vector c_data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map params; params["A"] = migraphx::argument(a_shape, a_data.data()); params["B"] = migraphx::argument(b_shape, b_data.data()); params["C"] = migraphx::argument(c_shape, c_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {1.071, 1.378, 1.465, 1.093, 0.968, 1.542, 1.145, 1.287, 1.533, 1.75, 1.338, 1.449, 1.592, 1.668, 1.265, 1.531, 1.656, 1.348, 1.2705, 1.525, 1.479, 1.754, 2.143, 2.062, 1.921, 1.836, 2.203, 1.952, 1.055, 1.225, 1.418, 1.209, 1.155, 1.42, 1.234, 1.302, 1.593, 1.368, 1.289, 1.327, 1.451, 1.394}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gemm_brcst_C_test.cpp000066400000000000000000000073641510465702400241460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gemm_test) { migraphx::program p = read_onnx("gemm_brcst_C_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a_shape{migraphx::shape::float_type, {5, 6}}; std::vector a_data = {0.26472837, 0.8525864, 0.41929847, 0.14151508, 0.43216065, 0.67468566, 0.42488748, 0.82021785, 0.9782456, 0.5794279, 0.6627283, 0.4790396, 0.9237051, 0.7340607, 0.67379653, 0.87168175, 0.37324256, 0.33278653, 0.42736676, 0.024699844, 0.75851107, 0.48719302, 0.5834426, 0.6938476, 0.43747696, 0.24054702, 0.26912406, 0.6760658, 0.5419149, 0.89949054}; migraphx::shape b_shape{migraphx::shape::float_type, {5, 7}}; std::vector b_data = { 0.65727437, 0.54262096, 0.14126152, 0.8994123, 0.21831702, 0.81191784, 0.9371278, 0.3438551, 0.7121373, 0.90316695, 0.26614252, 0.80144906, 0.80301756, 0.49930334, 0.0719704, 0.63484156, 0.7343097, 0.32130218, 0.7094916, 0.6116475, 0.74144083, 0.021210382, 0.38724765, 0.44830495, 0.62347615, 0.022489505, 0.23316588, 0.76540905, 0.895689, 0.81540287, 0.223875, 0.9275573, 0.4621397, 0.70785195, 0.5658555}; migraphx::shape c_shape{migraphx::shape::float_type, {6, 1}}; std::vector c_data = { 0.07358502, 0.13792239, 0.8574055, 0.40553397, 0.38205826, 0.62062204}; migraphx::parameter_map params; params["A"] = migraphx::argument(a_shape, a_data.data()); params["B"] = migraphx::argument(b_shape, b_data.data()); params["C"] = migraphx::argument(c_shape, c_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.45261115, 0.83629227, 0.7533463, 0.7189715, 0.69160205, 0.824082, 0.9187499, 0.6659525, 0.96956736, 0.84293026, 0.8400868, 0.84835225, 1.0982862, 1.0642393, 1.1447254, 1.6184721, 1.6048342, 1.4741788, 1.4334437, 1.638659, 1.7428316, 0.8098607, 1.2157929, 1.1010075, 1.0706307, 1.0429881, 1.1771785, 1.2362702, 0.8239243, 1.1112559, 0.9639262, 1.0813537, 0.8825792, 1.121141, 1.1885703, 1.2227502, 1.4568202, 1.1388762, 1.55058, 1.0958102, 1.4637487, 1.5756242}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gemm_fp8_test.cpp000066400000000000000000000076141510465702400232620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(gemm_fp8_test) { migraphx::program p = optimize_onnx("gemm_fp8_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a_shape{migraphx::shape::fp8e4m3fnuz_type, {8, 6}}; std::vector tmp = {0.2646, 0.8525, 0.4192, 0.1415, 0.4321, 0.675, 0.4248, 0.8203, 0.978, 0.5796, 0.6626, 0.479, 0.924, 0.734, 0.674, 0.8716, 0.3733, 0.3328, 0.4272, 0.0247, 0.7583, 0.4873, 0.5835, 0.694, 0.4375, 0.2406, 0.269, 0.6763, 0.542, 0.8994, 0.657, 0.5425, 0.1412, 0.8994, 0.2183, 0.812, 0.937, 0.3438, 0.712, 0.9033, 0.266, 0.8013, 0.803, 0.4993, 0.07196, 0.635, 0.7344, 0.3213}; std::vector a_data{tmp.cbegin(), tmp.cend()}; migraphx::shape b_shape{migraphx::shape::fp8e4m3fnuz_type, {8, 7}}; tmp = {0.7095, 0.612, 0.741, 0.02121, 0.3872, 0.4482, 0.6235, 0.02249, 0.2332, 0.7656, 0.8955, 0.8154, 0.2239, 0.9277, 0.4622, 0.708, 0.566, 0.0736, 0.138, 0.8574, 0.4055, 0.382, 0.6206, 0.424, 0.3674, 0.435, 0.998, 0.3594, 0.701, 0.6216, 0.01826, 0.6313, 0.514, 0.1095, 0.3203, 0.01636, 0.537, 0.01952, 0.4502, 0.8965, 0.5415, 0.7456, 0.793, 0.756, 0.9, 0.5264, 0.05368, 0.4214, 0.276, 0.1517, 0.08453, 0.83, 0.417, 0.1682, 0.845, 0.1729}; std::vector b_data{tmp.cbegin(), tmp.cend()}; migraphx::shape c_shape{migraphx::shape::fp8e4m3fnuz_type, {6, 1}}; tmp = {0.10846, 0.672, 0.527, 0.94, 0.429, 0.2291}; std::vector c_data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map params; params["A"] = migraphx::argument(a_shape, a_data.data()); params["B"] = migraphx::argument(b_shape, b_data.data()); params["C"] = migraphx::argument(c_shape, c_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {1.071, 1.378, 1.465, 1.093, 0.968, 1.542, 1.145, 1.287, 1.533, 1.75, 1.338, 1.449, 1.592, 1.668, 1.265, 1.531, 1.656, 1.348, 1.2705, 1.525, 1.479, 1.754, 2.143, 2.062, 1.921, 1.836, 2.203, 1.952, 1.055, 1.225, 1.418, 1.209, 1.155, 1.42, 1.234, 1.302, 1.593, 1.368, 1.289, 1.327, 1.451, 1.394}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gemm_half_test.cpp000066400000000000000000000074321510465702400234750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gemm_half_test) { migraphx::program p = read_onnx("gemm_half_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a_shape{migraphx::shape::half_type, {8, 6}}; std::vector tmp = {0.2646, 0.8525, 0.4192, 0.1415, 0.4321, 0.675, 0.4248, 0.8203, 0.978, 0.5796, 0.6626, 0.479, 0.924, 0.734, 0.674, 0.8716, 0.3733, 0.3328, 0.4272, 0.0247, 0.7583, 0.4873, 0.5835, 0.694, 0.4375, 0.2406, 0.269, 0.6763, 0.542, 0.8994, 0.657, 0.5425, 0.1412, 0.8994, 0.2183, 0.812, 0.937, 0.3438, 0.712, 0.9033, 0.266, 0.8013, 0.803, 0.4993, 0.07196, 0.635, 0.7344, 0.3213}; std::vector a_data{tmp.cbegin(), tmp.cend()}; migraphx::shape b_shape{migraphx::shape::half_type, {8, 7}}; tmp = {0.7095, 0.612, 0.741, 0.02121, 0.3872, 0.4482, 0.6235, 0.02249, 0.2332, 0.7656, 0.8955, 0.8154, 0.2239, 0.9277, 0.4622, 0.708, 0.566, 0.0736, 0.138, 0.8574, 0.4055, 0.382, 0.6206, 0.424, 0.3674, 0.435, 0.998, 0.3594, 0.701, 0.6216, 0.01826, 0.6313, 0.514, 0.1095, 0.3203, 0.01636, 0.537, 0.01952, 0.4502, 0.8965, 0.5415, 0.7456, 0.793, 0.756, 0.9, 0.5264, 0.05368, 0.4214, 0.276, 0.1517, 0.08453, 0.83, 0.417, 0.1682, 0.845, 0.1729}; std::vector b_data{tmp.cbegin(), tmp.cend()}; migraphx::shape c_shape{migraphx::shape::half_type, {6, 1}}; tmp = {0.10846, 0.672, 0.527, 0.94, 0.429, 0.2291}; std::vector c_data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map params; params["A"] = migraphx::argument(a_shape, a_data.data()); params["B"] = migraphx::argument(b_shape, b_data.data()); params["C"] = migraphx::argument(c_shape, c_data.data()); auto result = p.eval(params).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {1.071, 1.378, 1.465, 1.093, 0.968, 1.542, 1.145, 1.287, 1.533, 1.75, 1.338, 1.449, 1.592, 1.668, 1.265, 1.531, 1.656, 1.348, 1.2705, 1.525, 1.479, 1.754, 2.143, 2.062, 1.921, 1.836, 2.203, 1.952, 1.055, 1.225, 1.418, 1.209, 1.155, 1.42, 1.234, 1.302, 1.593, 1.368, 1.289, 1.327, 1.451, 1.394}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/greaterorequal_test.cpp000066400000000000000000000037411510465702400245770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(greaterorequal_test) { migraphx::program p = read_onnx("greaterorequal_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data1 = {0.25, 0.75, 0.9375}; std::vector data2 = {0.25, 0.74, 0.9411}; migraphx::parameter_map pp; pp["x1"] = migraphx::argument(s, data1.data()); pp["x2"] = migraphx::argument(s, data2.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.0, 1.0, 0.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_aligncorners_true_test.cpp000066400000000000000000000043701510465702400275100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_aligncorners_true_test) { migraphx::program p = read_onnx("gridsample_aligncorners_true_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0., 1., 2., 3., 4., 5.}; std::vector grid = { -1., -1., -0.5, -0.5, -0.2, -0.2, 0., 0., 0., 0., -0.2, -0.2, 0.5, 0.5, 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0000, 1.2500, 2.0000, 2.5000, 2.5000, 2.0000, 3.7500, 5.0000}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_bf16_test.cpp000066400000000000000000000060151510465702400245170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_bf16_test) { migraphx::program p = read_onnx("gridsample_bf16_test.onnx"); migraphx::compile_options options; p.compile(migraphx::make_target("ref")); migraphx::shape data_shape{migraphx::shape::bf16_type, {1, 1, 4, 4}}; migraphx::shape grid_shape{migraphx::shape::float_type, {1, 6, 6, 2}}; std::vector tmp = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15.}; std::vector data = {tmp.begin(), tmp.end()}; std::vector grid = {-1., -1., -0.6, -1., -0.2, -1., 0.2, -1., 0.6, -1., 1., -1., -1., -0.6, -0.6, -0.6, -0.2, -0.6, 0.2, -0.6, 0.6, -0.6, 1., -0.6, -1., -0.2, -0.6, -0.2, -0.2, -0.2, 0.2, -0.2, 0.6, -0.2, 1., -0.2, -1., 0.2, -0.6, 0.2, -0.2, 0.2, 0.2, 0.2, 0.6, 0.2, 1., 0.2, -1., 0.6, -0.6, 0.6, -0.2, 0.6, 0.2, 0.6, 0.6, 0.6, 1., 0.6, -1., 1., -0.6, 1., -0.2, 1., 0.2, 1., 0.6, 1., 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {0., 0.15, 0.55, 0.95, 1.35, 0.75, 0.6, 1.5, 2.3, 3.1, 3.9, 2.1, 2.2, 4.7, 5.5, 6.3, 7.1, 3.7, 3.8, 7.9, 8.7, 9.5, 10.3, 5.3, 5.4, 11.1, 11.9, 12.7, 13.5, 6.9, 3., 6.15, 6.55, 6.95, 7.35, 3.75}; std::vector gold = {tmp.begin(), tmp.end()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_bicubic_align_corners_0_additional_1_test.cpp000066400000000000000000000044731510465702400331230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_bicubic_align_corners_0_additional_1_test) { migraphx::program p = read_onnx("gridsample_bicubic_align_corners_0_additional_1_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0., 1., 2., 3., 4., 5.}; std::vector grid = { -1., -0.8, -0.6, -0.5, -0.1, -0.2, 0.7, 0., 0., 0.4, 0.2, -0.2, -0.3, 0.5, -1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { -0.173250, 0.284265, 1.923106, 2.568000, 5.170375, 2.284414, 4.744844, 1.046875}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_bicubic_align_corners_1_additional_1_test.cpp000066400000000000000000000044721510465702400331230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_bicubic_align_corners_1_additional_1_test) { migraphx::program p = read_onnx("gridsample_bicubic_align_corners_1_additional_1_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0., 1., 2., 3., 4., 5.}; std::vector grid = { -1., -0.8, -0.6, -0.5, -0.1, -0.2, 0.7, 0., 0., 0.4, 0.2, -0.2, -0.3, 0.5, -1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.304001, 1.128750, 2.266270, 3.144844, 4.531500, 2.455360, 4.599819, 4.000000}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_bicubic_test.cpp000066400000000000000000000043441510465702400253640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_bicubic_test) { migraphx::program p = read_onnx("gridsample_bicubic_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0., 1., 2., 3., 4., 5.}; std::vector grid = { -1., -1., -0.5, -0.5, -0.2, -0.2, 0., 0., 0., 0., -0.2, -0.2, 0.5, 0.5, 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-0.1406, 0.3828, 1.7556, 2.9688, 2.9688, 1.7556, 5.1445, 1.3906}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_bilinear_align_corners_0_additional_1_test.cpp000066400000000000000000000044221510465702400333020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_bilinear_align_corners_0_additional_1_test) { migraphx::program p = read_onnx("gridsample_bilinear_align_corners_0_additional_1_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}; std::vector grid = { -1., -0.8, -0.6, -0.5, -0.1, -0.2, 0.7, 0., 0., 0.4, 0.2, -0.2, -0.3, 0.5, -1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0, 0.45, 1.8, 2.4, 3.7, 2.1, 3.7, 1.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_bilinear_align_corners_1_additional_1_test.cpp000066400000000000000000000044241510465702400333050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_bilinear_align_corners_1_additional_1_test) { migraphx::program p = read_onnx("gridsample_bilinear_align_corners_1_additional_1_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}; std::vector grid = { -1., -0.8, -0.6, -0.5, -0.1, -0.2, 0.7, 0., 0., 0.4, 0.2, -0.2, -0.3, 0.5, -1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.4, 1.2, 2.05, 2.85, 3.3, 2.2, 3.35, 4.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_bilinear_test.cpp000066400000000000000000000043231510465702400255460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_bilinear_test) { migraphx::program p = read_onnx("gridsample_bilinear_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}; std::vector grid = { -1., -1., -0.5, -0.5, -0.2, -0.2, 0., 0., 0., 0., -0.2, -0.2, 0.5, 0.5, 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0., 0.5, 1.7, 2.5, 2.5, 1.7, 4.5, 1.25}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_border_padding_test.cpp000066400000000000000000000043661510465702400267330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_border_padding_test) { migraphx::program p = read_onnx("gridsample_border_padding_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0., 1., 2., 3., 4., 5.}; std::vector grid = { -10., -10., -5., -5., -0.2, -0.2, 10., 10., 10., 10., -0.2, -0.2, 5., 5., 10., 10.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0000, 0.0000, 1.7000, 5.0000, 5.0000, 1.7000, 5.0000, 5.0000}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_channel_test.cpp000066400000000000000000000117351510465702400253760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_channel_test) { migraphx::program p = read_onnx("gridsample_channel_test.onnx"); migraphx::compile_options options; p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 3, 4, 4}}; migraphx::shape grid_shape{input_type, {1, 6, 6, 2}}; std::vector data = { 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., }; std::vector grid = {-1., -1., -0.6, -1., -0.2, -1., 0.2, -1., 0.6, -1., 1., -1., -1., -0.6, -0.6, -0.6, -0.2, -0.6, 0.2, -0.6, 0.6, -0.6, 1., -0.6, -1., -0.2, -0.6, -0.2, -0.2, -0.2, 0.2, -0.2, 0.6, -0.2, 1., -0.2, -1., 0.2, -0.6, 0.2, -0.2, 0.2, 0.2, 0.2, 0.6, 0.2, 1., 0.2, -1., 0.6, -0.6, 0.6, -0.2, 0.6, 0.2, 0.6, 0.6, 0.6, 1., 0.6, -1., 1., -0.6, 1., -0.2, 1., 0.2, 1., 0.6, 1., 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // clang-format off /* Generated with the following Python script: import torch from torch import nn input = torch.tensor([[[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]], [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]], [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]]], dtype=torch.float) grid = torch.tensor([[[[-1, -1], [-0.6, -1], [-0.2, -1], [0.2, -1], [0.6, -1], [1, -1]], [[-1., -0.6], [-0.6, -0.6], [-0.2, -0.6], [0.2, -0.6], [0.6, -0.6], [1., -0.6]], [[-1., -0.2], [-0.6, -0.2], [-0.2, -0.2], [0.2, -0.2], [0.6, -0.2], [1., -0.2]], [[-1., 0.2], [-0.6, 0.2], [-0.2, 0.2], [0.2, 0.2], [0.6, 0.2], [1., 0.2]], [[-1., 0.6], [-0.6, 0.6], [-0.2, 0.6], [0.2, 0.6], [0.6, 0.6], [1., 0.6]], [[-1., 1.], [-0.6, 1.], [-0.2, 1.], [0.2, 1.], [0.6, 1.], [1., 1.]]]], dtype=torch.float) output = nn.functional.grid_sample(input, grid, mode='bilinear', padding_mode='border', align_corners=True) print(output) */ // clang-format on std::vector gold = {0., 0.6, 1.2, 1.8, 2.4, 3., 2.4, 3., 3.6, 4.2, 4.8, 5.4, 4.8, 5.4, 6., 6.6, 7.2, 7.8, 7.2, 7.8, 8.4, 9., 9.6, 10.2, 9.6, 10.2, 10.8, 11.4, 12., 12.6, 12., 12.6, 13.2, 13.8, 14.4, 15., 0., 0.6, 1.2, 1.8, 2.4, 3., 2.4, 3., 3.6, 4.2, 4.8, 5.4, 4.8, 5.4, 6., 6.6, 7.2, 7.8, 7.2, 7.8, 8.4, 9., 9.6, 10.2, 9.6, 10.2, 10.8, 11.4, 12., 12.6, 12., 12.6, 13.2, 13.8, 14.4, 15., 0., 0.6, 1.2, 1.8, 2.4, 3., 2.4, 3., 3.6, 4.2, 4.8, 5.4, 4.8, 5.4, 6., 6.6, 7.2, 7.8, 7.2, 7.8, 8.4, 9., 9.6, 10.2, 9.6, 10.2, 10.8, 11.4, 12., 12.6, 12., 12.6, 13.2, 13.8, 14.4, 15.}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_half_test.cpp000066400000000000000000000060151510465702400246730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_half_test) { migraphx::program p = read_onnx("gridsample_half_test.onnx"); migraphx::compile_options options; p.compile(migraphx::make_target("ref")); migraphx::shape data_shape{migraphx::shape::half_type, {1, 1, 4, 4}}; migraphx::shape grid_shape{migraphx::shape::float_type, {1, 6, 6, 2}}; std::vector tmp = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15.}; std::vector data = {tmp.begin(), tmp.end()}; std::vector grid = {-1., -1., -0.6, -1., -0.2, -1., 0.2, -1., 0.6, -1., 1., -1., -1., -0.6, -0.6, -0.6, -0.2, -0.6, 0.2, -0.6, 0.6, -0.6, 1., -0.6, -1., -0.2, -0.6, -0.2, -0.2, -0.2, 0.2, -0.2, 0.6, -0.2, 1., -0.2, -1., 0.2, -0.6, 0.2, -0.2, 0.2, 0.2, 0.2, 0.6, 0.2, 1., 0.2, -1., 0.6, -0.6, 0.6, -0.2, 0.6, 0.2, 0.6, 0.6, 0.6, 1., 0.6, -1., 1., -0.6, 1., -0.2, 1., 0.2, 1., 0.6, 1., 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {0., 0.15, 0.55, 0.95, 1.35, 0.75, 0.6, 1.5, 2.3, 3.1, 3.9, 2.1, 2.2, 4.7, 5.5, 6.3, 7.1, 3.7, 3.8, 7.9, 8.7, 9.5, 10.3, 5.3, 5.4, 11.1, 11.9, 12.7, 13.5, 6.9, 3., 6.15, 6.55, 6.95, 7.35, 3.75}; std::vector gold = {tmp.begin(), tmp.end()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_int_test.cpp000066400000000000000000000055101510465702400245520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_int_test) { migraphx::program p = read_onnx("gridsample_int_test.onnx"); migraphx::compile_options options; p.compile(migraphx::make_target("ref")); migraphx::shape data_shape{migraphx::shape::int32_type, {1, 1, 4, 4}}; migraphx::shape grid_shape{migraphx::shape::float_type, {1, 6, 6, 2}}; std::vector data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; std::vector grid = {-1., -1., -0.6, -1., -0.2, -1., 0.2, -1., 0.6, -1., 1., -1., -1., -0.6, -0.6, -0.6, -0.2, -0.6, 0.2, -0.6, 0.6, -0.6, 1., -0.6, -1., -0.2, -0.6, -0.2, -0.2, -0.2, 0.2, -0.2, 0.6, -0.2, 1., -0.2, -1., 0.2, -0.6, 0.2, -0.2, 0.2, 0.2, 0.2, 0.6, 0.2, 1., 0.2, -1., 0.6, -0.6, 0.6, -0.2, 0.6, 0.2, 0.6, 0.6, 0.6, 1., 0.6, -1., 1., -0.6, 1., -0.2, 1., 0.2, 1., 0.6, 1., 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 0, 0, 1, 0, 0, 1, 2, 3, 3, 2, 2, 4, 5, 6, 7, 3, 3, 7, 8, 9, 10, 5, 5, 11, 11, 12, 13, 6, 3, 6, 6, 6, 7, 3}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_nearest_align_corners_0_additional_1_test.cpp000066400000000000000000000044171510465702400331620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_nearest_align_corners_0_additional_1_test) { migraphx::program p = read_onnx("gridsample_nearest_align_corners_0_additional_1_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}; std::vector grid = { -1., -0.8, -0.6, -0.5, -0.1, -0.2, 0.7, 0., 0., 0.4, 0.2, -0.2, -0.3, 0.5, -1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0, 0.0, 2.0, 3.0, 4.0, 3.0, 4.0, 4.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_nearest_align_corners_1_additional_1_test.cpp000066400000000000000000000044171510465702400331630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_nearest_align_corners_1_additional_1_test) { migraphx::program p = read_onnx("gridsample_nearest_align_corners_1_additional_1_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}; std::vector grid = { -1., -0.8, -0.6, -0.5, -0.1, -0.2, 0.7, 0., 0., 0.4, 0.2, -0.2, -0.3, 0.5, -1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0, 0.0, 2.0, 3.0, 2.0, 3.0, 4.0, 4.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_nearest_test.cpp000066400000000000000000000043131510465702400254210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_nearest_test) { migraphx::program p = read_onnx("gridsample_nearest_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0., 1., 2., 3., 4., 5.}; std::vector grid = { -1., -1., -0.5, -0.5, -0.2, -0.2, 0., 0., 0., 0., -0.2, -0.2, 0.5, 0.5, 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0, 0.0, 2.0, 2.0, 2.0, 2.0, 5.0, 0.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_reflection_padding_test.cpp000066400000000000000000000043761510465702400276110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_reflection_padding_test) { migraphx::program p = read_onnx("gridsample_reflection_padding_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0., 1., 2., 3., 4., 5.}; std::vector grid = { -10., -10., -5., -5., -0.2, -0.2, 10., 10., 10., 10., -0.2, -0.2, 5., 5., 10., 10.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {2.5000, 0.0000, 1.7000, 2.5000, 2.5000, 1.7000, 5.0000, 2.5000}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_test.cpp000066400000000000000000000057641510465702400237130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_test) { migraphx::program p = read_onnx("gridsample_test.onnx"); migraphx::compile_options options; p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 4, 4}}; migraphx::shape grid_shape{input_type, {1, 6, 6, 2}}; std::vector data = { 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15.}; std::vector grid = {-1., -1., -0.6, -1., -0.2, -1., 0.2, -1., 0.6, -1., 1., -1., -1., -0.6, -0.6, -0.6, -0.2, -0.6, 0.2, -0.6, 0.6, -0.6, 1., -0.6, -1., -0.2, -0.6, -0.2, -0.2, -0.2, 0.2, -0.2, 0.6, -0.2, 1., -0.2, -1., 0.2, -0.6, 0.2, -0.2, 0.2, 0.2, 0.2, 0.6, 0.2, 1., 0.2, -1., 0.6, -0.6, 0.6, -0.2, 0.6, 0.2, 0.6, 0.6, 0.6, 1., 0.6, -1., 1., -0.6, 1., -0.2, 1., 0.2, 1., 0.6, 1., 1., 1.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0., 0.15, 0.55, 0.95, 1.35, 0.75, 0.6, 1.5, 2.3, 3.1, 3.9, 2.1, 2.2, 4.7, 5.5, 6.3, 7.1, 3.7, 3.8, 7.9, 8.7, 9.5, 10.3, 5.3, 5.4, 11.1, 11.9, 12.7, 13.5, 6.9, 3., 6.15, 6.55, 6.95, 7.35, 3.75}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/gridsample_zeros_padding_test.cpp000066400000000000000000000043331510465702400266120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(gridsample_zeros_padding_test) { migraphx::program p = read_onnx("gridsample_zeros_padding_test.onnx"); p.compile(migraphx::make_target("ref")); auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, {1, 1, 3, 2}}; migraphx::shape grid_shape{input_type, {1, 2, 4, 2}}; std::vector data = {0., 1., 2., 3., 4., 5.}; std::vector grid = { -10., -10., -5., -5., -0.2, -0.2, 10., 10., 10., 10., -0.2, -0.2, 5., 5., 10., 10.}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); pp["grid"] = migraphx::argument(grid_shape, grid.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0, 0.0, 1.7, 0.0, 0.0, 1.7, 0.0, 0.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/group_norm_3d_bf16_test.cpp000066400000000000000000000041001510465702400251360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(group_norm_bf16_test) { using migraphx::bf16; std::vector scale{bf16{1.2}, bf16{0.8}, bf16{0.4}, bf16{1.6}}; std::vector bias{bf16{0.5}, bf16{0.2}, bf16{0.9}, bf16{0.4}}; std::vector result_vector = norm_test({1, 4, 2}, scale, bias, read_onnx("group_norm_3d_bf16_test.onnx")); std::vector gold = {bf16{-0.96093750}, bf16{-0.37500000}, bf16{0.78125000}, bf16{1.17187500}, bf16{0.41210938}, bf16{0.60546875}, bf16{1.56250000}, bf16{2.34375000}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/group_norm_3d_half_test.cpp000066400000000000000000000041001510465702400253120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(group_norm_half_test) { using migraphx::half; std::vector scale{half{1.2}, half{0.8}, half{0.4}, half{1.6}}; std::vector bias{half{0.5}, half{0.2}, half{0.9}, half{0.4}}; std::vector result_vector = norm_test({1, 4, 2}, scale, bias, read_onnx("group_norm_3d_half_test.onnx")); std::vector gold = {half{-1.11035156}, half{-0.03674316}, half{0.55761719}, half{1.27343750}, half{0.36328125}, half{0.72119141}, half{1.11523438}, half{2.54687500}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/group_norm_3d_test.cpp000066400000000000000000000037101510465702400243260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(group_norm_test) { std::vector scale{1.2, 0.8, 0.4, 1.6}; std::vector bias{0.5, 0.2, 0.9, 0.4}; std::vector result_vector = norm_test({1, 4, 2}, scale, bias, read_onnx("group_norm_3d_test.onnx")); std::vector gold = {-1.10996258, -0.03665423, 0.55776948, 1.27330840, 0.36334583, 0.72111529, 1.11553872, 2.54661655}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/group_norm_contrib_3d_bf16_test.cpp000066400000000000000000000042721510465702400266700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(group_norm_contrib_bf16_test) { using migraphx::bf16; std::vector gamma{bf16{1.2}, bf16{0.8}}; std::vector beta{bf16{0.5}, bf16{0.2}}; std::vector result_vector = norm_test({1, 4, 2}, gamma, beta, read_onnx("group_norm_contrib_3d_channel_last_bf16_test.onnx"), "gamma", "beta"); std::vector gold = {bf16{-1.10996256}, bf16{-0.87330837}, bf16{-0.0366542}, bf16{-0.15776947}, bf16{1.0366542}, bf16{0.55776947}, bf16{2.10996256}, bf16{1.27330837}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/group_norm_contrib_3d_half_test.cpp000066400000000000000000000042721510465702400270440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(group_norm_contrib_half_test) { using migraphx::half; std::vector gamma{half{1.2}, half{0.8}}; std::vector beta{half{0.5}, half{0.2}}; std::vector result_vector = norm_test({1, 4, 2}, gamma, beta, read_onnx("group_norm_contrib_3d_channel_last_half_test.onnx"), "gamma", "beta"); std::vector gold = {half{-1.10996256}, half{-0.87330837}, half{-0.0366542}, half{-0.15776947}, half{1.0366542}, half{0.55776947}, half{2.10996256}, half{1.27330837}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/group_norm_contrib_3d_silu_test.cpp000066400000000000000000000037641510465702400271130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(group_norm_contrib_silu_test) { std::vector gamma{1.2, 0.8, 0.4, 1.6}; std::vector beta{0.5, 0.2, 0.9, 0.4}; std::vector result_vector = norm_test( {1, 4, 2}, gamma, beta, read_onnx("group_norm_contrib_silu_3d_test.onnx"), "gamma", "beta"); std::vector gold = {-0.27513519, -0.01799127, 0.35470587, 0.99484670, 0.21431959, 0.48520428, 0.84017944, 2.36159444}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/group_norm_contrib_3d_test.cpp000066400000000000000000000041371510465702400260520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(group_norm_contrib_test) { std::vector gamma{1.2, 0.8}; std::vector beta{0.5, 0.2}; std::vector result_vector = norm_test({1, 4, 2}, gamma, beta, read_onnx("group_norm_contrib_3d_channel_last_test.onnx"), "gamma", "beta"); std::vector gold = {-1.10996256, -0.87330837, -0.0366542, -0.15776947, 1.0366542, 0.55776947, 2.10996256, 1.27330837}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/hardmax_axis_neg_test.cpp000066400000000000000000000046241510465702400250570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(hardmax_axis_neg_verify_test) { migraphx::program p = read_onnx("hardmax_axis_neg_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::half_type; migraphx::shape data_shape{input_type, input_lens}; std::vector tmp = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/hardmax_axis_neg_ver11_test.cpp000066400000000000000000000047571510465702400261040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(hardmax_axis_neg_ver11_verify_test) { migraphx::program p = read_onnx("hardmax_axis_neg_ver11_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::half_type; migraphx::shape data_shape{input_type, input_lens}; std::vector tmp = {1.22461, 1.68262, -2.0293, -0.322021, 0.469971, 0.258057, 0.754395, 2.57422, -1.68457, 0.0927734, 0.901855, -0.876465, -0.408936, 0.929688, 2.07227, -1.57031, 0.486572, -0.149292, 0.695312, -0.217896, 0.713867, 0.717285, 0.0182953, 1.34961}; std::vector data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/hardmax_axis_test.cpp000066400000000000000000000045441510465702400242270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(hardmax_axis_verify_test) { migraphx::program p = read_onnx("hardmax_axis_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::double_type; migraphx::shape data_shape{input_type, input_lens}; std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/hardmax_axis_ver11_test.cpp000066400000000000000000000045601510465702400252430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(hardmax_axis_ver11_verify_test) { migraphx::program p = read_onnx("hardmax_axis_ver11_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::double_type; migraphx::shape data_shape{input_type, input_lens}; std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/hardmax_default_test.cpp000066400000000000000000000045011510465702400247000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(hardmax_default_verify_test) { migraphx::program p = read_onnx("hardmax_default_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/hardmax_default_ver11_test.cpp000066400000000000000000000045151510465702400257230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(hardmax_default_ver11_verify_test) { migraphx::program p = read_onnx("hardmax_default_ver11_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{1, 2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/hardsigmoid_verify_test.cpp000066400000000000000000000041251510465702400254300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(hardsigmoid_verify_test) { migraphx::program p = read_onnx("hardsigmoid_verify_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {2, 5}}; std::vector data = {-10.0, -2.5, -1.0, -0.5, 0, 1.0, 2.0, 2.5, 2.6, 100.0}; float alpha = 0.2; float beta = 0.5; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold(10); std::transform(data.begin(), data.end(), gold.begin(), [&](auto x) { return std::max(0.0f, std::min(x * alpha + beta, 1.0f)); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_else_test.cpp000066400000000000000000000042171510465702400231620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_else_test) { migraphx::program p = read_onnx("if_else_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_data{migraphx::shape::float_type, {2, 3}}; std::vector data = {0.0625, 0.75, -0.0625, 0.125, -0.125, -0.5625}; migraphx::shape bool_data{migraphx::shape::bool_type, {1}}; bool b_data = false; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_data, data.data()); pp["y"] = migraphx::argument(s_data, data.data()); pp["cond"] = migraphx::argument(bool_data, &b_data); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0866565, -0.371067, 0.017719, 0.0250614, 0.0612539, -0.744683}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_else_test_inlined.cpp000066400000000000000000000040051510465702400246570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_else_test_inlined) { migraphx::program p = read_onnx("if_else_test_inlined.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_data{migraphx::shape::float_type, {2, 3}}; std::vector data = {0.0625, 0.75, -0.0625, 0.125, -0.125, -0.5625}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_data, data.data()); pp["y"] = migraphx::argument(s_data, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0507132, -0.712328, 0.0105797, 0.04569, 0.0185013, -1.16472}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_literal_test.cpp000066400000000000000000000044151510465702400236660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_literal_test) { auto run_prog = [](bool cond) { migraphx::program p = read_onnx("if_literal_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_data{migraphx::shape::bool_type}; std::vector data = {static_cast(cond)}; migraphx::parameter_map pp; pp["cond"] = migraphx::argument(s_data, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); return result_vector; }; // then branch { auto result_vector = run_prog(true); std::vector gold = {1, 2, 3, 4, 5}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } // else branch { auto result_vector = run_prog(false); std::vector gold = {5, 4, 3, 2, 1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_pl_test.cpp000066400000000000000000000051351510465702400226450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_pl_test) { auto run_prog = [](bool cond) { migraphx::program p = read_onnx("if_pl_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; migraphx::shape ys{migraphx::shape::float_type, {3, 3}}; migraphx::shape cond_s{migraphx::shape::bool_type}; std::vector x_data(xs.elements(), 1.0f); std::vector y_data(ys.elements(), 2.0f); std::vector cond_data{static_cast(cond)}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(xs, x_data.data()); pp["y"] = migraphx::argument(ys, y_data.data()); pp["cond"] = migraphx::argument(cond_s, cond_data.data()); auto result = p.eval(pp).back(); std::vector ret; result.visit([&](auto output) { ret.assign(output.begin(), output.end()); }); return ret; }; // then branch { auto result_vector = run_prog(true); std::vector gold = {2, 3, 4, 5, 6, 7}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } // else branch { auto result_vector = run_prog(false); std::vector gold = {1, 2, 3, 4, 5, 6}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_then_else_multi_output_shapes_inlined_test.cpp000066400000000000000000000047601510465702400321020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_then_else_multi_output_shapes_inlined_test) { migraphx::program p = read_onnx("if_then_else_multi_output_shapes_inlined_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_data{migraphx::shape::float_type, {2, 3, 1}}; migraphx::shape y_data{migraphx::shape::float_type, {2, 3}}; std::vector data = {0.0625, 0.75, -0.0625, 0.125, -0.125, -0.5625}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(x_data, data.data()); pp["y"] = migraphx::argument(y_data, data.data()); auto result_args = p.eval(pp); const auto& result = result_args.front(); const auto& result_b = result_args.back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector result_vector_back; result_b.visit([&](auto output) { result_vector_back.assign(output.begin(), output.end()); }); result_vector.insert(result_vector.end(), result_vector_back.begin(), result_vector_back.end()); std::vector gold = { 1.0625, 1.75, 0.9375, 1.125, 0.875, 0.4375, 0.125, 1.50, -0.125, 0.250, -0.250, -1.125}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_then_else_multi_output_shapes_test.cpp000066400000000000000000000050661510465702400304000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_then_else_multi_output_shapes_test) { migraphx::program p = read_onnx("if_then_else_multi_output_shapes_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_data{migraphx::shape::float_type, {2, 3, 1}}; std::vector data = {0.0625, 0.75, -0.0625, 0.125, -0.125, -0.5625}; migraphx::shape bool_data{migraphx::shape::bool_type, {1}}; bool b_data = true; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_data, data.data()); pp["y"] = migraphx::argument(s_data, data.data()); pp["cond"] = migraphx::argument(bool_data, &b_data); auto result_args = p.eval(pp); const auto& result = result_args.front(); const auto& result_b = result_args.back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector result_vector_back; result_b.visit([&](auto output) { result_vector_back.assign(output.begin(), output.end()); }); result_vector.insert(result_vector.end(), result_vector_back.begin(), result_vector_back.end()); std::vector gold = { 1.0625, 1.75, 0.9375, 1.125, 0.875, 0.4375, 0.125, 1.50, -0.125, 0.250, -0.250, -1.125}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_then_test.cpp000066400000000000000000000042561510465702400231730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_then_test) { migraphx::program p = read_onnx("if_then_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_data{migraphx::shape::float_type, {2, 3}}; std::vector data = {0.0625, 0.75, -0.0625, 0.125, -0.125, -0.5625}; migraphx::shape bool_data{migraphx::shape::bool_type, {1}}; bool b_data = true; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_data, data.data()); pp["y"] = migraphx::argument(s_data, data.data()); pp["cond"] = migraphx::argument(bool_data, &b_data); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // onnx adds ones so result should be just + 1.0 std::vector gold = {1.0625, 1.75, 0.9375, 1.125, 0.875, 0.4375}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_then_test_inlined.cpp000066400000000000000000000037621510465702400246760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_then_test_inlined) { migraphx::program p = read_onnx("if_then_test_inlined.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_data{migraphx::shape::float_type, {2, 3}}; std::vector data = {0.0625, 0.75, -0.0625, 0.125, -0.125, -0.5625}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_data, data.data()); pp["y"] = migraphx::argument(s_data, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.0625, 1.75, 0.9375, 1.125, 0.875, 0.4375}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/if_tuple_test.cpp000066400000000000000000000056571510465702400233740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(if_tuple_test) { auto run_prog = [](bool cond) { migraphx::program p = read_onnx("if_tuple_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape xs{migraphx::shape::float_type, {1, 4}}; migraphx::shape ys{migraphx::shape::float_type, {3, 4}}; migraphx::shape cond_s{migraphx::shape::bool_type}; std::vector x_data(xs.elements(), 1.0f); std::vector y_data(ys.elements(), 2.0f); std::vector cond_data{static_cast(cond)}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(xs, x_data.data()); pp["y"] = migraphx::argument(ys, y_data.data()); pp["cond"] = migraphx::argument(cond_s, cond_data.data()); auto results = p.eval(pp); std::vector> rets; for(const auto& arg : results) { std::vector vec; arg.visit([&](auto output) { vec.assign(output.begin(), output.end()); }); rets.push_back(vec); } return rets; }; // then branch { auto results = run_prog(true); std::vector gold0(4, 2.0f); std::vector gold1(12, 4.0f); EXPECT(migraphx::verify::verify_rms_range(results.at(0), gold0)); EXPECT(migraphx::verify::verify_rms_range(results.at(1), gold1)); } // else branch { auto results = run_prog(false); std::vector gold0(4, 3.0f); std::vector gold1(12, 5.0f); EXPECT(migraphx::verify::verify_rms_range(results.at(0), gold0)); EXPECT(migraphx::verify::verify_rms_range(results.at(1), gold1)); } } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/instance_norm_dyn_batch_test.cpp000066400000000000000000000056021510465702400264250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(instance_norm_dyn_batch_test) { migraphx::program p = read_onnx("instance_norm_dyn_batch_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::float_type, {1, 2, 3, 3}}; std::vector data0 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8}; migraphx::shape s1{migraphx::shape::float_type, {2}}; std::vector data1 = {1, 2}; migraphx::shape s2{migraphx::shape::float_type, {2}}; std::vector data2 = {0, 1}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(s0, data0.data()); pp["1"] = migraphx::argument(s1, data1.data()); pp["2"] = migraphx::argument(s2, data2.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.54919, -1.16189, -0.774596, -0.387298, 0, 0.387298, 0.774596, 1.16189, 1.54919, -2.09838, -1.32379, -0.549192, 0.225404, 1, 1.7746, 2.54919, 3.32379, 4.09838}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/instance_norm_val_3d_test.cpp000066400000000000000000000044401510465702400256410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(instance_norm_3d_test) { migraphx::program p = read_onnx("instance_norm_val_3d_test.onnx"); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vector(16); result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.52752, -1.09109, -0.654653, -0.218218, 0.218218, 0.654653, 1.09109, 1.52752, -2.05505, -1.18218, -0.309306, 0.563565, 1.43644, 2.30931, 3.18218, 4.05505}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/instance_norm_val_test.cpp000066400000000000000000000045741510465702400252630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(instance_norm_test) { migraphx::program p = read_onnx("instance_norm_val_test.onnx"); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vector(9); result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.54919, -1.16189, -0.774596, -0.387298, 0, 0.387298, 0.774596, 1.16189, 1.54919, -2.09838, -1.32379, -0.549192, 0.225404, 1, 1.7746, 2.54919, 3.32379, 4.09838}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/isinf_bf16_test.cpp000066400000000000000000000044731510465702400235060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(isinf_bf16_test) { migraphx::program p = read_onnx("isinf_bf16_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::bf16_type, {2, 3}}; migraphx::parameter_map pp; migraphx::bf16 nan = std::numeric_limits::quiet_NaN(); migraphx::bf16 infinity = std::numeric_limits::infinity(); migraphx::bf16 max = std::numeric_limits::max(); migraphx::bf16 min = std::numeric_limits::min(); migraphx::bf16 val = migraphx::bf16(3.6); std::vector data = {-infinity, nan, min, val, max, infinity}; pp["t1"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 0, 0, 0, 0, 1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/isinf_double_pos_test.cpp000066400000000000000000000042751510465702400251030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(isinf_double_pos_test) { migraphx::program p = read_onnx("isinf_double_pos_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::double_type, {2, 3}}; migraphx::parameter_map pp; double nan = std::numeric_limits::quiet_NaN(); double infinity = std::numeric_limits::infinity(); double max = std::numeric_limits::max(); double min = std::numeric_limits::min(); std::vector data = {-infinity, nan, min, 3.6, max, infinity}; pp["t1"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 0, 0, 0, 1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/isinf_half_test.cpp000066400000000000000000000044731510465702400236620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(isinf_half_test) { migraphx::program p = read_onnx("isinf_half_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::half_type, {2, 3}}; migraphx::parameter_map pp; migraphx::half nan = std::numeric_limits::quiet_NaN(); migraphx::half infinity = std::numeric_limits::infinity(); migraphx::half max = std::numeric_limits::max(); migraphx::half min = std::numeric_limits::min(); migraphx::half val = migraphx::half(3.6); std::vector data = {-infinity, nan, min, val, max, infinity}; pp["t1"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 0, 0, 0, 0, 1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/isinf_neg_test.cpp000066400000000000000000000042441510465702400235150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(isinf_neg_test) { migraphx::program p = read_onnx("isinf_neg_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::parameter_map pp; float nan = std::numeric_limits::quiet_NaN(); float infinity = std::numeric_limits::infinity(); float max = std::numeric_limits::max(); float min = std::numeric_limits::min(); std::vector data = {-infinity, nan, min, 3.6, max, infinity}; pp["t1"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/isinf_no_detect_test.cpp000066400000000000000000000042661510465702400247140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(isinf_no_detect_test) { migraphx::program p = read_onnx("isinf_no_detect_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::parameter_map pp; float nan = std::numeric_limits::quiet_NaN(); float infinity = std::numeric_limits::infinity(); float max = std::numeric_limits::max(); float min = std::numeric_limits::min(); std::vector data = {-infinity, nan, min, 3.6, max, infinity}; pp["t1"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/layer_norm_3d_bf16_test.cpp000066400000000000000000000040261510465702400251250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(layer_norm_bf16_test) { using migraphx::bf16; std::vector scale{bf16{1.2}, bf16{0.8}}; std::vector bias{bf16{0.5}, bf16{0.2}}; std::vector result_vector = norm_test({1, 4, 2}, scale, bias, read_onnx("layer_norm_3d_bf16_test.onnx")); std::vector gold = {bf16{-0.69997597}, bf16{0.99998398}, bf16{-0.69997597}, bf16{0.99998398}, bf16{-0.69997597}, bf16{0.99998398}, bf16{-0.69997597}, bf16{0.99998398}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/layer_norm_3d_half_test.cpp000066400000000000000000000040261510465702400253010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(layer_norm_half_test) { using migraphx::half; std::vector scale{half{1.2}, half{0.8}}; std::vector bias{half{0.5}, half{0.2}}; std::vector result_vector = norm_test({1, 4, 2}, scale, bias, read_onnx("layer_norm_3d_half_test.onnx")); std::vector gold = {half{-0.69997597}, half{0.99998398}, half{-0.69997597}, half{0.99998398}, half{-0.69997597}, half{0.99998398}, half{-0.69997597}, half{0.99998398}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/layer_norm_3d_test.cpp000066400000000000000000000036661510465702400243200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(layer_norm_test) { std::vector scale{1.2, 0.8}; std::vector bias{0.5, 0.2}; std::vector result_vector = norm_test({1, 4, 2}, scale, bias, read_onnx("layer_norm_3d_test.onnx")); std::vector gold = {-0.69997597, 0.99998398, -0.69997597, 0.99998398, -0.69997597, 0.99998398, -0.69997597, 0.99998398}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/lessorequal_test.cpp000066400000000000000000000037251510465702400241160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(lessorequal_test) { migraphx::program p = read_onnx("lessorequal_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data1 = {0.25, 0.75, 0.9375}; std::vector data2 = {0.25, 0.74, 0.9411}; migraphx::parameter_map pp; pp["x1"] = migraphx::argument(s, data1.data()); pp["x2"] = migraphx::argument(s, data2.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 0, 1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/lpnormalization_l1_test.cpp000066400000000000000000000045011510465702400253660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(lpnormalization_1norm) { migraphx::program p = read_onnx("lpnormalization_l1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {3, 4}}; std::vector data{0.f, 2.f, -2.f, 1.f, 1.f, -5.f, 3.f, -1.f, -4.f, 3.f, 0.f, 0.f}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{0.f, 2.f / 5.f, -2.f / 5.f, 1.f / 5.f, 1.f / 10.f, -5.f / 10.f, 3.f / 10.f, -1.f / 10.f, -4.f / 7.f, 3.f / 7.f, 0.f, 0.f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/lpnormalization_l2_test.cpp000066400000000000000000000044751510465702400254010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(lpnormalization_2norm) { migraphx::program p = read_onnx("lpnormalization_l2_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {3, 4}}; std::vector data{0.f, 2.f, -2.f, 1.f, 1.f, -5.f, 3.f, -1.f, -4.f, 3.f, 0.f, 0.f}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{0.f, 2.f / 3.f, -2.f / 3.f, 1.f / 3.f, 1.f / 6.f, -5.f / 6.f, 3.f / 6.f, -1.f / 6.f, -4.f / 5.f, 3.f / 5.f, 0.f, 0.f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/main.cpp000066400000000000000000000023741510465702400214430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/matmulinteger_int8_uint8_dual_zp_test.cpp000066400000000000000000000042161510465702400302370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(matmulinteger_int8_uint8_dual_zp_test) { migraphx::program p = read_onnx("matmulinteger_int8_uint8_dual_zp_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::int8_type, {4, 3}}; std::vector data0 = {-1, 5, -9, -2, 6, 10, -3, 7, -11, -4, 8, 0}; migraphx::shape s1{migraphx::shape::uint8_type, {3, 2}}; std::vector data1 = {128, 129, 126, 131, 124, 133}; migraphx::parameter_map pp; pp["1"] = migraphx::argument(s0, data0.data()); pp["2"] = migraphx::argument(s1, data1.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-984, 992, 1351, -1362, -1234, 1244, 117, -118}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/matmulinteger_int8_uint8_one_zp_test.cpp000066400000000000000000000042001510465702400300640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(matmulinteger_int8_uint8_one_zp_test) { migraphx::program p = read_onnx("matmulinteger_int8_uint8_one_zp_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::int8_type, {4, 3}}; std::vector data0 = {-1, 5, -9, -2, 6, 10, -3, 7, -11, -4, 8, 0}; migraphx::shape s1{migraphx::shape::uint8_type, {3, 2}}; std::vector data1 = {128, 129, 126, 131, 124, 133}; migraphx::parameter_map pp; pp["1"] = migraphx::argument(s0, data0.data()); pp["2"] = migraphx::argument(s1, data1.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {56, -76, -22, 21, 60, -82, 14, -25}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/matmulinteger_int8_uint8_test.cpp000066400000000000000000000041621510465702400265210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(matmulinteger_int8_uint8_test) { migraphx::program p = read_onnx("matmulinteger_int8_uint8_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::int8_type, {4, 3}}; std::vector data0 = {-1, 5, -9, -2, 6, 10, -3, 7, -11, -4, 8, 0}; migraphx::shape s1{migraphx::shape::uint8_type, {3, 2}}; std::vector data1 = {128, 129, 126, 131, 124, 133}; migraphx::parameter_map pp; pp["1"] = migraphx::argument(s0, data0.data()); pp["2"] = migraphx::argument(s1, data1.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {26, -31, -52, 66, 30, -37, -16, 20}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/matmulinteger_uns_test.cpp000066400000000000000000000041501510465702400253120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(matmulinteger_uns_test) { migraphx::program p = read_onnx("matmulinteger_uns_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::uint8_type, {4, 3}}; std::vector data0 = {11, 7, 3, 10, 6, 2, 9, 5, 1, 8, 4, 0}; migraphx::shape s1{migraphx::shape::uint8_type, {3, 2}}; std::vector data1 = {1, 4, 2, 5, 3, 6}; migraphx::parameter_map pp; pp["1"] = migraphx::argument(s0, data0.data()); pp["2"] = migraphx::argument(s1, data1.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {45730, 44641, 46108, 45010, 46486, 45379, 46864, 45748}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/matmulinteger_uns_zp_test.cpp000066400000000000000000000041241510465702400260240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(matmulinteger_uns_zp_test) { migraphx::program p = read_onnx("matmulinteger_uns_zp_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::uint8_type, {4, 3}}; std::vector data0 = {11, 7, 3, 10, 6, 2, 9, 5, 1, 8, 4, 0}; migraphx::shape s1{migraphx::shape::uint8_type, {3, 2}}; std::vector data1 = {1, 4, 2, 5, 3, 6}; migraphx::parameter_map pp; pp["1"] = migraphx::argument(s0, data0.data()); pp["2"] = migraphx::argument(s1, data1.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {13, 76, 10, 64, 7, 52, 4, 40}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/matmulintegertofloat_int8_uint8_scales_zp_bias.cpp000066400000000000000000000057201510465702400321150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(matmulintegertofloat_int8_uint8_scales_zp_bias_test) { migraphx::program p = read_onnx("matmulintegertofloat_zp_bias_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::int8_type, {4, 3}}; std::vector data0 = {-1, 5, -9, -2, 6, 10, -3, 7, -11, -4, 8, 0}; migraphx::shape s1{migraphx::shape::uint8_type, {3, 2}}; std::vector data1 = {128, 129, 126, 131, 124, 133}; migraphx::shape scale1_s{migraphx::shape::float_type, {3}}; std::vector scale1 = {1.0f, 1.0f, 1.0f}; migraphx::shape scale2_s{migraphx::shape::float_type, {2}}; std::vector scale2 = {2.0f, 2.0f}; migraphx::shape zp1_s{migraphx::shape::int8_type, {3}}; std::vector zp1 = {1, 2, 3}; migraphx::shape zp2_s{migraphx::shape::uint8_type, {2}}; std::vector zp2 = {3, 5}; migraphx::shape bias_s{migraphx::shape::float_type, {2}}; std::vector bias = {-10.0f, -1.0f}; migraphx::parameter_map pp; pp["1"] = migraphx::argument(s0, data0.data()); pp["2"] = migraphx::argument(s1, data1.data()); pp["3"] = migraphx::argument(scale1_s, scale1.data()); pp["4"] = migraphx::argument(scale2_s, scale2.data()); pp["5"] = migraphx::argument(zp1_s, zp1.data()); pp["6"] = migraphx::argument(zp2_s, zp2.data()); pp["7"] = migraphx::argument(bias_s, bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2656, -2811, 1938, 2057, -3148, -3315, -490, -495}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/matmulintegertofloat_int8_uint8_scales_zp_bias_3d.cpp000066400000000000000000000064421510465702400325050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(matmulintegertofloat_int8_uint8_scales_zp_bias_3d_test) { migraphx::program p = read_onnx("matmulintegertofloat_zp_bias_3d_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::int8_type, {4, 3, 2}}; std::vector data0 = {-1, 5, -9, -2, 6, 10, -3, 7, -11, -4, 8, 0, -1, 5, -9, -2, 6, 10, -3, 7, -11, -4, 8, 0}; migraphx::shape s1{migraphx::shape::uint8_type, {4, 2, 3}}; std::vector data1(s1.elements(), 0); std::iota(data1.begin(), data1.end(), 0); migraphx::shape scale1_s{migraphx::shape::float_type, {2}}; std::vector scale1 = {1.0f, 1.0f}; migraphx::shape scale2_s{migraphx::shape::float_type, {3}}; std::vector scale2 = {2.0f, 2.0f, 2.0f}; migraphx::shape zp1_s{migraphx::shape::int8_type, {2}}; std::vector zp1 = {1, 3}; migraphx::shape zp2_s{migraphx::shape::uint8_type, {3}}; std::vector zp2 = {3, 5, 1}; migraphx::shape bias_s{migraphx::shape::float_type, {3}}; std::vector bias = {-10.0f, -1.0f, 0.0f}; migraphx::parameter_map pp; pp["1"] = migraphx::argument(s0, data0.data()); pp["2"] = migraphx::argument(s1, data1.data()); pp["3"] = migraphx::argument(scale1_s, scale1.data()); pp["4"] = migraphx::argument(scale2_s, scale2.data()); pp["5"] = migraphx::argument(zp1_s, zp1.data()); pp["6"] = migraphx::argument(zp2_s, zp2.data()); pp["7"] = migraphx::argument(bias_s, bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {22, 13, 12, 70, 91, -60, -20, -53, 66, 34, 25, 24, -146, -117, -308, 16, -1, 38, 22, 13, 12, -290, -269, -420, 268, 235, 354, 34, 25, 24, -602, -573, -764, 112, 95, 134}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/matmulnbits_tests.cpp000066400000000000000000000214761510465702400243040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "migraphx/argument.hpp" #include "migraphx/module.hpp" #include #include #include #include #include TEST_CASE(matmulnbits_mm_test) { auto p = optimize_onnx("matmulnbits_mm_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; auto a_shape = migraphx::shape{migraphx::shape::float_type, {2, 16}}; std::vector a(a_shape.elements()); std::iota(a.begin(), a.end(), 0); pm["a"] = migraphx::argument(a_shape, a.data()); auto b_shape = migraphx::shape{migraphx::shape::uint8_type, {4, 1, 8}}; std::vector b{0x2, 0xe3, 0xc7, 0x89, 0xbd, 0xbe, 0x50, 0x41, 0xe9, 0xb4, 0xd4, 0x54, 0xc6, 0xb2, 0xfa, 0x27, 0x14, 0x3d, 0xbb, 0xe7, 0xa5, 0x0, 0x52, 0x28, 0xc1, 0xd9, 0x1f, 0x33, 0x16, 0x1e, 0x8b, 0x3c}; pm["b"] = migraphx::argument(b_shape, b.data()); auto scales_shape = migraphx::shape{migraphx::shape::float_type, {4}}; std::vector scales{1, 2, 3, 4}; pm["scales"] = migraphx::argument(scales_shape, scales.data()); auto zp_shape = migraphx::shape{migraphx::shape::uint8_type, {4}}; std::vector zp{0x08, 0x09, 0x0a, 0x0b}; pm["zp"] = migraphx::argument{zp_shape, zp.data()}; auto result = p.eval(pm).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{ -111.0f, -290.0f, -1692.0f, -1960.0f, -335.0f, -770.0f, -4764.0f, -5992.0f}; EXPECT(result.get_shape().lens() == std::vector{2, 4}); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(matmulnbits_mm2_test) { auto p = optimize_onnx("matmulnbits_mm2_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; auto a_shape = migraphx::shape{migraphx::shape::float_type, {2, 33}}; std::vector a{ 0.15541f, 0.24434f, 0.66716f, 0.13632f, 0.76915f, 0.21328f, 0.17331f, 0.93251f, 0.14816f, 0.08181f, 0.54035f, 0.86664f, 0.92605f, 0.89766f, 0.02441f, 0.33504f, 0.60488f, 0.25918f, 0.64644f, 0.98881f, 0.27669f, 0.94888f, 0.21201f, 0.33377f, 0.95608f, 0.40923f, 0.66899f, 0.58904f, 0.41560f, 0.87399f, 0.74596f, 0.10849f, 0.94527f, 0.88573f, 0.66875f, 0.57536f, 0.81454f, 0.15699f, 0.15464f, 0.17399f, 0.08090f, 0.99368f, 0.45535f, 0.92528f, 0.91968f, 0.76970f, 0.59638f, 0.23635f, 0.54877f, 0.96025f, 0.48969f, 0.55297f, 0.52498f, 0.29102f, 0.01359f, 0.77372f, 0.81897f, 0.03003f, 0.00822f, 0.55477f, 0.54635f, 0.91918f, 0.76486f, 0.73698f, 0.29821f, 0.41801f}; pm["a"] = migraphx::argument(a_shape, a.data()); auto b_shape = migraphx::shape{migraphx::shape::uint8_type, {2, 3, 8}}; std::vector b{0x18, 0x9, 0x8b, 0xe1, 0xfb, 0x94, 0x11, 0x56, 0x4e, 0xac, 0xd3, 0x4b, 0xf7, 0x8e, 0x54, 0xef, 0x0b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6e, 0xb7, 0x20, 0x4f, 0xa7, 0x82, 0x83, 0xbf, 0x20, 0xde, 0xa4, 0xf, 0x72, 0x81, 0x8, 0x83, 0x0a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; pm["b"] = migraphx::argument(b_shape, b.data()); auto scales_shape = migraphx::shape{migraphx::shape::float_type, {6}}; std::vector scales{0.29033, 0.80435, 2.60200, 2.39623, 1.40796, 2.38139}; pm["scales"] = migraphx::argument(scales_shape, scales.data()); auto result = p.eval(pm).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{18.54672f, -62.38305f, 4.978874f, -31.228657f}; EXPECT(result.get_shape().lens() == std::vector{2, 2}); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(matmulnbits_vm_test) { auto p = optimize_onnx("matmulnbits_vm_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; auto a_shape = migraphx::shape{migraphx::shape::float_type, {20}}; std::vector a{0.10266f, 0.12772f, 0.10865f, 0.66181f, 0.49644f, 0.30307f, 0.11225f, 0.65619f, 0.06290f, 0.29208f, 0.63246f, 0.22758f, 0.99302f, 0.09735f, 0.68126f, 0.93334f, 0.90533f, 0.31082f, 0.58161f, 0.61385f}; pm["a"] = migraphx::argument(a_shape, a.data()); auto b_shape = migraphx::shape{migraphx::shape::uint8_type, {3, 2, 8}}; std::vector b{0xb7, 0x55, 0xfc, 0xc3, 0x66, 0xf9, 0x97, 0x83, 0xdd, 0x79, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcb, 0x52, 0xaf, 0x1d, 0x85, 0xbb, 0x64, 0x60, 0x23, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0xc6, 0xf7, 0x7a, 0x68, 0xb1, 0x5, 0xc3, 0x37, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; pm["b"] = migraphx::argument(b_shape, b.data()); auto scales_shape = migraphx::shape{migraphx::shape::float_type, {6}}; std::vector scales{3.74611f, 0.29444f, 0.29047f, 0.55739f, 3.94635f, 2.86177f}; pm["scales"] = migraphx::argument(scales_shape, scales.data()); auto zp_shape = migraphx::shape{migraphx::shape::uint8_type, {3}}; std::vector zp{0x43, 0x28, 0x65}; pm["zp"] = migraphx::argument{zp_shape, zp.data()}; auto result = p.eval(pm).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{131.22989f, -1.9659958f, 75.00621f}; EXPECT(result.get_shape().lens() == std::vector{3}); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(matmulnbits_bmm_test) { auto p = optimize_onnx("matmulnbits_bmm_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; auto a_shape = migraphx::shape{migraphx::shape::float_type, {2, 3, 8}}; std::vector a{0.01602f, 0.41420f, 0.97385f, 0.31764f, 0.40434f, 0.46265f, 0.93490f, 0.16076f, 0.62340f, 0.39614f, 0.45347f, 0.98619f, 0.65113f, 0.56039f, 0.33137f, 0.51959f, 0.70136f, 0.73935f, 0.95997f, 0.25623f, 0.26716f, 0.27764f, 0.52128f, 0.55242f, 0.31295f, 0.54679f, 0.43674f, 0.21178f, 0.99311f, 0.86172f, 0.10848f, 0.34330f, 0.36977f, 0.00948f, 0.93841f, 0.88137f, 0.31069f, 0.39034f, 0.22825f, 0.29626f, 0.22664f, 0.51612f, 0.39870f, 0.73411f, 0.07540f, 0.36283f, 0.62662f, 0.49075f}; pm["a"] = migraphx::argument(a_shape, a.data()); auto b_shape = migraphx::shape{migraphx::shape::uint8_type, {2, 1, 8}}; std::vector b{ 0xed, 0xf8, 0xa0, 0xac, 0x0, 0x0, 0x0, 0x0, 0x34, 0xf7, 0x42, 0x1f, 0x0, 0x0, 0x0, 0x0}; pm["b"] = migraphx::argument(b_shape, b.data()); auto scales_shape = migraphx::shape{migraphx::shape::float_type, {2}}; std::vector scales{1.43507, 1.28074}; pm["scales"] = migraphx::argument(scales_shape, scales.data()); auto result = p.eval(pm).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{ 9.386047f, 0.32900935f, 15.317321f, -7.0316725f, 16.28011f, -11.014428f, 1.7608745f, -17.91667f, 11.302611f, -0.2521392f, 18.625961f, 0.38458022f, }; EXPECT(result.get_shape().lens() == std::vector{2, 3, 2}); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mean_broadcast_test.cpp000066400000000000000000000046741510465702400245250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(mean_broadcast_test) { migraphx::program p = read_onnx("mean_broadcast_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s0{migraphx::shape::float_type, {1, 3, 4}}; std::vector data0(12, 1); migraphx::shape s1{migraphx::shape::float_type, {1, 2, 3, 4}}; std::vector data1(24, 2); migraphx::shape s2{migraphx::shape::float_type, {4}}; std::vector data2(4, 3); migraphx::shape s3{migraphx::shape::float_type, {1}}; std::vector data3(1, 4); migraphx::shape s4{migraphx::shape::float_type, {2, 3, 1}}; std::vector data4(6, 5); migraphx::parameter_map pp; pp["0"] = migraphx::argument(s0, data0.data()); pp["1"] = migraphx::argument(s1, data1.data()); pp["2"] = migraphx::argument(s2, data2.data()); pp["3"] = migraphx::argument(s3, data3.data()); pp["4"] = migraphx::argument(s4, data4.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold(24, 3); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mean_integral_test.cpp000066400000000000000000000044331510465702400243610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(mean_integral_test) { migraphx::program p = read_onnx("mean_integral_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::int32_type, {2, 2, 2}}; const int num_elms = 8; const int num_data = 10; const std::vector scalars{1, 5, 14, 2, 6, 21, 101, 0, -4, -11}; std::vector> data; std::transform(scalars.begin(), scalars.end(), std::back_inserter(data), [&](const auto i) { return std::vector(num_elms, i); }); migraphx::parameter_map pp; for(std::size_t i = 0; i < num_data; ++i) pp[std::to_string(i)] = migraphx::argument(s, data[i].data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); const auto mean = std::accumulate(scalars.begin(), scalars.end(), 0) / num_data; std::vector gold(num_elms, mean); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mean_test.cpp000066400000000000000000000044561510465702400225010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(mean_test) { migraphx::program p = read_onnx("mean_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::double_type, {2, 2, 2}}; const int num_elms = 8; const int num_data = 10; const std::vector scalars{1.0, 2.0, -2.5, 3.3, 10.7, -1.0, 100.0, 7.9, 0.01, -56.8}; std::vector> data; std::transform(scalars.begin(), scalars.end(), std::back_inserter(data), [&](const auto& i) { return std::vector(num_elms, i); }); migraphx::parameter_map pp; for(std::size_t i = 0; i < num_data; ++i) pp[std::to_string(i)] = migraphx::argument(s, data[i].data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); const auto mean = std::accumulate(scalars.begin(), scalars.end(), 0.0) / num_data; std::vector gold(num_elms, mean); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mha_cross_attention_test.cpp000066400000000000000000000043501510465702400256150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(multi_head_attention_cross_attention_test) { migraphx::program p = read_onnx("mha_cross_attention_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape q_s{migraphx::shape::float_type, {1, 2, 4}}; migraphx::shape kv_s{migraphx::shape::float_type, {1, 2, 2, 2}}; std::vector query = {1, 3, 5, 7, 2, 4, 6, 8}; std::vector key_value = {1, 3, 2, 4, 5, 7, 6, 8}; migraphx::parameter_map pp; pp["q"] = migraphx::argument(q_s, query.data()); pp["k"] = migraphx::argument(kv_s, key_value.data()); pp["v"] = migraphx::argument(kv_s, key_value.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 1.9441926, 3.9441926, 5.9997935, 7.999794, 1.9858339, 3.9858341, 5.99995, 7.9999495}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mha_kv_packed_test.cpp000066400000000000000000000042771510465702400243360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(multi_head_attention_kv_packed_test) { migraphx::program p = read_onnx("mha_kv_packed_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape q_s{migraphx::shape::float_type, {1, 2, 4}}; migraphx::shape kv_s{migraphx::shape::float_type, {1, 2, 2, 2, 2}}; std::vector query = {1, 3, 5, 7, 2, 4, 6, 8}; std::vector key_value = {1, 3, 1, 3, 5, 7, 5, 7, 2, 4, 2, 4, 6, 8, 6, 8}; migraphx::parameter_map pp; pp["q"] = migraphx::argument(q_s, query.data()); pp["kv"] = migraphx::argument(kv_s, key_value.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 1.9441926, 3.9441926, 5.9997935, 7.999794, 1.9858339, 3.9858341, 5.99995, 7.9999495}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mha_qkv_packed_test.cpp000066400000000000000000000040651510465702400245120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(multi_head_attention_qkv_packed_test) { migraphx::program p = read_onnx("mha_qkv_packed_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {1, 2, 2, 3, 2}}; std::vector data = {1, 3, 1, 3, 1, 3, 5, 7, 5, 7, 5, 7, 2, 4, 2, 4, 2, 4, 6, 8, 6, 8, 6, 8}; migraphx::parameter_map pp; pp["qkv"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 1.9441926, 3.9441926, 5.9997935, 7.999794, 1.9858339, 3.9858341, 5.99995, 7.9999495}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mha_scale_test.cpp000066400000000000000000000040701510465702400234650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(multi_head_attention_scale_test) { migraphx::program p = read_onnx("mha_scale_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {1, 2, 4}}; std::vector data = {1, 3, 5, 7, 2, 4, 6, 8}; migraphx::parameter_map pp; pp["q"] = migraphx::argument(s, data.data()); pp["k"] = migraphx::argument(s, data.data()); pp["v"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 1.5986876, 3.5986876, 5.7685246, 7.768524, 1.6456563, 3.645656, 5.802184, 7.8021836}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mha_test.cpp000066400000000000000000000040541510465702400223200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(multi_head_attention_test) { migraphx::program p = read_onnx("mha_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {1, 2, 4}}; std::vector data = {1, 3, 5, 7, 2, 4, 6, 8}; migraphx::parameter_map pp; pp["q"] = migraphx::argument(s, data.data()); pp["k"] = migraphx::argument(s, data.data()); pp["v"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 1.9441926, 3.9441926, 5.9997935, 7.999794, 1.9858339, 3.9858341, 5.99995, 7.9999495}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mod_test.cpp000066400000000000000000000044221510465702400223310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(mod_test) { migraphx::program p = read_onnx("mod_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::int32_type, {3, 3, 3}}; std::vector a = {-4, 7, 5, 4, -7, 8, -4, 7, 5, 4, -7, 8, -4, 7, 5, 4, -7, 8, -4, 7, 5, 4, -7, 8, -4, 7, 5}; std::vector b = {2, -3, 8, -2, 3, 5, 2, -3, 8, -2, 3, 5, 2, -3, 8, -2, 3, 5, 2, -3, 8, -2, 3, 5, 2, -3, 8}; migraphx::parameter_map p_map; p_map["0"] = migraphx::argument(s, a.data()); p_map["1"] = migraphx::argument(s, b.data()); auto result = p.eval(p_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, -2, 5, 0, 2, 3, 0, -2, 5, 0, 2, 3, 0, -2, 5, 0, 2, 3, 0, -2, 5, 0, 2, 3, 0, -2, 5}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mod_test_different_dtypes.cpp000066400000000000000000000046121510465702400257500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(mod_test_different_types) { migraphx::program p = read_onnx("mod_test_different_dtypes.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_int16{migraphx::shape::int16_type, {3, 3, 3}}; migraphx::shape s_int32{migraphx::shape::int32_type, {3, 3, 3}}; std::vector a = {-4, 7, 5, 4, -7, 8, -4, 7, 5, 4, -7, 8, -4, 7, 5, 4, -7, 8, -4, 7, 5, 4, -7, 8, -4, 7, 5}; std::vector b = {2, -3, 8, -2, 3, 5, 2, -3, 8, -2, 3, 5, 2, -3, 8, -2, 3, 5, 2, -3, 8, -2, 3, 5, 2, -3, 8}; migraphx::parameter_map p_map; p_map["0"] = migraphx::argument(s_int16, a.data()); p_map["1"] = migraphx::argument(s_int32, b.data()); auto result = p.eval(p_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, -2, 5, 0, 2, 3, 0, -2, 5, 0, 2, 3, 0, -2, 5, 0, 2, 3, 0, -2, 5, 0, 2, 3, 0, -2, 5}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mod_test_fmod.cpp000066400000000000000000000047361510465702400233460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(mod_test_fmod) { migraphx::program p = read_onnx("mod_test_fmod.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {3, 3, 3}}; std::vector a = {1.2, -2.2, 3.3, 4.1, -5.4, 6.7, 7.8, -8.4, 9.9, 10.7, 11.2, 12.3, 13.9, -14.2, 15.8, 16.6, 17.9, 18.2, 19.0, 20.0, 21.0, -22.0, 23.0, -24.0, 25.2, 26.3, 27.1}; std::vector b = {30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4}; migraphx::parameter_map p_map; p_map["0"] = migraphx::argument(s, a.data()); p_map["1"] = migraphx::argument(s, b.data()); auto result = p.eval(p_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{1.2, -2.2, 3.3, 4.1, -5.4, 6.7, 7.8, -8.4, 9.9, 10.7, 11.2, 12.3, 13.9, -14.2, 15.8, 1.6, 3.9, 5.2, 7.0, 9.0, 1.0, -4.0, 7.0, -3.0, 1.2, 1.3, 3.1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mod_test_fmod_different_dtypes.cpp000066400000000000000000000051261510465702400267560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(mod_test_fmod_different_types) { migraphx::program p = read_onnx("mod_test_fmod_different_dtypes.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_float{migraphx::shape::float_type, {3, 3, 3}}; migraphx::shape s_int{migraphx::shape::int32_type, {3, 3, 3}}; std::vector a = {1.2, -2.2, 3.3, 4.1, -5.4, 6.7, 7.8, -8.4, 9.9, 10.7, 11.2, 12.3, 13.9, -14.2, 15.8, 16.6, 17.9, 18.2, 19.0, 20.0, 21.0, -22.0, 23.0, -24.0, 25.2, 26.3, 27.1}; std::vector b = {30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4}; migraphx::parameter_map p_map; p_map["0"] = migraphx::argument(s_float, a.data()); p_map["1"] = migraphx::argument(s_int, b.data()); auto result = p.eval(p_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold{1.2, -2.2, 3.3, 4.1, -5.4, 6.7, 7.8, -8.4, 9.9, 10.7, 11.2, 12.3, 13.9, -14.2, 15.8, 1.6, 3.9, 5.2, 7.0, 9.0, 1.0, -4.0, 7.0, -3.0, 1.2, 1.3, 3.1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/multinomial_dyn_test.cpp000066400000000000000000000101441510465702400247540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(multinomial_dyn_test) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 4}; auto p = read_onnx("multinomial_dyn_test.onnx", options); const size_t batch_size(2); const size_t categories(5); const size_t sample_size(100000); p.compile(migraphx::make_target("ref")); // Distribution function (2 distributions of 5 categories each) std::vector dist{15, 25, 15, 25, 20, 20, 20, 10, 25, 25}; EXPECT(dist.size() == categories * batch_size); std::vector data(categories * batch_size); std::transform(dist.begin(), dist.end(), data.begin(), [&](auto d) { return log(d); }); // Shape of the probability distribution, which also defines the number of categories migraphx::shape s{migraphx::shape::float_type, {batch_size, categories}}; migraphx::parameter_map pp; pp["input"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vec(batch_size * sample_size); result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); // Make a categorical histogram of output // for first result in batch std::vector res_dist(categories, 0); size_t r = 0; for(r = 0; r < result_vec.size() / 2; r++) res_dist[result_vec[r]]++; // normalizing factors for original and measured distributions auto dist_sum = std::accumulate(dist.begin(), dist.begin() + 5, 0); auto res_dist_sum = std::accumulate(res_dist.begin(), res_dist.end(), 0); // Values approximate the distribution in dist std::vector norm(5); std::vector res_norm(5); std::transform(dist.begin(), dist.begin() + 5, norm.begin(), [&](auto n) { return static_cast(n) / dist_sum; }); std::transform(res_dist.begin(), res_dist.end(), res_norm.begin(), [&](auto n) { return static_cast(n) / res_dist_sum; }); EXPECT(migraphx::verify::verify_range_with_tolerance( norm, migraphx::verify::expected{res_norm}, migraphx::verify::tolerance{0.01})); // Make a categorical histogram of output // for second result in batch std::fill(res_dist.begin(), res_dist.end(), 0); for(; r < result_vec.size(); r++) res_dist[result_vec[r]]++; dist_sum = std::accumulate(dist.begin() + 5, dist.end(), 0); res_dist_sum = std::accumulate(res_dist.begin(), res_dist.end(), 0); std::transform(dist.begin() + 5, dist.end(), norm.begin(), [&](auto n) { return static_cast(n) / dist_sum; }); std::transform(res_dist.begin(), res_dist.end(), res_norm.begin(), [&](auto n) { return static_cast(n) / res_dist_sum; }); EXPECT(migraphx::verify::verify_range_with_tolerance( res_norm, migraphx::verify::expected{norm}, migraphx::verify::tolerance{0.01})); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_default_axes_bf16_test.cpp000066400000000000000000000042201510465702400257100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_default_axes_bf16_test) { using migraphx::bf16; auto result = mvn_test({2, 2, 2, 2}, read_onnx("mvn_default_axes_bf16_test.onnx")); std::vector gold{bf16{-1.324}, bf16{-1.084}, bf16{-0.843}, bf16{-0.602}, bf16{-1.324}, bf16{-1.084}, bf16{-0.843}, bf16{-0.602}, bf16{0.602}, bf16{0.843}, bf16{1.084}, bf16{1.324}, bf16{0.602}, bf16{0.843}, bf16{1.084}, bf16{1.324}}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_default_axes_fp16_test.cpp000066400000000000000000000042201510465702400257260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_default_axes_fp16_test) { using migraphx::half; auto result = mvn_test({2, 2, 2, 2}, read_onnx("mvn_default_axes_fp16_test.onnx")); std::vector gold{half{-1.324}, half{-1.084}, half{-0.843}, half{-0.602}, half{-1.324}, half{-1.084}, half{-0.843}, half{-0.602}, half{0.602}, half{0.843}, half{1.084}, half{1.324}, half{0.602}, half{0.843}, half{1.084}, half{1.324}}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_default_axes_test.cpp000066400000000000000000000041461510465702400251010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_default_axes_test) { auto result = mvn_test({2, 2, 2, 2}, read_onnx("mvn_default_axes_test.onnx")); std::vector gold{-1.32424438, -1.08347268, -0.84270097, -0.60192927, -1.32424438, -1.08347268, -0.84270097, -0.60192927, 0.60192927, 0.84270097, 1.08347268, 1.32424438, 0.60192927, 0.84270097, 1.08347268, 1.32424438}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_rank_2_bf16_test.cpp000066400000000000000000000031131510465702400244200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_rank_2_bf16_test) { using migraphx::bf16; auto result = mvn_test({2, 2}, read_onnx("mvn_rank_2_bf16_test.onnx")); std::vector gold{bf16{-1}, bf16{1}, bf16{-1}, bf16{1}}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_rank_2_fp16_test.cpp000066400000000000000000000031131510465702400244360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_rank_2_fp16_test) { using migraphx::half; auto result = mvn_test({2, 2}, read_onnx("mvn_rank_2_fp16_test.onnx")); std::vector gold{half{-1}, half{1}, half{-1}, half{1}}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_rank_2_test.cpp000066400000000000000000000027661510465702400236170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_rank_2_test) { auto result = mvn_test({2, 2}, read_onnx("mvn_rank_2_test.onnx")); std::vector gold{-1, 1, -1, 1}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_rank_3_bf16_test.cpp000066400000000000000000000035011510465702400244220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_rank_3_bf16_test) { using migraphx::bf16; auto result = mvn_test({2, 2, 2}, read_onnx("mvn_rank_3_bf16_test.onnx")); std::vector gold{bf16{-1.342}, bf16{-1.342}, bf16{-0.4473}, bf16{-0.4473}, bf16{0.4473}, bf16{0.4473}, bf16{1.342}, bf16{1.342}}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_rank_3_fp16_test.cpp000066400000000000000000000035011510465702400244400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_rank_3_fp16_test) { using migraphx::half; auto result = mvn_test({2, 2, 2}, read_onnx("mvn_rank_3_fp16_test.onnx")); std::vector gold{half{-1.342}, half{-1.342}, half{-0.4473}, half{-0.4473}, half{0.4473}, half{0.4473}, half{1.342}, half{1.342}}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mvn_rank_3_test.cpp000066400000000000000000000034171510465702400236120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(mvn_rank_3_test) { auto result = mvn_test({2, 2, 2}, read_onnx("mvn_rank_3_test.onnx")); std::vector gold{-1.34164079, -1.34164079, -0.4472136, -0.4472136, 0.4472136, 0.4472136, 1.34164079, 1.34164079}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/mxfixneuron_small_test.cpp000066400000000000000000000061271510465702400253300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(mxfixneuron_small_test) { migraphx::program p = read_onnx("mxfixneuron_small_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector input_lens{4, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; std::vector data = {-100.f, -12.f, 32.f, 819.f, -6.f, -5.75f, -5.50f, -5.25f, -5.f, -0.30f, -1.40f, -1.20f, 2.0f, 0.25f, 0.33f, 2.20f}; migraphx::parameter_map pp; pp["input"] = migraphx::argument(data_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // hand calculated values std::vector gold = {-128.0, -0.0, 0.0, 768.0, -6.0, -6.0, -6.0, -6.0, -4.0, -0.5, -1.5, -1.0, 2.0, 0.25, 0.25, 2.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/negativelogliklihood_kd_dim_weighted.cpp000066400000000000000000000230311510465702400301020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(negativeloglikelihoodloss_kd_no_reduction_weighted_test) { migraphx::program p = optimize_onnx("negativeloglikelihoodloss_kd_no_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements(), 0.0f); std::iota(score_data.begin(), score_data.end(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 4.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.0f, -56.0f, -3.5f, -24.0f, -17.0f, -120.0f, -11.5f, -56.0f, -33.0f, -184.0f, -19.5f, -88.0f, -49.0f, -248.0f, -27.5f, -120.0f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(negativeloglikelihoodloss_kd_no_reduction_weighted_test2) { migraphx::program p = optimize_onnx("negativeloglikelihoodloss_kd_no_reduction_weighted_test2.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {2, 3, 2}}; std::vector score_data = { 1.0f, 2.0f, 2.0f, 2.0f, 3.0f, 2.0f, 0.0f, 1.0f, 2.0f, 2.0f, 1.0f, 2.0f}; migraphx::shape label_shape{migraphx::shape::int32_type, {2, 2}}; std::vector label_data = {2, 1, 0, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {3}}; std::vector weight_data = {1.0f, 1.0f, 1.0f, 1.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-3.0f, -2.0f, 0.0f, -2.0f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(negativeloglikelihoodloss_kd_sum_reduction_weighted_test) { migraphx::program p = optimize_onnx("negativeloglikelihoodloss_kd_sum_reduction_double_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::double_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements()); std::iota(score_data.begin(), score_data.end(), 1.0); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::double_type, {4}}; std::vector weight_data = {1.0, 0.5, 2.0, 4.0}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1058.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(negativeloglikelihoodloss_kd_sum_reduction_weighted_test2) { migraphx::program p = optimize_onnx("negativeloglikelihoodloss_kd_sum_reduction_double_weighted_test2.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::double_type, {2, 3, 2}}; std::vector score_data = {1.0, 2.0, 2.0, 2.0, 3.0, 2.0, 0.0, 1.0, 2.0, 2.0, 1.0, 2.0}; migraphx::shape label_shape{migraphx::shape::int32_type, {2, 2}}; std::vector label_data = {2, 1, 0, 2}; migraphx::shape weight_shape{migraphx::shape::double_type, {3}}; std::vector weight_data = {0.2, 0.3, 0.1}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.1}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(negativeloglikelihoodloss_kd_mean_reduction_weighted_test) { using migraphx::half; migraphx::program p = optimize_onnx("negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::half_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements()); std::iota(score_data.begin(), score_data.end(), half(1.0)); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::half_type, {4}}; std::vector weight_data = {half(1.0), half(0.5), half(2.0), half(4.0)}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {half{-35.266666666666666}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(negativeloglikelihoodloss_kd_mean_reduction_weighted_test2) { using migraphx::half; migraphx::program p = optimize_onnx("negativeloglikelihoodloss_kd_mean_reduction_half_weighted_test2.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::half_type, {2, 3, 2}}; std::vector score_data = {half(1.0), half(2.0), half(2.0), half(2.0), half(3.0), half(2.0), half(0.0), half(1.0), half(2.0), half(2.0), half(1.0), half(2.0)}; migraphx::shape label_shape{migraphx::shape::int32_type, {2, 2}}; std::vector label_data = {2, 1, 0, 2}; migraphx::shape weight_shape{migraphx::shape::half_type, {3}}; std::vector weight_data = {half(0.2), half(0.3), half(0.1)}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {half{-1.5714285714285714}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/nhwcconv_test.cpp000066400000000000000000000112131510465702400233730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(nhwcconv_test) { migraphx::program p = read_onnx("nhwcconv_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {1, 7, 7, 1}}; std::vector x_data = { 0.45246148109436035f, 0.15498268604278564f, 0.11199361085891724f, -0.39421093463897705f, 0.2626858949661255f, 0.13414543867111206f, -0.27184486389160156f, -0.43028733134269714f, -0.26825493574142456f, 0.3893144130706787f, -0.13631996512413025f, -0.009590476751327515f, -0.48771554231643677f, -0.25256502628326416f, -0.2812897562980652f, 0.4043201804161072f, 0.07795023918151855f, 0.326981782913208f, 0.13114392757415771f, -0.4416425824165344f, 0.12446999549865723f, 0.36739975214004517f, 0.1698915958404541f, 0.2008744478225708f, 0.23339951038360596f, 0.38613730669021606f, 0.11117297410964966f, 0.3877097964286804f, 0.20812749862670898f, -0.34297940135002136f, -0.029246658086776733f, -0.20483523607254028f, -0.19244328141212463f, -0.11104947328567505f, -0.32830488681793213f, -0.01800677180290222f, 0.3618946671485901f, -0.40949052572250366f, -0.18248388171195984f, -0.3349453806877136f, -0.34091079235076904f, 0.006497859954833984f, 0.4537564516067505f, 0.08006560802459717f, -0.14788749814033508f, 0.034442365169525146f, -0.33322954177856445f, 0.06049239635467529f, 0.42619407176971436f}; migraphx::shape w_shape{migraphx::shape::float_type, {1, 1, 1, 1}}; std::vector w_data = {-0.4406261742115021f}; migraphx::parameter_map pm; pm["0"] = migraphx::argument{x_shape, x_data.data()}; pm["1"] = migraphx::argument{w_shape, w_data.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape().lens() == std::vector{1, 7, 7, 1}); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { -0.19936637580394745f, -0.06828942894935608f, -0.04934731498360634f, 0.17369966208934784f, -0.11574628204107285f, -0.05910799279808998f, 0.1197819635272026f, 0.18959586322307587f, 0.1182001456618309f, -0.17154212296009064f, 0.06006614491343498f, 0.0042258151806890965f, 0.21490024030208588f, 0.11128675937652588f, 0.12394362688064575f, -0.17815405130386353f, -0.034346915781497955f, -0.14407673478126526f, -0.05778544768691063f, 0.19459928572177887f, -0.05484473705291748f, -0.16188594698905945f, -0.07485868036746979f, -0.08851054310798645f, -0.10284193605184555f, -0.17014220356941223f, -0.04898572340607643f, -0.17083507776260376f, -0.09170642495155334f, 0.1511256992816925f, 0.012886842712759972f, 0.09025576710700989f, 0.08479554951190948f, 0.0489313043653965f, 0.14465972781181335f, 0.007934254594147205f, -0.15946026146411896f, 0.1804322451353073f, 0.08040717244148254f, 0.1475857049226761f, 0.15021422505378723f, -0.0028631272725760937f, -0.19993697106838226f, -0.03527900204062462f, 0.06516310572624207f, -0.015176207758486271f, 0.14682966470718384f, -0.02665453404188156f, -0.18779225647449493f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/nonzero_dynamic_test.cpp000066400000000000000000000035641510465702400247560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(nonzero_test) { migraphx::program p = read_onnx("nonzero_dynamic_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::bool_type, {2, 2}}; std::vector data = {1, 1, 1, 0}; migraphx::parameter_map pp; pp["data"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 0, 0, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/pad_edge_1d_test.cpp000066400000000000000000000036101510465702400236640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(pad_edge_1d_test) { migraphx::program p = read_onnx("pad_edge_1d_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape input_shape{migraphx::shape::float_type, {4}}; std::vector data = {1, 2, 3, 4}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(input_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 1, 1, 2, 3, 4, 4, 4, 4}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/pad_edge_2d_test.cpp000066400000000000000000000044061510465702400236710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(pad_edge_2d_test) { migraphx::program p = read_onnx("pad_edge_2d_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape input_shape{migraphx::shape::float_type, {3, 3}}; // clang-format off std::vector data = {0, 1, 2, 3, 4, 5, 6, 7, 8}; // clang-format on migraphx::parameter_map pp; pp["0"] = migraphx::argument(input_shape, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold = {0, 0, 0, 1, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 3, 3, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 8, 8, 6, 6, 6, 7, 8, 8, 8}; // clang-format on EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearadd_bcast_test.cpp000066400000000000000000000066411510465702400250370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearadd_bcast_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearAdd migraphx::program p = read_onnx("qlinearadd_bcast_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::int8_type, {64}}; std::vector data_a = {-64, -62, -60, -58, -56, -54, -52, -50, -48, -46, -44, -42, -40, -38, -36, -34, -32, -30, -28, -26, -24, -22, -20, -18, -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62}; migraphx::shape b{migraphx::shape::int8_type, {1, 1, 64}}; std::vector data_b = {96, 94, 92, 90, 88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66, 64, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0, -2, -4, -6, -8, -10, -12, -14, -16, -18, -20, -22, -24, -26, -28, -30}; migraphx::parameter_map pp; pp["A"] = migraphx::argument(a, data_a.data()); pp["B"] = migraphx::argument(b, data_b.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64, -64}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearadd_test.cpp000066400000000000000000000065101510465702400236560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearadd_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearAdd migraphx::program p = read_onnx("qlinearadd_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::uint8_type, {64}}; std::vector data_a = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126}; migraphx::shape b{migraphx::shape::uint8_type, {64}}; std::vector data_b = {128, 126, 124, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96, 94, 92, 90, 88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66, 64, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2}; migraphx::parameter_map pp; pp["A"] = migraphx::argument(a, data_a.data()); pp["B"] = migraphx::argument(b, data_b.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_1d_test.cpp000066400000000000000000000056451510465702400260260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_1d_test) { auto p = read_onnx("qlinearaveragepool_1d_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = { -31, 51, 125, 30, -17, -125, 121, -19, -13, 52, 18, -70, 97, 15, 56, 42, -65, -26, 40, -109, -70, 83, 110, -94, 34, 70, 5, -23, -60, -68, 19, 48, -113, 3, -44, 20, -99, -103, -49, -38, 122, 75, 38, -7, -65, -56, 96, 99, 50, -27, -114, 49, -65, 105, -3, 54, 8, 38, -81, -46, -86, -46, -104, 36, 22, -51, 48, 59, -116, 6, 93, 16, -111, 98, 51, -87, -111, -74, -39, 7, 107, 115, 59, 60, -66, -14, -106, -23, 119, -122, -51, -100, 26, 125, 45, 90}; migraphx::shape s_x{migraphx::shape::int8_type, {1, 3, 32}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 26, 104, 94, 22, -55, 14, 67, 0, 36, 51, -10, 29, 72, 52, 65, 5, -30, 23, -19, -74, 23, 112, 24, -14, 68, 54, 7, -26, -48, -8, 50, -39, -4, 4, -24, -85, -60, -28, 58, 114, 72, 31, -20, -44, 36, 114, 90, 28, -54, -16, 8, 36, 67, 42, 47, 39, -6, -48, -50, -50, -59, -18, 2, 15, 70, -13, -39, 66, 71, -32, 9, 90, -2, -83, -76, -40, 0, 73, 127, 103, 75, 13, -24, -44, -48, 64, 15, -70, -60, -21, 92, 101, 84}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_2d_ceil_test.cpp000066400000000000000000000037021510465702400270130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_2d_ceil_test) { auto p = read_onnx("qlinearaveragepool_2d_ceil_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}; migraphx::shape s_x{migraphx::shape::uint8_type, {1, 1, 4, 4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {120, 150, 240, 255}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_2d_dilations_test.cpp000066400000000000000000000037101510465702400300640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_2d_dilations_test) { auto p = read_onnx("qlinearaveragepool_2d_dilations_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}; migraphx::shape s_x{migraphx::shape::int8_type, {1, 1, 4, 4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {108, 112, 124, 127}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_2d_pads_count_include_pad_test.cpp000066400000000000000000000055301510465702400325660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_2d_pads_count_include_pad_test) { auto p = read_onnx("qlinearaveragepool_2d_pads_count_include_pad_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {-30, 50, 91, -87, -21, -113, -16, 6, -128, 104, 82, -126, 54, 41, -71, 62, -11, -111, 13, 104, -43, -48, 30, 85, -62, -33, -27, -114, 32, -17, 30, -26, -18, 15, 17, 100, -122, 115, 84, -34, -86, 82, 102, -117, -91, -105, 112, 91}; migraphx::shape s_x{migraphx::shape::int8_type, {1, 3, 4, 4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 15, 43, 94, 62, 34, -16, 4, -31, 10, -6, 29, -13, -67, -45, 43, 27, 4, -83, -21, -3, -6, 15, -3, 0, -9, 71, 78, 83, 3, -4, 62, 85, 45, 50, 27, 66, 26, -36, -29, 35, 97, 90, 2, -86, -62, 73, 127, 127, -32, -128, -128, -24, 83, 74, -9, -63, -45, -35, 20, 1, 15, -12, -11, -72, -44, -46, 50, 40, 57, 25, 34, 18, 22, 30, 40, 105, 97, 88, -46, 26, 83, 127, 125, 69, -94, 24, 127, 127, 116, 4, -128, -83, 83, 127, 127, -1, -66, -79, 40, 124, 127, 18, -19, -77, -15, 86, 127, 83}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_2d_same_lower_test.cpp000066400000000000000000000050501510465702400302320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_2d_same_lower_test) { auto p = read_onnx("qlinearaveragepool_2d_same_lower_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {195, 102, 250, 61, 222, 6, 243, 218, 230, 105, 36, 116, 194, 31, 113, 85, 126, 204, 80, 38, 115, 167, 221, 67, 69, 140, 11, 209, 136, 120, 39, 96, 29, 5, 167, 40, 58, 51, 157, 179, 244, 149, 76, 243, 126, 144, 192, 199}; migraphx::shape s_x{migraphx::shape::uint8_type, {1, 3, 4, 4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {195, 148, 176, 156, 208, 131, 150, 193, 226, 141, 98, 153, 212, 140, 71, 88, 126, 165, 142, 59, 120, 153, 168, 102, 92, 123, 135, 127, 102, 116, 78, 89, 29, 17, 86, 104, 44, 36, 95, 136, 151, 126, 108, 164, 185, 166, 140, 178}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_2d_same_upper_test.cpp000066400000000000000000000050111510465702400302320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_2d_same_upper_test) { auto p = read_onnx("qlinearaveragepool_2d_same_upper_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {-61, 102, -6, 61, -34, 6, -13, -38, -26, 105, 36, 116, -62, 31, 113, 85, 126, -52, 80, 38, 115, -89, -35, 67, 69, -116, 11, -47, -120, 120, 39, 96, 29, 5, -89, 40, 58, 51, -99, -77, -12, -107, 76, -13, 126, -112, -64, -57}; migraphx::shape s_x{migraphx::shape::int8_type, {1, 3, 4, 4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { -58, -20, -62, -41, -38, 3, -14, 14, -40, 78, 111, 127, -95, 80, 127, 106, -14, -112, 11, 41, -74, -128, -66, -44, -88, -37, -14, -15, -64, 95, 71, 127, 8, -128, -128, -101, -69, -104, -120, -128, -116, -128, -93, -128, -50, -128, -128, -128}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_2d_strides_test.cpp000066400000000000000000000060571510465702400275620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_2d_strides_test) { auto p = read_onnx("qlinearaveragepool_2d_strides_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = { 84, -73, 117, -2, -97, 72, 67, 27, 1, -44, 110, 51, 9, 7, 58, 113, -34, 34, 124, -20, 6, 66, 68, 98, 31, -84, 25, 101, -69, -100, -68, 116, 33, -121, 78, 49, 102, -86, 65, 69, -87, -89, 16, -125, 51, -54, -86, 79, -112, -37, -6, 74, 118, -75, -41, 52, 101, -22, -28, -92, -59, -128, 32, 78, -20, 121, 11, -107, -92, -31, 81, 117, -55, -3, 80, 119, 126, -98, -11, 52, -4, -66, 37, -57, -16, -33, -12, 100, 55, 2, 27, 62, -15, 64, -74, -21, -123, 22, -45, 12, 30, 24, 20, 120, -36, -102, -75, -39, -76, 55, 74, -120, 103, 67, -80, -89, -112, 36, 69, 98, 110, -82, 60, 119, 98, 88, 5, 42, -88, -86, -58, -33, 93, 80, -57, -56, 87, 7, -4, 114, -73, -91, -12, -123, 96, -99, -31, -99, 85, 34, -126, 106, 88, 126, -60, 14, 75, -117, -15, 6, 55, -14, 117, -87, -75, -50, -85, 54, 70, 125, 74, -100, 25, -112, 74, -66, -116, -102, 1, -75, -107, 83, -120, -66, 57, 29, 62, -45, -103, -56, 90, -53}; migraphx::shape s_x{migraphx::shape::int8_type, {1, 3, 8, 8}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {24, 37, 10, 17, 12, 12, -13, -1, 14, -10, 7, -19}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_2d_test.cpp000066400000000000000000000046161510465702400260240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_2d_test) { auto p = read_onnx("qlinearaveragepool_2d_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {84, -73, 117, -2, -97, 72, 67, 27, 1, -44, 110, 51, 9, 7, 58, 113, -34, 34, 124, -20, 6, 66, 68, 98, 31, -84, 25, 101, -69, -100, -68, 116, 33, -121, 78, 49, 102, -86, 65, 69, -87, -89, 16, -125, 51, -54, -86, 79}; migraphx::shape s_x{migraphx::shape::int8_type, {1, 3, 4, 4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {4, 127, 127, -41, 127, 127, -6, 125, 127, 76, 127, 127, 32, 78, 127, -128, -128, 127, -44, -37, 127, -117, -62, 37, -128, -128, -81}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_3d_test.cpp000066400000000000000000000047031510465702400260220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_3d_test) { auto p = read_onnx("qlinearaveragepool_3d_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = { -61, 102, -6, 61, -34, 6, -13, -38, -26, 105, 36, 116, -62, 31, 113, 85, 126, -52, 80, 38, 115, -89, -35, 67, 69, -116, 11, -47, -120, 120, 39, 96, 29, 5, -89, 40, 58, 51, -99, -77, -12, -107, 76, -13, 126, -112, -64, -57, 99, -54, 27, 99, 126, -46, -7, 109, 17, 77, 94, -92, 84, -92, 48, 71, 45, -102, 95, 118, 24, 13, -70, 33, 35, -60, 102, 81, 34, 108, -79, 14, -42}; migraphx::shape s_x{migraphx::shape::int8_type, {1, 3, 3, 3, 3}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {56, 114, 49, 39, 32, 127, 3, 45, -4, -13, 8, 22, -35, -98, 76, 15, 127, 67, 100, 20, 127, 84, 64, 68}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_notset_test.cpp000066400000000000000000000037661510465702400270400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_notset_test) { auto p = read_onnx("qlinearaveragepool_notset_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; migraphx::shape s_x{migraphx::shape::int8_type, {1, 1, 5, 5}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {22}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearaveragepool_nt_cip_test.cpp000066400000000000000000000041471510465702400267720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearaveragepool_nt_cip_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearAveragePool auto p = read_onnx("qlinearaveragepool_nt_cip_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_x = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; migraphx::shape s_x{migraphx::shape::uint8_type, {1, 1, 5, 5}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {18}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearconcat_3d_test.cpp000066400000000000000000000044741510465702400247720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearconcat_3d_test) { auto p = read_onnx("qlinearconcat_3d_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_t0 = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}; migraphx::shape s_t0{migraphx::shape::int8_type, {3, 4, 2}}; migraphx::parameter_map pp; pp["t0"] = migraphx::argument(s_t0, data_t0.data()); std::vector data_t1 = {25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25}; migraphx::shape s_t1{migraphx::shape::int8_type, {3, 2, 2}}; pp["t1"] = migraphx::argument(s_t1, data_t1.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearconcat_test.cpp000066400000000000000000000040111510465702400243670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearconcat_test) { auto p = read_onnx("qlinearconcat_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_t0 = {2, 3}; migraphx::shape s_t0{migraphx::shape::int8_type, {2}}; migraphx::parameter_map pp; pp["t0"] = migraphx::argument(s_t0, data_t0.data()); std::vector data_t1 = {6, 8, 10}; migraphx::shape s_t1{migraphx::shape::int8_type, {3}}; pp["t1"] = migraphx::argument(s_t1, data_t1.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {3, 4, 5, 6, 7}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearconv_pad_0_test.cpp000066400000000000000000000042521510465702400251370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearconv_pad_0_test) { // https:xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html migraphx::program p = read_onnx("qlinearconv_pad_0_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::uint8_type, {1, 1, 5, 5}}; std::vector x_data = {0, 11, 21, 32, 42, 53, 64, 74, 85, 96, 106, 117, 128, 138, 149, 159, 170, 181, 191, 202, 212, 223, 234, 244, 255}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, x_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // # (1, 1, 3, 3) output tensor std::vector gold = {-43, -29, -15, 28, 42, 56, 99, 113, 127}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearconv_pad_1_test.cpp000066400000000000000000000044401510465702400251370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearconv_pad_1_test) { // https:xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html migraphx::program p = read_onnx("qlinearconv_pad_1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::uint8_type, {1, 1, 5, 5}}; std::vector x_data = {0, 11, 21, 32, 42, 53, 64, 74, 85, 96, 106, 117, 128, 138, 149, 159, 170, 181, 191, 202, 212, 223, 234, 244, 255}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, x_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // # (1, 1, 5, 5) output tensor std::vector gold = {19, 33, 43, 52, 38, 52, 85, 99, 113, 80, 99, 156, 170, 184, 128, 146, 227, 241, 255, 175, 113, 175, 184, 194, 132}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearconv_scale_1D_test.cpp000066400000000000000000000043431510465702400255700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearconv_scale_1_d_test) { // https:xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html migraphx::program p = read_onnx("qlinearconv_scale_1D_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::uint8_type, {1, 1, 5, 5}}; std::vector x_data = {0, 11, 21, 32, 42, 53, 64, 74, 85, 96, 106, 117, 128, 138, 149, 159, 170, 181, 191, 202, 212, 223, 234, 244, 255}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, x_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // # (1, 2, 3, 3) output tensor std::vector gold = { -43, -29, -15, 28, 42, 56, 99, 113, 127, -43, -29, -15, 28, 42, 56, 99, 113, 127}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearconv_test.cpp000066400000000000000000000051511510465702400240730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearconv_test) { // https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__QLinearConv.html migraphx::program p = read_onnx("qlinearconv_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::uint8_type, {1, 1, 7, 7}}; std::vector x_data = {255, 174, 162, 25, 203, 168, 58, 15, 59, 237, 95, 129, 0, 64, 56, 242, 153, 221, 168, 12, 166, 232, 178, 186, 195, 237, 162, 237, 188, 39, 124, 77, 80, 102, 43, 127, 230, 21, 83, 41, 40, 134, 255, 154, 92, 141, 42, 148, 247}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, x_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 81, 93, 230, 52, 87, 197, 240, 196, 18, 160, 126, 255, 191, 199, 13, 102, 34, 87, 243, 89, 23, 77, 69, 60, 18, 93, 18, 67, 216, 131, 178, 175, 153, 212, 128, 25, 234, 172, 214, 215, 121, 0, 101, 163, 114, 213, 107, 8}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearglobalavgpool_test.cpp000066400000000000000000000045311510465702400257570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearglobalavgpool_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md // #com.microsoft.QLinearGlobalAveragePool migraphx::program p = read_onnx("qlinearglobalavgpool_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sh_x{migraphx::shape::uint8_type, {1, 3, 4, 4}}; std::vector data_x = {160, 156, 152, 148, 144, 140, 136, 132, 124, 120, 116, 112, 108, 104, 100, 96, 64, 72, 80, 88, 96, 104, 112, 120, 136, 144, 152, 160, 168, 176, 184, 192, 120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134, 135, 136}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sh_x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {64, 64, 64}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearleakyrelu_test.cpp000066400000000000000000000053071510465702400251260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearleakyrelu_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearSigmoid migraphx::program p = read_onnx("qlinearleakyrelu_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x{migraphx::shape::int8_type, {64}}; std::vector data_x = { -128, -124, -120, -116, -112, -108, -104, -100, -96, -92, -88, -84, -80, -76, -72, -68, -64, -60, -56, -52, -48, -44, -40, -36, -32, -28, -24, -20, -16, -12, -8, -4, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { -128, -126, -122, -118, -113, -109, -104, -100, -96, -91, -87, -82, -78, -74, -69, -65, -60, -56, -52, -47, -43, -38, -34, -30, -25, -21, -16, -12, -8, -3, 1, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126, 127, 127}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearmatmul_1D_test.cpp000066400000000000000000000041041510465702400247460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearmatmul_1_d_test) { migraphx::program p = read_onnx("qlinearmatmul_1D_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::uint8_type, {8}}; std::vector data_a = {2, 4, 6, 8, 10, 12, 14, 16}; migraphx::shape b{migraphx::shape::uint8_type, {8}}; std::vector data_b = {126, 130, 124, 132, 122, 134, 120, 136}; migraphx::parameter_map pp; pp["A"] = migraphx::argument(a, data_a.data()); pp["B"] = migraphx::argument(b, data_b.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {66}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearmatmul_2D_test.cpp000066400000000000000000000041121510465702400247460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearmatmul_2_d_test) { migraphx::program p = read_onnx("qlinearmatmul_2D_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::uint8_type, {1, 8}}; std::vector data_a = {2, 4, 6, 8, 10, 12, 14, 16}; migraphx::shape b{migraphx::shape::uint8_type, {8, 1}}; std::vector data_b = {126, 130, 124, 132, 122, 134, 120, 136}; migraphx::parameter_map pp; pp["A"] = migraphx::argument(a, data_a.data()); pp["B"] = migraphx::argument(b, data_b.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {66}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearmatmul_3D_test.cpp000066400000000000000000000045641510465702400247620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearmatmul_3_d_test) { // https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__QLinearMatMul.html migraphx::program p = read_onnx("qlinearmatmul_3D_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::uint8_type, {2, 2, 4}}; std::vector data_a = { 208, 236, 0, 238, 3, 214, 255, 29, 208, 236, 0, 238, 3, 214, 255, 29}; migraphx::shape b{migraphx::shape::uint8_type, {2, 4, 3}}; std::vector data_b = {152, 51, 244, 60, 26, 255, 0, 127, 246, 127, 254, 247, 152, 51, 244, 60, 26, 255, 0, 127, 246, 127, 254, 247}; migraphx::parameter_map pp; pp["A"] = migraphx::argument(a, data_a.data()); pp["B"] = migraphx::argument(b, data_b.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {168, 115, 255, 1, 66, 151, 168, 115, 255, 1, 66, 151}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearmul_bcast_test.cpp000066400000000000000000000067721510465702400251110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearmul_bcast_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearMul migraphx::program p = read_onnx("qlinearmul_bcast_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::int8_type, {64}}; std::vector data_a = {-64, -62, -60, -58, -56, -54, -52, -50, -48, -46, -44, -42, -40, -38, -36, -34, -32, -30, -28, -26, -24, -22, -20, -18, -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62}; migraphx::shape b{migraphx::shape::int8_type, {1, 1, 64}}; std::vector data_b = {96, 94, 92, 90, 88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66, 64, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0, -2, -4, -6, -8, -10, -12, -14, -16, -18, -20, -22, -24, -26, -28, -30}; migraphx::parameter_map pp; pp["A"] = migraphx::argument(a, data_a.data()); pp["B"] = migraphx::argument(b, data_b.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-128, -128, -128, -128, -128, -128, -128, -128, -128, -126, -118, -109, -101, -93, -86, -78, -70, -63, -56, -49, -42, -35, -28, -21, -15, -9, -2, 4, 10, 15, 21, 27, 32, 37, 42, 47, 52, 57, 62, 66, 70, 75, 79, 83, 86, 90, 94, 97, 100, 103, 106, 109, 112, 115, 117, 119, 122, 124, 126, 127, 127, 127, 127, 127}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearmul_test.cpp000066400000000000000000000066501510465702400237300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearmul_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearMul migraphx::program p = read_onnx("qlinearmul_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::uint8_type, {64}}; std::vector data_a = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126}; migraphx::shape b{migraphx::shape::uint8_type, {64}}; std::vector data_b = {128, 126, 124, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96, 94, 92, 90, 88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66, 64, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2}; migraphx::parameter_map pp; pp["A"] = migraphx::argument(a, data_a.data()); pp["B"] = migraphx::argument(b, data_b.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {100, 111, 122, 132, 142, 151, 160, 169, 177, 185, 192, 199, 206, 212, 218, 223, 228, 233, 237, 241, 244, 247, 250, 252, 254, 255, 255, 255, 255, 255, 255, 255, 254, 252, 250, 247, 244, 241, 237, 233, 228, 223, 218, 212, 206, 199, 192, 185, 177, 169, 160, 151, 142, 132, 122, 111, 100, 89, 77, 65, 52, 39, 26, 12}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/qlinearsigmoid_test.cpp000066400000000000000000000055371510465702400245710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(qlinearsigmoid_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearSigmoid migraphx::program p = read_onnx("qlinearsigmoid_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x{migraphx::shape::int8_type, {64}}; std::vector data_x = { -128, -124, -120, -116, -112, -108, -104, -100, -96, -92, -88, -84, -80, -76, -72, -68, -64, -60, -56, -52, -48, -44, -40, -36, -32, -28, -24, -20, -16, -12, -8, -4, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(x, data_x.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-128, -127, -127, -127, -127, -127, -126, -126, -126, -125, -125, -124, -123, -122, -120, -119, -117, -114, -112, -108, -104, -99, -94, -87, -80, -71, -62, -51, -39, -27, -13, 1, 15, 29, 43, 56, 69, 81, 92, 101, 110, 117, 124, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/quant_convolution_dual_bias_test.cpp000066400000000000000000000156111510465702400273460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include TEST_CASE(quant_convolution_dual_zero_bias_test) { // TODO: use other dual_bias test, verify with other framework once convinteger supported migraphx::program p = read_onnx("convinteger_dual_bias_simple_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::int8_type, {1, 3, 5, 5}}; std::vector data_a = { 0, -2, 4, -6, 8, -10, 12, -14, 16, -18, 20, 22, -24, 26, -28, 30, -32, 34, -36, 38, -40, 42, -44, 46, -48, 50, -52, 54, -56, 58, -60, 62, -64, 66, -68, 70, -72, 74, -76, 78, -80, 82, -84, 86, -88, 90, -92, 94, -96, 98, -100, 102, -104, 106, -108, 110, -112, 114, -116, 118, -120, 122, -124, 126, -127, 127, -64, 32, -16, 8, -4, 2, -1, 0, 1}; migraphx::shape b{migraphx::shape::int8_type, {1, 3, 2, 2}}; std::vector data_b = {-127, -64, -32, -8, -4, 0, 2, 4, 8, 16, 64, 127}; migraphx::shape a_bias{migraphx::shape::int8_type, {1}, {1}}; std::vector data_a_bias = {0}; migraphx::shape b_bias{migraphx::shape::int8_type, {1}, {1}}; std::vector data_b_bias = {0}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(a, data_a.data()); pp["1"] = migraphx::argument(b, data_b.data()); pp["2"] = migraphx::argument(a_bias, data_a_bias.data()); pp["3"] = migraphx::argument(b_bias, data_b_bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-6072, 6264, -6456, 6648, 6680, -8248, 8536, -8697, -3772, -1430, 1504, -1570, -696, 761, -898, 1035}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(quant_convolution_dual_non_zero_bias_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearMul migraphx::program p = read_onnx("convinteger_dual_bias_simple_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::int8_type, {1, 3, 5, 5}}; std::vector data_a = { 0, -2, 4, -6, 8, -10, 12, -14, 16, -18, 20, 22, -24, 26, -28, 30, -32, 34, -36, 38, -40, 42, -44, 46, -48, 50, -52, 54, -56, 58, -60, 62, -64, 66, -68, 70, -72, 74, -76, 78, -80, 82, -84, 86, -88, 90, -92, 94, -96, 98, -100, 102, -104, 106, -108, 110, -112, 114, -116, 118, -120, 122, -124, 126, -127, 127, -64, 32, -16, 8, -4, 2, -1, 0, 1}; migraphx::shape b{migraphx::shape::int8_type, {1, 3, 2, 2}}; std::vector data_b = {-127, -64, -32, -8, -4, 0, 2, 4, 8, 16, 64, 127}; migraphx::shape a_bias{migraphx::shape::int8_type, {1}, {1}}; std::vector data_a_bias = {10}; migraphx::shape b_bias{migraphx::shape::int8_type, {1}, {1}}; std::vector data_b_bias = {-2}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(a, data_a.data()); pp["1"] = migraphx::argument(b, data_b.data()); pp["2"] = migraphx::argument(a_bias, data_a_bias.data()); pp["3"] = migraphx::argument(b_bias, data_b_bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // create the following program to compare: // conv(x-x_bias,w-w_bias) // where datatypes for x,w,x_bias,w_bias are int32 migraphx::program p2; migraphx::module* mm = p2.get_main_module(); migraphx::shape a_i32{migraphx::shape::int32_type, {1, 3, 5, 5}}; migraphx::shape b_i32{migraphx::shape::int32_type, {1, 3, 2, 2}}; migraphx::shape bias_i32{migraphx::shape::int32_type, {1}, {1}}; auto x = mm->add_parameter("0", a_i32); auto weights = mm->add_parameter("1", b_i32); auto x_bias = mm->add_parameter("2", bias_i32); auto weights_bias = mm->add_parameter("3", bias_i32); auto sub_input = add_common_op(*mm, migraphx::make_op("sub"), {x, x_bias}); auto sub_weights = add_common_op(*mm, migraphx::make_op("sub"), {weights, weights_bias}); mm->add_instruction(migraphx::make_op("convolution"), sub_input, sub_weights); std::vector data_a_i32(data_a.begin(), data_a.end()); std::vector data_b_i32(data_b.begin(), data_b.end()); std::vector data_a_bias_i32 = {10}; std::vector data_b_bias_i32 = {-2}; migraphx::parameter_map pp2; pp2["0"] = migraphx::argument(a_i32, data_a_i32.data()); pp2["1"] = migraphx::argument(b_i32, data_b_i32.data()); pp2["2"] = migraphx::argument(bias_i32, data_a_bias_i32.data()); pp2["3"] = migraphx::argument(bias_i32, data_b_bias_i32.data()); auto result2 = p2.eval(pp2).back(); std::vector result_vector_i32; result2.visit([&](auto output) { result_vector_i32.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, result_vector_i32)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/quant_convolution_mismatched_input_dual_bias_test.cpp000066400000000000000000000155531510465702400327700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(quant_convolution_mismatched_inputs_dual_zero_bias_test) { migraphx::program p = read_onnx("convinteger_mismatched_inputs_dual_bias_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::uint8_type, {1, 3, 5, 5}}; std::vector data_a = {128, 126, 132, 122, 136, 118, 140, 114, 144, 110, 148, 150, 104, 154, 100, 158, 96, 162, 92, 166, 88, 170, 84, 174, 80, 178, 76, 182, 72, 186, 68, 190, 64, 194, 60, 198, 56, 202, 52, 206, 48, 210, 44, 214, 40, 218, 36, 222, 32, 226, 28, 230, 24, 234, 20, 238, 16, 242, 12, 246, 8, 250, 4, 254, 1, 255, 64, 160, 112, 136, 124, 130, 127, 128, 129}; migraphx::shape b{migraphx::shape::int8_type, {1, 3, 2, 2}}; std::vector data_b = {-127, -64, -32, -8, -4, 0, 2, 4, 8, 16, 64, 127}; migraphx::shape a_bias{migraphx::shape::uint8_type, {1}, {1}}; std::vector data_a_bias = {128}; migraphx::shape b_bias{migraphx::shape::int8_type, {1}, {1}}; std::vector data_b_bias = {0}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(a, data_a.data()); pp["1"] = migraphx::argument(b, data_b.data()); pp["2"] = migraphx::argument(a_bias, data_a_bias.data()); pp["3"] = migraphx::argument(b_bias, data_b_bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-6072, 6264, -6456, 6648, 6680, -8248, 8536, -8697, -3772, -1430, 1504, -1570, -696, 761, -898, 1035}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(quant_convolution_mismatched_inputs_dual_non_zero_bias_test) { migraphx::program p = read_onnx("convinteger_mismatched_inputs_dual_bias_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::uint8_type, {1, 3, 5, 5}}; std::vector data_a = {128, 126, 132, 122, 136, 118, 140, 114, 144, 110, 148, 150, 104, 154, 100, 158, 96, 162, 92, 166, 88, 170, 84, 174, 80, 178, 76, 182, 72, 186, 68, 190, 64, 194, 60, 198, 56, 202, 52, 206, 48, 210, 44, 214, 40, 218, 36, 222, 32, 226, 28, 230, 24, 234, 20, 238, 16, 242, 12, 246, 8, 250, 4, 254, 1, 255, 64, 160, 112, 136, 124, 130, 127, 128, 129}; migraphx::shape b{migraphx::shape::int8_type, {1, 3, 2, 2}}; std::vector data_b = {-127, -64, -32, -8, -4, 0, 2, 4, 8, 16, 64, 127}; migraphx::shape a_bias{migraphx::shape::uint8_type, {1}, {1}}; std::vector data_a_bias = {138}; migraphx::shape b_bias{migraphx::shape::int8_type, {1}, {1}}; std::vector data_b_bias = {-2}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(a, data_a.data()); pp["1"] = migraphx::argument(b, data_b.data()); pp["2"] = migraphx::argument(a_bias, data_a_bias.data()); pp["3"] = migraphx::argument(b_bias, data_b_bias.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // create the following program to compare: // conv(x-x_bias,w-w_bias) // where datatypes for x,w,x_bias,w_bias are int32 migraphx::program p2; migraphx::module* mm = p2.get_main_module(); migraphx::shape a_i32{migraphx::shape::int32_type, {1, 3, 5, 5}}; migraphx::shape b_i32{migraphx::shape::int32_type, {1, 3, 2, 2}}; migraphx::shape bias_i32{migraphx::shape::int32_type, {1}, {1}}; auto x = mm->add_parameter("0", a_i32); auto weights = mm->add_parameter("1", b_i32); auto x_bias = mm->add_parameter("2", bias_i32); auto weights_bias = mm->add_parameter("3", bias_i32); auto sub_input = add_common_op(*mm, migraphx::make_op("sub"), {x, x_bias}); auto sub_weights = add_common_op(*mm, migraphx::make_op("sub"), {weights, weights_bias}); mm->add_instruction(migraphx::make_op("convolution"), sub_input, sub_weights); std::vector data_a_i32(data_a.begin(), data_a.end()); std::vector data_b_i32(data_b.begin(), data_b.end()); std::vector data_a_bias_i32 = {138}; std::vector data_b_bias_i32 = {-2}; migraphx::parameter_map pp2; pp2["0"] = migraphx::argument(a_i32, data_a_i32.data()); pp2["1"] = migraphx::argument(b_i32, data_b_i32.data()); pp2["2"] = migraphx::argument(bias_i32, data_a_bias_i32.data()); pp2["3"] = migraphx::argument(bias_i32, data_b_bias_i32.data()); auto result2 = p2.eval(pp2).back(); std::vector result_vector_i32; result2.visit([&](auto output) { result_vector_i32.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, result_vector_i32)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/quant_convolution_test.cpp000066400000000000000000000062741510465702400253500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(quant_convolution_test) { // github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.QLinearMul migraphx::program p = read_onnx("convinteger_no_bias_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape a{migraphx::shape::int8_type, {1, 3, 5, 5}}; std::vector data_a = { 0, -2, 4, -6, 8, -10, 12, -14, 16, -18, 20, 22, -24, 26, -28, 30, -32, 34, -36, 38, -40, 42, -44, 46, -48, 50, -52, 54, -56, 58, -60, 62, -64, 66, -68, 70, -72, 74, -76, 78, -80, 82, -84, 86, -88, 90, -92, 94, -96, 98, -100, 102, -104, 106, -108, 110, -112, 114, -116, 118, -120, 122, -124, 126, -127, 127, -64, 32, -16, 8, -4, 2, -1, 0, 1}; migraphx::shape b{migraphx::shape::int8_type, {1, 3, 2, 2}}; std::vector data_b = {-127, -64, -32, -8, -4, 0, 2, 4, 8, 16, 64, 127}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(a, data_a.data()); pp["1"] = migraphx::argument(b, data_b.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-6072, 6264, -6456, 6648, 6680, -8248, 8536, -8697, -3772, -1430, 1504, -1570, -696, 761, -898, 1035}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/quantizelinear_blocked_test.cpp000066400000000000000000000125201510465702400262660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include static migraphx::shape make_shape(migraphx::shape::type_t type, std::vector lens) { return migraphx::shape{type, std::move(lens)}; } TEST_CASE(quantizelinear_2d_blocked_with_zp_test) { migraphx::program p = read_onnx("quantizelinear_2d_blocked_with_zp_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {6, 2}}; std::vector x = { 6.0f, 12.0f, 50.0f, 5.0f, 40.0f, 1.0f, 8.0f, 4.0f, 7.0f, 3.0f, 0.0f, 20.0f}; migraphx::shape scale_shape{migraphx::shape::float_type, {2, 2}}; std::vector scale = {1.5f, 2.5f, 3.0f, 4.9f}; migraphx::shape zp_shape{migraphx::shape::int8_type, {2, 2}}; std::vector zp = {0, 1, 2, 3}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x.data()}; pm["scale"] = migraphx::argument{scale_shape, scale.data()}; pm["zp"] = migraphx::argument{zp_shape, zp.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape(migraphx::shape::int8_type, {6, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {4, 6, 33, 3, 27, 1, 5, 4, 4, 4, 2, 7}; EXPECT(result_vector == gold); } TEST_CASE(quantizelinear_2d_blocked_runt_block_test) { migraphx::program p = read_onnx("quantizelinear_2d_blocked_runt_block_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {3, 5}}; std::vector x = {6.0f, 12.0f, 50.0f, 5.0f, 50.0f, 1.0f, 8.0f, 4.0f, 5.0f, 4.0f, 0.0f, 20.0f, 10.0f, 4.0f, 10.0f}; migraphx::shape scale_shape{migraphx::shape::float_type, {3, 3}}; std::vector scale = {1.5f, 2.5f, 3.6f, 3.0f, 4.9f, 5.4f, 5.1f, 6.9f, 3.8f}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x.data()}; pm["y_scale"] = migraphx::argument{scale_shape, scale.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape(migraphx::shape::uint8_type, {3, 5})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {4, 8, 20, 2, 14, 0, 3, 1, 1, 1, 0, 4, 1, 1, 3}; EXPECT(result_vector == gold); } TEST_CASE(quantizelinear_3d_blocked_with_zp_runt_block_test) { migraphx::program p = read_onnx("quantizelinear_3d_blocked_with_zp_runt_block_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x_shape{migraphx::shape::float_type, {2, 5, 2}}; std::vector x = {6.0f, 1.0f, 12.0f, 8.0f, 50.0f, 4.0f, 5.0f, 7.0f, 40.0f, 3.0f, 0.0f, 14.0f, 20.0f, 42.0f, 13.0f, 25.0f, 17.0f, 22.0f, 31.0f, 33.0f}; migraphx::shape scale_shape{migraphx::shape::float_type, {2, 2, 2}}; std::vector scale = {1.5f, 2.5f, 3.0f, 4.9f, 1.8f, 3.6f, 2.3f, 4.1f}; migraphx::shape zp_shape{migraphx::shape::int8_type, {2, 2, 2}}; std::vector zp = {0, 1, 2, 3, 3, 2, 1, 0}; migraphx::parameter_map pm; pm["x"] = migraphx::argument{x_shape, x.data()}; pm["scale"] = migraphx::argument{scale_shape, scale.data()}; pm["zp"] = migraphx::argument{zp_shape, zp.data()}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape(migraphx::shape::int8_type, {2, 5, 2})); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {4, 1, 8, 4, 33, 3, 4, 4, 15, 4, 3, 6, 14, 14, 10, 9, 8, 5, 14, 8}; EXPECT(result_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/reducesum_variable_axes_test.cpp000066400000000000000000000111431510465702400264310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include static auto reducesum_variable_axes_test_base(const std::string& file, size_t axes_size) { std::pair, migraphx::shape> ret; migraphx::onnx_options options; options.map_input_dims["axes"] = std::vector{axes_size}; migraphx::program p = read_onnx(file, options); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; migraphx::shape x_shape{migraphx::shape::float_type, {3, 4, 5, 6}}; std::vector x(x_shape.elements()); std::iota(x.begin(), x.end(), 0); pm["x"] = migraphx::argument(x_shape, x.data()); auto axes_data = axes_size == 0 ? std::vector{} : std::vector{2}; pm["axes"] = migraphx::argument(migraphx::shape{migraphx::shape::int64_type, {axes_size}}, axes_data.data()); auto result = p.eval(pm).back(); std::vector result_vector; result.visit([&](auto output) { ret.first.assign(output.begin(), output.end()); }); ret.second = result.get_shape(); return ret; } TEST_CASE(bla) { auto [result_vector, shape] = reducesum_variable_axes_test_base("reducesum_variable_axes_test.onnx", 1); std::vector gold{60, 65, 70, 75, 80, 85, 210, 215, 220, 225, 230, 235, 360, 365, 370, 375, 380, 385, 510, 515, 520, 525, 530, 535, 660, 665, 670, 675, 680, 685, 810, 815, 820, 825, 830, 835, 960, 965, 970, 975, 980, 985, 1110, 1115, 1120, 1125, 1130, 1135, 1260, 1265, 1270, 1275, 1280, 1285, 1410, 1415, 1420, 1425, 1430, 1435, 1560, 1565, 1570, 1575, 1580, 1585, 1710, 1715, 1720, 1725, 1730, 1735}; EXPECT(shape == migraphx::shape{migraphx::shape::float_type, {3, 4, 1, 6}}); EXPECT(result_vector == gold); } TEST_CASE(bla2) { auto [result_vector, shape] = reducesum_variable_axes_test_base("reducesum_variable_axes_test.onnx", 0); std::vector gold{64620}; EXPECT(shape == migraphx::shape{migraphx::shape::float_type, {1, 1, 1, 1}}); EXPECT(result_vector == gold); } TEST_CASE(bla3) { auto [result_vector, shape] = reducesum_variable_axes_test_base("reducesum_variable_axes_noop_test.onnx", 1); std::vector gold{60, 65, 70, 75, 80, 85, 210, 215, 220, 225, 230, 235, 360, 365, 370, 375, 380, 385, 510, 515, 520, 525, 530, 535, 660, 665, 670, 675, 680, 685, 810, 815, 820, 825, 830, 835, 960, 965, 970, 975, 980, 985, 1110, 1115, 1120, 1125, 1130, 1135, 1260, 1265, 1270, 1275, 1280, 1285, 1410, 1415, 1420, 1425, 1430, 1435, 1560, 1565, 1570, 1575, 1580, 1585, 1710, 1715, 1720, 1725, 1730, 1735}; EXPECT(shape == migraphx::shape{migraphx::shape::float_type, {3, 4, 1, 6}}); EXPECT(result_vector == gold); } TEST_CASE(bla4) { auto [result_vector, shape] = reducesum_variable_axes_test_base("reducesum_variable_axes_noop_test.onnx", 0); std::vector gold(3 * 4 * 5 * 6); std::iota(gold.begin(), gold.end(), 0); EXPECT(shape == migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); EXPECT(result_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/reducesum_variable_dynamic_axes_test.cpp000066400000000000000000000100051510465702400301310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include static auto reducesum_variable_dynamic_axes_test_base(migraphx::shape axes_shape, std::vector axes_data, const std::string& file) { std::pair, migraphx::shape> ret; migraphx::onnx_options options; const std::vector axes_dims{{0, 3}}; options.map_dyn_input_dims["axes"] = axes_dims; migraphx::program p = read_onnx(file, options); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; migraphx::shape x_shape{migraphx::shape::float_type, {2, 2, 2}}; std::vector x(x_shape.elements()); std::iota(x.begin(), x.end(), 0); pm["x"] = migraphx::argument(x_shape, x.data()); std::vector axes{1}; pm["axes"] = migraphx::argument(axes_shape, axes_data.data()); auto result = p.eval(pm).back(); ret.second = result.get_shape(); result.visit([&](auto output) { ret.first.assign(output.begin(), output.end()); }); return ret; } TEST_CASE(reducesum_variable_dynamic_axes_test) { auto [result, shape] = reducesum_variable_dynamic_axes_test_base( {migraphx::shape::int64_type, {1}}, std::vector{1}, "reducesum_variable_dynamic_axes_verify_test.onnx"); std::vector gold{2, 4, 10, 12}; EXPECT(shape == migraphx::shape{migraphx::shape::float_type, {2, 1, 2}}); EXPECT(result == gold); } TEST_CASE(reducesum_variable_dynamic_axes_empty_test) { auto [result, shape] = reducesum_variable_dynamic_axes_test_base( {migraphx::shape::int64_type, {0}}, std::vector{}, "reducesum_variable_dynamic_axes_verify_test.onnx"); std::vector gold{28}; EXPECT(shape == migraphx::shape{migraphx::shape::float_type, {1, 1, 1}}); EXPECT(result == gold); } TEST_CASE(reducesum_variable_dynamic_axes_noop_set_test) { auto [result, shape] = reducesum_variable_dynamic_axes_test_base( {migraphx::shape::int64_type, {1}}, std::vector{1}, "reducesum_variable_dynamic_axes_noop_set_verify_test.onnx"); std::vector gold{2, 4, 10, 12}; EXPECT(shape == migraphx::shape{migraphx::shape::float_type, {2, 1, 2}}); EXPECT(result == gold); } TEST_CASE(reducesum_variable_dynamic_axes_empty_noop_set_test) { auto [result, shape] = reducesum_variable_dynamic_axes_test_base( {migraphx::shape::int64_type, {0}}, std::vector{}, "reducesum_variable_dynamic_axes_noop_set_verify_test.onnx"); std::vector gold(8); std::iota(gold.begin(), gold.end(), 0); EXPECT(shape == migraphx::shape{migraphx::shape::float_type, {2, 2, 2}}); EXPECT(result == gold); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_downsample_f_dyn2_test.cpp000066400000000000000000000061341510465702400265470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_downsample_f_dyn2_test) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; auto p = read_onnx("resize_downsample_f_dyn2_test.onnx", options); p.compile(migraphx::make_target("ref")); // A Resize op. with static input shape goes through a different code path // but should give same result auto reference_p = read_onnx("resize_downsample_f_ref2_test.onnx", options); reference_p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::float_type, {2, 1, 5, 9}}; std::vector dx(sx.elements()); std::iota(dx.begin(), dx.end(), 0.1f); migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // clang-format off // gold values different than in resize_downsample_f_dyn_test because the deduced scales are // slightly different. std::vector gold = { 0.1f, 1.1f, 3.1f, 5.1f, 7.1f, 9.1f, 10.1f, 12.1f, 14.1f, 16.1f, 27.1f, 28.1f, 30.1f, 32.1f, 34.1f, 45.1f, 46.1f, 48.1f, 50.1f, 52.1f, 54.1f, 55.1f, 57.1f, 59.1f, 61.1f, 72.1f, 73.1f, 75.1f, 77.1f, 79.1f }; // clang-format on EXPECT(migraphx::verify::verify_range_with_tolerance(result_vector, migraphx::verify::expected{gold})); auto reference_result = reference_p.eval(pp).back(); std::vector reference_vector; reference_result.visit( [&](auto output) { reference_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{reference_vector})); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_downsample_f_dyn3_test.cpp000066400000000000000000000063721510465702400265540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_downsample_f_dyn3_test) { // scales is a runtime, not a literal, input migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; auto p = read_onnx("resize_downsample_f_dyn3_test.onnx", options); p.compile(migraphx::make_target("ref")); // A Resize op. with static input shape goes through a different code path // but should give same result auto reference_p = read_onnx("resize_downsample_f_ref_test.onnx", options); reference_p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::float_type, {2, 1, 5, 9}}; std::vector dx(sx.elements()); std::iota(dx.begin(), dx.end(), 0.1f); migraphx::shape scales{migraphx::shape::float_type, {4}}; std::vector d_scales{1.f, 1.f, 0.601, 0.601}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); pp["scales"] = migraphx::argument(scales, d_scales.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold = { 0.1f, 1.1f, 3.1f, 4.1f, 6.1f, 9.1f, 10.1f, 12.1f, 13.1f, 15.1f, 27.1f, 28.1f, 30.1f, 31.1f, 33.1f, 45.1f, 46.1f, 48.1f, 49.1f, 51.1f, 54.1f, 55.1f, 57.1f, 58.1f, 60.1f, 72.1f, 73.1f, 75.1f, 76.1f, 78.1f}; // clang-format on EXPECT(migraphx::verify::verify_range_with_tolerance(result_vector, migraphx::verify::expected{gold})); auto reference_result = reference_p.eval(pp).back(); std::vector reference_vector; reference_result.visit( [&](auto output) { reference_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{reference_vector})); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_downsample_f_dyn_test.cpp000066400000000000000000000060121510465702400264600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_downsample_f_dyn_test) { migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; auto p = read_onnx("resize_downsample_f_dyn_test.onnx", options); p.compile(migraphx::make_target("ref")); // A Resize op. with static input shape goes through a different code path // but should give same result auto reference_p = read_onnx("resize_downsample_f_ref_test.onnx", options); reference_p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::float_type, {2, 1, 5, 9}}; std::vector dx(sx.elements()); std::iota(dx.begin(), dx.end(), 0.1f); migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold = { 0.1f, 1.1f, 3.1f, 4.1f, 6.1f, 9.1f, 10.1f, 12.1f, 13.1f, 15.1f, 27.1f, 28.1f, 30.1f, 31.1f, 33.1f, 45.1f, 46.1f, 48.1f, 49.1f, 51.1f, 54.1f, 55.1f, 57.1f, 58.1f, 60.1f, 72.1f, 73.1f, 75.1f, 76.1f, 78.1f}; // clang-format on EXPECT(migraphx::verify::verify_range_with_tolerance(result_vector, migraphx::verify::expected{gold})); auto reference_result = reference_p.eval(pp).back(); std::vector reference_vector; reference_result.visit( [&](auto output) { reference_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{reference_vector})); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_downsample_f_test.cpp000066400000000000000000000036471510465702400256210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_downsample_f_test) { migraphx::program p = read_onnx("resize_downsample_f_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 4}}; std::vector dx(sx.elements()); std::iota(dx.begin(), dx.end(), 0.0f); migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0f, 3.0f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_downsample_linear_half_test.cpp000066400000000000000000000040661510465702400276340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_downsample_linear_half_test) { using migraphx::half; migraphx::program p = read_onnx("resize_downsample_linear_half_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::half_type, {1, 1, 2, 4}}; std::vector dx = {half{1}, half{2}, half{3}, half{4}, half{5}, half{6}, half{7}, half{8}}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // Expected output was calculated without any quantization std::vector gold = {half{2.8333333}, half{4.833333}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_outsize_test.cpp000066400000000000000000000050721510465702400246370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_outsize_test) { // resize using output_size input, rather than scales migraphx::program p = read_onnx("resize_outsize_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; std::vector dx(sx.elements()); std::iota(dx.begin(), dx.end(), 0.1f); migraphx::shape sy{migraphx::shape::float_type, {1, 1, 4, 6}}; std::vector dy(sx.elements(), 0); migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); // Input Y is defined as type int64 in the Onnx file and will therefore be // interpreted as output shape (not scales) even though the input array is type float. pp["Y"] = migraphx::argument(sx, dy.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold = {0.1f, 0.1f, 1.1f, 1.1f, 1.1f, 1.1f, 2.1f, 2.1f, 3.1f, 3.1f, 3.1f, 3.1f, 2.1f, 2.1f, 3.1f, 3.1f, 3.1f, 3.1f, 2.1f, 2.1f, 3.1f, 3.1f, 3.1f, 3.1f}; // clang-format on EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_upsample_f_dyn_test.cpp000066400000000000000000000056141510465702400261440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_upsample_f_dyn_test) { // resize with half_pixel and round_prefer_ceil, with scale > 1 migraphx::onnx_options options; options.default_dyn_dim_value = {1, 10}; auto p = read_onnx("resize_upsample_f_dyn_test.onnx", options); p.compile(migraphx::make_target("ref")); // should upscale to 2x4x8 migraphx::shape sx{migraphx::shape::float_type, {2, 1, 3, 5}}; std::vector dx(sx.elements()); std::iota(dx.begin(), dx.end(), 0.1f); migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold = { 0.1f, 0.1f, 1.1f, 2.1f, 2.1f, 3.1f, 4.1f, 4.1f, 0.1f, 0.1f, 1.1f, 2.1f, 2.1f, 3.1f, 4.1f, 4.1f, 5.1f, 5.1f, 6.1f, 7.1f, 7.1f, 8.1f, 9.1f, 9.1f, 10.1f, 10.1f, 11.1f, 12.1f, 12.1f, 13.1f, 14.1f, 14.1f, 15.1f, 15.1f, 16.1f, 17.1f, 17.1f, 18.1f, 19.1f, 19.1f, 15.1f, 15.1f, 16.1f, 17.1f, 17.1f, 18.1f, 19.1f, 19.1f, 20.1f, 20.1f, 21.1f, 22.1f, 22.1f, 23.1f, 24.1f, 24.1f, 25.1f, 25.1f, 26.1f, 27.1f, 27.1f, 28.1f, 29.1f, 29.1f}; // clang-format on // Using verify_range_with_tolerance() because floating-point // rounding errors were observed. EXPECT(migraphx::verify::verify_range_with_tolerance(result_vector, migraphx::verify::expected{gold})); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_upsample_linear_ac_test.cpp000066400000000000000000000047221510465702400267610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_upsample_linear_ac_test) { migraphx::program p = read_onnx("resize_upsample_linear_ac_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; std::vector dx = {1.0f, 2.0f, 3.0f, 4.0f}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 4.0f / 3, 5.0f / 3, 2, 5.0f / 3, 2, 7.0f / 3, 8.0f / 3, 7.0f / 3, 8.0f / 3, 3, 10.0f / 3, 3, 10.0f / 3, 11.0f / 3, 4}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_upsample_linear_test.cpp000066400000000000000000000037331510465702400263170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_upsample_linear_test) { migraphx::program p = read_onnx("resize_upsample_linear_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; std::vector dx = {1.0f, 2.0f, 3.0f, 4.0f}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 1, 1.25, 1.75, 2, 1.5, 1.75, 2.25, 2.5, 2.5, 2.75, 3.25, 3.5, 3, 3.25, 3.75, 4}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/resize_upsample_pf_test.cpp000066400000000000000000000037411510465702400254510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(resize_upsample_pf_test) { migraphx::program p = read_onnx("resize_upsample_pf_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; std::vector dx = {1.0f, 2.0f, 3.0f, 4.0f}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, dx.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/reversesequence_4D_test.cpp000066400000000000000000000041041510465702400253020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(reversesequence_4_d_verify_test) { migraphx::program p = read_onnx("reversesequence_4D_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape xs{migraphx::shape::float_type, {2, 2, 2, 2}}; std::vector x_data = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0}; migraphx::parameter_map param_map; param_map["x"] = migraphx::argument(xs, x_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 8.0, 9.0, 10.0, 11.0, 4.0, 5.0, 6.0, 7.0, 0.0, 1.0, 2.0, 3.0, 12.0, 13.0, 14.0, 15.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/reversesequence_batch_test.cpp000066400000000000000000000041031510465702400261130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(reversesequence_batch_verify_test) { migraphx::program p = read_onnx("reversesequence_batch_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape xs{migraphx::shape::float_type, {4, 4}}; std::vector x_data = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0}; migraphx::parameter_map param_map; param_map["x"] = migraphx::argument(xs, x_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.0, 1.0, 2.0, 3.0, 5.0, 4.0, 6.0, 7.0, 10.0, 9.0, 8.0, 11.0, 15.0, 14.0, 13.0, 12.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/reversesequence_time_test.cpp000066400000000000000000000041011510465702400257660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(reversesequence_time_verify_test) { migraphx::program p = read_onnx("reversesequence_time_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape xs{migraphx::shape::float_type, {4, 4}}; std::vector x_data = { 0.0, 4.0, 8.0, 12.0, 1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0}; migraphx::parameter_map param_map; param_map["x"] = migraphx::argument(xs, x_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { 3.0, 6.0, 9.0, 12.0, 2.0, 5.0, 8.0, 13.0, 1.0, 4.0, 10.0, 14.0, 0.0, 7.0, 11.0, 15.0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/rotary_embedding_test.cpp000066400000000000000000000672021510465702400250750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(rotary_embedding_verify_test) { std::size_t batch_size = 1; std::size_t sequence_length = 2; std::size_t num_heads = 3; std::size_t head_size = 6; std::size_t max_sequence_length = 4; migraphx::program p = read_onnx("rotary_embedding_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape input_s{migraphx::shape::half_type, {batch_size, sequence_length, num_heads * head_size}}; migraphx::shape pos_ids_s{migraphx::shape::int32_type, {batch_size, sequence_length}}; migraphx::shape cache_s{migraphx::shape::half_type, {max_sequence_length, head_size / 2}}; std::vector input = { -1.0408f, 0.9166f, -1.3042f, -1.1097f, -1.2188f, 1.1676f, 1.0076f, -0.7529f, -0.2250f, -0.4327f, -1.5071f, -0.4586f, -0.8663f, -0.2656f, 0.1665f, 0.7911f, -0.9320f, -0.8579f, -1.0574f, -0.1188f, -0.9078f, 0.3452f, -0.5713f, -0.2351f, -0.8480f, 0.5266f, -1.2944f, -0.0243f, -0.2354f, -0.7087f, -0.9647f, -0.0991f, -0.2994f, -0.0650f, -1.5720f, -1.3211f}; std::vector input_data{input.cbegin(), input.cend()}; std::vector pos_ids_data = {0, 1}; std::vector cos_cache = {1.0000f, 1.0000f, 1.0000f, 0.5403f, 0.9989f, 1.0000f, -0.4161f, 0.9957f, 1.0000f, -0.9900f, 0.9903f, 1.0000f}; std::vector cos_cache_data{cos_cache.cbegin(), cos_cache.cend()}; std::vector sin_cache = {0.0000f, 0.0000f, 0.0000f, 0.8415f, 0.0464f, 0.0022f, 0.9093f, 0.0927f, 0.0043f, 0.1411f, 0.1388f, 0.0065f}; std::vector sin_cache_data{sin_cache.cbegin(), sin_cache.cend()}; migraphx::parameter_map param_map; param_map["input"] = migraphx::argument(input_s, input_data.data()); param_map["pos_ids"] = migraphx::argument(pos_ids_s, pos_ids_data.data()); param_map["cos_cache"] = migraphx::argument(cache_s, cos_cache_data.data()); param_map["sin_cache"] = migraphx::argument(cache_s, sin_cache_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { -1.0408f, 0.9166f, -1.3042f, -1.1097f, -1.2188f, 1.1676f, 1.0076f, -0.7529f, -0.2250f, -0.4327f, -1.5071f, -0.4586f, -0.8663f, -0.2656f, 0.1665f, 0.7911f, -0.9320f, -0.8579f, -0.8618f, -0.0922f, -0.9073f, -0.7032f, -0.5762f, -0.2371f, -0.4377f, 0.5370f, -1.2929f, -0.7267f, -0.2107f, -0.7115f, -0.4666f, -0.0261f, -0.2965f, -0.8469f, -1.5749f, -1.3217f}; EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{gold}, migraphx::verify::tolerance{1e-3})); } TEST_CASE(rotary_embedding_float_verify_test) { std::size_t batch_size = 1; std::size_t sequence_length = 2; std::size_t num_heads = 3; std::size_t head_size = 6; std::size_t max_sequence_length = 4; migraphx::program p = read_onnx("rotary_embedding_float_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape input_s{migraphx::shape::float_type, {batch_size, num_heads, sequence_length, head_size}}; migraphx::shape pos_ids_s{migraphx::shape::int32_type, {batch_size, sequence_length}}; migraphx::shape cache_s{migraphx::shape::float_type, {max_sequence_length, head_size / 2}}; std::vector input_data = { -1.0408f, 0.9166f, -1.3042f, -1.1097f, -1.2188f, 1.1676f, 1.0076f, -0.7529f, -0.2250f, -0.4327f, -1.5071f, -0.4586f, -0.8663f, -0.2656f, 0.1665f, 0.7911f, -0.9320f, -0.8579f, -1.0574f, -0.1188f, -0.9078f, 0.3452f, -0.5713f, -0.2351f, -0.8480f, 0.5266f, -1.2944f, -0.0243f, -0.2354f, -0.7087f, -0.9647f, -0.0991f, -0.2994f, -0.0650f, -1.5720f, -1.3211f}; std::vector pos_ids_data = {0, 1}; std::vector cos_cache_data = {1.0000f, 1.0000f, 1.0000f, 0.5403f, 0.9989f, 1.0000f, -0.4161f, 0.9957f, 1.0000f, -0.9900f, 0.9903f, 1.0000f}; std::vector sin_cache_data = {0.0000f, 0.0000f, 0.0000f, 0.8415f, 0.0464f, 0.0022f, 0.9093f, 0.0927f, 0.0043f, 0.1411f, 0.1388f, 0.0065f}; migraphx::parameter_map param_map; param_map["input"] = migraphx::argument(input_s, input_data.data()); param_map["pos_ids"] = migraphx::argument(pos_ids_s, pos_ids_data.data()); param_map["cos_cache"] = migraphx::argument(cache_s, cos_cache_data.data()); param_map["sin_cache"] = migraphx::argument(cache_s, sin_cache_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { -1.0408f, 0.9166f, -1.3042f, -1.1097f, -1.2188f, 1.1676f, 1.0076f, -0.7529f, -0.2250f, -0.4327f, -1.5071f, -0.4586f, -0.8663f, -0.2656f, 0.1665f, 0.7911f, -0.9320f, -0.8579f, -0.8618f, -0.0922f, -0.9073f, -0.7032f, -0.5762f, -0.2371f, -0.4377f, 0.5370f, -1.2929f, -0.7267f, -0.2107f, -0.7115f, -0.4666f, -0.0261f, -0.2965f, -0.8469f, -1.5749f, -1.3217f}; EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{gold}, migraphx::verify::tolerance{1e-3})); } TEST_CASE(rotary_embedding_1s_verify_test) { std::size_t batch_size = 1; std::size_t sequence_length = 2; std::size_t num_heads = 3; std::size_t head_size = 6; std::size_t max_sequence_length = 4; migraphx::program p = read_onnx("rotary_embedding_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape input_s{migraphx::shape::half_type, {batch_size, sequence_length, num_heads * head_size}}; migraphx::shape pos_ids_s{migraphx::shape::int32_type, {batch_size, sequence_length}}; migraphx::shape cache_s{migraphx::shape::half_type, {max_sequence_length, head_size / 2}}; std::vector input(input_s.elements(), 1.0); std::vector input_data{input.cbegin(), input.cend()}; std::vector pos_ids_data = {0, 1}; std::vector cos_cache(cache_s.elements(), 1.0); std::vector cos_cache_data{cos_cache.cbegin(), cos_cache.cend()}; std::vector sin_cache(cache_s.elements(), 1.0); std::vector sin_cache_data{sin_cache.cbegin(), sin_cache.cend()}; migraphx::parameter_map param_map; param_map["input"] = migraphx::argument(input_s, input_data.data()); param_map["pos_ids"] = migraphx::argument(pos_ids_s, pos_ids_data.data()); param_map["cos_cache"] = migraphx::argument(cache_s, cos_cache_data.data()); param_map["sin_cache"] = migraphx::argument(cache_s, sin_cache_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold(input_s.elements(), 0.0); for(auto i = 0; i < gold.size(); ++i) { if(i % 6 > 2) gold[i] = 2.0; } EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{gold}, migraphx::verify::tolerance{1e-3})); } TEST_CASE(rotary_embedding_interleaved_verify_test) { std::size_t batch_size = 1; std::size_t sequence_length = 3; std::size_t num_heads = 2; std::size_t head_size = 4; std::size_t max_sequence_length = 8; migraphx::program p = read_onnx("rotary_embedding_interleaved_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape input_s{migraphx::shape::half_type, {batch_size, sequence_length, num_heads * head_size}}; migraphx::shape pos_ids_s{migraphx::shape::int32_type, {1}}; migraphx::shape cache_s{migraphx::shape::half_type, {max_sequence_length, head_size / 2}}; std::vector input = {-1.0408f, 0.9166f, -1.3042f, -1.1097f, -0.1320f, -0.2751f, -0.2350f, 0.0937f, -1.2188f, 1.1676f, -1.0574f, -0.1188f, -0.7396f, -1.2425f, -0.1752f, 0.6990f, -0.8110f, 0.6737f, -1.1233f, -0.0919f, -0.6861f, 0.7202f, 0.1963f, 0.6142f}; std::vector input_data{input.cbegin(), input.cend()}; std::vector pos_ids_data = {0}; std::vector cos_cache = {1.0000f, 1.0000f, 0.5403f, 0.9999f, -0.4161f, 0.9998f, -0.9900f, 0.9996f, -0.6536f, 0.9992f, 0.2837f, 0.9988f, 0.9602f, 0.9982f, 0.7539f, 0.9976f}; std::vector cos_cache_data{cos_cache.cbegin(), cos_cache.cend()}; std::vector sin_cache = {0.0000f, 0.0000f, 0.8415f, 0.0100f, 0.9093f, 0.0200f, 0.1411f, 0.0300f, -0.7568f, 0.0400f, -0.9589f, 0.0500f, -0.2794f, 0.0600f, 0.6570f, 0.0699f}; std::vector sin_cache_data{sin_cache.cbegin(), sin_cache.cend()}; migraphx::parameter_map param_map; param_map["input"] = migraphx::argument(input_s, input_data.data()); param_map["pos_ids"] = migraphx::argument(pos_ids_s, pos_ids_data.data()); param_map["cos_cache"] = migraphx::argument(cache_s, cos_cache_data.data()); param_map["sin_cache"] = migraphx::argument(cache_s, sin_cache_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.0408f, 0.9166f, -1.3042f, -1.1097f, -0.1320f, -0.2751f, -0.2350f, 0.0937f, -1.6411f, -0.3948f, -1.0561f, -0.1294f, 0.6460f, -1.2937f, -0.1822f, 0.6972f, -0.2751f, -1.0178f, -1.1212f, -0.1143f, -0.3694f, -0.9235f, 0.1840f, 0.6180f}; EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{gold}, migraphx::verify::tolerance{1e-3})); } TEST_CASE(rotary_embedding_interleaved_large_verify_test) { std::size_t batch_size = 2; std::size_t sequence_length = 8; std::size_t num_heads = 4; std::size_t head_size = 6; std::size_t max_sequence_length = 16; migraphx::program p = read_onnx("rotary_embedding_interleaved_large_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape input_s{migraphx::shape::half_type, {batch_size, sequence_length, num_heads * head_size}}; migraphx::shape pos_ids_s{migraphx::shape::int32_type, {1}}; migraphx::shape cache_s{migraphx::shape::half_type, {max_sequence_length, head_size / 2}}; std::vector input = { -1.0408f, 0.9166f, -1.3042f, -1.1097f, -1.2188f, 1.1676f, -1.0190f, 0.3157f, -1.6036f, 1.8493f, 0.0447f, 1.5853f, 0.1036f, -0.3514f, 0.2421f, 0.6463f, 0.8730f, -0.9276f, 1.0311f, -1.9557f, -0.1482f, 1.7376f, 2.2039f, -0.6589f, -1.0574f, -0.1188f, -0.9078f, 0.3452f, -0.5713f, -0.2351f, -0.5912f, 1.1312f, 0.7562f, -1.2023f, -0.5833f, -0.4407f, 0.1766f, 1.0224f, -0.4826f, -0.5421f, -0.5342f, -0.6413f, 1.3314f, -0.4498f, 0.5493f, 0.0539f, 0.2601f, 0.8570f, 1.0076f, -0.7529f, -0.2250f, -0.4327f, -1.5071f, -0.4586f, -1.9791f, 0.7787f, -0.7749f, -0.1398f, 1.1414f, -0.6354f, 0.0352f, -0.4765f, -0.0409f, 1.1993f, 0.5374f, -0.1930f, 2.5211f, -0.0452f, -0.3105f, -0.9407f, -0.0034f, 1.5199f, -0.8480f, 0.5266f, 0.0299f, -0.0498f, 1.0651f, 0.8860f, -1.4702f, -0.2134f, -0.8707f, 1.6159f, -0.2356f, 0.9444f, 0.5937f, 0.7203f, 0.5061f, 1.5192f, -0.4897f, 0.9231f, 0.2654f, -0.1441f, 0.5407f, -1.5476f, 0.6455f, -1.1382f, 0.4640f, -0.4986f, 0.1289f, 2.7631f, 0.1405f, 1.1191f, 2.1134f, -0.9754f, 0.1757f, -0.1319f, -0.2735f, 0.3355f, -0.6008f, -1.1164f, 0.2577f, -0.7226f, -0.9244f, 1.8737f, 0.6052f, 1.1904f, 1.2195f, -0.0470f, -1.0914f, 1.0223f, 0.3152f, 1.7528f, -0.7650f, 1.8299f, -0.2784f, -0.2719f, 0.1885f, 2.1432f, 0.8527f, 0.0965f, -0.0625f, 0.8269f, 1.0122f, -1.4482f, -0.0644f, 0.3215f, 0.5908f, -1.4197f, 0.2113f, 0.0306f, 0.3604f, 0.3166f, -0.8975f, -0.6393f, -1.2944f, -0.0243f, -0.2354f, -0.7087f, 1.1566f, 0.4296f, 0.5599f, -0.7776f, 0.3339f, 0.1759f, 2.1108f, 1.0702f, 0.8279f, -0.2969f, 0.7120f, -0.2068f, -0.1548f, 0.1553f, 0.6207f, -0.1690f, -0.5816f, 1.2632f, 0.0695f, 1.1862f, -1.1874f, -0.7468f, -0.9320f, -0.8579f, -0.9647f, -0.0991f, 0.0195f, 1.1213f, -1.4873f, -0.2043f, -1.0466f, -1.5772f, -0.0489f, 0.3430f, 0.1264f, 0.1519f, -1.3639f, -1.6593f, 1.8127f, -1.4459f, -0.2158f, -0.9792f, -1.4392f, 0.6508f, 0.8964f, 0.5717f, -0.2390f, 0.6983f, -1.3416f, 0.2715f, -0.2852f, 0.6051f, 0.2167f, -0.2181f, -1.6306f, 1.4788f, 0.2754f, -0.0261f, -0.4618f, -0.5646f, -1.0389f, 0.5819f, 1.3697f, 0.0002f, 1.5333f, -1.0556f, -0.1254f, 0.1527f, -0.5996f, -1.0962f, 1.6327f, 1.3951f, 0.8784f, 0.3389f, 1.2907f, 0.3124f, 0.7299f, 1.4220f, 0.3375f, 0.0438f, 1.8698f, -0.2635f, -2.0799f, -0.6313f, 0.4090f, -1.1458f, 0.0784f, -1.8848f, -1.6165f, 0.6179f, 0.9905f, -0.0729f, 0.5054f, -0.6681f, -1.4382f, 1.7547f, -0.9605f, -0.4558f, -1.6105f, 0.2979f, 1.1537f, -1.5604f, 1.2779f, -1.2514f, 0.6056f, 0.5763f, -3.3558f, 0.2836f, 0.6909f, -0.7631f, 2.4451f, -0.3500f, 1.3289f, -0.6494f, 0.3478f, 1.0038f, -0.2937f, 0.9238f, -1.2185f, 0.4138f, 0.5033f, 0.9174f, 1.8131f, 1.4436f, -0.4207f, 0.0220f, -0.6807f, -1.3306f, 1.5646f, 0.3338f, 0.7105f, 0.4683f, -0.6179f, 0.0818f, -0.0488f, -0.9810f, -1.3632f, 0.0929f, -1.7926f, -0.2921f, -0.4792f, 0.6756f, -0.3413f, -0.2242f, -0.2111f, 0.6282f, 0.1667f, -1.4055f, 1.5895f, 1.0838f, -0.9077f, -0.8060f, 0.7967f, -2.9351f, 2.4179f, -0.4026f, 0.6451f, 1.6845f, -0.0901f, 0.6106f, 2.3603f, 1.3908f, -0.7917f, -0.6734f, -0.1213f, -1.1116f, -0.7401f, -0.7879f, 0.0606f, -2.3337f, -1.2603f, -1.7245f, -0.3533f, -0.9421f, -0.1776f, 0.3992f, -1.7142f, -0.5319f, -0.8848f, 0.6513f, 1.0002f, -1.4699f, -1.4254f, 0.7013f, 0.2414f, 0.2551f, -0.7457f, 0.3133f, -1.0941f, -0.3682f, -0.0163f, -0.0645f, -0.8101f, 0.1415f, 0.0551f, 0.5873f, -0.5887f, -1.4733f, -0.8565f, 0.7400f, -0.5033f, 0.0553f, 0.9265f, -0.8652f, -0.0288f, -0.2209f, 0.0610f, 0.6776f, 0.4361f, -0.8052f, 0.3955f, 0.8988f, 0.8238f, 0.2262f, 1.2912f, 0.6488f, 1.2114f, 1.3569f, 0.2983f, 0.4718f, -1.1936f, 0.7928f, -0.8665f, 0.9468f, 1.1629f, 0.0616f, -1.3136f, -0.2764f, 0.0277f, -0.1126f, 0.2342f, -0.5866f, -1.8219f, 1.1079f, 0.5795f, -1.4249f}; std::vector input_data{input.cbegin(), input.cend()}; std::vector pos_ids_data = {0}; std::vector cos_cache = { 1.0000f, 1.0000f, 1.0000f, 0.5403f, 0.9989f, 1.0000f, -0.4161f, 0.9957f, 1.0000f, -0.9900f, 0.9903f, 1.0000f, -0.6536f, 0.9828f, 1.0000f, 0.2837f, 0.9732f, 0.9999f, 0.9602f, 0.9615f, 0.9999f, 0.7539f, 0.9477f, 0.9999f, -0.1455f, 0.9318f, 0.9999f, -0.9111f, 0.9140f, 0.9998f, -0.8391f, 0.8942f, 0.9998f, 0.0044f, 0.8725f, 0.9997f, 0.8439f, 0.8488f, 0.9997f, 0.9074f, 0.8234f, 0.9996f, 0.1367f, 0.7962f, 0.9995f, -0.7597f, 0.7673f, 0.9995f}; std::vector cos_cache_data{cos_cache.cbegin(), cos_cache.cend()}; std::vector sin_cache = { 0.0000f, 0.0000f, 0.0000f, 0.8415f, 0.0464f, 0.0022f, 0.9093f, 0.0927f, 0.0043f, 0.1411f, 0.1388f, 0.0065f, -0.7568f, 0.1846f, 0.0086f, -0.9589f, 0.2300f, 0.0108f, -0.2794f, 0.2749f, 0.0129f, 0.6570f, 0.3192f, 0.0151f, 0.9894f, 0.3629f, 0.0172f, 0.4121f, 0.4057f, 0.0194f, -0.5440f, 0.4477f, 0.0215f, -1.0000f, 0.4887f, 0.0237f, -0.5366f, 0.5286f, 0.0259f, 0.4202f, 0.5675f, 0.0280f, 0.9906f, 0.6050f, 0.0302f, 0.6503f, 0.6413f, 0.0323f}; std::vector sin_cache_data{sin_cache.cbegin(), sin_cache.cend()}; migraphx::parameter_map param_map; param_map["input"] = migraphx::argument(input_s, input_data.data()); param_map["pos_ids"] = migraphx::argument(pos_ids_s, pos_ids_data.data()); param_map["cos_cache"] = migraphx::argument(cache_s, cos_cache_data.data()); param_map["sin_cache"] = migraphx::argument(cache_s, sin_cache_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = { -1.0408f, 0.9166f, -1.3042f, -1.1097f, -1.2188f, 1.1676f, -1.0190f, 0.3157f, -1.6036f, 1.8493f, 0.0447f, 1.5853f, 0.1036f, -0.3514f, 0.2421f, 0.6463f, 0.8730f, -0.9276f, 1.0311f, -1.9557f, -0.1482f, 1.7376f, 2.2039f, -0.6589f, -0.4713f, -0.9540f, -0.9229f, 0.3027f, -0.5708f, -0.2363f, -1.2713f, 0.1137f, 0.8112f, -1.1659f, -0.5824f, -0.4419f, -0.7649f, 0.7011f, -0.4569f, -0.5639f, -0.5328f, -0.6424f, 1.0979f, 0.8773f, 0.5462f, 0.0793f, 0.2582f, 0.8576f, 0.2653f, 1.2295f, -0.1839f, -0.4517f, -1.5052f, -0.4651f, 0.1155f, -2.1237f, -0.7586f, -0.2110f, 1.1441f, -0.6304f, 0.4186f, 0.2303f, -0.1519f, 1.1903f, 0.5382f, -0.1906f, -1.0080f, 2.3112f, -0.2220f, -0.9655f, -0.0099f, 1.5198f, 0.7652f, -0.6410f, 0.0365f, -0.0452f, 1.0593f, 0.8929f, 1.4856f, 0.0038f, -1.0865f, 1.4794f, -0.2417f, 0.9428f, -0.6894f, -0.6293f, 0.2904f, 1.5747f, -0.4956f, 0.9199f, -0.2424f, 0.1801f, 0.7503f, -1.4576f, 0.6529f, -1.1340f, -0.6807f, -0.0252f, -0.3834f, 2.7394f, 0.1308f, 1.1203f, -2.1196f, -0.9618f, 0.1970f, -0.0972f, -0.2764f, 0.3332f, -0.4522f, 1.1844f, 0.3867f, -0.6626f, -0.9405f, 1.8656f, 0.5053f, -1.2361f, 1.2072f, 0.1789f, -1.1002f, 1.0129f, 1.7702f, 0.1949f, -1.1653f, 1.6049f, -0.2755f, -0.2749f, 2.1087f, 0.4272f, 0.8076f, 0.2900f, -0.0714f, 0.8261f, -1.1016f, -1.3814f, -0.1366f, 0.2981f, 0.6060f, -1.4132f, 0.0893f, -0.1939f, 0.2779f, 0.3910f, -0.8906f, -0.6489f, -1.2496f, 0.3383f, -0.0315f, -0.7461f, 1.1510f, 0.4445f, 0.3203f, -0.9031f, 0.2727f, 0.2609f, 2.0968f, 1.0974f, 0.7120f, -0.5164f, 0.7415f, -0.0031f, -0.1568f, 0.1533f, 0.5487f, -0.3357f, -0.9064f, 1.0546f, 0.0542f, 1.1870f, -0.4045f, -1.3431f, -0.6094f, -1.1105f, -0.9631f, -0.1137f, -0.7219f, 0.8582f, -1.3443f, -0.6684f, -1.0227f, -1.5929f, -0.2622f, 0.2264f, 0.0713f, 0.1843f, -1.3387f, -1.6797f, 2.3165f, 0.1009f, 0.1081f, -0.9969f, -1.4488f, 0.6291f, 0.8964f, 0.5717f, -0.2390f, 0.6983f, -1.3416f, 0.2715f, -0.2852f, 0.6051f, 0.2167f, -0.2181f, -1.6306f, 1.4788f, 0.2754f, -0.0261f, -0.4618f, -0.5646f, -1.0389f, 0.5819f, 1.3697f, 0.0002f, 1.5333f, -1.0556f, -0.1254f, 0.1527f, 0.5985f, -1.0968f, 1.5662f, 1.4693f, 0.8776f, 0.3408f, 0.4345f, 1.2549f, 0.6631f, 1.4543f, 0.3374f, 0.0445f, 1.2320f, 1.4311f, -2.0483f, -0.7272f, 0.4114f, -1.1449f, 1.6283f, -0.9524f, -1.6435f, 0.5422f, 0.9907f, -0.0708f, 0.3972f, 0.7376f, -1.5947f, 1.6138f, -0.9586f, -0.4600f, 0.3993f, -1.5884f, 1.2934f, -1.4467f, 1.2833f, -1.2459f, -0.7760f, 0.3108f, -3.3677f, -0.0287f, 0.6942f, -0.7601f, -0.6993f, 2.3690f, 1.3834f, -0.5234f, 0.3435f, 1.0053f, 0.1604f, -0.9560f, -1.2641f, 0.2406f, 0.4973f, 0.9206f, -1.9987f, -1.1733f, -0.4197f, -0.0366f, -0.6720f, -1.3350f, -1.5960f, -0.1097f, 0.6386f, 0.5624f, -0.6184f, 0.0778f, 0.1867f, 0.9643f, -1.3629f, -0.0972f, -1.7907f, -0.3037f, 0.8245f, -0.0789f, -0.2940f, -0.2833f, -0.2165f, 0.6264f, -1.1726f, 0.7926f, 1.3621f, 1.3586f, -0.9007f, -0.8138f, -2.7421f, 1.3155f, 2.4507f, 0.0507f, 0.6305f, 1.6900f, 0.5210f, -0.3309f, 2.0630f, 1.8026f, -0.7859f, -0.6802f, -1.1003f, -0.1990f, -0.5391f, -0.9370f, 0.0857f, -2.3330f, -2.0112f, 0.7193f, -0.1272f, -0.9981f, -0.1818f, 0.3973f, -0.9963f, 1.4929f, -1.0109f, 0.4304f, 1.0160f, -1.4590f, 0.2682f, 1.5658f, 0.1762f, 0.3038f, -0.7491f, 0.3052f, -1.1534f, -0.0478f, 0.0021f, -0.0665f, -0.8118f, 0.1310f, 0.2171f, 0.5485f, -0.1610f, -1.5784f, -0.8660f, 0.7289f, -0.4678f, 0.1937f, 1.1287f, -0.5772f, -0.0259f, -0.2212f, 0.2479f, 0.6336f, 0.6407f, -0.6543f, 0.3838f, 0.9039f, 0.4724f, 0.7117f, 1.0165f, 1.0270f, 1.1908f, 1.3750f, -0.0850f, 0.5517f, -1.3842f, 0.3703f, -0.8806f, 0.9336f, 0.8362f, 0.8105f, -1.1566f, -0.6813f, 0.0294f, -0.1122f, 0.5620f, -0.2884f, -2.0803f, 0.4684f, 0.6009f, -1.4160f}; EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{gold}, migraphx::verify::tolerance{1e-3})); } TEST_CASE(rotary_embedding_dim_verify_test) { std::size_t batch_size = 1; std::size_t sequence_length = 2; std::size_t num_heads = 1; std::size_t head_size = 6; std::size_t rotary_embedding_dim = 4; std::size_t max_sequence_length = 2; migraphx::program p = read_onnx("rotary_embedding_dim_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape input_s{migraphx::shape::half_type, {batch_size, sequence_length, num_heads * head_size}}; migraphx::shape pos_ids_s{migraphx::shape::int32_type, {1}}; migraphx::shape cache_s{migraphx::shape::half_type, {max_sequence_length, rotary_embedding_dim / 2}}; std::vector input = {-1.0408f, 0.9166f, -1.3042f, -1.1097f, -1.2188f, 1.1676f, 1.0076f, -0.7529f, -0.2250f, -0.4327f, -1.5071f, -0.4586f}; std::vector input_data{input.cbegin(), input.cend()}; std::vector pos_ids_data = {0, 1}; std::vector cos_cache = {1.0000f, 1.0000f, 1.0000f, 0.5403f}; std::vector cos_cache_data{cos_cache.cbegin(), cos_cache.cend()}; std::vector sin_cache = {0.0000f, 0.0000f, 0.0000f, 0.8415f}; std::vector sin_cache_data{sin_cache.cbegin(), sin_cache.cend()}; migraphx::parameter_map param_map; param_map["input"] = migraphx::argument(input_s, input_data.data()); param_map["pos_ids"] = migraphx::argument(pos_ids_s, pos_ids_data.data()); param_map["cos_cache"] = migraphx::argument(cache_s, cos_cache_data.data()); param_map["sin_cache"] = migraphx::argument(cache_s, sin_cache_data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.0408f, 0.9166f, -1.3042f, -1.1097f, -1.2188f, 1.1676f, 1.0076f, -0.0427f, -0.2250f, -0.8673f, -1.5071f, -0.4586f}; EXPECT(migraphx::verify::verify_range_with_tolerance( result_vector, migraphx::verify::expected{gold}, migraphx::verify::tolerance{1e-3})); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/round_bf16_test.cpp000066400000000000000000000051231510465702400235160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(round_bf16_test) { migraphx::program p = read_onnx("round_bf16_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape xs{migraphx::shape::bf16_type, {4, 4}}; std::vector tmp = {-3.51, -3.5, -3.49, -2.51, -2.50, -2.49, -1.6, -1.5, -0.51, -0.5, 0.5, 0.6, 2.4, 2.5, 3.5, 4.5}; std::vector data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map param_map; param_map["x"] = migraphx::argument(xs, data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {-4.0, -4.0, -3.0, -3.0, -2.0, -2.0, -2.0, -2.0, -1.0, 0.0, 0.0, 1.0, 2.0, 2.0, 4.0, 4.0}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/round_half_test.cpp000066400000000000000000000051231510465702400236720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(round_half_test) { migraphx::program p = read_onnx("round_half_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape xs{migraphx::shape::half_type, {4, 4}}; std::vector tmp = {-3.51, -3.5, -3.49, -2.51, -2.50, -2.49, -1.6, -1.5, -0.51, -0.5, 0.5, 0.6, 2.4, 2.5, 3.5, 4.5}; std::vector data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map param_map; param_map["x"] = migraphx::argument(xs, data.data()); auto result = p.eval(param_map).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {-4.0, -4.0, -3.0, -3.0, -2.0, -2.0, -2.0, -2.0, -1.0, 0.0, 0.0, 1.0, 2.0, 2.0, 4.0, 4.0}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/scan_test.cpp000066400000000000000000000172661510465702400225100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include static migraphx::shape make_shape(const std::vector& lens) { return migraphx::shape{migraphx::shape::float_type, lens}; } static auto scan_test(const std::string& test_file, migraphx::shape scan_ins1_sh, migraphx::shape scan_ins2_sh) { auto prog = read_onnx(test_file); prog.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; migraphx::shape init_state_sh{migraphx::shape::float_type, {2, 2}}; std::vector init_state(init_state_sh.elements(), 0); pm["init_state"] = migraphx::argument(init_state_sh, init_state.data()); std::vector scan_ins1(scan_ins1_sh.elements()); std::iota(scan_ins1.begin(), scan_ins1.end(), 1); pm["scan_ins1"] = migraphx::argument(scan_ins1_sh, scan_ins1.data()); std::vector scan_ins2(scan_ins2_sh.elements()); std::iota(scan_ins2.begin(), scan_ins2.end(), 0); pm["scan_ins2"] = migraphx::argument(scan_ins2_sh, scan_ins2.data()); auto result = prog.eval(pm); EXPECT(result.size() == 3); return std::make_tuple(result[0], result[1], result[2]); } TEST_CASE(scan_test1) { auto [final_state, scan_out1, scan_out2] = scan_test("scan_test1.onnx", make_shape({3, 2, 2}), make_shape({3, 1})); EXPECT(final_state.get_shape() == make_shape({2, 2})); std::vector final_state_gold{18, 21, 24, 27}; EXPECT(final_state.to_vector() == final_state_gold); EXPECT(scan_out1.get_shape() == make_shape({3, 2, 2})); std::vector scan_out1_gold{1, 2, 3, 4, 7, 9, 11, 13, 18, 21, 24, 27}; EXPECT(scan_out1.to_vector() == scan_out1_gold); EXPECT(scan_out2.get_shape() == make_shape({3, 2})); std::vector scan_out2_gold{4, 6, 18, 22, 42, 48}; EXPECT(scan_out2.to_vector() == scan_out2_gold); } TEST_CASE(scan_test2) { auto [final_state, scan_out1, scan_out2] = scan_test("scan_test2.onnx", make_shape({3, 2, 2}), make_shape({3, 1})); EXPECT(final_state.get_shape() == make_shape({2, 2})); std::vector final_state_gold{18, 21, 24, 27}; EXPECT(final_state.to_vector() == final_state_gold); EXPECT(scan_out1.get_shape() == make_shape({3, 2, 2})); std::vector scan_out1_gold{18, 21, 24, 27, 7, 9, 11, 13, 1, 2, 3, 4}; EXPECT(scan_out1.to_vector() == scan_out1_gold); EXPECT(scan_out2.get_shape() == make_shape({3, 2})); std::vector scan_out2_gold{4, 6, 18, 22, 42, 48}; EXPECT(scan_out2.to_vector() == scan_out2_gold); } TEST_CASE(scan_test3) { auto [final_state, scan_out1, scan_out2] = scan_test("scan_test3.onnx", make_shape({3, 2, 2}), make_shape({3, 1})); EXPECT(final_state.get_shape() == make_shape({2, 2})); std::vector final_state_gold{18, 21, 24, 27}; EXPECT(final_state.to_vector() == final_state_gold); EXPECT(scan_out1.get_shape() == make_shape({2, 3, 2})); std::vector scan_out1_gold{1, 2, 7, 9, 18, 21, 3, 4, 11, 13, 24, 27}; EXPECT(scan_out1.to_vector() == scan_out1_gold); EXPECT(scan_out2.get_shape() == make_shape({2, 3})); std::vector scan_out2_gold{4, 18, 42, 6, 22, 48}; EXPECT(scan_out2.to_vector() == scan_out2_gold); } TEST_CASE(scan_test4) { auto [final_state, scan_out1, scan_out2] = scan_test("scan_test4.onnx", make_shape({3, 2, 2}), make_shape({3, 1})); EXPECT(final_state.get_shape() == make_shape({2, 2})); std::vector final_state_gold{18, 21, 24, 27}; EXPECT(final_state.to_vector() == final_state_gold); EXPECT(scan_out1.get_shape() == make_shape({3, 2, 2})); std::vector scan_out1_gold{9, 10, 11, 12, 15, 17, 19, 21, 18, 21, 24, 27}; EXPECT(scan_out1.to_vector() == scan_out1_gold); EXPECT(scan_out2.get_shape() == make_shape({3, 2})); std::vector scan_out2_gold{20, 22, 34, 38, 42, 48}; EXPECT(scan_out2.to_vector() == scan_out2_gold); } TEST_CASE(scan_test5) { auto [final_state, scan_out1, scan_out2] = scan_test("scan_test5.onnx", make_shape({2, 2, 3}), make_shape({1, 3})); EXPECT(final_state.get_shape() == make_shape({2, 2})); std::vector final_state_gold{9, 18, 27, 36}; EXPECT(final_state.to_vector() == final_state_gold); EXPECT(scan_out1.get_shape() == make_shape({3, 2, 2})); std::vector scan_out1_gold{1, 4, 7, 10, 4, 10, 16, 22, 9, 18, 27, 36}; EXPECT(scan_out1.to_vector() == scan_out1_gold); EXPECT(scan_out2.get_shape() == make_shape({3, 2})); std::vector scan_out2_gold{8, 14, 20, 32, 36, 54}; EXPECT(scan_out2.to_vector() == scan_out2_gold); } TEST_CASE(scan_test6) { auto [final_state, scan_out1, scan_out2] = scan_test("scan_test6.onnx", make_shape({2, 3, 2}), make_shape({3, 1})); EXPECT(final_state.get_shape() == make_shape({2, 2})); std::vector final_state_gold{12, 15, 30, 33}; EXPECT(final_state.to_vector() == final_state_gold); EXPECT(scan_out1.get_shape() == make_shape({2, 2, 3})); std::vector scan_out1_gold{12, 7, 3, 15, 9, 4, 30, 19, 9, 33, 21, 10}; EXPECT(scan_out1.to_vector() == scan_out1_gold); EXPECT(scan_out2.get_shape() == make_shape({2, 3})); std::vector scan_out2_gold{42, 26, 12, 48, 30, 14}; EXPECT(scan_out2.to_vector() == scan_out2_gold); } TEST_CASE(scan_test7) { auto prog = read_onnx("scan_test7.onnx"); prog.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; migraphx::shape init_state_sh{migraphx::shape::float_type, {2, 2}}; std::vector init_state(init_state_sh.elements(), 0); pm["init_state"] = migraphx::argument(init_state_sh, init_state.data()); migraphx::shape scan_ins_sh{migraphx::shape::float_type, {3, 2, 2}}; std::vector scan_ins(scan_ins_sh.elements()); std::iota(scan_ins.begin(), scan_ins.end(), 1); pm["scan_ins"] = migraphx::argument(scan_ins_sh, scan_ins.data()); auto result = prog.eval(pm); EXPECT(result.size() == 2); EXPECT(result[0].get_shape() == make_shape({2, 2})); std::vector final_state_gold{30, 36, 42, 48}; EXPECT(result[0].to_vector() == final_state_gold); EXPECT(result[1].get_shape() == make_shape({3, 2, 2})); EXPECT(result[1].to_vector() == scan_ins); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/selu_test.cpp000066400000000000000000000036221510465702400225230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(selu_test) { migraphx::program p = read_onnx("selu_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape xs{migraphx::shape::double_type, {2, 3}}; std::vector x_data = {1.1, 2.1, 0.0, -1.3, -5.3, 12.0}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(xs, x_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.55, 1.05, 0, -0.10912, -0.149251, 6}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/shrink_fp8_test.cpp000066400000000000000000000043471510465702400236330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(shrink_fp8_test) { migraphx::program p = optimize_onnx("shrink_fp8_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::fp8e4m3fnuz_type, {3, 3}}; // TODO: Make FP8 vector work for initializer list. std::vector tmp_data{-4, -3, -2, -1, 0, 1, 2, 3, 4}; std::vector data{tmp_data.cbegin(), tmp_data.cend()}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); // TODO: Make FP8 vector work for initializer list. std::vector tmp_gold = {-2, -1, 0, 0, 0, 0, 0, 1, 2}; std::vector gold{tmp_gold.cbegin(), tmp_gold.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/shrink_hard_test.cpp000066400000000000000000000035501510465702400240470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(shrink_hard_test) { migraphx::program p = read_onnx("shrink_hard_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {5}}; std::vector data{-2, -1, 0, 1, 2}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2, 0, 0, 0, 2}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/shrink_int8_test.cpp000066400000000000000000000036101510465702400240100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(shrink_int8_test) { migraphx::program p = read_onnx("shrink_int8_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::int8_type, {3, 3}}; std::vector data{-4, -3, -2, -1, 0, 1, 2, 3, 4}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2, -1, 0, 0, 0, 0, 0, 1, 2}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/shrink_soft_test.cpp000066400000000000000000000035541510465702400241100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(shrink_soft_test) { migraphx::program p = read_onnx("shrink_soft_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {5}}; std::vector data{-2, -1, 0, 1, 2}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {-0.5, 0, 0, 0, 0.5}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/shrink_uint8_test.cpp000066400000000000000000000036141510465702400242010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(shrink_uint8_test) { migraphx::program p = read_onnx("shrink_uint8_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::uint8_type, {3, 3}}; std::vector data{1, 2, 3, 4, 5, 6, 7, 8, 9}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 0, 0, 0, 10, 11, 12, 13}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/shrink_verify2_test.cpp000066400000000000000000000037731510465702400245260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(shrink_verify2_test) { migraphx::program p = read_onnx("shrink_verify2_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::half_type, {5}}; std::vector tmp = {-10.0, -5.0, 0.0, 5.0, 10.0}; std::vector data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {-5.0, 0.0, 5.0, 10.0, 5.0}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/shrink_verify_test.cpp000066400000000000000000000037711510465702400244420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(shrink_verify_test) { migraphx::program p = read_onnx("shrink_verify_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::half_type, {5}}; std::vector tmp = {-10.0, -5.0, 0.0, 5.0, 10.0}; std::vector data{tmp.cbegin(), tmp.cend()}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); tmp = {-9.0, -4.0, 1.0, 4.0, 9.0}; std::vector gold{tmp.cbegin(), tmp.cend()}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/simplified_layer_normalization.cpp000066400000000000000000000064271510465702400270110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(simplified_layer_normalization_test) { using migraphx::half; std::vector x{half{0.8}, half{-0.5}, half{0.0}, half{1.0}, half{0.5}, half{0.2}, half{0.3}, half{-0.6}, half{10.0}, half{-1.0}, half{0.0}, half{1.0}, half{1.2}, half{3.2}, half{-4.1}, half{5.3}}; std::vector scale{half{0.1}, half{0.2}, half{4.0}, half{-2.2}}; auto p = read_onnx("simplified_layer_normalization_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::half_type, {2, 2, 4}}; migraphx::shape s_s{migraphx::shape::half_type, {4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp["scale"] = migraphx::argument(s_s, scale.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {half{0.11633}, half{-0.1455}, half{0.0}, half{-3.2}, half{0.1162}, half{0.09296}, half{2.791}, half{3.068}, half{0.198}, half{-0.03958}, half{0.0}, half{-0.4355}, half{0.0319}, half{0.17}, half{-4.363}, half{-3.1}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/size_fp8_test.cpp000066400000000000000000000033471510465702400233060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(size_fp8_test) { migraphx::program p = read_onnx("size_fp8_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::fp8e4m3fnuz_type, {2, 5, 3}}; std::vector data(30, 1.); migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); auto size_result = result.at(); EXPECT(size_result == int64_t{30}); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/size_verify_test.cpp000066400000000000000000000033101510465702400241030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(size_verify_test) { migraphx::program p = read_onnx("size_verify_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {2, 5, 3}}; std::vector data(30, 1.); migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); auto size_result = result.at(); EXPECT(size_result == int64_t{30}); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/skip_layer_normalization.cpp000066400000000000000000000631411510465702400256260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(skip_layer_normalization_test) { using migraphx::half; std::vector x{half{0.8}, half{-0.5}, half{0.0}, half{1.0}, half{0.5}, half{0.2}, half{0.3}, half{-0.6}, half{10.0}, half{-1.0}, half{0.0}, half{1.0}, half{1.2}, half{3.2}, half{-4.1}, half{5.3}}; std::vector skip{half{1.2}, half{-1.0}, half{2.0}, half{1.0}, half{1.5}, half{2.2}, half{-3.3}, half{2.6}, half{1.0}, half{-10.0}, half{-1.0}, half{2.0}, half{-1.2}, half{1.6}, half{-1.1}, half{1.3}}; std::vector scale{half{0.1}, half{0.2}, half{4.0}, half{-2.2}}; auto p = read_onnx("skip_layer_normalization_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::half_type, {2, 2, 4}}; migraphx::shape s_s{migraphx::shape::half_type, {4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp["skip"] = migraphx::argument(s_x, skip.data()); pp["gamma"] = migraphx::argument(s_s, scale.data()); auto results = p.eval(pp); const auto& output = results.at(0); const auto& mean = results.at(1); const auto& inv_std_var = results.at(2); const auto& input_skip_bias_sum = results.at(3); std::vector result_vector; std::vector mean_vector; std::vector inv_std_var_vector; std::vector input_skip_bias_sum_vector; output.visit([&](auto vals) { result_vector.assign(vals.begin(), vals.end()); }); mean.visit([&](auto vals) { mean_vector.assign(vals.begin(), vals.end()); }); inv_std_var.visit([&](auto vals) { inv_std_var_vector.assign(vals.begin(), vals.end()); }); input_skip_bias_sum.visit( [&](auto vals) { input_skip_bias_sum_vector.assign(vals.begin(), vals.end()); }); std::vector gold = {half{0.05770874}, half{-0.34619141}, half{2.30859375}, half{-1.26953125}, half{0.05160522}, half{0.13891602}, half{-6.91015625}, half{-1.13476562}, half{0.13244629}, half{-0.29028320}, half{-0.75732422}, half{-0.69384766}, half{-0.03375244}, half{0.14160156}, half{-5.88671875}, half{-2.42187500}}; std::vector gold_mean = { half{1.12500000}, half{0.84960938}, half{0.50000000}, half{1.54882812}}; std::vector gold_inv_std_var = { half{0.65966797}, half{0.44873047}, half{0.12622070}, half{0.21801758}}; std::vector gold_input_skip_bias_sum = {half{2.00000000}, half{-1.50000000}, half{2.00000000}, half{2.00000000}, half{2.00000000}, half{2.39843750}, half{-3.00000000}, half{2.00000000}, half{11.00000000}, half{-11.00000000}, half{-1.00000000}, half{3.00000000}, half{0.00000000}, half{4.79687500}, half{-5.20312500}, half{6.60156250}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(mean_vector, gold_mean)); EXPECT(migraphx::verify::verify_rms_range(inv_std_var_vector, gold_inv_std_var)); EXPECT( migraphx::verify::verify_rms_range(input_skip_bias_sum_vector, gold_input_skip_bias_sum)); } TEST_CASE(skip_layer_normalization_beta_test) { using migraphx::half; std::vector x{half{0.8}, half{-0.5}, half{0.0}, half{1.0}, half{0.5}, half{0.2}, half{0.3}, half{-0.6}, half{10.0}, half{-1.0}, half{0.0}, half{1.0}, half{1.2}, half{3.2}, half{-4.1}, half{5.3}}; std::vector skip{half{1.2}, half{-1.0}, half{2.0}, half{1.0}, half{1.5}, half{2.2}, half{-3.3}, half{2.6}, half{1.0}, half{-10.0}, half{-1.0}, half{2.0}, half{-1.2}, half{1.6}, half{-1.1}, half{1.3}}; std::vector scale{half{0.1}, half{0.2}, half{4.0}, half{-2.2}}; std::vector beta{half{1.0}, half{1.0}, half{1.0}, half{1.0}}; auto p = read_onnx("skip_layer_normalization_beta_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::half_type, {2, 2, 4}}; migraphx::shape s_s{migraphx::shape::half_type, {4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp["skip"] = migraphx::argument(s_x, skip.data()); pp["gamma"] = migraphx::argument(s_s, scale.data()); pp["beta"] = migraphx::argument(s_s, beta.data()); auto results = p.eval(pp); const auto& output = results.at(0); const auto& mean = results.at(1); const auto& inv_std_var = results.at(2); const auto& input_skip_bias_sum = results.at(3); std::vector result_vector; std::vector mean_vector; std::vector inv_std_var_vector; std::vector input_skip_bias_sum_vector; output.visit([&](auto vals) { result_vector.assign(vals.begin(), vals.end()); }); mean.visit([&](auto vals) { mean_vector.assign(vals.begin(), vals.end()); }); inv_std_var.visit([&](auto vals) { inv_std_var_vector.assign(vals.begin(), vals.end()); }); input_skip_bias_sum.visit( [&](auto vals) { input_skip_bias_sum_vector.assign(vals.begin(), vals.end()); }); std::vector gold = {half{1.05761719}, half{0.65380859}, half{3.30859375}, half{-0.26953125}, half{1.05175781}, half{1.13867188}, half{-5.91015625}, half{-0.13476562}, half{1.13281250}, half{0.70996094}, half{0.24267578}, half{0.30615234}, half{0.96630859}, half{1.14160156}, half{-4.88671875}, half{-1.42187500}}; std::vector gold_mean = { half{1.12500000}, half{0.84960938}, half{0.50000000}, half{1.54882812}}; std::vector gold_inv_std_var = { half{0.65966797}, half{0.44873047}, half{0.12622070}, half{0.21801758}}; std::vector gold_input_skip_bias_sum = {half{2.00000000}, half{-1.50000000}, half{2.00000000}, half{2.00000000}, half{2.00000000}, half{2.39843750}, half{-3.00000000}, half{2.00000000}, half{11.00000000}, half{-11.00000000}, half{-1.00000000}, half{3.00000000}, half{0.00000000}, half{4.79687500}, half{-5.20312500}, half{6.60156250}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(mean_vector, gold_mean)); EXPECT(migraphx::verify::verify_rms_range(inv_std_var_vector, gold_inv_std_var)); EXPECT( migraphx::verify::verify_rms_range(input_skip_bias_sum_vector, gold_input_skip_bias_sum)); } TEST_CASE(skip_layer_normalization_beta_bias_test) { using migraphx::half; std::vector x{half{0.8}, half{-0.5}, half{0.0}, half{1.0}, half{0.5}, half{0.2}, half{0.3}, half{-0.6}, half{10.0}, half{-1.0}, half{0.0}, half{1.0}, half{1.2}, half{3.2}, half{-4.1}, half{5.3}}; std::vector skip{half{1.2}, half{-1.0}, half{2.0}, half{1.0}, half{1.5}, half{2.2}, half{-3.3}, half{2.6}, half{1.0}, half{-10.0}, half{-1.0}, half{2.0}, half{-1.2}, half{1.6}, half{-1.1}, half{1.3}}; std::vector scale{half{0.1}, half{0.2}, half{4.0}, half{-2.2}}; std::vector beta{half{1.0}, half{1.0}, half{1.0}, half{1.0}}; std::vector bias{half{1.0}, half{1.0}, half{1.0}, half{1.0}}; auto p = read_onnx("skip_layer_normalization_beta_bias_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::half_type, {2, 2, 4}}; migraphx::shape s_s{migraphx::shape::half_type, {4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp["skip"] = migraphx::argument(s_x, skip.data()); pp["gamma"] = migraphx::argument(s_s, scale.data()); pp["beta"] = migraphx::argument(s_s, beta.data()); pp["bias"] = migraphx::argument(s_s, bias.data()); auto results = p.eval(pp); const auto& output = results.at(0); const auto& mean = results.at(1); const auto& inv_std_var = results.at(2); const auto& input_skip_bias_sum = results.at(3); std::vector result_vector; std::vector mean_vector; std::vector inv_std_var_vector; std::vector input_skip_bias_sum_vector; output.visit([&](auto vals) { result_vector.assign(vals.begin(), vals.end()); }); mean.visit([&](auto vals) { mean_vector.assign(vals.begin(), vals.end()); }); inv_std_var.visit([&](auto vals) { inv_std_var_vector.assign(vals.begin(), vals.end()); }); input_skip_bias_sum.visit( [&](auto vals) { input_skip_bias_sum_vector.assign(vals.begin(), vals.end()); }); std::vector gold = {half{1.05761719}, half{0.65380859}, half{3.30859375}, half{-0.26953125}, half{1.05175781}, half{1.13867188}, half{-5.91015625}, half{-0.13476562}, half{1.13281250}, half{0.70996094}, half{0.24267578}, half{0.30615234}, half{0.96630859}, half{1.14160156}, half{-4.88671875}, half{-1.42187500}}; std::vector gold_mean = { half{2.12500000}, half{1.84960938}, half{1.50000000}, half{2.54882812}}; std::vector gold_inv_std_var = { half{0.65966797}, half{0.44873047}, half{0.12622070}, half{0.21801758}}; std::vector gold_input_skip_bias_sum = {half{3.00000000}, half{-0.50000000}, half{3.00000000}, half{3.00000000}, half{3.00000000}, half{3.39843750}, half{-2.00000000}, half{3.00000000}, half{12.00000000}, half{-10.00000000}, half{0.00000000}, half{4.00000000}, half{1.00000000}, half{5.79687500}, half{-4.20312500}, half{7.60156250}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(mean_vector, gold_mean)); EXPECT(migraphx::verify::verify_rms_range(inv_std_var_vector, gold_inv_std_var)); EXPECT( migraphx::verify::verify_rms_range(input_skip_bias_sum_vector, gold_input_skip_bias_sum)); } TEST_CASE(skip_layer_normalization_2d_skip_test) { using migraphx::half; std::vector x{half{0.8}, half{-0.5}, half{0.0}, half{1.0}, half{0.5}, half{0.2}, half{0.3}, half{-0.6}, half{10.0}, half{-1.0}, half{0.0}, half{1.0}, half{1.2}, half{3.2}, half{-4.1}, half{5.3}}; // 2D skip has only 8 elements instead of 16 std::vector skip{ half{1.2}, half{-1.0}, half{2.0}, half{1.0}, half{-1.2}, half{1.6}, half{-1.1}, half{1.3}}; std::vector scale{half{0.1}, half{0.2}, half{4.0}, half{-2.2}}; auto p = read_onnx("skip_layer_normalization_2d_skip_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::half_type, {2, 2, 4}}; migraphx::shape s_skip{migraphx::shape::half_type, {2, 4}}; migraphx::shape s_s{migraphx::shape::half_type, {4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp["skip"] = migraphx::argument(s_skip, skip.data()); pp["gamma"] = migraphx::argument(s_s, scale.data()); auto results = p.eval(pp); const auto& output = results.at(0); const auto& mean = results.at(1); const auto& inv_std_var = results.at(2); const auto& input_skip_bias_sum = results.at(3); std::vector result_vector; std::vector mean_vector; std::vector inv_std_var_vector; std::vector input_skip_bias_sum_vector; output.visit([&](auto vals) { result_vector.assign(vals.begin(), vals.end()); }); mean.visit([&](auto vals) { mean_vector.assign(vals.begin(), vals.end()); }); inv_std_var.visit([&](auto vals) { inv_std_var_vector.assign(vals.begin(), vals.end()); }); input_skip_bias_sum.visit( [&](auto vals) { input_skip_bias_sum_vector.assign(vals.begin(), vals.end()); }); std::vector gold = {half{0.0577393}, half{-0.346191}, half{2.3125}, half{-1.27051}, half{-0.0884399}, half{0.288574}, half{-3.91406}, half{-0.922363}, half{0.162842}, half{-0.218628}, half{-1.07227}, half{0.589355}, half{-0.0338135}, half{0.141846}, half{-5.89062}, half{-2.42188}}; std::vector gold_mean = {half{1.12402}, half{0.25}, half{3.29883}, half{1.54883}}; std::vector gold_inv_std_var = { half{0.660156}, half{0.932129}, half{0.206543}, half{0.218506}}; std::vector gold_input_skip_bias_sum = {half{1.99902}, half{-1.5}, half{2}, half{2}, half{-0.699219}, half{1.79883}, half{-0.799805}, half{0.700195}, half{11.1953}, half{-2}, half{2}, half{2}, half{0}, half{4.79688}, half{-5.19531}, half{6.59375}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(mean_vector, gold_mean)); EXPECT(migraphx::verify::verify_rms_range(inv_std_var_vector, gold_inv_std_var)); EXPECT( migraphx::verify::verify_rms_range(input_skip_bias_sum_vector, gold_input_skip_bias_sum)); } TEST_CASE(skip_layer_normalization_skip_batch_size_1_test) { using migraphx::half; std::vector x{half{0.8}, half{-0.5}, half{0.0}, half{1.0}, half{0.5}, half{0.2}, half{0.3}, half{-0.6}, half{10.0}, half{-1.0}, half{0.0}, half{1.0}, half{1.2}, half{3.2}, half{-4.1}, half{5.3}}; std::vector skip{ half{1.2}, half{-1.0}, half{2.0}, half{1.0}, half{1.5}, half{2.2}, half{-3.3}, half{2.6}}; std::vector scale{half{0.1}, half{0.2}, half{4.0}, half{-2.2}}; auto p = read_onnx("skip_layer_normalization_skip_batch_size_1_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::half_type, {2, 2, 4}}; migraphx::shape s_skip{migraphx::shape::half_type, {1, 2, 4}}; migraphx::shape s_s{migraphx::shape::half_type, {4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp["skip"] = migraphx::argument(s_skip, skip.data()); pp["gamma"] = migraphx::argument(s_s, scale.data()); auto results = p.eval(pp); const auto& output = results.at(0); const auto& mean = results.at(1); const auto& inv_std_var = results.at(2); const auto& input_skip_bias_sum = results.at(3); std::vector result_vector; std::vector mean_vector; std::vector inv_std_var_vector; std::vector input_skip_bias_sum_vector; output.visit([&](auto vals) { result_vector.assign(vals.begin(), vals.end()); }); mean.visit([&](auto vals) { mean_vector.assign(vals.begin(), vals.end()); }); inv_std_var.visit([&](auto vals) { inv_std_var_vector.assign(vals.begin(), vals.end()); }); input_skip_bias_sum.visit( [&](auto vals) { input_skip_bias_sum_vector.assign(vals.begin(), vals.end()); }); std::vector gold = {half{0.05773491}, half{-0.34640941}, half{2.30939627}, half{-1.27016795}, half{0.05159748}, half{0.13908887}, half{-6.90957546}, half{-1.13514447}, half{0.16306864}, half{-0.21880098}, half{-1.07336318}, half{0.59034979}, half{0.00946274}, half{0.11183234}, half{-6.57230043}, half{-2.17642951}}; std::vector gold_mean = { half{1.12500000}, half{0.85000002}, half{3.29999995}, half{2.15000010}}; std::vector gold_inv_std_var = { half{0.65982747}, half{0.44867375}, half{0.20641601}, half{0.17204976}}; std::vector gold_input_skip_bias_sum = {half{2.00000000}, half{-1.50000000}, half{2.00000000}, half{2.00000000}, half{2.00000000}, half{2.40000010}, half{-3.00000000}, half{1.99999988}, half{11.19999981}, half{-2.00000000}, half{2.00000000}, half{2.00000000}, half{2.70000005}, half{5.40000010}, half{-7.39999962}, half{7.90000010}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(mean_vector, gold_mean)); EXPECT(migraphx::verify::verify_rms_range(inv_std_var_vector, gold_inv_std_var)); EXPECT( migraphx::verify::verify_rms_range(input_skip_bias_sum_vector, gold_input_skip_bias_sum)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/skip_simplified_layer_normalization.cpp000066400000000000000000000255121510465702400300330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(skip_simplified_layer_normalization_test) { using migraphx::half; std::vector x{half{0.8}, half{-0.5}, half{0.0}, half{1.0}, half{0.5}, half{0.2}, half{0.3}, half{-0.6}, half{10.0}, half{-1.0}, half{0.0}, half{1.0}, half{1.2}, half{3.2}, half{-4.1}, half{5.3}}; std::vector skip{half{1.2}, half{-1.0}, half{2.0}, half{1.0}, half{1.5}, half{2.2}, half{-3.3}, half{2.6}, half{1.0}, half{-10.0}, half{-1.0}, half{2.0}, half{-1.2}, half{1.6}, half{-1.1}, half{1.3}}; std::vector scale{half{0.1}, half{0.2}, half{4.0}, half{-2.2}}; auto p = read_onnx("skip_simplified_layer_normalization_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::half_type, {2, 2, 4}}; migraphx::shape s_s{migraphx::shape::half_type, {4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp["skip"] = migraphx::argument(s_x, skip.data()); pp["gamma"] = migraphx::argument(s_s, scale.data()); auto results = p.eval(pp); const auto& output = results.at(0); const auto& mean = results.at(1); const auto& inv_std_var = results.at(2); const auto& input_skip_bias_sum = results.at(3); std::vector result_vector; std::vector mean_vector; std::vector inv_std_var_vector; std::vector input_skip_bias_sum_vector; output.visit([&](auto vals) { result_vector.assign(vals.begin(), vals.end()); }); mean.visit([&](auto vals) { mean_vector.assign(vals.begin(), vals.end()); }); inv_std_var.visit([&](auto vals) { inv_std_var_vector.assign(vals.begin(), vals.end()); }); input_skip_bias_sum.visit( [&](auto vals) { input_skip_bias_sum_vector.assign(vals.begin(), vals.end()); }); std::vector gold = {half{0.10596}, half{-0.1589}, half{4.24}, half{-2.33}, half{0.0838}, half{0.2012}, half{-5.03}, half{-1.844}, half{0.1385}, half{-0.277}, half{-0.504}, half{-0.831}, half{0.0}, half{0.1984}, half{-4.3}, half{-3.0}}; std::vector gold_mean{half{3.5625}, half{5.6875}, half{63.0}, half{23.421875}}; std::vector gold_inv_std_var{half{0.5298}, half{0.4194}, half{0.1260}, half{0.2067}}; std::vector gold_input_skip_bias_sum = {half{2.0}, half{-1.5}, half{2.0}, half{2.0}, half{2.0}, half{2.3984375}, half{-3.0}, half{2.0}, half{11.0}, half{-11.0}, half{-1.0}, half{3.0}, half{0.0}, half{4.796875}, half{-5.203125}, half{6.6015625}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(mean_vector, gold_mean)); EXPECT(migraphx::verify::verify_rms_range(inv_std_var_vector, gold_inv_std_var)); EXPECT( migraphx::verify::verify_rms_range(input_skip_bias_sum_vector, gold_input_skip_bias_sum)); } TEST_CASE(skip_simplified_layer_normalization_bias_test) { using migraphx::half; std::vector x{half{0.8}, half{-0.5}, half{0.0}, half{1.0}, half{0.5}, half{0.2}, half{0.3}, half{-0.6}, half{10.0}, half{-1.0}, half{0.0}, half{1.0}, half{1.2}, half{3.2}, half{-4.1}, half{5.3}}; std::vector skip{half{1.2}, half{-1.0}, half{2.0}, half{1.0}, half{1.5}, half{2.2}, half{-3.3}, half{2.6}, half{1.0}, half{-10.0}, half{-1.0}, half{2.0}, half{-1.2}, half{1.6}, half{-1.1}, half{1.3}}; std::vector scale{half{0.1}, half{0.2}, half{4.0}, half{-2.2}}; std::vector bias{half{1.0}, half{1.0}, half{1.0}, half{1.0}}; auto p = read_onnx("skip_simplified_layer_normalization_bias_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s_x{migraphx::shape::half_type, {2, 2, 4}}; migraphx::shape s_s{migraphx::shape::half_type, {4}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, x.data()); pp["skip"] = migraphx::argument(s_x, skip.data()); pp["gamma"] = migraphx::argument(s_s, scale.data()); pp["bias"] = migraphx::argument(s_s, bias.data()); auto results = p.eval(pp); const auto& output = results.at(0); const auto& mean = results.at(1); const auto& inv_std_var = results.at(2); const auto& input_skip_bias_sum = results.at(3); std::vector result_vector; std::vector mean_vector; std::vector inv_std_var_vector; std::vector input_skip_bias_sum_vector; output.visit([&](auto vals) { result_vector.assign(vals.begin(), vals.end()); }); mean.visit([&](auto vals) { mean_vector.assign(vals.begin(), vals.end()); }); inv_std_var.visit([&](auto vals) { inv_std_var_vector.assign(vals.begin(), vals.end()); }); input_skip_bias_sum.visit( [&](auto vals) { input_skip_bias_sum_vector.assign(vals.begin(), vals.end()); }); std::vector gold = {half{1.10596}, half{0.8411}, half{5.24}, half{-1.33}, half{1.0838}, half{1.2012}, half{-4.03}, half{-0.844}, half{1.1385}, half{0.723}, half{0.496}, half{0.169}, half{1.0}, half{1.1984}, half{-3.3}, half{-2.0}}; std::vector gold_mean{half{3.5625}, half{5.6875}, half{63.0}, half{23.421875}}; std::vector gold_inv_std_var{half{0.5298}, half{0.4194}, half{0.1260}, half{0.2067}}; std::vector gold_input_skip_bias_sum = {half{3.0}, half{-0.5}, half{3.0}, half{3.0}, half{3.0}, half{3.3984375}, half{-2.0}, half{3.0}, half{12.0}, half{-10.0}, half{0.0}, half{4.0}, half{1.0}, half{5.796875}, half{-4.203125}, half{7.6015625}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(mean_vector, gold_mean)); EXPECT(migraphx::verify::verify_rms_range(inv_std_var_vector, gold_inv_std_var)); EXPECT( migraphx::verify::verify_rms_range(input_skip_bias_sum_vector, gold_input_skip_bias_sum)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/slice_5arg_reverse_test.cpp000066400000000000000000000040201510465702400253140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(slice_reverse_test) { migraphx::program p = read_onnx("slice_5arg_reverse_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sh_data{migraphx::shape::float_type, {5, 5}}; // start std::vector data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(sh_data, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {14, 13, 12, 11, 19, 18, 17, 16}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/slice_5arg_step_test.cpp000066400000000000000000000037621510465702400246300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(slice_step_test) { migraphx::program p = read_onnx("slice_5arg_step_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sh_data{migraphx::shape::float_type, {5, 5}}; // start std::vector data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(sh_data, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {14, 12}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/slice_5arg_test.cpp000066400000000000000000000040051510465702400235640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(slice_5arg_test) { migraphx::program p = read_onnx("slice_5arg_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sh_data{migraphx::shape::float_type, {5, 5}}; // start std::vector data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(sh_data, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {10, 11, 12, 13, 15, 16, 17, 18}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/slice_test.cpp000066400000000000000000000035471510465702400226600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(slice_test) { migraphx::program p = read_onnx("slice_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape sh_data{migraphx::shape::float_type, {3, 2}}; std::vector data = {0, 1, 2, 3, 4, 5}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(sh_data, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {2, 3}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/softmaxcrossentropyloss_kd_dim_weighted.cpp000066400000000000000000000231671510465702400307660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(softmaxcrossentropyloss_kd_no_reduction_even_weighted_ones_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_kd_no_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 1.0f, 1.0f, 1.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436, 1.38629436}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_kd_no_reduction_uneven_weighted_ones_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_kd_no_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0.69314718, 2.77258872, 1.38629436, 4.15888308, 0.69314718, 2.77258872, 1.38629436, 4.15888308, 0.69314718, 2.77258872, 1.38629436, 4.15888308, 0.69314718, 2.77258872}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_kd_sum_reduction_weighted_test_ones) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_kd_sum_reduction_double_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::double_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements(), 1.0); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::double_type, {4}}; std::vector weight_data = {1.0, 1.0, 1.0, 1.0}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {22.18070977791825}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_kd_sum_reduction_weighted_test_zeroes) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_kd_sum_reduction_double_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::double_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements()); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::double_type, {4}}; std::vector weight_data = {1.0, 1.0, 1.0, 1.0}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {22.18070977791825}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_kd_mean_reduction_weighted_test) { using migraphx::half; migraphx::program p = optimize_onnx("softmaxcrossentropyloss_kd_mean_reduction_half_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::half_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements(), half(1.0)); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::half_type, {4}}; std::vector weight_data = {half{1.0}, half{1.0}, half{1.0}, half{1.0}}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {half{1.38629436}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_kd_mean_reduction_uneven_weighted_test) { using migraphx::half; migraphx::program p = optimize_onnx("softmaxcrossentropyloss_kd_mean_reduction_half_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::half_type, {4, 4, 2, 2}}; std::vector score_data(score_shape.elements(), half(1.0)); migraphx::shape label_shape{migraphx::shape::int32_type, {4, 2, 2}}; std::vector label_data = {0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2, 0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::half_type, {4}}; std::vector weight_data = {half{1.0}, half{0.5}, half{2.0}, half{3.0}}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {half{1.38629436}}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/softmaxcrossentropyloss_no_attr.cpp000066400000000000000000000322641510465702400273230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_test_ones) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629f, 1.38629f, 1.38629f, 1.38629f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_test_asym_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_asym_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {3, 4}}; std::vector score_data = {1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3}; migraphx::shape label_shape{migraphx::shape::int32_type, {3}}; std::vector label_data = {0, 3, 1}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 1.38629436, 1.38629436}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_one_hot_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {1, 1, 1, 1}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.74366838, 0.74366838, 1.74366838, 1.74366838}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_test_zeros) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 0); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629f, 1.38629f, 1.38629f, 1.38629f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_test_ones) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {5.5452f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_test_zeroes) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 0); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {5.5452f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_test_ones) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_test_zeroes) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 0); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_test_rand_data) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data{0.96771913, 0.41032661, 0.20715942, 0.03879957, 0.72009297, 0.87246912, 0.45548323, 0.08531375, 0.19684932, 0.53406917, 0.96924279, 0.32202707, 0.41944426, 0.42816934, 0.36742527, 0.6424184}; migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 2, 1, 0}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.88998183, 1.50650129, 1.40313031, 1.43695476}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_test_rand_data) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data{0.70832104, 0.57696298, 0.60347884, 0.93392199, 0.25057635, 0.33402099, 0.57135317, 0.38481569, 0.14061437, 0.60612205, 0.24493107, 0.07951325, 0.32728171, 0.06750434, 0.34526496, 0.28548175}; migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 2, 1, 0}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.2480964271388393}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_test_rand_data) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data{0.49182203, 0.68737944, 0.98275259, 0.52598715, 0.28430275, 0.59839527, 0.84222958, 0.19842379, 0.5450379, 0.58118435, 0.40838571, 0.55503269, 0.9424222, 0.38791064, 0.74781717, 0.37622127}; migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 2, 1, 0}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {5.060988893272207}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/softmaxcrossentropyloss_weighted.cpp000066400000000000000000000635771510465702400274700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_even_weighted_ones_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 1.0f, 1.0f, 1.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 1.38629436, 1.38629436, 1.38629436}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_uneven_weighted_ones_asym_batch) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_asym_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {3, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {3}}; std::vector label_data = {0, 3, 1}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0.69314718}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_uneven_weighted_ones_asym_class) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_asym_class_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 3}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 2, 1, 0}; migraphx::shape weight_shape{migraphx::shape::float_type, {3}}; std::vector weight_data = {1.0f, 0.5f, 2.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.09861229, 2.19722458, 0.54930614, 1.09861229}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_uneven_weighted_ones_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0.69314718, 2.77258872}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_uneven_weighted_ones_out_bounds_ignore_idx_test) { migraphx::program p = optimize_onnx( "softmaxcrossentropyloss_2d_no_reduction_weighted_out_bounds_ignore_idx_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0.69314718, 2.77258872}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE( softmaxcrossentropyloss_2d_no_reduction_uneven_weighted_ones_neg_out_bounds_ignore_idx_test) { migraphx::program p = optimize_onnx( "softmaxcrossentropyloss_2d_no_reduction_weighted_neg_out_bounds_ignore_idx_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0.69314718, 2.77258872}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_uneven_weighted_ignore_index_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_ignore_idx_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0.69314718, 0.0f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_uneven_weighted_ignore_index_iota_data_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_ignore_idx_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements()); std::iota(score_data.begin(), score_data.end(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {3.4401897f, 1.3205691f, 1.22009485f, 0.f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_weighted_iota_data_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements()); std::iota(score_data.begin(), score_data.end(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {8.861233040647768}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_weighted_iota_data_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements()); std::iota(score_data.begin(), score_data.end(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.3632666216381182}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_weighted_iota_zero_one_data_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements()); std::fill(score_data.begin(), score_data.begin() + 4, 0); std::fill(score_data.begin() + 4, score_data.begin() + 8, 1); std::iota(score_data.begin() + 8, score_data.end(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {1, 1, 1, 1}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.69314718f, 0.69314718f, 1.22009485f, 1.22009485f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_weighted_iota_zero_one_data_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements()); std::fill(score_data.begin(), score_data.begin() + 4, 0); std::fill(score_data.begin() + 4, score_data.begin() + 8, 1); std::iota(score_data.begin() + 8, score_data.end(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {1, 1, 1, 1}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {3.8264840596810856f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_weighted_iota_zero_one_data_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements()); std::fill(score_data.begin(), score_data.begin() + 4, 0); std::fill(score_data.begin() + 4, score_data.begin() + 8, 1); std::iota(score_data.begin() + 8, score_data.end(), 1); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {1, 1, 1, 1}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.9132420298405428f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_uneven_weighted_neg_ignore_index_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_idx_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0.69314718, 2.77259f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_index_and_label_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_idx_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, -2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0.69314718, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_index_and_label_test2) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_idx_test2.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, -1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436, 4.15888308, 0, 2.77259f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_neg_ignore_index_invalid_label_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_no_reduction_weighted_neg_ignore_idx_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, -5}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); // Should throw as even through ignore_idx out of bounds and negative label being out of bounds // is invalid EXPECT(test::throws([&] { std::ignore = p.eval(pp).back(); })); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_weighted_test_ones) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 1.0f, 1.0f, 1.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {5.545177}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_sum_reduction_weighted_test_zeroes) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_sum_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 0.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 1.0f, 1.0f, 1.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {5.545177}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_weighted_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 1.0f, 1.0f, 1.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(softmaxcrossentropyloss_2d_mean_reduction_uneven_weighted_test) { migraphx::program p = optimize_onnx("softmaxcrossentropyloss_2d_mean_reduction_weighted_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape score_shape{migraphx::shape::float_type, {4, 4}}; std::vector score_data(score_shape.elements(), 1.0f); migraphx::shape label_shape{migraphx::shape::int32_type, {4}}; std::vector label_data = {0, 3, 1, 2}; migraphx::shape weight_shape{migraphx::shape::float_type, {4}}; std::vector weight_data = {1.0f, 0.5f, 2.0f, 3.0f}; migraphx::parameter_map pp; pp["0"] = migraphx::argument(score_shape, score_data.data()); pp["1"] = migraphx::argument(label_shape, label_data.data()); pp["2"] = migraphx::argument(weight_shape, weight_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.38629436}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/softplus_test.cpp000066400000000000000000000037121510465702400234320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(softplus_test) { migraphx::program p = read_onnx("softplus_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {5}}; std::vector data = {0, 1, 2, 3, 4}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold(5); std::transform( data.begin(), data.end(), gold.begin(), [](auto x) { return std::log1p(std::exp(x)); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/softsign_test.cpp000066400000000000000000000037121510465702400234070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(softsign_test) { migraphx::program p = read_onnx("softsign_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape s{migraphx::shape::float_type, {5}}; std::vector data = {0, 1, 2, 3, 4}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s, data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold(5); std::transform( data.begin(), data.end(), gold.begin(), [](auto x) { return x / (1.0 + std::abs(x)); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/spacetodepth_simple_test.cpp000066400000000000000000000060751510465702400256140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(spacetodepth_simple_test) { auto p = read_onnx("spacetodepth_simple_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector data_in(48); std::iota(std::begin(data_in), std::end(data_in), 0); migraphx::shape s_x{migraphx::shape::float_type, {1, 2, 4, 6}}; migraphx::parameter_map pp; pp["x"] = migraphx::argument(s_x, data_in.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 2, 4, 12, 14, 16, 24, 26, 28, 36, 38, 40, 1, 3, 5, 13, 15, 17, 25, 27, 29, 37, 39, 41, 6, 8, 10, 18, 20, 22, 30, 32, 34, 42, 44, 46, 7, 9, 11, 19, 21, 23, 31, 33, 35, 43, 45, 47}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(spacetodepth_depthtospace_test) { // space to depth auto p1 = read_onnx("spacetodepth_simple_test.onnx"); p1.compile(migraphx::make_target("ref")); std::vector gold_data_in(48); std::iota(std::begin(gold_data_in), std::end(gold_data_in), 0); migraphx::shape s_x_1{migraphx::shape::float_type, {1, 2, 4, 6}}; migraphx::parameter_map pp1; pp1["x"] = migraphx::argument(s_x_1, gold_data_in.data()); auto result1 = p1.eval(pp1).back(); // depth to space auto p2 = read_onnx("depthtospace_simple_test.onnx"); p2.compile(migraphx::make_target("ref")); migraphx::parameter_map pp2; pp2["x"] = result1; auto result2 = p2.eval(pp2).back(); std::vector result_vector2; result2.visit([&](auto output) { result_vector2.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector2, gold_data_in)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/split_dyn_verify_test.cpp000066400000000000000000000111011510465702400251330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(split_dyn_input_fixed_split_axis_test) { migraphx::onnx_options options; options.default_dyn_dim_value = {10, 30}; auto p = read_onnx("split_dyn_input_fixed_split_axis_test.onnx", options); p.compile(migraphx::make_target("ref")); migraphx::shape data_shape{migraphx::shape::float_type, {10, 15}}; std::vector data(150, 1.23); migraphx::parameter_map pm; pm["x"] = migraphx::argument(data_shape, data.data()); auto results = p.eval(pm); std::vector result_vector; std::vector gold(50, 1.23); results.at(0).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); results.at(1).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); results.at(2).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(split_dyn_input_dyn_split_axis_test0) { migraphx::onnx_options options; options.default_dyn_dim_value = {10, 30}; auto p = read_onnx("split_dyn_input_dyn_split_axis_test.onnx", options); p.compile(migraphx::make_target("ref")); migraphx::shape data_shape{migraphx::shape::float_type, {12, 15}}; std::vector data(180, 1.23); migraphx::parameter_map pm; pm["x"] = migraphx::argument(data_shape, data.data()); auto results = p.eval(pm); std::vector result_vector; std::vector gold(60, 1.23); results.at(0).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); results.at(1).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); results.at(2).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } // different static shape that doesn't split evenly TEST_CASE(split_dyn_input_dyn_split_axis_test1) { migraphx::onnx_options options; options.default_dyn_dim_value = {10, 30}; auto p = read_onnx("split_dyn_input_dyn_split_axis_test.onnx", options); p.compile(migraphx::make_target("ref")); migraphx::shape data_shape{migraphx::shape::float_type, {20, 15}}; std::vector data(300, 1.23); migraphx::parameter_map pm; pm["x"] = migraphx::argument(data_shape, data.data()); auto results = p.eval(pm); std::vector result_vector; std::vector gold_1(105, 1.23); results.at(0).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold_1)); results.at(1).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold_1)); std::vector gold_2(90, 1.23); results.at(2).visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold_2)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/tril_batch_diff_k_test.cpp000066400000000000000000000032041510465702400251640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(tril_batch_diff_k_test) { migraphx::program p = read_onnx("tril_batch_diff_k_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {2, 2, 3}}, p); std::vector gold = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/tril_neg_k_test.cpp000066400000000000000000000031611510465702400236660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(tril_neg_k_test) { migraphx::program p = read_onnx("tril_neg_k_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {3, 4}}, p); std::vector gold = {0, 0, 0, 0, 5, 0, 0, 0, 9, 10, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/tril_out_k_test.cpp000066400000000000000000000031631510465702400237260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(tril_out_k_test) { migraphx::program p = read_onnx("tril_out_k_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {3, 4}}, p); std::vector gold = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/tril_row_one_test.cpp000066400000000000000000000031341510465702400242530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(tril_row_one_test) { migraphx::program p = read_onnx("tril_row_one_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {1, 4}}, p); std::vector gold = {1, 2, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/tril_test.cpp000066400000000000000000000031461510465702400225260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(tril_test) { migraphx::program p = read_onnx("tril_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {3, 4}}, p); std::vector gold = {1, 0, 0, 0, 5, 6, 0, 0, 9, 10, 11, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/triu_batch_diff_k_test.cpp000066400000000000000000000032011510465702400251720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(triu_batch_diff_k_test) { migraphx::program p = read_onnx("triu_batch_diff_k_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {2, 2, 3}}, p); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 0, 9, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/triu_neg_k_test.cpp000066400000000000000000000031631510465702400237010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(triu_neg_k_test) { migraphx::program p = read_onnx("triu_neg_k_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {3, 4}}, p); std::vector gold = {1, 2, 3, 4, 5, 6, 7, 8, 0, 10, 11, 12}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/triu_out_k_test.cpp000066400000000000000000000031201510465702400237300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(triu_out_k_test) { migraphx::program p = read_onnx("triu_out_k_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {3, 4}}, p); std::vector gold(12, 0); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/triu_row_one_test.cpp000066400000000000000000000031341510465702400242640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(triu_row_one_test) { migraphx::program p = read_onnx("triu_row_one_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {1, 4}}, p); std::vector gold = {0, 2, 3, 4}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/triu_test.cpp000066400000000000000000000031461510465702400225370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(triu_test) { migraphx::program p = read_onnx("triu_test.onnx"); std::vector result_vector = gen_trilu_test({migraphx::shape::float_type, {3, 4}}, p); std::vector gold = {1, 2, 3, 4, 0, 6, 7, 8, 0, 0, 11, 12}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/unique_dynamic_sorted_test.cpp000066400000000000000000000046521510465702400261510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(unique_dynamic_sorted_test) { migraphx::program p = read_onnx("unique_dynamic_sorted_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector x{2, 1, 1, 3, 4, 3}; std::vector y_gold = {1, 2, 3, 4}; std::vector y_idx_gold = {1, 0, 3, 4}; std::vector x_idx_gold = {1, 0, 0, 2, 3, 2}; std::vector y_ct_gold = {2, 1, 2, 1}; migraphx::shape s{migraphx::shape::float_type, {x.size()}}; migraphx::parameter_map pm; pm["X"] = migraphx::argument(s, x.data()); auto result = p.eval(pm); std::vector yvec; result[0].visit([&](auto out) { yvec.assign(out.begin(), out.end()); }); EXPECT(yvec == y_gold); std::vector y_idx_vec; result[1].visit([&](auto out) { y_idx_vec.assign(out.begin(), out.end()); }); EXPECT(y_idx_vec == y_idx_gold); std::vector x_idx_vec; result[2].visit([&](auto out) { x_idx_vec.assign(out.begin(), out.end()); }); EXPECT(x_idx_vec == x_idx_gold); std::vector y_ct_vec; result[3].visit([&](auto out) { y_ct_vec.assign(out.begin(), out.end()); }); EXPECT(y_ct_vec == y_ct_gold); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/unique_dynamic_unsorted_test.cpp000066400000000000000000000046561510465702400265200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(unique_dynamic_unsorted_test) { migraphx::program p = read_onnx("unique_dynamic_unsorted_test.onnx"); p.compile(migraphx::make_target("ref")); std::vector x{2, 1, 1, 3, 4, 3}; std::vector y_gold = {2, 1, 3, 4}; std::vector y_idx_gold = {0, 1, 3, 4}; std::vector x_idx_gold = {0, 1, 1, 2, 3, 2}; std::vector y_ct_gold = {1, 2, 2, 1}; migraphx::shape s{migraphx::shape::float_type, {x.size()}}; migraphx::parameter_map pm; pm["X"] = migraphx::argument(s, x.data()); auto result = p.eval(pm); std::vector yvec; result[0].visit([&](auto out) { yvec.assign(out.begin(), out.end()); }); EXPECT(yvec == y_gold); std::vector y_idx_vec; result[1].visit([&](auto out) { y_idx_vec.assign(out.begin(), out.end()); }); EXPECT(y_idx_vec == y_idx_gold); std::vector x_idx_vec; result[2].visit([&](auto out) { x_idx_vec.assign(out.begin(), out.end()); }); EXPECT(x_idx_vec == x_idx_gold); std::vector y_ct_vec; result[3].visit([&](auto out) { y_ct_vec.assign(out.begin(), out.end()); }); EXPECT(y_ct_vec == y_ct_gold); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/unpack_int4_qdq.cpp000066400000000000000000000055361510465702400236060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(unpack_int4_qdq_test) { migraphx::program p = read_onnx("int4_const_identity_qdq_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x2_shape{migraphx::shape::half_type, {4, 4}}; std::vector x2(16, migraphx::half(1.0)); migraphx::parameter_map pm; pm["x2"] = migraphx::argument{x2_shape, x2.data()}; auto result = p.eval(pm).back(); std::vector rv; result.visit([&](auto output) { rv.assign(output.begin(), output.end()); }); // MatMul output is 4x4: should be all ones: // Based on Identity matrix A supplied to AxB. (B aka 'x2' is all ones) std::vector gold(16, migraphx::half(1.0)); EXPECT(rv == gold); } TEST_CASE(unpack_int4_block_sz_2_qdq_test) { migraphx::program p = read_onnx("int4_const_identity_block_sz_2_qdq_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape x2_shape{migraphx::shape::half_type, {2, 4}}; std::vector x2(16, migraphx::half(1.0)); migraphx::parameter_map pm; pm["x2"] = migraphx::argument{x2_shape, x2.data()}; auto result = p.eval(pm).back(); std::vector rv; result.visit([&](auto output) { rv.assign(output.begin(), output.end()); }); // MatMul output is 4x4: its first half should be all zeros. Rest all ones: // Based on partial Identity matrix A supplied to AxB. (B aka 'x2' is all ones). std::vector gold(16); std::fill(gold.begin() + 8, gold.end(), migraphx::half(1.0)); EXPECT(rv == gold); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/upsample_test.cpp000066400000000000000000000036331510465702400234030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(upsample_test) { migraphx::program p = read_onnx("upsample_test.onnx"); std::vector x_data = {1, 2, 3, 4}; migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; migraphx::parameter_map pp; pp["X"] = migraphx::argument(sx, x_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/verify/where_test.cpp000066400000000000000000000053321510465702400226650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(where_test) { migraphx::program p = read_onnx("where_test.onnx"); p.compile(migraphx::make_target("ref")); migraphx::shape c_shape{migraphx::shape::bool_type, {2}}; std::vector c_data = {1, 0}; migraphx::shape x_shape{migraphx::shape::float_type, {2, 2, 2}}; std::vector x_data(8, 1.0f); migraphx::shape y_shape{migraphx::shape::float_type, {2, 1, 2, 2}}; std::vector y_data(8, 2.0f); migraphx::parameter_map pp; pp["c"] = migraphx::argument(c_shape, c_data.data()); pp["x"] = migraphx::argument(x_shape, x_data.data()); pp["y"] = migraphx::argument(y_shape, y_data.data()); auto result = p.eval(pp).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.0f, 2.0f, 1.0f, 2.0f, 1.0f, 2.0f, 1.0f, 2.0f, 1.0f, 2.0f, 1.0f, 2.0f, 1.0f, 2.0f, 1.0f, 2.0f}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/onnx/where_dyn_test.onnx000066400000000000000000000002321510465702400224250ustar00rootroot00000000000000where_dyn_test:  c x yz"Wherewhere_dyn_testZ c    Z x   Z y   b z   BROCm-AMDMIGraphX-46524e8/test/onnx/where_mixed_test.onnx000066400000000000000000000002401510465702400227400ustar00rootroot00000000000000where_mixed_test:…  c x yz"Wherewhere_mixed_testZ c    Z x   Z y    b z   BROCm-AMDMIGraphX-46524e8/test/onnx/where_test.onnx000066400000000000000000000002321510465702400215530ustar00rootroot00000000000000 where_test:…  c x yz"Where where_testZ c   Z x    Z y     b z     B ROCm-AMDMIGraphX-46524e8/test/op_shape_test.cpp000066400000000000000000006405261510465702400210750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include "test.hpp" template static void expect_shape(const migraphx::shape& expected, const migraphx::operation& op, Ts... xs) { migraphx::program p; auto* mm = p.get_main_module(); std::vector shapes{xs...}; std::vector args(shapes.size()); std::transform( shapes.begin(), shapes.end(), args.begin(), [&](auto&& s) { return mm->add_outline(s); }); mm->add_instruction(op, args); if(p.get_output_shapes().back() != expected) { std::cout << "FAILED: Incorrect shape for " << op << ": "; std::cout << expected << " != " << p.get_output_shapes().back() << std::endl; for(auto&& s : shapes) std::cout << " " << s << std::endl; } } template static void throws_shape(const migraphx::operation& op, Ts... xs) { migraphx::program p; auto* mm = p.get_main_module(); std::vector shapes{xs...}; std::vector args(shapes.size()); std::transform( shapes.begin(), shapes.end(), args.begin(), [&](auto&& s) { return mm->add_outline(s); }); bool thrown = test::throws([&] { mm->add_instruction(op, args); }); if(not thrown) { std::cout << "FAILED: No error found for " << op.name() << ": "; for(auto&& s : shapes) std::cout << " " << s << std::endl; } } template struct always_false : std::false_type { }; template [[maybe_unused]] static void throws_shape(const migraphx::shape&, Ts...) { static_assert(always_false{}, "An expected shape should not be passed to throws_shape function"); } TEST_CASE(allocate_static) { migraphx::shape out_shape{migraphx::shape::float_type, {2, 3, 4}}; expect_shape(out_shape, migraphx::make_op("allocate", {{"shape", to_value(out_shape)}})); } TEST_CASE(allocate_static_input) { migraphx::shape input{migraphx::shape::int64_type, {3}}; migraphx::shape out_shape{migraphx::shape::float_type, {2, 3, 4}}; expect_shape(out_shape, migraphx::make_op("allocate", {{"shape", to_value(out_shape)}}), input); } TEST_CASE(allocate_dyn) { migraphx::shape input{migraphx::shape::int64_type, {2}}; auto max_val = std::numeric_limits::max(); std::vector dyn_dims( 2, migraphx::shape::dynamic_dimension{0, max_val}); expect_shape(migraphx::shape{migraphx::shape::float_type, dyn_dims}, migraphx::make_op("allocate", {{"buf_type", migraphx::shape::float_type}}), input); } TEST_CASE(allocate_dyn_with_shape_attr) { migraphx::shape input{migraphx::shape::int64_type, {4}}; migraphx::shape shape_attr{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 8, {4, 6}}, {4, 8}, {4, 6}}}; expect_shape(shape_attr, migraphx::make_op("allocate", {{"shape", migraphx::to_value(shape_attr)}}), input); } TEST_CASE(allocate_dyn_no_input) { migraphx::shape shape_attr{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 8, {4, 6}}, {4, 8}, {4, 6}}}; expect_shape(shape_attr, migraphx::make_op("allocate", {{"shape", migraphx::to_value(shape_attr)}})); } TEST_CASE(allocate_shape_and_buf_type_error) { migraphx::shape shape_attr{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 8, {4, 6}}, {4, 8}, {4, 6}}}; throws_shape(migraphx::make_op( "allocate", {{"shape", migraphx::to_value(shape_attr)}, {"buf_type", migraphx::shape::half_type}})); } TEST_CASE(allocate_no_attr_error) { migraphx::shape input{migraphx::shape::int64_type, {4}}; throws_shape(migraphx::make_op("allocate"), input); } TEST_CASE(argmax_axis0) { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {1, 3, 4, 5}}, migraphx::make_op("argmax", {{"axis", 0}}), input); } TEST_CASE(argmax_axis1) { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 1, 4, 5}}, migraphx::make_op("argmax", {{"axis", 1}}), input); } TEST_CASE(argmax_axis2) { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 3, 1, 5}}, migraphx::make_op("argmax", {{"axis", 2}}), input); } TEST_CASE(argmax_axis_neg) { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 3, 4, 1}}, migraphx::make_op("argmax", {{"axis", -1}}), input); } TEST_CASE(argmax_axis_outofbounds) { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; throws_shape(migraphx::make_op("argmax", {{"axis", 4}}), input); } TEST_CASE(argmax_dyn0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}, {5, 5}}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {{1, 4}, {1, 1}, {4, 4}, {5, 5}}}, migraphx::make_op("argmax", {{"axis", 1}}), input); } TEST_CASE(argmax_dyn1) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 6}, {4, 6}}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {{1, 4}, {3, 3}, {1, 1}, {4, 6}}}, migraphx::make_op("argmax", {{"axis", 2}}), input); } TEST_CASE(binary_dyn_static_error) { migraphx::shape a_shape{migraphx::shape::float_type, {1, 4, 4}}; std::vector b{{1, 1}, {4, 4, {4}}, {4, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; throws_shape(migraphx::make_op("add"), a_shape, b_shape); } TEST_CASE(bit_cast_typesize_mismatch) { migraphx::shape a_shape{migraphx::shape::int8_type, {1, 4, 4}}; throws_shape(migraphx::make_op("bit_cast", {{"target_type", migraphx::shape::int32_type}}), a_shape); } TEST_CASE(bit_cast_dyn) { migraphx::shape a_shape{migraphx::shape::int8_type, {{1, 1}, {4, 8}, {4, 8}}}; expect_shape(migraphx::shape{migraphx::shape::uint8_type, {{1, 1}, {4, 8}, {4, 8}}}, migraphx::make_op("bit_cast", {{"target_type", migraphx::shape::uint8_type}}), a_shape); } TEST_CASE(bitwise_and_not_integral_error) { migraphx::shape a_shape{migraphx::shape::float_type, {1, 4, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, {1, 4, 4}}; throws_shape(migraphx::make_op("bitwise_and"), a_shape, b_shape); } TEST_CASE(broadcast) { { std::vector lens{1, 1}; migraphx::shape input{migraphx::shape::float_type, {1}, {0}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 1}, {0, 0}}, migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", lens}}), input); } { std::vector lens{1, 1}; migraphx::shape input{migraphx::shape::float_type, {2}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", lens}}), input); } { std::vector lens{2, 2}; migraphx::shape input{migraphx::shape::float_type, {1, 2}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", lens}}), input); } { std::vector lens{3, 2, 4, 3}; migraphx::shape input{migraphx::shape::float_type, {4, 3}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {3, 2, 4, 3}, {0, 0, 3, 1}}, migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", lens}}), input); } { std::vector lens{3, 2, 4, 3}; migraphx::shape input{migraphx::shape::float_type, {4, 4}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", lens}}), input); } } TEST_CASE(broadcast_axis_out_of_range_error) { std::vector lens{1, 1}; migraphx::shape input{migraphx::shape::float_type, {1}, {0}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 4}, {"out_lens", lens}}), input); } TEST_CASE(broadcast_1in_dyn_error) { // broadcast doesn't support single dynamic shape input std::vector lens{3, 2, 4, 3}; migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {2, 2}}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", lens}}), input); } TEST_CASE(broadcast_2in_static_static) { migraphx::shape a_input{migraphx::shape::float_type, {4}, {1}}; migraphx::shape b_input{migraphx::shape::float_type, {4, 4}, {4, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {4, 4}, {1, 0}}, migraphx::make_op("broadcast", {{"axis", 0}}), a_input, b_input); expect_shape(migraphx::shape{migraphx::shape::float_type, {4, 4}, {0, 1}}, migraphx::make_op("broadcast", {{"axis", 1}}), a_input, b_input); throws_shape(migraphx::make_op("broadcast", {{"axis", 2}}), a_input, b_input); } TEST_CASE(broadcast_2in_not_matching_error) { migraphx::shape a_input{migraphx::shape::float_type, {4}, {1}}; migraphx::shape b_input{migraphx::shape::float_type, {2, 2}, {2, 1}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 1}}), a_input, b_input); } TEST_CASE(broadcast_2in_dynamic_s0_error1) { migraphx::shape a_input{migraphx::shape::float_type, {4, 2}, {2, 1}}; migraphx::shape b_input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {2, 2}}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 0}}), b_input, a_input); } TEST_CASE(broadcast_2in_dynamic_s0_error2) { std::vector dd{{4, 4}}; migraphx::shape a_input{migraphx::shape::float_type, dd}; migraphx::shape b_input{migraphx::shape::float_type, {4, 4}, {4, 1}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 0}}), a_input, b_input); } TEST_CASE(broadcast_2in_static_dyn) { migraphx::shape a_input{migraphx::shape::float_type, {4}, {1}}; migraphx::shape b_input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {2, 2}}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 0}}), a_input, b_input); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}, {2, 2}}}, migraphx::make_op("broadcast", {{"axis", 1}}), a_input, b_input); throws_shape(migraphx::make_op("broadcast", {{"axis", 2}}), a_input, b_input); } TEST_CASE(broadcast_2in_dyn_s0_ndim_greater_than_1_error) { migraphx::shape a_input{migraphx::shape::float_type, {4, 2}}; migraphx::shape b_input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {2, 2}}}; throws_shape(migraphx::make_op("broadcast", {{"axis", 0}}), a_input, b_input); } TEST_CASE(conv_2d_0) { migraphx::shape output{migraphx::shape::float_type, {4, 4, 1, 1}}; migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; expect_shape(output, migraphx::make_op("convolution"), input, weights); throws_shape(migraphx::make_op("convolution"), input); throws_shape( migraphx::make_op("convolution", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), input); } TEST_CASE(conv_2d_1) { migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape input2{migraphx::shape::float_type, {3, 3}}; migraphx::shape weights2{migraphx::shape::float_type, {3, 3}}; throws_shape(migraphx::make_op("convolution"), input2, weights2); throws_shape(migraphx::make_op("convolution"), input2, weights); } TEST_CASE(conv_1d) { migraphx::shape output_1d{migraphx::shape::float_type, {4, 4, 1}}; migraphx::shape input_1d{migraphx::shape::float_type, {4, 3, 3}}; migraphx::shape weights_1d{migraphx::shape::float_type, {4, 3, 3}}; expect_shape( output_1d, migraphx::make_op("convolution", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), input_1d, weights_1d); } TEST_CASE(conv_channel_mismatch) { migraphx::shape input_1d{migraphx::shape::float_type, {4, 3, 3}}; migraphx::shape weights_1d = {migraphx::shape::float_type, {4, 8, 3}}; throws_shape(migraphx::make_op("convolution"), input_1d, weights_1d); } TEST_CASE(conv_3_d) { migraphx::shape output_3d{migraphx::shape::float_type, {4, 4, 1, 1, 1}}; migraphx::shape input_3d{migraphx::shape::float_type, {4, 3, 3, 3, 3}}; migraphx::shape weights_3d{migraphx::shape::float_type, {4, 3, 3, 3, 3}}; expect_shape( output_3d, migraphx::make_op("convolution", {{"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"dilation", {1, 1, 1}}}), input_3d, weights_3d); throws_shape(migraphx::make_op("convolution"), input_3d, weights_3d); } TEST_CASE(conv_dyn_batch) { migraphx::shape input_dyn_shape{migraphx::shape::float_type, {{1, 100}, {3, 3}, {5, 5}, {5, 5}}}; migraphx::shape weights_shape{migraphx::shape::float_type, {1, 3, 3, 3}}; migraphx::shape output_dyn_shape{migraphx::shape::float_type, {{1, 100}, {1, 1}, {3, 3}, {3, 3}}}; expect_shape(output_dyn_shape, migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), input_dyn_shape, weights_shape); } TEST_CASE(conv_dyn_img) { migraphx::shape input_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {3, 3}, {5, 20}, {5, 20}}}; migraphx::shape weights_shape = {migraphx::shape::float_type, {1, 3, 3, 3}}; migraphx::shape output_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {1, 1}, {3, 18}, {3, 18}}}; expect_shape(output_dyn_shape, migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), input_dyn_shape, weights_shape); } TEST_CASE(conv_dyn_weights) { migraphx::shape input_dyn_shape = {migraphx::shape::float_type, {1, 3, 10, 10}}; migraphx::shape weights_shape = {migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 4}, {2, 4}}}; migraphx::shape output_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {1, 1}, {7, 9}, {7, 9}}}; expect_shape(output_dyn_shape, migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), input_dyn_shape, weights_shape); } TEST_CASE(conv_dyn_img_weights) { migraphx::shape input_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {3, 3}, {5, 20}, {5, 20}}}; migraphx::shape weights_shape = {migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 4}, {2, 4}}}; migraphx::shape output_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {1, 1}, {2, 19}, {2, 19}}}; expect_shape(output_dyn_shape, migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), input_dyn_shape, weights_shape); } TEST_CASE(conv_attr_shape_mismatch) { migraphx::shape input_dyn_shape = {migraphx::shape::float_type, {{1, 100}, {3, 3}, {5, 5}, {5, 5}, {5, 5}}}; migraphx::shape weights_shape = {migraphx::shape::float_type, {1, 3, 3, 3, 3}}; throws_shape(migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), input_dyn_shape, weights_shape); } TEST_CASE(conv_autopad_dyn_batch) { // auto_pad dynamic batch migraphx::shape input_dyn_shape = {migraphx::shape::float_type, {{1, 10}, {3, 3}, {5, 5}, {5, 5}}}; migraphx::shape weights_shape = {migraphx::shape::float_type, {1, 3, 3, 3}}; migraphx::shape output_dyn_shape = {migraphx::shape::float_type, {{1, 10}, {1, 1}, {5, 5}, {5, 5}}}; expect_shape(output_dyn_shape, migraphx::make_op("convolution", {{"stride", {1, 1}}, {"dilation", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::same_upper}}), input_dyn_shape, weights_shape); } TEST_CASE(conv_autopad_dyn_img) { // auto_pad dynamic img migraphx::shape input_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {3, 3}, {5, 10}, {5, 10}}}; migraphx::shape weights_shape = {migraphx::shape::float_type, {1, 3, 3, 3}}; migraphx::shape output_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {1, 1}, {5, 10}, {5, 10}}}; expect_shape(output_dyn_shape, migraphx::make_op("convolution", {{"stride", {1, 1}}, {"dilation", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::same_upper}}), input_dyn_shape, weights_shape); } TEST_CASE(conv_autopad_dyn_kernel) { migraphx::shape input_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {3, 3}, {10, 10}, {10, 10}}}; migraphx::shape weights_shape = {migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 4}, {2, 4}}}; migraphx::shape output_dyn_shape = {migraphx::shape::float_type, {{1, 1}, {1, 1}, {10, 10}, {10, 10}}}; expect_shape(output_dyn_shape, migraphx::make_op("convolution", {{"stride", {1, 1}}, {"dilation", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::same_lower}}), input_dyn_shape, weights_shape); } TEST_CASE(contiguous_shape) { migraphx::shape output{migraphx::shape::float_type, {2, 2}}; migraphx::shape input{migraphx::shape::float_type, {2, 2}, {1, 2}}; expect_shape(output, migraphx::make_op("contiguous"), input); throws_shape(migraphx::make_op("contiguous"), input, input); migraphx::shape single{migraphx::shape::float_type, {2}}; expect_shape(single, migraphx::make_op("contiguous"), single); } TEST_CASE(contiguous_dyn_shape) { migraphx::shape s0{migraphx::shape::float_type, {{1, 4}, {2, 2, {2}}}}; expect_shape(s0, migraphx::make_op("contiguous"), s0); } TEST_CASE(contiguous_shape_scalar) { migraphx::shape output{migraphx::shape::float_type, {1}}; migraphx::shape input{migraphx::shape::float_type}; expect_shape(output, migraphx::make_op("contiguous"), input); } TEST_CASE(contiguous_shape_singleton_dim) { migraphx::shape output{migraphx::shape::float_type, {5, 1, 8}, {8, 8, 1}}; migraphx::shape input{migraphx::shape::float_type, {5, 1, 8}, {8, 4, 1}}; expect_shape(output, migraphx::make_op("contiguous"), input); } TEST_CASE(convolution_backwards_1d) { migraphx::shape input_1d{migraphx::shape::float_type, {4, 4, 1}}; migraphx::shape weights_1d{migraphx::shape::float_type, {4, 3, 3}}; migraphx::shape output_1d{migraphx::shape::float_type, {4, 3, 3}}; expect_shape(output_1d, migraphx::make_op("convolution_backwards", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), input_1d, weights_1d); } TEST_CASE(convolution_backwards_2d) { migraphx::shape input{migraphx::shape::float_type, {4, 4, 1, 1}}; migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 3, 3}}; expect_shape(output, migraphx::make_op("convolution_backwards"), input, weights); throws_shape(migraphx::make_op("convolution_backwards"), input); throws_shape(migraphx::make_op("convolution_backwards", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), input); } TEST_CASE(convolution_backwards_1padding) { migraphx::shape input{migraphx::shape::float_type, {4, 4, 1, 1}}; migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 1, 1}}; expect_shape(output, migraphx::make_op("convolution_backwards", {{"padding", {1, 1}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), input, weights); } TEST_CASE(convolution_backwards_2stride) { migraphx::shape input{migraphx::shape::float_type, {4, 4, 4, 4}}; migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 9, 9}}; expect_shape(output, migraphx::make_op("convolution_backwards", {{"padding", {0, 0}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), input, weights); } TEST_CASE(convolution_backwards_2dilation) { migraphx::shape input{migraphx::shape::float_type, {4, 4, 4, 4}}; migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 8, 8}}; expect_shape(output, migraphx::make_op("convolution_backwards", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {2, 2}}}), input, weights); } TEST_CASE(convolution_backwards_2group) { migraphx::shape input{migraphx::shape::float_type, {4, 4, 4, 4}}; migraphx::shape weights{migraphx::shape::float_type, {4, 2, 4, 4}}; migraphx::shape output{migraphx::shape::float_type, {4, 4, 7, 7}}; expect_shape(output, migraphx::make_op( "convolution_backwards", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 2}}), input, weights); } TEST_CASE(convolution_backwards_3d) { migraphx::shape input_3d{migraphx::shape::float_type, {4, 4, 1, 1, 1}}; migraphx::shape output_3d{migraphx::shape::float_type, {4, 3, 3, 3, 3}}; migraphx::shape weights_3d{migraphx::shape::float_type, {4, 3, 3, 3, 3}}; expect_shape( output_3d, migraphx::make_op("convolution_backwards", {{"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"dilation", {1, 1, 1}}}), input_3d, weights_3d); } TEST_CASE(convolution_backwards_channel_mismatch) { migraphx::shape input{migraphx::shape::float_type, {4, 4, 1, 1}}; migraphx::shape weights{migraphx::shape::float_type, {3, 3, 3, 3}}; throws_shape(migraphx::make_op("convolution_backwards"), input, weights); } TEST_CASE(convolution_backwards_dyn_batch_2d) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {1, 1}, {1, 1}}}; migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {{1, 4}, {3, 3}, {3, 3}, {3, 3}}}; expect_shape(output, migraphx::make_op("convolution_backwards"), input, weights); } TEST_CASE(convolution_backwards_dyn_batch_2dgroup) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {4, 4}, {4, 4}}}; migraphx::shape weights{migraphx::shape::float_type, {4, 2, 4, 4}}; migraphx::shape output{migraphx::shape::float_type, {{1, 4}, {4, 4}, {7, 7}, {7, 7}}}; expect_shape( output, migraphx::make_op("convolution_backwards", {{"group", 2}}), input, weights); } TEST_CASE(convolution_backwards_dyn_img_2d) { migraphx::shape input{migraphx::shape::float_type, {{1, 1}, {4, 4}, {1, 5}, {1, 5}}}; migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {{1, 1}, {3, 3}, {3, 7}, {3, 7}}}; expect_shape(output, migraphx::make_op("convolution_backwards"), input, weights); } TEST_CASE(convolution_backwards_dyn_kernel_2d) { migraphx::shape input{migraphx::shape::float_type, {1, 4, 1, 1}}; migraphx::shape weights{migraphx::shape::float_type, {{4, 4}, {3, 3}, {2, 6}, {2, 6}}}; migraphx::shape output{migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 6}, {2, 6}}}; expect_shape(output, migraphx::make_op("convolution_backwards"), input, weights); } TEST_CASE(dimensions_of0) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 2, 1}}; migraphx::shape output{migraphx::shape::int64_type, {4}}; expect_shape(output, migraphx::make_op("dimensions_of", {{"end", 4}}), input); } TEST_CASE(dimensions_of1) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 2, 1}}; migraphx::shape output{migraphx::shape::int64_type, {2}}; expect_shape(output, migraphx::make_op("dimensions_of", {{"start", 1}, {"end", 3}}), input); } TEST_CASE(dimensions_of2) { migraphx::shape input{migraphx::shape::float_type, {{1, 4, {2}}, {2, 4}, {2, 4}, {1, 6, {2}}}}; migraphx::shape output{migraphx::shape::int64_type, {2}}; expect_shape(output, migraphx::make_op("dimensions_of", {{"start", 1}, {"end", 3}}), input); } TEST_CASE(dimensions_of_error0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4, {2}}, {2, 4}}}; throws_shape(migraphx::make_op("dimensions_of", {{"start", 3}, {"end", 3}}), input); } TEST_CASE(dimensions_of_error1) { migraphx::shape input{migraphx::shape::float_type, {{1, 4, {2}}, {2, 4}}}; throws_shape(migraphx::make_op("dimensions_of", {{"start", 3}, {"end", 0}}), input); } TEST_CASE(dot_ndim_error0) { migraphx::shape s_m1{migraphx::shape::float_type, {5}}; migraphx::shape s_m2{migraphx::shape::float_type, {5}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_ndim_error1) { migraphx::shape s_m1{migraphx::shape::float_type, {5}}; migraphx::shape s_m2{migraphx::shape::float_type, {5, 2}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_ndim_error2) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {5}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_ndim_error3) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {6, 5, 4}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_ndim_error4) { migraphx::shape s_m1{migraphx::shape::float_type, {4, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {1, 1, 5, 7}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_mismatch_inner_error0) { migraphx::shape s_m1{migraphx::shape::float_type, {4, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {10, 8}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_mismatch_inner_error1) { migraphx::shape s_m1{migraphx::shape::float_type, {4, 6}}; migraphx::shape s_m2{migraphx::shape::float_type, {5, 8}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_mismatch_inner_error2) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {4, 4}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_mismatch_inner_error3) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 1, 4, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {1, 2, 5, 7}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_mismatch_outer_error) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 4, 6}}; migraphx::shape s_m2{migraphx::shape::float_type, {2, 5, 8}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_2_d_test0) { migraphx::shape s_m1{migraphx::shape::float_type, {4, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {5, 8}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {4, 8}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_2_d_test1) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {5, 4}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {1, 4}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_2_d_test2) { migraphx::shape s_m1{migraphx::shape::float_type, {4, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {5, 8}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {4, 8}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_2_d_test3) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 1}}; migraphx::shape s_m2{migraphx::shape::float_type, {1, 1}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {1, 1}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_3_d_test0) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 4, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {1, 5, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 4, 8}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_3_d_test_1) { migraphx::shape s_m1{migraphx::shape::float_type, {6, 1, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {6, 5, 4}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {6, 1, 4}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_3_d_test2) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 4, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {1, 5, 7}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 4, 7}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_4_d_test) { migraphx::shape s_m1{migraphx::shape::float_type, {1, 6, 1, 5}}; migraphx::shape s_m2{migraphx::shape::float_type, {1, 6, 5, 4}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 6, 1, 4}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_static_test0) { migraphx::shape s_m1{migraphx::shape::float_type, {{1, 4}, {5, 5}}}; migraphx::shape s_m2{migraphx::shape::float_type, {5, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {8, 8}}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_static_test1) { migraphx::shape s_m1{migraphx::shape::float_type, {{3, 3}, {5, 5}, {5, 5}}}; migraphx::shape s_m2{migraphx::shape::float_type, {3, 5, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {5, 5}, {8, 8}}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_static_test2) { migraphx::shape s_m1{migraphx::shape::float_type, {{1, 4}, {3, 3}, {5, 5}, {5, 5}}}; migraphx::shape s_m2{migraphx::shape::float_type, {2, 3, 5, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{2, 2}, {3, 3}, {5, 5}, {8, 8}}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_test0) { migraphx::shape s_m1{migraphx::shape::float_type, {{1, 4}, {5, 5}}}; migraphx::shape s_m2{migraphx::shape::float_type, {{5, 5}, {6, 8, {8}}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {6, 8, {8}}}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_test1) { migraphx::shape s_m1{migraphx::shape::float_type, {{1, 4}, {4, 5, {5}}}}; migraphx::shape s_m2{migraphx::shape::float_type, {{4, 5, {5}}, {6, 8, {8}}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {6, 8, {8}}}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_test2) { migraphx::shape s_m1{migraphx::shape::float_type, {{1, 20}, {5, 5}, {5, 5}}}; migraphx::shape s_m2{migraphx::shape::float_type, {1, 5, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 1}, {5, 5}, {8, 8}}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_test3) { std::size_t max_val = std::numeric_limits::max(); migraphx::shape s_m1{migraphx::shape::float_type, {{4, 4}, {5, 5}, {0, max_val}}}; migraphx::shape s_m2{migraphx::shape::float_type, {4, 5, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{4, 4}, {5, 5}, {8, 8}}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_test4) { std::size_t max_val = std::numeric_limits::max(); migraphx::shape s_m1{migraphx::shape::float_type, {{0, max_val}, {5, 5}, {0, max_val}}}; migraphx::shape s_m2{migraphx::shape::float_type, {{4, 8}, {5, 5}, {8, 8}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{4, 8}, {5, 5}, {8, 8}}}, migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_inner_mismatch) { migraphx::shape s_m1{migraphx::shape::float_type, {{1, 4}, {5, 5}, {4, 8}}}; migraphx::shape s_m2{migraphx::shape::float_type, {{1, 4}, {10, 20}, {8, 8}}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(dot_dyn_test_outer_mismatch) { migraphx::shape s_m1{migraphx::shape::float_type, {{1, 4}, {1, 4}, {5, 5}}}; migraphx::shape s_m2{migraphx::shape::float_type, {{5, 8}, {5, 5}, {6, 8, {8}}}}; throws_shape(migraphx::make_op("dot"), s_m1, s_m2); } TEST_CASE(broadcast_for_dot_static) { migraphx::shape s0{migraphx::shape::float_type, {481, 356}}; migraphx::shape s1{migraphx::shape::float_type, {1, 4, 356, 254}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 4, 481, 356}, {0, 0, 356, 1}}, migraphx::make_op("broadcast_for_dot"), s0, s1); expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 4, 356, 254}}, migraphx::make_op("broadcast_for_dot"), s1, s0); } TEST_CASE(broadcast_for_dot_dyn0) { migraphx::shape s0{migraphx::shape::float_type, {{124, 282}, {254, 484}}}; migraphx::shape s1{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {4, 4}, {254, 484}, {356, 584}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {4, 4}, {124, 282}, {254, 484}}}, migraphx::make_op("broadcast_for_dot"), s0, s1); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {4, 4}, {254, 484}, {356, 584}}}, migraphx::make_op("broadcast_for_dot"), s1, s0); } TEST_CASE(broadcast_for_dot_dyn1) { std::size_t max_val = std::numeric_limits::max(); migraphx::shape s0{migraphx::shape::float_type, {{124, 282}, {0, max_val}}}; migraphx::shape s1{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {4, 4}, {254, 484}, {356, 584}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {4, 4}, {124, 282}, {0, max_val}}}, migraphx::make_op("broadcast_for_dot"), s0, s1); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {4, 4}, {254, 484}, {356, 584}}}, migraphx::make_op("broadcast_for_dot"), s1, s0); } TEST_CASE(broadcast_for_dot_dyn2) { migraphx::shape s0{migraphx::shape::float_type, {{6, 12}, {4, 4}, {8, 8}}}; migraphx::shape s1{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {2, 10}, {8, 8}, {4, 4}}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {6, 10}, {4, 4}, {8, 8}}}, migraphx::make_op("broadcast_for_dot"), s0, s1); expect_shape( migraphx::shape{migraphx::shape::float_type, {{1, 4, {1, 2, 4}}, {6, 10}, {8, 8}, {4, 4}}}, migraphx::make_op("broadcast_for_dot"), s1, s0); } TEST_CASE(broadcast_with_dims0) { using migraphx::shape; shape s0{migraphx::shape::float_type, {2, 4}}; shape s1{migraphx::shape::int64_type, {4}}; std::size_t max_int = std::numeric_limits::max(); std::vector dyn_dims(4, shape::dynamic_dimension{0, max_int}); expect_shape( shape{shape::float_type, dyn_dims}, migraphx::make_op("broadcast_with_dims"), s0, s1); } TEST_CASE(broadcast_with_dims1) { using migraphx::shape; shape s0{migraphx::shape::int32_type, {1, 2, 4}}; shape s1{migraphx::shape::int64_type, {1}}; std::size_t max_int = std::numeric_limits::max(); std::vector dyn_dims(3, shape::dynamic_dimension{0, max_int}); expect_shape(shape{migraphx::shape::int32_type, dyn_dims}, migraphx::make_op("broadcast_with_dims"), s0, s1); } TEST_CASE(broadcast_with_dims2) { using migraphx::shape; shape s0{migraphx::shape::float_type, {{1, 4}, {2, 2}, {4, 4}}}; shape s1{migraphx::shape::int64_type, {4}}; std::size_t max_int = std::numeric_limits::max(); std::vector dyn_dims(4, shape::dynamic_dimension{0, max_int}); expect_shape(shape{migraphx::shape::float_type, dyn_dims}, migraphx::make_op("broadcast_with_dims"), s0, s1); } TEST_CASE(fixed_pad) { using migraphx::shape; shape input{migraphx::shape::float_type, {{2, 4, {}}, {3, 3}}}; shape input_static{migraphx::shape::float_type, {2, 3}}; shape output{migraphx::shape::float_type, {4, 3}}; expect_shape(output, migraphx::make_op("fixed_pad"), input); expect_shape(input_static, migraphx::make_op("fixed_pad"), input_static); // effectively no-op } TEST_CASE(flatten_shape) { migraphx::shape input{migraphx::shape::float_type, {2, 4, 6, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 2 * 4 * 6 * 8}}, migraphx::make_op("flatten", {{"axis", 0}}), input); expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 2 * 4 * 6 * 8}}, migraphx::make_op("flatten", {{"axis", -4}}), input); expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 4 * 6 * 8}}, migraphx::make_op("flatten", {{"axis", 1}}), input); expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 4 * 6 * 8}}, migraphx::make_op("flatten", {{"axis", -3}}), input); expect_shape(migraphx::shape{migraphx::shape::float_type, {2 * 4, 6 * 8}}, migraphx::make_op("flatten", {{"axis", 2}}), input); expect_shape(migraphx::shape{migraphx::shape::float_type, {2 * 4 * 6, 8}}, migraphx::make_op("flatten", {{"axis", 3}}), input); expect_shape(migraphx::shape{migraphx::shape::float_type, {2 * 4 * 6 * 8, 1}}, migraphx::make_op("flatten", {{"axis", 4}}), input); throws_shape(migraphx::make_op("flatten", {{"axis", 5}}), input); throws_shape(migraphx::make_op("flatten", {{"axis", -5}}), input); } TEST_CASE(flatten_dyn_axis0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {6, 6}, {8, 8}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 1}, {192, 768}}}, migraphx::make_op("flatten", {{"axis", 0}}), input); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 1}, {192, 768}}}, migraphx::make_op("flatten", {{"axis", -4}}), input); } TEST_CASE(flatten_dyn_axis1) { migraphx::shape input{migraphx::shape::float_type, {{2, 2, {2}}, {4, 4}, {4, 6, {5}}, {4, 6, {5}}}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {{2, 2, {2}}, {4 * 4 * 4, 4 * 6 * 6}}}, migraphx::make_op("flatten", {{"axis", 1}}), input); expect_shape( migraphx::shape{migraphx::shape::float_type, {{2, 2, {2}}, {4 * 4 * 4, 4 * 6 * 6}}}, migraphx::make_op("flatten", {{"axis", -3}}), input); } TEST_CASE(flatten_dyn_axis2) { migraphx::shape input{migraphx::shape::float_type, {{2, 2, {2}}, {4, 4}, {4, 6, {5}}, {4, 6, {5}}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{2 * 4, 2 * 4}, {4 * 4, 6 * 6}}}, migraphx::make_op("flatten", {{"axis", 2}}), input); } TEST_CASE(flatten_dyn_axis3) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {6, 6}, {8, 8}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1 * 4 * 6, 4 * 4 * 6}, {8, 8}}}, migraphx::make_op("flatten", {{"axis", 3}}), input); } TEST_CASE(flatten_dyn_axis4) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {6, 6}, {8, 8}}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {{1 * 4 * 6 * 8, 4 * 4 * 6 * 8}, {1, 1}}}, migraphx::make_op("flatten", {{"axis", 4}}), input); } TEST_CASE(fill_static_int) { migraphx::shape default_value{migraphx::shape::int64_type, {1}, {0}}; migraphx::shape data{migraphx::shape::int64_type, {3, 4, 4}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {3, 4, 4}}, migraphx::make_op("fill"), default_value, data); } TEST_CASE(fill_static_float) { migraphx::shape default_value{migraphx::shape::float_type, {1}, {0}}; migraphx::shape data{migraphx::shape::float_type, {4, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {4, 8}}, migraphx::make_op("fill"), default_value, data); } TEST_CASE(fill_dyn_int) { migraphx::shape default_value{migraphx::shape::int64_type, {1}, {0}}; migraphx::shape data{migraphx::shape::int64_type, {{1, 4}, {4, 8, {4, 6, 8}}, {4, 8, {4, 6, 8}}}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {{1, 4}, {4, 8, {4, 6, 8}}, {4, 8, {4, 6, 8}}}}, migraphx::make_op("fill"), default_value, data); } TEST_CASE(fill_dyn_float) { migraphx::shape default_value{migraphx::shape::float_type, {1}, {0}}; migraphx::shape data{migraphx::shape::float_type, {{1, 4}, {4, 8, {4, 6, 8}}, {4, 8, {4, 6, 8}}}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 8, {4, 6, 8}}, {4, 8, {4, 6, 8}}}}, migraphx::make_op("fill"), default_value, data); } TEST_CASE(gather) { { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; migraphx::shape indices{migraphx::shape::int32_type, {2, 3}}; int axis = 1; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 2, 3, 4, 5}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; migraphx::shape indices{migraphx::shape::int32_type, {2, 3}}; int axis = -4; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 3, 3, 4, 5}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; migraphx::shape indices{migraphx::shape::int32_type, {1}}; int axis = -4; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 3, 4, 5}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; migraphx::shape indices{migraphx::shape::int32_type}; int axis = -4; expect_shape(migraphx::shape{migraphx::shape::float_type, {3, 4, 5}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; migraphx::shape indices{migraphx::shape::int32_type}; int axis = 3; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 3, 4}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } { migraphx::shape input{migraphx::shape::float_type, {3}}; migraphx::shape indices{migraphx::shape::int32_type}; int axis = 0; expect_shape(migraphx::shape{migraphx::shape::float_type}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } { migraphx::shape input{migraphx::shape::float_type, {3}}; migraphx::shape indices{migraphx::shape::int32_type, {1}}; int axis = 0; expect_shape(migraphx::shape{migraphx::shape::float_type, {1}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; migraphx::shape indices{migraphx::shape::int32_type, {2, 3}}; int axis = 4; throws_shape(migraphx::make_op("gather", {{"axis", axis}}), input, indices); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; migraphx::shape indices{migraphx::shape::int32_type, {2, 3}}; int axis = -5; throws_shape(migraphx::make_op("gather", {{"axis", axis}}), input, indices); } } TEST_CASE(gather_dyn0) { // Insert dynamic index into dynamic shape migraphx::shape input{migraphx::shape::float_type, {{2, 3, {2}}, {3, 4, {3}}, {6, 9, {7}}, {12, 14, {13}}}}; migraphx::shape indices{migraphx::shape::int32_type, {{2, 7, {3}}, {3, 3}}}; int axis = 1; expect_shape(migraphx::shape{migraphx::shape::float_type, {{2, 3, {2}}, {2, 7, {3}}, {3, 3}, {6, 9, {7}}, {12, 14, {13}}}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } TEST_CASE(gather_dyn1) { // Insert static index into dynamic shape migraphx::shape input{migraphx::shape::float_type, {{2, 3, {2}}, {3, 4, {3}}, {6, 9, {7}}, {12, 14, {13}}}}; migraphx::shape indices{migraphx::shape::int32_type, {2, 3}}; int axis = 1; expect_shape(migraphx::shape{migraphx::shape::float_type, {{2, 3, {2}}, {2, 2}, {3, 3}, {6, 9, {7}}, {12, 14, {13}}}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } TEST_CASE(gather_dyn2) { // Insert scalar (static) index into dynamic shape migraphx::shape input{migraphx::shape::float_type, {{2, 3, {2}}, {3, 4, {3}}, {6, 9, {7}}, {12, 14, {13}}}}; std::vector mins; std::vector maxes; std::vector> opts; migraphx::shape indices{migraphx::shape::int32_type, mins, maxes, opts}; int axis = 1; expect_shape( migraphx::shape{migraphx::shape::float_type, {{2, 3, {2}}, {6, 9, {7}}, {12, 14, {13}}}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } TEST_CASE(gather_dyn3) { // Insert dynamic index into static shape, axis 1 migraphx::shape input{migraphx::shape::float_type, {2, 3, 6, 12}}; migraphx::shape indices{migraphx::shape::int32_type, {{2, 3, {2}}, {3, 4, {3}}}}; int axis = 1; expect_shape(migraphx::shape{migraphx::shape::float_type, {{2, 2}, {2, 3, {2}}, {3, 4, {3}}, {6, 6}, {12, 12}}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } TEST_CASE(gather_dyn4) { // Insert dynamic index into static shape, axis 0 migraphx::shape input{migraphx::shape::float_type, {2, 3, 6, 12}}; migraphx::shape indices{migraphx::shape::int32_type, {{2, 3, {2}}, {3, 4, {3}}}}; int axis = 0; expect_shape(migraphx::shape{migraphx::shape::float_type, {{2, 3, {2}}, {3, 4, {3}}, {3, 3}, {6, 6}, {12, 12}}}, migraphx::make_op("gather", {{"axis", axis}}), input, indices); } TEST_CASE(get_tuple_elem_test) { migraphx::shape s0{migraphx::shape::bool_type, {1, 1}}; migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::int32_type, {5, 6}}; migraphx::shape s_tuple({s0, s1, s2}); expect_shape(s0, migraphx::make_op("get_tuple_elem", {{"index", 0}}), s_tuple); expect_shape(s1, migraphx::make_op("get_tuple_elem", {{"index", 1}}), s_tuple); expect_shape(s2, migraphx::make_op("get_tuple_elem", {{"index", 2}}), s_tuple); throws_shape(migraphx::make_op("get_tuple_elem", {{"index", 3}}), s_tuple); throws_shape(migraphx::make_op("get_tuple_elem", {{"index", 0}}), s0); throws_shape(migraphx::make_op("get_tuple_elem", {{"index", 1}}), s1); throws_shape(migraphx::make_op("get_tuple_elem", {{"index", 0}}), s2); } TEST_CASE(group_op) { { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1, 2}}; std::vector l0_data{0, 1}; auto l0 = mm->add_literal(migraphx::literal{s0, l0_data}); migraphx::module m1; std::unordered_map map_mm_to_m1; m1.add_params({l0}, &map_mm_to_m1); auto add = m1.add_instruction(migraphx::make_op("add"), map_mm_to_m1.at(l0), map_mm_to_m1.at(l0)); m1.add_return({add}); migraphx::module_ref m1_ref = p.create_module("m_add", std::move(m1)); auto group = mm->add_instruction(migraphx::make_op("group", {{"tag", "add"}}), {l0}, {m1_ref}); EXPECT(group->get_shape() == s0); } { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1, 2}}; std::vector l0_data{0, 1}; auto l0 = mm->add_literal(migraphx::literal{s0, l0_data}); migraphx::module m1; std::unordered_map map_mm_to_m1; m1.add_params({l0}, &map_mm_to_m1); auto add = m1.add_instruction(migraphx::make_op("add"), map_mm_to_m1.at(l0), map_mm_to_m1.at(l0)); auto ret = m1.add_return({add, add}); migraphx::module_ref m1_ref = p.create_module("m_add", std::move(m1)); auto group = mm->add_instruction(migraphx::make_op("group", {{"tag", "add"}}), {l0}, {m1_ref}); auto out_shapes = ret->get_shape(); EXPECT(group->get_shape() == out_shapes); } { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1, 2}}; std::vector l0_data{0, 1}; auto l0 = mm->add_literal(migraphx::literal{s0, l0_data}); EXPECT(test::throws( [&] { mm->add_instruction(migraphx::make_op("group", {{"tag", "add"}}), {l0}, {}); })); } } TEST_CASE(gru) { { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; throws_shape( migraphx::make_op( "gru", {{"hidden_size", hidden_size + 1}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; throws_shape( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; throws_shape( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } } TEST_CASE(inconsistent_attr_shape) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape weights{migraphx::shape::float_type, {4, 3, 3, 3}}; throws_shape(migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2}}, {"dilation", {3, 3, 3}}}), input, weights); throws_shape(migraphx::make_op("convolution_backwards", {{"padding", {1, 1}}, {"stride", {2}}, {"dilation", {3, 3, 3}}}), input, weights); throws_shape(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {1}}, {"stride", {0}}, {"lengths", {1, 1}}}), input); } static void test_softmax_variations(const std::string& name) { { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}, migraphx::make_op(name, {{"axis", 0}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}, migraphx::make_op(name, {{"axis", 1}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}, migraphx::make_op(name, {{"axis", 2}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 5}}, migraphx::make_op(name, {{"axis", 3}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; int axis = 4; throws_shape(migraphx::make_op(name, {{"axis", axis}}), input); } } TEST_CASE(logsoftmax) { test_softmax_variations("logsoftmax"); } TEST_CASE(softmax) { test_softmax_variations("softmax"); } TEST_CASE(lstm) { { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; throws_shape( migraphx::make_op( "lstm", {{"hidden_size", hidden_size + 1}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; throws_shape( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; throws_shape( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } } TEST_CASE(multibroadcast) { { std::vector lens{4, 2, 5, 3}; migraphx::shape input{migraphx::shape::float_type, {2, 1, 3}}; expect_shape(migraphx::shape{migraphx::shape::float_type, lens, {0, 3, 0, 1}}, migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{4, 2, 5, 3}; migraphx::shape input{migraphx::shape::float_type, {2, 1, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, lens, {0, 1, 0, 0}}, migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{4, 2, 5, 3}; migraphx::shape input{migraphx::shape::float_type, {5, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, lens, {0, 0, 1, 0}}, migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{4, 2, 5, 3}; migraphx::shape input{migraphx::shape::float_type, {4, 1, 1, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, lens, {1, 0, 0, 0}}, migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{4, 2, 5, 3}; migraphx::shape input{migraphx::shape::float_type, {3}}; expect_shape(migraphx::shape{migraphx::shape::float_type, lens, {0, 0, 0, 1}}, migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{4, 4, 1, 3}; migraphx::shape input{migraphx::shape::float_type, {4, 1, 3}}; expect_shape(migraphx::shape{migraphx::shape::float_type, lens, {0, 3, 3, 1}}, migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{4, 1, 1, 3}; migraphx::shape input{migraphx::shape::float_type, {4, 1, 1, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, lens, {1, 1, 1, 0}}, migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{4, 1, 3}; migraphx::shape input{migraphx::shape::float_type, {4, 1, 1, 1}}; throws_shape(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{4, 1, 3}; std::vector empt = {}; migraphx::shape input{migraphx::shape::float_type, empt}; throws_shape(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{2, 3, 4, 5}; migraphx::shape input{migraphx::shape::float_type, {3, 4}}; throws_shape(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } { std::vector lens{2, 3, 4, 5}; migraphx::shape input{migraphx::shape::float_type, {2, 3, 4}}; throws_shape(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } } TEST_CASE(multibroadcast_1in_dyn_error_0) { // multibroadcast doesn't support single dynamic shape input std::vector lens{4, 4, 1, 3}; migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {4, 4}}}; throws_shape(migraphx::make_op("multibroadcast", {{"out_lens", lens}}), input); } TEST_CASE(multibroadcast_2in_static_dyn0) { migraphx::shape a_shape{migraphx::shape::float_type, {4, 4}}; std::vector b{{1, 4}, {4, 4, {4}}, {4, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}, {4, 4}}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}, {4, 4}}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_dyn1) { migraphx::shape a_shape{migraphx::shape::float_type, {1, 6}}; std::vector b{{8, 8}, {6, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{8, 8}, {6, 6}}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{8, 8}, {6, 6}}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_dyn2) { migraphx::shape a_shape{migraphx::shape::float_type, {1, 6}}; std::vector b{{8, 8}, {6, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{8, 8}, {6, 6}}}, migraphx::make_op("multibroadcast", {{"out_dyn_dims", migraphx::to_value(b)}}), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{8, 8}, {6, 6}}}, migraphx::make_op("multibroadcast", {{"out_dyn_dims", migraphx::to_value(b)}}), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_dyn_intersection0) { // dynamic_dimension.intersection for first dimension migraphx::shape a_shape{migraphx::shape::float_type, {3, 6}}; std::vector b{{1, 3}, {6, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {6, 6}}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {6, 6}}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_dyn_intersection1) { std::vector a_dds{{5, 10}, {1, 6}}; migraphx::shape a_shape{migraphx::shape::float_type, a_dds}; std::vector b_dds{{3, 8}, {3, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, b_dds}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{5, 8}, {3, 6}}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{5, 8}, {3, 6}}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_dyn_intersection2) { migraphx::shape a_shape{migraphx::shape::float_type, {3, 6}}; auto max_val = std::numeric_limits::max(); std::vector b{{0, max_val}, {6, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {6, 6}}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {6, 6}}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_dyn_intersection_error) { // not compatible for first dimension migraphx::shape a_shape{migraphx::shape::float_type, {3, 6}}; std::vector b{{1, 2}, {6, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; throws_shape(migraphx::make_op("multibroadcast"), a_shape, b_shape); throws_shape(migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_dyn_dyn0) { std::vector a{{1, 4}, {2, 4, {2}}, {2, 4}}; migraphx::shape a_shape{migraphx::shape::float_type, a}; std::vector b{{2, 4, {2}}, {2, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 4, {2}}, {2, 4}}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 4, {2}}, {2, 4}}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_dyn_dyn1) { std::vector a{{1, 4}, {2, 4, {2}}, {2, 4}}; migraphx::shape a_shape{migraphx::shape::float_type, a}; std::vector b{{2, 4, {2}}, {2, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 4, {2}}, {2, 4}}}, migraphx::make_op("multibroadcast", {{"out_dyn_dims", migraphx::to_value(a)}}), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 4, {2}}, {2, 4}}}, migraphx::make_op("multibroadcast", {{"out_dyn_dims", migraphx::to_value(a)}}), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_dyn_dyn_within0) { // dynamic_dimension.within_range on second dimension of a std::vector a{{1, 4}, {2, 4, {2}}, {2, 4}}; migraphx::shape a_shape{migraphx::shape::float_type, a}; std::vector b{{2, 5, {2}}, {2, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 4}, {2, 4}}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 4}, {2, 4}}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_dyn_dyn_within1) { // dynamic_dimension.within_range on second dimension of a, different opt dim std::vector a{{1, 4}, {2, 4, {2}}, {2, 4}}; migraphx::shape a_shape{migraphx::shape::float_type, a}; std::vector b{{2, 4, {3}}, {2, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 4}, {2, 4}}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 4}, {2, 4}}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_static0) { migraphx::shape a_shape{migraphx::shape::float_type, {3, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, {3, 6}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {3, 6}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {3, 6}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_static1) { migraphx::shape a_shape{migraphx::shape::float_type, {1, 8}}; migraphx::shape b_shape{migraphx::shape::float_type, {4, 8}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {4, 8}, {0, 1}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {4, 8}, {8, 1}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_static2) { migraphx::shape a_shape{migraphx::shape::float_type, {8}}; migraphx::shape b_shape{migraphx::shape::float_type, {4, 4, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {4, 4, 8}, {0, 0, 1}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {4, 4, 8}, {4, 1, 0}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_static3) { migraphx::shape a_shape{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, {4, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {3, 4, 4}, {16, 4, 1}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {3, 4, 4}, {0, 1, 0}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_static4) { migraphx::shape a_shape{migraphx::shape::float_type, {3, 1, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, {4, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {3, 4, 4}, {4, 0, 1}}, migraphx::make_op("multibroadcast"), a_shape, b_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {3, 4, 4}, {0, 1, 0}}, migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_2in_static_static_error0) { migraphx::shape a_shape{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, {4, 3}}; throws_shape(migraphx::make_op("multibroadcast"), a_shape, b_shape); throws_shape(migraphx::make_op("multibroadcast"), b_shape, a_shape); } TEST_CASE(multibroadcast_3in_static) { migraphx::shape a_shape{migraphx::shape::float_type, {3, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, {1, 2, 3, 6}}; migraphx::shape c_shape{migraphx::shape::float_type, {5, 1, 1, 1}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {5, 2, 3, 6}, {0, 0, 6, 1}}, migraphx::make_op("multibroadcast"), a_shape, b_shape, c_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {5, 2, 3, 6}, {0, 18, 6, 1}}, migraphx::make_op("multibroadcast"), b_shape, a_shape, c_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {5, 2, 3, 6}, {1, 0, 0, 0}}, migraphx::make_op("multibroadcast"), c_shape, a_shape, b_shape); } TEST_CASE(multibroadcast_4in_static) { migraphx::shape a_shape{migraphx::shape::float_type, {3, 6}}; migraphx::shape b_shape{migraphx::shape::float_type, {2, 3, 6}}; migraphx::shape c_shape{migraphx::shape::float_type, {5, 1, 1, 1}}; migraphx::shape d_shape{migraphx::shape::float_type, {6}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {5, 2, 3, 6}, {0, 0, 6, 1}}, migraphx::make_op("multibroadcast"), a_shape, b_shape, c_shape, d_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {5, 2, 3, 6}, {0, 18, 6, 1}}, migraphx::make_op("multibroadcast"), b_shape, a_shape, c_shape, d_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {5, 2, 3, 6}, {1, 0, 0, 0}}, migraphx::make_op("multibroadcast"), c_shape, a_shape, b_shape, d_shape); expect_shape(migraphx::shape{migraphx::shape::float_type, {5, 2, 3, 6}, {0, 0, 0, 1}}, migraphx::make_op("multibroadcast"), d_shape, a_shape, b_shape, c_shape); } TEST_CASE(multibroadcast_3in_dyn_static) { std::vector a{{1, 4}, {2, 4, {2}}, {2, 4}}; migraphx::shape a_shape{migraphx::shape::float_type, a}; std::vector b{{2, 4, {2}}, {2, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; migraphx::shape c_shape{migraphx::shape::float_type, {5, 1, 1, 1}}; migraphx::shape expected_shape{migraphx::shape::float_type, {{5, 5}, {1, 4}, {2, 4, {2}}, {2, 4}}}; expect_shape(expected_shape, migraphx::make_op("multibroadcast"), a_shape, b_shape, c_shape); expect_shape(expected_shape, migraphx::make_op("multibroadcast"), b_shape, a_shape, c_shape); expect_shape(expected_shape, migraphx::make_op("multibroadcast"), c_shape, a_shape, b_shape); } TEST_CASE(multibroadcast_3in_dyn_dyn) { std::vector a{{1, 4}, {2, 4, {2}}, {2, 4}}; migraphx::shape a_shape{migraphx::shape::float_type, a}; std::vector b{{2, 4, {2}}, {2, 4}}; migraphx::shape b_shape{migraphx::shape::float_type, b}; std::vector c{{1, 5, {1, 5}}, {1, 1}, {2, 4, {2}}, {2, 4}}; migraphx::shape c_shape{migraphx::shape::float_type, c}; migraphx::shape expected_shape{migraphx::shape::float_type, {{1, 5, {1, 5}}, {1, 4}, {2, 4, {2}}, {2, 4}}}; expect_shape(expected_shape, migraphx::make_op("multibroadcast"), a_shape, b_shape, c_shape); expect_shape(expected_shape, migraphx::make_op("multibroadcast"), b_shape, a_shape, c_shape); expect_shape(expected_shape, migraphx::make_op("multibroadcast"), c_shape, a_shape, b_shape); } TEST_CASE(multinomial_bool_type) { migraphx::shape s1{migraphx::shape::float_type, {1, 2}}; migraphx::shape s2{migraphx::shape::float_type, {3, 4}}; int dtype = 0; throws_shape(migraphx::make_op("multinomial", {{"dtype", dtype}}), s1, s2); } TEST_CASE(multinomial) { migraphx::shape s1{migraphx::shape::float_type, {1, 2}}; migraphx::shape s2{migraphx::shape::float_type, {3, 4}}; migraphx::shape s3{migraphx::shape::float_type, {1, 4}}; int dtype = 2; expect_shape(s3, migraphx::make_op("multinomial", {{"dtype", dtype}}), s1, s2); } TEST_CASE(multinomial_0size_input) { migraphx::shape s1{migraphx::shape::float_type, {1, 2}}; migraphx::shape s2{migraphx::shape::float_type, {}}; int dtype = 2; throws_shape(migraphx::make_op("multinomial", {{"dtype", dtype}}), s1, s2); } TEST_CASE(multinomial_dyn) { migraphx::shape s1{migraphx::shape::int32_type, {{2, 3}, {5, 6}}}; migraphx::shape s2{migraphx::shape::int32_type, {{7, 8}, {9, 10}}}; migraphx::shape s3{migraphx::shape::int32_type, {{2, 3}, {9, 10}}}; expect_shape( s3, migraphx::make_op("multinomial", {{"dtype", migraphx::shape::int32_type}}), s1, s2); } TEST_CASE(nms_shape) { // use_dyn_output == false migraphx::shape boxes_s{migraphx::shape::float_type, {1, 6, 4}}; migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; migraphx::shape max_out_s{migraphx::shape::int64_type, {1}}; migraphx::shape iou_thres_s{migraphx::shape::float_type, {1}}; migraphx::shape score_thres_s{migraphx::shape::float_type, {1}}; migraphx::shape output_s{migraphx::shape::int64_type, {6, 3}}; expect_shape(output_s, migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", false}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // use_dyn_output == true output_s = {migraphx::shape::int64_type, {{0, 6}, {3, 3}}}; expect_shape(output_s, migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // dynamic batches boxes_s = {migraphx::shape::float_type, {{1, 3}, {6, 6}, {4, 4}}}; scores_s = {migraphx::shape::float_type, {{1, 3}, {1, 1}, {6, 6}}}; output_s = {migraphx::shape::int64_type, {{0, 18}, {3, 3}}}; expect_shape(output_s, migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // dynamic num boxes boxes_s = {migraphx::shape::float_type, {{1, 1}, {6, 20}, {4, 4}}}; scores_s = {migraphx::shape::float_type, {{1, 1}, {1, 1}, {6, 20}}}; output_s = {migraphx::shape::int64_type, {{0, 20}, {3, 3}}}; expect_shape(output_s, migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // use_dyn_output false with dynamic input shape throws_shape(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", false}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // dynamic classes boxes_s = {migraphx::shape::float_type, {{1, 1}, {6, 6}, {4, 4}}}; scores_s = {migraphx::shape::float_type, {{1, 1}, {1, 3}, {6, 6}}}; output_s = {migraphx::shape::int64_type, {{0, 6}, {3, 3}}}; expect_shape(output_s, migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // fixed mismatch batches boxes_s = {migraphx::shape::float_type, {2, 6, 4}}; scores_s = {migraphx::shape::float_type, {1, 1, 6}}; throws_shape(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // fixed mismatch num boxes boxes_s = {migraphx::shape::float_type, {1, 6, 4}}; scores_s = {migraphx::shape::float_type, {1, 1, 4}}; throws_shape(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // dynamic mismatch batches boxes_s = {migraphx::shape::float_type, {{1, 4}, {6, 6}, {4, 4}}}; scores_s = {migraphx::shape::float_type, {{2, 8}, {1, 1}, {6, 6}}}; throws_shape(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // dynamic mismatch num boxes boxes_s = {migraphx::shape::float_type, {{1, 1}, {6, 8}, {4, 4}}}; scores_s = {migraphx::shape::float_type, {{1, 1}, {1, 1}, {3, 9}}}; throws_shape(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // dynamic number of classes, fixed boxes_s, mismatch batches boxes_s = {migraphx::shape::float_type, {1, 6, 4}}; scores_s = {migraphx::shape::float_type, {{1, 3}, {1, 3}, {6, 6}}}; throws_shape(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); // dynamic number of classes, fixed boxes_s, mismatch num boxes boxes_s = {migraphx::shape::float_type, {1, 6, 4}}; scores_s = {migraphx::shape::float_type, {{1, 1}, {1, 3}, {4, 8}}}; throws_shape(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_s, scores_s, max_out_s, iou_thres_s, score_thres_s); } TEST_CASE(onehot_static_2arg0) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape values{migraphx::shape::float_type, {2}}; migraphx::shape output{migraphx::shape::float_type, {2, 3, 4}}; expect_shape(output, migraphx::make_op("onehot", {{"depth", 4}}), indices, values); } TEST_CASE(onehot_static_2arg1) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape values{migraphx::shape::float_type, {2}}; migraphx::shape output{migraphx::shape::float_type, {2, 6, 3}}; expect_shape(output, migraphx::make_op("onehot", {{"axis", 1}, {"depth", 6}}), indices, values); } TEST_CASE(onehot_dyn_2arg0) { migraphx::shape indices{migraphx::shape::int64_type, {{1, 4}, {2, 2}, {3, 3}}}; migraphx::shape values{migraphx::shape::int32_type, {2}}; migraphx::shape output{migraphx::shape::int32_type, {{1, 4}, {2, 2}, {8, 8}, {3, 3}}}; expect_shape(output, migraphx::make_op("onehot", {{"axis", 2}, {"depth", 8}}), indices, values); } TEST_CASE(onehot_dyn_3arg0) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::float_type, {2}}; std::size_t max_val = std::numeric_limits::max(); migraphx::shape output{migraphx::shape::float_type, {{2, 2}, {3, 3}, {0, max_val}}}; expect_shape(output, migraphx::make_op("onehot"), indices, depth, values); } TEST_CASE(onehot_dyn_3arg1) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::float_type, {2}}; std::size_t max_val = std::numeric_limits::max(); migraphx::shape output{migraphx::shape::float_type, {{2, 2}, {3, 3}, {0, max_val}}}; expect_shape(output, migraphx::make_op("onehot", {{"axis", 2}}), indices, depth, values); } TEST_CASE(onehot_dyn_3arg2) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::float_type, {2}}; std::size_t max_val = std::numeric_limits::max(); migraphx::shape output{migraphx::shape::float_type, {{2, 2}, {0, max_val}, {3, 3}}}; expect_shape(output, migraphx::make_op("onehot", {{"axis", 1}}), indices, depth, values); } TEST_CASE(onehot_dyn_3arg3) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::float_type, {2}}; std::size_t max_val = std::numeric_limits::max(); migraphx::shape output{migraphx::shape::float_type, {{0, max_val}, {2, 2}, {3, 3}}}; expect_shape(output, migraphx::make_op("onehot", {{"axis", -3}}), indices, depth, values); } TEST_CASE(onehot_dyn_indices) { migraphx::shape indices{migraphx::shape::int64_type, {{1, 4}, {2, 2}, {3, 3}}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::int32_type, {2}}; std::size_t max_val = std::numeric_limits::max(); migraphx::shape output{migraphx::shape::int32_type, {{1, 4}, {2, 2}, {0, max_val}, {3, 3}}}; expect_shape(output, migraphx::make_op("onehot", {{"axis", 2}}), indices, depth, values); } TEST_CASE(onehot_axis_error0) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::float_type, {2}}; throws_shape(migraphx::make_op("onehot", {{"axis", 3}}), indices, depth, values); } TEST_CASE(onehot_axis_error1) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::float_type, {2}}; throws_shape(migraphx::make_op("onehot", {{"axis", -4}}), indices, depth, values); } TEST_CASE(onehot_axis_out_of_range0) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::float_type, {2}}; throws_shape(migraphx::make_op("onehot", {{"axis", 3}}), indices, depth, values); } TEST_CASE(onehot_axis_out_of_range1) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape depth{migraphx::shape::int64_type, {1}}; migraphx::shape values{migraphx::shape::float_type, {2}}; throws_shape(migraphx::make_op("onehot", {{"axis", -4}}), indices, depth, values); } TEST_CASE(onehot_neg_depth_attr) { migraphx::shape indices{migraphx::shape::int64_type, {2, 3}}; migraphx::shape values{migraphx::shape::float_type, {2}}; throws_shape(migraphx::make_op("onehot", {{"axis", 1}, {"depth", -3}}), indices, values); } TEST_CASE(pack_int4) { migraphx::shape input{migraphx::shape::uint8_type, {1, 4, 16, 16}}; migraphx::shape output{migraphx::shape::uint8_type, {1, 4, 16, 8}}; expect_shape(output, migraphx::make_op("pack_int4"), input); } TEST_CASE(pack_int4_axis1) { migraphx::shape input{migraphx::shape::uint8_type, {1, 4, 16, 16}}; migraphx::shape output{migraphx::shape::uint8_type, {1, 2, 16, 16}}; expect_shape(output, migraphx::make_op("pack_int4", {{"axis", 1}}), input); } TEST_CASE(pack_int4_axis2) { migraphx::shape input{migraphx::shape::uint8_type, {1, 4, 16, 16}}; migraphx::shape output{migraphx::shape::uint8_type, {1, 2, 16, 16}}; expect_shape(output, migraphx::make_op("pack_int4", {{"axis", -3}}), input); } TEST_CASE(pack_int4_invalid_axis) { migraphx::shape input{migraphx::shape::uint8_type, {1, 4, 16, 16}}; throws_shape(migraphx::make_op("pack_int4", {{"axis", 4}}), input); } TEST_CASE(pack_int4_nonstandard) { migraphx::shape input{migraphx::shape::uint8_type, {1, 16, 16, 4}, {1024, 16, 1, 256}}; migraphx::shape output{migraphx::shape::uint8_type, {1, 8, 16, 4}}; expect_shape(output, migraphx::make_op("pack_int4", {{"axis", 1}}), input); } TEST_CASE(pack_int4_invalid_dtype) { migraphx::shape input{migraphx::shape::float_type, {1, 4, 16, 16}}; throws_shape(migraphx::make_op("pack_int4", {{"axis", 0}}), input); } TEST_CASE(pack_int4_odd_lengths) { migraphx::shape input{migraphx::shape::uint8_type, {3, 4, 16, 16}}; throws_shape(migraphx::make_op("pack_int4", {{"axis", 0}}), input); } TEST_CASE(unpack_int4) { migraphx::shape input{migraphx::shape::uint8_type, {1, 4, 16, 8}}; migraphx::shape output{migraphx::shape::uint8_type, {1, 4, 16, 16}}; expect_shape(output, migraphx::make_op("unpack_int4"), input); } TEST_CASE(unpack_int4_axis1) { migraphx::shape input{migraphx::shape::uint8_type, {1, 2, 16, 16}}; migraphx::shape output{migraphx::shape::uint8_type, {1, 4, 16, 16}}; expect_shape(output, migraphx::make_op("unpack_int4", {{"axis", 1}}), input); } TEST_CASE(unpack_int4_axis2) { migraphx::shape input{migraphx::shape::uint8_type, {1, 2, 16, 16}}; migraphx::shape output{migraphx::shape::uint8_type, {1, 4, 16, 16}}; expect_shape(output, migraphx::make_op("unpack_int4", {{"axis", -3}}), input); } TEST_CASE(unpack_int4_invalid_axis) { migraphx::shape input{migraphx::shape::uint8_type, {1, 4, 16, 16}}; throws_shape(migraphx::make_op("unpack_int4", {{"axis", 4}}), input); } TEST_CASE(unpack_int4_nonstandard) { migraphx::shape input{migraphx::shape::uint8_type, {1, 16, 16, 4}, {1024, 16, 1, 256}}; migraphx::shape output{migraphx::shape::uint8_type, {1, 32, 16, 4}}; expect_shape(output, migraphx::make_op("unpack_int4", {{"axis", 1}}), input); } TEST_CASE(unpack_int4_invalid_dtype) { migraphx::shape input{migraphx::shape::float_type, {1, 4, 16, 16}}; throws_shape(migraphx::make_op("unpack_int4", {{"axis", 0}}), input); } TEST_CASE(unpack_int4_odd_lengths) { migraphx::shape input{migraphx::shape::uint8_type, {3, 4, 16, 16}}; migraphx::shape output{migraphx::shape::uint8_type, {6, 4, 16, 16}}; expect_shape(output, migraphx::make_op("unpack_int4", {{"axis", 0}}), input); } TEST_CASE(pad_shape0) { migraphx::shape input{migraphx::shape::float_type, {2, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {2, 3, 5, 5}}; expect_shape(output, migraphx::make_op("pad", {{"pads", {0, 0, 1, 1, 0, 0, 1, 1}}}), input); } TEST_CASE(pad_shape1) { migraphx::shape input{migraphx::shape::float_type, {2, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {2, 3, 6, 6}}; expect_shape(output, migraphx::make_op("pad", {{"pads", {0, 0, 2, 2, 0, 0, 1, 1}}}), input); } TEST_CASE(pad_dyn_shape0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4, {2}}, {3, 3}, {3, 5}, {3, 5}}}; migraphx::shape output{migraphx::shape::float_type, {{1, 4, {2}}, {3, 3}, {5, 7}, {5, 7}}}; expect_shape(output, migraphx::make_op("pad", {{"pads", {0, 0, 1, 1, 0, 0, 1, 1}}}), input); } TEST_CASE(pad_dyn_shape1) { migraphx::shape input{migraphx::shape::float_type, {{1, 4, {2}}, {3, 3}, {3, 5, {5}}, {3, 5, {5}}}}; migraphx::shape output{migraphx::shape::float_type, {{1, 4, {2}}, {3, 3}, {5, 7, {7}}, {5, 7, {7}}}}; expect_shape(output, migraphx::make_op("pad", {{"pads", {0, 0, 1, 1, 0, 0, 1, 1}}}), input); } TEST_CASE(pointwise_no_module) { migraphx::shape input{migraphx::shape::float_type, {0}, {0}}; throws_shape(migraphx::make_op("pointwise"), input); } TEST_CASE(pointwise_no_input) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::module m; std::vector args{}; auto output = migraphx::shape(migraphx::shape::float_type, {1}, {0}); auto l = m.add_literal(migraphx::literal(output, {1})); m.add_return({l}); EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("pointwise"), args, {&m}); })); } TEST_CASE(pointwise_no_output) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::module m; std::vector args{}; EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("pointwise"), args, {&m}); })); } TEST_CASE(pointwise_strict_type) { migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::program p; auto* mm = p.get_main_module(); migraphx::module pm; { auto x = pm.add_parameter("x", s.with_type(migraphx::shape::half_type)); pm.add_return({x}); } auto x = mm->add_parameter("x", s); EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("pointwise"), {x}, {&pm}); })); } TEST_CASE(pooling_shape0) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; throws_shape(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {1}}, {"stride", {0}}, {"lengths", {1}}, {"dilations", {1}}}), input); } TEST_CASE(pooling_shape1) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 1, 1}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {3, 3}}, {"lengths", {1, 1}}, {"dilations", {1, 1}}}), input); } TEST_CASE(pooling_shape2) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 2, 2}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {3, 3}}, {"lengths", {1, 1}}, {"dilations", {1, 1}}, {"ceil_mode", true}}), input); } TEST_CASE(pooling_shape3) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 3, 3}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {2, 2}}, {"stride", {3, 3}}, {"lengths", {3, 3}}, {"dilations", {1, 1}}, {"ceil_mode", true}}), input); } TEST_CASE(pooling_shape4) { migraphx::shape tiny_input{migraphx::shape::float_type, {4, 1}}; throws_shape(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}}), tiny_input); } TEST_CASE(pooling_shape5) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 1, 1}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {1, 1}}, {"lengths", {2, 2}}, {"dilations", {2, 2}}}), input); } TEST_CASE(pooling_shape6) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 2, 2}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {2, 2}}, {"lengths", {1, 1}}, {"dilations", {2, 2}}}), input); } TEST_CASE(pooling_shape7) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 2, 2}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {3, 3}}, {"lengths", {1, 1}}, {"dilations", {3, 3}}, {"ceil_mode", true}}), input); } TEST_CASE(pooling_shape8) { migraphx::shape input{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape output{migraphx::shape::float_type, {4, 3, 3, 3}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {2, 2}}, {"stride", {1, 1}}, {"lengths", {3, 3}}, {"dilations", {2, 2}}}), input); } TEST_CASE(pooling_dyn_shape0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {3, 3, {3}}, {3, 3, {3}}, {3, 3}}}; throws_shape(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {1}}, {"stride", {0}}, {"lengths", {1}}, {"dilations", {1}}}), input); } TEST_CASE(pooling_dyn_shape1) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {3, 3, {3}}, {3, 3, {3}}, {3, 3}}}; migraphx::shape output{migraphx::shape::float_type, {{1, 4}, {3, 3}, {1, 1}, {1, 1}}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {3, 3}}, {"lengths", {1, 1}}, {"dilations", {1, 1}}}), input); } TEST_CASE(pooling_dyn_shape2) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {5, 5}, {3, 3, {3}}, {3, 3}}}; migraphx::shape output{migraphx::shape::float_type, {{1, 4}, {5, 5}, {2, 2}, {2, 2}}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {3, 3}}, {"lengths", {1, 1}}, {"dilations", {1, 1}}, {"ceil_mode", true}}), input); } TEST_CASE(pooling_dyn_shape3) { migraphx::shape input{migraphx::shape::float_type, {{4, 4}, {3, 3}, {4, 12, {8}}, {4, 12, {8}}}}; migraphx::shape output{migraphx::shape::float_type, {{4, 4}, {3, 3}, {2, 4}, {2, 4}}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {3, 3}}, {"lengths", {1, 1}}, {"dilations", {1, 1}}}), input); } TEST_CASE(pooling_dyn_shape4) { migraphx::shape input{migraphx::shape::float_type, {{4, 4}, {3, 3}, {4, 12, {8}}, {4, 12, {8}}}}; migraphx::shape output{migraphx::shape::float_type, {{4, 4}, {3, 3}, {3, 6}, {3, 6}}}; expect_shape(output, migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {2, 2}}, {"stride", {3, 3}}, {"lengths", {3, 3}}, {"dilations", {1, 1}}, {"ceil_mode", true}}), input); } TEST_CASE(prefix_scan_sum) { { migraphx::shape s{migraphx::shape::float_type, {1, 2, 3}}; throws_shape( migraphx::make_op("prefix_scan_sum", {{"axis", 3}, {"exclusive", 0}, {"reverse", 0}}), s); } { migraphx::shape s{migraphx::shape::float_type, {1, 2}}; throws_shape( migraphx::make_op("prefix_scan_sum", {{"axis", -3}, {"exclusive", 0}, {"reverse", 0}}), s); } } TEST_CASE(prefix_scan_sum_dyn) { { std::vector dd{{5, 8}}; migraphx::shape s{migraphx::shape::float_type, dd}; expect_shape( s, migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", 0}, {"reverse", 0}}), s); } } TEST_CASE(prefix_scan_sum_dyn_2d) { { std::vector dd{{5, 8}, {3, 7}}; migraphx::shape s{migraphx::shape::float_type, dd}; expect_shape( s, migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", 0}, {"reverse", 0}}), s); } } TEST_CASE(random_uniform) { std::vector dd{{5, 8}, {3, 7}}; migraphx::shape s0{migraphx::shape::uint64_type, {1}}; migraphx::shape s1{migraphx::shape::float_type, dd}; expect_shape(s1, migraphx::make_op("random_uniform"), s0, s1); } TEST_CASE(random_seed) { migraphx::shape s{migraphx::shape::uint64_type, {1}, {0}}; expect_shape(s, migraphx::make_op("random_seed")); } TEST_CASE(quant_convolution_shape) { migraphx::shape output{migraphx::shape::int32_type, {4, 4, 1, 1}}; migraphx::shape input{migraphx::shape::int8_type, {4, 3, 3, 3}}; migraphx::shape weights{migraphx::shape::int8_type, {4, 3, 3, 3}}; expect_shape(output, migraphx::make_op("quant_convolution"), input, weights); throws_shape(migraphx::make_op("quant_convolution"), input); throws_shape(migraphx::make_op("quant_convolution", {{"padding", {0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), input, weights); throws_shape(migraphx::make_op("quant_convolution", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), input, weights); migraphx::shape input2{migraphx::shape::int32_type, {3, 3}}; migraphx::shape weights2{migraphx::shape::float_type, {3, 3}}; throws_shape(migraphx::make_op("quant_convolution"), input2, weights2); throws_shape(migraphx::make_op("quant_convolution"), input2, weights); migraphx::shape input3{migraphx::shape::int32_type, {4, 3, 3, 3}}; migraphx::shape weight3{migraphx::shape::float_type, {4, 3, 3, 3}}; throws_shape(migraphx::make_op("quant_convolution"), input3, weights); throws_shape(migraphx::make_op("quant_convolution"), input, weight3); throws_shape(migraphx::make_op("quant_convolution"), input3, weight3); } // quant_dot TEST_CASE(quant_dot_2args) { { migraphx::shape s_m1{migraphx::shape::int8_type, {2, 4}}; migraphx::shape s_m2{migraphx::shape::int8_type, {4, 8}}; expect_shape(migraphx::shape{migraphx::shape::int32_type, {2, 8}}, migraphx::make_op("quant_dot"), s_m1, s_m2); } { migraphx::shape s_m1{migraphx::shape::int8_type, {3, 8}}; migraphx::shape s_m2{migraphx::shape::int8_type, {8, 7}}; expect_shape(migraphx::shape{migraphx::shape::int32_type, {3, 7}}, migraphx::make_op("quant_dot"), s_m1, s_m2); } { migraphx::shape s_m1{migraphx::shape::int8_type, {2, 4}}; migraphx::shape s_m2{migraphx::shape::int8_type, {8, 8}}; throws_shape(migraphx::make_op("quant_dot"), s_m1, s_m2); } } TEST_CASE(qlinear) { migraphx::shape scales{migraphx::shape::float_type, {2, 4}}; migraphx::shape input{migraphx::shape::float_type, {2, 4}}; migraphx::shape result{migraphx::shape::uint8_type, {2, 4}}; expect_shape(result, migraphx::make_op("quantizelinear"), input, scales); } TEST_CASE(qlinear_zeros) { migraphx::shape zeros{migraphx::shape::int8_type, {2, 4}}; migraphx::shape scales{migraphx::shape::float_type, {2, 4}}; migraphx::shape input{migraphx::shape::float_type, {2, 4}}; migraphx::shape result{migraphx::shape::int8_type, {2, 4}}; expect_shape(result, migraphx::make_op("quantizelinear"), input, scales, zeros); } TEST_CASE(qlinear_fp16) { migraphx::shape scales{migraphx::shape::half_type, {2, 4}}; migraphx::shape input{migraphx::shape::half_type, {2, 4}}; migraphx::shape result{migraphx::shape::uint8_type, {2, 4}}; expect_shape(result, migraphx::make_op("quantizelinear"), input, scales); } TEST_CASE(qlinear_output_type_1) { migraphx::shape scales{migraphx::shape::half_type, {2, 4}}; migraphx::shape input{migraphx::shape::half_type, {2, 4}}; migraphx::shape result{migraphx::shape::int8_type, {2, 4}}; expect_shape( result, migraphx::make_op("quantizelinear", {{"out_type", result.type()}}), input, scales); } TEST_CASE(qlinear_output_type_2) { migraphx::shape scales{migraphx::shape::half_type, {2, 4}}; migraphx::shape input{migraphx::shape::half_type, {2, 4}}; migraphx::shape result{migraphx::shape::int8_type, {2, 4}}; auto op = migraphx::make_op("quantizelinear"); auto val = op.to_value(); val["out_type"] = migraphx::to_value(migraphx::shape::int8_type); expect_shape(result, migraphx::make_op("quantizelinear", val), input, scales); } TEST_CASE(qlinear_mismatch_type) { migraphx::shape scales{migraphx::shape::int8_type, {2, 4}}; migraphx::shape input{migraphx::shape::float_type, {2, 4}}; throws_shape(migraphx::make_op("quantizelinear"), input, scales); } TEST_CASE(dqlinear) { migraphx::shape scales{migraphx::shape::float_type, {2, 4}}; migraphx::shape input{migraphx::shape::int8_type, {2, 4}}; migraphx::shape result{migraphx::shape::float_type, {2, 4}}; expect_shape(result, migraphx::make_op("dequantizelinear"), input, scales); } TEST_CASE(dqlinear_fp16) { migraphx::shape scales{migraphx::shape::half_type, {2, 4}}; migraphx::shape input{migraphx::shape::int8_type, {2, 4}}; migraphx::shape result{migraphx::shape::half_type, {2, 4}}; expect_shape(result, migraphx::make_op("dequantizelinear"), input, scales); } TEST_CASE(dqlinear_mismatch_type) { migraphx::shape zeros{migraphx::shape::float_type, {2, 4}}; migraphx::shape scales{migraphx::shape::float_type, {2, 4}}; migraphx::shape input{migraphx::shape::int8_type, {2, 4}}; throws_shape(migraphx::make_op("dequantizelinear"), input, scales, zeros); } static void test_reduce_ops(const std::string& name) { { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 1, 1, 1}}, migraphx::make_op(name, {{"axes", {0, 1, 2, 3}}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 3, 1, 1}}, migraphx::make_op(name, {{"axes", {2, 3}}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {1, 3, 4, 5}}, migraphx::make_op(name, {{"axes", {0}}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {2, 3, 4, 1}}, migraphx::make_op(name, {{"axes", {-1}}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; throws_shape(migraphx::make_op(name, {{"axes", {4}}}), input); } } // dynamic shape static void test_dyn_reduce_ops(const std::string& name) { { migraphx::shape input{migraphx::shape::float_type, {{2, 3, {3}}, {2, 4, {4}}}}; expect_shape( migraphx::shape{migraphx::shape::float_type, std::vector({{2, 3, {3}}, {1, 1}})}, migraphx::make_op(name, {{"axes", {-1}}}), input); } { migraphx::shape input{migraphx::shape::float_type, {{2, 3, {3}}, {2, 4, {4}}}}; expect_shape( migraphx::shape{migraphx::shape::float_type, std::vector({{1, 1}, {2, 4, {4}}})}, migraphx::make_op(name, {{"axes", {0}}}), input); } { migraphx::shape input{migraphx::shape::float_type, {{2, 3, {3}}, {2, 4, {4}}}}; expect_shape( migraphx::shape{migraphx::shape::float_type, std::vector({{1, 1}, {1, 1}})}, migraphx::make_op(name, {{"axes", {0, 1}}}), input); } { migraphx::shape input{migraphx::shape::float_type, {{2, 3, {3}}, {2, 4, {4}}}}; throws_shape(migraphx::make_op(name, {{"axes", {4}}}), input); } } static void test_reduce_ops_variable_axes(const std::string& name) { { migraphx::shape input_shape{migraphx::shape::float_type, {2, 3, 4}}; migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; migraphx::shape expected_shape{migraphx::shape::float_type, {{1, 2}, {1, 3}, {1, 4}}}; expect_shape(expected_shape, migraphx::make_op(name), input_shape, axes_shape); } { migraphx::shape input_shape{migraphx::shape::float_type, {{2, 3}, {3, 4}}}; migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; migraphx::shape expected_shape{migraphx::shape::float_type, {{1, 3}, {1, 4}}}; expect_shape(expected_shape, migraphx::make_op(name), input_shape, axes_shape); } } TEST_CASE(reduce_max) { test_reduce_ops("reduce_max"); } TEST_CASE(reduce_min) { test_reduce_ops("reduce_min"); } TEST_CASE(reduce_mean) { test_reduce_ops("reduce_mean"); } TEST_CASE(reduce_prod) { test_reduce_ops("reduce_prod"); } TEST_CASE(reduce_sum) { test_reduce_ops("reduce_sum"); } TEST_CASE(reduce_max_dyn) { test_dyn_reduce_ops("reduce_max"); } TEST_CASE(reduce_min_dyn) { test_dyn_reduce_ops("reduce_min"); } TEST_CASE(reduce_mean_dyn) { test_dyn_reduce_ops("reduce_mean"); } TEST_CASE(reduce_prod_dyn) { test_dyn_reduce_ops("reduce_prod"); } TEST_CASE(reduce_sum_dyn) { test_dyn_reduce_ops("reduce_sum"); } TEST_CASE(reduce_max_variable_axes) { test_reduce_ops_variable_axes("reduce_max"); } TEST_CASE(reduce_min_variable_axes) { test_reduce_ops_variable_axes("reduce_min"); } TEST_CASE(reduce_mean_variable_axes) { test_reduce_ops_variable_axes("reduce_mean"); } TEST_CASE(reduce_prod_variable_axes) { test_reduce_ops_variable_axes("reduce_prod"); } TEST_CASE(reduce_sum_variable_axes) { test_reduce_ops_variable_axes("reduce_sum"); } TEST_CASE(reshape_shape) { migraphx::shape input{migraphx::shape::float_type, {24, 1, 1, 1}}; for(auto&& new_shape : std::vector>{{8, 3, 1, 1}, {1, 3, 4, 2}, {1, 3, 4, 2}}) { std::vector lens(new_shape.size()); std::copy(new_shape.begin(), new_shape.end(), lens.begin()); migraphx::shape output{migraphx::shape::float_type, lens}; expect_shape(output, migraphx::make_op("reshape", {{"dims", new_shape}}), input); } } TEST_CASE(reshape_shape_invalid) { migraphx::shape input{migraphx::shape::float_type, {24, 1, 1, 1}}; for(auto&& new_shape : std::vector>{{8, 3, 2, 2}, {1, 3, -1, -1}, {3, 0}, {3, 2}}) { throws_shape(migraphx::make_op("reshape", {{"dims", new_shape}}), input); } } TEST_CASE(reshape_shape_minus1_reshapes) { migraphx::shape input{migraphx::shape::float_type, {24, 1, 1, 1}}; std::vector, migraphx::shape>> minus1_tests{ {{2, -1, 3}, {migraphx::shape::float_type, {2, 4, 3}}}, {{0, -1, 0}, {migraphx::shape::float_type, {24, 1, 1}}}, {{2, -1, 0}, {migraphx::shape::float_type, {2, 12, 1}}}, {{0, 0, -1}, {migraphx::shape::float_type, {24, 1, 1}}}, {{2, 0, -1}, {migraphx::shape::float_type, {2, 1, 12}}}, {{-1, 2, 3}, {migraphx::shape::float_type, {4, 2, 3}}}, {{-1, 0, 3}, {migraphx::shape::float_type, {8, 1, 3}}}, {{-1, 0, 0}, {migraphx::shape::float_type, {24, 1, 1}}}, {{-1, 3, 0}, {migraphx::shape::float_type, {8, 3, 1}}}}; for(auto& it : minus1_tests) { expect_shape(it.second, migraphx::make_op("reshape", {{"dims", it.first}}), input); } } TEST_CASE(reshape_nonstandard) { auto input = migraphx::shape::from_permutation(migraphx::shape::float_type, {4, 24, 1, 1, 1}, migraphx::invert_permutation({1, 0, 2, 3, 4})); std::vector> tests{{4, 24}, {4, 24, 1, 1, 1, 1}, {4, 8, 3, 1, 1}, {4, 1, 3, 4, 2}, {4, 1, 4, 3, 2}, {4, 2, 4, 3}, {4, 2, 12, 1}, {4, 2, 1, 12}, {4, 4, 2, 3}, {4, 8, 1, 3}, {4, 8, 3, 1}}; for(auto dims : tests) { migraphx::shape output = migraphx::shape{migraphx::shape::float_type, dims}; expect_shape(output, migraphx::make_op("reshape", {{"dims", dims}}), input); } } TEST_CASE(reshape_nonstandard_squeeze) { auto input = migraphx::shape::from_permutation( migraphx::shape::float_type, {2, 16, 16, 1280}, migraphx::invert_permutation({0, 2, 3, 1})); std::vector lens = {2, 256, 1280}; migraphx::shape output = migraphx::shape{migraphx::shape::float_type, lens}; expect_shape(output, migraphx::make_op("reshape", {{"dims", lens}}), input); } TEST_CASE(reshape_nonstandard_error) { auto input = migraphx::shape::from_permutation(migraphx::shape::float_type, {4, 24, 1, 1, 1}, migraphx::invert_permutation({1, 0, 2, 3, 4})); for(auto&& new_shape : std::vector>{{4, 8, 3, 2, 2}, {1}, {4, 8, 4}, {4, 24, 1, 1, 1, 1, 2}, {8, 4, 4}, {4, 1, 3, -1, -1}, {4, 3, 0}, {4, 3, 2}, {3, 0}, {3, 2}}) { throws_shape(migraphx::make_op("reshape", {{"dims", new_shape}}), input); } } TEST_CASE(reshape_transposed_squeeze) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {1, 4}}; migraphx::shape output{migraphx::shape::float_type, {64}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_nonpacked_unsqueeze1) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; migraphx::shape output{migraphx::shape::float_type, {4, 2, 8}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_nonpacked_unsqueeze2) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; migraphx::shape output{migraphx::shape::float_type, {2, 2, 16}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_nonpacked_squeeze1) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; migraphx::shape output{migraphx::shape::float_type, {64}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_nonpacked_squeeze2) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; migraphx::shape output{migraphx::shape::float_type, {64}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_broadcast_unsqueeze1) { migraphx::shape input{migraphx::shape::float_type, {2, 256, 1280}, {0, 0, 1}}; migraphx::shape output{migraphx::shape::float_type, {2, 16, 16, 1280}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_broadcast_unsqueeze2) { migraphx::shape input{migraphx::shape::float_type, {2, 256, 1280}, {0, 0, 1}}; migraphx::shape output{migraphx::shape::float_type, {2, 256, 16, 80}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_broadcast_squeeze1) { migraphx::shape input{migraphx::shape::float_type, {2, 16, 16, 1280}, {0, 0, 0, 1}}; migraphx::shape output{migraphx::shape::float_type, {2, 256, 1280}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_broadcast_squeeze2) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {0, 1}}; migraphx::shape output{migraphx::shape::float_type, {64}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_broadcast_squeeze3) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {1, 0}}; migraphx::shape output{migraphx::shape::float_type, {64}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_broadcast_squeeze_memlayout_change) { migraphx::shape input{migraphx::shape::float_type, {2, 16, 16, 1280}, {0, 0, 0, 1}}; migraphx::shape output{migraphx::shape::float_type, {2, 16, 256, 80}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_dyn_1in) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; for(auto&& new_shape : std::vector>{ {-1, 1, 1, 24}, {0, 8, 3, 1}, {-1, 3, 4, 2}, {0, 2, 4, 3}, {2, 2, 12, 0}}) { std::vector out_dyn_dims{}; for(std::size_t i = 0; i < new_shape.size(); ++i) { if(new_shape[i] == 0 or new_shape[i] == -1) { out_dyn_dims.push_back(input.dyn_dims().at(i)); } else { std::size_t d = new_shape[i]; out_dyn_dims.push_back({d, d}); } } migraphx::shape output{migraphx::shape::float_type, out_dyn_dims}; expect_shape(output, migraphx::make_op("reshape", {{"dims", new_shape}}), input); } } // more -1 dims attribute testing TEST_CASE(reshape_dyn_1in_negative_1_dims_0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {2, 8}, {2, 8}}}; std::vector out_dyn_dims = { {1, 4}, {12, 12}, {2, 8}, {4, 16}}; migraphx::shape output{migraphx::shape::float_type, out_dyn_dims}; expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 12, 0, -1}}}), input); } // output dynamic shape is surprising but that's how the calculation works out TEST_CASE(reshape_dyn_1in_negative_1_dims_1) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {2, 8}, {2, 8}}}; std::vector out_dyn_dims = { {1, 4}, {24, 384}, {2, 2}, {2, 2}}; migraphx::shape output{migraphx::shape::float_type, out_dyn_dims}; expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, -1, 2, 2}}}), input); } TEST_CASE(reshape_dyn_1in_negative_1_dims_2) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {2, 8}, {2, 8}}}; std::vector out_dyn_dims = {{1, 4}, {24, 24}, {4, 64}}; migraphx::shape output{migraphx::shape::float_type, out_dyn_dims}; expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 0, -1}}}), input); } TEST_CASE(reshape_dyn_1in_negative_1_dims_3) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}}}; std::vector out_dyn_dims = {{1, 4}, {4, 4}, {3, 3}, {2, 2}}; migraphx::shape output{migraphx::shape::float_type, out_dyn_dims}; expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 4, 3, 2}}}), input); } // note how non-fixed dynamic dimension on axis=0 goes to 2 from `dims` attribute // code assumes that this will work at run-time TEST_CASE(reshape_dyn_1in_dyn_to_fixed) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; std::vector dims_attr = {2, 1, 1, 24}; migraphx::shape output{migraphx::shape::float_type, {{2, 2}, {1, 1}, {1, 1}, {24, 24}}}; expect_shape(output, migraphx::make_op("reshape", {{"dims", dims_attr}}), input); } TEST_CASE(reshape_dyn_2in_0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; migraphx::shape output{migraphx::shape::float_type, {{1, 4}, {8, 8}, {3, 3}, {1, 1}}}; expect_shape(output, migraphx::make_op("reshape"), input, output); } TEST_CASE(reshape_dyn_2in_1) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; migraphx::shape output{migraphx::shape::float_type, {{12, 12}, {2, 2}, {1, 1}, {1, 4}}}; expect_shape(output, migraphx::make_op("reshape"), input, output); } TEST_CASE(reshape_dyn_2in_2) { migraphx::shape input{migraphx::shape::float_type, {2, 24, 1, 1}}; migraphx::shape output{migraphx::shape::float_type, {{1, 2}, {6, 12}, {1, 1}, {4, 4}}}; expect_shape(output, migraphx::make_op("reshape"), input, output); } TEST_CASE(reshape_dyn_1in_multiple_non_fixed0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {10, 20}, {1, 1}}}; migraphx::shape output{migraphx::shape::float_type, {{1, 4}, {1, 1}, {10, 20}, {24, 24}}}; std::vector new_shape = {0, 1, 0, 24}; expect_shape(output, migraphx::make_op("reshape", {{"dims", new_shape}}), input); } TEST_CASE(reshape_dyn_1in_multiple_non_fixed1) { migraphx::shape input{migraphx::shape::float_type, {{1, 8}, {24, 24}, {10, 20}, {1, 1}}}; migraphx::shape output{migraphx::shape::float_type, {{1, 8}, {1, 1}, {10, 20}, {24, 24}}}; std::vector new_shape = {-1, 1, 0, 24}; expect_shape(output, migraphx::make_op("reshape", {{"dims", new_shape}}), input); } TEST_CASE(reshape_lazy_shape) { migraphx::shape input{migraphx::shape::float_type, {24, 1, 1, 1}}; for(auto&& new_shape : std::vector>{{8, 3, 1, 1}, {1, 3, 4, 2}, {1, 3, 4, 2}}) { std::vector lens(new_shape.size()); std::copy(new_shape.begin(), new_shape.end(), lens.begin()); migraphx::shape output{migraphx::shape::float_type, lens}; expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); } for(auto&& new_shape : std::vector>{{8, 3, 2, 2}, {1, 3, -1, -1}, {3, 0}, {3, 2}}) { throws_shape(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); } std::vector, migraphx::shape>> minus1_tests{ {{2, -1, 3}, {migraphx::shape::float_type, {2, 4, 3}}}, {{0, -1, 0}, {migraphx::shape::float_type, {24, 1, 1}}}, {{2, -1, 0}, {migraphx::shape::float_type, {2, 12, 1}}}, {{0, 0, -1}, {migraphx::shape::float_type, {24, 1, 1}}}, {{2, 0, -1}, {migraphx::shape::float_type, {2, 1, 12}}}, {{-1, 2, 3}, {migraphx::shape::float_type, {4, 2, 3}}}, {{-1, 0, 3}, {migraphx::shape::float_type, {8, 1, 3}}}, {{-1, 0, 0}, {migraphx::shape::float_type, {24, 1, 1}}}, {{-1, 3, 0}, {migraphx::shape::float_type, {8, 3, 1}}}}; for(auto& it : minus1_tests) { expect_shape(it.second, migraphx::make_op("reshape_lazy", {{"dims", it.first}}), input); } } // This uses the permutation to compute the reshape_lazy since its simpler than // trying to calculate strides. As we collapse or expand dimensions, we // remove the collapsed dimensions or duplicate the expanded dimensions in // the permutation. Then we renumber the permutation. So for dimensions of 4, // 24, 1, 1, 1 with a permutation of 1, 0, 2, 3, 4 that reshape_lazys to 4, 1, 3, // 4, 2, we first remove the collapsed dimensions or duplicate the expanded // dimensions which gives 1, 0, 0, 0, 0. Then after renumbering we get a // final permutation of 4, 0, 1, 2, 3. TEST_CASE(reshape_lazy_nonstandard) { auto input = migraphx::shape::from_permutation(migraphx::shape::float_type, {4, 24, 1, 1, 1}, migraphx::invert_permutation({1, 0, 2, 3, 4})); std::vector, std::vector>> tests{ {{4, 24}, {1, 0}}, {{4, 24, 1, 1, 1, 1}, {1, 0, 2, 3, 4, 5}}, {{4, 8, 3, 1, 1}, {2, 0, 1, 3, 4}}, {{4, 1, 3, 4, 2}, {4, 0, 1, 2, 3}}, {{4, 1, 4, 3, 2}, {4, 0, 1, 2, 3}}, {{4, 2, 4, 3}, {3, 0, 1, 2}}, {{4, 2, 12, 1}, {2, 0, 1, 3}}, {{4, 2, 1, 12}, {3, 0, 1, 2}}, {{4, 4, 2, 3}, {3, 0, 1, 2}}, {{4, 8, 1, 3}, {3, 0, 1, 2}}, {{4, 8, 3, 1}, {2, 0, 1, 3}}}; for(const auto& [dims, perm] : tests) { migraphx::shape output = migraphx::shape::from_permutation( migraphx::shape::float_type, dims, migraphx::invert_permutation(perm)); expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", dims}}), input); } } TEST_CASE(reshape_lazy_nonstandard_squeeze) { auto input = migraphx::shape::from_permutation( migraphx::shape::float_type, {2, 16, 16, 1280}, migraphx::invert_permutation({0, 2, 3, 1})); std::vector lens = {2, 256, 1280}; migraphx::shape output = migraphx::shape::from_permutation( migraphx::shape::float_type, lens, migraphx::invert_permutation({0, 2, 1})); expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", lens}}), input); } TEST_CASE(reshape_lazy_nonstandard_error) { auto input = migraphx::shape::from_permutation(migraphx::shape::float_type, {4, 24, 1, 1, 1}, migraphx::invert_permutation({1, 0, 2, 3, 4})); for(auto&& new_shape : std::vector>{{4, 8, 3, 2, 2}, {1}, {4, 8, 4}, {4, 24, 1, 1, 1, 1, 2}, {8, 4, 4}, {4, 1, 3, -1, -1}, {4, 3, 0}, {4, 3, 2}, {3, 0}, {3, 2}}) { throws_shape(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); } } TEST_CASE(reshape_lazy_transposed_squeeze) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {1, 4}}; throws_shape(migraphx::make_op("reshape_lazy", {{"dims", {64}}}), input); } TEST_CASE(reshape_lazy_nonpacked_unsqueeze1) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; migraphx::shape output{migraphx::shape::float_type, {4, 2, 8}, {32, 16, 2}}; expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_lazy_nonpacked_unsqueeze2) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; migraphx::shape output{migraphx::shape::float_type, {2, 2, 16}, {64, 32, 2}}; expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_lazy_nonpacked_squeeze1) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; migraphx::shape output{migraphx::shape::float_type, {64}, {2}}; expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_lazy_nonpacked_squeeze2) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 1}}; throws_shape(migraphx::make_op("reshape_lazy", {{"dims", {64}}}), input); } TEST_CASE(reshape_lazy_broadcast_unsqueeze1) { migraphx::shape input{migraphx::shape::float_type, {2, 256, 1280}, {0, 0, 1}}; migraphx::shape output{migraphx::shape::float_type, {2, 16, 16, 1280}, {0, 0, 0, 1}}; expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_lazy_broadcast_unsqueeze2) { migraphx::shape input{migraphx::shape::float_type, {2, 256, 1280}, {0, 0, 1}}; migraphx::shape output{migraphx::shape::float_type, {2, 256, 16, 80}, {0, 0, 80, 1}}; expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_lazy_broadcast_squeeze1) { migraphx::shape input{migraphx::shape::float_type, {2, 16, 16, 1280}, {0, 0, 0, 1}}; migraphx::shape output{migraphx::shape::float_type, {2, 256, 1280}, {0, 0, 1}}; expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", output.lens()}}), input); } TEST_CASE(reshape_lazy_broadcast_squeeze2) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {0, 1}}; throws_shape(migraphx::make_op("reshape_lazy", {{"dims", {64}}}), input); } TEST_CASE(reshape_lazy_broadcast_squeeze3) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {1, 0}}; throws_shape(migraphx::make_op("reshape_lazy", {{"dims", {64}}}), input); } TEST_CASE(reshape_lazy_broadcast_squeeze_error) { migraphx::shape input{migraphx::shape::float_type, {2, 16, 16, 1280}, {0, 0, 0, 1}}; std::vector new_shape = {2, 16, 20480}; throws_shape(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); } TEST_CASE(reshape_lazy_dyn_shape) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; for(auto&& new_shape : std::vector>{ {-1, 1, 1, 24}, {0, 8, 3, 1}, {-1, 3, 4, 2}, {0, 2, 4, 3}}) { std::vector out_dyn_dims{}; for(std::size_t i = 0; i < new_shape.size(); ++i) { if(new_shape[i] == 0 or new_shape[i] == -1) { out_dyn_dims.push_back(input.dyn_dims().at(i)); } else { std::size_t d = new_shape[i]; out_dyn_dims.push_back({d, d}); } } migraphx::shape output{migraphx::shape::float_type, out_dyn_dims}; expect_shape(output, migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); } } TEST_CASE(reshape_lazy_multiple_non_fixed_error) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {10, 20}, {1, 1}}}; std::vector new_shape = {0, 1, 0, 24}; throws_shape(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); } TEST_CASE(reshape_lazy_fixed_ele_not_matching_error) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {10, 10}, {1, 1}}}; std::vector new_shape = {0, 1, 5, 24}; throws_shape(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); } TEST_CASE(reshape_lazy_non_fixed_not_matching_error) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; std::vector new_shape = {2, 1, 1, 24}; throws_shape(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); } TEST_CASE(resize_single_input) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; std::vector sizes_vec{3, 4}; migraphx::shape output{migraphx::shape::float_type, {sizes_vec}}; expect_shape(output, migraphx::make_op("resize", {{"sizes", {3, 4}}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "asymmetric"}}), input); } TEST_CASE(resize_single_scale_input) { migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; std::vector sizes_vec{3, 4}; migraphx::shape output{migraphx::shape::float_type, {sizes_vec}}; expect_shape(output, migraphx::make_op("resize", {{"scales", {0.75, 0.25}}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "asymmetric"}}), input); } TEST_CASE(resize_single_input_err1) { // doesn't have either sizes or scales attribute migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; throws_shape(migraphx::make_op( "resize", {{"nearest_mode", "floor"}, {"coordinate_transformation_mode", "asymmetric"}}), input); } TEST_CASE(resize_single_input_err2) { // can't have both sizes and scales attribute migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; throws_shape(migraphx::make_op("resize", {{"scales", {0.75, 0.25}}, {"sizes", {87, 88}}}), input); } TEST_CASE(resize_single_dyn_input_err3) { // single dynamic input not supported yet std::vector input_dims = {{4, 5}, {15, 16}}; migraphx::shape input{migraphx::shape::float_type, input_dims}; throws_shape(migraphx::make_op("resize", {{"scales", {0.75, 0.25}}}), input); } TEST_CASE(resize_multi_input) { // resize always outputs a dynamic shape if there are 2 inputs migraphx::shape input{migraphx::shape::float_type, {4, 16}, {32, 2}}; std::size_t max_val = std::numeric_limits::max(); migraphx::shape sizes{migraphx::shape::int64_type, {2}}; migraphx::shape output{migraphx::shape::float_type, {{0, max_val}, {0, max_val}}}; expect_shape(output, migraphx::make_op("resize", {{"mode", "nearest"}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "asymmetric"}}), input, sizes); } TEST_CASE(return_shape_tuple) { using migraphx::shape; auto op = migraphx::make_op("@return"); shape s0{shape::bool_type, {1, 1}}; shape s1{shape::float_type, {2, 3}}; std::vector s{s0, s1}; auto s_out = op.compute_shape(s); EXPECT(s_out.type() == shape::tuple_type); EXPECT(s0 == s_out.sub_shapes()[0]); EXPECT(s1 == s_out.sub_shapes()[1]); } TEST_CASE(return_shape_half) { using migraphx::shape; auto op = migraphx::make_op("@return"); std::vector s{{shape::half_type}}; EXPECT(op.compute_shape(s) == shape{shape::half_type}); } TEST_CASE(return_shape_empty) { using migraphx::shape; auto op = migraphx::make_op("@return"); std::vector s; EXPECT(op.compute_shape(s) == shape{}); } TEST_CASE(rnn) { { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; expect_shape( migraphx::shape{migraphx::shape::float_type, {seq_len, num_dirct, batch_size, hidden_size}}, migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; throws_shape( migraphx::make_op( "rnn", {{"hidden_size", hidden_size + 1}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; throws_shape( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; throws_shape( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), in_shape, w_shape, r_shape, b_shape, ih_shape); } } TEST_CASE(select_module_dyn) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {3, 3}, {255, 255}, {255, 255}}}; std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {1000, 1000}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; expect_shape( out_attr, migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), input); } TEST_CASE(slice_static_shape) { migraphx::shape input{migraphx::shape::int32_type, {2, 2, 3}}; expect_shape(migraphx::shape{migraphx::shape::int32_type, {2, 2, 2}, {6, 3, 1}}, migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1}}, {"ends", {3}}}), input); expect_shape(migraphx::shape{migraphx::shape::int32_type, {2, 2, 2}, {6, 3, 1}}, migraphx::make_op( "slice", {{"axes", {0, 1, 2}}, {"starts", {0, 0, 1}}, {"ends", {2, 2, 3}}}), input); expect_shape(migraphx::shape{migraphx::shape::int32_type, {2, 2, 1}, {6, 3, 1}}, migraphx::make_op("slice", {{"axes", {2}}, {"starts", {2}}, {"ends", {10}}}), input); expect_shape(migraphx::shape{migraphx::shape::int32_type, {2, 2, 1}, {6, 3, 1}}, migraphx::make_op("slice", {{"axes", {2}}, {"starts", {-1}}, {"ends", {10}}}), input); } TEST_CASE(slice_var_inputs_static_shape0) { // attr ends and axes set; inputs are (data, input_starts) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {0, 4}, {0, 4}}}, migraphx::make_op("slice", {{"ends", {2, 3}}, {"axes", {1, 2}}}), input, starts); } TEST_CASE(slice_var_inputs_static_mismatch_error0) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; throws_shape( migraphx::make_op("slice", {{"ends", {2, 3, 4}}, {"axes", {0, 1, 2}}}), input, starts); } TEST_CASE(slice_var_inputs_static_shape1) { // attr starts and axes set; inputs are (data, input_ends) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {0, 4}, {0, 4}}}, migraphx::make_op("slice", {{"starts", {0, 1}}, {"axes", {1, 2}}}), input, ends); } TEST_CASE(slice_var_inputs_static_mismatch_error1) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; throws_shape( migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"axes", {0, 1, 2}}}), input, ends); } TEST_CASE(slice_var_inputs_static_shape2) { // attr starts and ends set; inputs are (data, input_axes) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, migraphx::make_op("slice", {{"starts", {0, 1}}, {"ends", {1, 2}}}), input, axes); } TEST_CASE(slice_var_inputs_static_mismatch_error2) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; throws_shape( migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"ends", {3, 4, 4}}}), input, axes); } TEST_CASE(slice_var_inputs_static_shape3) { // attr axes set; inputs are (data, input_starts, input_ends) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {0, 4}, {0, 4}}}, migraphx::make_op("slice", {{"axes", {1, 2}}}), input, starts, ends); } TEST_CASE(slice_var_inputs_static_mismatch_error3) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; throws_shape(migraphx::make_op("slice", {{"axes", {0, 1, 2}}}), input, starts, ends); } TEST_CASE(slice_var_inputs_static_shape4) { // attr ends set; inputs are (data, input_starts, input_axes) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, migraphx::make_op("slice", {{"ends", {3, 4}}}), input, starts, axes); } TEST_CASE(slice_var_inputs_static_mismatch_error4) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; throws_shape(migraphx::make_op("slice", {{"ends", {3, 3, 3}}}), input, starts, axes); } TEST_CASE(slice_var_inputs_static_shape5) { // attr starts set; inputs are (data, input_ends, input_axes) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, migraphx::make_op("slice", {{"starts", {0, 2}}}), input, ends, axes); } TEST_CASE(slice_var_inputs_static_mismatch_error5) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; throws_shape(migraphx::make_op("slice", {{"starts", {0, 1, 2}}}), input, ends, axes); } TEST_CASE(slice_var_inputs_static_shape6) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, migraphx::make_op("slice"), input, starts, ends, axes); } TEST_CASE(slice_var_inputs_static_mismatch_error6) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {3}}; throws_shape(migraphx::make_op("slice"), input, starts, ends, axes); } TEST_CASE(slice_var_inputs_dyn_shape0) { // attr ends and axes set; inputs are (data, input_starts) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 6}, {0, 6}, {0, 6}}}, migraphx::make_op("slice", {{"ends", {2, 3}}, {"axes", {1, 2}}}), input, starts); } TEST_CASE(slice_var_inputs_dyn_mismatch_error0) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; throws_shape( migraphx::make_op("slice", {{"ends", {2, 3, 4}}, {"axes", {0, 1, 2}}}), input, starts); } TEST_CASE(slice_var_inputs_dyn_shape1) { // attr starts and axes set; inputs are (data, input_ends) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 6}, {0, 6}, {0, 6}}}, migraphx::make_op("slice", {{"starts", {0, 1}}, {"axes", {1, 2}}}), input, ends); } TEST_CASE(slice_var_inputs_dyn_mismatch_error1) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; throws_shape( migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"axes", {0, 1, 2}}}), input, ends); } TEST_CASE(slice_var_inputs_dyn_shape2) { // attr starts and ends set; inputs are (data, input_axes) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 6}, {0, 6}}}, migraphx::make_op("slice", {{"starts", {0, 1}}, {"ends", {8, 8}}}), input, axes); } TEST_CASE(slice_var_inputs_dyn_mismatch_error2) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; throws_shape( migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"ends", {3, 4, 4}}}), input, axes); } TEST_CASE(slice_var_inputs_dyn_shape3) { // attr axes set; inputs are (data, input_starts, input_ends) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 6}, {0, 6}, {0, 6}}}, migraphx::make_op("slice", {{"axes", {1, 2}}}), input, starts, ends); } TEST_CASE(slice_var_inputs_dyn_mismatch_error3) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; throws_shape(migraphx::make_op("slice", {{"axes", {0, 1, 2}}}), input, starts, ends); } TEST_CASE(slice_var_inputs_dyn_shape4) { // attr ends set; inputs are (data, input_starts, input_axes) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 6}, {0, 6}}}, migraphx::make_op("slice", {{"ends", {3, 4}}}), input, starts, axes); } TEST_CASE(slice_var_inputs_dyn_mismatch_error4) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; throws_shape(migraphx::make_op("slice", {{"ends", {3, 3, 3}}}), input, starts, axes); } TEST_CASE(slice_var_inputs_dyn_shape5) { // attr starts set; inputs are (data, input_ends, input_axes) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 6}, {0, 6}}}, migraphx::make_op("slice", {{"starts", {0, 2}}}), input, ends, axes); } TEST_CASE(slice_var_inputs_dyn_mismatch_error5) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; throws_shape(migraphx::make_op("slice", {{"starts", {0, 1, 2}}}), input, ends, axes); } TEST_CASE(slice_var_inputs_dyn_shape6) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {2, 4, {2, 4}}, {2, 4, {2, 4}}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 4}, {0, 4}}}, migraphx::make_op("slice"), input, starts, ends, axes); } TEST_CASE(slice_var_inputs_dyn_mismatch_error6) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {3}}; throws_shape(migraphx::make_op("slice"), input, starts, ends, axes); } TEST_CASE(slice_dyn_shape0) { migraphx::shape input{migraphx::shape::int32_type, {{2, 3}, {7, 7}, {2, 3}}}; // Slice axis 1 to size 4-1=3 expect_shape(migraphx::shape{migraphx::shape::int32_type, {{2, 3}, {3, 3}, {2, 3}}}, migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {4}}}), input); } TEST_CASE(slice_dyn_shape1) { migraphx::shape input{migraphx::shape::int32_type, {{2, 3}, {7, 7}, {2, 3}}}; // Slice axis 1 with negative index expect_shape(migraphx::shape{migraphx::shape::int32_type, {{2, 3}, {2, 2}, {2, 3}}}, migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {-4}}}), input); } TEST_CASE(slice_dyn_shape2) { migraphx::shape input{migraphx::shape::int32_type, {{2, 3}, {7, 7}, {2, 3}}}; // Sliced range max bigger than dimension; is clipped expect_shape(migraphx::shape{migraphx::shape::int32_type, {{2, 3}, {6, 6}, {2, 3}}}, migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {10}}}), input); } TEST_CASE(slice_dyn_shape3) { // TODO: When non-fixed dimension slicing is allowed, Slice to a size smaller than min. // Until then, this action is an error. migraphx::shape input{migraphx::shape::int32_type, {{2, 3}, {7, 8}, {2, 3}}}; throws_shape(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); // clang-format off // expect_shape(migraphx::shape{migraphx::shape::int32_type, {{2, 3}, {1, 1}, {2, 3}}}, // migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), // input); // clang-format on } TEST_CASE(slice_dyn_shape4) { migraphx::shape input{migraphx::shape::int32_type, {{2, 2}, {7, 7}, {2, 3}}}; // Slice multiple axes: axis 0 to size 2-1=1 and axis 1 to size 4-1=3 expect_shape( migraphx::shape{migraphx::shape::int32_type, {{1, 1}, {3, 3}, {2, 3}}}, migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {1, 1}}, {"ends", {2, 4}}}), input); } TEST_CASE(slice_dyn_shape5) { // Axis out of range. migraphx::shape input{migraphx::shape::int32_type, {{2, 2}, {7, 7}, {2, 3}}}; throws_shape( migraphx::make_op("slice", {{"axes", {0, 20}}, {"starts", {1, 1}}, {"ends", {2, 4}}}), input); } TEST_CASE(test_scan_slice1) { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4}}; migraphx::shape axis_input{migraphx::shape::int64_type}; migraphx::shape expected{migraphx::shape::float_type, {1, 3, 4}}; expect_shape(expected, migraphx::make_op("scan_slice", {{"axis", 0}, {"direction", 0}}), input, axis_input); } TEST_CASE(test_scan_slice2) { migraphx::shape input{migraphx::shape::float_type, {4, 6, 5}}; migraphx::shape axis_input{migraphx::shape::int64_type}; migraphx::shape expected{migraphx::shape::float_type, {4, 1, 5}, {30, 5, 1}}; expect_shape(expected, migraphx::make_op("scan_slice", {{"axis", 1}, {"direction", 0}}), input, axis_input); } TEST_CASE(test_scan_slice3) { migraphx::shape input{migraphx::shape::float_type, {2, 5, 7}}; migraphx::shape axis_input{migraphx::shape::int64_type}; migraphx::shape expected{migraphx::shape::float_type, {2, 5, 1}, {35, 7, 1}}; expect_shape(expected, migraphx::make_op("scan_slice", {{"axis", -1}, {"direction", 0}}), input, axis_input); } TEST_CASE(test_scan_slice4) { migraphx::shape input{migraphx::shape::float_type, {2, 5, 7}}; migraphx::shape axis_input{migraphx::shape::int64_type}; migraphx::shape expected{migraphx::shape::float_type, {1, 5, 7}, {35, 7, 1}}; expect_shape(expected, migraphx::make_op("scan_slice", {{"axis", -3}, {"direction", 1}}), input, axis_input); } TEST_CASE(softmax_dyn0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}, {5, 5}}}; expect_shape(input, migraphx::make_op("softmax", {{"axis", 0}}), input); } TEST_CASE(softmax_dyn1) { migraphx::shape input{migraphx::shape::float_type, {{1, 1}, {3, 3}, {4, 6}, {5, 8, {6}}}}; expect_shape(input, migraphx::make_op("softmax", {{"axis", 0}}), input); } TEST_CASE(test_argmax) { { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {1, 3, 4, 5}}, migraphx::make_op("argmax", {{"axis", 0}}), input); } { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 1, 4, 5}}, migraphx::make_op("argmax", {{"axis", 1}}), input); } { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 3, 1, 5}}, migraphx::make_op("argmax", {{"axis", 2}}), input); } { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 3, 4, 1}}, migraphx::make_op("argmax", {{"axis", 3}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; throws_shape(migraphx::make_op("argmax", {{"axis", 4}}), input); } } TEST_CASE(test_argmin) { { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {1, 3, 4, 5}}, migraphx::make_op("argmin", {{"axis", 0}}), input); } { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 1, 4, 5}}, migraphx::make_op("argmin", {{"axis", 1}}), input); } { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 3, 1, 5}}, migraphx::make_op("argmin", {{"axis", 2}}), input); } { migraphx::shape input{migraphx::shape::half_type, {2, 3, 4, 5}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {2, 3, 4, 1}}, migraphx::make_op("argmin", {{"axis", 3}}), input); } { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; throws_shape(migraphx::make_op("argmin", {{"axis", 4}}), input); } } TEST_CASE(test_scalar) { migraphx::shape s1{migraphx::shape::float_type, {1}, {1}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3, 4, 5}, {0, 0, 0, 0}}; expect_shape(s2, migraphx::make_op("scalar", {{"scalar_bcst_dims", {2, 3, 4, 5}}}), s1); } TEST_CASE(test_scalar_nelemnts) { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4, 5}}; throws_shape(migraphx::make_op("scalar", {{"scalar_bcst_dims", {2, 3, 4, 5}}}), input); } TEST_CASE(test_gathernd) { { // k > r auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 4}}; migraphx::shape ds{dtype, {8}}; int batch_dims(1); throws_shape(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } { // k > r - batch_dims auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 4}}; migraphx::shape ds{dtype, {2}}; int batch_dims(1); throws_shape(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } { // batch_dims >= r auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 1}}; migraphx::shape ds{dtype, {2, 5, 6, 7}}; int batch_dims(3); throws_shape(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } { // int(q) + r - k - batch_dims - 1 = 0 => returns a scalar auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {1}}; migraphx::shape ds{dtype, {2}}; migraphx::shape s0{dtype, {1}}; expect_shape(s0, migraphx::make_op("gathernd"), ds, is); } { // See Example 4 at https://github.com/onnx/onnx/blob/main/docs/Operators.md#GatherND auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 2}}; migraphx::shape ds{dtype, {2, 2}}; migraphx::shape s0{dtype, {2}}; expect_shape(s0, migraphx::make_op("gathernd"), ds, is); } { // See Example 5 at https://github.com/onnx/onnx/blob/main/docs/Operators.md#GatherND auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 1}}; migraphx::shape ds{dtype, {2, 2, 2}}; int batch_dims(1); migraphx::shape s0{dtype, {2, 2}}; expect_shape(s0, migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } } TEST_CASE(test_gathernd_dynamic0) { // k > r auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 4}}; std::vector b{{8, 8}}; migraphx::shape ds{dtype, b}; int batch_dims(1); throws_shape(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } TEST_CASE(test_gathernd_dynamic1) { // k > r - batch_dims auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 4}}; std::vector b{{2, 2}}; migraphx::shape ds{dtype, b}; int batch_dims(1); throws_shape(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } TEST_CASE(test_gathernd_dynamic2) { // batch_dims >= r auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 1}}; migraphx::shape ds{dtype, {{2, 3, {3}}, {5, 6, {5}}, {6, 9, {7}}, {7, 8, {8}}}}; int batch_dims(3); throws_shape(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } TEST_CASE(test_gathernd_dynamic3) { // int(q) + r - k - batch_dims - 1 = 0 => returns a scalar auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {1}}; std::vector b{{2, 2}}; migraphx::shape ds{dtype, b}; migraphx::shape::dynamic_dimension ddout{1, 1}; migraphx::shape s0{dtype, {ddout}}; expect_shape(s0, migraphx::make_op("gathernd"), ds, is); } TEST_CASE(test_gathernd_dynamic4) { // See Example 1 at https://github.com/onnx/onnx/blob/main/docs/Operators.md#GatherND auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 2}}; std::vector b{{2, 2}, {2, 2}}; migraphx::shape ds{dtype, b}; migraphx::shape::dynamic_dimension ddout{2, 2}; migraphx::shape s0{dtype, {ddout}}; expect_shape(s0, migraphx::make_op("gathernd"), ds, is); } TEST_CASE(test_gathernd_dynamic5) { // See Example 5 at https://github.com/onnx/onnx/blob/main/docs/Operators.md#GatherND // index static shape, data dynamic auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 1}}; std::vector b{{2, 2}, {2, 2}, {2, 2}}; migraphx::shape ds{dtype, b}; std::vector ddout{{2, 2}, {2, 2}}; int batch_dims(1); migraphx::shape s0{dtype, {ddout}}; expect_shape(s0, migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } TEST_CASE(test_gathernd_dynamic6) { // See Example 5 at https://github.com/onnx/onnx/blob/main/docs/Operators.md#GatherND // index dynamic shape, data static auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; std::vector b{{2, 3}, {1, 1}}; migraphx::shape is{itype, b}; migraphx::shape ds{dtype, {2, 2, 2}}; std::vector ddout{{2, 3}, {2, 2}}; int batch_dims(1); migraphx::shape s0{dtype, {ddout}}; expect_shape(s0, migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } TEST_CASE(test_gathernd_dynamic6a) { // indices with non-fixed dynamic dimension k auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; std::vector b{{2, 2}, {1, 3}}; migraphx::shape is{itype, b}; migraphx::shape ds{dtype, {2, 2, 2}}; int batch_dims(1); throws_shape(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } TEST_CASE(test_gathernd_dynamic7) { // See Example 5 at https://github.com/onnx/onnx/blob/main/docs/Operators.md#GatherND // index and data both dynamic shapes auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; std::vector idyn{{2, 5}, {1, 1}}; migraphx::shape is{itype, idyn}; std::vector bdyn{{1, 2}, {1, 2}, {1, 2}}; migraphx::shape ds{dtype, bdyn}; std::vector ddout{{2, 5}, {1, 2}}; int batch_dims(1); migraphx::shape s0{dtype, {ddout}}; expect_shape(s0, migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } TEST_CASE(test_gathernd_dynamic8) { // Same shapes as ref_ops_test gathernd_dynamic // index static shape, data dynamic auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {2, 5, 1}}; std::vector b{{6, 7, {7}}, {3, 3}, {1, 4}}; migraphx::shape ds{dtype, b}; std::vector ddout{{2, 2}, {5, 5}, {1, 4}}; int batch_dims(1); migraphx::shape s0{dtype, {ddout}}; expect_shape(s0, migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), ds, is); } TEST_CASE(test_scatternd0) { // good auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; expect_shape(ds, migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd1) { // good, broadcasted auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}, {4, 0}}; migraphx::shape us{dtype, {4}}; expect_shape(ds, migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd2) { // too many inputs auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; migraphx::shape zs{dtype, {4}}; throws_shape(migraphx::make_op("scatternd_none"), ds, is, us, zs); } TEST_CASE(test_scatternd3) { // q + r - k - 1 matches upd_lens.size(), but k > r auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {5, 4, 2}}; migraphx::shape us{dtype, {4}}; throws_shape(migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd4) { // q + r - k - 1 != upd_lens.size() auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {2, 2}}; throws_shape(migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd5) { // dimensions don't match: update.lens != indices.lens[0:q-1] auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8, 3}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {2, 2}}; throws_shape(migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd_dyn0) { // one dynamic input, invalid index auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {4}}; migraphx::shape is{itype, {4, 13}}; migraphx::shape::dynamic_dimension dd{4, 4}; migraphx::shape us{dtype, {dd}}; throws_shape(migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd_dyn1) { // one dynamic input auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape::dynamic_dimension dd{4, 4}; migraphx::shape us{dtype, {dd}}; expect_shape(ds, migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd_dyn2) { // one dynamic input and broadcasted data auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 3, 1, 4}, {0, 1, 1, 0}}; migraphx::shape ds_std{dtype, {2, 3, 1, 4}}; migraphx::shape is{itype, {4, 4}}; migraphx::shape::dynamic_dimension dd{4, 4}; migraphx::shape us{dtype, {dd}}; expect_shape(ds_std, migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd_dyn3) { // one dynamic input and standard, static data auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 3, 1, 4}}; migraphx::shape is{itype, {4, 4}}; migraphx::shape::dynamic_dimension dd{4, 4}; migraphx::shape us{dtype, {dd}}; expect_shape(ds, migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd_dyn4) { // index is dynamic with last dimension not fixed auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 3, 1, 4}}; migraphx::shape::dynamic_dimension dd{4, 5}; migraphx::shape is{itype, {dd, dd}}; migraphx::shape us{dtype, {dd}}; throws_shape(migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_scatternd_dyn5) { // dimensions don't match: update.lens != indices.lens[0:q-1] auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 3, 1, 4}}; migraphx::shape::dynamic_dimension dd{4, 4}; migraphx::shape::dynamic_dimension dbad{2, 3}; migraphx::shape is{itype, {dd, dd}}; migraphx::shape us{dtype, {dbad}}; throws_shape(migraphx::make_op("scatternd_none"), ds, is, us); } TEST_CASE(test_squeeze) { migraphx::shape s1{migraphx::shape::float_type, {4, 1, 3, 1, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 1, 3, 3}}; expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {3}}}), s1); } TEST_CASE(test_squeeze_all) { migraphx::shape s1{migraphx::shape::float_type, {1}}; migraphx::shape s2{migraphx::shape::float_type}; expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {0}}}), s1); } TEST_CASE(test_squeeze_dyn) { migraphx::shape s1{migraphx::shape::float_type, {{1, 4}, {1, 1}, {3, 3}, {1, 1}, {3, 3}}}; migraphx::shape s2{migraphx::shape::float_type, {{1, 4}, {1, 1}, {3, 3}, {3, 3}}}; expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {3}}}), s1); migraphx::shape s3{migraphx::shape::float_type, {{1, 4}, {3, 3}, {3, 3}}}; expect_shape(s3, migraphx::make_op("squeeze"), s1); // allowing to squeeze dynamic_dimension that intersect with {1, 1} migraphx::shape s4{migraphx::shape::float_type, {{1, 1}, {3, 3}, {1, 1}, {3, 3}}}; expect_shape(s4, migraphx::make_op("squeeze", {{"axes", {0}}}), s1); throws_shape(migraphx::make_op("squeeze", {{"axes", {2}}}), s1); } TEST_CASE(test_squeeze_dyn_neg_axes) { migraphx::shape s1{migraphx::shape::float_type, {{1, 4}, {1, 1}, {3, 3}, {1, 1}, {3, 3}}}; migraphx::shape s2{migraphx::shape::float_type, {{1, 4}, {1, 1}, {3, 3}, {3, 3}}}; expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {-2}}}), s1); migraphx::shape s3{migraphx::shape::float_type, {{1, 4}, {3, 3}, {3, 3}}}; expect_shape(s3, migraphx::make_op("squeeze", {{"axes", {-2, -4}}}), s1); } TEST_CASE(test_squeeze_transpose) { migraphx::shape s1{migraphx::shape::float_type, {4, 4, 1}, {4, 1, 4}}; migraphx::shape s2{migraphx::shape::float_type, {4, 4}, {4, 1}}; expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {2}}}), s1); } TEST_CASE(test_squeeze_multibroadcast) { migraphx::shape s1{migraphx::shape::float_type, {2, 3, 1, 4}, {0, 1, 1, 0}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3, 4}, {0, 1, 0}}; expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {2}}}), s1); } TEST_CASE(test_squeeze_slice) { migraphx::shape s1{migraphx::shape::float_type, {2, 3, 1, 4}, {108, 36, 6, 1}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3, 4}, {108, 36, 1}}; expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {2}}}), s1); } TEST_CASE(test_squeeze_negative_axis) { migraphx::shape s1{migraphx::shape::float_type, {4, 1, 3, 1, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 1, 3, 3}}; expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {-2}}}), s1); } TEST_CASE(test_squeeze_wrong_axis) { migraphx::shape s1{migraphx::shape::float_type, {4, 1, 3, 1, 3}}; throws_shape(migraphx::make_op("squeeze", {{"axes", {0}}}), s1); } TEST_CASE(test_unique_axis_invalid) { migraphx::shape x_shape{migraphx::shape::float_type, {10, 4, 3}}; throws_shape(migraphx::make_op("unique", {{"axis", -1}}), x_shape); } TEST_CASE(test_unique_axis_negative) { migraphx::shape x_shape{migraphx::shape::float_type, {10, 4, 3}}; std::vector y_dims{{1, 10}, {4, 4}, {3, 3}}; std::vector idx_dims{{1, 10}}; std::vector y_dyn_shape{{migraphx::shape::float_type, y_dims}, {migraphx::shape::int64_type, idx_dims}, {migraphx::shape::int64_type, idx_dims}, {migraphx::shape::int64_type, idx_dims}}; expect_shape( migraphx::shape(y_dyn_shape), migraphx::make_op("unique", {{"axis", -3}}), x_shape); } TEST_CASE(test_unique_axis_none) { migraphx::shape x_shape{migraphx::shape::half_type, {10, 4, 3}}; std::vector y_dims{{1, 120}}; std::vector idx_dims{{1, 120}}; std::vector y_dyn_shape{{migraphx::shape::half_type, y_dims}, {migraphx::shape::int64_type, idx_dims}, {migraphx::shape::int64_type, idx_dims}, {migraphx::shape::int64_type, idx_dims}}; expect_shape(migraphx::shape(y_dyn_shape), migraphx::make_op("unique"), x_shape); } TEST_CASE(test_unsqueeze) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 5, 1, 3}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}}), s1); } TEST_CASE(test_unsqueeze_dyn) { migraphx::shape s1{migraphx::shape::float_type, {{1, 4, {3}}, {2, 5}, {3, 3}}}; migraphx::shape s2{migraphx::shape::float_type, {{1, 4, {3}}, {2, 5}, {1, 1}, {3, 3}}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}}), s1); migraphx::shape s3{migraphx::shape::float_type, {{1, 4, {3}}, {2, 5}, {1, 1}, {3, 3}, {1, 1}}}; expect_shape(s3, migraphx::make_op("unsqueeze", {{"axes", {2, 4}}}), s1); throws_shape(migraphx::make_op("unsqueeze", {{"axes", {2, 4}}, {"steps", {2}}}), s1); } TEST_CASE(test_unsqueeze_dyn_neg_axes) { migraphx::shape s1{migraphx::shape::float_type, {{1, 4, {3}}, {2, 5}, {3, 3}}}; migraphx::shape s2{migraphx::shape::float_type, {{1, 4, {3}}, {2, 5}, {1, 1}, {3, 3}}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {-2}}}), s1); migraphx::shape s3{migraphx::shape::float_type, {{1, 4, {3}}, {2, 5}, {1, 1}, {3, 3}, {1, 1}}}; expect_shape(s3, migraphx::make_op("unsqueeze", {{"axes", {-1, -3}}}), s1); } TEST_CASE(test_unsqueeze_step) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 12}}; migraphx::shape s2{migraphx::shape::float_type, {4, 5, 2, 6}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), s1); } TEST_CASE(test_unsqueeze_step_non_divisable) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 3}}; throws_shape(migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), s1); } TEST_CASE(test_unsqueeze_step_zero) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 12}}; throws_shape(migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {0}}}), s1); } TEST_CASE(test_unsqueeze_step_at_end) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 12}}; throws_shape(migraphx::make_op("unsqueeze", {{"axes", {3}}, {"steps", {2}}}), s1); } TEST_CASE(test_unsqueeze_mismatch_step_axis) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 12}}; throws_shape(migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2, 3}}}), s1); } TEST_CASE(test_unsqueeze_negative_axis1) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 5, 1, 3}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {-2}}}), s1); } TEST_CASE(test_unsqueeze_negative_axis2) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 5, 3, 1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {-1}}}), s1); } TEST_CASE(test_unsqueeze_negative_axis3) { migraphx::shape s1{migraphx::shape::float_type, {4, 5, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 1, 5, 3}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {-3}}}), s1); } TEST_CASE(test_unsqueeze_scalar) { migraphx::shape s1{migraphx::shape::float_type, {1}, {0}}; migraphx::shape s2{migraphx::shape::float_type, {1}, {1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {0}}}), s1); } TEST_CASE(test_unsqueeze_scalar_tensor1) { migraphx::shape s1{migraphx::shape::float_type, {4, 3, 3}, {0, 0, 0}}; migraphx::shape s2{migraphx::shape::float_type, {4, 3, 1, 3}, {0, 0, 1, 0}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {-2}}}), s1); } TEST_CASE(test_unsqueeze_scalar_tensor2) { migraphx::shape s1{migraphx::shape::float_type, {1, 1, 1}, {0, 0, 0}}; migraphx::shape s2{migraphx::shape::float_type, {1, 1, 1, 1}, {0, 0, 0, 1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {-1}}}), s1); } TEST_CASE(test_unsqueeze_scalar_step) { migraphx::shape s{migraphx::shape::float_type, {6, 1, 2}, {0, 0, 0}}; throws_shape(migraphx::make_op("unsqueeze", {{"axes", {0}}, {"steps", {3}}}), s); } TEST_CASE(test_unsqueeze_transpose) { migraphx::shape s1{migraphx::shape::float_type, {4, 4, 3}, {12, 1, 4}}; migraphx::shape s2{migraphx::shape::float_type, {4, 4, 1, 3}, {12, 1, 12, 4}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}}), s1); } TEST_CASE(test_unsqueeze_transpose_step) { migraphx::shape s1{migraphx::shape::float_type, {4, 4, 6}, {24, 1, 4}}; migraphx::shape s2{migraphx::shape::float_type, {4, 4, 2, 3}, {24, 1, 12, 4}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), s1); } TEST_CASE(test_unsqueeze_multibroadcast) { migraphx::shape s1{migraphx::shape::float_type, {2, 3, 4}, {0, 1, 0}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3, 1, 4}, {0, 1, 0, 0}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}}), s1); } TEST_CASE(test_unsqueeze_slice) { migraphx::shape s1{migraphx::shape::float_type, {2, 3, 4}, {108, 36, 1}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3, 1, 4}, {108, 36, 4, 1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}}), s1); } TEST_CASE(test_unsqueeze_axis_zero) { migraphx::shape s1{migraphx::shape::float_type, {2, 3, 4}}; migraphx::shape s2{migraphx::shape::float_type, {1, 2, 3, 4}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {0}}}), s1); } TEST_CASE(test_unsqueeze_axis_last) { migraphx::shape s1{migraphx::shape::float_type, {2, 3, 4}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3, 4, 1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {-1}}}), s1); } TEST_CASE(test_unsqueeze_multiple_axes_1) { migraphx::shape s1{migraphx::shape::float_type, {2, 3, 4}}; migraphx::shape s2{migraphx::shape::float_type, {1, 2, 3, 4, 1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {0, -1}}}), s1); } TEST_CASE(test_unsqueeze_multiple_axes_2) { migraphx::shape s1{migraphx::shape::float_type, {2, 3, 4}}; migraphx::shape s2{migraphx::shape::float_type, {1, 1, 2, 3, 4}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {0, 1}}}), s1); } TEST_CASE(test_unsqueeze_multiple_axes_3) { migraphx::shape s1{migraphx::shape::float_type, {3, 4, 5}}; migraphx::shape s2{migraphx::shape::float_type, {3, 4, 1, 5, 1, 1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2, 4, 5}}}), s1); } TEST_CASE(test_unsqueeze_multiple_axes_4) { migraphx::shape s1{migraphx::shape::float_type, {3, 4, 5}}; migraphx::shape s2{migraphx::shape::float_type, {3, 4, 1, 5, 1, 1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {5, 4, 2}}}), s1); } TEST_CASE(test_unsqueeze_multiple_axes_step) { migraphx::shape s1{migraphx::shape::float_type, {3, 4, 10}}; migraphx::shape s2{migraphx::shape::float_type, {3, 4, 2, 5, 1, 1}}; expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2, 4, 5}}, {"steps", {2}}}), s1); } TEST_CASE(transpose_shape) { migraphx::shape input{migraphx::shape::float_type, {2, 2}}; migraphx::shape output{migraphx::shape::float_type, {2, 2}, {1, 2}}; expect_shape(input, migraphx::make_op("transpose", {{"permutation", {0, 1}}}), input); expect_shape(output, migraphx::make_op("transpose", {{"permutation", {1, 0}}}), input); throws_shape(migraphx::make_op("transpose", {{"permutation", {1, 2}}}), input); } TEST_CASE(transpose_dyn_shape0) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {2, 2}}}; migraphx::shape output{migraphx::shape::float_type, {{2, 2}, {1, 4}}}; expect_shape(input, migraphx::make_op("transpose", {{"permutation", {0, 1}}}), input); expect_shape(output, migraphx::make_op("transpose", {{"permutation", {1, 0}}}), input); } TEST_CASE(transpose_dyn_shape1) { migraphx::shape input{migraphx::shape::float_type, {{1, 4}, {4, 4}, {4, 4}}}; migraphx::shape output{migraphx::shape::float_type, {{4, 4}, {4, 4}, {1, 4}}}; expect_shape(input, migraphx::make_op("transpose", {{"permutation", {0, 1, 2}}}), input); expect_shape(output, migraphx::make_op("transpose", {{"permutation", {2, 1, 0}}}), input); } TEST_CASE(transpose_axes_error) { migraphx::shape input{migraphx::shape::float_type, {2, 2}}; throws_shape(migraphx::make_op("transpose", {{"permutation", {1}}}), input); } TEST_CASE(step_test) { migraphx::shape s1{migraphx::shape::float_type, {1, 2, 4}}; { migraphx::shape s2{migraphx::shape::float_type, {1, 1, 2}, {8, 8, 3}}; expect_shape(s2, migraphx::make_op("step", {{"axes", {1, 2}}, {"steps", {2, 3}}}), s1); } { migraphx::shape s{migraphx::shape::float_type, {1, 2, 4}}; throws_shape(migraphx::make_op("step", {{"axes", {1, 2}}, {"steps", {1}}}), s1); } { migraphx::shape s{migraphx::shape::float_type, {1, 2, 4}}; throws_shape(migraphx::make_op("step", {{"axes", {2, 3}}, {"steps", {2, 3}}}), s1); } } TEST_CASE(unary_scalar_input) { migraphx::shape ss{migraphx::shape::half_type}; expect_shape(ss, migraphx::make_op("sin"), ss); migraphx::shape s{migraphx::shape::float_type, {1}}; expect_shape(s, migraphx::make_op("sin"), s); } TEST_CASE(unary_broadcast_input) { migraphx::shape ss{migraphx::shape::half_type, {2, 3}, {1, 0}}; migraphx::shape s{migraphx::shape::half_type, {2, 3}}; expect_shape(s, migraphx::make_op("sin"), ss); } TEST_CASE(where_broadcast_input) { migraphx::shape s1{migraphx::shape::float_type, {2, 2}, {3, 0}}; migraphx::shape s2{migraphx::shape::float_type, {2, 2}}; migraphx::shape s3{migraphx::shape::bool_type, {2, 2}}; expect_shape(s2, migraphx::make_op("where"), s3, s1, s2); } TEST_CASE(where_dyn_input0) { // dynamic shapes not the same migraphx::shape s1{migraphx::shape::float_type, {{2, 3}, {3, 3}}}; migraphx::shape s2{migraphx::shape::float_type, {{2, 3}, {2, 3}}}; migraphx::shape s3{migraphx::shape::bool_type, {2, 2}}; throws_shape(migraphx::make_op("where"), s3, s1, s2); } TEST_CASE(where_dyn_input1) { // mixed static/dynamic inputs (not allowed) migraphx::shape s1{migraphx::shape::float_type, {2, 2}, {2, 1}}; migraphx::shape s2{migraphx::shape::float_type, {{2, 2}, {2, 2}}}; migraphx::shape s3{migraphx::shape::bool_type, {2, 2}, {2, 1}}; throws_shape(migraphx::make_op("where"), s3, s1, s2); } TEST_CASE(where_dyn_input2) { // dynamic shapes migraphx::shape s1{migraphx::shape::float_type, {{2, 3}, {3, 3}}}; migraphx::shape s2{migraphx::shape::float_type, {{2, 3}, {3, 3}}}; migraphx::shape s3{migraphx::shape::bool_type, {{2, 3}, {3, 3}}}; expect_shape(s2, migraphx::make_op("where"), s3, s1, s2); } TEST_CASE(where_dyn_input3) { // dynamic shapes, predicate shape is different migraphx::shape s1{migraphx::shape::float_type, {{2, 3}, {3, 3}}}; migraphx::shape s2{migraphx::shape::float_type, {{2, 3}, {3, 3}}}; migraphx::shape s3{migraphx::shape::bool_type, {{2, 3}, {3, 4}}}; throws_shape(migraphx::make_op("where"), s3, s1, s2); } TEST_CASE(roialign_test) { migraphx::shape sx{migraphx::shape::float_type, {3, 4, 5, 6}}; migraphx::shape srois{migraphx::shape::float_type, {2, 4}}; migraphx::shape sbi{migraphx::shape::int64_type, {2}}; migraphx::shape sout{migraphx::shape::float_type, {2, 4, 1, 1}}; expect_shape(sout, migraphx::make_op("roialign"), sx, srois, sbi); migraphx::shape sbi1{migraphx::shape::int64_type, {2, 3}}; throws_shape(migraphx::make_op("roialign"), sx, srois, sbi1); migraphx::shape sbi2{migraphx::shape::int64_type, {3}}; throws_shape(migraphx::make_op("roialign"), sx, srois, sbi2); migraphx::shape srois1{migraphx::shape::float_type, {2, 4, 3}}; throws_shape(migraphx::make_op("roialign"), sx, srois1, sbi); migraphx::shape srois2{migraphx::shape::float_type, {2, 3}}; throws_shape(migraphx::make_op("roialign"), sx, srois2, sbi); } TEST_CASE(test_concat) { migraphx::shape sx{migraphx::shape::float_type, {3, 4, 5, 6}}; migraphx::shape sy{migraphx::shape::float_type, {3, 4, 1, 6}}; migraphx::shape sout{migraphx::shape::float_type, {3, 4, 6, 6}}; expect_shape(sout, migraphx::make_op("concat", {{"axis", 2}}), sx, sy); // axis out of range throws_shape(migraphx::make_op("concat", {{"axis", 11}}), sx, sy); // 1 input; no-op expect_shape(sx, migraphx::make_op("concat", {{"axis", 2}}), sx); // rank doesn't match migraphx::shape sbi1{migraphx::shape::int64_type, {2, 3}}; throws_shape(migraphx::make_op("concat", {{"axis", 0}}), sx, sbi1); // non-matching dimension 2 throws_shape(migraphx::make_op("concat", {{"axis", 1}}), sx, sy); // no input shapes (at least one is required) throws_shape(migraphx::make_op("concat", {{"axis", 0}})); } TEST_CASE(test_dyn_concat) { migraphx::shape sx{migraphx::shape::float_type, {{1, 3, {3}}, {4, 4}, {1, 5, {5}}, {6, 6}}}; migraphx::shape sy{migraphx::shape::float_type, {{1, 3, {3}}, {4, 4}, {1, 4, {4}}, {6, 6}}}; migraphx::shape sout{migraphx::shape::float_type, {{1, 3, {3}}, {4, 4}, {2, 9}, {6, 6}}}; expect_shape(sout, migraphx::make_op("concat", {{"axis", 2}}), sx, sy); // axis out of range throws_shape(migraphx::make_op("concat", {{"axis", 4}}), sx, sy); // rank doesn't match migraphx::shape srank{migraphx::shape::int64_type, {{1, 3, {3}}, {4, 4}}}; throws_shape(migraphx::make_op("concat", {{"axis", 0}}), sx, srank); // non-matching dimension 2 throws_shape(migraphx::make_op("concat", {{"axis", 1}}), sx, sy); // static and dynamic shapes together migraphx::shape sstat{migraphx::shape::float_type, {3, 4, 1, 6}}; throws_shape(migraphx::make_op("concat", {{"axis", 2}}), sx, sstat); } TEST_CASE(test_binary_nonpacked) { auto sx = migraphx::shape(migraphx::shape::float_type, {4, 3}, {1, 8}); auto sy = migraphx::shape(migraphx::shape::float_type, {4, 3}, {1, 16}); auto sout = migraphx::shape::from_permutation(migraphx::shape::float_type, {4, 3}, {1, 0}); expect_shape(sout, migraphx::make_op("mul"), sx, sy); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/operation.cpp000066400000000000000000000165761510465702400202420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" struct simple_operation { template static auto reflect(T& x, F f) { return migraphx::pack(f(x.data, "data")); } int data = 1; std::string name() const { return "simple"; } migraphx::shape compute_shape(const std::vector&) const { MIGRAPHX_THROW("not computable"); } migraphx::argument compute(migraphx::context&, const migraphx::shape&, const std::vector&) const { MIGRAPHX_THROW("not computable"); } friend std::ostream& operator<<(std::ostream& os, const simple_operation& op) { os << op.name() << "[" << op.data << "]"; return os; } }; struct simple_operation_no_print { std::string name() const { return "simple"; } migraphx::shape compute_shape(const std::vector&) const { MIGRAPHX_THROW("not computable"); } migraphx::argument compute(migraphx::context&, const migraphx::shape&, const std::vector&) const { MIGRAPHX_THROW("not computable"); } }; struct compilable_op { std::string name() const { return "compilable"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } int output_alias(const std::vector&) const { return 0; } migraphx::value compile(migraphx::context&, const migraphx::shape&, const std::vector&) { return {{"compiled", true}}; } }; TEST_CASE(operation_copy_test) { simple_operation s{}; migraphx::operation op1 = s; // NOLINT migraphx::operation op2 = op1; // NOLINT // cppcheck-suppress duplicateExpression EXPECT(s == op1); // cppcheck-suppress duplicateExpression EXPECT(op2 == op1); } TEST_CASE(operation_copy_assign_test) { simple_operation s{}; migraphx::operation op; op = s; // cppcheck-suppress duplicateExpression EXPECT(s == op); } TEST_CASE(operation_equal_test) { simple_operation s{}; migraphx::operation op1 = s; s.data = 2; migraphx::operation op2 = op1; // NOLINT migraphx::operation op3 = s; // NOLINT EXPECT(s != op1); EXPECT(op2 == op1); EXPECT(op3 != op2); EXPECT(op3 != op1); } struct not_operation { }; TEST_CASE(operation_any_cast) { migraphx::operation op1 = simple_operation{}; EXPECT(migraphx::any_cast(op1).data == 1); EXPECT(migraphx::any_cast(&op1) == nullptr); EXPECT(test::throws([&] { migraphx::any_cast(op1); })); migraphx::operation op2 = simple_operation{2}; EXPECT(migraphx::any_cast(op2).data == 2); EXPECT(migraphx::any_cast(&op2) == nullptr); } TEST_CASE(operation_print) { migraphx::operation op = simple_operation{}; std::stringstream ss; ss << op; std::string s = ss.str(); EXPECT(s == "simple[1]"); } TEST_CASE(operation_default_print) { migraphx::operation op = simple_operation_no_print{}; std::stringstream ss; ss << op; std::string s = ss.str(); EXPECT(s == "simple"); } struct final_operation { std::string name() const { return "final"; } migraphx::shape compute_shape(const std::vector&) const { MIGRAPHX_THROW("not computable"); } void finalize(migraphx::context&, const migraphx::shape&, const std::vector&) const { } }; struct final_operation_throw { std::string name() const { return "final"; } migraphx::shape compute_shape(const std::vector&) const { MIGRAPHX_THROW("not computable"); } [[gnu::noreturn]] void finalize(migraphx::context&, const migraphx::shape&, const std::vector&) const { MIGRAPHX_THROW("finalize"); } }; TEST_CASE(check_has_finalize_simple) { migraphx::operation op = simple_operation{}; EXPECT(not migraphx::has_finalize(op)); } TEST_CASE(check_has_finalize) { migraphx::operation op = final_operation{}; EXPECT(migraphx::has_finalize(op)); } TEST_CASE(check_run_finalize) { migraphx::operation op = final_operation{}; migraphx::context ctx{}; op.finalize(ctx, {}, {}); } TEST_CASE(check_run_finalize_simple) { migraphx::operation op = simple_operation{}; migraphx::context ctx{}; op.finalize(ctx, {}, {}); } TEST_CASE(check_run_finalize_throw) { migraphx::operation op = final_operation_throw{}; migraphx::context ctx{}; EXPECT(test::throws([&] { op.finalize(ctx, {}, {}); })); } TEST_CASE(check_to_value1) { migraphx::operation op = simple_operation{}; auto v = op.to_value(); EXPECT(v == migraphx::value{{"data", 1}}); } TEST_CASE(check_to_value2) { migraphx::operation op = simple_operation{}; auto v = migraphx::to_value(op); EXPECT(v == migraphx::value{{"name", "simple"}, {"operator", {{"data", 1}}}}); } TEST_CASE(check_from_value1) { migraphx::operation op1 = simple_operation{}; migraphx::operation op2 = simple_operation{3}; op1.from_value({{"data", 3}}); EXPECT(op1 == op2); } TEST_CASE(check_from_value2) { migraphx::operation op1 = migraphx::from_value({{"data", 3}}); migraphx::operation op2 = simple_operation{3}; EXPECT(op1 == op2); } TEST_CASE(compile) { migraphx::operation op = compilable_op{}; migraphx::context ctx{}; auto v = op.compile(ctx, {}, {}); EXPECT(v.at("compiled").to() == true); } TEST_CASE(compile_non_compilable) { migraphx::operation op = simple_operation{}; migraphx::context ctx{}; auto v = op.compile(ctx, {}, {}); EXPECT(v.empty()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/operators.cpp000066400000000000000000000131031510465702400202370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include "test.hpp" TEST_CASE(load_op) { for(const auto& name : migraphx::get_operators()) { auto op = migraphx::load_op(name); CHECK(op.name() == name); } } TEST_CASE(make_op) { for(const auto& name : migraphx::get_operators()) { auto op = migraphx::load_op(name); CHECK(op == migraphx::make_op(name)); } } TEST_CASE(save_op) { for(const auto& name : migraphx::get_operators()) { auto op1 = migraphx::load_op(name); auto v = migraphx::to_value(op1); auto op2 = migraphx::from_value(v); CHECK(op1 == op2); } } TEST_CASE(make_op_from_value1) { migraphx::operation x = migraphx::make_op( "convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {2, 2}}}); migraphx::operation y = migraphx::make_op( "convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {2, 2}}}); EXPECT(x == y); } TEST_CASE(make_op_from_value2) { migraphx::operation x = migraphx::make_op("convolution", {{"padding", {1, 1}}}); migraphx::operation y = migraphx::make_op("convolution", {{"padding", {1, 1}}}); EXPECT(x == y); } TEST_CASE(make_rnn_op_from_value) { migraphx::op::rnn_direction dirct = migraphx::op::rnn_direction::reverse; migraphx::operation x = migraphx::make_op( "rnn_var_sl_shift_output", {{"output_name", "hidden_states"}, {"direction", dirct}}); migraphx::operation y = migraphx::make_op( "rnn_var_sl_shift_output", {{"output_name", "hidden_states"}, {"direction", migraphx::to_value(dirct)}}); EXPECT(x == y); } TEST_CASE(make_op_invalid_key) { EXPECT(test::throws([] { migraphx::make_op("convolution", {{"paddings", {1, 1}}}); })); } TEST_CASE(load_offset) { migraphx::shape s{migraphx::shape::float_type, {4}}; migraphx::shape bs{migraphx::shape::int8_type, {32}}; auto op = migraphx::make_op("load", {{"offset", 4}, {"shape", migraphx::to_value(s)}}); EXPECT(op.compute_shape({bs}) == s); migraphx::argument a{bs}; EXPECT(op.compute(bs, {a}).data() == a.data() + 4); } TEST_CASE(load_out_of_bounds) { migraphx::shape s{migraphx::shape::float_type, {4}}; migraphx::shape bs{migraphx::shape::int8_type, {16}}; auto op = migraphx::make_op("load", {{"offset", 4}, {"shape", migraphx::to_value(s)}}); migraphx::argument a{bs}; EXPECT(test::throws([&] { op.compute(bs, {a}); })); } TEST_CASE(load_tuple) { migraphx::shape s{{migraphx::shape{migraphx::shape::int8_type, {3}}, migraphx::shape{migraphx::shape::float_type, {4}}}}; migraphx::shape bs{migraphx::shape::int8_type, {32}}; auto op = migraphx::make_op("load", {{"offset", 4}, {"shape", migraphx::to_value(s)}}); EXPECT(op.compute_shape({bs}) == s); migraphx::argument a{bs}; auto r = op.compute(bs, {a}); EXPECT(r.get_sub_objects().size() == 2); auto* start = a.data() + 4; EXPECT(r.get_sub_objects()[0].data() == start + 16); EXPECT(r.get_sub_objects()[1].data() == start); } TEST_CASE(ops) { auto names = migraphx::get_operators(); EXPECT(names.size() > 1); } TEST_CASE(rnn) { migraphx::shape s{migraphx::shape::float_type, {2, 1}}; std::vector data1(2, 2.0f); std::vector data2(2, 3.0f); migraphx::argument a1(s, data1.data()); migraphx::argument a2(s, data2.data()); auto op = migraphx::make_op("rnn"); EXPECT(test::throws([&] { op.compute(s, {a1, a2}); })); } TEST_CASE(if_op) { migraphx::shape s{migraphx::shape::bool_type, {1}}; std::vector data = {1}; migraphx::argument cond(s, data.data()); migraphx::shape sd{migraphx::shape::float_type, {2, 1}}; std::vector data1(2, 2.0f); std::vector data2(2, 3.0f); migraphx::argument a1(sd, data1.data()); migraphx::argument a2(sd, data2.data()); migraphx::module m("name"); auto l = m.add_literal(migraphx::literal(sd, data1)); m.add_return({l}); auto op = migraphx::make_op("add"); EXPECT(test::throws([&] { op.compute(s, {cond, a1, a2}, {&m, &m}, {}); })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/optimize_module_test.cpp000066400000000000000000000200651510465702400224720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::optimize_module{}}); } TEST_CASE(broadcast_transpose_inner_broadcast) { // first optimizes broadcast+transpose to just a broadcast, // then finds inner broadcast to become mul+broadcast migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto y = m1.add_parameter("y", {migraphx::shape::float_type, {1}, {0}}); auto mb1 = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3}}}), x); auto mb2 = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 2}}}), y); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), mb1); auto mul = m1.add_instruction(migraphx::make_op("mul"), mb2, t1); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto y = m2.add_parameter("y", {migraphx::shape::float_type, {1}, {0}}); auto mul = m2.add_instruction(migraphx::make_op("mul"), y, x); auto mb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 2}}}), mul); m2.add_return({mb}); } EXPECT(m1 == m2); } TEST_CASE(broadcast_transpose_inner_broadcast_generic) { // first optimizes broadcast+transpose to unsqueeze+transpose+broadcast, // then finds inner broadcast to become mul+broadcast migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {5, 10}}); auto y = m1.add_parameter("y", {migraphx::shape::float_type, {5}}); auto mb1 = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 5, 10}}}), x); auto mb2 = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 10, 5}}}), y); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), mb2); auto mul = m1.add_instruction(migraphx::make_op("mul"), mb1, t1); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {5, 10}}); auto y = m2.add_parameter("y", {migraphx::shape::float_type, {5}}); auto yb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {5, 10}}}), y); auto mul = m2.add_instruction(migraphx::make_op("mul"), x, yb); auto mb2 = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 5, 10}}}), mul); m2.add_return({mb2}); } EXPECT(m1 == m2); } TEST_CASE(mul_add_transpose_dot) { auto lit1 = migraphx::generate_literal({migraphx::shape::float_type, {64}}, 0); auto lit2 = migraphx::generate_literal({migraphx::shape::float_type, {64}}, 1); auto lit3 = migraphx::generate_literal({migraphx::shape::float_type, {64, 64}}, 2); migraphx::module m1; { auto in1 = m1.add_parameter("x", {migraphx::shape::float_type, {2, 64, 4, 4}}); auto lit1_ins = m1.add_literal(lit1); auto lit1_unsq = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), lit1_ins); auto lit1_mb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 64, 4, 4}}}), lit1_unsq); auto mul = m1.add_instruction(migraphx::make_op("mul"), lit1_mb, in1); auto lit2_ins = m1.add_literal(lit2); auto lit2_unsq = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), lit2_ins); auto lit2_tp = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), lit2_unsq); auto lit2_mb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 4, 64}}}), lit2_tp); auto mul_tp = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), mul); auto add = m1.add_instruction(migraphx::make_op("add"), mul_tp, lit2_mb); auto lit3_ins = m1.add_literal(lit3); auto lit3_mb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 64, 64}}}), lit3_ins); auto dot = m1.add_instruction(migraphx::make_op("dot"), add, lit3_mb); m1.add_return({dot}); } run_pass(m1); // Compute const propagated literals migraphx::literal lit13; migraphx::literal lit23; migraphx::module lit_mod; { auto lit1_ins = lit_mod.add_literal(lit1); auto lit1_b = lit_mod.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {64, 64}}}), lit1_ins); auto lit3_ins = lit_mod.add_literal(lit3); auto mul_lit = lit_mod.add_instruction(migraphx::make_op("mul"), lit1_b, lit3_ins); auto lit13_arg = mul_lit->eval(); lit13 = migraphx::literal(lit13_arg.get_shape(), lit13_arg.data()); auto lit2_ins = lit_mod.add_literal(lit2); auto lit2_unsq = lit_mod.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), lit2_ins); auto lit2_mb = lit_mod.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {4, 64}}}), lit2_unsq); auto dot_lit = lit_mod.add_instruction(migraphx::make_op("dot"), lit2_mb, lit3_ins); auto lit23_arg = dot_lit->eval(); lit23 = migraphx::literal(lit23_arg.get_shape(), lit23_arg.data()); } migraphx::module m2; { auto in1 = m2.add_parameter("x", {migraphx::shape::float_type, {2, 64, 4, 4}}); auto in_tp = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), in1); auto lit13_ins = m2.add_literal(lit13); auto lit13_b = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 64, 64}}}), lit13_ins); auto dot = m2.add_instruction(migraphx::make_op("dot"), in_tp, lit13_b); auto lit23_ins = m2.add_literal(lit23); auto lit23_mb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 4, 64}}}), lit23_ins); auto add = m2.add_instruction(migraphx::make_op("add"), dot, lit23_mb); m2.add_return({add}); } EXPECT(m1.sort() == m2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/output_alias.cpp000066400000000000000000000046401510465702400207400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include TEST_CASE(simple_alias) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(1); auto p1 = mm->add_instruction(pass_op{}, l); EXPECT(migraphx::instruction::get_output_alias(l) == l); EXPECT(migraphx::instruction::get_output_alias(p1) == l); } TEST_CASE(cascade_alias) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(1); auto p1 = mm->add_instruction(pass_op{}, l); auto p2 = mm->add_instruction(pass_op{}, p1); auto p3 = mm->add_instruction(pass_op{}, p2); EXPECT(migraphx::instruction::get_output_alias(l) == l); EXPECT(migraphx::instruction::get_output_alias(p1) == l); EXPECT(migraphx::instruction::get_output_alias(p2) == l); EXPECT(migraphx::instruction::get_output_alias(p3) == l); } TEST_CASE(no_alias) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(1); auto y = mm->add_literal(2); auto sum = mm->add_instruction(sum_op{}, x, y); EXPECT(migraphx::instruction::get_output_alias(sum) == sum); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/pad_calc_test.cpp000066400000000000000000000061261510465702400210150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include "test.hpp" TEST_CASE(pad_calc_test_no_pad) { std::vector golden_pads{0, 0, 0, 0}; std::vector pads{0, 0, 0, 0}; // 1x1 filter size migraphx::calculate_padding(0, pads, 16, 1, 1, 1); migraphx::calculate_padding(1, pads, 16, 1, 1, 1); EXPECT(pads == golden_pads); } TEST_CASE(pad_calc_test_pad_by_1) { std::vector golden_pads{1, 1, 1, 1}; std::vector pads{0, 0, 0, 0}; // 3x3 filter size migraphx::calculate_padding(0, pads, 16, 1, 1, 3); migraphx::calculate_padding(1, pads, 16, 1, 1, 3); EXPECT(pads == golden_pads); } TEST_CASE(pad_calc_test_pad_by_1_asym_2x2_filter) { std::vector golden_pads{0, 0, 1, 1}; std::vector pads{0, 0, 0, 0}; // 2x2 filter size migraphx::calculate_padding(0, pads, 16, 1, 1, 2); migraphx::calculate_padding(1, pads, 16, 1, 1, 2); EXPECT(pads == golden_pads); } TEST_CASE(pad_calc_test_pad_by_2) { std::vector golden_pads{2, 2, 2, 2}; std::vector pads{0, 0, 0, 0}; // 5x5 filter size migraphx::calculate_padding(0, pads, 16, 1, 1, 5); migraphx::calculate_padding(1, pads, 16, 1, 1, 5); EXPECT(pads == golden_pads); } TEST_CASE(pad_calc_test_pad_by_1_asym_stride_2) { std::vector golden_pads{0, 0, 1, 1}; std::vector pads{0, 0, 0, 0}; // 3x3 filter size migraphx::calculate_padding(0, pads, 16, 2, 1, 3); migraphx::calculate_padding(1, pads, 16, 2, 1, 3); EXPECT(pads == golden_pads); } TEST_CASE(pad_calc_test_dilation_2) { std::vector golden_pads{2, 2, 2, 2}; std::vector pads{0, 0, 0, 0}; // 3x3 filter size migraphx::calculate_padding(0, pads, 16, 1, 2, 3); migraphx::calculate_padding(1, pads, 16, 1, 2, 3); EXPECT(pads == golden_pads); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/param_utils.cpp000066400000000000000000000054741510465702400205550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include TEST_CASE(test_param_name) { CHECK(migraphx::param_name(0) == "x0"); CHECK(migraphx::param_name(1) == "x1"); CHECK(migraphx::param_name(10) == "x:00010"); CHECK(migraphx::param_name(11) == "x:00011"); CHECK(migraphx::param_name(100) == "x:00100"); CHECK(migraphx::param_name(101) == "x:00101"); CHECK(migraphx::param_name(10011) == "x:10011"); CHECK(migraphx::param_name(99999) == "x:99999"); CHECK(test::throws([] { migraphx::param_name(100000); })); CHECK(test::throws([] { migraphx::param_name(100001); })); } TEST_CASE(test_param_name_sorted) { auto pname = [](std::size_t i) { return migraphx::param_name(i); }; std::vector names; migraphx::transform(migraphx::range(8, 25), std::back_inserter(names), pname); migraphx::transform(migraphx::range(90, 130), std::back_inserter(names), pname); migraphx::transform(migraphx::range(990, 1030), std::back_inserter(names), pname); migraphx::transform(migraphx::range(9990, 10030), std::back_inserter(names), pname); migraphx::transform(migraphx::range(99990, 100000), std::back_inserter(names), pname); CHECK(std::is_sorted(names.begin(), names.end())); auto xnames = names; // Shuffled std::shuffle(xnames.begin(), xnames.end(), std::minstd_rand{}); std::sort(xnames.begin(), xnames.end()); EXPECT(xnames == names); // Reversed std::reverse(xnames.begin(), xnames.end()); std::sort(xnames.begin(), xnames.end()); EXPECT(xnames == names); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/perf_report.cpp000066400000000000000000000042421510465702400205540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include "test.hpp" TEST_CASE(perf_report) { migraphx::program p; auto* mm = p.get_main_module(); std::stringstream ss; auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); p.compile(migraphx::make_target("ref")); p.perf_report(ss, 2, {}); std::string output = ss.str(); EXPECT(migraphx::contains(output, "Summary:")); EXPECT(migraphx::contains(output, "Batch size:")); EXPECT(migraphx::contains(output, "Rate:")); EXPECT(migraphx::contains(output, "Total time:")); EXPECT(migraphx::contains(output, "Total instructions time:")); EXPECT(migraphx::contains(output, "Overhead time:")); EXPECT(migraphx::contains(output, "Overhead:")); EXPECT(not migraphx::contains(output, "fast")); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/print_graph_test.cpp000066400000000000000000000100411510465702400215730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include "test.hpp" #include static migraphx::program create_program() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::int64_type}); auto y = mm->add_parameter("y", {migraphx::shape::int64_type}); auto sum = mm->add_instruction(sum_op{}, x, y); auto one = mm->add_literal(1); mm->add_instruction(sum_op{}, sum, one); return p; } TEST_CASE(basic_graph_test) { migraphx::program p = create_program(); std::stringstream ss; p.print_graph(ss); std::string test = ss.str(); std::cout << "test = " << test << std::endl; EXPECT(migraphx::contains(test, "digraph")); EXPECT(migraphx::contains(test, "peripheries=0")); EXPECT(migraphx::contains( test, R"("@0"[label=<
@literal
> style="filled" fillcolor="#ADD8E6" fontcolor="#000000" color="" shape=rectangle fontname=Helvetica];)")); EXPECT(migraphx::contains( test, R"("y"[label=<
@param
int64_type
{1}, {0}
> style="filled" fillcolor="#F0E68C" fontcolor="#000000" color="" shape=rectangle fontname=Helvectica];)")); EXPECT(migraphx::contains( test, R"("x"[label=<
@param
int64_type
{1}, {0}
> style="filled" fillcolor="#F0E68C" fontcolor="#000000" color="" shape=rectangle fontname=Helvectica];)")); EXPECT(migraphx::contains( test, R"("@3"[label=<
sum
> style="rounded,filled" fillcolor="#D3D3D3" fontcolor="#000000" color="" shape=none fontname=Helvetica];)")); EXPECT(migraphx::contains(test, R"("x" -> "@3"[label="int64_type\n{1}, {0}"];)")); EXPECT(migraphx::contains(test, R"("y" -> "@3"[label="int64_type\n{1}, {0}"];)")); EXPECT(migraphx::contains( test, R"("@4"[label=<
sum
> style="rounded,filled" fillcolor="#D3D3D3" fontcolor="#000000" color="" shape=none fontname=Helvetica];)")); EXPECT(migraphx::contains(test, R"("@3" -> "@4"[label="int64_type\n{1}, {0}"];)")); EXPECT(migraphx::contains(test, R"("@0" -> "@4"[label="int64_type\n{1}, {0}"];)")); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/process_test.cpp000066400000000000000000000137571510465702400207550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #else #include #include #endif #include "test.hpp" static migraphx::fs::path executable; // NOLINT constexpr std::string_view string_data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " "sed do eiusmod tempor incididunt ut labore et dolore magna " "aliqua. Ut enim ad minim veniam, quis nostrud exercitation " "ullamco laboris nisi ut aliquip ex ea commodo consequat. " "Duis aute irure dolor in reprehenderit in voluptate velit " "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " "occaecat cupidatat non proident, sunt in culpa qui officia " "deserunt mollit anim id est laborum."; static std::vector read_stdin() { std::vector result; std::array buffer{}; std::size_t len = 0; #ifdef _WIN32 // Set stream translation mode to BINARY to suppress translations. // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setmode?view=msvc-170 auto old_mode = _setmode(_fileno(stdin), _O_BINARY); if(old_mode == -1) throw std::runtime_error{"failure setting IO mode to binary"}; #endif while((len = std::fread(buffer.data(), 1, buffer.size(), stdin)) > 0) { if(std::ferror(stdin) != 0 and std::feof(stdin) == 0) throw std::runtime_error{std::strerror(errno)}; result.insert(result.end(), buffer.begin(), buffer.begin() + len); } #ifdef _WIN32 // Reset to the previously set translation mode. _setmode(_fileno(stdin), old_mode); #endif return result; } TEST_CASE(string_stdin) { auto tmp = migraphx::tmp_dir{}; auto out = (tmp.path / "output.txt").string(); migraphx::process{executable, {"--stdin", out}}.write( [&](const auto& writer) { writer(string_data.data(), string_data.size()); }); EXPECT(migraphx::fs::is_regular_file(out)); std::string result{migraphx::read_string(out)}; EXPECT(result == string_data); EXPECT(migraphx::fs::remove(out)); } TEST_CASE(binary_stdin) { std::random_device rd; std::independent_bits_engine rbe(rd()); std::vector binary_data(4096); std::generate(binary_data.begin(), binary_data.end(), std::ref(rbe)); auto tmp = migraphx::tmp_dir{}; auto out = (tmp.path / "output.bin").string(); migraphx::process{executable, {"--stdin", out}}.write( [&](const auto& writer) { writer(binary_data.data(), binary_data.size()); }); EXPECT(migraphx::fs::is_regular_file(out)); std::vector result{migraphx::read_buffer(out)}; EXPECT(result == binary_data); EXPECT(migraphx::fs::remove(out)); } TEST_CASE(read_stdout) { std::string buffer; migraphx::process{executable, {"--stdout"}}.read([&buffer](const char* buf, std::size_t size) { buffer = std::string{buf, size}; }); EXPECT(buffer == string_data); } TEST_CASE(current_working_dir) { constexpr auto filename = "output.txt"; auto tmp = migraphx::tmp_dir{}; auto out = tmp.path / filename; migraphx::process{executable, {"--stdin", filename}}.cwd(tmp.path).write( [&](const auto& writer) { writer(string_data.data(), string_data.size()); }); EXPECT(migraphx::fs::is_regular_file(out)); std::string result{migraphx::read_string(out)}; EXPECT(result == string_data); EXPECT(migraphx::fs::remove(out)); } TEST_CASE(environment_variable) { std::string buffer; migraphx::process{executable, {"--stdout"}} .env({"MIGRAPHX_PROCESS_TEST_ENVIRONMENT_VARIABLE=1"}) .read([&buffer](const char* buf, std::size_t size) { buffer = std::string{buf, size}; }); std::string reversed(string_data); std::reverse(reversed.begin(), reversed.end()); EXPECT(buffer == reversed); } MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_PROCESS_TEST_ENVIRONMENT_VARIABLE) int main(int argc, const char* argv[]) { if(argc > 1) { std::string arg = argv[1]; if(arg == "--stdin") { migraphx::write_buffer(argv[2], read_stdin()); return 0; } if(arg == "--stdout") { std::vector result{string_data.begin(), string_data.end()}; if(migraphx::enabled(MIGRAPHX_PROCESS_TEST_ENVIRONMENT_VARIABLE{})) std::reverse(result.begin(), result.end()); std::fwrite(result.data(), 1, result.size(), stdout); return 0; } } else { executable = argv[0]; test::run(argc, argv); } } ROCm-AMDMIGraphX-46524e8/test/program_test.cpp000066400000000000000000000127041510465702400207350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include "test.hpp" #include #include static migraphx::program create_program() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::int64_type}); auto y = mm->add_parameter("y", {migraphx::shape::int64_type}); auto sum = mm->add_instruction(sum_op{}, x, y); auto one = mm->add_literal(1); mm->add_instruction(sum_op{}, sum, one); return p; } TEST_CASE(program_equality) { migraphx::program x = create_program(); migraphx::program y = create_program(); EXPECT(x.size() == 1); EXPECT(x == y); } TEST_CASE(program_not_equality1) { migraphx::program x; migraphx::program y = create_program(); EXPECT(x != y); x = y; EXPECT(x == y); } TEST_CASE(program_not_equality2) { migraphx::program x; migraphx::program y = create_program(); EXPECT(x != y); y = x; EXPECT(x == y); } TEST_CASE(program_default_copy_construct) { migraphx::program x; migraphx::program y; EXPECT(x == y); } TEST_CASE(program_print) { migraphx::program p = create_program(); auto* mm = p.get_main_module(); auto in1 = mm->end(); // print end instruction p.debug_print(in1); // print instruction not in the program auto p2 = p; auto* mm2 = p2.get_main_module(); auto in2 = mm2->begin(); p.debug_print(in2); // print last instruction auto in3 = std::prev(in1); p.debug_print(in3); } TEST_CASE(program_annotate) { migraphx::program p1 = create_program(); migraphx::program p2 = create_program(); std::stringstream ss1; p1.annotate(ss1, [](auto ins) { std::cout << ins->name() << "_1" << std::endl; }); std::stringstream ss2; p2.annotate(ss2, [](auto ins) { std::cout << ins->name() << "_1" << std::endl; }); EXPECT(ss1.str() == ss2.str()); } TEST_CASE(program_copy) { auto create_program_1 = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 4, 5}}; std::vector data(3 * 4 * 5); std::iota(data.begin(), data.end(), 1.0f); auto l2 = mm->add_literal(migraphx::literal(s, data)); auto p1 = mm->add_parameter("x", s); auto po = mm->add_outline(s); auto sum = mm->add_instruction(migraphx::make_op("add"), l2, po); mm->add_instruction(migraphx::make_op("mul"), sum, p1); return p; }; { auto p1 = create_program_1(); migraphx::program p2{}; p2 = p1; p2.compile(migraphx::make_target("ref")); EXPECT(p1 != p2); p1.compile(migraphx::make_target("ref")); EXPECT(p1 == p2); EXPECT(p1.get_parameter_names() == p2.get_parameter_names()); } { auto p1 = create_program_1(); auto p2(p1); EXPECT(p1 == p2); p1.compile(migraphx::make_target("ref")); EXPECT(p1 != p2); p2 = p1; EXPECT(p1 == p2); } { auto p1 = create_program_1(); auto p2 = create_program(); EXPECT(p1 != p2); p2 = p1; EXPECT(p1 == p2); p1.compile(migraphx::make_target("ref")); p2.compile(migraphx::make_target("ref")); EXPECT(p1 == p2); } { migraphx::program p1; auto* mm1 = p1.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::float_type, {3, 6}}; migraphx::shape s3{migraphx::shape::float_type, {2, 6}}; auto para1 = mm1->add_parameter("m1", s1); auto para2 = mm1->add_parameter("m2", s2); auto para3 = mm1->add_parameter("m3", s3); migraphx::add_apply_alpha_beta( *mm1, {para1, para2, para3}, migraphx::make_op("dot"), 0.31f, 0.28f); migraphx::program p2{}; p2 = p1; EXPECT(p2 == p1); p1.compile(migraphx::make_target("ref")); p2.compile(migraphx::make_target("ref")); EXPECT(p2 == p1); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/promote_literals_test.cpp000066400000000000000000000341671510465702400226610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include static void run_promote(migraphx::program& p) { migraphx::run_passes(p, {migraphx::promote_literals{}, migraphx::dead_code_elimination{}}); } static void run_promote_and_ecs(migraphx::program& p) { migraphx::run_passes(p, {migraphx::promote_literals{}, migraphx::dead_code_elimination{}, migraphx::eliminate_common_subexpression{}, migraphx::dead_code_elimination{}}); } TEST_CASE(promote_only) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } run_promote(p0); migraphx::program p1; { auto* mm1 = p1.get_main_module(); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins3 = mm1->add_literal(migraphx::literal{lit_s, {6}}); auto literal_ins2 = mm1->add_literal(migraphx::literal{lit_s, {6}}); auto literal_ins1 = mm1->add_literal(migraphx::literal{lit_s, {6}}); auto literal_ins0 = mm1->add_literal(migraphx::literal{lit_s, {6}}); // create batch submodules auto create_submodule = [&](std::size_t batch_size, migraphx::instruction_ref lit, const std::string& module_name) { auto* submod = p1.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), lit, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, literal_ins0, "dim_1"); auto* dim2 = create_submodule(2, literal_ins1, "dim_2"); auto* dim3 = create_submodule(3, literal_ins2, "dim_3"); auto* dim4 = create_submodule(4, literal_ins3, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm1->insert_parameter(std::next(literal_ins3), "data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm1->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm1->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm1->add_return({ret}); } EXPECT(p0 == p1); } TEST_CASE(promote_and_ecs0) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } run_promote_and_ecs(p0); migraphx::program p1; { auto* mm1 = p1.get_main_module(); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm1->add_literal(migraphx::literal{lit_s, {6}}); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p1.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm1->insert_parameter(std::next(literal_ins), "data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm1->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm1->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm1->add_return({ret}); } EXPECT(p0 == p1); } TEST_CASE(promote_and_ecs1) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins0 = submod->add_literal(migraphx::literal{lit_s, {6}}); auto literal_ins1 = submod->add_literal(migraphx::literal{lit_s, {2}}); auto broadcast_lit0 = submod->add_instruction( migraphx::make_op("multibroadcast"), literal_ins0, sm_input); auto broadcast_lit1 = submod->add_instruction( migraphx::make_op("multibroadcast"), literal_ins1, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit0); auto mul_ins = submod->add_instruction(migraphx::make_op("mul"), add_ins, broadcast_lit1); submod->add_return({mul_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } run_promote_and_ecs(p0); migraphx::program p1; { auto* mm1 = p1.get_main_module(); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins1 = mm1->add_literal(migraphx::literal{lit_s, {2}}); auto literal_ins0 = mm1->add_literal(migraphx::literal{lit_s, {6}}); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p1.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); auto broadcast_lit0 = submod->add_instruction( migraphx::make_op("multibroadcast"), literal_ins0, sm_input); auto broadcast_lit1 = submod->add_instruction( migraphx::make_op("multibroadcast"), literal_ins1, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit0); auto mul_ins = submod->add_instruction(migraphx::make_op("mul"), add_ins, broadcast_lit1); submod->add_return({mul_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm1->insert_parameter(std::next(literal_ins1), "data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm1->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2}); auto ret = mm1->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm1->add_return({ret}); } EXPECT(p0 == p1); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/propagate_constant_test.cpp000066400000000000000000000500021510465702400231520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static void run_pass(migraphx::module& m, const std::unordered_set& skip_ops = {}) { migraphx::run_passes( m, {migraphx::propagate_constant{skip_ops}, migraphx::dead_code_elimination{}}); } TEST_CASE(const_add) { migraphx::module m1; auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto sum = m1.add_instruction(migraphx::make_op("add"), one, two); m1.add_instruction(non_const_pass_op{}, sum); run_pass(m1); migraphx::module m2; auto total = m2.add_literal(3); m2.add_instruction(non_const_pass_op{}, total); EXPECT(m1 == m2); } TEST_CASE(const_add_parameter) { migraphx::module m1; auto one = m1.add_parameter("one", {migraphx::shape::int32_type, {1}}); auto two = m1.add_literal(2); auto sum = m1.add_instruction(migraphx::make_op("add"), one, two); m1.add_instruction(non_const_pass_op{}, sum); run_pass(m1); migraphx::module m2; auto total = m2.add_literal(3); m2.add_instruction(non_const_pass_op{}, total); EXPECT(m1 != m2); } TEST_CASE(const_multiadd) { migraphx::module m1; auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m1.add_instruction(migraphx::make_op("add"), sum1, two); m1.add_instruction(non_const_pass_op{}, sum2); run_pass(m1); migraphx::module m2; auto total = m2.add_literal(5); m2.add_instruction(non_const_pass_op{}, total); EXPECT(m1 == m2); } TEST_CASE(const_add_mul) { migraphx::module m1; auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto mul = m1.add_instruction(migraphx::make_op("mul"), two, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, mul); auto sum2 = m1.add_instruction(migraphx::make_op("add"), sum1, two); m1.add_instruction(non_const_pass_op{}, sum2); run_pass(m1); migraphx::module m2; auto total = m2.add_literal(7); m2.add_instruction(non_const_pass_op{}, total); EXPECT(m1 == m2); } TEST_CASE(const_add_scalar) { migraphx::module m1; auto one = m1.add_instruction(migraphx::make_op("scalar", {{"scalar_bcst_dims", {2, 2}}}), m1.add_literal(1)); auto two = m1.add_instruction(migraphx::make_op("scalar", {{"scalar_bcst_dims", {2, 2}}}), m1.add_literal(2)); auto sum = m1.add_instruction(migraphx::make_op("add"), one, two); m1.add_instruction(non_const_pass_op{}, sum); run_pass(m1); migraphx::module m2; auto total = m2.add_literal(migraphx::literal{{migraphx::shape::int32_type, {2, 2}}, {3, 3, 3, 3}}); m2.add_instruction(non_const_pass_op{}, total); EXPECT(m1 == m2); } TEST_CASE(const_scalar) { migraphx::module m1; { auto one = m1.add_instruction(migraphx::make_op("scalar", {{"scalar_bcst_dims", {2, 2}}}), m1.add_literal(1)); m1.add_instruction(non_const_pass_op{}, one); } run_pass(m1); migraphx::module m2; { auto one = m2.add_instruction(migraphx::make_op("scalar", {{"scalar_bcst_dims", {2, 2}}}), m2.add_literal(1)); m2.add_instruction(non_const_pass_op{}, one); } EXPECT(m1 == m2); } TEST_CASE(const_dot) { migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector vec = {1.0f, 2.0f, 1.0f, 2.0f}; auto l = m1.add_literal(migraphx::literal(s, vec)); auto dl = m1.add_instruction(migraphx::make_op("dot"), l, l); auto x = m1.add_parameter("x", s); auto r = m1.add_instruction(migraphx::make_op("add"), dl, x); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector vec = {3.0f, 6.0f, 3.0f, 6.0f}; auto x = m2.add_parameter("x", s); auto l = m2.add_literal(migraphx::literal(s, vec)); auto r = m2.add_instruction(migraphx::make_op("add"), l, x); m2.add_return({r}); } EXPECT(m1 == m2); } TEST_CASE(last_const) { const std::vector vec = {1.0f, 2.0f, 1.0f, 2.0f}; migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l = m1.add_literal(migraphx::literal(s, vec)); m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l); } run_pass(m1); migraphx::module m2; { migraphx::shape s{migraphx::shape::half_type, {2, 2}}; auto l = m2.add_literal(migraphx::literal(s, vec)); m2.add_instruction(migraphx::make_op("identity"), l); } EXPECT(m1 == m2); } TEST_CASE(skip_broadcast) { migraphx::module m1; { auto one = m1.add_literal(1); auto oneb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2}}}), one); m1.add_return({oneb}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(skip_broadcast_transpose) { const std::vector vec = {1.0f, 2.0f}; migraphx::shape s{migraphx::shape::float_type, {1, 2}}; migraphx::module m1; { auto one = m1.add_literal(migraphx::literal(s, vec)); auto oneb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2}}}), one); auto transpose = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), oneb); m1.add_return({transpose}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(fold_broadcast) { const std::vector vec = {1.0f, 2.0f, 1.0f, 2.0f}; migraphx::shape s{migraphx::shape::float_type, {2, 2}}; migraphx::module m1; { auto one = m1.add_literal(1.0f); auto oneb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2}}}), one); auto l = m1.add_literal(migraphx::literal(s, vec)); auto mul = m1.add_instruction(migraphx::make_op("mul"), oneb, l); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { auto l = m2.add_literal(migraphx::literal(s, vec)); m2.add_return({l}); } EXPECT(m1 == m2); } TEST_CASE(fold_broadcast_non_overlapping_broadcast) { const std::vector vec = {1.0f, 2.0f}; migraphx::shape s1{migraphx::shape::float_type, {1, 2}}; migraphx::shape s2{migraphx::shape::float_type, {2, 1}}; migraphx::module m1; { auto l1 = m1.add_literal(migraphx::literal(s1, vec)); auto l1b = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2}}}), l1); auto l2 = m1.add_literal(migraphx::literal(s2, vec)); auto l2b = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2}}}), l2); auto mul = m1.add_instruction(migraphx::make_op("mul"), l1b, l2b); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l = m2.add_literal(migraphx::literal(s, {1.0f, 2.0f, 2.0f, 4.0f})); m2.add_return({l}); } EXPECT(m1 == m2); } TEST_CASE(fold_packed_broadcast) { const std::vector vec = {0.0f, 1.0f}; migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2}}; auto l = m1.add_literal(migraphx::literal(s, vec)); auto lb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 2}}}), l); auto mul = m1.add_instruction(migraphx::make_op("mul"), lb, lb); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { migraphx::shape s{migraphx::shape::float_type, {1, 2}, {0, 1}}; auto l = m2.add_literal(migraphx::literal(s, vec)); m2.add_return({l}); } EXPECT(m1 == m2); } TEST_CASE(fold_slice) { migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 2}}; const std::vector vec = {1.0f, 2.0f, 1.0f, 2.0f}; auto l = m1.add_literal(migraphx::literal(s, vec)); auto slice = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), l); m1.add_return({slice}); } run_pass(m1); migraphx::module m2; { migraphx::shape s{migraphx::shape::float_type, {1, 2}}; const std::vector vec = {1.0f, 2.0f}; auto l = m2.add_literal(migraphx::literal(s, vec)); m2.add_return({l}); } EXPECT(m1 == m2); } TEST_CASE(fold_slice_nonpack) { migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 2}}; const std::vector vec = {1.0f, 1.0f, 2.0f, 2.0f}; auto l = m1.add_literal(migraphx::literal(s, vec)); auto slice = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), l); m1.add_return({slice}); } run_pass(m1); migraphx::module m2; { migraphx::shape s{migraphx::shape::float_type, {2, 1}}; const std::vector vec = {1.0f, 2.0f}; auto l = m2.add_literal(migraphx::literal(s, vec)); m2.add_return({l}); } EXPECT(m1 == m2); } TEST_CASE(pack_unpack_int4) { migraphx::shape s1{migraphx::shape::int8_type, {4}}; migraphx::shape s2{migraphx::shape::int8_type, {2}}; migraphx::module m1; { const std::vector vec = {1, 0, 2, 0}; auto l = m1.add_literal(migraphx::literal(s1, vec)); auto pack = m1.add_instruction(migraphx::make_op("pack_int4"), l); auto unpack = m1.add_instruction(migraphx::make_op("unpack_int4"), pack); m1.add_return({unpack}); } run_pass(m1); migraphx::module m2; { const std::vector vec = {1, 2}; auto l = m2.add_literal(migraphx::literal(s2, vec)); auto unpack = m2.add_instruction(migraphx::make_op("unpack_int4"), l); m2.add_return({unpack}); } EXPECT(m1 == m2); } TEST_CASE(skip_ops) { const std::vector vec = {1.0f, 2.0f, 1.0f, 2.0f}; migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l = m1.add_literal(migraphx::literal(s, vec)); auto scale = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2}}}), m1.add_literal(0.5f)); auto zp = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2}}}), m1.add_literal(0)); auto q = m1.add_instruction(migraphx::make_op("quantizelinear"), l, scale, zp); m1.add_instruction(migraphx::make_op("dequantizelinear"), q, scale, zp); } migraphx::module m2 = m1; run_pass(m1, {"quantizelinear", "dequantizelinear"}); EXPECT(m1 == m2); } TEST_CASE(block_dequantize) { migraphx::module m1; { auto x = m1.add_parameter("x", migraphx::shape{migraphx::shape::int8_type, {2, 5, 2}}); auto scalelit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 2, 2}})); auto zplit = m1.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {2, 2, 2}})); auto unsqueeze1 = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scalelit); auto broadcast1 = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze1); auto reshape1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto unsqueeze2 = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), zplit); auto broadcast2 = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze2); auto reshape2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto dq = m1.add_instruction(migraphx::make_op("dequantizelinear"), x, scale, zp); m1.add_return({dq}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", migraphx::shape{migraphx::shape::int8_type, {2, 5, 2}}); auto scalelit = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 2, 1, 2}})); auto zplit = m2.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {2, 2, 1, 2}})); auto broadcast1 = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), scalelit); auto reshape1 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto broadcast2 = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), zplit); auto reshape2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto dq = m2.add_instruction(migraphx::make_op("dequantizelinear"), x, scale, zp); m2.add_return({dq}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(block_dequantize_int4) { migraphx::module m1; { auto x = m1.add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 5, 2}}); auto w = m1.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {2, 5, 1}})); auto wunpack = m1.add_instruction(migraphx::make_op("unpack_int4"), w); auto scalelit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 2, 2}})); auto zplit = m1.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {2, 2, 2}})); auto unsqueeze1 = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), scalelit); auto broadcast1 = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze1); auto reshape1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto unsqueeze2 = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), zplit); auto broadcast2 = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), unsqueeze2); auto reshape2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto dq = m1.add_instruction(migraphx::make_op("dequantizelinear"), wunpack, scale, zp); auto transpose = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dq); auto dot = m1.add_instruction(migraphx::make_op("dot"), x, transpose); m1.add_return({dot}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 5, 2}}); auto w = m2.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {2, 5, 1}})); auto wunpack = m2.add_instruction(migraphx::make_op("unpack_int4"), w); auto scalelit = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 2, 1, 2}})); auto zplit = m2.add_literal(migraphx::generate_literal({migraphx::shape::int8_type, {2, 2, 1, 2}})); auto broadcast1 = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), scalelit); auto reshape1 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast1); auto scale = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape1); auto broadcast2 = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 2}}}), zplit); auto reshape2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 6, 2}}}), broadcast2); auto zp = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {5}}}), reshape2); auto dq = m2.add_instruction(migraphx::make_op("dequantizelinear"), wunpack, scale, zp); auto transpose = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), dq); auto dot = m2.add_instruction(migraphx::make_op("dot"), x, transpose); m2.add_return({dot}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(pack_unpack_fp4) { migraphx::shape s1{migraphx::shape::float_type, {4}}; migraphx::shape s2{migraphx::shape::fp4x2_type, {2}}; migraphx::module m1; { const std::vector vec = {1.f, 0.f, 2.f, 0.f}; auto l = m1.add_literal(migraphx::literal(s1, vec)); auto pack = m1.add_instruction(migraphx::make_op("pack_fp4"), l); auto unpack = m1.add_instruction(migraphx::make_op("unpack_fp4"), pack); m1.add_return({unpack}); } run_pass(m1); migraphx::module m2; { using migraphx::shape; const std::vector vec = {0x2, 0x4}; auto l = m2.add_literal(migraphx::literal(s2, vec.data())); auto unpack = m2.add_instruction(migraphx::make_op("unpack_fp4"), l); m2.add_return({unpack}); } EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/propagate_precision.cpp000066400000000000000000000232441510465702400222650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::propagate_precision{}, migraphx::eliminate_common_subexpression{}, migraphx::dead_code_elimination{}}); } TEST_CASE(propagate_input) { migraphx::shape s1{migraphx::shape::half_type, {2, 3}}; migraphx::shape s2{migraphx::shape::float_type, {2, 3}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto two = m1.add_literal(migraphx::literal{{migraphx::shape::half_type}, {2}}); auto div = migraphx::add_common_op(m1, migraphx::make_op("div"), {x, two}); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), div); auto convert1 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), sqrt); auto mul = m1.add_instruction(migraphx::make_op("mul"), convert1, y); auto convert2 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), mul); m1.add_return({convert2}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto convert1 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); auto two = m2.add_literal(migraphx::literal{{migraphx::shape::half_type}, {2}}); auto div = migraphx::add_common_op(m2, migraphx::make_op("div"), {convert1, two}); auto sqrt = m2.add_instruction(migraphx::make_op("sqrt"), div); auto mul = m2.add_instruction(migraphx::make_op("mul"), sqrt, y); auto convert2 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), mul); m2.add_return({convert2}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(propagate_output) { migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::half_type, {2, 3}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto convert1 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), x); auto two = m1.add_literal(migraphx::literal{{migraphx::shape::half_type}, {2}}); auto div = migraphx::add_common_op(m1, migraphx::make_op("div"), {convert1, two}); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), div); auto mul = m1.add_instruction(migraphx::make_op("mul"), sqrt, y); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto two = m2.add_literal(migraphx::literal{{migraphx::shape::half_type}, {2}}); auto div = migraphx::add_common_op(m2, migraphx::make_op("div"), {x, two}); auto sqrt = m2.add_instruction(migraphx::make_op("sqrt"), div); auto convert1 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), sqrt); auto mul = m2.add_instruction(migraphx::make_op("mul"), convert1, y); m2.add_return({mul}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(propagate_conflict) { migraphx::shape s1{migraphx::shape::float_type, {2, 3}}; migraphx::shape s2{migraphx::shape::double_type, {2, 3}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto convert1 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), x); auto two = m1.add_literal(migraphx::literal{{migraphx::shape::half_type}, {2}}); auto div = migraphx::add_common_op(m1, migraphx::make_op("div"), {convert1, two}); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), div); auto convert2 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::double_type}}), sqrt); auto mul = m1.add_instruction(migraphx::make_op("mul"), convert2, y); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto convert1 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::double_type}}), x); auto two = m2.add_literal(migraphx::literal{{migraphx::shape::half_type}, {2}}); auto div = migraphx::add_common_op(m2, migraphx::make_op("div"), {convert1, two}); auto sqrt = m2.add_instruction(migraphx::make_op("sqrt"), div); auto mul = m2.add_instruction(migraphx::make_op("mul"), sqrt, y); m2.add_return({mul}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(propagate_reduce) { migraphx::shape s1{migraphx::shape::half_type, {2, 3}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto three = m1.add_literal(migraphx::literal{{migraphx::shape::half_type}, {3}}); auto squared = m1.add_instruction(migraphx::make_op("mul"), x, x); auto div = migraphx::add_common_op(m1, migraphx::make_op("div"), {squared, three}); auto convert1 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), div); auto reduce = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), convert1); auto convert2 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), reduce); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), convert2); auto mul = migraphx::add_common_op(m1, migraphx::make_op("mul"), {x, sqrt}); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto convert1 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); auto three = m2.add_literal(migraphx::literal{{migraphx::shape::half_type}, {3}}); auto squared = m2.add_instruction(migraphx::make_op("mul"), convert1, convert1); auto div = migraphx::add_common_op(m2, migraphx::make_op("div"), {squared, three}); auto reduce = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), div); auto sqrt = m2.add_instruction(migraphx::make_op("sqrt"), reduce); auto convert2 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), sqrt); auto mul = migraphx::add_common_op(m2, migraphx::make_op("mul"), {x, convert2}); m2.add_return({mul}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(propagate_reduce_float_to_double) { migraphx::shape s1{migraphx::shape::double_type, {3, 4}, {4, 1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto convert1 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), x); auto reduce = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), convert1); auto convert2 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::double_type}}), reduce); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), convert2); m1.add_return({squeeze}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto reduce = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), reduce); m2.add_return({squeeze}); } EXPECT(m1.sort() == m2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/py/000077500000000000000000000000001510465702400161475ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/py/CMakeLists.txt000066400000000000000000000146221510465702400207140ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### include(PythonModules) set(VENV ${CMAKE_BINARY_DIR}/test/py/venv) set(VENV_ONNX ${CMAKE_BINARY_DIR}/test/py/venv-onnx) set(REQUIREMENTS ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt) set(REQUIREMENTS_ONNX ${CMAKE_CURRENT_SOURCE_DIR}/requirements-onnx.txt) set(PYTHON_VERSION_TO_DISABLE_ONNX 3.6) option(MIGRAPHX_DISABLE_VIRTUAL_ENV "Disable python virtual environments" OFF) option(MIGRAPHX_DISABLE_ONNX_TESTS "Disable Onnx backend tests" OFF) function(add_py_venv_fixture FIXTURE_NAME VIRTUAL_ENV_DIR REQUIREMENTS_FILE) foreach(PYTHON_VERSION ${PYTHON_VERSIONS}) set (ENV_COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=$" "PYTHONMALLOC=debug" "MALLOC_CHECK_=3" ) set(PYTHON_EXECUTABLE ${PYTHON_${PYTHON_VERSION}_EXECUTABLE}) if(NOT TEST py_${PYTHON_VERSION}_${FIXTURE_NAME}_initialize_env) if (NOT (${FIXTURE_NAME} STREQUAL "onnx" AND ${PYTHON_VERSION} STREQUAL ${PYTHON_VERSION_TO_DISABLE_ONNX})) add_test(NAME py_${PYTHON_VERSION}_${FIXTURE_NAME}_initialize_env COMMAND ${PYTHON_EXECUTABLE} -m venv ${VIRTUAL_ENV_DIR}/${PYTHON_VERSION} --clear) set_tests_properties(py_${PYTHON_VERSION}_${FIXTURE_NAME}_initialize_env PROPERTIES FIXTURES_SETUP ${FIXTURE_NAME}_${PYTHON_VERSION}_INIT_VENV) set(PYTHON_EXECUTABLE ${VIRTUAL_ENV_DIR}/${PYTHON_VERSION}/bin/python) if(EXISTS ${REQUIREMENTS_FILE}) add_test( NAME py_${PYTHON_VERSION}_${FIXTURE_NAME}_setup_env COMMAND ${PYTHON_EXECUTABLE} -m pip install -r ${REQUIREMENTS_FILE}) else() # If there is no requirements file, then there are no packages to install in the virtual env. # Just create a placeholder test for setting up the required fixture for running the tests. add_test( NAME py_${PYTHON_VERSION}_${FIXTURE_NAME}_setup_env COMMAND ${PYTHON_EXECUTABLE} -m pip install --help) endif() set_tests_properties(py_${PYTHON_VERSION}_${FIXTURE_NAME}_setup_env PROPERTIES FIXTURES_REQUIRED ${FIXTURE_NAME}_${PYTHON_VERSION}_INIT_VENV) set_tests_properties(py_${PYTHON_VERSION}_${FIXTURE_NAME}_setup_env PROPERTIES FIXTURES_SETUP ${FIXTURE_NAME}_${PYTHON_VERSION}_VENV) endif() endif() endforeach() endfunction() function(add_py_test NAME SCRIPT FIXTURE_NAME VENV_DIR) foreach(PYTHON_VERSION ${PYTHON_VERSIONS}) set (ENV_COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=$" "PYTHONMALLOC=debug" "MALLOC_CHECK_=3" ) if(MIGRAPHX_DISABLE_VIRTUAL_ENV) set(PYTHON_EXECUTABLE ${PYTHON_${PYTHON_VERSION}_EXECUTABLE}) else() set(PYTHON_EXECUTABLE ${VENV_DIR}/${PYTHON_VERSION}/bin/python) endif() if(NOT (${FIXTURE_NAME} STREQUAL "onnx" AND ${PYTHON_VERSION} STREQUAL ${PYTHON_VERSION_TO_DISABLE_ONNX})) add_test( NAME test_py_${PYTHON_VERSION}_${NAME} COMMAND ${ENV_COMMAND} ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${SCRIPT} ${ARGN}) add_custom_target(test_py_${PYTHON_VERSION}_${NAME} COMMAND ${ENV_COMMAND} ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${SCRIPT} ${ARGN} COMMENT "${PYTHON_EXECUTABLE} ${SCRIPT}") if(NOT MIGRAPHX_DISABLE_VIRTUAL_ENV) set_tests_properties(test_py_${PYTHON_VERSION}_${NAME} PROPERTIES FIXTURES_REQUIRED ${FIXTURE_NAME}_${PYTHON_VERSION}_VENV) endif() endif() endforeach() endfunction() add_dependencies(tests migraphx_py) add_dependencies(check migraphx_py) if(NOT MIGRAPHX_DISABLE_VIRTUAL_ENV) add_py_venv_fixture(common ${VENV} ${REQUIREMENTS}) add_py_venv_fixture(onnx ${VENV_ONNX} ${REQUIREMENTS_ONNX}) endif() add_py_test(ref test_cpu.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) add_py_test(save_load test_save_load.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) add_py_test(op test_op.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) add_py_test(shape test_shape.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) add_py_test(module_construct test_module_construct.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) add_py_test(literal test_literal.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) add_py_test(autocast_fp8 test_autocast_fp8.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) if(MIGRAPHX_ENABLE_GPU) add_py_test(gpu_offload test_gpu_offload.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) add_py_test(gpu test_gpu.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) add_py_test(array test_array.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) if(NOT MIGRAPHX_DISABLE_ONNX_TESTS) add_py_test(backend onnx_backend_test.py onnx ${VENV_ONNX} WORKING_DIRECTORY ${TEST_ONNX_DIR}) endif() add_py_test(gpu_async test_gpu_async.py common ${VENV} WORKING_DIRECTORY ${TEST_ONNX_DIR}) endif() ROCm-AMDMIGraphX-46524e8/test/py/onnx_backend_test.py000066400000000000000000002030411510465702400222110ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import sys if sys.version_info < (3, 0): sys.exit() import argparse import os import unittest import onnx import onnx.backend.test import numpy as np from onnx_migraphx.backend import MIGraphXBackend as c2 from packaging import version pytest_plugins = 'onnx.backend.test.report', class MIGraphXBackendTest(onnx.backend.test.BackendTest): def __init__(self, backend, parent_module=None): super(MIGraphXBackendTest, self).__init__(backend, parent_module) @classmethod def assert_similar_outputs(cls, ref_outputs, outputs, rtol, atol, model_dir=None): prog_string = c2.get_program() np.testing.assert_equal(len(ref_outputs), len(outputs), err_msg=prog_string) for i in range(len(outputs)): np.testing.assert_equal(ref_outputs[i].dtype, outputs[i].dtype, err_msg=prog_string) if ref_outputs[i].dtype == object: np.testing.assert_array_equal(ref_outputs[i], outputs[i], err_msg=prog_string) else: np.testing.assert_allclose(ref_outputs[i], outputs[i], rtol=1e-3, atol=1e-5, err_msg=prog_string) def disabled_tests_onnx_1_7_0(backend_test): # fails # from OnnxBackendNodeModelTest backend_test.exclude(r'test_maxpool_with_argmax_2d_precomputed_pads_cpu') backend_test.exclude( r'test_maxpool_with_argmax_2d_precomputed_strides_cpu') backend_test.exclude(r'test_nonmaxsuppression_center_point_box_format_cpu') backend_test.exclude(r'test_nonmaxsuppression_flipped_coordinates_cpu') backend_test.exclude(r'test_nonmaxsuppression_identical_boxes_cpu') backend_test.exclude(r'test_nonmaxsuppression_limit_output_size_cpu') backend_test.exclude( r'test_nonmaxsuppression_suppress_by_IOU_and_scores_cpu') backend_test.exclude(r'test_nonmaxsuppression_suppress_by_IOU_cpu') backend_test.exclude(r'test_nonmaxsuppression_two_batches_cpu') backend_test.exclude(r'test_nonmaxsuppression_two_classes_cpu') backend_test.exclude(r'test_nonzero_example_cpu') # from OnnxBackendPyTorchConvertedModelTest backend_test.exclude(r'test_ConvTranspose2d_cpu') backend_test.exclude(r'test_ConvTranspose2d_no_bias_cpu') # from OnnxBackendPyTorchOperatorModelTest backend_test.exclude(r'test_operator_add_broadcast_cpu') backend_test.exclude(r'test_operator_add_size1_right_broadcast_cpu') backend_test.exclude(r'test_operator_addconstant_cpu') backend_test.exclude(r'test_operator_convtranspose_cpu') # errors # from OnnxBackendNodeModelTest backend_test.exclude(r'test_bitshift_left_uint16_cpu') backend_test.exclude(r'test_bitshift_left_uint32_cpu') backend_test.exclude(r'test_bitshift_left_uint64_cpu') backend_test.exclude(r'test_bitshift_left_uint8_cpu') backend_test.exclude(r'test_bitshift_right_uint16_cpu') backend_test.exclude(r'test_bitshift_right_uint32_cpu') backend_test.exclude(r'test_bitshift_right_uint64_cpu') backend_test.exclude(r'test_bitshift_right_uint8_cpu') backend_test.exclude(r'test_cast_FLOAT_to_STRING_cpu') backend_test.exclude(r'test_cast_STRING_to_FLOAT_cpu') backend_test.exclude(r'test_compress_0_cpu') backend_test.exclude(r'test_compress_1_cpu') backend_test.exclude(r'test_compress_default_axis_cpu') backend_test.exclude(r'test_compress_negative_axis_cpu') backend_test.exclude(r'test_constant_pad_cpu') backend_test.exclude(r'test_convinteger_with_padding_cpu') backend_test.exclude(r'test_convtranspose_1d_cpu') backend_test.exclude(r'test_det_2d_cpu') backend_test.exclude(r'test_det_nd_cpu') backend_test.exclude(r'test_edge_pad_cpu') backend_test.exclude(r'test_maxunpool_export_with_output_shape_cpu') backend_test.exclude(r'test_maxunpool_export_without_output_shape_cpu') backend_test.exclude(r'test_qlinearmatmul_2D_cpu') backend_test.exclude(r'test_qlinearmatmul_3D_cpu') backend_test.exclude(r'test_range_float_type_positive_delta_expanded_cpu') backend_test.exclude(r'test_range_int32_type_negative_delta_expanded_cpu') backend_test.exclude(r'test_reflect_pad_cpu') backend_test.exclude( r'test_resize_downsample_scales_cubic_A_n0p5_exclude_outside_cpu') backend_test.exclude( r'test_resize_downsample_scales_cubic_align_corners_cpu') backend_test.exclude(r'test_resize_downsample_scales_cubic_cpu') backend_test.exclude( r'test_resize_downsample_scales_linear_align_corners_cpu') backend_test.exclude(r'test_resize_downsample_scales_linear_cpu') backend_test.exclude(r'test_resize_downsample_sizes_cubic_cpu') backend_test.exclude( r'test_resize_downsample_sizes_linear_pytorch_half_pixel_cpu') backend_test.exclude(r'test_resize_tf_crop_and_resize_cpu') backend_test.exclude( r'test_resize_upsample_scales_cubic_A_n0p5_exclude_outside_cpu') backend_test.exclude( r'test_resize_upsample_scales_cubic_align_corners_cpu') backend_test.exclude(r'test_resize_upsample_scales_cubic_asymmetric_cpu') backend_test.exclude(r'test_resize_upsample_scales_cubic_cpu') backend_test.exclude( r'test_resize_upsample_scales_linear_align_corners_cpu') backend_test.exclude(r'test_resize_upsample_scales_linear_cpu') backend_test.exclude(r'test_resize_upsample_sizes_cubic_cpu') backend_test.exclude(r'test_reversesequence_batch_cpu') backend_test.exclude(r'test_reversesequence_time_cpu') backend_test.exclude(r'test_scan_sum_cpu') backend_test.exclude(r'test_slice_cpu') backend_test.exclude(r'test_slice_end_out_of_bounds_cpu') backend_test.exclude(r'test_slice_neg_cpu') backend_test.exclude(r'test_slice_neg_steps_cpu') backend_test.exclude(r'test_slice_start_out_of_bounds_cpu') backend_test.exclude( r'test_strnormalizer_export_monday_casesensintive_lower_cpu') backend_test.exclude( r'test_strnormalizer_export_monday_casesensintive_nochangecase_cpu') backend_test.exclude( r'test_strnormalizer_export_monday_casesensintive_upper_cpu') backend_test.exclude(r'test_strnormalizer_export_monday_empty_output_cpu') backend_test.exclude( r'test_strnormalizer_export_monday_insensintive_upper_twodim_cpu') backend_test.exclude(r'test_strnormalizer_nostopwords_nochangecase_cpu') backend_test.exclude( r'test_tfidfvectorizer_tf_batch_onlybigrams_skip0_cpu') backend_test.exclude( r'test_tfidfvectorizer_tf_batch_onlybigrams_skip5_cpu') backend_test.exclude( r'test_tfidfvectorizer_tf_batch_uniandbigrams_skip5_cpu') backend_test.exclude(r'test_tfidfvectorizer_tf_only_bigrams_skip0_cpu') backend_test.exclude(r'test_tfidfvectorizer_tf_onlybigrams_levelempty_cpu') backend_test.exclude(r'test_tfidfvectorizer_tf_onlybigrams_skip5_cpu') backend_test.exclude(r'test_tfidfvectorizer_tf_uniandbigrams_skip5_cpu') backend_test.exclude(r'test_top_k_cpu') backend_test.exclude(r'test_top_k_negative_axis_cpu') backend_test.exclude(r'test_top_k_smallest_cpu') backend_test.exclude(r'test_unique_sorted_with_axis_3d_cpu') backend_test.exclude(r'test_unique_sorted_with_negative_axis_cpu') backend_test.exclude(r'test_upsample_nearest_cpu') # from OnnxBackendPyTorchConvertedModelTest backend_test.exclude(r'test_PReLU_1d_multiparam_cpu') backend_test.exclude(r'test_PReLU_2d_multiparam_cpu') backend_test.exclude(r'test_PReLU_3d_multiparam_cpu') backend_test.exclude(r'test_ReplicationPad2d_cpu') # from OnnxBackendPyTorchOperatorModelTest backend_test.exclude(r'test_operator_add_size1_broadcast_cpu') backend_test.exclude(r'test_operator_add_size1_singleton_broadcast_cpu') # from OnnxBackendSimpleModelTest backend_test.exclude(r'test_gradient_of_add_and_mul_cpu') backend_test.exclude(r'test_gradient_of_add_cpu') backend_test.exclude(r'test_sequence_model1_cpu') backend_test.exclude(r'test_sequence_model2_cpu') backend_test.exclude(r'test_sequence_model3_cpu') backend_test.exclude(r'test_sequence_model4_cpu') backend_test.exclude(r'test_sequence_model5_cpu') backend_test.exclude(r'test_sequence_model6_cpu') backend_test.exclude(r'test_sequence_model7_cpu') backend_test.exclude(r'test_sequence_model8_cpu') backend_test.exclude(r'test_strnorm_model_monday_casesensintive_lower_cpu') backend_test.exclude( r'test_strnorm_model_monday_casesensintive_nochangecase_cpu') backend_test.exclude(r'test_strnorm_model_monday_casesensintive_upper_cpu') backend_test.exclude(r'test_strnorm_model_monday_empty_output_cpu') backend_test.exclude( r'test_strnorm_model_monday_insensintive_upper_twodim_cpu') backend_test.exclude(r'test_strnorm_model_nostopwords_nochangecase_cpu') def disabled_tests_onnx_1_8_0(backend_test): # errors # from OnnxBackendNodeModelTest backend_test.exclude(r'test_cast_BFLOAT16_to_FLOAT_cpu') backend_test.exclude(r'test_cast_FLOAT_to_BFLOAT16_cpu') backend_test.exclude(r'test_if_seq_cpu') backend_test.exclude(r'test_loop11_cpu') backend_test.exclude(r'test_loop13_seq_cpu') backend_test.exclude(r'test_nllloss_NC_cpu') backend_test.exclude(r'test_nllloss_NCd1_cpu') backend_test.exclude(r'test_nllloss_NCd1_ii_cpu') backend_test.exclude(r'test_nllloss_NCd1_mean_weight_negative_ii_cpu') backend_test.exclude(r'test_nllloss_NCd1_weight_cpu') backend_test.exclude(r'test_nllloss_NCd1_weight_ii_cpu') backend_test.exclude(r'test_nllloss_NCd1d2_cpu') backend_test.exclude( r'test_nllloss_NCd1d2_no_weight_reduction_mean_ii_cpu') backend_test.exclude(r'test_nllloss_NCd1d2_reduction_mean_cpu') backend_test.exclude(r'test_nllloss_NCd1d2_reduction_sum_cpu') backend_test.exclude(r'test_nllloss_NCd1d2_with_weight_cpu') backend_test.exclude(r'test_nllloss_NCd1d2_with_weight_reduction_mean_cpu') backend_test.exclude(r'test_nllloss_NCd1d2_with_weight_reduction_sum_cpu') backend_test.exclude( r'test_nllloss_NCd1d2_with_weight_reduction_sum_ii_cpu') backend_test.exclude( r'test_nllloss_NCd1d2d3_none_no_weight_negative_ii_cpu') backend_test.exclude(r'test_nllloss_NCd1d2d3_sum_weight_high_ii_cpu') backend_test.exclude(r'test_nllloss_NCd1d2d3d4d5_mean_weight_cpu') backend_test.exclude(r'test_nllloss_NCd1d2d3d4d5_none_no_weight_cpu') backend_test.exclude(r'test_sce_NCd1_mean_weight_negative_ii_cpu') backend_test.exclude(r'test_sce_NCd1_mean_weight_negative_ii_expanded_cpu') backend_test.exclude(r'test_sce_NCd1_mean_weight_negative_ii_log_prob_cpu') backend_test.exclude( r'test_sce_NCd1_mean_weight_negative_ii_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_NCd1d2d3_none_no_weight_negative_ii_cpu') backend_test.exclude( r'test_sce_NCd1d2d3_none_no_weight_negative_ii_expanded_cpu') backend_test.exclude( r'test_sce_NCd1d2d3_none_no_weight_negative_ii_log_prob_cpu') backend_test.exclude( r'test_sce_NCd1d2d3_none_no_weight_negative_ii_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_NCd1d2d3_sum_weight_high_ii_cpu') backend_test.exclude(r'test_sce_NCd1d2d3_sum_weight_high_ii_expanded_cpu') backend_test.exclude(r'test_sce_NCd1d2d3_sum_weight_high_ii_log_prob_cpu') backend_test.exclude( r'test_sce_NCd1d2d3_sum_weight_high_ii_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_NCd1d2d3d4d5_mean_weight_cpu') backend_test.exclude(r'test_sce_NCd1d2d3d4d5_mean_weight_expanded_cpu') backend_test.exclude(r'test_sce_NCd1d2d3d4d5_mean_weight_log_prob_cpu') backend_test.exclude( r'test_sce_NCd1d2d3d4d5_mean_weight_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_NCd1d2d3d4d5_none_no_weight_cpu') backend_test.exclude(r'test_sce_NCd1d2d3d4d5_none_no_weight_expanded_cpu') backend_test.exclude(r'test_sce_NCd1d2d3d4d5_none_no_weight_log_prob_cpu') backend_test.exclude( r'test_sce_NCd1d2d3d4d5_none_no_weight_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_3d_cpu') backend_test.exclude(r'test_sce_mean_3d_expanded_cpu') backend_test.exclude(r'test_sce_mean_3d_log_prob_cpu') backend_test.exclude(r'test_sce_mean_3d_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_cpu') backend_test.exclude(r'test_sce_mean_expanded_cpu') backend_test.exclude(r'test_sce_mean_log_prob_cpu') backend_test.exclude(r'test_sce_mean_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_3d_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_3d_expanded_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_3d_log_prob_cpu') backend_test.exclude( r'test_sce_mean_no_weight_ii_3d_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_4d_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_4d_expanded_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_4d_log_prob_cpu') backend_test.exclude( r'test_sce_mean_no_weight_ii_4d_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_expanded_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_log_prob_cpu') backend_test.exclude(r'test_sce_mean_no_weight_ii_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_weight_cpu') backend_test.exclude(r'test_sce_mean_weight_expanded_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_3d_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_3d_expanded_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_3d_log_prob_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_3d_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_4d_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_4d_expanded_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_4d_log_prob_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_4d_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_expanded_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_log_prob_cpu') backend_test.exclude(r'test_sce_mean_weight_ii_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_mean_weight_log_prob_cpu') backend_test.exclude(r'test_sce_mean_weight_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_none_cpu') backend_test.exclude(r'test_sce_none_expanded_cpu') backend_test.exclude(r'test_sce_none_log_prob_cpu') backend_test.exclude(r'test_sce_none_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_none_weights_cpu') backend_test.exclude(r'test_sce_none_weights_expanded_cpu') backend_test.exclude(r'test_sce_none_weights_log_prob_cpu') backend_test.exclude(r'test_sce_none_weights_log_prob_expanded_cpu') backend_test.exclude(r'test_sce_sum_cpu') backend_test.exclude(r'test_sce_sum_expanded_cpu') backend_test.exclude(r'test_sce_sum_log_prob_cpu') backend_test.exclude(r'test_sce_sum_log_prob_expanded_cpu') backend_test.exclude(r'test_sequence_insert_at_back_cpu') backend_test.exclude(r'test_sequence_insert_at_front_cpu') def disabled_tests_onnx_1_9_0(backend_test): # fails # from OnnxBackendPyTorchConvertedModelTest # MaxPool dialtion is partially supported on GPU by a workaround # But these tests require too large allocations to work properly backend_test.exclude(r'test_MaxPool1d_stride_padding_dilation_cpu') backend_test.exclude(r'test_MaxPool2d_stride_padding_dilation_cpu') # errors # from OnnxBackendNodeModelTest backend_test.exclude(r'test_convinteger_without_padding_cpu') backend_test.exclude(r'test_convtranspose_autopad_same_cpu') backend_test.exclude(r'test_identity_sequence_cpu') backend_test.exclude(r'test_tril_neg_cpu') backend_test.exclude(r'test_tril_out_neg_cpu') backend_test.exclude(r'test_tril_out_pos_cpu') backend_test.exclude(r'test_tril_pos_cpu') backend_test.exclude(r'test_tril_square_neg_cpu') backend_test.exclude(r'test_tril_zero_cpu') backend_test.exclude(r'test_triu_neg_cpu') backend_test.exclude(r'test_triu_one_row_cpu') backend_test.exclude(r'test_triu_out_neg_out_cpu') backend_test.exclude(r'test_triu_out_pos_cpu') backend_test.exclude(r'test_triu_pos_cpu') backend_test.exclude(r'test_triu_square_neg_cpu') backend_test.exclude(r'test_triu_zero_cpu') def disabled_tests_onnx_1_10_0(backend_test): # fails # from OnnxBackendNodeModelTest backend_test.exclude(r'test_bernoulli_double_expanded_cpu') backend_test.exclude(r'test_bernoulli_expanded_cpu') backend_test.exclude(r'test_bernoulli_seed_expanded_cpu') # errors # from OnnxBackendNodeModelTest backend_test.exclude(r'test_bernoulli_cpu') backend_test.exclude(r'test_bernoulli_double_cpu') backend_test.exclude(r'test_bernoulli_seed_cpu') backend_test.exclude(r'test_castlike_BFLOAT16_to_FLOAT_cpu') backend_test.exclude(r'test_castlike_BFLOAT16_to_FLOAT_expanded_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_BFLOAT16_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_BFLOAT16_expanded_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_STRING_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_STRING_expanded_cpu') backend_test.exclude(r'test_castlike_STRING_to_FLOAT_cpu') backend_test.exclude(r'test_castlike_STRING_to_FLOAT_expanded_cpu') backend_test.exclude(r'test_optional_get_element_sequence_cpu') def disabled_tests_onnx_1_11_0(backend_test): # errors # from OnnxBackendNodeModelTest backend_test.exclude(r'test_identity_opt_cpu') backend_test.exclude(r'test_if_opt_cpu') backend_test.exclude(r'test_loop16_seq_none_cpu') def disabled_tests_onnx_1_12_0(backend_test): # errors # from OnnxBackendNodeModelTest backend_test.exclude(r'test_blackmanwindow_cpu') backend_test.exclude(r'test_blackmanwindow_expanded_cpu') backend_test.exclude(r'test_blackmanwindow_symmetric_cpu') backend_test.exclude(r'test_blackmanwindow_symmetric_expanded_cpu') backend_test.exclude(r'test_dft_axis_cpu') backend_test.exclude(r'test_dft_cpu') backend_test.exclude(r'test_dft_inverse_cpu') backend_test.exclude(r'test_hammingwindow_cpu') backend_test.exclude(r'test_hammingwindow_expanded_cpu') backend_test.exclude(r'test_hammingwindow_symmetric_cpu') backend_test.exclude(r'test_hammingwindow_symmetric_expanded_cpu') backend_test.exclude(r'test_hannwindow_cpu') backend_test.exclude(r'test_hannwindow_expanded_cpu') backend_test.exclude(r'test_hannwindow_symmetric_cpu') backend_test.exclude(r'test_hannwindow_symmetric_expanded_cpu') backend_test.exclude(r'test_melweightmatrix_cpu') backend_test.exclude(r'test_sequence_map_add_1_sequence_1_tensor_cpu') backend_test.exclude( r'test_sequence_map_add_1_sequence_1_tensor_expanded_cpu') backend_test.exclude(r'test_sequence_map_add_2_sequences_cpu') backend_test.exclude(r'test_sequence_map_add_2_sequences_expanded_cpu') backend_test.exclude(r'test_sequence_map_extract_shapes_cpu') backend_test.exclude(r'test_sequence_map_extract_shapes_expanded_cpu') backend_test.exclude(r'test_sequence_map_identity_1_sequence_1_tensor_cpu') backend_test.exclude( r'test_sequence_map_identity_1_sequence_1_tensor_expanded_cpu') backend_test.exclude(r'test_sequence_map_identity_1_sequence_cpu') backend_test.exclude(r'test_sequence_map_identity_1_sequence_expanded_cpu') backend_test.exclude(r'test_sequence_map_identity_2_sequences_cpu') backend_test.exclude( r'test_sequence_map_identity_2_sequences_expanded_cpu') backend_test.exclude(r'test_stft_cpu') backend_test.exclude(r'test_stft_with_window_cpu') def disabled_tests_onnx_1_13_0(backend_test): # fails # from OnnxBackendNodeModelTest backend_test.exclude(r'test_reduce_l1_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_l1_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_l1_keep_dims_example_cpu') backend_test.exclude(r'test_reduce_l1_keep_dims_random_cpu') backend_test.exclude(r'test_reduce_l1_negative_axes_keep_dims_example_cpu') backend_test.exclude(r'test_reduce_l1_negative_axes_keep_dims_random_cpu') backend_test.exclude(r'test_reduce_l2_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_l2_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_l2_keep_dims_example_cpu') backend_test.exclude(r'test_reduce_l2_keep_dims_random_cpu') backend_test.exclude(r'test_reduce_l2_negative_axes_keep_dims_example_cpu') backend_test.exclude(r'test_reduce_l2_negative_axes_keep_dims_random_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_log_sum_exp_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_log_sum_exp_keepdims_example_cpu') backend_test.exclude(r'test_reduce_log_sum_exp_keepdims_random_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_negative_axes_keepdims_example_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_negative_axes_keepdims_random_cpu') backend_test.exclude(r'test_reduce_sum_square_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_sum_square_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_sum_square_keepdims_example_cpu') backend_test.exclude(r'test_reduce_sum_square_keepdims_random_cpu') backend_test.exclude( r'test_reduce_sum_square_negative_axes_keepdims_example_cpu') backend_test.exclude( r'test_reduce_sum_square_negative_axes_keepdims_random_cpu') # errors # from OnnxBackendNodeModelTest backend_test.exclude(r'test_bitwise_and_i16_3d_cpu') backend_test.exclude(r'test_bitwise_and_i32_2d_cpu') backend_test.exclude(r'test_bitwise_and_ui64_bcast_3v1d_cpu') backend_test.exclude(r'test_bitwise_and_ui8_bcast_4v3d_cpu') backend_test.exclude(r'test_bitwise_not_2d_cpu') backend_test.exclude(r'test_bitwise_not_3d_cpu') backend_test.exclude(r'test_bitwise_not_4d_cpu') backend_test.exclude(r'test_bitwise_or_i16_4d_cpu') backend_test.exclude(r'test_bitwise_or_i32_2d_cpu') backend_test.exclude(r'test_bitwise_or_ui64_bcast_3v1d_cpu') backend_test.exclude(r'test_bitwise_or_ui8_bcast_4v3d_cpu') backend_test.exclude(r'test_bitwise_xor_i16_3d_cpu') backend_test.exclude(r'test_bitwise_xor_i32_2d_cpu') backend_test.exclude(r'test_bitwise_xor_ui64_bcast_3v1d_cpu') backend_test.exclude(r'test_bitwise_xor_ui8_bcast_4v3d_cpu') backend_test.exclude(r'test_center_crop_pad_crop_and_pad_cpu') backend_test.exclude(r'test_center_crop_pad_crop_and_pad_expanded_cpu') backend_test.exclude(r'test_center_crop_pad_crop_axes_chw_cpu') backend_test.exclude(r'test_center_crop_pad_crop_axes_chw_expanded_cpu') backend_test.exclude(r'test_center_crop_pad_crop_axes_hwc_cpu') backend_test.exclude(r'test_center_crop_pad_crop_axes_hwc_expanded_cpu') backend_test.exclude(r'test_center_crop_pad_crop_cpu') backend_test.exclude(r'test_center_crop_pad_crop_expanded_cpu') backend_test.exclude(r'test_center_crop_pad_pad_cpu') backend_test.exclude(r'test_center_crop_pad_pad_expanded_cpu') backend_test.exclude(r'test_col2im_5d_cpu') backend_test.exclude(r'test_col2im_cpu') backend_test.exclude(r'test_col2im_dilations_cpu') backend_test.exclude(r'test_col2im_pads_cpu') backend_test.exclude(r'test_col2im_strides_cpu') backend_test.exclude(r'test_constant_pad_axes_cpu') backend_test.exclude(r'test_mish_cpu') backend_test.exclude(r'test_optional_get_element_optional_sequence_cpu') backend_test.exclude(r'test_optional_get_element_optional_tensor_cpu') backend_test.exclude(r'test_optional_get_element_tensor_cpu') backend_test.exclude( r'test_optional_has_element_empty_no_input_name_optional_input_cpu') backend_test.exclude( r'test_optional_has_element_empty_no_input_name_tensor_input_cpu') backend_test.exclude( r'test_optional_has_element_empty_no_input_optional_input_cpu') backend_test.exclude( r'test_optional_has_element_empty_no_input_tensor_input_cpu') backend_test.exclude(r'test_optional_has_element_empty_optional_input_cpu') backend_test.exclude(r'test_optional_has_element_optional_input_cpu') backend_test.exclude(r'test_optional_has_element_tensor_input_cpu') backend_test.exclude(r'test_reduce_l1_default_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_l1_default_axes_keepdims_random_cpu') backend_test.exclude(r'test_reduce_l2_default_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_l2_default_axes_keepdims_random_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_default_axes_keepdims_example_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_default_axes_keepdims_random_cpu') backend_test.exclude( r'test_reduce_sum_square_default_axes_keepdims_example_cpu') backend_test.exclude( r'test_reduce_sum_square_default_axes_keepdims_random_cpu') backend_test.exclude(r'test_resize_downsample_scales_cubic_antialias_cpu') backend_test.exclude(r'test_resize_downsample_scales_linear_antialias_cpu') backend_test.exclude(r'test_resize_downsample_sizes_cubic_antialias_cpu') backend_test.exclude(r'test_resize_downsample_sizes_linear_antialias_cpu') backend_test.exclude( r'test_resize_downsample_sizes_nearest_not_larger_cpu') backend_test.exclude( r'test_resize_downsample_sizes_nearest_not_smaller_cpu') backend_test.exclude(r'test_resize_tf_crop_and_resize_axes_2_3_cpu') backend_test.exclude(r'test_resize_tf_crop_and_resize_axes_3_2_cpu') backend_test.exclude(r'test_resize_upsample_scales_nearest_axes_2_3_cpu') backend_test.exclude(r'test_resize_upsample_scales_nearest_axes_3_2_cpu') backend_test.exclude(r'test_resize_upsample_sizes_nearest_axes_2_3_cpu') backend_test.exclude(r'test_resize_upsample_sizes_nearest_axes_3_2_cpu') backend_test.exclude(r'test_resize_upsample_sizes_nearest_not_larger_cpu') def disabled_tests_onnx_1_14_0(backend_test): # errors # from OnnxBackendNodeModelTest backend_test.exclude(r'test_basic_deform_conv_with_padding_cpu') backend_test.exclude(r'test_basic_deform_conv_without_padding_cpu') backend_test.exclude(r'test_center_crop_pad_crop_negative_axes_hwc_cpu') backend_test.exclude( r'test_center_crop_pad_crop_negative_axes_hwc_expanded_cpu') backend_test.exclude(r'test_constant_pad_negative_axes_cpu') backend_test.exclude(r'test_deform_conv_with_mask_bias_cpu') backend_test.exclude(r'test_deform_conv_with_multiple_offset_groups_cpu') backend_test.exclude(r'test_equal_string_broadcast_cpu') backend_test.exclude(r'test_equal_string_cpu') backend_test.exclude(r'test_lppool_1d_default_cpu') backend_test.exclude(r'test_lppool_2d_default_cpu') backend_test.exclude(r'test_lppool_2d_dilations_cpu') backend_test.exclude(r'test_lppool_2d_pads_cpu') backend_test.exclude(r'test_lppool_2d_same_lower_cpu') backend_test.exclude(r'test_lppool_2d_same_upper_cpu') backend_test.exclude(r'test_lppool_2d_strides_cpu') backend_test.exclude(r'test_lppool_3d_default_cpu') backend_test.exclude( r'test_resize_downsample_scales_linear_half_pixel_symmetric_cpu') backend_test.exclude( r'test_resize_upsample_scales_linear_half_pixel_symmetric_cpu') backend_test.exclude(r'test_split_to_sequence_1_cpu') backend_test.exclude(r'test_split_to_sequence_2_cpu') backend_test.exclude(r'test_split_to_sequence_nokeepdims_cpu') backend_test.exclude(r'test_wrap_pad_cpu') # Scale and bias shape in GroupNorm were changed in 1.16.0 from num_groups to channels; MIGX implementation does not support the older version backend_test.exclude(r'test_group_normalization_epsilon_cpu') backend_test.exclude(r'test_group_normalization_example_cpu') def disabled_tests_onnx_1_16_0(backend_test): backend_test.exclude(r'test_dft_axis_opset19_cpu') backend_test.exclude(r'test_dft_inverse_opset19_cpu') backend_test.exclude(r'test_dft_opset19_cpu') backend_test.exclude( r'test_gridsample_bicubic_align_corners_0_additional_1_cpu') backend_test.exclude( r'test_gridsample_bicubic_align_corners_1_additional_1_cpu') backend_test.exclude( r'test_gridsample_volumetric_bilinear_align_corners_0_cpu') backend_test.exclude( r'test_gridsample_volumetric_bilinear_align_corners_1_cpu') backend_test.exclude( r'test_gridsample_volumetric_nearest_align_corners_0_cpu') backend_test.exclude( r'test_gridsample_volumetric_nearest_align_corners_1_cpu') backend_test.exclude(r'test_quantizelinear_int16_cpu') backend_test.exclude(r'test_quantizelinear_uint16_cpu') backend_test.exclude(r'test_qlinearmatmul_2D_int8_float16_cpu') backend_test.exclude(r'test_qlinearmatmul_2D_int8_float32_cpu') backend_test.exclude(r'test_qlinearmatmul_2D_uint8_float16_cpu') backend_test.exclude(r'test_qlinearmatmul_2D_uint8_float32_cpu') backend_test.exclude(r'test_qlinearmatmul_3D_int8_float16_cpu') backend_test.exclude(r'test_qlinearmatmul_3D_int8_float32_cpu') backend_test.exclude(r'test_qlinearmatmul_3D_uint8_float16_cpu') backend_test.exclude(r'test_qlinearmatmul_3D_uint8_float32_cpu') backend_test.exclude(r'test_reduce_l1_empty_set_cpu') backend_test.exclude(r'test_reduce_l1_empty_set_expanded_cpu') backend_test.exclude(r'test_reduce_l2_empty_set_cpu') backend_test.exclude(r'test_reduce_l2_empty_set_expanded_cpu') backend_test.exclude(r'test_reduce_log_sum_empty_set_cpu') backend_test.exclude(r'test_reduce_log_sum_empty_set_expanded_cpu') backend_test.exclude(r'test_reduce_log_sum_exp_empty_set_cpu') backend_test.exclude(r'test_reduce_log_sum_exp_empty_set_expanded_cpu') backend_test.exclude(r'test_reduce_max_bool_inputs_cpu') backend_test.exclude(r'test_reduce_min_bool_inputs_cpu') # TODO: empty set ReduceOps tests are generating dynamic shapes backend_test.exclude(r'test_reduce_min_empty_set_cpu') backend_test.exclude(r'test_reduce_prod_empty_set_cpu') backend_test.exclude(r'test_reduce_sum_empty_set_cpu') backend_test.exclude( r'test_reduce_sum_empty_set_non_reduced_axis_zero_cpu') backend_test.exclude(r'test_reduce_sum_square_empty_set_cpu') backend_test.exclude(r'test_reduce_sum_square_empty_set_expanded_cpu') # TODO: Pooling tests are failing with shape mismatches, look into it. backend_test.exclude( r'test_averagepool_3d_dilations_large_count_include_pad_is_0_ceil_mode_is_True_cpu' ) backend_test.exclude( r'test_averagepool_3d_dilations_large_count_include_pad_is_1_ceil_mode_is_True_cpu' ) backend_test.exclude(r'test_maxpool_2d_ceil_output_size_reduce_by_one_cpu') backend_test.exclude(r'test_maxpool_3d_dilations_use_ref_impl_large_cpu') def disabled_tests_onnx_1_17_0(backend_test): # TODO: empty set ReduceOps tests are generating dynamic shapes backend_test.exclude(r'test_reduce_max_empty_set_cpu') backend_test.exclude(r'test_reduce_sum_empty_axes_input_noop_cpu') # tf_crop_and_resize not supported backend_test.exclude( r'test_resize_tf_crop_and_resize_extrapolation_value_cpu') # keep_aspect_ratio_policy not supported backend_test.exclude(r'test_resize_upsample_sizes_nearest_not_smaller_cpu') def disabled_tests_onnx_1_18_0(backend_test): # src/onnx/onnx_parser.cpp:841: get_type: Prototensor data type 23 not supported backend_test.exclude(r'test_cast_FLOAT16_to_FLOAT4E2M1_cpu') backend_test.exclude(r'test_cast_FLOAT4E2M1_to_FLOAT16_cpu') backend_test.exclude(r'test_cast_FLOAT4E2M1_to_FLOAT_cpu') backend_test.exclude(r'test_cast_FLOAT_to_FLOAT4E2M1_cpu') backend_test.exclude(r'test_dequantizelinear_float4e2m1_cpu') backend_test.exclude(r'test_quantizelinear_float4e2m1_cpu') # src/onnx/checks.cpp:35: check_arg_empty: PARSE_TopK: k input must be constant backend_test.exclude(r'test_top_k_same_values_2d_cpu') backend_test.exclude(r'test_top_k_same_values_cpu') backend_test.exclude(r'test_top_k_same_values_largest_cpu') backend_test.exclude(r'test_top_k_uint64_cpu') #src/shape.cpp:367: lens: SHAPE: lens() called on a dynamic shape backend_test.exclude(r'test_unique_length_1_cpu') # backend_test.exclude(r'test_averagepool_2d_ceil_last_window_starts_on_pad_cpu') def disabled_tests_int4(backend_test): # quantizelinear backend_test.exclude(r'test_quantizelinear_int4') backend_test.exclude(r'test_quantizelinear_uint4') # dequantizelinear backend_test.exclude(r'test_dequantizelinear_int4') backend_test.exclude(r'test_dequantizelinear_uint4') # cast backend_test.exclude(r'test_cast_FLOAT16_to_UINT4') backend_test.exclude(r'test_cast_FLOAT16_to_INT4') backend_test.exclude(r'test_cast_FLOAT_to_INT4') backend_test.exclude(r'test_cast_FLOAT_to_UINT4') backend_test.exclude(r'test_cast_INT4_to_FLOAT') backend_test.exclude(r'test_cast_INT4_to_FLOAT16') backend_test.exclude(r'test_cast_INT4_to_INT8') backend_test.exclude(r'test_cast_UINT4_to_FLOAT') backend_test.exclude(r'test_cast_UINT4_to_FLOAT16') backend_test.exclude(r'test_cast_UINT4_to_INT8') backend_test.exclude(r'test_cast_UINT4_to_UINT8_cpu') def disabled_tests_float8(backend_test): # e4m3fn (Prototensor data type 17 not supported) backend_test.exclude(r'test_dequantizelinear_e4m3fn_cpu') backend_test.exclude(r'test_dequantizelinear_e4m3fn_float16_cpu') backend_test.exclude(r'test_dequantizelinear_e4m3fn_zero_point_cpu') backend_test.exclude(r'test_quantizelinear_e4m3fn_cpu') backend_test.exclude(r'test_cast_FLOAT16_to_FLOAT8E4M3FN_cpu') backend_test.exclude(r'test_cast_FLOAT8E4M3FN_to_FLOAT16_cpu') backend_test.exclude(r'test_cast_FLOAT8E4M3FN_to_FLOAT_cpu') backend_test.exclude(r'test_cast_FLOAT_to_FLOAT8E4M3FN_cpu') backend_test.exclude(r'test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FN_cpu') backend_test.exclude(r'test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FN_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_FLOAT8E4M3FN_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_FLOAT8E4M3FN_expanded_cpu') # e4m3fnuz (Prototensor data type 18 not supported) backend_test.exclude(r'test_cast_FLOAT16_to_FLOAT8E4M3FNUZ_cpu') backend_test.exclude(r'test_cast_FLOAT8E4M3FNUZ_to_FLOAT16_cpu') backend_test.exclude(r'test_cast_FLOAT8E4M3FNUZ_to_FLOAT_cpu') backend_test.exclude(r'test_cast_FLOAT_to_FLOAT8E4M3FNUZ_cpu') backend_test.exclude( r'test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ_cpu') backend_test.exclude(r'test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ_cpu') backend_test.exclude(r'test_castlike_FLOAT8E4M3FNUZ_to_FLOAT_cpu') backend_test.exclude(r'test_castlike_FLOAT8E4M3FNUZ_to_FLOAT_expanded_cpu') backend_test.exclude(r'test_castlike_FLOAT8E4M3FN_to_FLOAT_cpu') backend_test.exclude(r'test_castlike_FLOAT8E4M3FN_to_FLOAT_expanded_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_FLOAT8E4M3FNUZ_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_FLOAT8E4M3FNUZ_expanded_cpu') # e5m2 ( Prototensor data type 19 not supported ) backend_test.exclude(r'test_dequantizelinear_e5m2_cpu') backend_test.exclude(r'test_quantizelinear_e5m2_cpu') backend_test.exclude(r'test_cast_FLOAT16_to_FLOAT8E5M2_cpu') backend_test.exclude(r'test_cast_FLOAT8E5M2_to_FLOAT16_cpu') backend_test.exclude(r'test_cast_FLOAT8E5M2_to_FLOAT_cpu') backend_test.exclude(r'test_cast_FLOAT_to_FLOAT8E5M2_cpu') backend_test.exclude(r'test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2_cpu') backend_test.exclude(r'test_cast_no_saturate_FLOAT_to_FLOAT8E5M2_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_FLOAT8E5M2_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_FLOAT8E5M2_expanded_cpu') # e5m2fnuz (Prototensor data type 20 not supported) backend_test.exclude(r'test_cast_FLOAT16_to_FLOAT8E5M2FNUZ_cpu') backend_test.exclude(r'test_cast_FLOAT8E5M2FNUZ_to_FLOAT16_cpu') backend_test.exclude(r'test_cast_FLOAT8E5M2FNUZ_to_FLOAT_cpu') backend_test.exclude(r'test_cast_FLOAT_to_FLOAT8E5M2FNUZ_cpu') backend_test.exclude( r'test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ_cpu') backend_test.exclude(r'test_cast_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ_cpu') backend_test.exclude(r'test_castlike_FLOAT8E5M2FNUZ_to_FLOAT_cpu') backend_test.exclude(r'test_castlike_FLOAT8E5M2FNUZ_to_FLOAT_expanded_cpu') backend_test.exclude(r'test_castlike_FLOAT8E5M2_to_FLOAT_cpu') backend_test.exclude(r'test_castlike_FLOAT8E5M2_to_FLOAT_expanded_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_FLOAT8E5M2FNUZ_cpu') backend_test.exclude(r'test_castlike_FLOAT_to_FLOAT8E5M2FNUZ_expanded_cpu') def disabled_tests_dynamic_shape(backend_test): # constantofshape backend_test.exclude(r'test_constantofshape_float_ones_cpu') backend_test.exclude(r'test_constantofshape_int_shape_zero_cpu') backend_test.exclude(r'test_constantofshape_int_zeros_cpu') # cumsum backend_test.exclude(r'test_cumsum_1d_cpu') backend_test.exclude(r'test_cumsum_1d_exclusive_cpu') backend_test.exclude(r'test_cumsum_1d_reverse_cpu') backend_test.exclude(r'test_cumsum_1d_reverse_exclusive_cpu') backend_test.exclude(r'test_cumsum_2d_axis_0_cpu') backend_test.exclude(r'test_cumsum_2d_axis_1_cpu') backend_test.exclude(r'test_cumsum_2d_negative_axis_cpu') # expand backend_test.exclude(r'test_expand_dim_changed_cpu') backend_test.exclude(r'test_expand_dim_unchanged_cpu') backend_test.exclude(r'test_expand_shape_model1_cpu') backend_test.exclude(r'test_expand_shape_model2_cpu') backend_test.exclude(r'test_expand_shape_model3_cpu') backend_test.exclude(r'test_expand_shape_model4_cpu') # onehot backend_test.exclude(r'test_onehot_negative_indices_cpu') backend_test.exclude(r'test_onehot_with_axis_cpu') backend_test.exclude(r'test_onehot_with_negative_axis_cpu') backend_test.exclude(r'test_onehot_without_axis_cpu') # range backend_test.exclude(r'test_range_float_type_positive_delta_cpu') backend_test.exclude(r'test_range_int32_type_negative_delta_cpu') # slice backend_test.exclude(r'test_slice_default_axes_cpu') backend_test.exclude(r'test_slice_default_steps_cpu') backend_test.exclude(r'test_slice_negative_axes_cpu') # split backend_test.exclude(r'test_split_variable_parts_1d_opset13_cpu') backend_test.exclude(r'test_split_variable_parts_1d_opset18_cpu') backend_test.exclude(r'test_split_variable_parts_2d_opset13_cpu') backend_test.exclude(r'test_split_variable_parts_2d_opset18_cpu') backend_test.exclude(r'test_split_variable_parts_default_axis_opset13_cpu') backend_test.exclude(r'test_split_variable_parts_default_axis_opset18_cpu') backend_test.exclude(r'test_split_zero_size_splits_opset13_cpu') backend_test.exclude(r'test_split_zero_size_splits_opset18_cpu') # squeeze backend_test.exclude(r'test_squeeze_cpu') backend_test.exclude(r'test_squeeze_negative_axes_cpu') # unsqueeze backend_test.exclude(r'test_unsqueeze_axis_0_cpu') backend_test.exclude(r'test_unsqueeze_axis_1_cpu') backend_test.exclude(r'test_unsqueeze_axis_2_cpu') backend_test.exclude(r'test_unsqueeze_negative_axes_cpu') backend_test.exclude(r'test_unsqueeze_three_axes_cpu') backend_test.exclude(r'test_unsqueeze_two_axes_cpu') backend_test.exclude(r'test_unsqueeze_unsorted_axes_cpu') # unique backend_test.exclude(r'test_unique_not_sorted_without_axis_cpu') backend_test.exclude(r'test_unique_sorted_with_axis_cpu') backend_test.exclude(r'test_unique_sorted_without_axis_cpu') # tile backend_test.exclude(r'test_tile_cpu') backend_test.exclude(r'test_tile_precomputed_cpu') # resize backend_test.exclude(r'test_resize_upsample_scales_nearest_cpu') backend_test.exclude(r'test_resize_downsample_scales_nearest_cpu') backend_test.exclude(r'test_resize_upsample_sizes_nearest_cpu') backend_test.exclude(r'test_resize_downsample_sizes_nearest_cpu') backend_test.exclude( r'test_resize_upsample_sizes_nearest_floor_align_corners_cpu') backend_test.exclude( r'test_resize_upsample_sizes_nearest_round_prefer_ceil_asymmetric_cpu') backend_test.exclude( r'test_resize_upsample_sizes_nearest_ceil_half_pixel_cpu') # reshape backend_test.exclude(r'test_reshape_allowzero_reordered_cpu') backend_test.exclude(r'test_reshape_extended_dims_cpu') backend_test.exclude(r'test_reshape_negative_dim_cpu') backend_test.exclude(r'test_reshape_negative_extended_dims_cpu') backend_test.exclude(r'test_reshape_one_dim_cpu') backend_test.exclude(r'test_reshape_reduced_dims_cpu') backend_test.exclude(r'test_reshape_reordered_all_dims_cpu') backend_test.exclude(r'test_reshape_reordered_last_dims_cpu') backend_test.exclude(r'test_reshape_zero_and_negative_dim_cpu') backend_test.exclude(r'test_reshape_zero_dim_cpu') # reduce backend_test.exclude( r'test_reduce_l1_default_axes_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_l1_default_axes_keepdims_random_expanded_cpu') backend_test.exclude( r'test_reduce_l1_do_not_keepdims_example_expanded_cpu') backend_test.exclude(r'test_reduce_l1_do_not_keepdims_random_expanded_cpu') backend_test.exclude(r'test_reduce_l1_keep_dims_example_expanded_cpu') backend_test.exclude(r'test_reduce_l1_keep_dims_random_expanded_cpu') backend_test.exclude( r'test_reduce_l1_negative_axes_keep_dims_example_expanded_cpu') backend_test.exclude( r'test_reduce_l1_negative_axes_keep_dims_random_expanded_cpu') backend_test.exclude( r'test_reduce_l2_default_axes_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_l2_default_axes_keepdims_random_expanded_cpu') backend_test.exclude( r'test_reduce_l2_do_not_keepdims_example_expanded_cpu') backend_test.exclude(r'test_reduce_l2_do_not_keepdims_random_expanded_cpu') backend_test.exclude(r'test_reduce_l2_keep_dims_example_expanded_cpu') backend_test.exclude(r'test_reduce_l2_keep_dims_random_expanded_cpu') backend_test.exclude( r'test_reduce_l2_negative_axes_keep_dims_example_expanded_cpu') backend_test.exclude( r'test_reduce_l2_negative_axes_keep_dims_random_expanded_cpu') backend_test.exclude(r'test_reduce_log_sum_asc_axes_cpu') backend_test.exclude(r'test_reduce_log_sum_asc_axes_expanded_cpu') backend_test.exclude(r'test_reduce_log_sum_default_cpu') backend_test.exclude(r'test_reduce_log_sum_default_expanded_cpu') backend_test.exclude(r'test_reduce_log_sum_desc_axes_cpu') backend_test.exclude(r'test_reduce_log_sum_desc_axes_expanded_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_default_axes_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_default_axes_keepdims_random_expanded_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_do_not_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_do_not_keepdims_random_expanded_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_keepdims_random_expanded_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_negative_axes_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_log_sum_exp_negative_axes_keepdims_random_expanded_cpu') backend_test.exclude(r'test_reduce_log_sum_negative_axes_cpu') backend_test.exclude(r'test_reduce_log_sum_negative_axes_expanded_cpu') backend_test.exclude(r'test_reduce_max_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_max_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_max_keepdims_example_cpu') backend_test.exclude(r'test_reduce_max_keepdims_random_cpu') backend_test.exclude(r'test_reduce_max_negative_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_max_negative_axes_keepdims_random_cpu') backend_test.exclude(r'test_reduce_mean_default_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_mean_default_axes_keepdims_random_cpu') backend_test.exclude(r'test_reduce_mean_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_mean_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_mean_keepdims_example_cpu') backend_test.exclude(r'test_reduce_mean_keepdims_random_cpu') backend_test.exclude( r'test_reduce_mean_negative_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_mean_negative_axes_keepdims_random_cpu') backend_test.exclude(r'test_reduce_min_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_min_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_min_keepdims_example_cpu') backend_test.exclude(r'test_reduce_min_keepdims_random_cpu') backend_test.exclude(r'test_reduce_min_negative_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_min_negative_axes_keepdims_random_cpu') backend_test.exclude(r'test_reduce_prod_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_prod_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_prod_keepdims_example_cpu') backend_test.exclude(r'test_reduce_prod_keepdims_random_cpu') backend_test.exclude( r'test_reduce_prod_negative_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_prod_negative_axes_keepdims_random_cpu') backend_test.exclude(r'test_reduce_sum_default_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_sum_default_axes_keepdims_random_cpu') backend_test.exclude(r'test_reduce_sum_do_not_keepdims_example_cpu') backend_test.exclude(r'test_reduce_sum_do_not_keepdims_random_cpu') backend_test.exclude(r'test_reduce_sum_empty_axes_input_noop_example_cpu') backend_test.exclude(r'test_reduce_sum_keepdims_example_cpu') backend_test.exclude(r'test_reduce_sum_keepdims_random_cpu') backend_test.exclude(r'test_reduce_sum_negative_axes_keepdims_example_cpu') backend_test.exclude(r'test_reduce_sum_negative_axes_keepdims_random_cpu') backend_test.exclude( r'test_reduce_sum_square_default_axes_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_sum_square_default_axes_keepdims_random_expanded_cpu') backend_test.exclude( r'test_reduce_sum_square_do_not_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_sum_square_do_not_keepdims_random_expanded_cpu') backend_test.exclude( r'test_reduce_sum_square_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_sum_square_keepdims_random_expanded_cpu') backend_test.exclude( r'test_reduce_sum_square_negative_axes_keepdims_example_expanded_cpu') backend_test.exclude( r'test_reduce_sum_square_negative_axes_keepdims_random_expanded_cpu') def create_backend_test(testname=None, target_device=None): if target_device is not None: c2.set_device(target_device) backend_test = MIGraphXBackendTest(c2, __name__) if testname: backend_test.include(testname + '.*') else: # Onnx Operator tests backend_test.include(r'.*test_abs.*') backend_test.include(r'.*test_acos.*') backend_test.include(r'.*test_acosh.*') backend_test.include(r'.*test_add.*') backend_test.include(r'.*test_and.*') backend_test.include(r'.*test_argmax.*') backend_test.include(r'.*test_argmin.*') backend_test.include(r'.*test_asin.*') backend_test.include(r'.*test_asinh.*') backend_test.include(r'.*test_atan.*') backend_test.include(r'.*test_atanh.*') backend_test.include(r'.*test_averagepool.*') backend_test.include(r'.*test_AvgPool.*') backend_test.include(r'.*test_[bB]atch[nN]orm(?!.*training).*') backend_test.include(r'.*test_bitshift.*') backend_test.include(r'.*test_bitwise.*') backend_test.include(r'.*test_ceil.*') backend_test.include(r'.*test_cast_.*') backend_test.include(r'.*test_col2im.*') backend_test.include(r'.*test_compress.*') backend_test.include(r'.*test_concat.*') backend_test.include(r'.*test_constant_.*') backend_test.include(r'.*test_Constant.*') backend_test.include(r'.*test_constantofshape.*') backend_test.include(r'.*test_(basic_)?conv_.*') backend_test.include(r'.*test_Conv[1-3]d.*') backend_test.include(r'.*test_convinteger.*') backend_test.include(r'.*test_convtranspose.*') backend_test.include(r'.*test_ConvTranspose[1-3]d.*') backend_test.include(r'.*test_cos.*') backend_test.include(r'.*test_cosh.*') backend_test.include(r'.*test_cumsum.*') backend_test.include(r'.*test_(basic_)?deform_conv.*') backend_test.include(r'.*test_depthtospace.*') backend_test.include(r'.*test_dequantizelinear.*') backend_test.include(r'.*test_det.*') backend_test.include(r'.*test_dft.*') backend_test.include(r'.*test_div.*') backend_test.include(r'.*test_dropout.*') backend_test.include(r'.*test_einsum.*') backend_test.include(r'.*test_equal.*') backend_test.include(r'.*test_Embedding.*') backend_test.include(r'.*test_erf.*') backend_test.include(r'.*test_exp_.*') backend_test.include(r'.*test_expand.*') backend_test.include(r'.*test_eyelike.*') backend_test.include(r'.*test_flatten.*') backend_test.include(r'.*test_floor.*') backend_test.include(r'.*test_gru.*') backend_test.include(r'.*test_gather.*') backend_test.include(r'.*test_gemm.*') backend_test.include(r'.*test_globalaveragepool.*') backend_test.include(r'.*test_globallppool.*') backend_test.include(r'.*test_globalmaxpool.*') backend_test.include(r'.*test_greater.*') backend_test.include(r'.*test_gridsample.*') backend_test.include(r'.*test_hardmax.*') backend_test.include(r'.*test_identity.*') backend_test.include(r'.*test_if.*') backend_test.include(r'.*test_instancenorm.*') backend_test.include(r'.*test_isinf.*') backend_test.include(r'.*test_isnan.*') backend_test.include(r'.*test_lrn.*') backend_test.include(r'.*test_lstm.*') backend_test.include(r'.*test_log.*') backend_test.include(r'.*test_log2.*') backend_test.include(r'.*test_loop.*') backend_test.include(r'.*test_lpnorm.*') backend_test.include(r'.*test_lppool.*') backend_test.include(r'.*test_matmul.*') backend_test.include(r'.*test_max_.*') backend_test.include(r'.*test_maxpool.*') backend_test.include(r'.*test_MaxPool[1-3]d.*') backend_test.include(r'.*test_maxroipool.*') backend_test.include(r'.*test_maxunpool.*') backend_test.include(r'.*test_mean.*') backend_test.include(r'.*test_melweightmatrix.*') backend_test.include(r'.*test_min.*') backend_test.include(r'.*test_mod.*') backend_test.include(r'.*test_mul.*') backend_test.include(r'.*test_[mM]ultinomial.*') backend_test.include(r'.*test_neg.*') backend_test.include(r'.*test_nonmaxsuppression.*') backend_test.include(r'.*test_nonzero.*') backend_test.include(r'.*test_not.*') backend_test.include(r'.*test_onehot.*') backend_test.include(r'.*optional_get_element.*') backend_test.include(r'.*optional_has_element.*') backend_test.include(r'.*test_or.*') backend_test.include(r'.*test_(constant_|edge_|reflect_|wrap_)?pad.*') backend_test.include( r'.*test_(Constant|Reflection|Replication|Zero)+Pad2d.*') backend_test.include(r'.*test_pow.*') backend_test.include(r'.*test_qlinearconv.*') backend_test.include(r'.*test_qlinearmatmul.*') backend_test.include(r'.*test_quantizelinear.*') backend_test.include(r'.*test_(simple_)?rnn.*') backend_test.include(r'.*test_randomnormal.*') backend_test.include(r'.*test_randomuniform.*') backend_test.include(r'.*test_reciprocal.*') backend_test.include(r'.*test_reduce_max.*') backend_test.include(r'.*test_reduce_mean.*') backend_test.include(r'.*test_reduce_min.*') backend_test.include(r'.*test_reduce_prod.*') backend_test.include(r'.*test_reduce_sum.*') backend_test.include(r'.*test_reshape.*') backend_test.include(r'.*test_resize.*') backend_test.include(r'.*test_reversesequence.*') backend_test.include(r'.*test_roialign.*') backend_test.include(r'.*test_round.*') backend_test.include(r'.*test_stft.*') backend_test.include(r'.*test_scan.*') backend_test.include(r'.*test_scatter.*') backend_test.include(r'.*test_sequence_at.*') backend_test.include(r'.*test_sequence_construct.*') backend_test.include(r'.*test_sequence_empty.*') backend_test.include(r'.*test_sequence_erase.*') backend_test.include(r'.*test_sequence_insert.*') backend_test.include(r'.*test_sequence_length.*') backend_test.include(r'.*test_shape.*') backend_test.include(r'.*test_[sS]igmoid.*') backend_test.include(r'.*test_sign.*') backend_test.include(r'.*test_sin_.*') backend_test.include(r'.*test_sinh.*') backend_test.include(r'.*test_size.*') backend_test.include(r'.*test_slice.*') backend_test.include(r'.*test_spacetodepth.*') backend_test.include(r'.*test_split.*') backend_test.include(r'.*test_split_to_sequence.*') backend_test.include(r'.*test_sqrt.*') backend_test.include(r'.*test_squeeze.*') backend_test.include(r'.*test_squeeze.*') backend_test.include(r'.*test_strnorm.*') backend_test.include(r'.*test_sub.*') backend_test.include(r'.*test_sum.*') backend_test.include(r'.*test_tan_.*') backend_test.include(r'.*test_[tT]anh.*') backend_test.include(r'.*test_tfidfvectorizer.*') backend_test.include(r'.*test_tile.*') backend_test.include(r'.*test_top_k.*') backend_test.include(r'.*test_transpose.*') backend_test.include(r'.*test_tril.*') backend_test.include(r'.*test_triu.*') backend_test.include(r'.*test_unique.*') backend_test.include(r'.*test_unsqueeze.*') backend_test.include(r'.*test_upsample.*') backend_test.include(r'.*test_where.*') backend_test.include(r'.*test_xor.*') # Onnx Function tests backend_test.include(r'.*test_bernoulli.*') backend_test.include(r'.*test_blackmanwindow.*') backend_test.include(r'.*test_castlike.*') backend_test.include(r'.*test_celu.*') backend_test.include(r'.*test_center_crop_pad.*') backend_test.include(r'.*test_clip.*') backend_test.include(r'.*test_dynamicquantizelinear.*') backend_test.include(r'.*test_elu.*') backend_test.include(r'.*test_ELU.*') backend_test.include(r'.*test_GLU.*') backend_test.include(r'.*test_greater_equal.*') backend_test.include(r'.*test_group_normalization.*') backend_test.include(r'.*test_hammingwindow.*') backend_test.include(r'.*test_hannwindow.*') backend_test.include(r'.*test_hardsigmoid.*') backend_test.include(r'.*test_hardswish.*') backend_test.include(r'.*test_layer_normalization.*') backend_test.include(r'.*test_LeakyReLU.*') backend_test.include(r'.*test_leakyrelu.*') backend_test.include(r'.*test_less.*') backend_test.include(r'.*test_Linear.*') backend_test.include(r'.*test_logsoftmax.*') backend_test.include(r'.*test_log_softmax.*') backend_test.include(r'.*test_LogSoftmax.*') backend_test.include(r'.*test_mvn.*') backend_test.include(r'.*test_mish.*') backend_test.include(r'.*test_nllloss.*') backend_test.include(r'.*test_PixelShuffle.*') backend_test.include(r'.*test_PoissonNLLLLoss_no_reduce.*') backend_test.include(r'.*test_prelu.*') backend_test.include(r'.*test_PReLU.*') backend_test.include(r'.*test_range.*') backend_test.include(r'.*test_reduce_l1.*') backend_test.include(r'.*test_reduce_l2.*') backend_test.include(r'.*test_reduce_log.*') backend_test.include(r'.*test_ReLU.*') backend_test.include(r'.*test_relu.*') backend_test.include(r'.*test_selu.*') backend_test.include(r'.*test_SELU.*') backend_test.include(r'.*test_sequence_map.*') backend_test.include(r'.*test_shrink.*') backend_test.include(r'.*test_[sS]oftmax.*') backend_test.include(r'.*test_[sS]oftmin.*') backend_test.include(r'.*test_[sS]oftplus.*') backend_test.include(r'.*test_[sS]oftsign.*') backend_test.include(r'.*test_sce.*') backend_test.include(r'.*test_thresholdedrelu.*') # OnnxBackendPyTorchOperatorModelTest backend_test.include(r'.*test_operator_add_broadcast.*') backend_test.include(r'.*test_operator_addconstant.*') backend_test.include(r'.*test_operator_addmm.*') backend_test.include(r'.*test_operator_add_size1.*') backend_test.include(r'.*test_operator_basic.*') backend_test.include(r'.*test_operator_chunk.*') backend_test.include(r'.*test_operator_clip.*') backend_test.include(r'.*test_operator_concat2.*') backend_test.include(r'.*test_operator_conv_.*') backend_test.include(r'.*test_operator_convtranspose.*') backend_test.include(r'.*test_operator_exp.*') backend_test.include(r'.*test_operator_flatten.*') backend_test.include(r'.*test_operator_index.*') backend_test.include(r'.*test_operator_max_.*') backend_test.include(r'.*test_operator_maxpool.*') backend_test.include(r'.*test_operator_min.*') backend_test.include(r'.*test_operator_mm.*') backend_test.include(r'.*test_operator_negativeloglikelihoodloss.*') backend_test.include(r'.*test_operator_non_float_params.*') backend_test.include(r'.*test_operator_pad.*') backend_test.include(r'.*test_operator_params.*') backend_test.include(r'.*test_operator_permute2.*') backend_test.include(r'.*test_operator_pow.*') backend_test.include(r'.*test_operator_reduced_mean_.*') backend_test.include(r'.*test_operator_reduced_mean_keepdim.*') backend_test.include(r'.*test_operator_reduced_sum_.*') backend_test.include(r'.*test_operator_reduced_sum_keepdim.*') backend_test.include(r'.*test_operator_repeat.*') backend_test.include(r'.*test_operator_selu.*') backend_test.include(r'.*test_operator_softmaxcrossentropyloss.*') backend_test.include(r'.*test_operator_sqrt.*') backend_test.include(r'.*test_operator_symbolic_override.*') backend_test.include(r'.*test_operator_symbolic_override_nested.*') backend_test.include(r'.*test_operator_view.*') # OnnxBackendSimpleModelTest backend_test.include(r'.*test_gradient_of.*') backend_test.include(r'.*test_sequence_model.*') backend_test.include(r'.*test_single_relu_model.*') # OnnxBackendRealModelTest backend_test.include(r'.*test_bvlc_alexnet.*') backend_test.include(r'.*test_densenet121.*') backend_test.include(r'.*test_inception_v1.*') backend_test.include(r'.*test_inception_v2.*') backend_test.include(r'.*test_resnet50.*') backend_test.include(r'.*test_shufflenet.*') backend_test.include(r'.*test_squeezenet.*') backend_test.include(r'.*test_vgg19.*') backend_test.include(r'.*test_zfnet512.*') # Skipped tests # backend_test.include(r'.*test_adagrad.*') # backend_test.include(r'.*test_adam.*') # backend_test.include(r'.*test_ai_onnx_ml.*') # backend_test.include(r'.*test_batchnorm_epsilon_training.*') # backend_test.include(r'.*test_batchnorm_example_training.*') # backend_test.include(r'.*test_momentum.*') # backend_test.include(r'.*test_nesterov_momentum.*') # backend_test.include(r'.*test_training_dropout.*') # Exclude failing tests # from OnnxBackendRealModelTest backend_test.exclude(r'test_inception_v1_cpu') # PRelu OnnxBackendPyTorchConvertedModelTest has wrong dim for broadcasting backend_test.exclude(r'[a-z,_]*PReLU_[0-9]d_multiparam[a-z,_]*') # Remove when float8 is supported disabled_tests_float8(backend_test) # Remove Int4 tests disabled_tests_int4(backend_test) # Remove when dynamic shapes are supported disabled_tests_dynamic_shape(backend_test) # additional cases disabled for a specific onnx version if version.parse(onnx.__version__) >= version.parse("1.7.0"): disabled_tests_onnx_1_7_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.8.0"): disabled_tests_onnx_1_8_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.9.0"): disabled_tests_onnx_1_9_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.10.0"): disabled_tests_onnx_1_10_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.11.0"): disabled_tests_onnx_1_11_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.12.0"): disabled_tests_onnx_1_12_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.13.0"): disabled_tests_onnx_1_13_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.14.0"): disabled_tests_onnx_1_14_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.16.0"): disabled_tests_onnx_1_16_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.17.0"): disabled_tests_onnx_1_17_0(backend_test) if version.parse(onnx.__version__) >= version.parse("1.18.0"): disabled_tests_onnx_1_18_0(backend_test) # import all test cases at global scope to make # them visible to python.unittest. globals().update(backend_test.enable_report().test_cases) return backend_test def parse_args(): parser = argparse.ArgumentParser( os.path.basename(__file__), description='Run the ONNX backend tests using MIGraphX.') # Add an argument to match a single test name, by adding the name to the 'include' filter. # Using -k with python unittest (https://docs.python.org/3/library/unittest.html#command-line-options) # doesn't work as it filters on the test method name (Runner._add_model_test) rather than inidividual # test case names. parser.add_argument( '-t', '--test-name', dest='testname', type=str, help= "Only run tests that match this value. Matching is regex based, and '.*' is automatically appended" ) parser.add_argument('-d', '--device', dest='device', type=str, help="Specify the device to run test on") # parse just our args. python unittest has its own args and arg parsing, and that runs inside unittest.main() args, left = parser.parse_known_args() sys.argv = sys.argv[:1] + left if args.device is not None: print("run on {} device....".format(args.device)) else: print("Default GPU device is used ....") return args if __name__ == '__main__': if sys.version_info < (3, 0): sys.exit() args = parse_args() backend_test = create_backend_test(args.testname, args.device) unittest.main() ROCm-AMDMIGraphX-46524e8/test/py/requirements-onnx.txt000066400000000000000000000027631510465702400224230ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### onnx==1.18.0;python_version>="3.11" onnx==1.14.1;python_version<"3.11" protobuf==4.25.8 numpy==1.26.4;python_version>="3.11" numpy==1.21.6;python_version<"3.11" packaging==23.0 pytest==6.0.1 ROCm-AMDMIGraphX-46524e8/test/py/test_array.py000066400000000000000000000066231510465702400207050ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx, struct, array, sys try: from functools import reduce except: pass def assert_eq(x, y): if x == y: pass else: raise Exception(str(x) + " != " + str(y)) def read_float(b, index): return struct.unpack_from('f', b, index * 4)[0] def write_float(b, index): struct.pack_into('f', b, index * 4) def nelements(lens): return reduce(lambda x, y: x * y, lens, 1) def create_buffer(t, data, shape): a = array.array(t, data) if sys.version_info >= (3, 0): m = memoryview(a.tobytes()) return m.cast(t, shape) else: m = memoryview(a.tostring()) return m def check_argument(a): l = a.tolist() for i in range(len(l)): assert_eq(l[i], read_float(a, i)) def check_shapes(r, m): lens = list(m.shape) strides = [int(s / m.itemsize) for s in m.strides] elements = nelements(lens) assert_eq(r.get_shape().elements(), elements) assert_eq(r.get_shape().lens(), lens) assert_eq(r.get_shape().strides(), strides) def run(p): params = {} for key, value in p.get_parameter_shapes().items(): params[key] = migraphx.generate_argument(value) return p.run(params) def test_shape(shape): data = list(range(nelements(shape))) m = create_buffer('f', data, shape) a = migraphx.argument(m) check_shapes(a, m) assert_eq(a.tolist(), data) def test_input(): if sys.version_info >= (3, 0): test_shape([4]) test_shape([2, 3]) else: data = list(range(4)) m = create_buffer('f', data, [4]) a1 = migraphx.argument(m) a2 = migraphx.argument(bytearray(a1)) check_shapes(a2, m) assert_eq(a1.tolist(), m.tolist()) def test_output(): p = migraphx.parse_onnx("conv_relu_maxpool_test.onnx") p.compile(migraphx.get_target("gpu")) r1 = run(p)[-1] r2 = run(p)[-1] assert_eq(r1, r2) assert_eq(r1.tolist(), r2.tolist()) check_argument(r1) check_argument(r2) m1 = memoryview(r1) m2 = memoryview(r2) check_shapes(r1, m1) check_shapes(r2, m2) test_input() test_output() ROCm-AMDMIGraphX-46524e8/test/py/test_autocast_fp8.py000066400000000000000000000056461510465702400221730ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx def test_autocast_fp8_1(): p1 = migraphx.program() m1 = p1.get_main_module() x = m1.add_parameter("x", shape=migraphx.shape(type='fp8e4m3fnuz_type')) y = m1.add_parameter("y", shape=migraphx.shape(type='fp8e4m3fnuz_type')) sum_op = m1.add_instruction(migraphx.op("add"), [x, y]) m1.add_return([sum_op]) m1 = migraphx.autocast_fp8(p1) p2 = migraphx.program() m2 = p2.get_main_module() y_fp32 = m2.add_parameter("y", shape=migraphx.shape(type='float_type')) x_fp32 = m2.add_parameter("x", shape=migraphx.shape(type='float_type')) y_fp8 = m2.add_instruction( migraphx.op("convert", target_type=int(migraphx.shape.type_t.fp8e4m3fnuz_type)), [y_fp32]) x_fp8 = m2.add_instruction( migraphx.op("convert", target_type=int(migraphx.shape.type_t.fp8e4m3fnuz_type)), [x_fp32]) sum_fp8 = m2.add_instruction(migraphx.op("add"), [x_fp8, y_fp8]) sum_fp32 = m2.add_instruction( migraphx.op("convert", target_type=int(migraphx.shape.type_t.float_type)), [sum_fp8]) m2.add_return([sum_fp32]) assert p1 == p2 def test_autocast_fp8_2(): p1 = migraphx.program() m1 = p1.get_main_module() x = m1.add_parameter("x", shape=migraphx.shape(type='float_type')) y = m1.add_parameter("y", shape=migraphx.shape(type='float_type')) sum = m1.add_instruction(migraphx.op("add"), [x, y]) m1.add_return([sum]) m1 = migraphx.autocast_fp8(p1) p2 = p1 assert p1 == p2 if __name__ == "__main__": test_autocast_fp8_1() test_autocast_fp8_2() ROCm-AMDMIGraphX-46524e8/test/py/test_cpu.py000066400000000000000000000054151510465702400203540ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx, array, sys def test_conv_relu(): p = migraphx.parse_onnx("conv_relu_maxpool_test.onnx") print(p) s1 = p.get_output_shapes()[-1] print("Compiling ...") p.compile(migraphx.get_target("ref")) print(p) s2 = p.get_output_shapes()[-1] assert s1 == s2 params = {} for key, value in p.get_parameter_shapes().items(): print("Parameter {} -> {}".format(key, value)) params[key] = migraphx.generate_argument(value) r = p.run(params)[-1] print(r) def create_buffer(t, data, shape): a = array.array(t, data) if sys.version_info >= (3, 0): m = memoryview(a.tobytes()) return m.cast(t, shape) else: m = memoryview(a.tostring()) return m def test_add_scalar(): p = migraphx.parse_onnx("add_scalar_test.onnx") print(p) s1 = p.get_output_shapes()[-1] print("Compiling ...") p.compile(migraphx.get_target("ref")) print(p) s2 = p.get_output_shapes()[-1] assert s1 == s2 d0 = list(range(120)) arg0 = create_buffer("B", d0, [2, 3, 4, 5]) d1 = [1] arg1 = create_buffer("B", d1, ()) params = {} params["0"] = migraphx.argument(arg0) params["1"] = migraphx.argument(arg1) r = p.run(params)[-1] print(r) def test_module(): p = migraphx.parse_onnx("add_scalar_test.onnx") mm = p.get_main_module() print(p) print(mm) test_conv_relu() test_module() if sys.version_info >= (3, 0): test_add_scalar() ROCm-AMDMIGraphX-46524e8/test/py/test_gpu.py000066400000000000000000000130101510465702400203460ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx def test_conv_relu(): p = migraphx.parse_onnx("conv_relu_maxpool_test.onnx") print(p) print("Compiling ...") # set offload_copy, fast_match to true p.compile(migraphx.get_target("gpu"), True, True) print(p) params = {} for key, value in p.get_parameter_shapes().items(): print("Parameter {} -> {}".format(key, value)) params[key] = migraphx.generate_argument(value) r = p.run(params) print(r) def test_sub_uint64(): p = migraphx.parse_onnx("implicit_sub_bcast_test.onnx") print(p) print("Compiling ...") p.compile(migraphx.get_target("gpu")) print(p) params = {} shapes = p.get_parameter_shapes() params["0"] = migraphx.create_argument( migraphx.shape(type='uint64_type', lens=shapes["0"].lens()), list(range(120))) params["1"] = migraphx.create_argument( migraphx.shape(type='uint64_type', lens=shapes["1"].lens()), list(range(20))) r = p.run(params) print(r) def test_neg_int64(): p = migraphx.parse_onnx("neg_test.onnx") print(p) print("Compiling ...") p.compile(migraphx.get_target("gpu")) print(p) params = {} shapes = p.get_parameter_shapes() params["0"] = migraphx.create_argument( migraphx.shape(type='int64_type', lens=shapes["0"].lens()), list(range(6))) r = p.run(params) print(r) def test_nonzero(): p = migraphx.parse_onnx("nonzero_dynamic_test.onnx") print(p) print("Compiling ...") p.compile(migraphx.get_target("gpu")) print(p) params = {} shapes = p.get_parameter_shapes() params["data"] = migraphx.create_argument( migraphx.shape(type='bool_type', lens=shapes["data"].lens()), [1, 1, 0, 1]) r = p.run(params) print(r) def test_fp16_imagescaler(): p = migraphx.parse_onnx("imagescaler_half_test.onnx") print(p) s1 = p.get_output_shapes()[-1] print("Compiling ...") p.compile(migraphx.get_target("gpu")) print(p) s2 = p.get_output_shapes()[-1] assert s1 == s2 params = {} shapes = p.get_parameter_shapes() params["0"] = migraphx.generate_argument( migraphx.shape(type='half_type', lens=shapes["0"].lens()), 768) r = p.run(params)[-1] print(r) def test_if_pl(): p = migraphx.parse_onnx("if_pl_test.onnx") print(p) s1 = p.get_output_shapes()[-1] print("Compiling ...") p.compile(migraphx.get_target("gpu")) print(p) s2 = p.get_output_shapes()[-1] assert s1 == s2 params = {} shapes = p.get_parameter_shapes() params["x"] = migraphx.fill_argument( migraphx.shape(type='float_type', lens=shapes["x"].lens()), 1) params["y"] = migraphx.fill_argument( migraphx.shape(type='float_type', lens=shapes["y"].lens()), 2.0) params["cond"] = migraphx.fill_argument( migraphx.shape(type="bool", lens=[1], strides=[0]), 1) r = p.run(params)[-1] print(r) def test_dyn_batch(): a = migraphx.shape.dynamic_dimension(1, 4, {2, 4}) b = migraphx.shape.dynamic_dimension(3, 3) c = migraphx.shape.dynamic_dimension(32, 32) dd_map = {"0": [a, b, c, c]} p = migraphx.parse_onnx("conv_relu_maxpool_test.onnx", map_dyn_input_dims=dd_map) print(p) print("Compiling ...") p.compile(migraphx.get_target("gpu")) print(p) def run_prog(batch_size): params = {} for key, value in p.get_parameter_shapes().items(): # convert to a static shape if value.dynamic(): dds = value.dyn_dims() new_lens = [] for dd in dds: if dd.is_fixed(): new_lens.append(dd.min) else: new_lens.append(batch_size) s = migraphx.shape(type=value.type_string(), lens=new_lens) else: s = value print("Parameter {} -> {}".format(key, s)) params[key] = migraphx.generate_argument(s) r = p.run(params) print(r) run_prog(1) run_prog(2) run_prog(3) run_prog(4) test_conv_relu() test_sub_uint64() test_neg_int64() test_fp16_imagescaler() test_if_pl() test_nonzero() test_dyn_batch() ROCm-AMDMIGraphX-46524e8/test/py/test_gpu_async.py000066400000000000000000000105051510465702400215510ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx import ctypes import os import glob def test_conv_relu(): # Full path of the library is needed to fix an issue on sles # where the library is not loaded otherwise. # We check for the presence of library at the following paths, # in the order listed below: # # 1. 'rocm_path' environment variable # 2. /opt/rocm # 3. /opt/rocm-* # # If the library is not found at any of these paths, we fall back # to the library path being detected automatically. library = "libamdhip64.so" # Environment variable containing path to rocm rocm_path_env_var = "rocm_path" # Check for rocm_path, default to /opt/rocm if it does not exist. rocm_path_var = os.getenv(rocm_path_env_var, default="/opt/rocm") # Join the paths to the library to get full path, # e.g. /opt/rocm/lib/libamdhip64.so library_file = os.path.join(rocm_path_var, "lib", library) # Check if the library file exists at the specified path if os.path.exists(library_file): # Replace library name by full path to the library library = library_file else: # Pattern match to look for path to different # rocm versions: /opt/rocm-* rocm_path_pattern = "/opt/rocm-*/lib/libamdhip64.so" matching_libraries = glob.glob(rocm_path_pattern) if matching_libraries: # Replace library name by full path to the first # library found. library = matching_libraries[0] # Loads library either by using the full path to the # library, if it has been detected earlier, # or, proceeds to load the library based on the name # of the library. hip = ctypes.cdll.LoadLibrary(library) p = migraphx.parse_onnx("conv_relu_maxpool_test.onnx") print(p) print("Compiling ...") # Need to have offload_copy = False to avoid syncs() back to the host device p.compile(migraphx.get_target("gpu"), offload_copy=False) print(p) params = {} # Using default value in api for hipSuccess which is always 0 hipSuccess = ctypes.c_long(0) # Alloc a stream stream = ctypes.c_void_p() err = ctypes.c_long( hip.hipStreamCreateWithFlags(ctypes.byref(stream), ctypes.c_uint(0))) if err.value != hipSuccess.value: print("FAILED hipStreamCreate") return err # Use to_gpu to push generated argument to the GPU before we perform a run for key, value in p.get_parameter_shapes().items(): params[key] = migraphx.to_gpu(migraphx.generate_argument(value)) result = migraphx.from_gpu( p.run_async(params, stream.value, "ihipStream_t")[-1]) # Wait for all commands in stream to complete err = ctypes.c_long(hip.hipStreamSynchronize(stream)) if err.value != hipSuccess.value: print("FAILED: hipStreamSyncronize") return err # Cleanup Stream err = ctypes.c_long(hip.hipStreamDestroy(stream)) if err.value != hipSuccess.value: print("FAILED: hipStreamDestroy") return err print(result) test_conv_relu() ROCm-AMDMIGraphX-46524e8/test/py/test_gpu_offload.py000066400000000000000000000054511510465702400220520ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx def test_conv_relu(): p = migraphx.parse_onnx("conv_relu_maxpool_test.onnx") print(p) print("Compiling ...") p.compile(migraphx.get_target("gpu"), offload_copy=False) print(p) params = {} for key, value in p.get_parameter_shapes().items(): print("Parameter {} -> {}".format(key, value)) params[key] = migraphx.to_gpu(migraphx.generate_argument(value)) r = migraphx.from_gpu(p.run(params)[-1]) print(r) # TODO: placeholder until tuple shapes and arguments exposed #def test_dyn_batch(): # a = migraphx.shape.dynamic_dimension(1, 4, {2, 4}) # b = migraphx.shape.dynamic_dimension(3, 3) # c = migraphx.shape.dynamic_dimension(32, 32) # dd_map = {"0": [a, b, c, c]} # p = migraphx.parse_onnx("conv_relu_maxpool_test.onnx", # map_dyn_input_dims=dd_map) # print(p) # print("Compiling ...") # p.compile(migraphx.get_target("gpu"), offload_copy=False) # # print(p) # # def run_prog(batch_size): # params = {} # for key, value in p.get_parameter_shapes().items(): # print("Parameter {} -> {}".format(key, value)) # params[key] = migraphx.to_gpu( # migraphx.generate_argument(value.to_static(batch_size))) # # print("before_output") # outputs = p.run(params) # print(outputs) # r = migraphx.from_gpu(p.run(params)[-1]) # print(r) # # run_prog(1) # run_prog(2) # run_prog(3) # run_prog(4) test_conv_relu() ROCm-AMDMIGraphX-46524e8/test/py/test_instruction.py000066400000000000000000000041451510465702400221450ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx def test_instruction_shape(): p = migraphx.program() mm = p.get_main_module() input_shape = migraphx.shape(lens=[4, 4, 64], type="half_type") i = mm.add_parameter("x", input_shape) i2 = mm.add_instruction(migraphx.op("reshape", dims=[16, 64]), [i]) out_shape = i2.shape() assert out_shape.lens() == [16, 64] assert out_shape.strides() == [64, 1] assert out_shape.type_string() == "half_type" def test_instruction_op(): p = migraphx.program() mm = p.get_main_module() input_shape = migraphx.shape(lens=[2, 24]) i = mm.add_parameter("x", input_shape) i2 = mm.add_instruction(migraphx.op("relu"), [i]) out_op = i2.op() assert out_op.name() == "relu" if __name__ == "__main__": test_instruction_shape() test_instruction_op() ROCm-AMDMIGraphX-46524e8/test/py/test_literal.py000066400000000000000000000050141510465702400212140ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx def test_add_fill(): p = migraphx.program() mm = p.get_main_module() x = mm.add_literal( migraphx.fill_argument(migraphx.shape(type='float_type', lens=[3, 3]), 1)) y = mm.add_literal( migraphx.fill_argument(migraphx.shape(type='float_type', lens=[3, 3]), 2)) add_op = mm.add_instruction(migraphx.op("add"), [x, y]) mm.add_return([add_op]) p.compile(migraphx.get_target("ref")) params = {} output = p.run(params)[-1].tolist() assert output == list([3.0] * 9) def test_add_create(): p = migraphx.program() mm = p.get_main_module() x = mm.add_literal( migraphx.create_argument( migraphx.shape(type='float_type', lens=[2, 2]), [1, 2, 3, 4])) y = mm.add_literal( migraphx.create_argument( migraphx.shape(type='float_type', lens=[2, 2]), [5, 6, 7, 8])) add_op = mm.add_instruction(migraphx.op("add"), [x, y]) mm.add_return([add_op]) p.compile(migraphx.get_target("ref")) params = {} output = p.run(params)[-1].tolist() assert output == list([6, 8, 10, 12]) if __name__ == "__main__": test_add_fill() test_add_create() ROCm-AMDMIGraphX-46524e8/test/py/test_module_construct.py000066400000000000000000000066221510465702400231570ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx, array, sys def create_buffer(t, data, shape): a = array.array(t, data) m = memoryview(a.tobytes()) return m.cast(t, shape) def test_add_op(): p = migraphx.program() mm = p.get_main_module() x = mm.add_literal(create_buffer('f', [1.0] * 9, (3, 3))) y = mm.add_literal(create_buffer('f', [2.0] * 9, (3, 3))) add_op = mm.add_instruction(migraphx.op("add"), [x, y]) mm.add_return([add_op]) p.compile(migraphx.get_target("ref")) params = {} output = p.run(params)[-1].tolist() assert output == list([3.0] * 9) def test_if_then_else(): param_shape = migraphx.shape(lens=[3, 3], type="float") cond_shape = migraphx.shape(type="bool", lens=[1], strides=[0]) def create_program(): p = migraphx.program() mm = p.get_main_module() cond = mm.add_parameter("cond", cond_shape) x = mm.add_parameter("x", param_shape) y = mm.add_parameter("y", param_shape) then_mod = p.create_module("If_0_if") x_identity = then_mod.add_instruction(migraphx.op("identity"), [x]) then_mod.add_return([x_identity]) else_mod = p.create_module("If_0_else") y_identity = else_mod.add_instruction(migraphx.op("identity"), [y]) else_mod.add_return([y_identity]) if_ins = mm.add_instruction(migraphx.op("if"), [cond], [then_mod, else_mod]) ret = mm.add_instruction(migraphx.op("get_tuple_elem", **{"index": 0}), [if_ins]) mm.add_return([ret]) return p params = {} params["x"] = migraphx.generate_argument(param_shape) params["y"] = migraphx.generate_argument(param_shape) def run_prog(cond): p = create_program() p.compile(migraphx.get_target("ref")) params["cond"] = migraphx.fill_argument(cond_shape, cond) output = p.run(params)[-1] return output assert run_prog(True) == params["x"] assert run_prog(False) == params["y"] if __name__ == "__main__": if sys.version_info >= (3, 0): test_add_op() test_if_then_else() ROCm-AMDMIGraphX-46524e8/test/py/test_numpy.py000066400000000000000000000035001510465702400207260ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx import numpy as np def test_add_op(): p = migraphx.program() mm = p.get_main_module() x = mm.add_literal(np.ones((3, 3), dtype='float32')) y = mm.add_literal(2 * np.ones((3, 3), dtype='float32')) add_op = mm.add_instruction(migraphx.op("add"), [x, y]) mm.add_return([add_op]) p.compile(migraphx.get_target("ref")) params = {} output = p.run(params)[-1].tolist() assert output == list(3 * np.ones((9), dtype='float32')) if __name__ == "__main__": test_add_op() ROCm-AMDMIGraphX-46524e8/test/py/test_op.py000066400000000000000000000031661510465702400202040ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx def test_add_op(): add_op = migraphx.op("add") name = add_op.name() assert name == "add" def test_reduce_mean(): reduce_mean_op = migraphx.op("reduce_mean", **{"axes": [1, 2, 3, 4]}) name = reduce_mean_op.name() assert name == "reduce_mean" test_add_op() test_reduce_mean() ROCm-AMDMIGraphX-46524e8/test/py/test_save_load.py000066400000000000000000000053451510465702400215240ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx, array, tempfile, sys def test_conv_relu(format): p1 = migraphx.parse_onnx("conv_relu_maxpool_test.onnx") print(p1) s1 = p1.get_output_shapes()[-1] with tempfile.NamedTemporaryFile() as t: migraphx.save(p1, t.name, format=format) p2 = migraphx.load(t.name, format=format) print(p2) s2 = p2.get_output_shapes()[-1] assert s1 == s2 assert p1.sort() == p2.sort() def test_save_load_buffer(): p1 = migraphx.parse_onnx("conv_relu_maxpool_test.onnx") print(p1) s1 = p1.get_output_shapes()[-1] p1_bytes = migraphx.save_buffer(p1) assert isinstance(p1_bytes, bytes) p2 = migraphx.load_buffer(p1_bytes) print(p2) s2 = p2.get_output_shapes()[-1] assert s1 == s2 assert p1.sort() == p2.sort() def create_buffer(t, data, shape): a = array.array(t, data) if sys.version_info >= (3, 0): m = memoryview(a.tobytes()) return m.cast(t, shape) else: m = memoryview(a.tostring()) return m def test_load_save_arg(): data = [1,2,3,4] buffer1 = create_buffer('f', data, [2,2]) arg1 = migraphx.argument(buffer1) migraphx.argument.save(arg1, 'load_save_arg.msgpack') arg2 = migraphx.argument.load('load_save_arg.msgpack') assert arg1 == arg2 if __name__ == "__main__": test_load_save_arg() test_conv_relu('msgpack') test_conv_relu('json') test_save_load_buffer() ROCm-AMDMIGraphX-46524e8/test/py/test_shape.py000066400000000000000000000071531510465702400206660ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx def test_create_shape(): s = migraphx.shape(lens=[1, 64, 3, 3]) assert s.standard() assert s.packed() assert s.lens() == [1, 64, 3, 3] assert s.ndim() == 4 def test_create_shape_transposed(): s = migraphx.shape(lens=[1, 64, 3, 3], permutation=[0, 2, 3, 1]) assert not s.standard() assert s.packed() assert s.transposed() assert s.lens() == [1, 64, 3, 3] assert s.strides() == [64 * 3 * 3, 1, 64 * 3, 64] assert s.ndim() == 4 def test_create_shape_broadcast(): s = migraphx.shape(lens=[1, 64, 3, 3], strides=[0, 1, 0, 0]) assert s.broadcasted() assert s.lens() == [1, 64, 3, 3] assert s.strides() == [0, 1, 0, 0] def test_create_shape_type(): s = migraphx.shape(type='int64_t') assert s.type_string() == 'int64_type' assert s.type_size() == 8 s = migraphx.shape(type='uint8_t') assert s.type_string() == "uint8_type" assert s.type_size() == 1 s = migraphx.shape(type='float') assert s.type_size() == 4 def test_create_dyn_dims(): a = migraphx.shape.dynamic_dimension() assert a.is_fixed() assert a.min == 0 b = migraphx.shape.dynamic_dimension(4, 4) assert b.is_fixed() assert b.max == 4 c = migraphx.shape.dynamic_dimension(1, 4, {2, 4}) assert not c.is_fixed() assert c.min == 1 assert c.max == 4 assert c.optimals == {2, 4} dyn_dims = [a, b] dyn_dims.append(c) assert dyn_dims[1] == b def test_create_dyn_shape(): a = migraphx.shape.dynamic_dimension(1, 4, {2, 4}) b = migraphx.shape.dynamic_dimension(4, 4) dds = [a, b] dyn_shape = migraphx.shape(type='float', dyn_dims=dds) assert dyn_shape.dynamic() assert dyn_shape.dyn_dims()[0].min == dds[0].min assert dyn_shape.dyn_dims()[0].max == dds[0].max assert dyn_shape.dyn_dims()[0].optimals == dds[0].optimals def test_type_enum(): mgx_types = [ 'bool_type', 'double_type', 'float_type', 'half_type', 'int16_type', 'int32_type', 'int64_type', 'int8_type', 'uint16_type', 'uint32_type', 'uint64_type', 'uint8_type' ] for t in mgx_types: assert hasattr(migraphx.shape.type_t, t) if __name__ == "__main__": test_create_shape() test_create_shape_broadcast() test_create_shape_type() test_create_dyn_dims() test_create_dyn_shape() ROCm-AMDMIGraphX-46524e8/test/quantization.cpp000066400000000000000000001574141510465702400207650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test.hpp" #include static void optimize_prog_int8(migraphx::program& prog) { migraphx::run_passes(prog, {migraphx::simplify_qdq{}, migraphx::eliminate_common_subexpression{}, migraphx::dead_code_elimination{}}); } TEST_CASE(param_add) { auto create_program_float = [](bool add_return = false) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto sum = mm->add_instruction(migraphx::make_op("add"), p1, p2); if(add_return) { mm->add_return({sum}); } return p; }; auto create_program_half = [](bool add_return = false) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto hp1 = mm->add_instruction(migraphx::make_op("convert"), p1); auto hp2 = mm->add_instruction(migraphx::make_op("convert"), p2); auto hs = mm->add_instruction(migraphx::make_op("add"), hp1, hp2); auto fs = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), hs); if(add_return) { mm->add_return({fs}); } else { mm->add_instruction(migraphx::make_op("identity"), {fs}); } return p; }; { auto p1 = create_program_float(); auto p2 = create_program_half(); migraphx::quantize_fp16(p1); EXPECT(p1 == p2); } { auto p1 = create_program_float(); auto p2 = create_program_half(); migraphx::quantize_fp16(p1, {"add"}); EXPECT(p1 == p2); } { auto p1 = create_program_float(true); auto p2 = create_program_half(true); migraphx::quantize_fp16(p1); EXPECT(p1 == p2); } { auto p1 = create_program_float(true); auto p2 = create_program_half(true); migraphx::quantize_fp16(p1, {"add"}); EXPECT(p1 == p2); } } TEST_CASE(param_add_sub) { auto create_program_float = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto sum = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto diff = mm->add_instruction(migraphx::make_op("sub"), sum, p2); auto r = mm->add_instruction(migraphx::make_op("add"), diff, p1); mm->add_return({r}); return p; }; auto create_program_half_add = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto hp1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p1); auto hp2 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p2); auto hsum = mm->add_instruction(migraphx::make_op("add"), hp1, hp2); auto sum = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), hsum); auto diff = mm->add_instruction(migraphx::make_op("sub"), sum, p2); auto hdiff = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), diff); auto hadd = mm->add_instruction(migraphx::make_op("add"), hdiff, hp1); auto fadd = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), hadd); mm->add_return({fadd}); return p; }; auto create_program_half_sub = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto sum = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto hsum = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), sum); auto hp2 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p2); auto hdiff = mm->add_instruction(migraphx::make_op("sub"), hsum, hp2); auto diff = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), hdiff); auto r = mm->add_instruction(migraphx::make_op("add"), diff, p1); mm->add_return({r}); return p; }; { auto p1 = create_program_float(); auto p2 = create_program_half_add(); migraphx::quantize_fp16(p1, {"add"}); EXPECT(p1 == p2); } { auto p1 = create_program_float(); auto p2 = create_program_half_sub(); migraphx::quantize_fp16(p1, {"sub"}); EXPECT(p1 == p2); } { auto create_program_fp16 = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto hp1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p1); auto hp2 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p2); auto hsum = mm->add_instruction(migraphx::make_op("add"), hp1, hp2); auto sum = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), hsum); auto hsum1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), sum); auto p3 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p2); auto diff = mm->add_instruction(migraphx::make_op("sub"), hsum1, p3); auto fdiff = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), diff); auto hdiff1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), fdiff); auto p4 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p1); auto res = mm->add_instruction(migraphx::make_op("add"), hdiff1, p4); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), res); mm->add_return({r}); return p; }; auto create_program_quant_fp16 = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto hp1 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p1); auto hp2 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), p2); auto hsum = mm->add_instruction(migraphx::make_op("add"), hp1, hp2); auto hdiff = mm->add_instruction(migraphx::make_op("sub"), hsum, hp2); auto hres = mm->add_instruction(migraphx::make_op("add"), hdiff, hp1); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), hres); mm->add_return({r}); return p; }; auto p0 = create_program_float(); migraphx::run_passes(p0, {migraphx::truncate_float_pass{{"all"}, migraphx::shape::half_type}, migraphx::dead_code_elimination{}}); EXPECT(p0 == create_program_fp16()); auto p1 = create_program_float(); migraphx::quantize_fp16(p1); EXPECT(p1 == create_program_quant_fp16()); } } TEST_CASE(literal_add) { auto create_program_float = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data(2 * 3); std::iota(data.begin(), data.end(), 1.0f); auto l1 = mm->add_literal(migraphx::literal(s, data)); auto l2 = mm->add_literal(migraphx::literal(s, data)); mm->add_instruction(migraphx::make_op("add"), l1, l2); return p; }; auto create_program_half = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 3}}; std::vector data(2 * 3); std::iota(data.begin(), data.end(), 1.0f); auto l1 = mm->add_literal(migraphx::literal(s, data)); auto l2 = mm->add_literal(migraphx::literal(s, data)); auto hs = mm->add_instruction(migraphx::make_op("add"), l1, l2); auto fs = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), hs); mm->add_instruction(migraphx::make_op("identity"), fs); return p; }; { auto p1 = create_program_float(); auto p2 = create_program_half(); migraphx::quantize_fp16(p1, {"all"}); migraphx::run_passes(*p1.get_main_module(), {migraphx::propagate_constant{}, migraphx::dead_code_elimination{}}); migraphx::run_passes(*p2.get_main_module(), {migraphx::propagate_constant{}, migraphx::dead_code_elimination{}}); EXPECT(p1 == p2); } { auto p1 = create_program_float(); auto p2 = create_program_half(); migraphx::quantize_fp16(p1, {"add"}); migraphx::run_passes(*p1.get_main_module(), {migraphx::propagate_constant{}, migraphx::dead_code_elimination{}}); migraphx::run_passes(*p2.get_main_module(), {migraphx::propagate_constant{}, migraphx::dead_code_elimination{}}); EXPECT(p1 == p2); } } TEST_CASE(fp16_subgraph) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {1}}; auto l1 = mm->add_literal(migraphx::literal(sd, {1})); auto l2 = mm->add_literal(migraphx::literal(sd, {2})); auto l3 = mm->add_literal(migraphx::literal(sd, {3})); migraphx::shape sx{migraphx::shape::float_type, {1, 4}}; migraphx::shape sy{migraphx::shape::float_type, {3, 4}}; migraphx::shape sc{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", sc); auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sy); auto* then_mod = p.create_module("If_6_if"); auto m1 = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l1); auto add0 = then_mod->add_instruction(migraphx::make_op("add"), x, m1); auto m2 = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l2); auto mul0 = then_mod->add_instruction(migraphx::make_op("mul"), y, m2); auto mfp16 = then_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), mul0); then_mod->add_return({add0, mul0, mfp16}); auto* else_mod = p.create_module("If_6_else"); auto me1 = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l3); auto mul1 = else_mod->add_instruction(migraphx::make_op("mul"), x, me1); auto me2 = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), l3); auto add1 = else_mod->add_instruction(migraphx::make_op("add"), y, me2); auto afp16 = else_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), add1); else_mod->add_return({mul1, add1, afp16}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); auto r16 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), ret); mm->add_return({r0, r1, r16}); return p; }; auto create_fp16_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::half_type, {1}}; migraphx::shape sx{migraphx::shape::float_type, {1, 4}}; migraphx::shape sy{migraphx::shape::float_type, {3, 4}}; migraphx::shape sc{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", sc); auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sy); auto* then_mod = p.create_module("If_6_if"); auto hl2 = then_mod->add_literal(migraphx::literal(sd, {2})); auto hl1 = then_mod->add_literal(migraphx::literal(sd, {1})); auto mhl1 = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), hl1); auto hx = then_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), x); auto ad = then_mod->add_instruction(migraphx::make_op("add"), hx, mhl1); auto fad = then_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), ad); auto mhl2 = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), hl2); auto hy1 = then_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), y); auto mu = then_mod->add_instruction(migraphx::make_op("mul"), hy1, mhl2); auto fmu = then_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), mu); then_mod->add_return({fad, fmu, mu}); auto* else_mod = p.create_module("If_6_else"); auto hl3 = else_mod->add_literal(migraphx::literal(sd, {3})); auto mhl3 = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), hl3); auto hx2 = else_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), x); auto mu1 = else_mod->add_instruction(migraphx::make_op("mul"), hx2, mhl3); auto fmu1 = else_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), mu1); auto mhl4 = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {3, 4}}}), hl3); auto hy = else_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), y); auto ad1 = else_mod->add_instruction(migraphx::make_op("add"), hy, mhl4); auto fad1 = else_mod->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), ad1); else_mod->add_return({fmu1, fad1, ad1}); auto iff = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), iff); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), iff); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), iff); mm->add_return({r0, r1, r2}); return p; }; auto p1 = create_program(); migraphx::quantize_fp16(p1); auto p2 = create_fp16_program(); EXPECT(p1.sort() == p2.sort()); } TEST_CASE(topk) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); auto r = mm->add_instruction( migraphx::make_op("topk", {{"axis", 0}, {"k", 3}, {"largest", 0}}), data); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); return p; }; auto create_fp16_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto fdata = mm->add_parameter("data", s); auto hdata = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), fdata); auto r = mm->add_instruction( migraphx::make_op("topk", {{"axis", 0}, {"k", 3}, {"largest", 0}}), hdata); auto hr0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto fr0 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), hr0); auto ir1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({fr0, ir1}); return p; }; auto p1 = create_program(); migraphx::quantize_fp16(p1); auto p2 = create_fp16_program(); EXPECT(p1 == p2); } TEST_CASE(op_capture) { auto create_program_float = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {3, 3}}; migraphx::shape s2{migraphx::shape::float_type, {3, 6}}; auto p1 = mm->add_parameter("x", s1); auto p2 = mm->add_parameter("y", s1); auto pb = mm->add_parameter("b", s2); auto pc = mm->add_parameter("c", s2); auto pa = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto ps = migraphx::add_apply_alpha_beta(*mm, {pa, pb, pc}, migraphx::make_op("dot"), 1.0f, 1.0f); mm->add_instruction(migraphx::make_op("dot"), pa, ps); return p; }; auto create_program_op = [&] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {3, 3}}; migraphx::shape s2{migraphx::shape::float_type, {3, 6}}; auto p1 = mm->add_parameter("x", s1); auto p2 = mm->add_parameter("y", s1); auto pb = mm->add_parameter("b", s2); auto pc = mm->add_parameter("c", s2); auto pa = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto opa = mm->add_instruction(migraphx::make_op("capture", {{"ins_index", 0}}), pa); auto opb = mm->add_instruction(migraphx::make_op("capture", {{"ins_index", 1}}), pb); auto ps = migraphx::add_apply_alpha_beta( *mm, {opa, opb, pc}, migraphx::make_op("dot"), 1.0f, 1.0f); auto opm = mm->add_instruction(migraphx::make_op("capture", {{"ins_index", 2}}), pa); auto ops = mm->add_instruction(migraphx::make_op("capture", {{"ins_index", 3}}), ps); mm->add_instruction(migraphx::make_op("dot"), opm, ops); return p; }; { auto p = create_program_float(); auto op_capture_p = create_program_op(); migraphx::target t = migraphx::make_target("ref"); std::size_t param_index = 0; migraphx::run_passes( p, {migraphx::capture_arguments_pass{{"dot", "convolution"}, {}, ¶m_index}}); EXPECT(p == op_capture_p); } } TEST_CASE(op_capture_subgraph) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {2, 2, 4, 8}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2, 8, 6}}; migraphx::shape sc{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", sc); auto a = mm->add_parameter("a", sx); auto b = mm->add_parameter("b", sy); migraphx::shape sd{migraphx::shape::float_type, {2, 2, 4, 6}}; migraphx::shape sw{migraphx::shape::float_type, {2, 2, 1, 1}}; auto x = mm->add_parameter("x", sd); auto w = mm->add_parameter("w", sw); auto* then_mod = p.create_module("If_6_if"); auto out1 = then_mod->add_instruction(migraphx::make_op("dot"), a, b); then_mod->add_return({out1}); auto* else_mod = p.create_module("If_6_else"); auto out2 = else_mod->add_instruction(migraphx::make_op("convolution"), x, w); else_mod->add_return({out2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); mm->add_return({ret}); return p; }; auto create_program_op = [&] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {2, 2, 4, 8}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2, 8, 6}}; migraphx::shape sc{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", sc); auto a = mm->add_parameter("a", sx); auto b = mm->add_parameter("b", sy); migraphx::shape sd{migraphx::shape::float_type, {2, 2, 4, 6}}; migraphx::shape sw{migraphx::shape::float_type, {2, 2, 1, 1}}; auto x = mm->add_parameter("x", sd); auto w = mm->add_parameter("w", sw); auto* then_mod = p.create_module("If_6_if"); auto ca = then_mod->add_instruction(migraphx::make_op("capture", {{"ins_index", 2}}), a); auto cb = then_mod->add_instruction(migraphx::make_op("capture", {{"ins_index", 3}}), b); auto out1 = then_mod->add_instruction(migraphx::make_op("dot"), ca, cb); then_mod->add_return({out1}); auto* else_mod = p.create_module("If_6_else"); auto cx = else_mod->add_instruction(migraphx::make_op("capture", {{"ins_index", 0}}), x); auto cw = else_mod->add_instruction(migraphx::make_op("capture", {{"ins_index", 1}}), w); auto out2 = else_mod->add_instruction(migraphx::make_op("convolution"), cx, cw); else_mod->add_return({out2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); mm->add_return({ret}); return p; }; { auto p = create_program(); auto op_capture_p = create_program_op(); migraphx::target t = migraphx::make_target("ref"); std::size_t param_index = 0; migraphx::run_passes( p, {migraphx::capture_arguments_pass{{"dot", "convolution"}, {}, ¶m_index}}); EXPECT(p == op_capture_p); } } TEST_CASE(dot_float) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {2, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; migraphx::shape sc{migraphx::shape::float_type, {2, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto r = migraphx::add_apply_alpha_beta(*mm, {pa, pb}, migraphx::make_op("dot")); mm->add_return({r}); return p; }; auto create_int8_quantized_prog = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {2, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; migraphx::shape sc{migraphx::shape::float_type, {2, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto zp_a = mm->add_literal(static_cast(0)); auto scale_a = mm->add_literal(10.0f); scale_a = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), scale_a); zp_a = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), zp_a); auto qa = mm->add_instruction(migraphx::make_op("quantizelinear"), pa, scale_a, zp_a); auto dqa = mm->add_instruction(migraphx::make_op("dequantizelinear"), qa, scale_a, zp_a); auto zp_b = mm->add_literal(static_cast(0)); auto scale_b = mm->add_literal(10.0f); scale_b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sb.lens()}}), scale_b); zp_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sb.lens()}}), zp_b); auto qb = mm->add_instruction(migraphx::make_op("quantizelinear"), pb, scale_b, zp_b); auto dqb = mm->add_instruction(migraphx::make_op("dequantizelinear"), qb, scale_b, zp_b); auto r = migraphx::add_apply_alpha_beta(*mm, {dqa, dqb}, migraphx::make_op("dot")); mm->add_return({r}); return p; }; auto create_int8_optimized_prog = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {2, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; migraphx::shape sc{migraphx::shape::float_type, {2, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto zp = mm->add_literal(static_cast(0)); auto scale = mm->add_literal(10.0f); auto scale_a = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), scale); auto zp_a = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), zp); auto quant_a = mm->add_instruction(migraphx::make_op("quantizelinear"), pa, scale_a, zp_a); auto scale_b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sb.lens()}}), scale); auto zp_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sb.lens()}}), zp); auto quant_b = mm->add_instruction(migraphx::make_op("quantizelinear"), pb, scale_b, zp_b); auto quant = mm->add_instruction(migraphx::make_op("quant_dot"), quant_a, quant_b); auto scale_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", quant->get_shape().lens()}}), scale); auto out_scale = mm->add_instruction(migraphx::make_op("mul"), scale_mb, scale_mb); auto r = mm->add_instruction(migraphx::make_op("dequantizelinear"), quant, out_scale); mm->add_return({r}); return p; }; const std::vector> quant_params = { {0.1f, 0.0f}, {0.1f, 0.0f}, {0.1f, 100.0f}}; auto p = create_program(); std::size_t param_index = 0; migraphx::run_passes(p, {migraphx::capture_arguments_pass{{"dot"}, {}, ¶m_index}}); migraphx::run_passes( p, {migraphx::quantize_8bits_pass{migraphx::shape::type_t::int8_type, quant_params}, migraphx::dead_code_elimination{}}); auto qp = create_int8_quantized_prog(); EXPECT(p.sort() == qp.sort()); optimize_prog_int8(p); auto op = create_int8_optimized_prog(); EXPECT(p.sort() == op.sort()); } TEST_CASE(dot_double_2args) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::double_type, {2, 16}}; migraphx::shape sb{migraphx::shape::double_type, {16, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto r = migraphx::add_apply_alpha_beta(*mm, {pa, pb}, migraphx::make_op("dot")); mm->add_return({r}); return p; }; auto create_int8_quantized_prog = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::double_type, {2, 16}}; migraphx::shape sb{migraphx::shape::double_type, {16, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto zp_a = mm->add_literal(static_cast(0)); auto scale_a = mm->add_literal(10.0); scale_a = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), scale_a); zp_a = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), zp_a); auto qa = mm->add_instruction(migraphx::make_op("quantizelinear"), pa, scale_a, zp_a); auto dqa = mm->add_instruction(migraphx::make_op("dequantizelinear"), qa, scale_a, zp_a); auto zp_b = mm->add_literal(static_cast(0)); auto scale_b = mm->add_literal(5.0); scale_b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sb.lens()}}), scale_b); zp_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sb.lens()}}), zp_b); auto qb = mm->add_instruction(migraphx::make_op("quantizelinear"), pb, scale_b, zp_b); auto dqb = mm->add_instruction(migraphx::make_op("dequantizelinear"), qb, scale_b, zp_b); auto r = migraphx::add_apply_alpha_beta(*mm, {dqa, dqb}, migraphx::make_op("dot")); mm->add_return({r}); return p; }; auto create_int8_optimized_prog = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::double_type, {2, 16}}; migraphx::shape sb{migraphx::shape::double_type, {16, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto scale_a_lit = mm->add_literal(10.0); auto zp = mm->add_literal(static_cast(0)); auto scale_a = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), scale_a_lit); auto zp_a = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), zp); auto qa = mm->add_instruction(migraphx::make_op("quantizelinear"), pa, scale_a, zp_a); auto scale_b_lit = mm->add_literal(5.0); auto scale_b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sb.lens()}}), scale_b_lit); auto zp_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sb.lens()}}), zp); auto qb = mm->add_instruction(migraphx::make_op("quantizelinear"), pb, scale_b, zp_b); auto qdot = mm->add_instruction(migraphx::make_op("quant_dot"), qa, qb); auto scale_a_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", qdot->get_shape().lens()}}), scale_a_lit); auto scale_b_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", qdot->get_shape().lens()}}), scale_b_lit); auto out_scale = mm->add_instruction(migraphx::make_op("mul"), scale_a_mb, scale_b_mb); auto r = mm->add_instruction(migraphx::make_op("dequantizelinear"), qdot, out_scale); mm->add_return({r}); return p; }; auto p = create_program(); const std::vector>& quant_params{{0.1f, 0.0f}, {0.2f, 0.0f}}; std::size_t param_index = 0; migraphx::run_passes(p, {migraphx::capture_arguments_pass{{"dot"}, {}, ¶m_index}}); migraphx::run_passes( p, {migraphx::quantize_8bits_pass{migraphx::shape::type_t::int8_type, quant_params}, migraphx::dead_code_elimination{}}); EXPECT(p == create_int8_quantized_prog()); optimize_prog_int8(p); EXPECT(p == create_int8_optimized_prog()); } TEST_CASE(dot_half_1arg) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {9, 9}}; auto x = mm->add_parameter("x", s); auto r = mm->add_instruction(migraphx::make_op("dot"), x, x); mm->add_return({r}); return p; }; auto create_int8_quantized_prog = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::half_type, {9, 9}}; auto x = mm->add_parameter("x", sa); auto zp_a = mm->add_literal(static_cast(0)); auto scale_a = mm->add_literal(migraphx::literal({sa.type()}, {10.0})); scale_a = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), scale_a); zp_a = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), zp_a); auto qa = mm->add_instruction(migraphx::make_op("quantizelinear"), x, scale_a, zp_a); auto dqa = mm->add_instruction(migraphx::make_op("dequantizelinear"), qa, scale_a, zp_a); auto zp_b = mm->add_literal(static_cast(0)); auto scale_b = mm->add_literal(migraphx::literal({sa.type()}, {10.0})); scale_b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), scale_b); zp_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), zp_b); auto qb = mm->add_instruction(migraphx::make_op("quantizelinear"), x, scale_b, zp_b); auto dqb = mm->add_instruction(migraphx::make_op("dequantizelinear"), qb, scale_b, zp_b); auto r = mm->add_instruction(migraphx::make_op("dot"), dqa, dqb); mm->add_return({r}); return p; }; auto create_int8_optimized_prog = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::half_type, {9, 9}}; auto x = mm->add_parameter("x", sa); auto zp = mm->add_literal(static_cast(0)); auto scale_lit = mm->add_literal(migraphx::literal({sa.type()}, {10.0})); auto scale = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), scale_lit); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sa.lens()}}), zp); auto qx = mm->add_instruction(migraphx::make_op("quantizelinear"), x, scale, zp); auto qdot = mm->add_instruction(migraphx::make_op("quant_dot"), qx, qx); auto out_scale = mm->add_instruction(migraphx::make_op("mul"), scale, scale); auto r = mm->add_instruction(migraphx::make_op("dequantizelinear"), qdot, out_scale); mm->add_return({r}); return p; }; auto p = create_program(); const std::vector>& quant_params{{0.1f, 0.0f}, {0.1f, 0.0f}}; std::size_t param_index = 0; migraphx::run_passes(p, {migraphx::capture_arguments_pass{{"dot"}, {}, ¶m_index}}); migraphx::run_passes(p, {migraphx::quantize_8bits_pass{migraphx::shape::int8_type, quant_params}, migraphx::dead_code_elimination{}}); EXPECT(p == create_int8_quantized_prog()); optimize_prog_int8(p); EXPECT(p == create_int8_optimized_prog()); } TEST_CASE(conv_float) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto r = mm->add_instruction(migraphx::make_op("convolution"), input, weights); mm->add_return({r}); return p; }; auto create_int8_quantized_prog = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::shape sw{migraphx::shape::float_type, {4, 3, 3, 3}}; auto px = mm->add_parameter("x", sx); auto pw = mm->add_parameter("w", sw); auto zp = mm->add_literal(static_cast(0)); auto scale_lit = mm->add_literal(10.0f); auto scale = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sx.lens()}}), scale_lit); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sx.lens()}}), zp); auto quant_x = mm->add_instruction(migraphx::make_op("quantizelinear"), px, scale, zp); auto quant_w = mm->add_instruction(migraphx::make_op("quantizelinear"), pw, scale, zp); auto quant = mm->add_instruction(migraphx::make_op("quant_convolution"), quant_x, quant_w); auto scale_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", quant->get_shape().lens()}}), scale_lit); auto out_scale = mm->add_instruction(migraphx::make_op("mul"), scale_mb, scale_mb); auto r = mm->add_instruction(migraphx::make_op("dequantizelinear"), quant, out_scale); mm->add_return({r}); return p; }; auto p = create_program(); const std::vector>& quant_params{{0.1f, 0.0f}, {0.1f, 0.0f}}; std::size_t param_index = 0; migraphx::run_passes(p, {migraphx::capture_arguments_pass{{"convolution"}, {}, ¶m_index}}); migraphx::run_passes( p, {migraphx::quantize_8bits_pass{migraphx::shape::type_t::int8_type, quant_params}}); optimize_prog_int8(p); auto qp = create_int8_quantized_prog(); EXPECT(p == qp); } TEST_CASE(conv_float_throw) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto r = mm->add_instruction(migraphx::make_op("convolution"), input, weights); mm->add_return({r}); return p; }; auto p = create_program(); const std::vector>& quant_params{{0.1f, 0.0f}, {0.1f, 0.0f}}; test::throws([&] { migraphx::run_passes( p, {migraphx::quantize_8bits_pass{migraphx::shape::type_t::int8_type, quant_params}}); }); } TEST_CASE(conv_half) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::half_type, {4, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::half_type, {4, 3, 3, 3}}); auto r = mm->add_instruction(migraphx::make_op("convolution"), input, weights); mm->add_return({r}); return p; }; auto create_int8_quantized_prog = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::half_type, {4, 3, 3, 3}}; migraphx::shape sw{migraphx::shape::half_type, {4, 3, 3, 3}}; auto px = mm->add_parameter("x", sx); auto pw = mm->add_parameter("w", sw); auto zp = mm->add_literal(static_cast(0)); auto scale_lit = mm->add_literal(migraphx::literal({sx.type()}, {10.0})); auto scale = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sx.lens()}}), scale_lit); zp = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", sx.lens()}}), zp); auto quant_x = mm->add_instruction(migraphx::make_op("quantizelinear"), px, scale, zp); auto quant_w = mm->add_instruction(migraphx::make_op("quantizelinear"), pw, scale, zp); auto quant = mm->add_instruction(migraphx::make_op("quant_convolution"), quant_x, quant_w); auto scale_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", quant->get_shape().lens()}}), scale_lit); auto out_scale = mm->add_instruction(migraphx::make_op("mul"), scale_mb, scale_mb); auto r = mm->add_instruction(migraphx::make_op("dequantizelinear"), quant, out_scale); mm->add_return({r}); return p; }; auto p = create_program(); const std::vector>& quant_params{{0.1f, 0.0f}, {0.1f, 0.0f}}; std::size_t param_index = 0; migraphx::run_passes(p, {migraphx::capture_arguments_pass{{"convolution"}, {}, ¶m_index}}); migraphx::run_passes( p, {migraphx::quantize_8bits_pass{migraphx::shape::type_t::int8_type, quant_params}}); optimize_prog_int8(p); auto qp = create_int8_quantized_prog(); EXPECT(p == qp); } template static auto get_hash(const T& x) { return std::hash{}(x); } TEST_CASE(target_copy) { auto run_prog = [](migraphx::program p, const migraphx::target& t, migraphx::parameter_map& m_in, std::vector& res) { p.compile(t); migraphx::parameter_map m; for(auto&& x : p.get_parameter_shapes()) { if(m_in.count(x.first) > 0) { m[x.first] = t.copy_to(m_in[x.first]); } else { m[x.first] = t.allocate(x.second); } } auto result = t.copy_from(p.eval(m).back()); result.visit([&](auto v) { res.assign(v.begin(), v.end()); }); }; auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("add"), p1, p2); return p; }; { auto p = create_program(); migraphx::parameter_map m; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; m["x"] = migraphx::generate_argument(s); std::vector ref_result; migraphx::target ref_t = migraphx::make_target("ref"); run_prog(p, ref_t, m, ref_result); std::vector orig_result; run_prog(p, ref_t, m, orig_result); EXPECT(migraphx::verify::verify_rms_range(ref_result, orig_result)); } } TEST_CASE(int8_quantization_dot) { auto run_prog = [](migraphx::program p, const migraphx::target& t, migraphx::parameter_map& m_in, std::vector& res, bool b_quantize = false) { if(b_quantize) { std::vector cali_data; cali_data.push_back(m_in); migraphx::quantize_int8(p, t, cali_data); } p.compile(t); migraphx::parameter_map m; for(auto&& x : p.get_parameter_shapes()) { if(m_in.count(x.first) > 0) { m[x.first] = t.copy_to(m_in[x.first]); } else { m[x.first] = t.allocate(x.second); } } auto result = t.copy_from(p.eval(m).back()); result.visit([&](auto v) { res.assign(v.begin(), v.end()); }); }; auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{migraphx::shape::float_type, {2, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; migraphx::shape sc{migraphx::shape::float_type, {2, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto pc = mm->add_parameter("c", sc); auto r = migraphx::add_apply_alpha_beta(*mm, {pa, pb, pc}, migraphx::make_op("dot"), 1.0f, 1.0f); mm->add_return({r}); return p; }; { auto p = create_program(); migraphx::parameter_map m; migraphx::shape sa{migraphx::shape::float_type, {2, 16}}; migraphx::shape sb{migraphx::shape::float_type, {16, 8}}; m["a"] = migraphx::generate_argument(sa, get_hash(std::string("a"))); m["b"] = migraphx::generate_argument(sb, get_hash(std::string("b"))); std::vector quant_result; migraphx::target ref_t = migraphx::make_target("ref"); run_prog(p, ref_t, m, quant_result, true); std::vector no_quant_result; run_prog(p, ref_t, m, no_quant_result); EXPECT(migraphx::verify::verify_range_with_tolerance( quant_result, migraphx::verify::expected{no_quant_result}, migraphx::verify::tolerance{0.003})); } } TEST_CASE(int8_quantization_conv) { auto run_prog = [](migraphx::program p, const migraphx::target& t, std::vector& res, bool b_quantize = false) { if(b_quantize) { std::vector cali_data; migraphx::quantize_int8(p, t, cali_data); } p.compile(t); migraphx::parameter_map m; auto result = t.copy_from(p.eval(m).back()); result.visit([&](auto v) { res.assign(v.begin(), v.end()); }); }; auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {4, 2, 2, 2}}; migraphx::shape sw{migraphx::shape::float_type, {4, 2, 2, 2}}; std::vector v(sx.elements(), 0.5f); auto input = mm->add_literal(migraphx::literal(sx, v)); auto weights = mm->add_literal(migraphx::literal(sw, v)); auto r = mm->add_instruction(migraphx::make_op("convolution"), input, weights); mm->add_return({r}); return p; }; { auto p = create_program(); std::vector quant_result; migraphx::target ref_t = migraphx::make_target("ref"); run_prog(p, ref_t, quant_result, true); std::vector no_quant_result; run_prog(p, ref_t, no_quant_result); EXPECT(migraphx::verify::verify_rms_range(quant_result, no_quant_result)); } } TEST_CASE(int8_subgraph) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {2, 2, 4, 8}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2, 8, 6}}; migraphx::shape sc{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", sc); auto a = mm->add_parameter("a", sx); auto b = mm->add_parameter("b", sy); migraphx::shape sd{migraphx::shape::float_type, {2, 2, 4, 6}}; migraphx::shape sw{migraphx::shape::float_type, {2, 2, 1, 1}}; auto x = mm->add_parameter("x", sd); auto w = mm->add_parameter("w", sw); auto* then_mod = p.create_module("If_6_if"); auto out1 = migraphx::add_apply_alpha_beta(*then_mod, {a, b}, migraphx::make_op("dot")); then_mod->add_return({out1}); auto* else_mod = p.create_module("If_6_else"); auto out2 = else_mod->add_instruction(migraphx::make_op("convolution"), x, w); else_mod->add_return({out2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); mm->add_return({ret}); return p; }; auto create_int8_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {2, 2, 4, 8}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2, 8, 6}}; migraphx::shape sout{migraphx::shape::float_type, {2, 2, 4, 6}}; migraphx::shape sc{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", sc); auto a = mm->add_parameter("a", sx); auto b = mm->add_parameter("b", sy); // then submod auto* then_mod = p.create_module("If_6_if"); auto zp1 = then_mod->add_literal(static_cast(0)); auto s1 = then_mod->add_literal(10.0f); auto sa = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sx.lens()}}), s1); auto zpa = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sx.lens()}}), zp1); auto qa = then_mod->add_instruction(migraphx::make_op("quantizelinear"), a, sa, zpa); auto sb = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sy.lens()}}), s1); auto zpb = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sy.lens()}}), zp1); auto qb = then_mod->add_instruction(migraphx::make_op("quantizelinear"), b, sb, zpb); auto qdot = then_mod->add_instruction(migraphx::make_op("quant_dot"), qa, qb); auto s1_mb = then_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", qdot->get_shape().lens()}}), s1); auto so = then_mod->add_instruction(migraphx::make_op("mul"), s1_mb, s1_mb); auto r = then_mod->add_instruction(migraphx::make_op("dequantizelinear"), qdot, so); then_mod->add_return({r}); migraphx::shape sd{migraphx::shape::float_type, {2, 2, 4, 6}}; migraphx::shape sw{migraphx::shape::float_type, {2, 2, 1, 1}}; auto x = mm->add_parameter("x", sd); auto w = mm->add_parameter("w", sw); // else submod auto* else_mod = p.create_module("If_6_else"); auto sax_lit = else_mod->add_literal(2.0f); auto zp = else_mod->add_literal(static_cast(0)); auto sax = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sd.lens()}}), sax_lit); auto zpx = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sd.lens()}}), zp); auto qx = else_mod->add_instruction(migraphx::make_op("quantizelinear"), x, sax, zpx); auto ssw_lit = else_mod->add_literal(1.66667f); auto ssw = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sw.lens()}}), ssw_lit); auto zpw = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sw.lens()}}), zp); auto qw = else_mod->add_instruction(migraphx::make_op("quantizelinear"), w, ssw, zpw); auto qconv = else_mod->add_instruction(migraphx::make_op("quant_convolution"), qx, qw); auto ssw_mb = else_mod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", qconv->get_shape().lens()}}), ssw_lit); auto so1 = else_mod->add_instruction(migraphx::make_op("mul"), sax, ssw_mb); auto r1 = else_mod->add_instruction(migraphx::make_op("dequantizelinear"), qconv, so1); else_mod->add_return({r1}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); mm->add_return({ret}); return p; }; auto p1 = create_program(); const std::vector>& quant_params{ {0.5f, 0.0f}, {0.6f, 0.0f}, {0.1f, 0.0f}, {0.1f, 0.0f}}; std::size_t param_index = 0; migraphx::run_passes( p1, {migraphx::capture_arguments_pass{{"convolution", "dot"}, {}, ¶m_index}}); migraphx::run_passes( p1, {migraphx::quantize_8bits_pass{migraphx::shape::type_t::int8_type, quant_params}}); optimize_prog_int8(p1); auto p2 = create_int8_program(); EXPECT(p1 == p2); } TEST_CASE(test_op_capture) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {3, 3}}; migraphx::shape s2{migraphx::shape::float_type, {3, 6}}; std::vector d1(s1.elements()); std::vector d2(s2.elements()); std::iota(d1.begin(), d1.end(), 0.0f); std::iota(d2.begin(), d2.end(), 0.0f); auto p1 = mm->add_literal(s1, d1); auto p2 = mm->add_literal(s1, d1); auto pb = mm->add_literal(s2, d2); auto pc = mm->add_literal(s2, d2); auto pa = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto ps = migraphx::add_apply_alpha_beta(*mm, {pa, pb, pc}, migraphx::make_op("dot"), 1.0f, 1.0f); mm->add_instruction(migraphx::make_op("dot"), pa, ps); auto calc = [](std::size_t, const std::vector&) {}; migraphx::program capture_p = p; migraphx::target t = migraphx::make_target("ref"); std::size_t param_index = 0; migraphx::run_passes(capture_p, {migraphx::capture_arguments_pass{{"dot"}, calc, ¶m_index}}); p.compile(migraphx::make_target("ref")); capture_p.compile(migraphx::make_target("ref")); auto cap_res = capture_p.eval({}).back(); auto res = p.eval({}).back(); std::vector vec; std::vector cap_vec; cap_res.visit([&](auto output) { cap_vec.assign(output.begin(), output.end()); }); res.visit([&](auto output) { vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(vec, cap_vec)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/reduce_dims.cpp000066400000000000000000000167731510465702400205240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include "test.hpp" static migraphx::shape make_shape(std::vector lens) { return {migraphx::shape::float_type, std::move(lens)}; } static migraphx::shape make_shape(std::vector lens, std::vector strides) { return {migraphx::shape::float_type, std::move(lens), std::move(strides)}; } static bool verify_shape(const migraphx::shape& s1, const migraphx::shape& s2) { if(s1.elements() != s2.elements()) return false; return migraphx::all_of(migraphx::range(s1.elements()), [&](auto i) { return s1.index(i) == s2.index(i); }); } template static bool verify_shapes(const Range1& r1, const Range2& r2) { return migraphx::equal( r1, r2, [](const auto& s1, const auto& s2) { return verify_shape(s1, s2); }); } TEST_CASE(same_standard) { auto is = make_shape({64, 3, 7, 7}); auto os = make_shape({64 * 3 * 7 * 7}); std::vector ishapes = {is, is, is}; std::vector eshapes = {os, os, os}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(same_broadcast1) { auto is = make_shape({64, 3, 7, 7}); auto os = make_shape({64, 3, 7 * 7}); std::vector ishapes = {is, make_shape({64, 3, 7, 7}, {0, 1, 0, 0}), is}; std::vector eshapes = {os, make_shape({64, 3, 7 * 7}, {0, 1, 0}), os}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(same_broadcast2) { auto is = make_shape({64, 3, 8, 7, 7}); auto os = make_shape({64, 8 * 3, 7 * 7}); std::vector ishapes = {is, make_shape({64, 3, 8, 7, 7}, {0, 8, 1, 0, 0}), is}; std::vector eshapes = {os, make_shape({64, 8 * 3, 7 * 7}, {0, 1, 0}), os}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(same_transposed) { auto is = make_shape({64, 3, 7, 7}); auto os = make_shape({64 * 3, 7, 7}); std::vector ishapes = {is, migraphx::reorder_shape(is, {0, 1, 3, 2}), is}; std::vector eshapes = {os, migraphx::reorder_shape(os, {0, 2, 1}), os}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(different_masked1) { auto is = make_shape({64, 3, 7, 7}); auto os = make_shape({64, 3, 7 * 7}); std::vector ishapes = {is, make_shape({1, 3, 1, 1}), is}; std::vector eshapes = {os, make_shape({1, 3, 1}), os}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(different_masked2) { auto is = make_shape({64, 3, 7, 7}); auto os = make_shape({64, 3, 7 * 7}); std::vector ishapes = { is, make_shape({1, 3, 1, 1}), make_shape({64, 1, 7, 7})}; std::vector eshapes = {os, make_shape({1, 3, 1}), make_shape({64, 1, 7 * 7})}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(different_incompatible) { auto is = make_shape({64, 3, 7, 7}); std::vector ishapes = {is, make_shape({1, 3, 2, 1}), is}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(ishapes == rshapes); } TEST_CASE(different_ranks) { auto is = make_shape({64, 3, 7, 7}); std::vector ishapes = {is, make_shape({1, 3}), is}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(ishapes == rshapes); } TEST_CASE(transposed1) { std::vector ishapes = { make_shape({8, 28, 4, 56, 56}), make_shape({8, 28, 4, 56, 56}, {351232, 3136, 87808, 56, 1})}; std::vector eshapes = { make_shape({8, 28, 4, 56 * 56}), make_shape({8, 28, 4, 56 * 56}, {351232, 3136, 87808, 1})}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(non_packed_empty1) { std::vector ishapes = {make_shape({1, 12}, {589824, 64})}; std::vector eshapes = {make_shape({12}, {64})}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(non_packed_empty2) { std::vector ishapes = {make_shape({12, 1}, {64, 589824})}; std::vector eshapes = {make_shape({12}, {64})}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(single_dim) { std::vector ishapes = {make_shape({1}, {1})}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(ishapes == rshapes); } TEST_CASE(step_broadcast_transpose) { std::vector ishapes = {make_shape({1, 2, 2, 1}, {0, 0, 3, 6}), make_shape({1, 2, 2, 1}, {4, 2, 1, 1})}; std::vector eshapes = {make_shape({2, 2}, {0, 3}), make_shape({2, 2}, {2, 1})}; auto rshapes = migraphx::reduce_dims(ishapes); EXPECT(verify_shapes(ishapes, rshapes)); EXPECT(eshapes == rshapes); } TEST_CASE(empty) { auto rshapes = migraphx::reduce_dims({}); EXPECT(rshapes.empty()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/ref/000077500000000000000000000000001510465702400162735ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/ref/CMakeLists.txt000066400000000000000000000030251510465702400210330ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### file(GLOB REF CONFIGURE_DEPENDS *.cpp) rocm_add_test_executable(test_ref ${REF}) target_include_directories(test_ref PUBLIC ../include) target_link_libraries(test_ref migraphx migraphx_ref) rocm_clang_tidy_check(test_ref) ROCm-AMDMIGraphX-46524e8/test/ref/abs.cpp000066400000000000000000000054171510465702400175530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(abs_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l = mm->add_literal(migraphx::literal{s, {-1, 2, -3, 4}}); mm->add_instruction(migraphx::make_op("abs"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 3, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(abs_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 8}, {2, 2}}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("abs"), input); p.compile(migraphx::make_target("ref")); std::vector a = {-1, 2, -3, 4}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 2}}; params0["X"] = migraphx::argument(input_fixed_shape0, a.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 3, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/acos.cpp000066400000000000000000000061111510465702400177230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(acos_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {3}}; std::vector data{-0.8f, 0.0f, 1.0f}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("acos"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return acosf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(acos_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("acos"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-0.8f, 0.0f, 1.0f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return acosf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/acosh.cpp000066400000000000000000000061151510465702400200770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(acosh_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {3}}; std::vector data{1.1f, 1.2f, 2.0f}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("acosh"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return acoshf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(acosh_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); std::vector input_data{1.1f, 1.2f, 2.0f}; mm->add_instruction(migraphx::make_op("acosh"), input); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return acoshf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/add.cpp000066400000000000000000000174241510465702400175370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(add_broadcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::float_type, {2, 2, 3}}; std::vector a_data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; migraphx::shape b_shape{migraphx::shape::float_type, {2, 2}}; std::vector b_data{0, -1, -2, -3}; uint64_t axis = 0; auto l1 = mm->add_literal(migraphx::literal{a_shape, a_data}); auto l2 = mm->add_literal(migraphx::literal{b_shape, b_data}); auto l3 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l1->get_shape().lens()}}), l2); mm->add_instruction(migraphx::make_op("add"), l1, l3); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); EXPECT(result.get_shape().packed()); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(add_multibroadcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::float_type, {2, 2, 3}}; std::vector a_data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; migraphx::shape b_shape{migraphx::shape::float_type, {2, 2, 1}}; std::vector b_data{0, -1, -2, -3}; auto l1 = mm->add_literal(migraphx::literal{a_shape, a_data}); auto l2 = mm->add_literal(migraphx::literal{b_shape, b_data}); auto l3 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3}}}), l1); auto l4 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3}}}), l2); mm->add_instruction(migraphx::make_op("add"), l3, l4); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); EXPECT(result.get_shape().packed()); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(add_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l1 = mm->add_literal(migraphx::literal{s, {-1, 0, 1}}); auto l2 = mm->add_literal(migraphx::literal{s, {1, 2, 3}}); mm->add_instruction(migraphx::make_op("add"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 2, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(add_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("add"), x, y); p.compile(migraphx::make_target("ref")); std::vector x_data{-1, 0, 1}; std::vector y_data{1, 2, 3}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 2, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(fp16_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {1}}; migraphx::half a{1.5}; migraphx::half b{2.5}; migraphx::half c{4.0}; auto l0 = mm->add_literal(migraphx::literal{s, {a}}); auto l1 = mm->add_literal(migraphx::literal{s, {b}}); mm->add_instruction(migraphx::make_op("add"), l0, l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(1); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{c}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {1}}; migraphx::bf16 a{1.5}; migraphx::bf16 b{2.5}; migraphx::bf16 c{4.0}; auto l0 = mm->add_literal(migraphx::literal{s, {a}}); auto l1 = mm->add_literal(migraphx::literal{s, {b}}); mm->add_instruction(migraphx::make_op("add"), l0, l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(1); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{c}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(fp32_fp16_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data(2 * 3); std::iota(data.begin(), data.end(), 1.0f); auto l1 = mm->add_literal(migraphx::literal(s, data)); auto l2 = mm->add_literal(migraphx::literal(s, data)); mm->add_instruction(migraphx::make_op("add"), l1, l2); return p; }; auto test_case = [&](const std::vector& op_names) { std::vector gold_res = {2.0, 4.0, 6.0, 8.0, 10.0, 12.0}; auto p = create_program(); migraphx::quantize_fp16(p, op_names); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res; result.visit([&](auto output) { res.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res, gold_res)); }; test_case({"all"}); test_case({"add"}); } ROCm-AMDMIGraphX-46524e8/test/ref/allocate.cpp000066400000000000000000000055151510465702400205710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(allocate_dyn0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int64_type, {4}}; auto out_dims = mm->add_parameter("out_dims", s); mm->add_instruction(migraphx::make_op("allocate", {{"buf_type", migraphx::shape::float_type}}), out_dims); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector data = {2, 3, 4, 4}; params["out_dims"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); migraphx::shape sresult{migraphx::shape::float_type, {2, 3, 4, 4}}; result.visit([&](auto output) { EXPECT(output.get_shape() == sresult); }); } TEST_CASE(allocate_dyn1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int64_type, {4}}; migraphx::shape out_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; auto out_dims = mm->add_parameter("out_dims", s); mm->add_instruction(migraphx::make_op("allocate", {{"shape", migraphx::to_value(out_shape)}}), out_dims); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector data = {2, 3, 4, 4}; params["out_dims"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); result.visit([&](auto output) { EXPECT(output.get_shape() == out_shape); }); } ROCm-AMDMIGraphX-46524e8/test/ref/argmax.cpp000066400000000000000000000213411510465702400202570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(argmax_test_0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3, 4}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmax", {{"axis", 0}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmax_test_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {0, 0, 2, 1, 2, 0, 0, 2}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3, 4}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmax", {{"axis", 1}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmax_test_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {1, 3, 2, 2, 2, 3}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3, 4}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmax", {{"axis", 2}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmax_test_neg_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {0, 0, 2, 1, 2, 0, 0, 2}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3, 4}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmax", {{"axis", -2}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmax_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 2}, {3, 6}, {3, 6}}}; auto dl = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("argmax", {{"axis", 0}}), dl); p.compile(migraphx::make_target("ref")); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 3, 4}}; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); std::vector res_gold = {0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1}; EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmax_test_nonstd_shape) { migraphx::program p; auto* mm = p.get_main_module(); auto dl = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 3, 4}})); auto dl_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 2, 0}}}), dl); mm->add_instruction(migraphx::make_op("argmax", {{"axis", -3}}), dl_trans); auto p_uncompiled = p; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto res_gold = p_uncompiled.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); std::vector res_gold_vec; res_gold.visit([&](auto output) { res_gold_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold_vec)); } TEST_CASE(argmax_test_select_last_index_0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {2.0305, -1.853, 2.0305, -1.5706, 0.7545, 0.7545}; std::vector res_gold = {2, 2}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmax", {{"axis", 1}, {"select_last_index", true}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmax_test_select_last_index_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {2.0305, -1.853, 2.0305, -1.5706, 0.7545, 0.7545}; std::vector res_gold = {0, 1}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmax", {{"axis", 1}, {"select_last_index", false}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/argmin.cpp000066400000000000000000000171621510465702400202630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(argmin_test_0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3, 4}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmin", {{"axis", 0}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmin_test_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {2, 2, 0, 2, 0, 1, 2, 0}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3, 4}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmin", {{"axis", 1}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmin_test_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {2, 1, 0, 3, 3, 2}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3, 4}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmin", {{"axis", 2}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmin_test_neg_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {2, 1, 0, 3, 3, 2}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3, 4}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmin", {{"axis", -1}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmin_test_nonstd_shape) { migraphx::program p; auto* mm = p.get_main_module(); auto dl = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {2, 3, 4}})); auto dl_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 2, 0}}}), dl); mm->add_instruction(migraphx::make_op("argmin", {{"axis", -1}}), dl_trans); auto p_uncompiled = p; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto res_gold = p_uncompiled.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); std::vector res_gold_vec; res_gold.visit([&](auto output) { res_gold_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold_vec)); } TEST_CASE(argmin_test_select_last_index_0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {-2.0305, 0.853, -2.0305, 1.5706, 0.7545, 0.7545}; std::vector res_gold = {2, 2}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmin", {{"axis", 1}, {"select_last_index", true}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(argmin_test_select_last_index_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {-2.0305, 0.853, -2.0305, 1.5706, 0.7545, 0.7545}; std::vector res_gold = {0, 1}; migraphx::shape data_shape{migraphx::shape::float_type, {2, 3}}; auto dl = mm->add_literal(migraphx::literal{data_shape, data}); mm->add_instruction(migraphx::make_op("argmin", {{"axis", 1}, {"select_last_index", false}}), dl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/asin.cpp000066400000000000000000000061101510465702400177270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(asin_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data{-0.5f, 0.0f, 0.9f}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("asin"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return asinf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(asin_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("asin"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-0.5f, 0.0f, 0.9f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return asinf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/asinh.cpp000066400000000000000000000061161510465702400201050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(asinh_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data{-0.5f, 0.0f, 0.9f}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("asinh"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return asinhf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(asinh_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("asinh"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-0.5f, 0.0f, 0.9f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return asinhf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/atan.cpp000066400000000000000000000061111510465702400177210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(atan_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {3}}; std::vector data{-1.0f, 0.0f, 1.0f}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("atan"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return atanf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(atan_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("atan"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-1.0f, 0.0f, 1.0f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return atanf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/atanh.cpp000066400000000000000000000061571510465702400201030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(atanh_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {3}}; std::vector data{0.4435683f, 0.6223626f, 0.316958f}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("atanh"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return atanhf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(atanh_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("atanh"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{0.4435683f, 0.6223626f, 0.316958f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return atanhf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/bit_cast.cpp000066400000000000000000000061331510465702400205720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(bit_cast_fp8) { using migraphx::fp8::fp8e4m3fn; using migraphx::fp8::fp8e4m3fnuz; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::fp8e4m3fn_type, {2, 2}}; std::vector data; data.push_back(fp8e4m3fn{26.0f}); data.push_back(fp8e4m3fn{3.0f}); data.push_back(fp8e4m3fn{96.0f}); data.push_back(fp8e4m3fn{-1.25f}); auto lit = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction( migraphx::make_op("bit_cast", {{"target_type", migraphx::shape::fp8e4m3fnuz_type}}), lit); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold; gold.push_back(fp8e4m3fnuz{13.0f}); gold.push_back(fp8e4m3fnuz{1.5f}); gold.push_back(fp8e4m3fnuz{48.0f}); gold.push_back(fp8e4m3fnuz{-0.625f}); EXPECT(results_vector == gold); } TEST_CASE(bit_cast_uint8) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int8_type, {2, 2}}; std::vector data = {23, -3, 0, -1}; auto lit = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction( migraphx::make_op("bit_cast", {{"target_type", migraphx::shape::uint8_type}}), lit); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {23, 253, 0, 255}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/bitwise_and.cpp000066400000000000000000000073421510465702400212750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(bitwise_and_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {4}}; std::vector data1{2, 5, 255, -20}; std::vector data2{3, 14, 16, 38}; auto l1 = mm->add_literal(migraphx::literal{s, data1}); auto l2 = mm->add_literal(migraphx::literal{s, data2}); mm->add_instruction(migraphx::make_op("bitwise_and"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(data2.size()); std::transform(data1.begin(), data1.end(), data2.begin(), gold.begin(), [](int32_t n1, int32_t n2) -> int32_t { // NOLINTNEXTLINE(hicpp-signed-bitwise) return n1 & n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(bitwise_and_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6, {4}}}; migraphx::shape s{migraphx::shape::uint8_type, dd}; auto left = mm->add_parameter("l", s); auto right = mm->add_parameter("r", s); mm->add_instruction(migraphx::make_op("bitwise_and"), left, right); p.compile(migraphx::make_target("ref")); std::vector left_data{28, 0, 12, 55}; std::vector right_data{4, 1, 128, 62}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::uint8_type, {4}}; params0["l"] = migraphx::argument(input_fixed_shape0, left_data.data()); params0["r"] = migraphx::argument(input_fixed_shape0, right_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(left_data.size()); std::transform(left_data.begin(), left_data.end(), right_data.begin(), gold.begin(), [](uint8_t n1, uint8_t n2) -> uint8_t { return n1 & n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/broadcast.cpp000066400000000000000000000116551510465702400207510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(broadcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int32_type, {2, 2}}; std::vector a_data{0, 0, 0, 0}; migraphx::shape b_shape{migraphx::shape::int32_type, {2}}; std::vector b_data{-2, -3}; uint64_t axis = 0; auto l1 = mm->add_literal(migraphx::literal{a_shape, a_data}); auto l2 = mm->add_literal(migraphx::literal{b_shape, b_data}); mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l1->get_shape().lens()}}), l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto output = result.get(); EXPECT(output(0, 0) == -2); EXPECT(output(0, 1) == -2); EXPECT(output(1, 0) == -3); EXPECT(output(1, 1) == -3); } TEST_CASE(broadcast_2in_static_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int32_type, {2, 2}}; std::vector a_data{0, 0, 0, 0}; migraphx::shape b_shape{migraphx::shape::int32_type, {2}}; std::vector b_data{-2, -3}; uint64_t axis = 0; auto l1 = mm->add_literal(migraphx::literal{a_shape, a_data}); auto l2 = mm->add_literal(migraphx::literal{b_shape, b_data}); mm->add_instruction(migraphx::make_op("broadcast", {{"axis", axis}}), l2, l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto output = result.get(); EXPECT(output(0, 0) == -2); EXPECT(output(0, 1) == -2); EXPECT(output(1, 0) == -3); EXPECT(output(1, 1) == -3); } TEST_CASE(broadcast_2in_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int32_type, {{2, 2}, {2, 4}}}; migraphx::shape b_shape{migraphx::shape::int32_type, {2}}; std::vector b_data{-2, -3}; uint64_t axis = 0; auto pa = mm->add_parameter("a", a_shape); auto lb = mm->add_literal(migraphx::literal{b_shape, b_data}); mm->add_instruction(migraphx::make_op("broadcast", {{"axis", axis}}), lb, pa); p.compile(migraphx::make_target("ref")); std::vector a_data{0, 0, 0, 0}; migraphx::shape input_fixed_shape0{migraphx::shape::int32_type, {2, 2}}; migraphx::parameter_map params0; params0["a"] = migraphx::argument(input_fixed_shape0, a_data.data()); auto result = p.eval(params0).back(); auto output = result.get(); EXPECT(output(0, 0) == -2); EXPECT(output(0, 1) == -2); EXPECT(output(1, 0) == -3); EXPECT(output(1, 1) == -3); } TEST_CASE(isnan_broadcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {3}}; migraphx::shape s1{migraphx::shape::float_type, {3, 2}}; auto nan_val = std::numeric_limits::quiet_NaN(); std::vector data0 = {1.2, 5.2, nan_val}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); auto l1 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", s1.lens()}}), l0); mm->add_instruction(migraphx::make_op("isnan"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 0, 0, 1, 1}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/broadcast_for_dot.cpp000066400000000000000000000075151510465702400224650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include "test.hpp" TEST_CASE(broadcast_for_dot_static) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {2, 4}}; std::vector data0(8); std::iota(data0.begin(), data0.end(), 0.0); std::vector data1(16); std::iota(data1.begin(), data1.end(), 9.0); migraphx::shape s1{migraphx::shape::float_type, {1, 2, 4, 2}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); auto l1 = mm->add_literal(migraphx::literal{s1, data1}); auto broadcast_for_dot = mm->add_instruction(migraphx::make_op("broadcast_for_dot"), l0, l1); mm->add_return({broadcast_for_dot}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(16); std::iota(gold.begin(), gold.begin() + 8, 0.0); std::iota(gold.begin() + 8, gold.end(), 0.0); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(broadcast_for_dot_dyn) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::int32_type, {{2, 6}, {4, 4}}}; migraphx::shape s1{migraphx::shape::int32_type, {{1, 4}, {2, 2}, {4, 4}, {4, 6}}}; auto p0 = mm->add_parameter("0", s0); auto p1 = mm->add_parameter("1", s1); auto broadcast_for_dot = mm->add_instruction(migraphx::make_op("broadcast_for_dot"), p0, p1); mm->add_return({broadcast_for_dot}); p.compile(migraphx::make_target("ref")); std::vector data0(8); std::iota(data0.begin(), data0.end(), 0); std::vector data1(16); std::iota(data1.begin(), data1.end(), 9); migraphx::shape fixed_shape0{migraphx::shape::int32_type, {2, 4}}; migraphx::shape fixed_shape1{migraphx::shape::int32_type, {1, 2, 4, 2}}; migraphx::parameter_map params; params["0"] = migraphx::argument(fixed_shape0, data0.data()); params["1"] = migraphx::argument(fixed_shape1, data1.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(16); std::iota(gold.begin(), gold.begin() + 8, 0.0); std::iota(gold.begin() + 8, gold.end(), 0.0); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/broadcast_with_dims.cpp000066400000000000000000000164771510465702400230270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include "test.hpp" TEST_CASE(broadcast_with_dims_static0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_shape{migraphx::shape::int32_type, {2}}; migraphx::shape dims_shape{migraphx::shape::int64_type, {2}}; auto input_param = mm->add_parameter("x", input_shape); auto dims_param = mm->add_parameter("dims", dims_shape); mm->add_instruction(migraphx::make_op("broadcast_with_dims"), input_param, dims_param); p.compile(migraphx::make_target("ref")); std::vector input_data{-3, 3}; std::vector dims_data{2, 1}; migraphx::parameter_map params; params["x"] = migraphx::argument(input_shape, input_data.data()); params["dims"] = migraphx::argument(dims_shape, dims_data.data()); auto result = p.eval(params).back(); auto output = result.get(); EXPECT(output.get_shape().lens() == std::vector{2, 2}); EXPECT(output.get_shape().strides() == std::vector{0, 1}); EXPECT(output(0, 0) == -3); EXPECT(output(0, 1) == 3); EXPECT(output(1, 0) == -3); EXPECT(output(1, 1) == 3); } TEST_CASE(broadcast_with_dims_static1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_shape{migraphx::shape::float_type, {2, 1}, {1, 0}}; migraphx::shape dims_shape{migraphx::shape::int64_type, {1}}; auto input_param = mm->add_parameter("x", input_shape); auto dims_param = mm->add_parameter("dims", dims_shape); mm->add_instruction(migraphx::make_op("broadcast_with_dims"), input_param, dims_param); p.compile(migraphx::make_target("ref")); std::vector input_data{7, 11}; std::vector dims_data{3}; migraphx::parameter_map params; params["x"] = migraphx::argument(input_shape, input_data.data()); params["dims"] = migraphx::argument(dims_shape, dims_data.data()); auto result = p.eval(params).back(); auto output = result.get(); EXPECT(output.get_shape().lens() == std::vector{2, 3}); EXPECT(output.get_shape().strides() == std::vector{1, 0}); EXPECT(migraphx::float_equal(output(0, 0), 7.f)); EXPECT(migraphx::float_equal(output(0, 1), 7.f)); EXPECT(migraphx::float_equal(output(0, 2), 7.f)); EXPECT(migraphx::float_equal(output(1, 0), 11.f)); EXPECT(migraphx::float_equal(output(1, 1), 11.f)); EXPECT(migraphx::float_equal(output(1, 2), 11.f)); } TEST_CASE(broadcast_with_dims_static2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_shape{migraphx::shape::float_type, {2, 3}, {3, 1}}; migraphx::shape dims_shape{migraphx::shape::int64_type, {3}}; auto input_param = mm->add_parameter("x", input_shape); auto dims_param = mm->add_parameter("dims", dims_shape); mm->add_instruction(migraphx::make_op("broadcast_with_dims"), input_param, dims_param); p.compile(migraphx::make_target("ref")); std::vector input_data(6); std::iota(input_data.begin(), input_data.end(), 0.0); std::vector dims_data{4, 2, 3}; migraphx::parameter_map params; params["x"] = migraphx::argument(input_shape, input_data.data()); params["dims"] = migraphx::argument(dims_shape, dims_data.data()); auto result = p.eval(params).back(); auto output = result.get(); EXPECT(output.get_shape().lens() == std::vector{4, 2, 3}); EXPECT(output.get_shape().strides() == std::vector{0, 3, 1}); std::vector results_vector; result.visit([&](auto x) { results_vector.assign(x.begin(), x.end()); }); std::vector gold = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, }; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(broadcast_with_dims_dyn) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dds = {{2, 4}}; migraphx::shape input_shape{migraphx::shape::int32_type, dds}; migraphx::shape dims_shape{migraphx::shape::int64_type, {2}}; auto input_param = mm->add_parameter("x", input_shape); auto dims_param = mm->add_parameter("dims", dims_shape); mm->add_instruction(migraphx::make_op("broadcast_with_dims"), input_param, dims_param); p.compile(migraphx::make_target("ref")); std::vector input_data{-3, 3}; std::vector dims_data{2, 2}; migraphx::shape input_static_shape{migraphx::shape::int32_type, {2}}; migraphx::parameter_map params; params["x"] = migraphx::argument(input_static_shape, input_data.data()); params["dims"] = migraphx::argument(dims_shape, dims_data.data()); auto result = p.eval(params).back(); auto output = result.get(); EXPECT(output.get_shape().lens() == std::vector{2, 2}); EXPECT(output.get_shape().strides() == std::vector{0, 1}); EXPECT(output(0, 0) == -3); EXPECT(output(0, 1) == 3); EXPECT(output(1, 0) == -3); EXPECT(output(1, 1) == 3); } TEST_CASE(broadcast_with_dims_mismatch) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_shape{migraphx::shape::float_type, {2, 3}}; migraphx::shape dims_shape{migraphx::shape::int64_type, {1}}; auto input_param = mm->add_parameter("x", input_shape); auto dims_param = mm->add_parameter("dims", dims_shape); mm->add_instruction(migraphx::make_op("broadcast_with_dims"), input_param, dims_param); p.compile(migraphx::make_target("ref")); std::vector input_data{3, 9}; std::vector dims_data{6}; migraphx::parameter_map params; params["x"] = migraphx::argument(input_shape, input_data.data()); params["dims"] = migraphx::argument(dims_shape, dims_data.data()); EXPECT(test::throws([&] { std::ignore = p.eval(params).back(); })); } ROCm-AMDMIGraphX-46524e8/test/ref/ceil.cpp000066400000000000000000000062361510465702400177220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(ceil_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {9}}; std::vector data = {1.1, 1.5, 1.6, -1.1, -1.5, -1.6, 0.0, 2.0, -2.0}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("ceil"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return std::ceil(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(ceil_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{4, 12}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("ceil"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {1.1, 1.5, 1.6, -1.1, -1.5, -1.6, 0.0, 2.0, -2.0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {9}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return std::ceil(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/clip.cpp000066400000000000000000000067621510465702400177410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(clip_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l = mm->add_literal(migraphx::literal{s, {-1.0, 0.0, 10.0}}); auto min_val = mm->add_literal(0.0f); auto max_val = mm->add_literal(6.0f); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), max_val); mm->add_instruction(migraphx::make_op("clip"), l, min_val, max_val); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0, 0.0, 6.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(clip_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dds = {{2, 8, {3}}}; migraphx::shape s{migraphx::shape::float_type, dds}; auto l = mm->add_parameter("X", s); auto min_val = mm->add_literal(0.0f); auto max_val = mm->add_literal(6.0f); min_val = mm->add_instruction(migraphx::make_op("multibroadcast"), min_val, l); max_val = mm->add_instruction(migraphx::make_op("multibroadcast"), max_val, l); mm->add_instruction(migraphx::make_op("clip"), l, min_val, max_val); p.compile(migraphx::make_target("ref")); migraphx::shape static_shape{migraphx::shape::float_type, {3}}; migraphx::parameter_map params; std::vector data = {-1.0, 0.0, 10.0}; params["X"] = migraphx::argument(static_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0, 0.0, 6.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/concat.cpp000066400000000000000000000207711510465702400202550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(concat_test_1) { migraphx::program p; auto* mm = p.get_main_module(); int axis = 1; std::vector data0 = {0, 1, 5, 6}; std::vector data1 = {2, 3, 4, 7, 8, 9}; std::vector data2 = {10, 20}; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {2, 3}}; migraphx::shape s2{migraphx::shape::int32_type, {2, 1}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); auto l1 = mm->add_literal(migraphx::literal{s1, data1}); auto l2 = mm->add_literal(migraphx::literal{s2, data2}); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {0, 1, 2, 3, 4, 10, 5, 6, 7, 8, 9, 20}; std::vector results_vector(2 * 6); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().lens(), std::vector({2, 6}))); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().strides(), std::vector({6, 1}))); } TEST_CASE(concat_test_2) { migraphx::program p; auto* mm = p.get_main_module(); int axis = -1; std::vector data0 = {0, 1, 5, 6}; std::vector data1 = {2, 3, 4, 7, 8, 9}; std::vector data2 = {10, 20}; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {2, 3}}; migraphx::shape s2{migraphx::shape::int32_type, {2, 1}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); auto l1 = mm->add_literal(migraphx::literal{s1, data1}); auto l2 = mm->add_literal(migraphx::literal{s2, data2}); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {0, 1, 2, 3, 4, 10, 5, 6, 7, 8, 9, 20}; std::vector results_vector(2 * 6); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().lens(), std::vector({2, 6}))); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().strides(), std::vector({6, 1}))); } TEST_CASE(concat_test_3) { migraphx::program p; auto* mm = p.get_main_module(); int axis = 0; std::vector data0 = {0, 1, 2, 3}; std::vector data1 = {4, 5, 6, 7, 8, 9}; std::vector data2 = {10, 11}; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {3, 2}}; migraphx::shape s2{migraphx::shape::int32_type, {1, 2}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); auto l1 = mm->add_literal(migraphx::literal{s1, data1}); auto l2 = mm->add_literal(migraphx::literal{s2, data2}); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; std::vector results_vector(6 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().lens(), std::vector({6, 2}))); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().strides(), std::vector({2, 1}))); } TEST_CASE(concat_test_4) { migraphx::program p; auto* mm = p.get_main_module(); int axis = -2; std::vector data0 = {0, 1, 2, 3}; std::vector data1 = {4, 5, 6, 7, 8, 9}; std::vector data2 = {10, 11}; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {3, 2}}; migraphx::shape s2{migraphx::shape::int32_type, {1, 2}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); auto l1 = mm->add_literal(migraphx::literal{s1, data1}); auto l2 = mm->add_literal(migraphx::literal{s2, data2}); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; std::vector results_vector(6 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().lens(), std::vector({6, 2}))); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().strides(), std::vector({2, 1}))); } TEST_CASE(concat_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); int axis = 0; migraphx::shape s0{migraphx::shape::int32_type, {{2, 4, {2}}, {2, 3, {2}}}}; migraphx::shape s1{migraphx::shape::int32_type, {{3, 4, {4}}, {2, 3, {2}}}}; migraphx::shape s2{migraphx::shape::int32_type, {{1, 5, {3}}, {2, 3, {2}}}}; auto input0 = mm->add_parameter("X", s0); auto input1 = mm->add_parameter("Y", s1); auto input2 = mm->add_parameter("Z", s2); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), input0, input1, input2); p.compile(migraphx::make_target("ref")); migraphx::shape static_shape0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape static_shape1{migraphx::shape::int32_type, {3, 2}}; migraphx::shape static_shape2{migraphx::shape::int32_type, {1, 2}}; std::vector data0 = {0, 1, 2, 3}; std::vector data1 = {4, 5, 6, 7, 8, 9}; std::vector data2 = {10, 11}; migraphx::parameter_map params; params["X"] = migraphx::argument(static_shape0, data0.data()); params["Y"] = migraphx::argument(static_shape1, data1.data()); params["Z"] = migraphx::argument(static_shape2, data2.data()); auto result = p.eval(params).back(); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(migraphx::verify::verify_rms_range(result.get_shape().lens(), std::vector({6, 2}))); } ROCm-AMDMIGraphX-46524e8/test/ref/contiguous.cpp000066400000000000000000000102471510465702400212020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(contiguous_test) { migraphx::shape a_shape{migraphx::shape::float_type, {1, 3, 2, 2}, {12, 1, 6, 3}}; std::vector data(12); std::iota(data.begin(), data.end(), 0); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, data}); mm->add_instruction(migraphx::make_op("contiguous"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector new_strides = {12, 4, 2, 1}; EXPECT(result.get_shape().strides() == new_strides); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(contiguous_param_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::float_type, {1, 3, 2, 2}, {12, 1, 6, 3}}; auto a = mm->add_parameter("X", a_shape); mm->add_instruction(migraphx::make_op("contiguous"), a); p.compile(migraphx::make_target("ref")); std::vector data(12); std::iota(data.begin(), data.end(), 0); migraphx::parameter_map params; params["X"] = migraphx::argument(a_shape, data.data()); auto result = p.eval(params).back(); std::vector new_strides = {12, 4, 2, 1}; EXPECT(result.get_shape().strides() == new_strides); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(contiguous_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape dyn_shape{migraphx::shape::float_type, {{1, 1}, {2, 6}, {2, 2}, {2, 2}}}; auto input = mm->add_parameter("X", dyn_shape); mm->add_instruction(migraphx::make_op("contiguous"), input); p.compile(migraphx::make_target("ref")); migraphx::shape static_shape{migraphx::shape::float_type, {1, 3, 2, 2}, {12, 1, 6, 3}}; std::vector data(12); std::iota(data.begin(), data.end(), 0); migraphx::parameter_map params; params["X"] = migraphx::argument(static_shape, data.data()); auto result = p.eval(params).back(); std::vector new_strides = {12, 4, 2, 1}; EXPECT(result.get_shape().strides() == new_strides); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/convert.cpp000066400000000000000000000145701510465702400204660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(convert_downcast_overflow_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector data(4, 2 * std::numeric_limits::max()); auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(std::all_of(results_vector.begin(), results_vector.end(), [](const auto& x) { return x == std::numeric_limits::max(); })); } TEST_CASE(convert_downcast_underflow_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector data(4, 2 * std::numeric_limits::lowest()); auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(std::all_of(results_vector.begin(), results_vector.end(), [](const auto& x) { return x == std::numeric_limits::lowest(); })); } TEST_CASE(convert_nan_upcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 2}}; std::vector data(4, std::numeric_limits::quiet_NaN()); auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4, -1); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(std::all_of( results_vector.begin(), results_vector.end(), [](const auto& x) { return std::isnan(x); })); } TEST_CASE(convert_nan_downcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 2}}; std::vector data(4, std::numeric_limits::quiet_NaN()); auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4, -1); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(std::all_of( results_vector.begin(), results_vector.end(), [](const auto& x) { return std::isnan(x); })); } TEST_CASE(convert_nan_double_convert_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 2}}; std::vector data(4, std::numeric_limits::quiet_NaN()); auto l = mm->add_literal(migraphx::literal{s, data}); auto f_l = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), l); mm->add_instruction(migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), f_l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(std::all_of( results_vector.begin(), results_vector.end(), [](const auto& x) { return std::isnan(x); })); } TEST_CASE(convert_nan_convert_updown_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector data(4, std::numeric_limits::quiet_NaN()); auto l = mm->add_literal(migraphx::literal{s, data}); auto f_l = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), l); auto h_l = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), f_l); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), h_l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(std::all_of( results_vector.begin(), results_vector.end(), [](const auto& x) { return std::isnan(x); })); } ROCm-AMDMIGraphX-46524e8/test/ref/convolution.cpp000066400000000000000000001157721510465702400213730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(conv_dyn_batch_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_dyn_shape{migraphx::shape::float_type, {{1, 100}, {3, 3}, {4, 4}, {4, 4}}}; migraphx::shape weights_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto input = mm->add_parameter("X", input_dyn_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction(migraphx::make_op("convolution", { {"padding", {1, 1}}, {"stride", {2, 2}}, }), input, weights); p.compile(migraphx::make_target("ref")); std::vector a = { 2.71567607, -0.9960829, 0.91671127, 0.28140706, 0.63235772, 0.08077253, 0.80927712, -0.59108931, -1.05421555, -2.76622486, -0.85044265, -0.52049929, 0.67726439, -0.65290606, 0.02345525, -0.33579525, 0.38901961, 1.05473483, -1.31188095, 1.8963089, -0.07265259, 0.947339, 0.41949373, -0.70814759, 0.25892952, 1.07311416, 1.2571274, -0.62318051, -0.19951548, -0.94232577, -0.29393643, 0.42292568, -0.80230367, 1.40909171, 0.63617158, 0.13900366, 1.09253144, -0.15265895, 1.54781747, 0.72780299, 1.09189606, -0.38068101, 0.97057933, -0.58958799, 1.56188643, 0.21474874, 0.58725154, -1.27097559, -0.03024297, 1.09437096, -0.4897908, 0.34838957, -1.31042492, -1.69069934, 0.86956722, -0.40457946, 0.46691212, 1.29273605, 0.26464137, 0.22073045, -1.02178168, 0.22163901, -1.84387338, 0.75522131, -0.45775682, -0.42241111, -1.50944722, 1.07256448, -1.95876884, -0.28106022, 0.3341668, 2.13129425, -1.14728117, -1.06555498, -0.298444, -0.88322699, -0.65866792, -2.06007552, 0.01374334, 0.45612028, 0.52715492, 1.01914406, -1.72659791, 0.80650896, 0.16860051, 2.24112225, -0.78620857, 0.36566174, -0.07020134, -0.47976932, -0.68230027, -0.94711417, -0.54506505, 1.66504931, -0.71860826, 0.61132306}; std::vector c = { -0.14601797, -0.13000923, 0.06521662, 0.06178288, -0.11083675, 0.10154136, 0.09990512, 0.06030385, -0.11374587, -0.17523311, -0.14344215, 0.17802463, 0.06300922, -0.15325832, 0.07066704, 0.05166031, 0.00615084, -0.02606523, 0.08083995, -0.17913306, 0.0624622, 0.0735731, -0.04198661, -0.0164391, -0.06374192, 0.16569914, 0.10681538, 0.07370754, 0.02802075, 0.00282027, 0.15104802, -0.11084409, -0.00197773, 0.07924436, 0.03528272, 0.04765259, -0.15896152, 0.07917164, 0.12125669, -0.1154705, -0.11999125, 0.12749968, -0.06269585, 0.18658121, -0.03944227, 0.0111798, -0.17731084, 0.11789055, -0.09982193, 0.08142821, 0.0729029, 0.11303909, 0.12735154, 0.03885292}; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 3, 4, 4}}; migraphx::parameter_map params0; params0["X"] = migraphx::argument(input_fixed_shape0, a.data()); params0["W"] = migraphx::argument(weights_shape, c.data()); auto result = p.eval(params0).back(); std::vector results_vector(64); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-0.20817225, 0.87965256, 0.14958936, -1.24887264, -0.06540672, 0.20778663, 0.40456355, -0.99900877, 0.4917807, 0.1994698, 0.64205718, 0.37798831, -0.25315839, 0.44276932, -0.16138598, 0.79344082}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv_dyn_img_shape_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_dyn_shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {4, 6}, {4, 6}}}; migraphx::shape weights_shape{migraphx::shape::float_type, {1, 3, 3, 3}}; auto input = mm->add_parameter("X", input_dyn_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}}), input, weights); p.compile(migraphx::make_target("ref")); std::vector a = {0.28007596, 0.46114671, 0.12171969, 0.52260835, 0.40916841, 0.07163955, 0.09896668, 0.98628836, 0.69406788, 0.44868846, 0.64017681, 0.27048886, 0.30187397, 0.07334207, 0.05258557, 0.80747513, 0.81330534, 0.00497161, 0.33005534, 0.08908686, 0.46794691, 0.61768946, 0.55104806, 0.13406187, 0.70244284, 0.61296941, 0.46742536, 0.29712714, 0.91839388, 0.0834397, 0.14476327, 0.37857075, 0.25922384, 0.61620963, 0.69455439, 0.70389431, 0.77388606, 0.1752363, 0.74631394, 0.24604889, 0.53600244, 0.22116457, 0.81217463, 0.10789447, 0.43083784, 0.63371852, 0.69742316, 0.09536905}; std::vector c = {0.98411968, 0.2899219, 0.44638833, 0.30390816, 0.03989896, 0.2445332, 0.32700131, 0.57517075, 0.06956476, 0.93079306, 0.19882314, 0.52940601, 0.35624753, 0.35938406, 0.9111428, 0.88923574, 0.61040283, 0.2797513, 0.15479768, 0.46534674, 0.16970931, 0.49704618, 0.07062198, 0.01678321, 0.53150934, 0.39244495, 0.9963813}; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {1, 3, 4, 4}}; migraphx::parameter_map params0; params0["X"] = migraphx::argument(input_fixed_shape0, a.data()); params0["W"] = migraphx::argument(weights_shape, c.data()); auto result = p.eval(params0).back(); std::vector results_vector(72); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {6.1329393, 4.3199925, 5.448438, 3.8497565}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); a = {0.95600171, 0.20768181, 0.82844489, 0.14928212, 0.51280462, 0.1359196, 0.68903648, 0.84174772, 0.425509, 0.956926, 0.82533291, 0.33821531, 0.57576055, 0.75330186, 0.82710394, 0.93343847, 0.14499469, 0.74558021, 0.13935139, 0.90652876, 0.22611443, 0.85323975, 0.30631787, 0.96983037, 0.51783421, 0.32247456, 0.28243352, 0.605865, 0.33376446, 0.67864877, 0.15442507, 0.24977552, 0.86989425, 0.60036782, 0.26198306, 0.1494149, 0.13678915, 0.24892094, 0.38282467, 0.64907906, 0.83756376, 0.77603195, 0.33951558, 0.14856874, 0.45701939, 0.43786436, 0.57421759, 0.37326922, 0.63382506, 0.11464436, 0.23309047, 0.76724102, 0.98712427, 0.80800108, 0.84296564, 0.79568268, 0.45684131, 0.73867068, 0.57845499, 0.45073557, 0.27102442, 0.86460315, 0.06865567, 0.81673446, 0.881835, 0.42351639, 0.83322931, 0.34101671, 0.51979151, 0.54920645, 0.19287718, 0.33321689, 0.27752456, 0.45755893, 0.67484562, 0.68383122, 0.52361312, 0.46437257, 0.50862936, 0.32460429, 0.1726007, 0.29933345, 0.64856728, 0.06471591, 0.63370843, 0.27900152, 0.18595992, 0.48904812, 0.35368508, 0.09620202}; c = {0.709561, 0.7916206, 0.0443115, 0.62592275, 0.2498623, 0.42725624, 0.7905135, 0.53160169, 0.01303743, 0.01987505, 0.39041803, 0.89530203, 0.23155373, 0.44435213, 0.14407301, 0.80968594, 0.38216188, 0.35692557, 0.2568538, 0.83587388, 0.43654904, 0.04974508, 0.80375029, 0.25350374, 0.1820275, 0.23369029, 0.54358755}; gold = {6.305986, 5.564665, 6.122996, 5.7262855, 5.5546584, 5.779489, 5.798161, 5.160476, 6.702436, 5.4851074, 6.227567, 5.2016754}; migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {1, 3, 6, 5}}; migraphx::parameter_map params1; params1["X"] = migraphx::argument(input_fixed_shape1, a.data()); params1["W"] = migraphx::argument(weights_shape, c.data()); result = p.eval(params1).back(); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv_dyn_weights_shape_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_shape{migraphx::shape::float_type, {1, 3, 4, 4}}; migraphx::shape weights_shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 3}, {2, 3}}}; auto input = mm->add_parameter("X", input_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}}), input, weights); p.compile(migraphx::make_target("ref")); std::vector a = {0.28007596, 0.46114671, 0.12171969, 0.52260835, 0.40916841, 0.07163955, 0.09896668, 0.98628836, 0.69406788, 0.44868846, 0.64017681, 0.27048886, 0.30187397, 0.07334207, 0.05258557, 0.80747513, 0.81330534, 0.00497161, 0.33005534, 0.08908686, 0.46794691, 0.61768946, 0.55104806, 0.13406187, 0.70244284, 0.61296941, 0.46742536, 0.29712714, 0.91839388, 0.0834397, 0.14476327, 0.37857075, 0.25922384, 0.61620963, 0.69455439, 0.70389431, 0.77388606, 0.1752363, 0.74631394, 0.24604889, 0.53600244, 0.22116457, 0.81217463, 0.10789447, 0.43083784, 0.63371852, 0.69742316, 0.09536905}; std::vector c = {0.98411968, 0.2899219, 0.44638833, 0.30390816, 0.03989896, 0.2445332, 0.32700131, 0.57517075, 0.06956476, 0.93079306, 0.19882314, 0.52940601}; migraphx::shape weight_fixed_shape0{migraphx::shape::float_type, {1, 3, 2, 2}}; migraphx::parameter_map params0; params0["X"] = migraphx::argument(input_shape, a.data()); params0["W"] = migraphx::argument(weight_fixed_shape0, c.data()); auto result = p.eval(params0).back(); std::vector results_vector(72); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.9939406, 2.2703054, 1.8896171, 2.062202, 2.3035214, 1.629366, 2.1606991, 2.1917608, 1.6797699}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); c = {0.98411968, 0.2899219, 0.44638833, 0.30390816, 0.03989896, 0.2445332, 0.32700131, 0.57517075, 0.06956476, 0.93079306, 0.19882314, 0.52940601, 0.35624753, 0.35938406, 0.9111428, 0.88923574, 0.61040283, 0.2797513, 0.15479768, 0.46534674, 0.16970931, 0.49704618, 0.07062198, 0.01678321, 0.53150934, 0.39244495, 0.9963813}; gold = {6.1329393, 4.3199925, 5.448438, 3.8497565}; migraphx::shape weights_fixed_shape1{migraphx::shape::float_type, {1, 3, 3, 3}}; migraphx::parameter_map params1; params1["X"] = migraphx::argument(input_shape, a.data()); params1["W"] = migraphx::argument(weights_fixed_shape1, c.data()); result = p.eval(params1).back(); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv_dyn_img_same_upper_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_dyn_shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {4, 6}, {4, 6}}}; migraphx::shape weights_shape{migraphx::shape::float_type, {1, 3, 3, 3}}; auto input = mm->add_parameter("X", input_dyn_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction( migraphx::make_op( "convolution", {{"stride", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::same_upper}}), input, weights); p.compile(migraphx::make_target("ref")); std::vector a = {0.63321185, 0.6466339, 0.8515352, 0.44240063, 0.5018913, 0.5068494, 0.75330657, 0.7383877, 0.15870683, 0.8171611, 0.56118083, 0.87004256, 0.24401724, 0.8815178, 0.4222333, 0.27191755, 0.41633207, 0.2460619, 0.32004243, 0.6962248, 0.12284133, 0.2620491, 0.96931046, 0.6030955, 0.7623861, 0.2395751, 0.61440414, 0.577285, 0.80087787, 0.12776066, 0.26566318, 0.46569306, 0.96701574, 0.3850145, 0.14165345, 0.5887347, 0.7152134, 0.5295342, 0.6303507, 0.4037548, 0.18556239, 0.79416305, 0.29107493, 0.18770285, 0.6870904, 0.30701008, 0.314684, 0.91075855}; std::vector c = { 2.8150102e-01, 3.3198616e-01, 9.5149356e-01, 7.4039467e-02, 9.6555042e-01, 2.8815505e-01, 2.5100240e-01, 5.2186239e-01, 2.3850012e-01, 8.2963020e-01, 3.0763101e-04, 6.7026985e-01, 1.4260857e-01, 9.7517288e-01, 3.6847427e-02, 8.5804445e-01, 7.3440993e-01, 6.7948365e-01, 7.9253986e-02, 7.3943835e-01, 1.7813577e-01, 1.0780835e-01, 4.2304707e-01, 4.0084350e-01, 1.1114500e-01, 4.4846520e-01, 5.0109702e-01}; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {1, 3, 4, 4}}; migraphx::parameter_map params0; params0["X"] = migraphx::argument(input_fixed_shape0, a.data()); params0["W"] = migraphx::argument(weights_shape, c.data()); auto result = p.eval(params0).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {3.013387, 3.7111127, 4.2946506, 3.579301, 4.5306826, 6.1262493, 6.332169, 4.495293, 4.46013, 6.0938954, 5.848162, 4.514299, 2.9587686, 4.117671, 3.5187216, 2.3236327}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv_dyn_kernel_same_upper_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_shape{migraphx::shape::float_type, {1, 3, 4, 4}}; migraphx::shape weights_shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 3}, {2, 3}}}; auto input = mm->add_parameter("X", input_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction( migraphx::make_op( "convolution", {{"stride", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::same_upper}}), input, weights); p.compile(migraphx::make_target("ref")); std::vector a = {0.63321185, 0.6466339, 0.8515352, 0.44240063, 0.5018913, 0.5068494, 0.75330657, 0.7383877, 0.15870683, 0.8171611, 0.56118083, 0.87004256, 0.24401724, 0.8815178, 0.4222333, 0.27191755, 0.41633207, 0.2460619, 0.32004243, 0.6962248, 0.12284133, 0.2620491, 0.96931046, 0.6030955, 0.7623861, 0.2395751, 0.61440414, 0.577285, 0.80087787, 0.12776066, 0.26566318, 0.46569306, 0.96701574, 0.3850145, 0.14165345, 0.5887347, 0.7152134, 0.5295342, 0.6303507, 0.4037548, 0.18556239, 0.79416305, 0.29107493, 0.18770285, 0.6870904, 0.30701008, 0.314684, 0.91075855}; std::vector c = {2.8150102e-01, 3.3198616e-01, 9.5149356e-01, 7.4039467e-02, 9.6555042e-01, 2.8815505e-01, 2.5100240e-01, 5.2186239e-01, 2.3850012e-01, 8.2963020e-01, 3.0763101e-04, 6.7026985e-01}; migraphx::shape weight_fixed_shape0{migraphx::shape::float_type, {1, 3, 2, 2}}; migraphx::parameter_map params0; params0["X"] = migraphx::argument(input_shape, a.data()); params0["W"] = migraphx::argument(weight_fixed_shape0, c.data()); auto result = p.eval(params0).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {2.453681, 2.536207, 3.0187201, 1.7912633, 2.1738236, 2.9695358, 3.2319589, 1.859269, 2.5953722, 2.50734, 2.7736917, 1.2229807, 1.5900216, 0.9225286, 1.43048, 0.74341124}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv_dyn_kernel_same_lower_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape input_shape{migraphx::shape::float_type, {1, 3, 4, 4}}; migraphx::shape weights_shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 3}, {2, 3}}}; auto input = mm->add_parameter("X", input_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction( migraphx::make_op( "convolution", {{"stride", {1, 1}}, {"padding_mode", migraphx::op::padding_mode_t::same_lower}}), input, weights); p.compile(migraphx::make_target("ref")); std::vector a = {0.63321185, 0.6466339, 0.8515352, 0.44240063, 0.5018913, 0.5068494, 0.75330657, 0.7383877, 0.15870683, 0.8171611, 0.56118083, 0.87004256, 0.24401724, 0.8815178, 0.4222333, 0.27191755, 0.41633207, 0.2460619, 0.32004243, 0.6962248, 0.12284133, 0.2620491, 0.96931046, 0.6030955, 0.7623861, 0.2395751, 0.61440414, 0.577285, 0.80087787, 0.12776066, 0.26566318, 0.46569306, 0.96701574, 0.3850145, 0.14165345, 0.5887347, 0.7152134, 0.5295342, 0.6303507, 0.4037548, 0.18556239, 0.79416305, 0.29107493, 0.18770285, 0.6870904, 0.30701008, 0.314684, 0.91075855}; std::vector c = {2.8150102e-01, 3.3198616e-01, 9.5149356e-01, 7.4039467e-02, 9.6555042e-01, 2.8815505e-01, 2.5100240e-01, 5.2186239e-01, 2.3850012e-01, 8.2963020e-01, 3.0763101e-04, 6.7026985e-01}; migraphx::shape weight_fixed_shape0{migraphx::shape::float_type, {1, 3, 2, 2}}; migraphx::parameter_map params0; params0["X"] = migraphx::argument(input_shape, a.data()); params0["W"] = migraphx::argument(weight_fixed_shape0, c.data()); auto result = p.eval(params0).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.91231215, 1.1416453, 1.00216, 1.6813052, 1.7131033, 2.453681, 2.536207, 3.0187201, 1.3293691, 2.1738236, 2.9695358, 3.2319589, 1.3228729, 2.5953722, 2.50734, 2.7736917}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv2d_padding_stride_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 2.71567607, -0.9960829, 0.91671127, 0.28140706, 0.63235772, 0.08077253, 0.80927712, -0.59108931, -1.05421555, -2.76622486, -0.85044265, -0.52049929, 0.67726439, -0.65290606, 0.02345525, -0.33579525, 0.38901961, 1.05473483, -1.31188095, 1.8963089, -0.07265259, 0.947339, 0.41949373, -0.70814759, 0.25892952, 1.07311416, 1.2571274, -0.62318051, -0.19951548, -0.94232577, -0.29393643, 0.42292568, -0.80230367, 1.40909171, 0.63617158, 0.13900366, 1.09253144, -0.15265895, 1.54781747, 0.72780299, 1.09189606, -0.38068101, 0.97057933, -0.58958799, 1.56188643, 0.21474874, 0.58725154, -1.27097559, -0.03024297, 1.09437096, -0.4897908, 0.34838957, -1.31042492, -1.69069934, 0.86956722, -0.40457946, 0.46691212, 1.29273605, 0.26464137, 0.22073045, -1.02178168, 0.22163901, -1.84387338, 0.75522131, -0.45775682, -0.42241111, -1.50944722, 1.07256448, -1.95876884, -0.28106022, 0.3341668, 2.13129425, -1.14728117, -1.06555498, -0.298444, -0.88322699, -0.65866792, -2.06007552, 0.01374334, 0.45612028, 0.52715492, 1.01914406, -1.72659791, 0.80650896, 0.16860051, 2.24112225, -0.78620857, 0.36566174, -0.07020134, -0.47976932, -0.68230027, -0.94711417, -0.54506505, 1.66504931, -0.71860826, 0.61132306}; std::vector c = { -0.14601797, -0.13000923, 0.06521662, 0.06178288, -0.11083675, 0.10154136, 0.09990512, 0.06030385, -0.11374587, -0.17523311, -0.14344215, 0.17802463, 0.06300922, -0.15325832, 0.07066704, 0.05166031, 0.00615084, -0.02606523, 0.08083995, -0.17913306, 0.0624622, 0.0735731, -0.04198661, -0.0164391, -0.06374192, 0.16569914, 0.10681538, 0.07370754, 0.02802075, 0.00282027, 0.15104802, -0.11084409, -0.00197773, 0.07924436, 0.03528272, 0.04765259, -0.15896152, 0.07917164, 0.12125669, -0.1154705, -0.11999125, 0.12749968, -0.06269585, 0.18658121, -0.03944227, 0.0111798, -0.17731084, 0.11789055, -0.09982193, 0.08142821, 0.0729029, 0.11303909, 0.12735154, 0.03885292}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape c_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto cl = mm->add_literal(migraphx::literal{c_shape, c}); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}}), al, cl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-0.20817225, 0.87965256, 0.14958936, -1.24887264, -0.06540672, 0.20778663, 0.40456355, -0.99900877, 0.4917807, 0.1994698, 0.64205718, 0.37798831, -0.25315839, 0.44276932, -0.16138598, 0.79344082}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv2d_padding_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 2.71567607, -0.9960829, 0.91671127, 0.28140706, 0.63235772, 0.08077253, 0.80927712, -0.59108931, -1.05421555, -2.76622486, -0.85044265, -0.52049929, 0.67726439, -0.65290606, 0.02345525, -0.33579525, 0.38901961, 1.05473483, -1.31188095, 1.8963089, -0.07265259, 0.947339, 0.41949373, -0.70814759, 0.25892952, 1.07311416, 1.2571274, -0.62318051, -0.19951548, -0.94232577, -0.29393643, 0.42292568, -0.80230367, 1.40909171, 0.63617158, 0.13900366, 1.09253144, -0.15265895, 1.54781747, 0.72780299, 1.09189606, -0.38068101, 0.97057933, -0.58958799, 1.56188643, 0.21474874, 0.58725154, -1.27097559, -0.03024297, 1.09437096, -0.4897908, 0.34838957, -1.31042492, -1.69069934, 0.86956722, -0.40457946, 0.46691212, 1.29273605, 0.26464137, 0.22073045, -1.02178168, 0.22163901, -1.84387338, 0.75522131, -0.45775682, -0.42241111, -1.50944722, 1.07256448, -1.95876884, -0.28106022, 0.3341668, 2.13129425, -1.14728117, -1.06555498, -0.298444, -0.88322699, -0.65866792, -2.06007552, 0.01374334, 0.45612028, 0.52715492, 1.01914406, -1.72659791, 0.80650896, 0.16860051, 2.24112225, -0.78620857, 0.36566174, -0.07020134, -0.47976932, -0.68230027, -0.94711417, -0.54506505, 1.66504931, -0.71860826, 0.61132306}; std::vector c = { -0.16115488, -0.09800646, -0.05412646, 0.10475694, 0.00555485, -0.12667653, 0.0458357, -0.02656217, -0.16338061, 0.15037455, 0.0102711, 0.01303349, 0.05242859, 0.02034754, 0.04751867, -0.17038961, -0.1434752, -0.10770349, 0.05676742, -0.15838449, 0.10128359, -0.18958683, 0.11954515, 0.10758857, -0.01058291, -0.12797487, 0.08971019, 0.18793164, -0.00881396, -0.06588994, -0.13321903, -0.03300409, 0.01439607, 0.07618178, -0.11556662, 0.00764295, 0.12956454, -0.08937147, -0.12763587, 0.04674943, 0.05765297, 0.11336918, 0.14747436, -0.06199479, -0.01166052, -0.12432006, -0.04494537, -0.17581205, 0.09475745, 0.1149437, -0.1014564, 0.0274073, -0.01323579, -0.11092556}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape c_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto cl = mm->add_literal(migraphx::literal{c_shape, c}); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {1, 1}}}), al, cl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(64); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = { -0.0201216, 0.40407312, -0.39005592, -0.0631946, 0.37963012, -0.64611685, 0.1349397, -0.54113752, 0.28533003, 0.27667275, -0.16442731, -0.181494, 0.30564839, 0.58744538, 0.32015014, 0.24969585, -0.27367792, -0.53308117, 0.41236052, 0.26136363, -0.01489828, 0.57652152, -0.38506854, 0.119615, 0.0437076, 0.04779706, 0.57887721, 0.23126155, 0.05695833, -0.68200272, 0.02063358, -0.10267162, 0.8062973, -0.38149622, -0.40134856, -0.03353126, 0.38991132, -0.3478111, 0.03661491, 0.25783631, 0.62772679, -0.1961118, 0.76423508, -0.36241418, -0.20994355, -0.12368261, -0.9406727, 0.02340185, -0.08793129, -0.02471633, -0.58163726, -0.02211772, -0.42014724, 0.77525634, 0.504951, -0.20537445, -0.20369984, -0.83037728, -1.40423918, -0.46160448, -0.22944322, 0.36074194, 0.49579027, 0.46527559}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv2d_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 2.71567607, -0.9960829, 0.91671127, 0.28140706, 0.63235772, 0.08077253, 0.80927712, -0.59108931, -1.05421555, -2.76622486, -0.85044265, -0.52049929, 0.67726439, -0.65290606, 0.02345525, -0.33579525, 0.38901961, 1.05473483, -1.31188095, 1.8963089, -0.07265259, 0.947339, 0.41949373, -0.70814759, 0.25892952, 1.07311416, 1.2571274, -0.62318051, -0.19951548, -0.94232577, -0.29393643, 0.42292568, -0.80230367, 1.40909171, 0.63617158, 0.13900366, 1.09253144, -0.15265895, 1.54781747, 0.72780299, 1.09189606, -0.38068101, 0.97057933, -0.58958799, 1.56188643, 0.21474874, 0.58725154, -1.27097559, -0.03024297, 1.09437096, -0.4897908, 0.34838957, -1.31042492, -1.69069934, 0.86956722, -0.40457946, 0.46691212, 1.29273605, 0.26464137, 0.22073045, -1.02178168, 0.22163901, -1.84387338, 0.75522131, -0.45775682, -0.42241111, -1.50944722, 1.07256448, -1.95876884, -0.28106022, 0.3341668, 2.13129425, -1.14728117, -1.06555498, -0.298444, -0.88322699, -0.65866792, -2.06007552, 0.01374334, 0.45612028, 0.52715492, 1.01914406, -1.72659791, 0.80650896, 0.16860051, 2.24112225, -0.78620857, 0.36566174, -0.07020134, -0.47976932, -0.68230027, -0.94711417, -0.54506505, 1.66504931, -0.71860826, 0.61132306}; std::vector c = { 2.82721668e-02, 6.44195229e-02, 1.53499246e-02, 1.72468081e-01, -6.33238107e-02, 9.49496776e-02, 1.40258059e-01, -7.92879611e-02, -1.29301161e-01, 3.11307609e-03, -1.90624535e-01, 1.13238767e-01, -2.80647576e-02, 3.12882811e-02, -3.52091640e-02, 3.33581865e-02, 6.43158704e-02, 7.40238279e-02, -1.00106120e-01, -9.56912562e-02, 1.44342467e-01, 9.40258950e-02, 6.36333972e-02, 1.66158378e-03, -8.91554281e-02, 2.58734226e-02, 1.70919895e-02, 1.78214177e-01, 8.84564668e-02, 8.98126513e-02, -1.63809001e-01, 1.37802169e-01, 1.66439757e-01, -1.45631135e-02, 1.88469887e-04, 4.76950556e-02, -1.91969007e-01, -1.76233292e-01, -7.70473927e-02, 1.14828631e-01, 1.76608220e-01, -1.50728196e-01, 1.99946314e-02, -5.88052124e-02, 1.31612435e-01, 1.61106288e-02, -1.35080189e-01, 1.49512306e-01, 3.86456847e-02, 1.29330024e-01, -3.22975963e-02, -5.60784787e-02, -5.41997552e-02, 4.78562862e-02}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape c_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto cl = mm->add_literal(migraphx::literal{c_shape, c}); mm->add_instruction(migraphx::make_op("convolution"), al, cl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.27039781, 0.19105849, -0.06339942, -0.65087199, 0.40867025, 0.05063812, -0.14907975, 0.49018705, -0.49197209, 0.33236548, -0.39374301, 0.16012701, 0.06574871, 0.71606487, -0.55201721, -0.46427044}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv2d_dilation_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a(16); std::iota(a.begin(), a.end(), 0.0f); std::vector c(9); std::iota(c.begin(), c.end(), 0.0f); migraphx::shape a_shape{migraphx::shape::float_type, {1, 1, 4, 4}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape c_shape{migraphx::shape::float_type, {1, 1, 3, 3}}; auto cl = mm->add_literal(migraphx::literal{c_shape, c}); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {1, 1}}, {"dilation", {2, 2}}}), al, cl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {266, 206, 98, 66}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(conv3d_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 2.71567607, -0.9960829, 0.91671127, 0.28140706, 0.63235772, 0.08077253, 0.80927712, -0.59108931, -1.05421555, -2.76622486, -0.85044265, -0.52049929, 0.67726439, -0.65290606, 0.02345525, -0.33579525, 0.38901961, 1.05473483, -1.31188095, 1.8963089, -0.07265259, 0.947339, 0.41949373, -0.70814759, 0.25892952, 1.07311416, 1.2571274, -0.62318051, -0.19951548, -0.94232577, -0.29393643, 0.42292568, -0.80230367, 1.40909171, 0.63617158, 0.13900366, 1.09253144, -0.15265895, 1.54781747, 0.72780299, 1.09189606, -0.38068101, 0.97057933, -0.58958799, 1.56188643, 0.21474874, 0.58725154, -1.27097559, -0.03024297, 1.09437096, -0.4897908, 0.34838957, -1.31042492, -1.69069934, 0.86956722, -0.40457946, 0.46691212, 1.29273605, 0.26464137, 0.22073045, -1.02178168, 0.22163901, -1.84387338, 0.75522131, -0.45775682, -0.42241111, -1.50944722, 1.07256448, -1.95876884, -0.28106022, 0.3341668, 2.13129425, -1.14728117, -1.06555498, -0.298444, -0.88322699, -0.65866792, -2.06007552, 0.01374334, 0.45612028, 0.52715492, 1.01914406, -1.72659791, 0.80650896, 0.16860051, 2.24112225, -0.78620857, 0.36566174, -0.07020134, -0.47976932, -0.68230027, -0.94711417, -0.54506505, 1.66504931, -0.71860826, 0.61132306}; std::vector c = { 2.82721668e-02, 6.44195229e-02, 1.53499246e-02, 1.72468081e-01, -6.33238107e-02, 9.49496776e-02, 1.40258059e-01, -7.92879611e-02, -1.29301161e-01, 3.11307609e-03, -1.90624535e-01, 1.13238767e-01, -2.80647576e-02, 3.12882811e-02, -3.52091640e-02, 3.33581865e-02, 6.43158704e-02, 7.40238279e-02, -1.00106120e-01, -9.56912562e-02, 1.44342467e-01, 9.40258950e-02, 6.36333972e-02, 1.66158378e-03, -8.91554281e-02, 2.58734226e-02, 1.70919895e-02, 1.78214177e-01, 8.84564668e-02, 8.98126513e-02, -1.63809001e-01, 1.37802169e-01, 1.66439757e-01, -1.45631135e-02, 1.88469887e-04, 4.76950556e-02, -1.91969007e-01, -1.76233292e-01, -7.70473927e-02, 1.14828631e-01, 1.76608220e-01, -1.50728196e-01, 1.99946314e-02, -5.88052124e-02, 1.31612435e-01, 1.61106288e-02, -1.35080189e-01, 1.49512306e-01, 3.86456847e-02, 1.29330024e-01, -3.22975963e-02, -5.60784787e-02, -5.41997552e-02, 4.78562862e-02}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 4, 4, 1}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape c_shape{migraphx::shape::float_type, {2, 3, 3, 3, 1}}; auto cl = mm->add_literal(migraphx::literal{c_shape, c}); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"dilation", {1, 1, 1}}}), al, cl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.27039781, 0.19105849, -0.06339942, -0.65087199, 0.40867025, 0.05063812, -0.14907975, 0.49018705, -0.49197209, 0.33236548, -0.39374301, 0.16012701, 0.06574871, 0.71606487, -0.55201721, -0.46427044}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/convolution_backwards.cpp000066400000000000000000000334451510465702400234100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(convolution_backwards_1d) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 3}}; std::vector x_data{0, 0.5, 1}; std::vector w_data{0.5, 0.5, 0.5}; std::vector gold{0, 0.25, 0.75, 0.75, 0.5}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(migraphx::literal{s, x_data}); auto w = mm->add_literal(migraphx::literal{s, w_data}); mm->add_instruction(migraphx::make_op("convolution_backwards", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), x, w); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(convolution_backwards_2d) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; std::vector x_data{0, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector w_data{1, 1, 1, 1, 1, 1, 1, 1, 1}; std::vector gold{0, 1, 3, 3, 2, 3, 8, 15, 12, 7, 9, 21, 36, 27, 15, 9, 20, 33, 24, 13, 6, 13, 21, 15, 8}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(migraphx::literal{s, x_data}); auto w = mm->add_literal(migraphx::literal{s, w_data}); mm->add_instruction(migraphx::make_op("convolution_backwards"), x, w); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } // test values from pytorch TEST_CASE(convolution_backwards_3d) { migraphx::shape s_1{migraphx::shape::float_type, {1, 1, 1, 2, 3}}; migraphx::shape s_2{migraphx::shape::float_type, {1, 1, 3, 2, 3}}; // clang-format off std::vector x_data{0.8471, -0.4195, -2.2749, 1.2491, 0.1722, 0.3246}; std::vector w_data{ 0.6478, -0.1985, 0.0633, -0.3479, 2.7056, -0.1440, -1.1229, -0.7507, -1.3151, 0.8884, -0.1859, -0.3407, -1.1544, -1.5893, 1.6265, -1.4624, 0.3812, -1.5378 }; std::vector gold{0.5488, -0.4399, -1.3369, 0.4251, -0.1439, 0.5145, 2.3015, -0.2104, -6.1482, 0.3482, -0.4346, 3.3197, 0.1731, 0.8533, -0.0467, -0.9512, -0.1649, 1.7553, 2.2594, 2.9917, -0.6500, -1.6612, -4.3680, 0.0957, 0.3482, 1.1097, -0.0792, -0.1692, -0.1190, -0.1106, -0.9779, -0.8621, 4.6707, 2.9332, -3.7001, -2.6808, -1.2476, 3.2475, -0.4578, 4.0263, -1.8267, 0.2243, -2.3299, -0.1411, -0.4991}; // clang-format on migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(migraphx::literal{s_1, x_data}); auto w = mm->add_literal(migraphx::literal{s_2, w_data}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"dilation", {1, 1, 1}}}), x, w); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(convolution_backwards_padding1) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; std::vector x_data{0, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector w_data{1, 1, 1, 1, 1, 1, 1, 1, 1}; std::vector gold{8, 15, 12, 21, 36, 27, 20, 33, 24}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(migraphx::literal{s, x_data}); auto w = mm->add_literal(migraphx::literal{s, w_data}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {1, 1}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), x, w); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(convolution_backwards_padding2) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; std::vector x_data{0, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector w_data{1, 1, 1, 1, 1, 1, 1, 1, 1}; std::vector gold{3., 8., 15., 12., 7., 9., 21., 36., 27., 15., 9., 20., 33., 24., 13.}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(migraphx::literal{s, x_data}); auto w = mm->add_literal(migraphx::literal{s, w_data}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {1, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), x, w); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(convolution_backwards_2stride) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; std::vector x_data{0, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector w_data{1, 1, 1, 1, 1, 1, 1, 1, 1}; std::vector gold{0., 0., 1., 1., 3., 2., 2., 0., 0., 1., 1., 3., 2., 2., 3., 3., 8., 5., 12., 7., 7., 3., 3., 7., 4., 9., 5., 5., 9., 9., 20., 11., 24., 13., 13., 6., 6., 13., 7., 15., 8., 8., 6., 6., 13., 7., 15., 8., 8.}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(migraphx::literal{s, x_data}); auto w = mm->add_literal(migraphx::literal{s, w_data}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0, 0}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(convolution_backwards_2dilation) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; std::vector x_data{0, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector w_data{1, 1, 1, 1, 1, 1, 1, 1, 1}; std::vector gold{0., 1., 2., 1., 2., 1., 2., 3., 4., 8., 4., 8., 4., 5., 6., 8., 16., 8., 16., 8., 10., 3., 4., 8., 4., 8., 4., 5., 6., 8., 16., 8., 16., 8., 10., 3., 4., 8., 4., 8., 4., 5., 6., 7., 14., 7., 14., 7., 8.}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(migraphx::literal{s, x_data}); auto w = mm->add_literal(migraphx::literal{s, w_data}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {2, 2}}}), x, w); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } // test values from pytorch TEST_CASE(convolution_backwards_2d_group) { migraphx::shape activations_shape{migraphx::shape::float_type, {1, 2, 3, 3}}; migraphx::shape weights_shape{migraphx::shape::float_type, {2, 2, 3, 3}}; std::vector x_data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; std::vector w_data(36, 1.0); std::vector gold{ 0., 1., 3., 3., 2., 3., 8., 15., 12., 7., 9., 21., 36., 27., 15., 9., 20., 33., 24., 13., 6., 13., 21., 15., 8., 0., 1., 3., 3., 2., 3., 8., 15., 12., 7., 9., 21., 36., 27., 15., 9., 20., 33., 24., 13., 6., 13., 21., 15., 8., 9., 19., 30., 21., 11., 21., 44., 69., 48., 25., 36., 75., 117., 81., 42., 27., 56., 87., 60., 31., 15., 31., 48., 33., 17., 9., 19., 30., 21., 11., 21., 44., 69., 48., 25., 36., 75., 117., 81., 42., 27., 56., 87., 60., 31., 15., 31., 48., 33., 17.}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(migraphx::literal{activations_shape, x_data}); auto w = mm->add_literal(migraphx::literal{weights_shape, w_data}); mm->add_instruction(migraphx::make_op("convolution_backwards", {{"group", 2}}), x, w); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(convolution_backwards_dyn_batch1) { migraphx::program p; auto* mm = p.get_main_module(); // clang-format off migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {1, 1}, {3, 3}, {3, 3}}}; // clang-format on auto x = mm->add_parameter("x", s); auto w = mm->add_parameter("w", s); mm->add_instruction(migraphx::make_op("convolution_backwards"), x, w); p.compile(migraphx::make_target("ref")); std::vector x_data{0, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector w_data{1, 1, 1, 1, 1, 1, 1, 1, 1}; migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 1, 3, 3}}; params["x"] = migraphx::argument(input_fixed_shape, x_data.data()); params["w"] = migraphx::argument(input_fixed_shape, w_data.data()); auto result = p.eval(params).back(); std::vector gold{0, 1, 3, 3, 2, 3, 8, 15, 12, 7, 9, 21, 36, 27, 15, 9, 20, 33, 24, 13, 6, 13, 21, 15, 8}; std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(convolution_backwards_dyn_batch2) { migraphx::program p; auto* mm = p.get_main_module(); // clang-format off migraphx::shape x_shape{migraphx::shape::float_type, {{1, 4}, {1, 1}, {5, 5}, {5, 5}}}; // clang-format on auto x = mm->add_parameter("x", x_shape); migraphx::shape w_shape{migraphx::shape::float_type, {1, 1, 3, 3}}; std::vector w_data(9, 1.); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {2, 2}}, {"stride", {2, 2}}, {"dilation", {2, 2}}}), x, w); p.compile(migraphx::make_target("ref")); std::vector x_data(25); std::iota(x_data.begin(), x_data.end(), 0.); migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 1, 5, 5}}; params["x"] = migraphx::argument(input_fixed_shape, x_data.data()); auto result = p.eval(params).back(); //clang-format off std::vector gold{12., 0., 21., 0., 27., 0., 33., 0., 24., 0., 0., 0., 0., 0., 0., 0., 0., 0., 33., 0., 54., 0., 63., 0., 72., 0., 51., 0., 0., 0., 0., 0., 0., 0., 0., 0., 63., 0., 99., 0., 108., 0., 117., 0., 81., 0., 0., 0., 0., 0., 0., 0., 0., 0., 93., 0., 144., 0., 153., 0., 162., 0., 111., 0., 0., 0., 0., 0., 0., 0., 0., 0., 72., 0., 111., 0., 117., 0., 123., 0., 84.}; //clang-format on std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/cos.cpp000066400000000000000000000060601510465702400175650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(cos_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data{-1, 0, 1}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("cos"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return cosf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(cos_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("cos"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-1, 0, 1}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return cosf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/cosh.cpp000066400000000000000000000061501510465702400177350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(cosh_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector data = {-1.0, 2.0, -3.0, 4.0}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("cosh"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return coshf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(cosh_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("cosh"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {-1.0, 2.0, -3.0, 4.0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return coshf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/dequantizelinear.cpp000066400000000000000000000067101510465702400223470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(dequantizelinear_unsigned_int8) { /*uint8*/ migraphx::shape xs{migraphx::shape::uint8_type, {1, 3, 3}}; std::vector xv = {0, 1, 2, 5, 10, 50, 100, 150, 250}; migraphx::shape ss{migraphx::shape::float_type, {1, 3, 3}}; std::vector sv = {2, 2, 2, 2, 2, 2, 2, 2, 2}; migraphx::shape zs{migraphx::shape::uint8_type, {1, 3, 3}}; std::vector zv = {0, 0, 0, 0, 0, 0, 0, 0, 0}; auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(ss, sv); auto z = mm->add_literal(zs, zv); mm->add_instruction(migraphx::make_op("dequantizelinear"), x, s, z); return p; }; migraphx::program p1 = create_program(); p1.compile(migraphx::make_target("ref")); auto result = p1.eval({}).back(); std::vector results_vector(9); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0, 2, 4, 10, 20, 100, 200, 300, 500}; EXPECT(results_vector == gold); } TEST_CASE(dequantizelinear_signed_int8) { /*int8*/ migraphx::shape xs{migraphx::shape::int8_type, {1, 3, 3}}; std::vector xv = {-128, -100, -50, -1, 0, 1, 50, 100, 127}; migraphx::shape ss{migraphx::shape::float_type, {1, 3, 3}}; std::vector sv = {2, 2, 2, 2, 2, 2, 2, 2, 2}; auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(ss, sv); mm->add_instruction(migraphx::make_op("dequantizelinear"), x, s); return p; }; migraphx::program p1 = create_program(); p1.compile(migraphx::make_target("ref")); auto result = p1.eval({}).back(); std::vector results_vector(9); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-256, -200, -100, -2, 0, 2, 100, 200, 254}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/dimensions_of.cpp000066400000000000000000000061401510465702400216340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(dimensions_of_test0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4, {2, 4}}, {3, 3}, {4, 4}}}; auto p1 = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("dimensions_of", {{"end", 3}}), p1); p.compile(migraphx::make_target("ref")); std::vector x_data(24, 1.0); migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 3, 4}}; migraphx::parameter_map params; params["x"] = migraphx::argument(input_fixed_shape, x_data.data()); auto result = p.eval(params).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {2, 3, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(dimensions_of_test1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4, {1, 4}}, {3, 3}, {3, 8}, {3, 8}}}; auto p1 = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("dimensions_of", {{"start", 2}, {"end", 4}}), p1); p.compile(migraphx::make_target("ref")); std::vector x_data(48, 1.0); migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 4, 4}}; migraphx::parameter_map params; params["x"] = migraphx::argument(input_fixed_shape, x_data.data()); auto result = p.eval(params).back(); std::vector results_vector(2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {4, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/div.cpp000066400000000000000000000066051510465702400175700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(div_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data1 = {-1.0f, 0.5f, 1.0f}; std::vector data2 = {1.0f, 2.0f, 4.0f}; auto l1 = mm->add_literal(migraphx::literal{s, data1}); auto l2 = mm->add_literal(migraphx::literal{s, data2}); mm->add_instruction(migraphx::make_op("div"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(data1.size()); std::transform(data1.begin(), data1.end(), data2.begin(), gold.begin(), std::divides()); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(div_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6, {3}}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("div"), x, y); p.compile(migraphx::make_target("ref")); std::vector x_data{-1.0f, 0.5f, 1.0f}; std::vector y_data{1.0f, 2.0f, 4.0f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(x_data.size()); std::transform( x_data.begin(), x_data.end(), y_data.begin(), gold.begin(), std::divides()); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/dot_op.cpp000066400000000000000000002455301510465702400202740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include "test.hpp" template static void dot_2d_test() { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {-0.00925222, 0.56250403, 0.70107397, 0.75402161, -0.505885, 1.33628943, -0.11413, -0.31270559, 1.59336732, -0.19361027, -0.91620867, 0.40108416, -0.06969921, 0.68483471, -0.39906632, -1.66423624, 0.69040076, -1.31490171, -0.11282616, -0.79391814}; std::vector b = {6.09568541e-01, -6.10527007e-01, 3.66646462e-01, 1.18951101e-01, 5.58777432e-01, -3.21296298e-01, -5.95997198e-01, -5.01425721e-01, -2.84606807e-01, -5.73673557e-01, -8.99430260e-01, -4.25103093e-01, 1.53027987e+00, -3.81407415e-04, -3.29650255e-01}; migraphx::shape a_shape{migraphx::shape::get_type{}, {4, 5}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::get_type{}, {5, 3}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); mm->add_instruction(migraphx::make_op("dot"), al, bl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.56327541e+00, -7.09570140e-01, -5.37424982e-01, -2.22994831e-01, -2.15586437e+00, 2.09177941e-03, -1.47279677e+00, 2.02627040e-01, -6.04527691e-01, -1.29885596e+00, 2.16294914e+00, -1.48101497e-01}; EXPECT(migraphx::verify::verify_range_with_tolerance( results_vector, migraphx::verify::expected{gold}, migraphx::verify::tolerance{9e-6})); } TEST_CASE_REGISTER(dot_2d_test) TEST_CASE_REGISTER(dot_2d_test) template static void dot_4d_test() { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {-0.00925222, 0.56250403, 0.70107397, 0.75402161, -0.505885, 1.33628943, -0.11413, -0.31270559, 1.59336732, -0.19361027, -0.91620867, 0.40108416, -0.06969921, 0.68483471, -0.39906632, -1.66423624, 0.69040076, -1.31490171, -0.11282616, -0.79391814}; std::vector b = {6.09568541e-01, -6.10527007e-01, 3.66646462e-01, 1.18951101e-01, 5.58777432e-01, -3.21296298e-01, -5.95997198e-01, -5.01425721e-01, -2.84606807e-01, -5.73673557e-01, -8.99430260e-01, -4.25103093e-01, 1.53027987e+00, -3.81407415e-04, -3.29650255e-01}; std::vector gold = {-1.56327541e+00, -7.09570140e-01, -5.37424982e-01, -2.22994831e-01, -2.15586437e+00, 2.09177941e-03, -1.47279677e+00, 2.02627040e-01, -6.04527691e-01, -1.29885596e+00, 2.16294914e+00, -1.48101497e-01}; migraphx::shape a_shape{migraphx::shape::get_type{}, {1, 1, 4, 5}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::get_type{}, {1, 1, 5, 3}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); mm->add_instruction(migraphx::make_op("dot"), al, bl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_range_with_tolerance( results_vector, migraphx::verify::expected{gold}, migraphx::verify::tolerance{9e-6})); } TEST_CASE_REGISTER(dot_4d_test) TEST_CASE_REGISTER(dot_4d_test) TEST_CASE(dot_3_d_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector m1 = {-0.76234141, 0.01368910, -0.86343423, -0.99465282, 0.76133268, 0.96507140, -0.55893585, 0.02625652, 0.75171776, 0.23112578, 0.25624787, -1.50442161}; migraphx::shape m1_shape{migraphx::shape::float_type, {2, 2, 3}}; std::vector m2 = {-0.15933632, -0.69594712, -0.06198966, -1.23905184, -0.83672704, -1.06971832, -0.12272917, 1.07094116, -0.08346820, 1.16820693, -0.95700874, 0.24059691, 0.43326023, 0.78305235, -0.53506601, -0.69359678, -0.26334436, 1.56292796, -0.33629175, -1.72693469, 0.41435494, 1.52136843, -0.40699791, -1.59839430}; migraphx::shape m2_shape{migraphx::shape::float_type, {2, 3, 4}}; auto l1 = mm->add_literal(migraphx::literal{m1_shape, m1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, m2}); mm->add_instruction(migraphx::make_op("dot"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); std::vector gold = {0.18208394, -0.49276402, 0.87189133, 0.75150114, -0.55909610, 1.00521735, -0.95536130, 2.27996211, 0.06239879, 0.74700068, -0.01570983, -0.85920856, -0.59070835, -1.70729902, 0.40245487, 1.80182751}; EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_3_d_c_test0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector m1 = {-0.76234141, 0.01368910, -0.86343423, -0.99465282, 0.76133268, 0.96507140, -0.55893585, 0.02625652, 0.75171776, 0.23112578, 0.25624787, -1.50442161}; migraphx::shape m1_shape{migraphx::shape::float_type, {2, 2, 3}}; std::vector m2 = {-0.15933632, -0.69594712, -0.06198966, -1.23905184, -0.83672704, -1.06971832, -0.12272917, 1.07094116, -0.08346820, 1.16820693, -0.95700874, 0.24059691, 0.43326023, 0.78305235, -0.53506601, -0.69359678, -0.26334436, 1.56292796, -0.33629175, -1.72693469, 0.41435494, 1.52136843, -0.40699791, -1.59839430}; migraphx::shape m2_shape{migraphx::shape::float_type, {2, 3, 4}}; std::vector m3 = {0.18208394, -0.49276402, 0.87189133, 0.75150114, -0.55909610, 1.00521735, -0.95536130, 2.27996211, 0.06239879, 0.74700068, -0.01570983, -0.85920856, -0.59070835, -1.70729902, 0.40245487, 1.80182751}; migraphx::shape m3_shape{migraphx::shape::float_type, {2, 2, 4}}; auto l1 = mm->add_literal(migraphx::literal{m1_shape, m1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, m2}); auto l3 = mm->add_literal(migraphx::literal{m3_shape, m3}); float alpha = 1.0f; float beta = 0.0f; migraphx::add_apply_alpha_beta(*mm, std::vector{l1, l2, l3}, migraphx::make_op("dot"), alpha, beta); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); std::vector gold = {0.18208394, -0.49276402, 0.87189133, 0.75150114, -0.55909610, 1.00521735, -0.95536130, 2.27996211, 0.06239879, 0.74700068, -0.01570983, -0.85920856, -0.59070835, -1.70729902, 0.40245487, 1.80182751}; EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_3_d_c_test1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector m1 = { -0.76234141, 0.01368910, -0.86343423, -0.99465282, 0.76133268, 0.96507140}; migraphx::shape m1_shape{migraphx::shape::float_type, {1, 2, 3}}; std::vector m2 = {-0.15933632, -0.69594712, -0.06198966, -1.23905184, -0.83672704, -1.06971832, -0.12272917, 1.07094116, -0.08346820, 1.16820693, -0.95700874, 0.24059691}; migraphx::shape m2_shape{migraphx::shape::float_type, {1, 3, 4}}; migraphx::shape m3_shape{migraphx::shape::float_type, {1, 2, 4}}; std::vector m3 = {0.18208394, -0.49276402, 0.87189133, 0.75150114, -0.55909610, 1.00521735, -0.95536130, 2.27996211}; auto l1 = mm->add_literal(migraphx::literal{m1_shape, m1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, m2}); auto l3 = mm->add_literal(migraphx::literal{m3_shape, m3}); float alpha = 1.0f; float beta = 0.0f; migraphx::add_apply_alpha_beta(*mm, std::vector{l1, l2, l3}, migraphx::make_op("dot"), alpha, beta); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); std::vector gold = {0.18208394, -0.49276402, 0.87189133, 0.75150114, -0.55909610, 1.00521735, -0.95536130, 2.27996211}; EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_4_d_test1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector m1 = { -1.93300070, 0.33902698, -0.45173527, -0.72283069, -0.17177134, 1.62199882, 0.87052847, 0.14989811, -0.88969184, -0.18131398, 0.72654339, -0.57123693, 0.03852506, -0.72332085, -1.81844083, -0.33465167, -0.71400352, 0.36883161, 0.08698452, 0.94974586, 0.40087323, -0.05448534, 0.03220677, -1.22494296, 0.97938472, -1.43714454, -0.80430904, -0.08098728, 0.31520301, 0.49642169, -1.63471091, 0.34390096, 2.81292176, -0.22666528, 1.54559556, -1.51075762}; migraphx::shape m1_shape{migraphx::shape::float_type, {2, 3, 2, 3}}; std::vector m2 = { -0.33170529, 2.26325120, -0.50639461, 0.64802947, 0.44748888, 0.33768068, -0.53621075, 0.34341460, 0.58742520, -1.13995790, -0.99322535, 0.35447353, 0.01977110, -0.10155016, -1.02288245, -0.16575791, -1.47870374, 0.29300008, -0.39112198, 1.42303608, -0.02853060, 1.52610164, 0.53540909, 0.75618998, -0.26877787, -1.90886366, 0.30622790, 0.59794535, 1.29795331, -0.37805803, -1.58167176, -1.26966832, 0.27435891, 0.89430347, 0.22854926, -0.50317658}; migraphx::shape m2_shape{migraphx::shape::float_type, {2, 3, 3, 2}}; auto l1 = mm->add_literal(migraphx::literal{m1_shape, m1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, m2}); mm->add_instruction(migraphx::make_op("dot"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); std::vector gold = {0.26735861, -4.30770895, 1.05257728, -1.19954265, 0.50493170, -0.18729756, 1.09137941, -1.09298312, 3.42956915, -0.41681939, 0.17833257, 0.26040336, 0.15351280, 1.87632715, -0.63545406, -0.95467340, -1.74728628, -2.42477030, 0.76262372, 0.15539164, 3.32281958, 0.96769613, 0.43727545, 2.43019906}; EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_4_d_alpha_beta_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector m1 = { 1.23636469, -0.47041261, -0.14375651, -0.48371852, 1.16479301, -0.89361055, -0.18569086, 1.10700457, -1.02632638, 0.82277012, 0.33525769, 0.52825145, -1.00141689, 0.45510090, -0.02675039, -0.60454439, 0.38551153, -0.01658514, 0.93059292, -0.54595188, -0.04911005, -0.91397221, -0.83127477, -1.57685603, -1.36200452, 2.25822236, -1.23416970, 0.12312496, 0.76232760, -0.83594234, 1.67418145, -0.19412936, 1.05261378, 0.66246074, -1.15233398, 0.16429736}; migraphx::shape m1_shape{migraphx::shape::float_type, {2, 3, 2, 3}}; std::vector m2 = { -0.87300530, -0.07112838, 0.19196860, -1.04986840, 1.20348200, 0.31966893, 1.04805440, -2.04777729, -0.67906052, -1.17250760, 0.34305044, -1.01957785, -1.12694862, 0.18431338, -1.63712290, 0.27566931, -1.11282021, 1.41738919, 0.47871283, -1.01980420, 1.00212436, -0.78740444, -1.65636133, 1.51466547, -0.12470397, 0.70404393, -0.15244797, 0.74288871, 0.07339926, -1.45811623, 0.27185845, 0.08804596, 0.99061977, -1.61752428, 0.29191159, 0.87271953}; migraphx::shape m2_shape{migraphx::shape::float_type, {2, 3, 3, 2}}; std::vector m3 = {-1.07692443, 0.85223457, -0.37266530, 2.31511577, 0.04227017, 1.13229428, -0.52769242, 0.27307182, -0.47779843, -0.08023168, -0.22862823, 0.81489871, 1.13139581, 1.13860467, 0.24309065, 0.26533729, 0.49106772, -1.18860493, 0.27842449, 1.03568141, 0.49759611, 0.10021662, 0.00592602, 0.90862000}; migraphx::shape m3_shape{migraphx::shape::float_type, {2, 3, 2, 2}}; auto l1 = mm->add_literal(migraphx::literal{m1_shape, m1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, m2}); auto l3 = mm->add_literal(migraphx::literal{m3_shape, m3}); float alpha = 0.35; float beta = 0.41; auto m12_alpha = migraphx::add_apply_alpha_beta( *mm, std::vector{l1, l2}, migraphx::make_op("dot"), alpha); auto l_beta = mm->add_literal(beta); auto b_beta = mm->add_instruction( migraphx::make_op("scalar", {{"scalar_bcst_dims", m12_alpha->get_shape().lens()}}), l_beta); auto m3_beta = mm->add_instruction(migraphx::make_op("mul"), b_beta, l3); mm->add_instruction(migraphx::make_op("add"), m3_beta, m12_alpha); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); std::vector gold = {-0.91147203, 0.47540785, -0.30313587, 0.43325099, -0.43711586, 0.50928632, 0.06919868, -0.80382802, -0.05125718, -0.06685650, -0.06972163, 0.32407764, 0.45677396, 0.25909489, 0.56911252, -0.17183724, 0.10858734, 0.39406289, 0.04662959, 1.07979824, 0.40355016, 0.52410648, -0.31728447, 1.09550845}; EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_4_d_alpha_beta_c_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector m1 = { 1.23636469, -0.47041261, -0.14375651, -0.48371852, 1.16479301, -0.89361055, -0.18569086, 1.10700457, -1.02632638, 0.82277012, 0.33525769, 0.52825145, -1.00141689, 0.45510090, -0.02675039, -0.60454439, 0.38551153, -0.01658514, 0.93059292, -0.54595188, -0.04911005, -0.91397221, -0.83127477, -1.57685603, -1.36200452, 2.25822236, -1.23416970, 0.12312496, 0.76232760, -0.83594234, 1.67418145, -0.19412936, 1.05261378, 0.66246074, -1.15233398, 0.16429736}; migraphx::shape m1_shape{migraphx::shape::float_type, {2, 3, 2, 3}}; std::vector m2 = { -0.87300530, -0.07112838, 0.19196860, -1.04986840, 1.20348200, 0.31966893, 1.04805440, -2.04777729, -0.67906052, -1.17250760, 0.34305044, -1.01957785, -1.12694862, 0.18431338, -1.63712290, 0.27566931, -1.11282021, 1.41738919, 0.47871283, -1.01980420, 1.00212436, -0.78740444, -1.65636133, 1.51466547, -0.12470397, 0.70404393, -0.15244797, 0.74288871, 0.07339926, -1.45811623, 0.27185845, 0.08804596, 0.99061977, -1.61752428, 0.29191159, 0.87271953}; migraphx::shape m2_shape{migraphx::shape::float_type, {2, 3, 3, 2}}; std::vector m3 = {-1.07692443, 0.85223457, -0.37266530, 2.31511577, 0.04227017, 1.13229428, -0.52769242, 0.27307182, -0.47779843, -0.08023168, -0.22862823, 0.81489871, 1.13139581, 1.13860467, 0.24309065, 0.26533729, 0.49106772, -1.18860493, 0.27842449, 1.03568141, 0.49759611, 0.10021662, 0.00592602, 0.90862000}; migraphx::shape m3_shape{migraphx::shape::float_type, {2, 3, 2, 2}}; auto l1 = mm->add_literal(migraphx::literal{m1_shape, m1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, m2}); auto l3 = mm->add_literal(migraphx::literal{m3_shape, m3}); float alpha = 0.35; float beta = 0.41; migraphx::add_apply_alpha_beta(*mm, std::vector{l1, l2, l3}, migraphx::make_op("dot"), alpha, beta); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); std::vector gold = {-0.91147203, 0.47540785, -0.30313587, 0.43325099, -0.43711586, 0.50928632, 0.06919868, -0.80382802, -0.05125718, -0.06685650, -0.06972163, 0.32407764, 0.45677396, 0.25909489, 0.56911252, -0.17183724, 0.10858734, 0.39406289, 0.04662959, 1.07979824, 0.40355016, 0.52410648, -0.31728447, 1.09550845}; EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_2_d_c_test0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {-0.86217194, -1.04129542, -0.64850364, -0.97078327, -0.40516386, 0.83136927, 0.37717502, 0.42271939, 1.10062165, -0.92239359, 0.40403076, -0.43935377}; std::vector b = {0.76084386, 1.89201125, 1.73218067, 0.7148568, -0.55578914, 0.05799101, -1.24090721, -0.51151978, 1.13255803, 0.21540723, -1.10459009, 0.45580331}; std::vector c = {-0.80473623, 0.35154171, -2.73077756, -0.09093885, -1.88850472, -0.03375556, -0.41798276, 2.87368099, 2.11031439}; migraphx::shape a_shape{migraphx::shape::float_type, {3, 4}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::float_type, {4, 3}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); migraphx::shape c_shape{migraphx::shape::float_type, {3, 3}}; auto cl = mm->add_literal(migraphx::literal{c_shape, c}); migraphx::add_apply_alpha_beta(*mm, {al, bl, cl}, migraphx::make_op("dot"), 1.0f, 1.0f); std::vector gold = { -1.60947, 0.703083, -5.46156, -0.181878, -3.77701, -0.0675112, -0.835966, 5.74736, 4.22063}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_vv_inner_product_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {0.7481789, 0.02906279, 1.01193836, 1.60222907, 1.89135978, 0.30054158, -0.4892588, -0.27027533}; std::vector b = {-0.25829116, 0.27908929, -1.27888957, 0.21152361, 0.08593658, 0.52163899, 1.38343824, -0.2342857}; migraphx::shape a_shape{migraphx::shape::float_type, {8}}; migraphx::shape b_shape{migraphx::shape::float_type, {8}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto ual = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), al); auto ubl = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), bl); mm->add_instruction(migraphx::make_op("dot"), ual, ubl); std::vector gold = {-1.43461}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_vv_inner_product_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {0.7481789, 0.02906279, 1.01193836, 1.60222907, 1.89135978, 0.30054158, -0.4892588, -0.27027533}; std::vector b = {-0.25829116, 0.27908929, -1.27888957, 0.21152361, 0.08593658, 0.52163899, 1.38343824, -0.2342857}; migraphx::shape a_shape{migraphx::shape::float_type, {8}}; migraphx::shape b_shape{migraphx::shape::float_type, {8}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto ual = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), al); auto ubl = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), bl); float alpha = 0.32f; migraphx::add_apply_alpha_beta( *mm, std::vector{ual, ubl}, migraphx::make_op("dot"), alpha); std::vector gold = {-0.4590752}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_vm_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {1.49530002, -0.07181969, 0.44593846, -0.8645019, 0.52992304, -0.4910338, -2.12179422, -0.45962977}; std::vector b = { -0.06210242, 0.0187149, 1.47482984, -1.19590602, -0.45601701, 0.36934488, -0.83913193, 0.75350964, 0.80707019, 0.35923582, -2.18480722, -0.85608682, 0.75849199, 0.49103473, -0.91329477, -0.36364322, -0.69688937, 0.07165814, -0.15505523, 0.52221663, -0.98631192, -0.37353654, -1.89818706, -0.87209739, -0.33942003, 0.11390353, 0.78181162, -0.18395337, -0.34743419, -0.08091231, 1.21119765, 1.23869861, 1.42169414, 0.86412382, 1.05898002, -0.31918307, 1.08546695, 1.50682711, -0.66083538, -0.32683929}; migraphx::shape a_shape{migraphx::shape::float_type, {8}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); auto ual = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), al); migraphx::shape b_shape{migraphx::shape::float_type, {8, 5}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); mm->add_instruction(migraphx::make_op("dot"), ual, bl); std::vector gold = {-3.78111, -3.40007, -2.1972, -3.31448, -3.80326}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_vm_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {1.49530002, -0.07181969, 0.44593846, -0.8645019, 0.52992304, -0.4910338, -2.12179422, -0.45962977}; std::vector b = { -0.06210242, 0.0187149, 1.47482984, -1.19590602, -0.45601701, 0.36934488, -0.83913193, 0.75350964, 0.80707019, 0.35923582, -2.18480722, -0.85608682, 0.75849199, 0.49103473, -0.91329477, -0.36364322, -0.69688937, 0.07165814, -0.15505523, 0.52221663, -0.98631192, -0.37353654, -1.89818706, -0.87209739, -0.33942003, 0.11390353, 0.78181162, -0.18395337, -0.34743419, -0.08091231, 1.21119765, 1.23869861, 1.42169414, 0.86412382, 1.05898002, -0.31918307, 1.08546695, 1.50682711, -0.66083538, -0.32683929}; migraphx::shape a_shape{migraphx::shape::float_type, {8}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); auto ual = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), al); migraphx::shape b_shape{migraphx::shape::float_type, {8, 5}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); float alpha = 0.5f; migraphx::add_apply_alpha_beta( *mm, std::vector{ual, bl}, migraphx::make_op("dot"), alpha); std::vector gold = {-1.89056, -1.70003, -1.0986, -1.65724, -1.90163}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_vm_3) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {-1.7468318, -0.38900251, 1.00183915, 0.06016438, 0.08295905, 1.5830535}; std::vector b = { 1.2459538, 0.39586199, -0.77035574, 0.22689828, 0.3289835, 1.02804361, -0.22941113, -0.33940324, 0.80078249, 1.0319152, 0.80034948, -0.11631159, 0.36899208, -0.28506697, -1.2211584, -0.55678377, -0.3618498, 0.34857264, -0.38700147, -0.43434611, 1.73029783, -0.71578372, 0.09777723, 0.06616614, -1.66721186, -0.16046032, -1.64581663, 1.09373609, -0.14127692, -0.01938473, -0.67310303, -1.56154787, -1.0665462, 0.68538535, -1.53920085, -0.35710272, 0.06887234, 0.17474616, 1.08194804, -0.19990148, -0.91149488, 0.95303646, 0.95448717, -0.49332393, -1.762213, -0.56571194, -1.69704968, -0.82798066, 0.65531872, 1.5007798, 0.99877355, 0.53386114, -0.88150609, -1.0756985, 0.50962511, -0.68019002, 0.1583068, 2.83988407, -1.10292457, 0.02126969, 0.21129951, 0.25690146, -1.6490316, 0.55261771, -1.70504303, -0.02870394, -0.18205627, 0.29446203, -1.91360924, 0.46102174, 0.44977568, -0.48113321}; migraphx::shape a_shape{migraphx::shape::float_type, {6}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); auto ual = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), al); auto bual = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 1, 6}}}), ual); migraphx::shape b_shape{migraphx::shape::float_type, {3, 6, 4}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); mm->add_instruction(migraphx::make_op("dot"), bual, bl); std::vector gold = {1.22914, -1.17896, 2.28596, -0.345637, -0.962362, 0.168508, -0.947471, -3.02458, -3.80131, 1.38484, -2.45019, -1.35064}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_vm_4) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {-1.7468318, -0.38900251, 1.00183915, 0.06016438, 0.08295905, 1.5830535}; std::vector b = { 1.2459538, 0.39586199, -0.77035574, 0.22689828, 0.3289835, 1.02804361, -0.22941113, -0.33940324, 0.80078249, 1.0319152, 0.80034948, -0.11631159, 0.36899208, -0.28506697, -1.2211584, -0.55678377, -0.3618498, 0.34857264, -0.38700147, -0.43434611, 1.73029783, -0.71578372, 0.09777723, 0.06616614, -1.66721186, -0.16046032, -1.64581663, 1.09373609, -0.14127692, -0.01938473, -0.67310303, -1.56154787, -1.0665462, 0.68538535, -1.53920085, -0.35710272, 0.06887234, 0.17474616, 1.08194804, -0.19990148, -0.91149488, 0.95303646, 0.95448717, -0.49332393, -1.762213, -0.56571194, -1.69704968, -0.82798066, 0.65531872, 1.5007798, 0.99877355, 0.53386114, -0.88150609, -1.0756985, 0.50962511, -0.68019002, 0.1583068, 2.83988407, -1.10292457, 0.02126969, 0.21129951, 0.25690146, -1.6490316, 0.55261771, -1.70504303, -0.02870394, -0.18205627, 0.29446203, -1.91360924, 0.46102174, 0.44977568, -0.48113321}; migraphx::shape a_shape{migraphx::shape::float_type, {6}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); auto ual = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), al); auto bual = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 1, 6}}}), ual); migraphx::shape b_shape{migraphx::shape::float_type, {3, 6, 4}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); migraphx::add_apply_alpha_beta( *mm, std::vector{bual, bl}, migraphx::make_op("dot"), 0.21f); std::vector gold = {0.25812, -0.247582, 0.480051, -0.0725837, -0.202096, 0.0353867, -0.198969, -0.635161, -0.798275, 0.290817, -0.514539, -0.283635}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mv_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {0.1612524, 0.61266466, -0.19212896, 1.34228825, -1.09746949, 0.4680955, -0.431748, -0.89791241, -2.19078702, -0.13767058, -1.66105228, -0.91834613, 0.59199744, 1.41967261, 0.76237423}; std::vector b = {0.14365572, 0.23401411, -0.8970094, -0.12526676, -1.04703286}; migraphx::shape a_shape{migraphx::shape::float_type, {3, 5}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::float_type, {5}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto ubl = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), bl); mm->add_instruction(migraphx::make_op("dot"), al, ubl); std::vector gold = {1.31982, 1.19022, -1.96062}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mv_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {0.1612524, 0.61266466, -0.19212896, 1.34228825, -1.09746949, 0.4680955, -0.431748, -0.89791241, -2.19078702, -0.13767058, -1.66105228, -0.91834613, 0.59199744, 1.41967261, 0.76237423}; std::vector b = {0.14365572, 0.23401411, -0.8970094, -0.12526676, -1.04703286}; migraphx::shape a_shape{migraphx::shape::float_type, {3, 5}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::float_type, {5}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto ubl = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), bl); float alpha = 0.3f; migraphx::add_apply_alpha_beta( *mm, std::vector{al, ubl}, migraphx::make_op("dot"), alpha); std::vector gold = {0.395946, 0.357067, -0.588187}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mv_3) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 1.24593227, -0.84351316, 0.27882229, -0.42518484, -1.11391528, 0.59141834, 1.34198714, 2.25884063, -1.32093452, 0.44766336, -0.09306479, 0.47526699, 0.25858488, 1.30820392, 1.17186787, 0.31530864, -1.19159424, -0.24100903, -1.03857886, 1.54453427, 0.05041654, 1.67108177, 0.965805, 0.52958924, -1.61243992, 0.02941846, 0.77523836, 1.97963853, -2.51093596, 0.21882645, -2.60193574, 1.1899952, 1.70883519, 0.94586745, 2.65002512, -1.42427102, 1.0143951, -1.34115312, 1.63833732, -1.46477355, 0.44014877, 0.58032696, -1.63874372, -0.82834423, 1.81131778, -0.52393379, 1.16721943, 0.39488835, 0.23947128, -0.15733194, 0.19451158, 1.21315445, 0.44594897, 0.40809135, -0.64252994, 0.7541716, -0.97203195, 0.69208485, 0.34350988, 0.9836842}; std::vector b = {0.05013914, 1.39932885, 2.56616476, 1.02225623, -0.03977829}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 2, 3, 5}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::float_type, {5}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto ubl = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), bl); auto bubl = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 5, 1}}}), ubl); mm->add_instruction(migraphx::make_op("dot"), al, bubl); std::vector gold = {-0.792717, 6.33595, 2.61466, -3.39322, 5.42485, 3.59084, 6.78139, -0.360492, -4.28998, 2.87146, 3.29447, 0.765651}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mm1_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { -0.49450006, -1.07431991, -0.02796692, -0.99631927, 0.20040449, -1.39709437, -0.15695328, 0.08208373, -0.09746386, 0.77923021, -0.1849151, 0.14419043, -0.25798175, -0.2504807, -1.11134383, -0.71030613, -0.20234025, 0.90229168, 0.62643053, -0.83512638, 1.66051254, 0.05941673, 0.73081559, 0.27111867, 0.55060745, 0.34999583, 1.02236619, 0.60178395, 1.49646162, 1.93255155, -3.65357913, -1.38059906, -0.46302398, 0.19847152, 0.39785875, 1.47004861, -1.24482133, -0.01954702, 0.36073898, 1.56055978, -0.10344603, -0.34283135, -0.56482649, 1.80861249, -0.92268202, 0.94371182, -0.02373232, -0.75441145, 0.43325034, 0.4057425, -0.48844822, -0.36390512, 0.74110406, 1.25158366, 0.52196654, 1.43461691, -0.57530864, -0.66716206, -1.76516289, 0.96582849}; std::vector b = {0.49899375, -2.20168661, 1.08895066, -0.01135643, 0.90570669, -1.43550963, -1.73033377, 0.21338776, 0.96962508, 0.38913968, -0.32822861, 0.88222863, 0.93330718, -1.24265228, -1.62587164}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 2, 3, 5}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::float_type, {5, 3}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto bbl = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 5, 3}}}), bl); mm->add_instruction(migraphx::make_op("dot"), al, bbl); std::vector gold = {-0.386828, 0.187735, -0.22822, -0.148057, 2.015, -2.56938, -0.782212, 1.9459, 0.927426, -2.44907, 2.40531, 2.30232, 0.182745, -4.21937, 1.77551, 1.50775, -2.60888, -2.32484, -0.557691, 6.13527, -2.91743, 2.37836, -6.42584, 1.14979, 0.77227, 0.349659, 2.92759, 2.32384, -2.90664, 0.0527679, -0.547761, -0.155467, 0.964619, 2.09133, -4.44281, -1.3864}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mm1_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {-0.0309568, -1.57294749, -0.00768606, 1.5786921, 0.50519718, 0.10530702, -0.05302112, -0.06503757, 0.4079716, 0.0799132, -0.82624962, 0.49341502}; std::vector b = { 0.3664867, 0.24649534, 1.14728076, 1.09911548, -1.23711247, -0.49436419, -0.67557879, -0.84180575, -1.09754376, 0.07807351, 0.74349043, -0.92084701, 0.50267885, 0.78709401, 0.80598159, -0.51269589, -0.40337193, 0.29457878, 1.25447301, -1.66251457, -1.54652239, -0.35067765, -0.5214464, -0.7866878, 1.11128573, 0.26927291, -0.0929818, 0.07523954, 0.3256776, -1.08617826, 0.89294253, -0.91007619, -2.42825765, -1.76805581, 1.08136334, -0.14521253, -1.32061148, 0.60663124, -1.19835255, -0.98803563, -1.06927896, -0.51967419, -0.98974639, 1.01287011, 1.34910394, 0.1203349, 0.67387452, -0.32447465, 1.15187449, -0.82253807, 0.22302433, 0.46434695, 0.319647, 1.56459445, 0.15664012, 0.03998102, 0.62981041, 0.11831296, 0.47824434, -0.93941882, -0.34674036, 1.17071104, 0.59203806, 2.75817738, -0.69300013, 1.30971899, -0.14231862, -1.90915568, -0.06895489, 0.20160375, 0.01945916, 0.03586956}; migraphx::shape a_shape{migraphx::shape::float_type, {3, 4}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); auto bal = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 3, 4}}}), al); migraphx::shape b_shape{migraphx::shape::float_type, {2, 3, 4, 3}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); mm->add_instruction(migraphx::make_op("dot"), bal, bl); std::vector gold = { -1.61175, 3.11849, -0.703205, 0.331635, -0.00946922, 0.645626, 0.834069, 1.06409, 0.881037, 0.227628, -0.200308, -1.71836, 0.156255, 0.477222, 0.571363, -1.04543, 1.40524, 1.24201, -2.95083, 1.19352, 1.5008, 0.636987, 0.148256, -0.0231631, -1.15079, 1.42139, 1.80996, 1.79259, 2.7192, 0.331902, -0.726565, 0.0963351, -0.710558, 0.259424, -0.342345, -1.80522, -0.580476, 0.277368, -3.95582, 0.614823, -0.415107, 0.305138, 0.435993, -0.107089, -0.767885, -4.00837, 1.09921, -2.02129, 0.109717, 0.618422, 0.438342, 0.29602, 2.00928, 0.420871}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mm2_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { -0.49450006, -1.07431991, -0.02796692, -0.99631927, 0.20040449, -1.39709437, -0.15695328, 0.08208373, -0.09746386, 0.77923021, -0.1849151, 0.14419043, -0.25798175, -0.2504807, -1.11134383, -0.71030613, -0.20234025, 0.90229168, 0.62643053, -0.83512638, 1.66051254, 0.05941673, 0.73081559, 0.27111867, 0.55060745, 0.34999583, 1.02236619, 0.60178395, 1.49646162, 1.93255155, -3.65357913, -1.38059906, -0.46302398, 0.19847152, 0.39785875, 1.47004861, -1.24482133, -0.01954702, 0.36073898, 1.56055978, -0.10344603, -0.34283135, -0.56482649, 1.80861249, -0.92268202, 0.94371182, -0.02373232, -0.75441145, 0.43325034, 0.4057425, -0.48844822, -0.36390512, 0.74110406, 1.25158366, 0.52196654, 1.43461691, -0.57530864, -0.66716206, -1.76516289, 0.96582849}; std::vector b = {-1.12211357, 1.74720423, 0.60382572, -0.61090125, -0.3315936, 0.30924675, -0.28906435, 0.64039247, -1.2822253, 0.55899286, 2.14013013, 1.00944809, 0.21660017, -0.75465098, 0.12097934, -1.64006315, 0.43582108, -0.64348541, 0.43101069, 1.30191386, 1.7746011, 0.24935804, 0.42830791, -0.13593643, 0.38749427, 1.39776254, -0.42911717, -1.3537624, -0.81999648, -0.1754485}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 2, 3, 5}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::float_type, {2, 1, 5, 3}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto bbl = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 5, 3}}}), bl); std::vector gold = { 0.70574512, -2.80915314, -1.57644969, 1.75415381, -3.13303087, -1.00150259, -0.18675123, -0.23349122, -0.12357225, 0.82911538, 1.37473744, -1.11709934, -1.84001907, 3.51427391, 0.42425673, 0.0638482, 2.40210271, 1.50027643, 4.81988916, -3.63687142, -0.19101717, -4.92522092, -1.76377022, -3.58095615, 1.83096922, 2.5512663, -1.07926588, -2.12749134, 0.33014536, -0.80393025, 0.60740202, 0.95217761, -1.06087445, -4.75868152, -3.6687713, -1.26539821}; mm->add_instruction(migraphx::make_op("dot"), al, bbl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mm2_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {-0.19276159, -1.2568421, -0.321242, 1.21471077, -0.4927751, 0.69446894, -0.1786371, -1.00763473, -0.10279314, 3.02931355, 1.08359235, -0.35190132, -0.00639111, 0.78989113, 1.23538029, 0.4590747, 0.17304142, 0.42512412, 0.21076913, -0.01724556, -0.17763898, 0.12852236, -0.00459301, 1.34498824, 0.02907823, 0.1784464, -0.20790355, -0.52336699, 0.45804085, 1.06025801}; std::vector b = {-1.12211357, 1.74720423, 0.60382572, -0.61090125, -0.3315936, 0.30924675, -0.28906435, 0.64039247, -1.2822253, 0.55899286, 2.14013013, 1.00944809, 0.21660017, -0.75465098, 0.12097934, -1.64006315, 0.43582108, -0.64348541, 0.43101069, 1.30191386, 1.7746011, 0.24935804, 0.42830791, -0.13593643, 0.38749427, 1.39776254, -0.42911717, -1.3537624, -0.81999648, -0.1754485}; migraphx::shape a_shape{migraphx::shape::float_type, {1, 2, 3, 5}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); auto bal = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 3, 5}}}), al); migraphx::shape b_shape{migraphx::shape::float_type, {2, 1, 5, 3}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto bbl = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 5, 3}}}), bl); mm->add_instruction(migraphx::make_op("dot"), bal, bbl); std::vector gold = {1.64924590e+00, 2.84575831e+00, 1.07340773e+00, 2.19817080e-01, -1.87873283e+00, 1.91883003e+00, -2.89962196e-01, 2.76404142e+00, 1.50048102e+00, -6.29650347e-01, 1.48105185e+00, -3.71716505e-03, 8.80281500e-01, 2.50057585e+00, 1.29958508e+00, 5.63751779e-01, 2.25703781e-01, 1.30516919e+00, 8.32118386e-01, 2.44050864e-01, -2.49748221e+00, -5.60803176e+00, -2.98919069e+00, -1.11429417e+00, -3.29675989e+00, 1.02442564e-01, -1.87659303e+00, -4.67302454e-01, 9.16189968e-01, -1.33537175e-01, 8.27398578e-01, 1.94406914e+00, -2.39250915e-01, -1.77062701e+00, -6.46239534e-01, -7.95202750e-01}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mm2_3) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { -0.55248691, 0.70275958, 0.56967633, 0.88206033, -0.85088547, 0.05689149, -0.20084703, 0.18024434, 1.0730491, 0.15913531, 0.93621628, 0.35072771, 1.28616952, 1.55384379, 0.30376261, -1.12356544, -0.64271552, -2.50703079, -0.23994372, 0.8166084, 0.06542249, -0.17472336, -0.37665211, 0.16342699, 0.07645941, 0.65024333, -1.19883423, -0.40536776, -0.31132765, 0.78113691, -0.16887638, 2.30797418, -0.36241233, 0.33552153, -1.05343996, -0.16909699, -1.22608815, 1.64165613, 0.96260828, -0.16733976, 0.84211199, 1.31243813, 0.89258549, -0.48250384, -1.06005206, 1.37021342, -0.35658565, 0.26879188}; std::vector b = { 0.17111129, -0.82134741, -1.58001178, -1.46759447, 0.31522514, -0.11567352, -0.038978, -0.3601414, -0.84379876, 0.24848939, -0.37080544, 0.00838631, 1.51316241, 0.42385344, 2.06043846, 1.82348849, 1.07180434, 0.6567393, 1.41164561, 0.73091185, -0.33541302, -0.98082287, -0.06605479, 0.82219717, -1.41619634, 0.51326658, 0.26916313, 0.79819769, 0.85583702, 0.07876046, -0.42375545, -0.7758751, 1.14334296, -0.14211708, -1.54520411, -0.55244869, -0.48478899, 0.10782164, -0.20879552, -0.99019754, 1.78783102, -1.31610052, 1.73510175, -0.48360172, 0.62367417, -1.34180545, -0.37512931, -1.50521357, 0.08383314, 0.76165608, -0.4961646, 0.95821311, -0.68407191, 0.48299435, -0.24323988, 0.34793412, 0.37908669, 1.19083454, 1.30218795, -0.26731035, -0.34544132, -0.09595373, 0.50951334, 0.48896956, 0.38753818, -0.4939919, 0.02352126, 0.42013764, 0.07027765, 0.21169851, -0.24411376, -1.77793736, -0.88370924, 0.95294025, -0.08208804, -0.95943892, 0.30280474, 1.1967013, -1.17700948, 0.29533973}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 2, 3, 4}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::float_type, {2, 2, 4, 5}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); mm->add_instruction(migraphx::make_op("dot"), al, bl); std::vector gold = { 1.22136035, 1.3765651, 2.0611395, 1.70445494, 1.8189619, 0.2509717, 0.88815736, 1.13837946, 1.37006127, -0.53617378, 0.45759693, -0.503786, -0.10575749, -0.81715738, 2.56316255, 0.85812927, -0.53425671, 1.38147704, 2.57874755, -1.05591061, -1.42065674, -0.25412658, -2.14494165, -2.81045272, 0.27491485, -0.04229986, 0.10181043, -0.55680682, -0.07633866, 0.313767, -0.28202571, -1.64696179, -0.50872733, -1.08935912, 0.94291084, -0.71792156, 0.82981387, 1.14797592, 3.13989358, -0.17507726, -0.63429162, -0.72241531, -0.61459168, -0.52561056, 0.3309648, -0.46185697, -1.60586695, -0.98590829, 0.63012062, -0.25606052, -0.69419352, -1.78299913, -0.38572706, 1.92249442, 0.3884186, -0.48153048, 0.84932351, 0.67234919, -1.07821322, -0.01208216}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_mm2_4) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { -0.55248691, 0.70275958, 0.56967633, 0.88206033, -0.85088547, 0.05689149, -0.20084703, 0.18024434, 1.0730491, 0.15913531, 0.93621628, 0.35072771, 1.28616952, 1.55384379, 0.30376261, -1.12356544, -0.64271552, -2.50703079, -0.23994372, 0.8166084, 0.06542249, -0.17472336, -0.37665211, 0.16342699, 0.07645941, 0.65024333, -1.19883423, -0.40536776, -0.31132765, 0.78113691, -0.16887638, 2.30797418, -0.36241233, 0.33552153, -1.05343996, -0.16909699, -1.22608815, 1.64165613, 0.96260828, -0.16733976, 0.84211199, 1.31243813, 0.89258549, -0.48250384, -1.06005206, 1.37021342, -0.35658565, 0.26879188}; std::vector b = { -0.33734601, 0.66386073, 0.41425048, 0.40190389, -0.99645073, -0.10017067, -0.58542118, 0.48636962, 0.06301405, 1.14669128, -0.06526677, 0.23172741, -1.49693143, -0.44464233, -0.12775566, -1.32038007, 1.1812471, 1.22362746, -0.49013843, 0.25339836, 1.31698705, 1.54256669, 0.11211132, -0.18005487, 0.36730145, 0.97705953, -0.18909084, 0.544932, 0.32891878, 0.64250015, -0.41381398, 0.47402562, 1.22286761, 1.07573211, -0.92988077, -0.36340925, -1.76152377, -0.96642674, -0.79231929, 0.11517073}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 2, 3, 4}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape b_shape{migraphx::shape::float_type, {2, 4, 5}}; auto bl = mm->add_literal(migraphx::literal{b_shape, b}); auto bbl = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 4, 5}}}), bl); mm->add_instruction(migraphx::make_op("dot"), al, bbl); std::vector gold = { -1.08585245, 0.39575611, 0.33947977, -0.86339678, 1.50710753, 0.05646156, -0.43180359, 0.19639674, -0.33742881, 0.98443538, -0.9021272, 1.25043704, -0.45038184, -0.14689614, -0.91749459, 3.49467934, 3.81336312, 2.4482385, 1.49649707, 1.05889193, -3.49343731, -2.06958956, -2.52082858, -1.61401519, -1.52966956, 0.01191848, -0.33246613, -0.70641362, -0.60391255, 0.28083355, 0.52255496, -1.08655006, 1.64648546, 0.80344255, 0.71987865, -3.00960296, 2.02318221, 3.32785057, -1.13203844, 1.81235734, 0.38067585, -0.88086897, 1.38307367, 0.42677257, 0.83759966, -0.34827442, -1.45067092, 2.09599671, 1.92882983, -0.30996324, 2.19736278, 2.32389426, 2.36741832, 1.62253915, 0.26698225, -0.00741609, -2.53680983, -0.0679954, 0.04499683, 0.85354276}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(dot_dyn_2_d_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::float_type, {{1, 4}, {5, 5}}}; auto ap = mm->add_parameter("a", a_shape); migraphx::shape b_shape{migraphx::shape::float_type, {5, 3}}; auto bp = mm->add_parameter("b", b_shape); mm->add_instruction(migraphx::make_op("dot"), ap, bp); p.compile(migraphx::make_target("ref")); std::vector a = {-0.00925222, 0.56250403, 0.70107397, 0.75402161, -0.505885, 1.33628943, -0.11413, -0.31270559, 1.59336732, -0.19361027, -0.91620867, 0.40108416, -0.06969921, 0.68483471, -0.39906632, -1.66423624, 0.69040076, -1.31490171, -0.11282616, -0.79391814}; std::vector b = {6.09568541e-01, -6.10527007e-01, 3.66646462e-01, 1.18951101e-01, 5.58777432e-01, -3.21296298e-01, -5.95997198e-01, -5.01425721e-01, -2.84606807e-01, -5.73673557e-01, -8.99430260e-01, -4.25103093e-01, 1.53027987e+00, -3.81407415e-04, -3.29650255e-01}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {4, 5}}; migraphx::parameter_map params; params["a"] = migraphx::argument(input_fixed_shape, a.data()); params["b"] = migraphx::argument(b_shape, b.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.56327541e+00, -7.09570140e-01, -5.37424982e-01, -2.22994831e-01, -2.15586437e+00, 2.09177941e-03, -1.47279677e+00, 2.02627040e-01, -6.04527691e-01, -1.29885596e+00, 2.16294914e+00, -1.48101497e-01}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(dot_dyn_4_d_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::float_type, {{1, 1}, {1, 1}, {4, 6, {4}}, {5, 5}}}; auto al = mm->add_parameter("a", a_shape); migraphx::shape b_shape{migraphx::shape::float_type, {1, 1, 5, 3}}; auto bl = mm->add_parameter("b", b_shape); mm->add_instruction(migraphx::make_op("dot"), al, bl); p.compile(migraphx::make_target("ref")); std::vector a = {-0.00925222, 0.56250403, 0.70107397, 0.75402161, -0.505885, 1.33628943, -0.11413, -0.31270559, 1.59336732, -0.19361027, -0.91620867, 0.40108416, -0.06969921, 0.68483471, -0.39906632, -1.66423624, 0.69040076, -1.31490171, -0.11282616, -0.79391814}; std::vector b = {6.09568541e-01, -6.10527007e-01, 3.66646462e-01, 1.18951101e-01, 5.58777432e-01, -3.21296298e-01, -5.95997198e-01, -5.01425721e-01, -2.84606807e-01, -5.73673557e-01, -8.99430260e-01, -4.25103093e-01, 1.53027987e+00, -3.81407415e-04, -3.29650255e-01}; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {1, 1, 4, 5}}; migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {1, 1, 5, 3}}; migraphx::parameter_map params; params["a"] = migraphx::argument(input_fixed_shape0, a.data()); params["b"] = migraphx::argument(input_fixed_shape1, b.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-1.56327541e+00, -7.09570140e-01, -5.37424982e-01, -2.22994831e-01, -2.15586437e+00, 2.09177941e-03, -1.47279677e+00, 2.02627040e-01, -6.04527691e-01, -1.29885596e+00, 2.16294914e+00, -1.48101497e-01}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(quant_dot_2args_multi4_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {4, 4}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {4, 8}}; std::vector data1(4 * 4); std::vector data2(4 * 8); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); mm->add_instruction(migraphx::make_op("quant_dot"), l1, l2); std::vector gold = {112, 118, 124, 130, 136, 142, 148, 154, 304, 326, 348, 370, 392, 414, 436, 458, 496, 534, 572, 610, 648, 686, 724, 762, 688, 742, 796, 850, 904, 958, 1012, 1066}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_2args_multi4_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {4, 4}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {4, 8}}; std::vector data1(4 * 4); std::vector data2(4 * 8); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); mm->add_instruction(migraphx::make_op("quant_dot"), tl1, l2); std::vector gold = {448, 472, 496, 520, 544, 568, 592, 616, 496, 524, 552, 580, 608, 636, 664, 692, 544, 576, 608, 640, 672, 704, 736, 768, 592, 628, 664, 700, 736, 772, 808, 844}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_2args_multi4_3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {4, 4}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {8, 4}}; std::vector data1(4 * 4); std::vector data2(4 * 8); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); mm->add_instruction(migraphx::make_op("quant_dot"), l1, tl2); std::vector gold = {14, 38, 62, 86, 110, 134, 158, 182, 38, 126, 214, 302, 390, 478, 566, 654, 62, 214, 366, 518, 670, 822, 974, 1126, 86, 302, 518, 734, 950, 1166, 1382, 1598}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_2args_multi4_4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {4, 4}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {8, 4}}; std::vector data1(4 * 4); std::vector data2(4 * 8); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); mm->add_instruction(migraphx::make_op("quant_dot"), tl1, tl2); std::vector gold = {56, 152, 248, 344, 440, 536, 632, 728, 62, 174, 286, 398, 510, 622, 734, 846, 68, 196, 324, 452, 580, 708, 836, 964, 74, 218, 362, 506, 650, 794, 938, 1082}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_2args_general_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {3, 4}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {4, 5}}; std::vector data1(3 * 4); std::vector data2(4 * 5); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); mm->add_instruction(migraphx::make_op("quant_dot"), l1, l2); std::vector gold = {70, 76, 82, 88, 94, 190, 212, 234, 256, 278, 310, 348, 386, 424, 462}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_2args_general_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {4, 3}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {4, 5}}; std::vector data1(4 * 3); std::vector data2(4 * 5); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); mm->add_instruction(migraphx::make_op("quant_dot"), tl1, l2); std::vector gold = { 210, 228, 246, 264, 282, 240, 262, 284, 306, 328, 270, 296, 322, 348, 374}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_2args_general_3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {3, 4}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {5, 4}}; std::vector data1(3 * 4); std::vector data2(4 * 5); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); migraphx::add_apply_alpha_beta(*mm, {l1, tl2}, migraphx::make_op("quant_dot"), 2); std::vector gold = { 28, 76, 124, 172, 220, 76, 252, 428, 604, 780, 124, 428, 732, 1036, 1340}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_2args_general_4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {4, 3}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {5, 4}}; std::vector data1(4 * 3); std::vector data2(4 * 5); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); migraphx::add_apply_alpha_beta(*mm, {tl1, tl2}, migraphx::make_op("quant_dot"), 3); std::vector gold = { 126, 342, 558, 774, 990, 144, 408, 672, 936, 1200, 162, 474, 786, 1098, 1410}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_3args_general_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {2, 8}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {8, 7}}; migraphx::shape m3_shape{migraphx::shape::int32_type, {2, 7}}; std::vector data1(2 * 8); std::vector data2(8 * 7); std::vector data3(2 * 7); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); std::iota(data3.begin(), data3.end(), 2); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto l3 = mm->add_literal(migraphx::literal{m3_shape, data3}); migraphx::add_apply_alpha_beta(*mm, {l1, l2, l3}, migraphx::make_op("quant_dot"), 1, 1); std::vector gold = { 982, 1011, 1040, 1069, 1098, 1127, 1156, 2557, 2650, 2743, 2836, 2929, 3022, 3115}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_3args_general_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {3, 4}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {4, 5}}; migraphx::shape m3_shape{migraphx::shape::int32_type, {3, 5}}; std::vector data1(3 * 4); std::vector data2(4 * 5); std::vector data3(3 * 5); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); std::iota(data3.begin(), data3.end(), 0); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); mm->add_instruction(migraphx::make_op("quant_dot"), l1, l2); std::vector gold = {70, 76, 82, 88, 94, 190, 212, 234, 256, 278, 310, 348, 386, 424, 462}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_3args_general_3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {8, 2}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {8, 7}}; migraphx::shape m3_shape{migraphx::shape::int32_type, {2, 7}}; std::vector data1(2 * 8); std::vector data2(8 * 7); std::vector data3(2 * 7); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); std::iota(data3.begin(), data3.end(), 2); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto l3 = mm->add_literal(migraphx::literal{m3_shape, data3}); migraphx::add_apply_alpha_beta(*mm, {tl1, l2, l3}, migraphx::make_op("quant_dot"), 1, 3); std::vector gold = { 1966, 2025, 2084, 2143, 2202, 2261, 2320, 2183, 2250, 2317, 2384, 2451, 2518, 2585}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_3args_general_4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {2, 8}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {7, 8}}; migraphx::shape m3_shape{migraphx::shape::int32_type, {2, 7}}; std::vector data1(2 * 8); std::vector data2(8 * 7); std::vector data3(2 * 7); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); std::iota(data3.begin(), data3.end(), 2); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); auto l3 = mm->add_literal(migraphx::literal{m3_shape, data3}); migraphx::add_apply_alpha_beta(*mm, {l1, tl2, l3}, migraphx::make_op("quant_dot"), 2, 3); std::vector gold = { 286, 737, 1188, 1639, 2090, 2541, 2992, 755, 2230, 3705, 5180, 6655, 8130, 9605}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_3args_general_5) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {8, 2}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {7, 8}}; migraphx::shape m3_shape{migraphx::shape::int32_type, {2, 7}}; std::vector data1(2 * 8); std::vector data2(8 * 7); std::vector data3(2 * 7); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); std::iota(data3.begin(), data3.end(), 2); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); auto l3 = mm->add_literal(migraphx::literal{m3_shape, data3}); migraphx::add_apply_alpha_beta(*mm, {tl1, tl2, l3}, migraphx::make_op("quant_dot"), 3, 2); std::vector gold = { 844, 2190, 3536, 4882, 6228, 7574, 8920, 942, 2480, 4018, 5556, 7094, 8632, 10170}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_3args_batch_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {2, 2, 2, 4}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {2, 2, 4, 7}}; migraphx::shape m3_shape{migraphx::shape::int32_type, {2, 2, 2, 7}}; std::vector data1(4 * 2 * 4); std::vector data2(4 * 4 * 7); std::vector data3(4 * 2 * 7); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); std::iota(data3.begin(), data3.end(), 2); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto l3 = mm->add_literal(migraphx::literal{m3_shape, data3}); migraphx::add_apply_alpha_beta(*mm, {l1, l2, l3}, migraphx::make_op("quant_dot"), 1, 2); std::vector gold = {102, 110, 118, 126, 134, 142, 150, 284, 308, 332, 356, 380, 404, 428, 1530, 1570, 1610, 1650, 1690, 1730, 1770, 2160, 2216, 2272, 2328, 2384, 2440, 2496, 4750, 4822, 4894, 4966, 5038, 5110, 5182, 5828, 5916, 6004, 6092, 6180, 6268, 6356, 9762, 9866, 9970, 10074, 10178, 10282, 10386, 11288, 11408, 11528, 11648, 11768, 11888, 12008}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } TEST_CASE(quant_dot_3args_batch_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::int8_type, {2, 2, 4, 3}}; migraphx::shape m2_shape{migraphx::shape::int8_type, {2, 2, 6, 4}}; migraphx::shape m3_shape{migraphx::shape::int32_type, {2, 2, 3, 6}}; std::vector data1(48); std::vector data2(96); std::vector data3(72); std::iota(data1.begin(), data1.end(), 0); std::iota(data2.begin(), data2.end(), 0); std::iota(data3.begin(), data3.end(), 2); auto l1 = mm->add_literal(migraphx::literal{m1_shape, data1}); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l1); auto l2 = mm->add_literal(migraphx::literal{m2_shape, data2}); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l2); auto l3 = mm->add_literal(migraphx::literal{m3_shape, data3}); migraphx::add_apply_alpha_beta(*mm, {tl1, tl2, l3}, migraphx::make_op("quant_dot"), 2, 3); std::vector gold = { 90, 237, 384, 531, 678, 825, 120, 299, 478, 657, 836, 1015, 150, 361, 572, 783, 994, 1205, 3456, 3987, 4518, 5049, 5580, 6111, 3678, 4241, 4804, 5367, 5930, 6493, 3900, 4495, 5090, 5685, 6280, 6875, 11430, 12345, 13260, 14175, 15090, 16005, 11844, 12791, 13738, 14685, 15632, 16579, 12258, 13237, 14216, 15195, 16174, 17153, 24012, 25311, 26610, 27909, 29208, 30507, 24618, 25949, 27280, 28611, 29942, 31273, 25224, 26587, 27950, 29313, 30676, 32039}; p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector m; result.visit([&](auto output) { m.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(m, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/elu.cpp000066400000000000000000000061361510465702400175720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static float elu(float a, float x) { return x > 0 ? x : a * std::expm1(x); } TEST_CASE(elu_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l = mm->add_literal(migraphx::literal{s, {-1.0, 2.0, -3.0, 4.0}}); float alpha = 0.5; mm->add_instruction(migraphx::make_op("elu", {{"alpha", alpha}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{elu(alpha, -1), elu(alpha, 2), elu(alpha, -3), elu(alpha, 4)}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(elu_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); float alpha = 0.5; mm->add_instruction(migraphx::make_op("elu", {{"alpha", alpha}}), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-1.0, 2.0, -3.0, 4.0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{elu(alpha, -1), elu(alpha, 2), elu(alpha, -3), elu(alpha, 4)}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/equal.cpp000066400000000000000000000114021510465702400201040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(equal_brcst_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {3, 3}}; auto l0 = mm->add_literal(migraphx::literal{s0, {1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}}); migraphx::shape s1{migraphx::shape::float_type, {3, 1}}; auto l1 = mm->add_literal(migraphx::literal{s1, {1.1, -1.5, 0.0}}); auto bl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 3}}}), l1); auto eq = mm->add_instruction(migraphx::make_op("equal"), l0, bl1); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), eq); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {true, false, false, false, true, false, true, false, false}; EXPECT(results_vector == gold); } TEST_CASE(equal_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {9}}; auto l0 = mm->add_literal(migraphx::literal{s, {1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}}); auto l1 = mm->add_literal(migraphx::literal{s, {1.1, 1.6, -0.1, -1.2, -1.5, -0.7, 0.0, 2.3, -2.1}}); auto eq = mm->add_instruction(migraphx::make_op("equal"), l0, l1); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), eq); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {true, false, false, false, true, false, true, false, false}; EXPECT(results_vector == gold); } TEST_CASE(equal_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{6, 12, {9}}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto p0 = mm->add_parameter("l", s); auto p1 = mm->add_parameter("r", s); auto eq = mm->add_instruction(migraphx::make_op("equal"), p0, p1); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), eq); mm->add_return({r}); p.compile(migraphx::make_target("ref")); std::vector l_data{1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}; std::vector r_data{1.1, 1.6, -0.1, -1.2, -1.5, -0.7, 0.0, 2.3, -2.1}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {9}}; params0["l"] = migraphx::argument(input_fixed_shape0, l_data.data()); params0["r"] = migraphx::argument(input_fixed_shape0, r_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {true, false, false, false, true, false, true, false, false}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/erf.cpp000066400000000000000000000062211510465702400175540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(erf_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4}}; std::vector data = {0.73785057, 1.58165966, -0.43597795, -0.01677432}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("erf"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return erff(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(erf_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("erf"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {0.73785057, 1.58165966, -0.43597795, -0.01677432}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return erff(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/exp.cpp000066400000000000000000000060601510465702400175750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(exp_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data{-1, 0, 1}; migraphx::shape s{migraphx::shape::float_type, {3}}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("exp"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return expf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(exp_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("exp"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-1, 0, 1}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return expf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/fill.cpp000066400000000000000000000107431510465702400177320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(fill_static_int) { // Note this case can be simplified to a literal migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape lit_shape{migraphx::shape::int64_type, {1}, {0}}; std::vector lit_data = {3}; auto l = mm->add_literal(migraphx::literal{lit_shape, lit_data}); migraphx::shape data_shape{migraphx::shape::int64_type, {3, 4, 4}}; auto input = mm->add_parameter("x", data_shape); mm->add_instruction(migraphx::make_op("fill"), l, input); p.compile(migraphx::make_target("ref")); std::vector input_data(48); migraphx::parameter_map params; params["x"] = migraphx::argument(data_shape, input_data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(48, 3); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(fill_dyn_float) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape lit_shape{migraphx::shape::float_type, {1}, {0}}; std::vector lit_data = {7.36}; auto l = mm->add_literal(migraphx::literal{lit_shape, lit_data}); migraphx::shape data_shape{migraphx::shape::float_type, {{1, 4}, {4, 8, {4, 6, 8}}, {4, 8, {4, 6, 8}}}}; auto input = mm->add_parameter("x", data_shape); mm->add_instruction(migraphx::make_op("fill"), l, input); p.compile(migraphx::make_target("ref")); std::vector input_data(72); migraphx::parameter_map params; migraphx::shape static_shape = {migraphx::shape::float_type, {2, 6, 6}}; params["x"] = migraphx::argument(static_shape, input_data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(72, 7.36); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(fill_var_default_value) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape dv_shape{migraphx::shape::int64_type, {1}, {0}}; auto dv = mm->add_parameter("dv", dv_shape); migraphx::shape data_shape{migraphx::shape::int64_type, {3, 4, 4}}; auto input = mm->add_parameter("x", data_shape); mm->add_instruction(migraphx::make_op("fill"), dv, input); p.compile(migraphx::make_target("ref")); std::vector dv_data = {2}; std::vector input_data(48); migraphx::parameter_map params; params["x"] = migraphx::argument(data_shape, input_data.data()); params["dv"] = migraphx::argument(dv_shape, dv_data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(48, 2); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/fixed_pad.cpp000066400000000000000000000056361510465702400207340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(fixed_pad_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 3}, {3, 3}}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("fixed_pad"), x); p.compile(migraphx::make_target("ref")); std::vector data = {-3, -2, -1, 0, 1, 2}; migraphx::shape s2{migraphx::shape::float_type, {2, 3}}; migraphx::argument arg(s2, data.data()); auto result = p.eval({{"x", arg}}).back(); std::vector results_vector(9); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-3, -2, -1, 0, 1, 2, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(fixed_pad_same_shape_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 2}, {3, 3}}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("fixed_pad"), x); p.compile(migraphx::make_target("ref")); std::vector data = {-3, -2, -1, 0, 1, 2}; migraphx::shape s2{migraphx::shape::float_type, {2, 3}}; migraphx::argument arg(s2, data.data()); auto result = p.eval({{"x", arg}}).back(); std::vector results_vector(6); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-3, -2, -1, 0, 1, 2}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/floor.cpp000066400000000000000000000062321510465702400201230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(floor_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {9}}; std::vector data = {1.1, 1.5, 0.6, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("floor"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return floor(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(floor_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{5, 12}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("floor"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {1.1, 1.5, 0.6, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {9}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return floor(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/fmod.cpp000066400000000000000000000104411510465702400177240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(fmod_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {3}}; auto l0 = mm->add_literal(migraphx::literal{s, {-7, 8, -3}}); auto l1 = mm->add_literal(migraphx::literal{s, {2, 4, 6}}); auto l2 = mm->add_literal(migraphx::literal{s, {7, 5, 9}}); auto curr_mod = mm->add_instruction(migraphx::make_op("fmod"), l0, l1); mm->add_instruction(migraphx::make_op("fmod"), curr_mod, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-1, 0, -3}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(fmod_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto curr_mod = mm->add_instruction(migraphx::make_op("fmod"), x, y); mm->add_instruction(migraphx::make_op("fmod"), curr_mod, z); p.compile(migraphx::make_target("ref")); std::vector x_data{-7, 8, -3}; std::vector y_data{2, 4, 6}; std::vector z_data{7, 5, 9}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); params0["z"] = migraphx::argument(input_fixed_shape0, z_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-1, 0, -3}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(fmod_float_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l0 = mm->add_literal(migraphx::literal{s, {-7.2f, 8.5f, -3.3f}}); auto l1 = mm->add_literal(migraphx::literal{s, {2.0f, 4.0f, 6.0f}}); auto l2 = mm->add_literal(migraphx::literal{s, {7.0f, 5.0f, 9.0f}}); auto curr_mod = mm->add_instruction(migraphx::make_op("fmod"), l0, l1); mm->add_instruction(migraphx::make_op("fmod"), curr_mod, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-1.2f, 0.5f, -3.3f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/fp8_ocp_to_fnuz.cpp000066400000000000000000000251451510465702400221100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include /** * test that before and after the fp8_ocp_to_fnuz pass * have equivalent results */ static void run_fp8_ocp_to_fnuz(migraphx::module& m) { migraphx::run_passes(m, {migraphx::fp8_ocp_to_fnuz{}, migraphx::dead_code_elimination{}}); } TEST_CASE(fp8_ocp_to_fnuz_gemm) { using migraphx::fp8::fp8e4m3fn; using migraphx::fp8::fp8e4m3fnuz; std::vector data_lens = {2, 2}; migraphx::shape data_shape{migraphx::shape::float_type, data_lens}; migraphx::program p1; auto* m1 = p1.get_main_module(); { auto a = m1->add_parameter("a", data_shape); auto b = m1->add_parameter("b", data_shape); auto scale = m1->add_literal(0.5f); std::vector data; data.push_back(fp8e4m3fn{0.f}); auto zero = m1->add_literal(migraphx::shape{migraphx::shape::fp8e4m3fn_type, {1}, {0}}, data); auto qa = add_quantize_op(*m1, "quantizelinear", a, scale, zero); auto qb = add_quantize_op(*m1, "quantizelinear", b, scale, zero); auto da = add_quantize_op(*m1, "dequantizelinear", qa, qa->inputs().at(1), qa->inputs().at(2)); auto db = add_quantize_op(*m1, "dequantizelinear", qb, qb->inputs().at(1), qb->inputs().at(2)); auto dot = m1->add_instruction(migraphx::make_op("dot"), da, db); m1->add_return({dot}); } migraphx::program p2 = p1; migraphx::module* m2 = p2.get_main_module(); run_fp8_ocp_to_fnuz(*m2); p1.compile(migraphx::make_target("ref")); p2.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector a_data = {20, -100, 100, 0.25}; std::vector b_data = {28, 0.125, 2.5, 0.25}; params["a"] = migraphx::argument(data_shape, a_data.data()); params["b"] = migraphx::argument(data_shape, b_data.data()); auto result_1 = p1.eval({params}).back(); auto result_2 = p2.eval({params}).back(); std::vector results_vector_1(4); std::vector results_vector_2(4); result_1.visit([&](auto output) { results_vector_1.assign(output.begin(), output.end()); }); result_2.visit([&](auto output) { results_vector_2.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector_1, results_vector_2)); } TEST_CASE(fp8_ocp_to_fnuz_gemm_multi_scale) { using migraphx::fp8::fp8e4m3fn; using migraphx::fp8::fp8e4m3fnuz; std::vector data_lens = {3, 3}; migraphx::shape data_shape{migraphx::shape::float_type, data_lens}; migraphx::shape scales_shape{migraphx::shape::float_type, {3}}; migraphx::program p1; auto* m1 = p1.get_main_module(); { auto a = m1->add_parameter("a", data_shape); auto b = m1->add_parameter("b", data_shape); auto scale1 = m1->add_literal(migraphx::generate_literal(scales_shape, 0)); auto scale2 = m1->add_literal(0.4f); std::vector data; data.push_back(fp8e4m3fn{0.f}); auto zero = m1->add_literal(migraphx::shape{migraphx::shape::fp8e4m3fn_type, {1}, {0}}, data); auto qa = add_quantize_op(*m1, "quantizelinear", a, scale1, zero); auto qb = add_quantize_op(*m1, "quantizelinear", b, scale2, zero); auto da = add_quantize_op(*m1, "dequantizelinear", qa, qa->inputs().at(1), qa->inputs().at(2)); auto db = add_quantize_op(*m1, "dequantizelinear", qb, qb->inputs().at(1), qb->inputs().at(2)); auto dot = m1->add_instruction(migraphx::make_op("dot"), da, db); m1->add_return({dot}); } migraphx::program p2 = p1; migraphx::module* m2 = p2.get_main_module(); run_fp8_ocp_to_fnuz(*m2); p1.compile(migraphx::make_target("ref")); p2.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector a_data = {20, -100, 100, 0.25, 0.3, 3.3, 5.0, -8.0, 63.0}; std::vector b_data = {28, 0.125, 2.5, 0.25, 0.0582, -187, 0.716, 8.12, 1.87}; params["a"] = migraphx::argument(data_shape, a_data.data()); params["b"] = migraphx::argument(data_shape, b_data.data()); auto result_1 = p1.eval({params}).back(); auto result_2 = p2.eval({params}).back(); std::vector results_vector_1(9); std::vector results_vector_2(9); result_1.visit([&](auto output) { results_vector_1.assign(output.begin(), output.end()); }); result_2.visit([&](auto output) { results_vector_2.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector_1, results_vector_2)); } TEST_CASE(fp8_ocp_to_fnuz_conv) { using migraphx::fp8::fp8e4m3fn; using migraphx::fp8::fp8e4m3fnuz; std::vector data_lens = {2, 2}; migraphx::shape data_shape{migraphx::shape::float_type, data_lens}; migraphx::program p1; auto* m1 = p1.get_main_module(); { std::vector a_data = { 2.71567607, -0.9960829, 0.91671127, 0.28140706, 0.63235772, 0.08077253, 0.80927712, -0.59108931, -1.05421555, -2.76622486, -0.85044265, -0.52049929, 0.67726439, -0.65290606, 0.02345525, -0.33579525, 0.38901961, 1.05473483, -1.31188095, 1.8963089, -0.07265259, 0.947339, 0.41949373, -0.70814759, 0.25892952, 1.07311416, 1.2571274, -0.62318051, -0.19951548, -0.94232577, -0.29393643, 0.42292568, -0.80230367, 1.40909171, 0.63617158, 0.13900366, 1.09253144, -0.15265895, 1.54781747, 0.72780299, 1.09189606, -0.38068101, 0.97057933, -0.58958799, 1.56188643, 0.21474874, 0.58725154, -1.27097559, -0.03024297, 1.09437096, -0.4897908, 0.34838957, -1.31042492, -1.69069934, 0.86956722, -0.40457946, 0.46691212, 1.29273605, 0.26464137, 0.22073045, -1.02178168, 0.22163901, -1.84387338, 0.75522131, -0.45775682, -0.42241111, -1.50944722, 1.07256448, -1.95876884, -0.28106022, 0.3341668, 2.13129425, -1.14728117, -1.06555498, -0.298444, -0.88322699, -0.65866792, -2.06007552, 0.01374334, 0.45612028, 0.52715492, 1.01914406, -1.72659791, 0.80650896, 0.16860051, 2.24112225, -0.78620857, 0.36566174, -0.07020134, -0.47976932, -0.68230027, -0.94711417, -0.54506505, 1.66504931, -0.71860826, 0.61132306}; std::vector b_data = { 2.82721668e-02, 6.44195229e-02, 1.53499246e-02, 1.72468081e-01, -6.33238107e-02, 9.49496776e-02, 1.40258059e-01, -7.92879611e-02, -1.29301161e-01, 3.11307609e-03, -1.90624535e-01, 1.13238767e-01, -2.80647576e-02, 3.12882811e-02, -3.52091640e-02, 3.33581865e-02, 6.43158704e-02, 7.40238279e-02, -1.00106120e-01, -9.56912562e-02, 1.44342467e-01, 9.40258950e-02, 6.36333972e-02, 1.66158378e-03, -8.91554281e-02, 2.58734226e-02, 1.70919895e-02, 1.78214177e-01, 8.84564668e-02, 8.98126513e-02, -1.63809001e-01, 1.37802169e-01, 1.66439757e-01, -1.45631135e-02, 1.88469887e-04, 4.76950556e-02, -1.91969007e-01, -1.76233292e-01, -7.70473927e-02, 1.14828631e-01, 1.76608220e-01, -1.50728196e-01, 1.99946314e-02, -5.88052124e-02, 1.31612435e-01, 1.61106288e-02, -1.35080189e-01, 1.49512306e-01, 3.86456847e-02, 1.29330024e-01, -3.22975963e-02, -5.60784787e-02, -5.41997552e-02, 4.78562862e-02}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; auto a = m1->add_literal(migraphx::literal{a_shape, a_data}); migraphx::shape b_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto b = m1->add_literal(migraphx::literal{b_shape, b_data}); auto scale = m1->add_literal(0.5f); std::vector data; data.push_back(fp8e4m3fn{0.f}); auto zero = m1->add_literal(migraphx::shape{migraphx::shape::fp8e4m3fn_type, {1}, {0}}, data); auto qa = add_quantize_op(*m1, "quantizelinear", a, scale, zero); auto qb = add_quantize_op(*m1, "quantizelinear", b, scale, zero); auto da = add_quantize_op(*m1, "dequantizelinear", qa, qa->inputs().at(1), qa->inputs().at(2)); auto db = add_quantize_op(*m1, "dequantizelinear", qb, qb->inputs().at(1), qb->inputs().at(2)); auto conv_ins = m1->add_instruction(migraphx::make_op("convolution"), da, db); m1->add_return({conv_ins}); } migraphx::program p2 = p1; migraphx::module* m2 = p2.get_main_module(); run_fp8_ocp_to_fnuz(*m2); p1.compile(migraphx::make_target("ref")); p2.compile(migraphx::make_target("ref")); auto result_1 = p1.eval({}).back(); auto result_2 = p2.eval({}).back(); std::vector results_vector_1(16); std::vector results_vector_2(16); result_1.visit([&](auto output) { results_vector_1.assign(output.begin(), output.end()); }); result_2.visit([&](auto output) { results_vector_2.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector_1, results_vector_2)); } ROCm-AMDMIGraphX-46524e8/test/ref/gather.cpp000066400000000000000000000272711510465702400202620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(gather_non_std_test) { { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {0.5f, 3.5f, 6.5f, 1.5f, 4.5f, 7.5f, 2.5f, 2.5f, 8.5f}; migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto d = mm->add_literal(migraphx::literal{s, data}); migraphx::shape s_indices{migraphx::shape::int32_type, {2, 2}}; std::vector indices{-3, -3, -1, -1}; auto ind = mm->add_literal(migraphx::literal{s_indices, indices}); auto td = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), d); auto tind = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), ind); mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), td, tind); auto result = p.eval({}).back(); std::vector golden = { 0.5f, 1.5f, 2.5f, 6.5f, 7.5f, 8.5f, 0.5f, 1.5f, 2.5f, 6.5f, 7.5f, 8.5f}; std::vector res_data; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } } TEST_CASE(gather_test_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape s_indices{migraphx::shape::int32_type, {1, 2}}; std::vector indices{0, 2}; auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = 0; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(4 * 5); std::vector golden = {0.5f, 1.5f, 2.5f, 6.5f, 7.5f, 8.5f}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(gather_test_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape s_indices{migraphx::shape::int32_type, {1, 2}}; std::vector indices{-3, -1}; auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = 0; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(4 * 5); std::vector golden = {0.5f, 1.5f, 2.5f, 6.5f, 7.5f, 8.5f}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(gather_test_3) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape s_indices{migraphx::shape::int32_type, {1, 2}}; std::vector indices{0, 2}; auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = 1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(4 * 5); std::vector golden = {0.5f, 2.5f, 3.5f, 5.5f, 6.5f, 8.5f}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(gather_test_4) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape s_indices{migraphx::shape::int32_type, {1, 2}}; std::vector indices{0, 2}; auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(4 * 5); std::vector golden = {0.5f, 2.5f, 3.5f, 5.5f, 6.5f, 8.5f}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(gather_test_5) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); // scalar index migraphx::shape s_indices{migraphx::shape::int32_type}; std::vector indices{0}; auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector golden = {0.5f, 3.5f, 6.5f}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(gather_test_6) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); // scalar index migraphx::shape s_indices{migraphx::shape::int32_type}; std::vector indices{-3}; auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector golden = {0.5f, 3.5f, 6.5f}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(gather_test_7) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); // scalar index migraphx::shape s_indices{migraphx::shape::int32_type}; std::vector indices{0}; auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector golden = {0.5f}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(gather_dyn_test0) { // Dynamic data, static indices migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {{2, 5}, {3, 3}}}; auto x = mm->add_parameter("x", s); std::vector indices{1, 2}; migraphx::shape s_ind{migraphx::shape::int32_type, {1, 2}}; auto ind = mm->add_parameter("indices", s_ind); mm->add_instruction(migraphx::make_op("gather", {{"axis", 1}}), x, ind); migraphx::shape sresult{migraphx::shape::int32_type, {{2, 5}, {1, 1}, {2, 2}}}; EXPECT(p.get_output_shapes().back() == sresult); p.compile(migraphx::make_target("ref")); migraphx::shape input_fixed_shape{migraphx::shape::int32_type, {2, 3}}; migraphx::shape input_indices{migraphx::shape::int32_type, {1, 2}}; migraphx::parameter_map params; std::vector data(2 * 3); std::iota(data.begin(), data.end(), 0); params["x"] = migraphx::argument(input_fixed_shape, data.data()); params["indices"] = migraphx::argument(input_indices, indices.data()); auto result = p.eval(params).back(); std::vector gold = {1, 2, 4, 5}; std::vector results_vector(2 * 1 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); migraphx::shape sfinal{migraphx::shape::int32_type, {2, 1, 2}}; EXPECT(result.get_shape() == sfinal); } TEST_CASE(gather_dyn_test1) { // Dynamic data, dynamic indices migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {{2, 5}, {4, 4}}}; auto x = mm->add_parameter("x", s); migraphx::shape s_ind{migraphx::shape::int32_type, {{1, 8, {7}}, {2, 3, {3}}}}; auto ind = mm->add_parameter("indices", s_ind); mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), x, ind); migraphx::shape sresult{migraphx::shape::int32_type, {{1, 8, {7}}, {2, 3, {3}}, {4, 4}}}; EXPECT(p.get_output_shapes().back() == sresult); p.compile(migraphx::make_target("ref")); migraphx::shape input_fixed_shape{migraphx::shape::int32_type, {3, 4}}; migraphx::shape input_indices_shape{migraphx::shape::int32_type, {1, 2}}; std::vector indices{2, 0}; migraphx::parameter_map params; std::vector data(3 * 4); std::iota(data.begin(), data.end(), 0); params["x"] = migraphx::argument(input_fixed_shape, data.data()); params["indices"] = migraphx::argument(input_indices_shape, indices.data()); auto result = p.eval(params).back(); std::vector gold = {8, 9, 10, 11, 0, 1, 2, 3}; std::vector results_vector(1 * 2 * 4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); migraphx::shape sfinal{migraphx::shape::int32_type, {1, 2, 4}}; EXPECT(result.get_shape() == sfinal); } ROCm-AMDMIGraphX-46524e8/test/ref/gathernd.cpp000066400000000000000000000360201510465702400205740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(gathernd_test_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 2}}; migraphx::shape is{migraphx::shape::int64_type, {2, 2}}; std::vector data_vec(2 * 2); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{0, 0, 1, 1}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, indices_vec}); mm->add_instruction(migraphx::make_op("gathernd"), data, indices); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector gold{0, 3}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_test_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 2}}; migraphx::shape is{migraphx::shape::int64_type, {2, 1}}; std::vector data_vec(2 * 2); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{1, 0}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, indices_vec}); mm->add_instruction(migraphx::make_op("gathernd"), data, indices); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector gold{2, 3, 0, 1}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_test_3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 3, 1}}; migraphx::shape is{migraphx::shape::int64_type, {2, 2, 1}}; std::vector data_vec(2 * 3 * 1); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{1, 0, 0, 1}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, indices_vec}); mm->add_instruction(migraphx::make_op("gathernd"), data, indices); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector gold{3, 4, 5, 0, 1, 2, 0, 1, 2, 3, 4, 5}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_test_4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 3, 2, 3}}; migraphx::shape is{migraphx::shape::int64_type, {2, 2, 2}}; std::vector data_vec(2 * 3 * 2 * 3); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{0, 0, 0, 1, 0, 0, 0, 1}; const int batch_dims = 1; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, indices_vec}); mm->add_instruction(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), data, indices); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector gold{0, 1, 2, 3, 4, 5, 18, 19, 20, 21, 22, 23}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_test_5) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 3, 1, 3}}; migraphx::shape is{migraphx::shape::int64_type, {2, 3, 2}}; std::vector data_vec(2 * 3 * 1 * 3); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{0, 0, 0, 1, 0, 2, 0, 2, 0, 1, 0, 0}; const int batch_dims = 2; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, indices_vec}); mm->add_instruction(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), data, indices); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector gold{0, 4, 8, 11, 13, 15}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_test_6) { // k > r - batch_dims migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 3, 1, 3}}; migraphx::shape is{migraphx::shape::int64_type, {2, 3, 3}}; std::vector data_vec(2 * 3 * 1 * 3); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec(2 * 3 * 3, 0); const int batch_dims = 2; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, indices_vec}); EXPECT(test::throws([&] { mm->add_instruction( migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), data, indices); })); } TEST_CASE(gathernd_dynamic0) { // dynamic data, all dimensions fixed migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {{2, 2, {2}}, {3, 3}, {1, 1}}}; migraphx::shape is{migraphx::shape::int64_type, {2, 2, 1}}; auto xdata = mm->add_parameter("X", ds); auto xindex = mm->add_parameter("I", is); auto gathernd_op = migraphx::make_op("gathernd"); auto gathernd = mm->add_instruction(gathernd_op, xdata, xindex); mm->add_return({gathernd}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 3, 1}}; // data migraphx::shape input_fixed_shape1{migraphx::shape::int64_type, {2, 2, 1}}; // index std::vector data_vec(2 * 3 * 1); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{1, 0, 0, 1}; params["X"] = migraphx::argument(input_fixed_shape0, data_vec.data()); params["I"] = migraphx::argument(input_fixed_shape1, indices_vec.data()); auto result = p.eval(params).back(); std::vector res_data{}; std::vector gold{3, 4, 5, 0, 1, 2, 0, 1, 2, 3, 4, 5}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_dynamic1) { // dynamic data, dims not fixed migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {{2, 5, {2}}, {1, 5}, {1, 5}}}; migraphx::shape is{migraphx::shape::int64_type, {2, 2, 1}}; auto xdata = mm->add_parameter("X", ds); auto xindex = mm->add_parameter("I", is); auto gathernd_op = migraphx::make_op("gathernd"); auto gathernd = mm->add_instruction(gathernd_op, xdata, xindex); mm->add_return({gathernd}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 3, 1}}; // data migraphx::shape input_fixed_shape1{migraphx::shape::int64_type, {2, 2, 1}}; // index std::vector data_vec(2 * 3 * 1); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{1, 0, 0, 1}; params["X"] = migraphx::argument(input_fixed_shape0, data_vec.data()); params["I"] = migraphx::argument(input_fixed_shape1, indices_vec.data()); auto result = p.eval(params).back(); std::vector res_data{}; std::vector gold{3, 4, 5, 0, 1, 2, 0, 1, 2, 3, 4, 5}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_dynamic2) { // dynamic both index and data migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {{2, 5, {2}}, {1, 5}, {1, 5}}}; migraphx::shape is{migraphx::shape::int64_type, {{2, 5, {3}}, {2, 3, {3}}, {1, 1}}}; auto xdata = mm->add_parameter("X", ds); auto xindex = mm->add_parameter("I", is); auto gathernd_op = migraphx::make_op("gathernd"); auto gathernd = mm->add_instruction(gathernd_op, xdata, xindex); mm->add_return({gathernd}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 3, 1}}; // data migraphx::shape input_fixed_shape1{migraphx::shape::int64_type, {2, 2, 1}}; // index std::vector data_vec(2 * 3 * 1); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{1, 0, 0, 1}; params["X"] = migraphx::argument(input_fixed_shape0, data_vec.data()); params["I"] = migraphx::argument(input_fixed_shape1, indices_vec.data()); auto result = p.eval(params).back(); std::vector res_data{}; std::vector gold{3, 4, 5, 0, 1, 2, 0, 1, 2, 3, 4, 5}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_dynamic3) { // dynamic index, static data and a batch_dims input migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 3, 1}}; migraphx::shape is{migraphx::shape::int64_type, {{2, 5, {3}}, {2, 3, {3}}, {1, 1}}}; auto xdata = mm->add_parameter("X", ds); auto xindex = mm->add_parameter("I", is); int batch_dims{1}; auto gathernd_op = migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}); auto gathernd = mm->add_instruction(gathernd_op, xdata, xindex); mm->add_return({gathernd}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 3, 1}}; // data migraphx::shape input_fixed_shape1{migraphx::shape::int64_type, {2, 2, 1}}; // index std::vector data_vec(2 * 3 * 1); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{1, 0, 0, 1}; params["X"] = migraphx::argument(input_fixed_shape0, data_vec.data()); params["I"] = migraphx::argument(input_fixed_shape1, indices_vec.data()); auto result = p.eval(params).back(); std::vector res_data{}; std::vector gold{1, 0, 3, 4}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_dynamic4) { // int(q) + r - k - batch_dims - 1 = 0 => returns a scalar migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {migraphx::shape::dynamic_dimension({2, 2})}}; migraphx::shape is{migraphx::shape::int64_type, {1}}; auto xdata = mm->add_parameter("X", ds); auto xindex = mm->add_parameter("I", is); auto gathernd_op = migraphx::make_op("gathernd"); auto gathernd = mm->add_instruction(gathernd_op, xdata, xindex); mm->add_return({gathernd}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2}}; // data migraphx::shape input_fixed_shape1{migraphx::shape::int64_type, {1}}; // index std::vector data_vec(2); std::iota(data_vec.begin(), data_vec.end(), 4); std::vector indices_vec{1}; params["X"] = migraphx::argument(input_fixed_shape0, data_vec.data()); params["I"] = migraphx::argument(input_fixed_shape1, indices_vec.data()); auto result = p.eval(params).back(); std::vector res_data{}; std::vector gold{5}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_negative_index_test_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 2}}; migraphx::shape is{migraphx::shape::int64_type, {2, 1, 1}}; std::vector data_vec(2 * 2); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{-1, 0}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, indices_vec}); mm->add_instruction(migraphx::make_op("gathernd"), data, indices); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data{}; std::vector gold{2, 3, 0, 1}; result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, gold)); } TEST_CASE(gathernd_negative_index_test_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 2}}; migraphx::shape is{migraphx::shape::int64_type, {2, 1, 1}}; std::vector data_vec(2 * 2); std::iota(data_vec.begin(), data_vec.end(), 0); std::vector indices_vec{-3, 0}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, indices_vec}); mm->add_instruction(migraphx::make_op("gathernd"), data, indices); p.compile(migraphx::make_target("ref")); EXPECT(test::throws([&] { p.eval({}); })); } ROCm-AMDMIGraphX-46524e8/test/ref/greater.cpp000066400000000000000000000114551510465702400204360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(greater_brcst_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {3, 3}}; auto l0 = mm->add_literal(migraphx::literal{s0, {1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}}); migraphx::shape s1{migraphx::shape::float_type, {3, 1}}; auto l1 = mm->add_literal(migraphx::literal{s1, {1.1, -1.5, 0.0}}); auto bl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 3}}}), l1); auto gr = mm->add_instruction(migraphx::make_op("greater"), l0, bl1); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), gr); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {false, true, false, true, false, true, false, true, false}; EXPECT(results_vector == gold); } TEST_CASE(greater_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {9}}; auto l0 = mm->add_literal(migraphx::literal{s, {1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}}); auto l1 = mm->add_literal(migraphx::literal{s, {1.1, 1.6, -0.1, -1.2, -1.5, -0.7, 0.0, 2.3, -2.1}}); auto gr = mm->add_instruction(migraphx::make_op("greater"), l0, l1); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), gr); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {false, false, true, true, false, true, false, false, true}; EXPECT(results_vector == gold); } TEST_CASE(greater_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{8, 10, {9}}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto left = mm->add_parameter("l", s); auto right = mm->add_parameter("r", s); auto gr = mm->add_instruction(migraphx::make_op("greater"), left, right); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), gr); mm->add_return({r}); p.compile(migraphx::make_target("ref")); std::vector left_data{1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}; std::vector right_data{1.1, 1.6, -0.1, -1.2, -1.5, -0.7, 0.0, 2.3, -2.1}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {9}}; params0["l"] = migraphx::argument(input_fixed_shape0, left_data.data()); params0["r"] = migraphx::argument(input_fixed_shape0, right_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {false, false, true, true, false, true, false, false, true}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/group_query_attention.cpp000066400000000000000000000117671510465702400234610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(gqa_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape qkvs{migraphx::shape::half_type, {1, 1, 12288}}; migraphx::shape pkvs{migraphx::shape::half_type, {1, 32, 4096, 128}}; migraphx::shape kvs{migraphx::shape::float_type, {1}}; migraphx::shape consts{migraphx::shape::int32_type, {1}}; migraphx::shape cs{migraphx::shape::half_type, {4096, 64}}; migraphx::shape outs{migraphx::shape::half_type, {1, 1, 4096}}; std::vector qkv_data(qkvs.elements(), 1.0); std::vector pkv_data(pkvs.elements(), 0.0); std::vector cs_data(cs.elements(), 1.0); auto qkv = mm->add_literal(migraphx::literal{qkvs, qkv_data}); auto kv = mm->add_literal(migraphx::literal{kvs, {1}}); auto pk = mm->add_literal(migraphx::literal{pkvs, pkv_data}); auto pv = mm->add_literal(migraphx::literal{pkvs, pkv_data}); auto slk = mm->add_literal(migraphx::literal{consts, {0}}); auto tsl = mm->add_literal(migraphx::literal{consts, {1}}); auto cc = mm->add_literal(migraphx::literal{cs, cs_data}); auto sc = mm->add_literal(migraphx::literal{cs, cs_data}); auto gqa = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 1}, {"kv_num_heads", 32}, {"local_window_size", -1}, {"num_heads", 32}, {"rotary_interleaved", 0}}), qkv, kv, kv, pk, pv, slk, tsl, cc, sc); auto gqa_output = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), gqa); auto gqa_present_key = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), gqa); auto gqa_present_value = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), gqa); mm->add_return({gqa_output, gqa_present_key, gqa_present_value}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& result = outputs.front(); std::vector results_vector(outs.elements()); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); const auto& pres_key = outputs.at(1); std::vector pres_key_vector(pkvs.elements()); pres_key.visit([&](auto output) { pres_key_vector.assign(output.begin(), output.end()); }); const auto& pres_val = outputs.back(); std::vector pres_val_vector(pkvs.elements()); pres_val.visit([&](auto output) { pres_val_vector.assign(output.begin(), output.end()); }); std::vector gold_output(outs.elements(), 1.0); std::vector gold_k_cache(pkvs.elements(), 0.0); std::vector gold_v_cache(pkvs.elements(), 0.0); const auto& kv_cache_lens = pkvs.lens(); for(auto i = 0; i < pkvs.elements(); i += kv_cache_lens[2] * kv_cache_lens[3]) { for(auto j = 0; j < kv_cache_lens[3]; j++) { gold_k_cache[i + j] = j >= 64 ? 2.0 : 0.0; gold_v_cache[i + j] = 1.0; } } EXPECT(results_vector == gold_output); EXPECT(pres_key_vector == gold_k_cache); EXPECT(pres_val_vector == gold_v_cache); } ROCm-AMDMIGraphX-46524e8/test/ref/group_query_attention_attr.cpp000066400000000000000000000114511510465702400245010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(group_query_attention_attributes_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape qkvs{migraphx::shape::half_type, {1, 3, 384}}; migraphx::shape pkvs{migraphx::shape::half_type, {1, 1, 4096, 128}}; migraphx::shape kvs{migraphx::shape::float_type, {1}}; migraphx::shape consts{migraphx::shape::int32_type, {1}}; migraphx::shape cs{migraphx::shape::half_type, {4096, 1}}; migraphx::shape outs{migraphx::shape::half_type, {1, 3, 128}}; std::vector qkv_data(qkvs.elements(), 0.0); std::vector pkv_data(pkvs.elements(), 0.0); std::vector cs_data(cs.elements(), 1.0); auto qkv = mm->add_literal(migraphx::literal{qkvs, qkv_data}); auto kv = mm->add_literal(migraphx::literal{kvs, {1}}); auto pk = mm->add_literal(migraphx::literal{pkvs, pkv_data}); auto pv = mm->add_literal(migraphx::literal{pkvs, pkv_data}); auto slk = mm->add_literal(migraphx::literal{consts, {3}}); auto tsl = mm->add_literal(migraphx::literal{consts, {4}}); auto cc = mm->add_literal(migraphx::literal{cs, cs_data}); auto sc = mm->add_literal(migraphx::literal{cs, cs_data}); auto gqa = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 0}, {"kv_num_heads", 1}, {"local_window_size", 1}, {"num_heads", 1}, {"rotary_interleaved", 1}, {"scale", 1.0}}), qkv, kv, kv, pk, pv, slk, tsl, cc, sc); auto gqa_output = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), gqa); auto gqa_present_key = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), gqa); auto gqa_present_value = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), gqa); mm->add_return({gqa_output, gqa_present_key, gqa_present_value}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& result = outputs.front(); std::vector results_vector(outs.elements()); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); const auto& pres_key = outputs.at(1); std::vector pres_key_vector(pkvs.elements()); pres_key.visit([&](auto output) { pres_key_vector.assign(output.begin(), output.end()); }); const auto& pres_val = outputs.back(); std::vector pres_val_vector(pkvs.elements()); pres_val.visit([&](auto output) { pres_val_vector.assign(output.begin(), output.end()); }); std::vector gold_output(outs.elements(), 0.0); std::vector gold_k_cache(pkvs.elements(), 0.0); std::vector gold_v_cache(pkvs.elements(), 0.0); EXPECT(results_vector == gold_output); EXPECT(pres_key_vector == gold_k_cache); EXPECT(pres_val_vector == gold_v_cache); } ROCm-AMDMIGraphX-46524e8/test/ref/hardmax.cpp000066400000000000000000000232401510465702400204240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(hardmax_test_0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1}; std::vector input_lens{2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_literal(migraphx::literal{data_shape, data}); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 0}}), input); auto zero_data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 0}}), zero_data, indices, updates); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(hardmax_test_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1}; std::vector input_lens{2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_literal(migraphx::literal{data_shape, data}); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 1}}), input); auto zero_data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 1}}), zero_data, indices, updates); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(hardmax_test_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1}; std::vector input_lens{2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_literal(migraphx::literal{data_shape, data}); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 2}}), input); auto zero_data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 2}}), zero_data, indices, updates); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(hardmax_test_neg_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1}; std::vector input_lens{2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_literal(migraphx::literal{data_shape, data}); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", -2}}), input); auto zero_data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", -2}}), zero_data, indices, updates); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } TEST_CASE(hardmax_test_neg_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data = {1.2255, 1.6834, -2.0305, -0.3221, 0.4701, 0.2583, 0.7545, 2.5758, -1.6849, 0.0928, 0.9022, -0.8765, -0.4090, 0.9301, 2.0724, -1.5706, 0.4867, -0.1493, 0.6957, -0.2179, 0.7142, 0.7177, 0.0183, 1.3497}; std::vector res_gold = {0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1}; std::vector input_lens{2, 3, 4}; auto input_type = migraphx::shape::float_type; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_literal(migraphx::literal{data_shape, data}); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", -1}}), input); auto zero_data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", indices->get_shape().lens()}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", -1}}), zero_data, indices, updates); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, res_gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/identity.cpp000066400000000000000000000054111510465702400206310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(identity_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector data{1, 2, 3, 4}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("identity"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(std::equal(data.begin(), data.end(), results_vector.begin())); } TEST_CASE(identity_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 4}, {2, 4}}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("identity"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{1, 2, 3, 4}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::int32_type, {2, 2}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(std::equal(input_data.begin(), input_data.end(), results_vector.begin())); } ROCm-AMDMIGraphX-46524e8/test/ref/if.cpp000066400000000000000000000204411510465702400173760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(if_literal_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", cond_s); migraphx::shape s{migraphx::shape::float_type, {5}}; auto* then_mod = p.create_module("If_0_if"); std::vector data1 = {1, 2, 3, 4, 5}; auto l1 = then_mod->add_literal(migraphx::literal(s, data1)); then_mod->add_return({l1}); auto* else_mod = p.create_module("If_0_else"); std::vector data2 = {5, 4, 3, 2, 1}; auto l2 = else_mod->add_literal(migraphx::literal(s, data2)); else_mod->add_return({l2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); return p; }; auto run_prog = [&](bool cond) { auto p = create_program(); p.compile(migraphx::make_target("ref")); std::vector c_data = {static_cast(cond)}; migraphx::shape cs{migraphx::shape::bool_type}; migraphx::parameter_map m; m["cond"] = migraphx::argument(cs, c_data.data()); auto res = p.eval(m).back(); std::vector ret; res.visit([&](auto v) { ret.assign(v.begin(), v.end()); }); return ret; }; // then branch { std::vector gold_ret = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}; auto ret = run_prog(true); EXPECT(gold_ret == ret); } // else branch { std::vector gold_ret = {5.0f, 4.0f, 3.0f, 2.0f, 1.0f}; auto ret = run_prog(false); EXPECT(gold_ret == ret); } } TEST_CASE(if_param_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", cond_s); migraphx::shape ds{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", ds); auto y = mm->add_parameter("y", ds); std::vector data2 = {-0.258047, 0.360394, 0.536804, -0.577762, 1.0217, 1.02442}; auto l2 = mm->add_literal(migraphx::literal(ds, data2)); auto sum = mm->add_instruction(migraphx::make_op("add"), x, l2); auto* then_mod = p.create_module("If_0_if"); std::vector data1 = {0.384804, -1.77948, -0.453775, 0.477438, -1.06333, -1.12893}; auto l1 = then_mod->add_literal(migraphx::literal(ds, data1)); auto tx = then_mod->add_parameter("x", ds); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), tx, l1); then_mod->add_return({a1}); auto* else_mod = p.create_module("If_0_else"); auto ey = else_mod->add_parameter("y", ds); auto a2 = else_mod->add_instruction(migraphx::make_op("mul"), ey, sum); else_mod->add_return({a2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond, x, y}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); return p; }; auto run_prog = [&](bool cond) { auto p = create_program(); p.compile(migraphx::make_target("ref")); std::vector c_data = {static_cast(cond)}; migraphx::shape cs{migraphx::shape::bool_type}; migraphx::parameter_map m; m["cond"] = migraphx::argument(cs, c_data.data()); migraphx::shape ds{migraphx::shape::float_type, {2, 3}}; std::vector data_x(ds.elements(), 1); m["x"] = migraphx::argument(ds, data_x.data()); std::vector data_y(ds.elements(), 2); m["y"] = migraphx::argument(ds, data_y.data()); auto res = p.eval(m).back(); std::vector ret; res.visit([&](auto v) { ret.assign(v.begin(), v.end()); }); return ret; }; // then branch { std::vector gold_ret = { 1.384804, -0.77947998, 0.54622501, 1.477438, -0.063330054, -0.12892997}; auto ret = run_prog(true); EXPECT(gold_ret == ret); } // else branch { std::vector gold_ret = { 1.483906, 2.720788, 3.0736079, 0.84447598, 4.0433998, 4.04884}; auto ret = run_prog(false); EXPECT(gold_ret == ret); } } TEST_CASE(if_pl_test) { auto create_program = [] { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape s{migraphx::shape::float_type, {5}}; auto cond = mm->add_parameter("cond", cond_s); auto x = mm->add_parameter("x", s); auto* then_mod = p.create_module("If_0_if"); std::vector data1 = {1, 2, 3, 4, 5}; auto l1 = then_mod->add_literal(migraphx::literal(s, data1)); then_mod->add_return({l1, x}); auto* else_mod = p.create_module("If_0_else"); std::vector data2 = {5, 4, 3, 2, 1}; auto l2 = else_mod->add_literal(migraphx::literal(s, data2)); auto s2 = else_mod->add_instruction(migraphx::make_op("add"), x, l2); else_mod->add_return({s2, l2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto outline = mm->add_outline(s); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({outline, r}); return p; }; auto run_prog = [&](bool cond) { auto p = create_program(); p.compile(migraphx::make_target("ref")); std::vector c_data = {static_cast(cond)}; migraphx::shape cs{migraphx::shape::bool_type}; migraphx::parameter_map m; m["cond"] = migraphx::argument(cs, c_data.data()); migraphx::shape ds{migraphx::shape::float_type, {5}}; std::vector data(ds.elements(), 1); m["x"] = migraphx::argument(ds, data.data()); auto res = p.eval(m).back(); std::vector ret; res.visit([&](auto v) { ret.assign(v.begin(), v.end()); }); return ret; }; // then branch { std::vector gold_ret = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}; auto ret = run_prog(true); EXPECT(gold_ret == ret); } // else branch { std::vector gold_ret = {6.0f, 5.0f, 4.0f, 3.0f, 2.0f}; auto ret = run_prog(false); EXPECT(gold_ret == ret); } } ROCm-AMDMIGraphX-46524e8/test/ref/im2col.cpp000066400000000000000000000225731510465702400201750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(im2col_3x3_no_pad_identity_test) { std::size_t f[2] = {3, 3}; std::size_t size[2] = {3, 3}; std::vector padding{0, 0}; std::vector stride{1, 1}; std::vector dilation{1, 1}; std::size_t channels = 1; std::vector weights(channels * f[0] * f[1]); std::vector gold(channels * size[0] * size[1]); std::iota(gold.begin(), gold.end(), 0); migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_image{migraphx::shape::int32_type, {1, channels, size[0], size[1]}}; migraphx::shape s_weights{migraphx::shape::int32_type, {1, channels, f[0], f[1]}}; auto l_image = mm->add_literal(migraphx::literal{s_image, gold}); auto l_weights = mm->add_literal(migraphx::literal{s_weights, weights}); mm->add_instruction( migraphx::make_op("im2col", {{"padding", padding}, {"stride", stride}, {"dilation", dilation}}), l_image, l_weights); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::size_t col_height = (size[0] - f[0] + 2 * padding[0]) / stride[0] + 1; std::size_t col_width = (size[1] - f[1] + 2 * padding[1]) / stride[1] + 1; std::vector results_vector(channels * f[0] * f[1] * col_height * col_width); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(im2col_3x3_no_pad_test) { std::size_t f[2] = {3, 3}; std::size_t size[2] = {4, 4}; std::vector padding{0, 0}; std::vector stride{1, 1}; std::vector dilation{1, 1}; std::size_t channels = 1; std::vector weights(channels * f[0] * f[1]); std::vector input(channels * size[0] * size[1]); std::iota(input.begin(), input.end(), 0); migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_image{migraphx::shape::int32_type, {1, channels, size[0], size[1]}}; migraphx::shape s_weights{migraphx::shape::int32_type, {1, channels, f[0], f[1]}}; auto l_image = mm->add_literal(migraphx::literal{s_image, input}); auto l_weights = mm->add_literal(migraphx::literal{s_weights, weights}); mm->add_instruction( migraphx::make_op("im2col", {{"padding", padding}, {"stride", stride}, {"dilation", dilation}}), l_image, l_weights); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {0, 1, 2, 4, 5, 6, 8, 9, 10, 1, 2, 3, 5, 6, 7, 9, 10, 11, 4, 5, 6, 8, 9, 10, 12, 13, 14, 5, 6, 7, 9, 10, 11, 13, 14, 15}; std::size_t col_height = (size[0] - f[0] + 2 * padding[0]) / stride[0] + 1; std::size_t col_width = (size[1] - f[1] + 2 * padding[1]) / stride[1] + 1; std::vector results_vector(channels * f[0] * f[1] * col_height * col_width); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(im2col_3x3_stride_2_no_pad_test) { std::size_t f[2] = {3, 3}; std::size_t size[2] = {6, 6}; std::vector padding{0, 0}; std::vector stride{2, 2}; std::vector dilation{1, 1}; std::size_t channels = 1; std::vector weights(channels * f[0] * f[1]); std::vector input(channels * size[0] * size[1]); std::iota(input.begin(), input.end(), 0); migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_image{migraphx::shape::int32_type, {1, channels, size[0], size[1]}}; migraphx::shape s_weights{migraphx::shape::int32_type, {1, channels, f[0], f[1]}}; auto l_image = mm->add_literal(migraphx::literal{s_image, input}); auto l_weights = mm->add_literal(migraphx::literal{s_weights, weights}); mm->add_instruction( migraphx::make_op("im2col", {{"padding", padding}, {"stride", stride}, {"dilation", dilation}}), l_image, l_weights); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {0, 1, 2, 6, 7, 8, 12, 13, 14, 2, 3, 4, 8, 9, 10, 14, 15, 16, 12, 13, 14, 18, 19, 20, 24, 25, 26, 14, 15, 16, 20, 21, 22, 26, 27, 28}; std::size_t col_height = (size[0] - f[0] + 2 * padding[0]) / stride[0] + 1; std::size_t col_width = (size[1] - f[1] + 2 * padding[1]) / stride[1] + 1; std::vector results_vector(channels * f[0] * f[1] * col_height * col_width); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(im2col_3x3_with_channels_identity_test) { std::size_t f[2] = {3, 3}; std::size_t size[2] = {3, 3}; std::vector padding{0, 0}; std::vector stride{1, 1}; std::vector dilation{1, 1}; std::size_t channels = 2; std::vector weights(channels * f[0] * f[1]); std::vector gold(channels * size[0] * size[1]); std::iota(gold.begin(), gold.end(), 0); migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_image{migraphx::shape::int32_type, {1, channels, size[0], size[1]}}; migraphx::shape s_weights{migraphx::shape::int32_type, {1, channels, f[0], f[1]}}; auto l_image = mm->add_literal(migraphx::literal{s_image, gold}); auto l_weights = mm->add_literal(migraphx::literal{s_weights, weights}); mm->add_instruction( migraphx::make_op("im2col", {{"padding", padding}, {"stride", stride}, {"dilation", dilation}}), l_image, l_weights); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::size_t col_height = (size[0] - f[0] + 2 * padding[0]) / stride[0] + 1; std::size_t col_width = (size[1] - f[1] + 2 * padding[1]) / stride[1] + 1; std::vector results_vector(channels * f[0] * f[1] * col_height * col_width); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(im2col_3x3_with_padding_test) { std::size_t f[2] = {3, 3}; std::size_t size[2] = {2, 2}; std::vector padding{1, 1}; std::vector stride{1, 1}; std::vector dilation{1, 1}; std::size_t channels = 1; std::vector weights(channels * f[0] * f[1]); std::vector input(channels * size[0] * size[1]); std::iota(input.begin(), input.end(), 0); migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_image{migraphx::shape::int32_type, {1, channels, size[0], size[1]}}; migraphx::shape s_weights{migraphx::shape::int32_type, {1, channels, f[0], f[1]}}; auto l_image = mm->add_literal(migraphx::literal{s_image, input}); auto l_weights = mm->add_literal(migraphx::literal{s_weights, weights}); mm->add_instruction( migraphx::make_op("im2col", {{"padding", padding}, {"stride", stride}, {"dilation", dilation}}), l_image, l_weights); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {0, 0, 0, 0, 0, 1, 0, 2, 3, 0, 0, 0, 0, 1, 0, 2, 3, 0, 0, 0, 1, 0, 2, 3, 0, 0, 0, 0, 1, 0, 2, 3, 0, 0, 0, 0}; std::size_t col_height = (size[0] - f[0] + 2 * padding[0]) / stride[0] + 1; std::size_t col_width = (size[1] - f[1] + 2 * padding[1]) / stride[1] + 1; std::vector results_vector(channels * f[0] * f[1] * col_height * col_width); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/isinf.cpp000066400000000000000000000126501510465702400201130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(isinf_double_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 3}}; auto inf_val = std::numeric_limits::infinity(); std::vector data0 = {1.2, 5.2, inf_val, -inf_val, 0., 100.}; auto l1 = mm->add_literal(migraphx::literal{s, data0}); mm->add_instruction(migraphx::make_op("isinf"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(isinf_float_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto inf_val = std::numeric_limits::infinity(); std::vector data0 = {1.2, 5.2, inf_val, -inf_val, 0., 100.}; auto l1 = mm->add_literal(migraphx::literal{s, data0}); mm->add_instruction(migraphx::make_op("isinf"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(isinf_half_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 3}}; auto inf_val = std::numeric_limits::infinity(); migraphx::half a{1.2}; migraphx::half b{5.2}; std::vector data0 = {a, b, inf_val, -inf_val, b, a}; auto l1 = mm->add_literal(migraphx::literal{s, data0}); mm->add_instruction(migraphx::make_op("isinf"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(isinf_bf16_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {2, 3}}; auto inf_val = std::numeric_limits::infinity(); migraphx::bf16 a{1.2}; migraphx::bf16 b{5.2}; std::vector data0 = {a, b, inf_val, -inf_val, b, a}; auto l1 = mm->add_literal(migraphx::literal{s, data0}); mm->add_instruction(migraphx::make_op("isinf"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(isinf_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 2}, {3, 8}}}; auto input = mm->add_parameter("X", s); auto inf_val = std::numeric_limits::infinity(); mm->add_instruction(migraphx::make_op("isinf"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {1.2, 5.2, inf_val, -inf_val, 0., 100.}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/isnan.cpp000066400000000000000000000077501510465702400201200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(isnan_test) { // float test { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto nan_val = std::numeric_limits::quiet_NaN(); std::vector data0 = {1.2, 5.2, nan_val, nan_val, 0., 100.}; auto l1 = mm->add_literal(migraphx::literal{s, data0}); mm->add_instruction(migraphx::make_op("isnan"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } // half test { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 3}}; auto nan_val = std::numeric_limits::quiet_NaN(); migraphx::half a{1.2}; migraphx::half b{5.2}; std::vector data0 = {a, b, nan_val, nan_val, b, a}; auto l1 = mm->add_literal(migraphx::literal{s, data0}); mm->add_instruction(migraphx::make_op("isnan"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } } TEST_CASE(isnan_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 2}, {3, 8}}}; auto input = mm->add_parameter("X", s); auto nan_val = std::numeric_limits::quiet_NaN(); mm->add_instruction(migraphx::make_op("isnan"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {1.2, 5.2, nan_val, nan_val, 0., 100.}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 1, 1, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/leaky_relu.cpp000066400000000000000000000040011510465702400211260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(leaky_relu_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l = mm->add_literal(migraphx::literal{s, {-1.f, 0.f, 1.f}}); mm->add_instruction(migraphx::make_op("leaky_relu", {{"alpha", 0.01}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-0.01f, 0.f, 1.f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/less.cpp000066400000000000000000000123211510465702400177440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(less_brcst_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {3, 3}}; auto l0 = mm->add_literal(migraphx::literal{s0, {1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}}); migraphx::shape s1{migraphx::shape::float_type, {3, 1}}; auto l1 = mm->add_literal(migraphx::literal{s1, {1.1, -1.5, 0.0}}); auto bl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 3}}}), l1); auto le = mm->add_instruction(migraphx::make_op("less"), l0, bl1); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), le); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {false, false, true, false, false, false, false, false, true}; EXPECT(results_vector == gold); } TEST_CASE(less_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {9}}; std::vector data1 = {1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}; std::vector data2 = {1.1, 1.6, -0.1, -1.2, -1.5, -0.7, 0.0, 2.3, -2.1}; auto l0 = mm->add_literal(migraphx::literal{s, data1}); auto l1 = mm->add_literal(migraphx::literal{s, data2}); auto le = mm->add_instruction(migraphx::make_op("less"), l0, l1); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), le); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(data1.size()); std::transform( data1.begin(), data1.end(), data2.begin(), gold.begin(), [](float n1, float n2) -> bool { return n1 < n2; }); EXPECT(results_vector == gold); } TEST_CASE(less_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{8, 10, {9}}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto left = mm->add_parameter("l", s); auto right = mm->add_parameter("r", s); auto le = mm->add_instruction(migraphx::make_op("less"), left, right); auto r = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::bool_type)}}), le); mm->add_return({r}); p.compile(migraphx::make_target("ref")); std::vector left_data = {1.1, 1.5, 0.1, -1.1, -1.5, -0.6, 0.0, 2.0, -2.0}; std::vector right_data = {1.1, 1.6, -0.1, -1.2, -1.5, -0.7, 0.0, 2.3, -2.1}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {9}}; params0["l"] = migraphx::argument(input_fixed_shape0, left_data.data()); params0["r"] = migraphx::argument(input_fixed_shape0, right_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(left_data.size()); std::transform(left_data.begin(), left_data.end(), right_data.begin(), gold.begin(), [](float n1, float n2) -> bool { return n1 < n2; }); EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/log.cpp000066400000000000000000000061051510465702400175620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(log_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data = {1, 2, 3}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("log"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return logf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(log_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("log"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {1, 2, 3}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return logf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/log2.cpp000066400000000000000000000061131510465702400176430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(log2_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data = {1, 2, 3}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("log2"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return log2f(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(log2_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("log2"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {1, 2, 3}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return log2f(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/logical_and.cpp000066400000000000000000000070621510465702400212400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(logical_and_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {4}}; std::vector data1{true, false, true, false}; std::vector data2{true, true, false, false}; auto l1 = mm->add_literal(migraphx::literal{s, data1}); auto l2 = mm->add_literal(migraphx::literal{s, data2}); mm->add_instruction(migraphx::make_op("logical_and"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(data2.size()); std::transform( data1.begin(), data1.end(), data2.begin(), gold.begin(), [](bool n1, bool n2) -> bool { return n1 and n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(logical_and_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6, {4}}}; migraphx::shape s{migraphx::shape::bool_type, dd}; auto left = mm->add_parameter("l", s); auto right = mm->add_parameter("r", s); mm->add_instruction(migraphx::make_op("logical_and"), left, right); p.compile(migraphx::make_target("ref")); std::vector left_data{1, 0, 1, 0}; std::vector right_data{1, 1, 0, 0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::bool_type, {4}}; params0["l"] = migraphx::argument(input_fixed_shape0, left_data.data()); params0["r"] = migraphx::argument(input_fixed_shape0, right_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(left_data.size()); std::transform(left_data.begin(), left_data.end(), right_data.begin(), gold.begin(), [](bool n1, bool n2) -> bool { return n1 and n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/logical_or.cpp000066400000000000000000000070541510465702400211170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(logical_or_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {4}}; std::vector data1{true, false, true, false}; std::vector data2{true, true, false, false}; auto l1 = mm->add_literal(migraphx::literal{s, data1}); auto l2 = mm->add_literal(migraphx::literal{s, data2}); mm->add_instruction(migraphx::make_op("logical_or"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(data1.size()); std::transform( data1.begin(), data1.end(), data2.begin(), gold.begin(), [](bool n1, bool n2) -> bool { return n1 or n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(logical_or_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6, {4}}}; migraphx::shape s{migraphx::shape::bool_type, dd}; auto left = mm->add_parameter("l", s); auto right = mm->add_parameter("r", s); mm->add_instruction(migraphx::make_op("logical_or"), left, right); p.compile(migraphx::make_target("ref")); std::vector left_data{1, 0, 1, 0}; std::vector right_data{1, 1, 0, 0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::bool_type, {4}}; params0["l"] = migraphx::argument(input_fixed_shape0, left_data.data()); params0["r"] = migraphx::argument(input_fixed_shape0, right_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(left_data.size()); std::transform(left_data.begin(), left_data.end(), right_data.begin(), gold.begin(), [](bool n1, bool n2) -> bool { return n1 or n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/logical_xor.cpp000066400000000000000000000071101510465702400213000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(logical_xor_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {4}}; std::vector data1{true, false, true, false}; std::vector data2{true, true, false, false}; auto l1 = mm->add_literal(migraphx::literal{s, data1}); auto l2 = mm->add_literal(migraphx::literal{s, data2}); mm->add_instruction(migraphx::make_op("logical_xor"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {false, true, true, false}; std::transform( data1.begin(), data1.end(), data2.begin(), gold.begin(), [](bool n1, bool n2) -> bool { return n1 ^ n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(logical_xor_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6, {4}}}; migraphx::shape s{migraphx::shape::bool_type, dd}; auto left = mm->add_parameter("l", s); auto right = mm->add_parameter("r", s); mm->add_instruction(migraphx::make_op("logical_xor"), left, right); p.compile(migraphx::make_target("ref")); std::vector left_data{1, 0, 1, 0}; std::vector right_data{1, 1, 0, 0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::bool_type, {4}}; params0["l"] = migraphx::argument(input_fixed_shape0, left_data.data()); params0["r"] = migraphx::argument(input_fixed_shape0, right_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {false, true, true, false}; std::transform(left_data.begin(), left_data.end(), right_data.begin(), gold.begin(), [](bool n1, bool n2) -> bool { return n1 ^ n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/logsoftmax.cpp000066400000000000000000000231051510465702400211630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(logsoftmax_test_axis_0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 1.93885877, -1.20006269, 0.90960855, 0.42108916, -1.50797544, -1.31047913, 1.07816336, -1.13288733, -0.86411064, 0.97800238, 0.76631385, 2.07962834, -0.8940665, -1.62855592, -0.53763057, -1.48165117, -0.64154112, 0.42486547, 0.89330917, -2.42022666, 0.192611, -0.01257413, -1.5326607, 0.53137897, -1.52383859, 0.46994381, 0.00453619, 0.0066996, 1.58394908, 0.84216752, -0.04137941, -0.88580789, 1.44055158, -0.17621241, -1.98917923, -0.08610038, 0.79020567, -0.67714548, 0.42774631, 0.1376574, 2.23569227, 1.16681234, -1.21191456, -0.28411502, -0.18688975, 1.67552548, 2.48357974, 0.95891282, -0.06616535, -0.99628491, 1.04314606, -1.22943315, 0.76930403, 0.31106618}; std::vector s = { -0.135261, -2.843968, -0.659995, -0.488413, -1.051857, -2.812936, -0.250956, -0.353985, -1.155980, -0.603651, -0.211969, -0.175371, -1.336552, -3.885010, -1.871544, -0.837083, -0.887745, -0.433338, -1.158864, -4.911197, -1.147972, -0.666711, -0.996874, -0.981418, -0.851145, -0.853988, -0.858112, -2.067420, -0.059956, -0.727436, -0.950881, -0.429689, -0.061906, -1.505332, -1.210277, -0.377970, -0.791448, -1.655428, -1.827253, -0.304828, -0.020762, -0.167101, -0.567346, -0.530319, -1.045094, -0.376648, -0.007391, -0.381670, -0.720302, -0.460499, -0.469651, -0.556740, -0.554628, -0.551582}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); int axis = 0; mm->add_instruction(migraphx::make_op("logsoftmax", {{"axis", axis}}), al); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, s)); } TEST_CASE(logsoftmax_test_axis_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 1.93885877, -1.20006269, 0.90960855, 0.42108916, -1.50797544, -1.31047913, 1.07816336, -1.13288733, -0.86411064, 0.97800238, 0.76631385, 2.07962834, -0.8940665, -1.62855592, -0.53763057, -1.48165117, -0.64154112, 0.42486547, 0.89330917, -2.42022666, 0.192611, -0.01257413, -1.5326607, 0.53137897, -1.52383859, 0.46994381, 0.00453619, 0.0066996, 1.58394908, 0.84216752, -0.04137941, -0.88580789, 1.44055158, -0.17621241, -1.98917923, -0.08610038, 0.79020567, -0.67714548, 0.42774631, 0.1376574, 2.23569227, 1.16681234, -1.21191456, -0.28411502, -0.18688975, 1.67552548, 2.48357974, 0.95891282, -0.06616535, -0.99628491, 1.04314606, -1.22943315, 0.76930403, 0.31106618}; std::vector s = { -0.550468, -2.132973, -1.549746, -0.650533, -1.051529, -2.248570, -0.141017, -2.028357, -1.947730, -1.511324, -0.166597, -0.379726, -1.965689, -1.172109, -1.475721, -2.700831, -1.537011, -0.658754, -1.596017, -3.353137, -2.266743, -1.084197, -1.076214, -0.406712, -2.743019, -0.425526, -1.079083, -2.139486, -1.270584, -1.024088, -1.154231, -3.201762, -0.888957, -0.532855, -3.103583, -1.221339, -1.355980, -3.531678, -1.438510, -0.975194, -0.080261, -1.162697, -1.568557, -1.398519, -1.322129, -0.470660, -0.370953, -0.907343, -1.179017, -3.312239, -1.286363, -1.586076, -0.345100, -0.824173}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); int axis = 1; mm->add_instruction(migraphx::make_op("logsoftmax", {{"axis", axis}}), al); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, s)); } TEST_CASE(logsoftmax_test_axis_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 1.93885877, -1.20006269, 0.90960855, 0.42108916, -1.50797544, -1.31047913, 1.07816336, -1.13288733, -0.86411064, 0.97800238, 0.76631385, 2.07962834, -0.8940665, -1.62855592, -0.53763057, -1.48165117, -0.64154112, 0.42486547, 0.89330917, -2.42022666, 0.192611, -0.01257413, -1.5326607, 0.53137897, -1.52383859, 0.46994381, 0.00453619, 0.0066996, 1.58394908, 0.84216752, -0.04137941, -0.88580789, 1.44055158, -0.17621241, -1.98917923, -0.08610038, 0.79020567, -0.67714548, 0.42774631, 0.1376574, 2.23569227, 1.16681234, -1.21191456, -0.28411502, -0.18688975, 1.67552548, 2.48357974, 0.95891282, -0.06616535, -0.99628491, 1.04314606, -1.22943315, 0.76930403, 0.31106618}; std::vector s = { -0.495957, -1.031212, -0.245531, -2.013726, -1.339125, -2.465619, -1.356652, -0.964037, -2.019250, -0.214522, -0.289569, -0.234392, -2.086591, -2.684439, -2.851651, -2.674176, -1.697424, -1.889155, -0.401029, -3.064586, -1.173030, -1.306912, -2.177020, -0.834262, -2.818177, -0.174415, -1.361105, -1.024571, -0.106766, -1.167645, -1.072650, -2.576522, -0.569261, -1.207483, -3.679894, -2.095913, -0.504264, -3.039291, -1.290559, -1.156812, -0.126453, -0.551493, -2.506384, -2.646261, -1.905195, -0.206994, -0.191369, -0.959754, -1.948685, -3.671233, -0.875521, -3.111952, -1.905644, -1.6076011}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); int axis = 2; mm->add_instruction(migraphx::make_op("logsoftmax", {{"axis", axis}}), al); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, s)); } TEST_CASE(logsoftmax_test_axis_3) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { 1.93885877, -1.20006269, 0.90960855, 0.42108916, -1.50797544, -1.31047913, 1.07816336, -1.13288733, -0.86411064, 0.97800238, 0.76631385, 2.07962834, -0.8940665, -1.62855592, -0.53763057, -1.48165117, -0.64154112, 0.42486547, 0.89330917, -2.42022666, 0.192611, -0.01257413, -1.5326607, 0.53137897, -1.52383859, 0.46994381, 0.00453619, 0.0066996, 1.58394908, 0.84216752, -0.04137941, -0.88580789, 1.44055158, -0.17621241, -1.98917923, -0.08610038, 0.79020567, -0.67714548, 0.42774631, 0.1376574, 2.23569227, 1.16681234, -1.21191456, -0.28411502, -0.18688975, 1.67552548, 2.48357974, 0.95891282, -0.06616535, -0.99628491, 1.04314606, -1.22943315, 0.76930403, 0.31106618}; std::vector s = { -0.336904, -3.475825, -1.366154, -0.279366, -2.208430, -2.010934, -0.225511, -2.436562, -2.167785, -1.572415, -1.784104, -0.470789, -1.067459, -1.801948, -0.711023, -2.307197, -1.467087, -0.400681, -0.426983, -3.740518, -1.127681, -1.078919, -2.599005, -0.534965, -2.561400, -0.567617, -1.033025, -2.097713, -0.520463, -1.262245, -1.763230, -2.607658, -0.281299, -0.814243, -2.627210, -0.724131, -0.655704, -2.123055, -1.018163, -2.480634, -0.382599, -1.451479, -1.843102, -0.915303, -0.818078, -1.316929, -0.508875, -2.033541, -1.487672, -2.417791, -0.378360, -2.568531, -0.569794, -1.028032}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); int axis = 3; mm->add_instruction(migraphx::make_op("logsoftmax", {{"axis", axis}}), al); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, s)); } ROCm-AMDMIGraphX-46524e8/test/ref/loop.cpp000066400000000000000000000115321510465702400177520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include "test.hpp" static auto run_prog(int64_t iter_num, bool cond, int64_t ini_val) { migraphx::shape si{migraphx::shape::int64_type}; migraphx::shape s{migraphx::shape::int64_type, {1}}; migraphx::shape sc{migraphx::shape::bool_type}; auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto in_iter = mm->add_parameter("iter_num", si); auto in_cond = mm->add_parameter("ccond", sc); auto in_val = mm->add_parameter("val", s); auto* body = p.create_module("loop_module"); auto iter = body->add_parameter("#loop_module_in_0", si); body->add_parameter("#loop_module_in_1", sc); auto in_v = body->add_parameter("#loop_module_in_2", s); std::vector vd = {3}; auto l = body->add_literal(migraphx::literal(si, vd)); auto ad = body->add_instruction(migraphx::make_op("add"), iter, l); auto val = body->add_instruction(migraphx::make_op("add"), in_v, ad); auto eq = body->add_instruction(migraphx::make_op("equal"), iter, l); auto beq = body->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), eq); auto neq = body->add_instruction(migraphx::make_op("not"), beq); body->add_return({neq, val, val}); auto rl = mm->add_instruction(migraphx::make_op("loop", {{"max_iterations", 10}}), {in_iter, in_cond, in_val}, {body}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), rl); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), rl); mm->add_return({r0, r1}); return p; }; auto p = create_program(); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; pp["iter_num"] = migraphx::argument(si, &iter_num); pp["ccond"] = migraphx::argument(sc, &cond); pp["val"] = migraphx::argument(s, &ini_val); auto rets = p.eval(pp); std::vector> res; for(auto& arg : rets) { std::vector vec; arg.visit([&](auto v) { vec.assign(v.begin(), v.end()); }); res.push_back(vec); } return res; } TEST_CASE(loop_test1) { auto ress = run_prog(10, true, 1); std::vector gold_last = {19}; EXPECT(ress.front() == gold_last); std::vector gold_concat = {4, 8, 13, 19, 0, 0, 0, 0, 0, 0}; EXPECT(ress.back() == gold_concat); } TEST_CASE(loop_test2) { auto ress = run_prog(4, true, 1); std::vector gold_last = {19}; EXPECT(ress.front() == gold_last); std::vector gold_concat = {4, 8, 13, 19, 0, 0, 0, 0, 0, 0}; EXPECT(ress.back() == gold_concat); } TEST_CASE(loop_test3) { auto ress = run_prog(3, true, 1); std::vector gold_last = {13}; EXPECT(ress.front() == gold_last); std::vector gold_concat = {4, 8, 13, 0, 0, 0, 0, 0, 0, 0}; EXPECT(ress.back() == gold_concat); } TEST_CASE(loop_test4) { auto ress = run_prog(5, true, 2); std::vector gold_last = {20}; EXPECT(ress.front() == gold_last); std::vector gold_concat = {5, 9, 14, 20, 0, 0, 0, 0, 0, 0}; EXPECT(ress.back() == gold_concat); } ROCm-AMDMIGraphX-46524e8/test/ref/lrn.cpp000066400000000000000000000041621510465702400175750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(lrn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 5, 1, 1}}; auto l = mm->add_literal(migraphx::literal{s, {-2.0f, 1.0f, 0.f, 1.0f, 2.0f}}); mm->add_instruction( migraphx::make_op("lrn", {{"alpha", 0.0001}, {"beta", 0.75}, {"bias", 1}, {"size", 5}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(5); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2 / 1.000075, 1 / 1.00009, 0 / 1.000145, 1 / 1.00009, 2 / 1.000075}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/main.cpp000066400000000000000000000023731510465702400177300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/ref/max.cpp000066400000000000000000000066571510465702400176020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(max_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l0 = mm->add_literal(migraphx::literal{s, {1, 4, 3}}); auto l1 = mm->add_literal(migraphx::literal{s, {2, 8, 6}}); auto l2 = mm->add_literal(migraphx::literal{s, {7, 5, 9}}); auto curr_max = mm->add_instruction(migraphx::make_op("max"), l0, l1); mm->add_instruction(migraphx::make_op("max"), curr_max, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{7, 8, 9}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(max_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto curr_max = mm->add_instruction(migraphx::make_op("max"), x, y); mm->add_instruction(migraphx::make_op("max"), curr_max, z); p.compile(migraphx::make_target("ref")); std::vector x_data{1, 4, 3}; std::vector y_data{2, 8, 6}; std::vector z_data{7, 5, 9}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); params0["z"] = migraphx::argument(input_fixed_shape0, z_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{7, 8, 9}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/min.cpp000066400000000000000000000066571510465702400176000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(min_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l0 = mm->add_literal(migraphx::literal{s, {1, 4, 3}}); auto l1 = mm->add_literal(migraphx::literal{s, {2, 8, 6}}); auto l2 = mm->add_literal(migraphx::literal{s, {7, 5, 9}}); auto curr_min = mm->add_instruction(migraphx::make_op("min"), l0, l1); mm->add_instruction(migraphx::make_op("min"), curr_min, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 4, 3}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(min_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto curr_min = mm->add_instruction(migraphx::make_op("min"), x, y); mm->add_instruction(migraphx::make_op("min"), curr_min, z); p.compile(migraphx::make_target("ref")); std::vector x_data{1, 4, 3}; std::vector y_data{2, 8, 6}; std::vector z_data{7, 5, 9}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); params0["z"] = migraphx::argument(input_fixed_shape0, z_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 4, 3}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/mod.cpp000066400000000000000000000104241510465702400175570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(mod_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {3}}; auto l0 = mm->add_literal(migraphx::literal{s, {-3, 8, -7}}); auto l1 = mm->add_literal(migraphx::literal{s, {3, 3, 3}}); auto l2 = mm->add_literal(migraphx::literal{s, {10, 2, 9}}); auto curr_mod = mm->add_instruction(migraphx::make_op("mod"), l0, l1); mm->add_instruction(migraphx::make_op("mod"), curr_mod, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0, 0, 2}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(mod_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto curr_mod = mm->add_instruction(migraphx::make_op("mod"), x, y); mm->add_instruction(migraphx::make_op("mod"), curr_mod, z); p.compile(migraphx::make_target("ref")); std::vector x_data{-3, 8, -7}; std::vector y_data{3, 3, 3}; std::vector z_data{10, 2, 9}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); params0["z"] = migraphx::argument(input_fixed_shape0, z_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0, 0, 2}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(mod_float_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l0 = mm->add_literal(migraphx::literal{s, {-3.0f, 8.5f, -7.0f}}); auto l1 = mm->add_literal(migraphx::literal{s, {2.0f, 3.0f, 3.0f}}); auto l2 = mm->add_literal(migraphx::literal{s, {3.0f, 3.0f, 4.0f}}); auto curr_mod = mm->add_instruction(migraphx::make_op("mod"), l0, l1); mm->add_instruction(migraphx::make_op("mod"), curr_mod, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1.0f, 2.5f, 2.0f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/mul.cpp000066400000000000000000000067311510465702400176030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(mul_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data1{-1, 0, 1}; std::vector data2{1, 2, 3}; auto l1 = mm->add_literal(migraphx::literal{s, {-1, 0, 1}}); auto l2 = mm->add_literal(migraphx::literal{s, {1, 2, 3}}); mm->add_instruction(migraphx::make_op("mul"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(data1.size()); std::transform( data1.begin(), data1.end(), data2.begin(), gold.begin(), [](float n1, float n2) -> float { return n1 * n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(mul_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("mul"), x, y); p.compile(migraphx::make_target("ref")); std::vector x_data{-1, 0, 1}; std::vector y_data{1, 2, 3}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(x_data.size()); std::transform(x_data.begin(), x_data.end(), y_data.begin(), gold.begin(), [](float n1, float n2) -> float { return n1 * n2; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/multibroadcast.cpp000066400000000000000000000124531510465702400220210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(multibroadcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int32_type, {2, 2}}; std::vector a_data{0, 0, 0, 0}; migraphx::shape b_shape{migraphx::shape::int32_type, {2}}; std::vector b_data{-2, -3}; auto l1 = mm->add_literal(migraphx::literal{a_shape, a_data}); auto l2 = mm->add_literal(migraphx::literal{b_shape, b_data}); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", l1->get_shape().lens()}}), l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto output = result.get(); EXPECT(output(0, 0) == -2); EXPECT(output(0, 1) == -3); EXPECT(output(1, 0) == -2); EXPECT(output(1, 1) == -3); } TEST_CASE(multibroadcast_2in_static_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int32_type, {2, 2}}; std::vector a_data{0, 0, 0, 0}; migraphx::shape b_shape{migraphx::shape::int32_type, {2}}; std::vector b_data{-2, -3}; auto l1 = mm->add_literal(migraphx::literal{a_shape, a_data}); auto l2 = mm->add_literal(migraphx::literal{b_shape, b_data}); mm->add_instruction(migraphx::make_op("multibroadcast"), l2, l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto output = result.get(); EXPECT(output(0, 0) == -2); EXPECT(output(0, 1) == -3); EXPECT(output(1, 0) == -2); EXPECT(output(1, 1) == -3); } TEST_CASE(multibroadcast_2in_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int32_type, {{2, 4}, {2, 2}}}; migraphx::shape b_shape{migraphx::shape::int32_type, {2}}; std::vector b_data{-2, -3}; auto l1 = mm->add_parameter("a", a_shape); auto l2 = mm->add_literal(migraphx::literal{b_shape, b_data}); mm->add_instruction(migraphx::make_op("multibroadcast"), l2, l1); p.compile(migraphx::make_target("ref")); std::vector a_data{0, 0, 0, 0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 2}}; params0["a"] = migraphx::argument(input_fixed_shape0, a_data.data()); auto result = p.eval(params0).back(); auto output = result.get(); EXPECT(output(0, 0) == -2); EXPECT(output(0, 1) == -3); EXPECT(output(1, 0) == -2); EXPECT(output(1, 1) == -3); } TEST_CASE(multibroadcast_3in_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int32_type, {{2, 4}, {2, 2}}}; migraphx::shape b_shape{migraphx::shape::int32_type, {2}}; migraphx::shape c_shape{migraphx::shape::int32_type, {{1, 4, {2, 4}}, {2, 4}, {2, 2}}}; auto l1 = mm->add_parameter("a", a_shape); std::vector b_data{-2, -3}; auto l2 = mm->add_literal(migraphx::literal{b_shape, b_data}); auto l3 = mm->add_parameter("c", c_shape); mm->add_instruction(migraphx::make_op("multibroadcast"), l2, l1, l3); p.compile(migraphx::make_target("ref")); std::vector a_data(4, 0); std::vector c_data(8, 0); migraphx::parameter_map params; migraphx::shape input_fixed_shape_a{migraphx::shape::float_type, {2, 2}}; migraphx::shape input_fixed_shape_c{migraphx::shape::float_type, {2, 2, 2}}; params["a"] = migraphx::argument(input_fixed_shape_a, a_data.data()); params["c"] = migraphx::argument(input_fixed_shape_c, c_data.data()); auto result = p.eval(params).back(); auto output = result.get(); EXPECT(output(0, 0, 0) == -2); EXPECT(output(0, 0, 1) == -3); EXPECT(output(0, 1, 0) == -2); EXPECT(output(0, 1, 1) == -3); EXPECT(output(1, 0, 0) == -2); EXPECT(output(1, 0, 1) == -3); EXPECT(output(1, 1, 0) == -2); EXPECT(output(1, 1, 1) == -3); } ROCm-AMDMIGraphX-46524e8/test/ref/multinomial.cpp000066400000000000000000000310651510465702400213360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include TEST_CASE(multinomial_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size = 100000; float seed = 0.0f; std::mt19937 gen(seed); std::uniform_real_distribution<> dis(0.0, 1.0); std::vector rand_samples(sample_size); std::generate(rand_samples.begin(), rand_samples.end(), [&]() { return dis(gen); }); migraphx::shape rs{migraphx::shape::float_type, {1, sample_size}}; auto rs_lit = mm->add_literal(migraphx::literal{rs, rand_samples}); migraphx::shape s{migraphx::shape::float_type, {1, 5}}; std::vector dist{15, 25, 15, 25, 20}; std::vector data(5); std::vector sum(5); // convert to float std::transform(dist.begin(), dist.end(), data.begin(), [&](auto d) { return d; }); // take cumulative sum std::partial_sum(data.begin(), data.end(), sum.begin(), std::plus()); // scale probabilities arbitrarily float odd_scale = 10000.; std::transform(sum.begin(), sum.end(), data.begin(), [&](auto d) { return d * odd_scale; }); auto input = mm->add_literal(migraphx::literal(s, data)); mm->add_instruction(migraphx::make_op("multinomial"), input, rs_lit); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); // result_vec contains an index, or category label, for each random input value std::vector result_vec(sample_size); result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); // res_dist is a count, or histogram, of the number of samples in each category. This is the // sampled distribution. std::vector res_dist(5, 0); for(const auto& r : result_vec) res_dist[r]++; // To check the result, normalize the original probability distribution dist // and the sampling result res_dist; they should be close // Total the unnormalized probabilities auto dist_sum = std::accumulate(dist.begin(), dist.end(), 0); // Total the number of values returned auto res_dist_sum = std::accumulate(res_dist.begin(), res_dist.end(), 0); std::vector norm(5); std::vector res_norm(5); std::transform(dist.begin(), dist.end(), norm.begin(), [&](auto n) { return static_cast(n) / dist_sum; }); std::transform(res_dist.begin(), res_dist.end(), res_norm.begin(), [&](auto n) { return static_cast(n) / res_dist_sum; }); EXPECT(migraphx::verify::verify_range_with_tolerance( res_norm, migraphx::verify::expected{norm}, migraphx::verify::tolerance{0.01})); } TEST_CASE(multinomial_dyn_test) { // Invokes random_uniform and multinomial ops together, to verify the interface // Dynamic Batch dimension input of 2 means there are 2 different probability // distribution functions contained in Input_2 migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size = 100000; size_t batch_size = 2; // Shape of the random data migraphx::shape rs{migraphx::shape::float_type, {{1, 2}, {2, sample_size + 1}}}; auto input = mm->add_parameter("Input_1", rs); // Runtime randomization seed // To seed the random_uniform, we can provide a value by literal or input, // or ask the system to auto-seed with random_seed op. migraphx::shape seed_shape{migraphx::shape::uint32_type, {migraphx::shape::dynamic_dimension{0, 1}}}; auto seed_input = mm->add_parameter("Seed", seed_shape); // Shape of the probability distribution, which also defines the number of categories migraphx::shape s{migraphx::shape::float_type, {{2, 2}, {5, 6}}}; // Unnormalized distributions for batch size 2: // 15, 25, 15, 15, 20 // 20, 20, 10, 25, 25 std::vector dist{15, 25, 15, 25, 20, 20, 20, 10, 25, 25}; // Hard-coded non-normalized, accumulated distribution follows: std::vector data{.15f, .40f, .55f, .80f, 1.0f, 20.f, 40.f, 50.f, 75.f, 100.f}; auto input2 = mm->add_parameter("Input_2", s); auto randoms = mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, input); mm->add_instruction(migraphx::make_op("multinomial"), input2, randoms); p.compile(migraphx::make_target("ref")); // Create a dummy input in the shape we want for the random data std::vector dummy(sample_size, 0); migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {batch_size, sample_size}}; migraphx::shape input_fixed_shape2{migraphx::shape::float_type, {batch_size, 5}}; migraphx::parameter_map params0; params0["Input_1"] = migraphx::argument(input_fixed_shape1, dummy.data()); migraphx::shape seed_fixed_shape{migraphx::shape::uint32_type, {1}}; std::vector seed_data = {4}; params0["Seed"] = migraphx::argument(seed_fixed_shape, seed_data.data()); params0["Input_2"] = migraphx::argument(input_fixed_shape2, data.data()); auto result = p.eval(params0).back(); std::vector result_vec(input_fixed_shape2.elements()); result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); // Make a categorical histogram of output std::vector res_dist(5, 0); size_t r = 0; for(r = 0; r < result_vec.size() / 2; r++) res_dist[result_vec[r]]++; // histogram for second set of batch std::vector res_dist2(5, 0); for(; r < result_vec.size(); r++) res_dist2[result_vec[r]]++; // Rescale or normalize both the input probability distribution and the output // histogram, and compare. Should be close but not identical. auto dist_sum = std::accumulate(dist.begin(), dist.begin() + 5, 0); auto res_dist_sum = std::accumulate(res_dist.begin(), res_dist.end(), 0); std::vector norm(5); std::vector res_norm(5); std::transform(dist.begin(), dist.begin() + 5, norm.begin(), [&](auto n) { return static_cast(n) / dist_sum; }); std::transform(res_dist.begin(), res_dist.end(), res_norm.begin(), [&](auto n) { return static_cast(n) / res_dist_sum; }); EXPECT(migraphx::verify::verify_range_with_tolerance( res_norm, migraphx::verify::expected{norm}, migraphx::verify::tolerance{0.01})); // Do the same rescaling for the 2nd in batch, which has a different probability distribution dist_sum = std::accumulate(dist.begin() + 5, dist.end(), 0); res_dist_sum = std::accumulate(res_dist2.begin(), res_dist2.end(), 0); std::transform(dist.begin() + 5, dist.end(), norm.begin(), [&](auto n) { return static_cast(n) / dist_sum; }); std::transform(res_dist2.begin(), res_dist2.end(), res_norm.begin(), [&](auto n) { return static_cast(n) / res_dist_sum; }); EXPECT(migraphx::verify::verify_range_with_tolerance( res_norm, migraphx::verify::expected{norm}, migraphx::verify::tolerance{0.01})); } TEST_CASE(multinomial_float_dyn_test) { // int data type for random_uniform op and float data type for multinomial. migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size = 100000; size_t batch_size = 2; // Shape of the random data migraphx::shape rs{migraphx::shape::int32_type, {{1, 2}, {2, sample_size + 1}}}; auto input = mm->add_parameter("Input_1", rs); // Runtime randomization seed // To seed the random_uniform, we can provide a value by literal or input, // or ask the system to auto-seed with random_seed op. migraphx::shape seed_shape{migraphx::shape::uint32_type, {migraphx::shape::dynamic_dimension{0, 1}}}; auto seed_input = mm->add_parameter("Seed", seed_shape); // Shape of the probability distribution, which also defines the number of categories migraphx::shape s{migraphx::shape::float_type, {{2, 2}, {5, 6}}}; // Unnormalized distributions for batch size 2: // 15, 25, 15, 15, 20 // 20, 20, 10, 25, 25 std::vector dist{15, 25, 15, 25, 20, 20, 20, 10, 25, 25}; // Hard-coded normalized, accumulated distribution follows: std::vector data{.15f, .40f, .55f, .80f, 1.0f, .20f, .40f, .50f, .75f, 1.0f}; auto input2 = mm->add_parameter("Input_2", s); auto randoms = mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, input); mm->add_instruction(migraphx::make_op("multinomial", {{"dtype", migraphx::shape::float_type}}), input2, randoms); p.compile(migraphx::make_target("ref")); // Create a dummy input in the shape we want for the random data std::vector dummy(sample_size, 0); migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {batch_size, sample_size}}; migraphx::shape input_fixed_shape2{migraphx::shape::float_type, {batch_size, 5}}; migraphx::parameter_map params0; params0["Input_1"] = migraphx::argument(input_fixed_shape1, dummy.data()); migraphx::shape seed_fixed_shape{migraphx::shape::uint32_type, {1}}; std::vector seed_data = {4}; params0["Seed"] = migraphx::argument(seed_fixed_shape, seed_data.data()); params0["Input_2"] = migraphx::argument(input_fixed_shape2, data.data()); auto result = p.eval(params0).back(); std::vector result_vec(input_fixed_shape2.elements()); result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); // Make a categorical histogram of output std::vector res_dist(5, 0); size_t r = 0; for(r = 0; r < result_vec.size() / 2; r++) res_dist[result_vec[r]]++; // histogram for second set of batch std::vector res_dist2(5, 0); for(; r < result_vec.size(); r++) res_dist2[result_vec[r]]++; // Rescale or normalize both the input probability distribution and the output // histogram, and compare. Should be close but not identical. auto dist_sum = std::accumulate(dist.begin(), dist.begin() + 5, 0); auto res_dist_sum = std::accumulate(res_dist.begin(), res_dist.end(), 0); std::vector norm(5); std::vector res_norm(5); std::transform(dist.begin(), dist.begin() + 5, norm.begin(), [&](auto n) { return static_cast(n) / dist_sum; }); std::transform(res_dist.begin(), res_dist.end(), res_norm.begin(), [&](auto n) { return static_cast(n) / res_dist_sum; }); EXPECT(migraphx::verify::verify_range_with_tolerance( res_norm, migraphx::verify::expected{norm}, migraphx::verify::tolerance{0.01})); // Do the same rescaling for the 2nd in batch, which has a different probability distribution dist_sum = std::accumulate(dist.begin() + 5, dist.end(), 0); res_dist_sum = std::accumulate(res_dist2.begin(), res_dist2.end(), 0); std::transform(dist.begin() + 5, dist.end(), norm.begin(), [&](auto n) { return static_cast(n) / dist_sum; }); std::transform(res_dist2.begin(), res_dist2.end(), res_norm.begin(), [&](auto n) { return static_cast(n) / res_dist_sum; }); EXPECT(migraphx::verify::verify_range_with_tolerance( res_norm, migraphx::verify::expected{norm}, migraphx::verify::tolerance{0.01})); } ROCm-AMDMIGraphX-46524e8/test/ref/nearbyint.cpp000066400000000000000000000106471510465702400210020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(nearbyint_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 4}}; auto l = mm->add_literal(migraphx::literal{s, {-3.51, -3.5, -3.49, -2.51, -2.50, -2.49, -1.6, -1.5, -0.51, -0.5, 0.5, 0.6, 2.4, 2.5, 3.5, 4.5}}); mm->add_instruction(migraphx::make_op("nearbyint"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = { -4.0, -4.0, -3.0, -3.0, -2.0, -2.0, -2.0, -2.0, -1.0, 0.0, 0.0, 1.0, 2.0, 2.0, 4.0, 4.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(nearbyint_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{4, 10}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("nearbyint"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-3.51, -3.5, -3.49, -2.51, -2.50, -2.49, -1.6, -1.5, -0.51, -0.5, 0.5, 0.6, 2.4, 2.5, 3.5, 4.5}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {16}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = { -4.0, -4.0, -3.0, -3.0, -2.0, -2.0, -2.0, -2.0, -1.0, 0.0, 0.0, 1.0, 2.0, 2.0, 4.0, 4.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/neg.cpp000066400000000000000000000061441510465702400175550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(neg_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data = {1.0f, 1.3f, -1.2f, 0.0f, -100.f, 200.f}; auto input = mm->add_literal(migraphx::literal(s, data)); auto ret = mm->add_instruction(migraphx::make_op("neg"), input); mm->add_return({ret}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform(gold.begin(), gold.end(), gold.begin(), std::negate()); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } TEST_CASE(neg_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 4}, {3, 3}}}; auto input = mm->add_parameter("X", s); auto ret = mm->add_instruction(migraphx::make_op("neg"), input); mm->add_return({ret}); p.compile(migraphx::make_target("ref")); std::vector a = {1.0f, 1.3f, -1.2f, 0.0f, -100.f, 200.f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 3}}; params0["X"] = migraphx::argument(input_fixed_shape0, a.data()); auto result = p.eval(params0).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = a; std::transform(gold.begin(), gold.end(), gold.begin(), std::negate()); EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/nonmaxsuppression.cpp000066400000000000000000000405061510465702400226170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(nms_dyn_out_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {1, 6, 4}}; std::vector boxes_vec = {0.5, 0.5, 1.0, 1.0, 0.5, 0.6, 1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 10.5, 1.0, 1.0, 0.5, 10.6, 1.0, 1.0, 0.5, 100.5, 1.0, 1.0}; migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; std::vector scores_vec = {0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; auto boxes_l = mm->add_literal(migraphx::literal(boxes_s, boxes_vec)); auto scores_l = mm->add_literal(migraphx::literal(scores_s, scores_vec)); auto max_out_l = mm->add_literal(int64_t{4}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); auto r = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_l, scores_l, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto output = p.eval({}).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 0, 5}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } TEST_CASE(nms_identical_all_dyn_out_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {1, 6, 4}}; // all identical boxes: some also with flipped (yet of an identical box) coordinates: std::vector boxes_vec = {0.5, 0.5, 0.7, 0.7, 0.7, 0.7, 0.5, 0.5, 0.7, 0.7, 0.5, 0.5, 0.5, 0.5, 0.7, 0.7, 0.5, 0.5, 0.7, 0.7, 0.7, 0.7, 0.5, 0.5}; migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; // all identical scores: std::vector scores_vec = {0.9, 0.9, 0.9, 0.9, 0.9, 0.9}; auto boxes_l = mm->add_literal(migraphx::literal(boxes_s, boxes_vec)); auto scores_l = mm->add_literal(migraphx::literal(scores_s, scores_vec)); auto max_out_l = mm->add_literal(int64_t{6}); auto iou_threshold = mm->add_literal(0.1f); auto score_threshold = mm->add_literal(0.0f); auto r = mm->add_instruction(migraphx::make_op("nonmaxsuppression", {{"use_dyn_output", true}}), boxes_l, scores_l, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto output = p.eval({}).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); // this test should pick only the first (identical) candidate std::vector gold = {0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } TEST_CASE(nms_dyn_batch_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {{1, 3}, {6, 6}, {4, 4}}}; migraphx::shape scores_s{migraphx::shape::float_type, {{1, 3}, {1, 1}, {6, 6}}}; auto boxes_p = mm->add_parameter("boxes", boxes_s); auto scores_p = mm->add_parameter("scores", scores_s); auto max_out_l = mm->add_literal(int64_t{4}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); auto r = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_p, scores_p, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); std::vector boxes_vec = {0.5, 0.5, 1.0, 1.0, 0.5, 0.6, 1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 10.5, 1.0, 1.0, 0.5, 10.6, 1.0, 1.0, 0.5, 100.5, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.6, 1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 10.5, 1.0, 1.0, 0.5, 10.6, 1.0, 1.0, 0.5, 100.5, 1.0, 1.0}; std::vector scores_vec = { 0.9, 0.75, 0.6, 0.95, 0.5, 0.3, 0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 6, 4}}; migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {2, 1, 6}}; migraphx::parameter_map params0; params0["boxes"] = migraphx::argument(input_fixed_shape0, boxes_vec.data()); params0["scores"] = migraphx::argument(input_fixed_shape1, scores_vec.data()); auto output = p.eval(params0).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 0, 5, 1, 0, 3, 1, 0, 0, 1, 0, 5}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } TEST_CASE(nms_dyn_boxes_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {{1, 1}, {4, 20}, {4, 4}}}; migraphx::shape scores_s{migraphx::shape::float_type, {{1, 1}, {1, 1}, {4, 20}}}; auto boxes_p = mm->add_parameter("boxes", boxes_s); auto scores_p = mm->add_parameter("scores", scores_s); auto max_out_l = mm->add_literal(int64_t{4}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); auto r = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_p, scores_p, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); std::vector boxes_vec = {0.5, 0.5, 1.0, 1.0, 0.5, 0.6, 1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 10.5, 1.0, 1.0, 0.5, 10.6, 1.0, 1.0, 0.5, 100.5, 1.0, 1.0}; std::vector scores_vec = {0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {1, 6, 4}}; migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {1, 1, 6}}; migraphx::parameter_map params0; params0["boxes"] = migraphx::argument(input_fixed_shape0, boxes_vec.data()); params0["scores"] = migraphx::argument(input_fixed_shape1, scores_vec.data()); auto output = p.eval(params0).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 0, 5}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } TEST_CASE(nms_dyn_classes_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {{1, 1}, {6, 6}, {4, 4}}}; migraphx::shape scores_s{migraphx::shape::float_type, {{1, 1}, {1, 3}, {6, 6}}}; auto boxes_p = mm->add_parameter("boxes", boxes_s); auto scores_p = mm->add_parameter("scores", scores_s); auto max_out_l = mm->add_literal(int64_t{2}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); auto r = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}, {"use_dyn_output", true}}), boxes_p, scores_p, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); std::vector boxes_vec = {0.0, 0.0, 1.0, 1.0, 0.0, 0.1, 1.0, 1.1, 0.0, -0.1, 1.0, 0.9, 0.0, 10.0, 1.0, 11.0, 0.0, 10.1, 1.0, 11.1, 0.0, 100.0, 1.0, 101.0}; std::vector scores_vec = { 0.9, 0.75, 0.6, 0.95, 0.5, 0.3, 0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {1, 6, 4}}; migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {1, 2, 6}}; migraphx::parameter_map params0; params0["boxes"] = migraphx::argument(input_fixed_shape0, boxes_vec.data()); params0["scores"] = migraphx::argument(input_fixed_shape1, scores_vec.data()); auto output = p.eval(params0).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 1, 3, 0, 1, 0}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } TEST_CASE(nms_not_center_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {1, 6, 4}}; std::vector boxes_vec = {1.0, 1.0, 0.0, 0.0, 0.0, 0.1, 1.0, 1.1, 0.0, 0.9, 1.0, -0.1, 0.0, 10.0, 1.0, 11.0, 1.0, 10.1, 0.0, 11.1, 1.0, 101.0, 0.0, 100.0}; migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; std::vector scores_vec = {0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; auto boxes_l = mm->add_literal(migraphx::literal(boxes_s, boxes_vec)); auto scores_l = mm->add_literal(migraphx::literal(scores_s, scores_vec)); auto max_out_l = mm->add_literal(int64_t{4}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); // set use_dyn_output back to false in operator map auto r = mm->add_instruction(migraphx::make_op("nonmaxsuppression", {{"use_dyn_output", false}}), boxes_l, scores_l, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto output = p.eval({}).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } TEST_CASE(nms_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {1, 6, 4}}; std::vector boxes_vec = {0.5, 0.5, 1.0, 1.0, 0.5, 0.6, 1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 10.5, 1.0, 1.0, 0.5, 10.6, 1.0, 1.0, 0.5, 100.5, 1.0, 1.0}; migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; std::vector scores_vec = {0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; auto boxes_l = mm->add_literal(migraphx::literal(boxes_s, boxes_vec)); auto scores_l = mm->add_literal(migraphx::literal(scores_s, scores_vec)); auto max_out_l = mm->add_literal(int64_t{4}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); auto r = mm->add_instruction(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}}), boxes_l, scores_l, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto output = p.eval({}).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } TEST_CASE(nms_transpose1_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {1, 4, 6}}; std::vector boxes_vec = { 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.4, 10.5, 10.6, 100.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, }; migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; std::vector scores_vec = {0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; auto t_boxes_l = mm->add_literal(migraphx::literal(boxes_s, boxes_vec)); auto scores_l = mm->add_literal(migraphx::literal(scores_s, scores_vec)); auto max_out_l = mm->add_literal(int64_t{4}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); auto transpose_boxes = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), t_boxes_l); auto r = mm->add_instruction(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}}), transpose_boxes, scores_l, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto output = p.eval({}).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } TEST_CASE(nms_transpose2_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {4, 1, 6}}; std::vector boxes_vec = { 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.4, 10.5, 10.6, 100.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, }; migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; std::vector scores_vec = {0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; auto t_boxes_l = mm->add_literal(migraphx::literal(boxes_s, boxes_vec)); auto scores_l = mm->add_literal(migraphx::literal(scores_s, scores_vec)); auto max_out_l = mm->add_literal(int64_t{4}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); auto transpose_boxes = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {1, 2, 0}}}), t_boxes_l); auto r = mm->add_instruction(migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}}), transpose_boxes, scores_l, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto output = p.eval({}).back(); std::vector result; output.visit([&](auto out) { result.assign(out.begin(), out.end()); }); std::vector gold = {0, 0, 3, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/nonzero.cpp000066400000000000000000000044001510465702400204670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(nonzero_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}}; std::vector data = { 1.0f, 1.3f, 0.0f, -1.2f, 0.0f, -100.f, 200.f, 0.0f, 0.1f, 0.2f, 0.0f, 0.5f}; auto input = mm->add_literal(migraphx::literal(s, data)); auto ret = mm->add_instruction(migraphx::make_op("nonzero"), input); mm->add_return({ret}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vector; result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/not.cpp000066400000000000000000000071261510465702400176050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(not_test_int32) // int32 { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {4}}; std::vector data{0, 8, 1, -32}; auto l1 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("not"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(not_test_bool) // bool { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {4}}; std::vector data{false, false, true, true}; auto l1 = mm->add_literal(migraphx::literal{s, {0, 0, 1, 1}}); mm->add_instruction(migraphx::make_op("not"), l1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(data.size()); std::transform(data.begin(), data.end(), gold.begin(), [](bool n) -> bool { return not n; }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(not_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("not"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{0, 8, 1, -32}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/onehot.cpp000066400000000000000000000314501510465702400202760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include TEST_CASE(onehot0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {4}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto depth_param = mm->add_parameter("depth", depth_s); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_param, depth_param, values_param); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector indices_data = {0, -8, -1, 5}; std::vector depth_data = {3}; std::vector values_data = {0.0, 5.0}; params["indices"] = migraphx::argument(inds_s, indices_data.data()); params["depth"] = migraphx::argument(depth_s, depth_data.data()); params["values"] = migraphx::argument(values_s, values_data.data()); auto result = p.eval(params).back(); // clang-format off std::vector gold = { 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0 }; // clang-format on std::vector results_vector(4 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(onehot1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {2, 2}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto depth_lit = mm->add_literal(migraphx::literal{depth_s, {3}}); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_param, depth_lit, values_param); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector indices_data = {0, 2, 1, -1}; std::vector values_data = {0.0, 1.0}; params["indices"] = migraphx::argument(inds_s, indices_data.data()); params["values"] = migraphx::argument(values_s, values_data.data()); auto result = p.eval(params).back(); // clang-format off std::vector gold = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; // clang-format on std::vector results_vector(2 * 2 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(onehot_depth_attr) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {2, 2}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}, {"depth", 3}}), inds_param, values_param); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector indices_data = {0, 2, 1, -1}; std::vector values_data = {0.0, 1.0}; params["indices"] = migraphx::argument(inds_s, indices_data.data()); params["values"] = migraphx::argument(values_s, values_data.data()); auto result = p.eval(params).back(); // clang-format off std::vector gold = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; // clang-format on std::vector results_vector(2 * 2 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(onehot_dyn) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {{1, 4}, {2, 2}}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::int32_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto depth_lit = mm->add_literal(migraphx::literal{depth_s, {3}}); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_param, depth_lit, values_param); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector indices_data = {0, 2, 1, -1}; std::vector values_data = {-3, 5}; migraphx::shape static_inds_shape{migraphx::shape::int64_type, {2, 2}}; params["indices"] = migraphx::argument(static_inds_shape, indices_data.data()); params["values"] = migraphx::argument(values_s, values_data.data()); auto result = p.eval(params).back(); // clang-format off std::vector gold = { 5, -3, -3, -3, -3, 5, -3, 5, -3, -3, -3, 5 }; // clang-format on std::vector results_vector(2 * 2 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(onehot_neg_depth_error) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {4}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto depth_param = mm->add_parameter("depth", depth_s); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_param, depth_param, values_param); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector indices_data = {0, 2, -1, 5}; std::vector depth_data = {-2}; std::vector values_data = {0.0, 5.0}; params["indices"] = migraphx::argument(inds_s, indices_data.data()); params["depth"] = migraphx::argument(depth_s, depth_data.data()); params["values"] = migraphx::argument(values_s, values_data.data()); EXPECT(test::throws([&] { std::ignore = p.eval(params).back(); })); } // make sure the result is the same when using the simplify pass TEST_CASE(onehot_simplify_test0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {2, 2}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto depth_lit = mm->add_literal(migraphx::literal{depth_s, {3}}); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", 0}}), inds_param, depth_lit, values_param); migraphx::run_passes(p, {migraphx::simplify_dyn_ops{}, migraphx::dead_code_elimination{}}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector indices_data = {0, 2, 1, -1}; std::vector values_data = {0.0, 1.0}; params["indices"] = migraphx::argument(inds_s, indices_data.data()); params["values"] = migraphx::argument(values_s, values_data.data()); auto result = p.eval(params).back(); // clang-format off std::vector gold = { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0 }; // clang-format on std::vector results_vector(2 * 2 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(onehot_simplify_test1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {2, 2}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::int32_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto depth_lit = mm->add_literal(migraphx::literal{depth_s, {3}}); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_param, depth_lit, values_param); migraphx::run_passes(p, {migraphx::simplify_dyn_ops{}, migraphx::dead_code_elimination{}}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector indices_data = {0, 2, 1, -1}; std::vector values_data = {-3, 5}; params["indices"] = migraphx::argument(inds_s, indices_data.data()); params["values"] = migraphx::argument(values_s, values_data.data()); auto result = p.eval(params).back(); // clang-format off std::vector gold = { 5, -3, -3, -3, -3, 5, -3, 5, -3, -3, -3, 5 }; // clang-format on std::vector results_vector(2 * 2 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(onehot_simplify_test2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {4}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}, {0}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto depth_lit = mm->add_literal(migraphx::literal{depth_s, {3}}); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_param, depth_lit, values_param); migraphx::run_passes(p, {migraphx::simplify_dyn_ops{}, migraphx::dead_code_elimination{}}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector indices_data = {0, -8, -1, 5}; std::vector values_data = {0.0, 5.0}; params["indices"] = migraphx::argument(inds_s, indices_data.data()); params["values"] = migraphx::argument(values_s, values_data.data()); auto result = p.eval(params).back(); // clang-format off std::vector gold = { 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0 }; // clang-format on std::vector results_vector(4 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/pack_int4.cpp000066400000000000000000000142251510465702400206570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(pack_uint4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); mm->add_instruction(migraphx::make_op("pack_int4"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0xBA, 0xDC}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_int4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int8_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); mm->add_instruction(migraphx::make_op("pack_int4"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x77, 0x77}; // clipped values EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_uint4_transposed) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {2, 2}, {1, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); mm->add_instruction(migraphx::make_op("pack_int4"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0xBA, 0xDC}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_uint4_broadcasted) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {4}, {1}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); auto l0b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 4}}}), l0); mm->add_instruction(migraphx::make_op("pack_int4"), l0b); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(8); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0xBA, 0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA, 0xDC}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_uint4_axis_0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); mm->add_instruction(migraphx::make_op("pack_int4", {{"axis", 0}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0xCA, 0xDB}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_uint4_nchw) { // input values >= 0x10 would be clipped to 0xf (the maximum for uint4) // As seen in the bottom half of the expected results (gold) below. migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {1, 2, 4, 4}}; auto l0 = mm->add_literal( migraphx::literal{s, {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}}); mm->add_instruction(migraphx::make_op("pack_int4", {{"axis", -1}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/pack_unpack_fp4.cpp000066400000000000000000000100061510465702400220240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(pack_fp4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {-2.f, 3.4f, 3.5f, 0.f}}); mm->add_instruction(migraphx::make_op("pack_fp4"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); result = result.reshape(migraphx::shape(migraphx::shape::uint8_type, result.get_shape().lens())); std::vector results_vector(2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x5C, 0x06}; EXPECT(results_vector.at(0) == gold.at(0)); EXPECT(results_vector.at(1) == gold.at(1)); } TEST_CASE(unpack_fp4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::fp4x2_type, {2, 1}}; std::vector packed_data = {0x5C, 0x06}; auto lit = migraphx::literal{s, packed_data.data()}; auto l0 = mm->add_literal(lit); mm->add_instruction(migraphx::make_op("unpack_fp4"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-2.f, 3.f, 4.f, 0.f}; EXPECT(migraphx::float_equal(results_vector.at(0), gold.at(0))); EXPECT(migraphx::float_equal(results_vector.at(1), gold.at(1))); EXPECT(migraphx::float_equal(results_vector.at(2), gold.at(2))); EXPECT(migraphx::float_equal(results_vector.at(3), gold.at(3))); } TEST_CASE(pack_unpack_fp4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {-2.f, 3.4f, 3.5f, 0.f}}); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_fp4", {{"axis", 0}}), l0); mm->add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 0}}), pack_ins); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-2.f, 3.f, 4.f, 0.f}; EXPECT(migraphx::float_equal(results_vector.at(0), gold.at(0))); EXPECT(migraphx::float_equal(results_vector.at(1), gold.at(1))); EXPECT(migraphx::float_equal(results_vector.at(2), gold.at(2))); EXPECT(migraphx::float_equal(results_vector.at(3), gold.at(3))); } ROCm-AMDMIGraphX-46524e8/test/ref/pack_unpack_int4.cpp000066400000000000000000000166171510465702400222270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(pack_unpack_uint4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_int4"), l0); mm->add_instruction(migraphx::make_op("unpack_int4"), pack_ins); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(s.elements()); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x0A, 0x0B, 0x0C, 0x0D}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_unpack_uint4_transposed) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {2, 2}, {1, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_int4"), l0); mm->add_instruction(migraphx::make_op("unpack_int4"), pack_ins); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(s.elements()); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x0A, 0x0B, 0x0C, 0x0D}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_multibroadcast_unpack_uint4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {4}, {1}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_int4"), l0); auto mb_pack = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 2}}}), pack_ins); mm->add_instruction(migraphx::make_op("unpack_int4"), mb_pack); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x0A, 0x0B, 0x0C, 0x0D, 0x0A, 0x0B, 0x0C, 0x0D, 0x0A, 0x0B, 0x0C, 0x0D, 0x0A, 0x0B, 0x0C, 0x0D}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_unpack_uint4_axis_0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x0A, 0x0B, 0x0C, 0x0D}}); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_int4", {{"axis", 0}}), l0); mm->add_instruction(migraphx::make_op("unpack_int4", {{"axis", 0}}), pack_ins); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(s.elements()); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x0A, 0x0B, 0x0C, 0x0D}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_unpack_uint4_nchw) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {1, 2, 4, 4}}; auto l0 = mm->add_literal( migraphx::literal{s, {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}}); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_int4", {{"axis", -1}}), l0); // The result of packing also includes a clip. Max clip value = 0xf for UINT4. mm->add_instruction(migraphx::make_op("unpack_int4", {{"axis", -1}}), pack_ins); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(s.elements()); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); // The gold unpacked values should contain input values clipped during pack std::vector gold{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pack_unpack_int4_nchw) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int8_type, {1, 2, 2, 4}}; auto l0 = mm->add_literal( migraphx::literal{s, {-10, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 10}}); auto pack_ins = mm->add_instruction(migraphx::make_op("pack_int4", {{"axis", -1}}), l0); // Packing also includes a clip: // Max clipped and packed nibble value = +7 for INT4. // Min clipped and packed nibble value = -8 for INT4. // Both the outer values of l0 should be clipped during pack_int4: -10, 10 mm->add_instruction(migraphx::make_op("unpack_int4", {{"axis", -1}}), pack_ins); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(s.elements()); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/pad.cpp000066400000000000000000000121671510465702400175520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(pad_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {1, 2, 3, 4}}); mm->add_instruction(migraphx::make_op("pad", {{"pads", {1, 1, 1, 1}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0, 0, 0, 0, 0, 1, 2, 0, 0, 3, 4, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pad_test_asym) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {1, 2, 3, 4}}); mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 1, 1}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(9); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 0, 3, 4, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pad_test_highest_half) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {1, 2, 3, 4}}); mm->add_instruction( migraphx::make_op("pad", {{"pads", {1, 1, 1, 1}}, {"value", std::numeric_limits::max()}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); const float x = std::numeric_limits::max(); std::vector gold{x, x, x, x, x, 1, 2, x, x, 3, 4, x, x, x, x, x}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pad_test_lowest_half) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {1, 2, 3, 4}}); mm->add_instruction( migraphx::make_op( "pad", {{"pads", {1, 1, 1, 1}}, {"value", std::numeric_limits::lowest()}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); const float x = std::numeric_limits::lowest(); std::vector gold{x, x, x, x, x, 1, 2, x, x, 3, 4, x, x, x, x, x}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pad_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 4, {2}}, {2, 4, {2}}}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("pad", {{"pads", {1, 1, 1, 1}}}), x); p.compile(migraphx::make_target("ref")); std::vector data = {1, 2, 3, 4}; migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 2}}; params["x"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector(16); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0, 0, 0, 0, 0, 1, 2, 0, 0, 3, 4, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/pointwise.cpp000066400000000000000000000065761510465702400210360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(pointwise_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l1 = mm->add_literal(migraphx::literal{s, {-1, 0, 1}}); auto l2 = mm->add_literal(migraphx::literal{s, {1, 2, 3}}); auto* pm = p.create_module("pointwise"); { auto x1 = pm->add_parameter("x1", {migraphx::shape::float_type}); auto x2 = pm->add_parameter("x2", {migraphx::shape::float_type}); auto add = pm->add_instruction(migraphx::make_op("add"), x1, x2); pm->add_return({add}); } mm->add_instruction(migraphx::make_op("pointwise"), {l1, l2}, {pm}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 2, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pointwise_multi_out_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto a1 = mm->add_literal(migraphx::literal{s, {-1, 0, 1}}); auto a2 = mm->add_literal(migraphx::literal{s, {1, 16, 3}}); auto* pm = p.create_module("pointwise"); { auto x1 = pm->add_parameter("x1", {migraphx::shape::float_type}); auto x2 = pm->add_parameter("x2", {migraphx::shape::float_type}); auto add = pm->add_instruction(migraphx::make_op("add"), x1, x2); auto sqrt = pm->add_instruction(migraphx::make_op("sqrt"), add); pm->add_return({add, sqrt}); } mm->add_instruction(migraphx::make_op("pointwise"), {a1, a2}, {pm}); p.compile(migraphx::make_target("ref")); auto results = p.eval({}).back().get_sub_objects(); std::vector gold1 = {0, 16, 4}; std::vector gold2 = {0, 4, 2}; EXPECT(results[0].to_vector() == gold1); EXPECT(results[1].to_vector() == gold2); } ROCm-AMDMIGraphX-46524e8/test/ref/pooling.cpp000066400000000000000000001263101510465702400204510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(avgpool_rank3_test) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {1}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.25, 0.3, 0.25, 0.65, 0.7, 0.5, 0.4, 0.4, 0.35}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_rank3_dil_test) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {2}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.35, 0.15, 0.85, 0.3, 0.1, 0.65}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_rank3_dil_test2) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {3}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.2, 0.45, 0.35}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_rank3_pad_test) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2}; op.padding = {1}; op.stride = {1}; op.dilations = {1}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 0.3, 0.25, 0.3, 0.25, 0.1, 0.8, 0.65, 0.7, 0.5, 0.1, 0.1, 0.4, 0.4, 0.35, 0.6}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_rank3_pad_dil_test) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2}; op.padding = {1}; op.stride = {1}; op.dilations = {3}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4, 0.2, 0.2, 0.9, 0.45, 0.5, 0.1, 0.35, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_dyn_test) { // Dynamic input, no padding migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}}}; auto x = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"lengths", {2}}, {"padding", {0}}, {"stride", {1}}, {"dilations", {1}}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 4}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.25, 0.3, 0.25, 0.65, 0.7, 0.5, 0.4, 0.4, 0.35}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_dyn_pad_test) { // Dynamic input with explicit padding migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 3}, {3, 3}, {4, 4}}}; auto x = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"lengths", {2}}, {"padding", {1}}, {"stride", {1}}, {"dilations", {1}}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 4}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 0.3, 0.25, 0.3, 0.25, 0.1, 0.8, 0.65, 0.7, 0.5, 0.1, 0.1, 0.4, 0.4, 0.35, 0.6}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_dyn_auto_pad_test) { // Pooling with dynamic input, multidimensional kernel and auto-padding migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 1}, {1, 3}, {2, 6, {2}}, {2, 6, {2}}}}; auto x = mm->add_parameter("X", s); mm->add_instruction( migraphx::make_op("pooling", { {"mode", migraphx::op::pooling_mode::average}, {"dyn_global", false}, // non-default auto padding {"padding_mode", migraphx::op::padding_mode_t::same_upper}, {"lengths", {2, 3}}, }), x); p.compile(migraphx::make_target("ref")); std::vector data{1, 2, 3, 4}; // * 1 2 * auto padding should look like this // * 3 4 * // * * * * migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 1, 2, 2}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2.5, 2.5, 3.5, 3.5}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_dyn_auto_pad_1d_test) { // Dynamic input with auto padding (== padding_mode specified) migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 3}, {3, 3}, {4, 4}}}; auto x = mm->add_parameter("X", s); mm->add_instruction( migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"lengths", {2}}, // padding added will be {1, 0} to make output // the same size as input {"padding_mode", migraphx::op::padding_mode_t::same_lower}, {"stride", {1}}, {"dilations", {1}}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 4}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold{0.3, 0.25, 0.3, 0.25, 0.8, 0.65, 0.7, 0.5, 0.1, 0.4, 0.4, 0.35}; // clang-format on EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_dyn_pad_ceil_test) { // pooling with dynamic input and padding migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 4}, {1, 3}, {2, 4}, {2, 4}}}; auto x = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"lengths", {2, 3}}, {"padding", {1, 2}}, {"ceil_mode", true}, {"stride", {1, 1}}, {"dilations", {1, 1}}}), x); p.compile(migraphx::make_target("ref")); std::vector data{1, 2, 3, 4}; // * * * * * * // * * 1 2 * * padded input will look like this // * * 3 4 * * but the * are ignored in averaging // * * * * * * migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 1, 2, 2}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold{1.0, 1.5, 1.5, 2.0, 2.0, 2.5, 2.5, 3.0, 3.0, 3.5, 3.5, 4.0}; // clang-format on EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_rank3_stride2_test) { // 1D case 2, stride 2 migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {2, 2, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2}; op.padding = {1}; op.stride = {2}; op.dilations = {1}; // clang-format off std::vector data{1.6321, -2.4186, 0.2239, -1.4232, 0.8158, 0.4103, -0.3149, -0.1361, -0.3442, 2.007, 0.4331, 1.5295, 0.9965, 0.4766, 1.0942, -0.2915}; // clang-format on auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold{1.6321, -1.09735, -1.4232, 0.8158, 0.0477, -0.1361, -0.3442, 1.22005, 1.5295, 0.9965, 0.7854, -0.2915}; // clang-format on EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(avgpool_rank5_test) { // 3D, input is 5D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {2, 2, 3, 3, 3}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2, 2, 2}; op.padding = {0, 0, 0}; op.stride = {1, 1, 1}; op.dilations = {1, 1, 1}; std::vector data{ -0.179, -1.756, 0.651, 1.955, 1.87, -0.604, 0.247, 0.449, -0.137, 1.187, 1.593, 0.424, 2.698, -0.104, -0.069, -1.293, 0.538, 1.291, 0.974, 1.096, 0.74, -0.669, -1.08, -1.041, -1.407, 1.43, -0.211, -0.017, 0.532, 1.276, 0.627, 0.236, -0.396, -0.204, 0.501, -0.599, -1.414, -0.615, -0.274, 0.168, -0.144, 0.5, 1.42, 1.082, -0.952, -0.846, -1.244, 1.475, 1.246, 1.344, -1.722, -1.24, -0.851, 0.06, 0.507, 0.762, -0.007, -1.484, 1.028, 0.317, 1.077, -1.289, 0.875, -0.417, -0.673, 1.715, -0.307, 0.264, -0.973, 1.412, 2.561, -0.515, -0.201, 0.827, -1.231, 1.958, -0.552, 0.036, -0.993, -0.859, -1.458, -0.575, 0.048, -0.779, -1.025, -1.135, 1.166, -0.131, 0.726, 0.52, 0.467, -0.494, 0.675, 0.203, -0.63, -0.918, -0.5, -1.395, 1.39, 1.705, 0.444, -0.835, -0.506, 0.101, 0.602, 0.543, 0.357, 1.042}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 0.908, 0.250625, 0.795, 0.40425, 0.711875, 0.194875, 0.014125, 0.09425, -0.078375, 0.139375, 0.46075, 0.0285, -0.188125, -0.085, 0.378125, -0.085375, -0.04, 0.304125, 0.40775, 0.2835, 0.112375, -0.073375, 0.4355, -0.187, -0.392625, -0.258375, -0.485875, -0.0345, 0.16125, -0.131875, -0.228375, 0.068625}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(globalavgpool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 2, 2}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; auto lens = s.lens(); op.lengths = {lens[2], lens[3]}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.25, 0.575, 0.375}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(globalavgpool_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 6}, {2, 6, {2}}}}; auto x = mm->add_parameter("X", s); mm->add_instruction( migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"dyn_global", true}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 2, 2}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.25, 0.575, 0.375}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(globallppool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 2, 2}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::lpnorm}; auto lens = s.lens(); op.lengths = {lens[2], lens[3]}; op.lp_order = 2; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.5477225575051662, 1.307669683062202, 0.9327379053088815}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(globallppool_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 6, {2}}, {2, 6, {2}}}}; auto x = mm->add_parameter("X", s); mm->add_instruction( migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"dyn_global", true}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 2, 2}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.5477225575051662, 1.307669683062202, 0.9327379053088815}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(globalmaxpool_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 2, 2}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; auto lens = s.lens(); op.lengths = {lens[2], lens[3]}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4, 0.9, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(globalmaxpool_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 1}, {3, 3}, {2, 6, {2}}, {2, 6, {2}}}}; auto x = mm->add_parameter("X", s); mm->add_instruction( migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"dyn_global", true}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 2, 2}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4, 0.9, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(lppool_l1_norm_test) { // L1 norm test migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::lpnorm}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {1}; op.lp_order = 1; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.5, 0.6, 0.5, 1.3, 1.4, 1.0, 0.8, 0.8, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } // TODO: this tests compliance with a oneDNN rule and a feature that's commented out // in pooling.hpp // TEST_CASE(lppool_l1_norm_err_test) // { // // padding too large for kernel size // migraphx::program p; // auto* mm = p.get_main_module(); // auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 5}}; // auto op = migraphx::op::pooling{migraphx::op::pooling_mode::lpnorm}; // op.lengths = {3}; // op.padding = {2}; // op.stride = {1}; // op.dilations = {1}; // op.lp_order = 1; // std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7}; // auto l0 = mm->add_literal(migraphx::literal{s, data}); // EXPECT(test::throws([&] { // mm->add_instruction(op, l0); // })); // } TEST_CASE(lppool_l2_norm_test) { // L2 norm test migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::lpnorm}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {1}; op.lp_order = 2; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.36055512754639896, 0.447213595499958, 0.4123105625617661, 0.9433981132056605, 1.0295630140987, 0.9055385138137417, 0.7071067811865475, 0.7071067811865475, 0.6082762530298219}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(lppool_l2_norm_test_asym_padding) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::lpnorm}; op.lengths = {2, 1}; op.padding = {1, 0, 0, 0}; op.stride = {1, 1}; op.dilations = {1, 1}; op.lp_order = 2; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6, 0.7, 0.5, 0.1, 0.2, 0.11, 0.2, 0.3, 0.4}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.3, 0.2, 0.4, 0.1, 0.8544004, 0.53851646, 0.98488575, 0.14142136, 0.8062258, 0.86023253, 0.9055385, 0.60827625, 0.70710677, 0.86023253, 0.14142136, 0.6324555, 0.70859015, 0.53851646, 0.31622776, 0.44721362}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(lppool_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}}}; auto x = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"lengths", {2}}, {"padding", {0}}, {"stride", {1}}, {"dilations", {1}}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 4}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.36055512754639896, 0.447213595499958, 0.4123105625617661, 0.9433981132056605, 1.0295630140987, 0.9055385138137417, 0.7071067811865475, 0.7071067811865475, 0.6082762530298219}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { -2.1314404, -1.63041711, 1.54562736, 1.04625261, -1.42931843, -0.48703974, 0.4065806, -0.1524526, 1.30775225, 0.45538983, -0.06631992, -1.75332725, 1.33493888, 0.47327688, 0.36873096, 1.18358743, -0.34640595, 1.22098756, 0.01946825, -0.20238149, 0.43348005, -0.67991608, -0.83041084, 0.93537551, 0.70241445, -0.5654031, -1.30899191, -0.26735824, -0.52444768, 1.99097753, 1.86504853, -0.26506025, 0.26236168, 0.43763575, 0.95300823, -1.02733946, -0.74655169, -0.5374338, -0.28901565, -0.59789604, 0.5310151, 0.99125904, 0.40609556, -1.57175648, 0.22031412, 1.45862222, 0.53217483, 1.39087725, 1.00170159, -0.87175864, -1.7204628, -1.72008383, -0.38656762, -0.01443311, 1.46645272, -1.39995027, 0.22505587, -0.43461126, -0.05511411, -0.79950953, -0.01439556, 0.08795211, 1.18943918, -0.84079367, -1.73383629, -0.55662078, -0.30626822, -0.67339015, 0.44179603, 0.54316711, 0.40899998, -0.27831686, -1.11900508, -0.0881724, 0.35483059, 2.36277103, -0.04765317, -0.36865309, 0.73814237, 1.47151589, 1.36546791, -0.32649881, -1.0517807, 2.24768877, 0.68883753, 0.58646208, -0.91017133, -0.50462508, -0.4013325, -0.72348958, -0.47368807, 0.35285577, -1.01817429, -0.5152272, 0.60321307, 0.43521205, -0.23733577, 0.66427642, 0.82949388, 0.82443929, 0.71550399, 0.34561086, 0.68570769, -0.40718508, -1.20350206, 0.15793853, -2.31013632, -0.07934658, -0.09348056, 0.36576006, 2.46601582, 0.11090943, 0.9144392, 0.56759721, -0.22112127, -0.21955389, 0.72474903, -1.28448462, 1.53285873, 0.37437943, 0.31409341, 1.95433736, 0.91620457, 0.86205518, 1.24365854, 0.19248386, 0.22526583, 0.13462132, -0.27561715, -2.06446075, -0.02306402, -1.38278747, 1.1411345, 1.31293464, -1.86041689, 1.06763375, -0.26541466, 1.4545635, 1.11430049, -0.66491818, 0.87101674, 0.67768967, -1.02062869, -1.05031872, -2.2764678, -2.0200038, 0.37592548, -0.26701379, -0.83388507, 0.19403623, 1.00968623, 0.11020003, 1.16736257, -1.1160326, 0.47346735, 0.6126079, -0.19135755, 1.33624589, -0.29802522, -0.57873946, -1.06555879, -0.20686582, 1.36892557, -0.19937795, 0.8649236, -1.40126073, 1.53441942, 0.34682792, -1.31724346, -1.32898355, 2.40126371, 0.07845283, 1.35732043, -0.63678312, 0.39429256, -1.36487007, -0.31026676, -0.44981545, -0.28994772, -0.14657612, -1.75206447, -0.70612341, 1.20071781, -1.64647579, -0.7133292, 0.88494766, 0.52119428, -2.77387547, 2.07681108, -0.90133125, 0.2847338, 0.6174528, -0.20616426, -0.64263535, -1.08496261, 0.54275119, -0.88503587, 0.6629802, 1.47319221, -1.05829155, -0.97027361, -0.93187737, -1.39954746, -0.52359426, -0.14743951, 1.51522756, 0.2078452, -1.28156149, -1.19363916, -0.78680223, -0.89094824, 1.30212069, -0.77974445, -0.58411664, 0.48764706, -0.67132682}; migraphx::shape a_shape{migraphx::shape::float_type, {2, 3, 6, 6}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {2, 2}}, {"lengths", {3, 2}}, {"dilations", {1, 1}}}), al); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(36); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = { 1.33493888, 1.54562736, 1.22098756, 1.33493888, 1.18358743, 1.99097753, 1.00170159, 1.45862222, 1.39087725, 1.46645272, 1.18943918, -0.01443311, 1.47151589, 2.36277103, 2.24768877, 0.68883753, 0.82949388, 0.71550399, 1.95433736, 2.46601582, 1.53285873, 1.95433736, 1.06763375, 1.4545635, 1.33624589, 1.16736257, 0.6126079, 1.36892557, 2.40126371, 1.53441942, 0.52119428, 2.07681108, 0.88494766, 1.51522756, 0.54275119, 0.6629802}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_pad_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {-6, -5, -4, -3, -5, -1, 0, 1, 2, 3, 4, 5}; migraphx::shape a_shape{migraphx::shape::float_type, {1, 2, 3, 2}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {1, 1}}, {"stride", {2, 2}}, {"lengths", {3, 2}}, {"dilations", {1, 1}}}), al); // * * * * * * * * // * -6 -5 * * 0 1 * // * -4 -3 * padding will look like this * 2 3 * // * -5 -1 * and this * 4 5 * // * * * * The * values are actually -INF * * * * p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(8); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-4, -3, -4, -1, 2, 3, 4, 5}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_rank3_test0) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {1}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.3, 0.4, 0.4, 0.8, 0.9, 0.9, 0.7, 0.7, 0.6}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_rank3_test1) { // 1D case 2, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {2, 2, 5}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2}; op.padding = {0}; op.stride = {2}; op.dilations = {1}; std::vector data{0.4975, -0.1226, -0.0405, -0.2861, -0.1227, -0.6186, -0.9618, 0.6022, -0.1912, 1.1925, 0.5493, 0.1692, -0.8039, -1.0281, 0.9907, 0.477, 1.5001, -1.1603, -1.361, 1.2556}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4975, -0.0405, -0.6186, 0.6022, 0.5493, -0.8039, 1.5001, -1.1603}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_rank3_test2) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {2}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4, 0.2, 0.9, 0.5, 0.1, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_rank3_test4) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2}; op.padding = {1}; op.stride = {1}; op.dilations = {3}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4, 0.3, 0.2, 0.9, 0.8, 0.5, 0.1, 0.6, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_rank3_ceil_test) { // 1D case 2, input is 3D, ceil mode migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {2, 2, 5}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2}; op.padding = {0}; op.stride = {2}; op.dilations = {1}; op.ceil_mode = true; // clang-format off std::vector data{0.4975, -0.1226, -0.0405, -0.2861, -0.1227, -0.6186, -0.9618, 0.6022, -0.1912, 1.1925, 0.5493, 0.1692, -0.8039, -1.0281, 0.9907, 0.477, 1.5001, -1.1603, -1.361, 1.2556}; // clang-format on auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold{0.4975, -0.0405, -0.1227, -0.6186, 0.6022, 1.1925, 0.5493, -0.8039, 0.9907, 1.5001, -1.1603, 1.2556}; // clang-format on EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_rank5_test) { // 3D, input is 5D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {2, 2, 3, 3, 3}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2, 2, 2}; op.padding = {0, 0, 0}; op.stride = {2, 2, 2}; op.dilations = {1, 1, 1}; std::vector data{ -2.8029, 0.5861, 0.7015, 0.1297, -1.44, -1.9472, 0.7812, 2.408, -0.3145, 0.3405, -0.9146, 0.0624, 1.5064, -0.8345, 1.7977, 1.8949, 1.0073, -0.2102, -0.042, -0.7146, 0.6227, -0.5263, -2.2598, 0.1713, 0.449, 0.5303, -0.8622, -0.5691, 0.907, -0.0569, -1.5348, -0.4109, -0.1461, -0.5445, 0.4266, 0.2282, 1.3655, -2.1519, 0.6068, -0.2001, -0.4702, 0.3864, 1.7083, 0.9096, 0.4286, -1.8866, 0.7034, 0.0293, 1.4587, 0.7672, -2.8614, 0.8124, -0.053, 1.0449, 0.845, -0.0131, 0.1139, -0.859, -1.2681, -0.6337, -0.4644, 0.1938, 0.2889, 0.9035, 0.7118, -0.5767, 0.4577, -0.0549, 0.2237, 0.5756, 0.0677, -0.0223, -0.329, 0.2364, 2.7666, -0.7417, -1.3196, -0.2655, 0.1698, -0.1777, -0.9427, 2.6859, -0.7501, 0.5175, 1.0029, -2.6436, -0.4388, -1.2348, -0.1539, -0.6229, -0.4136, 0.5085, 0.4136, -0.6439, -1.1953, -0.406, -0.0195, 0.1869, -0.8664, 1.1364, 0.5041, 0.0647, 0.1941, -1.0819, -0.4629, -0.5107, 0.3612, -0.3583}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1.5064, 1.3655, 0.9035, 2.6859}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}}}; auto x = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"lengths", {2}}, {"padding", {0}}, {"stride", {1}}, {"dilations", {1}}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 4}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.3, 0.4, 0.4, 0.8, 0.9, 0.9, 0.7, 0.7, 0.6}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_dyn_test2) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}}}; auto x = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"lengths", {2}}, {"padding", {0}}, {"stride", {1}}, {"dilations", {2}}}), x); p.compile(migraphx::make_target("ref")); std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 3, 4}}; migraphx::parameter_map params; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4, 0.2, 0.9, 0.5, 0.1, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/pow.cpp000066400000000000000000000063101510465702400176040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(pow_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data = {1, 2, 3}; auto b = mm->add_literal(migraphx::literal{s, data}); auto e = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("pow"), b, e); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return std::pow(n, n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(pow_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto b = mm->add_parameter("b", s); auto e = mm->add_parameter("e", s); mm->add_instruction(migraphx::make_op("pow"), b, e); p.compile(migraphx::make_target("ref")); std::vector data = {1, 2, 3}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["b"] = migraphx::argument(input_fixed_shape0, data.data()); params0["e"] = migraphx::argument(input_fixed_shape0, data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return std::pow(n, n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/prefix_scan_sum.cpp000066400000000000000000000315301510465702400221660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(prefix_scan_sum_1d) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {6}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1.0, 3.0, 6.0, 10.0, 15.0, 21.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_dyn_1d) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{5, 8}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", false}}), input); p.compile(migraphx::make_target("ref")); std::vector a = {1, 2, 3, 4, 5, 6}; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {6}}; migraphx::parameter_map params0; params0["X"] = migraphx::argument(input_fixed_shape0, a.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1.0, 3.0, 6.0, 10.0, 15.0, 21.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_2d_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 3.0, 6.0, 9.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_2d_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_3d_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 2.0, 4.0, 6.0, 2.0, 4.0, 6.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_3d_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 3.0, 6.0, 9.0, 1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 3.0, 6.0, 9.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_3d_3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 2}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_exclusive_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {8}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 1, 2, 3, 4}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", true}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.0, 1.0, 3.0, 6.0, 10.0, 11.0, 13.0, 16.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_exclusive_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", true}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 2.0, 4.0, 6.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_exclusive_reverse) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {6}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6}}; auto l0 = mm->add_literal(input); mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", true}, {"reverse", true}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{20.0, 18.0, 15.0, 11.0, 6.0, 0.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_negative_axis_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", -3}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 2.0, 4.0, 6.0, 2.0, 4.0, 6.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_negative_axis_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", -2}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 3.0, 6.0, 9.0, 1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 3.0, 6.0, 9.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_negative_axis_3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3, 3}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", -1}, {"exclusive", false}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ 1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0, 1.0, 3.0, 6.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_reverse_1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {8}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 1, 2, 3, 4}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", false}, {"reverse", true}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{20.0, 19.0, 17.0, 14.0, 10.0, 9.0, 7.0, 4.0}; EXPECT(results_vector == gold); } TEST_CASE(prefix_scan_sum_reverse_2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 1, 2, 3, 4}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", false}, {"reverse", true}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2.0, 4.0, 6.0, 8.0, 1.0, 2.0, 3.0, 4.0}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/prelu.cpp000066400000000000000000000061271510465702400201340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(prelu_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_literal(migraphx::literal{s, {-1, 0, 2}}); auto slope = mm->add_literal(migraphx::literal{s, {2, 1, 2}}); mm->add_instruction(migraphx::make_op("prelu"), x, slope); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2.0f, 0.0f, 2.0f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(prelu_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto slope = mm->add_parameter("slope", s); mm->add_instruction(migraphx::make_op("prelu"), x, slope); p.compile(migraphx::make_target("ref")); std::vector x_data{-1, 0, 2}; std::vector slope_data{2, 1, 2}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["slope"] = migraphx::argument(input_fixed_shape0, slope_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2.0f, 0.0f, 2.0f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/quant_convolution.cpp000066400000000000000000000135601510465702400225730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(quant_conv2d_padding_stride_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int8_type, {2, 3, 4, 4}}; std::vector a(2 * 3 * 4 * 4); std::iota(a.begin(), a.end(), 0); auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape c_shape{migraphx::shape::int8_type, {2, 3, 3, 3}}; std::vector c(2 * 3 * 3 * 3); std::iota(c.begin(), c.end(), 0); auto cl = mm->add_literal(migraphx::literal{c_shape, c}); mm->add_instruction( migraphx::make_op("quant_convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}}), al, cl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {4521, 7014, 7830, 11952, 10515, 16734, 19737, 30906, 13161, 19542, 19494, 28800, 34707, 52590, 54729, 82746}; std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(quant_conv2d_padding_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int8_type, {2, 3, 4, 4}}; std::vector a(2 * 3 * 4 * 4); std::iota(a.begin(), a.end(), 0); auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape c_shape{migraphx::shape::int8_type, {2, 3, 3, 3}}; std::vector c(2 * 3 * 3 * 3); std::iota(c.begin(), c.end(), 0); auto cl = mm->add_literal(migraphx::literal{c_shape, c}); mm->add_instruction( migraphx::make_op("quant_convolution", {{"padding", {1, 1}}, {"stride", {1, 1}}}), al, cl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = { 4521, 6753, 7014, 4635, 6858, 10197, 10548, 6939, 7830, 11601, 11952, 7839, 5007, 7383, 7590, 4953, 10515, 15987, 16734, 11277, 16821, 25506, 26586, 17874, 19737, 29826, 30906, 20718, 13593, 20505, 21198, 14187, 13161, 19281, 19542, 12699, 18522, 27045, 27396, 17739, 19494, 28449, 28800, 18639, 11919, 17319, 17526, 11289, 34707, 51843, 52590, 34893, 51813, 77346, 78426, 52002, 54729, 81666, 82746, 54846, 36057, 53769, 54462, 36075}; std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(quant_conv2d_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::int8_type, {2, 3, 4, 4}}; std::vector a(2 * 3 * 4 * 4); std::iota(a.begin(), a.end(), 0); auto al = mm->add_literal(migraphx::literal{a_shape, a}); migraphx::shape c_shape{migraphx::shape::int8_type, {2, 3, 3, 3}}; std::vector c(2 * 3 * 3 * 3); std::iota(c.begin(), c.end(), 0); auto cl = mm->add_literal(migraphx::literal{c_shape, c}); mm->add_instruction(migraphx::make_op("quant_convolution"), al, cl); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector gold = {10197, 10548, 11601, 11952, 25506, 26586, 29826, 30906, 27045, 27396, 28449, 28800, 77346, 78426, 81666, 82746}; std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/quantizelinear.cpp000066400000000000000000000164571510465702400220470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(quantizelinear_1) { migraphx::shape xs{migraphx::shape::float_type, {2, 3, 3}}; std::vector xv = { -300, 600, 129, -1000, 4, 3, -6, 600, 550, -300, 600, 129, -1000, 4, 3, -6, 600, 550}; migraphx::shape ss{migraphx::shape::float_type, {2, 3, 3}}; std::vector sv = {2, 2, 2, 4, 4, 4, 6, 6, 6, 2, 2, 2, 4, 4, 4, 6, 6, 6}; migraphx::shape zs{migraphx::shape::int8_type, {2, 3, 3}}; std::vector zv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(ss, sv); auto z = mm->add_literal(zs, zv); mm->add_instruction(migraphx::make_op("quantizelinear"), x, s, z); return p; }; migraphx::program p1 = create_program(); p1.compile(migraphx::make_target("ref")); auto result = p1.eval({}).back(); std::vector results_vector(18); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{ -128, 127, 64, -128, 1, 1, -1, 100, 92, -128, 127, 64, -128, 1, 1, -1, 100, 92}; EXPECT(results_vector == gold); } TEST_CASE(quantizelinear_2) { migraphx::shape xs{migraphx::shape::float_type, {2, 3, 3}}; std::vector xv = { -300, 600, 129, -1000, 4, 3, -6, 600, 550, -300, 600, 129, -1000, 4, 3, -6, 600, 550}; migraphx::shape ss{migraphx::shape::float_type, {2, 3, 3}}; std::vector sv = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(ss, sv); mm->add_instruction(migraphx::make_op("quantizelinear"), x, s); return p; }; migraphx::program p1 = create_program(); p1.compile(migraphx::make_target("ref")); auto result = p1.eval({}).back(); std::vector results_vector(18); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0, 255, 64, 0, 2, 2, 0, 255, 255, 0, 255, 64, 0, 2, 2, 0, 255, 255}; EXPECT(results_vector == gold); } template static void quantizelinear_fp8e4m3() { migraphx::shape xs{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape zs{migraphx::shape::get_type{}, {2, 2, 2}}; std::vector xv = {0.5, 0.75, -0.4375, 0.6875, -0.9375, -0.9375, 0.625, -0.5625}; std::vector sv = {0.25, 0.75, 0.5625, 0.4375, 0.8125, -0.6875, 0.875, -0.0625}; std::vector tmp = {0.6875, 0.75, -0.75, 0.5, -0.0625, 0.0625, -0.375, 0.25}; std::vector zero_pts; std::transform( tmp.begin(), tmp.end(), std::back_inserter(zero_pts), [](auto x) { return DType(x); }); auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(xs, sv); auto z = mm->add_literal(zs, zero_pts); mm->add_instruction(migraphx::make_op("quantizelinear"), x, s, z); return p; }; migraphx::program p1 = create_program(); p1.compile(migraphx::make_target("ref")); auto result = p1.eval({}).back(); std::vector results_vector(8); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold; auto min_value = std::numeric_limits::lowest(); auto max_value = std::numeric_limits::max(); for(int i = 0; i < xv.size(); ++i) { double quantized = xv.at(i) / sv.at(i) + zero_pts.at(i); quantized = std::max(static_cast(min_value), std::min(static_cast(max_value), quantized)); gold.push_back(DType(quantized)); } EXPECT(results_vector == gold); } TEST_CASE_REGISTER(quantizelinear_fp8e4m3); TEST_CASE_REGISTER(quantizelinear_fp8e4m3); template static void quantizelinear_fp8e5m2() { migraphx::shape xs{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape zs{migraphx::shape::get_type{}, {2, 2, 2}}; std::vector xv = {0.5, 0.75, -0.4375, 0.625, -0.875, -0.875, 0.625, -0.5}; std::vector sv = {0.25, 0.75, 0.5, 0.4375, 0.875, -0.625, 0.875, -0.0625}; std::vector tmp = {0.6875, 0.75, -0.75, 0.5, -0.0625, 0.0625, -0.375, 0.25}; std::vector zero_pts; std::transform( tmp.begin(), tmp.end(), std::back_inserter(zero_pts), [](auto x) { return DType(x); }); auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(xs, sv); auto z = mm->add_literal(zs, zero_pts); mm->add_instruction(migraphx::make_op("quantizelinear"), x, s, z); return p; }; migraphx::program p1 = create_program(); p1.compile(migraphx::make_target("ref")); auto result = p1.eval({}).back(); std::vector results_vector(8); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold; auto min_value = std::numeric_limits::lowest(); auto max_value = std::numeric_limits::max(); for(int i = 0; i < xv.size(); ++i) { double quantized = xv.at(i) / sv.at(i); quantized = std::max(static_cast(min_value), std::min(static_cast(max_value), quantized)); gold.push_back(DType(quantized + zero_pts.at(i))); } EXPECT(results_vector == gold); } TEST_CASE_REGISTER(quantizelinear_fp8e5m2); TEST_CASE_REGISTER(quantizelinear_fp8e5m2); ROCm-AMDMIGraphX-46524e8/test/ref/random_seed.cpp000066400000000000000000000041751510465702400212660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include /** * Reference test for the random_seed operation */ TEST_CASE(random_seed_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_instruction(migraphx::make_op("random_seed")); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec1(1); result.visit([&](auto output) { result_vec1.assign(output.begin(), output.end()); }); std::vector result_vec2(1); // Identical calls should give different seeds every time with 1/(2^64) chance of a repeat. // We don't analyze for true randomness. result = p.eval({}).back(); result.visit([&](auto output) { result_vec2.assign(output.begin(), output.end()); }); EXPECT(result_vec1[0] != result_vec2[0]); } ROCm-AMDMIGraphX-46524e8/test/ref/random_uniform.cpp000066400000000000000000000156711510465702400220300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include /** * Reference test for the random_uniform operation. Also invokes the random_seed operation. */ TEST_CASE(random_uniform_test) { migraphx::program p; auto* mm = p.get_main_module(); uint64_t seed(0); size_t sample_size(200); // Shape of the random data migraphx::shape rs{migraphx::shape::float_type, {1, sample_size}}; // data tensor must be allocated at this point but does not need to be initialized. std::vector data(sample_size); auto input = mm->add_literal(migraphx::literal(rs, data)); // Runtime randomization seed migraphx::shape seed_shape{migraphx::shape::uint64_type, {1}}; std::vector seed_data{seed}; auto seed_input = mm->add_literal(migraphx::literal(seed_shape, seed_data)); mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, input); p.compile(migraphx::make_target("ref")); // no params_map needed auto result = p.eval({}).back(); std::vector result_vec(sample_size); result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); // Compare result with the STL's mt19937 generator std::mt19937 gen(seed); std::uniform_real_distribution<> dis(0.0, 1.0); std::vector rand_samples(sample_size); std::generate(rand_samples.begin(), rand_samples.end(), [&]() { return dis(gen); }); EXPECT(migraphx::verify::verify_range_with_tolerance(result_vec, migraphx::verify::expected{rand_samples}, migraphx::verify::tolerance{0.00001})); } TEST_CASE(random_uniform_int_test) { // random uniform distribution with an integer type input shape migraphx::program p; auto* mm = p.get_main_module(); float seed(0.1); size_t sample_size(200); // Shape of the random data migraphx::shape rs{migraphx::shape::uint16_type, {1, sample_size}}; // data tensor must be allocated at this point but does not need to be initialized. std::vector data(sample_size); auto input = mm->add_literal(migraphx::literal(rs, data)); // Runtime randomization seed migraphx::shape seed_shape{migraphx::shape::float_type, {1}}; std::vector seed_data{seed}; auto seed_input = mm->add_literal(migraphx::literal(seed_shape, seed_data)); mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, input); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params0; auto result = p.eval(params0).back(); std::vector result_vec(sample_size); result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); // Compare result with the STL's mt19937 generator std::mt19937 gen(seed); std::uniform_int_distribution dis; std::vector gold_rand_samples(sample_size); std::generate(gold_rand_samples.begin(), gold_rand_samples.end(), [&]() { return dis(gen); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, gold_rand_samples)); } TEST_CASE(random_uniform_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); uint64_t seed(17); size_t sample_size(200); // Shape of the random data migraphx::shape rs{migraphx::shape::float_type, {{1, 2}, {2, sample_size + 1}}}; auto input = mm->add_parameter("Input_1", rs); // Runtime randomization seed migraphx::shape seed_shape{migraphx::shape::uint64_type, {1}}; auto seed_input = mm->add_parameter("Seed", seed_shape); mm->add_instruction(migraphx::make_op("random_uniform", {}), seed_input, input); p.compile(migraphx::make_target("ref")); // Create a dummy input to hold the random data migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {sample_size}}; migraphx::parameter_map params0; params0["Input_1"] = migraphx::argument(input_fixed_shape1); std::vector seed_data = {seed}; params0["Seed"] = migraphx::argument(seed_shape, seed_data.data()); auto result = p.eval(params0).back(); std::vector result_vec(sample_size); result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); // Compare result with the STL's mt19937 generator std::mt19937 gen(seed); std::uniform_real_distribution<> dis(0.0, 1.0); std::vector gold_rand_samples(sample_size); std::generate(gold_rand_samples.begin(), gold_rand_samples.end(), [&]() { return dis(gen); }); EXPECT(migraphx::verify::verify_rms_range(result_vec, gold_rand_samples)); } TEST_CASE(random_uniform_and_seed_test) { migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size(20000); // Shape of the random data migraphx::shape rs{migraphx::shape::float_type, {{1, 2}, {2, sample_size + 1}}}; auto input = mm->add_parameter("Input_1", rs); // Runtime randomization seed auto seed_input = mm->add_instruction(migraphx::make_op("random_seed")); mm->add_instruction(migraphx::make_op("random_uniform"), seed_input, input); p.compile(migraphx::make_target("ref")); // Create a dummy input to hold the random data migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {sample_size}}; migraphx::parameter_map params0; params0["Input_1"] = migraphx::argument(input_fixed_shape1); auto result = p.eval(params0).back(); result.visit([&](auto output) { EXPECT(output.size() == sample_size); }); // Do not check the content of the data since it's not repeatable } ROCm-AMDMIGraphX-46524e8/test/ref/recip.cpp000066400000000000000000000056071510465702400201110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(recip_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {3}}; std::vector data{-0.5f, 0.1f, 0.5f}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("recip"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2.0f, 10.0f, 2.0f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(recip_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("recip"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-0.5f, 0.1f, 0.5f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2.0f, 10.0f, 2.0f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/reduce_all.cpp000066400000000000000000000062241510465702400211020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reduce_all_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 2, 2}}; // clang-format off auto input = migraphx::literal{s, { -1.0, 2.0, 3.0, 1.2, 2.0, 0.0, -3.3, 0.0, -3.0, 0.0, 6.0, 0.0, 1.0, 3.0, 2.4, 3.2 }}; // clang-format on auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_all", {{"axes", {0}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 0, 1, 0}; EXPECT(results_vector == gold); } TEST_CASE(reduce_all_variable_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::bool_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_all"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; // clang-format off std::vector x_arg{ 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, }; // clang-format on pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 0, 0, 1}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/reduce_any.cpp000066400000000000000000000062241510465702400211210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reduce_any_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 2, 2}}; // clang-format off auto input = migraphx::literal{s, { -1.0, 0.0, 3.0, 1.2, 2.0, 0.0, -3.3, 0.0, -3.0, 0.0, 6.0, 0.0, 1.0, 0.0, 2.4, 3.2 }}; // clang-format on auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_any", {{"axes", {0}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 0, 1, 1}; EXPECT(results_vector == gold); } TEST_CASE(reduce_any_variable_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::bool_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_any"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; // clang-format off std::vector x_arg{ 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, }; // clang-format on pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 1, 1, 0}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/reduce_max.cpp000066400000000000000000000201601510465702400211120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reduce_max_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {0}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{9, 10, 11, 12}; EXPECT(results_vector == gold); } TEST_CASE(reduce_max_variable_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_max"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{9, 10, 11, 12}; EXPECT(results_vector == gold); } TEST_CASE(reduce_max_dynamic_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 4, {2}}, {3, 5, {3}}}}; auto input = mm->add_parameter("X", s); auto reduce_max_op = migraphx::make_op("reduce_max", {{"axes", {0}}}); mm->add_instruction(reduce_max_op, input); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 5}}; std::vector input_data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; params["X"] = migraphx::argument(input_fixed_shape, input_data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {6, 7, 8, 9, 10}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reduce_max_dynamic_variable_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {{2, 4, {2}}, {3, 5, {3}}}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_max"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; migraphx::shape x_fixed_shape{migraphx::shape::float_type, {2, 5}}; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; pm["x"] = migraphx::argument(x_fixed_shape, x_arg.data()); std::vector axes_arg{0}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {6, 7, 8, 9, 10}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reduce_max_axis01) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {0, 1}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{11, 12}; EXPECT(results_vector == gold); } TEST_CASE(reduce_max_variable_axes01) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_max"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0, 1}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{11, 12}; EXPECT(results_vector == gold); } TEST_CASE(reduce_max_axis02) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {0, 2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{10, 12}; EXPECT(results_vector == gold); } TEST_CASE(reduce_max_variable_axes02) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_max"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0, 2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{10, 12}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/reduce_mean.cpp000066400000000000000000000225571510465702400212610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reduce_mean_axis02) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{5.5, 7.5}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_variable_axes02) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_mean"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0, 2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{5.5, 7.5}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_axis1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {1}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2, 3, 6, 7, 10, 11}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_variable_axis1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_mean"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{1}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2, 3, 6, 7, 10, 11}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_axis12) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {1, 2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2.5f, 6.5f, 10.5f}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_variable_axes12) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_mean"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{1, 2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2.5, 6.5, 10.5}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_axis2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1.5f, 3.5f, 5.5f, 7.5f, 9.5f, 11.5f}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_variable_axis2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_mean"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1.5f, 3.5f, 5.5f, 7.5f, 9.5f, 11.5f}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_int) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {1, 2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2, 6, 10}; EXPECT(results_vector == gold); } TEST_CASE(reduce_mean_variable_axes12_int) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::int32_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_mean"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{1, 2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2, 6, 10}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/reduce_min.cpp000066400000000000000000000162721510465702400211210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reduce_min_axis02) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_min", {{"axes", {0, 2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 3}; EXPECT(results_vector == gold); } TEST_CASE(reduce_min_variable_axes02) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_min"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0, 2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 3}; EXPECT(results_vector == gold); } TEST_CASE(reduce_min_axis1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_min", {{"axes", {1}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 5, 6, 9, 10}; EXPECT(results_vector == gold); } TEST_CASE(reduce_min_variable_axis1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_min"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{1}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 5, 6, 9, 10}; EXPECT(results_vector == gold); } TEST_CASE(reduce_min_axis12) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_min", {{"axes", {1, 2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 5, 9}; EXPECT(results_vector == gold); } TEST_CASE(reduce_min_variable_axes12) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_min"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{1, 2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 5, 9}; EXPECT(results_vector == gold); } TEST_CASE(reduce_min_dynamic_variable_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {{2, 4, {2}}, {3, 5, {3}}}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_min"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; migraphx::shape x_fixed_shape{migraphx::shape::float_type, {2, 5}}; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; pm["x"] = migraphx::argument(x_fixed_shape, x_arg.data()); std::vector axes_arg{0}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {1, 2, 3, 4, 5}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/reduce_prod.cpp000066400000000000000000000057531510465702400213040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reduce_prod_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 3, 2, 3}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_prod", {{"axes", {0}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{6, 18, 12, 18}; EXPECT(results_vector == gold); } TEST_CASE(reduce_prod_variable_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {4, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_prod"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 3, 2, 3}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{6, 18, 12, 18}; EXPECT(results_vector == gold); } ROCm-AMDMIGraphX-46524e8/test/ref/reduce_sum.cpp000066400000000000000000000306141510465702400211360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reduce_sum_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{15, 18, 21, 24}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_variable_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_sum"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{15, 18, 21, 24}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_axis02) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{33, 45}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_variable_axes02) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_sum"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{0, 2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{33, 45}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_axis1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{4, 6, 12, 14, 20, 22}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_variable_axis1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_sum"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{1}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{4, 6, 12, 14, 20, 22}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_axis12) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1, 2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{10, 26, 42}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_variable_axes12) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {2}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_sum"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{1, 2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{10, 26, 42}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_axis2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; auto input = migraphx::literal{s, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}; auto l0 = mm->add_literal(input); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{3, 7, 11, 15, 19, 23}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_variable_axis2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {3, 2, 2}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_sum"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; pm["x"] = migraphx::argument(x_shape, x_arg.data()); std::vector axes_arg{2}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{3, 7, 11, 15, 19, 23}; EXPECT(results_vector == gold); } TEST_CASE(reduce_sum_dynamic_variable_axis0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_shape{migraphx::shape::float_type, {{2, 4, {2}}, {3, 5, {3}}}}; auto x = mm->add_parameter("x", x_shape); migraphx::shape axes_shape{migraphx::shape::int64_type, {1}}; auto axes = mm->add_parameter("axes", axes_shape); mm->add_instruction(migraphx::make_op("reduce_sum"), x, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pm; migraphx::shape x_fixed_shape{migraphx::shape::float_type, {2, 5}}; std::vector x_arg{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; pm["x"] = migraphx::argument(x_fixed_shape, x_arg.data()); std::vector axes_arg{0}; pm["axes"] = migraphx::argument(axes_shape, axes_arg.data()); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {7, 9, 11, 13, 15}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reduce_sum_variable_dynamic_empty_axes) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; std::vector input_data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; auto input = migraphx::literal{s, input_data}; auto l0 = mm->add_literal(input); const std::vector axes_dynamic_dims{{0, 3}}; migraphx::shape axes_dynamic_shape{migraphx::shape::int64_type, axes_dynamic_dims}; auto axes = mm->add_parameter("axes", axes_dynamic_shape); migraphx::parameter_map pm; migraphx::shape axes_shape{migraphx::shape::int64_type, {0}}; std::vector axes_data; pm["axes"] = migraphx::argument(axes_shape, axes_data.data()); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {}}}), l0, axes); p.compile(migraphx::make_target("ref")); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(results_vector == input_data); } TEST_CASE(reduce_sum_variable_empty_axes) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2, 2}}; std::vector input_data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; auto input = migraphx::literal{s, input_data}; auto l0 = mm->add_literal(input); migraphx::shape axes_shape{migraphx::shape::int64_type, {0}}; auto axes = mm->add_parameter("axes", axes_shape); migraphx::parameter_map pm; std::vector axes_data; pm["axes"] = migraphx::argument(axes_shape, axes_data.data()); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {}}}), l0, axes); p.compile(migraphx::make_target("ref")); auto result = p.eval(pm).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(results_vector == input_data); } ROCm-AMDMIGraphX-46524e8/test/ref/relu.cpp000066400000000000000000000055211510465702400177510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(relu_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l = mm->add_literal(migraphx::literal{s, {-1.f, 0.f, 1.f}}); mm->add_instruction(migraphx::make_op("relu"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.f, 0.f, 1.f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(relu_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("relu"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-1.f, 0.f, 1.f}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.f, 0.f, 1.f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/reshape.cpp000066400000000000000000000263461510465702400204410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reshape_lazy_test0) { migraphx::shape a_shape{migraphx::shape::float_type, {24, 1, 1, 1}}; std::vector data(24); std::iota(data.begin(), data.end(), -3); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, data}); std::vector new_shape = {8, 3, 1, 1}; mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, data)); } TEST_CASE(reshape_lazy_test1) { migraphx::shape a_shape{migraphx::shape::float_type, {24, 1, 1, 1}}; std::vector data(24); std::iota(data.begin(), data.end(), -3); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, data}); std::vector new_shape = {1, 3, 4, 2}; mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, data)); } TEST_CASE(reshape_lazy_test2) { migraphx::shape a_shape{migraphx::shape::float_type, {24, 1, 1, 1}}; std::vector data(24); std::iota(data.begin(), data.end(), -3); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, data}); std::vector new_shape = {1, 2, 3, 4}; mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, data)); } TEST_CASE(reshape_lazy_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; std::vector new_shape = {0, 8, 3, 1}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("reshape_lazy", {{"dims", new_shape}}), input); p.compile(migraphx::make_target("ref")); std::vector data(48); std::iota(data.begin(), data.end(), -3); migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 24, 1, 1}}; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, data)); } TEST_CASE(reshape_test0) { migraphx::shape a_shape{migraphx::shape::float_type, {24, 1, 1, 1}}; std::vector gold(24); std::iota(gold.begin(), gold.end(), -3); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, gold}); std::vector new_shape = {8, 3, 1, 1}; mm->add_instruction(migraphx::make_op("reshape", {{"dims", new_shape}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reshape_test1) { migraphx::shape a_shape{migraphx::shape::float_type, {24, 1, 1, 1}}; std::vector gold(24); std::iota(gold.begin(), gold.end(), -3); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, gold}); std::vector new_shape = {1, 3, 4, 2}; mm->add_instruction(migraphx::make_op("reshape", {{"dims", new_shape}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reshape_test2) { migraphx::shape a_shape{migraphx::shape::float_type, {24, 1, 1, 1}}; std::vector gold(24); std::iota(gold.begin(), gold.end(), -3); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, gold}); std::vector new_shape = {1, 2, 3, 4}; mm->add_instruction(migraphx::make_op("reshape", {{"dims", new_shape}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reshape_dyn_1in_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; std::vector new_shape = {0, 8, 3, 1}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("reshape", {{"dims", new_shape}}), input); p.compile(migraphx::make_target("ref")); std::vector gold(48); std::iota(gold.begin(), gold.end(), -3); migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 24, 1, 1}}; params["X"] = migraphx::argument(input_fixed_shape, gold.data()); auto result = p.eval(params).back(); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reshape_2in_test0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_in{migraphx::shape::float_type, {{1, 4}, {24, 24}, {1, 1}, {1, 1}}}; migraphx::shape s_out{migraphx::shape::float_type, {{1, 4}, {6, 6}, {4, 4}, {1, 1}}}; auto input = mm->add_parameter("X", s_in); auto output_buffer = mm->add_parameter("Y", s_out); mm->add_instruction(migraphx::make_op("reshape"), input, output_buffer); p.compile(migraphx::make_target("ref")); std::vector gold(48); std::iota(gold.begin(), gold.end(), -3.); std::vector buffer(48); std::iota(buffer.begin(), buffer.end(), 0.); migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 24, 1, 1}}; migraphx::shape output_fixed_shape{migraphx::shape::float_type, {2, 6, 4, 1}}; params["X"] = migraphx::argument(input_fixed_shape, gold.data()); params["Y"] = migraphx::argument(output_fixed_shape, buffer.data()); auto result = p.eval(params).back(); EXPECT(result.get_shape() == output_fixed_shape); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reshape_2in_test1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_in{migraphx::shape::float_type, {2, 24, 1, 1}}; migraphx::shape s_out{migraphx::shape::float_type, {{2, 4}, {6, 6}, {2, 4}, {1, 1}}}; auto input = mm->add_parameter("X", s_in); auto output_buffer = mm->add_parameter("Y", s_out); mm->add_instruction(migraphx::make_op("reshape"), input, output_buffer); p.compile(migraphx::make_target("ref")); std::vector gold(48); std::iota(gold.begin(), gold.end(), -3.); std::vector buffer(48); std::iota(buffer.begin(), buffer.end(), 0.); migraphx::parameter_map params; migraphx::shape output_fixed_shape{migraphx::shape::float_type, {2, 6, 4, 1}}; params["X"] = migraphx::argument(s_in, gold.data()); params["Y"] = migraphx::argument(output_fixed_shape, buffer.data()); auto result = p.eval(params).back(); EXPECT(result.get_shape() == output_fixed_shape); std::vector results_vector{}; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reshape_2in_elements_runtime_error) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_in{migraphx::shape::float_type, {2, 24, 1, 1}}; migraphx::shape s_out{migraphx::shape::float_type, {{2, 4}, {6, 6}, {2, 4}, {1, 1}}}; auto input = mm->add_parameter("X", s_in); auto output_buffer = mm->add_parameter("Y", s_out); mm->add_instruction(migraphx::make_op("reshape"), input, output_buffer); p.compile(migraphx::make_target("ref")); std::vector gold(48); std::iota(gold.begin(), gold.end(), -3.); std::vector buffer(48); std::iota(buffer.begin(), buffer.end(), 0.); migraphx::parameter_map params; // elements do not match up migraphx::shape output_fixed_shape{migraphx::shape::float_type, {2, 6, 2, 1}}; params["X"] = migraphx::argument(s_in, gold.data()); params["Y"] = migraphx::argument(output_fixed_shape, buffer.data()); EXPECT(test::throws([&] { std::ignore = p.eval(params).back(); })); } ROCm-AMDMIGraphX-46524e8/test/ref/resize.cpp000066400000000000000000000554341510465702400203130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include TEST_CASE(resize_test_1) { // batch size 1, 1 color channel, resize 3x3 to 5x8 migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape size_input{migraphx::shape::int32_type, {4}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output // non-matching sizes/scales attributes are ignored for 2 input arguments mm->add_instruction(migraphx::make_op("resize", {{"sizes", {1}}, {"scales", {1}}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "half_pixel"}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(1 * 1 * 5 * 8); // clang-format off std::vector golden = { 0.5f, 0.5f, 0.5f, 0.5f, 1.5f, 1.5f, 1.5f, 2.5f, 0.5f, 0.5f, 0.5f, 0.5f, 1.5f, 1.5f, 1.5f, 2.5f, 3.5f, 3.5f, 3.5f, 3.5f, 4.5f, 4.5f, 4.5f, 5.5f, 3.5f, 3.5f, 3.5f, 3.5f, 4.5f, 4.5f, 4.5f, 5.5f, 6.5f, 6.5f, 6.5f, 6.5f, 7.5f, 7.5f, 7.5f, 8.5f }; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_test_2) { // nearest_mode= "round_prefer_floor" coordinate_transformation_mode= "pytorch_half_pixel" migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape size_input{migraphx::shape::int32_type, {4}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output mm->add_instruction( migraphx::make_op("resize", {{"nearest_mode", "round_prefer_floor"}, {"coordinate_transformation_mode", "pytorch_half_pixel"}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(1 * 1 * 5 * 8); // clang-format off std::vector golden = { 0.5f, 0.5f, 0.5f, 1.5f, 1.5f, 2.5f, 2.5f, 2.5f, 0.5f, 0.5f, 0.5f, 1.5f, 1.5f, 2.5f, 2.5f, 2.5f, 3.5f, 3.5f, 3.5f, 4.5f, 4.5f, 5.5f, 5.5f, 5.5f, 6.5f, 6.5f, 6.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 6.5f, 6.5f, 6.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f }; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_test_3) { // nearest_mode= "ceil" coordinate_transformation_mode= "pytorch_half_pixel" migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape size_input{migraphx::shape::int32_type, {4}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output mm->add_instruction(migraphx::make_op("resize", {{"nearest_mode", "ceil"}, {"coordinate_transformation_mode", "align_corners"}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(1 * 1 * 5 * 8); // clang-format off std::vector golden = { 0.5f, 1.5f, 1.5f, 1.5f, 2.5f, 2.5f, 2.5f, 2.5f, 3.5f, 4.5f, 4.5f, 4.5f, 5.5f, 5.5f, 5.5f, 5.5f, 3.5f, 4.5f, 4.5f, 4.5f, 5.5f, 5.5f, 5.5f, 5.5f, 6.5f, 7.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 8.5f, 6.5f, 7.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 8.5 }; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_test_4) { // nearest_mode= "ceil" coordinate_transformation_mode= "pytorch_half_pixel" migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape size_input{migraphx::shape::int32_type, {4}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output mm->add_instruction( migraphx::make_op( "resize", {{"nearest_mode", "ceil"}, {"coordinate_transformation_mode", "asymmetric"}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(1 * 1 * 5 * 8); // clang-format off std::vector golden = { 0.5f, 1.5f, 1.5f, 2.5f, 2.5f, 2.5f, 2.5f, 2.5f, 3.5f, 4.5f, 4.5f, 5.5f, 5.5f, 5.5f, 5.5f, 5.5f, 6.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 8.5f, 8.5f, 6.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 8.5f, 8.5f, 6.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 8.5f, 8.5f }; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_test_5) { // nearest_mode= "ceil" coordinate_transformation_mode= "tf_half_pixel_for_nn" migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape size_input{migraphx::shape::int32_type, {4}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output mm->add_instruction( migraphx::make_op( "resize", {{"nearest_mode", "ceil"}, {"coordinate_transformation_mode", "tf_half_pixel_for_nn"}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(1 * 1 * 5 * 8); // clang-format off std::vector golden = { 4.5f, 4.5f, 4.5f, 5.5f, 5.5f, 5.5f, 5.5f, 5.5f, 4.5f, 4.5f, 4.5f, 5.5f, 5.5f, 5.5f, 5.5f, 5.5f, 7.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 8.5f, 8.5f, 7.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 8.5f, 8.5f, 7.5f, 7.5f, 7.5f, 8.5f, 8.5f, 8.5f, 8.5f, 8.5f }; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_upsample_test_2) { // batch size 2, 1 color channel, resize 3x5 by 1.6x // same input/output as resize_upsample_f_dyn_test migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 3 * 5); std::iota(data.begin(), data.end(), 0.1); // should upscale to 2x1x4x8 migraphx::shape s{migraphx::shape::float_type, {2, 1, 3, 5}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); // scale input migraphx::shape scale_input{migraphx::shape::float_type, {4}}; std::vector scale_values = {1.0, 1.0, 1.601, 1.601}; auto a1 = mm->add_literal(migraphx::literal{scale_input, scale_values}); // a0 = input data // a1 = scales mm->add_instruction(migraphx::make_op("resize", {{"sizes", {}}, {"scales", {1}}, {"nearest_mode", "round_prefer_ceil"}, {"coordinate_transformation_mode", "half_pixel"}}), a0, a1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(2 * 1 * 4 * 8); // clang-format off std::vector golden = { 0.1f, 0.1f, 1.1f, 2.1f, 2.1f, 3.1f, 4.1f, 4.1f, 0.1f, 0.1f, 1.1f, 2.1f, 2.1f, 3.1f, 4.1f, 4.1f, 5.1f, 5.1f, 6.1f, 7.1f, 7.1f, 8.1f, 9.1f, 9.1f, 10.1f, 10.1f, 11.1f, 12.1f, 12.1f, 13.1f, 14.1f, 14.1f, 15.1f, 15.1f, 16.1f, 17.1f, 17.1f, 18.1f, 19.1f, 19.1f, 15.1f, 15.1f, 16.1f, 17.1f, 17.1f, 18.1f, 19.1f, 19.1f, 20.1f, 20.1f, 21.1f, 22.1f, 22.1f, 23.1f, 24.1f, 24.1f, 25.1f, 25.1f, 26.1f, 27.1f, 27.1f, 28.1f, 29.1f, 29.1f}; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_test_3_1_input) { // same inputs and outputs as test 1 // batch size 1, 1 color channel, resize 3x3 to 5x8 migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("resize", {{"sizes", {1, 1, 5, 8}}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "half_pixel"}}), a0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(1 * 1 * 5 * 8); // clang-format off std::vector golden = { 0.5f, 0.5f, 0.5f, 0.5f, 1.5f, 1.5f, 1.5f, 2.5f, 0.5f, 0.5f, 0.5f, 0.5f, 1.5f, 1.5f, 1.5f, 2.5f, 3.5f, 3.5f, 3.5f, 3.5f, 4.5f, 4.5f, 4.5f, 5.5f, 3.5f, 3.5f, 3.5f, 3.5f, 4.5f, 4.5f, 4.5f, 5.5f, 6.5f, 6.5f, 6.5f, 6.5f, 7.5f, 7.5f, 7.5f, 8.5 }; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_upsample_test_4_1_input) { // batch size 2, 1 color channel, resize 3x5 by 1.6x migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 3 * 5); std::iota(data.begin(), data.end(), 0.1); // should upscale to 2x1x4x8 migraphx::shape s{migraphx::shape::float_type, {2, 1, 3, 5}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("resize", {{"sizes", {}}, {"scales", {1.0, 1.0, 1.601, 1.601}}, {"nearest_mode", "round_prefer_ceil"}, {"coordinate_transformation_mode", "half_pixel"}}), a0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector res_data(2 * 1 * 4 * 8); // clang-format off std::vector golden = { 0.1f, 0.1f, 1.1f, 2.1f, 2.1f, 3.1f, 4.1f, 4.1f, 0.1f, 0.1f, 1.1f, 2.1f, 2.1f, 3.1f, 4.1f, 4.1f, 5.1f, 5.1f, 6.1f, 7.1f, 7.1f, 8.1f, 9.1f, 9.1f, 10.1f, 10.1f, 11.1f, 12.1f, 12.1f, 13.1f, 14.1f, 14.1f, 15.1f, 15.1f, 16.1f, 17.1f, 17.1f, 18.1f, 19.1f, 19.1f, 15.1f, 15.1f, 16.1f, 17.1f, 17.1f, 18.1f, 19.1f, 19.1f, 20.1f, 20.1f, 21.1f, 22.1f, 22.1f, 23.1f, 24.1f, 24.1f, 25.1f, 25.1f, 26.1f, 27.1f, 27.1f, 28.1f, 29.1f, 29.1f}; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_optimize_test) { // matcher/optimized code should produce the same result as Resize op. migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape size_input{migraphx::shape::int32_type, {4}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output // non-matching sizes/scales attributes are ignored for 2 input arguments mm->add_instruction(migraphx::make_op("resize", {{"sizes", {1}}, {"scales", {1}}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "half_pixel"}}), a0, a1); auto p2 = p; migraphx::run_passes(p, {migraphx::split_single_dyn_dim{}, migraphx::simplify_dyn_ops{}, migraphx::dead_code_elimination{}}); EXPECT(p != p2); auto result = p.eval({}).back(); p.compile(migraphx::make_target("ref")); std::vector res_data(1 * 1 * 5 * 8); // clang-format off std::vector golden = { 0.5f, 0.5f, 0.5f, 0.5f, 1.5f, 1.5f, 1.5f, 2.5f, 0.5f, 0.5f, 0.5f, 0.5f, 1.5f, 1.5f, 1.5f, 2.5f, 3.5f, 3.5f, 3.5f, 3.5f, 4.5f, 4.5f, 4.5f, 5.5f, 3.5f, 3.5f, 3.5f, 3.5f, 4.5f, 4.5f, 4.5f, 5.5f, 6.5f, 6.5f, 6.5f, 6.5f, 7.5f, 7.5f, 7.5f, 8.5f }; // clang-format on result.visit([&](auto output) { res_data.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(res_data, golden)); } TEST_CASE(resize_fail_test_1) { // invalid resize mode migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("resize", {{"scales", {0.75, 0.25, 1., 1.}}, {"mode", "invalid"}, {"coordinate_transformation_mode", "asymmetric"}}), {a0}); })); } TEST_CASE(resize_fail_test_2) { // "sizes" attribute wrong vector size migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("resize", {{"sizes", {1, 2}}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "asymmetric"}}), {a0}); })); } TEST_CASE(resize_fail_test_3) { // "scales" attribute wrong vector size migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("resize", {{"scales", {1., 2.}}, {"nearest_mode", "floor"}, {"coordinate_transformation_mode", "asymmetric"}}), {a0}); })); } TEST_CASE(resize_fail_test_4) { // invalid coordinate_transformation_mode migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::module m; mm->add_instruction(migraphx::make_op("resize", {{"scales", {1., 1., 0.75, 0.25}}, {"nearest_mode", "round_prefer_floor"}, {"coordinate_transformation_mode", "invalid"}}), {a0}); p.compile(migraphx::make_target("ref")); EXPECT(test::throws([&] { p.eval({}); })); } TEST_CASE(resize_fail_test_5) { // invalid nearest_mode migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::module m; mm->add_instruction( migraphx::make_op("resize", {{"scales", {1., 1., 0.75, 0.25}}, {"nearest_mode", "invalid"}, {"coordinate_transformation_mode", "pytorch_half_pixel"}}), {a0}); p.compile(migraphx::make_target("ref")); EXPECT(test::throws([&] { p.eval({}); })); } TEST_CASE(resize_fail_test_6) { // wrong dimension for second input migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape size_input{migraphx::shape::int32_type, {3}}; std::vector size_values = {1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("resize", {{"nearest_mode", "floor"}, {"coordinate_transformation_mode", "half_pixel"}}), a0, a1); })); } TEST_CASE(resize_fail_test_7) { // wrong rank for second input migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape size_input{migraphx::shape::int32_type, {4, 1}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("resize", {{"nearest_mode", "floor"}, {"coordinate_transformation_mode", "half_pixel"}}), a0, a1); })); } TEST_CASE(resize_fail_test_8) { // dynamic second input migraphx::program p; auto* mm = p.get_main_module(); std::vector data(3 * 3); std::iota(data.begin(), data.end(), 0.5); migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 3}}; auto a0 = mm->add_literal(migraphx::literal{s, data}); migraphx::shape::dynamic_dimension dd{4, 4}; migraphx::shape size_input{migraphx::shape::int32_type, {dd}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_parameter("Y", size_input); // a0 = input data // a1 = sizes of output EXPECT(test::throws([&] { mm->add_instruction(migraphx::make_op("resize", {{"nearest_mode", "floor"}, {"coordinate_transformation_mode", "half_pixel"}}), a0, a1); })); } ROCm-AMDMIGraphX-46524e8/test/ref/reverse.cpp000066400000000000000000000077211510465702400204610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(reverse_test_axis0) { migraphx::shape in_shape{migraphx::shape::float_type, {2, 16}}; std::vector data(32); std::iota(data.begin(), data.end(), 1); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{in_shape, data}); std::vector axes = {0}; mm->add_instruction(migraphx::make_op("reverse", {{"axes", axes}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::swap_ranges(gold.begin(), gold.begin() + 16, gold.begin() + 16); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reverse_test_axis1) { migraphx::shape in_shape{migraphx::shape::float_type, {2, 16}}; std::vector data(32); std::iota(data.begin(), data.end(), 1); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{in_shape, data}); std::vector axes = {1}; mm->add_instruction(migraphx::make_op("reverse", {{"axes", axes}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::reverse(gold.begin(), gold.begin() + 16); std::reverse(gold.end() - 16, gold.end()); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reverse_test_axis10) { migraphx::shape in_shape{migraphx::shape::float_type, {2, 16}}; std::vector data(32); std::iota(data.begin(), data.end(), 1); migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{in_shape, data}); std::vector axes = {1, 0}; mm->add_instruction(migraphx::make_op("reverse", {{"axes", axes}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::reverse(gold.begin(), gold.begin() + 16); std::reverse(gold.end() - 16, gold.end()); std::swap_ranges(gold.begin(), gold.begin() + 16, gold.begin() + 16); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/rnn_ops.cpp000066400000000000000000015413501510465702400204660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include "test.hpp" TEST_CASE(rnn_forward) { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{0.4691, 0.3185, -0.2227, 0.4423, -0.0609, -0.2803, 0.1744, 0.3146, 0.4049, -0.3973, -0.0890, -0.1636}; std::vector r_data{-0.0456, 0.1061, 0.1574, -0.4928, -0.4300, -0.1909, -0.0225, -0.2668, 0.1840, -0.4453, -0.4896, 0.1302, -0.0929, 0.3545, -0.4981, 0.0616}; std::vector bias_data{ -0.4938, 0.4355, -0.3186, 0.2094, 0.1037, -0.1071, 0.4504, -0.3990}; std::vector ih_data(num_dirct * batch_size * hidden_size, 0); std::vector input(seq_len * batch_size * input_size, 0); input[0] = input[1] = 1.0; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; float clip = 0.0f; // concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& res_hs = outputs.front(); const auto& res_lho = outputs.back(); std::vector hs_data; std::vector lho_data; res_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.37780784, 0.61055139, 0.55168478, -0.5888475, -0.37144644, 0.31708236, 0.13104209, -0.18736027, 0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477}; std::vector lho_data_gold{0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {2, batch_size, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto last_out = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_return({out_hs, last_out}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_last_output = outputs.back(); std::vector last_output_data; std::vector hs_data; arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); arg_last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{ 0.37780784, 0.61055139, 0.55168478, -0.5888475, -0.37144644, 0.31708236, 0.13104209, -0.18736027, 0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477, 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}; std::vector last_output_data_gold{0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{2, 1}; auto sql = mm->add_literal(seq_len_s, len_data); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto last_out = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_return({out_hs, last_out}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_last_output = outputs.back(); std::vector last_output_data; std::vector hs_data; arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); arg_last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{0.377808, 0.610551, 0.551685, -0.588848, -0.371446, 0.317082, 0.131042, -0.18736, 0.034457, 0.191679, -0.394683, -0.308897, 0, 0, 0, 0}; std::vector last_output_data_gold{ 0.034457, 0.191679, -0.394683, -0.308897, -0.371446, 0.317082, 0.131042, -0.18736}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); p.compile(migraphx::make_target("ref")); auto last_output = p.eval({}).back(); std::vector last_output_data; last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector last_output_data_gold{ 0.2935145, -0.23719997, -0.31123261, -0.18357255, 0., 0., 0., 0.}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // seq_len = 1 { seq_len = 1; std::vector input_1(seq_len * batch_size * input_size, 0); input_1[0] = input_1[1] = 1.0; migraphx::shape in_shape_1{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape_1, input_1}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.37780784, 0.61055139, 0.55168478, -0.5888475, -0.37144644, 0.31708236, 0.13104209, -0.18736027}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(rnn_forward_layout) { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{0.4691, 0.3185, -0.2227, 0.4423, -0.0609, -0.2803, 0.1744, 0.3146, 0.4049, -0.3973, -0.0890, -0.1636}; std::vector r_data{-0.0456, 0.1061, 0.1574, -0.4928, -0.4300, -0.1909, -0.0225, -0.2668, 0.1840, -0.4453, -0.4896, 0.1302, -0.0929, 0.3545, -0.4981, 0.0616}; std::vector bias_data{ -0.4938, 0.4355, -0.3186, 0.2094, 0.1037, -0.1071, 0.4504, -0.3990}; std::vector ih_data(num_dirct * batch_size * hidden_size, 0); std::vector input(seq_len * batch_size * input_size, 0); input[0] = input[1] = 1.0; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; float clip = 0.0f; // concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& res_hs = outputs.front(); const auto& res_lho = outputs.back(); std::vector hs_data; std::vector lho_data; res_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.37780784, 0.61055139, 0.55168478, -0.5888475, 0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.37144644, 0.31708236, 0.13104209, -0.18736027, -0.22276389, 0.44193283, -0.16477929, -0.11893477}; std::vector lho_data_gold{0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {batch_size, 2, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto last_out = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); last_out = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_out); mm->add_return({out_hs, last_out}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_last_output = outputs.back(); std::vector last_output_data; std::vector hs_data; arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); arg_last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{ 0.37780784, 0.61055139, 0.55168478, -0.5888475, 0.03445704, 0.19167931, -0.3946827, -0.30889652, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.37144644, 0.31708236, 0.13104209, -0.18736027, -0.22276389, 0.44193283, -0.16477929, -0.11893477, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; std::vector last_output_data_gold{0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{2, 1}; auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto last_out = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); last_out = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_out); mm->add_return({out_hs, last_out}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_last_output = outputs.back(); std::vector last_output_data; std::vector hs_data; arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); arg_last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{0.377808, 0.610551, 0.551685, -0.588848, 0.034457, 0.191679, -0.394683, -0.308897, -0.371446, 0.317082, 0.131042, -0.18736, 0, 0, 0, 0}; std::vector last_output_data_gold{ 0.034457, 0.191679, -0.394683, -0.308897, -0.371446, 0.317082, 0.131042, -0.18736}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // seq_len = 1 { seq_len = 1; std::vector input_1(seq_len * batch_size * input_size, 0); input_1[0] = input_1[1] = 1.0; migraphx::shape in_shape_1{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape_1, input_1}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.37780784, 0.61055139, 0.55168478, -0.5888475, -0.37144644, 0.31708236, 0.13104209, -0.18736027}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(rnn_reverse) { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{-0.0296, -0.1341, 0.1761, -0.2325, -0.0717, 0.1852, 0.2720, 0.1471, -0.1097, 0.3363, -0.0587, -0.2302}; std::vector r_data{0.2528, -0.2333, 0.3973, 0.1593, -0.0388, 0.1702, 0.3829, -0.0712, -0.1668, 0.3074, -0.2854, 0.4049, -0.3737, -0.1051, 0.4482, -0.2841}; std::vector bias_data{-0.3188, 0.1341, -0.4446, 0.1389, 0.3117, 0.3664, 0.2352, 0.2552}; std::vector input(seq_len * batch_size * input_size, 0); input[0] = input[1] = 1.0; std::vector ih_data(num_dirct * batch_size * hidden_size, 0); float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; // concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654, -0.0070999, 0.46251031, -0.20639211, 0.37488942, -0.0070999, 0.46251031, -0.20639211, 0.37488942}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // rnn last output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); p.compile(migraphx::make_target("ref")); auto last_output = p.eval({}).back(); std::vector last_output_data; last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector last_output_data_gold{-0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // rnn hidden states and last hidden state output as program outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {2, batch_size, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_return({out_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); std::vector hs_data; std::vector last_output_data; const auto& arg_hs = outputs.front(); arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); const auto& arg_lho = outputs.back(); arg_lho.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{ -0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654, -0.0070999, 0.46251031, -0.20639211, 0.37488942, -0.0070999, 0.46251031, -0.20639211, 0.37488942, 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}; std::vector last_output_data_gold{-0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // rnn hidden states and last hidden state output as program outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{2, 1}; auto sql = mm->add_literal(seq_len_s, len_data); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_return({out_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); std::vector hs_data; std::vector last_output_data; const auto& arg_hs = outputs.front(); arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); const auto& arg_lho = outputs.back(); arg_lho.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{-0.293853, 0.167968, 0.51076, 0.402587, -0.0070999, 0.46251, -0.206392, 0.374889, -0.0070999, 0.46251, -0.206392, 0.374889, 0, 0, 0, 0}; std::vector last_output_data_gold{ -0.293853, 0.167968, 0.51076, 0.402587, -0.0070999, 0.46251, -0.206392, 0.374889}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } } TEST_CASE(rnn_reverse_layout) { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{-0.0296, -0.1341, 0.1761, -0.2325, -0.0717, 0.1852, 0.2720, 0.1471, -0.1097, 0.3363, -0.0587, -0.2302}; std::vector r_data{0.2528, -0.2333, 0.3973, 0.1593, -0.0388, 0.1702, 0.3829, -0.0712, -0.1668, 0.3074, -0.2854, 0.4049, -0.3737, -0.1051, 0.4482, -0.2841}; std::vector bias_data{-0.3188, 0.1341, -0.4446, 0.1389, 0.3117, 0.3664, 0.2352, 0.2552}; std::vector input(seq_len * batch_size * input_size, 0); input[0] = input[1] = 1.0; std::vector ih_data(num_dirct * batch_size * hidden_size, 0); float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; // concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.0070999, 0.46251031, -0.20639211, 0.37488942, -0.13818839, 0.44124447, 0.14365635, 0.14803654, -0.0070999, 0.46251031, -0.20639211, 0.37488942}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // rnn last output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); p.compile(migraphx::make_target("ref")); auto last_output = p.eval({}).back(); std::vector last_output_data; last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector last_output_data_gold{-0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // rnn hidden states and last hidden state output as program outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {batch_size, 2, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({out_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); std::vector hs_data; std::vector last_output_data; const auto& arg_hs = outputs.front(); arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); const auto& arg_lho = outputs.back(); arg_lho.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{ -0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.0070999, 0.46251031, -0.20639211, 0.37488942, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.13818839, 0.44124447, 0.14365635, 0.14803654, -0.0070999, 0.46251031, -0.20639211, 0.37488942, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; std::vector last_output_data_gold{-0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // rnn hidden states and last hidden state output as program outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{2, 1}; auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({out_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); std::vector hs_data; std::vector last_output_data; const auto& arg_hs = outputs.front(); arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); const auto& arg_lho = outputs.back(); arg_lho.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{-0.293853, 0.167968, 0.51076, 0.402587, -0.0070999, 0.46251, -0.206392, 0.374889, -0.0070999, 0.46251, -0.206392, 0.374889, 0, 0, 0, 0}; std::vector last_output_data_gold{ -0.293853, 0.167968, 0.51076, 0.402587, -0.0070999, 0.46251, -0.206392, 0.374889}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } } TEST_CASE(rnn_bidirectional) { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; std::vector w_data{0.4691, 0.3185, -0.2227, 0.4423, -0.0609, -0.2803, 0.1744, 0.3146, 0.4049, -0.3973, -0.0890, -0.1636, -0.0296, -0.1341, 0.1761, -0.2325, -0.0717, 0.1852, 0.2720, 0.1471, -0.1097, 0.3363, -0.0587, -0.2302}; std::vector r_data{-0.0456, 0.1061, 0.1574, -0.4928, -0.4300, -0.1909, -0.0225, -0.2668, 0.1840, -0.4453, -0.4896, 0.1302, -0.0929, 0.3545, -0.4981, 0.0616, 0.2528, -0.2333, 0.3973, 0.1593, -0.0388, 0.1702, 0.3829, -0.0712, -0.1668, 0.3074, -0.2854, 0.4049, -0.3737, -0.1051, 0.4482, -0.2841}; std::vector bias_data{-0.4938, 0.4355, -0.3186, 0.2094, 0.1037, -0.1071, 0.4504, -0.3990, -0.3188, 0.1341, -0.4446, 0.1389, 0.3117, 0.3664, 0.2352, 0.2552}; std::vector input(seq_len * batch_size * input_size, 0); input[0] = input[1] = 1.0; std::vector ih_data(num_dirct * batch_size * hidden_size, 0); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; float clip = 0.0f; // concatenation of hidden state and last hs output for program outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_return({out_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_lho = outputs.back(); std::vector hs_data; arg_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector last_output_data; arg_lho.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{ 0.37780784, 0.61055139, 0.55168478, -0.5888475, -0.37144644, 0.31708236, 0.13104209, -0.18736027, -0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654, 0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477, -0.0070999, 0.46251031, -0.20639211, 0.37488942, -0.0070999, 0.46251031, -0.20639211, 0.37488942}; std::vector last_output_data_gold{0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477, -0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // last rnn output for program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{1, 2}; auto sql = mm->add_literal(seq_len_s, len_data); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); mm->add_return({out_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_lho = outputs.back(); std::vector hs_data; std::vector last_output_data; arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); arg_lho.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{ 0.377808, 0.610551, 0.551685, -0.588848, -0.371446, 0.317082, 0.131042, -0.18736, -0.169158, 0.193817, 0.206679, 0.586097, -0.138188, 0.441244, 0.143656, 0.148037, 0, 0, 0, 0, -0.222764, 0.441933, -0.164779, -0.118935, 0, 0, 0, 0, -0.0070999, 0.46251, -0.206392, 0.374889}; std::vector last_output_data_gold{0.377808, 0.610551, 0.551685, -0.588848, -0.222764, 0.441933, -0.164779, -0.118935, -0.169158, 0.193817, 0.206679, 0.586097, -0.138188, 0.441244, 0.143656, 0.148037}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // 4 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); p.compile(migraphx::make_target("ref")); auto last_output = p.eval({}).back(); std::vector last_output_data; last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector last_output_data_gold{0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.22276389, 0.44193283, -0.16477929, -0.11893477, -0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.13818839, 0.44124447, 0.14365635, 0.14803654}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto last_output = p.eval({}).back(); std::vector last_output_data; last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector last_output_data_gold{ 0.6570473, 0.36392266, 0.45342238, -0.45127486, 0., 0., 0., 0., -0.16225325, -0.29515147, 0.39617197, 0.27068236, 0., 0., 0., 0., 0.2935145, -0.23719997, -0.31123261, -0.18357255, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // concatenation of hidden state for program output { seq_len = 1; std::vector input_1(seq_len * batch_size * input_size, 0); input_1[0] = input_1[1] = 1.0; migraphx::shape in_shape_1{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape_1, input_1}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.37780784, 0.61055139, 0.55168478, -0.5888475, -0.37144644, 0.31708236, 0.13104209, -0.18736027, -0.16915828, 0.1938169, 0.20667936, 0.58609703, -0.0070999, 0.46251031, -0.20639211, 0.37488942}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(rnn_bidirectional_layout) { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; std::vector w_data{0.4691, 0.3185, -0.2227, 0.4423, -0.0609, -0.2803, 0.1744, 0.3146, 0.4049, -0.3973, -0.0890, -0.1636, -0.0296, -0.1341, 0.1761, -0.2325, -0.0717, 0.1852, 0.2720, 0.1471, -0.1097, 0.3363, -0.0587, -0.2302}; std::vector r_data{-0.0456, 0.1061, 0.1574, -0.4928, -0.4300, -0.1909, -0.0225, -0.2668, 0.1840, -0.4453, -0.4896, 0.1302, -0.0929, 0.3545, -0.4981, 0.0616, 0.2528, -0.2333, 0.3973, 0.1593, -0.0388, 0.1702, 0.3829, -0.0712, -0.1668, 0.3074, -0.2854, 0.4049, -0.3737, -0.1051, 0.4482, -0.2841}; std::vector bias_data{-0.4938, 0.4355, -0.3186, 0.2094, 0.1037, -0.1071, 0.4504, -0.3990, -0.3188, 0.1341, -0.4446, 0.1389, 0.3117, 0.3664, 0.2352, 0.2552}; std::vector input(seq_len * batch_size * input_size, 0); input[0] = input[1] = 1.0; std::vector ih_data(num_dirct * batch_size * hidden_size, 0); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; float clip = 0.0f; // concatenation of hidden state and last hs output for program outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({out_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_lho = outputs.back(); std::vector hs_data; arg_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector last_output_data; arg_lho.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{ 0.37780784, 0.61055139, 0.55168478, -0.5888475, -0.29385301, 0.16796815, 0.51075965, 0.40258689, 0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.0070999, 0.46251031, -0.20639211, 0.37488942, -0.37144644, 0.31708236, 0.13104209, -0.18736027, -0.13818839, 0.44124447, 0.14365635, 0.14803654, -0.22276389, 0.44193283, -0.16477929, -0.11893477, -0.0070999, 0.46251031, -0.20639211, 0.37488942}; std::vector last_output_data_gold{0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.22276389, 0.44193283, -0.16477929, -0.11893477, -0.13818839, 0.44124447, 0.14365635, 0.14803654}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // last rnn output for program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{1, 2}; auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({out_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_lho = outputs.back(); std::vector hs_data; std::vector last_output_data; arg_hs.visit([&](auto out) { hs_data.assign(out.begin(), out.end()); }); arg_lho.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector hs_data_gold{ 0.377808, 0.610551, 0.551685, -0.588848, -0.169158, 0.193817, 0.206679, 0.586097, 0, 0, 0, 0, 0, 0, 0, 0, -0.371446, 0.317082, 0.131042, -0.18736, -0.138188, 0.441244, 0.143656, 0.148037, -0.222764, 0.441933, -0.164779, -0.118935, -0.0070999, 0.46251, -0.206392, 0.374889}; std::vector last_output_data_gold{0.377808, 0.610551, 0.551685, -0.588848, -0.169158, 0.193817, 0.206679, 0.586097, -0.222764, 0.441933, -0.164779, -0.118935, -0.138188, 0.441244, 0.143656, 0.148037}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // 4 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); p.compile(migraphx::make_target("ref")); auto last_output = p.eval({}).back(); std::vector last_output_data; last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector last_output_data_gold{0.03445704, 0.19167931, -0.3946827, -0.30889652, -0.29385301, 0.16796815, 0.51075965, 0.40258689, -0.22276389, 0.44193283, -0.16477929, -0.11893477, -0.13818839, 0.44124447, 0.14365635, 0.14803654}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); p.compile(migraphx::make_target("ref")); auto last_output = p.eval({}).back(); std::vector last_output_data; last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector last_output_data_gold{ 0.6570473, 0.36392266, 0.45342238, -0.45127486, -0.16225325, -0.29515147, 0.39617197, 0.27068236, 0.2935145, -0.23719997, -0.31123261, -0.18357255, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}; EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); } // concatenation of hidden state for program output { seq_len = 1; std::vector input_1(seq_len * batch_size * input_size, 0); input_1[0] = input_1[1] = 1.0; migraphx::shape in_shape_1{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape_1, input_1}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto out_hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.37780784, 0.61055139, 0.55168478, -0.5888475, -0.16915828, 0.1938169, 0.20667936, 0.58609703, -0.37144644, 0.31708236, 0.13104209, -0.18736027, -0.0070999, 0.46251031, -0.20639211, 0.37488942}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(rnn_fp16) { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{0.4691, 0.3185, -0.2227, 0.4423, -0.0609, -0.2803, 0.1744, 0.3146, 0.4049, -0.3973, -0.0890, -0.1636}; std::vector r_data{-0.0456, 0.1061, 0.1574, -0.4928, -0.4300, -0.1909, -0.0225, -0.2668, 0.1840, -0.4453, -0.4896, 0.1302, -0.0929, 0.3545, -0.4981, 0.0616}; std::vector bias_data{ -0.4938, 0.4355, -0.3186, 0.2094, 0.1037, -0.1071, 0.4504, -0.3990}; std::vector ih_data(num_dirct * batch_size * hidden_size, 0); std::vector input(seq_len * batch_size * input_size, 0); input[0] = input[1] = 1.0; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto seq_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), seq); auto w_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), w); auto r_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), r); auto out_hs = mm->add_instruction( migraphx::make_op("rnn", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq_half, w_half, r_half); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); p.compile(migraphx::make_target("ref")); auto last_output = p.eval({}).back(); std::vector last_output_data; last_output.visit([&](auto out) { last_output_data.assign(out.begin(), out.end()); }); std::vector last_output_data_gold{ 0.2935145, -0.23719997, -0.31123261, -0.18357255, 0., 0., 0., 0.}; EXPECT(migraphx::verify::verify_range_with_tolerance( last_output_data, migraphx::verify::expected{last_output_data_gold}, migraphx::verify::tolerance{0.005})); } TEST_CASE(gru_forward) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 1; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3485, -0.0378, -0.1782, 0.1416, -0.3096, -0.2212, -0.3883, 0.1983, -0.2418, 0.1480, -0.3255, 0.1359, -0.3551, -0.3605, -0.3482, -0.1424, -0.0495, -0.1640, -0.1979, -0.2577, -0.4097, -0.1211, -0.0412, 0.1801, 0.1721, -0.4327, -0.0498, 0.2628, -0.1573, -0.1577, 0.2759, -0.2023, -0.1185, -0.2136, 0.1294, -0.2331, 0.0701, 0.4316, 0.0480, 0.0247, -0.0166, -0.2729, 0.1712, -0.3984, -0.3905}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ 0.2848, -0.2851, -0.3466, -0.1718, -0.1492, -0.0082, 0.2452, -0.0401, 0.3399, 0.2529, -0.0953, -0.0903, -0.1518, -0.1373, 0.3848, -0.0130, -0.4339, 0.0406, -0.1926, -0.1131, 0.4285, -0.0013, 0.2243, 0.2752, 0.1776, -0.1720, 0.0822, -0.0295, 0.1062, -0.2721, -0.2736, -0.1826, 0.3541, -0.4259, 0.2188, 0.0706, 0.3650, 0.3947, 0.2522, 0.2179, -0.0744, 0.2122, -0.4346, 0.2760, 0.4076, 0.1183, -0.1500, -0.1704, 0.3090, -0.0706, -0.2442, 0.3021, 0.1680, 0.0783, -0.3754, -0.3469, -0.2972, -0.0170, 0.4143, 0.3801, 0.3852, -0.1170, -0.2937, 0.2979, -0.1357, 0.4257, 0.3884, -0.2916, 0.1071, 0.0934, 0.3645, -0.4310, -0.3480, 0.0702, -0.1558}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ 0.0560, 0.0310, -0.1669, -0.0781, 0.1793, -0.1758, 0.3173, -0.1650, -0.3732, 0.2946, -0.0912, 0.3118, 0.1391, 0.2755, 0.2695, -0.1059, -0.2357, 0.3629, -0.2534, -0.0494, 0.0556, 0.0881, -0.2592, -0.2213, 0.2310, -0.4044, 0.1801, 0.1438, 0.3108, -0.3607}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; std::vector ih_data{ -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; // concatenation of hidden states for output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ -0.27298412, 0.42363745, -0.09368783, 0.4823072, -0.02183238, -0.6873896, 0.16144305, 0.31932795, 0.6104771, 0.79759157, -0.31791314, 0.5249062, 0.08800987, 0.46404213, -0.11872687, -0.26210734, 0.34448293, -0.0176422, 0.48523626, 0.60002893, -0.3969709, 0.43360898, 0.35775262, 0.23280787, -0.52179873, -0.21944991, 0.4535257, -0.13735442, 0.51757574, 0.50380427}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // last output for output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.3969709, 0.43360898, 0.35775262, 0.23280787, -0.52179873, -0.21944991, 0.4535257, -0.13735442, 0.51757574, 0.50380427}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // two rnn_last_hs_output operators after gru { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.3969709, 0.43360898, 0.35775262, 0.23280787, -0.52179873, -0.21944991, 0.4535257, -0.13735442, 0.51757574, 0.50380427}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // last output for output, linear_before_reset = 0 { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 0}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.53291196, 0.50160867, 0.39010462, 0.39292926, -0.5960838, -0.38451535, 0.454239, -0.10620412, 0.6014447, 0.43445644}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_forward_layout) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 1; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3485, -0.0378, -0.1782, 0.1416, -0.3096, -0.2212, -0.3883, 0.1983, -0.2418, 0.1480, -0.3255, 0.1359, -0.3551, -0.3605, -0.3482, -0.1424, -0.0495, -0.1640, -0.1979, -0.2577, -0.4097, -0.1211, -0.0412, 0.1801, 0.1721, -0.4327, -0.0498, 0.2628, -0.1573, -0.1577, 0.2759, -0.2023, -0.1185, -0.2136, 0.1294, -0.2331, 0.0701, 0.4316, 0.0480, 0.0247, -0.0166, -0.2729, 0.1712, -0.3984, -0.3905}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ 0.2848, -0.2851, -0.3466, -0.1718, -0.1492, -0.0082, 0.2452, -0.0401, 0.3399, 0.2529, -0.0953, -0.0903, -0.1518, -0.1373, 0.3848, -0.0130, -0.4339, 0.0406, -0.1926, -0.1131, 0.4285, -0.0013, 0.2243, 0.2752, 0.1776, -0.1720, 0.0822, -0.0295, 0.1062, -0.2721, -0.2736, -0.1826, 0.3541, -0.4259, 0.2188, 0.0706, 0.3650, 0.3947, 0.2522, 0.2179, -0.0744, 0.2122, -0.4346, 0.2760, 0.4076, 0.1183, -0.1500, -0.1704, 0.3090, -0.0706, -0.2442, 0.3021, 0.1680, 0.0783, -0.3754, -0.3469, -0.2972, -0.0170, 0.4143, 0.3801, 0.3852, -0.1170, -0.2937, 0.2979, -0.1357, 0.4257, 0.3884, -0.2916, 0.1071, 0.0934, 0.3645, -0.4310, -0.3480, 0.0702, -0.1558}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ 0.0560, 0.0310, -0.1669, -0.0781, 0.1793, -0.1758, 0.3173, -0.1650, -0.3732, 0.2946, -0.0912, 0.3118, 0.1391, 0.2755, 0.2695, -0.1059, -0.2357, 0.3629, -0.2534, -0.0494, 0.0556, 0.0881, -0.2592, -0.2213, 0.2310, -0.4044, 0.1801, 0.1438, 0.3108, -0.3607}; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -0.3933, 0.5151, -0.2951, 0.0373, 1.3211, 0.7854, -2.6430, -0.3306, -0.8504, 0.0093, -1.1948, -0.1239, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; std::vector ih_data{ -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; // concatenation of hidden states for output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ -0.27298412, 0.42363745, -0.09368783, 0.4823072, -0.02183238, -0.31791314, 0.5249062, 0.08800987, 0.46404213, -0.11872687, -0.3969709, 0.43360898, 0.35775262, 0.23280787, -0.52179873, -0.6873896, 0.16144305, 0.31932795, 0.6104771, 0.79759157, -0.26210734, 0.34448293, -0.0176422, 0.48523626, 0.60002893, -0.21944991, 0.4535257, -0.13735442, 0.51757574, 0.50380427}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // last output for output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.3969709, 0.43360898, 0.35775262, 0.23280787, -0.52179873, -0.21944991, 0.4535257, -0.13735442, 0.51757574, 0.50380427}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // two rnn_last_hs_output operators after gru { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.3969709, 0.43360898, 0.35775262, 0.23280787, -0.52179873, -0.21944991, 0.4535257, -0.13735442, 0.51757574, 0.50380427}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // last output for output, linear_before_reset = 0 { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 0}}), seq, w, r, bias, und, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.53291196, 0.50160867, 0.39010462, 0.39292926, -0.5960838, -0.38451535, 0.454239, -0.10620412, 0.6014447, 0.43445644}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_forward_args) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 1; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3485, -0.0378, -0.1782, 0.1416, -0.3096, -0.2212, -0.3883, 0.1983, -0.2418, 0.1480, -0.3255, 0.1359, -0.3551, -0.3605, -0.3482, -0.1424, -0.0495, -0.1640, -0.1979, -0.2577, -0.4097, -0.1211, -0.0412, 0.1801, 0.1721, -0.4327, -0.0498, 0.2628, -0.1573, -0.1577, 0.2759, -0.2023, -0.1185, -0.2136, 0.1294, -0.2331, 0.0701, 0.4316, 0.0480, 0.0247, -0.0166, -0.2729, 0.1712, -0.3984, -0.3905}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ 0.2848, -0.2851, -0.3466, -0.1718, -0.1492, -0.0082, 0.2452, -0.0401, 0.3399, 0.2529, -0.0953, -0.0903, -0.1518, -0.1373, 0.3848, -0.0130, -0.4339, 0.0406, -0.1926, -0.1131, 0.4285, -0.0013, 0.2243, 0.2752, 0.1776, -0.1720, 0.0822, -0.0295, 0.1062, -0.2721, -0.2736, -0.1826, 0.3541, -0.4259, 0.2188, 0.0706, 0.3650, 0.3947, 0.2522, 0.2179, -0.0744, 0.2122, -0.4346, 0.2760, 0.4076, 0.1183, -0.1500, -0.1704, 0.3090, -0.0706, -0.2442, 0.3021, 0.1680, 0.0783, -0.3754, -0.3469, -0.2972, -0.0170, 0.4143, 0.3801, 0.3852, -0.1170, -0.2937, 0.2979, -0.1357, 0.4257, 0.3884, -0.2916, 0.1071, 0.0934, 0.3645, -0.4310, -0.3480, 0.0702, -0.1558}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; float clip = 0.0f; // 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.114674, -0.129581, -0.218156, -0.140788, -0.114242, -0.346569, 0.321367, -0.0838253, 0.102097, 0.00232137, -0.149055, 0.0590743, -0.0533094, -0.0446122, -0.112588, 0.0153261, 0.168883, -0.326836, 0.0843562, 0.160872, -0.232523, 0.00214573, 0.231693, -0.160475, -0.518952, 0.0467166, 0.12327, -0.374162, 0.137778, 0.251976}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 4 args (bias is used) { std::vector bias_data{ 0.0560, 0.0310, -0.1669, -0.0781, 0.1793, -0.1758, 0.3173, -0.1650, -0.3732, 0.2946, -0.0912, 0.3118, 0.1391, 0.2755, 0.2695, -0.1059, -0.2357, 0.3629, -0.2534, -0.0494, 0.0556, 0.0881, -0.2592, -0.2213, 0.2310, -0.4044, 0.1801, 0.1438, 0.3108, -0.3607}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.273619, 0.0931375, -0.104717, 0.0203752, -0.0797887, -0.493948, 0.472118, -0.0336318, 0.332706, 0.0182268, -0.341684, 0.38063, 0.0589275, 0.2644, -0.115737, -0.152324, 0.442277, -0.201626, 0.408909, 0.12905, -0.416866, 0.377186, 0.32922, 0.162214, -0.519973, -0.140072, 0.465076, -0.229563, 0.500164, 0.195166}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 4 args (ih is used) { std::vector ih_data{ -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, und, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.0801064, 0.27025, -0.20704, 0.333579, -0.0452438, -0.56265, 0.061061, 0.262172, 0.405193, 0.775226, -0.100683, 0.258729, -0.0187297, 0.215815, -0.108936, -0.0941018, 0.129665, -0.159421, 0.190636, 0.597412, -0.197, 0.0885705, 0.269396, -0.0414511, -0.515137, -0.03075, 0.158326, -0.296488, 0.177983, 0.519498}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_forward_actv_funcs) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 1; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3485, -0.0378, -0.1782, 0.1416, -0.3096, -0.2212, -0.3883, 0.1983, -0.2418, 0.1480, -0.3255, 0.1359, -0.3551, -0.3605, -0.3482, -0.1424, -0.0495, -0.1640, -0.1979, -0.2577, -0.4097, -0.1211, -0.0412, 0.1801, 0.1721, -0.4327, -0.0498, 0.2628, -0.1573, -0.1577, 0.2759, -0.2023, -0.1185, -0.2136, 0.1294, -0.2331, 0.0701, 0.4316, 0.0480, 0.0247, -0.0166, -0.2729, 0.1712, -0.3984, -0.3905}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ 0.2848, -0.2851, -0.3466, -0.1718, -0.1492, -0.0082, 0.2452, -0.0401, 0.3399, 0.2529, -0.0953, -0.0903, -0.1518, -0.1373, 0.3848, -0.0130, -0.4339, 0.0406, -0.1926, -0.1131, 0.4285, -0.0013, 0.2243, 0.2752, 0.1776, -0.1720, 0.0822, -0.0295, 0.1062, -0.2721, -0.2736, -0.1826, 0.3541, -0.4259, 0.2188, 0.0706, 0.3650, 0.3947, 0.2522, 0.2179, -0.0744, 0.2122, -0.4346, 0.2760, 0.4076, 0.1183, -0.1500, -0.1704, 0.3090, -0.0706, -0.2442, 0.3021, 0.1680, 0.0783, -0.3754, -0.3469, -0.2972, -0.0170, 0.4143, 0.3801, 0.3852, -0.1170, -0.2937, 0.2979, -0.1357, 0.4257, 0.3884, -0.2916, 0.1071, 0.0934, 0.3645, -0.4310, -0.3480, 0.0702, -0.1558}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ 0.0560, 0.0310, -0.1669, -0.0781, 0.1793, -0.1758, 0.3173, -0.1650, -0.3732, 0.2946, -0.0912, 0.3118, 0.1391, 0.2755, 0.2695, -0.1059, -0.2357, 0.3629, -0.2534, -0.0494, 0.0556, 0.0881, -0.2592, -0.2213, 0.2310, -0.4044, 0.1801, 0.1438, 0.3108, -0.3607}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; std::vector ih_data{ -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; // no activation function specified, so default is used. { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.3969709, 0.43360898, 0.35775262, 0.23280787, -0.52179873, -0.21944991, 0.4535257, -0.13735442, 0.51757574, 0.50380427}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 1 activation function (sigmoid) specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.26905832, 0.5669211, 0.20464146, 0.67195725, 0.24752215, 0.11411376, 0.12353572, 0.4245067, 0.73908687, 0.8644615, 0.34754312, 0.61424744, 0.36769435, 0.6499579, 0.3168031, 0.3296533, 0.3055136, 0.42514813, 0.6851256, 0.7967266, 0.35652235, 0.6033026, 0.52634895, 0.5815402, 0.3001663, 0.39814138, 0.4354002, 0.4310627, 0.6708563, 0.7509278}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 1 activation function (tanh) specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.49333298, -0.06104589, 0.5629142, -0.97955984, -0.9314696, -0.03033514, 0.5280315, -0.27354342, 0.65615714, 0.53612584}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // seq length of 1 { migraphx::program p; auto* mm = p.get_main_module(); seq_len = 1; migraphx::shape in_shape_one{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input_one{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504}; auto seq = mm->add_literal(migraphx::literal{in_shape_one, input_one}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.27298412, 0.42363745, -0.09368783, 0.4823072, -0.02183238, -0.6873896, 0.16144305, 0.31932795, 0.6104771, 0.79759157}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_reverse) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 1; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3485, -0.0378, -0.1782, 0.1416, -0.3096, -0.2212, -0.3883, 0.1983, -0.2418, 0.1480, -0.3255, 0.1359, -0.3551, -0.3605, -0.3482, -0.1424, -0.0495, -0.1640, -0.1979, -0.2577, -0.4097, -0.1211, -0.0412, 0.1801, 0.1721, -0.4327, -0.0498, 0.2628, -0.1573, -0.1577, 0.2759, -0.2023, -0.1185, -0.2136, 0.1294, -0.2331, 0.0701, 0.4316, 0.0480, 0.0247, -0.0166, -0.2729, 0.1712, -0.3984, -0.3905}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ 0.2848, -0.2851, -0.3466, -0.1718, -0.1492, -0.0082, 0.2452, -0.0401, 0.3399, 0.2529, -0.0953, -0.0903, -0.1518, -0.1373, 0.3848, -0.0130, -0.4339, 0.0406, -0.1926, -0.1131, 0.4285, -0.0013, 0.2243, 0.2752, 0.1776, -0.1720, 0.0822, -0.0295, 0.1062, -0.2721, -0.2736, -0.1826, 0.3541, -0.4259, 0.2188, 0.0706, 0.3650, 0.3947, 0.2522, 0.2179, -0.0744, 0.2122, -0.4346, 0.2760, 0.4076, 0.1183, -0.1500, -0.1704, 0.3090, -0.0706, -0.2442, 0.3021, 0.1680, 0.0783, -0.3754, -0.3469, -0.2972, -0.0170, 0.4143, 0.3801, 0.3852, -0.1170, -0.2937, 0.2979, -0.1357, 0.4257, 0.3884, -0.2916, 0.1071, 0.0934, 0.3645, -0.4310, -0.3480, 0.0702, -0.1558}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ 0.0560, 0.0310, -0.1669, -0.0781, 0.1793, -0.1758, 0.3173, -0.1650, -0.3732, 0.2946, -0.0912, 0.3118, 0.1391, 0.2755, 0.2695, -0.1059, -0.2357, 0.3629, -0.2534, -0.0494, 0.0556, 0.0881, -0.2592, -0.2213, 0.2310, -0.4044, 0.1801, 0.1438, 0.3108, -0.3607}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; std::vector ih_data{ -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; // concatenation of hidden states and last hs output for outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({lho, hs}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& res_hs = outputs.back(); const auto& res_lho = outputs.front(); std::vector hs_data; std::vector lho_data; res_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.263403, 0.317655, -0.00634162, 0.200443, -0.349125, -0.600874, 0.542386, -0.0856531, 0.55703, 0.54711, -0.276245, 0.521348, 0.302874, 0.394353, -0.334369, -0.187861, 0.213553, -0.0708377, 0.545435, 0.654301, -0.329512, 0.476095, 0.284044, 0.392077, -0.369226, -0.3275, -0.027301, 0.143774, 0.655686, 0.782831}; std::vector lho_data_gold{-0.263403, 0.317655, -0.00634162, 0.200443, -0.349125, -0.600874, 0.542386, -0.0856531, 0.55703, 0.54711}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // variable input sequence length { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{1, 2}; auto sql = mm->add_literal(seq_len_s, len_data); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({lho, hs}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& res_hs = outputs.back(); const auto& res_lho = outputs.front(); std::vector hs_data; std::vector lho_data; res_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ -0.272984, 0.423637, -0.0936878, 0.482307, -0.0218324, -0.630874, 0.401448, 0.0488417, 0.558397, 0.664423, 0, 0, 0, 0, 0, -0.238202, -0.0752721, 0.0919409, 0.669654, 0.782363, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector lho_data_gold{-0.272984, 0.423637, -0.0936878, 0.482307, -0.0218324, -0.630874, 0.401448, 0.0488417, 0.558397, 0.664423}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // last output for output, linear_before_reset = 0 { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 0}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.388654, 0.384975, 0.0179455, 0.350101, -0.456872, -0.690085, 0.534512, -0.0558191, 0.646604, 0.463943}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // no activation function specified, so default is used. { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.263403, 0.317655, -0.00634162, 0.200443, -0.349125, -0.600874, 0.542386, -0.0856531, 0.55703, 0.54711, -0.276245, 0.521348, 0.302874, 0.394353, -0.334369, -0.187861, 0.213553, -0.0708377, 0.545435, 0.654301, -0.329512, 0.476095, 0.284044, 0.392077, -0.369226, -0.3275, -0.027301, 0.143774, 0.655686, 0.782831}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // seq length of 1 { migraphx::program p; auto* mm = p.get_main_module(); seq_len = 1; migraphx::shape in_shape_one{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input_one{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504}; auto seq = mm->add_literal(migraphx::literal{in_shape_one, input_one}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.272984, 0.423637, -0.0936878, 0.482307, -0.0218324, -0.68739, 0.161443, 0.319328, 0.610477, 0.797592}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_reverse_layout) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 1; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3485, -0.0378, -0.1782, 0.1416, -0.3096, -0.2212, -0.3883, 0.1983, -0.2418, 0.1480, -0.3255, 0.1359, -0.3551, -0.3605, -0.3482, -0.1424, -0.0495, -0.1640, -0.1979, -0.2577, -0.4097, -0.1211, -0.0412, 0.1801, 0.1721, -0.4327, -0.0498, 0.2628, -0.1573, -0.1577, 0.2759, -0.2023, -0.1185, -0.2136, 0.1294, -0.2331, 0.0701, 0.4316, 0.0480, 0.0247, -0.0166, -0.2729, 0.1712, -0.3984, -0.3905}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ 0.2848, -0.2851, -0.3466, -0.1718, -0.1492, -0.0082, 0.2452, -0.0401, 0.3399, 0.2529, -0.0953, -0.0903, -0.1518, -0.1373, 0.3848, -0.0130, -0.4339, 0.0406, -0.1926, -0.1131, 0.4285, -0.0013, 0.2243, 0.2752, 0.1776, -0.1720, 0.0822, -0.0295, 0.1062, -0.2721, -0.2736, -0.1826, 0.3541, -0.4259, 0.2188, 0.0706, 0.3650, 0.3947, 0.2522, 0.2179, -0.0744, 0.2122, -0.4346, 0.2760, 0.4076, 0.1183, -0.1500, -0.1704, 0.3090, -0.0706, -0.2442, 0.3021, 0.1680, 0.0783, -0.3754, -0.3469, -0.2972, -0.0170, 0.4143, 0.3801, 0.3852, -0.1170, -0.2937, 0.2979, -0.1357, 0.4257, 0.3884, -0.2916, 0.1071, 0.0934, 0.3645, -0.4310, -0.3480, 0.0702, -0.1558}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ 0.0560, 0.0310, -0.1669, -0.0781, 0.1793, -0.1758, 0.3173, -0.1650, -0.3732, 0.2946, -0.0912, 0.3118, 0.1391, 0.2755, 0.2695, -0.1059, -0.2357, 0.3629, -0.2534, -0.0494, 0.0556, 0.0881, -0.2592, -0.2213, 0.2310, -0.4044, 0.1801, 0.1438, 0.3108, -0.3607}; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -0.3933, 0.5151, -0.2951, 0.0373, 1.3211, 0.7854, -2.6430, -0.3306, -0.8504, 0.0093, -1.1948, -0.1239, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; std::vector ih_data{ -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; // concatenation of hidden states and last hs output for outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({lho, hs}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& res_hs = outputs.back(); const auto& res_lho = outputs.front(); std::vector hs_data; std::vector lho_data; res_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.263403, 0.317655, -0.00634162, 0.200443, -0.349125, -0.276245, 0.521348, 0.302874, 0.394353, -0.334369, -0.329512, 0.476095, 0.284044, 0.392077, -0.369226, -0.600874, 0.542386, -0.0856531, 0.55703, 0.54711, -0.187861, 0.213553, -0.0708377, 0.545435, 0.654301, -0.3275, -0.027301, 0.143774, 0.655686, 0.782831}; std::vector lho_data_gold{-0.263403, 0.317655, -0.00634162, 0.200443, -0.349125, -0.600874, 0.542386, -0.0856531, 0.55703, 0.54711}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // variable input sequence length { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{1, 2}; auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({lho, hs}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& res_hs = outputs.back(); const auto& res_lho = outputs.front(); std::vector hs_data; std::vector lho_data; res_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.272984, 0.423637, -0.0936878, 0.482307, -0.0218324, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.630874, 0.401448, 0.0488417, 0.558397, 0.664423, -0.238202, -0.0752721, 0.0919409, 0.669654, 0.782363, 0, 0, 0, 0, 0}; std::vector lho_data_gold{-0.272984, 0.423637, -0.0936878, 0.482307, -0.0218324, -0.630874, 0.401448, 0.0488417, 0.558397, 0.664423}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // last output for output, linear_before_reset = 0 { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 0}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.388654, 0.384975, 0.0179455, 0.350101, -0.456872, -0.690085, 0.534512, -0.0558191, 0.646604, 0.463943}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // seq length of 1 { migraphx::program p; auto* mm = p.get_main_module(); seq_len = 1; migraphx::shape in_shape_one{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; std::vector input_one{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504}; auto seq = mm->add_literal(migraphx::literal{in_shape_one, input_one}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.272984, 0.423637, -0.0936878, 0.482307, -0.0218324, -0.68739, 0.161443, 0.319328, 0.610477, 0.797592}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_bidirectional) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 2; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3809, 0.4283, 0.2294, -0.1018, -0.1226, -0.0037, 0.2449, -0.2712, -0.1418, 0.1363, -0.3453, -0.0693, -0.2281, 0.2699, -0.2024, -0.3085, -0.3338, 0.4109, 0.2605, -0.1019, -0.2813, 0.3323, -0.1590, 0.0788, -0.3535, 0.0397, 0.2732, 0.2906, 0.0519, 0.3617, -0.2664, 0.1441, 0.0464, -0.1057, 0.2204, -0.3294, 0.3670, 0.1411, 0.3852, 0.3572, 0.3918, 0.0483, -0.3906, -0.2841, -0.2778, -0.4272, 0.2335, -0.1811, -0.3885, -0.1279, 0.1000, 0.0206, -0.3284, -0.0353, 0.1197, 0.1190, 0.3862, 0.0965, -0.0492, 0.2657, -0.1430, 0.0597, 0.1408, -0.0315, 0.1248, 0.0751, 0.3838, 0.3020, 0.0515, 0.2375, -0.4255, 0.1714, -0.0432, 0.3447, -0.2441, -0.3989, -0.3428, -0.4204, -0.4080, -0.2683, -0.0996, -0.1685, -0.0532, -0.1258, 0.1663, -0.3526, -0.3915, -0.1721, 0.1292, -0.2279}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ -0.2683, 0.0699, -0.4021, -0.1379, 0.0042, -0.2447, 0.4006, 0.0270, -0.0446, 0.1063, 0.1381, 0.1310, -0.3596, 0.3869, 0.3929, 0.2750, 0.0890, 0.3069, -0.1691, -0.2194, -0.1066, 0.3187, -0.4369, -0.0603, -0.0834, -0.1182, -0.2047, 0.3253, -0.2931, 0.2082, 0.0424, 0.1111, -0.2773, -0.0279, -0.0869, 0.1413, -0.4227, -0.3672, 0.4137, 0.0609, 0.4223, -0.4032, 0.2945, 0.3600, 0.3345, -0.3880, -0.0192, -0.0090, -0.2648, 0.4339, -0.0155, 0.4437, -0.1766, 0.1957, 0.2475, 0.3773, -0.2710, 0.3289, -0.2077, -0.2534, -0.0832, -0.1632, 0.0728, 0.2520, 0.4153, 0.1659, -0.4342, 0.0541, 0.1812, -0.2305, 0.4440, 0.0946, 0.0410, -0.4381, -0.3161, 0.3906, -0.3958, -0.4238, 0.1975, 0.3440, 0.1437, -0.0568, 0.1492, -0.4248, -0.3304, 0.2786, -0.1328, -0.3740, -0.3566, 0.3074, 0.0924, 0.2684, -0.1527, 0.1826, 0.2424, 0.2002, 0.3479, -0.1089, 0.3472, -0.3677, -0.4231, -0.0798, -0.3709, 0.3924, 0.2774, -0.3690, -0.0233, 0.2845, 0.1969, 0.1618, -0.3742, -0.3619, 0.2925, -0.1838, -0.1495, -0.3747, 0.0341, -0.4243, -0.0732, -0.3997, 0.2139, 0.2425, 0.4171, -0.3358, 0.3534, 0.0938, -0.0582, -0.2681, -0.4293, 0.1027, 0.4101, 0.2641, -0.4110, -0.1681, 0.3582, -0.2089, 0.0852, 0.0963, 0.3866, 0.1955, -0.2174, 0.1996, -0.2252, 0.1748, 0.1833, -0.3155, 0.2567, -0.4387, 0.3402, 0.0599}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ -0.1582, -0.0826, 0.4008, 0.0118, 0.2511, 0.1900, -0.2838, 0.2549, -0.2484, 0.2363, -0.4083, -0.0295, -0.1161, 0.1211, 0.2509, -0.1414, -0.2628, -0.2992, 0.1517, 0.1817, -0.2783, 0.3183, -0.1629, -0.3108, -0.3418, 0.0411, 0.2203, 0.2187, -0.2990, -0.0416, 0.0209, -0.1024, 0.4443, -0.4420, -0.0330, -0.3591, -0.2990, 0.2167, 0.1395, 0.2317, 0.1318, 0.1909, -0.3615, 0.1953, -0.2582, -0.2217, 0.3723, 0.1458, 0.2630, -0.0377, 0.1754, 0.0800, -0.3964, -0.3247, 0.4219, -0.0900, 0.3553, 0.2614, -0.1298, -0.1124}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; std::vector ih_data{-0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212, -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; // concatenation of hidden states and last hs output for outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& hs_concat = outputs.front(); const auto& res_lho = outputs.back(); std::vector hs_data; std::vector lho_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0352243, 0.0146756, 0.00570925, 0.152446, 0.208683, 0.214342, -0.0454273, -0.135177, -0.0800739, 0.903659, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229, -0.0930435, 0.174108, -0.063834, 0.0909285, 0.22759, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, -0.159396, 0.299061, -0.116652, 0.238649, 0.109945, 0.192866, 0.307073, 0.191113, 0.658287, -0.0340374, -0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693533, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, -0.395918, 0.231694, -0.160503, 0.383289, 0.0879262, -0.0254665, 0.079043, 0.322652, 0.752701, 0.243775}; std::vector lho_data_gold{-0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693533, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // same input sequence length, but shorter than max squence length { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {2, batch_size, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); mm->add_return({concat_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& hs_concat = outputs.front(); const auto& res_lho = outputs.back(); std::vector hs_data; std::vector lho_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0352244, 0.0146756, 0.00570924, 0.152446, 0.208683, 0.214342, -0.0454273, -0.135177, -0.0800739, 0.903659, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229, -0.0930435, 0.174108, -0.063834, 0.0909285, 0.22759, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, -0.159396, 0.299061, -0.116652, 0.238649, 0.109945, 0.192866, 0.307073, 0.191113, 0.658287, -0.0340374, -0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693531, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, -0.395918, 0.231694, -0.160503, 0.383289, 0.0879262, -0.0254665, 0.079043, 0.322652, 0.752701, 0.243775, 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, 0, 0, 0, 0, 0, 0}; std::vector lho_data_gold{-0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693531, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // variable input sequence lengths { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{1, 2}; auto sql = mm->add_literal(seq_len_s, len_data); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); mm->add_return({concat_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& hs_concat = outputs.front(); const auto& res_lho = outputs.back(); std::vector hs_data; std::vector lho_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0352244, 0.0146756, 0.00570924, 0.152446, 0.208683, 0.214342, -0.0454273, -0.135177, -0.0800739, 0.903659, -0.0271321, 0.624762, -0.117084, 0.509115, -0.0175078, 0.182457, 0.304506, 0.313825, 0.397697, 0.300873, 0, 0, 0, 0, 0, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, 0, 0, 0, 0, 0, -0.059911, 0.0552807, 0.306764, 0.794409, 0.194492, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector lho_data_gold{0.0352244, 0.0146756, 0.00570924, 0.152446, 0.208683, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, -0.0271321, 0.624762, -0.117084, 0.509115, -0.0175078, 0.182457, 0.304506, 0.313825, 0.397697, 0.300873}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // last output for output, linear_before_reset = 0 { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 0}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ -0.09280921, 0.18506107, 0.32247013, 0.17034212, -0.00115255, -0.29865006, -0.04513004, -0.10688055, -0.4767866, 0.6317833, 0.00286336, 0.53692746, -0.00617076, 0.04564289, -0.18030001, 0.39584228, 0.53879917, 0.384983, 0.2759448, 0.11611474}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_bidirectional_layout) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 2; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3809, 0.4283, 0.2294, -0.1018, -0.1226, -0.0037, 0.2449, -0.2712, -0.1418, 0.1363, -0.3453, -0.0693, -0.2281, 0.2699, -0.2024, -0.3085, -0.3338, 0.4109, 0.2605, -0.1019, -0.2813, 0.3323, -0.1590, 0.0788, -0.3535, 0.0397, 0.2732, 0.2906, 0.0519, 0.3617, -0.2664, 0.1441, 0.0464, -0.1057, 0.2204, -0.3294, 0.3670, 0.1411, 0.3852, 0.3572, 0.3918, 0.0483, -0.3906, -0.2841, -0.2778, -0.4272, 0.2335, -0.1811, -0.3885, -0.1279, 0.1000, 0.0206, -0.3284, -0.0353, 0.1197, 0.1190, 0.3862, 0.0965, -0.0492, 0.2657, -0.1430, 0.0597, 0.1408, -0.0315, 0.1248, 0.0751, 0.3838, 0.3020, 0.0515, 0.2375, -0.4255, 0.1714, -0.0432, 0.3447, -0.2441, -0.3989, -0.3428, -0.4204, -0.4080, -0.2683, -0.0996, -0.1685, -0.0532, -0.1258, 0.1663, -0.3526, -0.3915, -0.1721, 0.1292, -0.2279}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ -0.2683, 0.0699, -0.4021, -0.1379, 0.0042, -0.2447, 0.4006, 0.0270, -0.0446, 0.1063, 0.1381, 0.1310, -0.3596, 0.3869, 0.3929, 0.2750, 0.0890, 0.3069, -0.1691, -0.2194, -0.1066, 0.3187, -0.4369, -0.0603, -0.0834, -0.1182, -0.2047, 0.3253, -0.2931, 0.2082, 0.0424, 0.1111, -0.2773, -0.0279, -0.0869, 0.1413, -0.4227, -0.3672, 0.4137, 0.0609, 0.4223, -0.4032, 0.2945, 0.3600, 0.3345, -0.3880, -0.0192, -0.0090, -0.2648, 0.4339, -0.0155, 0.4437, -0.1766, 0.1957, 0.2475, 0.3773, -0.2710, 0.3289, -0.2077, -0.2534, -0.0832, -0.1632, 0.0728, 0.2520, 0.4153, 0.1659, -0.4342, 0.0541, 0.1812, -0.2305, 0.4440, 0.0946, 0.0410, -0.4381, -0.3161, 0.3906, -0.3958, -0.4238, 0.1975, 0.3440, 0.1437, -0.0568, 0.1492, -0.4248, -0.3304, 0.2786, -0.1328, -0.3740, -0.3566, 0.3074, 0.0924, 0.2684, -0.1527, 0.1826, 0.2424, 0.2002, 0.3479, -0.1089, 0.3472, -0.3677, -0.4231, -0.0798, -0.3709, 0.3924, 0.2774, -0.3690, -0.0233, 0.2845, 0.1969, 0.1618, -0.3742, -0.3619, 0.2925, -0.1838, -0.1495, -0.3747, 0.0341, -0.4243, -0.0732, -0.3997, 0.2139, 0.2425, 0.4171, -0.3358, 0.3534, 0.0938, -0.0582, -0.2681, -0.4293, 0.1027, 0.4101, 0.2641, -0.4110, -0.1681, 0.3582, -0.2089, 0.0852, 0.0963, 0.3866, 0.1955, -0.2174, 0.1996, -0.2252, 0.1748, 0.1833, -0.3155, 0.2567, -0.4387, 0.3402, 0.0599}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ -0.1582, -0.0826, 0.4008, 0.0118, 0.2511, 0.1900, -0.2838, 0.2549, -0.2484, 0.2363, -0.4083, -0.0295, -0.1161, 0.1211, 0.2509, -0.1414, -0.2628, -0.2992, 0.1517, 0.1817, -0.2783, 0.3183, -0.1629, -0.3108, -0.3418, 0.0411, 0.2203, 0.2187, -0.2990, -0.0416, 0.0209, -0.1024, 0.4443, -0.4420, -0.0330, -0.3591, -0.2990, 0.2167, 0.1395, 0.2317, 0.1318, 0.1909, -0.3615, 0.1953, -0.2582, -0.2217, 0.3723, 0.1458, 0.2630, -0.0377, 0.1754, 0.0800, -0.3964, -0.3247, 0.4219, -0.0900, 0.3553, 0.2614, -0.1298, -0.1124}; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -0.3933, 0.5151, -0.2951, 0.0373, 1.3211, 0.7854, -2.6430, -0.3306, -0.8504, 0.0093, -1.1948, -0.1239, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; std::vector ih_data{-0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; // concatenation of hidden states and last hs output for outputs { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& hs_concat = outputs.front(); const auto& res_lho = outputs.back(); std::vector hs_data; std::vector lho_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0352243, 0.0146756, 0.00570925, 0.152446, 0.208683, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, -0.0930435, 0.174108, -0.063834, 0.0909285, 0.22759, -0.159396, 0.299061, -0.116652, 0.238649, 0.109945, -0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693533, -0.395918, 0.231694, -0.160503, 0.383289, 0.0879262, 0.214342, -0.0454273, -0.135177, -0.0800739, 0.903659, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, 0.192866, 0.307073, 0.191113, 0.658287, -0.0340374, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, -0.0254665, 0.079043, 0.322652, 0.752701, 0.243775}; std::vector lho_data_gold{-0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693533, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // same input sequence length, but shorter than max squence length { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {batch_size, 2, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); std::vector perm_hid{2, 0, 1, 3}; concat_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), concat_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({concat_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& hs_concat = outputs.front(); const auto& res_lho = outputs.back(); std::vector hs_data; std::vector lho_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0352244, 0.0146756, 0.00570924, 0.152446, 0.208683, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, -0.0930435, 0.174108, -0.063834, 0.0909285, 0.22759, -0.159396, 0.299061, -0.116652, 0.238649, 0.109945, -0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693531, -0.395918, 0.231694, -0.160503, 0.383289, 0.0879262, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.214342, -0.0454273, -0.135177, -0.0800739, 0.903659, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, 0.192866, 0.307073, 0.191113, 0.658287, -0.0340374, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, -0.0254665, 0.079043, 0.322652, 0.752701, 0.243775, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector lho_data_gold{-0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693531, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // variable input sequence lengths { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{1, 2}; auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); std::vector perm_hid{2, 0, 1, 3}; concat_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), concat_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({concat_hs, lho}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& hs_concat = outputs.front(); const auto& res_lho = outputs.back(); std::vector hs_data; std::vector lho_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0352244, 0.0146756, 0.00570924, 0.152446, 0.208683, -0.0271321, 0.624762, -0.117084, 0.509115, -0.0175078, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.214342, -0.0454273, -0.135177, -0.0800739, 0.903659, 0.182457, 0.304506, 0.313825, 0.397697, 0.300873, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, -0.059911, 0.0552807, 0.306764, 0.794409, 0.194492, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector lho_data_gold{0.0352244, 0.0146756, 0.00570924, 0.152446, 0.208683, -0.0271321, 0.624762, -0.117084, 0.509115, -0.0175078, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, 0.182457, 0.304506, 0.313825, 0.397697, 0.300873}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); } // last output for output, linear_before_reset = 0 { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 0}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ -0.09280921, 0.18506107, 0.32247013, 0.17034212, -0.00115255, 0.00286336, 0.53692746, -0.00617076, 0.04564289, -0.18030001, -0.29865006, -0.04513004, -0.10688055, -0.4767866, 0.6317833, 0.39584228, 0.53879917, 0.384983, 0.2759448, 0.11611474}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_bidirectional_args) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 2; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3809, 0.4283, 0.2294, -0.1018, -0.1226, -0.0037, 0.2449, -0.2712, -0.1418, 0.1363, -0.3453, -0.0693, -0.2281, 0.2699, -0.2024, -0.3085, -0.3338, 0.4109, 0.2605, -0.1019, -0.2813, 0.3323, -0.1590, 0.0788, -0.3535, 0.0397, 0.2732, 0.2906, 0.0519, 0.3617, -0.2664, 0.1441, 0.0464, -0.1057, 0.2204, -0.3294, 0.3670, 0.1411, 0.3852, 0.3572, 0.3918, 0.0483, -0.3906, -0.2841, -0.2778, -0.4272, 0.2335, -0.1811, -0.3885, -0.1279, 0.1000, 0.0206, -0.3284, -0.0353, 0.1197, 0.1190, 0.3862, 0.0965, -0.0492, 0.2657, -0.1430, 0.0597, 0.1408, -0.0315, 0.1248, 0.0751, 0.3838, 0.3020, 0.0515, 0.2375, -0.4255, 0.1714, -0.0432, 0.3447, -0.2441, -0.3989, -0.3428, -0.4204, -0.4080, -0.2683, -0.0996, -0.1685, -0.0532, -0.1258, 0.1663, -0.3526, -0.3915, -0.1721, 0.1292, -0.2279}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ -0.2683, 0.0699, -0.4021, -0.1379, 0.0042, -0.2447, 0.4006, 0.0270, -0.0446, 0.1063, 0.1381, 0.1310, -0.3596, 0.3869, 0.3929, 0.2750, 0.0890, 0.3069, -0.1691, -0.2194, -0.1066, 0.3187, -0.4369, -0.0603, -0.0834, -0.1182, -0.2047, 0.3253, -0.2931, 0.2082, 0.0424, 0.1111, -0.2773, -0.0279, -0.0869, 0.1413, -0.4227, -0.3672, 0.4137, 0.0609, 0.4223, -0.4032, 0.2945, 0.3600, 0.3345, -0.3880, -0.0192, -0.0090, -0.2648, 0.4339, -0.0155, 0.4437, -0.1766, 0.1957, 0.2475, 0.3773, -0.2710, 0.3289, -0.2077, -0.2534, -0.0832, -0.1632, 0.0728, 0.2520, 0.4153, 0.1659, -0.4342, 0.0541, 0.1812, -0.2305, 0.4440, 0.0946, 0.0410, -0.4381, -0.3161, 0.3906, -0.3958, -0.4238, 0.1975, 0.3440, 0.1437, -0.0568, 0.1492, -0.4248, -0.3304, 0.2786, -0.1328, -0.3740, -0.3566, 0.3074, 0.0924, 0.2684, -0.1527, 0.1826, 0.2424, 0.2002, 0.3479, -0.1089, 0.3472, -0.3677, -0.4231, -0.0798, -0.3709, 0.3924, 0.2774, -0.3690, -0.0233, 0.2845, 0.1969, 0.1618, -0.3742, -0.3619, 0.2925, -0.1838, -0.1495, -0.3747, 0.0341, -0.4243, -0.0732, -0.3997, 0.2139, 0.2425, 0.4171, -0.3358, 0.3534, 0.0938, -0.0582, -0.2681, -0.4293, 0.1027, 0.4101, 0.2641, -0.4110, -0.1681, 0.3582, -0.2089, 0.0852, 0.0963, 0.3866, 0.1955, -0.2174, 0.1996, -0.2252, 0.1748, 0.1833, -0.3155, 0.2567, -0.4387, 0.3402, 0.0599}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; float clip = 0.0f; // 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 0}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0863793, -0.227845, 0.0283059, -0.258645, 0.14187, 0.43541, 0.190748, -0.530196, -0.440444, 0.293767, 0.0402142, 0.0788687, -0.013, -0.233298, -0.0739615, 0.467104, 0.446285, 0.306097, 0.125636, 0.272524, 0.0949838, 0.0522264, -0.0872712, -0.084203, 0.140013, 0.12739, -0.0111171, -0.431119, -0.468382, 0.388067, -0.109174, -0.119064, -0.0242958, -0.180555, 0.118983, 0.341578, 0.275472, 0.0853083, 0.332205, -0.0498387, 0.140338, 0.0319435, 0.247019, 0.275848, -0.158223, 0.0495464, -0.0681034, -0.418158, -0.523234, 0.469122, -0.306578, -0.221095, -0.106449, -0.248934, -0.00682121, 0.288407, 0.198708, 0.0695644, 0.211621, 0.00246037}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 4 args (bias is used) { std::vector bias_data{ -0.1582, -0.0826, 0.4008, 0.0118, 0.2511, 0.1900, -0.2838, 0.2549, -0.2484, 0.2363, -0.4083, -0.0295, -0.1161, 0.1211, 0.2509, -0.1414, -0.2628, -0.2992, 0.1517, 0.1817, -0.2783, 0.3183, -0.1629, -0.3108, -0.3418, 0.0411, 0.2203, 0.2187, -0.2990, -0.0416, 0.0209, -0.1024, 0.4443, -0.4420, -0.0330, -0.3591, -0.2990, 0.2167, 0.1395, 0.2317, 0.1318, 0.1909, -0.3615, 0.1953, -0.2582, -0.2217, 0.3723, 0.1458, 0.2630, -0.0377, 0.1754, 0.0800, -0.3964, -0.3247, 0.4219, -0.0900, 0.3553, 0.2614, -0.1298, -0.1124}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ -0.156667, -0.248473, 0.0255282, -0.24566, 0.211589, 0.192707, 0.253025, -0.515283, -0.414174, 0.227127, 0.124773, 0.284532, -0.203929, -0.120517, -0.2794, 0.547635, 0.518549, 0.0447674, 0.258461, 0.0502881, -0.219516, 0.0927382, -0.0760062, -0.0906231, 0.237615, -0.215638, 0.0128074, -0.425813, -0.433378, 0.375383, -0.0381738, 0.117793, -0.180851, -0.0841245, -0.116649, 0.419469, 0.393515, -0.076395, 0.427436, -0.264071, -0.185829, 0.0483585, 0.242955, 0.25233, 0.0148512, -0.304127, -0.0616653, -0.411568, -0.491748, 0.476508, -0.313413, -0.0361821, -0.173037, -0.235731, -0.163113, 0.349008, 0.248674, -0.0295413, 0.291437, -0.165005}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 4 args (ih is used) { std::vector ih_data{-0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212, -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, und, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.248571, 0.0982155, 0.00808877, 0.0986508, 0.0969705, 0.434692, -0.141696, -0.164271, -0.121157, 0.863222, -0.0718357, 0.137711, 0.109221, -0.00207995, 0.0331223, 0.262705, 0.346587, 0.457158, 0.240744, 0.404261, 0.222779, 0.179757, -0.0845316, 0.0690347, 0.10204, 0.100155, -0.190286, -0.122062, -0.274379, 0.547281, -0.226753, -0.0397069, 0.120404, 0.171299, 0.259989, 0.0864604, 0.111322, 0.331784, 0.604653, 0.181017, 0.237426, 0.0911999, 0.233106, 0.32996, -0.17175, 0.0190231, -0.154805, -0.205631, -0.405354, 0.519054, -0.380409, -0.0350301, -0.00633752, 0.403791, 0.181883, -0.0977917, -0.0339407, 0.413089, 0.721238, 0.431879}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_bidirectional_actv_funcs) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 2; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3809, 0.4283, 0.2294, -0.1018, -0.1226, -0.0037, 0.2449, -0.2712, -0.1418, 0.1363, -0.3453, -0.0693, -0.2281, 0.2699, -0.2024, -0.3085, -0.3338, 0.4109, 0.2605, -0.1019, -0.2813, 0.3323, -0.1590, 0.0788, -0.3535, 0.0397, 0.2732, 0.2906, 0.0519, 0.3617, -0.2664, 0.1441, 0.0464, -0.1057, 0.2204, -0.3294, 0.3670, 0.1411, 0.3852, 0.3572, 0.3918, 0.0483, -0.3906, -0.2841, -0.2778, -0.4272, 0.2335, -0.1811, -0.3885, -0.1279, 0.1000, 0.0206, -0.3284, -0.0353, 0.1197, 0.1190, 0.3862, 0.0965, -0.0492, 0.2657, -0.1430, 0.0597, 0.1408, -0.0315, 0.1248, 0.0751, 0.3838, 0.3020, 0.0515, 0.2375, -0.4255, 0.1714, -0.0432, 0.3447, -0.2441, -0.3989, -0.3428, -0.4204, -0.4080, -0.2683, -0.0996, -0.1685, -0.0532, -0.1258, 0.1663, -0.3526, -0.3915, -0.1721, 0.1292, -0.2279}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ -0.2683, 0.0699, -0.4021, -0.1379, 0.0042, -0.2447, 0.4006, 0.0270, -0.0446, 0.1063, 0.1381, 0.1310, -0.3596, 0.3869, 0.3929, 0.2750, 0.0890, 0.3069, -0.1691, -0.2194, -0.1066, 0.3187, -0.4369, -0.0603, -0.0834, -0.1182, -0.2047, 0.3253, -0.2931, 0.2082, 0.0424, 0.1111, -0.2773, -0.0279, -0.0869, 0.1413, -0.4227, -0.3672, 0.4137, 0.0609, 0.4223, -0.4032, 0.2945, 0.3600, 0.3345, -0.3880, -0.0192, -0.0090, -0.2648, 0.4339, -0.0155, 0.4437, -0.1766, 0.1957, 0.2475, 0.3773, -0.2710, 0.3289, -0.2077, -0.2534, -0.0832, -0.1632, 0.0728, 0.2520, 0.4153, 0.1659, -0.4342, 0.0541, 0.1812, -0.2305, 0.4440, 0.0946, 0.0410, -0.4381, -0.3161, 0.3906, -0.3958, -0.4238, 0.1975, 0.3440, 0.1437, -0.0568, 0.1492, -0.4248, -0.3304, 0.2786, -0.1328, -0.3740, -0.3566, 0.3074, 0.0924, 0.2684, -0.1527, 0.1826, 0.2424, 0.2002, 0.3479, -0.1089, 0.3472, -0.3677, -0.4231, -0.0798, -0.3709, 0.3924, 0.2774, -0.3690, -0.0233, 0.2845, 0.1969, 0.1618, -0.3742, -0.3619, 0.2925, -0.1838, -0.1495, -0.3747, 0.0341, -0.4243, -0.0732, -0.3997, 0.2139, 0.2425, 0.4171, -0.3358, 0.3534, 0.0938, -0.0582, -0.2681, -0.4293, 0.1027, 0.4101, 0.2641, -0.4110, -0.1681, 0.3582, -0.2089, 0.0852, 0.0963, 0.3866, 0.1955, -0.2174, 0.1996, -0.2252, 0.1748, 0.1833, -0.3155, 0.2567, -0.4387, 0.3402, 0.0599}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ -0.1582, -0.0826, 0.4008, 0.0118, 0.2511, 0.1900, -0.2838, 0.2549, -0.2484, 0.2363, -0.4083, -0.0295, -0.1161, 0.1211, 0.2509, -0.1414, -0.2628, -0.2992, 0.1517, 0.1817, -0.2783, 0.3183, -0.1629, -0.3108, -0.3418, 0.0411, 0.2203, 0.2187, -0.2990, -0.0416, 0.0209, -0.1024, 0.4443, -0.4420, -0.0330, -0.3591, -0.2990, 0.2167, 0.1395, 0.2317, 0.1318, 0.1909, -0.3615, 0.1953, -0.2582, -0.2217, 0.3723, 0.1458, 0.2630, -0.0377, 0.1754, 0.0800, -0.3964, -0.3247, 0.4219, -0.0900, 0.3553, 0.2614, -0.1298, -0.1124}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; std::vector ih_data{-0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212, -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; // no activation function specified, so default is used. { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693533, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 1 activation function (sigmoid) specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 0}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.325495, 0.469214, 0.164517, 0.585327, 0.328398, 0.457928, 0.065011, 0.35986, 0.545029, 0.859425, 0.427923, 0.667133, 0.41591, 0.540971, 0.365475, 0.482058, 0.565495, 0.556993, 0.607649, 0.543627, 0.428915, 0.537405, 0.306046, 0.518399, 0.403561, 0.410694, 0.301163, 0.407397, 0.471334, 0.726446, 0.309389, 0.612072, 0.360619, 0.590861, 0.366545, 0.367001, 0.433829, 0.501275, 0.72481, 0.512745, 0.463795, 0.539649, 0.487682, 0.554471, 0.395916, 0.430744, 0.415923, 0.424275, 0.409655, 0.698256, 0.126883, 0.554374, 0.216137, 0.671491, 0.263833, 0.0678646, 0.132732, 0.477083, 0.802206, 0.626802}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 2 activation functions (tanh) specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0919632, -0.398302, -0.0267752, -0.326771, 0.401983, 0.949841, 0.557779, -0.745259, -1.52726, 0.946066, 0.330446, 0.301982, -0.443763, -0.0655817, -0.326473, 0.861394, 0.560799, -0.101768, 0.145142, 0.128956, -0.329758, 0.458253, -0.339208, 0.289109, 0.36728, -1.09574, -0.181394, -0.575781, -0.823083, 0.804262, -0.0965933, 0.20405, -0.430215, 0.00884668, 0.0716857, 0.844222, 0.516472, -0.191571, 0.596968, -0.545405, -0.336693, -0.0280516, 0.339058, 1.00367, 0.12655, -0.0984504, -0.174945, -0.5365, 0.183188, 0.66716, -0.704461, -0.393346, -0.627123, 0.210395, 0.0563026, 0.31419, 0.759629, 0.000258222, 0.350835, -0.682684}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 3 activation functions specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto concat_hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), concat_hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.351019, 0.474363, 0.570719, 0.717703, 0.468843, 1.15142, 0.457633, 0.300962, 0.361245, 0.666199, 0.330446, 0.301982, -0.443763, -0.0655817, -0.326473, 0.861394, 0.560799, -0.101768, 0.145142, 0.128956}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // 4 activation functions all specified { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0352243, 0.0146756, 0.00570925, 0.152446, 0.208683, 0.214342, -0.0454273, -0.135177, -0.0800739, 0.903659, 0.0248217, 0.435231, -0.144448, 0.101531, -0.111305, 0.381317, 0.468983, 0.230557, 0.348021, 0.180229, -0.0930435, 0.174108, -0.063834, 0.0909285, 0.22759, -0.221983, -0.139656, -0.0938906, -0.247681, 0.69647, -0.159396, 0.299061, -0.116652, 0.238649, 0.109945, 0.192866, 0.307073, 0.191113, 0.658287, -0.0340374, -0.0959787, 0.0794681, 0.241526, 0.321104, 0.00693533, -0.311839, -0.12802, -0.16643, -0.393849, 0.648851, -0.395918, 0.231694, -0.160503, 0.383289, 0.0879262, -0.0254665, 0.079043, 0.322652, 0.752701, 0.243775}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(gru_bidirectional_seq_1) { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 2; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3809, 0.4283, 0.2294, -0.1018, -0.1226, -0.0037, 0.2449, -0.2712, -0.1418, 0.1363, -0.3453, -0.0693, -0.2281, 0.2699, -0.2024, -0.3085, -0.3338, 0.4109, 0.2605, -0.1019, -0.2813, 0.3323, -0.1590, 0.0788, -0.3535, 0.0397, 0.2732, 0.2906, 0.0519, 0.3617, -0.2664, 0.1441, 0.0464, -0.1057, 0.2204, -0.3294, 0.3670, 0.1411, 0.3852, 0.3572, 0.3918, 0.0483, -0.3906, -0.2841, -0.2778, -0.4272, 0.2335, -0.1811, -0.3885, -0.1279, 0.1000, 0.0206, -0.3284, -0.0353, 0.1197, 0.1190, 0.3862, 0.0965, -0.0492, 0.2657, -0.1430, 0.0597, 0.1408, -0.0315, 0.1248, 0.0751, 0.3838, 0.3020, 0.0515, 0.2375, -0.4255, 0.1714, -0.0432, 0.3447, -0.2441, -0.3989, -0.3428, -0.4204, -0.4080, -0.2683, -0.0996, -0.1685, -0.0532, -0.1258, 0.1663, -0.3526, -0.3915, -0.1721, 0.1292, -0.2279}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ -0.2683, 0.0699, -0.4021, -0.1379, 0.0042, -0.2447, 0.4006, 0.0270, -0.0446, 0.1063, 0.1381, 0.1310, -0.3596, 0.3869, 0.3929, 0.2750, 0.0890, 0.3069, -0.1691, -0.2194, -0.1066, 0.3187, -0.4369, -0.0603, -0.0834, -0.1182, -0.2047, 0.3253, -0.2931, 0.2082, 0.0424, 0.1111, -0.2773, -0.0279, -0.0869, 0.1413, -0.4227, -0.3672, 0.4137, 0.0609, 0.4223, -0.4032, 0.2945, 0.3600, 0.3345, -0.3880, -0.0192, -0.0090, -0.2648, 0.4339, -0.0155, 0.4437, -0.1766, 0.1957, 0.2475, 0.3773, -0.2710, 0.3289, -0.2077, -0.2534, -0.0832, -0.1632, 0.0728, 0.2520, 0.4153, 0.1659, -0.4342, 0.0541, 0.1812, -0.2305, 0.4440, 0.0946, 0.0410, -0.4381, -0.3161, 0.3906, -0.3958, -0.4238, 0.1975, 0.3440, 0.1437, -0.0568, 0.1492, -0.4248, -0.3304, 0.2786, -0.1328, -0.3740, -0.3566, 0.3074, 0.0924, 0.2684, -0.1527, 0.1826, 0.2424, 0.2002, 0.3479, -0.1089, 0.3472, -0.3677, -0.4231, -0.0798, -0.3709, 0.3924, 0.2774, -0.3690, -0.0233, 0.2845, 0.1969, 0.1618, -0.3742, -0.3619, 0.2925, -0.1838, -0.1495, -0.3747, 0.0341, -0.4243, -0.0732, -0.3997, 0.2139, 0.2425, 0.4171, -0.3358, 0.3534, 0.0938, -0.0582, -0.2681, -0.4293, 0.1027, 0.4101, 0.2641, -0.4110, -0.1681, 0.3582, -0.2089, 0.0852, 0.0963, 0.3866, 0.1955, -0.2174, 0.1996, -0.2252, 0.1748, 0.1833, -0.3155, 0.2567, -0.4387, 0.3402, 0.0599}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ -0.1582, -0.0826, 0.4008, 0.0118, 0.2511, 0.1900, -0.2838, 0.2549, -0.2484, 0.2363, -0.4083, -0.0295, -0.1161, 0.1211, 0.2509, -0.1414, -0.2628, -0.2992, 0.1517, 0.1817, -0.2783, 0.3183, -0.1629, -0.3108, -0.3418, 0.0411, 0.2203, 0.2187, -0.2990, -0.0416, 0.0209, -0.1024, 0.4443, -0.4420, -0.0330, -0.3591, -0.2990, 0.2167, 0.1395, 0.2317, 0.1318, 0.1909, -0.3615, 0.1953, -0.2582, -0.2217, 0.3723, 0.1458, 0.2630, -0.0377, 0.1754, 0.0800, -0.3964, -0.3247, 0.4219, -0.0900, 0.3553, 0.2614, -0.1298, -0.1124}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; std::vector ih_data{-0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212, -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape_one{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input_one{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504}; auto seq = mm->add_literal(migraphx::literal{in_shape_one, input_one}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"linear_before_reset", 1}}), seq, w, r, bias, und, ih); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.0352243, 0.0146756, 0.00570925, 0.152446, 0.208683, 0.214342, -0.0454273, -0.135177, -0.0800739, 0.903659, -0.0271321, 0.624762, -0.117084, 0.509115, -0.0175078, -0.144492, -0.0115366, 0.409153, 0.487015, 0.550755}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } TEST_CASE(gru_fp16) { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 3; std::size_t num_dirct = 1; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; std::vector w_data{ 0.3485, -0.0378, -0.1782, 0.1416, -0.3096, -0.2212, -0.3883, 0.1983, -0.2418, 0.1480, -0.3255, 0.1359, -0.3551, -0.3605, -0.3482, -0.1424, -0.0495, -0.1640, -0.1979, -0.2577, -0.4097, -0.1211, -0.0412, 0.1801, 0.1721, -0.4327, -0.0498, 0.2628, -0.1573, -0.1577, 0.2759, -0.2023, -0.1185, -0.2136, 0.1294, -0.2331, 0.0701, 0.4316, 0.0480, 0.0247, -0.0166, -0.2729, 0.1712, -0.3984, -0.3905}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; std::vector r_data{ 0.2848, -0.2851, -0.3466, -0.1718, -0.1492, -0.0082, 0.2452, -0.0401, 0.3399, 0.2529, -0.0953, -0.0903, -0.1518, -0.1373, 0.3848, -0.0130, -0.4339, 0.0406, -0.1926, -0.1131, 0.4285, -0.0013, 0.2243, 0.2752, 0.1776, -0.1720, 0.0822, -0.0295, 0.1062, -0.2721, -0.2736, -0.1826, 0.3541, -0.4259, 0.2188, 0.0706, 0.3650, 0.3947, 0.2522, 0.2179, -0.0744, 0.2122, -0.4346, 0.2760, 0.4076, 0.1183, -0.1500, -0.1704, 0.3090, -0.0706, -0.2442, 0.3021, 0.1680, 0.0783, -0.3754, -0.3469, -0.2972, -0.0170, 0.4143, 0.3801, 0.3852, -0.1170, -0.2937, 0.2979, -0.1357, 0.4257, 0.3884, -0.2916, 0.1071, 0.0934, 0.3645, -0.4310, -0.3480, 0.0702, -0.1558}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; std::vector bias_data{ 0.0560, 0.0310, -0.1669, -0.0781, 0.1793, -0.1758, 0.3173, -0.1650, -0.3732, 0.2946, -0.0912, 0.3118, 0.1391, 0.2755, 0.2695, -0.1059, -0.2357, 0.3629, -0.2534, -0.0494, 0.0556, 0.0881, -0.2592, -0.2213, 0.2310, -0.4044, 0.1801, 0.1438, 0.3108, -0.3607}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input{-0.8432, -0.9887, 1.3041, -2.6430, -0.3306, -0.8504, -0.3933, 0.5151, -0.2951, 0.0093, -1.1948, -0.1239, 0.0373, 1.3211, 0.7854, -0.4838, -1.0536, -0.2529}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; std::vector ih_data{ -0.0468, 0.5691, -0.0882, 0.8340, 0.1483, -0.3902, -0.5348, 0.4178, 1.0175, 0.9212}; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto seq_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), seq); auto w_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), w); auto r_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), r); auto bias_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), bias); auto und_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), und); auto ih_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), ih); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"linear_before_reset", 1}}), seq_half, w_half, r_half, bias_half, und_half, ih_half); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{-0.27298412, 0.42363745, -0.09368783, 0.4823072, -0.02183238, -0.6873896, 0.16144305, 0.31932795, 0.6104771, 0.79759157, -0.31791314, 0.5249062, 0.08800987, 0.46404213, -0.11872687, -0.26210734, 0.34448293, -0.0176422, 0.48523626, 0.60002893, -0.3969709, 0.43360898, 0.35775262, 0.23280787, -0.52179873, -0.21944991, 0.4535257, -0.13735442, 0.51757574, 0.50380427}; EXPECT(migraphx::verify::verify_range_with_tolerance( hs_data, migraphx::verify::expected{hs_data_gold}, migraphx::verify::tolerance{0.005})); } TEST_CASE(lstm_forward) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260}; std::vector bias_data{0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331, -0.0910, 1.2122, -0.1952, 0.4661, 0.6494, 2.1332, -1.0972, 0.9816, 0.1122, 0.3577, 1.3508, -0.5366, 1.7449, 0.5483, -0.0701, -0.4100, -2.2344, 0.3685, 0.4583, 2.3794, 1.0372, -0.8887, 0.7892, -0.4012, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 0.5671, 0.0458, 0.4514, -0.8968, -0.9201, 0.1962, 0.5771, -0.5332}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, 1.1055, -0.1212, -0.9097, 0.7831, -1.6991, -1.9498, -1.2567, -0.4114}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; // forward, hidden state concatenation as output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, und); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0417273, -0.272355, 0.206765, 0.223879, 0.138193, -0.0322939, -0.0891815, 0.15773, 0.19139, -0.127708, -0.409371, -0.136186, 0.0742487, -0.0800085, 0.259897, 0.0670196, 0.184266, 0.0610048, -0.138041, 0.0963885, 0.0213755, -0.146027, -0.0324509, -0.0620429, -0.00532985, 0.0440265, 0.29654, -0.0463156, 0.0498799, 0.125772, 0.0533032, -0.131413, 0.0988431, -0.018085, -0.159434, 0.030266, -0.0847427, 0.0874114, 0.304256, -0.0585745, -0.0223018, 0.131113, 0.135643, -0.0566208, 0.142701, 0.0342236, -0.198664, 0.0702607}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // forward, last_output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto last_hs = p.eval({}).back(); std::vector output_data; last_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.0847427, 0.0874114, 0.304256, -0.0585745, -0.0223018, 0.131113, 0.135643, -0.0566208, 0.142701, 0.0342236, -0.198664, 0.0702607}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // forward, last_cell_output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, und); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); p.compile(migraphx::make_target("ref")); auto last_hs = p.eval({}).back(); std::vector output_data; last_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.111454, 0.247794, 0.471087, -0.220574, -0.048196, 0.263184, 0.283258, -0.14882, 0.605585, 0.078598, -0.64457, 0.119811}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } } TEST_CASE(lstm_forward_layout) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260}; std::vector bias_data{0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.0910, 1.2122, -0.1952, 0.3577, 1.3508, -0.5366, 0.4583, 2.3794, 1.0372, -0.4313, -0.9730, -0.2005, 0.4661, 0.6494, 2.1332, 1.7449, 0.5483, -0.0701, -0.8887, 0.7892, -0.4012, 2.3930, -0.5221, -0.1331, -1.0972, 0.9816, 0.1122, -0.4100, -2.2344, 0.3685, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 0.5671, 0.0458, 0.4514, -0.8968, -0.9201, 0.1962, 0.5771, -0.5332}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, 1.1055, -0.1212, -0.9097, 0.7831, -1.6991, -1.9498, -1.2567, -0.4114}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; // forward, hidden state concatenation as output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, und); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0417273, -0.272355, 0.206765, 0.223879, 0.0742487, -0.0800085, 0.259897, 0.0670196, -0.00532985, 0.0440265, 0.29654, -0.0463156, -0.0847427, 0.0874114, 0.304256, -0.0585745, 0.138193, -0.0322939, -0.0891815, 0.15773, 0.184266, 0.0610048, -0.138041, 0.0963885, 0.0498799, 0.125772, 0.0533032, -0.131413, -0.0223018, 0.131113, 0.135643, -0.056620, 0.19139, -0.127708, -0.409371, -0.136186, 0.0213755, -0.146027, -0.0324509, -0.0620429, 0.0988431, -0.018085, -0.159434, 0.030266, 0.142701, 0.0342236, -0.198664, 0.0702607}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // forward, last_output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, und); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); p.compile(migraphx::make_target("ref")); auto last_hs = p.eval({}).back(); std::vector output_data; last_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.0847427, 0.0874114, 0.304256, -0.0585745, -0.0223018, 0.131113, 0.135643, -0.0566208, 0.142701, 0.0342236, -0.198664, 0.0702607}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // forward, last_cell_output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, und); auto cell_output = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), cell_output); p.compile(migraphx::make_target("ref")); auto last_hs = p.eval({}).back(); std::vector output_data; last_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.111454, 0.247794, 0.471087, -0.220574, -0.048196, 0.263184, 0.283258, -0.14882, 0.605585, 0.078598, -0.64457, 0.119811}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } } TEST_CASE(lstm_forward_more) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260}; std::vector bias_data{0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331, -0.0910, 1.2122, -0.1952, 0.4661, 0.6494, 2.1332, -1.0972, 0.9816, 0.1122, 0.3577, 1.3508, -0.5366, 1.7449, 0.5483, -0.0701, -0.4100, -2.2344, 0.3685, 0.4583, 2.3794, 1.0372, -0.8887, 0.7892, -0.4012, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 0.5671, 0.0458, 0.4514, -0.8968, -0.9201, 0.1962, 0.5771, -0.5332}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, 1.1055, -0.1212, -0.9097, 0.7831, -1.6991, -1.9498, -1.2567, -0.4114}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; // forward, 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto last_hs = p.eval({}).back(); std::vector output_data; last_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.0327039, -0.0543852, 0.114378, -0.0768855, 0.0319021, -0.00298698, -0.0623361, 0.0598866, 0.101585, 0.0687269, -0.161725, -0.25617, -0.0786602, -0.0613048, 0.179592, -0.071286, 0.074206, 0.0124086, -0.139544, 0.108016, -0.00973633, -0.0552699, 0.0252681, -0.0562072, -0.102509, -0.0372696, 0.252296, -0.144544, 0.00496085, 0.0662588, -0.048577, -0.187329, 0.0855831, -0.0171894, -0.140202, 0.0828391, -0.165194, -0.0372928, 0.273786, -0.100877, -0.0458544, -0.0401315, 0.0737483, -0.064505, 0.136898, 0.00160891, -0.184812, 0.147774}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // forward, 8 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.022618, -0.121195, -0.4065, -0.252054, 0.186991, -0.0624168, 0.205513, 0.0836373, 0.421857, 0.0459771, -0.144955, 0.0720673, -0.0300906, -0.0890598, -0.135266, -0.0413375, 0.0459032, 0.0414126, 0.272303, 0.0393149, 0.218258, 0.0944405, 0.0431211, -0.132394, 0.103489, 0.0142918, -0.123408, 0.0401075, -0.058052, 0.0795391, 0.266617, -0.0128746, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.187761, 0.0501726, -0.121584, 0.0606723}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // forward, last_output as program output, sequence length shorter // than max_seq_len { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {2, batch_size, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto last_hs = p.eval({}).back(); std::vector output_data; last_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.0847427, 0.0874114, 0.304256, -0.0585745, -0.0223018, 0.131113, 0.135643, -0.0566208, 0.142701, 0.0342236, -0.198664, 0.0702607}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // seq_len = 1 { seq_len = 1; migraphx::shape in_shape1{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input_data1{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape1, input_data1}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.079753, -0.289854, 0.160043, 0.115056, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.022618, -0.121195, -0.4065, -0.252054}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(lstm_forward_more_layout) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260}; std::vector bias_data{0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.0910, 1.2122, -0.1952, 0.3577, 1.3508, -0.5366, 0.4583, 2.3794, 1.0372, -0.4313, -0.9730, -0.2005, 0.4661, 0.6494, 2.1332, 1.7449, 0.5483, -0.0701, -0.8887, 0.7892, -0.4012, 2.3930, -0.5221, -0.1331, -1.0972, 0.9816, 0.1122, -0.4100, -2.2344, 0.3685, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 0.5671, 0.0458, 0.4514, -0.8968, -0.9201, 0.1962, 0.5771, -0.5332}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, 1.1055, -0.1212, -0.9097, 0.7831, -1.6991, -1.9498, -1.2567, -0.4114}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; // forward, 3 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto last_hs = p.eval({}).back(); std::vector output_data; last_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.0327039, -0.0543852, 0.114378, -0.0768855, -0.0786602, -0.0613048, 0.179592, -0.071286, -0.102509, -0.0372696, 0.252296, -0.144544, -0.165194, -0.0372928, 0.273786, -0.100877, 0.0319021, -0.00298698, -0.0623361, 0.0598866, 0.074206, 0.0124086, -0.139544, 0.108016, 0.00496085, 0.0662588, -0.048577, -0.187329, -0.0458544, -0.0401315, 0.0737483, -0.064505, 0.101585, 0.0687269, -0.161725, -0.25617, -0.00973633, -0.0552699, 0.0252681, -0.0562072, 0.0855831, -0.0171894, -0.140202, 0.0828391, 0.136898, 0.00160891, -0.184812, 0.147774}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // forward, 8 args { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, 0.186991, -0.0624168, 0.205513, 0.0836373, 0.0459033, 0.0414126, 0.272303, 0.0393149, -0.058052, 0.0795391, 0.266617, -0.0128746, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.421857, 0.0459771, -0.144955, 0.0720673, 0.218258, 0.0944405, 0.0431211, -0.132394, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.022618, -0.121195, -0.4065, -0.252054, -0.0300906, -0.0890598, -0.135266, -0.0413375, 0.103489, 0.0142918, -0.123408, 0.0401075, 0.187761, 0.0501726, -0.121584, 0.0606723}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } // forward, last_output as program output, sequence length shorter // than max_seq_len { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {batch_size, 2, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, und); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); p.compile(migraphx::make_target("ref")); auto last_hs = p.eval({}).back(); std::vector output_data; last_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.0847427, 0.0874114, 0.304256, -0.0585745, -0.0223018, 0.131113, 0.135643, -0.0566208, 0.142701, 0.0342236, -0.198664, 0.0702607}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // seq_len = 1 { seq_len = 1; migraphx::shape in_shape1{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; std::vector input_data1{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape1, input_data1}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{0.079753, -0.289854, 0.160043, 0.115056, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.022618, -0.121195, -0.4065, -0.252054}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); } } TEST_CASE(lstm_reverse) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{ -0.2763, -0.4715, -0.3010, -0.2306, -0.2283, -0.2656, 0.2035, 0.3570, -0.1499, 0.4390, -0.1843, 0.2351, 0.3357, 0.1217, 0.1401, 0.3300, -0.0429, 0.3266, 0.4834, -0.3914, -0.1480, 0.3734, -0.0372, -0.1746, 0.0550, 0.4177, -0.1332, 0.4391, -0.3287, -0.4401, 0.1486, 0.1346, 0.1048, -0.4361, 0.0886, -0.3840, -0.2730, -0.1710, 0.3274, 0.0169, -0.4462, 0.0729, 0.3983, -0.0669, 0.0756, 0.4150, -0.4684, -0.2522}; std::vector r_data{ -0.4564, -0.4432, 0.1605, 0.4387, 0.0034, 0.4116, 0.2824, 0.4775, -0.2729, -0.4707, 0.1363, 0.2218, 0.0559, 0.2828, 0.2093, 0.4687, 0.3794, -0.1069, -0.3049, 0.1430, -0.2506, 0.4644, 0.2755, -0.3645, -0.3155, 0.1425, 0.2891, 0.1786, -0.3274, 0.2365, 0.2522, -0.4312, -0.0562, -0.2748, 0.0776, -0.3154, 0.2851, -0.3930, -0.1174, 0.4360, 0.2436, 0.0164, -0.0680, 0.3403, -0.2857, -0.0459, -0.2991, -0.2624, 0.4194, -0.3291, -0.4659, 0.3300, 0.0454, 0.4981, -0.4706, -0.4584, 0.2596, 0.2871, -0.3509, -0.1910, 0.3987, -0.1687, -0.0032, -0.1038}; std::vector bias_data{-0.0258, 0.0073, -0.4780, -0.4101, -0.3556, -0.1017, 0.3632, -0.1823, 0.1479, 0.1677, -0.2603, 0.0381, 0.1575, 0.1896, 0.4755, -0.4794, 0.2167, -0.4474, -0.3139, 0.1018, 0.4470, -0.4232, 0.3247, -0.1636, -0.1582, -0.1703, 0.3920, 0.2055, -0.4386, 0.4208, 0.0717, 0.3789}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331, -0.0910, 1.2122, -0.1952, 0.4661, 0.6494, 2.1332, -1.0972, 0.9816, 0.1122, 0.3577, 1.3508, -0.5366, 1.7449, 0.5483, -0.0701, -0.4100, -2.2344, 0.3685, 0.4583, 2.3794, 1.0372, -0.8887, 0.7892, -0.4012, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.5289, 1.0986, 0.6091, 1.6462, 0.8720, 0.5349, -0.1962, -1.7416, -0.9912, 1.2831, 1.0896, -0.6959}; std::vector ic_data{-0.8323, 0.3998, 0.1831, 0.5938, 2.7096, -0.1790, 0.0022, -0.8040, 0.1578, 0.0567, 0.8069, -0.5141}; std::vector pph_data{-0.8271, -0.5683, 0.4562, -1.2545, 1.2729, -0.4082, -0.4392, -0.9406, 0.7794, 1.8194, -0.5811, 0.2166}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; float clip = 0.0f; // reverse, concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.120174, 0.043157, 0.117138, -0.222188, 0.789732, 0.128538, 0.20909, 0.0553812, -0.224905, 0.32421, 0.344048, 0.271694, -0.175114, -0.00543549, 0.178681, -0.266999, 0.928866, 0.113685, 0.220626, -0.0432316, -0.063456, 0.148524, 0.05108, -0.0234895, -0.182201, -0.0232277, 0.235501, -0.213485, 0.960938, 0.133565, 0.269741, 0.130438, -0.0252804, 0.267356, 0.146353, 0.0789186, -0.185038, -0.026845, 0.177273, -0.0774616, 0.946669, 0.0868676, 0.044508, -0.373961, -0.0681467, 0.382748, 0.230211, -0.161537}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // reverse, sequence lengths are the same, but less than max_seq_lens { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {2, batch_size, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, pph); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.120174, 0.043157, 0.117138, -0.222188, 0.789732, 0.128538, 0.20909, 0.0553812, -0.224905, 0.32421, 0.344048, 0.271694, -0.175114, -0.00543549, 0.178681, -0.266999, 0.928866, 0.113685, 0.220626, -0.0432316, -0.063456, 0.148524, 0.05108, -0.0234895, -0.182201, -0.0232277, 0.235501, -0.213485, 0.960938, 0.133565, 0.269741, 0.130438, -0.0252804, 0.267356, 0.146353, 0.0789186, -0.185038, -0.026845, 0.177273, -0.0774616, 0.946669, 0.0868676, 0.044508, -0.373961, -0.0681467, 0.382748, 0.230211, -0.161537, 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, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // variable sequence lengths { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{3, 2, 1}; auto sql = mm->add_literal(seq_len_s, len_data); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, pph); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.126517, 0.0359124, 0.107453, -0.0617278, 0.911307, 0.11468, 0.114449, 0.0196755, -0.102969, 0.295872, 0.515859, 0.246501, -0.168327, 0.00023761, 0.167567, -0.0621982, 0.96657, 0.0755112, 0.0620917, -0.264845, 0, 0, 0, 0, -0.204545, 0.0146403, 0.210057, 0.0296268, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // reverse, 3 args, last cell output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.443077, -0.325425, -0.249367, -0.270812, 0.122913, 0.118537, 0.0370199, -0.0164687, -0.00754759, 0.141613, 0.348002, 0.667298}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // reverse, 3 args, 0 actv function { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.443077, -0.325425, -0.249367, -0.270812, 0.122913, 0.118537, 0.0370199, -0.0164687, -0.00754759, 0.141613, 0.348002, 0.667298}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } } TEST_CASE(lstm_reverse_layout) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{ -0.2763, -0.4715, -0.3010, -0.2306, -0.2283, -0.2656, 0.2035, 0.3570, -0.1499, 0.4390, -0.1843, 0.2351, 0.3357, 0.1217, 0.1401, 0.3300, -0.0429, 0.3266, 0.4834, -0.3914, -0.1480, 0.3734, -0.0372, -0.1746, 0.0550, 0.4177, -0.1332, 0.4391, -0.3287, -0.4401, 0.1486, 0.1346, 0.1048, -0.4361, 0.0886, -0.3840, -0.2730, -0.1710, 0.3274, 0.0169, -0.4462, 0.0729, 0.3983, -0.0669, 0.0756, 0.4150, -0.4684, -0.2522}; std::vector r_data{ -0.4564, -0.4432, 0.1605, 0.4387, 0.0034, 0.4116, 0.2824, 0.4775, -0.2729, -0.4707, 0.1363, 0.2218, 0.0559, 0.2828, 0.2093, 0.4687, 0.3794, -0.1069, -0.3049, 0.1430, -0.2506, 0.4644, 0.2755, -0.3645, -0.3155, 0.1425, 0.2891, 0.1786, -0.3274, 0.2365, 0.2522, -0.4312, -0.0562, -0.2748, 0.0776, -0.3154, 0.2851, -0.3930, -0.1174, 0.4360, 0.2436, 0.0164, -0.0680, 0.3403, -0.2857, -0.0459, -0.2991, -0.2624, 0.4194, -0.3291, -0.4659, 0.3300, 0.0454, 0.4981, -0.4706, -0.4584, 0.2596, 0.2871, -0.3509, -0.1910, 0.3987, -0.1687, -0.0032, -0.1038}; std::vector bias_data{-0.0258, 0.0073, -0.4780, -0.4101, -0.3556, -0.1017, 0.3632, -0.1823, 0.1479, 0.1677, -0.2603, 0.0381, 0.1575, 0.1896, 0.4755, -0.4794, 0.2167, -0.4474, -0.3139, 0.1018, 0.4470, -0.4232, 0.3247, -0.1636, -0.1582, -0.1703, 0.3920, 0.2055, -0.4386, 0.4208, 0.0717, 0.3789}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.0910, 1.2122, -0.1952, 0.3577, 1.3508, -0.5366, 0.4583, 2.3794, 1.0372, -0.4313, -0.9730, -0.2005, 0.4661, 0.6494, 2.1332, 1.7449, 0.5483, -0.0701, -0.8887, 0.7892, -0.4012, 2.3930, -0.5221, -0.1331, -1.0972, 0.9816, 0.1122, -0.4100, -2.2344, 0.3685, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.5289, 1.0986, 0.6091, 1.6462, 0.8720, 0.5349, -0.1962, -1.7416, -0.9912, 1.2831, 1.0896, -0.6959}; std::vector ic_data{-0.8323, 0.3998, 0.1831, 0.5938, 2.7096, -0.1790, 0.0022, -0.8040, 0.1578, 0.0567, 0.8069, -0.5141}; std::vector pph_data{-0.8271, -0.5683, 0.4562, -1.2545, 1.2729, -0.4082, -0.4392, -0.9406, 0.7794, 1.8194, -0.5811, 0.2166}; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; float clip = 0.0f; // reverse, concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.120174, 0.043157, 0.117138, -0.222188, -0.175114, -0.00543549, 0.178681, -0.266999, -0.182201, -0.0232277, 0.235501, -0.213485, -0.185038, -0.026845, 0.177273, -0.0774616, 0.789732, 0.128538, 0.20909, 0.0553812, 0.928866, 0.113685, 0.220626, -0.0432316, 0.960938, 0.133565, 0.269741, 0.130438, 0.946669, 0.0868676, 0.044508, -0.373961, -0.224905, 0.32421, 0.344048, 0.271694, -0.063456, 0.148524, 0.05108, -0.0234895, -0.0252804, 0.267356, 0.146353, 0.0789186, -0.0681467, 0.382748, 0.230211, -0.161537}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // reverse, sequence lengths are the same, but less than max_seq_lens { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {batch_size, 2, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, pph); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.120174, 0.043157, 0.117138, -0.222188, -0.175114, -0.00543549, 0.178681, -0.266999, -0.182201, -0.0232277, 0.235501, -0.213485, -0.185038, -0.026845, 0.177273, -0.0774616, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.789732, 0.128538, 0.20909, 0.0553812, 0.928866, 0.113685, 0.220626, -0.0432316, 0.960938, 0.133565, 0.269741, 0.130438, 0.946669, 0.0868676, 0.044508, -0.373961, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.224905, 0.32421, 0.344048, 0.271694, -0.063456, 0.148524, 0.05108, -0.0234895, -0.0252804, 0.267356, 0.146353, 0.0789186, -0.0681467, 0.382748, 0.230211, -0.161537, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // variable sequence lengths { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data{3, 2, 1}; auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, pph); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.126517, 0.0359124, 0.107453, -0.0617278, -0.168327, 0.00023761, 0.167567, -0.0621982, -0.204545, 0.0146403, 0.210057, 0.0296268, 0, 0, 0, 0, 0.911307, 0.11468, 0.114449, 0.0196755, 0.96657, 0.0755112, 0.0620917, -0.264845, 0, 0, 0, 0, 0, 0, 0, 0, -0.102969, 0.295872, 0.515859, 0.246501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // reverse, 3 args, last cell output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); auto cell_output = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), cell_output); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.443077, -0.325425, -0.249367, -0.270812, 0.122913, 0.118537, 0.0370199, -0.0164687, -0.00754759, 0.141613, 0.348002, 0.667298}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } } // lstm activation function test TEST_CASE(lstm_reverse_actv) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{ -0.2763, -0.4715, -0.3010, -0.2306, -0.2283, -0.2656, 0.2035, 0.3570, -0.1499, 0.4390, -0.1843, 0.2351, 0.3357, 0.1217, 0.1401, 0.3300, -0.0429, 0.3266, 0.4834, -0.3914, -0.1480, 0.3734, -0.0372, -0.1746, 0.0550, 0.4177, -0.1332, 0.4391, -0.3287, -0.4401, 0.1486, 0.1346, 0.1048, -0.4361, 0.0886, -0.3840, -0.2730, -0.1710, 0.3274, 0.0169, -0.4462, 0.0729, 0.3983, -0.0669, 0.0756, 0.4150, -0.4684, -0.2522}; std::vector r_data{ -0.4564, -0.4432, 0.1605, 0.4387, 0.0034, 0.4116, 0.2824, 0.4775, -0.2729, -0.4707, 0.1363, 0.2218, 0.0559, 0.2828, 0.2093, 0.4687, 0.3794, -0.1069, -0.3049, 0.1430, -0.2506, 0.4644, 0.2755, -0.3645, -0.3155, 0.1425, 0.2891, 0.1786, -0.3274, 0.2365, 0.2522, -0.4312, -0.0562, -0.2748, 0.0776, -0.3154, 0.2851, -0.3930, -0.1174, 0.4360, 0.2436, 0.0164, -0.0680, 0.3403, -0.2857, -0.0459, -0.2991, -0.2624, 0.4194, -0.3291, -0.4659, 0.3300, 0.0454, 0.4981, -0.4706, -0.4584, 0.2596, 0.2871, -0.3509, -0.1910, 0.3987, -0.1687, -0.0032, -0.1038}; std::vector bias_data{-0.0258, 0.0073, -0.4780, -0.4101, -0.3556, -0.1017, 0.3632, -0.1823, 0.1479, 0.1677, -0.2603, 0.0381, 0.1575, 0.1896, 0.4755, -0.4794, 0.2167, -0.4474, -0.3139, 0.1018, 0.4470, -0.4232, 0.3247, -0.1636, -0.1582, -0.1703, 0.3920, 0.2055, -0.4386, 0.4208, 0.0717, 0.3789}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331, -0.0910, 1.2122, -0.1952, 0.4661, 0.6494, 2.1332, -1.0972, 0.9816, 0.1122, 0.3577, 1.3508, -0.5366, 1.7449, 0.5483, -0.0701, -0.4100, -2.2344, 0.3685, 0.4583, 2.3794, 1.0372, -0.8887, 0.7892, -0.4012, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.5289, 1.0986, 0.6091, 1.6462, 0.8720, 0.5349, -0.1962, -1.7416, -0.9912, 1.2831, 1.0896, -0.6959}; std::vector ic_data{-0.8323, 0.3998, 0.1831, 0.5938, 2.7096, -0.1790, 0.0022, -0.8040, 0.1578, 0.0567, 0.8069, -0.5141}; std::vector pph_data{-0.8271, -0.5683, 0.4562, -1.2545, 1.2729, -0.4082, -0.4392, -0.9406, 0.7794, 1.8194, -0.5811, 0.2166}; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; float clip = 0.0f; { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ 0.246078, 0.199709, 0.303753, 0.301178, 0.264634, 0.304661, 0.349371, 0.288934, 0.405483, 0.445586, 0.515814, 0.473186, 0.301937, 0.264893, 0.254353, 0.269231, 0.359258, 0.400097, 0.288884, 0.247329, 0.276519, 0.264249, 0.1769, 0.23213, 0.310306, 0.262902, 0.276964, 0.295002, 0.373802, 0.366785, 0.419791, 0.393216, 0.262827, 0.371441, 0.369022, 0.298262, 0.334143, 0.309444, 0.174822, 0.251634, 0.244564, 0.214386, 0.185994, 0.226699, 0.28445, 0.376092, 0.338326, 0.259502}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // reverse, 3 args, 2 actv functions { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.132123, -0.37531, -0.12943, -0.00798307, -0.133882, -0.0251383, 0.0486486, -0.0220606, 0.292495, 0.233866, 0.48646, 0.481844}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // reverse, 3 args, seq_len = 1, concatenation of hidden states as program output { seq_len = 1; std::vector input_data1{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331}; migraphx::shape in_shape1{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape1, input_data1}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{-0.104351, -0.0471426, -0.0905753, 0.01506, 0.059797, 0.104239, -0.0266768, 0.0727547, -0.146298, 0.070535, 0.327809, 0.407388}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } } TEST_CASE(lstm_bidirectional) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285, -0.2763, -0.4715, -0.3010, -0.2306, -0.2283, -0.2656, 0.2035, 0.3570, -0.1499, 0.4390, -0.1843, 0.2351, 0.3357, 0.1217, 0.1401, 0.3300, -0.0429, 0.3266, 0.4834, -0.3914, -0.1480, 0.3734, -0.0372, -0.1746, 0.0550, 0.4177, -0.1332, 0.4391, -0.3287, -0.4401, 0.1486, 0.1346, 0.1048, -0.4361, 0.0886, -0.3840, -0.2730, -0.1710, 0.3274, 0.0169, -0.4462, 0.0729, 0.3983, -0.0669, 0.0756, 0.4150, -0.4684, -0.2522}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260, -0.4564, -0.4432, 0.1605, 0.4387, 0.0034, 0.4116, 0.2824, 0.4775, -0.2729, -0.4707, 0.1363, 0.2218, 0.0559, 0.2828, 0.2093, 0.4687, 0.3794, -0.1069, -0.3049, 0.1430, -0.2506, 0.4644, 0.2755, -0.3645, -0.3155, 0.1425, 0.2891, 0.1786, -0.3274, 0.2365, 0.2522, -0.4312, -0.0562, -0.2748, 0.0776, -0.3154, 0.2851, -0.3930, -0.1174, 0.4360, 0.2436, 0.0164, -0.0680, 0.3403, -0.2857, -0.0459, -0.2991, -0.2624, 0.4194, -0.3291, -0.4659, 0.3300, 0.0454, 0.4981, -0.4706, -0.4584, 0.2596, 0.2871, -0.3509, -0.1910, 0.3987, -0.1687, -0.0032, -0.1038}; std::vector bias_data{ 0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655, -0.0258, 0.0073, -0.4780, -0.4101, -0.3556, -0.1017, 0.3632, -0.1823, 0.1479, 0.1677, -0.2603, 0.0381, 0.1575, 0.1896, 0.4755, -0.4794, 0.2167, -0.4474, -0.3139, 0.1018, 0.4470, -0.4232, 0.3247, -0.1636, -0.1582, -0.1703, 0.3920, 0.2055, -0.4386, 0.4208, 0.0717, 0.3789}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331, -0.0910, 1.2122, -0.1952, 0.4661, 0.6494, 2.1332, -1.0972, 0.9816, 0.1122, 0.3577, 1.3508, -0.5366, 1.7449, 0.5483, -0.0701, -0.4100, -2.2344, 0.3685, 0.4583, 2.3794, 1.0372, -0.8887, 0.7892, -0.4012, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 0.5671, 0.0458, 0.4514, -0.8968, -0.9201, 0.1962, 0.5771, -0.5332, 1.5289, 1.0986, 0.6091, 1.6462, 0.8720, 0.5349, -0.1962, -1.7416, -0.9912, 1.2831, 1.0896, -0.6959}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, 1.1055, -0.1212, -0.9097, 0.7831, -1.6991, -1.9498, -1.2567, -0.4114, -0.8323, 0.3998, 0.1831, 0.5938, 2.7096, -0.1790, 0.0022, -0.8040, 0.1578, 0.0567, 0.8069, -0.5141}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736, -0.8271, -0.5683, 0.4562, -1.2545, 1.2729, -0.4082, -0.4392, -0.9406, 0.7794, 1.8194, -0.5811, 0.2166}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; // concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.022618, -0.121195, -0.4065, -0.252054, -0.120174, 0.043157, 0.117138, -0.222188, 0.789732, 0.128538, 0.20909, 0.0553812, -0.224905, 0.32421, 0.344048, 0.271694, 0.186991, -0.0624168, 0.205513, 0.0836373, 0.421857, 0.0459771, -0.144955, 0.0720673, -0.0300906, -0.0890598, -0.135266, -0.0413375, -0.175114, -0.00543549, 0.178681, -0.266999, 0.928866, 0.113685, 0.220626, -0.0432316, -0.063456, 0.148524, 0.05108, -0.0234895, 0.0459032, 0.0414126, 0.272303, 0.0393149, 0.218258, 0.0944405, 0.0431211, -0.132394, 0.103489, 0.0142918, -0.123408, 0.0401075, -0.182201, -0.0232277, 0.235501, -0.213485, 0.960938, 0.133565, 0.269741, 0.130438, -0.0252804, 0.267356, 0.146353, 0.0789186, -0.058052, 0.0795391, 0.266617, -0.0128746, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.187761, 0.0501726, -0.121584, 0.0606723, -0.185038, -0.026845, 0.177273, -0.0774616, 0.946669, 0.0868676, 0.044508, -0.373961, -0.0681467, 0.382748, 0.230211, -0.161537}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // last hidden state as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.058052, 0.0795391, 0.266617, -0.0128746, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.187761, 0.0501726, -0.121584, 0.0606723, -0.120174, 0.043157, 0.117138, -0.222188, 0.789732, 0.128538, 0.20909, 0.0553812, -0.224905, 0.32421, 0.344048, 0.271694}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // last cell output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.077353, 0.245616, 0.361023, -0.0443759, 0.0685243, 0.20465, 0.277867, -0.112934, 0.67312, 0.120508, -0.726968, 0.113845, -0.889294, 0.182463, 0.186512, -0.402334, 1.48161, 0.524116, 0.347113, 0.181813, -0.434265, 0.747833, 0.416053, 0.558713}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // 3 args, concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.0327039, -0.0543852, 0.114378, -0.0768855, 0.0319021, -0.00298698, -0.0623361, 0.0598866, 0.101585, 0.0687269, -0.161725, -0.25617, -0.162851, -0.102647, -0.113827, -0.142818, 0.0513685, 0.0547876, 0.0201981, -0.00808453, -0.00520328, 0.0945081, 0.264123, 0.410805, -0.0786602, -0.0613048, 0.179592, -0.071286, 0.074206, 0.0124086, -0.139544, 0.108016, -0.00973633, -0.0552699, 0.0252681, -0.0562072, -0.123496, -0.153616, -0.032874, -0.195349, 0.0192675, -0.108636, 0.098927, -0.140733, 0.162602, 0.0143099, -0.0455534, 0.0151574, -0.102509, -0.0372696, 0.252296, -0.144544, 0.00496085, 0.0662588, -0.048577, -0.187329, 0.0855831, -0.0171894, -0.140202, 0.0828391, -0.1073, -0.150145, 0.015065, -0.192699, -0.112764, -0.120496, 0.155754, 0.148256, 0.208491, 0.348432, 0.0291103, 0.230275, -0.165194, -0.0372928, 0.273786, -0.100877, -0.0458544, -0.0401315, 0.0737483, -0.064505, 0.136898, 0.00160891, -0.184812, 0.147774, -0.021205, -0.125423, 0.0206439, -0.187097, -0.0051453, -0.0767618, -0.0735348, -0.0826436, 0.214159, 0.262295, 0.0247127, 0.14472}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // sequence length is 1, contenation of hidden state as program output { migraphx::program p; auto* mm = p.get_main_module(); seq_len = 1; migraphx::shape in_shape1{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; std::vector input_data1{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331}; auto seq = mm->add_literal(migraphx::literal{in_shape1, input_data1}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.0327039, -0.0543852, 0.114378, -0.0768855, 0.0319021, -0.00298698, -0.0623361, 0.0598866, 0.101585, 0.0687269, -0.161725, -0.25617, -0.104351, -0.0471426, -0.0905753, 0.01506, 0.059797, 0.104239, -0.0266768, 0.0727547, -0.146298, 0.070535, 0.327809, 0.407388}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } } TEST_CASE(lstm_bidirectional_layout) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285, -0.2763, -0.4715, -0.3010, -0.2306, -0.2283, -0.2656, 0.2035, 0.3570, -0.1499, 0.4390, -0.1843, 0.2351, 0.3357, 0.1217, 0.1401, 0.3300, -0.0429, 0.3266, 0.4834, -0.3914, -0.1480, 0.3734, -0.0372, -0.1746, 0.0550, 0.4177, -0.1332, 0.4391, -0.3287, -0.4401, 0.1486, 0.1346, 0.1048, -0.4361, 0.0886, -0.3840, -0.2730, -0.1710, 0.3274, 0.0169, -0.4462, 0.0729, 0.3983, -0.0669, 0.0756, 0.4150, -0.4684, -0.2522}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260, -0.4564, -0.4432, 0.1605, 0.4387, 0.0034, 0.4116, 0.2824, 0.4775, -0.2729, -0.4707, 0.1363, 0.2218, 0.0559, 0.2828, 0.2093, 0.4687, 0.3794, -0.1069, -0.3049, 0.1430, -0.2506, 0.4644, 0.2755, -0.3645, -0.3155, 0.1425, 0.2891, 0.1786, -0.3274, 0.2365, 0.2522, -0.4312, -0.0562, -0.2748, 0.0776, -0.3154, 0.2851, -0.3930, -0.1174, 0.4360, 0.2436, 0.0164, -0.0680, 0.3403, -0.2857, -0.0459, -0.2991, -0.2624, 0.4194, -0.3291, -0.4659, 0.3300, 0.0454, 0.4981, -0.4706, -0.4584, 0.2596, 0.2871, -0.3509, -0.1910, 0.3987, -0.1687, -0.0032, -0.1038}; std::vector bias_data{ 0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655, -0.0258, 0.0073, -0.4780, -0.4101, -0.3556, -0.1017, 0.3632, -0.1823, 0.1479, 0.1677, -0.2603, 0.0381, 0.1575, 0.1896, 0.4755, -0.4794, 0.2167, -0.4474, -0.3139, 0.1018, 0.4470, -0.4232, 0.3247, -0.1636, -0.1582, -0.1703, 0.3920, 0.2055, -0.4386, 0.4208, 0.0717, 0.3789}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.0910, 1.2122, -0.1952, 0.3577, 1.3508, -0.5366, 0.4583, 2.3794, 1.0372, -0.4313, -0.9730, -0.2005, 0.4661, 0.6494, 2.1332, 1.7449, 0.5483, -0.0701, -0.8887, 0.7892, -0.4012, 2.3930, -0.5221, -0.1331, -1.0972, 0.9816, 0.1122, -0.4100, -2.2344, 0.3685, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 1.5289, 1.0986, 0.6091, 1.6462, 0.5671, 0.0458, 0.4514, -0.8968, 0.8720, 0.5349, -0.1962, -1.7416, -0.9201, 0.1962, 0.5771, -0.5332, -0.9912, 1.2831, 1.0896, -0.6959}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, -0.8323, 0.3998, 0.1831, 0.5938, 1.1055, -0.1212, -0.9097, 0.7831, 2.7096, -0.1790, 0.0022, -0.8040, -1.6991, -1.9498, -1.2567, -0.4114, 0.1578, 0.0567, 0.8069, -0.5141}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736, -0.8271, -0.5683, 0.4562, -1.2545, 1.2729, -0.4082, -0.4392, -0.9406, 0.7794, 1.8194, -0.5811, 0.2166}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; // concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, -0.120174, 0.043157, 0.117138, -0.222188, 0.186991, -0.0624168, 0.205513, 0.0836373, -0.175114, -0.00543549, 0.178681, -0.266999, 0.0459032, 0.0414126, 0.272303, 0.0393149, -0.182201, -0.0232277, 0.235501, -0.213485, -0.058052, 0.0795391, 0.266617, -0.0128746, -0.185038, -0.026845, 0.177273, -0.0774616, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.789732, 0.128538, 0.20909, 0.0553812, 0.421857, 0.0459771, -0.144955, 0.0720673, 0.928866, 0.113685, 0.220626, -0.0432316, 0.218258, 0.0944405, 0.0431211, -0.132394, 0.960938, 0.133565, 0.269741, 0.130438, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.946669, 0.0868676, 0.044508, -0.373961, 0.022618, -0.121195, -0.4065, -0.252054, -0.224905, 0.32421, 0.344048, 0.271694, -0.0300906, -0.0890598, -0.135266, -0.0413375, -0.063456, 0.148524, 0.05108, -0.0234895, 0.103489, 0.0142918, -0.123408, 0.0401075, -0.0252804, 0.267356, 0.146353, 0.0789186, 0.187761, 0.0501726, -0.121584, 0.0606723, -0.0681467, 0.382748, 0.230211, -0.161537}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // last hidden state as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.058052, 0.0795391, 0.266617, -0.0128746, -0.120174, 0.043157, 0.117138, -0.222188, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.789732, 0.128538, 0.20909, 0.0553812, 0.187761, 0.0501726, -0.121584, 0.0606723, -0.224905, 0.32421, 0.344048, 0.271694}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // last cell output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, und, ih, ic, pph); auto cell_output = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), cell_output); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.077353, 0.245616, 0.361023, -0.0443759, -0.889294, 0.182463, 0.186512, -0.402334, 0.0685243, 0.20465, 0.277867, -0.112934, 1.48161, 0.524116, 0.347113, 0.181813, 0.67312, 0.120508, -0.726968, 0.113845, -0.434265, 0.747833, 0.416053, 0.558713}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // 3 args, concatenation of hidden states as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.0327039, -0.0543852, 0.114378, -0.0768855, -0.162851, -0.102647, -0.113827, -0.142818, -0.0786602, -0.0613048, 0.179592, -0.071286, -0.123496, -0.153616, -0.032874, -0.195349, -0.102509, -0.0372696, 0.252296, -0.144544, -0.1073, -0.150145, 0.015065, -0.192699, -0.165194, -0.0372928, 0.273786, -0.100877, -0.021205, -0.125423, 0.0206439, -0.187097, 0.0319021, -0.00298698, -0.0623361, 0.0598866, 0.0513685, 0.0547876, 0.0201981, -0.00808453, 0.074206, 0.0124086, -0.139544, 0.108016, 0.0192675, -0.108636, 0.098927, -0.140733, 0.00496085, 0.0662588, -0.048577, -0.187329, -0.112764, -0.120496, 0.155754, 0.148256, -0.0458544, -0.0401315, 0.0737483, -0.064505, -0.0051453, -0.0767618, -0.0735348, -0.0826436, 0.101585, 0.0687269, -0.161725, -0.25617, -0.00520328, 0.0945081, 0.264123, 0.410805, -0.00973633, -0.0552699, 0.0252681, -0.0562072, 0.162602, 0.0143099, -0.0455534, 0.0151574, 0.0855831, -0.0171894, -0.140202, 0.0828391, 0.208491, 0.348432, 0.0291103, 0.230275, 0.136898, 0.00160891, -0.184812, 0.147774, 0.214159, 0.262295, 0.0247127, 0.14472}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // sequence length is 1, contenation of hidden state as program output { migraphx::program p; auto* mm = p.get_main_module(); seq_len = 1; migraphx::shape in_shape1{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; std::vector input_data1{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331}; auto seq = mm->add_literal(migraphx::literal{in_shape1, input_data1}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.0327039, -0.0543852, 0.114378, -0.0768855, -0.104351, -0.0471426, -0.0905753, 0.01506, 0.0319021, -0.00298698, -0.0623361, 0.0598866, 0.059797, 0.104239, -0.0266768, 0.0727547, 0.101585, 0.0687269, -0.161725, -0.25617, -0.146298, 0.070535, 0.327809, 0.407388}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } } TEST_CASE(lstm_bidirectional_var_seq_lens) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285, -0.2763, -0.4715, -0.3010, -0.2306, -0.2283, -0.2656, 0.2035, 0.3570, -0.1499, 0.4390, -0.1843, 0.2351, 0.3357, 0.1217, 0.1401, 0.3300, -0.0429, 0.3266, 0.4834, -0.3914, -0.1480, 0.3734, -0.0372, -0.1746, 0.0550, 0.4177, -0.1332, 0.4391, -0.3287, -0.4401, 0.1486, 0.1346, 0.1048, -0.4361, 0.0886, -0.3840, -0.2730, -0.1710, 0.3274, 0.0169, -0.4462, 0.0729, 0.3983, -0.0669, 0.0756, 0.4150, -0.4684, -0.2522}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260, -0.4564, -0.4432, 0.1605, 0.4387, 0.0034, 0.4116, 0.2824, 0.4775, -0.2729, -0.4707, 0.1363, 0.2218, 0.0559, 0.2828, 0.2093, 0.4687, 0.3794, -0.1069, -0.3049, 0.1430, -0.2506, 0.4644, 0.2755, -0.3645, -0.3155, 0.1425, 0.2891, 0.1786, -0.3274, 0.2365, 0.2522, -0.4312, -0.0562, -0.2748, 0.0776, -0.3154, 0.2851, -0.3930, -0.1174, 0.4360, 0.2436, 0.0164, -0.0680, 0.3403, -0.2857, -0.0459, -0.2991, -0.2624, 0.4194, -0.3291, -0.4659, 0.3300, 0.0454, 0.4981, -0.4706, -0.4584, 0.2596, 0.2871, -0.3509, -0.1910, 0.3987, -0.1687, -0.0032, -0.1038}; std::vector bias_data{ 0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655, -0.0258, 0.0073, -0.4780, -0.4101, -0.3556, -0.1017, 0.3632, -0.1823, 0.1479, 0.1677, -0.2603, 0.0381, 0.1575, 0.1896, 0.4755, -0.4794, 0.2167, -0.4474, -0.3139, 0.1018, 0.4470, -0.4232, 0.3247, -0.1636, -0.1582, -0.1703, 0.3920, 0.2055, -0.4386, 0.4208, 0.0717, 0.3789}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331, -0.0910, 1.2122, -0.1952, 0.4661, 0.6494, 2.1332, -1.0972, 0.9816, 0.1122, 0.3577, 1.3508, -0.5366, 1.7449, 0.5483, -0.0701, -0.4100, -2.2344, 0.3685, 0.4583, 2.3794, 1.0372, -0.8887, 0.7892, -0.4012, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 0.5671, 0.0458, 0.4514, -0.8968, -0.9201, 0.1962, 0.5771, -0.5332, 1.5289, 1.0986, 0.6091, 1.6462, 0.8720, 0.5349, -0.1962, -1.7416, -0.9912, 1.2831, 1.0896, -0.6959}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, 1.1055, -0.1212, -0.9097, 0.7831, -1.6991, -1.9498, -1.2567, -0.4114, -0.8323, 0.3998, 0.1831, 0.5938, 2.7096, -0.1790, 0.0022, -0.8040, 0.1578, 0.0567, 0.8069, -0.5141}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736, -0.8271, -0.5683, 0.4562, -1.2545, 1.2729, -0.4082, -0.4392, -0.9406, 0.7794, 1.8194, -0.5811, 0.2166}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; // concatenation of hidden states as program output { std::vector sl_data{1, 2, 3}; migraphx::shape sl_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto sql = mm->add_literal(migraphx::literal{sl_shape, sl_data}); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, pph); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto lco = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); mm->add_return({out_hs, lho, lco}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_lho = outputs.at(1); const auto& arg_lco = outputs.at(2); std::vector output_data; std::vector last_output_data; std::vector last_cell_data; arg_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); arg_lho.visit([&](auto output) { last_output_data.assign(output.begin(), output.end()); }); arg_lco.visit([&](auto output) { last_cell_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.022618, -0.121195, -0.4065, -0.252054, -0.141643, 0.0451978, 0.140804, 0.0745128, 0.911307, 0.11468, 0.114449, 0.0196755, -0.262807, 0.275286, 0.358395, 0.266267, 0, 0, 0, 0, 0.421857, 0.0459771, -0.144955, 0.0720673, -0.0300906, -0.0890598, -0.135266, -0.0413375, 0, 0, 0, 0, 0.96657, 0.0755112, 0.0620917, -0.264845, -0.128254, 0.125398, 0.0665142, -0.163651, 0, 0, 0, 0, 0, 0, 0, 0, 0.103489, 0.0142918, -0.123408, 0.0401075, 0, 0, 0, 0, 0, 0, 0, 0, -0.0644683, 0.371512, 0.212431, -0.116131, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector last_output_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, 0.421857, 0.0459771, -0.144955, 0.0720673, 0.103489, 0.0142918, -0.123408, 0.0401075, -0.141643, 0.0451978, 0.140804, 0.0745128, 0.911307, 0.11468, 0.114449, 0.0196755, -0.262807, 0.275286, 0.358395, 0.266267}; std::vector last_cell_data_gold{ 0.600582, -0.601197, 0.353558, 0.789097, 0.737121, 0.134902, -0.303595, 0.241948, 0.391174, 0.0308845, -0.561745, 0.0730323, -0.326822, 0.301121, 0.219523, 0.415242, 2.08242, 0.442513, 0.187127, 0.0577626, -0.611307, 0.55454, 0.4364, 0.509436}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_cell_data, last_cell_data_gold)); } // last cell output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {2, batch_size, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, pph); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); auto lco = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); mm->add_return({hs, lho, lco}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& res_hs = outputs.at(0); const auto& res_lho = outputs.at(1); const auto& res_lco = outputs.at(2); std::vector hs_data; std::vector lho_data; std::vector lco_data; res_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); res_lco.visit([&](auto output) { lco_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.022618, -0.121195, -0.4065, -0.252054, -0.120174, 0.043157, 0.117138, -0.222188, 0.789732, 0.128538, 0.20909, 0.0553812, -0.224905, 0.32421, 0.344048, 0.271694, 0.186991, -0.0624168, 0.205513, 0.0836373, 0.421857, 0.0459771, -0.144955, 0.0720673, -0.0300906, -0.0890598, -0.135266, -0.0413375, -0.175114, -0.00543549, 0.178681, -0.266999, 0.928866, 0.113685, 0.220626, -0.0432316, -0.063456, 0.148524, 0.05108, -0.0234895, 0.0459033, 0.0414126, 0.272303, 0.0393149, 0.218258, 0.0944405, 0.0431211, -0.132394, 0.103489, 0.0142918, -0.123408, 0.0401075, -0.182201, -0.0232277, 0.235501, -0.213485, 0.960938, 0.133565, 0.269741, 0.130438, -0.0252804, 0.267356, 0.146353, 0.0789186, -0.058052, 0.0795391, 0.266617, -0.0128746, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.187761, 0.0501726, -0.121584, 0.0606723, -0.185038, -0.026845, 0.177273, -0.0774616, 0.946669, 0.0868676, 0.044508, -0.373961, -0.0681467, 0.382748, 0.230211, -0.161537, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector lho_data_gold{ -0.058052, 0.0795391, 0.266617, -0.0128746, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.187761, 0.0501726, -0.121584, 0.0606723, -0.120174, 0.043157, 0.117138, -0.222188, 0.789732, 0.128538, 0.20909, 0.0553812, -0.224905, 0.32421, 0.344048, 0.271694}; std::vector lco_data_gold{ -0.077353, 0.245616, 0.361023, -0.0443759, 0.0685243, 0.20465, 0.277867, -0.112934, 0.67312, 0.120508, -0.726968, 0.113845, -0.889294, 0.182463, 0.186512, -0.402334, 1.48161, 0.524116, 0.347113, 0.181813, -0.434265, 0.747833, 0.416053, 0.558713}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lco_data, lco_data_gold)); } } TEST_CASE(lstm_bidirectional_var_seq_lens_layout) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285, -0.2763, -0.4715, -0.3010, -0.2306, -0.2283, -0.2656, 0.2035, 0.3570, -0.1499, 0.4390, -0.1843, 0.2351, 0.3357, 0.1217, 0.1401, 0.3300, -0.0429, 0.3266, 0.4834, -0.3914, -0.1480, 0.3734, -0.0372, -0.1746, 0.0550, 0.4177, -0.1332, 0.4391, -0.3287, -0.4401, 0.1486, 0.1346, 0.1048, -0.4361, 0.0886, -0.3840, -0.2730, -0.1710, 0.3274, 0.0169, -0.4462, 0.0729, 0.3983, -0.0669, 0.0756, 0.4150, -0.4684, -0.2522}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260, -0.4564, -0.4432, 0.1605, 0.4387, 0.0034, 0.4116, 0.2824, 0.4775, -0.2729, -0.4707, 0.1363, 0.2218, 0.0559, 0.2828, 0.2093, 0.4687, 0.3794, -0.1069, -0.3049, 0.1430, -0.2506, 0.4644, 0.2755, -0.3645, -0.3155, 0.1425, 0.2891, 0.1786, -0.3274, 0.2365, 0.2522, -0.4312, -0.0562, -0.2748, 0.0776, -0.3154, 0.2851, -0.3930, -0.1174, 0.4360, 0.2436, 0.0164, -0.0680, 0.3403, -0.2857, -0.0459, -0.2991, -0.2624, 0.4194, -0.3291, -0.4659, 0.3300, 0.0454, 0.4981, -0.4706, -0.4584, 0.2596, 0.2871, -0.3509, -0.1910, 0.3987, -0.1687, -0.0032, -0.1038}; std::vector bias_data{ 0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655, -0.0258, 0.0073, -0.4780, -0.4101, -0.3556, -0.1017, 0.3632, -0.1823, 0.1479, 0.1677, -0.2603, 0.0381, 0.1575, 0.1896, 0.4755, -0.4794, 0.2167, -0.4474, -0.3139, 0.1018, 0.4470, -0.4232, 0.3247, -0.1636, -0.1582, -0.1703, 0.3920, 0.2055, -0.4386, 0.4208, 0.0717, 0.3789}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.0910, 1.2122, -0.1952, 0.3577, 1.3508, -0.5366, 0.4583, 2.3794, 1.0372, -0.4313, -0.9730, -0.2005, 0.4661, 0.6494, 2.1332, 1.7449, 0.5483, -0.0701, -0.8887, 0.7892, -0.4012, 2.3930, -0.5221, -0.1331, -1.0972, 0.9816, 0.1122, -0.4100, -2.2344, 0.3685, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 1.5289, 1.0986, 0.6091, 1.6462, 0.5671, 0.0458, 0.4514, -0.8968, 0.8720, 0.5349, -0.1962, -1.7416, -0.9201, 0.1962, 0.5771, -0.5332, -0.9912, 1.2831, 1.0896, -0.6959}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, -0.8323, 0.3998, 0.1831, 0.5938, 1.1055, -0.1212, -0.9097, 0.7831, 2.7096, -0.1790, 0.0022, -0.8040, -1.6991, -1.9498, -1.2567, -0.4114, 0.1578, 0.0567, 0.8069, -0.5141}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736, -0.8271, -0.5683, 0.4562, -1.2545, 1.2729, -0.4082, -0.4392, -0.9406, 0.7794, 1.8194, -0.5811, 0.2166}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; // concatenation of hidden states as program output { std::vector sl_data{1, 2, 3}; migraphx::shape sl_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); auto sql = mm->add_literal(migraphx::literal{sl_shape, sl_data}); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto out_hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, pph); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), out_hs); auto lco = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), out_hs); std::vector perm_hid{2, 0, 1, 3}; out_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), out_hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); lco = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lco); mm->add_return({out_hs, lho, lco}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& arg_hs = outputs.front(); const auto& arg_lho = outputs.at(1); const auto& arg_lco = outputs.at(2); std::vector output_data; std::vector last_output_data; std::vector last_cell_data; arg_hs.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); arg_lho.visit([&](auto output) { last_output_data.assign(output.begin(), output.end()); }); arg_lco.visit([&](auto output) { last_cell_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, -0.141643, 0.0451978, 0.140804, 0.0745128, 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.294074, -0.0319677, -0.0955337, 0.104168, 0.911307, 0.11468, 0.114449, 0.0196755, 0.421857, 0.0459771, -0.144955, 0.0720673, 0.96657, 0.0755112, 0.0620917, -0.264845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.022618, -0.121195, -0.4065, -0.252054, -0.262807, 0.275286, 0.358395, 0.266267, -0.0300906, -0.0890598, -0.135266, -0.0413375, -0.128254, 0.125398, 0.0665142, -0.163651, 0.103489, 0.0142918, -0.123408, 0.0401075, -0.0644683, 0.371512, 0.212431, -0.116131, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector last_output_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, -0.141643, 0.0451978, 0.140804, 0.0745128, 0.421857, 0.0459771, -0.144955, 0.0720673, 0.911307, 0.11468, 0.114449, 0.0196755, 0.103489, 0.0142918, -0.123408, 0.0401075, -0.262807, 0.275286, 0.358395, 0.266267}; std::vector last_cell_data_gold{ 0.600582, -0.601197, 0.353558, 0.789097, -0.326822, 0.301121, 0.219523, 0.415242, 0.737121, 0.134902, -0.303595, 0.241948, 2.08242, 0.442513, 0.187127, 0.0577626, 0.391174, 0.0308845, -0.561745, 0.0730323, -0.611307, 0.55454, 0.4364, 0.509436}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_output_data, last_output_data_gold)); EXPECT(migraphx::verify::verify_rms_range(last_cell_data, last_cell_data_gold)); } // last cell output as program output { migraphx::program p; auto* mm = p.get_main_module(); auto seq_orig = mm->add_literal(migraphx::literal{in_shape, input_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto pph = mm->add_literal(migraphx::literal{pph_shape, pph_data}); migraphx::shape pad_seq_s{migraphx::shape::float_type, {batch_size, 2, input_size}}; std::vector pad_data(pad_seq_s.elements(), 0.0f); auto seq_p = mm->add_literal(migraphx::literal{pad_seq_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), seq_orig, seq_p); migraphx::shape seq_len_s{migraphx::shape::int32_type, {batch_size}}; std::vector len_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(seq_len_s, len_data); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r, bias, sql, ih, ic, pph); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); auto lco = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); lco = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lco); mm->add_return({hs, lho, lco}); p.compile(migraphx::make_target("ref")); auto outputs = p.eval({}); const auto& res_hs = outputs.at(0); const auto& res_lho = outputs.at(1); const auto& res_lco = outputs.at(2); std::vector hs_data; std::vector lho_data; std::vector lco_data; res_hs.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); res_lho.visit([&](auto output) { lho_data.assign(output.begin(), output.end()); }); res_lco.visit([&](auto output) { lco_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.079753, -0.289854, 0.160043, 0.115056, -0.120174, 0.043157, 0.117138, -0.222188, 0.186991, -0.0624168, 0.205513, 0.0836373, -0.175114, -0.00543549, 0.178681, -0.266999, 0.0459033, 0.0414126, 0.272303, 0.0393149, -0.182201, -0.0232277, 0.235501, -0.213485, -0.058052, 0.0795391, 0.266617, -0.0128746, -0.185038, -0.026845, 0.177273, -0.0774616, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.294074, -0.0319677, -0.0955337, 0.104168, 0.789732, 0.128538, 0.20909, 0.0553812, 0.421857, 0.0459771, -0.144955, 0.0720673, 0.928866, 0.113685, 0.220626, -0.0432316, 0.218258, 0.0944405, 0.0431211, -0.132394, 0.960938, 0.133565, 0.269741, 0.130438, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.946669, 0.0868676, 0.044508, -0.373961, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.022618, -0.121195, -0.4065, -0.252054, -0.224905, 0.32421, 0.344048, 0.271694, -0.0300906, -0.0890598, -0.135266, -0.0413375, -0.063456, 0.148524, 0.05108, -0.0234895, 0.103489, 0.0142918, -0.123408, 0.0401075, -0.0252804, 0.267356, 0.146353, 0.0789186, 0.187761, 0.0501726, -0.121584, 0.0606723, -0.0681467, 0.382748, 0.230211, -0.161537, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; std::vector lho_data_gold{ -0.058052, 0.0795391, 0.266617, -0.0128746, -0.120174, 0.043157, 0.117138, -0.222188, 0.0309878, 0.0971544, 0.149294, -0.0492549, 0.789732, 0.128538, 0.20909, 0.0553812, 0.187761, 0.0501726, -0.121584, 0.0606723, -0.224905, 0.32421, 0.344048, 0.271694}; std::vector lco_data_gold{ -0.077353, 0.245616, 0.361023, -0.0443759, -0.889294, 0.182463, 0.186512, -0.402334, 0.0685243, 0.20465, 0.277867, -0.112934, 1.48161, 0.524116, 0.347113, 0.181813, 0.67312, 0.120508, -0.726968, 0.113845, -0.434265, 0.747833, 0.416053, 0.558713}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lho_data, lho_data_gold)); EXPECT(migraphx::verify::verify_rms_range(lco_data, lco_data_gold)); } } TEST_CASE(lstm_bidirectional_actv_func) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285, -0.2763, -0.4715, -0.3010, -0.2306, -0.2283, -0.2656, 0.2035, 0.3570, -0.1499, 0.4390, -0.1843, 0.2351, 0.3357, 0.1217, 0.1401, 0.3300, -0.0429, 0.3266, 0.4834, -0.3914, -0.1480, 0.3734, -0.0372, -0.1746, 0.0550, 0.4177, -0.1332, 0.4391, -0.3287, -0.4401, 0.1486, 0.1346, 0.1048, -0.4361, 0.0886, -0.3840, -0.2730, -0.1710, 0.3274, 0.0169, -0.4462, 0.0729, 0.3983, -0.0669, 0.0756, 0.4150, -0.4684, -0.2522}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260, -0.4564, -0.4432, 0.1605, 0.4387, 0.0034, 0.4116, 0.2824, 0.4775, -0.2729, -0.4707, 0.1363, 0.2218, 0.0559, 0.2828, 0.2093, 0.4687, 0.3794, -0.1069, -0.3049, 0.1430, -0.2506, 0.4644, 0.2755, -0.3645, -0.3155, 0.1425, 0.2891, 0.1786, -0.3274, 0.2365, 0.2522, -0.4312, -0.0562, -0.2748, 0.0776, -0.3154, 0.2851, -0.3930, -0.1174, 0.4360, 0.2436, 0.0164, -0.0680, 0.3403, -0.2857, -0.0459, -0.2991, -0.2624, 0.4194, -0.3291, -0.4659, 0.3300, 0.0454, 0.4981, -0.4706, -0.4584, 0.2596, 0.2871, -0.3509, -0.1910, 0.3987, -0.1687, -0.0032, -0.1038}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331, -0.0910, 1.2122, -0.1952, 0.4661, 0.6494, 2.1332, -1.0972, 0.9816, 0.1122, 0.3577, 1.3508, -0.5366, 1.7449, 0.5483, -0.0701, -0.4100, -2.2344, 0.3685, 0.4583, 2.3794, 1.0372, -0.8887, 0.7892, -0.4012, -0.2818, -2.3374, 1.5310}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; // 3 args, 0 actv func { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.0327039, -0.0543852, 0.114378, -0.0768855, 0.0319021, -0.00298698, -0.0623361, 0.0598866, 0.101585, 0.0687269, -0.161725, -0.25617, -0.162851, -0.102647, -0.113827, -0.142818, 0.0513685, 0.0547876, 0.0201981, -0.00808453, -0.00520328, 0.0945081, 0.264123, 0.410805, -0.0786602, -0.0613048, 0.179592, -0.071286, 0.074206, 0.0124086, -0.139544, 0.108016, -0.00973633, -0.0552699, 0.0252681, -0.0562072, -0.123496, -0.153616, -0.032874, -0.195349, 0.0192675, -0.108636, 0.098927, -0.140733, 0.162602, 0.0143099, -0.0455534, 0.0151574, -0.102509, -0.0372696, 0.252296, -0.144544, 0.00496085, 0.0662588, -0.048577, -0.187329, 0.0855831, -0.0171894, -0.140202, 0.0828391, -0.1073, -0.150145, 0.015065, -0.192699, -0.112764, -0.120496, 0.155754, 0.148256, 0.208491, 0.348432, 0.0291103, 0.230275, -0.165194, -0.0372928, 0.273786, -0.100877, -0.0458544, -0.0401315, 0.0737483, -0.064505, 0.136898, 0.00160891, -0.184812, 0.147774, -0.021205, -0.125423, 0.0206439, -0.187097, -0.0051453, -0.0767618, -0.0735348, -0.0826436, 0.214159, 0.262295, 0.0247127, 0.14472}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // 3 args, 1 actv func { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ 0.227861, 0.328562, 0.277867, 0.272945, 0.204389, 0.296123, 0.223834, 0.311113, 0.424666, 0.173974, 0.40628, 0.286631, 0.246078, 0.199709, 0.303753, 0.301178, 0.264634, 0.304661, 0.349371, 0.288934, 0.405483, 0.445586, 0.515814, 0.473186, 0.339438, 0.29655, 0.331832, 0.242338, 0.409384, 0.236272, 0.306045, 0.26269, 0.261246, 0.334357, 0.23622, 0.245288, 0.301937, 0.264893, 0.254353, 0.269231, 0.359258, 0.400097, 0.288884, 0.247329, 0.276519, 0.264249, 0.1769, 0.23213, 0.374123, 0.283167, 0.377129, 0.245726, 0.444712, 0.203168, 0.411446, 0.269965, 0.172792, 0.296224, 0.17319, 0.352547, 0.310306, 0.262902, 0.276964, 0.295002, 0.373802, 0.366785, 0.419791, 0.393216, 0.262827, 0.371441, 0.369022, 0.298262, 0.450186, 0.263538, 0.402895, 0.216177, 0.267257, 0.342535, 0.257797, 0.268563, 0.193043, 0.275645, 0.167678, 0.350889, 0.334143, 0.309444, 0.174822, 0.251634, 0.244564, 0.214386, 0.185994, 0.226699, 0.28445, 0.376092, 0.338326, 0.259502}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // 3 args, 2 actv func { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.165194, -0.0372928, 0.273786, -0.100877, -0.0458544, -0.0401315, 0.0737483, -0.064505, 0.136898, 0.00160891, -0.184812, 0.147774, -0.162851, -0.102647, -0.113827, -0.142818, 0.0513685, 0.0547876, 0.0201981, -0.00808453, -0.00520328, 0.0945081, 0.264123, 0.410805}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // 3 args, 4 actv func { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.165194, -0.0372928, 0.273786, -0.100877, -0.0458544, -0.0401315, 0.0737483, -0.064505, 0.136898, 0.00160891, -0.184812, 0.147774, 0.246078, 0.199709, 0.303753, 0.301178, 0.264634, 0.304661, 0.349371, 0.288934, 0.405483, 0.445586, 0.515814, 0.473186}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // 3 args, 5 actv func { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.165194, -0.0372928, 0.273786, -0.100877, -0.0458544, -0.0401315, 0.0737483, -0.064505, 0.136898, 0.00160891, -0.184812, 0.147774, -0.162851, -0.102647, -0.113827, -0.142818, 0.0513685, 0.0547876, 0.0201981, -0.00808453, -0.00520328, 0.0945081, 0.264123, 0.410805}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } // 3 args, 6 actv func { migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}, {"input_forget", 0}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector output_data; hs_concat.visit([&](auto output) { output_data.assign(output.begin(), output.end()); }); std::vector output_data_gold{ -0.165194, -0.0372928, 0.273786, -0.100877, -0.0458544, -0.0401315, 0.0737483, -0.064505, 0.136898, 0.00160891, -0.184812, 0.147774, -0.162851, -0.102647, -0.113827, -0.142818, 0.0513685, 0.0547876, 0.0201981, -0.00808453, -0.00520328, 0.0945081, 0.264123, 0.410805}; EXPECT(migraphx::verify::verify_rms_range(output_data, output_data_gold)); } } TEST_CASE(lstm_fp16) { std::size_t batch_size = 3; std::size_t seq_len = 4; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; std::vector w_data{ 0.1236, -0.3942, 0.4149, 0.0795, 0.4934, -0.2858, 0.2602, -0.3098, 0.0567, 0.3344, 0.3607, -0.0551, 0.4952, 0.3799, 0.0630, -0.3532, 0.0023, -0.0592, 0.4267, 0.2382, -0.0784, -0.0032, -0.2476, -0.0206, -0.4963, 0.4837, 0.0827, 0.0123, -0.1203, -0.0279, -0.0049, 0.4721, -0.3564, -0.1286, 0.4090, -0.0504, 0.0575, -0.2138, 0.1071, 0.1976, -0.0758, 0.0139, -0.0761, 0.3991, -0.2965, -0.4845, -0.1496, 0.3285}; std::vector r_data{ 0.1237, 0.1229, -0.0766, -0.1144, -0.1186, 0.2922, 0.2478, 0.3159, -0.0522, 0.1685, -0.4621, 0.1728, 0.0670, -0.2458, -0.3835, -0.4589, -0.3109, 0.4908, -0.0133, -0.1858, -0.0590, -0.0347, -0.2353, -0.0671, -0.3812, -0.0004, -0.1432, 0.2406, 0.1033, -0.0265, -0.3902, 0.0755, 0.3733, 0.4383, -0.3140, 0.2537, -0.1818, -0.4127, 0.3506, 0.2562, 0.2926, 0.1620, -0.4849, -0.4861, 0.4426, 0.2106, -0.0005, 0.4418, -0.2926, -0.3100, 0.1500, -0.0362, -0.3801, -0.0065, -0.0631, 0.1277, 0.2315, 0.4087, -0.3963, -0.4161, -0.2169, -0.1344, 0.3468, -0.2260}; std::vector bias_data{0.0088, 0.1183, 0.1642, -0.2631, -0.1330, -0.4008, 0.3881, -0.4407, -0.2760, 0.1274, -0.0083, -0.2885, 0.3949, -0.0182, 0.4445, 0.3477, 0.2266, 0.3423, -0.0674, -0.4067, 0.0807, 0.1109, -0.2036, 0.1782, -0.2467, -0.0730, -0.4216, 0.0316, -0.3025, 0.3637, -0.3181, -0.4655}; std::vector input_data{ -0.5516, 0.2391, -1.6951, -0.4313, -0.9730, -0.2005, 2.3930, -0.5221, -0.1331, -0.0910, 1.2122, -0.1952, 0.4661, 0.6494, 2.1332, -1.0972, 0.9816, 0.1122, 0.3577, 1.3508, -0.5366, 1.7449, 0.5483, -0.0701, -0.4100, -2.2344, 0.3685, 0.4583, 2.3794, 1.0372, -0.8887, 0.7892, -0.4012, -0.2818, -2.3374, 1.5310}; std::vector ih_data{1.9104, -1.9004, 0.3337, 0.5741, 0.5671, 0.0458, 0.4514, -0.8968, -0.9201, 0.1962, 0.5771, -0.5332}; std::vector ic_data{0.9569, -0.5981, 1.1312, 1.0945, 1.1055, -0.1212, -0.9097, 0.7831, -1.6991, -1.9498, -1.2567, -0.4114}; std::vector pph_data{1.84369764, 0.68413646, -0.44892886, -1.50904413, 0.3860796, -0.52186625, 1.08474445, -1.80867321, 1.32594529, 0.4336262, -0.83699064, 0.49162736}; float clip = 0.0f; migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; migraphx::program p; auto* mm = p.get_main_module(); auto seq = mm->add_literal(migraphx::literal{in_shape, input_data}); auto w = mm->add_literal(migraphx::literal{w_shape, w_data}); auto r = mm->add_literal(migraphx::literal{r_shape, r_data}); auto bias = mm->add_literal(migraphx::literal{b_shape, bias_data}); auto ih = mm->add_literal(migraphx::literal{ih_shape, ih_data}); auto ic = mm->add_literal(migraphx::literal{ic_shape, ic_data}); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto seq_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), seq); auto w_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), w); auto r_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), r); auto bias_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), bias); auto ih_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), ih); auto ic_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), ic); auto und_half = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), und); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}, {"input_forget", 0}}), seq_half, w_half, r_half, bias_half, und_half, ih_half, ic_half, und_half); p.compile(migraphx::make_target("ref")); auto hs_concat = p.eval({}).back(); std::vector hs_data; hs_concat.visit([&](auto output) { hs_data.assign(output.begin(), output.end()); }); std::vector hs_data_gold{ 0.0417273, -0.272355, 0.206765, 0.223879, 0.138193, -0.0322939, -0.0891815, 0.15773, 0.19139, -0.127708, -0.409371, -0.136186, 0.0742487, -0.0800085, 0.259897, 0.0670196, 0.184266, 0.0610048, -0.138041, 0.0963885, 0.0213755, -0.146027, -0.0324509, -0.0620429, -0.00532985, 0.0440265, 0.29654, -0.0463156, 0.0498799, 0.125772, 0.0533032, -0.131413, 0.0988431, -0.018085, -0.159434, 0.030266, -0.0847427, 0.0874114, 0.304256, -0.0585745, -0.0223018, 0.131113, 0.135643, -0.0566208, 0.142701, 0.0342236, -0.198664, 0.0702607}; EXPECT(migraphx::verify::verify_rms_range(hs_data, hs_data_gold, 5e4)); } ROCm-AMDMIGraphX-46524e8/test/ref/roialign.cpp000066400000000000000000000253021510465702400206050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(roialign_out_of_bound_test) { auto create_program = [](const std::string& trans_mode = "half_pixel") { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_s{migraphx::shape::float_type, {1, 1, 10, 10}}; std::vector x_vec = { 0.2764, 0.7150, 0.1958, 0.3416, 0.4638, 0.0259, 0.2963, 0.6518, 0.4856, 0.7250, 0.9637, 0.0895, 0.2919, 0.6753, 0.0234, 0.6132, 0.8085, 0.5324, 0.8992, 0.4467, 0.3265, 0.8479, 0.9698, 0.2471, 0.9336, 0.1878, 0.4766, 0.4308, 0.3400, 0.2162, 0.0206, 0.1720, 0.2155, 0.4394, 0.0653, 0.3406, 0.7724, 0.3921, 0.2541, 0.5799, 0.4062, 0.2194, 0.4473, 0.4687, 0.7109, 0.9327, 0.9815, 0.6320, 0.1728, 0.6119, 0.3097, 0.1283, 0.4984, 0.5068, 0.4279, 0.0173, 0.4388, 0.0430, 0.4671, 0.7119, 0.1011, 0.8477, 0.4726, 0.1777, 0.9923, 0.4042, 0.1869, 0.7795, 0.9946, 0.9689, 0.1366, 0.3671, 0.7011, 0.6234, 0.9867, 0.5585, 0.6985, 0.5609, 0.8788, 0.9928, 0.5697, 0.8511, 0.6711, 0.9406, 0.8751, 0.7496, 0.1650, 0.1049, 0.1559, 0.2514, 0.7012, 0.4056, 0.7879, 0.3461, 0.0415, 0.2998, 0.5094, 0.3727, 0.5482, 0.0502}; migraphx::shape roi_s{migraphx::shape::float_type, {3, 4}}; std::vector roi_vec = {0, 0, 9.99, 9.99, 0, 5, 4, 9, 5, 5, 9.9, 9.9}; migraphx::shape ind_s{migraphx::shape::int64_type, {3}}; std::vector ind_vec = {0, 0, 0}; auto x = mm->add_literal(migraphx::literal(x_s, x_vec)); auto roi = mm->add_literal(migraphx::literal(roi_s, roi_vec)); auto ind = mm->add_literal(migraphx::literal(ind_s, ind_vec)); auto r = mm->add_instruction(migraphx::make_op("roialign", {{"coordinate_transformation_mode", trans_mode}, {"spatial_scale", 5.0}, {"output_height", 1}, {"output_width", 1}, {"sampling_ratio", 1}}), x, roi, ind); mm->add_return({r}); return p; }; { auto p = create_program("half_pixel"); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.0f, 0.0f, 0.0f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } } TEST_CASE(roialign_test) { auto create_program = [](const std::string& trans_mode = "half_pixel", const migraphx::op::pooling_mode pooling_mode = migraphx::op::pooling_mode::average, int64_t sampling_ratio = 2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_s{migraphx::shape::float_type, {1, 1, 10, 10}}; std::vector x_vec = { 0.2764, 0.7150, 0.1958, 0.3416, 0.4638, 0.0259, 0.2963, 0.6518, 0.4856, 0.7250, 0.9637, 0.0895, 0.2919, 0.6753, 0.0234, 0.6132, 0.8085, 0.5324, 0.8992, 0.4467, 0.3265, 0.8479, 0.9698, 0.2471, 0.9336, 0.1878, 0.4766, 0.4308, 0.3400, 0.2162, 0.0206, 0.1720, 0.2155, 0.4394, 0.0653, 0.3406, 0.7724, 0.3921, 0.2541, 0.5799, 0.4062, 0.2194, 0.4473, 0.4687, 0.7109, 0.9327, 0.9815, 0.6320, 0.1728, 0.6119, 0.3097, 0.1283, 0.4984, 0.5068, 0.4279, 0.0173, 0.4388, 0.0430, 0.4671, 0.7119, 0.1011, 0.8477, 0.4726, 0.1777, 0.9923, 0.4042, 0.1869, 0.7795, 0.9946, 0.9689, 0.1366, 0.3671, 0.7011, 0.6234, 0.9867, 0.5585, 0.6985, 0.5609, 0.8788, 0.9928, 0.5697, 0.8511, 0.6711, 0.9406, 0.8751, 0.7496, 0.1650, 0.1049, 0.1559, 0.2514, 0.7012, 0.4056, 0.7879, 0.3461, 0.0415, 0.2998, 0.5094, 0.3727, 0.5482, 0.0502}; migraphx::shape roi_s{migraphx::shape::float_type, {3, 4}}; std::vector roi_vec = {0, 0, 9, 9, 0, 5, 4, 9, 5, 5, 9, 9}; migraphx::shape ind_s{migraphx::shape::int64_type, {3}}; std::vector ind_vec = {0, 0, 0}; auto x = mm->add_literal(migraphx::literal(x_s, x_vec)); auto roi = mm->add_literal(migraphx::literal(roi_s, roi_vec)); auto ind = mm->add_literal(migraphx::literal(ind_s, ind_vec)); auto r = mm->add_instruction(migraphx::make_op("roialign", {{"coordinate_transformation_mode", trans_mode}, {"spatial_scale", 1.0}, {"output_height", 5}, {"output_width", 5}, {"sampling_ratio", sampling_ratio}, {"mode", pooling_mode}}), x, roi, ind); mm->add_return({r}); return p; }; { auto p = create_program("output_half_pixel"); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.466421425, 0.446552634, 0.340521216, 0.568848491, 0.606780827, 0.371379346, 0.429571986, 0.383519977, 0.556241512, 0.351050019, 0.27680251, 0.488286227, 0.522200167, 0.552770197, 0.417057365, 0.471240699, 0.4844096, 0.690457463, 0.492039412, 0.877398551, 0.623889625, 0.712461948, 0.628926516, 0.335504025, 0.349469036, 0.302179992, 0.43046391, 0.469585985, 0.39774403, 0.542259991, 0.365552008, 0.704923987, 0.516481996, 0.317131996, 0.701444089, 0.291239977, 0.505897999, 0.647610962, 0.623489916, 0.829879999, 0.591567993, 0.738860011, 0.704825997, 0.837148011, 0.889315963, 0.622680008, 0.615276039, 0.709713995, 0.615356028, 0.458524048, 0.238451958, 0.337952018, 0.371693879, 0.609999895, 0.760059953, 0.376724035, 0.378532052, 0.71468991, 0.924308002, 0.972783983, 0.574903965, 0.582623959, 0.570936024, 0.761904061, 0.876998067, 0.535508037, 0.256580025, 0.214098021, 0.279604018, 0.360000014, 0.436488032, 0.350427985, 0.288755983, 0.366139978, 0.234920025}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } { auto p = create_program("half_pixel"); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.517783, 0.343411, 0.322905, 0.447362, 0.634375, 0.40308, 0.536647, 0.442791, 0.486144, 0.402313, 0.251194, 0.400154, 0.515524, 0.695369, 0.346537, 0.33504, 0.460099, 0.588069, 0.343863, 0.684932, 0.49319, 0.714058, 0.821744, 0.471935, 0.403946, 0.306955, 0.218678, 0.33369, 0.488001, 0.486962, 0.18709, 0.49142, 0.55611, 0.419167, 0.368608, 0.143278, 0.460835, 0.597125, 0.53096, 0.498207, 0.278818, 0.438569, 0.6022, 0.700038, 0.752436, 0.577385, 0.702383, 0.725097, 0.733754, 0.816304, 0.23933, 0.407514, 0.337893, 0.252521, 0.474335, 0.367075, 0.270168, 0.41051, 0.64189, 0.830777, 0.55564, 0.454295, 0.55645, 0.75015, 0.929997, 0.66257, 0.561664, 0.481275, 0.495449, 0.666306, 0.663573, 0.372107, 0.205603, 0.192776, 0.247849}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } { auto p = create_program("half_pixel", migraphx::op::pooling_mode::max, 0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.819145, 0.373103, 0.258302, 0.515419, 0.726104, 0.540536, 0.545512, 0.38511, 0.376545, 0.274635, 0.22341, 0.184511, 0.230843, 0.404869, 0.29546, 0.540409, 0.265838, 0.409324, 0.213915, 0.708654, 0.687264, 0.580821, 0.461283, 0.462879, 0.709632, 0.27873, 0.083619, 0.22428, 0.313992, 0.410508, 0.0929099, 0.415373, 0.296695, 0.231574, 0.136836, 0.0683, 0.296695, 0.211925, 0.245385, 0.28053, 0.17091, 0.179879, 0.245385, 0.343539, 0.392742, 0.51273, 0.536193, 0.382995, 0.422793, 0.761886, 0.0839429, 0.276444, 0.19746, 0.126117, 0.378351, 0.254646, 0.092148, 0.272825, 0.381955, 0.626599, 0.251325, 0.244475, 0.194875, 0.272825, 0.44757, 0.351855, 0.342265, 0.244475, 0.274841, 0.553644, 0.607176, 0.202392, 0.07425, 0.066087, 0.126279}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } } ROCm-AMDMIGraphX-46524e8/test/ref/rsqrt.cpp000066400000000000000000000055351510465702400201620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(rsqrt_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l = mm->add_literal(migraphx::literal{s, {4.0, 16.0, 64.0}}); mm->add_instruction(migraphx::make_op("rsqrt"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.5, 0.25, 0.125}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rsqrt_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("rsqrt"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{4.0, 16.0, 64.0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.5, 0.25, 0.125}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/scalar.cpp000066400000000000000000000072721510465702400202540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(imagescaler_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 3, 2, 2}}; auto img = mm->add_literal(migraphx::literal{s, {0.2, 0.3, 0.5, 0.4, 0.7, 0.8, 0.1, 0.9, 0.15, 0.25, 0.35, 0.45}}); auto scale_val = mm->add_literal(2.f); auto scaled_tensor = mm->add_instruction( migraphx::make_op("scalar", {{"scalar_bcst_dims", s.lens()}}), scale_val); auto img_scaled = mm->add_instruction(migraphx::make_op("mul"), img, scaled_tensor); auto bias_vals = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {3}}, {0.01, 0.02, 0.03}}); auto bias_bcast = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), bias_vals); mm->add_instruction(migraphx::make_op("add"), img_scaled, bias_bcast); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.41, 0.61, 1.01, 0.81, 1.42, 1.62, 0.22, 1.82, 0.33, 0.53, 0.73, 0.93}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/scan_slice.cpp000066400000000000000000000140031510465702400211000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include static migraphx::shape make_shape(const std::vector& lens) { return migraphx::shape{migraphx::shape::int32_type, lens}; } static migraphx::program make_scan_slice_program(int64_t axis, int64_t direction) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape data_sh{migraphx::shape::int32_type, {2, 2, 2}}; std::vector data(data_sh.elements()); std::iota(data.begin(), data.end(), 0); auto data_lit = mm->add_literal(migraphx::literal{data_sh, data}); migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; auto idx_param = mm->add_parameter("idx", idx_sh); mm->add_instruction(migraphx::make_op("scan_slice", {{"axis", axis}, {"direction", direction}}), data_lit, idx_param); p.compile(migraphx::make_target("ref")); return p; } TEST_CASE(scan_slice_test_1) { auto p = make_scan_slice_program(0, 0); migraphx::parameter_map pm; int64_t idx = 0; migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; pm["idx"] = migraphx::argument{idx_sh, &idx}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({1, 2, 2})); EXPECT(result.to_vector() == std::vector{0, 1, 2, 3}); idx = 1; result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({1, 2, 2})); EXPECT(result.to_vector() == std::vector{4, 5, 6, 7}); } TEST_CASE(scan_slice_test_2) { auto p = make_scan_slice_program(1, 0); migraphx::parameter_map pm; int64_t idx = 0; migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; pm["idx"] = migraphx::argument{idx_sh, &idx}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 1, 2})); EXPECT(result.to_vector() == std::vector{0, 1, 4, 5}); idx = 1; result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 1, 2})); EXPECT(result.to_vector() == std::vector{2, 3, 6, 7}); } TEST_CASE(scan_slice_test_3) { auto p = make_scan_slice_program(2, 0); migraphx::parameter_map pm; int64_t idx = 0; migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; pm["idx"] = migraphx::argument{idx_sh, &idx}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 2, 1})); EXPECT(result.to_vector() == std::vector{0, 2, 4, 6}); idx = 1; result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 2, 1})); EXPECT(result.to_vector() == std::vector{1, 3, 5, 7}); } TEST_CASE(scan_slice_test_4) { auto p = make_scan_slice_program(-3, 0); migraphx::parameter_map pm; int64_t idx = 0; migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; pm["idx"] = migraphx::argument{idx_sh, &idx}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({1, 2, 2})); EXPECT(result.to_vector() == std::vector{0, 1, 2, 3}); idx = 1; result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({1, 2, 2})); EXPECT(result.to_vector() == std::vector{4, 5, 6, 7}); } TEST_CASE(scan_slice_test_5) { auto p = make_scan_slice_program(0, 1); migraphx::parameter_map pm; int64_t idx = 0; migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; pm["idx"] = migraphx::argument{idx_sh, &idx}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({1, 2, 2})); EXPECT(result.to_vector() == std::vector{4, 5, 6, 7}); idx = 1; result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({1, 2, 2})); EXPECT(result.to_vector() == std::vector{0, 1, 2, 3}); } TEST_CASE(scan_slice_test_6) { auto p = make_scan_slice_program(-2, 1); migraphx::parameter_map pm; int64_t idx = 0; migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; pm["idx"] = migraphx::argument{idx_sh, &idx}; auto result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 1, 2})); EXPECT(result.to_vector() == std::vector{2, 3, 6, 7}); idx = 1; result = p.eval(pm).back(); EXPECT(result.get_shape() == make_shape({2, 1, 2})); EXPECT(result.to_vector() == std::vector{0, 1, 4, 5}); } TEST_CASE(scan_slice_test_7) { auto p = make_scan_slice_program(0, 0); migraphx::parameter_map pm; int64_t idx = 2; migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; pm["idx"] = migraphx::argument{idx_sh, &idx}; EXPECT(test::throws([&] { p.eval(pm); })); } ROCm-AMDMIGraphX-46524e8/test/ref/scatter_elements.cpp000066400000000000000000000374101510465702400223450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static migraphx::program create_scatter_elements_program(const std::string& reduction_mode, int axis) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {3, 3}}; std::vector vd(sd.elements(), 0.0f); migraphx::shape si{migraphx::shape::int32_type, {2, 3}}; std::vector vi = {1, 0, 2, 0, 2, 1}; migraphx::shape su{migraphx::shape::float_type, {2, 3}}; std::vector vu = {1.0, 1.1, 1.2, 2.0, 2.1, 2.2}; auto ld = mm->add_literal(migraphx::literal{sd, vd}); auto li = mm->add_literal(migraphx::literal{si, vi}); auto lu = mm->add_literal(migraphx::literal{su, vu}); auto r = mm->add_instruction( migraphx::make_op("scatter_" + reduction_mode, {{"axis", axis}}), ld, li, lu); mm->add_return({r}); return p; } TEST_CASE(scatter_elements_axis_0_test) { migraphx::program p = create_scatter_elements_program("none", 0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {2.0, 1.1, 0.0, 1.0, 0.0, 2.2, 0.0, 2.1, 1.2}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatter_elements_axis_neg_2_test) { migraphx::program p = create_scatter_elements_program("none", -2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {2.0, 1.1, 0.0, 1.0, 0.0, 2.2, 0.0, 2.1, 1.2}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatter_elements_axis_1_test) { migraphx::program p = create_scatter_elements_program("none", 1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.1, 1.0, 1.2, 2.0, 2.2, 2.1, 0.0, 0.0, 0.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } static migraphx::program create_scatter_elements_program2(const std::string& reduction_mode, int axis) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {1, 5}}; std::vector vd({1., 2., 3., 4., 5.}); migraphx::shape si{migraphx::shape::int32_type, {1, 2}}; std::vector vi = {1, 3}; migraphx::shape su{migraphx::shape::float_type, {1, 2}}; std::vector vu = {1.1, 2.1}; auto ld = mm->add_literal(migraphx::literal{sd, vd}); auto li = mm->add_literal(migraphx::literal{si, vi}); auto lu = mm->add_literal(migraphx::literal{su, vu}); auto r = mm->add_instruction( migraphx::make_op("scatter_" + reduction_mode, {{"axis", axis}}), ld, li, lu); mm->add_return({r}); return p; } TEST_CASE(scatter_elements_none_axis_1_test) { migraphx::program p = create_scatter_elements_program2("none", 1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_none = {1.0, 1.1, 3.0, 2.1, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_none)); } TEST_CASE(scatter_elements_mul_axis_1_test) { migraphx::program p = create_scatter_elements_program2("mul", 1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_mul = {1.0, 2.2, 3.0, 8.4, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_mul)); } TEST_CASE(scatter_elements_add_axis_1_test) { migraphx::program p = create_scatter_elements_program2("add", 1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_add = {1.0, 3.1, 3.0, 6.1, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_add)); } TEST_CASE(scatter_elements_min_axis_1_test) { migraphx::program p = create_scatter_elements_program2("min", 1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_min = {1.0, 1.1, 3.0, 2.1, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_min)); } TEST_CASE(scatter_elements_max_axis_1_test) { migraphx::program p = create_scatter_elements_program2("max", 1); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_max = {1.0, 2.0, 3.0, 4.0, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_max)); } static auto scatter_elements_duplicate_index_test(const std::string& reduction_mode) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {1, 5}}; std::vector vd({1., 2., 3., 4., 5.}); migraphx::shape si{migraphx::shape::int32_type, {1, 2}}; std::vector vi = {1, 1}; migraphx::shape su{migraphx::shape::float_type, {1, 2}}; std::vector vu = {1.1, 2.1}; auto ld = mm->add_literal(migraphx::literal{sd, vd}); auto li = mm->add_literal(migraphx::literal{si, vi}); auto lu = mm->add_literal(migraphx::literal{su, vu}); auto r = mm->add_instruction( migraphx::make_op("scatter_" + reduction_mode, {{"axis", 1}}), ld, li, lu); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); return results_vector; } TEST_CASE(scatter_elements_add_axis_1_duplicate_idx_test) { const auto results = scatter_elements_duplicate_index_test("add"); const std::vector gold{1.0, 5.2, 3.0, 4.0, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results, gold)); } TEST_CASE(scatter_elements_mul_axis_1_duplicate_idx_test) { const auto results = scatter_elements_duplicate_index_test("mul"); const std::vector gold{1.0, 4.62, 3.0, 4.0, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results, gold)); } TEST_CASE(scatter_elements_min_axis_1_duplicate_idx_test) { const auto results = scatter_elements_duplicate_index_test("min"); const std::vector gold{1.0, 1.1, 3.0, 4.0, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results, gold)); } TEST_CASE(scatter_elements_max_axis_1_duplicate_idx_test) { const auto results = scatter_elements_duplicate_index_test("max"); const std::vector gold{1.0, 2.1, 3.0, 4.0, 5.0}; EXPECT(migraphx::verify::verify_rms_range(results, gold)); } static migraphx::program create_scatter_elements_program_3x3(const std::string& reduction_mode, int axis) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {3, 3}}; std::vector vd(sd.elements(), 3.0f); migraphx::shape si{migraphx::shape::int32_type, {3, 2}}; std::vector vi = {1, 0, 0, 2, 2, 1}; migraphx::shape su{migraphx::shape::float_type, {3, 2}}; std::vector vu = {1.0, 7.0, 1.1, 7.1, 1.2, 7.2}; auto ld = mm->add_literal(migraphx::literal{sd, vd}); auto li = mm->add_literal(migraphx::literal{si, vi}); auto lu = mm->add_literal(migraphx::literal{su, vu}); auto r = mm->add_instruction( migraphx::make_op("scatter_" + reduction_mode, {{"axis", axis}}), ld, li, lu); mm->add_return({r}); return p; } TEST_CASE(scatter_elements_none_axis_0_3x3_test) { migraphx::program p = create_scatter_elements_program_3x3("none", 0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_none2 = {1.1, 7.0, 3.0, 1.0, 7.2, 3.0, 1.2, 7.1, 3.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_none2)); } TEST_CASE(scatter_elements_add_axis_0_3x3_test) { migraphx::program p = create_scatter_elements_program_3x3("add", 0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_a3 = {4.1, 10.0, 3.0, 4.0, 10.2, 3.0, 4.2, 10.1, 3.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_a3)); } TEST_CASE(scatter_elements_mul_axis_0_3x3_test) { migraphx::program p = create_scatter_elements_program_3x3("mul", 0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_mul2 = {3.3, 21.0, 3.0, 3.0, 21.6, 3.0, 3.6, 21.3, 3.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_mul2)); } TEST_CASE(scatter_elements_min_axis_0_3x3_test) { migraphx::program p = create_scatter_elements_program_3x3("min", 0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_min = {1.1, 3.0, 3.0, 1.0, 3.0, 3.0, 1.2, 3.0, 3.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_min)); } TEST_CASE(scatter_elements_max_axis_0_3x3_test) { migraphx::program p = create_scatter_elements_program_3x3("max", 0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold_max = {3.0, 7.0, 3.0, 3.0, 7.2, 3.0, 3.0, 7.1, 3.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold_max)); } static auto scatter_elements_3x3_duplicate_index_test(const std::string& reduction_mode) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {3, 3}}; std::vector vd(sd.elements(), 1.0f); migraphx::shape si{migraphx::shape::int32_type, {3, 2}}; std::vector vi = {1, 0, 1, 0, 1, 0}; migraphx::shape su{migraphx::shape::float_type, {3, 2}}; std::vector vu = {0.9, 2.0, 1.1, 2.1, 1.2, 2.2}; auto ld = mm->add_literal(migraphx::literal{sd, vd}); auto li = mm->add_literal(migraphx::literal{si, vi}); auto lu = mm->add_literal(migraphx::literal{su, vu}); auto r = mm->add_instruction( migraphx::make_op("scatter_" + reduction_mode, {{"axis", 0}}), ld, li, lu); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); return results_vector; } TEST_CASE(scatter_elements_skip_out_of_bounds_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {3, 3}}; std::vector vd(sd.elements(), 0.0f); migraphx::shape si{migraphx::shape::int32_type, {2, 3}}; // clang-format off std::vector vi = { -1, 0, 20, 0, -8, 1 }; // clang-format on migraphx::shape su{migraphx::shape::float_type, {2, 3}}; // clang-format off std::vector vu = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 }; // clang-format on auto lit_data = mm->add_literal(migraphx::literal{sd, vd}); auto lit_inds = mm->add_literal(migraphx::literal{si, vi}); auto lit_updates = mm->add_literal(migraphx::literal{su, vu}); auto r = mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 0}, {"skip_out_of_bounds", true}}), lit_data, lit_inds, lit_updates); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); // clang-format off std::vector gold = { 4.0, 2.0, 0.0, 0.0, 0.0, 6.0, 1.0, 0.0, 0.0 }; // clang-format on EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatter_elements_add_axis_0_3x3_duplicate_index_test) { const auto results = scatter_elements_3x3_duplicate_index_test("add"); const std::vector gold = {1.0, 7.3, 1.0, 4.2, 1.0, 1.0, 1.0, 1.0, 1.0}; EXPECT(migraphx::verify::verify_rms_range(results, gold)); } TEST_CASE(scatter_elements_mul_axis_0_3x3_duplicate_index_test) { const auto results = scatter_elements_3x3_duplicate_index_test("mul"); const std::vector gold = {1.0, 9.24, 1.0, 1.188, 1.0, 1.0, 1.0, 1.0, 1.0}; EXPECT(migraphx::verify::verify_rms_range(results, gold)); } TEST_CASE(scatter_elements_min_axis_0_3x3_duplicate_index_test) { const auto results = scatter_elements_3x3_duplicate_index_test("min"); const std::vector gold = {1.0, 1.0, 1.0, 0.9, 1.0, 1.0, 1.0, 1.0, 1.0}; EXPECT(migraphx::verify::verify_rms_range(results, gold)); } TEST_CASE(scatter_elements_max_axis_0_3x3_duplicate_index_test) { const auto results = scatter_elements_3x3_duplicate_index_test("max"); const std::vector gold = {1.0, 2.2, 1.0, 1.2, 1.0, 1.0, 1.0, 1.0, 1.0}; EXPECT(migraphx::verify::verify_rms_range(results, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/scatternd_add.cpp000066400000000000000000000115751510465702400216070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(scatternd_add_reduction_test) { // reduction = add migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {8, 1}}; migraphx::shape us{dtype, {8}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{4, 3, 1, 7, 4, 3, 1, 7}; std::vector upd_vec{9, 10, 11, 12, -8, -9, -10, -11}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_add"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 3, 3, 5, 6, 6, 7, 9}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_reduction_dyn_test) { // reduction = add, with dynamic input shapes migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape::dynamic_dimension dd{3, 6}; migraphx::shape ds{migraphx::shape::float_type, {dd, dd, dd}}; migraphx::shape is{itype, {2, 1}}; migraphx::shape us{dtype, {{2, 2}, dd, dd}}; auto xdata = mm->add_parameter("X", ds); auto xindex = mm->add_parameter("I", is); auto xupdates = mm->add_parameter("U", us); auto scatternd_add_op = migraphx::make_op("scatternd_add"); auto scatternd = mm->add_instruction(scatternd_add_op, xdata, xindex, xupdates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4, 4, 4}}; // data std::vector input_data{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector input_index{0, 2}; migraphx::shape input_fixed_shape1{migraphx::shape::float_type, {2, 4, 4}}; // updates std::vector input_updates{5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}; params["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); params["I"] = migraphx::argument(is, input_index.data()); params["U"] = migraphx::argument(input_fixed_shape1, input_updates.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{6, 7, 8, 9, 11, 12, 13, 14, 15, 14, 13, 12, 12, 11, 10, 9, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 9, 8, 7, 6, 6, 5, 4, 3, 4, 5, 6, 7, 9, 10, 11, 12, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/scatternd_max.cpp000066400000000000000000000202501510465702400216320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(scatternd_max_test_1) { // r=1, q=2, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{4, 3, 1, 7}; std::vector upd_vec{9, 3, 1, 12}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_max"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 3, 4, 9, 6, 7, 12}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_max_test_2) { // r=2, q=2, k=2 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2}}; migraphx::shape is{itype, {2, 2}}; migraphx::shape us{dtype, {2}}; std::vector data_vec{1, 2, 3, 4}; std::vector ind_vec{0, 0, 0, 1}; std::vector upd_vec{5, 1}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_max"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{5, 2, 3, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_max_test_3) { // r=3, q=3, k=3 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2, 2}}; migraphx::shape is{itype, {2, 1, 3}}; migraphx::shape us{dtype, {2, 1}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 0, 0, 1, 1, 1}; std::vector upd_vec{9, 1}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_max"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{9, 2, 3, 4, 5, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_max_test_4) { // r=3, q=2, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {4, 4, 4}}; migraphx::shape is{itype, {2, 1}}; migraphx::shape us{dtype, {2, 4, 4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 2}; std::vector upd_vec{5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_max"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{5, 5, 5, 5, 6, 6, 7, 8, 8, 7, 7, 7, 8, 8, 8, 8, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_max_test_duplicate_idx) { // r=3, q=2, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {4, 4, 4}}; migraphx::shape is{itype, {2, 1}}; migraphx::shape us{dtype, {2, 4, 4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 0}; std::vector upd_vec{5, 5, 5, 5, 2, 2, 2, 2, 7, 7, 7, 7, 4, 4, 4, 4, 1, 1, 1, 1, 6, 6, 6, 6, 3, 3, 3, 3, 8, 8, 8, 8}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_max"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{5, 5, 5, 5, 6, 6, 7, 8, 8, 7, 7, 7, 8, 8, 8, 8, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/scatternd_min.cpp000066400000000000000000000202471510465702400216360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(scatternd_min_test_1) { // r=1, q=2, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{4, 3, 1, 7}; std::vector upd_vec{9, 3, 1, 12}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_min"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 1, 3, 3, 5, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_min_test_2) { // r=2, q=2, k=2 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2}}; migraphx::shape is{itype, {2, 2}}; migraphx::shape us{dtype, {2}}; std::vector data_vec{1, 2, 3, 4}; std::vector ind_vec{0, 0, 0, 1}; std::vector upd_vec{5, 1}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_min"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 1, 3, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_min_test_3) { // r=3, q=3, k=3 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2, 2}}; migraphx::shape is{itype, {2, 1, 3}}; migraphx::shape us{dtype, {2, 1}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 0, 0, 1, 1, 1}; std::vector upd_vec{9, 1}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_min"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 3, 4, 5, 6, 7, 1}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_min_test_4) { // r=3, q=2, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {4, 4, 4}}; migraphx::shape is{itype, {2, 1}}; migraphx::shape us{dtype, {2, 4, 4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 2}; std::vector upd_vec{5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_min"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 3, 4, 5, 6, 6, 6, 7, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 3, 3, 4, 4, 4, 4, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_min_test_duplicate_idx) { // r=3, q=2, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {4, 4, 4}}; migraphx::shape is{itype, {2, 1}}; migraphx::shape us{dtype, {2, 4, 4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 0}; std::vector upd_vec{5, 5, 5, 5, 2, 2, 2, 2, 7, 7, 7, 7, 4, 4, 4, 4, 1, 1, 1, 1, 6, 6, 6, 6, 3, 3, 3, 3, 8, 8, 8, 8}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_min"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/scatternd_mul.cpp000066400000000000000000000050341510465702400216450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(scatternd_mul_reduction_test) { // reduction = mul migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{4, 3, 1, 7}; std::vector upd_vec{9, 10, 11, 12}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_mul"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 22, 3, 40, 45, 6, 7, 96}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/scatternd_none.cpp000066400000000000000000000263731510465702400220200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(scatternd_shapes_test_1) { // broadcasted input migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector ind_vec{4, 3, 1, 7}; std::vector upd_vec{9, 10, 11, 12}; auto data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8}}}), mm->add_literal(migraphx::literal{0.0f})); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0, 11, 0, 10, 9, 0, 0, 12}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_shapes_test_2) { // non-standard shape input migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2}}; migraphx::shape is{itype, {2, 2}}; migraphx::shape us{dtype, {2}}; std::vector data_vec{1, 2, 3, 4}; std::vector ind_vec{0, 0, 0, 1}; std::vector upd_vec{5, 6}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto td = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), data); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), td, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{5, 6, 2, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_shapes_test_3) { // non-standard updates shape migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2, 2}}; migraphx::shape is{itype, {2, 1, 3}}; migraphx::shape us{dtype, {1, 2}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 0, 0, 1, 1, 1}; std::vector upd_vec{9, 10}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto tu = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), updates); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), data, indices, tu); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{9, 2, 3, 4, 5, 6, 7, 10}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_test_1) { // r=1, q=2, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{4, 3, 1, 7}; std::vector upd_vec{9, 10, 11, 12}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 11, 3, 10, 9, 6, 7, 12}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_test_2) { // r=2, q=2, k=2 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2}}; migraphx::shape is{itype, {2, 2}}; migraphx::shape us{dtype, {2}}; std::vector data_vec{1, 2, 3, 4}; std::vector ind_vec{0, 0, 0, 1}; std::vector upd_vec{5, 6}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{5, 6, 3, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_test_3) { // r=3, q=3, k=3 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2, 2}}; migraphx::shape is{itype, {2, 1, 3}}; migraphx::shape us{dtype, {2, 1}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 0, 0, 1, 1, 1}; std::vector upd_vec{9, 10}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{9, 2, 3, 4, 5, 6, 7, 10}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_test_4) { // r=3, q=2, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {4, 4, 4}}; migraphx::shape is{itype, {2, 1}}; migraphx::shape us{dtype, {2, 4, 4}}; std::vector data_vec{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; std::vector ind_vec{0, 2}; std::vector upd_vec{5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}; auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(scatternd_test_5) { // r=5, q=1, k=1 migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {2, 2, 2, 2, 2}}; migraphx::shape is{itype, {1}}; migraphx::shape us{dtype, {2, 2, 2, 2}}; std::vector data_vec(32, 1); std::vector ind_vec{1}; std::vector upd_vec(16, 0); auto data = mm->add_literal(migraphx::literal{ds, data_vec}); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_literal(migraphx::literal{us, upd_vec}); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), data, indices, updates); mm->add_return({scatternd}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold(32, 0); std::copy(data_vec.begin(), data_vec.begin() + 16, gold.begin()); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/select_module.cpp000066400000000000000000000242011510465702400216220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(select_module_add_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm->add_literal(migraphx::literal{lit_s, {6}}); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* batch1 = create_submodule(1, "batch_1"); auto* batch2 = create_submodule(2, "batch_2"); auto* batch3 = create_submodule(3, "batch_3"); auto* batch4 = create_submodule(4, "batch_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input = mm->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input}, {batch1, batch2, batch3, batch4}); auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm->add_return({ret}); p.compile(migraphx::make_target("ref")); std::vector input_data{-4, 8, -1, 4, -1, 8, 8, -4}; migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 4}}; params["data"] = migraphx::argument(input_fixed_shape, input_data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{2, 14, 5, 10, 5, 14, 14, 2}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(select_module_reduce_test0) { migraphx::program p; // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 2, 2}}; auto sm_input = submod->add_parameter("data", sm_shape); auto reduce_ins = submod->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sm_input); auto squeeze_ins = submod->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), reduce_ins); submod->add_return({squeeze_ins}); return submod; }; auto* batch1 = create_submodule(1, "batch_1"); auto* batch2 = create_submodule(2, "batch_2"); auto* batch3 = create_submodule(3, "batch_3"); auto* batch4 = create_submodule(4, "batch_4"); auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {2, 2}, {2, 2}}}; auto input = mm->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input}, {batch1, batch2, batch3, batch4}); auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm->add_return({ret}); p.compile(migraphx::make_target("ref")); std::vector input_data{-4, 8, -1, 4, -1, 8, 8, -4}; migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {2, 2, 2}}; params["data"] = migraphx::argument(input_fixed_shape, input_data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-5, 12, 7, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(select_module_reduce_test1) { migraphx::program p; // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 2, 2}}; auto sm_input = submod->add_parameter("data", sm_shape); auto reduce_ins = submod->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sm_input); auto squeeze_ins = submod->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), reduce_ins); submod->add_return({squeeze_ins}); return submod; }; auto* batch1 = create_submodule(1, "batch_1"); auto* batch2 = create_submodule(2, "batch_2"); auto* batch3 = create_submodule(3, "batch_3"); auto* batch4 = create_submodule(4, "batch_4"); auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {2, 2}, {2, 2}}}; auto input = mm->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input}, {batch1, batch2, batch3, batch4}); auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm->add_return({ret}); p.compile(migraphx::make_target("ref")); std::vector input_data{-4, 8, -1, 4, -1, 8, 8, -4, -4, 8, -1, 4, -1, 8, 8, -4}; migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {4, 2, 2}}; params["data"] = migraphx::argument(input_fixed_shape, input_data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-5, 12, 7, 4, -5, 12, 7, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(select_module_not_found_error) { migraphx::program p; // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 2, 2}}; auto sm_input = submod->add_parameter("data", sm_shape); auto reduce_ins = submod->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sm_input); auto squeeze_ins = submod->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), reduce_ins); submod->add_return({squeeze_ins}); return submod; }; auto* batch1 = create_submodule(1, "batch_1"); auto* batch2 = create_submodule(2, "batch_2"); auto* batch3 = create_submodule(3, "batch_3"); auto* batch4 = create_submodule(4, "batch_4"); auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {2, 2}, {2, 2}}}; auto input = mm->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input}, {batch1, batch2, batch3, batch4}); auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm->add_return({ret}); p.compile(migraphx::make_target("ref")); std::vector input_data{-4, 8, -1, 4, -1, 8, 8, -4, -4, 8, -1, 4, -1, 8, 8, -4, -1, 8, 8, -4}; migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {5, 2, 2}}; params["data"] = migraphx::argument(input_fixed_shape, input_data.data()); EXPECT(test::throws([&] { std::ignore = p.eval(params).back(); })); } ROCm-AMDMIGraphX-46524e8/test/ref/sigmoid.cpp000066400000000000000000000056701510465702400204420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static float sigmoid(float x) { return 1 / (1 + expf(-x)); } TEST_CASE(sigmoid_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; auto l = mm->add_literal(migraphx::literal{s, {-1, 2, -3, 4}}); mm->add_instruction(migraphx::make_op("sigmoid"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{sigmoid(-1), sigmoid(2), sigmoid(-3), sigmoid(4)}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(sigmoid_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 4}, {2, 2}}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("sigmoid"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{-1, 2, -3, 4}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {2, 2}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{sigmoid(-1), sigmoid(2), sigmoid(-3), sigmoid(4)}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/sign.cpp000066400000000000000000000056721510465702400177510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(sign_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {5}}; auto l = mm->add_literal( migraphx::literal{s, {1.02481645, 0.85643062, -0.03404123, -0.92791926, 0.0}}); mm->add_instruction(migraphx::make_op("sign"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.0, 1.0, -1.0, -1.0, 0.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(sign_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("sign"), input); p.compile(migraphx::make_target("ref")); std::vector input_data{1.02481645, 0.85643062, -0.03404123, -0.92791926, 0.0}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {5}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {1.0, 1.0, -1.0, -1.0, 0.0}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/sin.cpp000066400000000000000000000061071510465702400175740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(sin_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data = {-1, 0, 1}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("sin"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return sinf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(sin_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); mm->add_instruction(migraphx::make_op("sin"), input); p.compile(migraphx::make_target("ref")); std::vector input_data = {-1, 0, 1}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return sinf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/sinh.cpp000066400000000000000000000060601510465702400177420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(sinh_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector data{-1.0, 2.0, -3.0, 4.0}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("sinh"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return sinhf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(sinh_dynamic_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{2, 4}, {2, 4}}}; auto input = mm->add_parameter("X", s); std::vector input_data{-1.0, 2.0, -3.0, 4.0}; mm->add_instruction(migraphx::make_op("sinh"), input); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return sinhf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/slice.cpp000066400000000000000000000456201510465702400201050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(slice_test_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 2 * 3); std::iota(data.begin(), data.end(), 0); migraphx::shape s{migraphx::shape::int32_type, {2, 2, 3}}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1}}, {"ends", {3}}}), l0); migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 2}, {6, 3, 1}}; EXPECT(p.get_output_shapes().back() == s2); p.compile(migraphx::make_target("ref")); migraphx::shape sresult{migraphx::shape::int32_type, {2, 2, 2}, {4, 2, 1}}; auto result = p.eval({}).back(); std::vector gold = {1, 2, 4, 5, 7, 8, 10, 11}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(result.get_shape() == sresult); } TEST_CASE(slice_test_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 2 * 3); std::iota(data.begin(), data.end(), 0); migraphx::shape s{migraphx::shape::int32_type, {2, 2, 3}}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1, 2}}, {"starts", {0, 0, 0}}, {"ends", {2, 2, 2}}}), l0); migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 2}, {6, 3, 1}}; EXPECT(p.get_output_shapes().back() == s2); p.compile(migraphx::make_target("ref")); migraphx::shape sresult{migraphx::shape::int32_type, {2, 2, 2}, {4, 2, 1}}; auto result = p.eval({}).back(); std::vector gold = {0, 1, 3, 4, 6, 7, 9, 10}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(result.get_shape() == sresult); } TEST_CASE(slice_var_inputs_static0) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 2 * 3); std::iota(data.begin(), data.end(), 0); migraphx::shape s0{migraphx::shape::int32_type, {2, 2, 3}}; auto l0 = mm->add_literal(migraphx::literal{s0, data}); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), l0, starts, ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector start_data = {1}; std::vector end_data = {3}; params["starts"] = migraphx::argument(s1, start_data.data()); params["ends"] = migraphx::argument(s1, end_data.data()); auto result = p.eval(params).back(); std::vector gold = {1, 2, 4, 5, 7, 8, 10, 11}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_static1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 2 * 3); std::iota(data.begin(), data.end(), 0); migraphx::shape s0{migraphx::shape::int32_type, {2, 2, 3}}; auto l0 = mm->add_literal(migraphx::literal{s0, data}); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), l0, starts, ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector start_data = {-2}; std::vector end_data = {2831}; params["starts"] = migraphx::argument(s1, start_data.data()); params["ends"] = migraphx::argument(s1, end_data.data()); auto result = p.eval(params).back(); std::vector gold = {1, 2, 4, 5, 7, 8, 10, 11}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_static2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 2 * 3); std::iota(data.begin(), data.end(), 0); migraphx::shape s0{migraphx::shape::float_type, {2, 2, 3}}; auto l0 = mm->add_literal(migraphx::literal{s0, data}); migraphx::shape s1{migraphx::shape::int64_type, {3}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); auto axes = mm->add_parameter("axes", s1); mm->add_instruction(migraphx::make_op("slice"), l0, starts, ends, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; std::vector start_data = {0, 0, 0}; std::vector end_data = {2, 2, 2}; std::vector axes_data = {0, 1, 2}; params["starts"] = migraphx::argument(s1, start_data.data()); params["ends"] = migraphx::argument(s1, end_data.data()); params["axes"] = migraphx::argument(s1, axes_data.data()); auto result = p.eval(params).back(); std::vector gold = {0, 1, 3, 4, 6, 7, 9, 10}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_dyn0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::int32_type, {{2, 4, {2, 4}}, {2, 4, {2, 4}}, {3, 8}}}; auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"ends", {10}}}), input, starts); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 3}}; std::vector input_data(2 * 2 * 3); std::iota(input_data.begin(), input_data.end(), 0); std::vector start_data = {1}; params["input"] = migraphx::argument(s2, input_data.data()); params["starts"] = migraphx::argument(s1, start_data.data()); auto result = p.eval(params).back(); std::vector gold = {1, 2, 4, 5, 7, 8, 10, 11}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_dyn1) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::int32_type, {{2, 4, {2, 4}}, {2, 4, {2, 4}}, {3, 8}}}; auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto ends = mm->add_parameter("ends", s1); mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {-5}}}), input, ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 3}}; std::vector input_data(2 * 2 * 3); std::iota(input_data.begin(), input_data.end(), 0); std::vector ends_data = {3}; params["input"] = migraphx::argument(s2, input_data.data()); params["ends"] = migraphx::argument(s1, ends_data.data()); auto result = p.eval(params).back(); std::vector gold = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; std::vector results_vector(2 * 2 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_dyn2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::int32_type, {{2, 4, {2, 4}}, {2, 4, {2, 4}}, {3, 8}}}; auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto axes = mm->add_parameter("axes", s1); mm->add_instruction(migraphx::make_op("slice", {{"starts", {1}}, {"ends", {-1}}}), input, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 3}}; std::vector input_data(2 * 2 * 3); std::iota(input_data.begin(), input_data.end(), 0); std::vector axes_data = {2}; params["input"] = migraphx::argument(s2, input_data.data()); params["axes"] = migraphx::argument(s1, axes_data.data()); auto result = p.eval(params).back(); std::vector gold = {1, 4, 7, 10}; std::vector results_vector(2 * 2 * 1); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_dyn3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::int32_type, {{2, 4, {2, 4}}, {2, 4, {2, 4}}, {3, 8}}}; auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), input, starts, ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 3}}; std::vector input_data(2 * 2 * 3); std::iota(input_data.begin(), input_data.end(), 0); std::vector starts_data = {1}; std::vector ends_data = {std::numeric_limits::max()}; params["input"] = migraphx::argument(s2, input_data.data()); params["starts"] = migraphx::argument(s1, starts_data.data()); params["ends"] = migraphx::argument(s1, ends_data.data()); auto result = p.eval(params).back(); std::vector gold = {1, 2, 4, 5, 7, 8, 10, 11}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_dyn4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::int32_type, {{2, 4, {2, 4}}, {2, 4, {2, 4}}, {3, 8}}}; auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto axes = mm->add_parameter("axes", s1); mm->add_instruction(migraphx::make_op("slice", {{"ends", {std::numeric_limits::max()}}}), input, starts, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 3}}; std::vector input_data(2 * 2 * 3); std::iota(input_data.begin(), input_data.end(), 0); std::vector starts_data = {1}; std::vector axes_data = {2}; params["input"] = migraphx::argument(s2, input_data.data()); params["starts"] = migraphx::argument(s1, starts_data.data()); params["axes"] = migraphx::argument(s1, axes_data.data()); auto result = p.eval(params).back(); std::vector gold = {1, 2, 4, 5, 7, 8, 10, 11}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_dyn5) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::int32_type, {{2, 4, {2, 4}}, {2, 4, {2, 4}}, {3, 8}}}; auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto ends = mm->add_parameter("ends", s1); auto axes = mm->add_parameter("axes", s1); mm->add_instruction(migraphx::make_op("slice", {{"starts", {-4}}}), input, ends, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 3}}; std::vector input_data(2 * 2 * 3); std::iota(input_data.begin(), input_data.end(), 0); std::vector ends_data = {2}; std::vector axes_data = {2}; params["input"] = migraphx::argument(s2, input_data.data()); params["ends"] = migraphx::argument(s1, ends_data.data()); params["axes"] = migraphx::argument(s1, axes_data.data()); auto result = p.eval(params).back(); std::vector gold = {0, 1, 3, 4, 6, 7, 9, 10}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_var_inputs_dyn6) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::int32_type, {{2, 4, {2, 4}}, {2, 4, {2, 4}}, {3, 8}}}; auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), input, starts, ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; migraphx::shape s2{migraphx::shape::int32_type, {2, 2, 3}}; std::vector input_data(2 * 2 * 3); std::iota(input_data.begin(), input_data.end(), 0); std::vector start_data = {1}; std::vector end_data = {3}; params["input"] = migraphx::argument(s2, input_data.data()); params["starts"] = migraphx::argument(s1, start_data.data()); params["ends"] = migraphx::argument(s1, end_data.data()); auto result = p.eval(params).back(); std::vector gold = {1, 2, 4, 5, 7, 8, 10, 11}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(slice_dyn_test0) { // Slice a single dynamic dimension. ax1 slice limits are smaller than min; ax2 "ends" is // too large migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {{2, 3}, {2, 2}, {3, 3}}}; auto x = mm->add_parameter("x", s); mm->add_instruction( migraphx::make_op("slice", {{"axes", {1, 2}}, {"starts", {0, 1}}, {"ends", {1, 6}}}), x); migraphx::shape s2{migraphx::shape::int32_type, {{2, 3}, {1, 1}, {2, 2}}}; EXPECT(p.get_output_shapes().back() == s2); p.compile(migraphx::make_target("ref")); // the strides of sresult are those of the original shape, not // reduced to sliced size. migraphx::shape sresult{migraphx::shape::int32_type, {2, 1, 2}, {6, 3, 1}}; migraphx::shape input_fixed_shape{migraphx::shape::int32_type, {2, 2, 3}}; migraphx::parameter_map params; std::vector data(2 * 2 * 3); std::iota(data.begin(), data.end(), 0); params["x"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector gold = {1, 2, 7, 8}; std::vector results_vector(2 * 1 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(result.get_shape() == sresult); } TEST_CASE(slice_dyn_test1) { // Slice all three dynamic dimensions migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {{2, 2}, {2, 2}, {3, 3}}}; auto x = mm->add_parameter("x", s); mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1, 2}}, {"starts", {0, 0, 0}}, {"ends", {2, 2, 2}}}), x); migraphx::shape s2{migraphx::shape::int32_type, {{2, 2}, {2, 2}, {2, 2}}}; EXPECT(p.get_output_shapes().back() == s2); p.compile(migraphx::make_target("ref")); migraphx::shape sresult{migraphx::shape::int32_type, {2, 2, 2}, {6, 3, 1}}; migraphx::shape input_fixed_shape{migraphx::shape::int32_type, {2, 2, 3}}; migraphx::parameter_map params; std::vector data(2 * 2 * 3); std::iota(data.begin(), data.end(), 0); params["x"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector gold = {0, 1, 3, 4, 6, 7, 9, 10}; std::vector results_vector(2 * 2 * 2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(result.get_shape() == sresult); } ROCm-AMDMIGraphX-46524e8/test/ref/softmax.cpp000066400000000000000000000260031510465702400204610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(softmax_simple_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = {0.25, 0.75}; std::vector gold = {0.377541, 0.622459}; migraphx::shape a_shape{migraphx::shape::float_type, {1, 2}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), al); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(2); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(softmax_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector a = { -5.61869681e-01, 9.07827199e-01, 1.29255986e+00, 3.18533443e-02, -1.22183852e-03, -2.83830553e-01, -1.03245842e+00, -9.28322077e-01, -8.82696748e-01, 1.11327164e-01, -9.20038462e-01, 8.47388089e-01, 2.51734018e-01, 1.50563884e+00, 2.23056650e+00, -6.17576987e-02, -1.00264274e-01, -6.10369384e-01, 1.17537189e+00, -2.51560897e-01, -8.50333512e-01, -8.03578615e-01, -6.51194930e-01, -2.58137047e-01, 4.65528190e-01, 3.23284641e-02, -1.54700470e+00, 1.38096774e+00, 5.39869189e-01, -7.56884992e-01, 1.81503093e+00, -2.11269641e+00, 1.92466557e+00, 1.77230799e+00, 2.21660900e+00, 1.56777036e+00, -2.08995026e-03, 3.50566894e-01, -1.15042710e+00, -1.18577778e+00, 8.90633047e-01, -6.63949102e-02, 1.44661188e+00, 1.59215283e+00, -2.56262213e-01, 9.39079225e-01, 4.07298543e-02, 3.86590779e-01, 6.09607756e-01, 8.22331488e-01, -2.82126725e-01, -9.49052632e-01, -4.24012303e-01, -5.32990396e-01, -3.18386006e+00, 3.27092171e-01, -1.33315325e+00, 3.62459183e-01, 3.74710828e-01, -1.30302286e+00, 1.79680198e-01, -4.51832324e-01, 4.34282750e-01, -7.09520102e-01, 6.20333970e-01, -1.28712380e+00, 2.04130828e-01, -7.70607769e-01, 1.61889160e+00, -1.50951004e+00, -4.10505563e-01, -3.56566496e-02, -1.29747534e+00, -1.49967879e-01, 7.77626812e-01, -8.28408226e-02, 2.73412596e-02, 5.79780899e-03, 9.87900198e-02, -7.95276761e-01, -1.38536084e+00, -6.63573861e-01, 3.89783204e-01, -1.30670881e+00, -7.62425125e-01, -4.04883057e-01, 6.24344349e-01, 3.68128955e-01, -1.01577950e+00, -3.06715906e-01, 5.67961395e-01, 2.98198581e-01, -1.63613629e+00, -3.75131965e-01, -6.75393403e-01, 2.59172034e+00, 6.75538957e-01, 9.07939598e-02, 1.92257717e-01, -1.21592450e+00, -2.73682117e-01, 1.25232983e+00, -1.39969170e+00, -1.91483587e-01, 2.57732719e-01, 3.10056299e-01, 1.41833842e+00, -1.81386679e-01, 3.92868072e-01, -8.14771175e-01, 2.02392387e+00, -9.42091495e-02, -3.77683818e-01, 2.05638766e+00, 2.93796062e-01, -6.02131486e-01, 2.70461679e-01, -8.92358482e-01, 1.04388881e+00, 2.66154885e-01}; std::vector gold = { 0.30191708, 0.59879845, 0.50029165, 0.24915339, 0.36823985, 0.13190967, 0.0349741, 0.18750034, 0.21905553, 0.27000085, 0.0547399, 0.56318235, 0.47422904, 0.78964758, 0.91381913, 0.44601166, 0.47902739, 0.13120073, 0.4449684, 0.18766427, 0.15753111, 0.07844277, 0.05120674, 0.36648798, 0.14637007, 0.13152322, 0.01560997, 0.29065287, 0.49196178, 0.10550152, 0.81890774, 0.06369215, 0.62972021, 0.74931765, 0.67285055, 0.35034987, 0.28612873, 0.31931475, 0.04220394, 0.16093165, 0.22390974, 0.11915915, 0.3115395, 0.35899726, 0.22190949, 0.57518375, 0.13888834, 0.7753762, 0.4642328, 0.57055861, 0.21954368, 0.34515455, 0.09486015, 0.40631217, 0.01842281, 0.48770609, 0.06652815, 0.36023033, 0.42343026, 0.24226256, 0.17348589, 0.44066274, 0.6865865, 0.17296699, 0.46923906, 0.06921105, 0.3570261, 0.4125829, 0.73165393, 0.15302512, 0.29499072, 0.33932695, 0.30852377, 0.40762195, 0.40170741, 0.36259529, 0.60848355, 0.42618036, 0.31721094, 0.02960522, 0.28256637, 0.24389413, 0.2725659, 0.10663581, 0.27622163, 0.28264219, 0.53652936, 0.09476089, 0.40890986, 0.34848392, 0.32572666, 0.53076893, 0.11529481, 0.29117745, 0.14625968, 0.8756339, 0.49818122, 0.10656087, 0.1813329, 0.17664003, 0.21410346, 0.80408043, 0.02315119, 0.27155462, 0.32804728, 0.13268511, 0.61795473, 0.49703068, 0.41696799, 0.10175809, 0.71028161, 0.29929739, 0.17377149, 0.76075399, 0.20071237, 0.32632929, 0.36892858, 0.09416146, 0.26656723, 0.42914796}; migraphx::shape a_shape{migraphx::shape::float_type, {5, 3, 4, 2}}; auto al = mm->add_literal(migraphx::literal{a_shape, a}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), al); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(120); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(softmax_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{migraphx::shape::float_type, {{1, 10}, {1, 3, {3}}, {4, 4}, {2, 2, {2}}}}; auto al = mm->add_parameter("a", a_shape); mm->add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), al); p.compile(migraphx::make_target("ref")); std::vector a = { -5.61869681e-01, 9.07827199e-01, 1.29255986e+00, 3.18533443e-02, -1.22183852e-03, -2.83830553e-01, -1.03245842e+00, -9.28322077e-01, -8.82696748e-01, 1.11327164e-01, -9.20038462e-01, 8.47388089e-01, 2.51734018e-01, 1.50563884e+00, 2.23056650e+00, -6.17576987e-02, -1.00264274e-01, -6.10369384e-01, 1.17537189e+00, -2.51560897e-01, -8.50333512e-01, -8.03578615e-01, -6.51194930e-01, -2.58137047e-01, 4.65528190e-01, 3.23284641e-02, -1.54700470e+00, 1.38096774e+00, 5.39869189e-01, -7.56884992e-01, 1.81503093e+00, -2.11269641e+00, 1.92466557e+00, 1.77230799e+00, 2.21660900e+00, 1.56777036e+00, -2.08995026e-03, 3.50566894e-01, -1.15042710e+00, -1.18577778e+00, 8.90633047e-01, -6.63949102e-02, 1.44661188e+00, 1.59215283e+00, -2.56262213e-01, 9.39079225e-01, 4.07298543e-02, 3.86590779e-01, 6.09607756e-01, 8.22331488e-01, -2.82126725e-01, -9.49052632e-01, -4.24012303e-01, -5.32990396e-01, -3.18386006e+00, 3.27092171e-01, -1.33315325e+00, 3.62459183e-01, 3.74710828e-01, -1.30302286e+00, 1.79680198e-01, -4.51832324e-01, 4.34282750e-01, -7.09520102e-01, 6.20333970e-01, -1.28712380e+00, 2.04130828e-01, -7.70607769e-01, 1.61889160e+00, -1.50951004e+00, -4.10505563e-01, -3.56566496e-02, -1.29747534e+00, -1.49967879e-01, 7.77626812e-01, -8.28408226e-02, 2.73412596e-02, 5.79780899e-03, 9.87900198e-02, -7.95276761e-01, -1.38536084e+00, -6.63573861e-01, 3.89783204e-01, -1.30670881e+00, -7.62425125e-01, -4.04883057e-01, 6.24344349e-01, 3.68128955e-01, -1.01577950e+00, -3.06715906e-01, 5.67961395e-01, 2.98198581e-01, -1.63613629e+00, -3.75131965e-01, -6.75393403e-01, 2.59172034e+00, 6.75538957e-01, 9.07939598e-02, 1.92257717e-01, -1.21592450e+00, -2.73682117e-01, 1.25232983e+00, -1.39969170e+00, -1.91483587e-01, 2.57732719e-01, 3.10056299e-01, 1.41833842e+00, -1.81386679e-01, 3.92868072e-01, -8.14771175e-01, 2.02392387e+00, -9.42091495e-02, -3.77683818e-01, 2.05638766e+00, 2.93796062e-01, -6.02131486e-01, 2.70461679e-01, -8.92358482e-01, 1.04388881e+00, 2.66154885e-01}; migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {5, 3, 4, 2}}; params["a"] = migraphx::argument(input_fixed_shape, a.data()); auto result = p.eval(params).back(); std::vector results_vector(120); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = { 0.30191708, 0.59879845, 0.50029165, 0.24915339, 0.36823985, 0.13190967, 0.0349741, 0.18750034, 0.21905553, 0.27000085, 0.0547399, 0.56318235, 0.47422904, 0.78964758, 0.91381913, 0.44601166, 0.47902739, 0.13120073, 0.4449684, 0.18766427, 0.15753111, 0.07844277, 0.05120674, 0.36648798, 0.14637007, 0.13152322, 0.01560997, 0.29065287, 0.49196178, 0.10550152, 0.81890774, 0.06369215, 0.62972021, 0.74931765, 0.67285055, 0.35034987, 0.28612873, 0.31931475, 0.04220394, 0.16093165, 0.22390974, 0.11915915, 0.3115395, 0.35899726, 0.22190949, 0.57518375, 0.13888834, 0.7753762, 0.4642328, 0.57055861, 0.21954368, 0.34515455, 0.09486015, 0.40631217, 0.01842281, 0.48770609, 0.06652815, 0.36023033, 0.42343026, 0.24226256, 0.17348589, 0.44066274, 0.6865865, 0.17296699, 0.46923906, 0.06921105, 0.3570261, 0.4125829, 0.73165393, 0.15302512, 0.29499072, 0.33932695, 0.30852377, 0.40762195, 0.40170741, 0.36259529, 0.60848355, 0.42618036, 0.31721094, 0.02960522, 0.28256637, 0.24389413, 0.2725659, 0.10663581, 0.27622163, 0.28264219, 0.53652936, 0.09476089, 0.40890986, 0.34848392, 0.32572666, 0.53076893, 0.11529481, 0.29117745, 0.14625968, 0.8756339, 0.49818122, 0.10656087, 0.1813329, 0.17664003, 0.21410346, 0.80408043, 0.02315119, 0.27155462, 0.32804728, 0.13268511, 0.61795473, 0.49703068, 0.41696799, 0.10175809, 0.71028161, 0.29929739, 0.17377149, 0.76075399, 0.20071237, 0.32632929, 0.36892858, 0.09416146, 0.26656723, 0.42914796}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/sqdiff.cpp000066400000000000000000000060411510465702400202540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(sqdiff_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l1 = mm->add_literal(migraphx::literal{s, {-1, 0, 1}}); auto l2 = mm->add_literal(migraphx::literal{s, {1, 2, 3}}); mm->add_instruction(migraphx::make_op("sqdiff"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {4, 4, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(sqdiff_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("sqdiff"), x, y); p.compile(migraphx::make_target("ref")); std::vector x_data{-1, 0, 1}; std::vector y_data{1, 2, 3}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {4, 4, 4}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/sqrt.cpp000066400000000000000000000062301510465702400177710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(sqrt_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {5}}; std::vector data{1.02481645, 0.85643062, 0.03404123, 0.92791926, 0.10569184}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("sqrt"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return sqrtf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(sqrt_dynamic_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); std::vector input_data{1.02481645, 0.85643062, 0.03404123, 0.92791926, 0.10569184}; mm->add_instruction(migraphx::make_op("sqrt"), input); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {5}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return sqrtf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/squeeze.cpp000066400000000000000000000146111510465702400204630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(squeeze_test_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(4 * 3 * 3); migraphx::shape s1{migraphx::shape::float_type, {4, 1, 3, 1, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 3, 1, 3}}; auto l0 = mm->add_literal(migraphx::literal{s1, data}); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); EXPECT(result.get_shape() == s2); } TEST_CASE(squeeze_test_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(4 * 3 * 3); migraphx::shape s1{migraphx::shape::float_type, {4, 1, 3, 1, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 1, 3, 3}}; auto l0 = mm->add_literal(migraphx::literal{s1, data}); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {3}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); EXPECT(result.get_shape() == s2); } TEST_CASE(squeeze_test_3) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(4 * 3 * 3); migraphx::shape s1{migraphx::shape::float_type, {4, 1, 3, 1, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 3, 3}}; auto l0 = mm->add_literal(migraphx::literal{s1, data}); mm->add_instruction(migraphx::make_op("squeeze"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); EXPECT(result.get_shape() == s2); } TEST_CASE(squeeze_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {{1, 4}, {1, 1}, {3, 3}, {1, 1}, {3, 3}}}; auto p0 = mm->add_parameter("x", s1); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), p0); p.compile(migraphx::make_target("ref")); std::vector input_data(4 * 3 * 3); migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4, 1, 3, 1, 3}}; params0["x"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); migraphx::shape s2{migraphx::shape::float_type, {4, 3, 1, 3}}; EXPECT(result.get_shape() == s2); } TEST_CASE(squeeze_transpose_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {4, 1, 3, 1, 3}})); auto l0_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 2, 3, 0, 4}}}), l0); mm->add_instruction(migraphx::make_op("squeeze"), l0_trans); auto p_uncompiled = p; // contiguous is required to read the values in standard shaped order auto* mm_uncompiled = p_uncompiled.get_main_module(); mm_uncompiled->add_instruction(migraphx::make_op("contiguous"), std::prev(mm_uncompiled->end())); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto expected_result = p_uncompiled.eval({}).back(); EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::float_type, {3, 4, 3}}); EXPECT(result == expected_result); } TEST_CASE(squeeze_multibroadcast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 3, 1, 3}})); auto l0_brcst = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {4, 1, 3, 4, 3}}}), l0); mm->add_instruction(migraphx::make_op("squeeze"), l0_brcst); auto p_uncompiled = p; auto* mm_uncompiled = p_uncompiled.get_main_module(); mm_uncompiled->add_instruction(migraphx::make_op("contiguous"), std::prev(mm_uncompiled->end())); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto expected_result = p_uncompiled.eval({}).back(); EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::float_type, {4, 3, 4, 3}}); EXPECT(result == expected_result); } TEST_CASE(squeeze_slice_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 3, 4, 3}})); auto l0_slice = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {2}}, {"ends", {3}}}), l0); mm->add_instruction(migraphx::make_op("squeeze"), l0_slice); auto p_uncompiled = p; auto* mm_uncompiled = p_uncompiled.get_main_module(); mm_uncompiled->add_instruction(migraphx::make_op("contiguous"), std::prev(mm_uncompiled->end())); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto expected_result = p_uncompiled.eval({}).back(); EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::float_type, {3, 3}}); EXPECT(result == expected_result); } ROCm-AMDMIGraphX-46524e8/test/ref/step.cpp000066400000000000000000000053431510465702400177570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(step_test_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 4 * 6); std::iota(data.begin(), data.end(), 2); migraphx::shape s1{migraphx::shape::float_type, {2, 1, 4, 6}}; auto l0 = mm->add_literal(migraphx::literal{s1, data}); auto r = mm->add_instruction( migraphx::make_op("step", {{"axes", {0, 2, 3}}, {"steps", {2, 2, 3}}}), l0); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); migraphx::shape s2{migraphx::shape::float_type, {1, 1, 2, 2}}; EXPECT(result.get_shape() == s2); } TEST_CASE(step_test_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(2 * 4 * 6); std::iota(data.begin(), data.end(), 2); migraphx::shape s1{migraphx::shape::float_type, {2, 1, 4, 6}}; auto l0 = mm->add_literal(migraphx::literal{s1, data}); auto tl = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l0); auto r = mm->add_instruction( migraphx::make_op("step", {{"axes", {0, 1, 2}}, {"steps", {2, 2, 3}}}), tl); mm->add_return({r}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); migraphx::shape s2{migraphx::shape::float_type, {1, 2, 2, 1}}; EXPECT(result.get_shape() == s2); } ROCm-AMDMIGraphX-46524e8/test/ref/sub.cpp000066400000000000000000000060331510465702400175720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(sub_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l1 = mm->add_literal(migraphx::literal{s, {-1, 0, 1}}); auto l2 = mm->add_literal(migraphx::literal{s, {1, 2, 3}}); mm->add_instruction(migraphx::make_op("sub"), l1, l2); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2, -2, -2}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(sub_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector dd{{2, 6}}; migraphx::shape s{migraphx::shape::float_type, dd}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("sub"), x, y); p.compile(migraphx::make_target("ref")); std::vector x_data{-1, 0, 1}; std::vector y_data{1, 2, 3}; migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["x"] = migraphx::argument(input_fixed_shape0, x_data.data()); params0["y"] = migraphx::argument(input_fixed_shape0, y_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {-2, -2, -2}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/tan.cpp000066400000000000000000000060641510465702400175670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(tan_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; std::vector data{-1, 0, 1}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("tan"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return tanf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(tan_dynamic_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); std::vector input_data{-1, 0, 1}; mm->add_instruction(migraphx::make_op("tan"), input); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return tanf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/tanh.cpp000066400000000000000000000061251510465702400177350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(tanh_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2}}; std::vector data{-1.0, 2.0, -3.0, 4.0}; auto l = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(migraphx::make_op("tanh"), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return tanhf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(tanh_dynamic_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::dynamic_dimension dd{3, 8}; migraphx::shape s{migraphx::shape::float_type, {dd}}; auto input = mm->add_parameter("X", s); std::vector input_data{-1.0, 2.0, -3.0, 4.0}; mm->add_instruction(migraphx::make_op("tanh"), input); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4}}; params0["X"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = input_data; std::transform( gold.begin(), gold.end(), gold.begin(), [](float n) -> float { return tanhf(n); }); EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/topk.cpp000066400000000000000000000130701510465702400177550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include static auto run_program(const migraphx::value& op, bool custom_idx = false, bool fill1 = false) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); std::vector topk_inputs = {data}; if(custom_idx) { migraphx::shape is{migraphx::shape::uint16_type, {3, 5}}; std::vector indices(is.elements()); std::iota(indices.rbegin(), indices.rend(), 1); topk_inputs.push_back(mm->add_literal(migraphx::literal{is, indices})); } auto r = mm->add_instruction(migraphx::make_op("topk", op), topk_inputs); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); p.compile(migraphx::make_target("ref")); std::vector input_data = { 2.1, 2.3, 2.0, 2.5, 1.9, 3.3, 0.2, 4.5, 0.1, 0.8, 1.0, 4.5, 2.1, 0.8, 1.5, }; if(fill1) input_data.assign(input_data.size(), 1); migraphx::parameter_map pp; pp["data"] = migraphx::argument(s, input_data.data()); auto rets = p.eval(pp); std::vector ret_val; rets.front().visit([&](auto v) { ret_val.assign(v.begin(), v.end()); }); std::vector ret_ind; rets.back().visit([&](auto v) { ret_ind.assign(v.begin(), v.end()); }); return std::make_pair(ret_val, ret_ind); } TEST_CASE(topk_largest0) { auto results = run_program({{"axis", 0}, {"k", 2}, {"largest", 1}}); std::vector gold_val = {3.3, 4.5, 4.5, 2.5, 1.9, 2.1, 2.3, 2.1, 0.8, 1.5}; EXPECT(results.first == gold_val); std::vector gold_ind = {1, 2, 1, 0, 0, 0, 0, 2, 2, 2}; EXPECT(results.second == gold_ind); } TEST_CASE(topk_largest1) { auto results = run_program({{"axis", 1}, {"k", 4}, {"largest", 1}}); std::vector gold_val = {2.5, 2.3, 2.1, 2, 4.5, 3.3, 0.8, 0.2, 4.5, 2.1, 1.5, 1}; EXPECT(results.first == gold_val); std::vector gold_ind = {3, 1, 0, 2, 2, 0, 4, 1, 1, 2, 4, 0}; EXPECT(results.second == gold_ind); } TEST_CASE(topk_largest_same) { auto results = run_program({{"axis", 1}, {"k", 4}, {"largest", 1}}, false, true); EXPECT(std::all_of(results.first.begin(), results.first.end(), [](auto i) { return migraphx::float_equal(i, 1); })); std::vector gold_ind = {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}; EXPECT(results.second == gold_ind); } TEST_CASE(topk_smallest1) { auto results = run_program({{"axis", 1}, {"k", 4}, {"largest", 0}}); std::vector gold_val = {1.9, 2, 2.1, 2.3, 0.1, 0.2, 0.8, 3.3, 0.8, 1, 1.5, 2.1}; EXPECT(results.first == gold_val); std::vector gold_ind = {4, 2, 0, 1, 3, 1, 4, 0, 3, 0, 4, 2}; EXPECT(results.second == gold_ind); } TEST_CASE(topk_smallest_same) { auto results = run_program({{"axis", 1}, {"k", 4}, {"largest", 0}}, false, true); EXPECT(std::all_of(results.first.begin(), results.first.end(), [](auto i) { return migraphx::float_equal(i, 1); })); std::vector gold_ind = {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}; EXPECT(results.second == gold_ind); } TEST_CASE(topk_largest_custom_indices) { auto results = run_program({{"axis", 1}, {"k", 4}, {"largest", 1}}, true); std::vector gold_val = {2.5, 2.3, 2.1, 2, 4.5, 3.3, 0.8, 0.2, 4.5, 2.1, 1.5, 1}; EXPECT(results.first == gold_val); std::vector gold_ind = {12, 14, 15, 13, 8, 10, 6, 9, 4, 3, 1, 5}; EXPECT(results.second == gold_ind); } TEST_CASE(topk_smallest_custom_indices) { auto results = run_program({{"axis", 1}, {"k", 4}, {"largest", 0}}, true); std::vector gold_val = {1.9, 2, 2.1, 2.3, 0.1, 0.2, 0.8, 3.3, 0.8, 1, 1.5, 2.1}; EXPECT(results.first == gold_val); std::vector gold_ind = {11, 13, 15, 14, 7, 9, 6, 10, 2, 5, 1, 3}; EXPECT(results.second == gold_ind); } ROCm-AMDMIGraphX-46524e8/test/ref/transpose.cpp000066400000000000000000000075121510465702400210220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(transpose_test) { migraphx::shape a_shape{migraphx::shape::float_type, {1, 2, 2, 3}}; std::vector data(12); std::iota(data.begin(), data.end(), 0); { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, data}); std::vector perm = {0, 3, 1, 2}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), l); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); } { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(migraphx::literal{a_shape, data}); std::vector perm = {0, 3, 1, 2}; auto result = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), l); mm->add_instruction(migraphx::make_op("contiguous"), result); p.compile(migraphx::make_target("ref")); auto result2 = p.eval({}).back(); std::vector results_vector(12); result2.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } } TEST_CASE(transpose_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {2, 2}, {2, 2}, {3, 3}}}; auto l = mm->add_parameter("X", s); std::vector perm = {0, 3, 1, 2}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), l); p.compile(migraphx::make_target("ref")); std::vector data(12); std::iota(data.begin(), data.end(), 0); migraphx::parameter_map params; migraphx::shape input_fixed_shape{migraphx::shape::float_type, {1, 2, 2, 3}}; params["X"] = migraphx::argument(input_fixed_shape, data.data()); auto result = p.eval(params).back(); std::vector new_lens = {1, 3, 2, 2}; EXPECT(result.get_shape().lens() == new_lens); std::vector results_vector(12); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/unique.cpp000066400000000000000000000266671510465702400203260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include namespace { migraphx::program create_program(const migraphx::shape& data_shape, int64_t sorted, std::optional axis) { migraphx::program p; auto* mm = p.get_main_module(); auto data = mm->add_parameter("X", data_shape); auto op = axis ? migraphx::make_op("unique", {{"axis", *axis}, {"sorted", sorted}}) : migraphx::make_op("unique", {{"sorted", sorted}}); auto r = mm->add_instruction(op, data); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), r); auto r3 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 3}}), r); mm->add_return({r0, r1, r2, r3}); return p; }; template auto run_program(T& data, const migraphx::shape& data_shape, int sorted, std::optional axis = std::nullopt) { auto p = create_program(data_shape, sorted, axis); p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; pp["X"] = migraphx::argument(data_shape, data.data()); auto rets = p.eval(pp); std::vector::value_type> y; rets[0].visit([&](auto v) { y.assign(v.begin(), v.end()); }); std::vector y_idx; rets[1].visit([&](auto v) { y_idx.assign(v.begin(), v.end()); }); std::vector x_rev_idx; rets[2].visit([&](auto v) { x_rev_idx.assign(v.begin(), v.end()); }); std::vector y_ct; rets[3].visit([&](auto v) { y_ct.assign(v.begin(), v.end()); }); return std::make_tuple(y, y_idx, x_rev_idx, y_ct); } } // namespace // sorted. single entry TEST_CASE(unique_sorted_single_entry_test) { std::vector data = {2}; int64_t axis = 0; int64_t sorted = 1; std::vector lens = {1}; migraphx::shape data_shape{migraphx::shape::int32_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {2}; EXPECT(y == gold_val); std::vector gold_y_idx = {0}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {0}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {1}; EXPECT(ct == gold_ct); } // unsorted. single entry TEST_CASE(unique_unsorted_single_entry_test) { std::vector data = {3.33}; int64_t axis = -1; int64_t sorted = 0; std::vector lens = {1}; migraphx::shape data_shape{migraphx::shape::float_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {3.33}; EXPECT(y == gold_val); std::vector gold_y_idx = {0}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {0}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {1}; EXPECT(ct == gold_ct); } // case 2 sorted. all unique input.. TEST_CASE(unique_sorted_all_unique_test) { std::vector data = {2.1, 2.3, 2.4, 2.5, 1.9}; int64_t axis = 0; int64_t sorted = 1; std::vector lens = {5}; migraphx::shape data_shape{migraphx::shape::float_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {1.9, 2.1, 2.3, 2.4, 2.5}; EXPECT(y == gold_val); std::vector gold_y_idx = {4, 0, 1, 2, 3}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {1, 2, 3, 4, 0}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {1, 1, 1, 1, 1}; EXPECT(ct == gold_ct); } // case 3 unsorted. all unique input TEST_CASE(unique_unsorted_all_unique_test) { std::vector data = {2.1, 2.3, 2.4, 2.5, 1.9}; int64_t axis = 0; int64_t sorted = 0; std::vector lens = {5}; migraphx::shape data_shape{migraphx::shape::float_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {2.1, 2.3, 2.4, 2.5, 1.9}; EXPECT(y == gold_val); std::vector gold_y_idx = {0, 1, 2, 3, 4}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {0, 1, 2, 3, 4}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {1, 1, 1, 1, 1}; EXPECT(ct == gold_ct); } // case 4 sorted (with dup entries) TEST_CASE(unique_sorted_dupes_test) { std::vector data = {2.1, 2.3, 2.4, 2.5, 1.9, 2.5, 2.3, 2.5}; int64_t axis = 0; int64_t sorted = 1; std::vector lens = {8}; migraphx::shape data_shape{migraphx::shape::double_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {1.9, 2.1, 2.3, 2.4, 2.5}; EXPECT(y == gold_val); std::vector gold_ct = {1, 1, 2, 1, 3}; EXPECT(ct == gold_ct); } // case 5 unsorted (with dup entries) TEST_CASE(unique_unsorted_dupes_test) { std::vector data = {2.1, 2.3, 2.4, 2.5, 1.9, 2.5, 2.3, 2.1}; int64_t axis = -1; int64_t sorted = 0; std::vector lens = {8}; migraphx::shape data_shape{migraphx::shape::float_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {2.1, 2.3, 2.4, 2.5, 1.9}; EXPECT(y == gold_val); std::vector gold_y_idx = {0, 1, 2, 3, 4}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {0, 1, 2, 3, 4, 3, 1, 0}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {2, 2, 1, 2, 1}; EXPECT(ct == gold_ct); } TEST_CASE(unique_3_d_no_axis_test) { // sorted 3D (with dup entries). no axis int sorted = 1; std::vector data_3d = {2.1, 2.3, 2.4, 2.5, 1.9, 2.5, 2.3, 2.5}; std::vector lens = {2, 2, 2}; // 3D data. type double migraphx::shape data_shape{migraphx::shape::double_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data_3d, data_shape, sorted); std::vector gold_val = {1.9, 2.1, 2.3, 2.4, 2.5}; EXPECT(y == gold_val); std::vector gold_ct = {1, 1, 2, 1, 3}; EXPECT(ct == gold_ct); } TEST_CASE(unique_3_d_no_axis_unsorted_test) // unsorted 3D (with dup entries). no axis { int sorted = 0; std::vector data = {2.1, 2.3, 2.4, 2.5, 1.9, 2.5, 2.3, 2.1}; std::vector lens = {2, 1, 4}; // 3D data. type float migraphx::shape data_shape{migraphx::shape::float_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted); std::vector gold_val = {2.1, 2.3, 2.4, 2.5, 1.9}; EXPECT(y == gold_val); std::vector gold_y_idx = {0, 1, 2, 3, 4}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {0, 1, 2, 3, 4, 3, 1, 0}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {2, 2, 1, 2, 1}; EXPECT(ct == gold_ct); } // unique integer sub-tensors: sorted (with dup entries) TEST_CASE(unique_subtensors_sorted_test) { /* input_X = [[1, 0, 0], [1, 0, 0], [2, 3, 4]] attribute_sorted = 1 attribute_axis = 0 output_Y = [[1, 0, 0], [2, 3, 4]] output_indices = [0, 2] output_inverse_indices = [0, 0, 1] output_counts = [2, 1] */ int axis = 0; int sorted = 1; std::vector data = {1, 0, 0, 1, 0, 0, 2, 3, 4}; std::vector lens = {3, 3}; migraphx::shape data_shape{migraphx::shape::int32_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {1, 0, 0, 2, 3, 4}; EXPECT(y == gold_val); std::vector gold_y_idx = {0, 2}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {0, 0, 1}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {2, 1}; EXPECT(ct == gold_ct); } // unique integer sub-tensors: un-sorted (with dup entries) TEST_CASE(unique_subtensors_neg_axis_test) { /* input_X = [[1, 0, 0], [1, 0, 0], [2, 3, 4]] attribute_sorted = 0 attribute_axis = 0 output_Y = [[1, 0, 0], [2, 3, 4]] output_indices = [0, 2] output_inverse_indices = [0, 0, 1] output_counts = [2, 1] */ int axis = -2; // == 0 int sorted = 0; std::vector data = {1, 0, 0, 1, 0, 0, 2, 3, 4}; std::vector lens = {3, 3}; migraphx::shape data_shape{migraphx::shape::int32_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {1, 0, 0, 2, 3, 4}; EXPECT(y == gold_val); std::vector gold_y_idx = {0, 2}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {0, 0, 1}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {2, 1}; EXPECT(ct == gold_ct); } // unique float sub-tensors: sorted (with dup entries) axis = 0 TEST_CASE(unique_subtensors_zero_axis_test) { /* input_x = [[[1., 1.], [0., 1.], [2., 1.], [0., 1.]], [[1., 1.], [0., 1.], [2., 1.], [0., 1.]]] attribute_sorted = 1 attribute_axis = 0 */ int axis = 0; int sorted = 1; std::vector data = {1., 1., 0., 1., 2., 1., 0., 1., 1., 1., 0., 1., 2., 1., 0., 1.}; std::vector lens = {2, 4, 2}; migraphx::shape data_shape{migraphx::shape::float_type, lens}; const auto& [y, idx, x_rev, ct] = run_program(data, data_shape, sorted, axis); std::vector gold_val = {1., 1., 0., 1., 2., 1., 0., 1.}; EXPECT(y == gold_val); std::vector gold_y_idx = {0}; EXPECT(idx == gold_y_idx); std::vector gold_x_rev = {0, 0}; EXPECT(x_rev == gold_x_rev); std::vector gold_ct = {2}; EXPECT(ct == gold_ct); } ROCm-AMDMIGraphX-46524e8/test/ref/unpack_int4.cpp000066400000000000000000000152471510465702400212270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(unpack_int4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {2, 1}}; auto l0 = mm->add_literal(migraphx::literal{s, {0xBA, 0xDC}}); mm->add_instruction(migraphx::make_op("unpack_int4"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x0A, 0x0B, 0x0C, 0x0D}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(unpack_int4_transposed) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {2, 2}, {1, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x1A, 0x2B, 0x3C, 0x4D}}); mm->add_instruction(migraphx::make_op("unpack_int4"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(8); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x0A, 0x01, 0x0B, 0x02, 0x0C, 0x03, 0x0D, 0x04}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(unpack_int4_broadcasted) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {4}, {1}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x1A, 0x2B, 0x3C, 0x4D}}); auto l0b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 4}}}), l0); mm->add_instruction(migraphx::make_op("unpack_int4"), l0b); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(32); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x0A, 0x01, 0x0B, 0x02, 0x0C, 0x03, 0x0D, 0x04, 0x0A, 0x01, 0x0B, 0x02, 0x0C, 0x03, 0x0D, 0x04, 0x0A, 0x01, 0x0B, 0x02, 0x0C, 0x03, 0x0D, 0x04, 0x0A, 0x01, 0x0B, 0x02, 0x0C, 0x03, 0x0D, 0x04}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(unpack_int4_axis_0) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {1, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0xCA, 0xDB}}); mm->add_instruction(migraphx::make_op("unpack_int4", {{"axis", 0}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(4); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x0A, 0x0B, 0x0C, 0x0D}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(unpack_int4_nchw) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::uint8_type, {1, 2, 4, 2}}; auto l0 = mm->add_literal(migraphx::literal{s, {0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE, 0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE}}); mm->add_instruction(migraphx::make_op("unpack_int4", {{"axis", -1}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(32); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(unpack_int4_signed) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int8_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{ s, {0b1000'0111 /*-8'7*/, 0b0001'1111 /*1'-1*/, 0b1110'1001 /*-2'-7*/, 0b0101'0111 /*5'7*/}}); mm->add_instruction(migraphx::make_op("unpack_int4"), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector(s.elements()); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{7, -8, -1, 1, -7, -2, 7, 5}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref/unsqueeze.cpp000066400000000000000000000136521510465702400210320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include TEST_CASE(unsqueeze_test_1) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(4 * 3 * 3); migraphx::shape s1{migraphx::shape::float_type, {4, 3, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 1, 3, 3}}; auto l0 = mm->add_literal(migraphx::literal{s1, data}); mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); EXPECT(result.get_shape() == s2); } TEST_CASE(unsqueeze_test_2) { migraphx::program p; auto* mm = p.get_main_module(); std::vector data(4 * 3 * 3); migraphx::shape s1{migraphx::shape::float_type, {4, 3, 3}}; migraphx::shape s2{migraphx::shape::float_type, {4, 3, 1, 3}}; auto l0 = mm->add_literal(migraphx::literal{s1, data}); mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), l0); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); EXPECT(result.get_shape() == s2); } TEST_CASE(unsqueeze_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {{1, 4}, {3, 3}, {3, 3}}}; auto p0 = mm->add_parameter("x", s1); mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), p0); p.compile(migraphx::make_target("ref")); std::vector input_data(4 * 3 * 3); migraphx::parameter_map params0; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {4, 3, 3}}; params0["x"] = migraphx::argument(input_fixed_shape0, input_data.data()); auto result = p.eval(params0).back(); migraphx::shape s2{migraphx::shape::float_type, {4, 1, 3, 3}}; EXPECT(result.get_shape() == s2); } TEST_CASE(unsqueeze_transpose_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {4, 3, 3}}; auto l0 = mm->add_literal(migraphx::generate_literal(s1)); auto l0_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 0, 1}}}), l0); mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), l0_trans); auto p_uncompiled = p; auto* mm_uncompiled = p_uncompiled.get_main_module(); mm_uncompiled->add_instruction(migraphx::make_op("contiguous"), std::prev(mm_uncompiled->end())); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto expected_result = p_uncompiled.eval({}).back(); EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::float_type, {3, 4, 1, 3}}); EXPECT(result == expected_result); } TEST_CASE(unsqueeze_multibroadcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {4, 1, 3}}; auto l0 = mm->add_literal(migraphx::generate_literal(s1)); auto l0_brcst = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 4, 3, 3}}}), l0); mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}}), l0_brcst); auto p_uncompiled = p; auto* mm_uncompiled = p_uncompiled.get_main_module(); mm_uncompiled->add_instruction(migraphx::make_op("contiguous"), std::prev(mm_uncompiled->end())); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto expected_result = p_uncompiled.eval({}).back(); EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::float_type, {4, 4, 1, 3, 3}}); EXPECT(result == expected_result); } TEST_CASE(unsqueeze_slice_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {2, 3, 4, 4}}; auto l0 = mm->add_literal(migraphx::generate_literal(s1)); auto l0_slice = mm->add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {2}}, {"ends", {3}}}), l0); mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l0_slice); auto p_uncompiled = p; auto* mm_uncompiled = p_uncompiled.get_main_module(); mm_uncompiled->add_instruction(migraphx::make_op("contiguous"), std::prev(mm_uncompiled->end())); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); auto expected_result = p_uncompiled.eval({}).back(); EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::float_type, {2, 1, 3, 4, 1}}); EXPECT(result == expected_result); } ROCm-AMDMIGraphX-46524e8/test/ref/where.cpp000066400000000000000000000114671510465702400201220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include TEST_CASE(where_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::bool_type, {3, 3}}; migraphx::shape sx{migraphx::shape::float_type, {3, 3}}; std::vector b{true, true, true, false, false, false, true, false, true}; std::vector x(9, 1.0); std::vector y(9, 2.0); auto lb = mm->add_literal(migraphx::literal{sb, b}); auto lx = mm->add_literal(migraphx::literal{sx, x}); auto ly = mm->add_literal(migraphx::literal{sx, y}); auto w = mm->add_instruction(migraphx::make_op("where"), lb, lx, ly); mm->add_return({w}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); std::vector gold(9); for(int i = 0; i < gold.size(); ++i) gold[i] = b[i] ? x[i] : y[i]; EXPECT(migraphx::verify::verify_rms_range(result_vec, gold)); } TEST_CASE(where_dyn_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::bool_type, {{2, 3}, {2, 3}}}; migraphx::shape sx{migraphx::shape::float_type, {{2, 3}, {2, 3}}}; auto lb = mm->add_parameter("predicate", sb); auto lx = mm->add_parameter("X", sx); auto ly = mm->add_parameter("Y", sx); mm->add_instruction(migraphx::make_op("where"), lb, lx, ly); p.compile(migraphx::make_target("ref")); std::vector b{1, 1, 1, 0, 0, 0, 1, 0, 1}; std::vector x(9, 1.0); std::vector y(9, 2.0); migraphx::parameter_map params; migraphx::shape input_fixed_shape0{migraphx::shape::float_type, {3, 3}}; migraphx::shape input_fixed_shape1{migraphx::shape::uint8_type, {3, 3}}; params["X"] = migraphx::argument(input_fixed_shape0, x.data()); params["Y"] = migraphx::argument(input_fixed_shape0, y.data()); params["predicate"] = migraphx::argument(input_fixed_shape1, b.data()); auto result = p.eval(params).back(); std::vector results_vector(3 * 3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 1, 1, 2, 2, 2, 1, 2, 1}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(where_broadcasted_inputs_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::bool_type, {3, 3}}; std::vector b{true, true, true, false, false, false, true, false, true}; auto lb = mm->add_literal(migraphx::literal{sb, b}); auto lx = mm->add_literal(migraphx::literal(1.0f)); auto ly = mm->add_literal(migraphx::literal(2.0f)); auto mbx = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 3}}}), lx); auto mby = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 3}}}), ly); auto w = mm->add_instruction(migraphx::make_op("where"), lb, mbx, mby); mm->add_return({w}); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector result_vec; result.visit([&](auto output) { result_vec.assign(output.begin(), output.end()); }); std::vector gold(9); std::vector x(9, 1.0); std::vector y(9, 2.0); for(int i = 0; i < gold.size(); ++i) gold[i] = b[i] ? x[i] : y[i]; EXPECT(migraphx::verify::verify_rms_range(result_vec, gold)); } ROCm-AMDMIGraphX-46524e8/test/ref_dev_examples.cpp000066400000000000000000000173011510465702400215350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include "test.hpp" /*! * Example MIGraphX programs for following the Contributor's Guide. */ TEST_CASE(add_two_literals) { /*! * Simple MIGraphX program to add two literal values. * Equivalent to adding two constant scalar values together. */ // create the program a get a pointer to the main module migraphx::program p; auto* mm = p.get_main_module(); // add two literals to the program auto one = mm->add_literal(1); auto two = mm->add_literal(2); // make the "add" operation between the two literals and add it to the program mm->add_instruction(migraphx::make_op("add"), one, two); // compile the program on the reference device p.compile(migraphx::make_target("ref")); // evaulate the program and retreive the result auto result = p.eval({}).back(); std::cout << "add_two_literals: 1 + 2 = " << result << "\n"; EXPECT(result.at() == 3); } TEST_CASE(add_parameters) { /*! * Modified version of MIGraphX program seen in add_two_literals to accept a parameter. * Equivalent to adding a constant scalar value with another scalar input. */ migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {1}}; // add a "x" parameter with the shape s auto x = mm->add_parameter("x", s); auto two = mm->add_literal(2); // add the "add" instruction between the "x" parameter and "two" to the module mm->add_instruction(migraphx::make_op("add"), x, two); p.compile(migraphx::make_target("ref")); // create a parameter_map object for passing a value to the "x" parameter std::vector data = {4}; migraphx::parameter_map params; params["x"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); std::cout << "add_parameters: 4 + 2 = " << result << "\n"; EXPECT(result.at() == 6); } TEST_CASE(handling_tensors) { /*! * This example does a convolution operation over an input tensor using the given weighting * tensor. This is meant to show an example of working with tensors in MIGraphX. The output * tensor is compared against a precomputed solution tensor at the end of the program. */ migraphx::program p; auto* mm = p.get_main_module(); // create shape objects for the input tensor and weights migraphx::shape input_shape{migraphx::shape::float_type, {2, 3, 4, 4}}; migraphx::shape weights_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; // create the parameters and add the "convolution" operation to the module auto input = mm->add_parameter("X", input_shape); auto weights = mm->add_parameter("W", weights_shape); mm->add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}}), input, weights); p.compile(migraphx::make_target("ref")); // Allocated buffers by the user std::vector a = { 2.71567607, -0.9960829, 0.91671127, 0.28140706, 0.63235772, 0.08077253, 0.80927712, -0.59108931, -1.05421555, -2.76622486, -0.85044265, -0.52049929, 0.67726439, -0.65290606, 0.02345525, -0.33579525, 0.38901961, 1.05473483, -1.31188095, 1.8963089, -0.07265259, 0.947339, 0.41949373, -0.70814759, 0.25892952, 1.07311416, 1.2571274, -0.62318051, -0.19951548, -0.94232577, -0.29393643, 0.42292568, -0.80230367, 1.40909171, 0.63617158, 0.13900366, 1.09253144, -0.15265895, 1.54781747, 0.72780299, 1.09189606, -0.38068101, 0.97057933, -0.58958799, 1.56188643, 0.21474874, 0.58725154, -1.27097559, -0.03024297, 1.09437096, -0.4897908, 0.34838957, -1.31042492, -1.69069934, 0.86956722, -0.40457946, 0.46691212, 1.29273605, 0.26464137, 0.22073045, -1.02178168, 0.22163901, -1.84387338, 0.75522131, -0.45775682, -0.42241111, -1.50944722, 1.07256448, -1.95876884, -0.28106022, 0.3341668, 2.13129425, -1.14728117, -1.06555498, -0.298444, -0.88322699, -0.65866792, -2.06007552, 0.01374334, 0.45612028, 0.52715492, 1.01914406, -1.72659791, 0.80650896, 0.16860051, 2.24112225, -0.78620857, 0.36566174, -0.07020134, -0.47976932, -0.68230027, -0.94711417, -0.54506505, 1.66504931, -0.71860826, 0.61132306}; std::vector c = { -0.14601797, -0.13000923, 0.06521662, 0.06178288, -0.11083675, 0.10154136, 0.09990512, 0.06030385, -0.11374587, -0.17523311, -0.14344215, 0.17802463, 0.06300922, -0.15325832, 0.07066704, 0.05166031, 0.00615084, -0.02606523, 0.08083995, -0.17913306, 0.0624622, 0.0735731, -0.04198661, -0.0164391, -0.06374192, 0.16569914, 0.10681538, 0.07370754, 0.02802075, 0.00282027, 0.15104802, -0.11084409, -0.00197773, 0.07924436, 0.03528272, 0.04765259, -0.15896152, 0.07917164, 0.12125669, -0.1154705, -0.11999125, 0.12749968, -0.06269585, 0.18658121, -0.03944227, 0.0111798, -0.17731084, 0.11789055, -0.09982193, 0.08142821, 0.0729029, 0.11303909, 0.12735154, 0.03885292}; // Create the arguments in a parameter_map migraphx::parameter_map params; params["X"] = migraphx::argument(input_shape, a.data()); params["W"] = migraphx::argument(weights_shape, c.data()); // Evaluate and confirm the result auto result = p.eval(params).back(); std::vector results_vector(64); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); // Solution vector std::vector gold = {-0.20817225, 0.87965256, 0.14958936, -1.24887264, -0.06540672, 0.20778663, 0.40456355, -0.99900877, 0.4917807, 0.1994698, 0.64205718, 0.37798831, -0.25315839, 0.44276932, -0.16138598, 0.79344082}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/register_target.cpp000066400000000000000000000031121510465702400214120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include namespace { struct auto_load_targets { auto_load_targets() { migraphx::make_target("ref"); #ifdef HAVE_CPU migraphx::make_target("cpu"); #endif #ifdef HAVE_GPU migraphx::make_target("gpu"); #endif #ifdef HAVE_FPGA migraphx::make_target("fpga"); #endif } }; [[maybe_unused]] static auto load_targets{auto_load_targets{}}; } // namespace ROCm-AMDMIGraphX-46524e8/test/replace_allocate.cpp000066400000000000000000000236661510465702400215170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include struct test_copy : migraphx::auto_register_op { std::string name() const { return "test_copy"; } migraphx::shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this}.has(2); return inputs.back(); } migraphx::argument compute(migraphx::context&, const migraphx::shape&, const std::vector& inputs) const { inputs.at(0).visit([&](auto x) { inputs.at(1).visit([&](auto y) { std::copy(x.begin(), x.end(), y.begin()); }); }); return inputs.back(); } std::ptrdiff_t output_alias(const std::vector&) const { return 1; } }; struct allocate_no_out : migraphx::auto_register_op { migraphx::shape s{}; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.s, "shape")); } std::string name() const { return "allocate_no_out"; } migraphx::shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this}.has(0); return s; } migraphx::argument compute(migraphx::context&, const migraphx::shape& output_shape, const std::vector&) const { return migraphx::argument{output_shape}; } }; struct allocate_with_out : migraphx::auto_register_op { migraphx::shape s{}; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.s, "shape")); } std::string name() const { return "allocate_with_out"; } migraphx::shape compute_shape(const std::vector& inputs) const { migraphx::check_shapes{inputs, *this}.has(0); return s; } migraphx::argument compute(migraphx::context&, const migraphx::shape& output_shape, const std::vector&) const { return migraphx::argument{output_shape}; } }; // allocation model that has no out params struct allocation_no_out_model { std::string name() const { return "allocate_no_out"; } migraphx::operation allocate(const migraphx::shape& s) const { return migraphx::make_op(name(), {{"shape", to_value(s)}}); } migraphx::operation preallocate(const migraphx::shape&, const std::string&) const { return {}; } std::string copy() const { return "test_copy"; } bool needs_out_params() const { return false; } }; // allocation model with out params struct allocation_with_out_model { std::string name() const { return "allocate_with_out"; } migraphx::operation allocate(const migraphx::shape& s) const { return migraphx::make_op(name(), {{"shape", to_value(s)}}); } migraphx::operation preallocate(const migraphx::shape&, const std::string&) const { return {}; } std::string copy() const { return "test_copy"; } bool needs_out_params() const { return true; } }; static void run_pass(migraphx::module& m, migraphx::allocation_model model, bool offload_copy = false) { migraphx::run_passes(m, {migraphx::replace_allocate{std::move(model), offload_copy}, migraphx::dead_code_elimination{}}); } static void run_pass(migraphx::program& p, migraphx::allocation_model model, bool offload_copy = false) { migraphx::run_passes(p, {migraphx::replace_allocate{std::move(model), offload_copy}, migraphx::dead_code_elimination{}}); } static migraphx::module create_simple_program() { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {5}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto alloc = m.add_instruction(migraphx::make_op("allocate", {{"shape", migraphx::to_value(s)}})); m.add_instruction(pass_op{}, alloc, x, y); return m; } TEST_CASE(allocate_no_out) { migraphx::module m = create_simple_program(); run_pass(m, allocation_no_out_model{}); EXPECT(std::any_of(m.begin(), m.end(), [](const migraphx::instruction& ins) { return migraphx::contains(ins.name(), "allocate_no_out"); })); } TEST_CASE(allocate_with_out_param) { migraphx::module m = create_simple_program(); run_pass(m, allocation_with_out_model{}); EXPECT(std::none_of(m.begin(), m.end(), [](const migraphx::instruction& ins) { return migraphx::contains(ins.name(), "allocate"); })); } TEST_CASE(allocate_with_out_return) { migraphx::module m = create_simple_program(); m.add_return({std::prev(m.end())}); run_pass(m, allocation_with_out_model{}); EXPECT(std::none_of(m.begin(), m.end(), [](const migraphx::instruction& ins) { return migraphx::contains(ins.name(), "allocate"); })); } TEST_CASE(allocate_with_out_no_params) { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {5}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto z = m.add_parameter("z", s); auto alloc = m.add_instruction(migraphx::make_op("allocate", {{"shape", migraphx::to_value(s)}})); auto pass1 = m.add_instruction(pass_op{}, alloc, x, y); auto alloc2 = m.add_instruction(migraphx::make_op("allocate", {{"shape", migraphx::to_value(s)}})); m.add_instruction(pass_op{}, alloc2, z, pass1); run_pass(m, allocation_with_out_model{}); EXPECT(std::any_of(m.begin(), m.end(), [](const migraphx::instruction& ins) { return migraphx::contains(ins.name(), "allocate_with_out"); })); } TEST_CASE(if_allocate) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", cond_s); migraphx::shape s{migraphx::shape::float_type, {5}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto* then_mod = p.create_module("If_0_if"); auto alloc = then_mod->add_instruction( migraphx::make_op("allocate", {{"shape", migraphx::to_value(s)}})); auto a1 = then_mod->add_instruction(pass_op{}, alloc, x); then_mod->add_return({a1}); auto* else_mod = p.create_module("If_0_else"); auto alloc1 = else_mod->add_instruction( migraphx::make_op("allocate", {{"shape", migraphx::to_value(s)}})); auto a2 = else_mod->add_instruction(pass_op{}, alloc1, y); else_mod->add_return({a2}); mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); run_pass(p, allocation_with_out_model{}); EXPECT(std::any_of(mm->begin(), mm->end(), [](const migraphx::instruction& ins) { return migraphx::contains(ins.name(), "allocate_with_out"); })); } TEST_CASE(allocate_copy_with_out) { migraphx::shape s{migraphx::shape::float_type, {5}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto pass = m1.add_instruction(tuple_op{}, x, y); auto elem1 = m1.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), pass); m1.add_return({elem1}); } run_pass(m1, allocation_with_out_model{}); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto output = m2.add_parameter("output", s); auto pass = m2.add_instruction(tuple_op{}, x, y); auto elem1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), pass); auto copy = m2.add_instruction(migraphx::make_op("test_copy"), elem1, output); m2.add_return({copy}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(allocate_copy_with_no_out) { migraphx::shape s{migraphx::shape::float_type, {5}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto pass = m1.add_instruction(tuple_op{}, x, y); auto elem1 = m1.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), pass); m1.add_return({elem1}); } migraphx::module m2 = m1; run_pass(m1, allocation_no_out_model{}); EXPECT(m1.sort() == m2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/rewrite_dot.cpp000066400000000000000000000116221510465702400205540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::rewrite_dot{}, migraphx::dead_code_elimination{}}); } TEST_CASE(nchw_conv_1x1) { migraphx::shape s1{migraphx::shape::float_type, {64, 128, 28, 28}}; migraphx::shape s2{migraphx::shape::float_type, {512, 128, 1, 1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto w = m1.add_literal(migraphx::generate_literal(s2)); auto conv = m1.add_instruction(migraphx::make_op("convolution"), x, w); m1.add_return({conv}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto w = m2.add_literal(migraphx::generate_literal(s2)); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), w); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {64, 512, 128}}}), squeeze); auto reshape1 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {64, 128, 784}}}), x); auto dot = m2.add_instruction(migraphx::make_op("dot"), broadcast, reshape1); auto reshape2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {64, 512, 28, 28}}}), dot); m2.add_return({reshape2}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(nhwc_conv_1x1) { auto s1 = migraphx::shape::from_permutation( migraphx::shape::float_type, {64, 128, 28, 28}, {0, 2, 3, 1}); auto s2 = migraphx::shape::from_permutation( migraphx::shape::float_type, {512, 128, 1, 1}, {0, 2, 3, 1}); migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto w = m1.add_literal(migraphx::generate_literal(s2)); auto conv = m1.add_instruction(migraphx::make_op("convolution"), x, w); m1.add_return({conv}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto w = m2.add_literal(migraphx::generate_literal(s2)); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), w); auto transpose1 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), squeeze); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {64, 28, 128, 512}}}), transpose1); auto transpose2 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), x); auto dot = m2.add_instruction(migraphx::make_op("dot"), transpose2, broadcast); auto transpose3 = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), dot); m2.add_return({transpose3}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(nhwc_group_conv_1x1) { auto s1 = migraphx::shape::from_permutation( migraphx::shape::float_type, {64, 192, 83, 83}, {0, 2, 3, 1}); auto s2 = migraphx::shape::from_permutation( migraphx::shape::float_type, {84, 96, 1, 1}, {0, 2, 3, 1}); migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto w = m1.add_literal(migraphx::generate_literal(s2)); auto conv = m1.add_instruction(migraphx::make_op("convolution", {{"group", 2}}), x, w); m1.add_return({conv}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/rewrite_gelu_test.cpp000066400000000000000000000261471510465702400217710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include TEST_CASE(bias_gelu) { migraphx::shape s1{migraphx::shape::half_type, {2, 4, 8}}; migraphx::shape s2{migraphx::shape::half_type}; migraphx::module m1; { auto a = m1.add_parameter("a", s1); auto b = m1.add_parameter("b", s1); auto add1 = m1.add_instruction(migraphx::make_op("add"), a, b); auto l1 = m1.add_literal(migraphx::literal{s2, {1.4140625f}}); auto div = add_common_op(m1, migraphx::make_op("div"), {add1, l1}); auto erf = m1.add_instruction(migraphx::make_op("erf"), div); auto l2 = m1.add_literal(migraphx::literal{s2, {1.0f}}); auto add2 = add_common_op(m1, migraphx::make_op("add"), {erf, l2}); auto mul = m1.add_instruction(migraphx::make_op("mul"), add1, add2); auto l3 = m1.add_literal(migraphx::literal{s2, {0.5f}}); mul = add_common_op(m1, migraphx::make_op("mul"), {mul, l3}); m1.add_return({mul}); } migraphx::rewrite_gelu pass; pass.apply(m1); migraphx::dead_code_elimination dce; dce.apply(m1); migraphx::module m2; { using migraphx::literal; using migraphx::make_op; using migraphx::shape; auto x_param = m2.add_parameter("a", s1); auto bias_param = m2.add_parameter("b", s1); auto bias_add = m2.add_instruction(migraphx::make_op("add"), x_param, bias_param); double const0 = -2. * sqrt(M_2_PI); double const1 = 0.044715 * const0; auto lit0 = m2.add_literal(literal{shape{s2.type()}, {const0}}); auto lit1 = m2.add_literal(literal{shape{s2.type()}, {const1}}); auto one = m2.add_literal(literal{shape{s2.type()}, {1.0}}); auto xb = add_common_op(m2, make_op("mul"), {bias_add, lit1}); auto a = m2.add_instruction(make_op("mul"), bias_add, xb); auto b = add_common_op(m2, make_op("add"), {a, lit0}); auto u = m2.add_instruction(make_op("mul"), bias_add, b); auto emu = m2.add_instruction(make_op("exp"), u); auto c = add_common_op(m2, make_op("add"), {one, emu}); auto y = m2.add_instruction(make_op("div"), bias_add, c); m2.add_return({y}); } EXPECT(m1 == m2); } TEST_CASE(non_bias_gelu) { migraphx::shape s1{migraphx::shape::half_type, {2, 4, 8}}; migraphx::shape s2{migraphx::shape::half_type}; migraphx::module m1; { auto a = m1.add_parameter("a", s1); auto b = m1.add_parameter("b", s1); auto sub = m1.add_instruction(migraphx::make_op("sub"), a, b); auto l1 = m1.add_literal(migraphx::literal{s2, {1.4140625f}}); auto div = add_common_op(m1, migraphx::make_op("div"), {sub, l1}); auto erf = m1.add_instruction(migraphx::make_op("erf"), div); auto l2 = m1.add_literal(migraphx::literal{s2, {1.0f}}); auto add2 = add_common_op(m1, migraphx::make_op("add"), {erf, l2}); auto mul = m1.add_instruction(migraphx::make_op("mul"), sub, add2); auto l3 = m1.add_literal(migraphx::literal{s2, {0.5f}}); mul = add_common_op(m1, migraphx::make_op("mul"), {mul, l3}); m1.add_return({mul}); } migraphx::rewrite_gelu pass; pass.apply(m1); migraphx::dead_code_elimination dce; dce.apply(m1); migraphx::module m2; { using migraphx::literal; using migraphx::make_op; using migraphx::shape; auto x_param = m2.add_parameter("a", s1); auto bias_param = m2.add_parameter("b", s1); auto bias_sub = m2.add_instruction(migraphx::make_op("sub"), x_param, bias_param); double const0 = -2. * sqrt(M_2_PI); double const1 = 0.044715 * const0; auto lit0 = m2.add_literal(literal{shape{s2.type()}, {const0}}); auto lit1 = m2.add_literal(literal{shape{s2.type()}, {const1}}); auto one = m2.add_literal(literal{shape{s2.type()}, {1.0}}); auto xb = add_common_op(m2, make_op("mul"), {bias_sub, lit1}); auto a = m2.add_instruction(make_op("mul"), bias_sub, xb); auto b = add_common_op(m2, make_op("add"), {a, lit0}); auto u = m2.add_instruction(make_op("mul"), bias_sub, b); auto emu = m2.add_instruction(make_op("exp"), u); auto c = add_common_op(m2, make_op("add"), {one, emu}); auto y = m2.add_instruction(make_op("div"), bias_sub, c); m2.add_return({y}); } EXPECT(m1 == m2); } TEST_CASE(tanh_gelu_distilgpt2_fp16) { // Uses constant values seen in the distilgpt2_fp16 model, note how they're not exactly right migraphx::shape s1{migraphx::shape::half_type, {2, 4, 8}}; migraphx::shape s2{migraphx::shape::half_type}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto fit_const = m1.add_literal(migraphx::literal{s2, {0.044708251953125}}); auto sqrt_2_rpi = m1.add_literal(migraphx::literal{s2, {0.7978515625}}); auto one = m1.add_literal(migraphx::literal{s2, {1.0f}}); auto one_half = m1.add_literal(migraphx::literal{s2, {0.5f}}); auto three = m1.add_literal(migraphx::literal{s2, {3.0f}}); auto pow0 = add_common_op(m1, migraphx::make_op("pow"), {x, three}); auto mul0 = add_common_op(m1, migraphx::make_op("mul"), {pow0, fit_const}); auto add0 = m1.add_instruction(migraphx::make_op("add"), {mul0, x}); auto mul1 = add_common_op(m1, migraphx::make_op("mul"), {add0, sqrt_2_rpi}); auto tanh0 = m1.add_instruction(migraphx::make_op("tanh"), mul1); auto add1 = add_common_op(m1, migraphx::make_op("add"), {tanh0, one}); auto mul2 = add_common_op(m1, migraphx::make_op("mul"), {x, one_half}); auto y = m1.add_instruction(migraphx::make_op("mul"), {add1, mul2}); m1.add_return({y}); } migraphx::rewrite_gelu pass; pass.apply(m1); migraphx::dead_code_elimination dce; dce.apply(m1); migraphx::module m2; { using migraphx::literal; using migraphx::make_op; using migraphx::shape; auto x = m2.add_parameter("x", s1); double const0 = -2. * sqrt(M_2_PI); double const1 = 0.044715 * const0; auto lit0 = m2.add_literal(literal{shape{s2.type()}, {const0}}); auto lit1 = m2.add_literal(literal{shape{s2.type()}, {const1}}); auto one = m2.add_literal(literal{shape{s2.type()}, {1.0}}); auto xb = add_common_op(m2, make_op("mul"), {x, lit1}); auto a = m2.add_instruction(make_op("mul"), x, xb); auto b = add_common_op(m2, make_op("add"), {a, lit0}); auto u = m2.add_instruction(make_op("mul"), x, b); auto emu = m2.add_instruction(make_op("exp"), u); auto c = add_common_op(m2, make_op("add"), {one, emu}); auto y = m2.add_instruction(make_op("div"), x, c); m2.add_return({y}); } EXPECT(m1 == m2); } TEST_CASE(fastgelu_fp16) { migraphx::shape s1{migraphx::shape::half_type, {2, 4, 8}}; migraphx::shape s2{migraphx::shape::half_type}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto fit_const = m1.add_literal(migraphx::literal{s2, {0.035677}}); auto sqrt_2_rpi = m1.add_literal(migraphx::literal{s2, {0.797885}}); auto one = m1.add_literal(migraphx::literal{s2, {1.0f}}); auto one_half = m1.add_literal(migraphx::literal{s2, {0.5f}}); auto three = m1.add_literal(migraphx::literal{s2, {3.0f}}); auto pow0 = add_common_op(m1, migraphx::make_op("pow"), {x, three}); auto mul0 = add_common_op(m1, migraphx::make_op("mul"), {pow0, fit_const}); auto mul1 = add_common_op(m1, migraphx::make_op("mul"), {sqrt_2_rpi, x}); auto add0 = m1.add_instruction(migraphx::make_op("add"), {mul0, mul1}); auto tanh0 = m1.add_instruction(migraphx::make_op("tanh"), add0); auto add1 = add_common_op(m1, migraphx::make_op("add"), {tanh0, one}); auto mul2 = add_common_op(m1, migraphx::make_op("mul"), {x, one_half}); auto y = m1.add_instruction(migraphx::make_op("mul"), {add1, mul2}); m1.add_return({y}); } migraphx::rewrite_gelu pass; pass.apply(m1); migraphx::dead_code_elimination dce; dce.apply(m1); migraphx::module m2; { using migraphx::literal; using migraphx::make_op; using migraphx::shape; auto x = m2.add_parameter("x", s1); double const0 = -2. * sqrt(M_2_PI); double const1 = 0.044715 * const0; auto lit0 = m2.add_literal(literal{shape{s2.type()}, {const0}}); auto lit1 = m2.add_literal(literal{shape{s2.type()}, {const1}}); auto one = m2.add_literal(literal{shape{s2.type()}, {1.0}}); auto xb = add_common_op(m2, make_op("mul"), {x, lit1}); auto a = m2.add_instruction(make_op("mul"), x, xb); auto b = add_common_op(m2, make_op("add"), {a, lit0}); auto u = m2.add_instruction(make_op("mul"), x, b); auto emu = m2.add_instruction(make_op("exp"), u); auto c = add_common_op(m2, make_op("add"), {one, emu}); auto y = m2.add_instruction(make_op("div"), x, c); m2.add_return({y}); } EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/rewrite_low_precision_test.cpp000066400000000000000000000216031510465702400237010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::rewrite_low_precision{}, migraphx::dead_code_elimination{}}); } template static void create_pow2_div(migraphx::module& m, const std::vector& input_lens, T divisor) { migraphx::shape s_input{DType, input_lens}; migraphx::shape s_lit{DType, {1}}; auto l_pow_2 = m.add_literal(migraphx::literal{s_lit, {2.0f}}); auto l_div = m.add_literal(migraphx::literal{s_lit, {divisor}}); auto input = m.add_parameter("input", s_input); auto pow = add_common_op(m, migraphx::make_op("pow"), {input, l_pow_2}); auto div = add_common_op(m, migraphx::make_op("div"), {pow, l_div}); m.add_return({div}); } TEST_CASE(simplify_pow2_div) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::half_type, {1}}); auto n = m1.add_literal(migraphx::half{5.0f}); auto two = m1.add_literal(migraphx::half{2.0f}); auto pow = m1.add_instruction(migraphx::make_op("pow"), x, two); auto div = m1.add_instruction(migraphx::make_op("div"), pow, n); m1.add_instruction(pass_op{}, div); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::half_type, {1}}); auto n = m2.add_literal(migraphx::half{5.0f}); auto x_div_n = m2.add_instruction(migraphx::make_op("div"), x, n); auto mul = m2.add_instruction(migraphx::make_op("mul"), x_div_n, x); m2.add_instruction(pass_op{}, mul); } EXPECT(m1 == m2); } TEST_CASE(no_simplify_float_pow2_div) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1}}); auto n = m1.add_literal(5.0f); auto two = m1.add_literal(2.0f); auto pow = m1.add_instruction(migraphx::make_op("pow"), x, two); auto div = m1.add_instruction(migraphx::make_op("div"), pow, n); m1.add_instruction(pass_op{}, div); } auto m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(no_simplify_int_pow2_div) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto n = m1.add_literal(5); auto two = m1.add_literal(2); auto pow = m1.add_instruction(migraphx::make_op("pow"), x, two); auto div = m1.add_instruction(migraphx::make_op("div"), pow, n); m1.add_instruction(pass_op{}, div); } auto m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(simplify_x_square_div) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::half_type, {1}}); auto n = m1.add_literal(migraphx::half{5.0f}); auto pow = m1.add_instruction(migraphx::make_op("mul"), x, x); auto div = m1.add_instruction(migraphx::make_op("div"), pow, n); m1.add_instruction(pass_op{}, div); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::half_type, {1}}); auto n = m2.add_literal(migraphx::half{5.0f}); auto x_div_n = m2.add_instruction(migraphx::make_op("div"), x, n); auto mul = m2.add_instruction(migraphx::make_op("mul"), x_div_n, x); m2.add_instruction(pass_op{}, mul); } EXPECT(m1 == m2); } TEST_CASE(no_simplify_x_mul_y_div) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::half_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::half_type, {1}}); auto n = m1.add_literal(migraphx::half{5.0f}); auto pow = m1.add_instruction(migraphx::make_op("mul"), x, y); auto div = m1.add_instruction(migraphx::make_op("div"), pow, n); m1.add_instruction(pass_op{}, div); } auto m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(rewrite_pow2_div_fp16_accuracy_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s_input{migraphx::shape::half_type, {1, 3, 9}}; create_pow2_div( *mm, s_input.lens(), migraphx::half{9.0}); run_pass(*mm); p.compile(migraphx::make_target("ref")); // >>> np.random.seed(0) // >>> d = (np.sqrt(np.finfo(np.float16).max) * np.random.randn(27)).astype(np.float16) std::vector tmp = {451.5, 102.4, 250.4, 573.5, 477.8, -250., 243.1, -38.72, -26.4, 105.06, 36.84, 372., 194.8, 31.14, 113.56, 85.4, 382.2, -52.5, 80.1, -218.5, -653., 167.2, 221.2, -189.9, 581., -372.2, 11.71}; std::vector data = {tmp.begin(), tmp.end()}; migraphx::parameter_map params; params["input"] = migraphx::argument(s_input, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); // The fp16 results without the rewrite should have `inf` values: // >>> (pow(d, 2) / 9.0) // inf, 1164., 6964., inf, inf, 6944., 6568., 166.5, 77.5, // 1227., 150.8, inf, 4212., 107.75, 1433., 810., inf, 306.2, // 713.5, 5304., inf, 3108., 5440., 4008., inf, inf, 15.234 // Expected results with rewrite: // >>> (pow(d.astype(np.float32), 2) / 9.0) .astype(np.float16) tmp = {22656., 1165., 6964., 36544., 25360., 6944., 6568., 166.6, 77.5, 1226., 150.9, 15376., 4216., 107.75, 1433., 810., 16232., 306.2, 713.5, 5304., 47392., 3108., 5440., 4006., 37504., 15400., 15.24}; std::vector gold = {tmp.begin(), tmp.end()}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rewrite_pow2_div_fp16_same_result_test) { migraphx::shape s_input{migraphx::shape::half_type, {1, 3, 9}}; // >>> np.random.seed(42) // >>> d = (100 * np.random.randn(27)).astype(np.float16) std::vector tmp{49.66, -13.83, 64.75, 152.2, -23.42, -23.4, 157.9, 76.75, -46.94, 54.25, -46.34, -46.56, 24.2, -191.4, -172.5, -56.22, -101.3, 31.42, -90.8, -141.2, 146.6, -22.58, 6.754, -142.5, -54.44, 11.09, -115.1}; std::vector data = {tmp.begin(), tmp.end()}; migraphx::parameter_map params; params["input"] = migraphx::argument(s_input, data.data()); migraphx::program p1; auto* mm = p1.get_main_module(); create_pow2_div( *mm, s_input.lens(), migraphx::half{9.0f}); run_pass(*mm); p1.compile(migraphx::make_target("ref")); auto result1 = p1.eval(params).back(); std::vector rewrite_results; result1.visit([&](auto output) { rewrite_results.assign(output.begin(), output.end()); }); migraphx::program p2; mm = p2.get_main_module(); create_pow2_div( *mm, s_input.lens(), migraphx::half{9.0f}); p2.compile(migraphx::make_target("ref")); auto result2 = p2.eval(params).back(); std::vector no_rewrite_results; result2.visit([&](auto output) { no_rewrite_results.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(rewrite_results, no_rewrite_results)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/rewrite_pooling_test.cpp000066400000000000000000001005771510465702400225040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_REWRITE_LRN); static void opt_pooling(migraphx::module& m) { migraphx::rewrite_pooling rp; migraphx::dead_code_elimination dce; rp.apply(m); dce.apply(m); } TEST_CASE(rewrite_pooling_test) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3, 4, 5}}; auto pooling_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"lengths", {3, 4, 5}}, {"dilations", {1, 1, 1}}}), input); m.add_return({ret}); return m; }; auto opt_program = [&](const migraphx::operation& reduce_op) { migraphx::module m; auto input = m.add_parameter("x", s); auto rdm = m.add_instruction(reduce_op, input); m.add_return({rdm}); return m; }; auto test_rewrite = [&](const migraphx::op::pooling_mode mode, const migraphx::operation& op) { migraphx::module m1 = pooling_program(mode); migraphx::module m2 = opt_program(op); opt_pooling(m1); EXPECT(m1 == m2); }; test_rewrite(migraphx::op::pooling_mode::average, migraphx::make_op("reduce_mean", {{"axes", {2, 3, 4}}})); test_rewrite(migraphx::op::pooling_mode::max, migraphx::make_op("reduce_max", {{"axes", {2, 3, 4}}})); } TEST_CASE(rewrite_pooling_dialtions_test) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 5, 5}}; auto pooling_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0}}, {"stride", {1, 1}}, {"lengths", {2, 2}}, {"dilations", {2, 2}}}), input); m.add_return({ret}); return m; }; auto opt_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); std::vector indices{0, 2, 1, 3, 2, 4}; migraphx::shape s_indices{migraphx::shape::int32_type, {indices.size()}}; auto i1 = m.add_literal(migraphx::literal{s_indices, indices}); auto g1 = m.add_instruction(migraphx::make_op("gather", {{"axis", 2}}), input, i1); auto i2 = m.add_literal(migraphx::literal{s_indices, indices}); auto g2 = m.add_instruction(migraphx::make_op("gather", {{"axis", 3}}), g1, i2); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0}}, {"stride", {2, 2}}, {"lengths", {2, 2}}, {"dilations", {1, 1}}}), g2); m.add_return({ret}); return m; }; auto test_rewrite = [&](const migraphx::op::pooling_mode mode) { migraphx::module m1 = pooling_program(mode); migraphx::module m2 = opt_program(mode); opt_pooling(m1); EXPECT(m1 == m2); }; test_rewrite(migraphx::op::pooling_mode::average); test_rewrite(migraphx::op::pooling_mode::max); } TEST_CASE(rewrite_pooling_dialtions_test2) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 5, 5, 5}}; auto pooling_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"lengths", {2, 2, 2}}, {"dilations", {2, 2, 2}}}), input); m.add_return({ret}); return m; }; auto opt_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); std::vector indices{0, 2, 1, 3, 2, 4}; migraphx::shape s_indices{migraphx::shape::int32_type, {indices.size()}}; auto i1 = m.add_literal(migraphx::literal{s_indices, indices}); auto g1 = m.add_instruction(migraphx::make_op("gather", {{"axis", 2}}), input, i1); auto i2 = m.add_literal(migraphx::literal{s_indices, indices}); auto g2 = m.add_instruction(migraphx::make_op("gather", {{"axis", 3}}), g1, i2); auto i3 = m.add_literal(migraphx::literal{s_indices, indices}); auto g3 = m.add_instruction(migraphx::make_op("gather", {{"axis", 4}}), g2, i3); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0, 0}}, {"stride", {2, 2, 2}}, {"lengths", {2, 2, 2}}, {"dilations", {1, 1, 1}}}), g3); m.add_return({ret}); return m; }; auto test_rewrite = [&](const migraphx::op::pooling_mode mode) { migraphx::module m1 = pooling_program(mode); migraphx::module m2 = opt_program(mode); opt_pooling(m1); EXPECT(m1 == m2); }; test_rewrite(migraphx::op::pooling_mode::average); test_rewrite(migraphx::op::pooling_mode::max); } TEST_CASE(rewrite_pooling_dialtions_test3) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 5}}; auto pooling_program = [&]() { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {1}}, {"stride", {1}}, {"lengths", {3}}, {"dilations", {2}}}), input); m.add_return({ret}); return m; }; migraphx::module m1 = pooling_program(); migraphx::module m2 = m1; opt_pooling(m1); EXPECT(m1 == m2); } TEST_CASE(rewrite_pooling_dialtions_test4) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 5, 5}}; auto pooling_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {1, 0}}, {"stride", {1, 3}}, {"lengths", {3, 1}}, {"dilations", {1, 2}}}), input); m.add_return({ret}); return m; }; auto opt_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); std::vector col_indices{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; migraphx::shape s_col_indices{migraphx::shape::int32_type, {col_indices.size()}}; std::vector row_indices{0, 3}; migraphx::shape s_row_indices{migraphx::shape::int32_type, {row_indices.size()}}; auto p = m.add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 1, 0, 0, 0, 1, 0}}, {"value", std::numeric_limits::lowest()}}), input); auto i1 = m.add_literal(migraphx::literal{s_col_indices, col_indices}); auto g1 = m.add_instruction(migraphx::make_op("gather", {{"axis", 2}}), p, i1); auto i2 = m.add_literal(migraphx::literal{s_row_indices, row_indices}); auto g2 = m.add_instruction(migraphx::make_op("gather", {{"axis", 3}}), g1, i2); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0}}, {"stride", {3, 1}}, {"lengths", {3, 1}}, {"dilations", {1, 1}}}), g2); m.add_return({ret}); return m; }; auto test_rewrite = [&](const migraphx::op::pooling_mode mode) { migraphx::module m1 = pooling_program(mode); migraphx::module m2 = opt_program(mode); opt_pooling(m1); EXPECT(m1 == m2); }; // Average won't work because of padding test_rewrite(migraphx::op::pooling_mode::max); } TEST_CASE(rewrite_pooling_dialtions_test5) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 5, 5}}; auto pooling_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0}}, {"stride", {2, 3}}, {"lengths", {2, 1}}, {"dilations", {1, 2}}}), input); m.add_return({ret}); return m; }; auto opt_program = [&](const migraphx::op::pooling_mode mode) { migraphx::module m; auto input = m.add_parameter("x", s); std::vector col_indices{0, 1, 2, 3}; migraphx::shape s_col_indices{migraphx::shape::int32_type, {col_indices.size()}}; std::vector row_indices{0, 3}; migraphx::shape s_row_indices{migraphx::shape::int32_type, {row_indices.size()}}; auto i1 = m.add_literal(migraphx::literal{s_col_indices, col_indices}); auto g1 = m.add_instruction(migraphx::make_op("gather", {{"axis", 2}}), input, i1); auto i2 = m.add_literal(migraphx::literal{s_row_indices, row_indices}); auto g2 = m.add_instruction(migraphx::make_op("gather", {{"axis", 3}}), g1, i2); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0}}, {"stride", {2, 1}}, {"lengths", {2, 1}}, {"dilations", {1, 1}}}), g2); m.add_return({ret}); return m; }; auto test_rewrite = [&](const migraphx::op::pooling_mode mode) { migraphx::module m1 = pooling_program(mode); migraphx::module m2 = opt_program(mode); opt_pooling(m1); EXPECT(m1 == m2); }; test_rewrite(migraphx::op::pooling_mode::average); test_rewrite(migraphx::op::pooling_mode::max); } TEST_CASE(test_lower_lrn_to_pooling) { migraphx::module m1; { migraphx::shape input_shape{migraphx::shape::float_type, {1, 64, 55, 55}}; auto input1 = m1.add_parameter("x", input_shape); auto lrn1 = m1.add_instruction( migraphx::make_op("lrn", {{"alpha", 0.0001f}, {"beta", 0.75f}, {"bias", 1.0f}, {"size", 4}}), input1); m1.add_return({lrn1}); } // Apply the pass directly when the flag enabled migraphx::rewrite_pooling rp{.rewrite_lrn = true}; migraphx::dead_code_elimination dce; rp.apply(m1); dce.apply(m1); migraphx::module m2; { migraphx::shape input_shape{migraphx::shape::float_type, {1, 64, 55, 55}}; auto input2 = m2.add_parameter("x", input_shape); auto x2 = m2.add_instruction(migraphx::make_op("mul"), input2, input2); auto transpose1 = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", std::vector{0, 2, 3, 1}}}), x2); int64_t lrn_size = 4; int64_t pad_left = (lrn_size - 1) / 2; int64_t pad_right = lrn_size - 1 - pad_left; std::vector expected_pads = {pad_left, pad_right}; auto avg = m2.add_instruction( migraphx::make_op( "pooling", {{"mode", migraphx::op::pooling_mode::average}, {"lengths", std::vector{1, lrn_size}}, {"stride", std::vector{1, 1}}, {"padding", std::vector{0, expected_pads[0], 0, expected_pads[1]}}, {"dilations", std::vector{1, 1}}, {"count_include_pad", true}}), transpose1); auto transpose2 = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", std::vector{0, 3, 1, 2}}}), avg); auto k_lit = m2.add_literal(1.0f); auto a_lit = m2.add_literal(0.0001f); auto b_lit = m2.add_literal(0.75f); auto k_mb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_shape.lens()}}), k_lit); auto a_mb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_shape.lens()}}), a_lit); auto b_mb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_shape.lens()}}), b_lit); auto alpha_avg = m2.add_instruction(migraphx::make_op("mul"), a_mb, transpose2); auto den = m2.add_instruction(migraphx::make_op("add"), k_mb, alpha_avg); auto denpow = m2.add_instruction(migraphx::make_op("pow"), den, b_mb); auto y = m2.add_instruction(migraphx::make_op("div"), input2, denpow); m2.add_return({y}); } EXPECT(m1 == m2); } TEST_CASE(rewrite_avgpool_rank3_dil_test) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {2}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.35, 0.15, 0.85, 0.3, 0.1, 0.65}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rewrite_avgpool_rank3_dil_test2) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {3}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.2, 0.45, 0.35}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rewrite_avgpool_rank4_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; op.lengths = {2, 1}; op.padding = {0, 0}; op.stride = {2, 3}; op.dilations = {1, 2}; std::vector data(25); std::iota(data.begin(), data.end(), 1); auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{3.5, 6.5, 13.5, 16.5}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rewrite_maxpool_rank3_test) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2}; op.padding = {0}; op.stride = {1}; op.dilations = {2}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4, 0.2, 0.9, 0.5, 0.1, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rewrite_maxpool_rank3_test2) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2}; op.padding = {1}; op.stride = {1}; op.dilations = {3}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.4, 0.3, 0.2, 0.9, 0.8, 0.5, 0.1, 0.6, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rewrite_maxpool_rank3_test3) { // 1D case 1, input is 3D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 3, 4}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {3}; op.padding = {2}; op.stride = {2}; op.dilations = {3}; std::vector data{0.3, 0.2, 0.4, 0.1, 0.8, 0.5, 0.9, 0.1, 0.1, 0.7, 0.1, 0.6}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.2, 0.5, 0.7}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rewrite_maxpool_rank4_test) { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 5}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {3, 1}; op.padding = {1, 0}; op.stride = {1, 3}; op.dilations = {1, 2}; std::vector data(25); std::iota(data.begin(), data.end(), 1); auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{6, 9, 11, 14, 16, 19, 21, 24, 21, 24}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_rank5_test) { // 3D, input is 5D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {2, 2, 3, 3, 3}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2, 2, 2}; op.padding = {0, 0, 0}; op.stride = {1, 1, 1}; op.dilations = {2, 2, 2}; std::vector data{ -2.8029, 0.5861, 0.7015, 0.1297, -1.44, -1.9472, 0.7812, 2.408, -0.3145, 0.3405, -0.9146, 0.0624, 1.5064, -0.8345, 1.7977, 1.8949, 1.0073, -0.2102, -0.042, -0.7146, 0.6227, -0.5263, -2.2598, 0.1713, 0.449, 0.5303, -0.8622, -0.5691, 0.907, -0.0569, -1.5348, -0.4109, -0.1461, -0.5445, 0.4266, 0.2282, 1.3655, -2.1519, 0.6068, -0.2001, -0.4702, 0.3864, 1.7083, 0.9096, 0.4286, -1.8866, 0.7034, 0.0293, 1.4587, 0.7672, -2.8614, 0.8124, -0.053, 1.0449, 0.845, -0.0131, 0.1139, -0.859, -1.2681, -0.6337, -0.4644, 0.1938, 0.2889, 0.9035, 0.7118, -0.5767, 0.4577, -0.0549, 0.2237, 0.5756, 0.0677, -0.0223, -0.329, 0.2364, 2.7666, -0.7417, -1.3196, -0.2655, 0.1698, -0.1777, -0.9427, 2.6859, -0.7501, 0.5175, 1.0029, -2.6436, -0.4388, -1.2348, -0.1539, -0.6229, -0.4136, 0.5085, 0.4136, -0.6439, -1.1953, -0.406, -0.0195, 0.1869, -0.8664, 1.1364, 0.5041, 0.0647, 0.1941, -1.0819, -0.4629, -0.5107, 0.3612, -0.3583}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{0.7812, 1.0449, 2.7666, 2.6859}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(maxpool_rank5_test2) { // 3D, input is 5D migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape{migraphx::shape::float_type, {2, 2, 3, 3, 3}}; auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; op.lengths = {2, 2, 2}; op.padding = {2, 2, 2}; op.stride = {2, 2, 2}; op.dilations = {3, 3, 3}; std::vector data{ -2.8029, 0.5861, 0.7015, 0.1297, -1.44, -1.9472, 0.7812, 2.408, -0.3145, 0.3405, -0.9146, 0.0624, 1.5064, -0.8345, 1.7977, 1.8949, 1.0073, -0.2102, -0.042, -0.7146, 0.6227, -0.5263, -2.2598, 0.1713, 0.449, 0.5303, -0.8622, -0.5691, 0.907, -0.0569, -1.5348, -0.4109, -0.1461, -0.5445, 0.4266, 0.2282, 1.3655, -2.1519, 0.6068, -0.2001, -0.4702, 0.3864, 1.7083, 0.9096, 0.4286, -1.8866, 0.7034, 0.0293, 1.4587, 0.7672, -2.8614, 0.8124, -0.053, 1.0449, 0.845, -0.0131, 0.1139, -0.859, -1.2681, -0.6337, -0.4644, 0.1938, 0.2889, 0.9035, 0.7118, -0.5767, 0.4577, -0.0549, 0.2237, 0.5756, 0.0677, -0.0223, -0.329, 0.2364, 2.7666, -0.7417, -1.3196, -0.2655, 0.1698, -0.1777, -0.9427, 2.6859, -0.7501, 0.5175, 1.0029, -2.6436, -0.4388, -1.2348, -0.1539, -0.6229, -0.4136, 0.5085, 0.4136, -0.6439, -1.1953, -0.406, -0.0195, 0.1869, -0.8664, 1.1364, 0.5041, 0.0647, 0.1941, -1.0819, -0.4629, -0.5107, 0.3612, -0.3583}; auto l0 = mm->add_literal(migraphx::literal{s, data}); mm->add_instruction(op, l0); opt_pooling(*mm); p.compile(migraphx::make_target("ref")); auto result = p.eval({}).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{-0.8345, 1.5064, -0.9146, 0.3405, -1.44, 0.1297, 0.5861, -2.8029, -0.4702, -0.2001, -2.1519, 1.3655, -0.4109, -1.5348, 0.907, -0.5691, -0.0549, 0.4577, 0.7118, 0.9035, -1.2681, -0.859, -0.0131, 0.845, -1.1953, -0.6439, 0.5085, -0.4136, -2.6436, 1.0029, -0.7501, 2.6859}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(rewrite_avepooling_na1_test) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3, 4, 5}}; auto pooling_program = [&]() { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 1, 0}}, {"stride", {1, 1, 1}}, {"lengths", {3, 4, 5}}, {"dilations", {1, 1, 1}}}), input); m.add_return({ret}); return m; }; migraphx::module m1 = pooling_program(); migraphx::module m2 = m1; opt_pooling(m1); EXPECT(m1 == m2); } TEST_CASE(rewrite_avepooling_na2_test) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3, 4, 5}}; auto pooling_program = [&]() { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 0}}, {"stride", {1, 2, 1}}, {"lengths", {3, 4, 5}}, {"dilations", {1, 1, 1}}}), input); m.add_return({ret}); return m; }; migraphx::module m1 = pooling_program(); migraphx::module m2 = m1; opt_pooling(m1); EXPECT(m1 == m2); } TEST_CASE(rewrite_avepooling_na3_test) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3, 4, 5}}; auto pooling_program = [&]() { migraphx::module m; auto input = m.add_parameter("x", s); auto ret = m.add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"lengths", {3, 3, 5}}, {"dilations", {1, 1, 1}}}), input); m.add_return({ret}); return m; }; migraphx::module m1 = pooling_program(); migraphx::module m2 = m1; opt_pooling(m1); EXPECT(m1 == m2); } TEST_CASE(literal_rewrite_pooling_test) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3, 4, 5}}; std::vector data(s.elements()); std::iota(data.begin(), data.end(), 1.0f); auto pooling_program = [&](const migraphx::op::pooling_mode mode) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_literal(migraphx::literal(s, data)); auto ret = mm->add_instruction(migraphx::make_op("pooling", {{"mode", mode}, {"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"lengths", {3, 4, 5}}, {"dilations", {1, 1, 1}}}), input); mm->add_return({ret}); return p; }; auto opt_program = [&](const migraphx::operation& op) { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_literal(migraphx::literal(s, data)); auto rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {4, -1}}}), input); auto rdm = mm->add_instruction(op, rsp); auto ret = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 1, 1, 1}}}), rdm); mm->add_return({ret}); return p; }; auto test_rewrite_pooling = [&](const migraphx::op::pooling_mode mode, const migraphx::operation& op) { migraphx::program p1 = pooling_program(mode); migraphx::program p2 = opt_program(op); p1.compile(migraphx::make_target("ref")); p2.compile(migraphx::make_target("ref")); auto result1 = p1.eval({}).back(); auto result2 = p2.eval({}).back(); visit_all(result1, result2)( [&](auto r1, auto r2) { EXPECT(migraphx::verify::verify_rms_range(r1, r2)); }); }; test_rewrite_pooling(migraphx::op::pooling_mode::max, migraphx::make_op("reduce_max", {{"axes", {1}}})); test_rewrite_pooling(migraphx::op::pooling_mode::average, migraphx::make_op("reduce_mean", {{"axes", {1}}})); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/rewrite_quantization_test.cpp000066400000000000000000000233741510465702400235620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_ENABLE_CK_WORKAROUNDS); static bool is_quantizelinear(migraphx::instruction& ins) { return ins.name() == "quantizelinear"; } static bool is_dequantizelinear(migraphx::instruction& ins) { return ins.name() == "dequantizelinear"; } static bool is_clip_scalar(migraphx::instruction& ins) { if(ins.name() == "clip") { assert(ins.inputs().size() > 1); return (std::all_of(ins.inputs().begin() + 1, ins.inputs().end(), [](auto input) { return input->get_shape().scalar(); })); } return false; } static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::rewrite_quantization{}}); } static migraphx::argument eval(const migraphx::program& p) { auto r = p.eval({}); EXPECT(r.size() == 1); return r.front(); } TEST_CASE(quantizelinear) { migraphx::shape xs{migraphx::shape::float_type, {1, 3, 3}}; std::vector xv = {-300, 200, 129, 1, 2, 3, 500, 1000, 50}; migraphx::shape ss{migraphx::shape::float_type, {1, 3, 3}}; std::vector sv = {2, 2, 2, 2, 2, 2, 2, 2, 2}; auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(ss, sv); mm->add_instruction(migraphx::make_op("quantizelinear"), x, s); return p; }; migraphx::program p1 = create_program(); migraphx::program p2 = create_program(); run_pass(*p2.get_main_module()); EXPECT(eval(p1) == eval(p2)); EXPECT(any_of(*p1.get_main_module(), &is_quantizelinear)); EXPECT(none_of(*p2.get_main_module(), &is_quantizelinear)); // ensure clip literals created in quantized program are scalar // unless CK workarounds are enabled if(migraphx::enabled(MIGRAPHX_ENABLE_CK_WORKAROUNDS{})) EXPECT(none_of(*p2.get_main_module(), &is_clip_scalar)); else EXPECT(any_of(*p2.get_main_module(), &is_clip_scalar)); } TEST_CASE(dequantizelinear) { migraphx::shape xs{migraphx::shape::float_type, {1, 3, 3}}; std::vector xv = {0, 1, 2, 5, 10, 50, 100, 150, 250}; migraphx::shape ss{migraphx::shape::float_type, {1, 3, 3}}; std::vector sv = {2, 2, 2, 2, 2, 2, 2, 2, 2}; migraphx::shape zs{migraphx::shape::float_type, {1, 3, 3}}; std::vector zv = {0, 0, 0, 0, 0, 0, 0, 0, 0}; auto create_program = [&]() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(ss, sv); auto z = mm->add_literal(zs, zv); mm->add_instruction(migraphx::make_op("dequantizelinear"), x, s, z); return p; }; migraphx::program p1 = create_program(); migraphx::program p2 = create_program(); run_pass(*p2.get_main_module()); EXPECT(eval(p1) == eval(p2)); EXPECT(any_of(*p1.get_main_module(), &is_dequantizelinear)); EXPECT(none_of(*p2.get_main_module(), &is_dequantizelinear)); } // has a nearbyint operation TEST_CASE(quantize_to_integral_type) { migraphx::shape xs{migraphx::shape::float_type, {2, 3, 3}}; std::vector xv = { -300, 600, 129, -1000, 4, 3, -6, 600, 550, -300, 600, 129, -1000, 4, 3, -6, 600, 550}; migraphx::shape ss{migraphx::shape::float_type, {2, 3, 3}}; std::vector sv = {2, 2, 2, 4, 4, 4, 6, 6, 6, 2, 2, 2, 4, 4, 4, 6, 6, 6}; migraphx::shape zs{migraphx::shape::int8_type, {2, 3, 3}}; std::vector zv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; migraphx::program p_run; { auto* mm = p_run.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(ss, sv); auto z = mm->add_literal(zs, zv); mm->add_instruction(migraphx::make_op("quantizelinear"), x, s, z); }; migraphx::program p_expected; { auto* mm = p_expected.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(ss, sv); auto z = mm->add_literal(zs, zv); auto div = mm->add_instruction(migraphx::make_op("div"), x, s); auto nearby_int = mm->add_instruction(migraphx::make_op("nearbyint"), div); auto zero_point = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), z); auto add_zero_point = mm->add_instruction(migraphx::make_op("add"), nearby_int, zero_point); double max_quant = 0; double min_quant = 0; auto zp_shape = add_zero_point->get_shape(); zs.visit_type([&](auto qt) { max_quant = qt.max(); min_quant = qt.min(); }); auto min_arg = mm->add_literal(migraphx::literal{migraphx::shape{zp_shape.type()}, {min_quant}}); auto max_arg = mm->add_literal(migraphx::literal{migraphx::shape{zp_shape.type()}, {max_quant}}); min_arg = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", zp_shape.lens()}}), min_arg); max_arg = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", zp_shape.lens()}}), max_arg); auto saturate = mm->add_instruction(migraphx::make_op("clip"), {add_zero_point, min_arg, max_arg}); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::int8_type}}), saturate); }; run_pass(*p_run.get_main_module()); EXPECT(p_run == p_expected); } // should not have a nearbyint operation TEST_CASE(quantize_to_floating_point_type) { migraphx::shape xs{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape zs{migraphx::shape::get_type{}, {2, 2, 2}}; std::vector xv = {0.5, 0.75, -0.4375, 0.6875, -0.9375, -0.9375, 0.625, -0.5625}; std::vector sv = {0.25, 0.75, 0.5625, 0.4375, 0.8125, -0.6875, 0.875, -0.0625}; std::vector tmp = {0.6875, 0.75, -0.75, 0.5, -0.0625, 0.0625, -0.375, 0.25}; std::vector zero_pts; std::transform(tmp.begin(), tmp.end(), std::back_inserter(zero_pts), [](auto x) { return migraphx::fp8::fp8e4m3fn(x); }); migraphx::program p_run; { auto* mm = p_run.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(xs, sv); auto z = mm->add_literal(zs, zero_pts); mm->add_instruction(migraphx::make_op("quantizelinear"), x, s, z); }; migraphx::program p_expected; { auto* mm = p_expected.get_main_module(); auto x = mm->add_literal(xs, xv); auto s = mm->add_literal(xs, sv); auto z = mm->add_literal(zs, zero_pts); auto div = mm->add_instruction(migraphx::make_op("div"), x, s); auto zero_point = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), z); auto add_zero_point = mm->add_instruction(migraphx::make_op("add"), div, zero_point); double max_quant = 0; double min_quant = 0; auto zp_shape = add_zero_point->get_shape(); zs.visit_type([&](auto qt) { max_quant = qt.max(); min_quant = qt.min(); }); auto min_arg = mm->add_literal(migraphx::literal{migraphx::shape{zp_shape.type()}, {min_quant}}); auto max_arg = mm->add_literal(migraphx::literal{migraphx::shape{zp_shape.type()}, {max_quant}}); min_arg = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", zp_shape.lens()}}), min_arg); max_arg = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", zp_shape.lens()}}), max_arg); auto saturate = mm->add_instruction(migraphx::make_op("clip"), {add_zero_point, min_arg, max_arg}); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::fp8e4m3fn_type}}), saturate); }; run_pass(*p_run.get_main_module()); EXPECT(p_run == p_expected); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/rewrite_reduce.cpp000066400000000000000000000424261510465702400212430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::rewrite_reduce{}, migraphx::dead_code_elimination{}}); } TEST_CASE(softmax) { migraphx::shape s{migraphx::shape::float_type, {10, 1000}}; migraphx::module m; auto x = m.add_parameter("x", s); auto softmax = m.add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), x); m.add_return({softmax}); run_pass(m); EXPECT(none_of(migraphx::iterator_for(m), [](auto ins) { return ins->name() == "softmax"; })); auto reduces = find_all(migraphx::iterator_for(m), [&](auto ins) { return migraphx::contains(ins->name(), "reduce"); }); EXPECT(all_of(reduces, [](auto ins) { auto axes = ins->get_operator().to_value()["axes"].template to_vector(); return axes.size() == 1 and axes[0] == 1; })); } TEST_CASE(softmax_upcast) { migraphx::shape s{migraphx::shape::half_type, {10, 1000}}; migraphx::module m; auto x = m.add_parameter("x", s); auto softmax = m.add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), x); m.add_return({softmax}); run_pass(m); EXPECT(none_of(migraphx::iterator_for(m), [](auto ins) { return ins->name() == "softmax"; })); auto reduces = find_all(migraphx::iterator_for(m), [&](auto ins) { return migraphx::contains(ins->name(), "reduce"); }); EXPECT(all_of(reduces, [](auto ins) { auto axes = ins->get_operator().to_value()["axes"].template to_vector(); auto dtype = ins->get_shape().type(); return axes.size() == 1 and axes[0] == 1 and dtype == migraphx::shape::float_type; })); } TEST_CASE(softmax_lse_upcast) { migraphx::shape s{migraphx::shape::half_type, {10, 1000}}; migraphx::module m; auto x = m.add_parameter("x", s); auto rmax = m.add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), x); auto rmax_mb = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rmax); auto sub = m.add_instruction(migraphx::make_op("sub"), x, rmax_mb); auto exp = m.add_instruction(migraphx::make_op("exp"), sub); auto rsum = m.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), exp); auto rsum_mb = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto div = m.add_instruction(migraphx::make_op("div"), exp, rsum_mb); auto log = m.add_instruction(migraphx::make_op("log"), rsum); auto add = m.add_instruction(migraphx::make_op("add"), log, rmax); m.add_return({div, add}); run_pass(m); auto reduces = find_all(migraphx::iterator_for(m), [&](auto ins) { return migraphx::contains(ins->name(), "reduce"); }); EXPECT(all_of(reduces, [](auto ins) { return ins->get_shape().type() == migraphx::shape::float_type; })); EXPECT(all_of(m.get_returns(), [&](auto ins) { return ins->get_shape().type() == s.type(); })); } TEST_CASE(reduce_mean) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 9}}; migraphx::module m; auto x = m.add_parameter("x", s); auto reduce_mean = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {-1}}}), x); m.add_return({reduce_mean}); run_pass(m); EXPECT( none_of(migraphx::iterator_for(m), [](auto ins) { return ins->name() == "reduce_mean"; })); auto reduces = find_all(migraphx::iterator_for(m), [&](auto ins) { return migraphx::contains(ins->name(), "reduce_sum"); }); EXPECT(all_of(reduces, [](auto ins) { auto axes = ins->get_operator().to_value()["axes"].template to_vector(); return axes.size() == 1 and axes[0] == -1; })); } TEST_CASE(reduce_mean_large) { migraphx::shape s{migraphx::shape::half_type, {1, 3, 65536}}; migraphx::module m; auto x = m.add_parameter("x", s); auto reduce_mean = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {-1}}}), x); m.add_return({reduce_mean}); run_pass(m); EXPECT( none_of(migraphx::iterator_for(m), [](auto ins) { return ins->name() == "reduce_mean"; })); auto reduces = find_all(migraphx::iterator_for(m), [&](auto ins) { return migraphx::contains(ins->name(), "reduce_sum"); }); EXPECT(all_of(reduces, [](migraphx::instruction_ref ins) { auto axes = ins->get_operator().to_value()["axes"].template to_vector(); return axes.size() == 1 and axes[0] == -1 and ins->get_shape().type() == migraphx::shape::float_type; })); } TEST_CASE(reduce_mean_accuracy) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 3, 9}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {-1}}}), x); mm->add_return({reduce_mean}); run_pass(*mm); p.compile(migraphx::make_target("ref")); std::vector data(s.elements()); std::iota(data.begin(), data.end(), 0); migraphx::parameter_map params; params["x"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{4.f, 13.f, 22.f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reduce_mean_accuracy2) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 3, 3, 3}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); mm->add_return({reduce_mean}); run_pass(*mm); p.compile(migraphx::make_target("ref")); std::vector data(s.elements()); std::iota(data.begin(), data.end(), 0); migraphx::parameter_map params; params["x"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{4.f, 13.f, 22.f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reduce_mean_accuracy3) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 3, 3, 3}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); mm->add_return({reduce_mean}); run_pass(*mm); p.compile(migraphx::make_target("ref")); std::vector data(s.elements()); std::iota(data.begin(), data.end(), 0); migraphx::parameter_map params; params["x"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{3.f, 4.f, 5.f, 12.f, 13.f, 14.f, 21.f, 22.f, 23.f}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reduce_mean_accuracy4) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {1, 3, 2, 2}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); mm->add_return({reduce_mean}); run_pass(*mm); p.compile(migraphx::make_target("ref")); std::vector data(s.elements()); std::iota(data.begin(), data.end(), 0); migraphx::parameter_map params; params["x"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{1, 2, 5, 6, 9, 10}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } TEST_CASE(reduce_mean_accuracy5) { using migraphx::half; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {1, 3, 2, 2}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); mm->add_return({reduce_mean}); run_pass(*mm); p.compile(migraphx::make_target("ref")); std::vector data(s.elements()); std::iota(data.begin(), data.end(), 0); migraphx::parameter_map params; params["x"] = migraphx::argument(s, data.data()); auto result = p.eval(params).back(); std::vector results_vector; result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold{half{1}, half{2}, half{5}, half{6}, half{9}, half{10}}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } static migraphx::instruction_ref add_reduce_mean(migraphx::module& m, std::vector axes, migraphx::instruction_ref input) { auto reduce_size = migraphx::transform_accumulate( axes.begin(), axes.end(), std::size_t{1}, std::multiplies<>{}, [&](auto axis) { return input->get_shape().lens()[axis]; }); auto t = input->get_shape().type(); auto rl = m.add_literal(migraphx::literal{{t, {1}}, {reduce_size}}); auto div = migraphx::add_common_op(m, migraphx::make_op("div"), {input, rl}); return m.add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), div); } TEST_CASE(reduce_mean_variance_sqdiff) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 9}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto mean = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto meanb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mean); auto sqdiff = m1.add_instruction(migraphx::make_op("sqdiff"), x, meanb); auto variance = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), sqdiff); m1.add_return({mean, variance}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto mean = add_reduce_mean(m2, {2}, x); auto x2 = m2.add_instruction(migraphx::make_op("mul"), x, x); auto mean_x2 = add_reduce_mean(m2, {2}, x2); auto mean2 = m2.add_instruction(migraphx::make_op("mul"), mean, mean); auto variance = m2.add_instruction(migraphx::make_op("sub"), mean_x2, mean2); m2.add_return({mean, variance}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_mean_variance_mul_x_minus) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 9}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto mean = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto meanb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mean); auto sub = m1.add_instruction(migraphx::make_op("sub"), x, meanb); auto mul = m1.add_instruction(migraphx::make_op("mul"), sub, sub); auto variance = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), mul); m1.add_return({mean, variance}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto mean = add_reduce_mean(m2, {2}, x); auto x2 = m2.add_instruction(migraphx::make_op("mul"), x, x); auto mean_x2 = add_reduce_mean(m2, {2}, x2); auto mean2 = m2.add_instruction(migraphx::make_op("mul"), mean, mean); auto variance = m2.add_instruction(migraphx::make_op("sub"), mean_x2, mean2); m2.add_return({mean, variance}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_mean_variance_pow_x_minus) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 9}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto mean = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto meanb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mean); auto sub = m1.add_instruction(migraphx::make_op("sub"), x, meanb); auto two = m1.add_literal(migraphx::literal{migraphx::shape::float_type, {2}}); auto pow = migraphx::add_common_op(m1, migraphx::make_op("pow"), {sub, two}); auto variance = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), pow); m1.add_return({mean, variance}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto mean = add_reduce_mean(m2, {2}, x); auto x2 = m2.add_instruction(migraphx::make_op("mul"), x, x); auto mean_x2 = add_reduce_mean(m2, {2}, x2); auto mean2 = m2.add_instruction(migraphx::make_op("mul"), mean, mean); auto variance = m2.add_instruction(migraphx::make_op("sub"), mean_x2, mean2); m2.add_return({mean, variance}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_mean_variance_diff_inputs) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 9}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto mean1 = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto mean2b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mean1); auto sqdiff = m1.add_instruction(migraphx::make_op("sqdiff"), y, mean2b); auto mean2 = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), sqdiff); m1.add_return({mean1, mean2}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto mean1 = add_reduce_mean(m2, {2}, x); auto mean2b = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mean1); auto sqdiff = m2.add_instruction(migraphx::make_op("sqdiff"), y, mean2b); auto mean2 = add_reduce_mean(m2, {2}, sqdiff); m2.add_return({mean1, mean2}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_mean_variance_sqdiff_diff_axes) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 9}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto mean = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto meanb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mean); auto sqdiff = m1.add_instruction(migraphx::make_op("sqdiff"), x, meanb); auto variance = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 2}}}), sqdiff); m1.add_return({mean, variance}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto mean = add_reduce_mean(m2, {2}, x); auto meanb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mean); auto sqdiff = m2.add_instruction(migraphx::make_op("sqdiff"), x, meanb); auto variance = add_reduce_mean(m2, {0, 2}, sqdiff); m2.add_return({mean, variance}); } EXPECT(m1.sort() == m2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/rewrite_topk.cpp000066400000000000000000000170321510465702400207440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::rewrite_topk{.split_threshold = 16384}}); } TEST_CASE(small_topk) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {300}}); auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}}), x); m1.add_return({r}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(large_topk_no_split) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {240000}}); auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 120000}}), x); m1.add_return({r}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(split_topk_batch_1) { const auto n = 240000; migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {n}}); auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}}), x); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { const auto group = 32; std::vector indices(n); std::iota(indices.begin(), indices.end(), 0); auto x = m2.add_parameter("x", {migraphx::shape::float_type, {n}}); auto input_idx = m2.add_literal(migraphx::literal{{migraphx::shape::uint32_type, {n}}, indices}); auto input_idxb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {n}}}), input_idx); auto input_idxr = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {group, n / group}}}), input_idxb); auto xr = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {group, n / group}}}), x); auto r1 = m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), xr, input_idxr); auto value1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r1); auto idx1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r1); auto valuer = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {8 * group}}}), value1); auto idxr = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {8 * group}}}), idx1); auto r2 = m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), valuer, idxr); m2.add_return({r2}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(split_topk_batch_64) { const auto n = 240000; const auto batch = 64; migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {batch, n}}); auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), x); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { const auto group = 32; std::vector indices(n); std::iota(indices.begin(), indices.end(), 0); auto x = m2.add_parameter("x", {migraphx::shape::float_type, {batch, n}}); auto input_idx = m2.add_literal(migraphx::literal{{migraphx::shape::uint32_type, {n}}, indices}); auto input_idxb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {batch, n}}}), input_idx); auto input_idxr = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {batch, group, n / group}}}), input_idxb); auto xr = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {batch, group, n / group}}}), x); auto r1 = m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 2}}), xr, input_idxr); auto value1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r1); auto idx1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r1); auto valuer = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {batch, 8 * group}}}), value1); auto idxr = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {batch, 8 * group}}}), idx1); auto r2 = m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), valuer, idxr); m2.add_return({r2}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(split_topk_batch_64_last) { const auto n = 240000; const auto batch = 64; migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {n, batch}}); auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), x); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { const auto group = 32; std::vector indices(n); std::iota(indices.begin(), indices.end(), 0); auto x = m2.add_parameter("x", {migraphx::shape::float_type, {n, batch}}); auto input_idx = m2.add_literal(migraphx::literal{{migraphx::shape::uint32_type, {n}}, indices}); auto input_idxb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {n, batch}}}), input_idx); auto input_idxr = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {group, n / group, batch}}}), input_idxb); auto xr = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {group, n / group, batch}}}), x); auto r1 = m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), xr, input_idxr); auto value1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r1); auto idx1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r1); auto valuer = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {8 * group, batch}}}), value1); auto idxr = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {8 * group, batch}}}), idx1); auto r2 = m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), valuer, idxr); m2.add_return({r2}); } EXPECT(m1.sort() == m2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/run_loop_test.cpp000066400000000000000000000240251510465702400211220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test.hpp" struct copy_op { std::string name() const { return "copy"; } migraphx::shape compute_shape(std::vector inputs) const { return inputs.front(); } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { visit_all(args[0], args[1])([&](auto input, auto output) { std::copy(input.begin(), input.end(), output.begin()); }); return args[1]; } int output_alias(const std::vector&) const { return 0; } }; struct test_loop_op { int64_t max_iterations = 10; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.max_iterations, "max_iterations")); } std::string name() const { return "test_loop_op"; } migraphx::shape compute_shape(const std::vector& inputs, std::vector mods) const { migraphx::check_shapes{inputs, *this}.standard(); if(mods.size() != 1) { MIGRAPHX_THROW("LOOP: operator should have one submodule."); } const auto& mod = mods.front(); auto mod_out_shapes = mod->get_output_shapes(); auto dep_param_num = inputs.size() - 2; // first item of the mod output shapes is condition used in loop, // which is not needed to compute output shape mod_out_shapes.erase(mod_out_shapes.begin()); std::vector ins_out_shapes(mod_out_shapes.begin(), mod_out_shapes.begin() + dep_param_num); mod_out_shapes.erase(mod_out_shapes.begin(), mod_out_shapes.begin() + dep_param_num); for(const auto& out_s : mod_out_shapes) { auto lens = out_s.lens(); lens.insert(lens.begin(), max_iterations); ins_out_shapes.push_back({out_s.type(), lens}); } return migraphx::shape(ins_out_shapes); } struct test_loop : public migraphx::op::loop::ref_loop { test_loop(int64_t iter_num) { max_iterations = iter_num; } std::unordered_map get_output_params(const migraphx::module& m) const { auto get_output_index = [](const std::string& name) { std::string out_prefix = "#output_"; auto loc = name.find(out_prefix); if(loc != std::string::npos) { return std::stoi(name.substr(loc + out_prefix.size())); } return -1; }; const auto& param_names = m.get_parameter_names(); std::unordered_map result; for(const auto& name : param_names) { auto index = get_output_index(name); if(index == -1) continue; result[name] = index; } return result; } }; migraphx::argument compute(migraphx::context& ctx, const migraphx::shape& out_shape, const std::vector& args, const std::vector& mods, const std::function( migraphx::module_ref&, const std::unordered_map&)>& run) const { // wrap up the arguments vector, so ref and gpu impl are the same auto cpy_args = args; bool in_cond = args.at(1).at(); bool cond = in_cond; int64_t iter = 0; // insert iter and cond used in the loop auto s_cond = args.at(1).get_shape(); auto s_iter = args.at(0).get_shape(); cpy_args.push_back({s_iter, &iter}); cpy_args.push_back({s_cond, &cond}); cpy_args.insert(cpy_args.end(), args.begin() + 2, args.end()); // add cond and mod outputs to the argument list cpy_args.push_back(migraphx::argument(s_cond)); cpy_args.push_back(migraphx::argument(out_shape)); // run loop return run_loop(test_loop{max_iterations}, {}, ctx, cpy_args, mods, run); } }; static auto create_program(int64_t max_loop_iterations = 10) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape si{migraphx::shape::int64_type}; migraphx::shape s{migraphx::shape::int64_type, {1}}; migraphx::shape sc{migraphx::shape::bool_type}; auto in_iter = mm->add_parameter("iter_num", si); auto in_cond = mm->add_parameter("ccond", sc); auto in_val = mm->add_parameter("val", s); auto* body = p.create_module("loop_module"); auto iter = body->add_parameter("#loop_module_in_0", si); body->add_parameter("#loop_module_in_1", sc); auto in_v = body->add_parameter("#loop_module_in_2", s); std::vector vd = {3}; auto l = body->add_literal(migraphx::literal(si, vd)); auto ad = body->add_instruction(migraphx::make_op("add"), iter, l); auto val = body->add_instruction(migraphx::make_op("add"), in_v, ad); auto eq = body->add_instruction(migraphx::make_op("equal"), iter, l); auto beq = body->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), eq); auto neq = body->add_instruction(migraphx::make_op("not"), beq); std::string out_param_prefix = "loop_module:#output_"; auto out0 = body->add_parameter(out_param_prefix + std::to_string(0), neq->get_shape()); auto r_neq = body->add_instruction(copy_op{}, neq, out0); auto out2 = body->add_parameter(out_param_prefix + std::to_string(2), val->get_shape()); auto r_val = body->add_instruction(copy_op{}, val, out2); body->add_return({r_neq, r_val, r_val}); auto rl = mm->add_instruction(test_loop_op{max_loop_iterations}, {in_iter, in_cond, in_val}, {body}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), rl); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), rl); mm->add_return({r0, r1}); return p; }; static auto run_prog(migraphx::program p, int64_t iter_num, bool cond, int64_t ini_val) { migraphx::shape si{migraphx::shape::int64_type}; migraphx::shape s{migraphx::shape::int64_type, {1}}; migraphx::shape sc{migraphx::shape::bool_type}; p.compile(migraphx::make_target("ref")); migraphx::parameter_map pp; pp["iter_num"] = migraphx::argument(si, &iter_num); pp["ccond"] = migraphx::argument(sc, &cond); pp["val"] = migraphx::argument(s, &ini_val); auto rets = p.eval(pp); std::vector> res; for(auto& arg : rets) { std::vector vec; arg.visit([&](auto v) { vec.assign(v.begin(), v.end()); }); res.push_back(vec); } return res; } TEST_CASE(loop_test1) { auto p = create_program(); auto ress = run_prog(p, 10, true, 1); std::vector gold_last = {19}; EXPECT(ress.front() == gold_last); std::vector gold_concat = {4, 8, 13, 19, 0, 0, 0, 0, 0, 0}; EXPECT(ress.back() == gold_concat); } TEST_CASE(loop_test2) { auto p = create_program(12); auto ress = run_prog(p, 4, true, 1); std::vector gold_last = {19}; EXPECT(ress.front() == gold_last); std::vector gold_concat = {4, 8, 13, 19, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(ress.back() == gold_concat); } TEST_CASE(loop_test3) { auto p = create_program(3); auto ress = run_prog(p, 3, true, 1); std::vector gold_last = {13}; EXPECT(ress.front() == gold_last); std::vector gold_concat = {4, 8, 13}; EXPECT(ress.back() == gold_concat); } TEST_CASE(loop_test4) { auto p = create_program(20); auto ress = run_prog(p, 5, true, 2); std::vector gold_last = {20}; EXPECT(ress.front() == gold_last); std::vector gold_concat = {5, 9, 14, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT(ress.back() == gold_concat); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/run_on_target_test.cpp000066400000000000000000000061411510465702400221320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include "test.hpp" TEST_CASE(run_on_target_shape_tests) { { test::throws([]() { migraphx::make_op("run_on_target"); }); } { migraphx::program p; auto s = migraphx::shape{migraphx::shape::float_type, {12, 12}}; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", s); auto* run_mod = p.create_module("run_mod"); run_mod->add_return({x, x}); test::throws( [&]() { mm->add_instruction(migraphx::make_op("run_on_target"), {x}, {run_mod}); }); test::throws([&]() { mm->add_instruction(migraphx::make_op("run_on_target"), {x}, {run_mod, run_mod}); }); } } TEST_CASE(eval_run_on_target) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto l = mm->add_literal(migraphx::literal{s, {4.0, 16.0, 64.0}}); auto* ref_mod = p.create_module("ref_mod"); auto ref_rsqrt = ref_mod->add_instruction(migraphx::make_op("rsqrt"), l); ref_mod->add_return({ref_rsqrt}); auto run_on_ins = mm->add_instruction(migraphx::make_op("run_on_target", {{"target_id", 0}}), {}, {ref_mod}); mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), run_on_ins); p.compile({migraphx::make_target("ref")}); auto result = p.eval({}).back(); std::vector results_vector(3); result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); std::vector gold = {0.5, 0.25, 0.125}; EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/schedule_test.cpp000066400000000000000000001100521510465702400210550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include struct unary_op { std::string name() const { return "unary"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } int output_alias(const std::vector&) const { return 0; } }; struct nary_op { std::string comment; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.comment, "comment")); } std::string name() const { return "nary"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } }; struct stream_free_op { std::string comment; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.comment, "comment")); } std::string name() const { return "stream_free"; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, std::vector args) const { if(args.empty()) return {}; return args.front(); } migraphx::shape compute_shape(std::vector inputs) const { if(inputs.empty()) return {}; return inputs.front(); } }; struct wait_event { std::shared_ptr> wait_for = std::make_shared>(); template static auto reflect(Self& self, F f) { return migraphx::pack(f(*self.wait_for, "wait_for")); } std::string name() const { return "wait_event"; } migraphx::shape compute_shape(const std::vector&) const { return {}; } migraphx::argument compute(migraphx::context&, const migraphx::shape&, const std::vector&) const { assert(wait_for != nullptr); assert(not wait_for->empty()); return {}; } }; using instruction_map = std::unordered_map; using int_map = std::unordered_map; using wait_map = std::unordered_map>>; struct schedule_model_test { std::shared_ptr ins2stream = std::make_shared(); std::shared_ptr wait2stream = std::make_shared(); std::shared_ptr ins2wait_for = std::make_shared(); std::size_t concurrency() const { return 4; } void sched(migraphx::module&, migraphx::instruction_ref ins, std::size_t n) const { (*ins2stream)[ins] = n; } void wait(migraphx::module& m, migraphx::instruction_ref ins, std::size_t wait_id) const { if(ins2wait_for->count(ins) == 0) { auto event = wait_event{}; m.insert_instruction(ins, event); (*ins2wait_for)[ins] = event.wait_for; } (*ins2wait_for)[ins]->push_back(wait2stream->at(wait_id)); } void record(migraphx::module&, migraphx::instruction_ref ins, std::size_t wait_id) const { (*wait2stream)[wait_id] = ins2stream->at(ins); } std::size_t weight(const migraphx::operation& op) const { if(op.name() == "stream_free") return 0; else if(op.name() == "binary" or op.name() == "unary") return 4; else return 1; } }; static bool check_conflicts(migraphx::module& m, migraphx::instruction_ref x, migraphx::instruction_ref y) { return migraphx::any_of(migraphx::iterator_for(m), [&](auto ins) { if(ins->name() != "identity") return false; if(not migraphx::contains(ins->inputs(), x)) return false; if(not migraphx::contains(ins->inputs(), y)) return false; return true; }); } struct scheduler { schedule_model_test model{}; std::size_t get_stream(migraphx::instruction_ref ins) { return model.ins2stream->at(ins); } std::vector get_streams(std::vector inss) { std::vector result; std::transform(inss.begin(), inss.end(), std::back_inserter(result), [&](auto ins) { return this->get_stream(ins); }); return result; } void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::schedule{model}}); } bool has_stream(migraphx::instruction_ref ins) { return model.ins2stream->count(ins) > 0; } void check_conflicts(migraphx::module& m, std::vector> conflicts, bool result = true) { migraphx::dfor(conflicts.size(), conflicts.size())([&](auto i, auto j) { if(i == j) return; for(auto ins1 : conflicts[i]) { for(auto ins2 : conflicts[j]) { // If both instructions are on the same stream then dont check for a conflict if(this->has_stream(ins1) and this->has_stream(ins2) and this->get_stream(ins1) == this->get_stream(ins2)) continue; CHECK(::check_conflicts(m, ins1, ins2) == result); } } }); } }; template static std::vector sorted(std::vector x) { std::sort(x.begin(), x.end()); return x; } template static std::vector unique(std::vector x) { std::sort(x.begin(), x.end()); x.erase(std::unique(x.begin(), x.end()), x.end()); return x; } static std::vector get_wait_for(std::vector wait_for) { return unique(std::move(wait_for)); } static std::vector get_wait_for(std::size_t wait_on, std::vector wait_for) { wait_for.erase(std::find(wait_for.begin(), wait_for.end(), wait_on)); return unique(wait_for); } static std::vector get_wait_for(migraphx::instruction_ref ins) { auto wait_ins = std::prev(ins); // Skip identity operators while(wait_ins->name() == "identity") wait_ins = std::prev(wait_ins); if(wait_ins->name() != "wait_event") return {}; auto wf = *migraphx::any_cast(wait_ins->get_operator()).wait_for; std::sort(wf.begin(), wf.end()); return wf; } template static std::vector chain(migraphx::module& m, std::size_t n, T x, migraphx::instruction_ref input) { std::vector result; for(std::size_t i = 0; i < n; i++) { result.push_back(m.add_instruction(x, input)); input = result.back(); } return result; } TEST_CASE(single_entry) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onem1 = m.add_instruction(unary_op{}, one); auto onem2 = m.add_instruction(unary_op{}, one); auto binary = m.add_instruction(nary_op{}, onem1, onem2); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(onem1) != t.get_stream(onem2)); EXPECT(t.get_stream(binary) == 0); EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(onem1), t.get_stream(onem2)})); EXPECT(check_conflicts(m, onem1, onem2)); } TEST_CASE(stream_free) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onem1 = m.add_instruction(stream_free_op{}, one); auto onem2 = m.add_instruction(stream_free_op{}, one); auto binary = m.add_instruction(nary_op{}, onem1, onem2); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(not t.has_stream(onem1)); EXPECT(not t.has_stream(onem2)); EXPECT(not t.has_stream(binary)); } TEST_CASE(zero_record) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onem1 = m.add_instruction(unary_op{}, one); auto onem2 = m.add_instruction(unary_op{}, one); auto onei1 = m.add_instruction(migraphx::make_op("identity"), onem1); auto onei2 = m.add_instruction(migraphx::make_op("identity"), onem2); auto binary = m.add_instruction(nary_op{}, onei1, onei2); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(onem1) != t.get_stream(onem2)); EXPECT(t.has_stream(binary)); EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(onem1), t.get_stream(onem2)})); EXPECT(check_conflicts(m, onem1, onem2)); t.check_conflicts(m, {{onem1, onei1}, {onem2, onei2}}); } TEST_CASE(zero_merge1) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onem1 = m.add_instruction(unary_op{}, one); auto onem2 = m.add_instruction(unary_op{}, one); auto binary = m.add_instruction(migraphx::make_op("identity"), onem1, onem2); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(onem1) != t.get_stream(onem2)); // No stream assignment EXPECT(not t.has_stream(binary)); // There is no wait EXPECT(get_wait_for(binary).empty()); EXPECT(check_conflicts(m, onem1, onem2)); } TEST_CASE(zero_merge2) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onem1 = m.add_instruction(unary_op{}, one); auto onem2 = m.add_instruction(unary_op{}, one); auto binary = m.add_instruction(migraphx::make_op("identity"), m.add_instruction(migraphx::make_op("identity"), onem1), m.add_instruction(migraphx::make_op("identity"), onem2)); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(onem1) != t.get_stream(onem2)); // No stream assignment EXPECT(not t.has_stream(binary)); // There is no wait EXPECT(get_wait_for(binary).empty()); EXPECT(check_conflicts(m, onem1, onem2)); } TEST_CASE(zero_merge3) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onem1 = m.add_instruction(unary_op{}, one); auto onem2 = m.add_instruction(unary_op{}, one); auto id = m.add_instruction(migraphx::make_op("identity"), onem1, onem2); auto final = m.add_instruction(unary_op{}, id); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(onem1) != t.get_stream(onem2)); // No stream assignment EXPECT(not t.has_stream(id)); // There is no wait EXPECT(get_wait_for(id).empty()); // Stream assignment for final op EXPECT(t.get_stream(final) == 0); EXPECT(get_wait_for(final) == get_wait_for(t.get_stream(final), {t.get_stream(onem1), t.get_stream(onem2)})); EXPECT(check_conflicts(m, onem1, onem2)); } TEST_CASE(zero_merge4) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onem1 = m.add_instruction(unary_op{}, one); auto onem2 = m.add_instruction(unary_op{}, one); auto id = m.add_instruction(migraphx::make_op("identity"), m.add_instruction(migraphx::make_op("identity"), onem1), m.add_instruction(migraphx::make_op("identity"), onem2)); auto final = m.add_instruction(unary_op{}, id); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(onem1) != t.get_stream(onem2)); // No stream assignment EXPECT(not t.has_stream(id)); // There is no wait EXPECT(get_wait_for(id).empty()); // Stream assignment for final op EXPECT(t.get_stream(final) == 0); EXPECT(get_wait_for(final) == get_wait_for(t.get_stream(final), {t.get_stream(onem1), t.get_stream(onem2)})); EXPECT(check_conflicts(m, onem1, onem2)); } TEST_CASE(double_entry) { scheduler t{}; migraphx::module m; auto one = m.add_instruction(stream_free_op{}, m.add_literal(1)); auto two = m.add_instruction(stream_free_op{}, m.add_literal(2)); auto onep = m.add_instruction(unary_op{}, one); auto twop = m.add_instruction(unary_op{}, two); auto binary = m.add_instruction(nary_op{}, onep, twop); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(not t.has_stream(two)); EXPECT(t.get_stream(onep) != t.get_stream(twop)); EXPECT(t.get_stream(binary) == 0); EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(onep), t.get_stream(twop)})); t.check_conflicts(m, {{onep, one}, {twop, two}}); } TEST_CASE(two_branches) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto c1 = chain(m, 2, unary_op{}, one); auto i1 = m.add_instruction(unary_op{}, one); auto binary = m.add_instruction(nary_op{}, i1, c1.back()); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(i1) == 1); for(auto ins : c1) EXPECT(t.get_stream(ins) == 0); EXPECT(t.get_stream(binary) == 0); EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(c1.back()), t.get_stream(i1)})); t.check_conflicts(m, {c1, {i1}}); } TEST_CASE(four_branches) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto c1 = chain(m, 4, unary_op{}, one); auto c2 = chain(m, 3, unary_op{}, one); auto c3 = chain(m, 2, unary_op{}, one); auto i1 = m.add_instruction(unary_op{}, one); auto binary = m.add_instruction(nary_op{}, i1, c1.back(), c2.back(), c3.back()); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(i1) == 3); for(auto ins : c1) EXPECT(t.get_stream(ins) == 0); for(auto ins : c2) EXPECT(t.get_stream(ins) == 1); for(auto ins : c3) EXPECT(t.get_stream(ins) == 2); EXPECT(t.get_stream(binary) == 0); EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(c1.back()), t.get_stream(c2.back()), t.get_stream(c3.back()), t.get_stream(i1)})); t.check_conflicts(m, {c1, c2, c3, {i1}}); } TEST_CASE(five_branches) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto c1 = chain(m, 5, unary_op{}, one); auto c2 = chain(m, 4, unary_op{}, one); auto c3 = chain(m, 3, unary_op{}, one); auto c4 = chain(m, 2, unary_op{}, one); auto i1 = m.add_instruction(unary_op{}, one); auto binary = m.add_instruction(nary_op{}, i1, c1.back(), c2.back(), c3.back(), c4.back()); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(i1) == 3); for(auto ins : c1) EXPECT(t.get_stream(ins) == 0); for(auto ins : c2) EXPECT(t.get_stream(ins) == 1); for(auto ins : c3) EXPECT(t.get_stream(ins) == 2); for(auto ins : c4) EXPECT(t.get_stream(ins) == 3); EXPECT(t.get_stream(binary) == 0); EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(c1.back()), t.get_stream(c2.back()), t.get_stream(c3.back()), t.get_stream(i1)})); t.check_conflicts(m, {c1, c2, c3, c4}); t.check_conflicts(m, {c1, c2, c3, {i1}}); } TEST_CASE(four_branches_eq) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onem1 = m.add_instruction(unary_op{}, one); auto onem2 = m.add_instruction(unary_op{}, one); auto onep3 = m.add_instruction(unary_op{}, one); auto onep4 = m.add_instruction(unary_op{}, one); auto binary = m.add_instruction(nary_op{}, onem1, onem2, onep3, onep4); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT( sorted( {t.get_stream(onem1), t.get_stream(onem2), t.get_stream(onep3), t.get_stream(onep4)}) == unique( {t.get_stream(onem1), t.get_stream(onem2), t.get_stream(onep3), t.get_stream(onep4)})); EXPECT(t.get_stream(binary) == 0); EXPECT( get_wait_for(binary) == get_wait_for( t.get_stream(binary), {t.get_stream(onem1), t.get_stream(onem2), t.get_stream(onep3), t.get_stream(onep4)})); t.check_conflicts(m, {{onem1}, {onem2}, {onep3}, {onep4}}); } TEST_CASE(seq_merge) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto c1 = chain(m, 2, unary_op{}, one); auto i1 = m.add_instruction(unary_op{}, one); auto binary1 = m.add_instruction(nary_op{}, i1, c1.back()); auto c2 = chain(m, 2, unary_op{}, binary1); auto i2 = m.add_instruction(unary_op{}, binary1); auto binary2 = m.add_instruction(nary_op{}, i2, c2.back()); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(i1) != t.get_stream(c1.back())); for(auto ins : c1) EXPECT(t.get_stream(ins) == t.get_stream(c1.back())); EXPECT(t.get_stream(binary1) == t.get_stream(c1.back())); EXPECT(get_wait_for(binary1) == get_wait_for(t.get_stream(binary1), {t.get_stream(c1.back()), t.get_stream(i1)})); t.check_conflicts(m, {c1, {i1}}); EXPECT(t.get_stream(i2) != t.get_stream(c2.back())); for(auto ins : c2) EXPECT(t.get_stream(ins) == t.get_stream(c2.back())); EXPECT(t.get_stream(binary2) == 0); EXPECT(get_wait_for(binary2) == get_wait_for(t.get_stream(binary2), {t.get_stream(c2.back()), t.get_stream(i2)})); t.check_conflicts(m, {c2, {i2}}); } TEST_CASE(par_merge) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto start1 = m.add_instruction(unary_op{}, one); auto c1 = chain(m, 3, unary_op{}, start1); auto i1 = m.add_instruction(unary_op{}, start1); auto binary1 = m.add_instruction(nary_op{}, i1, c1.back()); auto start2 = m.add_instruction(unary_op{}, one); auto c2 = chain(m, 2, unary_op{}, start2); auto i2 = m.add_instruction(unary_op{}, start2); auto binary2 = m.add_instruction(nary_op{}, i2, c2.back()); auto binary3 = m.add_instruction(nary_op{}, binary1, binary2); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(binary3) == 0); EXPECT(t.get_stream(i1) != t.get_stream(i2)); for(auto ins : c1) EXPECT(t.get_stream(ins) == 0); EXPECT(t.get_stream(binary1) == 0); EXPECT(get_wait_for(binary1) == get_wait_for(t.get_stream(binary1), {t.get_stream(c1.back()), t.get_stream(i1)})); t.check_conflicts(m, {c1, {i1}}); for(auto ins : c2) EXPECT(t.get_stream(ins) == t.get_stream(binary2)); EXPECT(t.get_stream(binary2) != t.get_stream(i1)); EXPECT(t.get_stream(binary2) != t.get_stream(i2)); EXPECT(get_wait_for(binary2) == get_wait_for(t.get_stream(binary2), {t.get_stream(c2.back()), t.get_stream(i2)})); t.check_conflicts(m, {c2, {i2}}); EXPECT(check_conflicts(m, binary1, binary2)); t.check_conflicts(m, {c1, {i1}, c2, {i2}}); } TEST_CASE(inner_par_merge) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto start1 = m.add_instruction(unary_op{}, one); auto c1 = chain(m, 3, unary_op{}, start1); auto i1 = m.add_instruction(unary_op{}, start1); auto binary1 = m.add_instruction(nary_op{}, i1, c1.back()); auto start2 = m.add_instruction(unary_op{}, one); auto c2 = chain(m, 2, unary_op{}, start2); auto i2 = m.add_instruction(unary_op{}, start2); auto binary2 = m.add_instruction(nary_op{}, i2, c2.back()); auto outer1 = m.add_instruction(unary_op{}, one); auto outer2 = m.add_instruction(unary_op{}, one); auto output = m.add_instruction(nary_op{}, binary1, binary2, outer1, outer2); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(output) == 0); EXPECT(get_wait_for(output) == get_wait_for(t.get_stream(output), {t.get_stream(binary1), t.get_stream(binary2), t.get_stream(outer1), t.get_stream(outer2)})); EXPECT(t.get_stream(outer1) != t.get_stream(outer2)); EXPECT(migraphx::contains({1, 2}, t.get_stream(outer1))); EXPECT(migraphx::contains({1, 2}, t.get_stream(outer2))); EXPECT(t.get_stream(i1) != t.get_stream(i2)); for(auto ins : c1) EXPECT(t.get_stream(ins) == 0); EXPECT(t.get_stream(binary1) == 0); EXPECT(get_wait_for(binary1) == get_wait_for(t.get_stream(binary1), {t.get_stream(c1.back()), t.get_stream(i1)})); t.check_conflicts(m, {c1, {i1}}); for(auto ins : c2) EXPECT(t.get_stream(ins) == t.get_stream(binary2)); EXPECT(t.get_stream(binary2) != t.get_stream(i1)); EXPECT(t.get_stream(binary2) != t.get_stream(i2)); EXPECT(get_wait_for(binary2) == get_wait_for(t.get_stream(binary2), {t.get_stream(c2.back()), t.get_stream(i2)})); t.check_conflicts(m, {c2, {i2}}); EXPECT(check_conflicts(m, binary1, binary2)); t.check_conflicts(m, {c1, {i1}, c2, {i2}, {outer1}, {outer2}}); } TEST_CASE(par_merge_multi_entry) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto start1 = m.add_instruction(unary_op{}, one); auto c1 = chain(m, 3, unary_op{}, start1); auto i1 = m.add_instruction(unary_op{}, start1); auto binary1 = m.add_instruction(nary_op{}, i1, c1.back()); auto two = m.add_literal(1); auto start2 = m.add_instruction(unary_op{}, two); auto c2 = chain(m, 2, unary_op{}, start2); auto i2 = m.add_instruction(unary_op{}, start2); auto binary2 = m.add_instruction(nary_op{}, i2, c2.back()); auto binary3 = m.add_instruction(nary_op{}, binary1, binary2); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(not t.has_stream(two)); EXPECT(t.get_stream(binary3) == 0); EXPECT(t.get_stream(i1) != t.get_stream(i2)); for(auto ins : c1) EXPECT(t.get_stream(ins) == 0); EXPECT(t.get_stream(binary1) == 0); EXPECT(get_wait_for(binary1) == get_wait_for(t.get_stream(binary1), {t.get_stream(c1.back()), t.get_stream(i1)})); t.check_conflicts(m, {c1, {i1}}); for(auto ins : c2) EXPECT(t.get_stream(ins) == t.get_stream(binary2)); EXPECT(t.get_stream(binary2) != t.get_stream(i1)); EXPECT(t.get_stream(binary2) != t.get_stream(i2)); EXPECT(get_wait_for(binary2) == get_wait_for(t.get_stream(binary2), {t.get_stream(c2.back()), t.get_stream(i2)})); t.check_conflicts(m, {c2, {i2}}); EXPECT(check_conflicts(m, binary1, binary2)); t.check_conflicts(m, {c1, {i1}, c2, {i2}}); } TEST_CASE(inner_split1) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto c1 = chain(m, 2, unary_op{}, one); auto i1 = m.add_instruction(unary_op{}, one); auto s1 = m.add_instruction(unary_op{}, c1); auto s2 = m.add_instruction(unary_op{}, c1); auto output = m.add_instruction(nary_op{}, i1, s1, s2); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(i1) != t.get_stream(s1)); EXPECT(t.get_stream(i1) != t.get_stream(s2)); for(auto ins : c1) EXPECT(t.get_stream(ins) != t.get_stream(i1)); EXPECT(t.get_stream(s1) != t.get_stream(s2)); EXPECT(t.get_stream(output) == 0); EXPECT( get_wait_for(output) == get_wait_for(t.get_stream(output), {t.get_stream(i1), t.get_stream(s1), t.get_stream(s2)})); // Either s1 or s2 has a wait depending on the sort order but not both EXPECT(get_wait_for(s1).empty() xor get_wait_for(s2).empty()); t.check_conflicts(m, {c1, {i1}, {s1}, {s2}}); } TEST_CASE(inner_split2) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto c1 = chain(m, 2, unary_op{}, one); auto i1 = m.add_instruction(unary_op{}, one); auto s1 = chain(m, 3, unary_op{}, c1.back()); auto s2 = chain(m, 4, unary_op{}, c1.back()); auto output = m.add_instruction(nary_op{}, i1, s1.back(), s2.back()); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(i1) != t.get_stream(s1.back())); EXPECT(t.get_stream(i1) != t.get_stream(s2.back())); for(auto ins : c1) EXPECT(t.get_stream(ins) != t.get_stream(i1)); EXPECT(t.get_stream(s1.back()) != t.get_stream(s2.back())); EXPECT(t.get_stream(output) == 0); EXPECT(get_wait_for(output) == get_wait_for(t.get_stream(output), {t.get_stream(i1), t.get_stream(s1.back()), t.get_stream(s2.back())})); EXPECT(get_wait_for(s1.front()) == get_wait_for({t.get_stream(c1.back())})); t.check_conflicts(m, {c1, {i1}, s1, s2}); } TEST_CASE(inception_resnet) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto input = m.add_instruction(unary_op{}, one); auto c1 = chain(m, 2, unary_op{}, input); auto i1 = m.add_instruction(unary_op{}, input); auto binary = m.add_instruction(nary_op{}, i1, c1.back()); auto output = m.add_instruction(nary_op{}, binary, input); t.run_pass(m); EXPECT(not t.has_stream(one)); EXPECT(t.get_stream(i1) != 0); for(auto ins : c1) EXPECT(t.get_stream(ins) == 0); EXPECT(t.get_stream(binary) == 0); EXPECT(get_wait_for(binary) == get_wait_for(t.get_stream(binary), {t.get_stream(c1.back()), t.get_stream(i1)})); EXPECT(t.get_stream(output) == 0); EXPECT(get_wait_for(output).empty()); t.check_conflicts(m, {c1, {i1}}); } TEST_CASE(dominate_conflicts) { scheduler t{}; migraphx::module m; auto one = m.add_literal(1); auto onep1 = m.add_instruction(unary_op{}, one); auto onep2 = m.add_instruction(unary_op{}, one); auto binary1 = m.add_instruction(nary_op{}, onep1, onep2); auto onep3 = m.add_instruction(unary_op{}, binary1); auto onep4 = m.add_instruction(unary_op{}, binary1); auto binary2 = m.add_instruction(nary_op{}, onep3, onep4); t.run_pass(m); EXPECT(t.get_stream(onep1) != t.get_stream(onep2)); EXPECT(t.get_stream(onep3) != t.get_stream(onep4)); EXPECT(get_wait_for(binary1) == get_wait_for(t.get_stream(binary1), {t.get_stream(onep1), t.get_stream(onep2)})); t.check_conflicts(m, {{onep1}, {onep2}}); t.check_conflicts(m, {{onep3}, {onep4}}); t.check_conflicts(m, {{onep1, onep2}, {onep3, onep4}}, false); t.check_conflicts(m, {{binary1}, {binary2}}, false); } TEST_CASE(inception1) { scheduler t{}; migraphx::module m; auto i1 = m.add_literal(0); auto i2 = m.add_literal(1); auto i3 = m.add_literal(1); auto i4 = m.add_literal(2); auto i7 = m.add_instruction(nary_op{"i7"}, i1, i4, i3, i2); auto i8 = m.add_literal(2); auto i9 = m.add_instruction(migraphx::make_op("identity"), i8); auto i10 = m.add_literal(1); auto i11 = m.add_instruction(nary_op{"i11"}, i7, i9, i10); auto i12 = m.add_literal(2); auto i13 = m.add_instruction(migraphx::make_op("identity"), i12); auto i14 = m.add_literal(1); auto i15 = m.add_literal(1); auto i16 = m.add_literal(2); auto i17 = m.add_instruction(nary_op{"i17"}, i11, i16, i15, i13, i14); auto i18 = m.add_literal(2); auto i19 = m.add_instruction(migraphx::make_op("identity"), i18); auto i20 = m.add_literal(1); auto i21 = m.add_literal(1); auto i22 = m.add_literal(2); auto i23 = m.add_instruction(nary_op{"i23"}, i17, i22, i21, i19, i20); auto i24 = m.add_literal(1); auto i25 = m.add_instruction(nary_op{"i25"}, i23, i24); auto i26 = m.add_literal(2); auto i27 = m.add_instruction(migraphx::make_op("identity"), i26); auto i28 = m.add_literal(1); auto i29 = m.add_literal(1); auto i30 = m.add_literal(2); auto i31 = m.add_instruction(nary_op{"i31"}, i25, i30, i29, i27, i28); auto i32 = m.add_literal(2); auto i33 = m.add_instruction(migraphx::make_op("identity"), i32); auto i34 = m.add_literal(1); auto i35 = m.add_literal(1); auto i36 = m.add_literal(2); auto i37 = m.add_instruction(nary_op{"i37"}, i31, i36, i35, i33, i34); auto i38 = m.add_literal(1); auto i39 = m.add_instruction(nary_op{"i39"}, i37, i38); auto i41 = m.add_literal(2); auto i42 = m.add_instruction(migraphx::make_op("identity"), i41); auto i43 = m.add_literal(1); auto i44 = m.add_literal(1); auto i45 = m.add_literal(2); auto i48 = m.add_instruction(nary_op{"i48"}, i39, i45, i44, i42, i43); auto i49 = m.add_literal(2); auto i50 = m.add_instruction(migraphx::make_op("identity"), i49); auto i51 = m.add_literal(1); auto i52 = m.add_literal(1); auto i53 = m.add_literal(2); auto i54 = m.add_instruction(nary_op{"i54"}, i48, i53, i52, i50, i51); auto i55 = m.add_literal(1); auto i56 = m.add_instruction(migraphx::make_op("identity"), i55); auto i57 = m.add_literal(2); auto i58 = m.add_instruction(migraphx::make_op("identity"), i57); auto i59 = m.add_literal(1); auto i60 = m.add_literal(2); auto i61 = m.add_instruction(nary_op{"i61"}, i54, i60, i59, i58, i56); auto i62 = m.add_literal(2); auto i63 = m.add_instruction(migraphx::make_op("identity"), i62); auto i64 = m.add_literal(1); auto i65 = m.add_literal(1); auto i66 = m.add_literal(2); auto i69 = m.add_instruction(nary_op{"i69"}, i39, i66, i65, i63, i64); auto i70 = m.add_instruction(migraphx::make_op("identity"), i55); auto i71 = m.add_literal(2); auto i72 = m.add_instruction(migraphx::make_op("identity"), i71); auto i73 = m.add_literal(1); auto i74 = m.add_literal(2); auto i75 = m.add_instruction(nary_op{"i75"}, i69, i74, i73, i72, i70); auto i77 = m.add_literal(1); auto i80 = m.add_instruction(nary_op{"i80"}, i39, i77); auto i81 = m.add_instruction(migraphx::make_op("identity"), i55); auto i82 = m.add_literal(2); auto i83 = m.add_instruction(migraphx::make_op("identity"), i82); auto i84 = m.add_literal(1); auto i85 = m.add_literal(2); auto i86 = m.add_instruction(nary_op{"i86"}, i80, i85, i84, i83, i81); auto i88 = m.add_instruction(migraphx::make_op("identity"), i55); auto i89 = m.add_literal(2); auto i90 = m.add_instruction(migraphx::make_op("identity"), i89); auto i91 = m.add_literal(1); auto i92 = m.add_literal(2); auto i94 = m.add_instruction(nary_op{"i94"}, i39, i92, i91, i90, i88); auto i96 = m.add_instruction(migraphx::make_op("identity"), i55, i94, i75, i61, i86); auto i97 = m.add_literal(2); auto i98 = m.add_instruction(migraphx::make_op("identity"), i97); auto i99 = m.add_literal(3); auto i100 = m.add_literal(1); auto i101 = m.add_literal(2); auto output = m.add_instruction(nary_op{"output"}, i96, i101, i100, i98, i99); t.run_pass(m); EXPECT(t.get_streams({i7, i11, i17, i23, i25, i31, i37, i39}) == t.get_streams({i7, i7, i7, i7, i7, i7, i7, i7})); EXPECT(t.get_streams({i48, i54, i61, output}) == t.get_streams({output, output, output, output})); EXPECT(t.get_streams({i80, i86}) == t.get_streams({i80, i80})); EXPECT(t.get_streams({i69, i75}) == t.get_streams({i69, i69})); EXPECT(t.get_stream(i7) != t.get_stream(i80)); EXPECT(t.get_stream(i69) != t.get_stream(i80)); EXPECT(t.get_stream(i69) != t.get_stream(i7)); EXPECT(t.get_stream(output) != t.get_stream(i69)); EXPECT(t.get_stream(output) != t.get_stream(i80)); EXPECT(get_wait_for(i80) == get_wait_for({t.get_stream(i39)})); EXPECT(get_wait_for(i69) == get_wait_for({t.get_stream(i39)})); EXPECT(get_wait_for(i94) == get_wait_for({t.get_stream(i39)})); EXPECT( get_wait_for(output) == get_wait_for(t.get_stream(output), {t.get_stream(i94), t.get_stream(i75), t.get_stream(i61), t.get_stream(i86)})); t.check_conflicts(m, {{i80, i86}, {i69, i75}, {i48, i54, i61}, {i94}}); } TEST_CASE(if_pl_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape xs{migraphx::shape::float_type, {2, 3}}; migraphx::shape ys{migraphx::shape::float_type, {3, 3}}; std::vector datax = {1, 2, 3, 4, 5, 6}; std::vector datay = {8, 7, 6, 5, 4, 3, 2, 1, 0}; auto lx = mm->add_literal(migraphx::literal(xs, datax)); auto ly = mm->add_literal(migraphx::literal(ys, datay)); auto cond = mm->add_parameter("cond", cond_s); auto x = mm->add_parameter("x", xs); auto y = mm->add_parameter("y", ys); auto* then_mod = p.create_module("If_5_if"); auto l1 = then_mod->add_literal(migraphx::literal(ys, datay)); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), x, lx); then_mod->add_return({a1, l1}); auto* else_mod = p.create_module("If_5_else"); auto l2 = else_mod->add_literal(migraphx::literal(xs, datax)); auto a2 = else_mod->add_instruction(migraphx::make_op("mul"), y, ly); else_mod->add_return({l2, a2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r2 = mm->add_return({ret}); scheduler t{}; auto sub_modules = p.get_modules(); std::reverse(sub_modules.begin(), sub_modules.end()); for(const auto& smod : sub_modules) { t.run_pass(*smod); } EXPECT(t.has_stream(ret) == false); EXPECT(t.has_stream(r2) == false); } TEST_CASE(unused_param_test) { migraphx::module mm; migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto x = mm.add_parameter("x", s); auto y = mm.add_parameter("y", s); auto z = mm.add_parameter("z", s); auto r = mm.add_instruction(migraphx::make_op("add"), x, y); mm.add_return({r}); scheduler t{}; t.run_pass(mm); EXPECT(t.has_stream(z) == false); EXPECT(t.has_stream(r) == false); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/serialize_program.cpp000066400000000000000000000112251510465702400217420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include "test.hpp" #include #include static migraphx::program create_program() { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::int32_type}); auto two = mm->add_literal(2); auto add = mm->add_instruction(migraphx::make_op("add"), x, two); mm->add_return({add}); return p; } TEST_CASE(as_value) { migraphx::program p1 = create_program(); migraphx::program p2; p2.from_value(p1.to_value()); EXPECT(p1.sort() == p2.sort()); } TEST_CASE(as_msgpack) { migraphx::file_options options; options.format = "msgpack"; migraphx::program p1 = create_program(); std::vector buffer = migraphx::save_buffer(p1, options); migraphx::program p2 = migraphx::load_buffer(buffer, options); EXPECT(p1.sort() == p2.sort()); } TEST_CASE(as_json) { migraphx::file_options options; options.format = "json"; migraphx::program p1 = create_program(); std::vector buffer = migraphx::save_buffer(p1, options); migraphx::program p2 = migraphx::load_buffer(buffer, options); EXPECT(p1.sort() == p2.sort()); } TEST_CASE(as_file) { std::string filename = "migraphx_program.mxr"; migraphx::program p1 = create_program(); migraphx::save(p1, filename); migraphx::program p2 = migraphx::load(filename); std::remove(filename.c_str()); EXPECT(p1.sort() == p2.sort()); } TEST_CASE(compiled) { migraphx::program p1 = create_program(); p1.compile(migraphx::make_target("ref")); std::vector buffer = migraphx::save_buffer(p1); migraphx::program p2 = migraphx::load_buffer(buffer); EXPECT(p1.sort() == p2.sort()); } TEST_CASE(unknown_format) { migraphx::file_options options; options.format = "???"; EXPECT(test::throws([&] { migraphx::save_buffer(create_program(), options); })); EXPECT(test::throws([&] { migraphx::load_buffer(std::vector{}, options); })); } TEST_CASE(program_with_module) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", sd); std::vector one(sd.elements(), 1); std::vector two(sd.elements(), 2); auto* then_smod = p.create_module("then_smod"); auto l1 = then_smod->add_literal(migraphx::literal{sd, one}); auto r1 = then_smod->add_instruction(migraphx::make_op("add"), x, l1); then_smod->add_return({r1}); auto* else_smod = p.create_module("else_smod"); auto l2 = else_smod->add_literal(migraphx::literal{sd, two}); auto r2 = else_smod->add_instruction(migraphx::make_op("mul"), x, l2); else_smod->add_return({r2}); migraphx::shape s_cond{migraphx::shape::bool_type, {1}}; auto cond = mm->add_parameter("cond", s_cond); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_smod, else_smod}); mm->add_return({ret}); migraphx::program p1 = p; auto v = p.to_value(); auto v1 = p1.to_value(); EXPECT(v == v1); std::stringstream ss; p.print_cpp(ss); std::stringstream ss1; p1.print_cpp(ss1); EXPECT(ss.str() == ss1.str()); migraphx::program p2; p2.from_value(v); EXPECT(p1.sort() == p2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/serialize_test.cpp000066400000000000000000000132121510465702400212500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include struct empty_type { }; struct reflectable_type { enum simple_enum { simple1, simple2, simple3 }; enum class class_enum { class1, class2, class3 }; std::vector ints = {}; std::string name = ""; float fvalue = 0.0; empty_type et{}; simple_enum se = simple1; class_enum ce = class_enum::class1; struct nested_type { int value; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.value, "value")); } }; std::vector nested_types = {}; std::tuple tuple_items = std::make_tuple(0, nested_type{0}, ""); migraphx::optional opt_value = migraphx::nullopt; template static auto reflect(Self& self, F f) { return migraphx::pack(f(self.ints, "ints"), f(self.name, "name"), f(self.fvalue, "fvalue"), f(self.et, "et"), f(self.se, "se"), f(self.ce, "ce"), f(self.nested_types, "nested_types"), f(self.tuple_items, "tuple_items")); } }; TEST_CASE(serialize_reflectable_type) { reflectable_type t1{{1, 2}, "hello", 1.0, {}, reflectable_type::simple1, reflectable_type::class_enum::class2, {{1}, {2}}, {5, {4}, "hello"}, {migraphx::nullopt}}; migraphx::value v1 = migraphx::to_value(t1); reflectable_type t2 = migraphx::from_value(v1); migraphx::value v2 = migraphx::to_value(t2); migraphx::value v3 = migraphx::to_value(reflectable_type{}); EXPECT(v1 == v2); EXPECT(v1 != v3); EXPECT(v2 != v3); } TEST_CASE(serialize_empty_array) { std::vector ints = {}; migraphx::value v = migraphx::to_value(ints); EXPECT(v.is_array()); EXPECT(v.empty()); v.push_back(1); EXPECT(v.size() == 1); EXPECT(v.front().to() == 1); } struct empty_struct { template static auto reflect(Self&, F) { return migraphx::pack(); } }; TEST_CASE(serialize_empty_struct) { empty_struct es{}; migraphx::value v = migraphx::to_value(es); EXPECT(v.is_object()); EXPECT(v.empty()); v["a"] = 1; EXPECT(v.size() == 1); EXPECT(v.at("a").to() == 1); } TEST_CASE(serialize_empty_optional) { migraphx::optional x{}; migraphx::value v = migraphx::to_value(x); EXPECT(v.is_null()); } TEST_CASE(serialize_optional) { migraphx::optional x{2}; migraphx::value v = migraphx::to_value(x); EXPECT(v.is_int64()); EXPECT(v.to() == 2); } TEST_CASE(serialize_map) { std::map m = {{1, 1}, {2, 4}, {3, 9}}; migraphx::value v = migraphx::to_value(m); EXPECT(v.is_array()); EXPECT(m == migraphx::from_value>(v)); } TEST_CASE(serialize_invalid_map1) { migraphx::value v = {{1, 1}, {2, 4}, {3, 9, 27}}; EXPECT(test::throws([&] { migraphx::from_value>(v); })); } TEST_CASE(serialize_invalid_map2) { migraphx::value v = 2; EXPECT(test::throws([&] { migraphx::from_value>(v); })); } TEST_CASE(serialize_struct_to_map) { migraphx::value v = migraphx::to_value(reflectable_type{}); EXPECT(v.is_object()); std::map m; migraphx::from_value(v, m); EXPECT(m.size() == v.size()); } TEST_CASE(serialize_vector_value) { std::vector x = {{1}, {2}}; migraphx::value v = migraphx::to_value(x); EXPECT(x == migraphx::from_value>(v)); } TEST_CASE(from_value_binary) { std::vector data(10); std::iota(data.begin(), data.end(), 0); migraphx::value v = migraphx::value::binary{data}; auto out = migraphx::from_value(v); EXPECT(out == data); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/shape_test.cpp000066400000000000000000001166111510465702400203700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include "test.hpp" TEST_CASE(test_shape_default) { migraphx::shape s{}; EXPECT(s.elements() == 0); EXPECT(s.bytes() == 0); } TEST_CASE(test_dyn_4arg_constructor) { migraphx::shape s0{migraphx::shape::float_type, {1, 4, 4}, {4, 4, 4}, {{}, {}, {}}}; migraphx::shape s1{migraphx::shape::float_type, {1, 4, 4}, {4, 4, 4}, {}}; std::vector expected_dyn_dims = {{1, 4}, {4, 4}, {4, 4}}; EXPECT(s0.dynamic()); EXPECT(s0.dyn_dims() == expected_dyn_dims); EXPECT(s1.dynamic()); EXPECT(s1.dyn_dims() == expected_dyn_dims); } TEST_CASE(test_shape_assign) { migraphx::shape s1{migraphx::shape::float_type, {100, 32, 8, 8}}; migraphx::shape s2 = s1; // NOLINT EXPECT(s1 == s2); EXPECT(not(s1 != s2)); } TEST_CASE(test_shape_packed_default) { migraphx::shape s{migraphx::shape::float_type, {2, 2}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_standard) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}, {6, 3, 1}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_standard_singleton_dim) { migraphx::shape s{migraphx::shape::float_type, {5, 1, 8}, {8, 4, 1}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_standard_stray_singleton_dim) { // A shape can be transposed (nonzero strides out of order) but still be considered // standard if the only out-of-order strides are on axes with a length of 1. migraphx::shape s{migraphx::shape::float_type, {5, 1, 1, 8}, {8, 3, 4, 1}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_min_max_opt) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}, {6, 3, 1}}; EXPECT(s.min_lens() == s.lens()); EXPECT(s.max_lens() == s.lens()); EXPECT(s.opt_lens().empty()); } TEST_CASE(test_shape_dynamic_fixed) { migraphx::shape s{migraphx::shape::float_type, {{2, 2}, {2, 2}, {3, 3}}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); EXPECT(s.dynamic()); EXPECT(s.dyn_dims().size() == 3); EXPECT(s.dyn_dims().at(0).is_fixed()); EXPECT(not s.dyn_dims().at(0).has_optimal()); EXPECT(s.min_lens() == std::vector{2, 2, 3}); EXPECT(s.max_lens() == std::vector{2, 2, 3}); std::vector> e_opt_lens = {{}, {}, {}}; EXPECT(s.opt_lens() == e_opt_lens); EXPECT(s.bytes() == 2 * 2 * 3 * sizeof(float)); } TEST_CASE(test_shape_dynamic_not_fixed) { using migraphx::shape; std::vector dims = {}; dims.push_back(shape::dynamic_dimension{2, 5, {2}}); dims.push_back(shape::dynamic_dimension{2, 8}); migraphx::shape s{migraphx::shape::float_type, dims}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); EXPECT(s.dynamic()); EXPECT(s.dyn_dims().size() == 2); EXPECT(not s.dyn_dims().at(0).is_fixed()); EXPECT(s.dyn_dims().at(0).has_optimal()); EXPECT(s.min_lens() == std::vector{2, 2}); EXPECT(s.max_lens() == std::vector{5, 8}); EXPECT(s.opt_lens() == std::vector>{{2}, {}}); EXPECT(s.bytes() == 5 * 8 * sizeof(float)); } TEST_CASE(test_shape_dynamic_compares) { using migraphx::shape; auto a = shape::dynamic_dimension{2, 5, {2}}; auto c = shape::dynamic_dimension{2, 5, {2}}; auto d = shape::dynamic_dimension{3, 8}; EXPECT(a == c); EXPECT(a != d); migraphx::shape s0{shape::float_type, {a, d}}; migraphx::shape s1 = s0; migraphx::shape s2{shape::float_type, {a, d}}; migraphx::shape s3{shape::int32_type, {a}}; EXPECT(s0 == s1); EXPECT(s0 == s2); EXPECT(s0 != s3); std::stringstream ss0; std::stringstream ss1; std::stringstream ss3; ss0 << s0; ss1 << s1; ss3 << s3; EXPECT(ss0.str() == ss1.str()); EXPECT(ss0.str() != ss3.str()); } TEST_CASE(dynamic_shape_element_space) { migraphx::shape s{migraphx::shape::float_type, {{1, 10}, {3, 20, {3}}}}; EXPECT(s.element_space() == 200); } TEST_CASE(dynamic_shape_element_space_overflow0) { std::size_t max_val = std::numeric_limits::max(); migraphx::shape s{migraphx::shape::float_type, {{0, max_val}, {0, max_val}}}; EXPECT(s.element_space() == max_val); } TEST_CASE(dynamic_shape_element_space_overflow1) { std::size_t max_val = std::numeric_limits::max(); std::size_t large_val = max_val / 10; migraphx::shape s{migraphx::shape::float_type, {{0, large_val}, {0, large_val}}}; EXPECT(s.element_space() == max_val); } TEST_CASE(dynamic_shape_element_space_zero) { std::size_t large_val = std::numeric_limits::max() / 10; migraphx::shape s{migraphx::shape::float_type, {{0, large_val}, {0, large_val}, {0, 0}}}; EXPECT(s.element_space() == 0); } TEST_CASE(dynamic_dimension_size_t_compares) { using migraphx::shape; auto a = shape::dynamic_dimension{2, 2, {2}}; EXPECT(a == 2); EXPECT(a != 3); EXPECT(static_cast(2) == a); EXPECT(static_cast(3) != a); auto b = shape::dynamic_dimension{2, 4}; EXPECT(b != 2); EXPECT(static_cast(2) != b); } TEST_CASE(dynamic_dimension_add_sub_fixed) { using migraphx::shape; auto a = shape::dynamic_dimension{2, 5, {2}}; a += 3; EXPECT(a == shape::dynamic_dimension{5, 8, {5}}); a -= 3; EXPECT(a == shape::dynamic_dimension{2, 5, {2}}); auto b = shape::dynamic_dimension{3, 6, {3}}; EXPECT((a + 1) == b); EXPECT((1 + a) == b); EXPECT((b - 1) == a); auto c = shape::dynamic_dimension{4, 7, {4}}; EXPECT((a + 2) == c); EXPECT((2 + a) == c); EXPECT((c - 2) == a); auto d = shape::dynamic_dimension{4, 8}; auto e = shape::dynamic_dimension{2, 6}; EXPECT((d - 2) == e); EXPECT((e + 2) == d); EXPECT((2 + e) == d); } TEST_CASE(dynamic_dimension_mul_fixed) { using migraphx::shape; auto a = shape::dynamic_dimension{2, 5, {2}}; a *= 3; EXPECT(a == shape::dynamic_dimension{6, 15, {6}}); auto b = shape::dynamic_dimension{3, 6, {3}}; EXPECT((b * 1) == b); EXPECT((b * 0) == shape::dynamic_dimension{0, 0, {0}}); } TEST_CASE(dynamic_dimension_intersection) { using migraphx::shape; auto a = shape::dynamic_dimension{2, 5, {2, 5}}; auto b = shape::dynamic_dimension{3, 4}; auto aib = a.intersection(b); auto bia = b.intersection(a); EXPECT(aib.has_value()); EXPECT(bia.has_value()); EXPECT(aib.value() == shape::dynamic_dimension{3, 4}); EXPECT(aib.value() == bia.value()); auto c = shape::dynamic_dimension{3, 8}; auto cia = c.intersection(a); EXPECT(cia.value() == shape::dynamic_dimension{3, 5}); auto d = shape::dynamic_dimension{8, 10}; auto dib = d.intersection(b); EXPECT(not dib.has_value()); auto e = shape::dynamic_dimension{4, 10}; auto eib = e.intersection(b); EXPECT(eib.value() == shape::dynamic_dimension{4, 4}); auto f = shape::dynamic_dimension{0, std::numeric_limits::max()}; auto fib = f.intersection(b); EXPECT(fib.value() == shape::dynamic_dimension{3, 4}); } TEST_CASE(dynamic_dimension_serialize) { using migraphx::shape; auto a = shape::dynamic_dimension{2, 5, {2, 3}}; auto b = shape::dynamic_dimension{3, 6, {3}}; auto v1 = migraphx::to_value(a); auto v2 = migraphx::to_value(b); EXPECT(v1 != v2); auto c = migraphx::from_value(v1); EXPECT(a == c); auto d = migraphx::from_value(v2); EXPECT(b == d); } TEST_CASE(test_shape_dynamic_errors) { using migraphx::shape; std::vector dims = {}; dims.push_back(shape::dynamic_dimension{2, 5, {2}}); dims.push_back(shape::dynamic_dimension{2, 8}); migraphx::shape s{shape::float_type, dims}; EXPECT(test::throws([&] { s.elements(); })); EXPECT(test::throws([&] { s.index({0, 1}); })); EXPECT(test::throws([&] { s.index(1); })); EXPECT(test::throws([&] { s.index(std::vector{0, 1}); })); EXPECT(test::throws([&] { s.with_lens({3, 5}); })); EXPECT(test::throws([&] { s.with_lens(shape::float_type, {3, 5}); })); EXPECT(test::throws([&] { s.lens(); })); EXPECT(test::throws([&] { s.strides(); })); } TEST_CASE(test_shape_static_dyn_dim_error) { using migraphx::shape; migraphx::shape s{shape::float_type, {2, 3, 4}}; EXPECT(test::throws([&] { s.dyn_dims(); })); } TEST_CASE(test_shape_dynamic_serialize) { using migraphx::shape; std::vector dims1 = {}; dims1.push_back(shape::dynamic_dimension{2, 5, {2}}); dims1.push_back(shape::dynamic_dimension{2, 8}); migraphx::shape s1{shape::float_type, dims1}; auto v1 = migraphx::to_value(s1); std::vector dims2 = {}; dims2.push_back(shape::dynamic_dimension{2, 5, {2}}); migraphx::shape s2{shape::uint64_type, dims2}; auto v2 = migraphx::to_value(s2); EXPECT(v1 != v2); auto s3 = migraphx::from_value(v1); EXPECT(s3 == s1); auto s4 = migraphx::from_value(v2); EXPECT(s4 == s2); EXPECT(s3 != s4); } TEST_CASE(any_of_dynamic_true) { std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {3, 4, 5}}); migraphx::shape s0{sub_shapes}; EXPECT(s0.any_of_dynamic()); sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 1}, {4, 4}}}); sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {3, 4, 5}}); migraphx::shape s1{sub_shapes}; EXPECT(s1.any_of_dynamic()); } TEST_CASE(any_of_dynamic_false) { std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {1, 4}}); sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {3, 4, 5}}); migraphx::shape s{sub_shapes}; EXPECT(not s.any_of_dynamic()); } TEST_CASE(test_shape_packed) { migraphx::shape s{migraphx::shape::float_type, {2, 2}, {2, 1}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_ndim_static) { migraphx::shape s0{migraphx::shape::float_type, {2, 2}}; EXPECT(s0.ndim() == 2); migraphx::shape s1{migraphx::shape::float_type, {1, 2, 4, 4}}; EXPECT(s1.ndim() == 4); migraphx::shape s2{migraphx::shape::float_type, {2, 4, 4, 1, 3}}; EXPECT(s2.ndim() == 5); } TEST_CASE(test_shape_ndim_dyn) { migraphx::shape s0{migraphx::shape::float_type, {{2, 2}, {2, 2}}}; EXPECT(s0.ndim() == 2); migraphx::shape s1{migraphx::shape::float_type, {{1, 1}, {2, 4}, {2, 4}, {2, 4}}}; EXPECT(s1.ndim() == 4); migraphx::shape s2{migraphx::shape::float_type, {{1, 1}, {2, 4}, {2, 4}, {1, 1}, {3, 3}}}; EXPECT(s2.ndim() == 5); } TEST_CASE(test_shape_non_packed_single_dim) { migraphx::shape s{migraphx::shape::float_type, {1, 64, 35, 35}, {156800, 1225, 35, 1}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_transposed1) { migraphx::shape s{migraphx::shape::float_type, {2, 2}, {1, 2}}; EXPECT(not s.standard()); EXPECT(s.packed()); EXPECT(s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_transposed2) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 1, 1, 2}, {2, 2, 2, 2, 1}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_static_to_dynamic) { migraphx::shape s0{migraphx::shape::float_type, {1, 2, 4, 4}}; migraphx::shape s1 = s0.to_dynamic(); migraphx::shape s2{migraphx::shape::float_type, {{1, 1}, {2, 2}, {4, 4}, {4, 4}}}; EXPECT(s1 == s2); } TEST_CASE(test_shape_dyn_to_dynamic) { migraphx::shape s0{migraphx::shape::float_type, {{1, 1}, {2, 4}, {2, 4}, {2, 4}}}; migraphx::shape s1 = s0.to_dynamic(); EXPECT(s0 == s1); } TEST_CASE(test_shape_subshapes_to_dynamic) { std::vector sub_shapes0 = {}; sub_shapes0.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); sub_shapes0.push_back(migraphx::shape{migraphx::shape::float_type, {3, 4, 5}}); migraphx::shape s0{sub_shapes0}; migraphx::shape s1 = s0.to_dynamic(); std::vector sub_shapes1 = {}; sub_shapes1.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); sub_shapes1.push_back(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {4, 4}, {5, 5}}}); migraphx::shape s2{sub_shapes1}; EXPECT(s1 == s2); } TEST_CASE(test_shape_dyn_to_static) { migraphx::shape s0{migraphx::shape::float_type, {{1, 1}, {2, 2}, {2, 10}, {2, 10}}}; migraphx::shape s1 = s0.to_static(4); migraphx::shape s2{migraphx::shape::float_type, {1, 2, 4, 4}}; EXPECT(s1 == s2); } TEST_CASE(test_shape_static_to_static) { migraphx::shape s0{migraphx::shape::float_type, {1, 2, 4, 4}}; migraphx::shape s1 = s0.to_static(8); EXPECT(s0 == s1); } TEST_CASE(test_shape_subshapes_to_static) { std::vector sub_shapes0 = {}; sub_shapes0.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); sub_shapes0.push_back(migraphx::shape{migraphx::shape::float_type, {3, 4, 5}}); migraphx::shape s0{sub_shapes0}; migraphx::shape s1 = s0.to_static(3); std::vector sub_shapes1 = {}; sub_shapes1.push_back(migraphx::shape{migraphx::shape::float_type, {3, 4}}); sub_shapes1.push_back(migraphx::shape{migraphx::shape::float_type, {3, 4, 5}}); migraphx::shape s2{sub_shapes1}; EXPECT(s1 == s2); } TEST_CASE(test_shape_overlap) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}, {6, 3, 2}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_overlap2) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}, {6, 2, 1}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_overlap3) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}, {4, 2, 1}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); } TEST_CASE(test_shape_scalar1) { migraphx::shape s{migraphx::shape::float_type}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_scalar2) { migraphx::shape s{migraphx::shape::float_type, {1}, {0}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_scalar_broadcast) { migraphx::shape s{migraphx::shape::float_type, {1, 2, 3, 3}, {0, 0, 0, 0}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_broadcasted) { migraphx::shape s{migraphx::shape::float_type, {2, 2}, {1, 0}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_broadcasted2) { migraphx::shape s{migraphx::shape::float_type, {1, 2}, {0, 1}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_broadcasted3) { migraphx::shape s{migraphx::shape::float_type, {3, 2}, {0, 1}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_broadcasted4) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}, {6, 0, 1}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_broadcasted5) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}, {1, 0, 6}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_step_broadcasted) { migraphx::shape s{migraphx::shape::float_type, {2, 2}, {0, 3}}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(s.broadcasted()); } TEST_CASE(test_shape_default_copy) { migraphx::shape s1{}; migraphx::shape s2{}; EXPECT(s1 == s2); EXPECT(not(s1 != s2)); } TEST_CASE(test_shape_normalize_standard1) { migraphx::shape s{migraphx::shape::float_type, {2, 2, 3}, {6, 3, 1}}; EXPECT(s.standard()); auto n = s.normalize_standard(); EXPECT(n == s); } TEST_CASE(test_shape_normalize_standard2) { migraphx::shape s{migraphx::shape::float_type, {1, 64, 35, 35}, {156800, 1225, 35, 1}}; EXPECT(s.standard()); auto n = s.normalize_standard(); EXPECT(n.standard()); EXPECT(n != s); EXPECT(n.lens() == s.lens()); EXPECT(n.type() == s.type()); } TEST_CASE(test_shape_normalize_standard3) { migraphx::shape s{migraphx::shape::float_type, {2, 2}, {1, 2}}; EXPECT(not s.standard()); auto n = s.normalize_standard(); EXPECT(n == s); } TEST_CASE(test_shape_normalize_scalar1) { migraphx::shape s{migraphx::shape::float_type}; EXPECT(s.standard()); EXPECT(s.scalar()); auto n = s.normalize_standard(); EXPECT(n != s); EXPECT(n.standard()); EXPECT(not n.scalar()); } TEST_CASE(test_shape_normalize_scalar2) { migraphx::shape s{migraphx::shape::float_type, {2, 2}, {0, 0}}; EXPECT(not s.standard()); EXPECT(s.scalar()); auto n = s.normalize_standard(); EXPECT(n == s); } TEST_CASE(test_shape4) { migraphx::shape s{migraphx::shape::float_type, {100, 32, 8, 8}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); EXPECT(s.type() == migraphx::shape::float_type); EXPECT(s.lens()[0] == 100); EXPECT(s.lens()[1] == 32); EXPECT(s.lens()[2] == 8); EXPECT(s.lens()[3] == 8); EXPECT(s.strides()[0] == s.lens()[1] * s.strides()[1]); EXPECT(s.strides()[1] == s.lens()[2] * s.strides()[2]); EXPECT(s.strides()[2] == s.lens()[3] * s.strides()[3]); EXPECT(s.strides()[3] == 1); EXPECT(s.elements() == 100 * 32 * 8 * 8); EXPECT(s.bytes() == 100 * 32 * 8 * 8 * sizeof(float)); EXPECT(s.index({0, 0, 0, 0}) == 0); EXPECT(s.index({0, 0, 0, 1}) == 1); EXPECT(s.index({0, 0, 0, 0}) == s.index(0)); EXPECT(s.index({0, 0, 0, 1}) == s.index(1)); EXPECT(s.index({0, 0, 1, 0}) == s.index(8)); EXPECT(s.index({0, 1, 0, 0}) == s.index(8 * 8)); EXPECT(s.index({1, 0, 0, 0}) == s.index(8 * 8 * 32)); EXPECT(s.index(0) == 0); EXPECT(s.index(1) == 1); EXPECT(s.index(8) == 8); EXPECT(s.index(8 * 8) == 8 * 8); EXPECT(s.index(8 * 8 * 32) == 8 * 8 * 32); EXPECT(s.index(s.elements() - 1) == s.elements() - 1); } TEST_CASE(test_shape42) { migraphx::shape s{migraphx::shape::float_type, {100, 32, 8, 8}, {2048, 64, 8, 1}}; EXPECT(s.standard()); EXPECT(s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); EXPECT(s.type() == migraphx::shape::float_type); EXPECT(s.lens()[0] == 100); EXPECT(s.lens()[1] == 32); EXPECT(s.lens()[2] == 8); EXPECT(s.lens()[3] == 8); EXPECT(s.strides()[0] == s.lens()[1] * s.strides()[1]); EXPECT(s.strides()[1] == s.lens()[2] * s.strides()[2]); EXPECT(s.strides()[2] == s.lens()[3] * s.strides()[3]); EXPECT(s.strides()[3] == 1); EXPECT(s.elements() == 100 * 32 * 8 * 8); EXPECT(s.bytes() == 100 * 32 * 8 * 8 * sizeof(float)); EXPECT(s.index({0, 0, 0, 0}) == 0); EXPECT(s.index({0, 0, 0, 1}) == 1); EXPECT(s.index({0, 0, 0, 0}) == s.index(0)); EXPECT(s.index({0, 0, 0, 1}) == s.index(1)); EXPECT(s.index({0, 0, 1, 0}) == s.index(8)); EXPECT(s.index({0, 1, 0, 0}) == s.index(8 * 8)); EXPECT(s.index({1, 0, 0, 0}) == s.index(8 * 8 * 32)); EXPECT(s.index(0) == 0); EXPECT(s.index(1) == 1); EXPECT(s.index(8) == 8); EXPECT(s.index(8 * 8) == 8 * 8); EXPECT(s.index(8 * 8 * 32) == 8 * 8 * 32); EXPECT(s.index(s.elements() - 1) == s.elements() - 1); } TEST_CASE(test_shape4_transposed) { migraphx::shape s{migraphx::shape::float_type, {32, 100, 8, 8}, {64, 2048, 8, 1}}; EXPECT(s.transposed()); EXPECT(s.packed()); EXPECT(not s.standard()); EXPECT(not s.broadcasted()); EXPECT(s.type() == migraphx::shape::float_type); EXPECT(s.lens()[0] == 32); EXPECT(s.lens()[1] == 100); EXPECT(s.lens()[2] == 8); EXPECT(s.lens()[3] == 8); EXPECT(s.strides()[0] == 64); EXPECT(s.strides()[1] == 2048); EXPECT(s.strides()[2] == 8); EXPECT(s.strides()[3] == 1); EXPECT(s.elements() == 100 * 32 * 8 * 8); EXPECT(s.bytes() == 100 * 32 * 8 * 8 * sizeof(float)); EXPECT(s.index({0, 0, 0, 0}) == 0); EXPECT(s.index({0, 0, 0, 1}) == 1); EXPECT(s.index({0, 0, 0, 0}) == s.index(0)); EXPECT(s.index({0, 0, 0, 1}) == s.index(1)); EXPECT(s.index({0, 0, 1, 0}) == s.index(8)); EXPECT(s.index({0, 1, 0, 0}) == s.index(8 * 8)); EXPECT(s.index({1, 0, 0, 0}) == s.index(8 * 8 * 100)); EXPECT(s.index(0) == 0); EXPECT(s.index(1) == 1); EXPECT(s.index(8) == 8); EXPECT(s.index(8 * 8) == 2048); EXPECT(s.index(8 * 8 * 100) == 64); EXPECT(s.index(s.elements() - 1) == s.elements() - 1); } TEST_CASE(test_shape4_nonpacked) { std::vector lens = {100, 32, 8, 8}; std::array offsets = {{5, 10, 0, 6}}; std::array adj_lens = {{0, 0, 0, 0}}; std::transform( lens.begin(), lens.end(), offsets.begin(), adj_lens.begin(), std::plus()); // adj_lens should be: { 105, 42, 8, 14 } std::vector strides(4); strides.back() = 1; std::partial_sum(adj_lens.rbegin(), adj_lens.rend() - 1, strides.rbegin() + 1, std::multiplies()); migraphx::shape s{migraphx::shape::float_type, lens, strides}; EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.transposed()); EXPECT(not s.broadcasted()); EXPECT(s.type() == migraphx::shape::float_type); EXPECT(s.lens()[0] == 100); EXPECT(s.lens()[1] == 32); EXPECT(s.lens()[2] == 8); EXPECT(s.lens()[3] == 8); EXPECT(s.strides()[0] == 4704); EXPECT(s.strides()[1] == 112); EXPECT(s.strides()[2] == 14); EXPECT(s.strides()[3] == 1); EXPECT(s.elements() == 100 * 32 * 8 * 8); EXPECT(s.bytes() == sizeof(float) * 469274); EXPECT(s.index(0) == 0); EXPECT(s.index(1) == 1); EXPECT(s.index({0, 0, 0, 0}) == 0); EXPECT(s.index({0, 0, 0, 1}) == s.index(1)); EXPECT(s.index({0, 0, 1, 0}) == s.index(8)); EXPECT(s.index({0, 1, 0, 0}) == s.index(8 * 8)); EXPECT(s.index({1, 0, 0, 0}) == s.index(8 * 8 * 32)); EXPECT(s.index(s.elements() - 1) == 469273); } TEST_CASE(test_serialize) { migraphx::shape s1{migraphx::shape::float_type, {100, 32, 8, 8}}; auto v1 = migraphx::to_value(s1); migraphx::shape s2{migraphx::shape::uint64_type, {2, 2}}; auto v2 = migraphx::to_value(s2); EXPECT(v1 != v2); auto s3 = migraphx::from_value(v1); EXPECT(s3 == s1); auto s4 = migraphx::from_value(v2); EXPECT(s4 == s2); EXPECT(s3 != s4); } TEST_CASE(tuple) { migraphx::shape s{{migraphx::shape{migraphx::shape::float_type}, migraphx::shape{migraphx::shape::int8_type}}}; EXPECT(s.type() == migraphx::shape::tuple_type); EXPECT(s.bytes() == 4 + 1); EXPECT(s.type_size() == 0); EXPECT(s.type_string() == "tuple_type"); EXPECT(s.lens().empty()); EXPECT(s.strides().empty()); EXPECT(not s.standard()); EXPECT(not s.packed()); EXPECT(not s.broadcasted()); EXPECT(not s.transposed()); EXPECT(not s.scalar()); EXPECT(s.sub_shapes().size() == 2); EXPECT(s.sub_shapes()[0].type() == migraphx::shape::float_type); EXPECT(s.sub_shapes()[0].elements() == 1); EXPECT(s.sub_shapes()[1].type() == migraphx::shape::int8_type); EXPECT(s.sub_shapes()[1].elements() == 1); EXPECT(test::throws([&] { s.visit_type([](auto) {}); })); } TEST_CASE(tuple_copy) { migraphx::shape s1{{migraphx::shape{migraphx::shape::float_type}, migraphx::shape{migraphx::shape::int8_type}}}; migraphx::shape s2{{migraphx::shape{migraphx::shape::float_type}, migraphx::shape{migraphx::shape::int8_type}}}; EXPECT(s1 == s2); auto s3 = s1; EXPECT(s3 == s1); EXPECT(s3 == s2); migraphx::shape s4{{migraphx::shape{migraphx::shape::int8_type}, migraphx::shape{migraphx::shape::float_type}}}; EXPECT(s4 != s1); EXPECT(s4 != s2); EXPECT(s4 != s3); } TEST_CASE(tuple_print) { migraphx::shape s{{migraphx::shape{migraphx::shape::float_type}, migraphx::shape{migraphx::shape::int8_type}}}; std::string x = migraphx::to_string(s); EXPECT(x.front() == '['); EXPECT(x.back() == ']'); EXPECT(migraphx::contains(x, "float")); EXPECT(migraphx::contains(x, "int8")); } TEST_CASE(tuple_serialize) { migraphx::shape s1{{migraphx::shape{migraphx::shape::float_type}, migraphx::shape{migraphx::shape::int8_type}}}; migraphx::shape s2{{migraphx::shape{migraphx::shape::int8_type}, migraphx::shape{migraphx::shape::float_type}}}; auto v1 = migraphx::to_value(s1); auto v2 = migraphx::to_value(s2); EXPECT(v1 != v2); auto s3 = migraphx::from_value(v1); EXPECT(s3 == s1); auto s4 = migraphx::from_value(v2); EXPECT(s4 == s2); EXPECT(s3 != s4); } TEST_CASE(test_with_lens1) { migraphx::shape s1{migraphx::shape::float_type, {2, 2}, {1, 2}}; auto s2 = s1.with_lens({4, 3}); EXPECT(s2.transposed()); migraphx::shape s3{migraphx::shape::float_type, {4, 3}, {1, 4}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens2) { migraphx::shape s1{migraphx::shape::float_type, {2, 2}, {2, 1}}; auto s2 = s1.with_lens({3, 4}); EXPECT(s2.standard()); migraphx::shape s3{migraphx::shape::float_type, {3, 4}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous1) { migraphx::shape s1{migraphx::shape::float_type, {64, 1, 24, 24}}; auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(not s2.transposed()); migraphx::shape s3{migraphx::shape::float_type, {64, 3, 24, 24}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous2) { auto s1 = migraphx::reorder_shape({migraphx::shape::float_type, {64, 24, 24, 1}}, {0, 3, 1, 2}); auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(s2.transposed()); migraphx::shape s3 = migraphx::reorder_shape({migraphx::shape::float_type, {64, 24, 24, 3}}, {0, 3, 1, 2}); EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous3) { migraphx::shape s1{migraphx::shape::float_type, {64, 3, 1, 1}}; auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(not s2.transposed()); migraphx::shape s3{migraphx::shape::float_type, {64, 3, 24, 24}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous4) { auto s1 = migraphx::reorder_shape({migraphx::shape::float_type, {64, 1, 1, 3}}, {0, 3, 1, 2}); auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(s2.transposed()); migraphx::shape s3 = migraphx::reorder_shape({migraphx::shape::float_type, {64, 24, 24, 3}}, {0, 3, 1, 2}); EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous5) { migraphx::shape s1{migraphx::shape::float_type, {1, 5, 24, 24}}; auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(not s2.transposed()); migraphx::shape s3{migraphx::shape::float_type, {64, 3, 24, 24}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous6) { auto s1 = migraphx::reorder_shape({migraphx::shape::float_type, {1, 24, 24, 5}}, {0, 3, 1, 2}); auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(s2.transposed()); migraphx::shape s3 = migraphx::reorder_shape({migraphx::shape::float_type, {64, 24, 24, 3}}, {0, 3, 1, 2}); EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous7) { auto s1 = migraphx::reorder_shape({migraphx::shape::float_type, {1, 1, 1, 3}}, {0, 3, 1, 2}); auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(s2.transposed()); migraphx::shape s3 = migraphx::reorder_shape({migraphx::shape::float_type, {64, 24, 24, 3}}, {0, 3, 1, 2}); EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous8) { migraphx::shape s1{migraphx::shape::float_type, {1, 1, 24, 24}}; auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(not s2.transposed()); migraphx::shape s3{migraphx::shape::float_type, {64, 3, 24, 24}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous9) { auto s1 = migraphx::reorder_shape({migraphx::shape::float_type, {1, 24, 24, 1}}, {0, 3, 1, 2}); auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(s2.transposed()); migraphx::shape s3 = migraphx::reorder_shape({migraphx::shape::float_type, {64, 24, 24, 3}}, {0, 3, 1, 2}); EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous10) { migraphx::shape s1{migraphx::shape::float_type, {3, 2, 4, 1}}; auto s2 = s1.with_lens({3, 2, 4, 1}); EXPECT(not s2.transposed()); migraphx::shape s3{migraphx::shape::float_type, {3, 2, 4, 1}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous11) { migraphx::shape s1{migraphx::shape::float_type, {64, 1, 1, 1}}; auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(s1.standard()); EXPECT(s2.standard()); migraphx::shape s3{migraphx::shape::float_type, {64, 3, 24, 24}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous12) { migraphx::shape s1{migraphx::shape::float_type, {1, 64, 1, 1}}; auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(s1.standard()); EXPECT(s2.standard()); migraphx::shape s3{migraphx::shape::float_type, {64, 3, 24, 24}}; EXPECT(s2 == s3); } TEST_CASE(test_with_lens_ambigous13) { auto s1 = migraphx::reorder_shape({migraphx::shape::float_type, {1, 1, 1, 3}}, {0, 3, 1, 2}); auto s2 = s1.with_lens({64, 3, 24, 24}); EXPECT(s2.transposed()); migraphx::shape s3 = migraphx::reorder_shape({migraphx::shape::float_type, {64, 24, 24, 3}}, {0, 3, 1, 2}); EXPECT(s2 == s3); } TEST_CASE(cpp_type_name) { EXPECT(migraphx::shape::cpp_type(migraphx::shape::int8_type) == "int8_t"); EXPECT(migraphx::shape::cpp_type(migraphx::shape::float_type) == "float"); EXPECT(migraphx::shape::cpp_type(migraphx::shape::half_type) == "half"); EXPECT(test::throws([&] { migraphx::shape::cpp_type(migraphx::shape::tuple_type); })); } TEST_CASE(test_with_type) { migraphx::shape s{migraphx::shape::float_type, {2, 2}, {1, 0}}; EXPECT(s.type() == migraphx::shape::float_type); auto new_s = s.with_type(migraphx::shape::half_type); EXPECT(s.type() == migraphx::shape::float_type); EXPECT(s.type() != new_s.type()); EXPECT(s.lens() == new_s.lens()); EXPECT(s.strides() == new_s.strides()); } TEST_CASE(test_multi_index) { migraphx::shape s{migraphx::shape::float_type, {2, 4, 6}}; EXPECT(s.multi(0) == std::vector{0, 0, 0}); EXPECT(s.multi(4) == std::vector{0, 0, 4}); EXPECT(s.multi(6) == std::vector{0, 1, 0}); EXPECT(s.multi(8) == std::vector{0, 1, 2}); EXPECT(s.multi(24) == std::vector{1, 0, 0}); EXPECT(s.multi(30) == std::vector{1, 1, 0}); EXPECT(s.multi(34) == std::vector{1, 1, 4}); } TEST_CASE(test_single_index) { migraphx::shape s{migraphx::shape::float_type, {2, 4, 6}}; EXPECT(0 == s.single({0, 0, 0})); EXPECT(4 == s.single({0, 0, 4})); EXPECT(6 == s.single({0, 1, 0})); EXPECT(8 == s.single({0, 1, 2})); EXPECT(24 == s.single({1, 0, 0})); EXPECT(30 == s.single({1, 1, 0})); EXPECT(34 == s.single({1, 1, 4})); } TEST_CASE(find_permutation_2d_standard) { migraphx::shape s = {migraphx::shape::float_type, {2, 3}}; std::vector permutation = {0, 1}; EXPECT(migraphx::find_permutation(s) == permutation); } TEST_CASE(find_permutation_2d_transpose) { migraphx::shape s = {migraphx::shape::float_type, {2, 3}, {1, 2}}; std::vector permutation = {1, 0}; EXPECT(migraphx::find_permutation(s) == permutation); } TEST_CASE(find_permutation_3d) { migraphx::shape s = {migraphx::shape::float_type, {2, 3, 4}, {1, 8, 2}}; std::vector permutation = {1, 2, 0}; EXPECT(migraphx::find_permutation(s) == permutation); } TEST_CASE(find_permutation_4d) { // ori_lens = 2, 3, 4, 5 // ori_strides = 60, 20, 5, 1 // perm = 3, 2, 0, 1 // inv_perm = 2, 3, 1, 0 // out_strides = 5, 1, 20, 60 migraphx::shape s = {migraphx::shape::float_type, {5, 4, 2, 3}, {5, 1, 20, 60}}; std::vector permutation = {3, 2, 0, 1}; EXPECT(migraphx::find_permutation(s) == permutation); } TEST_CASE(from_2d_permutation) { std::vector out_lens = {2, 3}; std::vector permutation = {1, 0}; migraphx::shape out_shape = migraphx::shape::from_permutation(migraphx::shape::float_type, out_lens, permutation); EXPECT(out_shape.lens() == out_lens); EXPECT(migraphx::find_permutation(out_shape) == permutation); } TEST_CASE(from_3d_permutation) { std::vector out_lens = {2, 3, 4}; std::vector permutation = {1, 2, 0}; migraphx::shape out_shape = migraphx::shape::from_permutation(migraphx::shape::float_type, out_lens, permutation); EXPECT(out_shape.lens() == out_lens); EXPECT(migraphx::find_permutation(out_shape) == permutation); } TEST_CASE(from_4d_permutation) { std::vector out_lens = {5, 4, 2, 3}; std::vector permutation = {3, 2, 0, 1}; migraphx::shape out_shape = migraphx::shape::from_permutation(migraphx::shape::float_type, out_lens, permutation); EXPECT(out_shape.lens() == out_lens); EXPECT(migraphx::find_permutation(out_shape) == permutation); } TEST_CASE(multi_within_bounds) { migraphx::shape in_shape{migraphx::shape::float_type, {3, 2, 2}}; std::vector multi_0 = {3, 1, 1}; EXPECT(not in_shape.multi_within_bounds(multi_0)); std::vector multi_1 = {2, 1, 1}; EXPECT(in_shape.multi_within_bounds(multi_1)); std::vector multi_2 = {0, 0, 0}; EXPECT(in_shape.multi_within_bounds(multi_2)); std::vector multi_3 = {100, 1, 1}; EXPECT(not in_shape.multi_within_bounds(multi_3)); std::vector multi_4 = {1, 2, 1}; EXPECT(not in_shape.multi_within_bounds(multi_4)); } TEST_CASE(shape_computable) { migraphx::shape s1{migraphx::shape::float_type, {1, 1, 8}, {8, 8, 1}}; migraphx::shape s2{migraphx::shape::fp4x2_type, {1, 1, 8}, {8, 8, 1}}; EXPECT(s1.computable()); EXPECT(not s2.computable()); } TEST_CASE(shape_is_compatible_diff_strides) { migraphx::shape actual{migraphx::shape::float_type, {1, 1, 8}, {8, 8, 1}}; migraphx::shape expected{migraphx::shape::float_type, {1, 1, 8}, {1, 1, 1}}; EXPECT(actual != expected); EXPECT(migraphx::shape::is_compatible(actual, expected)); } TEST_CASE(shape_is_compatible_diff_lens) { migraphx::shape actual{migraphx::shape::float_type, {1, 2, 8}, {8, 8, 1}}; migraphx::shape expected{migraphx::shape::float_type, {1, 1, 8}, {8, 8, 1}}; EXPECT(actual != expected); EXPECT(not migraphx::shape::is_compatible(actual, expected)); } TEST_CASE(shape_is_compatible_diff_type) { migraphx::shape actual{migraphx::shape::float_type, {1, 2, 8}}; migraphx::shape expected{migraphx::shape::half_type, {1, 2, 8}}; EXPECT(actual != expected); EXPECT(not migraphx::shape::is_compatible(actual, expected)); } TEST_CASE(shape_is_compatible_dynamic) { migraphx::shape actual{migraphx::shape::float_type, {1, 2, 2, 4}}; migraphx::shape expected{migraphx::shape::float_type, {{1, 1}, {2, 4}, {2, 4}, {2, 4}}}; EXPECT(actual != expected); EXPECT(migraphx::shape::is_compatible(actual, expected)); } TEST_CASE(shape_is_compatible_dynamic_actual) { migraphx::shape actual{migraphx::shape::float_type, {{1, 1}, {2, 4}, {2, 4}, {2, 4}}}; migraphx::shape expected{migraphx::shape::float_type, {1, 2, 2, 4}}; EXPECT(actual != expected); EXPECT(not migraphx::shape::is_compatible(actual, expected)); } TEST_CASE(shape_is_compatible_dynamic_diff_type) { migraphx::shape actual{migraphx::shape::float_type, {1, 2, 2, 4}}; migraphx::shape expected{migraphx::shape::half_type, {{1, 1}, {2, 4}, {2, 4}, {2, 4}}}; EXPECT(actual != expected); EXPECT(not migraphx::shape::is_compatible(actual, expected)); } TEST_CASE(shape_is_compatible_dynamic_diff_rank) { migraphx::shape actual{migraphx::shape::float_type, {1, 2, 2}}; migraphx::shape expected{migraphx::shape::half_type, {{1, 1}, {2, 4}, {2, 4}, {2, 4}}}; EXPECT(actual != expected); EXPECT(not migraphx::shape::is_compatible(actual, expected)); } TEST_CASE(shape_is_compatible_diff_strides_tuple) { migraphx::shape actual{migraphx::shape{migraphx::shape::float_type, {1, 1, 8}, {8, 8, 1}}}; migraphx::shape expected{migraphx::shape{migraphx::shape::float_type, {1, 1, 8}, {1, 1, 1}}}; EXPECT(actual != expected); EXPECT(migraphx::shape::is_compatible(actual, expected)); } TEST_CASE(shape_is_compatible_diff_lens_tuple) { migraphx::shape actual{migraphx::shape{migraphx::shape::float_type, {1, 2, 8}}}; migraphx::shape expected{migraphx::shape{migraphx::shape::float_type, {1, 1, 8}}}; EXPECT(actual != expected); EXPECT(not migraphx::shape::is_compatible(actual, expected)); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/shape_transform_descriptor.cpp000066400000000000000000001202621510465702400236570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include using migraphx::make_op; using migraphx::shape_transform_descriptor; using all_lens = std::vector>; using final_lens = std::vector; using all_axes = std::vector>>; using d_axes = std::vector>; using ops = std::vector; using dimension = shape_transform_descriptor::dimension; using sub = dimension::sub; using axes_map = std::vector>; static all_lens get_all_lens(const shape_transform_descriptor& d) { all_lens result; std::transform( d.dimensions.begin(), d.dimensions.end(), std::back_inserter(result), [](const auto& dim) { std::vector sub_lens; std::transform(dim.subdimensions.begin(), dim.subdimensions.end(), std::back_inserter(sub_lens), [](const auto& x) { return x.len; }); return sub_lens; }); return result; } static final_lens get_final_lens(const shape_transform_descriptor& d) { final_lens result; std::transform(d.dimensions.begin(), d.dimensions.end(), std::back_inserter(result), [](const auto& x) { return x.len(); }); return result; } static all_axes get_all_axes(const shape_transform_descriptor& d) { all_axes result; std::transform( d.dimensions.begin(), d.dimensions.end(), std::back_inserter(result), [](const auto& dim) { std::vector> sub_axis; std::transform(dim.subdimensions.begin(), dim.subdimensions.end(), std::back_inserter(sub_axis), [](const auto& x) { return x.axis; }); return sub_axis; }); return result; } static std::vector run_shape_transforms(const std::vector& dims, const std::vector& ops) { migraphx::shape s{migraphx::shape::int64_type, dims}; std::vector data(s.elements()); std::iota(data.begin(), data.end(), 0); migraphx::program p; auto* mm = p.get_main_module(); auto start = mm->add_literal(s, data); for(const auto& op : ops) start = mm->add_instruction(op, start); mm->add_return({start}); auto result = p.eval({}).at(0); return result.to_vector(); } static std::vector check_optimize_shape_transforms(const std::vector& dims, const std::vector& ops) { auto result = migraphx::optimize_shape_transforms(dims, ops); CHECK(run_shape_transforms(dims, ops) == run_shape_transforms(dims, result)); CHECK(result == migraphx::optimize_shape_transforms(dims, result)); return result; } template static shape_transform_descriptor make_descriptor(const std::vector& dims, const Ts&... xs) { auto desc = shape_transform_descriptor{dims}; CHECK(desc.apply({xs...})); return desc; } template static shape_transform_descriptor make_simple_descriptor(const std::vector& dims, const Ts&... xs) { auto desc = make_descriptor(dims, xs...); desc.simplify(); return desc; } TEST_CASE(dimension_len) { dimension dim; dim.subdimensions = std::vector{sub{4, {1}}, sub{5, {2}}}; EXPECT(dim.len() == 20); } TEST_CASE(record_reshape) { auto desc = make_descriptor({256, 3, 16, 16}, make_op("reshape", {{"dims", {16, 16, 48, 16}}})); EXPECT(get_final_lens(desc) == final_lens{16, 16, 48, 16}); EXPECT(get_all_lens(desc) == all_lens{{16}, {16}, {3, 16}, {16}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0, 0}}, d_axes{{0, 1}}, d_axes{{1}, {2}}, d_axes{{3}}}); } TEST_CASE(record_reshape_1s) { auto desc = make_descriptor({3, 4, 4}, make_op("reshape", {{"dims", {3, 1, 4, 1, 4}}})); EXPECT(get_final_lens(desc) == final_lens{3, 1, 4, 1, 4}); EXPECT(get_all_lens(desc) == all_lens{{3}, {1}, {4}, {1}, {4}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0}}, d_axes{{1, 0}}, d_axes{{1, 1}}, d_axes{{2, 0}}, d_axes{{2, 1}}}); } TEST_CASE(record_reshape_trailing_1s) { auto desc = make_descriptor({3, 4, 4}, make_op("reshape", {{"dims", {3, 4, 4, 1, 1}}})); EXPECT(get_final_lens(desc) == final_lens{3, 4, 4, 1, 1}); EXPECT(get_all_lens(desc) == all_lens{{3}, {4}, {4}, {1}, {1}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0}}, d_axes{{1}}, d_axes{{2, 0}}, d_axes{{2, 1}}, d_axes{{2, 2}}}); } TEST_CASE(record_reshape_merge) { auto desc = make_descriptor({3, 4, 5}, make_op("reshape", {{"dims", {3, 20}}})); EXPECT(get_final_lens(desc) == final_lens{3, 20}); EXPECT(get_all_lens(desc) == all_lens{{3}, {4, 5}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0}}, d_axes{{1}, {2}}}); } TEST_CASE(record_reshape_split) { auto desc = make_descriptor({3, 20}, make_op("reshape", {{"dims", {3, 4, 5}}})); EXPECT(get_final_lens(desc) == final_lens{3, 4, 5}); EXPECT(get_all_lens(desc) == all_lens{{3}, {4}, {5}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0}}, d_axes{{1, 0}}, d_axes{{1, 1}}}); } TEST_CASE(record_reshape_merge_split) { auto desc = make_descriptor({3, 10, 16}, make_op("reshape", {{"dims", {3, 40, 2, 2}}})); EXPECT(get_final_lens(desc) == final_lens{3, 40, 2, 2}); EXPECT(get_all_lens(desc) == all_lens{{3}, {10, 4}, {2}, {2}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0}}, d_axes{{1}, {2, 0}}, d_axes{{2, 1}}, d_axes{{2, 2}}}); } TEST_CASE(record_squeeze_trailing_1s) { auto desc = make_descriptor({3, 4, 4, 1, 1}, make_op("reshape", {{"dims", {3, 4, 4}}})); EXPECT(get_final_lens(desc) == final_lens{3, 4, 4}); EXPECT(get_all_lens(desc) == all_lens{{3}, {4}, {4}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0}}, d_axes{{1}}, d_axes{{2}}}); } TEST_CASE(record_reshape_squeeze_trailing_1s) { auto desc = make_descriptor({3, 4, 4}, make_op("reshape", {{"dims", {3, 4, 4, 1, 1}}}), make_op("reshape", {{"dims", {3, 4, 4}}})); EXPECT(get_final_lens(desc) == final_lens{3, 4, 4}); EXPECT(get_all_lens(desc) == all_lens{{3}, {4}, {4}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0}}, d_axes{{1}}, d_axes{{2, 0}}}); } TEST_CASE(record_reshape_non_divisible_fail) { auto desc = shape_transform_descriptor{{2, 3, 5}}; EXPECT(not desc.apply({make_op("reshape", {{"dims", {10, 3}}})})); } TEST_CASE(record_transpose) { auto desc = make_descriptor({256, 3, 16, 16}, make_op("transpose", {{"permutation", {0, 2, 3, 1}}})); EXPECT(get_final_lens(desc) == final_lens{256, 16, 16, 3}); EXPECT(get_all_lens(desc) == all_lens{{256}, {16}, {16}, {3}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{0}}, d_axes{{2}}, d_axes{{3}}, d_axes{{1}}}); } TEST_CASE(record_multibroadcast) { auto desc = make_descriptor({1, 3, 1, 1}, make_op("multibroadcast", {{"out_lens", {256, 3, 16, 16}}})); EXPECT(get_final_lens(desc) == final_lens{256, 3, 16, 16}); EXPECT(get_all_lens(desc) == all_lens{{256}, {3}, {16}, {16}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{}}, d_axes{{1}}, d_axes{{}}, d_axes{{}}}); } TEST_CASE(record_broadcast1) { auto desc = make_descriptor({3}, make_op("broadcast", {{"axis", 1}, {"out_lens", {256, 3, 16, 16}}})); EXPECT(get_final_lens(desc) == final_lens{256, 3, 16, 16}); EXPECT(get_all_lens(desc) == all_lens{{256}, {3}, {16}, {16}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{}}, d_axes{{0}}, d_axes{{}}, d_axes{{}}}); } TEST_CASE(record_broadcast2) { auto desc = make_descriptor( {32, 10}, make_op("broadcast", {{"axis", 1}, {"out_lens", {256, 32, 10, 16, 16}}})); EXPECT(get_final_lens(desc) == final_lens{256, 32, 10, 16, 16}); EXPECT(get_all_lens(desc) == all_lens{{256}, {32}, {10}, {16}, {16}}); EXPECT(get_all_axes(desc) == all_axes{d_axes{{}}, d_axes{{0}}, d_axes{{1}}, d_axes{{}}, d_axes{{}}}); } TEST_CASE(simplify_dimension_merge_adjacent) { auto d = dimension{{sub{2, {0, 0}}, sub{3, {0, 1}}}}; d.simplify(); EXPECT(d == dimension{{sub{6, {0, 1}}}}); } TEST_CASE(simplify_dimension_no_merge_adjacent1) { auto d = dimension{{sub{2, {0, 1}}, sub{3, {0, 0}}}}; d.simplify(); EXPECT(d == dimension{{sub{2, {0, 1}}, sub{3, {0, 0}}}}); } TEST_CASE(simplify_dimension_no_merge_adjacent2) { auto d = dimension{{sub{2, {0, 0, 0}}, sub{3, {0, 1, 1}}}}; d.simplify(); EXPECT(d == dimension{{sub{2, {0, 0, 0}}, sub{3, {0, 1, 1}}}}); } TEST_CASE(simplify_dimension_remove_1_dim) { auto d = dimension{{sub{2, {0, 1}}, sub{1, {1}}, sub{3, {0, 0}}}}; d.simplify(); EXPECT(d == dimension{{sub{2, {0, 1}}, sub{3, {0, 0}}}}); } TEST_CASE(optimize_transpose_transpose) { EXPECT(check_optimize_shape_transforms({3, 5, 2}, { make_op("transpose", {{"permutation", {0, 2, 1}}}), make_op("transpose", {{"permutation", {1, 0, 2}}}), }) == ops{ make_op("transpose", {{"permutation", {2, 0, 1}}}), }); } TEST_CASE(optimize_reshape_reshape1) { EXPECT(check_optimize_shape_transforms({3, 5, 2}, { make_op("reshape", {{"dims", {30}}}), make_op("reshape", {{"dims", {3, 10}}}), }) == ops{ make_op("reshape", {{"dims", {3, 10}}}), }); } TEST_CASE(optimize_reshape_reshape2) { EXPECT(check_optimize_shape_transforms({15, 4}, { make_op("reshape", {{"dims", {3, 5, 2, 2}}}), make_op("reshape", {{"dims", {15, 2, 2}}}), }) == ops{ make_op("reshape", {{"dims", {15, 2, 2}}}), }); } TEST_CASE(optimize_reshape_transpose_reshape_to_none) { EXPECT(check_optimize_shape_transforms( {6, 5, 2}, { make_op("reshape", {{"dims", {6, 5, 2, 1, 1}}}), make_op("transpose", {{"permutation", {0, 1, 2, 4, 3}}}), make_op("reshape", {{"dims", {6, 5, 2}}}), }) == ops{}); } TEST_CASE(optimize_reshape_transpose_reshape_to_same) { EXPECT(check_optimize_shape_transforms( {1, 112, 7, 7}, { make_op("reshape", {{"dims", {1, 4, 28, 7, 7}}}), make_op("transpose", {{"permutation", {0, 2, 1, 3, 4}}}), make_op("reshape", {{"dims", {1, 112, 7, 7}}}), }) == ops{ make_op("reshape", {{"dims", {1, 4, 28, 7, 7}}}), make_op("transpose", {{"permutation", {0, 2, 1, 3, 4}}}), make_op("reshape", {{"dims", {1, 112, 7, 7}}}), }); } TEST_CASE(optimize_reshape_transpose_reshape_to_transpose) { EXPECT(check_optimize_shape_transforms( {6, 5, 2}, { make_op("reshape", {{"dims", {2, 3, 5, 2}}}), make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), make_op("reshape", {{"dims", {6, 2, 5}}}), }) == ops{ make_op("transpose", {{"permutation", {0, 2, 1}}}), }); } TEST_CASE(optimize_reshape_transpose_reshape_to_reshape) { EXPECT( check_optimize_shape_transforms({6, 5, 2}, { make_op("reshape", {{"dims", {6, 5, 2, 1}}}), make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), make_op("reshape", {{"dims", {6, 10}}}), }) == ops{ make_op("reshape", {{"dims", {6, 10}}}), }); } TEST_CASE(optimize_multibroadcast_transpose_reshape) { EXPECT(check_optimize_shape_transforms( {1, 5, 2}, { make_op("multibroadcast", {{"out_lens", {20, 5, 2}}}), make_op("transpose", {{"permutation", {0, 2, 1}}}), make_op("reshape", {{"dims", {20, 10}}}), }) == ops{ make_op("transpose", {{"permutation", {0, 2, 1}}}), make_op("reshape", {{"dims", {1, 10}}}), make_op("multibroadcast", {{"out_lens", {20, 10}}}), }); } TEST_CASE(optimize_resize1) { EXPECT(check_optimize_shape_transforms( {3, 4, 4}, { make_op("reshape", {{"dims", {3, 1, 4, 1, 4}}}), make_op("multibroadcast", {{"out_lens", {3, 2, 4, 2, 4}}}), make_op("reshape", {{"dims", {3, 8, 8}}}), }) == ops{ make_op("unsqueeze", {{"axes", {1, 3}}}), make_op("multibroadcast", {{"out_lens", {3, 2, 4, 2, 4}}}), make_op("reshape", {{"dims", {3, 8, 8}}}), }); } TEST_CASE(optimize_resize2) { EXPECT(check_optimize_shape_transforms( {1, 1, 2, 2}, { make_op("reshape", {{"dims", {1, 1, 2, 1, 2, 1}}}), make_op("multibroadcast", {{"out_lens", {1, 2, 2, 2, 2, 3}}}), make_op("reshape", {{"dims", {1, 2, 4, 6}}}), }) == ops{ make_op("unsqueeze", {{"axes", {3, 5}}}), make_op("multibroadcast", {{"out_lens", {1, 1, 2, 2, 2, 3}}}), make_op("reshape", {{"dims", {1, 1, 4, 6}}}), make_op("multibroadcast", {{"out_lens", {1, 2, 4, 6}}}), }); } TEST_CASE(optimize_reshape_2_squeeze) { EXPECT(check_optimize_shape_transforms({3, 1, 5, 1, 2, 1, 1}, { make_op("reshape", {{"dims", {3, 5, 2}}}), }) == ops{ make_op("squeeze", {{"axes", {1, 3, 5, 6}}}), }); } TEST_CASE(optimize_reshape_2_unsqueeze) { EXPECT( check_optimize_shape_transforms({3, 5, 2}, { make_op("reshape", {{"dims", {3, 1, 5, 1, 2, 1, 1}}}), }) == ops{ make_op("unsqueeze", {{"axes", {1, 3, 5, 6}}}), }); } TEST_CASE(optimize_unsqueeze_multibroadcast) { EXPECT(check_optimize_shape_transforms( {32, 10}, { make_op("unsqueeze", {{"axes", {0, 3, 4}}}), make_op("multibroadcast", {{"out_lens", {4, 32, 10, 16, 16}}}), }) == ops{ make_op("broadcast", {{"axis", 1}, {"out_lens", {4, 32, 10, 16, 16}}}), }); } TEST_CASE(optimize_multibroadcast_reshape) { EXPECT(check_optimize_shape_transforms({1, 4, 1}, { make_op("multibroadcast", {{"out_lens", {2, 4, 6}}}), make_op("reshape", {{"dims", {2, 2, 2, 6}}}), }) == ops{ make_op("reshape", {{"dims", {1, 2, 2, 1}}}), make_op("multibroadcast", {{"out_lens", {2, 2, 2, 6}}}), }); } TEST_CASE(optimize_squeeze_broadcast1) { EXPECT(check_optimize_shape_transforms( {256, 1, 1}, { make_op("squeeze"), make_op("broadcast", {{"axis", 0}, {"out_lens", {256, 64, 1, 1}}}), }) == ops{ make_op("unsqueeze", {{"axes", {3}}}), make_op("multibroadcast", {{"out_lens", {256, 64, 1, 1}}}), }); } TEST_CASE(optimize_squeeze_broadcast2) { EXPECT(check_optimize_shape_transforms( {1, 128, 1}, { make_op("squeeze", {{"axes", {0}}}), make_op("multibroadcast", {{"out_lens", {128, 768}}}), }) == ops{ make_op("squeeze", {{"axes", {0}}}), make_op("multibroadcast", {{"out_lens", {128, 768}}}), }); } TEST_CASE(optimize_squeeze_unsqueeze_broadcast_to_broadcast) { EXPECT(check_optimize_shape_transforms( {256}, { make_op("unsqueeze", {{"axes", {0}}}), make_op("squeeze"), make_op("broadcast", {{"axis", 0}, {"out_lens", {256, 64, 1, 1}}}), }) == ops{ make_op("broadcast", {{"axis", 0}, {"out_lens", {256, 64, 1, 1}}}), }); } TEST_CASE(optimize_transpose_reshape_to_transpose) { EXPECT(check_optimize_shape_transforms( {3, 3, 3, 1}, { make_op("transpose", {{"permutation", {3, 2, 0, 1}}}), make_op("reshape", {{"dims", {3, 1, 3, 3}}}), }) == ops{ make_op("transpose", {{"permutation", {2, 3, 0, 1}}}), }); } TEST_CASE(optimize_scalar_broadcast_unsqueeze) { EXPECT(check_optimize_shape_transforms({1}, { make_op("multibroadcast", {{"out_lens", {2}}}), make_op("unsqueeze", {{"axes", {1}}}), }) == ops{ make_op("multibroadcast", {{"out_lens", {2, 1}}}), }); } TEST_CASE(optimize_broadcast_reshape_transpose) { EXPECT(check_optimize_shape_transforms( {2, 16, 1}, { make_op("multibroadcast", {{"out_lens", {2, 16, 10240}}}), make_op("reshape", {{"dims", {2, 160, 32, 32}}}), make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), }) == ops{ make_op("unsqueeze", {{"axes", {3, 4}}}), make_op("transpose", {{"permutation", {0, 3, 4, 1, 2}}}), make_op("multibroadcast", {{"out_lens", {2, 1, 1, 16, 10}}}), make_op("reshape", {{"dims", {2, 1, 1, 160}}}), make_op("multibroadcast", {{"out_lens", {2, 32, 32, 160}}}), }); } TEST_CASE(optimize_multibroadcast_transpose) { EXPECT(check_optimize_shape_transforms( {320, 1, 1}, { make_op("multibroadcast", {{"out_lens", {2, 320, 64, 64}}}), make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), }) == ops{ make_op("unsqueeze", {{"axes", {0}}}), make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), make_op("multibroadcast", {{"out_lens", {2, 64, 64, 320}}}), }); } TEST_CASE(optimize_unsqueeze_transpose_squeeze_multibroadcast) { EXPECT(check_optimize_shape_transforms( {320, 1, 1}, { make_op("unsqueeze", {{"axes", {0}}}), make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), make_op("squeeze", {{"axes", {0, 1}}}), make_op("multibroadcast", {{"out_lens", {320, 320}}}), }) == ops{ make_op("multibroadcast", {{"out_lens", {320, 1, 320}}}), make_op("squeeze", {{"axes", {1}}}), }); } TEST_CASE(optimize_squeeze_multibroadcast_transpose) { EXPECT(check_optimize_shape_transforms( {16, 1, 16}, { make_op("squeeze", {{"axes", {1}}}), make_op("multibroadcast", {{"out_lens", {4, 16, 16}}}), make_op("transpose", {{"permutation", {1, 0, 2}}}), }) == ops{ make_op("multibroadcast", {{"out_lens", {16, 4, 16}}}), }); } TEST_CASE(optimize_squeeze_1x1) { EXPECT(check_optimize_shape_transforms({1, 1}, { make_op("squeeze", {{"axes", {0}}}), }) == ops{ make_op("squeeze", {{"axes", {0}}}), }); } TEST_CASE(optimize_broadcast_squeeze_reshape) { EXPECT(check_optimize_shape_transforms( {2, 32, 1, 1, 1}, { make_op("multibroadcast", {{"out_lens", {2, 32, 40960, 1, 1}}}), make_op("squeeze", {{"axes", {3, 4}}}), make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), }) == ops{ make_op("multibroadcast", {{"out_lens", {2, 32, 10, 64, 64}}}), }); } TEST_CASE(common_dims_reshape_less) { auto desc = make_simple_descriptor({2, 32, 40, 8}, make_op("reshape", {{"dims", {2, 1280, 8}}})); EXPECT(desc.common_dims() == final_lens{2, 32, 40, 8}); EXPECT(desc.common_axes_map_from_src() == axes_map{{0}, {1}, {2}, {3}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{0}, {1, 2}, {3}}); EXPECT(desc.to_common_from_src().generate() == ops{}); EXPECT(desc.to_common_from_dst().generate() == ops{make_op("reshape", {{"dims", {2, 32, 40, 8}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 1280, 8}}})}); EXPECT(desc.to_src_from_common().generate() == ops{}); } TEST_CASE(common_dims_reshape1) { auto desc = make_simple_descriptor({2, 32, 2560}, make_op("reshape", {{"dims", {2, 1280, 8, 8}}})); EXPECT(desc.common_dims() == final_lens{2, 32, 40, 8, 8}); EXPECT(desc.common_axes_map_from_src() == axes_map{{{0}, {1}, {2, 3, 4}}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{0}, {1, 2}, {3}, {4}}); EXPECT(desc.to_common_from_src().generate() == ops{make_op("reshape", {{"dims", {2, 32, 40, 8, 8}}})}); EXPECT(desc.to_common_from_dst().generate() == ops{make_op("reshape", {{"dims", {2, 32, 40, 8, 8}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 1280, 8, 8}}})}); EXPECT(desc.to_src_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 32, 2560}}})}); } TEST_CASE(common_dims_reshape2) { auto desc = make_simple_descriptor({2, 1280, 8, 8}, make_op("reshape", {{"dims", {2, 32, 2560}}})); EXPECT(desc.common_dims() == final_lens{2, 32, 40, 8, 8}); EXPECT(desc.common_axes_map_from_src() == axes_map{{0}, {1, 2}, {3}, {4}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{{0}, {1}, {2, 3, 4}}}); EXPECT(desc.to_common_from_src().generate() == ops{make_op("reshape", {{"dims", {2, 32, 40, 8, 8}}})}); EXPECT(desc.to_common_from_dst().generate() == ops{make_op("reshape", {{"dims", {2, 32, 40, 8, 8}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 32, 2560}}})}); EXPECT(desc.to_src_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 1280, 8, 8}}})}); } TEST_CASE(common_dims_reshape3) { auto desc = make_simple_descriptor({2, 32, 4096}, make_op("reshape", {{"dims", {4, 16, 64, 64}}})); EXPECT(desc.common_dims() == final_lens{2, 2, 16, 64, 64}); EXPECT(desc.common_dims({2, 1, 4096}) == final_lens{2, 1, 1, 64, 64}); EXPECT(desc.common_dims({2, 32, 1}) == final_lens{2, 2, 16, 1, 1}); EXPECT(desc.common_axes_map_from_src() == axes_map{{0}, {1, 2}, {3, 4}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{0, 1}, {2}, {3}, {4}}); EXPECT(desc.to_common_from_src().generate() == ops{make_op("reshape", {{"dims", {2, 2, 16, 64, 64}}})}); EXPECT(desc.to_common_from_src().generate({2, 32, 1}) == ops{make_op("reshape", {{"dims", {2, 2, 16, 1, 1}}})}); EXPECT(desc.to_common_from_src().generate({2, 1, 4096}) == ops{make_op("reshape", {{"dims", {2, 1, 1, 64, 64}}})}); EXPECT(desc.to_common_from_dst().generate() == ops{make_op("reshape", {{"dims", {2, 2, 16, 64, 64}}})}); EXPECT(desc.to_common_from_dst().generate({4, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {2, 2, 16, 1, 1}}})}); EXPECT(desc.to_common_from_dst().generate({4, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 2, 1, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("reshape", {{"dims", {4, 16, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate({2, 2, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {4, 1, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate({2, 2, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {4, 16, 1, 1}}})}); EXPECT(desc.to_dst_from_common().generate({2, 1, 16, 64, 64}) == ops{make_op("squeeze", {{"axes", {1}}})}); EXPECT(desc.to_src_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 32, 4096}}})}); EXPECT(desc.to_src_from_common().generate({2, 2, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 2, 4096}}})}); EXPECT(desc.to_src_from_common().generate({2, 2, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {2, 32, 1}}})}); EXPECT(desc.to_src_from_common().generate({2, 1, 16, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 16, 4096}}})}); } TEST_CASE(common_dims_reshape4) { auto desc = make_simple_descriptor({4, 16, 64, 64}, make_op("reshape", {{"dims", {2, 32, 4096}}})); EXPECT(desc.common_dims() == final_lens{2, 2, 16, 64, 64}); EXPECT(desc.common_dims({4, 16, 1, 1}) == final_lens{2, 2, 16, 1, 1}); EXPECT(desc.common_dims({4, 1, 64, 64}) == final_lens{2, 2, 1, 64, 64}); EXPECT(desc.common_axes_map_from_src() == axes_map{{0, 1}, {2}, {3}, {4}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{0}, {1, 2}, {3, 4}}); EXPECT(desc.to_common_from_dst().generate() == ops{make_op("reshape", {{"dims", {2, 2, 16, 64, 64}}})}); EXPECT(desc.to_common_from_dst().generate({2, 32, 1}) == ops{make_op("reshape", {{"dims", {2, 2, 16, 1, 1}}})}); EXPECT(desc.to_common_from_dst().generate({2, 1, 4096}) == ops{make_op("reshape", {{"dims", {2, 1, 1, 64, 64}}})}); EXPECT(desc.to_common_from_src().generate() == ops{make_op("reshape", {{"dims", {2, 2, 16, 64, 64}}})}); EXPECT(desc.to_common_from_src().generate({4, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {2, 2, 16, 1, 1}}})}); EXPECT(desc.to_common_from_src().generate({4, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 2, 1, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 32, 4096}}})}); EXPECT(desc.to_dst_from_common().generate({2, 2, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 2, 4096}}})}); EXPECT(desc.to_dst_from_common().generate({2, 2, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {2, 32, 1}}})}); EXPECT(desc.to_dst_from_common().generate({2, 1, 16, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 16, 4096}}})}); EXPECT(desc.to_src_from_common().generate() == ops{make_op("reshape", {{"dims", {4, 16, 64, 64}}})}); EXPECT(desc.to_src_from_common().generate({2, 2, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {4, 1, 64, 64}}})}); EXPECT(desc.to_src_from_common().generate({2, 2, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {4, 16, 1, 1}}})}); EXPECT(desc.to_src_from_common().generate({2, 1, 16, 64, 64}) == ops{make_op("squeeze", {{"axes", {1}}})}); } TEST_CASE(common_dims_transpose_reshape) { auto desc = make_simple_descriptor({2, 16, 64, 64}, make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), make_op("reshape", {{"dims", {2, 32, 2048}}})); EXPECT(desc.common_dims() == final_lens{2, 32, 2, 64, 16}); EXPECT(desc.common_axes_map_from_src() == axes_map{{0}, {4}, {1, 2}, {3}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{0}, {1}, {2, 3, 4}}); EXPECT(desc.to_common_from_dst().generate() == ops{make_op("reshape", {{"dims", {2, 32, 2, 64, 16}}})}); EXPECT(desc.to_common_from_dst().generate({2, 32, 1}) == ops{make_op("unsqueeze", {{"axes", {3, 4}}})}); EXPECT(desc.to_common_from_dst().generate({2, 1, 2048}) == ops{make_op("reshape", {{"dims", {2, 1, 2, 64, 16}}})}); EXPECT(desc.to_common_from_src().generate() == ops{make_op("reshape", {{"dims", {2, 16, 32, 2, 64}}}), make_op("transpose", {{"permutation", {0, 2, 3, 4, 1}}})}); EXPECT(desc.to_common_from_src().generate({2, 16, 1, 1}) == ops{make_op("unsqueeze", {{"axes", {3}}}), make_op("transpose", {{"permutation", {0, 2, 3, 4, 1}}})}); EXPECT(desc.to_common_from_src().generate({2, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 1, 32, 2, 64}}}), make_op("transpose", {{"permutation", {0, 2, 3, 4, 1}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 32, 2048}}})}); EXPECT(desc.to_dst_from_common().generate({2, 1, 2, 64, 16}) == ops{make_op("reshape", {{"dims", {2, 1, 2048}}})}); EXPECT(desc.to_dst_from_common().generate({2, 1, 1, 1, 16}) == ops{make_op("squeeze", {{"axes", {2, 3}}})}); EXPECT(desc.to_dst_from_common().generate({2, 32, 2, 64, 1}) == ops{make_op("reshape", {{"dims", {2, 32, 128}}})}); // 2, 16, 32, 2, 64 EXPECT(desc.to_src_from_common().generate() == ops{make_op("transpose", {{"permutation", {0, 4, 1, 2, 3}}}), make_op("reshape", {{"dims", {2, 16, 64, 64}}})}); // 2, 16, 1, 2, 64 => 2, 16, 2, 64 EXPECT(desc.to_src_from_common().generate({2, 1, 2, 64, 16}) == ops{make_op("transpose", {{"permutation", {0, 4, 1, 2, 3}}}), make_op("squeeze", {{"axes", {2}}})}); // 2, 16, 1, 1, 1 => 2, 16, 1, 1 EXPECT(desc.to_src_from_common().generate({2, 1, 1, 1, 16}) == ops{make_op("transpose", {{"permutation", {0, 4, 1, 2, 3}}}), make_op("squeeze", {{"axes", {3}}})}); // 2, 1, 32, 2, 64 => 2, 1, 64, 64 EXPECT(desc.to_src_from_common().generate({2, 32, 2, 64, 1}) == ops{make_op("transpose", {{"permutation", {0, 4, 1, 2, 3}}}), make_op("reshape", {{"dims", {2, 1, 64, 64}}})}); } TEST_CASE(common_dims_broadcast_reshape) { auto desc = make_simple_descriptor({2, 32, 1}, make_op("multibroadcast", {{"out_lens", {2, 32, 4096}}}), make_op("reshape", {{"dims", {4, 16, 64, 64}}})); EXPECT(desc.common_dims() == final_lens{2, 2, 16, 64, 64}); EXPECT(desc.common_dims({2, 1, 1}) == final_lens{2, 1, 1, 64, 64}); EXPECT(desc.common_dims({2, 1, 4096}) == final_lens{2, 1, 1, 64, 64}); EXPECT(desc.common_dims({2, 32, 4096}) == final_lens{2, 2, 16, 64, 64}); EXPECT(desc.common_axes_map_from_src() == axes_map{{0}, {1, 2}, {3, 4}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{0, 1}, {2}, {3}, {4}}); EXPECT(desc.to_common_from_src().generate() == ops{make_op("reshape", {{"dims", {2, 2, 16, 1, 1}}}), make_op("multibroadcast", {{"out_lens", {2, 2, 16, 64, 64}}})}); EXPECT(desc.to_common_from_src().generate({2, 1, 1}) == ops{make_op("unsqueeze", {{"axes", {2, 4}}}), make_op("multibroadcast", {{"out_lens", {2, 1, 1, 64, 64}}})}); CHECK(desc.to_common_from_dst().generate() == ops{make_op("reshape", {{"dims", {2, 2, 16, 64, 64}}})}); EXPECT(desc.to_common_from_dst().generate({4, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {2, 2, 16, 1, 1}}})}); EXPECT(desc.to_common_from_dst().generate({4, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 2, 1, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("reshape", {{"dims", {4, 16, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate({2, 2, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {4, 1, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate({2, 2, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {4, 16, 1, 1}}})}); EXPECT(desc.to_dst_from_common().generate({2, 1, 16, 64, 64}) == ops{make_op("squeeze", {{"axes", {1}}})}); EXPECT(desc.to_src_from_common().generate() == ops{make_op("reshape", {{"dims", {2, 32, 4096}}})}); EXPECT(desc.to_src_from_common().generate({2, 2, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 2, 4096}}})}); EXPECT(desc.to_src_from_common().generate({2, 2, 16, 1, 1}) == ops{make_op("reshape", {{"dims", {2, 32, 1}}})}); EXPECT(desc.to_src_from_common().generate({2, 1, 16, 64, 64}) == ops{make_op("reshape", {{"dims", {2, 16, 4096}}})}); } TEST_CASE(common_dims_resize) { auto desc = make_simple_descriptor({4, 16, 32, 32}, make_op("reshape", {{"dims", {4, 16, 32, 1, 32, 1}}}), make_op("multibroadcast", {{"out_lens", {4, 16, 32, 2, 32, 2}}}), make_op("reshape", {{"dims", {4, 16, 64, 64}}})); EXPECT(desc.common_dims() == final_lens{4, 16, 32, 2, 32, 2}); EXPECT(desc.common_dims({4, 16, 1, 1}) == final_lens{4, 16, 1, 2, 1, 2}); EXPECT(desc.common_dims({4, 1, 32, 32}) == final_lens{4, 1, 32, 2, 32, 2}); EXPECT(desc.common_axes_map_from_src() == axes_map{{0}, {1}, {2}, {4}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{0}, {1}, {2, 3}, {4, 5}}); EXPECT(desc.to_common_from_src().generate() == ops{make_op("unsqueeze", {{"axes", {3, 5}}}), make_op("multibroadcast", {{"out_lens", {4, 16, 32, 2, 32, 2}}})}); EXPECT(desc.to_common_from_src().generate({4, 16, 1, 1}) == ops{make_op("unsqueeze", {{"axes", {3, 5}}}), make_op("multibroadcast", {{"out_lens", {4, 16, 1, 2, 1, 2}}})}); EXPECT(desc.to_common_from_src().generate({4, 1, 32, 32}) == ops{make_op("unsqueeze", {{"axes", {3, 5}}}), make_op("multibroadcast", {{"out_lens", {4, 1, 32, 2, 32, 2}}})}); EXPECT(desc.to_common_from_dst().generate() == ops{make_op("reshape", {{"dims", {4, 16, 32, 2, 32, 2}}})}); EXPECT(desc.to_common_from_dst().generate({4, 16, 1, 1}) == ops{make_op("unsqueeze", {{"axes", {3, 5}}})}); EXPECT(desc.to_common_from_dst().generate({4, 1, 64, 64}) == ops{make_op("reshape", {{"dims", {4, 1, 32, 2, 32, 2}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("reshape", {{"dims", {4, 16, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate({4, 16, 1, 2, 1, 2}) == ops{make_op("squeeze", {{"axes", {2, 4}}})}); EXPECT(desc.to_dst_from_common().generate({4, 1, 32, 2, 32, 2}) == ops{make_op("reshape", {{"dims", {4, 1, 64, 64}}})}); EXPECT(desc.to_dst_from_common().generate({4, 16, 32, 1, 32, 1}) == ops{make_op("squeeze", {{"axes", {3, 5}}})}); EXPECT(desc.to_src_from_common().generate() == ops{make_op("squeeze", {{"axes", {3, 5}}})}); EXPECT(desc.to_src_from_common().generate({4, 16, 1, 1, 1, 1}) == ops{make_op("squeeze", {{"axes", {3, 4}}})}); EXPECT(desc.to_src_from_common().generate({4, 1, 32, 1, 32, 1}) == ops{make_op("squeeze", {{"axes", {3, 5}}})}); EXPECT(desc.to_src_from_common().generate({4, 16, 32, 1, 32, 1}) == ops{make_op("squeeze", {{"axes", {3, 5}}})}); } TEST_CASE(common_dims_squeeze_1x1) { auto desc = make_simple_descriptor({1, 1}, make_op("squeeze", {{"axes", {0}}})); desc.simplify(); EXPECT(desc.common_dims() == final_lens{1, 1}); EXPECT(desc.common_axes_map_from_src() == axes_map{{0}, {1}}); EXPECT(desc.common_axes_map_from_dst() == axes_map{{0, 1}}); EXPECT(desc.to_common_from_src().generate() == ops{}); EXPECT(desc.to_common_from_dst().generate() == ops{make_op("unsqueeze", {{"axes", {1}}})}); EXPECT(desc.to_dst_from_common().generate() == ops{make_op("squeeze", {{"axes", {0}}})}); EXPECT(desc.to_src_from_common().generate() == ops{}); } TEST_CASE(rebase_reshape_broadcast) { auto base_desc = make_simple_descriptor({3, 4, 64, 1}, make_op("reshape", {{"dims", {12, 8, 8, 1, 1}}}), make_op("multibroadcast", {{"out_lens", {12, 8, 8, 2, 2}}})); { auto desc = base_desc.rebase({3, 4, 64, 4}); EXPECT(get_final_lens(desc) == final_lens{12, 8, 8, 2, 2}); EXPECT(get_all_lens(desc) == all_lens{{3, 4}, {8}, {8}, {2}, {2}}); EXPECT(desc.generate() == ops{make_op("reshape", {{"dims", {3, 4, 8, 8, 2, 2}}}), make_op("reshape", {{"dims", {12, 8, 8, 2, 2}}})}); } { auto desc = base_desc.rebase({3, 5, 64, 1}); EXPECT(get_final_lens(desc) == final_lens{15, 8, 8, 2, 2}); EXPECT(get_all_lens(desc) == all_lens{{3, 5}, {8}, {8}, {2}, {2}}); EXPECT(desc.generate() == ops{make_op("reshape", {{"dims", {3, 5, 8, 8, 1, 1}}}), make_op("reshape", {{"dims", {15, 8, 8, 1, 1}}}), make_op("multibroadcast", {{"out_lens", {15, 8, 8, 2, 2}}})}); } { auto desc = base_desc.rebase({3, 4, 1, 1}); EXPECT(get_final_lens(desc) == final_lens{12, 1, 1, 2, 2}); EXPECT(get_all_lens(desc) == all_lens{{3, 4}, {1}, {1}, {2}, {2}}); EXPECT(desc.generate() == ops{make_op("unsqueeze", {{"axes", {3, 5}}}), make_op("reshape", {{"dims", {12, 1, 1, 1, 1}}}), make_op("multibroadcast", {{"out_lens", {12, 1, 1, 2, 2}}})}); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/simplify_algebra_test.cpp000066400000000000000000006157001510465702400226040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::simplify_algebra{}, migraphx::dead_code_elimination{}}); } TEST_CASE(simplify_add1) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1}}); auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, one); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, two); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1}}); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m2.add_instruction(migraphx::make_op("add"), x, y); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum2, sum1); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(simplify_add2) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1}}); auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, x); auto sum2 = m1.add_instruction(migraphx::make_op("add"), two, y); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1}}); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m2.add_instruction(migraphx::make_op("add"), x, y); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum2, sum1); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(simplify_add3) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, x); auto sum2 = m1.add_instruction(migraphx::make_op("add"), one, two); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m2.add_instruction(migraphx::make_op("add"), one, sum1); auto sum3 = m2.add_instruction(migraphx::make_op("add"), x, sum2); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(simplify_zero_add_constant) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m1.add_literal(0); m1.add_instruction(migraphx::make_op("add"), zero, x); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); m2.add_instruction(migraphx::make_op("identity"), x); } migraphx::module m3; { auto x = m3.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m3.add_literal(0); m3.add_instruction(migraphx::make_op("add"), x, zero); } run_pass(m3); EXPECT((m1 == m2) and (m2 == m3)); } TEST_CASE(simplify_add_broadcast1) { migraphx::shape inner{migraphx::shape::int32_type, {2}}; migraphx::shape outer{migraphx::shape::int32_type, {1, 2, 3, 3}}; auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}); migraphx::module m1; { auto x = m1.add_parameter("x", outer); auto y = m1.add_parameter("y", outer); auto one = m1.add_literal({inner, {1, 1}}); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal({inner, {2, 2}}); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", outer); auto y = m2.add_parameter("y", outer); auto one = m2.add_literal({inner, {1, 1}}); auto two = m2.add_literal({inner, {2, 2}}); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, two); auto sum1b = m2.add_instruction(b, sum1); auto sum2 = m2.add_instruction(migraphx::make_op("add"), x, y); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum2, sum1b); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(simplify_add_broadcast2) { migraphx::shape inner{migraphx::shape::int32_type, {2}}; migraphx::shape outer{migraphx::shape::int32_type, {1, 2, 3, 3}}; auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}); auto create_program = [&] { migraphx::module m; auto x = m.add_parameter("x", outer); auto y = m.add_parameter("y", outer); auto one = m.add_literal({inner, {1, 1}}); auto oneb = m.add_instruction(b, one); auto two = m.add_literal({outer, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}}); auto sum1 = m.add_instruction(migraphx::make_op("add"), x, y); auto sum2 = m.add_instruction(migraphx::make_op("add"), oneb, two); auto sum3 = m.add_instruction(migraphx::make_op("add"), sum2, sum1); m.add_instruction(pass_op{}, sum3); return m; }; migraphx::module m1 = create_program(); run_pass(m1); migraphx::module m2 = create_program(); EXPECT(m1 == m2); } // TODO: Add test case // TEST_CASE(simplify_add4) [[maybe_unused]] static void simplify_add4() { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1}}); auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto sum1 = m1.add_instruction(migraphx::make_op("add"), one, x); auto sum2 = m1.add_instruction(migraphx::make_op("add"), sum1, y); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum2, two); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1}}); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto sum1 = m2.add_instruction(migraphx::make_op("add"), one, two); auto sum2 = m2.add_instruction(migraphx::make_op("add"), x, y); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum2, sum1); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1 == m2); } TEST_CASE(simplify_mul_conv1) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::int32_type, {1, 128, 28, 28}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {256, 128, 3, 3}})); auto conv = m.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto a = m.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {256}})); auto b = m.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 256, 14, 14}}}), a); auto mul = m.add_instruction(migraphx::make_op("mul"), conv, b); m.add_instruction(pass_op{}, mul); EXPECT(conv->outputs().front()->name() == "mul"); run_pass(m); auto new_conv = std::find_if(m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }); EXPECT(new_conv->outputs().front()->name() != "mul"); } TEST_CASE(simplify_mul_conv2) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::int32_type, {1, 128, 28, 28}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {256, 128, 3, 3}})); auto conv = m.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto a = m.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {256}})); auto unsq_a = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), a); auto b = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 256, 14, 14}}}), unsq_a); auto mul = m.add_instruction(migraphx::make_op("mul"), conv, b); m.add_instruction(pass_op{}, mul); EXPECT(conv->outputs().front()->name() == "mul"); run_pass(m); auto new_conv = std::find_if(m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }); EXPECT(new_conv->outputs().front()->name() != "mul"); } // len = 1 case TEST_CASE(simplify_mul_conv3) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::int32_type, {1, 128, 28, 28}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {256, 128, 3, 3}})); auto conv = m.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto a = m.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {256, 1, 1}, {1, 18, 1}})); auto b = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 256, 14, 14}}}), a); auto mul = m.add_instruction(migraphx::make_op("mul"), conv, b); m.add_instruction(pass_op{}, mul); EXPECT(conv->outputs().front()->name() == "mul"); run_pass(m); auto new_conv = std::find_if(m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }); EXPECT(new_conv->outputs().front()->name() != "mul"); } // Previously broadcasted literal case, should skip TEST_CASE(simplify_mul_conv_skip1) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::int32_type, {1, 128, 28, 28}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {256, 128, 3, 3}})); auto conv = m.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto a = m.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {256, 14, 14}, {1, 0, 0}})); auto b = m.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 256, 14, 14}}}), a); auto mul = m.add_instruction(migraphx::make_op("mul"), conv, b); m.add_instruction(pass_op{}, mul); EXPECT(conv->outputs().front()->name() == "mul"); run_pass(m); auto new_conv = std::find_if(m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }); EXPECT(new_conv->outputs().front()->name() == "mul"); } // Another previously broadcasted literal case, should skip TEST_CASE(simplify_mul_conv_skip2) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::int32_type, {1, 128, 28, 28}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {256, 128, 3, 3}})); auto conv = m.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto a = m.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {256, 14, 14}, {1, 0, 0}})); auto b = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 256, 14, 14}}}), a); auto mul = m.add_instruction(migraphx::make_op("mul"), conv, b); m.add_instruction(pass_op{}, mul); EXPECT(conv->outputs().front()->name() == "mul"); run_pass(m); auto new_conv = std::find_if(m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }); EXPECT(new_conv->outputs().front()->name() == "mul"); } TEST_CASE(simplify_mul_slice_conv1) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 1024, 17, 17}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {768, 1024, 1, 1}})); auto conv = m1.add_instruction(migraphx::make_op("convolution"), x, w); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {384}}}), conv); auto a = m1.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}})); auto b = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 384, 17, 17}}}), a); auto mul = m1.add_instruction(migraphx::make_op("mul"), slice1, b); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {384}}, {"ends", {768}}}), conv); auto add = m1.add_instruction(migraphx::make_op("add"), mul, slice2); m1.add_instruction(pass_op{}, add); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1, 1024, 17, 17}}); auto w = m2.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {768, 1024, 1, 1}})); auto wslice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {384}}}), w); auto a = m2.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}})); auto b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {384, 1024, 1, 1}}}), a); auto mul = m2.add_instruction(migraphx::make_op("mul"), b, wslice1); auto wslice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {384}}, {"ends", {768}}}), w); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), mul, wslice2); auto conv = m2.add_instruction(migraphx::make_op("convolution"), x, concat); auto slice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {384}}}), conv); auto slice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {384}}, {"ends", {768}}}), conv); auto add = m2.add_instruction(migraphx::make_op("add"), slice1, slice2); m2.add_instruction(pass_op{}, add); } EXPECT(m1 == m2); } TEST_CASE(simplify_mul_slice_conv_overlapping_slice) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 1024, 17, 17}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {768, 1024, 1, 1}})); auto conv = m1.add_instruction(migraphx::make_op("convolution"), x, w); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {384}}}), conv); auto a = m1.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}})); auto b = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 384, 17, 17}}}), a); auto mul = m1.add_instruction(migraphx::make_op("mul"), slice1, b); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {383}}, {"ends", {767}}}), conv); auto add = m1.add_instruction(migraphx::make_op("add"), mul, slice2); m1.add_instruction(pass_op{}, add); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(simplify_mul_slice_conv_not_all_slice) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 1024, 17, 17}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {768, 1024, 1, 1}})); auto conv = m1.add_instruction(migraphx::make_op("convolution"), x, w); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {384}}}), conv); auto a = m1.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}})); auto b = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 384, 17, 17}}}), a); auto mul = m1.add_instruction(migraphx::make_op("mul"), slice1, b); auto c = m1.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {1, 768, 17, 17}})); auto add = m1.add_instruction(migraphx::make_op("add"), conv, c); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), mul, add); m1.add_instruction(pass_op{}, concat); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(simplify_mul_add) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto one = m1.add_literal(3); auto two = m1.add_literal(2); auto sum = m1.add_instruction(migraphx::make_op("add"), one, x); auto mul = m1.add_instruction(migraphx::make_op("mul"), sum, two); m1.add_instruction(pass_op{}, mul); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto one = m2.add_literal(3); auto two = m2.add_literal(2); auto mul1 = m2.add_instruction(migraphx::make_op("mul"), two, x); auto mul2 = m2.add_instruction(migraphx::make_op("mul"), two, one); auto sum = m2.add_instruction(migraphx::make_op("add"), mul1, mul2); m2.add_instruction(pass_op{}, sum); } EXPECT(m1 == m2); } TEST_CASE(simplify_dot_add) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {2, 2}}); auto one = m1.add_literal(get_2x2()); auto two = m1.add_literal(get_2x2(1)); auto sum = m1.add_instruction(migraphx::make_op("add"), one, x); auto dot = m1.add_instruction(migraphx::make_op("dot"), sum, two); m1.add_instruction(pass_op{}, dot); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {2, 2}}); auto one = m2.add_literal(get_2x2()); auto two = m2.add_literal(get_2x2(1)); auto dot1 = m2.add_instruction(migraphx::make_op("dot"), x, two); auto dot2 = m2.add_instruction(migraphx::make_op("dot"), one, two); auto sum = m2.add_instruction(migraphx::make_op("add"), dot1, dot2); m2.add_instruction(pass_op{}, sum); } EXPECT(m1 == m2); } TEST_CASE(simplify_conv_add) { migraphx::shape s{migraphx::shape::float_type, {1, 3, 32, 32}}; migraphx::shape ws{migraphx::shape::float_type, {4, 3, 3, 3}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto c = m1.add_literal(migraphx::generate_literal(s, 1)); auto w = m1.add_literal(migraphx::generate_literal(ws, 2)); auto sum = m1.add_instruction(migraphx::make_op("add"), c, x); auto conv = m1.add_instruction(migraphx::make_op("convolution"), sum, w); m1.add_instruction(pass_op{}, conv); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto c = m2.add_literal(migraphx::generate_literal(s, 1)); auto w = m2.add_literal(migraphx::generate_literal(ws, 2)); auto conv1 = m2.add_instruction(migraphx::make_op("convolution"), c, w); auto conv2 = m2.add_instruction(migraphx::make_op("convolution"), x, w); auto sum = m2.add_instruction(migraphx::make_op("add"), conv1, conv2); m2.add_instruction(pass_op{}, sum); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast1) { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 1, 4, 5}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1}}); auto sum = m2.add_instruction(migraphx::make_op("add"), x, y); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast2) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {2, 1, 4, 5}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 1, 1, 1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1, 1, 1, 1}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1, 1, 1, 1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1, 1, 1, 1}}); auto sum = m2.add_instruction(migraphx::make_op("add"), x, y); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_scalar) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {32, 384}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 384}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1, 1}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1, 384}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1, 1}}); auto yb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 384}}}), y); auto sum = m2.add_instruction(migraphx::make_op("add"), x, yb); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_dims) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {2, 384, 768}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {384, 768}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {768}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {384, 768}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {768}}); auto yb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {384, 768}}}), y); auto sum = m2.add_instruction(migraphx::make_op("add"), x, yb); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_dims_single_element) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {2, 1, 4, 5}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 1, 1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1, 1, 1, 1}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1, 1, 1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1, 1, 1, 1}}); auto xs = m2.add_instruction(migraphx::make_op("squeeze"), x); auto xb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 1, 1}}}), xs); auto sum = m2.add_instruction(migraphx::make_op("add"), xb, y); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_dims_single_element_no_squeeze) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {2, 1, 4, 5}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1, 1, 1, 5}}); auto z = m1.add_parameter("z", {migraphx::shape::int32_type, {1, 1, 5}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(b, y); auto zb = m1.add_instruction(b, z); auto sum = m1.add_instruction(migraphx::make_op("where"), xb, yb, zb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1, 1, 1, 5}}); auto z = m2.add_parameter("z", {migraphx::shape::int32_type, {1, 1, 5}}); auto ys = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0, 1, 2}}}), y); auto zs = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0, 1}}}), z); auto xb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {5}}}), x); auto sum = m2.add_instruction(migraphx::make_op("where"), xb, ys, zs); auto sumb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 3}, {"out_lens", {2, 1, 4, 5}}}), sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_dims_broadcasted) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {2, 384, 768}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 768}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1, 768}}); auto xb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {384, 768}}}), x); auto xbb = m1.add_instruction(b, xb); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xbb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1, 768}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1, 768}}); auto sum = m2.add_instruction(migraphx::make_op("add"), x, y); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_dims_broadcasted_scalar) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {2, 384}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1, 384}}); auto xb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 1}}}), x); auto xbb = m1.add_instruction(b, xb); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xbb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1, 384}}); auto xb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 384}}}), x); auto sum = m2.add_instruction(migraphx::make_op("add"), xb, y); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_broadcasts) { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 24, 112, 112}}}); auto mb = migraphx::make_op("multibroadcast", {{"out_lens", {1, 24, 112, 112}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {24}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {24, 1, 1}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(mb, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {24}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {24, 1, 1}}); auto ys = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {1, 2}}}), y); auto sum = m2.add_instruction(migraphx::make_op("add"), x, ys); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_broadcasts_scalar_vector) { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 24, 112, 112}}}); auto mb = migraphx::make_op("multibroadcast", {{"out_lens", {1, 24, 112, 112}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {24}}); auto xb = m1.add_instruction(mb, x); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {24}}); auto xb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {24}}}), x); auto sum = m2.add_instruction(migraphx::make_op("add"), xb, y); auto sumb = m2.add_instruction(b, sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_broadcasts_diff_axis) { auto b = migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {1, 64, 112, 112}}}); auto mb = migraphx::make_op("multibroadcast", {{"out_lens", {1, 64, 112, 112}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 64}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {64, 1, 1}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(mb, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1, 64}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {64, 1, 1}}); auto xs = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), x); auto ys = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {1, 2}}}), y); auto sum = m2.add_instruction(migraphx::make_op("add"), xs, ys); auto sumb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 64, 112, 112}}}), sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_different_broadcasts_diff_dims) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {1, 5, 10}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {256}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1, 256, 31, 31}}); auto xb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {64, 256, 31, 31}}}), x); auto yb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {64, 256, 31, 31}}}), y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {256}}); auto y = m2.add_parameter("y", {migraphx::shape::int32_type, {1, 256, 31, 31}}); auto xb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {256, 31, 31}}}), x); auto ys = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), y); auto sum = m2.add_instruction(migraphx::make_op("add"), xb, ys); auto sumb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {64, 256, 31, 31}}}), sum); m2.add_instruction(pass_op{}, sumb); } EXPECT(m1 == m2); } TEST_CASE(simplify_inner_broadcast_no_common_axis) { auto b = migraphx::make_op("multibroadcast", {{"out_lens", {1, 5, 10}}}); migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {5, 10}}); auto y = m1.add_parameter("y", {migraphx::shape::int32_type, {1, 5, 1}}); auto xb = m1.add_instruction(b, x); auto yb = m1.add_instruction(b, y); auto sum = m1.add_instruction(migraphx::make_op("add"), xb, yb); m1.add_instruction(pass_op{}, sum); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(simplify_add_conv1) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 128, 28, 28}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 3, 3}})); auto y = m.add_parameter("y", {migraphx::shape::float_type, {1, 128, 28, 28}}); auto v = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 3, 3}})); auto conv1 = m.add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = m.add_instruction(migraphx::make_op("convolution"), y, v); auto sum = m.add_instruction(migraphx::make_op("add"), conv1, conv2); m.add_instruction(pass_op{}, sum); auto s = m.get_output_shapes().back(); run_pass(m); EXPECT(s == m.get_output_shapes().back()); EXPECT(std::count_if( m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }) == 1); } TEST_CASE(simplify_add_conv_no_fusion_7x7_diff_strides) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 128, 14, 14}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 7, 7}})); auto y = m.add_parameter("y", {migraphx::shape::float_type, {1, 128, 28, 28}}); auto v = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 7, 7}})); auto conv1 = m.add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = m.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {3, 3}}}), y, v); auto sum = m.add_instruction(migraphx::make_op("add"), conv1, conv2); m.add_instruction(pass_op{}, sum); auto s = m.get_output_shapes().back(); run_pass(m); EXPECT(s == m.get_output_shapes().back()); // No fusion EXPECT(std::count_if( m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }) == 2); } TEST_CASE(simplify_add_conv_1x1_diff_strides1) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 128, 14, 14}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto y = m.add_parameter("y", {migraphx::shape::float_type, {1, 128, 28, 28}}); auto v = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto conv1 = m.add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = m.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 2}}}), y, v); auto sum = m.add_instruction(migraphx::make_op("add"), conv1, conv2); m.add_instruction(pass_op{}, sum); auto s = m.get_output_shapes().back(); run_pass(m); EXPECT(s == m.get_output_shapes().back()); EXPECT(std::count_if( m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }) == 1); } TEST_CASE(simplify_add_conv_1x1_diff_strides2) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 128, 28, 28}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto y = m.add_parameter("y", {migraphx::shape::float_type, {1, 128, 14, 14}}); auto v = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto conv1 = m.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 2}}}), x, w); auto conv2 = m.add_instruction(migraphx::make_op("convolution"), y, v); auto sum = m.add_instruction(migraphx::make_op("add"), conv1, conv2); m.add_instruction(pass_op{}, sum); auto s = m.get_output_shapes().back(); run_pass(m); EXPECT(s == m.get_output_shapes().back()); EXPECT(std::count_if( m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }) == 1); } TEST_CASE(simplify_add_conv_1x1_diff_strides_odd) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 54, 83, 83}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {54, 54, 1, 1}})); auto y = m.add_parameter("y", {migraphx::shape::float_type, {1, 54, 165, 165}}); auto v = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {54, 54, 1, 1}})); auto conv1 = m.add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = m.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 2}}}), y, v); auto sum = m.add_instruction(migraphx::make_op("add"), conv1, conv2); m.add_instruction(pass_op{}, sum); auto s = m.get_output_shapes().back(); run_pass(m); EXPECT(s == m.get_output_shapes().back()); EXPECT(std::count_if( m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }) == 1); } TEST_CASE(simplify_add_conv_no_fusion_asymetrical_strides1) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 128, 28, 14}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto y = m.add_parameter("y", {migraphx::shape::float_type, {1, 128, 14, 14}}); auto v = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto conv1 = m.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 1}}}), x, w); auto conv2 = m.add_instruction(migraphx::make_op("convolution"), y, v); auto sum = m.add_instruction(migraphx::make_op("add"), conv1, conv2); m.add_instruction(pass_op{}, sum); auto s = m.get_output_shapes().back(); run_pass(m); EXPECT(s == m.get_output_shapes().back()); // No fusion EXPECT(std::count_if( m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }) == 2); } TEST_CASE(simplify_add_conv_no_fusion_asymetrical_strides2) { migraphx::module m; auto x = m.add_parameter("x", {migraphx::shape::float_type, {1, 128, 14, 14}}); auto w = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto y = m.add_parameter("y", {migraphx::shape::float_type, {1, 128, 28, 14}}); auto v = m.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto conv1 = m.add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = m.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 1}}}), y, v); auto sum = m.add_instruction(migraphx::make_op("add"), conv1, conv2); m.add_instruction(pass_op{}, sum); auto s = m.get_output_shapes().back(); run_pass(m); EXPECT(s == m.get_output_shapes().back()); // No fusion EXPECT(std::count_if( m.begin(), m.end(), [](auto&& ins) { return ins.name() == "convolution"; }) == 2); } TEST_CASE(simplify_concat_unpack_int4) { auto s = migraphx::shape{migraphx::shape::int8_type, {11008, 2048}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto unpack1 = m1.add_instruction(migraphx::make_op("unpack_int4"), x); auto unpack2 = m1.add_instruction(migraphx::make_op("unpack_int4"), y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), unpack1, unpack2); m1.add_return({concat}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto unpack = m2.add_instruction(migraphx::make_op("unpack_int4"), concat); m2.add_return({unpack}); } EXPECT(m1 == m2); } TEST_CASE(simplify_concat_add_relu) { auto s = migraphx::shape{migraphx::shape::int32_type, {1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto one = m1.add_literal({s, {1}}); auto two = m1.add_literal({s, {2}}); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, one); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, two); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), relu1, relu2); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto one = m2.add_literal({s, {1}}); auto two = m2.add_literal({s, {2}}); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto sum = m2.add_instruction(migraphx::make_op("add"), concat1, concat2); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); m2.add_instruction(pass_op{}, relu); } EXPECT(m1 == m2); } TEST_CASE(simplify_concat_add_relu_partial) { auto s = migraphx::shape{migraphx::shape::int32_type, {1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto one = m1.add_literal({s, {1}}); auto two = m1.add_literal({s, {2}}); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, one); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, two); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto sum3 = m1.add_instruction(migraphx::make_op("add"), x, y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), sum3, relu1, relu2); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto one = m2.add_literal({s, {1}}); auto two = m2.add_literal({s, {2}}); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto sum1 = m2.add_instruction(migraphx::make_op("add"), concat1, concat2); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m2.add_instruction(migraphx::make_op("add"), x, y); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), sum2, relu); m2.add_instruction(pass_op{}, concat); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_concat_add_relu_partial_broadcast) { auto s = migraphx::shape{migraphx::shape::int32_type, {2, 1, 4, 5}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 1, 4, 5}}}); auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), sum, oneb, twob); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 2, 4, 5}}}); auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto concatb = m2.add_instruction(b, concat1); auto sum = m2.add_instruction(migraphx::make_op("add"), x, y); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), sum, concatb); m2.add_instruction(pass_op{}, concat2); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_concat_add_relu_broadcast_different_axis) { auto s = migraphx::shape{migraphx::shape::int32_type, {2, 1, 4, 5}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 1, 4, 5}}}); auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), relu1, relu2); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 2, 4, 5}}}); auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto concat2b = m2.add_instruction(b, concat2); auto sum = m2.add_instruction(migraphx::make_op("add"), concat1, concat2b); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); m2.add_instruction(pass_op{}, relu); } EXPECT(m1 == m2); } TEST_CASE(simplify_concat_add_relu_broadcast_same_axis) { auto s = migraphx::shape{migraphx::shape::int32_type, {2, 1, 4, 5}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 1, 4, 5}}}); auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), relu1, relu2); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 1, 4, 5}}}); auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto one = m2.add_literal(1); auto oneb = m2.add_instruction(b, one); auto two = m2.add_literal(2); auto twob = m2.add_instruction(b, two); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), oneb, twob); auto sum = m2.add_instruction(migraphx::make_op("add"), concat1, concat2); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); m2.add_instruction(pass_op{}, relu); } EXPECT(m1 == m2); } TEST_CASE(simplify_concat_mul_broadcast_diff_size) { auto s1 = migraphx::shape{migraphx::shape::int32_type, {1, 38, 154, 64}}; auto s2 = migraphx::shape{migraphx::shape::int32_type, {1, 38, 4096, 64}}; auto s3 = migraphx::shape{migraphx::shape::int32_type, {64}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto lit1 = m1.add_literal(migraphx::generate_literal(s3, 1)); auto lit2 = m1.add_literal(migraphx::generate_literal(s3, 2)); auto lit1b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), lit1); auto lit2b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s2.lens()}}), lit2); auto mul1 = m1.add_instruction(migraphx::make_op("mul"), x, lit1b); auto mul2 = m1.add_instruction(migraphx::make_op("mul"), y, lit2b); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), mul1, mul2); m1.add_return({concat}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(simplify_concat_clip) { auto s = migraphx::shape{migraphx::shape::int32_type, {1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto min = m1.add_literal({s, {0}}); auto max = m1.add_literal({s, {10}}); auto clip1 = m1.add_instruction(migraphx::make_op("clip"), x, min, max); auto clip2 = m1.add_instruction(migraphx::make_op("clip"), y, min, max); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), clip1, clip2); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto min = m2.add_literal({s, {0}}); auto max = m2.add_literal({s, {10}}); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), min, min); auto concat3 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), max, max); auto clip = m2.add_instruction(migraphx::make_op("clip"), concat1, concat2, concat3); m2.add_instruction(pass_op{}, clip); } EXPECT(m1 == m2); } TEST_CASE(concat_convert_fusion) { auto s = migraphx::shape{migraphx::shape::float_type, {64}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xh = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::half_type)}}), x); auto yh = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::half_type)}}), y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), xh, yh); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto concath = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::half_type)}}), concat); m2.add_instruction(pass_op{}, concath); } EXPECT(m1 == m2); } TEST_CASE(simplify_div_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1}}); auto two = m1.add_literal(2.0f); m1.add_instruction(migraphx::make_op("div"), x, two); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {1}}); auto two = m2.add_literal(2.0f); auto recip = m2.insert_instruction(std::next(two), migraphx::make_op("recip"), two); m2.add_instruction(migraphx::make_op("mul"), x, recip); } EXPECT(m1 == m2); } TEST_CASE(simplify_div_integral_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto two = m1.add_literal(2); m1.add_instruction(migraphx::make_op("div"), x, two); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(simplify_unit_mult_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto unit = m1.add_literal(1); m1.add_instruction(migraphx::make_op("mul"), x, unit); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); m2.add_instruction(migraphx::make_op("identity"), x); } EXPECT(m1 == m2); } TEST_CASE(simplify_unit_mult_const2) { migraphx::module m1; { auto unit = m1.add_literal(1); auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); m1.add_instruction(migraphx::make_op("mul"), unit, x); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); m2.add_instruction(migraphx::make_op("identity"), x); } EXPECT(m1 == m2); } TEST_CASE(simplify_unit_mult_const_vec) { migraphx::shape unit_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto unit = m1.add_literal({unit_shape, {1, 1}}); auto x = m1.add_parameter("x", x_shape); auto unitb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), unit); m1.add_instruction(migraphx::make_op("mul"), x, unitb); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", x_shape); m2.add_instruction(migraphx::make_op("identity"), x); } EXPECT(m1 == m2); } TEST_CASE(simplify_unit_mult_const_vec2) { migraphx::shape unit_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto unit = m1.add_literal({unit_shape, {1, 1}}); auto x = m1.add_parameter("x", x_shape); auto unitb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), unit); m1.add_instruction(migraphx::make_op("mul"), unitb, x); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", x_shape); m2.add_instruction(migraphx::make_op("identity"), x); } EXPECT(m1 == m2); } TEST_CASE(simplify_unit_div_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto unit = m1.add_literal(1); auto div = m1.add_instruction(migraphx::make_op("div"), x, unit); m1.add_return({div}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); m2.add_return({x}); } EXPECT(m1 == m2); } TEST_CASE(simplify_unit_div_const_vec) { migraphx::shape unit_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto unit = m1.add_literal({unit_shape, {1, 1}}); auto x = m1.add_parameter("x", x_shape); auto unitb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), unit); m1.add_instruction(migraphx::make_op("div"), x, unitb); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", x_shape); m2.add_instruction(migraphx::make_op("identity"), x); } EXPECT(m1 == m2); } TEST_CASE(simplify_neg_unit_mult_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 6}}); auto unit = m1.add_literal( migraphx::literal{{migraphx::shape::int32_type, {1, 6}}, std::vector(6, -1)}); m1.add_instruction(migraphx::make_op("mul"), x, unit); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1, 6}}); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("identity"), x2); } EXPECT((m1 == m2)); } TEST_CASE(simplify_neg_unit_mult_const2) { migraphx::module m1; { auto unit = m1.add_literal(-1); auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); m1.add_instruction(migraphx::make_op("mul"), unit, x); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("identity"), x2); } EXPECT((m1 == m2)); } TEST_CASE(simplify_neg_unit_mult_const_add) { migraphx::module m1; { auto unit = m1.add_literal(-1); auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto x2 = m1.add_instruction(migraphx::make_op("mul"), unit, x); m1.add_instruction(migraphx::make_op("add"), x2, x2); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("add"), x2, x2); } EXPECT((m1 == m2)); } TEST_CASE(simplify_neg_unit_mul_const_vec) { migraphx::shape unit_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto unit = m1.add_literal({unit_shape, {-1, -1}}); auto x = m1.add_parameter("x", x_shape); auto unitb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), unit); m1.add_instruction(migraphx::make_op("mul"), x, unitb); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", x_shape); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("identity"), x2); } EXPECT(m1 == m2); } TEST_CASE(simplify_neg_unit_mul_const_vec2) { migraphx::shape zero_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto unit = m1.add_literal({zero_shape, {-1, -1}}); auto x = m1.add_parameter("x", x_shape); auto unitb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), unit); m1.add_instruction(migraphx::make_op("mul"), unitb, x); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", x_shape); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("identity"), x2); } EXPECT(m1 == m2); } TEST_CASE(simplify_neg_unit_div_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto unit = m1.add_literal(-1); m1.add_instruction(migraphx::make_op("div"), x, unit); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("identity"), x2); } EXPECT(m1 == m2); } TEST_CASE(simplify_neg_unit_div_const_vec) { migraphx::shape unit_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto unit = m1.add_literal({unit_shape, {-1, -1}}); auto x = m1.add_parameter("x", x_shape); auto unitb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), unit); m1.add_instruction(migraphx::make_op("div"), x, unitb); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", x_shape); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("identity"), x2); } EXPECT(m1 == m2); } TEST_CASE(simplify_sub_zero_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m1.add_literal(0); m1.add_instruction(migraphx::make_op("sub"), x, zero); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); m2.add_instruction(migraphx::make_op("identity"), x); } EXPECT(m1 == m2); } TEST_CASE(simplify_sub_zero_const_vec) { migraphx::shape zero_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto zero = m1.add_literal({zero_shape, {0, 0}}); auto x = m1.add_parameter("x", x_shape); auto zerob = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), zero); m1.add_instruction(migraphx::make_op("sub"), x, zerob); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", x_shape); m2.add_instruction(migraphx::make_op("identity"), x); } EXPECT(m1 == m2); } TEST_CASE(simplify_sub_neg_zero_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m1.add_literal(0); m1.add_instruction(migraphx::make_op("sub"), zero, x); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("identity"), x2); } EXPECT(m1 == m2); } TEST_CASE(simplify_sub_neg_zero_const_vec) { migraphx::shape zero_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto zero = m1.add_literal({zero_shape, {0, 0}}); auto x = m1.add_parameter("x", x_shape); auto zerob = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), zero); m1.add_instruction(migraphx::make_op("sub"), zerob, x); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", x_shape); auto x2 = m2.add_instruction(migraphx::make_op("neg"), x); m2.add_instruction(migraphx::make_op("identity"), x2); } EXPECT(m1 == m2); } TEST_CASE(simplify_sub_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto two = m1.add_literal(2); m1.add_instruction(migraphx::make_op("sub"), x, two); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto two = m2.add_literal(2); auto neg = m2.insert_instruction(std::next(two), migraphx::make_op("neg"), two); m2.add_instruction(migraphx::make_op("add"), x, neg); } EXPECT(m1 == m2); } TEST_CASE(simplify_zero_mult_const) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m1.add_literal(0); auto mul_ins = m1.add_instruction(migraphx::make_op("mul"), x, zero); m1.add_return({mul_ins}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m2.add_literal(0); auto reshape_zero = m2.add_instruction( migraphx::make_op("reshape", {{"dims", x->get_shape().lens()}}), zero); m2.add_return({reshape_zero}); } EXPECT(m1 == m2); } TEST_CASE(simplify_zero_mult_const2) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m1.add_literal(0); auto mul_ins = m1.add_instruction(migraphx::make_op("mul"), zero, x); m1.add_return({mul_ins}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m2.add_literal(0); auto reshape_zero = m2.add_instruction( migraphx::make_op("reshape", {{"dims", x->get_shape().lens()}}), zero); m2.add_return({reshape_zero}); } EXPECT(m1 == m2); } TEST_CASE(simplify_zero_mult_unsqueeze) { // unsqueeze axis 0 migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m1.add_literal(0); auto mul_x_zero = m1.add_instruction(migraphx::make_op("mul"), x, zero); auto unsqueeze_ins = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mul_x_zero); m1.add_return({unsqueeze_ins}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m2.add_literal(0); auto reshape_zero = m2.add_instruction( migraphx::make_op("reshape", {{"dims", x->get_shape().lens()}}), zero); auto unsqueeze_ins = m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), reshape_zero); m2.add_return({unsqueeze_ins}); } EXPECT(m1 == m2); // unsqueeze axis 1 migraphx::module m3; { auto x = m3.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m3.add_literal(0); auto mul_x_zero = m3.add_instruction(migraphx::make_op("mul"), x, zero); auto unsqueeze_ins = m3.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), mul_x_zero); m3.add_return({unsqueeze_ins}); } run_pass(m3); migraphx::module m4; { auto x = m4.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto zero = m4.add_literal(0); auto reshape_zero = m4.add_instruction( migraphx::make_op("reshape", {{"dims", x->get_shape().lens()}}), zero); auto unsqueeze_ins = m4.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), reshape_zero); m4.add_return({unsqueeze_ins}); } EXPECT(m3 == m4); } TEST_CASE(simplify_zero_mul_const_vec) { migraphx::shape zero_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto zero = m1.add_literal({zero_shape, {0, 0}}); auto x = m1.add_parameter("x", x_shape); auto zerob = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), zero); auto mul_ins = m1.add_instruction(migraphx::make_op("mul"), x, zerob); m1.add_return({mul_ins}); } run_pass(m1); migraphx::module m2; { auto zero = m2.add_literal({zero_shape, {0, 0}}); m2.add_parameter("x", x_shape); auto zerob = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), zero); m2.add_return({zerob}); } EXPECT(m1 == m2); } TEST_CASE(simplify_zero_mul_const_vec2) { migraphx::shape zero_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto zero = m1.add_literal({zero_shape, {0, 0}}); auto x = m1.add_parameter("x", x_shape); auto zerob = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), zero); auto mul_ins = m1.add_instruction(migraphx::make_op("mul"), zerob, x); m1.add_return({mul_ins}); } run_pass(m1); migraphx::module m2; { auto zero = m2.add_literal({zero_shape, {0, 0}}); m2.add_parameter("x", x_shape); auto zerob = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), zero); m2.add_return({zerob}); } EXPECT(m1 == m2); } TEST_CASE(simplify_zero_div_const) { migraphx::module m1; { auto zero = m1.add_literal(0); auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto div_ins = m1.add_instruction(migraphx::make_op("div"), zero, x); m1.add_return({div_ins}); } run_pass(m1); migraphx::module m2; { auto zero = m2.add_literal(0); auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto reshape_ins = m2.add_instruction( migraphx::make_op("reshape", {{"dims", x->get_shape().lens()}}), zero); m2.add_return({reshape_ins}); } EXPECT(m1 == m2); } TEST_CASE(simplify_zero_div_const_vec) { migraphx::shape zero_shape{migraphx::shape::int32_type, {2}}; migraphx::shape x_shape{migraphx::shape::int32_type, {1, 2, 3, 3}}; migraphx::module m1; { auto x = m1.add_parameter("x", x_shape); auto zero = m1.add_literal({zero_shape, {0, 0}}); auto zerob = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), zero); auto div_ins = m1.add_instruction(migraphx::make_op("div"), zerob, x); m1.add_return({div_ins}); } run_pass(m1); migraphx::module m2; { m2.add_parameter("x", x_shape); auto zero = m2.add_literal({zero_shape, {0, 0}}); auto zerob = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 2, 3, 3}}}), zero); m2.add_return({zerob}); } EXPECT(m1 == m2); } TEST_CASE(simplify_rsqrt) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), x); m1.add_instruction(migraphx::make_op("recip"), sqrt); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1}}); m2.add_instruction(migraphx::make_op("rsqrt"), x); } EXPECT(m1 == m2); } TEST_CASE(simplify_rsqrt_multi_use) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1}}); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), x); auto add = m1.add_instruction(migraphx::make_op("add"), sqrt, sqrt); auto rsqrt = m1.add_instruction(migraphx::make_op("recip"), sqrt); m1.add_instruction(migraphx::make_op("add"), rsqrt, add); } migraphx::module m2{m1}; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(simplify_slice_concat) { auto s = migraphx::shape{migraphx::shape::float_type, {256}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xslice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {128}}}), x); auto xslice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {128}}, {"ends", {256}}}), x); auto yslice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {128}}}), y); auto yslice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {128}}, {"ends", {256}}}), y); auto concat = m1.add_instruction( migraphx::make_op("concat", {{"axis", 0}}), xslice1, xslice2, yslice1, yslice2); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); m2.add_instruction(pass_op{}, concat); } EXPECT(m1 == m2); } TEST_CASE(simplify_slice_concat_non_uniform) { auto s = migraphx::shape{migraphx::shape::float_type, {256}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xslice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {64}}}), x); auto xslice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {64}}, {"ends", {192}}}), x); auto xslice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {192}}, {"ends", {256}}}), x); auto yslice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {64}}}), y); auto yslice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {64}}, {"ends", {192}}}), y); auto yslice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {192}}, {"ends", {256}}}), y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), xslice1, xslice2, xslice3, yslice1, yslice2, yslice3); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); m2.add_instruction(pass_op{}, concat); } EXPECT(m1 == m2); } TEST_CASE(simplify_slice_concat_flipped) { auto s = migraphx::shape{migraphx::shape::float_type, {256}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xslice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {64}}}), x); auto xslice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {192}}, {"ends", {256}}}), x); auto xslice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {64}}, {"ends", {192}}}), x); auto yslice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {64}}}), y); auto yslice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {192}}, {"ends", {256}}}), y); auto yslice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {64}}, {"ends", {192}}}), y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), xslice1, xslice2, xslice3, yslice1, yslice2, yslice3); m1.add_instruction(pass_op{}, concat); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(simplify_slice_concat_interleaved_non_slice) { // Matched by matcher find_split_concat, but no substitution because the "slice" // instructions in the input list have a different instruction mixed in migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {4, 3, 3, 3}}; auto x = m1.add_parameter("x", s); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {1}}}), x); auto relu = m1.add_instruction(migraphx::make_op("relu"), x); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1}}, {"ends", {3}}}), x); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), slice1, relu, slice2); m1.add_instruction(pass_op{}, concat); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_add_relu) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add = m1.add_instruction(migraphx::make_op("add"), relu1, relu2); m1.add_instruction(pass_op{}, add); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 2, 4}}}); auto input = m2.add_parameter("input", s); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto concatb = m2.add_instruction(b, concat); auto sum = m2.add_instruction(migraphx::make_op("add"), input, concatb); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), relu); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), relu); auto add = m2.add_instruction(migraphx::make_op("add"), x, y); m2.add_instruction(pass_op{}, add); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_add_flipped_input) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), twob, y); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add = m1.add_instruction(migraphx::make_op("add"), relu1, relu2); m1.add_instruction(pass_op{}, add); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 2, 4}}}); auto input = m2.add_parameter("input", s); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto concatb = m2.add_instruction(b, concat); auto sum = m2.add_instruction(migraphx::make_op("add"), input, concatb); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), relu); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), relu); auto add = m2.add_instruction(migraphx::make_op("add"), x, y); m2.add_instruction(pass_op{}, add); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_non_comm_flipped_input) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("sub"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("sub"), twob, y); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add = m1.add_instruction(migraphx::make_op("sub"), relu1, relu2); m1.add_instruction(pass_op{}, add); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m2.add_parameter("input", s); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m2.add_literal(1); auto neg_one = m2.add_instruction(migraphx::make_op("neg"), one); auto oneb = m2.add_instruction(b, neg_one); auto two = m2.add_literal(2); auto twob = m2.add_instruction(b, two); auto sum1 = m2.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m2.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m2.add_instruction(migraphx::make_op("sub"), twob, y); auto relu2 = m2.add_instruction(migraphx::make_op("relu"), sum2); auto add = m2.add_instruction(migraphx::make_op("sub"), relu1, relu2); m2.add_instruction(pass_op{}, add); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_reduce0) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto two = m1.add_literal(2); auto arx = m1.add_instruction(migraphx::make_op("contiguous"), x); auto ary = m1.add_instruction(migraphx::make_op("contiguous"), y); auto rmax0 = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1}}}), x); auto rmin0 = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 1}}}), x); auto rmax1 = m1.add_instruction(migraphx::make_op("gather", {{"axis", 1}}), arx, one); auto rmin1 = m1.add_instruction(migraphx::make_op("gather", {{"axis", 1}}), ary, two); auto rmax2 = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1}}}), y); auto rmin2 = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 1}}}), y); m1.add_return({rmax0, rmin0, rmax1, rmin1, rmax2, rmin2}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_reduce1) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto rmax0 = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 2}}}), x); auto rmin0 = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 2}}}), x); auto rmax2 = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 2}}}), y); auto rmin2 = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 2}}}), y); m1.add_return({rmax0, rmin0, rmax2, rmin2}); } migraphx::module m2; { auto input = m2.add_parameter("input", s); auto rmn = m2.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 2}}}), input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), rmn); auto rmx = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 2}}}), input); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), rmx); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), rmn); auto slc3 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), rmx); m2.add_return({slc3, slc2, slc1, slc0}); } run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_reduce2) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto rmax0 = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 2}}}), x); auto rmin0 = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 1}}}), x); auto rmax2 = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 2}}}), y); auto rmin2 = m1.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 1}}}), y); m1.add_return({rmax0, rmin0, rmax2, rmin2}); } migraphx::module m2; { auto input = m2.add_parameter("input", s); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto rmn1 = m2.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 1}}}), x); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto rmn2 = m2.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0, 1}}}), y); auto rms = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 2}}}), input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), rms); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), rms); m2.add_return({slc1, rmn2, slc0, rmn1}); } run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_add_relu_reshape) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto r = migraphx::make_op("reshape", {{"dims", {3, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto reshape1 = m1.add_instruction(r, relu1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto reshape2 = m1.add_instruction(r, relu2); auto add = m1.add_instruction(migraphx::make_op("add"), reshape1, reshape2); m1.add_instruction(pass_op{}, add); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 2, 4}}}); auto input = m2.add_parameter("input", s); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto concatb = m2.add_instruction(b, concat); auto sum = m2.add_instruction(migraphx::make_op("add"), input, concatb); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), relu); auto rsp1 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {3, 4}}}), slc1); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), relu); auto rsp2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {3, 4}}}), slc2); auto add = m2.add_instruction(migraphx::make_op("add"), rsp1, rsp2); m2.add_instruction(pass_op{}, add); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_mul_broadcast_diff_size) { auto s1 = migraphx::shape{migraphx::shape::int32_type, {1, 38, 4250, 64}}; auto s2 = migraphx::shape{migraphx::shape::int32_type, {64}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto lit1 = m1.add_literal(migraphx::generate_literal(s2, 1)); auto lit2 = m1.add_literal(migraphx::generate_literal(s2, 2)); auto split1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {154}}}), x); auto split2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {154}}, {"ends", {4250}}}), x); auto lit1b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 38, 154, 64}}}), lit1); auto lit2b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 38, 4096, 64}}}), lit2); auto mul1 = m1.add_instruction(migraphx::make_op("mul"), split1, lit1b); auto mul2 = m1.add_instruction(migraphx::make_op("mul"), split2, lit2b); m1.add_return({mul1, mul2}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } // Do not fuse with inter split group dependency TEST_CASE(find_splits_inter_group_dependency) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), x); auto sum1 = m1.add_instruction(migraphx::make_op("add"), relu1, x); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), y); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, relu2); m1.add_return({sum1, sum2}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(find_splits_for_pointwise0) { auto s = migraphx::shape{migraphx::shape::float_type, {3, 2, 4}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto input = mm->add_parameter("input", s); auto x = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto relu_x = add_pointwise(p1, "main:pointwise0", {x}, single_pointwise("relu")); auto relu_y = add_pointwise(p1, "main:pointwise1", {y}, single_pointwise("relu")); mm->add_return({relu_x, relu_y}); } migraphx::run_passes(p1, {migraphx::simplify_algebra{}, migraphx::dead_code_elimination{}}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto input = mm->add_parameter("input", s); auto relu = add_pointwise(p2, "main:pointwise0", {input}, single_pointwise("relu")); auto relu_x = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), relu); auto relu_y = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), relu); mm->add_return({relu_x, relu_y}); } EXPECT(p1 == p2); } TEST_CASE(find_splits_for_pointwise1) { auto s = migraphx::shape{migraphx::shape::float_type, {3, 2, 4}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto input = mm->add_parameter("input", s); auto x = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto sig_add_x = add_pointwise(p1, "main:pointwise0", {x}, [=](auto* pm, const auto& inputs) { auto sig = pm->add_instruction(migraphx::make_op("sigmoid"), inputs[0]); return pm->add_instruction(migraphx::make_op("add"), sig, inputs[0]); }); auto sig_add_y = add_pointwise(p1, "main:pointwise1", {y}, [=](auto* pm, const auto& inputs) { auto sig = pm->add_instruction(migraphx::make_op("sigmoid"), inputs[0]); return pm->add_instruction(migraphx::make_op("add"), sig, inputs[0]); }); mm->add_return({sig_add_x, sig_add_y}); } migraphx::run_passes(p1, {migraphx::simplify_algebra{}, migraphx::dead_code_elimination{}}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto input = mm->add_parameter("input", s); auto sig_add = add_pointwise(p2, "main:pointwise0", {input}, [=](auto* pm, const auto& inputs) { auto sig = pm->add_instruction(migraphx::make_op("sigmoid"), inputs[0]); return pm->add_instruction(migraphx::make_op("add"), sig, inputs[0]); }); auto relu_x = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), sig_add); auto relu_y = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), sig_add); mm->add_return({relu_x, relu_y}); } EXPECT(p1 == p2); } TEST_CASE(find_splits_for_pointwise2) { // Note: has inter-split dependency with sigmoid(x) + x so first simplify_algebra should be a // noop auto s = migraphx::shape{migraphx::shape::float_type, {3, 2, 4}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto input = mm->add_parameter("input", s); auto x = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto sig_x = mm->add_instruction(migraphx::make_op("sigmoid"), x); auto add_x = mm->add_instruction(migraphx::make_op("add"), sig_x, x); auto sig_y = mm->add_instruction(migraphx::make_op("sigmoid"), y); auto add_y = mm->add_instruction(migraphx::make_op("add"), sig_y, y); mm->add_return({add_x, add_y}); } migraphx::run_passes(p1, {migraphx::simplify_algebra{}, migraphx::dead_code_elimination{}, migraphx::fuse_pointwise{}, migraphx::dead_code_elimination{}, migraphx::simplify_algebra{}, migraphx::dead_code_elimination{}}); migraphx::program p2; { auto* mm = p2.get_main_module(); auto input = mm->add_parameter("input", s); auto sig_add = add_pointwise(p2, "main:pointwise0", {input}, [=](auto* pm, const auto& inputs) { auto sig = pm->add_instruction(migraphx::make_op("sigmoid"), inputs[0]); return pm->add_instruction(migraphx::make_op("add"), sig, inputs[0]); }); auto relu_x = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), sig_add); auto relu_y = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), sig_add); mm->add_return({relu_x, relu_y}); } EXPECT(p1 == p2); } TEST_CASE(simplify_slice_different_axis) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4, 2}}; migraphx::module m1; { auto r = migraphx::make_op("reshape", {{"dims", {3, 2, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {1}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4, 2}}}), one); auto two = m1.add_literal(2); auto twob = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 3}, {"out_lens", {3, 2, 4, 1}}}), two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto reshape1 = m1.add_instruction(r, relu1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto reshape2 = m1.add_instruction(r, relu2); auto add = m1.add_instruction(migraphx::make_op("add"), reshape1, reshape2); m1.add_instruction(pass_op{}, add); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_slice_missing_begining_slice) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 3, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {2}}, {"ends", {3}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add = m1.add_instruction(migraphx::make_op("add"), relu1, relu2); m1.add_instruction(pass_op{}, add); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_slice_missing_middle_slice) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 3, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {2}}, {"ends", {3}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add = m1.add_instruction(migraphx::make_op("add"), relu1, relu2); m1.add_instruction(pass_op{}, add); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_slice_missing_end_slice) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 3, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add = m1.add_instruction(migraphx::make_op("add"), relu1, relu2); m1.add_instruction(pass_op{}, add); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_add_relu_concat_same_axis) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), relu1, relu2); m1.add_instruction(pass_op{}, concat); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 2, 4}}}); auto input = m2.add_parameter("input", s); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto concatb = m2.add_instruction(b, concat); auto sum = m2.add_instruction(migraphx::make_op("add"), input, concatb); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); m2.add_instruction(pass_op{}, relu); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_add_relu_multi_axes) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4, 6}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4, 3}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1, 3}}, {"starts", {0, 0}}, {"ends", {1, 3}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1, 3}}, {"starts", {1, 3}}, {"ends", {2, 6}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add = m1.add_instruction(migraphx::make_op("add"), relu1, relu2); m1.add_instruction(pass_op{}, add); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_add_relu_used_multiple_split1) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add1 = m1.add_instruction(migraphx::make_op("add"), relu1, relu2); auto add2 = m1.add_instruction(migraphx::make_op("add"), x, add1); m1.add_instruction(pass_op{}, add2); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 2, 4}}}); auto input = m2.add_parameter("input", s); auto slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto concatb = m2.add_instruction(b, concat); auto sum = m2.add_instruction(migraphx::make_op("add"), input, concatb); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), relu); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), relu); auto add1 = m2.add_instruction(migraphx::make_op("add"), x, y); auto add2 = m2.add_instruction(migraphx::make_op("add"), slice, add1); m2.add_instruction(pass_op{}, add2); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_add_relu_used_multiple_split2) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 1, 4}}}); auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto z = m1.add_instruction(migraphx::make_op("relu"), x); auto one = m1.add_literal(1); auto oneb = m1.add_instruction(b, one); auto two = m1.add_literal(2); auto twob = m1.add_instruction(b, two); auto sum1 = m1.add_instruction(migraphx::make_op("add"), x, oneb); auto relu1 = m1.add_instruction(migraphx::make_op("relu"), sum1); auto sum2 = m1.add_instruction(migraphx::make_op("add"), y, twob); auto relu2 = m1.add_instruction(migraphx::make_op("relu"), sum2); auto add1 = m1.add_instruction(migraphx::make_op("add"), relu1, relu2); auto add2 = m1.add_instruction(migraphx::make_op("add"), z, add1); m1.add_instruction(pass_op{}, add2); } run_pass(m1); migraphx::module m2; { auto b = migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {3, 2, 4}}}); auto input = m2.add_parameter("input", s); auto slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto z = m2.add_instruction(migraphx::make_op("relu"), slice); auto one = m2.add_literal(1); auto two = m2.add_literal(2); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), one, two); auto concatb = m2.add_instruction(b, concat); auto sum = m2.add_instruction(migraphx::make_op("add"), input, concatb); auto relu = m2.add_instruction(migraphx::make_op("relu"), sum); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), relu); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), relu); auto add1 = m2.add_instruction(migraphx::make_op("add"), x, y); auto add2 = m2.add_instruction(migraphx::make_op("add"), z, add1); m2.add_instruction(pass_op{}, add2); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_split_between_add) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 4}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto x = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {1}}}), input); auto y = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), input); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); m1.add_instruction(pass_op{}, sum); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } static void test_dot_horiz(migraphx::shape::type_t type, const std::string& dot_type) { auto s = migraphx::shape{type, {3, 2, 2}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto a = m1.add_literal(migraphx::generate_literal(s, 0)); auto b = m1.add_literal(migraphx::generate_literal(s, 1)); auto x = m1.add_instruction(migraphx::make_op(dot_type), input, a); auto y = m1.add_instruction(migraphx::make_op(dot_type), input, b); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto input = m2.add_parameter("input", s); auto a = m2.add_literal(migraphx::generate_literal(s, 0)); auto b = m2.add_literal(migraphx::generate_literal(s, 1)); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), a, b); auto dot = m2.add_instruction(migraphx::make_op(dot_type), input, concat); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {2}}}), dot); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {2}}, {"ends", {4}}}), dot); auto sum = m2.add_instruction(migraphx::make_op("add"), x, y); m2.add_instruction(pass_op{}, sum); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_dot_horiz) { test_dot_horiz(migraphx::shape::int32_type, "dot"); } TEST_CASE(simplify_quant_dot_horiz) { test_dot_horiz(migraphx::shape::int8_type, "quant_dot"); } TEST_CASE(simplify_dot_horiz_same_constant) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 2}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto a = m1.add_literal(migraphx::generate_literal(s, 0)); auto x = m1.add_instruction(migraphx::make_op("dot"), input, a); auto y = m1.add_instruction(migraphx::make_op("dot"), input, a); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto input = m2.add_parameter("input", s); auto a = m2.add_literal(migraphx::generate_literal(s, 0)); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), a, a); auto dot = m2.add_instruction(migraphx::make_op("dot"), input, concat); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {2}}}), dot); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {2}}, {"ends", {4}}}), dot); auto sum = m2.add_instruction(migraphx::make_op("add"), x, y); m2.add_instruction(pass_op{}, sum); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_dot_horiz_flipped) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 2, 2}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto a = m1.add_literal(migraphx::generate_literal(s, 0)); auto b = m1.add_literal(migraphx::generate_literal(s, 1)); auto x = m1.add_instruction(migraphx::make_op("dot"), input, a); auto y = m1.add_instruction(migraphx::make_op("dot"), b, input); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); m1.add_instruction(pass_op{}, sum); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } // test if contiguous is added as necessary for reshapes TEST_CASE(simplify_dot_horiz_reshape) { auto s = migraphx::shape{migraphx::shape::int32_type, {3, 4, 4}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto a = m1.add_literal(migraphx::generate_literal(s, 0)); auto b = m1.add_literal(migraphx::generate_literal(s, 1)); auto x = m1.add_instruction(migraphx::make_op("dot"), input, a); auto y = m1.add_instruction(migraphx::make_op("dot"), input, b); auto x_rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {3, 4, 2, 2}}}), x); auto y_rsp = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), y); auto sum = m1.add_instruction(migraphx::make_op("add"), {x_rsp, y_rsp}); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto input = m2.add_parameter("input", s); auto a = m2.add_literal(migraphx::generate_literal(s, 0)); auto b = m2.add_literal(migraphx::generate_literal(s, 1)); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), a, b); auto dot = m2.add_instruction(migraphx::make_op("dot"), input, concat); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {4}}}), dot); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {4}}, {"ends", {8}}}), dot); auto x_rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {3, 4, 2, 2}}}), x); auto y_rsp = m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), y); auto sum = m2.add_instruction(migraphx::make_op("add"), {x_rsp, y_rsp}); m2.add_instruction(pass_op{}, sum); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_conv_horiz) { auto s = migraphx::shape{migraphx::shape::int32_type, {8, 3, 64, 64}}; auto ws = migraphx::shape{migraphx::shape::int32_type, {12, 3, 3, 3}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto a = m1.add_literal(migraphx::generate_literal(ws, 0)); auto b = m1.add_literal(migraphx::generate_literal(ws, 1)); auto x = m1.add_instruction(migraphx::make_op("convolution"), input, a); auto y = m1.add_instruction(migraphx::make_op("convolution"), input, b); auto sum = m1.add_instruction(migraphx::make_op("add"), x, y); m1.add_instruction(pass_op{}, sum); } run_pass(m1); migraphx::module m2; { auto input = m2.add_parameter("input", s); auto a = m2.add_literal(migraphx::generate_literal(ws, 0)); auto b = m2.add_literal(migraphx::generate_literal(ws, 1)); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), a, b); auto conv = m2.add_instruction(migraphx::make_op("convolution"), input, concat); auto x = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {12}}}), conv); auto y = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {12}}, {"ends", {24}}}), conv); auto sum = m2.add_instruction(migraphx::make_op("add"), x, y); m2.add_instruction(pass_op{}, sum); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_group_conv_horiz) { auto s = migraphx::shape{migraphx::shape::int32_type, {1, 32, 111, 111}}; auto ws = migraphx::shape{migraphx::shape::int32_type, {32, 1, 7, 7}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto w1 = m1.add_literal(migraphx::generate_literal(ws, 1)); auto w2 = m1.add_literal(migraphx::generate_literal(ws, 2)); auto conv1 = m1.add_instruction( migraphx::make_op( "convolution", {{"padding", {3, 3}}, {"stride", {2, 2}}, {"dilation", {1, 1}}, {"group", 32}}), x, w1); auto conv2 = m1.add_instruction( migraphx::make_op( "convolution", {{"padding", {3, 3}}, {"stride", {2, 2}}, {"dilation", {1, 1}}, {"group", 32}}), x, w2); m1.add_instruction(pass_op{}, conv1, conv2); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_conv_horiz_grouped) { auto s = migraphx::shape{migraphx::shape::int32_type, {8, 6, 64, 64}}; auto ws1 = migraphx::shape{migraphx::shape::int32_type, {6, 6, 3, 3}}; auto ws2 = migraphx::shape{migraphx::shape::int32_type, {8, 6, 64, 64}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto a = m1.add_literal(migraphx::generate_literal(ws1, 0)); auto b = m1.add_literal(migraphx::generate_literal(ws1, 1)); auto c = m1.add_literal(migraphx::generate_literal(ws2, 2)); auto d = m1.add_literal(migraphx::generate_literal(ws2, 3)); auto convx = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, a); auto convy = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, b); auto dotx = m1.add_instruction(migraphx::make_op("dot"), input, c); auto doty = m1.add_instruction(migraphx::make_op("dot"), input, d); auto sum1 = m1.add_instruction(migraphx::make_op("add"), convx, convy); auto sum2 = m1.add_instruction(migraphx::make_op("add"), dotx, doty); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); m1.add_instruction(pass_op{}, sum3); } run_pass(m1); migraphx::module m2; { auto input = m2.add_parameter("input", s); auto a = m2.add_literal(migraphx::generate_literal(ws1, 0)); auto b = m2.add_literal(migraphx::generate_literal(ws1, 1)); auto c = m2.add_literal(migraphx::generate_literal(ws2, 2)); auto d = m2.add_literal(migraphx::generate_literal(ws2, 3)); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), a, b); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 3}}), c, d); auto conv = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, concat1); auto convx = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {6}}}), conv); auto convy = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {6}}, {"ends", {12}}}), conv); auto sum1 = m2.add_instruction(migraphx::make_op("add"), convx, convy); auto dot = m2.add_instruction(migraphx::make_op("dot"), input, concat2); auto dotx = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {64}}}), dot); auto doty = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {64}}, {"ends", {128}}}), dot); auto sum2 = m2.add_instruction(migraphx::make_op("add"), dotx, doty); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sum1, sum2); m2.add_instruction(pass_op{}, sum3); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_conv_horiz_grouped_extra1) { auto s = migraphx::shape{migraphx::shape::int32_type, {8, 6, 64, 64}}; auto ws1 = migraphx::shape{migraphx::shape::int32_type, {6, 6, 3, 3}}; auto ws2 = migraphx::shape{migraphx::shape::int32_type, {8, 6, 64, 64}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto a = m1.add_literal(migraphx::generate_literal(ws1, 0)); auto b = m1.add_literal(migraphx::generate_literal(ws1, 1)); auto c = m1.add_literal(migraphx::generate_literal(ws2, 2)); auto d = m1.add_literal(migraphx::generate_literal(ws2, 3)); auto e = m1.add_literal(migraphx::generate_literal(s, 4)); auto convx = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, a); auto convy = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, b); auto dotx = m1.add_instruction(migraphx::make_op("dot"), input, c); auto doty = m1.add_instruction(migraphx::make_op("dot"), input, d); auto sqdiffx = m1.add_instruction(migraphx::make_op("sqdiff"), input, e); auto sum1 = m1.add_instruction(migraphx::make_op("add"), convx, convy); auto sum2 = m1.add_instruction(migraphx::make_op("add"), dotx, doty); auto sum3 = sqdiffx; auto sum4 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); auto sum5 = m1.add_instruction(migraphx::make_op("add"), sum4, sum3); m1.add_instruction(pass_op{}, sum5); } run_pass(m1); migraphx::module m2; { auto input = m2.add_parameter("input", s); auto a = m2.add_literal(migraphx::generate_literal(ws1, 0)); auto b = m2.add_literal(migraphx::generate_literal(ws1, 1)); auto c = m2.add_literal(migraphx::generate_literal(ws2, 2)); auto d = m2.add_literal(migraphx::generate_literal(ws2, 3)); auto e = m2.add_literal(migraphx::generate_literal(s, 4)); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), a, b); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 3}}), c, d); auto conv = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, concat1); auto convx = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {6}}}), conv); auto convy = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {6}}, {"ends", {12}}}), conv); auto sum1 = m2.add_instruction(migraphx::make_op("add"), convx, convy); auto dot = m2.add_instruction(migraphx::make_op("dot"), input, concat2); auto dotx = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {64}}}), dot); auto doty = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {64}}, {"ends", {128}}}), dot); auto sum2 = m2.add_instruction(migraphx::make_op("add"), dotx, doty); auto sqdiffx = m2.add_instruction(migraphx::make_op("sqdiff"), input, e); auto sum3 = sqdiffx; auto sum4 = m2.add_instruction(migraphx::make_op("add"), sum1, sum2); auto sum5 = m2.add_instruction(migraphx::make_op("add"), sum4, sum3); m2.add_instruction(pass_op{}, sum5); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_conv_horiz_grouped_extra2) { auto s = migraphx::shape{migraphx::shape::int32_type, {8, 6, 64, 64}}; auto ws1 = migraphx::shape{migraphx::shape::int32_type, {6, 6, 3, 3}}; auto ws2 = migraphx::shape{migraphx::shape::int32_type, {8, 6, 64, 64}}; migraphx::module m1; { auto input = m1.add_parameter("input", s); auto a = m1.add_literal(migraphx::generate_literal(ws1, 0)); auto b = m1.add_literal(migraphx::generate_literal(ws1, 1)); auto c = m1.add_literal(migraphx::generate_literal(ws2, 2)); auto d = m1.add_literal(migraphx::generate_literal(ws2, 3)); auto e = m1.add_literal(migraphx::generate_literal(s, 4)); auto f = m1.add_literal(migraphx::generate_literal(s, 5)); auto convx = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, a); auto convy = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, b); auto dotx = m1.add_instruction(migraphx::make_op("dot"), input, c); auto doty = m1.add_instruction(migraphx::make_op("dot"), input, d); auto sqdiffx = m1.add_instruction(migraphx::make_op("sqdiff"), input, e); auto sqdiffy = m1.add_instruction(migraphx::make_op("sqdiff"), input, f); auto sum1 = m1.add_instruction(migraphx::make_op("add"), convx, convy); auto sum2 = m1.add_instruction(migraphx::make_op("add"), dotx, doty); auto sum3 = m1.add_instruction(migraphx::make_op("add"), sqdiffx, sqdiffy); auto sum4 = m1.add_instruction(migraphx::make_op("add"), sum1, sum2); auto sum5 = m1.add_instruction(migraphx::make_op("add"), sum4, sum3); m1.add_instruction(pass_op{}, sum5); } run_pass(m1); migraphx::module m2; { auto input = m2.add_parameter("input", s); auto a = m2.add_literal(migraphx::generate_literal(ws1, 0)); auto b = m2.add_literal(migraphx::generate_literal(ws1, 1)); auto c = m2.add_literal(migraphx::generate_literal(ws2, 2)); auto d = m2.add_literal(migraphx::generate_literal(ws2, 3)); auto e = m2.add_literal(migraphx::generate_literal(s, 4)); auto f = m2.add_literal(migraphx::generate_literal(s, 5)); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), a, b); auto concat2 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 3}}), c, d); auto conv = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}}), input, concat1); auto convx = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {6}}}), conv); auto convy = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {6}}, {"ends", {12}}}), conv); auto sum1 = m2.add_instruction(migraphx::make_op("add"), convx, convy); auto dot = m2.add_instruction(migraphx::make_op("dot"), input, concat2); auto dotx = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {64}}}), dot); auto doty = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {64}}, {"ends", {128}}}), dot); auto sum2 = m2.add_instruction(migraphx::make_op("add"), dotx, doty); auto sqdiffx = m2.add_instruction(migraphx::make_op("sqdiff"), input, e); auto sqdiffy = m2.add_instruction(migraphx::make_op("sqdiff"), input, f); auto sum3 = m2.add_instruction(migraphx::make_op("add"), sqdiffx, sqdiffy); auto sum4 = m2.add_instruction(migraphx::make_op("add"), sum1, sum2); auto sum5 = m2.add_instruction(migraphx::make_op("add"), sum4, sum3); m2.add_instruction(pass_op{}, sum5); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(simplify_mul_slice_conv_horiz_fusion) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {1, 1024, 17, 17}}); auto w = m1.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {768, 1024, 1, 1}})); auto conv = m1.add_instruction(migraphx::make_op("convolution"), x, w); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {384}}}), conv); auto a1 = m1.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}}, 1)); auto b1 = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 384, 17, 17}}}), a1); auto mul = m1.add_instruction(migraphx::make_op("mul"), slice1, b1); auto a2 = m1.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}}, 2)); auto b2 = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 384, 17, 17}}}), a2); auto add1 = m1.add_instruction(migraphx::make_op("add"), mul, b2); auto a3 = m1.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}}, 3)); auto b3 = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 384, 17, 17}}}), a3); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {384}}, {"ends", {768}}}), conv); auto add2 = m1.add_instruction(migraphx::make_op("add"), slice2, b3); m1.add_instruction(pass_op{}, add1, add2); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {1, 1024, 17, 17}}); auto w = m2.add_literal( migraphx::generate_literal({migraphx::shape::int32_type, {768, 1024, 1, 1}})); auto wslice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {384}}}), w); auto a1 = m2.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}}, 1)); auto b1 = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {384, 1024, 1, 1}}}), a1); auto mul = m2.add_instruction(migraphx::make_op("mul"), b1, wslice1); auto wslice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {384}}, {"ends", {768}}}), w); auto concat1 = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), mul, wslice2); auto conv = m2.add_instruction(migraphx::make_op("convolution"), x, concat1); auto a2 = m2.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}}, 2)); auto a3 = m2.add_literal(migraphx::generate_literal({migraphx::shape::int32_type, {384}}, 3)); auto concat2 = m2.add_instruction(migraphx::make_op("concat"), a2, a3); auto b4 = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 768, 17, 17}}}), concat2); auto add = m2.add_instruction(migraphx::make_op("add"), conv, b4); auto slice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {384}}}), add); auto slice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {384}}, {"ends", {768}}}), add); m2.add_instruction(pass_op{}, slice1, slice2); } EXPECT(m1.sort() == m2.sort()); } template static void reorder_reshape_slice() { std::vector perm0 = {0, 2, 1, 3}; std::vector perm1 = {0, 2, 3, 1}; migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 128, 1920}}; if(TransposeInput) { s = migraphx::shape{migraphx::shape::float_type, {BS, 128, 1920}, {165120, 1, 128}}; } auto input = m1.add_parameter("input", s); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {640}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {640}}, {"ends", {1280}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1280}}, {"ends", {1920}}}), input); auto c0 = m1.add_instruction(migraphx::make_op("contiguous"), slc0); auto c1 = m1.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2 = m1.add_instruction(migraphx::make_op("contiguous"), slc2); std::vector lens = {static_cast(BS), 128, 10, 64}; auto r0 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c0); auto r1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c1); auto r2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c2); auto t0 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), r0); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), r1); auto t2 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm1}}), r2); auto sum = m1.add_instruction(migraphx::make_op("add"), t0, t1); auto ret = m1.add_instruction(migraphx::make_op("dot"), sum, t2); m1.add_return({ret}); }; migraphx::module m2; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 128, 1920}}; if(TransposeInput) { s = migraphx::shape{migraphx::shape::float_type, {BS, 128, 1920}, {165120, 1, 128}}; } auto input = m2.add_parameter("input", s); auto rsp_input = input; std::vector lens = {static_cast(BS), 128, 30, 64}; auto r = m2.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), rsp_input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {10}}}), r); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {10}}, {"ends", {20}}}), r); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {20}}, {"ends", {30}}}), r); auto t0 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), slc0); auto t1 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), slc1); auto t2 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", perm1}}), slc2); auto sum = m2.add_instruction(migraphx::make_op("add"), t0, t1); auto ret = m2.add_instruction(migraphx::make_op("dot"), sum, t2); m2.add_return({ret}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE_REGISTER(reorder_reshape_slice<1, true>); // test if contiguous is added as necessary if // input is transposed TEST_CASE_REGISTER(reorder_reshape_slice<4, true>); TEST_CASE_REGISTER(reorder_reshape_slice<8, true>); TEST_CASE_REGISTER(reorder_reshape_slice<1, false>); TEST_CASE_REGISTER(reorder_reshape_slice<4, false>); TEST_CASE_REGISTER(reorder_reshape_slice<8, false>); template static void reorder_reshape_slice_move_axis1() { migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 256, 96}}; std::vector perm0 = {0, 2, 1, 3}; std::vector perm1 = {0, 2, 3, 1}; auto input = m1.add_parameter("input", s); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {32}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {32}}, {"ends", {64}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {64}}, {"ends", {96}}}), input); auto c0 = m1.add_instruction(migraphx::make_op("contiguous"), slc0); auto c1 = m1.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2 = m1.add_instruction(migraphx::make_op("contiguous"), slc2); std::vector lens = {static_cast(BS), 64, 4, 32}; auto r0 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c0); auto r1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c1); auto r2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c2); auto t0 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), r0); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), r1); auto t2 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm1}}), r2); auto sum = m1.add_instruction(migraphx::make_op("add"), t0, t1); auto ret = m1.add_instruction(migraphx::make_op("dot"), sum, t2); m1.add_return({ret}); }; migraphx::module m2; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 256, 96}}; std::vector perm0 = {0, 2, 1, 3}; std::vector perm1 = {0, 2, 3, 1}; auto input = m2.add_parameter("input", s); std::vector lens = {static_cast(BS), 64, 4, 96}; auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {32}}}), rsp); auto t0 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), slc0); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {32}}, {"ends", {64}}}), rsp); auto t1 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), slc1); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {64}}, {"ends", {96}}}), rsp); auto t2 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", perm1}}), slc2); auto sum = m2.add_instruction(migraphx::make_op("add"), t0, t1); auto ret = m2.add_instruction(migraphx::make_op("dot"), sum, t2); m2.add_return({ret}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE_REGISTER(reorder_reshape_slice_move_axis1<4>); TEST_CASE_REGISTER(reorder_reshape_slice_move_axis1<8>); TEST_CASE(reorder_reshape_slice_move_axis2) { migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {128, 96}}; auto input = m1.add_parameter("input", s); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {32}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {32}}, {"ends", {64}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {64}}, {"ends", {96}}}), input); auto c0 = m1.add_instruction(migraphx::make_op("contiguous"), slc0); auto c1 = m1.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2 = m1.add_instruction(migraphx::make_op("contiguous"), slc2); std::vector lens = {1, 16, 8, 32}; auto r0 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c0); auto r1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c1); auto r2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c2); auto sum = m1.add_instruction(migraphx::make_op("add"), r0, r1); auto ret = m1.add_instruction(migraphx::make_op("mul"), sum, r2); m1.add_return({ret}); }; migraphx::module m2; { auto s = migraphx::shape{migraphx::shape::float_type, {128, 96}}; auto input = m2.add_parameter("input", s); std::vector lens = {1, 16, 8, 96}; auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {32}}}), rsp); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {32}}, {"ends", {64}}}), rsp); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {64}}, {"ends", {96}}}), rsp); auto sum = m2.add_instruction(migraphx::make_op("add"), slc0, slc1); auto ret = m2.add_instruction(migraphx::make_op("mul"), sum, slc2); m2.add_return({ret}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reorder_reshape_slice_len_1) { migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {1, 128, 3}}; auto input = m1.add_parameter("input", s); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {1}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1}}, {"ends", {2}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {2}}, {"ends", {3}}}), input); auto c0 = m1.add_instruction(migraphx::make_op("contiguous"), slc0); auto c1 = m1.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2 = m1.add_instruction(migraphx::make_op("contiguous"), slc2); std::vector lens = {1, 128}; auto r0 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c0); auto r1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c1); auto r2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c2); auto sum = m1.add_instruction(migraphx::make_op("add"), r0, r1); auto ret = m1.add_instruction(migraphx::make_op("mul"), sum, r2); m1.add_return({ret}); }; migraphx::module m2; { auto s = migraphx::shape{migraphx::shape::float_type, {1, 128, 3}}; auto input = m2.add_parameter("input", s); std::vector lens = {1, 384}; auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {128}}}), rsp); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {128}}, {"ends", {256}}}), rsp); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {256}}, {"ends", {384}}}), rsp); auto sum = m2.add_instruction(migraphx::make_op("add"), slc0, slc1); auto ret = m2.add_instruction(migraphx::make_op("mul"), sum, slc2); m2.add_return({ret}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reorder_reshape_slice_not_apply) { auto create_p = [] { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {128, 96}}; auto input = m.add_parameter("input", s); auto slc0 = m.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {32}}}), input); auto slc1 = m.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {32}}, {"ends", {64}}}), input); auto slc2 = m.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {64}}, {"ends", {96}}}), input); auto c0 = m.add_instruction(migraphx::make_op("contiguous"), slc0); auto c1 = m.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2 = m.add_instruction(migraphx::make_op("contiguous"), slc2); std::vector lens = {1, 16, 16, 16}; auto r0 = m.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c0); auto r1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c1); auto r2 = m.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c2); auto sum = m.add_instruction(migraphx::make_op("add"), r0, r1); auto ret = m.add_instruction(migraphx::make_op("mul"), sum, r2); m.add_return({ret}); return m; }; auto m1 = create_p(); auto m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reorder_reshape_slice_multi_rsp) { migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {4, 128, 3, 32, 80}}; auto input = m1.add_parameter("input", s); auto t1 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {2, 0, 3, 1, 4}}}), input); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), t1); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), t1); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {3}}}), t1); auto c1_1 = m1.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2_1 = m1.add_instruction(migraphx::make_op("contiguous"), slc2); auto c1 = m1.add_instruction(migraphx::make_op("contiguous"), slc1); auto r1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {4, 32, 128, 80}}}), c1); auto c2 = m1.add_instruction(migraphx::make_op("contiguous"), slc2); auto r2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {4, 32, 128, 80}}}), c2); auto r1_1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {128, 128, 80}}}), c1_1); auto r2_1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {128, 128, 80}}}), c2_1); auto c0 = m1.add_instruction(migraphx::make_op("contiguous"), slc0); auto r0 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {128, 128, 80}}}), c0); auto t2 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), r1_1); auto c_t2 = m1.add_instruction(migraphx::make_op("contiguous"), t2); auto dot = m1.add_instruction(migraphx::make_op("dot"), r0, c_t2); m1.add_return({r1, r2, dot, r2_1}); }; migraphx::module m2; { migraphx::shape s{migraphx::shape::float_type, {4, 128, 3, 32, 80}}; auto input = m2.add_parameter("input", s); auto t1 = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {2, 0, 3, 1, 4}}}), input); auto rsp1 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {384, 128, 80}}}), t1); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {256}}, {"ends", {384}}}), rsp1); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {128}}, {"ends", {256}}}), rsp1); auto t_slc1 = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), slc1); auto c_t_slc1 = m2.add_instruction(migraphx::make_op("contiguous"), t_slc1); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {128}}}), rsp1); auto dot = m2.add_instruction(migraphx::make_op("dot"), slc2, c_t_slc1); auto rsp2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {12, 32, 128, 80}}}), t1); auto slc2_1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {4}}, {"ends", {8}}}), rsp2); auto slc2_2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {8}}, {"ends", {12}}}), rsp2); m2.add_return({slc2_1, slc2_2, dot, slc0}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reorder_reshape_slice_partial) { migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {128, 96}}; auto input = m1.add_parameter("input", s); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {8}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {8}}, {"ends", {16}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {16}}, {"ends", {24}}}), input); auto slc3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {24}}, {"ends", {128}}}), input); auto c0 = m1.add_instruction(migraphx::make_op("contiguous"), slc0); auto c1 = m1.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2 = m1.add_instruction(migraphx::make_op("contiguous"), slc2); std::vector lens = {2, 4, 96}; auto r0 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c0); auto r1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c1); auto r2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c2); auto sum = m1.add_instruction(migraphx::make_op("add"), r0, r1); auto ret = m1.add_instruction(migraphx::make_op("mul"), sum, r2); m1.add_return({ret, slc3}); }; migraphx::module m2; { migraphx::shape s{migraphx::shape::float_type, {128, 96}}; auto input = m2.add_parameter("input", s); auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {32, 4, 96}}}), input); auto slc3 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {24}}, {"ends", {128}}}), input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {2}}}), rsp); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {4}}}), rsp); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {4}}, {"ends", {6}}}), rsp); auto sum = m2.add_instruction(migraphx::make_op("add"), slc0, slc1); auto ret = m2.add_instruction(migraphx::make_op("mul"), sum, slc2); m2.add_return({ret, slc3}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reorder_reshape_slice_uneven_slice) { auto create_p = [] { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {128, 96}}; auto input = m.add_parameter("input", s); auto slc0 = m.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {31}}}), input); auto slc1 = m.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {31}}, {"ends", {62}}}), input); auto slc2 = m.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {62}}, {"ends", {93}}}), input); auto slc3 = m.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {93}}, {"ends", {128}}}), input); auto c0 = m.add_instruction(migraphx::make_op("contiguous"), slc0); auto c1 = m.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2 = m.add_instruction(migraphx::make_op("contiguous"), slc2); std::vector lens = {1, 31, 96}; auto r0 = m.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c0); auto r1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c1); auto r2 = m.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c2); auto sum = m.add_instruction(migraphx::make_op("add"), r0, r1); auto ret = m.add_instruction(migraphx::make_op("mul"), sum, r2); m.add_return({ret, slc3}); return m; }; auto m1 = create_p(); auto m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } template static void reorder_reshape_slice_diff_dims() { migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 96, 96}}; auto input = m1.add_parameter("input", s); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {32}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {32}}, {"ends", {64}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {64}}, {"ends", {96}}}), input); auto c0 = m1.add_instruction(migraphx::make_op("contiguous"), slc0); auto c1 = m1.add_instruction(migraphx::make_op("contiguous"), slc1); auto c2 = m1.add_instruction(migraphx::make_op("contiguous"), slc2); std::vector lens = {static_cast(BS), 32, 3, 32}; std::vector lens1 = {static_cast(BS), 48, 2, 32}; auto r0 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c0); auto r1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens1}}), c1); auto r2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), c2); m1.add_return({r0, r1, r2}); }; migraphx::module m2; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 96, 96}}; auto input = m2.add_parameter("input", s); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {32}}, {"ends", {64}}}), input); auto c1 = m2.add_instruction(migraphx::make_op("contiguous"), slc1); std::vector lens1 = {static_cast(BS), 48, 2, 32}; auto r1 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", lens1}}), c1); std::vector lens = {static_cast(BS), 32, 3, 96}; auto r_new = m2.add_instruction(migraphx::make_op("reshape", {{"dims", lens}}), input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {32}}}), r_new); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {3}}, {"starts", {64}}, {"ends", {96}}}), r_new); m2.add_return({slc0, r1, slc2}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE_REGISTER(reorder_reshape_slice_diff_dims<4>); TEST_CASE_REGISTER(reorder_reshape_slice_diff_dims<8>); template static void reorder_slice_trans() { std::vector perm = {0, 2, 1}; migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 128, 1920}}; auto input = m1.add_parameter("input", s); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {640}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {640}}, {"ends", {1280}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1280}}, {"ends", {1920}}}), input); auto t0 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), slc0); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), slc1); auto t2 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), slc2); auto sum = m1.add_instruction(migraphx::make_op("add"), t0, t1); auto ret = m1.add_instruction(migraphx::make_op("mul"), sum, t2); m1.add_return({ret}); }; migraphx::module m2; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 128, 1920}}; auto input = m2.add_parameter("input", s); auto r = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), input); auto slc0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {640}}}), r); auto slc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {640}}, {"ends", {1280}}}), r); auto slc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1280}}, {"ends", {1920}}}), r); auto sum = m2.add_instruction(migraphx::make_op("add"), slc0, slc1); auto ret = m2.add_instruction(migraphx::make_op("mul"), sum, slc2); m2.add_return({ret}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE_REGISTER(reorder_slice_trans<1>); TEST_CASE_REGISTER(reorder_slice_trans<8>); template static void reorder_slice_trans_diff_perm() { migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::float_type, {BS, 128, 1920}}; std::vector perm0 = {0, 2, 1}; std::vector perm1 = {0, 1, 2}; auto input = m1.add_parameter("input", s); auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {640}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {640}}, {"ends", {1280}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1280}}, {"ends", {1920}}}), input); auto t0 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), slc0); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm0}}), slc1); auto t2 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm1}}), slc2); auto sum = m1.add_instruction(migraphx::make_op("add"), t0, t1); auto ret = m1.add_instruction(migraphx::make_op("dot"), sum, t2); m1.add_return({ret}); }; run_pass(m1); auto m2 = m1; EXPECT(m1.sort() == m2.sort()); } TEST_CASE_REGISTER(reorder_slice_trans_diff_perm<1>); TEST_CASE_REGISTER(reorder_slice_trans_diff_perm<4>); TEST_CASE(reorder_slice_trans_multi_outputs) { migraphx::module m1; { auto s = migraphx::shape{migraphx::shape::float_type, {8, 128, 1920}}; auto input = m1.add_parameter("input", s); std::vector perm = {0, 2, 1}; auto slc0 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {640}}}), input); auto slc1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {640}}, {"ends", {1280}}}), input); auto slc2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1280}}, {"ends", {1920}}}), input); auto t0 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), slc0); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), slc1); auto t2 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), slc2); auto sum = m1.add_instruction(migraphx::make_op("add"), t0, t1); auto dot = m1.add_instruction(migraphx::make_op("mul"), sum, t2); auto slc_cont = m1.add_instruction(migraphx::make_op("contiguous"), slc1); m1.add_return({slc_cont, dot}); }; run_pass(m1); auto m2 = m1; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reorder_slice_ins_deps) { auto create_module = [] { migraphx::module m; migraphx::shape sx{migraphx::shape::float_type, {4, 2}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2}}; std::vector datax = {0, 1, 2, 3, 4, 5, 6, 7}; std::vector datay = {0, 1, 2, 3}; auto inx = m.add_literal(migraphx::literal(sx, datax)); auto iny = m.add_literal(migraphx::literal(sy, datay)); auto slc0 = m.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {2}}}), inx); auto slc1 = m.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {4}}}), inx); auto n0 = m.add_instruction(migraphx::make_op("neg"), slc0); auto a0 = m.add_instruction(migraphx::make_op("add"), n0, slc1); auto m0 = m.add_instruction(migraphx::make_op("mul"), a0, iny); auto r = m.add_instruction(migraphx::make_op("add"), m0, slc0); m.add_return({r}); return m; }; auto m = create_module(); run_pass(m); EXPECT(m == create_module()); } TEST_CASE(dot_broadcast_different_broadcast1) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {64}}); auto y = m1.add_parameter("y", {migraphx::shape::float_type, {64, 64}}); auto xb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 3}, {"out_lens", {2, 4, 4, 64}}}), x); auto yb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 64, 64}}}), y); auto dot = m1.add_instruction(migraphx::make_op("dot"), xb, yb); m1.add_return({dot}); }; migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {64}}); auto y = m2.add_parameter("y", {migraphx::shape::float_type, {64, 64}}); auto xb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {4, 64}}}), x); auto dot = m2.add_instruction(migraphx::make_op("dot"), xb, y); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 4, 64}}}), dot); m2.add_return({broadcast}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_broadcast_different_broadcast2) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {384}}); auto y = m1.add_parameter("y", {migraphx::shape::float_type, {768, 3072}}); auto xb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 384, 768}}}), x); auto yb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 768, 3072}}}), y); auto dot = m1.add_instruction(migraphx::make_op("dot"), xb, yb); m1.add_return({dot}); }; migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {384}}); auto y = m2.add_parameter("y", {migraphx::shape::float_type, {768, 3072}}); auto xb = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {384, 768}}}), x); auto dot = m2.add_instruction(migraphx::make_op("dot"), xb, y); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 384, 3072}}}), dot); m2.add_return({broadcast}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_broadcast_different_rank) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {768}}); auto y = m1.add_parameter("y", {migraphx::shape::float_type, {768, 3072}}); auto xb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 384, 768}}}), x); auto yb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 768, 3072}}}), y); auto dot = m1.add_instruction(migraphx::make_op("dot"), xb, yb); m1.add_return({dot}); }; migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {768}}); auto y = m2.add_parameter("y", {migraphx::shape::float_type, {768, 3072}}); auto xb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {384, 768}}}), x); auto dot = m2.add_instruction(migraphx::make_op("dot"), xb, y); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 384, 3072}}}), dot); m2.add_return({broadcast}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_broadcast_unsqueezed_input) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 1, 1, 8}}); auto y = m1.add_parameter("y", {migraphx::shape::float_type, {8, 8}}); auto xb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 8, 8}}}), x); auto yb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 8, 8}}}), y); auto dot = m1.add_instruction(migraphx::make_op("dot"), xb, yb); m1.add_return({dot}); }; migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {1, 1, 1, 8}}); auto y = m2.add_parameter("y", {migraphx::shape::float_type, {8, 8}}); auto x_sq = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0, 1}}}), x); auto xb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8, 8}}}), x_sq); auto dot = m2.add_instruction(migraphx::make_op("dot"), xb, y); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 8, 8}}}), dot); m2.add_return({broadcast}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_fusion_reshape) { migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 4096, 320}}; auto input = m1.add_parameter("input", s); auto p0 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {2, 320, 320}}, 0)); auto p1 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {2, 320, 320}}, 1)); auto d0 = m1.add_instruction(migraphx::make_op("dot"), input, p0); auto d1 = m1.add_instruction(migraphx::make_op("dot"), input, p1); auto r0 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 4096, 8, 40}}}), d0); m1.add_return({r0, d1}); }; migraphx::module m2; { migraphx::shape s{migraphx::shape::float_type, {2, 4096, 320}}; auto input = m2.add_parameter("input", s); auto p0 = m2.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {2, 320, 320}}, 0)); auto p1 = m2.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {2, 320, 320}}, 1)); auto c = m2.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), p0, p1); auto d = m2.add_instruction(migraphx::make_op("dot"), input, c); auto s0 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {320}}}), d); auto s1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {320}}, {"ends", {640}}}), d); auto r0 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 4096, 8, 40}}}), s0); m2.add_return({r0, s1}); }; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(mul_dot_a) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("input", as); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 1, 32}})); auto litb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", as.lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), a, litb); auto b = m1.add_literal(migraphx::generate_literal(bs)); auto dot = m1.add_instruction(migraphx::make_op("dot"), mul, b); m1.add_return({dot}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_parameter("input", as); auto lit = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 1, 32}})); auto litb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", migraphx::reorder_dims(bs.lens(), {0, 2, 1})}}), lit); auto litt = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), litb); auto b = m2.add_literal(migraphx::generate_literal(bs)); auto mul = m2.add_instruction(migraphx::make_op("mul"), b, litt); auto dot = m2.add_instruction(migraphx::make_op("dot"), a, mul); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(mul_dot_b) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto b = m1.add_parameter("input", bs); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 32, 1}})); auto litb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", bs.lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), b, litb); auto a = m1.add_literal(migraphx::generate_literal(as)); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, mul); m1.add_return({dot}); }; run_pass(m1); migraphx::module m2; { auto b = m2.add_parameter("input", bs); auto lit = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 32, 1}})); auto litb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", migraphx::reorder_dims(as.lens(), {0, 2, 1})}}), lit); auto litt = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), litb); auto a = m2.add_literal(migraphx::generate_literal(as)); auto mul = m2.add_instruction(migraphx::make_op("mul"), a, litt); auto dot = m2.add_instruction(migraphx::make_op("dot"), mul, b); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(mul_dot_a_not_k_broadcast) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("input", as); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 256, 1}})); auto litb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", as.lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), a, litb); auto b = m1.add_literal(migraphx::generate_literal(bs)); auto dot = m1.add_instruction(migraphx::make_op("dot"), mul, b); m1.add_return({dot}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(mul_dot_b_not_k_broadcast) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto b = m1.add_parameter("input", bs); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 1, 128}})); auto litb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", bs.lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), b, litb); auto a = m1.add_literal(migraphx::generate_literal(as)); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, mul); m1.add_return({dot}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(mul_dot_a_int4_dq) { migraphx::shape as{migraphx::shape::float_type, {1, 32, 4096}}; migraphx::shape bs{migraphx::shape::int8_type, {22016, 2048}}; migraphx::shape cs{migraphx::shape::float_type, {22016, 4096}}; migraphx::module m1; { auto a = m1.add_parameter("input", as); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {4096}})); auto litb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", as.lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), a, litb); auto b = m1.add_literal(migraphx::generate_literal(bs)); auto unpack = m1.add_instruction(migraphx::make_op("unpack_int4"), b); auto scales = m1.add_literal(migraphx::generate_literal(cs)); auto dq = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack, scales); auto unsqueeze = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), dq); auto transpose = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), unsqueeze); auto dot = m1.add_instruction(migraphx::make_op("dot"), mul, transpose); m1.add_return({dot}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(mul_dot_a_int4_dq_concat) { migraphx::shape as{migraphx::shape::float_type, {1, 32, 4096}}; migraphx::shape bs{migraphx::shape::int8_type, {4096, 5504}}; migraphx::shape cs{migraphx::shape::float_type, {4096, 11008}}; migraphx::module m1; { auto a = m1.add_parameter("input", as); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {4096}})); auto litb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", as.lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), a, litb); std::vector concats; for(int i = 0; i < 2; i++) { auto b = m1.add_literal(migraphx::generate_literal(bs)); auto unpack = m1.add_instruction(migraphx::make_op("unpack_int4"), b); auto scales = m1.add_literal(migraphx::generate_literal(cs)); auto dq = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack, scales); concats.push_back( m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), dq)); } auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), concats); auto dot = m1.add_instruction(migraphx::make_op("dot"), mul, concat); m1.add_return({dot}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_mul_a) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("input", as); auto b = m1.add_literal(migraphx::generate_literal(bs)); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 1, 128}})); auto litb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot->get_shape().lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), dot, litb); m1.add_return({mul}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_parameter("input", as); auto b = m2.add_literal(migraphx::generate_literal(bs)); auto lit = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 1, 128}})); auto litb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", bs.lens()}}), lit); auto mul = m2.add_instruction(migraphx::make_op("mul"), b, litb); auto dot = m2.add_instruction(migraphx::make_op("dot"), a, mul); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_mul_a_used_twice) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("input", as); auto b = m1.add_literal(migraphx::generate_literal(bs)); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 1, 128}})); auto litb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot->get_shape().lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), dot, litb); auto relu = m1.add_instruction(migraphx::make_op("relu"), mul); auto add = m1.add_instruction(migraphx::make_op("add"), dot, relu); m1.add_return({add}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_mul_a_non_const) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("input", as); auto b = m1.add_literal(migraphx::generate_literal(bs)); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 256, 1}})); auto litb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot->get_shape().lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), dot, litb); m1.add_return({mul}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_mul_b) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_literal(migraphx::generate_literal(as)); auto b = m1.add_parameter("input", bs); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 256, 1}})); auto litb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot->get_shape().lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), dot, litb); m1.add_return({mul}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_literal(migraphx::generate_literal(as)); auto b = m2.add_parameter("input", bs); auto lit = m2.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 256, 1}})); auto litb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", as.lens()}}), lit); auto mul = m2.add_instruction(migraphx::make_op("mul"), a, litb); auto dot = m2.add_instruction(migraphx::make_op("dot"), mul, b); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_mul_b_non_const) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_literal(migraphx::generate_literal(as)); auto b = m1.add_parameter("input", bs); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto lit = m1.add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 1, 128}})); auto litb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot->get_shape().lens()}}), lit); auto mul = m1.add_instruction(migraphx::make_op("mul"), dot, litb); m1.add_return({mul}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_slice_b) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("a", as); auto b = m1.add_parameter("b", bs); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto slice = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {64}}, {"ends", {128}}}), dot); m1.add_return({slice}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_parameter("a", as); auto b = m2.add_parameter("b", bs); auto b_slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {64}}, {"ends", {128}}}), b); auto dot = m2.add_instruction(migraphx::make_op("dot"), a, b_slice); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_slice_a) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("a", as); auto b = m1.add_parameter("b", bs); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto slice = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {64}}, {"ends", {128}}}), dot); m1.add_return({slice}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_parameter("a", as); auto b = m2.add_parameter("b", bs); auto a_slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {64}}, {"ends", {128}}}), a); auto dot = m2.add_instruction(migraphx::make_op("dot"), a_slice, b); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_slice_ab) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("a", as); auto b = m1.add_parameter("b", bs); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto slice = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1, 2}}, {"starts", {64, 64}}, {"ends", {128, 128}}}), dot); m1.add_return({slice}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_parameter("a", as); auto b = m2.add_parameter("b", bs); auto a_slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {64}}, {"ends", {128}}}), a); auto b_slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {64}}, {"ends", {128}}}), b); auto dot = m2.add_instruction(migraphx::make_op("dot"), a_slice, b_slice); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_slice_batch_dims) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("a", as); auto b = m1.add_parameter("b", bs); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto slice = m1.add_instruction( migraphx::make_op( "slice", {{"axes", {0, 1, 2}}, {"starts", {0, 64, 64}}, {"ends", {1, 128, 128}}}), dot); m1.add_return({slice}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_parameter("a", as); auto b = m2.add_parameter("b", bs); auto a_slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 64}}, {"ends", {1, 128}}}), a); auto b_slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0, 2}}, {"starts", {0, 64}}, {"ends", {1, 128}}}), b); auto dot = m2.add_instruction(migraphx::make_op("dot"), a_slice, b_slice); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_slice_not_applicable_1) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("a", as); auto b = m1.add_parameter("b", bs); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1, 2}}, {"starts", {64, 64}}, {"ends", {128, 128}}}), dot); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), dot); m1.add_return({slice1, slice2}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_slice_not_applicable_2) { migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; migraphx::module m1; { auto a = m1.add_parameter("a", as); auto b = m1.add_parameter("b", bs); auto dot = m1.add_instruction(migraphx::make_op("dot"), a, b); auto slice = m1.add_instruction( migraphx::make_op("slice", {{"axes", {-2, -1}}, {"starts", {64, 64}}, {"ends", {128, 128}}}), dot); m1.add_return({slice}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_parameter("a", as); auto b = m2.add_parameter("b", bs); auto a_slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {64}}, {"ends", {128}}}), a); auto b_slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {64}}, {"ends", {128}}}), b); auto dot = m2.add_instruction(migraphx::make_op("dot"), a_slice, b_slice); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(conv_concat) { migraphx::shape xs{migraphx::shape::float_type, {1, 8, 4, 4}}; migraphx::shape ws{migraphx::shape::float_type, {2, 8, 3, 3}}; migraphx::module m1; { auto x = m1.add_parameter("x", xs); auto w = m1.add_literal(migraphx::generate_literal(ws, 1)); auto y = m1.add_parameter("y", xs); auto v = m1.add_literal(migraphx::generate_literal(ws, 2)); auto conv1 = m1.add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = m1.add_instruction(migraphx::make_op("convolution"), y, v); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), conv1, conv2); m1.add_instruction(migraphx::make_op("exp"), concat); }; migraphx::module m2; { auto x = m2.add_parameter("x", xs); auto w = m2.add_literal(migraphx::generate_literal(ws, 1)); auto y = m2.add_parameter("y", xs); auto v = m2.add_literal(migraphx::generate_literal(ws, 2)); auto xconcat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto wconcat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), w, v); auto conv = m2.add_instruction(migraphx::make_op("convolution", {{"group", 2}}), xconcat, wconcat); m2.add_instruction(migraphx::make_op("exp"), conv); } run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(conv_concat_group) { migraphx::shape xs{migraphx::shape::float_type, {1, 8, 4, 4}}; migraphx::shape ws{migraphx::shape::float_type, {2, 4, 3, 3}}; migraphx::module m1; { auto x = m1.add_parameter("x", xs); auto w = m1.add_literal(migraphx::generate_literal(ws, 1)); auto y = m1.add_parameter("y", xs); auto v = m1.add_literal(migraphx::generate_literal(ws, 2)); auto conv1 = m1.add_instruction(migraphx::make_op("convolution", {{"group", 2}}), x, w); auto conv2 = m1.add_instruction(migraphx::make_op("convolution", {{"group", 2}}), y, v); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), conv1, conv2); m1.add_instruction(migraphx::make_op("exp"), concat); }; migraphx::module m2; { auto x = m2.add_parameter("x", xs); auto w = m2.add_literal(migraphx::generate_literal(ws, 1)); auto y = m2.add_parameter("y", xs); auto v = m2.add_literal(migraphx::generate_literal(ws, 2)); auto xconcat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto wconcat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), w, v); auto conv = m2.add_instruction(migraphx::make_op("convolution", {{"group", 4}}), xconcat, wconcat); m2.add_instruction(migraphx::make_op("exp"), conv); } run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(find_concat_different_broadcast_axes) { migraphx::shape s1{migraphx::shape::float_type, {128, 1, 1, 1, 1}}; migraphx::shape s2{migraphx::shape::float_type, {1, 3, 1, 1, 1}}; migraphx::module m1; { auto l1 = m1.add_literal(migraphx::generate_literal(s1, 1)); auto l2 = m1.add_literal(migraphx::generate_literal(s2, 2)); auto bc1 = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {128, 3, 224, 224, 1}}}), l1); auto bc2 = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {128, 3, 224, 224, 1}}}), l2); auto cat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 4}}), bc1, bc2); m1.add_return({cat}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/simplify_dyn_ops_test.cpp000066400000000000000000001301701510465702400226530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::simplify_dyn_ops{}, migraphx::dead_code_elimination{}}); } TEST_CASE(broadcast_with_dims) { migraphx::module m0; { // the X input migraphx::shape sx{migraphx::shape::float_type, {3, 1, 1}}; auto inx = m0.add_parameter("x", sx); // the shape input. Broadcast to this migraphx::shape dims_s{migraphx::shape::int64_type, {4}}; std::vector dims = {2, 3, 4, 5}; auto out_dims = m0.add_literal(migraphx::literal{dims_s, dims}); auto r = m0.add_instruction(migraphx::make_op("broadcast_with_dims"), inx, out_dims); m0.add_return({r}); } run_pass(m0); migraphx::module m1; { migraphx::shape sx{migraphx::shape::float_type, {3, 1, 1}}; auto inx = m1.add_parameter("x", sx); auto r = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 5}}}), inx); m1.add_return({r}); } EXPECT(m0 == m1); } TEST_CASE(broadcast_with_dims_invalid) { migraphx::module m0; { // X input shape is not broadcastable to given shape migraphx::shape sx{migraphx::shape::float_type, {3, 1, 2}}; auto inx = m0.add_parameter("x", sx); // the shape input. Broadcast to this migraphx::shape dims_s{migraphx::shape::int64_type, {4}}; std::vector dims = {2, 3, 4, 5}; auto out_dims = m0.add_literal(migraphx::literal{dims_s, dims}); auto r = m0.add_instruction(migraphx::make_op("broadcast_with_dims"), inx, out_dims); m0.add_return({r}); } // replacement will be rejected by multibroadcast operation EXPECT(test::throws([&] { run_pass(m0); })); } TEST_CASE(resize) { migraphx::module m0; { std::vector ds = {1, 1, 4, 6}; migraphx::shape ss{migraphx::shape::int64_type, {4}}; auto li = m0.add_literal(migraphx::literal{ss, ds}); m0.add_instruction(migraphx::make_op("undefined")); migraphx::shape sx{migraphx::shape::int64_type, {1, 1, 2, 2}}; auto inx = m0.add_parameter("X", sx); auto r = m0.add_instruction( migraphx::make_op("resize", {{"mode", "nearest"}, {"nearest_mode", "floor"}, // scales attr. should be ignored when there are 2 inputs {"scales", {1., 2.1, 3.1, 4.1}}, {"coordinate_transformation_mode", "asymmetric"}}), inx, li); m0.add_return({r}); } run_pass(m0); migraphx::module m1; { migraphx::shape sx{migraphx::shape::int64_type, {1, 1, 2, 2}}; auto inx = m1.add_parameter("X", sx); std::vector indices = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; migraphx::shape ss{migraphx::shape::int32_type, {1, 1, 4, 6}}; auto li = m1.insert_literal(inx, migraphx::literal{ss, indices}); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), inx); auto gather_ins = m1.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), reshape_ins, li); m1.add_return({gather_ins}); } EXPECT(m0 == m1); } TEST_CASE(resize_scales) { migraphx::module m0; { // resize op. with scales (float_type) rather than sizes as an input std::vector ds = {1., 1., 2., 3.}; migraphx::shape ss{migraphx::shape::float_type, {4}}; auto li = m0.add_literal(migraphx::literal{ss, ds}); m0.add_instruction(migraphx::make_op("undefined")); migraphx::shape sx{migraphx::shape::int64_type, {1, 1, 2, 2}}; auto inx = m0.add_parameter("X", sx); auto r = m0.add_instruction( migraphx::make_op("resize", {{"mode", "nearest"}, {"nearest_mode", "floor"}, // scales attr. should be ignored when there are 2 inputs {"scales", {1., 2.1, 3.1, 4.1}}, {"coordinate_transformation_mode", "asymmetric"}}), inx, li); m0.add_return({r}); } run_pass(m0); migraphx::module m1; { migraphx::shape sx{migraphx::shape::int64_type, {1, 1, 2, 2}}; auto inx = m1.add_parameter("X", sx); std::vector indices = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; migraphx::shape ss{migraphx::shape::int32_type, {1, 1, 4, 6}}; auto li = m1.insert_literal(inx, migraphx::literal{ss, indices}); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), inx); auto gather_ins = m1.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), reshape_ins, li); m1.add_return({gather_ins}); } EXPECT(m0 == m1); } TEST_CASE(static_broadcast) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {2, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {4}}}; auto literal_ins = m0.add_literal(migraphx::literal{lit_s, {6, 5, 4, 3}}); auto broadcast_lit = m0.add_instruction(migraphx::make_op("broadcast", {{"axis", 1}}), literal_ins, input); auto add_ins = m0.add_instruction(migraphx::make_op("add"), input, broadcast_lit); m0.add_return({add_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 4}}; auto input = m1.add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {4}}}; auto literal_ins = m1.add_literal(migraphx::literal{lit_s, {6, 5, 4, 3}}); auto broadcast_lit = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), literal_ins); auto add_ins = m1.add_instruction(migraphx::make_op("add"), input, broadcast_lit); m1.add_return({add_ins}); } EXPECT(m0 == m1); } TEST_CASE(static_multibroadcast) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {2, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}, {0}}}; auto literal_ins = m0.add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = m0.add_instruction(migraphx::make_op("multibroadcast"), literal_ins, input); auto add_ins = m0.add_instruction(migraphx::make_op("add"), input, broadcast_lit); m0.add_return({add_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 4}}; auto input = m1.add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}, {0}}}; auto literal_ins = m1.add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), literal_ins); auto add_ins = m1.add_instruction(migraphx::make_op("add"), input, broadcast_lit); m1.add_return({add_ins}); } EXPECT(m0 == m1); } TEST_CASE(after_split_dyn_broadcast_match) { migraphx::program p0; { auto* mm1 = p0.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input1 = mm1->add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {4}}}; auto literal_ins = mm1->add_literal(migraphx::literal{lit_s, {6, 5, 4, 3}}); auto broadcast_lit = mm1->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}}), literal_ins, input1); auto add_ins = mm1->add_instruction(migraphx::make_op("add"), input1, broadcast_lit); mm1->add_return({add_ins}); } migraphx::run_passes(p0, {migraphx::split_single_dyn_dim{}, migraphx::dead_code_elimination{}, migraphx::simplify_dyn_ops{}}); migraphx::program p1; { auto* mm0 = p1.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p1.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {4}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6, 5, 4, 3}}); auto broadcast_lit = submod->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", sm_shape.lens()}}), literal_ins); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } EXPECT(p0 == p1); } TEST_CASE(const_slice_2input_ends_axes) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"ends", {3}}, {"axes", {0}}}), input, input_starts); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_slice_2input_starts_axes) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"axes", {0}}}), input, input_ends); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_slice_2input_starts_ends) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_axes = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}}), input, input_axes); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_slice_3input_axes_only) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"axes", {0}}}), input, input_starts, input_ends); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_slice_3input_ends_only) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto input_axes = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"ends", {3}}}), input, input_starts, input_axes); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_slice_3inputs_starts_only) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto input_axes = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"starts", {0}}}), input, input_ends, input_axes); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_slice_2input_ends_axes_dyn) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {{6, 6}, {2, 4, {2, 4}}, {2, 4, {2, 4}}}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"ends", {3}}, {"axes", {0}}}), input, input_starts); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {{6, 6}, {2, 4, {2, 4}}, {2, 4, {2, 4}}}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_slice_3input_dyn) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {{6, 6}, {2, 4, {2, 4}}, {2, 4, {2, 4}}}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"axes", {0}}}), input, input_starts, input_ends); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {{6, 6}, {2, 4, {2, 4}}, {2, 4, {2, 4}}}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_slice_4input) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m0.add_parameter("data", s); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto input_axes = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( migraphx::make_op("slice"), input, input_starts, input_ends, input_axes); m0.add_return({slice_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {6, 4, 4}}; auto input = m1.add_parameter("data", s); auto slice_ins = m1.add_instruction( migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"axes", {0}}}), input); m1.add_return({slice_ins}); } EXPECT(m0 == m1); } TEST_CASE(static_dimensions_of0) { // dead_code_elimination will get rid of atan migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {2, 4, 4}}; auto input = m0.add_parameter("data", s); auto atan_ins = m0.add_instruction(migraphx::make_op("atan"), input); auto dimensions_of_ins = m0.add_instruction(migraphx::make_op("dimensions_of", {{"end", 3}}), atan_ins); m0.add_return({dimensions_of_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 4, 4}}; m1.add_parameter("data", s); migraphx::shape lit_shape{migraphx::shape::int64_type, {3}}; std::vector lit_data = {2, 4, 4}; auto lit_ins = m1.add_literal(migraphx::literal{lit_shape, lit_data}); m1.add_return({lit_ins}); } EXPECT(m0 == m1); } TEST_CASE(static_dimensions_of1) { // dead_code_elimination will get rid of atan migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {{2, 4, {2, 4}}, {4, 4}, {4, 4}}}; auto input = m0.add_parameter("data", s); auto atan_ins = m0.add_instruction(migraphx::make_op("atan"), input); auto dimensions_of_ins = m0.add_instruction( migraphx::make_op("dimensions_of", {{"start", 1}, {"end", 3}}), atan_ins); m0.add_return({dimensions_of_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {{2, 4, {2, 4}}, {4, 4}, {4, 4}}}; m1.add_parameter("data", s); migraphx::shape lit_shape{migraphx::shape::int64_type, {2}}; std::vector lit_data = {4, 4}; auto lit_ins = m1.add_literal(migraphx::literal{lit_shape, lit_data}); m1.add_return({lit_ins}); } EXPECT(m0 == m1); } // Does nothing because the dynamic_dimensions from start to end // are not all fixed TEST_CASE(static_dimensions_of_nonfixed) { // dead_code_elimination will get rid of atan migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {{2, 4, {2, 4}}, {4, 8}, {4, 8}}}; auto input = m0.add_parameter("data", s); auto atan_ins = m0.add_instruction(migraphx::make_op("atan"), input); auto dimensions_of_ins = m0.add_instruction( migraphx::make_op("dimensions_of", {{"start", 1}, {"end", 3}}), atan_ins); m0.add_return({dimensions_of_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {{2, 4, {2, 4}}, {4, 8}, {4, 8}}}; auto input = m1.add_parameter("data", s); auto atan_ins = m1.add_instruction(migraphx::make_op("atan"), input); auto dimensions_of_ins = m1.add_instruction( migraphx::make_op("dimensions_of", {{"start", 1}, {"end", 3}}), atan_ins); m1.add_return({dimensions_of_ins}); } EXPECT(m0 == m1); } TEST_CASE(constant_alloc_reshape) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {3, 32}}; auto input = m0.add_parameter("data", s); migraphx::shape lit_s{migraphx::shape::int64_type, {3}}; auto literal_ins = m0.add_literal(migraphx::literal{lit_s, {3, 4, 8}}); auto alloc_ins = m0.add_instruction( migraphx::make_op("allocate", {{"buf_type", migraphx::shape::float_type}}), literal_ins); auto reshape_ins = m0.add_instruction(migraphx::make_op("reshape"), input, alloc_ins); m0.add_return({reshape_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {3, 32}}; auto input = m1.add_parameter("data", s); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {3, 4, 8}}}), input); m1.add_return({reshape_ins}); } EXPECT(m0 == m1); } // A more contrived example to test static dimensions_of and constant reshape TEST_CASE(static_dimensions_of_to_constant_alloc_reshape) { migraphx::module m0; { migraphx::shape input_shape{migraphx::shape::float_type, {3, 4, 8}}; auto x_param = m0.add_parameter("x", input_shape); auto dimensions_of_ins = m0.add_instruction(migraphx::make_op("dimensions_of", {{"end", 3}}), x_param); migraphx::shape lit_shape{migraphx::shape::int64_type, {1}}; auto lit0 = m0.add_literal(migraphx::literal{lit_shape, {0}}); auto gather_ins = m0.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), dimensions_of_ins, lit0); auto slice_ins = m0.add_instruction( migraphx::make_op("slice", {{"starts", {1}}, {"ends", {3}}, {"axes", {0}}}), dimensions_of_ins); auto reduce_ins = m0.add_instruction(migraphx::make_op("reduce_prod", {{"axes", {0}}}), slice_ins); auto concat_ins = m0.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), gather_ins, reduce_ins); auto alloc_ins = m0.add_instruction( migraphx::make_op("allocate", {{"buf_type", migraphx::shape::float_type}}), concat_ins); auto reshape_ins = m0.add_instruction(migraphx::make_op("reshape"), x_param, alloc_ins); m0.add_return({reshape_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {3, 4, 8}}; auto x_param = m1.add_parameter("x", s); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {3, 32}}}), x_param); m1.add_return({reshape_ins}); } EXPECT(m0 == m1); } TEST_CASE(const_alloc_fill) { migraphx::module m0; { migraphx::shape val_shape{migraphx::shape::int64_type, {1}, {0}}; std::vector lit_data = {3}; auto value_lit = m0.add_literal(migraphx::literal{val_shape, lit_data}); migraphx::shape lit_s{migraphx::shape::int64_type, {3}}; auto output_dim_lit = m0.add_literal(migraphx::literal{lit_s, {3, 4, 4}}); auto alloc_ins = m0.add_instruction( migraphx::make_op("allocate", {{"buf_type", migraphx::shape::int64_type}}), output_dim_lit); auto ret = m0.add_instruction(migraphx::make_op("fill"), value_lit, alloc_ins); m0.add_return({ret}); } run_pass(m0); migraphx::module m1; { migraphx::shape lit_shape{migraphx::shape::int64_type, {3, 4, 4}}; std::vector lit_data(3 * 4 * 4, 3); auto ret = m1.add_literal(migraphx::literal{lit_shape, lit_data}); m1.add_return({ret}); } EXPECT(m0 == m1); } TEST_CASE(static_broadcast_for_dot) { migraphx::module m0; { migraphx::shape s{migraphx::shape::float_type, {2, 4, 6, 8}}; auto input = m0.add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {8, 10}}}; std::vector lit_vec(80, 2.f); auto literal_ins = m0.add_literal(migraphx::literal{lit_s, lit_vec}); auto broadcast_for_dot_ins = m0.add_instruction(migraphx::make_op("broadcast_for_dot"), literal_ins, input); auto dot_ins = m0.add_instruction(migraphx::make_op("dot"), input, broadcast_for_dot_ins); m0.add_return({dot_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape s{migraphx::shape::float_type, {2, 4, 6, 8}}; auto input = m1.add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {8, 10}}}; std::vector lit_vec(80, 2.f); auto literal_ins = m1.add_literal(migraphx::literal{lit_s, lit_vec}); auto multibroadcast_ins = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 8, 10}}}), literal_ins); auto dot_ins = m1.add_instruction(migraphx::make_op("dot"), input, multibroadcast_ins); m1.add_return({dot_ins}); } EXPECT(m0 == m1); } TEST_CASE(static_onehot) { // depth as a literal migraphx::module m0; { migraphx::shape inds_s{migraphx::shape::int64_type, {4}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = m0.add_parameter("indices", inds_s); auto depth_lit = m0.add_literal(migraphx::literal{depth_s, {3}}); auto values_param = m0.add_parameter("values", values_s); auto onehot_ins = m0.add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_param, depth_lit, values_param); m0.add_return({onehot_ins}); } run_pass(m0); migraphx::module m1; { migraphx::shape inds_s{migraphx::shape::int64_type, {4}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = m1.add_parameter("indices", inds_s); auto values_param = m1.add_parameter("values", values_s); migraphx::shape output_shape{migraphx::shape::float_type, {4, 3}}; std::vector zeros(output_shape.elements(), 0); auto zeros_lit = m1.add_literal(migraphx::literal(output_shape, zeros)); auto unsqueeze_inds = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), inds_param); auto ones_lit = m1.add_literal( migraphx::literal(migraphx::shape{migraphx::shape::float_type, {1}, {0}}, {1})); auto mb_ones = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {4, 1}}}), ones_lit); auto mask = m1.add_instruction( migraphx::make_op("scatter_none", {{"axis", 1}, {"skip_out_of_bounds", 1}}), zeros_lit, unsqueeze_inds, mb_ones); auto off_val = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), values_param); auto on_val = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), values_param); auto diff_val = m1.add_instruction(migraphx::make_op("sub"), on_val, off_val); auto mul_diff_mask = add_common_op(m1, migraphx::make_op("mul"), {diff_val, mask}); auto ret = add_common_op(m1, migraphx::make_op("add"), {off_val, mul_diff_mask}); m1.add_return({ret}); } EXPECT(m0 == m1); // depth as an attribute migraphx::module m2; { migraphx::shape inds_s{migraphx::shape::int64_type, {4}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = m2.add_parameter("indices", inds_s); auto values_param = m2.add_parameter("values", values_s); auto onehot_ins = m2.add_instruction( migraphx::make_op("onehot", {{"axis", -1}, {"depth", 3}}), inds_param, values_param); m2.add_return({onehot_ins}); } run_pass(m2); EXPECT(m2 == m1); } TEST_CASE(onehot_cannot_simplify) { migraphx::module m0; { migraphx::shape inds_s{migraphx::shape::int64_type, {4}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = m0.add_parameter("indices", inds_s); auto depth_param = m0.add_parameter("depth", depth_s); auto values_param = m0.add_parameter("values", values_s); auto onehot_ins = m0.add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_param, depth_param, values_param); m0.add_return({onehot_ins}); } migraphx::module m1 = m0; run_pass(m0); EXPECT(m0 == m1); } // Test case with static output shape in the submodules (look at `sm_shape`) TEST_CASE(select_module_update0) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; auto max_int = std::numeric_limits::max(); sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{0, max_int}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } migraphx::run_passes(p0, {migraphx::simplify_dyn_ops{}, migraphx::dead_code_elimination{}}); // difference is `output_dyn_shapes` attribute in `select_module` // multibroadcast also simplified migraphx::program p1; { auto* mm1 = p1.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p1.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", sm_shape.lens()}}), literal_ins); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm1->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm1->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm1->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm1->add_return({ret}); } EXPECT(p0 == p1); } // Test case with dynamic output shape in the submodules (look at `sm_shape`) TEST_CASE(select_module_update1) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {{batch_size, batch_size}, {4, 20}}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 20}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; auto max_int = std::numeric_limits::max(); sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{0, max_int}, {4, 20}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } migraphx::run_passes(p0, {migraphx::simplify_dyn_ops{}, migraphx::dead_code_elimination{}}); // difference is `output_dyn_shapes` attribute in `select_module` // note that multibroadcast is not simplify-able for this case migraphx::program p1; { auto* mm1 = p1.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p1.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {{batch_size, batch_size}, {4, 20}}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 20}}}; auto input0 = mm1->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 20}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm1->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm1->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm1->add_return({ret}); } EXPECT(p0 == p1); } // contrived example where each submodule to select_module outputs the same static shape. TEST_CASE(select_module_update2) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); auto slice_data = submod->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), sm_input); submod->add_return({slice_data}); return submod; // output shape is static shape with lens={1, 4} }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; auto max_int = std::numeric_limits::max(); sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{0, max_int}, {4, 4}}}); // {0, max_int} dimension for `output_dyn_shapes` attribute will be simplified to // a fixed shape of {1, 4} migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } migraphx::run_passes(p0, {migraphx::simplify_dyn_ops{}, migraphx::dead_code_elimination{}}); migraphx::program p1; { auto* mm1 = p1.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p1.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); auto slice_data = submod->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), sm_input); submod->add_return({slice_data}); return submod; // output shape is static shape with lens={1, 4} }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm1->add_parameter("data", s); std::vector sub_shapes = {}; // note single static shape output sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {1, 4}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm1->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm1->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm1->add_return({ret}); } EXPECT(p0 == p1); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/simplify_qdq_test.cpp000066400000000000000000002643331510465702400217760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace match = migraphx::match; static bool is_convolution(const migraphx::instruction& ins) { return ins.name() == "convolution"; } static bool is_dot(const migraphx::instruction& ins) { return ins.name() == "dot"; } static void run_pass(migraphx::module& m) { run_passes(m, {migraphx::simplify_qdq{}, migraphx::dead_code_elimination{}}); } static void run_cse(migraphx::module& m) { run_passes(m, {migraphx::eliminate_common_subexpression{}, migraphx::dead_code_elimination{}}); } static migraphx::instruction_ref init_zero_point(migraphx::module& m, migraphx::instruction_ref q_ins) { auto zp = m.add_literal(migraphx::literal{migraphx::shape{q_ins->get_shape().type()}, {0}}); return m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", q_ins->get_shape().lens()}}), zp); } TEST_CASE(remove_qdq) { migraphx::shape sh1{migraphx::shape::float_type, {100, 100}}; migraphx::shape sh2{migraphx::shape::float_type, {100, 100}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto add = m1.add_instruction(migraphx::make_op("add"), d1, d2); m1.add_return({add}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto add = m2.add_instruction(migraphx::make_op("add"), t1, t2); m2.add_return({add}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(qdq_different_scales) { migraphx::shape sh1{migraphx::shape::float_type, {100, 100}}; migraphx::shape sh2{migraphx::shape::float_type, {100, 100}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale1 = m1.add_literal(0.5f); auto scale2 = m1.add_literal(0.4f); auto zero = m1.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale1, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale2, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale1, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale2, zero); auto add = m1.add_instruction(migraphx::make_op("add"), d1, d2); m1.add_return({add}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zero); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zero); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_fp16) { migraphx::shape sh1{migraphx::shape::half_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::half_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto t1f = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), t1); auto t2f = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), t2); auto q1 = add_quantize_op(m1, "quantizelinear", t1f, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2f, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto d1h = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), d1); auto d2h = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), d2); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1h, d2h); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto t1f = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), t1); auto t2f = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), t2); auto q1 = add_quantize_op(m2, "quantizelinear", t1f, scale, zero); auto q2 = add_quantize_op(m2, "quantizelinear", t2f, scale, zero); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); auto d3h = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), d3); m2.add_return({d3h}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_multi_scale) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::shape sh3{migraphx::shape::float_type, {1280}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale1 = m1.add_literal(migraphx::generate_literal(sh3, 0)); auto scale2 = m1.add_literal(0.4f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale1, zero, 0); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale1, zero, 0); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale2, zero, 1); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale2, zero, 1); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale1 = m2.add_literal(migraphx::generate_literal(sh3, 0)); auto scale2 = m2.add_literal(0.4f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale1, zero, 0); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale2, zero, 1); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale1, scale2, 0, 1, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_broadcasted) { migraphx::shape sh1{migraphx::shape::float_type, {2, 1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto d2_mb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 1000, 1024}}}), d2); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2_mb); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zero); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zero); auto q2_mb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 1000, 1024}}}), q2); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2_mb); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_transposed) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1024, 1000}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto d2_t = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), d2); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2_t); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zero); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zero); auto q2_t = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), q2); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2_t); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_reshaped) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1024, 1000}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto d2_t = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1000, 1024}}}), d2); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2_t); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zero); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zero); auto q2_t = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {1000, 1024}}}), q2); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2_t); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_multi_scale_all_skip_post_dq_ops) { migraphx::shape sh1{migraphx::shape::float_type, {2, 3, 1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1024, 10, 100}}; migraphx::shape sh3{migraphx::shape::float_type, {1280}}; migraphx::shape sh4{migraphx::shape::float_type, {1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale1 = m1.add_literal(migraphx::generate_literal(sh3, 0)); auto scale2 = m1.add_literal(migraphx::generate_literal(sh4, 0)); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale1, zero, 2); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale1, zero, 2); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale2, zero, 0); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale2, zero, 0); auto d2_r = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1024, 1000}}}), d2); auto d2_t = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), d2_r); auto d2_mb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 1000, 1024}}}), d2_t); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2_mb); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale1 = m2.add_literal(migraphx::generate_literal(sh3, 0)); auto scale2 = m2.add_literal(migraphx::generate_literal(sh4, 0)); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale1, zero, 2); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale2, zero, 0); auto q2_r = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {1024, 1000}}}), q2); auto q2_t = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), q2_r); auto q2_mb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 1000, 1024}}}), q2_t); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2_mb); auto out_scale = add_scale_mul(m2, scale1, scale2, 2, 3, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_multi_scale_unsupported_axis) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::shape sh3{migraphx::shape::float_type, {1000}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale1 = m1.add_literal(migraphx::generate_literal(sh3, 0)); auto scale2 = m1.add_literal(0.4f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale1, zero, 1); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale1, zero, 1); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale2, zero, 1); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale2, zero, 1); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto dot = m2.add_instruction(migraphx::make_op("dot"), t1, t2); m2.add_return({dot}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_asymmetric_first_arg) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zp1 = m1.add_literal(std::int8_t{1}); auto zp2 = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zp1); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zp1); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zp2); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zp2); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zp1 = m2.add_literal(std::int8_t{1}); auto zp2 = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zp1); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zp2); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto out_zp = init_zero_point(m2, dot); auto zp1_bc = broadcast_shift(m2, zp1, t1->get_shape().lens()); auto zp_term = m2.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, q2); out_zp = m2.add_instruction(migraphx::make_op("add"), out_zp, zp_term); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale, out_zp); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_asymmetric_second_arg) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zp1 = m1.add_literal(std::int8_t{0}); auto zp2 = m1.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zp1); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zp1); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zp2); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zp2); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zp1 = m2.add_literal(std::int8_t{0}); auto zp2 = m2.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zp1); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zp2); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto out_zp = init_zero_point(m2, dot); auto zp2_bc = broadcast_shift(m2, zp2, t2->get_shape().lens()); auto zp_term = m2.add_instruction(migraphx::make_op("quant_dot"), q1, zp2_bc); out_zp = m2.add_instruction(migraphx::make_op("add"), out_zp, zp_term); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale, out_zp); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_asymmetric_both_args) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zp1 = m1.add_literal(std::int8_t{2}); auto zp2 = m1.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zp1); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zp1); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zp2); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zp2); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zp1 = m2.add_literal(std::int8_t{2}); auto zp2 = m2.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zp1); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zp2); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto out_zp = init_zero_point(m2, dot); auto zp1_bc = broadcast_shift(m2, zp1, t1->get_shape().lens()); auto zp2_bc = broadcast_shift(m2, zp2, t2->get_shape().lens()); auto zp_term1 = m2.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, q2); out_zp = m2.add_instruction(migraphx::make_op("add"), out_zp, zp_term1); auto zp_term2 = m2.add_instruction(migraphx::make_op("quant_dot"), q1, zp2_bc); out_zp = m2.add_instruction(migraphx::make_op("add"), out_zp, zp_term2); auto zp_term3 = m2.add_instruction(migraphx::make_op("quant_dot"), zp1_bc, zp2_bc); out_zp = m2.add_instruction(migraphx::make_op("sub"), out_zp, zp_term3); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale, out_zp); m2.add_return({d3}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_uint8) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::uint8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2); m1.add_return({dot}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto dot = m2.add_instruction(migraphx::make_op("dot"), t1, t2); m2.add_return({dot}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_add) { migraphx::shape sh1{migraphx::shape::float_type, {1280, 1000}}; migraphx::shape sh2{migraphx::shape::float_type, {1000, 1024}}; migraphx::shape sh3{migraphx::shape::float_type, {1280, 1024}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto ab = m1.add_parameter("ab", sh3); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto dot = m1.add_instruction(migraphx::make_op("dot"), d1, d2); auto q3 = add_quantize_op(m1, "quantizelinear", dot, scale, zero); auto d3 = add_quantize_op(m1, "dequantizelinear", q3, scale, zero); auto add = m1.add_instruction(migraphx::make_op("add"), d3, ab); m1.add_return({add}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto ab = m2.add_parameter("ab", sh3); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zero); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zero); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot, out_scale); auto add = m2.add_instruction(migraphx::make_op("add"), d3, ab); m2.add_return({add}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(dot_add_multiple_dq_use) { migraphx::shape sh1{migraphx::shape::float_type, {32, 1}}; migraphx::shape sh2{migraphx::shape::float_type, {32, 32}}; migraphx::module m1; { auto t1 = m1.add_parameter("t1", sh1); auto t2 = m1.add_parameter("t2", sh2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", t1, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto d1_t = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), d1); auto d1_tmb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {32, 32}}}), d1_t); auto d1_tmbc = m1.add_instruction(migraphx::make_op("contiguous"), d1_tmb); auto q2 = add_quantize_op(m1, "quantizelinear", t2, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto dot_1 = m1.add_instruction(migraphx::make_op("dot"), d1_tmbc, d2); auto q3 = add_quantize_op(m1, "quantizelinear", dot_1, scale, zero); auto d3 = add_quantize_op(m1, "dequantizelinear", q3, scale, zero); auto dot_2 = m1.add_instruction(migraphx::make_op("dot"), d3, d1); auto add = m1.add_instruction(migraphx::make_op("add"), {dot_2, d1}); m1.add_return({add}); } migraphx::module m2; { auto t1 = m2.add_parameter("t1", sh1); auto t2 = m2.add_parameter("t2", sh2); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", t1, scale, zero); auto q1_t = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), q1); auto q1_tmb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {32, 32}}}), q1_t); auto q1_tmbc = m2.add_instruction(migraphx::make_op("contiguous"), q1_tmb); auto q2 = add_quantize_op(m2, "quantizelinear", t2, scale, zero); auto dot_1 = m2.add_instruction(migraphx::make_op("quant_dot"), q1_tmbc, q2); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, dot_1->get_shape().lens()); auto d3 = add_quantize_op(m2, "dequantizelinear", dot_1, out_scale); auto d3_q = add_quantize_op(m2, "quantizelinear", d3, scale, zero); auto dot_2 = m2.add_instruction(migraphx::make_op("quant_dot"), d3_q, q1); auto out_scale_2 = add_scale_mul(m2, scale, scale, 1, 1, dot_2->get_shape().lens()); auto d4 = add_quantize_op(m2, "dequantizelinear", dot_2, out_scale_2); auto add = m2.add_instruction(migraphx::make_op("add"), d4, t1); m2.add_return({add}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(conv) { migraphx::shape s4{migraphx::shape::int8_type, {1280, 320, 1, 1}}; migraphx::shape s7{migraphx::shape::float_type, {1, 320, 7, 7}}; migraphx::module m1; { auto input = m1.add_parameter("input", s7); auto weights = m1.add_parameter("weights", s4); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto d1 = add_quantize_op(m1, "dequantizelinear", weights, scale, zero); auto q1 = add_quantize_op(m1, "quantizelinear", input, scale, zero); auto d5 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto c1 = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); m1.add_return({c1}); } migraphx::module m2; { auto input = m2.add_parameter("input", s7); auto weights = m2.add_parameter("weights", s4); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", input, scale, zero); auto c1 = m2.add_instruction(migraphx::make_op("quant_convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), q1, weights); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, c1->get_shape().lens()); auto d6 = add_quantize_op(m2, "dequantizelinear", c1, out_scale); m2.add_return({d6}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(conv_asymmetric_input) { migraphx::shape s4{migraphx::shape::int8_type, {1280, 320, 1, 1}}; migraphx::shape s7{migraphx::shape::float_type, {1, 320, 7, 7}}; migraphx::module m1; { auto input = m1.add_parameter("input", s7); auto weights = m1.add_parameter("weights", s4); auto scale = m1.add_literal(0.5f); auto zp_in = m1.add_literal(std::int8_t{1}); auto zp_w = m1.add_literal(std::int8_t{0}); auto d1 = add_quantize_op(m1, "dequantizelinear", weights, scale, zp_w); auto q1 = add_quantize_op(m1, "quantizelinear", input, scale, zp_in); auto d5 = add_quantize_op(m1, "dequantizelinear", q1, scale, zp_in); auto c1 = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); m1.add_return({c1}); } migraphx::module m2; { auto input = m2.add_parameter("input", s7); auto weights = m2.add_parameter("weights", s4); auto scale = m2.add_literal(0.5f); auto zp_in = m2.add_literal(std::int8_t{1}); auto q1 = add_quantize_op(m2, "quantizelinear", input, scale, zp_in); auto c1 = m2.add_instruction(migraphx::make_op("quant_convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), q1, weights); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, c1->get_shape().lens()); auto out_zp = init_zero_point(m2, c1); auto zp_in_bc = broadcast_shift(m2, zp_in, input->get_shape().lens()); auto zp_term = m2.add_instruction(migraphx::make_op("quant_convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), zp_in_bc, weights); out_zp = m2.add_instruction(migraphx::make_op("add"), out_zp, zp_term); auto d6 = add_quantize_op(m2, "dequantizelinear", c1, out_scale, out_zp); m2.add_return({d6}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(conv_multi_scale) { migraphx::shape s4{migraphx::shape::int8_type, {1280, 320, 1, 1}}; migraphx::shape s7{migraphx::shape::float_type, {1, 320, 7, 7}}; migraphx::shape s8{migraphx::shape::float_type, {1280}}; migraphx::module m1; { auto input = m1.add_parameter("input", s7); auto weights = m1.add_parameter("weights", s4); auto w_scale = m1.add_literal(migraphx::generate_literal(s8, 0)); auto inp_scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto d1 = add_quantize_op(m1, "dequantizelinear", weights, w_scale, zero, 0); auto q1 = add_quantize_op(m1, "quantizelinear", input, inp_scale, zero); auto d5 = add_quantize_op(m1, "dequantizelinear", q1, inp_scale, zero); auto c1 = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); m1.add_return({c1}); } migraphx::module m2; { auto input = m2.add_parameter("input", s7); auto weights = m2.add_parameter("weights", s4); auto w_scale = m2.add_literal(migraphx::generate_literal(s8, 0)); auto inp_scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q_inp = add_quantize_op(m2, "quantizelinear", input, inp_scale, zero); auto c1 = m2.add_instruction(migraphx::make_op("quant_convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), q_inp, weights); auto out_scale = add_scale_mul(m2, inp_scale, w_scale, 1, 1, c1->get_shape().lens()); auto d1 = add_quantize_op(m2, "dequantizelinear", c1, out_scale); m2.add_return({d1}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(conv_multi_scale_unsupported_axis) { migraphx::shape s4{migraphx::shape::int8_type, {1280, 320, 1, 1}}; migraphx::shape s7{migraphx::shape::float_type, {1, 320, 7, 7}}; migraphx::shape s8{migraphx::shape::float_type, {320}}; migraphx::module m1; { auto input = m1.add_parameter("input", s7); auto weights = m1.add_parameter("weights", s4); auto scale = m1.add_literal(migraphx::generate_literal(s8, 0)); auto zero = m1.add_literal(std::int8_t{0}); auto d1 = add_quantize_op(m1, "dequantizelinear", weights, scale, zero); auto q1 = add_quantize_op(m1, "quantizelinear", input, scale, zero); auto d5 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto c1 = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); m1.add_return({c1}); } migraphx::module m2; { auto input = m2.add_parameter("input", s7); auto weights = m2.add_parameter("weights", s4); auto scale = m2.add_literal(migraphx::generate_literal(s8, 0)); auto d1 = add_quantize_op(m2, "dequantizelinear", weights, scale); auto c1 = m2.add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), input, d1); m2.add_return({c1}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(conv_bias_add) { migraphx::shape s4{migraphx::shape::int8_type, {1280, 320, 1, 1}}; migraphx::shape s6{migraphx::shape::int32_type, {1280}}; migraphx::shape s7{migraphx::shape::float_type, {1, 320, 7, 7}}; migraphx::module m1; { auto input = m1.add_parameter("input", s7); auto weights = m1.add_parameter("weights", s4); auto bias = m1.add_parameter("bias", s6); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto zero32 = m1.add_literal(std::int32_t{0}); auto d1 = add_quantize_op(m1, "dequantizelinear", weights, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", bias, scale, zero32); auto q1 = add_quantize_op(m1, "quantizelinear", input, scale, zero); auto d5 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto c1 = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); auto b1 = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 1280, 7, 7}}}), d2); auto a1 = m1.add_instruction(migraphx::make_op("add"), c1, b1); m1.add_return({a1}); } migraphx::module m2; { auto input = m2.add_parameter("input", s7); auto weights = m2.add_parameter("weights", s4); auto bias = m2.add_parameter("bias", s6); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto d2 = add_quantize_op(m2, "dequantizelinear", bias, scale); auto q1 = add_quantize_op(m2, "quantizelinear", input, scale, zero); auto c1 = m2.add_instruction(migraphx::make_op("quant_convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), q1, weights); auto out_scale = add_scale_mul(m2, scale, scale, 1, 1, c1->get_shape().lens()); auto d6 = add_quantize_op(m2, "dequantizelinear", c1, out_scale); auto b1 = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 1280, 7, 7}}}), d2); auto a1 = m2.add_instruction(migraphx::make_op("add"), d6, b1); m2.add_return({a1}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(conv_pooling_dot) { migraphx::shape s2{migraphx::shape::int8_type, {1280, 1000}}; migraphx::shape s3{migraphx::shape::int8_type, {1000}}; migraphx::shape s4{migraphx::shape::int8_type, {1280, 320, 1, 1}}; migraphx::shape s6{migraphx::shape::int32_type, {1280}}; migraphx::shape s7{migraphx::shape::float_type, {1, 320, 7, 7}}; migraphx::module m1; { auto db = m1.add_parameter("db", s2); // dot input b auto ab = m1.add_parameter("ab", s3); // add input b auto weights = m1.add_parameter("weights", s4); auto bias = m1.add_parameter("bias", s6); auto input = m1.add_parameter("input", s7); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto zero32 = m1.add_literal(std::int32_t{0}); auto d1 = add_quantize_op(m1, "dequantizelinear", weights, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", bias, scale, zero32); auto d3 = add_quantize_op(m1, "dequantizelinear", ab, scale, zero); auto d4 = add_quantize_op(m1, "dequantizelinear", db, scale, zero); auto q1 = add_quantize_op(m1, "quantizelinear", input, scale, zero); auto d5 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto c1 = m1.add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); auto bc1 = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 1280, 7, 7}}}), d2); auto a1 = m1.add_instruction(migraphx::make_op("add"), c1, bc1); auto ap = m1.add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"lengths", {7, 7}}, {"dilations", {1, 1}}, {"ceil_mode", 0}}), a1); auto fl = m1.add_instruction(migraphx::make_op("flatten", {{"axis", 1}}), ap); auto q4 = add_quantize_op(m1, "quantizelinear", fl, scale, zero); auto d8 = add_quantize_op(m1, "dequantizelinear", q4, scale, zero); auto dot = m1.add_instruction(migraphx::make_op("dot"), d8, d4); auto q5 = add_quantize_op(m1, "quantizelinear", dot, scale, zero); auto d9 = add_quantize_op(m1, "dequantizelinear", q5, scale, zero); auto mb1 = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 1000}}}), d3); auto a2 = m1.add_instruction(migraphx::make_op("add"), d9, mb1); m1.add_return({a2}); } migraphx::module m2; { auto db = m2.add_parameter("db", s2); // dot input b auto ab = m2.add_parameter("ab", s3); // add input b auto weights = m2.add_parameter("weights", s4); auto bias = m2.add_parameter("bias", s6); auto input = m2.add_parameter("input", s7); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto d2 = add_quantize_op(m2, "dequantizelinear", bias, scale); auto d3 = add_quantize_op(m2, "dequantizelinear", ab, scale); auto q1 = add_quantize_op(m2, "quantizelinear", input, scale, zero); auto c1 = m2.add_instruction(migraphx::make_op("quant_convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), q1, weights); auto out_scale1 = add_scale_mul(m2, scale, scale, 1, 1, c1->get_shape().lens()); auto d5 = add_quantize_op(m2, "dequantizelinear", c1, out_scale1); auto bc1 = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 1280, 7, 7}}}), d2); auto a1 = m2.add_instruction(migraphx::make_op("add"), d5, bc1); auto ap = m2.add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"lengths", {7, 7}}, {"dilations", {1, 1}}, {"ceil_mode", 0}}), a1); auto fl = m2.add_instruction(migraphx::make_op("flatten", {{"axis", 1}}), ap); auto q4 = add_quantize_op(m2, "quantizelinear", fl, scale, zero); auto dot = m2.add_instruction(migraphx::make_op("quant_dot"), q4, db); auto out_scale2 = add_scale_mul(m2, scale, scale, 1, 0, dot->get_shape().lens()); auto d9 = add_quantize_op(m2, "dequantizelinear", dot, out_scale2); auto mb1 = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 1000}}}), d3); auto a2 = m2.add_instruction(migraphx::make_op("add"), d9, mb1); m2.add_return({a2}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(mobilenet_snippet) { migraphx::shape s2{migraphx::shape::int8_type, {1280, 1000}}; migraphx::shape s3{migraphx::shape::int8_type, {1000}}; migraphx::shape s4{migraphx::shape::int8_type, {1280, 320, 1, 1}}; migraphx::shape s6{migraphx::shape::int32_type, {1280}}; migraphx::shape s7{migraphx::shape::float_type, {1, 320, 7, 7}}; auto create_module = [&]() { migraphx::module mm; auto db = mm.add_parameter("db", s2); // dot input b auto ab = mm.add_parameter("ab", s3); // add input b auto weights = mm.add_parameter("weights", s4); auto bias = mm.add_parameter("bias", s6); auto input = mm.add_parameter("input", s7); auto scale = mm.add_literal(0.5f); auto zero = mm.add_literal(std::int8_t{0}); auto zero32 = mm.add_literal(std::int32_t{0}); auto d1 = add_quantize_op(mm, "dequantizelinear", weights, scale, zero); auto d2 = add_quantize_op(mm, "dequantizelinear", bias, scale, zero32); auto d3 = add_quantize_op(mm, "dequantizelinear", ab, scale, zero); auto d4 = add_quantize_op(mm, "dequantizelinear", db, scale, zero); auto q1 = add_quantize_op(mm, "quantizelinear", input, scale, zero); auto d5 = add_quantize_op(mm, "dequantizelinear", q1, scale, zero); auto c1 = mm.add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); auto bc1 = mm.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 1280, 7, 7}}}), d2); auto a1 = mm.add_instruction(migraphx::make_op("add"), c1, bc1); auto q2 = add_quantize_op(mm, "quantizelinear", a1, scale, zero); auto d6 = add_quantize_op(mm, "dequantizelinear", q2, scale, zero); auto ap = mm.add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"lengths", {7, 7}}, {"dilations", {1, 1}}, {"ceil_mode", 0}}), d6); auto q3 = add_quantize_op(mm, "quantizelinear", ap, scale, zero); auto d7 = add_quantize_op(mm, "dequantizelinear", q3, scale, zero); auto rs = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {1, -1}}}), d7); auto q4 = add_quantize_op(mm, "quantizelinear", rs, scale, zero); auto d8 = add_quantize_op(mm, "dequantizelinear", q4, scale, zero); auto dot = mm.add_instruction(migraphx::make_op("dot"), d8, d4); auto q5 = add_quantize_op(mm, "quantizelinear", dot, scale, zero); auto d9 = add_quantize_op(mm, "dequantizelinear", q5, scale, zero); auto mb1 = mm.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 1000}}}), d3); auto a2 = mm.add_instruction(migraphx::make_op("add"), d9, mb1); mm.add_return({a2}); return mm; }; auto mod1 = create_module(); auto mod2 = create_module(); run_pass(mod2); auto match_qdq = migraphx::match::name("dequantizelinear")( migraphx::match::arg(0)(migraphx::match::name("quantizelinear"))); auto ins1 = migraphx::match::find_match(mod1, match_qdq); auto ins2 = migraphx::match::find_match(mod2, match_qdq); EXPECT((ins1.result != mod1.end()) and (ins2.result == mod2.end())); EXPECT(any_of(mod1, &is_convolution)); EXPECT(none_of(mod2, &is_convolution)); EXPECT(any_of(mod1, &is_dot)); EXPECT(none_of(mod2, &is_dot)); } TEST_CASE(conv_correctness) { migraphx::shape si{migraphx::shape::float_type, {2, 3, 4, 4}}; migraphx::shape sw{migraphx::shape::int8_type, {2, 3, 3, 3}}; migraphx::program p1; { auto* m1 = p1.get_main_module(); auto input = m1->add_parameter("input", si); auto weights = m1->add_parameter("weights", sw); auto scale_i = m1->add_literal(0.5f); auto scale_w = m1->add_literal(0.1f); auto zero = m1->add_literal(std::int8_t{0}); auto d1 = add_quantize_op(*m1, "dequantizelinear", weights, scale_w, zero); auto q1 = add_quantize_op(*m1, "quantizelinear", input, scale_i, zero); auto d5 = add_quantize_op(*m1, "dequantizelinear", q1, scale_i, zero); auto c1 = m1->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); m1->add_return({c1}); run_pass(*m1); } migraphx::program p2; { auto* m2 = p2.get_main_module(); auto input = m2->add_parameter("input", si); auto weights = m2->add_parameter("weights", sw); auto scale = m2->add_literal(0.1f); auto zero = m2->add_literal(std::int8_t{0}); auto d1 = add_quantize_op(*m2, "dequantizelinear", weights, scale, zero); auto c1 = m2->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), input, d1); m2->add_return({c1}); } std::vector iv(si.elements(), 4); auto input = migraphx::argument(si, iv.data()); std::vector wv(sw.elements(), 10); auto weights = migraphx::argument(sw, wv.data()); p1.compile(migraphx::target(migraphx::make_target("ref"))); p2.compile(migraphx::target(migraphx::make_target("ref"))); auto result1 = p1.eval({{"input", input}, {"weights", weights}}).back(); std::vector rv1(16); result1.visit([&](auto output) { rv1.assign(output.begin(), output.end()); }); auto result2 = p2.eval({{"input", input}, {"weights", weights}}).back(); std::vector rv2(16); result2.visit([&](auto output) { rv2.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(rv1, rv2)); } TEST_CASE(conv_asymmetric_correctness) { migraphx::shape si{migraphx::shape::float_type, {2, 3, 4, 4}}; migraphx::shape sw{migraphx::shape::int8_type, {2, 3, 3, 3}}; migraphx::program p1; { auto* m1 = p1.get_main_module(); auto input = m1->add_parameter("input", si); auto weights = m1->add_parameter("weights", sw); auto scale_i = m1->add_literal(0.5f); auto scale_w = m1->add_literal(0.1f); auto zp_i = m1->add_literal(std::int8_t{25}); auto zp_w = m1->add_literal(std::int8_t{-5}); auto d1 = add_quantize_op(*m1, "dequantizelinear", weights, scale_w, zp_w); auto q1 = add_quantize_op(*m1, "quantizelinear", input, scale_i, zp_i); auto d5 = add_quantize_op(*m1, "dequantizelinear", q1, scale_i, zp_i); auto c1 = m1->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), d5, d1); m1->add_return({c1}); run_pass(*m1); } migraphx::program p2; { auto* m2 = p2.get_main_module(); auto input = m2->add_parameter("input", si); auto weights = m2->add_parameter("weights", sw); auto scale = m2->add_literal(0.1f); auto zp_w = m2->add_literal(std::int8_t{-5}); auto d1 = add_quantize_op(*m2, "dequantizelinear", weights, scale, zp_w); auto c1 = m2->add_instruction(migraphx::make_op("convolution", {{"padding", {0, 0, 0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 1}, {"padding_mode", 0}}), input, d1); m2->add_return({c1}); } std::vector iv(si.elements(), 4); auto input = migraphx::argument(si, iv.data()); std::vector wv(sw.elements(), 10); auto weights = migraphx::argument(sw, wv.data()); p1.compile(migraphx::target(migraphx::make_target("ref"))); p2.compile(migraphx::target(migraphx::make_target("ref"))); auto result1 = p1.eval({{"input", input}, {"weights", weights}}).back(); std::vector rv1(16); result1.visit([&](auto output) { rv1.assign(output.begin(), output.end()); }); auto result2 = p2.eval({{"input", input}, {"weights", weights}}).back(); std::vector rv2(16); result2.visit([&](auto output) { rv2.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(rv1, rv2)); } TEST_CASE(dot_correctness) { migraphx::shape sh1{migraphx::shape::float_type, {10, 4}}; migraphx::shape sh2{migraphx::shape::float_type, {4, 12}}; migraphx::shape sh3{migraphx::shape::float_type, {10, 12}}; migraphx::program p1; { auto* m1 = p1.get_main_module(); auto a = m1->add_parameter("a", sh1); auto b = m1->add_parameter("b", sh2); auto scale_a = m1->add_literal(0.4f); auto scale_b = m1->add_literal(0.5f); auto zero = m1->add_literal(std::int8_t{0}); auto q1 = add_quantize_op(*m1, "quantizelinear", a, scale_a, zero); auto d1 = add_quantize_op(*m1, "dequantizelinear", q1, scale_a, zero); auto q2 = add_quantize_op(*m1, "quantizelinear", b, scale_b, zero); auto d2 = add_quantize_op(*m1, "dequantizelinear", q2, scale_b, zero); auto dot = m1->add_instruction(migraphx::make_op("dot"), d1, d2); m1->add_return({dot}); run_pass(*m1); } migraphx::program p2; { auto* m2 = p2.get_main_module(); auto a = m2->add_parameter("a", sh1); auto b = m2->add_parameter("b", sh2); auto dot = m2->add_instruction(migraphx::make_op("dot"), a, b); m2->add_return({dot}); } std::vector av(sh1.elements(), 10); auto a = migraphx::argument(sh1, av.data()); std::vector bv(sh2.elements(), 10); auto b = migraphx::argument(sh2, bv.data()); p1.compile(migraphx::target(migraphx::make_target("ref"))); p2.compile(migraphx::target(migraphx::make_target("ref"))); auto result1 = p1.eval({{"a", a}, {"b", b}}).back(); std::vector rv1(sh3.elements()); result1.visit([&](auto output) { rv1.assign(output.begin(), output.end()); }); auto result2 = p2.eval({{"a", a}, {"b", b}}).back(); std::vector rv2(sh3.elements()); result2.visit([&](auto output) { rv2.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(rv1, rv2)); } TEST_CASE(dot_reused) { migraphx::shape sh{migraphx::shape::float_type, {256, 256}}; migraphx::module m1; { auto x = m1.add_parameter("x", sh); auto y = m1.add_parameter("y", sh); auto w1 = m1.add_parameter("w1", sh); auto w2 = m1.add_parameter("w2", sh); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m1, "quantizelinear", x, scale, zero); auto d1 = add_quantize_op(m1, "dequantizelinear", q1, scale, zero); auto q2 = add_quantize_op(m1, "quantizelinear", w1, scale, zero); auto d2 = add_quantize_op(m1, "dequantizelinear", q2, scale, zero); auto dot1 = m1.add_instruction(migraphx::make_op("dot"), d1, d2); auto add1 = m1.add_instruction(migraphx::make_op("add"), dot1, y); auto q3 = add_quantize_op(m1, "quantizelinear", add1, scale, zero); auto d3 = add_quantize_op(m1, "dequantizelinear", q3, scale, zero); auto q4 = add_quantize_op(m1, "quantizelinear", w2, scale, zero); auto d4 = add_quantize_op(m1, "dequantizelinear", q4, scale, zero); auto dot2 = m1.add_instruction(migraphx::make_op("dot"), d3, d4); auto add2 = m1.add_instruction(migraphx::make_op("add"), dot2, add1); m1.add_return({add2}); } migraphx::module m2; { auto x = m2.add_parameter("x", sh); auto y = m2.add_parameter("y", sh); auto w1 = m2.add_parameter("w1", sh); auto w2 = m2.add_parameter("w2", sh); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto q1 = add_quantize_op(m2, "quantizelinear", x, scale, zero); auto q2 = add_quantize_op(m2, "quantizelinear", w1, scale, zero); auto dot1 = m2.add_instruction(migraphx::make_op("quant_dot"), q1, q2); auto out_scale1 = add_scale_mul(m2, scale, scale, 1, 1, sh.lens()); auto d1 = add_quantize_op(m2, "dequantizelinear", dot1, out_scale1); auto add1 = m2.add_instruction(migraphx::make_op("add"), d1, y); auto q3 = add_quantize_op(m2, "quantizelinear", add1, scale, zero); auto q4 = add_quantize_op(m2, "quantizelinear", w2, scale, zero); auto dot2 = m2.add_instruction(migraphx::make_op("quant_dot"), q3, q4); auto out_scale2 = add_scale_mul(m2, scale, scale, 1, 1, sh.lens()); auto d2 = add_quantize_op(m2, "dequantizelinear", dot2, out_scale2); auto d3 = add_quantize_op(m2, "dequantizelinear", q3, q3->inputs()[1]); auto add2 = m2.add_instruction(migraphx::make_op("add"), d2, d3); m2.add_return({add2}); } run_pass(m1); run_cse(m1); run_cse(m2); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(dot_asymmetric_correctness) { migraphx::shape sh1{migraphx::shape::float_type, {10, 4}}; migraphx::shape sh2{migraphx::shape::float_type, {4, 12}}; migraphx::shape sh3{migraphx::shape::float_type, {10, 12}}; migraphx::program p1; { auto* m1 = p1.get_main_module(); auto a = m1->add_parameter("a", sh1); auto b = m1->add_parameter("b", sh2); auto scale_a = m1->add_literal(0.4f); auto scale_b = m1->add_literal(0.5f); auto zp_a = m1->add_literal(std::int8_t{50}); auto zp_b = m1->add_literal(std::int8_t{-20}); auto q1 = add_quantize_op(*m1, "quantizelinear", a, scale_a, zp_a); auto d1 = add_quantize_op(*m1, "dequantizelinear", q1, scale_a, zp_a); auto q2 = add_quantize_op(*m1, "quantizelinear", b, scale_b, zp_b); auto d2 = add_quantize_op(*m1, "dequantizelinear", q2, scale_b, zp_b); auto dot = m1->add_instruction(migraphx::make_op("dot"), d1, d2); m1->add_return({dot}); run_pass(*m1); } migraphx::program p2; { auto* m2 = p2.get_main_module(); auto a = m2->add_parameter("a", sh1); auto b = m2->add_parameter("b", sh2); auto dot = m2->add_instruction(migraphx::make_op("dot"), a, b); m2->add_return({dot}); } std::vector av(sh1.elements(), 10); auto a = migraphx::argument(sh1, av.data()); std::vector bv(sh2.elements(), 10); auto b = migraphx::argument(sh2, bv.data()); p1.compile(migraphx::target(migraphx::make_target("ref"))); p2.compile(migraphx::target(migraphx::make_target("ref"))); auto result1 = p1.eval({{"a", a}, {"b", b}}).back(); std::vector rv1(sh3.elements()); result1.visit([&](auto output) { rv1.assign(output.begin(), output.end()); }); auto result2 = p2.eval({{"a", a}, {"b", b}}).back(); std::vector rv2(sh3.elements()); result2.visit([&](auto output) { rv2.assign(output.begin(), output.end()); }); EXPECT(migraphx::verify::verify_rms_range(rv1, rv2)); } TEST_CASE(int4_simplify_qdq_pass_test) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto scale = m1.add_parameter("sc", {migraphx::shape::float_type, {1, 8, 16, 16}}); auto zp = m1.add_parameter("zp", {migraphx::shape::int8_type, {1, 8, 16, 8}}); auto un_pk_zp = m1.add_instruction(migraphx::make_op("unpack_int4"), zp); auto q = m1.add_instruction(migraphx::make_op("quantizelinear"), x, scale, un_pk_zp); auto dq = m1.add_instruction(migraphx::make_op("dequantizelinear"), q, scale, un_pk_zp); m1.add_return({dq}); } migraphx::run_passes(m1, {migraphx::simplify_qdq{}}); auto chk_1 = match::name("quantizelinear")( match::output(match::name("pack_int4")(match::output(match::name( "unpack_int4")(match::output(match::name("dequantizelinear"))))))) .bind("q"); auto res_1 = find_match(m1, chk_1); EXPECT(migraphx::contains(res_1.instructions, "q")); } TEST_CASE(pointwise_concat_quant_per_tensor) { migraphx::shape s1{migraphx::shape::float_type, {1, 4, 28, 28}}; migraphx::shape s2{migraphx::shape::float_type, {1, 2, 28, 28}}; migraphx::module m1; { auto i1 = m1.add_parameter("i1", s1); auto i2 = m1.add_parameter("i2", s2); auto scale = m1.add_literal(0.5f); auto zero = m1.add_literal(std::int8_t{0}); auto relu = m1.add_instruction(migraphx::make_op("relu"), i2); auto cat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), i1, relu); auto q = add_quantize_op(m1, "quantizelinear", cat, scale, zero); m1.add_return({q}); } migraphx::module m2; { std::vector cat_lens{1, 6, 28, 28}; auto i1 = m2.add_parameter("i1", s1); auto i2 = m2.add_parameter("i2", s2); auto scale = m2.add_literal(0.5f); auto zero = m2.add_literal(std::int8_t{0}); auto relu = m2.add_instruction(migraphx::make_op("relu"), i2); auto scale_mb = broadcast_scale(m2, scale, cat_lens, 1); auto zero_mb = broadcast_shift(m2, zero, cat_lens); auto sc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {4}}}), scale_mb); auto zp1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {4}}}), zero_mb); auto q1 = add_quantize_op(m2, "quantizelinear", i1, sc1, zp1); auto sc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {4}}, {"ends", {6}}}), scale_mb); auto zp2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {4}}, {"ends", {6}}}), zero_mb); auto q2 = add_quantize_op(m2, "quantizelinear", relu, sc2, zp2); auto cat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), q1, q2); m2.add_return({cat}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(pointwise_concat_quant_per_channel) { migraphx::shape s1{migraphx::shape::float_type, {1, 4, 28, 28}}; migraphx::shape s2{migraphx::shape::float_type, {1, 2, 28, 28}}; migraphx::shape s3{migraphx::shape::float_type, {6}}; migraphx::module m1; { auto i1 = m1.add_parameter("i1", s1); auto i2 = m1.add_parameter("i2", s2); auto scale = m1.add_literal(migraphx::generate_literal(s3, 0)); auto zero = m1.add_literal(std::int8_t{0}); auto relu = m1.add_instruction(migraphx::make_op("relu"), i2); auto cat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), i1, relu); auto q = add_quantize_op(m1, "quantizelinear", cat, scale, zero); m1.add_return({q}); } migraphx::module m2; { std::vector cat_lens{1, 6, 28, 28}; auto i1 = m2.add_parameter("i1", s1); auto i2 = m2.add_parameter("i2", s2); auto scale = m2.add_literal(migraphx::generate_literal(s3, 0)); auto zero = m2.add_literal(std::int8_t{0}); auto relu = m2.add_instruction(migraphx::make_op("relu"), i2); auto scale_mb = broadcast_scale(m2, scale, cat_lens, 1); auto zero_mb = broadcast_shift(m2, zero, cat_lens); auto sc1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {4}}}), scale_mb); auto zp1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {4}}}), zero_mb); auto q1 = add_quantize_op(m2, "quantizelinear", i1, sc1, zp1); auto sc2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {4}}, {"ends", {6}}}), scale_mb); auto zp2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {4}}, {"ends", {6}}}), zero_mb); auto q2 = add_quantize_op(m2, "quantizelinear", relu, sc2, zp2); auto cat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), q1, q2); m2.add_return({cat}); } run_pass(m1); EXPECT(m1 == m2); } // TODO Disabled till rocMLIR support added // TEST_CASE(fp4x2_quant_conv_even) //{ // migraphx::shape shape_packed_input{migraphx::shape::fp4x2_type, {1, 3, 24, 12}}; // migraphx::shape shape_packed_weights{migraphx::shape::fp4x2_type, {1, 3, 4, 2}}; // migraphx::shape shape_scale_input{migraphx::shape::float_type, {1, 3, 24, 24}}; // migraphx::shape shape_scale_weights{migraphx::shape::float_type, {1, 3, 4, 4}}; // // migraphx::module m1; // { // auto packed_input = m1.add_parameter("input", shape_packed_input); // auto packed_weights = m1.add_parameter("weights", shape_packed_weights); // auto scale_input = m1.add_parameter("scale_input", shape_scale_input); // auto scale_weights = m1.add_parameter("scale_weights", shape_scale_weights); // // auto unpack_input = // m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_input); // auto unpack_weights = // m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_weights); // auto dq_input = // m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack_input, scale_input); // auto dq_weights = m1.add_instruction( // migraphx::make_op("dequantizelinear"), unpack_weights, scale_weights); // auto conv = m1.add_instruction(migraphx::make_op("convolution", // {{"padding", {0, 0, 0, 0}}, // {"stride", {1, 1}}, // {"dilation", {1, 1}}, // {"group", 1}, // {"padding_mode", 0}}), // dq_input, // dq_weights); // m1.add_return({conv}); // } // // migraphx::module m2; // { // auto packed_input = m2.add_parameter("input", shape_packed_input); // auto packed_weights = m2.add_parameter("weights", shape_packed_weights); // auto scale_input = m2.add_parameter("scale_input", shape_scale_input); // auto scale_weights = m2.add_parameter("scale_weights", shape_scale_weights); // // auto unpack_input = // m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_input); // auto unpack_weights = // m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_weights); // auto quant_conv = m2.add_instruction(migraphx::make_op("quant_convolution", // {{"padding", {0, 0, 0, 0}}, // {"stride", {1, 1}}, // {"dilation", {1, 1}}, // {"group", 1}, // {"padding_mode", 0}}), // unpack_input, // unpack_weights, // scale_input, // scale_weights); // m2.add_return({quant_conv}); // } // // run_pass(m1); // EXPECT(m1 == m2); //} // //// odd number of elements in input (shape = [1, 3, 21, 21]) that was padded by 1 // TEST_CASE(fp4x2_quant_conv_odd) //{ // migraphx::shape shape_packed_input{migraphx::shape::fp4x2_type, {1, 3, 21, 11}}; // migraphx::shape shape_packed_weights{migraphx::shape::fp4x2_type, {1, 3, 4, 2}}; // migraphx::shape shape_scale_input{migraphx::shape::float_type, {1, 3, 21, 21}}; // migraphx::shape shape_scale_weights{migraphx::shape::float_type, {1, 3, 4, 4}}; // // migraphx::module m1; // { // auto packed_input = m1.add_parameter("input", shape_packed_input); // auto packed_weights = m1.add_parameter("weights", shape_packed_weights); // auto scale_input = m1.add_parameter("scale_input", shape_scale_input); // auto scale_weights = m1.add_parameter("scale_weights", shape_scale_weights); // // auto unpack_input = // m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_input); // auto unpack_weights = // m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_weights); // auto slice_input = m1.add_instruction( // migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {21}}}), // unpack_input); // auto dq_input = // m1.add_instruction(migraphx::make_op("dequantizelinear"), slice_input, scale_input); // auto dq_weights = m1.add_instruction( // migraphx::make_op("dequantizelinear"), unpack_weights, scale_weights); // auto conv = m1.add_instruction(migraphx::make_op("convolution", // {{"padding", {0, 0, 0, 0}}, // {"stride", {1, 1}}, // {"dilation", {1, 1}}, // {"group", 1}, // {"padding_mode", 0}}), // dq_input, // dq_weights); // m1.add_return({conv}); // } // // migraphx::module m2; // { // auto packed_input = m2.add_parameter("input", shape_packed_input); // auto packed_weights = m2.add_parameter("weights", shape_packed_weights); // auto scale_input = m2.add_parameter("scale_input", shape_scale_input); // auto scale_weights = m2.add_parameter("scale_weights", shape_scale_weights); // // auto unpack_input = // m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_input); // auto unpack_weights = // m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_weights); // auto slice_input = m2.add_instruction( // migraphx::make_op("slice", {{"axes", {3}}, {"starts", {0}}, {"ends", {21}}}), // unpack_input); // auto quant_conv = m2.add_instruction(migraphx::make_op("quant_convolution", // {{"padding", {0, 0, 0, 0}}, // {"stride", {1, 1}}, // {"dilation", {1, 1}}, // {"group", 1}, // {"padding_mode", 0}}), // slice_input, // unpack_weights, // scale_input, // scale_weights); // m2.add_return({quant_conv}); // } // // run_pass(m1); // EXPECT(m1 == m2); // } // TODO add with NHWC format TEST_CASE(fp4x2_quant_dot_even) { migraphx::shape shape_packed_a{migraphx::shape::fp4x2_type, {1, 3, 6, 12}}; migraphx::shape shape_packed_b{migraphx::shape::fp4x2_type, {1, 3, 24, 4}}; migraphx::shape shape_scales_a{migraphx::shape::float_type, {1, 3, 6, 24}}; migraphx::shape shape_scales_b{migraphx::shape::float_type, {1, 3, 24, 8}}; migraphx::module m1; { auto packed_a = m1.add_parameter("input", shape_packed_a); auto packed_b = m1.add_parameter("weights", shape_packed_b); auto scale_a = m1.add_parameter("scale_a", shape_scales_a); auto scale_b = m1.add_parameter("scale_b", shape_scales_b); auto unpack_a = m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_a); auto unpack_b = m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_b); auto dq_a = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack_a, scale_a); auto dq_b = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack_b, scale_b); auto dot = m1.add_instruction(migraphx::make_op("dot"), dq_a, dq_b); m1.add_return({dot}); } migraphx::module m2; { auto packed_a = m2.add_parameter("input", shape_packed_a); auto packed_b = m2.add_parameter("weights", shape_packed_b); auto scale_a = m2.add_parameter("scale_a", shape_scales_a); auto scale_b = m2.add_parameter("scale_b", shape_scales_b); auto unpack_a = m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_a); auto unpack_b = m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_b); auto quant_dot = m2.add_instruction( migraphx::make_op("quant_dot"), unpack_a, unpack_b, scale_a, scale_b); m2.add_return({quant_dot}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(fp4x2_quant_dot_trans_b) { migraphx::shape shape_packed_a{migraphx::shape::fp4x2_type, {1, 3, 6, 12}}; migraphx::shape shape_packed_b{migraphx::shape::fp4x2_type, {1, 3, 8, 12}}; migraphx::shape shape_scales_a{migraphx::shape::float_type, {1, 3, 6, 24}}; migraphx::shape shape_scales_b{migraphx::shape::float_type, {1, 3, 8, 24}}; migraphx::module m1; { auto packed_a = m1.add_parameter("input", shape_packed_a); auto packed_b = m1.add_parameter("weights", shape_packed_b); auto scale_a = m1.add_parameter("scale_a", shape_scales_a); auto scale_b = m1.add_parameter("scale_b", shape_scales_b); auto unpack_a = m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_a); auto unpack_b = m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_b); auto dq_a = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack_a, scale_a); auto dq_b = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack_b, scale_b); auto trans_b = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), dq_b); auto dot = m1.add_instruction(migraphx::make_op("dot"), dq_a, trans_b); m1.add_return({dot}); } migraphx::module m2; { auto packed_a = m2.add_parameter("input", shape_packed_a); auto packed_b = m2.add_parameter("weights", shape_packed_b); auto scale_a = m2.add_parameter("scale_a", shape_scales_a); auto scale_b = m2.add_parameter("scale_b", shape_scales_b); auto unpack_a = m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_a); auto unpack_b = m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_b); auto trans_b = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), unpack_b); auto trans_scale_b = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), scale_b); auto quant_dot = m2.add_instruction( migraphx::make_op("quant_dot"), unpack_a, trans_b, scale_a, trans_scale_b); m2.add_return({quant_dot}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(fp4x2_quant_dot_const_b) { migraphx::shape shape_packed_a{migraphx::shape::fp4x2_type, {1, 3, 6, 12}}; migraphx::shape shape_packed_b{migraphx::shape::fp4x2_type, {1, 3, 24, 4}}; migraphx::shape shape_packed_b_gen{migraphx::shape::uint8_type, {1, 3, 24, 4}}; migraphx::shape shape_scales_a{migraphx::shape::float_type, {1, 3, 6, 24}}; migraphx::shape shape_scales_b{migraphx::shape::float_type, {1, 3, 24, 8}}; unsigned long seed = 826; migraphx::literal b_lit = generate_literal(shape_packed_b_gen, seed); migraphx::literal scale_b_lit = generate_literal(shape_scales_b, seed); migraphx::module m1; { auto packed_a = m1.add_parameter("input", shape_packed_a); // avoiding visit fp4x2_type auto packed_b = m1.add_literal(shape_packed_b, b_lit.data()); auto scale_a = m1.add_parameter("scale_a", shape_scales_a); auto scale_b = m1.add_literal(scale_b_lit); auto unpack_a = m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_a); auto unpack_b = m1.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_b); auto dq_a = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack_a, scale_a); auto dq_b = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack_b, scale_b); auto dot = m1.add_instruction(migraphx::make_op("dot"), dq_a, dq_b); m1.add_return({dot}); } migraphx::module m2; { auto packed_a = m2.add_parameter("input", shape_packed_a); auto packed_b = m2.add_literal(shape_packed_b, b_lit.data()); auto scale_a = m2.add_parameter("scale_a", shape_scales_a); auto scale_b = m2.add_literal(scale_b_lit); auto unpack_a = m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_a); auto unpack_b = m2.add_instruction(migraphx::make_op("unpack_fp4", {{"axis", 3}}), packed_b); auto quant_dot = m2.add_instruction( migraphx::make_op("quant_dot"), unpack_a, unpack_b, scale_a, scale_b); m2.add_return({quant_dot}); } run_pass(m1); EXPECT(m1 == m2); } // Test that unused qdq with pack_fp4, unpack_fp4 are removed TEST_CASE(fp4x2_even_remove_qdq) { migraphx::shape shape_input{migraphx::shape::float_type, {8, 8}}; migraphx::module m1; { auto a = m1.add_parameter("a", shape_input); auto b = m1.add_parameter("b", shape_input); // simulate scales calc with just abs() auto scale_a = m1.add_instruction(migraphx::make_op("abs"), a); auto quant = m1.add_instruction( migraphx::make_op("quantizelinear", {{"out_type", migraphx::shape::float_type}}), a, scale_a); auto pack_fp4 = m1.add_instruction(migraphx::make_op("pack_fp4"), quant); auto unpack_fp4 = m1.add_instruction(migraphx::make_op("unpack_fp4"), pack_fp4); auto dequant = m1.add_instruction(migraphx::make_op("dequantizelinear"), unpack_fp4, scale_a); auto add = m1.add_instruction(migraphx::make_op("add"), dequant, b); m1.add_return({add}); } migraphx::module m2; { auto a = m2.add_parameter("a", shape_input); auto b = m2.add_parameter("b", shape_input); auto add = m2.add_instruction(migraphx::make_op("add"), a, b); m2.add_return({add}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(fp4x2_odd_remove_qdq) { migraphx::shape shape_input{migraphx::shape::float_type, {7, 7}}; migraphx::module m1; { auto a = m1.add_parameter("a", shape_input); auto b = m1.add_parameter("b", shape_input); // simulate scales calc with just abs() auto scale_a = m1.add_instruction(migraphx::make_op("abs"), a); auto quant = m1.add_instruction( migraphx::make_op("quantizelinear", {{"out_type", migraphx::shape::float_type}}), a, scale_a); auto pad = m1.add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 1}}}), quant); auto pack_fp4 = m1.add_instruction(migraphx::make_op("pack_fp4"), pad); auto unpack_fp4 = m1.add_instruction(migraphx::make_op("unpack_fp4"), pack_fp4); auto slice = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {7}}}), unpack_fp4); auto dequant = m1.add_instruction(migraphx::make_op("dequantizelinear"), slice, scale_a); auto add = m1.add_instruction(migraphx::make_op("add"), dequant, b); m1.add_return({add}); } migraphx::module m2; { auto a = m2.add_parameter("a", shape_input); auto b = m2.add_parameter("b", shape_input); auto add = m2.add_instruction(migraphx::make_op("add"), a, b); m2.add_return({add}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(qdq_computed_scale) { migraphx::shape sh{migraphx::shape::float_type, {2, 2}}; migraphx::module m1; { auto a = m1.add_parameter("a", sh); auto b = m1.add_parameter("b", sh); auto scale = m1.add_instruction( migraphx::make_op("add"), m1.add_literal(0.5f), m1.add_literal(0.0f)); auto zero = m1.add_literal(std::int8_t{0}); auto qa = add_quantize_op(m1, "quantizelinear", a, scale, zero); auto da = add_quantize_op(m1, "dequantizelinear", qa, scale, zero); auto qb = add_quantize_op(m1, "quantizelinear", b, scale, zero); auto db = add_quantize_op(m1, "dequantizelinear", qb, scale, zero); auto add = m1.add_instruction(migraphx::make_op("add"), da, db); m1.add_return({add}); } migraphx::module m2; { auto a = m2.add_parameter("a", sh); auto b = m2.add_parameter("b", sh); auto add = m2.add_instruction(migraphx::make_op("add"), a, b); m2.add_return({add}); } run_pass(m1); EXPECT(m1 == m2); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/simplify_reshapes_test.cpp000066400000000000000000004476771510465702400230410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::module& m) { migraphx::run_passes(m, {migraphx::simplify_reshapes{.enable_op_shape_transform_op = true}, migraphx::dead_code_elimination{}}); } inline static std::vector> to_lens(const std::vector& shapes) { std::vector> result; std::transform(shapes.begin(), shapes.end(), std::back_inserter(result), [&](const auto& s) { return s.lens(); }); return result; } static migraphx::module make_concat_multibroadcast(const std::vector& in_lens, const std::vector& mbcast_lens, const int axis) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, in_lens}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto z = m.add_parameter("z", s); auto xm = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mbcast_lens}}), x); auto ym = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mbcast_lens}}), y); auto zm = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mbcast_lens}}), z); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", axis}}), xm, ym, zm); m.add_return({concat}); return m; } TEST_CASE(broadcast_transpose) { migraphx::module m1; { auto l = m1.add_parameter("x", {migraphx::shape::float_type, {5}}); auto mb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 5}}}), l); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 0, 1}}}), mb); m1.add_return({t1}); } run_pass(m1); migraphx::module m2; { auto l = m2.add_parameter("x", {migraphx::shape::float_type, {5}}); auto b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {5, 2, 3}}}), l); m2.add_return({b}); } EXPECT(m1 == m2); } TEST_CASE(broadcast_transpose_opt) { // extra transpose from transformation will be optimized out migraphx::module m1; { auto l = m1.add_parameter("x", {migraphx::shape::float_type, {5}}); auto mb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 5}}}), l); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), mb); m1.add_return({t1}); } run_pass(m1); migraphx::module m2; { auto l = m2.add_parameter("x", {migraphx::shape::float_type, {5}}); auto b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", {3, 2, 5}}}), l); m2.add_return({b}); } EXPECT(m1 == m2); } TEST_CASE(broadcast_transpose_scalar) { migraphx::module m1; { auto l = m1.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto mb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3}}}), l); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), mb); m1.add_return({t1}); } run_pass(m1); migraphx::module m2; { auto l = m2.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto mb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2}}}), l); m2.add_return({mb}); } EXPECT(m1 == m2); } TEST_CASE(broadcast_transpose_scalar_multi_use) { // multibroadcast used more than once migraphx::module m1; { auto l = m1.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto mb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3}}}), l); auto t1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), mb); auto id = m1.add_instruction(migraphx::make_op("identity"), mb); m1.add_return({t1, id}); } run_pass(m1); migraphx::module m2; { auto l = m2.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto mb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2}}}), l); auto mb2 = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3}}}), l); auto id = m2.add_instruction(migraphx::make_op("identity"), mb2); m2.add_return({mb, id}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(double_contig) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(get_2x2()); auto t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c1 = mm->add_instruction(migraphx::make_op("contiguous"), t1); auto c2 = mm->add_instruction(migraphx::make_op("contiguous"), c1); mm->add_return({c2}); EXPECT(mm->get_output_shapes().back().standard()); EXPECT(not mm->get_output_shapes().back().transposed()); run_pass(*mm); EXPECT(mm->get_output_shapes().back().standard()); EXPECT(not mm->get_output_shapes().back().transposed()); EXPECT(std::distance(mm->begin(), mm->end()) == 4); auto result = p.eval({}).back(); EXPECT(result != get_2x2()); } TEST_CASE(double_transpose) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(get_2x2()); auto t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto t2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t1); mm->add_return({t2}); EXPECT(mm->get_output_shapes().back().standard()); EXPECT(not mm->get_output_shapes().back().transposed()); run_pass(*mm); EXPECT(mm->get_output_shapes().back().standard()); EXPECT(not mm->get_output_shapes().back().transposed()); EXPECT(std::distance(mm->begin(), mm->end()) == 2); auto result = p.eval({}).back(); EXPECT(result == get_2x2()); } TEST_CASE(double_transpose_contig) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(get_2x2()); auto t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); auto c1 = mm->add_instruction(migraphx::make_op("contiguous"), t1); auto t2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), c1); auto c2 = mm->add_instruction(migraphx::make_op("contiguous"), t2); mm->add_return({c2}); EXPECT(mm->get_output_shapes().back().standard()); EXPECT(not mm->get_output_shapes().back().transposed()); run_pass(*mm); EXPECT(mm->get_output_shapes().back().standard()); EXPECT(not mm->get_output_shapes().back().transposed()); EXPECT(std::distance(mm->begin(), mm->end()) == 2); auto result = p.eval({}).back(); EXPECT(result == get_2x2()); } TEST_CASE(single_transpose) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(get_2x2()); auto t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); mm->add_return({t1}); EXPECT(not mm->get_output_shapes().back().standard()); EXPECT(mm->get_output_shapes().back().transposed()); run_pass(*mm); EXPECT(not mm->get_output_shapes().back().standard()); EXPECT(mm->get_output_shapes().back().transposed()); EXPECT(std::distance(mm->begin(), mm->end()) == 3); auto result = p.eval({}).back(); EXPECT(result != get_2x2()); } TEST_CASE(double_transpose_sin_pass) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(get_2x2()); auto t1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), t1); EXPECT(mm->get_output_shapes().back().standard()); EXPECT(not mm->get_output_shapes().back().transposed()); run_pass(*mm); EXPECT(mm->get_output_shapes().back().standard()); EXPECT(not mm->get_output_shapes().back().transposed()); // TODO: Fix this // EXPECT(std::distance(mm->begin(), mm->end()) == 1); auto result = p.eval({}).back(); EXPECT(result == get_2x2()); } TEST_CASE(single_transpose_sin_pass) { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_literal(get_2x2()); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l); EXPECT(not mm->get_output_shapes().back().standard()); EXPECT(mm->get_output_shapes().back().transposed()); run_pass(*mm); EXPECT(not mm->get_output_shapes().back().standard()); EXPECT(mm->get_output_shapes().back().transposed()); EXPECT(std::distance(mm->begin(), mm->end()) == 2); auto result = p.eval({}).back(); EXPECT(result != get_2x2()); } TEST_CASE(reshape_transpose) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 112, 56, 56}}; auto x = m.add_parameter("x", s); auto r1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 4, 28, 56, 56}}}), x); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3, 4}}}), r1); auto r2 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 112, 56, 56}}}), t); m.add_return({r2}); EXPECT(m.get_output_shapes().back() == s); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == s); EXPECT(std::distance(m.begin(), m.end()) == n); } TEST_CASE(transpose_contiguous) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {4, 4}}; auto x = m.add_parameter("x", s); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), x); auto c1 = m.add_instruction(migraphx::make_op("contiguous"), t); m.add_return({c1}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n); } TEST_CASE(transpose_double_contiguous) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {4, 4}}; auto x = m.add_parameter("x", s); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), x); auto c1 = m.add_instruction(migraphx::make_op("contiguous"), t); auto c2 = m.add_instruction(migraphx::make_op("contiguous"), c1); m.add_return({c2}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n - 1); EXPECT(m.has_instruction(t)); } TEST_CASE(transpose_partial1) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m.add_parameter("x", s); auto t1 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), x); auto t2 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 2, 0}}}), t1); m.add_return({t2}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n - 1); } TEST_CASE(transpose_partial2) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m.add_parameter("x", s); auto t1 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), x); auto t2 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 2, 0}}}), t1); auto t3 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), t2); m.add_return({t3}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n - 2); } TEST_CASE(transpose_partial3) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m.add_parameter("x", s); auto t1 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), x); auto t2 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 2, 0}}}), t1); auto t3 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), t2); auto t4 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), t3); m.add_return({t4}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n - 3); } TEST_CASE(nop_transpose1) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m.add_parameter("x", s); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 2}}}), x); m.add_return({t}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n - 1); } TEST_CASE(nop_transpose2) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m.add_parameter("x", s); auto t1 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 2}}}), x); auto t2 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 2}}}), t1); auto t3 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 2}}}), t2); auto t4 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 2}}}), t3); m.add_instruction(pass_op{}, t4); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n - 4); } TEST_CASE(nop_transpose3) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 3}}), x, y); auto t1 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 2, 3}}}), concat); auto t2 = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), t1); m.add_return({t2}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n - 1); } TEST_CASE(nop_convert) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3}}; auto x = m.add_parameter("x", s); auto t = m.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::float_type)}}), x); m.add_return({t}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back() == out_shape); EXPECT(std::distance(m.begin(), m.end()) == n - 1); } TEST_CASE(nested_reshape) { auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4, 5, 6, 7}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto rshp1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 3, 4, 5, 42}}}), x); auto rshp2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 12, 5, 42}}}), rshp1); auto rshp3 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 12, 5, 42}}}), rshp2); auto rshp4 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 60, 42}}}), rshp3); auto rshp5 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {120, 42}}}), rshp4); auto rshp6 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {5040}}}), rshp5); m1.add_return({rshp6}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto rshp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {5040}}}), x); m2.add_return({rshp}); } EXPECT(m1 == m2); } TEST_CASE(nested_reshape_contiguous) { auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4, 5, 6, 7}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto rshp1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 3, 4, 5, 42}}}), x); auto c1 = m1.add_instruction(migraphx::make_op("contiguous"), rshp1); auto rshp2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 12, 5, 42}}}), c1); auto c2 = m1.add_instruction(migraphx::make_op("contiguous"), rshp2); auto rshp3 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 12, 5, 42}}}), c2); auto c3 = m1.add_instruction(migraphx::make_op("contiguous"), rshp3); auto rshp4 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 60, 42}}}), c3); auto c4 = m1.add_instruction(migraphx::make_op("contiguous"), rshp4); auto rshp5 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {120, 42}}}), c4); auto c5 = m1.add_instruction(migraphx::make_op("contiguous"), rshp5); auto rshp6 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {5040}}}), c5); m1.add_return({rshp6}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto rshp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {5040}}}), x); m2.add_return({rshp}); } EXPECT(m1 == m2); } TEST_CASE(nested_reshape_squeeze) { auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto rshp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 12}}}), x); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), rshp); m1.add_return({squeeze}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto rshp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 12}}}), x); m2.add_return({rshp}); } EXPECT(m1 == m2); } TEST_CASE(nested_squeeze_reshape) { auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), x); auto rshp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 12}}}), squeeze); m1.add_return({rshp}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto rshp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 12}}}), x); m2.add_return({rshp}); } EXPECT(m1 == m2); } TEST_CASE(squeeze_unsqueeze_scalar) { auto s = migraphx::shape{migraphx::shape::float_type, {1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), x); auto unsqueeze = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), squeeze); m1.add_return({unsqueeze}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); m2.add_return({x}); } EXPECT(m1 == m2); } TEST_CASE(unsqueeze_broadcast_single) { auto s = migraphx::shape{migraphx::shape::float_type, {1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto unsqueeze = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), x); auto broadcast = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 8}}}), unsqueeze); m1.add_return({broadcast}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto broadcast = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", {1, 8}}}), x); m2.add_return({broadcast}); } EXPECT(m1 == m2); } TEST_CASE(concat_slice_different_axis_1) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 160}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), concat); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), concat); auto add = m1.add_instruction(migraphx::make_op("add"), slice1, slice2); m1.add_return({add}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(concat_slice_different_axis_2) { // two slices, one with same axis but other with different auto s = migraphx::shape{migraphx::shape::float_type, {2, 160}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), concat); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {160}}, {"ends", {320}}}), concat); auto add = m1.add_instruction(migraphx::make_op("add"), x, slice2); m1.add_return({slice1, add}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto slice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), concat); auto add = m2.add_instruction(migraphx::make_op("add"), x, y); m2.add_return({slice1, add}); } EXPECT(m1 == m2); } TEST_CASE(concat_slice_in_same_order) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 160}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {160}}}), concat); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {160}}, {"ends", {320}}}), concat); auto add = m1.add_instruction(migraphx::make_op("add"), slice1, slice2); m1.add_return({add}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto add = m2.add_instruction(migraphx::make_op("add"), x, y); m2.add_return({add}); } EXPECT(m1 == m2); } TEST_CASE(concat_slice_in_reverse_order) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 160}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {160}}, {"ends", {320}}}), concat); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {160}}}), concat); auto add = m1.add_instruction(migraphx::make_op("add"), slice1, slice2); m1.add_return({add}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto add = m2.add_instruction(migraphx::make_op("add"), y, x); m2.add_return({add}); } EXPECT(m1 == m2); } TEST_CASE(concat_slice_inorder_with_empty_slice) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 160}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {160}}}), concat); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {160}}, {"ends", {320}}}), concat); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {320}}, {"ends", {360}}}), concat); auto add = m1.add_instruction(migraphx::make_op("add"), slice1, slice2); m1.add_return({add, slice3}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto slice3 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {320}}, {"ends", {360}}}), concat); auto add = m2.add_instruction(migraphx::make_op("add"), x, y); m2.add_return({add, slice3}); } EXPECT(m1 == m2); } TEST_CASE(concat_slice_uneven_len_1) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 160}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto z = m1.add_parameter("z", s); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y, z); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {100}}}), concat); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {100}}, {"ends", {160}}}), concat); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {160}}, {"ends", {320}}}), concat); auto slice4 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {320}}, {"ends", {420}}}), concat); auto slice5 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {420}}, {"ends", {480}}}), concat); auto add1 = m1.add_instruction(migraphx::make_op("add"), slice1, slice4); auto add2 = m1.add_instruction(migraphx::make_op("add"), slice2, slice5); auto add3 = m1.add_instruction(migraphx::make_op("add"), slice3, z); m1.add_return({add1, add2, add3}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto z = m2.add_parameter("z", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y, z); auto slice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {100}}}), concat); auto slice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {100}}, {"ends", {160}}}), concat); auto slice4 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {320}}, {"ends", {420}}}), concat); auto slice5 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {420}}, {"ends", {480}}}), concat); auto add1 = m2.add_instruction(migraphx::make_op("add"), slice1, slice4); auto add2 = m2.add_instruction(migraphx::make_op("add"), slice2, slice5); auto add3 = m2.add_instruction(migraphx::make_op("add"), y, z); m2.add_return({add1, add2, add3}); } EXPECT(m1 == m2); } TEST_CASE(concat_slice_uneven_len_2) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 160}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {150}}}), concat); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {150}}, {"ends", {300}}}), concat); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {300}}, {"ends", {320}}}), concat); auto add = m1.add_instruction(migraphx::make_op("add"), slice1, slice2); m1.add_return({add, slice3}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(concat_slice_multiple_slice_use) { // multiple use for slice1 and slice3, single use for slice2 auto s = migraphx::shape{migraphx::shape::float_type, {2, 160}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto z = m1.add_parameter("z", s); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y, z); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {160}}}), concat); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {160}}, {"ends", {320}}}), concat); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {320}}, {"ends", {480}}}), concat); auto add1 = m1.add_instruction(migraphx::make_op("add"), slice1, z); auto add2 = m1.add_instruction(migraphx::make_op("add"), slice3, x); auto sub1 = m1.add_instruction(migraphx::make_op("sub"), slice1, z); auto sub2 = m1.add_instruction(migraphx::make_op("sub"), slice3, x); auto add3 = m1.add_instruction(migraphx::make_op("add"), sub1, sub2); auto sub3 = m1.add_instruction(migraphx::make_op("sub"), add3, slice2); m1.add_return({add1, add2, sub3}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto z = m2.add_parameter("z", s); auto add1 = m2.add_instruction(migraphx::make_op("add"), x, z); auto add2 = m2.add_instruction(migraphx::make_op("add"), z, x); auto sub1 = m2.add_instruction(migraphx::make_op("sub"), x, z); auto sub2 = m2.add_instruction(migraphx::make_op("sub"), z, x); auto add3 = m2.add_instruction(migraphx::make_op("add"), sub1, sub2); auto sub3 = m2.add_instruction(migraphx::make_op("sub"), add3, y); m2.add_return({add1, add2, sub3}); } EXPECT(m1 == m2); } TEST_CASE(concat_slice_with_multiple_concat_outs) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 160}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 480}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s1); auto w = m1.add_parameter("w", s2); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y, z); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {160}}, {"ends", {320}}}), concat); auto add1 = m1.add_instruction(migraphx::make_op("add"), concat, w); auto add2 = m1.add_instruction(migraphx::make_op("add"), slice1, z); m1.add_return({add1, add2}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s1); auto z = m2.add_parameter("z", s1); auto w = m2.add_parameter("w", s2); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y, z); auto add1 = m2.add_instruction(migraphx::make_op("add"), concat, w); auto add2 = m2.add_instruction(migraphx::make_op("add"), y, z); m2.add_return({add1, add2}); } EXPECT(m1 == m2); } TEST_CASE(concat_multibroadcasts1) { // Broadcasted batch dim, new axis < old axis std::vector in_lens = {3, 4}; std::vector mbcast_lens = {2, 3, 4}; const int axis = 2; auto m = make_concat_multibroadcast(in_lens, mbcast_lens, axis); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back().lens() == out_shape.lens()); EXPECT(std::distance(m.begin(), m.end()) == n - 2); auto new_concat = std::find_if(m.begin(), m.end(), [](const auto& ins) { return ins.name() == "concat"; }); EXPECT(new_concat != m.end()); auto cd = std::distance(m.begin(), new_concat); auto new_mb = std::find_if( m.begin(), m.end(), [](const auto& ins) { return ins.name() == "multibroadcast"; }); auto md = std::distance(m.begin(), new_mb); EXPECT(cd == md - 1); EXPECT(new_concat->get_operator().to_value()["axis"].to() == 1); } TEST_CASE(concat_multibroadcasts2) { // Broadcasted middle dim, new axis == old axis std::vector in_lens = {3, 1, 4}; std::vector mbcast_lens = {3, 2, 4}; const int axis = 0; auto m = make_concat_multibroadcast(in_lens, mbcast_lens, axis); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back().lens() == out_shape.lens()); EXPECT(std::distance(m.begin(), m.end()) == n - 2); auto new_concat = std::find_if(m.begin(), m.end(), [](const auto& ins) { return ins.name() == "concat"; }); EXPECT(new_concat != m.end()); auto cd = std::distance(m.begin(), new_concat); auto new_mb = std::find_if( m.begin(), m.end(), [](const auto& ins) { return ins.name() == "multibroadcast"; }); auto md = std::distance(m.begin(), new_mb); EXPECT(cd == md - 1); EXPECT(new_concat->get_operator().to_value()["axis"].to() == 0); } TEST_CASE(concat_multibroadcasts3) { // Broadcasted middle dim, new axis == old axis std::vector in_lens = {3, 1, 4}; std::vector mbcast_lens = {3, 2, 4}; const int axis = 2; auto m = make_concat_multibroadcast(in_lens, mbcast_lens, axis); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back().lens() == out_shape.lens()); EXPECT(std::distance(m.begin(), m.end()) == n - 2); auto new_concat = std::find_if(m.begin(), m.end(), [](const auto& ins) { return ins.name() == "concat"; }); EXPECT(new_concat != m.end()); auto cd = std::distance(m.begin(), new_concat); auto new_mb = std::find_if( m.begin(), m.end(), [](const auto& ins) { return ins.name() == "multibroadcast"; }); auto md = std::distance(m.begin(), new_mb); EXPECT(cd == md - 1); EXPECT(new_concat->get_operator().to_value()["axis"].to() == 2); } // Broadcasted batch dim, axis is broadcasted dim // matched by find_concat_multibroadcasts but it skips this case TEST_CASE(concat_multibroadcasts4) { std::vector in_lens = {3, 4}; std::vector mbcast_lens = {2, 3, 4}; const int axis = 0; auto m = make_concat_multibroadcast(in_lens, mbcast_lens, axis); auto m1 = m; run_pass(m); EXPECT(m1 == m); } // Matched by find_concat_multibroadcasts but skipped because dimensions other than concat axis do // not match TEST_CASE(concat_multibroadcasts5) { migraphx::module m; auto s0 = migraphx::shape{migraphx::shape::float_type, {1, 1, 1, 1, 64}}; auto s1 = migraphx::shape{migraphx::shape::float_type, {1, 1, 60, 64, 192}}; auto x = m.add_parameter("x", s0); auto y = m.add_parameter("y", s1); std::vector mb_lens0 = {1, 12, 60, 64, 64}; std::vector mb_lens1 = {1, 12, 60, 64, 192}; auto mb_x = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens0}}), x); auto mb_y = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens1}}), y); auto concat_xy = m.add_instruction(migraphx::make_op("concat", {{"axis", 4}}), mb_x, mb_y); m.add_return({concat_xy}); auto m_original = m; run_pass(m); EXPECT(m == m_original); } // Matched by find_concat_multibroadcasts but skipped because parameter inputs are not the same // rank. TEST_CASE(concat_multibroadcasts6) { migraphx::module m; auto s0 = migraphx::shape{migraphx::shape::float_type, {64}}; auto s1 = migraphx::shape{migraphx::shape::float_type, {60, 64, 192}}; auto x = m.add_parameter("x", s0); auto y = m.add_parameter("y", s1); std::vector mb_lens0 = {12, 60, 64, 64}; std::vector mb_lens1 = {12, 60, 64, 192}; auto mb_x = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens0}}), x); auto mb_y = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens1}}), y); auto concat_xy = m.add_instruction(migraphx::make_op("concat", {{"axis", 3}}), mb_x, mb_y); m.add_return({concat_xy}); auto m_original = m; run_pass(m); EXPECT(m == m_original); } // Concat axis moved to 2 because rank(in_dims) < rank(out_dims) // Matched by find_concat_multibroadcasts but skipped because the dimensions // other than the concat axis are not the same. // TODO: has common broadcast axes, so can be simplified by moving multibroadcast up to have a // smaller concat. TEST_CASE(concat_multibroadcasts7) { migraphx::module m; auto s0 = migraphx::shape{migraphx::shape::float_type, {1, 1, 64}}; auto s1 = migraphx::shape{migraphx::shape::float_type, {60, 64, 192}}; auto x = m.add_parameter("x", s0); auto y = m.add_parameter("y", s1); std::vector mb_lens0 = {1, 12, 60, 64, 64}; std::vector mb_lens1 = {1, 12, 60, 64, 192}; auto mb_x = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens0}}), x); auto mb_y = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens1}}), y); auto concat_xy = m.add_instruction(migraphx::make_op("concat", {{"axis", 4}}), mb_x, mb_y); m.add_return({concat_xy}); auto m_original = m; run_pass(m); EXPECT(m == m_original); } // Shape of inputs to multibroadcasts do not have the same rank. // Matched by find_concat_multibroadcasts but skipped. // TODO: has a common broadcast axis, so can be simplified by moving multibroadcast up to have a // smaller concat. TEST_CASE(concat_multibroadcasts8) { migraphx::module m; auto s0 = migraphx::shape{migraphx::shape::float_type, {64, 64}}; auto s1 = migraphx::shape{migraphx::shape::float_type, {60, 1, 192}}; auto x = m.add_parameter("x", s0); auto y = m.add_parameter("y", s1); std::vector mb_lens0 = {60, 64, 64}; std::vector mb_lens1 = {60, 64, 192}; auto mb_x = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens0}}), x); auto mb_y = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens1}}), y); auto concat_xy = m.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), mb_x, mb_y); m.add_return({concat_xy}); auto m_original = m; run_pass(m); EXPECT(m == m_original); } // Shape of inputs to multibroadcasts do not have a common broadcast axis. // Matched by find_concat_multibroadcasts, but skipped because the dimensions other than // the concat axis are not the same. TEST_CASE(concat_multibroadcasts9) { migraphx::module m; auto s0 = migraphx::shape{migraphx::shape::float_type, {1, 64, 64}}; auto s1 = migraphx::shape{migraphx::shape::float_type, {60, 1, 192}}; auto x = m.add_parameter("x", s0); auto y = m.add_parameter("y", s1); std::vector mb_lens0 = {60, 64, 64}; std::vector mb_lens1 = {60, 64, 192}; auto mb_x = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens0}}), x); auto mb_y = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", mb_lens1}}), y); auto concat_xy = m.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), mb_x, mb_y); m.add_return({concat_xy}); auto m_original = m; run_pass(m); EXPECT(m == m_original); } TEST_CASE(concat_broadcast1) { auto s = migraphx::shape{migraphx::shape::float_type, {1024, 1024}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {8, 1024, 1024}}}), x); auto yb = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {8, 1024, 1024}}}), y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), xb, yb); m1.add_return({concat}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {8, 1024, 2048}}}), concat); m2.add_return({b}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(concat_transpose1) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto xt = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), x); auto yt = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), y); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), xt, yt); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), concat); m.add_return({t}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back().lens() == out_shape.lens()); EXPECT(std::distance(m.begin(), m.end()) == n - 3); auto new_concat = std::find_if(m.begin(), m.end(), [](const auto& ins) { return ins.name() == "concat"; }); EXPECT(new_concat != m.end()); EXPECT(new_concat->get_operator().to_value()["axis"].to() == 3); } TEST_CASE(concat_transpose2) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto xt = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), x); auto yt = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), y); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", -1}}), xt, yt); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), concat); m.add_return({t}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back().lens() == out_shape.lens()); EXPECT(std::distance(m.begin(), m.end()) == n - 2); auto new_concat = std::find_if(m.begin(), m.end(), [](const auto& ins) { return ins.name() == "concat"; }); EXPECT(new_concat != m.end()); EXPECT(new_concat->get_operator().to_value()["axis"].to() == 1); } TEST_CASE(concat_transpose3) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}; auto x = m.add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}); auto y = m.add_parameter("y", migraphx::shape{migraphx::shape::float_type, {1, 5, 3, 4}}); auto xt = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), x); auto yt = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), y); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 3}}), xt, yt); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), concat); m.add_return({t}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back().lens() == out_shape.lens()); EXPECT(std::distance(m.begin(), m.end()) == n - 2); auto new_concat = std::find_if(m.begin(), m.end(), [](const auto& ins) { return ins.name() == "concat"; }); EXPECT(new_concat != m.end()); EXPECT(new_concat->get_operator().to_value()["axis"].to() == 1); } TEST_CASE(concat_transpose4) { migraphx::module m; auto sx = migraphx::shape{migraphx::shape::float_type, {1, 1, 12, 64}}; auto sy = migraphx::shape{migraphx::shape::float_type, {1, 12, 1, 64}}; auto x = m.add_parameter("x", sx); auto y = m.add_parameter("y", sy); auto xt = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), x); auto yt = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), y); auto concat = m.add_instruction(migraphx::make_op("concat", {{"axis", 3}}), xt, yt); auto t = m.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), concat); m.add_return({t}); migraphx::module m1 = m; run_pass(m); EXPECT(m1 == m); } TEST_CASE(concat_unsqueeze) { auto s = migraphx::shape{migraphx::shape::float_type, {11008, 4096}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xunsqueeze = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), x); auto yunsqueeze = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), xunsqueeze, yunsqueeze); m1.add_return({concat}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto unsqueeze = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 22016, 4096}}}), concat); m2.add_return({unsqueeze}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(concat_reshape) { auto s = migraphx::shape{migraphx::shape::float_type, {11008, 32, 128}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xreshape = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {11008, 4096}}}), x); auto yreshape = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {11008, 4096}}}), y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), xreshape, yreshape); m1.add_return({concat}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto reshape = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {22016, 4096}}}), concat); m2.add_return({reshape}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(concat_reshape_change_axis) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 256, 1280}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xreshape = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 16, 16, 1280}}}), x); auto yreshape = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 16, 16, 1280}}}), y); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 3}}), xreshape, yreshape); m1.add_return({concat}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 2}}), x, y); auto reshape = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 16, 16, 2560}}}), concat); m2.add_return({reshape}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(concat_reshape_broadcast) { auto s = migraphx::shape{migraphx::shape::float_type, {11008, 32, 1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto xb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {11008, 32, 128}}}), x); auto yb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {11008, 32, 128}}}), y); auto xreshape = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {11008, 4096}}}), xb); auto yreshape = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {11008, 4096}}}), yb); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), xreshape, yreshape); m1.add_return({concat}); } migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, y); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {22016, 32, 128}}}), concat); auto reshape = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {22016, 4096}}}), broadcast); m2.add_return({reshape}); } run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(nested_concat) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto concat1 = m.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto concat2 = m.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), y, x); auto concat3 = m.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), concat1, concat2); m.add_return({concat3}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back().lens() == out_shape.lens()); EXPECT(std::distance(m.begin(), m.end()) == n - 2); EXPECT(std::count_if(m.begin(), m.end(), [](auto ins) { return ins.name() == "concat"; }) == 1); } TEST_CASE(nested_concat_partial) { migraphx::module m; auto s = migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 4}}; auto x = m.add_parameter("x", s); auto y = m.add_parameter("y", s); auto l = m.add_literal( migraphx::generate_literal(migraphx::shape{migraphx::shape::float_type, {1, 4, 3, 4}})); auto concat1 = m.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); auto concat2 = m.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), y, x); auto concat3 = m.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), concat1, concat2, l); m.add_return({concat3}); auto out_shape = m.get_output_shapes().back(); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(m.get_output_shapes().back().lens() == out_shape.lens()); EXPECT(std::distance(m.begin(), m.end()) == n - 2); EXPECT(std::count_if(m.begin(), m.end(), [](auto ins) { return ins.name() == "concat"; }) == 1); } TEST_CASE(multibroadcast_simplify) { migraphx::module m; std::vector s_lens{1, 2, 3, 4}; auto s = migraphx::shape{migraphx::shape::float_type, s_lens}; auto x = m.add_parameter("x", s); auto y = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s_lens}}), x); m.add_instruction(migraphx::make_op("mul"), y, y); auto n = std::distance(m.begin(), m.end()); run_pass(m); EXPECT(std::distance(m.begin(), m.end()) == n - 1); } TEST_CASE(multibroadcast_unsqueeze_scalar) { migraphx::module m1; { auto l = m1.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto mb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2}}}), l); auto t1 = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), mb); auto mul = m1.add_instruction(migraphx::make_op("mul"), t1, t1); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { auto l = m2.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto mb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 1}}}), l); auto mul = m2.add_instruction(migraphx::make_op("mul"), mb, mb); m2.add_return({mul}); } EXPECT(m1 == m2); } TEST_CASE(multibroadcast_unsqueeze_cont_scalar) { migraphx::module m1; { auto l = m1.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto mb = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2}}}), l); auto cnt = m1.add_instruction(migraphx::make_op("contiguous"), mb); auto t1 = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), cnt); auto mul = m1.add_instruction(migraphx::make_op("mul"), t1, t1); m1.add_return({mul}); } run_pass(m1); migraphx::module m2; { auto l = m2.add_parameter("x", {migraphx::shape::float_type, {1}, {0}}); auto mb = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 1}}}), l); auto mul = m2.add_instruction(migraphx::make_op("mul"), mb, mb); m2.add_return({mul}); } EXPECT(m1 == m2); } TEST_CASE(double_slice1) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {256}}); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {32}}, {"ends", {256}}}), x); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {32}}, {"ends", {64}}}), slice1); m1.add_return({slice2}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {256}}); auto slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {64}}, {"ends", {96}}}), x); m2.add_return({slice}); } EXPECT(m1 == m2); } TEST_CASE(double_slice2) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {256}}); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {32}}, {"ends", {128}}}), x); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {32}}}), slice1); m1.add_return({slice2}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {256}}); auto slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {32}}, {"ends", {64}}}), x); m2.add_return({slice}); } EXPECT(m1 == m2); } TEST_CASE(double_slice_multi_axes) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::int32_type, {256, 128}}); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {32}}, {"ends", {128}}}), x); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {32}}}), slice1); m1.add_return({slice2}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::int32_type, {256, 128}}); auto slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {32, 0}}, {"ends", {128, 32}}}), x); m2.add_return({slice}); } EXPECT(m1 == m2); } TEST_CASE(optimize_resize) { migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; auto create_resize_module = [&] { migraphx::module m; auto inx = m.add_parameter("X", sx); migraphx::shape si{migraphx::shape::int32_type, {1, 2, 4, 6}}; std::vector ind = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; auto li = m.add_literal(migraphx::literal(si, ind)); auto lrsp = m.add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), inx); auto gr = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); auto r = m.add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), gr); m.add_return({r}); return m; }; auto m1 = create_resize_module(); run_pass(m1); auto create_optimized_module = [&] { migraphx::module m; auto inx = m.add_parameter("X", sx); std::vector dims = {1, 1, 2, 1, 2, 1}; auto rspx = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {3, 5}}}), inx); auto mbx = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 2, 2, 2, 3}}}), rspx); std::vector orig_dims = {1, 2, 4, 6}; auto rmb = m.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 1, 4, 6}}}), mbx); auto rmbb = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 2, 4, 6}}}), rmb); auto r = m.add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), rmbb); m.add_return({r}); return m; }; EXPECT(m1 == create_optimized_module()); } TEST_CASE(optimize_resize_ind_not_apply) { migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; auto create_resize_module = [&] { migraphx::module m; auto inx = m.add_parameter("X", sx); migraphx::shape si{migraphx::shape::int32_type, {1, 2, 4, 6}}; std::vector ind = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; auto li = m.add_literal(migraphx::literal(si, ind)); auto lrsp = m.add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), inx); auto gr = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); auto r = m.add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), gr); m.add_return({r}); return m; }; auto m1 = create_resize_module(); run_pass(m1); EXPECT(m1 == create_resize_module()); } TEST_CASE(optimize_resize_rsp_dim_1) { migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; auto create_resize_module = [&] { migraphx::module m; auto inx = m.add_parameter("X", sx); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 3, 2}}; std::vector ind = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; auto li = m.add_literal(migraphx::literal(si, ind)); auto lrsp = m.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2}}}), inx); auto r = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); m.add_return({r}); return m; }; auto m = create_resize_module(); run_pass(m); EXPECT(m == create_resize_module()); } TEST_CASE(optimize_resize_ndims_unequal) { migraphx::shape sx{migraphx::shape::float_type, {1, 1, 2, 2}}; migraphx::shape sy{migraphx::shape::float_type, {1, 1, 4, 3, 2}}; auto create_resize_module = [&] { migraphx::module m; auto inx = m.add_parameter("X", sx); auto iny = m.add_parameter("Y", sy); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 3, 2}}; std::vector ind = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; auto li = m.add_literal(migraphx::literal(si, ind)); auto lrsp = m.add_instruction(migraphx::make_op("reshape", {{"dims", {4}}}), inx); auto gr = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); auto r = m.add_instruction(migraphx::make_op("sub"), iny, gr); m.add_return({r}); return m; }; auto m = create_resize_module(); run_pass(m); EXPECT(m == create_resize_module()); } TEST_CASE(optimize_resize_ind_non_brcst) { migraphx::shape sx{migraphx::shape::float_type, {1, 1, 3, 2}}; migraphx::shape sy{migraphx::shape::float_type, {1, 1, 4, 6}}; auto create_resize_module = [&] { migraphx::module m; auto inx = m.add_parameter("X", sx); auto iny = m.add_parameter("Y", sy); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 6}}; std::vector ind = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3}; auto li = m.add_literal(migraphx::literal(si, ind)); auto lrsp = m.add_instruction(migraphx::make_op("reshape", {{"dims", {6}}}), inx); auto gr = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); auto r = m.add_instruction(migraphx::make_op("sub"), iny, gr); m.add_return({r}); return m; }; auto m = create_resize_module(); run_pass(m); EXPECT(m == create_resize_module()); } TEST_CASE(optimize_resize_ind_non_const) { migraphx::shape sx{migraphx::shape::float_type, {1, 1, 3, 2}}; migraphx::shape sy{migraphx::shape::float_type, {1, 1, 4, 6}}; auto create_resize_module = [&] { migraphx::module m; auto inx = m.add_parameter("X", sx); auto iny = m.add_parameter("Y", sy); migraphx::shape si{migraphx::shape::int32_type, {1, 1, 4, 6}}; auto li = m.add_parameter("ind", si); auto lrsp = m.add_instruction(migraphx::make_op("reshape", {{"dims", {6}}}), inx); auto gr = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), lrsp, li); auto r = m.add_instruction(migraphx::make_op("sub"), iny, gr); m.add_return({r}); return m; }; auto m = create_resize_module(); run_pass(m); EXPECT(m == create_resize_module()); } TEST_CASE(optimize_where_true) { migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 2}}; auto create_where_module = [&](bool cond) { migraphx::module m; auto inx = m.add_parameter("X", s); auto iny = m.add_parameter("Y", s); migraphx::shape si{migraphx::shape::bool_type, {1, 1, 3, 2}}; std::vector idata(si.elements(), static_cast(cond)); auto li = m.add_literal(migraphx::literal(si, idata)); auto data = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), inx, iny); auto data_1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {12}}}), data); auto r = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), data_1, li); m.add_return({r}); return m; }; auto return_xy = [&](bool cond) { migraphx::module m; auto x = m.add_parameter("X", s); auto y = m.add_parameter("Y", s); cond ? m.add_return({x}) : m.add_return({y}); return m; }; auto m = create_where_module(true); run_pass(m); EXPECT(m == return_xy(true)); auto m1 = create_where_module(false); run_pass(m1); EXPECT(m1 == return_xy(false)); } TEST_CASE(where_different_cond_values) { auto create_where_module = [] { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 2}}; auto inx = m.add_parameter("X", s); auto iny = m.add_parameter("Y", s); migraphx::shape si{migraphx::shape::bool_type, {1, 1, 3, 2}}; std::vector idata = {1, 1, 0, 1, 0, 1}; auto li = m.add_literal(migraphx::literal(si, idata)); auto data = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), inx, iny); auto data_1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {12}}}), data); auto r = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), data_1, li); m.add_return({r}); return m; }; auto m = create_where_module(); run_pass(m); EXPECT(m == create_where_module()); } TEST_CASE(where_axis_nonzero) { auto create_where_module = [] { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 2}}; auto inx = m.add_parameter("X", s); auto iny = m.add_parameter("Y", s); migraphx::shape si{migraphx::shape::bool_type, {1, 1, 3, 2}}; std::vector idata(6, 1); auto li = m.add_literal(migraphx::literal(si, idata)); auto data = m.add_instruction(migraphx::make_op("concat", {{"axis", 1}}), inx, iny); auto data_1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {12}}}), data); auto r = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), data_1, li); m.add_return({r}); return m; }; auto m = create_where_module(); run_pass(m); EXPECT(m == create_where_module()); } TEST_CASE(where_three_concat_inputs) { auto create_where_module = [] { migraphx::module m; migraphx::shape s{migraphx::shape::float_type, {1, 1, 3, 2}}; auto inx = m.add_parameter("X", s); auto iny = m.add_parameter("Y", s); migraphx::shape si{migraphx::shape::bool_type, {1, 1, 3, 2}}; std::vector idata(6, 1); auto li = m.add_literal(migraphx::literal(si, idata)); auto data = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), inx, iny, inx); auto data_1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {18}}}), data); auto r = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), data_1, li); m.add_return({r}); return m; }; auto m = create_where_module(); run_pass(m); EXPECT(m == create_where_module()); } TEST_CASE(where_three_inputs_diff_shapes) { auto create_where_module = [] { migraphx::module m; migraphx::shape sx{migraphx::shape::float_type, {1, 1, 3, 2}}; migraphx::shape sy{migraphx::shape::float_type, {2, 1, 3, 2}}; auto inx = m.add_parameter("X", sx); auto iny = m.add_parameter("Y", sy); migraphx::shape si{migraphx::shape::bool_type, {1, 1, 3, 2}}; std::vector idata(6, 1); auto li = m.add_literal(migraphx::literal(si, idata)); auto data = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), inx, iny); auto data_1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {18}}}), data); auto r = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), data_1, li); m.add_return({r}); return m; }; auto m = create_where_module(); run_pass(m); EXPECT(m == create_where_module()); } TEST_CASE(where_three_lens_diff) { auto create_where_module = [] { migraphx::module m; migraphx::shape sx{migraphx::shape::float_type, {1, 1, 3, 2}}; migraphx::shape sy{migraphx::shape::float_type, {1, 1, 3, 2}}; auto inx = m.add_parameter("X", sx); auto iny = m.add_parameter("Y", sy); migraphx::shape si{migraphx::shape::bool_type, {1, 1, 6}}; std::vector idata(6, 1); auto li = m.add_literal(migraphx::literal(si, idata)); auto data = m.add_instruction(migraphx::make_op("concat", {{"axis", 0}}), inx, iny); auto data_1 = m.add_instruction(migraphx::make_op("reshape", {{"dims", {12}}}), data); auto r = m.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), data_1, li); m.add_return({r}); return m; }; auto m = create_where_module(); run_pass(m); EXPECT(m == create_where_module()); } TEST_CASE(reshape_cont) { auto create_module = [] { migraphx::module m; migraphx::shape sx{migraphx::shape::float_type, {1, 4, 1}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2, 2, 6}}; auto inx = m.add_parameter("x", sx); auto iny = m.add_parameter("y", sy); auto mb_inx = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 6}}}), inx); auto std_inx = m.add_instruction(migraphx::make_op("contiguous"), mb_inx); auto rsp = m.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 6}}}), std_inx); auto r = m.add_instruction(migraphx::make_op("add"), rsp, iny); m.add_return({r}); return m; }; auto m1 = create_module(); run_pass(m1); auto create_opt_module = [] { migraphx::module m; migraphx::shape sx{migraphx::shape::float_type, {1, 4, 1}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2, 2, 6}}; auto inx = m.add_parameter("x", sx); auto iny = m.add_parameter("y", sy); auto rsp = m.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 2, 1}}}), inx); auto mb_inx = m.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 2, 6}}}), rsp); auto r = m.add_instruction(migraphx::make_op("add"), mb_inx, iny); m.add_return({r}); return m; }; auto m2 = create_opt_module(); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_input_non_std) { migraphx::shape sx{migraphx::shape::float_type, {1, 4, 1}}; migraphx::shape sy{migraphx::shape::float_type, {2, 6, 2, 2}}; migraphx::module m1; { auto inx = m1.add_parameter("x", sx); auto iny = m1.add_parameter("y", sy); auto mb_inx = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 6}}}), inx); auto std_inx = m1.add_instruction(migraphx::make_op("contiguous"), mb_inx); auto rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 6}}}), std_inx); auto ty = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), iny); auto r = m1.add_instruction(migraphx::make_op("add"), rsp, ty); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { auto inx = m2.add_parameter("x", sx); auto iny = m2.add_parameter("y", sy); auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 2, 1}}}), inx); auto mb_inx = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 2, 6}}}), rsp); auto ty = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), iny); auto r = m2.add_instruction(migraphx::make_op("add"), mb_inx, ty); m2.add_return({r}); } EXPECT(m1 == m2); } TEST_CASE(reshape_cont_nonpw) { migraphx::module m1; { migraphx::shape sx{migraphx::shape::float_type, {1, 4, 1}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2, 2, 6}}; auto inx = m1.add_parameter("x", sx); auto iny = m1.add_parameter("y", sy); auto mb_inx = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4, 6}}}), inx); auto std_inx = m1.add_instruction(migraphx::make_op("contiguous"), mb_inx); auto rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 6}}}), std_inx); auto r = m1.add_instruction(migraphx::make_op("convolution"), rsp, iny); m1.add_return({r}); } run_pass(m1); migraphx::module m2; { migraphx::shape sx{migraphx::shape::float_type, {1, 4, 1}}; migraphx::shape sy{migraphx::shape::float_type, {2, 2, 2, 6}}; auto inx = m2.add_parameter("x", sx); auto iny = m2.add_parameter("y", sy); auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 2, 1}}}), inx); auto mb_inx = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 2, 6}}}), rsp); auto r = m2.add_instruction(migraphx::make_op("convolution"), mb_inx, iny); m2.add_return({r}); } EXPECT(m1 == m2); } TEST_CASE(reshape_unary_transpose) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 8, 5, 5}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), x); auto relu = m1.add_instruction(migraphx::make_op("relu"), reshape_ins); auto transpose = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), relu); m1.add_instruction(pass_op{}, transpose); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto relu = m2.add_instruction(migraphx::make_op("relu"), x); auto reshape_ins = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), relu); auto transpose = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), reshape_ins); m2.add_instruction(pass_op{}, transpose); } EXPECT(m1 == m2); } TEST_CASE(reshape_unary_last) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 8, 5, 5}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), x); m1.add_instruction(migraphx::make_op("relu"), reshape_ins); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(pointwise_reshape_unary_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 8, 5, 5}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 2, 2, 2, 5, 5}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s2); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), mul); auto relu = m1.add_instruction(migraphx::make_op("relu"), reshape_ins); auto pw = m1.add_instruction(migraphx::make_op("add"), z, relu); m1.add_instruction(pass_op{}, pw); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s1); auto z = m2.add_parameter("z", s2); auto reshape_x = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), x); auto reshape_y = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), y); auto mul = m2.add_instruction(migraphx::make_op("mul"), reshape_x, reshape_y); auto relu = m2.add_instruction(migraphx::make_op("relu"), mul); auto pw = m2.add_instruction(migraphx::make_op("add"), z, relu); m2.add_instruction(pass_op{}, pw); } EXPECT(m1 == m2); } TEST_CASE(pointwise_reshape_unary_pointwise_multi_use) { migraphx::shape s1{migraphx::shape::half_type, {2, 32, 10, 64, 64}}; migraphx::shape s2{migraphx::shape::float_type, {2, 32, 40960}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s2); auto add = m1.add_instruction(migraphx::make_op("add"), x, y); auto reshape1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 32, 40960}}}), add); auto convert2 = m1.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), reshape1); auto div = m1.add_instruction(migraphx::make_op("div"), convert2, z); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), add); auto reshape2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 32, 40960}}}), sqrt); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); auto reshape3 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 32, 40960}}}), relu); auto mul = m1.add_instruction(migraphx::make_op("mul"), reshape2, reshape3); m1.add_return({div, mul}); } auto output_shapes = m1.get_output_shapes(); run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s1); auto z = m2.add_parameter("z", s2); auto add = m2.add_instruction(migraphx::make_op("add"), x, y); auto convert2 = m2.add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), add); auto zreshape = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), z); auto div = m2.add_instruction(migraphx::make_op("div"), convert2, zreshape); auto reshape2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 32, 40960}}}), div); auto sqrt = m2.add_instruction(migraphx::make_op("sqrt"), add); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); auto mul = m2.add_instruction(migraphx::make_op("mul"), sqrt, relu); auto reshape3 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 32, 40960}}}), mul); m2.add_return({reshape2, reshape3}); } EXPECT(m1.get_output_shapes() == output_shapes); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(literal_reshape_unary_transpose_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 8, 5, 5}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 2, 5, 2, 5, 2}}; migraphx::module m1; { auto x = m1.add_parameter("x", s2); auto one = m1.add_literal(migraphx::generate_literal(s1)); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), one); auto relu = m1.add_instruction(migraphx::make_op("relu"), reshape_ins); auto transpose = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), relu); auto pw = m1.add_instruction(migraphx::make_op("add"), x, transpose); m1.add_instruction(pass_op{}, pw); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s2); auto one = m2.add_literal(migraphx::generate_literal(s1)); auto reshape_ins = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), one); auto transpose = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), reshape_ins); auto relu = m2.add_instruction(migraphx::make_op("relu"), transpose); auto pw = m2.add_instruction(migraphx::make_op("add"), x, relu); m2.add_instruction(pass_op{}, pw); } EXPECT(m1 == m2); } TEST_CASE(reshape_unary_transpose_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 8, 5, 5}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 2, 5, 2, 5, 2}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), x); auto relu = m1.add_instruction(migraphx::make_op("relu"), reshape_ins); auto transpose = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), relu); auto add = m1.add_instruction(migraphx::make_op("add"), transpose, y); m1.add_instruction(pass_op{}, add); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto reshape_ins = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), x); auto transpose = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), reshape_ins); auto relu = m2.add_instruction(migraphx::make_op("relu"), transpose); auto add = m2.add_instruction(migraphx::make_op("add"), relu, y); m2.add_instruction(pass_op{}, add); } EXPECT(m1 == m2); } TEST_CASE(pointwise_reshape_unary) { auto s = migraphx::shape{migraphx::shape::float_type, {2, 8, 5, 5}}; migraphx::module m1; { auto x = m1.add_parameter("x", s); auto y = m1.add_parameter("y", s); auto add = m1.add_instruction(migraphx::make_op("add"), x, y); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), add); auto relu = m1.add_instruction(migraphx::make_op("relu"), reshape_ins); m1.add_instruction(pass_op{}, relu); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s); auto y = m2.add_parameter("y", s); auto add = m2.add_instruction(migraphx::make_op("add"), x, y); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); auto reshape_ins = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 2, 2, 5, 5}}}), relu); m2.add_instruction(pass_op{}, reshape_ins); } EXPECT(m1 == m2); } TEST_CASE(pointwise_reshape_layout_convolution) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 32, 10, 64, 64}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {640, 320, 1, 1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto w = m1.add_parameter("w", s2); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto reshape_ins = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), mul); auto layout = m1.add_instruction( migraphx::make_op("layout", {{"permutation", {0, 2, 3, 1}}}), reshape_ins); auto conv = m1.add_instruction(migraphx::make_op("convolution"), layout, w); m1.add_instruction(pass_op{}, conv); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s1); auto w = m2.add_parameter("w", s2); auto mul = m2.add_instruction(migraphx::make_op("mul"), x, y); auto layout = m2.add_instruction( migraphx::make_op("layout", {{"permutation", {0, 3, 4, 1, 2}}}), mul); auto reshape_ins = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), layout); auto conv = m2.add_instruction(migraphx::make_op("convolution"), reshape_ins, w); m2.add_instruction(pass_op{}, conv); } EXPECT(m1 == m2); } TEST_CASE(pointwise_transpose_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 64, 4, 4}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 4, 4, 64}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s2); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto transpose = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), mul); auto add = m1.add_instruction(migraphx::make_op("add"), transpose, z); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s1); auto z = m2.add_parameter("z", s2); auto transposex = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), x); auto transposey = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), y); auto mul = m2.add_instruction(migraphx::make_op("mul"), transposex, transposey); auto add = m2.add_instruction(migraphx::make_op("add"), mul, z); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); m2.add_return({relu}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(pointwise_squeeze_1x1_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {1, 1}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s2); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), mul); auto add = m1.add_instruction(migraphx::make_op("add"), squeeze, z); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s1); auto z = m2.add_parameter("z", s2); auto unsqueezez = m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), z); auto mul = m2.add_instruction(migraphx::make_op("mul"), x, y); auto add = m2.add_instruction(migraphx::make_op("add"), mul, unsqueezez); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), relu); m2.add_return({squeeze}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(scalar_pointwise_unsqueeze_1x1_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type}; auto s2 = migraphx::shape{migraphx::shape::float_type, {1}}; auto s3 = migraphx::shape{migraphx::shape::float_type, {1, 1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto z = m1.add_parameter("z", s3); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto squeeze = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), mul); auto add = m1.add_instruction(migraphx::make_op("add"), squeeze, z); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto z = m2.add_parameter("z", s3); auto broadcastx = m2.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 1}}}), x); auto unsqueezey = m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), y); auto mul = m2.add_instruction(migraphx::make_op("mul"), broadcastx, unsqueezey); auto add = m2.add_instruction(migraphx::make_op("add"), mul, z); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); m2.add_return({relu}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(pointwise_transpose_pointwise_used_twice1) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 64, 4, 4}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 4, 4, 64}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s2); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto transpose = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), mul); auto add = m1.add_instruction(migraphx::make_op("add"), transpose, z); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu, mul}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s1); auto z = m2.add_parameter("z", s2); auto transposex = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), x); auto transposey = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), y); auto mul = m2.add_instruction(migraphx::make_op("mul"), transposex, transposey); auto transposemul = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), mul); auto add = m2.add_instruction(migraphx::make_op("add"), mul, z); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); m2.add_return({relu, transposemul}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(pointwise_transpose_pointwise_used_twice2) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 64, 4, 4}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 4, 4, 64}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s2); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto transpose = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), mul); auto add1 = m1.add_instruction(migraphx::make_op("add"), transpose, z); auto relu = m1.add_instruction(migraphx::make_op("relu"), add1); auto add2 = m1.add_instruction(migraphx::make_op("add"), x, mul); m1.add_return({relu, add2}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(pointwise_squeeze_scalar_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {1}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {1}, {0}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s2); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), mul); auto add = m1.add_instruction(migraphx::make_op("add"), squeeze, z); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } migraphx::module m2 = m1; run_pass(m1); // TODO: Enable a rewrite for this case. For now just check that we dont crash // { // auto x = m2.add_parameter("x", s1); // auto y = m2.add_parameter("y", s1); // auto z = m2.add_parameter("z", s2); // auto squeezex = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), x); // auto squeezey = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), y); // auto mul = m2.add_instruction(migraphx::make_op("mul"), squeezex, squeezey); // auto add = m2.add_instruction(migraphx::make_op("add"), mul, z); // auto relu = m2.add_instruction(migraphx::make_op("relu"), add); // m2.add_return({relu}); // } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(pointwise_unsqueeze_broadcast_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {64, 1, 1}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {64, 3, 7, 7}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s1); auto z = m1.add_parameter("z", s2); auto mul = m1.add_instruction(migraphx::make_op("mul"), x, y); auto unsqueeze = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {3}}}), mul); auto broadcast = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {64, 3, 7, 7}}}), unsqueeze); auto add = m1.add_instruction(migraphx::make_op("add"), broadcast, z); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(split_pointwise_reshape_transpose_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {1, 77, 1536}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {1, 77, 768}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto z = m1.add_parameter("z", s2); auto split1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {768}}}), x); auto split2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {768}}, {"ends", {1536}}}), x); auto add1 = m1.add_instruction(migraphx::make_op("add"), split1, y); auto reshape1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 77, 12, 64}}}), add1); auto transpose1 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), reshape1); auto scale1 = m1.add_literal(0.5f); auto scaleb1 = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 77, 64}}}), scale1); auto mul1 = m1.add_instruction(migraphx::make_op("mul"), transpose1, scaleb1); auto add2 = m1.add_instruction(migraphx::make_op("add"), split2, z); auto reshape2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {1, 77, 12, 64}}}), add2); auto transpose2 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), reshape2); auto scale2 = m1.add_literal(0.6f); auto scaleb2 = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 64, 77}}}), scale2); auto mul2 = m1.add_instruction(migraphx::make_op("mul"), transpose2, scaleb2); auto dot = m1.add_instruction(migraphx::make_op("dot"), mul1, mul2); m1.add_return({dot}); } // For now we dont rewrite since it prevents horizontal fusion migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_unsqueeze_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 32, 40960}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 32, 1, 1, 1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto reduce_sum = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto unsqueeze = m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {3, 4}}}), reduce_sum); auto add = m1.add_instruction(migraphx::make_op("add"), unsqueeze, y); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } // TODO: Enable a rewrite for this case. For now just check that we dont crash migraphx::module m2 = m1; // { // auto x = m2.add_parameter("x", s1); // auto y = m2.add_parameter("y", s2); // auto unsqueeze = // m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {3, 4}}}), x); // auto reduce_sum = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3, // 4}}}), unsqueeze); auto add = m2.add_instruction(migraphx::make_op("add"), reduce_sum, // y); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); // m2.add_return({relu}); // } run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_squeeze_pointwise1) { auto s1 = migraphx::shape{migraphx::shape::float_type, {1, 8, 1024, 1280}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {1, 1024, 1280}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto reduce_sum = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), reduce_sum); auto add = m1.add_instruction(migraphx::make_op("add"), squeeze, y); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto reduce_sum = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto unsqueeze = m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), y); auto add = m2.add_instruction(migraphx::make_op("add"), reduce_sum, unsqueeze); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), relu); m2.add_return({squeeze}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_squeeze_pointwise2) { auto s1 = migraphx::shape{migraphx::shape::float_type, {1, 1024, 1024, 1280}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {1, 1024, 1280}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto reduce_sum = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), reduce_sum); auto add = m1.add_instruction(migraphx::make_op("add"), squeeze, y); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto reduce_sum = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto unsqueeze = m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), y); auto add = m2.add_instruction(migraphx::make_op("add"), reduce_sum, unsqueeze); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), relu); m2.add_return({squeeze}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_squeeze_pointwise3) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 32, 10, 64, 64}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 32, 1}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto reduce_sum = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), x); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), reduce_sum); auto add = m1.add_instruction(migraphx::make_op("add"), squeeze, y); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto reduce_sum = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), x); auto unsqueeze = m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {3, 4}}}), y); auto add = m2.add_instruction(migraphx::make_op("add"), reduce_sum, unsqueeze); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), relu); m2.add_return({squeeze}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_squeeze_broadcast_pointwise) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 32, 10, 64, 64}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 32, 40960}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto reduce_sum = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), x); auto squeeze = m1.add_instruction(migraphx::make_op("squeeze", {{"axes", {3, 4}}}), reduce_sum); auto broadcast = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s2.lens()}}), squeeze); auto add = m1.add_instruction(migraphx::make_op("add"), broadcast, y); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto reduce_sum = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), x); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), reduce_sum); auto reshape1 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", s1.lens()}}), y); auto add = m2.add_instruction(migraphx::make_op("add"), broadcast, reshape1); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); auto reshape2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), relu); m2.add_return({reshape2}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_broadcast_reshape_pointwise1) { auto s1 = migraphx::shape{migraphx::shape::float_type, {64, 4}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {8, 8, 2, 2}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto reduce_sum = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), x); auto broadcast = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), reduce_sum); auto reshape = m1.add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), broadcast); auto add = m1.add_instruction(migraphx::make_op("add"), reshape, y); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto reshapex = m2.add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), x); auto reduce_sum = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3}}}), reshapex); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s2.lens()}}), reduce_sum); auto add = m2.add_instruction(migraphx::make_op("add"), broadcast, y); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); m2.add_return({relu}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reduce_broadcast_reshape_pointwise2) { auto s1 = migraphx::shape{migraphx::shape::float_type, {2, 32, 40960}}; auto s2 = migraphx::shape{migraphx::shape::float_type, {2, 320, 64, 64}}; auto s3 = migraphx::shape{migraphx::shape::float_type, {2, 32, 10, 64, 64}}; migraphx::module m1; { auto x = m1.add_parameter("x", s1); auto y = m1.add_parameter("y", s2); auto reduce_sum = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto broadcast = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), reduce_sum); auto reshape = m1.add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), broadcast); auto add = m1.add_instruction(migraphx::make_op("add"), reshape, y); auto relu = m1.add_instruction(migraphx::make_op("relu"), add); m1.add_return({relu}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", s1); auto y = m2.add_parameter("y", s2); auto reshapex = m2.add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), x); auto reduce_sum = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), reshapex); auto broadcast = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s3.lens()}}), reduce_sum); auto reshape1 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", s3.lens()}}), y); auto add = m2.add_instruction(migraphx::make_op("add"), broadcast, reshape1); auto relu = m2.add_instruction(migraphx::make_op("relu"), add); auto reshape2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", s2.lens()}}), relu); m2.add_return({reshape2}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(transpose_contiguous_reshape_binary_packed) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {2, 128, 28, 28}}); auto w1 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto conv1 = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), x, w1); // (2, 256, 28, 28) auto w2 = m1.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {512, 256, 1, 1}})); auto conv2 = m1.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), conv1, w2); // (2, 512, 14, 14) auto conv2_rsp1 = m1.add_instruction( migraphx::make_op("reshape", {{"dims", {2, 2, 2, 128, 14, 14}}}), conv2); auto conv2_trans = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), conv2_rsp1); auto conv2_cont = m1.add_instruction(migraphx::make_op("contiguous"), conv2_trans); auto conv2_rsp2 = m1.add_instruction( migraphx::make_op("reshape", {{"dims", {2, 128, 28, 28}}}), conv2_cont); auto add_ins = m1.add_instruction(migraphx::make_op("add"), conv2_rsp2, x); m1.add_instruction(pass_op{}, add_ins); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {2, 128, 28, 28}}); auto w1 = m2.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {256, 128, 1, 1}})); auto conv1 = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), x, w1); // (2, 256, 28, 28) auto w2 = m2.add_literal( migraphx::generate_literal({migraphx::shape::float_type, {512, 256, 1, 1}})); auto conv2 = m2.add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), conv1, w2); // (2, 512, 14, 14) auto conv2_rsp = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {2, 2, 2, 128, 14, 14}}}), conv2); auto conv2_trans = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 5, 2}}}), conv2_rsp); auto x_rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 128, 14, 2, 14, 2}}}), x); auto add_ins = m2.add_instruction(migraphx::make_op("add"), conv2_trans, x_rsp); auto add_rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 128, 28, 28}}}), add_ins); m2.add_instruction(pass_op{}, add_rsp); } EXPECT(m1 == m2); } TEST_CASE(transpose_contiguous_reshape_binary_broadcast) { migraphx::module m1; { migraphx::shape sx{migraphx::shape::float_type, {4}}; migraphx::shape sy{migraphx::shape::float_type, {2, 6, 2, 2}}; auto x = m1.add_parameter("x", sx); auto y = m1.add_parameter("y", sy); auto x_brcst = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 4, 6}}}), x); auto y_trans = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), y); auto y_rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 4, 6}}}), y_trans); auto r = m1.add_instruction(migraphx::make_op("add"), y_rsp, x_brcst); m1.add_return({r}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(transpose_unsqueeze_concat) { migraphx::module m1; { auto l0 = m1.add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); auto lt0 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l0); auto l1 = m1.add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); auto lt1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l1); auto l2 = m1.add_parameter("2", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); auto lt2 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l2); std::vector args{lt0, lt1, lt2}; std::vector unsqueezed_args; int64_t axis = 3; std::transform( args.begin(), args.end(), std::back_inserter(unsqueezed_args), [&](migraphx::instruction_ref arg) { return m1.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {axis}}}), arg); }); auto concat = m1.add_instruction(migraphx::make_op("concat", {{"axis", axis}}), unsqueezed_args); m1.add_return({concat}); } run_pass(m1); migraphx::module m2; { auto l0 = m2.add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); auto l1 = m2.add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); auto l2 = m2.add_parameter("2", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); std::vector args{l0, l1, l2}; std::vector unsqueezed_args; int64_t axis = 1; std::transform( args.begin(), args.end(), std::back_inserter(unsqueezed_args), [&](migraphx::instruction_ref arg) { return m2.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {axis}}}), arg); }); auto concat = m2.add_instruction(migraphx::make_op("concat", {{"axis", axis}}), unsqueezed_args); auto transpose = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 2}}}), concat); m2.add_return({transpose}); } EXPECT(m1 == m2); } TEST_CASE(transpose_slice) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 384, 36, 64}}); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {12}}}), x); auto transpose1 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), slice1); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {12}}, {"ends", {24}}}), x); auto transpose2 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), slice2); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {24}}, {"ends", {36}}}), x); auto transpose3 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), slice3); m1.add_return({transpose1, transpose2, transpose3}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {1, 384, 36, 64}}); auto transpose = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), x); auto slice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {12}}}), transpose); auto slice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {12}}, {"ends", {24}}}), transpose); auto slice3 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {24}}, {"ends", {36}}}), transpose); m2.add_return({slice1, slice2, slice3}); } EXPECT(m1 == m2); } TEST_CASE(transpose_slice_unsqueeze) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {4, 1024, 96, 64}}); auto transpose1 = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), x); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {8}}}), transpose1); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {16}}, {"ends", {24}}}), transpose1); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {32}}, {"ends", {40}}}), transpose1); m1.add_return({slice1, slice2, slice3}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {4, 1024, 96, 64}}); auto unsq = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {4, 1024, 12, 8, 64}}}), x); auto transpose = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {2, 0, 3, 4, 1}}}), unsq); auto slice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), transpose); auto sq1 = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), slice1); auto slice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {3}}}), transpose); auto sq2 = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), slice2); auto slice3 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {4}}, {"ends", {5}}}), transpose); auto sq3 = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), slice3); m2.add_return({sq1, sq2, sq3}); } EXPECT(m1 == m2); } TEST_CASE(transpose_slice_diff_perm) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 384, 36, 64}}); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {12}}}), x); auto transpose1 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), slice1); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {12}}, {"ends", {24}}}), x); auto transpose2 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), slice2); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {24}}, {"ends", {36}}}), x); auto transpose3 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), slice3); m1.add_return({transpose1, transpose2, transpose3}); } run_pass(m1); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {1, 384, 36, 64}}); auto transpose = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), x); auto slice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {12}}}), transpose); auto slice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {12}}, {"ends", {24}}}), transpose); auto transpose2 = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), slice2); auto slice3 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {24}}, {"ends", {36}}}), transpose); m2.add_return({slice1, transpose2, slice3}); } EXPECT(m1 == m2); } TEST_CASE(transpose_slice_single_transpose) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {1, 384, 36, 64}}); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {12}}}), x); auto sqrt1 = m1.add_instruction(migraphx::make_op("sqrt"), slice1); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {12}}, {"ends", {24}}}), x); auto transpose = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), slice2); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {24}}, {"ends", {36}}}), x); auto sqrt3 = m1.add_instruction(migraphx::make_op("sqrt"), slice3); m1.add_return({sqrt1, transpose, sqrt3}); } migraphx::module m2 = m1; run_pass(m1); EXPECT(m1 == m2); } TEST_CASE(transpose_slice_non_packed_axis) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {2, 384, 36, 64}}); auto transpose = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), x); auto slice = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {12}}}), transpose); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), slice); m1.add_return({sqrt}); } auto output_shapes = m1.get_output_shapes(); run_pass(m1); EXPECT(m1.get_output_shapes() == output_shapes); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {2, 384, 36, 64}}); auto unsqueeze = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 384, 3, 12, 64}}}), x); auto transpose = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {2, 0, 3, 1, 4}}}), unsqueeze); auto slice = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), transpose); auto squeeze = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), slice); auto sqrt = m2.add_instruction(migraphx::make_op("sqrt"), squeeze); m2.add_return({sqrt}); } EXPECT(m1 == m2); } TEST_CASE(transpose_slice_non_packed_multi_axis) { migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {2, 384, 36, 64}}); auto transpose = m1.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), x); auto slice1 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {12}}}), transpose); auto slice2 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {12}}, {"ends", {24}}}), transpose); auto transpose2 = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), slice2); auto slice3 = m1.add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {24}}, {"ends", {36}}}), transpose); m1.add_return({slice1, transpose2, slice3}); } auto output_shapes = m1.get_output_shapes(); run_pass(m1); EXPECT(to_lens(m1.get_output_shapes()) == to_lens(output_shapes)); migraphx::module m2; { auto x = m2.add_parameter("x", {migraphx::shape::float_type, {2, 384, 36, 64}}); auto unsqueeze = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 384, 3, 12, 64}}}), x); auto transpose = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {2, 0, 3, 1, 4}}}), unsqueeze); auto slice1 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {1}}}), transpose); auto squeeze1 = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), slice1); auto slice2 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {1}}, {"ends", {2}}}), transpose); auto transpose2 = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 2, 4, 3}}}), slice2); auto squeeze2 = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), transpose2); auto slice3 = m2.add_instruction( migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, {"ends", {3}}}), transpose); auto squeeze3 = m2.add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), slice3); m2.add_return({squeeze1, squeeze2, squeeze3}); } EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_reshape_dot) { migraphx::shape as{migraphx::shape::float_type, {2, 10, 32, 16}}; migraphx::shape bs{migraphx::shape::float_type, {2, 10, 16, 32}}; migraphx::module m1; { auto a = m1.add_literal(migraphx::generate_literal(as)); auto b = m1.add_parameter("input", bs); auto a_rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {20, 32, 16}}}), a); auto b_rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {20, 16, 32}}}), b); auto dot = m1.add_instruction(migraphx::make_op("dot"), a_rsp, b_rsp); auto dot_rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 10, 32, 32}}}), dot); m1.add_return({dot_rsp}); }; run_pass(m1); migraphx::module m2; { auto a = m2.add_literal(migraphx::generate_literal(as)); auto b = m2.add_parameter("input", bs); auto dot = m2.add_instruction(migraphx::make_op("dot"), a, b); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_reshape_dot_gemm_axis) { migraphx::shape as{migraphx::shape::float_type, {2, 10, 512}}; migraphx::shape bs{migraphx::shape::float_type, {2, 10, 512}}; migraphx::module m1; { auto a = m1.add_literal(migraphx::generate_literal(as)); auto b = m1.add_parameter("input", bs); auto a_rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {20, 32, 16}}}), a); auto b_rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {20, 16, 32}}}), b); auto dot = m1.add_instruction(migraphx::make_op("dot"), a_rsp, b_rsp); auto dot_rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 10, 1024}}}), dot); m1.add_return({dot_rsp}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_dot) { migraphx::shape s_inp{migraphx::shape::float_type, {2, 8, 8, 32}}; migraphx::shape s_w{migraphx::shape::float_type, {32, 32}}; migraphx::module m1; { auto inp = m1.add_parameter("inp", s_inp); auto rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 64, 32}}}), inp); auto w = m1.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 32}}}), w); auto dot = m1.add_instruction(migraphx::make_op("dot"), rsp, w_bc); m1.add_return({dot}); }; run_pass(m1); migraphx::module m2; { auto inp = m2.add_parameter("inp", s_inp); auto w = m2.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 8, 32, 32}}}), w); auto dot = m2.add_instruction(migraphx::make_op("dot"), inp, w_bc); auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 64, 32}}}), dot); m2.add_return({rsp}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_dot_flipped) { migraphx::shape s_inp{migraphx::shape::float_type, {2, 8, 8, 32}}; migraphx::shape s_w{migraphx::shape::float_type, {16, 8}}; migraphx::module m1; { auto inp = m1.add_parameter("inp", s_inp); auto rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {16, 8, 32}}}), inp); auto w = m1.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {16, 16, 8}}}), w); auto dot = m1.add_instruction(migraphx::make_op("dot"), w_bc, rsp); m1.add_return({dot}); }; run_pass(m1); migraphx::module m2; { auto inp = m2.add_parameter("inp", s_inp); auto w = m2.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 8, 16, 8}}}), w); auto dot = m2.add_instruction(migraphx::make_op("dot"), w_bc, inp); auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {16, 16, 32}}}), dot); m2.add_return({rsp}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_dot_dot_axis) { migraphx::shape s_inp{migraphx::shape::float_type, {2, 8, 8, 4}}; migraphx::shape s_w{migraphx::shape::float_type, {32, 32}}; migraphx::module m1; { auto inp = m1.add_parameter("inp", s_inp); auto rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 8, 32}}}), inp); auto w = m1.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 32}}}), w); auto dot = m1.add_instruction(migraphx::make_op("dot"), rsp, w_bc); m1.add_return({dot}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_dot_flipped_dot_axis) { migraphx::shape s_inp{migraphx::shape::float_type, {2, 8, 8, 32}}; migraphx::shape s_w{migraphx::shape::float_type, {8, 64}}; migraphx::module m1; { auto inp = m1.add_parameter("inp", s_inp); auto rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 64, 32}}}), inp); auto w = m1.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m1.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 8, 64}}}), w); auto dot = m1.add_instruction(migraphx::make_op("dot"), w_bc, rsp); m1.add_return({dot}); }; migraphx::module m2 = m1; run_pass(m1); EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_dot_broadcast) { migraphx::shape s_inp{migraphx::shape::float_type, {2, 8, 8, 32}}; migraphx::shape s_w{migraphx::shape::float_type, {32}}; migraphx::module m1; { auto inp = m1.add_parameter("inp", s_inp); auto rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 64, 32}}}), inp); auto w = m1.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 32, 32}}}), w); auto dot = m1.add_instruction(migraphx::make_op("dot"), rsp, w_bc); m1.add_return({dot}); }; run_pass(m1); migraphx::module m2; { auto inp = m2.add_parameter("inp", s_inp); auto w = m2.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", {2, 8, 32, 32}}}), w); auto dot = m2.add_instruction(migraphx::make_op("dot"), inp, w_bc); auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 64, 32}}}), dot); m2.add_return({rsp}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(reshape_dot_broadcast_2) { migraphx::shape s_inp{migraphx::shape::float_type, {2, 8, 8, 32}}; migraphx::shape s_w{migraphx::shape::float_type, {32}}; migraphx::module m1; { auto inp = m1.add_parameter("inp", s_inp); auto rsp = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {128, 32}}}), inp); auto w = m1.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {32, 32}}}), w); auto dot = m1.add_instruction(migraphx::make_op("dot"), rsp, w_bc); m1.add_return({dot}); }; run_pass(m1); migraphx::module m2; { auto inp = m2.add_parameter("inp", s_inp); auto w = m2.add_literal(migraphx::generate_literal(s_w)); auto w_bc = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 3}, {"out_lens", {2, 8, 32, 32}}}), w); auto dot = m2.add_instruction(migraphx::make_op("dot"), inp, w_bc); auto rsp = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {128, 32}}}), dot); m2.add_return({rsp}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(mul_transpose) { migraphx::shape s{migraphx::shape::float_type, {2, 32, 64, 64}}; migraphx::shape s2{migraphx::shape::float_type, {2, 64, 32, 32}}; migraphx::module m1; { auto inp = m1.add_parameter("input", s); auto c1 = m1.add_literal(migraphx::generate_literal(s)); auto mul = m1.add_instruction(migraphx::make_op("mul"), inp, c1); auto trans = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), mul); auto c3 = m1.add_literal(migraphx::generate_literal(s2)); auto dot = m1.add_instruction(migraphx::make_op("dot"), trans, c3); m1.add_return({dot}); }; run_pass(m1); migraphx::module m2; { auto inp = m2.add_parameter("input", s); auto inp_trans = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), inp); auto c1 = m2.add_literal(migraphx::generate_literal(s)); auto c1_trans = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), c1); auto mul = m2.add_instruction(migraphx::make_op("mul"), inp_trans, c1_trans); auto c3 = m2.add_literal(migraphx::generate_literal(s2)); auto dot = m2.add_instruction(migraphx::make_op("dot"), mul, c3); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(add_transpose) { migraphx::shape s{migraphx::shape::float_type, {2, 32, 64, 64}}; migraphx::shape s2{migraphx::shape::float_type, {2, 64, 32, 32}}; migraphx::module m1; { auto inp = m1.add_parameter("input", s); auto c1 = m1.add_literal(migraphx::generate_literal(s)); auto mul = m1.add_instruction(migraphx::make_op("add"), inp, c1); auto trans = m1.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), mul); auto c3 = m1.add_literal(migraphx::generate_literal(s2)); auto dot = m1.add_instruction(migraphx::make_op("dot"), trans, c3); m1.add_return({dot}); }; run_pass(m1); migraphx::module m2; { auto inp = m2.add_parameter("input", s); auto inp_trans = m2.add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), inp); auto c1 = m2.add_literal(migraphx::generate_literal(s)); auto c1_trans = m2.add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), c1); auto mul = m2.add_instruction(migraphx::make_op("add"), inp_trans, c1_trans); auto c3 = m2.add_literal(migraphx::generate_literal(s2)); auto dot = m2.add_instruction(migraphx::make_op("dot"), mul, c3); m2.add_return({dot}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(flatten) { migraphx::shape s{migraphx::shape::float_type, {4608, 8, 2}}; migraphx::module m1; { auto inp = m1.add_parameter("input", s); auto flat = m1.add_instruction(migraphx::make_op("flatten", {{"axis", 1}}), inp); m1.add_return({flat}); }; run_pass(m1); migraphx::module m2; { auto inp = m2.add_parameter("input", s); auto flat = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {4608, 16}}}), inp); m2.add_return({flat}); }; EXPECT(m1.sort() == m2.sort()); } TEST_CASE(conv_add_layernorm_conv) { migraphx::module m1; { auto p_x = m1.add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 4, 64, 64}}); auto p_w1 = m1.add_parameter("w1", migraphx::shape{migraphx::shape::float_type, {320, 4, 3, 3}}); auto p_w2 = m1.add_parameter("w2", migraphx::shape{migraphx::shape::float_type, {4, 320, 3, 3}}); auto p_y0 = m1.add_parameter("y0", migraphx::shape{migraphx::shape::float_type, {320}}); auto p_scale = m1.add_parameter("scale", migraphx::shape{migraphx::shape::float_type, {40960}}); auto p_bias = m1.add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {40960}}); auto p_y1 = m1.add_parameter("y1", migraphx::shape{migraphx::shape::float_type, {1}}); auto p_y2 = m1.add_parameter("y2", migraphx::shape{migraphx::shape::float_type, {1}}); auto p_y3 = m1.add_parameter("y3", migraphx::shape{migraphx::shape::float_type, {1}}); auto conv1 = m1.add_instruction(migraphx::make_op("convolution", {{"dilation", {1, 1}}, {"group", 1}, {"padding", {1, 1, 1, 1}}, {"padding_mode", 0}, {"stride", {1, 1}}}), p_x, p_w1); auto p_y0b = m1.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 320, 64, 64}}}), p_y0); auto add1 = m1.add_instruction(migraphx::make_op("add"), conv1, p_y0b); auto reshape1 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {0, 32, -1}}}), add1); auto p_y2b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 40960}}}), p_y2); auto div1 = m1.add_instruction(migraphx::make_op("div"), reshape1, p_y2b); auto reduce_sum1 = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), div1); auto reduce_sum1b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 40960}}}), reduce_sum1); auto sub1 = m1.add_instruction(migraphx::make_op("sub"), reshape1, reduce_sum1b); auto mul1 = m1.add_instruction(migraphx::make_op("mul"), reshape1, reshape1); auto p_y3b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 40960}}}), p_y3); auto div2 = m1.add_instruction(migraphx::make_op("div"), mul1, p_y3b); auto reduce_sum2 = m1.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), div2); auto mul2 = m1.add_instruction(migraphx::make_op("mul"), reduce_sum1, reduce_sum1); auto sub2 = m1.add_instruction(migraphx::make_op("sub"), reduce_sum2, mul2); auto p_y1b = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 1}}}), p_y1); auto add2 = m1.add_instruction(migraphx::make_op("add"), sub2, p_y1b); auto sqrt = m1.add_instruction(migraphx::make_op("sqrt"), add2); auto sqrtb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 40960}}}), sqrt); auto div3 = m1.add_instruction(migraphx::make_op("div"), sub1, sqrtb); auto p_scaleb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 40960}}}), p_scale); auto mul3 = m1.add_instruction(migraphx::make_op("mul"), div3, p_scaleb); auto p_biasb = m1.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 40960}}}), p_bias); auto add3 = m1.add_instruction(migraphx::make_op("add"), mul3, p_biasb); auto reshape2 = m1.add_instruction(migraphx::make_op("reshape", {{"dims", {0, 320, 64, 64}}}), add3); auto conv2 = m1.add_instruction(migraphx::make_op("convolution", {{"dilation", {1, 1}}, {"group", 1}, {"padding", {1, 1, 1, 1}}, {"padding_mode", 0}, {"stride", {1, 1}}}), reshape2, p_w2); m1.add_return({conv2}); }; run_pass(m1); migraphx::module m2; { auto p_y3 = m2.add_parameter("y3", migraphx::shape{migraphx::shape::float_type, {1}}); auto p_y2 = m2.add_parameter("y2", migraphx::shape{migraphx::shape::float_type, {1}}); auto p_y1 = m2.add_parameter("y1", migraphx::shape{migraphx::shape::float_type, {1}}); auto p_bias = m2.add_parameter("bias", migraphx::shape{migraphx::shape::float_type, {40960}}); auto p_scale = m2.add_parameter("scale", migraphx::shape{migraphx::shape::float_type, {40960}}); auto p_y0 = m2.add_parameter("y0", migraphx::shape{migraphx::shape::float_type, {320}}); auto p_w2 = m2.add_parameter("w2", migraphx::shape{migraphx::shape::float_type, {4, 320, 3, 3}}); auto p_w1 = m2.add_parameter("w1", migraphx::shape{migraphx::shape::float_type, {320, 4, 3, 3}}); auto p_x = m2.add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 4, 64, 64}}); auto conv1 = m2.add_instruction(migraphx::make_op("convolution", {{"dilation", {1, 1}}, {"group", 1}, {"padding", {1, 1, 1, 1}}, {"padding_mode", 0}, {"stride", {1, 1}}}), p_x, p_w1); auto reshape1 = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {2, 32, 10, 64, 64}}}), conv1); auto reshape2 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {32, 10}}}), p_y0); auto reshape2b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {2, 32, 10, 64, 64}}}), reshape2); auto add1 = m2.add_instruction(migraphx::make_op("add"), reshape1, reshape2b); auto unsqueeze_p_y2 = m2.add_instruction( migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3, 4}}, {"steps", {}}}), p_y2); auto unsqueeze_p_y2b = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 10, 64, 64}}}), unsqueeze_p_y2); auto div1 = m2.add_instruction(migraphx::make_op("div"), add1, unsqueeze_p_y2b); auto reduce_sum1 = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), div1); auto reduce_sum1b = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 10, 64, 64}}}), reduce_sum1); auto sub1 = m2.add_instruction(migraphx::make_op("sub"), add1, reduce_sum1b); auto mul1 = m2.add_instruction(migraphx::make_op("mul"), add1, add1); auto unsqueeze_p_y3 = m2.add_instruction( migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3, 4}}, {"steps", {}}}), p_y3); auto p_y3b = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 10, 64, 64}}}), unsqueeze_p_y3); auto div2 = m2.add_instruction(migraphx::make_op("div"), mul1, p_y3b); auto reduce_sum2 = m2.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2, 3, 4}}}), div2); auto mul2 = m2.add_instruction(migraphx::make_op("mul"), reduce_sum1, reduce_sum1); auto sub2 = m2.add_instruction(migraphx::make_op("sub"), reduce_sum2, mul2); auto unsqueeze_p_y1 = m2.add_instruction( migraphx::make_op("unsqueeze", {{"axes", {1, 2}}, {"steps", {}}}), p_y1); auto unsqueeze_p_y1b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", {2, 32, 1, 1, 1}}}), unsqueeze_p_y1); auto add2 = m2.add_instruction(migraphx::make_op("add"), sub2, unsqueeze_p_y1b); auto sqrt = m2.add_instruction(migraphx::make_op("sqrt"), add2); auto sqrtb = m2.add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 10, 64, 64}}}), sqrt); auto div3 = m2.add_instruction(migraphx::make_op("div"), sub1, sqrtb); auto reshape6 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {10, 64, 64}}}), p_scale); auto reshape6b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", {2, 32, 10, 64, 64}}}), reshape6); auto mul3 = m2.add_instruction(migraphx::make_op("mul"), div3, reshape6b); auto reshape7 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {10, 64, 64}}}), p_bias); auto reshape7b = m2.add_instruction( migraphx::make_op("broadcast", {{"axis", 2}, {"out_lens", {2, 32, 10, 64, 64}}}), reshape7); auto add3 = m2.add_instruction(migraphx::make_op("add"), mul3, reshape7b); auto reshape8 = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 320, 64, 64}}}), add3); auto conv2 = m2.add_instruction(migraphx::make_op("convolution", {{"dilation", {1, 1}}, {"group", 1}, {"padding", {1, 1, 1, 1}}, {"padding_mode", 0}, {"stride", {1, 1}}}), reshape8, p_w2); m2.add_return({conv2}); }; EXPECT(m1.sort() == m2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/split_reduce.cpp000066400000000000000000000301571510465702400207130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes(p, {migraphx::fuse_pointwise{}, migraphx::fuse_reduce{}, migraphx::split_reduce{.split_size = 8192}, migraphx::fuse_pointwise{.enable_rewrite_broadcasts = true}, migraphx::dead_code_elimination{}}); } static void run_fuse_pass(migraphx::program& p) { migraphx::run_passes( p, {migraphx::fuse_pointwise{}, migraphx::fuse_reduce{}, migraphx::dead_code_elimination{}}); } TEST_CASE(single) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 327680}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); mm->add_return({rsum}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = add_reduce( p2, "main:reduce_sum0_split", {x}, {2}, "assign_add", single_reduce("reduce_sum")); mm->add_return({rsum}); } EXPECT(p1 == p2); } TEST_CASE(fused) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 327680}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto add = mm->add_instruction(migraphx::make_op("add"), x, rsumb); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = add_reduce(p2, "main:reduce_sum0:main:pointwise0_split", {x}, {2}, "assign_add", single_reduce("reduce_sum")); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto add = add_pointwise(p2, mm, "main:pointwise0", {x, rsumb}, single_pointwise("add")); mm->add_return({add}); } EXPECT(p1 == p2); } TEST_CASE(small) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 1024}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto add = mm->add_instruction(migraphx::make_op("add"), x, rsumb); mm->add_return({add}); } migraphx::program p2 = p1; run_fuse_pass(p2); run_pass(p1); EXPECT(p1 == p2); } TEST_CASE(split_pointwise) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 327680}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto sqrt = mm->add_instruction(migraphx::make_op("sqrt"), x); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), sqrt); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto add = mm->add_instruction(migraphx::make_op("add"), sqrt, rsumb); mm->add_return({add}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto sqrt = add_pointwise(p2, mm, "main:pointwise0", {x}, single_pointwise("sqrt")); auto rsum = add_reduce(p2, "main:pointwise0:main:reduce_sum0:main:pointwise1_split", {sqrt}, {2}, "assign_add", single_reduce("reduce_sum")); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto add = add_pointwise(p2, mm, "main:pointwise1", {sqrt, rsumb}, single_pointwise("add")); mm->add_return({add}); } EXPECT(p1 == p2); } TEST_CASE(sequence_reduces) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 327680}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto rsum1b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto sub = mm->add_instruction(migraphx::make_op("sub"), x, rsum1b); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), sub); auto rsum2b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum2); auto add = mm->add_instruction(migraphx::make_op("add"), rsum2b, x); mm->add_return({add}); } migraphx::program p2 = p1; run_fuse_pass(p2); run_pass(p1); EXPECT(p1 == p2); } TEST_CASE(parallel_reduce) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 327680}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto xx = mm->add_instruction(migraphx::make_op("mul"), x, x); auto rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), x); auto rsum2 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), xx); auto mul = mm->add_instruction(migraphx::make_op("mul"), rsum1, rsum2); mm->add_return({mul}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = add_reduce( p2, "main:reduce_sum0:main:pointwise1:main:pointwise0:main:reduce_sum1_split", {x}, {2}, "assign_add", [&](auto* rm, const auto& inputs, const auto& axes) -> std::vector { auto xx = add_pointwise(p2, rm, "main:pointwise0", {inputs[0]}, squared()); auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), xx); return {rsum2, rsum1}; }); auto rsum2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), rsum); auto rsum1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), rsum); auto mul = add_pointwise(p2, mm, "main:pointwise1", {rsum1, rsum2}, single_pointwise("mul")); mm->add_return({mul}); } EXPECT(p1.sort() == p2.sort()); } TEST_CASE(double_split_live) { migraphx::shape s{migraphx::shape::float_type, {2, 3, 327680}}; migraphx::program p1; { auto* mm = p1.get_main_module(); auto x = mm->add_parameter("x", s); auto rsum = add_reduce( p1, "fuse_reduce0", {x}, {2}, [&](auto* rm, const auto& inputs, const auto& axes) { auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto sqrt = add_pointwise(p1, rm, "main:pointwise0", {rsum1}, single_pointwise("sqrt")); auto sqrtb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto mul = add_pointwise(p1, rm, "main:pointwise1", {inputs[0]}, squared()); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), mul); auto add = add_pointwise( p1, rm, "main:pointwise2", {rsum2, sqrt}, single_pointwise("add")); auto addb = rm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), add); return add_pointwise( p1, rm, "main:pointwise3", {addb, sqrtb}, single_pointwise("mul")); }); mm->add_return({rsum}); } run_pass(p1); migraphx::program p2; { auto* mm = p2.get_main_module(); auto x = mm->add_parameter("x", s); auto rsums = add_reduce( p2, "fuse_reduce0_split", {x}, {2}, "assign_add", [&](auto* rm, const auto& inputs, const auto& axes) -> std::vector { auto rsum1 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), inputs[0]); auto mul = add_pointwise(p2, rm, "main:pointwise1", {inputs[0]}, squared()); auto rsum2 = rm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), mul); return {rsum1, rsum2}; }); auto rsum1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), rsums); auto rsum2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), rsums); auto rsum1b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum1); auto rsum2b = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum2); auto sqrt_add_mul = add_pointwise( p2, "main:pointwise0", {rsum1b, rsum2b}, [](auto* pm, const auto& inputs) { auto sqrt = pm->add_instruction(migraphx::make_op("sqrt"), inputs[0]); auto add = pm->add_instruction(migraphx::make_op("add"), inputs[1], sqrt); return pm->add_instruction(migraphx::make_op("mul"), add, sqrt); }); mm->add_return({sqrt_add_mul}); } EXPECT(p1.sort() == p2.sort()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/split_single_dyn_dim_test.cpp000066400000000000000000000350111510465702400234610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include static void run_pass(migraphx::program& p) { migraphx::run_passes(p, {migraphx::split_single_dyn_dim{}, migraphx::dead_code_elimination{}}); } TEST_CASE(dynamic_batch) { // Slightly different from ref_ops_test in that the literal is copied over the submodules. // A different compiler pass will pull the literals from the submodules to the main module. migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); submod->add_return({add_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } migraphx::program p1; { auto* mm1 = p1.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input1 = mm1->add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm1->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = mm1->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, input1); auto add_ins = mm1->add_instruction(migraphx::make_op("add"), input1, broadcast_lit); mm1->add_return({add_ins}); } run_pass(p1); EXPECT(p0 == p1); } TEST_CASE(dynamic_batch_multiple_input) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input0 = submod->add_parameter("data0", sm_shape); auto sm_input1 = submod->add_parameter("data1", sm_shape); auto sm_input2 = submod->add_parameter("data2", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction( migraphx::make_op("multibroadcast"), literal_ins, sm_input0); auto add_ins0 = submod->add_instruction(migraphx::make_op("add"), sm_input0, broadcast_lit); auto add_ins1 = submod->add_instruction(migraphx::make_op("add"), add_ins0, sm_input1); auto add_ins2 = submod->add_instruction(migraphx::make_op("add"), add_ins1, sm_input2); submod->add_return({add_ins2}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data0", s); auto input1 = mm0->add_parameter("data1", s); auto input2 = mm0->add_parameter("data2", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0, input1, input2}, {dim1, dim2, dim3, dim4}); auto ret = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm0->add_return({ret}); } migraphx::program p1; { auto* mm1 = p1.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm1->add_parameter("data0", s); auto input1 = mm1->add_parameter("data1", s); auto input2 = mm1->add_parameter("data2", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm1->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = mm1->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, input0); auto add_ins0 = mm1->add_instruction(migraphx::make_op("add"), input0, broadcast_lit); auto add_ins1 = mm1->add_instruction(migraphx::make_op("add"), add_ins0, input1); auto add_ins2 = mm1->add_instruction(migraphx::make_op("add"), add_ins1, input2); mm1->add_return({add_ins2}); } run_pass(p1); EXPECT(p0.sort() == p1.sort()); } TEST_CASE(multiple_outputs) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p0.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = submod->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add0_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); auto add1_ins = submod->add_instruction(migraphx::make_op("add"), sm_input, sm_input); submod->add_return({add0_ins, add1_ins}); return submod; }; auto* dim1 = create_submodule(1, "dim_1"); auto* dim2 = create_submodule(2, "dim_2"); auto* dim3 = create_submodule(3, "dim_3"); auto* dim4 = create_submodule(4, "dim_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm0->add_parameter("data", s); std::vector sub_shapes = {}; migraphx::shape tmp_s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; sub_shapes.push_back(tmp_s); sub_shapes.push_back(tmp_s); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm0->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input0}, {dim1, dim2, dim3, dim4}); auto ret0 = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); auto ret1 = mm0->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), sm_ins); mm0->add_return({ret0, ret1}); } migraphx::program p1; { auto* mm1 = p1.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input1 = mm1->add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm1->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = mm1->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, input1); auto add0_ins = mm1->add_instruction(migraphx::make_op("add"), input1, broadcast_lit); auto add1_ins = mm1->add_instruction(migraphx::make_op("add"), input1, input1); mm1->add_return({add0_ins, add1_ins}); } run_pass(p1); EXPECT(p0 == p1); } // code coverage, does nothing TEST_CASE(empty_param_shapes) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 4}}; auto input0 = mm0->add_literal(migraphx::literal{s, {0, 1, 2, 3}}); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm0->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = mm0->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, input0); auto add0_ins = mm0->add_instruction(migraphx::make_op("add"), input0, broadcast_lit); mm0->add_return({add0_ins}); } migraphx::program p1 = p0; run_pass(p0); EXPECT(p0 == p1); }; // code coverage, does nothing TEST_CASE(multiple_non_fixed_dd_in_a_param) { migraphx::program p0; { auto* mm0 = p0.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 20}}}; auto input0 = mm0->add_parameter("data", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm0->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = mm0->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, input0); auto add0_ins = mm0->add_instruction(migraphx::make_op("add"), input0, broadcast_lit); mm0->add_return({add0_ins}); } migraphx::program p1 = p0; run_pass(p0); EXPECT(p0 == p1); } // code coverage, does nothing TEST_CASE(different_non_fixed_dd) { migraphx::program p0; { auto* mm1 = p0.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; migraphx::shape s1{migraphx::shape::float_type, {{3, 6}, {1, 1}, {4, 4}}}; auto input0 = mm1->add_parameter("data0", s0); auto input1 = mm1->add_parameter("data1", s1); auto broadcast_in0 = mm1->add_instruction(migraphx::make_op("multibroadcast"), input0, input1); auto broadcast_in1 = mm1->add_instruction(migraphx::make_op("multibroadcast"), input1, input0); auto add0_ins = mm1->add_instruction(migraphx::make_op("add"), broadcast_in0, broadcast_in1); mm1->add_return({add0_ins}); } migraphx::program p1 = p0; run_pass(p0); EXPECT(p0 == p1); } // check that the parameter inputs into select_module are lexiographically ordered TEST_CASE(ordered_inputs_to_select_module) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input0 = mm->add_parameter("breadfruit", s); auto input1 = mm->add_parameter("Apricot", s); auto input2 = mm->add_parameter("pineapple", s); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm->add_literal(migraphx::literal{lit_s, {6}}); auto broadcast_lit = mm->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, input0); auto add_ins0 = mm->add_instruction(migraphx::make_op("add"), input0, broadcast_lit); auto add_ins1 = mm->add_instruction(migraphx::make_op("add"), add_ins0, input1); auto add_ins2 = mm->add_instruction(migraphx::make_op("add"), add_ins1, input2); mm->add_return({add_ins2}); run_pass(p); auto sm_ins = std::find_if( mm->begin(), mm->end(), [&](auto&& ins) { return ins.name() == "select_module"; }); std::vector sm_param_names; for(auto&& ins : sm_ins->inputs()) { if(ins->name() == "@param") { auto&& param = migraphx::any_cast(ins->get_operator()); sm_param_names.push_back(param.parameter); } } EXPECT(std::is_sorted(sm_param_names.begin(), sm_param_names.end())); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/sqlite.cpp000066400000000000000000000041061510465702400175250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include TEST_CASE(read_write) { const std::string create_table = R"__migraphx__( CREATE TABLE IF NOT EXISTS test_db ( id INTEGER PRIMARY KEY ASC, data TEXT NOT NULL ); INSERT INTO test_db (id, data) VALUES (1, "a"); )__migraphx__"; const std::string select_all = R"__migraphx__( SELECT * FROM test_db; )__migraphx__"; migraphx::tmp_dir td{}; auto db_path = td.path / "test.db"; { auto db = migraphx::sqlite::write(db_path); db.execute(create_table); } { auto db = migraphx::sqlite::read(db_path); auto rows = db.execute(select_all); EXPECT(rows.size() == 1); const auto& row = rows.front(); EXPECT(row.at("data") == "a"); EXPECT(row.at("id") == "1"); } } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/stringutils.cpp000066400000000000000000000100031510465702400206040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include TEST_CASE(interpolate_string_simple1) { std::string input = "Hello ${w}!"; auto s = migraphx::interpolate_string(input, {{"w", "world"}}); EXPECT(s == "Hello world!"); } TEST_CASE(interpolate_string_simple2) { std::string input = "${hello}"; auto s = migraphx::interpolate_string(input, {{"hello", "bye"}}); EXPECT(s == "bye"); } TEST_CASE(interpolate_string_unbalanced) { std::string input = "${hello"; EXPECT(test::throws([&] { migraphx::interpolate_string(input, {{"hello", "bye"}}); })); } TEST_CASE(interpolate_string_extra_space) { std::string input = "${ hello }"; auto s = migraphx::interpolate_string(input, {{"hello", "bye"}}); EXPECT(s == "bye"); } TEST_CASE(interpolate_string_multiple) { std::string input = "${h} ${w}!"; auto s = migraphx::interpolate_string(input, {{"w", "world"}, {"h", "Hello"}}); EXPECT(s == "Hello world!"); } TEST_CASE(interpolate_string_next) { std::string input = "${hh}${ww}!"; auto s = migraphx::interpolate_string(input, {{"ww", "world"}, {"hh", "Hello"}}); EXPECT(s == "Helloworld!"); } TEST_CASE(interpolate_string_dollar_sign) { std::string input = "$hello"; auto s = migraphx::interpolate_string(input, {{"hello", "bye"}}); EXPECT(s == "$hello"); } TEST_CASE(interpolate_string_missing) { std::string input = "${hello}"; EXPECT(test::throws([&] { migraphx::interpolate_string(input, {{"h", "bye"}}); })); } TEST_CASE(interpolate_string_custom1) { std::string input = "****{{a}}****"; auto s = migraphx::interpolate_string(input, {{"a", "b"}}, "{{", "}}"); EXPECT(s == "****b****"); } TEST_CASE(interpolate_string_custom2) { std::string input = "****{{{a}}}****"; auto s = migraphx::interpolate_string(input, {{"a", "b"}}, "{{{", "}}}"); EXPECT(s == "****b****"); } TEST_CASE(interpolate_string_custom3) { std::string input = "****{{{{a}}}}****"; auto s = migraphx::interpolate_string(input, {{"a", "b"}}, "{{{{", "}}}}"); EXPECT(s == "****b****"); } TEST_CASE(slit_string_simple1) { std::string input = "one,two,three"; auto resuts = migraphx::split_string(input, ','); EXPECT(resuts.size() == 3); EXPECT(resuts.front() == "one"); EXPECT(resuts.back() == "three"); } TEST_CASE(slit_string_simple2) { std::string input = "one"; auto resuts = migraphx::split_string(input, ','); EXPECT(resuts.size() == 1); EXPECT(resuts.front() == "one"); } TEST_CASE(slit_string_simple3) { std::string input = "one two three"; auto resuts = migraphx::split_string(input, ','); EXPECT(resuts.size() == 1); EXPECT(resuts.front() == "one two three"); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/targets.cpp000066400000000000000000000033431510465702400176770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include "test.hpp" TEST_CASE(make_target) { for(const auto& name : migraphx::get_targets()) { auto t = migraphx::make_target(name); CHECK(t.name() == name); } } TEST_CASE(make_invalid_target) { EXPECT(test::throws([&] { migraphx::make_target("mi100"); })); } TEST_CASE(targets) { auto ref_target = migraphx::make_target("ref"); auto ts = migraphx::get_targets(); EXPECT(ts.size() >= 1); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/test.cpp000066400000000000000000000065251510465702400172120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include static bool glob_match(const std::string& input, const std::string& pattern) { return test::glob_match(input.begin(), input.end(), pattern.begin(), pattern.end()); } TEST_CASE(globbing) { EXPECT(not glob_match("ab", "a")); EXPECT(not glob_match("ba", "a")); EXPECT(not glob_match("bac", "a")); EXPECT(glob_match("ab", "ab")); // Star loop EXPECT(glob_match("/foo/bar/baz/blig/fig/blig", "/foo/*/blig")); EXPECT(glob_match("/foo/bar/baz/xlig/fig/blig", "/foo/*/blig")); EXPECT(glob_match("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", "a*a*a*a*a*a*a*a*b")); EXPECT(glob_match("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", "a*a*a*a*a*a*a*a**a*a*a*a*b")); EXPECT(glob_match("aabaabaab", "a*")); EXPECT(glob_match("aabaabaab", "a*b*ab")); EXPECT(glob_match("aabaabaab", "a*baab")); EXPECT(glob_match("aabaabaab", "aa*")); EXPECT(glob_match("aabaabaab", "aaba*")); EXPECT(glob_match("aabaabqqbaab", "a*baab")); EXPECT(glob_match("aabaabqqbaab", "a*baab")); EXPECT(glob_match("abcdd", "*d")); EXPECT(glob_match("abcdd", "*d*")); EXPECT(glob_match("daaadabadmanda", "da*da*da*")); EXPECT(glob_match("mississippi", "m*issip*")); EXPECT(glob_match("abc", "ab*c")); // Repeated star EXPECT(glob_match("aabaabqqbaab", "a****baab")); EXPECT(glob_match("abcdd", "***d")); EXPECT(glob_match("abcdd", "***d****")); EXPECT(not glob_match("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "a**z")); // Single wildcard EXPECT(glob_match("abc", "a?c")); EXPECT(not glob_match("abc", "ab?c")); // Special characters EXPECT(glob_match("test.foo[gpu]", "test.foo[gpu]")); EXPECT(glob_match("test.foo[gpu]", "test.foo[*]")); EXPECT(glob_match("test.foo[gpu]", "*[*")); EXPECT(glob_match("test.foo(gpu)", "test.foo(gpu)")); EXPECT(glob_match("test.foo(gpu)", "test.foo(*)")); EXPECT(glob_match("test.foo(gpu)", "*(*")); EXPECT(not glob_match("test.foog", "test.foo[gpu]")); EXPECT(not glob_match("test.foogpu", "test.foo[gpu]")); EXPECT(not glob_match("test_foo", "test.foo")); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/tf/000077500000000000000000000000001510465702400161305ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/tf/CMakeLists.txt000066400000000000000000000035011510465702400206670ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### function(add_tf_test TEST_NAME) rocm_add_test_executable(${TEST_NAME} ${ARGN}) rocm_clang_tidy_check(${TEST_NAME}) target_link_libraries(${TEST_NAME} migraphx_tf pb_files) target_include_directories(${TEST_NAME} PUBLIC ../include include) endfunction() file(GLOB_RECURSE PB_FILES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/models/*.pb) add_embed_library(pb_files ${PB_FILES} RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/models) file(GLOB TF_TESTS CONFIGRE_DEPENDS tests/*.cpp) add_tf_test(test_tf ${TF_TESTS}) ROCm-AMDMIGraphX-46524e8/test/tf/gen_tf_pb.py000066400000000000000000000666041510465702400204410ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### # This script generates tf pb files for MIGraphX tf operator tests. # To generate an individual pb file, you can use the following # command: python -c "import gen_tf_pb; gen_tf_pb.{test_name}_test()" import tensorflow as tf def tf_test(op_test): def run_test(): g1 = tf.Graph() op_test(g1) tf.io.write_graph(g1, './models', '{}.pb'.format(op_test.__name__), as_text=False) return run_test @tf_test def add_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='1') tf.add(g1_input, g2_input, name='add1') @tf_test def addv2_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='1') tf.raw_ops.AddV2(x=g1_input, y=g2_input, name='add1') @tf_test def add_bcast_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 1), name='1') tf.math.add(g1_input, g2_input, name='add_bcast1') @tf_test def addn_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 3), name='1') g3_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 3), name='2') tf.math.add_n([g1_input, g2_input, g3_input], name='addn1') @tf_test def addn_single_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 3), name='0') tf.math.add_n([g1_input], name='addn1') @tf_test def argmax_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(3, 4, 5, 6), name='0') tf.argmax(g1_input, axis=2, name='argmax1') @tf_test def argmin_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(3, 4, 5, 6), name='0') tf.argmin(g1_input, axis=2, name='argmin1') @tf_test def assert_less_equal_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 3), name='1') with tf.control_dependencies( [tf.compat.v1.assert_less_equal(g1_input, g2_input)]): tf.add(g1_input, g2_input, name='add1') @tf_test def batchmatmul_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 8, 4), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 4, 8), name='1') tf.matmul(g1_input, g2_input, transpose_a=True, transpose_b=True, name='batchmatmul1') @tf_test def batchnorm_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 32), name='x') g1_scale = tf.constant(1.0, dtype=tf.float32, shape=[32], name='scale') g1_offset = tf.compat.v1.placeholder(tf.float32, shape=(32), name='bias') g1_mean = tf.compat.v1.placeholder(tf.float32, shape=(32), name='mean') g1_variance = tf.compat.v1.placeholder(tf.float32, shape=(32), name='variance') tf.compat.v1.nn.fused_batch_norm(x=g1_input, scale=g1_scale, offset=g1_offset, mean=g1_mean, variance=g1_variance, epsilon=1e-4, is_training=False, name='batchnorm1') @tf_test def batchnorm_half_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float16, shape=(1, 16, 16, 32), name='x') g1_scale = tf.constant(1.0, dtype=tf.float32, shape=[32], name='scale') g1_offset = tf.compat.v1.placeholder(tf.float32, shape=(32), name='bias') g1_mean = tf.compat.v1.placeholder(tf.float32, shape=(32), name='mean') g1_variance = tf.compat.v1.placeholder(tf.float32, shape=(32), name='variance') tf.compat.v1.nn.fused_batch_norm(x=g1_input, scale=g1_scale, offset=g1_offset, mean=g1_mean, variance=g1_variance, epsilon=1e-4, is_training=False, name='batchnorm1') @tf_test def batchnormv3_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 32), name='x') g1_scale = tf.constant(1.0, dtype=tf.float32, shape=[32], name='scale') g1_offset = tf.compat.v1.placeholder(tf.float32, shape=(32), name='bias') g1_mean = tf.compat.v1.placeholder(tf.float32, shape=(32), name='mean') g1_variance = tf.compat.v1.placeholder(tf.float32, shape=(32), name='variance') tf.raw_ops.FusedBatchNormV3(x=g1_input, scale=g1_scale, offset=g1_offset, mean=g1_mean, variance=g1_variance, epsilon=1e-6, is_training=False, name='batchnorm1') @tf_test def biasadd_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 1, 1, 500), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(500), name='1') tf.nn.bias_add(g1_input, g2_input, name='bias_add1') @tf_test def biasadd_scalar_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 1), name='0') g2_const = tf.constant(1.0, tf.float32, shape=(1, ), name='1') tf.nn.bias_add(g1_input, g2_const, name='bias_add1') @tf_test def cast_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.cast(g1_input, dtype=tf.int32, name='cast1') @tf_test def concat_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(4, 7, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(4, 2, 3), name='1') tf.concat([g1_input, g2_input], axis=1, name='concat1') @tf_test def const_test(g1): with g1.as_default(): tf.constant(1.0, dtype=tf.float32, name='constant1') @tf_test def conv_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 3), name='0') g1_weights = tf.constant(value=1.0, dtype=tf.float32, shape=(3, 3, 3, 32), name='1') tf.nn.conv2d(g1_input, g1_weights, [1, 1, 1, 1], "SAME", name='conv1') @tf_test def conv_add_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 3), name='0') g1_weights = tf.constant(value=1.0, dtype=tf.float32, shape=(3, 3, 3, 32), name='1') conv = tf.nn.conv2d(g1_input, g1_weights, [1, 1, 1, 1], "SAME", name='conv1') tf.add(conv, conv, name='add1') @tf_test def conv_batch_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(None, 16, 16, 3), name='0') g1_weights = tf.constant(value=1.0, dtype=tf.float32, shape=(3, 3, 3, 32), name='1') tf.nn.conv2d(g1_input, g1_weights, [1, 1, 1, 1], "SAME", name='conv1') @tf_test def conv_nchw_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') g1_weights = tf.constant(value=1.0, dtype=tf.float32, shape=(3, 3, 3, 32), name='1') tf.nn.conv2d(g1_input, g1_weights, [1, 1, 1, 1], "SAME", data_format='NCHW', name='conv1') @tf_test def conv_relu_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 3), name='0') g1_weights = tf.constant(value=1.0, dtype=tf.float32, shape=(3, 3, 3, 32), name='1') conv = tf.nn.conv2d(g1_input, g1_weights, [1, 1, 1, 1], "SAME", name='conv1') tf.nn.relu(conv, name='relu1') @tf_test def conv_relu6_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 3), name='0') g1_weights = tf.constant(value=1.0, dtype=tf.float32, shape=(3, 3, 3, 32), name='1') conv = tf.nn.conv2d(g1_input, g1_weights, [1, 1, 1, 1], "SAME", name='conv1') tf.nn.relu6(conv, name='relu1') @tf_test def depthwiseconv_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 3), name='0') g1_weights = tf.constant(value=1.0, dtype=tf.float32, shape=(3, 3, 3, 1), name='1') tf.compat.v1.nn.depthwise_conv2d_native(g1_input, g1_weights, [1, 1, 1, 1], "SAME", name='depthwiseconv1') @tf_test def expanddims_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 3, 4), name='0') tf.expand_dims(g1_input, axis=0, name='expanddims_neg') @tf_test def gather_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 4), name='0') tf.gather(g1_input, [1, 1], axis=1, name='gather1') @tf_test def identity_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.identity(g1_input, 'identity') @tf_test def matmul_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(8, 4), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(4, 8), name='1') tf.matmul(g1_input, g2_input, transpose_a=True, transpose_b=True, name='matmul1') @tf_test def mean_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.math.reduce_mean(g1_input, axis=(2, 3), keepdims=True, name='mean1') tf.math.reduce_mean(g1_input, axis=(2, 3), keepdims=False, name='mean2') @tf_test def mean_test_nhwc(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 3), name='0') tf.math.reduce_mean(g1_input, axis=(1, 2), keepdims=False, name='mean2') @tf_test def mul_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 1, 1, 16), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 1, 1, 16), name='1') tf.multiply(g1_input, g2_input, name='mul1') @tf_test def multi_output_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.nn.relu(g1_input, 'relu') tf.tanh(g1_input, 'tanh') @tf_test def noop_test(g1): with g1.as_default(): tf.raw_ops.NoOp(name='noop1') @tf_test def onehot_test(g1): with g1.as_default(): g1_input = tf.constant((1, 1, 1, 1, 1), dtype=tf.int32) tf.one_hot(g1_input, 2, name='onehot1') @tf_test def pack_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(2), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(2), name='1') g3_input = tf.compat.v1.placeholder(tf.float32, shape=(2), name='2') tf.stack([g1_input, g2_input, g3_input], axis=1, name='pack1') @tf_test def pack_test_nhwc(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 1, 1, 2), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 1, 1, 2), name='1') g3_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 1, 1, 2), name='2') tf.stack([g1_input, g2_input, g3_input], axis=3, name='pack1') @tf_test def pad_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(2, 4), name='0') paddings = tf.constant([[1, 1], [2, 2]]) tf.pad(g1_input, paddings, name='pad1') @tf_test def pooling_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 16, 16, 3), name='0') tf.compat.v1.nn.avg_pool(value=g1_input, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1), padding='VALID', data_format='NHWC', name='avg_pooling') tf.compat.v1.nn.max_pool(value=g1_input, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1), padding='VALID', data_format='NHWC', name='max_pooling') @tf_test def pow_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='1') tf.pow(g1_input, g2_input, name='pow1') @tf_test def relu_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.nn.relu(g1_input, 'relu') @tf_test def relu6_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.nn.relu6(g1_input, 'relu6') @tf_test def relu6_half_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float16, shape=(1, 3, 16, 16), name='0') tf.nn.relu6(g1_input, 'relu6') @tf_test def reshape_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(16), name='0') tf.reshape(g1_input, (1, 1, 1, 16), 'reshape') @tf_test def rsqrt_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.math.rsqrt(g1_input, 'rsqrt') @tf_test def shape_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') g1.create_op(op_type='Shape', inputs=[g1_input]) @tf_test def sigmoid_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.math.sigmoid(g1_input, 'sigmoid') @tf_test def slice_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(5, 10), name='0') tf.slice(g1_input, [1, 0], [2, -1], name='slice1') @tf_test def softmax_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3), name='0') tf.nn.softmax(g1_input, name='softmax') @tf_test def split_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(5, 30), name='0') split0, split1, split2 = tf.split(g1_input, 3, 1, name='split') tf.concat([split0, split1], axis=1, name='concat1') tf.concat([split1, split2], axis=1, name='concat2') @tf_test def split_test_one_output(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(5, 30), name='0') tf.split(g1_input, 1, 1, name='split') @tf_test def split_test_vector_as_input(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(5, 30), name='0') split0, split1, split2 = tf.split(g1_input, [4, 15, 11], 1, name='split') tf.concat([split0, split1], axis=1, name='concat1') tf.concat([split1, split2], axis=1, name='concat2') @tf_test def sqdiff_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='1') tf.compat.v1.squared_difference(g1_input, g2_input, name='sqdiff') @tf_test def squeeze_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 3, 1), name='0') tf.squeeze(g1_input, name='squeeze') @tf_test def stopgradient_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.stop_gradient(g1_input, 'stopgradient') @tf_test def stridedslice_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 1, 1, 10), name='0') tf.strided_slice(g1_input, [0, 0, 0, 0], [1, 1, 1, 5], [1, 1, 1, 1], shrink_axis_mask=2, name='stridedslice1') @tf_test def stridedslice_masks_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 3, 10), name='0') tf.strided_slice(g1_input, [0, 1, 1, 0], [0, 0, 0, 0], [1, 1, 1, 1], begin_mask=9, end_mask=15, name='stridedslice1') @tf_test def sub_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='0') g2_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 2, 2, 3), name='1') tf.subtract(g1_input, g2_input, name='sub1') @tf_test def tanh_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.tanh(g1_input, 'tanh') @tf_test def transpose_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(1, 3, 16, 16), name='0') tf.transpose(g1_input, perm=[0, 2, 3, 1], name='transpose') @tf_test def variable_batch_test(g1): with g1.as_default(): g1_input = tf.compat.v1.placeholder(tf.float32, shape=(0, 3, 16, 16), name='0') tf.identity(g1_input, name='identity') if __name__ == '__main__': add_test() addv2_test() add_bcast_test() argmax_test() argmin_test() assert_less_equal_test() batchmatmul_test() batchnorm_test() batchnormv3_test() biasadd_test() biasadd_scalar_test() cast_test() concat_test() const_test() conv_test() conv_add_test() conv_batch_test() conv_nchw_test() conv_relu_test() conv_relu6_test() depthwiseconv_test() expanddims_test() gather_test() identity_test() matmul_test() mean_test() mean_test_nhwc() mul_test() multi_output_test() noop_test() onehot_test() pack_test() pack_test_nhwc() pad_test() pooling_test() pow_test() relu_test() relu6_test() relu6_half_test() reshape_test() rsqrt_test() shape_test() slice_test() softmax_test() split_test() split_test_one_output() split_test_vector_as_input() sqdiff_test() squeeze_test() stopgradient_test() stridedslice_test() stridedslice_masks_test() sub_test() tanh_test() transpose_test() variable_batch_test() ROCm-AMDMIGraphX-46524e8/test/tf/include/000077500000000000000000000000001510465702400175535ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/tf/include/tf_conv_utils.hpp000066400000000000000000000037351510465702400231520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #ifndef MIGRAPHX_GUARD_TEST_TF_TF_CONV_UTILS_HPP #define MIGRAPHX_GUARD_TEST_TF_TF_CONV_UTILS_HPP inline migraphx::program create_conv() { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); std::vector weight_data(3 * 3 * 3 * 32); std::fill(weight_data.begin(), weight_data.end(), 1.0f); auto l1 = mm->add_literal(migraphx::shape{migraphx::shape::float_type, {3, 3, 3, 32}}, weight_data); migraphx::op::convolution op; op.padding = {1, 1, 1, 1}; op.stride = {1, 1}; op.dilation = {1, 1}; auto l2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {3, 2, 0, 1}}}), l1); mm->add_instruction(op, l0, l2); return p; } #endif ROCm-AMDMIGraphX-46524e8/test/tf/include/tf_test.hpp000066400000000000000000000064751510465702400217500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_TF_TF_TEST_HPP #define MIGRAPHX_GUARD_TEST_TF_TF_TEST_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test.hpp" inline migraphx::program read_pb_file(const std::string& name, const migraphx::tf_options& options) { static auto pb_files{::pb_files()}; if(pb_files.find(name) == pb_files.end()) { std::cerr << "Can not find TensorFlow Protobuf file by name: " << name << " , aborting the program" << std::endl; std::abort(); } return migraphx::parse_tf_buffer(std::string{pb_files.at(name)}, options); } inline migraphx::program parse_tf(const std::string& name, bool is_nhwc, const std::unordered_map>& dim_params = {}, const std::vector& output_node_names = {}) { return read_pb_file(name, migraphx::tf_options{is_nhwc, 1, dim_params, output_node_names}); } inline migraphx::program optimize_tf(const std::string& name, bool is_nhwc) { auto prog = read_pb_file(name, migraphx::tf_options{is_nhwc, 1}); auto* mm = prog.get_main_module(); if(is_nhwc) migraphx::run_passes(*mm, {migraphx::simplify_reshapes{}, migraphx::dead_code_elimination{}, migraphx::eliminate_identity{}}); // remove the last return instruction if(mm->size() > 0) { auto last_ins = std::prev(mm->end()); if(last_ins->name() == "@return") { mm->remove_instruction(last_ins); } } return prog; } #endif ROCm-AMDMIGraphX-46524e8/test/tf/models/000077500000000000000000000000001510465702400174135ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/tf/models/add_bcast_test.pb000066400000000000000000000002171510465702400227010ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : 2 1 Placeholder* dtype0* shape : add_bcast1Add01* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/add_test.pb000066400000000000000000000002331510465702400215230ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: : 1 Placeholder* dtype0* shape:  add1AddV201* T0"æ ROCm-AMDMIGraphX-46524e8/test/tf/models/addn_single_test.pb000066400000000000000000000001301510465702400232360ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape :  addn1Identity0* T0"ÑROCm-AMDMIGraphX-46524e8/test/tf/models/addn_test.pb000066400000000000000000000003131510465702400217000ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : 2 1 Placeholder* dtype0* shape : 2 2 Placeholder* dtype0* shape : ( addn1AddN012* N* T0"ÑROCm-AMDMIGraphX-46524e8/test/tf/models/addv2_test.pb000066400000000000000000000002331510465702400217730ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: : 1 Placeholder* dtype0* shape:  add1AddV201* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/argmax_test.pb000066400000000000000000000003171510465702400222550ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: ; argmax1/dimensionConst* dtype0* value B: O argmax1ArgMax0argmax1/dimension* T0* Tidx0* output_type0 "¸ROCm-AMDMIGraphX-46524e8/test/tf/models/argmin_test.pb000066400000000000000000000003171510465702400222530ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: ; argmin1/dimensionConst* dtype0* value B: O argmin1ArgMin0argmin1/dimension* T0* Tidx0* output_type0 "¸ROCm-AMDMIGraphX-46524e8/test/tf/models/assert_less_equal_test.pb000066400000000000000000000045231510465702400245170ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : 2 1 Placeholder* dtype0* shape : 7 assert_less_equal/LessEqual LessEqual01* T0 L assert_less_equal/ConstConst* dtype0* valueB" o assert_less_equal/AllAllassert_less_equal/LessEqualassert_less_equal/Const* Tidx0* keep_dims( r assert_less_equal/Assert/ConstConst* dtype0*< value3B1B+Condition x <= y did not hold element-wise: S assert_less_equal/Assert/Const_1Const* dtype0* valueBB x (0:0) = S assert_less_equal/Assert/Const_2Const* dtype0* valueBB y (1:0) = ç $assert_less_equal/Assert/AssertGuardIfassert_less_equal/Allassert_less_equal/All01* Tcond0 * Tin 2 * Tout 2 * _lower_using_switch_merge(* _read_only_resource_inputs *@ else_branch1R/ -assert_less_equal_Assert_AssertGuard_false_30* output_shapes :*? then_branch0R. ,assert_less_equal_Assert_AssertGuard_true_29 h -assert_less_equal/Assert/AssertGuard/IdentityIdentity$assert_less_equal/Assert/AssertGuard* T0 J add1Add01.^assert_less_equal/Assert/AssertGuard/Identity* T0« ­ ~ -assert_less_equal_Assert_AssertGuard_false_30 assert_assert_less_equal_all  assert_0 assert_1 identity ˆr Assert/data_0Const* dtype0*< value3B1B+Condition x <= y did not hold element-wise:2 Assert/data_0Q Assert/data_1Const* dtype0* valueBB x (0:0) = 2 Assert/data_1Q Assert/data_3Const* dtype0* valueBB y (1:0) = 2 Assert/data_3µ AssertAssertassert_assert_less_equal_allAssert/data_0:output:0Assert/data_1:output:0assert_0Assert/data_3:output:0assert_1* T 2* summarize2 AssertP IdentityIdentityassert_assert_less_equal_all^Assert* T0 2 Identity" identityIdentity:output:0:  _output_shapes ::$  _output_shapes ::$  _output_shapes : ø „ ,assert_less_equal_Assert_AssertGuard_true_29" identity_assert_less_equal_all  placeholder placeholder_1 identity  NoOpNoOp2 NoOpP IdentityIdentityidentity_assert_less_equal_all^NoOp* T0 2 Identity" identityIdentity:output:0:  _output_shapes ::$  _output_shapes ::$  _output_shapes :"¸ ROCm-AMDMIGraphX-46524e8/test/tf/models/batchmatmul_test.pb000066400000000000000000000003051510465702400232740ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: : 1 Placeholder* dtype0* shape: F batchmatmul1 BatchMatMulV201* T0* adj_x(* adj_y("¸ROCm-AMDMIGraphX-46524e8/test/tf/models/batchnorm_half_test.pb000066400000000000000000000007061510465702400237470ustar00rootroot00000000000000 : x Placeholder* dtype0* shape: 6 scaleConst* dtype0* valueB *€? 1 bias Placeholder* dtype0* shape: 1 mean Placeholder* dtype0* shape: 5 variance Placeholder* dtype0* shape: ­ batchnorm1FusedBatchNormV3xscalebiasmeanvariance* T0* U0* data_formatNHWC* epsilon%·Ñ8* exponential_avg_factor%€?* is_training("‚ROCm-AMDMIGraphX-46524e8/test/tf/models/batchnorm_test.pb000066400000000000000000000007061510465702400227550ustar00rootroot00000000000000 : x Placeholder* dtype0* shape: 6 scaleConst* dtype0* valueB *€? 1 bias Placeholder* dtype0* shape: 1 mean Placeholder* dtype0* shape: 5 variance Placeholder* dtype0* shape: ­ batchnorm1FusedBatchNormV3xscalebiasmeanvariance* T0* U0* data_formatNHWC* epsilon%·Ñ8* exponential_avg_factor%€?* is_training("‚ROCm-AMDMIGraphX-46524e8/test/tf/models/batchnormv3_test.pb000066400000000000000000000007061510465702400232260ustar00rootroot00000000000000 : x Placeholder* dtype0* shape: 6 scaleConst* dtype0* valueB *€? 1 bias Placeholder* dtype0* shape: 1 mean Placeholder* dtype0* shape: 5 variance Placeholder* dtype0* shape: ­ batchnorm1FusedBatchNormV3xscalebiasmeanvariance* T0* U0* data_formatNHWC* epsilon%½7†5* exponential_avg_factor%€?* is_training("‚ROCm-AMDMIGraphX-46524e8/test/tf/models/biasadd_scalar_test.pb000066400000000000000000000002511510465702400237070ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : 2 1Const* dtype0* valueB*€? : bias_add1BiasAdd01* T0* data_formatNHWC"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/biasadd_test.pb000066400000000000000000000002571510465702400223700ustar00rootroot00000000000000 ; 0 Placeholder* dtype0* shape:ô / 1 Placeholder* dtype0* shape:ô : bias_add1BiasAdd01* T0* data_formatNHWC"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/cast_test.pb000066400000000000000000000001731510465702400217300ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: 8 cast1Cast0* DstT0* SrcT0* Truncate("¸ROCm-AMDMIGraphX-46524e8/test/tf/models/concat_test.pb000066400000000000000000000003641510465702400222470ustar00rootroot00000000000000 6 0 Placeholder* dtype0* shape:  6 1 Placeholder* dtype0* shape:  6 concat1/axisConst* dtype0* value B: E concat1ConcatV201 concat1/axis* N* T0* Tidx0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/constant_test.pb000066400000000000000000000000741510465702400226270ustar00rootroot00000000000000 6 constant1Const* value B *€?* dtype0"ROCm-AMDMIGraphX-46524e8/test/tf/models/conv_add_test.pb000066400000000000000000000005141510465702400225520ustar00rootroot00000000000000 : 0 Placeholder* shape:* dtype0 > 1Const* dtype0*% valueB *€? ¥ conv1Conv2D01* dilations * T0* data_formatNHWC* strides * explicit_paddings * use_cudnn_on_gpu(* paddingSAME " add1Addconv1conv1* T0"&ROCm-AMDMIGraphX-46524e8/test/tf/models/conv_batch_test.pb000066400000000000000000000004621510465702400231050ustar00rootroot00000000000000 C 0 Placeholder* dtype0*$ shape: ÿÿÿÿÿÿÿÿÿ > 1Const* dtype0*% valueB *€? ¥ conv1Conv2D01* T0* data_formatNHWC* dilations * explicit_paddings * paddingSAME* strides * use_cudnn_on_gpu("±ROCm-AMDMIGraphX-46524e8/test/tf/models/conv_nchw_test.pb000066400000000000000000000004511510465702400227610ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: > 1Const* dtype0*% valueB *€? ¥ conv1Conv2D01* T0* data_formatNCHW* dilations * explicit_paddings * paddingSAME* strides * use_cudnn_on_gpu("¸ROCm-AMDMIGraphX-46524e8/test/tf/models/conv_relu6_test.pb000066400000000000000000000005101510465702400230530ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: > 1Const* dtype0*% valueB *€? ¥ conv1Conv2D01* dilations * T0* strides * data_formatNHWC* use_cudnn_on_gpu(* explicit_paddings * paddingSAME  relu1Relu6conv1* T0"&ROCm-AMDMIGraphX-46524e8/test/tf/models/conv_relu_test.pb000066400000000000000000000005071510465702400227730ustar00rootroot00000000000000 : 0 Placeholder* shape:* dtype0 > 1Const*% valueB *€?* dtype0 ¥ conv1Conv2D01* paddingSAME* dilations * T0* strides * data_formatNHWC* explicit_paddings * use_cudnn_on_gpu(  relu1Reluconv1* T0"&ROCm-AMDMIGraphX-46524e8/test/tf/models/conv_test.pb000066400000000000000000000004511510465702400217420ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: > 1Const* dtype0*% valueB *€? ¥ conv1Conv2D01* T0* data_formatNHWC* dilations * explicit_paddings * paddingSAME* strides * use_cudnn_on_gpu("¸ROCm-AMDMIGraphX-46524e8/test/tf/models/depthwise_conv_test.pb000066400000000000000000000004171510465702400240200ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: > 1Const* dtype0*% valueB*€? Œ depthwiseconv1DepthwiseConv2dNative01* T0* data_formatNHWC* strides * dilations * paddingSAME"ROCm-AMDMIGraphX-46524e8/test/tf/models/expanddims_neg_test.pb000066400000000000000000000003151510465702400237610ustar00rootroot00000000000000 6 0 Placeholder* dtype0* shape:  E expanddims_neg/dimConst* valueB: ÿÿÿÿÿÿÿÿÿ* dtype0 H expanddims_neg ExpandDims0expanddims_neg/dim* Tdim0* T0"ROCm-AMDMIGraphX-46524e8/test/tf/models/expanddims_test.pb000066400000000000000000000003051510465702400231270ustar00rootroot00000000000000 6 0 Placeholder* dtype0* shape:  < expanddims_neg/dimConst* dtype0* value B: H expanddims_neg ExpandDims0expanddims_neg/dim* T0* Tdim0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/gather_test.pb000066400000000000000000000004541510465702400222520ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : D gather1/indicesConst* dtype0* valueB" 6 gather1/axisConst* dtype0* value B: s gather1GatherV20gather1/indices gather1/axis* Taxis0* Tindices0* Tparams0* batch_dims"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/identity_test.pb000066400000000000000000000001431510465702400226240ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: identityIdentity0* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/matmul_test.pb000066400000000000000000000002651510465702400222770ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : 2 1 Placeholder* dtype0* shape : F matmul1MatMul01* T0* transpose_a(* transpose_b("¸ROCm-AMDMIGraphX-46524e8/test/tf/models/mean_test.pb000066400000000000000000000005771510465702400217260ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: L mean1/reduction_indicesConst* dtype0* valueB" O mean1Mean0mean1/reduction_indices* T0* Tidx0* keep_dims( L mean2/reduction_indicesConst* dtype0* valueB" O mean2Mean0mean2/reduction_indices* T0* Tidx0* keep_dims("¸ROCm-AMDMIGraphX-46524e8/test/tf/models/mean_test_nhwc.pb000066400000000000000000000003371510465702400227370ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: L mean2/reduction_indicesConst* valueB"* dtype0 O mean2Mean0mean2/reduction_indices* Tidx0* keep_dims(* T0"&ROCm-AMDMIGraphX-46524e8/test/tf/models/mul_test.pb000066400000000000000000000002311510465702400215660ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: : 1 Placeholder* dtype0* shape:  mul1Mul01* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/multi_output_test.pb000066400000000000000000000001641510465702400235500ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape:  reluRelu0* T0  tanhTanh0* T0"&ROCm-AMDMIGraphX-46524e8/test/tf/models/noop_test.pb000066400000000000000000000000241510465702400217440ustar00rootroot00000000000000 noop1NoOp"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/onehot_test.pb000066400000000000000000000005731510465702400222760ustar00rootroot00000000000000 F ConstConst* dtype0*) value B" = onehot1/on_valueConst* dtype0* value B *€? > onehot1/off_valueConst* dtype0* value B * 7 onehot1/depthConst* dtype0* value B: t onehot1OneHotConst onehot1/depthonehot1/on_valueonehot1/off_value* T0* TI0* axis ÿÿÿÿÿÿÿÿÿ"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/pack_test.pb000066400000000000000000000003131510465702400217100ustar00rootroot00000000000000 . 0 Placeholder* dtype0* shape: . 1 Placeholder* dtype0* shape: . 2 Placeholder* dtype0* shape: 4 pack1Pack012* N* T0* axis"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/pack_test_nhwc.pb000066400000000000000000000003571510465702400227370ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: : 1 Placeholder* dtype0* shape: : 2 Placeholder* dtype0* shape: 4 pack1Pack012* N* T0* axis"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/pad_test.pb000066400000000000000000000002621510465702400215410ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : F ConstConst* dtype0*) value B" / pad1Pad0Const* T0* Tpaddings0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/pooling_test.pb000066400000000000000000000004571510465702400224520ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: u avg_poolingAvgPool0* T0* data_formatNHWC* ksize * paddingVALID* strides  u max_poolingMaxPool0* T0* data_formatNHWC* ksize * paddingVALID* strides "¸ROCm-AMDMIGraphX-46524e8/test/tf/models/pow_test.pb000066400000000000000000000002311510465702400215760ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: : 1 Placeholder* dtype0* shape:  pow1Pow01* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/relu6_half_test.pb000066400000000000000000000001351510465702400230230ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape:  relu6Relu60* T0"‚ROCm-AMDMIGraphX-46524e8/test/tf/models/relu6_test.pb000066400000000000000000000001351510465702400220310ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape:  relu6Relu60* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/relu_test.pb000066400000000000000000000001331510465702400217410ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape:  reluRelu0* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/reshape_test.pb000066400000000000000000000002761510465702400224310ustar00rootroot00000000000000 . 0 Placeholder* dtype0* shape: J reshape/shapeConst* dtype0*% valueB" ; reshapeReshape0 reshape/shape* T0* Tshape0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/rsqrt_test.pb000066400000000000000000000001351510465702400221470ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape:  rsqrtRsqrt0* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/shape_test.pb000066400000000000000000000001551510465702400220760ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: * ShapeShape0* T0* out_type0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/sigmoid_test.pb000066400000000000000000000001411510465702400224240ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape:  sigmoidSigmoid0* T0"ÑROCm-AMDMIGraphX-46524e8/test/tf/models/slice_test.pb000066400000000000000000000004031510465702400220710ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : A slice1/beginConst* dtype0* valueB" @ slice1/sizeConst* dtype0* valueB"ÿÿÿÿ C slice1Slice0 slice1/begin slice1/size* Index0* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/softmax_test.pb000066400000000000000000000001311510465702400224510ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape :  softmaxSoftmax0* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/split_test.pb000066400000000000000000000007671510465702400221420ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : / ConstConst* dtype0* value B: 9 split/split_dimConst* dtype0* value B: < splitSplitsplit/split_dim0* T0* num_split 6 concat1/axisConst* dtype0* value B: O concat1ConcatV2splitsplit:1 concat1/axis* N* T0* Tidx0 6 concat2/axisConst* dtype0* value B: Q concat2ConcatV2split:1split:2 concat2/axis* N* T0* Tidx0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/split_test_one_output.pb000066400000000000000000000003431510465702400244110ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : / ConstConst* dtype0* value B: 9 split/split_dimConst* dtype0* value B: < splitSplitsplit/split_dim0* T0* num_split"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/split_test_vector_as_input.pb000066400000000000000000000010321510465702400254100ustar00rootroot00000000000000 2 0 Placeholder* dtype0* shape : > ConstConst* dtype0*! valueB"  9 split/split_dimConst* dtype0* value B: P splitSplitV0Constsplit/split_dim* T0* Tlen0* num_split 6 concat1/axisConst* dtype0* value B: O concat1ConcatV2splitsplit:1 concat1/axis* N* T0* Tidx0 6 concat2/axisConst* dtype0* value B: Q concat2ConcatV2split:1split:2 concat2/axis* N* T0* Tidx0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/sqdiff_test.pb000066400000000000000000000002511510465702400222470ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: : 1 Placeholder* dtype0* shape: * sqdiffSquaredDifference01* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/squeeze_test.pb000066400000000000000000000001651510465702400224600ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: 2 squeezeSqueeze0* T0* squeeze_dims "¸ROCm-AMDMIGraphX-46524e8/test/tf/models/stopgradient_test.pb000066400000000000000000000001531510465702400234770ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: ( stopgradient StopGradient0* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/stridedslice_masks_test.pb000066400000000000000000000010231510465702400246450ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: P stridedslice1/beginConst* dtype0*% valueB" N stridedslice1/endConst* dtype0*% valueB" R stridedslice1/stridesConst* dtype0*% valueB" Ù stridedslice1 StridedSlice0stridedslice1/beginstridedslice1/endstridedslice1/strides* Index0* T0* begin_mask * ellipsis_mask* end_mask* new_axis_mask* shrink_axis_mask"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/stridedslice_test.pb000066400000000000000000000010231510465702400234470ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: P stridedslice1/beginConst* dtype0*% valueB" N stridedslice1/endConst* dtype0*% valueB" R stridedslice1/stridesConst* dtype0*% valueB" Ù stridedslice1 StridedSlice0stridedslice1/beginstridedslice1/endstridedslice1/strides* Index0* T0* begin_mask* ellipsis_mask* end_mask* new_axis_mask* shrink_axis_mask"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/sub_test.pb000066400000000000000000000002311510465702400215620ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: : 1 Placeholder* dtype0* shape:  sub1Sub01* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/tanh_test.pb000066400000000000000000000001331510465702400217240ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape:  tanhTanh0* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/transpose_test.pb000066400000000000000000000003171510465702400230140ustar00rootroot00000000000000 : 0 Placeholder* dtype0* shape: K transpose/permConst* dtype0*% valueB" ? transpose Transpose0transpose/perm* T0* Tperm0"¸ROCm-AMDMIGraphX-46524e8/test/tf/models/variable_batch_test.pb000066400000000000000000000001411510465702400237170ustar00rootroot00000000000000 8 0 Placeholder* dtype0* shape: identityIdentity0* T0"¸ROCm-AMDMIGraphX-46524e8/test/tf/tests/000077500000000000000000000000001510465702400172725ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/tf/tests/add_bcast_test.cpp000066400000000000000000000033331510465702400227430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(add_bcast_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {2, 3}}; auto l0 = mm->add_parameter("0", s0); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {2, 1}}); auto l2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s0.lens()}}), l1); mm->add_instruction(migraphx::make_op("add"), l0, l2); auto prog = optimize_tf("add_bcast_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/add_test.cpp000066400000000000000000000031421510465702400215650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(add_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); mm->add_instruction(migraphx::make_op("add"), l0, l1); auto prog = optimize_tf("add_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/addn_test.cpp000066400000000000000000000041151510465702400217440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(addn_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {2, 3}}); auto l2 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {2, 3}}); auto add1 = mm->add_instruction(migraphx::make_op("add"), l0, l1); mm->add_instruction(migraphx::make_op("add"), add1, l2); auto prog = optimize_tf("addn_test.pb", false); EXPECT(p == prog); } TEST_CASE(addn_single_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3}}); mm->add_instruction(migraphx::make_op("identity"), l0); auto prog = optimize_tf("addn_single_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/addv2_test.cpp000066400000000000000000000031451510465702400220400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(addv2_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); mm->add_instruction(migraphx::make_op("add"), l0, l1); auto prog = optimize_tf("addv2_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/argmax_test.cpp000066400000000000000000000033771510465702400223260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(argmax_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 5, 6, 7}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int32_type}, {2}}); auto ins = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 2}}), l0); auto l1 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), ins); mm->add_return({l1}); auto prog = parse_tf("argmax_test.pb", false, {{"0", {4, 5, 6, 7}}}); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/argmin_test.cpp000066400000000000000000000033501510465702400223130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(argmin_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {3, 4, 5, 6}}); mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int32_type}, {2}}); auto ins = mm->add_instruction(migraphx::make_op("argmin", {{"axis", 2}}), l0); auto l1 = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), ins); mm->add_return({l1}); auto prog = parse_tf("argmin_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/assert_less_equal_test.cpp000066400000000000000000000036701510465702400245610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include // Skip this test for libstdc++ debug for now since it exposes a bug in protobuf #ifndef _GLIBCXX_DEBUG TEST_CASE(assert_less_equal_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {2, 3}}; auto l0 = mm->add_parameter("0", s0); auto l1 = mm->add_parameter("1", s0); migraphx::literal l{migraphx::shape{migraphx::shape::int32_type, {2}}, {0, 1}}; auto l2 = mm->add_literal(l); mm->add_instruction(migraphx::make_op("add"), l0, l1); auto l3 = mm->add_instruction(migraphx::make_op("identity"), l0, l1); mm->add_instruction(migraphx::make_op("identity"), l3, l2); auto prog = optimize_tf("assert_less_equal_test.pb", false); EXPECT(p == prog); } #endif ROCm-AMDMIGraphX-46524e8/test/tf/tests/batchmatmul_test.cpp000066400000000000000000000035541510465702400233450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(batchmatmul_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 8, 4}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 4, 8}}); auto trans_l0 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l0); auto trans_l1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l1); mm->add_instruction(migraphx::make_op("dot"), trans_l0, trans_l1); auto prog = optimize_tf("batchmatmul_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/batchnorm_half_test.cpp000066400000000000000000000056041510465702400240110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(batchnorm_half_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::half_type, {1, 32, 16, 16}}); auto bias = mm->add_parameter("bias", {migraphx::shape::float_type, {32}}); auto mean = mm->add_parameter("mean", {migraphx::shape::float_type, {32}}); auto var = mm->add_parameter("variance", {migraphx::shape::float_type, {32}}); std::vector scale_data(32, 1.0); auto scale = mm->add_literal(migraphx::shape{migraphx::shape::float_type, {32}}, scale_data); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::half_type, {1e-4f}}); auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); auto usq_bias = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), bias); auto usq_mean = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), mean); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), var); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), var_eps); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {usq_scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); auto prog = optimize_tf("batchnorm_half_test.pb", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/batchnorm_test.cpp000066400000000000000000000055741510465702400230250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(batchnorm_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 32, 16, 16}}); auto bias = mm->add_parameter("bias", {migraphx::shape::float_type, {32}}); auto mean = mm->add_parameter("mean", {migraphx::shape::float_type, {32}}); auto var = mm->add_parameter("variance", {migraphx::shape::float_type, {32}}); std::vector scale_data(32, 1.0); auto scale = mm->add_literal(migraphx::shape{migraphx::shape::float_type, {32}}, scale_data); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {1e-4f}}); auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); auto usq_bias = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), bias); auto usq_mean = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), mean); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), var); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), var_eps); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {usq_scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); auto prog = optimize_tf("batchnorm_test.pb", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/batchnormv3_test.cpp000066400000000000000000000056001510465702400232640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(batchnormv3_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 32, 16, 16}}); auto bias = mm->add_parameter("bias", {migraphx::shape::float_type, {32}}); auto mean = mm->add_parameter("mean", {migraphx::shape::float_type, {32}}); auto var = mm->add_parameter("variance", {migraphx::shape::float_type, {32}}); std::vector scale_data(32, 1.0); auto scale = mm->add_literal(migraphx::shape{migraphx::shape::float_type, {32}}, scale_data); auto eps = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {1e-6f}}); auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); auto usq_bias = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), bias); auto usq_mean = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), mean); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), var); auto x_sub_mean = add_common_op(*mm, migraphx::make_op("sub"), {x, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto rsqrt = mm->add_instruction(migraphx::make_op("rsqrt"), var_eps); auto mul0 = add_common_op(*mm, migraphx::make_op("mul"), {usq_scale, rsqrt}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {x_sub_mean, mul0}); add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); auto prog = optimize_tf("batchnormv3_test.pb", true); EXPECT(p.sort() == prog.sort()); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/biasadd_scalar_test.cpp000066400000000000000000000034751510465702400237620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(biasadd_scalar_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1, 1}}; uint64_t axis = 1; auto l0 = mm->add_parameter("0", s0); auto l1 = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {1}, {0}}, {1.0}}); auto l2 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l0->get_shape().lens()}}), l1); mm->add_instruction(migraphx::make_op("add"), l0, l2); auto prog = optimize_tf("biasadd_scalar_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/biasadd_test.cpp000066400000000000000000000034361510465702400224320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(biasadd_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1, 500, 1, 1}}; uint64_t axis = 1; auto l0 = mm->add_parameter("0", s0); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {500}}); auto l2 = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", axis}, {"out_lens", l0->get_shape().lens()}}), l1); mm->add_instruction(migraphx::make_op("add"), l0, l2); auto prog = optimize_tf("biasadd_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/cast_test.cpp000066400000000000000000000031621510465702400217710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(cast_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::int32_type)}}), l0); auto prog = optimize_tf("cast_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/concat_test.cpp000066400000000000000000000035751510465702400223160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(concat_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 7, 3}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {4, 2, 3}}); int axis = 1; // tf uses axis as the third input, and it is in int32 format // add the literal using a vector in order to set stride to 1 (like in tf parser) mm->add_literal(migraphx::shape{migraphx::shape::int32_type}, std::vector{axis}); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1); auto prog = optimize_tf("concat_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/constant_test.cpp000066400000000000000000000027051510465702400226720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(const_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_literal(migraphx::shape{migraphx::shape::float_type}, std::vector{1.0f}); auto prog = optimize_tf("constant_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/conv_add_test.cpp000066400000000000000000000030141510465702400226100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include TEST_CASE(conv_add_test) { migraphx::program p = create_conv(); auto* mm = p.get_main_module(); auto l0 = std::prev(mm->end()); mm->add_instruction(migraphx::make_op("add"), l0, l0); auto prog = optimize_tf("conv_add_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/conv_nchw_test.cpp000066400000000000000000000025771510465702400230340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include TEST_CASE(conv_nchw_test) { migraphx::program p = create_conv(); auto prog = optimize_tf("conv_nchw_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/conv_relu6_test.cpp000066400000000000000000000036751510465702400231320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include TEST_CASE(conv_relu6_test) { migraphx::program p = create_conv(); auto* mm = p.get_main_module(); std::vector input_lens{1, 32, 16, 16}; auto l0 = std::prev(mm->end()); auto min_val = mm->add_literal(0.0f); auto max_val = mm->add_literal(6.0f); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), max_val); mm->add_instruction(migraphx::make_op("clip"), l0, min_val, max_val); auto prog = optimize_tf("conv_relu6_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/conv_relu_test.cpp000066400000000000000000000030131510465702400230260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include TEST_CASE(conv_relu_test) { migraphx::program p = create_conv(); auto* mm = p.get_main_module(); auto l0 = std::prev(mm->end()); mm->add_instruction(migraphx::make_op("relu"), l0); auto prog = optimize_tf("conv_relu_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/conv_test.cpp000066400000000000000000000025641510465702400220110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include TEST_CASE(conv_test) { migraphx::program p = create_conv(); auto prog = optimize_tf("conv_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/depthwise_conv_test.cpp000066400000000000000000000040431510465702400240570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include TEST_CASE(depthwiseconv_test) { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); std::vector weight_data(3 * 3 * 3 * 1); std::fill(weight_data.begin(), weight_data.end(), 1.0f); auto weights = mm->add_literal(migraphx::shape{migraphx::shape::float_type, {3, 3, 3, 1}}, weight_data); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {2, 3, 0, 1}}}), weights); mm->add_instruction( migraphx::make_op( "convolution", {{"padding", {1, 1}}, {"stride", {1, 1}}, {"dilation", {1, 1}}, {"group", 3}}), x, transpose); auto prog = optimize_tf("depthwise_conv_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/expanddims_neg_test.cpp000066400000000000000000000032221510465702400240210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(expanddims_test_neg_dims) { // this check makes sure the pb parses negative dim value correctly migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4}}); mm->add_literal(-1); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 3, 4, 1}}}), l0); auto prog = optimize_tf("expanddims_neg_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/expanddims_test.cpp000066400000000000000000000030741510465702400231750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(expanddims_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 3, 4}}); mm->add_literal(0); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 2, 3, 4}}}), l0); auto prog = optimize_tf("expanddims_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/gather_test.cpp000066400000000000000000000032651510465702400223150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(gather_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 4}}); auto l1 = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::int32_type, {2}}, {1, 1}}); mm->add_literal(1); int axis = 1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), l0, l1); auto prog = optimize_tf("gather_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/identity_test.cpp000066400000000000000000000030141510465702400226640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(identity_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction(migraphx::make_op("identity"), l0); auto prog = optimize_tf("identity_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/main.cpp000066400000000000000000000023771510465702400207330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/matmul_test.cpp000066400000000000000000000035121510465702400223350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(matmul_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {8, 4}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {4, 8}}); auto trans_l0 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l0); auto trans_l1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); mm->add_instruction(migraphx::make_op("dot"), trans_l0, trans_l1); auto prog = optimize_tf("matmul_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/mean_test.cpp000066400000000000000000000034201510465702400217540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(mean_test) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::literal l{migraphx::shape{migraphx::shape::int32_type, {2}}, {2, 3}}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_literal(l); mm->add_literal(l); migraphx::op::reduce_mean op{{2, 3}}; mm->add_instruction(op, l0); auto l3 = mm->add_instruction(op, l0); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2, 3}}}), l3); auto prog = optimize_tf("mean_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/mean_test_nhwc.cpp000066400000000000000000000034701510465702400230000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(mean_test_nhwc) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::literal l{migraphx::shape{migraphx::shape::int32_type, {2}}, {1, 2}}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); auto l1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l0); migraphx::op::reduce_mean op{{1, 2}}; auto l2 = mm->add_instruction(op, l1); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {1, 2}}}), l2); auto prog = optimize_tf("mean_test_nhwc.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/mul_test.cpp000066400000000000000000000031451510465702400216350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(mul_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 1, 1, 16}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 1, 1, 16}}); mm->add_instruction(migraphx::make_op("mul"), l0, l1); auto prog = optimize_tf("mul_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/multi_output_test.cpp000066400000000000000000000033631510465702400236140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(multi_output_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); auto l1 = mm->add_instruction(migraphx::make_op("relu"), l0); auto l2 = mm->add_instruction(migraphx::make_op("tanh"), l0); mm->add_return({l1, l2}); EXPECT(test::throws([&] { parse_tf("multi_output_test.pb", false, {}, {"relu", "relu6"}); })); auto prog = parse_tf("multi_output_test.pb", false, {}, {"relu", "tanh"}); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/noop_test.cpp000066400000000000000000000024761510465702400220210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(noop_test) { migraphx::program p; auto prog = optimize_tf("noop_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/onehot_test.cpp000066400000000000000000000034261510465702400223360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(onehot_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::int32_type, {5}}, {1, 1, 1, 1, 1}}); mm->add_literal(2); mm->add_literal(1.0f); mm->add_literal(0.0f); auto l1 = mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::float_type, {2, 2}}, {1, 0, 0, 1}}); int axis = 0; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), l1, l0); auto prog = optimize_tf("onehot_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/pack_test.cpp000066400000000000000000000042111510465702400217510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(pack_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {2}}); auto l2 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {2}}); std::vector args{l0, l1, l2}; std::vector unsqueezed_args; int64_t axis = 1; std::transform( args.begin(), args.end(), std::back_inserter(unsqueezed_args), [&](migraphx::instruction_ref arg) { return mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {axis}}}), arg); }); mm->add_instruction(migraphx::make_op("concat", {{"axis", static_cast(axis)}}), unsqueezed_args); auto prog = optimize_tf("pack_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/pack_test_nhwc.cpp000066400000000000000000000045261510465702400230010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(pack_test_nhwc) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); auto l2 = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {1, 2, 1, 1}}); std::vector args{l0, l1, l2}; std::vector unsqueezed_args; int64_t nchw_axis = 1; std::transform(args.begin(), args.end(), std::back_inserter(unsqueezed_args), [&](migraphx::instruction_ref arg) { return mm->add_instruction( migraphx::make_op("unsqueeze", {{"axes", {nchw_axis}}}), arg); }); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", nchw_axis}}), unsqueezed_args); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 2}}}), concat); auto prog = optimize_tf("pack_test_nhwc.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/pad_test.cpp000066400000000000000000000032671510465702400216110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(pad_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {2, 4}}); std::vector pad_literals{1, 1, 2, 2}; std::vector pads{1, 2, 1, 2}; mm->add_literal(migraphx::shape{migraphx::shape::int32_type, {2, 2}}, pad_literals); mm->add_instruction(migraphx::make_op("pad", {{"pads", pads}}), l0); auto prog = optimize_tf("pad_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/pooling_test.cpp000066400000000000000000000034751510465702400225150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(pooling_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); migraphx::op::pooling avg_pool_op{migraphx::op::pooling_mode::average}; migraphx::op::pooling max_pool_op{migraphx::op::pooling_mode::max}; avg_pool_op.stride = {2, 2}; max_pool_op.stride = {2, 2}; avg_pool_op.lengths = {2, 2}; max_pool_op.lengths = {2, 2}; mm->add_instruction(avg_pool_op, l0); mm->add_instruction(max_pool_op, l0); auto prog = optimize_tf("pooling_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/pow_test.cpp000066400000000000000000000031421510465702400216420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(pow_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); mm->add_instruction(migraphx::make_op("pow"), l0, l1); auto prog = optimize_tf("pow_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/relu6_half_test.cpp000066400000000000000000000041201510465702400230610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(relu6_half_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 3, 16, 16}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::half_type, input_lens}); auto min_val = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {0.0f}}); auto max_val = mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::half_type}, {6.0f}}); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), max_val); mm->add_instruction(migraphx::make_op("clip"), l0, min_val, max_val); auto prog = optimize_tf("relu6_half_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/relu6_test.cpp000066400000000000000000000036701510465702400221000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(relu6_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 3, 16, 16}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, input_lens}); auto min_val = mm->add_literal(0.0f); auto max_val = mm->add_literal(6.0f); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), max_val); mm->add_instruction(migraphx::make_op("clip"), l0, min_val, max_val); auto prog = optimize_tf("relu6_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/relu_test.cpp000066400000000000000000000030001510465702400217750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(relu_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction(migraphx::make_op("relu"), l0); auto prog = optimize_tf("relu_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/reshape_test.cpp000066400000000000000000000033261510465702400224700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(reshape_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {16}}); migraphx::shape s0{migraphx::shape::int32_type, {4}}; // in tf, the second arg is a literal that contains new dimensions mm->add_literal(migraphx::literal{s0, {1, 1, 1, 16}}); mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 1, 1, 16}}}), l0); auto prog = optimize_tf("reshape_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/rsqrt_test.cpp000066400000000000000000000030031510465702400222040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(rsqrt_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction(migraphx::make_op("rsqrt"), l0); auto prog = optimize_tf("rsqrt_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/shape_test.cpp000066400000000000000000000030631510465702400221370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(shape_test) { migraphx::program p; auto* mm = p.get_main_module(); mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::int32_type, {4}}, {1, 3, 16, 16}}); auto prog = optimize_tf("shape_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/sigmoid_test.cpp000066400000000000000000000030111510465702400224630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(sigmoid_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction(migraphx::make_op("sigmoid"), l0); auto prog = optimize_tf("sigmoid_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/slice_test.cpp000066400000000000000000000034241510465702400221370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(slice_test) { migraphx::program p; auto* mm = p.get_main_module(); std::size_t num_axes = 2; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {5, 10}}); migraphx::shape s0{migraphx::shape::int32_type, {num_axes}}; mm->add_literal(migraphx::literal{s0, {1, 0}}); mm->add_literal(migraphx::literal{s0, {2, -1}}); mm->add_instruction( migraphx::make_op("slice", {{"starts", {1, 0}}, {"ends", {3, 10}}, {"axes", {0, 1}}}), l0); auto prog = optimize_tf("slice_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/softmax_test.cpp000066400000000000000000000030201510465702400225110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(softmax_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3}}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", 1}}), l0); auto prog = optimize_tf("softmax_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/split_test.cpp000066400000000000000000000043331510465702400221730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(split_test) { migraphx::program p; auto* mm = p.get_main_module(); std::vector axes{0, 1}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {5, 30}}); mm->add_literal(3); // num_splits mm->add_literal(1); // split axis mm->add_literal(1); // concat axis mm->add_literal(1); // concat axis auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", axes}, {"starts", {0, 0}}, {"ends", {5, 10}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", axes}, {"starts", {0, 10}}, {"ends", {5, 20}}}), l0); auto l3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", axes}, {"starts", {0, 20}}, {"ends", {5, 30}}}), l0); auto l4 = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l1, l2); auto l5 = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l2, l3); mm->add_return({l4, l5}); auto prog = parse_tf("split_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/split_test_one_output.cpp000066400000000000000000000032021510465702400244460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(split_test_one_output) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {5, 30}}); mm->add_literal(1); // num_splits mm->add_literal(1); // split axis auto l1 = mm->add_instruction(migraphx::make_op("identity"), l0); mm->add_return({l1}); auto prog = parse_tf("split_test_one_output.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/split_test_vector_as_input.cpp000066400000000000000000000045271510465702400254640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(split_test_vector_as_input) { migraphx::program p; auto* mm = p.get_main_module(); std::vector axes{0, 1}; auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {5, 30}}); // split sizes mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::int32_type, {3}}, {4, 15, 11}}); mm->add_literal(1); // split axis mm->add_literal(1); // concat axis mm->add_literal(1); // concat axis auto l1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", axes}, {"starts", {0, 0}}, {"ends", {5, 4}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", axes}, {"starts", {0, 4}}, {"ends", {5, 19}}}), l0); auto l3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", axes}, {"starts", {0, 19}}, {"ends", {5, 30}}}), l0); auto l4 = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l1, l2); auto l5 = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), l2, l3); mm->add_return({l4, l5}); auto prog = parse_tf("split_test_vector_as_input.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/sqdiff_test.cpp000066400000000000000000000031531510465702400223130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(sqdiff_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); mm->add_instruction(migraphx::make_op("sqdiff"), l0, l1); auto prog = optimize_tf("sqdiff_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/squeeze_test.cpp000066400000000000000000000030331510465702400225150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(squeeze_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 1}}); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0, 3}}}), l0); auto prog = optimize_tf("squeeze_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/stopgradient_test.cpp000066400000000000000000000030241510465702400235370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(stopgradient_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction(migraphx::make_op("identity"), l0); auto prog = optimize_tf("stopgradient_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/stridedslice_masks_test.cpp000066400000000000000000000044741510465702400247220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(stridedslice_masks_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 10, 3, 3}}); // add literals for starts, ends, and strides in tf (NHWC format) mm->add_literal(migraphx::shape{migraphx::shape::int32_type, {4}}, std::vector{0, 1, 1, 0}); mm->add_literal(migraphx::shape{migraphx::shape::int32_type, {4}}, std::vector{0, 0, 0, 0}); mm->add_literal(migraphx::shape{migraphx::shape::int32_type, {4}}, std::vector{1, 1, 1, 1}); auto l1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op( "slice", {{"starts", {0, 1, 1, 0}}, {"ends", {1, 3, 3, 10}}, {"axes", {0, 1, 2, 3}}}), l1); auto l3 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), l2); mm->add_return({l3}); auto prog = parse_tf("stridedslice_masks_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/stridedslice_test.cpp000066400000000000000000000035431510465702400235200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(stridedslice_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 10, 1, 1}}); auto l1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l0); auto l2 = mm->add_instruction( migraphx::make_op( "slice", {{"starts", {0, 0, 0, 0}}, {"ends", {1, 1, 1, 5}}, {"axes", {0, 1, 2, 3}}}), l1); auto shrink_axis = 1; mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {shrink_axis}}}), l2); auto prog = optimize_tf("stridedslice_test.pb", true); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/sub_test.cpp000066400000000000000000000032041510465702400216250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(sub_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); auto l1 = mm->add_parameter("1", migraphx::shape{migraphx::shape::float_type, {1, 2, 2, 3}}); auto l2 = mm->add_instruction(migraphx::make_op("sub"), l0, l1); mm->add_return({l2}); auto prog = parse_tf("sub_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/tanh_test.cpp000066400000000000000000000030421510465702400217660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(tanh_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); auto l1 = mm->add_instruction(migraphx::make_op("tanh"), l0); mm->add_return({l1}); auto prog = parse_tf("tanh_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/transpose_test.cpp000066400000000000000000000032441510465702400230560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(transpose_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); migraphx::shape s0{migraphx::shape::int32_type, {4}}; mm->add_literal(migraphx::literal{s0, {0, 2, 3, 1}}); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l0); auto prog = optimize_tf("transpose_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/tf/tests/variable_batch_test.cpp000066400000000000000000000030301510465702400237570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include TEST_CASE(variable_batch_test) { migraphx::program p; auto* mm = p.get_main_module(); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); mm->add_instruction(migraphx::make_op("identity"), l0); auto prog = optimize_tf("variable_batch_test.pb", false); EXPECT(p == prog); } ROCm-AMDMIGraphX-46524e8/test/transform_view.cpp000066400000000000000000000214411510465702400212720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include TEST_CASE(basic_transform) { std::vector vec = {1, 2, 3, 4, 5}; auto view = migraphx::views::transform(vec, [](int x) { return x * x; }); auto it = view.begin(); EXPECT(it[0] == 1); EXPECT(it[1] == 4); EXPECT(it[2] == 9); EXPECT(it[3] == 16); EXPECT(it[4] == 25); EXPECT(*it == 1); it += 1; EXPECT(*it == 4); it += 1; EXPECT(*it == 9); it += 1; EXPECT(*it == 16); it += 1; EXPECT(*it == 25); auto it2 = view.end(); it2 -= 1; EXPECT(*it2 == 25); it2 -= 1; EXPECT(*it2 == 16); it2 -= 1; EXPECT(*it2 == 9); it2 -= 1; EXPECT(*it2 == 4); it2 -= 1; EXPECT(*it2 == 1); } TEST_CASE(transform_const) { std::vector vec = {1, 2, 3, 4, 5}; const auto& cvec = vec; auto view = migraphx::views::transform(cvec, [](int x) { return x * x; }); auto it = view.begin(); EXPECT(it[0] == 1); EXPECT(it[1] == 4); EXPECT(it[2] == 9); EXPECT(it[3] == 16); EXPECT(it[4] == 25); EXPECT(*it == 1); it += 1; EXPECT(*it == 4); it += 1; EXPECT(*it == 9); it += 1; EXPECT(*it == 16); it += 1; EXPECT(*it == 25); auto it2 = view.end(); it2 -= 1; EXPECT(*it2 == 25); it2 -= 1; EXPECT(*it2 == 16); it2 -= 1; EXPECT(*it2 == 9); it2 -= 1; EXPECT(*it2 == 4); it2 -= 1; EXPECT(*it2 == 1); } TEST_CASE(transform_with_reference) { std::vector vec = {1, 2, 3, 4, 5}; // cppcheck-suppress constParameterReference auto view = migraphx::views::transform(vec, [](int& x) -> int& { return x; }); auto it = view.begin(); EXPECT(*it == 1); ++it; EXPECT(*it == 2); ++it; EXPECT(*it == 3); ++it; EXPECT(*it == 4); ++it; EXPECT(*it == 5); auto it2 = view.end(); --it2; EXPECT(*it2 == 5); --it2; EXPECT(*it2 == 4); --it2; EXPECT(*it2 == 3); --it2; EXPECT(*it2 == 2); --it2; EXPECT(*it2 == 1); // Modify the original vector through the view *view.begin() = 10; EXPECT(vec[0] == 10); } TEST_CASE(empty_range) { std::vector vec; auto view = migraphx::views::transform(vec, [](int x) { return x * x; }); EXPECT(view.begin() == view.end()); } TEST_CASE(non_random_access_iterator) { std::list lst = {1, 2, 3, 4, 5}; auto view = migraphx::views::transform(lst, [](int x) { return x * 2; }); auto it = view.begin(); EXPECT(*it == 2); ++it; EXPECT(*it == 4); ++it; EXPECT(*it == 6); ++it; EXPECT(*it == 8); ++it; EXPECT(*it == 10); } TEST_CASE(non_random_access_iterator_with_reference) { std::list lst = {1, 2, 3, 4, 5}; // cppcheck-suppress constParameterReference auto view = migraphx::views::transform(lst, [](int& x) -> int& { return x; }); auto it = view.begin(); EXPECT(*it == 1); ++it; EXPECT(*it == 2); ++it; EXPECT(*it == 3); ++it; EXPECT(*it == 4); ++it; EXPECT(*it == 5); // Modify the original list through the view *view.begin() = 10; EXPECT(lst.front() == 10); } TEST_CASE(forward_iterator) { std::forward_list flst = {1, 2, 3, 4, 5}; auto view = migraphx::views::transform(flst, [](int x) { return x + 1; }); auto it = view.begin(); EXPECT(*it == 2); ++it; EXPECT(*it == 3); ++it; EXPECT(*it == 4); ++it; EXPECT(*it == 5); ++it; EXPECT(*it == 6); auto it2 = view.begin(); std::advance(it2, 3); EXPECT(*it2 == 5); } TEST_CASE(forward_iterator_with_reference) { std::forward_list flst = {1, 2, 3, 4, 5}; // cppcheck-suppress constParameterReference auto view = migraphx::views::transform(flst, [](int& x) -> int& { return x; }); auto it = view.begin(); EXPECT(*it == 1); ++it; EXPECT(*it == 2); ++it; EXPECT(*it == 3); ++it; EXPECT(*it == 4); ++it; EXPECT(*it == 5); // Modify the original forward_list through the view *view.begin() = 10; EXPECT(flst.front() == 10); } TEST_CASE(transform_view_element_comparison) { std::vector vec1 = {1, 2, 3, 4, 5}; std::vector vec2 = {1, 2, 3, 4, 5}; std::vector vec3 = {5, 4, 3, 2, 1}; auto squared = [](int x) { return x * x; }; auto view1 = migraphx::views::transform(vec1, squared); auto view2 = migraphx::views::transform(vec2, squared); auto view3 = migraphx::views::transform(vec3, squared); EXPECT(view1 == view2); // Same elements EXPECT(view1 != view3); // Different elements EXPECT(view1 < view3); // Lexicographical comparison EXPECT(view1 <= view3); EXPECT(view3 > view1); EXPECT(view3 >= view1); } TEST_CASE(transform_view_element_comparison_diff_view) { std::vector vec1 = {1, 2, 3, 4, 5}; std::vector vec2 = {2, 3, 4, 5, 6}; auto view1 = migraphx::views::transform(vec1, [](int x) { return x * x; }); auto view2 = migraphx::views::transform(vec2, [](int x) { return (x - 1) * (x - 1); }); auto view3 = migraphx::views::transform(vec2, [](int x) { return x * x; }); EXPECT(view1 == view2); EXPECT(view1 != view3); EXPECT(view2 != view3); EXPECT(view1 < view3); EXPECT(view2 < view3); EXPECT(view1 <= view3); EXPECT(view2 <= view3); EXPECT(view3 > view1); EXPECT(view3 > view2); EXPECT(view3 >= view1); EXPECT(view3 >= view2); } struct non_comparable { int value; friend bool operator==(const non_comparable& lhs, const non_comparable& rhs) { return lhs.value == rhs.value; } friend bool operator!=(const non_comparable& lhs, const non_comparable& rhs) { return not(lhs == rhs); } }; TEST_CASE(transform_view_non_comparable_elements) { std::vector vec1 = {1, 2, 3}; std::vector vec2 = {2, 3, 4}; auto as_non_comparable = [](int x) -> non_comparable { return {x}; }; auto view = migraphx::views::transform(vec1, as_non_comparable); auto view2 = migraphx::views::transform(vec1, as_non_comparable); auto view3 = migraphx::views::transform(vec2, as_non_comparable); EXPECT(view == view2); EXPECT(view != view3); } TEST_CASE(operator_arrow_in_loop_reference) { struct a { int val; }; std::vector
data{{1}, {2}, {3}}; auto view = migraphx::views::transform(data, [](const a& t) -> const a& { return t; }); int sum = 0; for(auto it = view.begin(); it != view.end(); ++it) { sum += it->val; } EXPECT(sum == 6); } TEST_CASE(operator_arrow_in_loop_value) { struct a { int val; }; std::vector data{{1}, {2}, {3}}; auto view = migraphx::views::transform(data, [](const a& t) { return a{t.val * 2}; }); std::vector out; for(auto it = view.begin(); it != view.end(); ++it) { out.push_back(it->val); } EXPECT(out.size() == 3); EXPECT(out[0] == 2); EXPECT(out[1] == 4); EXPECT(out[2] == 6); } TEST_CASE(transform_view_mutate_member) { struct a { int val; }; std::vector data{{1}, {2}, {3}}; // cppcheck-suppress constParameterReference auto view = migraphx::views::transform(data, [](auto& t) -> auto& { return t.val; }); std::for_each(view.begin(), view.end(), [](auto& i) { i++; }); std::vector edata{{2}, {3}, {4}}; EXPECT(std::equal(data.begin(), data.end(), edata.begin(), [](const a& lhs, const a& rhs) { return lhs.val == rhs.val; })); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/type_name.cpp000066400000000000000000000033331510465702400202060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "test.hpp" struct global_class { struct inner_class { }; }; namespace foo { struct ns_class { struct inner_class { }; }; } // namespace foo int main() { EXPECT(migraphx::get_type_name() == "global_class"); EXPECT(migraphx::get_type_name() == "global_class::inner_class"); EXPECT(migraphx::get_type_name() == "foo::ns_class"); EXPECT(migraphx::get_type_name() == "foo::ns_class::inner_class"); } ROCm-AMDMIGraphX-46524e8/test/unfold_test.cpp000066400000000000000000000233451510465702400205600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #include #include #include #include #include using migraphx::unfold; TEST_CASE(test_basic_sequence) { auto f = [](int x) { return x; }; auto g = [](int x) -> std::optional { return (x < 5) ? std::optional{x + 1} : std::nullopt; }; auto rng = unfold(1, f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got.size() == 5); EXPECT(got == std::vector{1, 2, 3, 4, 5}); } TEST_CASE(test_empty_sequence) { auto f = [](int x) { return x; }; auto g = [](auto) { return std::nullopt; }; auto rng = unfold(std::nullopt, f, g); auto it = rng.begin(); EXPECT(bool{it == rng.end()}); } TEST_CASE(test_one_element_sequence) { auto f = [](int x) { return x * x; }; auto g = [](int) -> std::optional { return std::nullopt; }; auto rng = unfold(7, f, g); auto it = rng.begin(); EXPECT(bool{it != rng.end()}); EXPECT(*it == 49); ++it; EXPECT(bool{it == rng.end()}); } TEST_CASE(test_pair_state) { using state = std::pair; auto f = [](const state& s) { return s.first + s.second; }; auto g = [](const state& s) -> std::optional { if(s.first > 0) return state{s.first - 1, s.second + 2}; return std::nullopt; }; auto rng = unfold(state{3, 10}, f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{13, 14, 15, 16}); } TEST_CASE(test_string_accumulation) { auto f = [](const std::string& s) { return s; }; auto g = [](const std::string& s) -> std::optional { if(s.size() < 4) return s + "a"; return std::nullopt; }; auto rng = unfold(std::string("b"), f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{"b", "ba", "baa", "baaa"}); } TEST_CASE(test_string_accumulation_auto) { auto g = [](const std::string& s) -> std::optional { if(s.size() < 4) return s + "a"; return std::nullopt; }; auto rng = unfold(std::string("b"), g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{"b", "ba", "baa", "baaa"}); } TEST_CASE(test_iterator_semantics) { auto f = [](int x) { return x * 2; }; auto g = [](int x) -> std::optional { return (x < 4) ? std::make_optional(x + 1) : std::nullopt; }; auto rng = unfold(2, f, g); auto it = rng.begin(); EXPECT(bool{it != rng.end()}); EXPECT(*it == 4); auto it2 = it++; EXPECT(*it2 == 4); EXPECT(*it == 6); ++it; EXPECT(*it == 8); ++it; EXPECT(bool{it == rng.end()}); } TEST_CASE(test_multiple_iterators) { auto f = [](int x) { return x; }; auto g = [](int x) -> std::optional { return (x < 3) ? std::make_optional(x + 1) : std::nullopt; }; auto rng = unfold(1, f, g); auto it1 = rng.begin(); auto it2 = it1; EXPECT(*it1 == 1); ++it1; EXPECT(*it1 == 2); EXPECT(*it2 == 1); ++it2; ++it2; EXPECT(*it2 == 3); ++it2; EXPECT(bool{it2 == rng.end()}); } TEST_CASE(test_large_sequence) { auto f = [](int x) { return x; }; auto g = [](int x) -> std::optional { return (x < 10000) ? std::make_optional(x + 1) : std::nullopt; }; auto rng = unfold(0, f, g); int count = std::distance(rng.begin(), rng.end()); EXPECT(count == 10001); } TEST_CASE(test_squares) { auto f = [](int x) { return x * x; }; auto g = [](int x) -> std::optional { return (x < 6) ? std::make_optional(x + 1) : std::nullopt; }; auto rng = unfold(1, f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{1, 4, 9, 16, 25, 36}); } TEST_CASE(test_move_only_function) { auto ptr = std::make_unique(10); auto f = [p = std::move(ptr)](int x) { return x + *p; }; auto g = [](int x) -> std::optional { return (x < 3) ? std::make_optional(x + 1) : std::nullopt; }; auto rng = unfold(1, std::move(f), g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{11, 12, 13}); } TEST_CASE(test_std_accumulate) { auto f = [](int x) { return x; }; auto g = [](int x) -> std::optional { return (x < 5) ? std::make_optional(x + 1) : std::nullopt; }; auto rng = unfold(1, f, g); int sum = std::accumulate(rng.begin(), rng.end(), 0); EXPECT(sum == 15); } TEST_CASE(test_std_copy_to_list) { auto f = [](int x) { return x * 2; }; auto g = [](int x) -> std::optional { return (x < 4) ? std::make_optional(x + 1) : std::nullopt; }; auto rng = unfold(1, f, g); std::list result; std::copy(rng.begin(), rng.end(), std::back_inserter(result)); EXPECT(result == std::list{2, 4, 6, 8}); } TEST_CASE(test_pointer_state) { int arr[] = {1, 2, 3, 4, 5, 0}; auto f = [](const int* p) { return *p; }; auto g = [](const int* p) -> std::optional { return (*(p + 1) == 0) ? std::nullopt : std::optional{p + 1}; }; auto rng = unfold(static_cast(arr), f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{1, 2, 3, 4, 5}); } TEST_CASE(test_infinite_sequence) { auto f = [](int x) { return x; }; auto g = [](int x) -> std::optional { return x + 1; }; auto rng = unfold(0, f, g); auto it = rng.begin(); std::advance(it, 1000); EXPECT(*it == 1000); } TEST_CASE(test_floating_point) { auto f = [](double x) { return x; }; auto g = [](double x) -> std::optional { return (x < 1.5) ? std::optional{x + 0.5} : std::nullopt; }; auto rng = unfold(0.0, f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got.size() == 4); EXPECT(test::within_abs(got[0], 0.0) and test::within_abs(got[1], 0.5) and test::within_abs(got[2], 1.0) and test::within_abs(got[3], 1.5)); } struct custom_struct_state { int v; friend bool operator==(const custom_struct_state& x, const custom_struct_state& y) { return x.v == y.v; } friend bool operator!=(const custom_struct_state& x, const custom_struct_state& y) { return x.v != y.v; } }; TEST_CASE(test_custom_struct_state) { auto f = [](const custom_struct_state& c) { return c.v * 3; }; auto g = [](const custom_struct_state& c) -> std::optional { return (c.v < 4) ? std::make_optional(custom_struct_state{c.v + 1}) : std::nullopt; }; auto rng = unfold(custom_struct_state{1}, f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{3, 6, 9, 12}); } TEST_CASE(test_string_state) { auto f = [](const std::string& s) { return s.size(); }; auto g = [](const std::string& s) -> std::optional { if(s.size() < 3) return s + "!"; return std::nullopt; }; auto rng = unfold(std::string("A"), f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{1, 2, 3}); } TEST_CASE(test_even_numbers) { auto f = [](int x) { return x; }; auto g = [](int x) -> std::optional { if(x < 10) return x + 2; return std::nullopt; }; auto rng = unfold(0, f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); EXPECT(got == std::vector{0, 2, 4, 6, 8, 10}); } TEST_CASE(test_fibonacci_sequence) { using state = std::pair; auto f = [](const state& s) { return s.first; }; auto g = [](const state& s) -> std::optional { if(s.first > 100) return std::nullopt; return state{s.second, s.first + s.second}; }; auto rng = unfold(state{0, 1}, f, g); std::vector got; std::copy(rng.begin(), rng.end(), std::back_inserter(got)); std::vector expected{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144}; got.resize(expected.size()); EXPECT(got == expected); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/utility_operators.cpp000066400000000000000000000202571510465702400220320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. * */ #include #include #ifdef CPPCHECK #define EXPECT_TOTALLY_ORDERED(...) #else // NOLINTNEXTLINE #define EXPECT_TOTALLY_ORDERED_IMPL(x, y) \ EXPECT((x <= y) or (x >= y)); \ EXPECT((x < y) or (x > y) or (x == y)); \ EXPECT(((x < y) or (x > y)) == (x != y)); \ EXPECT((x < y) == (y > x)); \ EXPECT((x <= y) == (y >= x)); \ EXPECT((x < y) != (x >= y)); \ EXPECT((x > y) != (x <= y)); \ EXPECT((x == y) != (x != y)) // NOLINTNEXTLINE #define EXPECT_TOTALLY_ORDERED(x, y) \ EXPECT_TOTALLY_ORDERED_IMPL(x, y); \ EXPECT_TOTALLY_ORDERED_IMPL(y, x) #endif struct custom_compare_any : migraphx::totally_ordered { int x; constexpr custom_compare_any(int px) : x(px) {} constexpr bool operator==(const custom_compare_any& rhs) const { return x == rhs.x; } template constexpr auto operator==(const T& rhs) const -> decltype(std::declval() == rhs) { return x == rhs; } constexpr bool operator<(const custom_compare_any& rhs) const { return x < rhs.x; } template constexpr auto operator<(const T& rhs) const -> decltype(std::declval() < rhs) { return x < rhs; } template constexpr auto operator>(const T& rhs) const -> decltype(std::declval() > rhs) { return x > rhs; } template friend Stream& operator<<(Stream& os, const custom_compare_any& self) { return os << self.x; } }; TEST_CASE(compare_any) { custom_compare_any x{1}; custom_compare_any y{2}; EXPECT(1 == x); EXPECT(x != y); EXPECT(y > x); EXPECT(x == x); EXPECT(x < 2); EXPECT(y > 1); EXPECT_TOTALLY_ORDERED(x, x); EXPECT_TOTALLY_ORDERED(x, y); EXPECT_TOTALLY_ORDERED(x, 1); } struct custom_compare_equivalent : migraphx::totally_ordered, migraphx::equivalence { int x; constexpr custom_compare_equivalent(int px) : x(px) {} constexpr bool operator<(const custom_compare_equivalent& rhs) const { return x < rhs.x; } template constexpr auto operator<(const T& rhs) const -> decltype(std::declval() < rhs) { return x < rhs; } template constexpr auto operator>(const T& rhs) const -> decltype(std::declval() > rhs) { return x > rhs; } template friend Stream& operator<<(Stream& os, const custom_compare_equivalent& self) { return os << self.x; } }; TEST_CASE(compare_equivalent) { custom_compare_equivalent x{1}; custom_compare_equivalent y{2}; EXPECT(1 == x); EXPECT(x != y); EXPECT(y > x); EXPECT(x == x); EXPECT(x < 2); EXPECT(y > 1); EXPECT_TOTALLY_ORDERED(x, x); EXPECT_TOTALLY_ORDERED(x, y); EXPECT_TOTALLY_ORDERED(x, 1); } struct custom_compare_adl : migraphx::totally_ordered { int x; constexpr custom_compare_adl(int px) : x(px) {} friend constexpr bool operator==(const custom_compare_adl& lhs, const custom_compare_adl& rhs) { return lhs.x == rhs.x; } template {})> friend constexpr auto operator==(const custom_compare_adl& lhs, const T& rhs) -> decltype(std::declval() == rhs) { return lhs.x == rhs; } friend constexpr bool operator<(const custom_compare_adl& lhs, const custom_compare_adl& rhs) { return lhs.x < rhs.x; } template {})> friend constexpr auto operator<(const custom_compare_adl& lhs, const T& rhs) -> decltype(std::declval() < rhs) { return lhs.x < rhs; } template {})> friend constexpr auto operator>(const custom_compare_adl& lhs, const T& rhs) -> decltype(std::declval() > rhs) { return lhs.x > rhs; } template friend Stream& operator<<(Stream& os, const custom_compare_adl& self) { return os << self.x; } }; TEST_CASE(compare_adl) { custom_compare_adl x{1}; custom_compare_adl y{2}; EXPECT(1 == x); EXPECT(x != y); EXPECT(x == x); EXPECT(y > x); EXPECT(x < 2); EXPECT(y > 1); EXPECT_TOTALLY_ORDERED(x, x); EXPECT_TOTALLY_ORDERED(x, y); EXPECT_TOTALLY_ORDERED(x, 1); } template struct custom_compare_template1 : migraphx::totally_ordered> { T x; constexpr custom_compare_template1(T px) : x(px) {} constexpr bool operator==(const custom_compare_template1& rhs) const { return x == rhs.x; } template constexpr auto operator==(const custom_compare_template1& rhs) const { return x == rhs.x; } constexpr bool operator<(const custom_compare_template1& rhs) const { return x < rhs.x; } template constexpr auto operator<(const custom_compare_template1& rhs) const { return x < rhs.x; } template friend Stream& operator<<(Stream& os, const custom_compare_template1& self) { return os << self.x; } }; TEST_CASE(compare_template1) { custom_compare_template1 x{1}; custom_compare_template1 y{2}; custom_compare_template1 z{2}; EXPECT(x != y); EXPECT(x == x); EXPECT(y > x); EXPECT(x < y); EXPECT(z == z); EXPECT(z != x); EXPECT(z == y); EXPECT(z > x); EXPECT(x < z); EXPECT_TOTALLY_ORDERED(x, x); EXPECT_TOTALLY_ORDERED(x, y); EXPECT_TOTALLY_ORDERED(x, z); EXPECT_TOTALLY_ORDERED(y, z); } template struct custom_compare_template2 : migraphx::totally_ordered> { T x; constexpr custom_compare_template2(T px) : x(px) {} template constexpr auto operator==(const custom_compare_template2& rhs) const { return x == rhs.x; } template constexpr auto operator<(const custom_compare_template2& rhs) const { return x < rhs.x; } template friend Stream& operator<<(Stream& os, const custom_compare_template2& self) { return os << self.x; } }; TEST_CASE(compare_template2) { custom_compare_template2 x{1}; custom_compare_template2 y{2}; custom_compare_template2 z{2}; EXPECT(x != y); EXPECT(x == x); EXPECT(y > x); EXPECT(x < y); EXPECT(z == z); EXPECT(z != x); EXPECT(z == y); EXPECT(z > x); EXPECT(x < z); EXPECT_TOTALLY_ORDERED(x, x); EXPECT_TOTALLY_ORDERED(x, y); EXPECT_TOTALLY_ORDERED(x, z); EXPECT_TOTALLY_ORDERED(y, z); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/validate.cpp000066400000000000000000000054771510465702400200310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include TEST_CASE(simple_test) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); mm->add_instruction(migraphx::make_op("add"), one, two); EXPECT(mm->validate() == mm->end()); auto result = p.eval({}); EXPECT(result.back() == migraphx::literal{3}); EXPECT(result.back() != migraphx::literal{4}); } TEST_CASE(out_of_order) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto ins = mm->add_instruction(migraphx::make_op("add"), one, two); mm->move_instruction(two, mm->end()); EXPECT(p.validate() == ins); } TEST_CASE(incomplete_args) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto ins = mm->add_instruction(migraphx::make_op("add"), one, two); ins->clear_arguments(); EXPECT(p.validate() == ins); } MIGRAPHX_ROB(access_ins_arguments, std::vector, migraphx::instruction, arguments) TEST_CASE(invalid_args) { migraphx::program p; auto* mm = p.get_main_module(); auto one = mm->add_literal(1); auto two = mm->add_literal(2); auto ins = mm->add_instruction(migraphx::make_op("add"), one, two); access_ins_arguments(*ins).clear(); EXPECT(mm->validate() == mm->begin()); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/value_test.cpp000066400000000000000000000707011510465702400204030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include enum class enum_type { a, b, c }; TEST_CASE(value_default_construct) { migraphx::value v; EXPECT(v.is_null()); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_null) { migraphx::value v = nullptr; EXPECT(v.is_null()); EXPECT(v.get_key().empty()); } TEST_CASE(value_assign_null) { migraphx::value v; v = nullptr; EXPECT(v.is_null()); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_int1) { EXPECT(migraphx::value(1).is_int64()); migraphx::value v(1); EXPECT(v.is_int64()); EXPECT(v.get_int64() == 1); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_int2) { migraphx::value v = 1; EXPECT(v.is_int64()); EXPECT(v.get_int64() == 1); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_string) { migraphx::value v = "one"; EXPECT(v.is_string()); EXPECT(v.get_string() == "one"); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_key_string_literal_pair) { // Use parens instead {} to construct to test the key-pair constructor migraphx::value v("key", "one"); EXPECT(v.is_string()); EXPECT(v.get_string() == "one"); EXPECT(v.get_key() == "key"); } TEST_CASE(value_construct_float) { migraphx::value v = 1.0; EXPECT(v.is_float()); EXPECT(migraphx::float_equal(v.get_float(), 1.0)); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_bool) { migraphx::value v = true; EXPECT(v.is_bool()); EXPECT(v.get_bool() == true); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_enum1) { migraphx::value v = enum_type::a; EXPECT(v.is_int64()); EXPECT(v.get_int64() == static_cast(enum_type::a)); EXPECT(v.to() == enum_type::a); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_enum2) { migraphx::value v = enum_type::b; EXPECT(v.is_int64()); EXPECT(v.get_int64() == static_cast(enum_type::b)); EXPECT(v.to() == enum_type::b); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_enum3) { migraphx::value v = enum_type::c; EXPECT(v.is_int64()); EXPECT(v.get_int64() == static_cast(enum_type::c)); EXPECT(v.to() == enum_type::c); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_empty_object) { migraphx::value v = migraphx::value::object{}; EXPECT(v.is_object()); EXPECT(v.get_object().empty()); EXPECT(v.get_key().empty()); } TEST_CASE(value_construct_empty_array) { migraphx::value v = migraphx::value::array{}; EXPECT(v.is_array()); EXPECT(v.get_array().empty()); EXPECT(v.get_key().empty()); } TEST_CASE(value_assign_int) { migraphx::value v; v = 0; EXPECT(v.is_int64()); EXPECT(v.get_int64() == 0); EXPECT(v.get_key().empty()); } TEST_CASE(value_copy_construct) { migraphx::value v1(1); migraphx::value v2 = v1; // NOLINT EXPECT(v1 == v2); } TEST_CASE(value_copy_assign) { migraphx::value v1(1); migraphx::value v2; v2 = v1; EXPECT(v1 == v2); } TEST_CASE(value_reassign) { migraphx::value v1(1); migraphx::value v2 = v1; v1 = 2; EXPECT(v1 != v2); } TEST_CASE(value_copy_assign_key) { migraphx::value v1("key", 1); migraphx::value v2; v2 = v1; EXPECT(v2.get_key() == "key"); EXPECT(v1 == v2); } TEST_CASE(value_copy_assign_keyless) { migraphx::value v1(1); migraphx::value v2("key", nullptr); v2 = v1; EXPECT(v2.get_key() == "key"); EXPECT(v1 != v2); EXPECT(v1.without_key() == v2.without_key()); } TEST_CASE(value_assign_key_string_literal_pair) { migraphx::value v = migraphx::value::object{}; v["key"] = "one"; EXPECT(v["key"].is_string()); EXPECT(v["key"].get_string() == "one"); EXPECT(v["key"].get_key() == "key"); } TEST_CASE(value_construct_array) { migraphx::value v = {1, 2, 3}; EXPECT(v.is_array()); EXPECT(v.get_array().size() == 3); EXPECT(v.size() == 3); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.front().is_int64()); EXPECT(v.front() == migraphx::value(1)); EXPECT(v[1] == migraphx::value(2)); EXPECT(v.at(1) == migraphx::value(2)); EXPECT(v.back() == migraphx::value(3)); EXPECT(test::throws([&] { v.at("???"); })); [=] { EXPECT(v.data() != nullptr); EXPECT(v.front().is_int64()); EXPECT(v.front() == migraphx::value(1)); EXPECT(v[1] == migraphx::value(2)); EXPECT(v.at(1) == migraphx::value(2)); EXPECT(v.back() == migraphx::value(3)); }(); } TEST_CASE(value_insert_array) { migraphx::value v; v.insert(v.end(), 1); v.insert(v.end(), 2); v.insert(v.end(), 3); EXPECT(v.is_array()); EXPECT(v.get_array().size() == 3); EXPECT(v.size() == 3); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.front().is_int64()); EXPECT(v.front() == migraphx::value(1)); EXPECT(v[1] == migraphx::value(2)); EXPECT(v.at(1) == migraphx::value(2)); EXPECT(v.back() == migraphx::value(3)); } TEST_CASE(value_key_array) { std::vector values = {1, 2, 3}; migraphx::value v("key", values); EXPECT(v.is_array()); EXPECT(v.get_key() == "key"); EXPECT(v.get_array().size() == 3); EXPECT(v.size() == 3); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.front().is_int64()); EXPECT(v.front() == migraphx::value(1)); EXPECT(v[1] == migraphx::value(2)); EXPECT(v.at(1) == migraphx::value(2)); EXPECT(v.back() == migraphx::value(3)); } TEST_CASE(value_key_array_empty) { std::vector values{}; migraphx::value v("key", values); EXPECT(v.is_array()); EXPECT(v.get_key() == "key"); EXPECT(v.get_array().size() == 0); EXPECT(v.size() == 0); EXPECT(v.empty()); } TEST_CASE(value_construct_key_int1) { migraphx::value v("one", 1); EXPECT(v.is_int64()); EXPECT(v.get_int64() == 1); EXPECT(v.get_key() == "one"); } TEST_CASE(value_construct_key_int2) { migraphx::value v = {"one", 1}; EXPECT(v.is_int64()); EXPECT(v.get_int64() == 1); EXPECT(v.get_key() == "one"); } TEST_CASE(value_construct_key_pair) { migraphx::value v = std::make_pair("one", 1); EXPECT(v.is_int64()); EXPECT(v.get_int64() == 1); EXPECT(v.get_key() == "one"); } TEST_CASE(value_construct_object) { migraphx::value v = {{"one", 1}, {"two", migraphx::value(2)}, {"three", 3}}; EXPECT(v.is_object()); EXPECT(v.get_object().size() == 3); EXPECT(v.size() == 3); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.front().is_int64()); EXPECT(v.front().get_int64() == 1); EXPECT(v.front().get_key() == "one"); EXPECT(v[1].is_int64()); EXPECT(v[1].get_int64() == 2); EXPECT(v[1].get_key() == "two"); EXPECT(v.back().is_int64()); EXPECT(v.back().get_int64() == 3); EXPECT(v.back().get_key() == "three"); EXPECT(v.contains("one")); EXPECT(v.contains("two")); EXPECT(v.contains("three")); EXPECT(not v.contains("four")); EXPECT(v.at("one").is_int64()); EXPECT(v.at("one").get_int64() == 1); EXPECT(v.at("one").get_key() == "one"); EXPECT(v.at("two").is_int64()); EXPECT(v.at("two").get_int64() == 2); EXPECT(v.at("two").get_key() == "two"); EXPECT(v.at("three").is_int64()); EXPECT(v.at("three").get_int64() == 3); EXPECT(v.at("three").get_key() == "three"); EXPECT(v["one"].is_int64()); EXPECT(v["one"].get_int64() == 1); EXPECT(v["one"].get_key() == "one"); EXPECT(v["two"].is_int64()); EXPECT(v["two"].get_int64() == 2); EXPECT(v["two"].get_key() == "two"); EXPECT(v["three"].is_int64()); EXPECT(v["three"].get_int64() == 3); EXPECT(v["three"].get_key() == "three"); } TEST_CASE(value_key_object) { std::unordered_map values = { {"one", 1}, {"two", migraphx::value(2)}, {"three", 3}}; migraphx::value v("key", values); EXPECT(v.get_key() == "key"); EXPECT(v.is_object()); EXPECT(v.get_object().size() == 3); EXPECT(v.size() == 3); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.contains("one")); EXPECT(v.contains("two")); EXPECT(v.contains("three")); EXPECT(not v.contains("four")); EXPECT(v.at("one").is_int64()); EXPECT(v.at("one").get_int64() == 1); EXPECT(v.at("one").get_key() == "one"); EXPECT(v.at("two").is_int64()); EXPECT(v.at("two").get_int64() == 2); EXPECT(v.at("two").get_key() == "two"); EXPECT(v.at("three").is_int64()); EXPECT(v.at("three").get_int64() == 3); EXPECT(v.at("three").get_key() == "three"); EXPECT(v["one"].is_int64()); EXPECT(v["one"].get_int64() == 1); EXPECT(v["one"].get_key() == "one"); EXPECT(v["two"].is_int64()); EXPECT(v["two"].get_int64() == 2); EXPECT(v["two"].get_key() == "two"); EXPECT(v["three"].is_int64()); EXPECT(v["three"].get_int64() == 3); EXPECT(v["three"].get_key() == "three"); } TEST_CASE(value_key_object_empty) { std::unordered_map values{}; migraphx::value v("key", values); EXPECT(v.get_key() == "key"); EXPECT(v.is_object()); EXPECT(v.get_object().size() == 0); EXPECT(v.size() == 0); EXPECT(v.empty()); EXPECT(not v.contains("one")); } TEST_CASE(value_bracket_object) { migraphx::value v; v["one"] = 1; v["two"] = migraphx::value(2); v["three"] = 3; EXPECT(v.is_object()); EXPECT(v.get_object().size() == 3); EXPECT(v.size() == 3); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.front().is_int64()); EXPECT(v.front().get_int64() == 1); EXPECT(v.front().get_key() == "one"); EXPECT(v[1].is_int64()); EXPECT(v[1].get_int64() == 2); EXPECT(v[1].get_key() == "two"); EXPECT(v.back().is_int64()); EXPECT(v.back().get_int64() == 3); EXPECT(v.back().get_key() == "three"); EXPECT(v.contains("one")); EXPECT(v.contains("two")); EXPECT(v.contains("three")); EXPECT(not v.contains("four")); EXPECT(v.at("one").is_int64()); EXPECT(v.at("one").get_int64() == 1); EXPECT(v.at("one").get_key() == "one"); EXPECT(v.at("two").is_int64()); EXPECT(v.at("two").get_int64() == 2); EXPECT(v.at("two").get_key() == "two"); EXPECT(v.at("three").is_int64()); EXPECT(v.at("three").get_int64() == 3); EXPECT(v.at("three").get_key() == "three"); } TEST_CASE(value_insert_object) { migraphx::value v; v.insert({"one", 1}); v.insert({"two", migraphx::value(2)}); v.insert({"three", 3}); EXPECT(v.is_object()); EXPECT(v.get_object().size() == 3); EXPECT(v.size() == 3); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.front().is_int64()); EXPECT(v.front().get_int64() == 1); EXPECT(v.front().get_key() == "one"); EXPECT(v[1].is_int64()); EXPECT(v[1].get_int64() == 2); EXPECT(v[1].get_key() == "two"); EXPECT(v.back().is_int64()); EXPECT(v.back().get_int64() == 3); EXPECT(v.back().get_key() == "three"); EXPECT(v.contains("one")); EXPECT(v.contains("two")); EXPECT(v.contains("three")); EXPECT(not v.contains("four")); EXPECT(v.at("one").is_int64()); EXPECT(v.at("one").get_int64() == 1); EXPECT(v.at("one").get_key() == "one"); EXPECT(v.at("two").is_int64()); EXPECT(v.at("two").get_int64() == 2); EXPECT(v.at("two").get_key() == "two"); EXPECT(v.at("three").is_int64()); EXPECT(v.at("three").get_int64() == 3); EXPECT(v.at("three").get_key() == "three"); EXPECT(v["one"].is_int64()); EXPECT(v["one"].get_int64() == 1); EXPECT(v["one"].get_key() == "one"); EXPECT(v["two"].is_int64()); EXPECT(v["two"].get_int64() == 2); EXPECT(v["two"].get_key() == "two"); EXPECT(v["three"].is_int64()); EXPECT(v["three"].get_int64() == 3); EXPECT(v["three"].get_key() == "three"); } TEST_CASE(value_emplace_object) { migraphx::value v; v.emplace("one", 1); v.emplace("two", migraphx::value(2)); v.emplace("three", 3); EXPECT(v.is_object()); EXPECT(v.size() == 3); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.front().is_int64()); EXPECT(v.front().get_int64() == 1); EXPECT(v.front().get_key() == "one"); EXPECT(v[1].is_int64()); EXPECT(v[1].get_int64() == 2); EXPECT(v[1].get_key() == "two"); EXPECT(v.back().is_int64()); EXPECT(v.back().get_int64() == 3); EXPECT(v.back().get_key() == "three"); EXPECT(v.contains("one")); EXPECT(v.contains("two")); EXPECT(v.contains("three")); EXPECT(not v.contains("four")); EXPECT(v.at("one").is_int64()); EXPECT(v.at("one").get_int64() == 1); EXPECT(v.at("one").get_key() == "one"); EXPECT(v.at("two").is_int64()); EXPECT(v.at("two").get_int64() == 2); EXPECT(v.at("two").get_key() == "two"); EXPECT(v.at("three").is_int64()); EXPECT(v.at("three").get_int64() == 3); EXPECT(v.at("three").get_key() == "three"); EXPECT(v["one"].is_int64()); EXPECT(v["one"].get_int64() == 1); EXPECT(v["one"].get_key() == "one"); EXPECT(v["two"].is_int64()); EXPECT(v["two"].get_int64() == 2); EXPECT(v["two"].get_key() == "two"); EXPECT(v["three"].is_int64()); EXPECT(v["three"].get_int64() == 3); EXPECT(v["three"].get_key() == "three"); } TEST_CASE(value_bracket_convert_throws) { migraphx::value v1; EXPECT(test::throws([&] { v1["key"].to(); })); } TEST_CASE(value_construct_object_string_value) { migraphx::value v = {{"one", "onev"}, {"two", "twov"}}; EXPECT(v.is_object()); EXPECT(v.size() == 2); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.at("one").is_string()); EXPECT(v.at("one").get_key() == "one"); EXPECT(v.at("one").get_string() == "onev"); EXPECT(v.at("two").is_string()); EXPECT(v.at("two").get_key() == "two"); EXPECT(v.at("two").get_string() == "twov"); } TEST_CASE(value_construct_object_string_mixed_value) { migraphx::value v = {{"one", "onev"}, {"two", 2}}; EXPECT(v.is_object()); EXPECT(v.size() == 2); EXPECT(not v.empty()); EXPECT(v.data() != nullptr); EXPECT(v.at("one").is_string()); EXPECT(v.at("one").get_key() == "one"); EXPECT(v.at("one").get_string() == "onev"); EXPECT(v.at("two").is_int64()); EXPECT(v.at("two").get_key() == "two"); EXPECT(v.at("two").get_int64() == 2); } template static auto compare_predicate(const Expression& e) { bool result = e.value(); return test::make_predicate(test::as_string(e) + " => " + test::as_string(result), [=] { return result; }); } TEST_CASE(value_compare) { EXPECT(migraphx::value(1) == migraphx::value(1)); EXPECT(migraphx::value("key", 1) == migraphx::value("key", 1)); EXPECT(migraphx::value(1) != migraphx::value(2)); EXPECT(migraphx::value("key", 1) != migraphx::value("key", 2)); EXPECT(migraphx::value("key1", 1) != migraphx::value("key2", 1)); EXPECT(migraphx::value(1) < migraphx::value(2)); EXPECT(migraphx::value(1) <= migraphx::value(2)); EXPECT(migraphx::value(1) <= migraphx::value(1)); EXPECT(migraphx::value(2) > migraphx::value(1)); EXPECT(migraphx::value(2) >= migraphx::value(1)); EXPECT(migraphx::value(1) >= migraphx::value(1)); EXPECT(migraphx::value(1) != migraphx::value("1")); EXPECT(migraphx::value(1) != migraphx::value()); } // NOLINTNEXTLINE #define MIGRAPHX_VALUE_TEST_COMPARE(...) compare_predicate(TEST_CAPTURE(__VA_ARGS__)) // NOLINTNEXTLINE #define EXPECT_TOTALLY_ORDERED_IMPL(_, x, y) \ EXPECT(_(x <= y) or _(x >= y)); \ EXPECT(_(x < y) or _(x > y) or _(x == y)); \ EXPECT((_(x < y) or _(x > y)) == _(x != y)); \ EXPECT(_(x < y) == _(y > x)); \ EXPECT(_(x <= y) == _(y >= x)); \ EXPECT(_(x < y) != _(x >= y)); \ EXPECT(_(x > y) != _(x <= y)); \ EXPECT(_(x == y) != _(x != y)) // NOLINTNEXTLINE #define EXPECT_TOTALLY_ORDERED(x, y) \ EXPECT_TOTALLY_ORDERED_IMPL(MIGRAPHX_VALUE_TEST_COMPARE, x, y); \ EXPECT_TOTALLY_ORDERED_IMPL(MIGRAPHX_VALUE_TEST_COMPARE, y, x) // NOLINTNEXTLINE(readability-function-size) TEST_CASE(value_compare_ordered) { EXPECT_TOTALLY_ORDERED(migraphx::value(), migraphx::value()); EXPECT_TOTALLY_ORDERED(migraphx::value(1), migraphx::value(1)); EXPECT_TOTALLY_ORDERED(migraphx::value(1), migraphx::value(2)); EXPECT_TOTALLY_ORDERED(migraphx::value("key", 1), migraphx::value("key", 1)); EXPECT_TOTALLY_ORDERED(migraphx::value("key1", 1), migraphx::value("key2", 2)); EXPECT_TOTALLY_ORDERED(migraphx::value("key", 1), migraphx::value("key", 2)); EXPECT_TOTALLY_ORDERED(migraphx::value("key1", 1), migraphx::value("key2", 2)); EXPECT_TOTALLY_ORDERED(migraphx::value("key", 1), migraphx::value("key", "2")); EXPECT_TOTALLY_ORDERED(migraphx::value("key1", 1), migraphx::value("key2", "2")); EXPECT_TOTALLY_ORDERED(migraphx::value(std::int64_t{1}), migraphx::value(std::uint64_t{1})); EXPECT_TOTALLY_ORDERED(migraphx::value(std::int64_t{1}), migraphx::value(std::uint64_t{2})); EXPECT_TOTALLY_ORDERED(migraphx::value(std::int64_t{2}), migraphx::value(std::uint64_t{1})); EXPECT_TOTALLY_ORDERED(migraphx::value(1), migraphx::value("1")); EXPECT_TOTALLY_ORDERED(migraphx::value(1), migraphx::value()); } TEST_CASE(value_compare_object_equal) { migraphx::value v1 = {{"a", 1}, {"c", 3}, {"b", 2}, {"d", 4}, {"e", 5}, {"f", 6}}; migraphx::value v2 = {{"e", 5}, {"f", 6}, {"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}; EXPECT(v1 == v2); EXPECT_TOTALLY_ORDERED(v1, v2); } TEST_CASE(value_compare_object_not_equal_missing_keys) { migraphx::value v1 = {{"a", 1}, {"c", 3}, {"b", 2}, {"d", 4}, {"e", 5}, {"f", 6}}; migraphx::value v2 = {{"e", 5}, {"f", 6}, {"b", 2}, {"c", 3}, {"d", 4}}; EXPECT(v1 != v2); EXPECT_TOTALLY_ORDERED(v1, v2); } TEST_CASE(value_compare_object_not_equal_diff_values) { migraphx::value v1 = {{"a", 1}, {"c", 3}, {"b", 2}, {"d", 4}, {"e", 5}, {"f", 6}}; migraphx::value v2 = {{"e", 1}, {"f", 3}, {"a", 2}, {"b", 2}, {"c", 3}, {"d", 4}}; EXPECT(v1 != v2); EXPECT_TOTALLY_ORDERED(v1, v2); } TEST_CASE(value_to_from_string) { migraphx::value v = "1"; EXPECT(v.to() == "1"); EXPECT(v.to() == 1); EXPECT(migraphx::float_equal(v.to(), 1.0)); } TEST_CASE(value_to_from_int) { migraphx::value v = 1; EXPECT(v.to() == "1"); EXPECT(v.to() == 1); EXPECT(migraphx::float_equal(v.to(), 1.0)); } TEST_CASE(value_to_from_float) { migraphx::value v = 1.5; EXPECT(v.to() == "1.5"); EXPECT(v.to() == 1); EXPECT(migraphx::float_equal(v.to(), 1.5)); } TEST_CASE(value_to_from_pair) { migraphx::value v = {"one", 1}; EXPECT(bool{v.to>() == std::pair("one", "1")}); EXPECT(v.to>() == std::pair("one", 1)); EXPECT( bool{v.to>() == std::pair("one", 1.0)}); } TEST_CASE(value_to_struct) { migraphx::value v = 1; struct local { int i = 0; local() = default; local(int ii) : i(ii) {} }; EXPECT(v.to().i == 1); } TEST_CASE(value_to_error1) { migraphx::value v = {1, 2, 3}; EXPECT(test::throws([&] { v.to(); })); } TEST_CASE(value_to_error2) { migraphx::value v = 1; struct local { }; EXPECT(test::throws([&] { v.to(); })); } TEST_CASE(value_to_error_parse) { migraphx::value v = "abc"; EXPECT(test::throws([&] { v.to(); })); } TEST_CASE(value_to_vector) { migraphx::value v = {1, 2, 3}; std::vector a = {1, 2, 3}; EXPECT(v.to_vector() == a); } TEST_CASE(not_array) { migraphx::value v = 1; EXPECT(v.size() == 0); EXPECT(not v.contains("???")); EXPECT(test::throws([&] { v.at(0); })); EXPECT(test::throws([&] { v.at("???"); })); EXPECT(v.data() == nullptr); [=] { EXPECT(test::throws([&] { v.at(0); })); EXPECT(test::throws([&] { v.at("???"); })); EXPECT(v.data() == nullptr); }(); } TEST_CASE(print) { std::stringstream ss; migraphx::value v = {1, {{"one", 1}, {"two", 2}}, {1, 2}, {}}; ss << v; EXPECT(ss.str() == "{1, {one: 1, two: 2}, {1, 2}, null}"); } TEST_CASE(value_clear) { migraphx::value values = {1, 2, 3}; EXPECT(values.is_array()); EXPECT(values.size() == 3); values.clear(); EXPECT(values.empty()); values.push_back(3); EXPECT(values.size() == 1); EXPECT(values.at(0).to() == 3); } TEST_CASE(value_clear_non_array) { migraphx::value values = 1.0; EXPECT(test::throws([&] { values.clear(); })); } TEST_CASE(value_clear_object) { migraphx::value values = {{"a", 1}, {"b", 2}}; EXPECT(values.is_object()); EXPECT(values.size() == 2); values.clear(); EXPECT(values.empty()); values["c"] = 3; EXPECT(values.size() == 1); EXPECT(values.at("c").to() == 3); } TEST_CASE(value_clear_empty_array) { migraphx::value values = migraphx::value::array{}; EXPECT(values.empty()); values.clear(); EXPECT(values.empty()); } TEST_CASE(value_clear_empty_object) { migraphx::value values = migraphx::value::object{}; EXPECT(values.empty()); values.clear(); EXPECT(values.empty()); } TEST_CASE(value_resize) { migraphx::value values = {1, 2, 3}; EXPECT(values.is_array()); EXPECT(values.size() == 3); values.resize(5); EXPECT(values.size() == 5); EXPECT(values.at(3).is_null()); EXPECT(values.at(4).is_null()); } TEST_CASE(value_resize_with_value) { migraphx::value values = {1, 2, 3}; EXPECT(values.is_array()); EXPECT(values.size() == 3); values.resize(5, 7); EXPECT(values.size() == 5); EXPECT(values.at(3).to() == 7); EXPECT(values.at(4).to() == 7); } TEST_CASE(value_resize_empty_array) { migraphx::value values = migraphx::value::array{}; EXPECT(values.is_array()); EXPECT(values.empty()); values.resize(3); EXPECT(values.size() == 3); EXPECT(values.at(0).is_null()); EXPECT(values.at(1).is_null()); EXPECT(values.at(2).is_null()); } TEST_CASE(value_resize_object) { migraphx::value values = migraphx::value::object{}; EXPECT(values.is_object()); EXPECT(test::throws([&] { values.resize(4); })); } TEST_CASE(value_resize_n_object) { migraphx::value values = migraphx::value::object{}; EXPECT(values.is_object()); EXPECT(test::throws([&] { values.resize(4, ""); })); } TEST_CASE(value_assign_construct_from_vector) { std::vector v = {1, 2, 3}; migraphx::value values = v; EXPECT(values.to_vector() == v); } TEST_CASE(value_construct_from_vector) { std::vector v = {1, 2, 3}; migraphx::value values(v); EXPECT(values.to_vector() == v); } TEST_CASE(value_assign_from_vector) { std::vector v = {1, 2, 3}; migraphx::value values{}; values = v; EXPECT(values.to_vector() == v); } TEST_CASE(value_init_from_vector) { std::vector v = {1, 2, 3}; migraphx::value values = {{"a", v}}; EXPECT(values.at("a").to_vector() == v); } TEST_CASE(value_binary_default) { migraphx::value v; v = migraphx::value::binary{}; EXPECT(v.is_binary()); EXPECT(v.get_key().empty()); } TEST_CASE(value_binary) { migraphx::value v; std::vector data(20); std::iota(data.begin(), data.end(), 0); v = migraphx::value::binary{data}; EXPECT(v.is_binary()); EXPECT(v.get_binary().size() == data.size()); EXPECT(v.get_binary() == data); EXPECT(v.get_key().empty()); } TEST_CASE(value_binary_object) { std::vector data(20); std::iota(data.begin(), data.end(), 0); migraphx::value v = {{"data", migraphx::value::binary{data}}}; EXPECT(v["data"].is_binary()); EXPECT(v["data"].get_binary().size() == data.size()); EXPECT(v["data"].get_binary() == data); } TEST_CASE(value_binary_object_conv) { std::vector data(20); std::iota(data.begin(), data.end(), 0); migraphx::value v = {{"data", migraphx::value::binary{data}}}; EXPECT(v["data"].is_binary()); EXPECT(v["data"].get_binary().size() == data.size()); EXPECT(migraphx::equal(v["data"].get_binary(), data)); } template static bool is_null_type(const T&) { return false; } static bool is_null_type(std::nullptr_t) { return true; } TEST_CASE(visit_null) { migraphx::value v; EXPECT(v.is_null()); bool visited = false; v.visit([&](auto&& x) { visited = is_null_type(x); }); EXPECT(visited); } TEST_CASE(value_or_convert) { migraphx::value v = 1; EXPECT(v.is_int64()); EXPECT(v.value_or(3) == 1); } TEST_CASE(value_or_null) { migraphx::value v; EXPECT(v.is_null()); EXPECT(v.value_or(3) == 3); } TEST_CASE(value_get_default) { migraphx::value v = {{"key", 1}}; EXPECT(v.get("key", 3) == 1); EXPECT(v.get("missing", 3) == 3); } TEST_CASE(value_get_default_vector) { std::vector ints = {1, 2, 3}; std::vector fallback = {-1}; migraphx::value v = {{"key", ints}}; EXPECT(v.get("key", fallback) == ints); EXPECT(v.get("missing", fallback) == fallback); EXPECT(v.get("missing", {-1}) == fallback); } TEST_CASE(value_get_default_string_literal) { migraphx::value v = {{"key", "hello"}}; EXPECT(v.get("key", "none") == "hello"); EXPECT(v.get("missing", "none") == "none"); } TEST_CASE(value_get_default_string_literal_vector) { std::vector strings = {"1", "2", "3"}; std::vector fallback = {"none"}; migraphx::value v = {{"key", strings}}; EXPECT(v.get("key", fallback) == strings); EXPECT(v.get("missing", fallback) == fallback); EXPECT(v.get("missing", {"none"}) == fallback); } TEST_CASE(value_object_as_hash_key1) { migraphx::value v1 = {{"a", 1}, {"c", 3}, {"b", 2}, {"d", 4}, {"e", 5}, {"f", 6}}; migraphx::value v2 = {{"e", 5}, {"f", 6}, {"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}; migraphx::value v3 = {{"e", 1}, {"f", 3}, {"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}; migraphx::value v4 = {{"e", 5}, {"b", 2}, {"c", 3}, {"d", 4}}; std::unordered_set set; set.insert(v1); EXPECT(migraphx::contains(set, v1)); EXPECT(migraphx::contains(set, v2)); EXPECT(not migraphx::contains(set, v3)); EXPECT(not migraphx::contains(set, v4)); } TEST_CASE(value_object_as_hash_key2) { migraphx::value v1 = {{"a", 1}, {"c", 3}, {"b", 2}, {"d", 4}, {"e", 5}, {"f", 6}}; migraphx::value v2 = {{"e", 5}, {"f", 6}, {"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}; migraphx::value v3 = {{"e", 1}, {"f", 3}, {"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}; migraphx::value v4 = {{"e", 5}, {"b", 2}, {"c", 3}, {"d", 4}}; std::unordered_set set; set.insert(v1); set.insert(v2); set.insert(v3); set.insert(v4); EXPECT(migraphx::contains(set, v1)); EXPECT(migraphx::contains(set, v2)); EXPECT(migraphx::contains(set, v3)); EXPECT(migraphx::contains(set, v4)); EXPECT(set.size() == 3); } int main(int argc, const char* argv[]) { test::run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/verify/000077500000000000000000000000001510465702400170235ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/test/verify/CMakeLists.txt000066400000000000000000000042571510465702400215730ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### file(GLOB VERIFY_TESTS CONFIGURE_DEPENDS *.cpp) add_executable(test_verify ${VERIFY_TESTS}) rocm_mark_as_test(test_verify) rocm_install_test(TARGETS test_verify) target_link_libraries(test_verify migraphx migraphx_all_targets) target_include_directories(test_verify PUBLIC ../include) rocm_clang_tidy_check(test_verify) foreach(SECTION general reduce rnn conv gemm) rocm_add_test(NAME test_verify_${SECTION} COMMAND test_verify ${SECTION}) set_tests_properties(test_verify_${SECTION} PROPERTIES COST 100 ) if(MIGRAPHX_ENABLE_GPU) set_tests_properties(test_verify_${SECTION} PROPERTIES RESOURCE_LOCK gpu ) endif() endforeach() # TODO: remove this when MIGraphX disables attn-like offloading to rocMLIR # f32 not supported on navi3x set_tests_properties(test_verify_rnn PROPERTIES ENVIRONMENT "MIGRAPHX_ENABLE_MLIR_GEG_FUSION=0") ROCm-AMDMIGraphX-46524e8/test/verify/auto_print.cpp000066400000000000000000000052511510465702400217160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "auto_print.hpp" #include #include #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wglobal-constructors" #endif using handler_map = std::map>; static handler_map create_handlers() { handler_map m; for(const auto& name : migraphx::get_targets()) m[name] = [] {}; return m; } std::function& auto_print::get_handler(const std::string& name) { // NOLINTNEXTLINE static handler_map handlers = create_handlers(); return handlers.at(name); } void auto_print::set_terminate_handler(const std::string& name) { // NOLINTNEXTLINE static std::string pname; pname = name; std::set_terminate(+[] { std::cout << "FAILED: " << pname << std::endl; try { std::rethrow_exception(std::current_exception()); } catch(const std::exception& e) { std::cout << " what(): " << e.what() << std::endl; } std::cout << std::endl; for(const auto& tname : migraphx::get_targets()) get_handler(tname)(); }); } static bool in_exception() { #if __cplusplus >= 201703L return std::uncaught_exceptions() > 0; #else return std::uncaught_exception(); #endif } auto_print::~auto_print() { if(in_exception()) { std::cout << std::endl; for(const auto& tname : migraphx::get_targets()) get_handler(tname)(); } get_handler(name) = [] {}; } ROCm-AMDMIGraphX-46524e8/test/verify/auto_print.hpp000066400000000000000000000032521510465702400217220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_AUTO_PRINT_HPP #define MIGRAPHX_GUARD_TEST_AUTO_PRINT_HPP #include #include struct auto_print { static std::function& get_handler(const std::string& name); static void set_terminate_handler(const std::string& name); std::string name; template auto_print(T& x, std::string s) : name(std::move(s)) { get_handler(name) = [&x] { std::cout << x << std::endl; }; } ~auto_print(); }; #endif ROCm-AMDMIGraphX-46524e8/test/verify/main.cpp000066400000000000000000000173631510465702400204650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "run_verify.hpp" #include #include #ifdef HAVE_GPU #include #include #endif #ifdef HAVE_CPU #include #endif inline static void check_gpu_streams(const migraphx::program& p) { #ifdef HAVE_GPU const auto* mm = p.get_main_module(); auto races = migraphx::gpu::analyze_streams(*mm); for(auto&& race : races) { std::cout << "FAILED: " << std::endl; std::cout << "Race condition detected for: "; mm->debug_print(race.ins); std::cout << "Should happen after: "; mm->debug_print(race.before); } #else (void)p; #endif } static void validate_gpu(const migraphx::program& p, const migraphx::parameter_map& m) { check_gpu_streams(p); // Ensure the program doesn't modify the context in a dry run auto ctx = p.get_context(); assert(&ctx != &p.get_context()); EXPECT(is_shared(ctx, p.get_context())); p.dry_run(m); EXPECT(is_shared(ctx, p.get_context())); } int main(int argc, const char* argv[]) { run_verify rv; rv.add_validation_for("gpu", &validate_gpu); rv.disable_test_for( "cpu", {"test_if_lp", "test_if_param", "test_if_literal", "test_select_module_add", "test_select_module_reduce", "test_select_module_conv", "test_split_single_dyn_dim", "test_resize_dyn", "test_instancenorm_large_3d", "test_instancenorm_large_3d", "test_isinf >", "test_isinf", // these tests are disabled due issue of lossy downcast, see issue#2517 #if defined(__GNUC__) and !defined(__clang__) "test_batch_quant_dot_1, " "float>", "test_quant_dot_3args_4, " "float>", "test_quant_dot_3args_5, " "float>", "test_batch_quant_dot_1, " "float>", "test_quant_dot_3args_4, " "float>", "test_quant_dot_3args_5, " "float>", "test_batch_quant_dot_1, " "float>", "test_quant_dot_3args_4, " "float>", "test_quant_dot_3args_5, " "float>", "test_batch_quant_dot_1, " "float>", "test_quant_dot_3args_4, " "float>", "test_quant_dot_3args_5, " "float>", #else "test_batch_quant_dot_1", "test_quant_dot_3args_4", "test_quant_dot_3args_5", "test_batch_quant_dot_1", "test_quant_dot_3args_4", "test_quant_dot_3args_5", "test_batch_quant_dot_1", "test_quant_dot_3args_4", "test_quant_dot_3args_5", "test_batch_quant_dot_1", "test_quant_dot_3args_4", "test_quant_dot_3args_5", #endif "test_block_reduce_small<3, migraphx::shape::int8_type>", "test_block_reduce_small<4, migraphx::shape::int8_type>", "test_block_reduce_small<8, migraphx::shape::int8_type>", "test_block_reduce_small<16, migraphx::shape::int8_type>", "test_block_reduce_small<25, migraphx::shape::int8_type>", "test_block_reduce_small<32, migraphx::shape::int8_type>", "test_block_reduce_small<64, migraphx::shape::int8_type>", "test_block_reduce_small<67, migraphx::shape::int8_type>", "test_block_reduce_small<128, migraphx::shape::int8_type>", "test_block_reduce_small<129, migraphx::shape::int8_type>", // disabled because CPU does eliminate_data_type to float for everything "test_bitwise_and", "test_bitwise_and", "test_unpack_int4", "test_unpack_int4", "test_unpack_int4", "test_unpack_int4", "test_bit_cast", "test_bit_cast", "test_bit_cast", "test_bit_cast"}); rv.disable_test_for("gpu", { // These passes on MI300 but fails on others, same issue as CPU. "test_batch_quant_dot_1", "test_quant_dot_3args_4", "test_quant_dot_3args_5", "test_batch_quant_dot_1", "test_quant_dot_3args_4", "test_quant_dot_3args_5", "test_batch_quant_dot_1", "test_quant_dot_3args_4", "test_quant_dot_3args_5", "test_batch_quant_dot_1", "test_quant_dot_3args_4", "test_quant_dot_3args_5", }); rv.run(argc, argv); } ROCm-AMDMIGraphX-46524e8/test/verify/run_verify.cpp000066400000000000000000000236621510465702400217300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "run_verify.hpp" #include "auto_print.hpp" #include "verify_program.hpp" #include "test.hpp" #include #include #include #include #include #include #include #include #include #include #include MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_TEST_COMPILE) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_TRACE_TEST) MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_DUMP_TEST) // An improved async, that doesn't block template static std::future> detach_async(Function&& f, bool parallel = true) { if(parallel) { using result_type = typename std::invoke_result::type; std::packaged_task task(std::forward(f)); auto fut = task.get_future(); std::thread(std::move(task)).detach(); return fut; } return std::async(std::launch::deferred, std::forward(f)); } inline static void verify_load_save(const migraphx::program& p) { migraphx::tmp_dir td{"migraphx_test"}; auto path = td.path / "test.mxr"; migraphx::save(p, path.string()); auto loaded = migraphx::load(path.string()); EXPECT(p == loaded); } inline static void compile_check(migraphx::program& p, const migraphx::target& t, migraphx::compile_options c_opts, bool show_trace = false) { auto name = t.name(); auto shapes = p.get_output_shapes(); std::stringstream ss; if(show_trace) c_opts.trace = migraphx::tracer{std::cout}; p.compile(t, c_opts); if(shapes.size() != p.get_output_shapes().size()) { std::cout << ss.str() << std::endl; throw std::runtime_error("Compiling program with " + name + " alters its number of outputs"); } auto num = shapes.size(); for(std::size_t i = 0; i < num; ++i) { auto output_shape = p.get_output_shapes()[i]; // for static output shapes check that the intial parsed shapes are // the same as the ones after compiling // no check for dynamic shapes because the shapes can change after compiling if(not output_shape.dynamic() and not shapes[i].dynamic()) { if(output_shape.lens() != shapes[i].lens()) { std::cout << ss.str() << std::endl; throw std::runtime_error("Compiling program with " + name + " alters its static output dimensions"); } } } if(t.name() != "ref") verify_load_save(p); } target_info run_verify::get_target_info(const std::string& name) const { auto it = info.find(name); if(it != info.end()) return it->second; else return {}; } void run_verify::validate(const migraphx::target& t, const migraphx::program& p, const migraphx::parameter_map& m) const { auto ti = get_target_info(t.name()); if(ti.validate) ti.validate(p, m); } std::pair> run_verify::run_ref(migraphx::program p, const migraphx::parameter_map& inputs, const migraphx::compile_options& c_opts) const { migraphx::target t = migraphx::make_target("ref"); auto_print pp{p, t.name()}; auto trace_target = migraphx::string_value_of(MIGRAPHX_TRACE_TEST_COMPILE{}); compile_check(p, t, c_opts, (trace_target == "ref")); return std::make_pair(std::move(p), p.eval(inputs)); } std::pair> run_verify::run_target(const migraphx::target& t, migraphx::program p, const migraphx::parameter_map& inputs, const migraphx::compile_options& c_opts) const { auto_print pp{p, t.name()}; auto trace_target = migraphx::string_value_of(MIGRAPHX_TRACE_TEST_COMPILE{}); compile_check(p, t, c_opts, (trace_target == t.name())); migraphx::parameter_map m; for(auto&& input : inputs) { m[input.first] = t.copy_to(input.second); } for(auto&& x : p.get_parameter_shapes()) { if(m.count(x.first) == 0) { m[x.first] = t.allocate(x.second); } } validate(t, p, m); p.eval(m); auto tres = p.eval(m); std::vector res(tres.size()); std::transform( tres.begin(), tres.end(), res.begin(), [&](auto& argu) { return t.copy_from(argu); }); return std::make_pair(std::move(p), res); } template static auto get_hash(const T& x) { return std::hash{}(x); } void run_verify::verify(const program_info& pi) const { const std::string name = pi.name; const migraphx::program p = pi.get_program(); using result_future = std::future>>; auto_print::set_terminate_handler(name); if(migraphx::enabled(MIGRAPHX_DUMP_TEST{})) migraphx::save(p, name + ".mxr"); verify_load_save(p); std::vector target_names; for(const auto& tname : migraphx::get_targets()) { // TODO(varunsh): once verify tests can run, remove fpga if(tname == "ref" or tname == "fpga") continue; // if tests disabled, skip running it target_info ti = get_target_info(tname); if(migraphx::contains(ti.disabled_tests, name)) continue; target_names.push_back(tname); } if(not target_names.empty()) { std::vector> results; migraphx::parameter_map m; for(auto&& x : p.get_parameter_shapes()) { if(x.second.dynamic()) { // create static shape using maximum dimensions migraphx::shape static_shape{x.second.type(), x.second.max_lens()}; m[x.first] = migraphx::generate_argument(static_shape, get_hash(x.first)); } else { m[x.first] = migraphx::generate_argument(x.second, get_hash(x.first)); } } const migraphx::compile_options c_opts = pi.compile_options; auto ref_f = detach_async([=] { return run_ref(p, m, c_opts); }); for(const auto& tname : target_names) { target_info ti = get_target_info(tname); auto t = migraphx::make_target(tname); results.emplace_back( tname, detach_async([=] { return run_target(t, p, m, c_opts); }, ti.parallel)); } assert(ref_f.valid()); auto ref_results = ref_f.get(); for(auto&& pp : results) { assert(pp.second.valid()); auto tname = pp.first; auto x = pp.second.get(); auto cp = x.first; auto result = x.second; auto gold = ref_results.second; bool passed = true; passed &= (gold.size() == result.size()); std::size_t num = gold.size(); for(std::size_t i = 0; ((i < num) and passed); ++i) { passed &= migraphx::verify_args_with_tolerance( tname, result[i], migraphx::verify::expected{gold[i]}, pi.tolerance); } if(not passed or migraphx::enabled(MIGRAPHX_TRACE_TEST{})) { std::cout << p << std::endl; std::cout << "ref:\n" << ref_results.first << std::endl; std::cout << tname << ":\n" << cp << std::endl; std::cout << std::endl; } EXPECT(passed); } } std::set_terminate(nullptr); } void run_verify::run(int argc, const char* argv[]) const { std::unordered_map> labels; for(auto&& p : get_programs()) { labels[p.section].push_back(p.name); test::add_test_case(p.name, [=] { verify(p); }); } test::driver d{}; d.get_case_names = [&](const std::string& name) -> std::vector { if(labels.count(name) > 0) return labels.at(name); return {name}; }; d.run(argc, argv); } void run_verify::disable_parallel_for(const std::string& name) { info[name].parallel = false; } void run_verify::add_validation_for(const std::string& name, target_info::validation_function v) { info[name].validate = std::move(v); } void run_verify::disable_test_for(const std::string& name, const std::vector& tests) { auto& disabled_tests = info[name].disabled_tests; disabled_tests.insert(disabled_tests.end(), tests.begin(), tests.end()); } ROCm-AMDMIGraphX-46524e8/test/verify/run_verify.hpp000066400000000000000000000052541510465702400217320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_TEST_RUN_VERIFY_HPP #define MIGRAPHX_GUARD_TEST_RUN_VERIFY_HPP #include #include #include struct program_info; struct target_info { using validation_function = std::function; bool parallel = true; validation_function validate; std::vector disabled_tests; }; struct run_verify { std::pair> run_ref(migraphx::program p, const migraphx::parameter_map& inputs, const migraphx::compile_options& c_opts) const; std::pair> run_target(const migraphx::target& t, migraphx::program p, const migraphx::parameter_map& inputs, const migraphx::compile_options& c_opts) const; void validate(const migraphx::target& t, const migraphx::program& p, const migraphx::parameter_map& m) const; void verify(const program_info& pi) const; void run(int argc, const char* argv[]) const; target_info get_target_info(const std::string& name) const; void disable_parallel_for(const std::string& name); void add_validation_for(const std::string& name, target_info::validation_function v); void disable_test_for(const std::string& name, const std::vector& tests); private: std::map info{}; }; #endif ROCm-AMDMIGraphX-46524e8/test/verify/test_abs.cpp000066400000000000000000000040621510465702400213350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_abs : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{DType, {4, 3, 3, 3}}); mm->add_instruction(migraphx::make_op("abs"), x); return p; } }; template struct test_abs; template struct test_abs; template struct test_abs; template struct test_abs; template struct test_abs; template struct test_abs; template struct test_abs; ROCm-AMDMIGraphX-46524e8/test/verify/test_acos.cpp000066400000000000000000000040771510465702400215230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_acos : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("acos"), x); return p; } }; template struct test_acos; template struct test_acos; template struct test_acos; template struct test_acos; template struct test_acos; template struct test_acos; template struct test_acos; ROCm-AMDMIGraphX-46524e8/test/verify/test_acosh.cpp000066400000000000000000000050751510465702400216720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_acosh : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::type_t dtype = migraphx::shape::get_type(); migraphx::shape s{dtype, {16}}; auto x = mm->add_parameter("x", s); auto min_val = mm->add_literal(migraphx::literal{migraphx::shape{dtype}, {1.1f}}); auto max_val = mm->add_literal(migraphx::literal{migraphx::shape{dtype}, {100.0f}}); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {16}}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {16}}}), max_val); auto cx = mm->add_instruction(migraphx::make_op("clip"), x, min_val, max_val); mm->add_instruction(migraphx::make_op("acosh"), cx); return p; } }; template struct test_acosh; template struct test_acosh; template struct test_acosh; template struct test_acosh; template struct test_acosh; template struct test_acosh; ROCm-AMDMIGraphX-46524e8/test/verify/test_add.cpp000066400000000000000000000041431510465702400213200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_add : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {8}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("add"), x, y); return p; } }; template struct test_add; template struct test_add; template struct test_add; template struct test_add; template struct test_add; template struct test_add; template struct test_add; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_bf16.cpp000066400000000000000000000033001510465702400221300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("add"), x, y); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_broadcast.cpp000066400000000000000000000037111510465702400233420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_add_broadcast : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 2, 3}}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {2, 2}}); auto by = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", x->get_shape().lens()}}), y); mm->add_instruction(migraphx::make_op("add"), x, by); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_broadcast2.cpp000066400000000000000000000037101510465702400234230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_add_broadcast2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 3, 4}}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {3}}); auto by = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), y); mm->add_instruction(migraphx::make_op("add"), x, by); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_broadcast3.cpp000066400000000000000000000037101510465702400234240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_add_broadcast3 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 4, 5}}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {4}}); auto by = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), y); mm->add_instruction(migraphx::make_op("add"), x, by); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_broadcast4.cpp000066400000000000000000000037101510465702400234250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_add_broadcast4 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 3, 5}}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {3}}); auto by = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), y); mm->add_instruction(migraphx::make_op("add"), x, by); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_broadcast5.cpp000066400000000000000000000037101510465702400234260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_add_broadcast5 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 4, 8}}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {4}}); auto by = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", x->get_shape().lens()}}), y); mm->add_instruction(migraphx::make_op("add"), x, by); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_broadcast6.cpp000066400000000000000000000036251510465702400234340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_add_broadcast6 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 64, 568, 1328}}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {64}}); auto by = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 64, 568, 1328}}}), y); mm->add_instruction(migraphx::make_op("add"), x, by); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_conv_constant.cpp000066400000000000000000000040151510465702400242540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_conv_constant : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 3, 32, 32}}; migraphx::shape ws{migraphx::shape::float_type, {4, 3, 3, 3}}; auto x = mm->add_parameter("x", s); auto c = mm->add_literal(migraphx::generate_literal(s, 1)); auto w = mm->add_literal(migraphx::generate_literal(ws, 2)); auto sum = mm->add_instruction(migraphx::make_op("add"), c, x); mm->add_instruction(migraphx::make_op("convolution"), sum, w); return p; } std::string section() const { return "conv"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_dot.cpp000066400000000000000000000040661510465702400221720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_add_dot : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {256, 256}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); auto dot = mm->add_instruction(migraphx::make_op("dot"), add, z); mm->add_return({dot}); return p; } }; template struct test_add_dot; template struct test_add_dot; template struct test_add_dot; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_gelu.cpp000066400000000000000000000054151510465702400223370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_gelu : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 1, 5}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, input_lens}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, input_lens}); auto half = mm->add_literal(0.5f); auto one = mm->add_literal(1.0f); auto sqrt2 = mm->add_literal(static_cast(M_SQRT2)); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); auto half_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), half); auto mul_half = mm->add_instruction(migraphx::make_op("mul"), add, half_mbcast); auto sqrt2_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), sqrt2); auto div = mm->add_instruction(migraphx::make_op("div"), add, sqrt2_mbcast); auto erf = mm->add_instruction(migraphx::make_op("erf"), div); auto one_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), one); auto add_one = mm->add_instruction(migraphx::make_op("add"), erf, one_mbcast); mm->add_instruction(migraphx::make_op("mul"), mul_half, add_one); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_gelu_bf16.cpp000066400000000000000000000055661510465702400231640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_gelu_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 1, 5}; auto x = mm->add_parameter("x", {migraphx::shape::bf16_type, input_lens}); auto y = mm->add_parameter("y", {migraphx::shape::bf16_type, input_lens}); auto bf16 = mm->add_literal(migraphx::literal{{migraphx::shape::bf16_type}, {0.5f}}); auto one = mm->add_literal(migraphx::literal{{migraphx::shape::bf16_type}, {1.0f}}); auto sqrt2 = mm->add_literal(migraphx::literal{{migraphx::shape::bf16_type}, {M_SQRT2}}); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); auto bf16_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), bf16); auto mul_bf16 = mm->add_instruction(migraphx::make_op("mul"), add, bf16_mbcast); auto sqrt2_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), sqrt2); auto div = mm->add_instruction(migraphx::make_op("div"), add, sqrt2_mbcast); auto erf = mm->add_instruction(migraphx::make_op("erf"), div); auto one_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), one); auto add_one = mm->add_instruction(migraphx::make_op("add"), erf, one_mbcast); mm->add_instruction(migraphx::make_op("mul"), mul_bf16, add_one); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_gelu_half.cpp000066400000000000000000000055661510465702400233400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_gelu_half : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 1, 5}; auto x = mm->add_parameter("x", {migraphx::shape::half_type, input_lens}); auto y = mm->add_parameter("y", {migraphx::shape::half_type, input_lens}); auto half = mm->add_literal(migraphx::literal{{migraphx::shape::half_type}, {0.5f}}); auto one = mm->add_literal(migraphx::literal{{migraphx::shape::half_type}, {1.0f}}); auto sqrt2 = mm->add_literal(migraphx::literal{{migraphx::shape::half_type}, {M_SQRT2}}); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); auto half_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), half); auto mul_half = mm->add_instruction(migraphx::make_op("mul"), add, half_mbcast); auto sqrt2_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), sqrt2); auto div = mm->add_instruction(migraphx::make_op("div"), add, sqrt2_mbcast); auto erf = mm->add_instruction(migraphx::make_op("erf"), div); auto one_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), one); auto add_one = mm->add_instruction(migraphx::make_op("add"), erf, one_mbcast); mm->add_instruction(migraphx::make_op("mul"), mul_half, add_one); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_half.cpp000066400000000000000000000033001510465702400223040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_half : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("add"), x, y); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_mixed_layout.cpp000066400000000000000000000040551510465702400241050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_add_mixed_layout : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto s1 = migraphx::shape{DType, {1, 32, 16, 16}}; auto s2 = migraphx::shape::from_permutation(DType, {1, 32, 16, 16}, {0, 2, 3, 1}); auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s2); mm->add_instruction(migraphx::make_op("add"), x, y); return p; } }; template struct test_add_mixed_layout; template struct test_add_mixed_layout; template struct test_add_mixed_layout; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_nhwc.cpp000066400000000000000000000034561510465702400223450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_nhwc : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape::from_permutation( migraphx::shape::float_type, {4, 3, 8, 8}, {0, 2, 3, 1}); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_return({add}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_relu.cpp000066400000000000000000000034761510465702400223570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_relu : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_instruction(migraphx::make_op("relu"), add); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_relu_add.cpp000066400000000000000000000040611510465702400231560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_relu_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 5, 2, 2}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {1, 5, 2, 2}}); auto z = mm->add_parameter("z", migraphx::shape{migraphx::shape::float_type, {1, 5, 2, 2}}); auto a = mm->add_instruction(migraphx::make_op("add"), x, y); auto b = mm->add_instruction(migraphx::make_op("relu"), a); auto c = mm->add_instruction(migraphx::make_op("add"), b, z); mm->add_instruction(migraphx::make_op("relu"), c); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_sigmoid.cpp000066400000000000000000000035071510465702400230360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_sigmoid : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_instruction(migraphx::make_op("sigmoid"), add); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_sub_concat_slice_mul.cpp000066400000000000000000000045541510465702400255620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include // test for fuse_concat pass where pointwise input to concat has multiple outputs struct test_add_sub_concat_slice_mul : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {1, 4, 8, 8}}; auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); auto sub = mm->add_instruction(migraphx::make_op("sub"), x, y); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), add, sub); auto relu = mm->add_instruction(migraphx::make_op("relu"), concat); auto slice = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {2}}, {"ends", {6}}}), relu); auto mul = mm->add_instruction(migraphx::make_op("mul"), sub, slice); mm->add_return({mul}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_add_tanh.cpp000066400000000000000000000034761510465702400223420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_add_tanh : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_instruction(migraphx::make_op("tanh"), add); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_and.cpp000066400000000000000000000032761510465702400213400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_and : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("logical_and"), x, y); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_arg_ops.cpp000066400000000000000000000267601510465702400222330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_arg_ops : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {2, 1, 4, 1025}}; auto param = mm->add_parameter("data", s); switch(NonStdShape) { case 0: param = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), param); break; case 1: param = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 1025}}}), param); break; case 2: param = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {1}}, {"ends", {3}}}), param); break; default: break; } mm->add_instruction(T{Axis, LastIndex}, param); return p; } std::string section() const { return "reduce"; } }; // transpose argmax tests template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; // transpose argmin tests template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; // broadcast argmax tests template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; // broadcast argmin tests template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; // slice argmax tests template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; // slice argmin tests template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; // default case, standard shape argmax tests template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; // default case, standard shape argmin tests template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; template struct test_arg_ops; ROCm-AMDMIGraphX-46524e8/test/verify/test_asin.cpp000066400000000000000000000040771510465702400215300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_asin : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("asin"), x); return p; } }; template struct test_asin; template struct test_asin; template struct test_asin; template struct test_asin; template struct test_asin; template struct test_asin; template struct test_asin; ROCm-AMDMIGraphX-46524e8/test/verify/test_asinh.cpp000066400000000000000000000041111510465702400216650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_asinh : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("asinh"), x); return p; } }; template struct test_asinh; template struct test_asinh; template struct test_asinh; template struct test_asinh; template struct test_asinh; template struct test_asinh; template struct test_asinh; ROCm-AMDMIGraphX-46524e8/test/verify/test_atan.cpp000066400000000000000000000040771510465702400215210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_atan : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("atan"), x); return p; } }; template struct test_atan; template struct test_atan; template struct test_atan; template struct test_atan; template struct test_atan; template struct test_atan; template struct test_atan; ROCm-AMDMIGraphX-46524e8/test/verify/test_atanh.cpp000066400000000000000000000053061510465702400216650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_atanh : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::type_t dtype = migraphx::shape::get_type(); migraphx::shape s{dtype, {16}}; auto x = mm->add_parameter("x", s); auto min_val = mm->add_literal(migraphx::literal{migraphx::shape{dtype}, {-0.875f}}); auto max_val = mm->add_literal(migraphx::literal{migraphx::shape{dtype}, {0.875f}}); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {16}}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {16}}}), max_val); auto cx = mm->add_instruction(migraphx::make_op("clip"), x, min_val, max_val); auto atanh_x = mm->add_instruction(migraphx::make_op("atanh"), cx); mm->add_return({atanh_x}); return p; } }; template struct test_atanh; template struct test_atanh; template struct test_atanh; template struct test_atanh; template struct test_atanh; template struct test_atanh; template struct test_atanh; ROCm-AMDMIGraphX-46524e8/test/verify/test_avg_pooling_1d.cpp000066400000000000000000000033721510465702400234630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_avg_pooling_1d : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 5}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average, {0}, {1}, {3}, {1}}; mm->add_instruction(op, input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_avg_pooling_3d.cpp000066400000000000000000000034451510465702400234660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_avg_pooling_3d : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 5, 5, 5}}); auto op = migraphx::op::pooling{ migraphx::op::pooling_mode::average, {1, 1, 1}, {3, 3, 3}, {3, 3, 3}, {1, 1, 1}}; mm->add_instruction(op, input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_avg_pooling_3d_opt.cpp000066400000000000000000000034551510465702400243510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_avg_pooling_3d_opt : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 2, 3, 3, 3}}); auto op = migraphx::op::pooling{ migraphx::op::pooling_mode::average, {0, 0, 0}, {1, 1, 1}, {3, 3, 3}, {1, 1, 1}}; mm->add_instruction(op, input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_avg_pooling_asym_pad.cpp000066400000000000000000000040621510465702400247510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_avg_pooling_asym_pad : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 64, 112, 112}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0, 1, 1}}, {"stride", {2, 2}}, {"lengths", {3, 3}}}), input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_avg_pooling_ceil_3d.cpp000066400000000000000000000034661510465702400244650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_avg_pooling_ceil_3d : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 5, 5, 5}}); auto op = migraphx::op::pooling{ migraphx::op::pooling_mode::average, {1, 1, 1}, {3, 3, 3}, {3, 3, 3}, {1, 1, 1}, true}; mm->add_instruction(op, input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_avg_pooling_include_pad.cpp000066400000000000000000000041711510465702400254240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_avg_pooling_include_pad : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 8, 8}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {2, 2}}, {"stride", {1, 1}}, {"lengths", {3, 3}}, {"count_include_pad", true}}), input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_avg_pooling_nhwc.cpp000066400000000000000000000041011510465702400241050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_avg_pooling_nhwc : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape::from_permutation( migraphx::shape::float_type, {1, 64, 113, 113}, {0, 2, 3, 1})); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"stride", {2, 2}}, {"lengths", {3, 3}}}), input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_avg_pooling_pad.cpp000066400000000000000000000034511510465702400237210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_avg_pooling_pad : verify_program { migraphx::program create_program() const { // pooling test with nonzero padding migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 7}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average, {2}, {1}, {3}, {1}}; mm->add_instruction(op, input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_batch_quant_dot_1.cpp000066400000000000000000000054761510465702400241610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_batch_quant_dot_1 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::get_type{}; auto ctype = migraphx::shape::get_type{}; migraphx::shape m1_shape{dtype, {3, 2, 8, 2}}; migraphx::shape m2_shape{dtype, {3, 2, 7, 8}}; migraphx::shape m3_shape{ctype, {3, 2, 2, 7}}; auto l1 = mm->add_parameter("a", m1_shape); auto tl1 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l1); auto l2 = mm->add_parameter("b", m2_shape); auto tl2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l2); auto l3 = mm->add_parameter("c", m3_shape); migraphx::add_apply_alpha_beta( *mm, {tl1, tl2, l3}, migraphx::make_op("quant_dot"), CType{3}, CType{2}); return p; } std::string section() const { return "gemm"; } }; template struct test_batch_quant_dot_1; template struct test_batch_quant_dot_1; template struct test_batch_quant_dot_1; template struct test_batch_quant_dot_1; template struct test_batch_quant_dot_1; ROCm-AMDMIGraphX-46524e8/test/verify/test_batch_quant_dot_2.cpp000066400000000000000000000051061510465702400241500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_batch_quant_dot_2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::get_type{}; auto ctype = migraphx::shape::get_type{}; migraphx::shape m1_shape{dtype, {3, 2, 2, 8}}; migraphx::shape m2_shape{dtype, {3, 2, 8, 7}}; migraphx::shape m3_shape{ctype, {3, 2, 2, 7}}; auto l1 = mm->add_parameter("a", m1_shape); auto l2 = mm->add_parameter("b", m2_shape); auto l3 = mm->add_parameter("c", m3_shape); migraphx::add_apply_alpha_beta( *mm, {l1, l2, l3}, migraphx::make_op("quant_dot"), CType{1}, CType{3}); return p; } std::string section() const { return "gemm"; } }; template struct test_batch_quant_dot_2; template struct test_batch_quant_dot_2; template struct test_batch_quant_dot_2; template struct test_batch_quant_dot_2; template struct test_batch_quant_dot_2; ROCm-AMDMIGraphX-46524e8/test/verify/test_batch_quant_dot_3.cpp000066400000000000000000000043521510465702400241530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_batch_quant_dot_3 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {3, 2, 2, 6}}; migraphx::shape m2_shape{DType, {3, 2, 6, 7}}; auto l1 = mm->add_parameter("a", m1_shape); auto l2 = mm->add_parameter("b", m2_shape); mm->add_instruction(migraphx::make_op("quant_dot"), l1, l2); return p; } std::string section() const { return "gemm"; } }; template struct test_batch_quant_dot_3; template struct test_batch_quant_dot_3; template struct test_batch_quant_dot_3; template struct test_batch_quant_dot_3; template struct test_batch_quant_dot_3; ROCm-AMDMIGraphX-46524e8/test/verify/test_batch_quant_dot_4.cpp000066400000000000000000000047421510465702400241570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_batch_quant_dot_4 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 4, 6, 3}}; migraphx::shape m2_shape{DType, {7, 2, 6, 3}}; auto l1 = mm->add_parameter("a", m1_shape); auto l2 = mm->add_parameter("b", m2_shape); auto tl1 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {3, 0, 1, 2}}}), l1); auto tl2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {3, 1, 2, 0}}}), l2); mm->add_instruction(migraphx::make_op("quant_dot"), tl1, tl2); return p; } std::string section() const { return "gemm"; } }; template struct test_batch_quant_dot_4; template struct test_batch_quant_dot_4; template struct test_batch_quant_dot_4; template struct test_batch_quant_dot_4; template struct test_batch_quant_dot_4; ROCm-AMDMIGraphX-46524e8/test/verify/test_batch_quant_dot_5.cpp000066400000000000000000000051721510465702400241560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_batch_quant_dot_5 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {3, 2, 7, 2}}; migraphx::shape m2_shape{DType, {3, 2, 5, 7}}; auto l1 = mm->add_parameter("a", m1_shape); auto l2 = mm->add_parameter("b", m2_shape); auto tl1 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l1); auto sl1 = mm->add_instruction(migraphx::make_op("add"), tl1, tl1); auto tl2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), l2); auto sl2 = mm->add_instruction(migraphx::make_op("add"), tl2, tl2); mm->add_instruction(migraphx::make_op("quant_dot"), sl1, sl2); return p; } std::string section() const { return "gemm"; } }; template struct test_batch_quant_dot_5; template struct test_batch_quant_dot_5; template struct test_batch_quant_dot_5; template struct test_batch_quant_dot_5; template struct test_batch_quant_dot_5; ROCm-AMDMIGraphX-46524e8/test/verify/test_bit_cast.cpp000066400000000000000000000047051510465702400223640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_bit_cast : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{From, {8}}; auto pa = mm->add_parameter("a", s); auto pb = mm->add_parameter("b", s); auto ia = mm->add_instruction( migraphx::make_op("bit_cast", {{"target_type", migraphx::to_value(To)}}), pa); auto ib = mm->add_instruction( migraphx::make_op("bit_cast", {{"target_type", migraphx::to_value(To)}}), pb); auto ret = mm->add_instruction(migraphx::make_op("add"), ia, ib); mm->add_return({ret}); return p; }; }; template struct test_bit_cast; template struct test_bit_cast; template struct test_bit_cast; template struct test_bit_cast; ROCm-AMDMIGraphX-46524e8/test/verify/test_bitwise_and.cpp000066400000000000000000000035501510465702400230610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_bitwise_and : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {8}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("bitwise_and"), x, y); return p; } }; template struct test_bitwise_and; template struct test_bitwise_and; ROCm-AMDMIGraphX-46524e8/test/verify/test_block_reduce_small.cpp000066400000000000000000000063111510465702400244000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_block_reduce_small : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{T, {2, N}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto two = mm->add_literal(migraphx::literal{migraphx::shape{s.type(), {1}}, {2}}); auto mul = migraphx::add_common_op(*mm, migraphx::make_op("div"), {x, two}); auto r = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), mul); auto rb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), r); auto add = mm->add_instruction(migraphx::make_op("add"), rb, y); mm->add_return({add}); return p; }; std::string section() const { return "reduce"; } }; template struct test_block_reduce_small_for_size : test_block_reduce_small, test_block_reduce_small, test_block_reduce_small, test_block_reduce_small { }; template struct test_block_reduce_small_for_size<2>; template struct test_block_reduce_small_for_size<3>; template struct test_block_reduce_small_for_size<4>; template struct test_block_reduce_small_for_size<8>; template struct test_block_reduce_small_for_size<16>; template struct test_block_reduce_small_for_size<25>; template struct test_block_reduce_small_for_size<32>; template struct test_block_reduce_small_for_size<64>; template struct test_block_reduce_small_for_size<67>; template struct test_block_reduce_small_for_size<128>; template struct test_block_reduce_small_for_size<129>; ROCm-AMDMIGraphX-46524e8/test/verify/test_ceil.cpp000066400000000000000000000041211510465702400215000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_ceil : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {2, 3, 4, 6}}; auto param = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("ceil"), param); return p; }; }; template struct test_ceil; template struct test_ceil; template struct test_ceil; template struct test_ceil; template struct test_ceil; template struct test_ceil; template struct test_ceil; ROCm-AMDMIGraphX-46524e8/test/verify/test_ck_gemm_softmax_gemm.cpp000066400000000000000000000052761510465702400247500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_ck_gemm_softmax_gemm : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::half_type, {1, 12, 256, 256}}; migraphx::shape m2_shape{migraphx::shape::half_type, {1, 12, 256, 256}}; auto m2_elements = m2_shape.elements(); auto a = mm->add_parameter("1", m1_shape); auto b = mm->add_parameter("2", m1_shape); auto b1 = mm->add_parameter("3", m1_shape); std::vector eights(m2_elements, 0.125); auto eight = mm->add_literal(migraphx::literal{m2_shape, eights}); std::vector zeros(m2_elements, 0); auto zero = mm->add_literal(migraphx::literal{m2_shape, zeros}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto scale = mm->add_instruction(migraphx::make_op("mul"), gemm1, eight); auto bias = mm->add_instruction(migraphx::make_op("add"), scale, zero); auto softmax = mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), bias); mm->add_instruction(migraphx::make_op("dot"), softmax, b1); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_ck_gemm_softmax_gemm_bf16.cpp000066400000000000000000000054151510465702400255610ustar00rootroot00000000000000// /* // * The MIT License (MIT) // * // * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. // * // * 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. // */ #include "verify_program.hpp" #include #include #include struct test_ck_gemm_softmax_gemm_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::bf16_type, {1, 12, 256, 256}}; migraphx::shape m2_shape{migraphx::shape::bf16_type, {1, 12, 256, 256}}; auto m2_elements = m2_shape.elements(); auto a = mm->add_parameter("1", m1_shape); auto b = mm->add_parameter("2", m1_shape); auto b1 = mm->add_parameter("3", m1_shape); std::vector eights(m2_elements, 0.125); auto eight = mm->add_literal(migraphx::literal{m2_shape, eights}); std::vector zeros(m2_elements, 0); auto zero = mm->add_literal(migraphx::literal{m2_shape, zeros}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto scale = mm->add_instruction(migraphx::make_op("mul"), gemm1, eight); auto bias = mm->add_instruction(migraphx::make_op("add"), scale, zero); auto softmax = mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), bias); mm->add_instruction(migraphx::make_op("dot"), softmax, b1); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_clip.cpp000066400000000000000000000037421510465702400215230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_clip : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {3}}); auto min_val = mm->add_literal(0.0f); auto max_val = mm->add_literal(6.0f); min_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), min_val); max_val = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3}}}), max_val); mm->add_instruction(migraphx::make_op("clip"), x, min_val, max_val); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_axis_0.cpp000066400000000000000000000046731510465702400233120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_concat_axis_0 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); int axis = 0; migraphx::shape s0{DType, {2, 2}}; migraphx::shape s1{DType, {3, 2}}; migraphx::shape s2{DType, {1, 2}}; auto l0 = mm->add_parameter("x", s0); auto l1 = mm->add_parameter("y", s1); auto l2 = mm->add_parameter("z", s2); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); return p; } }; template struct test_concat_axis_0; template struct test_concat_axis_0; template struct test_concat_axis_0; template struct test_concat_axis_0; template struct test_concat_axis_0; template struct test_concat_axis_0; template struct test_concat_axis_0; template struct test_concat_axis_0; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_axis_1.cpp000066400000000000000000000036641510465702400233120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_axis_1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); int axis = 1; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {2, 3}}; migraphx::shape s2{migraphx::shape::int32_type, {2, 1}}; auto l0 = mm->add_parameter("x", s0); auto l1 = mm->add_parameter("y", s1); auto l2 = mm->add_parameter("z", s2); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_axis_2.cpp000066400000000000000000000035151510465702400233060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_axis_2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {3, 2, 1}}; auto x0 = mm->add_parameter("x0", s); auto x1 = mm->add_parameter("x1", s); auto x2 = mm->add_parameter("x2", s); auto x3 = mm->add_parameter("x3", s); mm->add_instruction(migraphx::make_op("concat", {{"axis", 2}}), x0, x1, x2, x3); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_axis_neg_1.cpp000066400000000000000000000036751510465702400241450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_axis_neg_1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); int axis = -1; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {2, 3}}; migraphx::shape s2{migraphx::shape::int32_type, {2, 1}}; auto l0 = mm->add_parameter("x", s0); auto l1 = mm->add_parameter("y", s1); auto l2 = mm->add_parameter("z", s2); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_broadcast_add.cpp000066400000000000000000000043121510465702400246670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_broadcast_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1, 2, 4}}; migraphx::shape s1{migraphx::shape::float_type, {1, 6, 4}}; migraphx::shape s2{migraphx::shape::float_type, {6, 1}}; auto x = mm->add_parameter("x", s0); auto y = mm->add_parameter("y", s0); auto z = mm->add_parameter("z", s0); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y, z); auto b = mm->add_literal(migraphx::generate_literal(s2, 15)); auto bb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s1.lens()}}), b); mm->add_instruction(migraphx::make_op("add"), concat, bb); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_multi_out.cpp000066400000000000000000000042171510465702400241420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_multi_out : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); int axis = 0; migraphx::shape s0{migraphx::shape::float_type, {2, 2}}; migraphx::shape s1{migraphx::shape::float_type, {3, 2}}; migraphx::shape s2{migraphx::shape::float_type, {5, 2}}; auto x = mm->add_parameter("x", s0); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), x, y); auto relu = mm->add_instruction(migraphx::make_op("relu"), concat); auto add = mm->add_instruction(migraphx::make_op("add"), relu, z); mm->add_return({relu, add}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_nhwc.cpp000066400000000000000000000041311510465702400230530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_concat_nhwc : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{DType, {2, 64, 56, 56}, {200704, 1, 3584, 64}}; auto x = mm->add_parameter("x", s0); auto y = mm->add_parameter("y", s0); mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), x, y); return p; } }; template struct test_concat_nhwc; template struct test_concat_nhwc; template struct test_concat_nhwc; template struct test_concat_nhwc; template struct test_concat_nhwc; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_pooling.cpp000066400000000000000000000051051510465702400235650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_concat_pooling : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 256, 8, 8}}); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), input); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 3}}), transpose); auto concat_t = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), concat); auto pooling = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {0, 0}}, {"stride", {1, 1}}, {"lengths", {8, 8}}, {"dilations", {1, 1}}}), concat_t); mm->add_instruction(migraphx::make_op("relu"), pooling); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_relu.cpp000066400000000000000000000043101510465702400230620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_relu : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); int axis = 0; migraphx::shape s0{migraphx::shape::float_type, {2, 2}}; migraphx::shape s1{migraphx::shape::float_type, {3, 2}}; migraphx::shape s2{migraphx::shape::float_type, {1, 2}}; auto l0 = mm->add_parameter("x", s0); auto l1 = mm->add_parameter("y", s1); auto l2 = mm->add_parameter("z", s2); auto r0 = mm->add_instruction(migraphx::make_op("relu"), l0); auto r1 = mm->add_instruction(migraphx::make_op("relu"), l1); auto r2 = mm->add_instruction(migraphx::make_op("relu"), l2); auto c0 = mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), r0, r1, r2); mm->add_instruction(migraphx::make_op("relu"), c0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_transpose.cpp000066400000000000000000000040571510465702400241410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_transpose : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); int axis = 1; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {3, 2}}; migraphx::shape s2{migraphx::shape::int32_type, {2, 4}}; auto l0 = mm->add_parameter("x", s0); auto lp1 = mm->add_parameter("y", s1); auto l1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), lp1); auto l2 = mm->add_parameter("z", s2); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_transpose2.cpp000066400000000000000000000040621510465702400242170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_transpose2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); int axis = 1; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {2, 3}}; migraphx::shape s2{migraphx::shape::int32_type, {5, 2}}; auto l0 = mm->add_parameter("x", s0); auto l1 = mm->add_parameter("y", s1); auto lp2 = mm->add_parameter("z", s2); auto l2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), lp2); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_concat_transpose3.cpp000066400000000000000000000042451510465702400242230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_concat_transpose3 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); int axis = 1; migraphx::shape s0{migraphx::shape::int32_type, {2, 2}}; migraphx::shape s1{migraphx::shape::int32_type, {3, 2}}; migraphx::shape s2{migraphx::shape::int32_type, {5, 2}}; auto l0 = mm->add_parameter("x", s0); auto lp1 = mm->add_parameter("y", s1); auto l1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), lp1); auto lp2 = mm->add_parameter("z", s2); auto l2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), lp2); mm->add_instruction(migraphx::make_op("concat", {{"axis", axis}}), l0, l1, l2); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_contiguous.cpp000066400000000000000000000041461510465702400227720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_contiguous : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {4, 4, 4, 3}, {48, 4, 1, 16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("contiguous"), x); assert(p.get_output_shapes().back().standard()); return p; } }; template struct test_contiguous; template struct test_contiguous; template struct test_contiguous; template struct test_contiguous; template struct test_contiguous; ROCm-AMDMIGraphX-46524e8/test/verify/test_contiguous_broadcast.cpp000066400000000000000000000034111510465702400250060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_contiguous_broadcast : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 2}, {0, 1}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("contiguous"), x); assert(p.get_output_shapes().back().standard()); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_contiguous_broadcast_transpose.cpp000066400000000000000000000034531510465702400271120ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_contiguous_broadcast_transpose : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 3072, 768}, {0, 1, 3072}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("contiguous"), x); assert(p.get_output_shapes().back().standard()); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv.cpp000066400000000000000000000042451510465702400215400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_conv : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {4, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {4, 3, 3, 3}}); mm->add_instruction(migraphx::make_op("convolution"), input, weights); return p; } std::string section() const { return "conv"; } }; template struct test_conv; template struct test_conv; template struct test_conv; template struct test_conv; template struct test_conv; template struct test_conv; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv2.cpp000066400000000000000000000044701510465702400216220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_conv2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {1, 512, 28, 28}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {256, 512, 1, 1}}); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), input, weights); return p; } std::string section() const { return "conv"; } }; template struct test_conv2; template struct test_conv2; template struct test_conv2; template struct test_conv2; template struct test_conv2; template struct test_conv2; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv3d.cpp000066400000000000000000000037571510465702400217760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_conv3d : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3, 3}}); mm->add_instruction( migraphx::make_op( "convolution", {{"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"dilation", {1, 1, 1}}}), input, weights); return p; } std::string section() const { return "conv"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_add.cpp000066400000000000000000000050761510465702400223530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_conv_add : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {DType, {1, 8, 4, 4}}); auto w = mm->add_literal(migraphx::generate_literal({DType, {2, 8, 3, 3}}, 1)); auto y = mm->add_parameter("y", {DType, {1, 8, 4, 4}}); auto v = mm->add_literal(migraphx::generate_literal({DType, {2, 8, 3, 3}}, 2)); auto conv1 = mm->add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = mm->add_instruction(migraphx::make_op("convolution"), y, v); auto sum = mm->add_instruction(migraphx::make_op("add"), conv1, conv2); mm->add_instruction(migraphx::make_op("exp"), sum); return p; } std::string section() const { return "conv"; } }; template struct test_conv_add; template struct test_conv_add; template struct test_conv_add; template struct test_conv_add; template struct test_conv_add; template struct test_conv_add; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_add_1x1_diff_strides.cpp000066400000000000000000000057151510465702400255710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_conv_add_1x1_diff_strides : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {DType, {1, 8, 2, 2}}); auto w = mm->add_literal(migraphx::generate_literal({DType, {2, 8, 1, 1}}, 1)); auto y = mm->add_parameter("y", {DType, {1, 8, 4, 4}}); auto v = mm->add_literal(migraphx::generate_literal({DType, {2, 8, 1, 1}}, 2)); auto conv1 = mm->add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 2}}}), y, v); auto sum = mm->add_instruction(migraphx::make_op("add"), conv1, conv2); mm->add_instruction(migraphx::make_op("exp"), sum); return p; } std::string section() const { return "conv"; } // Turn on Exhaustive-tune to enable split-k perf-configs from MLIR migraphx::compile_options get_compile_options() const { return migraphx::compile_options{.exhaustive_tune = true}; } }; template struct test_conv_add_1x1_diff_strides; template struct test_conv_add_1x1_diff_strides; template struct test_conv_add_1x1_diff_strides; template struct test_conv_add_1x1_diff_strides; template struct test_conv_add_1x1_diff_strides; template struct test_conv_add_1x1_diff_strides; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_add_dot.cpp000066400000000000000000000060561510465702400232200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_conv_add_dot : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {4, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {4, 3, 3, 3}}); auto bias_literal = migraphx::literal{migraphx::shape{DType, {4}}, {2.0f, 2.0f, 2.0f, 2.0f}}; auto bias = mm->add_literal(bias_literal); auto conv = mm->add_instruction(migraphx::make_op("convolution"), input, weights); auto bcast_bias = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv->get_shape().lens()}}), bias); auto bias_add = mm->add_instruction(migraphx::make_op("add"), conv, bcast_bias); // Create a literal for dot (matmul) with shape {4, 3, 3, 3} std::vector bias_add_lens = bias_add->get_shape().lens(); // The shape is {4, 3, 3, 3}, so we want a rhs shape {4, 3, 3, 3} migraphx::shape dot_rhs_shape{DType, bias_add_lens}; std::vector dot_rhs_data(dot_rhs_shape.elements(), 1.0f); auto dot_rhs = mm->add_literal(migraphx::literal{dot_rhs_shape, dot_rhs_data}); // Matmul (dot) with same shape, so this is elementwise matmul auto dot = mm->add_instruction(migraphx::make_op("dot"), bias_add, dot_rhs); mm->add_return({dot}); return p; } std::string section() const { return "conv"; } }; template struct test_conv_add_dot; template struct test_conv_add_dot; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_add_layernorm_conv.cpp000066400000000000000000000070321510465702400254620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_conv_add_layernorm_conv : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {2, 4, 64, 64}}); auto weights1 = mm->add_parameter("w1", migraphx::shape{DType, {320, 4, 3, 3}}); auto weights2 = mm->add_parameter("w2", migraphx::shape{DType, {4, 320, 3, 3}}); auto bias_literal = abs(migraphx::generate_literal(migraphx::shape{DType, {320}}, 1)); auto bias = mm->add_literal(bias_literal); auto conv1 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), input, weights1); auto bcast_bias = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv1->get_shape().lens()}}), bias); auto bias_add = mm->add_instruction(migraphx::make_op("add"), conv1, bcast_bias); auto rsp_add = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {0, 32, -1}}}), bias_add); float eps = 1e-12; if constexpr((DType) == migraphx::shape::fp8e4m3fnuz_type) { // use 0.250 for fp8 eps = 0.250; } else if constexpr((DType) == migraphx::shape::half_type) { eps = 1e-6; } auto layernorm = add_layernorm(*mm, rsp_add, rsp_add->get_shape().lens(), eps); auto layernorm_rsp = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {0, 320, 64, 64}}}), layernorm); mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 1}}}), layernorm_rsp, weights2); return p; } std::string section() const { return "conv"; } }; template struct test_conv_add_layernorm_conv; template struct test_conv_add_layernorm_conv; template struct test_conv_add_layernorm_conv; template struct test_conv_add_layernorm_conv; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_add_relu.cpp000066400000000000000000000054241510465702400233770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_conv_add_relu : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {4, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {4, 3, 3, 3}}); auto bias_literal = migraphx::literal{migraphx::shape{DType, {4}}, {2.0f, 2.0f, 2.0f, 2.0f}}; auto bias = mm->add_literal(bias_literal); auto conv = mm->add_instruction(migraphx::make_op("convolution"), input, weights); auto bcast_bias = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv->get_shape().lens()}}), bias); auto bias_add = mm->add_instruction(migraphx::make_op("add"), conv, bcast_bias); mm->add_instruction(migraphx::make_op("relu"), bias_add); return p; } std::string section() const { return "conv"; } }; template struct test_conv_add_relu; template struct test_conv_add_relu; template struct test_conv_add_relu; template struct test_conv_add_relu; template struct test_conv_add_relu; template struct test_conv_add_relu; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_add_tune.cpp000066400000000000000000000074701510465702400234060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_conv_add_tune : verify_program> { // this test is for testing MLIR split-k convolution perfConfigs and problemKey clash in problem // cache migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); // choose sizes such that, it would pick mlir for convolutions auto x1 = mm->add_parameter("x1", {DType, {1, 256, 16, 16}}); auto w1 = mm->add_literal(migraphx::generate_literal({DType, {1, 256, 3, 2}}, 1)); auto x2 = mm->add_parameter("x2", {DType, {1, 256, 16, 16}}); auto w2 = mm->add_literal(migraphx::generate_literal({DType, {1, 256, 3, 2}}, 1)); auto conv1 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 0}}, {"stride", {2, 2}}}), x1, w1); // add pooling so that it doesn't get fused with conv1. auto pooling = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {1, 1, 1, 1}}, {"stride", {1, 1}}, {"lengths", {3, 3}}, {"count_include_pad", false}}), conv1); // conv2 + pointwise-add auto conv2 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1, 1, 0}}, {"stride", {2, 2}}}), x2, w2); mm->add_instruction(migraphx::make_op("add"), pooling, conv2); return p; } // Turn on Exhaustive-tune to enable split-k perf-configs from MLIR migraphx::compile_options get_compile_options() const { return migraphx::compile_options{.exhaustive_tune = true}; } std::string section() const { return "conv"; } }; template struct test_conv_add_tune; template struct test_conv_add_tune; template struct test_conv_add_tune; template struct test_conv_add_tune; template struct test_conv_add_tune; template struct test_conv_add_tune; template struct test_conv_add_tune; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_bias_clipped_relu.cpp000066400000000000000000000064041510465702400252640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_conv_bias_clipped_relu : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {4, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {4, 3, 3, 3}}); auto l0 = migraphx::literal{migraphx::shape{DType, {4}}, {2.0f, 2.0f, 2.0f, 2.0f}}; auto bias = mm->add_literal(l0); auto conv = mm->add_instruction(migraphx::make_op("convolution"), input, weights); auto bcast_add = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv->get_shape().lens()}}), bias); auto bias_add = mm->add_instruction(migraphx::make_op("add"), conv, bcast_add); auto min_val = mm->add_literal(migraphx::literal(DType, {0.0f})); auto max_val = mm->add_literal(migraphx::literal(DType, {6.0f})); min_val = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", conv->get_shape().lens()}}), min_val); max_val = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", conv->get_shape().lens()}}), max_val); mm->add_instruction(migraphx::make_op("clip"), bias_add, min_val, max_val); return p; } std::string section() const { return "conv"; } }; template struct test_conv_bias_clipped_relu; template struct test_conv_bias_clipped_relu; template struct test_conv_bias_clipped_relu; template struct test_conv_bias_clipped_relu; template struct test_conv_bias_clipped_relu; template struct test_conv_bias_clipped_relu; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_bn.cpp000066400000000000000000000077611510465702400222250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_conv_bn : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape xs{DType, {1, 3, 224, 224}}; migraphx::shape ws{DType, {64, 3, 7, 7}}; migraphx::shape vars{DType, {64}}; auto x = mm->add_parameter("x", xs); auto w = mm->add_parameter("w", ws); // non-symmetrical tiling auto conv = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {3, 3}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto scale = mm->add_literal(migraphx::abs(migraphx::generate_literal(vars, 1))); auto bias = mm->add_literal(migraphx::abs(migraphx::generate_literal(vars, 2))); auto mean = mm->add_literal(migraphx::abs(migraphx::generate_literal(vars, 3))); auto variance = mm->add_literal(migraphx::abs(migraphx::generate_literal(vars, 4))); auto rt = mm->add_literal(migraphx::literal{DType, {0.5}}); auto eps = mm->add_literal(migraphx::literal{DType, {1e-5f}}); if constexpr(DType == migraphx::shape::fp8e4m3fnuz_type or DType == migraphx::shape::fp8e4m3fn_type or DType == migraphx::shape::fp8e5m2_type) { // use 0.250 for fp8 eps = mm->add_literal(migraphx::literal{DType, {0.250}}); } auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); auto usq_bias = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), bias); auto usq_mean = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), mean); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), variance); auto numer = add_common_op(*mm, migraphx::make_op("sub"), {conv, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto denom = add_common_op(*mm, migraphx::make_op("pow"), {var_eps, rt}); auto div0 = add_common_op(*mm, migraphx::make_op("div"), {numer, denom}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {div0, usq_scale}); add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); return p; } std::string section() const { return "conv"; } }; template struct test_conv_bn; template struct test_conv_bn; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_bn_add.cpp000066400000000000000000000117731510465702400230330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_conv_bn_add : verify_program> { static migraphx::instruction_ref add_bn(migraphx::module& m, migraphx::instruction_ref x) { auto bn_lens = x->get_shape().lens(); auto c_len = bn_lens.at(1); migraphx::shape vars{DType, {c_len}}; auto scale = m.add_literal(migraphx::abs(migraphx::generate_literal(vars, 1 + c_len))); auto bias = m.add_literal(migraphx::abs(migraphx::generate_literal(vars, 2 + c_len))); auto mean = m.add_literal(migraphx::abs(migraphx::generate_literal(vars, 3 + c_len))); auto variance = m.add_literal(migraphx::abs(migraphx::generate_literal(vars, 4 + c_len))); auto rt = m.add_literal(migraphx::literal{DType, {0.5}}); auto eps = m.add_literal(migraphx::literal{DType, {1e-5f}}); if constexpr(DType == migraphx::shape::fp8e4m3fnuz_type or DType == migraphx::shape::fp8e4m3fn_type or DType == migraphx::shape::fp8e5m2_type) { // use 0.250 for fp8 eps = m.add_literal(migraphx::literal{DType, {0.250}}); } auto usq_scale = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); auto usq_bias = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), bias); auto usq_mean = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), mean); auto usq_var = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), variance); auto numer = add_common_op(m, migraphx::make_op("sub"), {x, usq_mean}); auto var_eps = add_common_op(m, migraphx::make_op("add"), {usq_var, eps}); auto denom = add_common_op(m, migraphx::make_op("pow"), {var_eps, rt}); auto div0 = add_common_op(m, migraphx::make_op("div"), {numer, denom}); auto r0 = add_common_op(m, migraphx::make_op("mul"), {div0, usq_scale}); return add_common_op(m, migraphx::make_op("add"), {r0, usq_bias}); } migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::size_t ichannels = 64; std::size_t ochannels = 256; auto x = mm->add_parameter("x", {DType, {1, ichannels, 56, 56}}); auto w = mm->add_literal(migraphx::generate_literal({DType, {ochannels, ichannels, 1, 1}}, 1)); auto y = mm->add_parameter("y", {DType, {1, ichannels, 56, 56}}); auto v = mm->add_literal(migraphx::generate_literal({DType, {ochannels, ichannels, 1, 1}}, 2)); auto relu1 = mm->add_instruction(migraphx::make_op("relu"), x); auto conv1 = mm->add_instruction(migraphx::make_op("convolution"), relu1, w); auto bn1 = add_bn(*mm, conv1); auto relu2 = mm->add_instruction(migraphx::make_op("relu"), y); auto conv2 = mm->add_instruction(migraphx::make_op("convolution"), relu2, v); auto bn2 = add_bn(*mm, conv2); auto sum = mm->add_instruction(migraphx::make_op("add"), bn1, bn2); mm->add_instruction(migraphx::make_op("relu"), sum); return p; } std::string section() const { return "conv"; } }; template struct test_conv_bn_add; template struct test_conv_bn_add; template struct test_conv_bn_add; template struct test_conv_bn_add; template struct test_conv_bn_add; template struct test_conv_bn_add; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_bn_relu_pooling.cpp000066400000000000000000000111161510465702400247700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include #include template struct test_conv_bn_relu_pooling : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape xs{DType, {1, 3, 224, 224}}; migraphx::shape ws{DType, {64, 3, 7, 7}}; migraphx::shape vars{DType, {64}}; auto x = mm->add_parameter("x", xs); auto w = mm->add_parameter("w", ws); auto conv = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {3, 3}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x, w); auto scale = mm->add_literal(migraphx::abs(migraphx::generate_literal(vars, 1))); auto bias = mm->add_literal(migraphx::abs(migraphx::generate_literal(vars, 2))); auto mean = mm->add_literal(migraphx::abs(migraphx::generate_literal(vars, 3))); auto variance = mm->add_literal(migraphx::abs(migraphx::generate_literal(vars, 4))); auto rt = mm->add_literal(migraphx::literal{DType, {0.5}}); auto eps = mm->add_literal(migraphx::literal{DType, {1e-5f}}); if constexpr(DType == migraphx::shape::fp8e4m3fnuz_type or DType == migraphx::shape::fp8e4m3fn_type or DType == migraphx::shape::fp8e5m2_type) { // use 0.250 for fp8 eps = mm->add_literal(migraphx::literal{DType, {0.250}}); } auto usq_scale = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); auto usq_bias = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), bias); auto usq_mean = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), mean); auto usq_var = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), variance); auto numer = add_common_op(*mm, migraphx::make_op("sub"), {conv, usq_mean}); auto var_eps = add_common_op(*mm, migraphx::make_op("add"), {usq_var, eps}); auto denom = add_common_op(*mm, migraphx::make_op("pow"), {var_eps, rt}); auto div0 = add_common_op(*mm, migraphx::make_op("div"), {numer, denom}); auto r0 = add_common_op(*mm, migraphx::make_op("mul"), {div0, usq_scale}); auto bn = add_common_op(*mm, migraphx::make_op("add"), {r0, usq_bias}); auto relu = mm->add_instruction(migraphx::make_op("relu"), bn); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {1, 1}}, {"stride", {2, 2}}, {"lengths", {3, 3}}, {"dilations", {1, 1}}}), relu); return p; } std::string section() const { return "conv"; } }; template struct test_conv_bn_relu_pooling; template struct test_conv_bn_relu_pooling; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_bn_relu_pooling2.cpp000066400000000000000000000131761510465702400250620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include #include template struct test_conv_bn_relu_pooling2 : verify_program> { static migraphx::instruction_ref add_bn(migraphx::module& m, migraphx::instruction_ref x) { auto bn_lens = x->get_shape().lens(); auto c_len = bn_lens.at(1); migraphx::shape vars{DType, {c_len}}; auto scale = m.add_literal(migraphx::abs(migraphx::generate_literal(vars, 1 + c_len))); auto bias = m.add_literal(migraphx::abs(migraphx::generate_literal(vars, 2 + c_len))); auto mean = m.add_literal(migraphx::abs(migraphx::generate_literal(vars, 3 + c_len))); auto variance = m.add_literal(migraphx::abs(migraphx::generate_literal(vars, 4 + c_len))); auto rt = m.add_literal(migraphx::literal{DType, {0.5}}); auto eps = m.add_literal(migraphx::literal{DType, {1e-5f}}); if constexpr(DType == migraphx::shape::fp8e4m3fnuz_type or DType == migraphx::shape::fp8e4m3fn_type or DType == migraphx::shape::fp8e5m2_type) { // use 0.250 for fp8 eps = m.add_literal(migraphx::literal{DType, {0.250}}); } auto usq_scale = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); auto usq_bias = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), bias); auto usq_mean = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), mean); auto usq_var = m.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), variance); auto numer = add_common_op(m, migraphx::make_op("sub"), {x, usq_mean}); auto var_eps = add_common_op(m, migraphx::make_op("add"), {usq_var, eps}); auto denom = add_common_op(m, migraphx::make_op("pow"), {var_eps, rt}); auto div0 = add_common_op(m, migraphx::make_op("div"), {numer, denom}); auto r0 = add_common_op(m, migraphx::make_op("mul"), {div0, usq_scale}); return add_common_op(m, migraphx::make_op("add"), {r0, usq_bias}); } migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape xs1{DType, {1, 512, 7, 7}}; migraphx::shape xs2{DType, {1, 1024, 14, 14}}; migraphx::shape ws1{DType, {2048, 512, 1, 1}}; migraphx::shape ws2{DType, {2048, 1024, 1, 1}}; auto x1 = mm->add_parameter("x1", xs1); auto w1 = mm->add_parameter("w1", ws1); auto conv1 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}), x1, w1); auto bn1 = add_bn(*mm, conv1); auto x2 = mm->add_parameter("x2", xs2); auto w2 = mm->add_parameter("w2", ws2); auto conv2 = mm->add_instruction( migraphx::make_op("convolution", {{"padding", {0, 0}}, {"stride", {2, 2}}, {"dilation", {1, 1}}}), x2, w2); auto bn2 = add_bn(*mm, conv2); auto add = mm->add_instruction(migraphx::make_op("add"), bn1, bn2); auto relu = mm->add_instruction(migraphx::make_op("relu"), add); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::average}, {"padding", {1, 1}}, {"stride", {2, 2}}, {"lengths", {3, 3}}, {"dilations", {1, 1}}}), relu); return p; } std::string section() const { return "conv"; } }; template struct test_conv_bn_relu_pooling2; template struct test_conv_bn_relu_pooling2; template struct test_conv_bn_relu_pooling2; template struct test_conv_bn_relu_pooling2; template struct test_conv_bn_relu_pooling2; template struct test_conv_bn_relu_pooling2; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_concat.cpp000066400000000000000000000064541510465702400230730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_conv_concat : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 8, 4, 4}}); auto w = mm->add_literal( migraphx::generate_literal({migraphx::shape::float_type, {2, 8, 3, 3}}, 1)); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {1, 8, 4, 4}}); auto v = mm->add_literal( migraphx::generate_literal({migraphx::shape::float_type, {2, 8, 3, 3}}, 2)); auto conv1 = mm->add_instruction(migraphx::make_op("convolution"), x, w); auto conv2 = mm->add_instruction(migraphx::make_op("convolution"), y, v); auto sum = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), conv1, conv2); mm->add_instruction(migraphx::make_op("exp"), sum); return p; } std::string section() const { return "conv"; } }; struct test_conv_concat_group : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 8, 4, 4}}); auto w = mm->add_literal( migraphx::generate_literal({migraphx::shape::float_type, {2, 4, 3, 3}}, 1)); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {1, 8, 4, 4}}); auto v = mm->add_literal( migraphx::generate_literal({migraphx::shape::float_type, {2, 4, 3, 3}}, 2)); auto conv1 = mm->add_instruction(migraphx::make_op("convolution", {{"group", 2}}), x, w); auto conv2 = mm->add_instruction(migraphx::make_op("convolution", {{"group", 2}}), y, v); auto sum = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), conv1, conv2); mm->add_instruction(migraphx::make_op("exp"), sum); return p; } std::string section() const { return "conv"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_group_add.cpp000066400000000000000000000046051510465702400235640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_conv_group_add : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {1, 68, 28, 28}}; auto x = mm->add_parameter("x", s); auto w = mm->add_parameter("w", {DType, {68, 17, 1, 1}}); auto b = mm->add_parameter("b", {DType, {68}}); auto conv = mm->add_instruction(migraphx::make_op("convolution", {{"group", 4}}), x, w); auto bb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 68, 28, 28}}}), b); mm->add_instruction(migraphx::make_op("add"), conv, bb); return p; } std::string section() const { return "conv"; } }; template struct test_conv_group_add; template struct test_conv_group_add; // TODO grouped convolutions are not supported with MLIR therefore disable it // template struct test_conv_group_add; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_pooling.cpp000066400000000000000000000047431510465702400232720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_conv_pooling : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {4, 3, 32, 32}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {4, 3, 3, 3}}); auto conv = mm->add_instruction(migraphx::make_op("convolution"), input, weights); auto pooling = mm->add_instruction( migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}}), conv); mm->add_instruction(migraphx::make_op("relu"), pooling); return p; } std::string section() const { return "conv"; } }; template struct test_conv_pooling; template struct test_conv_pooling; template struct test_conv_pooling; template struct test_conv_pooling; template struct test_conv_pooling; template struct test_conv_pooling; ROCm-AMDMIGraphX-46524e8/test/verify/test_conv_relu.cpp000066400000000000000000000044171510465702400225700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_conv_relu : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {4, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {4, 3, 3, 3}}); auto conv = mm->add_instruction(migraphx::make_op("convolution"), input, weights); mm->add_instruction(migraphx::make_op("relu"), conv); return p; } std::string section() const { return "conv"; } }; template struct test_conv_relu; template struct test_conv_relu; template struct test_conv_relu; template struct test_conv_relu; template struct test_conv_relu; template struct test_conv_relu; ROCm-AMDMIGraphX-46524e8/test/verify/test_convert.cpp000066400000000000000000000053761510465702400222610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_convert : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{From, {8, 24}}; migraphx::shape sb{From, {24, 6}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto ia = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(To)}}), pa); auto ib = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(To)}}), pb); mm->add_instruction(migraphx::make_op("dot"), ia, ib); return p; }; std::string section() const { return "gemm"; } }; template struct test_convert; template struct test_convert; template struct test_convert; template struct test_convert; template struct test_convert; template struct test_convert; template struct test_convert; ROCm-AMDMIGraphX-46524e8/test/verify/test_convolution_backwards.cpp000066400000000000000000000040321510465702400251650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_convolution_backwards : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {1, 1, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {1, 1, 3, 3}}); mm->add_instruction(migraphx::make_op("convolution_backwards"), input, weights); return p; } }; template struct test_convolution_backwards; template struct test_convolution_backwards; template struct test_convolution_backwards; ROCm-AMDMIGraphX-46524e8/test/verify/test_convolution_backwards_1d.cpp000066400000000000000000000037111510465702400255540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_convolution_backwards_1d : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {1, 1, 3}}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), input, weights); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_convolution_backwards_2d_alt.cpp000066400000000000000000000043031510465702400264130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_convolution_backwards_2d_alt : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {1, 1, 10, 10}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {1, 1, 3, 3}}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {2, 2}}, {"stride", {2, 2}}, {"dilation", {2, 2}}}), input, weights); return p; } }; template struct test_convolution_backwards_2d_alt; template struct test_convolution_backwards_2d_alt; template struct test_convolution_backwards_2d_alt; ROCm-AMDMIGraphX-46524e8/test/verify/test_convolution_backwards_2d_group.cpp000066400000000000000000000041521510465702400267710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_convolution_backwards_2d_group : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {1, 2, 10, 10}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {2, 4, 3, 3}}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"group", 2}}), input, weights); return p; } }; template struct test_convolution_backwards_2d_group; template struct test_convolution_backwards_2d_group; template struct test_convolution_backwards_2d_group; ROCm-AMDMIGraphX-46524e8/test/verify/test_convolution_backwards_2x3.cpp000066400000000000000000000042621510465702400256660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_convolution_backwards_2x3 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{DType, {1, 3, 6, 7}}); auto weights = mm->add_parameter("w", migraphx::shape{DType, {3, 4, 3, 3}}); mm->add_instruction( migraphx::make_op("convolution_backwards", {{"padding", {1, 1}}, {"stride", {2, 3}}, {"dilation", {1, 1}}}), input, weights); return p; } }; template struct test_convolution_backwards_2x3; template struct test_convolution_backwards_2x3; template struct test_convolution_backwards_2x3; ROCm-AMDMIGraphX-46524e8/test/verify/test_convolution_backwards_3d.cpp000066400000000000000000000037521510465702400255630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_convolution_backwards_3d : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {1, 1, 3, 3, 3}}); mm->add_instruction( migraphx::make_op( "convolution_backwards", {{"padding", {0, 0, 0}}, {"stride", {1, 1, 1}}, {"dilation", {1, 1, 1}}}), input, weights); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_cos.cpp000066400000000000000000000040641510465702400213560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_cos : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {8}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("cos"), x); return p; } }; template struct test_cos; template struct test_cos; template struct test_cos; template struct test_cos; template struct test_cos; template struct test_cos; template struct test_cos; ROCm-AMDMIGraphX-46524e8/test/verify/test_cosh.cpp000066400000000000000000000040771510465702400215320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_cosh : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("cosh"), x); return p; } }; template struct test_cosh; template struct test_cosh; template struct test_cosh; template struct test_cosh; template struct test_cosh; template struct test_cosh; template struct test_cosh; ROCm-AMDMIGraphX-46524e8/test/verify/test_dequantizelinear.cpp000066400000000000000000000037641510465702400241440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_dequantizelinear : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::int8_type, {2, 2, 2}}; migraphx::shape ss{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape sz{migraphx::shape::int8_type, {2, 2, 2}}; auto input1 = mm->add_parameter("x", sx); auto input2 = mm->add_parameter("x_scale", ss); auto input3 = mm->add_parameter("x_zero_point", sz); auto r = mm->add_instruction(migraphx::make_op("dequantizelinear"), input1, input2, input3); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_div.cpp000066400000000000000000000034701510465702400213540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_div : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto diff = mm->add_instruction(migraphx::make_op("div"), x, y); mm->add_instruction(migraphx::make_op("div"), diff, z); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_div2.cpp000066400000000000000000000037631510465702400214430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_div2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::shape b{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", b); auto zb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), z); auto diff = mm->add_instruction(migraphx::make_op("div"), x, y); mm->add_instruction(migraphx::make_op("div"), diff, zb); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_dot_add_dot.cpp000066400000000000000000000042141510465702400230330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_dot_add_dot : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {256, 256}}; auto a = mm->add_parameter("a", s); auto b = mm->add_parameter("b", s); auto c = mm->add_parameter("c", s); auto d = mm->add_parameter("d", s); auto dot1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto add = mm->add_instruction(migraphx::make_op("add"), dot1, c); auto dot2 = mm->add_instruction(migraphx::make_op("dot"), add, d); mm->add_return({dot2}); return p; } }; template struct test_dot_add_dot; template struct test_dot_add_dot; ROCm-AMDMIGraphX-46524e8/test/verify/test_dot_mul_a.cpp000066400000000000000000000044021510465702400225310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_dot_mul_a : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; auto a = mm->add_parameter("input", as); auto b = mm->add_literal(migraphx::generate_literal(bs)); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto lit = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 1, 128}})); auto litb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot->get_shape().lens()}}), lit); auto mul = mm->add_instruction(migraphx::make_op("mul"), dot, litb); mm->add_return({mul}); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_dot_mul_b.cpp000066400000000000000000000044021510465702400225320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_dot_mul_b : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape as{migraphx::shape::float_type, {2, 256, 32}}; migraphx::shape bs{migraphx::shape::float_type, {2, 32, 128}}; auto a = mm->add_literal(migraphx::generate_literal(as)); auto b = mm->add_parameter("input", bs); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto lit = mm->add_literal(migraphx::generate_literal({migraphx::shape::float_type, {1, 256, 1}})); auto litb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", dot->get_shape().lens()}}), lit); auto mul = mm->add_instruction(migraphx::make_op("mul"), dot, litb); mm->add_return({mul}); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_equal.cpp000066400000000000000000000034101510465702400216730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_equal : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 3, 4, 6}}; auto input1 = mm->add_parameter("x", s); auto input2 = mm->add_parameter("y", s); auto r = mm->add_instruction(migraphx::make_op("equal"), input1, input2); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_equal_brcst.cpp000066400000000000000000000036651510465702400231040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_equal_brcst : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {3, 3}}; auto l0 = mm->add_parameter("x", s0); migraphx::shape s1{migraphx::shape::float_type, {3, 1}}; auto l1 = mm->add_parameter("y", s1); auto bl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s0.lens()}}), l1); auto r = mm->add_instruction(migraphx::make_op("equal"), l0, bl1); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_erf.cpp000066400000000000000000000041051510465702400213420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_erf : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {2, 3, 4, 6}}; auto param = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("erf"), param); return p; } }; template struct test_erf; template struct test_erf; template struct test_erf; template struct test_erf; template struct test_erf; template struct test_erf; template struct test_erf; ROCm-AMDMIGraphX-46524e8/test/verify/test_exp.cpp000066400000000000000000000041431510465702400213640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_exp : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {6}}; auto x = mm->add_instruction(migraphx::make_op("abs"), mm->add_parameter("x", s)); mm->add_instruction(migraphx::make_op("exp"), x); return p; } }; template struct test_exp; template struct test_exp; template struct test_exp; template struct test_exp; template struct test_exp; template struct test_exp; template struct test_exp; ROCm-AMDMIGraphX-46524e8/test/verify/test_fixed_pad.cpp000066400000000000000000000032351510465702400225140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_fixed_pad : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 3}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("fixed_pad"), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_flatten_dot_relu.cpp000066400000000000000000000041101510465702400241140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_flatten_dot_relu : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{migraphx::shape::float_type, {1, 2, 3, 3, 5}}); a = mm->add_instruction(migraphx::make_op("flatten", {{"axis", 3}}), a); auto b = mm->add_parameter("b", migraphx::shape{migraphx::shape::float_type, {1, 5, 3, 3, 1}}); b = mm->add_instruction(migraphx::make_op("flatten", {{"axis", 3}}), b); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); mm->add_instruction(migraphx::make_op("relu"), dot); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_floor.cpp000066400000000000000000000041331510465702400217100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_floor : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {2, 3, 4, 6}}; auto param = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("floor"), param); return p; }; }; template struct test_floor; template struct test_floor; template struct test_floor; template struct test_floor; template struct test_floor; template struct test_floor; template struct test_floor; ROCm-AMDMIGraphX-46524e8/test/verify/test_fmod_mod.cpp000066400000000000000000000073111510465702400223540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include /* Checking for y == 0 ? eps : y Adding this because HIP fmod sign changes when y = 0 resulting in nan and -nan not beign consistent between ref and gpu implementations. */ static migraphx::instruction_ref add_epsilon(migraphx::module& m, migraphx::instruction_ref y, migraphx::shape::type_t dtype = migraphx::shape::float_type) { auto zero = m.add_literal(migraphx::literal{migraphx::shape{dtype}, {0.0f}}); auto eps = m.add_literal(migraphx::literal{migraphx::shape{dtype}, {1e-3f}}); auto op_y = add_common_op(m, migraphx::make_op("equal"), {y, zero}); return add_common_op(m, migraphx::make_op("where"), {op_y, eps, y}); } template struct test_fmod : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {64}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto op_where = add_epsilon(*mm, y, DType); mm->add_instruction(migraphx::make_op("fmod"), x, op_where); return p; } }; template struct test_fmod; template struct test_fmod; template struct test_fmod; template struct test_fmod; template struct test_fmod; template struct test_fmod; template struct test_mod : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {64}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto op_where = add_epsilon(*mm, y, DType); mm->add_instruction(migraphx::make_op("mod"), x, op_where); return p; } }; template struct test_mod; template struct test_mod; template struct test_mod; template struct test_mod; template struct test_mod; template struct test_mod; template struct test_mod; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp32_bf16_add.cpp000066400000000000000000000037041510465702400227720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp32_bf16_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto sum = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto diff = mm->add_instruction(migraphx::make_op("sub"), sum, p2); mm->add_instruction(migraphx::make_op("add"), diff, p1); migraphx::quantize_bf16(p, {"add"}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp32_bf16_ladd.cpp000066400000000000000000000036261510465702400231510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp32_bf16_ladd : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data(2 * 3); std::iota(data.begin(), data.end(), 1.0f); auto l1 = mm->add_literal(migraphx::literal(s, data)); auto l2 = mm->add_parameter("p2", s); mm->add_instruction(migraphx::make_op("add"), l1, l2); migraphx::quantize_bf16(p, {"add"}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp32_bf16_lall.cpp000066400000000000000000000036261510465702400231710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp32_bf16_lall : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data(2 * 3); std::iota(data.begin(), data.end(), 1.0f); auto l1 = mm->add_literal(migraphx::literal(s, data)); auto l2 = mm->add_parameter("p2", s); mm->add_instruction(migraphx::make_op("add"), l1, l2); migraphx::quantize_bf16(p, {"all"}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp32_bf16_sub.cpp000066400000000000000000000037041510465702400230330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp32_bf16_sub : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto sum = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto diff = mm->add_instruction(migraphx::make_op("sub"), sum, p2); mm->add_instruction(migraphx::make_op("add"), diff, p1); migraphx::quantize_bf16(p, {"sub"}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp32_fp16_add.cpp000066400000000000000000000037041510465702400230100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp32_fp16_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto sum = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto diff = mm->add_instruction(migraphx::make_op("sub"), sum, p2); mm->add_instruction(migraphx::make_op("add"), diff, p1); migraphx::quantize_fp16(p, {"add"}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp32_fp16_ladd.cpp000066400000000000000000000036261510465702400231670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp32_fp16_ladd : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data(2 * 3); std::iota(data.begin(), data.end(), 1.0f); auto l1 = mm->add_literal(migraphx::literal(s, data)); auto l2 = mm->add_parameter("p2", s); mm->add_instruction(migraphx::make_op("add"), l1, l2); migraphx::quantize_fp16(p, {"add"}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp32_fp16_lall.cpp000066400000000000000000000036261510465702400232070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp32_fp16_lall : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; std::vector data(2 * 3); std::iota(data.begin(), data.end(), 1.0f); auto l1 = mm->add_literal(migraphx::literal(s, data)); auto l2 = mm->add_parameter("p2", s); mm->add_instruction(migraphx::make_op("add"), l1, l2); migraphx::quantize_fp16(p, {"all"}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp32_fp16_sub.cpp000066400000000000000000000037041510465702400230510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp32_fp16_sub : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; auto p1 = mm->add_parameter("x", s); auto p2 = mm->add_parameter("y", s); auto sum = mm->add_instruction(migraphx::make_op("add"), p1, p2); auto diff = mm->add_instruction(migraphx::make_op("sub"), sum, p2); mm->add_instruction(migraphx::make_op("add"), diff, p1); migraphx::quantize_fp16(p, {"sub"}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_fp8_ocp_to_fnuz_gemm.cpp000066400000000000000000000052371510465702400247040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_fp8_ocp_to_fnuz_gemm : verify_program { using fp8e4m3fn = migraphx::fp8::fp8e4m3fn; using fp8e4m3fnuz = migraphx::fp8::fp8e4m3fnuz; migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector data_lens = {2, 2}; migraphx::shape data_shape{migraphx::shape::float_type, data_lens}; auto a = mm->add_parameter("a", data_shape); auto b = mm->add_parameter("b", data_shape); auto scale = mm->add_literal(0.5f); std::vector data; data.push_back(fp8e4m3fn{0.f}); auto zero = mm->add_literal(migraphx::shape{migraphx::shape::fp8e4m3fn_type, {1}, {0}}, data); auto qa = add_quantize_op(*mm, "quantizelinear", a, scale, zero); auto qb = add_quantize_op(*mm, "quantizelinear", b, scale, zero); auto da = add_quantize_op(*mm, "dequantizelinear", qa, qa->inputs().at(1), qa->inputs().at(2)); auto db = add_quantize_op(*mm, "dequantizelinear", qb, qb->inputs().at(1), qb->inputs().at(2)); auto dot = mm->add_instruction(migraphx::make_op("dot"), da, db); mm->add_return({dot}); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather.cpp000066400000000000000000000055401510465702400220440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gather : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {3, 3}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 2}}; std::vector indices{1, 2, 2, 1}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = Axis; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; // Standard gather test template struct test_gather<0, migraphx::shape::float_type>; template struct test_gather<0, migraphx::shape::half_type>; template struct test_gather<0, migraphx::shape::bf16_type>; template struct test_gather<0, migraphx::shape::fp8e4m3fnuz_type>; template struct test_gather<0, migraphx::shape::fp8e5m2fnuz_type>; template struct test_gather<0, migraphx::shape::fp8e4m3fn_type>; template struct test_gather<0, migraphx::shape::fp8e5m2_type>; // Test Negative axis template struct test_gather<-2, migraphx::shape::float_type>; template struct test_gather<-2, migraphx::shape::half_type>; template struct test_gather<-2, migraphx::shape::bf16_type>; template struct test_gather<-2, migraphx::shape::fp8e4m3fnuz_type>; template struct test_gather<-2, migraphx::shape::fp8e5m2fnuz_type>; template struct test_gather<-2, migraphx::shape::fp8e4m3fn_type>; template struct test_gather<-2, migraphx::shape::fp8e5m2_type>; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_1d_index.cpp000066400000000000000000000040671510465702400236220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gather_1d_index : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; migraphx::shape s_indices{migraphx::shape::int32_type, {1}}; std::vector indices{Index}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; // Test for positive and negative single dim indices template struct test_gather_1d_index<1>; template struct test_gather_1d_index<-1>; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_4d_index_0.cpp000066400000000000000000000036231510465702400240410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gather_4d_index_0 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int8_type, {4, 2, 2, 1}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 4}}; std::vector indices{3, 2, 1, 0, 0, 1, 2, 3}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); mm->add_instruction(migraphx::make_op("gather", {{"axis", 0}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_4d_index_2.cpp000066400000000000000000000036121510465702400240410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gather_4d_index_2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int8_type, {4, 2, 2, 2}}; migraphx::shape s_indices{migraphx::shape::int32_type, {1, 5}}; std::vector indices{1, 1, 0, 0, 1}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); mm->add_instruction(migraphx::make_op("gather", {{"axis", 2}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_4d_index_3.cpp000066400000000000000000000036101510465702400240400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gather_4d_index_3 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int8_type, {4, 2, 2, 1}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 2}}; std::vector indices{0, 0, 0, 0}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); mm->add_instruction(migraphx::make_op("gather", {{"axis", -1}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_asymmetric.cpp000066400000000000000000000036271510465702400243050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gather_asymmetric : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 1}}; std::vector indices{1, 2}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = 0; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_neg_axis_even_dims.cpp000066400000000000000000000036561510465702400257600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gather_neg_axis_even_dims : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 4}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 2}}; std::vector indices{1, 2, 2, 1}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_neg_indices.cpp000066400000000000000000000036441510465702400243760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gather_neg_indices : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 2}}; std::vector indices{-2, -1, -1, -2}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_onnx_axis_one_ex.cpp000066400000000000000000000037771510465702400255010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include /* Test case mirrors the example for for axis = 1 found on the onnx gather documentation */ struct test_gather_onnx_axis_one_ex : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 1}}; std::vector indices{0, 2}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = 1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_onnx_axis_zero_ex.cpp000066400000000000000000000040031510465702400256560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include /* Test case mirrors the example for axis = 0 found on the onnx gather documentation */ struct test_gather_onnx_axis_zero_ex : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 2}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 2}}; std::vector indices{0, 1, 1, 2}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = 0; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_pos_neg_indices.cpp000066400000000000000000000036521510465702400252560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gather_pos_neg_indices : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 2}}; std::vector indices{-2, 2, 2, -2}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_scalar_index.cpp000066400000000000000000000040721510465702400245570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gather_scalar_index : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; migraphx::shape s_indices{migraphx::shape::int32_type}; std::vector indices{Index}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = -1; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; // Add positive and negative cases for tests template struct test_gather_scalar_index<1>; template struct test_gather_scalar_index<-1>; ROCm-AMDMIGraphX-46524e8/test/verify/test_gather_scalar_output.cpp000066400000000000000000000036171510465702400250140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gather_scalar_output : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; migraphx::shape s_indices{migraphx::shape::int32_type}; std::vector indices{1}; auto a0 = mm->add_parameter("data", s); auto a1 = mm->add_literal(migraphx::literal{s_indices, indices}); int axis = 0; mm->add_instruction(migraphx::make_op("gather", {{"axis", axis}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gathernd_batch_dims_1.cpp000066400000000000000000000037361510465702400247700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gathernd_batch_dims_1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 3, 2, 3}}; migraphx::shape is{migraphx::shape::int64_type, {2, 3, 2}}; std::vector indices{1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}; auto a0 = mm->add_parameter("data", ds); auto a1 = mm->add_literal(migraphx::literal{is, indices}); int batch_dims = 1; mm->add_instruction(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gathernd_batch_dims_2.cpp000066400000000000000000000037351510465702400247700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gathernd_batch_dims_2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 3, 1, 3}}; migraphx::shape is{migraphx::shape::int64_type, {2, 3, 2}}; std::vector indices{0, 0, 0, 1, 0, 2, 0, 2, 0, 1, 0, 0}; auto a0 = mm->add_parameter("data", ds); auto a1 = mm->add_literal(migraphx::literal{is, indices}); int batch_dims = 2; mm->add_instruction(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gathernd_default.cpp000066400000000000000000000045611510465702400240740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gathernd_default : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{DType, {2, 2}}; migraphx::shape is{migraphx::shape::int64_type, {2, 2}}; std::vector indices{0, 0, 1, 1}; auto a0 = mm->add_parameter("data", ds); auto a1 = mm->add_literal(migraphx::literal{is, indices}); mm->add_instruction(migraphx::make_op("gathernd"), a0, a1); return p; } }; template struct test_gathernd_default; template struct test_gathernd_default; template struct test_gathernd_default; template struct test_gathernd_default; template struct test_gathernd_default; template struct test_gathernd_default; template struct test_gathernd_default; ROCm-AMDMIGraphX-46524e8/test/verify/test_gathernd_negative_indices.cpp000066400000000000000000000037031510465702400257450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gathernd_negative_indices : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape ds{migraphx::shape::float_type, {2, 2}}; migraphx::shape is{migraphx::shape::int64_type, {2, 1, 1}}; std::vector indices{-1, 0}; auto a0 = mm->add_parameter("data", ds); auto a1 = mm->add_literal(migraphx::literal{is, indices}); int batch_dims = 1; mm->add_instruction(migraphx::make_op("gathernd", {{"batch_dims", batch_dims}}), a0, a1); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gelu.cpp000066400000000000000000000051231510465702400215230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gelu : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector input_lens{1, 1, 5}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, input_lens}); auto half = mm->add_literal(0.5f); auto one = mm->add_literal(1.0f); auto sqrt2 = mm->add_literal(static_cast(M_SQRT2)); auto half_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), half); auto mul_half = mm->add_instruction(migraphx::make_op("mul"), x, half_mbcast); auto sqrt2_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), sqrt2); auto div = mm->add_instruction(migraphx::make_op("div"), x, sqrt2_mbcast); auto erf = mm->add_instruction(migraphx::make_op("erf"), div); auto one_mbcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), one); auto add_one = mm->add_instruction(migraphx::make_op("add"), erf, one_mbcast); mm->add_instruction(migraphx::make_op("mul"), mul_half, add_one); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm.cpp000066400000000000000000000043041510465702400215140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {1, 2, 1280}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {1, 1280, 320}}); mm->add_instruction(migraphx::make_op("dot"), a, b); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm; template struct test_gemm; template struct test_gemm; template struct test_gemm; template struct test_gemm; template struct test_gemm; template struct test_gemm; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_bmv.cpp000066400000000000000000000050551510465702400234620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_bmv : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 3, 3, 5}}; migraphx::shape m2_shape{DType, {5}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto ul2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l2); auto bul2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 5, 1}}}), ul2); mm->add_instruction(migraphx::make_op("dot"), l1, bul2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_bmv; template struct test_gemm_2args_bmv; template struct test_gemm_2args_bmv; template struct test_gemm_2args_bmv; template struct test_gemm_2args_bmv; template struct test_gemm_2args_bmv; template struct test_gemm_2args_bmv; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mm_1.cpp000066400000000000000000000047201510465702400235250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_mm_1 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 2, 3}}; migraphx::shape m2_shape{DType, {1, 3, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto bl2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4}}}), l2); mm->add_instruction(migraphx::make_op("dot"), l1, bl2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mm_1; template struct test_gemm_2args_mm_1; template struct test_gemm_2args_mm_1; template struct test_gemm_2args_mm_1; template struct test_gemm_2args_mm_1; template struct test_gemm_2args_mm_1; template struct test_gemm_2args_mm_1; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mm_2.cpp000066400000000000000000000047531510465702400235340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_2args_mm_2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 2, 3}}; migraphx::shape m2_shape{DType, {3, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto bl2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4}}}), l2); mm->add_instruction(migraphx::make_op("dot"), l1, bl2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mm_2; template struct test_gemm_2args_mm_2; template struct test_gemm_2args_mm_2; template struct test_gemm_2args_mm_2; template struct test_gemm_2args_mm_2; template struct test_gemm_2args_mm_2; template struct test_gemm_2args_mm_2; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mm_3.cpp000066400000000000000000000047561510465702400235400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_2args_mm_3 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2, 3}}; migraphx::shape m2_shape{DType, {3, 3, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto bl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2, 3}}}), l1); auto l2 = mm->add_parameter("2", m2_shape); mm->add_instruction(migraphx::make_op("dot"), bl1, l2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mm_3; template struct test_gemm_2args_mm_3; template struct test_gemm_2args_mm_3; template struct test_gemm_2args_mm_3; template struct test_gemm_2args_mm_3; template struct test_gemm_2args_mm_3; template struct test_gemm_2args_mm_3; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mm_4.cpp000066400000000000000000000046421510465702400235330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_2args_mm_4 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 3}}; migraphx::shape m2_shape{DType, {3, 3, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto bl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 2, 3}}}), l1); auto l2 = mm->add_parameter("2", m2_shape); mm->add_instruction(migraphx::make_op("dot"), bl1, l2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mm_4; template struct test_gemm_2args_mm_4; template struct test_gemm_2args_mm_4; template struct test_gemm_2args_mm_4; template struct test_gemm_2args_mm_4; template struct test_gemm_2args_mm_4; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mm_5.cpp000066400000000000000000000047331510465702400235350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_mm_5 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 1, 2, 3}}; migraphx::shape m2_shape{DType, {2, 3, 3, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto bl1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 2, 3}}}), l1); auto l2 = mm->add_parameter("2", m2_shape); mm->add_instruction(migraphx::make_op("dot"), bl1, l2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mm_5; template struct test_gemm_2args_mm_5; template struct test_gemm_2args_mm_5; template struct test_gemm_2args_mm_5; template struct test_gemm_2args_mm_5; template struct test_gemm_2args_mm_5; template struct test_gemm_2args_mm_5; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mm_6.cpp000066400000000000000000000051321510465702400235300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_mm_6 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 1, 2, 3}}; migraphx::shape m2_shape{DType, {1, 3, 3, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto bl1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 2, 3}}}), l1); auto l2 = mm->add_parameter("2", m2_shape); auto bl2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 3, 4}}}), l2); mm->add_instruction(migraphx::make_op("dot"), bl1, bl2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mm_6; template struct test_gemm_2args_mm_6; template struct test_gemm_2args_mm_6; template struct test_gemm_2args_mm_6; template struct test_gemm_2args_mm_6; template struct test_gemm_2args_mm_6; template struct test_gemm_2args_mm_6; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mm_7.cpp000066400000000000000000000047251510465702400235400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_mm_7 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 3}}; migraphx::shape m2_shape{DType, {2, 3, 3, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto bl1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 2, 3}}}), l1); auto l2 = mm->add_parameter("2", m2_shape); mm->add_instruction(migraphx::make_op("dot"), bl1, l2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mm_7; template struct test_gemm_2args_mm_7; template struct test_gemm_2args_mm_7; template struct test_gemm_2args_mm_7; template struct test_gemm_2args_mm_7; template struct test_gemm_2args_mm_7; template struct test_gemm_2args_mm_7; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mm_8.cpp000066400000000000000000000046731510465702400235430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_mm_8 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{DType, {2, 128, 32}, {4096, 1, 128}}; migraphx::shape b_shape{DType, {32, 32}}; auto a = mm->add_parameter("a", a_shape); auto b = mm->add_parameter("b", b_shape); auto bb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 32, 32}}}), b); mm->add_instruction(migraphx::make_op("dot"), a, bb); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mm_8; // template struct test_gemm_2args_mm_8; // fails with CK, issue#2514 template struct test_gemm_2args_mm_8; template struct test_gemm_2args_mm_8; template struct test_gemm_2args_mm_8; template struct test_gemm_2args_mm_8; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_mv.cpp000066400000000000000000000046341510465702400233220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_mv : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {3, 5}}; migraphx::shape m2_shape{DType, {5}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto ul2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l2); mm->add_instruction(migraphx::make_op("dot"), l1, ul2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_mv; template struct test_gemm_2args_mv; template struct test_gemm_2args_mv; template struct test_gemm_2args_mv; template struct test_gemm_2args_mv; template struct test_gemm_2args_mv; template struct test_gemm_2args_mv; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_vbm.cpp000066400000000000000000000052101510465702400234530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_vbm : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {5}}; migraphx::shape m2_shape{DType, {2, 2, 5, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto ul1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), l1); auto bul1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 2, 1, 5}}}), ul1); auto l2 = mm->add_parameter("2", m2_shape); auto res = mm->add_instruction(migraphx::make_op("dot"), bul1, l2); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), res); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_vbm; template struct test_gemm_2args_vbm; template struct test_gemm_2args_vbm; template struct test_gemm_2args_vbm; template struct test_gemm_2args_vbm; template struct test_gemm_2args_vbm; template struct test_gemm_2args_vbm; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_vm.cpp000066400000000000000000000050351510465702400233160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_vm : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {5}}; migraphx::shape m2_shape{DType, {5, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto ul1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), l1); auto l2 = mm->add_parameter("2", m2_shape); auto res = mm->add_instruction(migraphx::make_op("dot"), ul1, l2); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), res); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_vm; template struct test_gemm_2args_vm; template struct test_gemm_2args_vm; template struct test_gemm_2args_vm; template struct test_gemm_2args_vm; // TODO need hipblaslt support // template struct test_gemm_2args_vm; // template struct test_gemm_2args_vm; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_2args_vv.cpp000066400000000000000000000054331510465702400233310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include template struct test_gemm_2args_vv : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {8}}; migraphx::shape m2_shape{DType, {8}}; auto l1 = mm->add_parameter("1", m1_shape); auto ul1 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), l1); auto l2 = mm->add_parameter("2", m2_shape); auto ul2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), l2); float alpha = 0.23f; auto res = migraphx::add_apply_alpha_beta(*mm, {ul1, ul2}, migraphx::make_op("dot"), alpha); auto sres = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), res); mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {0}}}), sres); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_2args_vv; template struct test_gemm_2args_vv; template struct test_gemm_2args_vv; template struct test_gemm_2args_vv; template struct test_gemm_2args_vv; template struct test_gemm_2args_vv; template struct test_gemm_2args_vv; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_add.cpp000066400000000000000000000053641510465702400223330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_add : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2, 1024}}; migraphx::shape m2_shape{DType, {1, 1024, 320}}; migraphx::shape m3_shape{DType, {1, 2, 320}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); auto dot = mm->add_instruction(migraphx::make_op("dot"), l1, l2); mm->add_instruction(migraphx::make_op("add"), dot, l3); return p; } std::string section() const { return "gemm"; } // Turn on Exhaustive-tune to enable split-k GEMM perf-configs from MLIR migraphx::compile_options get_compile_options() const { return migraphx::compile_options{.exhaustive_tune = true}; } }; template struct test_gemm_add; template struct test_gemm_add; template struct test_gemm_add; // TODO template struct test_gemm_add; // TODO template struct test_gemm_add; // TODO template struct test_gemm_add; // TODO template struct test_gemm_add; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_add_broadcast1.cpp000066400000000000000000000053211510465702400244270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_add_broadcast1 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2, 3}}; migraphx::shape m2_shape{DType, {1, 3, 4}}; migraphx::shape m3_shape{DType, {1, 1, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); auto l3_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 2, 4}}}), l3); auto dot = mm->add_instruction(migraphx::make_op("dot"), l1, l2); mm->add_instruction(migraphx::make_op("add"), dot, l3_b); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_add_broadcast1; template struct test_gemm_add_broadcast1; template struct test_gemm_add_broadcast1; template struct test_gemm_add_broadcast1; template struct test_gemm_add_broadcast1; template struct test_gemm_add_broadcast1; template struct test_gemm_add_broadcast1; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_add_broadcast2.cpp000066400000000000000000000052561510465702400244370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_add_broadcast2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2, 3}}; migraphx::shape m2_shape{DType, {1, 3, 4}}; migraphx::shape m3_shape{DType, {1, 2, 1}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); auto l3_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 2, 4}}}), l3); auto dot = mm->add_instruction(migraphx::make_op("dot"), l1, l2); mm->add_instruction(migraphx::make_op("add"), dot, l3_b); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_add_broadcast2; // template struct test_gemm_add_broadcast2; // fails with CK, // issue#2514 template struct test_gemm_add_broadcast2; template struct test_gemm_add_broadcast2; template struct test_gemm_add_broadcast2; template struct test_gemm_add_broadcast2; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_add_broadcast3.cpp000066400000000000000000000052101510465702400244260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_add_broadcast3 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2}}; migraphx::shape m2_shape{DType, {2, 4}}; migraphx::shape m3_shape{DType, {4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); auto l3_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 4}}}), l3); auto dot = mm->add_instruction(migraphx::make_op("dot"), l1, l2); mm->add_instruction(migraphx::make_op("add"), l3_b, dot); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_add_broadcast3; template struct test_gemm_add_broadcast3; // template struct test_gemm_add_broadcast3; // template struct test_gemm_add_broadcast3; // template struct test_gemm_add_broadcast3; // template struct test_gemm_add_broadcast3; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_copy.cpp000066400000000000000000000047741510465702400225610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include template struct test_gemm_copy : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sa{DType, {2, 16}}; migraphx::shape sb{DType, {16, 8}}; migraphx::shape sc{DType, {1, 8}}; auto pa = mm->add_parameter("a", sa); auto pb = mm->add_parameter("b", sb); auto pc = mm->add_parameter("c", sc); auto dr = migraphx::add_apply_alpha_beta(*mm, {pa, pb, pc}, migraphx::make_op("dot"), 1.0f, 1.0f); mm->add_instruction(migraphx::make_op("add"), dr, dr); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_copy; template struct test_gemm_copy; template struct test_gemm_copy; template struct test_gemm_copy; template struct test_gemm_copy; template struct test_gemm_copy; template struct test_gemm_copy; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_ex.cpp000066400000000000000000000043341510465702400222130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_ex : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {1, 1, 4, 5}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {1, 1, 5, 3}}); mm->add_instruction(migraphx::make_op("dot"), a, b); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_ex; template struct test_gemm_ex; template struct test_gemm_ex; template struct test_gemm_ex; template struct test_gemm_ex; template struct test_gemm_ex; template struct test_gemm_ex; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_ld.cpp000066400000000000000000000035071510465702400221770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gemm_ld //: verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{migraphx::shape::float_type, {4, 5}, {10, 1}}); auto b = mm->add_parameter("b", migraphx::shape{migraphx::shape::float_type, {5, 3}, {20, 1}}); mm->add_instruction(migraphx::make_op("dot"), a, b); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_literal.cpp000066400000000000000000000045031510465702400232310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_literal : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{DType, {2, 4}}; migraphx::shape b_shape{DType, {4, 4}}; auto a = mm->add_literal(migraphx::generate_literal(a_shape)); auto b = mm->add_parameter("b", b_shape); mm->add_instruction(migraphx::make_op("dot"), a, b); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_literal; template struct test_gemm_literal; template struct test_gemm_literal; template struct test_gemm_literal; template struct test_gemm_literal; template struct test_gemm_literal; template struct test_gemm_literal; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_mul_where_softmax_gemm.cpp000066400000000000000000000054201510465702400263310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gemm_mul_where_softmax_gemm : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::half_type, {1, 12, 256, 256}}; migraphx::shape m2_shape{migraphx::shape::bool_type, {1, 12, 256, 256}}; auto m1_elements = m1_shape.elements(); auto a = mm->add_parameter("1", m1_shape); auto b = mm->add_parameter("2", m1_shape); auto b1 = mm->add_parameter("3", m1_shape); auto select = mm->add_parameter("4", m2_shape); std::vector eights(m1_elements, 0.125); std::vector tens(m1_elements, 10); auto eight = mm->add_literal(migraphx::literal{m1_shape, eights}); auto ten = mm->add_literal(migraphx::literal{m1_shape, tens}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto scale = mm->add_instruction(migraphx::make_op("mul"), gemm1, eight); auto where = mm->add_instruction(migraphx::make_op("where"), select, scale, ten); auto softmax = mm->add_instruction(migraphx::make_op("softmax", {{"axis", 3}}), where); mm->add_instruction(migraphx::make_op("dot"), softmax, b1); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_mul_where_softmax_gemm_bf16.cpp000066400000000000000000000054321510465702400271520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_gemm_mul_where_softmax_gemm_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::bf16_type, {1, 12, 256, 256}}; migraphx::shape m2_shape{migraphx::shape::bool_type, {1, 12, 256, 256}}; auto m1_elements = m1_shape.elements(); auto a = mm->add_parameter("1", m1_shape); auto b = mm->add_parameter("2", m1_shape); auto b1 = mm->add_parameter("3", m1_shape); auto select = mm->add_parameter("4", m2_shape); std::vector eights(m1_elements, 0.125); std::vector tens(m1_elements, 10); auto eight = mm->add_literal(migraphx::literal{m1_shape, eights}); auto ten = mm->add_literal(migraphx::literal{m1_shape, tens}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto scale = mm->add_instruction(migraphx::make_op("mul"), gemm1, eight); auto where = mm->add_instruction(migraphx::make_op("where"), select, scale, ten); auto softmax = mm->add_instruction(migraphx::make_op("softmax", {{"axis", 3}}), where); mm->add_instruction(migraphx::make_op("dot"), softmax, b1); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_multi_3args.cpp000066400000000000000000000051401510465702400240240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include template struct test_gemm_multi_3args : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 3, 2, 3}}; migraphx::shape m2_shape{DType, {2, 3, 3, 2}}; migraphx::shape m3_shape{DType, {2, 3, 2, 2}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); float alpha = 0.35; float beta = 0.41; migraphx::add_apply_alpha_beta(*mm, {l1, l2, l3}, migraphx::make_op("dot"), alpha, beta); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_multi_3args; template struct test_gemm_multi_3args; template struct test_gemm_multi_3args; template struct test_gemm_multi_3args; template struct test_gemm_multi_3args; template struct test_gemm_multi_3args; template struct test_gemm_multi_3args; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_multi_3args_alpha0.cpp000066400000000000000000000052121510465702400252510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_multi_3args_alpha0 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2, 3}}; migraphx::shape m2_shape{DType, {1, 3, 4}}; migraphx::shape m3_shape{DType, {1, 2, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); float alpha = 0.0f; float beta = 1.0f; migraphx::add_apply_alpha_beta(*mm, {l1, l2, l3}, migraphx::make_op("dot"), alpha, beta); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_multi_3args_alpha0; template struct test_gemm_multi_3args_alpha0; template struct test_gemm_multi_3args_alpha0; template struct test_gemm_multi_3args_alpha0; template struct test_gemm_multi_3args_alpha0; template struct test_gemm_multi_3args_alpha0; template struct test_gemm_multi_3args_alpha0; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_multi_3args_beta0.cpp000066400000000000000000000052011510465702400250750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include template struct test_gemm_multi_3args_beta0 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2, 3}}; migraphx::shape m2_shape{DType, {1, 3, 4}}; migraphx::shape m3_shape{DType, {1, 2, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); float alpha = 1.0f; float beta = 0.0f; migraphx::add_apply_alpha_beta(*mm, {l1, l2, l3}, migraphx::make_op("dot"), alpha, beta); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_multi_3args_beta0; template struct test_gemm_multi_3args_beta0; template struct test_gemm_multi_3args_beta0; template struct test_gemm_multi_3args_beta0; template struct test_gemm_multi_3args_beta0; template struct test_gemm_multi_3args_beta0; template struct test_gemm_multi_3args_beta0; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_multi_3args_c25.cpp000066400000000000000000000051621510465702400245010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include template struct test_gemm_multi_3args_c25 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 3}}; migraphx::shape m2_shape{DType, {3, 5}}; migraphx::shape m3_shape{DType, {2, 5}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); float alpha = 0.35; float beta = 0.41; migraphx::add_apply_alpha_beta(*mm, {l1, l2, l3}, migraphx::make_op("dot"), alpha, beta); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_multi_3args_c25; template struct test_gemm_multi_3args_c25; template struct test_gemm_multi_3args_c25; template struct test_gemm_multi_3args_c25; template struct test_gemm_multi_3args_c25; template struct test_gemm_multi_3args_c25; template struct test_gemm_multi_3args_c25; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_multi_dim_2.cpp000066400000000000000000000042101510465702400237740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_multi_dim_2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 2, 3}}; migraphx::shape m2_shape{DType, {2, 3, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); mm->add_instruction(migraphx::make_op("dot"), l1, l2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_multi_dim_2; template struct test_gemm_multi_dim_2; template struct test_gemm_multi_dim_2; template struct test_gemm_multi_dim_2; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_multi_dim_2_3.cpp000066400000000000000000000045701510465702400242270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_multi_dim_2_3 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 3, 2, 3}}; migraphx::shape m2_shape{DType, {2, 3, 3, 2}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); mm->add_instruction(migraphx::make_op("dot"), l1, l2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_multi_dim_2_3; template struct test_gemm_multi_dim_2_3; template struct test_gemm_multi_dim_2_3; template struct test_gemm_multi_dim_2_3; template struct test_gemm_multi_dim_2_3; template struct test_gemm_multi_dim_2_3; template struct test_gemm_multi_dim_2_3; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_multi_transpose.cpp000066400000000000000000000051721510465702400250300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include template struct test_gemm_multi_transpose : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 2, 3}}; migraphx::shape m2_shape{DType, {3, 2, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0, 2}}}), l2); float alpha = 1.0f; float beta = 1.0f; migraphx::add_apply_alpha_beta(*mm, {l1, tl2}, migraphx::make_op("dot"), alpha, beta); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_multi_transpose; template struct test_gemm_multi_transpose; template struct test_gemm_multi_transpose; template struct test_gemm_multi_transpose; template struct test_gemm_multi_transpose; template struct test_gemm_multi_transpose; template struct test_gemm_multi_transpose; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_multibroadcast.cpp000066400000000000000000000047021510465702400246130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_multibroadcast : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {2, 2, 1025}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {2, 1, 2}}); auto bb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 1025, 2}}}), b); mm->add_instruction(migraphx::make_op("dot"), a, bb); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_multibroadcast; template struct test_gemm_multibroadcast; template struct test_gemm_multibroadcast; template struct test_gemm_multibroadcast; template struct test_gemm_multibroadcast; template struct test_gemm_multibroadcast; template struct test_gemm_multibroadcast; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_pointwise.cpp000066400000000000000000000054631510465702400236240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_pointwise : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2, 3}}; migraphx::shape m2_shape{DType, {1, 3, 4}}; migraphx::shape m3_shape{DType, {1, 1, 4}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); auto l3_b = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {1, 2, 4}}}), l3); auto dot = mm->add_instruction(migraphx::make_op("dot"), l1, l2); auto add = mm->add_instruction(migraphx::make_op("add"), dot, l3_b); auto abs = mm->add_instruction(migraphx::make_op("abs"), add); mm->add_instruction(migraphx::make_op("sqrt"), abs); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_pointwise; template struct test_gemm_pointwise; template struct test_gemm_pointwise; template struct test_gemm_pointwise; template struct test_gemm_pointwise; template struct test_gemm_pointwise; template struct test_gemm_pointwise; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_reshapes_add.cpp000066400000000000000000000053201510465702400242150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_gemm_reshapes_add : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {1, 2, 1024}}; migraphx::shape m2_shape{DType, {1, 1024, 320}}; migraphx::shape m3_shape{DType, {320, 2}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_parameter("2", m2_shape); auto l3 = mm->add_parameter("3", m3_shape); auto dot = mm->add_instruction(migraphx::make_op("dot"), l1, l2); auto dot_sq = mm->add_instruction(migraphx::make_op("squeeze"), dot); auto dot_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), dot_sq); mm->add_instruction(migraphx::make_op("add"), dot_trans, l3); return p; } std::string section() const { return "gemm"; } // Turn on Exhaustive-tune to enable split-k GEMM perf-configs from MLIR migraphx::compile_options get_compile_options() const { return migraphx::compile_options{.exhaustive_tune = true}; } }; template struct test_gemm_reshapes_add; template struct test_gemm_reshapes_add; template struct test_gemm_reshapes_add; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_softmax_gemm_relu.cpp000066400000000000000000000066671510465702400253270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include enum class bias { without, with, with_standard_shape }; template struct test_gemm_softmax_gemm_relu : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::half_type, {1, 12, 256, 256}}; auto m2_elements = m1_shape.elements(); auto a = mm->add_parameter("1", m1_shape); auto b = mm->add_parameter("2", m1_shape); auto b1 = mm->add_parameter("3", m1_shape); std::vector eights(m2_elements, 0.125); auto eight = mm->add_literal(migraphx::literal{m1_shape, eights}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto scale = mm->add_instruction(migraphx::make_op("mul"), gemm1, eight); std::optional add_bias{std::nullopt}; if constexpr(Config == bias::with or Config == bias::with_standard_shape) { auto bias_shape = m1_shape; if(Config != bias::with_standard_shape) { bias_shape = migraphx::shape::from_permutation( bias_shape.type(), bias_shape.lens(), {0, 1, 3, 2}); } auto bias_term = mm->add_parameter("4", bias_shape); add_bias = mm->add_instruction(migraphx::make_op("add"), scale, bias_term); } auto softmax = mm->add_instruction(migraphx::make_op("softmax", {{"axis", 3}}), Config == bias::without ? scale : add_bias.value()); auto gemm2 = mm->add_instruction(migraphx::make_op("dot"), softmax, b1); mm->add_instruction(migraphx::make_op("relu"), gemm2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_softmax_gemm_relu; template struct test_gemm_softmax_gemm_relu; template struct test_gemm_softmax_gemm_relu; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_softmax_gemm_relu_bf16.cpp000066400000000000000000000070251510465702400261320ustar00rootroot00000000000000// /* // * The MIT License (MIT) // * // * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. // * // * 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. // */ #include "verify_program.hpp" #include #include #include enum class bias { without, with, with_standard_shape }; template struct test_gemm_softmax_gemm_relu_bf16 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{migraphx::shape::bf16_type, {1, 12, 256, 256}}; auto m2_elements = m1_shape.elements(); auto a = mm->add_parameter("1", m1_shape); auto b = mm->add_parameter("2", m1_shape); auto b1 = mm->add_parameter("3", m1_shape); std::vector eights(m2_elements, 0.125); auto eight = mm->add_literal(migraphx::literal{m1_shape, eights}); b = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), b); auto gemm1 = mm->add_instruction(migraphx::make_op("dot"), a, b); auto scale = mm->add_instruction(migraphx::make_op("mul"), gemm1, eight); std::optional add_bias{std::nullopt}; if constexpr(Config == bias::with or Config == bias::with_standard_shape) { auto bias_shape = m1_shape; if(Config != bias::with_standard_shape) { bias_shape = migraphx::shape::from_permutation( bias_shape.type(), bias_shape.lens(), {0, 1, 3, 2}); } auto bias_term = mm->add_parameter("4", bias_shape); add_bias = mm->add_instruction(migraphx::make_op("add"), scale, bias_term); } auto softmax = mm->add_instruction(migraphx::make_op("softmax", {{"axis", 3}}), Config == bias::without ? scale : add_bias.value()); auto gemm2 = mm->add_instruction(migraphx::make_op("dot"), softmax, b1); mm->add_instruction(migraphx::make_op("relu"), gemm2); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_softmax_gemm_relu_bf16; template struct test_gemm_softmax_gemm_relu_bf16; template struct test_gemm_softmax_gemm_relu_bf16; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_transpose_add_pooling_sub.cpp000066400000000000000000000071711510465702400270270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_gemm_transpose_add_pooling_sub : verify_program> { migraphx::program create_program() const { migraphx::shape s1{migraphx::shape::float_type, {1, 1, 2, 5}}; migraphx::shape s2{migraphx::shape::float_type, {1, 1, 5, 10}}; migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1, 5, 4}}); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, b); auto dot_trans = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), dot); auto dot_rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 1, 5, 4}}}), dot_trans); auto add = mm->add_instruction(migraphx::make_op("add"), {dot_rsp, x}); auto pooling = mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {1, 0, 0, 0}}, {"stride", {1, 1}}, {"lengths", {2, 1}}, {"lp_order", 2}}), add); auto sub = mm->add_instruction(migraphx::make_op("sub"), dot_rsp, pooling); mm->add_return({sub}); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_transpose_add_pooling_sub; template struct test_gemm_transpose_add_pooling_sub; template struct test_gemm_transpose_add_pooling_sub; template struct test_gemm_transpose_add_pooling_sub; template struct test_gemm_transpose_add_pooling_sub; template struct test_gemm_transpose_add_pooling_sub; template struct test_gemm_transpose_add_pooling_sub; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_transposea.cpp000066400000000000000000000046441510465702400237620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_transposea : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {5, 4}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {5, 3}}); auto at = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), a); mm->add_instruction(migraphx::make_op("dot"), at, b); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_transposea; template struct test_gemm_transposea; template struct test_gemm_transposea; template struct test_gemm_transposea; template struct test_gemm_transposea; // TODO need hipblaslt support // template struct test_gemm_transposea; // template struct test_gemm_transposea; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_transposea_ex.cpp000066400000000000000000000046701510465702400244550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_transposea_ex : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {1, 1, 5, 4}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {1, 1, 5, 3}}); auto at = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), a); mm->add_instruction(migraphx::make_op("dot"), at, b); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_transposea_ex; template struct test_gemm_transposea_ex; template struct test_gemm_transposea_ex; template struct test_gemm_transposea_ex; template struct test_gemm_transposea_ex; template struct test_gemm_transposea_ex; template struct test_gemm_transposea_ex; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_transposeab.cpp000066400000000000000000000047561510465702400241300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_transposeab : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {5, 4}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {3, 5}}); auto at = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), a); auto bt = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), b); mm->add_instruction(migraphx::make_op("dot"), at, bt); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_transposeab; template struct test_gemm_transposeab; template struct test_gemm_transposeab; template struct test_gemm_transposeab; template struct test_gemm_transposeab; template struct test_gemm_transposeab; template struct test_gemm_transposeab; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_transposeb.cpp000066400000000000000000000045771510465702400237700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_transposeb : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {4, 5}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {3, 5}}); auto bt = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), b); mm->add_instruction(migraphx::make_op("dot"), a, bt); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_transposeb; template struct test_gemm_transposeb; template struct test_gemm_transposeb; template struct test_gemm_transposeb; template struct test_gemm_transposeb; template struct test_gemm_transposeb; template struct test_gemm_transposeb; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_transposeb_detect.cpp000066400000000000000000000047231510465702400253110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_transposeb_detect : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {2, 2, 1}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {2, 2, 1}}); auto bt = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), b); mm->add_instruction(migraphx::make_op("dot"), a, bt); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_transposeb_detect; template struct test_gemm_transposeb_detect; template struct test_gemm_transposeb_detect; template struct test_gemm_transposeb_detect; template struct test_gemm_transposeb_detect; template struct test_gemm_transposeb_detect; template struct test_gemm_transposeb_detect; ROCm-AMDMIGraphX-46524e8/test/verify/test_gemm_transposeb_ex.cpp000066400000000000000000000046571510465702400244630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_gemm_transposeb_ex : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", migraphx::shape{DType, {1, 4, 5}}); auto b = mm->add_parameter("b", migraphx::shape{DType, {1, 3, 5}}); auto bt = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1}}}), b); mm->add_instruction(migraphx::make_op("dot"), a, bt); return p; } std::string section() const { return "gemm"; } }; template struct test_gemm_transposeb_ex; template struct test_gemm_transposeb_ex; template struct test_gemm_transposeb_ex; template struct test_gemm_transposeb_ex; template struct test_gemm_transposeb_ex; template struct test_gemm_transposeb_ex; template struct test_gemm_transposeb_ex; ROCm-AMDMIGraphX-46524e8/test/verify/test_global_avg_pooling.cpp000066400000000000000000000035631510465702400244210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_global_avg_pooling : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 16, 16}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::average}; auto lens = input->get_shape().lens(); op.lengths = {lens[2], lens[3]}; mm->add_instruction(op, input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_global_max_pooling.cpp000066400000000000000000000037761510465702400244370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_global_max_pooling : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{T, {1, 3, 16, 16}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::max}; auto lens = input->get_shape().lens(); op.lengths = {lens[2], lens[3]}; mm->add_instruction(op, input); return p; } }; template struct test_global_max_pooling; template struct test_global_max_pooling; ROCm-AMDMIGraphX-46524e8/test/verify/test_greater.cpp000066400000000000000000000034161510465702400222230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_greater : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 3, 4, 6}}; auto input1 = mm->add_parameter("x", s); auto input2 = mm->add_parameter("y", s); auto r = mm->add_instruction(migraphx::make_op("greater"), input1, input2); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_greater_brcst.cpp000066400000000000000000000036731510465702400234250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_greater_brcst : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {3, 3}}; auto l0 = mm->add_parameter("x", s0); migraphx::shape s1{migraphx::shape::float_type, {3, 1}}; auto l1 = mm->add_parameter("y", s1); auto bl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s0.lens()}}), l1); auto r = mm->add_instruction(migraphx::make_op("greater"), l0, bl1); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_group_conv.cpp000066400000000000000000000035251510465702400227540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_group_conv : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 4, 16, 16}}); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {4, 1, 3, 3}}); migraphx::op::convolution op; op.group = 4; mm->add_instruction(op, input, weights); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_group_query_attention_decode.cpp000066400000000000000000000115471510465702400265470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_group_query_attention_decode : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector query_lens{1, 1, 12288}; std::vector kv_lens{1, 32, 2048, 128}; std::vector slk_lens{1, 1}; std::vector tsl_lens{1, 1}; std::vector cs_cache_lens{2048, 64}; auto dtype = migraphx::shape::half_type; migraphx::shape query_s{dtype, query_lens}; migraphx::shape kv_s{dtype, kv_lens}; migraphx::shape slk_s{migraphx::shape::int64_type, slk_lens}; migraphx::shape tsl_s{migraphx::shape::int64_type, tsl_lens}; migraphx::shape cs_cache_s{dtype, cs_cache_lens}; std::vector slk_vec(slk_s.elements(), 15); std::vector tsl_vec(tsl_s.elements(), 2048); std::vector k_vec(kv_s.elements(), 1.0); std::vector v_vec(kv_s.elements(), 0.0); std::vector q_min_vec(query_s.elements(), -100.0); std::vector q_max_vec(query_s.elements(), 100.0); std::vector cs_min_vec(cs_cache_s.elements(), -1.0); std::vector cs_max_vec(cs_cache_s.elements(), 1.0); auto k_cache = mm->add_literal(kv_s, k_vec); auto v_cache = mm->add_literal(kv_s, v_vec); auto query = mm->add_parameter("query", query_s); auto q_min = mm->add_literal(query_s, q_min_vec); auto q_max = mm->add_literal(query_s, q_max_vec); query = mm->add_instruction(migraphx::make_op("clip"), query, q_min, q_max); auto slk = mm->add_literal(slk_s, slk_vec); auto tsl = mm->add_literal(tsl_s, tsl_vec); auto key = mm->add_literal(0.0f); auto value = mm->add_literal(0.0f); auto cs_min = mm->add_literal(cs_cache_s, cs_min_vec); auto cs_max = mm->add_literal(cs_cache_s, cs_max_vec); auto cos_cache = mm->add_parameter("cos_cache", cs_cache_s); auto sin_cache = mm->add_parameter("sin_cache", cs_cache_s); cos_cache = mm->add_instruction(migraphx::make_op("clip"), cos_cache, cs_min, cs_max); sin_cache = mm->add_instruction(migraphx::make_op("clip"), sin_cache, cs_min, cs_max); auto r = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 1}, {"kv_num_heads", 32}, {"local_window_size", -1}, {"num_heads", 32}, {"rotary_interleaved", 0}}), query, key, value, k_cache, v_cache, slk, tsl, cos_cache, sin_cache); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), r); mm->add_return({r0, r1, r2}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_group_query_attention_decode_small.cpp000066400000000000000000000123771510465702400277410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_group_query_attention_decode_small : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector query_lens{1, 1, 12}; std::vector kv_lens{1, 2, 4, 2}; std::vector slk_lens{1, 1}; std::vector tsl_lens{1, 1}; std::vector cs_cache_lens{4, 1}; auto dtype = migraphx::shape::half_type; migraphx::shape query_s{dtype, query_lens}; migraphx::shape kv_s{dtype, kv_lens}; migraphx::shape slk_s{migraphx::shape::int32_type, slk_lens}; migraphx::shape tsl_s{migraphx::shape::int32_type, tsl_lens}; migraphx::shape cs_cache_s{dtype, cs_cache_lens}; auto query = mm->add_parameter("query", query_s); std::vector slk_vec(slk_s.elements(), 3); std::vector tsl_vec(tsl_s.elements(), 4); std::vector cs_min_vec(cs_cache_s.elements(), -1.0); std::vector cs_max_vec(cs_cache_s.elements(), 1.0); std::vector q_min_vec(query_s.elements(), -1.0); std::vector q_max_vec(query_s.elements(), 1.0); std::vector kv_min_vec(kv_s.elements(), -1.0); std::vector kv_max_vec(kv_s.elements(), 1.0); auto k_cache = mm->add_parameter("k_cache", kv_s); auto v_cache = mm->add_parameter("v_cache", kv_s); auto slk = mm->add_literal(slk_s, slk_vec); auto tsl = mm->add_literal(tsl_s, tsl_vec); auto key = mm->add_literal(0.0f); auto value = mm->add_literal(0.0f); auto cs_min = mm->add_literal(cs_cache_s, cs_min_vec); auto cs_max = mm->add_literal(cs_cache_s, cs_max_vec); auto q_min = mm->add_literal(query_s, q_min_vec); auto q_max = mm->add_literal(query_s, q_max_vec); auto kv_min = mm->add_literal(kv_s, kv_min_vec); auto kv_max = mm->add_literal(kv_s, kv_max_vec); auto cos_cache = mm->add_parameter("cos_cache", cs_cache_s); auto sin_cache = mm->add_parameter("sin_cache", cs_cache_s); query = mm->add_instruction(migraphx::make_op("clip"), query, q_min, q_max); k_cache = mm->add_instruction(migraphx::make_op("clip"), k_cache, kv_min, kv_max); v_cache = mm->add_instruction(migraphx::make_op("clip"), v_cache, kv_min, kv_max); cos_cache = mm->add_instruction(migraphx::make_op("clip"), cos_cache, cs_min, cs_max); sin_cache = mm->add_instruction(migraphx::make_op("clip"), sin_cache, cs_min, cs_max); auto r = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 0}, {"kv_num_heads", 2}, {"local_window_size", -1}, {"num_heads", 2}, {"rotary_interleaved", 0}, {"scale", 1.0}}), query, key, value, k_cache, v_cache, slk, tsl, cos_cache, sin_cache); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), r); mm->add_return({r0, r1, r2}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_group_query_attention_no_rotary.cpp000066400000000000000000000115541510465702400273360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_group_query_attention_no_rotary : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector query_lens{1, 5, 12288}; std::vector kv_lens{1, 32, 4096, 128}; std::vector slk_lens{1, 1}; std::vector tsl_lens{1, 1}; std::vector cs_cache_lens{4096, 64}; auto dtype = migraphx::shape::half_type; migraphx::shape query_s{dtype, query_lens}; migraphx::shape kv_s{dtype, kv_lens}; migraphx::shape slk_s{migraphx::shape::int64_type, slk_lens}; migraphx::shape tsl_s{migraphx::shape::int64_type, tsl_lens}; migraphx::shape cs_cache_s{dtype, cs_cache_lens}; std::vector slk_vec(slk_s.elements(), 5); std::vector tsl_vec(tsl_s.elements(), 4096); std::vector k_vec(kv_s.elements(), 1.0); std::vector v_vec(kv_s.elements(), 0.0); std::vector q_min_vec(query_s.elements(), -100.0); std::vector q_max_vec(query_s.elements(), 100.0); std::vector cs_min_vec(cs_cache_s.elements(), -1.0); std::vector cs_max_vec(cs_cache_s.elements(), 1.0); auto k_cache = mm->add_literal(kv_s, k_vec); auto v_cache = mm->add_literal(kv_s, v_vec); auto query = mm->add_parameter("query", query_s); auto q_min = mm->add_literal(query_s, q_min_vec); auto q_max = mm->add_literal(query_s, q_max_vec); query = mm->add_instruction(migraphx::make_op("clip"), query, q_min, q_max); auto slk = mm->add_literal(slk_s, slk_vec); auto tsl = mm->add_literal(tsl_s, tsl_vec); auto key = mm->add_literal(0.0f); auto value = mm->add_literal(0.0f); auto cs_min = mm->add_literal(cs_cache_s, cs_min_vec); auto cs_max = mm->add_literal(cs_cache_s, cs_max_vec); auto cos_cache = mm->add_parameter("cos_cache", cs_cache_s); auto sin_cache = mm->add_parameter("sin_cache", cs_cache_s); cos_cache = mm->add_instruction(migraphx::make_op("clip"), cos_cache, cs_min, cs_max); sin_cache = mm->add_instruction(migraphx::make_op("clip"), sin_cache, cs_min, cs_max); auto r = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 0}, {"kv_num_heads", 32}, {"local_window_size", -1}, {"num_heads", 32}, {"rotary_interleaved", 0}}), query, key, value, k_cache, v_cache, slk, tsl, cos_cache, sin_cache); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), r); mm->add_return({r0, r1, r2}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_group_query_attention_prefill.cpp000066400000000000000000000117271510465702400267610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_group_query_attention_prefill : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector query_lens{1, 15, 12288}; std::vector kv_lens{1, 32, 4096, 128}; std::vector slk_lens{1, 1}; std::vector tsl_lens{1, 1}; std::vector cs_cache_lens{4096, 64}; auto dtype = migraphx::shape::half_type; migraphx::shape query_s{dtype, query_lens}; migraphx::shape kv_s{dtype, kv_lens}; migraphx::shape slk_s{migraphx::shape::int32_type, slk_lens}; migraphx::shape tsl_s{migraphx::shape::int32_type, tsl_lens}; migraphx::shape cs_cache_s{dtype, cs_cache_lens}; auto query = mm->add_parameter("query", query_s); std::vector slk_vec(slk_s.elements(), 15); std::vector tsl_vec(tsl_s.elements(), 4096); std::vector cs_min_vec(cs_cache_s.elements(), -1.0); std::vector cs_max_vec(cs_cache_s.elements(), 1.0); std::vector q_min_vec(query_s.elements(), -8.3); std::vector q_max_vec(query_s.elements(), 11.5); std::vector q_scale_vec(query_s.elements(), 15); auto q_min = mm->add_literal(query_s, q_min_vec); auto q_max = mm->add_literal(query_s, q_max_vec); auto q_scale = mm->add_literal(query_s, q_scale_vec); query = mm->add_instruction(migraphx::make_op("mul"), query, q_scale); query = mm->add_instruction(migraphx::make_op("clip"), query, q_min, q_max); auto k_cache = mm->add_parameter("k_cache", kv_s); auto v_cache = mm->add_parameter("v_cache", kv_s); auto slk = mm->add_literal(slk_s, slk_vec); auto tsl = mm->add_literal(tsl_s, tsl_vec); auto key = mm->add_literal(0.0f); auto value = mm->add_literal(0.0f); auto cs_min = mm->add_literal(cs_cache_s, cs_min_vec); auto cs_max = mm->add_literal(cs_cache_s, cs_max_vec); auto cos_cache = mm->add_parameter("cos_cache", cs_cache_s); auto sin_cache = mm->add_parameter("sin_cache", cs_cache_s); cos_cache = mm->add_instruction(migraphx::make_op("clip"), cos_cache, cs_min, cs_max); sin_cache = mm->add_instruction(migraphx::make_op("clip"), sin_cache, cs_min, cs_max); auto r = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 1}, {"kv_num_heads", 32}, {"local_window_size", -1}, {"num_heads", 32}, {"rotary_interleaved", 0}}), query, key, value, k_cache, v_cache, slk, tsl, cos_cache, sin_cache); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), r); mm->add_return({r0, r1, r2}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_group_query_attention_prefill_small.cpp000066400000000000000000000124011510465702400301370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_group_query_attention_prefill_small : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector query_lens{1, 2, 12}; std::vector kv_lens{1, 2, 4, 2}; std::vector slk_lens{1, 1}; std::vector tsl_lens{1, 1}; std::vector cs_cache_lens{4, 1}; auto dtype = migraphx::shape::half_type; migraphx::shape query_s{dtype, query_lens}; migraphx::shape kv_s{dtype, kv_lens}; migraphx::shape slk_s{migraphx::shape::int32_type, slk_lens}; migraphx::shape tsl_s{migraphx::shape::int32_type, tsl_lens}; migraphx::shape cs_cache_s{dtype, cs_cache_lens}; auto query = mm->add_parameter("query", query_s); std::vector slk_vec(slk_s.elements(), 2); std::vector tsl_vec(tsl_s.elements(), 4); std::vector cs_min_vec(cs_cache_s.elements(), -1.0); std::vector cs_max_vec(cs_cache_s.elements(), 1.0); std::vector q_min_vec(query_s.elements(), -1.0); std::vector q_max_vec(query_s.elements(), 1.0); std::vector kv_min_vec(kv_s.elements(), -1.0); std::vector kv_max_vec(kv_s.elements(), 1.0); auto k_cache = mm->add_parameter("k_cache", kv_s); auto v_cache = mm->add_parameter("v_cache", kv_s); auto slk = mm->add_literal(slk_s, slk_vec); auto tsl = mm->add_literal(tsl_s, tsl_vec); auto key = mm->add_literal(0.0f); auto value = mm->add_literal(0.0f); auto cs_min = mm->add_literal(cs_cache_s, cs_min_vec); auto cs_max = mm->add_literal(cs_cache_s, cs_max_vec); auto q_min = mm->add_literal(query_s, q_min_vec); auto q_max = mm->add_literal(query_s, q_max_vec); auto kv_min = mm->add_literal(kv_s, kv_min_vec); auto kv_max = mm->add_literal(kv_s, kv_max_vec); auto cos_cache = mm->add_parameter("cos_cache", cs_cache_s); auto sin_cache = mm->add_parameter("sin_cache", cs_cache_s); query = mm->add_instruction(migraphx::make_op("clip"), query, q_min, q_max); k_cache = mm->add_instruction(migraphx::make_op("clip"), k_cache, kv_min, kv_max); v_cache = mm->add_instruction(migraphx::make_op("clip"), v_cache, kv_min, kv_max); cos_cache = mm->add_instruction(migraphx::make_op("clip"), cos_cache, cs_min, cs_max); sin_cache = mm->add_instruction(migraphx::make_op("clip"), sin_cache, cs_min, cs_max); auto r = mm->add_instruction(migraphx::make_op("group_query_attention", {{"do_rotary", 0}, {"kv_num_heads", 2}, {"local_window_size", -1}, {"num_heads", 2}, {"rotary_interleaved", 0}, {"scale", 1.0}}), query, key, value, k_cache, v_cache, slk, tsl, cos_cache, sin_cache); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); auto r2 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 2}}), r); mm->add_return({r0, r1, r2}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_bidirct.cpp000066400000000000000000000071661510465702400230750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_bidirct : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, lho}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_bidirct_3args.cpp000066400000000000000000000061171510465702400241670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_bidirct_3args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_bidirct_3args_layout.cpp000066400000000000000000000066071510465702400255700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_bidirct_3args_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_bidirct_3args_und.cpp000066400000000000000000000063221510465702400250330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_bidirct_3args_und : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, und, und, und); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_bidirct_default_actv.cpp000066400000000000000000000053251510465702400256110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_bidirct_default_actv : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_bidirct_default_actv1.cpp000066400000000000000000000064011510465702400256660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_bidirct_default_actv1 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_bidirct_layout.cpp000066400000000000000000000101521510465702400244570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_bidirct_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({hs, lho}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_bidirct_seq1.cpp000066400000000000000000000061151510465702400240170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_bidirct_seq1 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_forward.cpp000066400000000000000000000066531510465702400231210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_forward : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({lho, hs}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_forward_3args.cpp000066400000000000000000000056041510465702400242130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_forward_3args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_forward_3args_layout.cpp000066400000000000000000000062741510465702400256140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_forward_3args_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_forward_3args_und.cpp000066400000000000000000000060071510465702400250570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_forward_3args_und : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, und, und, und); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_forward_default_actv.cpp000066400000000000000000000053171510465702400256360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_forward_default_actv : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_forward_default_actv1.cpp000066400000000000000000000064311510465702400257150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_forward_default_actv1 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_forward_layout.cpp000066400000000000000000000076371510465702400245210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_forward_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({lho, hs}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_forward_seq1.cpp000066400000000000000000000056021510465702400240430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_forward_seq1 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_reverse_3args.cpp000066400000000000000000000056041510465702400242220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_reverse_3args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_reverse_3args_layout.cpp000066400000000000000000000062741510465702400256230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_reverse_3args_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_reverse_last.cpp000066400000000000000000000066171510465702400241530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_reverse_last : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto output = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_reverse_last_layout.cpp000066400000000000000000000073611510465702400255450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_reverse_last_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto output = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_gru_two_outputs.cpp000066400000000000000000000055101510465702400240600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_gru_two_outputs : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); auto last_hs = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, last_hs}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_hardmax_axis.cpp000066400000000000000000000046451510465702400232470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_hardmax_axis : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input_type = migraphx::shape::float_type; std::vector input_lens{2, 1, 4, 1025}; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", 2}}), input); auto zero_data = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 1, 1, 1025}}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", 2}}), zero_data, indices, updates); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_hardmax_axis_neg.cpp000066400000000000000000000046571510465702400241030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_hardmax_axis_neg : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input_type = migraphx::shape::float_type; std::vector input_lens{2, 1, 4, 1025}; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", -2}}), input); auto zero_data = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 1, 1, 1025}}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", -2}}), zero_data, indices, updates); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_hardmax_default.cpp000066400000000000000000000046521510465702400237250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_hardmax_default : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input_type = migraphx::shape::float_type; std::vector input_lens{2, 1, 4, 1025}; migraphx::shape data_shape{input_type, input_lens}; auto input = mm->add_parameter("x", data_shape); auto indices = mm->add_instruction(migraphx::make_op("argmax", {{"axis", -1}}), input); auto zero_data = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {0}})); auto updates = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 1, 4, 1}}}), mm->add_literal(migraphx::literal{migraphx::shape{input_type}, {1}})); mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", -1}}), zero_data, indices, updates); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_hsqrt.cpp000066400000000000000000000033641510465702400217350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_hsqrt : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {2, 3, 4, 6}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_hsqrt_bf16.cpp000066400000000000000000000033761510465702400225560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_hsqrt_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {2, 3, 4, 6}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_if_literal.cpp000066400000000000000000000045441510465702400227070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_if_literal : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", cond_s); migraphx::shape s{migraphx::shape::float_type, {5}}; auto* then_mod = p.create_module("If_0_if"); std::vector data1 = {1, 2, 3, 4, 5}; auto l1 = then_mod->add_literal(migraphx::literal(s, data1)); then_mod->add_return({l1}); auto* else_mod = p.create_module("If_0_else"); std::vector data2 = {5, 4, 3, 2, 1}; auto l2 = else_mod->add_literal(migraphx::literal(s, data2)); else_mod->add_return({l2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_if_lp.cpp000066400000000000000000000051251510465702400216620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_if_lp : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; migraphx::shape s{migraphx::shape::float_type, {5}}; auto cond = mm->add_parameter("cond", cond_s); auto x = mm->add_parameter("x", s); auto* then_mod = p.create_module("If_0_if"); std::vector data1 = {1, 2, 3, 4, 5}; auto l1 = then_mod->add_literal(migraphx::literal(s, data1)); then_mod->add_return({l1, x}); auto* else_mod = p.create_module("If_0_else"); std::vector data2 = {5, 4, 3, 2, 1}; auto l2 = else_mod->add_literal(migraphx::literal(s, data2)); auto s2 = else_mod->add_instruction(migraphx::make_op("add"), x, l2); else_mod->add_return({s2, l2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), ret); mm->add_return({r0, r1}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_if_param.cpp000066400000000000000000000053271510465702400223530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_if_param : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape cond_s{migraphx::shape::bool_type}; auto cond = mm->add_parameter("cond", cond_s); migraphx::shape ds{migraphx::shape::float_type, {2, 3}}; auto x = mm->add_parameter("x", ds); auto y = mm->add_parameter("y", ds); auto* then_mod = p.create_module("If_0_if"); std::vector data1 = {0.384804, -1.77948, -0.453775, 0.477438, -1.06333, -1.12893}; auto l1 = then_mod->add_literal(migraphx::literal(ds, data1)); auto a1 = then_mod->add_instruction(migraphx::make_op("add"), x, l1); then_mod->add_return({a1}); auto* else_mod = p.create_module("If_0_else"); std::vector data2 = {-0.258047, 0.360394, 0.536804, -0.577762, 1.0217, 1.02442}; auto l2 = else_mod->add_literal(migraphx::literal(ds, data2)); auto a2 = else_mod->add_instruction(migraphx::make_op("mul"), y, l2); else_mod->add_return({a2}); auto ret = mm->add_instruction(migraphx::make_op("if"), {cond}, {then_mod, else_mod}); auto r = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), ret); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_instancenorm.cpp000066400000000000000000000113651510465702400232740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include static migraphx::instruction_ref add_instancenorm(migraphx::module& m, migraphx::instruction_ref x, const std::vector& dims, float eps = 1e-5f) { auto mgx_type = x->get_shape().type(); auto x_lens = x->get_shape().lens(); std::vector axes(x_lens.size() - 2); std::iota(axes.begin(), axes.end(), 2); auto scale = m.add_parameter("scale", migraphx::shape{mgx_type, dims}); auto bias = m.add_parameter("bias", migraphx::shape{mgx_type, dims}); auto epsilon = m.add_literal(migraphx::literal{migraphx::shape{mgx_type}, {eps}}); auto mean = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), x); auto mean_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", x_lens}}), mean); auto sub = m.add_instruction(migraphx::make_op("sub"), x, mean_mbcast); auto l0 = m.add_instruction(migraphx::make_op("sqdiff"), {x, mean_mbcast}); auto var = m.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), {l0}); auto epsilon_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", x_lens}}), epsilon); auto var_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", x_lens}}), var); auto add_epsilon = m.add_instruction(migraphx::make_op("add"), var_mbcast, epsilon_mbcast); auto rsqrt = m.add_instruction(migraphx::make_op("rsqrt"), add_epsilon); auto l1 = m.add_instruction(migraphx::make_op("mul"), {sub, rsqrt}); auto scale_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", x_lens}}), scale); auto mul = m.add_instruction(migraphx::make_op("mul"), scale_mbcast, l1); auto bias_mbcast = m.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", x_lens}}), bias); return m.add_instruction(migraphx::make_op("add"), mul, bias_mbcast); } template struct test_instancenorm : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 2, 5, 5}; auto x = mm->add_parameter("x", migraphx::shape{TYPE, dims}); add_instancenorm(*mm, x, {1, 2, 1, 1}); return p; } std::string section() const { return "reduce"; } }; template struct test_instancenorm; template struct test_instancenorm; template struct test_instancenorm_large_3d : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 32, 64, 64, 64}; auto x = mm->add_parameter("x", migraphx::shape{TYPE, dims}); add_instancenorm(*mm, x, {1, 32, 1, 1, 1}); return p; } std::string section() const { return "reduce"; } }; template struct test_instancenorm_large_3d; template struct test_instancenorm_large_3d; template struct test_instancenorm_large_3d; ROCm-AMDMIGraphX-46524e8/test/verify/test_isinf.cpp000066400000000000000000000044201510465702400216760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include template struct test_isinf : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto max = std::numeric_limits::max(); auto min = std::numeric_limits::min(); auto inf = std::numeric_limits::infinity(); auto nan = std::numeric_limits::quiet_NaN(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::get_type(), {5}}); std::vector data0{inf, -inf, max, min, nan}; migraphx::shape s1{migraphx::shape::get_type(), {5}}; auto l0 = mm->add_literal(migraphx::literal{s1, data0}); x = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, l0); mm->add_instruction(migraphx::make_op("isinf"), x); return p; } }; template struct test_isinf; template struct test_isinf; template struct test_isinf; ROCm-AMDMIGraphX-46524e8/test/verify/test_isinf_broadcast.cpp000066400000000000000000000042651510465702400237270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include struct test_isinf_broadcast : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2}}); auto s0 = migraphx::shape{migraphx::shape::float_type, {2, 2}}; x = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", s0.lens()}}), x); auto inf = std::numeric_limits::infinity(); std::vector data0{-inf, inf}; migraphx::shape s1{migraphx::shape::float_type, {1, 2}}; auto l0 = mm->add_literal(migraphx::literal{s1, data0}); x = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, l0); mm->add_instruction(migraphx::make_op("isinf"), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_isnan.cpp000066400000000000000000000044141510465702400217010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include template struct test_isnan : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2}}); auto l0 = mm->add_literal(std::numeric_limits::quiet_NaN()); x = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, l0); mm->add_instruction(migraphx::make_op("isnan"), x); return p; } }; template struct test_isnan; template struct test_isnan; template struct test_isnan; template struct test_isnan; template struct test_isnan; template struct test_isnan; template struct test_isnan; ROCm-AMDMIGraphX-46524e8/test/verify/test_isnan_broadcast.cpp000066400000000000000000000042331510465702400237220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include "verify_program.hpp" #include #include #include struct test_isnan_broadcast : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2}}); auto s0 = migraphx::shape{migraphx::shape::float_type, {2, 2}}; x = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", s0.lens()}}), x); std::vector data0{2, std::numeric_limits::quiet_NaN()}; migraphx::shape s1{migraphx::shape::float_type, {1, 2}}; auto l0 = mm->add_literal(migraphx::literal{s1, data0}); x = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), x, l0); mm->add_instruction(migraphx::make_op("isnan"), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_layernorm.cpp000066400000000000000000000212751510465702400226050ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_layernorm : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 2, 5}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 4, 24}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_large : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 32, 262144}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_fp16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 24, 64}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::half_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 24, 64}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::bf16_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_fp8_1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 24, 64}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e4m3fnuz_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_fp8_2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 24, 64}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e5m2fnuz_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_fp8_3 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 24, 64}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e4m3fn_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_fp8_4 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 24, 64}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp8e5m2_type, dims}); add_layernorm(*mm, x, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_eps : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 2, 5}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dims}); add_layernorm(*mm, x, dims, 1e-5f); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_triadd : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 4, 24}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dims}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, dims}); auto z = mm->add_parameter("z", migraphx::shape{migraphx::shape::float_type, dims}); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, z); add_layernorm(*mm, add2, dims); return p; } std::string section() const { return "reduce"; } }; struct test_layernorm_triadd_large : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 384, 1024}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dims}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, dims}); auto z = mm->add_parameter("z", migraphx::shape{migraphx::shape::float_type, dims}); auto add1 = mm->add_instruction(migraphx::make_op("add"), x, y); auto add2 = mm->add_instruction(migraphx::make_op("add"), add1, z); add_layernorm(*mm, add2, dims); return p; } std::string section() const { return "reduce"; } }; struct test_add_layernorm_add_gemm_nonstd : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape::from_permutation(migraphx::shape::float_type, {8, 1, 16}, {1, 2, 0}); auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", migraphx::shape{migraphx::shape::float_type, {8, 16, 64}}); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); auto layernorm_ins = add_layernorm(*mm, add, s.lens()); mm->add_instruction(migraphx::make_op("dot"), layernorm_ins, z); return p; } std::string section() const { return "gemm"; } }; struct test_pw_layernorm : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector dims = {1, 9, 6}; auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, dims}); add_pointwise_layernorm(*mm, x, dims); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_layout.cpp000066400000000000000000000033751510465702400221130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_layout : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 320, 128, 128}}; auto x = mm->add_parameter("x", s); auto layout = mm->add_instruction(migraphx::make_op("layout", {{"permutation", {0, 2, 3, 1}}}), x); mm->add_return({layout}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_less.cpp000066400000000000000000000034051510465702400215360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_less : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 3, 4, 6}}; auto input1 = mm->add_parameter("x", s); auto input2 = mm->add_parameter("y", s); auto r = mm->add_instruction(migraphx::make_op("less"), input1, input2); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_less_brcst.cpp000066400000000000000000000036621510465702400227400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_less_brcst : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {3, 3}}; auto l0 = mm->add_parameter("x", s0); migraphx::shape s1{migraphx::shape::float_type, {3, 1}}; auto l1 = mm->add_parameter("y", s1); auto bl1 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s0.lens()}}), l1); auto r = mm->add_instruction(migraphx::make_op("less"), l0, bl1); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_literal_limits.cpp000066400000000000000000000060701510465702400236060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_literal_limits : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input_s = migraphx::shape(Q, {3, 1}); T infinity_val{0}; if constexpr(std::numeric_limits::has_infinity and std::is_floating_point{}) { infinity_val = std::numeric_limits::infinity(); } std::vector s_data{ infinity_val, static_cast(-infinity_val), std::numeric_limits::quiet_NaN()}; auto input_param = mm->add_parameter("test_input", input_s); auto input = mm->add_literal(migraphx::literal{input_s, s_data}); auto o1 = mm->add_instruction(migraphx::make_op("mul"), input, input_param); mm->add_instruction(migraphx::make_op("isnan"), o1); return p; } }; template struct test_literal_limits; template struct test_literal_limits; template struct test_literal_limits; template struct test_literal_limits; template struct test_literal_limits; template struct test_literal_limits; template struct test_literal_limits; template struct test_literal_limits; template struct test_literal_limits; template struct test_literal_limits; ROCm-AMDMIGraphX-46524e8/test/verify/test_literals.cpp000066400000000000000000000037011510465702400224060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_literals : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_literal( generate_literal(migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}})); auto weights = mm->add_literal( generate_literal(migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}})); auto conv = mm->add_instruction(migraphx::make_op("convolution"), input, weights); mm->add_instruction(migraphx::make_op("relu"), conv); return p; } std::string section() const { return "conv"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_log.cpp000066400000000000000000000041431510465702400213510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_log : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {6}}; auto x = mm->add_instruction(migraphx::make_op("abs"), mm->add_parameter("x", s)); mm->add_instruction(migraphx::make_op("log"), x); return p; } }; template struct test_log; template struct test_log; template struct test_log; template struct test_log; template struct test_log; template struct test_log; template struct test_log; ROCm-AMDMIGraphX-46524e8/test/verify/test_log2.cpp000066400000000000000000000041551510465702400214360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_log2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {6}}; auto x = mm->add_instruction(migraphx::make_op("abs"), mm->add_parameter("x", s)); mm->add_instruction(migraphx::make_op("log2"), x); return p; } }; template struct test_log2; template struct test_log2; template struct test_log2; template struct test_log2; template struct test_log2; template struct test_log2; template struct test_log2; ROCm-AMDMIGraphX-46524e8/test/verify/test_logsoftmax.cpp000066400000000000000000000061201510465702400227500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_logsoftmax : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{T, {10, 4, 2080, 6}}; auto param = mm->add_parameter("0", s); mm->add_instruction(migraphx::make_op("logsoftmax", {{"axis", Axis}}), param); return p; } std::string section() const { return "reduce"; } }; template struct test_logsoftmax<0, migraphx::shape::float_type>; template struct test_logsoftmax<1, migraphx::shape::float_type>; template struct test_logsoftmax<2, migraphx::shape::float_type>; template struct test_logsoftmax<3, migraphx::shape::float_type>; template struct test_logsoftmax<1, migraphx::shape::half_type>; template struct test_logsoftmax<0, migraphx::shape::half_type>; template struct test_logsoftmax<2, migraphx::shape::half_type>; template struct test_logsoftmax<3, migraphx::shape::half_type>; template struct test_logsoftmax<1, migraphx::shape::bf16_type>; template struct test_logsoftmax<0, migraphx::shape::bf16_type>; template struct test_logsoftmax<2, migraphx::shape::bf16_type>; template struct test_logsoftmax<3, migraphx::shape::bf16_type>; template struct test_logsoftmax<1, migraphx::shape::fp8e4m3fnuz_type>; template struct test_logsoftmax<3, migraphx::shape::fp8e4m3fnuz_type>; template struct test_logsoftmax<1, migraphx::shape::fp8e5m2fnuz_type>; template struct test_logsoftmax<3, migraphx::shape::fp8e5m2fnuz_type>; template struct test_logsoftmax<1, migraphx::shape::fp8e4m3fn_type>; template struct test_logsoftmax<3, migraphx::shape::fp8e4m3fn_type>; template struct test_logsoftmax<1, migraphx::shape::fp8e5m2_type>; template struct test_logsoftmax<3, migraphx::shape::fp8e5m2_type>; ROCm-AMDMIGraphX-46524e8/test/verify/test_logsoftmax1.cpp000066400000000000000000000035761510465702400230450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_logsoftmax1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {5, 3, 3, 4}}); auto tx = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {2, 3, 0, 1}}}), x); auto r = mm->add_instruction(migraphx::make_op("logsoftmax", {{"axis", 0}}), tx); mm->add_return({r}); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_loop.cpp000066400000000000000000000062541510465702400215460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_loop : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape si{migraphx::shape::int64_type}; migraphx::shape s{migraphx::shape::int64_type, {1}}; migraphx::shape sc{migraphx::shape::bool_type}; int64_t iter_num = 10; auto in_iter = mm->add_literal(migraphx::literal(si, {iter_num})); auto in_cond = mm->add_parameter("ccond", sc); int64_t value = 5; auto in_val = mm->add_literal(migraphx::literal(s, {value})); auto* body = p.create_module("loop_module"); auto iter = body->add_parameter("iter_num", si); body->add_parameter("cond", sc); auto in_v = body->add_parameter("input", s); std::vector vd = {3}; auto l = body->add_literal(migraphx::literal(si, vd)); auto ad = body->add_instruction(migraphx::make_op("add"), iter, l); auto val = body->add_instruction(migraphx::make_op("add"), in_v, ad); auto eq = body->add_instruction(migraphx::make_op("equal"), iter, l); auto beq = body->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::bool_type}}), eq); auto neq = body->add_instruction(migraphx::make_op("not"), beq); body->add_return({neq, val, val}); auto rl = mm->add_instruction( migraphx::make_op("loop", {{"max_iterations", 8}}), {in_iter, in_cond, in_val}, {body}); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), rl); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), rl); mm->add_return({r0, r1}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lpnorm_pooling.cpp000066400000000000000000000043241510465702400236270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_lpnorm_pooling : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 3, 5, 5}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {0, 0}}, {"stride", {1, 1}}, {"lengths", {3, 3}}, {"lp_order", N}}), x); return p; } }; template struct test_lpnorm_pooling<0>; template struct test_lpnorm_pooling<1>; template struct test_lpnorm_pooling<2>; template struct test_lpnorm_pooling<3>; ROCm-AMDMIGraphX-46524e8/test/verify/test_lpnorm_pooling_asym_pad.cpp000066400000000000000000000040571510465702400255070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_lpnorm_pooling_asym_pad : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 1, 5, 4}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {1, 0, 0, 0}}, {"stride", {1, 1}}, {"lengths", {2, 1}}, {"lp_order", 2}}), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lpnorm_pooling_ceil_3d.cpp000066400000000000000000000043761510465702400252200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_lpnorm_pooling_ceil_3d : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 5, 5, 5}}); auto op = migraphx::op::pooling{migraphx::op::pooling_mode::lpnorm, {1, 1, 1}, {3, 3, 3}, {3, 3, 3}, {1, 1, 1}, true, N}; mm->add_instruction(op, input); return p; } }; template struct test_lpnorm_pooling_ceil_3d<0>; template struct test_lpnorm_pooling_ceil_3d<1>; template struct test_lpnorm_pooling_ceil_3d<2>; template struct test_lpnorm_pooling_ceil_3d<3>; ROCm-AMDMIGraphX-46524e8/test/verify/test_lpnorm_pooling_pad.cpp000066400000000000000000000040371510465702400244540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_lpnorm_pooling_pad : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 3, 5, 5}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::lpnorm}, {"padding", {2, 2}}, {"stride", {1, 1}}, {"lengths", {3, 3}}, {"lp_order", 2}}), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lrn.cpp000066400000000000000000000037231510465702400213660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_lrn : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter( "x", migraphx::shape{migraphx::shape::float_type, {1, ChannelSize, 28, 28}}); mm->add_instruction( migraphx::make_op( "lrn", {{"alpha", 0.0001}, {"beta", 0.75}, {"bias", 1.0}, {"size", LrnSize}}), x); return p; } }; template struct test_lrn<32, 6>; template struct test_lrn<32, 5>; template struct test_lrn<31, 8>; template struct test_lrn<31, 5>; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_3args.cpp000066400000000000000000000064241510465702400243520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_3args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_3args_layout.cpp000066400000000000000000000071141510465702400257440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_3args_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_3args_und.cpp000066400000000000000000000062221510465702400252140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_3args_und : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, und, und, und, und, und); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_default_actv.cpp000066400000000000000000000053301510465702400257670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_default_actv : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_default_actv1.cpp000066400000000000000000000066031510465702400260540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_default_actv1 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {batch_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); std::vector sl_data(batch_size, 2); auto sql = mm->add_literal(migraphx::literal{sl_shape, sl_data}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, sql, ih); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_default_actv2.cpp000066400000000000000000000064371510465702400260620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_default_actv2 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{ migraphx::make_op("tanh"), migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_hs.cpp000066400000000000000000000075101510465702400237420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_hs : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {batch_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); std::vector sl_data{3, 2}; auto sql = mm->add_literal(migraphx::literal{migraphx::literal{sl_shape, sl_data}}); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, sql, ih); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_last.cpp000066400000000000000000000101631510465702400242710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_last : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto ic = mm->add_parameter("ic", ic_shape); auto pph = mm->add_parameter("pph", pph_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto output = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_last_layout.cpp000066400000000000000000000111051510465702400256630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_last_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto ic = mm->add_parameter("ic", ic_shape); auto pph = mm->add_parameter("pph", pph_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto output = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih, ic, pph); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_bidirct_seq1.cpp000066400000000000000000000064221510465702400242020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_bidirct_seq1 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_3args.cpp000066400000000000000000000057501510465702400243770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_3args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_3args_und.cpp000066400000000000000000000062151510465702400252420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_3args_und : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, und, und, und, und, und); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_default_actv.cpp000066400000000000000000000053221510465702400260140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_default_actv : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", {}}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_default_actv1.cpp000066400000000000000000000063761510465702400261070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_default_actv1 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value( std::vector{migraphx::make_op("sigmoid")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_hs.cpp000066400000000000000000000073471510465702400237760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_hs : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto ic = mm->add_parameter("ic", ic_shape); auto pph = mm->add_parameter("pph", pph_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih, ic, pph); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_hs_layout.cpp000066400000000000000000000103361510465702400253630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_hs_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto ic = mm->add_parameter("ic", ic_shape); auto pph = mm->add_parameter("pph", pph_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih, ic, pph); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_last.cpp000066400000000000000000000076301510465702400243220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_last : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape l_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto len = mm->add_literal(migraphx::literal(l_shape, {1, 2})); auto ic = mm->add_parameter("ic", ic_shape); auto pph = mm->add_parameter("pph", pph_shape); auto output = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, len, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output, len); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_last_layout.cpp000066400000000000000000000105661510465702400257210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_last_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape l_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto len = mm->add_literal(migraphx::literal(l_shape, {1, 2})); auto ic = mm->add_parameter("ic", ic_shape); auto pph = mm->add_parameter("pph", pph_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); ic = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ic); auto output = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, len, ih, ic, pph); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output, len); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_forward_seq1.cpp000066400000000000000000000057461510465702400242360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_forward_seq1 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_reverse_3args.cpp000066400000000000000000000057501510465702400244060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_reverse_3args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_reverse_3args_cell_output.cpp000066400000000000000000000061271510465702400270240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_reverse_3args_cell_output : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_reverse_3args_cell_output_layout.cpp000066400000000000000000000065331510465702400304220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_reverse_3args_cell_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r); auto cell_output = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), cell_output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_reverse_3args_layout.cpp000066400000000000000000000064401510465702400260000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_reverse_3args_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_reverse_last.cpp000066400000000000000000000075071510465702400243340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_reverse_last : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 8 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape ic_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; migraphx::shape pph_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto ic = mm->add_parameter("ic", ic_shape); auto pph = mm->add_parameter("pph", pph_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto output = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih, ic, pph); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_three_outputs.cpp000066400000000000000000000063351510465702400245460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_three_outputs : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); auto last_hs = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); auto last_cell = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); mm->add_return({hs, last_hs, last_cell}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_three_outputs_layout.cpp000066400000000000000000000073751510465702400261500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_three_outputs_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); auto last_hs = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); auto last_cell = mm->add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); last_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_hs); last_cell = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_cell); mm->add_return({hs, last_hs, last_cell}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_lstm_two_outputs.cpp000066400000000000000000000061571510465702400242520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_lstm_two_outputs : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 4 * hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto hs = mm->add_instruction( migraphx::make_op( "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r); auto last_hs = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, last_hs}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_max_pooling_1x1_s2.cpp000066400000000000000000000040411510465702400241760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_max_pooling_1x1_s2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 4, 16, 16}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0}}, {"stride", {2, 2}}, {"lengths", {1, 1}}}), input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_max_pooling_asym_pad.cpp000066400000000000000000000040561510465702400247640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_max_pooling_asym_pad : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 64, 112, 112}}); mm->add_instruction(migraphx::make_op("pooling", {{"mode", migraphx::op::pooling_mode::max}, {"padding", {0, 0, 1, 1}}, {"stride", {2, 2}}, {"lengths", {3, 3}}}), input); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_max_pooling_ceil_3d.cpp000066400000000000000000000037021510465702400244660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_max_pooling_ceil_3d : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{T, {1, 3, 5, 5, 5}}); auto op = migraphx::op::pooling{ migraphx::op::pooling_mode::max, {1, 1, 1}, {3, 3, 3}, {3, 3, 3}, {1, 1, 1}, true}; mm->add_instruction(op, input); return p; } }; template struct test_max_pooling_ceil_3d; template struct test_max_pooling_ceil_3d; ROCm-AMDMIGraphX-46524e8/test/verify/test_min_max.cpp000066400000000000000000000057531510465702400222300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_min_max : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{T, {128}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(Op{}, x, y); return p; } }; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; template struct test_min_max; ROCm-AMDMIGraphX-46524e8/test/verify/test_mul.cpp000066400000000000000000000032671510465702400213730ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_mul : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("mul"), x, y); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_mul_add.cpp000066400000000000000000000041711510465702400221760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_mul_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::shape bs{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto a = mm->add_parameter("a", bs); auto b = mm->add_parameter("b", bs); auto ab = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), a); auto bb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), b); auto mul = mm->add_instruction(migraphx::make_op("mul"), x, ab); mm->add_instruction(migraphx::make_op("add"), mul, bb); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_mul_dot_a.cpp000066400000000000000000000051541510465702400225360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_mul_dot_a : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape as{DType, {2, 256, 32}}; migraphx::shape bs{DType, {2, 32, 128}}; auto a = mm->add_parameter("input", as); auto lit = mm->add_literal(migraphx::generate_literal({DType, {1, 1, 32}})); auto litb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", as.lens()}}), lit); auto mul = mm->add_instruction(migraphx::make_op("mul"), a, litb); auto b = mm->add_literal(migraphx::generate_literal(bs)); auto dot = mm->add_instruction(migraphx::make_op("dot"), mul, b); mm->add_return({dot}); return p; } std::string section() const { return "gemm"; } }; template struct test_mul_dot_a; template struct test_mul_dot_a; template struct test_mul_dot_a; template struct test_mul_dot_a; template struct test_mul_dot_a; template struct test_mul_dot_a; template struct test_mul_dot_a; ROCm-AMDMIGraphX-46524e8/test/verify/test_mul_dot_b.cpp000066400000000000000000000051551510465702400225400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_mul_dot_b : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape as{DType, {2, 256, 32}}; migraphx::shape bs{DType, {2, 32, 128}}; auto b = mm->add_parameter("input", bs); auto lit = mm->add_literal(migraphx::generate_literal({DType, {1, 32, 1}})); auto litb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", bs.lens()}}), lit); auto mul = mm->add_instruction(migraphx::make_op("mul"), b, litb); auto a = mm->add_literal(migraphx::generate_literal(as)); auto dot = mm->add_instruction(migraphx::make_op("dot"), a, mul); mm->add_return({dot}); return p; } std::string section() const { return "gemm"; } }; template struct test_mul_dot_b; template struct test_mul_dot_b; template struct test_mul_dot_b; template struct test_mul_dot_b; template struct test_mul_dot_b; template struct test_mul_dot_b; template struct test_mul_dot_b; ROCm-AMDMIGraphX-46524e8/test/verify/test_multinomial.cpp000066400000000000000000000065651510465702400231340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_multinomial : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); size_t sample_size = 10; size_t batch_size = 2; float seed = 0.0f; std::mt19937 gen(seed); std::uniform_real_distribution<> dis(0.0, 1.0); std::vector rand_samples(batch_size * sample_size); std::generate(rand_samples.begin(), rand_samples.end(), [&]() { return dis(gen); }); migraphx::shape rs{DType, {batch_size, sample_size}}; auto rs_lit = mm->add_literal(migraphx::literal{rs, rand_samples}); migraphx::shape s{DType, {batch_size, 5}}; auto input = mm->add_parameter("input", s); auto maxes = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {1}}}), input); auto mb_maxes = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 5}}}), maxes); auto cdf = mm->add_instruction(migraphx::make_op("sub"), input, mb_maxes); cdf = mm->add_instruction(migraphx::make_op("exp"), cdf); cdf = mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), cdf); mm->add_instruction(migraphx::make_op("multinomial"), cdf, rs_lit); return p; } }; template struct test_multinomial; template struct test_multinomial; // TODO bf16 accumulates more rounding errors in exp() and prefix_scan_sum, which is slightly // shifting the cdf values and causing off-by-one errors. // template struct test_multinomial; // TODO This fails, need to figure out why // template struct test_multinomial; // template struct test_multinomial; // template struct test_multinomial; // template struct test_multinomial; ROCm-AMDMIGraphX-46524e8/test/verify/test_nearbyint.cpp000066400000000000000000000043121510465702400225610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_nearbyint : verify_program> { migraphx::program create_program() const { migraphx::program p; std::vector tmp{-4.5, -3.5, 0.5, 2.5, 3.5}; std::vector data{tmp.cbegin(), tmp.cend()}; migraphx::shape s1{migraphx::shape::get_type(), {5}}; auto* mm = p.get_main_module(); auto l0 = mm->add_literal(migraphx::literal{s1, data}); mm->add_instruction(migraphx::make_op("isinf"), l0); return p; }; }; template struct test_nearbyint; template struct test_nearbyint; template struct test_nearbyint; template struct test_nearbyint; template struct test_nearbyint; template struct test_nearbyint; template struct test_nearbyint; ROCm-AMDMIGraphX-46524e8/test/verify/test_neg.cpp000066400000000000000000000032341510465702400213410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_neg : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 3, 4, 6}}; auto input = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("neg"), input); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_nms.cpp000066400000000000000000000045741510465702400213750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_nms : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape boxes_s{migraphx::shape::float_type, {1, 6, 4}}; migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 6}}; std::vector scores_vec = {0.9, 0.75, 0.6, 0.95, 0.5, 0.3}; auto boxes_l = mm->add_parameter("boxes", boxes_s); auto scores_l = mm->add_literal(migraphx::literal(scores_s, scores_vec)); auto max_out_l = mm->add_literal(int64_t{4}); auto iou_threshold = mm->add_literal(0.5f); auto score_threshold = mm->add_literal(0.0f); auto r = mm->add_instruction(migraphx::make_op("nonmaxsuppression", {{"center_point_box", 1}}), boxes_l, scores_l, max_out_l, iou_threshold, score_threshold); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_nonstd_gather.cpp000066400000000000000000000041761510465702400234350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_nonstd_gather : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3}}; migraphx::shape s_indices{migraphx::shape::int32_type, {2, 2}}; std::vector indices{1, 1, 0, 2}; auto d = mm->add_parameter("data", s); auto td = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), d); auto ind = mm->add_literal(migraphx::literal{s_indices, indices}); auto tind = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), ind); auto r = mm->add_instruction(migraphx::make_op("gather", {{"axis", 1}}), td, tind); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_nonzero.cpp000066400000000000000000000042171510465702400222640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_nonzero : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {2, 3, 4, 5}}; auto x = mm->add_parameter("data", s); auto r = mm->add_instruction(migraphx::make_op("nonzero"), x); mm->add_return({r}); return p; } }; template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; ROCm-AMDMIGraphX-46524e8/test/verify/test_not.cpp000066400000000000000000000032071510465702400213700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_not : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {4}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("not"), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_onehot.cpp000066400000000000000000000056461510465702400220750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_onehot : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {4}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}, {0}}; migraphx::shape values_s{DType, {2}}; auto inds_lit = mm->add_literal(migraphx::literal{inds_s, {0, -8, -1, 5}}); auto depth_lit = mm->add_literal(migraphx::literal{depth_s, {3}}); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", -1}}), inds_lit, depth_lit, values_param); return p; } }; template struct test_onehot; template struct test_onehot; struct test_onehot_param_inds : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape inds_s{migraphx::shape::int64_type, {2, 2}}; migraphx::shape depth_s{migraphx::shape::int64_type, {1}, {0}}; migraphx::shape values_s{migraphx::shape::float_type, {2}}; auto inds_param = mm->add_parameter("indices", inds_s); auto depth_lit = mm->add_literal(migraphx::literal{depth_s, {3}}); auto values_param = mm->add_parameter("values", values_s); mm->add_instruction( migraphx::make_op("onehot", {{"axis", 0}}), inds_param, depth_lit, values_param); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_or.cpp000066400000000000000000000032731510465702400212130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_or : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("logical_or"), x, y); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pack_fp4.cpp000066400000000000000000000035011510465702400222540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_pack_fp4 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{T, {64, 32}}); mm->add_instruction(migraphx::make_op("pack_fp4", {{"axis", Axis}}), x); return p; } }; template struct test_pack_fp4; template struct test_pack_fp4; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad.cpp000066400000000000000000000046331510465702400213400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_pad : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{DType, {1, 96, 165, 165}}; std::vector pads0 = {0, 0, 0, 0, 0, 0, 1, 1}; std::vector pads1 = {0, 0, 0, 0, 1, 1, 1, 1}; std::vector pads2 = {1, 1, 1, 1, 0, 0, 0, 0}; std::vector pads3 = {1, 0, 1, 0, 1, 0, 2, 0}; auto l0 = mm->add_parameter("x", s0); mm->add_instruction(migraphx::make_op("pad", {{"pads", pads0}}), l0); mm->add_instruction(migraphx::make_op("pad", {{"pads", pads1}}), l0); mm->add_instruction(migraphx::make_op("pad", {{"pads", pads2}}), l0); mm->add_instruction(migraphx::make_op("pad", {{"pads", pads3}}), l0); return p; } }; template struct test_pad; template struct test_pad; template struct test_pad; template struct test_pad; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad_asymmetrical.cpp000066400000000000000000000033221510465702400241040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pad_asymmetrical : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {1, 16, 1, 1}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 0, 0, 0, 0, 1, 1}}}), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad_highest.cpp000066400000000000000000000035721510465702400230540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pad_highest : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector data0(4); std::iota(data0.begin(), data0.end(), 0); migraphx::shape s0{migraphx::shape::half_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); migraphx::op::pad op{}; op.value = std::numeric_limits::max(); op.pads = {0, 0, 1, 1}; mm->add_instruction(op, l0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad_highest_bf16.cpp000066400000000000000000000036041510465702400236660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pad_highest_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector data0(4); std::iota(data0.begin(), data0.end(), 0); migraphx::shape s0{migraphx::shape::bf16_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); migraphx::op::pad op{}; op.value = std::numeric_limits::max(); op.pads = {0, 0, 1, 1}; mm->add_instruction(op, l0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad_int8.cpp000066400000000000000000000035231510465702400222770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pad_int8 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector data0 = {0, 1, 2, 3}; migraphx::shape s0{migraphx::shape::float_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); migraphx::op::pad op{}; op.value = std::numeric_limits::lowest(); op.pads = {0, 0, 1, 1}; mm->add_instruction(op, l0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad_large.cpp000066400000000000000000000034141510465702400225060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pad_large : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {586, 3, 224, 224}}; std::vector pads0 = {0, 0, 1, 1, 0, 0, 1, 1}; auto l0 = mm->add_parameter("x", s0); mm->add_instruction(migraphx::make_op("pad", {{"pads", pads0}}), l0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad_lowest.cpp000066400000000000000000000035731510465702400227370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pad_lowest : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector data0(4); std::iota(data0.begin(), data0.end(), 0); migraphx::shape s0{migraphx::shape::half_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); migraphx::op::pad op{}; op.value = std::numeric_limits::lowest(); op.pads = {0, 0, 1, 1}; mm->add_instruction(op, l0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad_lowest_bf16.cpp000066400000000000000000000036051510465702400235510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pad_lowest_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); std::vector data0(4); std::iota(data0.begin(), data0.end(), 0); migraphx::shape s0{migraphx::shape::bf16_type, {2, 2}}; auto l0 = mm->add_literal(migraphx::literal{s0, data0}); migraphx::op::pad op{}; op.value = std::numeric_limits::lowest(); op.pads = {0, 0, 1, 1}; mm->add_instruction(op, l0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pad_transposed.cpp000066400000000000000000000035071510465702400236010ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pad_transposed : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {1, 224, 224, 3}}; auto x = mm->add_parameter("x", s); auto t = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), x); mm->add_instruction(migraphx::make_op("pad", {{"pads", {0, 0, 2, 2, 0, 0, 3, 3}}}), t); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pointwise_broadcast_reduce.cpp000066400000000000000000000051731510465702400261660ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_pointwise_broadcast_reduce : verify_program { migraphx::program create_program() const { migraphx::shape s{migraphx::shape::half_type, {2, 32, 96}}; migraphx::shape rs{migraphx::shape::half_type, {2, 1, 1}}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", rs); auto y = mm->add_parameter("y", s); auto abs = mm->add_instruction(migraphx::make_op("abs"), x); auto sqrt = mm->add_instruction(migraphx::make_op("sqrt"), abs); auto sqrtb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto add = mm->add_instruction(migraphx::make_op("add"), y, sqrtb); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1, 2}}}), add); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto sub = mm->add_instruction(migraphx::make_op("sub"), rsumb, add); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {s.elements()}}}), sub); mm->add_return({reshape}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pointwise_broadcast_reduce_bf16.cpp000066400000000000000000000052051510465702400270000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_pointwise_broadcast_reduce_bf16 : verify_program { migraphx::program create_program() const { migraphx::shape s{migraphx::shape::bf16_type, {2, 32, 96}}; migraphx::shape rs{migraphx::shape::bf16_type, {2, 1, 1}}; migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", rs); auto y = mm->add_parameter("y", s); auto abs = mm->add_instruction(migraphx::make_op("abs"), x); auto sqrt = mm->add_instruction(migraphx::make_op("sqrt"), abs); auto sqrtb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), sqrt); auto add = mm->add_instruction(migraphx::make_op("add"), y, sqrtb); auto rsum = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1, 2}}}), add); auto rsumb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), rsum); auto sub = mm->add_instruction(migraphx::make_op("sub"), rsumb, add); auto reshape = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {s.elements()}}}), sub); mm->add_return({reshape}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pointwise_conv_nhwc.cpp000066400000000000000000000055501510465702400246600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_pointwise_conv_nhwc : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {DType, {1, 8, 4, 4}}); auto w = mm->add_literal(migraphx::generate_literal({DType, {2, 8, 3, 3}}, 1)); auto v = mm->add_parameter("v", {DType, {2, 8, 3, 3}}); auto y = mm->add_parameter("y", {DType, {1, 8, 4, 4}}); auto mul = mm->add_instruction(migraphx::make_op("mul"), x, y); auto sigmoid = mm->add_instruction(migraphx::make_op("sigmoid"), mul); auto layout_ins = mm->add_instruction( migraphx::make_op("layout", {{"permutation", {0, 2, 3, 1}}}), sigmoid); auto add_ins = mm->add_instruction(migraphx::make_op("add"), w, v); auto layout_w = mm->add_instruction( migraphx::make_op("layout", {{"permutation", {0, 2, 3, 1}}}), add_ins); mm->add_instruction(migraphx::make_op("convolution"), layout_ins, layout_w); return p; } std::string section() const { return "conv"; } }; template struct test_pointwise_conv_nhwc; template struct test_pointwise_conv_nhwc; template struct test_pointwise_conv_nhwc; template struct test_pointwise_conv_nhwc; template struct test_pointwise_conv_nhwc; ROCm-AMDMIGraphX-46524e8/test/verify/test_pointwise_multi_out.cpp000066400000000000000000000050631510465702400247140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pointwise_multi_out : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto z1 = mm->add_parameter("z1", s); auto z2 = mm->add_parameter("z2", s); auto* pm = p.create_module("pointwise"); { auto x1 = pm->add_parameter("x1", {migraphx::shape::float_type}); auto x2 = pm->add_parameter("x2", {migraphx::shape::float_type}); auto add = pm->add_instruction(migraphx::make_op("add"), x1, x2); auto abs = pm->add_instruction(migraphx::make_op("abs"), add); auto sqrt = pm->add_instruction(migraphx::make_op("sqrt"), abs); pm->add_return({add, sqrt}); } pm->set_bypass(); auto pw = mm->add_instruction(migraphx::make_op("pointwise"), {z1, z2}, {pm}); auto e0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), pw); auto e1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), pw); auto sub = mm->add_instruction(migraphx::make_op("sub"), e0, e1); mm->add_return({sub}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pooling_add_concat_relu.cpp000066400000000000000000000051021510465702400254210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include static migraphx::program create_concat_fusion_program(bool post_pointwise) { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {1, 4, 8, 8}}; migraphx::shape s2{migraphx::shape::float_type, {1, 4, 16, 16}}; auto x = mm->add_parameter("x", s1); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s2); auto pooling = mm->add_instruction( migraphx::make_op("pooling", {{"lengths", {2, 2}}, {"stride", {2, 2}}}), z); auto add = mm->add_instruction(migraphx::make_op("add"), x, y); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), add, pooling); if(post_pointwise) { auto relu = mm->add_instruction(migraphx::make_op("relu"), concat); mm->add_return({relu}); } else { mm->add_return({concat}); } return p; } struct test_pooling_add_concat_relu : verify_program { migraphx::program create_program() const { return create_concat_fusion_program(true); } }; struct test_pooling_add_concat : verify_program { migraphx::program create_program() const { return create_concat_fusion_program(false); } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pooling_autopad.cpp000066400000000000000000000034311510465702400237530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_pooling_autopad : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1, 3, 63, 63}}; auto l0 = mm->add_parameter("x", s0); migraphx::op::pooling op{migraphx::op::pooling_mode::max}; op.lengths = {2, 2}; op.stride = {2, 2}; mm->add_instruction(op, l0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_pow.cpp000066400000000000000000000042631510465702400214000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_pow : verify_program> { migraphx::program create_program() const { migraphx::program p; migraphx::shape::type_t dtype = migraphx::shape::get_type(); auto* mm = p.get_main_module(); migraphx::shape s{dtype, {6}}; std::vector vec_e(s.elements(), 2.0f); auto b = mm->add_parameter("x", s); auto e = mm->add_literal(migraphx::literal(s, vec_e)); mm->add_instruction(migraphx::make_op("pow"), b, e); return p; } }; template struct test_pow; template struct test_pow; template struct test_pow; template struct test_pow; template struct test_pow; template struct test_pow; template struct test_pow; ROCm-AMDMIGraphX-46524e8/test/verify/test_prefix_scan_sum_2d.cpp000066400000000000000000000062171510465702400243460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_prefix_scan_sum_2d_small : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {1}}; auto x = mm->add_parameter("x", s); auto xb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {3, 3}}}), x); mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), xb); return p; } }; template struct test_prefix_scan_sum_2d_small; template struct test_prefix_scan_sum_2d_small; template struct test_prefix_scan_sum_2d_small; template struct test_prefix_scan_sum_2d_small; template struct test_prefix_scan_sum_2d_small; template struct test_prefix_scan_sum_2d_small; template struct test_prefix_scan_sum_2d_small; template struct test_prefix_scan_sum_2d_large : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {3, 1000}}; auto x = mm->add_parameter("x", s); mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}}), x); return p; } }; template struct test_prefix_scan_sum_2d_large; template struct test_prefix_scan_sum_2d_large; template struct test_prefix_scan_sum_2d_large; ROCm-AMDMIGraphX-46524e8/test/verify/test_prefix_scan_sum_exclusive.cpp000066400000000000000000000034641510465702400260510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_prefix_scan_sum_exclusive : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3, 3}}; auto x = mm->add_parameter("x", s); mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 2}, {"exclusive", true}, {"reverse", false}}), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_prefix_scan_sum_exclusive_reverse.cpp000066400000000000000000000035071510465702400276020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_prefix_scan_sum_exclusive_reverse : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3, 3}}; auto x = mm->add_parameter("x", s); mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 0}, {"exclusive", true}, {"reverse", true}}), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_prefix_scan_sum_reverse.cpp000066400000000000000000000034601510465702400255110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_prefix_scan_sum_reverse : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 3, 3}}; auto x = mm->add_parameter("x", s); mm->add_instruction( migraphx::make_op("prefix_scan_sum", {{"axis", 1}, {"exclusive", false}, {"reverse", true}}), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_prelu_brcst.cpp000066400000000000000000000033721510465702400231170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_prelu_brcst : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {6}}; auto x = mm->add_parameter("x", s); auto slp = mm->add_parameter("slp", s); auto r = mm->add_instruction(migraphx::make_op("prelu"), x, slp); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_conv.cpp000066400000000000000000000042751510465702400227530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_quant_conv : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{DType, {2, 3, 4, 4}}; auto pa = mm->add_parameter("a", a_shape); migraphx::shape c_shape{DType, {2, 3, 3, 3}}; auto pc = mm->add_parameter("c", c_shape); mm->add_instruction(migraphx::make_op("quant_convolution"), pa, pc); return p; } std::string section() const { return "conv"; } }; template struct test_quant_conv; template struct test_quant_conv; template struct test_quant_conv; template struct test_quant_conv; template struct test_quant_conv; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_conv_1.cpp000066400000000000000000000042301510465702400231620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_quant_conv_1 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{DType, {2, 3, 4, 4}}; auto pa = mm->add_parameter("a", a_shape); migraphx::shape c_shape{DType, {2, 3, 3, 3}}; auto pc = mm->add_parameter("c", c_shape); mm->add_instruction(migraphx::make_op("quant_convolution"), pa, pc); return p; } }; template struct test_quant_conv_1; template struct test_quant_conv_1; template struct test_quant_conv_1; template struct test_quant_conv_1; template struct test_quant_conv_1; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_conv_1d.cpp000066400000000000000000000043341510465702400233330ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_quant_conv_1d : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{DType, {2, 3, 4}}; auto pa = mm->add_parameter("a", a_shape); migraphx::shape c_shape{DType, {2, 3, 3}}; auto pc = mm->add_parameter("c", c_shape); mm->add_instruction( migraphx::make_op("quant_convolution", {{"padding", {0}}, {"stride", {1}}, {"dilation", {1}}}), pa, pc); return p; } std::string section() const { return "conv"; } }; template struct test_quant_conv_1d; // MLIR 1D convolution is not supported in MIGraphX yet. Enable this through MIOpen route later. // template struct test_quant_conv_1d; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_conv_2.cpp000066400000000000000000000042341510465702400231670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_quant_conv_2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{DType, {16, 16, 4, 4}}; auto pa = mm->add_parameter("a", a_shape); migraphx::shape c_shape{DType, {16, 16, 3, 3}}; auto pc = mm->add_parameter("c", c_shape); mm->add_instruction(migraphx::make_op("quant_convolution"), pa, pc); return p; } }; template struct test_quant_conv_2; template struct test_quant_conv_2; template struct test_quant_conv_2; template struct test_quant_conv_2; template struct test_quant_conv_2; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_conv_padding.cpp000066400000000000000000000045051510465702400244350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_quant_conv_padding : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{DType, {2, 3, 4, 4}}; auto pa = mm->add_parameter("a", a_shape); migraphx::shape c_shape{DType, {2, 3, 3, 3}}; auto pc = mm->add_parameter("c", c_shape); mm->add_instruction( migraphx::make_op("quant_convolution", {{"padding", {1, 1}}, {"stride", {1, 1}}}), pa, pc); return p; } std::string section() const { return "conv"; } }; template struct test_quant_conv_padding; template struct test_quant_conv_padding; template struct test_quant_conv_padding; template struct test_quant_conv_padding; template struct test_quant_conv_padding; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_conv_padding_stride.cpp000066400000000000000000000045661510465702400260160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_quant_conv_padding_stride : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape a_shape{DType, {2, 3, 4, 4}}; auto pa = mm->add_parameter("a", a_shape); migraphx::shape c_shape{DType, {2, 3, 3, 3}}; auto pc = mm->add_parameter("c", c_shape); mm->add_instruction( migraphx::make_op("quant_convolution", {{"padding", {1, 1}}, {"stride", {2, 2}}}), pa, pc); return p; } std::string section() const { return "conv"; } }; template struct test_quant_conv_padding_stride; template struct test_quant_conv_padding_stride; template struct test_quant_conv_padding_stride; template struct test_quant_conv_padding_stride; template struct test_quant_conv_padding_stride; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_dot_3args_1.cpp000066400000000000000000000050641510465702400241100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_quant_dot_3args_1 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto ctype = migraphx::shape::get_type(); auto dtype = migraphx::shape::get_type(); migraphx::shape m1_shape{dtype, {2, 8}}; migraphx::shape m2_shape{dtype, {8, 7}}; migraphx::shape m3_shape{ctype, {2, 7}}; auto l1 = mm->add_parameter("a", m1_shape); auto l2 = mm->add_parameter("b", m2_shape); auto l3 = mm->add_parameter("c", m3_shape); migraphx::add_apply_alpha_beta( *mm, {l1, l2, l3}, migraphx::make_op("quant_dot"), CType{1}, CType{1}); return p; } std::string section() const { return "gemm"; } }; template struct test_quant_dot_3args_1; template struct test_quant_dot_3args_1; template struct test_quant_dot_3args_1; template struct test_quant_dot_3args_1; template struct test_quant_dot_3args_1; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_dot_3args_2.cpp000066400000000000000000000052111510465702400241030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_quant_dot_3args_2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto ctype = migraphx::shape::get_type(); auto dtype = migraphx::shape::get_type(); migraphx::shape m1_shape{dtype, {8, 2}}; migraphx::shape m2_shape{dtype, {8, 7}}; migraphx::shape m3_shape{ctype, {2, 7}}; auto l1 = mm->add_parameter("a", m1_shape); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_parameter("b", m2_shape); auto l3 = mm->add_parameter("c", m3_shape); migraphx::add_apply_alpha_beta( *mm, {tl1, l2, l3}, migraphx::make_op("quant_dot"), CType{1}, CType{3}); return p; } std::string section() const { return "gemm"; } }; template struct test_quant_dot_3args_2; template struct test_quant_dot_3args_2; template struct test_quant_dot_3args_2; template struct test_quant_dot_3args_2; template struct test_quant_dot_3args_2; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_dot_3args_3.cpp000066400000000000000000000052101510465702400241030ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_quant_dot_3args_3 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto ctype = migraphx::shape::get_type(); auto dtype = migraphx::shape::get_type(); migraphx::shape m1_shape{dtype, {2, 8}}; migraphx::shape m2_shape{dtype, {7, 8}}; migraphx::shape m3_shape{ctype, {2, 7}}; auto l1 = mm->add_parameter("a", m1_shape); auto l2 = mm->add_parameter("b", m2_shape); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); auto l3 = mm->add_parameter("c", m3_shape); migraphx::add_apply_alpha_beta( *mm, {l1, tl2, l3}, migraphx::make_op("quant_dot"), CType{2}, CType{3}); return p; } std::string section() const { return "gemm"; } }; template struct test_quant_dot_3args_3; template struct test_quant_dot_3args_3; template struct test_quant_dot_3args_3; template struct test_quant_dot_3args_3; template struct test_quant_dot_3args_3; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_dot_3args_4.cpp000066400000000000000000000053751510465702400241200ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_quant_dot_3args_4 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto ctype = migraphx::shape::get_type(); auto dtype = migraphx::shape::get_type(); migraphx::shape m1_shape{dtype, {8, 2}}; migraphx::shape m2_shape{dtype, {7, 8}}; migraphx::shape m3_shape{ctype, {2, 7}}; auto l1 = mm->add_parameter("a", m1_shape); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_parameter("b", m2_shape); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); auto l3 = mm->add_parameter("c", m3_shape); migraphx::add_apply_alpha_beta( *mm, {tl1, tl2, l3}, migraphx::make_op("quant_dot"), CType{3}, CType{2}); return p; } std::string section() const { return "gemm"; } }; template struct test_quant_dot_3args_4; template struct test_quant_dot_3args_4; template struct test_quant_dot_3args_4; template struct test_quant_dot_3args_4; template struct test_quant_dot_3args_4; ROCm-AMDMIGraphX-46524e8/test/verify/test_quant_dot_3args_5.cpp000066400000000000000000000051051510465702400241100ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_quant_dot_3args_5 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::get_type(); migraphx::shape m1_shape{dtype, {6, 2}}; migraphx::shape m2_shape{dtype, {7, 6}}; auto l1 = mm->add_parameter("a", m1_shape); auto tl1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l1); auto l2 = mm->add_parameter("b", m2_shape); auto tl2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), l2); migraphx::add_apply_alpha_beta(*mm, {tl1, tl2}, migraphx::make_op("quant_dot"), CType{3}); return p; } std::string section() const { return "gemm"; } }; template struct test_quant_dot_3args_5; template struct test_quant_dot_3args_5; template struct test_quant_dot_3args_5; template struct test_quant_dot_3args_5; template struct test_quant_dot_3args_5; ROCm-AMDMIGraphX-46524e8/test/verify/test_quantizelinear.cpp000066400000000000000000000056221510465702400236260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_quantizelinear : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape ss{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape sz{migraphx::shape::int8_type, {2, 2, 2}}; auto input1 = mm->add_parameter("x", sx); auto input2 = mm->add_parameter("y_scale", ss); auto input3 = mm->add_parameter("y_zero_point", sz); auto r = mm->add_instruction(migraphx::make_op("quantizelinear"), input1, input2, input3); mm->add_return({r}); return p; }; }; struct test_quantizelinear_convert : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape ss{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape sz{migraphx::shape::fp8e4m3fnuz_type, {2, 2, 2}}; auto input1 = mm->add_parameter("x", sx); auto input2 = mm->add_parameter("y_scale", ss); auto input3 = mm->add_parameter("y_zero_point", sz); auto r = mm->add_instruction(migraphx::make_op("quantizelinear"), input1, input2, input3); auto rf = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), r); mm->add_return({rf}); return p; }; std::size_t get_tolerance() const { return 100000; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_quantizelinear_int32.cpp000066400000000000000000000042631510465702400246450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_quantizelinear_int32 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sx{migraphx::shape::int32_type, {2, 2, 2}}; migraphx::shape ss{migraphx::shape::float_type, {2, 2, 2}}; migraphx::shape sz{migraphx::shape::int8_type, {2, 2, 2}}; auto input1 = mm->add_parameter("x", sx); auto input2 = mm->add_parameter("y_scale", ss); auto input3 = mm->add_parameter("y_zero_point", sz); auto input1_float = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), input1); auto r = mm->add_instruction(migraphx::make_op("quantizelinear"), input1_float, input2, input3); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_recip.cpp000066400000000000000000000032161510465702400216720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_recip : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("recip"), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_add.cpp000066400000000000000000000043421510465702400226500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_reduce_add : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {4, 1000, 2, 2}}; migraphx::shape bs{migraphx::shape::half_type, {1, 32, 128}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto reduce_max = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2, 3}}}), x); auto add = mm->add_instruction(migraphx::make_op("add"), reduce_mean, reduce_max); mm->add_return({add}); return p; }; std::string section() const { return "reduce"; } }; template struct test_reduce_add; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_add_bf16.cpp000066400000000000000000000043611510465702400234670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_reduce_add_bf16 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {4, 1000, 2, 2}}; migraphx::shape bs{migraphx::shape::bf16_type, {1, 32, 128}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto reduce_max = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2, 3}}}), x); auto add = mm->add_instruction(migraphx::make_op("add"), reduce_mean, reduce_max); mm->add_return({add}); return p; }; std::string section() const { return "reduce"; } }; template struct test_reduce_add_bf16; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mean_bias_bf16.cpp000066400000000000000000000043171510465702400246560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_reduce_mean_bias_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {1, 32, 128}}; migraphx::shape bs{migraphx::shape::bf16_type, {1, 32, 128}}; auto x = mm->add_parameter("x", s); auto reduce = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto bias = mm->add_parameter("bias", reduce->get_shape()); auto add = mm->add_instruction(migraphx::make_op("add"), reduce, bias); auto abs = mm->add_instruction(migraphx::make_op("abs"), add); auto sqrt = mm->add_instruction(migraphx::make_op("sqrt"), abs); mm->add_return({sqrt}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mean_bias_half.cpp000066400000000000000000000043171510465702400250320ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_reduce_mean_bias_half : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {1, 32, 128}}; migraphx::shape bs{migraphx::shape::half_type, {1, 32, 128}}; auto x = mm->add_parameter("x", s); auto reduce = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto bias = mm->add_parameter("bias", reduce->get_shape()); auto add = mm->add_instruction(migraphx::make_op("add"), reduce, bias); auto abs = mm->add_instruction(migraphx::make_op("abs"), add); auto sqrt = mm->add_instruction(migraphx::make_op("sqrt"), abs); mm->add_return({sqrt}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mean_large_bf16.cpp000066400000000000000000000034101510465702400250230ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_reduce_mean_large_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {1, 32, 65536}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mean_large_half.cpp000066400000000000000000000034101510465702400251770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_reduce_mean_large_half : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {1, 32, 65536}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mean_nhwc.cpp000066400000000000000000000044001510465702400240520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_reduce_mean_nhwc : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto s = migraphx::shape::from_permutation(DType, {4, 256, 2, 2}, {0, 2, 3, 1}); auto x = mm->add_parameter("x", s); auto reduce = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {1}}}), x); auto abs = mm->add_instruction(migraphx::make_op("abs"), reduce); auto sqrt = mm->add_instruction(migraphx::make_op("sqrt"), abs); mm->add_return({sqrt}); return p; }; std::string section() const { return "reduce"; } }; template struct test_reduce_mean_nhwc; template struct test_reduce_mean_nhwc; template struct test_reduce_mean_nhwc; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mean_reduce_sum.cpp000066400000000000000000000050671510465702400252600ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_reduce_mean_reduce_sum : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {5, 784, 768}}; auto x = mm->add_parameter("x", s); auto n = mm->add_literal(migraphx::literal{ migraphx::shape{migraphx::shape::float_type, {1}}, {s.lens().back()}}); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto meanb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), mean); auto sub = mm->add_instruction(migraphx::make_op("sub"), x, meanb); auto nb = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", s.lens()}}), n); auto div = mm->add_instruction(migraphx::make_op("div"), sub, nb); auto div2 = mm->add_instruction(migraphx::make_op("mul"), div, div); auto mean_div2 = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), div2); mm->add_return({mean_div2}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mean_variance.cpp000066400000000000000000000044171510465702400247130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_reduce_mean_variance : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {1, 32, 128}}; auto x = mm->add_parameter("x", s); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto x2 = mm->add_instruction(migraphx::make_op("mul"), x, x); auto mean_x2 = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x2); auto mean_2 = mm->add_instruction(migraphx::make_op("mul"), mean, mean); auto variance = mm->add_instruction(migraphx::make_op("sub"), mean_x2, mean_2); auto add = mm->add_instruction(migraphx::make_op("add"), mean, variance); mm->add_return({add}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mean_variance_bf16.cpp000066400000000000000000000044311510465702400255250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_reduce_mean_variance_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {1, 32, 128}}; auto x = mm->add_parameter("x", s); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x); auto x2 = mm->add_instruction(migraphx::make_op("mul"), x, x); auto mean_x2 = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x2); auto mean_2 = mm->add_instruction(migraphx::make_op("mul"), mean, mean); auto variance = mm->add_instruction(migraphx::make_op("sub"), mean_x2, mean_2); auto add = mm->add_instruction(migraphx::make_op("add"), mean, variance); mm->add_return({add}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_mixed_types.cpp000066400000000000000000000054321510465702400244530ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include // Test to check for reduction(s), of an operator of two non-similar types, won't be fused in // fuse_reduce pass. 'reduce_mean' here contains 'reduce_add' of types 'half' and 'float'. struct test_reduce_mixed_type : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {1, 32, 128}}; migraphx::shape s2{migraphx::shape::float_type, {1, 32, 128}}; auto x1 = mm->add_parameter("x1", s); auto x2 = mm->add_parameter("x2", s2); auto mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), x1); auto sq = mm->add_instruction(migraphx::make_op("mul"), x2, x2); auto sq_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2}}}), sq); auto sq_mean_c = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::to_value(migraphx::shape::half_type)}}), sq_mean); auto mean_sq = mm->add_instruction(migraphx::make_op("mul"), mean, mean); auto variance = mm->add_instruction(migraphx::make_op("sub"), sq_mean_c, mean_sq); auto add = mm->add_instruction(migraphx::make_op("add"), mean, variance); mm->add_return({add}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_noop_add.cpp000066400000000000000000000041651510465702400237060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_reduce_noop_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 1000, 1, 1}}; migraphx::shape bs{migraphx::shape::half_type, {1, 32, 128}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto reduce_max = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2, 3}}}), x); auto add = mm->add_instruction(migraphx::make_op("add"), reduce_mean, reduce_max); mm->add_return({add}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_noop_add_bf16.cpp000066400000000000000000000041771510465702400245270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_reduce_noop_add_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 1000, 1, 1}}; migraphx::shape bs{migraphx::shape::bf16_type, {1, 32, 128}}; auto x = mm->add_parameter("x", s); auto reduce_mean = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {2, 3}}}), x); auto reduce_max = mm->add_instruction(migraphx::make_op("reduce_max", {{"axes", {2, 3}}}), x); auto add = mm->add_instruction(migraphx::make_op("add"), reduce_mean, reduce_max); mm->add_return({add}); return p; }; std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_op_large.cpp000066400000000000000000000116641510465702400237150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include #include #include #include #include template struct test_reduce_op_large : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{T, {3, 1026, 4, 3}}; auto x = mm->add_parameter("x", s); mm->add_instruction(Op{{Axis}}, x); return p; }; std::string section() const { return "reduce"; } }; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; template struct test_reduce_op_large; struct test_reduce_mean_1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 384, 1024}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::op::reduce_mean{{1}}, x); return p; }; }; struct test_reduce_mean_2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {336, 400}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::op::reduce_mean{{1}}, x); return p; }; }; struct test_large_reduce_mean1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 256 * 256 * 16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::op::reduce_mean{{1}}, x); return p; }; }; struct test_large_reduce_mean2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 32, 262144}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::op::reduce_mean{{2}}, x); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reduce_op_small.cpp000066400000000000000000000213171510465702400237270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include #include #include #include #include template struct test_reduce_op_small : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{T, {3, 4, 2, 2}}; auto x = mm->add_parameter("x", s); mm->add_instruction(Op{{Axis}}, x); return p; }; std::string section() const { return "reduce"; } }; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; template struct test_reduce_op_small; ROCm-AMDMIGraphX-46524e8/test/verify/test_relu_lrn.cpp000066400000000000000000000035121510465702400224110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_relu_lrn : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 5, 2, 2}}); auto y = mm->add_instruction(migraphx::make_op("relu"), x); mm->add_instruction( migraphx::make_op("lrn", {{"alpha", 0.0001}, {"beta", 0.75}, {"bias", 1.0}, {"size", 5}}), y); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reshape_transpose_reshape_broadcast_sub.cpp000066400000000000000000000046621510465702400307250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_reshape_transpose_reshape_broadcast_sub : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x2 = mm->add_parameter("x2", migraphx::shape{migraphx::shape::float_type, {1, 1, 32, 1}}); auto x1 = mm->add_parameter("x1", migraphx::shape{migraphx::shape::float_type, {1, 512, 16, 16}}); auto reshape1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {1, 32, 16, 16, 16}}}), x1); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 4, 1, 2}}}), reshape1); auto reshape2 = mm->add_instruction( migraphx::make_op("reshape", {{"dims", {1, 256, 32, 16}}}), transpose); auto broadcast = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 256, 32, 16}}}), x2); auto sub = mm->add_instruction(migraphx::make_op("sub"), reshape2, broadcast); mm->add_return({sub}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_resize_dyn.cpp000066400000000000000000000067641510465702400227560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_resize_dyn : verify_program { migraphx::program create_program() const { // matcher/optimized code should produce the same result as Resize op. migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 2}, {1, 1}, {3, 3}, {3, 3}}}; auto a0 = mm->add_parameter("a0", s); migraphx::shape size_input{migraphx::shape::int32_type, {4}}; std::vector size_values = {1, 1, 5, 8}; auto a1 = mm->add_literal(migraphx::literal{size_input, size_values}); // a0 = input data // a1 = sizes of output // non-matching sizes/scales attributes are ignored for 2 input arguments auto resize_ins = mm->add_instruction( migraphx::make_op( "resize", {{"nearest_mode", "floor"}, {"coordinate_transformation_mode", "half_pixel"}}), a0, a1); mm->add_return({resize_ins}); return p; }; }; struct test_resize_dyn_scale : verify_program { // A Resize op. taking input scales instead of sizes migraphx::program create_program() const { // matcher/optimized code should produce the same result as Resize op. migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 2}, {1, 1}, {3, 3}, {3, 3}}}; auto a0 = mm->add_parameter("a0", s); migraphx::shape scale_input{migraphx::shape::float_type, {4}}; std::vector scale_values = {1., 1., 5.00 / 3, 8.00 / 3}; auto a1 = mm->add_literal(migraphx::literal{scale_input, scale_values}); // a0 = input data // a1 = scales of output // non-matching sizes/scales attributes are ignored for 2 input arguments auto resize_ins = mm->add_instruction( migraphx::make_op( "resize", {{"nearest_mode", "floor"}, {"coordinate_transformation_mode", "half_pixel"}}), a0, a1); mm->add_return({resize_ins}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reverse.cpp000066400000000000000000000042611510465702400222440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_reverse : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {4, 16}}; auto a0 = mm->add_parameter("data", s); std::vector axis = {0}; mm->add_instruction(migraphx::make_op("reverse", {{"axes", axis}}), a0); return p; } }; template struct test_reverse; template struct test_reverse; template struct test_reverse; template struct test_reverse; template struct test_reverse; template struct test_reverse; template struct test_reverse; ROCm-AMDMIGraphX-46524e8/test/verify/test_reverse_multiaxis.cpp000066400000000000000000000034001510465702400243350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_reverse_multiaxis : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 16}}; auto a0 = mm->add_parameter("data", s); std::vector axes = {0, 1}; mm->add_instruction(migraphx::make_op("reverse", {{"axes", axes}}), a0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_reverse_negaxis.cpp000066400000000000000000000033721510465702400237640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_reverse_negaxis : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 16}}; auto a0 = mm->add_parameter("data", s); std::vector axis = {-1}; mm->add_instruction(migraphx::make_op("reverse", {{"axes", axis}}), a0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_3args.cpp000066400000000000000000000053121510465702400224630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_3args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_4args.cpp000066400000000000000000000055631510465702400224740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_4args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 5; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_4args_layout.cpp000066400000000000000000000062521510465702400240650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_4args_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 5; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias); std::vector perm_hid{2, 0, 1, 3}; mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_5args.cpp000066400000000000000000000060521510465702400224670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_5args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 10; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto output = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_bi_3args.cpp000066400000000000000000000061351510465702400231410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_bi_3args : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 10; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto output = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_bi_3args_layout.cpp000066400000000000000000000063361510465702400245410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_bi_3args_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 10; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); auto output = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); last_output = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_bidirectional.cpp000066400000000000000000000065141510465702400242610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_bidirectional : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto output = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_bidirectional10.cpp000066400000000000000000000065341510465702400244240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_bidirectional10 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 10; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto output = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_bidirectional_layout.cpp000066400000000000000000000073661510465702400256640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_bidirectional_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto output = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto last_output = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), output); last_output = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_output); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_forward.cpp000066400000000000000000000063771510465702400231240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_forward : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, lho}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_forward10.cpp000066400000000000000000000064041510465702400232540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_forward10 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 10; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, lho}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_forward_layout.cpp000066400000000000000000000073631510465702400245150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_forward_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, und, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); lho = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), lho); mm->add_return({hs, lho}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_reverse.cpp000066400000000000000000000061751510465702400231270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_reverse : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_reverse2.cpp000066400000000000000000000061771510465702400232130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_reverse2 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 2; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_reverse_layout.cpp000066400000000000000000000070131510465702400245140ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_reverse : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 1; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); auto und = mm->add_instruction(migraphx::make_op("undefined")); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::reverse)}, {"clip", clip}}), seq, w, r, bias, und, ih); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_sql_1.cpp000066400000000000000000000073651510465702400224750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_rnn_sql_1 : verify_program> { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 10; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{DType, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{DType, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{DType, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{DType, {num_dirct, 2 * hidden_size}}; migraphx::shape s_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::shape ih_shape{DType, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); std::vector sl_data{5, 7}; auto sql = mm->add_literal(migraphx::literal{s_shape, sl_data}); auto ih = mm->add_parameter("ih", ih_shape); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto last_hs = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, last_hs}); return p; } std::string section() const { return "rnn"; } }; template struct test_rnn_sql_1; template struct test_rnn_sql_1; template struct test_rnn_sql_1; template struct test_rnn_sql_1; template struct test_rnn_sql_1; template struct test_rnn_sql_1; template struct test_rnn_sql_1; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_sql_1_layout.cpp000066400000000000000000000075761510465702400240760ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_sql_1_layout : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 10; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {batch_size, seq_len, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape s_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {batch_size, num_dirct, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); std::vector sl_data{5, 7}; auto sql = mm->add_literal(migraphx::literal{s_shape, sl_data}); auto ih = mm->add_parameter("ih", ih_shape); std::vector perm{1, 0, 2}; seq = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), seq); ih = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), ih); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto last_hs = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); std::vector perm_hid{2, 0, 1, 3}; hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm_hid}}), hs); last_hs = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), last_hs); mm->add_return({hs, last_hs}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rnn_sql_2.cpp000066400000000000000000000073751510465702400224770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_rnn_sql_2 : verify_program { migraphx::program create_program() const { std::size_t batch_size = 2; std::size_t seq_len = 10; std::size_t hidden_size = 4; std::size_t input_size = 3; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 2 * hidden_size}}; migraphx::shape s_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq_orig = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); migraphx::shape pad_s{migraphx::shape::float_type, {2, batch_size, input_size}}; std::vector pad_data(pad_s.elements(), 0.0f); auto seq_pad = mm->add_literal(migraphx::literal{pad_s, pad_data}); auto seq = mm->add_instruction(migraphx::make_op("concat", {{"axis", 0}}), seq_orig, seq_pad); std::vector sl_data(batch_size, static_cast(seq_len)); auto sql = mm->add_literal(migraphx::literal{s_shape, sl_data}); auto ih = mm->add_parameter("ih", ih_shape); auto hs = mm->add_instruction( migraphx::make_op( "rnn", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto last_hs = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, last_hs}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_roialign.cpp000066400000000000000000000051771510465702400224040ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_roialign : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_s{DType, {5, 4, 10, 10}}; migraphx::shape roi_s{DType, {5, 4}}; migraphx::shape ind_s{migraphx::shape::int64_type, {5}}; std::vector ind_vec = {0, 2, 3, 4, 1}; auto x = mm->add_parameter("x", x_s); auto roi = mm->add_parameter("roi", roi_s); auto ind = mm->add_literal(migraphx::literal(ind_s, ind_vec)); auto r = mm->add_instruction(migraphx::make_op("roialign", {{"spatial_scale", 1.0}, {"output_height", 5}, {"output_width", 5}, {"sampling_ratio", 2}}), x, roi, ind); mm->add_return({r}); return p; } }; template struct test_roialign; template struct test_roialign; template struct test_roialign; ROCm-AMDMIGraphX-46524e8/test/verify/test_roialign_nondefault.cpp000066400000000000000000000047701510465702400246210ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_roialign_nondefault : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape x_s{migraphx::shape::float_type, {5, 4, 10, 10}}; migraphx::shape roi_s{migraphx::shape::float_type, {5, 4}}; migraphx::shape ind_s{migraphx::shape::int64_type, {5}}; std::vector ind_vec = {0, 2, 3, 4, 1}; auto x = mm->add_parameter("x", x_s); auto roi = mm->add_parameter("roi", roi_s); auto ind = mm->add_literal(migraphx::literal(ind_s, ind_vec)); auto r = mm->add_instruction( migraphx::make_op("roialign", {{"coordinate_transformation_mode", "output_half_pixel"}, {"mode", migraphx::op::pooling_mode::max}, {"spatial_scale", 1.0}, {"output_height", 5}, {"output_width", 5}, {"sampling_ratio", 2}}), x, roi, ind); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_roialign_nonstandard.cpp000066400000000000000000000050141510465702400247650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_roialign_nonstandard : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x_s = migraphx::shape::from_permutation( migraphx::shape::float_type, {5, 4, 10, 10}, {0, 2, 3, 1}); migraphx::shape roi_s{migraphx::shape::float_type, {5, 4}}; migraphx::shape ind_s{migraphx::shape::int64_type, {5}}; std::vector ind_vec = {0, 2, 3, 4, 1}; auto x = mm->add_parameter("x", x_s); auto roi = mm->add_parameter("roi", roi_s); auto ind = mm->add_literal(migraphx::literal(ind_s, ind_vec)); auto r = mm->add_instruction(migraphx::make_op("roialign", {{"spatial_scale", 1.0}, {"output_height", 5}, {"output_width", 5}, {"sampling_ratio", 2}}), x, roi, ind); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_rsqrt.cpp000066400000000000000000000053461510465702400217510ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_rsqrt : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape::type_t dtype = migraphx::shape::get_type(); std::vector input_lens{1, 3, 16, 16}; migraphx::shape s{dtype, input_lens}; auto x = mm->add_parameter("x", s); auto min_val = mm->add_literal(migraphx::literal{migraphx::shape{dtype}, {1.0}}); auto max_val = mm->add_literal( migraphx::literal{migraphx::shape{dtype}, {std::numeric_limits::max()}}); min_val = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), min_val); max_val = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", input_lens}}), max_val); auto l0 = mm->add_instruction(migraphx::make_op("clip"), x, min_val, max_val); mm->add_instruction(migraphx::make_op("rsqrt"), l0); return p; }; }; template struct test_rsqrt; template struct test_rsqrt; template struct test_rsqrt; template struct test_rsqrt; template struct test_rsqrt; template struct test_rsqrt; template struct test_rsqrt; ROCm-AMDMIGraphX-46524e8/test/verify/test_scale.cpp000066400000000000000000000035211510465702400216560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scale : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", migraphx::shape::float_type); auto scale = mm->add_instruction(migraphx::make_op("scalar", {{"scalar_bcst_dims", s.lens()}}), y); mm->add_instruction(migraphx::make_op("mul"), x, scale); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scan_slice.cpp000066400000000000000000000043421510465702400226740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_scan_slice_base : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape data_sh{migraphx::shape::int32_type, {2, 2, 2}}; auto data_param = mm->add_parameter("data", data_sh); migraphx::shape idx_sh{migraphx::shape::int64_type, {1}}; auto idx_lit = mm->add_literal(migraphx::literal{idx_sh, {Idx}}); mm->add_instruction( migraphx::make_op("scan_slice", {{"axis", Axis}, {"direction", Direction}}), data_param, idx_lit); return p; } }; struct test_scan_slice1 : test_scan_slice_base { }; struct test_scan_slice2 : test_scan_slice_base { }; struct test_scan_slice3 : test_scan_slice_base { }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatter_elements_duplicate_index.cpp000066400000000000000000000057611510465702400273610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_scatter_elements_duplicate_index_base : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {3, 3}}; migraphx::shape si{migraphx::shape::int32_type, {3, 2}}; std::vector vi = {1, 0, 1, 0, 1, 0}; migraphx::shape su{migraphx::shape::float_type, {3, 2}}; auto pd = mm->add_parameter("data", sd); auto li = mm->add_literal(migraphx::literal{si, vi}); auto pu = mm->add_parameter("update", su); const auto reduction = static_cast(*this).reduction(); auto r = mm->add_instruction( migraphx::make_op("scatter_" + reduction, {{"axis", 0}}), pd, li, pu); mm->add_return({r}); return p; } }; struct test_scatter_elements_add_duplicate_index : test_scatter_elements_duplicate_index_base { std::string reduction() const { return "add"; } }; struct test_scatter_elements_mul_duplicate_index : test_scatter_elements_duplicate_index_base { std::string reduction() const { return "mul"; } }; struct test_scatter_elements_min_duplicate_index : test_scatter_elements_duplicate_index_base { std::string reduction() const { return "min"; } }; struct test_scatter_elements_max_duplicate_index : test_scatter_elements_duplicate_index_base { std::string reduction() const { return "max"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatter_elements_none_axis_neg_1.cpp000066400000000000000000000052371510465702400272520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_scatter_elements_none_axis_neg_1 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{DType, {3, 3}}; migraphx::shape si{migraphx::shape::int32_type, {2, 3}}; std::vector vi = {1, 0, 2, 0, 2, 1}; migraphx::shape su{DType, {2, 3}}; auto pd = mm->add_parameter("data", sd); auto li = mm->add_literal(migraphx::literal{si, vi}); auto pu = mm->add_parameter("update", su); auto r = mm->add_instruction(migraphx::make_op("scatter_none", {{"axis", -1}}), pd, li, pu); mm->add_return({r}); return p; } }; template struct test_scatter_elements_none_axis_neg_1; template struct test_scatter_elements_none_axis_neg_1; template struct test_scatter_elements_none_axis_neg_1; template struct test_scatter_elements_none_axis_neg_1; template struct test_scatter_elements_none_axis_neg_1; template struct test_scatter_elements_none_axis_neg_1; template struct test_scatter_elements_none_axis_neg_1; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatter_elements_none_axis_neg_2.cpp000066400000000000000000000040751510465702400272520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatter_elements_none_axis_neg_2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {3, 3}}; migraphx::shape si{migraphx::shape::int32_type, {2, 3}}; std::vector vi = {-2, 0, 2, 0, -1, 1}; migraphx::shape su{migraphx::shape::float_type, {2, 3}}; auto pd = mm->add_parameter("data", sd); auto li = mm->add_literal(migraphx::literal{si, vi}); auto pu = mm->add_parameter("update", su); auto r = mm->add_instruction(migraphx::make_op("scatter_none", {{"axis", -2}}), pd, li, pu); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatter_nonstandard_shape.cpp000066400000000000000000000041261510465702400260110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatter_nonstandard_shape : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {3, 1, 3}, {1, 3, 2}}; migraphx::shape si{migraphx::shape::int32_type, {2, 1, 3}, {1, 3, 2}}; std::vector vi = {1, 0, 2, 0, 2, 1}; migraphx::shape su{migraphx::shape::float_type, {2, 1, 3}, {1, 2, 3}}; auto pd = mm->add_parameter("data", sd); auto li = mm->add_literal(migraphx::literal{si, vi}); auto pu = mm->add_parameter("update", su); auto r = mm->add_instruction(migraphx::make_op("scatter_none", {{"axis", -1}}), pd, li, pu); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatter_skip_out_of_bounds.cpp000066400000000000000000000042521510465702400262110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatter_skip_out_of_bounds : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sd{migraphx::shape::float_type, {3, 1, 3}, {1, 3, 2}}; migraphx::shape si{migraphx::shape::int32_type, {2, 1, 3}, {1, 3, 2}}; std::vector vi = {-1, 0, 20, 0, -8, 1}; migraphx::shape su{migraphx::shape::float_type, {2, 1, 3}, {1, 2, 3}}; auto pd = mm->add_parameter("data", sd); auto li = mm->add_literal(migraphx::literal{si, vi}); auto pu = mm->add_parameter("update", su); auto r = mm->add_instruction( migraphx::make_op("scatter_none", {{"axis", -1}, {"skip_out_of_bounds", true}}), pd, li, pu); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatternd.cpp000066400000000000000000000047501510465702400225630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_scatternd : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto itype = migraphx::shape::int64_type; migraphx::shape ds{DType, {1}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{DType, {4}}; std::vector ind_vec{4, 3, 1, 7}; auto ld = mm->add_literal(migraphx::literal{ds, {1}}); auto data = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {8}}}), ld); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_parameter("update", us); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_none"), data, indices, updates); mm->add_return({scatternd}); return p; } }; template struct test_scatternd; template struct test_scatternd; template struct test_scatternd; template struct test_scatternd; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatternd_add.cpp000066400000000000000000000043261510465702400233720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatternd_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {1, 4}}; migraphx::shape us{dtype, {4}}; std::vector ind_vec{4, 3, 1, 7}; auto data = mm->add_parameter("data", ds); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto t_ind = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), indices); auto updates = mm->add_parameter("update", us); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_add"), data, t_ind, updates); mm->add_return({scatternd}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatternd_max.cpp000066400000000000000000000041361510465702400234260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatternd_max : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector ind_vec{4, 3, 1, 7}; auto data = mm->add_parameter("data", ds); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_parameter("update", us); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_max"), data, indices, updates); mm->add_return({scatternd}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatternd_max_duplicate_idx.cpp000066400000000000000000000041721510465702400263240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatternd_max_duplicate_idx : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector ind_vec{4, 7, 4, 7}; auto data = mm->add_parameter("data", ds); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_parameter("update", us); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_max"), data, indices, updates); mm->add_return({scatternd}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatternd_min.cpp000066400000000000000000000041361510465702400234240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatternd_min : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector ind_vec{4, 3, 1, 7}; auto data = mm->add_parameter("data", ds); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_parameter("update", us); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_min"), data, indices, updates); mm->add_return({scatternd}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatternd_min_duplicate_idx.cpp000066400000000000000000000041721510465702400263220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatternd_min_duplicate_idx : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector ind_vec{4, 7, 4, 7}; auto data = mm->add_parameter("data", ds); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_parameter("update", us); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_min"), data, indices, updates); mm->add_return({scatternd}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_scatternd_mul.cpp000066400000000000000000000041361510465702400234360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_scatternd_mul : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto dtype = migraphx::shape::float_type; auto itype = migraphx::shape::int64_type; migraphx::shape ds{dtype, {8}}; migraphx::shape is{itype, {4, 1}}; migraphx::shape us{dtype, {4}}; std::vector ind_vec{4, 3, 1, 7}; auto data = mm->add_parameter("data", ds); auto indices = mm->add_literal(migraphx::literal{is, ind_vec}); auto updates = mm->add_parameter("update", us); auto scatternd = mm->add_instruction(migraphx::make_op("scatternd_mul"), data, indices, updates); mm->add_return({scatternd}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_select_module_add.cpp000066400000000000000000000071711510465702400242300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_select_module_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm->add_literal(migraphx::literal{lit_s, {6}}); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); auto broadcast_lit = submod->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, sm_input); auto add_ins0 = submod->add_instruction(migraphx::make_op("add"), sm_input, broadcast_lit); auto add_ins1 = submod->add_instruction(migraphx::make_op("add"), add_ins0, broadcast_lit); submod->add_return({add_ins0, add_ins1}); return submod; }; auto* batch1 = create_submodule(1, "batch_1"); auto* batch2 = create_submodule(2, "batch_2"); auto* batch3 = create_submodule(3, "batch_3"); auto* batch4 = create_submodule(4, "batch_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input = mm->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input}, {batch1, batch2, batch3, batch4}); auto ret0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); auto ret1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), sm_ins); mm->add_return({ret0, ret1}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_select_module_conv.cpp000066400000000000000000000065671510465702400244550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_select_module_conv : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 3, 4, 4}}; auto sm_input = submod->add_parameter("data", sm_shape); migraphx::shape weights_shape{migraphx::shape::float_type, {2, 3, 3, 3}}; std::vector weights_data(2 * 3 * 3 * 3, 2.0); auto weights = submod->add_literal(migraphx::literal{weights_shape, weights_data}); auto conv_ins = submod->add_instruction( migraphx::make_op("convolution", {{"padding", {1, 1}}}), sm_input, weights); submod->add_return({conv_ins}); return submod; }; auto* batch1 = create_submodule(1, "batch_1"); auto* batch2 = create_submodule(2, "batch_2"); auto* batch3 = create_submodule(3, "batch_3"); auto* batch4 = create_submodule(4, "batch_4"); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {3, 3}, {4, 4}, {4, 4}}}; auto input = mm->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back( migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}, {4, 4}, {4, 4}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input}, {batch1, batch2, batch3, batch4}); auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm->add_return({ret}); return p; } std::string section() const { return "conv"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_select_module_reduce.cpp000066400000000000000000000063101510465702400247410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_select_module_reduce : verify_program { migraphx::program create_program() const { migraphx::program p; // create batch submodules auto create_submodule = [&](std::size_t batch_size, const std::string& module_name) { auto* submod = p.create_module(module_name); migraphx::shape sm_shape{migraphx::shape::float_type, {batch_size, 2, 2}}; auto sm_input = submod->add_parameter("data", sm_shape); auto reduce_ins = submod->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sm_input); auto squeeze_ins = submod->add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), reduce_ins); submod->add_return({squeeze_ins}); return submod; }; auto* batch1 = create_submodule(1, "batch_1"); auto* batch2 = create_submodule(2, "batch_2"); auto* batch3 = create_submodule(3, "batch_3"); auto* batch4 = create_submodule(4, "batch_4"); auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {2, 2}, {2, 2}}}; auto input = mm->add_parameter("data", s); std::vector sub_shapes = {}; sub_shapes.push_back(migraphx::shape{migraphx::shape::float_type, {{1, 4}, {2, 2}}}); migraphx::shape out_attr = migraphx::shape{sub_shapes}; auto sm_ins = mm->add_instruction( migraphx::make_op("select_module", {{"output_dyn_shapes", migraphx::to_value(out_attr)}}), {input}, {batch1, batch2, batch3, batch4}); auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), sm_ins); mm->add_return({ret}); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_shape_alloc.cpp000066400000000000000000000056721510465702400230520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include /** * @brief test_shape_alloc sets up a situation that could lead to an exception "convolution: Shapes * are not in standard layout" if a "replace_allocate" compiler pass is not followed with * "adjust_allocation". The last transpose instruction generates a shape with a stride of 1 in * the 2nd index, a non-standard layout that should be reallocated by adjust_allocation. */ struct test_shape_alloc : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto weights = mm->add_literal(migraphx::generate_literal( migraphx::shape{migraphx::shape::float_type, {11, 8, 1, 1}, {8, 1, 1, 1}})); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 8, 7, 7}}); auto transpose1 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), x); // -> float_type, {1, 7, 7, 8}, {392, 7, 1, 49} auto reduce_ins = mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {1, 2}}}), transpose1); // -> float_type, {1, 1, 1, 8}, {8, 8, 8, 1} auto transpose2 = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 3, 1, 2}}}), reduce_ins); // -> float_type, {1, 8, 1, 1}, {8, 1, 8, 8} auto conv_op = migraphx::make_op("convolution"); mm->add_instruction(conv_op, transpose2, weights); return p; } std::string section() const { return "conv"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_shrink.cpp000066400000000000000000000101771510465702400220720ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include #include template struct test_shrink : verify_program> { migraphx::program create_program() const { migraphx::program p; float bias = 1.5; float lambd = 1.5; auto* mm = p.get_main_module(); migraphx::shape is{T, {2, 3}}; std::vector data; migraphx::shape::visit(T, [&](auto as) { as.is_signed() ? data.assign({-3.0, -2.0, -1.0, 0.0, 1.0, 2.0}) : data.assign({3.0, 2.0, 1.0, 0.0, 1.0, 2.0}); }); auto x = mm->add_literal(migraphx::literal{is, data}); auto lit_bias = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {bias}}); auto lit_neg_lambd = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {-lambd}}); auto lit_lambd = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {lambd}}); auto x_plus_bias = add_common_op(*mm, migraphx::make_op("add"), {x, lit_bias}); auto x_min_bias = add_common_op(*mm, migraphx::make_op("sub"), {x, lit_bias}); auto cond1 = add_common_op(*mm, migraphx::make_op("less"), {x, lit_neg_lambd}); auto cond2_a = add_common_op(*mm, migraphx::make_op("not"), {cond1}); auto cond2_b = add_common_op(*mm, migraphx::make_op("greater"), {x, lit_lambd}); auto cond2 = add_common_op(*mm, migraphx::make_op("logical_and"), {cond2_a, cond2_b}); auto mul1 = mm->add_instruction(migraphx::make_op("convert", {{"target_type", T}}), cond1); auto mul2 = mm->add_instruction(migraphx::make_op("convert", {{"target_type", T}}), cond2); auto first = add_common_op(*mm, migraphx::make_op("mul"), {mul1, x_plus_bias}); auto second = add_common_op(*mm, migraphx::make_op("mul"), {mul2, x_min_bias}); auto ret = add_common_op(*mm, migraphx::make_op("add"), {first, second}); if(ret->get_shape().type() != T) { mm->add_instruction(migraphx::make_op("convert", {{"target_type", T}}), ret); } return p; } }; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; template struct test_shrink; ROCm-AMDMIGraphX-46524e8/test/verify/test_sigmoid.cpp000066400000000000000000000032201510465702400222160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_sigmoid : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); mm->add_instruction(migraphx::make_op("sigmoid"), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sign.cpp000066400000000000000000000032351510465702400215310ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_sign : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::double_type, {2, 3, 4, 6}}; auto param = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("sign"), param); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sin.cpp000066400000000000000000000040651510465702400213640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_sin : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {10}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("sin"), x); return p; } }; template struct test_sin; template struct test_sin; template struct test_sin; template struct test_sin; template struct test_sin; template struct test_sin; template struct test_sin; ROCm-AMDMIGraphX-46524e8/test/verify/test_sin_bf16.cpp000066400000000000000000000032221510465702400221740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_sin_bf16 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {10}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("sin"), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sin_half.cpp000066400000000000000000000032221510465702400223500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_sin_half : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {10}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("sin"), x); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sinh.cpp000066400000000000000000000040771510465702400215370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_sinh : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("sinh"), x); return p; } }; template struct test_sinh; template struct test_sinh; template struct test_sinh; template struct test_sinh; template struct test_sinh; template struct test_sinh; template struct test_sinh; ROCm-AMDMIGraphX-46524e8/test/verify/test_slice.cpp000066400000000000000000000036001510465702400216640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_slice : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {2, 2, 4}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", {migraphx::shape::int32_type, {2, 2, 2}}); auto slice0 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {2}}}), x); mm->add_instruction(migraphx::make_op("add"), y, slice0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_slice2.cpp000066400000000000000000000040051510465702400217460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_slice2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {1, 44, 57, 57}}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, {1, 44, 57, 57}}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {1, 44, 56, 56}}); auto slice0 = mm->add_instruction( migraphx::make_op( "slice", {{"axes", {0, 2, 3, 1}}, {"starts", {0, 1, 1, 0}}, {"ends", {1, 57, 57, 44}}}), x); mm->add_instruction(migraphx::make_op("add"), y, slice0); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_slice_concat_add.cpp000066400000000000000000000041161510465702400240260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_slice_concat_add : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s0{migraphx::shape::float_type, {1, 24, 2, 2}}; migraphx::shape s1{migraphx::shape::float_type, {1, 8, 2, 2}}; auto x = mm->add_parameter("x", s0); auto y = mm->add_parameter("y", s1); auto z = mm->add_parameter("z", s0); auto slice = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {8}}}), x); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 1}}), slice, y, y); mm->add_instruction(migraphx::make_op("add"), concat, z); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_slice_reverse.cpp000066400000000000000000000035461510465702400234300ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_slice_reverse : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {3, 5}}; auto x = mm->add_parameter("x", s); auto slice_out = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 2}}, {"ends", {2, -1}}}), x); mm->add_instruction(migraphx::make_op("reverse", {{"axes", {0}}}), slice_out); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_slice_reverse_step.cpp000066400000000000000000000040231510465702400244520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_slice_reverse_step : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {7, 5}}; auto x = mm->add_parameter("x", s); auto slice_out = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 2}}, {"ends", {2, -1}}}), x); auto step_out = mm->add_instruction(migraphx::make_op("reverse", {{"axes", {0, 1}}}), slice_out); mm->add_instruction(migraphx::make_op("step", {{"axes", {0, 1}}, {"steps", {2, 2}}}), step_out); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_slice_sin.cpp000066400000000000000000000034221510465702400225370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_slice_sin : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto l = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 2}}); auto t = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {1}}, {"ends", {2}}}), l); mm->add_instruction(migraphx::make_op("sin"), t); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_slice_step_reverse.cpp000066400000000000000000000037661510465702400244670ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_slice_step_reverse : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::int32_type, {7, 5}}; auto x = mm->add_parameter("x", s); auto slice_out = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 1}}, {"starts", {0, 2}}, {"ends", {2, -1}}}), x); auto step_out = mm->add_instruction( migraphx::make_op("step", {{"axes", {0, 1}}, {"steps", {2, 2}}}), slice_out); mm->add_instruction(migraphx::make_op("reverse", {{"axes", {0}}}), step_out); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax.cpp000066400000000000000000000056201510465702400222520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_softmax : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{T, {512, 4, 1067, 6}}; auto param = mm->add_parameter("0", s); mm->add_instruction(migraphx::make_op("softmax", {{"axis", Axis}}), param); return p; } std::string section() const { return "reduce"; } }; template struct test_softmax<0, migraphx::shape::float_type>; template struct test_softmax<2, migraphx::shape::float_type>; template struct test_softmax<0, migraphx::shape::half_type>; template struct test_softmax<1, migraphx::shape::half_type>; template struct test_softmax<2, migraphx::shape::half_type>; template struct test_softmax<3, migraphx::shape::half_type>; template struct test_softmax<0, migraphx::shape::bf16_type>; template struct test_softmax<1, migraphx::shape::bf16_type>; template struct test_softmax<2, migraphx::shape::bf16_type>; template struct test_softmax<3, migraphx::shape::bf16_type>; template struct test_softmax<1, migraphx::shape::fp8e4m3fnuz_type>; template struct test_softmax<3, migraphx::shape::fp8e4m3fnuz_type>; template struct test_softmax<1, migraphx::shape::fp8e5m2fnuz_type>; template struct test_softmax<3, migraphx::shape::fp8e5m2fnuz_type>; template struct test_softmax<1, migraphx::shape::fp8e4m3fn_type>; template struct test_softmax<3, migraphx::shape::fp8e4m3fn_type>; template struct test_softmax<1, migraphx::shape::fp8e5m2_type>; template struct test_softmax<3, migraphx::shape::fp8e5m2_type>; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax1.cpp000066400000000000000000000033271510465702400223350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_softmax1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {5, 3, 3, 4}}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", 0}}), x); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax2.cpp000066400000000000000000000033271510465702400223360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_softmax2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 1000, 1, 1}}); mm->add_instruction(migraphx::make_op("softmax"), x); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax3.cpp000066400000000000000000000036271510465702400223420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_softmax3 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {5, 3, 3, 4}}); auto sx = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0, 3}}, {"starts", {1, 1}}, {"ends", {5, 4}}}), x); auto r = mm->add_instruction(migraphx::make_op("softmax", {{"axis", 0}}), sx); mm->add_return({r}); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax4.cpp000066400000000000000000000033471510465702400223420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_softmax4 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::half_type, {1, 12, 384, 384}}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", 3}}), x); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax5.cpp000066400000000000000000000033471510465702400223430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_softmax5 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::bf16_type, {1, 12, 384, 384}}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", 3}}), x); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax_large1.cpp000066400000000000000000000036751510465702400235150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_softmax_large1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 4}}); auto large = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {10000}}); auto add = migraphx::add_common_op(*mm, migraphx::make_op("add"), {x, large}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), add); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax_large2.cpp000066400000000000000000000036761510465702400235170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_softmax_large2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 4}}); auto large = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {-10000}}); auto add = migraphx::add_common_op(*mm, migraphx::make_op("add"), {x, large}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), add); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmax_large3.cpp000066400000000000000000000036731510465702400235150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_softmax_large3 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {2, 4}}); auto large = mm->add_literal({migraphx::shape{migraphx::shape::float_type}, {100}}); auto add = migraphx::add_common_op(*mm, migraphx::make_op("mul"), {x, large}); mm->add_instruction(migraphx::make_op("softmax", {{"axis", -1}}), add); return p; } std::string section() const { return "reduce"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmaxcrossentropyloss_2d.cpp000066400000000000000000000131311510465702400262270ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_softmaxcrossentropyloss_2d : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = NumBatches; size_t class_size = NumClasses; auto scores = mm->add_parameter("0", migraphx::shape{DType, {batch_size, NumClasses}}); auto labels = mm->add_literal(migraphx::literal(migraphx::shape(LType, {batch_size}), {0, 1, 2, 3})); auto weights = mm->add_literal(migraphx::literal(migraphx::shape(DType, {1}, {0}), {1})); std::vector label_indexes(NumBatches); std::iota(label_indexes.begin(), label_indexes.end(), 0); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(LType, {NumBatches}, {1}), label_indexes)); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size}}}), weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {NumBatches, 1}}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {class_size, batch_size}}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto neglogsoftmax = mm->add_instruction(migraphx::make_op("neg"), logsoftmax); mm->add_instruction(migraphx::make_op("mul"), neglogsoftmax, gathernd2); return p; } std::string section() const { return "reduce"; } }; // template struct test_softmaxcrossentropyloss_2d; template struct // test_softmaxcrossentropyloss_2d; template struct test_softmaxcrossentropyloss_2d; template struct test_softmaxcrossentropyloss_2d; template struct test_softmaxcrossentropyloss_2d; template struct test_softmaxcrossentropyloss_2d; template struct test_softmaxcrossentropyloss_2d; template struct test_softmaxcrossentropyloss_2d; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmaxcrossentropyloss_2d_mean.cpp000066400000000000000000000135511510465702400272350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_softmaxcrossentropyloss_2d_mean : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = NumBatches; size_t class_size = NumClasses; auto scores = mm->add_parameter("0", migraphx::shape{DType, {NumBatches, NumClasses}}); auto labels = mm->add_literal(migraphx::literal(migraphx::shape(LType, {batch_size}), {0, 1, 2, 3})); auto weights = mm->add_literal(migraphx::literal(migraphx::shape(DType, {1}, {0}), {1})); std::vector label_indexes(NumBatches); std::iota(label_indexes.begin(), label_indexes.end(), 0); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(LType, {NumBatches}, {1}), label_indexes)); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size}}}), weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 1}}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, class_size}}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto neglogsoftmax = mm->add_instruction(migraphx::make_op("neg"), logsoftmax); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), neglogsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_mean", {{"axes", {0}}}), weighted_loss); return p; } std::string section() const { return "reduce"; } }; // template struct test_softmaxcrossentropyloss_2d_mean; template struct // test_softmaxcrossentropyloss_2d_mean; template struct test_softmaxcrossentropyloss_2d_mean; template struct test_softmaxcrossentropyloss_2d_mean; template struct test_softmaxcrossentropyloss_2d_mean; template struct test_softmaxcrossentropyloss_2d_mean; template struct test_softmaxcrossentropyloss_2d_mean; template struct test_softmaxcrossentropyloss_2d_mean; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmaxcrossentropyloss_2d_sum.cpp000066400000000000000000000135131510465702400271170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_softmaxcrossentropyloss_2d_sum : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = NumBatches; size_t class_size = NumClasses; auto scores = mm->add_parameter("0", migraphx::shape{DType, {NumBatches, NumClasses}}); auto labels = mm->add_literal(migraphx::literal(migraphx::shape(LType, {batch_size}), {0, 1, 2, 3})); auto weights = mm->add_literal(migraphx::literal(migraphx::shape(DType, {1}, {0}), {1})); std::vector label_indexes(NumBatches); std::iota(label_indexes.begin(), label_indexes.end(), 0); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(LType, {batch_size}, {1}), label_indexes)); auto mb_weights = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size}}}), weights); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 1}}}), unsq_labels_idx); auto concat = mm->add_instruction( migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, unsq_labels); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), softmax, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), mb_weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, class_size}}}), unsq_mb_weights); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), unsq_mb, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto neglogsoftmax = mm->add_instruction(migraphx::make_op("neg"), logsoftmax); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), neglogsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0}}}), weighted_loss); return p; } std::string section() const { return "reduce"; } }; // template struct test_softmaxcrossentropyloss_2d_sum; template struct // test_softmaxcrossentropyloss_2d_sum; template struct test_softmaxcrossentropyloss_2d_sum; template struct test_softmaxcrossentropyloss_2d_sum; template struct test_softmaxcrossentropyloss_2d_sum; template struct test_softmaxcrossentropyloss_2d_sum; template struct test_softmaxcrossentropyloss_2d_sum; template struct test_softmaxcrossentropyloss_2d_sum; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmaxcrossentropyloss_kd.cpp000066400000000000000000000161141510465702400263240ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_softmaxcrossentropyloss_kd : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = 4; size_t class_size = 4; auto scores = mm->add_parameter( "0", migraphx::shape{migraphx::shape::float_type, {batch_size, class_size, 2, 2}}); auto labels = mm->add_literal(migraphx::literal(migraphx::shape(LType, {batch_size, 2, 2}), {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3})); auto weights = mm->add_parameter("2", migraphx::shape{migraphx::shape::float_type, {class_size}}); auto weights_dflt = mm->add_literal(migraphx::literal(migraphx::shape(DType, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(LType, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal(migraphx::literal(migraphx::shape(LType, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal(migraphx::literal(migraphx::shape(LType, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), softmax); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, class_size, 2, 2}}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto neglogsoftmax = mm->add_instruction(migraphx::make_op("neg"), logsoftmax); mm->add_instruction(migraphx::make_op("mul"), neglogsoftmax, gathernd2); return p; } std::string section() const { return "reduce"; } }; // template struct test_softmaxcrossentropyloss_kd; template struct // test_softmaxcrossentropyloss_kd; template struct test_softmaxcrossentropyloss_kd; template struct test_softmaxcrossentropyloss_kd; template struct test_softmaxcrossentropyloss_kd; template struct test_softmaxcrossentropyloss_kd; template struct test_softmaxcrossentropyloss_kd; template struct test_softmaxcrossentropyloss_kd; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmaxcrossentropyloss_kd_mean.cpp000066400000000000000000000164001510465702400273220ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_softmaxcrossentropyloss_kd_mean : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); size_t batch_size = NumBatches; size_t class_size = NumClasses; auto scores = mm->add_parameter("0", migraphx::shape{DType, {batch_size, class_size, 2, 2}}); auto labels = mm->add_literal(migraphx::literal(migraphx::shape(LType, {batch_size, 2, 2}), {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3})); auto weights = mm->add_parameter("2", migraphx::shape{DType, {class_size}}); auto weights_dflt = mm->add_literal(migraphx::literal(migraphx::shape(DType, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(LType, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal(migraphx::literal(migraphx::shape(LType, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal(migraphx::literal(migraphx::shape(LType, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), softmax); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {class_size, batch_size, 2, 2}}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto neglogsoftmax = mm->add_instruction(migraphx::make_op("neg"), logsoftmax); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), neglogsoftmax, gathernd2); auto loss_x = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), weighted_loss); auto loss_w = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), gathernd2); mm->add_instruction(migraphx::make_op("div"), loss_x, loss_w); return p; } std::string section() const { return "reduce"; } }; // template struct test_softmaxcrossentropyloss_kd_mean; template struct // test_softmaxcrossentropyloss_kd_mean; template struct test_softmaxcrossentropyloss_kd_mean; template struct // test_softmaxcrossentropyloss_kd_mean; template struct test_softmaxcrossentropyloss_kd_mean; template struct test_softmaxcrossentropyloss_kd_mean; template struct test_softmaxcrossentropyloss_kd_mean; template struct test_softmaxcrossentropyloss_kd_mean; ROCm-AMDMIGraphX-46524e8/test/verify/test_softmaxcrossentropyloss_kd_sum.cpp000066400000000000000000000163351510465702400272150ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_softmaxcrossentropyloss_kd_sum_reduction : verify_program< test_softmaxcrossentropyloss_kd_sum_reduction> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); const size_t batch_size = NumBatches; const size_t class_size = NumClasses; auto scores = mm->add_parameter("0", migraphx::shape{DType, {batch_size, class_size, 2, 2}}); auto labels = mm->add_literal(migraphx::literal(migraphx::shape(LType, {batch_size, 2, 2}), {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3})); auto weights = mm->add_parameter("2", migraphx::shape{DType, {class_size}}); auto weights_dflt = mm->add_literal(migraphx::literal(migraphx::shape(DType, {1}, {0}), {1})); auto labels_idx = mm->add_literal( migraphx::literal(migraphx::shape(LType, {class_size}, {1}), {0, 1, 2, 3})); // Index variables used for gather on k dimensions that span their dimension auto kd_1 = mm->add_literal(migraphx::literal(migraphx::shape(LType, {2}, {1}), {0, 1})); auto kd_2 = mm->add_literal(migraphx::literal(migraphx::shape(LType, {2}, {1}), {0, 1})); mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {class_size}}}), weights_dflt); auto softmax = mm->add_instruction(migraphx::make_op("softmax"), scores); auto unsq_labels = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {-1}}}), labels); auto unsq_labels_idx = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2, 3}}}), labels_idx); auto bc_unsq_labels_idx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx); auto unsq_labels_idx2 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), kd_1); auto bc_unsq_labels_idx2 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx2); auto unsq_labels_idx3 = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 1, 3}}}), kd_2); auto bc_unsq_labels_idx3 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, 2, 2, 1}}}), unsq_labels_idx3); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", -1}}), bc_unsq_labels_idx, bc_unsq_labels_idx2, bc_unsq_labels_idx3, unsq_labels); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), softmax); auto gathernd = mm->add_instruction(migraphx::make_op("gathernd"), transpose, concat); auto unsq_mb_weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0, 2, 3}}}), weights); auto unsq_mb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {batch_size, class_size, 2, 2}}}), unsq_mb_weights); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), unsq_mb); auto gathernd2 = mm->add_instruction(migraphx::make_op("gathernd"), transpose2, concat); auto logsoftmax = mm->add_instruction(migraphx::make_op("log"), gathernd); auto neglogsoftmax = mm->add_instruction(migraphx::make_op("neg"), logsoftmax); auto weighted_loss = mm->add_instruction(migraphx::make_op("mul"), neglogsoftmax, gathernd2); mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {0, 1, 2}}}), weighted_loss); return p; } std::string section() const { return "reduce"; } }; // template struct test_softmaxcrossentropyloss_kd_sum_reduction; template struct // test_softmaxcrossentropyloss_kd_sum_reduction; template struct // test_softmaxcrossentropyloss_kd_sum_reduction; template struct // test_softmaxcrossentropyloss_kd_sum_reduction; template struct test_softmaxcrossentropyloss_kd_sum_reduction; template struct test_softmaxcrossentropyloss_kd_sum_reduction; template struct test_softmaxcrossentropyloss_kd_sum_reduction; template struct test_softmaxcrossentropyloss_kd_sum_reduction; ROCm-AMDMIGraphX-46524e8/test/verify/test_spacetodepth.cpp000066400000000000000000000040211510465702400232460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_spacetodepth : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 2, 6, 6}}; auto x = mm->add_parameter("x", s); auto reshape1 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, 3, 2, 3, 2}}}), x); auto transpose = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 3, 5, 1, 2, 4}}}), reshape1); auto reshape2 = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {2, 8, 3, 3}}}), transpose); mm->add_return({reshape2}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_split_single_dyn_dim.cpp000066400000000000000000000041761510465702400247750ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include /** * Test that the split_single_dyn_dim GPU compiler pass produces the same results as ref. */ struct test_split_single_dyn_dim : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape lit_s{migraphx::shape{migraphx::shape::float_type, {1}}}; auto literal_ins = mm->add_literal(migraphx::literal{lit_s, {6}}); migraphx::shape s{migraphx::shape::float_type, {{1, 4}, {4, 4}}}; auto input = mm->add_parameter("data", s); auto broadcast_lit = mm->add_instruction(migraphx::make_op("multibroadcast"), literal_ins, input); auto add_ins = mm->add_instruction(migraphx::make_op("add"), input, broadcast_lit); mm->add_return({add_ins}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sqrt.cpp000066400000000000000000000042461510465702400215650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_sqrt : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {2, 3, 4, 6}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; template struct test_sqrt; template struct test_sqrt; template struct test_sqrt; template struct test_sqrt; template struct test_sqrt; template struct test_sqrt; template struct test_sqrt; ROCm-AMDMIGraphX-46524e8/test/verify/test_sqrt_bf161.cpp000066400000000000000000000035051510465702400224610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include // math op on bf16-precision float with odd size tensor can't fit bf162 packing struct test_sqrt_bf161 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {5}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sqrt_bf162.cpp000066400000000000000000000035321510465702400224620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include // math op on bf16-precision float with tensor size that's divisible by 2, // but not divisible by 4 struct test_sqrt_bf162 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {6}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sqrt_bf164.cpp000066400000000000000000000035061510465702400224650ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include // math op on bf16-precision float with tensor size that fits into bf164 packing struct test_sqrt_bf164 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bf16_type, {8}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sqrt_half1.cpp000066400000000000000000000035051510465702400226350ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include // math op on half-precision float with odd size tensor can't fit half2 packing struct test_sqrt_half1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {5}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sqrt_half2.cpp000066400000000000000000000035321510465702400226360ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include // math op on half-precision float with tensor size that's divisible by 2, // but not divisible by 4 struct test_sqrt_half2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {6}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sqrt_half4.cpp000066400000000000000000000035061510465702400226410ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include // math op on half-precision float with tensor size that fits into half4 packing struct test_sqrt_half4 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::half_type, {8}}; auto param = mm->add_parameter("x", s); auto param_abs = mm->add_instruction(migraphx::make_op("abs"), param); mm->add_instruction(migraphx::make_op("sqrt"), param_abs); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_squeeze_conv_relu.cpp000066400000000000000000000040271510465702400243260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_squeeze_conv_relu : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 1, 3, 3}}); input = mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), input); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto conv = mm->add_instruction(migraphx::make_op("convolution"), input, weights); mm->add_instruction(migraphx::make_op("relu"), conv); return p; } std::string section() const { return "conv"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_step.cpp000066400000000000000000000033721510465702400215460ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_step : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {2, 1, 4, 6}}; auto l0 = mm->add_parameter("x", s1); auto r = mm->add_instruction( migraphx::make_op("step", {{"axes", {0, 2, 3}}, {"steps", {2, 2, 3}}}), l0); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_step_broadcast_transpose.cpp000066400000000000000000000040251510465702400256620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_step_broadcast_transpose : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {1, 1, 1, 6}}; auto l0 = mm->add_parameter("x", s1); auto ml = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 1, 4, 6}}}), l0); auto tl = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), ml); auto r = mm->add_instruction( migraphx::make_op("step", {{"axes", {0, 1, 2}}, {"steps", {2, 2, 3}}}), tl); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_step_dot.cpp000066400000000000000000000040601510465702400224070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_step_dot : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape as{migraphx::shape::float_type, {128, 4, 64, 196}}; migraphx::shape bs{migraphx::shape::float_type, {128, 4, 196, 196}}; auto a = mm->add_parameter("input", as); auto b = mm->add_literal(migraphx::generate_literal(bs)); auto step = mm->add_instruction(migraphx::make_op("step", {{"axes", {2}}, {"steps", {2}}}), a); auto dot = mm->add_instruction(migraphx::make_op("dot"), step, b); mm->add_return({dot}); return p; } std::string section() const { return "gemm"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_step_transpose.cpp000066400000000000000000000036061510465702400236440ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_step_transpose : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s1{migraphx::shape::float_type, {2, 1, 4, 6}}; auto l0 = mm->add_parameter("x", s1); auto tl = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), l0); auto r = mm->add_instruction( migraphx::make_op("step", {{"axes", {0, 1, 2}}, {"steps", {2, 2, 3}}}), tl); mm->add_return({r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sub.cpp000066400000000000000000000034701510465702400213630ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_sub : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto diff = mm->add_instruction(migraphx::make_op("sub"), x, y); mm->add_instruction(migraphx::make_op("sub"), diff, z); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sub2.cpp000066400000000000000000000037631510465702400214520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_sub2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::shape b{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", b); auto zb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), z); auto diff = mm->add_instruction(migraphx::make_op("sub"), x, y); mm->add_instruction(migraphx::make_op("sub"), diff, zb); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_sub_int.cpp000066400000000000000000000036651510465702400222430ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_sub_int : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", {migraphx::shape::int16_type, {4, 5}}); auto y = mm->add_parameter("y", {migraphx::shape::int16_type, {2, 3, 4, 5}}); auto xb = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {2, 3, 4, 5}}}), x); auto diff = mm->add_instruction(migraphx::make_op("sub"), y, xb); mm->add_return({diff}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_tan.cpp000066400000000000000000000040651510465702400213550ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_tan : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {16}}; auto x = mm->add_parameter("x", s); mm->add_instruction(migraphx::make_op("tan"), x); return p; } }; template struct test_tan; template struct test_tan; template struct test_tan; template struct test_tan; template struct test_tan; template struct test_tan; template struct test_tan; ROCm-AMDMIGraphX-46524e8/test/verify/test_tanh.cpp000066400000000000000000000040741510465702400215250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_tanh : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{DType, {4, 3, 3, 3}}); mm->add_instruction(migraphx::make_op("tanh"), x); return p; } }; template struct test_tanh; template struct test_tanh; template struct test_tanh; template struct test_tanh; template struct test_tanh; template struct test_tanh; template struct test_tanh; ROCm-AMDMIGraphX-46524e8/test/verify/test_topk.cpp000066400000000000000000000066731510465702400215570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_topk : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); unsigned int batch = 3; migraphx::shape s{DType, {batch, N}}; auto x1 = mm->add_parameter("x1", s); auto r = mm->add_instruction( migraphx::make_op("topk", {{"axis", 1}, {"k", K}, {"largest", 1}}), x1); auto values = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto indices = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({values, indices}); return p; } std::size_t get_tolerance() const { return 2; }; }; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; template struct test_topk; ROCm-AMDMIGraphX-46524e8/test/verify/test_topk_0.cpp000066400000000000000000000040421510465702400217620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_topk_0 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{DType, {3, 5}}; auto data = mm->add_parameter("data", s); auto r = mm->add_instruction( migraphx::make_op("topk", {{"axis", 1}, {"k", 4}, {"largest", 1}}), data); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); mm->add_return({r0}); return p; } }; template struct test_topk_0; template struct test_topk_0; template struct test_topk_0; ROCm-AMDMIGraphX-46524e8/test/verify/test_topk_1.cpp000066400000000000000000000036771510465702400220000ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_topk_1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); auto r = mm->add_instruction( migraphx::make_op("topk", {{"axis", -2}, {"k", 3}, {"largest", 1}}), data); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_topk_2.cpp000066400000000000000000000035331510465702400217700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_topk_2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); auto r = mm->add_instruction( migraphx::make_op("topk", {{"axis", 1}, {"k", 4}, {"largest", 0}}), data); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); mm->add_return({r0}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_topk_3.cpp000066400000000000000000000036771510465702400220020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_topk_3 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); auto r = mm->add_instruction( migraphx::make_op("topk", {{"axis", -2}, {"k", 3}, {"largest", 0}}), data); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_trans_abs.cpp000066400000000000000000000036451510465702400225520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_trans_abs : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto tx = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), x); auto absx = mm->add_instruction(migraphx::make_op("abs"), tx); auto r = mm->add_instruction(migraphx::make_op("add"), absx, absx); mm->add_instruction(migraphx::make_op("contiguous"), r); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_trans_convert_gemm.cpp000066400000000000000000000051001510465702400244560ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_trans_convert_gemm : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("b", migraphx::shape{migraphx::shape::float_type, {2, 1920, 2, 2}}); auto b = mm->add_parameter("a", migraphx::shape{DType, {2, 2, 1920, 2}}); auto at = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 3, 1}}}), a); auto atc = mm->add_instruction(migraphx::make_op("convert", {{"target_type", DType}}), at); mm->add_instruction(migraphx::make_op("dot"), atc, b); return p; } std::string section() const { return "gemm"; } }; template struct test_trans_convert_gemm; template struct test_trans_convert_gemm; template struct test_trans_convert_gemm; template struct test_trans_convert_gemm; template struct test_trans_convert_gemm; template struct test_trans_convert_gemm; template struct test_trans_convert_gemm; ROCm-AMDMIGraphX-46524e8/test/verify/test_trans_ret.cpp000066400000000000000000000033541510465702400225740ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_trans_ret : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto tx = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), x); mm->add_return({tx}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_trans_slice.cpp000066400000000000000000000045431510465702400231020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_trans_slice : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 384, 36, 64}}); auto transpose = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 2, 1, 3}}}), x); auto slice1 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {12}}}), transpose); auto slice2 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {12}}, {"ends", {24}}}), transpose); auto transpose2 = mm->add_instruction( migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), slice2); auto slice3 = mm->add_instruction( migraphx::make_op("slice", {{"axes", {1}}, {"starts", {24}}, {"ends", {36}}}), transpose); mm->add_return({slice1, transpose2, slice3}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_trans_tanh.cpp000066400000000000000000000036541510465702400227370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_trans_tanh : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto tx = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), x); auto tanhx = mm->add_instruction(migraphx::make_op("tanh"), tx); auto r = mm->add_instruction(migraphx::make_op("add"), tanhx, tanhx); mm->add_instruction(migraphx::make_op("contiguous"), r); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_trans_tanh1.cpp000066400000000000000000000036161510465702400230160ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_trans_tanh1 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto tx = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {0, 1, 3, 2}}}), x); auto tanhx = mm->add_instruction(migraphx::make_op("tanh"), tx); auto r = mm->add_instruction(migraphx::make_op("add"), tanhx, tanhx); mm->add_return({tx, r}); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_transpose.cpp000066400000000000000000000035131510465702400226060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_transpose : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {4, 3, 4, 4}}; auto x = mm->add_parameter("x", s); std::vector perm = {0, 2, 3, 1}; auto l = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", perm}}), x); mm->add_instruction(migraphx::make_op("contiguous"), l); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_transpose_reshape_add_sub_mul.cpp000066400000000000000000000060071510465702400266540ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include template struct test_transpose_reshape_add_sub_mul : verify_program> { migraphx::program create_program() const { migraphx::shape s1{migraphx::shape::float_type, {2, 10}}; migraphx::shape s2{migraphx::shape::float_type, {5, 4}}; migraphx::program p; auto* mm = p.get_main_module(); auto a = mm->add_parameter("a", s1); auto b = mm->add_parameter("b", s2); auto x = mm->add_parameter("x", s2); auto a_trans = mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), a); auto a_rsp = mm->add_instruction(migraphx::make_op("reshape", {{"dims", {5, 4}}}), a_trans); auto add = mm->add_instruction(migraphx::make_op("add"), {a_rsp, b}); auto sub = mm->add_instruction(migraphx::make_op("sub"), {b, x}); auto mul = mm->add_instruction(migraphx::make_op("mul"), add, sub); mm->add_return({mul}); return p; } std::string section() const { return "gemm"; } }; template struct test_transpose_reshape_add_sub_mul; template struct test_transpose_reshape_add_sub_mul; template struct test_transpose_reshape_add_sub_mul; template struct test_transpose_reshape_add_sub_mul; template struct test_transpose_reshape_add_sub_mul; template struct test_transpose_reshape_add_sub_mul; template struct test_transpose_reshape_add_sub_mul; ROCm-AMDMIGraphX-46524e8/test/verify/test_triadd.cpp000066400000000000000000000034711510465702400220420ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_triadd : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", s); auto sum = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_instruction(migraphx::make_op("add"), sum, z); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_triadd2.cpp000066400000000000000000000037671510465702400221340ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_triadd2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 3}}; migraphx::shape b{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); auto z = mm->add_parameter("z", b); auto zb = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", s.lens()}}), z); auto sum = mm->add_instruction(migraphx::make_op("add"), x, y); mm->add_instruction(migraphx::make_op("add"), sum, zb); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_triadd_broadcast.cpp000066400000000000000000000041551510465702400240640ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include struct test_triadd_broadcast : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3}}; auto x = mm->add_parameter("x", {migraphx::shape::float_type, {2, 2, 3}}); auto y = mm->add_parameter("y", {migraphx::shape::float_type, {2, 2}}); auto z = mm->add_parameter("z", {migraphx::shape::float_type, {2, 2, 3}}); auto by = mm->add_instruction( migraphx::make_op("broadcast", {{"axis", 0}, {"out_lens", x->get_shape().lens()}}), y); auto sum = mm->add_instruction(migraphx::make_op("add"), x, by); mm->add_instruction(migraphx::make_op("add"), sum, z); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_triadd_relu.cpp000066400000000000000000000037741510465702400230770ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_triadd_relu : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto z = mm->add_parameter("z", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto sum = mm->add_instruction(migraphx::make_op("add"), x, y); auto triadd = mm->add_instruction(migraphx::make_op("add"), sum, z); mm->add_instruction(migraphx::make_op("relu"), triadd); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_triadd_sigmoid.cpp000066400000000000000000000040051510465702400235470ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_triadd_sigmoid : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto z = mm->add_parameter("z", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto sum = mm->add_instruction(migraphx::make_op("add"), x, y); auto triadd = mm->add_instruction(migraphx::make_op("add"), sum, z); mm->add_instruction(migraphx::make_op("sigmoid"), triadd); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_triadd_tanh.cpp000066400000000000000000000037741510465702400230620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_triadd_tanh : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto y = mm->add_parameter("y", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto z = mm->add_parameter("z", migraphx::shape{migraphx::shape::float_type, {4, 3, 3, 3}}); auto sum = mm->add_instruction(migraphx::make_op("add"), x, y); auto triadd = mm->add_instruction(migraphx::make_op("add"), sum, z); mm->add_instruction(migraphx::make_op("tanh"), triadd); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_unbatched_gemm_1.cpp000066400000000000000000000064111510465702400237520ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_unbatched_gemm_1 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {2, 32, 64}}; migraphx::shape m2_shape{DType, {64, 64}}; migraphx::shape m3_shape{DType, {2, 32, 192}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_literal(migraphx::generate_literal(m2_shape)); l2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 64, 64}}}), l2); auto l3 = mm->add_literal(migraphx::generate_literal(m2_shape)); l3 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 64, 64}}}), l3); auto l4 = mm->add_literal(migraphx::generate_literal(m2_shape)); l4 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 64, 64}}}), l4); auto concat = mm->add_instruction(migraphx::make_op("concat", {{"axis", 2}}), l2, l3, l4); auto l5 = mm->add_parameter("3", m3_shape); float alpha = 1.0f; float beta = 1.0f; migraphx::add_apply_alpha_beta( *mm, {l1, concat, l5}, migraphx::make_op("dot"), alpha, beta); return p; } std::string section() const { return "gemm"; } }; template struct test_unbatched_gemm_1; template struct test_unbatched_gemm_1; template struct test_unbatched_gemm_1; template struct test_unbatched_gemm_1; template struct test_unbatched_gemm_1; template struct test_unbatched_gemm_1; template struct test_unbatched_gemm_1; ROCm-AMDMIGraphX-46524e8/test/verify/test_unbatched_gemm_2.cpp000066400000000000000000000050471510465702400237570ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include template struct test_unbatched_gemm_2 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape m1_shape{DType, {4, 32, 64}}; migraphx::shape m2_shape{DType, {64, 64}}; auto l1 = mm->add_parameter("1", m1_shape); auto l2 = mm->add_literal(migraphx::generate_literal(m2_shape)); l2 = mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {4, 64, 64}}}), l2); mm->add_instruction(migraphx::make_op("dot"), l1, l2); return p; } std::string section() const { return "gemm"; } }; template struct test_unbatched_gemm_2; template struct test_unbatched_gemm_2; template struct test_unbatched_gemm_2; template struct test_unbatched_gemm_2; template struct test_unbatched_gemm_2; template struct test_unbatched_gemm_2; template struct test_unbatched_gemm_2; ROCm-AMDMIGraphX-46524e8/test/verify/test_unpack_fp4.cpp000066400000000000000000000034201510465702400226170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_unpack_fp4 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::fp4x2_type, {32, 16}}); mm->add_instruction(migraphx::make_op("unpack_fp4", {{"axis", Axis}}), x); return p; } }; template struct test_unpack_fp4<>; template struct test_unpack_fp4<0>; ROCm-AMDMIGraphX-46524e8/test/verify/test_unpack_int4.cpp000066400000000000000000000037201510465702400230070ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_unpack_int4 : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{T, {64, 32}}); mm->add_instruction(migraphx::make_op("unpack_int4", {{"axis", Axis}}), x); return p; } }; template struct test_unpack_int4; template struct test_unpack_int4; template struct test_unpack_int4; template struct test_unpack_int4; ROCm-AMDMIGraphX-46524e8/test/verify/test_unsqueeze_conv_relu.cpp000066400000000000000000000040351510465702400246700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_unsqueeze_conv_relu : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); auto input = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {1, 3, 3, 3}}); auto weights = mm->add_parameter("w", migraphx::shape{migraphx::shape::float_type, {3, 3, 3}}); weights = mm->add_instruction(migraphx::make_op("unsqueeze", {{"axes", {0}}}), weights); auto conv = mm->add_instruction(migraphx::make_op("convolution"), input, weights); mm->add_instruction(migraphx::make_op("relu"), conv); return p; } std::string section() const { return "conv"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_var_sl_gru_bidirct.cpp000066400000000000000000000073751510465702400244450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_var_sl_gru_bidirct : verify_program { migraphx::program create_program() const { std::size_t batch_size = 3; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 2; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); std::vector sl_data{2, 1, 3}; auto sql = mm->add_literal(migraphx::literal{sl_shape, sl_data}); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::bidirectional)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({hs, lho}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_var_sl_gru_forward.cpp000066400000000000000000000070621510465702400244620ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include #include #include struct test_var_sl_gru_forward : verify_program { migraphx::program create_program() const { std::size_t batch_size = 3; std::size_t seq_len = 3; std::size_t hidden_size = 5; std::size_t input_size = 8; std::size_t num_dirct = 1; float clip = 0.0f; migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape in_shape{migraphx::shape::float_type, {seq_len, batch_size, input_size}}; migraphx::shape w_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, input_size}}; migraphx::shape r_shape{migraphx::shape::float_type, {num_dirct, 3 * hidden_size, hidden_size}}; migraphx::shape b_shape{migraphx::shape::float_type, {num_dirct, 6 * hidden_size}}; migraphx::shape sl_shape{migraphx::shape::int32_type, {batch_size}}; migraphx::shape ih_shape{migraphx::shape::float_type, {num_dirct, batch_size, hidden_size}}; auto seq = mm->add_parameter("seq", in_shape); auto w = mm->add_parameter("w", w_shape); auto r = mm->add_parameter("r", r_shape); auto bias = mm->add_parameter("bias", b_shape); auto ih = mm->add_parameter("ih", ih_shape); std::vector sl_data{3, 2, 1}; auto sql = mm->add_literal(migraphx::literal{sl_shape, sl_data}); auto hs = mm->add_instruction( migraphx::make_op( "gru", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(std::vector{migraphx::make_op("sigmoid"), migraphx::make_op("tanh")})}, {"direction", migraphx::to_value(migraphx::op::rnn_direction::forward)}, {"clip", clip}}), seq, w, r, bias, sql, ih); auto lho = mm->add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); mm->add_return({lho, hs}); return p; } std::string section() const { return "rnn"; } }; ROCm-AMDMIGraphX-46524e8/test/verify/test_where.cpp000066400000000000000000000044671510465702400217130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include template struct test_where : verify_program> { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::bool_type, {1, 3, 4, 5}}; migraphx::shape sx{migraphx::shape::float_type, {1, 3, 4, 5}}; auto b = mm->add_parameter("b", sb); auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sx); auto r = mm->add_instruction(migraphx::make_op("where"), b, x, y); mm->add_return({r}); return p; }; }; template struct test_where; template struct test_where; template struct test_where; template struct test_where; template struct test_where; template struct test_where; template struct test_where; ROCm-AMDMIGraphX-46524e8/test/verify/test_where2.cpp000066400000000000000000000041401510465702400217610ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_where2 : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sb{migraphx::shape::bool_type, {1, 3, 4, 5}}; migraphx::shape sx{migraphx::shape::float_type, {1}}; auto b = mm->add_parameter("b", sb); auto x = mm->add_parameter("x", sx); auto y = mm->add_parameter("y", sx); auto mbx = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 3, 4, 5}}}), x); auto mby = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 3, 4, 5}}}), y); auto r = mm->add_instruction(migraphx::make_op("where"), b, mbx, mby); mm->add_return({r}); return p; }; }; ROCm-AMDMIGraphX-46524e8/test/verify/test_xor.cpp000066400000000000000000000032761510465702400214060ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" #include #include #include struct test_xor : verify_program { migraphx::program create_program() const { migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::bool_type, {3}}; auto x = mm->add_parameter("x", s); auto y = mm->add_parameter("y", s); mm->add_instruction(migraphx::make_op("logical_xor"), x, y); return p; } }; ROCm-AMDMIGraphX-46524e8/test/verify/verify_program.cpp000066400000000000000000000027701510465702400225700ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include "verify_program.hpp" static std::vector& get_programs_vector() { static std::vector result; // NOLINT return result; } void register_program_info(const program_info& pi) { get_programs_vector().push_back(pi); } const std::vector& get_programs() { return get_programs_vector(); } ROCm-AMDMIGraphX-46524e8/test/verify/verify_program.hpp000066400000000000000000000065641510465702400226020ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_AUTO_REGISTER_VERIFY_PROGRAM_HPP #define MIGRAPHX_GUARD_AUTO_REGISTER_VERIFY_PROGRAM_HPP #include #include #include #include #include #include struct program_info { std::string name; std::string section; std::size_t tolerance; std::function get_program; migraphx::compile_options compile_options; }; void register_program_info(const program_info& pi); const std::vector& get_programs(); struct register_verify_program_action { template static void apply() { T x; program_info pi; const std::string& test_type_name = migraphx::get_type_name(); const auto& split_name = migraphx::split_string(test_type_name, ':'); std::vector name_without_version = {}; // test_type_name could contain internal namespace name with version_x_y_z i.e. // test_instancenorm remove version and construct // test_name such as test_instancenorm std::copy_if( split_name.begin(), split_name.end(), std::back_inserter(name_without_version), [&](const auto& i) { return not i.empty() and not migraphx::contains(i, "version"); }); pi.name = migraphx::trim(migraphx::join_strings(name_without_version, "::")); pi.section = x.section(); pi.tolerance = x.get_tolerance(); pi.get_program = [x] { return x.create_program(); }; pi.compile_options = x.get_compile_options(); register_program_info(pi); } }; template using auto_register_verify_program = migraphx::auto_register; template struct verify_program : auto_register_verify_program { std::string section() const { return "general"; }; migraphx::compile_options get_compile_options() const { return migraphx::compile_options{}; }; std::size_t get_tolerance() const { return 80; }; }; #endif ROCm-AMDMIGraphX-46524e8/tools/000077500000000000000000000000001510465702400157005ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/CMakeLists.txt000066400000000000000000000034701510465702400204440ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### find_package(Python 3 COMPONENTS Interpreter) if(NOT Python_EXECUTABLE) message(WARNING "Python 3 interpreter not found - skipping 'generate' target!") return() endif() find_program(CLANG_FORMAT clang-format PATHS /opt/rocm/llvm ENV HIP_PATH PATH_SUFFIXES bin) if(NOT CLANG_FORMAT) message(WARNING "clang-format not found - skipping 'generate' target!") return() endif() add_custom_target(generate ${Python_EXECUTABLE} generate.py -f ${CLANG_FORMAT} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) ROCm-AMDMIGraphX-46524e8/tools/accuracy/000077500000000000000000000000001510465702400174725ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/accuracy/README.md000066400000000000000000000026061510465702400207550ustar00rootroot00000000000000# AMD MIGraphX Accuracy checker ## Instructions First ensure requirements and MIGraphX's python library are installed. Refer to MIGraphX instructions at the root directory to install the python library. Use the command below to install remaining dependencies: ``` pip install -r requirements.txt ``` The accuracy checker will compare outputs from MIGraphX and onnx runtime. Therefore, an onnx file is required argument. Example usage is below: ``` python accuracy_checker.py --onnx [path to onnx_file] ``` The output of the checker will either report as `PASSED` or `FAILED`. For detailed information, the `--verbose` flag can be passed in to the command line which shows the mismatched elements between MIGraphX and onnx runtime. By default, the tolerance is set to `1e-3`, but this can be changed by passing in `--tolerance [tolerance]`. If the tolerance value is increased, then less accurate results from MIGraphX will be accepted. For models that support variable batch sizes, use `--batch [batch_size]` to modify the batch size. Random values are assigned to the model's inputs. However, they can be set to only contain 1s if the `--fill1` flag is passed in. This is useful for verifying models such as bert which use integer datatypes. By default, the CPU Execution Provider is used when running onnx runtime. If building onnx runtime with a different version, specify the provider using `--provider`. ROCm-AMDMIGraphX-46524e8/tools/accuracy/accuracy_checker.py000066400000000000000000000320521510465702400233240ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import argparse import numpy as np import migraphx import onnxruntime as ort import sys def parse_args(): parser = argparse.ArgumentParser( description= 'MIGraphX accuracy checker. Use to verify onnx files to ensure MIGraphX\'s output \ is within tolerance of onnx runtime\'s expected output.' ) file_args = parser.add_argument_group(title='file type arguments') file_args.add_argument('--onnx', type=str, help='path to onnx file') file_args.add_argument('--tf', type=str, help='path to tf pb file') parser.add_argument('--provider', type=str, default='CPUExecutionProvider', help='execution provider for onnx runtime \ (default = CPUExecutionProvider)') parser.add_argument('--batch', type=int, default=1, help='batch size (if specified in onnx file)') parser.add_argument('--fill1', action='store_true', help='fill all arguments with a value of 1') parser.add_argument('--fill0', action='store_true', help='fill all arguments with a value of 0') parser.add_argument('--fp16', action='store_true', help='quantize MIGraphX model to fp16') parser.add_argument('--argmax', action='store_true', help='use argmax for accuracy') parser.add_argument('--verbose', action='store_true', help='show verbose information (for debugging)') parser.add_argument('--tolerance', type=float, default=1e-3, help='accuracy tolerance (default = 1e-3)') parser.add_argument('--input-dim', type=str, action='append', help='specify input parameter dimension \ with the following format --input-dim input_name:dim0,dim1,dim2...' ) parser.add_argument('--target', type=str, default='gpu', help='target to compile and run MIGraphX on') parser.add_argument('--ort-run', dest="ort_run", action='store_true', default=False, help='only perform an onnxruntime run') parser.add_argument('--ort-logging', dest="ort_logging", action='store_true', default=False, help='Turn on ort VERBOSE logging via session options') parser.add_argument('--show-test-data', dest='show_data', action='store_true', default=False, help='Display input data used for run') parser.add_argument( '--disable-offload-copy', dest="offload_copy", action='store_false', default=True, help= 'Disable offload copying (user must handle copy to and from device)') parser.add_argument( '--disable-fast-math', dest="fast_math", action='store_false', default=True, help='Disable fast math optimizations (etc: rewrite_gelu)') parser.add_argument('--exhaustive_tune', dest="exhaustive_tune", action='store_true', default=False, help='Enable exhaustive tuning for solutions') args = parser.parse_args() return args, parser # taken from ../test_runner.py def check_correctness(gold_outputs, outputs, rtol=1e-3, atol=1e-3, use_argmax=False, verbose=False): if len(gold_outputs) != len(outputs): print('Number of outputs {} is not equal to expected number {}'.format( len(outputs), len(gold_outputs))) return False out_num = len(gold_outputs) ret = True if not use_argmax: for i in range(out_num): if not np.allclose(gold_outputs[i], outputs[i], rtol, atol): ret = False if verbose: with np.printoptions(threshold=np.inf): print('\nOutput {} is incorrect ...'.format(i)) print('Expected value: \n{}'.format(gold_outputs[i])) print('\n......\n') print('Actual value: \n{}\n'.format(outputs[i])) diff = gold_outputs[i] - outputs[i] max_diff = np.max(np.abs(diff)) print(f'Max Difference: {max_diff}') else: print('Outputs do not match') break else: golden_argmax = np.argmax(gold_outputs) actual_argmax = np.argmax(outputs) if actual_argmax != golden_argmax: ret = False print('\nOutput argmax is incorrect ...') if verbose: print('Expected argmax value: \n{}'.format(golden_argmax)) print('......') print('Actual argmax value: \n{}\n'.format(actual_argmax)) return ret def get_np_datatype(in_type): datatypes = { 'double_type': np.float64, 'float_type': np.float32, 'half_type': np.half, 'int64_type': np.int64, 'uint64_type': np.uint64, 'int32_type': np.int32, 'uint32_type': np.uint32, 'int16_type': np.int16, 'uint16_type': np.uint16, 'int8_type': np.int8, 'uint8_type': np.uint8, 'bool_type': bool } return datatypes[in_type] def main(): args, parser = parse_args() use_onnx = True if args.onnx == None: use_onnx = False if not use_onnx and args.tf == None: print('Error: please specify either an onnx or tf pb file') parser.print_help() sys.exit(-1) model_name = args.onnx batch = args.batch custom_inputs = args.input_dim input_dims = {} if custom_inputs != None: for input in custom_inputs: input_dim = ''.join(input.split(':')[:-1]) dims = [int(dim) for dim in input.split(':')[-1].split(',')] input_dims[input_dim] = dims if use_onnx: if not input_dims: model = migraphx.parse_onnx(model_name, default_dim_value=batch) else: model = migraphx.parse_onnx(model_name, default_dim_value=batch, map_input_dims=input_dims) else: model_name = args.tf if not input_dims: model = migraphx.parse_tf(model_name, batch_size=batch) else: model = migraphx.parse_tf(model_name, batch_size=batch, map_input_dims=input_dims) if (args.fp16): migraphx.quantize_fp16(model) if args.verbose: print(model) if not args.ort_run: model.compile( migraphx.get_target(args.target), offload_copy=args.offload_copy, fast_math=args.fast_math, exhaustive_tune=args.exhaustive_tune, ) params = {} test_inputs = {} for name, shape in model.get_parameter_shapes().items(): if args.verbose or args.show_data: print(f'Parameter {name} -> {shape}') in_shape = shape.lens() in_type = shape.type_string() if not args.fill1 and not args.fill0: test_input = np.random.rand(*(in_shape)).astype( get_np_datatype(in_type)) elif not args.fill0: test_input = np.ones(in_shape).astype(get_np_datatype(in_type)) else: test_input = np.zeros(in_shape).astype(get_np_datatype(in_type)) test_inputs[name] = test_input if args.show_data: print(f"Input data for {name}: {test_input}\n") migraphx_arg = migraphx.argument(test_inputs[name]) if not args.offload_copy: migraphx_arg = migraphx.to_gpu(migraphx_arg) params[name] = migraphx_arg if not args.ort_run: if not args.offload_copy: pred_migx = np.array(migraphx.from_gpu(model.run(params)[-1])) else: pred_migx = np.array(model.run(params)[-1]) if use_onnx: sess_op = ort.SessionOptions() if args.ort_logging: sess_op.log_verbosity_level = 0 sess_op.log_severity_level = 0 sess = ort.InferenceSession(model_name, sess_options=sess_op, providers=[args.provider]) ort_params = {} for input in sess.get_inputs(): ort_params[input.name] = test_inputs[input.name] try: pred_fw = sess.run(None, ort_params)[-1] except Exception as e: if any(input_dims): print( 'Error: custom input dim may not be compatible with onnx runtime' ) raise e else: import tensorflow as tf def load_tf_graph(model_name): with tf.io.gfile.GFile(model_name, 'rb') as f: graph_def = tf.compat.v1.GraphDef() graph_def.ParseFromString(f.read()) with tf.compat.v1.Graph().as_default() as graph: tf.graph_util.import_graph_def(graph_def) return graph graph = load_tf_graph(model_name) is_nhwc = False graph_ops = [] for op in graph.get_operations(): graph_ops.append(op.name) if 'Conv' in op.node_def.op: if 'NHWC' in op.get_attr('data_format').decode('utf-8'): is_nhwc = True graph_ops_set = set(graph_ops) tf_dict = {} for name in test_inputs.keys(): # graph.get_operations() adds 'import/' to the op name tf_name = f'import/{name}' if tf_name not in graph_ops_set: continue x = graph.get_tensor_by_name(f'{tf_name}:0') tf_input = test_inputs[name] # transpose input for NHWC model if tf_input.ndim == 4 and is_nhwc: tf_dict[x] = np.transpose(tf_input, (0, 2, 3, 1)) else: tf_dict[x] = tf_input # assume last node in graph is output # TODO: let user specify op name for output y = graph.get_tensor_by_name(f'{graph_ops[-1]}:0') with tf.compat.v1.Session(graph=graph) as sess: y_out = sess.run(y, feed_dict=tf_dict) pred_fw = y_out if not args.ort_run: if args.show_data: if hasattr(pred_fw, '__iter__') and not isinstance(pred_fw, (str, bytes)): print('Output Gold Data:') for idx, output in enumerate(pred_fw): print(f'Output {idx}: {output}') else: print(f'Output Gold Data:\n{pred_fw}\n') is_correct = check_correctness(pred_fw, pred_migx, args.tolerance, args.tolerance, args.argmax, args.verbose) verbose_string = ' Rerun with --verbose for detailed information.' \ if not args.verbose else '' if is_correct: print('PASSED: MIGraphX meets tolerance') else: print('FAILED: MIGraphX is not within tolerance.' + verbose_string) if __name__ == '__main__': main() ROCm-AMDMIGraphX-46524e8/tools/accuracy/requirements.txt000066400000000000000000000026211510465702400227570ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### numpy==1.26.4;python_version>="3.11" numpy==1.21.6;python_version<"3.11" onnxruntime==1.17.3 ROCm-AMDMIGraphX-46524e8/tools/api.py000066400000000000000000001227131510465702400170310ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import string import sys import re import runpy from functools import wraps from typing import Any, Callable, Dict, List, Optional, Tuple, Union from pathlib import Path type_map: Dict[str, Callable[['Parameter'], None]] = {} cpp_type_map: Dict[str, str] = {} functions: List['Function'] = [] cpp_classes: List['CPPClass'] = [] error_type = '' success_type = '' try_wrap = '' export_c_macro = 'MIGRAPHX_C_EXPORT' c_header_preamble: List[str] = [] c_api_body_preamble: List[str] = [] cpp_header_preamble: List[str] = [] def bad_param_error(msg: str): return 'throw std::runtime_error("{}")'.format(msg) class Template(string.Template): idpattern = '[_a-zA-Z0-9@]+' class Type: def __init__(self, name: str) -> None: self.name = name.strip() def is_pointer(self) -> bool: return self.name.endswith('*') def is_reference(self) -> bool: return self.name.endswith('&') def is_const(self) -> bool: return self.name.startswith('const ') def is_variadic(self): return self.name.startswith('...') def add_pointer(self) -> 'Type': return Type(self.name + '*') def add_reference(self): return Type(self.name + '&') def add_const(self) -> 'Type': return Type('const ' + self.name) def inner_type(self) -> Optional['Type']: i = self.name.find('<') j = self.name.rfind('>') if i > 0 and j > 0: return Type(self.name[i + 1:j]) else: return None def remove_generic(self) -> 'Type': i = self.name.find('<') j = self.name.rfind('>') if i > 0 and j > 0: return Type(self.name[0:i] + self.name[j + 1:]) else: return self def remove_pointer(self) -> 'Type': if self.is_pointer(): return Type(self.name[0:-1]) return self def remove_reference(self) -> 'Type': if self.is_reference(): return Type(self.name[0:-1]) return self def remove_const(self) -> 'Type': if self.is_const(): return Type(self.name[6:]) return self def basic(self) -> 'Type': return self.remove_pointer().remove_const().remove_reference() def decay(self) -> 'Type': t = self.remove_reference() if t.is_pointer(): return t else: return t.remove_const() def const_compatible(self, t: 'Type'): if t.is_const(): return self.add_const() return self def str(self) -> str: return self.name header_function = Template(''' ${export_c_macro} ${error_type} ${name}(${params}); ''') function_pointer_typedef = Template(''' typedef ${error_type} (*${fname})(${params}); ''') c_api_impl = Template(''' extern "C" ${error_type} ${name}(${params}) { ${va_start}auto api_error_result = ${try_wrap}([&] { ${body}; }); ${va_end}return api_error_result; } ''') class CFunction: def __init__(self, name: str) -> None: self.name = name self.params: List[str] = [] self.body: List[str] = [] self.va_start: List[str] = [] self.va_end: List[str] = [] def add_param(self, type: str, pname: str) -> None: self.params.append('{} {}'.format(type, pname)) def add_statement(self, stmt: str) -> None: self.body.append(stmt) def add_vlist(self, name: str) -> None: last_param = self.params[-1].split()[-1] self.va_start = [ 'va_list {};'.format(name), 'va_start({}, {});'.format(name, last_param) ] self.va_end = ['va_end({});'.format(name)] self.add_param('...', '') def substitute(self, form: Template, **kwargs) -> str: return form.substitute(error_type=error_type, try_wrap=try_wrap, name=self.name, params=', '.join(self.params), body=";\n ".join(self.body), va_start="\n ".join(self.va_start), va_end="\n ".join(self.va_end), **kwargs) def generate_header(self) -> str: return self.substitute(header_function, export_c_macro=export_c_macro) def generate_function_pointer(self, name: Optional[str] = None) -> str: return self.substitute(function_pointer_typedef, fname=name or self.name) def generate_body(self) -> str: return self.substitute(c_api_impl) class BadParam: def __init__(self, cond: str, msg: str) -> None: self.cond = cond self.msg = msg class Parameter: def __init__(self, name: str, type: str, optional: bool = False, returns: bool = False, virtual: bool = False, this: bool = False, hidden: bool = False) -> None: self.name = name self.type = Type(type) self.optional = optional self.cparams: List[Tuple[str, str]] = [] self.size_cparam = -1 self.size_name = '' self.read = '${name}' self.write = ['*${name} = ${result}'] self.cpp_read = '${name}' self.cpp_write = '${name}' self.returns = returns self.virtual = virtual self.this = this self.hidden = hidden self.bad_param_check: Optional[BadParam] = None self.virtual_read: Optional[List[str]] = None self.virtual_write: Optional[str] = None def get_name(self, prefix: Optional[str] = None) -> str: if prefix: return prefix + self.name else: return self.name def get_cpp_type(self) -> str: if self.type.str() in cpp_type_map: return cpp_type_map[self.type.basic().str()] elif self.type.basic().str() in cpp_type_map: return cpp_type_map[self.type.basic().str()] elif self.returns: return self.type.decay().str() else: return self.type.str() def substitute(self, s: str, prefix: Optional[str] = None, result: Optional[str] = None) -> str: ctype = None if len(self.cparams) > 0: ctype = Type(self.cparams[0][0]).basic().str() return Template(s).safe_substitute(name=self.get_name(prefix), type=self.type.str(), ctype=ctype or '', cpptype=self.get_cpp_type(), size=self.size_name, result=result or '') def add_param(self, t: Union[str, Type], name: Optional[str] = None) -> None: if not isinstance(t, str): t = t.str() self.cparams.append((t, name or self.name)) def add_size_param(self, name: Optional[str] = None) -> None: self.size_cparam = len(self.cparams) self.size_name = name or self.name + '_size' if self.returns: self.add_param('size_t *', self.size_name) else: self.add_param('size_t', self.size_name) def bad_param(self, cond: str, msg: str) -> None: self.bad_param_check = BadParam(cond, msg) def remove_size_param(self, name): p = None if self.size_cparam >= 0: p = self.cparams[self.size_cparam] del self.cparams[self.size_cparam] self.size_name = name return p def update(self) -> None: t = self.type.basic().str() g = self.type.remove_generic().basic().str() if t in type_map: type_map[t](self) elif g in type_map: type_map[g](self) else: if self.returns: self.add_param(self.type.remove_reference().add_pointer()) else: self.add_param(self.type.remove_reference()) if isinstance(self.write, str): raise ValueError("Error for {}: write cannot be a string".format( self.type.str())) def virtual_arg(self, prefix: Optional[str] = None) -> List[str]: read = self.virtual_read if not read and len(self.write) >= len(self.cparams): read = [ Template(w.partition('=')[2]).safe_substitute(result='${name}') for w in self.write ] if not read: raise ValueError("No virtual_read parameter provided for: " + self.type.str()) if isinstance(read, str): raise ValueError( "Error for {}: virtual_read cannot be a string".format( self.type.str())) return [self.substitute(r, prefix=prefix) for r in read] def virtual_param(self, prefix: Optional[str] = None) -> str: return self.substitute('${type} ${name}', prefix=prefix) def virtual_output_args(self, prefix: Optional[str] = None) -> List[str]: container_type = self.type.remove_generic().basic().str() decl_list: List[str] = [] container = (container_type == "std::vector" or container_type == "vector") for t, n, in self.cparams: if not decl_list and container: decl_list.append('{prefix}{n}.data()'.format(prefix=prefix or '', n=n)) else: decl_list.append('&{prefix}{n}'.format(prefix=prefix or '', n=n)) return decl_list def virtual_output_declarations(self, prefix: Optional[str] = None) -> List[str]: container_type = self.type.remove_generic().basic().str() container = (container_type == "std::vector" or container_type == "vector") decl_list: List[str] = [] for t, n, in self.cparams: if not decl_list and container: inner_t = self.type.inner_type() if inner_t: decl_list.append( 'std::array<{inner_t}, 1024> {prefix}{n};'.format( inner_t=inner_t.str(), prefix=prefix or '', n=n)) else: decl_list.append( 'std::remove_pointer_t<{type}> {prefix}{n}'.format( type=Type(t).str(), prefix=prefix or '', n=n)) decl_list[-1] += '=1024;' if container else ';' return decl_list def virtual_output(self, prefix: Optional[str] = None) -> str: write = self.virtual_write if not write: if '*' in self.read or '->' in self.read: write = Template(self.read).safe_substitute(name='(&${name})') else: write = self.read return self.substitute(write, prefix=prefix) def cpp_param(self, prefix: Optional[str] = None) -> str: return self.substitute('${cpptype} ${name}', prefix=prefix) def cpp_arg(self, prefix: Optional[str] = None) -> str: return self.substitute(self.cpp_read, prefix=prefix) def cpp_output_args(self, prefix: Optional[str] = None) -> List[str]: return [ '&{prefix}{n}'.format(prefix=prefix, n=n) for t, n in self.cparams ] def output_declarations(self, prefix: Optional[str] = None) -> List[str]: return [ '{type} {prefix}{n};'.format(type=Type(t).remove_pointer().str(), prefix=prefix, n=n) for t, n in self.cparams ] def output_args(self, prefix=None): return [ '&{prefix}{n};'.format(prefix=prefix, n=n) for t, n in self.cparams ] def cpp_output(self, prefix: Optional[str] = None) -> str: return self.substitute(self.cpp_write, prefix=prefix) def input(self, prefix: Optional[str] = None) -> str: return '(' + self.substitute(self.read, prefix=prefix) + ')' def outputs(self, result: Optional[str] = None) -> List[str]: return [self.substitute(w, result=result) for w in self.write] def add_to_cfunction(self, cfunction: CFunction) -> None: for t, name in self.cparams: if t.startswith('...'): cfunction.add_vlist(name) else: cfunction.add_param(self.substitute(t), self.substitute(name)) if self.bad_param_check: msg = 'Bad parameter {name}: {msg}'.format( name=self.name, msg=self.bad_param_check.msg) cfunction.add_statement('if ({cond}) {body}'.format( cond=self.substitute(self.bad_param_check.cond), body=bad_param_error(msg))) def template_var(s: str) -> str: return '${' + s + '}' def to_template_vars(params: List[Union[Any, Parameter]]) -> str: return ', '.join([template_var(p.name) for p in params]) class Function: def __init__(self, name: str, params: Optional[List[Parameter]] = None, shared_size: bool = False, returns: Optional[str] = None, invoke: Optional[str] = None, fname: Optional[str] = None, return_name: Optional[str] = None, virtual: bool = False, **kwargs) -> None: self.name = name self.params = params or [] self.shared_size = False self.cfunction: Optional[CFunction] = None self.fname = fname self.invoke = invoke or '${__fname__}($@)' self.return_name = return_name or 'out' self.returns = Parameter(self.return_name, returns, returns=True) if returns else None for p in self.params: p.virtual = virtual if self.returns: self.returns.virtual = virtual def share_params(self) -> None: if self.shared_size == True: size_param_name = 'size' size_type = Type('size_t') for param in self.params: p = param.remove_size_param(size_param_name) if p: size_type = Type(p[0]) self.params.append(Parameter(size_param_name, size_type.str())) def update(self) -> None: self.share_params() for param in self.params: param.update() if self.returns: self.returns.update() self.create_cfunction() def inputs(self) -> str: return ', '.join([p.input() for p in self.params]) # TODO: Shoule we remove Optional? def input_map(self) -> Dict[str, Optional[str]]: m: Dict[str, Optional[str]] = {} for p in self.params: m[p.name] = p.input() m['return'] = self.return_name m['@'] = self.inputs() m['__fname__'] = self.fname return m def get_invoke(self) -> str: return Template(self.invoke).safe_substitute(self.input_map()) def write_to_tmp_var(self) -> bool: if not self.returns: return False return len(self.returns.write) > 1 or self.returns.write[0].count( '${result}') > 1 def get_cfunction(self) -> CFunction: if self.cfunction: return self.cfunction raise Exception( "self.cfunction is None: self.update() needs to be called.") def create_cfunction(self) -> None: self.cfunction = CFunction(self.name) # Add the return as a parameter if self.returns: self.returns.add_to_cfunction(self.cfunction) # Add the input parameters for param in self.params: param.add_to_cfunction(self.cfunction) f: Optional[str] = self.get_invoke() # Write the assignments assigns = [] if self.returns: result = f if self.write_to_tmp_var() and f: f = 'auto&& api_result = ' + f result = 'api_result' else: f = None assigns = self.returns.outputs(result) if f: self.cfunction.add_statement(f) for assign in assigns: self.cfunction.add_statement(assign) cpp_class_template = Template(''' struct ${name} : handle_base<${ctype}, decltype(&${destroy}), ${destroy}> { ${name}(${ctype} p, bool own = true) : m_handle(nullptr) { this->set_handle(p, own); } ${constructors} ${methods} }; ''') cpp_class_method_template = Template(''' ${return_type} ${name}(${params}) const { ${outputs} this->call_handle(${args}); return ${result}; } ''') cpp_class_void_method_template = Template(''' void ${name}(${params}) const { this->call_handle(${args}); } ''') cpp_class_constructor_template = Template(''' ${name}(${params}) : m_handle(nullptr) { m_handle = this->make_handle(${args}); } ''') class CPPMember: def __init__(self, name: str, function: Function, prefix: str, method: bool = True) -> None: self.name = name self.function = function self.prefix = prefix self.method = method def get_function_params(self) -> List[Union[Any, Parameter]]: if self.method: return self.function.params[1:] else: return self.function.params def get_args(self) -> str: output_args = [] if self.function.returns: output_args = self.function.returns.cpp_output_args(self.prefix) if not self.function.cfunction: raise Exception('self.function.update() must be called') return ', '.join( ['&{}'.format(self.function.cfunction.name)] + output_args + [p.cpp_arg(self.prefix) for p in self.get_function_params()]) def get_params(self) -> str: return ', '.join( [p.cpp_param(self.prefix) for p in self.get_function_params()]) def get_return_declarations(self) -> str: if self.function.returns: return '\n '.join([ d for d in self.function.returns.output_declarations(self.prefix) ]) else: return '' def get_result(self): return self.function.returns.input(self.prefix) def generate_method(self) -> str: if not self.function.cfunction: raise Exception('self.function.update() must be called') if self.function.returns: return_type = self.function.returns.get_cpp_type() return cpp_class_method_template.safe_substitute( return_type=return_type, name=self.name, cfunction=self.function.cfunction.name, result=self.function.returns.cpp_output(self.prefix), params=self.get_params(), outputs=self.get_return_declarations(), args=self.get_args(), success=success_type) else: return cpp_class_void_method_template.safe_substitute( name=self.name, cfunction=self.function.cfunction.name, params=self.get_params(), args=self.get_args(), success=success_type) def generate_constructor(self, name: str) -> str: if not self.function.cfunction: raise Exception('self.function.update() must be called') return cpp_class_constructor_template.safe_substitute( name=name, cfunction=self.function.cfunction.name, params=self.get_params(), args=self.get_args(), success=success_type) class CPPClass: def __init__(self, name: str, ctype: str) -> None: self.name = name self.ctype = ctype self.constructors: List[CPPMember] = [] self.methods: List[CPPMember] = [] self.prefix = 'p' def add_method(self, name: str, f: Function) -> None: self.methods.append(CPPMember(name, f, self.prefix, method=True)) def add_constructor(self, name: str, f: Function) -> None: self.constructors.append(CPPMember(name, f, self.prefix, method=True)) def generate_methods(self) -> str: return '\n '.join([m.generate_method() for m in self.methods]) def generate_constructors(self) -> str: return '\n '.join( [m.generate_constructor(self.name) for m in self.constructors]) def substitute(self, s: Union[string.Template, str], **kwargs) -> str: t = string.Template(s) if isinstance(s, str) else s destroy = self.ctype + '_destroy' return t.safe_substitute(name=self.name, ctype=self.ctype, destroy=destroy, **kwargs) def generate(self) -> str: return self.substitute( cpp_class_template, constructors=self.substitute(self.generate_constructors()), methods=self.substitute(self.generate_methods())) def params(virtual: Optional[Dict[str, str]] = None, **kwargs) -> List[Parameter]: result = [] v: Dict[str, str] = virtual or {} for name in v: result.append(Parameter(name, v[name])) for name in kwargs: result.append(Parameter(name, kwargs[name])) return result gparams = params def add_function(name: str, *args, **kwargs) -> Function: f = Function(name, *args, **kwargs) functions.append(f) return f def once(f: Callable) -> Any: @wraps(f) def decorated(*args, **kwargs): if not decorated.has_run: decorated.has_run = True return f(*args, **kwargs) d: Any = decorated d.has_run = False return d @once def process_functions() -> None: for f in functions: f.update() def generate_lines(p: List[str]) -> str: return '\n'.join(p) def generate_c_header() -> str: process_functions() return generate_lines( c_header_preamble + [f.get_cfunction().generate_header() for f in functions]) def generate_c_api_body() -> str: process_functions() return generate_lines( c_api_body_preamble + [f.get_cfunction().generate_body() for f in functions]) def generate_cpp_header() -> str: process_functions() return generate_lines(cpp_header_preamble + [c.generate() for c in cpp_classes]) c_type_map: Dict[str, Type] = {} def cwrap(name: str, c_type: Optional[str] = None) -> Callable: def with_cwrap(f): type_map[name] = f if c_type: c_type_map[name] = Type(c_type) @wraps(f) def decorated(*args, **kwargs): return f(*args, **kwargs) return decorated return with_cwrap handle_typedef = Template(''' typedef struct ${ctype} * ${ctype}_t; typedef const struct ${ctype} * const_${ctype}_t; ''') handle_definition = Template(''' extern "C" struct ${ctype}; struct ${ctype} { template ${ctype}(Ts&&... xs) : object(std::forward(xs)...) // NOLINT(readability-redundant-member-init) {} ${cpptype} object; }; ''') handle_preamble = ''' template> static Target* object_cast(U* x) { return reinterpret_cast(x); } template> static const Target* object_cast(const U* x) { return reinterpret_cast(x); } template > static Target* allocate(Ts&&... xs) { if constexpr(std::is_aggregate{}) return new Target{std::forward(xs)...}; // NOLINT else return new Target(std::forward(xs)...); // NOLINT } template static void destroy(T* x) { delete x; // NOLINT } // TODO: Move to interface preamble template struct manage_generic_ptr { manage_generic_ptr() = default; manage_generic_ptr(std::nullptr_t) { } manage_generic_ptr(void* pdata, const char* obj_tname, C pcopier, D pdeleter) : data(nullptr), obj_typename(obj_tname), copier(pcopier), deleter(pdeleter) { copier(&data, pdata); } manage_generic_ptr(const manage_generic_ptr& rhs) : data(nullptr), obj_typename(rhs.obj_typename), copier(rhs.copier), deleter(rhs.deleter) { if(copier) copier(&data, rhs.data); } manage_generic_ptr(manage_generic_ptr&& other) noexcept : data(other.data), obj_typename(other.obj_typename), copier(other.copier), deleter(other.deleter) { other.data = nullptr; other.obj_typename = ""; other.copier = nullptr; other.deleter = nullptr; } manage_generic_ptr& operator=(manage_generic_ptr rhs) { std::swap(data, rhs.data); std::swap(obj_typename, rhs.obj_typename); std::swap(copier, rhs.copier); std::swap(deleter, rhs.deleter); return *this; } ~manage_generic_ptr() { if(data != nullptr) deleter(data); } void* data = nullptr; const char* obj_typename = ""; C copier = nullptr; D deleter = nullptr; }; ''' cpp_handle_preamble = ''' template struct handle_base { template void make_handle(F f, Ts&&... xs) { T* result = nullptr; auto e = F(&result, std::forward(xs)...); if (e != ${success}) throw std::runtime_error("Failed to call function"); set_handle(result); } template void call_handle(F f, Ts&&... xs) { auto e = F(this->get_handle_ptr(), std::forward(xs)...); if (e != ${success}) throw std::runtime_error("Failed to call function"); } const std::shared_ptr& get_handle() const { return m_handle; } T* get_handle_ptr() const { assert(m_handle != nullptr); return get_handle().get(); } void set_handle(T* ptr, bool own = true) { if (own) m_handle = std::shared_ptr{ptr, deleter}; else m_handle = std::shared_ptr{ptr, [](T*) {}}; } protected: std::shared_ptr m_handle; }; ''' @once def add_handle_preamble() -> None: c_api_body_preamble.append(handle_preamble) cpp_header_preamble.append( string.Template(cpp_handle_preamble).substitute(success=success_type)) def add_handle(name: str, ctype: str, cpptype: str, destroy: Optional[str] = None, ref=False, skip_def=False) -> None: opaque_type = ctype + '_t' const_opaque_type = 'const_' + opaque_type def handle_wrap(p: Parameter): t = Type(opaque_type) if p.type.is_const(): t = Type('const_' + opaque_type) # p.read = 'object_cast<${ctype}>(&(${name}))' if p.virtual: p.add_param(t) elif p.returns: p.add_param(t.add_pointer()) else: p.add_param(t) p.bad_param('${name} == nullptr', 'Null pointer') if p.type.is_reference(): p.virtual_read = ['object_cast<${ctype}>(&(${name}))'] p.cpp_write = '${cpptype}(${name}, false)' p.write = ['*${name} = object_cast<${ctype}>(&(${result}))'] elif p.type.is_pointer(): p.virtual_read = ['object_cast<${ctype}>(${result})'] p.cpp_write = '${cpptype}(${name}, false)' p.write = ['*${name} = object_cast<${ctype}>(${result})'] else: p.virtual_read = ['object_cast<${ctype}>(&(${name}))'] p.cpp_write = '${cpptype}(${name})' p.write = ['*${name} = allocate<${ctype}>(${result})'] if skip_def: p.read = '*${name}' else: p.read = '${name}->object' p.cpp_read = '${name}.get_handle_ptr()' type_map[cpptype] = handle_wrap if not ref: add_function(destroy or ctype + '_' + 'destroy', params({name: opaque_type}), fname='destroy') add_function(ctype + '_' + 'assign_to', params(output=opaque_type, input=const_opaque_type), invoke='*output = *input') add_handle_preamble() c_header_preamble.append(handle_typedef.substitute(locals())) if not skip_def: c_api_body_preamble.append(handle_definition.substitute(locals())) @cwrap('std::vector') def vector_c_wrap(p: Parameter) -> None: inner = p.type.inner_type() # Not a generic type if not inner: return if inner.str() in c_type_map: inner = c_type_map[inner.str()] t = inner.add_pointer() if p.type.is_reference(): if p.type.is_const(): t = t.add_const() if p.returns: if p.type.is_reference(): p.add_param(t.add_pointer()) p.add_size_param() p.bad_param('${name} == nullptr or ${size} == nullptr', 'Null pointer') elif p.virtual: p.add_param(t) p.add_size_param() p.bad_param('${name} == nullptr or ${size} == nullptr', 'Null pointer') p.virtual_write = '{${name}.begin(), ${name}.begin()+${size}}; // cppcheck-suppress returnDanglingLifetime' else: p.add_param(t) p.bad_param('${name} == nullptr', 'Null pointer') else: p.add_param(t) p.add_size_param() p.bad_param('${name} == nullptr and ${size} != 0', 'Null pointer') p.read = '${type}(${name}, ${name}+${size})' p.cpp_write = '${type}(${name}, ${name}+${size})' p.virtual_read = ['${name}.data()', '${name}.size()'] if p.type.is_reference(): p.write = [ '*${name} = ${result}.data()', '*${size} = ${result}.size()' ] else: p.write = ['std::copy(${result}.begin(), ${result}.end(), ${name})'] @cwrap('std::string', 'char*') def string_c_wrap(p: Parameter) -> None: t = Type('char*') if p.returns: if p.type.is_reference(): p.add_param(t.add_pointer()) p.bad_param('${name} == nullptr', 'Null pointer') else: p.add_param(t) p.add_param('size_t', p.name + '_size') p.bad_param('${name} == nullptr', 'Null pointer') else: p.add_param(t) p.bad_param('${name} == nullptr', 'Null pointer') p.read = '${type}(${name})' p.cpp_write = '${type}(${name})' p.virtual_read = ['${name}.c_str()'] if p.type.is_reference(): p.write = ['*${name} = ${result}.c_str()'] else: p.write = [ 'auto* it = std::copy_n(${result}.begin(), std::min(${result}.size(), ${name}_size - 1), ${name});' '*it = \'\\0\'' ] class Handle: def __init__(self, name: str, ctype: str, cpptype: str, **kwargs) -> None: self.name = name self.ctype = ctype self.cpptype = cpptype self.opaque_type = self.ctype + '_t' self.cpp_class = CPPClass(name, ctype) add_handle(name, ctype, cpptype, **kwargs) cpp_type_map[cpptype] = name def cname(self, name: str) -> str: return self.ctype + '_' + name def substitute(self, s: str, **kwargs) -> str: return Template(s).safe_substitute(name=self.name, ctype=self.ctype, cpptype=self.cpptype, opaque_type=self.opaque_type, **kwargs) def constructor(self, name: str, params: Optional[List[Parameter]] = None, fname: Optional[str] = None, invoke: Optional[str] = None, **kwargs) -> 'Handle': create = self.substitute('allocate<${cpptype}>($@)') if fname: create = self.substitute('allocate<${cpptype}>(${fname}($@))', fname=fname) f = add_function(self.cname(name), params=params, invoke=invoke or create, returns=self.cpptype + '*', return_name=self.name, **kwargs) self.cpp_class.add_constructor(name, f) return self def method(self, name: str, params: Optional[List[Parameter]] = None, fname: Optional[str] = None, invoke: Optional[str] = None, cpp_name: Optional[str] = None, const: Optional[bool] = None, **kwargs) -> 'Handle': cpptype = self.cpptype if const: cpptype = Type(cpptype).add_const().str() p = Parameter(self.name, cpptype) args = to_template_vars(params or []) f = add_function(self.cname(name), params=[p] + (params or []), invoke=invoke or self.substitute('${var}.${fname}(${args})', var=template_var(self.name), fname=fname or name, args=args), **kwargs) self.cpp_class.add_method(cpp_name or name, f) return self def function(self, name, params=None, **kwargs): add_function(self.cname(name), params=params, **kwargs) return self def add_cpp_class(self) -> None: cpp_classes.append(self.cpp_class) interface_handle_definition = Template(''' extern "C" struct ${ctype}; struct ${ctype} { template ${ctype}(void* p, ${copier} c, ${deleter} d, const char* obj_typename, Ts&&... xs) : object_ptr(p, obj_typename, c, d), xobject(std::forward(xs)...) {} manage_generic_ptr<${copier}, ${deleter}> object_ptr = nullptr; ${cpptype} xobject; ${functions} }; ''') c_api_virtual_impl = Template(''' ${return_type} ${name}(${params}) const { if (${fname} == nullptr) throw std::runtime_error("${name} function is missing."); ${output_decls} std::array exception_msg; exception_msg.front() = '\\0'; auto api_error_result = ${fname}(${args}); if (api_error_result != ${success}) { const std::string exception_str(exception_msg.data()); throw std::runtime_error("Error in ${name} of: " + std::string(object_ptr.obj_typename) + ": " + exception_str); } return ${output}; } ''') def generate_virtual_impl(f: Function, fname: str) -> str: success = success_type name = f.name return_type = 'void' output_decls = '' output = '' largs = [] lparams = [] if f.returns: return_type = f.returns.type.str() output_decls = '\n'.join(f.returns.virtual_output_declarations()) largs += f.returns.virtual_output_args() output = f.returns.virtual_output() largs += [arg for p in f.params for arg in p.virtual_arg()] lparams += [ p.virtual_param() for p in f.params if not (p.this or p.hidden) ] args = ', '.join(largs) params = ', '.join(lparams) return c_api_virtual_impl.substitute(locals()) class Interface(Handle): def __init__(self, name: str, ctype: str, cpptype: str) -> None: super().__init__(name, ctype, cpptype, skip_def=True) self.ifunctions: List[Function] = [] self.members: List[str] = [] def mname(self, name: str) -> str: return name + "_f" def constructor( # type: ignore self, name: str, params: Optional[List[Parameter]] = None, **kwargs) -> 'Interface': create = self.substitute('allocate<${opaque_type}>($@)') initial_params = gparams(obj='void*', c=self.cname('copy'), d=self.cname('delete')) add_function(self.cname(name), params=initial_params + (params or []), invoke=create, returns=self.opaque_type, return_name=self.name, **kwargs) return self def method(self, *args, **kwargs) -> 'Interface': super().method(*args, **kwargs) return self def virtual(self, name: str, params: Optional[List[Parameter]] = None, const: Optional[bool] = None, **kwargs) -> 'Interface': # Add this parameter to the function this = Parameter('obj', 'void*', this=True) this.virtual_read = ['object_ptr.data'] exception_msg = Parameter('exception_msg', 'char*', hidden=True) exception_msg.virtual_read = ['${name}.data()'] exception_msg_size = Parameter('exception_msg_size', 'size_t', hidden=True) exception_msg_size.virtual_read = ['exception_msg.size()'] f = Function(name, params=[this, exception_msg, exception_msg_size] + (params or []), virtual=True, **kwargs) self.ifunctions.append(f) add_function(self.cname('set_' + name), params=gparams(obj=self.opaque_type, input=self.cname(name)), invoke='${{obj}}->{name} = ${{input}}'.format( name=self.mname(name))) return self def generate_function(self, f: Function): cname = self.cname(f.name) mname = self.mname(f.name) function = generate_virtual_impl(f, fname=mname) return f"{cname} {mname} = nullptr;{function}" def generate(self): required_functions = [ Function('copy', params=gparams(out='void**', input='void*'), virtual=True), Function('delete', params=gparams(input='void*'), virtual=True) ] for f in self.ifunctions + required_functions: f.update() c_header_preamble.extend([ f.get_cfunction().generate_function_pointer(self.cname(f.name)) for f in self.ifunctions + required_functions ]) function_list = [self.generate_function(f) for f in self.ifunctions] ctype = self.ctype cpptype = self.cpptype copier = self.cname('copy') deleter = self.cname('delete') functions = '\n'.join(function_list) c_api_body_preamble.append( interface_handle_definition.substitute(locals())) def handle(ctype: str, cpptype: str, name: Optional[str] = None, ref: Optional[bool] = None) -> Callable: def with_handle(f): n = name or f.__name__ h = Handle(n, ctype, cpptype, ref=ref) f(h) h.add_cpp_class() @wraps(f) def decorated(*args, **kwargs): return f(*args, **kwargs) return decorated return with_handle def interface(ctype: str, cpptype: str, name: Optional[str] = None) -> Callable: def with_interface(f): n = name or f.__name__ h = Interface(n, ctype, cpptype) f(h) h.generate() @wraps(f) def decorated(*args, **kwargs): return f(*args, **kwargs) return decorated return with_interface def template_eval(template, **kwargs): start = '<%' end = '%>' escaped = (re.escape(start), re.escape(end)) mark = re.compile('%s(.*?)%s' % escaped, re.DOTALL) for key in kwargs: exec('%s = %s' % (key, kwargs[key])) for item in mark.findall(template): e = eval(item.strip()) template = template.replace(start + item + end, str(e)) return template def run(path: Union[Path, str]) -> str: return template_eval(open(path).read()) if __name__ == "__main__": sys.modules['api'] = sys.modules['__main__'] runpy.run_path(sys.argv[1]) if len(sys.argv) > 2: r = run(sys.argv[2]) sys.stdout.write(r) else: sys.stdout.write(generate_c_header()) sys.stdout.write(generate_c_api_body()) # sys.stdout.write(generate_cpp_header()) ROCm-AMDMIGraphX-46524e8/tools/api/000077500000000000000000000000001510465702400164515ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/api/api.cpp000066400000000000000000000270371510465702400177370ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { #ifdef MIGRAPHX_BUILD_TESTING static thread_local bool disable_exception_catch = false; // NOLINT extern "C" MIGRAPHX_C_EXPORT void migraphx_test_private_disable_exception_catch(bool b) { disable_exception_catch = b; } #endif template migraphx_status try_(F f, bool output = true, source_location llc = source_location::current()) // NOLINT { #ifdef MIGRAPHX_BUILD_TESTING if(disable_exception_catch) { f(); } else { #endif try { f(); } catch(const migraphx::exception& ex) { if(output) std::cerr << llc.function_name() << ": Error: " << ex.what() << std::endl; if(ex.error > 0) return migraphx_status(ex.error); else return migraphx_status_unknown_error; } catch(const std::exception& ex) { if(output) std::cerr << llc.function_name() << ": Error: " << ex.what() << std::endl; return migraphx_status_unknown_error; } catch(...) { return migraphx_status_unknown_error; } #ifdef MIGRAPHX_BUILD_TESTING } #endif return migraphx_status_success; } static shape::type_t to_shape_type(migraphx_shape_datatype_t t) { switch(t) { case migraphx_shape_tuple_type: return shape::tuple_type; case migraphx_shape_fp4x2_type: return shape::fp4x2_type; #define MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT(x, y) \ case migraphx_shape_##x: return shape::x; MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT) #undef MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT } MIGRAPHX_THROW(migraphx_status_bad_param, "Unknown type"); } static migraphx_shape_datatype_t to_shape_type(shape::type_t t) { switch(t) { case shape::tuple_type: return migraphx_shape_tuple_type; case shape::fp4x2_type: return migraphx_shape_fp4x2_type; #define MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT(x, y) \ case shape::x: return migraphx_shape_##x; MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT) #undef MIGRAPHX_DETAIL_SHAPE_CASE_CONVERT } MIGRAPHX_THROW(migraphx_status_bad_param, "Unknown type"); } template static auto to_obj_vector(const T* x, std::size_t n) { std::vectorobject)> result; std::transform(x, x + n, std::back_inserter(result), [&](auto&& y) { return y->object; }); return result; } template static auto to_objptr_vector(const U* x, std::size_t n) { std::vector result; std::transform( x, x + n, std::back_inserter(result), [&](auto&& y) { return std::addressof(y->object); }); return result; } static target get_target(const std::string& name) { return make_target(name); } static void set_offload_copy(compile_options& options, bool value) { options.offload_copy = value; } static void set_fast_math(compile_options& options, bool value) { options.fast_math = value; } static void set_exhaustive_tune_flag(compile_options& options, bool value) { options.exhaustive_tune = value; } static void set_file_format(file_options& options, const char* format) { options.format = format; } static void set_default_dim_value(onnx_options& options, size_t value) { options.default_dim_value = value; } static void set_default_dyn_dim_value(onnx_options& options, const shape::dynamic_dimension& dd) { options.default_dyn_dim_value = dd; } static void set_default_loop_iterations(onnx_options& options, int64_t value) { options.max_loop_iterations = value; } static void set_external_data_path(onnx_options& options, const char* external_data_path) { options.external_data_path = std::string(external_data_path); } static void set_limit_loop_iterations(onnx_options& options, int64_t value) { options.limit_max_iterations = value; } static void set_nhwc(tf_options& options, bool is_nhwc) { options.is_nhwc = is_nhwc; } static void set_default_dim_value(tf_options& options, size_t value) { options.batch_size = value; } static void set_input_parameter_shape(onnx_options& options, const char* name, std::vector dims) { options.map_input_dims[std::string(name)] = std::move(dims); } static void set_dyn_input_parameter_shape(onnx_options& options, const char* name, std::vector dyn_dims) { options.map_dyn_input_dims[std::string(name)] = std::move(dyn_dims); } static void set_input_parameter_shape(tf_options& options, const char* name, std::vector dims) { options.map_input_dims[std::string(name)] = std::move(dims); } static void set_output_names(tf_options& options, std::vector names) { options.output_node_names = std::vector(names.begin(), names.end()); } static std::vector run_async(program& p, const parameter_map& params, void* s, std::string_view name) { execution_environment exec_env{any_ptr(s, name), true}; return p.eval(params, exec_env); } template static std::vector get_names(const std::unordered_map& m) { std::vector result; std::transform( m.begin(), m.end(), std::back_inserter(result), [](auto&& p) { return p.first.c_str(); }); return result; } template static std::set make_set(const T* x, std::size_t n) { return {x, x + n}; } static void quantize_fp16_with_op_names(program& prog, std::vector& names) { if(names.empty()) { names = {"all"}; } migraphx::quantize_fp16(prog, names); } static void quantize_bf16_with_op_names(program& prog, std::vector& names) { if(names.empty()) { names = {"all"}; } migraphx::quantize_bf16(prog, names); } struct quantize_int8_options { std::vector calibration = {}; std::unordered_set op_names = {}; }; static void add_op_name(quantize_int8_options& options, const char* name) { options.op_names.insert(name); } static void add_calibration_data(quantize_int8_options& options, parameter_map& data) { options.calibration.push_back(data); } static void quantize_int8_wrap(program& prog, const target& t, quantize_int8_options& options) { if(options.op_names.empty()) { options.op_names = {"dot", "convolution"}; } migraphx::quantize_int8(prog, t, options.calibration, options.op_names); } struct quantize_fp8_options { std::vector calibration = {}; }; static void add_calibration_data(quantize_fp8_options& options, parameter_map& data) { options.calibration.push_back(data); } static void quantize_fp8_wrap(program& prog, const target& t, quantize_fp8_options& options) { migraphx::quantize_fp8(prog, t, options.calibration); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" #endif static operation create_op(const char* name, const char* attributes, va_list vlist) { std::string sattributes = attributes == nullptr ? "" : attributes; std::vector buffer(sattributes.size() * 2); std::vsnprintf(buffer.data(), buffer.size(), sattributes.c_str(), vlist); value v = value::object{}; if(attributes != nullptr) { v = from_json_string(convert_to_json(std::string(buffer.data()))); } auto op = make_op(name, v); return op; } #ifdef __clang__ #pragma clang diagnostic pop #endif template static bool equal(const T& x, const T& y) { return x == y; } static std::vector run(program& p, const parameter_map& params) { return p.eval(params); } static std::vector get_output_shapes(program& p) { return p.get_output_shapes(); } static void print_program(const program& p) { std::cout << p << std::endl; } static void print_module(const module& m) { std::cout << m << std::endl; } static migraphx::instruction_ref add_allocation(module& m, const migraphx::shape& s) { return m.add_instruction(migraphx::make_op("allocate", {{"shape", migraphx::to_value(s)}}), {}); } struct experimental_custom_op { std::string name; experimental_custom_op() = default; experimental_custom_op(std::string pname) : name(std::move(pname)) {} }; template struct custom_operation { template static auto reflect(Self&, F) { return pack(); } value attributes() const { return {{"custom_op", true}, {"target", op.runs_on_offload_target() ? "gpu" : "cpu"}}; } CustomOp op; std::string name() const { return op.xobject.name; } shape compute_shape(std::vector inputs) const { return op.compute_shape(std::move(inputs)); } // TODO: Compute method with module_args argument compute(migraphx::context ctx, migraphx::shape output_shape, std::vector inputs) const { return op.compute(std::move(ctx), std::move(output_shape), std::move(inputs)); } std::ptrdiff_t output_alias(std::vector inputs) const { auto alias_vec = op.output_alias(std::move(inputs)); // TODO: For now, only support one output alias if(alias_vec.empty()) { return -1; } if(alias_vec.size() > 1) { MIGRAPHX_THROW("Currently, CustomOps in MIGraphX only supports one output_alias"); } return alias_vec.front(); } bool runs_on_offload_target() const { return op.runs_on_offload_target(); } }; template static void register_custom_op(const CustomOp& op) { register_op(custom_operation{op}); } static migraphx::context get_context(const program& p) { return p.get_context(); } } // namespace migraphx <% generate_c_api_body() %> ROCm-AMDMIGraphX-46524e8/tools/api/migraphx.h000066400000000000000000000052371510465702400204500ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_C_API_MIGRAPHX_H #define MIGRAPHX_GUARD_C_API_MIGRAPHX_H #include #include #include #include // Add new types here // clang-format off #define MIGRAPHX_SHAPE_VISIT_TYPES(m) \ m(bool_type, bool) \ m(half_type, half) \ m(float_type, float) \ m(double_type, double) \ m(uint8_type, uint8_t) \ m(int8_type, int8_t) \ m(uint16_type, uint16_t) \ m(int16_type, int16_t) \ m(int32_type, int32_t) \ m(int64_type, int64_t) \ m(uint32_type, uint32_t) \ m(uint64_type, uint64_t) \ m(fp8e4m3fnuz_type, migraphx::fp8::fp8e4m3fnuz) \ m(fp8e4m3fn_type, migraphx::fp8::fp8e4m3fn) \ m(fp8e5m2_type, migraphx::fp8::fp8e5m2) \ m(bf16_type, bf16) \ m(fp8e5m2fnuz_type, migraphx::fp8::fp8e5m2fnuz) // clang-format on #ifdef __cplusplus extern "C" { #endif // return code, more to be added later typedef enum { migraphx_status_success = 0, migraphx_status_bad_param = 1, migraphx_status_unknown_target = 3, migraphx_status_unknown_error = 4, } migraphx_status; #define MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES(x, t) migraphx_shape_##x, /// An enum to represent the different data type inputs typedef enum { migraphx_shape_tuple_type, migraphx_shape_fp4x2_type, MIGRAPHX_SHAPE_VISIT_TYPES(MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES) } migraphx_shape_datatype_t; #undef MIGRAPHX_SHAPE_GENERATE_ENUM_TYPES <% generate_c_header() %> #ifdef __cplusplus } #endif #endif ROCm-AMDMIGraphX-46524e8/tools/build_and_test_onnxrt.sh000077500000000000000000000057451510465702400226420ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### set -e ulimit -c unlimited # Copy these over in local runs but silence them in CI cp tools/pai_test_launcher.sh /onnxruntime/tools/ci_build/github/pai/pai_test_launcher.sh 2>/dev/null || : [ -f tools/pai_provider_test_launcher.sh ] && cp tools/pai_provider_test_launcher.sh /onnxruntime/tools/ci_build/github/pai/pai_provider_test_launcher.sh cd /onnxruntime pip3 install -r requirements-dev.txt # Add newer cmake to the path export PATH="/opt/cmake/bin:$PATH" export CXXFLAGS="-D__HIP_PLATFORM_AMD__=1 -w" echo "ONNX Runtime log..." git log -1 ./build.sh --config Release --cmake_extra_defines CMAKE_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ --update --build --build_wheel --parallel --cmake_extra_defines ONNXRUNTIME_VERSION=$(cat ./VERSION_NUMBER) --skip_tests --rocm_home /opt/rocm --use_migraphx --migraphx_home /opt/rocm --rocm_version=`cat /opt/rocm/.info/version-dev` --allow_running_as_root cd build/Linux/Release #Add test launcher for onnxrt tests echo 'InferenceSessionTests.CheckRunProfilerWithSessionOptions' >> ../../../tools/ci_build/github/pai/migraphx-excluded-tests.txt echo 'InferenceSessionTests.CheckRunProfilerWithSessionOptions2' >> ../../../tools/ci_build/github/pai/migraphx-excluded-tests.txt echo 'InferenceSessionTests.Test3LayerNestedSubgraph' >> ../../../tools/ci_build/github/pai/migraphx-excluded-tests.txt echo 'InferenceSessionTests.Test2LayerNestedSubgraph' >> ../../../tools/ci_build/github/pai/migraphx-excluded-tests.txt ../../../tools/ci_build/github/pai/pai_test_launcher.sh || (gdb ./onnxruntime_test_all core -batch -ex bt && exit 1) ../../../tools/ci_build/github/pai/pai_provider_test_launcher.sh || (gdb ./onnxruntime_provider_test core -batch -ex bt && exit 1) ROCm-AMDMIGraphX-46524e8/tools/check_stamped.py000066400000000000000000000103321510465702400210430ustar00rootroot00000000000000#!/usr/bin/env python3 ##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### #################################### GUIDE ########################################## ##################################################################################### # This Check_Stamped script is triggered by the Github workflows when a pull request is created. # It works by generating a list of files that have been modified/created between the current # up-to-date Develop Branch from MIGraphx and the Pull Request Branch via GIT DIFF. # The script then checks that each file has the current year in the license stamp, # with the assumption being that any modifications/creations will need to be stamped to the # year that the modification/creation was made. ##################################################################################### import sys import argparse import os from stamp_status import StampStatus, stamp_check, current_year from git_tools import get_changed_files SUPPORTED_FILE_TYPES = (".cpp", ".hpp", ".h", ".ipynb", ".py", ".txt", ".sh", ".bsh", "LICENSE", ".cmake") IGNORED_FILES = ("digits.txt", "Dockerfile", "Jenkinsfile", "imagenet_classes.txt", '') def check_file(file, debug=False): try: with open(file, 'r') as f: content = f.read() except (OSError, UnicodeDecodeError) as e: if debug: print(f"{file}: Skipping ({e})") return StampStatus.ERROR_READING year = current_year() return stamp_check(file, year, content, debug=debug) def print_status(status): files = status.files if files: print(f"\n{'Error' if status.error else 'Warning'}: " f"\n{len(files)} {status.label} files:\n{files}") return status.error return False def main(args): files = get_changed_files(args.against) if args.debug: print(f"Changed files vs {args.against}: {files}") for file in files: filename = os.path.basename(file) # Assign appropriate StampStatus based on filename or content if filename in IGNORED_FILES: status = StampStatus.IGNORED elif not filename.endswith(SUPPORTED_FILE_TYPES): status = StampStatus.UNSUPPORTED else: status = check_file(file, args.debug) status.files.append(file) has_errors = any([ print_status(StampStatus.UNSUPPORTED), print_status(StampStatus.IGNORED), print_status(StampStatus.NOT_STAMPED), print_status(StampStatus.WRONG_YEAR) ]) if has_errors: sys.exit(1) else: print("Success: All files properly stamped.") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('against', default='origin/develop', nargs='?') parser.add_argument("-d", "--debug", action="store_true", help="Enable debug output") args = parser.parse_args() main(args) ROCm-AMDMIGraphX-46524e8/tools/compile_analysis.py000066400000000000000000000125001510465702400216030ustar00rootroot00000000000000import pandas as pd import re import plotly.graph_objects as go from plotly.subplots import make_subplots import argparse def read_compile_log(file_path): # Regex pattern to match lines with "value: __ ms" pattern = r'([\w: ]+):\s*([\d.]+(?:e[+-]?\d+)?)ms' data = [] # Read the file and find matches with open(file_path, 'r') as file: for line in file: match = re.search(pattern, line) if match: operation = match.group(1).strip() try: value = float(match.group(2)) data.append({'Operation': operation, 'Value': value}) except: print(f"Invalid Float: {match.group(2)}") return data def filter_data(data, quantile): # Create a DataFrame from the list of dictionaries df = pd.DataFrame(data) df['Line Number'] = df.index df['Operation: Line Number'] = df.apply( lambda x: x["Operation"] + ": " + str(x['Line Number']), axis=1) df['Cumulative Sum'] = df['Value'].cumsum() print( f"Only included the top {round(1-quantile, 2)}% time-intensive operations" ) top_operation_time = df['Value'].quantile(quantile) df = df[df['Value'] >= top_operation_time] return df def plot_compile_analysis(df, output_path): df_sorted_by_time = df.sort_values(by='Value', ascending=False) df_sorted_by_total_time = df.groupby( 'Operation')['Value'].sum().sort_values(ascending=False).reset_index() df_sorted_by_avg_time = df.groupby('Operation')['Value'].mean( ).sort_values(ascending=False).reset_index() df_counts = df['Operation'].value_counts().reset_index() fig = make_subplots(rows=2, cols=4, row_heights=[0.4, 0.6], specs=[[{ 'type': 'bar' }, { 'type': 'bar' }, { 'type': 'bar' }, { 'type': 'bar' }], [{ 'type': 'scatter', 'colspan': 4 }, None, None, None]], subplot_titles=('Time Taken per Line', 'Total Time Taken per Operation', 'Avg Time Taken per Operation', 'Number of times Operation called', 'Compile Time Series Graph'), vertical_spacing=0.25) fig.add_trace(go.Bar( x=df_sorted_by_time['Operation: Line Number'], y=df_sorted_by_time['Value'], name='Time Taken per Line', marker=dict(color='red'), hoverinfo='x+y', ), row=1, col=1) fig.add_trace(go.Bar(x=df_sorted_by_total_time['Operation'], y=df_sorted_by_total_time['Value'], name='Total Time Taken per Operation', marker=dict(color='green'), hoverinfo='x+y'), row=1, col=2) fig.add_trace(go.Bar(x=df_sorted_by_avg_time['Operation'], y=df_sorted_by_avg_time['Value'], name='Avg Time Taken per Operation', marker=dict(color='green'), hoverinfo='x+y'), row=1, col=3) fig.add_trace(go.Bar(x=df_counts['Operation'], y=df_counts['count'], name='Number of times Operation called', marker=dict(color='royalblue'), hoverinfo='x+y'), row=1, col=4) fig.update_xaxes( row=1, tickangle=45, ) fig.add_trace(go.Scatter( x=list(range(len(df))), y=df['Cumulative Sum'], mode='lines+markers', text=df['Operation: Line Number'], hoverinfo='text', name='Cumulative Time', line=dict(width=2), marker=dict(size=3), ), row=2, col=1) fig.update_xaxes( title_text='Operation: Line Number', tickvals=list(range(len(df))), ticktext=df['Operation: Line Number'], row=2, col=1, tickangle=45, ) fig.update_yaxes(title_text='Cumulative Time (ms)', row=2, col=1) fig.update_layout( title='Compile Time Analysis', title_x=0.5, title_font=dict(size=24, color='darkblue'), # margin=dict(l=0, r=0, t=100, b=50), uniformtext_minsize=12, uniformtext_mode='hide', height=1200, template='plotly_white', showlegend=False) fig.write_html(output_path) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--file_path', required=True) parser.add_argument('--quantile', default=0.95) parser.add_argument('--output_path', default="compile_analysis.html") args = parser.parse_args() data = read_compile_log(args.file_path) df = filter_data(data, args.quantile) plot_compile_analysis(df, args.output_path) ROCm-AMDMIGraphX-46524e8/tools/convert_onnx_version.py000066400000000000000000000065261510465702400225520ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import argparse import onnx from onnx import version_converter def parse_args(): parser = argparse.ArgumentParser( description= 'MIGraphX Onnx Model Convertion. Use to convert the opset of the input model to MIGraphX\'s' ) req_args = parser.add_argument_group(title='required arguments') req_args.add_argument('--model', type=str, required=True, help='path to onnx file') req_args.add_argument('--output', type=str, required=True, help='path to output onnx file') req_args.add_argument('--opset', type=int, required=True, help='The output opset') req_args.add_argument('--infer_shapes', action='store_true', help='Infer shapes for output model') parser.add_argument('--verbose', action='store_true', help='show verbose information (for debugging)') args = parser.parse_args() return args def main(): args = parse_args() model_path = args.model out_model_path = args.output target_opset = args.opset verbose = args.verbose infer_shapes = args.infer_shapes original_model = onnx.load(model_path) if verbose: print(f"The model before conversion:\n{original_model}") # A full list of supported adapters can be found here: # https://github.com/onnx/onnx/blob/main/onnx/version_converter.py#L21 # Apply the version conversion on the original model converted_model = version_converter.convert_version( original_model, target_opset) if infer_shapes: converted_model = onnx.shape_inference.infer_shapes(converted_model) if verbose: print(f"The model after conversion:\n{converted_model}") # Save the ONNX model onnx.save(converted_model, out_model_path) if __name__ == '__main__': main() ROCm-AMDMIGraphX-46524e8/tools/converters/000077500000000000000000000000001510465702400200725ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/converters/mxr_to_onnx.py000066400000000000000000000164651510465702400230320ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import migraphx import onnx from onnx import helper, TensorProto, checker import numpy as np import os import argparse # Utility function to map MIGraphX types to ONNX data types def get_dtype(instruction): type_mapping = { 'float_type': TensorProto.FLOAT, 'bf16_type': TensorProto.BFLOAT16, 'half_type': TensorProto.FLOAT16 } return type_mapping[instruction.shape().type_string()] # Utility function to get the shape of an instruction def get_shape(instruction): if isinstance(instruction, list): raise ValueError("Expected instruction, got a list.") return instruction.shape().lens() # Utility function to map MIGraphX operations to ONNX operations def map_operation(operation): mxr_to_onnx_op = { "dot": "MatMul", "mul": "Mul", "add": "Add", "multibroadcast": "Expand", "erf": "Erf", "tanh": "Tanh", "exp": "Exp", "div": "Div", "relu": "Relu", "pow": "Pow" } if operation not in mxr_to_onnx_op: raise NotImplementedError(f"Operation '{operation}' is not supported.") return mxr_to_onnx_op[operation] # Helper function to create ONNX nodes for specific operations def create_node(instruction, parameters, node_name, n, initializers): if node_name == "multibroadcast" or node_name == "reshape": shape_key = "out_lens" if node_name == "multibroadcast" else "dims" shape_array = np.array(parameters[shape_key], dtype=np.int64) initializer_name = f"{node_name}_shape_{n}" initializers.append( helper.make_tensor(name=initializer_name, data_type=TensorProto.INT64, dims=shape_array.shape, vals=shape_array.flatten().tolist())) return helper.make_node( map_operation(node_name), inputs=[str(hash(i)) for i in instruction.inputs()] + [initializer_name], outputs=[str(hash(instruction))]) elif node_name == "transpose": return helper.make_node( "Transpose", inputs=[str(hash(i)) for i in instruction.inputs()], outputs=[str(hash(instruction))], perm=parameters["permutation"]) elif node_name == "convolution": return helper.make_node( "Conv", inputs=[str(hash(i)) for i in instruction.inputs()], outputs=[str(hash(instruction)) ], #[str(hash(i)) for i in instruction.outputs()], dilations=parameters["dilation"], group=parameters["group"], pads=parameters["padding"], strides=parameters["stride"]) return helper.make_node( map_operation(node_name), inputs=[str(hash(i)) for i in instruction.inputs()], outputs=[str(hash(instruction))]) # Main function to convert MIGraphX module to ONNX model def generate_onnx(module): inputs = {} operations = [] initializers = [] n = 0 # Node counter output = None for instruction in module: op_name = instruction.op().name() # Handle input nodes if op_name in ["@literal", "@param"]: inputs[str(hash(instruction))] = helper.make_tensor_value_info( str(hash(instruction)), get_dtype(instruction), get_shape(instruction)) # Handle computational nodes elif "@" not in op_name: n += 1 parameters = instruction.op().values() operations.append( create_node(instruction, parameters, op_name, n, initializers)) # Handle return node elif op_name == "@return": output = [ helper.make_tensor_value_info(str(hash(i)), get_dtype(i), get_shape(i)) for i in instruction.inputs() ] # Create the ONNX graph graph = helper.make_graph(nodes=operations, name="Graph", inputs=list(inputs.values()), initializer=initializers, outputs=output if output else []) return helper.make_model(graph, producer_name="onnx-dot-add-example") # Main function to process MIGraphX files and generate ONNX models def main(mxr_directory_path, onnx_directory_path): for file_name in os.listdir(mxr_directory_path): file_path = os.path.join(mxr_directory_path, file_name) if ".mxr" in file_path: try: program = migraphx.load(file_path) module = program.get_main_module() model = generate_onnx(module) # Validate the generated ONNX model try: checker.check_model(model) print(f"ONNX model for {file_path} is valid.") except onnx.checker.ValidationError as e: print(f"Validation failed for {file_path}: {e}") except Exception as e: print( f"Unexpected error during validation for {file_path}: {e}" ) os.makedirs(onnx_directory_path, exist_ok=True) onnx_file_path = os.path.join(onnx_directory_path, file_name.replace("mxr", "onnx")) onnx.save(model, onnx_file_path) except Exception as e: print(f"Error processing {file_path}: {e}") if __name__ == "__main__": parser = argparse.ArgumentParser( description="Process MXR files and generate ONNX models.") parser.add_argument("mxr_directory_path", type=str, help="Path to the directory containing MXR files.") parser.add_argument( "onnx_directory_path", type=str, help="Path to the directory where ONNX models will be saved.") args = parser.parse_args() main(args.mxr_directory_path, args.onnx_directory_path) ROCm-AMDMIGraphX-46524e8/tools/cppcheck/000077500000000000000000000000001510465702400174605ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/cppcheck/migraphx.py000066400000000000000000000337631510465702400216650ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import cppcheck, itertools from cppcheckdata import simpleMatch, match def skipTokenMatches(tokens, skip=None): for tok in tokens: if match(tok, skip): continue yield tok def isTokensEqual(xtokens, ytokens, skip=None): for x, y in itertools.zip_longest(skipTokenMatches(xtokens, skip), skipTokenMatches(ytokens, skip)): if not x: return False if not y: return False if x.str != y.str: return False return True def getInnerLink(token): if not token: return [] if not token.link: return [] return token.next.forward(token.link) def getVariableDecl(var): if not var: return [] end = var.typeEndToken if end: end = end.next return var.typeStartToken.forward(end) def isFunctionCall(token): if not token: return False if not token.isName: return False if not token.next: return False if not token.next.str == '(': return False return True @cppcheck.checker def AvoidBranchingStatementAsLastInLoop(cfg, data): for token in cfg.tokenlist: if not token.str in ['for', 'while']: continue end = match(token, "for|while (*) {*}").end if not end: continue stmt = end.tokAt(-2) if not match(stmt, "%any% ; }"): continue if not match(stmt, "break|continue"): stmt = stmt.astTop() if match(stmt, "break|continue|return"): cppcheck.reportError( stmt, "style", "Branching statement as the last statement inside a loop is very confusing." ) # @cppcheck.checker # def CollapsibleIfStatements(cfg, data): # for token in cfg.tokenlist: # if not match(token, "if (*) { if (*) {*} }"): # continue # cppcheck.reportError(token, "style", "These two if statements can be collapsed into one.") @cppcheck.checker def ConditionalAssert(cfg, data): for token in cfg.tokenlist: if token.str != 'if': continue if not match(token, "if (*) { assert (*) ; }"): continue cppcheck.reportError(token, "style", "The if condition should be included in assert.") @cppcheck.checker def EmptyCatchStatement(cfg, data): for token in cfg.tokenlist: if token.str != 'catch': continue if not match(token, "catch (*) { }"): continue cppcheck.reportError(token, "style", "An empty catch statement.") @cppcheck.checker def EmptyDoWhileStatement(cfg, data): for token in cfg.tokenlist: if token.str != 'do': continue if not simpleMatch(token, "do { } while ("): continue cppcheck.reportError(token, "style", "Empty do-while.") @cppcheck.checker def EmptyElseBlock(cfg, data): for token in cfg.tokenlist: if token.str != 'else': continue if not simpleMatch(token, "else { }"): continue cppcheck.reportError(token, "style", "Empty else statement can be safely removed.") @cppcheck.checker def EmptyForStatement(cfg, data): for token in cfg.tokenlist: if token.str != 'for': continue if not match(token, "for (*) { }"): continue cppcheck.reportError(token, "style", "Empty for statement.") @cppcheck.checker def EmptyIfStatement(cfg, data): for token in cfg.tokenlist: if token.str != 'if': continue if not match(token, "if (*) { }"): continue cppcheck.reportError(token, "style", "Empty if statement.") @cppcheck.checker def EmptySwitchStatement(cfg, data): for token in cfg.tokenlist: if token.str != 'switch': continue if not match(token, "switch (*) { }"): continue cppcheck.reportError(token, "style", "Empty switch statement.") @cppcheck.checker def EmptyWhileStatement(cfg, data): for token in cfg.tokenlist: if token.str != 'while': continue if not match(token, "while (*) { }"): continue cppcheck.reportError(token, "style", "Empty while statement.") @cppcheck.checker def ForLoopShouldBeWhileLoop(cfg, data): for token in cfg.tokenlist: if token.str != 'for': continue if not match(token, "for ( ; !!;"): continue # Skip empty for loops if match(token, "for (*) { }"): continue end = token.next.link if not match(end.tokAt(-1), "; )"): continue cppcheck.reportError(token, "style", "For loop should be written as a while loop.") @cppcheck.checker def GotoStatement(cfg, data): for token in cfg.tokenlist: if token.str != 'goto': continue cppcheck.reportError(token, "style", "Goto considered harmful.") # @cppcheck.checker # def InvertedLogic(cfg, data): # for token in cfg.tokenlist: # cond = None # if match(token, "if (*) {*} else { !!if"): # cond = token.next.astOperand2 # elif match(token, "?"): # cond = token.astOperand1 # if not match(cond, "!|!="): # continue # cppcheck.reportError(cond, "style", "It is cleaner to invert the logic.") @cppcheck.checker def LambdaAttribute(cfg, data): for token in cfg.tokenlist: if token.str != ']': continue if not match(token, "] __device__|__host__ {|("): continue cppcheck.reportError( token, "style", "Attributes to lambdas must come after parameter list.") @cppcheck.checker def MultipleUnaryOperator(cfg, data): for token in cfg.tokenlist: if not token.isUnaryOp(token.str): continue if not token.str in ['+', '-', '~', '!']: continue if not match(token.astOperand1, "+|-|~|!"): continue if not token.astOperand1.isUnaryOp(token.astOperand1.str): continue cppcheck.reportError(token, "style", "Muliple unary operators used together.") @cppcheck.checker def MutableVariable(cfg, data): for token in cfg.tokenlist: if token.str != 'mutable': continue if not match(token, "mutable %var%"): continue cppcheck.reportError(token, "style", "Do not create mutable variables.") @cppcheck.checker def NestedBlocks(cfg, data): for token in cfg.tokenlist: if not token.str in ['if', 'while', 'for', 'switch']: continue block = match(token, "if|while|for|switch (*) { {*}@block }").block if not block: block = match(token, "; { {*}@block break ; }").block if not block: continue cppcheck.reportError(block, "style", "Block directly inside block.") @cppcheck.checker def RedundantCast(cfg, data): for token in cfg.tokenlist: if not token.variable: continue m = match(token, "%var%@decl ; %var%@assign = static_cast <*>@cast (*) ;") if not m: continue if m.decl.varId != m.assign.varId: continue if not simpleMatch(token.previous, "auto"): if not isTokensEqual(getVariableDecl(m.decl.variable), getInnerLink(m.cast), skip='const|volatile|&|&&|*'): continue cppcheck.reportError(token, "style", "Static cast is redundant.") @cppcheck.checker def RedundantConditionalOperator(cfg, data): for token in cfg.tokenlist: if token.str != '?': continue if not match(token, "? true|false : true|false"): continue cppcheck.reportError(token, "style", "Conditional operator is redundant.") @cppcheck.checker def RedundantIfStatement(cfg, data): for token in cfg.tokenlist: if token.str != 'if': continue if not match( token, "if (*) { return true|false ; } else { return true|false ; }"): continue cppcheck.reportError(token, "style", "The if statement is redundant.") @cppcheck.checker def RedundantLocalVariable(cfg, data): for token in cfg.tokenlist: if not token.variable: continue m = match(token, "%var%@decl ; %var%@assign = **; return %var%@returned ;") if not m: continue if m.decl.varId != m.assign.varId: continue if m.decl.varId != m.returned.varId: continue cppcheck.reportError( m.returned, "style", "Variable is returned immediately after its declaration, can be simplified to just return expression." ) # @cppcheck.checker # def UnnecessaryElseStatement(cfg, data): # for token in cfg.tokenlist: # m = match(token, "if (*) {*}@block else@else_statement {") # if not m: # continue # stmt = m.block.link.tokAt(-2) # if not match(stmt, "%any% ; }"): # continue # if not match(stmt, "break|continue"): # stmt = stmt.astTop() # if not match(stmt, "break|continue|return|throw"): # continue # cppcheck.reportError(m.else_statement, "style", "Else statement is not necessary.") @cppcheck.checker def UnnecessaryEmptyCondition(cfg, data): for token in cfg.tokenlist: if token.str != 'if': continue m = match(token, "if (*)@if_cond { for (*)@for_cond {*} }") if not m: continue cond = m.if_cond.astOperand2 if match(cond, "!"): cond = cond.astOperand1 if not match(cond.tokAt(-2), ". empty ("): continue container = cond.tokAt(-2).astOperand1 if not container.varId: continue if not match(m.for_cond.astOperand2, ":"): continue container_iter = m.for_cond.astOperand2.astOperand2 if container_iter.varId != container.varId: continue cppcheck.reportError( container, "style", "Unnecessary check for empty before for range loop.") @cppcheck.checker def UseDeviceLaunch(cfg, data): for token in cfg.tokenlist: if not simpleMatch(token, "hipLaunchKernelGGL ("): continue cppcheck.reportError(token, "style", "Use device::launch instead.") @cppcheck.checker def UseManagePointer(cfg, data): functions = { "fclose", "free", "hipFree", "hipHostFree", "hipFreeArray", "hipMemFree", "hipStreamDestroy", "hipEventDestroy", "hipArrayDestroy", "hipCtxDestroy", "hipDestroyTextureObject", "hipDestroySurfaceObject", "miirDestroyHandle" } for token in cfg.tokenlist: if not isFunctionCall(token): continue if not token.str in functions: continue cppcheck.reportError(token, "style", "Use manage pointer for resource management.") @cppcheck.checker def UseSmartPointer(cfg, data): for token in cfg.tokenlist: if token.str != 'new': continue if not match(token, "new %name%"): continue cppcheck.reportError(token, "style", "Use manage pointer for resource management.") @cppcheck.checker def useStlAlgorithms(cfg, data): copy_functions = {"memcpy", "strcpy", "strncpy", "strcat", "strncat"} for token in cfg.tokenlist: if not isFunctionCall(token): continue if token.str in copy_functions: cppcheck.reportError(token, "style", "Use std::copy instead.") elif token.str == 'memset': cppcheck.reportError(token, "style", "Use std::fill instead.") elif token.str == 'memcmp': cppcheck.reportError(token, "style", "Use std::equal_range instead.") elif token.str == 'memchr': cppcheck.reportError(token, "style", "Use std::find instead.") @cppcheck.checker def MatcherNestedParentheses(cfg, data): for token in cfg.tokenlist: if not simpleMatch(token, "matcher ( ) const {"): continue for tok2 in token.tokAt(4).forward(token.linkAt(4)): if not simpleMatch(tok2, ") ) ) )"): continue if simpleMatch(tok2.link.previous, "bind"): continue cppcheck.reportError( tok2, "style", "Too many nested parentheses can affect readability; consider using variables instead." ) break ROCm-AMDMIGraphX-46524e8/tools/cppcheck/rules.xml000066400000000000000000000163031510465702400213370ustar00rootroot00000000000000 normal [;{}] [*] \w+? (\+\+|\-\-) ; UnusedDeref style Redundant * found, "*p++" is the same as "*(p++)". normal if \( ([!] )*?(strlen) \( \w+? \) ([>] [0] )*?\) { StrlenEmptyString performance Using strlen() to check if a string is empty is not efficient. normal [;{}] [*] \w+? (\+\+|\-\-) ; UnusedDeref style Redundant * found, "*p++" is the same as "*(p++)". define define [0-9A-Z_^a-z]*[a-z] defineUpperCase style Macros must be uppercase define define (MIGRAPH|[^X]{7})[^X][^_] definePrefix style Macros must be prefixed with MIGRAPHX_ raw UseNamedLogicOperator style Use 'and' instead of && raw UseNamedLogicOperator style Use 'and' instead of && raw UseNamedLogicOperator style Use 'or' instead of || raw UseNamedLogicOperator style Use 'not' instead of ! normal UnnecessaryElseStatement style Else statement is not necessary. normal InvertedLogic style It is cleaner to invert the logic. normal InvertedLogic style It is cleaner to invert the logic. normal InvertedLogic style It is cleaner to invert the logic. normal InvertedLogic style It is cleaner to invert the logic. normal |::) )*(?:\w+|>)(?: &|\*)* (\w+) = \w+ ; \1 < \w+ ; (\1 \+\+|\+\+ \1|\1 \-\-|\-\- \1) \) { \w+ \[ \1 \] = \w+ ; }]]> useStlAlgorithm style Considering using std::fill instead. normal |::) )*(?:\w+|>)(?: &|\*)* (\w+) = \w+ ; \1 < \w+ ; (\1 \+\+|\+\+ \1|\1 \-\-|\-\- \1) \) { \w+ \[ \1 \] = (?:\w+ :: )*\w+ \( \) ; }]]> useStlAlgorithm style Considering using std::generate instead. normal |::) )*(?:\w+|>)(?: &|\*)* (\w+) = \w+ ; \1 < \w+ ; (\1 \+\+|\+\+ \1|\1 \-\-|\-\- \1) \) { \w+ \[ \1 \] = (?:\w+ :: )*\w+ \( \w+ \[ \1 \] \) ; }]]> useStlAlgorithm style Considering using std::transform instead. normal |::) )*(?:\w+|>)(?: &|\*)* (\w+) = \w+ ; \1 < \w+ ; (\1 \+\+|\+\+ \1|\1 \-\-|\-\- \1) \) { \w+ \[ \1 \] = (?:\w+ :: )*\w+ \( \w+ \[ \1 \] , \w+ \[ \1 \] \) ; }]]> useStlAlgorithm style Considering using std::transform instead. normal |::) )*(?:\w+|>)(?: &|\*)* (\w+) : (?:[^()]*(\([^()]*(?-1)*[^()]*\)))*[^)]*\) { (?:(?\w+) \+\+|\+\+ (?\w+)) ; if (\([^()]*(?-1)*[^()]*\)) { \w+ = \g{idx1}|\g{idx2} ; (?:break ; )?(?:return [^;]*; )?} }]]> useStlAlgorithm style Considering using std::find or std::find_if instead. normal |::) )*(?:\w+|>)(?: &|\*)* (\w+) : (?:[^()]*(\([^()]*(?-1)*[^()]*\)))*[^)]*\) { if (\([^()]*(?-1)*[^()]*\)) { \w+ = (?\w) ; (?:break ; )?(?:return [^;]*; )?} (?:(\g{idx}) \+\+|\+\+ (\g{idx})) ; }]]> useStlAlgorithm style Considering using std::find or std::find_if instead. normal |::) )*(?:\w+|>)(?: &|\*)* (\w+) : (?:[^()]*(\([^()]*(?-1)*[^()]*\)))*[^)]*\) { (?:(?\w+) \+\+|\+\+ (?\w+)) ; if (\([^()]*(?-1)*[^()]*\)) { return \g{idx1}|\g{idx2} ; } }]]> useStlAlgorithm style Considering using std::find or std::find_if instead. normal |::) )*(?:\w+|>)(?: &|\*)* (\w+) : (?:[^()]*(\([^()]*(?-1)*[^()]*\)))*[^)]*\) { if (\([^()]*(?-1)*[^()]*\)) { return (?\w+) ; } (?:(\g{idx}) \+\+|\+\+ (\g{idx})) ; }]]> useStlAlgorithm style Considering using std::find or std::find_if instead. ROCm-AMDMIGraphX-46524e8/tools/docker/000077500000000000000000000000001510465702400171475ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/docker/migraphx_with_onnxruntime_pytorch.docker000066400000000000000000000021771510465702400274370ustar00rootroot00000000000000FROM ubuntu:22.04 ARG PREFIX=/usr/local #Prequisite packages to begin getting files RUN apt update && apt install -y wget #Aquire and install ROCm RUN wget https://repo.radeon.com/amdgpu-install/6.2/ubuntu/jammy/amdgpu-install_6.2.60200-1_all.deb RUN apt install -y ./amdgpu-install_6.2.60200-1_all.deb RUN amdgpu-install --usecase=rocm -y && rm amdgpu-install_6.2.60200-1_all.deb #Install MIGraphX from package manager RUN apt install -y migraphx #Pieces for Onnxruntime for ROCm and MIGraphX Execution Provider Support RUN pip3 install https://repo.radeon.com/rocm/manylinux/rocm-rel-6.2/onnxruntime_rocm-1.18.0-cp310-cp310-linux_x86_64.whl #Pieces for pytorch RUN pip3 install https://repo.radeon.com/rocm/manylinux/rocm-rel-6.2/pytorch_triton_rocm-2.2.0%2Brocm6.2.0.1d36d63aa0-cp310-cp310-linux_x86_64.whl RUN pip3 install https://repo.radeon.com/rocm/manylinux/rocm-rel-6.2/torch-2.2.1+rocm6.2.0-cp310-cp310-linux_x86_64.whl RUN pip3 install https://repo.radeon.com/rocm/manylinux/rocm-rel-6.2/torchvision-0.17.1+rocm6.2.0-cp310-cp310-linux_x86_64.whl #Adjust final path for ability to use rocm components ENV PATH=$PATH:/opt/rocm/bin/ ROCm-AMDMIGraphX-46524e8/tools/docker/sles.docker000066400000000000000000000025641510465702400213150ustar00rootroot00000000000000FROM registry.suse.com/suse/sle15:15.6 RUN sh -c 'echo -e "\ [rocm]\n\ name=rocm\n\ baseurl=https://repo.radeon.com/rocm/zyp/7.1/main\n\ enabled=1\n\ gpgcheck=0\n\ gpgkey=https://repo.radeon.com/rocm/rocm.gpg.key\n\ " > /etc/zypp/repos.d/rocm.repo' RUN cat /etc/zypp/repos.d/rocm.repo #addition of repos for packages RUN zypper addrepo https://download.opensuse.org/repositories/devel:/languages:/perl/15.6/devel:languages:perl.repo  RUN zypper -n --gpg-auto-import-keys refresh RUN zypper install -y -t pattern devel_basis enhanced_base RUN zypper --gpg-auto-import-keys install -y \ doxygen \ gcc-c++ \ gdb \ git \ hip-devel \ python3-pip \ rpm-build # Workaround broken rocm packages RUN ln -s /opt/rocm-* /opt/rocm RUN echo "/opt/rocm/lib" > /etc/ld.so.conf.d/rocm.conf RUN echo "/opt/rocm/llvm/lib" > /etc/ld.so.conf.d/rocm-llvm.conf RUN ldconfig ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 # Install yapf RUN pip3 install yapf==0.28.0 # Install doc requirements # ADD docs/sphinx/requirements.txt /doc-requirements.txt # RUN pip3 install -r /doc-requirements.txt # Install dependencies ADD dev-requirements.txt /dev-requirements.txt ADD requirements.txt /requirements.txt ADD rbuild.ini /rbuild.ini COPY ./tools/install_prereqs.sh / COPY ./tools/requirements-py.txt / RUN /install_prereqs.sh /usr/local / && rm /install_prereqs.sh && rm /requirements-py.txt ROCm-AMDMIGraphX-46524e8/tools/docker/ubuntu_2204.dockerfile000066400000000000000000000071331510465702400231750ustar00rootroot00000000000000FROM ubuntu:22.04 ARG PREFIX=/usr/local # Support multiarch RUN dpkg --add-architecture i386 # Install rocm key RUN apt-get update && apt-get install -y gnupg2 --no-install-recommends curl && \ curl -fsSL http://repo.radeon.com/rocm/rocm.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/rocm-keyring.gpg # Add rocm repository RUN sh -c "echo 'deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/rocm-keyring.gpg] http://repo.radeon.com/rocm/apt/6.4.2 jammy main' > /etc/apt/sources.list.d/rocm.list" # From docs.amd.com for installing rocm. Needed to install properly RUN sh -c "echo 'Package: *\nPin: release o=repo.radeon.com\nPin-priority: 600' > /etc/apt/preferences.d/rocm-pin-600" # Install dependencies RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \ apt-utils \ build-essential \ #clang-format-10 \ cmake \ curl \ doxygen \ #g++-7 \ gdb \ git \ lcov \ locales \ pkg-config \ python3 \ python3-dev \ python3-pip \ software-properties-common \ wget \ rocm-device-libs \ hip-dev \ libnuma-dev \ miopen-hip \ rocblas \ hipfft \ rocthrust \ rocrand \ hipsparse \ rccl \ rccl-dev \ rocm-smi-lib \ rocm-dev \ roctracer-dev \ hipcub \ hipblas \ hipify-clang \ half \ libssl-dev \ zlib1g-dev && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # add this for roctracer dependancies RUN pip3 install CppHeaderParser # Workaround broken rocm packages RUN ln -s /opt/rocm-* /opt/rocm RUN echo "/opt/rocm/lib" > /etc/ld.so.conf.d/rocm.conf RUN echo "/opt/rocm/llvm/lib" > /etc/ld.so.conf.d/rocm-llvm.conf RUN ldconfig RUN locale-gen en_US.UTF-8 RUN update-locale LANG=en_US.UTF-8 ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 # Install dependencies ADD dev-requirements.txt /dev-requirements.txt ADD requirements.txt /requirements.txt ADD rbuild.ini /rbuild.ini COPY ./tools/install_prereqs.sh / COPY ./tools/requirements-py.txt / RUN /install_prereqs.sh /usr/local / && rm /install_prereqs.sh && rm /requirements-py.txt RUN test -f /usr/local/hash || exit 1 # Install yapf RUN pip3 install yapf==0.28.0 # Install doc requirements ADD docs/sphinx/requirements.txt /doc-requirements.txt RUN pip3 install -r /doc-requirements.txt # Install latest ccache version RUN cget -p $PREFIX install facebook/zstd@v1.4.5 -X subdir -DCMAKE_DIR=build/cmake RUN cget -p $PREFIX install ccache@v4.1 -DENABLE_TESTING=OFF RUN cget -p /opt/cmake install kitware/cmake@v3.24.3 COPY ./test/onnx/.onnxrt-commit / ARG ONNXRUNTIME_REPO=https://github.com/Microsoft/onnxruntime ARG ONNXRUNTIME_BRANCH=main ARG ONNXRUNTIME_COMMIT RUN git clone --single-branch --branch ${ONNXRUNTIME_BRANCH} --recursive ${ONNXRUNTIME_REPO} onnxruntime && \ cd onnxruntime && \ if [ -z "$ONNXRUNTIME_COMMIT" ] ; then git checkout $(cat /.onnxrt-commit) ; else git checkout ${ONNXRUNTIME_COMMIT} ; fi && \ /bin/sh /onnxruntime/dockerfiles/scripts/install_common_deps.sh ADD tools/build_and_test_onnxrt.sh /onnxruntime/build_and_test_onnxrt.sh RUN cget -p /usr/local install ROCmSoftwarePlatform/rocMLIR@a997d5f51314b45d7a4c04f1599966dcf53f9b4d -DBUILD_MIXR_TARGET=On -DLLVM_ENABLE_ZSTD=Off -DLLVM_ENABLE_THREADS=Off ENV MIOPEN_FIND_DB_PATH=/tmp/miopen/find-db ENV MIOPEN_USER_DB_PATH=/tmp/miopen/user-db ENV LD_LIBRARY_PATH=$PREFIX/lib # Setup ubsan environment to printstacktrace ENV UBSAN_OPTIONS=print_stacktrace=1 ENV ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 RUN ln -s /opt/rocm/llvm/bin/llvm-symbolizer /usr/bin/llvm-symbolizer ROCm-AMDMIGraphX-46524e8/tools/format.py000066400000000000000000000072421510465702400175470ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import os import shutil import argparse import subprocess from git_tools import get_changed_files, get_merge_base, get_top, run CLANG_FORMAT_PATH = '/opt/rocm/llvm/bin' EXCLUDE_FILES = ['requirements.in', 'onnx.proto'] CLANG_EXTENSIONS = ('.c', '.cpp', '.hpp', '.h', '.cl', '.hip', '.in') YAPF_EXTENSIONS = ('.py') def is_excluded(f): base = os.path.basename(f) return base in EXCLUDE_FILES def clang_format(against, apply=False, path=CLANG_FORMAT_PATH): base = get_merge_base(against) clang_format = os.path.join(path, 'clang-format') if not os.path.exists(clang_format): print(f"{clang_format} not installed. Skipping format.") return git_clang_format = os.path.join(path, 'git-clang-format') if not os.path.exists(git_clang_format): print(f"{git_clang_format} not installed. Skipping format.") return diff_flag = [] if apply else ["--diff"] files = get_changed_files(base) files = [ f for f in files if f.endswith(CLANG_EXTENSIONS) and not is_excluded(f) ] run([git_clang_format, '--binary', clang_format] + diff_flag + [base] + files, cwd=get_top(), verbose=True) def yapf_format(against, apply=False): if not shutil.which('yapf'): print("yapf not installed. Skipping format.") return diff_flag = "--in-place" if apply else "--diff" files = ' '.join(get_changed_files(against)) files = [ f for f in files if f.endswith(YAPF_EXTENSIONS) and not is_excluded(f) ] if files: run(f"yapf {diff_flag} -p {files}", cwd=get_top(), verbose=True) else: print("No modified python files to format") def main(): parser = argparse.ArgumentParser() parser.add_argument('against', default='develop', nargs='?') parser.add_argument('-i', '--in-place', action='store_true') parser.add_argument('-q', '--quiet', action='store_true') args = parser.parse_args() try: clang_format(args.against, apply=args.in_place) yapf_format(args.against, apply=args.in_place) except subprocess.CalledProcessError as ex: if ex.stdout: print(ex.stdout) if ex.stderr: print(ex.stderr) if not args.quiet: print(f"Command '{ex.cmd}' returned {ex.returncode}") raise # sys.exit(ex.returncode) if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/tools/generate.py000066400000000000000000000067271510465702400200600ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import api, argparse, os, runpy, subprocess, sys, te from pathlib import Path clang_format_path = Path('clang-format.exe' if os.name == 'nt' else '/opt/rocm/llvm/bin/clang-format') work_dir = Path().cwd() src_dir = (work_dir / '../src').absolute() migraphx_py_path = src_dir / 'api/migraphx.py' def clang_format(buffer, **kwargs): return subprocess.run(f'{clang_format_path} -style=file', capture_output=True, shell=True, check=True, input=buffer.encode('utf-8'), cwd=work_dir, **kwargs).stdout.decode('utf-8') def api_generate(input_path: Path, output_path: Path): with open(output_path, 'w') as f: f.write(clang_format(api.run(input_path))) def te_generate(input_path: Path, output_path: Path): with open(output_path, 'w') as f: f.write(clang_format(te.run(input_path))) def main(): parser = argparse.ArgumentParser() parser.add_argument('-f', '--clang-format', type=Path) args = parser.parse_args() global clang_format_path if args.clang_format: clang_format_path = args.clang_format if not clang_format_path.is_file(): print(f"{clang_format_path}: invalid path or not installed", file=sys.stderr) return try: files = Path('include').absolute().iterdir() for f in [f for f in files if f.is_file()]: te_generate(f, src_dir / f'include/migraphx/{f.name}') runpy.run_path(str(migraphx_py_path)) api_generate(work_dir / 'api/migraphx.h', src_dir / 'api/include/migraphx/migraphx.h') print('Finished generating header migraphx.h') api_generate(work_dir / 'api/api.cpp', src_dir / 'api/api.cpp') print('Finished generating source api.cpp') except subprocess.CalledProcessError as ex: if ex.stdout: print(ex.stdout.decode('utf-8')) if ex.stderr: print(ex.stdout.decode('utf-8')) print(f"Command '{ex.cmd}' returned {ex.returncode}") raise if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/tools/git_tools.py000066400000000000000000000072171510465702400202640ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import subprocess import shlex import datetime def run(cmd, verbose=False, **kwargs): """ Run a shell command with optional output capture and verbosity. Args: cmd (str | list): The command to run. cwd (str): The current working directory. verbose (bool): If True, print the command being run. **kwargs: Extra arguments passed to subprocess.run(). Returns: str | None: If capture is True, returns the command's stdout as a string; otherwise None. """ if verbose: print(cmd if isinstance(cmd, str) else shlex.join(cmd)) result = subprocess.run(cmd, shell=isinstance(cmd, str), check=True, capture_output=True, text=True, **kwargs).stdout.strip() if verbose: print(result) return result def get_top(): """ Return the absolute path to the root directory of the Git repository. Useful as the base path for running Git commands or locating files. """ return run("git rev-parse --show-toplevel") def get_merge_base(branch): """ Get the common ancestor (merge base) commit hash between the current branch and the given branch. This is the commit where they diverged, used to compare changes. """ head = run("git rev-parse --abbrev-ref HEAD") # Get current branch name return run(f"git merge-base {branch} {head}" ) # Get merge base SHA between current and target branch def get_changed_files(branch): """ Return a list of files that are staged for commit and have changed compared to the merge base with the given branch. Only includes modified or added files (excludes deleted files). """ base = get_merge_base(branch) return run(f"git diff --cached --name-only --diff-filter=d {base}", cwd=get_top()).splitlines() def get_all_files(): """ Return a list of all tracked files in the Git repository. """ return run("git ls-files", cwd=get_top()).splitlines() def get_latest_commit_year(file): """ Get year of latest commit given file """ date_str = run(f"git log -1 --format=%cd --date=short {file}", cwd=get_top()) return datetime.datetime.strptime(date_str, '%Y-%m-%d').year ROCm-AMDMIGraphX-46524e8/tools/include/000077500000000000000000000000001510465702400173235ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/include/allocation_model.hpp000066400000000000000000000050311510465702400233400ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_ALLOCATION_MODEL_HPP #define MIGRAPHX_GUARD_ALLOCATION_MODEL_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// An interface for target-dependent allocation struct allocation_model { /// A name of the target-dependent allocate operator std::string name() const; /// A name of the target-dependent copy operator std::string copy() const; /// Create an allocation operator for the given shape operation allocate(const shape& s) const; /// Create a preallocated operator for the given shape operation preallocate(const shape& s, const std::string& id) const; /// Check if outputs are to be inserted bool needs_out_params() const; }; #else <% interface('allocation_model', virtual('name', returns='std::string', const=True), virtual('copy', returns='std::string', const=True), virtual('allocate', s='const shape&', returns='operation', const=True), virtual('preallocate', s='const shape&', id='std::string', returns='operation', const=True), virtual('needs_out_params', returns='bool', const=True) ) %> #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/include/concat_opt.hpp000066400000000000000000000042711510465702400221710ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_CONCAT_OPT_HPP #define MIGRAPHX_GUARD_CONCAT_OPT_HPP #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// An interface for target-dependent optimization for the concat instruction struct concat_optimization { /// A name of the target-dependent allocate operator std::string allocate() const; /// Return the target-independent concat operator optional get_concat(const operation& op) const; }; #else <% interface( 'concat_optimization', virtual('allocate', returns = 'std::string', const = True), virtual( 'get_concat', returns = 'optional', op = 'const operation&', const = True)) %> #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/include/context.hpp000066400000000000000000000056231510465702400215260ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_CONTEXT_HPP #define MIGRAPHX_GUARD_CONTEXT_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// A context is used to store internal data for a `target`. A context is /// constructed by a target during compilation and passed to the operations /// during `eval`. struct context { /// Wait for any tasks in the context to complete void finish() const; }; #else template value to_value_context(const T&) { return value{}; } template void from_value_context(T&, const value&) { } template any_ptr get_queue_context(T&) { return {}; } template void wait_for_context(T&, any_ptr) { } template void finish_on_context(T&, any_ptr){} <% interface('context', virtual('to_value', returns = 'value', const = True, default = 'to_value_context'), virtual('from_value', v = 'const value&', default = 'from_value_context'), virtual('get_queue', returns = 'any_ptr', default = 'get_queue_context'), virtual('wait_for', queue = 'any_ptr', returns = 'void', default = 'wait_for_context'), virtual('finish_on', queue = 'any_ptr', returns = 'void', default = 'finish_on_context'), virtual('finish', returns = 'void', const = True)) %> inline void migraphx_to_value(value& v, const context& ctx) { v = ctx.to_value(); } inline void migraphx_from_value(const value& v, context& ctx) { ctx.from_value(v); } #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/include/marker.hpp000066400000000000000000000040171510465702400213170ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MARKER_HPP #define MIGRAPHX_GUARD_MARKER_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// Marker is an interface to general marking functions, such as rocTX markers. #else <% interface('marker', virtual('mark_start', ins_ref = 'instruction_ref', returns = 'void'), virtual('mark_start', prog = 'const program&', returns = 'void'), virtual('mark_stop', ins = 'instruction_ref', returns = 'void'), virtual('mark_stop', prog = 'const program&', returns = 'void') ) %> #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/include/operation.hpp000066400000000000000000000557021510465702400220450ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_OPERAND_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_OPERAND_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct context; #ifdef DOXYGEN /// The operation interface represents an action an instruction will perform. All /// operation classes must be CopyConstructible. struct operation { /// A unique name identifying the operation std::string name() const; /// An optional method that can be used to finalize the operator before running void finalize(context& ctx); /// This is used to compute the resulting shape from an operation. If an /// operation cannot be run with input shapes, then it should throw an /// exception. shape compute_shape(const std::vector& input) const; /** * @brief This performs the operation's computation. * * This method can be optional when the operation is only used as a placeholder to be lowered * later on. * * @param ctx This is the context created by the `target` during compilation. Implementations * can use the target's `context` class rather than the `context` interface class. * @param output Equivalent to running `compute_shape` with each `shape` of the `argument`. * For a fixed shape, the returned argument will have the same shape as `output`. * For a dynamic shape, the returned `argument` will be a fixed shape within the bounds * set in the dynamic shape `output`. * @param input This is the `argument` result from the previous instruction's computation. * @return Return an `argument` of the result computation. The `shape` of `argument` should be * the same the `output` shape. */ argument compute(context& ctx, const shape& output, const std::vector& input) const; /// An optional method to return which argument the output will alias. If /// there is no aliased output then -1 can be returned. std::ptrdiff_t output_alias(const std::vector& input) const; /// An optional stream operator to print the operation. When this is not /// implemented, it will just print the operation's name. friend std::ostream& operator<<(std::ostream& os, const operation& op); }; /// Returns true if operation does not require a context to run compute bool is_context_free(const operation& x); /// Returns true if operation needs normalization before running compute bool need_normalization(const operation& x); /// Returns true if the operation has a finalize method bool has_finalize(const operation& x); #else namespace detail { namespace operation_operators { template auto operator<<(std::ostream& os, const T& x) -> decltype(os << x.name()) { os << x.name(); char delim = '['; reflect_each(x, [&](auto&& y, auto name) { os << delim; os << name << "="; stream_write_value(os, y); delim = ','; }); if(delim == ',') os << "]"; return os; } template auto operator==(const T& x, const U& y) -> decltype(x.name() == y.name()) { static_assert(is_reflectable{} or sizeof(T) <= 1, "Missing equality operator or reflect method."); if(x.name() != y.name()) return false; const auto& yy = any_cast(y); return reflect_tie(x) == reflect_tie(yy); } } // namespace operation_operators template auto compute_shape_op(rank<3>, const T& x, const std::vector& inputs) -> decltype(x.compute_shape(inputs)) { return x.compute_shape(inputs); } template auto compute_shape_op(rank<2>, const T& x, const std::vector& inputs) -> decltype(x.normalize_compute_shape(inputs)) { if(inputs.empty()) MIGRAPHX_THROW("At least one input is required for " + x.name()); dependent_type y = x; normalize_attributes(y, inputs[0]); return any_cast(y).normalize_compute_shape(inputs); } template auto compute_shape_op(rank<1>, const T& x, const std::vector& inputs) -> decltype(x.compute_shape(inputs, {})) { return x.compute_shape(inputs, {}); } template shape compute_shape_op(rank<0>, const T& x, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Shape not computable: " + name); } template shape compute_shape_op(const T& x, const std::vector& inputs) { return compute_shape_op(rank<3>{}, x, inputs); } template auto mod_compute_shape_op(rank<1>, const T& x, const std::vector& inputs, const std::vector& mod_args) -> decltype(x.compute_shape(inputs, mod_args)) { return x.compute_shape(inputs, mod_args); } template shape mod_compute_shape_op(rank<0>, const T& x, const std::vector& inputs, const std::vector& mod_args) { if(mod_args.empty()) return compute_shape_op(x, inputs); std::string name = x.name(); MIGRAPHX_THROW("Shape not computable: " + name); } template shape mod_compute_shape_op(const T& x, const std::vector& inputs, const std::vector& mod_args) { return mod_compute_shape_op(rank<1>{}, x, inputs, mod_args); } template auto compute_op(rank<1>, const T& x, context& ctx, const shape& output_shape, const std::vector& input) -> decltype(x.compute(auto_any_cast(ctx), make_compute_output_shape(pack(x, output_shape, input)), input)) { return x.compute( auto_any_cast(ctx), make_compute_output_shape(pack(x, output_shape, input)), input); } template argument compute_op(rank<0>, const T& x, context&, const shape&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument compute_op(const T& x, context& ctx, const shape& output_shape, const std::vector& input) { return compute_op(rank<1>{}, x, ctx, output_shape, input); } template auto compute_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input)) { return x.compute(make_compute_output_shape(pack(x, output_shape, input)), input); } template argument compute_op(rank<0>, const T& x, const shape&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument compute_op(const T& x, const shape& output_shape, const std::vector& input) { return compute_op(rank<1>{}, x, output_shape, input); } template auto compute_op(rank<1>, const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { return x.compute( make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template argument compute_op(rank<0>, const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, F) // NOLINT { if(module_args.empty()) return compute_op(x, output, inputs); std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument compute_op(const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) { return compute_op(rank<1>{}, x, output, inputs, module_args, std::move(f)); } template auto compute_op(rank<4>, const T& x, context& ctx, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { return x.compute(auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template auto compute_op(rank<3>, const T& x, context&, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute( make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { return x.compute( make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template auto compute_op(rank<2>, const T& x, context&, const shape& output, const std::vector& inputs, const std::vector&, F) // NOLINT -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs)) { return x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs); } template auto compute_op(rank<1>, const T& x, context& ctx, const shape& output, const std::vector& inputs, const std::vector&, F) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs)) { return x.compute( auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs); } template argument compute_op(rank<0>, const T& x, context&, const shape&, const std::vector&, const std::vector&, F) // NOLINT { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument compute_op(const T& x, context& ctx, const shape& output, const std::vector& inputs, const std::vector& module_args, F f) { return compute_op(rank<4>{}, x, ctx, output, inputs, module_args, std::move(f)); } template auto is_context_free_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input), std::true_type{}); template auto is_context_free_op(rank<0>, const T&, const shape&, const std::vector&) -> std::false_type; template auto is_context_free_op(const T& x) -> decltype(is_context_free_op( rank<1>{}, x, std::declval(), std::declval>())) { return {}; } template auto need_normalization_op(rank<1>, const T& x, const std::vector& inputs) -> decltype(x.normalize_compute_shape(inputs), std::true_type{}); template auto need_normalization_op(rank<0>, const T&, const std::vector&) -> std::false_type; template auto need_normalization_op(const T& x) -> decltype(need_normalization_op(rank<1>{}, x, std::declval>())) { return {}; } template std::ptrdiff_t output_alias_op(const T&, const std::vector&) { return -1; } template auto finalize_op( rank<1>, T& x, context& ctx, const shape& output_shape, const std::vector& input) -> decltype(x.finalize(auto_any_cast(ctx), output_shape, input), void()) { x.finalize(auto_any_cast(ctx), output_shape, input); } template void finalize_op(rank<0>, T&, context&, const shape&, const std::vector&) { } template void finalize_op(T& x, context& ctx, const shape& output_shape, const std::vector& input) { finalize_op(rank<1>{}, x, ctx, output_shape, input); } template auto has_finalize_op( rank<1>, T& x, context& ctx, const shape& output_shape, const std::vector& input) -> decltype(x.finalize(auto_any_cast(ctx), output_shape, input), std::true_type{}); template auto has_finalize_op(rank<0>, T&, context&, const shape&, const std::vector&) -> std::false_type; template auto has_finalize_op(const T&) -> decltype(has_finalize_op(rank<1>{}, std::declval(), std::declval(), std::declval(), std::declval>())) { return {}; } template auto compile_op( rank<1>, T& x, context& ctx, const shape& output_shape, const std::vector& input) -> decltype(x.compile(auto_any_cast(ctx), output_shape, input)) { return x.compile(auto_any_cast(ctx), output_shape, input); } template value compile_op(rank<0>, T&, context&, const shape&, const std::vector&) { return value::object{}; } template value compile_op(const T& x, context& ctx, const shape& output_shape, const std::vector& input) { return compile_op(rank<1>{}, x, ctx, output_shape, input); } template value attributes_op(const T&) { return value::object{}; } template value to_value_op(const T& x) { return migraphx::to_value(x); } template void from_value_op(T& x, const value& v) { if(not(v.is_object() or (v.empty() and v.is_array()))) MIGRAPHX_THROW("Value is not an object"); return migraphx::from_value(v, x); } template lifetime get_lifetime_op(const T&) { return lifetime::local; } } // namespace detail <% interface( 'operation', virtual('name', returns = 'std::string', const = True), virtual( 'is_context_free', returns = 'bool', const = True, default = 'detail::is_context_free_op'), virtual('need_normalization', returns = 'bool', const = True, default = 'detail::need_normalization_op'), virtual('has_finalize', returns = 'bool', const = True, default = 'detail::has_finalize_op'), virtual( 'get_lifetime', returns = 'lifetime', const = True, default = 'detail::get_lifetime_op'), virtual('output_alias', returns = 'std::ptrdiff_t', input = 'const std::vector&', const = True, default = 'detail::output_alias_op'), virtual('compile', returns = 'value', ctx = 'context&', output = 'const shape&', input = 'const std::vector&', default = 'detail::compile_op'), virtual('finalize', ctx = 'context&', output = 'const shape&', input = 'const std::vector&', default = 'detail::finalize_op'), virtual('compute_shape', returns = 'shape', input = 'const std::vector&', const = True, default = 'detail::compute_shape_op'), virtual('compute_shape', returns = 'shape', inputs = 'const std::vector&', mod_args = 'const std::vector&', const = True, default = 'detail::mod_compute_shape_op'), virtual('compute', returns = 'argument', ctx = 'context&', output = 'const shape&', input = 'const std::vector&', const = True, default = 'detail::compute_op'), virtual('compute', returns = 'argument', output = 'const shape&', input = 'const std::vector&', const = True, default = 'detail::compute_op'), virtual( 'compute', returns = 'argument', output = 'const shape&', input = 'const std::vector&', module_args = 'const std::vector&', run = 'std::function(module_ref&, const std::unordered_map&)>', const = True, default = 'detail::compute_op'), virtual( 'compute', returns = 'argument', ctx = 'context&', output = 'const shape&', input = 'const std::vector&', module_args = 'const std::vector&', run = 'std::function(module_ref&, const std::unordered_map&)>', const = True, default = 'detail::compute_op'), virtual('to_value', returns = 'value', const = True, default = 'detail::to_value_op'), virtual('from_value', v = 'const value&', default = 'detail::from_value_op'), virtual('attributes', returns = 'value', const = True, default = 'detail::attributes_op'), friend('operator<<', returns = 'std::ostream &', os = 'std::ostream &', op = 'const operation &', using = 'migraphx::detail::operation_operators::operator<<'), friend('operator==', returns = 'bool', x = 'const operation &', y = 'const operation &', using = 'migraphx::detail::operation_operators::operator==')) %> inline bool operator!=(const operation& x, const operation& y) { return not(x == y); } inline value compile(operation& op, context& ctx, const shape& output_shape, const std::vector& input) { return op.compile(ctx, output_shape, input); } template inline value compile(operation& op, Context& ctx, const shape& output_shape, const std::vector& input) { dependent_type ctx2 = std::ref(ctx); return compile(op, ctx2, output_shape, input); } template inline auto compile(T& op, Context& ctx, const shape& output_shape, const std::vector& input) -> decltype(op.compile(ctx, ctx, output_shape, input)) { return op.compile(ctx, ctx, output_shape, input); } inline shape compute_shape(const operation& op, const std::vector& inputs) { return op.compute_shape(inputs); } template inline auto compute_shape(const T& op, const std::vector& inputs) -> decltype(op.compute_shape(inputs)) { return op.compute_shape(inputs); } template inline auto compute_shape(const T& op, const std::vector& inputs) -> decltype(op.normalize_compute_shape(inputs)) { return detail::compute_shape_op(op, inputs); } inline shape compute_shape(const operation& op, const std::vector& inputs, const std::vector& mod_args) { return op.compute_shape(inputs, mod_args); } template inline auto compute_shape(const T& op, const std::vector& inputs, const std::vector& mod_args) -> decltype(op.compute_shape(inputs, mod_args)) { return op.compute_shape(inputs, mod_args); } template inline auto compute_shape(const T& op, const std::vector& inputs, const std::vector& mod_args) -> decltype(op.normalize_compute_shape(inputs, mod_args)) { return detail::compute_shape_op(op, inputs, mod_args); } inline bool is_context_free(const operation& op) { return op.is_context_free(); } template bool is_context_free(const T& x) { return detail::is_context_free_op(x); } inline bool need_normalization(const operation& op) { return op.need_normalization(); } template bool need_normalization(const T& x) { return detail::need_normalization_op(x); } inline bool has_finalize(const operation& op) { return op.has_finalize(); } template bool has_finalize(const T& x) { return detail::has_finalize_op(x); } MIGRAPHX_EXPORT void migraphx_to_value(value& v, const operation& op); MIGRAPHX_EXPORT void migraphx_from_value(const value& v, operation& op); #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/include/pass.hpp000066400000000000000000000056421510465702400210110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_PASS_HPP #define MIGRAPHX_GUARD_PASS_HPP #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct program; struct module; struct module_pass_manager; #ifdef DOXYGEN /// An interface for applying a transformation to the instructions in a /// `program` struct pass { /// A unique name used to identify the pass std::string name() const; /// Run the pass on the module void apply(module_pass_manager& mpm) const; void apply(module& m) const; /// Run the pass on the program void apply(program& p) const; }; #else MIGRAPHX_EXPORT module& get_module(module_pass_manager& mpm); namespace detail { template auto module_pass_manager_apply(rank<1>, const T& x, module_pass_manager& mpm) -> decltype(x.apply(get_module(mpm))) { return x.apply(get_module(mpm)); } template void module_pass_manager_apply(rank<0>, const T&, module_pass_manager&) { } template void module_pass_manager_apply(const T& x, module_pass_manager& mpm) { module_pass_manager_apply(rank<1>{}, x, mpm); } } // namespace detail <% interface('pass', virtual('name', returns='std::string', const=True), virtual('apply', returns='void', mpm='module_pass_manager &', const=True, default='migraphx::detail::module_pass_manager_apply'), virtual('apply', returns='void', p='program &', const=True, default='migraphx::nop') ) %> /// Used in the targets to enable/disable compiler passes MIGRAPHX_EXPORT pass enable_pass(bool enabled, pass p); #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/include/schedule_model.hpp000066400000000000000000000053211510465702400230110ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_SCHEDULE_MODEL_HPP #define MIGRAPHX_GUARD_SCHEDULE_MODEL_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct module; struct operation; #ifdef DOXYGEN /// An interface for target-dependent model for the scheduler struct schedule_model { /// Get the number of concurrent instruction allowed std::size_t concurrency() const; /// Schedule a concurrent instruction void sched(module& m, instruction_ref ins, std::size_t n) const; // Insert necessary waits before an instruction void wait(module& m, instruction_ref ins, std::size_t wait_id) const; // Insert necessary records after an instruction void record(module& m, instruction_ref ins, std::size_t wait_id) const; /// Compute weights for an operation std::size_t weight(const operation& op) const; }; #else <% interface('schedule_model', virtual('concurrency', returns='std::size_t', const=True), virtual('sched', m='module&', ins='instruction_ref', n='std::size_t', const=True), virtual('wait', m='module&', ins='instruction_ref', wait_id='std::size_t', const=True), virtual('record', m='module&', ins='instruction_ref', wait_id='std::size_t', const=True), virtual('weight', returns='std::size_t', op='const operation&', const=True) ) %> #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/include/stream_model.hpp000066400000000000000000000054051510465702400225130ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_STREAM_MODEL_HPP #define MIGRAPHX_GUARD_STREAM_MODEL_HPP #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { #ifdef DOXYGEN /// An interface for target-dependent model for the scheduler struct stream_model { /// Get the number of streams used in the program std::size_t get_nstream() const; /// Get stream for instruction std::size_t get_stream(instruction_ref ins) const; /// Get unique event id for instruction std::size_t get_event_id(instruction_ref ins) const; /// Returns true if instruction has a stream assignment bool has_stream(instruction_ref ins) const; /// Returns true if the instruction records the event bool is_record(instruction_ref ins) const; /// Returns true if the instruction wait on the event bool is_wait(instruction_ref ins) const; }; #else <% interface('stream_model', virtual('get_nstream', returns='std::size_t', const=True), virtual('get_stream', ins='instruction_ref', returns='std::size_t', const=True), virtual('get_event_id', ins='instruction_ref', returns='std::size_t', const=True), virtual('has_stream', ins='instruction_ref', returns='bool', const=True), virtual('is_record', ins='instruction_ref', returns='bool', const=True), virtual('is_wait', ins='instruction_ref', returns='bool', const=True) ) %> #endif } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/include/target.hpp000066400000000000000000000126221510465702400213250ustar00rootroot00000000000000/* * The MIT License (MIT) * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. * * 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. */ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_TARGET_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_TARGET_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { struct value; #ifdef DOXYGEN /// An interface for a compilation target struct target { /// A unique name used to identify the target std::string name() const; /** * @brief The transformation pass to be run during compilation. * * @param ctx This is the target-dependent context that is created by `get_context` * @param options Compiling options passed in by the user * @return The passes to be ran */ std::vector get_passes(context& ctx, const compile_options& options) const; /** * @brief Construct a context for the target. * @return The context to be used during compilation and execution. */ context get_context() const; /** * @brief Get the ranges of instructions that are supported on a target * @param module Module to check for supported instructions * @param metric Used to define how the quality of the support should be measured * @return the supported segments of the graph */ supported_segments target_is_supported(T&, const_module_ref mod, support_metric metric) const; /** * @brief copy an argument to the current target. * * @param arg Input argument to be copied to the target * @return Argument in the target. */ argument copy_to(const argument& arg) const; /** * @brief copy an argument from the current target. * * @param arg Input argument to be copied from the target * @return Argument in the host. */ argument copy_from(const argument& arg) const; /** * @brief Allocate an argument based on the input shape * * @param s Shape of the argument to be allocated in the target * @return Allocated argument in the target. */ argument allocate(const shape& s) const; }; #else template argument target_allocate(T& x, const shape&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } template argument copy_to_target(T&, const argument& arg) { return arg; } template argument copy_from_target(T&, const argument& arg) { return arg; } template supported_segments target_find_supported(T&, const_module_ref, support_metric) { return {}; } <% interface('target', virtual('name', returns = 'std::string', const = True), virtual('get_passes', ctx = 'context&', options = 'const compile_options&', returns = 'std::vector', const = True), virtual('get_context', returns = 'context', const = True), virtual('find_supported', returns = 'supported_segments', mod = 'const_module_ref', m = 'support_metric', const = True, default = 'target_find_supported'), virtual('copy_to', returns = 'argument', input = 'const argument&', const = True, default = 'copy_to_target'), virtual('copy_from', returns = 'argument', input = 'const argument&', const = True, default = 'copy_from_target'), virtual('allocate', s = 'const shape&', returns = 'argument', const = True, default = 'target_allocate')) %> #endif void migraphx_to_value(value& v, const target& t); void migraphx_from_value(const value& v, target& t); } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif ROCm-AMDMIGraphX-46524e8/tools/install_prereqs.sh000077500000000000000000000053131510465702400214500ustar00rootroot00000000000000#!/bin/bash ##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### # # Build MIGraphX prerequisites for docker container set -e export LC_ALL=C.UTF-8 export LANG=C.UTF-8 source /etc/os-release # hipcc added as workaround for broken hip-dev package if [[ ("${ID}" == "sles") ]]; then zypper -n --gpg-auto-import-keys install -y \ cmake \ miopen-hip-devel \ openmp-extras-devel \ python3-devel \ python3-pip \ rocblas-devel \ rocm-cmake \ libgfortran5 \ hipblas-devel \ hipblaslt-devel else # Need pip3 and Python headers to build dependencies apt update && apt install -y \ cmake \ libnuma-dev \ miopen-hip-dev \ openmp-extras \ python3-dev \ python3-pip \ python3-venv \ rocblas-dev \ libgfortran5 \ hipblas-dev \ hipblaslt-dev \ hipcc \ rocm-cmake \ rocm-llvm-dev \ libtbb-dev fi # Needed for cmake to build various pip packages pip3 install setuptools wheel # install rbuild to build dependencies pip3 install https://github.com/RadeonOpenCompute/rbuild/archive/master.tar.gz PREFIX=/usr/local REQ_FILE_DIR="$(dirname -- "$0")" if [ "$#" -ge 2 ]; then PREFIX=$1 cd $2 elif [ "$#" -eq 1 ]; then PREFIX=$1 fi echo "Dependencies are installed at $PREFIX" # Install deps with rbuild rbuild prepare -d $PREFIX -s develop if [[ ("${ID}" != "sles") ]]; then export CMAKE_ARGS="-DONNX_USE_PROTOBUF_SHARED_LIBS=ON" pip3 install -r $REQ_FILE_DIR/requirements-py.txt fi ROCm-AMDMIGraphX-46524e8/tools/license_stamper.py000066400000000000000000000221411510465702400214270ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### #################################### GUIDE ########################################## ##################################################################################### # This license_stamper script is to be triggered manually via users to update the license # stamps for all the files in the current local branch. It works by generating a list of # all the files in the current active local branch via GIT LS-FILES and then compares each # files license stamp year to the latest commit year (which is found via GIT LOG). # It then updates the year if needed. If a license stamp is not found, then # it will add a stamp at the begenning of the file with the year set to the current year. ##################################################################################### import os import argparse from stamp_status import StampStatus, stamp_check, update_year, current_year from git_tools import get_all_files, get_changed_files, get_latest_commit_year, get_top delimiters = { ".cpp": "*", ".hpp": "*", ".h": "*", ".ipynb": "//", ".py": "#", ".txt": "#", ".bsh": "#", ".sh": "#", ".cmake": "#" } extensions = delimiters.keys() # Get the delimiter from the file type based on what we care about to tag with our licence # file. Otherwise return None for the delimiter and skip the file def get_delimiter(filename): delimiter = None for extension in extensions: if filename.endswith(extension): delimiter = delimiters[extension] break return delimiter def getipynb_markdown_block_aslist(): markdown_block = [ '\t{\n' '\t\t"cell_type": "code",\n', '\t\t"execution_count": null,\n', '\t\t"metadata": {},\n', '\t\t"outputs": [],\n', '\t\t"source": [\n', '\t\t\t\"# The MIT License (MIT)\\n\",\n', '\t\t\t\"#\\n\",\n', f'\t\t\t\"# Copyright (c) {current_year()} Advanced Micro Devices, Inc. All rights reserved.\\n\",\n', '\t\t\t\"#\\n\",\n', '\t\t\t\"# Permission is hereby granted, free of charge, to any person obtaining a copy\\n\",\n', '\t\t\t\"# of this software and associated documentation files (the \'Software\'), to deal\\n\",\n', '\t\t\t\"# in the Software without restriction, including without limitation the rights\\n\",\n', '\t\t\t\"# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\\n\",\n', '\t\t\t\"# copies of the Software, and to permit persons to whom the Software is\\n\",\n', '\t\t\t\"# furnished to do so, subject to the following conditions:\\n\",\n', '\t\t\t\"#\\n\",\n', '\t\t\t\"# The above copyright notice and this permission notice shall be included in\\n\",\n', '\t\t\t\"# all copies or substantial portions of the Software.\\n\",\n', '\t\t\t\"#\\n\",\n', '\t\t\t\"# THE SOFTWARE IS PROVIDED \'AS IS\', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\n\",\n', '\t\t\t\"# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\n\",\n', '\t\t\t\"# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\\n\",\n', '\t\t\t\"# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\n\",\n', '\t\t\t\"# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\n\",\n', '\t\t\t\"# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\\n\",\n', '\t\t\t\"# THE SOFTWARE.\\n\"\n', '\t\t]\n', '\t},' ] return markdown_block # Header and footer of the comment block # modify these if we want some different style def top_header(comment_char): delim = None #Early return if "//" in comment_char: delim = getipynb_markdown_block_aslist() delim.append("\n") return ''.join(str(x) for x in delim) if "*" in comment_char: delim = "/*\n" if "#" in comment_char: delim = "#####################################################################################\n" return delim def bottom_footer(comment_char): delim = None #Early return - no footer handled by if "//" in comment_char: return delim if "*" in comment_char: delim = "*/\n" if "#" in comment_char: delim = "#####################################################################################\n" return delim # Simple just open and write stuff to each file with the license stamp def stamp_file(rfile, message, comment_char, use_last_commit_year=False, debug=False): filename = os.path.join(get_top(), rfile) add_shebang = False #markdown file stamping for .ipynb save_markdown_lines = [] modify_markdown = False #open save old contents and append things here if debug is True: print("Open", filename) try: with open(filename, 'r', encoding='utf-8') as contents: if comment_char != "//": saved_shebang = contents.readline() if "#!" in saved_shebang: add_shebang = True else: contents.seek(0) if comment_char == "//": save_markdown_lines.append(contents.readline()) save_markdown_lines.append(contents.readline()) modify_markdown = True save = contents.read() except (OSError, UnicodeDecodeError) as e: if debug: print(f"{e} \n Skipping file") return year = get_latest_commit_year( rfile) if use_last_commit_year else current_year() status = stamp_check(filename, year, save, debug=debug) if status in (StampStatus.OK, StampStatus.OTHER_LICENSE, StampStatus.WRONG_YEAR): if status == StampStatus.WRONG_YEAR: update_year(filename, year) return if debug: print("Writing header") with open(filename, 'w', encoding='utf-8') as contents: if add_shebang: contents.write(saved_shebang + "\n") if modify_markdown: contents.write(''.join(save_markdown_lines)) delim = top_header(comment_char) if delim: contents.write(delim) if not modify_markdown: for line in message: contents.write(f"{comment_char} {line}\n" if line else f"{comment_char}\n") delim = bottom_footer(comment_char) if delim: contents.write(delim) contents.write(save) if debug is True: print("Done") def main(args): with open(os.path.join(get_top(), 'LICENSE'), encoding='utf-8') as f: message = f.read().split('\n') filelist = None if args.all: filelist = get_all_files() else: filelist = get_changed_files(args.against) if args.debug is True: print("Target file list:\n" + '\n'.join(filelist)) print("Output Message:\n" + '\n'.join(message)) for rfile in filelist: comment_delim = get_delimiter(rfile) if comment_delim is not None: print(f"Updating file: {rfile}") stamp_file(rfile, message, comment_delim, use_last_commit_year=args.all, debug=args.debug) else: print(f"No valid delimeter for file: {rfile}") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('against', default='origin/develop', nargs='?') parser.add_argument('-a', '--all', action='store_true', help='Update all files') parser.add_argument('--debug', action='store_true', help='Enable debug output') args = parser.parse_args() main(args) ROCm-AMDMIGraphX-46524e8/tools/model_zoo/000077500000000000000000000000001510465702400176675ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/model_zoo/README.md000066400000000000000000000001331510465702400211430ustar00rootroot00000000000000# Model Zoo - [Test Generator with Datasets](./test_generator/) - [ONNX Zoo](./onnx_zoo/) ROCm-AMDMIGraphX-46524e8/tools/model_zoo/onnx_zoo/000077500000000000000000000000001510465702400215405ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/model_zoo/onnx_zoo/README.md000066400000000000000000000020751510465702400230230ustar00rootroot00000000000000# ONNX Zoo model tester Helper script to test [`ONNX Zoo models`](https://onnx.ai/models/) which have test data with [`test_runner.py`](../../test_runner.py) ## Getting the repository > [!IMPORTANT] > Make sure to enable git-lfs. ```bash git clone https://github.com/onnx/models.git --depth 1 ``` ## Running the tests > [!IMPORTANT] > The argument must point to a folder, not a file. ```bash # VERBOSE=1 DEBUG=1 # use these for more log # ATOL=0.001 RTOL=0.001 TARGET=gpu # are the default values ./test_models.sh models/validated ``` You can also pass multiple folders, e.g.: ```bash ./test_models.sh models/validated/text/machine_comprehension/t5/ models/validated/vision/classification/shufflenet/ ``` ## Results Result are separated by dtype: `logs/fp32` and `logs/fp16` ### Helpers ```bash # Something went wrong grep -HRL PASSED logs # Runtime error grep -HRi RuntimeError logs/ # Accuracy issue grep -HRl FAILED logs ``` ## Cleanup If at any point something fails, the following things might need cleanup: - Remove `tmp_model` folder - `git lfs prune` in `models`ROCm-AMDMIGraphX-46524e8/tools/model_zoo/onnx_zoo/test_models.sh000077500000000000000000000073521510465702400244300ustar00rootroot00000000000000#!/bin/bash ##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### set -e WORK_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" SCRIPT_PATH=$(dirname $(dirname $(dirname $(readlink -f "$0"))))/test_runner.py TESTER_SCRIPT="${TESTER:-$SCRIPT_PATH}" ATOL="${ATOL:-0.001}" RTOL="${RTOL:-0.001}" TARGET="${TARGET:-gpu}" if [[ "${DEBUG:-0}" -eq 1 ]]; then PIPE=/dev/stdout else PIPE=/dev/null fi if [[ "${VERBOSE:-0}" -eq 1 ]]; then set -x fi # Iterate through input recursively, process any tar.gz file function iterate() { local dir="$1" for file in "$dir"/*; do if [ -f "$file" ]; then if [[ $file = *.tar.gz ]]; then process "$file" fi fi if [ -d "$file" ]; then iterate "$file" fi done } # Process will download the lfs file, extract model and test data # Test it with test_runner.py, then cleanup function process() { local file="$1" echo "INFO: process $file started" setup $file test $file fp32 test $file fp16 cleanup $file echo "INFO: process $file finished" } # Download and extract files function setup() { local file="$1" echo "INFO: setup $file" local_file="$(basename $file)" # We need to change the folder to pull the file folder="$(cd -P -- "$(dirname -- "$file")" && pwd -P)" cd $folder &> "${PIPE}" && git lfs pull --include="$local_file" --exclude="" &> "${PIPE}"; cd - &> "${PIPE}" tar xzf $file -C $WORK_DIR/tmp_model &> "${PIPE}" } # Remove tmp files and prune models function cleanup() { local file="$1" echo "INFO: cleanup $file" # We need to change the folder to pull the file folder="$(cd -P -- "$(dirname -- "$file")" && pwd -P)" cd $folder &> "${PIPE}" && git lfs prune &> "${PIPE}"; cd - &> "${PIPE}" rm -r $WORK_DIR/tmp_model/* &> "${PIPE}" } # Run test_runner.py and log if something goes wrong function test() { local file="$1" echo "INFO: test $file ($2)" local_file="$(basename $file)" flag="--atol $ATOL --rtol $RTOL --target $TARGET" if [[ "$2" = "fp16" ]]; then flag="$flag --fp16" fi EXIT_CODE=0 python3 $TESTER_SCRIPT ${flag} $WORK_DIR/tmp_model/*/ &> "$WORK_DIR/logs/$2/${local_file//\//_}.log" || EXIT_CODE=$? if [[ "${EXIT_CODE:-0}" -ne 0 ]]; then echo "WARNING: ${file} failed ($2)" fi } mkdir -p $WORK_DIR/logs/fp32/ $WORK_DIR/logs/fp16/ $WORK_DIR/tmp_model rm -fr $WORK_DIR/tmp_model/* for arg in "$@"; do iterate "$(dirname $(readlink -e $arg))/$(basename $arg)" done ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/000077500000000000000000000000001510465702400227145ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/README.md000066400000000000000000000072041510465702400241760ustar00rootroot00000000000000# Test Generator with Datasets Helper module to generate real samples from datasets for specific models. ## Prerequisites ```bash python3 -m venv .venv . .venv/bin/activate pip install -r requirements.txt ``` To use audio based datasets, install sndfile and ffmpeg ```bash apt install libsndfile1 apt install ffmpeg ``` ## Usage ```bash usage: generate.py [-h] [--image {all,none,...}] [--text {all,none,...}] [--audio {all,none,...}] [--output-folder-prefix OUTPUT_FOLDER_PREFIX] [--sample-limit SAMPLE_LIMIT] [--decode-limit DECODE_LIMIT] optional arguments: -h, --help show this help message and exit --image {all,none,...} Image models to test with imagenet-2012-val dataset samples --text {all,none,...} Text models to test with squad-hf dataset samples --audio {all,none,...} Audio models to test with librispeech-asr dataset samples --output-folder-prefix OUTPUT_FOLDER_PREFIX Output path will be "//" --sample-limit SAMPLE_LIMIT Max number of samples generated. Use 0 to ignore it. --decode-limit DECODE_LIMIT Max number of sum-samples generated for decoder models. Use 0 to ignore it. (Only for decoder models) ``` > [!NOTE] > Some models require permission to access, use `huggingface-cli login`. To generate everything: ```bash python generate.py ``` To generate a subset of the supported models: - `none` to skip it - `all` for every models - list supported model names ```bash python generate.py --image resnet50_v1.5 clip-vit-large-patch14 --text none --audio none ``` ## Test models `test_models.sh` will run all downloaded models on the `generated` samples. The result will be in `logs`. ```bash ./test_models.sh generated/ ``` > [!NOTE] > `generated` is the default output folder, make sure to match `--output-folder-prefix` name. ## Adding more models To add mode models, first choose the proper place: - [image](./sample_generator/model/image.py) - [text](./sample_generator/model/text.py) - [audio](./sample_generator/model/audio.py) - [hybrid](./sample_generator/model/hybrid.py) For example, adding basic would be this (e.g. ResNet): ```python class ResNet50_v1_5(OptimumHFModelDownloadMixin, AutoImageProcessorHFMixin, BaseModel): @property def model_id(self): return "microsoft/resnet-50" @staticmethod def name(): return "resnet50_v1.5" ``` Define the class with the proper `Mixin`s: - `OptimumHFModelDownloadMixin`: Download model from Hugging Face and export it to onnx with Optimum - `AutoImageProcessorHFMixin`: Define the processor from Hugging Face (This depends on the model type) - `BaseModel`: Default model type, other choice is `DecoderModel` Provide 2 mandatory fields: - `model_id`: Hugging Face url - `name`: unique name for model To add a more complex model (e.g. Decoder), check [text](./sample_generator/model/text.py). The [generate](./generate.py) part will need further updating to include the model. ## Adding more datasets The 3 most common use cases are handled: - `Image`: with [imagenet](./sample_generator/dataset/imagenet.py) - `Text`: with [squad](./sample_generator/dataset/squad.py) - `Audio`: with [librispeech](./sample_generator/dataset/librispeech.py) To add a new use case, e.g. Video, create a new python file in dataset, and inherit a new class from Base. The [generate](./generate.py) part will need further updating to include the dataset. ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/generate.py000066400000000000000000000172641510465702400250720ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from argparse import ArgumentParser from collections import namedtuple import warnings warnings.filterwarnings('ignore') # datasets from sample_generator.dataset.imagenet import ImageNet2012Val from sample_generator.dataset.coco import COCO2017Val from sample_generator.dataset.librispeech import LibriSpeechASR from sample_generator.dataset.squad import SQuAD_HF from sample_generator.dataset.prompts import StylePrompts # models from sample_generator.model.image import ResNet50_v1, ResNet50_v1_5, VitBasePatch16_224, TIMM_MobileNetv3_large from sample_generator.model.text import BERT_large_uncased, DistilBERT_base_cased_distilled_SQuAD, RobertaBaseSquad2 from sample_generator.model.text import GPTJ, Llama2_7b_chat_hf, Llama3_8b_instruct, T5_base, Gemma_2b_it from sample_generator.model.audio import Wav2Vec2_base_960h, WhisperSmallEn from sample_generator.model.hybrid import ClipVitLargePatch14 from sample_generator.model.diffusion import StableDiffusion21, StableDiffusionXL # generator from sample_generator.generator import generate_test_dataset, generate_diffusion_data DatasetModelsPair = namedtuple('DatasetModelsPair', ['dataset', 'models']) imagenet_models = ( ResNet50_v1, ResNet50_v1_5, VitBasePatch16_224, TIMM_MobileNetv3_large, ClipVitLargePatch14, ) squad_models = ( BERT_large_uncased, DistilBERT_base_cased_distilled_SQuAD, RobertaBaseSquad2, GPTJ, T5_base, Gemma_2b_it, Llama2_7b_chat_hf, Llama3_8b_instruct, ) librispeech_models = (Wav2Vec2_base_960h, WhisperSmallEn) diffusion_models = (StableDiffusion21, StableDiffusionXL) default_dataset_model_mapping = { "image": DatasetModelsPair(ImageNet2012Val, imagenet_models), "text": DatasetModelsPair(SQuAD_HF, squad_models), "audio": DatasetModelsPair(LibriSpeechASR, librispeech_models), "diffusion": DatasetModelsPair((COCO2017Val, StylePrompts), diffusion_models) } def get_args(): parser = ArgumentParser() parser.add_argument( '--image', choices=['all', 'none'] + [model.name() for model in imagenet_models], nargs='+', default='all', dest='image_model_names', type=str, help= f'Image models to test with {ImageNet2012Val.name()} dataset samples') parser.add_argument( '--text', choices=['all', 'none'] + [model.name() for model in squad_models], nargs='+', default='all', dest='text_model_names', type=str, help=f'Text models to test with {SQuAD_HF.name()} dataset samples') parser.add_argument( '--audio', choices=['all', 'none'] + [model.name() for model in librispeech_models], nargs='+', default='all', dest='audio_model_names', type=str, help= f'Audio models to test with {LibriSpeechASR.name()} dataset samples') parser.add_argument( '--diffusion', choices=['all', 'none'] + [model.name() for model in diffusion_models], nargs='+', default='all', dest='diffusion_model_names', type=str, help= f'DiffusionModel models to test with {COCO2017Val.name()} and {StylePrompts} dataset samples' ) parser.add_argument( '--output-folder-prefix', default='generated', help='Output path will be "//"') parser.add_argument( '--sample-limit', default=5, type=int, help="Max number of samples generated. Use 0 to ignore it.") parser.add_argument( '--decode-limit', default=5, type=int, help= "Max number of sum-samples generated for decoder models. Use 0 to ignore it. (Only for decoder models)" ) return parser.parse_args() def get_dataset_models_pairs(dataset_type, model_names): if 'none' in model_names: return None ds_ms_mapping = default_dataset_model_mapping[dataset_type] if 'all' in model_names: return ds_ms_mapping return DatasetModelsPair(dataset=ds_ms_mapping.dataset, models=(model for model in ds_ms_mapping.models if model.name() in model_names)) def main(image_model_names='all', text_model_names='all', audio_model_names='all', diffusion_model_names='all', output_folder_prefix='generated', sample_limit=5, decode_limit=5): for dataset_type, model_names in zip( ('image', 'text', 'audio'), (image_model_names, text_model_names, audio_model_names)): dataset_model_pair = get_dataset_models_pairs(dataset_type, model_names) if dataset_model_pair is None: print(f"Skip {dataset_type}...") continue dataset = dataset_model_pair.dataset for model in dataset_model_pair.models: try: generate_test_dataset( model(), dataset(), output_folder_prefix=output_folder_prefix, sample_limit=sample_limit, decode_limit=decode_limit) except Exception as e: print(f"Something went wrong:\n{e}\nSkipping model...") continue for dataset_type, model_names in zip(('diffusion', ), (diffusion_model_names)): dataset_model_pair = get_dataset_models_pairs(dataset_type, model_names) if dataset_model_pair is None: print(f"Skip {dataset_type}...") continue image_dataset, prompt_dataset = dataset_model_pair.dataset for model in dataset_model_pair.models: try: generate_diffusion_data( model(), image_dataset(), prompt_dataset(), output_folder_prefix=output_folder_prefix, sample_limit=sample_limit, decode_limit=decode_limit) except Exception as e: print(f"Something went wrong:\n{e}\nSkipping model...") continue print( f'Use this to test MIGraphX with the result:\n./test_models.sh {output_folder_prefix}' ) if '__main__' == __name__: args = get_args() main(**vars(args)) ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/requirements.txt000066400000000000000000000030551510465702400262030ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### -f https://repo.radeon.com/rocm/manylinux/rocm-rel-6.1/ torch == 2.8.0 torchvision optimum >= 1.19.0 accelerate datasets numpy onnx huggingface_hub Pillow opencv-python-headless onnxruntime transformers diffusers timm soundfile librosa torchcodec ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/000077500000000000000000000000001510465702400262435ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/__init__.py000066400000000000000000000025531510465702400303610ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### __all__ = ["dataset", "generator", "model", "utils"] ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/dataset/000077500000000000000000000000001510465702400276705ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/dataset/__init__.py000066400000000000000000000025701510465702400320050ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### __all__ = ["imagenet", "coco", "librispeech", "prompts", "squad"] ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/dataset/base.py000066400000000000000000000041601510465702400311550ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### import abc from datasets import load_dataset class BaseDataset(abc.ABC): @property @abc.abstractmethod def url(self): pass @property @abc.abstractmethod def split(self): pass @staticmethod @abc.abstractmethod def name(): pass @abc.abstractmethod def __iter__(self): pass @abc.abstractmethod def __next__(self): pass @abc.abstractmethod def transform(self, *args, **kwargs): pass class ValidationDatasetHFIteratorMixin(object): def __iter__(self): print(f"Load dataset from {self.url} using {self.split} split") self.dataset = iter( load_dataset(self.url, split=self.split, streaming=True)) return self.dataset def __next__(self): return next(self.dataset) ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/dataset/coco.py000066400000000000000000000037471510465702400312000ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseDataset, ValidationDatasetHFIteratorMixin class COCO2017Val(ValidationDatasetHFIteratorMixin, BaseDataset): @property def url(self): return "lmms-lab/COCO-Caption2017" @property def split(self): return "val" @staticmethod def name(): return "coco-2017-val" def transform(self, inputs, data, prepocess_fn): result = prepocess_fn(data["image"]) inputs, keys = sorted(inputs), sorted(list(result.keys())) assert inputs == keys, f"{inputs = } == {keys = }" # The result should be a simple dict, the preproc returns a wrapped class, dict() will remove it return dict(result) ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/dataset/imagenet.py000066400000000000000000000045441510465702400320420ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseDataset from datasets import load_dataset class ImageNet2012Val(BaseDataset): @property def url(self): return "https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar" @property def split(self): return "val" @staticmethod def name(): return "imagenet-2012-val" def __iter__(self): print(f"Load dataset from {self.url}") self.dataset = iter( load_dataset("webdataset", data_files={self.split: self.url}, split=self.split, streaming=True)) return self.dataset def __next__(self): return next(self.dataset) def transform(self, inputs, data, prepocess_fn): result = prepocess_fn(data["jpeg"]) inputs, keys = sorted(inputs), sorted(list(result.keys())) assert inputs == keys, f"{inputs = } == {keys = }" # The result should be a simple dict, the preproc returns a wrapped class, dict() will remove it return dict(result) ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/dataset/librispeech.py000066400000000000000000000040661510465702400325410ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseDataset, ValidationDatasetHFIteratorMixin class LibriSpeechASR(ValidationDatasetHFIteratorMixin, BaseDataset): @property def url(self): return "librispeech_asr" @property def split(self): return "validation.clean" @staticmethod def name(): return "librispeech-asr" def transform(self, inputs, data, prepocess_fn): result = prepocess_fn(data["audio"]["array"], data["audio"]["sampling_rate"]) inputs, keys = sorted(inputs), sorted(list(result.keys())) assert inputs == keys, f"{inputs = } == {keys = }" # The result should be a simple dict, the preproc returns a wrapped class, dict() will remove it return dict(result) ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/dataset/prompts.py000066400000000000000000000117631510465702400317560ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseDataset from collections import namedtuple Prompt = namedtuple('Prompt', ['name', 'prompt', 'negative_prompt']) # source: https://huggingface.co/spaces/google/sdxl/blob/main/app.py#L19-L70 style_list = [ Prompt( name="Cinematic", prompt= "cinematic still, emotional, harmonious, vignette, highly detailed, high budget, bokeh, cinemascope, moody, epic, gorgeous, film grain, grainy", negative_prompt= "anime, cartoon, graphic, text, painting, crayon, graphite, abstract, glitch, deformed, mutated, ugly, disfigured", ), Prompt( name="Photographic", prompt= "cinematic photo, 35mm photograph, film, bokeh, professional, 4k, highly detailed", negative_prompt= "drawing, painting, crayon, sketch, graphite, impressionist, noisy, blurry, soft, deformed, ugly", ), Prompt( name="Anime", prompt= "anime artwork, anime style, key visual, vibrant, studio anime, highly detailed", negative_prompt= "photo, deformed, black and white, realism, disfigured, low contrast", ), Prompt( name="Manga", prompt= "manga style, vibrant, high-energy, detailed, iconic, Japanese comic style", negative_prompt= "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, Western comic style", ), Prompt( name="Digital Art", prompt= "concept art, digital artwork, illustrative, painterly, matte painting, highly detailed", negative_prompt="photo, photorealistic, realism, ugly", ), Prompt( name="Pixel art", prompt="pixel-art, low-res, blocky, pixel art style, 8-bit graphics", negative_prompt= "sloppy, messy, blurry, noisy, highly detailed, ultra textured, photo, realistic", ), Prompt( name="Fantasy art", prompt= "ethereal fantasy concept art, magnificent, celestial, ethereal, painterly, epic, majestic, magical, fantasy art, cover art, dreamy", negative_prompt= "photographic, realistic, realism, 35mm film, dslr, cropped, frame, text, deformed, glitch, noise, noisy, off-center, deformed, cross-eyed, closed eyes, bad anatomy, ugly, disfigured, sloppy, duplicate, mutated, black and white", ), Prompt( name="Neonpunk", prompt= "neonpunk style, cyberpunk, vaporwave, neon, vibes, vibrant, stunningly beautiful, crisp, detailed, sleek, ultramodern, magenta highlights, dark purple shadows, high contrast, cinematic, ultra detailed, intricate, professional", negative_prompt= "painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured", ), Prompt( name="3D Model", prompt= "professional 3d model, octane render, highly detailed, volumetric, dramatic lighting", negative_prompt="ugly, deformed, noisy, low poly, blurry, painting", ), ] class StylePrompts(BaseDataset): @property def url(self): return "" @property def split(self): return "" @staticmethod def name(): return "style-prompts" def __iter__(self): print(f"Load dataset for {self.name()}") self.dataset = iter(style_list) return self.dataset def __next__(self): return next(self.dataset) def transform(self, inputs, data, prepocess_fn): result = prepocess_fn(data.prompt, data.negative_prompt) inputs, keys = sorted(inputs), sorted(list(result.keys())) assert inputs == keys, f"{inputs = } == {keys = }" # The result should be a simple dict, the preproc returns a wrapped class, dict() will remove it return dict(result) ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/dataset/squad.py000066400000000000000000000061311510465702400313600ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseDataset, ValidationDatasetHFIteratorMixin from datasets import load_dataset class SQuADTransformMixin(object): def transform(self, inputs, data, prepocess_fn): result = prepocess_fn(data["question"], data["context"], max_length=384) inputs, keys = sorted(inputs), sorted(list(result.keys())) assert inputs == keys, f"{inputs = } == {keys = }" # The result should be a simple dict, the preproc returns a wrapped class, dict() will remove it return dict(result) class SQuADv1_1(SQuADTransformMixin, BaseDataset): @property def url(self): return "https://raw.githubusercontent.com/rajpurkar/SQuAD-explorer/master/dataset/dev-v1.1.json" @property def split(self): return "validation" @staticmethod def name(): return "squad-v1.1" def __iter__(self): print(f"Load dataset from {self.url}") self.dataset = ({ "context": paragraph["context"], "question": qas["question"] } for data in load_dataset("json", data_files={"val": self.url}, split="val", field="data", streaming=True) for paragraph in data["paragraphs"] for qas in paragraph["qas"]) return self.dataset def __next__(self): return next(self.dataset) class SQuAD_HF(ValidationDatasetHFIteratorMixin, SQuADTransformMixin, BaseDataset): @property def url(self): return "squad" @property def split(self): return "validation" @staticmethod def name(): return "squad-hf" ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/generator/000077500000000000000000000000001510465702400302315ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/generator/__init__.py000066400000000000000000000027201510465702400323430ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .generic import generate_test_dataset from .diffusion import generate_diffusion_data __all__ = ["generate_test_dataset", "generate_diffusion_data"] ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/generator/diffusion.py000066400000000000000000000152631510465702400326000ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### import os import onnxruntime as ort import numpy as np import torch from .generic import _inference from ..utils import get_model_io class DiffusionStage(object): def __init__(self, model_path): self.model_path = model_path self._initilize() def _initilize(self): self.inputs, self.outputs = get_model_io(self.model_path) self.session = ort.InferenceSession(self.model_path) self.folder_name_prefix = f"{os.path.dirname(self.model_path)}/test_data_set" def inference(self, input_data_map, test_idx): folder_name = f"{self.folder_name_prefix}_{test_idx}" output_data_map = _inference( self.session, input_data_map, self.outputs, folder_name, ) return output_data_map def generate_diffusion_data(model, image_dataset, prompt_dataset, output_folder_prefix=None, sample_limit=None, decode_limit=None): assert model.is_diffuser() == True, "Only diffuser supported" output_path = f"{output_folder_prefix or 'generated'}/diffusion/{model.name()}" try: model_paths = model.get_model(output_path) except Exception as e: print(f"Something went wrong:\n{e}\nSkipping model...") return is_xl = 'xl' in model.name() assert len(model_paths) == (4 + is_xl) text_encoder = DiffusionStage(model_paths[0]) text_encoder_2 = DiffusionStage(model_paths[1]) if is_xl else None vae_encoder = DiffusionStage(model_paths[1 + is_xl]) unet = DiffusionStage(model_paths[2 + is_xl]) vae_decoder = DiffusionStage(model_paths[3 + is_xl]) print('\n'.join([ f"Creating {stage.folder_name_prefix}s..." for stage in [text_encoder, text_encoder_2, vae_encoder, unet, vae_decoder] if stage is not None ])) test_idx = 0 scale = 7.0 seed = 42 # TODO: get it from latent size size = 128 if is_xl else 64 sample_shape = (1, 4, size, size) # TODO from config? vae_scaling_factor = 0.18215 noise = torch.randn(sample_shape, generator=torch.manual_seed(seed)) time_ids = np.array([[size * 8, size * 8, 0, 0, size * 8, size * 8]] * 2, dtype=np.float32) for idx, (image, prompt) in enumerate(zip(image_dataset, prompt_dataset)): text_encoder_input_data_map = prompt_dataset.transform( text_encoder.inputs, prompt, model.text_preprocess) text_encoder_output_data_map = text_encoder.inference( text_encoder_input_data_map, test_idx) if is_xl: text_encoder_2_input_data_map = prompt_dataset.transform( text_encoder_2.inputs, prompt, model.text_preprocess_2) text_encoder_2_output_data_map = text_encoder_2.inference( text_encoder_2_input_data_map, test_idx) text_encoder_output_data_map['last_hidden_state'] = np.concatenate( (text_encoder_output_data_map['last_hidden_state'], text_encoder_2_output_data_map['last_hidden_state']), axis=2) vae_encoder_input_data_map = image_dataset.transform( vae_encoder.inputs, image, model.image_preprocess) vae_encoder_output_data_map = vae_encoder.inference( vae_encoder_input_data_map, test_idx) model.scheduler.set_timesteps(decode_limit) latents = torch.from_numpy( vae_encoder_output_data_map["latent_sample"]) * vae_scaling_factor latents = model.scheduler.add_noise(latents, noise, model.scheduler.timesteps[:1]) for step, t in enumerate(model.scheduler.timesteps): latents_model_input = torch.concatenate([latents] * 2) latents_model_input = model.scheduler.scale_model_input( latents_model_input, t).numpy().astype(np.float32) timestep = np.atleast_1d(t.numpy().astype(np.int64)) unet_input_data_map = { 'sample': latents_model_input, 'encoder_hidden_states': text_encoder_output_data_map['last_hidden_state'], 'timestep': timestep } if is_xl: unet_input_data_map[ 'text_embeds'] = text_encoder_2_output_data_map[ 'text_embeds'] unet_input_data_map['time_ids'] = time_ids unet_output_data_map = unet.inference( unet_input_data_map, test_idx * decode_limit + step) noise_pred_text, noise_pred_uncond = np.array_split( unet_output_data_map['out_sample'], 2) noise_pred = noise_pred_uncond + scale * (noise_pred_text - noise_pred_uncond) # compute the previous noisy sample x_t -> x_t-1 latents = model.scheduler.step(torch.from_numpy(noise_pred), t, latents).prev_sample latents = 1 / vae_scaling_factor * latents vae_decoder_input_data_map = { "latent_sample": latents.numpy().astype(np.float32) } _ = vae_decoder.inference(vae_decoder_input_data_map, test_idx) test_idx += 1 if sample_limit and sample_limit - 1 <= idx: break ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/generator/generic.py000066400000000000000000000067461510465702400322340ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### import os import onnxruntime as ort from ..utils import get_model_io, numpy_to_pb def _inference(session, input_data_map, outputs, folder_name): input_pb_name = "input_{}.pb" output_pb_name = "output_{}.pb" os.makedirs(folder_name, exist_ok=True) for input_idx, (input_name, input_data) in enumerate(input_data_map.items()): numpy_to_pb(input_name, input_data, f"{folder_name}/{input_pb_name.format(input_idx)}") ort_result = session.run(outputs, input_data_map) output_data_map = { output_name: result_data for (output_name, result_data) in zip(outputs, ort_result) } for output_idx, (output_name, result_data) in enumerate(output_data_map.items()): numpy_to_pb(output_name, result_data, f"{folder_name}/{output_pb_name.format(output_idx)}") return output_data_map def generate_test_dataset(model, dataset, output_folder_prefix=None, sample_limit=None, decode_limit=None): assert model.is_diffuser() == False, "Only base and decoder supported" output_path = f"{output_folder_prefix or 'generated'}/{dataset.name()}/{model.name()}" folder_name_prefix = f"{output_path}/test_data_set" model_path = model.get_model(output_path) inputs, outputs = get_model_io(model_path) sess = ort.InferenceSession(model_path) print(f"Creating {folder_name_prefix}s...") test_idx = 0 for idx, data in enumerate(dataset): input_data_map = dataset.transform(inputs, data, model.preprocess) is_eos, decode_idx = False, 0 while not is_eos and not (decode_limit and decode_limit <= decode_idx): folder_name = f"{folder_name_prefix}_{test_idx}" output_data_map = _inference(sess, input_data_map, outputs, folder_name) is_eos = not model.is_decoder() or model.decode_step( input_data_map, output_data_map) test_idx += 1 decode_idx += 1 if sample_limit and sample_limit - 1 <= idx: break ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/000077500000000000000000000000001510465702400273435ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/__init__.py000066400000000000000000000025621510465702400314610ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### __all__ = ["audio", "image", "text", "hybrid", "diffusion"] ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/audio.py000066400000000000000000000067041510465702400310250ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseModel, DecoderModel, OptimumHFModelDownloadMixin import numpy as np from transformers import AutoFeatureExtractor class AutoFeatureExtractorHFMixin(object): _processor = None @property def processor(self): if self._processor is None: self._processor = AutoFeatureExtractor.from_pretrained( self.model_id) return self._processor def preprocess(self, audio_data, sampling_rate): return self.processor(audio_data, sampling_rate=sampling_rate, return_tensors="np") class Wav2Vec2_base_960h(OptimumHFModelDownloadMixin, AutoFeatureExtractorHFMixin, BaseModel): @property def model_id(self): return "facebook/wav2vec2-base-960h" @staticmethod def name(): return "wav2vec2-base-960h" class WhisperSmallEn(OptimumHFModelDownloadMixin, AutoFeatureExtractorHFMixin, DecoderModel): def __init__(self): # Whisper specific part # TODO get these from config self.eos_token_id = 50256 # "<|endoftext|>" decoder_start_token_id = 50257 # <|startoftranscript|> notimestamps = 50362 # <|notimestamps|> sot = [decoder_start_token_id, notimestamps] max_length = 448 self.initial_input_ids = np.array( [sot + [self.eos_token_id] * (max_length - len(sot))]) @property def model_id(self): return "openai/whisper-small.en" @staticmethod def name(): return "whisper-small-en" def preprocess(self, *args, **kwargs): # result only contains encoder data, extend it with decoder result = super().preprocess(*args, **kwargs) result["decoder_input_ids"] = np.copy(self.initial_input_ids) return result def decode_step(self, input_map, output_map): timestep = np.argmax( input_map["decoder_input_ids"][0] == self.eos_token_id) new_token = np.argmax(output_map["logits"][0][timestep - 1]) input_map["decoder_input_ids"][0][timestep] = new_token return new_token == self.eos_token_id ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/base.py000066400000000000000000000065611510465702400306370ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### import abc import os from optimum.exporters.onnx import main_export from ..utils import download class BaseModel(abc.ABC): @property @abc.abstractmethod def model_id(self): pass @staticmethod @abc.abstractmethod def name(): pass @property def task(self): return "auto" @abc.abstractmethod def get_model(self, folder, force_download): pass @abc.abstractmethod def preprocess(self, *args, **kwargs): pass def is_decoder(self): return False def is_diffuser(self): return False class DecoderModel(BaseModel): def is_decoder(self): return True @abc.abstractmethod def decode_step(self, *args, **kwargs): pass class DiffusionModel(BaseModel): def is_diffuser(self): return True @abc.abstractmethod def get_models(self, folder, models, force_download): pass @abc.abstractmethod def text_preprocess(self, *args, **kwargs): pass @abc.abstractmethod def text_preprocess_2(self, *args, **kwargs): pass @abc.abstractmethod def image_preprocess(self, *args, **kwargs): pass @abc.abstractmethod def scheduler(self, *args, **kwargs): pass class SingleModelDownloadMixin(object): def get_model(self, output_folder, force_download=False): filepath = f"{output_folder}/model.onnx" if force_download or not os.path.isfile(filepath): print(f"Download model from {self.model_id} to {filepath}") download(self.model_id, filepath) return filepath class OptimumHFModelDownloadMixin(object): def get_model(self, folder, force_download=False): filepath = f"{folder}/model.onnx" if force_download or not os.path.isfile(filepath): print(f"Download model from {self.model_id} to {filepath}") main_export( self.model_id, output=folder, monolith=True, # forces export into one model task=self.task) return filepath ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/diffusion.py000066400000000000000000000154761510465702400317200ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import DiffusionModel from transformers import AutoTokenizer from diffusers import EulerDiscreteScheduler from diffusers.image_processor import VaeImageProcessor from optimum.exporters.onnx import main_export import numpy as np import os from PIL import ImageOps class OptimumHFDiffusionModelDownloadMixin(object): def get_models(self, folder, models, force_download=False): filepaths = [f"{folder}/{model}/model.onnx" for model in models] if force_download or not all( os.path.isfile(filepath) for filepath in filepaths): print(f"Download model from {self.model_id} to {filepaths}") main_export( self.model_id, output=folder, monolith=True, # forces export into one model task=self.task) return filepaths class AutoTokenizerHFMixin(object): _tokenizer = None _tokenizer_2 = None @property def tokenizer(self): if self._tokenizer is None: self._tokenizer = AutoTokenizer.from_pretrained( self.model_id, subfolder="tokenizer") return self._tokenizer @property def tokenizer_2(self): if self._tokenizer_2 is None: self._tokenizer_2 = AutoTokenizer.from_pretrained( self.model_id, subfolder="tokenizer_2") return self._tokenizer_2 def text_preprocess(self, prompt, max_length=77, version_2=False): tokenizer = self.tokenizer_2 if version_2 else self.tokenizer return tokenizer(prompt, padding='max_length', max_length=max_length, return_tensors="np") class VAEImageProcessorHFMixin(object): _processor = None @property def processor(self): if self._processor is None: self._processor = VaeImageProcessor() return self._processor def image_preprocess(self, image_data): return self.processor.preprocess(image_data).numpy() class EulerDiscreteSchedulerHFMixin(object): _scheduler = None @property def scheduler(self): if self._scheduler is None: self._scheduler = EulerDiscreteScheduler.from_pretrained( self.model_id, subfolder="scheduler") return self._scheduler class StableDiffusion21(OptimumHFDiffusionModelDownloadMixin, AutoTokenizerHFMixin, VAEImageProcessorHFMixin, EulerDiscreteSchedulerHFMixin, DiffusionModel): def __init__(self): # it should be in pipeline exec order self.models = ('text_encoder', 'vae_encoder', 'unet', 'vae_decoder') @property def model_id(self): return "stabilityai/stable-diffusion-2-1" @staticmethod def name(): return "stable-diffusion-2-1" def get_model(self, folder, force_download=False): return super().get_models(folder, self.models, force_download) def preprocess(self, *args, **kwargs): raise RuntimeError("Call text_preprocess or image_preprocess directly") def text_preprocess(self, *args, **kwargs): prompt, negative_prompt = args prompt_result = super().text_preprocess(prompt, **kwargs) negative_prompt_result = super().text_preprocess( negative_prompt, **kwargs) result = { 'input_ids': np.concatenate( (prompt_result['input_ids'], negative_prompt_result['input_ids'])).astype(np.int32) } return result def text_preprocess_2(self, *args, **kwargs): raise RuntimeError("No tokenizer_2 for SD21 model") def image_preprocess(self, *args, **kwargs): resized_image = ImageOps.fit(args[0], (512, 512)) result = super().image_preprocess(resized_image, **kwargs) return {"sample": result} class StableDiffusionXL(OptimumHFDiffusionModelDownloadMixin, AutoTokenizerHFMixin, VAEImageProcessorHFMixin, EulerDiscreteSchedulerHFMixin, DiffusionModel): def __init__(self): # it should be in pipeline exec order self.models = ('text_encoder', 'text_encoder_2', 'vae_encoder', 'unet', 'vae_decoder') @property def model_id(self): return "stabilityai/stable-diffusion-xl-base-1.0" @staticmethod def name(): return "stable-diffusion-xl" def get_model(self, folder, force_download=False): return super().get_models(folder, self.models, force_download) def preprocess(self, *args, **kwargs): raise RuntimeError("Call text_preprocess or image_preprocess directly") def text_preprocess(self, *args, **kwargs): prompt, negative_prompt = args dtype = np.int64 if kwargs.get('version_2', False) else np.int32 prompt_result = super().text_preprocess(prompt, **kwargs) negative_prompt_result = super().text_preprocess( negative_prompt, **kwargs) result = { 'input_ids': np.concatenate((prompt_result['input_ids'], negative_prompt_result['input_ids'])).astype(dtype) } return result def text_preprocess_2(self, *args, **kwargs): new_kwargs = dict(kwargs) new_kwargs['version_2'] = True return self.text_preprocess(*args, **new_kwargs) def image_preprocess(self, *args, **kwargs): resized_image = ImageOps.fit(args[0], (1024, 1024)) result = super().image_preprocess(resized_image, **kwargs) return {"sample": result} ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/hybrid.py000066400000000000000000000053611510465702400312030ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseModel, OptimumHFModelDownloadMixin from ..utils import get_imagenet_classes from transformers import AutoProcessor class AutoProcessorHFMixin(object): _processor = None @property def processor(self): if self._processor is None: self._processor = AutoProcessor.from_pretrained(self.model_id) return self._processor def preprocess(self, images, text, max_length=384): return self.processor(images=images, text=text, max_length=max_length, padding='max_length', return_tensors="np") class ClipVitLargePatch14(OptimumHFModelDownloadMixin, AutoProcessorHFMixin, BaseModel): def __init__(self): import random random.seed(42) # cache a few labels, the full 1000 label is overkill self.imagenet_labels = random.sample(get_imagenet_classes(), 10) @property def model_id(self): return "openai/clip-vit-large-patch14" @staticmethod def name(): return "clip-vit-large-patch14" def preprocess(self, *args, **kwargs): # extend image with imagenet labels new_args, new_kwargs = list(args), kwargs new_args.append(self.imagenet_labels) new_kwargs["max_length"] = 77 result = super().preprocess(*new_args, **new_kwargs) return result ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/image.py000066400000000000000000000073041510465702400310030ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseModel, SingleModelDownloadMixin, OptimumHFModelDownloadMixin from .preprocess import process_image from transformers import AutoImageProcessor import timm class AutoImageProcessorHFMixin(object): _processor = None @property def processor(self): if self._processor is None: self._processor = AutoImageProcessor.from_pretrained(self.model_id) return self._processor def preprocess(self, image_data): return self.processor(image_data, return_tensors="np") class ResNet50_v1(SingleModelDownloadMixin, BaseModel): @property def model_id(self): return "https://zenodo.org/record/2592612/files/resnet50_v1.onnx" @staticmethod def name(): return "resnet50_v1" def preprocess(self, image_data): IMAGENET_MEANS = [123.68, 116.78, 103.94] # RGB return { "input_tensor:0": process_image(image_data, means=IMAGENET_MEANS) } class ResNet50_v1_5(OptimumHFModelDownloadMixin, AutoImageProcessorHFMixin, BaseModel): @property def model_id(self): return "microsoft/resnet-50" @staticmethod def name(): return "resnet50_v1.5" class VitBasePatch16_224(OptimumHFModelDownloadMixin, AutoImageProcessorHFMixin, BaseModel): @property def model_id(self): return "google/vit-base-patch16-224" @staticmethod def name(): return "vit-base-patch16-224" class TIMM_MobileNetv3_large(OptimumHFModelDownloadMixin, BaseModel): def __init__(self): data_config = timm.data.resolve_model_data_config( timm.create_model(self.model_id, pretrained=True)) self.processor = timm.data.create_transform(**data_config, is_training=False) @property def model_id(self): return "timm/mobilenetv3_large_100.ra_in1k" @staticmethod def name(): return "timm-mobilenetv3-large" def preprocess(self, image_data): return { "pixel_values": self.processor(image_data).unsqueeze(0).cpu().detach().numpy() } # TODO enable it when BiT is supported by optimum class Bit50(OptimumHFModelDownloadMixin, AutoImageProcessorHFMixin, BaseModel): @property def model_id(self): return "google/bit-50" @staticmethod def name(): return "bit-50" ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/preprocess.py000066400000000000000000000056521510465702400321120ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### import cv2 import numpy as np def process_image(pil_image, shape=(224, 224), means=None): # OpenCV default is BGR, the image is in RGB image = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR) # But we need it to be in RGB, not sure how to import and not convert image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) w, h = shape image = resize_with_aspectratio(image, h, w, inter_pol=cv2.INTER_AREA) image = center_crop(image, h, w) image = np.asarray(image, dtype="float32") # Normalize image. if means: means = np.array(means, dtype=np.float32) image -= means # RGB is channel_last (HWC), transpose to channel_first (CHW) image = image.transpose([2, 0, 1]) image = image[np.newaxis, :] return image def center_crop(img, out_height, out_width): height, width, _ = img.shape left = int((width - out_width) / 2) right = int((width + out_width) / 2) top = int((height - out_height) / 2) bottom = int((height + out_height) / 2) img = img[top:bottom, left:right] return img def resize_with_aspectratio(img, out_height, out_width, scale=87.5, inter_pol=cv2.INTER_LINEAR): height, width, _ = img.shape new_height = int(100.0 * out_height / scale) new_width = int(100.0 * out_width / scale) if height > width: w = new_width h = int(new_height * height / width) else: h = new_height w = int(new_width * width / height) img = cv2.resize(img, (w, h), interpolation=inter_pol) return img ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/model/text.py000066400000000000000000000216531510465702400307100ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .base import BaseModel, DecoderModel, OptimumHFModelDownloadMixin import numpy as np from transformers import AutoTokenizer class AutoTokenizerHFMixin(object): _processor = None @property def processor(self): if self._processor is None: self._processor = AutoTokenizer.from_pretrained(self.model_id) return self._processor def preprocess(self, question, context, max_length, truncation='only_second'): return self.processor(question, context, padding='max_length', max_length=max_length, truncation=truncation, return_tensors="np") class TextGenerationDecoderOnlyMixin(object): def __init__(self): # no pad token by default self.processor.pad_token = self.processor.eos_token def preprocess(self, *args, **kwargs): # swap squad's default "question - answer" order new_args, new_kwargs = list(args), kwargs new_args[0], new_args[1] = new_args[1], new_args[0] new_kwargs["truncation"] = "only_first" result = super().preprocess(*new_args, **new_kwargs) # result only contains "input_ids" and "attention_mask", extend it with "position_ids" result["position_ids"] = np.arange(0, len(result["input_ids"][0]), dtype=np.int64) result["position_ids"] = result["position_ids"][np.newaxis] return result def decode_step(self, input_map, output_map): timestep = np.argmax( input_map["input_ids"][0] == self.processor.eos_token_id) new_token = np.argmax(output_map["logits"][0][timestep - 1]) input_map["input_ids"][0][timestep] = new_token input_map["attention_mask"][0][timestep] = 1 return new_token == self.processor.eos_token_id class BERT_large_uncased(OptimumHFModelDownloadMixin, AutoTokenizerHFMixin, BaseModel): @property def model_id(self): return "google-bert/bert-large-uncased" @staticmethod def name(): return "bert-large-uncased" def preprocess(self, *args, **kwargs): # use squad's question for masking question, context = args # replace first word with [MASK] masked_question = question.replace(question[:question.index(' ')], '[MASK]', 1) # swap question-answer order new_args = [context, masked_question] result = super().preprocess(*new_args, **kwargs) return result class DistilBERT_base_cased_distilled_SQuAD(OptimumHFModelDownloadMixin, AutoTokenizerHFMixin, BaseModel): @property def model_id(self): return "distilbert/distilbert-base-cased-distilled-squad" @staticmethod def name(): return "distilbert-base-cased-distilled-squad" class RobertaBaseSquad2(OptimumHFModelDownloadMixin, AutoTokenizerHFMixin, BaseModel): @property def model_id(self): return "deepset/roberta-base-squad2" @staticmethod def name(): return "roberta-base-squad2" # Note: the inheritance order here important class GPTJ(OptimumHFModelDownloadMixin, TextGenerationDecoderOnlyMixin, AutoTokenizerHFMixin, DecoderModel): @property def model_id(self): return "EleutherAI/gpt-j-6b" @property def task(self): # override to ignore "with-past" return "text-generation" @staticmethod def name(): return "gpt-j" # Note: the inheritance order here important class Llama2_7b_chat_hf(OptimumHFModelDownloadMixin, TextGenerationDecoderOnlyMixin, AutoTokenizerHFMixin, DecoderModel): @property def model_id(self): return "meta-llama/Llama-2-7b-chat-hf" @property def task(self): # override to ignore "with-past" return "text-generation" @staticmethod def name(): return "llama2-7b-chat-hf" # Note: the inheritance order here important class Llama3_8b_instruct(OptimumHFModelDownloadMixin, TextGenerationDecoderOnlyMixin, AutoTokenizerHFMixin, DecoderModel): @property def model_id(self): return "meta-llama/Meta-Llama-3-8B-Instruct" @property def task(self): # override to ignore "with-past" return "text-generation" @staticmethod def name(): return "llama3-8b-instruct" class T5_base(OptimumHFModelDownloadMixin, AutoTokenizerHFMixin, DecoderModel): def __init__(self): max_length = 384 # default for squad # Note: no real start token, it requires the pad token self.initial_input_ids = np.array( [[self.processor.pad_token_id] + [self.processor.eos_token_id] * (max_length - 1)]) @property def model_id(self): return "google-t5/t5-base" @property def task(self): # override to ignore "with-past" return "text2text-generation" @staticmethod def name(): return "t5-base" def preprocess(self, *args, **kwargs): # only use squad's question new_args = ["translate English to French:", args[0]] result = super().preprocess(*new_args, **kwargs) result["decoder_input_ids"] = np.copy(self.initial_input_ids) return result def decode_step(self, input_map, output_map): timestep = np.argmax( input_map["decoder_input_ids"][0] == self.processor.eos_token_id) new_token = np.argmax(output_map["logits"][0][timestep - 1]) input_map["decoder_input_ids"][0][timestep] = new_token input_map["attention_mask"][0][timestep] = 1 return new_token == self.processor.eos_token_id class Gemma_2b_it(OptimumHFModelDownloadMixin, AutoTokenizerHFMixin, DecoderModel): @property def model_id(self): return "google/gemma-2b-it" @property def task(self): # override to ignore "with-past" return "text-generation" @staticmethod def name(): return "gemma-2b-it" def preprocess(self, *args, **kwargs): # swap squad's default "question - answer" order new_args, new_kwargs = list(args), kwargs new_args[0], new_args[1] = new_args[1], new_args[0] new_kwargs["truncation"] = "only_first" result = super().preprocess(*new_args, **new_kwargs) # Note: padding-side is left, pos ids will be 1,1,1,...,0,1,2,3,... result["position_ids"] = np.cumsum(result["attention_mask"][0], -1) - 1 result["position_ids"][:np.argmax(result["position_ids"] == 0)] = 1 result["position_ids"] = result["position_ids"][np.newaxis] return result def decode_step(self, input_map, output_map): # The result is in the last logits new_token = np.argmax(output_map["logits"][0][-1]) # Move everything left 1 step input_map["input_ids"][0] = np.roll(input_map["input_ids"][0], -1) input_map["input_ids"][0][-1] = new_token input_map["attention_mask"][0] = np.roll( input_map["attention_mask"][0], -1) input_map["attention_mask"][0][-1] = 1 input_map["position_ids"][0] = np.roll(input_map["position_ids"][0], -1) input_map["position_ids"][0][-1] += input_map["position_ids"][0][-2] return new_token == self.processor.eos_token_id ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/utils/000077500000000000000000000000001510465702400274035ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/utils/__init__.py000066400000000000000000000027211510465702400315160ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### from .utils import get_model_io, download, numpy_to_pb, get_imagenet_classes __all__ = ["get_model_io", "download", "numpy_to_pb", "get_imagenet_classes"] ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/sample_generator/utils/utils.py000066400000000000000000000054751510465702400311300ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2024 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### import onnx import logging import os import requests def get_model_io(model_path): model = onnx.load(model_path) outputs = [node.name for node in model.graph.output] input_all = [node.name for node in model.graph.input] input_initializer = [node.name for node in model.graph.initializer] inputs = list(set(input_all) - set(input_initializer)) return inputs, outputs def numpy_to_pb(name, np_data, out_filename): """Convert numpy data to a protobuf file.""" tensor = onnx.numpy_helper.from_array(np_data, name) onnx.save_tensor(tensor, out_filename) def download(url, filename, quiet=False): try: from tqdm import tqdm except: quiet = True logging.debug(f"Download {filename} from {url}") response = requests.get(url, stream=True) total_size_in_bytes = int(response.headers.get("content-length", 0)) block_size = 1024 if not quiet: progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True) os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, "wb") as f: for data in response.iter_content(block_size): if not quiet: progress_bar.update(len(data)) f.write(data) if not quiet: progress_bar.close() def get_imagenet_classes(): url = "https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt" response = requests.get(url) return response.text.split("\n") ROCm-AMDMIGraphX-46524e8/tools/model_zoo/test_generator/test_models.sh000077500000000000000000000101071510465702400255740ustar00rootroot00000000000000#!/bin/bash ##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. # ##################################################################################### set -e WORK_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" SCRIPT_PATH=$(dirname $(dirname $(dirname $(readlink -f "$0"))))/test_runner.py TESTER_SCRIPT="${TESTER:-$SCRIPT_PATH}" ATOL="${ATOL:-0.001}" RTOL="${RTOL:-0.001}" TARGET="${TARGET:-gpu}" if [[ "${DEBUG:-0}" -eq 1 ]]; then PIPE=/dev/stdout else PIPE=/dev/null fi if [[ "${VERBOSE:-0}" -eq 1 ]]; then set -x fi # Iterate through input recursively, process any onnx file function iterate() { local dir="$1" for file in "$dir"/*; do if [ -f "$file" ]; then if [[ $file = *.onnx ]]; then process "$file" fi fi if [ -d "$file" ]; then iterate "$file" fi done } # Test it with test_runner.py, both fp32 and fp16 function process() { local file="$1" echo "INFO: process $file started" test $file fp32 test $file fp16 echo "INFO: process $file finished" } # Run test_runner.py and log if something goes wrong function test() { local file="$1" echo "INFO: test $file ($2)" model_folder="$(dirname $file)" model_name="$(basename $model_folder)" # avoid name conflict for non-unique submodels if [[ "$model_folder" =~ "diffusion" ]]; then model_name="$(basename $(dirname $model_folder))_${model_name}" fi flag="--atol $ATOL --rtol $RTOL --target $TARGET" if [[ "$2" = "fp16" ]]; then flag="$flag --fp16" fi EXIT_CODE=0 python3 $TESTER_SCRIPT ${flag} ${model_folder}/ &> "$WORK_DIR/logs/$2/${model_name//\//_}.log" || EXIT_CODE=$? if [[ "${EXIT_CODE:-0}" -ne 0 ]]; then echo "WARNING: ${file} failed ($2)" fi } mkdir -p $WORK_DIR/logs/fp32/ $WORK_DIR/logs/fp16/ if [ -f $WORK_DIR/logs/Summary.log ]; then rm $WORK_DIR/logs/Summary.log fi touch $WORK_DIR/logs/Summary.log function iterateLogs() { local dir="$1" for file in "$dir"/*; do if [ -f "$file" ] && [[ "$file" != "Summary.log" ]]; then processLog "$file" fi if [ -d "$file" ]; then iterateLogs "$file" fi done } function processLog() { local file="$1" TEST_NAME=$(grep 'Running test "' "$file" | sed -E 's/Running test "(.*)" on.*/\1/') if [[ -z "$TEST_NAME" ]]; then return fi LOG_TYPE=$(basename "$(dirname "$file")"); TOTAL_CASES=$(grep "Test \"$TEST_NAME\" has" "$file" | sed -E 's/Test.*has ([0-9]+) cases:.*/\1/') PASSED_CASES=$(grep "Passed:" "$file" | sed -E 's/.*Passed: ([0-9]+).*/\1/') FAILED_CASES=$(grep "Failed:" "$file" | sed -E 's/.*Failed: ([0-9]+).*/\1/') echo -e "$TEST_NAME $LOG_TYPE\n Total number of cases = $TOTAL_CASES\n Passed=$PASSED_CASES\n Failed=$FAILED_CASES\n\n" >> "$WORK_DIR/logs/Summary.log" } iterateLogs "$WORK_DIR/logs/" for arg in "$@"; do iterate "$(dirname $(readlink -e $arg))/$(basename $arg)" done ROCm-AMDMIGraphX-46524e8/tools/ort/000077500000000000000000000000001510465702400165045ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/ort/README.md000066400000000000000000000025701510465702400177670ustar00rootroot00000000000000# ONNX Runtime driver The onnx runtime driver is a quick way to test onnx files via onnx runtime. ## Installation Instructions Use the command below to install dependencies: ``` pip install -r requirements.txt ``` ## Usage Example usage is below: ``` python ort_driver.py --onnx [path to onnx_file] ``` The output of the driver will be `"onnx runtime driver completed successfully."` if there are no errors. For detailed information, pass the `--verbose` flag to the command line. It shows information such as input parameters and output of onnx runtime. Random values are assigned to the model's inputs. However, they can be set to only contain 1s if the `--fill1` flag is passed in, or only 0s if the `--fill0` flag is passed in. This is potentially useful for verifying models such as bert which use integer datatypes. Onnx models may use dim values on their shapes to make their values dynamic. Use `--default-dim-value` them to a value other than 1. If the model has multiple dim values in the inputs, use `--input-dim` to specify the shape. Example syntax: ``` --input-dim input_ids:1,384 ``` Multiple inputs can be set by adding additional `--input-dim` flags. By default, the CPU Execution Provider is used when running onnx runtime. If building onnx runtime with a different version, specify the provider using `--provider`. For verbose onnx runtime logging, pass in `--ort-logging`. ROCm-AMDMIGraphX-46524e8/tools/ort/ort_driver.py000066400000000000000000000137721510465702400212470ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import argparse import numpy as np import onnxruntime as ort import sys def parse_args(): parser = argparse.ArgumentParser( description= 'ONNX Runtime python driver. Use to run onnx models using ONNX Runtime' ) file_args = parser.add_argument_group(title='file type arguments') file_args.add_argument('--onnx', type=str, help='path to onnx file') parser.add_argument('--provider', type=str, default='CPUExecutionProvider', help='execution provider for onnx runtime \ (default = CPUExecutionProvider)') parser.add_argument('--fill1', action='store_true', help='fill all arguments with a value of 1') parser.add_argument('--fill0', action='store_true', help='fill all arguments with a value of 0') parser.add_argument( '--default-dim-value', type=int, default=1, help='default dim value (if any specified in onnx file)') parser.add_argument('--verbose', action='store_true', help='show verbose information (for debugging)') parser.add_argument('--input-dim', type=str, action='append', help='specify input parameter dimension \ with the following format --input-dim input_name:dim0,dim1,dim2...' ) parser.add_argument('--ort-logging', dest="ort_logging", action='store_true', default=False, help='Turn on ort VERBOSE logging via session options') args = parser.parse_args() return args, parser def get_np_datatype(in_type): datatypes = { 'tensor(double)': np.float64, 'tensor(float)': np.float32, 'tensor(float16)': np.half, 'tensor(int64)': np.int64, 'tensor(uint64)': np.uint64, 'tensor(int32)': np.int32, 'tensor(uint32)': np.uint32, 'tensor(int16)': np.int16, 'tensor(uint16)': np.uint16, 'tensor(int8)': np.int8, 'tensor(uint8)': np.uint8, } return datatypes[in_type] def main(): args, parser = parse_args() if args.onnx == None: print('Error: please specify an onnx file') parser.print_help() sys.exit(-1) model_name = args.onnx custom_inputs = args.input_dim input_dims = {} if custom_inputs != None: for input in custom_inputs: input_dim = ''.join(input.split(':')[:-1]) dims = [int(dim) for dim in input.split(':')[-1].split(',')] input_dims[input_dim] = dims sess_op = ort.SessionOptions() if args.ort_logging: sess_op.log_verbosity_level = 0 sess_op.log_severity_level = 0 sess = ort.InferenceSession(model_name, sess_options=sess_op, providers=[args.provider]) test_inputs = {} for input in sess.get_inputs(): name = input.name in_shape = input.shape in_type = input.type for idx, dim in enumerate(in_shape): if not isinstance(dim, int): if args.verbose: print( f'''Dim param found at index {idx}: {dim}. ''' f'''Setting to default dim value of {args.default_dim_value}.''' ) in_shape[idx] = args.default_dim_value if name in input_dims: in_shape = input_dims[name] if args.verbose: print(f'Parameter {name} -> {in_shape}, {in_type}') if not args.fill1 and not args.fill0: test_input = np.random.rand(*(in_shape)).astype( get_np_datatype(in_type)) elif not args.fill0: test_input = np.ones(in_shape).astype(get_np_datatype(in_type)) else: test_input = np.zeros(in_shape).astype(get_np_datatype(in_type)) test_inputs[name] = test_input ort_params = {} for input in sess.get_inputs(): ort_params[input.name] = test_inputs[input.name] try: pred_ort = sess.run(None, ort_params) if args.verbose: for idx, output in enumerate(pred_ort): print(f'output at index {idx}: {output}') except Exception as e: if any(input_dims): print( 'Error: custom input dim may not be compatible with onnx runtime' ) raise e print('onnx runtime driver completed successfully.') if __name__ == '__main__': main() ROCm-AMDMIGraphX-46524e8/tools/ort/requirements.txt000066400000000000000000000026211510465702400217710ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### numpy==1.26.4;python_version>="3.11" numpy==1.21.6;python_version<"3.11" onnxruntime==1.17.3 ROCm-AMDMIGraphX-46524e8/tools/pai_provider_test_launcher.sh000077500000000000000000000033411510465702400236430ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### #!/bin/bash build_dir=${1:-"."} script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" echo "Warning: The following tests are EXCLUDED on PAI agent:" gtest_filter="-" while read line; do gtest_filter="$gtest_filter:$line" echo "$line" done <$script_dir/pai-excluded-tests.txt echo "" echo "Running ./onnxruntime_provider_test .." $build_dir/onnxruntime_provider_test --gtest_filter=$gtest_filter ROCm-AMDMIGraphX-46524e8/tools/pai_test_launcher.sh000077500000000000000000000033271510465702400217350ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### #!/bin/bash build_dir=${1:-"."} script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" echo "Warning: The following tests are EXCLUDED on PAI agent:" gtest_filter="-" while read line; do gtest_filter="$gtest_filter:$line" echo "$line" done <$script_dir/pai-excluded-tests.txt echo "" echo "Running ./onnxruntime_test_all .." $build_dir/onnxruntime_test_all --gtest_filter=$gtest_filter ROCm-AMDMIGraphX-46524e8/tools/requirements-py.txt000066400000000000000000000031311510465702400216100ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### onnx==1.17.0;python_version>="3.11" onnx==1.14.1;python_version<"3.11" numpy==1.26.4;python_version>="3.11" numpy==1.21.6;python_version<"3.11" typing==3.7.4 pytest==6.0.1 packaging==23.0 # pin version of protobuf in Python for onnx runtime unit tests between dist versions protobuf==4.25.8 ROCm-AMDMIGraphX-46524e8/tools/roctx.py000077500000000000000000000316231510465702400174210ustar00rootroot00000000000000#!/usr/bin/env python3 ##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import json import argparse import os from sys import argv as sysargs from sys import version_info as python_version from sys import exit as sys_exit import pandas as pd from datetime import datetime import venv import shutil if (python_version[0] < 3) or (python_version[0] < 3 and python_version[1] < 6): raise Exception("Please utilize Python version 3.6 and above. Exiting...") def parse_args(): parser = argparse.ArgumentParser( description="Parser for MIGraphX ROCTX Markers") parser.add_argument('--json-path', type=str, metavar='json-path', help='Path to json file') parser.add_argument('--out', type=str, metavar='out', help='Output directory for run.') parser.add_argument( '--study-name', type=str, metavar='study-name', help='Study-name is used for naming the output CSV file.') parser.add_argument('--repeat', type=int, metavar='repeat', help='Defines number of runs.', default=2) parser.add_argument('--parse', default=False, action='store_true', help='Parses given JSON file.') parser.add_argument('--clean', default=False, action='store_true', help='Removes temporary paths') parser.add_argument('--run', type=str, metavar='run', help='Enables run and fetches run configs.') parser.add_argument('--debug', default=False, action='store_true') args = parser.parse_args() return args args = parse_args() if not len(sysargs) > 1: raise Exception("No arg is passed. Exiting...") def parse(file): with open(file, "r") as read_file: data = json.load(read_file) #Get marker names and first marker's time list_names = [] first_marker = True first_marker_time = 0 for i in data: if (i): if ("Marker start:" in i['name']) and ( i['name'] not in list_names): list_names.append(i['name']) if first_marker: first_marker_time = i['ts'] first_marker = False if (args.debug): print(f"FIRST MARKER TIME DETERMINED: {first_marker_time}") if (first_marker_time == 0): raise ("FIRST MARKER TIME IS ZERO. EXITING...") kernel_launch_info = [] #kernel description kernel_launch_list = [] #kernel launch details kernel_launch_time = [] #kernel execution time for i in data: if (i and i.get('args')): try: if (("KernelExecution" in i['args']['desc']) and (i['ts'] >= first_marker_time)): kernel_launch_info.append(i['args']['desc']) kernel_launch_list.append(i) kernel_launch_time.append(int(i['dur'])) except: continue max_index = kernel_launch_time.index(max(kernel_launch_time)) max_kernel_info = kernel_launch_list[max_index] if (args.debug): with open('rocTX_kernel_launch_list.txt', 'w') as f: for i in kernel_launch_list: f.write(f'{i}') # Get timing information for each marker name list_times_per_names = [] for name in list_names: temp_list = [] for entry in data: if (entry) and ( name == entry['name'] ): # name can match on gpu or cpu side, for gpu, we need data from gpu markers. if (("gpu::" in name) and ("UserMarker frame:" in entry['args']['desc']) ): #gpu side information temp_list.append(int(entry.get('dur'))) elif (("gpu::" not in name) and ("Marker start:" in entry['args']['desc']) ): #cpu side information temp_list.append(int(entry.get('dur'))) list_times_per_names.append(temp_list) if (args.debug): print(list_times_per_names) sum_per_name = [] #TODO: refactor stat collection for list in list_times_per_names: sum_per_name.append(sum(list)) count_per_name = [] for list in list_times_per_names: try: count_per_name.append(len(list)) except: count_per_name.append(0) max_per_name = [] for list in list_times_per_names: try: max_per_name.append(max(list)) except: max_per_name.append(0) min_per_name = [] for list in list_times_per_names: try: min_per_name.append(min(list)) except: min_per_name.append(0) max_index_per_name = [] for list in list_times_per_names: try: max_index_per_name.append(list.index(max(list))) except: max_index_per_name.append(0) max_occur_per_name = [] for list in list_times_per_names: try: max_occur_per_name.append(list.count(max(list))) except: max_occur_per_name.append(0) total_time = sum(sum_per_name) d = { 'SUM': sum_per_name, 'MIN': min_per_name, 'MAX': max_per_name, 'COUNT': count_per_name, 'MAX_INDEX': max_index_per_name, 'MAX_OCCUR': max_occur_per_name } df2 = pd.DataFrame(d) df2.index = list_names df2.sort_values(by=['SUM'], inplace=True, ascending=False) if (args.debug): print(df2) print(f"\nTOTAL TIME: {total_time} us") return df2, total_time, max_kernel_info def run(): repeat_count = args.repeat if (repeat_count == 0 or repeat_count == float('inf') or not repeat_count): raise Exception("REPEAT COUNT CANNOT BE ZERO/INFINITY/NULL") run_args = args.run #configurations configs = '--hip-trace --roctx-trace --flush-rate 10ms --timestamp on' output_dir = f"-d {args.out}" executable = f"/opt/rocm/bin/migraphx-driver roctx {run_args}" process_args = configs + ' ' + output_dir + ' ' + executable for i in range(repeat_count): os.system('rocprof ' + process_args) print("RUN COMPLETE.") def clean(): shutil.rmtree('/tmp/rocm-profile-data/', ignore_errors=False) def main(): if (args.clean): clean() sys_exit() print("Initiating virtual environment...") builder = venv.EnvBuilder(clear=True, with_pip=True) builder.create('/tmp/rocm-profile-data/py/') python_bin = '/tmp/rocm-profile-data/py' + '/bin/python' file = args.json_path if (args.study_name): filename = args.study_name + ".csv" else: filename = "output" + datetime.now().strftime( "%Y_%m_%d-%I:%M:%S_%p") + ".csv" with open(filename, 'a') as f: f.write(f"{args.run}\n") if (args.run): curr = os.path.abspath(os.getcwd()) rpd_path = '/tmp/rocm-profile-data/rocmProfileData/' if not os.path.exists(rpd_path): print("rocmProfileData DOES NOT EXIST. CLONING...") os.system( f"git clone https://github.com/ROCmSoftwarePlatform/rocmProfileData.git {rpd_path}" ) os.chdir(rpd_path + "rocpd_python/") os.system(python_bin + ' -m pip install --upgrade pip') os.system(python_bin + ' setup.py install') os.chdir(curr) run() os.chdir(curr + f"/{args.out}/") out_path = os.popen(f"ls -td $PWD/*/*/ | head -{args.repeat}").read() print(f"\nFOLLOWING PATHS WILL BE PARSED:\n{out_path}") out_path = out_path.splitlines() df_tot = pd.DataFrame() tot_time = [] max_kernel_info_list = [] for path in out_path: path = path.strip('\n') print("\nPARSING OUTPUT PATH: " + path) os.chdir(path) os.system( f"{python_bin} -m rocpd.rocprofiler_import --ops_input_file hcc_ops_trace.txt --api_input_file hip_api_trace.txt --roctx_input_file roctx_trace.txt trace.rpd" ) os.system( f"{python_bin} {rpd_path}/rpd2tracing.py trace.rpd trace.json") os.chdir(curr) df, total_time, path_max_kernel_info = parse(path + "trace.json") max_kernel_info_list.append(path_max_kernel_info) tot_time.append(total_time) df_tot = pd.merge(df_tot, df, how='outer', left_index=True, right_index=True) if (args.debug): print("JSON FILE PATH: " + path + "trace.json") df_tot.to_csv("rocTX_runs_dataframe.csv") if (args.debug): print(df_tot) tmp_sum = df_tot.loc[:, df_tot.columns.str.contains('SUM')].astype(int) tmp_min = df_tot.loc[:, df_tot.columns.str.contains('MIN')].astype(int) tmp_max = df_tot.loc[:, df_tot.columns.str.match("^MAX_.$")].astype( int) tmp_count = df_tot.loc[:, df_tot.columns.str.match("COUNT")].astype( int) tmp_sum['SUM_avg'] = tmp_sum.mean(axis=1).astype(int) tmp_min['MIN_avg'] = tmp_min.mean(axis=1).astype(int) tmp_max['MAX_avg'] = tmp_max.mean(axis=1).astype(int) df2 = tmp_sum['SUM_avg'].copy() df2 = pd.merge(df2, tmp_min['MIN_avg'], how='outer', left_index=True, right_index=True) df2 = pd.merge(df2, tmp_max['MAX_avg'], how='outer', left_index=True, right_index=True) df2 = pd.merge(df2, tmp_count['COUNT_x'], how='outer', left_index=True, right_index=True) df2.rename(columns={'COUNT_x': 'COUNT'}, inplace=True) df2 = df2.loc[:, ~df2.columns.duplicated( )] #there will be many COUNT_x in df2 df2.sort_values(by=['SUM_avg'], inplace=True, ascending=False) if (args.debug): pd.set_option('display.max_columns', None) print(df_tot) #all data from all runs print("\n*** RESULTS ***") print(df2) out_time = sum(tot_time) / len(tot_time) print(f"\nAVG TOTAL TIME: {out_time} us\n") df2.to_csv(filename, mode='a') with open(filename, 'a') as f: f.write(f"AVG TOTAL TIME: {out_time} us\n") print(f"OUTPUT CSV FILE:\t{filename}") if (args.debug): #kernels that took the longest time printed for item in max_kernel_info_list: print(f"KERNEL NAME: {item['name']}\t\t{item['dur']}") with open('rocTX_kernel_timing_details.txt', 'w') as f: f.write( "MOST TIME CONSUMING KERNELS IN EACH ITERATION (EXPECTED TO BE SAME KERNEL):\n" ) for i in max_kernel_info_list: f.write(f"KERNEL NAME: {i['name']}\t\t{i['dur']}\n") print("KERNEL TIMING DETAILS:\trocTX_kernel_timing_details.txt") print("ALL DATA FROM ALL RUNS:\trocTX_runs_dataframe.csv") elif (args.parse): if not (file): raise Exception("JSON PATH IS NOT PROVIDED FOR PARSING.") parse(file) else: raise Exception("PLEASE PROVIDE A COMMAND: RUN, PARSE, CLEAN") if __name__ == "__main__": main() ROCm-AMDMIGraphX-46524e8/tools/stamp_status.py000066400000000000000000000070171510465702400210060ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### from enum import Enum, auto import datetime import re class StampStatus(Enum): """ License Stamp Status """ OK = auto(), "Stamped correctly", False WRONG_YEAR = auto(), "Wrong year in license", True NOT_STAMPED = auto(), "Missing license stamp", True IGNORED = auto(), "Intentionally ignored", False UNSUPPORTED = auto(), "Unsupported file type", False ERROR_READING = auto(), "File read error", False OTHER_LICENSE = auto(), "Other software license", False def __init__(self, *_args): self._value_ = _args[0] self.label = _args[1] self.error = _args[2] self.files = [] def stamp_check(file, year, content, debug=False): """ Determine license stamp status for a given file's content. Returns: StampStatus: OK, WRONG_YEAR, NOT_STAMPED, or OTHER_LICENSE. """ if "Advanced Micro Devices, Inc. All rights reserved" in content: year_pattern = re.compile( rf"Copyright \(c\) (?:\d{{4}}\s*-\s*)?{year} Advanced Micro Devices" ) if year_pattern.search(content): return StampStatus.OK if debug: print(f"{file}: Wrong year") return StampStatus.WRONG_YEAR if "Software License" in content: return StampStatus.OTHER_LICENSE return StampStatus.NOT_STAMPED def current_year(): """ Get the current year based on the system clock. Returns: int: Current year in YYYY format. """ return datetime.datetime.now().date().year def update_year(filename: str, year: int): """ Update the license year in the specified file to match the latest Git commit year. Args: filename (str): File whose license year needs to be updated. year (int): Year to update the license to. """ with open(filename, 'r', encoding='utf-8') as f: content = f.read() def replacer(match): start_year = match.group(1) return f"Copyright (c) {start_year}-{year} Advanced Micro Devices" new_content = re.sub( r"Copyright \(c\) (\d{4})(?:\s*-\s*\d{4})? Advanced Micro Devices", replacer, content) with open(filename, 'w', encoding='utf-8') as f: f.write(new_content) ROCm-AMDMIGraphX-46524e8/tools/syntax/000077500000000000000000000000001510465702400172265ustar00rootroot00000000000000ROCm-AMDMIGraphX-46524e8/tools/syntax/migx.vim000066400000000000000000000022051510465702400207060ustar00rootroot00000000000000" Vim syntax file " Language: MIGraphX Intermediate Representation " Current Maintainer: Charlie Lin (https://github.com/CharlieL7) " Previous Maintainer: " Last Change: 20 Feb 2024 " quit when a syntax file was already loaded if exists("b:current_syntax") finish endif syn keyword migx_keyword param syn keyword migx_keyword return syn keyword migx_keyword target_id syn keyword migx_keyword literal syn match migx_ins_number "@\d\+" syn match migx_instruction "\s=\s\zs.\+\ze\[" syn match migx_instruction_and_attributes "\s=\s\zs.\+\ze(" contains=migx_attributes syn region migx_attributes start="\[" end="\]" contains=migx_keyword syn match migx_output_type "\s->\s\zs.\{-}\ze," nextgroup=migx_output_dims syn match migx_output_dims "\s\zs{.\{-}}\ze,\starget_id" syn match migx_exec_time ":\s\zs\d\{-}\.\d\{-}ms\ze" let b:current_syntax = "migx" hi def link migx_keyword NonText hi def link migx_ins_number Number hi def link migx_instruction Operator hi def link migx_instruction_and_attributes Operator hi def link migx_attributes Comment hi def link migx_output_type Type hi def link migx_output_dims Normal hi def link migx_exec_time NonText ROCm-AMDMIGraphX-46524e8/tools/te.py000066400000000000000000000413361510465702400166710ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import string, sys, re trivial = [ 'std::size_t', 'instruction_ref', 'support_metric', 'const_module_ref', 'bool', 'any_ptr' ] export_macro = 'MIGRAPHX_EXPORT' headers = ''' #include #include #include #include #include #include ''' form = string.Template(''' #ifdef TYPE_ERASED_DECLARATION // Type-erased interface for: struct ${export_macro} ${struct_name} { ${decl_members} }; #else // NOLINTBEGIN(performance-unnecessary-value-param) struct ${struct_name} { private: ${default_members} template struct private_te_unwrap_reference { using type = PrivateDetailTypeErasedT; }; template struct private_te_unwrap_reference> { using type = PrivateDetailTypeErasedT; }; template using private_te_pure = typename std::remove_cv::type>::type; template using private_te_constraints_impl = decltype(${constraint_members}, void()); template using private_te_constraints = private_te_constraints_impl>::type>; public: // Constructors ${struct_name} () = default; template , typename = typename std::enable_if, ${struct_name}>{}>::type> ${struct_name} (PrivateDetailTypeErasedT&& value) : private_detail_te_handle_mem_var ( std::make_shared< private_detail_te_handle_type> >(std::forward(value)) ) {} // Assignment template , typename = typename std::enable_if, ${struct_name}>{}>::type> ${struct_name} & operator= (PrivateDetailTypeErasedT&& value) { using std::swap; auto * derived = this->any_cast>(); if(derived and private_detail_te_handle_mem_var.use_count() == 1) { *derived = std::forward(value); } else { ${struct_name} rhs(value); swap(private_detail_te_handle_mem_var, rhs.private_detail_te_handle_mem_var); } return *this; } // Cast template PrivateDetailTypeErasedT * any_cast() { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type> &>(private_detail_te_get_handle()).private_detail_te_value) : nullptr; } template const typename std::remove_cv::type * any_cast() const { return this->type_id() == typeid(PrivateDetailTypeErasedT) ? std::addressof(static_cast::type> &>(private_detail_te_get_handle()).private_detail_te_value) : nullptr; } const std::type_info& type_id() const { if(private_detail_te_handle_empty()) return typeid(std::nullptr_t); else return private_detail_te_get_handle().type(); } ${nonvirtual_members} friend bool is_shared(const ${struct_name} & private_detail_x, const ${struct_name} & private_detail_y) { return private_detail_x.private_detail_te_handle_mem_var == private_detail_y.private_detail_te_handle_mem_var; } private: struct private_detail_te_handle_base_type { virtual ~private_detail_te_handle_base_type () {} virtual std::shared_ptr clone () const = 0; virtual const std::type_info& type() const = 0; ${pure_virtual_members} }; template struct private_detail_te_handle_type : private_detail_te_handle_base_type { template private_detail_te_handle_type (PrivateDetailTypeErasedT value, typename std::enable_if< std::is_reference::value >::type * = nullptr) : private_detail_te_value (value) {} template private_detail_te_handle_type (PrivateDetailTypeErasedT value, typename std::enable_if< not std::is_reference::value, int >::type * = nullptr) noexcept : private_detail_te_value (std::move(value)) {} std::shared_ptr clone () const override { return std::make_shared(private_detail_te_value); } const std::type_info& type() const override { return typeid(private_detail_te_value); } ${virtual_members} PrivateDetailTypeErasedT private_detail_te_value; }; template struct private_detail_te_handle_type> : private_detail_te_handle_type { private_detail_te_handle_type (std::reference_wrapper ref) : private_detail_te_handle_type (ref.get()) {} }; bool private_detail_te_handle_empty() const { return private_detail_te_handle_mem_var == nullptr; } const private_detail_te_handle_base_type & private_detail_te_get_handle () const { assert(private_detail_te_handle_mem_var != nullptr); return *private_detail_te_handle_mem_var; } private_detail_te_handle_base_type & private_detail_te_get_handle () { assert(private_detail_te_handle_mem_var != nullptr); if (private_detail_te_handle_mem_var.use_count() > 1) private_detail_te_handle_mem_var = private_detail_te_handle_mem_var->clone(); return *private_detail_te_handle_mem_var; } std::shared_ptr private_detail_te_handle_mem_var; }; template inline const ValueType * any_cast(const ${struct_name} * x) { return x->any_cast(); } template inline ValueType * any_cast(${struct_name} * x) { return x->any_cast(); } template inline ValueType & any_cast(${struct_name} & x) { auto * y = x.any_cast::type>(); if (y == nullptr) throw std::bad_cast(); return *y; } template inline const ValueType & any_cast(const ${struct_name} & x) { const auto * y = x.any_cast::type>(); if (y == nullptr) throw std::bad_cast(); return *y; } // NOLINTEND(performance-unnecessary-value-param) #endif ''') nonvirtual_member = string.Template(''' ${friend} ${return_type} ${name}(${params}) ${const} { assert(${this}.private_detail_te_handle_mem_var); ${return_} ${this}.private_detail_te_get_handle().${internal_name}(${member_args}); } ''') pure_virtual_member = string.Template( "virtual ${return_type} ${internal_name}(${member_params}) ${member_const} = 0;\n" ) virtual_member = string.Template(''' ${return_type} ${internal_name}(${member_params}) ${member_const} override { ${using} ${return_} ${call}; } ''') comment_member = string.Template( '''* ${friend} ${return_type} ${name}(${params}) ${const};''') decl_member = string.Template(''' ${comment} ${friend} ${return_type} ${name}(${params}) ${const}; ''') default_member = string.Template(''' template static auto private_detail_te_default_${name}(char, T&& private_detail_te_self ${comma} ${member_params}) -> decltype(private_detail_te_self.${name}(${args})) { ${return_} private_detail_te_self.${name}(${args}); } template static ${return_type} private_detail_te_default_${internal_name}(float, T&& private_detail_te_self ${comma} ${member_params}) { ${return_} ${default}(private_detail_te_self ${comma} ${args}); } ''') def trim_type_name(name): n = name.strip() if n.startswith('const'): return trim_type_name(n[5:]) if n.endswith(('&', '*')): return trim_type_name(n[0:-1]) return n def internal_name(name): internal_names = { 'operator<<': 'operator_shift_left', 'operator>>': 'operator_shift_right', } if name in internal_names: return internal_names[name] else: return name def generate_constraint(m, friend, indirect): if m['name'].startswith('operator'): return None if friend: return None if indirect: return string.Template( 'private_detail_te_default_${internal_name}(char(0), std::declval() ${comma} ${param_constraints})' ).substitute(m) return string.Template( 'std::declval().${name}(${param_constraints})' ).substitute(m) def generate_call(m, friend, indirect): if m['name'].startswith('operator'): op = m['name'][8:] args = m['args'] if ',' in args: return args.replace(',', op) else: return string.Template('${op}${args}').substitute(op=op, args=args) if friend: return string.Template('${name}(${args})').substitute(m) if indirect: return string.Template( 'private_detail_te_default_${internal_name}(char(0), private_detail_te_value ${comma} ${args})' ).substitute(m) return string.Template( 'private_detail_te_value.${name}(${args})').substitute(m) def convert_member(d, struct_name): for name in d: member = { 'name': name, 'internal_name': internal_name(name), 'const': '', 'member_const': '', 'friend': '', 'this': '(*this)', 'using': '', 'brief': '', 'return_': '', 'comment': '// ' } args = [] params = [] param_constraints = [] member_args = [] member_params = [] skip = False friend = False indirect = False if 'friend' in d[name]: friend = True skip = True if 'default' in d[name]: indirect = True for x in d[name]: t = d[name][x] if x == 'return': member['return_type'] = t if t else 'void' if member['return_type'] != 'void': member['return_'] = 'return' elif x == 'const': member['const'] = 'const' member['member_const'] = 'const' elif x == 'friend': member['friend'] = 'friend' elif x == 'default': member['default'] = t member['comment'] = member['comment'] + '(optional)' elif x == 'using': member['using'] = 'using {};'.format(d[name]['using']) elif x == '__brief__': member['doc'] = '/// ' + t elif x.startswith('__') and x.endswith('__'): continue else: use_member = not (skip and struct_name == trim_type_name(t)) arg_name = x if not use_member: arg_name = 'private_detail_te_value' member['this'] = x if 'const' in t: member['member_const'] = 'const' if t.endswith(('&', '*')) or t in trivial: if use_member: member_args.append(x) args.append(arg_name) else: if use_member: member_args.append('std::move({})'.format(x)) args.append('std::move({})'.format(arg_name)) params.append(t + ' ' + x) param_constraints.append('std::declval<{}>()'.format(t)) if use_member: member_params.append(t + ' ' + x) else: skip = False member['args'] = ','.join(args) member['member_args'] = ','.join(member_args) member['params'] = ','.join(params) member['params'] = ','.join(params) member['member_params'] = ','.join(member_params) member['param_constraints'] = ','.join(param_constraints) member['comma'] = ',' if len(args) > 0 else '' member['call'] = generate_call(member, friend, indirect) member['constraint'] = generate_constraint(member, friend, indirect) return member return None def generate_form(name, members): nonvirtual_members = [] pure_virtual_members = [] virtual_members = [] comment_members = [] default_members = [] decl_members = [] constraint_members = [] for member in members: m = convert_member(member, name) nonvirtual_members.append(nonvirtual_member.substitute(m)) pure_virtual_members.append(pure_virtual_member.substitute(m)) virtual_members.append(virtual_member.substitute(m)) comment_members.append(comment_member.substitute(m)) decl_members.append(decl_member.substitute(m)) m_constraint = m['constraint'] if m_constraint: constraint_members.append(m_constraint) if 'default' in m: default_members.append(default_member.substitute(m)) return form.substitute(nonvirtual_members=''.join(nonvirtual_members), pure_virtual_members=''.join(pure_virtual_members), virtual_members=''.join(virtual_members), default_members=''.join(default_members), decl_members=''.join(decl_members), constraint_members=','.join(constraint_members), comment_members='\n'.join(comment_members), struct_name=name, export_macro=export_macro) def virtual(name, returns=None, **kwargs): args = kwargs args['return'] = returns return {name: args} def friend(name, returns=None, **kwargs): args = kwargs args['return'] = returns args['friend'] = 'friend' return {name: args} def interface(name, *members): return generate_form(name, members) def template_eval(template, **kwargs): start = '<%' end = '%>' escaped = (re.escape(start), re.escape(end)) mark = re.compile('%s(.*?)%s' % escaped, re.DOTALL) for key in kwargs: exec('%s = %s' % (key, kwargs[key])) for item in mark.findall(template): template = template.replace(start + item + end, str(eval(item.strip()))) return template def run(p): return template_eval(open(p).read()) if __name__ == '__main__': sys.stdout.write(run(sys.argv[1])) ROCm-AMDMIGraphX-46524e8/tools/test_runner.py000066400000000000000000000232521510465702400206260ustar00rootroot00000000000000##################################################################################### # The MIT License (MIT) # # Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All rights reserved. # # 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. ##################################################################################### import os, sys import numpy as np import argparse import onnx from onnx import numpy_helper import migraphx def parse_args(): parser = argparse.ArgumentParser(description="MIGraphX test runner") parser.add_argument('test_dir', type=str, metavar='test_loc', help='folder where the test is stored') parser.add_argument('--target', type=str, default='gpu', help='Specify where the tests execute (ref, gpu)') parser.add_argument('--fp16', action='store_true', help='Quantize to fp16') parser.add_argument('--atol', type=float, default=1e-3, help='The absolute tolerance parameter') parser.add_argument('--rtol', type=float, default=1e-3, help='The relative tolerance parameter') args = parser.parse_args() return args def get_sub_folders(dir_name): dir_contents = os.listdir(dir_name) folders = [] for item in dir_contents: tmp_item = dir_name + '/' + item if os.path.isdir(tmp_item): folders.append(item) folders.sort() return folders def get_test_cases(dir_name): return get_sub_folders(dir_name) def get_model_name(dir_name): dir_contents = os.listdir(dir_name) for item in dir_contents: file_name = dir_name + '/' + item if os.path.isfile(file_name) and file_name.endswith('.onnx'): return item return '' def read_pb_file(filename): with open(filename, 'rb') as pfile: data_str = pfile.read() tensor = onnx.TensorProto() tensor.ParseFromString(data_str) np_array = numpy_helper.to_array(tensor) return tensor.name, np_array def wrapup_inputs(io_folder, param_names): param_map = {} data_array = [] name_array = [] for i in range(len(param_names)): file_name = io_folder + '/input_' + str(i) + '.pb' name, data = read_pb_file(file_name) param_map[name] = data data_array.append(data) if name: name_array.append(name) if len(name_array) < len(data_array): param_map = {} for i in range(len(param_names)): param_map[param_names[i]] = data_array[i] return param_map for name in param_names: if not name in param_map.keys(): print("Input {} does not exist!".format(name)) sys.exit() return param_map def read_outputs(io_folder, out_names): outputs = [] data_array = [] name_array = [] for i in range(len(out_names)): file_name = io_folder + '/output_' + str(i) + '.pb' name, data = read_pb_file(file_name) data_array.append(data) if name: name_array.append(name) if len(name_array) < len(data_array): return data_array for name in out_names: index = name_array.index(name) outputs.append(data_array[index]) return outputs def model_parameter_names(model_file_name): with open(model_file_name, 'rb') as pfile: data_str = pfile.read() model_proto = onnx.ModelProto() model_proto.ParseFromString(data_str) init_names = set([(i.name) for i in model_proto.graph.initializer]) param_names = [ input.name for input in model_proto.graph.input if input.name not in init_names ] return param_names def model_output_names(model_file_name): with open(model_file_name, 'rb') as pfile: data_str = pfile.read() model_proto = onnx.ModelProto() model_proto.ParseFromString(data_str) output_names = [out.name for out in model_proto.graph.output] return output_names def get_input_shapes(sample_case, param_names): param_shape_map = {} name_array = [] shape_array = [] for i in range(len(param_names)): file_name = sample_case + '/input_' + str(i) + '.pb' name, data = read_pb_file(file_name) param_shape_map[name] = data.shape shape_array.append(data.shape) if name: name_array.append(name) if len(name_array) < len(shape_array): param_shape_map = {} for i in range(len(param_names)): param_shape_map[param_names[i]] = shape_array[i] return param_shape_map for name in param_names: if not name in param_shape_map: print("Input {} does not exist!".format(name)) sys.exit() return param_shape_map def run_one_case(model, param_map): # convert np array to model argument pp = {} for key, val in param_map.items(): pp[key] = migraphx.argument(val) # run the model model_outputs = model.run(param_map) # convert argument to np array outputs = [] for output in model_outputs: outputs.append(np.array(output)) return outputs def check_correctness(gold_outputs, outputs, rtol=1e-3, atol=1e-3): if len(gold_outputs) != len(outputs): print("Number of outputs {} is not equal to expected number {}".format( len(outputs), len(gold_outputs))) return False out_num = len(gold_outputs) ret = True for i in range(out_num): if not np.allclose(gold_outputs[i], outputs[i], rtol, atol): print("\nOutput {} is incorrect ...".format(i)) print("Expected value: \n{}".format(gold_outputs[i])) print("......") print("Actual value: \n{}\n".format(outputs[i])) ret = False return ret def tune_input_shape(model, input_data): param_shapes = model.get_parameter_shapes() input_shapes = {} for name, s in param_shapes.items(): assert name in input_data data_shape = list(input_data[name].shape) if not np.array_equal(data_shape, s.lens()): input_shapes[name] = data_shape return input_shapes def main(): args = parse_args() test_loc = args.test_dir target = args.target test_name = os.path.basename(os.path.normpath(test_loc)) print("Running test \"{}\" on target \"{}\" ...\n".format( test_name, target)) # get model full path model_name = get_model_name(test_loc) model_path_name = test_loc + '/' + model_name # get param names param_names = model_parameter_names(model_path_name) # get output names output_names = model_output_names(model_path_name) # get test cases cases = get_test_cases(test_loc) sample_case = test_loc + '/' + cases[0] param_shapes = get_input_shapes(sample_case, param_names) for name, dims in param_shapes.items(): print("Input: {}, shape: {}".format(name, dims)) print() # read and compile model model = migraphx.parse_onnx(model_path_name, map_input_dims=param_shapes) if args.fp16: migraphx.quantize_fp16(model) model.compile(migraphx.get_target(target)) # get test cases case_num = len(cases) correct_num = 0 for case_name in cases: io_folder = test_loc + '/' + case_name input_data = wrapup_inputs(io_folder, param_names) gold_outputs = read_outputs(io_folder, output_names) # if input shape is different from model shape, reload and recompile # model input_shapes = tune_input_shape(model, input_data) if not len(input_shapes) == 0: model = migraphx.parse_onnx(model_path_name, map_input_dims=input_shapes) model.compile(migraphx.get_target(target)) # run the model and return outputs output_data = run_one_case(model, input_data) # check output correctness ret = check_correctness(gold_outputs, output_data, atol=args.atol, rtol=args.rtol) if ret: correct_num += 1 output_str = "PASSED" if ret else "FAILED" print("\tCase {}: {}".format(case_name, output_str)) print("\nTest \"{}\" has {} cases:".format(test_name, case_num)) print("\t Passed: {}".format(correct_num)) print("\t Failed: {}".format(case_num - correct_num)) if case_num > correct_num: error_num = case_num - correct_num print(str(error_num) + " cases failed!") sys.exit(1) if __name__ == "__main__": main()